From c638d8c30f66be88715e3c0824b5be2ccf490d27 Mon Sep 17 00:00:00 2001 From: Greg Dodd Date: Wed, 7 Aug 2019 12:30:15 +1000 Subject: [PATCH 001/662] Default @RestoreDiff = NULL If @RestoreDiff IS NULL and @BackupPathDiff != null: treat RestoreDiff=1 --- sp_DatabaseRestore.sql | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/sp_DatabaseRestore.sql b/sp_DatabaseRestore.sql index 3ced91721..94fd7a777 100755 --- a/sp_DatabaseRestore.sql +++ b/sp_DatabaseRestore.sql @@ -13,7 +13,7 @@ ALTER PROCEDURE [dbo].[sp_DatabaseRestore] @MoveFilestreamDrive NVARCHAR(260) = NULL, @TestRestore BIT = 0, @RunCheckDB BIT = 0, - @RestoreDiff BIT = 0, + @RestoreDiff BIT = NULL, @ContinueLogs BIT = 0, @StandbyMode BIT = 0, @StandbyUndoPath NVARCHAR(MAX) = NULL, @@ -729,6 +729,11 @@ END; IF @BackupPathDiff IS NOT NULL BEGIN + IF @RestoreDiff IS NULL + BEGIN + SET @RestoreDiff = 1 + END + DELETE FROM @FileList; DELETE FROM @FileListSimple; IF @SimpleFolderEnumeration = 1 From e3ab09752f1f06e1a4ef8c8b089c29829910c9e9 Mon Sep 17 00:00:00 2001 From: Greg Dodd Date: Mon, 12 Aug 2019 08:13:06 +1000 Subject: [PATCH 002/662] Set database owner after restore --- sp_DatabaseRestore.sql | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/sp_DatabaseRestore.sql b/sp_DatabaseRestore.sql index 94fd7a777..86fe950eb 100755 --- a/sp_DatabaseRestore.sql +++ b/sp_DatabaseRestore.sql @@ -23,6 +23,7 @@ ALTER PROCEDURE [dbo].[sp_DatabaseRestore] @StopAt NVARCHAR(14) = NULL, @OnlyLogsAfter NVARCHAR(14) = NULL, @SimpleFolderEnumeration BIT = 0, + @DatabaseOwner SYSNAME = NULL, @Execute CHAR(1) = Y, @Debug INT = 0, @Help BIT = 0, @@ -1118,6 +1119,28 @@ IF @RunCheckDB = 1 EXECUTE @sql = [dbo].[CommandExecute] @Command = @sql, @CommandType = 'INTEGRITY CHECK', @Mode = 1, @DatabaseName = @Database, @LogToTable = 'Y', @Execute = 'Y'; END; + +IF @DatabaseOwner IS NOT NULL + BEGIN + IF EXISTS (SELECT * FROM master.dbo.syslogins WHERE syslogins.loginname = @DatabaseOwner) + BEGIN + SET @sql = N'ALTER AUTHORIZATION ON DATABASE::' + @RestoreDatabaseName + ' TO [' + @databaseowner + ']'; + + IF @Debug = 1 OR @Execute = 'N' + BEGIN + IF @sql IS NULL PRINT '@sql is NULL for Set Database Owner'; + PRINT @sql; + END; + + IF @Debug IN (0, 1) AND @Execute = 'Y' + EXECUTE (@sql); + END + ELSE + BEGIN + PRINT @DatabaseOwner + ' is not a valid Login. Database Owner not set.' + END + END; + -- If test restore then blow the database away (be careful) IF @TestRestore = 1 BEGIN From ff37902b66d59f91c968846dcbcdc0c4bbaae9a3 Mon Sep 17 00:00:00 2001 From: Greg Dodd Date: Mon, 12 Aug 2019 08:21:56 +1000 Subject: [PATCH 003/662] Revert restorediff change from dev branch --- sp_DatabaseRestore.sql | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/sp_DatabaseRestore.sql b/sp_DatabaseRestore.sql index 86fe950eb..6c976d5ca 100755 --- a/sp_DatabaseRestore.sql +++ b/sp_DatabaseRestore.sql @@ -13,7 +13,7 @@ ALTER PROCEDURE [dbo].[sp_DatabaseRestore] @MoveFilestreamDrive NVARCHAR(260) = NULL, @TestRestore BIT = 0, @RunCheckDB BIT = 0, - @RestoreDiff BIT = NULL, + @RestoreDiff BIT = 0, @ContinueLogs BIT = 0, @StandbyMode BIT = 0, @StandbyUndoPath NVARCHAR(MAX) = NULL, @@ -730,11 +730,6 @@ END; IF @BackupPathDiff IS NOT NULL BEGIN - IF @RestoreDiff IS NULL - BEGIN - SET @RestoreDiff = 1 - END - DELETE FROM @FileList; DELETE FROM @FileListSimple; IF @SimpleFolderEnumeration = 1 From a39e7a94eabbfe2d79b54b76a84b9921566ff924 Mon Sep 17 00:00:00 2001 From: EmanueleMeazzo Date: Sun, 1 Sep 2019 23:36:29 +0200 Subject: [PATCH 004/662] Added Uninstaller Script Fulfills request #2080 Deletes all the FirstResponderKit procedures from the current DB or all DBs via parameter --- Uninstall.sql | 78 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 Uninstall.sql diff --git a/Uninstall.sql b/Uninstall.sql new file mode 100644 index 000000000..d8dfde8d6 --- /dev/null +++ b/Uninstall.sql @@ -0,0 +1,78 @@ +--First Responder Kit Uninstaller Script + +--Configuration Parameters + +DECLARE @allDatabases bit = 0; --Flip this bit to 1 if you want to uninstall the scripts from all the databases, not only the current one +DECLARE @printOnly bit = 0; --Flip this bit to 1 if you want to print the drop commands only without executing + +--End Configuration +--Variables + +SET NOCOUNT ON; +DECLARE @SQL nvarchar(max) = N''; + +IF OBJECT_ID('TempDB.dbo.#ToDelete') IS NOT NULL + DROP TABLE #ToDelete; + +SELECT 'sp_AllNightLog' as ProcedureName INTO #ToDelete UNION +SELECT 'sp_AllNightLog_Setup' as ProcedureName UNION +SELECT 'sp_Blitz' as ProcedureName UNION +SELECT 'sp_BlitzBackups' as ProcedureName UNION +SELECT 'sp_BlitzCache' as ProcedureName UNION +SELECT 'sp_BlitzFirst' as ProcedureName UNION +SELECT 'sp_BlitzInMemoryOLTP' as ProcedureName UNION +SELECT 'sp_BlitzIndex' as ProcedureName UNION +SELECT 'sp_BlitzLock' as ProcedureName UNION +SELECT 'sp_BlitzQueryStore' as ProcedureName UNION +SELECT 'sp_BlitzWho' as ProcedureName UNION +SELECT 'sp_DatabaseRestore' as ProcedureName UNION +SELECT 'sp_foreachdb' as ProcedureName UNION +SELECT 'sp_ineachdb' as ProcedureName + +--End Variables + +IF (@allDatabases = 0) +BEGIN + + SELECT @SQL += N'DROP PROCEDURE dbo.' + D.ProcedureName + ';' + CHAR(10) + FROM sys.procedures P + JOIN #ToDelete D ON D.ProcedureName = P.name; + +END +ELSE +BEGIN + + DECLARE @dbname SYSNAME; + DECLARE @innerSQL NVARCHAR(max); + + DECLARE c CURSOR LOCAL FAST_FORWARD + FOR SELECT QUOTENAME([name]) + FROM sys.databases + WHERE [state] = 0; + + OPEN c; + + FETCH NEXT FROM c INTO @dbname; + + WHILE(@@FETCH_STATUS = 0) + BEGIN + + SET @innerSQL = N' SELECT @SQL += N''USE ' + @dbname + N';' + NCHAR(10) + N'DROP PROCEDURE dbo.'' + D.ProcedureName + '';'' + NCHAR(10) + FROM ' + @dbname + N'.sys.procedures P + JOIN #ToDelete D ON D.ProcedureName = P.name COLLATE DATABASE_DEFAULT'; + + EXEC sp_executeSQL @innerSQL, N'@SQL nvarchar(max) OUTPUT', @SQL = @SQL OUTPUT; + + FETCH NEXT FROM c INTO @dbname; + + END + + CLOSE c; + DEALLOCATE c; + +END + +PRINT @SQL; + +IF(@printOnly = 0) + EXEC sp_executeSQL @SQL \ No newline at end of file From da5d52d8745fff2db5979c28eecd67ebb396e4d8 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Thu, 19 Sep 2019 11:05:43 -0700 Subject: [PATCH 005/662] Case sensitive collation fixes Gotta love those case sensitive servers. --- Uninstall.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Uninstall.sql b/Uninstall.sql index d8dfde8d6..adcadae8c 100644 --- a/Uninstall.sql +++ b/Uninstall.sql @@ -11,7 +11,7 @@ DECLARE @printOnly bit = 0; --Flip this bit to 1 if you want to print the drop c SET NOCOUNT ON; DECLARE @SQL nvarchar(max) = N''; -IF OBJECT_ID('TempDB.dbo.#ToDelete') IS NOT NULL +IF OBJECT_ID('tempdb.dbo.#ToDelete') IS NOT NULL DROP TABLE #ToDelete; SELECT 'sp_AllNightLog' as ProcedureName INTO #ToDelete UNION @@ -75,4 +75,4 @@ END PRINT @SQL; IF(@printOnly = 0) - EXEC sp_executeSQL @SQL \ No newline at end of file + EXEC sp_executesql @SQL From 821a4975ef69f8bb0160425be5f505254c663fd4 Mon Sep 17 00:00:00 2001 From: Emanuele Meazzo <619619.psp@gmail.com> Date: Wed, 6 Nov 2019 09:30:32 +0100 Subject: [PATCH 006/662] Update SqlServerVersions.sql Fixes #2181 --- SqlServerVersions.sql | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/SqlServerVersions.sql b/SqlServerVersions.sql index c221cc47d..89f4b2a63 100644 --- a/SqlServerVersions.sql +++ b/SqlServerVersions.sql @@ -18,19 +18,20 @@ BEGIN ( MajorVersionNumber ASC, MinorVersionNumber ASC, - ReleaseDate ASC + MinorVersionName ) ); END; GO -DELETE dbo.SqlServerVersions; +TRUNCATE TABLE dbo.SqlServerVersions; INSERT INTO dbo.SqlServerVersions (MajorVersionNumber, MinorVersionNumber, Branch, [Url], ReleaseDate, MainstreamSupportEndDate, ExtendedSupportEndDate, MajorVersionName, MinorVersionName) VALUES - (14, 3228, 'RTM CU17', 'https://support.microsoft.com/en-us/help/4515579', '2019-10-08', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 17'), + (15, 2000, 'RTM ', '', '2019-11-04', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'RTM '), + (14, 3238, 'RTM CU17', 'https://support.microsoft.com/en-us/help/4515579', '2019-10-08', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 17'), (14, 3223, 'RTM CU16', 'https://support.microsoft.com/en-us/help/4508218', '2019-08-01', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 16'), (14, 3162, 'RTM CU15', 'https://support.microsoft.com/en-us/help/4498951', '2019-05-24', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 15'), (14, 3076, 'RTM CU14', 'https://support.microsoft.com/en-us/help/4484710', '2019-03-25', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 14'), @@ -48,8 +49,8 @@ VALUES (14, 3008, 'RTM CU2', 'https://support.microsoft.com/en-us/help/4052574', '2017-11-28', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 2'), (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 '), - (13, 5426, 'SP2 CU10', 'https://support.microsoft.com/en-us/help/4505830', '2019-10-08', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 10'), - (13, 5426, 'SP2 CU9', 'https://support.microsoft.com/en-us/help/4505830', '2019-09-30', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 9'), + (13, 5492, 'SP2 CU10', 'https://support.microsoft.com/en-us/help/4505830', '2019-10-08', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 10'), + (13, 5479, 'SP2 CU9', 'https://support.microsoft.com/en-us/help/4505830', '2019-09-30', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 9'), (13, 5426, 'SP2 CU8', 'https://support.microsoft.com/en-us/help/4505830', '2019-07-31', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 8'), (13, 5337, 'SP2 CU7', 'https://support.microsoft.com/en-us/help/4495256', '2019-05-23', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 7'), (13, 5292, 'SP2 CU6', 'https://support.microsoft.com/en-us/help/4488536', '2019-03-19', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 6'), From 069e6e272c5b39cbeea3555d354dc101f49fd771 Mon Sep 17 00:00:00 2001 From: Emanuele Meazzo <619619.psp@gmail.com> Date: Wed, 6 Nov 2019 09:38:11 +0100 Subject: [PATCH 007/662] Fix Typo in BlitzFirst FIxes #2184 --- sp_BlitzFirst.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sp_BlitzFirst.sql b/sp_BlitzFirst.sql index 9b1eafb88..dfd9afcaf 100644 --- a/sp_BlitzFirst.sql +++ b/sp_BlitzFirst.sql @@ -1491,7 +1491,7 @@ BEGIN s.[host_name] AS HostName, r.[database_id] AS DatabaseID, DB_NAME(r.database_id) AS DatabaseName, - 0 AS OpenTransactionCount + 0 AS OpenTransactionCount, r.query_hash FROM sys.dm_os_waiting_tasks tBlocked INNER JOIN sys.dm_exec_sessions s ON tBlocked.blocking_session_id = s.session_id From 9615e4e389b88d922b00d7e5366dfab6c4270647 Mon Sep 17 00:00:00 2001 From: Emanuele Meazzo <619619.psp@gmail.com> Date: Mon, 18 Nov 2019 16:57:17 +0100 Subject: [PATCH 008/662] Fixed dumb decisions --- SqlServerVersions.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SqlServerVersions.sql b/SqlServerVersions.sql index 89f4b2a63..4b10ee3d1 100644 --- a/SqlServerVersions.sql +++ b/SqlServerVersions.sql @@ -18,14 +18,14 @@ BEGIN ( MajorVersionNumber ASC, MinorVersionNumber ASC, - MinorVersionName + ReleaseDate ASC ) ); END; GO -TRUNCATE TABLE dbo.SqlServerVersions; +DELETE FROM dbo.SqlServerVersions; INSERT INTO dbo.SqlServerVersions (MajorVersionNumber, MinorVersionNumber, Branch, [Url], ReleaseDate, MainstreamSupportEndDate, ExtendedSupportEndDate, MajorVersionName, MinorVersionName) From 413aadf1d02048ddd449978917008dfbb0989008 Mon Sep 17 00:00:00 2001 From: AdrianB1 Date: Thu, 29 Oct 2020 11:45:13 +0200 Subject: [PATCH 009/662] Update sp_BlitzWho.sql Fixes #2491. --- sp_BlitzWho.sql | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/sp_BlitzWho.sql b/sp_BlitzWho.sql index 9f215d34f..e39ca564d 100644 --- a/sp_BlitzWho.sql +++ b/sp_BlitzWho.sql @@ -525,6 +525,21 @@ IF @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @Output END + IF OBJECT_ID('tempdb..#WhoReadableDBs') IS NOT NULL + DROP TABLE #WhoReadableDBs; + +CREATE TABLE #WhoReadableDBs +( +database_id INT +); + +IF EXISTS (SELECT * FROM sys.all_objects o WHERE o.name = 'dm_hadr_database_replica_states') +BEGIN + RAISERROR('Checking for Read intent databases to exclude',0,0) WITH NOWAIT; + + EXEC('INSERT INTO #WhoReadableDBs (database_id) SELECT DBs.database_id FROM sys.databases DBs INNER JOIN sys.availability_replicas Replicas ON DBs.replica_id = Replicas.replica_id WHERE replica_server_name NOT IN (SELECT DISTINCT primary_replica FROM sys.dm_hadr_availability_group_states States) AND Replicas.secondary_role_allow_connections_desc = ''READ_ONLY'' AND replica_server_name = @@SERVERNAME;'); +END + SELECT @BlockingCheck = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; DECLARE @blocked TABLE @@ -999,6 +1014,7 @@ IF @ProductVersionMajor >= 11 N' WHERE s.session_id <> @@SPID AND s.host_name IS NOT NULL + AND r.database_id NOT IN (SELECT database_id FROM #WhoReadableDBs) ' + CASE WHEN @ShowSleepingSPIDs = 0 THEN N' AND COALESCE(DB_NAME(r.database_id), DB_NAME(blocked.dbid)) IS NOT NULL' From 70625cf646d15ae0b800eef3c0576d18f1690bb6 Mon Sep 17 00:00:00 2001 From: dba Date: Sat, 31 Oct 2020 23:18:23 +0200 Subject: [PATCH 010/662] Added check for backups to NUL, checkID is 256 --- sp_Blitz.sql | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index 98839937f..d0fd449d4 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -1030,6 +1030,64 @@ AS -7, GETDATE()) ); END; + /* + CheckID #256 is searching for backups to NUL. + */ + + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 256 ) + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 2) WITH NOWAIT; + + INSERT INTO #BlitzResults + ( CheckID , + DatabaseName , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT DISTINCT + 256 AS CheckID , + d.name AS DatabaseName, + 1 AS Priority , + 'Backup' AS FindingsGroup , + 'Log backups to NUL' AS Finding , + 'https://www.brentozar.com/archive/2009/08/backup-log-with-truncate-only-in-sql-server-2008/' AS URL , + ('' + CAST(d.name AS NVARCHAR(128)) + '''s Log file has been backed up ' + CAST((SELECT count(*) + FROM msdb.dbo.backupset AS b INNER JOIN + msdb.dbo.backupmediafamily AS bmf + ON b.media_set_id = bmf.media_set_id + WHERE b.database_name COLLATE SQL_Latin1_General_CP1_CI_AS = d.name COLLATE SQL_Latin1_General_CP1_CI_AS + AND bmf.physical_device_name = 'NUL' + AND b.type = 'L' + AND b.backup_finish_date >= DATEADD(dd, + -7, GETDATE())) AS NVARCHAR(8)) + ' time(s) to ''NUL'' in the last week. This could potentially break the backup chain.') AS Details + FROM master.sys.databases AS d + WHERE d.recovery_model IN ( 1, 2 ) + AND d.database_id NOT IN ( 2, 3 ) + AND d.source_database_id IS NULL + AND d.state NOT IN(1, 6, 10) /* Not currently offline or restoring, like log shipping databases */ + AND d.is_in_standby = 0 /* Not a log shipping target database */ + AND d.source_database_id IS NULL /* Excludes database snapshots */ + --AND d.name NOT IN ( SELECT DISTINCT + -- DatabaseName + -- FROM #SkipChecks + -- WHERE CheckID IS NULL OR CheckID = 2) + AND EXISTS ( SELECT * + FROM msdb.dbo.backupset AS b INNER JOIN + msdb.dbo.backupmediafamily AS bmf + ON b.media_set_id = bmf.media_set_id + WHERE d.name COLLATE SQL_Latin1_General_CP1_CI_AS = b.database_name COLLATE SQL_Latin1_General_CP1_CI_AS + AND bmf.physical_device_name = 'NUL' + AND b.type = 'L' + AND b.backup_finish_date >= DATEADD(dd, + -7, GETDATE()) ); + END; + /* Next up, we've got CheckID 8. (These don't have to go in order.) This one won't work on SQL Server 2005 because it relies on a new DMV that didn't From 43707ccc4292c64ca7752a4367d3e43741e09bcf Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Mon, 2 Nov 2020 02:28:46 -0800 Subject: [PATCH 011/662] #2650 sp_BlitzIndex spatial Don't show reads/writes for spatial indexes or disabled indexes since they're not tracked. Closes #2650. --- sp_BlitzIndex.sql | 44 +++++++++++++++++++++++++------------------- 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index 309bdddcd..3a2cf97c5 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -317,25 +317,31 @@ IF OBJECT_ID('tempdb..#Ignore_Databases') IS NOT NULL [reads_per_write] AS CAST(CASE WHEN user_updates > 0 THEN ( user_seeks + user_scans + user_lookups ) / (1.0 * user_updates) ELSE 0 END AS MONEY) , - [index_usage_summary] AS N'Reads: ' + - REPLACE(CONVERT(NVARCHAR(30),CAST((user_seeks + user_scans + user_lookups) AS MONEY), 1), N'.00', N'') - + CASE WHEN user_seeks + user_scans + user_lookups > 0 THEN - N' (' - + RTRIM( - CASE WHEN user_seeks > 0 THEN REPLACE(CONVERT(NVARCHAR(30),CAST((user_seeks) AS MONEY), 1), N'.00', N'') + N' seek ' ELSE N'' END - + CASE WHEN user_scans > 0 THEN REPLACE(CONVERT(NVARCHAR(30),CAST((user_scans) AS MONEY), 1), N'.00', N'') + N' scan ' ELSE N'' END - + CASE WHEN user_lookups > 0 THEN REPLACE(CONVERT(NVARCHAR(30),CAST((user_lookups) AS MONEY), 1), N'.00', N'') + N' lookup' ELSE N'' END - ) - + N') ' - ELSE N' ' END - + N'Writes:' + - REPLACE(CONVERT(NVARCHAR(30),CAST(user_updates AS MONEY), 1), N'.00', N''), - [more_info] AS - CASE WHEN is_in_memory_oltp = 1 - THEN N'EXEC dbo.sp_BlitzInMemoryOLTP @dbName=' + QUOTENAME([database_name],N'''') + - N', @tableName=' + QUOTENAME([object_name],N'''') + N';' - ELSE N'EXEC dbo.sp_BlitzIndex @DatabaseName=' + QUOTENAME([database_name],N'''') + - N', @SchemaName=' + QUOTENAME([schema_name],N'''') + N', @TableName=' + QUOTENAME([object_name],N'''') + N';' END + [index_usage_summary] AS + CASE WHEN is_spatial = 1 THEN N'Not Tracked' + WHEN is_disabled = 1 THEN N'Disabled' + ELSE N'Reads: ' + + REPLACE(CONVERT(NVARCHAR(30),CAST((user_seeks + user_scans + user_lookups) AS MONEY), 1), N'.00', N'') + + CASE WHEN user_seeks + user_scans + user_lookups > 0 THEN + N' (' + + RTRIM( + CASE WHEN user_seeks > 0 THEN REPLACE(CONVERT(NVARCHAR(30),CAST((user_seeks) AS MONEY), 1), N'.00', N'') + N' seek ' ELSE N'' END + + CASE WHEN user_scans > 0 THEN REPLACE(CONVERT(NVARCHAR(30),CAST((user_scans) AS MONEY), 1), N'.00', N'') + N' scan ' ELSE N'' END + + CASE WHEN user_lookups > 0 THEN REPLACE(CONVERT(NVARCHAR(30),CAST((user_lookups) AS MONEY), 1), N'.00', N'') + N' lookup' ELSE N'' END + ) + + N') ' + ELSE N' ' + END + + N'Writes:' + + REPLACE(CONVERT(NVARCHAR(30),CAST(user_updates AS MONEY), 1), N'.00', N'') + END /* First "end" is about is_spatial */, + [more_info] AS + CASE WHEN is_in_memory_oltp = 1 + THEN N'EXEC dbo.sp_BlitzInMemoryOLTP @dbName=' + QUOTENAME([database_name],N'''') + + N', @tableName=' + QUOTENAME([object_name],N'''') + N';' + ELSE N'EXEC dbo.sp_BlitzIndex @DatabaseName=' + QUOTENAME([database_name],N'''') + + N', @SchemaName=' + QUOTENAME([schema_name],N'''') + N', @TableName=' + QUOTENAME([object_name],N'''') + N';' + END ); RAISERROR (N'Adding UQ index on #IndexSanity (database_id, object_id, index_id)',0,1) WITH NOWAIT; IF NOT EXISTS(SELECT 1 FROM tempdb.sys.indexes WHERE name='uq_database_id_object_id_index_id') From bb94f86a12bc75c5302f1784d29e55451998e691 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Mon, 2 Nov 2020 02:37:42 -0800 Subject: [PATCH 012/662] #2640 sp_Blitz log backups to nul Adding documentation in checks list, tweaking wording. --- Documentation/sp_Blitz_Checks_by_Priority.md | 5 +++-- sp_Blitz.sql | 8 ++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/Documentation/sp_Blitz_Checks_by_Priority.md b/Documentation/sp_Blitz_Checks_by_Priority.md index 7d0d27401..a8df59e17 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: 255. -If you want to add a new one, start at 256. +CURRENT HIGH CHECKID: 256. +If you want to add a new one, start at 257. | Priority | FindingsGroup | Finding | URL | CheckID | |----------|-----------------------------|---------------------------------------------------------|------------------------------------------------------------------------|----------| @@ -18,6 +18,7 @@ If you want to add a new one, start at 256. | 1 | Backup | Backups Not Performed Recently | https://www.BrentOzar.com/go/nobak | 1 | | 1 | Backup | Encryption Certificate Not Backed Up Recently | https://www.BrentOzar.com/go/tde | 202 | | 1 | Backup | Full Recovery Mode w/o Log Backups | https://www.BrentOzar.com/go/biglogs | 2 | +| 1 | Backup | Log Backups to NUL | https://www.BrentOzar.com/go/nul | 256 | | 1 | Backup | TDE Certificate Not Backed Up Recently | https://www.BrentOzar.com/go/tde | 119 | | 1 | Corruption | Database Corruption Detected | https://www.BrentOzar.com/go/repair | 34 | | 1 | Corruption | Database Corruption Detected | https://www.BrentOzar.com/go/repair | 89 | diff --git a/sp_Blitz.sql b/sp_Blitz.sql index d0fd449d4..b0c29ee3f 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -1055,9 +1055,9 @@ AS d.name AS DatabaseName, 1 AS Priority , 'Backup' AS FindingsGroup , - 'Log backups to NUL' AS Finding , - 'https://www.brentozar.com/archive/2009/08/backup-log-with-truncate-only-in-sql-server-2008/' AS URL , - ('' + CAST(d.name AS NVARCHAR(128)) + '''s Log file has been backed up ' + CAST((SELECT count(*) + 'Log Backups to NUL' AS Finding , + 'https://www.brentozar.com/go/nul' AS URL , + N'The transaction log file has been backed up ' + CAST((SELECT count(*) FROM msdb.dbo.backupset AS b INNER JOIN msdb.dbo.backupmediafamily AS bmf ON b.media_set_id = bmf.media_set_id @@ -1065,7 +1065,7 @@ AS AND bmf.physical_device_name = 'NUL' AND b.type = 'L' AND b.backup_finish_date >= DATEADD(dd, - -7, GETDATE())) AS NVARCHAR(8)) + ' time(s) to ''NUL'' in the last week. This could potentially break the backup chain.') AS Details + -7, GETDATE())) AS NVARCHAR(8)) + ' time(s) to ''NUL'' in the last week, which means the backup does not exist. This breaks point-in-time recovery.' AS Details FROM master.sys.databases AS d WHERE d.recovery_model IN ( 1, 2 ) AND d.database_id NOT IN ( 2, 3 ) From a5189d03dccdf87d89185eff09940ae4aedb4568 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Mon, 2 Nov 2020 02:58:55 -0800 Subject: [PATCH 013/662] #2651 sp_BlitzIndex SortDirection Closes #2651. --- sp_BlitzIndex.sql | 59 +++++++++++++++++++++++++++++++++-------------- 1 file changed, 42 insertions(+), 17 deletions(-) diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index 3a2cf97c5..0f06442f0 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -34,6 +34,7 @@ ALTER PROCEDURE dbo.sp_BlitzIndex @IncludeInactiveIndexes BIT = 0 /* Will skip indexes with no reads or writes */, @ShowAllMissingIndexRequests BIT = 0 /*Will make all missing index requests show up*/, @SortOrder NVARCHAR(50) = NULL, /* Only affects @Mode = 2. */ + @SortDirection NVARCHAR(4) = 'DESC', /* Only affects @Mode = 2. */ @Help TINYINT = 0, @Debug BIT = 0, @Version VARCHAR(30) = NULL OUTPUT, @@ -123,6 +124,7 @@ DECLARE @ColumnList NVARCHAR(MAX); /* Let's get @SortOrder set to lower case here for comparisons later */ SET @SortOrder = REPLACE(LOWER(@SortOrder), N' ', N'_'); +SET @SortDirection = LOWER(@SortDirection); SET @LineFeed = CHAR(13) + CHAR(10); SELECT @SQLServerProductVersion = CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)); @@ -5390,23 +5392,46 @@ BEGIN; FROM #IndexSanity AS i --left join here so we don't lose disabled nc indexes LEFT JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id LEFT JOIN #IndexCreateTsql AS ict ON i.index_sanity_id = ict.index_sanity_id - ORDER BY CASE WHEN @SortOrder = N'rows' THEN sz.total_rows - WHEN @SortOrder = N'reserved_mb' THEN sz.total_reserved_MB - WHEN @SortOrder = N'size' THEN sz.total_reserved_MB - WHEN @SortOrder = N'reserved_lob_mb' THEN sz.total_reserved_LOB_MB - WHEN @SortOrder = N'lob' THEN sz.total_reserved_LOB_MB - WHEN @SortOrder = N'total_row_lock_wait_in_ms' THEN COALESCE(sz.total_row_lock_wait_in_ms,0) - WHEN @SortOrder = N'total_page_lock_wait_in_ms' THEN COALESCE(sz.total_page_lock_wait_in_ms,0) - WHEN @SortOrder = N'lock_time' THEN (COALESCE(sz.total_row_lock_wait_in_ms,0) + COALESCE(sz.total_page_lock_wait_in_ms,0)) - WHEN @SortOrder = N'total_reads' THEN total_reads - WHEN @SortOrder = N'reads' THEN total_reads - WHEN @SortOrder = N'user_updates' THEN user_updates - WHEN @SortOrder = N'writes' THEN user_updates - WHEN @SortOrder = N'reads_per_write' THEN reads_per_write - WHEN @SortOrder = N'ratio' THEN reads_per_write - WHEN @SortOrder = N'forward_fetches' THEN sz.total_forwarded_fetch_count - WHEN @SortOrder = N'fetches' THEN sz.total_forwarded_fetch_count - ELSE NULL END DESC, /* Shout out to DHutmacher */ + ORDER BY CASE WHEN @SortDirection = 'desc' THEN + CASE WHEN @SortOrder = N'rows' THEN sz.total_rows + WHEN @SortOrder = N'reserved_mb' THEN sz.total_reserved_MB + WHEN @SortOrder = N'size' THEN sz.total_reserved_MB + WHEN @SortOrder = N'reserved_lob_mb' THEN sz.total_reserved_LOB_MB + WHEN @SortOrder = N'lob' THEN sz.total_reserved_LOB_MB + WHEN @SortOrder = N'total_row_lock_wait_in_ms' THEN COALESCE(sz.total_row_lock_wait_in_ms,0) + WHEN @SortOrder = N'total_page_lock_wait_in_ms' THEN COALESCE(sz.total_page_lock_wait_in_ms,0) + WHEN @SortOrder = N'lock_time' THEN (COALESCE(sz.total_row_lock_wait_in_ms,0) + COALESCE(sz.total_page_lock_wait_in_ms,0)) + WHEN @SortOrder = N'total_reads' THEN total_reads + WHEN @SortOrder = N'reads' THEN total_reads + WHEN @SortOrder = N'user_updates' THEN user_updates + WHEN @SortOrder = N'writes' THEN user_updates + WHEN @SortOrder = N'reads_per_write' THEN reads_per_write + WHEN @SortOrder = N'ratio' THEN reads_per_write + WHEN @SortOrder = N'forward_fetches' THEN sz.total_forwarded_fetch_count + WHEN @SortOrder = N'fetches' THEN sz.total_forwarded_fetch_count + ELSE NULL END + ELSE 1 END + DESC, /* Shout out to DHutmacher */ + CASE WHEN @SortDirection = 'asc' THEN + CASE WHEN @SortOrder = N'rows' THEN sz.total_rows + WHEN @SortOrder = N'reserved_mb' THEN sz.total_reserved_MB + WHEN @SortOrder = N'size' THEN sz.total_reserved_MB + WHEN @SortOrder = N'reserved_lob_mb' THEN sz.total_reserved_LOB_MB + WHEN @SortOrder = N'lob' THEN sz.total_reserved_LOB_MB + WHEN @SortOrder = N'total_row_lock_wait_in_ms' THEN COALESCE(sz.total_row_lock_wait_in_ms,0) + WHEN @SortOrder = N'total_page_lock_wait_in_ms' THEN COALESCE(sz.total_page_lock_wait_in_ms,0) + WHEN @SortOrder = N'lock_time' THEN (COALESCE(sz.total_row_lock_wait_in_ms,0) + COALESCE(sz.total_page_lock_wait_in_ms,0)) + WHEN @SortOrder = N'total_reads' THEN total_reads + WHEN @SortOrder = N'reads' THEN total_reads + WHEN @SortOrder = N'user_updates' THEN user_updates + WHEN @SortOrder = N'writes' THEN user_updates + WHEN @SortOrder = N'reads_per_write' THEN reads_per_write + WHEN @SortOrder = N'ratio' THEN reads_per_write + WHEN @SortOrder = N'forward_fetches' THEN sz.total_forwarded_fetch_count + WHEN @SortOrder = N'fetches' THEN sz.total_forwarded_fetch_count + ELSE NULL END + ELSE 1 END + ASC, i.[database_name], [Schema Name], [Object Name], [Index ID] OPTION (RECOMPILE); END; From d782048a8b88b9c0099a9a464172bf871adcbab4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20DUCOM?= Date: Fri, 6 Nov 2020 14:38:01 +0100 Subject: [PATCH 014/662] Adds optional @IgnoreAlreadyPresentInMsdb BIT setting Avois log backups already referenced in msdb. --- sp_DatabaseRestore.sql | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/sp_DatabaseRestore.sql b/sp_DatabaseRestore.sql index 2a7f60c2d..05047b157 100755 --- a/sp_DatabaseRestore.sql +++ b/sp_DatabaseRestore.sql @@ -26,6 +26,7 @@ ALTER PROCEDURE [dbo].[sp_DatabaseRestore] @StopAt NVARCHAR(14) = NULL, @OnlyLogsAfter NVARCHAR(14) = NULL, @SimpleFolderEnumeration BIT = 0, + @IgnoreAlreadyPresentInMsdb BIT = 0, @DatabaseOwner sysname = NULL, @Execute CHAR(1) = Y, @Debug INT = 0, @@ -226,6 +227,7 @@ DECLARE @cmd NVARCHAR(4000) = N'', --Holds xp_cmdshell command @LogRestoreRanking SMALLINT = 1, --Holds Log iteration # when multiple paths & backup files are being stripped @LogFirstLSN NUMERIC(25, 0), --Holds first LSN in log backup headers @LogLastLSN NUMERIC(25, 0), --Holds last LSN in log backup headers + @LogLastNameInMsdbAS NVARCHAR(MAX) = N'', -- Holds last TRN file name already restored @FileListParamSQL NVARCHAR(4000) = N'', --Holds INSERT list for #FileListParameters @BackupParameters NVARCHAR(500) = N'', --Used to save BlockSize, MaxTransferSize and BufferCount @RestoreDatabaseID SMALLINT; --Holds DB_ID of @RestoreDatabaseName @@ -1183,6 +1185,24 @@ BEGIN END /*End folder sanity check*/ + +IF @IgnoreAlreadyPresentInMsdb = 1 +BEGIN + SELECT TOP 1 @LogLastNameInMsdbAS = bf.physical_device_name + FROM msdb.dbo.backupmediafamily bf + WHERE physical_device_name like @BackupPathLog + '%' + ORDER BY physical_device_name DESC + + IF @Debug = 1 + BEGIN + SELECT 'Keeping LOG backups with name > : ' + @LogLastNameInMsdbAS + END + + DELETE fl + FROM @FileList AS fl + WHERE fl.BackupPath + fl.BackupFile <= @LogLastNameInMsdbAS +END + IF @Debug = 1 BEGIN SELECT * FROM @FileList WHERE BackupFile IS NOT NULL; From 6d6ac544b5b6c6926ebac87ea8cc7c54125400a5 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Mon, 9 Nov 2020 02:35:26 -0800 Subject: [PATCH 015/662] #2660 sp_BlitzFirst Details truncation Only save the first 4000 characters of Details to the output table. Closes #2660. --- sp_BlitzFirst.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sp_BlitzFirst.sql b/sp_BlitzFirst.sql index ccb5dacc8..7ab5e5464 100644 --- a/sp_BlitzFirst.sql +++ b/sp_BlitzFirst.sql @@ -3158,7 +3158,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, + @OutputSchemaName + '.' + @OutputTableName + ' (ServerName, CheckDate, CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, QueryText, StartTime, LoginName, NTUserName, OriginalLoginName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, DetailsInt, QueryHash) SELECT ' - + ' @SrvName, @CheckDate, CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, QueryText, StartTime, LoginName, NTUserName, OriginalLoginName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, DetailsInt, QueryHash FROM #BlitzFirstResults ORDER BY Priority , FindingsGroup , Finding , Details'; + + ' @SrvName, @CheckDate, CheckID, Priority, FindingsGroup, Finding, URL, LEFT(Details,4000), HowToStopIt, QueryPlan, QueryText, StartTime, LoginName, NTUserName, OriginalLoginName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, DetailsInt, QueryHash FROM #BlitzFirstResults ORDER BY Priority , FindingsGroup , Finding , Details'; EXEC sp_executesql @StringToExecute, N'@SrvName NVARCHAR(128), @CheckDate datetimeoffset', @@ -3213,7 +3213,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, + ' INSERT ' + @OutputTableName + ' (ServerName, CheckDate, CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, QueryText, StartTime, LoginName, NTUserName, OriginalLoginName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, DetailsInt) SELECT ' - + ' @SrvName, @CheckDate, CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, QueryText, StartTime, LoginName, NTUserName, OriginalLoginName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, DetailsInt FROM #BlitzFirstResults ORDER BY Priority , FindingsGroup , Finding , Details'; + + ' @SrvName, @CheckDate, CheckID, Priority, FindingsGroup, Finding, URL, LEFT(Details,4000), HowToStopIt, QueryPlan, QueryText, StartTime, LoginName, NTUserName, OriginalLoginName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, DetailsInt FROM #BlitzFirstResults ORDER BY Priority , FindingsGroup , Finding , Details'; EXEC sp_executesql @StringToExecute, N'@SrvName NVARCHAR(128), @CheckDate datetimeoffset', From c6fad13018b9f4ca9cac8a303d6ceedf71502f33 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Mon, 9 Nov 2020 02:45:05 -0800 Subject: [PATCH 016/662] #2652 sp_BlitzWho ordering ten day queries Padding the number of days of runtime with an extra digit. Closes #2652. --- sp_BlitzWho.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sp_BlitzWho.sql b/sp_BlitzWho.sql index e39ca564d..84cb57f94 100644 --- a/sp_BlitzWho.sql +++ b/sp_BlitzWho.sql @@ -570,7 +570,7 @@ BEGIN /* Think of the StringToExecute as starting with this, but we'll set this up later depending on whether we're doing an insert or a select: SELECT @StringToExecute = N'SELECT GETDATE() AS run_date , */ - SET @StringToExecute = N'COALESCE( CONVERT(VARCHAR(20), (ABS(r.total_elapsed_time) / 1000) / 86400) + '':'' + CONVERT(VARCHAR(20), (DATEADD(SECOND, (r.total_elapsed_time / 1000), 0) + DATEADD(MILLISECOND, (r.total_elapsed_time % 1000), 0)), 114), CONVERT(VARCHAR(20), DATEDIFF(SECOND, s.last_request_start_time, GETDATE()) / 86400) + '':'' + CONVERT(VARCHAR(20), DATEADD(SECOND, DATEDIFF(SECOND, s.last_request_start_time, GETDATE()), 0), 114) ) AS [elapsed_time] , + SET @StringToExecute = N'COALESCE( RIGHT(''00'' + CONVERT(VARCHAR(20), (ABS(r.total_elapsed_time) / 1000) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), (DATEADD(SECOND, (r.total_elapsed_time / 1000), 0) + DATEADD(MILLISECOND, (r.total_elapsed_time % 1000), 0)), 114), CONVERT(VARCHAR(20), DATEDIFF(SECOND, s.last_request_start_time, GETDATE()) / 86400) + '':'' + CONVERT(VARCHAR(20), DATEADD(SECOND, DATEDIFF(SECOND, s.last_request_start_time, GETDATE()), 0), 114) ) AS [elapsed_time] , s.session_id , COALESCE(DB_NAME(r.database_id), DB_NAME(blocked.dbid), ''N/A'') AS database_name, ISNULL(SUBSTRING(dest.text, @@ -775,7 +775,7 @@ IF @ProductVersionMajor >= 11 /* Think of the StringToExecute as starting with this, but we'll set this up later depending on whether we're doing an insert or a select: SELECT @StringToExecute = N'SELECT GETDATE() AS run_date , */ - SELECT @StringToExecute = N'COALESCE( CONVERT(VARCHAR(20), (ABS(r.total_elapsed_time) / 1000) / 86400) + '':'' + CONVERT(VARCHAR(20), (DATEADD(SECOND, (r.total_elapsed_time / 1000), 0) + DATEADD(MILLISECOND, (r.total_elapsed_time % 1000), 0)), 114), CONVERT(VARCHAR(20), DATEDIFF(SECOND, s.last_request_start_time, GETDATE()) / 86400) + '':'' + CONVERT(VARCHAR(20), DATEADD(SECOND, DATEDIFF(SECOND, s.last_request_start_time, GETDATE()), 0), 114) ) AS [elapsed_time] , + SELECT @StringToExecute = N'COALESCE( RIGHT(''00'' + CONVERT(VARCHAR(20), (ABS(r.total_elapsed_time) / 1000) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), (DATEADD(SECOND, (r.total_elapsed_time / 1000), 0) + DATEADD(MILLISECOND, (r.total_elapsed_time % 1000), 0)), 114), CONVERT(VARCHAR(20), DATEDIFF(SECOND, s.last_request_start_time, GETDATE()) / 86400) + '':'' + CONVERT(VARCHAR(20), DATEADD(SECOND, DATEDIFF(SECOND, s.last_request_start_time, GETDATE()), 0), 114) ) AS [elapsed_time] , s.session_id , COALESCE(DB_NAME(r.database_id), DB_NAME(blocked.dbid), ''N/A'') AS database_name, ISNULL(SUBSTRING(dest.text, From d1b28cf9455096c7abadb742085088e7bd0b2cd8 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Mon, 9 Nov 2020 02:53:25 -0800 Subject: [PATCH 017/662] sp_DatabaseRestore: change new parameter name I know this is going to sound anal retentive, but it took me a while to understand what IgnoreAlreadyPresentInMsdb meant. I changed it to SkipBackupsAlreadyInMsdb. The rest looks good though! --- sp_DatabaseRestore.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sp_DatabaseRestore.sql b/sp_DatabaseRestore.sql index 05047b157..3e115708e 100755 --- a/sp_DatabaseRestore.sql +++ b/sp_DatabaseRestore.sql @@ -26,7 +26,7 @@ ALTER PROCEDURE [dbo].[sp_DatabaseRestore] @StopAt NVARCHAR(14) = NULL, @OnlyLogsAfter NVARCHAR(14) = NULL, @SimpleFolderEnumeration BIT = 0, - @IgnoreAlreadyPresentInMsdb BIT = 0, + @SkipBackupsAlreadyInMsdb BIT = 0, @DatabaseOwner sysname = NULL, @Execute CHAR(1) = Y, @Debug INT = 0, @@ -1186,7 +1186,7 @@ BEGIN /*End folder sanity check*/ -IF @IgnoreAlreadyPresentInMsdb = 1 +IF @SkipBackupsAlreadyInMsdb = 1 BEGIN SELECT TOP 1 @LogLastNameInMsdbAS = bf.physical_device_name FROM msdb.dbo.backupmediafamily bf From c89f25a7b9b51ffe8343ec8aaaad57e2ab082d10 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Mon, 9 Nov 2020 03:08:41 -0800 Subject: [PATCH 018/662] #2202 sp_BlitzCache documentation Explaining that SlowlySearchPlansFor hits a SQL Server bug, and added new sp_DatabaseRestore parameter. Closes #2202. --- README.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 0ea5d42fa..f8276a2b6 100644 --- a/README.md +++ b/README.md @@ -187,10 +187,11 @@ Other common parameters include: In addition to the [parameters common to many of the stored procedures](#parameters-common-to-many-of-the-stored-procedures), here are the ones specific to sp_BlitzCache: -* OnlyQueryHashes - if you want to examine specific query plans, you can pass in a comma-separated list of them in a string. -* IgnoreQueryHashes - if you know some queries suck and you don't want to see them, you can pass in a comma-separated list of them. -* OnlySqlHandles, @IgnoreSqlHandles - just like the above two params +* @OnlyQueryHashes - if you want to examine specific query plans, you can pass in a comma-separated list of them in a string. +* @IgnoreQueryHashes - if you know some queries suck and you don't want to see them, you can pass in a comma-separated list of them. +* @OnlySqlHandles, @IgnoreSqlHandles - just like the above two params * @DatabaseName - if you only want to analyze plans in a single database. However, keep in mind that this is only the database context. A single query that runs in Database1 can join across objects in Database2 and Database3, but we can only know that it ran in Database1. +* @SlowlySearchPlansFor - lets you search for strings, but will not find all results due to a [bug in the way SQL Server removes spaces from XML.](https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2202) If your search string includes spaces, SQL Server may remove those before the search runs, unfortunately. [*Back to top*](#header1) @@ -431,9 +432,10 @@ Parameters include: * @ExistingDBAction - if the database already exists when we try to restore it, 1 sets the database to single user mode, 2 kills the connections, and 3 kills the connections and then drops the database. * @Debug - default 0. When 1, we print out messages of what we're doing in the messages tab of SSMS. * @StopAt NVARCHAR(14) - pass in a date time to stop your restores at a time like '20170508201501'. This doesn't use the StopAt parameter for the restore command - it simply stops restoring logs that would have this date/time's contents in it. (For example, if you're taking backups every 15 minutes on the hour, and you pass in 9:05 AM as part of the restore time, the restores would stop at your last log backup that doesn't include 9:05AM's data - but it won't restore right up to 9:05 AM.) +* @SkipBackupsAlreadyInMsdb - default 0. When set to 1, we check MSDB for the most recently restored backup from this log path, and skip all backup files prior to that. Useful if you're pulling backups from across a slow network and you don't want to wait to check the restore header of each backup. -For information about how this works, see [Tara Kizer's white paper on Log Shipping 2.0 with Google Compute Engine.](https://BrentOzar.com/go/gce) +For information about how this works, see [Tara Kizer's white paper on Log Shipping 2.0 with Google Compute Engine.](https://www.brentozar.com/archive/2017/03/new-white-paper-build-sql-server-disaster-recovery-plan-google-compute-engine/) [*Back to top*](#header1) From 2116c46df7fee8b578fd20598f0312516376d02e Mon Sep 17 00:00:00 2001 From: Adrian Buckman <33764798+Adedba@users.noreply.github.com> Date: Mon, 9 Nov 2020 12:56:21 +0000 Subject: [PATCH 019/662] 2655 Computed column PlanCreationTimeHours definition change #2655 Altered the definition of the PlanCreationTimeHours coumputed column in the output table only. Old definition:DATEDIFF(HOUR, PlanCreationTime, SYSDATETIME()) New Definition: DATEDIFF(HOUR,CONVERT(DATETIMEOFFSET(7),[PlanCreationTime]),[CheckDate]) Create table statement updated and a new statement added to check the computed column definition, if this differs from the expected then the column is dropped and recreated. --- sp_BlitzCache.sql | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/sp_BlitzCache.sql b/sp_BlitzCache.sql index 9d5a44877..1d6ef949c 100644 --- a/sp_BlitzCache.sql +++ b/sp_BlitzCache.sql @@ -6972,7 +6972,7 @@ ELSE PercentExecutionsByType money, ExecutionsPerMinute money, PlanCreationTime datetime,' + N' - PlanCreationTimeHours AS DATEDIFF(HOUR, PlanCreationTime, SYSDATETIME()), + PlanCreationTimeHours AS DATEDIFF(HOUR,CONVERT(DATETIMEOFFSET(7),[PlanCreationTime]),[CheckDate]), LastExecutionTime datetime, LastCompletionTime datetime, PlanHandle varbinary(64), @@ -7018,7 +7018,28 @@ ELSE AvgSpills MONEY, QueryPlanCost FLOAT, JoinKey AS ServerName + Cast(CheckDate AS NVARCHAR(50)), - CONSTRAINT [PK_' + REPLACE(REPLACE(@OutputTableName,'[',''),']','') + '] PRIMARY KEY CLUSTERED(ID ASC));'; + CONSTRAINT [PK_' + REPLACE(REPLACE(@OutputTableName,'[',''),']','') + '] PRIMARY KEY CLUSTERED(ID ASC)); + + IF EXISTS(SELECT * FROM ' + +@OutputDatabaseName + +N'.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' + +@OutputSchemaName + +''') AND EXISTS (SELECT * FROM ' + +@OutputDatabaseName+ + N'.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''' + +@OutputSchemaName + +''' AND QUOTENAME(TABLE_NAME) = ''' + +@OutputTableName + +''') AND EXISTS (SELECT * FROM ' + +@OutputDatabaseName+ + N'.sys.computed_columns WHERE [name] = N''PlanCreationTimeHours'' AND QUOTENAME(OBJECT_NAME(object_id)) = N''' + +@OutputTableName + +''' AND [definition] = N''(datediff(hour,[PlanCreationTime],sysdatetime()))'') +BEGIN + RAISERROR(''We noticed that you are running an old computed column definition for PlanCreationTimeHours, fixing that now'',0,0) WITH NOWAIT; + ALTER TABLE '+@OutputDatabaseName+'.'+@OutputSchemaName+'.'+@OutputTableName+' DROP COLUMN [PlanCreationTimeHours]; + ALTER TABLE '+@OutputDatabaseName+'.'+@OutputSchemaName+'.'+@OutputTableName+' ADD [PlanCreationTimeHours] AS DATEDIFF(HOUR,CONVERT(DATETIMEOFFSET(7),[PlanCreationTime]),[CheckDate]); +END '; IF @ValidOutputServer = 1 BEGIN From eddd10c3ea5aa4e082a96ed08304de2de58598d7 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Mon, 9 Nov 2020 05:55:39 -0800 Subject: [PATCH 020/662] #2664 sp_Blitz service account filtering Skip the Launchpad service. Closes #2664. --- sp_Blitz.sql | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index b0c29ee3f..2a04bba47 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -4454,7 +4454,8 @@ IF @ProductVersionMajor >= 10 [sys].[dm_server_services] WHERE [service_account] LIKE 'NT Service%' AND [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%'; END; END; From eac64758312152506c36f74ec26fc9e4936af434 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Tue, 10 Nov 2020 08:13:18 -0800 Subject: [PATCH 021/662] #2662 sp_BlitzIndex reprioritize findings Using the DEATH Method. Closes #2662. --- .../sp_BlitzIndex_Checks_by_Priority.md | 76 + README.md | 6 +- sp_BlitzIndex.sql | 2502 +++++++++-------- 3 files changed, 1337 insertions(+), 1247 deletions(-) create mode 100644 Documentation/sp_BlitzIndex_Checks_by_Priority.md diff --git a/Documentation/sp_BlitzIndex_Checks_by_Priority.md b/Documentation/sp_BlitzIndex_Checks_by_Priority.md new file mode 100644 index 000000000..bfa2cedab --- /dev/null +++ b/Documentation/sp_BlitzIndex_Checks_by_Priority.md @@ -0,0 +1,76 @@ +# sp_BlitzIndex Checks by Priority + +This table lists all checks ordered by priority. + +Before adding a new check, make sure to add a Github issue for it first, and have a group discussion about its priority, description, and findings URL. + +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: 120 +If you want to add a new check, start at 121. + +| Priority | FindingsGroup | Finding | URL | CheckID | +|----------|---------------------------------|---------------------------------------|-------------------------------------------------|----------| + +| 10 | Index Hoarder | Many NC Indexes on a Single Table | https://www.brentozar.com/go/IndexHoarder | 20 | +| 10 | Index Hoarder | Unused NC Index with High Writes | https://www.brentozar.com/go/IndexHoarder | 22 | +| 20 | Multiple Index Personalities | Duplicate Keys | https://www.brentozar.com/go/duplicateindex | 1 | +| 30 | Multiple Index Personalities | Borderline Duplicate Keys | https://www.brentozar.com/go/duplicateindex | 2 | +| 40 | Indexaphobia | High Value Missing Index | https://www.brentozar.com/go/indexaphobia | 50 | +| 70 | Aggressive Indexes | Total Lock Time with Long Average Waits | https://www.brentozar.com/go/aggressiveindexes | 11 | +| 70 | Aggressive Indexes | Total Lock Time with Short Average Waits | https://www.brentozar.com/go/aggressiveindexes | 12 | +| 80 | Abnormal Psychology | Columnstore Indexes with Trace Flag 834 | https://support.microsoft.com/en-us/kb/3210239 | 72 | +| 80 | Abnormal Psychology | Identity Column Near End of Range | https://www.brentozar.com/go/AbnormalPsychology | 68 | +| 80 | Abnormal Psychology | Filter Columns Not In Index Definition | https://www.brentozar.com/go/IndexFeatures | 34 | + +| 90 | Functioning Statistaholics | Low Sampling Rates | https://www.brentozar.com/go/stats | 91 | + + + +| 90 | Functioning Statistaholics | Statistics Not Updated Recently | https://www.brentozar.com/go/stats | 90 | +| 90 | Functioning Statistaholics | Statistics with NO RECOMPUTE | https://www.brentozar.com/go/stats | 92 | +| 100 | Index Hoarder | NC index with High Writes:Reads | https://www.brentozar.com/go/IndexHoarder | 48 | +| 100 | Self Loathing Indexes | Heap with a Nonclustered Primary Key | https://www.brentozar.com/go/SelfLoathing | 47 | +| 100 | Self Loathing Indexes | Heap with Forwarded Fetches | https://www.brentozar.com/go/SelfLoathing | 43 | +| 100 | Self Loathing Indexes | Large Active Heap | https://www.brentozar.com/go/SelfLoathing | 44 | +| 100 | Self Loathing Indexes | Low Fill Factor on Clustered Index | https://www.brentozar.com/go/SelfLoathing | 40 | +| 100 | Self Loathing Indexes | Low Fill Factor on Nonclustered Index | https://www.brentozar.com/go/SelfLoathing | 40 | +| 100 | Self Loathing Indexes | Medium Active Heap | https://www.brentozar.com/go/SelfLoathing | 45 | +| 100 | Self Loathing Indexes | Small Active Heap | https://www.brentozar.com/go/SelfLoathing | 46 | +| 100 | Serial Forcer | Computed Column with Scalar UDF | https://www.brentozar.com/go/serialudf | 99 | +| 100 | Serial Forcer | Check Constraint with Scalar UDF | https://www.brentozar.com/go/computedscalar | 94 | + + +| 150 | Abnormal Psychology | Cascading Updates or Deletes | https://www.brentozar.com/go/AbnormalPsychology | 71 | +| 150 | Abnormal Psychology | Columnstore Index | https://www.brentozar.com/go/AbnormalPsychology | 61 | +| 150 | Abnormal Psychology | Column Collation Does Not Match Database Collation| https://www.brentozar.com/go/AbnormalPsychology | 69 | +| 150 | Abnormal Psychology | Compressed Index | https://www.brentozar.com/go/AbnormalPsychology | 63 | +| 150 | Abnormal Psychology | In-Memory OLTP | https://www.brentozar.com/go/AbnormalPsychology | 73 | +| 150 | Abnormal Psychology | Non-Aligned Index on a Partitioned Table | https://www.brentozar.com/go/AbnormalPsychology | 65 | +| 150 | Abnormal Psychology | Partitioned Index | https://www.brentozar.com/go/AbnormalPsychology | 64 | +| 150 | Abnormal Psychology | Spatial Index | https://www.brentozar.com/go/AbnormalPsychology | 62 | +| 150 | Abnormal Psychology | XML Index | https://www.brentozar.com/go/AbnormalPsychology | 60 | +| 150 | Index Hoarder | Borderline: Wide Indexes (7 or More Columns) | https://www.brentozar.com/go/IndexHoarder | 23 | +| 150 | Index Hoarder | More Than 5 Percent NC Indexes Are Unused | https://www.brentozar.com/go/IndexHoarder | 21 | +| 150 | Index Hoarder | Non-Unique Clustered Index | https://www.brentozar.com/go/IndexHoarder | 28 | +| 150 | Index Hoarder | Unused NC Index with Low Writes | https://www.brentozar.com/go/IndexHoarder | 29 | +| 150 | Index Hoarder | Wide Clustered Index (>3 columns or >16 bytes) | https://www.brentozar.com/go/IndexHoarder | 24 | +| 150 | Self Loathing Indexes | Disabled Index | https://www.brentozar.com/go/SelfLoathing | 42 | +| 150 | Self Loathing Indexes | Hypothetical Index | https://www.brentozar.com/go/SelfLoathing | 41 | +| 200 | Abnormal Psychology | Identity Column Using a Negative Seed or Increment Other Than 1 | https://www.brentozar.com/go/AbnormalPsychology | 74 | +| 200 | Abnormal Psychology | Recently Created Tables/Indexes (1 week) | https://www.brentozar.com/go/AbnormalPsychology | 66 | +| 200 | Abnormal Psychology | Recently Modified Tables/Indexes (2 days) | https://www.brentozar.com/go/AbnormalPsychology | 67 | +| 200 | Abnormal Psychology | Replicated Columns | https://www.brentozar.com/go/AbnormalPsychology | 70 | +| 200 | Abnormal Psychology | Temporal Tables | https://www.brentozar.com/go/AbnormalPsychology | 110 | +| 200 | Cold Calculators | Definition Defeatists | https://www.brentozar.com/go/serialudf | 100 | +| 200 | Functioning Statistaholics | Filter Fixation | https://www.brentozar.com/go/stats | 93 | +| 200 | Index Hoarder | Addicted to Nulls | https://www.brentozar.com/go/IndexHoarder | 25 | +| 200 | Index Hoarder | Addicted to Strings | https://www.brentozar.com/go/IndexHoarder | 27 | +| 200 | Index Hoarder | Wide Tables: 35+ cols or > 2000 non-LOB bytes | https://www.brentozar.com/go/IndexHoarder | 26 | +| 200 | Self Loathing Indexes | Heaps with Deletes | https://www.brentozar.com/go/SelfLoathing | 49 | +| 200 | Workaholics | Scan-a-lots (index-usage-stats) | https://www.brentozar.com/go/Workaholics | 80 | +| 200 | Workaholics | Top Recent Accesses (index-op-stats) | https://www.brentozar.com/go/Workaholics | 81 | +| 250 | Feature-Phobic Indexes | Few Indexes Use Includes | https://www.brentozar.com/go/IndexFeatures | 31 | +| 250 | Feature-Phobic Indexes | No Filtered Indexes or Indexed Views | https://www.brentozar.com/go/IndexFeatures | 32 | +| 250 | Feature-Phobic Indexes | No Indexes Use Includes | https://www.brentozar.com/go/IndexFeatures | 30 | +| 250 | Feature-Phobic Indexes | Potential Filtered Index (Based on Column Name) | https://www.brentozar.com/go/IndexFeatures | 33 | diff --git a/README.md b/README.md index f8276a2b6..a1ba0b914 100644 --- a/README.md +++ b/README.md @@ -261,7 +261,11 @@ Common parameters include: * @SchemaName, @TableName - if you pass in these, sp_BlitzIndex does a deeper-dive analysis of just one table. You get several result sets back describing more information about the table's current indexes, foreign key relationships, missing indexes, and fields in the table. * @GetAllDatabases = 1 - slower, but lets you analyze all the databases at once, up to 50. If you want more than 50 databases, you also have to pass in @BringThePain = 1. * @ThresholdMB = 250 - by default, we only analyze objects over 250MB because you're busy. -* @Mode = 0 (default) - get different data with 0=Diagnose, 1=Summarize, 2=Index Usage Detail, 3=Missing Index Detail, 4=Diagnose Details. +* @Mode = 0 (default) - returns high-priority (1-100) advice on the most urgent index issues. + * @Mode = 4: Diagnose Details - like @Mode 0, but returns even more advice (priorities 1-255) with things you may not be able to fix right away, and things we just want to warn you about. + * @Mode = 1: Summarize - total numbers of indexes, space used, etc per database. + * @Mode = 2: Index Usage Details - an inventory of your existing indexes and their usage statistics. Great for copy/pasting into Excel to do slice & dice analysis. This is the only mode that works with the @Output parameters: you can export this data to table on a monthly basis if you need to go back and look to see which indexes were used over time. + * @Mode = 3: Missing Indexes - an inventory of indexes SQL Server is suggesting. Also great for copy/pasting into Excel for later analysis. sp_BlitzIndex focuses on mainstream index types. Other index types have varying amounts of support: diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index 0f06442f0..ae50cdfe1 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -1754,14 +1754,18 @@ BEGIN TRY EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; - IF @SkipStatistics = 0 AND DB_NAME() = @DatabaseName /* Can only get stats in the current database - see https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/1947 */ + IF @SkipStatistics = 0 /* AND DB_NAME() = @DatabaseName /* Can only get stats in the current database - see https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/1947 */ */ BEGIN IF ((PARSENAME(@SQLServerProductVersion, 4) >= 12) OR (PARSENAME(@SQLServerProductVersion, 4) = 11 AND PARSENAME(@SQLServerProductVersion, 2) >= 3000) OR (PARSENAME(@SQLServerProductVersion, 4) = 10 AND PARSENAME(@SQLServerProductVersion, 3) = 50 AND PARSENAME(@SQLServerProductVersion, 2) >= 2500)) BEGIN RAISERROR (N'Gathering Statistics Info With Newer Syntax.',0,1) WITH NOWAIT; - SET @dsql=N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + SET @dsql=N'USE ' + @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, + 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) SELECT DB_ID(N' + QUOTENAME(@DatabaseName,'''') + N') AS [database_id], @i_DatabaseName AS database_name, obj.name AS table_name, @@ -1826,17 +1830,17 @@ BEGIN TRY PRINT SUBSTRING(@dsql, 32000, 36000); PRINT SUBSTRING(@dsql, 36000, 40000); END; - 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) EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; END; ELSE BEGIN RAISERROR (N'Gathering Statistics Info With Older Syntax.',0,1) WITH NOWAIT; - SET @dsql=N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + SET @dsql=N'USE ' + @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, 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) SELECT DB_ID(N' + QUOTENAME(@DatabaseName,'''') + N') AS [database_id], @i_DatabaseName AS database_name, obj.name AS table_name, @@ -1904,10 +1908,6 @@ BEGIN TRY PRINT SUBSTRING(@dsql, 32000, 36000); PRINT SUBSTRING(@dsql, 36000, 40000); END; - 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) EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; END; @@ -2596,7 +2596,7 @@ BEGIN GROUP BY i.database_id, i.schema_name, i.object_id ) SELECT N'Missing index.' AS Finding , - N'http://BrentOzar.com/go/Indexaphobia' AS URL , + N'https://www.brentozar.com/go/Indexaphobia' AS URL , mi.[statement] + ' Est. Benefit: ' + CASE WHEN magic_benefit_number >= 922337203685477 THEN '>= 922,337,203,685,477' @@ -2783,30 +2783,48 @@ BEGIN RAISERROR(N'Done visualizing columnstore index contents.', 0,1) WITH NOWAIT; END -END; +END; /* IF @TableName IS NOT NULL */ ---If @TableName is NOT specified... ---Act based on the @Mode and @Filter. (@Filter applies only when @Mode=0 "diagnose") -ELSE + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +ELSE IF @Mode IN (0, 4) /* DIAGNOSE*//* IF @TableName IS NOT NULL, so @TableName must not be null */ BEGIN; - IF @Mode IN (0, 4) /* DIAGNOSE*/ - BEGIN; - RAISERROR(N'@Mode=0 or 4, we are diagnosing.', 0,1) WITH NOWAIT; + IF @Mode IN (0, 4) /* DIAGNOSE priorities 1-100 */ + BEGIN; + RAISERROR(N'@Mode=0 or 4, running rules for priorities 1-100.', 0,1) WITH NOWAIT; ---------------------------------------- --Multiple Index Personalities: Check_id 0-10 ---------------------------------------- - BEGIN; - - --SELECT [object_id], key_column_names, database_id - -- FROM #IndexSanity - -- WHERE index_type IN (1,2) /* Clustered, NC only*/ - -- AND is_hypothetical = 0 - -- AND is_disabled = 0 - -- GROUP BY [object_id], key_column_names, database_id - -- HAVING COUNT(*) > 1 - - RAISERROR('check_id 1: Duplicate keys', 0,1) WITH NOWAIT; WITH duplicate_indexes AS ( SELECT [object_id], key_column_names, database_id, [schema_name] @@ -2833,11 +2851,11 @@ BEGIN; secret_columns, index_usage_summary, index_size_summary ) SELECT 1 AS check_id, ip.index_sanity_id, - 50 AS Priority, + 20 AS Priority, 'Multiple Index Personalities' AS findings_group, 'Duplicate keys' AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/duplicateindex' AS URL, + N'https://www.brentozar.com/go/duplicateindex' AS URL, N'Index Name: ' + ip.index_name + N' Table Name: ' + ip.db_schema_object_name AS details, ip.index_definition, ip.secret_columns, @@ -2870,11 +2888,11 @@ BEGIN; secret_columns, index_usage_summary, index_size_summary ) SELECT 2 AS check_id, ip.index_sanity_id, - 60 AS Priority, + 30 AS Priority, 'Multiple Index Personalities' AS findings_group, 'Borderline duplicate keys' AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/duplicateindex' AS URL, + N'https://www.brentozar.com/go/duplicateindex' AS URL, ip.db_schema_object_indexid AS details, ip.index_definition, ip.secret_columns, @@ -2895,18 +2913,16 @@ BEGIN; ORDER BY ip.[schema_name], ip.[object_name], ip.key_column_names, ip.include_column_names OPTION ( RECOMPILE ); - END; ---------------------------------------- --Aggressive Indexes: Check_id 10-19 ---------------------------------------- - BEGIN; RAISERROR(N'check_id 11: Total lock wait time > 5 minutes (row + page) with long average waits', 0,1) WITH NOWAIT; INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, secret_columns, index_usage_summary, index_size_summary ) SELECT 11 AS check_id, i.index_sanity_id, - 10 AS Priority, + 70 AS Priority, N'Aggressive ' + CASE COALESCE((SELECT SUM(1) FROM #IndexSanity iMe @@ -2932,7 +2948,7 @@ BEGIN; END AS findings_group, N'Total lock wait time > 5 minutes (row + page) with long average waits' AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/AggressiveIndexes' AS URL, + N'https://www.brentozar.com/go/AggressiveIndexes' AS URL, (i.db_schema_object_indexid + N': ' + sz.index_lock_wait_summary + N' NC indexes on table: ') COLLATE DATABASE_DEFAULT + CAST(COALESCE((SELECT SUM(1) @@ -2963,7 +2979,7 @@ BEGIN; secret_columns, index_usage_summary, index_size_summary ) SELECT 12 AS check_id, i.index_sanity_id, - 10 AS Priority, + 70 AS Priority, N'Aggressive ' + CASE COALESCE((SELECT SUM(1) FROM #IndexSanity iMe @@ -2989,7 +3005,7 @@ BEGIN; END AS findings_group, N'Total lock wait time > 5 minutes (row + page) with short average waits' AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/AggressiveIndexes' AS URL, + N'https://www.brentozar.com/go/AggressiveIndexes' AS URL, (i.db_schema_object_indexid + N': ' + sz.index_lock_wait_summary + N' NC indexes on table: ') COLLATE DATABASE_DEFAULT + CAST(COALESCE((SELECT SUM(1) @@ -3015,22 +3031,20 @@ BEGIN; ORDER BY 4, [database_name], 8 OPTION ( RECOMPILE ); - END; ---------------------------------------- --Index Hoarder: Check_id 20-29 ---------------------------------------- - BEGIN - RAISERROR(N'check_id 20: >=7 NC indexes on any given table. Yes, 7 is an arbitrary number.', 0,1) WITH NOWAIT; + RAISERROR(N'check_id 20: >= 10 NC indexes on any given table. Yes, 10 is an arbitrary number.', 0,1) WITH NOWAIT; INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, secret_columns, index_usage_summary, index_size_summary ) SELECT 20 AS check_id, MAX(i.index_sanity_id) AS index_sanity_id, - 100 AS Priority, + 10 AS Priority, 'Index Hoarder' AS findings_group, - 'Many NC indexes on a single table' AS finding, + 'Many NC Indexes on a Single Table' AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/IndexHoarder' AS URL, + N'https://www.brentozar.com/go/IndexHoarder' AS URL, CAST (COUNT(*) AS NVARCHAR(30)) + ' NC indexes on ' + i.db_schema_object_name AS details, i.db_schema_object_name + ' (' + CAST (COUNT(*) AS NVARCHAR(30)) + ' indexes)' AS index_definition, '' AS secret_columns, @@ -3047,88 +3061,20 @@ BEGIN; JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id WHERE index_id NOT IN ( 0, 1 ) GROUP BY db_schema_object_name, [i].[database_name] - HAVING COUNT(*) >= CASE WHEN (@GetAllDatabases = 1 OR @Mode = 0) - THEN 21 - ELSE 7 - END + HAVING COUNT(*) >= 10 ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); - IF @Filter = 1 /*@Filter=1 is "ignore unusued" */ - BEGIN - RAISERROR(N'Skipping checks on unused indexes (21 and 22) because @Filter=1', 0,1) WITH NOWAIT; - END; - ELSE /*Otherwise, go ahead and do the checks*/ - BEGIN - RAISERROR(N'check_id 21: >=5 percent of indexes are unused. Yes, 5 is an arbitrary number.', 0,1) WITH NOWAIT; - DECLARE @percent_NC_indexes_unused NUMERIC(29,1); - DECLARE @NC_indexes_unused_reserved_MB NUMERIC(29,1); - - SELECT @percent_NC_indexes_unused = ( 100.00 * SUM(CASE - WHEN total_reads = 0 - THEN 1 - ELSE 0 - END) ) / COUNT(*), - @NC_indexes_unused_reserved_MB = SUM(CASE - WHEN total_reads = 0 - THEN sz.total_reserved_MB - ELSE 0 - END) - FROM #IndexSanity i - 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()) - 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, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 21 AS check_id, - MAX(i.index_sanity_id) AS index_sanity_id, - 150 AS Priority, - N'Index Hoarder' AS findings_group, - N'More than 5 percent NC indexes are unused' AS finding, - [database_name] AS [Database Name], - N'http://BrentOzar.com/go/IndexHoarder' AS URL, - CAST (@percent_NC_indexes_unused AS NVARCHAR(30)) + N' percent NC indexes (' + CAST(COUNT(*) AS NVARCHAR(10)) + N') unused. ' + - N'These take up ' + CAST (@NC_indexes_unused_reserved_MB AS NVARCHAR(30)) + N'MB of space.' AS details, - i.database_name + ' (' + CAST (COUNT(*) AS NVARCHAR(30)) + N' indexes)' AS index_definition, - '' AS secret_columns, - CAST(SUM(total_reads) AS NVARCHAR(256)) + N' reads (ALL); ' - + CAST(SUM([user_updates]) AS NVARCHAR(256)) + N' writes (ALL)' AS index_usage_summary, - - REPLACE(CONVERT(NVARCHAR(30),CAST(MAX([total_rows]) AS MONEY), 1), '.00', '') + N' rows (MAX)' - + CASE WHEN SUM(total_reserved_MB) > 1024 THEN - N'; ' + CAST(CAST(SUM(total_reserved_MB)/1024. AS NUMERIC(29,1)) AS NVARCHAR(30)) + 'GB (ALL)' - WHEN SUM(total_reserved_MB) > 0 THEN - N'; ' + CAST(CAST(SUM(total_reserved_MB) AS NUMERIC(29,1)) AS NVARCHAR(30)) + 'MB (ALL)' - ELSE '' - END AS index_size_summary - FROM #IndexSanity i - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE index_id NOT IN ( 0, 1 ) - AND i.is_unique = 0 - AND total_reads = 0 - AND NOT (@GetAllDatabases = 1 OR @Mode = 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()) - GROUP BY i.database_name - OPTION ( RECOMPILE ); - RAISERROR(N'check_id 22: NC indexes with 0 reads. (Borderline) and >= 10,000 writes', 0,1) WITH NOWAIT; INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, secret_columns, index_usage_summary, index_size_summary ) SELECT 22 AS check_id, i.index_sanity_id, - 100 AS Priority, + 10 AS Priority, N'Index Hoarder' AS findings_group, - N'Unused NC index with High Writes' AS finding, + N'Unused NC Index with High Writes' AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/IndexHoarder' AS URL, + N'https://www.brentozar.com/go/IndexHoarder' AS URL, N'Reads: 0,' + N' Writes: ' + REPLACE(CONVERT(NVARCHAR(30), CAST((i.user_updates) AS MONEY), 1), N'.00', N'') @@ -3146,861 +3092,1125 @@ 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 + AND @Filter <> 1 /* 1 = "ignore unused */ ORDER BY i.db_schema_object_indexid OPTION ( RECOMPILE ); - END; /*end checks only run when @Filter <> 1*/ - RAISERROR(N'check_id 23: Indexes with 7 or more columns. (Borderline)', 0,1) WITH NOWAIT; + + RAISERROR(N'check_id 34: Filtered index definition columns not in index definition', 0,1) WITH NOWAIT; + + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 34 AS check_id, + i.index_sanity_id, + 80 AS Priority, + N'Abnormal Psychology' AS findings_group, + N'Filter Columns Not In Index Definition' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/IndexFeatures' AS URL, + N'The index ' + + QUOTENAME(i.index_name) + + N' on [' + + i.db_schema_object_name + + N'] has a filter on [' + + i.filter_definition + + N'] but is missing [' + + LTRIM(i.filter_columns_not_in_index) + + N'] from the index definition.' + AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + sz.index_size_summary + FROM #IndexSanity i + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + WHERE i.filter_columns_not_in_index IS NOT NULL + ORDER BY i.db_schema_object_indexid + OPTION ( RECOMPILE ); + + ---------------------------------------- + --Self Loathing Indexes : Check_id 40-49 + ---------------------------------------- + + RAISERROR(N'check_id 40: Fillfactor in nonclustered 80 percent or less', 0,1) WITH NOWAIT; INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, secret_columns, index_usage_summary, index_size_summary ) - SELECT 23 AS check_id, + SELECT 40 AS check_id, i.index_sanity_id, - 150 AS Priority, - N'Index Hoarder' AS findings_group, - N'Borderline: Wide indexes (7 or more columns)' AS finding, + 100 AS Priority, + N'Self Loathing Indexes' AS findings_group, + N'Low Fill Factor on Nonclustered Index' AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/IndexHoarder' AS URL, - CAST(count_key_columns + count_included_columns AS NVARCHAR(10)) + ' columns on ' - + i.db_schema_object_indexid AS details, i.index_definition, - i.secret_columns, + N'https://www.brentozar.com/go/SelfLoathing' AS URL, + CAST(fill_factor AS NVARCHAR(10)) + N'% fill factor on ' + db_schema_object_indexid + N'. '+ + CASE WHEN (last_user_update IS NULL OR user_updates < 1) + THEN N'No writes have been made.' + ELSE + N'Last write was ' + CONVERT(NVARCHAR(16),last_user_update,121) + N' and ' + + CAST(user_updates AS NVARCHAR(25)) + N' updates have been made.' + END + AS details, + i.index_definition, + i.secret_columns, i.index_usage_summary, sz.index_size_summary FROM #IndexSanity AS i JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id - WHERE ( count_key_columns + count_included_columns ) >= 7 - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) - OPTION ( RECOMPILE ); + WHERE index_id > 1 + AND fill_factor BETWEEN 1 AND 80 OPTION ( RECOMPILE ); - RAISERROR(N'check_id 24: Wide clustered indexes (> 3 columns or > 16 bytes).', 0,1) WITH NOWAIT; - WITH count_columns AS ( - SELECT database_id, [object_id], - SUM(CASE max_length WHEN -1 THEN 0 ELSE max_length END) AS sum_max_length - FROM #IndexColumns ic - WHERE index_id IN (1,0) /*Heap or clustered only*/ - AND key_ordinal > 0 - GROUP BY database_id, object_id - ) + RAISERROR(N'check_id 40: Fillfactor in clustered 80 percent or less', 0,1) WITH NOWAIT; INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, secret_columns, index_usage_summary, index_size_summary ) - SELECT 24 AS check_id, - i.index_sanity_id, - 150 AS Priority, - N'Index Hoarder' AS findings_group, - N'Wide clustered index (> 3 columns OR > 16 bytes)' AS finding, + SELECT 40 AS check_id, + i.index_sanity_id, + 100 AS Priority, + N'Self Loathing Indexes' AS findings_group, + N'Low Fill Factor on Clustered Index' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/SelfLoathing' AS URL, + N'Fill factor on ' + db_schema_object_indexid + N' is ' + CAST(fill_factor AS NVARCHAR(10)) + N'%. '+ + CASE WHEN (last_user_update IS NULL OR user_updates < 1) + THEN N'No writes have been made.' + ELSE + N'Last write was ' + CONVERT(NVARCHAR(16),last_user_update,121) + N' and ' + + CAST(user_updates AS NVARCHAR(25)) + N' updates have been made.' + END + AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + sz.index_size_summary + FROM #IndexSanity AS i + JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id + WHERE index_id = 1 + AND fill_factor BETWEEN 1 AND 80 OPTION ( RECOMPILE ); + + + RAISERROR(N'check_id 43: Heaps with forwarded records', 0,1) WITH NOWAIT; + WITH heaps_cte + AS ( SELECT [object_id], + [database_id], + [schema_name], + SUM(forwarded_fetch_count) AS forwarded_fetch_count, + SUM(leaf_delete_count) AS leaf_delete_count + FROM #IndexPartitionSanity + GROUP BY [object_id], + [database_id], + [schema_name] + HAVING SUM(forwarded_fetch_count) > 0) + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 43 AS check_id, + i.index_sanity_id, + 100 AS Priority, + N'Self Loathing Indexes' AS findings_group, + N'Heaps with Forwarded Fetches' AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/IndexHoarder' AS URL, - CAST (i.count_key_columns AS NVARCHAR(10)) + N' columns with potential size of ' - + CAST(cc.sum_max_length AS NVARCHAR(10)) - + N' bytes in clustered index:' + i.db_schema_object_name - + N'. ' + - (SELECT CAST(COUNT(*) AS NVARCHAR(23)) - FROM #IndexSanity i2 - WHERE i2.[object_id]=i.[object_id] - AND i2.database_id = i.database_id - AND i2.index_id <> 1 - AND i2.is_disabled=0 - AND i2.is_hypothetical=0) - + N' NC indexes on the table.' - AS details, - i.index_definition, - secret_columns, + N'https://www.brentozar.com/go/SelfLoathing' AS URL, + CASE WHEN h.forwarded_fetch_count >= 922337203685477 THEN '>= 922,337,203,685,477' + WHEN @DaysUptime < 1 THEN CAST(h.forwarded_fetch_count AS NVARCHAR(256)) + N' forwarded fetches against heap: ' + db_schema_object_indexid + ELSE REPLACE(CONVERT(NVARCHAR(256),CAST(CAST( + (h.forwarded_fetch_count /*/@DaysUptime */) + AS BIGINT) AS MONEY), 1), '.00', '') + END + N' forwarded fetches per day against heap: ' + + db_schema_object_indexid AS details, + i.index_definition, + i.secret_columns, i.index_usage_summary, - ip.index_size_summary + sz.index_size_summary FROM #IndexSanity i - JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id - JOIN count_columns AS cc ON i.[object_id]=cc.[object_id] - AND i.database_id = cc.database_id - WHERE index_id = 1 /* clustered only */ - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) - AND - (count_key_columns > 3 /*More than three key columns.*/ - OR cc.sum_max_length > 16 /*More than 16 bytes in key */) - AND i.is_CX_columnstore = 0 - ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); + JOIN heaps_cte h ON i.[object_id] = h.[object_id] + AND i.[database_id] = h.[database_id] + AND i.[schema_name] = h.[schema_name] + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + WHERE i.index_id = 0 + AND h.forwarded_fetch_count / @DaysUptime > 1000 + AND sz.total_reserved_MB >= CASE WHEN NOT (@GetAllDatabases = 1 OR @Mode = 4) THEN @ThresholdMB ELSE sz.total_reserved_MB END + OPTION ( RECOMPILE ); - RAISERROR(N'check_id 25: Addicted to nullable columns.', 0,1) WITH NOWAIT; - WITH count_columns AS ( - SELECT [object_id], - [database_id], - [schema_name], - SUM(CASE is_nullable WHEN 1 THEN 0 ELSE 1 END) AS non_nullable_columns, - COUNT(*) AS total_columns - FROM #IndexColumns ic - WHERE index_id IN (1,0) /*Heap or clustered only*/ - GROUP BY [object_id], + RAISERROR(N'check_id 44: Large Heaps with reads or writes.', 0,1) WITH NOWAIT; + WITH heaps_cte + AS ( SELECT [object_id], + [database_id], + [schema_name], + SUM(forwarded_fetch_count) AS forwarded_fetch_count, + SUM(leaf_delete_count) AS leaf_delete_count + FROM #IndexPartitionSanity + GROUP BY [object_id], [database_id], [schema_name] - ) + HAVING SUM(forwarded_fetch_count) > 0 + OR SUM(leaf_delete_count) > 0) INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, secret_columns, index_usage_summary, index_size_summary ) - SELECT 25 AS check_id, - i.index_sanity_id, - 200 AS Priority, - N'Index Hoarder' AS findings_group, - N'Addicted to nulls' AS finding, - [database_name] AS [Database Name], - N'http://BrentOzar.com/go/IndexHoarder' AS URL, - i.db_schema_object_name - + N' allows null in ' + CAST((total_columns-non_nullable_columns) AS NVARCHAR(10)) - + N' of ' + CAST(total_columns AS NVARCHAR(10)) - + N' columns.' AS details, - i.index_definition, - secret_columns, - ISNULL(i.index_usage_summary,''), - ISNULL(ip.index_size_summary,'') + SELECT 44 AS check_id, + i.index_sanity_id, + 100 AS Priority, + N'Self Loathing Indexes' AS findings_group, + N'Large Active Heap' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/SelfLoathing' AS URL, + N'Should this table be a heap? ' + db_schema_object_indexid AS details, + i.index_definition, + 'N/A' AS secret_columns, + i.index_usage_summary, + sz.index_size_summary FROM #IndexSanity i - JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id - JOIN count_columns AS cc ON i.[object_id]=cc.[object_id] - AND cc.database_id = ip.database_id - AND cc.[schema_name] = ip.[schema_name] - WHERE i.index_id IN (1,0) - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) - AND cc.non_nullable_columns < 2 - AND cc.total_columns > 3 - ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); + LEFT JOIN heaps_cte h ON i.[object_id] = h.[object_id] + AND i.[database_id] = h.[database_id] + AND i.[schema_name] = h.[schema_name] + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + WHERE i.index_id = 0 + AND (i.total_reads > 0 OR i.user_updates > 0) + AND sz.total_rows >= 100000 + AND h.[object_id] IS NULL /*don't duplicate the prior check.*/ + OPTION ( RECOMPILE ); - RAISERROR(N'check_id 26: Wide tables (35+ cols or > 2000 non-LOB bytes).', 0,1) WITH NOWAIT; - WITH count_columns AS ( - SELECT [object_id], - [database_id], - [schema_name], - SUM(CASE max_length WHEN -1 THEN 1 ELSE 0 END) AS count_lob_columns, - SUM(CASE max_length WHEN -1 THEN 0 ELSE max_length END) AS sum_max_length, - COUNT(*) AS total_columns - FROM #IndexColumns ic - WHERE index_id IN (1,0) /*Heap or clustered only*/ - GROUP BY [object_id], + RAISERROR(N'check_id 45: Medium Heaps with reads or writes.', 0,1) WITH NOWAIT; + WITH heaps_cte + AS ( SELECT [object_id], + [database_id], + [schema_name], + SUM(forwarded_fetch_count) AS forwarded_fetch_count, + SUM(leaf_delete_count) AS leaf_delete_count + FROM #IndexPartitionSanity + GROUP BY [object_id], [database_id], [schema_name] - ) + HAVING SUM(forwarded_fetch_count) > 0 + OR SUM(leaf_delete_count) > 0) INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, secret_columns, index_usage_summary, index_size_summary ) - SELECT 26 AS check_id, - i.index_sanity_id, - 150 AS Priority, - N'Index Hoarder' AS findings_group, - N'Wide tables: 35+ cols or > 2000 non-LOB bytes' AS finding, + SELECT 45 AS check_id, + i.index_sanity_id, + 100 AS Priority, + N'Self Loathing Indexes' AS findings_group, + N'Medium Active heap' AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/IndexHoarder' AS URL, - i.db_schema_object_name - + N' has ' + CAST((total_columns) AS NVARCHAR(10)) - + N' total columns with a max possible width of ' + CAST(sum_max_length AS NVARCHAR(10)) - + N' bytes.' + - CASE WHEN count_lob_columns > 0 THEN CAST((count_lob_columns) AS NVARCHAR(10)) - + ' columns are LOB types.' ELSE '' - END - AS details, - i.index_definition, - secret_columns, - ISNULL(i.index_usage_summary,''), - ISNULL(ip.index_size_summary,'') + N'https://www.brentozar.com/go/SelfLoathing' AS URL, + N'Should this table be a heap? ' + db_schema_object_indexid AS details, + i.index_definition, + 'N/A' AS secret_columns, + i.index_usage_summary, + sz.index_size_summary FROM #IndexSanity i - JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id - JOIN count_columns AS cc ON i.[object_id]=cc.[object_id] - AND cc.database_id = i.database_id - AND cc.[schema_name] = i.[schema_name] - WHERE i.index_id IN (1,0) - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) - AND - (cc.total_columns >= 35 OR - cc.sum_max_length >= 2000) - ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 27: Addicted to strings.', 0,1) WITH NOWAIT; - WITH count_columns AS ( - SELECT [object_id], - [database_id], - [schema_name], - SUM(CASE WHEN system_type_name IN ('varchar','nvarchar','char') OR max_length=-1 THEN 1 ELSE 0 END) AS string_or_LOB_columns, - COUNT(*) AS total_columns - FROM #IndexColumns ic - WHERE index_id IN (1,0) /*Heap or clustered only*/ - GROUP BY [object_id], + LEFT JOIN heaps_cte h ON i.[object_id] = h.[object_id] + AND i.[database_id] = h.[database_id] + AND i.[schema_name] = h.[schema_name] + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + WHERE i.index_id = 0 + AND + (i.total_reads > 0 OR i.user_updates > 0) + AND sz.total_rows >= 10000 AND sz.total_rows < 100000 + AND h.[object_id] IS NULL /*don't duplicate the prior check.*/ + OPTION ( RECOMPILE ); + + RAISERROR(N'check_id 46: Small Heaps with reads or writes.', 0,1) WITH NOWAIT; + WITH heaps_cte + AS ( SELECT [object_id], + [database_id], + [schema_name], + SUM(forwarded_fetch_count) AS forwarded_fetch_count, + SUM(leaf_delete_count) AS leaf_delete_count + FROM #IndexPartitionSanity + GROUP BY [object_id], [database_id], [schema_name] - ) + HAVING SUM(forwarded_fetch_count) > 0 + OR SUM(leaf_delete_count) > 0) INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, secret_columns, index_usage_summary, index_size_summary ) - SELECT 27 AS check_id, - i.index_sanity_id, - 200 AS Priority, - N'Index Hoarder' AS findings_group, - N'Addicted to strings' AS finding, + SELECT 46 AS check_id, + i.index_sanity_id, + 100 AS Priority, + N'Self Loathing Indexes' AS findings_group, + N'Small Active heap' AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/IndexHoarder' AS URL, - i.db_schema_object_name - + N' uses string or LOB types for ' + CAST((string_or_LOB_columns) AS NVARCHAR(10)) - + N' of ' + CAST(total_columns AS NVARCHAR(10)) - + N' columns. Check if data types are valid.' AS details, - i.index_definition, - secret_columns, - ISNULL(i.index_usage_summary,''), - ISNULL(ip.index_size_summary,'') + N'https://www.brentozar.com/go/SelfLoathing' AS URL, + N'Should this table be a heap? ' + db_schema_object_indexid AS details, + i.index_definition, + 'N/A' AS secret_columns, + i.index_usage_summary, + sz.index_size_summary FROM #IndexSanity i - JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id - JOIN count_columns AS cc ON i.[object_id]=cc.[object_id] - AND cc.database_id = i.database_id - AND cc.[schema_name] = i.[schema_name] - CROSS APPLY (SELECT cc.total_columns - string_or_LOB_columns AS non_string_or_lob_columns) AS calc1 - WHERE i.index_id IN (1,0) - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) - AND calc1.non_string_or_lob_columns <= 1 - AND cc.total_columns > 3 - ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); + LEFT JOIN heaps_cte h ON i.[object_id] = h.[object_id] + AND i.[database_id] = h.[database_id] + AND i.[schema_name] = h.[schema_name] + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + WHERE i.index_id = 0 + AND + (i.total_reads > 0 OR i.user_updates > 0) + AND sz.total_rows < 10000 + AND h.[object_id] IS NULL /*don't duplicate the prior check.*/ + OPTION ( RECOMPILE ); - RAISERROR(N'check_id 28: Non-unique clustered index.', 0,1) WITH NOWAIT; + RAISERROR(N'check_id 47: Heap with a Nonclustered Primary Key', 0,1) WITH NOWAIT; INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, secret_columns, index_usage_summary, index_size_summary ) - SELECT 28 AS check_id, - i.index_sanity_id, + SELECT 47 AS check_id, + i.index_sanity_id, 100 AS Priority, - N'Index Hoarder' AS findings_group, - N'Non-Unique clustered index' AS finding, + N'Self Loathing Indexes' AS findings_group, + N'Heap with a Nonclustered Primary Key' AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/IndexHoarder' AS URL, - N'Uniquifiers will be required! Clustered index: ' + i.db_schema_object_name - + N' and all NC indexes. ' + - (SELECT CAST(COUNT(*) AS NVARCHAR(23)) - FROM #IndexSanity i2 - WHERE i2.[object_id]=i.[object_id] - AND i2.database_id = i.database_id - AND i2.index_id <> 1 - AND i2.is_disabled=0 - AND i2.is_hypothetical=0) - + N' NC indexes on the table.' - AS details, - i.index_definition, - secret_columns, + N'https://www.brentozar.com/go/SelfLoathing' AS URL, + db_schema_object_indexid + N' is a HEAP with a Nonclustered Primary Key' AS details, + i.index_definition, + i.secret_columns, i.index_usage_summary, - ip.index_size_summary + sz.index_size_summary FROM #IndexSanity i - JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id - WHERE index_id = 1 /* clustered only */ - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) - AND is_unique=0 /* not unique */ - AND is_CX_columnstore=0 /* not a clustered columnstore-- no unique option on those */ - ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + WHERE i.index_type = 2 AND i.is_primary_key = 1 + AND EXISTS + ( + SELECT 1/0 + FROM #IndexSanity AS isa + WHERE i.database_id = isa.database_id + AND i.object_id = isa.object_id + AND isa.index_id = 0 + ) + OPTION ( RECOMPILE ); - RAISERROR(N'check_id 29: NC indexes with 0 reads. (Borderline) and < 10,000 writes', 0,1) WITH NOWAIT; + RAISERROR(N'check_id 48: Nonclustered indexes with a bad read to write ratio', 0,1) WITH NOWAIT; INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, secret_columns, index_usage_summary, index_size_summary ) - SELECT 29 AS check_id, + SELECT 48 AS check_id, i.index_sanity_id, - 150 AS Priority, + 100 AS Priority, N'Index Hoarder' AS findings_group, - N'Unused NC index with Low Writes' AS finding, + N'NC index with High Writes:Reads' AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/IndexHoarder' AS URL, - N'0 reads: ' + i.db_schema_object_indexid AS details, + N'https://www.brentozar.com/go/IndexHoarder' AS URL, + N'Reads: ' + + REPLACE(CONVERT(NVARCHAR(30), CAST((i.total_reads) AS MONEY), 1), N'.00', N'') + + N' Writes: ' + + REPLACE(CONVERT(NVARCHAR(30), CAST((i.user_updates) AS MONEY), 1), N'.00', N'') + + N' on: ' + + i.db_schema_object_indexid AS details, i.index_definition, i.secret_columns, i.index_usage_summary, sz.index_size_summary - FROM #IndexSanity AS i - JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.total_reads=0 - AND i.user_updates < 10000 + FROM #IndexSanity i + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + WHERE i.total_reads > 0 /*Not totally unused*/ + AND i.user_updates >= 10000 /*Decent write activity*/ + AND i.total_reads < 10000 + AND ((i.total_reads * 10) < i.user_updates) /*10x more writes than reads*/ AND i.index_id NOT IN (0,1) /*NCs only*/ - AND i.is_unique = 0 + 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()) - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) ORDER BY i.db_schema_object_indexid OPTION ( RECOMPILE ); - END; - ---------------------------------------- - --Feature-Phobic Indexes: Check_id 30-39 - ---------------------------------------- - BEGIN - RAISERROR(N'check_id 30: No indexes with includes', 0,1) WITH NOWAIT; - /* This does not work the way you'd expect with @GetAllDatabases = 1. For details: - https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/825 - */ - - SELECT database_name, - SUM(CASE WHEN count_included_columns > 0 THEN 1 ELSE 0 END) AS number_indexes_with_includes, - 100.* SUM(CASE WHEN count_included_columns > 0 THEN 1 ELSE 0 END) / ( 1.0 * COUNT(*) ) AS percent_indexes_with_includes - INTO #index_includes - FROM #IndexSanity - WHERE is_hypothetical = 0 - AND is_disabled = 0 - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) - GROUP BY database_name; - - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 30 AS check_id, - NULL AS index_sanity_id, - 250 AS Priority, - N'Feature-Phobic Indexes' AS findings_group, - database_name AS [Database Name], - N'No indexes use includes' AS finding, 'http://BrentOzar.com/go/IndexFeatures' AS URL, - N'No indexes use includes' AS details, - database_name + N' (Entire database)' AS index_definition, - N'' AS secret_columns, - N'N/A' AS index_usage_summary, - N'N/A' AS index_size_summary - FROM #index_includes - WHERE number_indexes_with_includes = 0 - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) - OPTION ( RECOMPILE ); + ---------------------------------------- + --Indexaphobia + --Missing indexes with value >= 5 million: : Check_id 50-59 + ---------------------------------------- + RAISERROR(N'check_id 50: Indexaphobia.', 0,1) WITH NOWAIT; + WITH index_size_cte + AS ( SELECT i.database_id, + i.schema_name, + i.[object_id], + MAX(i.index_sanity_id) AS index_sanity_id, + ISNULL(NULLIF(MAX(DATEDIFF(DAY, i.create_date, SYSDATETIME())), 0), 1) AS create_days, + ISNULL ( + CAST(SUM(CASE WHEN index_id NOT IN (0,1) THEN 1 ELSE 0 END) + AS NVARCHAR(30))+ N' NC indexes exist (' + + CASE WHEN SUM(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) > 1024 + THEN CAST(CAST(SUM(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END )/1024. - RAISERROR(N'check_id 31: < 3 percent of indexes have includes', 0,1) WITH NOWAIT; + AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'GB); ' + ELSE CAST(SUM(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) + AS NVARCHAR(30)) + N'MB); ' + END + + CASE WHEN MAX(sz.[total_rows]) >= 922337203685477 THEN '>= 922,337,203,685,477' + ELSE REPLACE(CONVERT(NVARCHAR(30),CAST(MAX(sz.[total_rows]) AS MONEY), 1), '.00', '') + END + + + N' Estimated Rows;' + ,N'') AS index_size_summary + FROM #IndexSanity AS i + LEFT JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id AND i.database_id = sz.database_id + WHERE i.is_hypothetical = 0 + AND i.is_disabled = 0 + GROUP BY i.database_id, i.schema_name, i.[object_id]) INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 31 AS check_id, - NULL AS index_sanity_id, - 150 AS Priority, - N'Feature-Phobic Indexes' AS findings_group, - N'Borderline: Includes are used in < 3% of indexes' AS findings, - database_name AS [Database Name], - N'http://BrentOzar.com/go/IndexFeatures' AS URL, - N'Only ' + CAST(percent_indexes_with_includes AS NVARCHAR(20)) + '% of indexes have includes' AS details, - N'Entire database' AS index_definition, - N'' AS secret_columns, - N'N/A' AS index_usage_summary, - N'N/A' AS index_size_summary - FROM #index_includes - WHERE number_indexes_with_includes > 0 AND percent_indexes_with_includes <= 3 - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) - OPTION ( RECOMPILE ); + index_usage_summary, index_size_summary, create_tsql, more_info ) + + SELECT check_id, t.index_sanity_id, t.check_id, t.findings_group, t.finding, t.[Database Name], t.URL, t.details, t.[definition], + index_estimated_impact, t.index_size_summary, create_tsql, more_info + FROM + ( + SELECT ROW_NUMBER() OVER (ORDER BY magic_benefit_number DESC) AS rownum, + 50 AS check_id, + sz.index_sanity_id, + 40 AS Priority, + N'Indexaphobia' AS findings_group, + N'High Value Missing Index' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/Indexaphobia' AS URL, + mi.[statement] + + N' Est. benefit per day: ' + + CASE WHEN magic_benefit_number >= 922337203685477 THEN '>= 922,337,203,685,477' + ELSE REPLACE(CONVERT(NVARCHAR(256),CAST(CAST( + (magic_benefit_number/@DaysUptime) + AS BIGINT) AS MONEY), 1), '.00', '') + END AS details, + missing_index_details AS [definition], + index_estimated_impact, + sz.index_size_summary, + mi.create_tsql, + mi.more_info, + magic_benefit_number, + mi.is_low + FROM #MissingIndexes mi + LEFT JOIN index_size_cte sz ON mi.[object_id] = sz.object_id + AND mi.database_id = sz.database_id + AND mi.schema_name = sz.schema_name + /* Minimum benefit threshold = 100k/day of uptime OR since table creation date, whichever is lower*/ + WHERE @ShowAllMissingIndexRequests=1 + OR ( @Mode = 4 AND (magic_benefit_number / CASE WHEN sz.create_days < @DaysUptime THEN sz.create_days ELSE @DaysUptime END) >= 100000 ) + OR (magic_benefit_number / CASE WHEN sz.create_days < @DaysUptime THEN sz.create_days ELSE @DaysUptime END) >= 100000 + ) AS t + WHERE t.rownum <= CASE WHEN (@Mode <> 4) THEN 20 ELSE t.rownum END + ORDER BY magic_benefit_number DESC + OPTION ( RECOMPILE ); - RAISERROR(N'check_id 32: filtered indexes and indexed views', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT DISTINCT - 32 AS check_id, - NULL AS index_sanity_id, - 250 AS Priority, - N'Feature-Phobic Indexes' AS findings_group, - N'Borderline: No filtered indexes or indexed views exist' AS finding, - i.database_name AS [Database Name], - N'http://BrentOzar.com/go/IndexFeatures' AS URL, - N'These are NOT always needed-- but do you know when you would use them?' AS details, - i.database_name + N' (Entire database)' AS index_definition, - N'' AS secret_columns, - N'N/A' AS index_usage_summary, - N'N/A' AS index_size_summary - FROM #IndexSanity i - WHERE i.database_name NOT IN ( - SELECT database_name - FROM #IndexSanity - WHERE filter_definition <> '' ) - AND i.database_name NOT IN ( - SELECT database_name - FROM #IndexSanity - WHERE is_indexed_view = 1 ) - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) - OPTION ( RECOMPILE ); - END; - RAISERROR(N'check_id 33: Potential filtered indexes based on column names.', 0,1) WITH NOWAIT; + RAISERROR(N'check_id 68: Identity columns within 30 percent of the end of range', 0,1) WITH NOWAIT; + -- Allowed Ranges: + --int -2,147,483,648 to 2,147,483,647 + --smallint -32,768 to 32,768 + --tinyint 0 to 255 INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, secret_columns, index_usage_summary, index_size_summary ) - SELECT 33 AS check_id, - i.index_sanity_id AS index_sanity_id, - 250 AS Priority, - N'Feature-Phobic Indexes' AS findings_group, - N'Potential filtered index (based on column name)' AS finding, - [database_name] AS [Database Name], - N'http://BrentOzar.com/go/IndexFeatures' AS URL, - N'A column name in this index suggests it might be a candidate for filtering (is%, %archive%, %active%, %flag%)' AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - sz.index_size_summary - FROM #IndexColumns ic - JOIN #IndexSanity i ON ic.[object_id]=i.[object_id] - AND ic.database_id =i.database_id - AND ic.schema_name = i.schema_name - AND ic.[index_id]=i.[index_id] - AND i.[index_id] > 1 /* non-clustered index */ - JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id - WHERE (column_name LIKE 'is%' - OR column_name LIKE '%archive%' - OR column_name LIKE '%active%' - OR column_name LIKE '%flag%') - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 34: Filtered index definition columns not in index definition', 0,1) WITH NOWAIT; - - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 34 AS check_id, - i.index_sanity_id, + SELECT 68 AS check_id, + i.index_sanity_id, 80 AS Priority, - N'Forgetful Indexes' AS findings_group, - N'Filter Columns Not In Index Definition' AS finding, + N'Abnormal Psychology' AS findings_group, + N'Identity Column Within ' + + CAST (calc1.percent_remaining AS NVARCHAR(256)) + + N' Percent End of Range' AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/IndexFeatures' AS URL, - N'The index ' - + QUOTENAME(i.index_name) - + N' on [' - + i.db_schema_object_name - + N'] has a filter on [' - + i.filter_definition - + N'] but is missing [' - + LTRIM(i.filter_columns_not_in_index) - + N'] from the index definition.' - AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - sz.index_size_summary + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, + i.db_schema_object_name + N'.' + QUOTENAME(ic.column_name) + + N' is an identity with type ' + ic.system_type_name + + N', last value of ' + + ISNULL((CONVERT(NVARCHAR(256),CAST(ic.last_value AS DECIMAL(38,0)), 1)),N'NULL') + + N', seed of ' + + ISNULL((CONVERT(NVARCHAR(256),CAST(ic.seed_value AS DECIMAL(38,0)), 1)),N'NULL') + + N', increment of ' + CAST(ic.increment_value AS NVARCHAR(256)) + + N', and range of ' + + CASE ic.system_type_name WHEN 'int' THEN N'+/- 2,147,483,647' + WHEN 'smallint' THEN N'+/- 32,768' + WHEN 'tinyint' THEN N'0 to 255' + ELSE 'unknown' + END + AS details, + i.index_definition, + secret_columns, + ISNULL(i.index_usage_summary,''), + ISNULL(ip.index_size_summary,'') FROM #IndexSanity i - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.filter_columns_not_in_index IS NOT NULL - ORDER BY i.db_schema_object_indexid - OPTION ( RECOMPILE ); - - ---------------------------------------- - --Self Loathing Indexes : Check_id 40-49 + JOIN #IndexColumns ic ON + i.object_id=ic.object_id + AND i.database_id = ic.database_id + AND i.schema_name = ic.schema_name + AND i.index_id IN (0,1) /* heaps and cx only */ + AND ic.is_identity=1 + AND ic.system_type_name IN ('tinyint', 'smallint', 'int') + JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id + CROSS APPLY ( + SELECT CAST(CASE WHEN ic.increment_value >= 0 + THEN + CASE ic.system_type_name + WHEN 'int' THEN (2147483647 - (ISNULL(ic.last_value,ic.seed_value) + ic.increment_value)) / 2147483647.*100 + WHEN 'smallint' THEN (32768 - (ISNULL(ic.last_value,ic.seed_value) + ic.increment_value)) / 32768.*100 + WHEN 'tinyint' THEN ( 255 - (ISNULL(ic.last_value,ic.seed_value) + ic.increment_value)) / 255.*100 + ELSE 999 + END + ELSE --ic.increment_value is negative + CASE ic.system_type_name + WHEN 'int' THEN ABS(-2147483647 - (ISNULL(ic.last_value,ic.seed_value) + ic.increment_value)) / 2147483647.*100 + WHEN 'smallint' THEN ABS(-32768 - (ISNULL(ic.last_value,ic.seed_value) + ic.increment_value)) / 32768.*100 + WHEN 'tinyint' THEN ABS( 0 - (ISNULL(ic.last_value,ic.seed_value) + ic.increment_value)) / 255.*100 + ELSE -1 + END + END AS NUMERIC(5,1)) AS percent_remaining + ) AS calc1 + WHERE i.index_id IN (1,0) + AND calc1.percent_remaining <= 30 + OPTION (RECOMPILE); + + + RAISERROR(N'check_id 72: Columnstore indexes with Trace Flag 834', 0,1) WITH NOWAIT; + IF EXISTS (SELECT * FROM #IndexSanity WHERE index_type IN (5,6)) + AND EXISTS (SELECT * FROM #TraceStatus WHERE TraceFlag = 834 AND status = 1) + BEGIN + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 72 AS check_id, + i.index_sanity_id, + 80 AS Priority, + N'Abnormal Psychology' AS findings_group, + 'Columnstore Indexes with Trace Flag 834' AS finding, + [database_name] AS [Database Name], + N'https://support.microsoft.com/en-us/kb/3210239' AS URL, + i.db_schema_object_indexid AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + ISNULL(sz.index_size_summary,'') AS index_size_summary + FROM #IndexSanity AS i + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + WHERE i.index_type IN (5,6) + OPTION ( RECOMPILE ); + END; + ---------------------------------------- - BEGIN - - RAISERROR(N'check_id 40: Fillfactor in nonclustered 80 percent or less', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + --Statistics Info: Check_id 90-99 + ---------------------------------------- + + RAISERROR(N'check_id 90: Outdated 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 ) - SELECT 40 AS check_id, - i.index_sanity_id, - 100 AS Priority, - N'Self Loathing Indexes' AS findings_group, - N'Low Fill Factor: nonclustered index' AS finding, - [database_name] AS [Database Name], - N'http://BrentOzar.com/go/SelfLoathing' AS URL, - CAST(fill_factor AS NVARCHAR(10)) + N'% fill factor on ' + db_schema_object_indexid + N'. '+ - CASE WHEN (last_user_update IS NULL OR user_updates < 1) - THEN N'No writes have been made.' - ELSE - N'Last write was ' + CONVERT(NVARCHAR(16),last_user_update,121) + N' and ' + - CAST(user_updates AS NVARCHAR(25)) + N' updates have been made.' - END - AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - sz.index_size_summary - FROM #IndexSanity AS i - JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id - WHERE index_id > 1 - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) - AND fill_factor BETWEEN 1 AND 80 OPTION ( RECOMPILE ); + SELECT 90 AS check_id, + 90 AS Priority, + 'Functioning Statistaholics' AS findings_group, + 'Statistics Not Updated Recently', + s.database_name, + 'https://www.brentozar.com/go/stats' AS URL, + 'Statistics on this table were last updated ' + + CASE WHEN s.last_statistics_update IS NULL THEN N' NEVER ' + ELSE CONVERT(NVARCHAR(20), s.last_statistics_update) + + ' have had ' + CONVERT(NVARCHAR(100), s.modification_counter) + + ' modifications in that time, which is ' + + CONVERT(NVARCHAR(100), s.percent_modifications) + + '% of the table.' + END 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.last_statistics_update <= CONVERT(DATETIME, GETDATE() - 30) + AND s.percent_modifications >= 10. + AND s.rows >= 10000 + OPTION ( RECOMPILE ); - RAISERROR(N'check_id 40: Fillfactor in clustered 80 percent or less', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + RAISERROR(N'check_id 91: Statistics with a low 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 40 AS check_id, - i.index_sanity_id, - 100 AS Priority, - N'Self Loathing Indexes' AS findings_group, - N'Low Fill Factor: clustered index' AS finding, - [database_name] AS [Database Name], - N'http://BrentOzar.com/go/SelfLoathing' AS URL, - N'Fill factor on ' + db_schema_object_indexid + N' is ' + CAST(fill_factor AS NVARCHAR(10)) + N'%. '+ - CASE WHEN (last_user_update IS NULL OR user_updates < 1) - THEN N'No writes have been made.' - ELSE - N'Last write was ' + CONVERT(NVARCHAR(16),last_user_update,121) + N' and ' + - CAST(user_updates AS NVARCHAR(25)) + N' updates have been made.' - END - AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - sz.index_size_summary - FROM #IndexSanity AS i - JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id - WHERE index_id = 1 - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) - AND fill_factor BETWEEN 1 AND 80 OPTION ( RECOMPILE ); + SELECT 91 AS check_id, + 90 AS Priority, + 'Functioning Statistaholics' AS findings_group, + 'Low Sampling Rates', + s.database_name, + 'https://www.brentozar.com/go/stats' AS URL, + 'Only ' + CONVERT(NVARCHAR(100), s.percent_sampled) + '% of the rows were sampled during the last statistics update. This may lead to poor cardinality estimates.' 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.rows BETWEEN 10000 AND 1000000 AND s.percent_sampled < 10) + OR (s.rows > 1000000 AND s.percent_sampled < 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 ) + SELECT 92 AS check_id, + 90 AS Priority, + 'Functioning Statistaholics' AS findings_group, + 'Statistics With NO RECOMPUTE', + s.database_name, + 'https://www.brentozar.com/go/stats' AS URL, + 'The statistic ' + QUOTENAME(s.statistics_name) + ' is set to not recompute. This can be helpful if data is really skewed, but harmful if you expect automatic statistics updates.' 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.no_recompute = 1 + OPTION ( RECOMPILE ); - RAISERROR(N'check_id 41: Hypothetical indexes ', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + + 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 ) - SELECT 41 AS check_id, - i.index_sanity_id, - 150 AS Priority, - N'Self Loathing Indexes' AS findings_group, - N'Hypothetical Index' AS finding, - [database_name] AS [Database Name], - N'http://BrentOzar.com/go/SelfLoathing' AS URL, - N'Hypothetical Index: ' + db_schema_object_indexid AS details, - i.index_definition, - i.secret_columns, - N'' AS index_usage_summary, - N'' AS index_size_summary - FROM #IndexSanity AS i - WHERE is_hypothetical = 1 - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) - OPTION ( RECOMPILE ); + SELECT 94 AS check_id, + 100 AS Priority, + 'Serial Forcer' AS findings_group, + 'Check Constraint with Scalar UDF' AS finding, + cc.database_name, + 'https://www.brentozar.com/go/computedscalar' AS URL, + 'The check constraint ' + QUOTENAME(cc.constraint_name) + ' on ' + QUOTENAME(cc.schema_name) + '.' + QUOTENAME(cc.table_name) + ' is based on ' + cc.definition + + '. That indicates it may reference a scalar function, or a CLR function with data access, which can cause all queries and maintenance to run serially.' AS details, + cc.column_definition, + 'N/A' AS secret_columns, + 'N/A' AS index_usage_summary, + 'N/A' AS index_size_summary + FROM #CheckConstraints AS cc + WHERE cc.is_function = 1 + OPTION ( RECOMPILE ); + RAISERROR(N'check_id 99: Computed Columns 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 ) + SELECT 99 AS check_id, + 100 AS Priority, + 'Serial Forcer' AS findings_group, + 'Computed Column with Scalar UDF' AS finding, + cc.database_name, + 'https://www.brentozar.com/go/serialudf' AS URL, + 'The computed column ' + QUOTENAME(cc.column_name) + ' on ' + QUOTENAME(cc.schema_name) + '.' + QUOTENAME(cc.table_name) + ' is based on ' + cc.definition + + '. That indicates it may reference a scalar function, or a CLR function with data access, which can cause all queries and maintenance to run serially.' AS details, + cc.column_definition, + 'N/A' AS secret_columns, + 'N/A' AS index_usage_summary, + 'N/A' AS index_size_summary + FROM #ComputedColumns AS cc + WHERE cc.is_function = 1 + OPTION ( RECOMPILE ); - RAISERROR(N'check_id 42: Disabled indexes', 0,1) WITH NOWAIT; - --Note: disabled NC indexes will have O rows in #IndexSanitySize! + + + END /* IF @Mode IN (0, 4) DIAGNOSE priorities 1-100 */ + + + + + + + + + + + + + + + + + + + + + + + + + + IF @Mode = 4 /* DIAGNOSE*/ + BEGIN; + RAISERROR(N'@Mode=4, running rules for priorities 101+.', 0,1) WITH NOWAIT; + + RAISERROR(N'check_id 21: More Than 5 Percent NC Indexes Are Unused', 0,1) WITH NOWAIT; + DECLARE @percent_NC_indexes_unused NUMERIC(29,1); + DECLARE @NC_indexes_unused_reserved_MB NUMERIC(29,1); + + SELECT @percent_NC_indexes_unused = ( 100.00 * SUM(CASE + WHEN total_reads = 0 + THEN 1 + ELSE 0 + END) ) / COUNT(*), + @NC_indexes_unused_reserved_MB = SUM(CASE + WHEN total_reads = 0 + THEN sz.total_reserved_MB + ELSE 0 + END) + FROM #IndexSanity i + 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()) + 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, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 21 AS check_id, + MAX(i.index_sanity_id) AS index_sanity_id, + 150 AS Priority, + N'Index Hoarder' AS findings_group, + N'More Than 5 Percent NC Indexes Are Unused' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/IndexHoarder' AS URL, + CAST (@percent_NC_indexes_unused AS NVARCHAR(30)) + N' percent NC indexes (' + CAST(COUNT(*) AS NVARCHAR(10)) + N') unused. ' + + N'These take up ' + CAST (@NC_indexes_unused_reserved_MB AS NVARCHAR(30)) + N'MB of space.' AS details, + i.database_name + ' (' + CAST (COUNT(*) AS NVARCHAR(30)) + N' indexes)' AS index_definition, + '' AS secret_columns, + CAST(SUM(total_reads) AS NVARCHAR(256)) + N' reads (ALL); ' + + CAST(SUM([user_updates]) AS NVARCHAR(256)) + N' writes (ALL)' AS index_usage_summary, + + REPLACE(CONVERT(NVARCHAR(30),CAST(MAX([total_rows]) AS MONEY), 1), '.00', '') + N' rows (MAX)' + + CASE WHEN SUM(total_reserved_MB) > 1024 THEN + N'; ' + CAST(CAST(SUM(total_reserved_MB)/1024. AS NUMERIC(29,1)) AS NVARCHAR(30)) + 'GB (ALL)' + WHEN SUM(total_reserved_MB) > 0 THEN + N'; ' + CAST(CAST(SUM(total_reserved_MB) AS NUMERIC(29,1)) AS NVARCHAR(30)) + 'MB (ALL)' + ELSE '' + END AS index_size_summary + FROM #IndexSanity i + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + 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()) + GROUP BY i.database_name + OPTION ( RECOMPILE ); + + RAISERROR(N'check_id 23: Indexes with 7 or more columns. (Borderline)', 0,1) WITH NOWAIT; INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, secret_columns, index_usage_summary, index_size_summary ) - SELECT 42 AS check_id, - index_sanity_id, - 150 AS Priority, - N'Self Loathing Indexes' AS findings_group, - N'Disabled Index' AS finding, + SELECT 23 AS check_id, + i.index_sanity_id, + 150 AS Priority, + N'Index Hoarder' AS findings_group, + N'Borderline: Wide Indexes (7 or More Columns)' AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/SelfLoathing' AS URL, - N'Disabled Index:' + db_schema_object_indexid AS details, - i.index_definition, - i.secret_columns, + N'https://www.brentozar.com/go/IndexHoarder' AS URL, + CAST(count_key_columns + count_included_columns AS NVARCHAR(10)) + ' columns on ' + + i.db_schema_object_indexid AS details, i.index_definition, + i.secret_columns, i.index_usage_summary, - 'DISABLED' AS index_size_summary + sz.index_size_summary FROM #IndexSanity AS i - WHERE is_disabled = 1 - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) + JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id + WHERE ( count_key_columns + count_included_columns ) >= 7 OPTION ( RECOMPILE ); - RAISERROR(N'check_id 43: Heaps with forwarded records', 0,1) WITH NOWAIT; - WITH heaps_cte - AS ( SELECT [object_id], - [database_id], - [schema_name], - SUM(forwarded_fetch_count) AS forwarded_fetch_count, - SUM(leaf_delete_count) AS leaf_delete_count - FROM #IndexPartitionSanity - GROUP BY [object_id], - [database_id], - [schema_name] - HAVING SUM(forwarded_fetch_count) > 0) + RAISERROR(N'check_id 24: Wide clustered indexes (> 3 columns or > 16 bytes).', 0,1) WITH NOWAIT; + WITH count_columns AS ( + SELECT database_id, [object_id], + SUM(CASE max_length WHEN -1 THEN 0 ELSE max_length END) AS sum_max_length + FROM #IndexColumns ic + WHERE index_id IN (1,0) /*Heap or clustered only*/ + AND key_ordinal > 0 + GROUP BY database_id, object_id + ) INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, secret_columns, index_usage_summary, index_size_summary ) - SELECT 43 AS check_id, - i.index_sanity_id, - 100 AS Priority, - N'Self Loathing Indexes' AS findings_group, - N'Heaps with forwarded records' AS finding, + SELECT 24 AS check_id, + i.index_sanity_id, + 150 AS Priority, + N'Index Hoarder' AS findings_group, + N'Wide Clustered Index (> 3 columns OR > 16 bytes)' AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/SelfLoathing' AS URL, - CASE WHEN h.forwarded_fetch_count >= 922337203685477 THEN '>= 922,337,203,685,477' - WHEN @DaysUptime < 1 THEN CAST(h.forwarded_fetch_count AS NVARCHAR(256)) + N' forwarded fetches against heap: ' + db_schema_object_indexid - ELSE REPLACE(CONVERT(NVARCHAR(256),CAST(CAST( - (h.forwarded_fetch_count /*/@DaysUptime */) - AS BIGINT) AS MONEY), 1), '.00', '') - END + N' forwarded fetches per day against heap: ' - + db_schema_object_indexid AS details, - i.index_definition, - i.secret_columns, + N'https://www.brentozar.com/go/IndexHoarder' AS URL, + CAST (i.count_key_columns AS NVARCHAR(10)) + N' columns with potential size of ' + + CAST(cc.sum_max_length AS NVARCHAR(10)) + + N' bytes in clustered index:' + i.db_schema_object_name + + N'. ' + + (SELECT CAST(COUNT(*) AS NVARCHAR(23)) + FROM #IndexSanity i2 + WHERE i2.[object_id]=i.[object_id] + AND i2.database_id = i.database_id + AND i2.index_id <> 1 + AND i2.is_disabled=0 + AND i2.is_hypothetical=0) + + N' NC indexes on the table.' + AS details, + i.index_definition, + secret_columns, i.index_usage_summary, - sz.index_size_summary + ip.index_size_summary FROM #IndexSanity i - JOIN heaps_cte h ON i.[object_id] = h.[object_id] - AND i.[database_id] = h.[database_id] - AND i.[schema_name] = h.[schema_name] - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.index_id = 0 - AND h.forwarded_fetch_count / @DaysUptime > 1000 - AND sz.total_reserved_MB >= CASE WHEN NOT (@GetAllDatabases = 1 OR @Mode = 4) THEN @ThresholdMB ELSE sz.total_reserved_MB END - OPTION ( RECOMPILE ); + JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id + JOIN count_columns AS cc ON i.[object_id]=cc.[object_id] + AND i.database_id = cc.database_id + WHERE index_id = 1 /* clustered only */ + AND + (count_key_columns > 3 /*More than three key columns.*/ + OR cc.sum_max_length > 16 /*More than 16 bytes in key */) + AND i.is_CX_columnstore = 0 + ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); - RAISERROR(N'check_id 49: Heaps with deletes', 0,1) WITH NOWAIT; - WITH heaps_cte - AS ( SELECT [object_id], - [database_id], - [schema_name], - SUM(leaf_delete_count) AS leaf_delete_count - FROM #IndexPartitionSanity - GROUP BY [object_id], - [database_id], - [schema_name] - HAVING SUM(forwarded_fetch_count) < 1000 * @DaysUptime /* Only alert about indexes with no forwarded fetches - we already alerted about those in check_id 43 */ - AND SUM(leaf_delete_count) > 0) + RAISERROR(N'check_id 25: Addicted to nullable columns.', 0,1) WITH NOWAIT; + WITH count_columns AS ( + SELECT [object_id], + [database_id], + [schema_name], + SUM(CASE is_nullable WHEN 1 THEN 0 ELSE 1 END) AS non_nullable_columns, + COUNT(*) AS total_columns + FROM #IndexColumns ic + WHERE index_id IN (1,0) /*Heap or clustered only*/ + GROUP BY [object_id], + [database_id], + [schema_name] + ) INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, secret_columns, index_usage_summary, index_size_summary ) - SELECT 49 AS check_id, - i.index_sanity_id, + SELECT 25 AS check_id, + i.index_sanity_id, 200 AS Priority, - N'Self Loathing Indexes' AS findings_group, - N'Heaps with deletes' AS finding, + N'Index Hoarder' AS findings_group, + N'Addicted to Nulls' AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/SelfLoathing' AS URL, - CAST(h.leaf_delete_count AS NVARCHAR(256)) + N' deletes against heap:' - + db_schema_object_indexid AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - sz.index_size_summary + N'https://www.brentozar.com/go/IndexHoarder' AS URL, + i.db_schema_object_name + + N' allows null in ' + CAST((total_columns-non_nullable_columns) AS NVARCHAR(10)) + + N' of ' + CAST(total_columns AS NVARCHAR(10)) + + N' columns.' AS details, + i.index_definition, + secret_columns, + ISNULL(i.index_usage_summary,''), + ISNULL(ip.index_size_summary,'') FROM #IndexSanity i - JOIN heaps_cte h ON i.[object_id] = h.[object_id] - AND i.[database_id] = h.[database_id] - AND i.[schema_name] = h.[schema_name] - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.index_id = 0 - AND sz.total_reserved_MB >= CASE WHEN NOT (@GetAllDatabases = 1 OR @Mode = 4) THEN @ThresholdMB ELSE sz.total_reserved_MB END - OPTION ( RECOMPILE ); + JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id + JOIN count_columns AS cc ON i.[object_id]=cc.[object_id] + AND cc.database_id = ip.database_id + AND cc.[schema_name] = ip.[schema_name] + WHERE i.index_id IN (1,0) + AND cc.non_nullable_columns < 2 + AND cc.total_columns > 3 + ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); - RAISERROR(N'check_id 44: Large Heaps with reads or writes.', 0,1) WITH NOWAIT; - WITH heaps_cte - AS ( SELECT [object_id], - [database_id], - [schema_name], - SUM(forwarded_fetch_count) AS forwarded_fetch_count, - SUM(leaf_delete_count) AS leaf_delete_count - FROM #IndexPartitionSanity - GROUP BY [object_id], + RAISERROR(N'check_id 26: Wide tables (35+ cols or > 2000 non-LOB bytes).', 0,1) WITH NOWAIT; + WITH count_columns AS ( + SELECT [object_id], + [database_id], + [schema_name], + SUM(CASE max_length WHEN -1 THEN 1 ELSE 0 END) AS count_lob_columns, + SUM(CASE max_length WHEN -1 THEN 0 ELSE max_length END) AS sum_max_length, + COUNT(*) AS total_columns + FROM #IndexColumns ic + WHERE index_id IN (1,0) /*Heap or clustered only*/ + GROUP BY [object_id], [database_id], [schema_name] - HAVING SUM(forwarded_fetch_count) > 0 - OR SUM(leaf_delete_count) > 0) + ) INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, secret_columns, index_usage_summary, index_size_summary ) - SELECT 44 AS check_id, - i.index_sanity_id, - 100 AS Priority, - N'Self Loathing Indexes' AS findings_group, - N'Large Active heap' AS finding, + SELECT 26 AS check_id, + i.index_sanity_id, + 150 AS Priority, + N'Index Hoarder' AS findings_group, + N'Wide Tables: 35+ cols or > 2000 non-LOB bytes' AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/SelfLoathing' AS URL, - N'Should this table be a heap? ' + db_schema_object_indexid AS details, - i.index_definition, - 'N/A' AS secret_columns, + N'https://www.brentozar.com/go/IndexHoarder' AS URL, + i.db_schema_object_name + + N' has ' + CAST((total_columns) AS NVARCHAR(10)) + + N' total columns with a max possible width of ' + CAST(sum_max_length AS NVARCHAR(10)) + + N' bytes.' + + CASE WHEN count_lob_columns > 0 THEN CAST((count_lob_columns) AS NVARCHAR(10)) + + ' columns are LOB types.' ELSE '' + END + AS details, + i.index_definition, + secret_columns, + ISNULL(i.index_usage_summary,''), + ISNULL(ip.index_size_summary,'') + FROM #IndexSanity i + JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id + JOIN count_columns AS cc ON i.[object_id]=cc.[object_id] + AND cc.database_id = i.database_id + AND cc.[schema_name] = i.[schema_name] + WHERE i.index_id IN (1,0) + AND + (cc.total_columns >= 35 OR + cc.sum_max_length >= 2000) + ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); + + RAISERROR(N'check_id 27: Addicted to strings.', 0,1) WITH NOWAIT; + WITH count_columns AS ( + SELECT [object_id], + [database_id], + [schema_name], + SUM(CASE WHEN system_type_name IN ('varchar','nvarchar','char') OR max_length=-1 THEN 1 ELSE 0 END) AS string_or_LOB_columns, + COUNT(*) AS total_columns + FROM #IndexColumns ic + WHERE index_id IN (1,0) /*Heap or clustered only*/ + GROUP BY [object_id], + [database_id], + [schema_name] + ) + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 27 AS check_id, + i.index_sanity_id, + 200 AS Priority, + N'Index Hoarder' AS findings_group, + N'Addicted to strings' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/IndexHoarder' AS URL, + i.db_schema_object_name + + N' uses string or LOB types for ' + CAST((string_or_LOB_columns) AS NVARCHAR(10)) + + N' of ' + CAST(total_columns AS NVARCHAR(10)) + + N' columns. Check if data types are valid.' AS details, + i.index_definition, + secret_columns, + ISNULL(i.index_usage_summary,''), + ISNULL(ip.index_size_summary,'') + FROM #IndexSanity i + JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id + JOIN count_columns AS cc ON i.[object_id]=cc.[object_id] + AND cc.database_id = i.database_id + AND cc.[schema_name] = i.[schema_name] + CROSS APPLY (SELECT cc.total_columns - string_or_LOB_columns AS non_string_or_lob_columns) AS calc1 + WHERE i.index_id IN (1,0) + AND calc1.non_string_or_lob_columns <= 1 + AND cc.total_columns > 3 + ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); + + RAISERROR(N'check_id 28: Non-unique clustered index.', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 28 AS check_id, + i.index_sanity_id, + 150 AS Priority, + N'Index Hoarder' AS findings_group, + N'Non-Unique Clustered JIndex' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/IndexHoarder' AS URL, + N'Uniquifiers will be required! Clustered index: ' + i.db_schema_object_name + + N' and all NC indexes. ' + + (SELECT CAST(COUNT(*) AS NVARCHAR(23)) + FROM #IndexSanity i2 + WHERE i2.[object_id]=i.[object_id] + AND i2.database_id = i.database_id + AND i2.index_id <> 1 + AND i2.is_disabled=0 + AND i2.is_hypothetical=0) + + N' NC indexes on the table.' + AS details, + i.index_definition, + secret_columns, i.index_usage_summary, - sz.index_size_summary + ip.index_size_summary FROM #IndexSanity i - LEFT JOIN heaps_cte h ON i.[object_id] = h.[object_id] - AND i.[database_id] = h.[database_id] - AND i.[schema_name] = h.[schema_name] - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.index_id = 0 - AND (i.total_reads > 0 OR i.user_updates > 0) - AND sz.total_rows >= 100000 - AND h.[object_id] IS NULL /*don't duplicate the prior check.*/ - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) + JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id + WHERE index_id = 1 /* clustered only */ + AND is_unique=0 /* not unique */ + AND is_CX_columnstore=0 /* not a clustered columnstore-- no unique option on those */ + ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); + + RAISERROR(N'check_id 29: NC indexes with 0 reads. (Borderline) and < 10,000 writes', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 29 AS check_id, + i.index_sanity_id, + 150 AS Priority, + N'Index Hoarder' AS findings_group, + N'Unused NC index with Low Writes' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/IndexHoarder' AS URL, + N'0 reads: ' + i.db_schema_object_indexid AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + sz.index_size_summary + FROM #IndexSanity AS i + JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id + WHERE i.total_reads=0 + AND i.user_updates < 10000 + 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()) + ORDER BY i.db_schema_object_indexid OPTION ( RECOMPILE ); - RAISERROR(N'check_id 45: Medium Heaps with reads or writes.', 0,1) WITH NOWAIT; - WITH heaps_cte - AS ( SELECT [object_id], - [database_id], - [schema_name], - SUM(forwarded_fetch_count) AS forwarded_fetch_count, - SUM(leaf_delete_count) AS leaf_delete_count - FROM #IndexPartitionSanity - GROUP BY [object_id], - [database_id], - [schema_name] - HAVING SUM(forwarded_fetch_count) > 0 - OR SUM(leaf_delete_count) > 0) + + ---------------------------------------- + --Feature-Phobic Indexes: Check_id 30-39 + ---------------------------------------- + RAISERROR(N'check_id 30: No indexes with includes', 0,1) WITH NOWAIT; + /* This does not work the way you'd expect with @GetAllDatabases = 1. For details: + https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/825 + */ + + SELECT database_name, + SUM(CASE WHEN count_included_columns > 0 THEN 1 ELSE 0 END) AS number_indexes_with_includes, + 100.* SUM(CASE WHEN count_included_columns > 0 THEN 1 ELSE 0 END) / ( 1.0 * COUNT(*) ) AS percent_indexes_with_includes + INTO #index_includes + FROM #IndexSanity + WHERE is_hypothetical = 0 + AND is_disabled = 0 + AND NOT (@GetAllDatabases = 1 OR @Mode = 0) + GROUP BY database_name; + + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 30 AS check_id, + NULL AS index_sanity_id, + 250 AS Priority, + N'Feature-Phobic Indexes' AS findings_group, + database_name AS [Database Name], + N'No Indexes Use Includes' AS finding, 'https://www.brentozar.com/go/IndexFeatures' AS URL, + N'No Indexes Use Includes' AS details, + database_name + N' (Entire database)' AS index_definition, + N'' AS secret_columns, + N'N/A' AS index_usage_summary, + N'N/A' AS index_size_summary + FROM #index_includes + WHERE number_indexes_with_includes = 0 + OPTION ( RECOMPILE ); + + RAISERROR(N'check_id 31: < 3 percent of indexes have includes', 0,1) WITH NOWAIT; INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, secret_columns, index_usage_summary, index_size_summary ) - SELECT 45 AS check_id, - i.index_sanity_id, - 100 AS Priority, - N'Self Loathing Indexes' AS findings_group, - N'Medium Active heap' AS finding, - [database_name] AS [Database Name], - N'http://BrentOzar.com/go/SelfLoathing' AS URL, - N'Should this table be a heap? ' + db_schema_object_indexid AS details, - i.index_definition, - 'N/A' AS secret_columns, - i.index_usage_summary, - sz.index_size_summary - FROM #IndexSanity i - LEFT JOIN heaps_cte h ON i.[object_id] = h.[object_id] - AND i.[database_id] = h.[database_id] - AND i.[schema_name] = h.[schema_name] - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.index_id = 0 - AND - (i.total_reads > 0 OR i.user_updates > 0) - AND sz.total_rows >= 10000 AND sz.total_rows < 100000 - AND h.[object_id] IS NULL /*don't duplicate the prior check.*/ - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) - OPTION ( RECOMPILE ); + SELECT 31 AS check_id, + NULL AS index_sanity_id, + 250 AS Priority, + N'Feature-Phobic Indexes' AS findings_group, + N'Few Indexes Use Includes' AS findings, + database_name AS [Database Name], + N'https://www.brentozar.com/go/IndexFeatures' AS URL, + N'Only ' + CAST(percent_indexes_with_includes AS NVARCHAR(20)) + '% of indexes have includes' AS details, + N'Entire database' AS index_definition, + N'' AS secret_columns, + N'N/A' AS index_usage_summary, + N'N/A' AS index_size_summary + FROM #index_includes + WHERE number_indexes_with_includes > 0 AND percent_indexes_with_includes <= 3 + OPTION ( RECOMPILE ); + + RAISERROR(N'check_id 32: filtered indexes and indexed views', 0,1) WITH NOWAIT; - RAISERROR(N'check_id 46: Small Heaps with reads or writes.', 0,1) WITH NOWAIT; - WITH heaps_cte - AS ( SELECT [object_id], - [database_id], - [schema_name], - SUM(forwarded_fetch_count) AS forwarded_fetch_count, - SUM(leaf_delete_count) AS leaf_delete_count - FROM #IndexPartitionSanity - GROUP BY [object_id], - [database_id], - [schema_name] - HAVING SUM(forwarded_fetch_count) > 0 - OR SUM(leaf_delete_count) > 0) INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, secret_columns, index_usage_summary, index_size_summary ) - SELECT 46 AS check_id, - i.index_sanity_id, - 100 AS Priority, - N'Self Loathing Indexes' AS findings_group, - N'Small Active heap' AS finding, - [database_name] AS [Database Name], - N'http://BrentOzar.com/go/SelfLoathing' AS URL, - N'Should this table be a heap? ' + db_schema_object_indexid AS details, - i.index_definition, - 'N/A' AS secret_columns, - i.index_usage_summary, - sz.index_size_summary - FROM #IndexSanity i - LEFT JOIN heaps_cte h ON i.[object_id] = h.[object_id] - AND i.[database_id] = h.[database_id] - AND i.[schema_name] = h.[schema_name] - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.index_id = 0 - AND - (i.total_reads > 0 OR i.user_updates > 0) - AND sz.total_rows < 10000 - AND h.[object_id] IS NULL /*don't duplicate the prior check.*/ - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) - OPTION ( RECOMPILE ); + SELECT DISTINCT + 32 AS check_id, + NULL AS index_sanity_id, + 250 AS Priority, + N'Feature-Phobic Indexes' AS findings_group, + N'No Filtered Indexes or Indexed Views' AS finding, + i.database_name AS [Database Name], + N'https://www.brentozar.com/go/IndexFeatures' AS URL, + N'These are NOT always needed-- but do you know when you would use them?' AS details, + i.database_name + N' (Entire database)' AS index_definition, + N'' AS secret_columns, + N'N/A' AS index_usage_summary, + N'N/A' AS index_size_summary + FROM #IndexSanity i + WHERE i.database_name NOT IN ( + SELECT database_name + FROM #IndexSanity + WHERE filter_definition <> '' ) + AND i.database_name NOT IN ( + SELECT database_name + FROM #IndexSanity + WHERE is_indexed_view = 1 ) + OPTION ( RECOMPILE ); + + RAISERROR(N'check_id 33: Potential filtered indexes based on column names.', 0,1) WITH NOWAIT; - RAISERROR(N'check_id 47: Heap with a Nonclustered Primary Key', 0,1) WITH NOWAIT; INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, secret_columns, index_usage_summary, index_size_summary ) - SELECT 47 AS check_id, - i.index_sanity_id, - 100 AS Priority, - N'Self Loathing Indexes' AS findings_group, - N'Heap with a Nonclustered Primary Key' AS finding, - [database_name] AS [Database Name], - N'http://BrentOzar.com/go/SelfLoathing' AS URL, - db_schema_object_indexid + N' is a HEAP with a Nonclustered Primary Key' AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - sz.index_size_summary - FROM #IndexSanity i - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.index_type = 2 AND i.is_primary_key = 1 - AND EXISTS - ( - SELECT 1/0 - FROM #IndexSanity AS isa - WHERE i.database_id = isa.database_id - AND i.object_id = isa.object_id - AND isa.index_id = 0 - ) - OPTION ( RECOMPILE ); + SELECT 33 AS check_id, + i.index_sanity_id AS index_sanity_id, + 250 AS Priority, + N'Feature-Phobic Indexes' AS findings_group, + N'Potential Filtered Index (Based on Column Name)' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/IndexFeatures' AS URL, + N'A column name in this index suggests it might be a candidate for filtering (is%, %archive%, %active%, %flag%)' AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + sz.index_size_summary + FROM #IndexColumns ic + JOIN #IndexSanity i ON ic.[object_id]=i.[object_id] + AND ic.database_id =i.database_id + AND ic.schema_name = i.schema_name + AND ic.[index_id]=i.[index_id] + AND i.[index_id] > 1 /* non-clustered index */ + JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id + WHERE (column_name LIKE 'is%' + OR column_name LIKE '%archive%' + OR column_name LIKE '%active%' + OR column_name LIKE '%flag%') + OPTION ( RECOMPILE ); - RAISERROR(N'check_id 48: Nonclustered indexes with a bad read to write ratio', 0,1) WITH NOWAIT; + RAISERROR(N'check_id 41: Hypothetical indexes ', 0,1) WITH NOWAIT; INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, secret_columns, index_usage_summary, index_size_summary ) - SELECT 48 AS check_id, - i.index_sanity_id, - 100 AS Priority, - N'Index Hoarder' AS findings_group, - N'NC index with High Writes:Reads' AS finding, - [database_name] AS [Database Name], - N'http://BrentOzar.com/go/IndexHoarder' AS URL, - N'Reads: ' - + REPLACE(CONVERT(NVARCHAR(30), CAST((i.total_reads) AS MONEY), 1), N'.00', N'') - + N' Writes: ' - + REPLACE(CONVERT(NVARCHAR(30), CAST((i.user_updates) AS MONEY), 1), N'.00', N'') - + N' on: ' - + i.db_schema_object_indexid AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - sz.index_size_summary - FROM #IndexSanity i - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.total_reads > 0 /*Not totally unused*/ - AND i.user_updates >= 10000 /*Decent write activity*/ - AND i.total_reads < 10000 - AND ((i.total_reads * 10) < i.user_updates) /*10x more writes than reads*/ - 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 - ORDER BY i.db_schema_object_indexid - OPTION ( RECOMPILE ); + SELECT 41 AS check_id, + i.index_sanity_id, + 150 AS Priority, + N'Self Loathing Indexes' AS findings_group, + N'Hypothetical Index' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/SelfLoathing' AS URL, + N'Hypothetical Index: ' + db_schema_object_indexid AS details, + i.index_definition, + i.secret_columns, + N'' AS index_usage_summary, + N'' AS index_size_summary + FROM #IndexSanity AS i + WHERE is_hypothetical = 1 + OPTION ( RECOMPILE ); - END; - ---------------------------------------- - --Indexaphobia - --Missing indexes with value >= 5 million: : Check_id 50-59 - ---------------------------------------- - BEGIN - RAISERROR(N'check_id 50: Indexaphobia.', 0,1) WITH NOWAIT; - WITH index_size_cte - AS ( SELECT i.database_id, - i.schema_name, - i.[object_id], - MAX(i.index_sanity_id) AS index_sanity_id, - ISNULL(NULLIF(MAX(DATEDIFF(DAY, i.create_date, SYSDATETIME())), 0), 1) AS create_days, - ISNULL ( - CAST(SUM(CASE WHEN index_id NOT IN (0,1) THEN 1 ELSE 0 END) - AS NVARCHAR(30))+ N' NC indexes exist (' + - CASE WHEN SUM(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) > 1024 - THEN CAST(CAST(SUM(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END )/1024. - AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'GB); ' - ELSE CAST(SUM(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) - AS NVARCHAR(30)) + N'MB); ' - END + - CASE WHEN MAX(sz.[total_rows]) >= 922337203685477 THEN '>= 922,337,203,685,477' - ELSE REPLACE(CONVERT(NVARCHAR(30),CAST(MAX(sz.[total_rows]) AS MONEY), 1), '.00', '') - END + - + N' Estimated Rows;' - ,N'') AS index_size_summary - FROM #IndexSanity AS i - LEFT JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id AND i.database_id = sz.database_id - WHERE i.is_hypothetical = 0 - AND i.is_disabled = 0 - GROUP BY i.database_id, i.schema_name, i.[object_id]) + RAISERROR(N'check_id 42: Disabled indexes', 0,1) WITH NOWAIT; + --Note: disabled NC indexes will have O rows in #IndexSanitySize! + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 42 AS check_id, + index_sanity_id, + 150 AS Priority, + N'Self Loathing Indexes' AS findings_group, + N'Disabled Index' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/SelfLoathing' AS URL, + N'Disabled Index:' + db_schema_object_indexid AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + 'DISABLED' AS index_size_summary + FROM #IndexSanity AS i + WHERE is_disabled = 1 + OPTION ( RECOMPILE ); + + RAISERROR(N'check_id 49: Heaps with deletes', 0,1) WITH NOWAIT; + WITH heaps_cte + AS ( SELECT [object_id], + [database_id], + [schema_name], + SUM(leaf_delete_count) AS leaf_delete_count + FROM #IndexPartitionSanity + GROUP BY [object_id], + [database_id], + [schema_name] + HAVING SUM(forwarded_fetch_count) < 1000 * @DaysUptime /* Only alert about indexes with no forwarded fetches - we already alerted about those in check_id 43 */ + AND SUM(leaf_delete_count) > 0) INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - index_usage_summary, index_size_summary, create_tsql, more_info ) - - SELECT check_id, t.index_sanity_id, t.check_id, t.findings_group, t.finding, t.[Database Name], t.URL, t.details, t.[definition], - index_estimated_impact, t.index_size_summary, create_tsql, more_info - FROM - ( - SELECT ROW_NUMBER() OVER (ORDER BY magic_benefit_number DESC) AS rownum, - 50 AS check_id, - sz.index_sanity_id, - 10 AS Priority, - N'Indexaphobia' AS findings_group, - N'High value missing index' AS finding, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 49 AS check_id, + i.index_sanity_id, + 200 AS Priority, + N'Self Loathing Indexes' AS findings_group, + N'Heaps with Deletes' AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/Indexaphobia' AS URL, - mi.[statement] + - N' Est. benefit per day: ' + - CASE WHEN magic_benefit_number >= 922337203685477 THEN '>= 922,337,203,685,477' - ELSE REPLACE(CONVERT(NVARCHAR(256),CAST(CAST( - (magic_benefit_number/@DaysUptime) - AS BIGINT) AS MONEY), 1), '.00', '') - END AS details, - missing_index_details AS [definition], - index_estimated_impact, - sz.index_size_summary, - mi.create_tsql, - mi.more_info, - magic_benefit_number, - mi.is_low - FROM #MissingIndexes mi - LEFT JOIN index_size_cte sz ON mi.[object_id] = sz.object_id - AND mi.database_id = sz.database_id - AND mi.schema_name = sz.schema_name - /* Minimum benefit threshold = 100k/day of uptime OR since table creation date, whichever is lower*/ - WHERE @ShowAllMissingIndexRequests=1 - OR ( @Mode = 4 AND (magic_benefit_number / CASE WHEN sz.create_days < @DaysUptime THEN sz.create_days ELSE @DaysUptime END) >= 100000 ) - OR (magic_benefit_number / CASE WHEN sz.create_days < @DaysUptime THEN sz.create_days ELSE @DaysUptime END) >= 100000 - ) AS t - WHERE t.rownum <= CASE WHEN (@Mode <> 4) THEN 20 ELSE t.rownum END - ORDER BY magic_benefit_number DESC - OPTION ( RECOMPILE ); - + N'https://www.brentozar.com/go/SelfLoathing' AS URL, + CAST(h.leaf_delete_count AS NVARCHAR(256)) + N' deletes against heap:' + + db_schema_object_indexid AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + sz.index_size_summary + FROM #IndexSanity i + JOIN heaps_cte h ON i.[object_id] = h.[object_id] + AND i.[database_id] = h.[database_id] + AND i.[schema_name] = h.[schema_name] + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + WHERE i.index_id = 0 + AND sz.total_reserved_MB >= CASE WHEN NOT (@GetAllDatabases = 1 OR @Mode = 4) THEN @ThresholdMB ELSE sz.total_reserved_MB END + OPTION ( RECOMPILE ); - END; ---------------------------------------- --Abnormal Psychology : Check_id 60-79 ---------------------------------------- - BEGIN RAISERROR(N'check_id 60: XML indexes', 0,1) WITH NOWAIT; INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, secret_columns, index_usage_summary, index_size_summary ) @@ -4008,9 +4218,9 @@ BEGIN; i.index_sanity_id, 150 AS Priority, N'Abnormal Psychology' AS findings_group, - N'XML Indexes' AS finding, + N'XML Index' AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/AbnormalPsychology' AS URL, + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, i.db_schema_object_indexid AS details, i.index_definition, i.secret_columns, @@ -4019,7 +4229,6 @@ BEGIN; FROM #IndexSanity AS i JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id WHERE i.is_XML = 1 - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) OPTION ( RECOMPILE ); RAISERROR(N'check_id 61: Columnstore indexes', 0,1) WITH NOWAIT; @@ -4034,7 +4243,7 @@ BEGIN; ELSE N'Clustered Columnstore Index' END AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/AbnormalPsychology' AS URL, + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, i.db_schema_object_indexid AS details, i.index_definition, i.secret_columns, @@ -4043,7 +4252,6 @@ BEGIN; FROM #IndexSanity AS i JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id WHERE i.is_NC_columnstore = 1 OR i.is_CX_columnstore=1 - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) OPTION ( RECOMPILE ); @@ -4054,9 +4262,9 @@ BEGIN; i.index_sanity_id, 150 AS Priority, N'Abnormal Psychology' AS findings_group, - N'Spatial indexes' AS finding, + N'Spatial Index' AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/AbnormalPsychology' AS URL, + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, i.db_schema_object_indexid AS details, i.index_definition, i.secret_columns, @@ -4065,7 +4273,6 @@ BEGIN; FROM #IndexSanity AS i JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id WHERE i.is_spatial = 1 - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) OPTION ( RECOMPILE ); RAISERROR(N'check_id 63: Compressed indexes', 0,1) WITH NOWAIT; @@ -4075,220 +4282,112 @@ BEGIN; i.index_sanity_id, 150 AS Priority, N'Abnormal Psychology' AS findings_group, - N'Compressed indexes' AS finding, - [database_name] AS [Database Name], - N'http://BrentOzar.com/go/AbnormalPsychology' AS URL, - i.db_schema_object_indexid + N'. COMPRESSION: ' + sz.data_compression_desc AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - ISNULL(sz.index_size_summary,'') AS index_size_summary - FROM #IndexSanity AS i - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE sz.data_compression_desc LIKE '%PAGE%' OR sz.data_compression_desc LIKE '%ROW%' - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 64: Partitioned', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 64 AS check_id, - i.index_sanity_id, - 150 AS Priority, - N'Abnormal Psychology' AS findings_group, - N'Partitioned indexes' AS finding, - [database_name] AS [Database Name], - N'http://BrentOzar.com/go/AbnormalPsychology' AS URL, - i.db_schema_object_indexid AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - ISNULL(sz.index_size_summary,'') AS index_size_summary - FROM #IndexSanity AS i - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.partition_key_column_name IS NOT NULL - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 65: Non-Aligned Partitioned', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 65 AS check_id, - i.index_sanity_id, - 150 AS Priority, - N'Abnormal Psychology' AS findings_group, - N'Non-Aligned index on a partitioned table' AS finding, - i.[database_name] AS [Database Name], - N'http://BrentOzar.com/go/AbnormalPsychology' AS URL, - i.db_schema_object_indexid AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - ISNULL(sz.index_size_summary,'') AS index_size_summary - FROM #IndexSanity AS i - JOIN #IndexSanity AS iParent ON - i.[object_id]=iParent.[object_id] - AND i.database_id = iParent.database_id - AND i.schema_name = iParent.schema_name - AND iParent.index_id IN (0,1) /* could be a partitioned heap or clustered table */ - AND iParent.partition_key_column_name IS NOT NULL /* parent is partitioned*/ - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.partition_key_column_name IS NULL - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 66: Recently created tables/indexes (1 week)', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 66 AS check_id, - i.index_sanity_id, - 200 AS Priority, - N'Abnormal Psychology' AS findings_group, - N'Recently created tables/indexes (1 week)' AS finding, - [database_name] AS [Database Name], - N'http://BrentOzar.com/go/AbnormalPsychology' AS URL, - i.db_schema_object_indexid + N' was created on ' + - CONVERT(NVARCHAR(16),i.create_date,121) + - N'. Tables/indexes which are dropped/created regularly require special methods for index tuning.' - AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - ISNULL(sz.index_size_summary,'') AS index_size_summary - FROM #IndexSanity AS i - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.create_date >= DATEADD(dd,-7,GETDATE()) - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 67: Recently modified tables/indexes (2 days)', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 67 AS check_id, - i.index_sanity_id, - 200 AS Priority, - N'Abnormal Psychology' AS findings_group, - N'Recently modified tables/indexes (2 days)' AS finding, + N'Compressed Index' AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/AbnormalPsychology' AS URL, - i.db_schema_object_indexid + N' was modified on ' + - CONVERT(NVARCHAR(16),i.modify_date,121) + - N'. A large amount of recently modified indexes may mean a lot of rebuilds are occurring each night.' - AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - ISNULL(sz.index_size_summary,'') AS index_size_summary - FROM #IndexSanity AS i - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.modify_date > DATEADD(dd,-2,GETDATE()) - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) - AND /*Exclude recently created tables.*/ - i.create_date < DATEADD(dd,-7,GETDATE()) - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 68: Identity columns within 30 percent of the end of range', 0,1) WITH NOWAIT; - -- Allowed Ranges: - --int -2,147,483,648 to 2,147,483,647 - --smallint -32,768 to 32,768 - --tinyint 0 to 255 - - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 68 AS check_id, - i.index_sanity_id, - 200 AS Priority, - N'Abnormal Psychology' AS findings_group, - N'Identity column within ' + - CAST (calc1.percent_remaining AS NVARCHAR(256)) - + N' percent end of range' AS finding, - [database_name] AS [Database Name], - N'http://BrentOzar.com/go/AbnormalPsychology' AS URL, - i.db_schema_object_name + N'.' + QUOTENAME(ic.column_name) - + N' is an identity with type ' + ic.system_type_name - + N', last value of ' - + ISNULL((CONVERT(NVARCHAR(256),CAST(ic.last_value AS DECIMAL(38,0)), 1)),N'NULL') - + N', seed of ' - + ISNULL((CONVERT(NVARCHAR(256),CAST(ic.seed_value AS DECIMAL(38,0)), 1)),N'NULL') - + N', increment of ' + CAST(ic.increment_value AS NVARCHAR(256)) - + N', and range of ' + - CASE ic.system_type_name WHEN 'int' THEN N'+/- 2,147,483,647' - WHEN 'smallint' THEN N'+/- 32,768' - WHEN 'tinyint' THEN N'0 to 255' - ELSE 'unknown' - END - AS details, - i.index_definition, - secret_columns, - ISNULL(i.index_usage_summary,''), - ISNULL(ip.index_size_summary,'') - FROM #IndexSanity i - JOIN #IndexColumns ic ON - i.object_id=ic.object_id - AND i.database_id = ic.database_id - AND i.schema_name = ic.schema_name - AND i.index_id IN (0,1) /* heaps and cx only */ - AND ic.is_identity=1 - AND ic.system_type_name IN ('tinyint', 'smallint', 'int') - JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id - CROSS APPLY ( - SELECT CAST(CASE WHEN ic.increment_value >= 0 - THEN - CASE ic.system_type_name - WHEN 'int' THEN (2147483647 - (ISNULL(ic.last_value,ic.seed_value) + ic.increment_value)) / 2147483647.*100 - WHEN 'smallint' THEN (32768 - (ISNULL(ic.last_value,ic.seed_value) + ic.increment_value)) / 32768.*100 - WHEN 'tinyint' THEN ( 255 - (ISNULL(ic.last_value,ic.seed_value) + ic.increment_value)) / 255.*100 - ELSE 999 - END - ELSE --ic.increment_value is negative - CASE ic.system_type_name - WHEN 'int' THEN ABS(-2147483647 - (ISNULL(ic.last_value,ic.seed_value) + ic.increment_value)) / 2147483647.*100 - WHEN 'smallint' THEN ABS(-32768 - (ISNULL(ic.last_value,ic.seed_value) + ic.increment_value)) / 32768.*100 - WHEN 'tinyint' THEN ABS( 0 - (ISNULL(ic.last_value,ic.seed_value) + ic.increment_value)) / 255.*100 - ELSE -1 - END - END AS NUMERIC(5,1)) AS percent_remaining - ) AS calc1 - WHERE i.index_id IN (1,0) - AND calc1.percent_remaining <= 30 - UNION ALL - SELECT 68 AS check_id, - i.index_sanity_id, - 200 AS Priority, - N'Abnormal Psychology' AS findings_group, - N'Identity column using a negative seed or increment other than 1' AS finding, - [database_name] AS [Database Name], - N'http://BrentOzar.com/go/AbnormalPsychology' AS URL, - i.db_schema_object_name + N'.' + QUOTENAME(ic.column_name) - + N' is an identity with type ' + ic.system_type_name - + N', last value of ' - + ISNULL((CONVERT(NVARCHAR(256),CAST(ic.last_value AS DECIMAL(38,0)), 1)),N'NULL') - + N', seed of ' - + ISNULL((CONVERT(NVARCHAR(256),CAST(ic.seed_value AS DECIMAL(38,0)), 1)),N'NULL') - + N', increment of ' + CAST(ic.increment_value AS NVARCHAR(256)) - + N', and range of ' + - CASE ic.system_type_name WHEN 'int' THEN N'+/- 2,147,483,647' - WHEN 'smallint' THEN N'+/- 32,768' - WHEN 'tinyint' THEN N'0 to 255' - ELSE 'unknown' - END - AS details, - i.index_definition, - secret_columns, - ISNULL(i.index_usage_summary,''), - ISNULL(ip.index_size_summary,'') - FROM #IndexSanity i - JOIN #IndexColumns ic ON - i.object_id=ic.object_id - AND i.database_id = ic.database_id - AND i.schema_name = ic.schema_name - AND i.index_id IN (0,1) /* heaps and cx only */ - AND ic.is_identity=1 - AND ic.system_type_name IN ('tinyint', 'smallint', 'int') - JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id - WHERE i.index_id IN (1,0) - AND (ic.seed_value < 0 OR ic.increment_value <> 1) - ORDER BY finding, details DESC - OPTION ( RECOMPILE ); + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, + i.db_schema_object_indexid + N'. COMPRESSION: ' + sz.data_compression_desc AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + ISNULL(sz.index_size_summary,'') AS index_size_summary + FROM #IndexSanity AS i + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + WHERE sz.data_compression_desc LIKE '%PAGE%' OR sz.data_compression_desc LIKE '%ROW%' + OPTION ( RECOMPILE ); + + RAISERROR(N'check_id 64: Partitioned', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 64 AS check_id, + i.index_sanity_id, + 150 AS Priority, + N'Abnormal Psychology' AS findings_group, + N'Partitioned Index' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, + i.db_schema_object_indexid AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + ISNULL(sz.index_size_summary,'') AS index_size_summary + FROM #IndexSanity AS i + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + WHERE i.partition_key_column_name IS NOT NULL + OPTION ( RECOMPILE ); + + RAISERROR(N'check_id 65: Non-Aligned Partitioned', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 65 AS check_id, + i.index_sanity_id, + 150 AS Priority, + N'Abnormal Psychology' AS findings_group, + N'Non-Aligned Index on a Partitioned Table' AS finding, + i.[database_name] AS [Database Name], + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, + i.db_schema_object_indexid AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + ISNULL(sz.index_size_summary,'') AS index_size_summary + FROM #IndexSanity AS i + JOIN #IndexSanity AS iParent ON + i.[object_id]=iParent.[object_id] + AND i.database_id = iParent.database_id + AND i.schema_name = iParent.schema_name + AND iParent.index_id IN (0,1) /* could be a partitioned heap or clustered table */ + AND iParent.partition_key_column_name IS NOT NULL /* parent is partitioned*/ + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + WHERE i.partition_key_column_name IS NULL + OPTION ( RECOMPILE ); + + RAISERROR(N'check_id 66: Recently created tables/indexes (1 week)', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 66 AS check_id, + i.index_sanity_id, + 200 AS Priority, + N'Abnormal Psychology' AS findings_group, + N'Recently Created Tables/Indexes (1 week)' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, + i.db_schema_object_indexid + N' was created on ' + + CONVERT(NVARCHAR(16),i.create_date,121) + + N'. Tables/indexes which are dropped/created regularly require special methods for index tuning.' + AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + ISNULL(sz.index_size_summary,'') AS index_size_summary + FROM #IndexSanity AS i + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + WHERE i.create_date >= DATEADD(dd,-7,GETDATE()) + OPTION ( RECOMPILE ); + + RAISERROR(N'check_id 67: Recently modified tables/indexes (2 days)', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 67 AS check_id, + i.index_sanity_id, + 200 AS Priority, + N'Abnormal Psychology' AS findings_group, + N'Recently Modified Tables/Indexes (2 days)' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, + i.db_schema_object_indexid + N' was modified on ' + + CONVERT(NVARCHAR(16),i.modify_date,121) + + N'. A large amount of recently modified indexes may mean a lot of rebuilds are occurring each night.' + AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + ISNULL(sz.index_size_summary,'') AS index_size_summary + FROM #IndexSanity AS i + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + WHERE i.modify_date > DATEADD(dd,-2,GETDATE()) + AND /*Exclude recently created tables.*/ + i.create_date < DATEADD(dd,-7,GETDATE()) + OPTION ( RECOMPILE ); RAISERROR(N'check_id 69: Column collation does not match database collation', 0,1) WITH NOWAIT; WITH count_columns AS ( @@ -4309,9 +4408,9 @@ BEGIN; i.index_sanity_id, 150 AS Priority, N'Abnormal Psychology' AS findings_group, - N'Column collation does not match database collation' AS finding, + N'Column Collation Does Not Match Database Collation' AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/AbnormalPsychology' AS URL, + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, i.db_schema_object_name + N' has ' + CAST(column_count AS NVARCHAR(20)) + N' column' + CASE WHEN column_count > 1 THEN 's' ELSE '' END @@ -4327,7 +4426,6 @@ BEGIN; AND cc.database_id = i.database_id AND cc.schema_name = i.schema_name WHERE i.index_id IN (1,0) - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); RAISERROR(N'check_id 70: Replicated columns', 0,1) WITH NOWAIT; @@ -4349,9 +4447,9 @@ BEGIN; i.index_sanity_id, 200 AS Priority, N'Abnormal Psychology' AS findings_group, - N'Replicated columns' AS finding, + N'Replicated Columns' AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/AbnormalPsychology' AS URL, + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, i.db_schema_object_name + N' has ' + CAST(replicated_column_count AS NVARCHAR(20)) + N' out of ' + CAST(column_count AS NVARCHAR(20)) @@ -4369,7 +4467,6 @@ BEGIN; AND i.schema_name = cc.schema_name WHERE i.index_id IN (1,0) AND replicated_column_count > 0 - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); @@ -4382,7 +4479,7 @@ BEGIN; N'Abnormal Psychology' AS findings_group, N'Cascading Updates or Deletes' AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/AbnormalPsychology' AS URL, + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, N'Foreign Key ' + foreign_key_name + N' on ' + QUOTENAME(parent_object_name) + N'(' + LTRIM(parent_fk_columns) + N')' + N' referencing ' + QUOTENAME(referenced_object_name) + N'(' + LTRIM(referenced_fk_columns) + N')' @@ -4400,32 +4497,8 @@ BEGIN; FROM #ForeignKeys fk WHERE ([delete_referential_action_desc] <> N'NO_ACTION' OR [update_referential_action_desc] <> N'NO_ACTION') - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) OPTION ( RECOMPILE ); - RAISERROR(N'check_id 72: Columnstore indexes with Trace Flag 834', 0,1) WITH NOWAIT; - IF EXISTS (SELECT * FROM #IndexSanity WHERE index_type IN (5,6)) - AND EXISTS (SELECT * FROM #TraceStatus WHERE TraceFlag = 834 AND status = 1) - BEGIN - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 72 AS check_id, - i.index_sanity_id, - 150 AS Priority, - N'Abnormal Psychology' AS findings_group, - 'Columnstore Indexes are being used in conjunction with trace flag 834. Visit the link to see why this can be a bad idea' AS finding, - [database_name] AS [Database Name], - N'https://support.microsoft.com/en-us/kb/3210239' AS URL, - i.db_schema_object_indexid AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - ISNULL(sz.index_size_summary,'') AS index_size_summary - FROM #IndexSanity AS i - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.index_type IN (5,6) - OPTION ( RECOMPILE ); - END; RAISERROR(N'check_id 73: In-Memory OLTP', 0,1) WITH NOWAIT; INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, @@ -4436,7 +4509,7 @@ BEGIN; N'Abnormal Psychology' AS findings_group, N'In-Memory OLTP' AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/AbnormalPsychology' AS URL, + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, i.db_schema_object_indexid AS details, i.index_definition, i.secret_columns, @@ -4445,15 +4518,53 @@ BEGIN; FROM #IndexSanity AS i JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id WHERE i.is_in_memory_oltp = 1 - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) OPTION ( RECOMPILE ); - END; + RAISERROR(N'check_id 74: Identity column with unusual seed', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 74 AS check_id, + i.index_sanity_id, + 200 AS Priority, + N'Abnormal Psychology' AS findings_group, + N'Identity Column Using a Negative Seed or Increment Other Than 1' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, + i.db_schema_object_name + N'.' + QUOTENAME(ic.column_name) + + N' is an identity with type ' + ic.system_type_name + + N', last value of ' + + ISNULL((CONVERT(NVARCHAR(256),CAST(ic.last_value AS DECIMAL(38,0)), 1)),N'NULL') + + N', seed of ' + + ISNULL((CONVERT(NVARCHAR(256),CAST(ic.seed_value AS DECIMAL(38,0)), 1)),N'NULL') + + N', increment of ' + CAST(ic.increment_value AS NVARCHAR(256)) + + N', and range of ' + + CASE ic.system_type_name WHEN 'int' THEN N'+/- 2,147,483,647' + WHEN 'smallint' THEN N'+/- 32,768' + WHEN 'tinyint' THEN N'0 to 255' + ELSE 'unknown' + END + AS details, + i.index_definition, + secret_columns, + ISNULL(i.index_usage_summary,''), + ISNULL(ip.index_size_summary,'') + FROM #IndexSanity i + JOIN #IndexColumns ic ON + i.object_id=ic.object_id + AND i.database_id = ic.database_id + AND i.schema_name = ic.schema_name + AND i.index_id IN (0,1) /* heaps and cx only */ + AND ic.is_identity=1 + AND ic.system_type_name IN ('tinyint', 'smallint', 'int') + JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id + WHERE i.index_id IN (1,0) + AND (ic.seed_value < 0 OR ic.increment_value <> 1) + ORDER BY finding, details DESC + OPTION ( RECOMPILE ); - ---------------------------------------- + ---------------------------------------- --Workaholics: Check_id 80-89 ---------------------------------------- - BEGIN RAISERROR(N'check_id 80: Most scanned indexes (index_usage_stats)', 0,1) WITH NOWAIT; INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, @@ -4468,9 +4579,9 @@ BEGIN; i.index_sanity_id AS index_sanity_id, 200 AS Priority, N'Workaholics' AS findings_group, - N'Scan-a-lots (index_usage_stats)' AS finding, + N'Scan-a-lots (index-usage-stats)' AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/Workaholics' AS URL, + N'https://www.brentozar.com/go/Workaholics' AS URL, REPLACE(CONVERT( NVARCHAR(50),CAST(i.user_scans AS MONEY),1),'.00','') + N' scans against ' + i.db_schema_object_indexid + N'. Latest scan: ' + ISNULL(CAST(i.last_user_scan AS NVARCHAR(128)),'?') + N'. ' @@ -4482,7 +4593,6 @@ BEGIN; FROM #IndexSanity i JOIN #IndexSanitySize iss ON i.index_sanity_id=iss.index_sanity_id WHERE ISNULL(i.user_scans,0) > 0 - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) ORDER BY i.user_scans * iss.total_reserved_MB DESC OPTION ( RECOMPILE ); @@ -4497,9 +4607,9 @@ BEGIN; i.index_sanity_id AS index_sanity_id, 200 AS Priority, N'Workaholics' AS findings_group, - N'Top recent accesses (index_op_stats)' AS finding, + N'Top Recent Accesses (index-op-stats)' AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/Workaholics' AS URL, + N'https://www.brentozar.com/go/Workaholics' AS URL, ISNULL(REPLACE( CONVERT(NVARCHAR(50),CAST((iss.total_range_scan_count + iss.total_singleton_lookup_count) AS MONEY),1), N'.00',N'') @@ -4514,84 +4624,10 @@ BEGIN; FROM #IndexSanity i JOIN #IndexSanitySize iss ON i.index_sanity_id=iss.index_sanity_id WHERE (ISNULL(iss.total_range_scan_count,0) > 0 OR ISNULL(iss.total_singleton_lookup_count,0) > 0) - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) ORDER BY ((iss.total_range_scan_count + iss.total_singleton_lookup_count) * iss.total_reserved_MB) DESC OPTION ( RECOMPILE ); - END; - - ---------------------------------------- - --Statistics Info: Check_id 90-99 - ---------------------------------------- - BEGIN - - RAISERROR(N'check_id 90: Outdated 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 ) - SELECT 90 AS check_id, - 200 AS Priority, - 'Functioning Statistaholics' AS findings_group, - 'Statistic Abandonment Issues', - s.database_name, - '' AS URL, - 'Statistics on this table were last updated ' + - CASE WHEN s.last_statistics_update IS NULL THEN N' NEVER ' - ELSE CONVERT(NVARCHAR(20), s.last_statistics_update) + - ' have had ' + CONVERT(NVARCHAR(100), s.modification_counter) + - ' modifications in that time, which is ' + - CONVERT(NVARCHAR(100), s.percent_modifications) + - '% of the table.' - END 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.last_statistics_update <= CONVERT(DATETIME, GETDATE() - 7) - AND s.percent_modifications >= 10. - AND s.rows >= 10000 - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 91: Statistics with a low 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 91 AS check_id, - 200 AS Priority, - 'Functioning Statistaholics' AS findings_group, - 'Antisocial Samples', - s.database_name, - '' AS URL, - 'Only ' + CONVERT(NVARCHAR(100), s.percent_sampled) + '% of the rows were sampled during the last statistics update. This may lead to poor cardinality estimates.' 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.rows_sampled < 1. - AND s.rows >= 10000 - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) - 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 ) - SELECT 92 AS check_id, - 200 AS Priority, - 'Functioning Statistaholics' AS findings_group, - 'Cyberphobic Samples', - s.database_name, - '' AS URL, - 'The statistic ' + QUOTENAME(s.statistics_name) + ' is set to not recompute. This can be helpful if data is really skewed, but harmful if you expect automatic statistics updates.' 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.no_recompute = 1 - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) - OPTION ( RECOMPILE ); RAISERROR(N'check_id 93: Statistics with filters', 0,1) WITH NOWAIT; INSERT #BlitzIndexResults ( check_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, @@ -4601,7 +4637,7 @@ BEGIN; 'Functioning Statistaholics' AS findings_group, 'Filter Fixation', s.database_name, - '' AS URL, + 'https://www.brentozar.com/go/stats' AS URL, 'The statistic ' + QUOTENAME(s.statistics_name) + ' is filtered on [' + s.filter_definition + ']. It could be part of a filtered index, or just a filtered statistic. This is purely informational.' 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, @@ -4609,34 +4645,8 @@ BEGIN; 'N/A' AS index_size_summary FROM #Statistics AS s WHERE s.has_filter = 1 - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) OPTION ( RECOMPILE ); - END; - - ---------------------------------------- - --Computed Column Info: Check_id 99-109 - ---------------------------------------- - BEGIN - - RAISERROR(N'check_id 99: Computed Columns 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 ) - SELECT 99 AS check_id, - 50 AS Priority, - 'Cold Calculators' AS findings_group, - 'Serial Forcer' AS finding, - cc.database_name, - '' AS URL, - 'The computed column ' + QUOTENAME(cc.column_name) + ' on ' + QUOTENAME(cc.schema_name) + '.' + QUOTENAME(cc.table_name) + ' is based on ' + cc.definition - + '. That indicates it may reference a scalar function, or a CLR function with data access, which can cause all queries and maintenance to run serially.' AS details, - cc.column_definition, - 'N/A' AS secret_columns, - 'N/A' AS index_usage_summary, - 'N/A' AS index_size_summary - FROM #ComputedColumns AS cc - WHERE cc.is_function = 1 - OPTION ( RECOMPILE ); RAISERROR(N'check_id 100: Computed Columns that are not Persisted.', 0,1) WITH NOWAIT; INSERT #BlitzIndexResults ( check_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, @@ -4656,20 +4666,16 @@ BEGIN; 'N/A' AS index_size_summary FROM #ComputedColumns AS cc WHERE cc.is_persisted = 0 - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) OPTION ( RECOMPILE ); - ---------------------------------------- - --Temporal Table Info: Check_id 110-119 - ---------------------------------------- RAISERROR(N'check_id 110: Temporal Tables.', 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 110 AS check_id, 200 AS Priority, - 'Temporal Tables' AS findings_group, - 'Obsessive Compulsive Tables', + 'Abnormal Psychology' AS findings_group, + 'Temporal Tables', t.database_name, '' AS URL, 'The table ' + QUOTENAME(t.schema_name) + '.' + QUOTENAME(t.table_name) + ' is a temporal table, with rows versioned in ' @@ -4680,33 +4686,39 @@ BEGIN; 'N/A' AS index_usage_summary, 'N/A' AS index_size_summary FROM #TemporalTables AS t - WHERE NOT (@GetAllDatabases = 1 OR @Mode = 0) OPTION ( RECOMPILE ); - ---------------------------------------- - --Check Constraint Info: Check_id 120-129 - ---------------------------------------- - RAISERROR(N'check_id 120: 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 ) - SELECT 99 AS check_id, - 50 AS Priority, - 'Obsessive Constraintive' AS findings_group, - 'Serial Forcer' AS finding, - cc.database_name, - 'https://www.brentozar.com/archive/2016/01/another-reason-why-scalar-functions-in-computed-columns-is-a-bad-idea/' AS URL, - 'The check constraint ' + QUOTENAME(cc.constraint_name) + ' on ' + QUOTENAME(cc.schema_name) + '.' + QUOTENAME(cc.table_name) + ' is based on ' + cc.definition - + '. That indicates it may reference a scalar function, or a CLR function with data access, which can cause all queries and maintenance to run serially.' AS details, - cc.column_definition, - 'N/A' AS secret_columns, - 'N/A' AS index_usage_summary, - 'N/A' AS index_size_summary - FROM #CheckConstraints AS cc - WHERE cc.is_function = 1 - OPTION ( RECOMPILE ); - END; + + END /* IF @Mode = 4 */ + + + + + + + + + + + + + + + + + + + + + + + + + + + RAISERROR(N'Insert a row to help people find help', 0,1) WITH NOWAIT; IF DATEDIFF(MM, @VersionDate, GETDATE()) > 6 @@ -4798,9 +4810,6 @@ BEGIN; br.index_sanity_id=sn.index_sanity_id LEFT JOIN #IndexCreateTsql ts ON br.index_sanity_id=ts.index_sanity_id - WHERE br.check_id IN ( 0, 1, 2, 11, 12, 13, - 22, 34, 43, 47, 48, - 50, 65, 68, 73, 99 ) ORDER BY br.Priority ASC, br.check_id ASC, br.blitz_result_id ASC, br.findings_group ASC OPTION (RECOMPILE); END; @@ -4830,8 +4839,9 @@ BEGIN; OPTION (RECOMPILE); END; - END; /* End @Mode=0 or 4 (diagnose)*/ - ELSE IF (@Mode=1) /*Summarize*/ +END /* End @Mode=0 or 4 (diagnose)*/ + +ELSE IF (@Mode=1) /*Summarize*/ BEGIN --This mode is to give some overall stats on the database. IF(@OutputType <> 'NONE') @@ -5503,7 +5513,7 @@ BEGIN; END; /* End @Mode=3 (index detail)*/ -END; + END TRY BEGIN CATCH From fe25af80b676027641b926dd075237e5f58a2cca Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Tue, 10 Nov 2020 08:15:09 -0800 Subject: [PATCH 022/662] sp_BlitzIndex documentation Fixing up formatting goofs. --- Documentation/sp_BlitzIndex_Checks_by_Priority.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/Documentation/sp_BlitzIndex_Checks_by_Priority.md b/Documentation/sp_BlitzIndex_Checks_by_Priority.md index bfa2cedab..2e85d16e0 100644 --- a/Documentation/sp_BlitzIndex_Checks_by_Priority.md +++ b/Documentation/sp_BlitzIndex_Checks_by_Priority.md @@ -22,11 +22,7 @@ If you want to add a new check, start at 121. | 80 | Abnormal Psychology | Columnstore Indexes with Trace Flag 834 | https://support.microsoft.com/en-us/kb/3210239 | 72 | | 80 | Abnormal Psychology | Identity Column Near End of Range | https://www.brentozar.com/go/AbnormalPsychology | 68 | | 80 | Abnormal Psychology | Filter Columns Not In Index Definition | https://www.brentozar.com/go/IndexFeatures | 34 | - | 90 | Functioning Statistaholics | Low Sampling Rates | https://www.brentozar.com/go/stats | 91 | - - - | 90 | Functioning Statistaholics | Statistics Not Updated Recently | https://www.brentozar.com/go/stats | 90 | | 90 | Functioning Statistaholics | Statistics with NO RECOMPUTE | https://www.brentozar.com/go/stats | 92 | | 100 | Index Hoarder | NC index with High Writes:Reads | https://www.brentozar.com/go/IndexHoarder | 48 | @@ -39,8 +35,6 @@ If you want to add a new check, start at 121. | 100 | Self Loathing Indexes | Small Active Heap | https://www.brentozar.com/go/SelfLoathing | 46 | | 100 | Serial Forcer | Computed Column with Scalar UDF | https://www.brentozar.com/go/serialudf | 99 | | 100 | Serial Forcer | Check Constraint with Scalar UDF | https://www.brentozar.com/go/computedscalar | 94 | - - | 150 | Abnormal Psychology | Cascading Updates or Deletes | https://www.brentozar.com/go/AbnormalPsychology | 71 | | 150 | Abnormal Psychology | Columnstore Index | https://www.brentozar.com/go/AbnormalPsychology | 61 | | 150 | Abnormal Psychology | Column Collation Does Not Match Database Collation| https://www.brentozar.com/go/AbnormalPsychology | 69 | From 1b6eae07e0c45bbe79993744b4180d014542a31f Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Tue, 10 Nov 2020 08:17:27 -0800 Subject: [PATCH 023/662] More formatting goofs --- Documentation/sp_BlitzIndex_Checks_by_Priority.md | 1 - 1 file changed, 1 deletion(-) diff --git a/Documentation/sp_BlitzIndex_Checks_by_Priority.md b/Documentation/sp_BlitzIndex_Checks_by_Priority.md index 2e85d16e0..c949a1227 100644 --- a/Documentation/sp_BlitzIndex_Checks_by_Priority.md +++ b/Documentation/sp_BlitzIndex_Checks_by_Priority.md @@ -11,7 +11,6 @@ If you want to add a new check, start at 121. | Priority | FindingsGroup | Finding | URL | CheckID | |----------|---------------------------------|---------------------------------------|-------------------------------------------------|----------| - | 10 | Index Hoarder | Many NC Indexes on a Single Table | https://www.brentozar.com/go/IndexHoarder | 20 | | 10 | Index Hoarder | Unused NC Index with High Writes | https://www.brentozar.com/go/IndexHoarder | 22 | | 20 | Multiple Index Personalities | Duplicate Keys | https://www.brentozar.com/go/duplicateindex | 1 | From d814e4cfe83b4533ec41e16229e293c632993a79 Mon Sep 17 00:00:00 2001 From: John McCall Date: Wed, 11 Nov 2020 15:48:48 -0500 Subject: [PATCH 024/662] Add space after colon to match "Reads: " format --- sp_BlitzIndex.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index ae50cdfe1..03cecc51c 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -334,7 +334,7 @@ IF OBJECT_ID('tempdb..#Ignore_Databases') IS NOT NULL + N') ' ELSE N' ' END - + N'Writes:' + + + N'Writes: ' + REPLACE(CONVERT(NVARCHAR(30),CAST(user_updates AS MONEY), 1), N'.00', N'') END /* First "end" is about is_spatial */, [more_info] AS From abe97e88a8b7267b176bda4bf163b6ecea903ce7 Mon Sep 17 00:00:00 2001 From: Erik Darling Date: Fri, 13 Nov 2020 07:40:19 -0500 Subject: [PATCH 025/662] Fix multiple plan query MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add DatabaseName to group by and join. Also discovered that this particular update was running twice, so remove the second one. This marks the first time BlitzCache has ever gotten shorter. Sorry if that screws up any metrics for you 😃 Closes #2653 --- sp_BlitzCache.sql | 24 ++++++------------------ 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/sp_BlitzCache.sql b/sp_BlitzCache.sql index 1d6ef949c..d5119b28b 100644 --- a/sp_BlitzCache.sql +++ b/sp_BlitzCache.sql @@ -2810,12 +2810,15 @@ SET NumberOfDistinctPlans = distinct_plan_count, FROM ( SELECT COUNT(DISTINCT QueryHash) AS distinct_plan_count, COUNT(QueryHash) AS number_of_plans, - QueryHash + QueryHash, + DatabaseName FROM ##BlitzCacheProcs WHERE SPID = @@SPID - GROUP BY QueryHash + GROUP BY QueryHash, + DatabaseName ) AS x WHERE ##BlitzCacheProcs.QueryHash = x.QueryHash +AND ##BlitzCacheProcs.DatabaseName = x.DatabaseName OPTION (RECOMPILE) ; -- query level checks @@ -3664,22 +3667,7 @@ END; /* END Testing using XML nodes to speed up processing */ -RAISERROR(N'Gathering additional plan level information', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE ##BlitzCacheProcs -SET NumberOfDistinctPlans = distinct_plan_count, - NumberOfPlans = number_of_plans, - plan_multiple_plans = CASE WHEN distinct_plan_count < number_of_plans THEN number_of_plans END -FROM ( -SELECT COUNT(DISTINCT QueryHash) AS distinct_plan_count, - COUNT(QueryHash) AS number_of_plans, - QueryHash -FROM ##BlitzCacheProcs -WHERE SPID = @@SPID -GROUP BY QueryHash -) AS x -WHERE ##BlitzCacheProcs.QueryHash = x.QueryHash -OPTION (RECOMPILE); + /* Update to grab stored procedure name for individual statements */ RAISERROR(N'Attempting to get stored procedure name for individual statements', 0, 1) WITH NOWAIT; From baecc99ae9f448c5cccdf71b2a8d3749705d3e81 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Sat, 14 Nov 2020 14:20:15 -0800 Subject: [PATCH 026/662] 2020_11 release Bumping version numbers and dates. --- Install-All-Scripts.sql | 2270 +++++++++++---------- Install-Core-Blitz-No-Query-Store.sql | 2485 ++++++++++++----------- Install-Core-Blitz-With-Query-Store.sql | 2213 +++++++++++--------- sp_AllNightLog.sql | 2 +- sp_AllNightLog_Setup.sql | 2 +- sp_Blitz.sql | 2 +- sp_BlitzBackups.sql | 2 +- sp_BlitzCache.sql | 2 +- sp_BlitzFirst.sql | 2 +- sp_BlitzInMemoryOLTP.sql | 2 +- sp_BlitzIndex.sql | 2 +- sp_BlitzLock.sql | 2 +- sp_BlitzQueryStore.sql | 2 +- sp_BlitzWho.sql | 2 +- sp_DatabaseRestore.sql | 2 +- sp_ineachdb.sql | 4 +- 16 files changed, 3805 insertions(+), 3191 deletions(-) diff --git a/Install-All-Scripts.sql b/Install-All-Scripts.sql index 2a2fe0455..a40d2f82e 100755 --- a/Install-All-Scripts.sql +++ b/Install-All-Scripts.sql @@ -30,7 +30,7 @@ SET NOCOUNT ON; BEGIN; -SELECT @Version = '3.99', @VersionDate = '20201011'; +SELECT @Version = '3.999', @VersionDate = '20201114'; IF(@VersionCheckMode = 1) BEGIN @@ -1524,7 +1524,7 @@ SET NOCOUNT ON; BEGIN; -SELECT @Version = '3.99', @VersionDate = '20201011'; +SELECT @Version = '3.999', @VersionDate = '20201114'; IF(@VersionCheckMode = 1) BEGIN @@ -2856,7 +2856,7 @@ AS SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '7.999', @VersionDate = '20201011'; + SELECT @Version = '7.9999', @VersionDate = '20201114'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -3849,6 +3849,64 @@ AS -7, GETDATE()) ); END; + /* + CheckID #256 is searching for backups to NUL. + */ + + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 256 ) + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 2) WITH NOWAIT; + + INSERT INTO #BlitzResults + ( CheckID , + DatabaseName , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT DISTINCT + 256 AS CheckID , + d.name AS DatabaseName, + 1 AS Priority , + 'Backup' AS FindingsGroup , + 'Log Backups to NUL' AS Finding , + 'https://www.brentozar.com/go/nul' AS URL , + N'The transaction log file has been backed up ' + CAST((SELECT count(*) + FROM msdb.dbo.backupset AS b INNER JOIN + msdb.dbo.backupmediafamily AS bmf + ON b.media_set_id = bmf.media_set_id + WHERE b.database_name COLLATE SQL_Latin1_General_CP1_CI_AS = d.name COLLATE SQL_Latin1_General_CP1_CI_AS + AND bmf.physical_device_name = 'NUL' + AND b.type = 'L' + AND b.backup_finish_date >= DATEADD(dd, + -7, GETDATE())) AS NVARCHAR(8)) + ' time(s) to ''NUL'' in the last week, which means the backup does not exist. This breaks point-in-time recovery.' AS Details + FROM master.sys.databases AS d + WHERE d.recovery_model IN ( 1, 2 ) + AND d.database_id NOT IN ( 2, 3 ) + AND d.source_database_id IS NULL + AND d.state NOT IN(1, 6, 10) /* Not currently offline or restoring, like log shipping databases */ + AND d.is_in_standby = 0 /* Not a log shipping target database */ + AND d.source_database_id IS NULL /* Excludes database snapshots */ + --AND d.name NOT IN ( SELECT DISTINCT + -- DatabaseName + -- FROM #SkipChecks + -- WHERE CheckID IS NULL OR CheckID = 2) + AND EXISTS ( SELECT * + FROM msdb.dbo.backupset AS b INNER JOIN + msdb.dbo.backupmediafamily AS bmf + ON b.media_set_id = bmf.media_set_id + WHERE d.name COLLATE SQL_Latin1_General_CP1_CI_AS = b.database_name COLLATE SQL_Latin1_General_CP1_CI_AS + AND bmf.physical_device_name = 'NUL' + AND b.type = 'L' + AND b.backup_finish_date >= DATEADD(dd, + -7, GETDATE()) ); + END; + /* Next up, we've got CheckID 8. (These don't have to go in order.) This one won't work on SQL Server 2005 because it relies on a new DMV that didn't @@ -7215,7 +7273,8 @@ IF @ProductVersionMajor >= 10 [sys].[dm_server_services] WHERE [service_account] LIKE 'NT Service%' AND [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%'; END; END; @@ -12220,7 +12279,7 @@ AS SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '3.999', @VersionDate = '20201011'; + SELECT @Version = '3.9999', @VersionDate = '20201114'; IF(@VersionCheckMode = 1) BEGIN @@ -13999,7 +14058,7 @@ BEGIN SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '7.999', @VersionDate = '20201011'; +SELECT @Version = '7.9999', @VersionDate = '20201114'; IF(@VersionCheckMode = 1) @@ -14072,7 +14131,7 @@ BEGIN UNION ALL SELECT N'@SortOrder', N'VARCHAR(10)', - N'Data processing and display order. @SortOrder will still be used, even when preparing output for a table or for excel. Possible values are: "CPU", "Reads", "Writes", "Duration", "Executions", "Recent Compilations", "Memory Grant", "Spills", "Query Hash". Additionally, the word "Average" or "Avg" can be used to sort on averages rather than total. "Executions per minute" and "Executions / minute" can be used to sort by execution per minute. For the truly lazy, "xpm" can also be used. Note that when you use all or all avg, the only parameters you can use are @Top and @DatabaseName. All others will be ignored.' + N'Data processing and display order. @SortOrder will still be used, even when preparing output for a table or for excel. Possible values are: "CPU", "Reads", "Writes", "Duration", "Executions", "Recent Compilations", "Memory Grant", "Unused Grant", "Spills", "Query Hash". Additionally, the word "Average" or "Avg" can be used to sort on averages rather than total. "Executions per minute" and "Executions / minute" can be used to sort by execution per minute. For the truly lazy, "xpm" can also be used. Note that when you use all or all avg, the only parameters you can use are @Top and @DatabaseName. All others will be ignored.' UNION ALL SELECT N'@UseTriggersAnyway', @@ -14513,7 +14572,7 @@ IF @SortOrder LIKE 'query hash%' /* Set @Top based on sort */ IF ( @Top IS NULL - AND LOWER(@SortOrder) IN ( 'all', 'all sort' ) + AND @SortOrder IN ( 'all', 'all sort' ) ) BEGIN SET @Top = 5; @@ -14521,7 +14580,7 @@ IF ( IF ( @Top IS NULL - AND LOWER(@SortOrder) NOT IN ( 'all', 'all sort' ) + AND @SortOrder NOT IN ( 'all', 'all sort' ) ) BEGIN SET @Top = 10; @@ -14824,6 +14883,7 @@ SET @SortOrder = CASE WHEN @SortOrder IN ('avg write') THEN 'avg writes' WHEN @SortOrder IN ('memory grants') THEN 'memory grant' WHEN @SortOrder IN ('avg memory grants') THEN 'avg memory grant' + WHEN @SortOrder IN ('unused grants','unused memory', 'unused memory grant', 'unused memory grants') THEN 'unused grant' WHEN @SortOrder IN ('spill') THEN 'spills' WHEN @SortOrder IN ('avg spill') THEN 'avg spills' WHEN @SortOrder IN ('execution') THEN 'executions' @@ -14832,7 +14892,7 @@ SET @SortOrder = CASE RAISERROR(N'Checking sort order', 0, 1) WITH NOWAIT; IF @SortOrder NOT IN ('cpu', 'avg cpu', 'reads', 'avg reads', 'writes', 'avg writes', 'duration', 'avg duration', 'executions', 'avg executions', - 'compiles', 'memory grant', 'avg memory grant', + 'compiles', 'memory grant', 'avg memory grant', 'unused grant', 'spills', 'avg spills', 'all', 'all avg', 'sp_BlitzIndex', 'query hash') BEGIN @@ -15739,6 +15799,7 @@ SELECT @body += N' ORDER BY ' + WHEN N'executions' THEN N'execution_count' WHEN N'compiles' THEN N'cached_time' WHEN N'memory grant' THEN N'max_grant_kb' + WHEN N'unused grant' THEN N'max_grant_kb - max_used_grant_kb' WHEN N'spills' THEN N'max_spills' /* And now the averages */ WHEN N'avg cpu' THEN N'total_worker_time / execution_count' @@ -16055,6 +16116,7 @@ BEGIN WHEN N'executions' THEN N'AND execution_count > 0' WHEN N'compiles' THEN N'AND (age_minutes + age_minutes_lifetime) > 0' WHEN N'memory grant' THEN N'AND max_grant_kb > 0' + WHEN N'unused grant' THEN N'AND max_grant_kb > 0' WHEN N'spills' THEN N'AND max_spills > 0' /* And now the averages */ WHEN N'avg cpu' THEN N'AND (total_worker_time / execution_count) > 0' @@ -16086,7 +16148,7 @@ END; IF (@QueryFilter = 'all' AND (SELECT COUNT(*) FROM #only_query_hashes) = 0 AND (SELECT COUNT(*) FROM #ignore_query_hashes) = 0) - AND (@SortOrder NOT IN ('memory grant', 'avg memory grant')) + AND (@SortOrder NOT IN ('memory grant', 'avg memory grant', 'unused grant')) OR (LEFT(@QueryFilter, 3) = 'pro') BEGIN SET @sql += @insert_list; @@ -16107,7 +16169,7 @@ IF (@v >= 13 AND @QueryFilter = 'all' AND (SELECT COUNT(*) FROM #only_query_hashes) = 0 AND (SELECT COUNT(*) FROM #ignore_query_hashes) = 0) - AND (@SortOrder NOT IN ('memory grant', 'avg memory grant')) + AND (@SortOrder NOT IN ('memory grant', 'avg memory grant', 'unused grant')) AND (@SortOrder NOT IN ('spills', 'avg spills')) OR (LEFT(@QueryFilter, 3) = 'fun') BEGIN @@ -16150,7 +16212,7 @@ IF (@UseTriggersAnyway = 1 OR @v >= 11) AND (SELECT COUNT(*) FROM #only_query_hashes) = 0 AND (SELECT COUNT(*) FROM #ignore_query_hashes) = 0 AND (@QueryFilter = 'all') - AND (@SortOrder NOT IN ('memory grant', 'avg memory grant')) + AND (@SortOrder NOT IN ('memory grant', 'avg memory grant', 'unused grant')) BEGIN RAISERROR (N'Adding SQL to collect trigger stats.',0,1) WITH NOWAIT; @@ -16180,6 +16242,7 @@ SELECT @sort = CASE @SortOrder WHEN N'cpu' THEN N'total_worker_time' WHEN N'executions' THEN N'execution_count' WHEN N'compiles' THEN N'cached_time' WHEN N'memory grant' THEN N'max_grant_kb' + WHEN N'unused grant' THEN N'max_grant_kb - max_used_grant_kb' WHEN N'spills' THEN N'max_spills' /* And now the averages */ WHEN N'avg cpu' THEN N'total_worker_time / execution_count' @@ -16240,6 +16303,7 @@ SELECT @sort = CASE @SortOrder WHEN N'cpu' THEN N'TotalCPU' WHEN N'executions' THEN N'ExecutionCount' WHEN N'compiles' THEN N'PlanCreationTime' WHEN N'memory grant' THEN N'MaxGrantKB' + WHEN N'unused grant' THEN N'MaxGrantKB - MaxUsedGrantKB' WHEN N'spills' THEN N'MaxSpills' /* And now the averages */ WHEN N'avg cpu' THEN N'TotalCPU / ExecutionCount' @@ -16526,12 +16590,15 @@ SET NumberOfDistinctPlans = distinct_plan_count, FROM ( SELECT COUNT(DISTINCT QueryHash) AS distinct_plan_count, COUNT(QueryHash) AS number_of_plans, - QueryHash + QueryHash, + DatabaseName FROM ##BlitzCacheProcs WHERE SPID = @@SPID - GROUP BY QueryHash + GROUP BY QueryHash, + DatabaseName ) AS x WHERE ##BlitzCacheProcs.QueryHash = x.QueryHash +AND ##BlitzCacheProcs.DatabaseName = x.DatabaseName OPTION (RECOMPILE) ; -- query level checks @@ -17380,22 +17447,7 @@ END; /* END Testing using XML nodes to speed up processing */ -RAISERROR(N'Gathering additional plan level information', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE ##BlitzCacheProcs -SET NumberOfDistinctPlans = distinct_plan_count, - NumberOfPlans = number_of_plans, - plan_multiple_plans = CASE WHEN distinct_plan_count < number_of_plans THEN number_of_plans END -FROM ( -SELECT COUNT(DISTINCT QueryHash) AS distinct_plan_count, - COUNT(QueryHash) AS number_of_plans, - QueryHash -FROM ##BlitzCacheProcs -WHERE SPID = @@SPID -GROUP BY QueryHash -) AS x -WHERE ##BlitzCacheProcs.QueryHash = x.QueryHash -OPTION (RECOMPILE); + /* Update to grab stored procedure name for individual statements */ RAISERROR(N'Attempting to get stored procedure name for individual statements', 0, 1) WITH NOWAIT; @@ -18579,6 +18631,7 @@ BEGIN WHEN N'executions' THEN N' ExecutionCount ' WHEN N'compiles' THEN N' PlanCreationTime ' WHEN N'memory grant' THEN N' MaxGrantKB' + WHEN N'unused grant' THEN N' MaxGrantKB - MaxUsedGrantKB' WHEN N'spills' THEN N' MaxSpills' WHEN N'avg cpu' THEN N' AverageCPU' WHEN N'avg reads' THEN N' AverageReads' @@ -18828,6 +18881,7 @@ SELECT @sql += N' ORDER BY ' + CASE @SortOrder WHEN N'cpu' THEN N' TotalCPU ' WHEN N'executions' THEN N' ExecutionCount ' WHEN N'compiles' THEN N' PlanCreationTime ' WHEN N'memory grant' THEN N' MaxGrantKB' + WHEN N'unused grant' THEN N' MaxGrantKB - MaxUsedGrantKB ' WHEN N'spills' THEN N' MaxSpills' WHEN N'avg cpu' THEN N' AverageCPU' WHEN N'avg reads' THEN N' AverageReads' @@ -20204,7 +20258,7 @@ IF OBJECT_ID('tempdb.. #bou_allsort') IS NULL END; -IF LOWER(@SortOrder) = 'all' +IF @SortOrder = 'all' BEGIN RAISERROR('Beginning for ALL', 0, 1) WITH NOWAIT; SET @AllSortSql += N' @@ -20379,7 +20433,7 @@ SET @AllSortSql += N' END; -IF LOWER(@SortOrder) = 'all avg' +IF @SortOrder = 'all avg' BEGIN RAISERROR('Beginning for ALL AVG', 0, 1) WITH NOWAIT; SET @AllSortSql += N' @@ -20686,7 +20740,7 @@ ELSE PercentExecutionsByType money, ExecutionsPerMinute money, PlanCreationTime datetime,' + N' - PlanCreationTimeHours AS DATEDIFF(HOUR, PlanCreationTime, SYSDATETIME()), + PlanCreationTimeHours AS DATEDIFF(HOUR,CONVERT(DATETIMEOFFSET(7),[PlanCreationTime]),[CheckDate]), LastExecutionTime datetime, LastCompletionTime datetime, PlanHandle varbinary(64), @@ -20732,7 +20786,28 @@ ELSE AvgSpills MONEY, QueryPlanCost FLOAT, JoinKey AS ServerName + Cast(CheckDate AS NVARCHAR(50)), - CONSTRAINT [PK_' + REPLACE(REPLACE(@OutputTableName,'[',''),']','') + '] PRIMARY KEY CLUSTERED(ID ASC));'; + CONSTRAINT [PK_' + REPLACE(REPLACE(@OutputTableName,'[',''),']','') + '] PRIMARY KEY CLUSTERED(ID ASC)); + + IF EXISTS(SELECT * FROM ' + +@OutputDatabaseName + +N'.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' + +@OutputSchemaName + +''') AND EXISTS (SELECT * FROM ' + +@OutputDatabaseName+ + N'.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''' + +@OutputSchemaName + +''' AND QUOTENAME(TABLE_NAME) = ''' + +@OutputTableName + +''') AND EXISTS (SELECT * FROM ' + +@OutputDatabaseName+ + N'.sys.computed_columns WHERE [name] = N''PlanCreationTimeHours'' AND QUOTENAME(OBJECT_NAME(object_id)) = N''' + +@OutputTableName + +''' AND [definition] = N''(datediff(hour,[PlanCreationTime],sysdatetime()))'') +BEGIN + RAISERROR(''We noticed that you are running an old computed column definition for PlanCreationTimeHours, fixing that now'',0,0) WITH NOWAIT; + ALTER TABLE '+@OutputDatabaseName+'.'+@OutputSchemaName+'.'+@OutputTableName+' DROP COLUMN [PlanCreationTimeHours]; + ALTER TABLE '+@OutputDatabaseName+'.'+@OutputSchemaName+'.'+@OutputTableName+' ADD [PlanCreationTimeHours] AS DATEDIFF(HOUR,CONVERT(DATETIMEOFFSET(7),[PlanCreationTime]),[CheckDate]); +END '; IF @ValidOutputServer = 1 BEGIN @@ -21163,7 +21238,7 @@ BEGIN SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '7.999', @VersionDate = '20201011'; +SELECT @Version = '7.9999', @VersionDate = '20201114'; IF(@VersionCheckMode = 1) BEGIN @@ -22439,12 +22514,12 @@ BEGIN CASE @Seconds WHEN 0 THEN 0 ELSE SUM(os.waiting_tasks_count) OVER (PARTITION BY os.wait_type) END AS sum_waiting_tasks FROM sys.dm_os_wait_stats os ) x - WHERE EXISTS + WHERE NOT EXISTS ( - SELECT 1/0 + SELECT * FROM ##WaitCategories AS wc WHERE wc.WaitType = x.wait_type - AND wc.Ignorable = 0 + AND wc.Ignorable = 1 ) GROUP BY x.Pass, x.SampleTime, x.wait_type ORDER BY sum_wait_time_ms DESC; @@ -23394,12 +23469,12 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, SUM(os.waiting_tasks_count) OVER (PARTITION BY os.wait_type) AS sum_waiting_tasks FROM sys.dm_os_wait_stats os ) x - WHERE EXISTS + WHERE NOT EXISTS ( - SELECT 1/0 + SELECT * FROM ##WaitCategories AS wc WHERE wc.WaitType = x.wait_type - AND wc.Ignorable = 0 + AND wc.Ignorable = 1 ) GROUP BY x.Pass, x.SampleTime, x.wait_type ORDER BY sum_wait_time_ms DESC; @@ -24276,7 +24351,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, + @OutputSchemaName + '.' + @OutputTableName + ' (ServerName, CheckDate, CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, QueryText, StartTime, LoginName, NTUserName, OriginalLoginName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, DetailsInt, QueryHash) SELECT ' - + ' @SrvName, @CheckDate, CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, QueryText, StartTime, LoginName, NTUserName, OriginalLoginName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, DetailsInt, QueryHash FROM #BlitzFirstResults ORDER BY Priority , FindingsGroup , Finding , Details'; + + ' @SrvName, @CheckDate, CheckID, Priority, FindingsGroup, Finding, URL, LEFT(Details,4000), HowToStopIt, QueryPlan, QueryText, StartTime, LoginName, NTUserName, OriginalLoginName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, DetailsInt, QueryHash FROM #BlitzFirstResults ORDER BY Priority , FindingsGroup , Finding , Details'; EXEC sp_executesql @StringToExecute, N'@SrvName NVARCHAR(128), @CheckDate datetimeoffset', @@ -24331,7 +24406,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, + ' INSERT ' + @OutputTableName + ' (ServerName, CheckDate, CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, QueryText, StartTime, LoginName, NTUserName, OriginalLoginName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, DetailsInt) SELECT ' - + ' @SrvName, @CheckDate, CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, QueryText, StartTime, LoginName, NTUserName, OriginalLoginName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, DetailsInt FROM #BlitzFirstResults ORDER BY Priority , FindingsGroup , Finding , Details'; + + ' @SrvName, @CheckDate, CheckID, Priority, FindingsGroup, Finding, URL, LEFT(Details,4000), HowToStopIt, QueryPlan, QueryText, StartTime, LoginName, NTUserName, OriginalLoginName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, DetailsInt FROM #BlitzFirstResults ORDER BY Priority , FindingsGroup , Finding , Details'; EXEC sp_executesql @StringToExecute, N'@SrvName NVARCHAR(128), @CheckDate datetimeoffset', @@ -25494,6 +25569,7 @@ ALTER PROCEDURE dbo.sp_BlitzIndex @IncludeInactiveIndexes BIT = 0 /* Will skip indexes with no reads or writes */, @ShowAllMissingIndexRequests BIT = 0 /*Will make all missing index requests show up*/, @SortOrder NVARCHAR(50) = NULL, /* Only affects @Mode = 2. */ + @SortDirection NVARCHAR(4) = 'DESC', /* Only affects @Mode = 2. */ @Help TINYINT = 0, @Debug BIT = 0, @Version VARCHAR(30) = NULL OUTPUT, @@ -25504,7 +25580,7 @@ AS SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '7.999', @VersionDate = '20201011'; +SELECT @Version = '7.9999', @VersionDate = '20201114'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -25583,6 +25659,7 @@ DECLARE @ColumnList NVARCHAR(MAX); /* Let's get @SortOrder set to lower case here for comparisons later */ SET @SortOrder = REPLACE(LOWER(@SortOrder), N' ', N'_'); +SET @SortDirection = LOWER(@SortDirection); SET @LineFeed = CHAR(13) + CHAR(10); SELECT @SQLServerProductVersion = CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)); @@ -25777,25 +25854,31 @@ IF OBJECT_ID('tempdb..#Ignore_Databases') IS NOT NULL [reads_per_write] AS CAST(CASE WHEN user_updates > 0 THEN ( user_seeks + user_scans + user_lookups ) / (1.0 * user_updates) ELSE 0 END AS MONEY) , - [index_usage_summary] AS N'Reads: ' + - REPLACE(CONVERT(NVARCHAR(30),CAST((user_seeks + user_scans + user_lookups) AS MONEY), 1), N'.00', N'') - + CASE WHEN user_seeks + user_scans + user_lookups > 0 THEN - N' (' - + RTRIM( - CASE WHEN user_seeks > 0 THEN REPLACE(CONVERT(NVARCHAR(30),CAST((user_seeks) AS MONEY), 1), N'.00', N'') + N' seek ' ELSE N'' END - + CASE WHEN user_scans > 0 THEN REPLACE(CONVERT(NVARCHAR(30),CAST((user_scans) AS MONEY), 1), N'.00', N'') + N' scan ' ELSE N'' END - + CASE WHEN user_lookups > 0 THEN REPLACE(CONVERT(NVARCHAR(30),CAST((user_lookups) AS MONEY), 1), N'.00', N'') + N' lookup' ELSE N'' END - ) - + N') ' - ELSE N' ' END - + N'Writes:' + - REPLACE(CONVERT(NVARCHAR(30),CAST(user_updates AS MONEY), 1), N'.00', N''), - [more_info] AS - CASE WHEN is_in_memory_oltp = 1 - THEN N'EXEC dbo.sp_BlitzInMemoryOLTP @dbName=' + QUOTENAME([database_name],N'''') + - N', @tableName=' + QUOTENAME([object_name],N'''') + N';' - ELSE N'EXEC dbo.sp_BlitzIndex @DatabaseName=' + QUOTENAME([database_name],N'''') + - N', @SchemaName=' + QUOTENAME([schema_name],N'''') + N', @TableName=' + QUOTENAME([object_name],N'''') + N';' END + [index_usage_summary] AS + CASE WHEN is_spatial = 1 THEN N'Not Tracked' + WHEN is_disabled = 1 THEN N'Disabled' + ELSE N'Reads: ' + + REPLACE(CONVERT(NVARCHAR(30),CAST((user_seeks + user_scans + user_lookups) AS MONEY), 1), N'.00', N'') + + CASE WHEN user_seeks + user_scans + user_lookups > 0 THEN + N' (' + + RTRIM( + CASE WHEN user_seeks > 0 THEN REPLACE(CONVERT(NVARCHAR(30),CAST((user_seeks) AS MONEY), 1), N'.00', N'') + N' seek ' ELSE N'' END + + CASE WHEN user_scans > 0 THEN REPLACE(CONVERT(NVARCHAR(30),CAST((user_scans) AS MONEY), 1), N'.00', N'') + N' scan ' ELSE N'' END + + CASE WHEN user_lookups > 0 THEN REPLACE(CONVERT(NVARCHAR(30),CAST((user_lookups) AS MONEY), 1), N'.00', N'') + N' lookup' ELSE N'' END + ) + + N') ' + ELSE N' ' + END + + N'Writes: ' + + REPLACE(CONVERT(NVARCHAR(30),CAST(user_updates AS MONEY), 1), N'.00', N'') + END /* First "end" is about is_spatial */, + [more_info] AS + CASE WHEN is_in_memory_oltp = 1 + THEN N'EXEC dbo.sp_BlitzInMemoryOLTP @dbName=' + QUOTENAME([database_name],N'''') + + N', @tableName=' + QUOTENAME([object_name],N'''') + N';' + ELSE N'EXEC dbo.sp_BlitzIndex @DatabaseName=' + QUOTENAME([database_name],N'''') + + N', @SchemaName=' + QUOTENAME([schema_name],N'''') + N', @TableName=' + QUOTENAME([object_name],N'''') + N';' + END ); RAISERROR (N'Adding UQ index on #IndexSanity (database_id, object_id, index_id)',0,1) WITH NOWAIT; IF NOT EXISTS(SELECT 1 FROM tempdb.sys.indexes WHERE name='uq_database_id_object_id_index_id') @@ -26055,7 +26138,8 @@ IF OBJECT_ID('tempdb..#Ignore_Databases') IS NOT NULL + N';' , [more_info] AS N'EXEC dbo.sp_BlitzIndex @DatabaseName=' + QUOTENAME([database_name],'''') + - N', @SchemaName=' + QUOTENAME([schema_name],'''') + N', @TableName=' + QUOTENAME([table_name],'''') + N';' + N', @SchemaName=' + QUOTENAME([schema_name],'''') + N', @TableName=' + QUOTENAME([table_name],'''') + N';', + [sample_query_plan] XML NULL ); CREATE TABLE #ForeignKeys ( @@ -27087,8 +27171,24 @@ BEGIN TRY FROM ColumnNamesWithDataTypes WHERE ColumnNamesWithDataTypes.index_handle = id.index_handle AND ColumnNamesWithDataTypes.object_id = id.object_id AND ColumnNamesWithDataTypes.IndexColumnType = ''Included'' - ) AS included_columns_with_data_type - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_groups ig + ) AS included_columns_with_data_type ' + + IF NOT EXISTS (SELECT * FROM sys.all_objects WHERE name = 'dm_db_missing_index_group_stats_query') + SET @dsql = @dsql + N' , NULL AS sample_query_plan ' + ELSE + BEGIN + SET @dsql = @dsql + N' , sample_query_plan = (SELECT TOP 1 p.query_plan + FROM sys.dm_db_missing_index_group_stats gs + CROSS APPLY (SELECT TOP 1 s.plan_handle + FROM sys.dm_db_missing_index_group_stats_query q + INNER JOIN sys.dm_exec_query_stats s ON q.query_plan_hash = s.query_plan_hash + WHERE gs.group_handle = q.group_handle + ORDER BY (q.user_seeks + q.user_scans) DESC, s.total_logical_reads DESC) q2 + CROSS APPLY sys.dm_exec_query_plan(q2.plan_handle) p + WHERE gs.group_handle = gs.group_handle) ' + END + + SET @dsql = @dsql + N'FROM ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_groups ig JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_details id ON ig.index_handle = id.index_handle JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_group_stats gs ON ig.index_group_handle = gs.group_handle JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects so on @@ -27119,7 +27219,7 @@ BEGIN TRY INSERT #MissingIndexes ( [database_id], [object_id], [database_name], [schema_name], [table_name], [statement], avg_total_user_cost, avg_user_impact, user_seeks, user_scans, unique_compiles, equality_columns, inequality_columns, included_columns, equality_columns_with_data_type, inequality_columns_with_data_type, - included_columns_with_data_type) + included_columns_with_data_type, sample_query_plan) EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; SET @dsql = N' @@ -27189,14 +27289,18 @@ BEGIN TRY EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; - IF @SkipStatistics = 0 AND DB_NAME() = @DatabaseName /* Can only get stats in the current database - see https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/1947 */ + IF @SkipStatistics = 0 /* AND DB_NAME() = @DatabaseName /* Can only get stats in the current database - see https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/1947 */ */ BEGIN IF ((PARSENAME(@SQLServerProductVersion, 4) >= 12) OR (PARSENAME(@SQLServerProductVersion, 4) = 11 AND PARSENAME(@SQLServerProductVersion, 2) >= 3000) OR (PARSENAME(@SQLServerProductVersion, 4) = 10 AND PARSENAME(@SQLServerProductVersion, 3) = 50 AND PARSENAME(@SQLServerProductVersion, 2) >= 2500)) BEGIN RAISERROR (N'Gathering Statistics Info With Newer Syntax.',0,1) WITH NOWAIT; - SET @dsql=N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + SET @dsql=N'USE ' + @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, + 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) SELECT DB_ID(N' + QUOTENAME(@DatabaseName,'''') + N') AS [database_id], @i_DatabaseName AS database_name, obj.name AS table_name, @@ -27261,17 +27365,17 @@ BEGIN TRY PRINT SUBSTRING(@dsql, 32000, 36000); PRINT SUBSTRING(@dsql, 36000, 40000); END; - 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) EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; END; ELSE BEGIN RAISERROR (N'Gathering Statistics Info With Older Syntax.',0,1) WITH NOWAIT; - SET @dsql=N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + SET @dsql=N'USE ' + @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, 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) SELECT DB_ID(N' + QUOTENAME(@DatabaseName,'''') + N') AS [database_id], @i_DatabaseName AS database_name, obj.name AS table_name, @@ -27339,10 +27443,6 @@ BEGIN TRY PRINT SUBSTRING(@dsql, 32000, 36000); PRINT SUBSTRING(@dsql, 36000, 40000); END; - 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) EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; END; @@ -28031,7 +28131,7 @@ BEGIN GROUP BY i.database_id, i.schema_name, i.object_id ) SELECT N'Missing index.' AS Finding , - N'http://BrentOzar.com/go/Indexaphobia' AS URL , + N'https://www.brentozar.com/go/Indexaphobia' AS URL , mi.[statement] + ' Est. Benefit: ' + CASE WHEN magic_benefit_number >= 922337203685477 THEN '>= 922,337,203,685,477' @@ -28041,7 +28141,8 @@ BEGIN END AS [Estimated Benefit], missing_index_details AS [Missing Index Request] , index_estimated_impact AS [Estimated Impact], - create_tsql AS [Create TSQL] + create_tsql AS [Create TSQL], + sample_query_plan AS [Sample Query Plan] FROM #MissingIndexes mi LEFT JOIN create_date AS cd ON mi.[object_id] = cd.object_id @@ -28217,30 +28318,48 @@ BEGIN RAISERROR(N'Done visualizing columnstore index contents.', 0,1) WITH NOWAIT; END -END; +END; /* IF @TableName IS NOT NULL */ ---If @TableName is NOT specified... ---Act based on the @Mode and @Filter. (@Filter applies only when @Mode=0 "diagnose") -ELSE + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +ELSE IF @Mode IN (0, 4) /* DIAGNOSE*//* IF @TableName IS NOT NULL, so @TableName must not be null */ BEGIN; - IF @Mode IN (0, 4) /* DIAGNOSE*/ - BEGIN; - RAISERROR(N'@Mode=0 or 4, we are diagnosing.', 0,1) WITH NOWAIT; + IF @Mode IN (0, 4) /* DIAGNOSE priorities 1-100 */ + BEGIN; + RAISERROR(N'@Mode=0 or 4, running rules for priorities 1-100.', 0,1) WITH NOWAIT; ---------------------------------------- --Multiple Index Personalities: Check_id 0-10 ---------------------------------------- - BEGIN; - - --SELECT [object_id], key_column_names, database_id - -- FROM #IndexSanity - -- WHERE index_type IN (1,2) /* Clustered, NC only*/ - -- AND is_hypothetical = 0 - -- AND is_disabled = 0 - -- GROUP BY [object_id], key_column_names, database_id - -- HAVING COUNT(*) > 1 - - RAISERROR('check_id 1: Duplicate keys', 0,1) WITH NOWAIT; WITH duplicate_indexes AS ( SELECT [object_id], key_column_names, database_id, [schema_name] @@ -28267,11 +28386,11 @@ BEGIN; secret_columns, index_usage_summary, index_size_summary ) SELECT 1 AS check_id, ip.index_sanity_id, - 50 AS Priority, + 20 AS Priority, 'Multiple Index Personalities' AS findings_group, 'Duplicate keys' AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/duplicateindex' AS URL, + N'https://www.brentozar.com/go/duplicateindex' AS URL, N'Index Name: ' + ip.index_name + N' Table Name: ' + ip.db_schema_object_name AS details, ip.index_definition, ip.secret_columns, @@ -28304,11 +28423,11 @@ BEGIN; secret_columns, index_usage_summary, index_size_summary ) SELECT 2 AS check_id, ip.index_sanity_id, - 60 AS Priority, + 30 AS Priority, 'Multiple Index Personalities' AS findings_group, 'Borderline duplicate keys' AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/duplicateindex' AS URL, + N'https://www.brentozar.com/go/duplicateindex' AS URL, ip.db_schema_object_indexid AS details, ip.index_definition, ip.secret_columns, @@ -28329,18 +28448,16 @@ BEGIN; ORDER BY ip.[schema_name], ip.[object_name], ip.key_column_names, ip.include_column_names OPTION ( RECOMPILE ); - END; ---------------------------------------- --Aggressive Indexes: Check_id 10-19 ---------------------------------------- - BEGIN; RAISERROR(N'check_id 11: Total lock wait time > 5 minutes (row + page) with long average waits', 0,1) WITH NOWAIT; INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, secret_columns, index_usage_summary, index_size_summary ) SELECT 11 AS check_id, i.index_sanity_id, - 10 AS Priority, + 70 AS Priority, N'Aggressive ' + CASE COALESCE((SELECT SUM(1) FROM #IndexSanity iMe @@ -28366,7 +28483,7 @@ BEGIN; END AS findings_group, N'Total lock wait time > 5 minutes (row + page) with long average waits' AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/AggressiveIndexes' AS URL, + N'https://www.brentozar.com/go/AggressiveIndexes' AS URL, (i.db_schema_object_indexid + N': ' + sz.index_lock_wait_summary + N' NC indexes on table: ') COLLATE DATABASE_DEFAULT + CAST(COALESCE((SELECT SUM(1) @@ -28397,7 +28514,7 @@ BEGIN; secret_columns, index_usage_summary, index_size_summary ) SELECT 12 AS check_id, i.index_sanity_id, - 10 AS Priority, + 70 AS Priority, N'Aggressive ' + CASE COALESCE((SELECT SUM(1) FROM #IndexSanity iMe @@ -28423,7 +28540,7 @@ BEGIN; END AS findings_group, N'Total lock wait time > 5 minutes (row + page) with short average waits' AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/AggressiveIndexes' AS URL, + N'https://www.brentozar.com/go/AggressiveIndexes' AS URL, (i.db_schema_object_indexid + N': ' + sz.index_lock_wait_summary + N' NC indexes on table: ') COLLATE DATABASE_DEFAULT + CAST(COALESCE((SELECT SUM(1) @@ -28449,22 +28566,20 @@ BEGIN; ORDER BY 4, [database_name], 8 OPTION ( RECOMPILE ); - END; ---------------------------------------- --Index Hoarder: Check_id 20-29 ---------------------------------------- - BEGIN - RAISERROR(N'check_id 20: >=7 NC indexes on any given table. Yes, 7 is an arbitrary number.', 0,1) WITH NOWAIT; + RAISERROR(N'check_id 20: >= 10 NC indexes on any given table. Yes, 10 is an arbitrary number.', 0,1) WITH NOWAIT; INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, secret_columns, index_usage_summary, index_size_summary ) SELECT 20 AS check_id, MAX(i.index_sanity_id) AS index_sanity_id, - 100 AS Priority, + 10 AS Priority, 'Index Hoarder' AS findings_group, - 'Many NC indexes on a single table' AS finding, + 'Many NC Indexes on a Single Table' AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/IndexHoarder' AS URL, + N'https://www.brentozar.com/go/IndexHoarder' AS URL, CAST (COUNT(*) AS NVARCHAR(30)) + ' NC indexes on ' + i.db_schema_object_name AS details, i.db_schema_object_name + ' (' + CAST (COUNT(*) AS NVARCHAR(30)) + ' indexes)' AS index_definition, '' AS secret_columns, @@ -28481,88 +28596,20 @@ BEGIN; JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id WHERE index_id NOT IN ( 0, 1 ) GROUP BY db_schema_object_name, [i].[database_name] - HAVING COUNT(*) >= CASE WHEN (@GetAllDatabases = 1 OR @Mode = 0) - THEN 21 - ELSE 7 - END + HAVING COUNT(*) >= 10 ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); - IF @Filter = 1 /*@Filter=1 is "ignore unusued" */ - BEGIN - RAISERROR(N'Skipping checks on unused indexes (21 and 22) because @Filter=1', 0,1) WITH NOWAIT; - END; - ELSE /*Otherwise, go ahead and do the checks*/ - BEGIN - RAISERROR(N'check_id 21: >=5 percent of indexes are unused. Yes, 5 is an arbitrary number.', 0,1) WITH NOWAIT; - DECLARE @percent_NC_indexes_unused NUMERIC(29,1); - DECLARE @NC_indexes_unused_reserved_MB NUMERIC(29,1); - - SELECT @percent_NC_indexes_unused = ( 100.00 * SUM(CASE - WHEN total_reads = 0 - THEN 1 - ELSE 0 - END) ) / COUNT(*), - @NC_indexes_unused_reserved_MB = SUM(CASE - WHEN total_reads = 0 - THEN sz.total_reserved_MB - ELSE 0 - END) - FROM #IndexSanity i - 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()) - 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, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 21 AS check_id, - MAX(i.index_sanity_id) AS index_sanity_id, - 150 AS Priority, - N'Index Hoarder' AS findings_group, - N'More than 5 percent NC indexes are unused' AS finding, - [database_name] AS [Database Name], - N'http://BrentOzar.com/go/IndexHoarder' AS URL, - CAST (@percent_NC_indexes_unused AS NVARCHAR(30)) + N' percent NC indexes (' + CAST(COUNT(*) AS NVARCHAR(10)) + N') unused. ' + - N'These take up ' + CAST (@NC_indexes_unused_reserved_MB AS NVARCHAR(30)) + N'MB of space.' AS details, - i.database_name + ' (' + CAST (COUNT(*) AS NVARCHAR(30)) + N' indexes)' AS index_definition, - '' AS secret_columns, - CAST(SUM(total_reads) AS NVARCHAR(256)) + N' reads (ALL); ' - + CAST(SUM([user_updates]) AS NVARCHAR(256)) + N' writes (ALL)' AS index_usage_summary, - - REPLACE(CONVERT(NVARCHAR(30),CAST(MAX([total_rows]) AS MONEY), 1), '.00', '') + N' rows (MAX)' - + CASE WHEN SUM(total_reserved_MB) > 1024 THEN - N'; ' + CAST(CAST(SUM(total_reserved_MB)/1024. AS NUMERIC(29,1)) AS NVARCHAR(30)) + 'GB (ALL)' - WHEN SUM(total_reserved_MB) > 0 THEN - N'; ' + CAST(CAST(SUM(total_reserved_MB) AS NUMERIC(29,1)) AS NVARCHAR(30)) + 'MB (ALL)' - ELSE '' - END AS index_size_summary - FROM #IndexSanity i - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE index_id NOT IN ( 0, 1 ) - AND i.is_unique = 0 - AND total_reads = 0 - AND NOT (@GetAllDatabases = 1 OR @Mode = 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()) - GROUP BY i.database_name - OPTION ( RECOMPILE ); - RAISERROR(N'check_id 22: NC indexes with 0 reads. (Borderline) and >= 10,000 writes', 0,1) WITH NOWAIT; INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, secret_columns, index_usage_summary, index_size_summary ) SELECT 22 AS check_id, i.index_sanity_id, - 100 AS Priority, + 10 AS Priority, N'Index Hoarder' AS findings_group, - N'Unused NC index with High Writes' AS finding, + N'Unused NC Index with High Writes' AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/IndexHoarder' AS URL, + N'https://www.brentozar.com/go/IndexHoarder' AS URL, N'Reads: 0,' + N' Writes: ' + REPLACE(CONVERT(NVARCHAR(30), CAST((i.user_updates) AS MONEY), 1), N'.00', N'') @@ -28580,387 +28627,10 @@ 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 + AND @Filter <> 1 /* 1 = "ignore unused */ ORDER BY i.db_schema_object_indexid OPTION ( RECOMPILE ); - END; /*end checks only run when @Filter <> 1*/ - - RAISERROR(N'check_id 23: Indexes with 7 or more columns. (Borderline)', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 23 AS check_id, - i.index_sanity_id, - 150 AS Priority, - N'Index Hoarder' AS findings_group, - N'Borderline: Wide indexes (7 or more columns)' AS finding, - [database_name] AS [Database Name], - N'http://BrentOzar.com/go/IndexHoarder' AS URL, - CAST(count_key_columns + count_included_columns AS NVARCHAR(10)) + ' columns on ' - + i.db_schema_object_indexid AS details, i.index_definition, - i.secret_columns, - i.index_usage_summary, - sz.index_size_summary - FROM #IndexSanity AS i - JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id - WHERE ( count_key_columns + count_included_columns ) >= 7 - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 24: Wide clustered indexes (> 3 columns or > 16 bytes).', 0,1) WITH NOWAIT; - WITH count_columns AS ( - SELECT database_id, [object_id], - SUM(CASE max_length WHEN -1 THEN 0 ELSE max_length END) AS sum_max_length - FROM #IndexColumns ic - WHERE index_id IN (1,0) /*Heap or clustered only*/ - AND key_ordinal > 0 - GROUP BY database_id, object_id - ) - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 24 AS check_id, - i.index_sanity_id, - 150 AS Priority, - N'Index Hoarder' AS findings_group, - N'Wide clustered index (> 3 columns OR > 16 bytes)' AS finding, - [database_name] AS [Database Name], - N'http://BrentOzar.com/go/IndexHoarder' AS URL, - CAST (i.count_key_columns AS NVARCHAR(10)) + N' columns with potential size of ' - + CAST(cc.sum_max_length AS NVARCHAR(10)) - + N' bytes in clustered index:' + i.db_schema_object_name - + N'. ' + - (SELECT CAST(COUNT(*) AS NVARCHAR(23)) - FROM #IndexSanity i2 - WHERE i2.[object_id]=i.[object_id] - AND i2.database_id = i.database_id - AND i2.index_id <> 1 - AND i2.is_disabled=0 - AND i2.is_hypothetical=0) - + N' NC indexes on the table.' - AS details, - i.index_definition, - secret_columns, - i.index_usage_summary, - ip.index_size_summary - FROM #IndexSanity i - JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id - JOIN count_columns AS cc ON i.[object_id]=cc.[object_id] - AND i.database_id = cc.database_id - WHERE index_id = 1 /* clustered only */ - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) - AND - (count_key_columns > 3 /*More than three key columns.*/ - OR cc.sum_max_length > 16 /*More than 16 bytes in key */) - AND i.is_CX_columnstore = 0 - ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 25: Addicted to nullable columns.', 0,1) WITH NOWAIT; - WITH count_columns AS ( - SELECT [object_id], - [database_id], - [schema_name], - SUM(CASE is_nullable WHEN 1 THEN 0 ELSE 1 END) AS non_nullable_columns, - COUNT(*) AS total_columns - FROM #IndexColumns ic - WHERE index_id IN (1,0) /*Heap or clustered only*/ - GROUP BY [object_id], - [database_id], - [schema_name] - ) - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 25 AS check_id, - i.index_sanity_id, - 200 AS Priority, - N'Index Hoarder' AS findings_group, - N'Addicted to nulls' AS finding, - [database_name] AS [Database Name], - N'http://BrentOzar.com/go/IndexHoarder' AS URL, - i.db_schema_object_name - + N' allows null in ' + CAST((total_columns-non_nullable_columns) AS NVARCHAR(10)) - + N' of ' + CAST(total_columns AS NVARCHAR(10)) - + N' columns.' AS details, - i.index_definition, - secret_columns, - ISNULL(i.index_usage_summary,''), - ISNULL(ip.index_size_summary,'') - FROM #IndexSanity i - JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id - JOIN count_columns AS cc ON i.[object_id]=cc.[object_id] - AND cc.database_id = ip.database_id - AND cc.[schema_name] = ip.[schema_name] - WHERE i.index_id IN (1,0) - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) - AND cc.non_nullable_columns < 2 - AND cc.total_columns > 3 - ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 26: Wide tables (35+ cols or > 2000 non-LOB bytes).', 0,1) WITH NOWAIT; - WITH count_columns AS ( - SELECT [object_id], - [database_id], - [schema_name], - SUM(CASE max_length WHEN -1 THEN 1 ELSE 0 END) AS count_lob_columns, - SUM(CASE max_length WHEN -1 THEN 0 ELSE max_length END) AS sum_max_length, - COUNT(*) AS total_columns - FROM #IndexColumns ic - WHERE index_id IN (1,0) /*Heap or clustered only*/ - GROUP BY [object_id], - [database_id], - [schema_name] - ) - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 26 AS check_id, - i.index_sanity_id, - 150 AS Priority, - N'Index Hoarder' AS findings_group, - N'Wide tables: 35+ cols or > 2000 non-LOB bytes' AS finding, - [database_name] AS [Database Name], - N'http://BrentOzar.com/go/IndexHoarder' AS URL, - i.db_schema_object_name - + N' has ' + CAST((total_columns) AS NVARCHAR(10)) - + N' total columns with a max possible width of ' + CAST(sum_max_length AS NVARCHAR(10)) - + N' bytes.' + - CASE WHEN count_lob_columns > 0 THEN CAST((count_lob_columns) AS NVARCHAR(10)) - + ' columns are LOB types.' ELSE '' - END - AS details, - i.index_definition, - secret_columns, - ISNULL(i.index_usage_summary,''), - ISNULL(ip.index_size_summary,'') - FROM #IndexSanity i - JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id - JOIN count_columns AS cc ON i.[object_id]=cc.[object_id] - AND cc.database_id = i.database_id - AND cc.[schema_name] = i.[schema_name] - WHERE i.index_id IN (1,0) - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) - AND - (cc.total_columns >= 35 OR - cc.sum_max_length >= 2000) - ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 27: Addicted to strings.', 0,1) WITH NOWAIT; - WITH count_columns AS ( - SELECT [object_id], - [database_id], - [schema_name], - SUM(CASE WHEN system_type_name IN ('varchar','nvarchar','char') OR max_length=-1 THEN 1 ELSE 0 END) AS string_or_LOB_columns, - COUNT(*) AS total_columns - FROM #IndexColumns ic - WHERE index_id IN (1,0) /*Heap or clustered only*/ - GROUP BY [object_id], - [database_id], - [schema_name] - ) - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 27 AS check_id, - i.index_sanity_id, - 200 AS Priority, - N'Index Hoarder' AS findings_group, - N'Addicted to strings' AS finding, - [database_name] AS [Database Name], - N'http://BrentOzar.com/go/IndexHoarder' AS URL, - i.db_schema_object_name - + N' uses string or LOB types for ' + CAST((string_or_LOB_columns) AS NVARCHAR(10)) - + N' of ' + CAST(total_columns AS NVARCHAR(10)) - + N' columns. Check if data types are valid.' AS details, - i.index_definition, - secret_columns, - ISNULL(i.index_usage_summary,''), - ISNULL(ip.index_size_summary,'') - FROM #IndexSanity i - JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id - JOIN count_columns AS cc ON i.[object_id]=cc.[object_id] - AND cc.database_id = i.database_id - AND cc.[schema_name] = i.[schema_name] - CROSS APPLY (SELECT cc.total_columns - string_or_LOB_columns AS non_string_or_lob_columns) AS calc1 - WHERE i.index_id IN (1,0) - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) - AND calc1.non_string_or_lob_columns <= 1 - AND cc.total_columns > 3 - ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 28: Non-unique clustered index.', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 28 AS check_id, - i.index_sanity_id, - 100 AS Priority, - N'Index Hoarder' AS findings_group, - N'Non-Unique clustered index' AS finding, - [database_name] AS [Database Name], - N'http://BrentOzar.com/go/IndexHoarder' AS URL, - N'Uniquifiers will be required! Clustered index: ' + i.db_schema_object_name - + N' and all NC indexes. ' + - (SELECT CAST(COUNT(*) AS NVARCHAR(23)) - FROM #IndexSanity i2 - WHERE i2.[object_id]=i.[object_id] - AND i2.database_id = i.database_id - AND i2.index_id <> 1 - AND i2.is_disabled=0 - AND i2.is_hypothetical=0) - + N' NC indexes on the table.' - AS details, - i.index_definition, - secret_columns, - i.index_usage_summary, - ip.index_size_summary - FROM #IndexSanity i - JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id - WHERE index_id = 1 /* clustered only */ - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) - AND is_unique=0 /* not unique */ - AND is_CX_columnstore=0 /* not a clustered columnstore-- no unique option on those */ - ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 29: NC indexes with 0 reads. (Borderline) and < 10,000 writes', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 29 AS check_id, - i.index_sanity_id, - 150 AS Priority, - N'Index Hoarder' AS findings_group, - N'Unused NC index with Low Writes' AS finding, - [database_name] AS [Database Name], - N'http://BrentOzar.com/go/IndexHoarder' AS URL, - N'0 reads: ' + i.db_schema_object_indexid AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - sz.index_size_summary - FROM #IndexSanity AS i - JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.total_reads=0 - AND i.user_updates < 10000 - 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()) - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) - ORDER BY i.db_schema_object_indexid - OPTION ( RECOMPILE ); - - END; - ---------------------------------------- - --Feature-Phobic Indexes: Check_id 30-39 - ---------------------------------------- - BEGIN - RAISERROR(N'check_id 30: No indexes with includes', 0,1) WITH NOWAIT; - /* This does not work the way you'd expect with @GetAllDatabases = 1. For details: - https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/825 - */ - - SELECT database_name, - SUM(CASE WHEN count_included_columns > 0 THEN 1 ELSE 0 END) AS number_indexes_with_includes, - 100.* SUM(CASE WHEN count_included_columns > 0 THEN 1 ELSE 0 END) / ( 1.0 * COUNT(*) ) AS percent_indexes_with_includes - INTO #index_includes - FROM #IndexSanity - WHERE is_hypothetical = 0 - AND is_disabled = 0 - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) - GROUP BY database_name; - - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 30 AS check_id, - NULL AS index_sanity_id, - 250 AS Priority, - N'Feature-Phobic Indexes' AS findings_group, - database_name AS [Database Name], - N'No indexes use includes' AS finding, 'http://BrentOzar.com/go/IndexFeatures' AS URL, - N'No indexes use includes' AS details, - database_name + N' (Entire database)' AS index_definition, - N'' AS secret_columns, - N'N/A' AS index_usage_summary, - N'N/A' AS index_size_summary - FROM #index_includes - WHERE number_indexes_with_includes = 0 - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 31: < 3 percent of indexes have includes', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 31 AS check_id, - NULL AS index_sanity_id, - 150 AS Priority, - N'Feature-Phobic Indexes' AS findings_group, - N'Borderline: Includes are used in < 3% of indexes' AS findings, - database_name AS [Database Name], - N'http://BrentOzar.com/go/IndexFeatures' AS URL, - N'Only ' + CAST(percent_indexes_with_includes AS NVARCHAR(20)) + '% of indexes have includes' AS details, - N'Entire database' AS index_definition, - N'' AS secret_columns, - N'N/A' AS index_usage_summary, - N'N/A' AS index_size_summary - FROM #index_includes - WHERE number_indexes_with_includes > 0 AND percent_indexes_with_includes <= 3 - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 32: filtered indexes and indexed views', 0,1) WITH NOWAIT; - - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT DISTINCT - 32 AS check_id, - NULL AS index_sanity_id, - 250 AS Priority, - N'Feature-Phobic Indexes' AS findings_group, - N'Borderline: No filtered indexes or indexed views exist' AS finding, - i.database_name AS [Database Name], - N'http://BrentOzar.com/go/IndexFeatures' AS URL, - N'These are NOT always needed-- but do you know when you would use them?' AS details, - i.database_name + N' (Entire database)' AS index_definition, - N'' AS secret_columns, - N'N/A' AS index_usage_summary, - N'N/A' AS index_size_summary - FROM #IndexSanity i - WHERE i.database_name NOT IN ( - SELECT database_name - FROM #IndexSanity - WHERE filter_definition <> '' ) - AND i.database_name NOT IN ( - SELECT database_name - FROM #IndexSanity - WHERE is_indexed_view = 1 ) - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) - OPTION ( RECOMPILE ); - END; - RAISERROR(N'check_id 33: Potential filtered indexes based on column names.', 0,1) WITH NOWAIT; - - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 33 AS check_id, - i.index_sanity_id AS index_sanity_id, - 250 AS Priority, - N'Feature-Phobic Indexes' AS findings_group, - N'Potential filtered index (based on column name)' AS finding, - [database_name] AS [Database Name], - N'http://BrentOzar.com/go/IndexFeatures' AS URL, - N'A column name in this index suggests it might be a candidate for filtering (is%, %archive%, %active%, %flag%)' AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - sz.index_size_summary - FROM #IndexColumns ic - JOIN #IndexSanity i ON ic.[object_id]=i.[object_id] - AND ic.database_id =i.database_id - AND ic.schema_name = i.schema_name - AND ic.[index_id]=i.[index_id] - AND i.[index_id] > 1 /* non-clustered index */ - JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id - WHERE (column_name LIKE 'is%' - OR column_name LIKE '%archive%' - OR column_name LIKE '%active%' - OR column_name LIKE '%flag%') - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) - OPTION ( RECOMPILE ); RAISERROR(N'check_id 34: Filtered index definition columns not in index definition', 0,1) WITH NOWAIT; @@ -28969,10 +28639,10 @@ BEGIN; SELECT 34 AS check_id, i.index_sanity_id, 80 AS Priority, - N'Forgetful Indexes' AS findings_group, + N'Abnormal Psychology' AS findings_group, N'Filter Columns Not In Index Definition' AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/IndexFeatures' AS URL, + N'https://www.brentozar.com/go/IndexFeatures' AS URL, N'The index ' + QUOTENAME(i.index_name) + N' on [' @@ -28996,7 +28666,6 @@ BEGIN; ---------------------------------------- --Self Loathing Indexes : Check_id 40-49 ---------------------------------------- - BEGIN RAISERROR(N'check_id 40: Fillfactor in nonclustered 80 percent or less', 0,1) WITH NOWAIT; INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, @@ -29005,9 +28674,9 @@ BEGIN; i.index_sanity_id, 100 AS Priority, N'Self Loathing Indexes' AS findings_group, - N'Low Fill Factor: nonclustered index' AS finding, + N'Low Fill Factor on Nonclustered Index' AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/SelfLoathing' AS URL, + N'https://www.brentozar.com/go/SelfLoathing' AS URL, CAST(fill_factor AS NVARCHAR(10)) + N'% fill factor on ' + db_schema_object_indexid + N'. '+ CASE WHEN (last_user_update IS NULL OR user_updates < 1) THEN N'No writes have been made.' @@ -29023,7 +28692,6 @@ BEGIN; FROM #IndexSanity AS i JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id WHERE index_id > 1 - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) AND fill_factor BETWEEN 1 AND 80 OPTION ( RECOMPILE ); RAISERROR(N'check_id 40: Fillfactor in clustered 80 percent or less', 0,1) WITH NOWAIT; @@ -29033,9 +28701,9 @@ BEGIN; i.index_sanity_id, 100 AS Priority, N'Self Loathing Indexes' AS findings_group, - N'Low Fill Factor: clustered index' AS finding, + N'Low Fill Factor on Clustered Index' AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/SelfLoathing' AS URL, + N'https://www.brentozar.com/go/SelfLoathing' AS URL, N'Fill factor on ' + db_schema_object_indexid + N' is ' + CAST(fill_factor AS NVARCHAR(10)) + N'%. '+ CASE WHEN (last_user_update IS NULL OR user_updates < 1) THEN N'No writes have been made.' @@ -29051,52 +28719,9 @@ BEGIN; FROM #IndexSanity AS i JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id WHERE index_id = 1 - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) AND fill_factor BETWEEN 1 AND 80 OPTION ( RECOMPILE ); - RAISERROR(N'check_id 41: Hypothetical indexes ', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 41 AS check_id, - i.index_sanity_id, - 150 AS Priority, - N'Self Loathing Indexes' AS findings_group, - N'Hypothetical Index' AS finding, - [database_name] AS [Database Name], - N'http://BrentOzar.com/go/SelfLoathing' AS URL, - N'Hypothetical Index: ' + db_schema_object_indexid AS details, - i.index_definition, - i.secret_columns, - N'' AS index_usage_summary, - N'' AS index_size_summary - FROM #IndexSanity AS i - WHERE is_hypothetical = 1 - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) - OPTION ( RECOMPILE ); - - - RAISERROR(N'check_id 42: Disabled indexes', 0,1) WITH NOWAIT; - --Note: disabled NC indexes will have O rows in #IndexSanitySize! - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 42 AS check_id, - index_sanity_id, - 150 AS Priority, - N'Self Loathing Indexes' AS findings_group, - N'Disabled Index' AS finding, - [database_name] AS [Database Name], - N'http://BrentOzar.com/go/SelfLoathing' AS URL, - N'Disabled Index:' + db_schema_object_indexid AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - 'DISABLED' AS index_size_summary - FROM #IndexSanity AS i - WHERE is_disabled = 1 - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) - OPTION ( RECOMPILE ); - RAISERROR(N'check_id 43: Heaps with forwarded records', 0,1) WITH NOWAIT; WITH heaps_cte AS ( SELECT [object_id], @@ -29115,9 +28740,9 @@ BEGIN; i.index_sanity_id, 100 AS Priority, N'Self Loathing Indexes' AS findings_group, - N'Heaps with forwarded records' AS finding, + N'Heaps with Forwarded Fetches' AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/SelfLoathing' AS URL, + N'https://www.brentozar.com/go/SelfLoathing' AS URL, CASE WHEN h.forwarded_fetch_count >= 922337203685477 THEN '>= 922,337,203,685,477' WHEN @DaysUptime < 1 THEN CAST(h.forwarded_fetch_count AS NVARCHAR(256)) + N' forwarded fetches against heap: ' + db_schema_object_indexid ELSE REPLACE(CONVERT(NVARCHAR(256),CAST(CAST( @@ -29139,42 +28764,6 @@ BEGIN; AND sz.total_reserved_MB >= CASE WHEN NOT (@GetAllDatabases = 1 OR @Mode = 4) THEN @ThresholdMB ELSE sz.total_reserved_MB END OPTION ( RECOMPILE ); - RAISERROR(N'check_id 49: Heaps with deletes', 0,1) WITH NOWAIT; - WITH heaps_cte - AS ( SELECT [object_id], - [database_id], - [schema_name], - SUM(leaf_delete_count) AS leaf_delete_count - FROM #IndexPartitionSanity - GROUP BY [object_id], - [database_id], - [schema_name] - HAVING SUM(forwarded_fetch_count) < 1000 * @DaysUptime /* Only alert about indexes with no forwarded fetches - we already alerted about those in check_id 43 */ - AND SUM(leaf_delete_count) > 0) - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 49 AS check_id, - i.index_sanity_id, - 200 AS Priority, - N'Self Loathing Indexes' AS findings_group, - N'Heaps with deletes' AS finding, - [database_name] AS [Database Name], - N'http://BrentOzar.com/go/SelfLoathing' AS URL, - CAST(h.leaf_delete_count AS NVARCHAR(256)) + N' deletes against heap:' - + db_schema_object_indexid AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - sz.index_size_summary - FROM #IndexSanity i - JOIN heaps_cte h ON i.[object_id] = h.[object_id] - AND i.[database_id] = h.[database_id] - AND i.[schema_name] = h.[schema_name] - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.index_id = 0 - AND sz.total_reserved_MB >= CASE WHEN NOT (@GetAllDatabases = 1 OR @Mode = 4) THEN @ThresholdMB ELSE sz.total_reserved_MB END - OPTION ( RECOMPILE ); - RAISERROR(N'check_id 44: Large Heaps with reads or writes.', 0,1) WITH NOWAIT; WITH heaps_cte AS ( SELECT [object_id], @@ -29194,9 +28783,9 @@ BEGIN; i.index_sanity_id, 100 AS Priority, N'Self Loathing Indexes' AS findings_group, - N'Large Active heap' AS finding, + N'Large Active Heap' AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/SelfLoathing' AS URL, + N'https://www.brentozar.com/go/SelfLoathing' AS URL, N'Should this table be a heap? ' + db_schema_object_indexid AS details, i.index_definition, 'N/A' AS secret_columns, @@ -29211,7 +28800,6 @@ BEGIN; AND (i.total_reads > 0 OR i.user_updates > 0) AND sz.total_rows >= 100000 AND h.[object_id] IS NULL /*don't duplicate the prior check.*/ - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) OPTION ( RECOMPILE ); RAISERROR(N'check_id 45: Medium Heaps with reads or writes.', 0,1) WITH NOWAIT; @@ -29235,7 +28823,7 @@ BEGIN; N'Self Loathing Indexes' AS findings_group, N'Medium Active heap' AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/SelfLoathing' AS URL, + N'https://www.brentozar.com/go/SelfLoathing' AS URL, N'Should this table be a heap? ' + db_schema_object_indexid AS details, i.index_definition, 'N/A' AS secret_columns, @@ -29251,7 +28839,6 @@ BEGIN; (i.total_reads > 0 OR i.user_updates > 0) AND sz.total_rows >= 10000 AND sz.total_rows < 100000 AND h.[object_id] IS NULL /*don't duplicate the prior check.*/ - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) OPTION ( RECOMPILE ); RAISERROR(N'check_id 46: Small Heaps with reads or writes.', 0,1) WITH NOWAIT; @@ -29275,7 +28862,7 @@ BEGIN; N'Self Loathing Indexes' AS findings_group, N'Small Active heap' AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/SelfLoathing' AS URL, + N'https://www.brentozar.com/go/SelfLoathing' AS URL, N'Should this table be a heap? ' + db_schema_object_indexid AS details, i.index_definition, 'N/A' AS secret_columns, @@ -29291,7 +28878,6 @@ BEGIN; (i.total_reads > 0 OR i.user_updates > 0) AND sz.total_rows < 10000 AND h.[object_id] IS NULL /*don't duplicate the prior check.*/ - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) OPTION ( RECOMPILE ); RAISERROR(N'check_id 47: Heap with a Nonclustered Primary Key', 0,1) WITH NOWAIT; @@ -29303,7 +28889,7 @@ BEGIN; N'Self Loathing Indexes' AS findings_group, N'Heap with a Nonclustered Primary Key' AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/SelfLoathing' AS URL, + N'https://www.brentozar.com/go/SelfLoathing' AS URL, db_schema_object_indexid + N' is a HEAP with a Nonclustered Primary Key' AS details, i.index_definition, i.secret_columns, @@ -29322,7 +28908,7 @@ BEGIN; ) OPTION ( RECOMPILE ); - RAISERROR(N'check_id 48: Nonclustered indexes with a bad read to write ratio', 0,1) WITH NOWAIT; + RAISERROR(N'check_id 48: Nonclustered indexes with a bad read to write ratio', 0,1) WITH NOWAIT; INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, secret_columns, index_usage_summary, index_size_summary ) SELECT 48 AS check_id, @@ -29331,7 +28917,7 @@ BEGIN; N'Index Hoarder' AS findings_group, N'NC index with High Writes:Reads' AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/IndexHoarder' AS URL, + N'https://www.brentozar.com/go/IndexHoarder' AS URL, N'Reads: ' + REPLACE(CONVERT(NVARCHAR(30), CAST((i.total_reads) AS MONEY), 1), N'.00', N'') + N' Writes: ' @@ -29354,12 +28940,10 @@ BEGIN; ORDER BY i.db_schema_object_indexid OPTION ( RECOMPILE ); - END; ---------------------------------------- --Indexaphobia --Missing indexes with value >= 5 million: : Check_id 50-59 ---------------------------------------- - BEGIN RAISERROR(N'check_id 50: Indexaphobia.', 0,1) WITH NOWAIT; WITH index_size_cte AS ( SELECT i.database_id, @@ -29397,11 +28981,11 @@ BEGIN; SELECT ROW_NUMBER() OVER (ORDER BY magic_benefit_number DESC) AS rownum, 50 AS check_id, sz.index_sanity_id, - 10 AS Priority, + 40 AS Priority, N'Indexaphobia' AS findings_group, - N'High value missing index' AS finding, + N'High Value Missing Index' AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/Indexaphobia' AS URL, + N'https://www.brentozar.com/go/Indexaphobia' AS URL, mi.[statement] + N' Est. benefit per day: ' + CASE WHEN magic_benefit_number >= 922337203685477 THEN '>= 922,337,203,685,477' @@ -29430,11 +29014,738 @@ BEGIN; OPTION ( RECOMPILE ); - END; + + RAISERROR(N'check_id 68: Identity columns within 30 percent of the end of range', 0,1) WITH NOWAIT; + -- Allowed Ranges: + --int -2,147,483,648 to 2,147,483,647 + --smallint -32,768 to 32,768 + --tinyint 0 to 255 + + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 68 AS check_id, + i.index_sanity_id, + 80 AS Priority, + N'Abnormal Psychology' AS findings_group, + N'Identity Column Within ' + + CAST (calc1.percent_remaining AS NVARCHAR(256)) + + N' Percent End of Range' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, + i.db_schema_object_name + N'.' + QUOTENAME(ic.column_name) + + N' is an identity with type ' + ic.system_type_name + + N', last value of ' + + ISNULL((CONVERT(NVARCHAR(256),CAST(ic.last_value AS DECIMAL(38,0)), 1)),N'NULL') + + N', seed of ' + + ISNULL((CONVERT(NVARCHAR(256),CAST(ic.seed_value AS DECIMAL(38,0)), 1)),N'NULL') + + N', increment of ' + CAST(ic.increment_value AS NVARCHAR(256)) + + N', and range of ' + + CASE ic.system_type_name WHEN 'int' THEN N'+/- 2,147,483,647' + WHEN 'smallint' THEN N'+/- 32,768' + WHEN 'tinyint' THEN N'0 to 255' + ELSE 'unknown' + END + AS details, + i.index_definition, + secret_columns, + ISNULL(i.index_usage_summary,''), + ISNULL(ip.index_size_summary,'') + FROM #IndexSanity i + JOIN #IndexColumns ic ON + i.object_id=ic.object_id + AND i.database_id = ic.database_id + AND i.schema_name = ic.schema_name + AND i.index_id IN (0,1) /* heaps and cx only */ + AND ic.is_identity=1 + AND ic.system_type_name IN ('tinyint', 'smallint', 'int') + JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id + CROSS APPLY ( + SELECT CAST(CASE WHEN ic.increment_value >= 0 + THEN + CASE ic.system_type_name + WHEN 'int' THEN (2147483647 - (ISNULL(ic.last_value,ic.seed_value) + ic.increment_value)) / 2147483647.*100 + WHEN 'smallint' THEN (32768 - (ISNULL(ic.last_value,ic.seed_value) + ic.increment_value)) / 32768.*100 + WHEN 'tinyint' THEN ( 255 - (ISNULL(ic.last_value,ic.seed_value) + ic.increment_value)) / 255.*100 + ELSE 999 + END + ELSE --ic.increment_value is negative + CASE ic.system_type_name + WHEN 'int' THEN ABS(-2147483647 - (ISNULL(ic.last_value,ic.seed_value) + ic.increment_value)) / 2147483647.*100 + WHEN 'smallint' THEN ABS(-32768 - (ISNULL(ic.last_value,ic.seed_value) + ic.increment_value)) / 32768.*100 + WHEN 'tinyint' THEN ABS( 0 - (ISNULL(ic.last_value,ic.seed_value) + ic.increment_value)) / 255.*100 + ELSE -1 + END + END AS NUMERIC(5,1)) AS percent_remaining + ) AS calc1 + WHERE i.index_id IN (1,0) + AND calc1.percent_remaining <= 30 + OPTION (RECOMPILE); + + + RAISERROR(N'check_id 72: Columnstore indexes with Trace Flag 834', 0,1) WITH NOWAIT; + IF EXISTS (SELECT * FROM #IndexSanity WHERE index_type IN (5,6)) + AND EXISTS (SELECT * FROM #TraceStatus WHERE TraceFlag = 834 AND status = 1) + BEGIN + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 72 AS check_id, + i.index_sanity_id, + 80 AS Priority, + N'Abnormal Psychology' AS findings_group, + 'Columnstore Indexes with Trace Flag 834' AS finding, + [database_name] AS [Database Name], + N'https://support.microsoft.com/en-us/kb/3210239' AS URL, + i.db_schema_object_indexid AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + ISNULL(sz.index_size_summary,'') AS index_size_summary + FROM #IndexSanity AS i + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + WHERE i.index_type IN (5,6) + OPTION ( RECOMPILE ); + END; + + ---------------------------------------- + --Statistics Info: Check_id 90-99 + ---------------------------------------- + + RAISERROR(N'check_id 90: Outdated 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 ) + SELECT 90 AS check_id, + 90 AS Priority, + 'Functioning Statistaholics' AS findings_group, + 'Statistics Not Updated Recently', + s.database_name, + 'https://www.brentozar.com/go/stats' AS URL, + 'Statistics on this table were last updated ' + + CASE WHEN s.last_statistics_update IS NULL THEN N' NEVER ' + ELSE CONVERT(NVARCHAR(20), s.last_statistics_update) + + ' have had ' + CONVERT(NVARCHAR(100), s.modification_counter) + + ' modifications in that time, which is ' + + CONVERT(NVARCHAR(100), s.percent_modifications) + + '% of the table.' + END 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.last_statistics_update <= CONVERT(DATETIME, GETDATE() - 30) + AND s.percent_modifications >= 10. + AND s.rows >= 10000 + OPTION ( RECOMPILE ); + + RAISERROR(N'check_id 91: Statistics with a low 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 91 AS check_id, + 90 AS Priority, + 'Functioning Statistaholics' AS findings_group, + 'Low Sampling Rates', + s.database_name, + 'https://www.brentozar.com/go/stats' AS URL, + 'Only ' + CONVERT(NVARCHAR(100), s.percent_sampled) + '% of the rows were sampled during the last statistics update. This may lead to poor cardinality estimates.' 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.rows BETWEEN 10000 AND 1000000 AND s.percent_sampled < 10) + OR (s.rows > 1000000 AND s.percent_sampled < 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 ) + SELECT 92 AS check_id, + 90 AS Priority, + 'Functioning Statistaholics' AS findings_group, + 'Statistics With NO RECOMPUTE', + s.database_name, + 'https://www.brentozar.com/go/stats' AS URL, + 'The statistic ' + QUOTENAME(s.statistics_name) + ' is set to not recompute. This can be helpful if data is really skewed, but harmful if you expect automatic statistics updates.' 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.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 ) + SELECT 94 AS check_id, + 100 AS Priority, + 'Serial Forcer' AS findings_group, + 'Check Constraint with Scalar UDF' AS finding, + cc.database_name, + 'https://www.brentozar.com/go/computedscalar' AS URL, + 'The check constraint ' + QUOTENAME(cc.constraint_name) + ' on ' + QUOTENAME(cc.schema_name) + '.' + QUOTENAME(cc.table_name) + ' is based on ' + cc.definition + + '. That indicates it may reference a scalar function, or a CLR function with data access, which can cause all queries and maintenance to run serially.' AS details, + cc.column_definition, + 'N/A' AS secret_columns, + 'N/A' AS index_usage_summary, + 'N/A' AS index_size_summary + FROM #CheckConstraints AS cc + WHERE cc.is_function = 1 + OPTION ( RECOMPILE ); + + RAISERROR(N'check_id 99: Computed Columns 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 ) + SELECT 99 AS check_id, + 100 AS Priority, + 'Serial Forcer' AS findings_group, + 'Computed Column with Scalar UDF' AS finding, + cc.database_name, + 'https://www.brentozar.com/go/serialudf' AS URL, + 'The computed column ' + QUOTENAME(cc.column_name) + ' on ' + QUOTENAME(cc.schema_name) + '.' + QUOTENAME(cc.table_name) + ' is based on ' + cc.definition + + '. That indicates it may reference a scalar function, or a CLR function with data access, which can cause all queries and maintenance to run serially.' AS details, + cc.column_definition, + 'N/A' AS secret_columns, + 'N/A' AS index_usage_summary, + 'N/A' AS index_size_summary + FROM #ComputedColumns AS cc + WHERE cc.is_function = 1 + OPTION ( RECOMPILE ); + + + + END /* IF @Mode IN (0, 4) DIAGNOSE priorities 1-100 */ + + + + + + + + + + + + + + + + + + + + + + + + + + IF @Mode = 4 /* DIAGNOSE*/ + BEGIN; + RAISERROR(N'@Mode=4, running rules for priorities 101+.', 0,1) WITH NOWAIT; + + RAISERROR(N'check_id 21: More Than 5 Percent NC Indexes Are Unused', 0,1) WITH NOWAIT; + DECLARE @percent_NC_indexes_unused NUMERIC(29,1); + DECLARE @NC_indexes_unused_reserved_MB NUMERIC(29,1); + + SELECT @percent_NC_indexes_unused = ( 100.00 * SUM(CASE + WHEN total_reads = 0 + THEN 1 + ELSE 0 + END) ) / COUNT(*), + @NC_indexes_unused_reserved_MB = SUM(CASE + WHEN total_reads = 0 + THEN sz.total_reserved_MB + ELSE 0 + END) + FROM #IndexSanity i + 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()) + 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, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 21 AS check_id, + MAX(i.index_sanity_id) AS index_sanity_id, + 150 AS Priority, + N'Index Hoarder' AS findings_group, + N'More Than 5 Percent NC Indexes Are Unused' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/IndexHoarder' AS URL, + CAST (@percent_NC_indexes_unused AS NVARCHAR(30)) + N' percent NC indexes (' + CAST(COUNT(*) AS NVARCHAR(10)) + N') unused. ' + + N'These take up ' + CAST (@NC_indexes_unused_reserved_MB AS NVARCHAR(30)) + N'MB of space.' AS details, + i.database_name + ' (' + CAST (COUNT(*) AS NVARCHAR(30)) + N' indexes)' AS index_definition, + '' AS secret_columns, + CAST(SUM(total_reads) AS NVARCHAR(256)) + N' reads (ALL); ' + + CAST(SUM([user_updates]) AS NVARCHAR(256)) + N' writes (ALL)' AS index_usage_summary, + + REPLACE(CONVERT(NVARCHAR(30),CAST(MAX([total_rows]) AS MONEY), 1), '.00', '') + N' rows (MAX)' + + CASE WHEN SUM(total_reserved_MB) > 1024 THEN + N'; ' + CAST(CAST(SUM(total_reserved_MB)/1024. AS NUMERIC(29,1)) AS NVARCHAR(30)) + 'GB (ALL)' + WHEN SUM(total_reserved_MB) > 0 THEN + N'; ' + CAST(CAST(SUM(total_reserved_MB) AS NUMERIC(29,1)) AS NVARCHAR(30)) + 'MB (ALL)' + ELSE '' + END AS index_size_summary + FROM #IndexSanity i + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + 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()) + GROUP BY i.database_name + OPTION ( RECOMPILE ); + + RAISERROR(N'check_id 23: Indexes with 7 or more columns. (Borderline)', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 23 AS check_id, + i.index_sanity_id, + 150 AS Priority, + N'Index Hoarder' AS findings_group, + N'Borderline: Wide Indexes (7 or More Columns)' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/IndexHoarder' AS URL, + CAST(count_key_columns + count_included_columns AS NVARCHAR(10)) + ' columns on ' + + i.db_schema_object_indexid AS details, i.index_definition, + i.secret_columns, + i.index_usage_summary, + sz.index_size_summary + FROM #IndexSanity AS i + JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id + WHERE ( count_key_columns + count_included_columns ) >= 7 + OPTION ( RECOMPILE ); + + RAISERROR(N'check_id 24: Wide clustered indexes (> 3 columns or > 16 bytes).', 0,1) WITH NOWAIT; + WITH count_columns AS ( + SELECT database_id, [object_id], + SUM(CASE max_length WHEN -1 THEN 0 ELSE max_length END) AS sum_max_length + FROM #IndexColumns ic + WHERE index_id IN (1,0) /*Heap or clustered only*/ + AND key_ordinal > 0 + GROUP BY database_id, object_id + ) + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 24 AS check_id, + i.index_sanity_id, + 150 AS Priority, + N'Index Hoarder' AS findings_group, + N'Wide Clustered Index (> 3 columns OR > 16 bytes)' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/IndexHoarder' AS URL, + CAST (i.count_key_columns AS NVARCHAR(10)) + N' columns with potential size of ' + + CAST(cc.sum_max_length AS NVARCHAR(10)) + + N' bytes in clustered index:' + i.db_schema_object_name + + N'. ' + + (SELECT CAST(COUNT(*) AS NVARCHAR(23)) + FROM #IndexSanity i2 + WHERE i2.[object_id]=i.[object_id] + AND i2.database_id = i.database_id + AND i2.index_id <> 1 + AND i2.is_disabled=0 + AND i2.is_hypothetical=0) + + N' NC indexes on the table.' + AS details, + i.index_definition, + secret_columns, + i.index_usage_summary, + ip.index_size_summary + FROM #IndexSanity i + JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id + JOIN count_columns AS cc ON i.[object_id]=cc.[object_id] + AND i.database_id = cc.database_id + WHERE index_id = 1 /* clustered only */ + AND + (count_key_columns > 3 /*More than three key columns.*/ + OR cc.sum_max_length > 16 /*More than 16 bytes in key */) + AND i.is_CX_columnstore = 0 + ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); + + RAISERROR(N'check_id 25: Addicted to nullable columns.', 0,1) WITH NOWAIT; + WITH count_columns AS ( + SELECT [object_id], + [database_id], + [schema_name], + SUM(CASE is_nullable WHEN 1 THEN 0 ELSE 1 END) AS non_nullable_columns, + COUNT(*) AS total_columns + FROM #IndexColumns ic + WHERE index_id IN (1,0) /*Heap or clustered only*/ + GROUP BY [object_id], + [database_id], + [schema_name] + ) + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 25 AS check_id, + i.index_sanity_id, + 200 AS Priority, + N'Index Hoarder' AS findings_group, + N'Addicted to Nulls' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/IndexHoarder' AS URL, + i.db_schema_object_name + + N' allows null in ' + CAST((total_columns-non_nullable_columns) AS NVARCHAR(10)) + + N' of ' + CAST(total_columns AS NVARCHAR(10)) + + N' columns.' AS details, + i.index_definition, + secret_columns, + ISNULL(i.index_usage_summary,''), + ISNULL(ip.index_size_summary,'') + FROM #IndexSanity i + JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id + JOIN count_columns AS cc ON i.[object_id]=cc.[object_id] + AND cc.database_id = ip.database_id + AND cc.[schema_name] = ip.[schema_name] + WHERE i.index_id IN (1,0) + AND cc.non_nullable_columns < 2 + AND cc.total_columns > 3 + ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); + + RAISERROR(N'check_id 26: Wide tables (35+ cols or > 2000 non-LOB bytes).', 0,1) WITH NOWAIT; + WITH count_columns AS ( + SELECT [object_id], + [database_id], + [schema_name], + SUM(CASE max_length WHEN -1 THEN 1 ELSE 0 END) AS count_lob_columns, + SUM(CASE max_length WHEN -1 THEN 0 ELSE max_length END) AS sum_max_length, + COUNT(*) AS total_columns + FROM #IndexColumns ic + WHERE index_id IN (1,0) /*Heap or clustered only*/ + GROUP BY [object_id], + [database_id], + [schema_name] + ) + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 26 AS check_id, + i.index_sanity_id, + 150 AS Priority, + N'Index Hoarder' AS findings_group, + N'Wide Tables: 35+ cols or > 2000 non-LOB bytes' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/IndexHoarder' AS URL, + i.db_schema_object_name + + N' has ' + CAST((total_columns) AS NVARCHAR(10)) + + N' total columns with a max possible width of ' + CAST(sum_max_length AS NVARCHAR(10)) + + N' bytes.' + + CASE WHEN count_lob_columns > 0 THEN CAST((count_lob_columns) AS NVARCHAR(10)) + + ' columns are LOB types.' ELSE '' + END + AS details, + i.index_definition, + secret_columns, + ISNULL(i.index_usage_summary,''), + ISNULL(ip.index_size_summary,'') + FROM #IndexSanity i + JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id + JOIN count_columns AS cc ON i.[object_id]=cc.[object_id] + AND cc.database_id = i.database_id + AND cc.[schema_name] = i.[schema_name] + WHERE i.index_id IN (1,0) + AND + (cc.total_columns >= 35 OR + cc.sum_max_length >= 2000) + ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); + + RAISERROR(N'check_id 27: Addicted to strings.', 0,1) WITH NOWAIT; + WITH count_columns AS ( + SELECT [object_id], + [database_id], + [schema_name], + SUM(CASE WHEN system_type_name IN ('varchar','nvarchar','char') OR max_length=-1 THEN 1 ELSE 0 END) AS string_or_LOB_columns, + COUNT(*) AS total_columns + FROM #IndexColumns ic + WHERE index_id IN (1,0) /*Heap or clustered only*/ + GROUP BY [object_id], + [database_id], + [schema_name] + ) + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 27 AS check_id, + i.index_sanity_id, + 200 AS Priority, + N'Index Hoarder' AS findings_group, + N'Addicted to strings' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/IndexHoarder' AS URL, + i.db_schema_object_name + + N' uses string or LOB types for ' + CAST((string_or_LOB_columns) AS NVARCHAR(10)) + + N' of ' + CAST(total_columns AS NVARCHAR(10)) + + N' columns. Check if data types are valid.' AS details, + i.index_definition, + secret_columns, + ISNULL(i.index_usage_summary,''), + ISNULL(ip.index_size_summary,'') + FROM #IndexSanity i + JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id + JOIN count_columns AS cc ON i.[object_id]=cc.[object_id] + AND cc.database_id = i.database_id + AND cc.[schema_name] = i.[schema_name] + CROSS APPLY (SELECT cc.total_columns - string_or_LOB_columns AS non_string_or_lob_columns) AS calc1 + WHERE i.index_id IN (1,0) + AND calc1.non_string_or_lob_columns <= 1 + AND cc.total_columns > 3 + ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); + + RAISERROR(N'check_id 28: Non-unique clustered index.', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 28 AS check_id, + i.index_sanity_id, + 150 AS Priority, + N'Index Hoarder' AS findings_group, + N'Non-Unique Clustered JIndex' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/IndexHoarder' AS URL, + N'Uniquifiers will be required! Clustered index: ' + i.db_schema_object_name + + N' and all NC indexes. ' + + (SELECT CAST(COUNT(*) AS NVARCHAR(23)) + FROM #IndexSanity i2 + WHERE i2.[object_id]=i.[object_id] + AND i2.database_id = i.database_id + AND i2.index_id <> 1 + AND i2.is_disabled=0 + AND i2.is_hypothetical=0) + + N' NC indexes on the table.' + AS details, + i.index_definition, + secret_columns, + i.index_usage_summary, + ip.index_size_summary + FROM #IndexSanity i + JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id + WHERE index_id = 1 /* clustered only */ + AND is_unique=0 /* not unique */ + AND is_CX_columnstore=0 /* not a clustered columnstore-- no unique option on those */ + ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); + + RAISERROR(N'check_id 29: NC indexes with 0 reads. (Borderline) and < 10,000 writes', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 29 AS check_id, + i.index_sanity_id, + 150 AS Priority, + N'Index Hoarder' AS findings_group, + N'Unused NC index with Low Writes' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/IndexHoarder' AS URL, + N'0 reads: ' + i.db_schema_object_indexid AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + sz.index_size_summary + FROM #IndexSanity AS i + JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id + WHERE i.total_reads=0 + AND i.user_updates < 10000 + 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()) + ORDER BY i.db_schema_object_indexid + OPTION ( RECOMPILE ); + + + ---------------------------------------- + --Feature-Phobic Indexes: Check_id 30-39 + ---------------------------------------- + RAISERROR(N'check_id 30: No indexes with includes', 0,1) WITH NOWAIT; + /* This does not work the way you'd expect with @GetAllDatabases = 1. For details: + https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/825 + */ + + SELECT database_name, + SUM(CASE WHEN count_included_columns > 0 THEN 1 ELSE 0 END) AS number_indexes_with_includes, + 100.* SUM(CASE WHEN count_included_columns > 0 THEN 1 ELSE 0 END) / ( 1.0 * COUNT(*) ) AS percent_indexes_with_includes + INTO #index_includes + FROM #IndexSanity + WHERE is_hypothetical = 0 + AND is_disabled = 0 + AND NOT (@GetAllDatabases = 1 OR @Mode = 0) + GROUP BY database_name; + + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 30 AS check_id, + NULL AS index_sanity_id, + 250 AS Priority, + N'Feature-Phobic Indexes' AS findings_group, + database_name AS [Database Name], + N'No Indexes Use Includes' AS finding, 'https://www.brentozar.com/go/IndexFeatures' AS URL, + N'No Indexes Use Includes' AS details, + database_name + N' (Entire database)' AS index_definition, + N'' AS secret_columns, + N'N/A' AS index_usage_summary, + N'N/A' AS index_size_summary + FROM #index_includes + WHERE number_indexes_with_includes = 0 + OPTION ( RECOMPILE ); + + RAISERROR(N'check_id 31: < 3 percent of indexes have includes', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 31 AS check_id, + NULL AS index_sanity_id, + 250 AS Priority, + N'Feature-Phobic Indexes' AS findings_group, + N'Few Indexes Use Includes' AS findings, + database_name AS [Database Name], + N'https://www.brentozar.com/go/IndexFeatures' AS URL, + N'Only ' + CAST(percent_indexes_with_includes AS NVARCHAR(20)) + '% of indexes have includes' AS details, + N'Entire database' AS index_definition, + N'' AS secret_columns, + N'N/A' AS index_usage_summary, + N'N/A' AS index_size_summary + FROM #index_includes + WHERE number_indexes_with_includes > 0 AND percent_indexes_with_includes <= 3 + OPTION ( RECOMPILE ); + + RAISERROR(N'check_id 32: filtered indexes and indexed views', 0,1) WITH NOWAIT; + + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT DISTINCT + 32 AS check_id, + NULL AS index_sanity_id, + 250 AS Priority, + N'Feature-Phobic Indexes' AS findings_group, + N'No Filtered Indexes or Indexed Views' AS finding, + i.database_name AS [Database Name], + N'https://www.brentozar.com/go/IndexFeatures' AS URL, + N'These are NOT always needed-- but do you know when you would use them?' AS details, + i.database_name + N' (Entire database)' AS index_definition, + N'' AS secret_columns, + N'N/A' AS index_usage_summary, + N'N/A' AS index_size_summary + FROM #IndexSanity i + WHERE i.database_name NOT IN ( + SELECT database_name + FROM #IndexSanity + WHERE filter_definition <> '' ) + AND i.database_name NOT IN ( + SELECT database_name + FROM #IndexSanity + WHERE is_indexed_view = 1 ) + OPTION ( RECOMPILE ); + + RAISERROR(N'check_id 33: Potential filtered indexes based on column names.', 0,1) WITH NOWAIT; + + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 33 AS check_id, + i.index_sanity_id AS index_sanity_id, + 250 AS Priority, + N'Feature-Phobic Indexes' AS findings_group, + N'Potential Filtered Index (Based on Column Name)' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/IndexFeatures' AS URL, + N'A column name in this index suggests it might be a candidate for filtering (is%, %archive%, %active%, %flag%)' AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + sz.index_size_summary + FROM #IndexColumns ic + JOIN #IndexSanity i ON ic.[object_id]=i.[object_id] + AND ic.database_id =i.database_id + AND ic.schema_name = i.schema_name + AND ic.[index_id]=i.[index_id] + AND i.[index_id] > 1 /* non-clustered index */ + JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id + WHERE (column_name LIKE 'is%' + OR column_name LIKE '%archive%' + OR column_name LIKE '%active%' + OR column_name LIKE '%flag%') + OPTION ( RECOMPILE ); + + RAISERROR(N'check_id 41: Hypothetical indexes ', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 41 AS check_id, + i.index_sanity_id, + 150 AS Priority, + N'Self Loathing Indexes' AS findings_group, + N'Hypothetical Index' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/SelfLoathing' AS URL, + N'Hypothetical Index: ' + db_schema_object_indexid AS details, + i.index_definition, + i.secret_columns, + N'' AS index_usage_summary, + N'' AS index_size_summary + FROM #IndexSanity AS i + WHERE is_hypothetical = 1 + OPTION ( RECOMPILE ); + + + RAISERROR(N'check_id 42: Disabled indexes', 0,1) WITH NOWAIT; + --Note: disabled NC indexes will have O rows in #IndexSanitySize! + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 42 AS check_id, + index_sanity_id, + 150 AS Priority, + N'Self Loathing Indexes' AS findings_group, + N'Disabled Index' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/SelfLoathing' AS URL, + N'Disabled Index:' + db_schema_object_indexid AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + 'DISABLED' AS index_size_summary + FROM #IndexSanity AS i + WHERE is_disabled = 1 + OPTION ( RECOMPILE ); + + RAISERROR(N'check_id 49: Heaps with deletes', 0,1) WITH NOWAIT; + WITH heaps_cte + AS ( SELECT [object_id], + [database_id], + [schema_name], + SUM(leaf_delete_count) AS leaf_delete_count + FROM #IndexPartitionSanity + GROUP BY [object_id], + [database_id], + [schema_name] + HAVING SUM(forwarded_fetch_count) < 1000 * @DaysUptime /* Only alert about indexes with no forwarded fetches - we already alerted about those in check_id 43 */ + AND SUM(leaf_delete_count) > 0) + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 49 AS check_id, + i.index_sanity_id, + 200 AS Priority, + N'Self Loathing Indexes' AS findings_group, + N'Heaps with Deletes' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/SelfLoathing' AS URL, + CAST(h.leaf_delete_count AS NVARCHAR(256)) + N' deletes against heap:' + + db_schema_object_indexid AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + sz.index_size_summary + FROM #IndexSanity i + JOIN heaps_cte h ON i.[object_id] = h.[object_id] + AND i.[database_id] = h.[database_id] + AND i.[schema_name] = h.[schema_name] + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + WHERE i.index_id = 0 + AND sz.total_reserved_MB >= CASE WHEN NOT (@GetAllDatabases = 1 OR @Mode = 4) THEN @ThresholdMB ELSE sz.total_reserved_MB END + OPTION ( RECOMPILE ); + ---------------------------------------- --Abnormal Psychology : Check_id 60-79 ---------------------------------------- - BEGIN RAISERROR(N'check_id 60: XML indexes', 0,1) WITH NOWAIT; INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, secret_columns, index_usage_summary, index_size_summary ) @@ -29442,9 +29753,9 @@ BEGIN; i.index_sanity_id, 150 AS Priority, N'Abnormal Psychology' AS findings_group, - N'XML Indexes' AS finding, + N'XML Index' AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/AbnormalPsychology' AS URL, + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, i.db_schema_object_indexid AS details, i.index_definition, i.secret_columns, @@ -29453,7 +29764,6 @@ BEGIN; FROM #IndexSanity AS i JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id WHERE i.is_XML = 1 - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) OPTION ( RECOMPILE ); RAISERROR(N'check_id 61: Columnstore indexes', 0,1) WITH NOWAIT; @@ -29468,7 +29778,7 @@ BEGIN; ELSE N'Clustered Columnstore Index' END AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/AbnormalPsychology' AS URL, + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, i.db_schema_object_indexid AS details, i.index_definition, i.secret_columns, @@ -29477,7 +29787,6 @@ BEGIN; FROM #IndexSanity AS i JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id WHERE i.is_NC_columnstore = 1 OR i.is_CX_columnstore=1 - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) OPTION ( RECOMPILE ); @@ -29488,9 +29797,9 @@ BEGIN; i.index_sanity_id, 150 AS Priority, N'Abnormal Psychology' AS findings_group, - N'Spatial indexes' AS finding, + N'Spatial Index' AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/AbnormalPsychology' AS URL, + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, i.db_schema_object_indexid AS details, i.index_definition, i.secret_columns, @@ -29499,7 +29808,6 @@ BEGIN; FROM #IndexSanity AS i JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id WHERE i.is_spatial = 1 - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) OPTION ( RECOMPILE ); RAISERROR(N'check_id 63: Compressed indexes', 0,1) WITH NOWAIT; @@ -29509,9 +29817,9 @@ BEGIN; i.index_sanity_id, 150 AS Priority, N'Abnormal Psychology' AS findings_group, - N'Compressed indexes' AS finding, + N'Compressed Index' AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/AbnormalPsychology' AS URL, + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, i.db_schema_object_indexid + N'. COMPRESSION: ' + sz.data_compression_desc AS details, i.index_definition, i.secret_columns, @@ -29520,7 +29828,6 @@ BEGIN; FROM #IndexSanity AS i JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id WHERE sz.data_compression_desc LIKE '%PAGE%' OR sz.data_compression_desc LIKE '%ROW%' - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) OPTION ( RECOMPILE ); RAISERROR(N'check_id 64: Partitioned', 0,1) WITH NOWAIT; @@ -29530,9 +29837,9 @@ BEGIN; i.index_sanity_id, 150 AS Priority, N'Abnormal Psychology' AS findings_group, - N'Partitioned indexes' AS finding, + N'Partitioned Index' AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/AbnormalPsychology' AS URL, + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, i.db_schema_object_indexid AS details, i.index_definition, i.secret_columns, @@ -29541,7 +29848,6 @@ BEGIN; FROM #IndexSanity AS i JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id WHERE i.partition_key_column_name IS NOT NULL - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) OPTION ( RECOMPILE ); RAISERROR(N'check_id 65: Non-Aligned Partitioned', 0,1) WITH NOWAIT; @@ -29551,9 +29857,9 @@ BEGIN; i.index_sanity_id, 150 AS Priority, N'Abnormal Psychology' AS findings_group, - N'Non-Aligned index on a partitioned table' AS finding, + N'Non-Aligned Index on a Partitioned Table' AS finding, i.[database_name] AS [Database Name], - N'http://BrentOzar.com/go/AbnormalPsychology' AS URL, + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, i.db_schema_object_indexid AS details, i.index_definition, i.secret_columns, @@ -29577,9 +29883,9 @@ BEGIN; i.index_sanity_id, 200 AS Priority, N'Abnormal Psychology' AS findings_group, - N'Recently created tables/indexes (1 week)' AS finding, + N'Recently Created Tables/Indexes (1 week)' AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/AbnormalPsychology' AS URL, + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, i.db_schema_object_indexid + N' was created on ' + CONVERT(NVARCHAR(16),i.create_date,121) + N'. Tables/indexes which are dropped/created regularly require special methods for index tuning.' @@ -29591,7 +29897,6 @@ BEGIN; FROM #IndexSanity AS i JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id WHERE i.create_date >= DATEADD(dd,-7,GETDATE()) - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) OPTION ( RECOMPILE ); RAISERROR(N'check_id 67: Recently modified tables/indexes (2 days)', 0,1) WITH NOWAIT; @@ -29601,9 +29906,9 @@ BEGIN; i.index_sanity_id, 200 AS Priority, N'Abnormal Psychology' AS findings_group, - N'Recently modified tables/indexes (2 days)' AS finding, + N'Recently Modified Tables/Indexes (2 days)' AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/AbnormalPsychology' AS URL, + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, i.db_schema_object_indexid + N' was modified on ' + CONVERT(NVARCHAR(16),i.modify_date,121) + N'. A large amount of recently modified indexes may mean a lot of rebuilds are occurring each night.' @@ -29615,115 +29920,10 @@ BEGIN; FROM #IndexSanity AS i JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id WHERE i.modify_date > DATEADD(dd,-2,GETDATE()) - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) AND /*Exclude recently created tables.*/ i.create_date < DATEADD(dd,-7,GETDATE()) OPTION ( RECOMPILE ); - RAISERROR(N'check_id 68: Identity columns within 30 percent of the end of range', 0,1) WITH NOWAIT; - -- Allowed Ranges: - --int -2,147,483,648 to 2,147,483,647 - --smallint -32,768 to 32,768 - --tinyint 0 to 255 - - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 68 AS check_id, - i.index_sanity_id, - 200 AS Priority, - N'Abnormal Psychology' AS findings_group, - N'Identity column within ' + - CAST (calc1.percent_remaining AS NVARCHAR(256)) - + N' percent end of range' AS finding, - [database_name] AS [Database Name], - N'http://BrentOzar.com/go/AbnormalPsychology' AS URL, - i.db_schema_object_name + N'.' + QUOTENAME(ic.column_name) - + N' is an identity with type ' + ic.system_type_name - + N', last value of ' - + ISNULL((CONVERT(NVARCHAR(256),CAST(ic.last_value AS DECIMAL(38,0)), 1)),N'NULL') - + N', seed of ' - + ISNULL((CONVERT(NVARCHAR(256),CAST(ic.seed_value AS DECIMAL(38,0)), 1)),N'NULL') - + N', increment of ' + CAST(ic.increment_value AS NVARCHAR(256)) - + N', and range of ' + - CASE ic.system_type_name WHEN 'int' THEN N'+/- 2,147,483,647' - WHEN 'smallint' THEN N'+/- 32,768' - WHEN 'tinyint' THEN N'0 to 255' - ELSE 'unknown' - END - AS details, - i.index_definition, - secret_columns, - ISNULL(i.index_usage_summary,''), - ISNULL(ip.index_size_summary,'') - FROM #IndexSanity i - JOIN #IndexColumns ic ON - i.object_id=ic.object_id - AND i.database_id = ic.database_id - AND i.schema_name = ic.schema_name - AND i.index_id IN (0,1) /* heaps and cx only */ - AND ic.is_identity=1 - AND ic.system_type_name IN ('tinyint', 'smallint', 'int') - JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id - CROSS APPLY ( - SELECT CAST(CASE WHEN ic.increment_value >= 0 - THEN - CASE ic.system_type_name - WHEN 'int' THEN (2147483647 - (ISNULL(ic.last_value,ic.seed_value) + ic.increment_value)) / 2147483647.*100 - WHEN 'smallint' THEN (32768 - (ISNULL(ic.last_value,ic.seed_value) + ic.increment_value)) / 32768.*100 - WHEN 'tinyint' THEN ( 255 - (ISNULL(ic.last_value,ic.seed_value) + ic.increment_value)) / 255.*100 - ELSE 999 - END - ELSE --ic.increment_value is negative - CASE ic.system_type_name - WHEN 'int' THEN ABS(-2147483647 - (ISNULL(ic.last_value,ic.seed_value) + ic.increment_value)) / 2147483647.*100 - WHEN 'smallint' THEN ABS(-32768 - (ISNULL(ic.last_value,ic.seed_value) + ic.increment_value)) / 32768.*100 - WHEN 'tinyint' THEN ABS( 0 - (ISNULL(ic.last_value,ic.seed_value) + ic.increment_value)) / 255.*100 - ELSE -1 - END - END AS NUMERIC(5,1)) AS percent_remaining - ) AS calc1 - WHERE i.index_id IN (1,0) - AND calc1.percent_remaining <= 30 - UNION ALL - SELECT 68 AS check_id, - i.index_sanity_id, - 200 AS Priority, - N'Abnormal Psychology' AS findings_group, - N'Identity column using a negative seed or increment other than 1' AS finding, - [database_name] AS [Database Name], - N'http://BrentOzar.com/go/AbnormalPsychology' AS URL, - i.db_schema_object_name + N'.' + QUOTENAME(ic.column_name) - + N' is an identity with type ' + ic.system_type_name - + N', last value of ' - + ISNULL((CONVERT(NVARCHAR(256),CAST(ic.last_value AS DECIMAL(38,0)), 1)),N'NULL') - + N', seed of ' - + ISNULL((CONVERT(NVARCHAR(256),CAST(ic.seed_value AS DECIMAL(38,0)), 1)),N'NULL') - + N', increment of ' + CAST(ic.increment_value AS NVARCHAR(256)) - + N', and range of ' + - CASE ic.system_type_name WHEN 'int' THEN N'+/- 2,147,483,647' - WHEN 'smallint' THEN N'+/- 32,768' - WHEN 'tinyint' THEN N'0 to 255' - ELSE 'unknown' - END - AS details, - i.index_definition, - secret_columns, - ISNULL(i.index_usage_summary,''), - ISNULL(ip.index_size_summary,'') - FROM #IndexSanity i - JOIN #IndexColumns ic ON - i.object_id=ic.object_id - AND i.database_id = ic.database_id - AND i.schema_name = ic.schema_name - AND i.index_id IN (0,1) /* heaps and cx only */ - AND ic.is_identity=1 - AND ic.system_type_name IN ('tinyint', 'smallint', 'int') - JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id - WHERE i.index_id IN (1,0) - AND (ic.seed_value < 0 OR ic.increment_value <> 1) - ORDER BY finding, details DESC - OPTION ( RECOMPILE ); - RAISERROR(N'check_id 69: Column collation does not match database collation', 0,1) WITH NOWAIT; WITH count_columns AS ( SELECT [object_id], @@ -29743,9 +29943,9 @@ BEGIN; i.index_sanity_id, 150 AS Priority, N'Abnormal Psychology' AS findings_group, - N'Column collation does not match database collation' AS finding, + N'Column Collation Does Not Match Database Collation' AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/AbnormalPsychology' AS URL, + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, i.db_schema_object_name + N' has ' + CAST(column_count AS NVARCHAR(20)) + N' column' + CASE WHEN column_count > 1 THEN 's' ELSE '' END @@ -29761,7 +29961,6 @@ BEGIN; AND cc.database_id = i.database_id AND cc.schema_name = i.schema_name WHERE i.index_id IN (1,0) - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); RAISERROR(N'check_id 70: Replicated columns', 0,1) WITH NOWAIT; @@ -29783,9 +29982,9 @@ BEGIN; i.index_sanity_id, 200 AS Priority, N'Abnormal Psychology' AS findings_group, - N'Replicated columns' AS finding, + N'Replicated Columns' AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/AbnormalPsychology' AS URL, + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, i.db_schema_object_name + N' has ' + CAST(replicated_column_count AS NVARCHAR(20)) + N' out of ' + CAST(column_count AS NVARCHAR(20)) @@ -29803,7 +30002,6 @@ BEGIN; AND i.schema_name = cc.schema_name WHERE i.index_id IN (1,0) AND replicated_column_count > 0 - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); @@ -29816,7 +30014,7 @@ BEGIN; N'Abnormal Psychology' AS findings_group, N'Cascading Updates or Deletes' AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/AbnormalPsychology' AS URL, + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, N'Foreign Key ' + foreign_key_name + N' on ' + QUOTENAME(parent_object_name) + N'(' + LTRIM(parent_fk_columns) + N')' + N' referencing ' + QUOTENAME(referenced_object_name) + N'(' + LTRIM(referenced_fk_columns) + N')' @@ -29834,32 +30032,8 @@ BEGIN; FROM #ForeignKeys fk WHERE ([delete_referential_action_desc] <> N'NO_ACTION' OR [update_referential_action_desc] <> N'NO_ACTION') - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) OPTION ( RECOMPILE ); - RAISERROR(N'check_id 72: Columnstore indexes with Trace Flag 834', 0,1) WITH NOWAIT; - IF EXISTS (SELECT * FROM #IndexSanity WHERE index_type IN (5,6)) - AND EXISTS (SELECT * FROM #TraceStatus WHERE TraceFlag = 834 AND status = 1) - BEGIN - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 72 AS check_id, - i.index_sanity_id, - 150 AS Priority, - N'Abnormal Psychology' AS findings_group, - 'Columnstore Indexes are being used in conjunction with trace flag 834. Visit the link to see why this can be a bad idea' AS finding, - [database_name] AS [Database Name], - N'https://support.microsoft.com/en-us/kb/3210239' AS URL, - i.db_schema_object_indexid AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - ISNULL(sz.index_size_summary,'') AS index_size_summary - FROM #IndexSanity AS i - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.index_type IN (5,6) - OPTION ( RECOMPILE ); - END; RAISERROR(N'check_id 73: In-Memory OLTP', 0,1) WITH NOWAIT; INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, @@ -29870,7 +30044,7 @@ BEGIN; N'Abnormal Psychology' AS findings_group, N'In-Memory OLTP' AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/AbnormalPsychology' AS URL, + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, i.db_schema_object_indexid AS details, i.index_definition, i.secret_columns, @@ -29879,15 +30053,53 @@ BEGIN; FROM #IndexSanity AS i JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id WHERE i.is_in_memory_oltp = 1 - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) OPTION ( RECOMPILE ); - END; + RAISERROR(N'check_id 74: Identity column with unusual seed', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 74 AS check_id, + i.index_sanity_id, + 200 AS Priority, + N'Abnormal Psychology' AS findings_group, + N'Identity Column Using a Negative Seed or Increment Other Than 1' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, + i.db_schema_object_name + N'.' + QUOTENAME(ic.column_name) + + N' is an identity with type ' + ic.system_type_name + + N', last value of ' + + ISNULL((CONVERT(NVARCHAR(256),CAST(ic.last_value AS DECIMAL(38,0)), 1)),N'NULL') + + N', seed of ' + + ISNULL((CONVERT(NVARCHAR(256),CAST(ic.seed_value AS DECIMAL(38,0)), 1)),N'NULL') + + N', increment of ' + CAST(ic.increment_value AS NVARCHAR(256)) + + N', and range of ' + + CASE ic.system_type_name WHEN 'int' THEN N'+/- 2,147,483,647' + WHEN 'smallint' THEN N'+/- 32,768' + WHEN 'tinyint' THEN N'0 to 255' + ELSE 'unknown' + END + AS details, + i.index_definition, + secret_columns, + ISNULL(i.index_usage_summary,''), + ISNULL(ip.index_size_summary,'') + FROM #IndexSanity i + JOIN #IndexColumns ic ON + i.object_id=ic.object_id + AND i.database_id = ic.database_id + AND i.schema_name = ic.schema_name + AND i.index_id IN (0,1) /* heaps and cx only */ + AND ic.is_identity=1 + AND ic.system_type_name IN ('tinyint', 'smallint', 'int') + JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id + WHERE i.index_id IN (1,0) + AND (ic.seed_value < 0 OR ic.increment_value <> 1) + ORDER BY finding, details DESC + OPTION ( RECOMPILE ); - ---------------------------------------- + ---------------------------------------- --Workaholics: Check_id 80-89 ---------------------------------------- - BEGIN RAISERROR(N'check_id 80: Most scanned indexes (index_usage_stats)', 0,1) WITH NOWAIT; INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, @@ -29902,9 +30114,9 @@ BEGIN; i.index_sanity_id AS index_sanity_id, 200 AS Priority, N'Workaholics' AS findings_group, - N'Scan-a-lots (index_usage_stats)' AS finding, + N'Scan-a-lots (index-usage-stats)' AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/Workaholics' AS URL, + N'https://www.brentozar.com/go/Workaholics' AS URL, REPLACE(CONVERT( NVARCHAR(50),CAST(i.user_scans AS MONEY),1),'.00','') + N' scans against ' + i.db_schema_object_indexid + N'. Latest scan: ' + ISNULL(CAST(i.last_user_scan AS NVARCHAR(128)),'?') + N'. ' @@ -29916,7 +30128,6 @@ BEGIN; FROM #IndexSanity i JOIN #IndexSanitySize iss ON i.index_sanity_id=iss.index_sanity_id WHERE ISNULL(i.user_scans,0) > 0 - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) ORDER BY i.user_scans * iss.total_reserved_MB DESC OPTION ( RECOMPILE ); @@ -29931,9 +30142,9 @@ BEGIN; i.index_sanity_id AS index_sanity_id, 200 AS Priority, N'Workaholics' AS findings_group, - N'Top recent accesses (index_op_stats)' AS finding, + N'Top Recent Accesses (index-op-stats)' AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/Workaholics' AS URL, + N'https://www.brentozar.com/go/Workaholics' AS URL, ISNULL(REPLACE( CONVERT(NVARCHAR(50),CAST((iss.total_range_scan_count + iss.total_singleton_lookup_count) AS MONEY),1), N'.00',N'') @@ -29948,84 +30159,10 @@ BEGIN; FROM #IndexSanity i JOIN #IndexSanitySize iss ON i.index_sanity_id=iss.index_sanity_id WHERE (ISNULL(iss.total_range_scan_count,0) > 0 OR ISNULL(iss.total_singleton_lookup_count,0) > 0) - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) ORDER BY ((iss.total_range_scan_count + iss.total_singleton_lookup_count) * iss.total_reserved_MB) DESC OPTION ( RECOMPILE ); - END; - - ---------------------------------------- - --Statistics Info: Check_id 90-99 - ---------------------------------------- - BEGIN - - RAISERROR(N'check_id 90: Outdated 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 ) - SELECT 90 AS check_id, - 200 AS Priority, - 'Functioning Statistaholics' AS findings_group, - 'Statistic Abandonment Issues', - s.database_name, - '' AS URL, - 'Statistics on this table were last updated ' + - CASE s.last_statistics_update WHEN NULL THEN N' NEVER ' - ELSE CONVERT(NVARCHAR(20), s.last_statistics_update) + - ' have had ' + CONVERT(NVARCHAR(100), s.modification_counter) + - ' modifications in that time, which is ' + - CONVERT(NVARCHAR(100), s.percent_modifications) + - '% of the table.' - END 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.last_statistics_update <= CONVERT(DATETIME, GETDATE() - 7) - AND s.percent_modifications >= 10. - AND s.rows >= 10000 - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 91: Statistics with a low 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 91 AS check_id, - 200 AS Priority, - 'Functioning Statistaholics' AS findings_group, - 'Antisocial Samples', - s.database_name, - '' AS URL, - 'Only ' + CONVERT(NVARCHAR(100), s.percent_sampled) + '% of the rows were sampled during the last statistics update. This may lead to poor cardinality estimates.' 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.rows_sampled < 1. - AND s.rows >= 10000 - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) - 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 ) - SELECT 92 AS check_id, - 200 AS Priority, - 'Functioning Statistaholics' AS findings_group, - 'Cyberphobic Samples', - s.database_name, - '' AS URL, - 'The statistic ' + QUOTENAME(s.statistics_name) + ' is set to not recompute. This can be helpful if data is really skewed, but harmful if you expect automatic statistics updates.' 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.no_recompute = 1 - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) - OPTION ( RECOMPILE ); RAISERROR(N'check_id 93: Statistics with filters', 0,1) WITH NOWAIT; INSERT #BlitzIndexResults ( check_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, @@ -30035,7 +30172,7 @@ BEGIN; 'Functioning Statistaholics' AS findings_group, 'Filter Fixation', s.database_name, - '' AS URL, + 'https://www.brentozar.com/go/stats' AS URL, 'The statistic ' + QUOTENAME(s.statistics_name) + ' is filtered on [' + s.filter_definition + ']. It could be part of a filtered index, or just a filtered statistic. This is purely informational.' 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, @@ -30043,34 +30180,8 @@ BEGIN; 'N/A' AS index_size_summary FROM #Statistics AS s WHERE s.has_filter = 1 - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) OPTION ( RECOMPILE ); - END; - - ---------------------------------------- - --Computed Column Info: Check_id 99-109 - ---------------------------------------- - BEGIN - - RAISERROR(N'check_id 99: Computed Columns 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 ) - SELECT 99 AS check_id, - 50 AS Priority, - 'Cold Calculators' AS findings_group, - 'Serial Forcer' AS finding, - cc.database_name, - '' AS URL, - 'The computed column ' + QUOTENAME(cc.column_name) + ' on ' + QUOTENAME(cc.schema_name) + '.' + QUOTENAME(cc.table_name) + ' is based on ' + cc.definition - + '. That indicates it may reference a scalar function, or a CLR function with data access, which can cause all queries and maintenance to run serially.' AS details, - cc.column_definition, - 'N/A' AS secret_columns, - 'N/A' AS index_usage_summary, - 'N/A' AS index_size_summary - FROM #ComputedColumns AS cc - WHERE cc.is_function = 1 - OPTION ( RECOMPILE ); RAISERROR(N'check_id 100: Computed Columns that are not Persisted.', 0,1) WITH NOWAIT; INSERT #BlitzIndexResults ( check_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, @@ -30090,20 +30201,16 @@ BEGIN; 'N/A' AS index_size_summary FROM #ComputedColumns AS cc WHERE cc.is_persisted = 0 - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) OPTION ( RECOMPILE ); - ---------------------------------------- - --Temporal Table Info: Check_id 110-119 - ---------------------------------------- RAISERROR(N'check_id 110: Temporal Tables.', 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 110 AS check_id, 200 AS Priority, - 'Temporal Tables' AS findings_group, - 'Obsessive Compulsive Tables', + 'Abnormal Psychology' AS findings_group, + 'Temporal Tables', t.database_name, '' AS URL, 'The table ' + QUOTENAME(t.schema_name) + '.' + QUOTENAME(t.table_name) + ' is a temporal table, with rows versioned in ' @@ -30114,33 +30221,39 @@ BEGIN; 'N/A' AS index_usage_summary, 'N/A' AS index_size_summary FROM #TemporalTables AS t - WHERE NOT (@GetAllDatabases = 1 OR @Mode = 0) OPTION ( RECOMPILE ); - ---------------------------------------- - --Check Constraint Info: Check_id 120-129 - ---------------------------------------- - RAISERROR(N'check_id 120: 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 ) - SELECT 99 AS check_id, - 50 AS Priority, - 'Obsessive Constraintive' AS findings_group, - 'Serial Forcer' AS finding, - cc.database_name, - 'https://www.brentozar.com/archive/2016/01/another-reason-why-scalar-functions-in-computed-columns-is-a-bad-idea/' AS URL, - 'The check constraint ' + QUOTENAME(cc.constraint_name) + ' on ' + QUOTENAME(cc.schema_name) + '.' + QUOTENAME(cc.table_name) + ' is based on ' + cc.definition - + '. That indicates it may reference a scalar function, or a CLR function with data access, which can cause all queries and maintenance to run serially.' AS details, - cc.column_definition, - 'N/A' AS secret_columns, - 'N/A' AS index_usage_summary, - 'N/A' AS index_size_summary - FROM #CheckConstraints AS cc - WHERE cc.is_function = 1 - OPTION ( RECOMPILE ); - END; + + END /* IF @Mode = 4 */ + + + + + + + + + + + + + + + + + + + + + + + + + + + RAISERROR(N'Insert a row to help people find help', 0,1) WITH NOWAIT; IF DATEDIFF(MM, @VersionDate, GETDATE()) > 6 @@ -30232,9 +30345,6 @@ BEGIN; br.index_sanity_id=sn.index_sanity_id LEFT JOIN #IndexCreateTsql ts ON br.index_sanity_id=ts.index_sanity_id - WHERE br.check_id IN ( 0, 1, 2, 11, 12, 13, - 22, 34, 43, 47, 48, - 50, 65, 68, 73, 99 ) ORDER BY br.Priority ASC, br.check_id ASC, br.blitz_result_id ASC, br.findings_group ASC OPTION (RECOMPILE); END; @@ -30264,8 +30374,9 @@ BEGIN; OPTION (RECOMPILE); END; - END; /* End @Mode=0 or 4 (diagnose)*/ - ELSE IF (@Mode=1) /*Summarize*/ +END /* End @Mode=0 or 4 (diagnose)*/ + +ELSE IF (@Mode=1) /*Summarize*/ BEGIN --This mode is to give some overall stats on the database. IF(@OutputType <> 'NONE') @@ -30826,23 +30937,46 @@ BEGIN; FROM #IndexSanity AS i --left join here so we don't lose disabled nc indexes LEFT JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id LEFT JOIN #IndexCreateTsql AS ict ON i.index_sanity_id = ict.index_sanity_id - ORDER BY CASE WHEN @SortOrder = N'rows' THEN sz.total_rows - WHEN @SortOrder = N'reserved_mb' THEN sz.total_reserved_MB - WHEN @SortOrder = N'size' THEN sz.total_reserved_MB - WHEN @SortOrder = N'reserved_lob_mb' THEN sz.total_reserved_LOB_MB - WHEN @SortOrder = N'lob' THEN sz.total_reserved_LOB_MB - WHEN @SortOrder = N'total_row_lock_wait_in_ms' THEN COALESCE(sz.total_row_lock_wait_in_ms,0) - WHEN @SortOrder = N'total_page_lock_wait_in_ms' THEN COALESCE(sz.total_page_lock_wait_in_ms,0) - WHEN @SortOrder = N'lock_time' THEN (COALESCE(sz.total_row_lock_wait_in_ms,0) + COALESCE(sz.total_page_lock_wait_in_ms,0)) - WHEN @SortOrder = N'total_reads' THEN total_reads - WHEN @SortOrder = N'reads' THEN total_reads - WHEN @SortOrder = N'user_updates' THEN user_updates - WHEN @SortOrder = N'writes' THEN user_updates - WHEN @SortOrder = N'reads_per_write' THEN reads_per_write - WHEN @SortOrder = N'ratio' THEN reads_per_write - WHEN @SortOrder = N'forward_fetches' THEN sz.total_forwarded_fetch_count - WHEN @SortOrder = N'fetches' THEN sz.total_forwarded_fetch_count - ELSE NULL END DESC, /* Shout out to DHutmacher */ + ORDER BY CASE WHEN @SortDirection = 'desc' THEN + CASE WHEN @SortOrder = N'rows' THEN sz.total_rows + WHEN @SortOrder = N'reserved_mb' THEN sz.total_reserved_MB + WHEN @SortOrder = N'size' THEN sz.total_reserved_MB + WHEN @SortOrder = N'reserved_lob_mb' THEN sz.total_reserved_LOB_MB + WHEN @SortOrder = N'lob' THEN sz.total_reserved_LOB_MB + WHEN @SortOrder = N'total_row_lock_wait_in_ms' THEN COALESCE(sz.total_row_lock_wait_in_ms,0) + WHEN @SortOrder = N'total_page_lock_wait_in_ms' THEN COALESCE(sz.total_page_lock_wait_in_ms,0) + WHEN @SortOrder = N'lock_time' THEN (COALESCE(sz.total_row_lock_wait_in_ms,0) + COALESCE(sz.total_page_lock_wait_in_ms,0)) + WHEN @SortOrder = N'total_reads' THEN total_reads + WHEN @SortOrder = N'reads' THEN total_reads + WHEN @SortOrder = N'user_updates' THEN user_updates + WHEN @SortOrder = N'writes' THEN user_updates + WHEN @SortOrder = N'reads_per_write' THEN reads_per_write + WHEN @SortOrder = N'ratio' THEN reads_per_write + WHEN @SortOrder = N'forward_fetches' THEN sz.total_forwarded_fetch_count + WHEN @SortOrder = N'fetches' THEN sz.total_forwarded_fetch_count + ELSE NULL END + ELSE 1 END + DESC, /* Shout out to DHutmacher */ + CASE WHEN @SortDirection = 'asc' THEN + CASE WHEN @SortOrder = N'rows' THEN sz.total_rows + WHEN @SortOrder = N'reserved_mb' THEN sz.total_reserved_MB + WHEN @SortOrder = N'size' THEN sz.total_reserved_MB + WHEN @SortOrder = N'reserved_lob_mb' THEN sz.total_reserved_LOB_MB + WHEN @SortOrder = N'lob' THEN sz.total_reserved_LOB_MB + WHEN @SortOrder = N'total_row_lock_wait_in_ms' THEN COALESCE(sz.total_row_lock_wait_in_ms,0) + WHEN @SortOrder = N'total_page_lock_wait_in_ms' THEN COALESCE(sz.total_page_lock_wait_in_ms,0) + WHEN @SortOrder = N'lock_time' THEN (COALESCE(sz.total_row_lock_wait_in_ms,0) + COALESCE(sz.total_page_lock_wait_in_ms,0)) + WHEN @SortOrder = N'total_reads' THEN total_reads + WHEN @SortOrder = N'reads' THEN total_reads + WHEN @SortOrder = N'user_updates' THEN user_updates + WHEN @SortOrder = N'writes' THEN user_updates + WHEN @SortOrder = N'reads_per_write' THEN reads_per_write + WHEN @SortOrder = N'ratio' THEN reads_per_write + WHEN @SortOrder = N'forward_fetches' THEN sz.total_forwarded_fetch_count + WHEN @SortOrder = N'fetches' THEN sz.total_forwarded_fetch_count + ELSE NULL END + ELSE 1 END + ASC, i.[database_name], [Schema Name], [Object Name], [Index ID] OPTION (RECOMPILE); END; @@ -30879,7 +31013,8 @@ BEGIN; mi.create_tsql AS [Create TSQL], mi.more_info AS [More Info], 1 AS [Display Order], - mi.is_low + mi.is_low, + mi.sample_query_plan AS [Sample Query Plan] FROM #MissingIndexes AS mi LEFT JOIN create_date AS cd ON mi.[object_id] = cd.object_id @@ -30896,7 +31031,7 @@ BEGIN; 100000000000, @DaysUptimeInsertValue, NULL,NULL,NULL,NULL,NULL,NULL,NULL, - NULL, NULL, NULL, NULL, 0 AS [Display Order], NULL AS is_low + NULL, NULL, NULL, NULL, 0 AS [Display Order], NULL AS is_low, NULL ORDER BY [Display Order] ASC, [Magic Benefit Number] DESC OPTION (RECOMPILE); END; @@ -30913,7 +31048,7 @@ BEGIN; END; /* End @Mode=3 (index detail)*/ -END; + END TRY BEGIN CATCH @@ -30966,7 +31101,7 @@ BEGIN SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '2.999', @VersionDate = '20201011'; +SELECT @Version = '2.9999', @VersionDate = '20201114'; IF(@VersionCheckMode = 1) @@ -31254,12 +31389,12 @@ You need to use an Azure storage account, and the path has to look like this: ht ORDER BY xml.deadlock_xml.value('(/event/@timestamp)[1]', 'datetime') DESC OPTION ( RECOMPILE ); - /*Parse process and input buffer XML*/ SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); RAISERROR('Parse process and input buffer XML %s', 0, 1, @d) WITH NOWAIT; SELECT q.event_date, q.victim_id, + CONVERT(BIT, q.is_parallel) AS is_parallel, q.deadlock_graph, q.id, q.database_id, @@ -31283,6 +31418,7 @@ You need to use an Azure storage account, and the path has to look like this: ht FROM ( SELECT dd.deadlock_xml, CONVERT(DATETIME2(7), SWITCHOFFSET(CONVERT(datetimeoffset, dd.event_date ), DATENAME(TzOffset, SYSDATETIMEOFFSET()))) AS event_date, dd.victim_id, + dd.is_parallel, dd.deadlock_graph, ca.dp.value('@id', 'NVARCHAR(256)') AS id, ca.dp.value('@currentdb', 'BIGINT') AS database_id, @@ -31304,6 +31440,7 @@ You need to use an Azure storage account, and the path has to look like this: ht FROM ( SELECT d1.deadlock_xml, d1.deadlock_xml.value('(event/@timestamp)[1]', 'DATETIME2') AS event_date, d1.deadlock_xml.value('(//deadlock/victim-list/victimProcess/@id)[1]', 'NVARCHAR(256)') AS victim_id, + d1.deadlock_xml.exist('//deadlock/resource-list/exchangeEvent') AS is_parallel, d1.deadlock_xml.query('/event/data/value/deadlock') AS deadlock_graph FROM #deadlock_data AS d1 ) AS dd CROSS APPLY dd.deadlock_xml.nodes('//deadlock/process-list/process') AS ca(dp) @@ -31527,13 +31664,13 @@ You need to use an Azure storage account, and the path has to look like this: ht ca.spilling, ca.waiting_to_close, w.l.value('@id', 'NVARCHAR(256)') AS waiter_id, - o.l.value('@id', 'NVARCHAR(256)') AS owner_id + o.l.value('@id', 'NVARCHAR(256)') AS owner_id INTO #deadlock_resource_parallel FROM ( SELECT dr.event_date, ca.dr.value('@id', 'NVARCHAR(256)') AS id, - ca.dr.value('@WaitType', 'NVARCHAR(256)') AS wait_type, - ca.dr.value('@nodeId', 'BIGINT') AS node_id, + ca.dr.value('@WaitType', 'NVARCHAR(256)') AS wait_type, + ca.dr.value('@nodeId', 'BIGINT') AS node_id, /* These columns are in 2017 CU5 ONLY */ ca.dr.value('@waiterType', 'NVARCHAR(256)') AS waiter_type, ca.dr.value('@ownerActivity', 'NVARCHAR(256)') AS owner_activity, @@ -32132,6 +32269,22 @@ You need to use an Azure storage account, and the path has to look like this: ht GROUP BY DB_NAME(aj.database_id), aj.job_name, aj.step_name OPTION ( RECOMPILE ); + /*Check 13 is total parallel deadlocks*/ + SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + RAISERROR('Check 13 %s', 0, 1, @d) WITH NOWAIT; + INSERT #deadlock_findings WITH (TABLOCKX) + ( check_id, database_name, object_name, finding_group, finding ) + SELECT 13 AS check_id, + N'-' AS database_name, + '-' AS object_name, + 'Total parallel deadlocks' AS finding_group, + 'There have been ' + + CONVERT(NVARCHAR(20), COUNT_BIG(DISTINCT drp.event_date)) + + ' parallel deadlocks.' + FROM #deadlock_resource_parallel AS drp + WHERE 1 = 1 + OPTION ( RECOMPILE ); + /*Thank you goodnight*/ INSERT #deadlock_findings WITH (TABLOCKX) ( check_id, database_name, object_name, finding_group, finding ) @@ -32208,6 +32361,7 @@ You need to use an Azure storage account, and the path has to look like this: ht dp.deadlock_graph FROM #deadlock_process AS dp WHERE dp.victim_id IS NOT NULL + AND dp.is_parallel = 0 UNION ALL @@ -32219,7 +32373,20 @@ You need to use an Azure storage account, and the path has to look like this: ht dp.priority, dp.log_used, dp.wait_resource COLLATE DATABASE_DEFAULT, - CONVERT(XML, N'parallel_deadlock' COLLATE DATABASE_DEFAULT) AS object_names, + CASE WHEN @ExportToExcel = 0 THEN + CONVERT( + XML, + STUFF(( SELECT DISTINCT NCHAR(10) + + N' ' + + ISNULL(c.object_name, N'') + + N' ' COLLATE DATABASE_DEFAULT AS object_name + FROM #deadlock_owner_waiter AS c + WHERE ( dp.id = c.owner_id + OR dp.victim_id = c.waiter_id ) + AND CONVERT(DATE, dp.event_date) = CONVERT(DATE, c.event_date) + FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(4000)'), + 1, 1, N'')) + ELSE NULL END AS object_names, dp.wait_time, dp.transaction_name, dp.last_tran_started, @@ -32254,8 +32421,7 @@ You need to use an Azure storage account, and the path has to look like this: ht FROM #deadlock_process AS dp CROSS APPLY (SELECT TOP 1 * FROM #deadlock_resource_parallel AS drp WHERE drp.owner_id = dp.id AND drp.wait_type = 'e_waitPipeNewRow' ORDER BY drp.event_date) AS cao CROSS APPLY (SELECT TOP 1 * FROM #deadlock_resource_parallel AS drp WHERE drp.owner_id = dp.id AND drp.wait_type = 'e_waitPipeGetRow' ORDER BY drp.event_date) AS caw - WHERE dp.victim_id IS NULL - AND dp.login_name IS NOT NULL) + WHERE dp.is_parallel = 1 ) insert into DeadLockTbl ( ServerName, deadlock_type, @@ -32422,7 +32588,8 @@ ELSE --Output to database is not set output to client app dp.deadlock_graph FROM #deadlock_process AS dp WHERE dp.victim_id IS NOT NULL - + AND dp.is_parallel = 0 + UNION ALL SELECT N'Parallel Deadlock' AS deadlock_type, @@ -32433,7 +32600,20 @@ ELSE --Output to database is not set output to client app dp.priority, dp.log_used, dp.wait_resource COLLATE DATABASE_DEFAULT, - CASE WHEN @ExportToExcel = 0 THEN CONVERT(XML, N'parallel_deadlock' COLLATE DATABASE_DEFAULT) ELSE 'parallel_deadlock' END AS object_names, + CASE WHEN @ExportToExcel = 0 THEN + CONVERT( + XML, + STUFF(( SELECT DISTINCT NCHAR(10) + + N' ' + + ISNULL(c.object_name, N'') + + N' ' COLLATE DATABASE_DEFAULT AS object_name + FROM #deadlock_owner_waiter AS c + WHERE ( dp.id = c.owner_id + OR dp.victim_id = c.waiter_id ) + AND CONVERT(DATE, dp.event_date) = CONVERT(DATE, c.event_date) + FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(4000)'), + 1, 1, N'')) + ELSE NULL END AS object_names, dp.wait_time, dp.transaction_name, dp.last_tran_started, @@ -32466,10 +32646,9 @@ ELSE --Output to database is not set output to client app caw.waiting_to_close AS waiter_waiting_to_close, dp.deadlock_graph FROM #deadlock_process AS dp - OUTER APPLY (SELECT TOP 1 * FROM #deadlock_resource_parallel AS drp WHERE drp.owner_id = dp.id AND drp.wait_type = 'e_waitPipeNewRow' ORDER BY drp.event_date) AS cao - OUTER APPLY (SELECT TOP 1 * FROM #deadlock_resource_parallel AS drp WHERE drp.owner_id = dp.id AND drp.wait_type = 'e_waitPipeGetRow' ORDER BY drp.event_date) AS caw - WHERE dp.victim_id IS NULL - AND dp.login_name IS NOT NULL + OUTER APPLY (SELECT TOP 1 * FROM #deadlock_resource_parallel AS drp WHERE drp.owner_id = dp.id AND drp.wait_type IN ('e_waitPortOpen', 'e_waitPipeNewRow') ORDER BY drp.event_date) AS cao + OUTER APPLY (SELECT TOP 1 * FROM #deadlock_resource_parallel AS drp WHERE drp.owner_id = dp.id AND drp.wait_type IN ('e_waitPortOpen', 'e_waitPipeGetRow') ORDER BY drp.event_date) AS caw + WHERE dp.is_parallel = 1 ) SELECT d.deadlock_type, d.event_date, @@ -32628,7 +32807,7 @@ BEGIN /*First BEGIN*/ SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '3.999', @VersionDate = '20201011'; +SELECT @Version = '3.9999', @VersionDate = '20201114'; IF(@VersionCheckMode = 1) BEGIN RETURN; @@ -38355,7 +38534,7 @@ BEGIN SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '7.999', @VersionDate = '20201011'; + SELECT @Version = '7.9999', @VersionDate = '20201114'; IF(@VersionCheckMode = 1) BEGIN @@ -38851,6 +39030,21 @@ IF @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @Output END + IF OBJECT_ID('tempdb..#WhoReadableDBs') IS NOT NULL + DROP TABLE #WhoReadableDBs; + +CREATE TABLE #WhoReadableDBs +( +database_id INT +); + +IF EXISTS (SELECT * FROM sys.all_objects o WHERE o.name = 'dm_hadr_database_replica_states') +BEGIN + RAISERROR('Checking for Read intent databases to exclude',0,0) WITH NOWAIT; + + EXEC('INSERT INTO #WhoReadableDBs (database_id) SELECT DBs.database_id FROM sys.databases DBs INNER JOIN sys.availability_replicas Replicas ON DBs.replica_id = Replicas.replica_id WHERE replica_server_name NOT IN (SELECT DISTINCT primary_replica FROM sys.dm_hadr_availability_group_states States) AND Replicas.secondary_role_allow_connections_desc = ''READ_ONLY'' AND replica_server_name = @@SERVERNAME;'); +END + SELECT @BlockingCheck = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; DECLARE @blocked TABLE @@ -38881,7 +39075,7 @@ BEGIN /* Think of the StringToExecute as starting with this, but we'll set this up later depending on whether we're doing an insert or a select: SELECT @StringToExecute = N'SELECT GETDATE() AS run_date , */ - SET @StringToExecute = N'COALESCE( CONVERT(VARCHAR(20), (ABS(r.total_elapsed_time) / 1000) / 86400) + '':'' + CONVERT(VARCHAR(20), (DATEADD(SECOND, (r.total_elapsed_time / 1000), 0) + DATEADD(MILLISECOND, (r.total_elapsed_time % 1000), 0)), 114), CONVERT(VARCHAR(20), DATEDIFF(SECOND, s.last_request_start_time, GETDATE()) / 86400) + '':'' + CONVERT(VARCHAR(20), DATEADD(SECOND, DATEDIFF(SECOND, s.last_request_start_time, GETDATE()), 0), 114) ) AS [elapsed_time] , + SET @StringToExecute = N'COALESCE( RIGHT(''00'' + CONVERT(VARCHAR(20), (ABS(r.total_elapsed_time) / 1000) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), (DATEADD(SECOND, (r.total_elapsed_time / 1000), 0) + DATEADD(MILLISECOND, (r.total_elapsed_time % 1000), 0)), 114), CONVERT(VARCHAR(20), DATEDIFF(SECOND, s.last_request_start_time, GETDATE()) / 86400) + '':'' + CONVERT(VARCHAR(20), DATEADD(SECOND, DATEDIFF(SECOND, s.last_request_start_time, GETDATE()), 0), 114) ) AS [elapsed_time] , s.session_id , COALESCE(DB_NAME(r.database_id), DB_NAME(blocked.dbid), ''N/A'') AS database_name, ISNULL(SUBSTRING(dest.text, @@ -39086,7 +39280,7 @@ IF @ProductVersionMajor >= 11 /* Think of the StringToExecute as starting with this, but we'll set this up later depending on whether we're doing an insert or a select: SELECT @StringToExecute = N'SELECT GETDATE() AS run_date , */ - SELECT @StringToExecute = N'COALESCE( CONVERT(VARCHAR(20), (ABS(r.total_elapsed_time) / 1000) / 86400) + '':'' + CONVERT(VARCHAR(20), (DATEADD(SECOND, (r.total_elapsed_time / 1000), 0) + DATEADD(MILLISECOND, (r.total_elapsed_time % 1000), 0)), 114), CONVERT(VARCHAR(20), DATEDIFF(SECOND, s.last_request_start_time, GETDATE()) / 86400) + '':'' + CONVERT(VARCHAR(20), DATEADD(SECOND, DATEDIFF(SECOND, s.last_request_start_time, GETDATE()), 0), 114) ) AS [elapsed_time] , + SELECT @StringToExecute = N'COALESCE( RIGHT(''00'' + CONVERT(VARCHAR(20), (ABS(r.total_elapsed_time) / 1000) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), (DATEADD(SECOND, (r.total_elapsed_time / 1000), 0) + DATEADD(MILLISECOND, (r.total_elapsed_time % 1000), 0)), 114), CONVERT(VARCHAR(20), DATEDIFF(SECOND, s.last_request_start_time, GETDATE()) / 86400) + '':'' + CONVERT(VARCHAR(20), DATEADD(SECOND, DATEDIFF(SECOND, s.last_request_start_time, GETDATE()), 0), 114) ) AS [elapsed_time] , s.session_id , COALESCE(DB_NAME(r.database_id), DB_NAME(blocked.dbid), ''N/A'') AS database_name, ISNULL(SUBSTRING(dest.text, @@ -39325,6 +39519,7 @@ IF @ProductVersionMajor >= 11 N' WHERE s.session_id <> @@SPID AND s.host_name IS NOT NULL + AND r.database_id NOT IN (SELECT database_id FROM #WhoReadableDBs) ' + CASE WHEN @ShowSleepingSPIDs = 0 THEN N' AND COALESCE(DB_NAME(r.database_id), DB_NAME(blocked.dbid)) IS NOT NULL' @@ -39562,6 +39757,7 @@ ALTER PROCEDURE [dbo].[sp_DatabaseRestore] @StopAt NVARCHAR(14) = NULL, @OnlyLogsAfter NVARCHAR(14) = NULL, @SimpleFolderEnumeration BIT = 0, + @SkipBackupsAlreadyInMsdb BIT = 0, @DatabaseOwner sysname = NULL, @Execute CHAR(1) = Y, @Debug INT = 0, @@ -39574,7 +39770,7 @@ SET NOCOUNT ON; /*Versioning details*/ -SELECT @Version = '7.999', @VersionDate = '20201011'; +SELECT @Version = '7.9999', @VersionDate = '20201114'; IF(@VersionCheckMode = 1) BEGIN @@ -39762,6 +39958,7 @@ DECLARE @cmd NVARCHAR(4000) = N'', --Holds xp_cmdshell command @LogRestoreRanking SMALLINT = 1, --Holds Log iteration # when multiple paths & backup files are being stripped @LogFirstLSN NUMERIC(25, 0), --Holds first LSN in log backup headers @LogLastLSN NUMERIC(25, 0), --Holds last LSN in log backup headers + @LogLastNameInMsdbAS NVARCHAR(MAX) = N'', -- Holds last TRN file name already restored @FileListParamSQL NVARCHAR(4000) = N'', --Holds INSERT list for #FileListParameters @BackupParameters NVARCHAR(500) = N'', --Used to save BlockSize, MaxTransferSize and BufferCount @RestoreDatabaseID SMALLINT; --Holds DB_ID of @RestoreDatabaseName @@ -40132,6 +40329,17 @@ BEGIN END; END /*End folder sanity check*/ + + IF @StopAt IS NOT NULL + BEGIN + DELETE + FROM @FileList + WHERE BackupFile LIKE N'%.bak' + AND + BackupFile LIKE N'%' + @Database + N'%' + AND + (REPLACE( RIGHT( REPLACE( BackupFile, RIGHT( BackupFile, PATINDEX( '%_[0-9][0-9]%', REVERSE( BackupFile ) ) ), '' ), 16 ), '_', '' ) > @StopAt); + END -- Find latest full backup SELECT @LastFullBackup = MAX(BackupFile) @@ -40708,6 +40916,24 @@ BEGIN END /*End folder sanity check*/ + +IF @SkipBackupsAlreadyInMsdb = 1 +BEGIN + SELECT TOP 1 @LogLastNameInMsdbAS = bf.physical_device_name + FROM msdb.dbo.backupmediafamily bf + WHERE physical_device_name like @BackupPathLog + '%' + ORDER BY physical_device_name DESC + + IF @Debug = 1 + BEGIN + SELECT 'Keeping LOG backups with name > : ' + @LogLastNameInMsdbAS + END + + DELETE fl + FROM @FileList AS fl + WHERE fl.BackupPath + fl.BackupFile <= @LogLastNameInMsdbAS +END + IF @Debug = 1 BEGIN SELECT * FROM @FileList WHERE BackupFile IS NOT NULL; @@ -41002,12 +41228,12 @@ ALTER PROCEDURE [dbo].[sp_ineachdb] @Version VARCHAR(30) = NULL OUTPUT, @VersionDate DATETIME = NULL OUTPUT, @VersionCheckMode BIT = 0 --- WITH EXECUTE AS OWNER – maybe not a great idea, depending on the security your system +-- WITH EXECUTE AS OWNER – maybe not a great idea, depending on the security of your system AS BEGIN SET NOCOUNT ON; - SELECT @Version = '2.999', @VersionDate = '20201011'; + SELECT @Version = '2.9999', @VersionDate = '20201114'; IF(@VersionCheckMode = 1) BEGIN @@ -41073,22 +41299,22 @@ BEGIN @cmd nvarchar(max), @thisdb sysname, @cr char(2) = CHAR(13) + CHAR(10), - @SQLVersion AS tinyint = (@@microsoftversion / 0x1000000) & 0xff, -- Stores the SQL Server Version Number(8(2000),9(2005),10(2008 & 2008R2),11(2012),12(2014),13(2016),14(2017) + @SQLVersion AS tinyint = (@@microsoftversion / 0x1000000) & 0xff, -- Stores the SQL Server Version Number(8(2000),9(2005),10(2008 & 2008R2),11(2012),12(2014),13(2016),14(2017),15(2019) @ServerName AS sysname = CONVERT(sysname, SERVERPROPERTY('ServerName')); -- Stores the SQL Server Instance name. CREATE TABLE #ineachdb(id int, name nvarchar(512), is_distributor bit); + -- first, let's limit to only DBs the caller is interested in IF @database_list > N'' -- comma-separated list of potentially valid/invalid/quoted/unquoted names BEGIN - ;WITH n(n) AS (SELECT 1 UNION ALL SELECT n+1 FROM n WHERE n < 4000), + ;WITH n(n) AS (SELECT 1 UNION ALL SELECT n+1 FROM n WHERE n <= LEN(@database_list)), names AS ( SELECT name = LTRIM(RTRIM(PARSENAME(SUBSTRING(@database_list, n, CHARINDEX(N',', @database_list + N',', n) - n), 1))) FROM n - WHERE n <= LEN(@database_list) - AND SUBSTRING(N',' + @database_list, n, 1) = N',' + WHERE SUBSTRING(N',' + @database_list, n, 1) = N',' ) INSERT #ineachdb(id,name,is_distributor) SELECT d.database_id, d.name, d.is_distributor @@ -41101,19 +41327,17 @@ BEGIN INSERT #ineachdb(id,name,is_distributor) SELECT database_id, name, is_distributor FROM sys.databases; END - -- first, let's delete any that have been explicitly excluded + -- now delete any that have been explicitly excluded - exclude trumps include IF @exclude_list > N'' -- comma-separated list of potentially valid/invalid/quoted/unquoted names - -- exclude trumps include BEGIN - ;WITH n(n) AS (SELECT 1 UNION ALL SELECT n+1 FROM n WHERE n < 4000), + ;WITH n(n) AS (SELECT 1 UNION ALL SELECT n+1 FROM n WHERE n <= LEN(@exclude_list)), names AS ( SELECT name = LTRIM(RTRIM(PARSENAME(SUBSTRING(@exclude_list, n, CHARINDEX(N',', @exclude_list + N',', n) - n), 1))) FROM n - WHERE n <= LEN(@exclude_list) - AND SUBSTRING(N',' + @exclude_list, n, 1) = N',' + WHERE SUBSTRING(N',' + @exclude_list, n, 1) = N',' ) DELETE d FROM #ineachdb AS d diff --git a/Install-Core-Blitz-No-Query-Store.sql b/Install-Core-Blitz-No-Query-Store.sql index f4fc53152..71c92605e 100755 --- a/Install-Core-Blitz-No-Query-Store.sql +++ b/Install-Core-Blitz-No-Query-Store.sql @@ -37,7 +37,7 @@ AS SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '7.999', @VersionDate = '20201011'; + SELECT @Version = '7.9999', @VersionDate = '20201114'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -1030,6 +1030,64 @@ AS -7, GETDATE()) ); END; + /* + CheckID #256 is searching for backups to NUL. + */ + + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 256 ) + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 2) WITH NOWAIT; + + INSERT INTO #BlitzResults + ( CheckID , + DatabaseName , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT DISTINCT + 256 AS CheckID , + d.name AS DatabaseName, + 1 AS Priority , + 'Backup' AS FindingsGroup , + 'Log Backups to NUL' AS Finding , + 'https://www.brentozar.com/go/nul' AS URL , + N'The transaction log file has been backed up ' + CAST((SELECT count(*) + FROM msdb.dbo.backupset AS b INNER JOIN + msdb.dbo.backupmediafamily AS bmf + ON b.media_set_id = bmf.media_set_id + WHERE b.database_name COLLATE SQL_Latin1_General_CP1_CI_AS = d.name COLLATE SQL_Latin1_General_CP1_CI_AS + AND bmf.physical_device_name = 'NUL' + AND b.type = 'L' + AND b.backup_finish_date >= DATEADD(dd, + -7, GETDATE())) AS NVARCHAR(8)) + ' time(s) to ''NUL'' in the last week, which means the backup does not exist. This breaks point-in-time recovery.' AS Details + FROM master.sys.databases AS d + WHERE d.recovery_model IN ( 1, 2 ) + AND d.database_id NOT IN ( 2, 3 ) + AND d.source_database_id IS NULL + AND d.state NOT IN(1, 6, 10) /* Not currently offline or restoring, like log shipping databases */ + AND d.is_in_standby = 0 /* Not a log shipping target database */ + AND d.source_database_id IS NULL /* Excludes database snapshots */ + --AND d.name NOT IN ( SELECT DISTINCT + -- DatabaseName + -- FROM #SkipChecks + -- WHERE CheckID IS NULL OR CheckID = 2) + AND EXISTS ( SELECT * + FROM msdb.dbo.backupset AS b INNER JOIN + msdb.dbo.backupmediafamily AS bmf + ON b.media_set_id = bmf.media_set_id + WHERE d.name COLLATE SQL_Latin1_General_CP1_CI_AS = b.database_name COLLATE SQL_Latin1_General_CP1_CI_AS + AND bmf.physical_device_name = 'NUL' + AND b.type = 'L' + AND b.backup_finish_date >= DATEADD(dd, + -7, GETDATE()) ); + END; + /* Next up, we've got CheckID 8. (These don't have to go in order.) This one won't work on SQL Server 2005 because it relies on a new DMV that didn't @@ -4396,7 +4454,8 @@ IF @ProductVersionMajor >= 10 [sys].[dm_server_services] WHERE [service_account] LIKE 'NT Service%' AND [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%'; END; END; @@ -9401,7 +9460,7 @@ AS SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '3.999', @VersionDate = '20201011'; + SELECT @Version = '3.9999', @VersionDate = '20201114'; IF(@VersionCheckMode = 1) BEGIN @@ -11180,7 +11239,7 @@ BEGIN SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '7.999', @VersionDate = '20201011'; +SELECT @Version = '7.9999', @VersionDate = '20201114'; IF(@VersionCheckMode = 1) @@ -11253,7 +11312,7 @@ BEGIN UNION ALL SELECT N'@SortOrder', N'VARCHAR(10)', - N'Data processing and display order. @SortOrder will still be used, even when preparing output for a table or for excel. Possible values are: "CPU", "Reads", "Writes", "Duration", "Executions", "Recent Compilations", "Memory Grant", "Spills", "Query Hash". Additionally, the word "Average" or "Avg" can be used to sort on averages rather than total. "Executions per minute" and "Executions / minute" can be used to sort by execution per minute. For the truly lazy, "xpm" can also be used. Note that when you use all or all avg, the only parameters you can use are @Top and @DatabaseName. All others will be ignored.' + N'Data processing and display order. @SortOrder will still be used, even when preparing output for a table or for excel. Possible values are: "CPU", "Reads", "Writes", "Duration", "Executions", "Recent Compilations", "Memory Grant", "Unused Grant", "Spills", "Query Hash". Additionally, the word "Average" or "Avg" can be used to sort on averages rather than total. "Executions per minute" and "Executions / minute" can be used to sort by execution per minute. For the truly lazy, "xpm" can also be used. Note that when you use all or all avg, the only parameters you can use are @Top and @DatabaseName. All others will be ignored.' UNION ALL SELECT N'@UseTriggersAnyway', @@ -11694,7 +11753,7 @@ IF @SortOrder LIKE 'query hash%' /* Set @Top based on sort */ IF ( @Top IS NULL - AND LOWER(@SortOrder) IN ( 'all', 'all sort' ) + AND @SortOrder IN ( 'all', 'all sort' ) ) BEGIN SET @Top = 5; @@ -11702,7 +11761,7 @@ IF ( IF ( @Top IS NULL - AND LOWER(@SortOrder) NOT IN ( 'all', 'all sort' ) + AND @SortOrder NOT IN ( 'all', 'all sort' ) ) BEGIN SET @Top = 10; @@ -12005,6 +12064,7 @@ SET @SortOrder = CASE WHEN @SortOrder IN ('avg write') THEN 'avg writes' WHEN @SortOrder IN ('memory grants') THEN 'memory grant' WHEN @SortOrder IN ('avg memory grants') THEN 'avg memory grant' + WHEN @SortOrder IN ('unused grants','unused memory', 'unused memory grant', 'unused memory grants') THEN 'unused grant' WHEN @SortOrder IN ('spill') THEN 'spills' WHEN @SortOrder IN ('avg spill') THEN 'avg spills' WHEN @SortOrder IN ('execution') THEN 'executions' @@ -12013,7 +12073,7 @@ SET @SortOrder = CASE RAISERROR(N'Checking sort order', 0, 1) WITH NOWAIT; IF @SortOrder NOT IN ('cpu', 'avg cpu', 'reads', 'avg reads', 'writes', 'avg writes', 'duration', 'avg duration', 'executions', 'avg executions', - 'compiles', 'memory grant', 'avg memory grant', + 'compiles', 'memory grant', 'avg memory grant', 'unused grant', 'spills', 'avg spills', 'all', 'all avg', 'sp_BlitzIndex', 'query hash') BEGIN @@ -12920,6 +12980,7 @@ SELECT @body += N' ORDER BY ' + WHEN N'executions' THEN N'execution_count' WHEN N'compiles' THEN N'cached_time' WHEN N'memory grant' THEN N'max_grant_kb' + WHEN N'unused grant' THEN N'max_grant_kb - max_used_grant_kb' WHEN N'spills' THEN N'max_spills' /* And now the averages */ WHEN N'avg cpu' THEN N'total_worker_time / execution_count' @@ -13236,6 +13297,7 @@ BEGIN WHEN N'executions' THEN N'AND execution_count > 0' WHEN N'compiles' THEN N'AND (age_minutes + age_minutes_lifetime) > 0' WHEN N'memory grant' THEN N'AND max_grant_kb > 0' + WHEN N'unused grant' THEN N'AND max_grant_kb > 0' WHEN N'spills' THEN N'AND max_spills > 0' /* And now the averages */ WHEN N'avg cpu' THEN N'AND (total_worker_time / execution_count) > 0' @@ -13267,7 +13329,7 @@ END; IF (@QueryFilter = 'all' AND (SELECT COUNT(*) FROM #only_query_hashes) = 0 AND (SELECT COUNT(*) FROM #ignore_query_hashes) = 0) - AND (@SortOrder NOT IN ('memory grant', 'avg memory grant')) + AND (@SortOrder NOT IN ('memory grant', 'avg memory grant', 'unused grant')) OR (LEFT(@QueryFilter, 3) = 'pro') BEGIN SET @sql += @insert_list; @@ -13288,7 +13350,7 @@ IF (@v >= 13 AND @QueryFilter = 'all' AND (SELECT COUNT(*) FROM #only_query_hashes) = 0 AND (SELECT COUNT(*) FROM #ignore_query_hashes) = 0) - AND (@SortOrder NOT IN ('memory grant', 'avg memory grant')) + AND (@SortOrder NOT IN ('memory grant', 'avg memory grant', 'unused grant')) AND (@SortOrder NOT IN ('spills', 'avg spills')) OR (LEFT(@QueryFilter, 3) = 'fun') BEGIN @@ -13331,7 +13393,7 @@ IF (@UseTriggersAnyway = 1 OR @v >= 11) AND (SELECT COUNT(*) FROM #only_query_hashes) = 0 AND (SELECT COUNT(*) FROM #ignore_query_hashes) = 0 AND (@QueryFilter = 'all') - AND (@SortOrder NOT IN ('memory grant', 'avg memory grant')) + AND (@SortOrder NOT IN ('memory grant', 'avg memory grant', 'unused grant')) BEGIN RAISERROR (N'Adding SQL to collect trigger stats.',0,1) WITH NOWAIT; @@ -13361,6 +13423,7 @@ SELECT @sort = CASE @SortOrder WHEN N'cpu' THEN N'total_worker_time' WHEN N'executions' THEN N'execution_count' WHEN N'compiles' THEN N'cached_time' WHEN N'memory grant' THEN N'max_grant_kb' + WHEN N'unused grant' THEN N'max_grant_kb - max_used_grant_kb' WHEN N'spills' THEN N'max_spills' /* And now the averages */ WHEN N'avg cpu' THEN N'total_worker_time / execution_count' @@ -13421,6 +13484,7 @@ SELECT @sort = CASE @SortOrder WHEN N'cpu' THEN N'TotalCPU' WHEN N'executions' THEN N'ExecutionCount' WHEN N'compiles' THEN N'PlanCreationTime' WHEN N'memory grant' THEN N'MaxGrantKB' + WHEN N'unused grant' THEN N'MaxGrantKB - MaxUsedGrantKB' WHEN N'spills' THEN N'MaxSpills' /* And now the averages */ WHEN N'avg cpu' THEN N'TotalCPU / ExecutionCount' @@ -13707,12 +13771,15 @@ SET NumberOfDistinctPlans = distinct_plan_count, FROM ( SELECT COUNT(DISTINCT QueryHash) AS distinct_plan_count, COUNT(QueryHash) AS number_of_plans, - QueryHash + QueryHash, + DatabaseName FROM ##BlitzCacheProcs WHERE SPID = @@SPID - GROUP BY QueryHash + GROUP BY QueryHash, + DatabaseName ) AS x WHERE ##BlitzCacheProcs.QueryHash = x.QueryHash +AND ##BlitzCacheProcs.DatabaseName = x.DatabaseName OPTION (RECOMPILE) ; -- query level checks @@ -14561,22 +14628,7 @@ END; /* END Testing using XML nodes to speed up processing */ -RAISERROR(N'Gathering additional plan level information', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE ##BlitzCacheProcs -SET NumberOfDistinctPlans = distinct_plan_count, - NumberOfPlans = number_of_plans, - plan_multiple_plans = CASE WHEN distinct_plan_count < number_of_plans THEN number_of_plans END -FROM ( -SELECT COUNT(DISTINCT QueryHash) AS distinct_plan_count, - COUNT(QueryHash) AS number_of_plans, - QueryHash -FROM ##BlitzCacheProcs -WHERE SPID = @@SPID -GROUP BY QueryHash -) AS x -WHERE ##BlitzCacheProcs.QueryHash = x.QueryHash -OPTION (RECOMPILE); + /* Update to grab stored procedure name for individual statements */ RAISERROR(N'Attempting to get stored procedure name for individual statements', 0, 1) WITH NOWAIT; @@ -15760,6 +15812,7 @@ BEGIN WHEN N'executions' THEN N' ExecutionCount ' WHEN N'compiles' THEN N' PlanCreationTime ' WHEN N'memory grant' THEN N' MaxGrantKB' + WHEN N'unused grant' THEN N' MaxGrantKB - MaxUsedGrantKB' WHEN N'spills' THEN N' MaxSpills' WHEN N'avg cpu' THEN N' AverageCPU' WHEN N'avg reads' THEN N' AverageReads' @@ -16009,6 +16062,7 @@ SELECT @sql += N' ORDER BY ' + CASE @SortOrder WHEN N'cpu' THEN N' TotalCPU ' WHEN N'executions' THEN N' ExecutionCount ' WHEN N'compiles' THEN N' PlanCreationTime ' WHEN N'memory grant' THEN N' MaxGrantKB' + WHEN N'unused grant' THEN N' MaxGrantKB - MaxUsedGrantKB ' WHEN N'spills' THEN N' MaxSpills' WHEN N'avg cpu' THEN N' AverageCPU' WHEN N'avg reads' THEN N' AverageReads' @@ -17385,7 +17439,7 @@ IF OBJECT_ID('tempdb.. #bou_allsort') IS NULL END; -IF LOWER(@SortOrder) = 'all' +IF @SortOrder = 'all' BEGIN RAISERROR('Beginning for ALL', 0, 1) WITH NOWAIT; SET @AllSortSql += N' @@ -17560,7 +17614,7 @@ SET @AllSortSql += N' END; -IF LOWER(@SortOrder) = 'all avg' +IF @SortOrder = 'all avg' BEGIN RAISERROR('Beginning for ALL AVG', 0, 1) WITH NOWAIT; SET @AllSortSql += N' @@ -17867,7 +17921,7 @@ ELSE PercentExecutionsByType money, ExecutionsPerMinute money, PlanCreationTime datetime,' + N' - PlanCreationTimeHours AS DATEDIFF(HOUR, PlanCreationTime, SYSDATETIME()), + PlanCreationTimeHours AS DATEDIFF(HOUR,CONVERT(DATETIMEOFFSET(7),[PlanCreationTime]),[CheckDate]), LastExecutionTime datetime, LastCompletionTime datetime, PlanHandle varbinary(64), @@ -17913,7 +17967,28 @@ ELSE AvgSpills MONEY, QueryPlanCost FLOAT, JoinKey AS ServerName + Cast(CheckDate AS NVARCHAR(50)), - CONSTRAINT [PK_' + REPLACE(REPLACE(@OutputTableName,'[',''),']','') + '] PRIMARY KEY CLUSTERED(ID ASC));'; + CONSTRAINT [PK_' + REPLACE(REPLACE(@OutputTableName,'[',''),']','') + '] PRIMARY KEY CLUSTERED(ID ASC)); + + IF EXISTS(SELECT * FROM ' + +@OutputDatabaseName + +N'.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' + +@OutputSchemaName + +''') AND EXISTS (SELECT * FROM ' + +@OutputDatabaseName+ + N'.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''' + +@OutputSchemaName + +''' AND QUOTENAME(TABLE_NAME) = ''' + +@OutputTableName + +''') AND EXISTS (SELECT * FROM ' + +@OutputDatabaseName+ + N'.sys.computed_columns WHERE [name] = N''PlanCreationTimeHours'' AND QUOTENAME(OBJECT_NAME(object_id)) = N''' + +@OutputTableName + +''' AND [definition] = N''(datediff(hour,[PlanCreationTime],sysdatetime()))'') +BEGIN + RAISERROR(''We noticed that you are running an old computed column definition for PlanCreationTimeHours, fixing that now'',0,0) WITH NOWAIT; + ALTER TABLE '+@OutputDatabaseName+'.'+@OutputSchemaName+'.'+@OutputTableName+' DROP COLUMN [PlanCreationTimeHours]; + ALTER TABLE '+@OutputDatabaseName+'.'+@OutputSchemaName+'.'+@OutputTableName+' ADD [PlanCreationTimeHours] AS DATEDIFF(HOUR,CONVERT(DATETIMEOFFSET(7),[PlanCreationTime]),[CheckDate]); +END '; IF @ValidOutputServer = 1 BEGIN @@ -18344,7 +18419,7 @@ BEGIN SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '7.999', @VersionDate = '20201011'; +SELECT @Version = '7.9999', @VersionDate = '20201114'; IF(@VersionCheckMode = 1) BEGIN @@ -19620,12 +19695,12 @@ BEGIN CASE @Seconds WHEN 0 THEN 0 ELSE SUM(os.waiting_tasks_count) OVER (PARTITION BY os.wait_type) END AS sum_waiting_tasks FROM sys.dm_os_wait_stats os ) x - WHERE EXISTS + WHERE NOT EXISTS ( - SELECT 1/0 + SELECT * FROM ##WaitCategories AS wc WHERE wc.WaitType = x.wait_type - AND wc.Ignorable = 0 + AND wc.Ignorable = 1 ) GROUP BY x.Pass, x.SampleTime, x.wait_type ORDER BY sum_wait_time_ms DESC; @@ -20575,12 +20650,12 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, SUM(os.waiting_tasks_count) OVER (PARTITION BY os.wait_type) AS sum_waiting_tasks FROM sys.dm_os_wait_stats os ) x - WHERE EXISTS + WHERE NOT EXISTS ( - SELECT 1/0 + SELECT * FROM ##WaitCategories AS wc WHERE wc.WaitType = x.wait_type - AND wc.Ignorable = 0 + AND wc.Ignorable = 1 ) GROUP BY x.Pass, x.SampleTime, x.wait_type ORDER BY sum_wait_time_ms DESC; @@ -21457,7 +21532,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, + @OutputSchemaName + '.' + @OutputTableName + ' (ServerName, CheckDate, CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, QueryText, StartTime, LoginName, NTUserName, OriginalLoginName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, DetailsInt, QueryHash) SELECT ' - + ' @SrvName, @CheckDate, CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, QueryText, StartTime, LoginName, NTUserName, OriginalLoginName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, DetailsInt, QueryHash FROM #BlitzFirstResults ORDER BY Priority , FindingsGroup , Finding , Details'; + + ' @SrvName, @CheckDate, CheckID, Priority, FindingsGroup, Finding, URL, LEFT(Details,4000), HowToStopIt, QueryPlan, QueryText, StartTime, LoginName, NTUserName, OriginalLoginName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, DetailsInt, QueryHash FROM #BlitzFirstResults ORDER BY Priority , FindingsGroup , Finding , Details'; EXEC sp_executesql @StringToExecute, N'@SrvName NVARCHAR(128), @CheckDate datetimeoffset', @@ -21512,7 +21587,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, + ' INSERT ' + @OutputTableName + ' (ServerName, CheckDate, CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, QueryText, StartTime, LoginName, NTUserName, OriginalLoginName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, DetailsInt) SELECT ' - + ' @SrvName, @CheckDate, CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, QueryText, StartTime, LoginName, NTUserName, OriginalLoginName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, DetailsInt FROM #BlitzFirstResults ORDER BY Priority , FindingsGroup , Finding , Details'; + + ' @SrvName, @CheckDate, CheckID, Priority, FindingsGroup, Finding, URL, LEFT(Details,4000), HowToStopIt, QueryPlan, QueryText, StartTime, LoginName, NTUserName, OriginalLoginName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, DetailsInt FROM #BlitzFirstResults ORDER BY Priority , FindingsGroup , Finding , Details'; EXEC sp_executesql @StringToExecute, N'@SrvName NVARCHAR(128), @CheckDate datetimeoffset', @@ -22675,6 +22750,7 @@ ALTER PROCEDURE dbo.sp_BlitzIndex @IncludeInactiveIndexes BIT = 0 /* Will skip indexes with no reads or writes */, @ShowAllMissingIndexRequests BIT = 0 /*Will make all missing index requests show up*/, @SortOrder NVARCHAR(50) = NULL, /* Only affects @Mode = 2. */ + @SortDirection NVARCHAR(4) = 'DESC', /* Only affects @Mode = 2. */ @Help TINYINT = 0, @Debug BIT = 0, @Version VARCHAR(30) = NULL OUTPUT, @@ -22685,7 +22761,7 @@ AS SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '7.999', @VersionDate = '20201011'; +SELECT @Version = '7.9999', @VersionDate = '20201114'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -22764,6 +22840,7 @@ DECLARE @ColumnList NVARCHAR(MAX); /* Let's get @SortOrder set to lower case here for comparisons later */ SET @SortOrder = REPLACE(LOWER(@SortOrder), N' ', N'_'); +SET @SortDirection = LOWER(@SortDirection); SET @LineFeed = CHAR(13) + CHAR(10); SELECT @SQLServerProductVersion = CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)); @@ -22958,25 +23035,31 @@ IF OBJECT_ID('tempdb..#Ignore_Databases') IS NOT NULL [reads_per_write] AS CAST(CASE WHEN user_updates > 0 THEN ( user_seeks + user_scans + user_lookups ) / (1.0 * user_updates) ELSE 0 END AS MONEY) , - [index_usage_summary] AS N'Reads: ' + - REPLACE(CONVERT(NVARCHAR(30),CAST((user_seeks + user_scans + user_lookups) AS MONEY), 1), N'.00', N'') - + CASE WHEN user_seeks + user_scans + user_lookups > 0 THEN - N' (' - + RTRIM( - CASE WHEN user_seeks > 0 THEN REPLACE(CONVERT(NVARCHAR(30),CAST((user_seeks) AS MONEY), 1), N'.00', N'') + N' seek ' ELSE N'' END - + CASE WHEN user_scans > 0 THEN REPLACE(CONVERT(NVARCHAR(30),CAST((user_scans) AS MONEY), 1), N'.00', N'') + N' scan ' ELSE N'' END - + CASE WHEN user_lookups > 0 THEN REPLACE(CONVERT(NVARCHAR(30),CAST((user_lookups) AS MONEY), 1), N'.00', N'') + N' lookup' ELSE N'' END - ) - + N') ' - ELSE N' ' END - + N'Writes:' + - REPLACE(CONVERT(NVARCHAR(30),CAST(user_updates AS MONEY), 1), N'.00', N''), - [more_info] AS - CASE WHEN is_in_memory_oltp = 1 - THEN N'EXEC dbo.sp_BlitzInMemoryOLTP @dbName=' + QUOTENAME([database_name],N'''') + - N', @tableName=' + QUOTENAME([object_name],N'''') + N';' - ELSE N'EXEC dbo.sp_BlitzIndex @DatabaseName=' + QUOTENAME([database_name],N'''') + - N', @SchemaName=' + QUOTENAME([schema_name],N'''') + N', @TableName=' + QUOTENAME([object_name],N'''') + N';' END + [index_usage_summary] AS + CASE WHEN is_spatial = 1 THEN N'Not Tracked' + WHEN is_disabled = 1 THEN N'Disabled' + ELSE N'Reads: ' + + REPLACE(CONVERT(NVARCHAR(30),CAST((user_seeks + user_scans + user_lookups) AS MONEY), 1), N'.00', N'') + + CASE WHEN user_seeks + user_scans + user_lookups > 0 THEN + N' (' + + RTRIM( + CASE WHEN user_seeks > 0 THEN REPLACE(CONVERT(NVARCHAR(30),CAST((user_seeks) AS MONEY), 1), N'.00', N'') + N' seek ' ELSE N'' END + + CASE WHEN user_scans > 0 THEN REPLACE(CONVERT(NVARCHAR(30),CAST((user_scans) AS MONEY), 1), N'.00', N'') + N' scan ' ELSE N'' END + + CASE WHEN user_lookups > 0 THEN REPLACE(CONVERT(NVARCHAR(30),CAST((user_lookups) AS MONEY), 1), N'.00', N'') + N' lookup' ELSE N'' END + ) + + N') ' + ELSE N' ' + END + + N'Writes: ' + + REPLACE(CONVERT(NVARCHAR(30),CAST(user_updates AS MONEY), 1), N'.00', N'') + END /* First "end" is about is_spatial */, + [more_info] AS + CASE WHEN is_in_memory_oltp = 1 + THEN N'EXEC dbo.sp_BlitzInMemoryOLTP @dbName=' + QUOTENAME([database_name],N'''') + + N', @tableName=' + QUOTENAME([object_name],N'''') + N';' + ELSE N'EXEC dbo.sp_BlitzIndex @DatabaseName=' + QUOTENAME([database_name],N'''') + + N', @SchemaName=' + QUOTENAME([schema_name],N'''') + N', @TableName=' + QUOTENAME([object_name],N'''') + N';' + END ); RAISERROR (N'Adding UQ index on #IndexSanity (database_id, object_id, index_id)',0,1) WITH NOWAIT; IF NOT EXISTS(SELECT 1 FROM tempdb.sys.indexes WHERE name='uq_database_id_object_id_index_id') @@ -23236,7 +23319,8 @@ IF OBJECT_ID('tempdb..#Ignore_Databases') IS NOT NULL + N';' , [more_info] AS N'EXEC dbo.sp_BlitzIndex @DatabaseName=' + QUOTENAME([database_name],'''') + - N', @SchemaName=' + QUOTENAME([schema_name],'''') + N', @TableName=' + QUOTENAME([table_name],'''') + N';' + N', @SchemaName=' + QUOTENAME([schema_name],'''') + N', @TableName=' + QUOTENAME([table_name],'''') + N';', + [sample_query_plan] XML NULL ); CREATE TABLE #ForeignKeys ( @@ -24268,8 +24352,24 @@ BEGIN TRY FROM ColumnNamesWithDataTypes WHERE ColumnNamesWithDataTypes.index_handle = id.index_handle AND ColumnNamesWithDataTypes.object_id = id.object_id AND ColumnNamesWithDataTypes.IndexColumnType = ''Included'' - ) AS included_columns_with_data_type - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_groups ig + ) AS included_columns_with_data_type ' + + IF NOT EXISTS (SELECT * FROM sys.all_objects WHERE name = 'dm_db_missing_index_group_stats_query') + SET @dsql = @dsql + N' , NULL AS sample_query_plan ' + ELSE + BEGIN + SET @dsql = @dsql + N' , sample_query_plan = (SELECT TOP 1 p.query_plan + FROM sys.dm_db_missing_index_group_stats gs + CROSS APPLY (SELECT TOP 1 s.plan_handle + FROM sys.dm_db_missing_index_group_stats_query q + INNER JOIN sys.dm_exec_query_stats s ON q.query_plan_hash = s.query_plan_hash + WHERE gs.group_handle = q.group_handle + ORDER BY (q.user_seeks + q.user_scans) DESC, s.total_logical_reads DESC) q2 + CROSS APPLY sys.dm_exec_query_plan(q2.plan_handle) p + WHERE gs.group_handle = gs.group_handle) ' + END + + SET @dsql = @dsql + N'FROM ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_groups ig JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_details id ON ig.index_handle = id.index_handle JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_group_stats gs ON ig.index_group_handle = gs.group_handle JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects so on @@ -24300,7 +24400,7 @@ BEGIN TRY INSERT #MissingIndexes ( [database_id], [object_id], [database_name], [schema_name], [table_name], [statement], avg_total_user_cost, avg_user_impact, user_seeks, user_scans, unique_compiles, equality_columns, inequality_columns, included_columns, equality_columns_with_data_type, inequality_columns_with_data_type, - included_columns_with_data_type) + included_columns_with_data_type, sample_query_plan) EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; SET @dsql = N' @@ -24370,14 +24470,18 @@ BEGIN TRY EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; - IF @SkipStatistics = 0 AND DB_NAME() = @DatabaseName /* Can only get stats in the current database - see https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/1947 */ + IF @SkipStatistics = 0 /* AND DB_NAME() = @DatabaseName /* Can only get stats in the current database - see https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/1947 */ */ BEGIN IF ((PARSENAME(@SQLServerProductVersion, 4) >= 12) OR (PARSENAME(@SQLServerProductVersion, 4) = 11 AND PARSENAME(@SQLServerProductVersion, 2) >= 3000) OR (PARSENAME(@SQLServerProductVersion, 4) = 10 AND PARSENAME(@SQLServerProductVersion, 3) = 50 AND PARSENAME(@SQLServerProductVersion, 2) >= 2500)) BEGIN RAISERROR (N'Gathering Statistics Info With Newer Syntax.',0,1) WITH NOWAIT; - SET @dsql=N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + SET @dsql=N'USE ' + @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, + 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) SELECT DB_ID(N' + QUOTENAME(@DatabaseName,'''') + N') AS [database_id], @i_DatabaseName AS database_name, obj.name AS table_name, @@ -24442,17 +24546,17 @@ BEGIN TRY PRINT SUBSTRING(@dsql, 32000, 36000); PRINT SUBSTRING(@dsql, 36000, 40000); END; - 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) EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; END; ELSE BEGIN RAISERROR (N'Gathering Statistics Info With Older Syntax.',0,1) WITH NOWAIT; - SET @dsql=N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + SET @dsql=N'USE ' + @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, 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) SELECT DB_ID(N' + QUOTENAME(@DatabaseName,'''') + N') AS [database_id], @i_DatabaseName AS database_name, obj.name AS table_name, @@ -24520,10 +24624,6 @@ BEGIN TRY PRINT SUBSTRING(@dsql, 32000, 36000); PRINT SUBSTRING(@dsql, 36000, 40000); END; - 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) EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; END; @@ -25212,7 +25312,7 @@ BEGIN GROUP BY i.database_id, i.schema_name, i.object_id ) SELECT N'Missing index.' AS Finding , - N'http://BrentOzar.com/go/Indexaphobia' AS URL , + N'https://www.brentozar.com/go/Indexaphobia' AS URL , mi.[statement] + ' Est. Benefit: ' + CASE WHEN magic_benefit_number >= 922337203685477 THEN '>= 922,337,203,685,477' @@ -25222,7 +25322,8 @@ BEGIN END AS [Estimated Benefit], missing_index_details AS [Missing Index Request] , index_estimated_impact AS [Estimated Impact], - create_tsql AS [Create TSQL] + create_tsql AS [Create TSQL], + sample_query_plan AS [Sample Query Plan] FROM #MissingIndexes mi LEFT JOIN create_date AS cd ON mi.[object_id] = cd.object_id @@ -25398,30 +25499,48 @@ BEGIN RAISERROR(N'Done visualizing columnstore index contents.', 0,1) WITH NOWAIT; END -END; +END; /* IF @TableName IS NOT NULL */ ---If @TableName is NOT specified... ---Act based on the @Mode and @Filter. (@Filter applies only when @Mode=0 "diagnose") -ELSE + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +ELSE IF @Mode IN (0, 4) /* DIAGNOSE*//* IF @TableName IS NOT NULL, so @TableName must not be null */ BEGIN; - IF @Mode IN (0, 4) /* DIAGNOSE*/ - BEGIN; - RAISERROR(N'@Mode=0 or 4, we are diagnosing.', 0,1) WITH NOWAIT; + IF @Mode IN (0, 4) /* DIAGNOSE priorities 1-100 */ + BEGIN; + RAISERROR(N'@Mode=0 or 4, running rules for priorities 1-100.', 0,1) WITH NOWAIT; ---------------------------------------- --Multiple Index Personalities: Check_id 0-10 ---------------------------------------- - BEGIN; - - --SELECT [object_id], key_column_names, database_id - -- FROM #IndexSanity - -- WHERE index_type IN (1,2) /* Clustered, NC only*/ - -- AND is_hypothetical = 0 - -- AND is_disabled = 0 - -- GROUP BY [object_id], key_column_names, database_id - -- HAVING COUNT(*) > 1 - - RAISERROR('check_id 1: Duplicate keys', 0,1) WITH NOWAIT; WITH duplicate_indexes AS ( SELECT [object_id], key_column_names, database_id, [schema_name] @@ -25448,11 +25567,11 @@ BEGIN; secret_columns, index_usage_summary, index_size_summary ) SELECT 1 AS check_id, ip.index_sanity_id, - 50 AS Priority, + 20 AS Priority, 'Multiple Index Personalities' AS findings_group, 'Duplicate keys' AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/duplicateindex' AS URL, + N'https://www.brentozar.com/go/duplicateindex' AS URL, N'Index Name: ' + ip.index_name + N' Table Name: ' + ip.db_schema_object_name AS details, ip.index_definition, ip.secret_columns, @@ -25485,11 +25604,11 @@ BEGIN; secret_columns, index_usage_summary, index_size_summary ) SELECT 2 AS check_id, ip.index_sanity_id, - 60 AS Priority, + 30 AS Priority, 'Multiple Index Personalities' AS findings_group, 'Borderline duplicate keys' AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/duplicateindex' AS URL, + N'https://www.brentozar.com/go/duplicateindex' AS URL, ip.db_schema_object_indexid AS details, ip.index_definition, ip.secret_columns, @@ -25510,18 +25629,16 @@ BEGIN; ORDER BY ip.[schema_name], ip.[object_name], ip.key_column_names, ip.include_column_names OPTION ( RECOMPILE ); - END; ---------------------------------------- --Aggressive Indexes: Check_id 10-19 ---------------------------------------- - BEGIN; RAISERROR(N'check_id 11: Total lock wait time > 5 minutes (row + page) with long average waits', 0,1) WITH NOWAIT; INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, secret_columns, index_usage_summary, index_size_summary ) SELECT 11 AS check_id, i.index_sanity_id, - 10 AS Priority, + 70 AS Priority, N'Aggressive ' + CASE COALESCE((SELECT SUM(1) FROM #IndexSanity iMe @@ -25547,7 +25664,7 @@ BEGIN; END AS findings_group, N'Total lock wait time > 5 minutes (row + page) with long average waits' AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/AggressiveIndexes' AS URL, + N'https://www.brentozar.com/go/AggressiveIndexes' AS URL, (i.db_schema_object_indexid + N': ' + sz.index_lock_wait_summary + N' NC indexes on table: ') COLLATE DATABASE_DEFAULT + CAST(COALESCE((SELECT SUM(1) @@ -25578,7 +25695,7 @@ BEGIN; secret_columns, index_usage_summary, index_size_summary ) SELECT 12 AS check_id, i.index_sanity_id, - 10 AS Priority, + 70 AS Priority, N'Aggressive ' + CASE COALESCE((SELECT SUM(1) FROM #IndexSanity iMe @@ -25604,7 +25721,7 @@ BEGIN; END AS findings_group, N'Total lock wait time > 5 minutes (row + page) with short average waits' AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/AggressiveIndexes' AS URL, + N'https://www.brentozar.com/go/AggressiveIndexes' AS URL, (i.db_schema_object_indexid + N': ' + sz.index_lock_wait_summary + N' NC indexes on table: ') COLLATE DATABASE_DEFAULT + CAST(COALESCE((SELECT SUM(1) @@ -25630,22 +25747,20 @@ BEGIN; ORDER BY 4, [database_name], 8 OPTION ( RECOMPILE ); - END; ---------------------------------------- --Index Hoarder: Check_id 20-29 ---------------------------------------- - BEGIN - RAISERROR(N'check_id 20: >=7 NC indexes on any given table. Yes, 7 is an arbitrary number.', 0,1) WITH NOWAIT; + RAISERROR(N'check_id 20: >= 10 NC indexes on any given table. Yes, 10 is an arbitrary number.', 0,1) WITH NOWAIT; INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, secret_columns, index_usage_summary, index_size_summary ) SELECT 20 AS check_id, MAX(i.index_sanity_id) AS index_sanity_id, - 100 AS Priority, + 10 AS Priority, 'Index Hoarder' AS findings_group, - 'Many NC indexes on a single table' AS finding, + 'Many NC Indexes on a Single Table' AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/IndexHoarder' AS URL, + N'https://www.brentozar.com/go/IndexHoarder' AS URL, CAST (COUNT(*) AS NVARCHAR(30)) + ' NC indexes on ' + i.db_schema_object_name AS details, i.db_schema_object_name + ' (' + CAST (COUNT(*) AS NVARCHAR(30)) + ' indexes)' AS index_definition, '' AS secret_columns, @@ -25662,88 +25777,20 @@ BEGIN; JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id WHERE index_id NOT IN ( 0, 1 ) GROUP BY db_schema_object_name, [i].[database_name] - HAVING COUNT(*) >= CASE WHEN (@GetAllDatabases = 1 OR @Mode = 0) - THEN 21 - ELSE 7 - END + HAVING COUNT(*) >= 10 ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); - IF @Filter = 1 /*@Filter=1 is "ignore unusued" */ - BEGIN - RAISERROR(N'Skipping checks on unused indexes (21 and 22) because @Filter=1', 0,1) WITH NOWAIT; - END; - ELSE /*Otherwise, go ahead and do the checks*/ - BEGIN - RAISERROR(N'check_id 21: >=5 percent of indexes are unused. Yes, 5 is an arbitrary number.', 0,1) WITH NOWAIT; - DECLARE @percent_NC_indexes_unused NUMERIC(29,1); - DECLARE @NC_indexes_unused_reserved_MB NUMERIC(29,1); - - SELECT @percent_NC_indexes_unused = ( 100.00 * SUM(CASE - WHEN total_reads = 0 - THEN 1 - ELSE 0 - END) ) / COUNT(*), - @NC_indexes_unused_reserved_MB = SUM(CASE - WHEN total_reads = 0 - THEN sz.total_reserved_MB - ELSE 0 - END) - FROM #IndexSanity i - 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()) - 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, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 21 AS check_id, - MAX(i.index_sanity_id) AS index_sanity_id, - 150 AS Priority, - N'Index Hoarder' AS findings_group, - N'More than 5 percent NC indexes are unused' AS finding, - [database_name] AS [Database Name], - N'http://BrentOzar.com/go/IndexHoarder' AS URL, - CAST (@percent_NC_indexes_unused AS NVARCHAR(30)) + N' percent NC indexes (' + CAST(COUNT(*) AS NVARCHAR(10)) + N') unused. ' + - N'These take up ' + CAST (@NC_indexes_unused_reserved_MB AS NVARCHAR(30)) + N'MB of space.' AS details, - i.database_name + ' (' + CAST (COUNT(*) AS NVARCHAR(30)) + N' indexes)' AS index_definition, - '' AS secret_columns, - CAST(SUM(total_reads) AS NVARCHAR(256)) + N' reads (ALL); ' - + CAST(SUM([user_updates]) AS NVARCHAR(256)) + N' writes (ALL)' AS index_usage_summary, - - REPLACE(CONVERT(NVARCHAR(30),CAST(MAX([total_rows]) AS MONEY), 1), '.00', '') + N' rows (MAX)' - + CASE WHEN SUM(total_reserved_MB) > 1024 THEN - N'; ' + CAST(CAST(SUM(total_reserved_MB)/1024. AS NUMERIC(29,1)) AS NVARCHAR(30)) + 'GB (ALL)' - WHEN SUM(total_reserved_MB) > 0 THEN - N'; ' + CAST(CAST(SUM(total_reserved_MB) AS NUMERIC(29,1)) AS NVARCHAR(30)) + 'MB (ALL)' - ELSE '' - END AS index_size_summary - FROM #IndexSanity i - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE index_id NOT IN ( 0, 1 ) - AND i.is_unique = 0 - AND total_reads = 0 - AND NOT (@GetAllDatabases = 1 OR @Mode = 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()) - GROUP BY i.database_name - OPTION ( RECOMPILE ); - RAISERROR(N'check_id 22: NC indexes with 0 reads. (Borderline) and >= 10,000 writes', 0,1) WITH NOWAIT; INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, secret_columns, index_usage_summary, index_size_summary ) SELECT 22 AS check_id, i.index_sanity_id, - 100 AS Priority, + 10 AS Priority, N'Index Hoarder' AS findings_group, - N'Unused NC index with High Writes' AS finding, + N'Unused NC Index with High Writes' AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/IndexHoarder' AS URL, + N'https://www.brentozar.com/go/IndexHoarder' AS URL, N'Reads: 0,' + N' Writes: ' + REPLACE(CONVERT(NVARCHAR(30), CAST((i.user_updates) AS MONEY), 1), N'.00', N'') @@ -25761,88 +25808,757 @@ 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 + AND @Filter <> 1 /* 1 = "ignore unused */ ORDER BY i.db_schema_object_indexid OPTION ( RECOMPILE ); - END; /*end checks only run when @Filter <> 1*/ - RAISERROR(N'check_id 23: Indexes with 7 or more columns. (Borderline)', 0,1) WITH NOWAIT; + + RAISERROR(N'check_id 34: Filtered index definition columns not in index definition', 0,1) WITH NOWAIT; + + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 34 AS check_id, + i.index_sanity_id, + 80 AS Priority, + N'Abnormal Psychology' AS findings_group, + N'Filter Columns Not In Index Definition' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/IndexFeatures' AS URL, + N'The index ' + + QUOTENAME(i.index_name) + + N' on [' + + i.db_schema_object_name + + N'] has a filter on [' + + i.filter_definition + + N'] but is missing [' + + LTRIM(i.filter_columns_not_in_index) + + N'] from the index definition.' + AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + sz.index_size_summary + FROM #IndexSanity i + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + WHERE i.filter_columns_not_in_index IS NOT NULL + ORDER BY i.db_schema_object_indexid + OPTION ( RECOMPILE ); + + ---------------------------------------- + --Self Loathing Indexes : Check_id 40-49 + ---------------------------------------- + + RAISERROR(N'check_id 40: Fillfactor in nonclustered 80 percent or less', 0,1) WITH NOWAIT; INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, secret_columns, index_usage_summary, index_size_summary ) - SELECT 23 AS check_id, + SELECT 40 AS check_id, i.index_sanity_id, - 150 AS Priority, - N'Index Hoarder' AS findings_group, - N'Borderline: Wide indexes (7 or more columns)' AS finding, + 100 AS Priority, + N'Self Loathing Indexes' AS findings_group, + N'Low Fill Factor on Nonclustered Index' AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/IndexHoarder' AS URL, - CAST(count_key_columns + count_included_columns AS NVARCHAR(10)) + ' columns on ' - + i.db_schema_object_indexid AS details, i.index_definition, - i.secret_columns, + N'https://www.brentozar.com/go/SelfLoathing' AS URL, + CAST(fill_factor AS NVARCHAR(10)) + N'% fill factor on ' + db_schema_object_indexid + N'. '+ + CASE WHEN (last_user_update IS NULL OR user_updates < 1) + THEN N'No writes have been made.' + ELSE + N'Last write was ' + CONVERT(NVARCHAR(16),last_user_update,121) + N' and ' + + CAST(user_updates AS NVARCHAR(25)) + N' updates have been made.' + END + AS details, + i.index_definition, + i.secret_columns, i.index_usage_summary, sz.index_size_summary FROM #IndexSanity AS i JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id - WHERE ( count_key_columns + count_included_columns ) >= 7 - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) - OPTION ( RECOMPILE ); + WHERE index_id > 1 + AND fill_factor BETWEEN 1 AND 80 OPTION ( RECOMPILE ); - RAISERROR(N'check_id 24: Wide clustered indexes (> 3 columns or > 16 bytes).', 0,1) WITH NOWAIT; - WITH count_columns AS ( - SELECT database_id, [object_id], - SUM(CASE max_length WHEN -1 THEN 0 ELSE max_length END) AS sum_max_length - FROM #IndexColumns ic - WHERE index_id IN (1,0) /*Heap or clustered only*/ - AND key_ordinal > 0 - GROUP BY database_id, object_id - ) + RAISERROR(N'check_id 40: Fillfactor in clustered 80 percent or less', 0,1) WITH NOWAIT; INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, secret_columns, index_usage_summary, index_size_summary ) - SELECT 24 AS check_id, - i.index_sanity_id, - 150 AS Priority, - N'Index Hoarder' AS findings_group, - N'Wide clustered index (> 3 columns OR > 16 bytes)' AS finding, - [database_name] AS [Database Name], - N'http://BrentOzar.com/go/IndexHoarder' AS URL, - CAST (i.count_key_columns AS NVARCHAR(10)) + N' columns with potential size of ' - + CAST(cc.sum_max_length AS NVARCHAR(10)) - + N' bytes in clustered index:' + i.db_schema_object_name - + N'. ' + - (SELECT CAST(COUNT(*) AS NVARCHAR(23)) - FROM #IndexSanity i2 - WHERE i2.[object_id]=i.[object_id] - AND i2.database_id = i.database_id - AND i2.index_id <> 1 - AND i2.is_disabled=0 - AND i2.is_hypothetical=0) - + N' NC indexes on the table.' - AS details, - i.index_definition, - secret_columns, - i.index_usage_summary, - ip.index_size_summary - FROM #IndexSanity i - JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id - JOIN count_columns AS cc ON i.[object_id]=cc.[object_id] - AND i.database_id = cc.database_id - WHERE index_id = 1 /* clustered only */ - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) - AND - (count_key_columns > 3 /*More than three key columns.*/ - OR cc.sum_max_length > 16 /*More than 16 bytes in key */) - AND i.is_CX_columnstore = 0 - ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); + SELECT 40 AS check_id, + i.index_sanity_id, + 100 AS Priority, + N'Self Loathing Indexes' AS findings_group, + N'Low Fill Factor on Clustered Index' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/SelfLoathing' AS URL, + N'Fill factor on ' + db_schema_object_indexid + N' is ' + CAST(fill_factor AS NVARCHAR(10)) + N'%. '+ + CASE WHEN (last_user_update IS NULL OR user_updates < 1) + THEN N'No writes have been made.' + ELSE + N'Last write was ' + CONVERT(NVARCHAR(16),last_user_update,121) + N' and ' + + CAST(user_updates AS NVARCHAR(25)) + N' updates have been made.' + END + AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + sz.index_size_summary + FROM #IndexSanity AS i + JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id + WHERE index_id = 1 + AND fill_factor BETWEEN 1 AND 80 OPTION ( RECOMPILE ); - RAISERROR(N'check_id 25: Addicted to nullable columns.', 0,1) WITH NOWAIT; - WITH count_columns AS ( - SELECT [object_id], - [database_id], - [schema_name], - SUM(CASE is_nullable WHEN 1 THEN 0 ELSE 1 END) AS non_nullable_columns, - COUNT(*) AS total_columns - FROM #IndexColumns ic - WHERE index_id IN (1,0) /*Heap or clustered only*/ - GROUP BY [object_id], + + RAISERROR(N'check_id 43: Heaps with forwarded records', 0,1) WITH NOWAIT; + WITH heaps_cte + AS ( SELECT [object_id], + [database_id], + [schema_name], + SUM(forwarded_fetch_count) AS forwarded_fetch_count, + SUM(leaf_delete_count) AS leaf_delete_count + FROM #IndexPartitionSanity + GROUP BY [object_id], + [database_id], + [schema_name] + HAVING SUM(forwarded_fetch_count) > 0) + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 43 AS check_id, + i.index_sanity_id, + 100 AS Priority, + N'Self Loathing Indexes' AS findings_group, + N'Heaps with Forwarded Fetches' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/SelfLoathing' AS URL, + CASE WHEN h.forwarded_fetch_count >= 922337203685477 THEN '>= 922,337,203,685,477' + WHEN @DaysUptime < 1 THEN CAST(h.forwarded_fetch_count AS NVARCHAR(256)) + N' forwarded fetches against heap: ' + db_schema_object_indexid + ELSE REPLACE(CONVERT(NVARCHAR(256),CAST(CAST( + (h.forwarded_fetch_count /*/@DaysUptime */) + AS BIGINT) AS MONEY), 1), '.00', '') + END + N' forwarded fetches per day against heap: ' + + db_schema_object_indexid AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + sz.index_size_summary + FROM #IndexSanity i + JOIN heaps_cte h ON i.[object_id] = h.[object_id] + AND i.[database_id] = h.[database_id] + AND i.[schema_name] = h.[schema_name] + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + WHERE i.index_id = 0 + AND h.forwarded_fetch_count / @DaysUptime > 1000 + AND sz.total_reserved_MB >= CASE WHEN NOT (@GetAllDatabases = 1 OR @Mode = 4) THEN @ThresholdMB ELSE sz.total_reserved_MB END + OPTION ( RECOMPILE ); + + RAISERROR(N'check_id 44: Large Heaps with reads or writes.', 0,1) WITH NOWAIT; + WITH heaps_cte + AS ( SELECT [object_id], + [database_id], + [schema_name], + SUM(forwarded_fetch_count) AS forwarded_fetch_count, + SUM(leaf_delete_count) AS leaf_delete_count + FROM #IndexPartitionSanity + GROUP BY [object_id], + [database_id], + [schema_name] + HAVING SUM(forwarded_fetch_count) > 0 + OR SUM(leaf_delete_count) > 0) + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 44 AS check_id, + i.index_sanity_id, + 100 AS Priority, + N'Self Loathing Indexes' AS findings_group, + N'Large Active Heap' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/SelfLoathing' AS URL, + N'Should this table be a heap? ' + db_schema_object_indexid AS details, + i.index_definition, + 'N/A' AS secret_columns, + i.index_usage_summary, + sz.index_size_summary + FROM #IndexSanity i + LEFT JOIN heaps_cte h ON i.[object_id] = h.[object_id] + AND i.[database_id] = h.[database_id] + AND i.[schema_name] = h.[schema_name] + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + WHERE i.index_id = 0 + AND (i.total_reads > 0 OR i.user_updates > 0) + AND sz.total_rows >= 100000 + AND h.[object_id] IS NULL /*don't duplicate the prior check.*/ + OPTION ( RECOMPILE ); + + RAISERROR(N'check_id 45: Medium Heaps with reads or writes.', 0,1) WITH NOWAIT; + WITH heaps_cte + AS ( SELECT [object_id], + [database_id], + [schema_name], + SUM(forwarded_fetch_count) AS forwarded_fetch_count, + SUM(leaf_delete_count) AS leaf_delete_count + FROM #IndexPartitionSanity + GROUP BY [object_id], + [database_id], + [schema_name] + HAVING SUM(forwarded_fetch_count) > 0 + OR SUM(leaf_delete_count) > 0) + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 45 AS check_id, + i.index_sanity_id, + 100 AS Priority, + N'Self Loathing Indexes' AS findings_group, + N'Medium Active heap' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/SelfLoathing' AS URL, + N'Should this table be a heap? ' + db_schema_object_indexid AS details, + i.index_definition, + 'N/A' AS secret_columns, + i.index_usage_summary, + sz.index_size_summary + FROM #IndexSanity i + LEFT JOIN heaps_cte h ON i.[object_id] = h.[object_id] + AND i.[database_id] = h.[database_id] + AND i.[schema_name] = h.[schema_name] + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + WHERE i.index_id = 0 + AND + (i.total_reads > 0 OR i.user_updates > 0) + AND sz.total_rows >= 10000 AND sz.total_rows < 100000 + AND h.[object_id] IS NULL /*don't duplicate the prior check.*/ + OPTION ( RECOMPILE ); + + RAISERROR(N'check_id 46: Small Heaps with reads or writes.', 0,1) WITH NOWAIT; + WITH heaps_cte + AS ( SELECT [object_id], + [database_id], + [schema_name], + SUM(forwarded_fetch_count) AS forwarded_fetch_count, + SUM(leaf_delete_count) AS leaf_delete_count + FROM #IndexPartitionSanity + GROUP BY [object_id], + [database_id], + [schema_name] + HAVING SUM(forwarded_fetch_count) > 0 + OR SUM(leaf_delete_count) > 0) + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 46 AS check_id, + i.index_sanity_id, + 100 AS Priority, + N'Self Loathing Indexes' AS findings_group, + N'Small Active heap' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/SelfLoathing' AS URL, + N'Should this table be a heap? ' + db_schema_object_indexid AS details, + i.index_definition, + 'N/A' AS secret_columns, + i.index_usage_summary, + sz.index_size_summary + FROM #IndexSanity i + LEFT JOIN heaps_cte h ON i.[object_id] = h.[object_id] + AND i.[database_id] = h.[database_id] + AND i.[schema_name] = h.[schema_name] + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + WHERE i.index_id = 0 + AND + (i.total_reads > 0 OR i.user_updates > 0) + AND sz.total_rows < 10000 + AND h.[object_id] IS NULL /*don't duplicate the prior check.*/ + OPTION ( RECOMPILE ); + + RAISERROR(N'check_id 47: Heap with a Nonclustered Primary Key', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 47 AS check_id, + i.index_sanity_id, + 100 AS Priority, + N'Self Loathing Indexes' AS findings_group, + N'Heap with a Nonclustered Primary Key' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/SelfLoathing' AS URL, + db_schema_object_indexid + N' is a HEAP with a Nonclustered Primary Key' AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + sz.index_size_summary + FROM #IndexSanity i + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + WHERE i.index_type = 2 AND i.is_primary_key = 1 + AND EXISTS + ( + SELECT 1/0 + FROM #IndexSanity AS isa + WHERE i.database_id = isa.database_id + AND i.object_id = isa.object_id + AND isa.index_id = 0 + ) + OPTION ( RECOMPILE ); + + RAISERROR(N'check_id 48: Nonclustered indexes with a bad read to write ratio', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 48 AS check_id, + i.index_sanity_id, + 100 AS Priority, + N'Index Hoarder' AS findings_group, + N'NC index with High Writes:Reads' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/IndexHoarder' AS URL, + N'Reads: ' + + REPLACE(CONVERT(NVARCHAR(30), CAST((i.total_reads) AS MONEY), 1), N'.00', N'') + + N' Writes: ' + + REPLACE(CONVERT(NVARCHAR(30), CAST((i.user_updates) AS MONEY), 1), N'.00', N'') + + N' on: ' + + i.db_schema_object_indexid AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + sz.index_size_summary + FROM #IndexSanity i + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + WHERE i.total_reads > 0 /*Not totally unused*/ + AND i.user_updates >= 10000 /*Decent write activity*/ + AND i.total_reads < 10000 + AND ((i.total_reads * 10) < i.user_updates) /*10x more writes than reads*/ + 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 + ORDER BY i.db_schema_object_indexid + OPTION ( RECOMPILE ); + + ---------------------------------------- + --Indexaphobia + --Missing indexes with value >= 5 million: : Check_id 50-59 + ---------------------------------------- + RAISERROR(N'check_id 50: Indexaphobia.', 0,1) WITH NOWAIT; + WITH index_size_cte + AS ( SELECT i.database_id, + i.schema_name, + i.[object_id], + MAX(i.index_sanity_id) AS index_sanity_id, + ISNULL(NULLIF(MAX(DATEDIFF(DAY, i.create_date, SYSDATETIME())), 0), 1) AS create_days, + ISNULL ( + CAST(SUM(CASE WHEN index_id NOT IN (0,1) THEN 1 ELSE 0 END) + AS NVARCHAR(30))+ N' NC indexes exist (' + + CASE WHEN SUM(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) > 1024 + THEN CAST(CAST(SUM(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END )/1024. + + AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'GB); ' + ELSE CAST(SUM(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) + AS NVARCHAR(30)) + N'MB); ' + END + + CASE WHEN MAX(sz.[total_rows]) >= 922337203685477 THEN '>= 922,337,203,685,477' + ELSE REPLACE(CONVERT(NVARCHAR(30),CAST(MAX(sz.[total_rows]) AS MONEY), 1), '.00', '') + END + + + N' Estimated Rows;' + ,N'') AS index_size_summary + FROM #IndexSanity AS i + LEFT JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id AND i.database_id = sz.database_id + WHERE i.is_hypothetical = 0 + AND i.is_disabled = 0 + GROUP BY i.database_id, i.schema_name, i.[object_id]) + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + index_usage_summary, index_size_summary, create_tsql, more_info ) + + SELECT check_id, t.index_sanity_id, t.check_id, t.findings_group, t.finding, t.[Database Name], t.URL, t.details, t.[definition], + index_estimated_impact, t.index_size_summary, create_tsql, more_info + FROM + ( + SELECT ROW_NUMBER() OVER (ORDER BY magic_benefit_number DESC) AS rownum, + 50 AS check_id, + sz.index_sanity_id, + 40 AS Priority, + N'Indexaphobia' AS findings_group, + N'High Value Missing Index' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/Indexaphobia' AS URL, + mi.[statement] + + N' Est. benefit per day: ' + + CASE WHEN magic_benefit_number >= 922337203685477 THEN '>= 922,337,203,685,477' + ELSE REPLACE(CONVERT(NVARCHAR(256),CAST(CAST( + (magic_benefit_number/@DaysUptime) + AS BIGINT) AS MONEY), 1), '.00', '') + END AS details, + missing_index_details AS [definition], + index_estimated_impact, + sz.index_size_summary, + mi.create_tsql, + mi.more_info, + magic_benefit_number, + mi.is_low + FROM #MissingIndexes mi + LEFT JOIN index_size_cte sz ON mi.[object_id] = sz.object_id + AND mi.database_id = sz.database_id + AND mi.schema_name = sz.schema_name + /* Minimum benefit threshold = 100k/day of uptime OR since table creation date, whichever is lower*/ + WHERE @ShowAllMissingIndexRequests=1 + OR ( @Mode = 4 AND (magic_benefit_number / CASE WHEN sz.create_days < @DaysUptime THEN sz.create_days ELSE @DaysUptime END) >= 100000 ) + OR (magic_benefit_number / CASE WHEN sz.create_days < @DaysUptime THEN sz.create_days ELSE @DaysUptime END) >= 100000 + ) AS t + WHERE t.rownum <= CASE WHEN (@Mode <> 4) THEN 20 ELSE t.rownum END + ORDER BY magic_benefit_number DESC + OPTION ( RECOMPILE ); + + + + RAISERROR(N'check_id 68: Identity columns within 30 percent of the end of range', 0,1) WITH NOWAIT; + -- Allowed Ranges: + --int -2,147,483,648 to 2,147,483,647 + --smallint -32,768 to 32,768 + --tinyint 0 to 255 + + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 68 AS check_id, + i.index_sanity_id, + 80 AS Priority, + N'Abnormal Psychology' AS findings_group, + N'Identity Column Within ' + + CAST (calc1.percent_remaining AS NVARCHAR(256)) + + N' Percent End of Range' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, + i.db_schema_object_name + N'.' + QUOTENAME(ic.column_name) + + N' is an identity with type ' + ic.system_type_name + + N', last value of ' + + ISNULL((CONVERT(NVARCHAR(256),CAST(ic.last_value AS DECIMAL(38,0)), 1)),N'NULL') + + N', seed of ' + + ISNULL((CONVERT(NVARCHAR(256),CAST(ic.seed_value AS DECIMAL(38,0)), 1)),N'NULL') + + N', increment of ' + CAST(ic.increment_value AS NVARCHAR(256)) + + N', and range of ' + + CASE ic.system_type_name WHEN 'int' THEN N'+/- 2,147,483,647' + WHEN 'smallint' THEN N'+/- 32,768' + WHEN 'tinyint' THEN N'0 to 255' + ELSE 'unknown' + END + AS details, + i.index_definition, + secret_columns, + ISNULL(i.index_usage_summary,''), + ISNULL(ip.index_size_summary,'') + FROM #IndexSanity i + JOIN #IndexColumns ic ON + i.object_id=ic.object_id + AND i.database_id = ic.database_id + AND i.schema_name = ic.schema_name + AND i.index_id IN (0,1) /* heaps and cx only */ + AND ic.is_identity=1 + AND ic.system_type_name IN ('tinyint', 'smallint', 'int') + JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id + CROSS APPLY ( + SELECT CAST(CASE WHEN ic.increment_value >= 0 + THEN + CASE ic.system_type_name + WHEN 'int' THEN (2147483647 - (ISNULL(ic.last_value,ic.seed_value) + ic.increment_value)) / 2147483647.*100 + WHEN 'smallint' THEN (32768 - (ISNULL(ic.last_value,ic.seed_value) + ic.increment_value)) / 32768.*100 + WHEN 'tinyint' THEN ( 255 - (ISNULL(ic.last_value,ic.seed_value) + ic.increment_value)) / 255.*100 + ELSE 999 + END + ELSE --ic.increment_value is negative + CASE ic.system_type_name + WHEN 'int' THEN ABS(-2147483647 - (ISNULL(ic.last_value,ic.seed_value) + ic.increment_value)) / 2147483647.*100 + WHEN 'smallint' THEN ABS(-32768 - (ISNULL(ic.last_value,ic.seed_value) + ic.increment_value)) / 32768.*100 + WHEN 'tinyint' THEN ABS( 0 - (ISNULL(ic.last_value,ic.seed_value) + ic.increment_value)) / 255.*100 + ELSE -1 + END + END AS NUMERIC(5,1)) AS percent_remaining + ) AS calc1 + WHERE i.index_id IN (1,0) + AND calc1.percent_remaining <= 30 + OPTION (RECOMPILE); + + + RAISERROR(N'check_id 72: Columnstore indexes with Trace Flag 834', 0,1) WITH NOWAIT; + IF EXISTS (SELECT * FROM #IndexSanity WHERE index_type IN (5,6)) + AND EXISTS (SELECT * FROM #TraceStatus WHERE TraceFlag = 834 AND status = 1) + BEGIN + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 72 AS check_id, + i.index_sanity_id, + 80 AS Priority, + N'Abnormal Psychology' AS findings_group, + 'Columnstore Indexes with Trace Flag 834' AS finding, + [database_name] AS [Database Name], + N'https://support.microsoft.com/en-us/kb/3210239' AS URL, + i.db_schema_object_indexid AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + ISNULL(sz.index_size_summary,'') AS index_size_summary + FROM #IndexSanity AS i + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + WHERE i.index_type IN (5,6) + OPTION ( RECOMPILE ); + END; + + ---------------------------------------- + --Statistics Info: Check_id 90-99 + ---------------------------------------- + + RAISERROR(N'check_id 90: Outdated 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 ) + SELECT 90 AS check_id, + 90 AS Priority, + 'Functioning Statistaholics' AS findings_group, + 'Statistics Not Updated Recently', + s.database_name, + 'https://www.brentozar.com/go/stats' AS URL, + 'Statistics on this table were last updated ' + + CASE WHEN s.last_statistics_update IS NULL THEN N' NEVER ' + ELSE CONVERT(NVARCHAR(20), s.last_statistics_update) + + ' have had ' + CONVERT(NVARCHAR(100), s.modification_counter) + + ' modifications in that time, which is ' + + CONVERT(NVARCHAR(100), s.percent_modifications) + + '% of the table.' + END 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.last_statistics_update <= CONVERT(DATETIME, GETDATE() - 30) + AND s.percent_modifications >= 10. + AND s.rows >= 10000 + OPTION ( RECOMPILE ); + + RAISERROR(N'check_id 91: Statistics with a low 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 91 AS check_id, + 90 AS Priority, + 'Functioning Statistaholics' AS findings_group, + 'Low Sampling Rates', + s.database_name, + 'https://www.brentozar.com/go/stats' AS URL, + 'Only ' + CONVERT(NVARCHAR(100), s.percent_sampled) + '% of the rows were sampled during the last statistics update. This may lead to poor cardinality estimates.' 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.rows BETWEEN 10000 AND 1000000 AND s.percent_sampled < 10) + OR (s.rows > 1000000 AND s.percent_sampled < 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 ) + SELECT 92 AS check_id, + 90 AS Priority, + 'Functioning Statistaholics' AS findings_group, + 'Statistics With NO RECOMPUTE', + s.database_name, + 'https://www.brentozar.com/go/stats' AS URL, + 'The statistic ' + QUOTENAME(s.statistics_name) + ' is set to not recompute. This can be helpful if data is really skewed, but harmful if you expect automatic statistics updates.' 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.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 ) + SELECT 94 AS check_id, + 100 AS Priority, + 'Serial Forcer' AS findings_group, + 'Check Constraint with Scalar UDF' AS finding, + cc.database_name, + 'https://www.brentozar.com/go/computedscalar' AS URL, + 'The check constraint ' + QUOTENAME(cc.constraint_name) + ' on ' + QUOTENAME(cc.schema_name) + '.' + QUOTENAME(cc.table_name) + ' is based on ' + cc.definition + + '. That indicates it may reference a scalar function, or a CLR function with data access, which can cause all queries and maintenance to run serially.' AS details, + cc.column_definition, + 'N/A' AS secret_columns, + 'N/A' AS index_usage_summary, + 'N/A' AS index_size_summary + FROM #CheckConstraints AS cc + WHERE cc.is_function = 1 + OPTION ( RECOMPILE ); + + RAISERROR(N'check_id 99: Computed Columns 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 ) + SELECT 99 AS check_id, + 100 AS Priority, + 'Serial Forcer' AS findings_group, + 'Computed Column with Scalar UDF' AS finding, + cc.database_name, + 'https://www.brentozar.com/go/serialudf' AS URL, + 'The computed column ' + QUOTENAME(cc.column_name) + ' on ' + QUOTENAME(cc.schema_name) + '.' + QUOTENAME(cc.table_name) + ' is based on ' + cc.definition + + '. That indicates it may reference a scalar function, or a CLR function with data access, which can cause all queries and maintenance to run serially.' AS details, + cc.column_definition, + 'N/A' AS secret_columns, + 'N/A' AS index_usage_summary, + 'N/A' AS index_size_summary + FROM #ComputedColumns AS cc + WHERE cc.is_function = 1 + OPTION ( RECOMPILE ); + + + + END /* IF @Mode IN (0, 4) DIAGNOSE priorities 1-100 */ + + + + + + + + + + + + + + + + + + + + + + + + + + IF @Mode = 4 /* DIAGNOSE*/ + BEGIN; + RAISERROR(N'@Mode=4, running rules for priorities 101+.', 0,1) WITH NOWAIT; + + RAISERROR(N'check_id 21: More Than 5 Percent NC Indexes Are Unused', 0,1) WITH NOWAIT; + DECLARE @percent_NC_indexes_unused NUMERIC(29,1); + DECLARE @NC_indexes_unused_reserved_MB NUMERIC(29,1); + + SELECT @percent_NC_indexes_unused = ( 100.00 * SUM(CASE + WHEN total_reads = 0 + THEN 1 + ELSE 0 + END) ) / COUNT(*), + @NC_indexes_unused_reserved_MB = SUM(CASE + WHEN total_reads = 0 + THEN sz.total_reserved_MB + ELSE 0 + END) + FROM #IndexSanity i + 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()) + 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, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 21 AS check_id, + MAX(i.index_sanity_id) AS index_sanity_id, + 150 AS Priority, + N'Index Hoarder' AS findings_group, + N'More Than 5 Percent NC Indexes Are Unused' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/IndexHoarder' AS URL, + CAST (@percent_NC_indexes_unused AS NVARCHAR(30)) + N' percent NC indexes (' + CAST(COUNT(*) AS NVARCHAR(10)) + N') unused. ' + + N'These take up ' + CAST (@NC_indexes_unused_reserved_MB AS NVARCHAR(30)) + N'MB of space.' AS details, + i.database_name + ' (' + CAST (COUNT(*) AS NVARCHAR(30)) + N' indexes)' AS index_definition, + '' AS secret_columns, + CAST(SUM(total_reads) AS NVARCHAR(256)) + N' reads (ALL); ' + + CAST(SUM([user_updates]) AS NVARCHAR(256)) + N' writes (ALL)' AS index_usage_summary, + + REPLACE(CONVERT(NVARCHAR(30),CAST(MAX([total_rows]) AS MONEY), 1), '.00', '') + N' rows (MAX)' + + CASE WHEN SUM(total_reserved_MB) > 1024 THEN + N'; ' + CAST(CAST(SUM(total_reserved_MB)/1024. AS NUMERIC(29,1)) AS NVARCHAR(30)) + 'GB (ALL)' + WHEN SUM(total_reserved_MB) > 0 THEN + N'; ' + CAST(CAST(SUM(total_reserved_MB) AS NUMERIC(29,1)) AS NVARCHAR(30)) + 'MB (ALL)' + ELSE '' + END AS index_size_summary + FROM #IndexSanity i + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + 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()) + GROUP BY i.database_name + OPTION ( RECOMPILE ); + + RAISERROR(N'check_id 23: Indexes with 7 or more columns. (Borderline)', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 23 AS check_id, + i.index_sanity_id, + 150 AS Priority, + N'Index Hoarder' AS findings_group, + N'Borderline: Wide Indexes (7 or More Columns)' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/IndexHoarder' AS URL, + CAST(count_key_columns + count_included_columns AS NVARCHAR(10)) + ' columns on ' + + i.db_schema_object_indexid AS details, i.index_definition, + i.secret_columns, + i.index_usage_summary, + sz.index_size_summary + FROM #IndexSanity AS i + JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id + WHERE ( count_key_columns + count_included_columns ) >= 7 + OPTION ( RECOMPILE ); + + RAISERROR(N'check_id 24: Wide clustered indexes (> 3 columns or > 16 bytes).', 0,1) WITH NOWAIT; + WITH count_columns AS ( + SELECT database_id, [object_id], + SUM(CASE max_length WHEN -1 THEN 0 ELSE max_length END) AS sum_max_length + FROM #IndexColumns ic + WHERE index_id IN (1,0) /*Heap or clustered only*/ + AND key_ordinal > 0 + GROUP BY database_id, object_id + ) + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 24 AS check_id, + i.index_sanity_id, + 150 AS Priority, + N'Index Hoarder' AS findings_group, + N'Wide Clustered Index (> 3 columns OR > 16 bytes)' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/IndexHoarder' AS URL, + CAST (i.count_key_columns AS NVARCHAR(10)) + N' columns with potential size of ' + + CAST(cc.sum_max_length AS NVARCHAR(10)) + + N' bytes in clustered index:' + i.db_schema_object_name + + N'. ' + + (SELECT CAST(COUNT(*) AS NVARCHAR(23)) + FROM #IndexSanity i2 + WHERE i2.[object_id]=i.[object_id] + AND i2.database_id = i.database_id + AND i2.index_id <> 1 + AND i2.is_disabled=0 + AND i2.is_hypothetical=0) + + N' NC indexes on the table.' + AS details, + i.index_definition, + secret_columns, + i.index_usage_summary, + ip.index_size_summary + FROM #IndexSanity i + JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id + JOIN count_columns AS cc ON i.[object_id]=cc.[object_id] + AND i.database_id = cc.database_id + WHERE index_id = 1 /* clustered only */ + AND + (count_key_columns > 3 /*More than three key columns.*/ + OR cc.sum_max_length > 16 /*More than 16 bytes in key */) + AND i.is_CX_columnstore = 0 + ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); + + RAISERROR(N'check_id 25: Addicted to nullable columns.', 0,1) WITH NOWAIT; + WITH count_columns AS ( + SELECT [object_id], + [database_id], + [schema_name], + SUM(CASE is_nullable WHEN 1 THEN 0 ELSE 1 END) AS non_nullable_columns, + COUNT(*) AS total_columns + FROM #IndexColumns ic + WHERE index_id IN (1,0) /*Heap or clustered only*/ + GROUP BY [object_id], [database_id], [schema_name] ) @@ -25852,9 +26568,9 @@ BEGIN; i.index_sanity_id, 200 AS Priority, N'Index Hoarder' AS findings_group, - N'Addicted to nulls' AS finding, + N'Addicted to Nulls' AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/IndexHoarder' AS URL, + N'https://www.brentozar.com/go/IndexHoarder' AS URL, i.db_schema_object_name + N' allows null in ' + CAST((total_columns-non_nullable_columns) AS NVARCHAR(10)) + N' of ' + CAST(total_columns AS NVARCHAR(10)) @@ -25869,7 +26585,6 @@ BEGIN; AND cc.database_id = ip.database_id AND cc.[schema_name] = ip.[schema_name] WHERE i.index_id IN (1,0) - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) AND cc.non_nullable_columns < 2 AND cc.total_columns > 3 ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); @@ -25894,9 +26609,9 @@ BEGIN; i.index_sanity_id, 150 AS Priority, N'Index Hoarder' AS findings_group, - N'Wide tables: 35+ cols or > 2000 non-LOB bytes' AS finding, + N'Wide Tables: 35+ cols or > 2000 non-LOB bytes' AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/IndexHoarder' AS URL, + N'https://www.brentozar.com/go/IndexHoarder' AS URL, i.db_schema_object_name + N' has ' + CAST((total_columns) AS NVARCHAR(10)) + N' total columns with a max possible width of ' + CAST(sum_max_length AS NVARCHAR(10)) @@ -25915,7 +26630,6 @@ BEGIN; AND cc.database_id = i.database_id AND cc.[schema_name] = i.[schema_name] WHERE i.index_id IN (1,0) - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) AND (cc.total_columns >= 35 OR cc.sum_max_length >= 2000) @@ -25942,7 +26656,7 @@ BEGIN; N'Index Hoarder' AS findings_group, N'Addicted to strings' AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/IndexHoarder' AS URL, + N'https://www.brentozar.com/go/IndexHoarder' AS URL, i.db_schema_object_name + N' uses string or LOB types for ' + CAST((string_or_LOB_columns) AS NVARCHAR(10)) + N' of ' + CAST(total_columns AS NVARCHAR(10)) @@ -25958,7 +26672,6 @@ BEGIN; AND cc.[schema_name] = i.[schema_name] CROSS APPLY (SELECT cc.total_columns - string_or_LOB_columns AS non_string_or_lob_columns) AS calc1 WHERE i.index_id IN (1,0) - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) AND calc1.non_string_or_lob_columns <= 1 AND cc.total_columns > 3 ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); @@ -25968,11 +26681,11 @@ BEGIN; secret_columns, index_usage_summary, index_size_summary ) SELECT 28 AS check_id, i.index_sanity_id, - 100 AS Priority, + 150 AS Priority, N'Index Hoarder' AS findings_group, - N'Non-Unique clustered index' AS finding, + N'Non-Unique Clustered JIndex' AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/IndexHoarder' AS URL, + N'https://www.brentozar.com/go/IndexHoarder' AS URL, N'Uniquifiers will be required! Clustered index: ' + i.db_schema_object_name + N' and all NC indexes. ' + (SELECT CAST(COUNT(*) AS NVARCHAR(23)) @@ -25991,45 +26704,42 @@ BEGIN; FROM #IndexSanity i JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id WHERE index_id = 1 /* clustered only */ - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) AND is_unique=0 /* not unique */ AND is_CX_columnstore=0 /* not a clustered columnstore-- no unique option on those */ ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); - RAISERROR(N'check_id 29: NC indexes with 0 reads. (Borderline) and < 10,000 writes', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 29 AS check_id, - i.index_sanity_id, - 150 AS Priority, - N'Index Hoarder' AS findings_group, - N'Unused NC index with Low Writes' AS finding, - [database_name] AS [Database Name], - N'http://BrentOzar.com/go/IndexHoarder' AS URL, - N'0 reads: ' + i.db_schema_object_indexid AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - sz.index_size_summary - FROM #IndexSanity AS i - JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.total_reads=0 - AND i.user_updates < 10000 - 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()) - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) - ORDER BY i.db_schema_object_indexid - OPTION ( RECOMPILE ); + RAISERROR(N'check_id 29: NC indexes with 0 reads. (Borderline) and < 10,000 writes', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 29 AS check_id, + i.index_sanity_id, + 150 AS Priority, + N'Index Hoarder' AS findings_group, + N'Unused NC index with Low Writes' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/IndexHoarder' AS URL, + N'0 reads: ' + i.db_schema_object_indexid AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + sz.index_size_summary + FROM #IndexSanity AS i + JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id + WHERE i.total_reads=0 + AND i.user_updates < 10000 + 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()) + ORDER BY i.db_schema_object_indexid + OPTION ( RECOMPILE ); - END; - ---------------------------------------- + + ---------------------------------------- --Feature-Phobic Indexes: Check_id 30-39 ---------------------------------------- - BEGIN RAISERROR(N'check_id 30: No indexes with includes', 0,1) WITH NOWAIT; /* This does not work the way you'd expect with @GetAllDatabases = 1. For details: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/825 @@ -26052,15 +26762,14 @@ BEGIN; 250 AS Priority, N'Feature-Phobic Indexes' AS findings_group, database_name AS [Database Name], - N'No indexes use includes' AS finding, 'http://BrentOzar.com/go/IndexFeatures' AS URL, - N'No indexes use includes' AS details, + N'No Indexes Use Includes' AS finding, 'https://www.brentozar.com/go/IndexFeatures' AS URL, + N'No Indexes Use Includes' AS details, database_name + N' (Entire database)' AS index_definition, N'' AS secret_columns, N'N/A' AS index_usage_summary, N'N/A' AS index_size_summary FROM #index_includes WHERE number_indexes_with_includes = 0 - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) OPTION ( RECOMPILE ); RAISERROR(N'check_id 31: < 3 percent of indexes have includes', 0,1) WITH NOWAIT; @@ -26068,11 +26777,11 @@ BEGIN; secret_columns, index_usage_summary, index_size_summary ) SELECT 31 AS check_id, NULL AS index_sanity_id, - 150 AS Priority, + 250 AS Priority, N'Feature-Phobic Indexes' AS findings_group, - N'Borderline: Includes are used in < 3% of indexes' AS findings, + N'Few Indexes Use Includes' AS findings, database_name AS [Database Name], - N'http://BrentOzar.com/go/IndexFeatures' AS URL, + N'https://www.brentozar.com/go/IndexFeatures' AS URL, N'Only ' + CAST(percent_indexes_with_includes AS NVARCHAR(20)) + '% of indexes have includes' AS details, N'Entire database' AS index_definition, N'' AS secret_columns, @@ -26080,7 +26789,6 @@ BEGIN; N'N/A' AS index_size_summary FROM #index_includes WHERE number_indexes_with_includes > 0 AND percent_indexes_with_includes <= 3 - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) OPTION ( RECOMPILE ); RAISERROR(N'check_id 32: filtered indexes and indexed views', 0,1) WITH NOWAIT; @@ -26092,9 +26800,9 @@ BEGIN; NULL AS index_sanity_id, 250 AS Priority, N'Feature-Phobic Indexes' AS findings_group, - N'Borderline: No filtered indexes or indexed views exist' AS finding, + N'No Filtered Indexes or Indexed Views' AS finding, i.database_name AS [Database Name], - N'http://BrentOzar.com/go/IndexFeatures' AS URL, + N'https://www.brentozar.com/go/IndexFeatures' AS URL, N'These are NOT always needed-- but do you know when you would use them?' AS details, i.database_name + N' (Entire database)' AS index_definition, N'' AS secret_columns, @@ -26103,138 +26811,42 @@ BEGIN; FROM #IndexSanity i WHERE i.database_name NOT IN ( SELECT database_name - FROM #IndexSanity - WHERE filter_definition <> '' ) - AND i.database_name NOT IN ( - SELECT database_name - FROM #IndexSanity - WHERE is_indexed_view = 1 ) - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) - OPTION ( RECOMPILE ); - END; - - RAISERROR(N'check_id 33: Potential filtered indexes based on column names.', 0,1) WITH NOWAIT; - - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 33 AS check_id, - i.index_sanity_id AS index_sanity_id, - 250 AS Priority, - N'Feature-Phobic Indexes' AS findings_group, - N'Potential filtered index (based on column name)' AS finding, - [database_name] AS [Database Name], - N'http://BrentOzar.com/go/IndexFeatures' AS URL, - N'A column name in this index suggests it might be a candidate for filtering (is%, %archive%, %active%, %flag%)' AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - sz.index_size_summary - FROM #IndexColumns ic - JOIN #IndexSanity i ON ic.[object_id]=i.[object_id] - AND ic.database_id =i.database_id - AND ic.schema_name = i.schema_name - AND ic.[index_id]=i.[index_id] - AND i.[index_id] > 1 /* non-clustered index */ - JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id - WHERE (column_name LIKE 'is%' - OR column_name LIKE '%archive%' - OR column_name LIKE '%active%' - OR column_name LIKE '%flag%') - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 34: Filtered index definition columns not in index definition', 0,1) WITH NOWAIT; - - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 34 AS check_id, - i.index_sanity_id, - 80 AS Priority, - N'Forgetful Indexes' AS findings_group, - N'Filter Columns Not In Index Definition' AS finding, - [database_name] AS [Database Name], - N'http://BrentOzar.com/go/IndexFeatures' AS URL, - N'The index ' - + QUOTENAME(i.index_name) - + N' on [' - + i.db_schema_object_name - + N'] has a filter on [' - + i.filter_definition - + N'] but is missing [' - + LTRIM(i.filter_columns_not_in_index) - + N'] from the index definition.' - AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - sz.index_size_summary - FROM #IndexSanity i - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.filter_columns_not_in_index IS NOT NULL - ORDER BY i.db_schema_object_indexid - OPTION ( RECOMPILE ); - - ---------------------------------------- - --Self Loathing Indexes : Check_id 40-49 - ---------------------------------------- - BEGIN - - RAISERROR(N'check_id 40: Fillfactor in nonclustered 80 percent or less', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 40 AS check_id, - i.index_sanity_id, - 100 AS Priority, - N'Self Loathing Indexes' AS findings_group, - N'Low Fill Factor: nonclustered index' AS finding, - [database_name] AS [Database Name], - N'http://BrentOzar.com/go/SelfLoathing' AS URL, - CAST(fill_factor AS NVARCHAR(10)) + N'% fill factor on ' + db_schema_object_indexid + N'. '+ - CASE WHEN (last_user_update IS NULL OR user_updates < 1) - THEN N'No writes have been made.' - ELSE - N'Last write was ' + CONVERT(NVARCHAR(16),last_user_update,121) + N' and ' + - CAST(user_updates AS NVARCHAR(25)) + N' updates have been made.' - END - AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - sz.index_size_summary - FROM #IndexSanity AS i - JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id - WHERE index_id > 1 - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) - AND fill_factor BETWEEN 1 AND 80 OPTION ( RECOMPILE ); + FROM #IndexSanity + WHERE filter_definition <> '' ) + AND i.database_name NOT IN ( + SELECT database_name + FROM #IndexSanity + WHERE is_indexed_view = 1 ) + OPTION ( RECOMPILE ); + + RAISERROR(N'check_id 33: Potential filtered indexes based on column names.', 0,1) WITH NOWAIT; - RAISERROR(N'check_id 40: Fillfactor in clustered 80 percent or less', 0,1) WITH NOWAIT; INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, secret_columns, index_usage_summary, index_size_summary ) - SELECT 40 AS check_id, - i.index_sanity_id, - 100 AS Priority, - N'Self Loathing Indexes' AS findings_group, - N'Low Fill Factor: clustered index' AS finding, - [database_name] AS [Database Name], - N'http://BrentOzar.com/go/SelfLoathing' AS URL, - N'Fill factor on ' + db_schema_object_indexid + N' is ' + CAST(fill_factor AS NVARCHAR(10)) + N'%. '+ - CASE WHEN (last_user_update IS NULL OR user_updates < 1) - THEN N'No writes have been made.' - ELSE - N'Last write was ' + CONVERT(NVARCHAR(16),last_user_update,121) + N' and ' + - CAST(user_updates AS NVARCHAR(25)) + N' updates have been made.' - END - AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - sz.index_size_summary - FROM #IndexSanity AS i - JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id - WHERE index_id = 1 - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) - AND fill_factor BETWEEN 1 AND 80 OPTION ( RECOMPILE ); - + SELECT 33 AS check_id, + i.index_sanity_id AS index_sanity_id, + 250 AS Priority, + N'Feature-Phobic Indexes' AS findings_group, + N'Potential Filtered Index (Based on Column Name)' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/IndexFeatures' AS URL, + N'A column name in this index suggests it might be a candidate for filtering (is%, %archive%, %active%, %flag%)' AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + sz.index_size_summary + FROM #IndexColumns ic + JOIN #IndexSanity i ON ic.[object_id]=i.[object_id] + AND ic.database_id =i.database_id + AND ic.schema_name = i.schema_name + AND ic.[index_id]=i.[index_id] + AND i.[index_id] > 1 /* non-clustered index */ + JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id + WHERE (column_name LIKE 'is%' + OR column_name LIKE '%archive%' + OR column_name LIKE '%active%' + OR column_name LIKE '%flag%') + OPTION ( RECOMPILE ); RAISERROR(N'check_id 41: Hypothetical indexes ', 0,1) WITH NOWAIT; INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, @@ -26245,7 +26857,7 @@ BEGIN; N'Self Loathing Indexes' AS findings_group, N'Hypothetical Index' AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/SelfLoathing' AS URL, + N'https://www.brentozar.com/go/SelfLoathing' AS URL, N'Hypothetical Index: ' + db_schema_object_indexid AS details, i.index_definition, i.secret_columns, @@ -26253,7 +26865,6 @@ BEGIN; N'' AS index_size_summary FROM #IndexSanity AS i WHERE is_hypothetical = 1 - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) OPTION ( RECOMPILE ); @@ -26267,7 +26878,7 @@ BEGIN; N'Self Loathing Indexes' AS findings_group, N'Disabled Index' AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/SelfLoathing' AS URL, + N'https://www.brentozar.com/go/SelfLoathing' AS URL, N'Disabled Index:' + db_schema_object_indexid AS details, i.index_definition, i.secret_columns, @@ -26275,51 +26886,8 @@ BEGIN; 'DISABLED' AS index_size_summary FROM #IndexSanity AS i WHERE is_disabled = 1 - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) OPTION ( RECOMPILE ); - RAISERROR(N'check_id 43: Heaps with forwarded records', 0,1) WITH NOWAIT; - WITH heaps_cte - AS ( SELECT [object_id], - [database_id], - [schema_name], - SUM(forwarded_fetch_count) AS forwarded_fetch_count, - SUM(leaf_delete_count) AS leaf_delete_count - FROM #IndexPartitionSanity - GROUP BY [object_id], - [database_id], - [schema_name] - HAVING SUM(forwarded_fetch_count) > 0) - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 43 AS check_id, - i.index_sanity_id, - 100 AS Priority, - N'Self Loathing Indexes' AS findings_group, - N'Heaps with forwarded records' AS finding, - [database_name] AS [Database Name], - N'http://BrentOzar.com/go/SelfLoathing' AS URL, - CASE WHEN h.forwarded_fetch_count >= 922337203685477 THEN '>= 922,337,203,685,477' - WHEN @DaysUptime < 1 THEN CAST(h.forwarded_fetch_count AS NVARCHAR(256)) + N' forwarded fetches against heap: ' + db_schema_object_indexid - ELSE REPLACE(CONVERT(NVARCHAR(256),CAST(CAST( - (h.forwarded_fetch_count /*/@DaysUptime */) - AS BIGINT) AS MONEY), 1), '.00', '') - END + N' forwarded fetches per day against heap: ' - + db_schema_object_indexid AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - sz.index_size_summary - FROM #IndexSanity i - JOIN heaps_cte h ON i.[object_id] = h.[object_id] - AND i.[database_id] = h.[database_id] - AND i.[schema_name] = h.[schema_name] - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.index_id = 0 - AND h.forwarded_fetch_count / @DaysUptime > 1000 - AND sz.total_reserved_MB >= CASE WHEN NOT (@GetAllDatabases = 1 OR @Mode = 4) THEN @ThresholdMB ELSE sz.total_reserved_MB END - OPTION ( RECOMPILE ); - RAISERROR(N'check_id 49: Heaps with deletes', 0,1) WITH NOWAIT; WITH heaps_cte AS ( SELECT [object_id], @@ -26338,284 +26906,27 @@ BEGIN; i.index_sanity_id, 200 AS Priority, N'Self Loathing Indexes' AS findings_group, - N'Heaps with deletes' AS finding, - [database_name] AS [Database Name], - N'http://BrentOzar.com/go/SelfLoathing' AS URL, - CAST(h.leaf_delete_count AS NVARCHAR(256)) + N' deletes against heap:' - + db_schema_object_indexid AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - sz.index_size_summary - FROM #IndexSanity i - JOIN heaps_cte h ON i.[object_id] = h.[object_id] - AND i.[database_id] = h.[database_id] - AND i.[schema_name] = h.[schema_name] - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.index_id = 0 - AND sz.total_reserved_MB >= CASE WHEN NOT (@GetAllDatabases = 1 OR @Mode = 4) THEN @ThresholdMB ELSE sz.total_reserved_MB END - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 44: Large Heaps with reads or writes.', 0,1) WITH NOWAIT; - WITH heaps_cte - AS ( SELECT [object_id], - [database_id], - [schema_name], - SUM(forwarded_fetch_count) AS forwarded_fetch_count, - SUM(leaf_delete_count) AS leaf_delete_count - FROM #IndexPartitionSanity - GROUP BY [object_id], - [database_id], - [schema_name] - HAVING SUM(forwarded_fetch_count) > 0 - OR SUM(leaf_delete_count) > 0) - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 44 AS check_id, - i.index_sanity_id, - 100 AS Priority, - N'Self Loathing Indexes' AS findings_group, - N'Large Active heap' AS finding, - [database_name] AS [Database Name], - N'http://BrentOzar.com/go/SelfLoathing' AS URL, - N'Should this table be a heap? ' + db_schema_object_indexid AS details, - i.index_definition, - 'N/A' AS secret_columns, - i.index_usage_summary, - sz.index_size_summary - FROM #IndexSanity i - LEFT JOIN heaps_cte h ON i.[object_id] = h.[object_id] - AND i.[database_id] = h.[database_id] - AND i.[schema_name] = h.[schema_name] - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.index_id = 0 - AND (i.total_reads > 0 OR i.user_updates > 0) - AND sz.total_rows >= 100000 - AND h.[object_id] IS NULL /*don't duplicate the prior check.*/ - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 45: Medium Heaps with reads or writes.', 0,1) WITH NOWAIT; - WITH heaps_cte - AS ( SELECT [object_id], - [database_id], - [schema_name], - SUM(forwarded_fetch_count) AS forwarded_fetch_count, - SUM(leaf_delete_count) AS leaf_delete_count - FROM #IndexPartitionSanity - GROUP BY [object_id], - [database_id], - [schema_name] - HAVING SUM(forwarded_fetch_count) > 0 - OR SUM(leaf_delete_count) > 0) - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 45 AS check_id, - i.index_sanity_id, - 100 AS Priority, - N'Self Loathing Indexes' AS findings_group, - N'Medium Active heap' AS finding, - [database_name] AS [Database Name], - N'http://BrentOzar.com/go/SelfLoathing' AS URL, - N'Should this table be a heap? ' + db_schema_object_indexid AS details, - i.index_definition, - 'N/A' AS secret_columns, - i.index_usage_summary, - sz.index_size_summary - FROM #IndexSanity i - LEFT JOIN heaps_cte h ON i.[object_id] = h.[object_id] - AND i.[database_id] = h.[database_id] - AND i.[schema_name] = h.[schema_name] - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.index_id = 0 - AND - (i.total_reads > 0 OR i.user_updates > 0) - AND sz.total_rows >= 10000 AND sz.total_rows < 100000 - AND h.[object_id] IS NULL /*don't duplicate the prior check.*/ - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 46: Small Heaps with reads or writes.', 0,1) WITH NOWAIT; - WITH heaps_cte - AS ( SELECT [object_id], - [database_id], - [schema_name], - SUM(forwarded_fetch_count) AS forwarded_fetch_count, - SUM(leaf_delete_count) AS leaf_delete_count - FROM #IndexPartitionSanity - GROUP BY [object_id], - [database_id], - [schema_name] - HAVING SUM(forwarded_fetch_count) > 0 - OR SUM(leaf_delete_count) > 0) - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 46 AS check_id, - i.index_sanity_id, - 100 AS Priority, - N'Self Loathing Indexes' AS findings_group, - N'Small Active heap' AS finding, - [database_name] AS [Database Name], - N'http://BrentOzar.com/go/SelfLoathing' AS URL, - N'Should this table be a heap? ' + db_schema_object_indexid AS details, - i.index_definition, - 'N/A' AS secret_columns, - i.index_usage_summary, - sz.index_size_summary - FROM #IndexSanity i - LEFT JOIN heaps_cte h ON i.[object_id] = h.[object_id] - AND i.[database_id] = h.[database_id] - AND i.[schema_name] = h.[schema_name] - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.index_id = 0 - AND - (i.total_reads > 0 OR i.user_updates > 0) - AND sz.total_rows < 10000 - AND h.[object_id] IS NULL /*don't duplicate the prior check.*/ - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 47: Heap with a Nonclustered Primary Key', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 47 AS check_id, - i.index_sanity_id, - 100 AS Priority, - N'Self Loathing Indexes' AS findings_group, - N'Heap with a Nonclustered Primary Key' AS finding, - [database_name] AS [Database Name], - N'http://BrentOzar.com/go/SelfLoathing' AS URL, - db_schema_object_indexid + N' is a HEAP with a Nonclustered Primary Key' AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - sz.index_size_summary - FROM #IndexSanity i - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.index_type = 2 AND i.is_primary_key = 1 - AND EXISTS - ( - SELECT 1/0 - FROM #IndexSanity AS isa - WHERE i.database_id = isa.database_id - AND i.object_id = isa.object_id - AND isa.index_id = 0 - ) - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 48: Nonclustered indexes with a bad read to write ratio', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 48 AS check_id, - i.index_sanity_id, - 100 AS Priority, - N'Index Hoarder' AS findings_group, - N'NC index with High Writes:Reads' AS finding, + N'Heaps with Deletes' AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/IndexHoarder' AS URL, - N'Reads: ' - + REPLACE(CONVERT(NVARCHAR(30), CAST((i.total_reads) AS MONEY), 1), N'.00', N'') - + N' Writes: ' - + REPLACE(CONVERT(NVARCHAR(30), CAST((i.user_updates) AS MONEY), 1), N'.00', N'') - + N' on: ' - + i.db_schema_object_indexid AS details, + N'https://www.brentozar.com/go/SelfLoathing' AS URL, + CAST(h.leaf_delete_count AS NVARCHAR(256)) + N' deletes against heap:' + + db_schema_object_indexid AS details, i.index_definition, - i.secret_columns, + i.secret_columns, i.index_usage_summary, sz.index_size_summary FROM #IndexSanity i + JOIN heaps_cte h ON i.[object_id] = h.[object_id] + AND i.[database_id] = h.[database_id] + AND i.[schema_name] = h.[schema_name] JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.total_reads > 0 /*Not totally unused*/ - AND i.user_updates >= 10000 /*Decent write activity*/ - AND i.total_reads < 10000 - AND ((i.total_reads * 10) < i.user_updates) /*10x more writes than reads*/ - 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 - ORDER BY i.db_schema_object_indexid - OPTION ( RECOMPILE ); - - END; - ---------------------------------------- - --Indexaphobia - --Missing indexes with value >= 5 million: : Check_id 50-59 - ---------------------------------------- - BEGIN - RAISERROR(N'check_id 50: Indexaphobia.', 0,1) WITH NOWAIT; - WITH index_size_cte - AS ( SELECT i.database_id, - i.schema_name, - i.[object_id], - MAX(i.index_sanity_id) AS index_sanity_id, - ISNULL(NULLIF(MAX(DATEDIFF(DAY, i.create_date, SYSDATETIME())), 0), 1) AS create_days, - ISNULL ( - CAST(SUM(CASE WHEN index_id NOT IN (0,1) THEN 1 ELSE 0 END) - AS NVARCHAR(30))+ N' NC indexes exist (' + - CASE WHEN SUM(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) > 1024 - THEN CAST(CAST(SUM(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END )/1024. - - AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'GB); ' - ELSE CAST(SUM(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) - AS NVARCHAR(30)) + N'MB); ' - END + - CASE WHEN MAX(sz.[total_rows]) >= 922337203685477 THEN '>= 922,337,203,685,477' - ELSE REPLACE(CONVERT(NVARCHAR(30),CAST(MAX(sz.[total_rows]) AS MONEY), 1), '.00', '') - END + - + N' Estimated Rows;' - ,N'') AS index_size_summary - FROM #IndexSanity AS i - LEFT JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id AND i.database_id = sz.database_id - WHERE i.is_hypothetical = 0 - AND i.is_disabled = 0 - GROUP BY i.database_id, i.schema_name, i.[object_id]) - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - index_usage_summary, index_size_summary, create_tsql, more_info ) - - SELECT check_id, t.index_sanity_id, t.check_id, t.findings_group, t.finding, t.[Database Name], t.URL, t.details, t.[definition], - index_estimated_impact, t.index_size_summary, create_tsql, more_info - FROM - ( - SELECT ROW_NUMBER() OVER (ORDER BY magic_benefit_number DESC) AS rownum, - 50 AS check_id, - sz.index_sanity_id, - 10 AS Priority, - N'Indexaphobia' AS findings_group, - N'High value missing index' AS finding, - [database_name] AS [Database Name], - N'http://BrentOzar.com/go/Indexaphobia' AS URL, - mi.[statement] + - N' Est. benefit per day: ' + - CASE WHEN magic_benefit_number >= 922337203685477 THEN '>= 922,337,203,685,477' - ELSE REPLACE(CONVERT(NVARCHAR(256),CAST(CAST( - (magic_benefit_number/@DaysUptime) - AS BIGINT) AS MONEY), 1), '.00', '') - END AS details, - missing_index_details AS [definition], - index_estimated_impact, - sz.index_size_summary, - mi.create_tsql, - mi.more_info, - magic_benefit_number, - mi.is_low - FROM #MissingIndexes mi - LEFT JOIN index_size_cte sz ON mi.[object_id] = sz.object_id - AND mi.database_id = sz.database_id - AND mi.schema_name = sz.schema_name - /* Minimum benefit threshold = 100k/day of uptime OR since table creation date, whichever is lower*/ - WHERE @ShowAllMissingIndexRequests=1 - OR ( @Mode = 4 AND (magic_benefit_number / CASE WHEN sz.create_days < @DaysUptime THEN sz.create_days ELSE @DaysUptime END) >= 100000 ) - OR (magic_benefit_number / CASE WHEN sz.create_days < @DaysUptime THEN sz.create_days ELSE @DaysUptime END) >= 100000 - ) AS t - WHERE t.rownum <= CASE WHEN (@Mode <> 4) THEN 20 ELSE t.rownum END - ORDER BY magic_benefit_number DESC - OPTION ( RECOMPILE ); - + WHERE i.index_id = 0 + AND sz.total_reserved_MB >= CASE WHEN NOT (@GetAllDatabases = 1 OR @Mode = 4) THEN @ThresholdMB ELSE sz.total_reserved_MB END + OPTION ( RECOMPILE ); - END; ---------------------------------------- --Abnormal Psychology : Check_id 60-79 ---------------------------------------- - BEGIN RAISERROR(N'check_id 60: XML indexes', 0,1) WITH NOWAIT; INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, secret_columns, index_usage_summary, index_size_summary ) @@ -26623,9 +26934,9 @@ BEGIN; i.index_sanity_id, 150 AS Priority, N'Abnormal Psychology' AS findings_group, - N'XML Indexes' AS finding, + N'XML Index' AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/AbnormalPsychology' AS URL, + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, i.db_schema_object_indexid AS details, i.index_definition, i.secret_columns, @@ -26634,7 +26945,6 @@ BEGIN; FROM #IndexSanity AS i JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id WHERE i.is_XML = 1 - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) OPTION ( RECOMPILE ); RAISERROR(N'check_id 61: Columnstore indexes', 0,1) WITH NOWAIT; @@ -26649,7 +26959,7 @@ BEGIN; ELSE N'Clustered Columnstore Index' END AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/AbnormalPsychology' AS URL, + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, i.db_schema_object_indexid AS details, i.index_definition, i.secret_columns, @@ -26658,7 +26968,6 @@ BEGIN; FROM #IndexSanity AS i JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id WHERE i.is_NC_columnstore = 1 OR i.is_CX_columnstore=1 - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) OPTION ( RECOMPILE ); @@ -26669,9 +26978,9 @@ BEGIN; i.index_sanity_id, 150 AS Priority, N'Abnormal Psychology' AS findings_group, - N'Spatial indexes' AS finding, + N'Spatial Index' AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/AbnormalPsychology' AS URL, + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, i.db_schema_object_indexid AS details, i.index_definition, i.secret_columns, @@ -26680,7 +26989,6 @@ BEGIN; FROM #IndexSanity AS i JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id WHERE i.is_spatial = 1 - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) OPTION ( RECOMPILE ); RAISERROR(N'check_id 63: Compressed indexes', 0,1) WITH NOWAIT; @@ -26690,9 +26998,9 @@ BEGIN; i.index_sanity_id, 150 AS Priority, N'Abnormal Psychology' AS findings_group, - N'Compressed indexes' AS finding, + N'Compressed Index' AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/AbnormalPsychology' AS URL, + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, i.db_schema_object_indexid + N'. COMPRESSION: ' + sz.data_compression_desc AS details, i.index_definition, i.secret_columns, @@ -26701,7 +27009,6 @@ BEGIN; FROM #IndexSanity AS i JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id WHERE sz.data_compression_desc LIKE '%PAGE%' OR sz.data_compression_desc LIKE '%ROW%' - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) OPTION ( RECOMPILE ); RAISERROR(N'check_id 64: Partitioned', 0,1) WITH NOWAIT; @@ -26711,9 +27018,9 @@ BEGIN; i.index_sanity_id, 150 AS Priority, N'Abnormal Psychology' AS findings_group, - N'Partitioned indexes' AS finding, + N'Partitioned Index' AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/AbnormalPsychology' AS URL, + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, i.db_schema_object_indexid AS details, i.index_definition, i.secret_columns, @@ -26722,7 +27029,6 @@ BEGIN; FROM #IndexSanity AS i JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id WHERE i.partition_key_column_name IS NOT NULL - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) OPTION ( RECOMPILE ); RAISERROR(N'check_id 65: Non-Aligned Partitioned', 0,1) WITH NOWAIT; @@ -26732,178 +27038,72 @@ BEGIN; i.index_sanity_id, 150 AS Priority, N'Abnormal Psychology' AS findings_group, - N'Non-Aligned index on a partitioned table' AS finding, + N'Non-Aligned Index on a Partitioned Table' AS finding, i.[database_name] AS [Database Name], - N'http://BrentOzar.com/go/AbnormalPsychology' AS URL, - i.db_schema_object_indexid AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - ISNULL(sz.index_size_summary,'') AS index_size_summary - FROM #IndexSanity AS i - JOIN #IndexSanity AS iParent ON - i.[object_id]=iParent.[object_id] - AND i.database_id = iParent.database_id - AND i.schema_name = iParent.schema_name - AND iParent.index_id IN (0,1) /* could be a partitioned heap or clustered table */ - AND iParent.partition_key_column_name IS NOT NULL /* parent is partitioned*/ - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.partition_key_column_name IS NULL - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 66: Recently created tables/indexes (1 week)', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 66 AS check_id, - i.index_sanity_id, - 200 AS Priority, - N'Abnormal Psychology' AS findings_group, - N'Recently created tables/indexes (1 week)' AS finding, - [database_name] AS [Database Name], - N'http://BrentOzar.com/go/AbnormalPsychology' AS URL, - i.db_schema_object_indexid + N' was created on ' + - CONVERT(NVARCHAR(16),i.create_date,121) + - N'. Tables/indexes which are dropped/created regularly require special methods for index tuning.' - AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - ISNULL(sz.index_size_summary,'') AS index_size_summary - FROM #IndexSanity AS i - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.create_date >= DATEADD(dd,-7,GETDATE()) - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 67: Recently modified tables/indexes (2 days)', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 67 AS check_id, - i.index_sanity_id, - 200 AS Priority, - N'Abnormal Psychology' AS findings_group, - N'Recently modified tables/indexes (2 days)' AS finding, - [database_name] AS [Database Name], - N'http://BrentOzar.com/go/AbnormalPsychology' AS URL, - i.db_schema_object_indexid + N' was modified on ' + - CONVERT(NVARCHAR(16),i.modify_date,121) + - N'. A large amount of recently modified indexes may mean a lot of rebuilds are occurring each night.' - AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - ISNULL(sz.index_size_summary,'') AS index_size_summary - FROM #IndexSanity AS i - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.modify_date > DATEADD(dd,-2,GETDATE()) - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) - AND /*Exclude recently created tables.*/ - i.create_date < DATEADD(dd,-7,GETDATE()) - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 68: Identity columns within 30 percent of the end of range', 0,1) WITH NOWAIT; - -- Allowed Ranges: - --int -2,147,483,648 to 2,147,483,647 - --smallint -32,768 to 32,768 - --tinyint 0 to 255 - - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 68 AS check_id, - i.index_sanity_id, - 200 AS Priority, - N'Abnormal Psychology' AS findings_group, - N'Identity column within ' + - CAST (calc1.percent_remaining AS NVARCHAR(256)) - + N' percent end of range' AS finding, - [database_name] AS [Database Name], - N'http://BrentOzar.com/go/AbnormalPsychology' AS URL, - i.db_schema_object_name + N'.' + QUOTENAME(ic.column_name) - + N' is an identity with type ' + ic.system_type_name - + N', last value of ' - + ISNULL((CONVERT(NVARCHAR(256),CAST(ic.last_value AS DECIMAL(38,0)), 1)),N'NULL') - + N', seed of ' - + ISNULL((CONVERT(NVARCHAR(256),CAST(ic.seed_value AS DECIMAL(38,0)), 1)),N'NULL') - + N', increment of ' + CAST(ic.increment_value AS NVARCHAR(256)) - + N', and range of ' + - CASE ic.system_type_name WHEN 'int' THEN N'+/- 2,147,483,647' - WHEN 'smallint' THEN N'+/- 32,768' - WHEN 'tinyint' THEN N'0 to 255' - ELSE 'unknown' - END - AS details, - i.index_definition, - secret_columns, - ISNULL(i.index_usage_summary,''), - ISNULL(ip.index_size_summary,'') - FROM #IndexSanity i - JOIN #IndexColumns ic ON - i.object_id=ic.object_id - AND i.database_id = ic.database_id - AND i.schema_name = ic.schema_name - AND i.index_id IN (0,1) /* heaps and cx only */ - AND ic.is_identity=1 - AND ic.system_type_name IN ('tinyint', 'smallint', 'int') - JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id - CROSS APPLY ( - SELECT CAST(CASE WHEN ic.increment_value >= 0 - THEN - CASE ic.system_type_name - WHEN 'int' THEN (2147483647 - (ISNULL(ic.last_value,ic.seed_value) + ic.increment_value)) / 2147483647.*100 - WHEN 'smallint' THEN (32768 - (ISNULL(ic.last_value,ic.seed_value) + ic.increment_value)) / 32768.*100 - WHEN 'tinyint' THEN ( 255 - (ISNULL(ic.last_value,ic.seed_value) + ic.increment_value)) / 255.*100 - ELSE 999 - END - ELSE --ic.increment_value is negative - CASE ic.system_type_name - WHEN 'int' THEN ABS(-2147483647 - (ISNULL(ic.last_value,ic.seed_value) + ic.increment_value)) / 2147483647.*100 - WHEN 'smallint' THEN ABS(-32768 - (ISNULL(ic.last_value,ic.seed_value) + ic.increment_value)) / 32768.*100 - WHEN 'tinyint' THEN ABS( 0 - (ISNULL(ic.last_value,ic.seed_value) + ic.increment_value)) / 255.*100 - ELSE -1 - END - END AS NUMERIC(5,1)) AS percent_remaining - ) AS calc1 - WHERE i.index_id IN (1,0) - AND calc1.percent_remaining <= 30 - UNION ALL - SELECT 68 AS check_id, - i.index_sanity_id, - 200 AS Priority, - N'Abnormal Psychology' AS findings_group, - N'Identity column using a negative seed or increment other than 1' AS finding, - [database_name] AS [Database Name], - N'http://BrentOzar.com/go/AbnormalPsychology' AS URL, - i.db_schema_object_name + N'.' + QUOTENAME(ic.column_name) - + N' is an identity with type ' + ic.system_type_name - + N', last value of ' - + ISNULL((CONVERT(NVARCHAR(256),CAST(ic.last_value AS DECIMAL(38,0)), 1)),N'NULL') - + N', seed of ' - + ISNULL((CONVERT(NVARCHAR(256),CAST(ic.seed_value AS DECIMAL(38,0)), 1)),N'NULL') - + N', increment of ' + CAST(ic.increment_value AS NVARCHAR(256)) - + N', and range of ' + - CASE ic.system_type_name WHEN 'int' THEN N'+/- 2,147,483,647' - WHEN 'smallint' THEN N'+/- 32,768' - WHEN 'tinyint' THEN N'0 to 255' - ELSE 'unknown' - END - AS details, - i.index_definition, - secret_columns, - ISNULL(i.index_usage_summary,''), - ISNULL(ip.index_size_summary,'') - FROM #IndexSanity i - JOIN #IndexColumns ic ON - i.object_id=ic.object_id - AND i.database_id = ic.database_id - AND i.schema_name = ic.schema_name - AND i.index_id IN (0,1) /* heaps and cx only */ - AND ic.is_identity=1 - AND ic.system_type_name IN ('tinyint', 'smallint', 'int') - JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id - WHERE i.index_id IN (1,0) - AND (ic.seed_value < 0 OR ic.increment_value <> 1) - ORDER BY finding, details DESC - OPTION ( RECOMPILE ); + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, + i.db_schema_object_indexid AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + ISNULL(sz.index_size_summary,'') AS index_size_summary + FROM #IndexSanity AS i + JOIN #IndexSanity AS iParent ON + i.[object_id]=iParent.[object_id] + AND i.database_id = iParent.database_id + AND i.schema_name = iParent.schema_name + AND iParent.index_id IN (0,1) /* could be a partitioned heap or clustered table */ + AND iParent.partition_key_column_name IS NOT NULL /* parent is partitioned*/ + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + WHERE i.partition_key_column_name IS NULL + OPTION ( RECOMPILE ); + + RAISERROR(N'check_id 66: Recently created tables/indexes (1 week)', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 66 AS check_id, + i.index_sanity_id, + 200 AS Priority, + N'Abnormal Psychology' AS findings_group, + N'Recently Created Tables/Indexes (1 week)' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, + i.db_schema_object_indexid + N' was created on ' + + CONVERT(NVARCHAR(16),i.create_date,121) + + N'. Tables/indexes which are dropped/created regularly require special methods for index tuning.' + AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + ISNULL(sz.index_size_summary,'') AS index_size_summary + FROM #IndexSanity AS i + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + WHERE i.create_date >= DATEADD(dd,-7,GETDATE()) + OPTION ( RECOMPILE ); + + RAISERROR(N'check_id 67: Recently modified tables/indexes (2 days)', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 67 AS check_id, + i.index_sanity_id, + 200 AS Priority, + N'Abnormal Psychology' AS findings_group, + N'Recently Modified Tables/Indexes (2 days)' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, + i.db_schema_object_indexid + N' was modified on ' + + CONVERT(NVARCHAR(16),i.modify_date,121) + + N'. A large amount of recently modified indexes may mean a lot of rebuilds are occurring each night.' + AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + ISNULL(sz.index_size_summary,'') AS index_size_summary + FROM #IndexSanity AS i + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + WHERE i.modify_date > DATEADD(dd,-2,GETDATE()) + AND /*Exclude recently created tables.*/ + i.create_date < DATEADD(dd,-7,GETDATE()) + OPTION ( RECOMPILE ); RAISERROR(N'check_id 69: Column collation does not match database collation', 0,1) WITH NOWAIT; WITH count_columns AS ( @@ -26924,9 +27124,9 @@ BEGIN; i.index_sanity_id, 150 AS Priority, N'Abnormal Psychology' AS findings_group, - N'Column collation does not match database collation' AS finding, + N'Column Collation Does Not Match Database Collation' AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/AbnormalPsychology' AS URL, + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, i.db_schema_object_name + N' has ' + CAST(column_count AS NVARCHAR(20)) + N' column' + CASE WHEN column_count > 1 THEN 's' ELSE '' END @@ -26942,7 +27142,6 @@ BEGIN; AND cc.database_id = i.database_id AND cc.schema_name = i.schema_name WHERE i.index_id IN (1,0) - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); RAISERROR(N'check_id 70: Replicated columns', 0,1) WITH NOWAIT; @@ -26964,9 +27163,9 @@ BEGIN; i.index_sanity_id, 200 AS Priority, N'Abnormal Psychology' AS findings_group, - N'Replicated columns' AS finding, + N'Replicated Columns' AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/AbnormalPsychology' AS URL, + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, i.db_schema_object_name + N' has ' + CAST(replicated_column_count AS NVARCHAR(20)) + N' out of ' + CAST(column_count AS NVARCHAR(20)) @@ -26984,7 +27183,6 @@ BEGIN; AND i.schema_name = cc.schema_name WHERE i.index_id IN (1,0) AND replicated_column_count > 0 - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); @@ -26997,7 +27195,7 @@ BEGIN; N'Abnormal Psychology' AS findings_group, N'Cascading Updates or Deletes' AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/AbnormalPsychology' AS URL, + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, N'Foreign Key ' + foreign_key_name + N' on ' + QUOTENAME(parent_object_name) + N'(' + LTRIM(parent_fk_columns) + N')' + N' referencing ' + QUOTENAME(referenced_object_name) + N'(' + LTRIM(referenced_fk_columns) + N')' @@ -27015,32 +27213,8 @@ BEGIN; FROM #ForeignKeys fk WHERE ([delete_referential_action_desc] <> N'NO_ACTION' OR [update_referential_action_desc] <> N'NO_ACTION') - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) OPTION ( RECOMPILE ); - RAISERROR(N'check_id 72: Columnstore indexes with Trace Flag 834', 0,1) WITH NOWAIT; - IF EXISTS (SELECT * FROM #IndexSanity WHERE index_type IN (5,6)) - AND EXISTS (SELECT * FROM #TraceStatus WHERE TraceFlag = 834 AND status = 1) - BEGIN - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 72 AS check_id, - i.index_sanity_id, - 150 AS Priority, - N'Abnormal Psychology' AS findings_group, - 'Columnstore Indexes are being used in conjunction with trace flag 834. Visit the link to see why this can be a bad idea' AS finding, - [database_name] AS [Database Name], - N'https://support.microsoft.com/en-us/kb/3210239' AS URL, - i.db_schema_object_indexid AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - ISNULL(sz.index_size_summary,'') AS index_size_summary - FROM #IndexSanity AS i - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.index_type IN (5,6) - OPTION ( RECOMPILE ); - END; RAISERROR(N'check_id 73: In-Memory OLTP', 0,1) WITH NOWAIT; INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, @@ -27051,7 +27225,7 @@ BEGIN; N'Abnormal Psychology' AS findings_group, N'In-Memory OLTP' AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/AbnormalPsychology' AS URL, + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, i.db_schema_object_indexid AS details, i.index_definition, i.secret_columns, @@ -27060,15 +27234,53 @@ BEGIN; FROM #IndexSanity AS i JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id WHERE i.is_in_memory_oltp = 1 - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) OPTION ( RECOMPILE ); - END; + RAISERROR(N'check_id 74: Identity column with unusual seed', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 74 AS check_id, + i.index_sanity_id, + 200 AS Priority, + N'Abnormal Psychology' AS findings_group, + N'Identity Column Using a Negative Seed or Increment Other Than 1' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, + i.db_schema_object_name + N'.' + QUOTENAME(ic.column_name) + + N' is an identity with type ' + ic.system_type_name + + N', last value of ' + + ISNULL((CONVERT(NVARCHAR(256),CAST(ic.last_value AS DECIMAL(38,0)), 1)),N'NULL') + + N', seed of ' + + ISNULL((CONVERT(NVARCHAR(256),CAST(ic.seed_value AS DECIMAL(38,0)), 1)),N'NULL') + + N', increment of ' + CAST(ic.increment_value AS NVARCHAR(256)) + + N', and range of ' + + CASE ic.system_type_name WHEN 'int' THEN N'+/- 2,147,483,647' + WHEN 'smallint' THEN N'+/- 32,768' + WHEN 'tinyint' THEN N'0 to 255' + ELSE 'unknown' + END + AS details, + i.index_definition, + secret_columns, + ISNULL(i.index_usage_summary,''), + ISNULL(ip.index_size_summary,'') + FROM #IndexSanity i + JOIN #IndexColumns ic ON + i.object_id=ic.object_id + AND i.database_id = ic.database_id + AND i.schema_name = ic.schema_name + AND i.index_id IN (0,1) /* heaps and cx only */ + AND ic.is_identity=1 + AND ic.system_type_name IN ('tinyint', 'smallint', 'int') + JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id + WHERE i.index_id IN (1,0) + AND (ic.seed_value < 0 OR ic.increment_value <> 1) + ORDER BY finding, details DESC + OPTION ( RECOMPILE ); - ---------------------------------------- + ---------------------------------------- --Workaholics: Check_id 80-89 ---------------------------------------- - BEGIN RAISERROR(N'check_id 80: Most scanned indexes (index_usage_stats)', 0,1) WITH NOWAIT; INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, @@ -27083,9 +27295,9 @@ BEGIN; i.index_sanity_id AS index_sanity_id, 200 AS Priority, N'Workaholics' AS findings_group, - N'Scan-a-lots (index_usage_stats)' AS finding, + N'Scan-a-lots (index-usage-stats)' AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/Workaholics' AS URL, + N'https://www.brentozar.com/go/Workaholics' AS URL, REPLACE(CONVERT( NVARCHAR(50),CAST(i.user_scans AS MONEY),1),'.00','') + N' scans against ' + i.db_schema_object_indexid + N'. Latest scan: ' + ISNULL(CAST(i.last_user_scan AS NVARCHAR(128)),'?') + N'. ' @@ -27097,7 +27309,6 @@ BEGIN; FROM #IndexSanity i JOIN #IndexSanitySize iss ON i.index_sanity_id=iss.index_sanity_id WHERE ISNULL(i.user_scans,0) > 0 - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) ORDER BY i.user_scans * iss.total_reserved_MB DESC OPTION ( RECOMPILE ); @@ -27112,9 +27323,9 @@ BEGIN; i.index_sanity_id AS index_sanity_id, 200 AS Priority, N'Workaholics' AS findings_group, - N'Top recent accesses (index_op_stats)' AS finding, + N'Top Recent Accesses (index-op-stats)' AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/Workaholics' AS URL, + N'https://www.brentozar.com/go/Workaholics' AS URL, ISNULL(REPLACE( CONVERT(NVARCHAR(50),CAST((iss.total_range_scan_count + iss.total_singleton_lookup_count) AS MONEY),1), N'.00',N'') @@ -27129,84 +27340,10 @@ BEGIN; FROM #IndexSanity i JOIN #IndexSanitySize iss ON i.index_sanity_id=iss.index_sanity_id WHERE (ISNULL(iss.total_range_scan_count,0) > 0 OR ISNULL(iss.total_singleton_lookup_count,0) > 0) - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) ORDER BY ((iss.total_range_scan_count + iss.total_singleton_lookup_count) * iss.total_reserved_MB) DESC OPTION ( RECOMPILE ); - END; - - ---------------------------------------- - --Statistics Info: Check_id 90-99 - ---------------------------------------- - BEGIN - - RAISERROR(N'check_id 90: Outdated 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 ) - SELECT 90 AS check_id, - 200 AS Priority, - 'Functioning Statistaholics' AS findings_group, - 'Statistic Abandonment Issues', - s.database_name, - '' AS URL, - 'Statistics on this table were last updated ' + - CASE s.last_statistics_update WHEN NULL THEN N' NEVER ' - ELSE CONVERT(NVARCHAR(20), s.last_statistics_update) + - ' have had ' + CONVERT(NVARCHAR(100), s.modification_counter) + - ' modifications in that time, which is ' + - CONVERT(NVARCHAR(100), s.percent_modifications) + - '% of the table.' - END 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.last_statistics_update <= CONVERT(DATETIME, GETDATE() - 7) - AND s.percent_modifications >= 10. - AND s.rows >= 10000 - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 91: Statistics with a low 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 91 AS check_id, - 200 AS Priority, - 'Functioning Statistaholics' AS findings_group, - 'Antisocial Samples', - s.database_name, - '' AS URL, - 'Only ' + CONVERT(NVARCHAR(100), s.percent_sampled) + '% of the rows were sampled during the last statistics update. This may lead to poor cardinality estimates.' 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.rows_sampled < 1. - AND s.rows >= 10000 - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) - 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 ) - SELECT 92 AS check_id, - 200 AS Priority, - 'Functioning Statistaholics' AS findings_group, - 'Cyberphobic Samples', - s.database_name, - '' AS URL, - 'The statistic ' + QUOTENAME(s.statistics_name) + ' is set to not recompute. This can be helpful if data is really skewed, but harmful if you expect automatic statistics updates.' 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.no_recompute = 1 - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) - OPTION ( RECOMPILE ); RAISERROR(N'check_id 93: Statistics with filters', 0,1) WITH NOWAIT; INSERT #BlitzIndexResults ( check_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, @@ -27216,7 +27353,7 @@ BEGIN; 'Functioning Statistaholics' AS findings_group, 'Filter Fixation', s.database_name, - '' AS URL, + 'https://www.brentozar.com/go/stats' AS URL, 'The statistic ' + QUOTENAME(s.statistics_name) + ' is filtered on [' + s.filter_definition + ']. It could be part of a filtered index, or just a filtered statistic. This is purely informational.' 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, @@ -27224,34 +27361,8 @@ BEGIN; 'N/A' AS index_size_summary FROM #Statistics AS s WHERE s.has_filter = 1 - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) OPTION ( RECOMPILE ); - END; - - ---------------------------------------- - --Computed Column Info: Check_id 99-109 - ---------------------------------------- - BEGIN - - RAISERROR(N'check_id 99: Computed Columns 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 ) - SELECT 99 AS check_id, - 50 AS Priority, - 'Cold Calculators' AS findings_group, - 'Serial Forcer' AS finding, - cc.database_name, - '' AS URL, - 'The computed column ' + QUOTENAME(cc.column_name) + ' on ' + QUOTENAME(cc.schema_name) + '.' + QUOTENAME(cc.table_name) + ' is based on ' + cc.definition - + '. That indicates it may reference a scalar function, or a CLR function with data access, which can cause all queries and maintenance to run serially.' AS details, - cc.column_definition, - 'N/A' AS secret_columns, - 'N/A' AS index_usage_summary, - 'N/A' AS index_size_summary - FROM #ComputedColumns AS cc - WHERE cc.is_function = 1 - OPTION ( RECOMPILE ); RAISERROR(N'check_id 100: Computed Columns that are not Persisted.', 0,1) WITH NOWAIT; INSERT #BlitzIndexResults ( check_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, @@ -27271,20 +27382,16 @@ BEGIN; 'N/A' AS index_size_summary FROM #ComputedColumns AS cc WHERE cc.is_persisted = 0 - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) OPTION ( RECOMPILE ); - ---------------------------------------- - --Temporal Table Info: Check_id 110-119 - ---------------------------------------- RAISERROR(N'check_id 110: Temporal Tables.', 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 110 AS check_id, 200 AS Priority, - 'Temporal Tables' AS findings_group, - 'Obsessive Compulsive Tables', + 'Abnormal Psychology' AS findings_group, + 'Temporal Tables', t.database_name, '' AS URL, 'The table ' + QUOTENAME(t.schema_name) + '.' + QUOTENAME(t.table_name) + ' is a temporal table, with rows versioned in ' @@ -27295,33 +27402,39 @@ BEGIN; 'N/A' AS index_usage_summary, 'N/A' AS index_size_summary FROM #TemporalTables AS t - WHERE NOT (@GetAllDatabases = 1 OR @Mode = 0) OPTION ( RECOMPILE ); - ---------------------------------------- - --Check Constraint Info: Check_id 120-129 - ---------------------------------------- - RAISERROR(N'check_id 120: 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 ) - SELECT 99 AS check_id, - 50 AS Priority, - 'Obsessive Constraintive' AS findings_group, - 'Serial Forcer' AS finding, - cc.database_name, - 'https://www.brentozar.com/archive/2016/01/another-reason-why-scalar-functions-in-computed-columns-is-a-bad-idea/' AS URL, - 'The check constraint ' + QUOTENAME(cc.constraint_name) + ' on ' + QUOTENAME(cc.schema_name) + '.' + QUOTENAME(cc.table_name) + ' is based on ' + cc.definition - + '. That indicates it may reference a scalar function, or a CLR function with data access, which can cause all queries and maintenance to run serially.' AS details, - cc.column_definition, - 'N/A' AS secret_columns, - 'N/A' AS index_usage_summary, - 'N/A' AS index_size_summary - FROM #CheckConstraints AS cc - WHERE cc.is_function = 1 - OPTION ( RECOMPILE ); - END; + + END /* IF @Mode = 4 */ + + + + + + + + + + + + + + + + + + + + + + + + + + + RAISERROR(N'Insert a row to help people find help', 0,1) WITH NOWAIT; IF DATEDIFF(MM, @VersionDate, GETDATE()) > 6 @@ -27413,9 +27526,6 @@ BEGIN; br.index_sanity_id=sn.index_sanity_id LEFT JOIN #IndexCreateTsql ts ON br.index_sanity_id=ts.index_sanity_id - WHERE br.check_id IN ( 0, 1, 2, 11, 12, 13, - 22, 34, 43, 47, 48, - 50, 65, 68, 73, 99 ) ORDER BY br.Priority ASC, br.check_id ASC, br.blitz_result_id ASC, br.findings_group ASC OPTION (RECOMPILE); END; @@ -27445,8 +27555,9 @@ BEGIN; OPTION (RECOMPILE); END; - END; /* End @Mode=0 or 4 (diagnose)*/ - ELSE IF (@Mode=1) /*Summarize*/ +END /* End @Mode=0 or 4 (diagnose)*/ + +ELSE IF (@Mode=1) /*Summarize*/ BEGIN --This mode is to give some overall stats on the database. IF(@OutputType <> 'NONE') @@ -28007,23 +28118,46 @@ BEGIN; FROM #IndexSanity AS i --left join here so we don't lose disabled nc indexes LEFT JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id LEFT JOIN #IndexCreateTsql AS ict ON i.index_sanity_id = ict.index_sanity_id - ORDER BY CASE WHEN @SortOrder = N'rows' THEN sz.total_rows - WHEN @SortOrder = N'reserved_mb' THEN sz.total_reserved_MB - WHEN @SortOrder = N'size' THEN sz.total_reserved_MB - WHEN @SortOrder = N'reserved_lob_mb' THEN sz.total_reserved_LOB_MB - WHEN @SortOrder = N'lob' THEN sz.total_reserved_LOB_MB - WHEN @SortOrder = N'total_row_lock_wait_in_ms' THEN COALESCE(sz.total_row_lock_wait_in_ms,0) - WHEN @SortOrder = N'total_page_lock_wait_in_ms' THEN COALESCE(sz.total_page_lock_wait_in_ms,0) - WHEN @SortOrder = N'lock_time' THEN (COALESCE(sz.total_row_lock_wait_in_ms,0) + COALESCE(sz.total_page_lock_wait_in_ms,0)) - WHEN @SortOrder = N'total_reads' THEN total_reads - WHEN @SortOrder = N'reads' THEN total_reads - WHEN @SortOrder = N'user_updates' THEN user_updates - WHEN @SortOrder = N'writes' THEN user_updates - WHEN @SortOrder = N'reads_per_write' THEN reads_per_write - WHEN @SortOrder = N'ratio' THEN reads_per_write - WHEN @SortOrder = N'forward_fetches' THEN sz.total_forwarded_fetch_count - WHEN @SortOrder = N'fetches' THEN sz.total_forwarded_fetch_count - ELSE NULL END DESC, /* Shout out to DHutmacher */ + ORDER BY CASE WHEN @SortDirection = 'desc' THEN + CASE WHEN @SortOrder = N'rows' THEN sz.total_rows + WHEN @SortOrder = N'reserved_mb' THEN sz.total_reserved_MB + WHEN @SortOrder = N'size' THEN sz.total_reserved_MB + WHEN @SortOrder = N'reserved_lob_mb' THEN sz.total_reserved_LOB_MB + WHEN @SortOrder = N'lob' THEN sz.total_reserved_LOB_MB + WHEN @SortOrder = N'total_row_lock_wait_in_ms' THEN COALESCE(sz.total_row_lock_wait_in_ms,0) + WHEN @SortOrder = N'total_page_lock_wait_in_ms' THEN COALESCE(sz.total_page_lock_wait_in_ms,0) + WHEN @SortOrder = N'lock_time' THEN (COALESCE(sz.total_row_lock_wait_in_ms,0) + COALESCE(sz.total_page_lock_wait_in_ms,0)) + WHEN @SortOrder = N'total_reads' THEN total_reads + WHEN @SortOrder = N'reads' THEN total_reads + WHEN @SortOrder = N'user_updates' THEN user_updates + WHEN @SortOrder = N'writes' THEN user_updates + WHEN @SortOrder = N'reads_per_write' THEN reads_per_write + WHEN @SortOrder = N'ratio' THEN reads_per_write + WHEN @SortOrder = N'forward_fetches' THEN sz.total_forwarded_fetch_count + WHEN @SortOrder = N'fetches' THEN sz.total_forwarded_fetch_count + ELSE NULL END + ELSE 1 END + DESC, /* Shout out to DHutmacher */ + CASE WHEN @SortDirection = 'asc' THEN + CASE WHEN @SortOrder = N'rows' THEN sz.total_rows + WHEN @SortOrder = N'reserved_mb' THEN sz.total_reserved_MB + WHEN @SortOrder = N'size' THEN sz.total_reserved_MB + WHEN @SortOrder = N'reserved_lob_mb' THEN sz.total_reserved_LOB_MB + WHEN @SortOrder = N'lob' THEN sz.total_reserved_LOB_MB + WHEN @SortOrder = N'total_row_lock_wait_in_ms' THEN COALESCE(sz.total_row_lock_wait_in_ms,0) + WHEN @SortOrder = N'total_page_lock_wait_in_ms' THEN COALESCE(sz.total_page_lock_wait_in_ms,0) + WHEN @SortOrder = N'lock_time' THEN (COALESCE(sz.total_row_lock_wait_in_ms,0) + COALESCE(sz.total_page_lock_wait_in_ms,0)) + WHEN @SortOrder = N'total_reads' THEN total_reads + WHEN @SortOrder = N'reads' THEN total_reads + WHEN @SortOrder = N'user_updates' THEN user_updates + WHEN @SortOrder = N'writes' THEN user_updates + WHEN @SortOrder = N'reads_per_write' THEN reads_per_write + WHEN @SortOrder = N'ratio' THEN reads_per_write + WHEN @SortOrder = N'forward_fetches' THEN sz.total_forwarded_fetch_count + WHEN @SortOrder = N'fetches' THEN sz.total_forwarded_fetch_count + ELSE NULL END + ELSE 1 END + ASC, i.[database_name], [Schema Name], [Object Name], [Index ID] OPTION (RECOMPILE); END; @@ -28060,7 +28194,8 @@ BEGIN; mi.create_tsql AS [Create TSQL], mi.more_info AS [More Info], 1 AS [Display Order], - mi.is_low + mi.is_low, + mi.sample_query_plan AS [Sample Query Plan] FROM #MissingIndexes AS mi LEFT JOIN create_date AS cd ON mi.[object_id] = cd.object_id @@ -28077,7 +28212,7 @@ BEGIN; 100000000000, @DaysUptimeInsertValue, NULL,NULL,NULL,NULL,NULL,NULL,NULL, - NULL, NULL, NULL, NULL, 0 AS [Display Order], NULL AS is_low + NULL, NULL, NULL, NULL, 0 AS [Display Order], NULL AS is_low, NULL ORDER BY [Display Order] ASC, [Magic Benefit Number] DESC OPTION (RECOMPILE); END; @@ -28094,7 +28229,7 @@ BEGIN; END; /* End @Mode=3 (index detail)*/ -END; + END TRY BEGIN CATCH @@ -28147,7 +28282,7 @@ BEGIN SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '2.999', @VersionDate = '20201011'; +SELECT @Version = '2.9999', @VersionDate = '20201114'; IF(@VersionCheckMode = 1) @@ -28435,12 +28570,12 @@ You need to use an Azure storage account, and the path has to look like this: ht ORDER BY xml.deadlock_xml.value('(/event/@timestamp)[1]', 'datetime') DESC OPTION ( RECOMPILE ); - /*Parse process and input buffer XML*/ SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); RAISERROR('Parse process and input buffer XML %s', 0, 1, @d) WITH NOWAIT; SELECT q.event_date, q.victim_id, + CONVERT(BIT, q.is_parallel) AS is_parallel, q.deadlock_graph, q.id, q.database_id, @@ -28464,6 +28599,7 @@ You need to use an Azure storage account, and the path has to look like this: ht FROM ( SELECT dd.deadlock_xml, CONVERT(DATETIME2(7), SWITCHOFFSET(CONVERT(datetimeoffset, dd.event_date ), DATENAME(TzOffset, SYSDATETIMEOFFSET()))) AS event_date, dd.victim_id, + dd.is_parallel, dd.deadlock_graph, ca.dp.value('@id', 'NVARCHAR(256)') AS id, ca.dp.value('@currentdb', 'BIGINT') AS database_id, @@ -28485,6 +28621,7 @@ You need to use an Azure storage account, and the path has to look like this: ht FROM ( SELECT d1.deadlock_xml, d1.deadlock_xml.value('(event/@timestamp)[1]', 'DATETIME2') AS event_date, d1.deadlock_xml.value('(//deadlock/victim-list/victimProcess/@id)[1]', 'NVARCHAR(256)') AS victim_id, + d1.deadlock_xml.exist('//deadlock/resource-list/exchangeEvent') AS is_parallel, d1.deadlock_xml.query('/event/data/value/deadlock') AS deadlock_graph FROM #deadlock_data AS d1 ) AS dd CROSS APPLY dd.deadlock_xml.nodes('//deadlock/process-list/process') AS ca(dp) @@ -28708,13 +28845,13 @@ You need to use an Azure storage account, and the path has to look like this: ht ca.spilling, ca.waiting_to_close, w.l.value('@id', 'NVARCHAR(256)') AS waiter_id, - o.l.value('@id', 'NVARCHAR(256)') AS owner_id + o.l.value('@id', 'NVARCHAR(256)') AS owner_id INTO #deadlock_resource_parallel FROM ( SELECT dr.event_date, ca.dr.value('@id', 'NVARCHAR(256)') AS id, - ca.dr.value('@WaitType', 'NVARCHAR(256)') AS wait_type, - ca.dr.value('@nodeId', 'BIGINT') AS node_id, + ca.dr.value('@WaitType', 'NVARCHAR(256)') AS wait_type, + ca.dr.value('@nodeId', 'BIGINT') AS node_id, /* These columns are in 2017 CU5 ONLY */ ca.dr.value('@waiterType', 'NVARCHAR(256)') AS waiter_type, ca.dr.value('@ownerActivity', 'NVARCHAR(256)') AS owner_activity, @@ -29313,6 +29450,22 @@ You need to use an Azure storage account, and the path has to look like this: ht GROUP BY DB_NAME(aj.database_id), aj.job_name, aj.step_name OPTION ( RECOMPILE ); + /*Check 13 is total parallel deadlocks*/ + SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + RAISERROR('Check 13 %s', 0, 1, @d) WITH NOWAIT; + INSERT #deadlock_findings WITH (TABLOCKX) + ( check_id, database_name, object_name, finding_group, finding ) + SELECT 13 AS check_id, + N'-' AS database_name, + '-' AS object_name, + 'Total parallel deadlocks' AS finding_group, + 'There have been ' + + CONVERT(NVARCHAR(20), COUNT_BIG(DISTINCT drp.event_date)) + + ' parallel deadlocks.' + FROM #deadlock_resource_parallel AS drp + WHERE 1 = 1 + OPTION ( RECOMPILE ); + /*Thank you goodnight*/ INSERT #deadlock_findings WITH (TABLOCKX) ( check_id, database_name, object_name, finding_group, finding ) @@ -29389,6 +29542,7 @@ You need to use an Azure storage account, and the path has to look like this: ht dp.deadlock_graph FROM #deadlock_process AS dp WHERE dp.victim_id IS NOT NULL + AND dp.is_parallel = 0 UNION ALL @@ -29400,7 +29554,20 @@ You need to use an Azure storage account, and the path has to look like this: ht dp.priority, dp.log_used, dp.wait_resource COLLATE DATABASE_DEFAULT, - CONVERT(XML, N'parallel_deadlock' COLLATE DATABASE_DEFAULT) AS object_names, + CASE WHEN @ExportToExcel = 0 THEN + CONVERT( + XML, + STUFF(( SELECT DISTINCT NCHAR(10) + + N' ' + + ISNULL(c.object_name, N'') + + N' ' COLLATE DATABASE_DEFAULT AS object_name + FROM #deadlock_owner_waiter AS c + WHERE ( dp.id = c.owner_id + OR dp.victim_id = c.waiter_id ) + AND CONVERT(DATE, dp.event_date) = CONVERT(DATE, c.event_date) + FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(4000)'), + 1, 1, N'')) + ELSE NULL END AS object_names, dp.wait_time, dp.transaction_name, dp.last_tran_started, @@ -29435,8 +29602,7 @@ You need to use an Azure storage account, and the path has to look like this: ht FROM #deadlock_process AS dp CROSS APPLY (SELECT TOP 1 * FROM #deadlock_resource_parallel AS drp WHERE drp.owner_id = dp.id AND drp.wait_type = 'e_waitPipeNewRow' ORDER BY drp.event_date) AS cao CROSS APPLY (SELECT TOP 1 * FROM #deadlock_resource_parallel AS drp WHERE drp.owner_id = dp.id AND drp.wait_type = 'e_waitPipeGetRow' ORDER BY drp.event_date) AS caw - WHERE dp.victim_id IS NULL - AND dp.login_name IS NOT NULL) + WHERE dp.is_parallel = 1 ) insert into DeadLockTbl ( ServerName, deadlock_type, @@ -29603,7 +29769,8 @@ ELSE --Output to database is not set output to client app dp.deadlock_graph FROM #deadlock_process AS dp WHERE dp.victim_id IS NOT NULL - + AND dp.is_parallel = 0 + UNION ALL SELECT N'Parallel Deadlock' AS deadlock_type, @@ -29614,7 +29781,20 @@ ELSE --Output to database is not set output to client app dp.priority, dp.log_used, dp.wait_resource COLLATE DATABASE_DEFAULT, - CASE WHEN @ExportToExcel = 0 THEN CONVERT(XML, N'parallel_deadlock' COLLATE DATABASE_DEFAULT) ELSE 'parallel_deadlock' END AS object_names, + CASE WHEN @ExportToExcel = 0 THEN + CONVERT( + XML, + STUFF(( SELECT DISTINCT NCHAR(10) + + N' ' + + ISNULL(c.object_name, N'') + + N' ' COLLATE DATABASE_DEFAULT AS object_name + FROM #deadlock_owner_waiter AS c + WHERE ( dp.id = c.owner_id + OR dp.victim_id = c.waiter_id ) + AND CONVERT(DATE, dp.event_date) = CONVERT(DATE, c.event_date) + FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(4000)'), + 1, 1, N'')) + ELSE NULL END AS object_names, dp.wait_time, dp.transaction_name, dp.last_tran_started, @@ -29647,10 +29827,9 @@ ELSE --Output to database is not set output to client app caw.waiting_to_close AS waiter_waiting_to_close, dp.deadlock_graph FROM #deadlock_process AS dp - OUTER APPLY (SELECT TOP 1 * FROM #deadlock_resource_parallel AS drp WHERE drp.owner_id = dp.id AND drp.wait_type = 'e_waitPipeNewRow' ORDER BY drp.event_date) AS cao - OUTER APPLY (SELECT TOP 1 * FROM #deadlock_resource_parallel AS drp WHERE drp.owner_id = dp.id AND drp.wait_type = 'e_waitPipeGetRow' ORDER BY drp.event_date) AS caw - WHERE dp.victim_id IS NULL - AND dp.login_name IS NOT NULL + OUTER APPLY (SELECT TOP 1 * FROM #deadlock_resource_parallel AS drp WHERE drp.owner_id = dp.id AND drp.wait_type IN ('e_waitPortOpen', 'e_waitPipeNewRow') ORDER BY drp.event_date) AS cao + OUTER APPLY (SELECT TOP 1 * FROM #deadlock_resource_parallel AS drp WHERE drp.owner_id = dp.id AND drp.wait_type IN ('e_waitPortOpen', 'e_waitPipeGetRow') ORDER BY drp.event_date) AS caw + WHERE dp.is_parallel = 1 ) SELECT d.deadlock_type, d.event_date, @@ -29782,7 +29961,7 @@ BEGIN SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '7.999', @VersionDate = '20201011'; + SELECT @Version = '7.9999', @VersionDate = '20201114'; IF(@VersionCheckMode = 1) BEGIN @@ -30278,6 +30457,21 @@ IF @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @Output END + IF OBJECT_ID('tempdb..#WhoReadableDBs') IS NOT NULL + DROP TABLE #WhoReadableDBs; + +CREATE TABLE #WhoReadableDBs +( +database_id INT +); + +IF EXISTS (SELECT * FROM sys.all_objects o WHERE o.name = 'dm_hadr_database_replica_states') +BEGIN + RAISERROR('Checking for Read intent databases to exclude',0,0) WITH NOWAIT; + + EXEC('INSERT INTO #WhoReadableDBs (database_id) SELECT DBs.database_id FROM sys.databases DBs INNER JOIN sys.availability_replicas Replicas ON DBs.replica_id = Replicas.replica_id WHERE replica_server_name NOT IN (SELECT DISTINCT primary_replica FROM sys.dm_hadr_availability_group_states States) AND Replicas.secondary_role_allow_connections_desc = ''READ_ONLY'' AND replica_server_name = @@SERVERNAME;'); +END + SELECT @BlockingCheck = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; DECLARE @blocked TABLE @@ -30308,7 +30502,7 @@ BEGIN /* Think of the StringToExecute as starting with this, but we'll set this up later depending on whether we're doing an insert or a select: SELECT @StringToExecute = N'SELECT GETDATE() AS run_date , */ - SET @StringToExecute = N'COALESCE( CONVERT(VARCHAR(20), (ABS(r.total_elapsed_time) / 1000) / 86400) + '':'' + CONVERT(VARCHAR(20), (DATEADD(SECOND, (r.total_elapsed_time / 1000), 0) + DATEADD(MILLISECOND, (r.total_elapsed_time % 1000), 0)), 114), CONVERT(VARCHAR(20), DATEDIFF(SECOND, s.last_request_start_time, GETDATE()) / 86400) + '':'' + CONVERT(VARCHAR(20), DATEADD(SECOND, DATEDIFF(SECOND, s.last_request_start_time, GETDATE()), 0), 114) ) AS [elapsed_time] , + SET @StringToExecute = N'COALESCE( RIGHT(''00'' + CONVERT(VARCHAR(20), (ABS(r.total_elapsed_time) / 1000) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), (DATEADD(SECOND, (r.total_elapsed_time / 1000), 0) + DATEADD(MILLISECOND, (r.total_elapsed_time % 1000), 0)), 114), CONVERT(VARCHAR(20), DATEDIFF(SECOND, s.last_request_start_time, GETDATE()) / 86400) + '':'' + CONVERT(VARCHAR(20), DATEADD(SECOND, DATEDIFF(SECOND, s.last_request_start_time, GETDATE()), 0), 114) ) AS [elapsed_time] , s.session_id , COALESCE(DB_NAME(r.database_id), DB_NAME(blocked.dbid), ''N/A'') AS database_name, ISNULL(SUBSTRING(dest.text, @@ -30513,7 +30707,7 @@ IF @ProductVersionMajor >= 11 /* Think of the StringToExecute as starting with this, but we'll set this up later depending on whether we're doing an insert or a select: SELECT @StringToExecute = N'SELECT GETDATE() AS run_date , */ - SELECT @StringToExecute = N'COALESCE( CONVERT(VARCHAR(20), (ABS(r.total_elapsed_time) / 1000) / 86400) + '':'' + CONVERT(VARCHAR(20), (DATEADD(SECOND, (r.total_elapsed_time / 1000), 0) + DATEADD(MILLISECOND, (r.total_elapsed_time % 1000), 0)), 114), CONVERT(VARCHAR(20), DATEDIFF(SECOND, s.last_request_start_time, GETDATE()) / 86400) + '':'' + CONVERT(VARCHAR(20), DATEADD(SECOND, DATEDIFF(SECOND, s.last_request_start_time, GETDATE()), 0), 114) ) AS [elapsed_time] , + SELECT @StringToExecute = N'COALESCE( RIGHT(''00'' + CONVERT(VARCHAR(20), (ABS(r.total_elapsed_time) / 1000) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), (DATEADD(SECOND, (r.total_elapsed_time / 1000), 0) + DATEADD(MILLISECOND, (r.total_elapsed_time % 1000), 0)), 114), CONVERT(VARCHAR(20), DATEDIFF(SECOND, s.last_request_start_time, GETDATE()) / 86400) + '':'' + CONVERT(VARCHAR(20), DATEADD(SECOND, DATEDIFF(SECOND, s.last_request_start_time, GETDATE()), 0), 114) ) AS [elapsed_time] , s.session_id , COALESCE(DB_NAME(r.database_id), DB_NAME(blocked.dbid), ''N/A'') AS database_name, ISNULL(SUBSTRING(dest.text, @@ -30752,6 +30946,7 @@ IF @ProductVersionMajor >= 11 N' WHERE s.session_id <> @@SPID AND s.host_name IS NOT NULL + AND r.database_id NOT IN (SELECT database_id FROM #WhoReadableDBs) ' + CASE WHEN @ShowSleepingSPIDs = 0 THEN N' AND COALESCE(DB_NAME(r.database_id), DB_NAME(blocked.dbid)) IS NOT NULL' diff --git a/Install-Core-Blitz-With-Query-Store.sql b/Install-Core-Blitz-With-Query-Store.sql index c2019d059..800acbba4 100755 --- a/Install-Core-Blitz-With-Query-Store.sql +++ b/Install-Core-Blitz-With-Query-Store.sql @@ -37,7 +37,7 @@ AS SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '7.999', @VersionDate = '20201011'; + SELECT @Version = '7.9999', @VersionDate = '20201114'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -1030,6 +1030,64 @@ AS -7, GETDATE()) ); END; + /* + CheckID #256 is searching for backups to NUL. + */ + + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 256 ) + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 2) WITH NOWAIT; + + INSERT INTO #BlitzResults + ( CheckID , + DatabaseName , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT DISTINCT + 256 AS CheckID , + d.name AS DatabaseName, + 1 AS Priority , + 'Backup' AS FindingsGroup , + 'Log Backups to NUL' AS Finding , + 'https://www.brentozar.com/go/nul' AS URL , + N'The transaction log file has been backed up ' + CAST((SELECT count(*) + FROM msdb.dbo.backupset AS b INNER JOIN + msdb.dbo.backupmediafamily AS bmf + ON b.media_set_id = bmf.media_set_id + WHERE b.database_name COLLATE SQL_Latin1_General_CP1_CI_AS = d.name COLLATE SQL_Latin1_General_CP1_CI_AS + AND bmf.physical_device_name = 'NUL' + AND b.type = 'L' + AND b.backup_finish_date >= DATEADD(dd, + -7, GETDATE())) AS NVARCHAR(8)) + ' time(s) to ''NUL'' in the last week, which means the backup does not exist. This breaks point-in-time recovery.' AS Details + FROM master.sys.databases AS d + WHERE d.recovery_model IN ( 1, 2 ) + AND d.database_id NOT IN ( 2, 3 ) + AND d.source_database_id IS NULL + AND d.state NOT IN(1, 6, 10) /* Not currently offline or restoring, like log shipping databases */ + AND d.is_in_standby = 0 /* Not a log shipping target database */ + AND d.source_database_id IS NULL /* Excludes database snapshots */ + --AND d.name NOT IN ( SELECT DISTINCT + -- DatabaseName + -- FROM #SkipChecks + -- WHERE CheckID IS NULL OR CheckID = 2) + AND EXISTS ( SELECT * + FROM msdb.dbo.backupset AS b INNER JOIN + msdb.dbo.backupmediafamily AS bmf + ON b.media_set_id = bmf.media_set_id + WHERE d.name COLLATE SQL_Latin1_General_CP1_CI_AS = b.database_name COLLATE SQL_Latin1_General_CP1_CI_AS + AND bmf.physical_device_name = 'NUL' + AND b.type = 'L' + AND b.backup_finish_date >= DATEADD(dd, + -7, GETDATE()) ); + END; + /* Next up, we've got CheckID 8. (These don't have to go in order.) This one won't work on SQL Server 2005 because it relies on a new DMV that didn't @@ -4396,7 +4454,8 @@ IF @ProductVersionMajor >= 10 [sys].[dm_server_services] WHERE [service_account] LIKE 'NT Service%' AND [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%'; END; END; @@ -9401,7 +9460,7 @@ AS SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '3.999', @VersionDate = '20201011'; + SELECT @Version = '3.9999', @VersionDate = '20201114'; IF(@VersionCheckMode = 1) BEGIN @@ -11180,7 +11239,7 @@ BEGIN SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '7.999', @VersionDate = '20201011'; +SELECT @Version = '7.9999', @VersionDate = '20201114'; IF(@VersionCheckMode = 1) @@ -11253,7 +11312,7 @@ BEGIN UNION ALL SELECT N'@SortOrder', N'VARCHAR(10)', - N'Data processing and display order. @SortOrder will still be used, even when preparing output for a table or for excel. Possible values are: "CPU", "Reads", "Writes", "Duration", "Executions", "Recent Compilations", "Memory Grant", "Spills", "Query Hash". Additionally, the word "Average" or "Avg" can be used to sort on averages rather than total. "Executions per minute" and "Executions / minute" can be used to sort by execution per minute. For the truly lazy, "xpm" can also be used. Note that when you use all or all avg, the only parameters you can use are @Top and @DatabaseName. All others will be ignored.' + N'Data processing and display order. @SortOrder will still be used, even when preparing output for a table or for excel. Possible values are: "CPU", "Reads", "Writes", "Duration", "Executions", "Recent Compilations", "Memory Grant", "Unused Grant", "Spills", "Query Hash". Additionally, the word "Average" or "Avg" can be used to sort on averages rather than total. "Executions per minute" and "Executions / minute" can be used to sort by execution per minute. For the truly lazy, "xpm" can also be used. Note that when you use all or all avg, the only parameters you can use are @Top and @DatabaseName. All others will be ignored.' UNION ALL SELECT N'@UseTriggersAnyway', @@ -11694,7 +11753,7 @@ IF @SortOrder LIKE 'query hash%' /* Set @Top based on sort */ IF ( @Top IS NULL - AND LOWER(@SortOrder) IN ( 'all', 'all sort' ) + AND @SortOrder IN ( 'all', 'all sort' ) ) BEGIN SET @Top = 5; @@ -11702,7 +11761,7 @@ IF ( IF ( @Top IS NULL - AND LOWER(@SortOrder) NOT IN ( 'all', 'all sort' ) + AND @SortOrder NOT IN ( 'all', 'all sort' ) ) BEGIN SET @Top = 10; @@ -12005,6 +12064,7 @@ SET @SortOrder = CASE WHEN @SortOrder IN ('avg write') THEN 'avg writes' WHEN @SortOrder IN ('memory grants') THEN 'memory grant' WHEN @SortOrder IN ('avg memory grants') THEN 'avg memory grant' + WHEN @SortOrder IN ('unused grants','unused memory', 'unused memory grant', 'unused memory grants') THEN 'unused grant' WHEN @SortOrder IN ('spill') THEN 'spills' WHEN @SortOrder IN ('avg spill') THEN 'avg spills' WHEN @SortOrder IN ('execution') THEN 'executions' @@ -12013,7 +12073,7 @@ SET @SortOrder = CASE RAISERROR(N'Checking sort order', 0, 1) WITH NOWAIT; IF @SortOrder NOT IN ('cpu', 'avg cpu', 'reads', 'avg reads', 'writes', 'avg writes', 'duration', 'avg duration', 'executions', 'avg executions', - 'compiles', 'memory grant', 'avg memory grant', + 'compiles', 'memory grant', 'avg memory grant', 'unused grant', 'spills', 'avg spills', 'all', 'all avg', 'sp_BlitzIndex', 'query hash') BEGIN @@ -12920,6 +12980,7 @@ SELECT @body += N' ORDER BY ' + WHEN N'executions' THEN N'execution_count' WHEN N'compiles' THEN N'cached_time' WHEN N'memory grant' THEN N'max_grant_kb' + WHEN N'unused grant' THEN N'max_grant_kb - max_used_grant_kb' WHEN N'spills' THEN N'max_spills' /* And now the averages */ WHEN N'avg cpu' THEN N'total_worker_time / execution_count' @@ -13236,6 +13297,7 @@ BEGIN WHEN N'executions' THEN N'AND execution_count > 0' WHEN N'compiles' THEN N'AND (age_minutes + age_minutes_lifetime) > 0' WHEN N'memory grant' THEN N'AND max_grant_kb > 0' + WHEN N'unused grant' THEN N'AND max_grant_kb > 0' WHEN N'spills' THEN N'AND max_spills > 0' /* And now the averages */ WHEN N'avg cpu' THEN N'AND (total_worker_time / execution_count) > 0' @@ -13267,7 +13329,7 @@ END; IF (@QueryFilter = 'all' AND (SELECT COUNT(*) FROM #only_query_hashes) = 0 AND (SELECT COUNT(*) FROM #ignore_query_hashes) = 0) - AND (@SortOrder NOT IN ('memory grant', 'avg memory grant')) + AND (@SortOrder NOT IN ('memory grant', 'avg memory grant', 'unused grant')) OR (LEFT(@QueryFilter, 3) = 'pro') BEGIN SET @sql += @insert_list; @@ -13288,7 +13350,7 @@ IF (@v >= 13 AND @QueryFilter = 'all' AND (SELECT COUNT(*) FROM #only_query_hashes) = 0 AND (SELECT COUNT(*) FROM #ignore_query_hashes) = 0) - AND (@SortOrder NOT IN ('memory grant', 'avg memory grant')) + AND (@SortOrder NOT IN ('memory grant', 'avg memory grant', 'unused grant')) AND (@SortOrder NOT IN ('spills', 'avg spills')) OR (LEFT(@QueryFilter, 3) = 'fun') BEGIN @@ -13331,7 +13393,7 @@ IF (@UseTriggersAnyway = 1 OR @v >= 11) AND (SELECT COUNT(*) FROM #only_query_hashes) = 0 AND (SELECT COUNT(*) FROM #ignore_query_hashes) = 0 AND (@QueryFilter = 'all') - AND (@SortOrder NOT IN ('memory grant', 'avg memory grant')) + AND (@SortOrder NOT IN ('memory grant', 'avg memory grant', 'unused grant')) BEGIN RAISERROR (N'Adding SQL to collect trigger stats.',0,1) WITH NOWAIT; @@ -13361,6 +13423,7 @@ SELECT @sort = CASE @SortOrder WHEN N'cpu' THEN N'total_worker_time' WHEN N'executions' THEN N'execution_count' WHEN N'compiles' THEN N'cached_time' WHEN N'memory grant' THEN N'max_grant_kb' + WHEN N'unused grant' THEN N'max_grant_kb - max_used_grant_kb' WHEN N'spills' THEN N'max_spills' /* And now the averages */ WHEN N'avg cpu' THEN N'total_worker_time / execution_count' @@ -13421,6 +13484,7 @@ SELECT @sort = CASE @SortOrder WHEN N'cpu' THEN N'TotalCPU' WHEN N'executions' THEN N'ExecutionCount' WHEN N'compiles' THEN N'PlanCreationTime' WHEN N'memory grant' THEN N'MaxGrantKB' + WHEN N'unused grant' THEN N'MaxGrantKB - MaxUsedGrantKB' WHEN N'spills' THEN N'MaxSpills' /* And now the averages */ WHEN N'avg cpu' THEN N'TotalCPU / ExecutionCount' @@ -13707,12 +13771,15 @@ SET NumberOfDistinctPlans = distinct_plan_count, FROM ( SELECT COUNT(DISTINCT QueryHash) AS distinct_plan_count, COUNT(QueryHash) AS number_of_plans, - QueryHash + QueryHash, + DatabaseName FROM ##BlitzCacheProcs WHERE SPID = @@SPID - GROUP BY QueryHash + GROUP BY QueryHash, + DatabaseName ) AS x WHERE ##BlitzCacheProcs.QueryHash = x.QueryHash +AND ##BlitzCacheProcs.DatabaseName = x.DatabaseName OPTION (RECOMPILE) ; -- query level checks @@ -14561,22 +14628,7 @@ END; /* END Testing using XML nodes to speed up processing */ -RAISERROR(N'Gathering additional plan level information', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE ##BlitzCacheProcs -SET NumberOfDistinctPlans = distinct_plan_count, - NumberOfPlans = number_of_plans, - plan_multiple_plans = CASE WHEN distinct_plan_count < number_of_plans THEN number_of_plans END -FROM ( -SELECT COUNT(DISTINCT QueryHash) AS distinct_plan_count, - COUNT(QueryHash) AS number_of_plans, - QueryHash -FROM ##BlitzCacheProcs -WHERE SPID = @@SPID -GROUP BY QueryHash -) AS x -WHERE ##BlitzCacheProcs.QueryHash = x.QueryHash -OPTION (RECOMPILE); + /* Update to grab stored procedure name for individual statements */ RAISERROR(N'Attempting to get stored procedure name for individual statements', 0, 1) WITH NOWAIT; @@ -15760,6 +15812,7 @@ BEGIN WHEN N'executions' THEN N' ExecutionCount ' WHEN N'compiles' THEN N' PlanCreationTime ' WHEN N'memory grant' THEN N' MaxGrantKB' + WHEN N'unused grant' THEN N' MaxGrantKB - MaxUsedGrantKB' WHEN N'spills' THEN N' MaxSpills' WHEN N'avg cpu' THEN N' AverageCPU' WHEN N'avg reads' THEN N' AverageReads' @@ -16009,6 +16062,7 @@ SELECT @sql += N' ORDER BY ' + CASE @SortOrder WHEN N'cpu' THEN N' TotalCPU ' WHEN N'executions' THEN N' ExecutionCount ' WHEN N'compiles' THEN N' PlanCreationTime ' WHEN N'memory grant' THEN N' MaxGrantKB' + WHEN N'unused grant' THEN N' MaxGrantKB - MaxUsedGrantKB ' WHEN N'spills' THEN N' MaxSpills' WHEN N'avg cpu' THEN N' AverageCPU' WHEN N'avg reads' THEN N' AverageReads' @@ -17385,7 +17439,7 @@ IF OBJECT_ID('tempdb.. #bou_allsort') IS NULL END; -IF LOWER(@SortOrder) = 'all' +IF @SortOrder = 'all' BEGIN RAISERROR('Beginning for ALL', 0, 1) WITH NOWAIT; SET @AllSortSql += N' @@ -17560,7 +17614,7 @@ SET @AllSortSql += N' END; -IF LOWER(@SortOrder) = 'all avg' +IF @SortOrder = 'all avg' BEGIN RAISERROR('Beginning for ALL AVG', 0, 1) WITH NOWAIT; SET @AllSortSql += N' @@ -17867,7 +17921,7 @@ ELSE PercentExecutionsByType money, ExecutionsPerMinute money, PlanCreationTime datetime,' + N' - PlanCreationTimeHours AS DATEDIFF(HOUR, PlanCreationTime, SYSDATETIME()), + PlanCreationTimeHours AS DATEDIFF(HOUR,CONVERT(DATETIMEOFFSET(7),[PlanCreationTime]),[CheckDate]), LastExecutionTime datetime, LastCompletionTime datetime, PlanHandle varbinary(64), @@ -17913,7 +17967,28 @@ ELSE AvgSpills MONEY, QueryPlanCost FLOAT, JoinKey AS ServerName + Cast(CheckDate AS NVARCHAR(50)), - CONSTRAINT [PK_' + REPLACE(REPLACE(@OutputTableName,'[',''),']','') + '] PRIMARY KEY CLUSTERED(ID ASC));'; + CONSTRAINT [PK_' + REPLACE(REPLACE(@OutputTableName,'[',''),']','') + '] PRIMARY KEY CLUSTERED(ID ASC)); + + IF EXISTS(SELECT * FROM ' + +@OutputDatabaseName + +N'.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' + +@OutputSchemaName + +''') AND EXISTS (SELECT * FROM ' + +@OutputDatabaseName+ + N'.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''' + +@OutputSchemaName + +''' AND QUOTENAME(TABLE_NAME) = ''' + +@OutputTableName + +''') AND EXISTS (SELECT * FROM ' + +@OutputDatabaseName+ + N'.sys.computed_columns WHERE [name] = N''PlanCreationTimeHours'' AND QUOTENAME(OBJECT_NAME(object_id)) = N''' + +@OutputTableName + +''' AND [definition] = N''(datediff(hour,[PlanCreationTime],sysdatetime()))'') +BEGIN + RAISERROR(''We noticed that you are running an old computed column definition for PlanCreationTimeHours, fixing that now'',0,0) WITH NOWAIT; + ALTER TABLE '+@OutputDatabaseName+'.'+@OutputSchemaName+'.'+@OutputTableName+' DROP COLUMN [PlanCreationTimeHours]; + ALTER TABLE '+@OutputDatabaseName+'.'+@OutputSchemaName+'.'+@OutputTableName+' ADD [PlanCreationTimeHours] AS DATEDIFF(HOUR,CONVERT(DATETIMEOFFSET(7),[PlanCreationTime]),[CheckDate]); +END '; IF @ValidOutputServer = 1 BEGIN @@ -18344,7 +18419,7 @@ BEGIN SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '7.999', @VersionDate = '20201011'; +SELECT @Version = '7.9999', @VersionDate = '20201114'; IF(@VersionCheckMode = 1) BEGIN @@ -19620,12 +19695,12 @@ BEGIN CASE @Seconds WHEN 0 THEN 0 ELSE SUM(os.waiting_tasks_count) OVER (PARTITION BY os.wait_type) END AS sum_waiting_tasks FROM sys.dm_os_wait_stats os ) x - WHERE EXISTS + WHERE NOT EXISTS ( - SELECT 1/0 + SELECT * FROM ##WaitCategories AS wc WHERE wc.WaitType = x.wait_type - AND wc.Ignorable = 0 + AND wc.Ignorable = 1 ) GROUP BY x.Pass, x.SampleTime, x.wait_type ORDER BY sum_wait_time_ms DESC; @@ -20575,12 +20650,12 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, SUM(os.waiting_tasks_count) OVER (PARTITION BY os.wait_type) AS sum_waiting_tasks FROM sys.dm_os_wait_stats os ) x - WHERE EXISTS + WHERE NOT EXISTS ( - SELECT 1/0 + SELECT * FROM ##WaitCategories AS wc WHERE wc.WaitType = x.wait_type - AND wc.Ignorable = 0 + AND wc.Ignorable = 1 ) GROUP BY x.Pass, x.SampleTime, x.wait_type ORDER BY sum_wait_time_ms DESC; @@ -21457,7 +21532,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, + @OutputSchemaName + '.' + @OutputTableName + ' (ServerName, CheckDate, CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, QueryText, StartTime, LoginName, NTUserName, OriginalLoginName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, DetailsInt, QueryHash) SELECT ' - + ' @SrvName, @CheckDate, CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, QueryText, StartTime, LoginName, NTUserName, OriginalLoginName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, DetailsInt, QueryHash FROM #BlitzFirstResults ORDER BY Priority , FindingsGroup , Finding , Details'; + + ' @SrvName, @CheckDate, CheckID, Priority, FindingsGroup, Finding, URL, LEFT(Details,4000), HowToStopIt, QueryPlan, QueryText, StartTime, LoginName, NTUserName, OriginalLoginName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, DetailsInt, QueryHash FROM #BlitzFirstResults ORDER BY Priority , FindingsGroup , Finding , Details'; EXEC sp_executesql @StringToExecute, N'@SrvName NVARCHAR(128), @CheckDate datetimeoffset', @@ -21512,7 +21587,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, + ' INSERT ' + @OutputTableName + ' (ServerName, CheckDate, CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, QueryText, StartTime, LoginName, NTUserName, OriginalLoginName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, DetailsInt) SELECT ' - + ' @SrvName, @CheckDate, CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, QueryText, StartTime, LoginName, NTUserName, OriginalLoginName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, DetailsInt FROM #BlitzFirstResults ORDER BY Priority , FindingsGroup , Finding , Details'; + + ' @SrvName, @CheckDate, CheckID, Priority, FindingsGroup, Finding, URL, LEFT(Details,4000), HowToStopIt, QueryPlan, QueryText, StartTime, LoginName, NTUserName, OriginalLoginName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, DetailsInt FROM #BlitzFirstResults ORDER BY Priority , FindingsGroup , Finding , Details'; EXEC sp_executesql @StringToExecute, N'@SrvName NVARCHAR(128), @CheckDate datetimeoffset', @@ -22675,6 +22750,7 @@ ALTER PROCEDURE dbo.sp_BlitzIndex @IncludeInactiveIndexes BIT = 0 /* Will skip indexes with no reads or writes */, @ShowAllMissingIndexRequests BIT = 0 /*Will make all missing index requests show up*/, @SortOrder NVARCHAR(50) = NULL, /* Only affects @Mode = 2. */ + @SortDirection NVARCHAR(4) = 'DESC', /* Only affects @Mode = 2. */ @Help TINYINT = 0, @Debug BIT = 0, @Version VARCHAR(30) = NULL OUTPUT, @@ -22685,7 +22761,7 @@ AS SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '7.999', @VersionDate = '20201011'; +SELECT @Version = '7.9999', @VersionDate = '20201114'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -22764,6 +22840,7 @@ DECLARE @ColumnList NVARCHAR(MAX); /* Let's get @SortOrder set to lower case here for comparisons later */ SET @SortOrder = REPLACE(LOWER(@SortOrder), N' ', N'_'); +SET @SortDirection = LOWER(@SortDirection); SET @LineFeed = CHAR(13) + CHAR(10); SELECT @SQLServerProductVersion = CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)); @@ -22958,25 +23035,31 @@ IF OBJECT_ID('tempdb..#Ignore_Databases') IS NOT NULL [reads_per_write] AS CAST(CASE WHEN user_updates > 0 THEN ( user_seeks + user_scans + user_lookups ) / (1.0 * user_updates) ELSE 0 END AS MONEY) , - [index_usage_summary] AS N'Reads: ' + - REPLACE(CONVERT(NVARCHAR(30),CAST((user_seeks + user_scans + user_lookups) AS MONEY), 1), N'.00', N'') - + CASE WHEN user_seeks + user_scans + user_lookups > 0 THEN - N' (' - + RTRIM( - CASE WHEN user_seeks > 0 THEN REPLACE(CONVERT(NVARCHAR(30),CAST((user_seeks) AS MONEY), 1), N'.00', N'') + N' seek ' ELSE N'' END - + CASE WHEN user_scans > 0 THEN REPLACE(CONVERT(NVARCHAR(30),CAST((user_scans) AS MONEY), 1), N'.00', N'') + N' scan ' ELSE N'' END - + CASE WHEN user_lookups > 0 THEN REPLACE(CONVERT(NVARCHAR(30),CAST((user_lookups) AS MONEY), 1), N'.00', N'') + N' lookup' ELSE N'' END - ) - + N') ' - ELSE N' ' END - + N'Writes:' + - REPLACE(CONVERT(NVARCHAR(30),CAST(user_updates AS MONEY), 1), N'.00', N''), - [more_info] AS - CASE WHEN is_in_memory_oltp = 1 - THEN N'EXEC dbo.sp_BlitzInMemoryOLTP @dbName=' + QUOTENAME([database_name],N'''') + - N', @tableName=' + QUOTENAME([object_name],N'''') + N';' - ELSE N'EXEC dbo.sp_BlitzIndex @DatabaseName=' + QUOTENAME([database_name],N'''') + - N', @SchemaName=' + QUOTENAME([schema_name],N'''') + N', @TableName=' + QUOTENAME([object_name],N'''') + N';' END + [index_usage_summary] AS + CASE WHEN is_spatial = 1 THEN N'Not Tracked' + WHEN is_disabled = 1 THEN N'Disabled' + ELSE N'Reads: ' + + REPLACE(CONVERT(NVARCHAR(30),CAST((user_seeks + user_scans + user_lookups) AS MONEY), 1), N'.00', N'') + + CASE WHEN user_seeks + user_scans + user_lookups > 0 THEN + N' (' + + RTRIM( + CASE WHEN user_seeks > 0 THEN REPLACE(CONVERT(NVARCHAR(30),CAST((user_seeks) AS MONEY), 1), N'.00', N'') + N' seek ' ELSE N'' END + + CASE WHEN user_scans > 0 THEN REPLACE(CONVERT(NVARCHAR(30),CAST((user_scans) AS MONEY), 1), N'.00', N'') + N' scan ' ELSE N'' END + + CASE WHEN user_lookups > 0 THEN REPLACE(CONVERT(NVARCHAR(30),CAST((user_lookups) AS MONEY), 1), N'.00', N'') + N' lookup' ELSE N'' END + ) + + N') ' + ELSE N' ' + END + + N'Writes: ' + + REPLACE(CONVERT(NVARCHAR(30),CAST(user_updates AS MONEY), 1), N'.00', N'') + END /* First "end" is about is_spatial */, + [more_info] AS + CASE WHEN is_in_memory_oltp = 1 + THEN N'EXEC dbo.sp_BlitzInMemoryOLTP @dbName=' + QUOTENAME([database_name],N'''') + + N', @tableName=' + QUOTENAME([object_name],N'''') + N';' + ELSE N'EXEC dbo.sp_BlitzIndex @DatabaseName=' + QUOTENAME([database_name],N'''') + + N', @SchemaName=' + QUOTENAME([schema_name],N'''') + N', @TableName=' + QUOTENAME([object_name],N'''') + N';' + END ); RAISERROR (N'Adding UQ index on #IndexSanity (database_id, object_id, index_id)',0,1) WITH NOWAIT; IF NOT EXISTS(SELECT 1 FROM tempdb.sys.indexes WHERE name='uq_database_id_object_id_index_id') @@ -23236,7 +23319,8 @@ IF OBJECT_ID('tempdb..#Ignore_Databases') IS NOT NULL + N';' , [more_info] AS N'EXEC dbo.sp_BlitzIndex @DatabaseName=' + QUOTENAME([database_name],'''') + - N', @SchemaName=' + QUOTENAME([schema_name],'''') + N', @TableName=' + QUOTENAME([table_name],'''') + N';' + N', @SchemaName=' + QUOTENAME([schema_name],'''') + N', @TableName=' + QUOTENAME([table_name],'''') + N';', + [sample_query_plan] XML NULL ); CREATE TABLE #ForeignKeys ( @@ -24268,8 +24352,24 @@ BEGIN TRY FROM ColumnNamesWithDataTypes WHERE ColumnNamesWithDataTypes.index_handle = id.index_handle AND ColumnNamesWithDataTypes.object_id = id.object_id AND ColumnNamesWithDataTypes.IndexColumnType = ''Included'' - ) AS included_columns_with_data_type - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_groups ig + ) AS included_columns_with_data_type ' + + IF NOT EXISTS (SELECT * FROM sys.all_objects WHERE name = 'dm_db_missing_index_group_stats_query') + SET @dsql = @dsql + N' , NULL AS sample_query_plan ' + ELSE + BEGIN + SET @dsql = @dsql + N' , sample_query_plan = (SELECT TOP 1 p.query_plan + FROM sys.dm_db_missing_index_group_stats gs + CROSS APPLY (SELECT TOP 1 s.plan_handle + FROM sys.dm_db_missing_index_group_stats_query q + INNER JOIN sys.dm_exec_query_stats s ON q.query_plan_hash = s.query_plan_hash + WHERE gs.group_handle = q.group_handle + ORDER BY (q.user_seeks + q.user_scans) DESC, s.total_logical_reads DESC) q2 + CROSS APPLY sys.dm_exec_query_plan(q2.plan_handle) p + WHERE gs.group_handle = gs.group_handle) ' + END + + SET @dsql = @dsql + N'FROM ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_groups ig JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_details id ON ig.index_handle = id.index_handle JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_group_stats gs ON ig.index_group_handle = gs.group_handle JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects so on @@ -24300,7 +24400,7 @@ BEGIN TRY INSERT #MissingIndexes ( [database_id], [object_id], [database_name], [schema_name], [table_name], [statement], avg_total_user_cost, avg_user_impact, user_seeks, user_scans, unique_compiles, equality_columns, inequality_columns, included_columns, equality_columns_with_data_type, inequality_columns_with_data_type, - included_columns_with_data_type) + included_columns_with_data_type, sample_query_plan) EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; SET @dsql = N' @@ -24370,14 +24470,18 @@ BEGIN TRY EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; - IF @SkipStatistics = 0 AND DB_NAME() = @DatabaseName /* Can only get stats in the current database - see https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/1947 */ + IF @SkipStatistics = 0 /* AND DB_NAME() = @DatabaseName /* Can only get stats in the current database - see https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/1947 */ */ BEGIN IF ((PARSENAME(@SQLServerProductVersion, 4) >= 12) OR (PARSENAME(@SQLServerProductVersion, 4) = 11 AND PARSENAME(@SQLServerProductVersion, 2) >= 3000) OR (PARSENAME(@SQLServerProductVersion, 4) = 10 AND PARSENAME(@SQLServerProductVersion, 3) = 50 AND PARSENAME(@SQLServerProductVersion, 2) >= 2500)) BEGIN RAISERROR (N'Gathering Statistics Info With Newer Syntax.',0,1) WITH NOWAIT; - SET @dsql=N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + SET @dsql=N'USE ' + @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, + 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) SELECT DB_ID(N' + QUOTENAME(@DatabaseName,'''') + N') AS [database_id], @i_DatabaseName AS database_name, obj.name AS table_name, @@ -24442,17 +24546,17 @@ BEGIN TRY PRINT SUBSTRING(@dsql, 32000, 36000); PRINT SUBSTRING(@dsql, 36000, 40000); END; - 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) EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; END; ELSE BEGIN RAISERROR (N'Gathering Statistics Info With Older Syntax.',0,1) WITH NOWAIT; - SET @dsql=N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + SET @dsql=N'USE ' + @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, 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) SELECT DB_ID(N' + QUOTENAME(@DatabaseName,'''') + N') AS [database_id], @i_DatabaseName AS database_name, obj.name AS table_name, @@ -24520,10 +24624,6 @@ BEGIN TRY PRINT SUBSTRING(@dsql, 32000, 36000); PRINT SUBSTRING(@dsql, 36000, 40000); END; - 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) EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; END; @@ -25212,7 +25312,7 @@ BEGIN GROUP BY i.database_id, i.schema_name, i.object_id ) SELECT N'Missing index.' AS Finding , - N'http://BrentOzar.com/go/Indexaphobia' AS URL , + N'https://www.brentozar.com/go/Indexaphobia' AS URL , mi.[statement] + ' Est. Benefit: ' + CASE WHEN magic_benefit_number >= 922337203685477 THEN '>= 922,337,203,685,477' @@ -25222,7 +25322,8 @@ BEGIN END AS [Estimated Benefit], missing_index_details AS [Missing Index Request] , index_estimated_impact AS [Estimated Impact], - create_tsql AS [Create TSQL] + create_tsql AS [Create TSQL], + sample_query_plan AS [Sample Query Plan] FROM #MissingIndexes mi LEFT JOIN create_date AS cd ON mi.[object_id] = cd.object_id @@ -25398,30 +25499,48 @@ BEGIN RAISERROR(N'Done visualizing columnstore index contents.', 0,1) WITH NOWAIT; END -END; +END; /* IF @TableName IS NOT NULL */ ---If @TableName is NOT specified... ---Act based on the @Mode and @Filter. (@Filter applies only when @Mode=0 "diagnose") -ELSE + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +ELSE IF @Mode IN (0, 4) /* DIAGNOSE*//* IF @TableName IS NOT NULL, so @TableName must not be null */ BEGIN; - IF @Mode IN (0, 4) /* DIAGNOSE*/ - BEGIN; - RAISERROR(N'@Mode=0 or 4, we are diagnosing.', 0,1) WITH NOWAIT; + IF @Mode IN (0, 4) /* DIAGNOSE priorities 1-100 */ + BEGIN; + RAISERROR(N'@Mode=0 or 4, running rules for priorities 1-100.', 0,1) WITH NOWAIT; ---------------------------------------- --Multiple Index Personalities: Check_id 0-10 ---------------------------------------- - BEGIN; - - --SELECT [object_id], key_column_names, database_id - -- FROM #IndexSanity - -- WHERE index_type IN (1,2) /* Clustered, NC only*/ - -- AND is_hypothetical = 0 - -- AND is_disabled = 0 - -- GROUP BY [object_id], key_column_names, database_id - -- HAVING COUNT(*) > 1 - - RAISERROR('check_id 1: Duplicate keys', 0,1) WITH NOWAIT; WITH duplicate_indexes AS ( SELECT [object_id], key_column_names, database_id, [schema_name] @@ -25448,11 +25567,11 @@ BEGIN; secret_columns, index_usage_summary, index_size_summary ) SELECT 1 AS check_id, ip.index_sanity_id, - 50 AS Priority, + 20 AS Priority, 'Multiple Index Personalities' AS findings_group, 'Duplicate keys' AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/duplicateindex' AS URL, + N'https://www.brentozar.com/go/duplicateindex' AS URL, N'Index Name: ' + ip.index_name + N' Table Name: ' + ip.db_schema_object_name AS details, ip.index_definition, ip.secret_columns, @@ -25485,11 +25604,11 @@ BEGIN; secret_columns, index_usage_summary, index_size_summary ) SELECT 2 AS check_id, ip.index_sanity_id, - 60 AS Priority, + 30 AS Priority, 'Multiple Index Personalities' AS findings_group, 'Borderline duplicate keys' AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/duplicateindex' AS URL, + N'https://www.brentozar.com/go/duplicateindex' AS URL, ip.db_schema_object_indexid AS details, ip.index_definition, ip.secret_columns, @@ -25510,18 +25629,16 @@ BEGIN; ORDER BY ip.[schema_name], ip.[object_name], ip.key_column_names, ip.include_column_names OPTION ( RECOMPILE ); - END; ---------------------------------------- --Aggressive Indexes: Check_id 10-19 ---------------------------------------- - BEGIN; RAISERROR(N'check_id 11: Total lock wait time > 5 minutes (row + page) with long average waits', 0,1) WITH NOWAIT; INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, secret_columns, index_usage_summary, index_size_summary ) SELECT 11 AS check_id, i.index_sanity_id, - 10 AS Priority, + 70 AS Priority, N'Aggressive ' + CASE COALESCE((SELECT SUM(1) FROM #IndexSanity iMe @@ -25547,7 +25664,7 @@ BEGIN; END AS findings_group, N'Total lock wait time > 5 minutes (row + page) with long average waits' AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/AggressiveIndexes' AS URL, + N'https://www.brentozar.com/go/AggressiveIndexes' AS URL, (i.db_schema_object_indexid + N': ' + sz.index_lock_wait_summary + N' NC indexes on table: ') COLLATE DATABASE_DEFAULT + CAST(COALESCE((SELECT SUM(1) @@ -25578,7 +25695,7 @@ BEGIN; secret_columns, index_usage_summary, index_size_summary ) SELECT 12 AS check_id, i.index_sanity_id, - 10 AS Priority, + 70 AS Priority, N'Aggressive ' + CASE COALESCE((SELECT SUM(1) FROM #IndexSanity iMe @@ -25604,7 +25721,7 @@ BEGIN; END AS findings_group, N'Total lock wait time > 5 minutes (row + page) with short average waits' AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/AggressiveIndexes' AS URL, + N'https://www.brentozar.com/go/AggressiveIndexes' AS URL, (i.db_schema_object_indexid + N': ' + sz.index_lock_wait_summary + N' NC indexes on table: ') COLLATE DATABASE_DEFAULT + CAST(COALESCE((SELECT SUM(1) @@ -25630,22 +25747,20 @@ BEGIN; ORDER BY 4, [database_name], 8 OPTION ( RECOMPILE ); - END; ---------------------------------------- --Index Hoarder: Check_id 20-29 ---------------------------------------- - BEGIN - RAISERROR(N'check_id 20: >=7 NC indexes on any given table. Yes, 7 is an arbitrary number.', 0,1) WITH NOWAIT; + RAISERROR(N'check_id 20: >= 10 NC indexes on any given table. Yes, 10 is an arbitrary number.', 0,1) WITH NOWAIT; INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, secret_columns, index_usage_summary, index_size_summary ) SELECT 20 AS check_id, MAX(i.index_sanity_id) AS index_sanity_id, - 100 AS Priority, + 10 AS Priority, 'Index Hoarder' AS findings_group, - 'Many NC indexes on a single table' AS finding, + 'Many NC Indexes on a Single Table' AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/IndexHoarder' AS URL, + N'https://www.brentozar.com/go/IndexHoarder' AS URL, CAST (COUNT(*) AS NVARCHAR(30)) + ' NC indexes on ' + i.db_schema_object_name AS details, i.db_schema_object_name + ' (' + CAST (COUNT(*) AS NVARCHAR(30)) + ' indexes)' AS index_definition, '' AS secret_columns, @@ -25662,88 +25777,20 @@ BEGIN; JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id WHERE index_id NOT IN ( 0, 1 ) GROUP BY db_schema_object_name, [i].[database_name] - HAVING COUNT(*) >= CASE WHEN (@GetAllDatabases = 1 OR @Mode = 0) - THEN 21 - ELSE 7 - END + HAVING COUNT(*) >= 10 ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); - IF @Filter = 1 /*@Filter=1 is "ignore unusued" */ - BEGIN - RAISERROR(N'Skipping checks on unused indexes (21 and 22) because @Filter=1', 0,1) WITH NOWAIT; - END; - ELSE /*Otherwise, go ahead and do the checks*/ - BEGIN - RAISERROR(N'check_id 21: >=5 percent of indexes are unused. Yes, 5 is an arbitrary number.', 0,1) WITH NOWAIT; - DECLARE @percent_NC_indexes_unused NUMERIC(29,1); - DECLARE @NC_indexes_unused_reserved_MB NUMERIC(29,1); - - SELECT @percent_NC_indexes_unused = ( 100.00 * SUM(CASE - WHEN total_reads = 0 - THEN 1 - ELSE 0 - END) ) / COUNT(*), - @NC_indexes_unused_reserved_MB = SUM(CASE - WHEN total_reads = 0 - THEN sz.total_reserved_MB - ELSE 0 - END) - FROM #IndexSanity i - 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()) - 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, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 21 AS check_id, - MAX(i.index_sanity_id) AS index_sanity_id, - 150 AS Priority, - N'Index Hoarder' AS findings_group, - N'More than 5 percent NC indexes are unused' AS finding, - [database_name] AS [Database Name], - N'http://BrentOzar.com/go/IndexHoarder' AS URL, - CAST (@percent_NC_indexes_unused AS NVARCHAR(30)) + N' percent NC indexes (' + CAST(COUNT(*) AS NVARCHAR(10)) + N') unused. ' + - N'These take up ' + CAST (@NC_indexes_unused_reserved_MB AS NVARCHAR(30)) + N'MB of space.' AS details, - i.database_name + ' (' + CAST (COUNT(*) AS NVARCHAR(30)) + N' indexes)' AS index_definition, - '' AS secret_columns, - CAST(SUM(total_reads) AS NVARCHAR(256)) + N' reads (ALL); ' - + CAST(SUM([user_updates]) AS NVARCHAR(256)) + N' writes (ALL)' AS index_usage_summary, - - REPLACE(CONVERT(NVARCHAR(30),CAST(MAX([total_rows]) AS MONEY), 1), '.00', '') + N' rows (MAX)' - + CASE WHEN SUM(total_reserved_MB) > 1024 THEN - N'; ' + CAST(CAST(SUM(total_reserved_MB)/1024. AS NUMERIC(29,1)) AS NVARCHAR(30)) + 'GB (ALL)' - WHEN SUM(total_reserved_MB) > 0 THEN - N'; ' + CAST(CAST(SUM(total_reserved_MB) AS NUMERIC(29,1)) AS NVARCHAR(30)) + 'MB (ALL)' - ELSE '' - END AS index_size_summary - FROM #IndexSanity i - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE index_id NOT IN ( 0, 1 ) - AND i.is_unique = 0 - AND total_reads = 0 - AND NOT (@GetAllDatabases = 1 OR @Mode = 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()) - GROUP BY i.database_name - OPTION ( RECOMPILE ); - RAISERROR(N'check_id 22: NC indexes with 0 reads. (Borderline) and >= 10,000 writes', 0,1) WITH NOWAIT; INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, secret_columns, index_usage_summary, index_size_summary ) SELECT 22 AS check_id, i.index_sanity_id, - 100 AS Priority, + 10 AS Priority, N'Index Hoarder' AS findings_group, - N'Unused NC index with High Writes' AS finding, + N'Unused NC Index with High Writes' AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/IndexHoarder' AS URL, + N'https://www.brentozar.com/go/IndexHoarder' AS URL, N'Reads: 0,' + N' Writes: ' + REPLACE(CONVERT(NVARCHAR(30), CAST((i.user_updates) AS MONEY), 1), N'.00', N'') @@ -25761,387 +25808,10 @@ 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 + AND @Filter <> 1 /* 1 = "ignore unused */ ORDER BY i.db_schema_object_indexid OPTION ( RECOMPILE ); - END; /*end checks only run when @Filter <> 1*/ - - RAISERROR(N'check_id 23: Indexes with 7 or more columns. (Borderline)', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 23 AS check_id, - i.index_sanity_id, - 150 AS Priority, - N'Index Hoarder' AS findings_group, - N'Borderline: Wide indexes (7 or more columns)' AS finding, - [database_name] AS [Database Name], - N'http://BrentOzar.com/go/IndexHoarder' AS URL, - CAST(count_key_columns + count_included_columns AS NVARCHAR(10)) + ' columns on ' - + i.db_schema_object_indexid AS details, i.index_definition, - i.secret_columns, - i.index_usage_summary, - sz.index_size_summary - FROM #IndexSanity AS i - JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id - WHERE ( count_key_columns + count_included_columns ) >= 7 - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 24: Wide clustered indexes (> 3 columns or > 16 bytes).', 0,1) WITH NOWAIT; - WITH count_columns AS ( - SELECT database_id, [object_id], - SUM(CASE max_length WHEN -1 THEN 0 ELSE max_length END) AS sum_max_length - FROM #IndexColumns ic - WHERE index_id IN (1,0) /*Heap or clustered only*/ - AND key_ordinal > 0 - GROUP BY database_id, object_id - ) - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 24 AS check_id, - i.index_sanity_id, - 150 AS Priority, - N'Index Hoarder' AS findings_group, - N'Wide clustered index (> 3 columns OR > 16 bytes)' AS finding, - [database_name] AS [Database Name], - N'http://BrentOzar.com/go/IndexHoarder' AS URL, - CAST (i.count_key_columns AS NVARCHAR(10)) + N' columns with potential size of ' - + CAST(cc.sum_max_length AS NVARCHAR(10)) - + N' bytes in clustered index:' + i.db_schema_object_name - + N'. ' + - (SELECT CAST(COUNT(*) AS NVARCHAR(23)) - FROM #IndexSanity i2 - WHERE i2.[object_id]=i.[object_id] - AND i2.database_id = i.database_id - AND i2.index_id <> 1 - AND i2.is_disabled=0 - AND i2.is_hypothetical=0) - + N' NC indexes on the table.' - AS details, - i.index_definition, - secret_columns, - i.index_usage_summary, - ip.index_size_summary - FROM #IndexSanity i - JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id - JOIN count_columns AS cc ON i.[object_id]=cc.[object_id] - AND i.database_id = cc.database_id - WHERE index_id = 1 /* clustered only */ - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) - AND - (count_key_columns > 3 /*More than three key columns.*/ - OR cc.sum_max_length > 16 /*More than 16 bytes in key */) - AND i.is_CX_columnstore = 0 - ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 25: Addicted to nullable columns.', 0,1) WITH NOWAIT; - WITH count_columns AS ( - SELECT [object_id], - [database_id], - [schema_name], - SUM(CASE is_nullable WHEN 1 THEN 0 ELSE 1 END) AS non_nullable_columns, - COUNT(*) AS total_columns - FROM #IndexColumns ic - WHERE index_id IN (1,0) /*Heap or clustered only*/ - GROUP BY [object_id], - [database_id], - [schema_name] - ) - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 25 AS check_id, - i.index_sanity_id, - 200 AS Priority, - N'Index Hoarder' AS findings_group, - N'Addicted to nulls' AS finding, - [database_name] AS [Database Name], - N'http://BrentOzar.com/go/IndexHoarder' AS URL, - i.db_schema_object_name - + N' allows null in ' + CAST((total_columns-non_nullable_columns) AS NVARCHAR(10)) - + N' of ' + CAST(total_columns AS NVARCHAR(10)) - + N' columns.' AS details, - i.index_definition, - secret_columns, - ISNULL(i.index_usage_summary,''), - ISNULL(ip.index_size_summary,'') - FROM #IndexSanity i - JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id - JOIN count_columns AS cc ON i.[object_id]=cc.[object_id] - AND cc.database_id = ip.database_id - AND cc.[schema_name] = ip.[schema_name] - WHERE i.index_id IN (1,0) - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) - AND cc.non_nullable_columns < 2 - AND cc.total_columns > 3 - ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 26: Wide tables (35+ cols or > 2000 non-LOB bytes).', 0,1) WITH NOWAIT; - WITH count_columns AS ( - SELECT [object_id], - [database_id], - [schema_name], - SUM(CASE max_length WHEN -1 THEN 1 ELSE 0 END) AS count_lob_columns, - SUM(CASE max_length WHEN -1 THEN 0 ELSE max_length END) AS sum_max_length, - COUNT(*) AS total_columns - FROM #IndexColumns ic - WHERE index_id IN (1,0) /*Heap or clustered only*/ - GROUP BY [object_id], - [database_id], - [schema_name] - ) - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 26 AS check_id, - i.index_sanity_id, - 150 AS Priority, - N'Index Hoarder' AS findings_group, - N'Wide tables: 35+ cols or > 2000 non-LOB bytes' AS finding, - [database_name] AS [Database Name], - N'http://BrentOzar.com/go/IndexHoarder' AS URL, - i.db_schema_object_name - + N' has ' + CAST((total_columns) AS NVARCHAR(10)) - + N' total columns with a max possible width of ' + CAST(sum_max_length AS NVARCHAR(10)) - + N' bytes.' + - CASE WHEN count_lob_columns > 0 THEN CAST((count_lob_columns) AS NVARCHAR(10)) - + ' columns are LOB types.' ELSE '' - END - AS details, - i.index_definition, - secret_columns, - ISNULL(i.index_usage_summary,''), - ISNULL(ip.index_size_summary,'') - FROM #IndexSanity i - JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id - JOIN count_columns AS cc ON i.[object_id]=cc.[object_id] - AND cc.database_id = i.database_id - AND cc.[schema_name] = i.[schema_name] - WHERE i.index_id IN (1,0) - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) - AND - (cc.total_columns >= 35 OR - cc.sum_max_length >= 2000) - ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 27: Addicted to strings.', 0,1) WITH NOWAIT; - WITH count_columns AS ( - SELECT [object_id], - [database_id], - [schema_name], - SUM(CASE WHEN system_type_name IN ('varchar','nvarchar','char') OR max_length=-1 THEN 1 ELSE 0 END) AS string_or_LOB_columns, - COUNT(*) AS total_columns - FROM #IndexColumns ic - WHERE index_id IN (1,0) /*Heap or clustered only*/ - GROUP BY [object_id], - [database_id], - [schema_name] - ) - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 27 AS check_id, - i.index_sanity_id, - 200 AS Priority, - N'Index Hoarder' AS findings_group, - N'Addicted to strings' AS finding, - [database_name] AS [Database Name], - N'http://BrentOzar.com/go/IndexHoarder' AS URL, - i.db_schema_object_name - + N' uses string or LOB types for ' + CAST((string_or_LOB_columns) AS NVARCHAR(10)) - + N' of ' + CAST(total_columns AS NVARCHAR(10)) - + N' columns. Check if data types are valid.' AS details, - i.index_definition, - secret_columns, - ISNULL(i.index_usage_summary,''), - ISNULL(ip.index_size_summary,'') - FROM #IndexSanity i - JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id - JOIN count_columns AS cc ON i.[object_id]=cc.[object_id] - AND cc.database_id = i.database_id - AND cc.[schema_name] = i.[schema_name] - CROSS APPLY (SELECT cc.total_columns - string_or_LOB_columns AS non_string_or_lob_columns) AS calc1 - WHERE i.index_id IN (1,0) - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) - AND calc1.non_string_or_lob_columns <= 1 - AND cc.total_columns > 3 - ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 28: Non-unique clustered index.', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 28 AS check_id, - i.index_sanity_id, - 100 AS Priority, - N'Index Hoarder' AS findings_group, - N'Non-Unique clustered index' AS finding, - [database_name] AS [Database Name], - N'http://BrentOzar.com/go/IndexHoarder' AS URL, - N'Uniquifiers will be required! Clustered index: ' + i.db_schema_object_name - + N' and all NC indexes. ' + - (SELECT CAST(COUNT(*) AS NVARCHAR(23)) - FROM #IndexSanity i2 - WHERE i2.[object_id]=i.[object_id] - AND i2.database_id = i.database_id - AND i2.index_id <> 1 - AND i2.is_disabled=0 - AND i2.is_hypothetical=0) - + N' NC indexes on the table.' - AS details, - i.index_definition, - secret_columns, - i.index_usage_summary, - ip.index_size_summary - FROM #IndexSanity i - JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id - WHERE index_id = 1 /* clustered only */ - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) - AND is_unique=0 /* not unique */ - AND is_CX_columnstore=0 /* not a clustered columnstore-- no unique option on those */ - ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 29: NC indexes with 0 reads. (Borderline) and < 10,000 writes', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 29 AS check_id, - i.index_sanity_id, - 150 AS Priority, - N'Index Hoarder' AS findings_group, - N'Unused NC index with Low Writes' AS finding, - [database_name] AS [Database Name], - N'http://BrentOzar.com/go/IndexHoarder' AS URL, - N'0 reads: ' + i.db_schema_object_indexid AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - sz.index_size_summary - FROM #IndexSanity AS i - JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.total_reads=0 - AND i.user_updates < 10000 - 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()) - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) - ORDER BY i.db_schema_object_indexid - OPTION ( RECOMPILE ); - - END; - ---------------------------------------- - --Feature-Phobic Indexes: Check_id 30-39 - ---------------------------------------- - BEGIN - RAISERROR(N'check_id 30: No indexes with includes', 0,1) WITH NOWAIT; - /* This does not work the way you'd expect with @GetAllDatabases = 1. For details: - https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/825 - */ - - SELECT database_name, - SUM(CASE WHEN count_included_columns > 0 THEN 1 ELSE 0 END) AS number_indexes_with_includes, - 100.* SUM(CASE WHEN count_included_columns > 0 THEN 1 ELSE 0 END) / ( 1.0 * COUNT(*) ) AS percent_indexes_with_includes - INTO #index_includes - FROM #IndexSanity - WHERE is_hypothetical = 0 - AND is_disabled = 0 - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) - GROUP BY database_name; - - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 30 AS check_id, - NULL AS index_sanity_id, - 250 AS Priority, - N'Feature-Phobic Indexes' AS findings_group, - database_name AS [Database Name], - N'No indexes use includes' AS finding, 'http://BrentOzar.com/go/IndexFeatures' AS URL, - N'No indexes use includes' AS details, - database_name + N' (Entire database)' AS index_definition, - N'' AS secret_columns, - N'N/A' AS index_usage_summary, - N'N/A' AS index_size_summary - FROM #index_includes - WHERE number_indexes_with_includes = 0 - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 31: < 3 percent of indexes have includes', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 31 AS check_id, - NULL AS index_sanity_id, - 150 AS Priority, - N'Feature-Phobic Indexes' AS findings_group, - N'Borderline: Includes are used in < 3% of indexes' AS findings, - database_name AS [Database Name], - N'http://BrentOzar.com/go/IndexFeatures' AS URL, - N'Only ' + CAST(percent_indexes_with_includes AS NVARCHAR(20)) + '% of indexes have includes' AS details, - N'Entire database' AS index_definition, - N'' AS secret_columns, - N'N/A' AS index_usage_summary, - N'N/A' AS index_size_summary - FROM #index_includes - WHERE number_indexes_with_includes > 0 AND percent_indexes_with_includes <= 3 - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 32: filtered indexes and indexed views', 0,1) WITH NOWAIT; - - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT DISTINCT - 32 AS check_id, - NULL AS index_sanity_id, - 250 AS Priority, - N'Feature-Phobic Indexes' AS findings_group, - N'Borderline: No filtered indexes or indexed views exist' AS finding, - i.database_name AS [Database Name], - N'http://BrentOzar.com/go/IndexFeatures' AS URL, - N'These are NOT always needed-- but do you know when you would use them?' AS details, - i.database_name + N' (Entire database)' AS index_definition, - N'' AS secret_columns, - N'N/A' AS index_usage_summary, - N'N/A' AS index_size_summary - FROM #IndexSanity i - WHERE i.database_name NOT IN ( - SELECT database_name - FROM #IndexSanity - WHERE filter_definition <> '' ) - AND i.database_name NOT IN ( - SELECT database_name - FROM #IndexSanity - WHERE is_indexed_view = 1 ) - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) - OPTION ( RECOMPILE ); - END; - RAISERROR(N'check_id 33: Potential filtered indexes based on column names.', 0,1) WITH NOWAIT; - - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 33 AS check_id, - i.index_sanity_id AS index_sanity_id, - 250 AS Priority, - N'Feature-Phobic Indexes' AS findings_group, - N'Potential filtered index (based on column name)' AS finding, - [database_name] AS [Database Name], - N'http://BrentOzar.com/go/IndexFeatures' AS URL, - N'A column name in this index suggests it might be a candidate for filtering (is%, %archive%, %active%, %flag%)' AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - sz.index_size_summary - FROM #IndexColumns ic - JOIN #IndexSanity i ON ic.[object_id]=i.[object_id] - AND ic.database_id =i.database_id - AND ic.schema_name = i.schema_name - AND ic.[index_id]=i.[index_id] - AND i.[index_id] > 1 /* non-clustered index */ - JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id - WHERE (column_name LIKE 'is%' - OR column_name LIKE '%archive%' - OR column_name LIKE '%active%' - OR column_name LIKE '%flag%') - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) - OPTION ( RECOMPILE ); RAISERROR(N'check_id 34: Filtered index definition columns not in index definition', 0,1) WITH NOWAIT; @@ -26150,10 +25820,10 @@ BEGIN; SELECT 34 AS check_id, i.index_sanity_id, 80 AS Priority, - N'Forgetful Indexes' AS findings_group, + N'Abnormal Psychology' AS findings_group, N'Filter Columns Not In Index Definition' AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/IndexFeatures' AS URL, + N'https://www.brentozar.com/go/IndexFeatures' AS URL, N'The index ' + QUOTENAME(i.index_name) + N' on [' @@ -26177,7 +25847,6 @@ BEGIN; ---------------------------------------- --Self Loathing Indexes : Check_id 40-49 ---------------------------------------- - BEGIN RAISERROR(N'check_id 40: Fillfactor in nonclustered 80 percent or less', 0,1) WITH NOWAIT; INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, @@ -26186,9 +25855,9 @@ BEGIN; i.index_sanity_id, 100 AS Priority, N'Self Loathing Indexes' AS findings_group, - N'Low Fill Factor: nonclustered index' AS finding, + N'Low Fill Factor on Nonclustered Index' AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/SelfLoathing' AS URL, + N'https://www.brentozar.com/go/SelfLoathing' AS URL, CAST(fill_factor AS NVARCHAR(10)) + N'% fill factor on ' + db_schema_object_indexid + N'. '+ CASE WHEN (last_user_update IS NULL OR user_updates < 1) THEN N'No writes have been made.' @@ -26204,7 +25873,6 @@ BEGIN; FROM #IndexSanity AS i JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id WHERE index_id > 1 - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) AND fill_factor BETWEEN 1 AND 80 OPTION ( RECOMPILE ); RAISERROR(N'check_id 40: Fillfactor in clustered 80 percent or less', 0,1) WITH NOWAIT; @@ -26214,9 +25882,9 @@ BEGIN; i.index_sanity_id, 100 AS Priority, N'Self Loathing Indexes' AS findings_group, - N'Low Fill Factor: clustered index' AS finding, + N'Low Fill Factor on Clustered Index' AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/SelfLoathing' AS URL, + N'https://www.brentozar.com/go/SelfLoathing' AS URL, N'Fill factor on ' + db_schema_object_indexid + N' is ' + CAST(fill_factor AS NVARCHAR(10)) + N'%. '+ CASE WHEN (last_user_update IS NULL OR user_updates < 1) THEN N'No writes have been made.' @@ -26232,52 +25900,9 @@ BEGIN; FROM #IndexSanity AS i JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id WHERE index_id = 1 - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) AND fill_factor BETWEEN 1 AND 80 OPTION ( RECOMPILE ); - RAISERROR(N'check_id 41: Hypothetical indexes ', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 41 AS check_id, - i.index_sanity_id, - 150 AS Priority, - N'Self Loathing Indexes' AS findings_group, - N'Hypothetical Index' AS finding, - [database_name] AS [Database Name], - N'http://BrentOzar.com/go/SelfLoathing' AS URL, - N'Hypothetical Index: ' + db_schema_object_indexid AS details, - i.index_definition, - i.secret_columns, - N'' AS index_usage_summary, - N'' AS index_size_summary - FROM #IndexSanity AS i - WHERE is_hypothetical = 1 - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) - OPTION ( RECOMPILE ); - - - RAISERROR(N'check_id 42: Disabled indexes', 0,1) WITH NOWAIT; - --Note: disabled NC indexes will have O rows in #IndexSanitySize! - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 42 AS check_id, - index_sanity_id, - 150 AS Priority, - N'Self Loathing Indexes' AS findings_group, - N'Disabled Index' AS finding, - [database_name] AS [Database Name], - N'http://BrentOzar.com/go/SelfLoathing' AS URL, - N'Disabled Index:' + db_schema_object_indexid AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - 'DISABLED' AS index_size_summary - FROM #IndexSanity AS i - WHERE is_disabled = 1 - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) - OPTION ( RECOMPILE ); - RAISERROR(N'check_id 43: Heaps with forwarded records', 0,1) WITH NOWAIT; WITH heaps_cte AS ( SELECT [object_id], @@ -26296,9 +25921,9 @@ BEGIN; i.index_sanity_id, 100 AS Priority, N'Self Loathing Indexes' AS findings_group, - N'Heaps with forwarded records' AS finding, + N'Heaps with Forwarded Fetches' AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/SelfLoathing' AS URL, + N'https://www.brentozar.com/go/SelfLoathing' AS URL, CASE WHEN h.forwarded_fetch_count >= 922337203685477 THEN '>= 922,337,203,685,477' WHEN @DaysUptime < 1 THEN CAST(h.forwarded_fetch_count AS NVARCHAR(256)) + N' forwarded fetches against heap: ' + db_schema_object_indexid ELSE REPLACE(CONVERT(NVARCHAR(256),CAST(CAST( @@ -26320,42 +25945,6 @@ BEGIN; AND sz.total_reserved_MB >= CASE WHEN NOT (@GetAllDatabases = 1 OR @Mode = 4) THEN @ThresholdMB ELSE sz.total_reserved_MB END OPTION ( RECOMPILE ); - RAISERROR(N'check_id 49: Heaps with deletes', 0,1) WITH NOWAIT; - WITH heaps_cte - AS ( SELECT [object_id], - [database_id], - [schema_name], - SUM(leaf_delete_count) AS leaf_delete_count - FROM #IndexPartitionSanity - GROUP BY [object_id], - [database_id], - [schema_name] - HAVING SUM(forwarded_fetch_count) < 1000 * @DaysUptime /* Only alert about indexes with no forwarded fetches - we already alerted about those in check_id 43 */ - AND SUM(leaf_delete_count) > 0) - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 49 AS check_id, - i.index_sanity_id, - 200 AS Priority, - N'Self Loathing Indexes' AS findings_group, - N'Heaps with deletes' AS finding, - [database_name] AS [Database Name], - N'http://BrentOzar.com/go/SelfLoathing' AS URL, - CAST(h.leaf_delete_count AS NVARCHAR(256)) + N' deletes against heap:' - + db_schema_object_indexid AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - sz.index_size_summary - FROM #IndexSanity i - JOIN heaps_cte h ON i.[object_id] = h.[object_id] - AND i.[database_id] = h.[database_id] - AND i.[schema_name] = h.[schema_name] - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.index_id = 0 - AND sz.total_reserved_MB >= CASE WHEN NOT (@GetAllDatabases = 1 OR @Mode = 4) THEN @ThresholdMB ELSE sz.total_reserved_MB END - OPTION ( RECOMPILE ); - RAISERROR(N'check_id 44: Large Heaps with reads or writes.', 0,1) WITH NOWAIT; WITH heaps_cte AS ( SELECT [object_id], @@ -26375,9 +25964,9 @@ BEGIN; i.index_sanity_id, 100 AS Priority, N'Self Loathing Indexes' AS findings_group, - N'Large Active heap' AS finding, + N'Large Active Heap' AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/SelfLoathing' AS URL, + N'https://www.brentozar.com/go/SelfLoathing' AS URL, N'Should this table be a heap? ' + db_schema_object_indexid AS details, i.index_definition, 'N/A' AS secret_columns, @@ -26392,7 +25981,6 @@ BEGIN; AND (i.total_reads > 0 OR i.user_updates > 0) AND sz.total_rows >= 100000 AND h.[object_id] IS NULL /*don't duplicate the prior check.*/ - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) OPTION ( RECOMPILE ); RAISERROR(N'check_id 45: Medium Heaps with reads or writes.', 0,1) WITH NOWAIT; @@ -26416,7 +26004,7 @@ BEGIN; N'Self Loathing Indexes' AS findings_group, N'Medium Active heap' AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/SelfLoathing' AS URL, + N'https://www.brentozar.com/go/SelfLoathing' AS URL, N'Should this table be a heap? ' + db_schema_object_indexid AS details, i.index_definition, 'N/A' AS secret_columns, @@ -26432,7 +26020,6 @@ BEGIN; (i.total_reads > 0 OR i.user_updates > 0) AND sz.total_rows >= 10000 AND sz.total_rows < 100000 AND h.[object_id] IS NULL /*don't duplicate the prior check.*/ - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) OPTION ( RECOMPILE ); RAISERROR(N'check_id 46: Small Heaps with reads or writes.', 0,1) WITH NOWAIT; @@ -26456,7 +26043,7 @@ BEGIN; N'Self Loathing Indexes' AS findings_group, N'Small Active heap' AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/SelfLoathing' AS URL, + N'https://www.brentozar.com/go/SelfLoathing' AS URL, N'Should this table be a heap? ' + db_schema_object_indexid AS details, i.index_definition, 'N/A' AS secret_columns, @@ -26472,7 +26059,6 @@ BEGIN; (i.total_reads > 0 OR i.user_updates > 0) AND sz.total_rows < 10000 AND h.[object_id] IS NULL /*don't duplicate the prior check.*/ - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) OPTION ( RECOMPILE ); RAISERROR(N'check_id 47: Heap with a Nonclustered Primary Key', 0,1) WITH NOWAIT; @@ -26484,7 +26070,7 @@ BEGIN; N'Self Loathing Indexes' AS findings_group, N'Heap with a Nonclustered Primary Key' AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/SelfLoathing' AS URL, + N'https://www.brentozar.com/go/SelfLoathing' AS URL, db_schema_object_indexid + N' is a HEAP with a Nonclustered Primary Key' AS details, i.index_definition, i.secret_columns, @@ -26503,7 +26089,7 @@ BEGIN; ) OPTION ( RECOMPILE ); - RAISERROR(N'check_id 48: Nonclustered indexes with a bad read to write ratio', 0,1) WITH NOWAIT; + RAISERROR(N'check_id 48: Nonclustered indexes with a bad read to write ratio', 0,1) WITH NOWAIT; INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, secret_columns, index_usage_summary, index_size_summary ) SELECT 48 AS check_id, @@ -26512,7 +26098,7 @@ BEGIN; N'Index Hoarder' AS findings_group, N'NC index with High Writes:Reads' AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/IndexHoarder' AS URL, + N'https://www.brentozar.com/go/IndexHoarder' AS URL, N'Reads: ' + REPLACE(CONVERT(NVARCHAR(30), CAST((i.total_reads) AS MONEY), 1), N'.00', N'') + N' Writes: ' @@ -26535,12 +26121,10 @@ BEGIN; ORDER BY i.db_schema_object_indexid OPTION ( RECOMPILE ); - END; ---------------------------------------- --Indexaphobia --Missing indexes with value >= 5 million: : Check_id 50-59 ---------------------------------------- - BEGIN RAISERROR(N'check_id 50: Indexaphobia.', 0,1) WITH NOWAIT; WITH index_size_cte AS ( SELECT i.database_id, @@ -26578,11 +26162,11 @@ BEGIN; SELECT ROW_NUMBER() OVER (ORDER BY magic_benefit_number DESC) AS rownum, 50 AS check_id, sz.index_sanity_id, - 10 AS Priority, + 40 AS Priority, N'Indexaphobia' AS findings_group, - N'High value missing index' AS finding, + N'High Value Missing Index' AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/Indexaphobia' AS URL, + N'https://www.brentozar.com/go/Indexaphobia' AS URL, mi.[statement] + N' Est. benefit per day: ' + CASE WHEN magic_benefit_number >= 922337203685477 THEN '>= 922,337,203,685,477' @@ -26611,11 +26195,738 @@ BEGIN; OPTION ( RECOMPILE ); - END; + + RAISERROR(N'check_id 68: Identity columns within 30 percent of the end of range', 0,1) WITH NOWAIT; + -- Allowed Ranges: + --int -2,147,483,648 to 2,147,483,647 + --smallint -32,768 to 32,768 + --tinyint 0 to 255 + + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 68 AS check_id, + i.index_sanity_id, + 80 AS Priority, + N'Abnormal Psychology' AS findings_group, + N'Identity Column Within ' + + CAST (calc1.percent_remaining AS NVARCHAR(256)) + + N' Percent End of Range' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, + i.db_schema_object_name + N'.' + QUOTENAME(ic.column_name) + + N' is an identity with type ' + ic.system_type_name + + N', last value of ' + + ISNULL((CONVERT(NVARCHAR(256),CAST(ic.last_value AS DECIMAL(38,0)), 1)),N'NULL') + + N', seed of ' + + ISNULL((CONVERT(NVARCHAR(256),CAST(ic.seed_value AS DECIMAL(38,0)), 1)),N'NULL') + + N', increment of ' + CAST(ic.increment_value AS NVARCHAR(256)) + + N', and range of ' + + CASE ic.system_type_name WHEN 'int' THEN N'+/- 2,147,483,647' + WHEN 'smallint' THEN N'+/- 32,768' + WHEN 'tinyint' THEN N'0 to 255' + ELSE 'unknown' + END + AS details, + i.index_definition, + secret_columns, + ISNULL(i.index_usage_summary,''), + ISNULL(ip.index_size_summary,'') + FROM #IndexSanity i + JOIN #IndexColumns ic ON + i.object_id=ic.object_id + AND i.database_id = ic.database_id + AND i.schema_name = ic.schema_name + AND i.index_id IN (0,1) /* heaps and cx only */ + AND ic.is_identity=1 + AND ic.system_type_name IN ('tinyint', 'smallint', 'int') + JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id + CROSS APPLY ( + SELECT CAST(CASE WHEN ic.increment_value >= 0 + THEN + CASE ic.system_type_name + WHEN 'int' THEN (2147483647 - (ISNULL(ic.last_value,ic.seed_value) + ic.increment_value)) / 2147483647.*100 + WHEN 'smallint' THEN (32768 - (ISNULL(ic.last_value,ic.seed_value) + ic.increment_value)) / 32768.*100 + WHEN 'tinyint' THEN ( 255 - (ISNULL(ic.last_value,ic.seed_value) + ic.increment_value)) / 255.*100 + ELSE 999 + END + ELSE --ic.increment_value is negative + CASE ic.system_type_name + WHEN 'int' THEN ABS(-2147483647 - (ISNULL(ic.last_value,ic.seed_value) + ic.increment_value)) / 2147483647.*100 + WHEN 'smallint' THEN ABS(-32768 - (ISNULL(ic.last_value,ic.seed_value) + ic.increment_value)) / 32768.*100 + WHEN 'tinyint' THEN ABS( 0 - (ISNULL(ic.last_value,ic.seed_value) + ic.increment_value)) / 255.*100 + ELSE -1 + END + END AS NUMERIC(5,1)) AS percent_remaining + ) AS calc1 + WHERE i.index_id IN (1,0) + AND calc1.percent_remaining <= 30 + OPTION (RECOMPILE); + + + RAISERROR(N'check_id 72: Columnstore indexes with Trace Flag 834', 0,1) WITH NOWAIT; + IF EXISTS (SELECT * FROM #IndexSanity WHERE index_type IN (5,6)) + AND EXISTS (SELECT * FROM #TraceStatus WHERE TraceFlag = 834 AND status = 1) + BEGIN + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 72 AS check_id, + i.index_sanity_id, + 80 AS Priority, + N'Abnormal Psychology' AS findings_group, + 'Columnstore Indexes with Trace Flag 834' AS finding, + [database_name] AS [Database Name], + N'https://support.microsoft.com/en-us/kb/3210239' AS URL, + i.db_schema_object_indexid AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + ISNULL(sz.index_size_summary,'') AS index_size_summary + FROM #IndexSanity AS i + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + WHERE i.index_type IN (5,6) + OPTION ( RECOMPILE ); + END; + + ---------------------------------------- + --Statistics Info: Check_id 90-99 + ---------------------------------------- + + RAISERROR(N'check_id 90: Outdated 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 ) + SELECT 90 AS check_id, + 90 AS Priority, + 'Functioning Statistaholics' AS findings_group, + 'Statistics Not Updated Recently', + s.database_name, + 'https://www.brentozar.com/go/stats' AS URL, + 'Statistics on this table were last updated ' + + CASE WHEN s.last_statistics_update IS NULL THEN N' NEVER ' + ELSE CONVERT(NVARCHAR(20), s.last_statistics_update) + + ' have had ' + CONVERT(NVARCHAR(100), s.modification_counter) + + ' modifications in that time, which is ' + + CONVERT(NVARCHAR(100), s.percent_modifications) + + '% of the table.' + END 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.last_statistics_update <= CONVERT(DATETIME, GETDATE() - 30) + AND s.percent_modifications >= 10. + AND s.rows >= 10000 + OPTION ( RECOMPILE ); + + RAISERROR(N'check_id 91: Statistics with a low 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 91 AS check_id, + 90 AS Priority, + 'Functioning Statistaholics' AS findings_group, + 'Low Sampling Rates', + s.database_name, + 'https://www.brentozar.com/go/stats' AS URL, + 'Only ' + CONVERT(NVARCHAR(100), s.percent_sampled) + '% of the rows were sampled during the last statistics update. This may lead to poor cardinality estimates.' 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.rows BETWEEN 10000 AND 1000000 AND s.percent_sampled < 10) + OR (s.rows > 1000000 AND s.percent_sampled < 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 ) + SELECT 92 AS check_id, + 90 AS Priority, + 'Functioning Statistaholics' AS findings_group, + 'Statistics With NO RECOMPUTE', + s.database_name, + 'https://www.brentozar.com/go/stats' AS URL, + 'The statistic ' + QUOTENAME(s.statistics_name) + ' is set to not recompute. This can be helpful if data is really skewed, but harmful if you expect automatic statistics updates.' 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.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 ) + SELECT 94 AS check_id, + 100 AS Priority, + 'Serial Forcer' AS findings_group, + 'Check Constraint with Scalar UDF' AS finding, + cc.database_name, + 'https://www.brentozar.com/go/computedscalar' AS URL, + 'The check constraint ' + QUOTENAME(cc.constraint_name) + ' on ' + QUOTENAME(cc.schema_name) + '.' + QUOTENAME(cc.table_name) + ' is based on ' + cc.definition + + '. That indicates it may reference a scalar function, or a CLR function with data access, which can cause all queries and maintenance to run serially.' AS details, + cc.column_definition, + 'N/A' AS secret_columns, + 'N/A' AS index_usage_summary, + 'N/A' AS index_size_summary + FROM #CheckConstraints AS cc + WHERE cc.is_function = 1 + OPTION ( RECOMPILE ); + + RAISERROR(N'check_id 99: Computed Columns 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 ) + SELECT 99 AS check_id, + 100 AS Priority, + 'Serial Forcer' AS findings_group, + 'Computed Column with Scalar UDF' AS finding, + cc.database_name, + 'https://www.brentozar.com/go/serialudf' AS URL, + 'The computed column ' + QUOTENAME(cc.column_name) + ' on ' + QUOTENAME(cc.schema_name) + '.' + QUOTENAME(cc.table_name) + ' is based on ' + cc.definition + + '. That indicates it may reference a scalar function, or a CLR function with data access, which can cause all queries and maintenance to run serially.' AS details, + cc.column_definition, + 'N/A' AS secret_columns, + 'N/A' AS index_usage_summary, + 'N/A' AS index_size_summary + FROM #ComputedColumns AS cc + WHERE cc.is_function = 1 + OPTION ( RECOMPILE ); + + + + END /* IF @Mode IN (0, 4) DIAGNOSE priorities 1-100 */ + + + + + + + + + + + + + + + + + + + + + + + + + + IF @Mode = 4 /* DIAGNOSE*/ + BEGIN; + RAISERROR(N'@Mode=4, running rules for priorities 101+.', 0,1) WITH NOWAIT; + + RAISERROR(N'check_id 21: More Than 5 Percent NC Indexes Are Unused', 0,1) WITH NOWAIT; + DECLARE @percent_NC_indexes_unused NUMERIC(29,1); + DECLARE @NC_indexes_unused_reserved_MB NUMERIC(29,1); + + SELECT @percent_NC_indexes_unused = ( 100.00 * SUM(CASE + WHEN total_reads = 0 + THEN 1 + ELSE 0 + END) ) / COUNT(*), + @NC_indexes_unused_reserved_MB = SUM(CASE + WHEN total_reads = 0 + THEN sz.total_reserved_MB + ELSE 0 + END) + FROM #IndexSanity i + 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()) + 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, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 21 AS check_id, + MAX(i.index_sanity_id) AS index_sanity_id, + 150 AS Priority, + N'Index Hoarder' AS findings_group, + N'More Than 5 Percent NC Indexes Are Unused' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/IndexHoarder' AS URL, + CAST (@percent_NC_indexes_unused AS NVARCHAR(30)) + N' percent NC indexes (' + CAST(COUNT(*) AS NVARCHAR(10)) + N') unused. ' + + N'These take up ' + CAST (@NC_indexes_unused_reserved_MB AS NVARCHAR(30)) + N'MB of space.' AS details, + i.database_name + ' (' + CAST (COUNT(*) AS NVARCHAR(30)) + N' indexes)' AS index_definition, + '' AS secret_columns, + CAST(SUM(total_reads) AS NVARCHAR(256)) + N' reads (ALL); ' + + CAST(SUM([user_updates]) AS NVARCHAR(256)) + N' writes (ALL)' AS index_usage_summary, + + REPLACE(CONVERT(NVARCHAR(30),CAST(MAX([total_rows]) AS MONEY), 1), '.00', '') + N' rows (MAX)' + + CASE WHEN SUM(total_reserved_MB) > 1024 THEN + N'; ' + CAST(CAST(SUM(total_reserved_MB)/1024. AS NUMERIC(29,1)) AS NVARCHAR(30)) + 'GB (ALL)' + WHEN SUM(total_reserved_MB) > 0 THEN + N'; ' + CAST(CAST(SUM(total_reserved_MB) AS NUMERIC(29,1)) AS NVARCHAR(30)) + 'MB (ALL)' + ELSE '' + END AS index_size_summary + FROM #IndexSanity i + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + 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()) + GROUP BY i.database_name + OPTION ( RECOMPILE ); + + RAISERROR(N'check_id 23: Indexes with 7 or more columns. (Borderline)', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 23 AS check_id, + i.index_sanity_id, + 150 AS Priority, + N'Index Hoarder' AS findings_group, + N'Borderline: Wide Indexes (7 or More Columns)' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/IndexHoarder' AS URL, + CAST(count_key_columns + count_included_columns AS NVARCHAR(10)) + ' columns on ' + + i.db_schema_object_indexid AS details, i.index_definition, + i.secret_columns, + i.index_usage_summary, + sz.index_size_summary + FROM #IndexSanity AS i + JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id + WHERE ( count_key_columns + count_included_columns ) >= 7 + OPTION ( RECOMPILE ); + + RAISERROR(N'check_id 24: Wide clustered indexes (> 3 columns or > 16 bytes).', 0,1) WITH NOWAIT; + WITH count_columns AS ( + SELECT database_id, [object_id], + SUM(CASE max_length WHEN -1 THEN 0 ELSE max_length END) AS sum_max_length + FROM #IndexColumns ic + WHERE index_id IN (1,0) /*Heap or clustered only*/ + AND key_ordinal > 0 + GROUP BY database_id, object_id + ) + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 24 AS check_id, + i.index_sanity_id, + 150 AS Priority, + N'Index Hoarder' AS findings_group, + N'Wide Clustered Index (> 3 columns OR > 16 bytes)' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/IndexHoarder' AS URL, + CAST (i.count_key_columns AS NVARCHAR(10)) + N' columns with potential size of ' + + CAST(cc.sum_max_length AS NVARCHAR(10)) + + N' bytes in clustered index:' + i.db_schema_object_name + + N'. ' + + (SELECT CAST(COUNT(*) AS NVARCHAR(23)) + FROM #IndexSanity i2 + WHERE i2.[object_id]=i.[object_id] + AND i2.database_id = i.database_id + AND i2.index_id <> 1 + AND i2.is_disabled=0 + AND i2.is_hypothetical=0) + + N' NC indexes on the table.' + AS details, + i.index_definition, + secret_columns, + i.index_usage_summary, + ip.index_size_summary + FROM #IndexSanity i + JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id + JOIN count_columns AS cc ON i.[object_id]=cc.[object_id] + AND i.database_id = cc.database_id + WHERE index_id = 1 /* clustered only */ + AND + (count_key_columns > 3 /*More than three key columns.*/ + OR cc.sum_max_length > 16 /*More than 16 bytes in key */) + AND i.is_CX_columnstore = 0 + ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); + + RAISERROR(N'check_id 25: Addicted to nullable columns.', 0,1) WITH NOWAIT; + WITH count_columns AS ( + SELECT [object_id], + [database_id], + [schema_name], + SUM(CASE is_nullable WHEN 1 THEN 0 ELSE 1 END) AS non_nullable_columns, + COUNT(*) AS total_columns + FROM #IndexColumns ic + WHERE index_id IN (1,0) /*Heap or clustered only*/ + GROUP BY [object_id], + [database_id], + [schema_name] + ) + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 25 AS check_id, + i.index_sanity_id, + 200 AS Priority, + N'Index Hoarder' AS findings_group, + N'Addicted to Nulls' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/IndexHoarder' AS URL, + i.db_schema_object_name + + N' allows null in ' + CAST((total_columns-non_nullable_columns) AS NVARCHAR(10)) + + N' of ' + CAST(total_columns AS NVARCHAR(10)) + + N' columns.' AS details, + i.index_definition, + secret_columns, + ISNULL(i.index_usage_summary,''), + ISNULL(ip.index_size_summary,'') + FROM #IndexSanity i + JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id + JOIN count_columns AS cc ON i.[object_id]=cc.[object_id] + AND cc.database_id = ip.database_id + AND cc.[schema_name] = ip.[schema_name] + WHERE i.index_id IN (1,0) + AND cc.non_nullable_columns < 2 + AND cc.total_columns > 3 + ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); + + RAISERROR(N'check_id 26: Wide tables (35+ cols or > 2000 non-LOB bytes).', 0,1) WITH NOWAIT; + WITH count_columns AS ( + SELECT [object_id], + [database_id], + [schema_name], + SUM(CASE max_length WHEN -1 THEN 1 ELSE 0 END) AS count_lob_columns, + SUM(CASE max_length WHEN -1 THEN 0 ELSE max_length END) AS sum_max_length, + COUNT(*) AS total_columns + FROM #IndexColumns ic + WHERE index_id IN (1,0) /*Heap or clustered only*/ + GROUP BY [object_id], + [database_id], + [schema_name] + ) + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 26 AS check_id, + i.index_sanity_id, + 150 AS Priority, + N'Index Hoarder' AS findings_group, + N'Wide Tables: 35+ cols or > 2000 non-LOB bytes' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/IndexHoarder' AS URL, + i.db_schema_object_name + + N' has ' + CAST((total_columns) AS NVARCHAR(10)) + + N' total columns with a max possible width of ' + CAST(sum_max_length AS NVARCHAR(10)) + + N' bytes.' + + CASE WHEN count_lob_columns > 0 THEN CAST((count_lob_columns) AS NVARCHAR(10)) + + ' columns are LOB types.' ELSE '' + END + AS details, + i.index_definition, + secret_columns, + ISNULL(i.index_usage_summary,''), + ISNULL(ip.index_size_summary,'') + FROM #IndexSanity i + JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id + JOIN count_columns AS cc ON i.[object_id]=cc.[object_id] + AND cc.database_id = i.database_id + AND cc.[schema_name] = i.[schema_name] + WHERE i.index_id IN (1,0) + AND + (cc.total_columns >= 35 OR + cc.sum_max_length >= 2000) + ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); + + RAISERROR(N'check_id 27: Addicted to strings.', 0,1) WITH NOWAIT; + WITH count_columns AS ( + SELECT [object_id], + [database_id], + [schema_name], + SUM(CASE WHEN system_type_name IN ('varchar','nvarchar','char') OR max_length=-1 THEN 1 ELSE 0 END) AS string_or_LOB_columns, + COUNT(*) AS total_columns + FROM #IndexColumns ic + WHERE index_id IN (1,0) /*Heap or clustered only*/ + GROUP BY [object_id], + [database_id], + [schema_name] + ) + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 27 AS check_id, + i.index_sanity_id, + 200 AS Priority, + N'Index Hoarder' AS findings_group, + N'Addicted to strings' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/IndexHoarder' AS URL, + i.db_schema_object_name + + N' uses string or LOB types for ' + CAST((string_or_LOB_columns) AS NVARCHAR(10)) + + N' of ' + CAST(total_columns AS NVARCHAR(10)) + + N' columns. Check if data types are valid.' AS details, + i.index_definition, + secret_columns, + ISNULL(i.index_usage_summary,''), + ISNULL(ip.index_size_summary,'') + FROM #IndexSanity i + JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id + JOIN count_columns AS cc ON i.[object_id]=cc.[object_id] + AND cc.database_id = i.database_id + AND cc.[schema_name] = i.[schema_name] + CROSS APPLY (SELECT cc.total_columns - string_or_LOB_columns AS non_string_or_lob_columns) AS calc1 + WHERE i.index_id IN (1,0) + AND calc1.non_string_or_lob_columns <= 1 + AND cc.total_columns > 3 + ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); + + RAISERROR(N'check_id 28: Non-unique clustered index.', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 28 AS check_id, + i.index_sanity_id, + 150 AS Priority, + N'Index Hoarder' AS findings_group, + N'Non-Unique Clustered JIndex' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/IndexHoarder' AS URL, + N'Uniquifiers will be required! Clustered index: ' + i.db_schema_object_name + + N' and all NC indexes. ' + + (SELECT CAST(COUNT(*) AS NVARCHAR(23)) + FROM #IndexSanity i2 + WHERE i2.[object_id]=i.[object_id] + AND i2.database_id = i.database_id + AND i2.index_id <> 1 + AND i2.is_disabled=0 + AND i2.is_hypothetical=0) + + N' NC indexes on the table.' + AS details, + i.index_definition, + secret_columns, + i.index_usage_summary, + ip.index_size_summary + FROM #IndexSanity i + JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id + WHERE index_id = 1 /* clustered only */ + AND is_unique=0 /* not unique */ + AND is_CX_columnstore=0 /* not a clustered columnstore-- no unique option on those */ + ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); + + RAISERROR(N'check_id 29: NC indexes with 0 reads. (Borderline) and < 10,000 writes', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 29 AS check_id, + i.index_sanity_id, + 150 AS Priority, + N'Index Hoarder' AS findings_group, + N'Unused NC index with Low Writes' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/IndexHoarder' AS URL, + N'0 reads: ' + i.db_schema_object_indexid AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + sz.index_size_summary + FROM #IndexSanity AS i + JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id + WHERE i.total_reads=0 + AND i.user_updates < 10000 + 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()) + ORDER BY i.db_schema_object_indexid + OPTION ( RECOMPILE ); + + + ---------------------------------------- + --Feature-Phobic Indexes: Check_id 30-39 + ---------------------------------------- + RAISERROR(N'check_id 30: No indexes with includes', 0,1) WITH NOWAIT; + /* This does not work the way you'd expect with @GetAllDatabases = 1. For details: + https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/825 + */ + + SELECT database_name, + SUM(CASE WHEN count_included_columns > 0 THEN 1 ELSE 0 END) AS number_indexes_with_includes, + 100.* SUM(CASE WHEN count_included_columns > 0 THEN 1 ELSE 0 END) / ( 1.0 * COUNT(*) ) AS percent_indexes_with_includes + INTO #index_includes + FROM #IndexSanity + WHERE is_hypothetical = 0 + AND is_disabled = 0 + AND NOT (@GetAllDatabases = 1 OR @Mode = 0) + GROUP BY database_name; + + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 30 AS check_id, + NULL AS index_sanity_id, + 250 AS Priority, + N'Feature-Phobic Indexes' AS findings_group, + database_name AS [Database Name], + N'No Indexes Use Includes' AS finding, 'https://www.brentozar.com/go/IndexFeatures' AS URL, + N'No Indexes Use Includes' AS details, + database_name + N' (Entire database)' AS index_definition, + N'' AS secret_columns, + N'N/A' AS index_usage_summary, + N'N/A' AS index_size_summary + FROM #index_includes + WHERE number_indexes_with_includes = 0 + OPTION ( RECOMPILE ); + + RAISERROR(N'check_id 31: < 3 percent of indexes have includes', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 31 AS check_id, + NULL AS index_sanity_id, + 250 AS Priority, + N'Feature-Phobic Indexes' AS findings_group, + N'Few Indexes Use Includes' AS findings, + database_name AS [Database Name], + N'https://www.brentozar.com/go/IndexFeatures' AS URL, + N'Only ' + CAST(percent_indexes_with_includes AS NVARCHAR(20)) + '% of indexes have includes' AS details, + N'Entire database' AS index_definition, + N'' AS secret_columns, + N'N/A' AS index_usage_summary, + N'N/A' AS index_size_summary + FROM #index_includes + WHERE number_indexes_with_includes > 0 AND percent_indexes_with_includes <= 3 + OPTION ( RECOMPILE ); + + RAISERROR(N'check_id 32: filtered indexes and indexed views', 0,1) WITH NOWAIT; + + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT DISTINCT + 32 AS check_id, + NULL AS index_sanity_id, + 250 AS Priority, + N'Feature-Phobic Indexes' AS findings_group, + N'No Filtered Indexes or Indexed Views' AS finding, + i.database_name AS [Database Name], + N'https://www.brentozar.com/go/IndexFeatures' AS URL, + N'These are NOT always needed-- but do you know when you would use them?' AS details, + i.database_name + N' (Entire database)' AS index_definition, + N'' AS secret_columns, + N'N/A' AS index_usage_summary, + N'N/A' AS index_size_summary + FROM #IndexSanity i + WHERE i.database_name NOT IN ( + SELECT database_name + FROM #IndexSanity + WHERE filter_definition <> '' ) + AND i.database_name NOT IN ( + SELECT database_name + FROM #IndexSanity + WHERE is_indexed_view = 1 ) + OPTION ( RECOMPILE ); + + RAISERROR(N'check_id 33: Potential filtered indexes based on column names.', 0,1) WITH NOWAIT; + + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 33 AS check_id, + i.index_sanity_id AS index_sanity_id, + 250 AS Priority, + N'Feature-Phobic Indexes' AS findings_group, + N'Potential Filtered Index (Based on Column Name)' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/IndexFeatures' AS URL, + N'A column name in this index suggests it might be a candidate for filtering (is%, %archive%, %active%, %flag%)' AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + sz.index_size_summary + FROM #IndexColumns ic + JOIN #IndexSanity i ON ic.[object_id]=i.[object_id] + AND ic.database_id =i.database_id + AND ic.schema_name = i.schema_name + AND ic.[index_id]=i.[index_id] + AND i.[index_id] > 1 /* non-clustered index */ + JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id + WHERE (column_name LIKE 'is%' + OR column_name LIKE '%archive%' + OR column_name LIKE '%active%' + OR column_name LIKE '%flag%') + OPTION ( RECOMPILE ); + + RAISERROR(N'check_id 41: Hypothetical indexes ', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 41 AS check_id, + i.index_sanity_id, + 150 AS Priority, + N'Self Loathing Indexes' AS findings_group, + N'Hypothetical Index' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/SelfLoathing' AS URL, + N'Hypothetical Index: ' + db_schema_object_indexid AS details, + i.index_definition, + i.secret_columns, + N'' AS index_usage_summary, + N'' AS index_size_summary + FROM #IndexSanity AS i + WHERE is_hypothetical = 1 + OPTION ( RECOMPILE ); + + + RAISERROR(N'check_id 42: Disabled indexes', 0,1) WITH NOWAIT; + --Note: disabled NC indexes will have O rows in #IndexSanitySize! + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 42 AS check_id, + index_sanity_id, + 150 AS Priority, + N'Self Loathing Indexes' AS findings_group, + N'Disabled Index' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/SelfLoathing' AS URL, + N'Disabled Index:' + db_schema_object_indexid AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + 'DISABLED' AS index_size_summary + FROM #IndexSanity AS i + WHERE is_disabled = 1 + OPTION ( RECOMPILE ); + + RAISERROR(N'check_id 49: Heaps with deletes', 0,1) WITH NOWAIT; + WITH heaps_cte + AS ( SELECT [object_id], + [database_id], + [schema_name], + SUM(leaf_delete_count) AS leaf_delete_count + FROM #IndexPartitionSanity + GROUP BY [object_id], + [database_id], + [schema_name] + HAVING SUM(forwarded_fetch_count) < 1000 * @DaysUptime /* Only alert about indexes with no forwarded fetches - we already alerted about those in check_id 43 */ + AND SUM(leaf_delete_count) > 0) + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 49 AS check_id, + i.index_sanity_id, + 200 AS Priority, + N'Self Loathing Indexes' AS findings_group, + N'Heaps with Deletes' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/SelfLoathing' AS URL, + CAST(h.leaf_delete_count AS NVARCHAR(256)) + N' deletes against heap:' + + db_schema_object_indexid AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + sz.index_size_summary + FROM #IndexSanity i + JOIN heaps_cte h ON i.[object_id] = h.[object_id] + AND i.[database_id] = h.[database_id] + AND i.[schema_name] = h.[schema_name] + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + WHERE i.index_id = 0 + AND sz.total_reserved_MB >= CASE WHEN NOT (@GetAllDatabases = 1 OR @Mode = 4) THEN @ThresholdMB ELSE sz.total_reserved_MB END + OPTION ( RECOMPILE ); + ---------------------------------------- --Abnormal Psychology : Check_id 60-79 ---------------------------------------- - BEGIN RAISERROR(N'check_id 60: XML indexes', 0,1) WITH NOWAIT; INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, secret_columns, index_usage_summary, index_size_summary ) @@ -26623,9 +26934,9 @@ BEGIN; i.index_sanity_id, 150 AS Priority, N'Abnormal Psychology' AS findings_group, - N'XML Indexes' AS finding, + N'XML Index' AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/AbnormalPsychology' AS URL, + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, i.db_schema_object_indexid AS details, i.index_definition, i.secret_columns, @@ -26634,7 +26945,6 @@ BEGIN; FROM #IndexSanity AS i JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id WHERE i.is_XML = 1 - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) OPTION ( RECOMPILE ); RAISERROR(N'check_id 61: Columnstore indexes', 0,1) WITH NOWAIT; @@ -26649,7 +26959,7 @@ BEGIN; ELSE N'Clustered Columnstore Index' END AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/AbnormalPsychology' AS URL, + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, i.db_schema_object_indexid AS details, i.index_definition, i.secret_columns, @@ -26658,7 +26968,6 @@ BEGIN; FROM #IndexSanity AS i JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id WHERE i.is_NC_columnstore = 1 OR i.is_CX_columnstore=1 - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) OPTION ( RECOMPILE ); @@ -26669,9 +26978,9 @@ BEGIN; i.index_sanity_id, 150 AS Priority, N'Abnormal Psychology' AS findings_group, - N'Spatial indexes' AS finding, + N'Spatial Index' AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/AbnormalPsychology' AS URL, + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, i.db_schema_object_indexid AS details, i.index_definition, i.secret_columns, @@ -26680,7 +26989,6 @@ BEGIN; FROM #IndexSanity AS i JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id WHERE i.is_spatial = 1 - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) OPTION ( RECOMPILE ); RAISERROR(N'check_id 63: Compressed indexes', 0,1) WITH NOWAIT; @@ -26690,9 +26998,9 @@ BEGIN; i.index_sanity_id, 150 AS Priority, N'Abnormal Psychology' AS findings_group, - N'Compressed indexes' AS finding, + N'Compressed Index' AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/AbnormalPsychology' AS URL, + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, i.db_schema_object_indexid + N'. COMPRESSION: ' + sz.data_compression_desc AS details, i.index_definition, i.secret_columns, @@ -26701,7 +27009,6 @@ BEGIN; FROM #IndexSanity AS i JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id WHERE sz.data_compression_desc LIKE '%PAGE%' OR sz.data_compression_desc LIKE '%ROW%' - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) OPTION ( RECOMPILE ); RAISERROR(N'check_id 64: Partitioned', 0,1) WITH NOWAIT; @@ -26711,9 +27018,9 @@ BEGIN; i.index_sanity_id, 150 AS Priority, N'Abnormal Psychology' AS findings_group, - N'Partitioned indexes' AS finding, + N'Partitioned Index' AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/AbnormalPsychology' AS URL, + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, i.db_schema_object_indexid AS details, i.index_definition, i.secret_columns, @@ -26722,7 +27029,6 @@ BEGIN; FROM #IndexSanity AS i JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id WHERE i.partition_key_column_name IS NOT NULL - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) OPTION ( RECOMPILE ); RAISERROR(N'check_id 65: Non-Aligned Partitioned', 0,1) WITH NOWAIT; @@ -26732,9 +27038,9 @@ BEGIN; i.index_sanity_id, 150 AS Priority, N'Abnormal Psychology' AS findings_group, - N'Non-Aligned index on a partitioned table' AS finding, + N'Non-Aligned Index on a Partitioned Table' AS finding, i.[database_name] AS [Database Name], - N'http://BrentOzar.com/go/AbnormalPsychology' AS URL, + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, i.db_schema_object_indexid AS details, i.index_definition, i.secret_columns, @@ -26758,9 +27064,9 @@ BEGIN; i.index_sanity_id, 200 AS Priority, N'Abnormal Psychology' AS findings_group, - N'Recently created tables/indexes (1 week)' AS finding, + N'Recently Created Tables/Indexes (1 week)' AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/AbnormalPsychology' AS URL, + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, i.db_schema_object_indexid + N' was created on ' + CONVERT(NVARCHAR(16),i.create_date,121) + N'. Tables/indexes which are dropped/created regularly require special methods for index tuning.' @@ -26772,7 +27078,6 @@ BEGIN; FROM #IndexSanity AS i JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id WHERE i.create_date >= DATEADD(dd,-7,GETDATE()) - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) OPTION ( RECOMPILE ); RAISERROR(N'check_id 67: Recently modified tables/indexes (2 days)', 0,1) WITH NOWAIT; @@ -26782,9 +27087,9 @@ BEGIN; i.index_sanity_id, 200 AS Priority, N'Abnormal Psychology' AS findings_group, - N'Recently modified tables/indexes (2 days)' AS finding, + N'Recently Modified Tables/Indexes (2 days)' AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/AbnormalPsychology' AS URL, + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, i.db_schema_object_indexid + N' was modified on ' + CONVERT(NVARCHAR(16),i.modify_date,121) + N'. A large amount of recently modified indexes may mean a lot of rebuilds are occurring each night.' @@ -26796,115 +27101,10 @@ BEGIN; FROM #IndexSanity AS i JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id WHERE i.modify_date > DATEADD(dd,-2,GETDATE()) - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) AND /*Exclude recently created tables.*/ i.create_date < DATEADD(dd,-7,GETDATE()) OPTION ( RECOMPILE ); - RAISERROR(N'check_id 68: Identity columns within 30 percent of the end of range', 0,1) WITH NOWAIT; - -- Allowed Ranges: - --int -2,147,483,648 to 2,147,483,647 - --smallint -32,768 to 32,768 - --tinyint 0 to 255 - - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 68 AS check_id, - i.index_sanity_id, - 200 AS Priority, - N'Abnormal Psychology' AS findings_group, - N'Identity column within ' + - CAST (calc1.percent_remaining AS NVARCHAR(256)) - + N' percent end of range' AS finding, - [database_name] AS [Database Name], - N'http://BrentOzar.com/go/AbnormalPsychology' AS URL, - i.db_schema_object_name + N'.' + QUOTENAME(ic.column_name) - + N' is an identity with type ' + ic.system_type_name - + N', last value of ' - + ISNULL((CONVERT(NVARCHAR(256),CAST(ic.last_value AS DECIMAL(38,0)), 1)),N'NULL') - + N', seed of ' - + ISNULL((CONVERT(NVARCHAR(256),CAST(ic.seed_value AS DECIMAL(38,0)), 1)),N'NULL') - + N', increment of ' + CAST(ic.increment_value AS NVARCHAR(256)) - + N', and range of ' + - CASE ic.system_type_name WHEN 'int' THEN N'+/- 2,147,483,647' - WHEN 'smallint' THEN N'+/- 32,768' - WHEN 'tinyint' THEN N'0 to 255' - ELSE 'unknown' - END - AS details, - i.index_definition, - secret_columns, - ISNULL(i.index_usage_summary,''), - ISNULL(ip.index_size_summary,'') - FROM #IndexSanity i - JOIN #IndexColumns ic ON - i.object_id=ic.object_id - AND i.database_id = ic.database_id - AND i.schema_name = ic.schema_name - AND i.index_id IN (0,1) /* heaps and cx only */ - AND ic.is_identity=1 - AND ic.system_type_name IN ('tinyint', 'smallint', 'int') - JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id - CROSS APPLY ( - SELECT CAST(CASE WHEN ic.increment_value >= 0 - THEN - CASE ic.system_type_name - WHEN 'int' THEN (2147483647 - (ISNULL(ic.last_value,ic.seed_value) + ic.increment_value)) / 2147483647.*100 - WHEN 'smallint' THEN (32768 - (ISNULL(ic.last_value,ic.seed_value) + ic.increment_value)) / 32768.*100 - WHEN 'tinyint' THEN ( 255 - (ISNULL(ic.last_value,ic.seed_value) + ic.increment_value)) / 255.*100 - ELSE 999 - END - ELSE --ic.increment_value is negative - CASE ic.system_type_name - WHEN 'int' THEN ABS(-2147483647 - (ISNULL(ic.last_value,ic.seed_value) + ic.increment_value)) / 2147483647.*100 - WHEN 'smallint' THEN ABS(-32768 - (ISNULL(ic.last_value,ic.seed_value) + ic.increment_value)) / 32768.*100 - WHEN 'tinyint' THEN ABS( 0 - (ISNULL(ic.last_value,ic.seed_value) + ic.increment_value)) / 255.*100 - ELSE -1 - END - END AS NUMERIC(5,1)) AS percent_remaining - ) AS calc1 - WHERE i.index_id IN (1,0) - AND calc1.percent_remaining <= 30 - UNION ALL - SELECT 68 AS check_id, - i.index_sanity_id, - 200 AS Priority, - N'Abnormal Psychology' AS findings_group, - N'Identity column using a negative seed or increment other than 1' AS finding, - [database_name] AS [Database Name], - N'http://BrentOzar.com/go/AbnormalPsychology' AS URL, - i.db_schema_object_name + N'.' + QUOTENAME(ic.column_name) - + N' is an identity with type ' + ic.system_type_name - + N', last value of ' - + ISNULL((CONVERT(NVARCHAR(256),CAST(ic.last_value AS DECIMAL(38,0)), 1)),N'NULL') - + N', seed of ' - + ISNULL((CONVERT(NVARCHAR(256),CAST(ic.seed_value AS DECIMAL(38,0)), 1)),N'NULL') - + N', increment of ' + CAST(ic.increment_value AS NVARCHAR(256)) - + N', and range of ' + - CASE ic.system_type_name WHEN 'int' THEN N'+/- 2,147,483,647' - WHEN 'smallint' THEN N'+/- 32,768' - WHEN 'tinyint' THEN N'0 to 255' - ELSE 'unknown' - END - AS details, - i.index_definition, - secret_columns, - ISNULL(i.index_usage_summary,''), - ISNULL(ip.index_size_summary,'') - FROM #IndexSanity i - JOIN #IndexColumns ic ON - i.object_id=ic.object_id - AND i.database_id = ic.database_id - AND i.schema_name = ic.schema_name - AND i.index_id IN (0,1) /* heaps and cx only */ - AND ic.is_identity=1 - AND ic.system_type_name IN ('tinyint', 'smallint', 'int') - JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id - WHERE i.index_id IN (1,0) - AND (ic.seed_value < 0 OR ic.increment_value <> 1) - ORDER BY finding, details DESC - OPTION ( RECOMPILE ); - RAISERROR(N'check_id 69: Column collation does not match database collation', 0,1) WITH NOWAIT; WITH count_columns AS ( SELECT [object_id], @@ -26924,9 +27124,9 @@ BEGIN; i.index_sanity_id, 150 AS Priority, N'Abnormal Psychology' AS findings_group, - N'Column collation does not match database collation' AS finding, + N'Column Collation Does Not Match Database Collation' AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/AbnormalPsychology' AS URL, + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, i.db_schema_object_name + N' has ' + CAST(column_count AS NVARCHAR(20)) + N' column' + CASE WHEN column_count > 1 THEN 's' ELSE '' END @@ -26942,7 +27142,6 @@ BEGIN; AND cc.database_id = i.database_id AND cc.schema_name = i.schema_name WHERE i.index_id IN (1,0) - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); RAISERROR(N'check_id 70: Replicated columns', 0,1) WITH NOWAIT; @@ -26964,9 +27163,9 @@ BEGIN; i.index_sanity_id, 200 AS Priority, N'Abnormal Psychology' AS findings_group, - N'Replicated columns' AS finding, + N'Replicated Columns' AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/AbnormalPsychology' AS URL, + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, i.db_schema_object_name + N' has ' + CAST(replicated_column_count AS NVARCHAR(20)) + N' out of ' + CAST(column_count AS NVARCHAR(20)) @@ -26984,7 +27183,6 @@ BEGIN; AND i.schema_name = cc.schema_name WHERE i.index_id IN (1,0) AND replicated_column_count > 0 - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); @@ -26997,7 +27195,7 @@ BEGIN; N'Abnormal Psychology' AS findings_group, N'Cascading Updates or Deletes' AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/AbnormalPsychology' AS URL, + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, N'Foreign Key ' + foreign_key_name + N' on ' + QUOTENAME(parent_object_name) + N'(' + LTRIM(parent_fk_columns) + N')' + N' referencing ' + QUOTENAME(referenced_object_name) + N'(' + LTRIM(referenced_fk_columns) + N')' @@ -27015,32 +27213,8 @@ BEGIN; FROM #ForeignKeys fk WHERE ([delete_referential_action_desc] <> N'NO_ACTION' OR [update_referential_action_desc] <> N'NO_ACTION') - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) OPTION ( RECOMPILE ); - RAISERROR(N'check_id 72: Columnstore indexes with Trace Flag 834', 0,1) WITH NOWAIT; - IF EXISTS (SELECT * FROM #IndexSanity WHERE index_type IN (5,6)) - AND EXISTS (SELECT * FROM #TraceStatus WHERE TraceFlag = 834 AND status = 1) - BEGIN - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 72 AS check_id, - i.index_sanity_id, - 150 AS Priority, - N'Abnormal Psychology' AS findings_group, - 'Columnstore Indexes are being used in conjunction with trace flag 834. Visit the link to see why this can be a bad idea' AS finding, - [database_name] AS [Database Name], - N'https://support.microsoft.com/en-us/kb/3210239' AS URL, - i.db_schema_object_indexid AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - ISNULL(sz.index_size_summary,'') AS index_size_summary - FROM #IndexSanity AS i - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.index_type IN (5,6) - OPTION ( RECOMPILE ); - END; RAISERROR(N'check_id 73: In-Memory OLTP', 0,1) WITH NOWAIT; INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, @@ -27051,7 +27225,7 @@ BEGIN; N'Abnormal Psychology' AS findings_group, N'In-Memory OLTP' AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/AbnormalPsychology' AS URL, + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, i.db_schema_object_indexid AS details, i.index_definition, i.secret_columns, @@ -27060,15 +27234,53 @@ BEGIN; FROM #IndexSanity AS i JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id WHERE i.is_in_memory_oltp = 1 - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) OPTION ( RECOMPILE ); - END; + RAISERROR(N'check_id 74: Identity column with unusual seed', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 74 AS check_id, + i.index_sanity_id, + 200 AS Priority, + N'Abnormal Psychology' AS findings_group, + N'Identity Column Using a Negative Seed or Increment Other Than 1' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, + i.db_schema_object_name + N'.' + QUOTENAME(ic.column_name) + + N' is an identity with type ' + ic.system_type_name + + N', last value of ' + + ISNULL((CONVERT(NVARCHAR(256),CAST(ic.last_value AS DECIMAL(38,0)), 1)),N'NULL') + + N', seed of ' + + ISNULL((CONVERT(NVARCHAR(256),CAST(ic.seed_value AS DECIMAL(38,0)), 1)),N'NULL') + + N', increment of ' + CAST(ic.increment_value AS NVARCHAR(256)) + + N', and range of ' + + CASE ic.system_type_name WHEN 'int' THEN N'+/- 2,147,483,647' + WHEN 'smallint' THEN N'+/- 32,768' + WHEN 'tinyint' THEN N'0 to 255' + ELSE 'unknown' + END + AS details, + i.index_definition, + secret_columns, + ISNULL(i.index_usage_summary,''), + ISNULL(ip.index_size_summary,'') + FROM #IndexSanity i + JOIN #IndexColumns ic ON + i.object_id=ic.object_id + AND i.database_id = ic.database_id + AND i.schema_name = ic.schema_name + AND i.index_id IN (0,1) /* heaps and cx only */ + AND ic.is_identity=1 + AND ic.system_type_name IN ('tinyint', 'smallint', 'int') + JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id + WHERE i.index_id IN (1,0) + AND (ic.seed_value < 0 OR ic.increment_value <> 1) + ORDER BY finding, details DESC + OPTION ( RECOMPILE ); - ---------------------------------------- + ---------------------------------------- --Workaholics: Check_id 80-89 ---------------------------------------- - BEGIN RAISERROR(N'check_id 80: Most scanned indexes (index_usage_stats)', 0,1) WITH NOWAIT; INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, @@ -27083,9 +27295,9 @@ BEGIN; i.index_sanity_id AS index_sanity_id, 200 AS Priority, N'Workaholics' AS findings_group, - N'Scan-a-lots (index_usage_stats)' AS finding, + N'Scan-a-lots (index-usage-stats)' AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/Workaholics' AS URL, + N'https://www.brentozar.com/go/Workaholics' AS URL, REPLACE(CONVERT( NVARCHAR(50),CAST(i.user_scans AS MONEY),1),'.00','') + N' scans against ' + i.db_schema_object_indexid + N'. Latest scan: ' + ISNULL(CAST(i.last_user_scan AS NVARCHAR(128)),'?') + N'. ' @@ -27097,7 +27309,6 @@ BEGIN; FROM #IndexSanity i JOIN #IndexSanitySize iss ON i.index_sanity_id=iss.index_sanity_id WHERE ISNULL(i.user_scans,0) > 0 - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) ORDER BY i.user_scans * iss.total_reserved_MB DESC OPTION ( RECOMPILE ); @@ -27112,9 +27323,9 @@ BEGIN; i.index_sanity_id AS index_sanity_id, 200 AS Priority, N'Workaholics' AS findings_group, - N'Top recent accesses (index_op_stats)' AS finding, + N'Top Recent Accesses (index-op-stats)' AS finding, [database_name] AS [Database Name], - N'http://BrentOzar.com/go/Workaholics' AS URL, + N'https://www.brentozar.com/go/Workaholics' AS URL, ISNULL(REPLACE( CONVERT(NVARCHAR(50),CAST((iss.total_range_scan_count + iss.total_singleton_lookup_count) AS MONEY),1), N'.00',N'') @@ -27129,84 +27340,10 @@ BEGIN; FROM #IndexSanity i JOIN #IndexSanitySize iss ON i.index_sanity_id=iss.index_sanity_id WHERE (ISNULL(iss.total_range_scan_count,0) > 0 OR ISNULL(iss.total_singleton_lookup_count,0) > 0) - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) ORDER BY ((iss.total_range_scan_count + iss.total_singleton_lookup_count) * iss.total_reserved_MB) DESC OPTION ( RECOMPILE ); - END; - - ---------------------------------------- - --Statistics Info: Check_id 90-99 - ---------------------------------------- - BEGIN - - RAISERROR(N'check_id 90: Outdated 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 ) - SELECT 90 AS check_id, - 200 AS Priority, - 'Functioning Statistaholics' AS findings_group, - 'Statistic Abandonment Issues', - s.database_name, - '' AS URL, - 'Statistics on this table were last updated ' + - CASE s.last_statistics_update WHEN NULL THEN N' NEVER ' - ELSE CONVERT(NVARCHAR(20), s.last_statistics_update) + - ' have had ' + CONVERT(NVARCHAR(100), s.modification_counter) + - ' modifications in that time, which is ' + - CONVERT(NVARCHAR(100), s.percent_modifications) + - '% of the table.' - END 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.last_statistics_update <= CONVERT(DATETIME, GETDATE() - 7) - AND s.percent_modifications >= 10. - AND s.rows >= 10000 - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 91: Statistics with a low 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 91 AS check_id, - 200 AS Priority, - 'Functioning Statistaholics' AS findings_group, - 'Antisocial Samples', - s.database_name, - '' AS URL, - 'Only ' + CONVERT(NVARCHAR(100), s.percent_sampled) + '% of the rows were sampled during the last statistics update. This may lead to poor cardinality estimates.' 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.rows_sampled < 1. - AND s.rows >= 10000 - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) - 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 ) - SELECT 92 AS check_id, - 200 AS Priority, - 'Functioning Statistaholics' AS findings_group, - 'Cyberphobic Samples', - s.database_name, - '' AS URL, - 'The statistic ' + QUOTENAME(s.statistics_name) + ' is set to not recompute. This can be helpful if data is really skewed, but harmful if you expect automatic statistics updates.' 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.no_recompute = 1 - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) - OPTION ( RECOMPILE ); RAISERROR(N'check_id 93: Statistics with filters', 0,1) WITH NOWAIT; INSERT #BlitzIndexResults ( check_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, @@ -27216,7 +27353,7 @@ BEGIN; 'Functioning Statistaholics' AS findings_group, 'Filter Fixation', s.database_name, - '' AS URL, + 'https://www.brentozar.com/go/stats' AS URL, 'The statistic ' + QUOTENAME(s.statistics_name) + ' is filtered on [' + s.filter_definition + ']. It could be part of a filtered index, or just a filtered statistic. This is purely informational.' 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, @@ -27224,34 +27361,8 @@ BEGIN; 'N/A' AS index_size_summary FROM #Statistics AS s WHERE s.has_filter = 1 - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) OPTION ( RECOMPILE ); - END; - - ---------------------------------------- - --Computed Column Info: Check_id 99-109 - ---------------------------------------- - BEGIN - - RAISERROR(N'check_id 99: Computed Columns 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 ) - SELECT 99 AS check_id, - 50 AS Priority, - 'Cold Calculators' AS findings_group, - 'Serial Forcer' AS finding, - cc.database_name, - '' AS URL, - 'The computed column ' + QUOTENAME(cc.column_name) + ' on ' + QUOTENAME(cc.schema_name) + '.' + QUOTENAME(cc.table_name) + ' is based on ' + cc.definition - + '. That indicates it may reference a scalar function, or a CLR function with data access, which can cause all queries and maintenance to run serially.' AS details, - cc.column_definition, - 'N/A' AS secret_columns, - 'N/A' AS index_usage_summary, - 'N/A' AS index_size_summary - FROM #ComputedColumns AS cc - WHERE cc.is_function = 1 - OPTION ( RECOMPILE ); RAISERROR(N'check_id 100: Computed Columns that are not Persisted.', 0,1) WITH NOWAIT; INSERT #BlitzIndexResults ( check_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, @@ -27271,20 +27382,16 @@ BEGIN; 'N/A' AS index_size_summary FROM #ComputedColumns AS cc WHERE cc.is_persisted = 0 - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) OPTION ( RECOMPILE ); - ---------------------------------------- - --Temporal Table Info: Check_id 110-119 - ---------------------------------------- RAISERROR(N'check_id 110: Temporal Tables.', 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 110 AS check_id, 200 AS Priority, - 'Temporal Tables' AS findings_group, - 'Obsessive Compulsive Tables', + 'Abnormal Psychology' AS findings_group, + 'Temporal Tables', t.database_name, '' AS URL, 'The table ' + QUOTENAME(t.schema_name) + '.' + QUOTENAME(t.table_name) + ' is a temporal table, with rows versioned in ' @@ -27295,33 +27402,39 @@ BEGIN; 'N/A' AS index_usage_summary, 'N/A' AS index_size_summary FROM #TemporalTables AS t - WHERE NOT (@GetAllDatabases = 1 OR @Mode = 0) OPTION ( RECOMPILE ); - ---------------------------------------- - --Check Constraint Info: Check_id 120-129 - ---------------------------------------- - RAISERROR(N'check_id 120: 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 ) - SELECT 99 AS check_id, - 50 AS Priority, - 'Obsessive Constraintive' AS findings_group, - 'Serial Forcer' AS finding, - cc.database_name, - 'https://www.brentozar.com/archive/2016/01/another-reason-why-scalar-functions-in-computed-columns-is-a-bad-idea/' AS URL, - 'The check constraint ' + QUOTENAME(cc.constraint_name) + ' on ' + QUOTENAME(cc.schema_name) + '.' + QUOTENAME(cc.table_name) + ' is based on ' + cc.definition - + '. That indicates it may reference a scalar function, or a CLR function with data access, which can cause all queries and maintenance to run serially.' AS details, - cc.column_definition, - 'N/A' AS secret_columns, - 'N/A' AS index_usage_summary, - 'N/A' AS index_size_summary - FROM #CheckConstraints AS cc - WHERE cc.is_function = 1 - OPTION ( RECOMPILE ); - END; + + END /* IF @Mode = 4 */ + + + + + + + + + + + + + + + + + + + + + + + + + + + RAISERROR(N'Insert a row to help people find help', 0,1) WITH NOWAIT; IF DATEDIFF(MM, @VersionDate, GETDATE()) > 6 @@ -27413,9 +27526,6 @@ BEGIN; br.index_sanity_id=sn.index_sanity_id LEFT JOIN #IndexCreateTsql ts ON br.index_sanity_id=ts.index_sanity_id - WHERE br.check_id IN ( 0, 1, 2, 11, 12, 13, - 22, 34, 43, 47, 48, - 50, 65, 68, 73, 99 ) ORDER BY br.Priority ASC, br.check_id ASC, br.blitz_result_id ASC, br.findings_group ASC OPTION (RECOMPILE); END; @@ -27445,8 +27555,9 @@ BEGIN; OPTION (RECOMPILE); END; - END; /* End @Mode=0 or 4 (diagnose)*/ - ELSE IF (@Mode=1) /*Summarize*/ +END /* End @Mode=0 or 4 (diagnose)*/ + +ELSE IF (@Mode=1) /*Summarize*/ BEGIN --This mode is to give some overall stats on the database. IF(@OutputType <> 'NONE') @@ -28007,23 +28118,46 @@ BEGIN; FROM #IndexSanity AS i --left join here so we don't lose disabled nc indexes LEFT JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id LEFT JOIN #IndexCreateTsql AS ict ON i.index_sanity_id = ict.index_sanity_id - ORDER BY CASE WHEN @SortOrder = N'rows' THEN sz.total_rows - WHEN @SortOrder = N'reserved_mb' THEN sz.total_reserved_MB - WHEN @SortOrder = N'size' THEN sz.total_reserved_MB - WHEN @SortOrder = N'reserved_lob_mb' THEN sz.total_reserved_LOB_MB - WHEN @SortOrder = N'lob' THEN sz.total_reserved_LOB_MB - WHEN @SortOrder = N'total_row_lock_wait_in_ms' THEN COALESCE(sz.total_row_lock_wait_in_ms,0) - WHEN @SortOrder = N'total_page_lock_wait_in_ms' THEN COALESCE(sz.total_page_lock_wait_in_ms,0) - WHEN @SortOrder = N'lock_time' THEN (COALESCE(sz.total_row_lock_wait_in_ms,0) + COALESCE(sz.total_page_lock_wait_in_ms,0)) - WHEN @SortOrder = N'total_reads' THEN total_reads - WHEN @SortOrder = N'reads' THEN total_reads - WHEN @SortOrder = N'user_updates' THEN user_updates - WHEN @SortOrder = N'writes' THEN user_updates - WHEN @SortOrder = N'reads_per_write' THEN reads_per_write - WHEN @SortOrder = N'ratio' THEN reads_per_write - WHEN @SortOrder = N'forward_fetches' THEN sz.total_forwarded_fetch_count - WHEN @SortOrder = N'fetches' THEN sz.total_forwarded_fetch_count - ELSE NULL END DESC, /* Shout out to DHutmacher */ + ORDER BY CASE WHEN @SortDirection = 'desc' THEN + CASE WHEN @SortOrder = N'rows' THEN sz.total_rows + WHEN @SortOrder = N'reserved_mb' THEN sz.total_reserved_MB + WHEN @SortOrder = N'size' THEN sz.total_reserved_MB + WHEN @SortOrder = N'reserved_lob_mb' THEN sz.total_reserved_LOB_MB + WHEN @SortOrder = N'lob' THEN sz.total_reserved_LOB_MB + WHEN @SortOrder = N'total_row_lock_wait_in_ms' THEN COALESCE(sz.total_row_lock_wait_in_ms,0) + WHEN @SortOrder = N'total_page_lock_wait_in_ms' THEN COALESCE(sz.total_page_lock_wait_in_ms,0) + WHEN @SortOrder = N'lock_time' THEN (COALESCE(sz.total_row_lock_wait_in_ms,0) + COALESCE(sz.total_page_lock_wait_in_ms,0)) + WHEN @SortOrder = N'total_reads' THEN total_reads + WHEN @SortOrder = N'reads' THEN total_reads + WHEN @SortOrder = N'user_updates' THEN user_updates + WHEN @SortOrder = N'writes' THEN user_updates + WHEN @SortOrder = N'reads_per_write' THEN reads_per_write + WHEN @SortOrder = N'ratio' THEN reads_per_write + WHEN @SortOrder = N'forward_fetches' THEN sz.total_forwarded_fetch_count + WHEN @SortOrder = N'fetches' THEN sz.total_forwarded_fetch_count + ELSE NULL END + ELSE 1 END + DESC, /* Shout out to DHutmacher */ + CASE WHEN @SortDirection = 'asc' THEN + CASE WHEN @SortOrder = N'rows' THEN sz.total_rows + WHEN @SortOrder = N'reserved_mb' THEN sz.total_reserved_MB + WHEN @SortOrder = N'size' THEN sz.total_reserved_MB + WHEN @SortOrder = N'reserved_lob_mb' THEN sz.total_reserved_LOB_MB + WHEN @SortOrder = N'lob' THEN sz.total_reserved_LOB_MB + WHEN @SortOrder = N'total_row_lock_wait_in_ms' THEN COALESCE(sz.total_row_lock_wait_in_ms,0) + WHEN @SortOrder = N'total_page_lock_wait_in_ms' THEN COALESCE(sz.total_page_lock_wait_in_ms,0) + WHEN @SortOrder = N'lock_time' THEN (COALESCE(sz.total_row_lock_wait_in_ms,0) + COALESCE(sz.total_page_lock_wait_in_ms,0)) + WHEN @SortOrder = N'total_reads' THEN total_reads + WHEN @SortOrder = N'reads' THEN total_reads + WHEN @SortOrder = N'user_updates' THEN user_updates + WHEN @SortOrder = N'writes' THEN user_updates + WHEN @SortOrder = N'reads_per_write' THEN reads_per_write + WHEN @SortOrder = N'ratio' THEN reads_per_write + WHEN @SortOrder = N'forward_fetches' THEN sz.total_forwarded_fetch_count + WHEN @SortOrder = N'fetches' THEN sz.total_forwarded_fetch_count + ELSE NULL END + ELSE 1 END + ASC, i.[database_name], [Schema Name], [Object Name], [Index ID] OPTION (RECOMPILE); END; @@ -28060,7 +28194,8 @@ BEGIN; mi.create_tsql AS [Create TSQL], mi.more_info AS [More Info], 1 AS [Display Order], - mi.is_low + mi.is_low, + mi.sample_query_plan AS [Sample Query Plan] FROM #MissingIndexes AS mi LEFT JOIN create_date AS cd ON mi.[object_id] = cd.object_id @@ -28077,7 +28212,7 @@ BEGIN; 100000000000, @DaysUptimeInsertValue, NULL,NULL,NULL,NULL,NULL,NULL,NULL, - NULL, NULL, NULL, NULL, 0 AS [Display Order], NULL AS is_low + NULL, NULL, NULL, NULL, 0 AS [Display Order], NULL AS is_low, NULL ORDER BY [Display Order] ASC, [Magic Benefit Number] DESC OPTION (RECOMPILE); END; @@ -28094,7 +28229,7 @@ BEGIN; END; /* End @Mode=3 (index detail)*/ -END; + END TRY BEGIN CATCH @@ -28147,7 +28282,7 @@ BEGIN SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '2.999', @VersionDate = '20201011'; +SELECT @Version = '2.9999', @VersionDate = '20201114'; IF(@VersionCheckMode = 1) @@ -28435,12 +28570,12 @@ You need to use an Azure storage account, and the path has to look like this: ht ORDER BY xml.deadlock_xml.value('(/event/@timestamp)[1]', 'datetime') DESC OPTION ( RECOMPILE ); - /*Parse process and input buffer XML*/ SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); RAISERROR('Parse process and input buffer XML %s', 0, 1, @d) WITH NOWAIT; SELECT q.event_date, q.victim_id, + CONVERT(BIT, q.is_parallel) AS is_parallel, q.deadlock_graph, q.id, q.database_id, @@ -28464,6 +28599,7 @@ You need to use an Azure storage account, and the path has to look like this: ht FROM ( SELECT dd.deadlock_xml, CONVERT(DATETIME2(7), SWITCHOFFSET(CONVERT(datetimeoffset, dd.event_date ), DATENAME(TzOffset, SYSDATETIMEOFFSET()))) AS event_date, dd.victim_id, + dd.is_parallel, dd.deadlock_graph, ca.dp.value('@id', 'NVARCHAR(256)') AS id, ca.dp.value('@currentdb', 'BIGINT') AS database_id, @@ -28485,6 +28621,7 @@ You need to use an Azure storage account, and the path has to look like this: ht FROM ( SELECT d1.deadlock_xml, d1.deadlock_xml.value('(event/@timestamp)[1]', 'DATETIME2') AS event_date, d1.deadlock_xml.value('(//deadlock/victim-list/victimProcess/@id)[1]', 'NVARCHAR(256)') AS victim_id, + d1.deadlock_xml.exist('//deadlock/resource-list/exchangeEvent') AS is_parallel, d1.deadlock_xml.query('/event/data/value/deadlock') AS deadlock_graph FROM #deadlock_data AS d1 ) AS dd CROSS APPLY dd.deadlock_xml.nodes('//deadlock/process-list/process') AS ca(dp) @@ -28708,13 +28845,13 @@ You need to use an Azure storage account, and the path has to look like this: ht ca.spilling, ca.waiting_to_close, w.l.value('@id', 'NVARCHAR(256)') AS waiter_id, - o.l.value('@id', 'NVARCHAR(256)') AS owner_id + o.l.value('@id', 'NVARCHAR(256)') AS owner_id INTO #deadlock_resource_parallel FROM ( SELECT dr.event_date, ca.dr.value('@id', 'NVARCHAR(256)') AS id, - ca.dr.value('@WaitType', 'NVARCHAR(256)') AS wait_type, - ca.dr.value('@nodeId', 'BIGINT') AS node_id, + ca.dr.value('@WaitType', 'NVARCHAR(256)') AS wait_type, + ca.dr.value('@nodeId', 'BIGINT') AS node_id, /* These columns are in 2017 CU5 ONLY */ ca.dr.value('@waiterType', 'NVARCHAR(256)') AS waiter_type, ca.dr.value('@ownerActivity', 'NVARCHAR(256)') AS owner_activity, @@ -29313,6 +29450,22 @@ You need to use an Azure storage account, and the path has to look like this: ht GROUP BY DB_NAME(aj.database_id), aj.job_name, aj.step_name OPTION ( RECOMPILE ); + /*Check 13 is total parallel deadlocks*/ + SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + RAISERROR('Check 13 %s', 0, 1, @d) WITH NOWAIT; + INSERT #deadlock_findings WITH (TABLOCKX) + ( check_id, database_name, object_name, finding_group, finding ) + SELECT 13 AS check_id, + N'-' AS database_name, + '-' AS object_name, + 'Total parallel deadlocks' AS finding_group, + 'There have been ' + + CONVERT(NVARCHAR(20), COUNT_BIG(DISTINCT drp.event_date)) + + ' parallel deadlocks.' + FROM #deadlock_resource_parallel AS drp + WHERE 1 = 1 + OPTION ( RECOMPILE ); + /*Thank you goodnight*/ INSERT #deadlock_findings WITH (TABLOCKX) ( check_id, database_name, object_name, finding_group, finding ) @@ -29389,6 +29542,7 @@ You need to use an Azure storage account, and the path has to look like this: ht dp.deadlock_graph FROM #deadlock_process AS dp WHERE dp.victim_id IS NOT NULL + AND dp.is_parallel = 0 UNION ALL @@ -29400,7 +29554,20 @@ You need to use an Azure storage account, and the path has to look like this: ht dp.priority, dp.log_used, dp.wait_resource COLLATE DATABASE_DEFAULT, - CONVERT(XML, N'parallel_deadlock' COLLATE DATABASE_DEFAULT) AS object_names, + CASE WHEN @ExportToExcel = 0 THEN + CONVERT( + XML, + STUFF(( SELECT DISTINCT NCHAR(10) + + N' ' + + ISNULL(c.object_name, N'') + + N' ' COLLATE DATABASE_DEFAULT AS object_name + FROM #deadlock_owner_waiter AS c + WHERE ( dp.id = c.owner_id + OR dp.victim_id = c.waiter_id ) + AND CONVERT(DATE, dp.event_date) = CONVERT(DATE, c.event_date) + FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(4000)'), + 1, 1, N'')) + ELSE NULL END AS object_names, dp.wait_time, dp.transaction_name, dp.last_tran_started, @@ -29435,8 +29602,7 @@ You need to use an Azure storage account, and the path has to look like this: ht FROM #deadlock_process AS dp CROSS APPLY (SELECT TOP 1 * FROM #deadlock_resource_parallel AS drp WHERE drp.owner_id = dp.id AND drp.wait_type = 'e_waitPipeNewRow' ORDER BY drp.event_date) AS cao CROSS APPLY (SELECT TOP 1 * FROM #deadlock_resource_parallel AS drp WHERE drp.owner_id = dp.id AND drp.wait_type = 'e_waitPipeGetRow' ORDER BY drp.event_date) AS caw - WHERE dp.victim_id IS NULL - AND dp.login_name IS NOT NULL) + WHERE dp.is_parallel = 1 ) insert into DeadLockTbl ( ServerName, deadlock_type, @@ -29603,7 +29769,8 @@ ELSE --Output to database is not set output to client app dp.deadlock_graph FROM #deadlock_process AS dp WHERE dp.victim_id IS NOT NULL - + AND dp.is_parallel = 0 + UNION ALL SELECT N'Parallel Deadlock' AS deadlock_type, @@ -29614,7 +29781,20 @@ ELSE --Output to database is not set output to client app dp.priority, dp.log_used, dp.wait_resource COLLATE DATABASE_DEFAULT, - CASE WHEN @ExportToExcel = 0 THEN CONVERT(XML, N'parallel_deadlock' COLLATE DATABASE_DEFAULT) ELSE 'parallel_deadlock' END AS object_names, + CASE WHEN @ExportToExcel = 0 THEN + CONVERT( + XML, + STUFF(( SELECT DISTINCT NCHAR(10) + + N' ' + + ISNULL(c.object_name, N'') + + N' ' COLLATE DATABASE_DEFAULT AS object_name + FROM #deadlock_owner_waiter AS c + WHERE ( dp.id = c.owner_id + OR dp.victim_id = c.waiter_id ) + AND CONVERT(DATE, dp.event_date) = CONVERT(DATE, c.event_date) + FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(4000)'), + 1, 1, N'')) + ELSE NULL END AS object_names, dp.wait_time, dp.transaction_name, dp.last_tran_started, @@ -29647,10 +29827,9 @@ ELSE --Output to database is not set output to client app caw.waiting_to_close AS waiter_waiting_to_close, dp.deadlock_graph FROM #deadlock_process AS dp - OUTER APPLY (SELECT TOP 1 * FROM #deadlock_resource_parallel AS drp WHERE drp.owner_id = dp.id AND drp.wait_type = 'e_waitPipeNewRow' ORDER BY drp.event_date) AS cao - OUTER APPLY (SELECT TOP 1 * FROM #deadlock_resource_parallel AS drp WHERE drp.owner_id = dp.id AND drp.wait_type = 'e_waitPipeGetRow' ORDER BY drp.event_date) AS caw - WHERE dp.victim_id IS NULL - AND dp.login_name IS NOT NULL + OUTER APPLY (SELECT TOP 1 * FROM #deadlock_resource_parallel AS drp WHERE drp.owner_id = dp.id AND drp.wait_type IN ('e_waitPortOpen', 'e_waitPipeNewRow') ORDER BY drp.event_date) AS cao + OUTER APPLY (SELECT TOP 1 * FROM #deadlock_resource_parallel AS drp WHERE drp.owner_id = dp.id AND drp.wait_type IN ('e_waitPortOpen', 'e_waitPipeGetRow') ORDER BY drp.event_date) AS caw + WHERE dp.is_parallel = 1 ) SELECT d.deadlock_type, d.event_date, @@ -29809,7 +29988,7 @@ BEGIN /*First BEGIN*/ SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '3.999', @VersionDate = '20201011'; +SELECT @Version = '3.9999', @VersionDate = '20201114'; IF(@VersionCheckMode = 1) BEGIN RETURN; @@ -35536,7 +35715,7 @@ BEGIN SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '7.999', @VersionDate = '20201011'; + SELECT @Version = '7.9999', @VersionDate = '20201114'; IF(@VersionCheckMode = 1) BEGIN @@ -36032,6 +36211,21 @@ IF @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @Output END + IF OBJECT_ID('tempdb..#WhoReadableDBs') IS NOT NULL + DROP TABLE #WhoReadableDBs; + +CREATE TABLE #WhoReadableDBs +( +database_id INT +); + +IF EXISTS (SELECT * FROM sys.all_objects o WHERE o.name = 'dm_hadr_database_replica_states') +BEGIN + RAISERROR('Checking for Read intent databases to exclude',0,0) WITH NOWAIT; + + EXEC('INSERT INTO #WhoReadableDBs (database_id) SELECT DBs.database_id FROM sys.databases DBs INNER JOIN sys.availability_replicas Replicas ON DBs.replica_id = Replicas.replica_id WHERE replica_server_name NOT IN (SELECT DISTINCT primary_replica FROM sys.dm_hadr_availability_group_states States) AND Replicas.secondary_role_allow_connections_desc = ''READ_ONLY'' AND replica_server_name = @@SERVERNAME;'); +END + SELECT @BlockingCheck = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; DECLARE @blocked TABLE @@ -36062,7 +36256,7 @@ BEGIN /* Think of the StringToExecute as starting with this, but we'll set this up later depending on whether we're doing an insert or a select: SELECT @StringToExecute = N'SELECT GETDATE() AS run_date , */ - SET @StringToExecute = N'COALESCE( CONVERT(VARCHAR(20), (ABS(r.total_elapsed_time) / 1000) / 86400) + '':'' + CONVERT(VARCHAR(20), (DATEADD(SECOND, (r.total_elapsed_time / 1000), 0) + DATEADD(MILLISECOND, (r.total_elapsed_time % 1000), 0)), 114), CONVERT(VARCHAR(20), DATEDIFF(SECOND, s.last_request_start_time, GETDATE()) / 86400) + '':'' + CONVERT(VARCHAR(20), DATEADD(SECOND, DATEDIFF(SECOND, s.last_request_start_time, GETDATE()), 0), 114) ) AS [elapsed_time] , + SET @StringToExecute = N'COALESCE( RIGHT(''00'' + CONVERT(VARCHAR(20), (ABS(r.total_elapsed_time) / 1000) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), (DATEADD(SECOND, (r.total_elapsed_time / 1000), 0) + DATEADD(MILLISECOND, (r.total_elapsed_time % 1000), 0)), 114), CONVERT(VARCHAR(20), DATEDIFF(SECOND, s.last_request_start_time, GETDATE()) / 86400) + '':'' + CONVERT(VARCHAR(20), DATEADD(SECOND, DATEDIFF(SECOND, s.last_request_start_time, GETDATE()), 0), 114) ) AS [elapsed_time] , s.session_id , COALESCE(DB_NAME(r.database_id), DB_NAME(blocked.dbid), ''N/A'') AS database_name, ISNULL(SUBSTRING(dest.text, @@ -36267,7 +36461,7 @@ IF @ProductVersionMajor >= 11 /* Think of the StringToExecute as starting with this, but we'll set this up later depending on whether we're doing an insert or a select: SELECT @StringToExecute = N'SELECT GETDATE() AS run_date , */ - SELECT @StringToExecute = N'COALESCE( CONVERT(VARCHAR(20), (ABS(r.total_elapsed_time) / 1000) / 86400) + '':'' + CONVERT(VARCHAR(20), (DATEADD(SECOND, (r.total_elapsed_time / 1000), 0) + DATEADD(MILLISECOND, (r.total_elapsed_time % 1000), 0)), 114), CONVERT(VARCHAR(20), DATEDIFF(SECOND, s.last_request_start_time, GETDATE()) / 86400) + '':'' + CONVERT(VARCHAR(20), DATEADD(SECOND, DATEDIFF(SECOND, s.last_request_start_time, GETDATE()), 0), 114) ) AS [elapsed_time] , + SELECT @StringToExecute = N'COALESCE( RIGHT(''00'' + CONVERT(VARCHAR(20), (ABS(r.total_elapsed_time) / 1000) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), (DATEADD(SECOND, (r.total_elapsed_time / 1000), 0) + DATEADD(MILLISECOND, (r.total_elapsed_time % 1000), 0)), 114), CONVERT(VARCHAR(20), DATEDIFF(SECOND, s.last_request_start_time, GETDATE()) / 86400) + '':'' + CONVERT(VARCHAR(20), DATEADD(SECOND, DATEDIFF(SECOND, s.last_request_start_time, GETDATE()), 0), 114) ) AS [elapsed_time] , s.session_id , COALESCE(DB_NAME(r.database_id), DB_NAME(blocked.dbid), ''N/A'') AS database_name, ISNULL(SUBSTRING(dest.text, @@ -36506,6 +36700,7 @@ IF @ProductVersionMajor >= 11 N' WHERE s.session_id <> @@SPID AND s.host_name IS NOT NULL + AND r.database_id NOT IN (SELECT database_id FROM #WhoReadableDBs) ' + CASE WHEN @ShowSleepingSPIDs = 0 THEN N' AND COALESCE(DB_NAME(r.database_id), DB_NAME(blocked.dbid)) IS NOT NULL' diff --git a/sp_AllNightLog.sql b/sp_AllNightLog.sql index 005bb269e..ff8c022f4 100644 --- a/sp_AllNightLog.sql +++ b/sp_AllNightLog.sql @@ -30,7 +30,7 @@ SET NOCOUNT ON; BEGIN; -SELECT @Version = '3.99', @VersionDate = '20201011'; +SELECT @Version = '3.999', @VersionDate = '20201114'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_AllNightLog_Setup.sql b/sp_AllNightLog_Setup.sql index c84a906af..6aea6265c 100644 --- a/sp_AllNightLog_Setup.sql +++ b/sp_AllNightLog_Setup.sql @@ -36,7 +36,7 @@ SET NOCOUNT ON; BEGIN; -SELECT @Version = '3.99', @VersionDate = '20201011'; +SELECT @Version = '3.999', @VersionDate = '20201114'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_Blitz.sql b/sp_Blitz.sql index 2a04bba47..3e52a715b 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -37,7 +37,7 @@ AS SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '7.999', @VersionDate = '20201011'; + SELECT @Version = '7.9999', @VersionDate = '20201114'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) diff --git a/sp_BlitzBackups.sql b/sp_BlitzBackups.sql index 0424c9290..6171d0d74 100755 --- a/sp_BlitzBackups.sql +++ b/sp_BlitzBackups.sql @@ -23,7 +23,7 @@ AS SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '3.999', @VersionDate = '20201011'; + SELECT @Version = '3.9999', @VersionDate = '20201114'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_BlitzCache.sql b/sp_BlitzCache.sql index d5119b28b..305b0f785 100644 --- a/sp_BlitzCache.sql +++ b/sp_BlitzCache.sql @@ -278,7 +278,7 @@ BEGIN SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '7.999', @VersionDate = '20201011'; +SELECT @Version = '7.9999', @VersionDate = '20201114'; IF(@VersionCheckMode = 1) diff --git a/sp_BlitzFirst.sql b/sp_BlitzFirst.sql index 7ab5e5464..e6c7de699 100644 --- a/sp_BlitzFirst.sql +++ b/sp_BlitzFirst.sql @@ -45,7 +45,7 @@ BEGIN SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '7.999', @VersionDate = '20201011'; +SELECT @Version = '7.9999', @VersionDate = '20201114'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_BlitzInMemoryOLTP.sql b/sp_BlitzInMemoryOLTP.sql index aac5daade..f7cdd18c0 100644 --- a/sp_BlitzInMemoryOLTP.sql +++ b/sp_BlitzInMemoryOLTP.sql @@ -82,7 +82,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI */ AS DECLARE @ScriptVersion VARCHAR(30); -SELECT @ScriptVersion = '1.8', @VersionDate = '20201011'; +SELECT @ScriptVersion = '1.8', @VersionDate = '20201114'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index 03cecc51c..09714edfd 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -45,7 +45,7 @@ AS SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '7.999', @VersionDate = '20201011'; +SELECT @Version = '7.9999', @VersionDate = '20201114'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index 07e2e3035..01e0568b8 100644 --- a/sp_BlitzLock.sql +++ b/sp_BlitzLock.sql @@ -32,7 +32,7 @@ BEGIN SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '2.999', @VersionDate = '20201011'; +SELECT @Version = '2.9999', @VersionDate = '20201114'; IF(@VersionCheckMode = 1) diff --git a/sp_BlitzQueryStore.sql b/sp_BlitzQueryStore.sql index ab4fa5a7b..994c46b6e 100644 --- a/sp_BlitzQueryStore.sql +++ b/sp_BlitzQueryStore.sql @@ -56,7 +56,7 @@ BEGIN /*First BEGIN*/ SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '3.999', @VersionDate = '20201011'; +SELECT @Version = '3.9999', @VersionDate = '20201114'; IF(@VersionCheckMode = 1) BEGIN RETURN; diff --git a/sp_BlitzWho.sql b/sp_BlitzWho.sql index 84cb57f94..536d2e8f0 100644 --- a/sp_BlitzWho.sql +++ b/sp_BlitzWho.sql @@ -29,7 +29,7 @@ BEGIN SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '7.999', @VersionDate = '20201011'; + SELECT @Version = '7.9999', @VersionDate = '20201114'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_DatabaseRestore.sql b/sp_DatabaseRestore.sql index 3e115708e..b74b4b22e 100755 --- a/sp_DatabaseRestore.sql +++ b/sp_DatabaseRestore.sql @@ -39,7 +39,7 @@ SET NOCOUNT ON; /*Versioning details*/ -SELECT @Version = '7.999', @VersionDate = '20201011'; +SELECT @Version = '7.9999', @VersionDate = '20201114'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_ineachdb.sql b/sp_ineachdb.sql index 6fb768b9f..57961f94b 100644 --- a/sp_ineachdb.sql +++ b/sp_ineachdb.sql @@ -29,12 +29,12 @@ ALTER PROCEDURE [dbo].[sp_ineachdb] @Version VARCHAR(30) = NULL OUTPUT, @VersionDate DATETIME = NULL OUTPUT, @VersionCheckMode BIT = 0 --- WITH EXECUTE AS OWNER – maybe not a great idea, depending on the security your system +-- WITH EXECUTE AS OWNER – maybe not a great idea, depending on the security of your system AS BEGIN SET NOCOUNT ON; - SELECT @Version = '2.999', @VersionDate = '20201011'; + SELECT @Version = '2.9999', @VersionDate = '20201114'; IF(@VersionCheckMode = 1) BEGIN From d74995e3d59b5beb040f33ab4c5e2a7004065a8d Mon Sep 17 00:00:00 2001 From: Luporpi Date: Mon, 16 Nov 2020 12:48:55 +0100 Subject: [PATCH 027/662] 4k limit exceeded --- sp_BlitzCache.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sp_BlitzCache.sql b/sp_BlitzCache.sql index 305b0f785..8791acf29 100644 --- a/sp_BlitzCache.sql +++ b/sp_BlitzCache.sql @@ -7006,9 +7006,9 @@ ELSE AvgSpills MONEY, QueryPlanCost FLOAT, JoinKey AS ServerName + Cast(CheckDate AS NVARCHAR(50)), - CONSTRAINT [PK_' + REPLACE(REPLACE(@OutputTableName,'[',''),']','') + '] PRIMARY KEY CLUSTERED(ID ASC)); + CONSTRAINT [PK_' + REPLACE(REPLACE(@OutputTableName,'[',''),']','') + '] PRIMARY KEY CLUSTERED(ID ASC));' - IF EXISTS(SELECT * FROM ' + SET @StringToExecute += N'IF EXISTS(SELECT * FROM ' +@OutputDatabaseName +N'.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' +@OutputSchemaName From f4881346f7347f13a0a146a1a49718d3e634cadf Mon Sep 17 00:00:00 2001 From: Luporpi Date: Mon, 16 Nov 2020 12:48:55 +0100 Subject: [PATCH 028/662] 4k limit exceeded --- sp_BlitzCache.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sp_BlitzCache.sql b/sp_BlitzCache.sql index 305b0f785..3bafae06b 100644 --- a/sp_BlitzCache.sql +++ b/sp_BlitzCache.sql @@ -7006,9 +7006,9 @@ ELSE AvgSpills MONEY, QueryPlanCost FLOAT, JoinKey AS ServerName + Cast(CheckDate AS NVARCHAR(50)), - CONSTRAINT [PK_' + REPLACE(REPLACE(@OutputTableName,'[',''),']','') + '] PRIMARY KEY CLUSTERED(ID ASC)); + CONSTRAINT [PK_' + REPLACE(REPLACE(@OutputTableName,'[',''),']','') + '] PRIMARY KEY CLUSTERED(ID ASC));'; - IF EXISTS(SELECT * FROM ' + SET @StringToExecute += N'IF EXISTS(SELECT * FROM ' +@OutputDatabaseName +N'.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' +@OutputSchemaName From 61e8546616cbe1d83ec73588a87085f5d90f9774 Mon Sep 17 00:00:00 2001 From: Luporpi Date: Mon, 16 Nov 2020 12:55:42 +0100 Subject: [PATCH 029/662] corrected strange merge behavior --- sp_BlitzCache.sql | 1 - 1 file changed, 1 deletion(-) diff --git a/sp_BlitzCache.sql b/sp_BlitzCache.sql index 825e21384..3bafae06b 100644 --- a/sp_BlitzCache.sql +++ b/sp_BlitzCache.sql @@ -7007,7 +7007,6 @@ ELSE QueryPlanCost FLOAT, JoinKey AS ServerName + Cast(CheckDate AS NVARCHAR(50)), CONSTRAINT [PK_' + REPLACE(REPLACE(@OutputTableName,'[',''),']','') + '] PRIMARY KEY CLUSTERED(ID ASC));'; - CONSTRAINT [PK_' + REPLACE(REPLACE(@OutputTableName,'[',''),']','') + '] PRIMARY KEY CLUSTERED(ID ASC));' SET @StringToExecute += N'IF EXISTS(SELECT * FROM ' +@OutputDatabaseName From 1c0dd1a71bdf8a97056f1826934e759e9ee7c550 Mon Sep 17 00:00:00 2001 From: Ali Soylu Date: Tue, 17 Nov 2020 11:27:57 -0500 Subject: [PATCH 030/662] Fix issue where sp_BlitzIndex columnstore visualization does not work for non-default schema (by using @SchemaName param) Changed the queries to rely on @ObjectId which we already have instead of OBJECT_ID(@TableName) --- sp_BlitzIndex.sql | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index 09714edfd..5cbf31dd2 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -2708,7 +2708,7 @@ BEGIN FROM ' + QUOTENAME(@DatabaseName) + N'.sys.partitions p INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns c ON p.object_id = c.object_id INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.types t ON c.system_type_id = t.system_type_id AND c.user_type_id = t.user_type_id - WHERE p.object_id = OBJECT_ID(@TableName) + WHERE p.object_id = @ObjectID AND EXISTS (SELECT * FROM ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_segments seg WHERE p.partition_id = seg.partition_id AND seg.column_id = c.column_id) AND p.data_compression IN (3,4) ) @@ -2731,7 +2731,7 @@ BEGIN PRINT SUBSTRING(@dsql, 36000, 40000); END; - EXEC sp_executesql @dsql, N'@ObjectID INT, @TableName NVARCHAR(128), @ColumnList NVARCHAR(MAX) OUTPUT', @ObjectID, @TableName, @ColumnList OUTPUT; + EXEC sp_executesql @dsql, N'@ObjectID INT, @ColumnList NVARCHAR(MAX) OUTPUT', @ObjectID, @ColumnList OUTPUT; IF @Debug = 1 SELECT @ColumnList AS ColumnstoreColumnList; @@ -2750,7 +2750,7 @@ BEGIN INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns c ON rg.object_id = c.object_id INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partitions p ON rg.object_id = p.object_id AND rg.partition_number = p.partition_number LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_segments seg ON p.partition_id = seg.partition_id AND c.column_id = seg.column_id AND rg.row_group_id = seg.segment_id - WHERE rg.object_id = OBJECT_ID(@TableName) + WHERE rg.object_id = @ObjectID ) AS x PIVOT (MAX(details) FOR column_name IN ( ' + @ColumnList + N')) AS pivot1 ORDER BY partition_number, row_group_id;'; @@ -2772,7 +2772,7 @@ BEGIN IF @dsql IS NULL RAISERROR('@dsql is null',16,1); ELSE - EXEC sp_executesql @dsql, N'@TableName NVARCHAR(128)', @TableName; + EXEC sp_executesql @dsql, N'@ObjectID INT', @ObjectID; END ELSE /* No columns were found for this object */ BEGIN From 6f014413fc749f625efc3e93e7fa0606026f2f01 Mon Sep 17 00:00:00 2001 From: Ali Soylu Date: Tue, 17 Nov 2020 14:35:18 -0500 Subject: [PATCH 031/662] Added a fix for issue 2684 as well by joining to sys.index_columns --- sp_BlitzIndex.sql | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index 5cbf31dd2..430d31af5 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -2707,9 +2707,10 @@ BEGIN SELECT DISTINCT QUOTENAME(c.name) AS column_name, c.column_id FROM ' + QUOTENAME(@DatabaseName) + N'.sys.partitions p INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns c ON p.object_id = c.object_id + INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.index_columns ic on ic.column_id = c.column_id and ic.object_id = c.object_id INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.types t ON c.system_type_id = t.system_type_id AND c.user_type_id = t.user_type_id WHERE p.object_id = @ObjectID - AND EXISTS (SELECT * FROM ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_segments seg WHERE p.partition_id = seg.partition_id AND seg.column_id = c.column_id) + AND EXISTS (SELECT * FROM ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_segments seg WHERE p.partition_id = seg.partition_id AND seg.column_id = ic.index_column_id) AND p.data_compression IN (3,4) ) SELECT @ColumnList = @ColumnList + column_name + N'', '' @@ -2748,8 +2749,9 @@ BEGIN details = CAST(seg.min_data_id AS VARCHAR(20)) + '' to '' + CAST(seg.max_data_id AS VARCHAR(20)) + '', '' + CAST(CAST((seg.on_disk_size / 1024.0 / 1024) AS DECIMAL(18,0)) AS VARCHAR(20)) + '' MB'' FROM ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_row_groups rg INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns c ON rg.object_id = c.object_id + INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.index_columns ic on ic.column_id = c.column_id and ic.object_id = c.object_id INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partitions p ON rg.object_id = p.object_id AND rg.partition_number = p.partition_number - LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_segments seg ON p.partition_id = seg.partition_id AND c.column_id = seg.column_id AND rg.row_group_id = seg.segment_id + LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_segments seg ON p.partition_id = seg.partition_id AND ic.index_column_id = seg.column_id AND rg.row_group_id = seg.segment_id WHERE rg.object_id = @ObjectID ) AS x PIVOT (MAX(details) FOR column_name IN ( ' + @ColumnList + N')) AS pivot1 From e6a6fac53c975a21bb8e7c3f49d89c9db9756f42 Mon Sep 17 00:00:00 2001 From: Ali Soylu Date: Tue, 17 Nov 2020 15:30:13 -0500 Subject: [PATCH 032/662] fix the join to sys.index_columns for columnstore visualization --- sp_BlitzIndex.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index 430d31af5..c529dee04 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -2707,7 +2707,7 @@ BEGIN SELECT DISTINCT QUOTENAME(c.name) AS column_name, c.column_id FROM ' + QUOTENAME(@DatabaseName) + N'.sys.partitions p INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns c ON p.object_id = c.object_id - INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.index_columns ic on ic.column_id = c.column_id and ic.object_id = c.object_id + INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.index_columns ic on ic.column_id = c.column_id and ic.object_id = c.object_id AND ic.index_id = p.index_id INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.types t ON c.system_type_id = t.system_type_id AND c.user_type_id = t.user_type_id WHERE p.object_id = @ObjectID AND EXISTS (SELECT * FROM ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_segments seg WHERE p.partition_id = seg.partition_id AND seg.column_id = ic.index_column_id) @@ -2749,8 +2749,8 @@ BEGIN details = CAST(seg.min_data_id AS VARCHAR(20)) + '' to '' + CAST(seg.max_data_id AS VARCHAR(20)) + '', '' + CAST(CAST((seg.on_disk_size / 1024.0 / 1024) AS DECIMAL(18,0)) AS VARCHAR(20)) + '' MB'' FROM ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_row_groups rg INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns c ON rg.object_id = c.object_id - INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.index_columns ic on ic.column_id = c.column_id and ic.object_id = c.object_id INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partitions p ON rg.object_id = p.object_id AND rg.partition_number = p.partition_number + INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.index_columns ic on ic.column_id = c.column_id AND ic.object_id = c.object_id AND ic.index_id = p.index_id LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_segments seg ON p.partition_id = seg.partition_id AND ic.index_column_id = seg.column_id AND rg.row_group_id = seg.segment_id WHERE rg.object_id = @ObjectID ) AS x From 99ed1e62eaff08a00c5e2d0d931a6d15b445c608 Mon Sep 17 00:00:00 2001 From: Erik Darling Date: Sat, 21 Nov 2020 11:49:43 -0500 Subject: [PATCH 033/662] having mercy Adds a having clause to checkid 13 to prevent 0 count rows from being inserteded. Fixes #2682 --- sp_BlitzLock.sql | 1 + 1 file changed, 1 insertion(+) diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index 01e0568b8..9a1ad4f0b 100644 --- a/sp_BlitzLock.sql +++ b/sp_BlitzLock.sql @@ -1214,6 +1214,7 @@ You need to use an Azure storage account, and the path has to look like this: ht + ' parallel deadlocks.' FROM #deadlock_resource_parallel AS drp WHERE 1 = 1 + HAVING COUNT_BIG(DISTINCT drp.event_date) > 0 OPTION ( RECOMPILE ); /*Thank you goodnight*/ From 37caa24ae4badb00f61569c75a3c948b39a89b12 Mon Sep 17 00:00:00 2001 From: Erik Darling Date: Sat, 21 Nov 2020 11:56:21 -0500 Subject: [PATCH 034/662] Stinkin batches Correctly identifies batch mode parallel deadlocks. Closes #2686 --- sp_BlitzLock.sql | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index 01e0568b8..0a913ea78 100644 --- a/sp_BlitzLock.sql +++ b/sp_BlitzLock.sql @@ -349,7 +349,7 @@ You need to use an Azure storage account, and the path has to look like this: ht FROM ( SELECT dd.deadlock_xml, CONVERT(DATETIME2(7), SWITCHOFFSET(CONVERT(datetimeoffset, dd.event_date ), DATENAME(TzOffset, SYSDATETIMEOFFSET()))) AS event_date, dd.victim_id, - dd.is_parallel, + CONVERT(tinyint, dd.is_parallel) + CONVERT(tinyint, dd.is_parallel_batch) AS is_parallel, dd.deadlock_graph, ca.dp.value('@id', 'NVARCHAR(256)') AS id, ca.dp.value('@currentdb', 'BIGINT') AS database_id, @@ -372,6 +372,7 @@ You need to use an Azure storage account, and the path has to look like this: ht d1.deadlock_xml.value('(event/@timestamp)[1]', 'DATETIME2') AS event_date, d1.deadlock_xml.value('(//deadlock/victim-list/victimProcess/@id)[1]', 'NVARCHAR(256)') AS victim_id, d1.deadlock_xml.exist('//deadlock/resource-list/exchangeEvent') AS is_parallel, + d1.deadlock_xml.exist('//deadlock/resource-list/SyncPoint') AS is_parallel_batch, d1.deadlock_xml.query('/event/data/value/deadlock') AS deadlock_graph FROM #deadlock_data AS d1 ) AS dd CROSS APPLY dd.deadlock_xml.nodes('//deadlock/process-list/process') AS ca(dp) From 37f34447652ff3e9be62ef12aa26caa7135169ff Mon Sep 17 00:00:00 2001 From: merlinbruno Date: Sun, 22 Nov 2020 13:25:55 -0300 Subject: [PATCH 035/662] sp_BlitzLock - TimeZone convertion Adjusted conversion of TimeZone that won't match with the join criteria in "Check 6" --- sp_BlitzLock.sql | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index 01e0568b8..ecc0c25bf 100644 --- a/sp_BlitzLock.sql +++ b/sp_BlitzLock.sql @@ -403,12 +403,19 @@ You need to use an Azure storage account, and the path has to look like this: ht /*Grab the full resource list*/ SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); RAISERROR('Grab the full resource list %s', 0, 1, @d) WITH NOWAIT; + SELECT + CONVERT(DATETIME2(7), SWITCHOFFSET(CONVERT(datetimeoffset, dr.event_date), DATENAME(TzOffset, SYSDATETIMEOFFSET()))) AS event_date, + dr.victim_id, + dr.resource_xml + INTO #deadlock_resource + FROM + ( SELECT dd.deadlock_xml.value('(event/@timestamp)[1]', 'DATETIME2') AS event_date, dd.deadlock_xml.value('(//deadlock/victim-list/victimProcess/@id)[1]', 'NVARCHAR(256)') AS victim_id, ISNULL(ca.dp.query('.'), '') AS resource_xml - INTO #deadlock_resource FROM #deadlock_data AS dd CROSS APPLY dd.deadlock_xml.nodes('//deadlock/resource-list') AS ca(dp) + ) AS dr OPTION ( RECOMPILE ); From df09ff41506b47ade7068b327f2c67ddf37faf66 Mon Sep 17 00:00:00 2001 From: merlinbruno Date: Sun, 22 Nov 2020 13:36:05 -0300 Subject: [PATCH 036/662] sp_BlitzLock - filter includes non-index locks Added on Check 2 summary filter that shows only index locks --- sp_BlitzLock.sql | 1 + 1 file changed, 1 insertion(+) diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index 01e0568b8..a9fcbd5af 100644 --- a/sp_BlitzLock.sql +++ b/sp_BlitzLock.sql @@ -816,6 +816,7 @@ You need to use an Azure storage account, and the path has to look like this: ht AND (dow.event_date < @EndDate OR @EndDate IS NULL) AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) AND dow.lock_type NOT IN (N'HEAP', N'RID') + AND dow.index_name is not null GROUP BY DB_NAME(dow.database_id), dow.index_name OPTION ( RECOMPILE ); From 5e4e928b94f04fc19ad37ec3b72a2da6ca652534 Mon Sep 17 00:00:00 2001 From: Ali Soylu Date: Tue, 24 Nov 2020 17:12:32 -0500 Subject: [PATCH 037/662] Changes proposed in #2691 by @erikdarlingdata --- sp_BlitzLock.sql | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index 01e0568b8..e192b33cc 100644 --- a/sp_BlitzLock.sql +++ b/sp_BlitzLock.sql @@ -314,7 +314,9 @@ You need to use an Azure storage account, and the path has to look like this: ht FROM xml LEFT JOIN #t AS t ON 1 = 1 - WHERE xml.deadlock_xml.value('(/event/@name)[1]', 'VARCHAR(256)') = 'xml_deadlock_report' + CROSS APPLY xml.deadlock_xml.nodes('/event/@name') AS x(c) + WHERE 1 = 1 + AND x.c.exist('/event/@name[ .= "xml_deadlock_report"]') = 1 AND CONVERT(datetime, SWITCHOFFSET(CONVERT(datetimeoffset, xml.deadlock_xml.value('(/event/@timestamp)[1]', 'datetime') ), DATENAME(TzOffset, SYSDATETIMEOFFSET()))) > @StartDate AND CONVERT(datetime, SWITCHOFFSET(CONVERT(datetimeoffset, xml.deadlock_xml.value('(/event/@timestamp)[1]', 'datetime') ), DATENAME(TzOffset, SYSDATETIMEOFFSET()))) < @EndDate ORDER BY xml.deadlock_xml.value('(/event/@timestamp)[1]', 'datetime') DESC From 1609ec3485a2c56fdaed22ddf9d18eea2169aac9 Mon Sep 17 00:00:00 2001 From: Ali Soylu Date: Tue, 24 Nov 2020 19:39:30 -0500 Subject: [PATCH 038/662] remove TOP from main deadlock_data query, but still respect @Top if needed --- sp_BlitzLock.sql | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index e192b33cc..9d933b816 100644 --- a/sp_BlitzLock.sql +++ b/sp_BlitzLock.sql @@ -187,7 +187,7 @@ You need to use an Azure storage account, and the path has to look like this: ht finding NVARCHAR(4000) ); - DECLARE @d VARCHAR(40), @StringToExecute NVARCHAR(4000),@StringToExecuteParams NVARCHAR(500),@r NVARCHAR(200),@OutputTableFindings NVARCHAR(100); + DECLARE @d VARCHAR(40), @StringToExecute NVARCHAR(4000),@StringToExecuteParams NVARCHAR(500),@r NVARCHAR(200),@OutputTableFindings NVARCHAR(100),@DeadlockCount INT; DECLARE @ServerName NVARCHAR(256) DECLARE @OutputDatabaseCheck BIT; SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); @@ -309,7 +309,7 @@ You need to use an Azure storage account, and the path has to look like this: ht WITH xml AS ( SELECT CONVERT(XML, event_data) AS deadlock_xml FROM sys.fn_xe_file_target_read_file(@EventSessionPath, NULL, NULL, NULL) ) - SELECT TOP ( @Top ) ISNULL(xml.deadlock_xml, '') AS deadlock_xml + SELECT ISNULL(xml.deadlock_xml, '') AS deadlock_xml INTO #deadlock_data FROM xml LEFT JOIN #t AS t @@ -322,6 +322,17 @@ You need to use an Azure storage account, and the path has to look like this: ht ORDER BY xml.deadlock_xml.value('(/event/@timestamp)[1]', 'datetime') DESC OPTION ( RECOMPILE ); + /*Optimization: if we got back more rows than @Top, remove them. This seems to be better than using @Top in the query above as that results in excessive memory grant*/ + SET @DeadlockCount = @@ROWCOUNT + IF( @Top < @DeadlockCount ) BEGIN + WITH T + AS ( + SELECT TOP ( @DeadlockCount - @Top) * + FROM #deadlock_data + ORDER BY #deadlock_data.deadlock_xml.value('(/event/@timestamp)[1]', 'datetime') ASC) + DELETE FROM T + END + /*Parse process and input buffer XML*/ SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); RAISERROR('Parse process and input buffer XML %s', 0, 1, @d) WITH NOWAIT; From ff8696be1e7ae7bac6eff4f3c1bfc05e1b7f5ca0 Mon Sep 17 00:00:00 2001 From: skrishnan31 <75037312+skrishnan31@users.noreply.github.com> Date: Wed, 25 Nov 2020 18:06:40 -0600 Subject: [PATCH 039/662] 2020_11 release Issue #2693 Changed on 11/25/2020 to exclude IgnoreDatabases when counting # of databases --- sp_BlitzIndex.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index 09714edfd..9b8565400 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -828,7 +828,7 @@ ELSE ELSE @DatabaseName END; END; -SET @NumDatabases = (SELECT COUNT(*) FROM #DatabaseList); +SET @NumDatabases = (SELECT COUNT(*) FROM #DatabaseList AS D LEFT OUTER JOIN #Ignore_Databases AS I ON D.DatabaseName = I.DatabaseName WHERE I.DatabaseName IS NULL); SET @msg = N'Number of databases to examine: ' + CAST(@NumDatabases AS NVARCHAR(50)); RAISERROR (@msg,0,1) WITH NOWAIT; From 5c8e02a56fe79eea2b721d94e87e5ea195e1845c Mon Sep 17 00:00:00 2001 From: MikeyBronowski Date: Thu, 26 Nov 2020 14:44:55 +0000 Subject: [PATCH 040/662] Addressing 2694 - column order --- sp_BlitzIndex.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index c529dee04..559e9e50d 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -1990,8 +1990,8 @@ BEGIN TRY IF @dsql IS NULL RAISERROR('@dsql is null',16,1); - INSERT #TemporalTables ( database_name, database_id, schema_name, table_name, history_table_name, - history_schema_name, start_column_name, end_column_name, period_name ) + INSERT #TemporalTables ( database_name, database_id, schema_name, table_name, history_schema_name, + history_table_name, start_column_name, end_column_name, period_name ) EXEC sp_executesql @dsql; From 7d42f651197bd21b93e1ee033fee7a58bcb97fdc Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Fri, 27 Nov 2020 02:52:21 -0800 Subject: [PATCH 041/662] #2697 sp_BlitzFirst ignore FT_IFTSCHC_MUTEX Closes #2697. --- sp_BlitzFirst.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sp_BlitzFirst.sql b/sp_BlitzFirst.sql index e6c7de699..c6a9ae7b0 100644 --- a/sp_BlitzFirst.sql +++ b/sp_BlitzFirst.sql @@ -509,7 +509,7 @@ BEGIN INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_COMPROWSET_RWLOCK','Full Text Search',0); INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_IFTS_RWLOCK','Full Text Search',0); INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_IFTS_SCHEDULER_IDLE_WAIT','Idle',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_IFTSHC_MUTEX','Full Text Search',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_IFTSHC_MUTEX','Full Text Search',1); INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_IFTSISM_MUTEX','Full Text Search',1); INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_MASTER_MERGE','Full Text Search',0); INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_MASTER_MERGE_COORDINATOR','Full Text Search',0); From 18e8045eb161ec056d45ace0e37713e84655f61b Mon Sep 17 00:00:00 2001 From: fvanderhaegen Date: Fri, 27 Nov 2020 15:39:37 +0100 Subject: [PATCH 042/662] #2700 sp_DatabaseRestore added CommandExecute sp_executesql was replaced by CommandExecute so there would be some logging in case of error. --- sp_DatabaseRestore.sql | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/sp_DatabaseRestore.sql b/sp_DatabaseRestore.sql index b74b4b22e..589482a6f 100755 --- a/sp_DatabaseRestore.sql +++ b/sp_DatabaseRestore.sql @@ -230,7 +230,8 @@ DECLARE @cmd NVARCHAR(4000) = N'', --Holds xp_cmdshell command @LogLastNameInMsdbAS NVARCHAR(MAX) = N'', -- Holds last TRN file name already restored @FileListParamSQL NVARCHAR(4000) = N'', --Holds INSERT list for #FileListParameters @BackupParameters NVARCHAR(500) = N'', --Used to save BlockSize, MaxTransferSize and BufferCount - @RestoreDatabaseID SMALLINT; --Holds DB_ID of @RestoreDatabaseName + @RestoreDatabaseID SMALLINT, --Holds DB_ID of @RestoreDatabaseName + @UnquotedRestoreDatabaseName nvarchar(128); --Holds the unquoted @RestoreDatabaseName DECLARE @FileListSimple TABLE ( BackupFile NVARCHAR(255) NOT NULL, @@ -465,6 +466,8 @@ END SET @RestoreDatabaseID = DB_ID(@RestoreDatabaseName); SET @RestoreDatabaseName = QUOTENAME(@RestoreDatabaseName); +SET @UnquotedRestoreDatabaseName = PARSENAME(@RestoreDatabaseName,1); + --If xp_cmdshell is disabled, force use of xp_dirtree IF NOT EXISTS (SELECT * FROM sys.configurations WHERE name = 'xp_cmdshell' AND value_in_use = 1) SET @SimpleFolderEnumeration = 1; @@ -752,10 +755,8 @@ BEGIN IF @sql IS NULL PRINT '@sql is NULL for SINGLE_USER'; PRINT @sql; END; - IF @Debug IN (0, 1) AND @Execute = 'Y' - EXECUTE master.sys.sp_executesql @stmt = @sql; IF @Debug IN (0, 1) AND @Execute = 'Y' AND DATABASEPROPERTYEX(@RestoreDatabaseName,'STATUS') != 'RESTORING' - EXECUTE @sql = [dbo].[CommandExecute] @Command = @sql, @CommandType = 'ALTER DATABASE SINGLE_USER', @Mode = 1, @DatabaseName = @Database, @LogToTable = 'Y', @Execute = 'Y'; + EXECUTE @sql = [dbo].[CommandExecute] @Command = @sql, @CommandType = 'ALTER DATABASE SINGLE_USER', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y'; END IF @ExistingDBAction IN (2, 3) BEGIN @@ -774,7 +775,7 @@ BEGIN PRINT @sql; END; IF @Debug IN (0, 1) AND @Execute = 'Y' - EXECUTE master.sys.sp_executesql @stmt = @sql; + EXECUTE @sql = [dbo].[CommandExecute] @Command = @sql, @CommandType = 'KILL CONNECTIONS', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y'; END IF @ExistingDBAction = 3 BEGIN @@ -787,7 +788,7 @@ BEGIN PRINT @sql; END; IF @Debug IN (0, 1) AND @Execute = 'Y' - EXECUTE master.sys.sp_executesql @stmt = @sql; + EXECUTE @sql = [dbo].[CommandExecute] @Command = @sql, @CommandType = 'DROP DATABASE', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y'; END IF @ExistingDBAction = 4 BEGIN @@ -800,7 +801,7 @@ BEGIN PRINT @sql; END; IF @Debug IN (0, 1) AND @Execute = 'Y' AND DATABASEPROPERTYEX(@RestoreDatabaseName,'STATUS') != 'RESTORING' - EXECUTE @sql = [dbo].[CommandExecute] @Command = @sql, @CommandType = 'OFFLINE DATABASE', @Mode = 1, @DatabaseName = @Database, @LogToTable = 'Y', @Execute = 'Y'; + EXECUTE @sql = [dbo].[CommandExecute] @Command = @sql, @CommandType = 'OFFLINE DATABASE', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y'; END; END ELSE @@ -854,7 +855,7 @@ BEGIN END; IF @Debug IN (0, 1) AND @Execute = 'Y' - EXECUTE master.sys.sp_executesql @stmt = @sql; + EXECUTE @sql = [dbo].[CommandExecute] @Command = @sql, @CommandType = 'RESTORE DATABASE', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y'; -- We already loaded #Headers above @@ -1037,7 +1038,7 @@ BEGIN PRINT @sql; END; IF @Debug IN (0, 1) AND @Execute = 'Y' - EXECUTE master.sys.sp_executesql @stmt = @sql; + EXECUTE @sql = [dbo].[CommandExecute] @Command = @sql, @CommandType = 'RESTORE DATABASE', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y'; --get the backup completed data so we can apply tlogs from that point forwards SET @sql = REPLACE(@HeadersSQL, N'{Path}', @CurrentBackupPathDiff + @LastDiffBackup); @@ -1358,7 +1359,7 @@ WHERE BackupFile IS NOT NULL; END; IF @Debug IN (0, 1) AND @Execute = 'Y' - EXECUTE master.sys.sp_executesql @stmt = @sql; + EXECUTE @sql = [dbo].[CommandExecute] @Command = @sql, @CommandType = 'RESTORE LOG', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y'; END; SET @LogRestoreRanking += 1; @@ -1383,7 +1384,7 @@ IF @RunRecovery = 1 END; IF @Debug IN (0, 1) AND @Execute = 'Y' - EXECUTE master.sys.sp_executesql @stmt = @sql; + EXECUTE @sql = [dbo].[CommandExecute] @Command = @sql, @CommandType = 'RECOVER DATABASE', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y'; END; -- Ensure simple recovery model @@ -1398,7 +1399,7 @@ IF @ForceSimpleRecovery = 1 END; IF @Debug IN (0, 1) AND @Execute = 'Y' - EXECUTE master.sys.sp_executesql @stmt = @sql; + EXECUTE @sql = [dbo].[CommandExecute] @Command = @sql, @CommandType = 'SIMPLE LOGGING', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y'; END; -- Run checkdb against this database @@ -1413,7 +1414,7 @@ IF @RunCheckDB = 1 END; IF @Debug IN (0, 1) AND @Execute = 'Y' - EXECUTE master.sys.sp_executesql @stmt = @sql; + EXECUTE @sql = [dbo].[CommandExecute] @Command = @sql, @CommandType = 'DBCC CHECKDB', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y'; END; @@ -1432,7 +1433,7 @@ IF @DatabaseOwner IS NOT NULL END; IF @Debug IN (0, 1) AND @Execute = 'Y' - EXECUTE (@sql); + EXECUTE @sql = [dbo].[CommandExecute] @Command = @sql, @CommandType = 'ALTER AUTHORIZATION', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y'; END ELSE BEGIN @@ -1457,7 +1458,7 @@ IF @TestRestore = 1 END; IF @Debug IN (0, 1) AND @Execute = 'Y' - EXECUTE master.sys.sp_executesql @stmt = @sql; + EXECUTE @sql = [dbo].[CommandExecute] @Command = @sql, @CommandType = 'DROP DATABASE', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y'; END; From 170665459c84bfeb1a2660312922f0f83c28eec2 Mon Sep 17 00:00:00 2001 From: Rene Guerra Millet Date: Sat, 28 Nov 2020 10:15:10 -0500 Subject: [PATCH 043/662] ineachdb proc --- sp_ineachdb.sql | 62 +++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 60 insertions(+), 2 deletions(-) diff --git a/sp_ineachdb.sql b/sp_ineachdb.sql index 57961f94b..3d5eb5c8c 100644 --- a/sp_ineachdb.sql +++ b/sp_ineachdb.sql @@ -101,10 +101,14 @@ BEGIN @thisdb sysname, @cr char(2) = CHAR(13) + CHAR(10), @SQLVersion AS tinyint = (@@microsoftversion / 0x1000000) & 0xff, -- Stores the SQL Server Version Number(8(2000),9(2005),10(2008 & 2008R2),11(2012),12(2014),13(2016),14(2017),15(2019) - @ServerName AS sysname = CONVERT(sysname, SERVERPROPERTY('ServerName')); -- Stores the SQL Server Instance name. + @ServerName AS sysname = CONVERT(sysname, SERVERPROPERTY('ServerName')), -- Stores the SQL Server Instance name. + @NoSpaces nvarchar(20) = N'%[^' + CHAR(9) + CHAR(32) + CHAR(10) + CHAR(13) + N']%'; --Pattern for PATINDEX + CREATE TABLE #ineachdb(id int, name nvarchar(512), is_distributor bit); + +/* -- first, let's limit to only DBs the caller is interested in IF @database_list > N'' -- comma-separated list of potentially valid/invalid/quoted/unquoted names @@ -146,7 +150,62 @@ BEGIN ON names.name = d.name OPTION (MAXRECURSION 0); END +*/ +/* +@database_list and @exclude_list are are processed at the same time +1)Read the list searching for a comma or [ +2)If we find a comma, save the name +3)If we find a [, we begin to accumulate the result until we reach closing ], (jumping over escaped ]]). +4)Finally, tabs, line breaks and spaces are removed from unquoted names +*/ +WITH C +AS (SELECT V.SrcList + , CAST('' AS nvarchar(MAX)) AS Name + , V.DBList + , 0 AS InBracket + , 0 AS Quoted + FROM (VALUES ('In', @database_list + ','), ('Out', @exclude_list + ',')) AS V (SrcList, DBList) + UNION ALL + SELECT C.SrcList + , IIF(V.Found = '[', '', SUBSTRING(C.DBList, 1, V.Place - 1))/*remove initial [*/ + , STUFF(C.DBList, 1, V.Place, '') + , IIF(V.Found = '[', 1, 0) + , 0 + FROM C + CROSS APPLY + ( VALUES (PATINDEX('%[,[]%', C.DBList), SUBSTRING(C.DBList, PATINDEX('%[,[]%', C.DBList), 1))) AS V (Place, Found) + WHERE C.DBList > '' + AND C.InBracket = 0 + UNION ALL + SELECT C.SrcList + , CONCAT(C.Name, SUBSTRING(C.DBList, 1, V.Place + W.DoubleBracket - 1)) /*Accumulates only one ] if escaped]] or none if end]*/ + , STUFF(C.DBList, 1, V.Place + W.DoubleBracket, '') + , W.DoubleBracket + , 1 + FROM C + CROSS APPLY (VALUES (CHARINDEX(']', C.DBList))) AS V (Place) + CROSS APPLY (VALUES (IIF(SUBSTRING(C.DBList, V.Place + 1, 1) = ']', 1, 0))) AS W (DoubleBracket) + WHERE C.DBList > '' + AND C.InBracket = 1) + , F +AS (SELECT C.SrcList + , IIF(C.Quoted = 0 + ,SUBSTRING(C.name, PATINDEX(@NoSpaces, name), DATALENGTH (name)/2 - PATINDEX(@NoSpaces, name) - PATINDEX(@NoSpaces, REVERSE(name))+2) + , C.Name) + AS name + FROM C + WHERE C.InBracket = 0 + AND C.Name > '') + SELECT d.database_id + , d.name + , d.is_distributor +FROM sys.databases AS d +WHERE ( EXISTS (SELECT NULL FROM F WHERE F.name = d.name AND F.SrcList = 'In') + OR @database_list IS NULL) + AND NOT EXISTS (SELECT NULL FROM F WHERE F.name = d.name AND F.SrcList = 'Out') +OPTION (MAXRECURSION 0); +; -- next, let's delete any that *don't* match various criteria passed in DELETE dbs FROM #ineachdb AS dbs WHERE (@system_only = 1 AND (id NOT IN (1,2,3,4) AND is_distributor <> 1)) @@ -272,4 +331,3 @@ BEGIN CLOSE dbs; DEALLOCATE dbs; END -GO From b3e4850a5bc664d957ad15a87fa384013d7ba61c Mon Sep 17 00:00:00 2001 From: Ali Soylu Date: Sun, 29 Nov 2020 18:48:37 -0500 Subject: [PATCH 044/662] sp_BlitzFirst Update comments and documentation to be consistent --- .../sp_BlitzFirst_Checks_by_Priority.md | 1 + sp_BlitzFirst.sql | 40 +++++++++---------- 2 files changed, 21 insertions(+), 20 deletions(-) diff --git a/Documentation/sp_BlitzFirst_Checks_by_Priority.md b/Documentation/sp_BlitzFirst_Checks_by_Priority.md index 2150e0663..1bb54e753 100644 --- a/Documentation/sp_BlitzFirst_Checks_by_Priority.md +++ b/Documentation/sp_BlitzFirst_Checks_by_Priority.md @@ -25,6 +25,7 @@ If you want to add a new check, start at 47 | 1 | SQL Server Internal Maintenance | Log File Shrinking | https://BrentOzar.com/go/logsize | 14 | | 10 | Server Performance | Poison Wait Detected | https://BrentOzar.com/go/poison | 30 | | 10 | Server Performance | Target Memory Lower Than Max | https://BrentOzar.com/go/target | 35 | +| 10 | Azure Performance | Database is Maxed Out | https://BrentOzar.com/go/maxedout | 41 | | 40 | Table Problems | Forwarded Fetches/Sec High | https://BrentOzar.com/go/fetch | 29 | | 50 | In-Memory OLTP | Garbage Collection in Progress | https://BrentOzar.com/go/garbage | 31 | | 50 | Query Problems | Compilations/Sec High | https://BrentOzar.com/go/compile | 15 | diff --git a/sp_BlitzFirst.sql b/sp_BlitzFirst.sql index c6a9ae7b0..49d802c68 100644 --- a/sp_BlitzFirst.sql +++ b/sp_BlitzFirst.sql @@ -168,6 +168,7 @@ END; IF UPPER(@OutputType) LIKE 'TOP 10%' SET @OutputType = 'Top10'; IF @OutputType = 'Top10' SET @SinceStartup = 1; +/* Logged Message - CheckID 38 */ IF @LogMessage IS NOT NULL BEGIN @@ -1564,7 +1565,7 @@ BEGIN EXECUTE sp_executesql @StringToExecute; END; - /* Query Problems - Plan Cache Erased Recently */ + /* Query Problems - Plan Cache Erased Recently - CheckID 7 */ IF DATEADD(mi, -15, SYSDATETIME()) < (SELECT TOP 1 creation_time FROM sys.dm_exec_query_stats ORDER BY creation_time) BEGIN INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) @@ -1619,7 +1620,7 @@ BEGIN AND NOT (resource_type = N'DATABASE' AND request_mode = N'S' AND request_status = N'GRANT' AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE')); - /*Query Problems - Clients using implicit transactions */ + /*Query Problems - Clients using implicit transactions - CheckID 37 */ IF @Seconds > 0 AND ( @@VERSION NOT LIKE 'Microsoft SQL Server 2005%' AND @@VERSION NOT LIKE 'Microsoft SQL Server 2008%' @@ -1821,7 +1822,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, OUTER APPLY sys.dm_exec_query_plan(Grants.[plan_handle]) AS QueryPlan WHERE Grants.granted_memory_kb > ((@MemoryGrantThresholdPct/100.00)*(@MaxWorkspace*1024)); - /* Query Problems - Memory Leak in USERSTORE_TOKENPERM Cache */ + /* Query Problems - Memory Leak in USERSTORE_TOKENPERM Cache - CheckID 45 */ IF EXISTS (SELECT * FROM sys.all_columns WHERE object_id = OBJECT_ID('sys.dm_os_memory_clerks') AND name = 'pages_kb') BEGIN /* SQL 2012+ version */ @@ -1976,9 +1977,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, y.request_id, y.parallelism_skew; - /* - CheckID 42: Queries in dm_exec_query_profiles showing signs of poor cardinality estimates - */ + /* Queries in dm_exec_query_profiles showing signs of poor cardinality estimates - CheckID 42 */ INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, OpenTransactionCount, QueryHash, QueryPlan) SELECT 42 AS CheckID, @@ -2024,9 +2023,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, SET @StringToExecute = @StringToExecute + N'; - /* - CheckID 43: Queries in dm_exec_query_profiles showing signs of unbalanced parallelism - */ + /* Queries in dm_exec_query_profiles showing signs of unbalanced parallelism - CheckID 43 */ INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, OpenTransactionCount, QueryHash, QueryPlan) SELECT 43 AS CheckID, @@ -2078,7 +2075,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, END END - /* Server Performance - High CPU Utilization CheckID 24 */ + /* Server Performance - High CPU Utilization - CheckID 24 */ IF @Seconds < 30 BEGIN /* If we're waiting less than 30 seconds, run this check now rather than wait til the end. @@ -2099,6 +2096,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, ) AS y WHERE 100 - SystemIdle >= 50; + /* CPU Utilization - CheckID 23 */ IF SERVERPROPERTY('Edition') <> 'SQL Azure' WITH y AS @@ -2139,7 +2137,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, ORDER BY y.event_date DESC; - /* Highlight if non SQL processes are using >25% CPU */ + /* Highlight if non SQL processes are using >25% CPU - CheckID 28 */ INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) SELECT 28, 50, 'Server Performance', 'High CPU Utilization - Not SQL', CONVERT(NVARCHAR(100),100 - (y.SQLUsage + y.SystemIdle)) + N'% - Other Processes (not SQL Server) are using this much CPU. This may impact on the performance of your SQL Server instance', 100 - (y.SQLUsage + y.SystemIdle), 'http://www.BrentOzar.com/go/cpu' FROM ( @@ -2342,7 +2340,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, WHERE DATEDIFF(ss, pFirst.SampleTime, pNow.SampleTime) > 0; - /* If we're within 10 seconds of our projected finish time, do the plan cache analysis. */ + /* Query Stats - If we're within 10 seconds of our projected finish time, do the plan cache analysis. - CheckID 18 */ IF DATEDIFF(ss, @FinishSampleTime, SYSDATETIMEOFFSET()) > 10 AND @CheckProcedureCache = 1 BEGIN @@ -2483,7 +2481,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, FROM #QueryStats qs INNER JOIN qsTop ON qs.ID = qsTop.ID; - /* Query Stats - CheckID 17 - Most Resource-Intensive Queries */ + /* Query Stats - Most Resource-Intensive Queries - CheckID 17 */ INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, QueryText, QueryStatsNowID, QueryStatsFirstID, PlanHandle, QueryHash) SELECT 17, 210, 'Query Stats', 'Most Resource-Intensive Queries', 'http://www.BrentOzar.com/go/topqueries', 'Query stats during the sample:' + @LineFeed + @@ -2882,13 +2880,13 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, CROSS JOIN waits2; END; - /* Server Performance - High CPU Utilization CheckID 24 */ + /* If we're waiting 30+ seconds, run these checks at the end. + We get this data from the ring buffers, and it's only updated once per minute, so might + as well get it now - whereas if we're checking 30+ seconds, it might get updated by the + end of our sp_BlitzFirst session. */ IF @Seconds >= 30 BEGIN - /* If we're waiting 30+ seconds, run this check at the end. - We get this data from the ring buffers, and it's only updated once per minute, so might - as well get it now - whereas if we're checking 30+ seconds, it might get updated by the - end of our sp_BlitzFirst session. */ + /* Server Performance - High CPU Utilization CheckID 24 */ INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) SELECT 24, 50, 'Server Performance', 'High CPU Utilization', CAST(100 - SystemIdle AS NVARCHAR(20)) + N'%. Ring buffer details: ' + CAST(record AS NVARCHAR(4000)), 100 - SystemIdle, 'http://www.BrentOzar.com/go/cpu' FROM ( @@ -2903,6 +2901,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, ) AS y WHERE 100 - SystemIdle >= 50; + /* Server Performance - CPU Utilization CheckID 23 */ INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) SELECT 23, 250, 'Server Info', 'CPU Utilization', CAST(100 - SystemIdle AS NVARCHAR(20)) + N'%. Ring buffer details: ' + CAST(record AS NVARCHAR(4000)), 100 - SystemIdle, 'http://www.BrentOzar.com/go/cpu' FROM ( @@ -2975,7 +2974,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 'We hope you found this tool useful.' ); - /* Outdated sp_BlitzFirst - sp_BlitzFirst is Over 6 Months Old */ + /* Outdated sp_BlitzFirst - sp_BlitzFirst is Over 6 Months Old - CheckID 27 */ IF DATEDIFF(MM, @VersionDate, SYSDATETIMEOFFSET()) > 6 BEGIN INSERT INTO #BlitzFirstResults @@ -3064,8 +3063,9 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, END; - ELSE /* No sp_BlitzCache found, or it's outdated */ + ELSE BEGIN + /* No sp_BlitzCache found, or it's outdated - CheckID 36 */ INSERT INTO #BlitzFirstResults ( CheckID , Priority , From 661a24f4b6fadc0119cbb4c8a87d46652120e14d Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Mon, 30 Nov 2020 02:53:16 -0800 Subject: [PATCH 045/662] Documenting how to check versions --- README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/README.md b/README.md index a1ba0b914..32016bff4 100644 --- a/README.md +++ b/README.md @@ -451,6 +451,18 @@ For information about how this works, see [Tara Kizer's white paper on Log Shipp * @ExpertMode = 1 - turns on more details useful for digging deeper into results. * @OutputDatabaseName, @OutputSchemaName, @OutputTableName - pass all three of these in, and the stored proc's output will be written to a table. We'll create the table if it doesn't already exist. @OutputServerName will push the data to a linked server as long as you configure the linked server first and enable RPC OUT calls. +To check versions of any of the stored procedures, use their output parameters for Version and VersionDate like this: + +``` +DECLARE @VersionOutput VARCHAR(30), @VersionDateOutput DATETIME; +EXEC sp_Blitz + @Version = @VersionOutput OUTPUT, + @VersionDate = @VersionDateOutput OUTPUT, + @VersionCheckMode = 1; +SELECT @VersionOutput AS Version, + @VersionDateOutput AS VersionDate; +``` + [*Back to top*](#header1) From 65541680efb3197c09389877f089df5c6fae15bc Mon Sep 17 00:00:00 2001 From: Ali Soylu Date: Tue, 1 Dec 2020 18:01:42 -0500 Subject: [PATCH 046/662] Update sp_BlitzCache documentation based on code --- Documentation/sp_BlitzCache_Checks_by_Priority.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Documentation/sp_BlitzCache_Checks_by_Priority.md b/Documentation/sp_BlitzCache_Checks_by_Priority.md index 2aed1a3f9..b0b5fdf63 100644 --- a/Documentation/sp_BlitzCache_Checks_by_Priority.md +++ b/Documentation/sp_BlitzCache_Checks_by_Priority.md @@ -13,6 +13,8 @@ If you want to add a new check, start at 70 |----------|---------------------------------|---------------------------------------|-------------------------------------------------|----------|-------------| | 10 | Execution Plans | Forced Serialization | http://www.brentozar.com/blitzcache/forced-serialization/ | 25 | No | | 10 | Large USERSTORE_TOKENPERM cache | Using Over 10% of the Buffer Pool | https://brentozar.com/go/userstore | 69 | No | +| 50 | Complexity | High Compile CPU | https://www.brentozar.com/blitzcache/high-compilers/ | 64 | No | +| 50 | Complexity | High Compile Memory | https://www.brentozar.com/blitzcache/high-compilers/ | 65 | No | | 50 | Execution Plans | Compilation timeout | http://brentozar.com/blitzcache/compilation-timeout/ | 18 | No | | 50 | Execution Plans | Compile Memory Limit Exceeded | http://brentozar.com/blitzcache/compile-memory-limit-exceeded/ | 19 | No | | 50 | Execution Plans | No join predicate | http://brentozar.com/blitzcache/no-join-predicate/ | 20 | No | @@ -28,6 +30,7 @@ If you want to add a new check, start at 70 | 50 | Performance | Long Running Query | http://brentozar.com/blitzcache/long-running-queries/ | 9 | No | | 50 | Performance | Missing Indexes | http://brentozar.com/blitzcache/missing-index-request/ | 10 | No | | 50 | Selects w/ Writes | Read queries are causing writes | https://dba.stackexchange.com/questions/191825/ | 66 | No | +| 100 | Complexity | Long Compile Time | https://www.brentozar.com/blitzcache/high-compilers/ | No | | 100 | Complexity | Many to Many Merge | Blog not published yet | 61 | Yes | | 100 | Complexity | Row Estimate Mismatch | https://www.brentozar.com/blitzcache/bad-estimates/ | 56 | Yes | | 100 | Compute Scalar That References A CLR Function | Calls CLR Functions | https://www.brentozar.com/blitzcache/compute-scalar-functions/| 31 | Yes | @@ -75,7 +78,7 @@ If you want to add a new check, start at 70 | 200 | Execution Plans | Nearly Parallel | http://brentozar.com/blitzcache/query-cost-near-cost-threshold-parallelism/ | 7 | No | | 200 | Execution Plans | Parallel | http://brentozar.com/blitzcache/parallel-plans-detected/ | 6 | No | | 200 | Indexes | Backwards Scans | https://www.brentozar.com/blitzcache/backwards-scans/ | 38 | Yes | -| 200 | Is Paul White Electric? | This query has a Switch operator in it! | https://www.sql.kiwi/2013/06/hello-operator-my-switch-is-bored.html | 998 | Yes | +| 200 | Is Paul White Electric? | This query has a Switch operator in it! | https://www.sql.kiwi/2013/06/hello-operator-my-switch-is-bored.html | 57 | Yes | | 200 | Trace Flags | Session Level Trace Flags Enabled | https://www.brentozar.com/blitz/trace-flags-enabled-globally/ | 29 | No | | 254 | Plan Cache Information | Breaks cache down by creation date (24/4/1 hrs) | None | 999 | No | | 255 | Global Trace Flags Enabled | You have Global Trace Flags enabled on your server | https://www.brentozar.com/blitz/trace-flags-enabled-globally/ | 1000 | No | From 075461d59c7faf1882f2ab16b7d6a09e68c507aa Mon Sep 17 00:00:00 2001 From: Ali Soylu Date: Tue, 1 Dec 2020 18:44:45 -0500 Subject: [PATCH 047/662] Update sp_blitz documentation from source --- Documentation/sp_Blitz_Checks_by_Priority.md | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/Documentation/sp_Blitz_Checks_by_Priority.md b/Documentation/sp_Blitz_Checks_by_Priority.md index a8df59e17..375b80e3e 100644 --- a/Documentation/sp_Blitz_Checks_by_Priority.md +++ b/Documentation/sp_Blitz_Checks_by_Priority.md @@ -12,6 +12,7 @@ If you want to add a new one, start at 257. | Priority | FindingsGroup | Finding | URL | CheckID | |----------|-----------------------------|---------------------------------------------------------|------------------------------------------------------------------------|----------| | 0 | Outdated sp_Blitz | sp_Blitz is Over 6 Months Old | https://www.BrentOzar.com/blitz/ | 155 | +| 0 | Informational | @CheckUserDatabaseObjects Disabled | https://www.BrentOzar.com/blitz/ | 201 | | 0 | Informational | @CheckUserDatabaseObjects Disabled | https://www.BrentOzar.com/blitz/ | 204 | | 0 | Informational | Some Checks Skipped | https://www.BrentOzar.com/blitz/ | 223 | | 1 | Backup | Backing Up to Same Drive Where Databases Reside | https://www.BrentOzar.com/go/backup | 93 | @@ -39,10 +40,10 @@ If you want to add a new one, start at 257. | 10 | Performance | DBCC DROPCLEANBUFFERS Ran Recently | https://www.BrentOzar.com/go/dbcc | 207 | | 10 | Performance | DBCC FREEPROCCACHE Ran Recently | https://www.BrentOzar.com/go/dbcc | 208 | | 10 | Performance | DBCC SHRINK% Ran Recently | https://www.BrentOzar.com/go/dbcc | 210 | -| 10 | Performance | DBCC WRITEPAGE Used Recently | https://www.BrentOzar.com/go/dbcc | 209 | | 10 | Performance | High Memory Use for In-Memory OLTP (Hekaton) | https://www.BrentOzar.com/go/hekaton | 145 | | 10 | Performance | Memory Nodes Offline | https://www.BrentOzar.com/go/schedulers | 110 | | 10 | Performance | Plan Cache Erased Recently | https://www.BrentOzar.com/askbrent/plan-cache-erased-recently/ | 125 | +| 10 | Reliability | DBCC WRITEPAGE Used Recently | https://www.BrentOzar.com/go/dbcc | 209 | | 10 | Reliability | Server restarted in last 24 hours | | 221 | | 20 | Reliability | Dangerous Build of SQL Server (Corruption) | http://sqlperformance.com/2014/06/sql-indexes/hotfix-sql-2012-rebuilds | 129 | | 20 | Reliability | Dangerous Build of SQL Server (Security) | https://technet.microsoft.com/en-us/library/security/MS14-044 | 157 | @@ -63,7 +64,6 @@ If you want to add a new one, start at 257. | 50 | Performance | Snapshotting Too Many Databases | https://www.BrentOzar.com/go/toomanysnaps | 236 | | 50 | Performance | Too Much Free Memory | https://www.BrentOzar.com/go/freememory | 165 | | 50 | Performance | Wait Stats Cleared Recently| | 205 | -| 50 | Reliability | DBCC WRITEPAGE Used Recently | https://www.BrentOzar.com/go/dbcc | 209 | | 50 | Reliability | Full Text Indexes Not Updating | https://www.BrentOzar.com/go/fulltext | 113 | | 50 | Reliability | Page Verification Not Optimal | https://www.BrentOzar.com/go/torn | 14 | | 50 | Reliability | Possibly Broken Log Shipping | https://www.BrentOzar.com/go/shipping | 111 | @@ -135,7 +135,6 @@ If you want to add a new one, start at 257. | 170 | Reliability | Errors Logged Recently in the Default Trace | https://www.BrentOzar.com/go/defaulttrace | 150 | | 170 | Reliability | Max File Size Set | https://www.BrentOzar.com/go/maxsize | 80 | | 170 | Reliability | Remote Admin Connections Disabled | https://www.BrentOzar.com/go/dac | 100 | -| 200 | Backup | Backing Up Unneeded Database | https://www.BrentOzar.com/go/reportservertempdb | 127 | | 200 | Backup | MSDB Backup History Not Purged | https://www.BrentOzar.com/go/history | 3 | | 200 | Backup | MSDB Backup History Purged Too Frequently | https://www.BrentOzar.com/go/history | 186 | | 200 | Informational | @@Servername not set | https://www.BrentOzar.com/go/servername | 70 | @@ -143,7 +142,6 @@ If you want to add a new one, start at 257. | 200 | Informational | Backup Compression Default Off | https://www.BrentOzar.com/go/backup | 116 | | 200 | Informational | Cluster Node | https://www.BrentOzar.com/go/node | 53 | | 200 | Informational | Collation different than tempdb | https://www.BrentOzar.com/go/collate | 76 | -| 200 | Informational | Database Collation Mismatch | https://www.BrentOzar.com/go/collate | 58 | | 200 | Informational | Database Encrypted | https://www.BrentOzar.com/go/tde | 21 | | 200 | Informational | Date Correlation On | https://www.BrentOzar.com/go/corr | 20 | | 200 | Informational | Linked Server Configured | https://www.BrentOzar.com/go/link | 49 | @@ -258,7 +256,6 @@ If you want to add a new one, start at 257. | 210 | Non-Default Database Config | Delayed Durability Enabled | https://www.BrentOzar.com/go/dbdefaults | 143 | | 210 | Non-Default Database Config | Forced Parameterization Enabled | https://www.BrentOzar.com/go/dbdefaults | 138 | | 210 | Non-Default Database Config | Memory Optimized Enabled | https://www.BrentOzar.com/go/dbdefaults | 144 | -| 210 | Non-Default Database Config | Query Store Enabled | https://www.BrentOzar.com/go/dbdefaults | 139 | | 210 | Non-Default Database Config | Read Committed Snapshot Isolation Enabled | https://www.BrentOzar.com/go/dbdefaults | 133 | | 210 | Non-Default Database Config | Recursive Triggers Enabled | https://www.BrentOzar.com/go/dbdefaults | 136 | | 210 | Non-Default Database Config | Snapshot Isolation Enabled | https://www.BrentOzar.com/go/dbdefaults | 132 | From 304eef643d721f832a5ba5ee00504037d895f474 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Wed, 2 Dec 2020 05:58:36 -0800 Subject: [PATCH 048/662] #2707 sp_BlitzFirst more files Show 20 files instead of 5, and sort them by stalls. Closes #2707. --- sp_BlitzFirst.sql | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/sp_BlitzFirst.sql b/sp_BlitzFirst.sql index c6a9ae7b0..4977adfd5 100644 --- a/sp_BlitzFirst.sql +++ b/sp_BlitzFirst.sql @@ -4258,13 +4258,14 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, AND wd1.FileID = wd2.FileID ) SELECT - Pattern, [Sample Time], [Sample (seconds)], [File Name], [Drive], [# Reads/Writes],[MB Read/Written],[Avg Stall (ms)], [file physical name] + Pattern, [Sample Time], [Sample (seconds)], [File Name], [Drive], [# Reads/Writes],[MB Read/Written],[Avg Stall (ms)], [file physical name], [StallRank] FROM readstats - WHERE StallRank <=5 AND [MB Read/Written] > 0 + WHERE StallRank <=20 AND [MB Read/Written] > 0 UNION ALL - SELECT Pattern, [Sample Time], [Sample (seconds)], [File Name], [Drive], [# Reads/Writes],[MB Read/Written],[Avg Stall (ms)], [file physical name] + SELECT Pattern, [Sample Time], [Sample (seconds)], [File Name], [Drive], [# Reads/Writes],[MB Read/Written],[Avg Stall (ms)], [file physical name], [StallRank] FROM writestats - WHERE StallRank <=5 AND [MB Read/Written] > 0; + WHERE StallRank <=20 AND [MB Read/Written] > 0 + ORDER BY Pattern, StallRank; ------------------------- From 02b3eef40bd2b20985de977d10be05e8578a35d4 Mon Sep 17 00:00:00 2001 From: Greg Dodd Date: Wed, 9 Dec 2020 06:22:02 -0500 Subject: [PATCH 049/662] Update sp_DatabaseRestore.sql Using restorehistory to work out which database the log files have been restored to Updating calls to CommandExecute to pass in DatabaseContext --- sp_DatabaseRestore.sql | 47 +++++++++++++++++++++++------------------- 1 file changed, 26 insertions(+), 21 deletions(-) diff --git a/sp_DatabaseRestore.sql b/sp_DatabaseRestore.sql index 589482a6f..0b69da849 100755 --- a/sp_DatabaseRestore.sql +++ b/sp_DatabaseRestore.sql @@ -756,7 +756,7 @@ BEGIN PRINT @sql; END; IF @Debug IN (0, 1) AND @Execute = 'Y' AND DATABASEPROPERTYEX(@RestoreDatabaseName,'STATUS') != 'RESTORING' - EXECUTE @sql = [dbo].[CommandExecute] @Command = @sql, @CommandType = 'ALTER DATABASE SINGLE_USER', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y'; + EXECUTE @sql = [dbo].[CommandExecute] @DatabaseContext=N'master', @Command = @sql, @CommandType = 'ALTER DATABASE SINGLE_USER', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y'; END IF @ExistingDBAction IN (2, 3) BEGIN @@ -775,7 +775,7 @@ BEGIN PRINT @sql; END; IF @Debug IN (0, 1) AND @Execute = 'Y' - EXECUTE @sql = [dbo].[CommandExecute] @Command = @sql, @CommandType = 'KILL CONNECTIONS', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y'; + EXECUTE @sql = [dbo].[CommandExecute] @DatabaseContext=N'master', @Command = @sql, @CommandType = 'KILL CONNECTIONS', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y'; END IF @ExistingDBAction = 3 BEGIN @@ -788,7 +788,7 @@ BEGIN PRINT @sql; END; IF @Debug IN (0, 1) AND @Execute = 'Y' - EXECUTE @sql = [dbo].[CommandExecute] @Command = @sql, @CommandType = 'DROP DATABASE', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y'; + EXECUTE @sql = [dbo].[CommandExecute] @DatabaseContext=N'master', @Command = @sql, @CommandType = 'DROP DATABASE', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y'; END IF @ExistingDBAction = 4 BEGIN @@ -801,7 +801,7 @@ BEGIN PRINT @sql; END; IF @Debug IN (0, 1) AND @Execute = 'Y' AND DATABASEPROPERTYEX(@RestoreDatabaseName,'STATUS') != 'RESTORING' - EXECUTE @sql = [dbo].[CommandExecute] @Command = @sql, @CommandType = 'OFFLINE DATABASE', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y'; + EXECUTE @sql = [dbo].[CommandExecute] @DatabaseContext=N'master', @Command = @sql, @CommandType = 'OFFLINE DATABASE', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y'; END; END ELSE @@ -855,7 +855,7 @@ BEGIN END; IF @Debug IN (0, 1) AND @Execute = 'Y' - EXECUTE @sql = [dbo].[CommandExecute] @Command = @sql, @CommandType = 'RESTORE DATABASE', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y'; + EXECUTE @sql = [dbo].[CommandExecute] @DatabaseContext=N'master', @Command = @sql, @CommandType = 'RESTORE DATABASE', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y'; -- We already loaded #Headers above @@ -1038,7 +1038,7 @@ BEGIN PRINT @sql; END; IF @Debug IN (0, 1) AND @Execute = 'Y' - EXECUTE @sql = [dbo].[CommandExecute] @Command = @sql, @CommandType = 'RESTORE DATABASE', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y'; + EXECUTE @sql = [dbo].[CommandExecute] @DatabaseContext=N'master', @Command = @sql, @CommandType = 'RESTORE DATABASE', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y'; --get the backup completed data so we can apply tlogs from that point forwards SET @sql = REPLACE(@HeadersSQL, N'{Path}', @CurrentBackupPathDiff + @LastDiffBackup); @@ -1186,16 +1186,24 @@ BEGIN END /*End folder sanity check*/ - +IF @Debug = 1 +BEGIN + SELECT * FROM @FileList WHERE BackupFile IS NOT NULL; +END + IF @SkipBackupsAlreadyInMsdb = 1 BEGIN + SELECT TOP 1 @LogLastNameInMsdbAS = bf.physical_device_name - FROM msdb.dbo.backupmediafamily bf - WHERE physical_device_name like @BackupPathLog + '%' - ORDER BY physical_device_name DESC + FROM msdb.dbo.backupmediafamily bf + INNER JOIN msdb.dbo.backupset bs ON bs.media_set_id = bf.media_set_id + INNER JOIN msdb.dbo.restorehistory rh ON rh.backup_set_id = bs.backup_set_id + WHERE physical_device_name like @BackupPathLog + '%' + AND rh.destination_database_name = @UnquotedRestoreDatabaseName + ORDER BY physical_device_name DESC IF @Debug = 1 - BEGIN + BEGIN SELECT 'Keeping LOG backups with name > : ' + @LogLastNameInMsdbAS END @@ -1204,10 +1212,7 @@ BEGIN WHERE fl.BackupPath + fl.BackupFile <= @LogLastNameInMsdbAS END - IF @Debug = 1 - BEGIN - SELECT * FROM @FileList WHERE BackupFile IS NOT NULL; - END + IF (@OnlyLogsAfter IS NOT NULL) BEGIN @@ -1359,7 +1364,7 @@ WHERE BackupFile IS NOT NULL; END; IF @Debug IN (0, 1) AND @Execute = 'Y' - EXECUTE @sql = [dbo].[CommandExecute] @Command = @sql, @CommandType = 'RESTORE LOG', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y'; + EXECUTE @sql = [dbo].[CommandExecute] @DatabaseContext=N'master', @Command = @sql, @CommandType = 'RESTORE LOG', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y'; END; SET @LogRestoreRanking += 1; @@ -1384,7 +1389,7 @@ IF @RunRecovery = 1 END; IF @Debug IN (0, 1) AND @Execute = 'Y' - EXECUTE @sql = [dbo].[CommandExecute] @Command = @sql, @CommandType = 'RECOVER DATABASE', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y'; + EXECUTE @sql = [dbo].[CommandExecute] @DatabaseContext=N'master', @Command = @sql, @CommandType = 'RECOVER DATABASE', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y'; END; -- Ensure simple recovery model @@ -1399,7 +1404,7 @@ IF @ForceSimpleRecovery = 1 END; IF @Debug IN (0, 1) AND @Execute = 'Y' - EXECUTE @sql = [dbo].[CommandExecute] @Command = @sql, @CommandType = 'SIMPLE LOGGING', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y'; + EXECUTE @sql = [dbo].[CommandExecute] @DatabaseContext=N'master', @Command = @sql, @CommandType = 'SIMPLE LOGGING', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y'; END; -- Run checkdb against this database @@ -1414,7 +1419,7 @@ IF @RunCheckDB = 1 END; IF @Debug IN (0, 1) AND @Execute = 'Y' - EXECUTE @sql = [dbo].[CommandExecute] @Command = @sql, @CommandType = 'DBCC CHECKDB', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y'; + EXECUTE @sql = [dbo].[CommandExecute] @DatabaseContext=N'master', @Command = @sql, @CommandType = 'DBCC CHECKDB', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y'; END; @@ -1433,7 +1438,7 @@ IF @DatabaseOwner IS NOT NULL END; IF @Debug IN (0, 1) AND @Execute = 'Y' - EXECUTE @sql = [dbo].[CommandExecute] @Command = @sql, @CommandType = 'ALTER AUTHORIZATION', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y'; + EXECUTE @sql = [dbo].[CommandExecute] @DatabaseContext=N'master',@Command = @sql, @CommandType = 'ALTER AUTHORIZATION', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y'; END ELSE BEGIN @@ -1458,7 +1463,7 @@ IF @TestRestore = 1 END; IF @Debug IN (0, 1) AND @Execute = 'Y' - EXECUTE @sql = [dbo].[CommandExecute] @Command = @sql, @CommandType = 'DROP DATABASE', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y'; + EXECUTE @sql = [dbo].[CommandExecute] @DatabaseContext=N'master',@Command = @sql, @CommandType = 'DROP DATABASE', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y'; END; From 956158f0fe72d916265070dfc56ad8399ff92d70 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Fri, 11 Dec 2020 03:51:19 -0800 Subject: [PATCH 050/662] #2714 2020-12 release Closes #2714. --- Install-All-Scripts.sql | 248 ++++++++++++++++-------- Install-Core-Blitz-No-Query-Store.sql | 122 +++++++----- Install-Core-Blitz-With-Query-Store.sql | 124 +++++++----- sp_AllNightLog.sql | 2 +- sp_AllNightLog_Setup.sql | 2 +- sp_Blitz.sql | 2 +- sp_BlitzBackups.sql | 2 +- sp_BlitzCache.sql | 2 +- sp_BlitzFirst.sql | 2 +- sp_BlitzInMemoryOLTP.sql | 2 +- sp_BlitzIndex.sql | 2 +- sp_BlitzLock.sql | 2 +- sp_BlitzQueryStore.sql | 2 +- sp_BlitzWho.sql | 2 +- sp_DatabaseRestore.sql | 2 +- sp_ineachdb.sql | 2 +- 16 files changed, 331 insertions(+), 189 deletions(-) diff --git a/Install-All-Scripts.sql b/Install-All-Scripts.sql index a40d2f82e..993648b65 100755 --- a/Install-All-Scripts.sql +++ b/Install-All-Scripts.sql @@ -30,7 +30,7 @@ SET NOCOUNT ON; BEGIN; -SELECT @Version = '3.999', @VersionDate = '20201114'; +SELECT @Version = '3.9999', @VersionDate = '20201211'; IF(@VersionCheckMode = 1) BEGIN @@ -1524,7 +1524,7 @@ SET NOCOUNT ON; BEGIN; -SELECT @Version = '3.999', @VersionDate = '20201114'; +SELECT @Version = '3.9999', @VersionDate = '20201211'; IF(@VersionCheckMode = 1) BEGIN @@ -2856,7 +2856,7 @@ AS SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '7.9999', @VersionDate = '20201114'; + SELECT @Version = '7.99999', @VersionDate = '20201211'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -12279,7 +12279,7 @@ AS SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '3.9999', @VersionDate = '20201114'; + SELECT @Version = '3.99999', @VersionDate = '20201211'; IF(@VersionCheckMode = 1) BEGIN @@ -14058,7 +14058,7 @@ BEGIN SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '7.9999', @VersionDate = '20201114'; +SELECT @Version = '7.99999', @VersionDate = '20201211'; IF(@VersionCheckMode = 1) @@ -20786,9 +20786,9 @@ ELSE AvgSpills MONEY, QueryPlanCost FLOAT, JoinKey AS ServerName + Cast(CheckDate AS NVARCHAR(50)), - CONSTRAINT [PK_' + REPLACE(REPLACE(@OutputTableName,'[',''),']','') + '] PRIMARY KEY CLUSTERED(ID ASC)); + CONSTRAINT [PK_' + REPLACE(REPLACE(@OutputTableName,'[',''),']','') + '] PRIMARY KEY CLUSTERED(ID ASC));'; - IF EXISTS(SELECT * FROM ' + SET @StringToExecute += N'IF EXISTS(SELECT * FROM ' +@OutputDatabaseName +N'.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' +@OutputSchemaName @@ -21238,7 +21238,7 @@ BEGIN SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '7.9999', @VersionDate = '20201114'; +SELECT @Version = '7.99999', @VersionDate = '20201211'; IF(@VersionCheckMode = 1) BEGIN @@ -21361,6 +21361,7 @@ END; IF UPPER(@OutputType) LIKE 'TOP 10%' SET @OutputType = 'Top10'; IF @OutputType = 'Top10' SET @SinceStartup = 1; +/* Logged Message - CheckID 38 */ IF @LogMessage IS NOT NULL BEGIN @@ -21702,7 +21703,7 @@ BEGIN INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_COMPROWSET_RWLOCK','Full Text Search',0); INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_IFTS_RWLOCK','Full Text Search',0); INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_IFTS_SCHEDULER_IDLE_WAIT','Idle',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_IFTSHC_MUTEX','Full Text Search',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_IFTSHC_MUTEX','Full Text Search',1); INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_IFTSISM_MUTEX','Full Text Search',1); INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_MASTER_MERGE','Full Text Search',0); INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_MASTER_MERGE_COORDINATOR','Full Text Search',0); @@ -22757,7 +22758,7 @@ BEGIN EXECUTE sp_executesql @StringToExecute; END; - /* Query Problems - Plan Cache Erased Recently */ + /* Query Problems - Plan Cache Erased Recently - CheckID 7 */ IF DATEADD(mi, -15, SYSDATETIME()) < (SELECT TOP 1 creation_time FROM sys.dm_exec_query_stats ORDER BY creation_time) BEGIN INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) @@ -22812,7 +22813,7 @@ BEGIN AND NOT (resource_type = N'DATABASE' AND request_mode = N'S' AND request_status = N'GRANT' AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE')); - /*Query Problems - Clients using implicit transactions */ + /*Query Problems - Clients using implicit transactions - CheckID 37 */ IF @Seconds > 0 AND ( @@VERSION NOT LIKE 'Microsoft SQL Server 2005%' AND @@VERSION NOT LIKE 'Microsoft SQL Server 2008%' @@ -23014,7 +23015,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, OUTER APPLY sys.dm_exec_query_plan(Grants.[plan_handle]) AS QueryPlan WHERE Grants.granted_memory_kb > ((@MemoryGrantThresholdPct/100.00)*(@MaxWorkspace*1024)); - /* Query Problems - Memory Leak in USERSTORE_TOKENPERM Cache */ + /* Query Problems - Memory Leak in USERSTORE_TOKENPERM Cache - CheckID 45 */ IF EXISTS (SELECT * FROM sys.all_columns WHERE object_id = OBJECT_ID('sys.dm_os_memory_clerks') AND name = 'pages_kb') BEGIN /* SQL 2012+ version */ @@ -23169,9 +23170,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, y.request_id, y.parallelism_skew; - /* - CheckID 42: Queries in dm_exec_query_profiles showing signs of poor cardinality estimates - */ + /* Queries in dm_exec_query_profiles showing signs of poor cardinality estimates - CheckID 42 */ INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, OpenTransactionCount, QueryHash, QueryPlan) SELECT 42 AS CheckID, @@ -23217,9 +23216,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, SET @StringToExecute = @StringToExecute + N'; - /* - CheckID 43: Queries in dm_exec_query_profiles showing signs of unbalanced parallelism - */ + /* Queries in dm_exec_query_profiles showing signs of unbalanced parallelism - CheckID 43 */ INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, OpenTransactionCount, QueryHash, QueryPlan) SELECT 43 AS CheckID, @@ -23271,7 +23268,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, END END - /* Server Performance - High CPU Utilization CheckID 24 */ + /* Server Performance - High CPU Utilization - CheckID 24 */ IF @Seconds < 30 BEGIN /* If we're waiting less than 30 seconds, run this check now rather than wait til the end. @@ -23292,6 +23289,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, ) AS y WHERE 100 - SystemIdle >= 50; + /* CPU Utilization - CheckID 23 */ IF SERVERPROPERTY('Edition') <> 'SQL Azure' WITH y AS @@ -23332,7 +23330,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, ORDER BY y.event_date DESC; - /* Highlight if non SQL processes are using >25% CPU */ + /* Highlight if non SQL processes are using >25% CPU - CheckID 28 */ INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) SELECT 28, 50, 'Server Performance', 'High CPU Utilization - Not SQL', CONVERT(NVARCHAR(100),100 - (y.SQLUsage + y.SystemIdle)) + N'% - Other Processes (not SQL Server) are using this much CPU. This may impact on the performance of your SQL Server instance', 100 - (y.SQLUsage + y.SystemIdle), 'http://www.BrentOzar.com/go/cpu' FROM ( @@ -23535,7 +23533,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, WHERE DATEDIFF(ss, pFirst.SampleTime, pNow.SampleTime) > 0; - /* If we're within 10 seconds of our projected finish time, do the plan cache analysis. */ + /* Query Stats - If we're within 10 seconds of our projected finish time, do the plan cache analysis. - CheckID 18 */ IF DATEDIFF(ss, @FinishSampleTime, SYSDATETIMEOFFSET()) > 10 AND @CheckProcedureCache = 1 BEGIN @@ -23676,7 +23674,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, FROM #QueryStats qs INNER JOIN qsTop ON qs.ID = qsTop.ID; - /* Query Stats - CheckID 17 - Most Resource-Intensive Queries */ + /* Query Stats - Most Resource-Intensive Queries - CheckID 17 */ INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, QueryText, QueryStatsNowID, QueryStatsFirstID, PlanHandle, QueryHash) SELECT 17, 210, 'Query Stats', 'Most Resource-Intensive Queries', 'http://www.BrentOzar.com/go/topqueries', 'Query stats during the sample:' + @LineFeed + @@ -24075,13 +24073,13 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, CROSS JOIN waits2; END; - /* Server Performance - High CPU Utilization CheckID 24 */ + /* If we're waiting 30+ seconds, run these checks at the end. + We get this data from the ring buffers, and it's only updated once per minute, so might + as well get it now - whereas if we're checking 30+ seconds, it might get updated by the + end of our sp_BlitzFirst session. */ IF @Seconds >= 30 BEGIN - /* If we're waiting 30+ seconds, run this check at the end. - We get this data from the ring buffers, and it's only updated once per minute, so might - as well get it now - whereas if we're checking 30+ seconds, it might get updated by the - end of our sp_BlitzFirst session. */ + /* Server Performance - High CPU Utilization CheckID 24 */ INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) SELECT 24, 50, 'Server Performance', 'High CPU Utilization', CAST(100 - SystemIdle AS NVARCHAR(20)) + N'%. Ring buffer details: ' + CAST(record AS NVARCHAR(4000)), 100 - SystemIdle, 'http://www.BrentOzar.com/go/cpu' FROM ( @@ -24096,6 +24094,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, ) AS y WHERE 100 - SystemIdle >= 50; + /* Server Performance - CPU Utilization CheckID 23 */ INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) SELECT 23, 250, 'Server Info', 'CPU Utilization', CAST(100 - SystemIdle AS NVARCHAR(20)) + N'%. Ring buffer details: ' + CAST(record AS NVARCHAR(4000)), 100 - SystemIdle, 'http://www.BrentOzar.com/go/cpu' FROM ( @@ -24168,7 +24167,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 'We hope you found this tool useful.' ); - /* Outdated sp_BlitzFirst - sp_BlitzFirst is Over 6 Months Old */ + /* Outdated sp_BlitzFirst - sp_BlitzFirst is Over 6 Months Old - CheckID 27 */ IF DATEDIFF(MM, @VersionDate, SYSDATETIMEOFFSET()) > 6 BEGIN INSERT INTO #BlitzFirstResults @@ -24257,8 +24256,9 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, END; - ELSE /* No sp_BlitzCache found, or it's outdated */ + ELSE BEGIN + /* No sp_BlitzCache found, or it's outdated - CheckID 36 */ INSERT INTO #BlitzFirstResults ( CheckID , Priority , @@ -25451,13 +25451,14 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, AND wd1.FileID = wd2.FileID ) SELECT - Pattern, [Sample Time], [Sample (seconds)], [File Name], [Drive], [# Reads/Writes],[MB Read/Written],[Avg Stall (ms)], [file physical name] + Pattern, [Sample Time], [Sample (seconds)], [File Name], [Drive], [# Reads/Writes],[MB Read/Written],[Avg Stall (ms)], [file physical name], [StallRank] FROM readstats - WHERE StallRank <=5 AND [MB Read/Written] > 0 + WHERE StallRank <=20 AND [MB Read/Written] > 0 UNION ALL - SELECT Pattern, [Sample Time], [Sample (seconds)], [File Name], [Drive], [# Reads/Writes],[MB Read/Written],[Avg Stall (ms)], [file physical name] + SELECT Pattern, [Sample Time], [Sample (seconds)], [File Name], [Drive], [# Reads/Writes],[MB Read/Written],[Avg Stall (ms)], [file physical name], [StallRank] FROM writestats - WHERE StallRank <=5 AND [MB Read/Written] > 0; + WHERE StallRank <=20 AND [MB Read/Written] > 0 + ORDER BY Pattern, StallRank; ------------------------- @@ -25580,7 +25581,7 @@ AS SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '7.9999', @VersionDate = '20201114'; +SELECT @Version = '7.99999', @VersionDate = '20201211'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -26363,7 +26364,7 @@ ELSE ELSE @DatabaseName END; END; -SET @NumDatabases = (SELECT COUNT(*) FROM #DatabaseList); +SET @NumDatabases = (SELECT COUNT(*) FROM #DatabaseList AS D LEFT OUTER JOIN #Ignore_Databases AS I ON D.DatabaseName = I.DatabaseName WHERE I.DatabaseName IS NULL); SET @msg = N'Number of databases to examine: ' + CAST(@NumDatabases AS NVARCHAR(50)); RAISERROR (@msg,0,1) WITH NOWAIT; @@ -27525,8 +27526,8 @@ BEGIN TRY IF @dsql IS NULL RAISERROR('@dsql is null',16,1); - INSERT #TemporalTables ( database_name, database_id, schema_name, table_name, history_table_name, - history_schema_name, start_column_name, end_column_name, period_name ) + INSERT #TemporalTables ( database_name, database_id, schema_name, table_name, history_schema_name, + history_table_name, start_column_name, end_column_name, period_name ) EXEC sp_executesql @dsql; @@ -28242,9 +28243,10 @@ BEGIN SELECT DISTINCT QUOTENAME(c.name) AS column_name, c.column_id FROM ' + QUOTENAME(@DatabaseName) + N'.sys.partitions p INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns c ON p.object_id = c.object_id + INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.index_columns ic on ic.column_id = c.column_id and ic.object_id = c.object_id AND ic.index_id = p.index_id INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.types t ON c.system_type_id = t.system_type_id AND c.user_type_id = t.user_type_id - WHERE p.object_id = OBJECT_ID(@TableName) - AND EXISTS (SELECT * FROM ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_segments seg WHERE p.partition_id = seg.partition_id AND seg.column_id = c.column_id) + WHERE p.object_id = @ObjectID + AND EXISTS (SELECT * FROM ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_segments seg WHERE p.partition_id = seg.partition_id AND seg.column_id = ic.index_column_id) AND p.data_compression IN (3,4) ) SELECT @ColumnList = @ColumnList + column_name + N'', '' @@ -28266,7 +28268,7 @@ BEGIN PRINT SUBSTRING(@dsql, 36000, 40000); END; - EXEC sp_executesql @dsql, N'@ObjectID INT, @TableName NVARCHAR(128), @ColumnList NVARCHAR(MAX) OUTPUT', @ObjectID, @TableName, @ColumnList OUTPUT; + EXEC sp_executesql @dsql, N'@ObjectID INT, @ColumnList NVARCHAR(MAX) OUTPUT', @ObjectID, @ColumnList OUTPUT; IF @Debug = 1 SELECT @ColumnList AS ColumnstoreColumnList; @@ -28284,8 +28286,9 @@ BEGIN FROM ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_row_groups rg INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns c ON rg.object_id = c.object_id INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partitions p ON rg.object_id = p.object_id AND rg.partition_number = p.partition_number - LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_segments seg ON p.partition_id = seg.partition_id AND c.column_id = seg.column_id AND rg.row_group_id = seg.segment_id - WHERE rg.object_id = OBJECT_ID(@TableName) + INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.index_columns ic on ic.column_id = c.column_id AND ic.object_id = c.object_id AND ic.index_id = p.index_id + LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_segments seg ON p.partition_id = seg.partition_id AND ic.index_column_id = seg.column_id AND rg.row_group_id = seg.segment_id + WHERE rg.object_id = @ObjectID ) AS x PIVOT (MAX(details) FOR column_name IN ( ' + @ColumnList + N')) AS pivot1 ORDER BY partition_number, row_group_id;'; @@ -28307,7 +28310,7 @@ BEGIN IF @dsql IS NULL RAISERROR('@dsql is null',16,1); ELSE - EXEC sp_executesql @dsql, N'@TableName NVARCHAR(128)', @TableName; + EXEC sp_executesql @dsql, N'@ObjectID INT', @ObjectID; END ELSE /* No columns were found for this object */ BEGIN @@ -31101,7 +31104,7 @@ BEGIN SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '2.9999', @VersionDate = '20201114'; +SELECT @Version = '2.99999', @VersionDate = '20201211'; IF(@VersionCheckMode = 1) @@ -31256,7 +31259,7 @@ You need to use an Azure storage account, and the path has to look like this: ht finding NVARCHAR(4000) ); - DECLARE @d VARCHAR(40), @StringToExecute NVARCHAR(4000),@StringToExecuteParams NVARCHAR(500),@r NVARCHAR(200),@OutputTableFindings NVARCHAR(100); + DECLARE @d VARCHAR(40), @StringToExecute NVARCHAR(4000),@StringToExecuteParams NVARCHAR(500),@r NVARCHAR(200),@OutputTableFindings NVARCHAR(100),@DeadlockCount INT; DECLARE @ServerName NVARCHAR(256) DECLARE @OutputDatabaseCheck BIT; SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); @@ -31378,17 +31381,30 @@ You need to use an Azure storage account, and the path has to look like this: ht WITH xml AS ( SELECT CONVERT(XML, event_data) AS deadlock_xml FROM sys.fn_xe_file_target_read_file(@EventSessionPath, NULL, NULL, NULL) ) - SELECT TOP ( @Top ) ISNULL(xml.deadlock_xml, '') AS deadlock_xml + SELECT ISNULL(xml.deadlock_xml, '') AS deadlock_xml INTO #deadlock_data FROM xml LEFT JOIN #t AS t ON 1 = 1 - WHERE xml.deadlock_xml.value('(/event/@name)[1]', 'VARCHAR(256)') = 'xml_deadlock_report' + CROSS APPLY xml.deadlock_xml.nodes('/event/@name') AS x(c) + WHERE 1 = 1 + AND x.c.exist('/event/@name[ .= "xml_deadlock_report"]') = 1 AND CONVERT(datetime, SWITCHOFFSET(CONVERT(datetimeoffset, xml.deadlock_xml.value('(/event/@timestamp)[1]', 'datetime') ), DATENAME(TzOffset, SYSDATETIMEOFFSET()))) > @StartDate AND CONVERT(datetime, SWITCHOFFSET(CONVERT(datetimeoffset, xml.deadlock_xml.value('(/event/@timestamp)[1]', 'datetime') ), DATENAME(TzOffset, SYSDATETIMEOFFSET()))) < @EndDate ORDER BY xml.deadlock_xml.value('(/event/@timestamp)[1]', 'datetime') DESC OPTION ( RECOMPILE ); + /*Optimization: if we got back more rows than @Top, remove them. This seems to be better than using @Top in the query above as that results in excessive memory grant*/ + SET @DeadlockCount = @@ROWCOUNT + IF( @Top < @DeadlockCount ) BEGIN + WITH T + AS ( + SELECT TOP ( @DeadlockCount - @Top) * + FROM #deadlock_data + ORDER BY #deadlock_data.deadlock_xml.value('(/event/@timestamp)[1]', 'datetime') ASC) + DELETE FROM T + END + /*Parse process and input buffer XML*/ SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); RAISERROR('Parse process and input buffer XML %s', 0, 1, @d) WITH NOWAIT; @@ -31418,7 +31434,7 @@ You need to use an Azure storage account, and the path has to look like this: ht FROM ( SELECT dd.deadlock_xml, CONVERT(DATETIME2(7), SWITCHOFFSET(CONVERT(datetimeoffset, dd.event_date ), DATENAME(TzOffset, SYSDATETIMEOFFSET()))) AS event_date, dd.victim_id, - dd.is_parallel, + CONVERT(tinyint, dd.is_parallel) + CONVERT(tinyint, dd.is_parallel_batch) AS is_parallel, dd.deadlock_graph, ca.dp.value('@id', 'NVARCHAR(256)') AS id, ca.dp.value('@currentdb', 'BIGINT') AS database_id, @@ -31441,6 +31457,7 @@ You need to use an Azure storage account, and the path has to look like this: ht d1.deadlock_xml.value('(event/@timestamp)[1]', 'DATETIME2') AS event_date, d1.deadlock_xml.value('(//deadlock/victim-list/victimProcess/@id)[1]', 'NVARCHAR(256)') AS victim_id, d1.deadlock_xml.exist('//deadlock/resource-list/exchangeEvent') AS is_parallel, + d1.deadlock_xml.exist('//deadlock/resource-list/SyncPoint') AS is_parallel_batch, d1.deadlock_xml.query('/event/data/value/deadlock') AS deadlock_graph FROM #deadlock_data AS d1 ) AS dd CROSS APPLY dd.deadlock_xml.nodes('//deadlock/process-list/process') AS ca(dp) @@ -31472,12 +31489,19 @@ You need to use an Azure storage account, and the path has to look like this: ht /*Grab the full resource list*/ SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); RAISERROR('Grab the full resource list %s', 0, 1, @d) WITH NOWAIT; + SELECT + CONVERT(DATETIME2(7), SWITCHOFFSET(CONVERT(datetimeoffset, dr.event_date), DATENAME(TzOffset, SYSDATETIMEOFFSET()))) AS event_date, + dr.victim_id, + dr.resource_xml + INTO #deadlock_resource + FROM + ( SELECT dd.deadlock_xml.value('(event/@timestamp)[1]', 'DATETIME2') AS event_date, dd.deadlock_xml.value('(//deadlock/victim-list/victimProcess/@id)[1]', 'NVARCHAR(256)') AS victim_id, ISNULL(ca.dp.query('.'), '') AS resource_xml - INTO #deadlock_resource FROM #deadlock_data AS dd CROSS APPLY dd.deadlock_xml.nodes('//deadlock/resource-list') AS ca(dp) + ) AS dr OPTION ( RECOMPILE ); @@ -31885,6 +31909,7 @@ You need to use an Azure storage account, and the path has to look like this: ht AND (dow.event_date < @EndDate OR @EndDate IS NULL) AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) AND dow.lock_type NOT IN (N'HEAP', N'RID') + AND dow.index_name is not null GROUP BY DB_NAME(dow.database_id), dow.index_name OPTION ( RECOMPILE ); @@ -32283,6 +32308,7 @@ You need to use an Azure storage account, and the path has to look like this: ht + ' parallel deadlocks.' FROM #deadlock_resource_parallel AS drp WHERE 1 = 1 + HAVING COUNT_BIG(DISTINCT drp.event_date) > 0 OPTION ( RECOMPILE ); /*Thank you goodnight*/ @@ -32807,7 +32833,7 @@ BEGIN /*First BEGIN*/ SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '3.9999', @VersionDate = '20201114'; +SELECT @Version = '3.99999', @VersionDate = '20201211'; IF(@VersionCheckMode = 1) BEGIN RETURN; @@ -38534,7 +38560,7 @@ BEGIN SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '7.9999', @VersionDate = '20201114'; + SELECT @Version = '7.99999', @VersionDate = '20201211'; IF(@VersionCheckMode = 1) BEGIN @@ -39770,7 +39796,7 @@ SET NOCOUNT ON; /*Versioning details*/ -SELECT @Version = '7.9999', @VersionDate = '20201114'; +SELECT @Version = '7.99999', @VersionDate = '20201211'; IF(@VersionCheckMode = 1) BEGIN @@ -39961,7 +39987,8 @@ DECLARE @cmd NVARCHAR(4000) = N'', --Holds xp_cmdshell command @LogLastNameInMsdbAS NVARCHAR(MAX) = N'', -- Holds last TRN file name already restored @FileListParamSQL NVARCHAR(4000) = N'', --Holds INSERT list for #FileListParameters @BackupParameters NVARCHAR(500) = N'', --Used to save BlockSize, MaxTransferSize and BufferCount - @RestoreDatabaseID SMALLINT; --Holds DB_ID of @RestoreDatabaseName + @RestoreDatabaseID SMALLINT, --Holds DB_ID of @RestoreDatabaseName + @UnquotedRestoreDatabaseName nvarchar(128); --Holds the unquoted @RestoreDatabaseName DECLARE @FileListSimple TABLE ( BackupFile NVARCHAR(255) NOT NULL, @@ -40196,6 +40223,8 @@ END SET @RestoreDatabaseID = DB_ID(@RestoreDatabaseName); SET @RestoreDatabaseName = QUOTENAME(@RestoreDatabaseName); +SET @UnquotedRestoreDatabaseName = PARSENAME(@RestoreDatabaseName,1); + --If xp_cmdshell is disabled, force use of xp_dirtree IF NOT EXISTS (SELECT * FROM sys.configurations WHERE name = 'xp_cmdshell' AND value_in_use = 1) SET @SimpleFolderEnumeration = 1; @@ -40483,10 +40512,8 @@ BEGIN IF @sql IS NULL PRINT '@sql is NULL for SINGLE_USER'; PRINT @sql; END; - IF @Debug IN (0, 1) AND @Execute = 'Y' - EXECUTE master.sys.sp_executesql @stmt = @sql; IF @Debug IN (0, 1) AND @Execute = 'Y' AND DATABASEPROPERTYEX(@RestoreDatabaseName,'STATUS') != 'RESTORING' - EXECUTE @sql = [dbo].[CommandExecute] @Command = @sql, @CommandType = 'ALTER DATABASE SINGLE_USER', @Mode = 1, @DatabaseName = @Database, @LogToTable = 'Y', @Execute = 'Y'; + EXECUTE @sql = [dbo].[CommandExecute] @DatabaseContext=N'master', @Command = @sql, @CommandType = 'ALTER DATABASE SINGLE_USER', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y'; END IF @ExistingDBAction IN (2, 3) BEGIN @@ -40505,7 +40532,7 @@ BEGIN PRINT @sql; END; IF @Debug IN (0, 1) AND @Execute = 'Y' - EXECUTE master.sys.sp_executesql @stmt = @sql; + EXECUTE @sql = [dbo].[CommandExecute] @DatabaseContext=N'master', @Command = @sql, @CommandType = 'KILL CONNECTIONS', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y'; END IF @ExistingDBAction = 3 BEGIN @@ -40518,7 +40545,7 @@ BEGIN PRINT @sql; END; IF @Debug IN (0, 1) AND @Execute = 'Y' - EXECUTE master.sys.sp_executesql @stmt = @sql; + EXECUTE @sql = [dbo].[CommandExecute] @DatabaseContext=N'master', @Command = @sql, @CommandType = 'DROP DATABASE', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y'; END IF @ExistingDBAction = 4 BEGIN @@ -40531,7 +40558,7 @@ BEGIN PRINT @sql; END; IF @Debug IN (0, 1) AND @Execute = 'Y' AND DATABASEPROPERTYEX(@RestoreDatabaseName,'STATUS') != 'RESTORING' - EXECUTE @sql = [dbo].[CommandExecute] @Command = @sql, @CommandType = 'OFFLINE DATABASE', @Mode = 1, @DatabaseName = @Database, @LogToTable = 'Y', @Execute = 'Y'; + EXECUTE @sql = [dbo].[CommandExecute] @DatabaseContext=N'master', @Command = @sql, @CommandType = 'OFFLINE DATABASE', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y'; END; END ELSE @@ -40585,7 +40612,7 @@ BEGIN END; IF @Debug IN (0, 1) AND @Execute = 'Y' - EXECUTE master.sys.sp_executesql @stmt = @sql; + EXECUTE @sql = [dbo].[CommandExecute] @DatabaseContext=N'master', @Command = @sql, @CommandType = 'RESTORE DATABASE', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y'; -- We already loaded #Headers above @@ -40768,7 +40795,7 @@ BEGIN PRINT @sql; END; IF @Debug IN (0, 1) AND @Execute = 'Y' - EXECUTE master.sys.sp_executesql @stmt = @sql; + EXECUTE @sql = [dbo].[CommandExecute] @DatabaseContext=N'master', @Command = @sql, @CommandType = 'RESTORE DATABASE', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y'; --get the backup completed data so we can apply tlogs from that point forwards SET @sql = REPLACE(@HeadersSQL, N'{Path}', @CurrentBackupPathDiff + @LastDiffBackup); @@ -40916,16 +40943,24 @@ BEGIN END /*End folder sanity check*/ - +IF @Debug = 1 +BEGIN + SELECT * FROM @FileList WHERE BackupFile IS NOT NULL; +END + IF @SkipBackupsAlreadyInMsdb = 1 BEGIN + SELECT TOP 1 @LogLastNameInMsdbAS = bf.physical_device_name - FROM msdb.dbo.backupmediafamily bf - WHERE physical_device_name like @BackupPathLog + '%' - ORDER BY physical_device_name DESC + FROM msdb.dbo.backupmediafamily bf + INNER JOIN msdb.dbo.backupset bs ON bs.media_set_id = bf.media_set_id + INNER JOIN msdb.dbo.restorehistory rh ON rh.backup_set_id = bs.backup_set_id + WHERE physical_device_name like @BackupPathLog + '%' + AND rh.destination_database_name = @UnquotedRestoreDatabaseName + ORDER BY physical_device_name DESC IF @Debug = 1 - BEGIN + BEGIN SELECT 'Keeping LOG backups with name > : ' + @LogLastNameInMsdbAS END @@ -40934,10 +40969,7 @@ BEGIN WHERE fl.BackupPath + fl.BackupFile <= @LogLastNameInMsdbAS END - IF @Debug = 1 - BEGIN - SELECT * FROM @FileList WHERE BackupFile IS NOT NULL; - END + IF (@OnlyLogsAfter IS NOT NULL) BEGIN @@ -41089,7 +41121,7 @@ WHERE BackupFile IS NOT NULL; END; IF @Debug IN (0, 1) AND @Execute = 'Y' - EXECUTE master.sys.sp_executesql @stmt = @sql; + EXECUTE @sql = [dbo].[CommandExecute] @DatabaseContext=N'master', @Command = @sql, @CommandType = 'RESTORE LOG', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y'; END; SET @LogRestoreRanking += 1; @@ -41114,7 +41146,7 @@ IF @RunRecovery = 1 END; IF @Debug IN (0, 1) AND @Execute = 'Y' - EXECUTE master.sys.sp_executesql @stmt = @sql; + EXECUTE @sql = [dbo].[CommandExecute] @DatabaseContext=N'master', @Command = @sql, @CommandType = 'RECOVER DATABASE', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y'; END; -- Ensure simple recovery model @@ -41129,7 +41161,7 @@ IF @ForceSimpleRecovery = 1 END; IF @Debug IN (0, 1) AND @Execute = 'Y' - EXECUTE master.sys.sp_executesql @stmt = @sql; + EXECUTE @sql = [dbo].[CommandExecute] @DatabaseContext=N'master', @Command = @sql, @CommandType = 'SIMPLE LOGGING', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y'; END; -- Run checkdb against this database @@ -41144,7 +41176,7 @@ IF @RunCheckDB = 1 END; IF @Debug IN (0, 1) AND @Execute = 'Y' - EXECUTE master.sys.sp_executesql @stmt = @sql; + EXECUTE @sql = [dbo].[CommandExecute] @DatabaseContext=N'master', @Command = @sql, @CommandType = 'DBCC CHECKDB', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y'; END; @@ -41163,7 +41195,7 @@ IF @DatabaseOwner IS NOT NULL END; IF @Debug IN (0, 1) AND @Execute = 'Y' - EXECUTE (@sql); + EXECUTE @sql = [dbo].[CommandExecute] @DatabaseContext=N'master',@Command = @sql, @CommandType = 'ALTER AUTHORIZATION', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y'; END ELSE BEGIN @@ -41188,7 +41220,7 @@ IF @TestRestore = 1 END; IF @Debug IN (0, 1) AND @Execute = 'Y' - EXECUTE master.sys.sp_executesql @stmt = @sql; + EXECUTE @sql = [dbo].[CommandExecute] @DatabaseContext=N'master',@Command = @sql, @CommandType = 'DROP DATABASE', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y'; END; @@ -41233,7 +41265,7 @@ AS BEGIN SET NOCOUNT ON; - SELECT @Version = '2.9999', @VersionDate = '20201114'; + SELECT @Version = '2.99999', @VersionDate = '20201211'; IF(@VersionCheckMode = 1) BEGIN @@ -41300,10 +41332,14 @@ BEGIN @thisdb sysname, @cr char(2) = CHAR(13) + CHAR(10), @SQLVersion AS tinyint = (@@microsoftversion / 0x1000000) & 0xff, -- Stores the SQL Server Version Number(8(2000),9(2005),10(2008 & 2008R2),11(2012),12(2014),13(2016),14(2017),15(2019) - @ServerName AS sysname = CONVERT(sysname, SERVERPROPERTY('ServerName')); -- Stores the SQL Server Instance name. + @ServerName AS sysname = CONVERT(sysname, SERVERPROPERTY('ServerName')), -- Stores the SQL Server Instance name. + @NoSpaces nvarchar(20) = N'%[^' + CHAR(9) + CHAR(32) + CHAR(10) + CHAR(13) + N']%'; --Pattern for PATINDEX + CREATE TABLE #ineachdb(id int, name nvarchar(512), is_distributor bit); + +/* -- first, let's limit to only DBs the caller is interested in IF @database_list > N'' -- comma-separated list of potentially valid/invalid/quoted/unquoted names @@ -41345,7 +41381,62 @@ BEGIN ON names.name = d.name OPTION (MAXRECURSION 0); END +*/ +/* +@database_list and @exclude_list are are processed at the same time +1)Read the list searching for a comma or [ +2)If we find a comma, save the name +3)If we find a [, we begin to accumulate the result until we reach closing ], (jumping over escaped ]]). +4)Finally, tabs, line breaks and spaces are removed from unquoted names +*/ +WITH C +AS (SELECT V.SrcList + , CAST('' AS nvarchar(MAX)) AS Name + , V.DBList + , 0 AS InBracket + , 0 AS Quoted + FROM (VALUES ('In', @database_list + ','), ('Out', @exclude_list + ',')) AS V (SrcList, DBList) + UNION ALL + SELECT C.SrcList + , IIF(V.Found = '[', '', SUBSTRING(C.DBList, 1, V.Place - 1))/*remove initial [*/ + , STUFF(C.DBList, 1, V.Place, '') + , IIF(V.Found = '[', 1, 0) + , 0 + FROM C + CROSS APPLY + ( VALUES (PATINDEX('%[,[]%', C.DBList), SUBSTRING(C.DBList, PATINDEX('%[,[]%', C.DBList), 1))) AS V (Place, Found) + WHERE C.DBList > '' + AND C.InBracket = 0 + UNION ALL + SELECT C.SrcList + , CONCAT(C.Name, SUBSTRING(C.DBList, 1, V.Place + W.DoubleBracket - 1)) /*Accumulates only one ] if escaped]] or none if end]*/ + , STUFF(C.DBList, 1, V.Place + W.DoubleBracket, '') + , W.DoubleBracket + , 1 + FROM C + CROSS APPLY (VALUES (CHARINDEX(']', C.DBList))) AS V (Place) + CROSS APPLY (VALUES (IIF(SUBSTRING(C.DBList, V.Place + 1, 1) = ']', 1, 0))) AS W (DoubleBracket) + WHERE C.DBList > '' + AND C.InBracket = 1) + , F +AS (SELECT C.SrcList + , IIF(C.Quoted = 0 + ,SUBSTRING(C.name, PATINDEX(@NoSpaces, name), DATALENGTH (name)/2 - PATINDEX(@NoSpaces, name) - PATINDEX(@NoSpaces, REVERSE(name))+2) + , C.Name) + AS name + FROM C + WHERE C.InBracket = 0 + AND C.Name > '') + SELECT d.database_id + , d.name + , d.is_distributor +FROM sys.databases AS d +WHERE ( EXISTS (SELECT NULL FROM F WHERE F.name = d.name AND F.SrcList = 'In') + OR @database_list IS NULL) + AND NOT EXISTS (SELECT NULL FROM F WHERE F.name = d.name AND F.SrcList = 'Out') +OPTION (MAXRECURSION 0); +; -- next, let's delete any that *don't* match various criteria passed in DELETE dbs FROM #ineachdb AS dbs WHERE (@system_only = 1 AND (id NOT IN (1,2,3,4) AND is_distributor <> 1)) @@ -41471,7 +41562,6 @@ BEGIN CLOSE dbs; DEALLOCATE dbs; END -GO IF (OBJECT_ID('dbo.SqlServerVersions') IS NULL) BEGIN diff --git a/Install-Core-Blitz-No-Query-Store.sql b/Install-Core-Blitz-No-Query-Store.sql index 71c92605e..b84e335b1 100755 --- a/Install-Core-Blitz-No-Query-Store.sql +++ b/Install-Core-Blitz-No-Query-Store.sql @@ -37,7 +37,7 @@ AS SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '7.9999', @VersionDate = '20201114'; + SELECT @Version = '7.99999', @VersionDate = '20201211'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -9460,7 +9460,7 @@ AS SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '3.9999', @VersionDate = '20201114'; + SELECT @Version = '3.99999', @VersionDate = '20201211'; IF(@VersionCheckMode = 1) BEGIN @@ -11239,7 +11239,7 @@ BEGIN SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '7.9999', @VersionDate = '20201114'; +SELECT @Version = '7.99999', @VersionDate = '20201211'; IF(@VersionCheckMode = 1) @@ -17967,9 +17967,9 @@ ELSE AvgSpills MONEY, QueryPlanCost FLOAT, JoinKey AS ServerName + Cast(CheckDate AS NVARCHAR(50)), - CONSTRAINT [PK_' + REPLACE(REPLACE(@OutputTableName,'[',''),']','') + '] PRIMARY KEY CLUSTERED(ID ASC)); + CONSTRAINT [PK_' + REPLACE(REPLACE(@OutputTableName,'[',''),']','') + '] PRIMARY KEY CLUSTERED(ID ASC));'; - IF EXISTS(SELECT * FROM ' + SET @StringToExecute += N'IF EXISTS(SELECT * FROM ' +@OutputDatabaseName +N'.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' +@OutputSchemaName @@ -18419,7 +18419,7 @@ BEGIN SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '7.9999', @VersionDate = '20201114'; +SELECT @Version = '7.99999', @VersionDate = '20201211'; IF(@VersionCheckMode = 1) BEGIN @@ -18542,6 +18542,7 @@ END; IF UPPER(@OutputType) LIKE 'TOP 10%' SET @OutputType = 'Top10'; IF @OutputType = 'Top10' SET @SinceStartup = 1; +/* Logged Message - CheckID 38 */ IF @LogMessage IS NOT NULL BEGIN @@ -18883,7 +18884,7 @@ BEGIN INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_COMPROWSET_RWLOCK','Full Text Search',0); INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_IFTS_RWLOCK','Full Text Search',0); INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_IFTS_SCHEDULER_IDLE_WAIT','Idle',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_IFTSHC_MUTEX','Full Text Search',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_IFTSHC_MUTEX','Full Text Search',1); INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_IFTSISM_MUTEX','Full Text Search',1); INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_MASTER_MERGE','Full Text Search',0); INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_MASTER_MERGE_COORDINATOR','Full Text Search',0); @@ -19938,7 +19939,7 @@ BEGIN EXECUTE sp_executesql @StringToExecute; END; - /* Query Problems - Plan Cache Erased Recently */ + /* Query Problems - Plan Cache Erased Recently - CheckID 7 */ IF DATEADD(mi, -15, SYSDATETIME()) < (SELECT TOP 1 creation_time FROM sys.dm_exec_query_stats ORDER BY creation_time) BEGIN INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) @@ -19993,7 +19994,7 @@ BEGIN AND NOT (resource_type = N'DATABASE' AND request_mode = N'S' AND request_status = N'GRANT' AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE')); - /*Query Problems - Clients using implicit transactions */ + /*Query Problems - Clients using implicit transactions - CheckID 37 */ IF @Seconds > 0 AND ( @@VERSION NOT LIKE 'Microsoft SQL Server 2005%' AND @@VERSION NOT LIKE 'Microsoft SQL Server 2008%' @@ -20195,7 +20196,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, OUTER APPLY sys.dm_exec_query_plan(Grants.[plan_handle]) AS QueryPlan WHERE Grants.granted_memory_kb > ((@MemoryGrantThresholdPct/100.00)*(@MaxWorkspace*1024)); - /* Query Problems - Memory Leak in USERSTORE_TOKENPERM Cache */ + /* Query Problems - Memory Leak in USERSTORE_TOKENPERM Cache - CheckID 45 */ IF EXISTS (SELECT * FROM sys.all_columns WHERE object_id = OBJECT_ID('sys.dm_os_memory_clerks') AND name = 'pages_kb') BEGIN /* SQL 2012+ version */ @@ -20350,9 +20351,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, y.request_id, y.parallelism_skew; - /* - CheckID 42: Queries in dm_exec_query_profiles showing signs of poor cardinality estimates - */ + /* Queries in dm_exec_query_profiles showing signs of poor cardinality estimates - CheckID 42 */ INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, OpenTransactionCount, QueryHash, QueryPlan) SELECT 42 AS CheckID, @@ -20398,9 +20397,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, SET @StringToExecute = @StringToExecute + N'; - /* - CheckID 43: Queries in dm_exec_query_profiles showing signs of unbalanced parallelism - */ + /* Queries in dm_exec_query_profiles showing signs of unbalanced parallelism - CheckID 43 */ INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, OpenTransactionCount, QueryHash, QueryPlan) SELECT 43 AS CheckID, @@ -20452,7 +20449,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, END END - /* Server Performance - High CPU Utilization CheckID 24 */ + /* Server Performance - High CPU Utilization - CheckID 24 */ IF @Seconds < 30 BEGIN /* If we're waiting less than 30 seconds, run this check now rather than wait til the end. @@ -20473,6 +20470,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, ) AS y WHERE 100 - SystemIdle >= 50; + /* CPU Utilization - CheckID 23 */ IF SERVERPROPERTY('Edition') <> 'SQL Azure' WITH y AS @@ -20513,7 +20511,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, ORDER BY y.event_date DESC; - /* Highlight if non SQL processes are using >25% CPU */ + /* Highlight if non SQL processes are using >25% CPU - CheckID 28 */ INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) SELECT 28, 50, 'Server Performance', 'High CPU Utilization - Not SQL', CONVERT(NVARCHAR(100),100 - (y.SQLUsage + y.SystemIdle)) + N'% - Other Processes (not SQL Server) are using this much CPU. This may impact on the performance of your SQL Server instance', 100 - (y.SQLUsage + y.SystemIdle), 'http://www.BrentOzar.com/go/cpu' FROM ( @@ -20716,7 +20714,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, WHERE DATEDIFF(ss, pFirst.SampleTime, pNow.SampleTime) > 0; - /* If we're within 10 seconds of our projected finish time, do the plan cache analysis. */ + /* Query Stats - If we're within 10 seconds of our projected finish time, do the plan cache analysis. - CheckID 18 */ IF DATEDIFF(ss, @FinishSampleTime, SYSDATETIMEOFFSET()) > 10 AND @CheckProcedureCache = 1 BEGIN @@ -20857,7 +20855,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, FROM #QueryStats qs INNER JOIN qsTop ON qs.ID = qsTop.ID; - /* Query Stats - CheckID 17 - Most Resource-Intensive Queries */ + /* Query Stats - Most Resource-Intensive Queries - CheckID 17 */ INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, QueryText, QueryStatsNowID, QueryStatsFirstID, PlanHandle, QueryHash) SELECT 17, 210, 'Query Stats', 'Most Resource-Intensive Queries', 'http://www.BrentOzar.com/go/topqueries', 'Query stats during the sample:' + @LineFeed + @@ -21256,13 +21254,13 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, CROSS JOIN waits2; END; - /* Server Performance - High CPU Utilization CheckID 24 */ + /* If we're waiting 30+ seconds, run these checks at the end. + We get this data from the ring buffers, and it's only updated once per minute, so might + as well get it now - whereas if we're checking 30+ seconds, it might get updated by the + end of our sp_BlitzFirst session. */ IF @Seconds >= 30 BEGIN - /* If we're waiting 30+ seconds, run this check at the end. - We get this data from the ring buffers, and it's only updated once per minute, so might - as well get it now - whereas if we're checking 30+ seconds, it might get updated by the - end of our sp_BlitzFirst session. */ + /* Server Performance - High CPU Utilization CheckID 24 */ INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) SELECT 24, 50, 'Server Performance', 'High CPU Utilization', CAST(100 - SystemIdle AS NVARCHAR(20)) + N'%. Ring buffer details: ' + CAST(record AS NVARCHAR(4000)), 100 - SystemIdle, 'http://www.BrentOzar.com/go/cpu' FROM ( @@ -21277,6 +21275,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, ) AS y WHERE 100 - SystemIdle >= 50; + /* Server Performance - CPU Utilization CheckID 23 */ INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) SELECT 23, 250, 'Server Info', 'CPU Utilization', CAST(100 - SystemIdle AS NVARCHAR(20)) + N'%. Ring buffer details: ' + CAST(record AS NVARCHAR(4000)), 100 - SystemIdle, 'http://www.BrentOzar.com/go/cpu' FROM ( @@ -21349,7 +21348,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 'We hope you found this tool useful.' ); - /* Outdated sp_BlitzFirst - sp_BlitzFirst is Over 6 Months Old */ + /* Outdated sp_BlitzFirst - sp_BlitzFirst is Over 6 Months Old - CheckID 27 */ IF DATEDIFF(MM, @VersionDate, SYSDATETIMEOFFSET()) > 6 BEGIN INSERT INTO #BlitzFirstResults @@ -21438,8 +21437,9 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, END; - ELSE /* No sp_BlitzCache found, or it's outdated */ + ELSE BEGIN + /* No sp_BlitzCache found, or it's outdated - CheckID 36 */ INSERT INTO #BlitzFirstResults ( CheckID , Priority , @@ -22632,13 +22632,14 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, AND wd1.FileID = wd2.FileID ) SELECT - Pattern, [Sample Time], [Sample (seconds)], [File Name], [Drive], [# Reads/Writes],[MB Read/Written],[Avg Stall (ms)], [file physical name] + Pattern, [Sample Time], [Sample (seconds)], [File Name], [Drive], [# Reads/Writes],[MB Read/Written],[Avg Stall (ms)], [file physical name], [StallRank] FROM readstats - WHERE StallRank <=5 AND [MB Read/Written] > 0 + WHERE StallRank <=20 AND [MB Read/Written] > 0 UNION ALL - SELECT Pattern, [Sample Time], [Sample (seconds)], [File Name], [Drive], [# Reads/Writes],[MB Read/Written],[Avg Stall (ms)], [file physical name] + SELECT Pattern, [Sample Time], [Sample (seconds)], [File Name], [Drive], [# Reads/Writes],[MB Read/Written],[Avg Stall (ms)], [file physical name], [StallRank] FROM writestats - WHERE StallRank <=5 AND [MB Read/Written] > 0; + WHERE StallRank <=20 AND [MB Read/Written] > 0 + ORDER BY Pattern, StallRank; ------------------------- @@ -22761,7 +22762,7 @@ AS SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '7.9999', @VersionDate = '20201114'; +SELECT @Version = '7.99999', @VersionDate = '20201211'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -23544,7 +23545,7 @@ ELSE ELSE @DatabaseName END; END; -SET @NumDatabases = (SELECT COUNT(*) FROM #DatabaseList); +SET @NumDatabases = (SELECT COUNT(*) FROM #DatabaseList AS D LEFT OUTER JOIN #Ignore_Databases AS I ON D.DatabaseName = I.DatabaseName WHERE I.DatabaseName IS NULL); SET @msg = N'Number of databases to examine: ' + CAST(@NumDatabases AS NVARCHAR(50)); RAISERROR (@msg,0,1) WITH NOWAIT; @@ -24706,8 +24707,8 @@ BEGIN TRY IF @dsql IS NULL RAISERROR('@dsql is null',16,1); - INSERT #TemporalTables ( database_name, database_id, schema_name, table_name, history_table_name, - history_schema_name, start_column_name, end_column_name, period_name ) + INSERT #TemporalTables ( database_name, database_id, schema_name, table_name, history_schema_name, + history_table_name, start_column_name, end_column_name, period_name ) EXEC sp_executesql @dsql; @@ -25423,9 +25424,10 @@ BEGIN SELECT DISTINCT QUOTENAME(c.name) AS column_name, c.column_id FROM ' + QUOTENAME(@DatabaseName) + N'.sys.partitions p INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns c ON p.object_id = c.object_id + INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.index_columns ic on ic.column_id = c.column_id and ic.object_id = c.object_id AND ic.index_id = p.index_id INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.types t ON c.system_type_id = t.system_type_id AND c.user_type_id = t.user_type_id - WHERE p.object_id = OBJECT_ID(@TableName) - AND EXISTS (SELECT * FROM ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_segments seg WHERE p.partition_id = seg.partition_id AND seg.column_id = c.column_id) + WHERE p.object_id = @ObjectID + AND EXISTS (SELECT * FROM ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_segments seg WHERE p.partition_id = seg.partition_id AND seg.column_id = ic.index_column_id) AND p.data_compression IN (3,4) ) SELECT @ColumnList = @ColumnList + column_name + N'', '' @@ -25447,7 +25449,7 @@ BEGIN PRINT SUBSTRING(@dsql, 36000, 40000); END; - EXEC sp_executesql @dsql, N'@ObjectID INT, @TableName NVARCHAR(128), @ColumnList NVARCHAR(MAX) OUTPUT', @ObjectID, @TableName, @ColumnList OUTPUT; + EXEC sp_executesql @dsql, N'@ObjectID INT, @ColumnList NVARCHAR(MAX) OUTPUT', @ObjectID, @ColumnList OUTPUT; IF @Debug = 1 SELECT @ColumnList AS ColumnstoreColumnList; @@ -25465,8 +25467,9 @@ BEGIN FROM ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_row_groups rg INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns c ON rg.object_id = c.object_id INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partitions p ON rg.object_id = p.object_id AND rg.partition_number = p.partition_number - LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_segments seg ON p.partition_id = seg.partition_id AND c.column_id = seg.column_id AND rg.row_group_id = seg.segment_id - WHERE rg.object_id = OBJECT_ID(@TableName) + INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.index_columns ic on ic.column_id = c.column_id AND ic.object_id = c.object_id AND ic.index_id = p.index_id + LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_segments seg ON p.partition_id = seg.partition_id AND ic.index_column_id = seg.column_id AND rg.row_group_id = seg.segment_id + WHERE rg.object_id = @ObjectID ) AS x PIVOT (MAX(details) FOR column_name IN ( ' + @ColumnList + N')) AS pivot1 ORDER BY partition_number, row_group_id;'; @@ -25488,7 +25491,7 @@ BEGIN IF @dsql IS NULL RAISERROR('@dsql is null',16,1); ELSE - EXEC sp_executesql @dsql, N'@TableName NVARCHAR(128)', @TableName; + EXEC sp_executesql @dsql, N'@ObjectID INT', @ObjectID; END ELSE /* No columns were found for this object */ BEGIN @@ -28282,7 +28285,7 @@ BEGIN SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '2.9999', @VersionDate = '20201114'; +SELECT @Version = '2.99999', @VersionDate = '20201211'; IF(@VersionCheckMode = 1) @@ -28437,7 +28440,7 @@ You need to use an Azure storage account, and the path has to look like this: ht finding NVARCHAR(4000) ); - DECLARE @d VARCHAR(40), @StringToExecute NVARCHAR(4000),@StringToExecuteParams NVARCHAR(500),@r NVARCHAR(200),@OutputTableFindings NVARCHAR(100); + DECLARE @d VARCHAR(40), @StringToExecute NVARCHAR(4000),@StringToExecuteParams NVARCHAR(500),@r NVARCHAR(200),@OutputTableFindings NVARCHAR(100),@DeadlockCount INT; DECLARE @ServerName NVARCHAR(256) DECLARE @OutputDatabaseCheck BIT; SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); @@ -28559,17 +28562,30 @@ You need to use an Azure storage account, and the path has to look like this: ht WITH xml AS ( SELECT CONVERT(XML, event_data) AS deadlock_xml FROM sys.fn_xe_file_target_read_file(@EventSessionPath, NULL, NULL, NULL) ) - SELECT TOP ( @Top ) ISNULL(xml.deadlock_xml, '') AS deadlock_xml + SELECT ISNULL(xml.deadlock_xml, '') AS deadlock_xml INTO #deadlock_data FROM xml LEFT JOIN #t AS t ON 1 = 1 - WHERE xml.deadlock_xml.value('(/event/@name)[1]', 'VARCHAR(256)') = 'xml_deadlock_report' + CROSS APPLY xml.deadlock_xml.nodes('/event/@name') AS x(c) + WHERE 1 = 1 + AND x.c.exist('/event/@name[ .= "xml_deadlock_report"]') = 1 AND CONVERT(datetime, SWITCHOFFSET(CONVERT(datetimeoffset, xml.deadlock_xml.value('(/event/@timestamp)[1]', 'datetime') ), DATENAME(TzOffset, SYSDATETIMEOFFSET()))) > @StartDate AND CONVERT(datetime, SWITCHOFFSET(CONVERT(datetimeoffset, xml.deadlock_xml.value('(/event/@timestamp)[1]', 'datetime') ), DATENAME(TzOffset, SYSDATETIMEOFFSET()))) < @EndDate ORDER BY xml.deadlock_xml.value('(/event/@timestamp)[1]', 'datetime') DESC OPTION ( RECOMPILE ); + /*Optimization: if we got back more rows than @Top, remove them. This seems to be better than using @Top in the query above as that results in excessive memory grant*/ + SET @DeadlockCount = @@ROWCOUNT + IF( @Top < @DeadlockCount ) BEGIN + WITH T + AS ( + SELECT TOP ( @DeadlockCount - @Top) * + FROM #deadlock_data + ORDER BY #deadlock_data.deadlock_xml.value('(/event/@timestamp)[1]', 'datetime') ASC) + DELETE FROM T + END + /*Parse process and input buffer XML*/ SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); RAISERROR('Parse process and input buffer XML %s', 0, 1, @d) WITH NOWAIT; @@ -28599,7 +28615,7 @@ You need to use an Azure storage account, and the path has to look like this: ht FROM ( SELECT dd.deadlock_xml, CONVERT(DATETIME2(7), SWITCHOFFSET(CONVERT(datetimeoffset, dd.event_date ), DATENAME(TzOffset, SYSDATETIMEOFFSET()))) AS event_date, dd.victim_id, - dd.is_parallel, + CONVERT(tinyint, dd.is_parallel) + CONVERT(tinyint, dd.is_parallel_batch) AS is_parallel, dd.deadlock_graph, ca.dp.value('@id', 'NVARCHAR(256)') AS id, ca.dp.value('@currentdb', 'BIGINT') AS database_id, @@ -28622,6 +28638,7 @@ You need to use an Azure storage account, and the path has to look like this: ht d1.deadlock_xml.value('(event/@timestamp)[1]', 'DATETIME2') AS event_date, d1.deadlock_xml.value('(//deadlock/victim-list/victimProcess/@id)[1]', 'NVARCHAR(256)') AS victim_id, d1.deadlock_xml.exist('//deadlock/resource-list/exchangeEvent') AS is_parallel, + d1.deadlock_xml.exist('//deadlock/resource-list/SyncPoint') AS is_parallel_batch, d1.deadlock_xml.query('/event/data/value/deadlock') AS deadlock_graph FROM #deadlock_data AS d1 ) AS dd CROSS APPLY dd.deadlock_xml.nodes('//deadlock/process-list/process') AS ca(dp) @@ -28653,12 +28670,19 @@ You need to use an Azure storage account, and the path has to look like this: ht /*Grab the full resource list*/ SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); RAISERROR('Grab the full resource list %s', 0, 1, @d) WITH NOWAIT; + SELECT + CONVERT(DATETIME2(7), SWITCHOFFSET(CONVERT(datetimeoffset, dr.event_date), DATENAME(TzOffset, SYSDATETIMEOFFSET()))) AS event_date, + dr.victim_id, + dr.resource_xml + INTO #deadlock_resource + FROM + ( SELECT dd.deadlock_xml.value('(event/@timestamp)[1]', 'DATETIME2') AS event_date, dd.deadlock_xml.value('(//deadlock/victim-list/victimProcess/@id)[1]', 'NVARCHAR(256)') AS victim_id, ISNULL(ca.dp.query('.'), '') AS resource_xml - INTO #deadlock_resource FROM #deadlock_data AS dd CROSS APPLY dd.deadlock_xml.nodes('//deadlock/resource-list') AS ca(dp) + ) AS dr OPTION ( RECOMPILE ); @@ -29066,6 +29090,7 @@ You need to use an Azure storage account, and the path has to look like this: ht AND (dow.event_date < @EndDate OR @EndDate IS NULL) AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) AND dow.lock_type NOT IN (N'HEAP', N'RID') + AND dow.index_name is not null GROUP BY DB_NAME(dow.database_id), dow.index_name OPTION ( RECOMPILE ); @@ -29464,6 +29489,7 @@ You need to use an Azure storage account, and the path has to look like this: ht + ' parallel deadlocks.' FROM #deadlock_resource_parallel AS drp WHERE 1 = 1 + HAVING COUNT_BIG(DISTINCT drp.event_date) > 0 OPTION ( RECOMPILE ); /*Thank you goodnight*/ @@ -29961,7 +29987,7 @@ BEGIN SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '7.9999', @VersionDate = '20201114'; + SELECT @Version = '7.99999', @VersionDate = '20201211'; IF(@VersionCheckMode = 1) BEGIN diff --git a/Install-Core-Blitz-With-Query-Store.sql b/Install-Core-Blitz-With-Query-Store.sql index 800acbba4..5fc9700bf 100755 --- a/Install-Core-Blitz-With-Query-Store.sql +++ b/Install-Core-Blitz-With-Query-Store.sql @@ -37,7 +37,7 @@ AS SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '7.9999', @VersionDate = '20201114'; + SELECT @Version = '7.99999', @VersionDate = '20201211'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -9460,7 +9460,7 @@ AS SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '3.9999', @VersionDate = '20201114'; + SELECT @Version = '3.99999', @VersionDate = '20201211'; IF(@VersionCheckMode = 1) BEGIN @@ -11239,7 +11239,7 @@ BEGIN SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '7.9999', @VersionDate = '20201114'; +SELECT @Version = '7.99999', @VersionDate = '20201211'; IF(@VersionCheckMode = 1) @@ -17967,9 +17967,9 @@ ELSE AvgSpills MONEY, QueryPlanCost FLOAT, JoinKey AS ServerName + Cast(CheckDate AS NVARCHAR(50)), - CONSTRAINT [PK_' + REPLACE(REPLACE(@OutputTableName,'[',''),']','') + '] PRIMARY KEY CLUSTERED(ID ASC)); + CONSTRAINT [PK_' + REPLACE(REPLACE(@OutputTableName,'[',''),']','') + '] PRIMARY KEY CLUSTERED(ID ASC));'; - IF EXISTS(SELECT * FROM ' + SET @StringToExecute += N'IF EXISTS(SELECT * FROM ' +@OutputDatabaseName +N'.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' +@OutputSchemaName @@ -18419,7 +18419,7 @@ BEGIN SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '7.9999', @VersionDate = '20201114'; +SELECT @Version = '7.99999', @VersionDate = '20201211'; IF(@VersionCheckMode = 1) BEGIN @@ -18542,6 +18542,7 @@ END; IF UPPER(@OutputType) LIKE 'TOP 10%' SET @OutputType = 'Top10'; IF @OutputType = 'Top10' SET @SinceStartup = 1; +/* Logged Message - CheckID 38 */ IF @LogMessage IS NOT NULL BEGIN @@ -18883,7 +18884,7 @@ BEGIN INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_COMPROWSET_RWLOCK','Full Text Search',0); INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_IFTS_RWLOCK','Full Text Search',0); INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_IFTS_SCHEDULER_IDLE_WAIT','Idle',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_IFTSHC_MUTEX','Full Text Search',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_IFTSHC_MUTEX','Full Text Search',1); INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_IFTSISM_MUTEX','Full Text Search',1); INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_MASTER_MERGE','Full Text Search',0); INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_MASTER_MERGE_COORDINATOR','Full Text Search',0); @@ -19938,7 +19939,7 @@ BEGIN EXECUTE sp_executesql @StringToExecute; END; - /* Query Problems - Plan Cache Erased Recently */ + /* Query Problems - Plan Cache Erased Recently - CheckID 7 */ IF DATEADD(mi, -15, SYSDATETIME()) < (SELECT TOP 1 creation_time FROM sys.dm_exec_query_stats ORDER BY creation_time) BEGIN INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) @@ -19993,7 +19994,7 @@ BEGIN AND NOT (resource_type = N'DATABASE' AND request_mode = N'S' AND request_status = N'GRANT' AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE')); - /*Query Problems - Clients using implicit transactions */ + /*Query Problems - Clients using implicit transactions - CheckID 37 */ IF @Seconds > 0 AND ( @@VERSION NOT LIKE 'Microsoft SQL Server 2005%' AND @@VERSION NOT LIKE 'Microsoft SQL Server 2008%' @@ -20195,7 +20196,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, OUTER APPLY sys.dm_exec_query_plan(Grants.[plan_handle]) AS QueryPlan WHERE Grants.granted_memory_kb > ((@MemoryGrantThresholdPct/100.00)*(@MaxWorkspace*1024)); - /* Query Problems - Memory Leak in USERSTORE_TOKENPERM Cache */ + /* Query Problems - Memory Leak in USERSTORE_TOKENPERM Cache - CheckID 45 */ IF EXISTS (SELECT * FROM sys.all_columns WHERE object_id = OBJECT_ID('sys.dm_os_memory_clerks') AND name = 'pages_kb') BEGIN /* SQL 2012+ version */ @@ -20350,9 +20351,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, y.request_id, y.parallelism_skew; - /* - CheckID 42: Queries in dm_exec_query_profiles showing signs of poor cardinality estimates - */ + /* Queries in dm_exec_query_profiles showing signs of poor cardinality estimates - CheckID 42 */ INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, OpenTransactionCount, QueryHash, QueryPlan) SELECT 42 AS CheckID, @@ -20398,9 +20397,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, SET @StringToExecute = @StringToExecute + N'; - /* - CheckID 43: Queries in dm_exec_query_profiles showing signs of unbalanced parallelism - */ + /* Queries in dm_exec_query_profiles showing signs of unbalanced parallelism - CheckID 43 */ INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, OpenTransactionCount, QueryHash, QueryPlan) SELECT 43 AS CheckID, @@ -20452,7 +20449,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, END END - /* Server Performance - High CPU Utilization CheckID 24 */ + /* Server Performance - High CPU Utilization - CheckID 24 */ IF @Seconds < 30 BEGIN /* If we're waiting less than 30 seconds, run this check now rather than wait til the end. @@ -20473,6 +20470,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, ) AS y WHERE 100 - SystemIdle >= 50; + /* CPU Utilization - CheckID 23 */ IF SERVERPROPERTY('Edition') <> 'SQL Azure' WITH y AS @@ -20513,7 +20511,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, ORDER BY y.event_date DESC; - /* Highlight if non SQL processes are using >25% CPU */ + /* Highlight if non SQL processes are using >25% CPU - CheckID 28 */ INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) SELECT 28, 50, 'Server Performance', 'High CPU Utilization - Not SQL', CONVERT(NVARCHAR(100),100 - (y.SQLUsage + y.SystemIdle)) + N'% - Other Processes (not SQL Server) are using this much CPU. This may impact on the performance of your SQL Server instance', 100 - (y.SQLUsage + y.SystemIdle), 'http://www.BrentOzar.com/go/cpu' FROM ( @@ -20716,7 +20714,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, WHERE DATEDIFF(ss, pFirst.SampleTime, pNow.SampleTime) > 0; - /* If we're within 10 seconds of our projected finish time, do the plan cache analysis. */ + /* Query Stats - If we're within 10 seconds of our projected finish time, do the plan cache analysis. - CheckID 18 */ IF DATEDIFF(ss, @FinishSampleTime, SYSDATETIMEOFFSET()) > 10 AND @CheckProcedureCache = 1 BEGIN @@ -20857,7 +20855,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, FROM #QueryStats qs INNER JOIN qsTop ON qs.ID = qsTop.ID; - /* Query Stats - CheckID 17 - Most Resource-Intensive Queries */ + /* Query Stats - Most Resource-Intensive Queries - CheckID 17 */ INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, QueryText, QueryStatsNowID, QueryStatsFirstID, PlanHandle, QueryHash) SELECT 17, 210, 'Query Stats', 'Most Resource-Intensive Queries', 'http://www.BrentOzar.com/go/topqueries', 'Query stats during the sample:' + @LineFeed + @@ -21256,13 +21254,13 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, CROSS JOIN waits2; END; - /* Server Performance - High CPU Utilization CheckID 24 */ + /* If we're waiting 30+ seconds, run these checks at the end. + We get this data from the ring buffers, and it's only updated once per minute, so might + as well get it now - whereas if we're checking 30+ seconds, it might get updated by the + end of our sp_BlitzFirst session. */ IF @Seconds >= 30 BEGIN - /* If we're waiting 30+ seconds, run this check at the end. - We get this data from the ring buffers, and it's only updated once per minute, so might - as well get it now - whereas if we're checking 30+ seconds, it might get updated by the - end of our sp_BlitzFirst session. */ + /* Server Performance - High CPU Utilization CheckID 24 */ INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) SELECT 24, 50, 'Server Performance', 'High CPU Utilization', CAST(100 - SystemIdle AS NVARCHAR(20)) + N'%. Ring buffer details: ' + CAST(record AS NVARCHAR(4000)), 100 - SystemIdle, 'http://www.BrentOzar.com/go/cpu' FROM ( @@ -21277,6 +21275,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, ) AS y WHERE 100 - SystemIdle >= 50; + /* Server Performance - CPU Utilization CheckID 23 */ INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) SELECT 23, 250, 'Server Info', 'CPU Utilization', CAST(100 - SystemIdle AS NVARCHAR(20)) + N'%. Ring buffer details: ' + CAST(record AS NVARCHAR(4000)), 100 - SystemIdle, 'http://www.BrentOzar.com/go/cpu' FROM ( @@ -21349,7 +21348,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 'We hope you found this tool useful.' ); - /* Outdated sp_BlitzFirst - sp_BlitzFirst is Over 6 Months Old */ + /* Outdated sp_BlitzFirst - sp_BlitzFirst is Over 6 Months Old - CheckID 27 */ IF DATEDIFF(MM, @VersionDate, SYSDATETIMEOFFSET()) > 6 BEGIN INSERT INTO #BlitzFirstResults @@ -21438,8 +21437,9 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, END; - ELSE /* No sp_BlitzCache found, or it's outdated */ + ELSE BEGIN + /* No sp_BlitzCache found, or it's outdated - CheckID 36 */ INSERT INTO #BlitzFirstResults ( CheckID , Priority , @@ -22632,13 +22632,14 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, AND wd1.FileID = wd2.FileID ) SELECT - Pattern, [Sample Time], [Sample (seconds)], [File Name], [Drive], [# Reads/Writes],[MB Read/Written],[Avg Stall (ms)], [file physical name] + Pattern, [Sample Time], [Sample (seconds)], [File Name], [Drive], [# Reads/Writes],[MB Read/Written],[Avg Stall (ms)], [file physical name], [StallRank] FROM readstats - WHERE StallRank <=5 AND [MB Read/Written] > 0 + WHERE StallRank <=20 AND [MB Read/Written] > 0 UNION ALL - SELECT Pattern, [Sample Time], [Sample (seconds)], [File Name], [Drive], [# Reads/Writes],[MB Read/Written],[Avg Stall (ms)], [file physical name] + SELECT Pattern, [Sample Time], [Sample (seconds)], [File Name], [Drive], [# Reads/Writes],[MB Read/Written],[Avg Stall (ms)], [file physical name], [StallRank] FROM writestats - WHERE StallRank <=5 AND [MB Read/Written] > 0; + WHERE StallRank <=20 AND [MB Read/Written] > 0 + ORDER BY Pattern, StallRank; ------------------------- @@ -22761,7 +22762,7 @@ AS SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '7.9999', @VersionDate = '20201114'; +SELECT @Version = '7.99999', @VersionDate = '20201211'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -23544,7 +23545,7 @@ ELSE ELSE @DatabaseName END; END; -SET @NumDatabases = (SELECT COUNT(*) FROM #DatabaseList); +SET @NumDatabases = (SELECT COUNT(*) FROM #DatabaseList AS D LEFT OUTER JOIN #Ignore_Databases AS I ON D.DatabaseName = I.DatabaseName WHERE I.DatabaseName IS NULL); SET @msg = N'Number of databases to examine: ' + CAST(@NumDatabases AS NVARCHAR(50)); RAISERROR (@msg,0,1) WITH NOWAIT; @@ -24706,8 +24707,8 @@ BEGIN TRY IF @dsql IS NULL RAISERROR('@dsql is null',16,1); - INSERT #TemporalTables ( database_name, database_id, schema_name, table_name, history_table_name, - history_schema_name, start_column_name, end_column_name, period_name ) + INSERT #TemporalTables ( database_name, database_id, schema_name, table_name, history_schema_name, + history_table_name, start_column_name, end_column_name, period_name ) EXEC sp_executesql @dsql; @@ -25423,9 +25424,10 @@ BEGIN SELECT DISTINCT QUOTENAME(c.name) AS column_name, c.column_id FROM ' + QUOTENAME(@DatabaseName) + N'.sys.partitions p INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns c ON p.object_id = c.object_id + INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.index_columns ic on ic.column_id = c.column_id and ic.object_id = c.object_id AND ic.index_id = p.index_id INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.types t ON c.system_type_id = t.system_type_id AND c.user_type_id = t.user_type_id - WHERE p.object_id = OBJECT_ID(@TableName) - AND EXISTS (SELECT * FROM ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_segments seg WHERE p.partition_id = seg.partition_id AND seg.column_id = c.column_id) + WHERE p.object_id = @ObjectID + AND EXISTS (SELECT * FROM ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_segments seg WHERE p.partition_id = seg.partition_id AND seg.column_id = ic.index_column_id) AND p.data_compression IN (3,4) ) SELECT @ColumnList = @ColumnList + column_name + N'', '' @@ -25447,7 +25449,7 @@ BEGIN PRINT SUBSTRING(@dsql, 36000, 40000); END; - EXEC sp_executesql @dsql, N'@ObjectID INT, @TableName NVARCHAR(128), @ColumnList NVARCHAR(MAX) OUTPUT', @ObjectID, @TableName, @ColumnList OUTPUT; + EXEC sp_executesql @dsql, N'@ObjectID INT, @ColumnList NVARCHAR(MAX) OUTPUT', @ObjectID, @ColumnList OUTPUT; IF @Debug = 1 SELECT @ColumnList AS ColumnstoreColumnList; @@ -25465,8 +25467,9 @@ BEGIN FROM ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_row_groups rg INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns c ON rg.object_id = c.object_id INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partitions p ON rg.object_id = p.object_id AND rg.partition_number = p.partition_number - LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_segments seg ON p.partition_id = seg.partition_id AND c.column_id = seg.column_id AND rg.row_group_id = seg.segment_id - WHERE rg.object_id = OBJECT_ID(@TableName) + INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.index_columns ic on ic.column_id = c.column_id AND ic.object_id = c.object_id AND ic.index_id = p.index_id + LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_segments seg ON p.partition_id = seg.partition_id AND ic.index_column_id = seg.column_id AND rg.row_group_id = seg.segment_id + WHERE rg.object_id = @ObjectID ) AS x PIVOT (MAX(details) FOR column_name IN ( ' + @ColumnList + N')) AS pivot1 ORDER BY partition_number, row_group_id;'; @@ -25488,7 +25491,7 @@ BEGIN IF @dsql IS NULL RAISERROR('@dsql is null',16,1); ELSE - EXEC sp_executesql @dsql, N'@TableName NVARCHAR(128)', @TableName; + EXEC sp_executesql @dsql, N'@ObjectID INT', @ObjectID; END ELSE /* No columns were found for this object */ BEGIN @@ -28282,7 +28285,7 @@ BEGIN SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '2.9999', @VersionDate = '20201114'; +SELECT @Version = '2.99999', @VersionDate = '20201211'; IF(@VersionCheckMode = 1) @@ -28437,7 +28440,7 @@ You need to use an Azure storage account, and the path has to look like this: ht finding NVARCHAR(4000) ); - DECLARE @d VARCHAR(40), @StringToExecute NVARCHAR(4000),@StringToExecuteParams NVARCHAR(500),@r NVARCHAR(200),@OutputTableFindings NVARCHAR(100); + DECLARE @d VARCHAR(40), @StringToExecute NVARCHAR(4000),@StringToExecuteParams NVARCHAR(500),@r NVARCHAR(200),@OutputTableFindings NVARCHAR(100),@DeadlockCount INT; DECLARE @ServerName NVARCHAR(256) DECLARE @OutputDatabaseCheck BIT; SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); @@ -28559,17 +28562,30 @@ You need to use an Azure storage account, and the path has to look like this: ht WITH xml AS ( SELECT CONVERT(XML, event_data) AS deadlock_xml FROM sys.fn_xe_file_target_read_file(@EventSessionPath, NULL, NULL, NULL) ) - SELECT TOP ( @Top ) ISNULL(xml.deadlock_xml, '') AS deadlock_xml + SELECT ISNULL(xml.deadlock_xml, '') AS deadlock_xml INTO #deadlock_data FROM xml LEFT JOIN #t AS t ON 1 = 1 - WHERE xml.deadlock_xml.value('(/event/@name)[1]', 'VARCHAR(256)') = 'xml_deadlock_report' + CROSS APPLY xml.deadlock_xml.nodes('/event/@name') AS x(c) + WHERE 1 = 1 + AND x.c.exist('/event/@name[ .= "xml_deadlock_report"]') = 1 AND CONVERT(datetime, SWITCHOFFSET(CONVERT(datetimeoffset, xml.deadlock_xml.value('(/event/@timestamp)[1]', 'datetime') ), DATENAME(TzOffset, SYSDATETIMEOFFSET()))) > @StartDate AND CONVERT(datetime, SWITCHOFFSET(CONVERT(datetimeoffset, xml.deadlock_xml.value('(/event/@timestamp)[1]', 'datetime') ), DATENAME(TzOffset, SYSDATETIMEOFFSET()))) < @EndDate ORDER BY xml.deadlock_xml.value('(/event/@timestamp)[1]', 'datetime') DESC OPTION ( RECOMPILE ); + /*Optimization: if we got back more rows than @Top, remove them. This seems to be better than using @Top in the query above as that results in excessive memory grant*/ + SET @DeadlockCount = @@ROWCOUNT + IF( @Top < @DeadlockCount ) BEGIN + WITH T + AS ( + SELECT TOP ( @DeadlockCount - @Top) * + FROM #deadlock_data + ORDER BY #deadlock_data.deadlock_xml.value('(/event/@timestamp)[1]', 'datetime') ASC) + DELETE FROM T + END + /*Parse process and input buffer XML*/ SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); RAISERROR('Parse process and input buffer XML %s', 0, 1, @d) WITH NOWAIT; @@ -28599,7 +28615,7 @@ You need to use an Azure storage account, and the path has to look like this: ht FROM ( SELECT dd.deadlock_xml, CONVERT(DATETIME2(7), SWITCHOFFSET(CONVERT(datetimeoffset, dd.event_date ), DATENAME(TzOffset, SYSDATETIMEOFFSET()))) AS event_date, dd.victim_id, - dd.is_parallel, + CONVERT(tinyint, dd.is_parallel) + CONVERT(tinyint, dd.is_parallel_batch) AS is_parallel, dd.deadlock_graph, ca.dp.value('@id', 'NVARCHAR(256)') AS id, ca.dp.value('@currentdb', 'BIGINT') AS database_id, @@ -28622,6 +28638,7 @@ You need to use an Azure storage account, and the path has to look like this: ht d1.deadlock_xml.value('(event/@timestamp)[1]', 'DATETIME2') AS event_date, d1.deadlock_xml.value('(//deadlock/victim-list/victimProcess/@id)[1]', 'NVARCHAR(256)') AS victim_id, d1.deadlock_xml.exist('//deadlock/resource-list/exchangeEvent') AS is_parallel, + d1.deadlock_xml.exist('//deadlock/resource-list/SyncPoint') AS is_parallel_batch, d1.deadlock_xml.query('/event/data/value/deadlock') AS deadlock_graph FROM #deadlock_data AS d1 ) AS dd CROSS APPLY dd.deadlock_xml.nodes('//deadlock/process-list/process') AS ca(dp) @@ -28653,12 +28670,19 @@ You need to use an Azure storage account, and the path has to look like this: ht /*Grab the full resource list*/ SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); RAISERROR('Grab the full resource list %s', 0, 1, @d) WITH NOWAIT; + SELECT + CONVERT(DATETIME2(7), SWITCHOFFSET(CONVERT(datetimeoffset, dr.event_date), DATENAME(TzOffset, SYSDATETIMEOFFSET()))) AS event_date, + dr.victim_id, + dr.resource_xml + INTO #deadlock_resource + FROM + ( SELECT dd.deadlock_xml.value('(event/@timestamp)[1]', 'DATETIME2') AS event_date, dd.deadlock_xml.value('(//deadlock/victim-list/victimProcess/@id)[1]', 'NVARCHAR(256)') AS victim_id, ISNULL(ca.dp.query('.'), '') AS resource_xml - INTO #deadlock_resource FROM #deadlock_data AS dd CROSS APPLY dd.deadlock_xml.nodes('//deadlock/resource-list') AS ca(dp) + ) AS dr OPTION ( RECOMPILE ); @@ -29066,6 +29090,7 @@ You need to use an Azure storage account, and the path has to look like this: ht AND (dow.event_date < @EndDate OR @EndDate IS NULL) AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) AND dow.lock_type NOT IN (N'HEAP', N'RID') + AND dow.index_name is not null GROUP BY DB_NAME(dow.database_id), dow.index_name OPTION ( RECOMPILE ); @@ -29464,6 +29489,7 @@ You need to use an Azure storage account, and the path has to look like this: ht + ' parallel deadlocks.' FROM #deadlock_resource_parallel AS drp WHERE 1 = 1 + HAVING COUNT_BIG(DISTINCT drp.event_date) > 0 OPTION ( RECOMPILE ); /*Thank you goodnight*/ @@ -29988,7 +30014,7 @@ BEGIN /*First BEGIN*/ SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '3.9999', @VersionDate = '20201114'; +SELECT @Version = '3.99999', @VersionDate = '20201211'; IF(@VersionCheckMode = 1) BEGIN RETURN; @@ -35715,7 +35741,7 @@ BEGIN SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '7.9999', @VersionDate = '20201114'; + SELECT @Version = '7.99999', @VersionDate = '20201211'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_AllNightLog.sql b/sp_AllNightLog.sql index ff8c022f4..f804defc7 100644 --- a/sp_AllNightLog.sql +++ b/sp_AllNightLog.sql @@ -30,7 +30,7 @@ SET NOCOUNT ON; BEGIN; -SELECT @Version = '3.999', @VersionDate = '20201114'; +SELECT @Version = '3.9999', @VersionDate = '20201211'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_AllNightLog_Setup.sql b/sp_AllNightLog_Setup.sql index 6aea6265c..ba5de06eb 100644 --- a/sp_AllNightLog_Setup.sql +++ b/sp_AllNightLog_Setup.sql @@ -36,7 +36,7 @@ SET NOCOUNT ON; BEGIN; -SELECT @Version = '3.999', @VersionDate = '20201114'; +SELECT @Version = '3.9999', @VersionDate = '20201211'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_Blitz.sql b/sp_Blitz.sql index 3e52a715b..c463300da 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -37,7 +37,7 @@ AS SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '7.9999', @VersionDate = '20201114'; + SELECT @Version = '7.99999', @VersionDate = '20201211'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) diff --git a/sp_BlitzBackups.sql b/sp_BlitzBackups.sql index 6171d0d74..28648322b 100755 --- a/sp_BlitzBackups.sql +++ b/sp_BlitzBackups.sql @@ -23,7 +23,7 @@ AS SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '3.9999', @VersionDate = '20201114'; + SELECT @Version = '3.99999', @VersionDate = '20201211'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_BlitzCache.sql b/sp_BlitzCache.sql index 3bafae06b..8fc4a9cf6 100644 --- a/sp_BlitzCache.sql +++ b/sp_BlitzCache.sql @@ -278,7 +278,7 @@ BEGIN SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '7.9999', @VersionDate = '20201114'; +SELECT @Version = '7.99999', @VersionDate = '20201211'; IF(@VersionCheckMode = 1) diff --git a/sp_BlitzFirst.sql b/sp_BlitzFirst.sql index 92c503091..cade0fd1e 100644 --- a/sp_BlitzFirst.sql +++ b/sp_BlitzFirst.sql @@ -45,7 +45,7 @@ BEGIN SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '7.9999', @VersionDate = '20201114'; +SELECT @Version = '7.99999', @VersionDate = '20201211'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_BlitzInMemoryOLTP.sql b/sp_BlitzInMemoryOLTP.sql index f7cdd18c0..2cbd36c67 100644 --- a/sp_BlitzInMemoryOLTP.sql +++ b/sp_BlitzInMemoryOLTP.sql @@ -82,7 +82,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI */ AS DECLARE @ScriptVersion VARCHAR(30); -SELECT @ScriptVersion = '1.8', @VersionDate = '20201114'; +SELECT @ScriptVersion = '1.8', @VersionDate = '20201211'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index 60122b74c..c1838e063 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -45,7 +45,7 @@ AS SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '7.9999', @VersionDate = '20201114'; +SELECT @Version = '7.99999', @VersionDate = '20201211'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index 18b6b4265..3ab3e918b 100644 --- a/sp_BlitzLock.sql +++ b/sp_BlitzLock.sql @@ -32,7 +32,7 @@ BEGIN SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '2.9999', @VersionDate = '20201114'; +SELECT @Version = '2.99999', @VersionDate = '20201211'; IF(@VersionCheckMode = 1) diff --git a/sp_BlitzQueryStore.sql b/sp_BlitzQueryStore.sql index 994c46b6e..1955ea6ed 100644 --- a/sp_BlitzQueryStore.sql +++ b/sp_BlitzQueryStore.sql @@ -56,7 +56,7 @@ BEGIN /*First BEGIN*/ SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '3.9999', @VersionDate = '20201114'; +SELECT @Version = '3.99999', @VersionDate = '20201211'; IF(@VersionCheckMode = 1) BEGIN RETURN; diff --git a/sp_BlitzWho.sql b/sp_BlitzWho.sql index 536d2e8f0..dd55c4d29 100644 --- a/sp_BlitzWho.sql +++ b/sp_BlitzWho.sql @@ -29,7 +29,7 @@ BEGIN SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '7.9999', @VersionDate = '20201114'; + SELECT @Version = '7.99999', @VersionDate = '20201211'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_DatabaseRestore.sql b/sp_DatabaseRestore.sql index 0b69da849..2ccb05930 100755 --- a/sp_DatabaseRestore.sql +++ b/sp_DatabaseRestore.sql @@ -39,7 +39,7 @@ SET NOCOUNT ON; /*Versioning details*/ -SELECT @Version = '7.9999', @VersionDate = '20201114'; +SELECT @Version = '7.99999', @VersionDate = '20201211'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_ineachdb.sql b/sp_ineachdb.sql index 3d5eb5c8c..618c2de3a 100644 --- a/sp_ineachdb.sql +++ b/sp_ineachdb.sql @@ -34,7 +34,7 @@ AS BEGIN SET NOCOUNT ON; - SELECT @Version = '2.9999', @VersionDate = '20201114'; + SELECT @Version = '2.99999', @VersionDate = '20201211'; IF(@VersionCheckMode = 1) BEGIN From 821336c187977dce06b609dee9642ae14c01ceb7 Mon Sep 17 00:00:00 2001 From: Adrian Buckman <33764798+Adedba@users.noreply.github.com> Date: Fri, 11 Dec 2020 12:09:59 +0000 Subject: [PATCH 051/662] Create sp_BlitzAnalysis.sql #2713 A work in progress version of sp_BlitzAnalysis --- sp_BlitzAnalysis.sql | 671 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 671 insertions(+) create mode 100644 sp_BlitzAnalysis.sql diff --git a/sp_BlitzAnalysis.sql b/sp_BlitzAnalysis.sql new file mode 100644 index 000000000..5ba361588 --- /dev/null +++ b/sp_BlitzAnalysis.sql @@ -0,0 +1,671 @@ +USE [master] +GO + + +SET ANSI_NULLS ON +GO + +SET QUOTED_IDENTIFIER ON +GO + +IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[sp_BlitzAnalysis]') AND type in (N'P', N'PC')) +BEGIN +EXEC dbo.sp_executesql @statement = N'CREATE PROCEDURE [dbo].[sp_BlitzAnalysis] AS' +END +GO + + +ALTER PROCEDURE [dbo].[sp_BlitzAnalysis] ( +@Help TINYINT = 0, +@FromDate DATETIMEOFFSET(7) = NULL, +@ToDate DATETIMEOFFSET(7) = NULL, +@OutputSchemaName NVARCHAR(256) = N'dbo', +@OutputDatabaseName NVARCHAR(256) = N'DBA', +@OutputTableNameBlitzFirst NVARCHAR(256) = N'BlitzFirst', /**/ +@OutputTableNameFileStats NVARCHAR(256) = N'BlitzFirst_FileStats',/**/ +@OutputTableNamePerfmonStats NVARCHAR(256) = N'BlitzFirst_PerfmonStats', +@OutputTableNameWaitStats NVARCHAR(256) = N'BlitzFirst_WaitStats', /**/ +@OutputTableNameBlitzCache NVARCHAR(256) = N'BlitzCache',/**/ +@OutputTableNameBlitzWho NVARCHAR(256) = N'BlitzWho', +@Servername NVARCHAR(128) = @@SERVERNAME, +@BlitzCacheSortorder NVARCHAR(20) = N'cpu', +@MaxBlitzFirstPriority INT = 249, +@ReadLatencyThreshold INT = 100, +@WriteLatencyThreshold INT = 100, +@WaitStatsTop TINYINT = 10, +@SkipAnalysis BIT = 0, +@Version VARCHAR(30) = NULL, +@VersionDate DATETIME = NULL, +@VersionCheckMode BIT = 0, +@Debug BIT = 0 +) +AS +SET NOCOUNT ON; + +SELECT @Version = '1.000', @VersionDate = '20201113'; + +IF(@VersionCheckMode = 1) +BEGIN + SELECT @Version AS [Version], @VersionDate AS [VersionDate]; + RETURN; +END; + +IF (@Help = 1) +BEGIN + PRINT 'EXEC sp_BlitzAnalysis +@FromDate = ''20201111 13:30'', +@ToDate = NULL, /* Get an hour of data */ +@OutputSchemaName = N''dbo'', +@OutputDatabaseName = N''DBA'', +@OutputTableNameBlitzFirst = N''BlitzFirst'', +@OutputTableNameFileStats = N''BlitzFirst_FileStats'', +@OutputTableNamePerfmonStats = N''BlitzFirst_PerfmonStats'', +@OutputTableNameWaitStats = N''BlitzFirst_WaitStats'', +@OutputTableNameBlitzCache = N''BlitzCache'', +@OutputTableNameBlitzWho = N''BlitzWho'', +@MaxBlitzFirstPriority = 249, +@BlitzCacheSortorder = ''cpu'', +@WaitStatsTop = 3, /* Controls the top for wait stats only */ +@Debug = 0;'; + RETURN; +END + +DECLARE @FullOutputTableNameBlitzFirst NVARCHAR(1000); +DECLARE @FullOutputTableNameFileStats NVARCHAR(1000); +DECLARE @FullOutputTableNamePerfmonStats NVARCHAR(1000); +DECLARE @FullOutputTableNameWaitStats NVARCHAR(1000); +DECLARE @FullOutputTableNameBlitzCache NVARCHAR(1000); +DECLARE @FullOutputTableNameBlitzWho NVARCHAR(1000); +DECLARE @Sql NVARCHAR(MAX); +DECLARE @NewLine NVARCHAR(2) = CHAR(10)+ CHAR(13); + +/* Set fully qualified table names */ +SET @FullOutputTableNameBlitzFirst = QUOTENAME(@OutputDatabaseName)+N'.'+QUOTENAME(@OutputSchemaName)+N'.'+QUOTENAME(@OutputTableNameBlitzFirst); +SET @FullOutputTableNameFileStats = QUOTENAME(@OutputDatabaseName)+N'.'+QUOTENAME(@OutputSchemaName)+N'.'+QUOTENAME(@OutputTableNameFileStats+N'_Deltas'); +SET @FullOutputTableNamePerfmonStats = QUOTENAME(@OutputDatabaseName)+N'.'+QUOTENAME(@OutputSchemaName)+N'.'+QUOTENAME(@OutputTableNamePerfmonStats+N'_Actuals'); +SET @FullOutputTableNameWaitStats = QUOTENAME(@OutputDatabaseName)+N'.'+QUOTENAME(@OutputSchemaName)+N'.'+QUOTENAME(@OutputTableNameWaitStats+N'_Deltas'); +SET @FullOutputTableNameBlitzCache = QUOTENAME(@OutputDatabaseName)+N'.'+QUOTENAME(@OutputSchemaName)+N'.'+QUOTENAME(@OutputTableNameBlitzCache); +SET @FullOutputTableNameBlitzWho = QUOTENAME(@OutputDatabaseName)+N'.'+QUOTENAME(@OutputSchemaName)+N'.'+QUOTENAME(@OutputTableNameBlitzWho); + +IF OBJECT_ID('tempdb.dbo.#BlitzFirstCounts') IS NOT NULL +BEGIN + DROP TABLE #BlitzFirstCounts; +END + +CREATE TABLE #BlitzFirstCounts ( + [Priority] TINYINT NOT NULL, + [FindingsGroup] VARCHAR(50) NOT NULL, + [Finding] VARCHAR(200) NOT NULL, + [TotalOccurrences] INT NULL, + [FirstOccurrence] DATETIMEOFFSET(7) NULL, + [LastOccurrence] DATETIMEOFFSET(7) NULL +); + +/* Sanitise variables */ +IF (@BlitzCacheSortorder IS NULL) +BEGIN + SET @BlitzCacheSortorder = N'cpu'; +END + +SET @BlitzCacheSortorder = LOWER(@BlitzCacheSortorder); + +IF (@FromDate IS NULL) +BEGIN + RAISERROR('Setting @FromDate to: 1 hour ago',0,0) WITH NOWAIT; + /* Set FromDate to be an hour ago */ + SET @FromDate = DATEADD(HOUR,-1,SYSDATETIMEOFFSET()); + + RAISERROR('Setting @ToDate to: Now',0,0) WITH NOWAIT; + /* Get data right up to now */ + SET @ToDate = SYSDATETIMEOFFSET(); +END + +IF (@ToDate IS NULL) +BEGIN + /* Default to an hour of data or SYSDATETIMEOFFSET() if now is earlier than the hour added to @FromDate */ + IF(DATEADD(HOUR,1,@FromDate) < SYSDATETIMEOFFSET()) + BEGIN + RAISERROR('@ToDate was NULL - Setting to return 1 hour of information, if you want more then set @ToDate aswell',0,0) WITH NOWAIT; + SET @ToDate = DATEADD(HOUR,1,@FromDate); + END + ELSE + BEGIN + RAISERROR('@ToDate was NULL - Setting to SYSDATETIMEOFFSET()',0,0) WITH NOWAIT; + SET @ToDate = SYSDATETIMEOFFSET(); + END +END + +IF (@OutputSchemaName IS NULL) +BEGIN + SET @OutputSchemaName = 'dbo'; +END + +SELECT + @Servername AS [ServerToReportOn], + CAST(1 AS NVARCHAR(20)) + N' - '+ CAST(@MaxBlitzFirstPriority AS NVARCHAR(20)) AS [PrioritesToInclude], + @FromDate AS [FromDatetime], + @ToDate AS [ToDatetime];; + + +/* BlitzFirst data */ +IF (OBJECT_ID(@FullOutputTableNameBlitzFirst) IS NULL) +BEGIN + RAISERROR('Table provided for BlitzFirst data: %s does not exist',10,0,@FullOutputTableNameBlitzFirst); + SELECT N'No BlitzFirst data available as the table cannot be found'; +END + +SET @Sql = N' +INSERT INTO #BlitzFirstCounts ([Priority],[FindingsGroup],[Finding],[TotalOccurrences],[FirstOccurrence],[LastOccurrence]) +SELECT +[Priority], +[FindingsGroup], +[Finding], +COUNT(*) AS [TotalOccurrences], +MIN(CheckDate) AS [FirstOccurrence], +MAX(CheckDate) AS [LastOccurrence] +FROM '+@FullOutputTableNameBlitzFirst+N' +WHERE [ServerName] = @Servername +AND [Priority] BETWEEN 1 AND @MaxBlitzFirstPriority +AND CheckDate BETWEEN @FromDate AND @ToDate +AND [CheckID] > -1 +GROUP BY [Priority],[FindingsGroup],[Finding]; + +IF EXISTS(SELECT 1 FROM #BlitzFirstCounts) +BEGIN + SELECT + [Priority], + [FindingsGroup], + [Finding], + [TotalOccurrences], + [FirstOccurrence], + [LastOccurrence] + FROM #BlitzFirstCounts + ORDER BY [Priority] ASC,[TotalOccurrences] DESC; +END +ELSE +BEGIN + SELECT ''No findings with a priority greater than -1 found for this period''; +END +SELECT + [ServerName] +,[CheckDate] +,[CheckID] +,[Priority] +,[Finding] +,[URL] +,[Details] +,[HowToStopIt] +,[QueryPlan] +,[QueryText] +FROM '+@FullOutputTableNameBlitzFirst+N' Findings +WHERE [ServerName] = @Servername +AND [Priority] BETWEEN 1 AND @MaxBlitzFirstPriority +AND [CheckDate] BETWEEN @FromDate AND @ToDate +AND [CheckID] > -1 +ORDER BY CheckDate ASC,[Priority] ASC +OPTION (RECOMPILE);'; + + +RAISERROR('Getting BlitzFirst info from %s',0,0,@FullOutputTableNameBlitzFirst) WITH NOWAIT; + +IF (@Debug = 1) +BEGIN + PRINT @Sql; +END + +EXEC sp_executesql @Sql, +N'@FromDate DATETIMEOFFSET(7), +@ToDate DATETIMEOFFSET(7), +@Servername NVARCHAR(128), +@MaxBlitzFirstPriority INT', +@FromDate=@FromDate, +@ToDate=@ToDate, +@Servername=@Servername, +@MaxBlitzFirstPriority = @MaxBlitzFirstPriority; + + +/* Blitz WaitStats data */ +IF (OBJECT_ID(@FullOutputTableNameWaitStats) IS NULL) +BEGIN + RAISERROR('Table provided for wait stats data: %s does not exist',10,0,@FullOutputTableNameWaitStats); + SELECT N'No wait stats data available as the table cannot be found'; +END + +SET @Sql = N'SELECT +[ServerName], +[CheckDate], +[wait_type], +[WaitsRank], +[WaitCategory], +[Ignorable], +[ElapsedSeconds], +[wait_time_ms_delta], +[wait_time_minutes_delta], +[wait_time_minutes_per_minute], +[signal_wait_time_ms_delta], +[waiting_tasks_count_delta], +CAST(ISNULL((CAST([wait_time_ms_delta] AS MONEY)/NULLIF(CAST([waiting_tasks_count_delta] AS MONEY),0)),0) AS DECIMAL(18,2)) AS [wait_time_ms_per_wait] +FROM +( + SELECT + [ServerName], + [CheckDate], + [wait_type], + [WaitCategory], + [Ignorable], + [ElapsedSeconds], + [wait_time_ms_delta], + [wait_time_minutes_delta], + [wait_time_minutes_per_minute], + [signal_wait_time_ms_delta], + [waiting_tasks_count_delta], + ROW_NUMBER() OVER(PARTITION BY [CheckDate] ORDER BY [CheckDate] ASC,[wait_time_ms_delta] DESC) AS [WaitsRank] + FROM '+@FullOutputTableNameWaitStats+N' AS [Waits] + WHERE [ServerName] = @Servername + AND [CheckDate] BETWEEN @FromDate AND @ToDate +) TopWaits +WHERE [WaitsRank] <= @WaitStatsTop +ORDER BY +[CheckDate] ASC, +[wait_time_ms_delta] DESC +OPTION(RECOMPILE);' + +RAISERROR('Getting wait stats info from %s',0,0,@FullOutputTableNameWaitStats) WITH NOWAIT; + +IF (@Debug = 1) +BEGIN + PRINT @Sql; +END + + +EXEC sp_executesql @Sql, +N'@FromDate DATETIMEOFFSET(7), +@ToDate DATETIMEOFFSET(7), +@Servername NVARCHAR(128), +@WaitStatsTop TINYINT', +@FromDate=@FromDate, +@ToDate=@ToDate, +@Servername=@Servername, +@WaitStatsTop=@WaitStatsTop; + + +/* BlitzFileStats info */ +IF (OBJECT_ID(@FullOutputTableNameFileStats) IS NULL) +BEGIN + RAISERROR('Table provided for FileStats data: %s does not exist',10,0,@FullOutputTableNameFileStats); + SELECT N'No FileStats data available as the table cannot be found'; +END + + +SET @Sql = N' +SELECT +[ServerName], +[CheckDate], +CASE + WHEN MAX([io_stall_read_ms_average]) > @ReadLatencyThreshold THEN ''Yes'' + WHEN MAX([io_stall_write_ms_average]) > @WriteLatencyThreshold THEN ''Yes'' + ELSE ''No'' +END AS [io_stall_ms_breached], +LEFT([PhysicalName],LEN([PhysicalName])-CHARINDEX(''\'',REVERSE([PhysicalName]))+1) AS [PhysicalPath], +SUM([SizeOnDiskMB]) AS [SizeOnDiskMB], +SUM([SizeOnDiskMBgrowth]) AS [SizeOnDiskMBgrowth], +MAX([io_stall_read_ms]) AS [max_io_stall_read_ms], +MAX([io_stall_read_ms_average]) AS [max_io_stall_read_ms_average], +@ReadLatencyThreshold AS [is_stall_read_ms_threshold], +SUM([num_of_reads]) AS [num_of_reads], +SUM([megabytes_read]) AS [megabytes_read], +MAX([io_stall_write_ms]) AS [max_io_stall_write_ms], +MAX([io_stall_write_ms_average]) AS [max_io_stall_write_ms_average], +@WriteLatencyThreshold AS [io_stall_write_ms_average], +SUM([num_of_writes]) AS [num_of_writes], +SUM([megabytes_written]) AS [megabytes_written] +FROM '+@FullOutputTableNameFileStats+N' +WHERE [ServerName] = @Servername +AND [CheckDate] BETWEEN @FromDate AND @ToDate +GROUP BY +[ServerName], +[CheckDate], +LEFT([PhysicalName],LEN([PhysicalName])-CHARINDEX(''\'',REVERSE([PhysicalName]))+1) +ORDER BY +[CheckDate] ASC +OPTION (RECOMPILE);' + +RAISERROR('Getting FileStats info from %s',0,0,@FullOutputTableNameFileStats) WITH NOWAIT; + +IF (@Debug = 1) +BEGIN + PRINT @Sql; +END + + +EXEC sp_executesql @Sql, +N'@FromDate DATETIMEOFFSET(7), +@ToDate DATETIMEOFFSET(7), +@Servername NVARCHAR(128), +@ReadLatencyThreshold INT, +@WriteLatencyThreshold INT', +@FromDate=@FromDate, +@ToDate=@ToDate, +@Servername=@Servername, +@ReadLatencyThreshold = @ReadLatencyThreshold, +@WriteLatencyThreshold = @WriteLatencyThreshold; + + +/* Blitz Perfmon stats*/ +IF (OBJECT_ID(@FullOutputTableNamePerfmonStats) IS NULL) +BEGIN + RAISERROR('Table provided for Perfmon stats data: %s does not exist',10,0,@FullOutputTableNamePerfmonStats); + SELECT N'No Perfmon data available as the table cannot be found'; +END + +SET @Sql = N' +SELECT + [ServerName] + ,[CheckDate] + ,[counter_name] + ,[object_name] + ,[instance_name] + ,[cntr_value] +FROM '+@FullOutputTableNamePerfmonStats+N' +WHERE CheckDate BETWEEN @FromDate AND @ToDate +ORDER BY + [CheckDate] ASC, + [counter_name] ASC;' + +RAISERROR('Getting Perfmon info from %s',0,0,@FullOutputTableNamePerfmonStats) WITH NOWAIT; + +IF (@Debug = 1) +BEGIN + PRINT @Sql; +END + + +EXEC sp_executesql @Sql, +N'@FromDate DATETIMEOFFSET(7), +@ToDate DATETIMEOFFSET(7), +@Servername NVARCHAR(128)', +@FromDate=@FromDate, +@ToDate=@ToDate, +@Servername=@Servername; + + +/* Blitz cache data */ +IF (OBJECT_ID(@FullOutputTableNameBlitzCache) IS NULL) +BEGIN + RAISERROR('Table provided for BlitzCache data: %s does not exist',10,0,@FullOutputTableNameBlitzCache); + SELECT N'No BlitzCache data available as the table cannot be found'; +END + +RAISERROR('Sortorder for BlitzCache data: %s',0,0,@BlitzCacheSortorder) WITH NOWAIT; + +/* Set intial CTE */ +SET @Sql = N'WITH CheckDates AS ( +SELECT DISTINCT CheckDate +FROM ' ++@FullOutputTableNameBlitzCache ++@NewLine ++N'WHERE [ServerName] = @Servername +AND [CheckDate] BETWEEN @FromDate AND @ToDate +)'; + +/* Append additional CTEs based on sortorder */ +SET @Sql += ( +SELECT N',' ++@NewLine ++[SortOptions].[Aliasname]+N' AS ( +SELECT + [ServerName] + ,'+[SortOptions].[Aliasname]+N'.[CheckDate] + ,[Sortorder] + ,[TimeFrameRank] + ,ROW_NUMBER() OVER(ORDER BY ['+[SortOptions].[Columnname]+N'] DESC) AS [OverallRank] + ,'+[SortOptions].[Aliasname]+N'.[QueryType] + ,'+[SortOptions].[Aliasname]+N'.[QueryText] + ,'+[SortOptions].[Aliasname]+N'.[DatabaseName] + ,'+[SortOptions].[Aliasname]+N'.[AverageCPU] + ,'+[SortOptions].[Aliasname]+N'.[TotalCPU] + ,'+[SortOptions].[Aliasname]+N'.[PercentCPUByType] + ,'+[SortOptions].[Aliasname]+N'.[AverageDuration] + ,'+[SortOptions].[Aliasname]+N'.[TotalDuration] + ,'+[SortOptions].[Aliasname]+N'.[PercentDurationByType] + ,'+[SortOptions].[Aliasname]+N'.[AverageReads] + ,'+[SortOptions].[Aliasname]+N'.[TotalReads] + ,'+[SortOptions].[Aliasname]+N'.[PercentReadsByType] + ,'+[SortOptions].[Aliasname]+N'.[AverageWrites] + ,'+[SortOptions].[Aliasname]+N'.[TotalWrites] + ,'+[SortOptions].[Aliasname]+N'.[PercentWritesByType] + ,'+[SortOptions].[Aliasname]+N'.[ExecutionCount] + ,'+[SortOptions].[Aliasname]+N'.[ExecutionWeight] + ,'+[SortOptions].[Aliasname]+N'.[PercentExecutionsByType] + ,'+[SortOptions].[Aliasname]+N'.[ExecutionsPerMinute] + ,'+[SortOptions].[Aliasname]+N'.[PlanCreationTime] + ,'+[SortOptions].[Aliasname]+N'.[PlanCreationTimeHours] + ,'+[SortOptions].[Aliasname]+N'.[LastExecutionTime] + ,'+[SortOptions].[Aliasname]+N'.[PlanHandle] + ,'+[SortOptions].[Aliasname]+N'.[SqlHandle] + ,'+[SortOptions].[Aliasname]+N'.[SQL Handle More Info] + ,'+[SortOptions].[Aliasname]+N'.[QueryHash] + ,'+[SortOptions].[Aliasname]+N'.[Query Hash More Info] + ,'+[SortOptions].[Aliasname]+N'.[QueryPlanHash] + ,'+[SortOptions].[Aliasname]+N'.[StatementStartOffset] + ,'+[SortOptions].[Aliasname]+N'.[StatementEndOffset] + ,'+[SortOptions].[Aliasname]+N'.[MinReturnedRows] + ,'+[SortOptions].[Aliasname]+N'.[MaxReturnedRows] + ,'+[SortOptions].[Aliasname]+N'.[AverageReturnedRows] + ,'+[SortOptions].[Aliasname]+N'.[TotalReturnedRows] + ,'+[SortOptions].[Aliasname]+N'.[QueryPlan] + ,'+[SortOptions].[Aliasname]+N'.[NumberOfPlans] + ,'+[SortOptions].[Aliasname]+N'.[NumberOfDistinctPlans] + ,'+[SortOptions].[Aliasname]+N'.[MinGrantKB] + ,'+[SortOptions].[Aliasname]+N'.[MaxGrantKB] + ,'+[SortOptions].[Aliasname]+N'.[MinUsedGrantKB] + ,'+[SortOptions].[Aliasname]+N'.[MaxUsedGrantKB] + ,'+[SortOptions].[Aliasname]+N'.[PercentMemoryGrantUsed] + ,'+[SortOptions].[Aliasname]+N'.[AvgMaxMemoryGrant] + ,'+[SortOptions].[Aliasname]+N'.[MinSpills] + ,'+[SortOptions].[Aliasname]+N'.[MaxSpills] + ,'+[SortOptions].[Aliasname]+N'.[TotalSpills] + ,'+[SortOptions].[Aliasname]+N'.[AvgSpills] + ,'+[SortOptions].[Aliasname]+N'.[QueryPlanCost] +FROM CheckDates +CROSS APPLY ( + SELECT TOP 5 + [ServerName] + ,'+[SortOptions].[Aliasname]+N'.[CheckDate] + ,'+QUOTENAME(UPPER([SortOptions].[Sortorder]),N'''')+N' AS [Sortorder] + ,ROW_NUMBER() OVER(ORDER BY ['+[SortOptions].[Columnname]+N'] DESC) AS [TimeFrameRank] + ,'+[SortOptions].[Aliasname]+N'.[QueryType] + ,'+[SortOptions].[Aliasname]+N'.[QueryText] + ,'+[SortOptions].[Aliasname]+N'.[DatabaseName] + ,'+[SortOptions].[Aliasname]+N'.[AverageCPU] + ,'+[SortOptions].[Aliasname]+N'.[TotalCPU] + ,'+[SortOptions].[Aliasname]+N'.[PercentCPUByType] + ,'+[SortOptions].[Aliasname]+N'.[AverageDuration] + ,'+[SortOptions].[Aliasname]+N'.[TotalDuration] + ,'+[SortOptions].[Aliasname]+N'.[PercentDurationByType] + ,'+[SortOptions].[Aliasname]+N'.[AverageReads] + ,'+[SortOptions].[Aliasname]+N'.[TotalReads] + ,'+[SortOptions].[Aliasname]+N'.[PercentReadsByType] + ,'+[SortOptions].[Aliasname]+N'.[AverageWrites] + ,'+[SortOptions].[Aliasname]+N'.[TotalWrites] + ,'+[SortOptions].[Aliasname]+N'.[PercentWritesByType] + ,'+[SortOptions].[Aliasname]+N'.[ExecutionCount] + ,'+[SortOptions].[Aliasname]+N'.[ExecutionWeight] + ,'+[SortOptions].[Aliasname]+N'.[PercentExecutionsByType] + ,'+[SortOptions].[Aliasname]+N'.[ExecutionsPerMinute] + ,'+[SortOptions].[Aliasname]+N'.[PlanCreationTime] + ,'+[SortOptions].[Aliasname]+N'.[PlanCreationTimeHours] + ,'+[SortOptions].[Aliasname]+N'.[LastExecutionTime] + ,'+[SortOptions].[Aliasname]+N'.[PlanHandle] + ,'+[SortOptions].[Aliasname]+N'.[SqlHandle] + ,'+[SortOptions].[Aliasname]+N'.[SQL Handle More Info] + ,'+[SortOptions].[Aliasname]+N'.[QueryHash] + ,'+[SortOptions].[Aliasname]+N'.[Query Hash More Info] + ,'+[SortOptions].[Aliasname]+N'.[QueryPlanHash] + ,'+[SortOptions].[Aliasname]+N'.[StatementStartOffset] + ,'+[SortOptions].[Aliasname]+N'.[StatementEndOffset] + ,'+[SortOptions].[Aliasname]+N'.[MinReturnedRows] + ,'+[SortOptions].[Aliasname]+N'.[MaxReturnedRows] + ,'+[SortOptions].[Aliasname]+N'.[AverageReturnedRows] + ,'+[SortOptions].[Aliasname]+N'.[TotalReturnedRows] + ,'+[SortOptions].[Aliasname]+N'.[QueryPlan] + ,'+[SortOptions].[Aliasname]+N'.[NumberOfPlans] + ,'+[SortOptions].[Aliasname]+N'.[NumberOfDistinctPlans] + ,'+[SortOptions].[Aliasname]+N'.[MinGrantKB] + ,'+[SortOptions].[Aliasname]+N'.[MaxGrantKB] + ,'+[SortOptions].[Aliasname]+N'.[MinUsedGrantKB] + ,'+[SortOptions].[Aliasname]+N'.[MaxUsedGrantKB] + ,'+[SortOptions].[Aliasname]+N'.[PercentMemoryGrantUsed] + ,'+[SortOptions].[Aliasname]+N'.[AvgMaxMemoryGrant] + ,'+[SortOptions].[Aliasname]+N'.[MinSpills] + ,'+[SortOptions].[Aliasname]+N'.[MaxSpills] + ,'+[SortOptions].[Aliasname]+N'.[TotalSpills] + ,'+[SortOptions].[Aliasname]+N'.[AvgSpills] + ,'+[SortOptions].[Aliasname]+N'.[QueryPlanCost] + FROM '+@FullOutputTableNameBlitzCache+N' AS '+[SortOptions].[Aliasname]+N' + WHERE [ServerName] = @Servername + AND [CheckDate] BETWEEN @FromDate AND @ToDate + AND ['+[SortOptions].[Aliasname]+N'].[CheckDate] = [CheckDates].[CheckDate] + ORDER BY ['+[SortOptions].[Columnname]+N'] DESC) '+[SortOptions].[Aliasname]+N' +)' +FROM (VALUES + (N'cpu',N'TopCPU',N'TotalCPU'), + (N'reads',N'TopReads',N'TotalReads'), + (N'writes',N'TopWrites',N'TotalWrites'), + (N'duration',N'TopDuration',N'TotalDuration'), + (N'executions',N'TopExecutions',N'ExecutionCount') + ) SortOptions(Sortorder,Aliasname,Columnname) +WHERE [SortOptions].[Sortorder] = ISNULL(NULLIF(@BlitzCacheSortorder,N'all'),[SortOptions].[Sortorder]) +FOR XML PATH(N''), TYPE).value(N'.[1]', N'NVARCHAR(MAX)') + + +/* Build the select statements to return the data after CTE declarations */ +SET @Sql += ( +SELECT STUFF(( +SELECT @NewLine ++N'UNION ALL' ++@NewLine ++N'SELECT * +FROM '+[SortOptions].[Aliasname] +FROM (VALUES + (N'cpu',N'TopCPU',N'TotalCPU'), + (N'reads',N'TopReads',N'TotalReads'), + (N'writes',N'TopWrites',N'TotalWrites'), + (N'duration',N'TopDuration',N'TotalDuration'), + (N'executions',N'TopExecutions',N'ExecutionCount') + ) SortOptions(Sortorder,Aliasname,Columnname) +WHERE [SortOptions].[Sortorder] = ISNULL(NULLIF(@BlitzCacheSortorder,N'all'),[SortOptions].[Sortorder]) +FOR XML PATH(N''), TYPE).value(N'.[1]', N'NVARCHAR(MAX)'),1,11,N'') +); + +/* Append Order By */ +SET @Sql += @NewLine ++'ORDER BY + [Sortorder] ASC, + [CheckDate] ASC, + [TimeFrameRank] ASC'; + +/* Append OPTION(RECOMPILE) complete the statement */ +SET @Sql += @NewLine ++'OPTION(RECOMPILE);'; + +RAISERROR('Getting BlitzCache info from %s',0,0,@FullOutputTableNameBlitzCache) WITH NOWAIT; + +IF (@Debug = 1) +BEGIN + PRINT SUBSTRING(@Sql, 0, 4000); + PRINT SUBSTRING(@Sql, 4000, 8000); + PRINT SUBSTRING(@Sql, 8000, 12000); + PRINT SUBSTRING(@Sql, 12000, 16000); + PRINT SUBSTRING(@Sql, 16000, 20000); + PRINT SUBSTRING(@Sql, 24000, 28000); + PRINT SUBSTRING(@Sql, 32000, 36000); + PRINT SUBSTRING(@Sql, 40000, 44000); +END + +EXEC sp_executesql @Sql, +N'@Servername NVARCHAR(128), +@BlitzCacheSortorder NVARCHAR(20), +@FromDate DATETIMEOFFSET(7), +@ToDate DATETIMEOFFSET(7)', +@Servername = @Servername, +@BlitzCacheSortorder = @BlitzCacheSortorder, +@FromDate = @FromDate, +@ToDate = @ToDate; + + + + +/* +SET @Sql = N' +SELECT +[ServerName], +[CheckDate], +CASE + WHEN [io_stall_read_ms_average] > @ReadLatencyThreshold THEN ''Yes'' + WHEN [io_stall_write_ms_average] > @WriteLatencyThreshold THEN ''Yes'' + ELSE ''No'' +END AS [io_stall_ms_breached], +[DatabaseName], +[FileID], +[FileLogicalName], +[TypeDesc], +[PhysicalName], +[SizeOnDiskMB], +[ElapsedSeconds], +[SizeOnDiskMBgrowth], +[io_stall_read_ms], +[io_stall_read_ms_average], +@ReadLatencyThreshold AS [is_stall_read_ms_threshold], +[num_of_reads], +[megabytes_read], +[io_stall_write_ms], +[io_stall_write_ms_average], +@WriteLatencyThreshold AS [io_stall_write_ms_average], +[num_of_writes], +[megabytes_written] +FROM '+@FullOutputTableNameFileStats+N' +WHERE [ServerName] = @Servername +AND [CheckDate] BETWEEN @FromDate AND @ToDate +ORDER BY +[CheckDate] ASC, +([io_stall_read_ms_average]+[io_stall_write_ms_average]) DESC +OPTION (RECOMPILE);' +*/ + + + + + + +/* +/* Blitz cache data */ +IF (OBJECT_ID(@FullOutputTableNameFileStats) IS NULL) +BEGIN + RAISERROR('Table provided for FileStats data: %s does not exist',10,0,@FullOutputTableNameFileStats); + SELECT N'No FileStats data available as the table cannot be found'; +END + +SET @Sql + +RAISERROR('Getting FileStats info from %s',0,0,@FullOutputTableNameFileStats) WITH NOWAIT; + +IF (@Debug = 1) +BEGIN + PRINT @Sql; +END + + +EXEC sp_executesql @Sql, +N'@FromDate DATETIMEOFFSET(7), +@ToDate DATETIMEOFFSET(7), +@Servername NVARCHAR(128)', +@FromDate=@FromDate, +@ToDate=@ToDate, +@Servername=@Servername; + +*/ + +GO + + From 3adb698c9c17a2d8b153de88d93d9daa3665a08e Mon Sep 17 00:00:00 2001 From: Rich Benner Date: Mon, 14 Dec 2020 16:35:00 +0000 Subject: [PATCH 052/662] Update sp_ineachdb.sql --- sp_ineachdb.sql | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sp_ineachdb.sql b/sp_ineachdb.sql index 618c2de3a..d5bfec5b8 100644 --- a/sp_ineachdb.sql +++ b/sp_ineachdb.sql @@ -197,7 +197,8 @@ AS (SELECT C.SrcList FROM C WHERE C.InBracket = 0 AND C.Name > '') - SELECT d.database_id +INSERT #ineachdb(id,name,is_distributor) +SELECT d.database_id , d.name , d.is_distributor FROM sys.databases AS d From 9a733779086a823cf627c672509f75f01e635c99 Mon Sep 17 00:00:00 2001 From: ToddChitt Date: Tue, 15 Dec 2020 08:41:00 -0500 Subject: [PATCH 053/662] Standardizing @Help behavior Standard behavior for parameter @Help = 1 should be to run the PRINT statement(s) and nothing else. --- sp_Blitz.sql | 7 ++++++- sp_BlitzCache.sql | 8 +++++--- sp_BlitzFirst.sql | 7 +++++-- sp_BlitzIndex.sql | 7 +++++-- sp_BlitzLock.sql | 9 ++++++--- sp_BlitzWho.sql | 3 +++ 6 files changed, 30 insertions(+), 11 deletions(-) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index c463300da..7b31a4fb7 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -45,7 +45,9 @@ AS RETURN; END; - IF @Help = 1 PRINT ' + IF @Help = 1 + BEGIN + PRINT ' /* sp_Blitz from http://FirstResponderKit.org @@ -113,6 +115,9 @@ AS SOFTWARE. */'; + RETURN; + END; /* @Help = 1 */ + ELSE IF @OutputType = 'SCHEMA' BEGIN SELECT FieldList = '[Priority] TINYINT, [FindingsGroup] VARCHAR(50), [Finding] VARCHAR(200), [DatabaseName] NVARCHAR(128), [URL] VARCHAR(200), [Details] NVARCHAR(4000), [QueryPlan] NVARCHAR(MAX), [QueryPlanFiltered] NVARCHAR(MAX), [CheckID] INT'; diff --git a/sp_BlitzCache.sql b/sp_BlitzCache.sql index 8fc4a9cf6..ddce00b23 100644 --- a/sp_BlitzCache.sql +++ b/sp_BlitzCache.sql @@ -286,7 +286,9 @@ BEGIN RETURN; END; -IF @Help = 1 PRINT ' +IF @Help = 1 +BEGIN +PRINT ' sp_BlitzCache from http://FirstResponderKit.org This script displays your most resource-intensive queries from the plan cache, @@ -337,8 +339,8 @@ SOFTWARE. DECLARE @nl NVARCHAR(2) = NCHAR(13) + NCHAR(10) ; -IF @Help = 1 -BEGIN +--IF @Help = 1 /* We're still under a @Help = 1 BEGIN from above */ +--BEGIN SELECT N'@Help' AS [Parameter Name] , N'BIT' AS [Data Type] , N'Displays this help message.' AS [Parameter Description] diff --git a/sp_BlitzFirst.sql b/sp_BlitzFirst.sql index cade0fd1e..aaec488aa 100644 --- a/sp_BlitzFirst.sql +++ b/sp_BlitzFirst.sql @@ -52,7 +52,9 @@ BEGIN RETURN; END; -IF @Help = 1 PRINT ' +IF @Help = 1 +BEGIN +PRINT ' sp_BlitzFirst from http://FirstResponderKit.org This script gives you a prioritized list of why your SQL Server is slow right now. @@ -104,7 +106,8 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. '; - +RETURN; +END; /* @Help = 1 */ RAISERROR('Setting up configuration variables',10,1) WITH NOWAIT; DECLARE @StringToExecute NVARCHAR(MAX), diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index c1838e063..0c59aaecc 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -53,7 +53,9 @@ BEGIN RETURN; END; -IF @Help = 1 PRINT ' +IF @Help = 1 +BEGIN +PRINT ' /* sp_BlitzIndex from http://FirstResponderKit.org @@ -100,7 +102,8 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. '; - +RETURN; +END; /* @Help = 1 */ DECLARE @ScriptVersionName NVARCHAR(50); DECLARE @DaysUptime NUMERIC(23,2); diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index 3ab3e918b..c1b8acb8a 100644 --- a/sp_BlitzLock.sql +++ b/sp_BlitzLock.sql @@ -39,7 +39,9 @@ IF(@VersionCheckMode = 1) BEGIN RETURN; END; - IF @Help = 1 PRINT ' + IF @Help = 1 + BEGIN + PRINT ' /* sp_BlitzLock from http://FirstResponderKit.org @@ -115,8 +117,9 @@ END; SOFTWARE. */'; - - + RETURN; + END; /* @Help = 1 */ + DECLARE @ProductVersion NVARCHAR(128); DECLARE @ProductVersionMajor FLOAT; DECLARE @ProductVersionMinor INT; diff --git a/sp_BlitzWho.sql b/sp_BlitzWho.sql index dd55c4d29..97e5f7df5 100644 --- a/sp_BlitzWho.sql +++ b/sp_BlitzWho.sql @@ -39,6 +39,7 @@ BEGIN IF @Help = 1 + BEGIN PRINT ' sp_BlitzWho from http://FirstResponderKit.org @@ -76,6 +77,8 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. '; +RETURN; +END; /* @Help = 1 */ /* Get the major and minor build numbers */ DECLARE @ProductVersion NVARCHAR(128) From d3e9fa0e4d950b9ce6327797a3757f74cf838a70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mergs=C2=AE?= Date: Wed, 16 Dec 2020 11:29:15 -0800 Subject: [PATCH 054/662] Update sp_BlitzIndex.sql --- sp_BlitzIndex.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index c1838e063..bbc6d6a23 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -5221,7 +5221,7 @@ ELSE IF (@Mode=1) /*Summarize*/ ISNULL(i.index_name, '''') AS [Index Name], CASE WHEN i.is_primary_key = 1 AND i.index_definition <> ''[HEAP]'' - THEN N''-ALTER TABLE '' + QUOTENAME(i.[database_name]) + N''.'' + QUOTENAME(i.[schema_name]) + N''.'' + QUOTENAME(i.[object_name]) + + THEN N''--ALTER TABLE '' + QUOTENAME(i.[database_name]) + N''.'' + QUOTENAME(i.[schema_name]) + N''.'' + QUOTENAME(i.[object_name]) + N'' DROP CONSTRAINT '' + QUOTENAME(i.index_name) + N'';'' WHEN i.is_primary_key = 0 AND i.index_definition <> ''[HEAP]'' THEN N''--DROP INDEX ''+ QUOTENAME(i.index_name) + N'' ON '' + QUOTENAME(i.[database_name]) + N''.'' + From 3b2f3cbb4262ac392283a146e34accfc2b6849de Mon Sep 17 00:00:00 2001 From: alihacks Date: Mon, 28 Dec 2020 18:00:45 -0500 Subject: [PATCH 055/662] Fix issue reported in #2721 - 2nd case in COALESCE using last_request_start_time was missing 00 padding --- sp_BlitzWho.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sp_BlitzWho.sql b/sp_BlitzWho.sql index dd55c4d29..b4a81e378 100644 --- a/sp_BlitzWho.sql +++ b/sp_BlitzWho.sql @@ -570,7 +570,7 @@ BEGIN /* Think of the StringToExecute as starting with this, but we'll set this up later depending on whether we're doing an insert or a select: SELECT @StringToExecute = N'SELECT GETDATE() AS run_date , */ - SET @StringToExecute = N'COALESCE( RIGHT(''00'' + CONVERT(VARCHAR(20), (ABS(r.total_elapsed_time) / 1000) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), (DATEADD(SECOND, (r.total_elapsed_time / 1000), 0) + DATEADD(MILLISECOND, (r.total_elapsed_time % 1000), 0)), 114), CONVERT(VARCHAR(20), DATEDIFF(SECOND, s.last_request_start_time, GETDATE()) / 86400) + '':'' + CONVERT(VARCHAR(20), DATEADD(SECOND, DATEDIFF(SECOND, s.last_request_start_time, GETDATE()), 0), 114) ) AS [elapsed_time] , + SET @StringToExecute = N'COALESCE( RIGHT(''00'' + CONVERT(VARCHAR(20), (ABS(r.total_elapsed_time) / 1000) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), (DATEADD(SECOND, (r.total_elapsed_time / 1000), 0) + DATEADD(MILLISECOND, (r.total_elapsed_time % 1000), 0)), 114), RIGHT(''00'' + CONVERT(VARCHAR(20), DATEDIFF(SECOND, s.last_request_start_time, GETDATE()) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), DATEADD(SECOND, DATEDIFF(SECOND, s.last_request_start_time, GETDATE()), 0), 114) ) AS [elapsed_time] , s.session_id , COALESCE(DB_NAME(r.database_id), DB_NAME(blocked.dbid), ''N/A'') AS database_name, ISNULL(SUBSTRING(dest.text, @@ -775,7 +775,7 @@ IF @ProductVersionMajor >= 11 /* Think of the StringToExecute as starting with this, but we'll set this up later depending on whether we're doing an insert or a select: SELECT @StringToExecute = N'SELECT GETDATE() AS run_date , */ - SELECT @StringToExecute = N'COALESCE( RIGHT(''00'' + CONVERT(VARCHAR(20), (ABS(r.total_elapsed_time) / 1000) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), (DATEADD(SECOND, (r.total_elapsed_time / 1000), 0) + DATEADD(MILLISECOND, (r.total_elapsed_time % 1000), 0)), 114), CONVERT(VARCHAR(20), DATEDIFF(SECOND, s.last_request_start_time, GETDATE()) / 86400) + '':'' + CONVERT(VARCHAR(20), DATEADD(SECOND, DATEDIFF(SECOND, s.last_request_start_time, GETDATE()), 0), 114) ) AS [elapsed_time] , + SELECT @StringToExecute = N'COALESCE( RIGHT(''00'' + CONVERT(VARCHAR(20), (ABS(r.total_elapsed_time) / 1000) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), (DATEADD(SECOND, (r.total_elapsed_time / 1000), 0) + DATEADD(MILLISECOND, (r.total_elapsed_time % 1000), 0)), 114), RIGHT(''00'' + CONVERT(VARCHAR(20), DATEDIFF(SECOND, s.last_request_start_time, GETDATE()) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), DATEADD(SECOND, DATEDIFF(SECOND, s.last_request_start_time, GETDATE()), 0), 114) ) AS [elapsed_time] , s.session_id , COALESCE(DB_NAME(r.database_id), DB_NAME(blocked.dbid), ''N/A'') AS database_name, ISNULL(SUBSTRING(dest.text, From d4e37ca88ffd5dbc96cc3c1fb460882a551d7975 Mon Sep 17 00:00:00 2001 From: alihacks Date: Thu, 31 Dec 2020 10:57:45 -0500 Subject: [PATCH 056/662] Fix wording for #2732 --- sp_BlitzFirst.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sp_BlitzFirst.sql b/sp_BlitzFirst.sql index cade0fd1e..8e3ba667d 100644 --- a/sp_BlitzFirst.sql +++ b/sp_BlitzFirst.sql @@ -1802,13 +1802,13 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, SELECT 46 AS CheckID, 100 AS Priority, 'Query Problems' AS FindingGroup, - 'Query with memory a grant exceeding ' + 'Query with a memory grant exceeding ' +CAST(@MemoryGrantThresholdPct AS NVARCHAR(15)) +'%' AS Finding, 'Granted size: '+ CAST(CAST(Grants.granted_memory_kb / 1024 AS INT) AS NVARCHAR(50)) +N'MB ' + @LineFeed - +N'Granted pct: ' + +N'Granted pct of max workspace: ' + CAST(ISNULL((CAST(Grants.granted_memory_kb / 1024 AS MONEY) / CAST(@MaxWorkspace AS MONEY)) * 100, 0) AS NVARCHAR(50)) + '%' + @LineFeed From 698bb755898cf55ec8ece66e63bcac28a355ccf6 Mon Sep 17 00:00:00 2001 From: Simon Kirk Date: Thu, 31 Dec 2020 17:31:38 +0000 Subject: [PATCH 057/662] Encode column names for XML Encode column names for XML to prevent column names with characters that should be converted to entities from breaking the procedure. --- Install-All-Scripts.sql | 12 ++++++------ sp_BlitzIndex.sql | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Install-All-Scripts.sql b/Install-All-Scripts.sql index 993648b65..05a6e39ec 100755 --- a/Install-All-Scripts.sql +++ b/Install-All-Scripts.sql @@ -27118,15 +27118,15 @@ BEGIN TRY FROM ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_details AS id_inner CROSS APPLY( SELECT LTRIM(RTRIM(v.value(''(./text())[1]'', ''varchar(max)''))) AS ColumnName, ''Equality'' AS IndexColumnType - FROM (VALUES (CONVERT(XML, N'''' + REPLACE(CAST(id_inner.equality_columns AS nvarchar(max)), N'','', N'''') + N''''))) x(n) + FROM (VALUES (CONVERT(XML, N'''' + REPLACE((SELECT CAST(id_inner.equality_columns AS nvarchar(max)) FOR XML PATH('''')), N'','', N'''') + N''''))) x(n) CROSS APPLY n.nodes(''x'') node(v) UNION ALL SELECT LTRIM(RTRIM(v.value(N''(./text())[1]'', ''varchar(max)''))) AS ColumnName, ''Inequality'' AS IndexColumnType - FROM (VALUES (CONVERT(XML, N'''' + REPLACE(CAST(id_inner.inequality_columns AS nvarchar(max)), N'','', N'''') + N''''))) x(n) + FROM (VALUES (CONVERT(XML, N'''' + REPLACE((SELECT CAST(id_inner.inequality_columns AS nvarchar(max)) FOR XML PATH('''')), N'','', N'''') + N''''))) x(n) CROSS APPLY n.nodes(''x'') node(v) UNION ALL SELECT LTRIM(RTRIM(v.value(''(./text())[1]'', ''varchar(max)''))) AS ColumnName, ''Included'' AS IndexColumnType - FROM (VALUES (CONVERT(XML, N'''' + REPLACE(CAST(id_inner.included_columns AS nvarchar(max)), N'','', N'''') + N''''))) x(n) + FROM (VALUES (CONVERT(XML, N'''' + REPLACE((SELECT CAST(id_inner.included_columns AS nvarchar(max)) FOR XML PATH('''')), N'','', N'''') + N''''))) x(n) CROSS APPLY n.nodes(''x'') node(v) )AS cn_inner' + /*split the string otherwise dsql cuts some of it out*/ @@ -27140,15 +27140,15 @@ BEGIN TRY FROM ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_details AS id CROSS APPLY( SELECT LTRIM(RTRIM(v.value(''(./text())[1]'', ''varchar(max)''))) AS ColumnName, ''Equality'' AS IndexColumnType - FROM (VALUES (CONVERT(XML, N'''' + REPLACE(CAST(id.equality_columns AS nvarchar(max)), N'','', N'''') + N''''))) x(n) + FROM (VALUES (CONVERT(XML, N'''' + REPLACE((SELECT CAST(id.equality_columns AS nvarchar(max)) FOR XML PATH('''')), N'','', N'''') + N''''))) x(n) CROSS APPLY n.nodes(''x'') node(v) UNION ALL SELECT LTRIM(RTRIM(v.value(''(./text())[1]'', ''varchar(max)''))) AS ColumnName, ''Inequality'' AS IndexColumnType - FROM (VALUES (CONVERT(XML, N'''' + REPLACE(CAST(id.inequality_columns AS nvarchar(max)), N'','', N'''') + N''''))) x(n) + FROM (VALUES (CONVERT(XML, N'''' + REPLACE((SELECT CAST(id.inequality_columns AS nvarchar(max)) FOR XML PATH('''')), N'','', N'''') + N''''))) x(n) CROSS APPLY n.nodes(''x'') node(v) UNION ALL SELECT LTRIM(RTRIM(v.value(''(./text())[1]'', ''varchar(max)''))) AS ColumnName, ''Included'' AS IndexColumnType - FROM (VALUES (CONVERT(XML, N'''' + REPLACE(CAST(id.included_columns AS nvarchar(max)), N'','', N'''') + N''''))) x(n) + FROM (VALUES (CONVERT(XML, N'''' + REPLACE((SELECT CAST(id.included_columns AS nvarchar(max)) FOR XML PATH('''')), N'','', N'''') + N''''))) x(n) CROSS APPLY n.nodes(''x'') node(v) )AS cn GROUP BY id.index_handle,id.object_id,cn.IndexColumnType diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index c1838e063..1e778f197 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -1582,15 +1582,15 @@ BEGIN TRY FROM ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_details AS id_inner CROSS APPLY( SELECT LTRIM(RTRIM(v.value(''(./text())[1]'', ''varchar(max)''))) AS ColumnName, ''Equality'' AS IndexColumnType - FROM (VALUES (CONVERT(XML, N'''' + REPLACE(CAST(id_inner.equality_columns AS nvarchar(max)), N'','', N'''') + N''''))) x(n) + FROM (VALUES (CONVERT(XML, N'''' + REPLACE((SELECT CAST(id_inner.equality_columns AS nvarchar(max)) FOR XML PATH('''')), N'','', N'''') + N''''))) x(n) CROSS APPLY n.nodes(''x'') node(v) UNION ALL SELECT LTRIM(RTRIM(v.value(N''(./text())[1]'', ''varchar(max)''))) AS ColumnName, ''Inequality'' AS IndexColumnType - FROM (VALUES (CONVERT(XML, N'''' + REPLACE(CAST(id_inner.inequality_columns AS nvarchar(max)), N'','', N'''') + N''''))) x(n) + FROM (VALUES (CONVERT(XML, N'''' + REPLACE((SELECT CAST(id_inner.inequality_columns AS nvarchar(max)) FOR XML PATH('''')), N'','', N'''') + N''''))) x(n) CROSS APPLY n.nodes(''x'') node(v) UNION ALL SELECT LTRIM(RTRIM(v.value(''(./text())[1]'', ''varchar(max)''))) AS ColumnName, ''Included'' AS IndexColumnType - FROM (VALUES (CONVERT(XML, N'''' + REPLACE(CAST(id_inner.included_columns AS nvarchar(max)), N'','', N'''') + N''''))) x(n) + FROM (VALUES (CONVERT(XML, N'''' + REPLACE((SELECT CAST(id_inner.included_columns AS nvarchar(max)) FOR XML PATH('''')), N'','', N'''') + N''''))) x(n) CROSS APPLY n.nodes(''x'') node(v) )AS cn_inner' + /*split the string otherwise dsql cuts some of it out*/ @@ -1604,15 +1604,15 @@ BEGIN TRY FROM ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_details AS id CROSS APPLY( SELECT LTRIM(RTRIM(v.value(''(./text())[1]'', ''varchar(max)''))) AS ColumnName, ''Equality'' AS IndexColumnType - FROM (VALUES (CONVERT(XML, N'''' + REPLACE(CAST(id.equality_columns AS nvarchar(max)), N'','', N'''') + N''''))) x(n) + FROM (VALUES (CONVERT(XML, N'''' + REPLACE((SELECT CAST(id.equality_columns AS nvarchar(max)) FOR XML PATH('''')), N'','', N'''') + N''''))) x(n) CROSS APPLY n.nodes(''x'') node(v) UNION ALL SELECT LTRIM(RTRIM(v.value(''(./text())[1]'', ''varchar(max)''))) AS ColumnName, ''Inequality'' AS IndexColumnType - FROM (VALUES (CONVERT(XML, N'''' + REPLACE(CAST(id.inequality_columns AS nvarchar(max)), N'','', N'''') + N''''))) x(n) + FROM (VALUES (CONVERT(XML, N'''' + REPLACE((SELECT CAST(id.inequality_columns AS nvarchar(max)) FOR XML PATH('''')), N'','', N'''') + N''''))) x(n) CROSS APPLY n.nodes(''x'') node(v) UNION ALL SELECT LTRIM(RTRIM(v.value(''(./text())[1]'', ''varchar(max)''))) AS ColumnName, ''Included'' AS IndexColumnType - FROM (VALUES (CONVERT(XML, N'''' + REPLACE(CAST(id.included_columns AS nvarchar(max)), N'','', N'''') + N''''))) x(n) + FROM (VALUES (CONVERT(XML, N'''' + REPLACE((SELECT CAST(id.included_columns AS nvarchar(max)) FOR XML PATH('''')), N'','', N'''') + N''''))) x(n) CROSS APPLY n.nodes(''x'') node(v) )AS cn GROUP BY id.index_handle,id.object_id,cn.IndexColumnType From 6b3887f7f0e24f93bb27a5a48ace86d8436195bf Mon Sep 17 00:00:00 2001 From: Simon Kirk Date: Thu, 31 Dec 2020 18:20:55 +0000 Subject: [PATCH 058/662] Undo auto-generated Install-All-Scripts.sql change --- Install-All-Scripts.sql | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Install-All-Scripts.sql b/Install-All-Scripts.sql index 05a6e39ec..993648b65 100755 --- a/Install-All-Scripts.sql +++ b/Install-All-Scripts.sql @@ -27118,15 +27118,15 @@ BEGIN TRY FROM ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_details AS id_inner CROSS APPLY( SELECT LTRIM(RTRIM(v.value(''(./text())[1]'', ''varchar(max)''))) AS ColumnName, ''Equality'' AS IndexColumnType - FROM (VALUES (CONVERT(XML, N'''' + REPLACE((SELECT CAST(id_inner.equality_columns AS nvarchar(max)) FOR XML PATH('''')), N'','', N'''') + N''''))) x(n) + FROM (VALUES (CONVERT(XML, N'''' + REPLACE(CAST(id_inner.equality_columns AS nvarchar(max)), N'','', N'''') + N''''))) x(n) CROSS APPLY n.nodes(''x'') node(v) UNION ALL SELECT LTRIM(RTRIM(v.value(N''(./text())[1]'', ''varchar(max)''))) AS ColumnName, ''Inequality'' AS IndexColumnType - FROM (VALUES (CONVERT(XML, N'''' + REPLACE((SELECT CAST(id_inner.inequality_columns AS nvarchar(max)) FOR XML PATH('''')), N'','', N'''') + N''''))) x(n) + FROM (VALUES (CONVERT(XML, N'''' + REPLACE(CAST(id_inner.inequality_columns AS nvarchar(max)), N'','', N'''') + N''''))) x(n) CROSS APPLY n.nodes(''x'') node(v) UNION ALL SELECT LTRIM(RTRIM(v.value(''(./text())[1]'', ''varchar(max)''))) AS ColumnName, ''Included'' AS IndexColumnType - FROM (VALUES (CONVERT(XML, N'''' + REPLACE((SELECT CAST(id_inner.included_columns AS nvarchar(max)) FOR XML PATH('''')), N'','', N'''') + N''''))) x(n) + FROM (VALUES (CONVERT(XML, N'''' + REPLACE(CAST(id_inner.included_columns AS nvarchar(max)), N'','', N'''') + N''''))) x(n) CROSS APPLY n.nodes(''x'') node(v) )AS cn_inner' + /*split the string otherwise dsql cuts some of it out*/ @@ -27140,15 +27140,15 @@ BEGIN TRY FROM ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_details AS id CROSS APPLY( SELECT LTRIM(RTRIM(v.value(''(./text())[1]'', ''varchar(max)''))) AS ColumnName, ''Equality'' AS IndexColumnType - FROM (VALUES (CONVERT(XML, N'''' + REPLACE((SELECT CAST(id.equality_columns AS nvarchar(max)) FOR XML PATH('''')), N'','', N'''') + N''''))) x(n) + FROM (VALUES (CONVERT(XML, N'''' + REPLACE(CAST(id.equality_columns AS nvarchar(max)), N'','', N'''') + N''''))) x(n) CROSS APPLY n.nodes(''x'') node(v) UNION ALL SELECT LTRIM(RTRIM(v.value(''(./text())[1]'', ''varchar(max)''))) AS ColumnName, ''Inequality'' AS IndexColumnType - FROM (VALUES (CONVERT(XML, N'''' + REPLACE((SELECT CAST(id.inequality_columns AS nvarchar(max)) FOR XML PATH('''')), N'','', N'''') + N''''))) x(n) + FROM (VALUES (CONVERT(XML, N'''' + REPLACE(CAST(id.inequality_columns AS nvarchar(max)), N'','', N'''') + N''''))) x(n) CROSS APPLY n.nodes(''x'') node(v) UNION ALL SELECT LTRIM(RTRIM(v.value(''(./text())[1]'', ''varchar(max)''))) AS ColumnName, ''Included'' AS IndexColumnType - FROM (VALUES (CONVERT(XML, N'''' + REPLACE((SELECT CAST(id.included_columns AS nvarchar(max)) FOR XML PATH('''')), N'','', N'''') + N''''))) x(n) + FROM (VALUES (CONVERT(XML, N'''' + REPLACE(CAST(id.included_columns AS nvarchar(max)), N'','', N'''') + N''''))) x(n) CROSS APPLY n.nodes(''x'') node(v) )AS cn GROUP BY id.index_handle,id.object_id,cn.IndexColumnType From 3ed9bf3d94f93291dce397658143dffbcb392190 Mon Sep 17 00:00:00 2001 From: AdeDBA <33764798+Adedba@users.noreply.github.com> Date: Thu, 7 Jan 2021 13:51:46 +0000 Subject: [PATCH 059/662] Added Running Check ID messages for Debug = 1 #2739 Added Running Check ID messages for Debug = 1. Added BEGIN and END where missing for various checks where I added RAISERROR commands. Forced execution of check ID 42 and 43 as the are executed within sp_executesql , tested ok. --- sp_BlitzFirst.sql | 673 +++++++++++++++++++++++++++++++--------------- 1 file changed, 461 insertions(+), 212 deletions(-) diff --git a/sp_BlitzFirst.sql b/sp_BlitzFirst.sql index cade0fd1e..7dff3d2d1 100644 --- a/sp_BlitzFirst.sql +++ b/sp_BlitzFirst.sql @@ -1382,40 +1382,47 @@ BEGIN /* Maintenance Tasks Running - Backup Running - CheckID 1 */ IF @Seconds > 0 - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, QueryHash) - SELECT 1 AS CheckID, - 1 AS Priority, - 'Maintenance Tasks Running' AS FindingGroup, - 'Backup Running' AS Finding, - 'http://www.BrentOzar.com/askbrent/backups/' AS URL, - 'Backup of ' + DB_NAME(db.resource_database_id) + ' database (' + (SELECT CAST(CAST(SUM(size * 8.0 / 1024 / 1024) AS BIGINT) AS NVARCHAR) FROM #MasterFiles WHERE database_id = db.resource_database_id) + 'GB) ' + @LineFeed - + CAST(r.percent_complete AS NVARCHAR(100)) + '% complete, has been running since ' + CAST(r.start_time AS NVARCHAR(100)) + '. ' + @LineFeed - + CASE WHEN COALESCE(s.nt_user_name, s.login_name) IS NOT NULL THEN (' Login: ' + COALESCE(s.nt_user_name, s.login_name) + ' ') ELSE '' END AS Details, - 'KILL ' + CAST(r.session_id AS NVARCHAR(100)) + ';' AS HowToStopIt, - pl.query_plan AS QueryPlan, - r.start_time AS StartTime, - s.login_name AS LoginName, - s.nt_user_name AS NTUserName, - s.[program_name] AS ProgramName, - s.[host_name] AS HostName, - db.[resource_database_id] AS DatabaseID, - DB_NAME(db.resource_database_id) AS DatabaseName, - 0 AS OpenTransactionCount, - r.query_hash - FROM sys.dm_exec_requests r - INNER JOIN sys.dm_exec_connections c ON r.session_id = c.session_id - INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id - INNER JOIN ( - SELECT DISTINCT request_session_id, resource_database_id - FROM sys.dm_tran_locks - WHERE resource_type = N'DATABASE' - AND request_mode = N'S' - AND request_status = N'GRANT' - AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.session_id = db.request_session_id - CROSS APPLY sys.dm_exec_query_plan(r.plan_handle) pl - WHERE r.command LIKE 'BACKUP%' - AND r.start_time <= DATEADD(minute, -5, GETDATE()) - AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs); + BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 1',10,1) WITH NOWAIT; + END + + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, QueryHash) + SELECT 1 AS CheckID, + 1 AS Priority, + 'Maintenance Tasks Running' AS FindingGroup, + 'Backup Running' AS Finding, + 'http://www.BrentOzar.com/askbrent/backups/' AS URL, + 'Backup of ' + DB_NAME(db.resource_database_id) + ' database (' + (SELECT CAST(CAST(SUM(size * 8.0 / 1024 / 1024) AS BIGINT) AS NVARCHAR) FROM #MasterFiles WHERE database_id = db.resource_database_id) + 'GB) ' + @LineFeed + + CAST(r.percent_complete AS NVARCHAR(100)) + '% complete, has been running since ' + CAST(r.start_time AS NVARCHAR(100)) + '. ' + @LineFeed + + CASE WHEN COALESCE(s.nt_user_name, s.login_name) IS NOT NULL THEN (' Login: ' + COALESCE(s.nt_user_name, s.login_name) + ' ') ELSE '' END AS Details, + 'KILL ' + CAST(r.session_id AS NVARCHAR(100)) + ';' AS HowToStopIt, + pl.query_plan AS QueryPlan, + r.start_time AS StartTime, + s.login_name AS LoginName, + s.nt_user_name AS NTUserName, + s.[program_name] AS ProgramName, + s.[host_name] AS HostName, + db.[resource_database_id] AS DatabaseID, + DB_NAME(db.resource_database_id) AS DatabaseName, + 0 AS OpenTransactionCount, + r.query_hash + FROM sys.dm_exec_requests r + INNER JOIN sys.dm_exec_connections c ON r.session_id = c.session_id + INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id + INNER JOIN ( + SELECT DISTINCT request_session_id, resource_database_id + FROM sys.dm_tran_locks + WHERE resource_type = N'DATABASE' + AND request_mode = N'S' + AND request_status = N'GRANT' + AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.session_id = db.request_session_id + CROSS APPLY sys.dm_exec_query_plan(r.plan_handle) pl + WHERE r.command LIKE 'BACKUP%' + AND r.start_time <= DATEADD(minute, -5, GETDATE()) + AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs); + END /* If there's a backup running, add details explaining how long full backup has been taking in the last month. */ IF @Seconds > 0 AND CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) <> 'SQL Azure' @@ -1427,109 +1434,132 @@ BEGIN /* Maintenance Tasks Running - DBCC CHECK* Running - CheckID 2 */ IF @Seconds > 0 AND EXISTS(SELECT * FROM sys.dm_exec_requests WHERE command LIKE 'DBCC%') - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, QueryHash) - SELECT 2 AS CheckID, - 1 AS Priority, - 'Maintenance Tasks Running' AS FindingGroup, - 'DBCC CHECK* Running' AS Finding, - 'http://www.BrentOzar.com/askbrent/dbcc/' AS URL, - 'Corruption check of ' + DB_NAME(db.resource_database_id) + ' database (' + (SELECT CAST(CAST(SUM(size * 8.0 / 1024 / 1024) AS BIGINT) AS NVARCHAR) FROM #MasterFiles WHERE database_id = db.resource_database_id) + 'GB) 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, - s.login_name AS LoginName, - s.nt_user_name AS NTUserName, - s.[program_name] AS ProgramName, - s.[host_name] AS HostName, - db.[resource_database_id] AS DatabaseID, - DB_NAME(db.resource_database_id) AS DatabaseName, - 0 AS OpenTransactionCount, - r.query_hash - FROM sys.dm_exec_requests r - INNER JOIN sys.dm_exec_connections c ON r.session_id = c.session_id - INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id - INNER JOIN (SELECT DISTINCT l.request_session_id, l.resource_database_id - FROM sys.dm_tran_locks l - INNER JOIN sys.databases d ON l.resource_database_id = d.database_id - WHERE l.resource_type = N'DATABASE' - AND l.request_mode = N'S' - AND l.request_status = N'GRANT' - AND l.request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.session_id = db.request_session_id - OUTER APPLY sys.dm_exec_query_plan(r.plan_handle) pl - OUTER APPLY sys.dm_exec_sql_text(r.sql_handle) AS t - WHERE r.command LIKE 'DBCC%' - AND CAST(t.text AS NVARCHAR(4000)) NOT LIKE '%dm_db_index_physical_stats%' - AND CAST(t.text AS NVARCHAR(4000)) NOT LIKE '%ALTER INDEX%' - AND CAST(t.text AS NVARCHAR(4000)) NOT LIKE '%fileproperty%' - AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs); + BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 2',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, QueryHash) + SELECT 2 AS CheckID, + 1 AS Priority, + 'Maintenance Tasks Running' AS FindingGroup, + 'DBCC CHECK* Running' AS Finding, + 'http://www.BrentOzar.com/askbrent/dbcc/' AS URL, + 'Corruption check of ' + DB_NAME(db.resource_database_id) + ' database (' + (SELECT CAST(CAST(SUM(size * 8.0 / 1024 / 1024) AS BIGINT) AS NVARCHAR) FROM #MasterFiles WHERE database_id = db.resource_database_id) + 'GB) 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, + s.login_name AS LoginName, + s.nt_user_name AS NTUserName, + s.[program_name] AS ProgramName, + s.[host_name] AS HostName, + db.[resource_database_id] AS DatabaseID, + DB_NAME(db.resource_database_id) AS DatabaseName, + 0 AS OpenTransactionCount, + r.query_hash + FROM sys.dm_exec_requests r + INNER JOIN sys.dm_exec_connections c ON r.session_id = c.session_id + INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id + INNER JOIN (SELECT DISTINCT l.request_session_id, l.resource_database_id + FROM sys.dm_tran_locks l + INNER JOIN sys.databases d ON l.resource_database_id = d.database_id + WHERE l.resource_type = N'DATABASE' + AND l.request_mode = N'S' + AND l.request_status = N'GRANT' + AND l.request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.session_id = db.request_session_id + OUTER APPLY sys.dm_exec_query_plan(r.plan_handle) pl + OUTER APPLY sys.dm_exec_sql_text(r.sql_handle) AS t + WHERE r.command LIKE 'DBCC%' + AND CAST(t.text AS NVARCHAR(4000)) NOT LIKE '%dm_db_index_physical_stats%' + AND CAST(t.text AS NVARCHAR(4000)) NOT LIKE '%ALTER INDEX%' + AND CAST(t.text AS NVARCHAR(4000)) NOT LIKE '%fileproperty%' + AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs); + END /* Maintenance Tasks Running - Restore Running - CheckID 3 */ IF @Seconds > 0 - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, QueryHash) - SELECT 3 AS CheckID, - 1 AS Priority, - 'Maintenance Tasks Running' AS FindingGroup, - 'Restore Running' AS Finding, - 'http://www.BrentOzar.com/askbrent/backups/' AS URL, - 'Restore of ' + DB_NAME(db.resource_database_id) + ' database (' + (SELECT CAST(CAST(SUM(size * 8.0 / 1024 / 1024) AS BIGINT) AS NVARCHAR) FROM #MasterFiles WHERE database_id = db.resource_database_id) + '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, - s.login_name AS LoginName, - s.nt_user_name AS NTUserName, - s.[program_name] AS ProgramName, - s.[host_name] AS HostName, - db.[resource_database_id] AS DatabaseID, - DB_NAME(db.resource_database_id) AS DatabaseName, - 0 AS OpenTransactionCount, - r.query_hash - FROM sys.dm_exec_requests r - INNER JOIN sys.dm_exec_connections c ON r.session_id = c.session_id - INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id - INNER JOIN ( - SELECT DISTINCT request_session_id, resource_database_id - FROM sys.dm_tran_locks - WHERE resource_type = N'DATABASE' - AND request_mode = N'S' - AND request_status = N'GRANT') AS db ON s.session_id = db.request_session_id - CROSS APPLY sys.dm_exec_query_plan(r.plan_handle) pl - WHERE r.command LIKE 'RESTORE%' - AND s.program_name <> 'SQL Server Log Shipping' - AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs); + BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 3',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, QueryHash) + SELECT 3 AS CheckID, + 1 AS Priority, + 'Maintenance Tasks Running' AS FindingGroup, + 'Restore Running' AS Finding, + 'http://www.BrentOzar.com/askbrent/backups/' AS URL, + 'Restore of ' + DB_NAME(db.resource_database_id) + ' database (' + (SELECT CAST(CAST(SUM(size * 8.0 / 1024 / 1024) AS BIGINT) AS NVARCHAR) FROM #MasterFiles WHERE database_id = db.resource_database_id) + '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, + s.login_name AS LoginName, + s.nt_user_name AS NTUserName, + s.[program_name] AS ProgramName, + s.[host_name] AS HostName, + db.[resource_database_id] AS DatabaseID, + DB_NAME(db.resource_database_id) AS DatabaseName, + 0 AS OpenTransactionCount, + r.query_hash + FROM sys.dm_exec_requests r + INNER JOIN sys.dm_exec_connections c ON r.session_id = c.session_id + INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id + INNER JOIN ( + SELECT DISTINCT request_session_id, resource_database_id + FROM sys.dm_tran_locks + WHERE resource_type = N'DATABASE' + AND request_mode = N'S' + AND request_status = N'GRANT') AS db ON s.session_id = db.request_session_id + CROSS APPLY sys.dm_exec_query_plan(r.plan_handle) pl + WHERE r.command LIKE 'RESTORE%' + AND s.program_name <> 'SQL Server Log Shipping' + AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs); + END /* SQL Server Internal Maintenance - Database File Growing - CheckID 4 */ IF @Seconds > 0 - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount) - SELECT 4 AS CheckID, - 1 AS Priority, - 'SQL Server Internal Maintenance' AS FindingGroup, - 'Database File Growing' AS Finding, - 'http://www.BrentOzar.com/go/instant' AS URL, - 'SQL Server is waiting for Windows to provide storage space for a database restore, a data file growth, or a log file growth. This task has been running since ' + CAST(r.start_time AS NVARCHAR(100)) + '.' + @LineFeed + 'Check the query plan (expert mode) to identify the database involved.' AS Details, - 'Unfortunately, you can''t stop this, but you can prevent it next time. Check out http://www.BrentOzar.com/go/instant for details.' AS HowToStopIt, - pl.query_plan AS QueryPlan, - r.start_time AS StartTime, - s.login_name AS LoginName, - s.nt_user_name AS NTUserName, - s.[program_name] AS ProgramName, - s.[host_name] AS HostName, - NULL AS DatabaseID, - NULL AS DatabaseName, - 0 AS OpenTransactionCount - FROM sys.dm_os_waiting_tasks t - INNER JOIN sys.dm_exec_connections c ON t.session_id = c.session_id - INNER JOIN sys.dm_exec_requests r ON t.session_id = r.session_id - INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id - CROSS APPLY sys.dm_exec_query_plan(r.plan_handle) pl - WHERE t.wait_type = 'PREEMPTIVE_OS_WRITEFILEGATHER' - AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs); + BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 4',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount) + SELECT 4 AS CheckID, + 1 AS Priority, + 'SQL Server Internal Maintenance' AS FindingGroup, + 'Database File Growing' AS Finding, + 'http://www.BrentOzar.com/go/instant' AS URL, + 'SQL Server is waiting for Windows to provide storage space for a database restore, a data file growth, or a log file growth. This task has been running since ' + CAST(r.start_time AS NVARCHAR(100)) + '.' + @LineFeed + 'Check the query plan (expert mode) to identify the database involved.' AS Details, + 'Unfortunately, you can''t stop this, but you can prevent it next time. Check out http://www.BrentOzar.com/go/instant for details.' AS HowToStopIt, + pl.query_plan AS QueryPlan, + r.start_time AS StartTime, + s.login_name AS LoginName, + s.nt_user_name AS NTUserName, + s.[program_name] AS ProgramName, + s.[host_name] AS HostName, + NULL AS DatabaseID, + NULL AS DatabaseName, + 0 AS OpenTransactionCount + FROM sys.dm_os_waiting_tasks t + INNER JOIN sys.dm_exec_connections c ON t.session_id = c.session_id + INNER JOIN sys.dm_exec_requests r ON t.session_id = r.session_id + INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id + CROSS APPLY sys.dm_exec_query_plan(r.plan_handle) pl + WHERE t.wait_type = 'PREEMPTIVE_OS_WRITEFILEGATHER' + AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs); + END /* Query Problems - Long-Running Query Blocking Others - CheckID 5 */ IF SERVERPROPERTY('Edition') <> 'SQL Azure' AND @Seconds > 0 AND EXISTS(SELECT * FROM sys.dm_os_waiting_tasks WHERE wait_type LIKE 'LCK%' AND wait_duration_ms > 30000) BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 5',10,1) WITH NOWAIT; + END + SET @StringToExecute = N'INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, QueryText, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, QueryHash) SELECT 5 AS CheckID, 1 AS Priority, @@ -1568,6 +1598,11 @@ BEGIN /* Query Problems - Plan Cache Erased Recently - CheckID 7 */ IF DATEADD(mi, -15, SYSDATETIME()) < (SELECT TOP 1 creation_time FROM sys.dm_exec_query_stats ORDER BY creation_time) BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 7',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) SELECT TOP 1 7 AS CheckID, 50 AS Priority, @@ -1587,38 +1622,44 @@ BEGIN /* Query Problems - Sleeping Query with Open Transactions - CheckID 8 */ IF @Seconds > 0 - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, OpenTransactionCount) - SELECT 8 AS CheckID, - 50 AS Priority, - 'Query Problems' AS FindingGroup, - 'Sleeping Query with Open Transactions' AS Finding, - 'http://www.brentozar.com/askbrent/sleeping-query-with-open-transactions/' AS URL, - 'Database: ' + DB_NAME(db.resource_database_id) + @LineFeed + 'Host: ' + s.[host_name] + @LineFeed + 'Program: ' + s.[program_name] + @LineFeed + 'Asleep with open transactions and locks since ' + CAST(s.last_request_end_time AS NVARCHAR(100)) + '. ' AS Details, - 'KILL ' + CAST(s.session_id AS NVARCHAR(100)) + ';' AS HowToStopIt, - s.last_request_start_time AS StartTime, - s.login_name AS LoginName, - s.nt_user_name AS NTUserName, - s.[program_name] AS ProgramName, - s.[host_name] AS HostName, - db.[resource_database_id] AS DatabaseID, - DB_NAME(db.resource_database_id) AS DatabaseName, - (SELECT TOP 1 [text] FROM sys.dm_exec_sql_text(c.most_recent_sql_handle)) AS QueryText, - sessions_with_transactions.open_transaction_count AS OpenTransactionCount - FROM (SELECT session_id, SUM(open_transaction_count) AS open_transaction_count FROM sys.dm_exec_requests WHERE open_transaction_count > 0 GROUP BY session_id) AS sessions_with_transactions - INNER JOIN sys.dm_exec_sessions s ON sessions_with_transactions.session_id = s.session_id - INNER JOIN sys.dm_exec_connections c ON s.session_id = c.session_id - INNER JOIN ( - SELECT DISTINCT request_session_id, resource_database_id - FROM sys.dm_tran_locks - WHERE resource_type = N'DATABASE' - AND request_mode = N'S' - AND request_status = N'GRANT' - AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.session_id = db.request_session_id - WHERE s.status = 'sleeping' - AND s.last_request_end_time < DATEADD(ss, -10, SYSDATETIME()) - AND EXISTS(SELECT * FROM sys.dm_tran_locks WHERE request_session_id = s.session_id - AND NOT (resource_type = N'DATABASE' AND request_mode = N'S' AND request_status = N'GRANT' AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE')); + BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 8',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, OpenTransactionCount) + SELECT 8 AS CheckID, + 50 AS Priority, + 'Query Problems' AS FindingGroup, + 'Sleeping Query with Open Transactions' AS Finding, + 'http://www.brentozar.com/askbrent/sleeping-query-with-open-transactions/' AS URL, + 'Database: ' + DB_NAME(db.resource_database_id) + @LineFeed + 'Host: ' + s.[host_name] + @LineFeed + 'Program: ' + s.[program_name] + @LineFeed + 'Asleep with open transactions and locks since ' + CAST(s.last_request_end_time AS NVARCHAR(100)) + '. ' AS Details, + 'KILL ' + CAST(s.session_id AS NVARCHAR(100)) + ';' AS HowToStopIt, + s.last_request_start_time AS StartTime, + s.login_name AS LoginName, + s.nt_user_name AS NTUserName, + s.[program_name] AS ProgramName, + s.[host_name] AS HostName, + db.[resource_database_id] AS DatabaseID, + DB_NAME(db.resource_database_id) AS DatabaseName, + (SELECT TOP 1 [text] FROM sys.dm_exec_sql_text(c.most_recent_sql_handle)) AS QueryText, + sessions_with_transactions.open_transaction_count AS OpenTransactionCount + FROM (SELECT session_id, SUM(open_transaction_count) AS open_transaction_count FROM sys.dm_exec_requests WHERE open_transaction_count > 0 GROUP BY session_id) AS sessions_with_transactions + INNER JOIN sys.dm_exec_sessions s ON sessions_with_transactions.session_id = s.session_id + INNER JOIN sys.dm_exec_connections c ON s.session_id = c.session_id + INNER JOIN ( + SELECT DISTINCT request_session_id, resource_database_id + FROM sys.dm_tran_locks + WHERE resource_type = N'DATABASE' + AND request_mode = N'S' + AND request_status = N'GRANT' + AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.session_id = db.request_session_id + WHERE s.status = 'sleeping' + AND s.last_request_end_time < DATEADD(ss, -10, SYSDATETIME()) + AND EXISTS(SELECT * FROM sys.dm_tran_locks WHERE request_session_id = s.session_id + AND NOT (resource_type = N'DATABASE' AND request_mode = N'S' AND request_status = N'GRANT' AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE')); + END /*Query Problems - Clients using implicit transactions - CheckID 37 */ IF @Seconds > 0 @@ -1626,6 +1667,11 @@ BEGIN AND @@VERSION NOT LIKE 'Microsoft SQL Server 2008%' AND @@VERSION NOT LIKE 'Microsoft SQL Server 2008 R2%' ) BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 37',10,1) WITH NOWAIT; + END + SET @StringToExecute = N'INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, OpenTransactionCount) SELECT 37 AS CheckId, 50 AS Priority, @@ -1662,37 +1708,48 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, /* Query Problems - Query Rolling Back - CheckID 9 */ IF @Seconds > 0 - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, QueryHash) - SELECT 9 AS CheckID, - 1 AS Priority, - 'Query Problems' AS FindingGroup, - 'Query Rolling Back' AS Finding, - 'http://www.BrentOzar.com/askbrent/rollback/' AS URL, - 'Rollback started at ' + CAST(r.start_time AS NVARCHAR(100)) + ', is ' + CAST(r.percent_complete AS NVARCHAR(100)) + '% complete.' AS Details, - 'Unfortunately, you can''t stop this. Whatever you do, don''t restart the server in an attempt to fix it - SQL Server will keep rolling back.' AS HowToStopIt, - r.start_time AS StartTime, - s.login_name AS LoginName, - s.nt_user_name AS NTUserName, - s.[program_name] AS ProgramName, - s.[host_name] AS HostName, - db.[resource_database_id] AS DatabaseID, - DB_NAME(db.resource_database_id) AS DatabaseName, - (SELECT TOP 1 [text] FROM sys.dm_exec_sql_text(c.most_recent_sql_handle)) AS QueryText, - r.query_hash - FROM sys.dm_exec_sessions s - INNER JOIN sys.dm_exec_connections c ON s.session_id = c.session_id - INNER JOIN sys.dm_exec_requests r ON s.session_id = r.session_id - LEFT OUTER JOIN ( - SELECT DISTINCT request_session_id, resource_database_id - FROM sys.dm_tran_locks - WHERE resource_type = N'DATABASE' - AND request_mode = N'S' - AND request_status = N'GRANT' - AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.session_id = db.request_session_id - WHERE r.status = 'rollback'; + BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 9',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, QueryHash) + SELECT 9 AS CheckID, + 1 AS Priority, + 'Query Problems' AS FindingGroup, + 'Query Rolling Back' AS Finding, + 'http://www.BrentOzar.com/askbrent/rollback/' AS URL, + 'Rollback started at ' + CAST(r.start_time AS NVARCHAR(100)) + ', is ' + CAST(r.percent_complete AS NVARCHAR(100)) + '% complete.' AS Details, + 'Unfortunately, you can''t stop this. Whatever you do, don''t restart the server in an attempt to fix it - SQL Server will keep rolling back.' AS HowToStopIt, + r.start_time AS StartTime, + s.login_name AS LoginName, + s.nt_user_name AS NTUserName, + s.[program_name] AS ProgramName, + s.[host_name] AS HostName, + db.[resource_database_id] AS DatabaseID, + DB_NAME(db.resource_database_id) AS DatabaseName, + (SELECT TOP 1 [text] FROM sys.dm_exec_sql_text(c.most_recent_sql_handle)) AS QueryText, + r.query_hash + FROM sys.dm_exec_sessions s + INNER JOIN sys.dm_exec_connections c ON s.session_id = c.session_id + INNER JOIN sys.dm_exec_requests r ON s.session_id = r.session_id + LEFT OUTER JOIN ( + SELECT DISTINCT request_session_id, resource_database_id + FROM sys.dm_tran_locks + WHERE resource_type = N'DATABASE' + AND request_mode = N'S' + AND request_status = N'GRANT' + AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.session_id = db.request_session_id + WHERE r.status = 'rollback'; + END /* Server Performance - Too Much Free Memory - CheckID 34 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 34',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) SELECT 34 AS CheckID, 50 AS Priority, @@ -1712,6 +1769,12 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, /* Server Performance - Target Memory Lower Than Max - CheckID 35 */ IF SERVERPROPERTY('Edition') <> 'SQL Azure' + BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 35',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) SELECT 35 AS CheckID, 10 AS Priority, @@ -1728,8 +1791,14 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, AND CAST(cMax.value_in_use AS BIGINT) >= 1.5 * (CAST(cTarget.cntr_value AS BIGINT) / 1024) AND CAST(cMax.value_in_use AS BIGINT) < 2147483647 /* Not set to default of unlimited */ AND CAST(cTarget.cntr_value AS BIGINT) < .8 * (SELECT available_physical_memory_kb FROM sys.dm_os_sys_memory); /* Target memory less than 80% of physical memory (in case they set max too high) */ + END /* Server Info - Database Size, Total GB - CheckID 21 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 21',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) SELECT 21 AS CheckID, 251 AS Priority, @@ -1742,6 +1811,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, WHERE database_id > 4; /* Server Info - Database Count - CheckID 22 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 22',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) SELECT 22 AS CheckID, 251 AS Priority, @@ -1754,6 +1828,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, WHERE database_id > 4; /* Server Info - Memory Grants pending - CheckID 39 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 39',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) SELECT 39 AS CheckID, 50 AS Priority, @@ -1773,6 +1852,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, WHERE PendingGrants.Details > 0; /* Server Info - Memory Grant/Workspace info - CheckID 40 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 40',10,1) WITH NOWAIT; + END + DECLARE @MaxWorkspace BIGINT SET @MaxWorkspace = (SELECT CAST(cntr_value AS BIGINT)/1024 FROM #PerfmonStats WHERE counter_name = N'Maximum Workspace Memory (KB)') @@ -1798,6 +1882,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, FROM sys.dm_exec_query_memory_grants AS Grants; /* Query Problems - Queries with high memory grants - CheckID 46 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 46',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, URL, QueryText, QueryPlan) SELECT 46 AS CheckID, 100 AS Priority, @@ -1825,6 +1914,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, /* Query Problems - Memory Leak in USERSTORE_TOKENPERM Cache - CheckID 45 */ IF EXISTS (SELECT * FROM sys.all_columns WHERE object_id = OBJECT_ID('sys.dm_os_memory_clerks') AND name = 'pages_kb') BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 45',10,1) WITH NOWAIT; + END + /* SQL 2012+ version */ SET @StringToExecute = N' INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, URL) @@ -1844,6 +1938,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, ELSE BEGIN /* Antiques Roadshow SQL 2008R2 - version */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 45 (Legacy version)',10,1) WITH NOWAIT; + END + SET @StringToExecute = N' INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, URL) SELECT 45 AS CheckID, @@ -1978,6 +2077,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, y.parallelism_skew; /* Queries in dm_exec_query_profiles showing signs of poor cardinality estimates - CheckID 42 */ + IF (@Debug = 1) + BEGIN + RAISERROR(''Running CheckID 42'',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, OpenTransactionCount, QueryHash, QueryPlan) SELECT 42 AS CheckID, @@ -2024,6 +2128,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, SET @StringToExecute = @StringToExecute + N'; /* Queries in dm_exec_query_profiles showing signs of unbalanced parallelism - CheckID 43 */ + IF (@Debug = 1) + BEGIN + RAISERROR(''Running CheckID 43'',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, OpenTransactionCount, QueryHash, QueryPlan) SELECT 43 AS CheckID, @@ -2069,7 +2178,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, SET @StringToExecute = @StringToExecute + N';'; - EXECUTE sp_executesql @StringToExecute; + EXECUTE sp_executesql @StringToExecute, N'@Debug BIT',@Debug = @Debug; END END @@ -2082,6 +2191,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, We get this data from the ring buffers, and it's only updated once per minute, so might as well get it now - whereas if we're checking 30+ seconds, it might get updated by the end of our sp_BlitzFirst session. */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 24',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) SELECT 24, 50, 'Server Performance', 'High CPU Utilization', CAST(100 - SystemIdle AS NVARCHAR(20)) + N'%.', 100 - SystemIdle, 'http://www.BrentOzar.com/go/cpu' FROM ( @@ -2097,6 +2211,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, WHERE 100 - SystemIdle >= 50; /* CPU Utilization - CheckID 23 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 23',10,1) WITH NOWAIT; + END + IF SERVERPROPERTY('Edition') <> 'SQL Azure' WITH y AS @@ -2138,6 +2257,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, /* Highlight if non SQL processes are using >25% CPU - CheckID 28 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 28',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) SELECT 28, 50, 'Server Performance', 'High CPU Utilization - Not SQL', CONVERT(NVARCHAR(100),100 - (y.SQLUsage + y.SystemIdle)) + N'% - Other Processes (not SQL Server) are using this much CPU. This may impact on the performance of your SQL Server instance', 100 - (y.SQLUsage + y.SystemIdle), 'http://www.BrentOzar.com/go/cpu' FROM ( @@ -2156,6 +2280,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, END; /* IF @Seconds < 30 */ /* Query Problems - Statistics Updated Recently - CheckID 44 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 44',10,1) WITH NOWAIT; + END + IF 20 >= (SELECT COUNT(*) FROM sys.databases WHERE name NOT IN ('master', 'model', 'msdb', 'tempdb')) BEGIN CREATE TABLE #UpdatedStats (HowToStopIt NVARCHAR(4000), RowsForSorting BIGINT); @@ -2343,6 +2472,10 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, /* Query Stats - If we're within 10 seconds of our projected finish time, do the plan cache analysis. - CheckID 18 */ IF DATEDIFF(ss, @FinishSampleTime, SYSDATETIMEOFFSET()) > 10 AND @CheckProcedureCache = 1 BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 18',10,1) WITH NOWAIT; + END INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) VALUES (18, 210, 'Query Stats', 'Plan Cache Analysis Skipped', 'http://www.BrentOzar.com/go/topqueries', @@ -2482,6 +2615,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, INNER JOIN qsTop ON qs.ID = qsTop.ID; /* Query Stats - Most Resource-Intensive Queries - CheckID 17 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 17',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, QueryText, QueryStatsNowID, QueryStatsFirstID, PlanHandle, QueryHash) SELECT 17, 210, 'Query Stats', 'Most Resource-Intensive Queries', 'http://www.BrentOzar.com/go/topqueries', 'Query stats during the sample:' + @LineFeed + @@ -2541,6 +2679,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, /* Wait Stats - CheckID 6 */ /* Compare the current wait stats to the sample we took at the start, and insert the top 10 waits. */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 6',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, DetailsInt) SELECT TOP 10 6 AS CheckID, 200 AS Priority, @@ -2556,6 +2699,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, ORDER BY (wNow.wait_time_ms - COALESCE(wBase.wait_time_ms,0)) DESC; /* Server Performance - Poison Wait Detected - CheckID 30 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 30',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, DetailsInt) SELECT 30 AS CheckID, 10 AS Priority, @@ -2574,6 +2722,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, /* Server Performance - Slow Data File Reads - CheckID 11 */ IF EXISTS (SELECT * FROM #BlitzFirstResults WHERE Finding LIKE 'PAGEIOLATCH%') BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 11',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, DatabaseID, DatabaseName) SELECT TOP 10 11 AS CheckID, 50 AS Priority, @@ -2599,6 +2752,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, /* Server Performance - Slow Log File Writes - CheckID 12 */ IF EXISTS (SELECT * FROM #BlitzFirstResults WHERE Finding LIKE 'WRITELOG%') BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 12',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, DatabaseID, DatabaseName) SELECT TOP 10 12 AS CheckID, 50 AS Priority, @@ -2623,6 +2781,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, /* SQL Server Internal Maintenance - Log File Growing - CheckID 13 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 13',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) SELECT 13 AS CheckID, 1 AS Priority, @@ -2640,6 +2803,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, /* SQL Server Internal Maintenance - Log File Shrinking - CheckID 14 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 14',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) SELECT 14 AS CheckID, 1 AS Priority, @@ -2656,6 +2824,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, AND value_delta > 0; /* Query Problems - Compilations/Sec High - CheckID 15 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 15',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) SELECT 15 AS CheckID, 50 AS Priority, @@ -2677,6 +2850,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, AND (psComp.value_delta * 10) > ps.value_delta; /* Compilations are more than 10% of batch requests per second */ /* Query Problems - Re-Compilations/Sec High - CheckID 16 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 16',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) SELECT 16 AS CheckID, 50 AS Priority, @@ -2698,6 +2876,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, AND (psComp.value_delta * 10) > ps.value_delta; /* Recompilations are more than 10% of batch requests per second */ /* Table Problems - Forwarded Fetches/Sec High - CheckID 29 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 29',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) SELECT 29 AS CheckID, 40 AS Priority, @@ -2736,6 +2919,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, END /* In-Memory OLTP - Garbage Collection in Progress - CheckID 31 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 31',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) SELECT 31 AS CheckID, 50 AS Priority, @@ -2754,6 +2942,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, AND ps.value_delta > (100 * @Seconds); /* Ignore servers sitting idle */ /* In-Memory OLTP - Transactions Aborted - CheckID 32 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 32',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) SELECT 32 AS CheckID, 100 AS Priority, @@ -2771,6 +2964,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, AND ps.value_delta > (10 * @Seconds); /* Ignore servers sitting idle */ /* Query Problems - Suboptimal Plans/Sec High - CheckID 33 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 33',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) SELECT 32 AS CheckID, 100 AS Priority, @@ -2789,6 +2987,12 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, /* Azure Performance - Database is Maxed Out - CheckID 41 */ IF SERVERPROPERTY('Edition') = 'SQL Azure' + BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 41',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) SELECT 41 AS CheckID, 10 AS Priority, @@ -2809,8 +3013,14 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, OR avg_log_write_percent >=90 OR max_worker_percent >= 90 OR max_session_percent >= 90); + END /* Server Info - Batch Requests per Sec - CheckID 19 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 19',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, DetailsInt) SELECT 19 AS CheckID, 250 AS Priority, @@ -2831,39 +3041,58 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, /* Server Info - SQL Compilations/sec - CheckID 25 */ IF @ExpertMode = 1 - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, DetailsInt) - SELECT 25 AS CheckID, - 250 AS Priority, - 'Server Info' AS FindingGroup, - 'SQL Compilations per Sec' AS Finding, - 'http://www.BrentOzar.com/go/measure' AS URL, - CAST(ps.value_delta / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS NVARCHAR(20)) AS Details, - ps.value_delta / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS DetailsInt - FROM #PerfmonStats ps - INNER JOIN #PerfmonStats ps1 ON ps.object_name = ps1.object_name AND ps.counter_name = ps1.counter_name AND ps1.Pass = 1 - WHERE ps.Pass = 2 - AND ps.object_name = @ServiceName + ':SQL Statistics' - AND ps.counter_name = 'SQL Compilations/sec'; + BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 25',10,1) WITH NOWAIT; + END + + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, DetailsInt) + SELECT 25 AS CheckID, + 250 AS Priority, + 'Server Info' AS FindingGroup, + 'SQL Compilations per Sec' AS Finding, + 'http://www.BrentOzar.com/go/measure' AS URL, + CAST(ps.value_delta / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS NVARCHAR(20)) AS Details, + ps.value_delta / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS DetailsInt + FROM #PerfmonStats ps + INNER JOIN #PerfmonStats ps1 ON ps.object_name = ps1.object_name AND ps.counter_name = ps1.counter_name AND ps1.Pass = 1 + WHERE ps.Pass = 2 + AND ps.object_name = @ServiceName + ':SQL Statistics' + AND ps.counter_name = 'SQL Compilations/sec'; + END /* Server Info - SQL Re-Compilations/sec - CheckID 26 */ IF @ExpertMode = 1 - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, DetailsInt) - SELECT 26 AS CheckID, - 250 AS Priority, - 'Server Info' AS FindingGroup, - 'SQL Re-Compilations per Sec' AS Finding, - 'http://www.BrentOzar.com/go/measure' AS URL, - CAST(ps.value_delta / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS NVARCHAR(20)) AS Details, - ps.value_delta / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS DetailsInt - FROM #PerfmonStats ps - INNER JOIN #PerfmonStats ps1 ON ps.object_name = ps1.object_name AND ps.counter_name = ps1.counter_name AND ps1.Pass = 1 - WHERE ps.Pass = 2 - AND ps.object_name = @ServiceName + ':SQL Statistics' - AND ps.counter_name = 'SQL Re-Compilations/sec'; + BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 26',10,1) WITH NOWAIT; + END + + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, DetailsInt) + SELECT 26 AS CheckID, + 250 AS Priority, + 'Server Info' AS FindingGroup, + 'SQL Re-Compilations per Sec' AS Finding, + 'http://www.BrentOzar.com/go/measure' AS URL, + CAST(ps.value_delta / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS NVARCHAR(20)) AS Details, + ps.value_delta / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS DetailsInt + FROM #PerfmonStats ps + INNER JOIN #PerfmonStats ps1 ON ps.object_name = ps1.object_name AND ps.counter_name = ps1.counter_name AND ps1.Pass = 1 + WHERE ps.Pass = 2 + AND ps.object_name = @ServiceName + ':SQL Statistics' + AND ps.counter_name = 'SQL Re-Compilations/sec'; + END /* Server Info - Wait Time per Core per Sec - CheckID 20 */ IF @Seconds > 0 BEGIN; + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 20',10,1) WITH NOWAIT; + END; + WITH waits1(SampleTime, waits_ms) AS (SELECT SampleTime, SUM(ws1.wait_time_ms) FROM #WaitStats ws1 WHERE ws1.Pass = 1 GROUP BY SampleTime), waits2(SampleTime, waits_ms) AS (SELECT SampleTime, SUM(ws2.wait_time_ms) FROM #WaitStats ws2 WHERE ws2.Pass = 2 GROUP BY SampleTime), cores(cpu_count) AS (SELECT SUM(1) FROM sys.dm_os_schedulers WHERE status = 'VISIBLE ONLINE' AND is_online = 1) @@ -2885,8 +3114,13 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, as well get it now - whereas if we're checking 30+ seconds, it might get updated by the end of our sp_BlitzFirst session. */ IF @Seconds >= 30 - BEGIN + BEGIN /* Server Performance - High CPU Utilization CheckID 24 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 24',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) SELECT 24, 50, 'Server Performance', 'High CPU Utilization', CAST(100 - SystemIdle AS NVARCHAR(20)) + N'%. Ring buffer details: ' + CAST(record AS NVARCHAR(4000)), 100 - SystemIdle, 'http://www.BrentOzar.com/go/cpu' FROM ( @@ -2902,6 +3136,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, WHERE 100 - SystemIdle >= 50; /* Server Performance - CPU Utilization CheckID 23 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 23',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) SELECT 23, 250, 'Server Info', 'CPU Utilization', CAST(100 - SystemIdle AS NVARCHAR(20)) + N'%. Ring buffer details: ' + CAST(record AS NVARCHAR(4000)), 100 - SystemIdle, 'http://www.BrentOzar.com/go/cpu' FROM ( @@ -2915,7 +3154,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, ORDER BY timestamp DESC) AS rb ) AS y; - END; /* IF @Seconds >= 30 */ + END; /* IF @Seconds >= 30 */ /* If we didn't find anything, apologize. */ @@ -2975,6 +3214,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, ); /* Outdated sp_BlitzFirst - sp_BlitzFirst is Over 6 Months Old - CheckID 27 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 27',10,1) WITH NOWAIT; + END + IF DATEDIFF(MM, @VersionDate, SYSDATETIMEOFFSET()) > 6 BEGIN INSERT INTO #BlitzFirstResults @@ -3066,6 +3310,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, ELSE BEGIN /* No sp_BlitzCache found, or it's outdated - CheckID 36 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 36',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults ( CheckID , Priority , From 94cd5a493f1d2b1e5a9e1b6168dcd3f8109c61c6 Mon Sep 17 00:00:00 2001 From: AdeDBA <33764798+Adedba@users.noreply.github.com> Date: Thu, 7 Jan 2021 15:28:33 +0000 Subject: [PATCH 060/662] Added BlitzWho and fixed table validations #2713 Added BlitzWho output and fixed table validations --- sp_BlitzAnalysis.sql | 398 +++++++++++++++++++++++++++---------------- 1 file changed, 252 insertions(+), 146 deletions(-) diff --git a/sp_BlitzAnalysis.sql b/sp_BlitzAnalysis.sql index 5ba361588..ba201ae18 100644 --- a/sp_BlitzAnalysis.sql +++ b/sp_BlitzAnalysis.sql @@ -42,7 +42,7 @@ ALTER PROCEDURE [dbo].[sp_BlitzAnalysis] ( AS SET NOCOUNT ON; -SELECT @Version = '1.000', @VersionDate = '20201113'; +SELECT @Version = '1.000', @VersionDate = '20210107'; IF(@VersionCheckMode = 1) BEGIN @@ -85,7 +85,7 @@ SET @FullOutputTableNameFileStats = QUOTENAME(@OutputDatabaseName)+N'.'+QUOTENAM SET @FullOutputTableNamePerfmonStats = QUOTENAME(@OutputDatabaseName)+N'.'+QUOTENAME(@OutputSchemaName)+N'.'+QUOTENAME(@OutputTableNamePerfmonStats+N'_Actuals'); SET @FullOutputTableNameWaitStats = QUOTENAME(@OutputDatabaseName)+N'.'+QUOTENAME(@OutputSchemaName)+N'.'+QUOTENAME(@OutputTableNameWaitStats+N'_Deltas'); SET @FullOutputTableNameBlitzCache = QUOTENAME(@OutputDatabaseName)+N'.'+QUOTENAME(@OutputSchemaName)+N'.'+QUOTENAME(@OutputTableNameBlitzCache); -SET @FullOutputTableNameBlitzWho = QUOTENAME(@OutputDatabaseName)+N'.'+QUOTENAME(@OutputSchemaName)+N'.'+QUOTENAME(@OutputTableNameBlitzWho); +SET @FullOutputTableNameBlitzWho = QUOTENAME(@OutputDatabaseName)+N'.'+QUOTENAME(@OutputSchemaName)+N'.'+QUOTENAME(@OutputTableNameBlitzWho+N'_Deltas'); IF OBJECT_ID('tempdb.dbo.#BlitzFirstCounts') IS NOT NULL BEGIN @@ -148,12 +148,6 @@ SELECT /* BlitzFirst data */ -IF (OBJECT_ID(@FullOutputTableNameBlitzFirst) IS NULL) -BEGIN - RAISERROR('Table provided for BlitzFirst data: %s does not exist',10,0,@FullOutputTableNameBlitzFirst); - SELECT N'No BlitzFirst data available as the table cannot be found'; -END - SET @Sql = N' INSERT INTO #BlitzFirstCounts ([Priority],[FindingsGroup],[Finding],[TotalOccurrences],[FirstOccurrence],[LastOccurrence]) SELECT @@ -212,25 +206,35 @@ IF (@Debug = 1) BEGIN PRINT @Sql; END - -EXEC sp_executesql @Sql, -N'@FromDate DATETIMEOFFSET(7), -@ToDate DATETIMEOFFSET(7), -@Servername NVARCHAR(128), -@MaxBlitzFirstPriority INT', -@FromDate=@FromDate, -@ToDate=@ToDate, -@Servername=@Servername, -@MaxBlitzFirstPriority = @MaxBlitzFirstPriority; +IF (OBJECT_ID(@FullOutputTableNameBlitzFirst) IS NULL) +BEGIN + IF (@OutputTableNameBlitzFirst IS NULL) + BEGIN + RAISERROR('BlitzFirst data skipped',10,0); + SELECT N'Skipped logged BlitzFirst data as NULL was passed to parameter @OutputTableNameBlitzFirst'; + END + ELSE + BEGIN + RAISERROR('Table provided for BlitzFirst data: %s does not exist',10,0,@FullOutputTableNameBlitzFirst); + SELECT N'No BlitzFirst data available as the table cannot be found'; + END -/* Blitz WaitStats data */ -IF (OBJECT_ID(@FullOutputTableNameWaitStats) IS NULL) +END +ELSE /* Table exists then run the query */ BEGIN - RAISERROR('Table provided for wait stats data: %s does not exist',10,0,@FullOutputTableNameWaitStats); - SELECT N'No wait stats data available as the table cannot be found'; -END + EXEC sp_executesql @Sql, + N'@FromDate DATETIMEOFFSET(7), + @ToDate DATETIMEOFFSET(7), + @Servername NVARCHAR(128), + @MaxBlitzFirstPriority INT', + @FromDate=@FromDate, + @ToDate=@ToDate, + @Servername=@Servername, + @MaxBlitzFirstPriority = @MaxBlitzFirstPriority; +END +/* Blitz WaitStats data */ SET @Sql = N'SELECT [ServerName], [CheckDate], @@ -277,26 +281,33 @@ BEGIN PRINT @Sql; END - -EXEC sp_executesql @Sql, -N'@FromDate DATETIMEOFFSET(7), -@ToDate DATETIMEOFFSET(7), -@Servername NVARCHAR(128), -@WaitStatsTop TINYINT', -@FromDate=@FromDate, -@ToDate=@ToDate, -@Servername=@Servername, -@WaitStatsTop=@WaitStatsTop; - +IF (OBJECT_ID(@FullOutputTableNameWaitStats) IS NULL) +BEGIN + IF (@OutputTableNameWaitStats IS NULL) + BEGIN + RAISERROR('Wait stats data skipped',10,0); + SELECT N'Skipped logged wait stats data as NULL was passed to parameter @OutputTableNameWaitStats'; + END + ELSE + BEGIN + RAISERROR('Table provided for wait stats data: %s does not exist',10,0,@FullOutputTableNameWaitStats); + SELECT N'No wait stats data available as the table cannot be found'; + END +END +ELSE /* Table exists then run the query */ +BEGIN + EXEC sp_executesql @Sql, + N'@FromDate DATETIMEOFFSET(7), + @ToDate DATETIMEOFFSET(7), + @Servername NVARCHAR(128), + @WaitStatsTop TINYINT', + @FromDate=@FromDate, + @ToDate=@ToDate, + @Servername=@Servername, + @WaitStatsTop=@WaitStatsTop; +END /* BlitzFileStats info */ -IF (OBJECT_ID(@FullOutputTableNameFileStats) IS NULL) -BEGIN - RAISERROR('Table provided for FileStats data: %s does not exist',10,0,@FullOutputTableNameFileStats); - SELECT N'No FileStats data available as the table cannot be found'; -END - - SET @Sql = N' SELECT [ServerName], @@ -337,27 +348,35 @@ BEGIN PRINT @Sql; END - -EXEC sp_executesql @Sql, -N'@FromDate DATETIMEOFFSET(7), -@ToDate DATETIMEOFFSET(7), -@Servername NVARCHAR(128), -@ReadLatencyThreshold INT, -@WriteLatencyThreshold INT', -@FromDate=@FromDate, -@ToDate=@ToDate, -@Servername=@Servername, -@ReadLatencyThreshold = @ReadLatencyThreshold, -@WriteLatencyThreshold = @WriteLatencyThreshold; - - -/* Blitz Perfmon stats*/ -IF (OBJECT_ID(@FullOutputTableNamePerfmonStats) IS NULL) +IF (OBJECT_ID(@FullOutputTableNameFileStats) IS NULL) +BEGIN + IF (@OutputTableNameFileStats IS NULL) + BEGIN + RAISERROR('File stats data skipped',10,0); + SELECT N'Skipped logged File stats data as NULL was passed to parameter @OutputTableNameFileStats'; + END + ELSE + BEGIN + RAISERROR('Table provided for FileStats data: %s does not exist',10,0,@FullOutputTableNameFileStats); + SELECT N'No File stats data available as the table cannot be found'; + END +END +ELSE /* Table exists then run the query */ BEGIN - RAISERROR('Table provided for Perfmon stats data: %s does not exist',10,0,@FullOutputTableNamePerfmonStats); - SELECT N'No Perfmon data available as the table cannot be found'; -END + EXEC sp_executesql @Sql, + N'@FromDate DATETIMEOFFSET(7), + @ToDate DATETIMEOFFSET(7), + @Servername NVARCHAR(128), + @ReadLatencyThreshold INT, + @WriteLatencyThreshold INT', + @FromDate=@FromDate, + @ToDate=@ToDate, + @Servername=@Servername, + @ReadLatencyThreshold = @ReadLatencyThreshold, + @WriteLatencyThreshold = @WriteLatencyThreshold; +END +/* Blitz Perfmon stats*/ SET @Sql = N' SELECT [ServerName] @@ -379,23 +398,31 @@ BEGIN PRINT @Sql; END - -EXEC sp_executesql @Sql, -N'@FromDate DATETIMEOFFSET(7), -@ToDate DATETIMEOFFSET(7), -@Servername NVARCHAR(128)', -@FromDate=@FromDate, -@ToDate=@ToDate, -@Servername=@Servername; - - -/* Blitz cache data */ -IF (OBJECT_ID(@FullOutputTableNameBlitzCache) IS NULL) +IF (OBJECT_ID(@FullOutputTableNamePerfmonStats) IS NULL) +BEGIN + IF (@OutputTableNamePerfmonStats IS NULL) + BEGIN + RAISERROR('Perfmon stats data skipped',10,0); + SELECT N'Skipped logged Perfmon stats data as NULL was passed to parameter @OutputTableNamePerfmonStats'; + END + ELSE + BEGIN + RAISERROR('Table provided for Perfmon stats data: %s does not exist',10,0,@FullOutputTableNamePerfmonStats); + SELECT N'No Perfmon data available as the table cannot be found'; + END +END +ELSE /* Table exists then run the query */ BEGIN - RAISERROR('Table provided for BlitzCache data: %s does not exist',10,0,@FullOutputTableNameBlitzCache); - SELECT N'No BlitzCache data available as the table cannot be found'; -END + EXEC sp_executesql @Sql, + N'@FromDate DATETIMEOFFSET(7), + @ToDate DATETIMEOFFSET(7), + @Servername NVARCHAR(128)', + @FromDate=@FromDate, + @ToDate=@ToDate, + @Servername=@Servername; +END +/* Blitz cache data */ RAISERROR('Sortorder for BlitzCache data: %s',0,0,@BlitzCacheSortorder) WITH NOWAIT; /* Set intial CTE */ @@ -583,88 +610,167 @@ BEGIN PRINT SUBSTRING(@Sql, 40000, 44000); END -EXEC sp_executesql @Sql, -N'@Servername NVARCHAR(128), -@BlitzCacheSortorder NVARCHAR(20), -@FromDate DATETIMEOFFSET(7), -@ToDate DATETIMEOFFSET(7)', -@Servername = @Servername, -@BlitzCacheSortorder = @BlitzCacheSortorder, -@FromDate = @FromDate, -@ToDate = @ToDate; - +IF (OBJECT_ID(@FullOutputTableNameBlitzCache) IS NULL) +BEGIN + IF (@OutputTableNameBlitzCache IS NULL) + BEGIN + RAISERROR('BlitzCache data skipped',10,0); + SELECT N'Skipped logged BlitzCache data as NULL was passed to parameter @OutputTableNameBlitzCache'; + END + ELSE + BEGIN + RAISERROR('Table provided for BlitzCache data: %s does not exist',10,0,@FullOutputTableNameBlitzCache); + SELECT N'No BlitzCache data available as the table cannot be found'; + END +END +ELSE /* Table exists then run the query */ +BEGIN + EXEC sp_executesql @Sql, + N'@Servername NVARCHAR(128), + @BlitzCacheSortorder NVARCHAR(20), + @FromDate DATETIMEOFFSET(7), + @ToDate DATETIMEOFFSET(7)', + @Servername = @Servername, + @BlitzCacheSortorder = @BlitzCacheSortorder, + @FromDate = @FromDate, + @ToDate = @ToDate; +END -/* +/* BlitzWho data */ SET @Sql = N' -SELECT -[ServerName], -[CheckDate], -CASE - WHEN [io_stall_read_ms_average] > @ReadLatencyThreshold THEN ''Yes'' - WHEN [io_stall_write_ms_average] > @WriteLatencyThreshold THEN ''Yes'' - ELSE ''No'' -END AS [io_stall_ms_breached], -[DatabaseName], -[FileID], -[FileLogicalName], -[TypeDesc], -[PhysicalName], -[SizeOnDiskMB], -[ElapsedSeconds], -[SizeOnDiskMBgrowth], -[io_stall_read_ms], -[io_stall_read_ms_average], -@ReadLatencyThreshold AS [is_stall_read_ms_threshold], -[num_of_reads], -[megabytes_read], -[io_stall_write_ms], -[io_stall_write_ms_average], -@WriteLatencyThreshold AS [io_stall_write_ms_average], -[num_of_writes], -[megabytes_written] -FROM '+@FullOutputTableNameFileStats+N' -WHERE [ServerName] = @Servername -AND [CheckDate] BETWEEN @FromDate AND @ToDate -ORDER BY -[CheckDate] ASC, -([io_stall_read_ms_average]+[io_stall_write_ms_average]) DESC -OPTION (RECOMPILE);' -*/ - - - - - - -/* -/* Blitz cache data */ -IF (OBJECT_ID(@FullOutputTableNameFileStats) IS NULL) -BEGIN - RAISERROR('Table provided for FileStats data: %s does not exist',10,0,@FullOutputTableNameFileStats); - SELECT N'No FileStats data available as the table cannot be found'; -END - -SET @Sql - -RAISERROR('Getting FileStats info from %s',0,0,@FullOutputTableNameFileStats) WITH NOWAIT; +SELECT [ServerName] + ,[CheckDate] + ,[elapsed_time] + ,[session_id] + ,[database_name] + ,[query_text_snippet] + ,[query_plan] + ,[live_query_plan] + ,[query_cost] + ,[status] + ,[wait_info] + ,[top_session_waits] + ,[blocking_session_id] + ,[open_transaction_count] + ,[is_implicit_transaction] + ,[nt_domain] + ,[host_name] + ,[login_name] + ,[nt_user_name] + ,[program_name] + ,[fix_parameter_sniffing] + ,[client_interface_name] + ,[login_time] + ,[start_time] + ,[request_time] + ,[request_cpu_time] + ,[degree_of_parallelism] + ,[request_logical_reads] + ,[Logical_Reads_MB] + ,[request_writes] + ,[Logical_Writes_MB] + ,[request_physical_reads] + ,[Physical_reads_MB] + ,[session_cpu] + ,[session_logical_reads] + ,[session_logical_reads_MB] + ,[session_physical_reads] + ,[session_physical_reads_MB] + ,[session_writes] + ,[session_writes_MB] + ,[tempdb_allocations_mb] + ,[memory_usage] + ,[estimated_completion_time] + ,[percent_complete] + ,[deadlock_priority] + ,[transaction_isolation_level] + ,[last_dop] + ,[min_dop] + ,[max_dop] + ,[last_grant_kb] + ,[min_grant_kb] + ,[max_grant_kb] + ,[last_used_grant_kb] + ,[min_used_grant_kb] + ,[max_used_grant_kb] + ,[last_ideal_grant_kb] + ,[min_ideal_grant_kb] + ,[max_ideal_grant_kb] + ,[last_reserved_threads] + ,[min_reserved_threads] + ,[max_reserved_threads] + ,[last_used_threads] + ,[min_used_threads] + ,[max_used_threads] + ,[grant_time] + ,[requested_memory_kb] + ,[grant_memory_kb] + ,[is_request_granted] + ,[required_memory_kb] + ,[query_memory_grant_used_memory_kb] + ,[ideal_memory_kb] + ,[is_small] + ,[timeout_sec] + ,[resource_semaphore_id] + ,[wait_order] + ,[wait_time_ms] + ,[next_candidate_for_memory_grant] + ,[target_memory_kb] + ,[max_target_memory_kb] + ,[total_memory_kb] + ,[available_memory_kb] + ,[granted_memory_kb] + ,[query_resource_semaphore_used_memory_kb] + ,[grantee_count] + ,[waiter_count] + ,[timeout_error_count] + ,[forced_grant_count] + ,[workload_group_name] + ,[resource_pool_name] + ,[context_info] + ,[query_hash] + ,[query_plan_hash] + ,[sql_handle] + ,[plan_handle] + ,[statement_start_offset] + ,[statement_end_offset] + FROM '+@FullOutputTableNameBlitzWho+N' + WHERE [ServerName] = @Servername + AND [CheckDate] BETWEEN @FromDate AND @ToDate;'; + +RAISERROR('Getting BlitzWho info from %s',0,0,@FullOutputTableNameBlitzWho) WITH NOWAIT; IF (@Debug = 1) BEGIN PRINT @Sql; END +IF (OBJECT_ID(@FullOutputTableNameBlitzWho) IS NULL) +BEGIN + IF (@OutputTableNameBlitzWho IS NULL) + BEGIN + RAISERROR('BlitzWho data skipped',10,0); + SELECT N'Skipped logged BlitzWho data as NULL was passed to parameter @OutputTableNameBlitzWho'; + END + ELSE + BEGIN + RAISERROR('Table provided for BlitzWho data: %s does not exist',10,0,@FullOutputTableNameBlitzWho); + SELECT N'No BlitzWho data available as the table cannot be found'; + END +END +ELSE +BEGIN + EXEC sp_executesql @Sql, + N'@FromDate DATETIMEOFFSET(7), + @ToDate DATETIMEOFFSET(7), + @Servername NVARCHAR(128)', + @FromDate=@FromDate, + @ToDate=@ToDate, + @Servername=@Servername; +END -EXEC sp_executesql @Sql, -N'@FromDate DATETIMEOFFSET(7), -@ToDate DATETIMEOFFSET(7), -@Servername NVARCHAR(128)', -@FromDate=@FromDate, -@ToDate=@ToDate, -@Servername=@Servername; - -*/ GO From b2e2312ed2505f1db61d4dd8249fde8183d5fdc6 Mon Sep 17 00:00:00 2001 From: AdeDBA <33764798+Adedba@users.noreply.github.com> Date: Thu, 7 Jan 2021 15:51:05 +0000 Subject: [PATCH 061/662] Update sp_BlitzAnalysis.sql #2713 - Added BringThePain to warn users that BlitzCacheSortorder = 'all' AND querying a date range with more than 4 hours of information could be bad. BringThePain needs to be set to 1 to let it happen --- sp_BlitzAnalysis.sql | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/sp_BlitzAnalysis.sql b/sp_BlitzAnalysis.sql index ba201ae18..71041804e 100644 --- a/sp_BlitzAnalysis.sql +++ b/sp_BlitzAnalysis.sql @@ -37,6 +37,7 @@ ALTER PROCEDURE [dbo].[sp_BlitzAnalysis] ( @Version VARCHAR(30) = NULL, @VersionDate DATETIME = NULL, @VersionCheckMode BIT = 0, +@BringThePain BIT = 0, @Debug BIT = 0 ) AS @@ -138,7 +139,14 @@ END IF (@OutputSchemaName IS NULL) BEGIN SET @OutputSchemaName = 'dbo'; -END +END + +IF(@BlitzCacheSortorder = 'all' AND DATEDIFF(HOUR,@FromDate,@ToDate) > 4 AND @BringThePain = 0) +BEGIN + RAISERROR('Wow! hold up now, are you sure you wanna do this? Are sure you want to query over 4 hours of data with @BlitzCacheSortorder set to ''all''? IF you do then set @BringThePain = 1 but I gotta warn you this might hurt a bit!',11,1) WITH NOWAIT; + RETURN; +END + SELECT @Servername AS [ServerToReportOn], @@ -738,7 +746,8 @@ SELECT [ServerName] ,[statement_end_offset] FROM '+@FullOutputTableNameBlitzWho+N' WHERE [ServerName] = @Servername - AND [CheckDate] BETWEEN @FromDate AND @ToDate;'; + AND [CheckDate] BETWEEN @FromDate AND @ToDate + ORDER BY [CheckDate] ASC;'; RAISERROR('Getting BlitzWho info from %s',0,0,@FullOutputTableNameBlitzWho) WITH NOWAIT; From 5b8f1cca941e15828e147e4c8eb4b89ff3652e51 Mon Sep 17 00:00:00 2001 From: Adrian Buckman <33764798+Adedba@users.noreply.github.com> Date: Sat, 9 Jan 2021 23:43:28 +0000 Subject: [PATCH 062/662] Update sp_BlitzAnalysis.sql Added memory grant and spills to the included sort orders list - work work required to filter correctly, this commit is just to include them. --- sp_BlitzAnalysis.sql | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/sp_BlitzAnalysis.sql b/sp_BlitzAnalysis.sql index 71041804e..6e9c7ead8 100644 --- a/sp_BlitzAnalysis.sql +++ b/sp_BlitzAnalysis.sql @@ -445,7 +445,7 @@ AND [CheckDate] BETWEEN @FromDate AND @ToDate /* Append additional CTEs based on sortorder */ SET @Sql += ( -SELECT N',' +SELECT CAST(N',' AS NVARCHAR(MAX)) +@NewLine +[SortOptions].[Aliasname]+N' AS ( SELECT @@ -568,7 +568,9 @@ FROM (VALUES (N'reads',N'TopReads',N'TotalReads'), (N'writes',N'TopWrites',N'TotalWrites'), (N'duration',N'TopDuration',N'TotalDuration'), - (N'executions',N'TopExecutions',N'ExecutionCount') + (N'executions',N'TopExecutions',N'ExecutionCount'), + (N'memory grant',N'TopMemoryGrants',N'MaxGrantKB'), + (N'spills',N'TopSpills',N'MaxSpills') ) SortOptions(Sortorder,Aliasname,Columnname) WHERE [SortOptions].[Sortorder] = ISNULL(NULLIF(@BlitzCacheSortorder,N'all'),[SortOptions].[Sortorder]) FOR XML PATH(N''), TYPE).value(N'.[1]', N'NVARCHAR(MAX)') @@ -587,7 +589,9 @@ FROM (VALUES (N'reads',N'TopReads',N'TotalReads'), (N'writes',N'TopWrites',N'TotalWrites'), (N'duration',N'TopDuration',N'TotalDuration'), - (N'executions',N'TopExecutions',N'ExecutionCount') + (N'executions',N'TopExecutions',N'ExecutionCount'), + (N'memory grant',N'TopMemoryGrants',N'MaxGrantKB'), + (N'spills',N'TopSpills',N'MaxSpills') ) SortOptions(Sortorder,Aliasname,Columnname) WHERE [SortOptions].[Sortorder] = ISNULL(NULLIF(@BlitzCacheSortorder,N'all'),[SortOptions].[Sortorder]) FOR XML PATH(N''), TYPE).value(N'.[1]', N'NVARCHAR(MAX)'),1,11,N'') From a1d59156e3179e04e00ad55e0e9378e671a41785 Mon Sep 17 00:00:00 2001 From: Adrian Buckman <33764798+Adedba@users.noreply.github.com> Date: Sat, 9 Jan 2021 23:49:58 +0000 Subject: [PATCH 063/662] Update sp_BlitzAnalysis.sql Added sort order validation --- sp_BlitzAnalysis.sql | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/sp_BlitzAnalysis.sql b/sp_BlitzAnalysis.sql index 6e9c7ead8..1b8d77b73 100644 --- a/sp_BlitzAnalysis.sql +++ b/sp_BlitzAnalysis.sql @@ -110,6 +110,12 @@ END SET @BlitzCacheSortorder = LOWER(@BlitzCacheSortorder); +IF (@OutputTableNameBlitzCache IS NOT NULL AND @BlitzCacheSortorder NOT IN (N'all',N'cpu',N'reads',N'writes',N'duration',N'executions',N'memory grant',N'spills')) +BEGIN + RAISERROR('Invalid sort option specified for @BlitzCacheSortorder, support values are ''all'', ''cpu'', ''reads'', ''writes'', ''duration'', ''executions'', ''memory grant'', ''spills''',11,0) WITH NOWAIT; + RETURN; +END + IF (@FromDate IS NULL) BEGIN RAISERROR('Setting @FromDate to: 1 hour ago',0,0) WITH NOWAIT; From 662256cb90c0ce047a1be2ce2208c160f8901261 Mon Sep 17 00:00:00 2001 From: Adrian Buckman <33764798+Adedba@users.noreply.github.com> Date: Sun, 10 Jan 2021 00:00:11 +0000 Subject: [PATCH 064/662] Update sp_BlitzAnalysis.sql Added sort order filtering within the dynamic SQL AND clause for BlitzCache --- sp_BlitzAnalysis.sql | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/sp_BlitzAnalysis.sql b/sp_BlitzAnalysis.sql index 1b8d77b73..293f2519c 100644 --- a/sp_BlitzAnalysis.sql +++ b/sp_BlitzAnalysis.sql @@ -566,7 +566,18 @@ CROSS APPLY ( FROM '+@FullOutputTableNameBlitzCache+N' AS '+[SortOptions].[Aliasname]+N' WHERE [ServerName] = @Servername AND [CheckDate] BETWEEN @FromDate AND @ToDate - AND ['+[SortOptions].[Aliasname]+N'].[CheckDate] = [CheckDates].[CheckDate] + AND ['+[SortOptions].[Aliasname]+N'].[CheckDate] = [CheckDates].[CheckDate]' + +CASE + WHEN [Sortorder] = N'cpu' THEN N' AND [TotalCPU] > 0' + WHEN [Sortorder] = N'reads' THEN N' AND [TotalReads] > 0' + WHEN [Sortorder] = N'writes' THEN N' AND [TotalWrites] > 0' + WHEN [Sortorder] = N'duration' THEN N' AND [TotalDuration] > 0' + WHEN [Sortorder] = N'executions' THEN N' AND [ExecutionCount] > 0' + WHEN [Sortorder] = N'memory grant' THEN N' AND [MaxGrantKB] > 0' + WHEN [Sortorder] = N'spills' THEN N' AND [MaxSpills] > 0' + ELSE N'' + END + +N' ORDER BY ['+[SortOptions].[Columnname]+N'] DESC) '+[SortOptions].[Aliasname]+N' )' FROM (VALUES From 61b89916e08692ac8aa9c17fb0820a7c8aa6ec6e Mon Sep 17 00:00:00 2001 From: Adrian Buckman <33764798+Adedba@users.noreply.github.com> Date: Sun, 10 Jan 2021 00:29:41 +0000 Subject: [PATCH 065/662] Update sp_BlitzAnalysis.sql Added column existence checks for sys.dm_exec_query_stats and columns max_grant_kb and max_spills. These checks control whether to include that sort order or not if the sort order is set to 'all' --- sp_BlitzAnalysis.sql | 33 +++++++++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/sp_BlitzAnalysis.sql b/sp_BlitzAnalysis.sql index 293f2519c..e5efd119a 100644 --- a/sp_BlitzAnalysis.sql +++ b/sp_BlitzAnalysis.sql @@ -43,7 +43,7 @@ ALTER PROCEDURE [dbo].[sp_BlitzAnalysis] ( AS SET NOCOUNT ON; -SELECT @Version = '1.000', @VersionDate = '20210107'; +SELECT @Version = '1.000', @VersionDate = '20210110'; IF(@VersionCheckMode = 1) BEGIN @@ -79,6 +79,8 @@ DECLARE @FullOutputTableNameBlitzCache NVARCHAR(1000); DECLARE @FullOutputTableNameBlitzWho NVARCHAR(1000); DECLARE @Sql NVARCHAR(MAX); DECLARE @NewLine NVARCHAR(2) = CHAR(10)+ CHAR(13); +DECLARE @IncludeMemoryGrants BIT; +DECLARE @IncludeSpills BIT; /* Set fully qualified table names */ SET @FullOutputTableNameBlitzFirst = QUOTENAME(@OutputDatabaseName)+N'.'+QUOTENAME(@OutputSchemaName)+N'.'+QUOTENAME(@OutputTableNameBlitzFirst); @@ -114,7 +116,20 @@ IF (@OutputTableNameBlitzCache IS NOT NULL AND @BlitzCacheSortorder NOT IN (N'al BEGIN RAISERROR('Invalid sort option specified for @BlitzCacheSortorder, support values are ''all'', ''cpu'', ''reads'', ''writes'', ''duration'', ''executions'', ''memory grant'', ''spills''',11,0) WITH NOWAIT; RETURN; -END +END + +SELECT @IncludeMemoryGrants = + CASE + WHEN (EXISTS(SELECT * FROM sys.all_columns WHERE [object_id] = OBJECT_ID('sys.dm_exec_query_stats') AND name = 'max_grant_kb')) THEN 1 + ELSE 0 + END; + +SELECT @IncludeSpills = + CASE + WHEN (EXISTS(SELECT * FROM sys.all_columns WHERE [object_id] = OBJECT_ID('sys.dm_exec_query_stats') AND name = 'max_spills')) THEN 1 + ELSE 0 + END; + IF (@FromDate IS NULL) BEGIN @@ -589,7 +604,12 @@ FROM (VALUES (N'memory grant',N'TopMemoryGrants',N'MaxGrantKB'), (N'spills',N'TopSpills',N'MaxSpills') ) SortOptions(Sortorder,Aliasname,Columnname) -WHERE [SortOptions].[Sortorder] = ISNULL(NULLIF(@BlitzCacheSortorder,N'all'),[SortOptions].[Sortorder]) +WHERE + CASE /* for spills and memory grant sorts make sure the underlying columns exist in the DMV otherwise do not include them */ + WHEN (@IncludeMemoryGrants = 0 OR @IncludeMemoryGrants IS NULL) AND [SortOptions].[Sortorder] = N'memory grant' THEN NULL + WHEN (@IncludeSpills = 0 OR @IncludeSpills IS NULL) AND [SortOptions].[Sortorder] = N'spills' THEN NULL + ELSE [SortOptions].[Sortorder] + END = ISNULL(NULLIF(@BlitzCacheSortorder,N'all'),[SortOptions].[Sortorder]) FOR XML PATH(N''), TYPE).value(N'.[1]', N'NVARCHAR(MAX)') @@ -610,7 +630,12 @@ FROM (VALUES (N'memory grant',N'TopMemoryGrants',N'MaxGrantKB'), (N'spills',N'TopSpills',N'MaxSpills') ) SortOptions(Sortorder,Aliasname,Columnname) -WHERE [SortOptions].[Sortorder] = ISNULL(NULLIF(@BlitzCacheSortorder,N'all'),[SortOptions].[Sortorder]) +WHERE + CASE /* for spills and memory grant sorts make sure the underlying columns exist in the DMV otherwise do not include them */ + WHEN (@IncludeMemoryGrants = 0 OR @IncludeMemoryGrants IS NULL) AND [SortOptions].[Sortorder] = N'memory grant' THEN NULL + WHEN (@IncludeSpills = 0 OR @IncludeSpills IS NULL) AND [SortOptions].[Sortorder] = N'spills' THEN NULL + ELSE [SortOptions].[Sortorder] + END = ISNULL(NULLIF(@BlitzCacheSortorder,N'all'),[SortOptions].[Sortorder]) FOR XML PATH(N''), TYPE).value(N'.[1]', N'NVARCHAR(MAX)'),1,11,N'') ); From 3370d1999af8f1209a36ed2c8f6d23be6265d450 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Sun, 10 Jan 2021 04:46:32 -0800 Subject: [PATCH 066/662] #2737 sp_ineachdb case sensitivity Tweaking "F" CTE for case sensitivity support. Closes #2737. --- sp_ineachdb.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sp_ineachdb.sql b/sp_ineachdb.sql index d5bfec5b8..ca0a848ca 100644 --- a/sp_ineachdb.sql +++ b/sp_ineachdb.sql @@ -191,7 +191,7 @@ AS (SELECT V.SrcList , F AS (SELECT C.SrcList , IIF(C.Quoted = 0 - ,SUBSTRING(C.name, PATINDEX(@NoSpaces, name), DATALENGTH (name)/2 - PATINDEX(@NoSpaces, name) - PATINDEX(@NoSpaces, REVERSE(name))+2) + ,SUBSTRING(C.Name, PATINDEX(@nospaces, Name), DATALENGTH (Name)/2 - PATINDEX(@nospaces, Name) - PATINDEX(@nospaces, REVERSE(Name))+2) , C.Name) AS name FROM C From d2915e05da025f9443d452022593cfbd5ff24d4e Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Sun, 10 Jan 2021 06:35:15 -0800 Subject: [PATCH 067/662] #2742 sp_BlitzFirst forwarded fetches Show table variables differently named than temp tables. Closes #2742. --- sp_BlitzFirst.sql | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/sp_BlitzFirst.sql b/sp_BlitzFirst.sql index 3a95f6fe0..342372161 100644 --- a/sp_BlitzFirst.sql +++ b/sp_BlitzFirst.sql @@ -2909,10 +2909,14 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, SELECT TOP 10 29 AS CheckID, 40 AS Priority, ''Table Problems'' AS FindingGroup, - ''Forwarded Fetches/Sec High: Temp Table'' AS Finding, + ''Forwarded Fetches/Sec High: TempDB Object'' AS Finding, ''https://BrentOzar.com/go/fetch/'' AS URL, - CAST(COALESCE(os.forwarded_fetch_count,0) AS NVARCHAR(20)) + '' forwarded fetches on temp table '' + COALESCE(OBJECT_NAME(os.object_id), ''Unknown'') AS Details, - ''Look through your source code to find the object creating these temp tables, and tune the creation and population to reduce fetches. See the URL for details.'' AS HowToStopIt + CAST(COALESCE(os.forwarded_fetch_count,0) AS NVARCHAR(20)) + '' forwarded fetches on '' + + CASE WHEN OBJECT_NAME(os.object_id) IS NULL THEN ''an unknown table '' + WHEN LEN(OBJECT_NAME(os.object_id)) < 50 THEN ''a table variable, internal identifier '' + OBJECT_NAME(os.object_id) + ELSE ''a temp table '' + OBJECT_NAME(os.object_id) + END AS Details, + ''Look through your source code to find the object creating these objects, and tune the creation and population to reduce fetches. See the URL for details.'' AS HowToStopIt FROM tempdb.sys.dm_db_index_operational_stats(DB_ID(''tempdb''), NULL, NULL, NULL) os WHERE os.database_id = DB_ID(''tempdb'') AND os.forwarded_fetch_count > 100 From fe09755e1f64c0fcae7bfc6ff2354bd638402be6 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Sun, 10 Jan 2021 06:59:26 -0800 Subject: [PATCH 068/662] #2743 sp_BlitzFirst forwarded fetch accounting Now only shows the number of forwarded fetches during this collection, not cumulatively. Closes #2743. --- sp_BlitzFirst.sql | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/sp_BlitzFirst.sql b/sp_BlitzFirst.sql index 342372161..4e65871e8 100644 --- a/sp_BlitzFirst.sql +++ b/sp_BlitzFirst.sql @@ -1368,6 +1368,16 @@ BEGIN AND counters.[object_name] COLLATE SQL_Latin1_General_CP1_CI_AS = RTRIM(dmv.[object_name]) COLLATE SQL_Latin1_General_CP1_CI_AS AND (counters.[instance_name] IS NULL OR counters.[instance_name] COLLATE SQL_Latin1_General_CP1_CI_AS = RTRIM(dmv.[instance_name]) COLLATE SQL_Latin1_General_CP1_CI_AS); + /* For Github #2743: */ + CREATE TABLE #TempdbOperationalStats (object_id BIGINT PRIMARY KEY CLUSTERED, + forwarded_fetch_count BIGINT); + INSERT INTO #TempdbOperationalStats (object_id, forwarded_fetch_count) + SELECT object_id, forwarded_fetch_count + FROM tempdb.sys.dm_db_index_operational_stats(DB_ID('tempdb'), NULL, NULL, NULL) os + WHERE os.database_id = DB_ID('tempdb') + AND os.forwarded_fetch_count > 100; + + /* If they want to run sp_BlitzWho and export to table, go for it. */ IF @OutputTableNameBlitzWho IS NOT NULL AND @OutputDatabaseName IS NOT NULL @@ -2890,7 +2900,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 'Table Problems' AS FindingGroup, 'Forwarded Fetches/Sec High' AS Finding, 'https://BrentOzar.com/go/fetch/' AS URL, - CAST(ps.value_delta AS NVARCHAR(20)) + ' Forwarded Records (from SQLServer:Access Methods counter)' + @LineFeed + CAST(ps.value_delta AS NVARCHAR(20)) + ' forwarded fetches (from SQLServer:Access Methods counter)' + @LineFeed + 'Check your heaps: they need to be rebuilt, or they need a clustered index applied.' + @LineFeed AS Details, 'Rebuild your heaps. If you use Ola Hallengren maintenance scripts, those do not rebuild heaps by default: https://www.brentozar.com/archive/2016/07/fix-forwarded-records/' AS HowToStopIt FROM #PerfmonStats ps @@ -2911,15 +2921,17 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, ''Table Problems'' AS FindingGroup, ''Forwarded Fetches/Sec High: TempDB Object'' AS Finding, ''https://BrentOzar.com/go/fetch/'' AS URL, - CAST(COALESCE(os.forwarded_fetch_count,0) AS NVARCHAR(20)) + '' forwarded fetches on '' + + CAST(COALESCE(os.forwarded_fetch_count,0) - COALESCE(os_prior.forwarded_fetch_count,0) AS NVARCHAR(20)) + '' forwarded fetches on '' + CASE WHEN OBJECT_NAME(os.object_id) IS NULL THEN ''an unknown table '' WHEN LEN(OBJECT_NAME(os.object_id)) < 50 THEN ''a table variable, internal identifier '' + OBJECT_NAME(os.object_id) ELSE ''a temp table '' + OBJECT_NAME(os.object_id) END AS Details, ''Look through your source code to find the object creating these objects, and tune the creation and population to reduce fetches. See the URL for details.'' AS HowToStopIt FROM tempdb.sys.dm_db_index_operational_stats(DB_ID(''tempdb''), NULL, NULL, NULL) os + LEFT OUTER JOIN #TempdbOperationalStats os_prior ON os.object_id = os_prior.object_id + AND os.forwarded_fetch_count > os_prior.forwarded_fetch_count WHERE os.database_id = DB_ID(''tempdb'') - AND os.forwarded_fetch_count > 100 + AND os.forwarded_fetch_count - COALESCE(os_prior.forwarded_fetch_count,0) > 100 ORDER BY os.forwarded_fetch_count DESC;' EXECUTE sp_executesql @StringToExecute; From cdaee5c68c376a399324eaff4fc5c6de57bbec99 Mon Sep 17 00:00:00 2001 From: sm8680 Date: Sun, 10 Jan 2021 10:02:29 -0500 Subject: [PATCH 069/662] Update sp_ineachdb.sql Line 194 changed 3 occurrences of variable @nospaces to @NoSpaces --- sp_ineachdb.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sp_ineachdb.sql b/sp_ineachdb.sql index ca0a848ca..21c609365 100644 --- a/sp_ineachdb.sql +++ b/sp_ineachdb.sql @@ -191,7 +191,7 @@ AS (SELECT V.SrcList , F AS (SELECT C.SrcList , IIF(C.Quoted = 0 - ,SUBSTRING(C.Name, PATINDEX(@nospaces, Name), DATALENGTH (Name)/2 - PATINDEX(@nospaces, Name) - PATINDEX(@nospaces, REVERSE(Name))+2) + ,SUBSTRING(C.Name, PATINDEX(@NoSpaces, Name), DATALENGTH (Name)/2 - PATINDEX(@NoSpaces, Name) - PATINDEX(@NoSpaces, REVERSE(Name))+2) , C.Name) AS name FROM C From f05c190ebe62c99bb41260fb6e237ea695bcceb9 Mon Sep 17 00:00:00 2001 From: sm8680 Date: Sun, 10 Jan 2021 11:23:16 -0500 Subject: [PATCH 070/662] Update sp_ineachdb.sql Changed 4 IIF statements to Case statements Changed 1 CONCAT statement to a ISNULL(C.Name,'') + ... This also contains the 3 @NoSpace case sensitivity fix. Brent and Team I left the previous 4 IIF statements and 1 CONCAT statement in the code but commented. Feel free to remove them and/or use them to test. I tested on SQL Server 2008 thru 2019. As well as a case sensitive instance. Feel free to reach out for any questions and/or concerns. --- sp_ineachdb.sql | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/sp_ineachdb.sql b/sp_ineachdb.sql index ca0a848ca..dc37dab4d 100644 --- a/sp_ineachdb.sql +++ b/sp_ineachdb.sql @@ -167,10 +167,12 @@ AS (SELECT V.SrcList , 0 AS Quoted FROM (VALUES ('In', @database_list + ','), ('Out', @exclude_list + ',')) AS V (SrcList, DBList) UNION ALL - SELECT C.SrcList - , IIF(V.Found = '[', '', SUBSTRING(C.DBList, 1, V.Place - 1))/*remove initial [*/ + SELECT C.SrcList +-- , IIF(V.Found = '[', '', SUBSTRING(C.DBList, 1, V.Place - 1))/*remove initial [*/ + , CASE WHEN V.Found = '[' THEN '' ELSE SUBSTRING(C.DBList, 1, V.Place - 1) END /*remove initial [*/ , STUFF(C.DBList, 1, V.Place, '') - , IIF(V.Found = '[', 1, 0) +-- , IIF(V.Found = '[', 1, 0) + ,Case WHEN V.Found = '[' THEN 1 ELSE 0 END , 0 FROM C CROSS APPLY @@ -179,24 +181,29 @@ AS (SELECT V.SrcList AND C.InBracket = 0 UNION ALL SELECT C.SrcList - , CONCAT(C.Name, SUBSTRING(C.DBList, 1, V.Place + W.DoubleBracket - 1)) /*Accumulates only one ] if escaped]] or none if end]*/ +-- , CONCAT(C.Name, SUBSTRING(C.DBList, 1, V.Place + W.DoubleBracket - 1)) /*Accumulates only one ] if escaped]] or none if end]*/ + , ISNULL(C.Name,'') + ISNULL(SUBSTRING(C.DBList, 1, V.Place + W.DoubleBracket - 1),'') /*Accumulates only one ] if escaped]] or none if end]*/ , STUFF(C.DBList, 1, V.Place + W.DoubleBracket, '') , W.DoubleBracket , 1 FROM C CROSS APPLY (VALUES (CHARINDEX(']', C.DBList))) AS V (Place) - CROSS APPLY (VALUES (IIF(SUBSTRING(C.DBList, V.Place + 1, 1) = ']', 1, 0))) AS W (DoubleBracket) + -- CROSS APPLY (VALUES (IIF(SUBSTRING(C.DBList, V.Place + 1, 1) = ']', 1, 0))) AS W (DoubleBracket) + CROSS APPLY (VALUES (CASE WHEN SUBSTRING(C.DBList, V.Place + 1, 1) = ']' THEN 1 ELSE 0 END)) AS W (DoubleBracket) WHERE C.DBList > '' AND C.InBracket = 1) , F AS (SELECT C.SrcList - , IIF(C.Quoted = 0 - ,SUBSTRING(C.Name, PATINDEX(@nospaces, Name), DATALENGTH (Name)/2 - PATINDEX(@nospaces, Name) - PATINDEX(@nospaces, REVERSE(Name))+2) - , C.Name) +-- , IIF(C.Quoted = 0 +-- ,SUBSTRING(C.Name, PATINDEX(@NoSpaces, Name), DATALENGTH (Name)/2 - PATINDEX(@NoSpaces, Name) - PATINDEX(@NoSpaces, REVERSE(Name))+2) +-- , C.Name) + , CASE WHEN C.Quoted = 0 THEN + SUBSTRING(C.Name, PATINDEX(@NoSpaces, Name), DATALENGTH (Name)/2 - PATINDEX(@NoSpaces, Name) - PATINDEX(@NoSpaces, REVERSE(Name))+2) + ELSE C.Name END AS name FROM C WHERE C.InBracket = 0 - AND C.Name > '') + AND C.Name > '') INSERT #ineachdb(id,name,is_distributor) SELECT d.database_id , d.name From ee4558271fafaed5ab2c52a0ae8a474e2b0388ac Mon Sep 17 00:00:00 2001 From: Chad Baldwin Date: Thu, 14 Jan 2021 09:40:15 -0800 Subject: [PATCH 071/662] sp_BlitzLock: Add error check for update stats on temp table, skip if fail When user runs without sysadmin or db_owner permissions sp_BlitzLock fails reporting '#t' cannot be found. Added try/catch to skip line if fails for 1088 error. --- sp_BlitzLock.sql | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index c1b8acb8a..05042f6aa 100644 --- a/sp_BlitzLock.sql +++ b/sp_BlitzLock.sql @@ -302,9 +302,24 @@ You need to use an Azure storage account, and the path has to look like this: ht AND LEFT(CAST(SERVERPROPERTY('MachineName') AS VARCHAR(8000)), 8) <> 'EC2AMAZ-' AND LEFT(CAST(SERVERPROPERTY('ServerName') AS VARCHAR(8000)), 8) <> 'EC2AMAZ-' AND db_id('rdsadmin') IS NULL - BEGIN - UPDATE STATISTICS #t WITH ROWCOUNT = 100000000, PAGECOUNT = 100000000; - END + BEGIN; + BEGIN TRY; + UPDATE STATISTICS #t WITH ROWCOUNT = 100000000, PAGECOUNT = 100000000; + END TRY + BEGIN CATCH; + /* Misleading error returned, if run without permissions to update statistics the error returned is "Cannot find object". + Catching specific error, and returning message with better info. If any other error is returned, then throw as normal */ + IF (ERROR_NUMBER() = 1088) + BEGIN; + SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + RAISERROR('Cannot run UPDATE STATISTICS on temp table without db_owner or sysadmin permissions %s', 0, 1, @d) WITH NOWAIT; + END; + ELSE + BEGIN; + THROW; + END; + END CATCH; + END; /*Grab the initial set of XML to parse*/ SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); From 027bdf64deea4276c670557e3a85961e52b86720 Mon Sep 17 00:00:00 2001 From: alihacks Date: Thu, 14 Jan 2021 20:03:06 -0500 Subject: [PATCH 072/662] sp_Blitz - Offline databases show up in auto-shrink and page verification checks #2750 --- sp_Blitz.sql | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index 7b31a4fb7..f2d57280e 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -1670,6 +1670,7 @@ AS + '] has auto-shrink enabled. This setting can dramatically decrease performance.' ) AS Details FROM sys.databases WHERE is_auto_shrink_on = 1 + AND state <> 6 /* Offline */ AND name NOT IN ( SELECT DISTINCT DatabaseName FROM #SkipChecks @@ -1703,7 +1704,7 @@ AS FROM sys.databases WHERE page_verify_option < 2 AND name <> ''tempdb'' - AND state <> 1 /* Restoring */ + AND state NOT IN (1, 6) /* Restoring, Offline */ and name not in (select distinct DatabaseName from #SkipChecks WHERE CheckID IS NULL OR CheckID = 14) OPTION (RECOMPILE);'; IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; From 37bfb41cb6240c426ec5926a5ee7285266a66e05 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Sat, 16 Jan 2021 04:51:07 -0800 Subject: [PATCH 073/662] #2753 sp_ineachdb add go Add GO at the end so Install-All-Scripts won't fail. Closes #2753. --- sp_ineachdb.sql | 1 + 1 file changed, 1 insertion(+) diff --git a/sp_ineachdb.sql b/sp_ineachdb.sql index 5b8efc305..fae2e30c4 100644 --- a/sp_ineachdb.sql +++ b/sp_ineachdb.sql @@ -336,3 +336,4 @@ OPTION (MAXRECURSION 0); CLOSE dbs; DEALLOCATE dbs; END +GO \ No newline at end of file From 9984f7fb43a6d8c854584bdb705c79301f1da8ba Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Sat, 16 Jan 2021 05:02:53 -0800 Subject: [PATCH 074/662] Update 2021 copyright dates --- LICENSE.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/LICENSE.md b/LICENSE.md index 2953a00f6..0dd845f31 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -3,13 +3,13 @@ MIT License Copyright for portions of sp_Blitz are held by Microsoft as part of project tigertoolbox and are provided under the MIT license: https://github.com/Microsoft/tigertoolbox -All other copyrights for sp_Blitz are held by Brent Ozar Unlimited, 2020 as +All other copyrights for sp_Blitz are held by Brent Ozar Unlimited, 2021 as described below. Copyright for portions of DatabaseRestore are held by GregWhiteDBA as part of project MSSQLAutoRestore and are provided under the MIT license: https://github.com/GregWhiteDBA/MSSQLAutoRestore -All other copyrights for DatabaseRestore are held by Brent Ozar Unlimited, 2020 +All other copyrights for DatabaseRestore are held by Brent Ozar Unlimited, 2021 as described below. Copyright for sp_BlitzInMemoryOLTP are held by Ned Otter and Konstantin @@ -21,7 +21,7 @@ project SqlServerVersionScript and are provided under the MIT license: https://github.com/jadarnel27/SqlServerVersionScript -Copyright (c) 2020 Brent Ozar Unlimited +Copyright (c) 2021 Brent Ozar Unlimited Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From 212ac1f3b93af05757c8388b9f5488ef4a3e7b19 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Sun, 17 Jan 2021 02:35:03 -0800 Subject: [PATCH 075/662] 2021-01 release Also fixing a bug in sp_BlitzCache introduced in the Help = 1 parameter standardization. --- Install-All-Scripts.sql | 868 ++++++++++++++++-------- Install-Core-Blitz-No-Query-Store.sql | 822 +++++++++++++++------- Install-Core-Blitz-With-Query-Store.sql | 826 +++++++++++++++------- SqlServerVersions.sql | 8 + sp_AllNightLog.sql | 4 +- sp_AllNightLog_Setup.sql | 4 +- sp_Blitz.sql | 6 +- sp_BlitzBackups.sql | 4 +- sp_BlitzCache.sql | 711 +++++++++---------- sp_BlitzFirst.sql | 4 +- sp_BlitzInMemoryOLTP.sql | 2 +- sp_BlitzIndex.sql | 4 +- sp_BlitzLock.sql | 4 +- sp_BlitzQueryStore.sql | 4 +- sp_BlitzWho.sql | 4 +- sp_DatabaseRestore.sql | 4 +- sp_ineachdb.sql | 6 +- 17 files changed, 2112 insertions(+), 1173 deletions(-) diff --git a/Install-All-Scripts.sql b/Install-All-Scripts.sql index 993648b65..1b401f668 100755 --- a/Install-All-Scripts.sql +++ b/Install-All-Scripts.sql @@ -30,7 +30,7 @@ SET NOCOUNT ON; BEGIN; -SELECT @Version = '3.9999', @VersionDate = '20201211'; +SELECT @Version = '8.0', @VersionDate = '20210117'; IF(@VersionCheckMode = 1) BEGIN @@ -82,7 +82,7 @@ BEGIN MIT License - Copyright (c) 2020 Brent Ozar Unlimited + Copyright (c) 2021 Brent Ozar Unlimited Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -1524,7 +1524,7 @@ SET NOCOUNT ON; BEGIN; -SELECT @Version = '3.9999', @VersionDate = '20201211'; +SELECT @Version = '8.0', @VersionDate = '20210117'; IF(@VersionCheckMode = 1) BEGIN @@ -1603,7 +1603,7 @@ BEGIN MIT License - Copyright (c) 2020 Brent Ozar Unlimited + Copyright (c) 2021 Brent Ozar Unlimited Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -2856,7 +2856,7 @@ AS SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '7.99999', @VersionDate = '20201211'; + SELECT @Version = '8.0', @VersionDate = '20210117'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -2864,7 +2864,9 @@ AS RETURN; END; - IF @Help = 1 PRINT ' + IF @Help = 1 + BEGIN + PRINT ' /* sp_Blitz from http://FirstResponderKit.org @@ -2909,9 +2911,9 @@ AS tigertoolbox and are provided under the MIT license: https://github.com/Microsoft/tigertoolbox - All other copyrights for sp_Blitz are held by Brent Ozar Unlimited, 2020. + All other copyrights for sp_Blitz are held by Brent Ozar Unlimited, 2021. - Copyright (c) 2020 Brent Ozar Unlimited + Copyright (c) 2021 Brent Ozar Unlimited Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -2932,6 +2934,9 @@ AS SOFTWARE. */'; + RETURN; + END; /* @Help = 1 */ + ELSE IF @OutputType = 'SCHEMA' BEGIN SELECT FieldList = '[Priority] TINYINT, [FindingsGroup] VARCHAR(50), [Finding] VARCHAR(200), [DatabaseName] NVARCHAR(128), [URL] VARCHAR(200), [Details] NVARCHAR(4000), [QueryPlan] NVARCHAR(MAX), [QueryPlanFiltered] NVARCHAR(MAX), [CheckID] INT'; @@ -4484,6 +4489,7 @@ AS + '] has auto-shrink enabled. This setting can dramatically decrease performance.' ) AS Details FROM sys.databases WHERE is_auto_shrink_on = 1 + AND state <> 6 /* Offline */ AND name NOT IN ( SELECT DISTINCT DatabaseName FROM #SkipChecks @@ -4517,7 +4523,7 @@ AS FROM sys.databases WHERE page_verify_option < 2 AND name <> ''tempdb'' - AND state <> 1 /* Restoring */ + AND state NOT IN (1, 6) /* Restoring, Offline */ and name not in (select distinct DatabaseName from #SkipChecks WHERE CheckID IS NULL OR CheckID = 14) OPTION (RECOMPILE);'; IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; @@ -12279,7 +12285,7 @@ AS SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '3.99999', @VersionDate = '20201211'; + SELECT @Version = '8.0', @VersionDate = '20210117'; IF(@VersionCheckMode = 1) BEGIN @@ -12326,7 +12332,7 @@ AS MIT License - Copyright (c) 2020 Brent Ozar Unlimited + Copyright (c) 2021 Brent Ozar Unlimited Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -14058,7 +14064,7 @@ BEGIN SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '7.99999', @VersionDate = '20201211'; +SELECT @Version = '8.0', @VersionDate = '20210117'; IF(@VersionCheckMode = 1) @@ -14066,7 +14072,9 @@ BEGIN RETURN; END; -IF @Help = 1 PRINT ' +IF @Help = 1 +BEGIN +PRINT ' sp_BlitzCache from http://FirstResponderKit.org This script displays your most resource-intensive queries from the plan cache, @@ -14094,7 +14102,7 @@ https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/ MIT License -Copyright (c) 2020 Brent Ozar Unlimited +Copyright (c) 2021 Brent Ozar Unlimited Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -14117,8 +14125,8 @@ SOFTWARE. DECLARE @nl NVARCHAR(2) = NCHAR(13) + NCHAR(10) ; -IF @Help = 1 -BEGIN +--IF @Help = 1 /* We're still under a @Help = 1 BEGIN from above */ +--BEGIN SELECT N'@Help' AS [Parameter Name] , N'BIT' AS [Data Type] , N'Displays this help message.' AS [Parameter Description] @@ -21238,14 +21246,16 @@ BEGIN SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '7.99999', @VersionDate = '20201211'; +SELECT @Version = '8.0', @VersionDate = '20210117'; IF(@VersionCheckMode = 1) BEGIN RETURN; END; -IF @Help = 1 PRINT ' +IF @Help = 1 +BEGIN +PRINT ' sp_BlitzFirst from http://FirstResponderKit.org This script gives you a prioritized list of why your SQL Server is slow right now. @@ -21276,7 +21286,7 @@ https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/ MIT License -Copyright (c) 2020 Brent Ozar Unlimited +Copyright (c) 2021 Brent Ozar Unlimited Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -21297,7 +21307,8 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. '; - +RETURN; +END; /* @Help = 1 */ RAISERROR('Setting up configuration variables',10,1) WITH NOWAIT; DECLARE @StringToExecute NVARCHAR(MAX), @@ -22558,6 +22569,16 @@ BEGIN AND counters.[object_name] COLLATE SQL_Latin1_General_CP1_CI_AS = RTRIM(dmv.[object_name]) COLLATE SQL_Latin1_General_CP1_CI_AS AND (counters.[instance_name] IS NULL OR counters.[instance_name] COLLATE SQL_Latin1_General_CP1_CI_AS = RTRIM(dmv.[instance_name]) COLLATE SQL_Latin1_General_CP1_CI_AS); + /* For Github #2743: */ + CREATE TABLE #TempdbOperationalStats (object_id BIGINT PRIMARY KEY CLUSTERED, + forwarded_fetch_count BIGINT); + INSERT INTO #TempdbOperationalStats (object_id, forwarded_fetch_count) + SELECT object_id, forwarded_fetch_count + FROM tempdb.sys.dm_db_index_operational_stats(DB_ID('tempdb'), NULL, NULL, NULL) os + WHERE os.database_id = DB_ID('tempdb') + AND os.forwarded_fetch_count > 100; + + /* If they want to run sp_BlitzWho and export to table, go for it. */ IF @OutputTableNameBlitzWho IS NOT NULL AND @OutputDatabaseName IS NOT NULL @@ -22575,40 +22596,47 @@ BEGIN /* Maintenance Tasks Running - Backup Running - CheckID 1 */ IF @Seconds > 0 - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, QueryHash) - SELECT 1 AS CheckID, - 1 AS Priority, - 'Maintenance Tasks Running' AS FindingGroup, - 'Backup Running' AS Finding, - 'http://www.BrentOzar.com/askbrent/backups/' AS URL, - 'Backup of ' + DB_NAME(db.resource_database_id) + ' database (' + (SELECT CAST(CAST(SUM(size * 8.0 / 1024 / 1024) AS BIGINT) AS NVARCHAR) FROM #MasterFiles WHERE database_id = db.resource_database_id) + 'GB) ' + @LineFeed - + CAST(r.percent_complete AS NVARCHAR(100)) + '% complete, has been running since ' + CAST(r.start_time AS NVARCHAR(100)) + '. ' + @LineFeed - + CASE WHEN COALESCE(s.nt_user_name, s.login_name) IS NOT NULL THEN (' Login: ' + COALESCE(s.nt_user_name, s.login_name) + ' ') ELSE '' END AS Details, - 'KILL ' + CAST(r.session_id AS NVARCHAR(100)) + ';' AS HowToStopIt, - pl.query_plan AS QueryPlan, - r.start_time AS StartTime, - s.login_name AS LoginName, - s.nt_user_name AS NTUserName, - s.[program_name] AS ProgramName, - s.[host_name] AS HostName, - db.[resource_database_id] AS DatabaseID, - DB_NAME(db.resource_database_id) AS DatabaseName, - 0 AS OpenTransactionCount, - r.query_hash - FROM sys.dm_exec_requests r - INNER JOIN sys.dm_exec_connections c ON r.session_id = c.session_id - INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id - INNER JOIN ( - SELECT DISTINCT request_session_id, resource_database_id - FROM sys.dm_tran_locks - WHERE resource_type = N'DATABASE' - AND request_mode = N'S' - AND request_status = N'GRANT' - AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.session_id = db.request_session_id - CROSS APPLY sys.dm_exec_query_plan(r.plan_handle) pl - WHERE r.command LIKE 'BACKUP%' - AND r.start_time <= DATEADD(minute, -5, GETDATE()) - AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs); + BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 1',10,1) WITH NOWAIT; + END + + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, QueryHash) + SELECT 1 AS CheckID, + 1 AS Priority, + 'Maintenance Tasks Running' AS FindingGroup, + 'Backup Running' AS Finding, + 'http://www.BrentOzar.com/askbrent/backups/' AS URL, + 'Backup of ' + DB_NAME(db.resource_database_id) + ' database (' + (SELECT CAST(CAST(SUM(size * 8.0 / 1024 / 1024) AS BIGINT) AS NVARCHAR) FROM #MasterFiles WHERE database_id = db.resource_database_id) + 'GB) ' + @LineFeed + + CAST(r.percent_complete AS NVARCHAR(100)) + '% complete, has been running since ' + CAST(r.start_time AS NVARCHAR(100)) + '. ' + @LineFeed + + CASE WHEN COALESCE(s.nt_user_name, s.login_name) IS NOT NULL THEN (' Login: ' + COALESCE(s.nt_user_name, s.login_name) + ' ') ELSE '' END AS Details, + 'KILL ' + CAST(r.session_id AS NVARCHAR(100)) + ';' AS HowToStopIt, + pl.query_plan AS QueryPlan, + r.start_time AS StartTime, + s.login_name AS LoginName, + s.nt_user_name AS NTUserName, + s.[program_name] AS ProgramName, + s.[host_name] AS HostName, + db.[resource_database_id] AS DatabaseID, + DB_NAME(db.resource_database_id) AS DatabaseName, + 0 AS OpenTransactionCount, + r.query_hash + FROM sys.dm_exec_requests r + INNER JOIN sys.dm_exec_connections c ON r.session_id = c.session_id + INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id + INNER JOIN ( + SELECT DISTINCT request_session_id, resource_database_id + FROM sys.dm_tran_locks + WHERE resource_type = N'DATABASE' + AND request_mode = N'S' + AND request_status = N'GRANT' + AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.session_id = db.request_session_id + CROSS APPLY sys.dm_exec_query_plan(r.plan_handle) pl + WHERE r.command LIKE 'BACKUP%' + AND r.start_time <= DATEADD(minute, -5, GETDATE()) + AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs); + END /* If there's a backup running, add details explaining how long full backup has been taking in the last month. */ IF @Seconds > 0 AND CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) <> 'SQL Azure' @@ -22620,109 +22648,132 @@ BEGIN /* Maintenance Tasks Running - DBCC CHECK* Running - CheckID 2 */ IF @Seconds > 0 AND EXISTS(SELECT * FROM sys.dm_exec_requests WHERE command LIKE 'DBCC%') - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, QueryHash) - SELECT 2 AS CheckID, - 1 AS Priority, - 'Maintenance Tasks Running' AS FindingGroup, - 'DBCC CHECK* Running' AS Finding, - 'http://www.BrentOzar.com/askbrent/dbcc/' AS URL, - 'Corruption check of ' + DB_NAME(db.resource_database_id) + ' database (' + (SELECT CAST(CAST(SUM(size * 8.0 / 1024 / 1024) AS BIGINT) AS NVARCHAR) FROM #MasterFiles WHERE database_id = db.resource_database_id) + 'GB) 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, - s.login_name AS LoginName, - s.nt_user_name AS NTUserName, - s.[program_name] AS ProgramName, - s.[host_name] AS HostName, - db.[resource_database_id] AS DatabaseID, - DB_NAME(db.resource_database_id) AS DatabaseName, - 0 AS OpenTransactionCount, - r.query_hash - FROM sys.dm_exec_requests r - INNER JOIN sys.dm_exec_connections c ON r.session_id = c.session_id - INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id - INNER JOIN (SELECT DISTINCT l.request_session_id, l.resource_database_id - FROM sys.dm_tran_locks l - INNER JOIN sys.databases d ON l.resource_database_id = d.database_id - WHERE l.resource_type = N'DATABASE' - AND l.request_mode = N'S' - AND l.request_status = N'GRANT' - AND l.request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.session_id = db.request_session_id - OUTER APPLY sys.dm_exec_query_plan(r.plan_handle) pl - OUTER APPLY sys.dm_exec_sql_text(r.sql_handle) AS t - WHERE r.command LIKE 'DBCC%' - AND CAST(t.text AS NVARCHAR(4000)) NOT LIKE '%dm_db_index_physical_stats%' - AND CAST(t.text AS NVARCHAR(4000)) NOT LIKE '%ALTER INDEX%' - AND CAST(t.text AS NVARCHAR(4000)) NOT LIKE '%fileproperty%' - AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs); + BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 2',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, QueryHash) + SELECT 2 AS CheckID, + 1 AS Priority, + 'Maintenance Tasks Running' AS FindingGroup, + 'DBCC CHECK* Running' AS Finding, + 'http://www.BrentOzar.com/askbrent/dbcc/' AS URL, + 'Corruption check of ' + DB_NAME(db.resource_database_id) + ' database (' + (SELECT CAST(CAST(SUM(size * 8.0 / 1024 / 1024) AS BIGINT) AS NVARCHAR) FROM #MasterFiles WHERE database_id = db.resource_database_id) + 'GB) 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, + s.login_name AS LoginName, + s.nt_user_name AS NTUserName, + s.[program_name] AS ProgramName, + s.[host_name] AS HostName, + db.[resource_database_id] AS DatabaseID, + DB_NAME(db.resource_database_id) AS DatabaseName, + 0 AS OpenTransactionCount, + r.query_hash + FROM sys.dm_exec_requests r + INNER JOIN sys.dm_exec_connections c ON r.session_id = c.session_id + INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id + INNER JOIN (SELECT DISTINCT l.request_session_id, l.resource_database_id + FROM sys.dm_tran_locks l + INNER JOIN sys.databases d ON l.resource_database_id = d.database_id + WHERE l.resource_type = N'DATABASE' + AND l.request_mode = N'S' + AND l.request_status = N'GRANT' + AND l.request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.session_id = db.request_session_id + OUTER APPLY sys.dm_exec_query_plan(r.plan_handle) pl + OUTER APPLY sys.dm_exec_sql_text(r.sql_handle) AS t + WHERE r.command LIKE 'DBCC%' + AND CAST(t.text AS NVARCHAR(4000)) NOT LIKE '%dm_db_index_physical_stats%' + AND CAST(t.text AS NVARCHAR(4000)) NOT LIKE '%ALTER INDEX%' + AND CAST(t.text AS NVARCHAR(4000)) NOT LIKE '%fileproperty%' + AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs); + END /* Maintenance Tasks Running - Restore Running - CheckID 3 */ IF @Seconds > 0 - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, QueryHash) - SELECT 3 AS CheckID, - 1 AS Priority, - 'Maintenance Tasks Running' AS FindingGroup, - 'Restore Running' AS Finding, - 'http://www.BrentOzar.com/askbrent/backups/' AS URL, - 'Restore of ' + DB_NAME(db.resource_database_id) + ' database (' + (SELECT CAST(CAST(SUM(size * 8.0 / 1024 / 1024) AS BIGINT) AS NVARCHAR) FROM #MasterFiles WHERE database_id = db.resource_database_id) + '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, - s.login_name AS LoginName, - s.nt_user_name AS NTUserName, - s.[program_name] AS ProgramName, - s.[host_name] AS HostName, - db.[resource_database_id] AS DatabaseID, - DB_NAME(db.resource_database_id) AS DatabaseName, - 0 AS OpenTransactionCount, - r.query_hash - FROM sys.dm_exec_requests r - INNER JOIN sys.dm_exec_connections c ON r.session_id = c.session_id - INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id - INNER JOIN ( - SELECT DISTINCT request_session_id, resource_database_id - FROM sys.dm_tran_locks - WHERE resource_type = N'DATABASE' - AND request_mode = N'S' - AND request_status = N'GRANT') AS db ON s.session_id = db.request_session_id - CROSS APPLY sys.dm_exec_query_plan(r.plan_handle) pl - WHERE r.command LIKE 'RESTORE%' - AND s.program_name <> 'SQL Server Log Shipping' - AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs); + BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 3',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, QueryHash) + SELECT 3 AS CheckID, + 1 AS Priority, + 'Maintenance Tasks Running' AS FindingGroup, + 'Restore Running' AS Finding, + 'http://www.BrentOzar.com/askbrent/backups/' AS URL, + 'Restore of ' + DB_NAME(db.resource_database_id) + ' database (' + (SELECT CAST(CAST(SUM(size * 8.0 / 1024 / 1024) AS BIGINT) AS NVARCHAR) FROM #MasterFiles WHERE database_id = db.resource_database_id) + '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, + s.login_name AS LoginName, + s.nt_user_name AS NTUserName, + s.[program_name] AS ProgramName, + s.[host_name] AS HostName, + db.[resource_database_id] AS DatabaseID, + DB_NAME(db.resource_database_id) AS DatabaseName, + 0 AS OpenTransactionCount, + r.query_hash + FROM sys.dm_exec_requests r + INNER JOIN sys.dm_exec_connections c ON r.session_id = c.session_id + INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id + INNER JOIN ( + SELECT DISTINCT request_session_id, resource_database_id + FROM sys.dm_tran_locks + WHERE resource_type = N'DATABASE' + AND request_mode = N'S' + AND request_status = N'GRANT') AS db ON s.session_id = db.request_session_id + CROSS APPLY sys.dm_exec_query_plan(r.plan_handle) pl + WHERE r.command LIKE 'RESTORE%' + AND s.program_name <> 'SQL Server Log Shipping' + AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs); + END /* SQL Server Internal Maintenance - Database File Growing - CheckID 4 */ IF @Seconds > 0 - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount) - SELECT 4 AS CheckID, - 1 AS Priority, - 'SQL Server Internal Maintenance' AS FindingGroup, - 'Database File Growing' AS Finding, - 'http://www.BrentOzar.com/go/instant' AS URL, - 'SQL Server is waiting for Windows to provide storage space for a database restore, a data file growth, or a log file growth. This task has been running since ' + CAST(r.start_time AS NVARCHAR(100)) + '.' + @LineFeed + 'Check the query plan (expert mode) to identify the database involved.' AS Details, - 'Unfortunately, you can''t stop this, but you can prevent it next time. Check out http://www.BrentOzar.com/go/instant for details.' AS HowToStopIt, - pl.query_plan AS QueryPlan, - r.start_time AS StartTime, - s.login_name AS LoginName, - s.nt_user_name AS NTUserName, - s.[program_name] AS ProgramName, - s.[host_name] AS HostName, - NULL AS DatabaseID, - NULL AS DatabaseName, - 0 AS OpenTransactionCount - FROM sys.dm_os_waiting_tasks t - INNER JOIN sys.dm_exec_connections c ON t.session_id = c.session_id - INNER JOIN sys.dm_exec_requests r ON t.session_id = r.session_id - INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id - CROSS APPLY sys.dm_exec_query_plan(r.plan_handle) pl - WHERE t.wait_type = 'PREEMPTIVE_OS_WRITEFILEGATHER' - AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs); + BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 4',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount) + SELECT 4 AS CheckID, + 1 AS Priority, + 'SQL Server Internal Maintenance' AS FindingGroup, + 'Database File Growing' AS Finding, + 'http://www.BrentOzar.com/go/instant' AS URL, + 'SQL Server is waiting for Windows to provide storage space for a database restore, a data file growth, or a log file growth. This task has been running since ' + CAST(r.start_time AS NVARCHAR(100)) + '.' + @LineFeed + 'Check the query plan (expert mode) to identify the database involved.' AS Details, + 'Unfortunately, you can''t stop this, but you can prevent it next time. Check out http://www.BrentOzar.com/go/instant for details.' AS HowToStopIt, + pl.query_plan AS QueryPlan, + r.start_time AS StartTime, + s.login_name AS LoginName, + s.nt_user_name AS NTUserName, + s.[program_name] AS ProgramName, + s.[host_name] AS HostName, + NULL AS DatabaseID, + NULL AS DatabaseName, + 0 AS OpenTransactionCount + FROM sys.dm_os_waiting_tasks t + INNER JOIN sys.dm_exec_connections c ON t.session_id = c.session_id + INNER JOIN sys.dm_exec_requests r ON t.session_id = r.session_id + INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id + CROSS APPLY sys.dm_exec_query_plan(r.plan_handle) pl + WHERE t.wait_type = 'PREEMPTIVE_OS_WRITEFILEGATHER' + AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs); + END /* Query Problems - Long-Running Query Blocking Others - CheckID 5 */ IF SERVERPROPERTY('Edition') <> 'SQL Azure' AND @Seconds > 0 AND EXISTS(SELECT * FROM sys.dm_os_waiting_tasks WHERE wait_type LIKE 'LCK%' AND wait_duration_ms > 30000) BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 5',10,1) WITH NOWAIT; + END + SET @StringToExecute = N'INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, QueryText, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, QueryHash) SELECT 5 AS CheckID, 1 AS Priority, @@ -22761,6 +22812,11 @@ BEGIN /* Query Problems - Plan Cache Erased Recently - CheckID 7 */ IF DATEADD(mi, -15, SYSDATETIME()) < (SELECT TOP 1 creation_time FROM sys.dm_exec_query_stats ORDER BY creation_time) BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 7',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) SELECT TOP 1 7 AS CheckID, 50 AS Priority, @@ -22780,38 +22836,44 @@ BEGIN /* Query Problems - Sleeping Query with Open Transactions - CheckID 8 */ IF @Seconds > 0 - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, OpenTransactionCount) - SELECT 8 AS CheckID, - 50 AS Priority, - 'Query Problems' AS FindingGroup, - 'Sleeping Query with Open Transactions' AS Finding, - 'http://www.brentozar.com/askbrent/sleeping-query-with-open-transactions/' AS URL, - 'Database: ' + DB_NAME(db.resource_database_id) + @LineFeed + 'Host: ' + s.[host_name] + @LineFeed + 'Program: ' + s.[program_name] + @LineFeed + 'Asleep with open transactions and locks since ' + CAST(s.last_request_end_time AS NVARCHAR(100)) + '. ' AS Details, - 'KILL ' + CAST(s.session_id AS NVARCHAR(100)) + ';' AS HowToStopIt, - s.last_request_start_time AS StartTime, - s.login_name AS LoginName, - s.nt_user_name AS NTUserName, - s.[program_name] AS ProgramName, - s.[host_name] AS HostName, - db.[resource_database_id] AS DatabaseID, - DB_NAME(db.resource_database_id) AS DatabaseName, - (SELECT TOP 1 [text] FROM sys.dm_exec_sql_text(c.most_recent_sql_handle)) AS QueryText, - sessions_with_transactions.open_transaction_count AS OpenTransactionCount - FROM (SELECT session_id, SUM(open_transaction_count) AS open_transaction_count FROM sys.dm_exec_requests WHERE open_transaction_count > 0 GROUP BY session_id) AS sessions_with_transactions - INNER JOIN sys.dm_exec_sessions s ON sessions_with_transactions.session_id = s.session_id - INNER JOIN sys.dm_exec_connections c ON s.session_id = c.session_id - INNER JOIN ( - SELECT DISTINCT request_session_id, resource_database_id - FROM sys.dm_tran_locks - WHERE resource_type = N'DATABASE' - AND request_mode = N'S' - AND request_status = N'GRANT' - AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.session_id = db.request_session_id - WHERE s.status = 'sleeping' - AND s.last_request_end_time < DATEADD(ss, -10, SYSDATETIME()) - AND EXISTS(SELECT * FROM sys.dm_tran_locks WHERE request_session_id = s.session_id - AND NOT (resource_type = N'DATABASE' AND request_mode = N'S' AND request_status = N'GRANT' AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE')); + BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 8',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, OpenTransactionCount) + SELECT 8 AS CheckID, + 50 AS Priority, + 'Query Problems' AS FindingGroup, + 'Sleeping Query with Open Transactions' AS Finding, + 'http://www.brentozar.com/askbrent/sleeping-query-with-open-transactions/' AS URL, + 'Database: ' + DB_NAME(db.resource_database_id) + @LineFeed + 'Host: ' + s.[host_name] + @LineFeed + 'Program: ' + s.[program_name] + @LineFeed + 'Asleep with open transactions and locks since ' + CAST(s.last_request_end_time AS NVARCHAR(100)) + '. ' AS Details, + 'KILL ' + CAST(s.session_id AS NVARCHAR(100)) + ';' AS HowToStopIt, + s.last_request_start_time AS StartTime, + s.login_name AS LoginName, + s.nt_user_name AS NTUserName, + s.[program_name] AS ProgramName, + s.[host_name] AS HostName, + db.[resource_database_id] AS DatabaseID, + DB_NAME(db.resource_database_id) AS DatabaseName, + (SELECT TOP 1 [text] FROM sys.dm_exec_sql_text(c.most_recent_sql_handle)) AS QueryText, + sessions_with_transactions.open_transaction_count AS OpenTransactionCount + FROM (SELECT session_id, SUM(open_transaction_count) AS open_transaction_count FROM sys.dm_exec_requests WHERE open_transaction_count > 0 GROUP BY session_id) AS sessions_with_transactions + INNER JOIN sys.dm_exec_sessions s ON sessions_with_transactions.session_id = s.session_id + INNER JOIN sys.dm_exec_connections c ON s.session_id = c.session_id + INNER JOIN ( + SELECT DISTINCT request_session_id, resource_database_id + FROM sys.dm_tran_locks + WHERE resource_type = N'DATABASE' + AND request_mode = N'S' + AND request_status = N'GRANT' + AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.session_id = db.request_session_id + WHERE s.status = 'sleeping' + AND s.last_request_end_time < DATEADD(ss, -10, SYSDATETIME()) + AND EXISTS(SELECT * FROM sys.dm_tran_locks WHERE request_session_id = s.session_id + AND NOT (resource_type = N'DATABASE' AND request_mode = N'S' AND request_status = N'GRANT' AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE')); + END /*Query Problems - Clients using implicit transactions - CheckID 37 */ IF @Seconds > 0 @@ -22819,6 +22881,11 @@ BEGIN AND @@VERSION NOT LIKE 'Microsoft SQL Server 2008%' AND @@VERSION NOT LIKE 'Microsoft SQL Server 2008 R2%' ) BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 37',10,1) WITH NOWAIT; + END + SET @StringToExecute = N'INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, OpenTransactionCount) SELECT 37 AS CheckId, 50 AS Priority, @@ -22855,37 +22922,48 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, /* Query Problems - Query Rolling Back - CheckID 9 */ IF @Seconds > 0 - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, QueryHash) - SELECT 9 AS CheckID, - 1 AS Priority, - 'Query Problems' AS FindingGroup, - 'Query Rolling Back' AS Finding, - 'http://www.BrentOzar.com/askbrent/rollback/' AS URL, - 'Rollback started at ' + CAST(r.start_time AS NVARCHAR(100)) + ', is ' + CAST(r.percent_complete AS NVARCHAR(100)) + '% complete.' AS Details, - 'Unfortunately, you can''t stop this. Whatever you do, don''t restart the server in an attempt to fix it - SQL Server will keep rolling back.' AS HowToStopIt, - r.start_time AS StartTime, - s.login_name AS LoginName, - s.nt_user_name AS NTUserName, - s.[program_name] AS ProgramName, - s.[host_name] AS HostName, - db.[resource_database_id] AS DatabaseID, - DB_NAME(db.resource_database_id) AS DatabaseName, - (SELECT TOP 1 [text] FROM sys.dm_exec_sql_text(c.most_recent_sql_handle)) AS QueryText, - r.query_hash - FROM sys.dm_exec_sessions s - INNER JOIN sys.dm_exec_connections c ON s.session_id = c.session_id - INNER JOIN sys.dm_exec_requests r ON s.session_id = r.session_id - LEFT OUTER JOIN ( - SELECT DISTINCT request_session_id, resource_database_id - FROM sys.dm_tran_locks - WHERE resource_type = N'DATABASE' - AND request_mode = N'S' - AND request_status = N'GRANT' - AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.session_id = db.request_session_id - WHERE r.status = 'rollback'; + BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 9',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, QueryHash) + SELECT 9 AS CheckID, + 1 AS Priority, + 'Query Problems' AS FindingGroup, + 'Query Rolling Back' AS Finding, + 'http://www.BrentOzar.com/askbrent/rollback/' AS URL, + 'Rollback started at ' + CAST(r.start_time AS NVARCHAR(100)) + ', is ' + CAST(r.percent_complete AS NVARCHAR(100)) + '% complete.' AS Details, + 'Unfortunately, you can''t stop this. Whatever you do, don''t restart the server in an attempt to fix it - SQL Server will keep rolling back.' AS HowToStopIt, + r.start_time AS StartTime, + s.login_name AS LoginName, + s.nt_user_name AS NTUserName, + s.[program_name] AS ProgramName, + s.[host_name] AS HostName, + db.[resource_database_id] AS DatabaseID, + DB_NAME(db.resource_database_id) AS DatabaseName, + (SELECT TOP 1 [text] FROM sys.dm_exec_sql_text(c.most_recent_sql_handle)) AS QueryText, + r.query_hash + FROM sys.dm_exec_sessions s + INNER JOIN sys.dm_exec_connections c ON s.session_id = c.session_id + INNER JOIN sys.dm_exec_requests r ON s.session_id = r.session_id + LEFT OUTER JOIN ( + SELECT DISTINCT request_session_id, resource_database_id + FROM sys.dm_tran_locks + WHERE resource_type = N'DATABASE' + AND request_mode = N'S' + AND request_status = N'GRANT' + AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.session_id = db.request_session_id + WHERE r.status = 'rollback'; + END /* Server Performance - Too Much Free Memory - CheckID 34 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 34',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) SELECT 34 AS CheckID, 50 AS Priority, @@ -22905,6 +22983,12 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, /* Server Performance - Target Memory Lower Than Max - CheckID 35 */ IF SERVERPROPERTY('Edition') <> 'SQL Azure' + BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 35',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) SELECT 35 AS CheckID, 10 AS Priority, @@ -22921,8 +23005,14 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, AND CAST(cMax.value_in_use AS BIGINT) >= 1.5 * (CAST(cTarget.cntr_value AS BIGINT) / 1024) AND CAST(cMax.value_in_use AS BIGINT) < 2147483647 /* Not set to default of unlimited */ AND CAST(cTarget.cntr_value AS BIGINT) < .8 * (SELECT available_physical_memory_kb FROM sys.dm_os_sys_memory); /* Target memory less than 80% of physical memory (in case they set max too high) */ + END /* Server Info - Database Size, Total GB - CheckID 21 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 21',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) SELECT 21 AS CheckID, 251 AS Priority, @@ -22935,6 +23025,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, WHERE database_id > 4; /* Server Info - Database Count - CheckID 22 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 22',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) SELECT 22 AS CheckID, 251 AS Priority, @@ -22947,6 +23042,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, WHERE database_id > 4; /* Server Info - Memory Grants pending - CheckID 39 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 39',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) SELECT 39 AS CheckID, 50 AS Priority, @@ -22966,6 +23066,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, WHERE PendingGrants.Details > 0; /* Server Info - Memory Grant/Workspace info - CheckID 40 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 40',10,1) WITH NOWAIT; + END + DECLARE @MaxWorkspace BIGINT SET @MaxWorkspace = (SELECT CAST(cntr_value AS BIGINT)/1024 FROM #PerfmonStats WHERE counter_name = N'Maximum Workspace Memory (KB)') @@ -22991,17 +23096,22 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, FROM sys.dm_exec_query_memory_grants AS Grants; /* Query Problems - Queries with high memory grants - CheckID 46 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 46',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, URL, QueryText, QueryPlan) SELECT 46 AS CheckID, 100 AS Priority, 'Query Problems' AS FindingGroup, - 'Query with memory a grant exceeding ' + 'Query with a memory grant exceeding ' +CAST(@MemoryGrantThresholdPct AS NVARCHAR(15)) +'%' AS Finding, 'Granted size: '+ CAST(CAST(Grants.granted_memory_kb / 1024 AS INT) AS NVARCHAR(50)) +N'MB ' + @LineFeed - +N'Granted pct: ' + +N'Granted pct of max workspace: ' + CAST(ISNULL((CAST(Grants.granted_memory_kb / 1024 AS MONEY) / CAST(@MaxWorkspace AS MONEY)) * 100, 0) AS NVARCHAR(50)) + '%' + @LineFeed @@ -23018,6 +23128,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, /* Query Problems - Memory Leak in USERSTORE_TOKENPERM Cache - CheckID 45 */ IF EXISTS (SELECT * FROM sys.all_columns WHERE object_id = OBJECT_ID('sys.dm_os_memory_clerks') AND name = 'pages_kb') BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 45',10,1) WITH NOWAIT; + END + /* SQL 2012+ version */ SET @StringToExecute = N' INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, URL) @@ -23037,6 +23152,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, ELSE BEGIN /* Antiques Roadshow SQL 2008R2 - version */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 45 (Legacy version)',10,1) WITH NOWAIT; + END + SET @StringToExecute = N' INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, URL) SELECT 45 AS CheckID, @@ -23171,6 +23291,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, y.parallelism_skew; /* Queries in dm_exec_query_profiles showing signs of poor cardinality estimates - CheckID 42 */ + IF (@Debug = 1) + BEGIN + RAISERROR(''Running CheckID 42'',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, OpenTransactionCount, QueryHash, QueryPlan) SELECT 42 AS CheckID, @@ -23217,6 +23342,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, SET @StringToExecute = @StringToExecute + N'; /* Queries in dm_exec_query_profiles showing signs of unbalanced parallelism - CheckID 43 */ + IF (@Debug = 1) + BEGIN + RAISERROR(''Running CheckID 43'',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, OpenTransactionCount, QueryHash, QueryPlan) SELECT 43 AS CheckID, @@ -23262,7 +23392,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, SET @StringToExecute = @StringToExecute + N';'; - EXECUTE sp_executesql @StringToExecute; + EXECUTE sp_executesql @StringToExecute, N'@Debug BIT',@Debug = @Debug; END END @@ -23275,6 +23405,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, We get this data from the ring buffers, and it's only updated once per minute, so might as well get it now - whereas if we're checking 30+ seconds, it might get updated by the end of our sp_BlitzFirst session. */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 24',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) SELECT 24, 50, 'Server Performance', 'High CPU Utilization', CAST(100 - SystemIdle AS NVARCHAR(20)) + N'%.', 100 - SystemIdle, 'http://www.BrentOzar.com/go/cpu' FROM ( @@ -23290,6 +23425,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, WHERE 100 - SystemIdle >= 50; /* CPU Utilization - CheckID 23 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 23',10,1) WITH NOWAIT; + END + IF SERVERPROPERTY('Edition') <> 'SQL Azure' WITH y AS @@ -23331,6 +23471,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, /* Highlight if non SQL processes are using >25% CPU - CheckID 28 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 28',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) SELECT 28, 50, 'Server Performance', 'High CPU Utilization - Not SQL', CONVERT(NVARCHAR(100),100 - (y.SQLUsage + y.SystemIdle)) + N'% - Other Processes (not SQL Server) are using this much CPU. This may impact on the performance of your SQL Server instance', 100 - (y.SQLUsage + y.SystemIdle), 'http://www.BrentOzar.com/go/cpu' FROM ( @@ -23349,6 +23494,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, END; /* IF @Seconds < 30 */ /* Query Problems - Statistics Updated Recently - CheckID 44 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 44',10,1) WITH NOWAIT; + END + IF 20 >= (SELECT COUNT(*) FROM sys.databases WHERE name NOT IN ('master', 'model', 'msdb', 'tempdb')) BEGIN CREATE TABLE #UpdatedStats (HowToStopIt NVARCHAR(4000), RowsForSorting BIGINT); @@ -23536,6 +23686,10 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, /* Query Stats - If we're within 10 seconds of our projected finish time, do the plan cache analysis. - CheckID 18 */ IF DATEDIFF(ss, @FinishSampleTime, SYSDATETIMEOFFSET()) > 10 AND @CheckProcedureCache = 1 BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 18',10,1) WITH NOWAIT; + END INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) VALUES (18, 210, 'Query Stats', 'Plan Cache Analysis Skipped', 'http://www.BrentOzar.com/go/topqueries', @@ -23675,6 +23829,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, INNER JOIN qsTop ON qs.ID = qsTop.ID; /* Query Stats - Most Resource-Intensive Queries - CheckID 17 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 17',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, QueryText, QueryStatsNowID, QueryStatsFirstID, PlanHandle, QueryHash) SELECT 17, 210, 'Query Stats', 'Most Resource-Intensive Queries', 'http://www.BrentOzar.com/go/topqueries', 'Query stats during the sample:' + @LineFeed + @@ -23734,6 +23893,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, /* Wait Stats - CheckID 6 */ /* Compare the current wait stats to the sample we took at the start, and insert the top 10 waits. */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 6',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, DetailsInt) SELECT TOP 10 6 AS CheckID, 200 AS Priority, @@ -23749,6 +23913,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, ORDER BY (wNow.wait_time_ms - COALESCE(wBase.wait_time_ms,0)) DESC; /* Server Performance - Poison Wait Detected - CheckID 30 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 30',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, DetailsInt) SELECT 30 AS CheckID, 10 AS Priority, @@ -23767,6 +23936,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, /* Server Performance - Slow Data File Reads - CheckID 11 */ IF EXISTS (SELECT * FROM #BlitzFirstResults WHERE Finding LIKE 'PAGEIOLATCH%') BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 11',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, DatabaseID, DatabaseName) SELECT TOP 10 11 AS CheckID, 50 AS Priority, @@ -23792,6 +23966,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, /* Server Performance - Slow Log File Writes - CheckID 12 */ IF EXISTS (SELECT * FROM #BlitzFirstResults WHERE Finding LIKE 'WRITELOG%') BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 12',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, DatabaseID, DatabaseName) SELECT TOP 10 12 AS CheckID, 50 AS Priority, @@ -23816,6 +23995,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, /* SQL Server Internal Maintenance - Log File Growing - CheckID 13 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 13',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) SELECT 13 AS CheckID, 1 AS Priority, @@ -23833,6 +24017,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, /* SQL Server Internal Maintenance - Log File Shrinking - CheckID 14 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 14',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) SELECT 14 AS CheckID, 1 AS Priority, @@ -23849,6 +24038,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, AND value_delta > 0; /* Query Problems - Compilations/Sec High - CheckID 15 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 15',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) SELECT 15 AS CheckID, 50 AS Priority, @@ -23870,6 +24064,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, AND (psComp.value_delta * 10) > ps.value_delta; /* Compilations are more than 10% of batch requests per second */ /* Query Problems - Re-Compilations/Sec High - CheckID 16 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 16',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) SELECT 16 AS CheckID, 50 AS Priority, @@ -23891,13 +24090,18 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, AND (psComp.value_delta * 10) > ps.value_delta; /* Recompilations are more than 10% of batch requests per second */ /* Table Problems - Forwarded Fetches/Sec High - CheckID 29 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 29',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) SELECT 29 AS CheckID, 40 AS Priority, 'Table Problems' AS FindingGroup, 'Forwarded Fetches/Sec High' AS Finding, 'https://BrentOzar.com/go/fetch/' AS URL, - CAST(ps.value_delta AS NVARCHAR(20)) + ' Forwarded Records (from SQLServer:Access Methods counter)' + @LineFeed + CAST(ps.value_delta AS NVARCHAR(20)) + ' forwarded fetches (from SQLServer:Access Methods counter)' + @LineFeed + 'Check your heaps: they need to be rebuilt, or they need a clustered index applied.' + @LineFeed AS Details, 'Rebuild your heaps. If you use Ola Hallengren maintenance scripts, those do not rebuild heaps by default: https://www.brentozar.com/archive/2016/07/fix-forwarded-records/' AS HowToStopIt FROM #PerfmonStats ps @@ -23916,19 +24120,30 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, SELECT TOP 10 29 AS CheckID, 40 AS Priority, ''Table Problems'' AS FindingGroup, - ''Forwarded Fetches/Sec High: Temp Table'' AS Finding, + ''Forwarded Fetches/Sec High: TempDB Object'' AS Finding, ''https://BrentOzar.com/go/fetch/'' AS URL, - CAST(COALESCE(os.forwarded_fetch_count,0) AS NVARCHAR(20)) + '' forwarded fetches on temp table '' + COALESCE(OBJECT_NAME(os.object_id), ''Unknown'') AS Details, - ''Look through your source code to find the object creating these temp tables, and tune the creation and population to reduce fetches. See the URL for details.'' AS HowToStopIt + CAST(COALESCE(os.forwarded_fetch_count,0) - COALESCE(os_prior.forwarded_fetch_count,0) AS NVARCHAR(20)) + '' forwarded fetches on '' + + CASE WHEN OBJECT_NAME(os.object_id) IS NULL THEN ''an unknown table '' + WHEN LEN(OBJECT_NAME(os.object_id)) < 50 THEN ''a table variable, internal identifier '' + OBJECT_NAME(os.object_id) + ELSE ''a temp table '' + OBJECT_NAME(os.object_id) + END AS Details, + ''Look through your source code to find the object creating these objects, and tune the creation and population to reduce fetches. See the URL for details.'' AS HowToStopIt FROM tempdb.sys.dm_db_index_operational_stats(DB_ID(''tempdb''), NULL, NULL, NULL) os + LEFT OUTER JOIN #TempdbOperationalStats os_prior ON os.object_id = os_prior.object_id + AND os.forwarded_fetch_count > os_prior.forwarded_fetch_count WHERE os.database_id = DB_ID(''tempdb'') - AND os.forwarded_fetch_count > 100 + AND os.forwarded_fetch_count - COALESCE(os_prior.forwarded_fetch_count,0) > 100 ORDER BY os.forwarded_fetch_count DESC;' EXECUTE sp_executesql @StringToExecute; END /* In-Memory OLTP - Garbage Collection in Progress - CheckID 31 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 31',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) SELECT 31 AS CheckID, 50 AS Priority, @@ -23947,6 +24162,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, AND ps.value_delta > (100 * @Seconds); /* Ignore servers sitting idle */ /* In-Memory OLTP - Transactions Aborted - CheckID 32 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 32',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) SELECT 32 AS CheckID, 100 AS Priority, @@ -23964,6 +24184,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, AND ps.value_delta > (10 * @Seconds); /* Ignore servers sitting idle */ /* Query Problems - Suboptimal Plans/Sec High - CheckID 33 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 33',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) SELECT 32 AS CheckID, 100 AS Priority, @@ -23982,6 +24207,12 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, /* Azure Performance - Database is Maxed Out - CheckID 41 */ IF SERVERPROPERTY('Edition') = 'SQL Azure' + BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 41',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) SELECT 41 AS CheckID, 10 AS Priority, @@ -24002,8 +24233,14 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, OR avg_log_write_percent >=90 OR max_worker_percent >= 90 OR max_session_percent >= 90); + END /* Server Info - Batch Requests per Sec - CheckID 19 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 19',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, DetailsInt) SELECT 19 AS CheckID, 250 AS Priority, @@ -24024,39 +24261,58 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, /* Server Info - SQL Compilations/sec - CheckID 25 */ IF @ExpertMode = 1 - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, DetailsInt) - SELECT 25 AS CheckID, - 250 AS Priority, - 'Server Info' AS FindingGroup, - 'SQL Compilations per Sec' AS Finding, - 'http://www.BrentOzar.com/go/measure' AS URL, - CAST(ps.value_delta / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS NVARCHAR(20)) AS Details, - ps.value_delta / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS DetailsInt - FROM #PerfmonStats ps - INNER JOIN #PerfmonStats ps1 ON ps.object_name = ps1.object_name AND ps.counter_name = ps1.counter_name AND ps1.Pass = 1 - WHERE ps.Pass = 2 - AND ps.object_name = @ServiceName + ':SQL Statistics' - AND ps.counter_name = 'SQL Compilations/sec'; + BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 25',10,1) WITH NOWAIT; + END + + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, DetailsInt) + SELECT 25 AS CheckID, + 250 AS Priority, + 'Server Info' AS FindingGroup, + 'SQL Compilations per Sec' AS Finding, + 'http://www.BrentOzar.com/go/measure' AS URL, + CAST(ps.value_delta / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS NVARCHAR(20)) AS Details, + ps.value_delta / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS DetailsInt + FROM #PerfmonStats ps + INNER JOIN #PerfmonStats ps1 ON ps.object_name = ps1.object_name AND ps.counter_name = ps1.counter_name AND ps1.Pass = 1 + WHERE ps.Pass = 2 + AND ps.object_name = @ServiceName + ':SQL Statistics' + AND ps.counter_name = 'SQL Compilations/sec'; + END /* Server Info - SQL Re-Compilations/sec - CheckID 26 */ IF @ExpertMode = 1 - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, DetailsInt) - SELECT 26 AS CheckID, - 250 AS Priority, - 'Server Info' AS FindingGroup, - 'SQL Re-Compilations per Sec' AS Finding, - 'http://www.BrentOzar.com/go/measure' AS URL, - CAST(ps.value_delta / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS NVARCHAR(20)) AS Details, - ps.value_delta / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS DetailsInt - FROM #PerfmonStats ps - INNER JOIN #PerfmonStats ps1 ON ps.object_name = ps1.object_name AND ps.counter_name = ps1.counter_name AND ps1.Pass = 1 - WHERE ps.Pass = 2 - AND ps.object_name = @ServiceName + ':SQL Statistics' - AND ps.counter_name = 'SQL Re-Compilations/sec'; + BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 26',10,1) WITH NOWAIT; + END + + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, DetailsInt) + SELECT 26 AS CheckID, + 250 AS Priority, + 'Server Info' AS FindingGroup, + 'SQL Re-Compilations per Sec' AS Finding, + 'http://www.BrentOzar.com/go/measure' AS URL, + CAST(ps.value_delta / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS NVARCHAR(20)) AS Details, + ps.value_delta / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS DetailsInt + FROM #PerfmonStats ps + INNER JOIN #PerfmonStats ps1 ON ps.object_name = ps1.object_name AND ps.counter_name = ps1.counter_name AND ps1.Pass = 1 + WHERE ps.Pass = 2 + AND ps.object_name = @ServiceName + ':SQL Statistics' + AND ps.counter_name = 'SQL Re-Compilations/sec'; + END /* Server Info - Wait Time per Core per Sec - CheckID 20 */ IF @Seconds > 0 BEGIN; + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 20',10,1) WITH NOWAIT; + END; + WITH waits1(SampleTime, waits_ms) AS (SELECT SampleTime, SUM(ws1.wait_time_ms) FROM #WaitStats ws1 WHERE ws1.Pass = 1 GROUP BY SampleTime), waits2(SampleTime, waits_ms) AS (SELECT SampleTime, SUM(ws2.wait_time_ms) FROM #WaitStats ws2 WHERE ws2.Pass = 2 GROUP BY SampleTime), cores(cpu_count) AS (SELECT SUM(1) FROM sys.dm_os_schedulers WHERE status = 'VISIBLE ONLINE' AND is_online = 1) @@ -24078,8 +24334,13 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, as well get it now - whereas if we're checking 30+ seconds, it might get updated by the end of our sp_BlitzFirst session. */ IF @Seconds >= 30 - BEGIN + BEGIN /* Server Performance - High CPU Utilization CheckID 24 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 24',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) SELECT 24, 50, 'Server Performance', 'High CPU Utilization', CAST(100 - SystemIdle AS NVARCHAR(20)) + N'%. Ring buffer details: ' + CAST(record AS NVARCHAR(4000)), 100 - SystemIdle, 'http://www.BrentOzar.com/go/cpu' FROM ( @@ -24095,6 +24356,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, WHERE 100 - SystemIdle >= 50; /* Server Performance - CPU Utilization CheckID 23 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 23',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) SELECT 23, 250, 'Server Info', 'CPU Utilization', CAST(100 - SystemIdle AS NVARCHAR(20)) + N'%. Ring buffer details: ' + CAST(record AS NVARCHAR(4000)), 100 - SystemIdle, 'http://www.BrentOzar.com/go/cpu' FROM ( @@ -24108,7 +24374,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, ORDER BY timestamp DESC) AS rb ) AS y; - END; /* IF @Seconds >= 30 */ + END; /* IF @Seconds >= 30 */ /* If we didn't find anything, apologize. */ @@ -24168,6 +24434,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, ); /* Outdated sp_BlitzFirst - sp_BlitzFirst is Over 6 Months Old - CheckID 27 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 27',10,1) WITH NOWAIT; + END + IF DATEDIFF(MM, @VersionDate, SYSDATETIMEOFFSET()) > 6 BEGIN INSERT INTO #BlitzFirstResults @@ -24259,6 +24530,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, ELSE BEGIN /* No sp_BlitzCache found, or it's outdated - CheckID 36 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 36',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults ( CheckID , Priority , @@ -25581,7 +25857,7 @@ AS SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '7.99999', @VersionDate = '20201211'; +SELECT @Version = '8.0', @VersionDate = '20210117'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -25589,7 +25865,9 @@ BEGIN RETURN; END; -IF @Help = 1 PRINT ' +IF @Help = 1 +BEGIN +PRINT ' /* sp_BlitzIndex from http://FirstResponderKit.org @@ -25616,7 +25894,7 @@ Unknown limitations of this version: MIT License -Copyright (c) 2020 Brent Ozar Unlimited +Copyright (c) 2021 Brent Ozar Unlimited Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -25636,7 +25914,8 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. '; - +RETURN; +END; /* @Help = 1 */ DECLARE @ScriptVersionName NVARCHAR(50); DECLARE @DaysUptime NUMERIC(23,2); @@ -27118,15 +27397,15 @@ BEGIN TRY FROM ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_details AS id_inner CROSS APPLY( SELECT LTRIM(RTRIM(v.value(''(./text())[1]'', ''varchar(max)''))) AS ColumnName, ''Equality'' AS IndexColumnType - FROM (VALUES (CONVERT(XML, N'''' + REPLACE(CAST(id_inner.equality_columns AS nvarchar(max)), N'','', N'''') + N''''))) x(n) + FROM (VALUES (CONVERT(XML, N'''' + REPLACE((SELECT CAST(id_inner.equality_columns AS nvarchar(max)) FOR XML PATH('''')), N'','', N'''') + N''''))) x(n) CROSS APPLY n.nodes(''x'') node(v) UNION ALL SELECT LTRIM(RTRIM(v.value(N''(./text())[1]'', ''varchar(max)''))) AS ColumnName, ''Inequality'' AS IndexColumnType - FROM (VALUES (CONVERT(XML, N'''' + REPLACE(CAST(id_inner.inequality_columns AS nvarchar(max)), N'','', N'''') + N''''))) x(n) + FROM (VALUES (CONVERT(XML, N'''' + REPLACE((SELECT CAST(id_inner.inequality_columns AS nvarchar(max)) FOR XML PATH('''')), N'','', N'''') + N''''))) x(n) CROSS APPLY n.nodes(''x'') node(v) UNION ALL SELECT LTRIM(RTRIM(v.value(''(./text())[1]'', ''varchar(max)''))) AS ColumnName, ''Included'' AS IndexColumnType - FROM (VALUES (CONVERT(XML, N'''' + REPLACE(CAST(id_inner.included_columns AS nvarchar(max)), N'','', N'''') + N''''))) x(n) + FROM (VALUES (CONVERT(XML, N'''' + REPLACE((SELECT CAST(id_inner.included_columns AS nvarchar(max)) FOR XML PATH('''')), N'','', N'''') + N''''))) x(n) CROSS APPLY n.nodes(''x'') node(v) )AS cn_inner' + /*split the string otherwise dsql cuts some of it out*/ @@ -27140,15 +27419,15 @@ BEGIN TRY FROM ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_details AS id CROSS APPLY( SELECT LTRIM(RTRIM(v.value(''(./text())[1]'', ''varchar(max)''))) AS ColumnName, ''Equality'' AS IndexColumnType - FROM (VALUES (CONVERT(XML, N'''' + REPLACE(CAST(id.equality_columns AS nvarchar(max)), N'','', N'''') + N''''))) x(n) + FROM (VALUES (CONVERT(XML, N'''' + REPLACE((SELECT CAST(id.equality_columns AS nvarchar(max)) FOR XML PATH('''')), N'','', N'''') + N''''))) x(n) CROSS APPLY n.nodes(''x'') node(v) UNION ALL SELECT LTRIM(RTRIM(v.value(''(./text())[1]'', ''varchar(max)''))) AS ColumnName, ''Inequality'' AS IndexColumnType - FROM (VALUES (CONVERT(XML, N'''' + REPLACE(CAST(id.inequality_columns AS nvarchar(max)), N'','', N'''') + N''''))) x(n) + FROM (VALUES (CONVERT(XML, N'''' + REPLACE((SELECT CAST(id.inequality_columns AS nvarchar(max)) FOR XML PATH('''')), N'','', N'''') + N''''))) x(n) CROSS APPLY n.nodes(''x'') node(v) UNION ALL SELECT LTRIM(RTRIM(v.value(''(./text())[1]'', ''varchar(max)''))) AS ColumnName, ''Included'' AS IndexColumnType - FROM (VALUES (CONVERT(XML, N'''' + REPLACE(CAST(id.included_columns AS nvarchar(max)), N'','', N'''') + N''''))) x(n) + FROM (VALUES (CONVERT(XML, N'''' + REPLACE((SELECT CAST(id.included_columns AS nvarchar(max)) FOR XML PATH('''')), N'','', N'''') + N''''))) x(n) CROSS APPLY n.nodes(''x'') node(v) )AS cn GROUP BY id.index_handle,id.object_id,cn.IndexColumnType @@ -31104,14 +31383,16 @@ BEGIN SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '2.99999', @VersionDate = '20201211'; +SELECT @Version = '8.0', @VersionDate = '20210117'; IF(@VersionCheckMode = 1) BEGIN RETURN; END; - IF @Help = 1 PRINT ' + IF @Help = 1 + BEGIN + PRINT ' /* sp_BlitzLock from http://FirstResponderKit.org @@ -31166,7 +31447,7 @@ END; MIT License - Copyright (c) 2020 Brent Ozar Unlimited + Copyright (c) 2021 Brent Ozar Unlimited Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -31187,8 +31468,9 @@ END; SOFTWARE. */'; - - + RETURN; + END; /* @Help = 1 */ + DECLARE @ProductVersion NVARCHAR(128); DECLARE @ProductVersionMajor FLOAT; DECLARE @ProductVersionMinor INT; @@ -31371,9 +31653,24 @@ You need to use an Azure storage account, and the path has to look like this: ht AND LEFT(CAST(SERVERPROPERTY('MachineName') AS VARCHAR(8000)), 8) <> 'EC2AMAZ-' AND LEFT(CAST(SERVERPROPERTY('ServerName') AS VARCHAR(8000)), 8) <> 'EC2AMAZ-' AND db_id('rdsadmin') IS NULL - BEGIN - UPDATE STATISTICS #t WITH ROWCOUNT = 100000000, PAGECOUNT = 100000000; - END + BEGIN; + BEGIN TRY; + UPDATE STATISTICS #t WITH ROWCOUNT = 100000000, PAGECOUNT = 100000000; + END TRY + BEGIN CATCH; + /* Misleading error returned, if run without permissions to update statistics the error returned is "Cannot find object". + Catching specific error, and returning message with better info. If any other error is returned, then throw as normal */ + IF (ERROR_NUMBER() = 1088) + BEGIN; + SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + RAISERROR('Cannot run UPDATE STATISTICS on temp table without db_owner or sysadmin permissions %s', 0, 1, @d) WITH NOWAIT; + END; + ELSE + BEGIN; + THROW; + END; + END CATCH; + END; /*Grab the initial set of XML to parse*/ SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); @@ -32833,7 +33130,7 @@ BEGIN /*First BEGIN*/ SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '3.99999', @VersionDate = '20201211'; +SELECT @Version = '8.0', @VersionDate = '20210117'; IF(@VersionCheckMode = 1) BEGIN RETURN; @@ -32919,7 +33216,7 @@ IF @Help = 1 MIT License - Copyright (c) 2020 Brent Ozar Unlimited + Copyright (c) 2021 Brent Ozar Unlimited Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -38560,7 +38857,7 @@ BEGIN SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '7.99999', @VersionDate = '20201211'; + SELECT @Version = '8.0', @VersionDate = '20210117'; IF(@VersionCheckMode = 1) BEGIN @@ -38570,6 +38867,7 @@ BEGIN IF @Help = 1 + BEGIN PRINT ' sp_BlitzWho from http://FirstResponderKit.org @@ -38587,7 +38885,7 @@ Known limitations of this version: MIT License -Copyright (c) 2020 Brent Ozar Unlimited +Copyright (c) 2021 Brent Ozar Unlimited Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -38607,6 +38905,8 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. '; +RETURN; +END; /* @Help = 1 */ /* Get the major and minor build numbers */ DECLARE @ProductVersion NVARCHAR(128) @@ -39101,7 +39401,7 @@ BEGIN /* Think of the StringToExecute as starting with this, but we'll set this up later depending on whether we're doing an insert or a select: SELECT @StringToExecute = N'SELECT GETDATE() AS run_date , */ - SET @StringToExecute = N'COALESCE( RIGHT(''00'' + CONVERT(VARCHAR(20), (ABS(r.total_elapsed_time) / 1000) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), (DATEADD(SECOND, (r.total_elapsed_time / 1000), 0) + DATEADD(MILLISECOND, (r.total_elapsed_time % 1000), 0)), 114), CONVERT(VARCHAR(20), DATEDIFF(SECOND, s.last_request_start_time, GETDATE()) / 86400) + '':'' + CONVERT(VARCHAR(20), DATEADD(SECOND, DATEDIFF(SECOND, s.last_request_start_time, GETDATE()), 0), 114) ) AS [elapsed_time] , + SET @StringToExecute = N'COALESCE( RIGHT(''00'' + CONVERT(VARCHAR(20), (ABS(r.total_elapsed_time) / 1000) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), (DATEADD(SECOND, (r.total_elapsed_time / 1000), 0) + DATEADD(MILLISECOND, (r.total_elapsed_time % 1000), 0)), 114), RIGHT(''00'' + CONVERT(VARCHAR(20), DATEDIFF(SECOND, s.last_request_start_time, GETDATE()) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), DATEADD(SECOND, DATEDIFF(SECOND, s.last_request_start_time, GETDATE()), 0), 114) ) AS [elapsed_time] , s.session_id , COALESCE(DB_NAME(r.database_id), DB_NAME(blocked.dbid), ''N/A'') AS database_name, ISNULL(SUBSTRING(dest.text, @@ -39306,7 +39606,7 @@ IF @ProductVersionMajor >= 11 /* Think of the StringToExecute as starting with this, but we'll set this up later depending on whether we're doing an insert or a select: SELECT @StringToExecute = N'SELECT GETDATE() AS run_date , */ - SELECT @StringToExecute = N'COALESCE( RIGHT(''00'' + CONVERT(VARCHAR(20), (ABS(r.total_elapsed_time) / 1000) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), (DATEADD(SECOND, (r.total_elapsed_time / 1000), 0) + DATEADD(MILLISECOND, (r.total_elapsed_time % 1000), 0)), 114), CONVERT(VARCHAR(20), DATEDIFF(SECOND, s.last_request_start_time, GETDATE()) / 86400) + '':'' + CONVERT(VARCHAR(20), DATEADD(SECOND, DATEDIFF(SECOND, s.last_request_start_time, GETDATE()), 0), 114) ) AS [elapsed_time] , + SELECT @StringToExecute = N'COALESCE( RIGHT(''00'' + CONVERT(VARCHAR(20), (ABS(r.total_elapsed_time) / 1000) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), (DATEADD(SECOND, (r.total_elapsed_time / 1000), 0) + DATEADD(MILLISECOND, (r.total_elapsed_time % 1000), 0)), 114), RIGHT(''00'' + CONVERT(VARCHAR(20), DATEDIFF(SECOND, s.last_request_start_time, GETDATE()) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), DATEADD(SECOND, DATEDIFF(SECOND, s.last_request_start_time, GETDATE()), 0), 114) ) AS [elapsed_time] , s.session_id , COALESCE(DB_NAME(r.database_id), DB_NAME(blocked.dbid), ''N/A'') AS database_name, ISNULL(SUBSTRING(dest.text, @@ -39796,7 +40096,7 @@ SET NOCOUNT ON; /*Versioning details*/ -SELECT @Version = '7.99999', @VersionDate = '20201211'; +SELECT @Version = '8.0', @VersionDate = '20210117'; IF(@VersionCheckMode = 1) BEGIN @@ -39828,7 +40128,7 @@ BEGIN MIT License - Copyright (c) 2020 Brent Ozar Unlimited + Copyright (c) 2021 Brent Ozar Unlimited Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -41265,7 +41565,7 @@ AS BEGIN SET NOCOUNT ON; - SELECT @Version = '2.99999', @VersionDate = '20201211'; + SELECT @Version = '8.0', @VersionDate = '20210117'; IF(@VersionCheckMode = 1) BEGIN @@ -41298,7 +41598,7 @@ BEGIN MIT License - Copyright (c) 2019 Brent Ozar Unlimited + Copyright (c) 2021 Brent Ozar Unlimited Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -41398,10 +41698,12 @@ AS (SELECT V.SrcList , 0 AS Quoted FROM (VALUES ('In', @database_list + ','), ('Out', @exclude_list + ',')) AS V (SrcList, DBList) UNION ALL - SELECT C.SrcList - , IIF(V.Found = '[', '', SUBSTRING(C.DBList, 1, V.Place - 1))/*remove initial [*/ + SELECT C.SrcList +-- , IIF(V.Found = '[', '', SUBSTRING(C.DBList, 1, V.Place - 1))/*remove initial [*/ + , CASE WHEN V.Found = '[' THEN '' ELSE SUBSTRING(C.DBList, 1, V.Place - 1) END /*remove initial [*/ , STUFF(C.DBList, 1, V.Place, '') - , IIF(V.Found = '[', 1, 0) +-- , IIF(V.Found = '[', 1, 0) + ,Case WHEN V.Found = '[' THEN 1 ELSE 0 END , 0 FROM C CROSS APPLY @@ -41410,25 +41712,28 @@ AS (SELECT V.SrcList AND C.InBracket = 0 UNION ALL SELECT C.SrcList - , CONCAT(C.Name, SUBSTRING(C.DBList, 1, V.Place + W.DoubleBracket - 1)) /*Accumulates only one ] if escaped]] or none if end]*/ +-- , CONCAT(C.Name, SUBSTRING(C.DBList, 1, V.Place + W.DoubleBracket - 1)) /*Accumulates only one ] if escaped]] or none if end]*/ + , ISNULL(C.Name,'') + ISNULL(SUBSTRING(C.DBList, 1, V.Place + W.DoubleBracket - 1),'') /*Accumulates only one ] if escaped]] or none if end]*/ , STUFF(C.DBList, 1, V.Place + W.DoubleBracket, '') , W.DoubleBracket , 1 FROM C CROSS APPLY (VALUES (CHARINDEX(']', C.DBList))) AS V (Place) - CROSS APPLY (VALUES (IIF(SUBSTRING(C.DBList, V.Place + 1, 1) = ']', 1, 0))) AS W (DoubleBracket) + -- CROSS APPLY (VALUES (IIF(SUBSTRING(C.DBList, V.Place + 1, 1) = ']', 1, 0))) AS W (DoubleBracket) + CROSS APPLY (VALUES (CASE WHEN SUBSTRING(C.DBList, V.Place + 1, 1) = ']' THEN 1 ELSE 0 END)) AS W (DoubleBracket) WHERE C.DBList > '' AND C.InBracket = 1) , F AS (SELECT C.SrcList - , IIF(C.Quoted = 0 - ,SUBSTRING(C.name, PATINDEX(@NoSpaces, name), DATALENGTH (name)/2 - PATINDEX(@NoSpaces, name) - PATINDEX(@NoSpaces, REVERSE(name))+2) - , C.Name) + , CASE WHEN C.Quoted = 0 THEN + SUBSTRING(C.Name, PATINDEX(@NoSpaces, Name), DATALENGTH (Name)/2 - PATINDEX(@NoSpaces, Name) - PATINDEX(@NoSpaces, REVERSE(Name))+2) + ELSE C.Name END AS name FROM C WHERE C.InBracket = 0 - AND C.Name > '') - SELECT d.database_id + AND C.Name > '') +INSERT #ineachdb(id,name,is_distributor) +SELECT d.database_id , d.name , d.is_distributor FROM sys.databases AS d @@ -41562,6 +41867,7 @@ OPTION (MAXRECURSION 0); CLOSE dbs; DEALLOCATE dbs; END +GO IF (OBJECT_ID('dbo.SqlServerVersions') IS NULL) BEGIN @@ -41605,6 +41911,7 @@ DELETE FROM dbo.SqlServerVersions; INSERT INTO dbo.SqlServerVersions (MajorVersionNumber, MinorVersionNumber, Branch, [Url], ReleaseDate, MainstreamSupportEndDate, ExtendedSupportEndDate, MajorVersionName, MinorVersionName) VALUES + (15, 4073, 'CU8 GDR', 'https://support.microsoft.com/en-us/help/4583459', '2021-01-12', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 8 GDR '), (15, 4073, 'CU8', 'https://support.microsoft.com/en-us/help/4577194', '2020-10-01', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 8 '), (15, 4063, 'CU7', 'https://support.microsoft.com/en-us/help/4570012', '2020-09-02', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 7 '), (15, 4053, 'CU6', 'https://support.microsoft.com/en-us/help/4563110', '2020-08-04', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 6 '), @@ -41615,6 +41922,7 @@ VALUES (15, 4003, 'CU1', 'https://support.microsoft.com/en-us/help/4527376', '2020-01-07', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 1 '), (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 '), + (14, 3370, 'RTM CU22 GDR', 'https://support.microsoft.com/en-us/help/4583457', '2021-01-12', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 22 GDR'), (14, 3356, 'RTM CU22', 'https://support.microsoft.com/en-us/help/4577467', '2020-09-10', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 22'), (14, 3335, 'RTM CU21', 'https://support.microsoft.com/en-us/help/4557397', '2020-07-01', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 21'), (14, 3294, 'RTM CU20', 'https://support.microsoft.com/en-us/help/4541283', '2020-04-07', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 20'), @@ -41638,6 +41946,7 @@ VALUES (14, 3008, 'RTM CU2', 'https://support.microsoft.com/en-us/help/4052574', '2017-11-28', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 2'), (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 '), + (13, 5865, 'SP2 CU15 GDR', 'https://support.microsoft.com/en-us/help/4583461', '2021-01-12', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 15 GDR'), (13, 5850, 'SP2 CU15', 'https://support.microsoft.com/en-us/help/4577775', '2020-09-28', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 15'), (13, 5830, 'SP2 CU14', 'https://support.microsoft.com/en-us/help/4564903', '2020-08-06', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 14'), (13, 5820, 'SP2 CU13', 'https://support.microsoft.com/en-us/help/4549825', '2020-05-28', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 13'), @@ -41654,6 +41963,7 @@ VALUES (13, 5201, 'SP2 CU2 + Security Update', 'https://support.microsoft.com/en-us/help/4458621', '2018-08-21', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 2 + Security Update'), (13, 5153, 'SP2 CU2', 'https://support.microsoft.com/en-us/help/4340355', '2018-07-16', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 2'), (13, 5149, 'SP2 CU1', 'https://support.microsoft.com/en-us/help/4135048', '2018-05-30', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 1'), + (13, 5103, 'SP2 GDR', 'https://support.microsoft.com/en-us/help/4583460', '2021-01-12', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 GDR'), (13, 5026, 'SP2 ', 'https://support.microsoft.com/en-us/help/4052908', '2018-04-24', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 '), (13, 4574, 'SP1 CU15', 'https://support.microsoft.com/en-us/help/4495257', '2019-05-16', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 15'), (13, 4560, 'SP1 CU14', 'https://support.microsoft.com/en-us/help/4488535', '2019-03-19', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 14'), @@ -41682,10 +41992,13 @@ VALUES (13, 2164, 'RTM CU2', 'https://support.microsoft.com/en-us/help/3182270 ', '2016-09-22', '2018-01-09', '2018-01-09', 'SQL Server 2016', 'RTM Cumulative Update 2'), (13, 2149, 'RTM CU1', 'https://support.microsoft.com/en-us/help/3164674 ', '2016-07-25', '2018-01-09', '2018-01-09', 'SQL Server 2016', 'RTM Cumulative Update 1'), (13, 1601, 'RTM ', '', '2016-06-01', '2019-01-09', '2019-01-09', 'SQL Server 2016', 'RTM '), + (12, 6433, 'SP3 CU4 GDR', 'https://support.microsoft.com/en-us/help/4583462', '2021-01-12', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 4 GDR'), + (12, 6372, 'SP3 CU4 GDR', 'https://support.microsoft.com/en-us/help/4535288', '2020-02-11', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 4 GDR'), (12, 6329, 'SP3 CU4', 'https://support.microsoft.com/en-us/help/4500181', '2019-07-29', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 4'), (12, 6259, 'SP3 CU3', 'https://support.microsoft.com/en-us/help/4491539', '2019-04-16', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 3'), (12, 6214, 'SP3 CU2', 'https://support.microsoft.com/en-us/help/4482960', '2019-02-19', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 2'), (12, 6205, 'SP3 CU1', 'https://support.microsoft.com/en-us/help/4470220', '2018-12-12', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 1'), + (12, 6164, 'SP3 GDR', 'https://support.microsoft.com/en-us/help/4583463', '2021-01-12', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 GDR'), (12, 6024, 'SP3 ', 'https://support.microsoft.com/en-us/help/4022619', '2018-10-30', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 '), (12, 5687, 'SP2 CU18', 'https://support.microsoft.com/en-us/help/4500180', '2019-07-29', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 18'), (12, 5632, 'SP2 CU17', 'https://support.microsoft.com/en-us/help/4491540', '2019-04-16', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 17'), @@ -41741,6 +42054,7 @@ VALUES (12, 2269, 'RTM MS15-058: GDR Security Update ', 'https://support.microsoft.com/en-us/help/3045324', '2015-07-14', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM MS15-058: GDR Security Update '), (12, 2254, 'RTM MS14-044: GDR Security Update', 'https://support.microsoft.com/en-us/help/2977315', '2014-08-12', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM MS14-044: GDR Security Update'), (12, 2000, 'RTM ', '', '2014-04-01', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM '), + (11, 7507, 'SP4 GDR Security Update', 'https://support.microsoft.com/en-us/help/4583465', '2021-01-12', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'Service Pack 4 GDR Security Update for CVE-2021-1636'), (11, 7493, 'SP4 GDR Security Update', 'https://support.microsoft.com/en-us/help/4532098', '2020-02-11', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'Service Pack 4 GDR Security Update for CVE-2020-0618'), (11, 7469, 'SP4 On-Demand Hotfix Update', 'https://support.microsoft.com/en-us/help/4091266', '2018-03-28', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'Service Pack 4 SP4 On-Demand Hotfix Update'), (11, 7462, 'SP4 ADV180002: GDR Security Update', 'https://support.microsoft.com/en-us/help/4057116', '2018-01-12', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'Service Pack 4 ADV180002: GDR Security Update'), diff --git a/Install-Core-Blitz-No-Query-Store.sql b/Install-Core-Blitz-No-Query-Store.sql index b84e335b1..b2a1a8761 100755 --- a/Install-Core-Blitz-No-Query-Store.sql +++ b/Install-Core-Blitz-No-Query-Store.sql @@ -37,7 +37,7 @@ AS SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '7.99999', @VersionDate = '20201211'; + SELECT @Version = '8.0', @VersionDate = '20210117'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -45,7 +45,9 @@ AS RETURN; END; - IF @Help = 1 PRINT ' + IF @Help = 1 + BEGIN + PRINT ' /* sp_Blitz from http://FirstResponderKit.org @@ -90,9 +92,9 @@ AS tigertoolbox and are provided under the MIT license: https://github.com/Microsoft/tigertoolbox - All other copyrights for sp_Blitz are held by Brent Ozar Unlimited, 2020. + All other copyrights for sp_Blitz are held by Brent Ozar Unlimited, 2021. - Copyright (c) 2020 Brent Ozar Unlimited + Copyright (c) 2021 Brent Ozar Unlimited Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -113,6 +115,9 @@ AS SOFTWARE. */'; + RETURN; + END; /* @Help = 1 */ + ELSE IF @OutputType = 'SCHEMA' BEGIN SELECT FieldList = '[Priority] TINYINT, [FindingsGroup] VARCHAR(50), [Finding] VARCHAR(200), [DatabaseName] NVARCHAR(128), [URL] VARCHAR(200), [Details] NVARCHAR(4000), [QueryPlan] NVARCHAR(MAX), [QueryPlanFiltered] NVARCHAR(MAX), [CheckID] INT'; @@ -1665,6 +1670,7 @@ AS + '] has auto-shrink enabled. This setting can dramatically decrease performance.' ) AS Details FROM sys.databases WHERE is_auto_shrink_on = 1 + AND state <> 6 /* Offline */ AND name NOT IN ( SELECT DISTINCT DatabaseName FROM #SkipChecks @@ -1698,7 +1704,7 @@ AS FROM sys.databases WHERE page_verify_option < 2 AND name <> ''tempdb'' - AND state <> 1 /* Restoring */ + AND state NOT IN (1, 6) /* Restoring, Offline */ and name not in (select distinct DatabaseName from #SkipChecks WHERE CheckID IS NULL OR CheckID = 14) OPTION (RECOMPILE);'; IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; @@ -9460,7 +9466,7 @@ AS SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '3.99999', @VersionDate = '20201211'; + SELECT @Version = '8.0', @VersionDate = '20210117'; IF(@VersionCheckMode = 1) BEGIN @@ -9507,7 +9513,7 @@ AS MIT License - Copyright (c) 2020 Brent Ozar Unlimited + Copyright (c) 2021 Brent Ozar Unlimited Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -11239,7 +11245,7 @@ BEGIN SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '7.99999', @VersionDate = '20201211'; +SELECT @Version = '8.0', @VersionDate = '20210117'; IF(@VersionCheckMode = 1) @@ -11247,7 +11253,9 @@ BEGIN RETURN; END; -IF @Help = 1 PRINT ' +IF @Help = 1 +BEGIN +PRINT ' sp_BlitzCache from http://FirstResponderKit.org This script displays your most resource-intensive queries from the plan cache, @@ -11275,7 +11283,7 @@ https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/ MIT License -Copyright (c) 2020 Brent Ozar Unlimited +Copyright (c) 2021 Brent Ozar Unlimited Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -11298,8 +11306,8 @@ SOFTWARE. DECLARE @nl NVARCHAR(2) = NCHAR(13) + NCHAR(10) ; -IF @Help = 1 -BEGIN +--IF @Help = 1 /* We're still under a @Help = 1 BEGIN from above */ +--BEGIN SELECT N'@Help' AS [Parameter Name] , N'BIT' AS [Data Type] , N'Displays this help message.' AS [Parameter Description] @@ -18419,14 +18427,16 @@ BEGIN SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '7.99999', @VersionDate = '20201211'; +SELECT @Version = '8.0', @VersionDate = '20210117'; IF(@VersionCheckMode = 1) BEGIN RETURN; END; -IF @Help = 1 PRINT ' +IF @Help = 1 +BEGIN +PRINT ' sp_BlitzFirst from http://FirstResponderKit.org This script gives you a prioritized list of why your SQL Server is slow right now. @@ -18457,7 +18467,7 @@ https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/ MIT License -Copyright (c) 2020 Brent Ozar Unlimited +Copyright (c) 2021 Brent Ozar Unlimited Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -18478,7 +18488,8 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. '; - +RETURN; +END; /* @Help = 1 */ RAISERROR('Setting up configuration variables',10,1) WITH NOWAIT; DECLARE @StringToExecute NVARCHAR(MAX), @@ -19739,6 +19750,16 @@ BEGIN AND counters.[object_name] COLLATE SQL_Latin1_General_CP1_CI_AS = RTRIM(dmv.[object_name]) COLLATE SQL_Latin1_General_CP1_CI_AS AND (counters.[instance_name] IS NULL OR counters.[instance_name] COLLATE SQL_Latin1_General_CP1_CI_AS = RTRIM(dmv.[instance_name]) COLLATE SQL_Latin1_General_CP1_CI_AS); + /* For Github #2743: */ + CREATE TABLE #TempdbOperationalStats (object_id BIGINT PRIMARY KEY CLUSTERED, + forwarded_fetch_count BIGINT); + INSERT INTO #TempdbOperationalStats (object_id, forwarded_fetch_count) + SELECT object_id, forwarded_fetch_count + FROM tempdb.sys.dm_db_index_operational_stats(DB_ID('tempdb'), NULL, NULL, NULL) os + WHERE os.database_id = DB_ID('tempdb') + AND os.forwarded_fetch_count > 100; + + /* If they want to run sp_BlitzWho and export to table, go for it. */ IF @OutputTableNameBlitzWho IS NOT NULL AND @OutputDatabaseName IS NOT NULL @@ -19756,40 +19777,47 @@ BEGIN /* Maintenance Tasks Running - Backup Running - CheckID 1 */ IF @Seconds > 0 - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, QueryHash) - SELECT 1 AS CheckID, - 1 AS Priority, - 'Maintenance Tasks Running' AS FindingGroup, - 'Backup Running' AS Finding, - 'http://www.BrentOzar.com/askbrent/backups/' AS URL, - 'Backup of ' + DB_NAME(db.resource_database_id) + ' database (' + (SELECT CAST(CAST(SUM(size * 8.0 / 1024 / 1024) AS BIGINT) AS NVARCHAR) FROM #MasterFiles WHERE database_id = db.resource_database_id) + 'GB) ' + @LineFeed - + CAST(r.percent_complete AS NVARCHAR(100)) + '% complete, has been running since ' + CAST(r.start_time AS NVARCHAR(100)) + '. ' + @LineFeed - + CASE WHEN COALESCE(s.nt_user_name, s.login_name) IS NOT NULL THEN (' Login: ' + COALESCE(s.nt_user_name, s.login_name) + ' ') ELSE '' END AS Details, - 'KILL ' + CAST(r.session_id AS NVARCHAR(100)) + ';' AS HowToStopIt, - pl.query_plan AS QueryPlan, - r.start_time AS StartTime, - s.login_name AS LoginName, - s.nt_user_name AS NTUserName, - s.[program_name] AS ProgramName, - s.[host_name] AS HostName, - db.[resource_database_id] AS DatabaseID, - DB_NAME(db.resource_database_id) AS DatabaseName, - 0 AS OpenTransactionCount, - r.query_hash - FROM sys.dm_exec_requests r - INNER JOIN sys.dm_exec_connections c ON r.session_id = c.session_id - INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id - INNER JOIN ( - SELECT DISTINCT request_session_id, resource_database_id - FROM sys.dm_tran_locks - WHERE resource_type = N'DATABASE' - AND request_mode = N'S' - AND request_status = N'GRANT' - AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.session_id = db.request_session_id - CROSS APPLY sys.dm_exec_query_plan(r.plan_handle) pl - WHERE r.command LIKE 'BACKUP%' - AND r.start_time <= DATEADD(minute, -5, GETDATE()) - AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs); + BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 1',10,1) WITH NOWAIT; + END + + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, QueryHash) + SELECT 1 AS CheckID, + 1 AS Priority, + 'Maintenance Tasks Running' AS FindingGroup, + 'Backup Running' AS Finding, + 'http://www.BrentOzar.com/askbrent/backups/' AS URL, + 'Backup of ' + DB_NAME(db.resource_database_id) + ' database (' + (SELECT CAST(CAST(SUM(size * 8.0 / 1024 / 1024) AS BIGINT) AS NVARCHAR) FROM #MasterFiles WHERE database_id = db.resource_database_id) + 'GB) ' + @LineFeed + + CAST(r.percent_complete AS NVARCHAR(100)) + '% complete, has been running since ' + CAST(r.start_time AS NVARCHAR(100)) + '. ' + @LineFeed + + CASE WHEN COALESCE(s.nt_user_name, s.login_name) IS NOT NULL THEN (' Login: ' + COALESCE(s.nt_user_name, s.login_name) + ' ') ELSE '' END AS Details, + 'KILL ' + CAST(r.session_id AS NVARCHAR(100)) + ';' AS HowToStopIt, + pl.query_plan AS QueryPlan, + r.start_time AS StartTime, + s.login_name AS LoginName, + s.nt_user_name AS NTUserName, + s.[program_name] AS ProgramName, + s.[host_name] AS HostName, + db.[resource_database_id] AS DatabaseID, + DB_NAME(db.resource_database_id) AS DatabaseName, + 0 AS OpenTransactionCount, + r.query_hash + FROM sys.dm_exec_requests r + INNER JOIN sys.dm_exec_connections c ON r.session_id = c.session_id + INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id + INNER JOIN ( + SELECT DISTINCT request_session_id, resource_database_id + FROM sys.dm_tran_locks + WHERE resource_type = N'DATABASE' + AND request_mode = N'S' + AND request_status = N'GRANT' + AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.session_id = db.request_session_id + CROSS APPLY sys.dm_exec_query_plan(r.plan_handle) pl + WHERE r.command LIKE 'BACKUP%' + AND r.start_time <= DATEADD(minute, -5, GETDATE()) + AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs); + END /* If there's a backup running, add details explaining how long full backup has been taking in the last month. */ IF @Seconds > 0 AND CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) <> 'SQL Azure' @@ -19801,109 +19829,132 @@ BEGIN /* Maintenance Tasks Running - DBCC CHECK* Running - CheckID 2 */ IF @Seconds > 0 AND EXISTS(SELECT * FROM sys.dm_exec_requests WHERE command LIKE 'DBCC%') - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, QueryHash) - SELECT 2 AS CheckID, - 1 AS Priority, - 'Maintenance Tasks Running' AS FindingGroup, - 'DBCC CHECK* Running' AS Finding, - 'http://www.BrentOzar.com/askbrent/dbcc/' AS URL, - 'Corruption check of ' + DB_NAME(db.resource_database_id) + ' database (' + (SELECT CAST(CAST(SUM(size * 8.0 / 1024 / 1024) AS BIGINT) AS NVARCHAR) FROM #MasterFiles WHERE database_id = db.resource_database_id) + 'GB) 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, - s.login_name AS LoginName, - s.nt_user_name AS NTUserName, - s.[program_name] AS ProgramName, - s.[host_name] AS HostName, - db.[resource_database_id] AS DatabaseID, - DB_NAME(db.resource_database_id) AS DatabaseName, - 0 AS OpenTransactionCount, - r.query_hash - FROM sys.dm_exec_requests r - INNER JOIN sys.dm_exec_connections c ON r.session_id = c.session_id - INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id - INNER JOIN (SELECT DISTINCT l.request_session_id, l.resource_database_id - FROM sys.dm_tran_locks l - INNER JOIN sys.databases d ON l.resource_database_id = d.database_id - WHERE l.resource_type = N'DATABASE' - AND l.request_mode = N'S' - AND l.request_status = N'GRANT' - AND l.request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.session_id = db.request_session_id - OUTER APPLY sys.dm_exec_query_plan(r.plan_handle) pl - OUTER APPLY sys.dm_exec_sql_text(r.sql_handle) AS t - WHERE r.command LIKE 'DBCC%' - AND CAST(t.text AS NVARCHAR(4000)) NOT LIKE '%dm_db_index_physical_stats%' - AND CAST(t.text AS NVARCHAR(4000)) NOT LIKE '%ALTER INDEX%' - AND CAST(t.text AS NVARCHAR(4000)) NOT LIKE '%fileproperty%' - AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs); + BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 2',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, QueryHash) + SELECT 2 AS CheckID, + 1 AS Priority, + 'Maintenance Tasks Running' AS FindingGroup, + 'DBCC CHECK* Running' AS Finding, + 'http://www.BrentOzar.com/askbrent/dbcc/' AS URL, + 'Corruption check of ' + DB_NAME(db.resource_database_id) + ' database (' + (SELECT CAST(CAST(SUM(size * 8.0 / 1024 / 1024) AS BIGINT) AS NVARCHAR) FROM #MasterFiles WHERE database_id = db.resource_database_id) + 'GB) 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, + s.login_name AS LoginName, + s.nt_user_name AS NTUserName, + s.[program_name] AS ProgramName, + s.[host_name] AS HostName, + db.[resource_database_id] AS DatabaseID, + DB_NAME(db.resource_database_id) AS DatabaseName, + 0 AS OpenTransactionCount, + r.query_hash + FROM sys.dm_exec_requests r + INNER JOIN sys.dm_exec_connections c ON r.session_id = c.session_id + INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id + INNER JOIN (SELECT DISTINCT l.request_session_id, l.resource_database_id + FROM sys.dm_tran_locks l + INNER JOIN sys.databases d ON l.resource_database_id = d.database_id + WHERE l.resource_type = N'DATABASE' + AND l.request_mode = N'S' + AND l.request_status = N'GRANT' + AND l.request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.session_id = db.request_session_id + OUTER APPLY sys.dm_exec_query_plan(r.plan_handle) pl + OUTER APPLY sys.dm_exec_sql_text(r.sql_handle) AS t + WHERE r.command LIKE 'DBCC%' + AND CAST(t.text AS NVARCHAR(4000)) NOT LIKE '%dm_db_index_physical_stats%' + AND CAST(t.text AS NVARCHAR(4000)) NOT LIKE '%ALTER INDEX%' + AND CAST(t.text AS NVARCHAR(4000)) NOT LIKE '%fileproperty%' + AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs); + END /* Maintenance Tasks Running - Restore Running - CheckID 3 */ IF @Seconds > 0 - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, QueryHash) - SELECT 3 AS CheckID, - 1 AS Priority, - 'Maintenance Tasks Running' AS FindingGroup, - 'Restore Running' AS Finding, - 'http://www.BrentOzar.com/askbrent/backups/' AS URL, - 'Restore of ' + DB_NAME(db.resource_database_id) + ' database (' + (SELECT CAST(CAST(SUM(size * 8.0 / 1024 / 1024) AS BIGINT) AS NVARCHAR) FROM #MasterFiles WHERE database_id = db.resource_database_id) + '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, - s.login_name AS LoginName, - s.nt_user_name AS NTUserName, - s.[program_name] AS ProgramName, - s.[host_name] AS HostName, - db.[resource_database_id] AS DatabaseID, - DB_NAME(db.resource_database_id) AS DatabaseName, - 0 AS OpenTransactionCount, - r.query_hash - FROM sys.dm_exec_requests r - INNER JOIN sys.dm_exec_connections c ON r.session_id = c.session_id - INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id - INNER JOIN ( - SELECT DISTINCT request_session_id, resource_database_id - FROM sys.dm_tran_locks - WHERE resource_type = N'DATABASE' - AND request_mode = N'S' - AND request_status = N'GRANT') AS db ON s.session_id = db.request_session_id - CROSS APPLY sys.dm_exec_query_plan(r.plan_handle) pl - WHERE r.command LIKE 'RESTORE%' - AND s.program_name <> 'SQL Server Log Shipping' - AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs); + BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 3',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, QueryHash) + SELECT 3 AS CheckID, + 1 AS Priority, + 'Maintenance Tasks Running' AS FindingGroup, + 'Restore Running' AS Finding, + 'http://www.BrentOzar.com/askbrent/backups/' AS URL, + 'Restore of ' + DB_NAME(db.resource_database_id) + ' database (' + (SELECT CAST(CAST(SUM(size * 8.0 / 1024 / 1024) AS BIGINT) AS NVARCHAR) FROM #MasterFiles WHERE database_id = db.resource_database_id) + '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, + s.login_name AS LoginName, + s.nt_user_name AS NTUserName, + s.[program_name] AS ProgramName, + s.[host_name] AS HostName, + db.[resource_database_id] AS DatabaseID, + DB_NAME(db.resource_database_id) AS DatabaseName, + 0 AS OpenTransactionCount, + r.query_hash + FROM sys.dm_exec_requests r + INNER JOIN sys.dm_exec_connections c ON r.session_id = c.session_id + INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id + INNER JOIN ( + SELECT DISTINCT request_session_id, resource_database_id + FROM sys.dm_tran_locks + WHERE resource_type = N'DATABASE' + AND request_mode = N'S' + AND request_status = N'GRANT') AS db ON s.session_id = db.request_session_id + CROSS APPLY sys.dm_exec_query_plan(r.plan_handle) pl + WHERE r.command LIKE 'RESTORE%' + AND s.program_name <> 'SQL Server Log Shipping' + AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs); + END /* SQL Server Internal Maintenance - Database File Growing - CheckID 4 */ IF @Seconds > 0 - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount) - SELECT 4 AS CheckID, - 1 AS Priority, - 'SQL Server Internal Maintenance' AS FindingGroup, - 'Database File Growing' AS Finding, - 'http://www.BrentOzar.com/go/instant' AS URL, - 'SQL Server is waiting for Windows to provide storage space for a database restore, a data file growth, or a log file growth. This task has been running since ' + CAST(r.start_time AS NVARCHAR(100)) + '.' + @LineFeed + 'Check the query plan (expert mode) to identify the database involved.' AS Details, - 'Unfortunately, you can''t stop this, but you can prevent it next time. Check out http://www.BrentOzar.com/go/instant for details.' AS HowToStopIt, - pl.query_plan AS QueryPlan, - r.start_time AS StartTime, - s.login_name AS LoginName, - s.nt_user_name AS NTUserName, - s.[program_name] AS ProgramName, - s.[host_name] AS HostName, - NULL AS DatabaseID, - NULL AS DatabaseName, - 0 AS OpenTransactionCount - FROM sys.dm_os_waiting_tasks t - INNER JOIN sys.dm_exec_connections c ON t.session_id = c.session_id - INNER JOIN sys.dm_exec_requests r ON t.session_id = r.session_id - INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id - CROSS APPLY sys.dm_exec_query_plan(r.plan_handle) pl - WHERE t.wait_type = 'PREEMPTIVE_OS_WRITEFILEGATHER' - AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs); + BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 4',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount) + SELECT 4 AS CheckID, + 1 AS Priority, + 'SQL Server Internal Maintenance' AS FindingGroup, + 'Database File Growing' AS Finding, + 'http://www.BrentOzar.com/go/instant' AS URL, + 'SQL Server is waiting for Windows to provide storage space for a database restore, a data file growth, or a log file growth. This task has been running since ' + CAST(r.start_time AS NVARCHAR(100)) + '.' + @LineFeed + 'Check the query plan (expert mode) to identify the database involved.' AS Details, + 'Unfortunately, you can''t stop this, but you can prevent it next time. Check out http://www.BrentOzar.com/go/instant for details.' AS HowToStopIt, + pl.query_plan AS QueryPlan, + r.start_time AS StartTime, + s.login_name AS LoginName, + s.nt_user_name AS NTUserName, + s.[program_name] AS ProgramName, + s.[host_name] AS HostName, + NULL AS DatabaseID, + NULL AS DatabaseName, + 0 AS OpenTransactionCount + FROM sys.dm_os_waiting_tasks t + INNER JOIN sys.dm_exec_connections c ON t.session_id = c.session_id + INNER JOIN sys.dm_exec_requests r ON t.session_id = r.session_id + INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id + CROSS APPLY sys.dm_exec_query_plan(r.plan_handle) pl + WHERE t.wait_type = 'PREEMPTIVE_OS_WRITEFILEGATHER' + AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs); + END /* Query Problems - Long-Running Query Blocking Others - CheckID 5 */ IF SERVERPROPERTY('Edition') <> 'SQL Azure' AND @Seconds > 0 AND EXISTS(SELECT * FROM sys.dm_os_waiting_tasks WHERE wait_type LIKE 'LCK%' AND wait_duration_ms > 30000) BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 5',10,1) WITH NOWAIT; + END + SET @StringToExecute = N'INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, QueryText, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, QueryHash) SELECT 5 AS CheckID, 1 AS Priority, @@ -19942,6 +19993,11 @@ BEGIN /* Query Problems - Plan Cache Erased Recently - CheckID 7 */ IF DATEADD(mi, -15, SYSDATETIME()) < (SELECT TOP 1 creation_time FROM sys.dm_exec_query_stats ORDER BY creation_time) BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 7',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) SELECT TOP 1 7 AS CheckID, 50 AS Priority, @@ -19961,38 +20017,44 @@ BEGIN /* Query Problems - Sleeping Query with Open Transactions - CheckID 8 */ IF @Seconds > 0 - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, OpenTransactionCount) - SELECT 8 AS CheckID, - 50 AS Priority, - 'Query Problems' AS FindingGroup, - 'Sleeping Query with Open Transactions' AS Finding, - 'http://www.brentozar.com/askbrent/sleeping-query-with-open-transactions/' AS URL, - 'Database: ' + DB_NAME(db.resource_database_id) + @LineFeed + 'Host: ' + s.[host_name] + @LineFeed + 'Program: ' + s.[program_name] + @LineFeed + 'Asleep with open transactions and locks since ' + CAST(s.last_request_end_time AS NVARCHAR(100)) + '. ' AS Details, - 'KILL ' + CAST(s.session_id AS NVARCHAR(100)) + ';' AS HowToStopIt, - s.last_request_start_time AS StartTime, - s.login_name AS LoginName, - s.nt_user_name AS NTUserName, - s.[program_name] AS ProgramName, - s.[host_name] AS HostName, - db.[resource_database_id] AS DatabaseID, - DB_NAME(db.resource_database_id) AS DatabaseName, - (SELECT TOP 1 [text] FROM sys.dm_exec_sql_text(c.most_recent_sql_handle)) AS QueryText, - sessions_with_transactions.open_transaction_count AS OpenTransactionCount - FROM (SELECT session_id, SUM(open_transaction_count) AS open_transaction_count FROM sys.dm_exec_requests WHERE open_transaction_count > 0 GROUP BY session_id) AS sessions_with_transactions - INNER JOIN sys.dm_exec_sessions s ON sessions_with_transactions.session_id = s.session_id - INNER JOIN sys.dm_exec_connections c ON s.session_id = c.session_id - INNER JOIN ( - SELECT DISTINCT request_session_id, resource_database_id - FROM sys.dm_tran_locks - WHERE resource_type = N'DATABASE' - AND request_mode = N'S' - AND request_status = N'GRANT' - AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.session_id = db.request_session_id - WHERE s.status = 'sleeping' - AND s.last_request_end_time < DATEADD(ss, -10, SYSDATETIME()) - AND EXISTS(SELECT * FROM sys.dm_tran_locks WHERE request_session_id = s.session_id - AND NOT (resource_type = N'DATABASE' AND request_mode = N'S' AND request_status = N'GRANT' AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE')); + BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 8',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, OpenTransactionCount) + SELECT 8 AS CheckID, + 50 AS Priority, + 'Query Problems' AS FindingGroup, + 'Sleeping Query with Open Transactions' AS Finding, + 'http://www.brentozar.com/askbrent/sleeping-query-with-open-transactions/' AS URL, + 'Database: ' + DB_NAME(db.resource_database_id) + @LineFeed + 'Host: ' + s.[host_name] + @LineFeed + 'Program: ' + s.[program_name] + @LineFeed + 'Asleep with open transactions and locks since ' + CAST(s.last_request_end_time AS NVARCHAR(100)) + '. ' AS Details, + 'KILL ' + CAST(s.session_id AS NVARCHAR(100)) + ';' AS HowToStopIt, + s.last_request_start_time AS StartTime, + s.login_name AS LoginName, + s.nt_user_name AS NTUserName, + s.[program_name] AS ProgramName, + s.[host_name] AS HostName, + db.[resource_database_id] AS DatabaseID, + DB_NAME(db.resource_database_id) AS DatabaseName, + (SELECT TOP 1 [text] FROM sys.dm_exec_sql_text(c.most_recent_sql_handle)) AS QueryText, + sessions_with_transactions.open_transaction_count AS OpenTransactionCount + FROM (SELECT session_id, SUM(open_transaction_count) AS open_transaction_count FROM sys.dm_exec_requests WHERE open_transaction_count > 0 GROUP BY session_id) AS sessions_with_transactions + INNER JOIN sys.dm_exec_sessions s ON sessions_with_transactions.session_id = s.session_id + INNER JOIN sys.dm_exec_connections c ON s.session_id = c.session_id + INNER JOIN ( + SELECT DISTINCT request_session_id, resource_database_id + FROM sys.dm_tran_locks + WHERE resource_type = N'DATABASE' + AND request_mode = N'S' + AND request_status = N'GRANT' + AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.session_id = db.request_session_id + WHERE s.status = 'sleeping' + AND s.last_request_end_time < DATEADD(ss, -10, SYSDATETIME()) + AND EXISTS(SELECT * FROM sys.dm_tran_locks WHERE request_session_id = s.session_id + AND NOT (resource_type = N'DATABASE' AND request_mode = N'S' AND request_status = N'GRANT' AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE')); + END /*Query Problems - Clients using implicit transactions - CheckID 37 */ IF @Seconds > 0 @@ -20000,6 +20062,11 @@ BEGIN AND @@VERSION NOT LIKE 'Microsoft SQL Server 2008%' AND @@VERSION NOT LIKE 'Microsoft SQL Server 2008 R2%' ) BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 37',10,1) WITH NOWAIT; + END + SET @StringToExecute = N'INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, OpenTransactionCount) SELECT 37 AS CheckId, 50 AS Priority, @@ -20036,37 +20103,48 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, /* Query Problems - Query Rolling Back - CheckID 9 */ IF @Seconds > 0 - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, QueryHash) - SELECT 9 AS CheckID, - 1 AS Priority, - 'Query Problems' AS FindingGroup, - 'Query Rolling Back' AS Finding, - 'http://www.BrentOzar.com/askbrent/rollback/' AS URL, - 'Rollback started at ' + CAST(r.start_time AS NVARCHAR(100)) + ', is ' + CAST(r.percent_complete AS NVARCHAR(100)) + '% complete.' AS Details, - 'Unfortunately, you can''t stop this. Whatever you do, don''t restart the server in an attempt to fix it - SQL Server will keep rolling back.' AS HowToStopIt, - r.start_time AS StartTime, - s.login_name AS LoginName, - s.nt_user_name AS NTUserName, - s.[program_name] AS ProgramName, - s.[host_name] AS HostName, - db.[resource_database_id] AS DatabaseID, - DB_NAME(db.resource_database_id) AS DatabaseName, - (SELECT TOP 1 [text] FROM sys.dm_exec_sql_text(c.most_recent_sql_handle)) AS QueryText, - r.query_hash - FROM sys.dm_exec_sessions s - INNER JOIN sys.dm_exec_connections c ON s.session_id = c.session_id - INNER JOIN sys.dm_exec_requests r ON s.session_id = r.session_id - LEFT OUTER JOIN ( - SELECT DISTINCT request_session_id, resource_database_id - FROM sys.dm_tran_locks - WHERE resource_type = N'DATABASE' - AND request_mode = N'S' - AND request_status = N'GRANT' - AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.session_id = db.request_session_id - WHERE r.status = 'rollback'; + BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 9',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, QueryHash) + SELECT 9 AS CheckID, + 1 AS Priority, + 'Query Problems' AS FindingGroup, + 'Query Rolling Back' AS Finding, + 'http://www.BrentOzar.com/askbrent/rollback/' AS URL, + 'Rollback started at ' + CAST(r.start_time AS NVARCHAR(100)) + ', is ' + CAST(r.percent_complete AS NVARCHAR(100)) + '% complete.' AS Details, + 'Unfortunately, you can''t stop this. Whatever you do, don''t restart the server in an attempt to fix it - SQL Server will keep rolling back.' AS HowToStopIt, + r.start_time AS StartTime, + s.login_name AS LoginName, + s.nt_user_name AS NTUserName, + s.[program_name] AS ProgramName, + s.[host_name] AS HostName, + db.[resource_database_id] AS DatabaseID, + DB_NAME(db.resource_database_id) AS DatabaseName, + (SELECT TOP 1 [text] FROM sys.dm_exec_sql_text(c.most_recent_sql_handle)) AS QueryText, + r.query_hash + FROM sys.dm_exec_sessions s + INNER JOIN sys.dm_exec_connections c ON s.session_id = c.session_id + INNER JOIN sys.dm_exec_requests r ON s.session_id = r.session_id + LEFT OUTER JOIN ( + SELECT DISTINCT request_session_id, resource_database_id + FROM sys.dm_tran_locks + WHERE resource_type = N'DATABASE' + AND request_mode = N'S' + AND request_status = N'GRANT' + AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.session_id = db.request_session_id + WHERE r.status = 'rollback'; + END /* Server Performance - Too Much Free Memory - CheckID 34 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 34',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) SELECT 34 AS CheckID, 50 AS Priority, @@ -20086,6 +20164,12 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, /* Server Performance - Target Memory Lower Than Max - CheckID 35 */ IF SERVERPROPERTY('Edition') <> 'SQL Azure' + BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 35',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) SELECT 35 AS CheckID, 10 AS Priority, @@ -20102,8 +20186,14 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, AND CAST(cMax.value_in_use AS BIGINT) >= 1.5 * (CAST(cTarget.cntr_value AS BIGINT) / 1024) AND CAST(cMax.value_in_use AS BIGINT) < 2147483647 /* Not set to default of unlimited */ AND CAST(cTarget.cntr_value AS BIGINT) < .8 * (SELECT available_physical_memory_kb FROM sys.dm_os_sys_memory); /* Target memory less than 80% of physical memory (in case they set max too high) */ + END /* Server Info - Database Size, Total GB - CheckID 21 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 21',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) SELECT 21 AS CheckID, 251 AS Priority, @@ -20116,6 +20206,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, WHERE database_id > 4; /* Server Info - Database Count - CheckID 22 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 22',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) SELECT 22 AS CheckID, 251 AS Priority, @@ -20128,6 +20223,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, WHERE database_id > 4; /* Server Info - Memory Grants pending - CheckID 39 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 39',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) SELECT 39 AS CheckID, 50 AS Priority, @@ -20147,6 +20247,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, WHERE PendingGrants.Details > 0; /* Server Info - Memory Grant/Workspace info - CheckID 40 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 40',10,1) WITH NOWAIT; + END + DECLARE @MaxWorkspace BIGINT SET @MaxWorkspace = (SELECT CAST(cntr_value AS BIGINT)/1024 FROM #PerfmonStats WHERE counter_name = N'Maximum Workspace Memory (KB)') @@ -20172,17 +20277,22 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, FROM sys.dm_exec_query_memory_grants AS Grants; /* Query Problems - Queries with high memory grants - CheckID 46 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 46',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, URL, QueryText, QueryPlan) SELECT 46 AS CheckID, 100 AS Priority, 'Query Problems' AS FindingGroup, - 'Query with memory a grant exceeding ' + 'Query with a memory grant exceeding ' +CAST(@MemoryGrantThresholdPct AS NVARCHAR(15)) +'%' AS Finding, 'Granted size: '+ CAST(CAST(Grants.granted_memory_kb / 1024 AS INT) AS NVARCHAR(50)) +N'MB ' + @LineFeed - +N'Granted pct: ' + +N'Granted pct of max workspace: ' + CAST(ISNULL((CAST(Grants.granted_memory_kb / 1024 AS MONEY) / CAST(@MaxWorkspace AS MONEY)) * 100, 0) AS NVARCHAR(50)) + '%' + @LineFeed @@ -20199,6 +20309,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, /* Query Problems - Memory Leak in USERSTORE_TOKENPERM Cache - CheckID 45 */ IF EXISTS (SELECT * FROM sys.all_columns WHERE object_id = OBJECT_ID('sys.dm_os_memory_clerks') AND name = 'pages_kb') BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 45',10,1) WITH NOWAIT; + END + /* SQL 2012+ version */ SET @StringToExecute = N' INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, URL) @@ -20218,6 +20333,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, ELSE BEGIN /* Antiques Roadshow SQL 2008R2 - version */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 45 (Legacy version)',10,1) WITH NOWAIT; + END + SET @StringToExecute = N' INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, URL) SELECT 45 AS CheckID, @@ -20352,6 +20472,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, y.parallelism_skew; /* Queries in dm_exec_query_profiles showing signs of poor cardinality estimates - CheckID 42 */ + IF (@Debug = 1) + BEGIN + RAISERROR(''Running CheckID 42'',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, OpenTransactionCount, QueryHash, QueryPlan) SELECT 42 AS CheckID, @@ -20398,6 +20523,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, SET @StringToExecute = @StringToExecute + N'; /* Queries in dm_exec_query_profiles showing signs of unbalanced parallelism - CheckID 43 */ + IF (@Debug = 1) + BEGIN + RAISERROR(''Running CheckID 43'',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, OpenTransactionCount, QueryHash, QueryPlan) SELECT 43 AS CheckID, @@ -20443,7 +20573,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, SET @StringToExecute = @StringToExecute + N';'; - EXECUTE sp_executesql @StringToExecute; + EXECUTE sp_executesql @StringToExecute, N'@Debug BIT',@Debug = @Debug; END END @@ -20456,6 +20586,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, We get this data from the ring buffers, and it's only updated once per minute, so might as well get it now - whereas if we're checking 30+ seconds, it might get updated by the end of our sp_BlitzFirst session. */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 24',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) SELECT 24, 50, 'Server Performance', 'High CPU Utilization', CAST(100 - SystemIdle AS NVARCHAR(20)) + N'%.', 100 - SystemIdle, 'http://www.BrentOzar.com/go/cpu' FROM ( @@ -20471,6 +20606,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, WHERE 100 - SystemIdle >= 50; /* CPU Utilization - CheckID 23 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 23',10,1) WITH NOWAIT; + END + IF SERVERPROPERTY('Edition') <> 'SQL Azure' WITH y AS @@ -20512,6 +20652,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, /* Highlight if non SQL processes are using >25% CPU - CheckID 28 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 28',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) SELECT 28, 50, 'Server Performance', 'High CPU Utilization - Not SQL', CONVERT(NVARCHAR(100),100 - (y.SQLUsage + y.SystemIdle)) + N'% - Other Processes (not SQL Server) are using this much CPU. This may impact on the performance of your SQL Server instance', 100 - (y.SQLUsage + y.SystemIdle), 'http://www.BrentOzar.com/go/cpu' FROM ( @@ -20530,6 +20675,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, END; /* IF @Seconds < 30 */ /* Query Problems - Statistics Updated Recently - CheckID 44 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 44',10,1) WITH NOWAIT; + END + IF 20 >= (SELECT COUNT(*) FROM sys.databases WHERE name NOT IN ('master', 'model', 'msdb', 'tempdb')) BEGIN CREATE TABLE #UpdatedStats (HowToStopIt NVARCHAR(4000), RowsForSorting BIGINT); @@ -20717,6 +20867,10 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, /* Query Stats - If we're within 10 seconds of our projected finish time, do the plan cache analysis. - CheckID 18 */ IF DATEDIFF(ss, @FinishSampleTime, SYSDATETIMEOFFSET()) > 10 AND @CheckProcedureCache = 1 BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 18',10,1) WITH NOWAIT; + END INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) VALUES (18, 210, 'Query Stats', 'Plan Cache Analysis Skipped', 'http://www.BrentOzar.com/go/topqueries', @@ -20856,6 +21010,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, INNER JOIN qsTop ON qs.ID = qsTop.ID; /* Query Stats - Most Resource-Intensive Queries - CheckID 17 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 17',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, QueryText, QueryStatsNowID, QueryStatsFirstID, PlanHandle, QueryHash) SELECT 17, 210, 'Query Stats', 'Most Resource-Intensive Queries', 'http://www.BrentOzar.com/go/topqueries', 'Query stats during the sample:' + @LineFeed + @@ -20915,6 +21074,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, /* Wait Stats - CheckID 6 */ /* Compare the current wait stats to the sample we took at the start, and insert the top 10 waits. */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 6',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, DetailsInt) SELECT TOP 10 6 AS CheckID, 200 AS Priority, @@ -20930,6 +21094,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, ORDER BY (wNow.wait_time_ms - COALESCE(wBase.wait_time_ms,0)) DESC; /* Server Performance - Poison Wait Detected - CheckID 30 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 30',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, DetailsInt) SELECT 30 AS CheckID, 10 AS Priority, @@ -20948,6 +21117,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, /* Server Performance - Slow Data File Reads - CheckID 11 */ IF EXISTS (SELECT * FROM #BlitzFirstResults WHERE Finding LIKE 'PAGEIOLATCH%') BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 11',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, DatabaseID, DatabaseName) SELECT TOP 10 11 AS CheckID, 50 AS Priority, @@ -20973,6 +21147,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, /* Server Performance - Slow Log File Writes - CheckID 12 */ IF EXISTS (SELECT * FROM #BlitzFirstResults WHERE Finding LIKE 'WRITELOG%') BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 12',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, DatabaseID, DatabaseName) SELECT TOP 10 12 AS CheckID, 50 AS Priority, @@ -20997,6 +21176,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, /* SQL Server Internal Maintenance - Log File Growing - CheckID 13 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 13',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) SELECT 13 AS CheckID, 1 AS Priority, @@ -21014,6 +21198,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, /* SQL Server Internal Maintenance - Log File Shrinking - CheckID 14 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 14',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) SELECT 14 AS CheckID, 1 AS Priority, @@ -21030,6 +21219,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, AND value_delta > 0; /* Query Problems - Compilations/Sec High - CheckID 15 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 15',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) SELECT 15 AS CheckID, 50 AS Priority, @@ -21051,6 +21245,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, AND (psComp.value_delta * 10) > ps.value_delta; /* Compilations are more than 10% of batch requests per second */ /* Query Problems - Re-Compilations/Sec High - CheckID 16 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 16',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) SELECT 16 AS CheckID, 50 AS Priority, @@ -21072,13 +21271,18 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, AND (psComp.value_delta * 10) > ps.value_delta; /* Recompilations are more than 10% of batch requests per second */ /* Table Problems - Forwarded Fetches/Sec High - CheckID 29 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 29',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) SELECT 29 AS CheckID, 40 AS Priority, 'Table Problems' AS FindingGroup, 'Forwarded Fetches/Sec High' AS Finding, 'https://BrentOzar.com/go/fetch/' AS URL, - CAST(ps.value_delta AS NVARCHAR(20)) + ' Forwarded Records (from SQLServer:Access Methods counter)' + @LineFeed + CAST(ps.value_delta AS NVARCHAR(20)) + ' forwarded fetches (from SQLServer:Access Methods counter)' + @LineFeed + 'Check your heaps: they need to be rebuilt, or they need a clustered index applied.' + @LineFeed AS Details, 'Rebuild your heaps. If you use Ola Hallengren maintenance scripts, those do not rebuild heaps by default: https://www.brentozar.com/archive/2016/07/fix-forwarded-records/' AS HowToStopIt FROM #PerfmonStats ps @@ -21097,19 +21301,30 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, SELECT TOP 10 29 AS CheckID, 40 AS Priority, ''Table Problems'' AS FindingGroup, - ''Forwarded Fetches/Sec High: Temp Table'' AS Finding, + ''Forwarded Fetches/Sec High: TempDB Object'' AS Finding, ''https://BrentOzar.com/go/fetch/'' AS URL, - CAST(COALESCE(os.forwarded_fetch_count,0) AS NVARCHAR(20)) + '' forwarded fetches on temp table '' + COALESCE(OBJECT_NAME(os.object_id), ''Unknown'') AS Details, - ''Look through your source code to find the object creating these temp tables, and tune the creation and population to reduce fetches. See the URL for details.'' AS HowToStopIt + CAST(COALESCE(os.forwarded_fetch_count,0) - COALESCE(os_prior.forwarded_fetch_count,0) AS NVARCHAR(20)) + '' forwarded fetches on '' + + CASE WHEN OBJECT_NAME(os.object_id) IS NULL THEN ''an unknown table '' + WHEN LEN(OBJECT_NAME(os.object_id)) < 50 THEN ''a table variable, internal identifier '' + OBJECT_NAME(os.object_id) + ELSE ''a temp table '' + OBJECT_NAME(os.object_id) + END AS Details, + ''Look through your source code to find the object creating these objects, and tune the creation and population to reduce fetches. See the URL for details.'' AS HowToStopIt FROM tempdb.sys.dm_db_index_operational_stats(DB_ID(''tempdb''), NULL, NULL, NULL) os + LEFT OUTER JOIN #TempdbOperationalStats os_prior ON os.object_id = os_prior.object_id + AND os.forwarded_fetch_count > os_prior.forwarded_fetch_count WHERE os.database_id = DB_ID(''tempdb'') - AND os.forwarded_fetch_count > 100 + AND os.forwarded_fetch_count - COALESCE(os_prior.forwarded_fetch_count,0) > 100 ORDER BY os.forwarded_fetch_count DESC;' EXECUTE sp_executesql @StringToExecute; END /* In-Memory OLTP - Garbage Collection in Progress - CheckID 31 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 31',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) SELECT 31 AS CheckID, 50 AS Priority, @@ -21128,6 +21343,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, AND ps.value_delta > (100 * @Seconds); /* Ignore servers sitting idle */ /* In-Memory OLTP - Transactions Aborted - CheckID 32 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 32',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) SELECT 32 AS CheckID, 100 AS Priority, @@ -21145,6 +21365,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, AND ps.value_delta > (10 * @Seconds); /* Ignore servers sitting idle */ /* Query Problems - Suboptimal Plans/Sec High - CheckID 33 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 33',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) SELECT 32 AS CheckID, 100 AS Priority, @@ -21163,6 +21388,12 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, /* Azure Performance - Database is Maxed Out - CheckID 41 */ IF SERVERPROPERTY('Edition') = 'SQL Azure' + BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 41',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) SELECT 41 AS CheckID, 10 AS Priority, @@ -21183,8 +21414,14 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, OR avg_log_write_percent >=90 OR max_worker_percent >= 90 OR max_session_percent >= 90); + END /* Server Info - Batch Requests per Sec - CheckID 19 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 19',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, DetailsInt) SELECT 19 AS CheckID, 250 AS Priority, @@ -21205,39 +21442,58 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, /* Server Info - SQL Compilations/sec - CheckID 25 */ IF @ExpertMode = 1 - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, DetailsInt) - SELECT 25 AS CheckID, - 250 AS Priority, - 'Server Info' AS FindingGroup, - 'SQL Compilations per Sec' AS Finding, - 'http://www.BrentOzar.com/go/measure' AS URL, - CAST(ps.value_delta / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS NVARCHAR(20)) AS Details, - ps.value_delta / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS DetailsInt - FROM #PerfmonStats ps - INNER JOIN #PerfmonStats ps1 ON ps.object_name = ps1.object_name AND ps.counter_name = ps1.counter_name AND ps1.Pass = 1 - WHERE ps.Pass = 2 - AND ps.object_name = @ServiceName + ':SQL Statistics' - AND ps.counter_name = 'SQL Compilations/sec'; + BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 25',10,1) WITH NOWAIT; + END + + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, DetailsInt) + SELECT 25 AS CheckID, + 250 AS Priority, + 'Server Info' AS FindingGroup, + 'SQL Compilations per Sec' AS Finding, + 'http://www.BrentOzar.com/go/measure' AS URL, + CAST(ps.value_delta / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS NVARCHAR(20)) AS Details, + ps.value_delta / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS DetailsInt + FROM #PerfmonStats ps + INNER JOIN #PerfmonStats ps1 ON ps.object_name = ps1.object_name AND ps.counter_name = ps1.counter_name AND ps1.Pass = 1 + WHERE ps.Pass = 2 + AND ps.object_name = @ServiceName + ':SQL Statistics' + AND ps.counter_name = 'SQL Compilations/sec'; + END /* Server Info - SQL Re-Compilations/sec - CheckID 26 */ IF @ExpertMode = 1 - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, DetailsInt) - SELECT 26 AS CheckID, - 250 AS Priority, - 'Server Info' AS FindingGroup, - 'SQL Re-Compilations per Sec' AS Finding, - 'http://www.BrentOzar.com/go/measure' AS URL, - CAST(ps.value_delta / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS NVARCHAR(20)) AS Details, - ps.value_delta / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS DetailsInt - FROM #PerfmonStats ps - INNER JOIN #PerfmonStats ps1 ON ps.object_name = ps1.object_name AND ps.counter_name = ps1.counter_name AND ps1.Pass = 1 - WHERE ps.Pass = 2 - AND ps.object_name = @ServiceName + ':SQL Statistics' - AND ps.counter_name = 'SQL Re-Compilations/sec'; + BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 26',10,1) WITH NOWAIT; + END + + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, DetailsInt) + SELECT 26 AS CheckID, + 250 AS Priority, + 'Server Info' AS FindingGroup, + 'SQL Re-Compilations per Sec' AS Finding, + 'http://www.BrentOzar.com/go/measure' AS URL, + CAST(ps.value_delta / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS NVARCHAR(20)) AS Details, + ps.value_delta / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS DetailsInt + FROM #PerfmonStats ps + INNER JOIN #PerfmonStats ps1 ON ps.object_name = ps1.object_name AND ps.counter_name = ps1.counter_name AND ps1.Pass = 1 + WHERE ps.Pass = 2 + AND ps.object_name = @ServiceName + ':SQL Statistics' + AND ps.counter_name = 'SQL Re-Compilations/sec'; + END /* Server Info - Wait Time per Core per Sec - CheckID 20 */ IF @Seconds > 0 BEGIN; + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 20',10,1) WITH NOWAIT; + END; + WITH waits1(SampleTime, waits_ms) AS (SELECT SampleTime, SUM(ws1.wait_time_ms) FROM #WaitStats ws1 WHERE ws1.Pass = 1 GROUP BY SampleTime), waits2(SampleTime, waits_ms) AS (SELECT SampleTime, SUM(ws2.wait_time_ms) FROM #WaitStats ws2 WHERE ws2.Pass = 2 GROUP BY SampleTime), cores(cpu_count) AS (SELECT SUM(1) FROM sys.dm_os_schedulers WHERE status = 'VISIBLE ONLINE' AND is_online = 1) @@ -21259,8 +21515,13 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, as well get it now - whereas if we're checking 30+ seconds, it might get updated by the end of our sp_BlitzFirst session. */ IF @Seconds >= 30 - BEGIN + BEGIN /* Server Performance - High CPU Utilization CheckID 24 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 24',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) SELECT 24, 50, 'Server Performance', 'High CPU Utilization', CAST(100 - SystemIdle AS NVARCHAR(20)) + N'%. Ring buffer details: ' + CAST(record AS NVARCHAR(4000)), 100 - SystemIdle, 'http://www.BrentOzar.com/go/cpu' FROM ( @@ -21276,6 +21537,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, WHERE 100 - SystemIdle >= 50; /* Server Performance - CPU Utilization CheckID 23 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 23',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) SELECT 23, 250, 'Server Info', 'CPU Utilization', CAST(100 - SystemIdle AS NVARCHAR(20)) + N'%. Ring buffer details: ' + CAST(record AS NVARCHAR(4000)), 100 - SystemIdle, 'http://www.BrentOzar.com/go/cpu' FROM ( @@ -21289,7 +21555,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, ORDER BY timestamp DESC) AS rb ) AS y; - END; /* IF @Seconds >= 30 */ + END; /* IF @Seconds >= 30 */ /* If we didn't find anything, apologize. */ @@ -21349,6 +21615,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, ); /* Outdated sp_BlitzFirst - sp_BlitzFirst is Over 6 Months Old - CheckID 27 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 27',10,1) WITH NOWAIT; + END + IF DATEDIFF(MM, @VersionDate, SYSDATETIMEOFFSET()) > 6 BEGIN INSERT INTO #BlitzFirstResults @@ -21440,6 +21711,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, ELSE BEGIN /* No sp_BlitzCache found, or it's outdated - CheckID 36 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 36',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults ( CheckID , Priority , @@ -22762,7 +23038,7 @@ AS SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '7.99999', @VersionDate = '20201211'; +SELECT @Version = '8.0', @VersionDate = '20210117'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -22770,7 +23046,9 @@ BEGIN RETURN; END; -IF @Help = 1 PRINT ' +IF @Help = 1 +BEGIN +PRINT ' /* sp_BlitzIndex from http://FirstResponderKit.org @@ -22797,7 +23075,7 @@ Unknown limitations of this version: MIT License -Copyright (c) 2020 Brent Ozar Unlimited +Copyright (c) 2021 Brent Ozar Unlimited Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -22817,7 +23095,8 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. '; - +RETURN; +END; /* @Help = 1 */ DECLARE @ScriptVersionName NVARCHAR(50); DECLARE @DaysUptime NUMERIC(23,2); @@ -24299,15 +24578,15 @@ BEGIN TRY FROM ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_details AS id_inner CROSS APPLY( SELECT LTRIM(RTRIM(v.value(''(./text())[1]'', ''varchar(max)''))) AS ColumnName, ''Equality'' AS IndexColumnType - FROM (VALUES (CONVERT(XML, N'''' + REPLACE(CAST(id_inner.equality_columns AS nvarchar(max)), N'','', N'''') + N''''))) x(n) + FROM (VALUES (CONVERT(XML, N'''' + REPLACE((SELECT CAST(id_inner.equality_columns AS nvarchar(max)) FOR XML PATH('''')), N'','', N'''') + N''''))) x(n) CROSS APPLY n.nodes(''x'') node(v) UNION ALL SELECT LTRIM(RTRIM(v.value(N''(./text())[1]'', ''varchar(max)''))) AS ColumnName, ''Inequality'' AS IndexColumnType - FROM (VALUES (CONVERT(XML, N'''' + REPLACE(CAST(id_inner.inequality_columns AS nvarchar(max)), N'','', N'''') + N''''))) x(n) + FROM (VALUES (CONVERT(XML, N'''' + REPLACE((SELECT CAST(id_inner.inequality_columns AS nvarchar(max)) FOR XML PATH('''')), N'','', N'''') + N''''))) x(n) CROSS APPLY n.nodes(''x'') node(v) UNION ALL SELECT LTRIM(RTRIM(v.value(''(./text())[1]'', ''varchar(max)''))) AS ColumnName, ''Included'' AS IndexColumnType - FROM (VALUES (CONVERT(XML, N'''' + REPLACE(CAST(id_inner.included_columns AS nvarchar(max)), N'','', N'''') + N''''))) x(n) + FROM (VALUES (CONVERT(XML, N'''' + REPLACE((SELECT CAST(id_inner.included_columns AS nvarchar(max)) FOR XML PATH('''')), N'','', N'''') + N''''))) x(n) CROSS APPLY n.nodes(''x'') node(v) )AS cn_inner' + /*split the string otherwise dsql cuts some of it out*/ @@ -24321,15 +24600,15 @@ BEGIN TRY FROM ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_details AS id CROSS APPLY( SELECT LTRIM(RTRIM(v.value(''(./text())[1]'', ''varchar(max)''))) AS ColumnName, ''Equality'' AS IndexColumnType - FROM (VALUES (CONVERT(XML, N'''' + REPLACE(CAST(id.equality_columns AS nvarchar(max)), N'','', N'''') + N''''))) x(n) + FROM (VALUES (CONVERT(XML, N'''' + REPLACE((SELECT CAST(id.equality_columns AS nvarchar(max)) FOR XML PATH('''')), N'','', N'''') + N''''))) x(n) CROSS APPLY n.nodes(''x'') node(v) UNION ALL SELECT LTRIM(RTRIM(v.value(''(./text())[1]'', ''varchar(max)''))) AS ColumnName, ''Inequality'' AS IndexColumnType - FROM (VALUES (CONVERT(XML, N'''' + REPLACE(CAST(id.inequality_columns AS nvarchar(max)), N'','', N'''') + N''''))) x(n) + FROM (VALUES (CONVERT(XML, N'''' + REPLACE((SELECT CAST(id.inequality_columns AS nvarchar(max)) FOR XML PATH('''')), N'','', N'''') + N''''))) x(n) CROSS APPLY n.nodes(''x'') node(v) UNION ALL SELECT LTRIM(RTRIM(v.value(''(./text())[1]'', ''varchar(max)''))) AS ColumnName, ''Included'' AS IndexColumnType - FROM (VALUES (CONVERT(XML, N'''' + REPLACE(CAST(id.included_columns AS nvarchar(max)), N'','', N'''') + N''''))) x(n) + FROM (VALUES (CONVERT(XML, N'''' + REPLACE((SELECT CAST(id.included_columns AS nvarchar(max)) FOR XML PATH('''')), N'','', N'''') + N''''))) x(n) CROSS APPLY n.nodes(''x'') node(v) )AS cn GROUP BY id.index_handle,id.object_id,cn.IndexColumnType @@ -28285,14 +28564,16 @@ BEGIN SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '2.99999', @VersionDate = '20201211'; +SELECT @Version = '8.0', @VersionDate = '20210117'; IF(@VersionCheckMode = 1) BEGIN RETURN; END; - IF @Help = 1 PRINT ' + IF @Help = 1 + BEGIN + PRINT ' /* sp_BlitzLock from http://FirstResponderKit.org @@ -28347,7 +28628,7 @@ END; MIT License - Copyright (c) 2020 Brent Ozar Unlimited + Copyright (c) 2021 Brent Ozar Unlimited Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -28368,8 +28649,9 @@ END; SOFTWARE. */'; - - + RETURN; + END; /* @Help = 1 */ + DECLARE @ProductVersion NVARCHAR(128); DECLARE @ProductVersionMajor FLOAT; DECLARE @ProductVersionMinor INT; @@ -28552,9 +28834,24 @@ You need to use an Azure storage account, and the path has to look like this: ht AND LEFT(CAST(SERVERPROPERTY('MachineName') AS VARCHAR(8000)), 8) <> 'EC2AMAZ-' AND LEFT(CAST(SERVERPROPERTY('ServerName') AS VARCHAR(8000)), 8) <> 'EC2AMAZ-' AND db_id('rdsadmin') IS NULL - BEGIN - UPDATE STATISTICS #t WITH ROWCOUNT = 100000000, PAGECOUNT = 100000000; - END + BEGIN; + BEGIN TRY; + UPDATE STATISTICS #t WITH ROWCOUNT = 100000000, PAGECOUNT = 100000000; + END TRY + BEGIN CATCH; + /* Misleading error returned, if run without permissions to update statistics the error returned is "Cannot find object". + Catching specific error, and returning message with better info. If any other error is returned, then throw as normal */ + IF (ERROR_NUMBER() = 1088) + BEGIN; + SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + RAISERROR('Cannot run UPDATE STATISTICS on temp table without db_owner or sysadmin permissions %s', 0, 1, @d) WITH NOWAIT; + END; + ELSE + BEGIN; + THROW; + END; + END CATCH; + END; /*Grab the initial set of XML to parse*/ SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); @@ -29987,7 +30284,7 @@ BEGIN SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '7.99999', @VersionDate = '20201211'; + SELECT @Version = '8.0', @VersionDate = '20210117'; IF(@VersionCheckMode = 1) BEGIN @@ -29997,6 +30294,7 @@ BEGIN IF @Help = 1 + BEGIN PRINT ' sp_BlitzWho from http://FirstResponderKit.org @@ -30014,7 +30312,7 @@ Known limitations of this version: MIT License -Copyright (c) 2020 Brent Ozar Unlimited +Copyright (c) 2021 Brent Ozar Unlimited Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -30034,6 +30332,8 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. '; +RETURN; +END; /* @Help = 1 */ /* Get the major and minor build numbers */ DECLARE @ProductVersion NVARCHAR(128) @@ -30528,7 +30828,7 @@ BEGIN /* Think of the StringToExecute as starting with this, but we'll set this up later depending on whether we're doing an insert or a select: SELECT @StringToExecute = N'SELECT GETDATE() AS run_date , */ - SET @StringToExecute = N'COALESCE( RIGHT(''00'' + CONVERT(VARCHAR(20), (ABS(r.total_elapsed_time) / 1000) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), (DATEADD(SECOND, (r.total_elapsed_time / 1000), 0) + DATEADD(MILLISECOND, (r.total_elapsed_time % 1000), 0)), 114), CONVERT(VARCHAR(20), DATEDIFF(SECOND, s.last_request_start_time, GETDATE()) / 86400) + '':'' + CONVERT(VARCHAR(20), DATEADD(SECOND, DATEDIFF(SECOND, s.last_request_start_time, GETDATE()), 0), 114) ) AS [elapsed_time] , + SET @StringToExecute = N'COALESCE( RIGHT(''00'' + CONVERT(VARCHAR(20), (ABS(r.total_elapsed_time) / 1000) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), (DATEADD(SECOND, (r.total_elapsed_time / 1000), 0) + DATEADD(MILLISECOND, (r.total_elapsed_time % 1000), 0)), 114), RIGHT(''00'' + CONVERT(VARCHAR(20), DATEDIFF(SECOND, s.last_request_start_time, GETDATE()) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), DATEADD(SECOND, DATEDIFF(SECOND, s.last_request_start_time, GETDATE()), 0), 114) ) AS [elapsed_time] , s.session_id , COALESCE(DB_NAME(r.database_id), DB_NAME(blocked.dbid), ''N/A'') AS database_name, ISNULL(SUBSTRING(dest.text, @@ -30733,7 +31033,7 @@ IF @ProductVersionMajor >= 11 /* Think of the StringToExecute as starting with this, but we'll set this up later depending on whether we're doing an insert or a select: SELECT @StringToExecute = N'SELECT GETDATE() AS run_date , */ - SELECT @StringToExecute = N'COALESCE( RIGHT(''00'' + CONVERT(VARCHAR(20), (ABS(r.total_elapsed_time) / 1000) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), (DATEADD(SECOND, (r.total_elapsed_time / 1000), 0) + DATEADD(MILLISECOND, (r.total_elapsed_time % 1000), 0)), 114), CONVERT(VARCHAR(20), DATEDIFF(SECOND, s.last_request_start_time, GETDATE()) / 86400) + '':'' + CONVERT(VARCHAR(20), DATEADD(SECOND, DATEDIFF(SECOND, s.last_request_start_time, GETDATE()), 0), 114) ) AS [elapsed_time] , + SELECT @StringToExecute = N'COALESCE( RIGHT(''00'' + CONVERT(VARCHAR(20), (ABS(r.total_elapsed_time) / 1000) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), (DATEADD(SECOND, (r.total_elapsed_time / 1000), 0) + DATEADD(MILLISECOND, (r.total_elapsed_time % 1000), 0)), 114), RIGHT(''00'' + CONVERT(VARCHAR(20), DATEDIFF(SECOND, s.last_request_start_time, GETDATE()) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), DATEADD(SECOND, DATEDIFF(SECOND, s.last_request_start_time, GETDATE()), 0), 114) ) AS [elapsed_time] , s.session_id , COALESCE(DB_NAME(r.database_id), DB_NAME(blocked.dbid), ''N/A'') AS database_name, ISNULL(SUBSTRING(dest.text, @@ -31225,6 +31525,7 @@ DELETE FROM dbo.SqlServerVersions; INSERT INTO dbo.SqlServerVersions (MajorVersionNumber, MinorVersionNumber, Branch, [Url], ReleaseDate, MainstreamSupportEndDate, ExtendedSupportEndDate, MajorVersionName, MinorVersionName) VALUES + (15, 4073, 'CU8 GDR', 'https://support.microsoft.com/en-us/help/4583459', '2021-01-12', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 8 GDR '), (15, 4073, 'CU8', 'https://support.microsoft.com/en-us/help/4577194', '2020-10-01', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 8 '), (15, 4063, 'CU7', 'https://support.microsoft.com/en-us/help/4570012', '2020-09-02', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 7 '), (15, 4053, 'CU6', 'https://support.microsoft.com/en-us/help/4563110', '2020-08-04', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 6 '), @@ -31235,6 +31536,7 @@ VALUES (15, 4003, 'CU1', 'https://support.microsoft.com/en-us/help/4527376', '2020-01-07', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 1 '), (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 '), + (14, 3370, 'RTM CU22 GDR', 'https://support.microsoft.com/en-us/help/4583457', '2021-01-12', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 22 GDR'), (14, 3356, 'RTM CU22', 'https://support.microsoft.com/en-us/help/4577467', '2020-09-10', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 22'), (14, 3335, 'RTM CU21', 'https://support.microsoft.com/en-us/help/4557397', '2020-07-01', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 21'), (14, 3294, 'RTM CU20', 'https://support.microsoft.com/en-us/help/4541283', '2020-04-07', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 20'), @@ -31258,6 +31560,7 @@ VALUES (14, 3008, 'RTM CU2', 'https://support.microsoft.com/en-us/help/4052574', '2017-11-28', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 2'), (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 '), + (13, 5865, 'SP2 CU15 GDR', 'https://support.microsoft.com/en-us/help/4583461', '2021-01-12', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 15 GDR'), (13, 5850, 'SP2 CU15', 'https://support.microsoft.com/en-us/help/4577775', '2020-09-28', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 15'), (13, 5830, 'SP2 CU14', 'https://support.microsoft.com/en-us/help/4564903', '2020-08-06', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 14'), (13, 5820, 'SP2 CU13', 'https://support.microsoft.com/en-us/help/4549825', '2020-05-28', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 13'), @@ -31274,6 +31577,7 @@ VALUES (13, 5201, 'SP2 CU2 + Security Update', 'https://support.microsoft.com/en-us/help/4458621', '2018-08-21', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 2 + Security Update'), (13, 5153, 'SP2 CU2', 'https://support.microsoft.com/en-us/help/4340355', '2018-07-16', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 2'), (13, 5149, 'SP2 CU1', 'https://support.microsoft.com/en-us/help/4135048', '2018-05-30', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 1'), + (13, 5103, 'SP2 GDR', 'https://support.microsoft.com/en-us/help/4583460', '2021-01-12', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 GDR'), (13, 5026, 'SP2 ', 'https://support.microsoft.com/en-us/help/4052908', '2018-04-24', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 '), (13, 4574, 'SP1 CU15', 'https://support.microsoft.com/en-us/help/4495257', '2019-05-16', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 15'), (13, 4560, 'SP1 CU14', 'https://support.microsoft.com/en-us/help/4488535', '2019-03-19', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 14'), @@ -31302,10 +31606,13 @@ VALUES (13, 2164, 'RTM CU2', 'https://support.microsoft.com/en-us/help/3182270 ', '2016-09-22', '2018-01-09', '2018-01-09', 'SQL Server 2016', 'RTM Cumulative Update 2'), (13, 2149, 'RTM CU1', 'https://support.microsoft.com/en-us/help/3164674 ', '2016-07-25', '2018-01-09', '2018-01-09', 'SQL Server 2016', 'RTM Cumulative Update 1'), (13, 1601, 'RTM ', '', '2016-06-01', '2019-01-09', '2019-01-09', 'SQL Server 2016', 'RTM '), + (12, 6433, 'SP3 CU4 GDR', 'https://support.microsoft.com/en-us/help/4583462', '2021-01-12', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 4 GDR'), + (12, 6372, 'SP3 CU4 GDR', 'https://support.microsoft.com/en-us/help/4535288', '2020-02-11', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 4 GDR'), (12, 6329, 'SP3 CU4', 'https://support.microsoft.com/en-us/help/4500181', '2019-07-29', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 4'), (12, 6259, 'SP3 CU3', 'https://support.microsoft.com/en-us/help/4491539', '2019-04-16', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 3'), (12, 6214, 'SP3 CU2', 'https://support.microsoft.com/en-us/help/4482960', '2019-02-19', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 2'), (12, 6205, 'SP3 CU1', 'https://support.microsoft.com/en-us/help/4470220', '2018-12-12', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 1'), + (12, 6164, 'SP3 GDR', 'https://support.microsoft.com/en-us/help/4583463', '2021-01-12', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 GDR'), (12, 6024, 'SP3 ', 'https://support.microsoft.com/en-us/help/4022619', '2018-10-30', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 '), (12, 5687, 'SP2 CU18', 'https://support.microsoft.com/en-us/help/4500180', '2019-07-29', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 18'), (12, 5632, 'SP2 CU17', 'https://support.microsoft.com/en-us/help/4491540', '2019-04-16', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 17'), @@ -31361,6 +31668,7 @@ VALUES (12, 2269, 'RTM MS15-058: GDR Security Update ', 'https://support.microsoft.com/en-us/help/3045324', '2015-07-14', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM MS15-058: GDR Security Update '), (12, 2254, 'RTM MS14-044: GDR Security Update', 'https://support.microsoft.com/en-us/help/2977315', '2014-08-12', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM MS14-044: GDR Security Update'), (12, 2000, 'RTM ', '', '2014-04-01', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM '), + (11, 7507, 'SP4 GDR Security Update', 'https://support.microsoft.com/en-us/help/4583465', '2021-01-12', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'Service Pack 4 GDR Security Update for CVE-2021-1636'), (11, 7493, 'SP4 GDR Security Update', 'https://support.microsoft.com/en-us/help/4532098', '2020-02-11', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'Service Pack 4 GDR Security Update for CVE-2020-0618'), (11, 7469, 'SP4 On-Demand Hotfix Update', 'https://support.microsoft.com/en-us/help/4091266', '2018-03-28', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'Service Pack 4 SP4 On-Demand Hotfix Update'), (11, 7462, 'SP4 ADV180002: GDR Security Update', 'https://support.microsoft.com/en-us/help/4057116', '2018-01-12', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'Service Pack 4 ADV180002: GDR Security Update'), diff --git a/Install-Core-Blitz-With-Query-Store.sql b/Install-Core-Blitz-With-Query-Store.sql index 5fc9700bf..cace2dcc5 100755 --- a/Install-Core-Blitz-With-Query-Store.sql +++ b/Install-Core-Blitz-With-Query-Store.sql @@ -37,7 +37,7 @@ AS SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '7.99999', @VersionDate = '20201211'; + SELECT @Version = '8.0', @VersionDate = '20210117'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -45,7 +45,9 @@ AS RETURN; END; - IF @Help = 1 PRINT ' + IF @Help = 1 + BEGIN + PRINT ' /* sp_Blitz from http://FirstResponderKit.org @@ -90,9 +92,9 @@ AS tigertoolbox and are provided under the MIT license: https://github.com/Microsoft/tigertoolbox - All other copyrights for sp_Blitz are held by Brent Ozar Unlimited, 2020. + All other copyrights for sp_Blitz are held by Brent Ozar Unlimited, 2021. - Copyright (c) 2020 Brent Ozar Unlimited + Copyright (c) 2021 Brent Ozar Unlimited Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -113,6 +115,9 @@ AS SOFTWARE. */'; + RETURN; + END; /* @Help = 1 */ + ELSE IF @OutputType = 'SCHEMA' BEGIN SELECT FieldList = '[Priority] TINYINT, [FindingsGroup] VARCHAR(50), [Finding] VARCHAR(200), [DatabaseName] NVARCHAR(128), [URL] VARCHAR(200), [Details] NVARCHAR(4000), [QueryPlan] NVARCHAR(MAX), [QueryPlanFiltered] NVARCHAR(MAX), [CheckID] INT'; @@ -1665,6 +1670,7 @@ AS + '] has auto-shrink enabled. This setting can dramatically decrease performance.' ) AS Details FROM sys.databases WHERE is_auto_shrink_on = 1 + AND state <> 6 /* Offline */ AND name NOT IN ( SELECT DISTINCT DatabaseName FROM #SkipChecks @@ -1698,7 +1704,7 @@ AS FROM sys.databases WHERE page_verify_option < 2 AND name <> ''tempdb'' - AND state <> 1 /* Restoring */ + AND state NOT IN (1, 6) /* Restoring, Offline */ and name not in (select distinct DatabaseName from #SkipChecks WHERE CheckID IS NULL OR CheckID = 14) OPTION (RECOMPILE);'; IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; @@ -9460,7 +9466,7 @@ AS SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '3.99999', @VersionDate = '20201211'; + SELECT @Version = '8.0', @VersionDate = '20210117'; IF(@VersionCheckMode = 1) BEGIN @@ -9507,7 +9513,7 @@ AS MIT License - Copyright (c) 2020 Brent Ozar Unlimited + Copyright (c) 2021 Brent Ozar Unlimited Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -11239,7 +11245,7 @@ BEGIN SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '7.99999', @VersionDate = '20201211'; +SELECT @Version = '8.0', @VersionDate = '20210117'; IF(@VersionCheckMode = 1) @@ -11247,7 +11253,9 @@ BEGIN RETURN; END; -IF @Help = 1 PRINT ' +IF @Help = 1 +BEGIN +PRINT ' sp_BlitzCache from http://FirstResponderKit.org This script displays your most resource-intensive queries from the plan cache, @@ -11275,7 +11283,7 @@ https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/ MIT License -Copyright (c) 2020 Brent Ozar Unlimited +Copyright (c) 2021 Brent Ozar Unlimited Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -11298,8 +11306,8 @@ SOFTWARE. DECLARE @nl NVARCHAR(2) = NCHAR(13) + NCHAR(10) ; -IF @Help = 1 -BEGIN +--IF @Help = 1 /* We're still under a @Help = 1 BEGIN from above */ +--BEGIN SELECT N'@Help' AS [Parameter Name] , N'BIT' AS [Data Type] , N'Displays this help message.' AS [Parameter Description] @@ -18419,14 +18427,16 @@ BEGIN SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '7.99999', @VersionDate = '20201211'; +SELECT @Version = '8.0', @VersionDate = '20210117'; IF(@VersionCheckMode = 1) BEGIN RETURN; END; -IF @Help = 1 PRINT ' +IF @Help = 1 +BEGIN +PRINT ' sp_BlitzFirst from http://FirstResponderKit.org This script gives you a prioritized list of why your SQL Server is slow right now. @@ -18457,7 +18467,7 @@ https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/ MIT License -Copyright (c) 2020 Brent Ozar Unlimited +Copyright (c) 2021 Brent Ozar Unlimited Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -18478,7 +18488,8 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. '; - +RETURN; +END; /* @Help = 1 */ RAISERROR('Setting up configuration variables',10,1) WITH NOWAIT; DECLARE @StringToExecute NVARCHAR(MAX), @@ -19739,6 +19750,16 @@ BEGIN AND counters.[object_name] COLLATE SQL_Latin1_General_CP1_CI_AS = RTRIM(dmv.[object_name]) COLLATE SQL_Latin1_General_CP1_CI_AS AND (counters.[instance_name] IS NULL OR counters.[instance_name] COLLATE SQL_Latin1_General_CP1_CI_AS = RTRIM(dmv.[instance_name]) COLLATE SQL_Latin1_General_CP1_CI_AS); + /* For Github #2743: */ + CREATE TABLE #TempdbOperationalStats (object_id BIGINT PRIMARY KEY CLUSTERED, + forwarded_fetch_count BIGINT); + INSERT INTO #TempdbOperationalStats (object_id, forwarded_fetch_count) + SELECT object_id, forwarded_fetch_count + FROM tempdb.sys.dm_db_index_operational_stats(DB_ID('tempdb'), NULL, NULL, NULL) os + WHERE os.database_id = DB_ID('tempdb') + AND os.forwarded_fetch_count > 100; + + /* If they want to run sp_BlitzWho and export to table, go for it. */ IF @OutputTableNameBlitzWho IS NOT NULL AND @OutputDatabaseName IS NOT NULL @@ -19756,40 +19777,47 @@ BEGIN /* Maintenance Tasks Running - Backup Running - CheckID 1 */ IF @Seconds > 0 - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, QueryHash) - SELECT 1 AS CheckID, - 1 AS Priority, - 'Maintenance Tasks Running' AS FindingGroup, - 'Backup Running' AS Finding, - 'http://www.BrentOzar.com/askbrent/backups/' AS URL, - 'Backup of ' + DB_NAME(db.resource_database_id) + ' database (' + (SELECT CAST(CAST(SUM(size * 8.0 / 1024 / 1024) AS BIGINT) AS NVARCHAR) FROM #MasterFiles WHERE database_id = db.resource_database_id) + 'GB) ' + @LineFeed - + CAST(r.percent_complete AS NVARCHAR(100)) + '% complete, has been running since ' + CAST(r.start_time AS NVARCHAR(100)) + '. ' + @LineFeed - + CASE WHEN COALESCE(s.nt_user_name, s.login_name) IS NOT NULL THEN (' Login: ' + COALESCE(s.nt_user_name, s.login_name) + ' ') ELSE '' END AS Details, - 'KILL ' + CAST(r.session_id AS NVARCHAR(100)) + ';' AS HowToStopIt, - pl.query_plan AS QueryPlan, - r.start_time AS StartTime, - s.login_name AS LoginName, - s.nt_user_name AS NTUserName, - s.[program_name] AS ProgramName, - s.[host_name] AS HostName, - db.[resource_database_id] AS DatabaseID, - DB_NAME(db.resource_database_id) AS DatabaseName, - 0 AS OpenTransactionCount, - r.query_hash - FROM sys.dm_exec_requests r - INNER JOIN sys.dm_exec_connections c ON r.session_id = c.session_id - INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id - INNER JOIN ( - SELECT DISTINCT request_session_id, resource_database_id - FROM sys.dm_tran_locks - WHERE resource_type = N'DATABASE' - AND request_mode = N'S' - AND request_status = N'GRANT' - AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.session_id = db.request_session_id - CROSS APPLY sys.dm_exec_query_plan(r.plan_handle) pl - WHERE r.command LIKE 'BACKUP%' - AND r.start_time <= DATEADD(minute, -5, GETDATE()) - AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs); + BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 1',10,1) WITH NOWAIT; + END + + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, QueryHash) + SELECT 1 AS CheckID, + 1 AS Priority, + 'Maintenance Tasks Running' AS FindingGroup, + 'Backup Running' AS Finding, + 'http://www.BrentOzar.com/askbrent/backups/' AS URL, + 'Backup of ' + DB_NAME(db.resource_database_id) + ' database (' + (SELECT CAST(CAST(SUM(size * 8.0 / 1024 / 1024) AS BIGINT) AS NVARCHAR) FROM #MasterFiles WHERE database_id = db.resource_database_id) + 'GB) ' + @LineFeed + + CAST(r.percent_complete AS NVARCHAR(100)) + '% complete, has been running since ' + CAST(r.start_time AS NVARCHAR(100)) + '. ' + @LineFeed + + CASE WHEN COALESCE(s.nt_user_name, s.login_name) IS NOT NULL THEN (' Login: ' + COALESCE(s.nt_user_name, s.login_name) + ' ') ELSE '' END AS Details, + 'KILL ' + CAST(r.session_id AS NVARCHAR(100)) + ';' AS HowToStopIt, + pl.query_plan AS QueryPlan, + r.start_time AS StartTime, + s.login_name AS LoginName, + s.nt_user_name AS NTUserName, + s.[program_name] AS ProgramName, + s.[host_name] AS HostName, + db.[resource_database_id] AS DatabaseID, + DB_NAME(db.resource_database_id) AS DatabaseName, + 0 AS OpenTransactionCount, + r.query_hash + FROM sys.dm_exec_requests r + INNER JOIN sys.dm_exec_connections c ON r.session_id = c.session_id + INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id + INNER JOIN ( + SELECT DISTINCT request_session_id, resource_database_id + FROM sys.dm_tran_locks + WHERE resource_type = N'DATABASE' + AND request_mode = N'S' + AND request_status = N'GRANT' + AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.session_id = db.request_session_id + CROSS APPLY sys.dm_exec_query_plan(r.plan_handle) pl + WHERE r.command LIKE 'BACKUP%' + AND r.start_time <= DATEADD(minute, -5, GETDATE()) + AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs); + END /* If there's a backup running, add details explaining how long full backup has been taking in the last month. */ IF @Seconds > 0 AND CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) <> 'SQL Azure' @@ -19801,109 +19829,132 @@ BEGIN /* Maintenance Tasks Running - DBCC CHECK* Running - CheckID 2 */ IF @Seconds > 0 AND EXISTS(SELECT * FROM sys.dm_exec_requests WHERE command LIKE 'DBCC%') - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, QueryHash) - SELECT 2 AS CheckID, - 1 AS Priority, - 'Maintenance Tasks Running' AS FindingGroup, - 'DBCC CHECK* Running' AS Finding, - 'http://www.BrentOzar.com/askbrent/dbcc/' AS URL, - 'Corruption check of ' + DB_NAME(db.resource_database_id) + ' database (' + (SELECT CAST(CAST(SUM(size * 8.0 / 1024 / 1024) AS BIGINT) AS NVARCHAR) FROM #MasterFiles WHERE database_id = db.resource_database_id) + 'GB) 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, - s.login_name AS LoginName, - s.nt_user_name AS NTUserName, - s.[program_name] AS ProgramName, - s.[host_name] AS HostName, - db.[resource_database_id] AS DatabaseID, - DB_NAME(db.resource_database_id) AS DatabaseName, - 0 AS OpenTransactionCount, - r.query_hash - FROM sys.dm_exec_requests r - INNER JOIN sys.dm_exec_connections c ON r.session_id = c.session_id - INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id - INNER JOIN (SELECT DISTINCT l.request_session_id, l.resource_database_id - FROM sys.dm_tran_locks l - INNER JOIN sys.databases d ON l.resource_database_id = d.database_id - WHERE l.resource_type = N'DATABASE' - AND l.request_mode = N'S' - AND l.request_status = N'GRANT' - AND l.request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.session_id = db.request_session_id - OUTER APPLY sys.dm_exec_query_plan(r.plan_handle) pl - OUTER APPLY sys.dm_exec_sql_text(r.sql_handle) AS t - WHERE r.command LIKE 'DBCC%' - AND CAST(t.text AS NVARCHAR(4000)) NOT LIKE '%dm_db_index_physical_stats%' - AND CAST(t.text AS NVARCHAR(4000)) NOT LIKE '%ALTER INDEX%' - AND CAST(t.text AS NVARCHAR(4000)) NOT LIKE '%fileproperty%' - AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs); + BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 2',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, QueryHash) + SELECT 2 AS CheckID, + 1 AS Priority, + 'Maintenance Tasks Running' AS FindingGroup, + 'DBCC CHECK* Running' AS Finding, + 'http://www.BrentOzar.com/askbrent/dbcc/' AS URL, + 'Corruption check of ' + DB_NAME(db.resource_database_id) + ' database (' + (SELECT CAST(CAST(SUM(size * 8.0 / 1024 / 1024) AS BIGINT) AS NVARCHAR) FROM #MasterFiles WHERE database_id = db.resource_database_id) + 'GB) 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, + s.login_name AS LoginName, + s.nt_user_name AS NTUserName, + s.[program_name] AS ProgramName, + s.[host_name] AS HostName, + db.[resource_database_id] AS DatabaseID, + DB_NAME(db.resource_database_id) AS DatabaseName, + 0 AS OpenTransactionCount, + r.query_hash + FROM sys.dm_exec_requests r + INNER JOIN sys.dm_exec_connections c ON r.session_id = c.session_id + INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id + INNER JOIN (SELECT DISTINCT l.request_session_id, l.resource_database_id + FROM sys.dm_tran_locks l + INNER JOIN sys.databases d ON l.resource_database_id = d.database_id + WHERE l.resource_type = N'DATABASE' + AND l.request_mode = N'S' + AND l.request_status = N'GRANT' + AND l.request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.session_id = db.request_session_id + OUTER APPLY sys.dm_exec_query_plan(r.plan_handle) pl + OUTER APPLY sys.dm_exec_sql_text(r.sql_handle) AS t + WHERE r.command LIKE 'DBCC%' + AND CAST(t.text AS NVARCHAR(4000)) NOT LIKE '%dm_db_index_physical_stats%' + AND CAST(t.text AS NVARCHAR(4000)) NOT LIKE '%ALTER INDEX%' + AND CAST(t.text AS NVARCHAR(4000)) NOT LIKE '%fileproperty%' + AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs); + END /* Maintenance Tasks Running - Restore Running - CheckID 3 */ IF @Seconds > 0 - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, QueryHash) - SELECT 3 AS CheckID, - 1 AS Priority, - 'Maintenance Tasks Running' AS FindingGroup, - 'Restore Running' AS Finding, - 'http://www.BrentOzar.com/askbrent/backups/' AS URL, - 'Restore of ' + DB_NAME(db.resource_database_id) + ' database (' + (SELECT CAST(CAST(SUM(size * 8.0 / 1024 / 1024) AS BIGINT) AS NVARCHAR) FROM #MasterFiles WHERE database_id = db.resource_database_id) + '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, - s.login_name AS LoginName, - s.nt_user_name AS NTUserName, - s.[program_name] AS ProgramName, - s.[host_name] AS HostName, - db.[resource_database_id] AS DatabaseID, - DB_NAME(db.resource_database_id) AS DatabaseName, - 0 AS OpenTransactionCount, - r.query_hash - FROM sys.dm_exec_requests r - INNER JOIN sys.dm_exec_connections c ON r.session_id = c.session_id - INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id - INNER JOIN ( - SELECT DISTINCT request_session_id, resource_database_id - FROM sys.dm_tran_locks - WHERE resource_type = N'DATABASE' - AND request_mode = N'S' - AND request_status = N'GRANT') AS db ON s.session_id = db.request_session_id - CROSS APPLY sys.dm_exec_query_plan(r.plan_handle) pl - WHERE r.command LIKE 'RESTORE%' - AND s.program_name <> 'SQL Server Log Shipping' - AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs); + BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 3',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, QueryHash) + SELECT 3 AS CheckID, + 1 AS Priority, + 'Maintenance Tasks Running' AS FindingGroup, + 'Restore Running' AS Finding, + 'http://www.BrentOzar.com/askbrent/backups/' AS URL, + 'Restore of ' + DB_NAME(db.resource_database_id) + ' database (' + (SELECT CAST(CAST(SUM(size * 8.0 / 1024 / 1024) AS BIGINT) AS NVARCHAR) FROM #MasterFiles WHERE database_id = db.resource_database_id) + '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, + s.login_name AS LoginName, + s.nt_user_name AS NTUserName, + s.[program_name] AS ProgramName, + s.[host_name] AS HostName, + db.[resource_database_id] AS DatabaseID, + DB_NAME(db.resource_database_id) AS DatabaseName, + 0 AS OpenTransactionCount, + r.query_hash + FROM sys.dm_exec_requests r + INNER JOIN sys.dm_exec_connections c ON r.session_id = c.session_id + INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id + INNER JOIN ( + SELECT DISTINCT request_session_id, resource_database_id + FROM sys.dm_tran_locks + WHERE resource_type = N'DATABASE' + AND request_mode = N'S' + AND request_status = N'GRANT') AS db ON s.session_id = db.request_session_id + CROSS APPLY sys.dm_exec_query_plan(r.plan_handle) pl + WHERE r.command LIKE 'RESTORE%' + AND s.program_name <> 'SQL Server Log Shipping' + AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs); + END /* SQL Server Internal Maintenance - Database File Growing - CheckID 4 */ IF @Seconds > 0 - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount) - SELECT 4 AS CheckID, - 1 AS Priority, - 'SQL Server Internal Maintenance' AS FindingGroup, - 'Database File Growing' AS Finding, - 'http://www.BrentOzar.com/go/instant' AS URL, - 'SQL Server is waiting for Windows to provide storage space for a database restore, a data file growth, or a log file growth. This task has been running since ' + CAST(r.start_time AS NVARCHAR(100)) + '.' + @LineFeed + 'Check the query plan (expert mode) to identify the database involved.' AS Details, - 'Unfortunately, you can''t stop this, but you can prevent it next time. Check out http://www.BrentOzar.com/go/instant for details.' AS HowToStopIt, - pl.query_plan AS QueryPlan, - r.start_time AS StartTime, - s.login_name AS LoginName, - s.nt_user_name AS NTUserName, - s.[program_name] AS ProgramName, - s.[host_name] AS HostName, - NULL AS DatabaseID, - NULL AS DatabaseName, - 0 AS OpenTransactionCount - FROM sys.dm_os_waiting_tasks t - INNER JOIN sys.dm_exec_connections c ON t.session_id = c.session_id - INNER JOIN sys.dm_exec_requests r ON t.session_id = r.session_id - INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id - CROSS APPLY sys.dm_exec_query_plan(r.plan_handle) pl - WHERE t.wait_type = 'PREEMPTIVE_OS_WRITEFILEGATHER' - AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs); + BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 4',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount) + SELECT 4 AS CheckID, + 1 AS Priority, + 'SQL Server Internal Maintenance' AS FindingGroup, + 'Database File Growing' AS Finding, + 'http://www.BrentOzar.com/go/instant' AS URL, + 'SQL Server is waiting for Windows to provide storage space for a database restore, a data file growth, or a log file growth. This task has been running since ' + CAST(r.start_time AS NVARCHAR(100)) + '.' + @LineFeed + 'Check the query plan (expert mode) to identify the database involved.' AS Details, + 'Unfortunately, you can''t stop this, but you can prevent it next time. Check out http://www.BrentOzar.com/go/instant for details.' AS HowToStopIt, + pl.query_plan AS QueryPlan, + r.start_time AS StartTime, + s.login_name AS LoginName, + s.nt_user_name AS NTUserName, + s.[program_name] AS ProgramName, + s.[host_name] AS HostName, + NULL AS DatabaseID, + NULL AS DatabaseName, + 0 AS OpenTransactionCount + FROM sys.dm_os_waiting_tasks t + INNER JOIN sys.dm_exec_connections c ON t.session_id = c.session_id + INNER JOIN sys.dm_exec_requests r ON t.session_id = r.session_id + INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id + CROSS APPLY sys.dm_exec_query_plan(r.plan_handle) pl + WHERE t.wait_type = 'PREEMPTIVE_OS_WRITEFILEGATHER' + AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs); + END /* Query Problems - Long-Running Query Blocking Others - CheckID 5 */ IF SERVERPROPERTY('Edition') <> 'SQL Azure' AND @Seconds > 0 AND EXISTS(SELECT * FROM sys.dm_os_waiting_tasks WHERE wait_type LIKE 'LCK%' AND wait_duration_ms > 30000) BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 5',10,1) WITH NOWAIT; + END + SET @StringToExecute = N'INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, QueryText, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, QueryHash) SELECT 5 AS CheckID, 1 AS Priority, @@ -19942,6 +19993,11 @@ BEGIN /* Query Problems - Plan Cache Erased Recently - CheckID 7 */ IF DATEADD(mi, -15, SYSDATETIME()) < (SELECT TOP 1 creation_time FROM sys.dm_exec_query_stats ORDER BY creation_time) BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 7',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) SELECT TOP 1 7 AS CheckID, 50 AS Priority, @@ -19961,38 +20017,44 @@ BEGIN /* Query Problems - Sleeping Query with Open Transactions - CheckID 8 */ IF @Seconds > 0 - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, OpenTransactionCount) - SELECT 8 AS CheckID, - 50 AS Priority, - 'Query Problems' AS FindingGroup, - 'Sleeping Query with Open Transactions' AS Finding, - 'http://www.brentozar.com/askbrent/sleeping-query-with-open-transactions/' AS URL, - 'Database: ' + DB_NAME(db.resource_database_id) + @LineFeed + 'Host: ' + s.[host_name] + @LineFeed + 'Program: ' + s.[program_name] + @LineFeed + 'Asleep with open transactions and locks since ' + CAST(s.last_request_end_time AS NVARCHAR(100)) + '. ' AS Details, - 'KILL ' + CAST(s.session_id AS NVARCHAR(100)) + ';' AS HowToStopIt, - s.last_request_start_time AS StartTime, - s.login_name AS LoginName, - s.nt_user_name AS NTUserName, - s.[program_name] AS ProgramName, - s.[host_name] AS HostName, - db.[resource_database_id] AS DatabaseID, - DB_NAME(db.resource_database_id) AS DatabaseName, - (SELECT TOP 1 [text] FROM sys.dm_exec_sql_text(c.most_recent_sql_handle)) AS QueryText, - sessions_with_transactions.open_transaction_count AS OpenTransactionCount - FROM (SELECT session_id, SUM(open_transaction_count) AS open_transaction_count FROM sys.dm_exec_requests WHERE open_transaction_count > 0 GROUP BY session_id) AS sessions_with_transactions - INNER JOIN sys.dm_exec_sessions s ON sessions_with_transactions.session_id = s.session_id - INNER JOIN sys.dm_exec_connections c ON s.session_id = c.session_id - INNER JOIN ( - SELECT DISTINCT request_session_id, resource_database_id - FROM sys.dm_tran_locks - WHERE resource_type = N'DATABASE' - AND request_mode = N'S' - AND request_status = N'GRANT' - AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.session_id = db.request_session_id - WHERE s.status = 'sleeping' - AND s.last_request_end_time < DATEADD(ss, -10, SYSDATETIME()) - AND EXISTS(SELECT * FROM sys.dm_tran_locks WHERE request_session_id = s.session_id - AND NOT (resource_type = N'DATABASE' AND request_mode = N'S' AND request_status = N'GRANT' AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE')); + BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 8',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, OpenTransactionCount) + SELECT 8 AS CheckID, + 50 AS Priority, + 'Query Problems' AS FindingGroup, + 'Sleeping Query with Open Transactions' AS Finding, + 'http://www.brentozar.com/askbrent/sleeping-query-with-open-transactions/' AS URL, + 'Database: ' + DB_NAME(db.resource_database_id) + @LineFeed + 'Host: ' + s.[host_name] + @LineFeed + 'Program: ' + s.[program_name] + @LineFeed + 'Asleep with open transactions and locks since ' + CAST(s.last_request_end_time AS NVARCHAR(100)) + '. ' AS Details, + 'KILL ' + CAST(s.session_id AS NVARCHAR(100)) + ';' AS HowToStopIt, + s.last_request_start_time AS StartTime, + s.login_name AS LoginName, + s.nt_user_name AS NTUserName, + s.[program_name] AS ProgramName, + s.[host_name] AS HostName, + db.[resource_database_id] AS DatabaseID, + DB_NAME(db.resource_database_id) AS DatabaseName, + (SELECT TOP 1 [text] FROM sys.dm_exec_sql_text(c.most_recent_sql_handle)) AS QueryText, + sessions_with_transactions.open_transaction_count AS OpenTransactionCount + FROM (SELECT session_id, SUM(open_transaction_count) AS open_transaction_count FROM sys.dm_exec_requests WHERE open_transaction_count > 0 GROUP BY session_id) AS sessions_with_transactions + INNER JOIN sys.dm_exec_sessions s ON sessions_with_transactions.session_id = s.session_id + INNER JOIN sys.dm_exec_connections c ON s.session_id = c.session_id + INNER JOIN ( + SELECT DISTINCT request_session_id, resource_database_id + FROM sys.dm_tran_locks + WHERE resource_type = N'DATABASE' + AND request_mode = N'S' + AND request_status = N'GRANT' + AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.session_id = db.request_session_id + WHERE s.status = 'sleeping' + AND s.last_request_end_time < DATEADD(ss, -10, SYSDATETIME()) + AND EXISTS(SELECT * FROM sys.dm_tran_locks WHERE request_session_id = s.session_id + AND NOT (resource_type = N'DATABASE' AND request_mode = N'S' AND request_status = N'GRANT' AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE')); + END /*Query Problems - Clients using implicit transactions - CheckID 37 */ IF @Seconds > 0 @@ -20000,6 +20062,11 @@ BEGIN AND @@VERSION NOT LIKE 'Microsoft SQL Server 2008%' AND @@VERSION NOT LIKE 'Microsoft SQL Server 2008 R2%' ) BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 37',10,1) WITH NOWAIT; + END + SET @StringToExecute = N'INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, OpenTransactionCount) SELECT 37 AS CheckId, 50 AS Priority, @@ -20036,37 +20103,48 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, /* Query Problems - Query Rolling Back - CheckID 9 */ IF @Seconds > 0 - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, QueryHash) - SELECT 9 AS CheckID, - 1 AS Priority, - 'Query Problems' AS FindingGroup, - 'Query Rolling Back' AS Finding, - 'http://www.BrentOzar.com/askbrent/rollback/' AS URL, - 'Rollback started at ' + CAST(r.start_time AS NVARCHAR(100)) + ', is ' + CAST(r.percent_complete AS NVARCHAR(100)) + '% complete.' AS Details, - 'Unfortunately, you can''t stop this. Whatever you do, don''t restart the server in an attempt to fix it - SQL Server will keep rolling back.' AS HowToStopIt, - r.start_time AS StartTime, - s.login_name AS LoginName, - s.nt_user_name AS NTUserName, - s.[program_name] AS ProgramName, - s.[host_name] AS HostName, - db.[resource_database_id] AS DatabaseID, - DB_NAME(db.resource_database_id) AS DatabaseName, - (SELECT TOP 1 [text] FROM sys.dm_exec_sql_text(c.most_recent_sql_handle)) AS QueryText, - r.query_hash - FROM sys.dm_exec_sessions s - INNER JOIN sys.dm_exec_connections c ON s.session_id = c.session_id - INNER JOIN sys.dm_exec_requests r ON s.session_id = r.session_id - LEFT OUTER JOIN ( - SELECT DISTINCT request_session_id, resource_database_id - FROM sys.dm_tran_locks - WHERE resource_type = N'DATABASE' - AND request_mode = N'S' - AND request_status = N'GRANT' - AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.session_id = db.request_session_id - WHERE r.status = 'rollback'; + BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 9',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, QueryHash) + SELECT 9 AS CheckID, + 1 AS Priority, + 'Query Problems' AS FindingGroup, + 'Query Rolling Back' AS Finding, + 'http://www.BrentOzar.com/askbrent/rollback/' AS URL, + 'Rollback started at ' + CAST(r.start_time AS NVARCHAR(100)) + ', is ' + CAST(r.percent_complete AS NVARCHAR(100)) + '% complete.' AS Details, + 'Unfortunately, you can''t stop this. Whatever you do, don''t restart the server in an attempt to fix it - SQL Server will keep rolling back.' AS HowToStopIt, + r.start_time AS StartTime, + s.login_name AS LoginName, + s.nt_user_name AS NTUserName, + s.[program_name] AS ProgramName, + s.[host_name] AS HostName, + db.[resource_database_id] AS DatabaseID, + DB_NAME(db.resource_database_id) AS DatabaseName, + (SELECT TOP 1 [text] FROM sys.dm_exec_sql_text(c.most_recent_sql_handle)) AS QueryText, + r.query_hash + FROM sys.dm_exec_sessions s + INNER JOIN sys.dm_exec_connections c ON s.session_id = c.session_id + INNER JOIN sys.dm_exec_requests r ON s.session_id = r.session_id + LEFT OUTER JOIN ( + SELECT DISTINCT request_session_id, resource_database_id + FROM sys.dm_tran_locks + WHERE resource_type = N'DATABASE' + AND request_mode = N'S' + AND request_status = N'GRANT' + AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.session_id = db.request_session_id + WHERE r.status = 'rollback'; + END /* Server Performance - Too Much Free Memory - CheckID 34 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 34',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) SELECT 34 AS CheckID, 50 AS Priority, @@ -20086,6 +20164,12 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, /* Server Performance - Target Memory Lower Than Max - CheckID 35 */ IF SERVERPROPERTY('Edition') <> 'SQL Azure' + BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 35',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) SELECT 35 AS CheckID, 10 AS Priority, @@ -20102,8 +20186,14 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, AND CAST(cMax.value_in_use AS BIGINT) >= 1.5 * (CAST(cTarget.cntr_value AS BIGINT) / 1024) AND CAST(cMax.value_in_use AS BIGINT) < 2147483647 /* Not set to default of unlimited */ AND CAST(cTarget.cntr_value AS BIGINT) < .8 * (SELECT available_physical_memory_kb FROM sys.dm_os_sys_memory); /* Target memory less than 80% of physical memory (in case they set max too high) */ + END /* Server Info - Database Size, Total GB - CheckID 21 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 21',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) SELECT 21 AS CheckID, 251 AS Priority, @@ -20116,6 +20206,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, WHERE database_id > 4; /* Server Info - Database Count - CheckID 22 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 22',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) SELECT 22 AS CheckID, 251 AS Priority, @@ -20128,6 +20223,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, WHERE database_id > 4; /* Server Info - Memory Grants pending - CheckID 39 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 39',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) SELECT 39 AS CheckID, 50 AS Priority, @@ -20147,6 +20247,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, WHERE PendingGrants.Details > 0; /* Server Info - Memory Grant/Workspace info - CheckID 40 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 40',10,1) WITH NOWAIT; + END + DECLARE @MaxWorkspace BIGINT SET @MaxWorkspace = (SELECT CAST(cntr_value AS BIGINT)/1024 FROM #PerfmonStats WHERE counter_name = N'Maximum Workspace Memory (KB)') @@ -20172,17 +20277,22 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, FROM sys.dm_exec_query_memory_grants AS Grants; /* Query Problems - Queries with high memory grants - CheckID 46 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 46',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, URL, QueryText, QueryPlan) SELECT 46 AS CheckID, 100 AS Priority, 'Query Problems' AS FindingGroup, - 'Query with memory a grant exceeding ' + 'Query with a memory grant exceeding ' +CAST(@MemoryGrantThresholdPct AS NVARCHAR(15)) +'%' AS Finding, 'Granted size: '+ CAST(CAST(Grants.granted_memory_kb / 1024 AS INT) AS NVARCHAR(50)) +N'MB ' + @LineFeed - +N'Granted pct: ' + +N'Granted pct of max workspace: ' + CAST(ISNULL((CAST(Grants.granted_memory_kb / 1024 AS MONEY) / CAST(@MaxWorkspace AS MONEY)) * 100, 0) AS NVARCHAR(50)) + '%' + @LineFeed @@ -20199,6 +20309,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, /* Query Problems - Memory Leak in USERSTORE_TOKENPERM Cache - CheckID 45 */ IF EXISTS (SELECT * FROM sys.all_columns WHERE object_id = OBJECT_ID('sys.dm_os_memory_clerks') AND name = 'pages_kb') BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 45',10,1) WITH NOWAIT; + END + /* SQL 2012+ version */ SET @StringToExecute = N' INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, URL) @@ -20218,6 +20333,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, ELSE BEGIN /* Antiques Roadshow SQL 2008R2 - version */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 45 (Legacy version)',10,1) WITH NOWAIT; + END + SET @StringToExecute = N' INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, URL) SELECT 45 AS CheckID, @@ -20352,6 +20472,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, y.parallelism_skew; /* Queries in dm_exec_query_profiles showing signs of poor cardinality estimates - CheckID 42 */ + IF (@Debug = 1) + BEGIN + RAISERROR(''Running CheckID 42'',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, OpenTransactionCount, QueryHash, QueryPlan) SELECT 42 AS CheckID, @@ -20398,6 +20523,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, SET @StringToExecute = @StringToExecute + N'; /* Queries in dm_exec_query_profiles showing signs of unbalanced parallelism - CheckID 43 */ + IF (@Debug = 1) + BEGIN + RAISERROR(''Running CheckID 43'',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, OpenTransactionCount, QueryHash, QueryPlan) SELECT 43 AS CheckID, @@ -20443,7 +20573,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, SET @StringToExecute = @StringToExecute + N';'; - EXECUTE sp_executesql @StringToExecute; + EXECUTE sp_executesql @StringToExecute, N'@Debug BIT',@Debug = @Debug; END END @@ -20456,6 +20586,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, We get this data from the ring buffers, and it's only updated once per minute, so might as well get it now - whereas if we're checking 30+ seconds, it might get updated by the end of our sp_BlitzFirst session. */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 24',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) SELECT 24, 50, 'Server Performance', 'High CPU Utilization', CAST(100 - SystemIdle AS NVARCHAR(20)) + N'%.', 100 - SystemIdle, 'http://www.BrentOzar.com/go/cpu' FROM ( @@ -20471,6 +20606,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, WHERE 100 - SystemIdle >= 50; /* CPU Utilization - CheckID 23 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 23',10,1) WITH NOWAIT; + END + IF SERVERPROPERTY('Edition') <> 'SQL Azure' WITH y AS @@ -20512,6 +20652,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, /* Highlight if non SQL processes are using >25% CPU - CheckID 28 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 28',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) SELECT 28, 50, 'Server Performance', 'High CPU Utilization - Not SQL', CONVERT(NVARCHAR(100),100 - (y.SQLUsage + y.SystemIdle)) + N'% - Other Processes (not SQL Server) are using this much CPU. This may impact on the performance of your SQL Server instance', 100 - (y.SQLUsage + y.SystemIdle), 'http://www.BrentOzar.com/go/cpu' FROM ( @@ -20530,6 +20675,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, END; /* IF @Seconds < 30 */ /* Query Problems - Statistics Updated Recently - CheckID 44 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 44',10,1) WITH NOWAIT; + END + IF 20 >= (SELECT COUNT(*) FROM sys.databases WHERE name NOT IN ('master', 'model', 'msdb', 'tempdb')) BEGIN CREATE TABLE #UpdatedStats (HowToStopIt NVARCHAR(4000), RowsForSorting BIGINT); @@ -20717,6 +20867,10 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, /* Query Stats - If we're within 10 seconds of our projected finish time, do the plan cache analysis. - CheckID 18 */ IF DATEDIFF(ss, @FinishSampleTime, SYSDATETIMEOFFSET()) > 10 AND @CheckProcedureCache = 1 BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 18',10,1) WITH NOWAIT; + END INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) VALUES (18, 210, 'Query Stats', 'Plan Cache Analysis Skipped', 'http://www.BrentOzar.com/go/topqueries', @@ -20856,6 +21010,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, INNER JOIN qsTop ON qs.ID = qsTop.ID; /* Query Stats - Most Resource-Intensive Queries - CheckID 17 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 17',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, QueryText, QueryStatsNowID, QueryStatsFirstID, PlanHandle, QueryHash) SELECT 17, 210, 'Query Stats', 'Most Resource-Intensive Queries', 'http://www.BrentOzar.com/go/topqueries', 'Query stats during the sample:' + @LineFeed + @@ -20915,6 +21074,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, /* Wait Stats - CheckID 6 */ /* Compare the current wait stats to the sample we took at the start, and insert the top 10 waits. */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 6',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, DetailsInt) SELECT TOP 10 6 AS CheckID, 200 AS Priority, @@ -20930,6 +21094,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, ORDER BY (wNow.wait_time_ms - COALESCE(wBase.wait_time_ms,0)) DESC; /* Server Performance - Poison Wait Detected - CheckID 30 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 30',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, DetailsInt) SELECT 30 AS CheckID, 10 AS Priority, @@ -20948,6 +21117,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, /* Server Performance - Slow Data File Reads - CheckID 11 */ IF EXISTS (SELECT * FROM #BlitzFirstResults WHERE Finding LIKE 'PAGEIOLATCH%') BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 11',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, DatabaseID, DatabaseName) SELECT TOP 10 11 AS CheckID, 50 AS Priority, @@ -20973,6 +21147,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, /* Server Performance - Slow Log File Writes - CheckID 12 */ IF EXISTS (SELECT * FROM #BlitzFirstResults WHERE Finding LIKE 'WRITELOG%') BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 12',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, DatabaseID, DatabaseName) SELECT TOP 10 12 AS CheckID, 50 AS Priority, @@ -20997,6 +21176,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, /* SQL Server Internal Maintenance - Log File Growing - CheckID 13 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 13',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) SELECT 13 AS CheckID, 1 AS Priority, @@ -21014,6 +21198,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, /* SQL Server Internal Maintenance - Log File Shrinking - CheckID 14 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 14',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) SELECT 14 AS CheckID, 1 AS Priority, @@ -21030,6 +21219,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, AND value_delta > 0; /* Query Problems - Compilations/Sec High - CheckID 15 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 15',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) SELECT 15 AS CheckID, 50 AS Priority, @@ -21051,6 +21245,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, AND (psComp.value_delta * 10) > ps.value_delta; /* Compilations are more than 10% of batch requests per second */ /* Query Problems - Re-Compilations/Sec High - CheckID 16 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 16',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) SELECT 16 AS CheckID, 50 AS Priority, @@ -21072,13 +21271,18 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, AND (psComp.value_delta * 10) > ps.value_delta; /* Recompilations are more than 10% of batch requests per second */ /* Table Problems - Forwarded Fetches/Sec High - CheckID 29 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 29',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) SELECT 29 AS CheckID, 40 AS Priority, 'Table Problems' AS FindingGroup, 'Forwarded Fetches/Sec High' AS Finding, 'https://BrentOzar.com/go/fetch/' AS URL, - CAST(ps.value_delta AS NVARCHAR(20)) + ' Forwarded Records (from SQLServer:Access Methods counter)' + @LineFeed + CAST(ps.value_delta AS NVARCHAR(20)) + ' forwarded fetches (from SQLServer:Access Methods counter)' + @LineFeed + 'Check your heaps: they need to be rebuilt, or they need a clustered index applied.' + @LineFeed AS Details, 'Rebuild your heaps. If you use Ola Hallengren maintenance scripts, those do not rebuild heaps by default: https://www.brentozar.com/archive/2016/07/fix-forwarded-records/' AS HowToStopIt FROM #PerfmonStats ps @@ -21097,19 +21301,30 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, SELECT TOP 10 29 AS CheckID, 40 AS Priority, ''Table Problems'' AS FindingGroup, - ''Forwarded Fetches/Sec High: Temp Table'' AS Finding, + ''Forwarded Fetches/Sec High: TempDB Object'' AS Finding, ''https://BrentOzar.com/go/fetch/'' AS URL, - CAST(COALESCE(os.forwarded_fetch_count,0) AS NVARCHAR(20)) + '' forwarded fetches on temp table '' + COALESCE(OBJECT_NAME(os.object_id), ''Unknown'') AS Details, - ''Look through your source code to find the object creating these temp tables, and tune the creation and population to reduce fetches. See the URL for details.'' AS HowToStopIt + CAST(COALESCE(os.forwarded_fetch_count,0) - COALESCE(os_prior.forwarded_fetch_count,0) AS NVARCHAR(20)) + '' forwarded fetches on '' + + CASE WHEN OBJECT_NAME(os.object_id) IS NULL THEN ''an unknown table '' + WHEN LEN(OBJECT_NAME(os.object_id)) < 50 THEN ''a table variable, internal identifier '' + OBJECT_NAME(os.object_id) + ELSE ''a temp table '' + OBJECT_NAME(os.object_id) + END AS Details, + ''Look through your source code to find the object creating these objects, and tune the creation and population to reduce fetches. See the URL for details.'' AS HowToStopIt FROM tempdb.sys.dm_db_index_operational_stats(DB_ID(''tempdb''), NULL, NULL, NULL) os + LEFT OUTER JOIN #TempdbOperationalStats os_prior ON os.object_id = os_prior.object_id + AND os.forwarded_fetch_count > os_prior.forwarded_fetch_count WHERE os.database_id = DB_ID(''tempdb'') - AND os.forwarded_fetch_count > 100 + AND os.forwarded_fetch_count - COALESCE(os_prior.forwarded_fetch_count,0) > 100 ORDER BY os.forwarded_fetch_count DESC;' EXECUTE sp_executesql @StringToExecute; END /* In-Memory OLTP - Garbage Collection in Progress - CheckID 31 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 31',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) SELECT 31 AS CheckID, 50 AS Priority, @@ -21128,6 +21343,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, AND ps.value_delta > (100 * @Seconds); /* Ignore servers sitting idle */ /* In-Memory OLTP - Transactions Aborted - CheckID 32 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 32',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) SELECT 32 AS CheckID, 100 AS Priority, @@ -21145,6 +21365,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, AND ps.value_delta > (10 * @Seconds); /* Ignore servers sitting idle */ /* Query Problems - Suboptimal Plans/Sec High - CheckID 33 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 33',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) SELECT 32 AS CheckID, 100 AS Priority, @@ -21163,6 +21388,12 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, /* Azure Performance - Database is Maxed Out - CheckID 41 */ IF SERVERPROPERTY('Edition') = 'SQL Azure' + BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 41',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) SELECT 41 AS CheckID, 10 AS Priority, @@ -21183,8 +21414,14 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, OR avg_log_write_percent >=90 OR max_worker_percent >= 90 OR max_session_percent >= 90); + END /* Server Info - Batch Requests per Sec - CheckID 19 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 19',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, DetailsInt) SELECT 19 AS CheckID, 250 AS Priority, @@ -21205,39 +21442,58 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, /* Server Info - SQL Compilations/sec - CheckID 25 */ IF @ExpertMode = 1 - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, DetailsInt) - SELECT 25 AS CheckID, - 250 AS Priority, - 'Server Info' AS FindingGroup, - 'SQL Compilations per Sec' AS Finding, - 'http://www.BrentOzar.com/go/measure' AS URL, - CAST(ps.value_delta / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS NVARCHAR(20)) AS Details, - ps.value_delta / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS DetailsInt - FROM #PerfmonStats ps - INNER JOIN #PerfmonStats ps1 ON ps.object_name = ps1.object_name AND ps.counter_name = ps1.counter_name AND ps1.Pass = 1 - WHERE ps.Pass = 2 - AND ps.object_name = @ServiceName + ':SQL Statistics' - AND ps.counter_name = 'SQL Compilations/sec'; + BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 25',10,1) WITH NOWAIT; + END + + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, DetailsInt) + SELECT 25 AS CheckID, + 250 AS Priority, + 'Server Info' AS FindingGroup, + 'SQL Compilations per Sec' AS Finding, + 'http://www.BrentOzar.com/go/measure' AS URL, + CAST(ps.value_delta / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS NVARCHAR(20)) AS Details, + ps.value_delta / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS DetailsInt + FROM #PerfmonStats ps + INNER JOIN #PerfmonStats ps1 ON ps.object_name = ps1.object_name AND ps.counter_name = ps1.counter_name AND ps1.Pass = 1 + WHERE ps.Pass = 2 + AND ps.object_name = @ServiceName + ':SQL Statistics' + AND ps.counter_name = 'SQL Compilations/sec'; + END /* Server Info - SQL Re-Compilations/sec - CheckID 26 */ IF @ExpertMode = 1 - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, DetailsInt) - SELECT 26 AS CheckID, - 250 AS Priority, - 'Server Info' AS FindingGroup, - 'SQL Re-Compilations per Sec' AS Finding, - 'http://www.BrentOzar.com/go/measure' AS URL, - CAST(ps.value_delta / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS NVARCHAR(20)) AS Details, - ps.value_delta / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS DetailsInt - FROM #PerfmonStats ps - INNER JOIN #PerfmonStats ps1 ON ps.object_name = ps1.object_name AND ps.counter_name = ps1.counter_name AND ps1.Pass = 1 - WHERE ps.Pass = 2 - AND ps.object_name = @ServiceName + ':SQL Statistics' - AND ps.counter_name = 'SQL Re-Compilations/sec'; + BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 26',10,1) WITH NOWAIT; + END + + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, DetailsInt) + SELECT 26 AS CheckID, + 250 AS Priority, + 'Server Info' AS FindingGroup, + 'SQL Re-Compilations per Sec' AS Finding, + 'http://www.BrentOzar.com/go/measure' AS URL, + CAST(ps.value_delta / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS NVARCHAR(20)) AS Details, + ps.value_delta / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS DetailsInt + FROM #PerfmonStats ps + INNER JOIN #PerfmonStats ps1 ON ps.object_name = ps1.object_name AND ps.counter_name = ps1.counter_name AND ps1.Pass = 1 + WHERE ps.Pass = 2 + AND ps.object_name = @ServiceName + ':SQL Statistics' + AND ps.counter_name = 'SQL Re-Compilations/sec'; + END /* Server Info - Wait Time per Core per Sec - CheckID 20 */ IF @Seconds > 0 BEGIN; + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 20',10,1) WITH NOWAIT; + END; + WITH waits1(SampleTime, waits_ms) AS (SELECT SampleTime, SUM(ws1.wait_time_ms) FROM #WaitStats ws1 WHERE ws1.Pass = 1 GROUP BY SampleTime), waits2(SampleTime, waits_ms) AS (SELECT SampleTime, SUM(ws2.wait_time_ms) FROM #WaitStats ws2 WHERE ws2.Pass = 2 GROUP BY SampleTime), cores(cpu_count) AS (SELECT SUM(1) FROM sys.dm_os_schedulers WHERE status = 'VISIBLE ONLINE' AND is_online = 1) @@ -21259,8 +21515,13 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, as well get it now - whereas if we're checking 30+ seconds, it might get updated by the end of our sp_BlitzFirst session. */ IF @Seconds >= 30 - BEGIN + BEGIN /* Server Performance - High CPU Utilization CheckID 24 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 24',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) SELECT 24, 50, 'Server Performance', 'High CPU Utilization', CAST(100 - SystemIdle AS NVARCHAR(20)) + N'%. Ring buffer details: ' + CAST(record AS NVARCHAR(4000)), 100 - SystemIdle, 'http://www.BrentOzar.com/go/cpu' FROM ( @@ -21276,6 +21537,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, WHERE 100 - SystemIdle >= 50; /* Server Performance - CPU Utilization CheckID 23 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 23',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) SELECT 23, 250, 'Server Info', 'CPU Utilization', CAST(100 - SystemIdle AS NVARCHAR(20)) + N'%. Ring buffer details: ' + CAST(record AS NVARCHAR(4000)), 100 - SystemIdle, 'http://www.BrentOzar.com/go/cpu' FROM ( @@ -21289,7 +21555,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, ORDER BY timestamp DESC) AS rb ) AS y; - END; /* IF @Seconds >= 30 */ + END; /* IF @Seconds >= 30 */ /* If we didn't find anything, apologize. */ @@ -21349,6 +21615,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, ); /* Outdated sp_BlitzFirst - sp_BlitzFirst is Over 6 Months Old - CheckID 27 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 27',10,1) WITH NOWAIT; + END + IF DATEDIFF(MM, @VersionDate, SYSDATETIMEOFFSET()) > 6 BEGIN INSERT INTO #BlitzFirstResults @@ -21440,6 +21711,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, ELSE BEGIN /* No sp_BlitzCache found, or it's outdated - CheckID 36 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 36',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults ( CheckID , Priority , @@ -22762,7 +23038,7 @@ AS SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '7.99999', @VersionDate = '20201211'; +SELECT @Version = '8.0', @VersionDate = '20210117'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -22770,7 +23046,9 @@ BEGIN RETURN; END; -IF @Help = 1 PRINT ' +IF @Help = 1 +BEGIN +PRINT ' /* sp_BlitzIndex from http://FirstResponderKit.org @@ -22797,7 +23075,7 @@ Unknown limitations of this version: MIT License -Copyright (c) 2020 Brent Ozar Unlimited +Copyright (c) 2021 Brent Ozar Unlimited Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -22817,7 +23095,8 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. '; - +RETURN; +END; /* @Help = 1 */ DECLARE @ScriptVersionName NVARCHAR(50); DECLARE @DaysUptime NUMERIC(23,2); @@ -24299,15 +24578,15 @@ BEGIN TRY FROM ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_details AS id_inner CROSS APPLY( SELECT LTRIM(RTRIM(v.value(''(./text())[1]'', ''varchar(max)''))) AS ColumnName, ''Equality'' AS IndexColumnType - FROM (VALUES (CONVERT(XML, N'''' + REPLACE(CAST(id_inner.equality_columns AS nvarchar(max)), N'','', N'''') + N''''))) x(n) + FROM (VALUES (CONVERT(XML, N'''' + REPLACE((SELECT CAST(id_inner.equality_columns AS nvarchar(max)) FOR XML PATH('''')), N'','', N'''') + N''''))) x(n) CROSS APPLY n.nodes(''x'') node(v) UNION ALL SELECT LTRIM(RTRIM(v.value(N''(./text())[1]'', ''varchar(max)''))) AS ColumnName, ''Inequality'' AS IndexColumnType - FROM (VALUES (CONVERT(XML, N'''' + REPLACE(CAST(id_inner.inequality_columns AS nvarchar(max)), N'','', N'''') + N''''))) x(n) + FROM (VALUES (CONVERT(XML, N'''' + REPLACE((SELECT CAST(id_inner.inequality_columns AS nvarchar(max)) FOR XML PATH('''')), N'','', N'''') + N''''))) x(n) CROSS APPLY n.nodes(''x'') node(v) UNION ALL SELECT LTRIM(RTRIM(v.value(''(./text())[1]'', ''varchar(max)''))) AS ColumnName, ''Included'' AS IndexColumnType - FROM (VALUES (CONVERT(XML, N'''' + REPLACE(CAST(id_inner.included_columns AS nvarchar(max)), N'','', N'''') + N''''))) x(n) + FROM (VALUES (CONVERT(XML, N'''' + REPLACE((SELECT CAST(id_inner.included_columns AS nvarchar(max)) FOR XML PATH('''')), N'','', N'''') + N''''))) x(n) CROSS APPLY n.nodes(''x'') node(v) )AS cn_inner' + /*split the string otherwise dsql cuts some of it out*/ @@ -24321,15 +24600,15 @@ BEGIN TRY FROM ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_details AS id CROSS APPLY( SELECT LTRIM(RTRIM(v.value(''(./text())[1]'', ''varchar(max)''))) AS ColumnName, ''Equality'' AS IndexColumnType - FROM (VALUES (CONVERT(XML, N'''' + REPLACE(CAST(id.equality_columns AS nvarchar(max)), N'','', N'''') + N''''))) x(n) + FROM (VALUES (CONVERT(XML, N'''' + REPLACE((SELECT CAST(id.equality_columns AS nvarchar(max)) FOR XML PATH('''')), N'','', N'''') + N''''))) x(n) CROSS APPLY n.nodes(''x'') node(v) UNION ALL SELECT LTRIM(RTRIM(v.value(''(./text())[1]'', ''varchar(max)''))) AS ColumnName, ''Inequality'' AS IndexColumnType - FROM (VALUES (CONVERT(XML, N'''' + REPLACE(CAST(id.inequality_columns AS nvarchar(max)), N'','', N'''') + N''''))) x(n) + FROM (VALUES (CONVERT(XML, N'''' + REPLACE((SELECT CAST(id.inequality_columns AS nvarchar(max)) FOR XML PATH('''')), N'','', N'''') + N''''))) x(n) CROSS APPLY n.nodes(''x'') node(v) UNION ALL SELECT LTRIM(RTRIM(v.value(''(./text())[1]'', ''varchar(max)''))) AS ColumnName, ''Included'' AS IndexColumnType - FROM (VALUES (CONVERT(XML, N'''' + REPLACE(CAST(id.included_columns AS nvarchar(max)), N'','', N'''') + N''''))) x(n) + FROM (VALUES (CONVERT(XML, N'''' + REPLACE((SELECT CAST(id.included_columns AS nvarchar(max)) FOR XML PATH('''')), N'','', N'''') + N''''))) x(n) CROSS APPLY n.nodes(''x'') node(v) )AS cn GROUP BY id.index_handle,id.object_id,cn.IndexColumnType @@ -28285,14 +28564,16 @@ BEGIN SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '2.99999', @VersionDate = '20201211'; +SELECT @Version = '8.0', @VersionDate = '20210117'; IF(@VersionCheckMode = 1) BEGIN RETURN; END; - IF @Help = 1 PRINT ' + IF @Help = 1 + BEGIN + PRINT ' /* sp_BlitzLock from http://FirstResponderKit.org @@ -28347,7 +28628,7 @@ END; MIT License - Copyright (c) 2020 Brent Ozar Unlimited + Copyright (c) 2021 Brent Ozar Unlimited Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -28368,8 +28649,9 @@ END; SOFTWARE. */'; - - + RETURN; + END; /* @Help = 1 */ + DECLARE @ProductVersion NVARCHAR(128); DECLARE @ProductVersionMajor FLOAT; DECLARE @ProductVersionMinor INT; @@ -28552,9 +28834,24 @@ You need to use an Azure storage account, and the path has to look like this: ht AND LEFT(CAST(SERVERPROPERTY('MachineName') AS VARCHAR(8000)), 8) <> 'EC2AMAZ-' AND LEFT(CAST(SERVERPROPERTY('ServerName') AS VARCHAR(8000)), 8) <> 'EC2AMAZ-' AND db_id('rdsadmin') IS NULL - BEGIN - UPDATE STATISTICS #t WITH ROWCOUNT = 100000000, PAGECOUNT = 100000000; - END + BEGIN; + BEGIN TRY; + UPDATE STATISTICS #t WITH ROWCOUNT = 100000000, PAGECOUNT = 100000000; + END TRY + BEGIN CATCH; + /* Misleading error returned, if run without permissions to update statistics the error returned is "Cannot find object". + Catching specific error, and returning message with better info. If any other error is returned, then throw as normal */ + IF (ERROR_NUMBER() = 1088) + BEGIN; + SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + RAISERROR('Cannot run UPDATE STATISTICS on temp table without db_owner or sysadmin permissions %s', 0, 1, @d) WITH NOWAIT; + END; + ELSE + BEGIN; + THROW; + END; + END CATCH; + END; /*Grab the initial set of XML to parse*/ SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); @@ -30014,7 +30311,7 @@ BEGIN /*First BEGIN*/ SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '3.99999', @VersionDate = '20201211'; +SELECT @Version = '8.0', @VersionDate = '20210117'; IF(@VersionCheckMode = 1) BEGIN RETURN; @@ -30100,7 +30397,7 @@ IF @Help = 1 MIT License - Copyright (c) 2020 Brent Ozar Unlimited + Copyright (c) 2021 Brent Ozar Unlimited Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -35741,7 +36038,7 @@ BEGIN SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '7.99999', @VersionDate = '20201211'; + SELECT @Version = '8.0', @VersionDate = '20210117'; IF(@VersionCheckMode = 1) BEGIN @@ -35751,6 +36048,7 @@ BEGIN IF @Help = 1 + BEGIN PRINT ' sp_BlitzWho from http://FirstResponderKit.org @@ -35768,7 +36066,7 @@ Known limitations of this version: MIT License -Copyright (c) 2020 Brent Ozar Unlimited +Copyright (c) 2021 Brent Ozar Unlimited Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -35788,6 +36086,8 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. '; +RETURN; +END; /* @Help = 1 */ /* Get the major and minor build numbers */ DECLARE @ProductVersion NVARCHAR(128) @@ -36282,7 +36582,7 @@ BEGIN /* Think of the StringToExecute as starting with this, but we'll set this up later depending on whether we're doing an insert or a select: SELECT @StringToExecute = N'SELECT GETDATE() AS run_date , */ - SET @StringToExecute = N'COALESCE( RIGHT(''00'' + CONVERT(VARCHAR(20), (ABS(r.total_elapsed_time) / 1000) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), (DATEADD(SECOND, (r.total_elapsed_time / 1000), 0) + DATEADD(MILLISECOND, (r.total_elapsed_time % 1000), 0)), 114), CONVERT(VARCHAR(20), DATEDIFF(SECOND, s.last_request_start_time, GETDATE()) / 86400) + '':'' + CONVERT(VARCHAR(20), DATEADD(SECOND, DATEDIFF(SECOND, s.last_request_start_time, GETDATE()), 0), 114) ) AS [elapsed_time] , + SET @StringToExecute = N'COALESCE( RIGHT(''00'' + CONVERT(VARCHAR(20), (ABS(r.total_elapsed_time) / 1000) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), (DATEADD(SECOND, (r.total_elapsed_time / 1000), 0) + DATEADD(MILLISECOND, (r.total_elapsed_time % 1000), 0)), 114), RIGHT(''00'' + CONVERT(VARCHAR(20), DATEDIFF(SECOND, s.last_request_start_time, GETDATE()) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), DATEADD(SECOND, DATEDIFF(SECOND, s.last_request_start_time, GETDATE()), 0), 114) ) AS [elapsed_time] , s.session_id , COALESCE(DB_NAME(r.database_id), DB_NAME(blocked.dbid), ''N/A'') AS database_name, ISNULL(SUBSTRING(dest.text, @@ -36487,7 +36787,7 @@ IF @ProductVersionMajor >= 11 /* Think of the StringToExecute as starting with this, but we'll set this up later depending on whether we're doing an insert or a select: SELECT @StringToExecute = N'SELECT GETDATE() AS run_date , */ - SELECT @StringToExecute = N'COALESCE( RIGHT(''00'' + CONVERT(VARCHAR(20), (ABS(r.total_elapsed_time) / 1000) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), (DATEADD(SECOND, (r.total_elapsed_time / 1000), 0) + DATEADD(MILLISECOND, (r.total_elapsed_time % 1000), 0)), 114), CONVERT(VARCHAR(20), DATEDIFF(SECOND, s.last_request_start_time, GETDATE()) / 86400) + '':'' + CONVERT(VARCHAR(20), DATEADD(SECOND, DATEDIFF(SECOND, s.last_request_start_time, GETDATE()), 0), 114) ) AS [elapsed_time] , + SELECT @StringToExecute = N'COALESCE( RIGHT(''00'' + CONVERT(VARCHAR(20), (ABS(r.total_elapsed_time) / 1000) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), (DATEADD(SECOND, (r.total_elapsed_time / 1000), 0) + DATEADD(MILLISECOND, (r.total_elapsed_time % 1000), 0)), 114), RIGHT(''00'' + CONVERT(VARCHAR(20), DATEDIFF(SECOND, s.last_request_start_time, GETDATE()) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), DATEADD(SECOND, DATEDIFF(SECOND, s.last_request_start_time, GETDATE()), 0), 114) ) AS [elapsed_time] , s.session_id , COALESCE(DB_NAME(r.database_id), DB_NAME(blocked.dbid), ''N/A'') AS database_name, ISNULL(SUBSTRING(dest.text, @@ -36979,6 +37279,7 @@ DELETE FROM dbo.SqlServerVersions; INSERT INTO dbo.SqlServerVersions (MajorVersionNumber, MinorVersionNumber, Branch, [Url], ReleaseDate, MainstreamSupportEndDate, ExtendedSupportEndDate, MajorVersionName, MinorVersionName) VALUES + (15, 4073, 'CU8 GDR', 'https://support.microsoft.com/en-us/help/4583459', '2021-01-12', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 8 GDR '), (15, 4073, 'CU8', 'https://support.microsoft.com/en-us/help/4577194', '2020-10-01', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 8 '), (15, 4063, 'CU7', 'https://support.microsoft.com/en-us/help/4570012', '2020-09-02', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 7 '), (15, 4053, 'CU6', 'https://support.microsoft.com/en-us/help/4563110', '2020-08-04', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 6 '), @@ -36989,6 +37290,7 @@ VALUES (15, 4003, 'CU1', 'https://support.microsoft.com/en-us/help/4527376', '2020-01-07', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 1 '), (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 '), + (14, 3370, 'RTM CU22 GDR', 'https://support.microsoft.com/en-us/help/4583457', '2021-01-12', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 22 GDR'), (14, 3356, 'RTM CU22', 'https://support.microsoft.com/en-us/help/4577467', '2020-09-10', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 22'), (14, 3335, 'RTM CU21', 'https://support.microsoft.com/en-us/help/4557397', '2020-07-01', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 21'), (14, 3294, 'RTM CU20', 'https://support.microsoft.com/en-us/help/4541283', '2020-04-07', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 20'), @@ -37012,6 +37314,7 @@ VALUES (14, 3008, 'RTM CU2', 'https://support.microsoft.com/en-us/help/4052574', '2017-11-28', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 2'), (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 '), + (13, 5865, 'SP2 CU15 GDR', 'https://support.microsoft.com/en-us/help/4583461', '2021-01-12', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 15 GDR'), (13, 5850, 'SP2 CU15', 'https://support.microsoft.com/en-us/help/4577775', '2020-09-28', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 15'), (13, 5830, 'SP2 CU14', 'https://support.microsoft.com/en-us/help/4564903', '2020-08-06', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 14'), (13, 5820, 'SP2 CU13', 'https://support.microsoft.com/en-us/help/4549825', '2020-05-28', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 13'), @@ -37028,6 +37331,7 @@ VALUES (13, 5201, 'SP2 CU2 + Security Update', 'https://support.microsoft.com/en-us/help/4458621', '2018-08-21', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 2 + Security Update'), (13, 5153, 'SP2 CU2', 'https://support.microsoft.com/en-us/help/4340355', '2018-07-16', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 2'), (13, 5149, 'SP2 CU1', 'https://support.microsoft.com/en-us/help/4135048', '2018-05-30', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 1'), + (13, 5103, 'SP2 GDR', 'https://support.microsoft.com/en-us/help/4583460', '2021-01-12', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 GDR'), (13, 5026, 'SP2 ', 'https://support.microsoft.com/en-us/help/4052908', '2018-04-24', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 '), (13, 4574, 'SP1 CU15', 'https://support.microsoft.com/en-us/help/4495257', '2019-05-16', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 15'), (13, 4560, 'SP1 CU14', 'https://support.microsoft.com/en-us/help/4488535', '2019-03-19', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 14'), @@ -37056,10 +37360,13 @@ VALUES (13, 2164, 'RTM CU2', 'https://support.microsoft.com/en-us/help/3182270 ', '2016-09-22', '2018-01-09', '2018-01-09', 'SQL Server 2016', 'RTM Cumulative Update 2'), (13, 2149, 'RTM CU1', 'https://support.microsoft.com/en-us/help/3164674 ', '2016-07-25', '2018-01-09', '2018-01-09', 'SQL Server 2016', 'RTM Cumulative Update 1'), (13, 1601, 'RTM ', '', '2016-06-01', '2019-01-09', '2019-01-09', 'SQL Server 2016', 'RTM '), + (12, 6433, 'SP3 CU4 GDR', 'https://support.microsoft.com/en-us/help/4583462', '2021-01-12', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 4 GDR'), + (12, 6372, 'SP3 CU4 GDR', 'https://support.microsoft.com/en-us/help/4535288', '2020-02-11', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 4 GDR'), (12, 6329, 'SP3 CU4', 'https://support.microsoft.com/en-us/help/4500181', '2019-07-29', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 4'), (12, 6259, 'SP3 CU3', 'https://support.microsoft.com/en-us/help/4491539', '2019-04-16', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 3'), (12, 6214, 'SP3 CU2', 'https://support.microsoft.com/en-us/help/4482960', '2019-02-19', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 2'), (12, 6205, 'SP3 CU1', 'https://support.microsoft.com/en-us/help/4470220', '2018-12-12', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 1'), + (12, 6164, 'SP3 GDR', 'https://support.microsoft.com/en-us/help/4583463', '2021-01-12', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 GDR'), (12, 6024, 'SP3 ', 'https://support.microsoft.com/en-us/help/4022619', '2018-10-30', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 '), (12, 5687, 'SP2 CU18', 'https://support.microsoft.com/en-us/help/4500180', '2019-07-29', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 18'), (12, 5632, 'SP2 CU17', 'https://support.microsoft.com/en-us/help/4491540', '2019-04-16', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 17'), @@ -37115,6 +37422,7 @@ VALUES (12, 2269, 'RTM MS15-058: GDR Security Update ', 'https://support.microsoft.com/en-us/help/3045324', '2015-07-14', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM MS15-058: GDR Security Update '), (12, 2254, 'RTM MS14-044: GDR Security Update', 'https://support.microsoft.com/en-us/help/2977315', '2014-08-12', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM MS14-044: GDR Security Update'), (12, 2000, 'RTM ', '', '2014-04-01', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM '), + (11, 7507, 'SP4 GDR Security Update', 'https://support.microsoft.com/en-us/help/4583465', '2021-01-12', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'Service Pack 4 GDR Security Update for CVE-2021-1636'), (11, 7493, 'SP4 GDR Security Update', 'https://support.microsoft.com/en-us/help/4532098', '2020-02-11', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'Service Pack 4 GDR Security Update for CVE-2020-0618'), (11, 7469, 'SP4 On-Demand Hotfix Update', 'https://support.microsoft.com/en-us/help/4091266', '2018-03-28', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'Service Pack 4 SP4 On-Demand Hotfix Update'), (11, 7462, 'SP4 ADV180002: GDR Security Update', 'https://support.microsoft.com/en-us/help/4057116', '2018-01-12', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'Service Pack 4 ADV180002: GDR Security Update'), diff --git a/SqlServerVersions.sql b/SqlServerVersions.sql index 99f4ab670..b265b6d98 100644 --- a/SqlServerVersions.sql +++ b/SqlServerVersions.sql @@ -41,6 +41,7 @@ DELETE FROM dbo.SqlServerVersions; INSERT INTO dbo.SqlServerVersions (MajorVersionNumber, MinorVersionNumber, Branch, [Url], ReleaseDate, MainstreamSupportEndDate, ExtendedSupportEndDate, MajorVersionName, MinorVersionName) VALUES + (15, 4073, 'CU8 GDR', 'https://support.microsoft.com/en-us/help/4583459', '2021-01-12', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 8 GDR '), (15, 4073, 'CU8', 'https://support.microsoft.com/en-us/help/4577194', '2020-10-01', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 8 '), (15, 4063, 'CU7', 'https://support.microsoft.com/en-us/help/4570012', '2020-09-02', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 7 '), (15, 4053, 'CU6', 'https://support.microsoft.com/en-us/help/4563110', '2020-08-04', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 6 '), @@ -51,6 +52,7 @@ VALUES (15, 4003, 'CU1', 'https://support.microsoft.com/en-us/help/4527376', '2020-01-07', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 1 '), (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 '), + (14, 3370, 'RTM CU22 GDR', 'https://support.microsoft.com/en-us/help/4583457', '2021-01-12', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 22 GDR'), (14, 3356, 'RTM CU22', 'https://support.microsoft.com/en-us/help/4577467', '2020-09-10', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 22'), (14, 3335, 'RTM CU21', 'https://support.microsoft.com/en-us/help/4557397', '2020-07-01', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 21'), (14, 3294, 'RTM CU20', 'https://support.microsoft.com/en-us/help/4541283', '2020-04-07', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 20'), @@ -74,6 +76,7 @@ VALUES (14, 3008, 'RTM CU2', 'https://support.microsoft.com/en-us/help/4052574', '2017-11-28', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 2'), (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 '), + (13, 5865, 'SP2 CU15 GDR', 'https://support.microsoft.com/en-us/help/4583461', '2021-01-12', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 15 GDR'), (13, 5850, 'SP2 CU15', 'https://support.microsoft.com/en-us/help/4577775', '2020-09-28', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 15'), (13, 5830, 'SP2 CU14', 'https://support.microsoft.com/en-us/help/4564903', '2020-08-06', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 14'), (13, 5820, 'SP2 CU13', 'https://support.microsoft.com/en-us/help/4549825', '2020-05-28', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 13'), @@ -90,6 +93,7 @@ VALUES (13, 5201, 'SP2 CU2 + Security Update', 'https://support.microsoft.com/en-us/help/4458621', '2018-08-21', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 2 + Security Update'), (13, 5153, 'SP2 CU2', 'https://support.microsoft.com/en-us/help/4340355', '2018-07-16', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 2'), (13, 5149, 'SP2 CU1', 'https://support.microsoft.com/en-us/help/4135048', '2018-05-30', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 1'), + (13, 5103, 'SP2 GDR', 'https://support.microsoft.com/en-us/help/4583460', '2021-01-12', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 GDR'), (13, 5026, 'SP2 ', 'https://support.microsoft.com/en-us/help/4052908', '2018-04-24', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 '), (13, 4574, 'SP1 CU15', 'https://support.microsoft.com/en-us/help/4495257', '2019-05-16', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 15'), (13, 4560, 'SP1 CU14', 'https://support.microsoft.com/en-us/help/4488535', '2019-03-19', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 14'), @@ -118,10 +122,13 @@ VALUES (13, 2164, 'RTM CU2', 'https://support.microsoft.com/en-us/help/3182270 ', '2016-09-22', '2018-01-09', '2018-01-09', 'SQL Server 2016', 'RTM Cumulative Update 2'), (13, 2149, 'RTM CU1', 'https://support.microsoft.com/en-us/help/3164674 ', '2016-07-25', '2018-01-09', '2018-01-09', 'SQL Server 2016', 'RTM Cumulative Update 1'), (13, 1601, 'RTM ', '', '2016-06-01', '2019-01-09', '2019-01-09', 'SQL Server 2016', 'RTM '), + (12, 6433, 'SP3 CU4 GDR', 'https://support.microsoft.com/en-us/help/4583462', '2021-01-12', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 4 GDR'), + (12, 6372, 'SP3 CU4 GDR', 'https://support.microsoft.com/en-us/help/4535288', '2020-02-11', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 4 GDR'), (12, 6329, 'SP3 CU4', 'https://support.microsoft.com/en-us/help/4500181', '2019-07-29', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 4'), (12, 6259, 'SP3 CU3', 'https://support.microsoft.com/en-us/help/4491539', '2019-04-16', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 3'), (12, 6214, 'SP3 CU2', 'https://support.microsoft.com/en-us/help/4482960', '2019-02-19', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 2'), (12, 6205, 'SP3 CU1', 'https://support.microsoft.com/en-us/help/4470220', '2018-12-12', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 1'), + (12, 6164, 'SP3 GDR', 'https://support.microsoft.com/en-us/help/4583463', '2021-01-12', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 GDR'), (12, 6024, 'SP3 ', 'https://support.microsoft.com/en-us/help/4022619', '2018-10-30', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 '), (12, 5687, 'SP2 CU18', 'https://support.microsoft.com/en-us/help/4500180', '2019-07-29', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 18'), (12, 5632, 'SP2 CU17', 'https://support.microsoft.com/en-us/help/4491540', '2019-04-16', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 17'), @@ -177,6 +184,7 @@ VALUES (12, 2269, 'RTM MS15-058: GDR Security Update ', 'https://support.microsoft.com/en-us/help/3045324', '2015-07-14', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM MS15-058: GDR Security Update '), (12, 2254, 'RTM MS14-044: GDR Security Update', 'https://support.microsoft.com/en-us/help/2977315', '2014-08-12', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM MS14-044: GDR Security Update'), (12, 2000, 'RTM ', '', '2014-04-01', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM '), + (11, 7507, 'SP4 GDR Security Update', 'https://support.microsoft.com/en-us/help/4583465', '2021-01-12', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'Service Pack 4 GDR Security Update for CVE-2021-1636'), (11, 7493, 'SP4 GDR Security Update', 'https://support.microsoft.com/en-us/help/4532098', '2020-02-11', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'Service Pack 4 GDR Security Update for CVE-2020-0618'), (11, 7469, 'SP4 On-Demand Hotfix Update', 'https://support.microsoft.com/en-us/help/4091266', '2018-03-28', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'Service Pack 4 SP4 On-Demand Hotfix Update'), (11, 7462, 'SP4 ADV180002: GDR Security Update', 'https://support.microsoft.com/en-us/help/4057116', '2018-01-12', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'Service Pack 4 ADV180002: GDR Security Update'), diff --git a/sp_AllNightLog.sql b/sp_AllNightLog.sql index f804defc7..00aeafbfc 100644 --- a/sp_AllNightLog.sql +++ b/sp_AllNightLog.sql @@ -30,7 +30,7 @@ SET NOCOUNT ON; BEGIN; -SELECT @Version = '3.9999', @VersionDate = '20201211'; +SELECT @Version = '8.0', @VersionDate = '20210117'; IF(@VersionCheckMode = 1) BEGIN @@ -82,7 +82,7 @@ BEGIN MIT License - Copyright (c) 2020 Brent Ozar Unlimited + Copyright (c) 2021 Brent Ozar Unlimited Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/sp_AllNightLog_Setup.sql b/sp_AllNightLog_Setup.sql index ba5de06eb..fd3231277 100644 --- a/sp_AllNightLog_Setup.sql +++ b/sp_AllNightLog_Setup.sql @@ -36,7 +36,7 @@ SET NOCOUNT ON; BEGIN; -SELECT @Version = '3.9999', @VersionDate = '20201211'; +SELECT @Version = '8.0', @VersionDate = '20210117'; IF(@VersionCheckMode = 1) BEGIN @@ -115,7 +115,7 @@ BEGIN MIT License - Copyright (c) 2020 Brent Ozar Unlimited + Copyright (c) 2021 Brent Ozar Unlimited Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/sp_Blitz.sql b/sp_Blitz.sql index f2d57280e..0bc13b3e8 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -37,7 +37,7 @@ AS SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '7.99999', @VersionDate = '20201211'; + SELECT @Version = '8.0', @VersionDate = '20210117'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -92,9 +92,9 @@ AS tigertoolbox and are provided under the MIT license: https://github.com/Microsoft/tigertoolbox - All other copyrights for sp_Blitz are held by Brent Ozar Unlimited, 2020. + All other copyrights for sp_Blitz are held by Brent Ozar Unlimited, 2021. - Copyright (c) 2020 Brent Ozar Unlimited + Copyright (c) 2021 Brent Ozar Unlimited Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/sp_BlitzBackups.sql b/sp_BlitzBackups.sql index 28648322b..1ba5807bd 100755 --- a/sp_BlitzBackups.sql +++ b/sp_BlitzBackups.sql @@ -23,7 +23,7 @@ AS SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '3.99999', @VersionDate = '20201211'; + SELECT @Version = '8.0', @VersionDate = '20210117'; IF(@VersionCheckMode = 1) BEGIN @@ -70,7 +70,7 @@ AS MIT License - Copyright (c) 2020 Brent Ozar Unlimited + Copyright (c) 2021 Brent Ozar Unlimited Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/sp_BlitzCache.sql b/sp_BlitzCache.sql index ddce00b23..717168898 100644 --- a/sp_BlitzCache.sql +++ b/sp_BlitzCache.sql @@ -278,465 +278,466 @@ BEGIN SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '7.99999', @VersionDate = '20201211'; +SELECT @Version = '8.0', @VersionDate = '20210117'; IF(@VersionCheckMode = 1) BEGIN RETURN; END; + +DECLARE @nl NVARCHAR(2) = NCHAR(13) + NCHAR(10) ; IF @Help = 1 -BEGIN -PRINT ' -sp_BlitzCache from http://FirstResponderKit.org + BEGIN + PRINT ' + sp_BlitzCache from http://FirstResponderKit.org -This script displays your most resource-intensive queries from the plan cache, -and points to ways you can tune these queries to make them faster. + This script displays your most resource-intensive queries from the plan cache, + and points to ways you can tune these queries to make them faster. -To learn more, visit http://FirstResponderKit.org where you can download new -versions for free, watch training videos on how it works, get more info on -the findings, contribute your own code, and more. + To learn more, visit http://FirstResponderKit.org where you can download new + versions for free, watch training videos on how it works, get more info on + the findings, contribute your own code, and more. -Known limitations of this version: - - This query will not run on SQL Server 2005. - - SQL Server 2008 and 2008R2 have a bug in trigger stats, so that output is - excluded by default. - - @IgnoreQueryHashes and @OnlyQueryHashes require a CSV list of hashes - with no spaces between the hash values. + Known limitations of this version: + - This query will not run on SQL Server 2005. + - SQL Server 2008 and 2008R2 have a bug in trigger stats, so that output is + excluded by default. + - @IgnoreQueryHashes and @OnlyQueryHashes require a CSV list of hashes + with no spaces between the hash values. -Unknown limitations of this version: - - May or may not be vulnerable to the wick effect. + Unknown limitations of this version: + - May or may not be vulnerable to the wick effect. -Changes - for the full list of improvements and fixes in this version, see: -https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/ + Changes - for the full list of improvements and fixes in this version, see: + https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/ -MIT License + MIT License -Copyright (c) 2020 Brent Ozar Unlimited + Copyright (c) 2021 Brent Ozar Unlimited -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -'; + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + '; -DECLARE @nl NVARCHAR(2) = NCHAR(13) + NCHAR(10) ; ---IF @Help = 1 /* We're still under a @Help = 1 BEGIN from above */ ---BEGIN - SELECT N'@Help' AS [Parameter Name] , - N'BIT' AS [Data Type] , - N'Displays this help message.' AS [Parameter Description] + SELECT N'@Help' AS [Parameter Name] , + N'BIT' AS [Data Type] , + N'Displays this help message.' AS [Parameter Description] - UNION ALL - SELECT N'@Top', - N'INT', - N'The number of records to retrieve and analyze from the plan cache. The following DMVs are used as the plan cache: dm_exec_query_stats, dm_exec_procedure_stats, dm_exec_trigger_stats.' + UNION ALL + SELECT N'@Top', + N'INT', + N'The number of records to retrieve and analyze from the plan cache. The following DMVs are used as the plan cache: dm_exec_query_stats, dm_exec_procedure_stats, dm_exec_trigger_stats.' - UNION ALL - SELECT N'@SortOrder', - N'VARCHAR(10)', - N'Data processing and display order. @SortOrder will still be used, even when preparing output for a table or for excel. Possible values are: "CPU", "Reads", "Writes", "Duration", "Executions", "Recent Compilations", "Memory Grant", "Unused Grant", "Spills", "Query Hash". Additionally, the word "Average" or "Avg" can be used to sort on averages rather than total. "Executions per minute" and "Executions / minute" can be used to sort by execution per minute. For the truly lazy, "xpm" can also be used. Note that when you use all or all avg, the only parameters you can use are @Top and @DatabaseName. All others will be ignored.' + UNION ALL + SELECT N'@SortOrder', + N'VARCHAR(10)', + N'Data processing and display order. @SortOrder will still be used, even when preparing output for a table or for excel. Possible values are: "CPU", "Reads", "Writes", "Duration", "Executions", "Recent Compilations", "Memory Grant", "Unused Grant", "Spills", "Query Hash". Additionally, the word "Average" or "Avg" can be used to sort on averages rather than total. "Executions per minute" and "Executions / minute" can be used to sort by execution per minute. For the truly lazy, "xpm" can also be used. Note that when you use all or all avg, the only parameters you can use are @Top and @DatabaseName. All others will be ignored.' - UNION ALL - SELECT N'@UseTriggersAnyway', - N'BIT', - N'On SQL Server 2008R2 and earlier, trigger execution count is incorrect - trigger execution count is incremented once per execution of a SQL agent job. If you still want to see relative execution count of triggers, then you can force sp_BlitzCache to include this information.' + UNION ALL + SELECT N'@UseTriggersAnyway', + N'BIT', + N'On SQL Server 2008R2 and earlier, trigger execution count is incorrect - trigger execution count is incremented once per execution of a SQL agent job. If you still want to see relative execution count of triggers, then you can force sp_BlitzCache to include this information.' - UNION ALL - SELECT N'@ExportToExcel', - N'BIT', - N'Prepare output for exporting to Excel. Newlines and additional whitespace are removed from query text and the execution plan is not displayed.' + UNION ALL + SELECT N'@ExportToExcel', + N'BIT', + N'Prepare output for exporting to Excel. Newlines and additional whitespace are removed from query text and the execution plan is not displayed.' - UNION ALL - SELECT N'@ExpertMode', - N'TINYINT', - N'Default 0. When set to 1, results include more columns. When 2, mode is optimized for Opserver, the open source dashboard.' + UNION ALL + SELECT N'@ExpertMode', + N'TINYINT', + N'Default 0. When set to 1, results include more columns. When 2, mode is optimized for Opserver, the open source dashboard.' - UNION ALL - SELECT N'@OutputDatabaseName', - N'NVARCHAR(128)', - N'The output database. If this does not exist SQL Server will divide by zero and everything will fall apart.' + UNION ALL + SELECT N'@OutputDatabaseName', + N'NVARCHAR(128)', + N'The output database. If this does not exist SQL Server will divide by zero and everything will fall apart.' - UNION ALL - SELECT N'@OutputSchemaName', - N'NVARCHAR(258)', - N'The output schema. If this does not exist SQL Server will divide by zero and everything will fall apart.' + UNION ALL + SELECT N'@OutputSchemaName', + N'NVARCHAR(258)', + N'The output schema. If this does not exist SQL Server will divide by zero and everything will fall apart.' - UNION ALL - SELECT N'@OutputTableName', - N'NVARCHAR(258)', - N'The output table. If this does not exist, it will be created for you.' + UNION ALL + SELECT N'@OutputTableName', + N'NVARCHAR(258)', + N'The output table. If this does not exist, it will be created for you.' - UNION ALL - SELECT N'@DurationFilter', - N'DECIMAL(38,4)', - N'Excludes queries with an average duration (in seconds) less than @DurationFilter.' + UNION ALL + SELECT N'@DurationFilter', + N'DECIMAL(38,4)', + N'Excludes queries with an average duration (in seconds) less than @DurationFilter.' - UNION ALL - SELECT N'@HideSummary', - N'BIT', - N'Hides the findings summary result set.' + UNION ALL + SELECT N'@HideSummary', + N'BIT', + N'Hides the findings summary result set.' - UNION ALL - SELECT N'@IgnoreSystemDBs', - N'BIT', - N'Ignores plans found in the system databases (master, model, msdb, tempdb, and resourcedb)' + UNION ALL + SELECT N'@IgnoreSystemDBs', + N'BIT', + N'Ignores plans found in the system databases (master, model, msdb, tempdb, and resourcedb)' - UNION ALL - SELECT N'@OnlyQueryHashes', - N'VARCHAR(MAX)', - N'A list of query hashes to query. All other query hashes will be ignored. Stored procedures and triggers will be ignored.' + UNION ALL + SELECT N'@OnlyQueryHashes', + N'VARCHAR(MAX)', + N'A list of query hashes to query. All other query hashes will be ignored. Stored procedures and triggers will be ignored.' - UNION ALL - SELECT N'@IgnoreQueryHashes', - N'VARCHAR(MAX)', - N'A list of query hashes to ignore.' + UNION ALL + SELECT N'@IgnoreQueryHashes', + N'VARCHAR(MAX)', + N'A list of query hashes to ignore.' - UNION ALL - SELECT N'@OnlySqlHandles', - N'VARCHAR(MAX)', - N'One or more sql_handles to use for filtering results.' + UNION ALL + SELECT N'@OnlySqlHandles', + N'VARCHAR(MAX)', + N'One or more sql_handles to use for filtering results.' UNION ALL - SELECT N'@IgnoreSqlHandles', - N'VARCHAR(MAX)', - N'One or more sql_handles to ignore.' + SELECT N'@IgnoreSqlHandles', + N'VARCHAR(MAX)', + N'One or more sql_handles to ignore.' - UNION ALL - SELECT N'@DatabaseName', - N'NVARCHAR(128)', - N'A database name which is used for filtering results.' + UNION ALL + SELECT N'@DatabaseName', + N'NVARCHAR(128)', + N'A database name which is used for filtering results.' - UNION ALL - SELECT N'@StoredProcName', - N'NVARCHAR(128)', - N'Name of stored procedure you want to find plans for.' + UNION ALL + SELECT N'@StoredProcName', + N'NVARCHAR(128)', + N'Name of stored procedure you want to find plans for.' - UNION ALL - SELECT N'@SlowlySearchPlansFor', - N'NVARCHAR(4000)', - N'String to search for in plan text. % wildcards allowed.' + UNION ALL + SELECT N'@SlowlySearchPlansFor', + N'NVARCHAR(4000)', + N'String to search for in plan text. % wildcards allowed.' - UNION ALL - SELECT N'@BringThePain', - N'BIT', - N'When using @SortOrder = ''all'' and @Top > 10, we require you to set @BringThePain = 1 so you understand that sp_BlitzCache will take a while to run.' + UNION ALL + SELECT N'@BringThePain', + N'BIT', + N'When using @SortOrder = ''all'' and @Top > 10, we require you to set @BringThePain = 1 so you understand that sp_BlitzCache will take a while to run.' - UNION ALL - SELECT N'@QueryFilter', - N'VARCHAR(10)', - N'Filter out stored procedures or statements. The default value is ''ALL''. Allowed values are ''procedures'', ''statements'', ''functions'', or ''all'' (any variation in capitalization is acceptable).' + UNION ALL + SELECT N'@QueryFilter', + N'VARCHAR(10)', + N'Filter out stored procedures or statements. The default value is ''ALL''. Allowed values are ''procedures'', ''statements'', ''functions'', or ''all'' (any variation in capitalization is acceptable).' - UNION ALL - SELECT N'@Reanalyze', - N'BIT', - N'The default is 0. When set to 0, sp_BlitzCache will re-evalute the plan cache. Set this to 1 to reanalyze existing results' + UNION ALL + SELECT N'@Reanalyze', + N'BIT', + N'The default is 0. When set to 0, sp_BlitzCache will re-evalute the plan cache. Set this to 1 to reanalyze existing results' - UNION ALL - SELECT N'@MinimumExecutionCount', - N'INT', - N'Queries with fewer than this number of executions will be omitted from results.' + UNION ALL + SELECT N'@MinimumExecutionCount', + N'INT', + N'Queries with fewer than this number of executions will be omitted from results.' UNION ALL - SELECT N'@Debug', - N'BIT', - N'Setting this to 1 will print dynamic SQL and select data from all tables used.' + SELECT N'@Debug', + N'BIT', + N'Setting this to 1 will print dynamic SQL and select data from all tables used.' - UNION ALL - SELECT N'@MinutesBack', - N'INT', - N'How many minutes back to begin plan cache analysis. If you put in a positive number, we''ll flip it to negtive.'; + UNION ALL + SELECT N'@MinutesBack', + N'INT', + N'How many minutes back to begin plan cache analysis. If you put in a positive number, we''ll flip it to negtive.'; - /* Column definitions */ - SELECT N'# Executions' AS [Column Name], - N'BIGINT' AS [Data Type], - N'The number of executions of this particular query. This is computed across statements, procedures, and triggers and aggregated by the SQL handle.' AS [Column Description] + /* Column definitions */ + SELECT N'# Executions' AS [Column Name], + N'BIGINT' AS [Data Type], + N'The number of executions of this particular query. This is computed across statements, procedures, and triggers and aggregated by the SQL handle.' AS [Column Description] - UNION ALL - SELECT N'Executions / Minute', - N'MONEY', - N'Number of executions per minute - calculated for the life of the current plan. Plan life is the last execution time minus the plan creation time.' + UNION ALL + SELECT N'Executions / Minute', + N'MONEY', + N'Number of executions per minute - calculated for the life of the current plan. Plan life is the last execution time minus the plan creation time.' - UNION ALL - SELECT N'Execution Weight', - N'MONEY', - N'An arbitrary metric of total "execution-ness". A weight of 2 is "one more" than a weight of 1.' + UNION ALL + SELECT N'Execution Weight', + N'MONEY', + N'An arbitrary metric of total "execution-ness". A weight of 2 is "one more" than a weight of 1.' - UNION ALL - SELECT N'Database', - N'sysname', - N'The name of the database where the plan was encountered. If the database name cannot be determined for some reason, a value of NA will be substituted. A value of 32767 indicates the plan comes from ResourceDB.' + UNION ALL + SELECT N'Database', + N'sysname', + N'The name of the database where the plan was encountered. If the database name cannot be determined for some reason, a value of NA will be substituted. A value of 32767 indicates the plan comes from ResourceDB.' - UNION ALL - SELECT N'Total CPU', - N'BIGINT', - N'Total CPU time, reported in milliseconds, that was consumed by all executions of this query since the last compilation.' + UNION ALL + SELECT N'Total CPU', + N'BIGINT', + N'Total CPU time, reported in milliseconds, that was consumed by all executions of this query since the last compilation.' - UNION ALL - SELECT N'Avg CPU', - N'BIGINT', - N'Average CPU time, reported in milliseconds, consumed by each execution of this query since the last compilation.' + UNION ALL + SELECT N'Avg CPU', + N'BIGINT', + N'Average CPU time, reported in milliseconds, consumed by each execution of this query since the last compilation.' - UNION ALL - SELECT N'CPU Weight', - N'MONEY', - N'An arbitrary metric of total "CPU-ness". A weight of 2 is "one more" than a weight of 1.' + UNION ALL + SELECT N'CPU Weight', + N'MONEY', + N'An arbitrary metric of total "CPU-ness". A weight of 2 is "one more" than a weight of 1.' - UNION ALL - SELECT N'Total Duration', - N'BIGINT', - N'Total elapsed time, reported in milliseconds, consumed by all executions of this query since last compilation.' + UNION ALL + SELECT N'Total Duration', + N'BIGINT', + N'Total elapsed time, reported in milliseconds, consumed by all executions of this query since last compilation.' - UNION ALL - SELECT N'Avg Duration', - N'BIGINT', - N'Average elapsed time, reported in milliseconds, consumed by each execution of this query since the last compilation.' + UNION ALL + SELECT N'Avg Duration', + N'BIGINT', + N'Average elapsed time, reported in milliseconds, consumed by each execution of this query since the last compilation.' - UNION ALL - SELECT N'Duration Weight', - N'MONEY', - N'An arbitrary metric of total "Duration-ness". A weight of 2 is "one more" than a weight of 1.' + UNION ALL + SELECT N'Duration Weight', + N'MONEY', + N'An arbitrary metric of total "Duration-ness". A weight of 2 is "one more" than a weight of 1.' - UNION ALL - SELECT N'Total Reads', - N'BIGINT', - N'Total logical reads performed by this query since last compilation.' + UNION ALL + SELECT N'Total Reads', + N'BIGINT', + N'Total logical reads performed by this query since last compilation.' - UNION ALL - SELECT N'Average Reads', - N'BIGINT', - N'Average logical reads performed by each execution of this query since the last compilation.' + UNION ALL + SELECT N'Average Reads', + N'BIGINT', + N'Average logical reads performed by each execution of this query since the last compilation.' - UNION ALL - SELECT N'Read Weight', - N'MONEY', - N'An arbitrary metric of "Read-ness". A weight of 2 is "one more" than a weight of 1.' + UNION ALL + SELECT N'Read Weight', + N'MONEY', + N'An arbitrary metric of "Read-ness". A weight of 2 is "one more" than a weight of 1.' - UNION ALL - SELECT N'Total Writes', - N'BIGINT', - N'Total logical writes performed by this query since last compilation.' + UNION ALL + SELECT N'Total Writes', + N'BIGINT', + N'Total logical writes performed by this query since last compilation.' - UNION ALL - SELECT N'Average Writes', - N'BIGINT', - N'Average logical writes performed by each execution this query since last compilation.' + UNION ALL + SELECT N'Average Writes', + N'BIGINT', + N'Average logical writes performed by each execution this query since last compilation.' - UNION ALL - SELECT N'Write Weight', - N'MONEY', - N'An arbitrary metric of "Write-ness". A weight of 2 is "one more" than a weight of 1.' + UNION ALL + SELECT N'Write Weight', + N'MONEY', + N'An arbitrary metric of "Write-ness". A weight of 2 is "one more" than a weight of 1.' - UNION ALL - SELECT N'Query Type', - N'NVARCHAR(258)', - N'The type of query being examined. This can be "Procedure", "Statement", or "Trigger".' + UNION ALL + SELECT N'Query Type', + N'NVARCHAR(258)', + N'The type of query being examined. This can be "Procedure", "Statement", or "Trigger".' - UNION ALL - SELECT N'Query Text', - N'NVARCHAR(4000)', - N'The text of the query. This may be truncated by either SQL Server or by sp_BlitzCache(tm) for display purposes.' + UNION ALL + SELECT N'Query Text', + N'NVARCHAR(4000)', + N'The text of the query. This may be truncated by either SQL Server or by sp_BlitzCache(tm) for display purposes.' - UNION ALL - SELECT N'% Executions (Type)', - N'MONEY', - N'Percent of executions relative to the type of query - e.g. 17.2% of all stored procedure executions.' + UNION ALL + SELECT N'% Executions (Type)', + N'MONEY', + N'Percent of executions relative to the type of query - e.g. 17.2% of all stored procedure executions.' - UNION ALL - SELECT N'% CPU (Type)', - N'MONEY', - N'Percent of CPU time consumed by this query for a given type of query - e.g. 22% of CPU of all stored procedures executed.' + UNION ALL + SELECT N'% CPU (Type)', + N'MONEY', + N'Percent of CPU time consumed by this query for a given type of query - e.g. 22% of CPU of all stored procedures executed.' - UNION ALL - SELECT N'% Duration (Type)', - N'MONEY', - N'Percent of elapsed time consumed by this query for a given type of query - e.g. 12% of all statements executed.' + UNION ALL + SELECT N'% Duration (Type)', + N'MONEY', + N'Percent of elapsed time consumed by this query for a given type of query - e.g. 12% of all statements executed.' - UNION ALL - SELECT N'% Reads (Type)', - N'MONEY', - N'Percent of reads consumed by this query for a given type of query - e.g. 34.2% of all stored procedures executed.' + UNION ALL + SELECT N'% Reads (Type)', + N'MONEY', + N'Percent of reads consumed by this query for a given type of query - e.g. 34.2% of all stored procedures executed.' - UNION ALL - SELECT N'% Writes (Type)', - N'MONEY', - N'Percent of writes performed by this query for a given type of query - e.g. 43.2% of all statements executed.' + UNION ALL + SELECT N'% Writes (Type)', + N'MONEY', + N'Percent of writes performed by this query for a given type of query - e.g. 43.2% of all statements executed.' - UNION ALL - SELECT N'Total Rows', - N'BIGINT', - N'Total number of rows returned for all executions of this query. This only applies to query level stats, not stored procedures or triggers.' + UNION ALL + SELECT N'Total Rows', + N'BIGINT', + N'Total number of rows returned for all executions of this query. This only applies to query level stats, not stored procedures or triggers.' - UNION ALL - SELECT N'Average Rows', - N'MONEY', - N'Average number of rows returned by each execution of the query.' + UNION ALL + SELECT N'Average Rows', + N'MONEY', + N'Average number of rows returned by each execution of the query.' - UNION ALL - SELECT N'Min Rows', - N'BIGINT', - N'The minimum number of rows returned by any execution of this query.' + UNION ALL + SELECT N'Min Rows', + N'BIGINT', + N'The minimum number of rows returned by any execution of this query.' - UNION ALL - SELECT N'Max Rows', - N'BIGINT', - N'The maximum number of rows returned by any execution of this query.' + UNION ALL + SELECT N'Max Rows', + N'BIGINT', + N'The maximum number of rows returned by any execution of this query.' - UNION ALL - SELECT N'MinGrantKB', - N'BIGINT', - N'The minimum memory grant the query received in kb.' + UNION ALL + SELECT N'MinGrantKB', + N'BIGINT', + N'The minimum memory grant the query received in kb.' - UNION ALL - SELECT N'MaxGrantKB', - N'BIGINT', - N'The maximum memory grant the query received in kb.' + UNION ALL + SELECT N'MaxGrantKB', + N'BIGINT', + N'The maximum memory grant the query received in kb.' - UNION ALL - SELECT N'MinUsedGrantKB', - N'BIGINT', - N'The minimum used memory grant the query received in kb.' + UNION ALL + SELECT N'MinUsedGrantKB', + N'BIGINT', + N'The minimum used memory grant the query received in kb.' - UNION ALL - SELECT N'MaxUsedGrantKB', - N'BIGINT', - N'The maximum used memory grant the query received in kb.' + UNION ALL + SELECT N'MaxUsedGrantKB', + N'BIGINT', + N'The maximum used memory grant the query received in kb.' - UNION ALL - SELECT N'MinSpills', - N'BIGINT', - N'The minimum amount this query has spilled to tempdb in 8k pages.' + UNION ALL + SELECT N'MinSpills', + N'BIGINT', + N'The minimum amount this query has spilled to tempdb in 8k pages.' - UNION ALL - SELECT N'MaxSpills', - N'BIGINT', - N'The maximum amount this query has spilled to tempdb in 8k pages.' + UNION ALL + SELECT N'MaxSpills', + N'BIGINT', + N'The maximum amount this query has spilled to tempdb in 8k pages.' - UNION ALL - SELECT N'TotalSpills', - N'BIGINT', - N'The total amount this query has spilled to tempdb in 8k pages.' + UNION ALL + SELECT N'TotalSpills', + N'BIGINT', + N'The total amount this query has spilled to tempdb in 8k pages.' - UNION ALL - SELECT N'AvgSpills', - N'BIGINT', - N'The average amount this query has spilled to tempdb in 8k pages.' + UNION ALL + SELECT N'AvgSpills', + N'BIGINT', + N'The average amount this query has spilled to tempdb in 8k pages.' - UNION ALL - SELECT N'PercentMemoryGrantUsed', - N'MONEY', - N'Result of dividing the maximum grant used by the minimum granted.' + UNION ALL + SELECT N'PercentMemoryGrantUsed', + N'MONEY', + N'Result of dividing the maximum grant used by the minimum granted.' - UNION ALL - SELECT N'AvgMaxMemoryGrant', - N'MONEY', - N'The average maximum memory grant for a query.' + UNION ALL + SELECT N'AvgMaxMemoryGrant', + N'MONEY', + N'The average maximum memory grant for a query.' - UNION ALL - SELECT N'# Plans', - N'INT', - N'The total number of execution plans found that match a given query.' + UNION ALL + SELECT N'# Plans', + N'INT', + N'The total number of execution plans found that match a given query.' - UNION ALL - SELECT N'# Distinct Plans', - N'INT', - N'The number of distinct execution plans that match a given query. ' - + NCHAR(13) + NCHAR(10) - + N'This may be caused by running the same query across multiple databases or because of a lack of proper parameterization in the database.' + UNION ALL + SELECT N'# Distinct Plans', + N'INT', + N'The number of distinct execution plans that match a given query. ' + + NCHAR(13) + NCHAR(10) + + N'This may be caused by running the same query across multiple databases or because of a lack of proper parameterization in the database.' - UNION ALL - SELECT N'Created At', - N'DATETIME', - N'Time that the execution plan was last compiled.' + UNION ALL + SELECT N'Created At', + N'DATETIME', + N'Time that the execution plan was last compiled.' - UNION ALL - SELECT N'Last Execution', - N'DATETIME', - N'The last time that this query was executed.' + UNION ALL + SELECT N'Last Execution', + N'DATETIME', + N'The last time that this query was executed.' - UNION ALL - SELECT N'Query Plan', - N'XML', - N'The query plan. Click to display a graphical plan or, if you need to patch SSMS, a pile of XML.' + UNION ALL + SELECT N'Query Plan', + N'XML', + N'The query plan. Click to display a graphical plan or, if you need to patch SSMS, a pile of XML.' - UNION ALL - SELECT N'Plan Handle', - N'VARBINARY(64)', - N'An arbitrary identifier referring to the compiled plan this query is a part of.' + UNION ALL + SELECT N'Plan Handle', + N'VARBINARY(64)', + N'An arbitrary identifier referring to the compiled plan this query is a part of.' - UNION ALL - SELECT N'SQL Handle', - N'VARBINARY(64)', - N'An arbitrary identifier referring to a batch or stored procedure that this query is a part of.' + UNION ALL + SELECT N'SQL Handle', + N'VARBINARY(64)', + N'An arbitrary identifier referring to a batch or stored procedure that this query is a part of.' - UNION ALL - SELECT N'Query Hash', - N'BINARY(8)', - N'A hash of the query. Queries with the same query hash have similar logic but only differ by literal values or database.' + UNION ALL + SELECT N'Query Hash', + N'BINARY(8)', + N'A hash of the query. Queries with the same query hash have similar logic but only differ by literal values or database.' - UNION ALL - SELECT N'Warnings', - N'VARCHAR(MAX)', - N'A list of individual warnings generated by this query.' ; + UNION ALL + SELECT N'Warnings', + N'VARCHAR(MAX)', + N'A list of individual warnings generated by this query.' ; - /* Configuration table description */ - SELECT N'Frequent Execution Threshold' AS [Configuration Parameter] , - N'100' AS [Default Value] , - N'Executions / Minute' AS [Unit of Measure] , - N'Executions / Minute before a "Frequent Execution Threshold" warning is triggered.' AS [Description] + /* Configuration table description */ + SELECT N'Frequent Execution Threshold' AS [Configuration Parameter] , + N'100' AS [Default Value] , + N'Executions / Minute' AS [Unit of Measure] , + N'Executions / Minute before a "Frequent Execution Threshold" warning is triggered.' AS [Description] - UNION ALL - SELECT N'Parameter Sniffing Variance Percent' , - N'30' , - N'Percent' , - N'Variance required between min/max values and average values before a "Parameter Sniffing" warning is triggered. Applies to worker time and returned rows.' + UNION ALL + SELECT N'Parameter Sniffing Variance Percent' , + N'30' , + N'Percent' , + N'Variance required between min/max values and average values before a "Parameter Sniffing" warning is triggered. Applies to worker time and returned rows.' - UNION ALL - SELECT N'Parameter Sniffing IO Threshold' , - N'100,000' , - N'Logical reads' , - N'Minimum number of average logical reads before parameter sniffing checks are evaluated.' + UNION ALL + SELECT N'Parameter Sniffing IO Threshold' , + N'100,000' , + N'Logical reads' , + N'Minimum number of average logical reads before parameter sniffing checks are evaluated.' - UNION ALL - SELECT N'Cost Threshold for Parallelism Warning' AS [Configuration Parameter] , - N'10' , - N'Percent' , - N'Trigger a "Nearly Parallel" warning when a query''s cost is within X percent of the cost threshold for parallelism.' + UNION ALL + SELECT N'Cost Threshold for Parallelism Warning' AS [Configuration Parameter] , + N'10' , + N'Percent' , + N'Trigger a "Nearly Parallel" warning when a query''s cost is within X percent of the cost threshold for parallelism.' + + UNION ALL + SELECT N'Long Running Query Warning' AS [Configuration Parameter] , + N'300' , + N'Seconds' , + N'Triggers a "Long Running Query Warning" when average duration, max CPU time, or max clock time is higher than this number.' + + UNION ALL + SELECT N'Unused Memory Grant Warning' AS [Configuration Parameter] , + N'10' , + N'Percent' , + N'Triggers an "Unused Memory Grant Warning" when a query uses >= X percent of its memory grant.'; + RETURN; + END; /* IF @Help = 1 */ - UNION ALL - SELECT N'Long Running Query Warning' AS [Configuration Parameter] , - N'300' , - N'Seconds' , - N'Triggers a "Long Running Query Warning" when average duration, max CPU time, or max clock time is higher than this number.' - UNION ALL - SELECT N'Unused Memory Grant Warning' AS [Configuration Parameter] , - N'10' , - N'Percent' , - N'Triggers an "Unused Memory Grant Warning" when a query uses >= X percent of its memory grant.'; - RETURN; -END; /*Validate version*/ IF ( diff --git a/sp_BlitzFirst.sql b/sp_BlitzFirst.sql index 4e65871e8..be4a54878 100644 --- a/sp_BlitzFirst.sql +++ b/sp_BlitzFirst.sql @@ -45,7 +45,7 @@ BEGIN SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '7.99999', @VersionDate = '20201211'; +SELECT @Version = '8.0', @VersionDate = '20210117'; IF(@VersionCheckMode = 1) BEGIN @@ -85,7 +85,7 @@ https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/ MIT License -Copyright (c) 2020 Brent Ozar Unlimited +Copyright (c) 2021 Brent Ozar Unlimited Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/sp_BlitzInMemoryOLTP.sql b/sp_BlitzInMemoryOLTP.sql index 2cbd36c67..694698dab 100644 --- a/sp_BlitzInMemoryOLTP.sql +++ b/sp_BlitzInMemoryOLTP.sql @@ -82,7 +82,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI */ AS DECLARE @ScriptVersion VARCHAR(30); -SELECT @ScriptVersion = '1.8', @VersionDate = '20201211'; +SELECT @ScriptVersion = '1.8', @VersionDate = '20210117'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index 38338fbfd..f14914ccd 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -45,7 +45,7 @@ AS SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '7.99999', @VersionDate = '20201211'; +SELECT @Version = '8.0', @VersionDate = '20210117'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -82,7 +82,7 @@ Unknown limitations of this version: MIT License -Copyright (c) 2020 Brent Ozar Unlimited +Copyright (c) 2021 Brent Ozar Unlimited Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index 05042f6aa..91ff6a052 100644 --- a/sp_BlitzLock.sql +++ b/sp_BlitzLock.sql @@ -32,7 +32,7 @@ BEGIN SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '2.99999', @VersionDate = '20201211'; +SELECT @Version = '8.0', @VersionDate = '20210117'; IF(@VersionCheckMode = 1) @@ -96,7 +96,7 @@ END; MIT License - Copyright (c) 2020 Brent Ozar Unlimited + Copyright (c) 2021 Brent Ozar Unlimited Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/sp_BlitzQueryStore.sql b/sp_BlitzQueryStore.sql index 1955ea6ed..aa601bece 100644 --- a/sp_BlitzQueryStore.sql +++ b/sp_BlitzQueryStore.sql @@ -56,7 +56,7 @@ BEGIN /*First BEGIN*/ SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '3.99999', @VersionDate = '20201211'; +SELECT @Version = '8.0', @VersionDate = '20210117'; IF(@VersionCheckMode = 1) BEGIN RETURN; @@ -142,7 +142,7 @@ IF @Help = 1 MIT License - Copyright (c) 2020 Brent Ozar Unlimited + Copyright (c) 2021 Brent Ozar Unlimited Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/sp_BlitzWho.sql b/sp_BlitzWho.sql index b4093c3ec..d3dcd66c2 100644 --- a/sp_BlitzWho.sql +++ b/sp_BlitzWho.sql @@ -29,7 +29,7 @@ BEGIN SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '7.99999', @VersionDate = '20201211'; + SELECT @Version = '8.0', @VersionDate = '20210117'; IF(@VersionCheckMode = 1) BEGIN @@ -57,7 +57,7 @@ Known limitations of this version: MIT License -Copyright (c) 2020 Brent Ozar Unlimited +Copyright (c) 2021 Brent Ozar Unlimited Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/sp_DatabaseRestore.sql b/sp_DatabaseRestore.sql index 2ccb05930..843c86f83 100755 --- a/sp_DatabaseRestore.sql +++ b/sp_DatabaseRestore.sql @@ -39,7 +39,7 @@ SET NOCOUNT ON; /*Versioning details*/ -SELECT @Version = '7.99999', @VersionDate = '20201211'; +SELECT @Version = '8.0', @VersionDate = '20210117'; IF(@VersionCheckMode = 1) BEGIN @@ -71,7 +71,7 @@ BEGIN MIT License - Copyright (c) 2020 Brent Ozar Unlimited + Copyright (c) 2021 Brent Ozar Unlimited Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/sp_ineachdb.sql b/sp_ineachdb.sql index fae2e30c4..f3d2527ac 100644 --- a/sp_ineachdb.sql +++ b/sp_ineachdb.sql @@ -34,7 +34,7 @@ AS BEGIN SET NOCOUNT ON; - SELECT @Version = '2.99999', @VersionDate = '20201211'; + SELECT @Version = '8.0', @VersionDate = '20210117'; IF(@VersionCheckMode = 1) BEGIN @@ -67,7 +67,7 @@ BEGIN MIT License - Copyright (c) 2019 Brent Ozar Unlimited + Copyright (c) 2021 Brent Ozar Unlimited Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -336,4 +336,4 @@ OPTION (MAXRECURSION 0); CLOSE dbs; DEALLOCATE dbs; END -GO \ No newline at end of file +GO From a515520d16fa6bd64480f069c3a77309ddf79ee8 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Sun, 17 Jan 2021 02:41:03 -0800 Subject: [PATCH 076/662] 2021-01 release With install scripts. --- Install-All-Scripts.sql | 709 ++++++++++++------------ Install-Core-Blitz-No-Query-Store.sql | 709 ++++++++++++------------ Install-Core-Blitz-With-Query-Store.sql | 709 ++++++++++++------------ 3 files changed, 1065 insertions(+), 1062 deletions(-) diff --git a/Install-All-Scripts.sql b/Install-All-Scripts.sql index 1b401f668..fd026050c 100755 --- a/Install-All-Scripts.sql +++ b/Install-All-Scripts.sql @@ -14071,458 +14071,459 @@ IF(@VersionCheckMode = 1) BEGIN RETURN; END; + +DECLARE @nl NVARCHAR(2) = NCHAR(13) + NCHAR(10) ; IF @Help = 1 -BEGIN -PRINT ' -sp_BlitzCache from http://FirstResponderKit.org + BEGIN + PRINT ' + sp_BlitzCache from http://FirstResponderKit.org -This script displays your most resource-intensive queries from the plan cache, -and points to ways you can tune these queries to make them faster. + This script displays your most resource-intensive queries from the plan cache, + and points to ways you can tune these queries to make them faster. -To learn more, visit http://FirstResponderKit.org where you can download new -versions for free, watch training videos on how it works, get more info on -the findings, contribute your own code, and more. + To learn more, visit http://FirstResponderKit.org where you can download new + versions for free, watch training videos on how it works, get more info on + the findings, contribute your own code, and more. -Known limitations of this version: - - This query will not run on SQL Server 2005. - - SQL Server 2008 and 2008R2 have a bug in trigger stats, so that output is - excluded by default. - - @IgnoreQueryHashes and @OnlyQueryHashes require a CSV list of hashes - with no spaces between the hash values. + Known limitations of this version: + - This query will not run on SQL Server 2005. + - SQL Server 2008 and 2008R2 have a bug in trigger stats, so that output is + excluded by default. + - @IgnoreQueryHashes and @OnlyQueryHashes require a CSV list of hashes + with no spaces between the hash values. -Unknown limitations of this version: - - May or may not be vulnerable to the wick effect. + Unknown limitations of this version: + - May or may not be vulnerable to the wick effect. -Changes - for the full list of improvements and fixes in this version, see: -https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/ + Changes - for the full list of improvements and fixes in this version, see: + https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/ -MIT License + MIT License -Copyright (c) 2021 Brent Ozar Unlimited + Copyright (c) 2021 Brent Ozar Unlimited -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -'; + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + '; -DECLARE @nl NVARCHAR(2) = NCHAR(13) + NCHAR(10) ; ---IF @Help = 1 /* We're still under a @Help = 1 BEGIN from above */ ---BEGIN - SELECT N'@Help' AS [Parameter Name] , - N'BIT' AS [Data Type] , - N'Displays this help message.' AS [Parameter Description] + SELECT N'@Help' AS [Parameter Name] , + N'BIT' AS [Data Type] , + N'Displays this help message.' AS [Parameter Description] - UNION ALL - SELECT N'@Top', - N'INT', - N'The number of records to retrieve and analyze from the plan cache. The following DMVs are used as the plan cache: dm_exec_query_stats, dm_exec_procedure_stats, dm_exec_trigger_stats.' + UNION ALL + SELECT N'@Top', + N'INT', + N'The number of records to retrieve and analyze from the plan cache. The following DMVs are used as the plan cache: dm_exec_query_stats, dm_exec_procedure_stats, dm_exec_trigger_stats.' - UNION ALL - SELECT N'@SortOrder', - N'VARCHAR(10)', - N'Data processing and display order. @SortOrder will still be used, even when preparing output for a table or for excel. Possible values are: "CPU", "Reads", "Writes", "Duration", "Executions", "Recent Compilations", "Memory Grant", "Unused Grant", "Spills", "Query Hash". Additionally, the word "Average" or "Avg" can be used to sort on averages rather than total. "Executions per minute" and "Executions / minute" can be used to sort by execution per minute. For the truly lazy, "xpm" can also be used. Note that when you use all or all avg, the only parameters you can use are @Top and @DatabaseName. All others will be ignored.' + UNION ALL + SELECT N'@SortOrder', + N'VARCHAR(10)', + N'Data processing and display order. @SortOrder will still be used, even when preparing output for a table or for excel. Possible values are: "CPU", "Reads", "Writes", "Duration", "Executions", "Recent Compilations", "Memory Grant", "Unused Grant", "Spills", "Query Hash". Additionally, the word "Average" or "Avg" can be used to sort on averages rather than total. "Executions per minute" and "Executions / minute" can be used to sort by execution per minute. For the truly lazy, "xpm" can also be used. Note that when you use all or all avg, the only parameters you can use are @Top and @DatabaseName. All others will be ignored.' - UNION ALL - SELECT N'@UseTriggersAnyway', - N'BIT', - N'On SQL Server 2008R2 and earlier, trigger execution count is incorrect - trigger execution count is incremented once per execution of a SQL agent job. If you still want to see relative execution count of triggers, then you can force sp_BlitzCache to include this information.' + UNION ALL + SELECT N'@UseTriggersAnyway', + N'BIT', + N'On SQL Server 2008R2 and earlier, trigger execution count is incorrect - trigger execution count is incremented once per execution of a SQL agent job. If you still want to see relative execution count of triggers, then you can force sp_BlitzCache to include this information.' - UNION ALL - SELECT N'@ExportToExcel', - N'BIT', - N'Prepare output for exporting to Excel. Newlines and additional whitespace are removed from query text and the execution plan is not displayed.' + UNION ALL + SELECT N'@ExportToExcel', + N'BIT', + N'Prepare output for exporting to Excel. Newlines and additional whitespace are removed from query text and the execution plan is not displayed.' - UNION ALL - SELECT N'@ExpertMode', - N'TINYINT', - N'Default 0. When set to 1, results include more columns. When 2, mode is optimized for Opserver, the open source dashboard.' + UNION ALL + SELECT N'@ExpertMode', + N'TINYINT', + N'Default 0. When set to 1, results include more columns. When 2, mode is optimized for Opserver, the open source dashboard.' - UNION ALL - SELECT N'@OutputDatabaseName', - N'NVARCHAR(128)', - N'The output database. If this does not exist SQL Server will divide by zero and everything will fall apart.' + UNION ALL + SELECT N'@OutputDatabaseName', + N'NVARCHAR(128)', + N'The output database. If this does not exist SQL Server will divide by zero and everything will fall apart.' - UNION ALL - SELECT N'@OutputSchemaName', - N'NVARCHAR(258)', - N'The output schema. If this does not exist SQL Server will divide by zero and everything will fall apart.' + UNION ALL + SELECT N'@OutputSchemaName', + N'NVARCHAR(258)', + N'The output schema. If this does not exist SQL Server will divide by zero and everything will fall apart.' - UNION ALL - SELECT N'@OutputTableName', - N'NVARCHAR(258)', - N'The output table. If this does not exist, it will be created for you.' + UNION ALL + SELECT N'@OutputTableName', + N'NVARCHAR(258)', + N'The output table. If this does not exist, it will be created for you.' - UNION ALL - SELECT N'@DurationFilter', - N'DECIMAL(38,4)', - N'Excludes queries with an average duration (in seconds) less than @DurationFilter.' + UNION ALL + SELECT N'@DurationFilter', + N'DECIMAL(38,4)', + N'Excludes queries with an average duration (in seconds) less than @DurationFilter.' - UNION ALL - SELECT N'@HideSummary', - N'BIT', - N'Hides the findings summary result set.' + UNION ALL + SELECT N'@HideSummary', + N'BIT', + N'Hides the findings summary result set.' - UNION ALL - SELECT N'@IgnoreSystemDBs', - N'BIT', - N'Ignores plans found in the system databases (master, model, msdb, tempdb, and resourcedb)' + UNION ALL + SELECT N'@IgnoreSystemDBs', + N'BIT', + N'Ignores plans found in the system databases (master, model, msdb, tempdb, and resourcedb)' - UNION ALL - SELECT N'@OnlyQueryHashes', - N'VARCHAR(MAX)', - N'A list of query hashes to query. All other query hashes will be ignored. Stored procedures and triggers will be ignored.' + UNION ALL + SELECT N'@OnlyQueryHashes', + N'VARCHAR(MAX)', + N'A list of query hashes to query. All other query hashes will be ignored. Stored procedures and triggers will be ignored.' - UNION ALL - SELECT N'@IgnoreQueryHashes', - N'VARCHAR(MAX)', - N'A list of query hashes to ignore.' + UNION ALL + SELECT N'@IgnoreQueryHashes', + N'VARCHAR(MAX)', + N'A list of query hashes to ignore.' - UNION ALL - SELECT N'@OnlySqlHandles', - N'VARCHAR(MAX)', - N'One or more sql_handles to use for filtering results.' + UNION ALL + SELECT N'@OnlySqlHandles', + N'VARCHAR(MAX)', + N'One or more sql_handles to use for filtering results.' UNION ALL - SELECT N'@IgnoreSqlHandles', - N'VARCHAR(MAX)', - N'One or more sql_handles to ignore.' + SELECT N'@IgnoreSqlHandles', + N'VARCHAR(MAX)', + N'One or more sql_handles to ignore.' - UNION ALL - SELECT N'@DatabaseName', - N'NVARCHAR(128)', - N'A database name which is used for filtering results.' + UNION ALL + SELECT N'@DatabaseName', + N'NVARCHAR(128)', + N'A database name which is used for filtering results.' - UNION ALL - SELECT N'@StoredProcName', - N'NVARCHAR(128)', - N'Name of stored procedure you want to find plans for.' + UNION ALL + SELECT N'@StoredProcName', + N'NVARCHAR(128)', + N'Name of stored procedure you want to find plans for.' - UNION ALL - SELECT N'@SlowlySearchPlansFor', - N'NVARCHAR(4000)', - N'String to search for in plan text. % wildcards allowed.' + UNION ALL + SELECT N'@SlowlySearchPlansFor', + N'NVARCHAR(4000)', + N'String to search for in plan text. % wildcards allowed.' - UNION ALL - SELECT N'@BringThePain', - N'BIT', - N'When using @SortOrder = ''all'' and @Top > 10, we require you to set @BringThePain = 1 so you understand that sp_BlitzCache will take a while to run.' + UNION ALL + SELECT N'@BringThePain', + N'BIT', + N'When using @SortOrder = ''all'' and @Top > 10, we require you to set @BringThePain = 1 so you understand that sp_BlitzCache will take a while to run.' - UNION ALL - SELECT N'@QueryFilter', - N'VARCHAR(10)', - N'Filter out stored procedures or statements. The default value is ''ALL''. Allowed values are ''procedures'', ''statements'', ''functions'', or ''all'' (any variation in capitalization is acceptable).' + UNION ALL + SELECT N'@QueryFilter', + N'VARCHAR(10)', + N'Filter out stored procedures or statements. The default value is ''ALL''. Allowed values are ''procedures'', ''statements'', ''functions'', or ''all'' (any variation in capitalization is acceptable).' - UNION ALL - SELECT N'@Reanalyze', - N'BIT', - N'The default is 0. When set to 0, sp_BlitzCache will re-evalute the plan cache. Set this to 1 to reanalyze existing results' + UNION ALL + SELECT N'@Reanalyze', + N'BIT', + N'The default is 0. When set to 0, sp_BlitzCache will re-evalute the plan cache. Set this to 1 to reanalyze existing results' - UNION ALL - SELECT N'@MinimumExecutionCount', - N'INT', - N'Queries with fewer than this number of executions will be omitted from results.' + UNION ALL + SELECT N'@MinimumExecutionCount', + N'INT', + N'Queries with fewer than this number of executions will be omitted from results.' UNION ALL - SELECT N'@Debug', - N'BIT', - N'Setting this to 1 will print dynamic SQL and select data from all tables used.' + SELECT N'@Debug', + N'BIT', + N'Setting this to 1 will print dynamic SQL and select data from all tables used.' - UNION ALL - SELECT N'@MinutesBack', - N'INT', - N'How many minutes back to begin plan cache analysis. If you put in a positive number, we''ll flip it to negtive.'; + UNION ALL + SELECT N'@MinutesBack', + N'INT', + N'How many minutes back to begin plan cache analysis. If you put in a positive number, we''ll flip it to negtive.'; - /* Column definitions */ - SELECT N'# Executions' AS [Column Name], - N'BIGINT' AS [Data Type], - N'The number of executions of this particular query. This is computed across statements, procedures, and triggers and aggregated by the SQL handle.' AS [Column Description] + /* Column definitions */ + SELECT N'# Executions' AS [Column Name], + N'BIGINT' AS [Data Type], + N'The number of executions of this particular query. This is computed across statements, procedures, and triggers and aggregated by the SQL handle.' AS [Column Description] - UNION ALL - SELECT N'Executions / Minute', - N'MONEY', - N'Number of executions per minute - calculated for the life of the current plan. Plan life is the last execution time minus the plan creation time.' + UNION ALL + SELECT N'Executions / Minute', + N'MONEY', + N'Number of executions per minute - calculated for the life of the current plan. Plan life is the last execution time minus the plan creation time.' - UNION ALL - SELECT N'Execution Weight', - N'MONEY', - N'An arbitrary metric of total "execution-ness". A weight of 2 is "one more" than a weight of 1.' + UNION ALL + SELECT N'Execution Weight', + N'MONEY', + N'An arbitrary metric of total "execution-ness". A weight of 2 is "one more" than a weight of 1.' - UNION ALL - SELECT N'Database', - N'sysname', - N'The name of the database where the plan was encountered. If the database name cannot be determined for some reason, a value of NA will be substituted. A value of 32767 indicates the plan comes from ResourceDB.' + UNION ALL + SELECT N'Database', + N'sysname', + N'The name of the database where the plan was encountered. If the database name cannot be determined for some reason, a value of NA will be substituted. A value of 32767 indicates the plan comes from ResourceDB.' - UNION ALL - SELECT N'Total CPU', - N'BIGINT', - N'Total CPU time, reported in milliseconds, that was consumed by all executions of this query since the last compilation.' + UNION ALL + SELECT N'Total CPU', + N'BIGINT', + N'Total CPU time, reported in milliseconds, that was consumed by all executions of this query since the last compilation.' - UNION ALL - SELECT N'Avg CPU', - N'BIGINT', - N'Average CPU time, reported in milliseconds, consumed by each execution of this query since the last compilation.' + UNION ALL + SELECT N'Avg CPU', + N'BIGINT', + N'Average CPU time, reported in milliseconds, consumed by each execution of this query since the last compilation.' - UNION ALL - SELECT N'CPU Weight', - N'MONEY', - N'An arbitrary metric of total "CPU-ness". A weight of 2 is "one more" than a weight of 1.' + UNION ALL + SELECT N'CPU Weight', + N'MONEY', + N'An arbitrary metric of total "CPU-ness". A weight of 2 is "one more" than a weight of 1.' - UNION ALL - SELECT N'Total Duration', - N'BIGINT', - N'Total elapsed time, reported in milliseconds, consumed by all executions of this query since last compilation.' + UNION ALL + SELECT N'Total Duration', + N'BIGINT', + N'Total elapsed time, reported in milliseconds, consumed by all executions of this query since last compilation.' - UNION ALL - SELECT N'Avg Duration', - N'BIGINT', - N'Average elapsed time, reported in milliseconds, consumed by each execution of this query since the last compilation.' + UNION ALL + SELECT N'Avg Duration', + N'BIGINT', + N'Average elapsed time, reported in milliseconds, consumed by each execution of this query since the last compilation.' - UNION ALL - SELECT N'Duration Weight', - N'MONEY', - N'An arbitrary metric of total "Duration-ness". A weight of 2 is "one more" than a weight of 1.' + UNION ALL + SELECT N'Duration Weight', + N'MONEY', + N'An arbitrary metric of total "Duration-ness". A weight of 2 is "one more" than a weight of 1.' - UNION ALL - SELECT N'Total Reads', - N'BIGINT', - N'Total logical reads performed by this query since last compilation.' + UNION ALL + SELECT N'Total Reads', + N'BIGINT', + N'Total logical reads performed by this query since last compilation.' - UNION ALL - SELECT N'Average Reads', - N'BIGINT', - N'Average logical reads performed by each execution of this query since the last compilation.' + UNION ALL + SELECT N'Average Reads', + N'BIGINT', + N'Average logical reads performed by each execution of this query since the last compilation.' - UNION ALL - SELECT N'Read Weight', - N'MONEY', - N'An arbitrary metric of "Read-ness". A weight of 2 is "one more" than a weight of 1.' + UNION ALL + SELECT N'Read Weight', + N'MONEY', + N'An arbitrary metric of "Read-ness". A weight of 2 is "one more" than a weight of 1.' - UNION ALL - SELECT N'Total Writes', - N'BIGINT', - N'Total logical writes performed by this query since last compilation.' + UNION ALL + SELECT N'Total Writes', + N'BIGINT', + N'Total logical writes performed by this query since last compilation.' - UNION ALL - SELECT N'Average Writes', - N'BIGINT', - N'Average logical writes performed by each execution this query since last compilation.' + UNION ALL + SELECT N'Average Writes', + N'BIGINT', + N'Average logical writes performed by each execution this query since last compilation.' - UNION ALL - SELECT N'Write Weight', - N'MONEY', - N'An arbitrary metric of "Write-ness". A weight of 2 is "one more" than a weight of 1.' + UNION ALL + SELECT N'Write Weight', + N'MONEY', + N'An arbitrary metric of "Write-ness". A weight of 2 is "one more" than a weight of 1.' - UNION ALL - SELECT N'Query Type', - N'NVARCHAR(258)', - N'The type of query being examined. This can be "Procedure", "Statement", or "Trigger".' + UNION ALL + SELECT N'Query Type', + N'NVARCHAR(258)', + N'The type of query being examined. This can be "Procedure", "Statement", or "Trigger".' - UNION ALL - SELECT N'Query Text', - N'NVARCHAR(4000)', - N'The text of the query. This may be truncated by either SQL Server or by sp_BlitzCache(tm) for display purposes.' + UNION ALL + SELECT N'Query Text', + N'NVARCHAR(4000)', + N'The text of the query. This may be truncated by either SQL Server or by sp_BlitzCache(tm) for display purposes.' - UNION ALL - SELECT N'% Executions (Type)', - N'MONEY', - N'Percent of executions relative to the type of query - e.g. 17.2% of all stored procedure executions.' + UNION ALL + SELECT N'% Executions (Type)', + N'MONEY', + N'Percent of executions relative to the type of query - e.g. 17.2% of all stored procedure executions.' - UNION ALL - SELECT N'% CPU (Type)', - N'MONEY', - N'Percent of CPU time consumed by this query for a given type of query - e.g. 22% of CPU of all stored procedures executed.' + UNION ALL + SELECT N'% CPU (Type)', + N'MONEY', + N'Percent of CPU time consumed by this query for a given type of query - e.g. 22% of CPU of all stored procedures executed.' - UNION ALL - SELECT N'% Duration (Type)', - N'MONEY', - N'Percent of elapsed time consumed by this query for a given type of query - e.g. 12% of all statements executed.' + UNION ALL + SELECT N'% Duration (Type)', + N'MONEY', + N'Percent of elapsed time consumed by this query for a given type of query - e.g. 12% of all statements executed.' - UNION ALL - SELECT N'% Reads (Type)', - N'MONEY', - N'Percent of reads consumed by this query for a given type of query - e.g. 34.2% of all stored procedures executed.' + UNION ALL + SELECT N'% Reads (Type)', + N'MONEY', + N'Percent of reads consumed by this query for a given type of query - e.g. 34.2% of all stored procedures executed.' - UNION ALL - SELECT N'% Writes (Type)', - N'MONEY', - N'Percent of writes performed by this query for a given type of query - e.g. 43.2% of all statements executed.' + UNION ALL + SELECT N'% Writes (Type)', + N'MONEY', + N'Percent of writes performed by this query for a given type of query - e.g. 43.2% of all statements executed.' - UNION ALL - SELECT N'Total Rows', - N'BIGINT', - N'Total number of rows returned for all executions of this query. This only applies to query level stats, not stored procedures or triggers.' + UNION ALL + SELECT N'Total Rows', + N'BIGINT', + N'Total number of rows returned for all executions of this query. This only applies to query level stats, not stored procedures or triggers.' - UNION ALL - SELECT N'Average Rows', - N'MONEY', - N'Average number of rows returned by each execution of the query.' + UNION ALL + SELECT N'Average Rows', + N'MONEY', + N'Average number of rows returned by each execution of the query.' - UNION ALL - SELECT N'Min Rows', - N'BIGINT', - N'The minimum number of rows returned by any execution of this query.' + UNION ALL + SELECT N'Min Rows', + N'BIGINT', + N'The minimum number of rows returned by any execution of this query.' - UNION ALL - SELECT N'Max Rows', - N'BIGINT', - N'The maximum number of rows returned by any execution of this query.' + UNION ALL + SELECT N'Max Rows', + N'BIGINT', + N'The maximum number of rows returned by any execution of this query.' - UNION ALL - SELECT N'MinGrantKB', - N'BIGINT', - N'The minimum memory grant the query received in kb.' + UNION ALL + SELECT N'MinGrantKB', + N'BIGINT', + N'The minimum memory grant the query received in kb.' - UNION ALL - SELECT N'MaxGrantKB', - N'BIGINT', - N'The maximum memory grant the query received in kb.' + UNION ALL + SELECT N'MaxGrantKB', + N'BIGINT', + N'The maximum memory grant the query received in kb.' - UNION ALL - SELECT N'MinUsedGrantKB', - N'BIGINT', - N'The minimum used memory grant the query received in kb.' + UNION ALL + SELECT N'MinUsedGrantKB', + N'BIGINT', + N'The minimum used memory grant the query received in kb.' - UNION ALL - SELECT N'MaxUsedGrantKB', - N'BIGINT', - N'The maximum used memory grant the query received in kb.' + UNION ALL + SELECT N'MaxUsedGrantKB', + N'BIGINT', + N'The maximum used memory grant the query received in kb.' - UNION ALL - SELECT N'MinSpills', - N'BIGINT', - N'The minimum amount this query has spilled to tempdb in 8k pages.' + UNION ALL + SELECT N'MinSpills', + N'BIGINT', + N'The minimum amount this query has spilled to tempdb in 8k pages.' - UNION ALL - SELECT N'MaxSpills', - N'BIGINT', - N'The maximum amount this query has spilled to tempdb in 8k pages.' + UNION ALL + SELECT N'MaxSpills', + N'BIGINT', + N'The maximum amount this query has spilled to tempdb in 8k pages.' - UNION ALL - SELECT N'TotalSpills', - N'BIGINT', - N'The total amount this query has spilled to tempdb in 8k pages.' + UNION ALL + SELECT N'TotalSpills', + N'BIGINT', + N'The total amount this query has spilled to tempdb in 8k pages.' - UNION ALL - SELECT N'AvgSpills', - N'BIGINT', - N'The average amount this query has spilled to tempdb in 8k pages.' + UNION ALL + SELECT N'AvgSpills', + N'BIGINT', + N'The average amount this query has spilled to tempdb in 8k pages.' - UNION ALL - SELECT N'PercentMemoryGrantUsed', - N'MONEY', - N'Result of dividing the maximum grant used by the minimum granted.' + UNION ALL + SELECT N'PercentMemoryGrantUsed', + N'MONEY', + N'Result of dividing the maximum grant used by the minimum granted.' - UNION ALL - SELECT N'AvgMaxMemoryGrant', - N'MONEY', - N'The average maximum memory grant for a query.' + UNION ALL + SELECT N'AvgMaxMemoryGrant', + N'MONEY', + N'The average maximum memory grant for a query.' - UNION ALL - SELECT N'# Plans', - N'INT', - N'The total number of execution plans found that match a given query.' + UNION ALL + SELECT N'# Plans', + N'INT', + N'The total number of execution plans found that match a given query.' - UNION ALL - SELECT N'# Distinct Plans', - N'INT', - N'The number of distinct execution plans that match a given query. ' - + NCHAR(13) + NCHAR(10) - + N'This may be caused by running the same query across multiple databases or because of a lack of proper parameterization in the database.' + UNION ALL + SELECT N'# Distinct Plans', + N'INT', + N'The number of distinct execution plans that match a given query. ' + + NCHAR(13) + NCHAR(10) + + N'This may be caused by running the same query across multiple databases or because of a lack of proper parameterization in the database.' - UNION ALL - SELECT N'Created At', - N'DATETIME', - N'Time that the execution plan was last compiled.' + UNION ALL + SELECT N'Created At', + N'DATETIME', + N'Time that the execution plan was last compiled.' - UNION ALL - SELECT N'Last Execution', - N'DATETIME', - N'The last time that this query was executed.' + UNION ALL + SELECT N'Last Execution', + N'DATETIME', + N'The last time that this query was executed.' - UNION ALL - SELECT N'Query Plan', - N'XML', - N'The query plan. Click to display a graphical plan or, if you need to patch SSMS, a pile of XML.' + UNION ALL + SELECT N'Query Plan', + N'XML', + N'The query plan. Click to display a graphical plan or, if you need to patch SSMS, a pile of XML.' - UNION ALL - SELECT N'Plan Handle', - N'VARBINARY(64)', - N'An arbitrary identifier referring to the compiled plan this query is a part of.' + UNION ALL + SELECT N'Plan Handle', + N'VARBINARY(64)', + N'An arbitrary identifier referring to the compiled plan this query is a part of.' - UNION ALL - SELECT N'SQL Handle', - N'VARBINARY(64)', - N'An arbitrary identifier referring to a batch or stored procedure that this query is a part of.' + UNION ALL + SELECT N'SQL Handle', + N'VARBINARY(64)', + N'An arbitrary identifier referring to a batch or stored procedure that this query is a part of.' - UNION ALL - SELECT N'Query Hash', - N'BINARY(8)', - N'A hash of the query. Queries with the same query hash have similar logic but only differ by literal values or database.' + UNION ALL + SELECT N'Query Hash', + N'BINARY(8)', + N'A hash of the query. Queries with the same query hash have similar logic but only differ by literal values or database.' - UNION ALL - SELECT N'Warnings', - N'VARCHAR(MAX)', - N'A list of individual warnings generated by this query.' ; + UNION ALL + SELECT N'Warnings', + N'VARCHAR(MAX)', + N'A list of individual warnings generated by this query.' ; - /* Configuration table description */ - SELECT N'Frequent Execution Threshold' AS [Configuration Parameter] , - N'100' AS [Default Value] , - N'Executions / Minute' AS [Unit of Measure] , - N'Executions / Minute before a "Frequent Execution Threshold" warning is triggered.' AS [Description] + /* Configuration table description */ + SELECT N'Frequent Execution Threshold' AS [Configuration Parameter] , + N'100' AS [Default Value] , + N'Executions / Minute' AS [Unit of Measure] , + N'Executions / Minute before a "Frequent Execution Threshold" warning is triggered.' AS [Description] - UNION ALL - SELECT N'Parameter Sniffing Variance Percent' , - N'30' , - N'Percent' , - N'Variance required between min/max values and average values before a "Parameter Sniffing" warning is triggered. Applies to worker time and returned rows.' + UNION ALL + SELECT N'Parameter Sniffing Variance Percent' , + N'30' , + N'Percent' , + N'Variance required between min/max values and average values before a "Parameter Sniffing" warning is triggered. Applies to worker time and returned rows.' - UNION ALL - SELECT N'Parameter Sniffing IO Threshold' , - N'100,000' , - N'Logical reads' , - N'Minimum number of average logical reads before parameter sniffing checks are evaluated.' + UNION ALL + SELECT N'Parameter Sniffing IO Threshold' , + N'100,000' , + N'Logical reads' , + N'Minimum number of average logical reads before parameter sniffing checks are evaluated.' - UNION ALL - SELECT N'Cost Threshold for Parallelism Warning' AS [Configuration Parameter] , - N'10' , - N'Percent' , - N'Trigger a "Nearly Parallel" warning when a query''s cost is within X percent of the cost threshold for parallelism.' + UNION ALL + SELECT N'Cost Threshold for Parallelism Warning' AS [Configuration Parameter] , + N'10' , + N'Percent' , + N'Trigger a "Nearly Parallel" warning when a query''s cost is within X percent of the cost threshold for parallelism.' + + UNION ALL + SELECT N'Long Running Query Warning' AS [Configuration Parameter] , + N'300' , + N'Seconds' , + N'Triggers a "Long Running Query Warning" when average duration, max CPU time, or max clock time is higher than this number.' + + UNION ALL + SELECT N'Unused Memory Grant Warning' AS [Configuration Parameter] , + N'10' , + N'Percent' , + N'Triggers an "Unused Memory Grant Warning" when a query uses >= X percent of its memory grant.'; + RETURN; + END; /* IF @Help = 1 */ - UNION ALL - SELECT N'Long Running Query Warning' AS [Configuration Parameter] , - N'300' , - N'Seconds' , - N'Triggers a "Long Running Query Warning" when average duration, max CPU time, or max clock time is higher than this number.' - UNION ALL - SELECT N'Unused Memory Grant Warning' AS [Configuration Parameter] , - N'10' , - N'Percent' , - N'Triggers an "Unused Memory Grant Warning" when a query uses >= X percent of its memory grant.'; - RETURN; -END; /*Validate version*/ IF ( diff --git a/Install-Core-Blitz-No-Query-Store.sql b/Install-Core-Blitz-No-Query-Store.sql index b2a1a8761..cea7352f5 100755 --- a/Install-Core-Blitz-No-Query-Store.sql +++ b/Install-Core-Blitz-No-Query-Store.sql @@ -11252,458 +11252,459 @@ IF(@VersionCheckMode = 1) BEGIN RETURN; END; + +DECLARE @nl NVARCHAR(2) = NCHAR(13) + NCHAR(10) ; IF @Help = 1 -BEGIN -PRINT ' -sp_BlitzCache from http://FirstResponderKit.org + BEGIN + PRINT ' + sp_BlitzCache from http://FirstResponderKit.org -This script displays your most resource-intensive queries from the plan cache, -and points to ways you can tune these queries to make them faster. + This script displays your most resource-intensive queries from the plan cache, + and points to ways you can tune these queries to make them faster. -To learn more, visit http://FirstResponderKit.org where you can download new -versions for free, watch training videos on how it works, get more info on -the findings, contribute your own code, and more. + To learn more, visit http://FirstResponderKit.org where you can download new + versions for free, watch training videos on how it works, get more info on + the findings, contribute your own code, and more. -Known limitations of this version: - - This query will not run on SQL Server 2005. - - SQL Server 2008 and 2008R2 have a bug in trigger stats, so that output is - excluded by default. - - @IgnoreQueryHashes and @OnlyQueryHashes require a CSV list of hashes - with no spaces between the hash values. + Known limitations of this version: + - This query will not run on SQL Server 2005. + - SQL Server 2008 and 2008R2 have a bug in trigger stats, so that output is + excluded by default. + - @IgnoreQueryHashes and @OnlyQueryHashes require a CSV list of hashes + with no spaces between the hash values. -Unknown limitations of this version: - - May or may not be vulnerable to the wick effect. + Unknown limitations of this version: + - May or may not be vulnerable to the wick effect. -Changes - for the full list of improvements and fixes in this version, see: -https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/ + Changes - for the full list of improvements and fixes in this version, see: + https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/ -MIT License + MIT License -Copyright (c) 2021 Brent Ozar Unlimited + Copyright (c) 2021 Brent Ozar Unlimited -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -'; + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + '; -DECLARE @nl NVARCHAR(2) = NCHAR(13) + NCHAR(10) ; ---IF @Help = 1 /* We're still under a @Help = 1 BEGIN from above */ ---BEGIN - SELECT N'@Help' AS [Parameter Name] , - N'BIT' AS [Data Type] , - N'Displays this help message.' AS [Parameter Description] + SELECT N'@Help' AS [Parameter Name] , + N'BIT' AS [Data Type] , + N'Displays this help message.' AS [Parameter Description] - UNION ALL - SELECT N'@Top', - N'INT', - N'The number of records to retrieve and analyze from the plan cache. The following DMVs are used as the plan cache: dm_exec_query_stats, dm_exec_procedure_stats, dm_exec_trigger_stats.' + UNION ALL + SELECT N'@Top', + N'INT', + N'The number of records to retrieve and analyze from the plan cache. The following DMVs are used as the plan cache: dm_exec_query_stats, dm_exec_procedure_stats, dm_exec_trigger_stats.' - UNION ALL - SELECT N'@SortOrder', - N'VARCHAR(10)', - N'Data processing and display order. @SortOrder will still be used, even when preparing output for a table or for excel. Possible values are: "CPU", "Reads", "Writes", "Duration", "Executions", "Recent Compilations", "Memory Grant", "Unused Grant", "Spills", "Query Hash". Additionally, the word "Average" or "Avg" can be used to sort on averages rather than total. "Executions per minute" and "Executions / minute" can be used to sort by execution per minute. For the truly lazy, "xpm" can also be used. Note that when you use all or all avg, the only parameters you can use are @Top and @DatabaseName. All others will be ignored.' + UNION ALL + SELECT N'@SortOrder', + N'VARCHAR(10)', + N'Data processing and display order. @SortOrder will still be used, even when preparing output for a table or for excel. Possible values are: "CPU", "Reads", "Writes", "Duration", "Executions", "Recent Compilations", "Memory Grant", "Unused Grant", "Spills", "Query Hash". Additionally, the word "Average" or "Avg" can be used to sort on averages rather than total. "Executions per minute" and "Executions / minute" can be used to sort by execution per minute. For the truly lazy, "xpm" can also be used. Note that when you use all or all avg, the only parameters you can use are @Top and @DatabaseName. All others will be ignored.' - UNION ALL - SELECT N'@UseTriggersAnyway', - N'BIT', - N'On SQL Server 2008R2 and earlier, trigger execution count is incorrect - trigger execution count is incremented once per execution of a SQL agent job. If you still want to see relative execution count of triggers, then you can force sp_BlitzCache to include this information.' + UNION ALL + SELECT N'@UseTriggersAnyway', + N'BIT', + N'On SQL Server 2008R2 and earlier, trigger execution count is incorrect - trigger execution count is incremented once per execution of a SQL agent job. If you still want to see relative execution count of triggers, then you can force sp_BlitzCache to include this information.' - UNION ALL - SELECT N'@ExportToExcel', - N'BIT', - N'Prepare output for exporting to Excel. Newlines and additional whitespace are removed from query text and the execution plan is not displayed.' + UNION ALL + SELECT N'@ExportToExcel', + N'BIT', + N'Prepare output for exporting to Excel. Newlines and additional whitespace are removed from query text and the execution plan is not displayed.' - UNION ALL - SELECT N'@ExpertMode', - N'TINYINT', - N'Default 0. When set to 1, results include more columns. When 2, mode is optimized for Opserver, the open source dashboard.' + UNION ALL + SELECT N'@ExpertMode', + N'TINYINT', + N'Default 0. When set to 1, results include more columns. When 2, mode is optimized for Opserver, the open source dashboard.' - UNION ALL - SELECT N'@OutputDatabaseName', - N'NVARCHAR(128)', - N'The output database. If this does not exist SQL Server will divide by zero and everything will fall apart.' + UNION ALL + SELECT N'@OutputDatabaseName', + N'NVARCHAR(128)', + N'The output database. If this does not exist SQL Server will divide by zero and everything will fall apart.' - UNION ALL - SELECT N'@OutputSchemaName', - N'NVARCHAR(258)', - N'The output schema. If this does not exist SQL Server will divide by zero and everything will fall apart.' + UNION ALL + SELECT N'@OutputSchemaName', + N'NVARCHAR(258)', + N'The output schema. If this does not exist SQL Server will divide by zero and everything will fall apart.' - UNION ALL - SELECT N'@OutputTableName', - N'NVARCHAR(258)', - N'The output table. If this does not exist, it will be created for you.' + UNION ALL + SELECT N'@OutputTableName', + N'NVARCHAR(258)', + N'The output table. If this does not exist, it will be created for you.' - UNION ALL - SELECT N'@DurationFilter', - N'DECIMAL(38,4)', - N'Excludes queries with an average duration (in seconds) less than @DurationFilter.' + UNION ALL + SELECT N'@DurationFilter', + N'DECIMAL(38,4)', + N'Excludes queries with an average duration (in seconds) less than @DurationFilter.' - UNION ALL - SELECT N'@HideSummary', - N'BIT', - N'Hides the findings summary result set.' + UNION ALL + SELECT N'@HideSummary', + N'BIT', + N'Hides the findings summary result set.' - UNION ALL - SELECT N'@IgnoreSystemDBs', - N'BIT', - N'Ignores plans found in the system databases (master, model, msdb, tempdb, and resourcedb)' + UNION ALL + SELECT N'@IgnoreSystemDBs', + N'BIT', + N'Ignores plans found in the system databases (master, model, msdb, tempdb, and resourcedb)' - UNION ALL - SELECT N'@OnlyQueryHashes', - N'VARCHAR(MAX)', - N'A list of query hashes to query. All other query hashes will be ignored. Stored procedures and triggers will be ignored.' + UNION ALL + SELECT N'@OnlyQueryHashes', + N'VARCHAR(MAX)', + N'A list of query hashes to query. All other query hashes will be ignored. Stored procedures and triggers will be ignored.' - UNION ALL - SELECT N'@IgnoreQueryHashes', - N'VARCHAR(MAX)', - N'A list of query hashes to ignore.' + UNION ALL + SELECT N'@IgnoreQueryHashes', + N'VARCHAR(MAX)', + N'A list of query hashes to ignore.' - UNION ALL - SELECT N'@OnlySqlHandles', - N'VARCHAR(MAX)', - N'One or more sql_handles to use for filtering results.' + UNION ALL + SELECT N'@OnlySqlHandles', + N'VARCHAR(MAX)', + N'One or more sql_handles to use for filtering results.' UNION ALL - SELECT N'@IgnoreSqlHandles', - N'VARCHAR(MAX)', - N'One or more sql_handles to ignore.' + SELECT N'@IgnoreSqlHandles', + N'VARCHAR(MAX)', + N'One or more sql_handles to ignore.' - UNION ALL - SELECT N'@DatabaseName', - N'NVARCHAR(128)', - N'A database name which is used for filtering results.' + UNION ALL + SELECT N'@DatabaseName', + N'NVARCHAR(128)', + N'A database name which is used for filtering results.' - UNION ALL - SELECT N'@StoredProcName', - N'NVARCHAR(128)', - N'Name of stored procedure you want to find plans for.' + UNION ALL + SELECT N'@StoredProcName', + N'NVARCHAR(128)', + N'Name of stored procedure you want to find plans for.' - UNION ALL - SELECT N'@SlowlySearchPlansFor', - N'NVARCHAR(4000)', - N'String to search for in plan text. % wildcards allowed.' + UNION ALL + SELECT N'@SlowlySearchPlansFor', + N'NVARCHAR(4000)', + N'String to search for in plan text. % wildcards allowed.' - UNION ALL - SELECT N'@BringThePain', - N'BIT', - N'When using @SortOrder = ''all'' and @Top > 10, we require you to set @BringThePain = 1 so you understand that sp_BlitzCache will take a while to run.' + UNION ALL + SELECT N'@BringThePain', + N'BIT', + N'When using @SortOrder = ''all'' and @Top > 10, we require you to set @BringThePain = 1 so you understand that sp_BlitzCache will take a while to run.' - UNION ALL - SELECT N'@QueryFilter', - N'VARCHAR(10)', - N'Filter out stored procedures or statements. The default value is ''ALL''. Allowed values are ''procedures'', ''statements'', ''functions'', or ''all'' (any variation in capitalization is acceptable).' + UNION ALL + SELECT N'@QueryFilter', + N'VARCHAR(10)', + N'Filter out stored procedures or statements. The default value is ''ALL''. Allowed values are ''procedures'', ''statements'', ''functions'', or ''all'' (any variation in capitalization is acceptable).' - UNION ALL - SELECT N'@Reanalyze', - N'BIT', - N'The default is 0. When set to 0, sp_BlitzCache will re-evalute the plan cache. Set this to 1 to reanalyze existing results' + UNION ALL + SELECT N'@Reanalyze', + N'BIT', + N'The default is 0. When set to 0, sp_BlitzCache will re-evalute the plan cache. Set this to 1 to reanalyze existing results' - UNION ALL - SELECT N'@MinimumExecutionCount', - N'INT', - N'Queries with fewer than this number of executions will be omitted from results.' + UNION ALL + SELECT N'@MinimumExecutionCount', + N'INT', + N'Queries with fewer than this number of executions will be omitted from results.' UNION ALL - SELECT N'@Debug', - N'BIT', - N'Setting this to 1 will print dynamic SQL and select data from all tables used.' + SELECT N'@Debug', + N'BIT', + N'Setting this to 1 will print dynamic SQL and select data from all tables used.' - UNION ALL - SELECT N'@MinutesBack', - N'INT', - N'How many minutes back to begin plan cache analysis. If you put in a positive number, we''ll flip it to negtive.'; + UNION ALL + SELECT N'@MinutesBack', + N'INT', + N'How many minutes back to begin plan cache analysis. If you put in a positive number, we''ll flip it to negtive.'; - /* Column definitions */ - SELECT N'# Executions' AS [Column Name], - N'BIGINT' AS [Data Type], - N'The number of executions of this particular query. This is computed across statements, procedures, and triggers and aggregated by the SQL handle.' AS [Column Description] + /* Column definitions */ + SELECT N'# Executions' AS [Column Name], + N'BIGINT' AS [Data Type], + N'The number of executions of this particular query. This is computed across statements, procedures, and triggers and aggregated by the SQL handle.' AS [Column Description] - UNION ALL - SELECT N'Executions / Minute', - N'MONEY', - N'Number of executions per minute - calculated for the life of the current plan. Plan life is the last execution time minus the plan creation time.' + UNION ALL + SELECT N'Executions / Minute', + N'MONEY', + N'Number of executions per minute - calculated for the life of the current plan. Plan life is the last execution time minus the plan creation time.' - UNION ALL - SELECT N'Execution Weight', - N'MONEY', - N'An arbitrary metric of total "execution-ness". A weight of 2 is "one more" than a weight of 1.' + UNION ALL + SELECT N'Execution Weight', + N'MONEY', + N'An arbitrary metric of total "execution-ness". A weight of 2 is "one more" than a weight of 1.' - UNION ALL - SELECT N'Database', - N'sysname', - N'The name of the database where the plan was encountered. If the database name cannot be determined for some reason, a value of NA will be substituted. A value of 32767 indicates the plan comes from ResourceDB.' + UNION ALL + SELECT N'Database', + N'sysname', + N'The name of the database where the plan was encountered. If the database name cannot be determined for some reason, a value of NA will be substituted. A value of 32767 indicates the plan comes from ResourceDB.' - UNION ALL - SELECT N'Total CPU', - N'BIGINT', - N'Total CPU time, reported in milliseconds, that was consumed by all executions of this query since the last compilation.' + UNION ALL + SELECT N'Total CPU', + N'BIGINT', + N'Total CPU time, reported in milliseconds, that was consumed by all executions of this query since the last compilation.' - UNION ALL - SELECT N'Avg CPU', - N'BIGINT', - N'Average CPU time, reported in milliseconds, consumed by each execution of this query since the last compilation.' + UNION ALL + SELECT N'Avg CPU', + N'BIGINT', + N'Average CPU time, reported in milliseconds, consumed by each execution of this query since the last compilation.' - UNION ALL - SELECT N'CPU Weight', - N'MONEY', - N'An arbitrary metric of total "CPU-ness". A weight of 2 is "one more" than a weight of 1.' + UNION ALL + SELECT N'CPU Weight', + N'MONEY', + N'An arbitrary metric of total "CPU-ness". A weight of 2 is "one more" than a weight of 1.' - UNION ALL - SELECT N'Total Duration', - N'BIGINT', - N'Total elapsed time, reported in milliseconds, consumed by all executions of this query since last compilation.' + UNION ALL + SELECT N'Total Duration', + N'BIGINT', + N'Total elapsed time, reported in milliseconds, consumed by all executions of this query since last compilation.' - UNION ALL - SELECT N'Avg Duration', - N'BIGINT', - N'Average elapsed time, reported in milliseconds, consumed by each execution of this query since the last compilation.' + UNION ALL + SELECT N'Avg Duration', + N'BIGINT', + N'Average elapsed time, reported in milliseconds, consumed by each execution of this query since the last compilation.' - UNION ALL - SELECT N'Duration Weight', - N'MONEY', - N'An arbitrary metric of total "Duration-ness". A weight of 2 is "one more" than a weight of 1.' + UNION ALL + SELECT N'Duration Weight', + N'MONEY', + N'An arbitrary metric of total "Duration-ness". A weight of 2 is "one more" than a weight of 1.' - UNION ALL - SELECT N'Total Reads', - N'BIGINT', - N'Total logical reads performed by this query since last compilation.' + UNION ALL + SELECT N'Total Reads', + N'BIGINT', + N'Total logical reads performed by this query since last compilation.' - UNION ALL - SELECT N'Average Reads', - N'BIGINT', - N'Average logical reads performed by each execution of this query since the last compilation.' + UNION ALL + SELECT N'Average Reads', + N'BIGINT', + N'Average logical reads performed by each execution of this query since the last compilation.' - UNION ALL - SELECT N'Read Weight', - N'MONEY', - N'An arbitrary metric of "Read-ness". A weight of 2 is "one more" than a weight of 1.' + UNION ALL + SELECT N'Read Weight', + N'MONEY', + N'An arbitrary metric of "Read-ness". A weight of 2 is "one more" than a weight of 1.' - UNION ALL - SELECT N'Total Writes', - N'BIGINT', - N'Total logical writes performed by this query since last compilation.' + UNION ALL + SELECT N'Total Writes', + N'BIGINT', + N'Total logical writes performed by this query since last compilation.' - UNION ALL - SELECT N'Average Writes', - N'BIGINT', - N'Average logical writes performed by each execution this query since last compilation.' + UNION ALL + SELECT N'Average Writes', + N'BIGINT', + N'Average logical writes performed by each execution this query since last compilation.' - UNION ALL - SELECT N'Write Weight', - N'MONEY', - N'An arbitrary metric of "Write-ness". A weight of 2 is "one more" than a weight of 1.' + UNION ALL + SELECT N'Write Weight', + N'MONEY', + N'An arbitrary metric of "Write-ness". A weight of 2 is "one more" than a weight of 1.' - UNION ALL - SELECT N'Query Type', - N'NVARCHAR(258)', - N'The type of query being examined. This can be "Procedure", "Statement", or "Trigger".' + UNION ALL + SELECT N'Query Type', + N'NVARCHAR(258)', + N'The type of query being examined. This can be "Procedure", "Statement", or "Trigger".' - UNION ALL - SELECT N'Query Text', - N'NVARCHAR(4000)', - N'The text of the query. This may be truncated by either SQL Server or by sp_BlitzCache(tm) for display purposes.' + UNION ALL + SELECT N'Query Text', + N'NVARCHAR(4000)', + N'The text of the query. This may be truncated by either SQL Server or by sp_BlitzCache(tm) for display purposes.' - UNION ALL - SELECT N'% Executions (Type)', - N'MONEY', - N'Percent of executions relative to the type of query - e.g. 17.2% of all stored procedure executions.' + UNION ALL + SELECT N'% Executions (Type)', + N'MONEY', + N'Percent of executions relative to the type of query - e.g. 17.2% of all stored procedure executions.' - UNION ALL - SELECT N'% CPU (Type)', - N'MONEY', - N'Percent of CPU time consumed by this query for a given type of query - e.g. 22% of CPU of all stored procedures executed.' + UNION ALL + SELECT N'% CPU (Type)', + N'MONEY', + N'Percent of CPU time consumed by this query for a given type of query - e.g. 22% of CPU of all stored procedures executed.' - UNION ALL - SELECT N'% Duration (Type)', - N'MONEY', - N'Percent of elapsed time consumed by this query for a given type of query - e.g. 12% of all statements executed.' + UNION ALL + SELECT N'% Duration (Type)', + N'MONEY', + N'Percent of elapsed time consumed by this query for a given type of query - e.g. 12% of all statements executed.' - UNION ALL - SELECT N'% Reads (Type)', - N'MONEY', - N'Percent of reads consumed by this query for a given type of query - e.g. 34.2% of all stored procedures executed.' + UNION ALL + SELECT N'% Reads (Type)', + N'MONEY', + N'Percent of reads consumed by this query for a given type of query - e.g. 34.2% of all stored procedures executed.' - UNION ALL - SELECT N'% Writes (Type)', - N'MONEY', - N'Percent of writes performed by this query for a given type of query - e.g. 43.2% of all statements executed.' + UNION ALL + SELECT N'% Writes (Type)', + N'MONEY', + N'Percent of writes performed by this query for a given type of query - e.g. 43.2% of all statements executed.' - UNION ALL - SELECT N'Total Rows', - N'BIGINT', - N'Total number of rows returned for all executions of this query. This only applies to query level stats, not stored procedures or triggers.' + UNION ALL + SELECT N'Total Rows', + N'BIGINT', + N'Total number of rows returned for all executions of this query. This only applies to query level stats, not stored procedures or triggers.' - UNION ALL - SELECT N'Average Rows', - N'MONEY', - N'Average number of rows returned by each execution of the query.' + UNION ALL + SELECT N'Average Rows', + N'MONEY', + N'Average number of rows returned by each execution of the query.' - UNION ALL - SELECT N'Min Rows', - N'BIGINT', - N'The minimum number of rows returned by any execution of this query.' + UNION ALL + SELECT N'Min Rows', + N'BIGINT', + N'The minimum number of rows returned by any execution of this query.' - UNION ALL - SELECT N'Max Rows', - N'BIGINT', - N'The maximum number of rows returned by any execution of this query.' + UNION ALL + SELECT N'Max Rows', + N'BIGINT', + N'The maximum number of rows returned by any execution of this query.' - UNION ALL - SELECT N'MinGrantKB', - N'BIGINT', - N'The minimum memory grant the query received in kb.' + UNION ALL + SELECT N'MinGrantKB', + N'BIGINT', + N'The minimum memory grant the query received in kb.' - UNION ALL - SELECT N'MaxGrantKB', - N'BIGINT', - N'The maximum memory grant the query received in kb.' + UNION ALL + SELECT N'MaxGrantKB', + N'BIGINT', + N'The maximum memory grant the query received in kb.' - UNION ALL - SELECT N'MinUsedGrantKB', - N'BIGINT', - N'The minimum used memory grant the query received in kb.' + UNION ALL + SELECT N'MinUsedGrantKB', + N'BIGINT', + N'The minimum used memory grant the query received in kb.' - UNION ALL - SELECT N'MaxUsedGrantKB', - N'BIGINT', - N'The maximum used memory grant the query received in kb.' + UNION ALL + SELECT N'MaxUsedGrantKB', + N'BIGINT', + N'The maximum used memory grant the query received in kb.' - UNION ALL - SELECT N'MinSpills', - N'BIGINT', - N'The minimum amount this query has spilled to tempdb in 8k pages.' + UNION ALL + SELECT N'MinSpills', + N'BIGINT', + N'The minimum amount this query has spilled to tempdb in 8k pages.' - UNION ALL - SELECT N'MaxSpills', - N'BIGINT', - N'The maximum amount this query has spilled to tempdb in 8k pages.' + UNION ALL + SELECT N'MaxSpills', + N'BIGINT', + N'The maximum amount this query has spilled to tempdb in 8k pages.' - UNION ALL - SELECT N'TotalSpills', - N'BIGINT', - N'The total amount this query has spilled to tempdb in 8k pages.' + UNION ALL + SELECT N'TotalSpills', + N'BIGINT', + N'The total amount this query has spilled to tempdb in 8k pages.' - UNION ALL - SELECT N'AvgSpills', - N'BIGINT', - N'The average amount this query has spilled to tempdb in 8k pages.' + UNION ALL + SELECT N'AvgSpills', + N'BIGINT', + N'The average amount this query has spilled to tempdb in 8k pages.' - UNION ALL - SELECT N'PercentMemoryGrantUsed', - N'MONEY', - N'Result of dividing the maximum grant used by the minimum granted.' + UNION ALL + SELECT N'PercentMemoryGrantUsed', + N'MONEY', + N'Result of dividing the maximum grant used by the minimum granted.' - UNION ALL - SELECT N'AvgMaxMemoryGrant', - N'MONEY', - N'The average maximum memory grant for a query.' + UNION ALL + SELECT N'AvgMaxMemoryGrant', + N'MONEY', + N'The average maximum memory grant for a query.' - UNION ALL - SELECT N'# Plans', - N'INT', - N'The total number of execution plans found that match a given query.' + UNION ALL + SELECT N'# Plans', + N'INT', + N'The total number of execution plans found that match a given query.' - UNION ALL - SELECT N'# Distinct Plans', - N'INT', - N'The number of distinct execution plans that match a given query. ' - + NCHAR(13) + NCHAR(10) - + N'This may be caused by running the same query across multiple databases or because of a lack of proper parameterization in the database.' + UNION ALL + SELECT N'# Distinct Plans', + N'INT', + N'The number of distinct execution plans that match a given query. ' + + NCHAR(13) + NCHAR(10) + + N'This may be caused by running the same query across multiple databases or because of a lack of proper parameterization in the database.' - UNION ALL - SELECT N'Created At', - N'DATETIME', - N'Time that the execution plan was last compiled.' + UNION ALL + SELECT N'Created At', + N'DATETIME', + N'Time that the execution plan was last compiled.' - UNION ALL - SELECT N'Last Execution', - N'DATETIME', - N'The last time that this query was executed.' + UNION ALL + SELECT N'Last Execution', + N'DATETIME', + N'The last time that this query was executed.' - UNION ALL - SELECT N'Query Plan', - N'XML', - N'The query plan. Click to display a graphical plan or, if you need to patch SSMS, a pile of XML.' + UNION ALL + SELECT N'Query Plan', + N'XML', + N'The query plan. Click to display a graphical plan or, if you need to patch SSMS, a pile of XML.' - UNION ALL - SELECT N'Plan Handle', - N'VARBINARY(64)', - N'An arbitrary identifier referring to the compiled plan this query is a part of.' + UNION ALL + SELECT N'Plan Handle', + N'VARBINARY(64)', + N'An arbitrary identifier referring to the compiled plan this query is a part of.' - UNION ALL - SELECT N'SQL Handle', - N'VARBINARY(64)', - N'An arbitrary identifier referring to a batch or stored procedure that this query is a part of.' + UNION ALL + SELECT N'SQL Handle', + N'VARBINARY(64)', + N'An arbitrary identifier referring to a batch or stored procedure that this query is a part of.' - UNION ALL - SELECT N'Query Hash', - N'BINARY(8)', - N'A hash of the query. Queries with the same query hash have similar logic but only differ by literal values or database.' + UNION ALL + SELECT N'Query Hash', + N'BINARY(8)', + N'A hash of the query. Queries with the same query hash have similar logic but only differ by literal values or database.' - UNION ALL - SELECT N'Warnings', - N'VARCHAR(MAX)', - N'A list of individual warnings generated by this query.' ; + UNION ALL + SELECT N'Warnings', + N'VARCHAR(MAX)', + N'A list of individual warnings generated by this query.' ; - /* Configuration table description */ - SELECT N'Frequent Execution Threshold' AS [Configuration Parameter] , - N'100' AS [Default Value] , - N'Executions / Minute' AS [Unit of Measure] , - N'Executions / Minute before a "Frequent Execution Threshold" warning is triggered.' AS [Description] + /* Configuration table description */ + SELECT N'Frequent Execution Threshold' AS [Configuration Parameter] , + N'100' AS [Default Value] , + N'Executions / Minute' AS [Unit of Measure] , + N'Executions / Minute before a "Frequent Execution Threshold" warning is triggered.' AS [Description] - UNION ALL - SELECT N'Parameter Sniffing Variance Percent' , - N'30' , - N'Percent' , - N'Variance required between min/max values and average values before a "Parameter Sniffing" warning is triggered. Applies to worker time and returned rows.' + UNION ALL + SELECT N'Parameter Sniffing Variance Percent' , + N'30' , + N'Percent' , + N'Variance required between min/max values and average values before a "Parameter Sniffing" warning is triggered. Applies to worker time and returned rows.' - UNION ALL - SELECT N'Parameter Sniffing IO Threshold' , - N'100,000' , - N'Logical reads' , - N'Minimum number of average logical reads before parameter sniffing checks are evaluated.' + UNION ALL + SELECT N'Parameter Sniffing IO Threshold' , + N'100,000' , + N'Logical reads' , + N'Minimum number of average logical reads before parameter sniffing checks are evaluated.' - UNION ALL - SELECT N'Cost Threshold for Parallelism Warning' AS [Configuration Parameter] , - N'10' , - N'Percent' , - N'Trigger a "Nearly Parallel" warning when a query''s cost is within X percent of the cost threshold for parallelism.' + UNION ALL + SELECT N'Cost Threshold for Parallelism Warning' AS [Configuration Parameter] , + N'10' , + N'Percent' , + N'Trigger a "Nearly Parallel" warning when a query''s cost is within X percent of the cost threshold for parallelism.' + + UNION ALL + SELECT N'Long Running Query Warning' AS [Configuration Parameter] , + N'300' , + N'Seconds' , + N'Triggers a "Long Running Query Warning" when average duration, max CPU time, or max clock time is higher than this number.' + + UNION ALL + SELECT N'Unused Memory Grant Warning' AS [Configuration Parameter] , + N'10' , + N'Percent' , + N'Triggers an "Unused Memory Grant Warning" when a query uses >= X percent of its memory grant.'; + RETURN; + END; /* IF @Help = 1 */ - UNION ALL - SELECT N'Long Running Query Warning' AS [Configuration Parameter] , - N'300' , - N'Seconds' , - N'Triggers a "Long Running Query Warning" when average duration, max CPU time, or max clock time is higher than this number.' - UNION ALL - SELECT N'Unused Memory Grant Warning' AS [Configuration Parameter] , - N'10' , - N'Percent' , - N'Triggers an "Unused Memory Grant Warning" when a query uses >= X percent of its memory grant.'; - RETURN; -END; /*Validate version*/ IF ( diff --git a/Install-Core-Blitz-With-Query-Store.sql b/Install-Core-Blitz-With-Query-Store.sql index cace2dcc5..50929ea72 100755 --- a/Install-Core-Blitz-With-Query-Store.sql +++ b/Install-Core-Blitz-With-Query-Store.sql @@ -11252,458 +11252,459 @@ IF(@VersionCheckMode = 1) BEGIN RETURN; END; + +DECLARE @nl NVARCHAR(2) = NCHAR(13) + NCHAR(10) ; IF @Help = 1 -BEGIN -PRINT ' -sp_BlitzCache from http://FirstResponderKit.org + BEGIN + PRINT ' + sp_BlitzCache from http://FirstResponderKit.org -This script displays your most resource-intensive queries from the plan cache, -and points to ways you can tune these queries to make them faster. + This script displays your most resource-intensive queries from the plan cache, + and points to ways you can tune these queries to make them faster. -To learn more, visit http://FirstResponderKit.org where you can download new -versions for free, watch training videos on how it works, get more info on -the findings, contribute your own code, and more. + To learn more, visit http://FirstResponderKit.org where you can download new + versions for free, watch training videos on how it works, get more info on + the findings, contribute your own code, and more. -Known limitations of this version: - - This query will not run on SQL Server 2005. - - SQL Server 2008 and 2008R2 have a bug in trigger stats, so that output is - excluded by default. - - @IgnoreQueryHashes and @OnlyQueryHashes require a CSV list of hashes - with no spaces between the hash values. + Known limitations of this version: + - This query will not run on SQL Server 2005. + - SQL Server 2008 and 2008R2 have a bug in trigger stats, so that output is + excluded by default. + - @IgnoreQueryHashes and @OnlyQueryHashes require a CSV list of hashes + with no spaces between the hash values. -Unknown limitations of this version: - - May or may not be vulnerable to the wick effect. + Unknown limitations of this version: + - May or may not be vulnerable to the wick effect. -Changes - for the full list of improvements and fixes in this version, see: -https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/ + Changes - for the full list of improvements and fixes in this version, see: + https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/ -MIT License + MIT License -Copyright (c) 2021 Brent Ozar Unlimited + Copyright (c) 2021 Brent Ozar Unlimited -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -'; + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + '; -DECLARE @nl NVARCHAR(2) = NCHAR(13) + NCHAR(10) ; ---IF @Help = 1 /* We're still under a @Help = 1 BEGIN from above */ ---BEGIN - SELECT N'@Help' AS [Parameter Name] , - N'BIT' AS [Data Type] , - N'Displays this help message.' AS [Parameter Description] + SELECT N'@Help' AS [Parameter Name] , + N'BIT' AS [Data Type] , + N'Displays this help message.' AS [Parameter Description] - UNION ALL - SELECT N'@Top', - N'INT', - N'The number of records to retrieve and analyze from the plan cache. The following DMVs are used as the plan cache: dm_exec_query_stats, dm_exec_procedure_stats, dm_exec_trigger_stats.' + UNION ALL + SELECT N'@Top', + N'INT', + N'The number of records to retrieve and analyze from the plan cache. The following DMVs are used as the plan cache: dm_exec_query_stats, dm_exec_procedure_stats, dm_exec_trigger_stats.' - UNION ALL - SELECT N'@SortOrder', - N'VARCHAR(10)', - N'Data processing and display order. @SortOrder will still be used, even when preparing output for a table or for excel. Possible values are: "CPU", "Reads", "Writes", "Duration", "Executions", "Recent Compilations", "Memory Grant", "Unused Grant", "Spills", "Query Hash". Additionally, the word "Average" or "Avg" can be used to sort on averages rather than total. "Executions per minute" and "Executions / minute" can be used to sort by execution per minute. For the truly lazy, "xpm" can also be used. Note that when you use all or all avg, the only parameters you can use are @Top and @DatabaseName. All others will be ignored.' + UNION ALL + SELECT N'@SortOrder', + N'VARCHAR(10)', + N'Data processing and display order. @SortOrder will still be used, even when preparing output for a table or for excel. Possible values are: "CPU", "Reads", "Writes", "Duration", "Executions", "Recent Compilations", "Memory Grant", "Unused Grant", "Spills", "Query Hash". Additionally, the word "Average" or "Avg" can be used to sort on averages rather than total. "Executions per minute" and "Executions / minute" can be used to sort by execution per minute. For the truly lazy, "xpm" can also be used. Note that when you use all or all avg, the only parameters you can use are @Top and @DatabaseName. All others will be ignored.' - UNION ALL - SELECT N'@UseTriggersAnyway', - N'BIT', - N'On SQL Server 2008R2 and earlier, trigger execution count is incorrect - trigger execution count is incremented once per execution of a SQL agent job. If you still want to see relative execution count of triggers, then you can force sp_BlitzCache to include this information.' + UNION ALL + SELECT N'@UseTriggersAnyway', + N'BIT', + N'On SQL Server 2008R2 and earlier, trigger execution count is incorrect - trigger execution count is incremented once per execution of a SQL agent job. If you still want to see relative execution count of triggers, then you can force sp_BlitzCache to include this information.' - UNION ALL - SELECT N'@ExportToExcel', - N'BIT', - N'Prepare output for exporting to Excel. Newlines and additional whitespace are removed from query text and the execution plan is not displayed.' + UNION ALL + SELECT N'@ExportToExcel', + N'BIT', + N'Prepare output for exporting to Excel. Newlines and additional whitespace are removed from query text and the execution plan is not displayed.' - UNION ALL - SELECT N'@ExpertMode', - N'TINYINT', - N'Default 0. When set to 1, results include more columns. When 2, mode is optimized for Opserver, the open source dashboard.' + UNION ALL + SELECT N'@ExpertMode', + N'TINYINT', + N'Default 0. When set to 1, results include more columns. When 2, mode is optimized for Opserver, the open source dashboard.' - UNION ALL - SELECT N'@OutputDatabaseName', - N'NVARCHAR(128)', - N'The output database. If this does not exist SQL Server will divide by zero and everything will fall apart.' + UNION ALL + SELECT N'@OutputDatabaseName', + N'NVARCHAR(128)', + N'The output database. If this does not exist SQL Server will divide by zero and everything will fall apart.' - UNION ALL - SELECT N'@OutputSchemaName', - N'NVARCHAR(258)', - N'The output schema. If this does not exist SQL Server will divide by zero and everything will fall apart.' + UNION ALL + SELECT N'@OutputSchemaName', + N'NVARCHAR(258)', + N'The output schema. If this does not exist SQL Server will divide by zero and everything will fall apart.' - UNION ALL - SELECT N'@OutputTableName', - N'NVARCHAR(258)', - N'The output table. If this does not exist, it will be created for you.' + UNION ALL + SELECT N'@OutputTableName', + N'NVARCHAR(258)', + N'The output table. If this does not exist, it will be created for you.' - UNION ALL - SELECT N'@DurationFilter', - N'DECIMAL(38,4)', - N'Excludes queries with an average duration (in seconds) less than @DurationFilter.' + UNION ALL + SELECT N'@DurationFilter', + N'DECIMAL(38,4)', + N'Excludes queries with an average duration (in seconds) less than @DurationFilter.' - UNION ALL - SELECT N'@HideSummary', - N'BIT', - N'Hides the findings summary result set.' + UNION ALL + SELECT N'@HideSummary', + N'BIT', + N'Hides the findings summary result set.' - UNION ALL - SELECT N'@IgnoreSystemDBs', - N'BIT', - N'Ignores plans found in the system databases (master, model, msdb, tempdb, and resourcedb)' + UNION ALL + SELECT N'@IgnoreSystemDBs', + N'BIT', + N'Ignores plans found in the system databases (master, model, msdb, tempdb, and resourcedb)' - UNION ALL - SELECT N'@OnlyQueryHashes', - N'VARCHAR(MAX)', - N'A list of query hashes to query. All other query hashes will be ignored. Stored procedures and triggers will be ignored.' + UNION ALL + SELECT N'@OnlyQueryHashes', + N'VARCHAR(MAX)', + N'A list of query hashes to query. All other query hashes will be ignored. Stored procedures and triggers will be ignored.' - UNION ALL - SELECT N'@IgnoreQueryHashes', - N'VARCHAR(MAX)', - N'A list of query hashes to ignore.' + UNION ALL + SELECT N'@IgnoreQueryHashes', + N'VARCHAR(MAX)', + N'A list of query hashes to ignore.' - UNION ALL - SELECT N'@OnlySqlHandles', - N'VARCHAR(MAX)', - N'One or more sql_handles to use for filtering results.' + UNION ALL + SELECT N'@OnlySqlHandles', + N'VARCHAR(MAX)', + N'One or more sql_handles to use for filtering results.' UNION ALL - SELECT N'@IgnoreSqlHandles', - N'VARCHAR(MAX)', - N'One or more sql_handles to ignore.' + SELECT N'@IgnoreSqlHandles', + N'VARCHAR(MAX)', + N'One or more sql_handles to ignore.' - UNION ALL - SELECT N'@DatabaseName', - N'NVARCHAR(128)', - N'A database name which is used for filtering results.' + UNION ALL + SELECT N'@DatabaseName', + N'NVARCHAR(128)', + N'A database name which is used for filtering results.' - UNION ALL - SELECT N'@StoredProcName', - N'NVARCHAR(128)', - N'Name of stored procedure you want to find plans for.' + UNION ALL + SELECT N'@StoredProcName', + N'NVARCHAR(128)', + N'Name of stored procedure you want to find plans for.' - UNION ALL - SELECT N'@SlowlySearchPlansFor', - N'NVARCHAR(4000)', - N'String to search for in plan text. % wildcards allowed.' + UNION ALL + SELECT N'@SlowlySearchPlansFor', + N'NVARCHAR(4000)', + N'String to search for in plan text. % wildcards allowed.' - UNION ALL - SELECT N'@BringThePain', - N'BIT', - N'When using @SortOrder = ''all'' and @Top > 10, we require you to set @BringThePain = 1 so you understand that sp_BlitzCache will take a while to run.' + UNION ALL + SELECT N'@BringThePain', + N'BIT', + N'When using @SortOrder = ''all'' and @Top > 10, we require you to set @BringThePain = 1 so you understand that sp_BlitzCache will take a while to run.' - UNION ALL - SELECT N'@QueryFilter', - N'VARCHAR(10)', - N'Filter out stored procedures or statements. The default value is ''ALL''. Allowed values are ''procedures'', ''statements'', ''functions'', or ''all'' (any variation in capitalization is acceptable).' + UNION ALL + SELECT N'@QueryFilter', + N'VARCHAR(10)', + N'Filter out stored procedures or statements. The default value is ''ALL''. Allowed values are ''procedures'', ''statements'', ''functions'', or ''all'' (any variation in capitalization is acceptable).' - UNION ALL - SELECT N'@Reanalyze', - N'BIT', - N'The default is 0. When set to 0, sp_BlitzCache will re-evalute the plan cache. Set this to 1 to reanalyze existing results' + UNION ALL + SELECT N'@Reanalyze', + N'BIT', + N'The default is 0. When set to 0, sp_BlitzCache will re-evalute the plan cache. Set this to 1 to reanalyze existing results' - UNION ALL - SELECT N'@MinimumExecutionCount', - N'INT', - N'Queries with fewer than this number of executions will be omitted from results.' + UNION ALL + SELECT N'@MinimumExecutionCount', + N'INT', + N'Queries with fewer than this number of executions will be omitted from results.' UNION ALL - SELECT N'@Debug', - N'BIT', - N'Setting this to 1 will print dynamic SQL and select data from all tables used.' + SELECT N'@Debug', + N'BIT', + N'Setting this to 1 will print dynamic SQL and select data from all tables used.' - UNION ALL - SELECT N'@MinutesBack', - N'INT', - N'How many minutes back to begin plan cache analysis. If you put in a positive number, we''ll flip it to negtive.'; + UNION ALL + SELECT N'@MinutesBack', + N'INT', + N'How many minutes back to begin plan cache analysis. If you put in a positive number, we''ll flip it to negtive.'; - /* Column definitions */ - SELECT N'# Executions' AS [Column Name], - N'BIGINT' AS [Data Type], - N'The number of executions of this particular query. This is computed across statements, procedures, and triggers and aggregated by the SQL handle.' AS [Column Description] + /* Column definitions */ + SELECT N'# Executions' AS [Column Name], + N'BIGINT' AS [Data Type], + N'The number of executions of this particular query. This is computed across statements, procedures, and triggers and aggregated by the SQL handle.' AS [Column Description] - UNION ALL - SELECT N'Executions / Minute', - N'MONEY', - N'Number of executions per minute - calculated for the life of the current plan. Plan life is the last execution time minus the plan creation time.' + UNION ALL + SELECT N'Executions / Minute', + N'MONEY', + N'Number of executions per minute - calculated for the life of the current plan. Plan life is the last execution time minus the plan creation time.' - UNION ALL - SELECT N'Execution Weight', - N'MONEY', - N'An arbitrary metric of total "execution-ness". A weight of 2 is "one more" than a weight of 1.' + UNION ALL + SELECT N'Execution Weight', + N'MONEY', + N'An arbitrary metric of total "execution-ness". A weight of 2 is "one more" than a weight of 1.' - UNION ALL - SELECT N'Database', - N'sysname', - N'The name of the database where the plan was encountered. If the database name cannot be determined for some reason, a value of NA will be substituted. A value of 32767 indicates the plan comes from ResourceDB.' + UNION ALL + SELECT N'Database', + N'sysname', + N'The name of the database where the plan was encountered. If the database name cannot be determined for some reason, a value of NA will be substituted. A value of 32767 indicates the plan comes from ResourceDB.' - UNION ALL - SELECT N'Total CPU', - N'BIGINT', - N'Total CPU time, reported in milliseconds, that was consumed by all executions of this query since the last compilation.' + UNION ALL + SELECT N'Total CPU', + N'BIGINT', + N'Total CPU time, reported in milliseconds, that was consumed by all executions of this query since the last compilation.' - UNION ALL - SELECT N'Avg CPU', - N'BIGINT', - N'Average CPU time, reported in milliseconds, consumed by each execution of this query since the last compilation.' + UNION ALL + SELECT N'Avg CPU', + N'BIGINT', + N'Average CPU time, reported in milliseconds, consumed by each execution of this query since the last compilation.' - UNION ALL - SELECT N'CPU Weight', - N'MONEY', - N'An arbitrary metric of total "CPU-ness". A weight of 2 is "one more" than a weight of 1.' + UNION ALL + SELECT N'CPU Weight', + N'MONEY', + N'An arbitrary metric of total "CPU-ness". A weight of 2 is "one more" than a weight of 1.' - UNION ALL - SELECT N'Total Duration', - N'BIGINT', - N'Total elapsed time, reported in milliseconds, consumed by all executions of this query since last compilation.' + UNION ALL + SELECT N'Total Duration', + N'BIGINT', + N'Total elapsed time, reported in milliseconds, consumed by all executions of this query since last compilation.' - UNION ALL - SELECT N'Avg Duration', - N'BIGINT', - N'Average elapsed time, reported in milliseconds, consumed by each execution of this query since the last compilation.' + UNION ALL + SELECT N'Avg Duration', + N'BIGINT', + N'Average elapsed time, reported in milliseconds, consumed by each execution of this query since the last compilation.' - UNION ALL - SELECT N'Duration Weight', - N'MONEY', - N'An arbitrary metric of total "Duration-ness". A weight of 2 is "one more" than a weight of 1.' + UNION ALL + SELECT N'Duration Weight', + N'MONEY', + N'An arbitrary metric of total "Duration-ness". A weight of 2 is "one more" than a weight of 1.' - UNION ALL - SELECT N'Total Reads', - N'BIGINT', - N'Total logical reads performed by this query since last compilation.' + UNION ALL + SELECT N'Total Reads', + N'BIGINT', + N'Total logical reads performed by this query since last compilation.' - UNION ALL - SELECT N'Average Reads', - N'BIGINT', - N'Average logical reads performed by each execution of this query since the last compilation.' + UNION ALL + SELECT N'Average Reads', + N'BIGINT', + N'Average logical reads performed by each execution of this query since the last compilation.' - UNION ALL - SELECT N'Read Weight', - N'MONEY', - N'An arbitrary metric of "Read-ness". A weight of 2 is "one more" than a weight of 1.' + UNION ALL + SELECT N'Read Weight', + N'MONEY', + N'An arbitrary metric of "Read-ness". A weight of 2 is "one more" than a weight of 1.' - UNION ALL - SELECT N'Total Writes', - N'BIGINT', - N'Total logical writes performed by this query since last compilation.' + UNION ALL + SELECT N'Total Writes', + N'BIGINT', + N'Total logical writes performed by this query since last compilation.' - UNION ALL - SELECT N'Average Writes', - N'BIGINT', - N'Average logical writes performed by each execution this query since last compilation.' + UNION ALL + SELECT N'Average Writes', + N'BIGINT', + N'Average logical writes performed by each execution this query since last compilation.' - UNION ALL - SELECT N'Write Weight', - N'MONEY', - N'An arbitrary metric of "Write-ness". A weight of 2 is "one more" than a weight of 1.' + UNION ALL + SELECT N'Write Weight', + N'MONEY', + N'An arbitrary metric of "Write-ness". A weight of 2 is "one more" than a weight of 1.' - UNION ALL - SELECT N'Query Type', - N'NVARCHAR(258)', - N'The type of query being examined. This can be "Procedure", "Statement", or "Trigger".' + UNION ALL + SELECT N'Query Type', + N'NVARCHAR(258)', + N'The type of query being examined. This can be "Procedure", "Statement", or "Trigger".' - UNION ALL - SELECT N'Query Text', - N'NVARCHAR(4000)', - N'The text of the query. This may be truncated by either SQL Server or by sp_BlitzCache(tm) for display purposes.' + UNION ALL + SELECT N'Query Text', + N'NVARCHAR(4000)', + N'The text of the query. This may be truncated by either SQL Server or by sp_BlitzCache(tm) for display purposes.' - UNION ALL - SELECT N'% Executions (Type)', - N'MONEY', - N'Percent of executions relative to the type of query - e.g. 17.2% of all stored procedure executions.' + UNION ALL + SELECT N'% Executions (Type)', + N'MONEY', + N'Percent of executions relative to the type of query - e.g. 17.2% of all stored procedure executions.' - UNION ALL - SELECT N'% CPU (Type)', - N'MONEY', - N'Percent of CPU time consumed by this query for a given type of query - e.g. 22% of CPU of all stored procedures executed.' + UNION ALL + SELECT N'% CPU (Type)', + N'MONEY', + N'Percent of CPU time consumed by this query for a given type of query - e.g. 22% of CPU of all stored procedures executed.' - UNION ALL - SELECT N'% Duration (Type)', - N'MONEY', - N'Percent of elapsed time consumed by this query for a given type of query - e.g. 12% of all statements executed.' + UNION ALL + SELECT N'% Duration (Type)', + N'MONEY', + N'Percent of elapsed time consumed by this query for a given type of query - e.g. 12% of all statements executed.' - UNION ALL - SELECT N'% Reads (Type)', - N'MONEY', - N'Percent of reads consumed by this query for a given type of query - e.g. 34.2% of all stored procedures executed.' + UNION ALL + SELECT N'% Reads (Type)', + N'MONEY', + N'Percent of reads consumed by this query for a given type of query - e.g. 34.2% of all stored procedures executed.' - UNION ALL - SELECT N'% Writes (Type)', - N'MONEY', - N'Percent of writes performed by this query for a given type of query - e.g. 43.2% of all statements executed.' + UNION ALL + SELECT N'% Writes (Type)', + N'MONEY', + N'Percent of writes performed by this query for a given type of query - e.g. 43.2% of all statements executed.' - UNION ALL - SELECT N'Total Rows', - N'BIGINT', - N'Total number of rows returned for all executions of this query. This only applies to query level stats, not stored procedures or triggers.' + UNION ALL + SELECT N'Total Rows', + N'BIGINT', + N'Total number of rows returned for all executions of this query. This only applies to query level stats, not stored procedures or triggers.' - UNION ALL - SELECT N'Average Rows', - N'MONEY', - N'Average number of rows returned by each execution of the query.' + UNION ALL + SELECT N'Average Rows', + N'MONEY', + N'Average number of rows returned by each execution of the query.' - UNION ALL - SELECT N'Min Rows', - N'BIGINT', - N'The minimum number of rows returned by any execution of this query.' + UNION ALL + SELECT N'Min Rows', + N'BIGINT', + N'The minimum number of rows returned by any execution of this query.' - UNION ALL - SELECT N'Max Rows', - N'BIGINT', - N'The maximum number of rows returned by any execution of this query.' + UNION ALL + SELECT N'Max Rows', + N'BIGINT', + N'The maximum number of rows returned by any execution of this query.' - UNION ALL - SELECT N'MinGrantKB', - N'BIGINT', - N'The minimum memory grant the query received in kb.' + UNION ALL + SELECT N'MinGrantKB', + N'BIGINT', + N'The minimum memory grant the query received in kb.' - UNION ALL - SELECT N'MaxGrantKB', - N'BIGINT', - N'The maximum memory grant the query received in kb.' + UNION ALL + SELECT N'MaxGrantKB', + N'BIGINT', + N'The maximum memory grant the query received in kb.' - UNION ALL - SELECT N'MinUsedGrantKB', - N'BIGINT', - N'The minimum used memory grant the query received in kb.' + UNION ALL + SELECT N'MinUsedGrantKB', + N'BIGINT', + N'The minimum used memory grant the query received in kb.' - UNION ALL - SELECT N'MaxUsedGrantKB', - N'BIGINT', - N'The maximum used memory grant the query received in kb.' + UNION ALL + SELECT N'MaxUsedGrantKB', + N'BIGINT', + N'The maximum used memory grant the query received in kb.' - UNION ALL - SELECT N'MinSpills', - N'BIGINT', - N'The minimum amount this query has spilled to tempdb in 8k pages.' + UNION ALL + SELECT N'MinSpills', + N'BIGINT', + N'The minimum amount this query has spilled to tempdb in 8k pages.' - UNION ALL - SELECT N'MaxSpills', - N'BIGINT', - N'The maximum amount this query has spilled to tempdb in 8k pages.' + UNION ALL + SELECT N'MaxSpills', + N'BIGINT', + N'The maximum amount this query has spilled to tempdb in 8k pages.' - UNION ALL - SELECT N'TotalSpills', - N'BIGINT', - N'The total amount this query has spilled to tempdb in 8k pages.' + UNION ALL + SELECT N'TotalSpills', + N'BIGINT', + N'The total amount this query has spilled to tempdb in 8k pages.' - UNION ALL - SELECT N'AvgSpills', - N'BIGINT', - N'The average amount this query has spilled to tempdb in 8k pages.' + UNION ALL + SELECT N'AvgSpills', + N'BIGINT', + N'The average amount this query has spilled to tempdb in 8k pages.' - UNION ALL - SELECT N'PercentMemoryGrantUsed', - N'MONEY', - N'Result of dividing the maximum grant used by the minimum granted.' + UNION ALL + SELECT N'PercentMemoryGrantUsed', + N'MONEY', + N'Result of dividing the maximum grant used by the minimum granted.' - UNION ALL - SELECT N'AvgMaxMemoryGrant', - N'MONEY', - N'The average maximum memory grant for a query.' + UNION ALL + SELECT N'AvgMaxMemoryGrant', + N'MONEY', + N'The average maximum memory grant for a query.' - UNION ALL - SELECT N'# Plans', - N'INT', - N'The total number of execution plans found that match a given query.' + UNION ALL + SELECT N'# Plans', + N'INT', + N'The total number of execution plans found that match a given query.' - UNION ALL - SELECT N'# Distinct Plans', - N'INT', - N'The number of distinct execution plans that match a given query. ' - + NCHAR(13) + NCHAR(10) - + N'This may be caused by running the same query across multiple databases or because of a lack of proper parameterization in the database.' + UNION ALL + SELECT N'# Distinct Plans', + N'INT', + N'The number of distinct execution plans that match a given query. ' + + NCHAR(13) + NCHAR(10) + + N'This may be caused by running the same query across multiple databases or because of a lack of proper parameterization in the database.' - UNION ALL - SELECT N'Created At', - N'DATETIME', - N'Time that the execution plan was last compiled.' + UNION ALL + SELECT N'Created At', + N'DATETIME', + N'Time that the execution plan was last compiled.' - UNION ALL - SELECT N'Last Execution', - N'DATETIME', - N'The last time that this query was executed.' + UNION ALL + SELECT N'Last Execution', + N'DATETIME', + N'The last time that this query was executed.' - UNION ALL - SELECT N'Query Plan', - N'XML', - N'The query plan. Click to display a graphical plan or, if you need to patch SSMS, a pile of XML.' + UNION ALL + SELECT N'Query Plan', + N'XML', + N'The query plan. Click to display a graphical plan or, if you need to patch SSMS, a pile of XML.' - UNION ALL - SELECT N'Plan Handle', - N'VARBINARY(64)', - N'An arbitrary identifier referring to the compiled plan this query is a part of.' + UNION ALL + SELECT N'Plan Handle', + N'VARBINARY(64)', + N'An arbitrary identifier referring to the compiled plan this query is a part of.' - UNION ALL - SELECT N'SQL Handle', - N'VARBINARY(64)', - N'An arbitrary identifier referring to a batch or stored procedure that this query is a part of.' + UNION ALL + SELECT N'SQL Handle', + N'VARBINARY(64)', + N'An arbitrary identifier referring to a batch or stored procedure that this query is a part of.' - UNION ALL - SELECT N'Query Hash', - N'BINARY(8)', - N'A hash of the query. Queries with the same query hash have similar logic but only differ by literal values or database.' + UNION ALL + SELECT N'Query Hash', + N'BINARY(8)', + N'A hash of the query. Queries with the same query hash have similar logic but only differ by literal values or database.' - UNION ALL - SELECT N'Warnings', - N'VARCHAR(MAX)', - N'A list of individual warnings generated by this query.' ; + UNION ALL + SELECT N'Warnings', + N'VARCHAR(MAX)', + N'A list of individual warnings generated by this query.' ; - /* Configuration table description */ - SELECT N'Frequent Execution Threshold' AS [Configuration Parameter] , - N'100' AS [Default Value] , - N'Executions / Minute' AS [Unit of Measure] , - N'Executions / Minute before a "Frequent Execution Threshold" warning is triggered.' AS [Description] + /* Configuration table description */ + SELECT N'Frequent Execution Threshold' AS [Configuration Parameter] , + N'100' AS [Default Value] , + N'Executions / Minute' AS [Unit of Measure] , + N'Executions / Minute before a "Frequent Execution Threshold" warning is triggered.' AS [Description] - UNION ALL - SELECT N'Parameter Sniffing Variance Percent' , - N'30' , - N'Percent' , - N'Variance required between min/max values and average values before a "Parameter Sniffing" warning is triggered. Applies to worker time and returned rows.' + UNION ALL + SELECT N'Parameter Sniffing Variance Percent' , + N'30' , + N'Percent' , + N'Variance required between min/max values and average values before a "Parameter Sniffing" warning is triggered. Applies to worker time and returned rows.' - UNION ALL - SELECT N'Parameter Sniffing IO Threshold' , - N'100,000' , - N'Logical reads' , - N'Minimum number of average logical reads before parameter sniffing checks are evaluated.' + UNION ALL + SELECT N'Parameter Sniffing IO Threshold' , + N'100,000' , + N'Logical reads' , + N'Minimum number of average logical reads before parameter sniffing checks are evaluated.' - UNION ALL - SELECT N'Cost Threshold for Parallelism Warning' AS [Configuration Parameter] , - N'10' , - N'Percent' , - N'Trigger a "Nearly Parallel" warning when a query''s cost is within X percent of the cost threshold for parallelism.' + UNION ALL + SELECT N'Cost Threshold for Parallelism Warning' AS [Configuration Parameter] , + N'10' , + N'Percent' , + N'Trigger a "Nearly Parallel" warning when a query''s cost is within X percent of the cost threshold for parallelism.' + + UNION ALL + SELECT N'Long Running Query Warning' AS [Configuration Parameter] , + N'300' , + N'Seconds' , + N'Triggers a "Long Running Query Warning" when average duration, max CPU time, or max clock time is higher than this number.' + + UNION ALL + SELECT N'Unused Memory Grant Warning' AS [Configuration Parameter] , + N'10' , + N'Percent' , + N'Triggers an "Unused Memory Grant Warning" when a query uses >= X percent of its memory grant.'; + RETURN; + END; /* IF @Help = 1 */ - UNION ALL - SELECT N'Long Running Query Warning' AS [Configuration Parameter] , - N'300' , - N'Seconds' , - N'Triggers a "Long Running Query Warning" when average duration, max CPU time, or max clock time is higher than this number.' - UNION ALL - SELECT N'Unused Memory Grant Warning' AS [Configuration Parameter] , - N'10' , - N'Percent' , - N'Triggers an "Unused Memory Grant Warning" when a query uses >= X percent of its memory grant.'; - RETURN; -END; /*Validate version*/ IF ( From 59d67fa60ae6850ff26cd5bc5dcbe2a571c4598b Mon Sep 17 00:00:00 2001 From: Emanuele Meazzo Date: Mon, 18 Jan 2021 16:08:54 +0100 Subject: [PATCH 077/662] Apply @OutputTableRetentionDays to BlitzWho results #2758 Fixes #2758 --- sp_BlitzFirst.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sp_BlitzFirst.sql b/sp_BlitzFirst.sql index be4a54878..4ba588adb 100644 --- a/sp_BlitzFirst.sql +++ b/sp_BlitzFirst.sql @@ -1387,7 +1387,7 @@ BEGIN WHERE QUOTENAME([name]) = @OutputDatabaseName) BEGIN RAISERROR('Logging sp_BlitzWho to table',10,1) WITH NOWAIT; - EXEC sp_BlitzWho @OutputDatabaseName = @UnquotedOutputDatabaseName, @OutputSchemaName = @UnquotedOutputSchemaName, @OutputTableName = @OutputTableNameBlitzWho, @CheckDateOverride = @StartSampleTime; + EXEC sp_BlitzWho @OutputDatabaseName = @UnquotedOutputDatabaseName, @OutputSchemaName = @UnquotedOutputSchemaName, @OutputTableName = @OutputTableNameBlitzWho, @CheckDateOverride = @StartSampleTime, @OutputTableRetentionDays = @OutputTableRetentionDays; END RAISERROR('Beginning investigatory queries',10,1) WITH NOWAIT; From cb3b7e3e717cd8157248a69925075b5b0c30edc4 Mon Sep 17 00:00:00 2001 From: Emanuele Meazzo Date: Mon, 18 Jan 2021 16:13:29 +0100 Subject: [PATCH 078/662] Update SqlServerVersions.sql --- SqlServerVersions.sql | 1 - 1 file changed, 1 deletion(-) diff --git a/SqlServerVersions.sql b/SqlServerVersions.sql index 3c1ef191a..b265b6d98 100644 --- a/SqlServerVersions.sql +++ b/SqlServerVersions.sql @@ -58,7 +58,6 @@ VALUES (14, 3294, 'RTM CU20', 'https://support.microsoft.com/en-us/help/4541283', '2020-04-07', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 20'), (14, 3257, 'RTM CU19', 'https://support.microsoft.com/en-us/help/4535007', '2020-02-05', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 19'), (14, 3257, 'RTM CU18', 'https://support.microsoft.com/en-us/help/4527377', '2019-12-09', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 18'), - (15, 2000, 'RTM ', '', '2019-11-04', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'RTM '), (14, 3238, 'RTM CU17', 'https://support.microsoft.com/en-us/help/4515579', '2019-10-08', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 17'), (14, 3223, 'RTM CU16', 'https://support.microsoft.com/en-us/help/4508218', '2019-08-01', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 16'), (14, 3162, 'RTM CU15', 'https://support.microsoft.com/en-us/help/4498951', '2019-05-24', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 15'), From 78846463517e13224e83ade2f1eea7395e122fca Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Tue, 19 Jan 2021 02:35:25 -0800 Subject: [PATCH 079/662] #2762 sp_BlitzIndex sort dupes Sort them by total rows descending instead of object name. Closes #2762. --- sp_BlitzIndex.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index f14914ccd..4c503f473 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -2877,7 +2877,7 @@ BEGIN; /* WHERE clause limits to only @ThresholdMB or larger duplicate indexes when getting all databases or using PainRelief mode */ WHERE ips.total_reserved_MB >= CASE WHEN (@GetAllDatabases = 1 OR @Mode = 0) THEN @ThresholdMB ELSE ips.total_reserved_MB END AND ip.is_primary_key = 0 - ORDER BY ip.object_id, ip.key_column_names_with_sort_order + ORDER BY ips.total_rows DESC, ip.[schema_name], ip.[object_name], ip.key_column_names_with_sort_order OPTION ( RECOMPILE ); RAISERROR('check_id 2: Keys w/ identical leading columns.', 0,1) WITH NOWAIT; @@ -2915,7 +2915,7 @@ BEGIN; di.number_dupes > 1 ) AND ip.is_primary_key = 0 - ORDER BY ip.[schema_name], ip.[object_name], ip.key_column_names, ip.include_column_names + ORDER BY ips.total_rows DESC, ip.[schema_name], ip.[object_name], ip.key_column_names, ip.include_column_names OPTION ( RECOMPILE ); ---------------------------------------- From 71c0082f77903441b1a70b3881d21142d1e03e9e Mon Sep 17 00:00:00 2001 From: Adrian Buckman <33764798+Adedba@users.noreply.github.com> Date: Tue, 19 Jan 2021 22:30:59 +0000 Subject: [PATCH 080/662] Update sp_BlitzAnalysis.sql Tidy up --- sp_BlitzAnalysis.sql | 59 ++++++++++++++++++++++---------------------- 1 file changed, 30 insertions(+), 29 deletions(-) diff --git a/sp_BlitzAnalysis.sql b/sp_BlitzAnalysis.sql index e5efd119a..47a4ec2e7 100644 --- a/sp_BlitzAnalysis.sql +++ b/sp_BlitzAnalysis.sql @@ -1,12 +1,5 @@ -USE [master] -GO - - -SET ANSI_NULLS ON -GO - +SET ANSI_NULLS ON; SET QUOTED_IDENTIFIER ON -GO IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[sp_BlitzAnalysis]') AND type in (N'P', N'PC')) BEGIN @@ -14,13 +7,12 @@ EXEC dbo.sp_executesql @statement = N'CREATE PROCEDURE [dbo].[sp_BlitzAnalysis] END GO - ALTER PROCEDURE [dbo].[sp_BlitzAnalysis] ( @Help TINYINT = 0, @FromDate DATETIMEOFFSET(7) = NULL, @ToDate DATETIMEOFFSET(7) = NULL, -@OutputSchemaName NVARCHAR(256) = N'dbo', @OutputDatabaseName NVARCHAR(256) = N'DBA', +@OutputSchemaName NVARCHAR(256) = N'dbo', @OutputTableNameBlitzFirst NVARCHAR(256) = N'BlitzFirst', /**/ @OutputTableNameFileStats NVARCHAR(256) = N'BlitzFirst_FileStats',/**/ @OutputTableNamePerfmonStats NVARCHAR(256) = N'BlitzFirst_PerfmonStats', @@ -55,22 +47,30 @@ IF (@Help = 1) BEGIN PRINT 'EXEC sp_BlitzAnalysis @FromDate = ''20201111 13:30'', -@ToDate = NULL, /* Get an hour of data */ -@OutputSchemaName = N''dbo'', -@OutputDatabaseName = N''DBA'', -@OutputTableNameBlitzFirst = N''BlitzFirst'', -@OutputTableNameFileStats = N''BlitzFirst_FileStats'', -@OutputTableNamePerfmonStats = N''BlitzFirst_PerfmonStats'', -@OutputTableNameWaitStats = N''BlitzFirst_WaitStats'', -@OutputTableNameBlitzCache = N''BlitzCache'', -@OutputTableNameBlitzWho = N''BlitzWho'', -@MaxBlitzFirstPriority = 249, -@BlitzCacheSortorder = ''cpu'', -@WaitStatsTop = 3, /* Controls the top for wait stats only */ -@Debug = 0;'; +@ToDate = NULL, /* NULL will get an hour of data since @FromDate */ +@OutputDatabaseName = N''DBA'', /* Specify the database name where where we can find your logged blitz data */ +@OutputSchemaName = N''dbo'', /* Specify the schema */ +@OutputTableNameBlitzFirst = N''BlitzFirst'', /* Table name where you are storing sp_BlitzFirst output, Set to NULL to ignore */ +@OutputTableNameFileStats = N''BlitzFirst_FileStats'', /* Table name where you are storing sp_BlitzFirst filestats output, Set to NULL to ignore */ +@OutputTableNamePerfmonStats = N''BlitzFirst_PerfmonStats'', /* Table name where you are storing sp_BlitzFirst Perfmon output, Set to NULL to ignore */ +@OutputTableNameWaitStats = N''BlitzFirst_WaitStats'', /* Table name where you are storing sp_BlitzFirst Wait stats output, Set to NULL to ignore */ +@OutputTableNameBlitzCache = N''BlitzCache'', /* Table name where you are storing sp_BlitzCache output, Set to NULL to ignore */ +@OutputTableNameBlitzWho = N''BlitzWho'', /* Table name where you are storing sp_BlitzWho output, Set to NULL to ignore */ +@MaxBlitzFirstPriority = 249, /* Max priority to include in the results */ +@BlitzCacheSortorder = ''cpu'', /* Accepted values ''all'' ''cpu'' ''reads'' ''writes'' ''duration'' ''executions'' ''memory grant'' ''spills'' */ +@WaitStatsTop = 3, /* Controls the top for wait stats only */ +@Debug = 0; /* Show sp_BlitzAnalysis SQL commands in the messages tab as they execute */ + +/* +Additional parameters: +@ReadLatencyThreshold INT /* Default: 100 - Sets the threshold in ms to compare against io_stall_read_average_ms in your filestats table */ +@WriteLatencyThreshold INT /* Default: 100 - Sets the threshold in ms to compare against io_stall_write_average_ms in your filestats table */ +@BringThePain BIT /* Default: 0 - If you are getting more than 4 hours of data with blitzcachesortorder set to ''all'' you will need to set BringThePain to 1 */ +*/'; RETURN; END +/* Declare all local veriables required */ DECLARE @FullOutputTableNameBlitzFirst NVARCHAR(1000); DECLARE @FullOutputTableNameFileStats NVARCHAR(1000); DECLARE @FullOutputTableNamePerfmonStats NVARCHAR(1000); @@ -104,7 +104,7 @@ CREATE TABLE #BlitzFirstCounts ( [LastOccurrence] DATETIMEOFFSET(7) NULL ); -/* Sanitise variables */ +/* Vallidate variables and set defaults as required */ IF (@BlitzCacheSortorder IS NULL) BEGIN SET @BlitzCacheSortorder = N'cpu'; @@ -118,6 +118,7 @@ BEGIN RETURN; END +/* We need to check if your SQL version has memory grant and spills columns in sys.dm_exec_query_stats */ SELECT @IncludeMemoryGrants = CASE WHEN (EXISTS(SELECT * FROM sys.all_columns WHERE [object_id] = OBJECT_ID('sys.dm_exec_query_stats') AND name = 'max_grant_kb')) THEN 1 @@ -606,11 +607,11 @@ FROM (VALUES ) SortOptions(Sortorder,Aliasname,Columnname) WHERE CASE /* for spills and memory grant sorts make sure the underlying columns exist in the DMV otherwise do not include them */ - WHEN (@IncludeMemoryGrants = 0 OR @IncludeMemoryGrants IS NULL) AND [SortOptions].[Sortorder] = N'memory grant' THEN NULL - WHEN (@IncludeSpills = 0 OR @IncludeSpills IS NULL) AND [SortOptions].[Sortorder] = N'spills' THEN NULL + WHEN (@IncludeMemoryGrants = 0 OR @IncludeMemoryGrants IS NULL) AND ([SortOptions].[Sortorder] = N'memory grant' OR [SortOptions].[Sortorder] = N'all') THEN NULL + WHEN (@IncludeSpills = 0 OR @IncludeSpills IS NULL) AND ([SortOptions].[Sortorder] = N'spills' OR [SortOptions].[Sortorder] = N'all') THEN NULL ELSE [SortOptions].[Sortorder] END = ISNULL(NULLIF(@BlitzCacheSortorder,N'all'),[SortOptions].[Sortorder]) -FOR XML PATH(N''), TYPE).value(N'.[1]', N'NVARCHAR(MAX)') +FOR XML PATH(N''), TYPE).value(N'.[1]', N'NVARCHAR(MAX)'); /* Build the select statements to return the data after CTE declarations */ @@ -632,8 +633,8 @@ FROM (VALUES ) SortOptions(Sortorder,Aliasname,Columnname) WHERE CASE /* for spills and memory grant sorts make sure the underlying columns exist in the DMV otherwise do not include them */ - WHEN (@IncludeMemoryGrants = 0 OR @IncludeMemoryGrants IS NULL) AND [SortOptions].[Sortorder] = N'memory grant' THEN NULL - WHEN (@IncludeSpills = 0 OR @IncludeSpills IS NULL) AND [SortOptions].[Sortorder] = N'spills' THEN NULL + WHEN (@IncludeMemoryGrants = 0 OR @IncludeMemoryGrants IS NULL) AND ([SortOptions].[Sortorder] = N'memory grant' OR [SortOptions].[Sortorder] = N'all') THEN NULL + WHEN (@IncludeSpills = 0 OR @IncludeSpills IS NULL) AND ([SortOptions].[Sortorder] = N'spills' OR [SortOptions].[Sortorder] = N'all') THEN NULL ELSE [SortOptions].[Sortorder] END = ISNULL(NULLIF(@BlitzCacheSortorder,N'all'),[SortOptions].[Sortorder]) FOR XML PATH(N''), TYPE).value(N'.[1]', N'NVARCHAR(MAX)'),1,11,N'') From 0da7584bd28135105839c0f5dc1f5a589eed233d Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Sat, 23 Jan 2021 06:36:51 +0000 Subject: [PATCH 081/662] #2766 sp_Blitz trace flags Add more trace flags to the list. Closes #2766. --- sp_Blitz.sql | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index 0bc13b3e8..a8d9c86af 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -7741,21 +7741,29 @@ IF @ProductVersionMajor >= 10 SELECT 74 AS CheckID , 200 AS Priority , 'Informational' AS FindingsGroup , - 'TraceFlag On' AS Finding , + 'Trace Flag On' AS Finding , CASE WHEN [T].[TraceFlag] = '834' AND @ColumnStoreIndexesInUse = 1 THEN 'https://support.microsoft.com/en-us/kb/3210239' ELSE'https://www.BrentOzar.com/go/traceflags/' END AS URL , 'Trace flag ' + - CASE WHEN [T].[TraceFlag] = '2330' THEN ' 2330 enabled globally. Using this trace Flag disables missing index requests!' - WHEN [T].[TraceFlag] = '1211' THEN ' 1211 enabled globally. Using this Trace Flag disables lock escalation when you least expect it. No Bueno!' - WHEN [T].[TraceFlag] = '1224' THEN ' 1224 enabled globally. Using this Trace Flag disables lock escalation based on the number of locks being taken. You shouldn''t have done that, Dave.' - WHEN [T].[TraceFlag] = '652' THEN ' 652 enabled globally. Using this Trace Flag disables pre-fetching during index scans. If you hate slow queries, you should turn that off.' - WHEN [T].[TraceFlag] = '661' THEN ' 661 enabled globally. Using this Trace Flag disables ghost record removal. Who you gonna call? No one, turn that thing off.' - WHEN [T].[TraceFlag] = '1806' THEN ' 1806 enabled globally. Using this Trace Flag disables Instant File Initialization. I question your sanity.' - WHEN [T].[TraceFlag] = '3505' THEN ' 3505 enabled globally. Using this Trace Flag disables Checkpoints. Probably not the wisest idea.' - WHEN [T].[TraceFlag] = '8649' THEN ' 8649 enabled globally. Using this Trace Flag drops cost threshold for parallelism down to 0. I hope this is a dev server.' - WHEN [T].[TraceFlag] = '834' AND @ColumnStoreIndexesInUse = 1 THEN ' 834 is enabled globally. Using this Trace Flag with Columnstore Indexes is not a great idea.' - WHEN [T].[TraceFlag] = '8017' AND (CAST(SERVERPROPERTY('Edition') AS NVARCHAR(1000)) LIKE N'%Express%') THEN ' 8017 is enabled globally, which is the default for express edition.' - WHEN [T].[TraceFlag] = '8017' AND (CAST(SERVERPROPERTY('Edition') AS NVARCHAR(1000)) NOT LIKE N'%Express%') THEN ' 8017 is enabled globally. Using this Trace Flag disables creation schedulers for all logical processors. Not good.' + CASE WHEN [T].[TraceFlag] = '652' THEN '652 enabled globally, which disables pre-fetching during index scans. This is usually a very bad idea.' + WHEN [T].[TraceFlag] = '661' THEN '661 enabled globally, which disables ghost record removal, causing the database to grow in size. This is usually a very bad idea.' + WHEN [T].[TraceFlag] = '834' AND @ColumnStoreIndexesInUse = 1 THEN '834 is enabled globally, but you also have columnstore indexes. That combination is not recommended by Microsoft.' + WHEN [T].[TraceFlag] = '1117' THEN '1117 enabled globally, which grows all files in a filegroup at the same time.' + WHEN [T].[TraceFlag] = '1118' THEN '1118 enabled globally, which tries to reduce SGAM waits.' + WHEN [T].[TraceFlag] = '1211' THEN '1211 enabled globally, which disables lock escalation when you least expect it. This is usually a very bad idea.' + WHEN [T].[TraceFlag] = '1222' THEN '1222 enabled globally, which captures deadlock graphs in the error log.' + WHEN [T].[TraceFlag] = '1224' THEN '1224 enabled globally, which disables lock escalation until the server has memory pressure. This is usually a very bad idea.' + WHEN [T].[TraceFlag] = '1806' THEN '1806 enabled globally, which disables Instant File Initialization, causing restores and file growths to take longer. This is usually a very bad idea.' + WHEN [T].[TraceFlag] = '2330' THEN '2330 enabled globally, which disables missing index requests. This is usually a very bad idea.' + WHEN [T].[TraceFlag] = '2371' THEN '2371 enabled globally, which changes the auto update stats threshold.' + WHEN [T].[TraceFlag] = '3023' THEN '3023 enabled globally, which performs checksums by default when doing database backups.' + WHEN [T].[TraceFlag] = '3226' THEN '3226 enabled globally, which keeps the event log clean by not reporting successful backups.' + WHEN [T].[TraceFlag] = '3505' THEN '3505 enabled globally, which disables Checkpoints. This is usually a very bad idea.' + WHEN [T].[TraceFlag] = '4199' THEN '4199 enabled globally, which enables non-default Query Optimizer fixes, changing query plans from the default behaviors.' + WHEN [T].[TraceFlag] = '8048' THEN '8048 enabled globally, which tries to reduce CMEMTHREAD waits on servers with a lot of logical processors.' + WHEN [T].[TraceFlag] = '8017' AND (CAST(SERVERPROPERTY('Edition') AS NVARCHAR(1000)) LIKE N'%Express%') THEN '8017 is enabled globally, but this is the default for Express Edition.' + WHEN [T].[TraceFlag] = '8017' AND (CAST(SERVERPROPERTY('Edition') AS NVARCHAR(1000)) NOT LIKE N'%Express%') THEN '8017 is enabled globally, which disables the creation of schedulers for all logical processors.' + WHEN [T].[TraceFlag] = '8649' THEN '8649 enabled globally, which cost threshold for parallelism down to 0. This is usually a very bad idea.' ELSE [T].[TraceFlag] + ' is enabled globally.' END AS Details FROM #TraceStatus T; From 5d478ee9d234af2931e2a074ca23176a4ab4372e Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Sun, 24 Jan 2021 05:05:43 +0000 Subject: [PATCH 082/662] #2768 sp_BlitzIndex: sort aggressive indexes By lock wait time descending. Closes #2768. --- sp_BlitzIndex.sql | 63 +++-------------------------------------------- 1 file changed, 3 insertions(+), 60 deletions(-) diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index 4c503f473..1fb634e2b 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -2922,7 +2922,7 @@ BEGIN; --Aggressive Indexes: Check_id 10-19 ---------------------------------------- - RAISERROR(N'check_id 11: Total lock wait time > 5 minutes (row + page) with long average waits', 0,1) WITH NOWAIT; + RAISERROR(N'check_id 11: Total lock wait time > 5 minutes (row + page)', 0,1) WITH NOWAIT; INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, secret_columns, index_usage_summary, index_size_summary ) SELECT 11 AS check_id, @@ -2951,7 +2951,7 @@ BEGIN; WHEN 9 THEN N'Indexes' ELSE N'Over-Indexing' END AS findings_group, - N'Total lock wait time > 5 minutes (row + page) with long average waits' AS finding, + N'Total lock wait time > 5 minutes (row + page)' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/AggressiveIndexes' AS URL, (i.db_schema_object_indexid + N': ' + @@ -2974,67 +2974,10 @@ BEGIN; FROM #IndexSanity AS i JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id WHERE (total_row_lock_wait_in_ms + total_page_lock_wait_in_ms) > 300000 - AND (sz.avg_page_lock_wait_in_ms + sz.avg_row_lock_wait_in_ms) > 5000 GROUP BY i.index_sanity_id, [database_name], i.db_schema_object_indexid, sz.index_lock_wait_summary, i.index_definition, i.secret_columns, i.index_usage_summary, sz.index_size_summary, sz.index_sanity_id - ORDER BY 4, [database_name], 8 + ORDER BY SUM(total_row_lock_wait_in_ms + total_page_lock_wait_in_ms) DESC, 4, [database_name], 8 OPTION ( RECOMPILE ); - RAISERROR(N'check_id 12: Total lock wait time > 5 minutes (row + page) with short average waits', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 12 AS check_id, - i.index_sanity_id, - 70 AS Priority, - N'Aggressive ' - + CASE COALESCE((SELECT SUM(1) - FROM #IndexSanity iMe - INNER JOIN #IndexSanity iOthers - ON iMe.database_id = iOthers.database_id - AND iMe.object_id = iOthers.object_id - AND iOthers.index_id > 1 - WHERE i.index_sanity_id = iMe.index_sanity_id - AND iOthers.is_hypothetical = 0 - AND iOthers.is_disabled = 0 - ), 0) - WHEN 0 THEN N'Under-Indexing' - WHEN 1 THEN N'Under-Indexing' - WHEN 2 THEN N'Under-Indexing' - WHEN 3 THEN N'Under-Indexing' - WHEN 4 THEN N'Indexes' - WHEN 5 THEN N'Indexes' - WHEN 6 THEN N'Indexes' - WHEN 7 THEN N'Indexes' - WHEN 8 THEN N'Indexes' - WHEN 9 THEN N'Indexes' - ELSE N'Over-Indexing' - END AS findings_group, - N'Total lock wait time > 5 minutes (row + page) with short average waits' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/AggressiveIndexes' AS URL, - (i.db_schema_object_indexid + N': ' + - sz.index_lock_wait_summary + N' NC indexes on table: ') COLLATE DATABASE_DEFAULT + - CAST(COALESCE((SELECT SUM(1) - FROM #IndexSanity iMe - INNER JOIN #IndexSanity iOthers - ON iMe.database_id = iOthers.database_id - AND iMe.object_id = iOthers.object_id - AND iOthers.index_id > 1 - WHERE i.index_sanity_id = iMe.index_sanity_id - AND iOthers.is_hypothetical = 0 - AND iOthers.is_disabled = 0 - ),0) - AS NVARCHAR(30)) AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - sz.index_size_summary - FROM #IndexSanity AS i - JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id - WHERE (total_row_lock_wait_in_ms + total_page_lock_wait_in_ms) > 300000 - AND (sz.avg_page_lock_wait_in_ms + sz.avg_row_lock_wait_in_ms) < 5000 - GROUP BY i.index_sanity_id, [database_name], i.db_schema_object_indexid, sz.index_lock_wait_summary, i.index_definition, i.secret_columns, i.index_usage_summary, sz.index_size_summary, sz.index_sanity_id - ORDER BY 4, [database_name], 8 - OPTION ( RECOMPILE ); ---------------------------------------- From bbda60f50dadb7e934c3a93dcf4f09e39e47277a Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Sun, 24 Jan 2021 13:45:01 +0000 Subject: [PATCH 083/662] #2770 sp_BlitzFirst compilations threshold Was looking for a minimum of 1,000 batch requests per second. Lowered that to 10 compilations, or more compilations than batch requests. Closes #2770. --- sp_BlitzFirst.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sp_BlitzFirst.sql b/sp_BlitzFirst.sql index 4ba588adb..5e39915a3 100644 --- a/sp_BlitzFirst.sql +++ b/sp_BlitzFirst.sql @@ -2859,7 +2859,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, WHERE ps.Pass = 2 AND ps.object_name = @ServiceName + ':SQL Statistics' AND ps.counter_name = 'Batch Requests/sec' - AND ps.value_delta > (1000 * @Seconds) /* Ignore servers sitting idle */ + AND (psComp.value_delta > (10 * @Seconds) OR psComp.value_delta > ps.value_delta) /* Either doing 10 compilations per second, or more compilations than queries */ AND (psComp.value_delta * 10) > ps.value_delta; /* Compilations are more than 10% of batch requests per second */ /* Query Problems - Re-Compilations/Sec High - CheckID 16 */ @@ -2885,7 +2885,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, WHERE ps.Pass = 2 AND ps.object_name = @ServiceName + ':SQL Statistics' AND ps.counter_name = 'Batch Requests/sec' - AND ps.value_delta > (1000 * @Seconds) /* Ignore servers sitting idle */ + AND (psComp.value_delta > (10 * @Seconds) OR psComp.value_delta > ps.value_delta) /* Either doing 10 recompilations per second, or more recompilations than queries */ AND (psComp.value_delta * 10) > ps.value_delta; /* Recompilations are more than 10% of batch requests per second */ /* Table Problems - Forwarded Fetches/Sec High - CheckID 29 */ From e496b5f9bfd95f0685de46e0bd25146f8374529b Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Sun, 24 Jan 2021 14:26:39 +0000 Subject: [PATCH 084/662] #2772 sp_BlitzCache recent compilations Removing minimum threshold when sort order is recent compilations. Closes #2772. --- sp_BlitzCache.sql | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sp_BlitzCache.sql b/sp_BlitzCache.sql index 717168898..30a937ddc 100644 --- a/sp_BlitzCache.sql +++ b/sp_BlitzCache.sql @@ -2337,7 +2337,7 @@ BEGIN WHEN N'writes' THEN N'AND total_logical_writes > 0' WHEN N'duration' THEN N'AND total_elapsed_time > 0' WHEN N'executions' THEN N'AND execution_count > 0' - WHEN N'compiles' THEN N'AND (age_minutes + age_minutes_lifetime) > 0' + /* WHEN N'compiles' THEN N'AND (age_minutes + age_minutes_lifetime) > 0' BGO 2021-01-24 commenting out for https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2772 */ WHEN N'memory grant' THEN N'AND max_grant_kb > 0' WHEN N'unused grant' THEN N'AND max_grant_kb > 0' WHEN N'spills' THEN N'AND max_spills > 0' @@ -2352,6 +2352,7 @@ BEGIN WHEN COALESCE(age_minutes, age_minutes_lifetime, 0) = 0 THEN 0 ELSE CAST((1.00 * execution_count / COALESCE(age_minutes, age_minutes_lifetime)) AS money) END > 0' + ELSE N' /* No minimum threshold set */ ' END; SET @sql += REPLACE(@body_where, 'cached_time', 'creation_time') ; From f16ff0d9c08c7d37c96158d45b682addd9a96f08 Mon Sep 17 00:00:00 2001 From: Erik Darling Date: Wed, 27 Jan 2021 12:58:23 -0500 Subject: [PATCH 085/662] Check for >25% runnable queries Updated documentation and sp_BlitzFirst. Blog post is scheduled for February 4th, so that link won't work yet. --- .../sp_BlitzFirst_Checks_by_Priority.md | 5 +- sp_BlitzFirst.sql | 90 +++++++++++++++++++ 2 files changed, 93 insertions(+), 2 deletions(-) diff --git a/Documentation/sp_BlitzFirst_Checks_by_Priority.md b/Documentation/sp_BlitzFirst_Checks_by_Priority.md index 1bb54e753..292913426 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: 46 -If you want to add a new check, start at 47 +CURRENT HIGH CHECKID: 47 +If you want to add a new check, start at 48 | Priority | FindingsGroup | Finding | URL | CheckID | |----------|---------------------------------|---------------------------------------|-------------------------------------------------|----------| @@ -34,6 +34,7 @@ If you want to add a new check, start at 47 | 50 | Query Problems | Plan Cache Erased Recently | https://BrentOzar.com/go/freeproccache | 7 | | 50 | Query Problems | Re-Compilations/Sec High | https://BrentOzar.com/go/recompile | 16 | | 50 | Query Problems | Statistics Updated Recently | https://BrentOzar.com/go/stats | 44 | +| 50 | Query Problems | High Percentage Of Runnable Queries | https://erikdarlingdata.com/go/RunnableQueue/ | 47 | | 50 | Server Performance | High CPU Utilization | https://BrentOzar.com/go/cpu | 24 | | 50 | Server Performance | High CPU Utilization - Non SQL Processes | https://BrentOzar.com/go/cpu | 28 | | 50 | Server Performance | Slow Data File Reads | https://BrentOzar.com/go/slow | 11 | diff --git a/sp_BlitzFirst.sql b/sp_BlitzFirst.sql index 5e39915a3..c2b721508 100644 --- a/sp_BlitzFirst.sql +++ b/sp_BlitzFirst.sql @@ -1757,6 +1757,51 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, WHERE r.status = 'rollback'; END + IF @Seconds > 0 + BEGIN + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) + SELECT + 47 AS CheckId, + 50 AS Priority, + 'Query Problems' AS FindingsGroup, + 'High Percentage Of Runnable Queries' AS Finding, + 'https://erikdarlingdata.com/go/RunnableQueue/' AS URL, + 'On the ' + + CASE WHEN y.pass = 1 + THEN '1st' + ELSE '2nd' + END + + ' pass, ' + + RTRIM(y.runnable_pct) + + '% of your queries were waiting to get on a CPU to run. ' + + ' This can indicate CPU pressure.' + FROM + ( + SELECT + 1 AS pass, + x.total, + x.runnable, + CONVERT(decimal(5,2), + ( + x.runnable / + (1. * NULLIF(x.total, 0)) + ) + ) * 100. AS runnable_pct + FROM + ( + SELECT + COUNT_BIG(*) AS total, + SUM(CASE WHEN status = 'runnable' + THEN 1 + ELSE 0 + END) AS runnable + FROM sys.dm_exec_requests + WHERE session_id > 50 + ) AS x + ) AS y + WHERE y.runnable_pct > 20.; + END + /* Server Performance - Too Much Free Memory - CheckID 34 */ IF (@Debug = 1) BEGIN @@ -3128,6 +3173,51 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, CROSS JOIN waits2; END; + IF @Seconds > 0 + BEGIN + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) + SELECT + 47 AS CheckId, + 50 AS Priority, + 'Query Problems' AS FindingsGroup, + 'High Percentage Of Runnable Queries' AS Finding, + 'https://erikdarlingdata.com/go/RunnableQueue/' AS URL, + 'On the ' + + CASE WHEN y.pass = 1 + THEN '1st' + ELSE '2nd' + END + + ' pass, ' + + RTRIM(y.runnable_pct) + + '% of your queries were waiting to get on a CPU to run. ' + + ' This can indicate CPU pressure.' + FROM + ( + SELECT + 2 AS pass, + x.total, + x.runnable, + CONVERT(decimal(5,2), + ( + x.runnable / + (1. * NULLIF(x.total, 0)) + ) + ) * 100. AS runnable_pct + FROM + ( + SELECT + COUNT_BIG(*) AS total, + SUM(CASE WHEN status = 'runnable' + THEN 1 + ELSE 0 + END) AS runnable + FROM sys.dm_exec_requests + WHERE session_id > 50 + ) AS x + ) AS y + WHERE y.runnable_pct > 20.; + END + /* If we're waiting 30+ seconds, run these checks at the end. We get this data from the ring buffers, and it's only updated once per minute, so might as well get it now - whereas if we're checking 30+ seconds, it might get updated by the From 5ee7de95ddff2fc8d0b2bf84faab7ffdfbb990c7 Mon Sep 17 00:00:00 2001 From: AdeDBA <33764798+Adedba@users.noreply.github.com> Date: Fri, 29 Jan 2021 14:49:05 +0000 Subject: [PATCH 086/662] Added Maxdop control #2713 - Removed default for OutputDatabaseName parameter. Added Maxdop parameter which defaults to a 1, this uses the option maxdop query hint to control parallelism per query so that there is some control at least. --- sp_BlitzAnalysis.sql | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/sp_BlitzAnalysis.sql b/sp_BlitzAnalysis.sql index 47a4ec2e7..d276b53ed 100644 --- a/sp_BlitzAnalysis.sql +++ b/sp_BlitzAnalysis.sql @@ -11,7 +11,7 @@ ALTER PROCEDURE [dbo].[sp_BlitzAnalysis] ( @Help TINYINT = 0, @FromDate DATETIMEOFFSET(7) = NULL, @ToDate DATETIMEOFFSET(7) = NULL, -@OutputDatabaseName NVARCHAR(256) = N'DBA', +@OutputDatabaseName NVARCHAR(256), @OutputSchemaName NVARCHAR(256) = N'dbo', @OutputTableNameBlitzFirst NVARCHAR(256) = N'BlitzFirst', /**/ @OutputTableNameFileStats NVARCHAR(256) = N'BlitzFirst_FileStats',/**/ @@ -30,12 +30,13 @@ ALTER PROCEDURE [dbo].[sp_BlitzAnalysis] ( @VersionDate DATETIME = NULL, @VersionCheckMode BIT = 0, @BringThePain BIT = 0, +@Maxdop INT = 1, @Debug BIT = 0 ) AS SET NOCOUNT ON; -SELECT @Version = '1.000', @VersionDate = '20210110'; +SELECT @Version = '1.000', @VersionDate = '20210129'; IF(@VersionCheckMode = 1) BEGIN @@ -118,6 +119,18 @@ BEGIN RETURN; END +/* Set @Maxdop to 1 if NULL was passed in */ +IF (@Maxdop IS NULL) +BEGIN + SET @Maxdop = 1; +END + +/* iF @Maxdop is set higher than the core count just set it to 0 */ +IF (@Maxdop > (SELECT CAST(cpu_count AS INT) FROM sys.dm_os_sys_info)) +BEGIN + SET @Maxdop = 0; +END + /* We need to check if your SQL version has memory grant and spills columns in sys.dm_exec_query_stats */ SELECT @IncludeMemoryGrants = CASE @@ -227,7 +240,7 @@ AND [Priority] BETWEEN 1 AND @MaxBlitzFirstPriority AND [CheckDate] BETWEEN @FromDate AND @ToDate AND [CheckID] > -1 ORDER BY CheckDate ASC,[Priority] ASC -OPTION (RECOMPILE);'; +OPTION (RECOMPILE, MAXDOP '+CAST(@Maxdop AS NVARCHAR(2))+N');'; RAISERROR('Getting BlitzFirst info from %s',0,0,@FullOutputTableNameBlitzFirst) WITH NOWAIT; @@ -302,7 +315,7 @@ WHERE [WaitsRank] <= @WaitStatsTop ORDER BY [CheckDate] ASC, [wait_time_ms_delta] DESC -OPTION(RECOMPILE);' +OPTION(RECOMPILE, MAXDOP '+CAST(@Maxdop AS NVARCHAR(2))+N');' RAISERROR('Getting wait stats info from %s',0,0,@FullOutputTableNameWaitStats) WITH NOWAIT; @@ -369,7 +382,7 @@ GROUP BY LEFT([PhysicalName],LEN([PhysicalName])-CHARINDEX(''\'',REVERSE([PhysicalName]))+1) ORDER BY [CheckDate] ASC -OPTION (RECOMPILE);' +OPTION (RECOMPILE, MAXDOP '+CAST(@Maxdop AS NVARCHAR(2))+N');' RAISERROR('Getting FileStats info from %s',0,0,@FullOutputTableNameFileStats) WITH NOWAIT; @@ -419,7 +432,8 @@ FROM '+@FullOutputTableNamePerfmonStats+N' WHERE CheckDate BETWEEN @FromDate AND @ToDate ORDER BY [CheckDate] ASC, - [counter_name] ASC;' + [counter_name] ASC +OPTION (RECOMPILE, MAXDOP '+CAST(@Maxdop AS NVARCHAR(2))+N');' RAISERROR('Getting Perfmon info from %s',0,0,@FullOutputTableNamePerfmonStats) WITH NOWAIT; @@ -649,7 +663,7 @@ SET @Sql += @NewLine /* Append OPTION(RECOMPILE) complete the statement */ SET @Sql += @NewLine -+'OPTION(RECOMPILE);'; ++'OPTION(RECOMPILE, MAXDOP '+CAST(@Maxdop AS NVARCHAR(2))+N');'; RAISERROR('Getting BlitzCache info from %s',0,0,@FullOutputTableNameBlitzCache) WITH NOWAIT; @@ -794,7 +808,8 @@ SELECT [ServerName] FROM '+@FullOutputTableNameBlitzWho+N' WHERE [ServerName] = @Servername AND [CheckDate] BETWEEN @FromDate AND @ToDate - ORDER BY [CheckDate] ASC;'; + ORDER BY [CheckDate] ASC + OPTION (RECOMPILE, MAXDOP '+CAST(@Maxdop AS NVARCHAR(2))+N');'; RAISERROR('Getting BlitzWho info from %s',0,0,@FullOutputTableNameBlitzWho) WITH NOWAIT; From 1e73a97f2cba82240875a3e8854f8f3b329e74e4 Mon Sep 17 00:00:00 2001 From: AdeDBA <33764798+Adedba@users.noreply.github.com> Date: Sat, 30 Jan 2021 21:01:56 +0000 Subject: [PATCH 087/662] Added Database name filtering #2713 Added Database name filtering --- sp_BlitzAnalysis.sql | 77 ++++++++++++++++++++++++++++++++------------ 1 file changed, 57 insertions(+), 20 deletions(-) diff --git a/sp_BlitzAnalysis.sql b/sp_BlitzAnalysis.sql index d276b53ed..3c6f86483 100644 --- a/sp_BlitzAnalysis.sql +++ b/sp_BlitzAnalysis.sql @@ -13,13 +13,14 @@ ALTER PROCEDURE [dbo].[sp_BlitzAnalysis] ( @ToDate DATETIMEOFFSET(7) = NULL, @OutputDatabaseName NVARCHAR(256), @OutputSchemaName NVARCHAR(256) = N'dbo', -@OutputTableNameBlitzFirst NVARCHAR(256) = N'BlitzFirst', /**/ -@OutputTableNameFileStats NVARCHAR(256) = N'BlitzFirst_FileStats',/**/ +@OutputTableNameBlitzFirst NVARCHAR(256) = N'BlitzFirst', +@OutputTableNameFileStats NVARCHAR(256) = N'BlitzFirst_FileStats', @OutputTableNamePerfmonStats NVARCHAR(256) = N'BlitzFirst_PerfmonStats', -@OutputTableNameWaitStats NVARCHAR(256) = N'BlitzFirst_WaitStats', /**/ -@OutputTableNameBlitzCache NVARCHAR(256) = N'BlitzCache',/**/ +@OutputTableNameWaitStats NVARCHAR(256) = N'BlitzFirst_WaitStats', +@OutputTableNameBlitzCache NVARCHAR(256) = N'BlitzCache', @OutputTableNameBlitzWho NVARCHAR(256) = N'BlitzWho', @Servername NVARCHAR(128) = @@SERVERNAME, +@Databasename NVARCHAR(128) = NULL, @BlitzCacheSortorder NVARCHAR(20) = N'cpu', @MaxBlitzFirstPriority INT = 249, @ReadLatencyThreshold INT = 100, @@ -36,7 +37,7 @@ ALTER PROCEDURE [dbo].[sp_BlitzAnalysis] ( AS SET NOCOUNT ON; -SELECT @Version = '1.000', @VersionDate = '20210129'; +SELECT @Version = '1.000', @VersionDate = '20210130'; IF(@VersionCheckMode = 1) BEGIN @@ -56,10 +57,12 @@ BEGIN @OutputTableNamePerfmonStats = N''BlitzFirst_PerfmonStats'', /* Table name where you are storing sp_BlitzFirst Perfmon output, Set to NULL to ignore */ @OutputTableNameWaitStats = N''BlitzFirst_WaitStats'', /* Table name where you are storing sp_BlitzFirst Wait stats output, Set to NULL to ignore */ @OutputTableNameBlitzCache = N''BlitzCache'', /* Table name where you are storing sp_BlitzCache output, Set to NULL to ignore */ -@OutputTableNameBlitzWho = N''BlitzWho'', /* Table name where you are storing sp_BlitzWho output, Set to NULL to ignore */ +@OutputTableNameBlitzWho = N''BlitzWho'', /* Table name where you are storing sp_BlitzWho output, Set to NULL to ignore */ +@Databasename = NULL, /* Filters results for BlitzCache, FileStats (will also include tempdb), BlitzWho. Leave as NULL for all databases */ @MaxBlitzFirstPriority = 249, /* Max priority to include in the results */ @BlitzCacheSortorder = ''cpu'', /* Accepted values ''all'' ''cpu'' ''reads'' ''writes'' ''duration'' ''executions'' ''memory grant'' ''spills'' */ @WaitStatsTop = 3, /* Controls the top for wait stats only */ +@Maxdop = 1, /* Control the degree of parallelism that the queries within this proc can use if they want to*/ @Debug = 0; /* Show sp_BlitzAnalysis SQL commands in the messages tab as they execute */ /* @@ -376,7 +379,14 @@ SUM([megabytes_written]) AS [megabytes_written] FROM '+@FullOutputTableNameFileStats+N' WHERE [ServerName] = @Servername AND [CheckDate] BETWEEN @FromDate AND @ToDate -GROUP BY +' ++CASE + WHEN @Databasename IS NOT NULL THEN N'AND [DatabaseName] IN (N''tempdb'',@Databasename) +' + ELSE N' +' +END ++N'GROUP BY [ServerName], [CheckDate], LEFT([PhysicalName],LEN([PhysicalName])-CHARINDEX(''\'',REVERSE([PhysicalName]))+1) @@ -410,11 +420,13 @@ BEGIN N'@FromDate DATETIMEOFFSET(7), @ToDate DATETIMEOFFSET(7), @Servername NVARCHAR(128), + @Databasename NVARCHAR(128), @ReadLatencyThreshold INT, @WriteLatencyThreshold INT', @FromDate=@FromDate, @ToDate=@ToDate, - @Servername=@Servername, + @Servername=@Servername, + @Databasename = @Databasename, @ReadLatencyThreshold = @ReadLatencyThreshold, @WriteLatencyThreshold = @WriteLatencyThreshold; END @@ -477,7 +489,14 @@ FROM ' +@NewLine +N'WHERE [ServerName] = @Servername AND [CheckDate] BETWEEN @FromDate AND @ToDate -)'; +' ++CASE + WHEN @Databasename IS NOT NULL THEN N'AND [DatabaseName] = @Databasename +' + ELSE N' +' +END ++N')'; /* Append additional CTEs based on sortorder */ SET @Sql += ( @@ -596,15 +615,22 @@ CROSS APPLY ( FROM '+@FullOutputTableNameBlitzCache+N' AS '+[SortOptions].[Aliasname]+N' WHERE [ServerName] = @Servername AND [CheckDate] BETWEEN @FromDate AND @ToDate - AND ['+[SortOptions].[Aliasname]+N'].[CheckDate] = [CheckDates].[CheckDate]' + AND ['+[SortOptions].[Aliasname]+N'].[CheckDate] = [CheckDates].[CheckDate] + ' + +CASE + WHEN @Databasename IS NOT NULL THEN N'AND ['+[SortOptions].[Aliasname]+N'].[DatabaseName] = @Databasename + ' + ELSE N' + ' + END +CASE - WHEN [Sortorder] = N'cpu' THEN N' AND [TotalCPU] > 0' - WHEN [Sortorder] = N'reads' THEN N' AND [TotalReads] > 0' - WHEN [Sortorder] = N'writes' THEN N' AND [TotalWrites] > 0' - WHEN [Sortorder] = N'duration' THEN N' AND [TotalDuration] > 0' - WHEN [Sortorder] = N'executions' THEN N' AND [ExecutionCount] > 0' - WHEN [Sortorder] = N'memory grant' THEN N' AND [MaxGrantKB] > 0' - WHEN [Sortorder] = N'spills' THEN N' AND [MaxSpills] > 0' + WHEN [Sortorder] = N'cpu' THEN N'AND [TotalCPU] > 0' + WHEN [Sortorder] = N'reads' THEN N'AND [TotalReads] > 0' + WHEN [Sortorder] = N'writes' THEN N'AND [TotalWrites] > 0' + WHEN [Sortorder] = N'duration' THEN N'AND [TotalDuration] > 0' + WHEN [Sortorder] = N'executions' THEN N'AND [ExecutionCount] > 0' + WHEN [Sortorder] = N'memory grant' THEN N'AND [MaxGrantKB] > 0' + WHEN [Sortorder] = N'spills' THEN N'AND [MaxSpills] > 0' ELSE N'' END +N' @@ -696,10 +722,12 @@ ELSE /* Table exists then run the query */ BEGIN EXEC sp_executesql @Sql, N'@Servername NVARCHAR(128), + @Databasename NVARCHAR(128), @BlitzCacheSortorder NVARCHAR(20), @FromDate DATETIMEOFFSET(7), @ToDate DATETIMEOFFSET(7)', @Servername = @Servername, + @Databasename = @Databasename, @BlitzCacheSortorder = @BlitzCacheSortorder, @FromDate = @FromDate, @ToDate = @ToDate; @@ -808,7 +836,14 @@ SELECT [ServerName] FROM '+@FullOutputTableNameBlitzWho+N' WHERE [ServerName] = @Servername AND [CheckDate] BETWEEN @FromDate AND @ToDate - ORDER BY [CheckDate] ASC + ' + +CASE + WHEN @Databasename IS NOT NULL THEN N'AND [database_name] = @Databasename + ' + ELSE N' + ' + END ++N'ORDER BY [CheckDate] ASC OPTION (RECOMPILE, MAXDOP '+CAST(@Maxdop AS NVARCHAR(2))+N');'; RAISERROR('Getting BlitzWho info from %s',0,0,@FullOutputTableNameBlitzWho) WITH NOWAIT; @@ -836,10 +871,12 @@ BEGIN EXEC sp_executesql @Sql, N'@FromDate DATETIMEOFFSET(7), @ToDate DATETIMEOFFSET(7), - @Servername NVARCHAR(128)', + @Servername NVARCHAR(128), + @Databasename NVARCHAR(128)', @FromDate=@FromDate, @ToDate=@ToDate, - @Servername=@Servername; + @Servername=@Servername, + @Databasename = @Databasename; END From 66b71988b62fe1086d5efb164cfadede73f0d1a9 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Tue, 2 Feb 2021 08:09:13 +0000 Subject: [PATCH 088/662] #2775 sp_AllNightLog rpo/rto Was hard-coded to 5 minutes, now pulls from the config table. Closes #2775. --- sp_AllNightLog.sql | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sp_AllNightLog.sql b/sp_AllNightLog.sql index 00aeafbfc..e7e10312a 100644 --- a/sp_AllNightLog.sql +++ b/sp_AllNightLog.sql @@ -349,7 +349,7 @@ Pollster: SELECT 1 FROM msdbCentral.dbo.backup_worker bw WITH (READPAST) WHERE bw.last_log_backup_finish_time < '99991231' - AND bw.last_log_backup_start_time < DATEADD(MINUTE, -5, GETDATE()) + AND bw.last_log_backup_start_time < DATEADD(SECOND, (@rpo * -1), GETDATE()) AND EXISTS ( SELECT 1 FROM msdb.dbo.backupset b @@ -369,7 +369,7 @@ Pollster: bw.last_log_backup_start_time = '19000101' FROM msdbCentral.dbo.backup_worker bw WHERE bw.last_log_backup_finish_time < '99991231' - AND bw.last_log_backup_start_time < DATEADD(MINUTE, -5, GETDATE()) + AND bw.last_log_backup_start_time < DATEADD(SECOND, (@rpo * -1), GETDATE()) AND EXISTS ( SELECT 1 FROM msdb.dbo.backupset b @@ -583,7 +583,7 @@ DiskPollster: SELECT 1 FROM msdb.dbo.restore_worker rw WITH (READPAST) WHERE rw.last_log_restore_finish_time < '99991231' - AND rw.last_log_restore_start_time < DATEADD(MINUTE, -5, GETDATE()) + AND rw.last_log_restore_start_time < DATEADD(SECOND, (@rto * -1), GETDATE()) AND EXISTS ( SELECT 1 FROM msdb.dbo.restorehistory r @@ -603,7 +603,7 @@ DiskPollster: rw.last_log_restore_start_time = '19000101' FROM msdb.dbo.restore_worker rw WHERE rw.last_log_restore_finish_time < '99991231' - AND rw.last_log_restore_start_time < DATEADD(MINUTE, -5, GETDATE()) + AND rw.last_log_restore_start_time < DATEADD(SECOND, (@rto * -1), GETDATE()) AND EXISTS ( SELECT 1 FROM msdb.dbo.restorehistory r From 9f1aca734cb6d6fe5f01ea5344295f5d67495670 Mon Sep 17 00:00:00 2001 From: Greg Dodd Date: Tue, 2 Feb 2021 15:53:03 -0500 Subject: [PATCH 089/662] Add Live Parameters and Cached Parameters into BlitzWho --- sp_BlitzWho.sql | 70 +++++++++++++++++++++++++++---------------------- 1 file changed, 39 insertions(+), 31 deletions(-) diff --git a/sp_BlitzWho.sql b/sp_BlitzWho.sql index 97df3c84d..5545d1a1b 100644 --- a/sp_BlitzWho.sql +++ b/sp_BlitzWho.sql @@ -109,11 +109,6 @@ DECLARE @ProductVersion NVARCHAR(128) AND r.statement_start_offset = session_stats.statement_start_offset AND r.statement_end_offset = session_stats.statement_end_offset' ,@QueryStatsXMLselect NVARCHAR(MAX) = N' CAST(COALESCE(qs_live.query_plan, '''') AS XML) AS live_query_plan , ' - ,@QueryStatsParameterSelect NVARCHAR(MAX) = N' STUFF((SELECT DISTINCT N'', '' + Node.Data.value(''(@Column)[1]'', ''NVARCHAR(4000)'') + N'' {'' + Node.Data.value(''(@ParameterDataType)[1]'', ''NVARCHAR(4000)'') + N''}: '' + Node.Data.value(''(@ParameterCompiledValue)[1]'', ''NVARCHAR(4000)'') + N'' (Actual: '' + Node.Data.value(''(@ParameterRuntimeValue)[1]'', ''NVARCHAR(4000)'') + N'')'' - FROM qs_live.query_plan.nodes(''/*:ShowPlanXML/*:BatchSequence/*:Batch/*:Statements/*:StmtSimple/*:QueryPlan/*:ParameterList/*:ColumnReference'') AS Node(Data) - FOR XML PATH('''')), 1,2,'''') - AS Live_Parameter_Info, ' - ,@QueryStatsXMLSQL NVARCHAR(MAX) = N'OUTER APPLY sys.dm_exec_query_statistics_xml(s.session_id) qs_live' ,@ObjectFullName NVARCHAR(2000) ,@OutputTableNameQueryStats_View NVARCHAR(256) ,@LineFeed NVARCHAR(MAX) /* Had to set as MAX up from 10 as it was truncating the view creation*/; @@ -124,21 +119,8 @@ 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) -IF EXISTS (SELECT * FROM sys.all_columns WHERE object_id = OBJECT_ID('sys.dm_exec_query_statistics_xml') AND name = 'query_plan') - BEGIN - SET @QueryStatsXMLselect = N' CAST(COALESCE(qs_live.query_plan, '''') AS XML) AS live_query_plan , '; - SET @QueryStatsParameterSelect = N' STUFF((SELECT DISTINCT N'', '' + Node.Data.value(''(@Column)[1]'', ''NVARCHAR(4000)'') + N'' {'' + Node.Data.value(''(@ParameterDataType)[1]'', ''NVARCHAR(4000)'') + N''}: '' + Node.Data.value(''(@ParameterCompiledValue)[1]'', ''NVARCHAR(4000)'') + N'' (Actual: '' + Node.Data.value(''(@ParameterRuntimeValue)[1]'', ''NVARCHAR(4000)'') + N'')'' - FROM qs_live.query_plan.nodes(''/*:ShowPlanXML/*:BatchSequence/*:Batch/*:Statements/*:StmtSimple/*:QueryPlan/*:ParameterList/*:ColumnReference'') AS Node(Data) - FOR XML PATH('''')), 1,2,'''') - AS Live_Parameter_Info, ' - SET @QueryStatsXMLSQL = N'OUTER APPLY sys.dm_exec_query_statistics_xml(s.session_id) qs_live'; - END - ELSE - BEGIN - SET @QueryStatsXMLselect = N' NULL AS live_query_plan , '; - SET @QueryStatsParameterSelect = N' NULL AS Live_Parameter_Info , ' - SET @QueryStatsXMLSQL = N' '; - END + +SET @QueryStatsXMLselect = N' NULL AS live_query_plan , '; SELECT @OutputTableNameQueryStats_View = QUOTENAME(@OutputTableName + '_Deltas'), @@ -579,7 +561,27 @@ SELECT @BlockingCheck = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; sys2.spid AS session_id, sys2.blocked AS blocking_session_id, sys2.lastwaittype, sys2.waittime, sys2.cpu, sys2.physical_io, sys2.memusage FROM sys.sysprocesses AS sys1 JOIN sys.sysprocesses AS sys2 - ON sys1.spid = sys2.blocked;'; + ON sys1.spid = sys2.blocked; + + + DECLARE @LiveQueryPlans TABLE + ( + Session_Id INT NOT NULL, + Query_Plan XML NOT NULL + ); + + ' + +IF EXISTS (SELECT * FROM sys.all_columns WHERE object_id = OBJECT_ID('sys.dm_exec_query_statistics_xml') AND name = 'query_plan') +BEGIN + SET @BlockingCheck = @BlockingCheck + N' + INSERT INTO @LiveQueryPlans + SELECT s.session_id, query_plan + FROM sys.dm_exec_sessions AS s + CROSS APPLY sys.dm_exec_query_statistics_xml(s.session_id) + WHERE s.session_id <> @@SPID;'; +END + IF @ProductVersionMajor > 9 and @ProductVersionMajor < 11 BEGIN @@ -805,15 +807,13 @@ IF @ProductVersionMajor >= 11 ELSE query_stats.statement_end_offset END - query_stats.statement_start_offset ) / 2 ) + 1), dest.text) AS query_text , - derp.query_plan ,' - + @QueryStatsXMLselect - + N' + derp.query_plan , + CAST(COALESCE(qs_live.query_plan, '''') AS XML) AS live_query_plan , STUFF((SELECT DISTINCT N'', '' + Node.Data.value(''(@Column)[1]'', ''NVARCHAR(4000)'') + N'' {'' + Node.Data.value(''(@ParameterDataType)[1]'', ''NVARCHAR(4000)'') + N''}: '' + Node.Data.value(''(@ParameterCompiledValue)[1]'', ''NVARCHAR(4000)'') FROM derp.query_plan.nodes(''/*:ShowPlanXML/*:BatchSequence/*:Batch/*:Statements/*:StmtSimple/*:QueryPlan/*:ParameterList/*:ColumnReference'') AS Node(Data) FOR XML PATH('''')), 1,2,'''') - AS Cached_Parameter_Info,' - + @QueryStatsParameterSelect - + N' + AS Cached_Parameter_Info, + qs_live.Live_Parameter_Info as Live_Parameter_Info, qmg.query_cost , s.status , CASE @@ -1034,10 +1034,18 @@ IF @ProductVersionMajor >= 11 AND tsu.session_id = r.session_id AND tsu.session_id = s.session_id ) as tempdb_allocations - ' - + @QueryStatsXMLSQL - + - N' + + OUTER APPLY ( + SELECT TOP 1 query_plan, + STUFF((SELECT DISTINCT N'', '' + Node.Data.value(''(@Column)[1]'', ''NVARCHAR(4000)'') + N'' {'' + Node.Data.value(''(@ParameterDataType)[1]'', ''NVARCHAR(4000)'') + N''}: '' + Node.Data.value(''(@ParameterCompiledValue)[1]'', ''NVARCHAR(4000)'') + N'' (Actual: '' + Node.Data.value(''(@ParameterRuntimeValue)[1]'', ''NVARCHAR(4000)'') + N'')'' + FROM q.query_plan.nodes(''/*:ShowPlanXML/*:BatchSequence/*:Batch/*:Statements/*:StmtSimple/*:QueryPlan/*:ParameterList/*:ColumnReference'') AS Node(Data) + FOR XML PATH('''')), 1,2,'''') + AS Live_Parameter_Info + FROM @LiveQueryPlans q + WHERE (s.session_id = q.session_id) + + ) AS qs_live + WHERE s.session_id <> @@SPID AND s.host_name IS NOT NULL ' From b40a914c3c859e2af813a8522af1c3bfe827b19f Mon Sep 17 00:00:00 2001 From: AdeDBA <33764798+Adedba@users.noreply.github.com> Date: Thu, 4 Feb 2021 22:54:14 +0000 Subject: [PATCH 090/662] Remove unused parameters and validate databasename #2713 Remove unused parameter SkipAnalysis, and added validattion for the Outputdatabasename parameter. Added a default of NULL to allow for CheckVersion only mode = 1. --- sp_BlitzAnalysis.sql | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/sp_BlitzAnalysis.sql b/sp_BlitzAnalysis.sql index 3c6f86483..6bf6358fe 100644 --- a/sp_BlitzAnalysis.sql +++ b/sp_BlitzAnalysis.sql @@ -11,7 +11,7 @@ ALTER PROCEDURE [dbo].[sp_BlitzAnalysis] ( @Help TINYINT = 0, @FromDate DATETIMEOFFSET(7) = NULL, @ToDate DATETIMEOFFSET(7) = NULL, -@OutputDatabaseName NVARCHAR(256), +@OutputDatabaseName NVARCHAR(256) = NULL, @OutputSchemaName NVARCHAR(256) = N'dbo', @OutputTableNameBlitzFirst NVARCHAR(256) = N'BlitzFirst', @OutputTableNameFileStats NVARCHAR(256) = N'BlitzFirst_FileStats', @@ -26,9 +26,8 @@ ALTER PROCEDURE [dbo].[sp_BlitzAnalysis] ( @ReadLatencyThreshold INT = 100, @WriteLatencyThreshold INT = 100, @WaitStatsTop TINYINT = 10, -@SkipAnalysis BIT = 0, -@Version VARCHAR(30) = NULL, -@VersionDate DATETIME = NULL, +@Version VARCHAR(30) = NULL OUTPUT, +@VersionDate DATETIME = NULL OUTPUT, @VersionCheckMode BIT = 0, @BringThePain BIT = 0, @Maxdop INT = 1, @@ -37,18 +36,17 @@ ALTER PROCEDURE [dbo].[sp_BlitzAnalysis] ( AS SET NOCOUNT ON; -SELECT @Version = '1.000', @VersionDate = '20210130'; +SELECT @Version = '1.000', @VersionDate = '20210204'; IF(@VersionCheckMode = 1) BEGIN - SELECT @Version AS [Version], @VersionDate AS [VersionDate]; RETURN; END; IF (@Help = 1) BEGIN PRINT 'EXEC sp_BlitzAnalysis -@FromDate = ''20201111 13:30'', +@FromDate = ''20210204 09:00'', @ToDate = NULL, /* NULL will get an hour of data since @FromDate */ @OutputDatabaseName = N''DBA'', /* Specify the database name where where we can find your logged blitz data */ @OutputSchemaName = N''dbo'', /* Specify the schema */ @@ -86,6 +84,13 @@ DECLARE @NewLine NVARCHAR(2) = CHAR(10)+ CHAR(13); DECLARE @IncludeMemoryGrants BIT; DECLARE @IncludeSpills BIT; +/* Validate the database name */ +IF (DB_ID(@OutputDatabaseName) IS NULL) +BEGIN + RAISERROR('Invalid database name provided for parameter @OutputDatabaseName: %s',11,0,@OutputDatabaseName); + RETURN; +END + /* Set fully qualified table names */ SET @FullOutputTableNameBlitzFirst = QUOTENAME(@OutputDatabaseName)+N'.'+QUOTENAME(@OutputSchemaName)+N'.'+QUOTENAME(@OutputTableNameBlitzFirst); SET @FullOutputTableNameFileStats = QUOTENAME(@OutputDatabaseName)+N'.'+QUOTENAME(@OutputSchemaName)+N'.'+QUOTENAME(@OutputTableNameFileStats+N'_Deltas'); From b93cd83573c89bad0766e26094ec07df6fb00339 Mon Sep 17 00:00:00 2001 From: AdeDBA <33764798+Adedba@users.noreply.github.com> Date: Thu, 4 Feb 2021 23:33:35 +0000 Subject: [PATCH 091/662] Fixed ToDate bug when fromDate was NULL #2713 Fixed ToDate bug when fromDate was NULL, if FromDate was null and ToDate had a value the Todate was overwritten with a default value. --- sp_BlitzAnalysis.sql | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/sp_BlitzAnalysis.sql b/sp_BlitzAnalysis.sql index 6bf6358fe..f5ea72013 100644 --- a/sp_BlitzAnalysis.sql +++ b/sp_BlitzAnalysis.sql @@ -159,9 +159,12 @@ BEGIN /* Set FromDate to be an hour ago */ SET @FromDate = DATEADD(HOUR,-1,SYSDATETIMEOFFSET()); - RAISERROR('Setting @ToDate to: Now',0,0) WITH NOWAIT; - /* Get data right up to now */ - SET @ToDate = SYSDATETIMEOFFSET(); + IF (@ToDate IS NULL) + BEGIN + RAISERROR('Setting @ToDate to: Now',0,0) WITH NOWAIT; + /* Get data right up to now */ + SET @ToDate = SYSDATETIMEOFFSET(); + END END IF (@ToDate IS NULL) From cff1e18a1422338b3ac67d653a786935714142c9 Mon Sep 17 00:00:00 2001 From: AdeDBA <33764798+Adedba@users.noreply.github.com> Date: Thu, 4 Feb 2021 23:44:43 +0000 Subject: [PATCH 092/662] Added an OR with CheckDate and start_time for BlitzWho #2713 Replaced CheckDate with start_time for BlitzWho_Deltas lookup as there could be a query that was active during the report period which may have ended outside of the window, start_time ensures we get those queries included too. --- sp_BlitzAnalysis.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sp_BlitzAnalysis.sql b/sp_BlitzAnalysis.sql index f5ea72013..f658e1930 100644 --- a/sp_BlitzAnalysis.sql +++ b/sp_BlitzAnalysis.sql @@ -843,7 +843,7 @@ SELECT [ServerName] ,[statement_end_offset] FROM '+@FullOutputTableNameBlitzWho+N' WHERE [ServerName] = @Servername - AND [CheckDate] BETWEEN @FromDate AND @ToDate + AND ([CheckDate] BETWEEN @FromDate AND @ToDate OR [start_time] BETWEEN CAST(@FromDate AS DATETIME) AND CAST(@ToDate AS DATETIME)) ' +CASE WHEN @Databasename IS NOT NULL THEN N'AND [database_name] = @Databasename From b9cdbf5ad307202aaae18b3e6a9b40a0c6089116 Mon Sep 17 00:00:00 2001 From: AdeDBA <33764798+Adedba@users.noreply.github.com> Date: Fri, 5 Feb 2021 09:33:25 +0000 Subject: [PATCH 093/662] Amended help text #2713 Amended help text --- sp_BlitzAnalysis.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sp_BlitzAnalysis.sql b/sp_BlitzAnalysis.sql index f658e1930..5148bd8e3 100644 --- a/sp_BlitzAnalysis.sql +++ b/sp_BlitzAnalysis.sql @@ -46,8 +46,8 @@ END; IF (@Help = 1) BEGIN PRINT 'EXEC sp_BlitzAnalysis -@FromDate = ''20210204 09:00'', -@ToDate = NULL, /* NULL will get an hour of data since @FromDate */ +@FromDate = NULL, /* Specify a datetime or NULL will get an hour ago */ +@ToDate = NULL, /* Specify a datetime or NULL will get an hour of data since @FromDate */ @OutputDatabaseName = N''DBA'', /* Specify the database name where where we can find your logged blitz data */ @OutputSchemaName = N''dbo'', /* Specify the schema */ @OutputTableNameBlitzFirst = N''BlitzFirst'', /* Table name where you are storing sp_BlitzFirst output, Set to NULL to ignore */ From 4d11b1422056e8cb193718481cfc4773a96b4380 Mon Sep 17 00:00:00 2001 From: AdeDBA <33764798+Adedba@users.noreply.github.com> Date: Mon, 8 Feb 2021 16:11:09 +0000 Subject: [PATCH 094/662] Corrected typos and added some extra comments #2713, #2781 Corrected typos and added some extra comments --- sp_BlitzAnalysis.sql | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/sp_BlitzAnalysis.sql b/sp_BlitzAnalysis.sql index 5148bd8e3..bfe7a06b7 100644 --- a/sp_BlitzAnalysis.sql +++ b/sp_BlitzAnalysis.sql @@ -72,7 +72,7 @@ Additional parameters: RETURN; END -/* Declare all local veriables required */ +/* Declare all local variables required */ DECLARE @FullOutputTableNameBlitzFirst NVARCHAR(1000); DECLARE @FullOutputTableNameFileStats NVARCHAR(1000); DECLARE @FullOutputTableNamePerfmonStats NVARCHAR(1000); @@ -113,7 +113,7 @@ CREATE TABLE #BlitzFirstCounts ( [LastOccurrence] DATETIMEOFFSET(7) NULL ); -/* Vallidate variables and set defaults as required */ +/* Validate variables and set defaults as required */ IF (@BlitzCacheSortorder IS NULL) BEGIN SET @BlitzCacheSortorder = N'cpu'; @@ -123,7 +123,7 @@ SET @BlitzCacheSortorder = LOWER(@BlitzCacheSortorder); IF (@OutputTableNameBlitzCache IS NOT NULL AND @BlitzCacheSortorder NOT IN (N'all',N'cpu',N'reads',N'writes',N'duration',N'executions',N'memory grant',N'spills')) BEGIN - RAISERROR('Invalid sort option specified for @BlitzCacheSortorder, support values are ''all'', ''cpu'', ''reads'', ''writes'', ''duration'', ''executions'', ''memory grant'', ''spills''',11,0) WITH NOWAIT; + RAISERROR('Invalid sort option specified for @BlitzCacheSortorder, supported values are ''all'', ''cpu'', ''reads'', ''writes'', ''duration'', ''executions'', ''memory grant'', ''spills''',11,0) WITH NOWAIT; RETURN; END @@ -182,18 +182,20 @@ BEGIN END END +/* Default to dbo schema if NULL is passed in */ IF (@OutputSchemaName IS NULL) BEGIN SET @OutputSchemaName = 'dbo'; END +/* Prompt the user for @BringThePain = 1 if they are searching a timeframe greater than 4 hours and they are using BlitzCacheSortorder = 'all' */ IF(@BlitzCacheSortorder = 'all' AND DATEDIFF(HOUR,@FromDate,@ToDate) > 4 AND @BringThePain = 0) BEGIN RAISERROR('Wow! hold up now, are you sure you wanna do this? Are sure you want to query over 4 hours of data with @BlitzCacheSortorder set to ''all''? IF you do then set @BringThePain = 1 but I gotta warn you this might hurt a bit!',11,1) WITH NOWAIT; RETURN; END - +/* Output report window information */ SELECT @Servername AS [ServerToReportOn], CAST(1 AS NVARCHAR(20)) + N' - '+ CAST(@MaxBlitzFirstPriority AS NVARCHAR(20)) AS [PrioritesToInclude], @@ -695,7 +697,7 @@ SET @Sql += @NewLine [CheckDate] ASC, [TimeFrameRank] ASC'; -/* Append OPTION(RECOMPILE) complete the statement */ +/* Append OPTION(RECOMPILE, MAXDOP) to complete the statement */ SET @Sql += @NewLine +'OPTION(RECOMPILE, MAXDOP '+CAST(@Maxdop AS NVARCHAR(2))+N');'; From 47c0d155ff4203b9e001e3fe6af797d59eceacb2 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Tue, 9 Feb 2021 04:16:19 +0000 Subject: [PATCH 095/662] #2780 sp_BlitzIndex remove sample plan From missing index DMVs in SQL Server 2019. Closes #2780. --- sp_BlitzIndex.sql | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index 1fb634e2b..3e05b2f6a 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -1641,9 +1641,12 @@ BEGIN TRY AND ColumnNamesWithDataTypes.IndexColumnType = ''Included'' ) AS included_columns_with_data_type ' - IF NOT EXISTS (SELECT * FROM sys.all_objects WHERE name = 'dm_db_missing_index_group_stats_query') + /* Github #2780 BGO Removing SQL Server 2019's new missing index sample plan because it doesn't work yet: + IF NOT EXISTS (SELECT * FROM sys.all_objects WHERE name = 'dm_db_missing_index_group_stats_query') + */ SET @dsql = @dsql + N' , NULL AS sample_query_plan ' - ELSE + /* Github #2780 BGO Removing SQL Server 2019's new missing index sample plan because it doesn't work yet: + ELSE BEGIN SET @dsql = @dsql + N' , sample_query_plan = (SELECT TOP 1 p.query_plan FROM sys.dm_db_missing_index_group_stats gs @@ -1655,6 +1658,8 @@ BEGIN TRY CROSS APPLY sys.dm_exec_query_plan(q2.plan_handle) p WHERE gs.group_handle = gs.group_handle) ' END + */ + SET @dsql = @dsql + N'FROM ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_groups ig JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_details id ON ig.index_handle = id.index_handle From 795baf14322b263fe904ecff7dbe8913544f95e1 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Tue, 9 Feb 2021 04:21:54 +0000 Subject: [PATCH 096/662] #2779 sp_Blitz CHECKDB Now only runs when CheckUserDatabaseObjects = 0. Closes #2779. --- sp_Blitz.sql | 126 +++++++++++++++++++++++++-------------------------- 1 file changed, 63 insertions(+), 63 deletions(-) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index a8d9c86af..cd048bfc8 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -7128,6 +7128,69 @@ IF @ProductVersionMajor >= 10 -- HAVING COUNT(DISTINCT o.object_id) > 0;'; --END; --of Check 220. + /*Check for the last good DBCC CHECKDB date */ + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 68 ) + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 68) WITH NOWAIT; + + EXEC sp_MSforeachdb N'USE [?]; + SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + INSERT #DBCCs + (ParentObject, + Object, + Field, + Value) + EXEC (''DBCC DBInfo() With TableResults, NO_INFOMSGS''); + UPDATE #DBCCs SET DbName = N''?'' WHERE DbName IS NULL OPTION (RECOMPILE);'; + + WITH DB2 + AS ( SELECT DISTINCT + Field , + Value , + DbName + FROM #DBCCs + INNER JOIN sys.databases d ON #DBCCs.DbName = d.name + WHERE Field = 'dbi_dbccLastKnownGood' + AND d.create_date < DATEADD(dd, -14, GETDATE()) + ) + INSERT INTO #BlitzResults + ( CheckID , + DatabaseName , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT 68 AS CheckID , + DB2.DbName AS DatabaseName , + 1 AS PRIORITY , + 'Reliability' AS FindingsGroup , + 'Last good DBCC CHECKDB over 2 weeks old' AS Finding , + 'https://BrentOzar.com/go/checkdb' AS URL , + 'Last successful CHECKDB: ' + + CASE DB2.Value + WHEN '1900-01-01 00:00:00.000' + THEN ' never.' + ELSE DB2.Value + END AS Details + FROM DB2 + WHERE DB2.DbName <> 'tempdb' + AND DB2.DbName NOT IN ( SELECT DISTINCT + DatabaseName + FROM + #SkipChecks + WHERE CheckID IS NULL OR CheckID = 68) + AND DB2.DbName NOT IN ( SELECT name + FROM sys.databases + WHERE is_read_only = 1) + AND CONVERT(DATETIME, DB2.Value, 121) < DATEADD(DD, + -14, + CURRENT_TIMESTAMP); + END; END; /* IF @CheckUserDatabaseObjects = 1 */ @@ -7557,69 +7620,6 @@ IF @ProductVersionMajor >= 10 WHERE s.service_account IS NULL AND ep.principal_id <> 1; END; - /*Check for the last good DBCC CHECKDB date */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 68 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 68) WITH NOWAIT; - - EXEC sp_MSforeachdb N'USE [?]; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - INSERT #DBCCs - (ParentObject, - Object, - Field, - Value) - EXEC (''DBCC DBInfo() With TableResults, NO_INFOMSGS''); - UPDATE #DBCCs SET DbName = N''?'' WHERE DbName IS NULL OPTION (RECOMPILE);'; - - WITH DB2 - AS ( SELECT DISTINCT - Field , - Value , - DbName - FROM #DBCCs - INNER JOIN sys.databases d ON #DBCCs.DbName = d.name - WHERE Field = 'dbi_dbccLastKnownGood' - AND d.create_date < DATEADD(dd, -14, GETDATE()) - ) - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 68 AS CheckID , - DB2.DbName AS DatabaseName , - 1 AS PRIORITY , - 'Reliability' AS FindingsGroup , - 'Last good DBCC CHECKDB over 2 weeks old' AS Finding , - 'https://BrentOzar.com/go/checkdb' AS URL , - 'Last successful CHECKDB: ' - + CASE DB2.Value - WHEN '1900-01-01 00:00:00.000' - THEN ' never.' - ELSE DB2.Value - END AS Details - FROM DB2 - WHERE DB2.DbName <> 'tempdb' - AND DB2.DbName NOT IN ( SELECT DISTINCT - DatabaseName - FROM - #SkipChecks - WHERE CheckID IS NULL OR CheckID = 68) - AND DB2.DbName NOT IN ( SELECT name - FROM sys.databases - WHERE is_read_only = 1) - AND CONVERT(DATETIME, DB2.Value, 121) < DATEADD(DD, - -14, - CURRENT_TIMESTAMP); - END; /*Verify that the servername is set */ IF NOT EXISTS ( SELECT 1 From bbd32520c068233c752d935d8b9819740f78fc5d Mon Sep 17 00:00:00 2001 From: Aleksandr Shalimov Date: Tue, 9 Feb 2021 13:04:20 +0800 Subject: [PATCH 097/662] fix sql version --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 32016bff4..f1636744a 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ The First Responder Kit runs on: * SQL Server 2012, 2014, 2016, 2017, 2019 on Windows - fully supported. * SQL Server 2017, 2019 on Linux - yes, fully supported except sp_AllNightLog and sp_DatabaseRestore, which require xp_cmdshell, which Microsoft doesn't provide on Linux. -* SQL Server 2008, 200R2 - not officially supported since it's out of Microsoft support, but we try not to make changes that would break functionality here. +* SQL Server 2008, 2008R2 - not officially supported since it's out of Microsoft support, but we try not to make changes that would break functionality here. * SQL Server 2000, 2005 - not supported at all. * Amazon RDS SQL Server - fully supported. * Azure SQL DB - not supported. Some of the procedures work, but some don't, and Microsoft has a tendency to change DMVs in Azure without warning, so we don't put any effort into supporting it. If it works, great! If not, any changes to make it work would be on you. [See the contributing.md file](CONTRIBUTING.md) for how to do that. From ad5872caf4c3a8c407d8f5f8cc3646a039f0e947 Mon Sep 17 00:00:00 2001 From: Tom Lovie Date: Fri, 12 Feb 2021 20:30:33 -0500 Subject: [PATCH 098/662] added comments around xp_cmdshell since some firewalls block this string --- sp_Blitz.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index cd048bfc8..818d91477 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -8861,7 +8861,7 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 CREATE TABLE #services (cmdshell_output varchar(max)); INSERT INTO #services - EXEC xp_cmdshell 'net start' + EXEC /**/xp_cmdshell/**/ 'net start' /* added comments around command since some firewalls block this string TL 20210221 */ IF EXISTS (SELECT 1 FROM #services From 9325e085f07a6dfff152d2397c1efc587f7c7645 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Sat, 13 Feb 2021 07:19:50 +0000 Subject: [PATCH 099/662] #2786 sp_Blitz typo Changing BrenOzar to BrentOzar. Closes #2786. --- sp_Blitz.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index 818d91477..763ca0bea 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -8833,7 +8833,7 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 250 AS Priority , ''Server Info'' AS FindingsGroup , ''Azure Managed Instance'' AS Finding , - ''https://www.BrenOzar.com/go/azurevm'' AS URL , + ''https://www.BrentOzar.com/go/azurevm'' AS URL , ''cpu_rate: '' + CAST(COALESCE(cpu_rate, 0) AS VARCHAR(20)) + '', memory_limit_mb: '' + CAST(COALESCE(memory_limit_mb, 0) AS NVARCHAR(20)) + '', process_memory_limit_mb: '' + CAST(COALESCE(process_memory_limit_mb, 0) AS NVARCHAR(20)) + From 3058ec0c7feae37eeeca79fdba0407373a3c36f9 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Sat, 13 Feb 2021 07:54:14 +0000 Subject: [PATCH 100/662] #2790 sp_ineachdb Azure SQL DB compat Closes #2790. --- sp_ineachdb.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sp_ineachdb.sql b/sp_ineachdb.sql index f3d2527ac..539bd6354 100644 --- a/sp_ineachdb.sql +++ b/sp_ineachdb.sql @@ -262,7 +262,7 @@ OPTION (MAXRECURSION 0); -- from Andy Mallon / First Responders Kit. Make sure that if we're an -- AG secondary, we skip any database where allow connections is off - IF @SQLVersion >= 11 + IF @SQLVersion >= 11 AND 3 = (SELECT COUNT(*) FROM sys.all_objects WHERE name IN('availability_replicas','dm_hadr_availability_group_states','dm_hadr_database_replica_states')) BEGIN DELETE dbs FROM #ineachdb AS dbs WHERE EXISTS From ffb6a555a383b57e4f9c38187123b5cb3c0b085e Mon Sep 17 00:00:00 2001 From: Adrian Buckman <33764798+Adedba@users.noreply.github.com> Date: Tue, 16 Feb 2021 21:54:01 +0000 Subject: [PATCH 101/662] Replaced parameters FromDate and ToDate with StartDate and EndDate #2713 Replaced parameters FromDate and ToDate with StartDate and EndDate for consistency to bring in line with sp_BlitzQueryStore which supports start and end datetime imput variables. --- sp_BlitzAnalysis.sql | 106 +++++++++++++++++++++---------------------- 1 file changed, 53 insertions(+), 53 deletions(-) diff --git a/sp_BlitzAnalysis.sql b/sp_BlitzAnalysis.sql index bfe7a06b7..66c65320c 100644 --- a/sp_BlitzAnalysis.sql +++ b/sp_BlitzAnalysis.sql @@ -9,8 +9,8 @@ GO ALTER PROCEDURE [dbo].[sp_BlitzAnalysis] ( @Help TINYINT = 0, -@FromDate DATETIMEOFFSET(7) = NULL, -@ToDate DATETIMEOFFSET(7) = NULL, +@StartDate DATETIMEOFFSET(7) = NULL, +@EndDate DATETIMEOFFSET(7) = NULL, @OutputDatabaseName NVARCHAR(256) = NULL, @OutputSchemaName NVARCHAR(256) = N'dbo', @OutputTableNameBlitzFirst NVARCHAR(256) = N'BlitzFirst', @@ -46,8 +46,8 @@ END; IF (@Help = 1) BEGIN PRINT 'EXEC sp_BlitzAnalysis -@FromDate = NULL, /* Specify a datetime or NULL will get an hour ago */ -@ToDate = NULL, /* Specify a datetime or NULL will get an hour of data since @FromDate */ +@StartDate = NULL, /* Specify a datetime or NULL will get an hour ago */ +@EndDate = NULL, /* Specify a datetime or NULL will get an hour of data since @StartDate */ @OutputDatabaseName = N''DBA'', /* Specify the database name where where we can find your logged blitz data */ @OutputSchemaName = N''dbo'', /* Specify the schema */ @OutputTableNameBlitzFirst = N''BlitzFirst'', /* Table name where you are storing sp_BlitzFirst output, Set to NULL to ignore */ @@ -153,32 +153,32 @@ SELECT @IncludeSpills = END; -IF (@FromDate IS NULL) +IF (@StartDate IS NULL) BEGIN - RAISERROR('Setting @FromDate to: 1 hour ago',0,0) WITH NOWAIT; - /* Set FromDate to be an hour ago */ - SET @FromDate = DATEADD(HOUR,-1,SYSDATETIMEOFFSET()); + RAISERROR('Setting @StartDate to: 1 hour ago',0,0) WITH NOWAIT; + /* Set StartDate to be an hour ago */ + SET @StartDate = DATEADD(HOUR,-1,SYSDATETIMEOFFSET()); - IF (@ToDate IS NULL) + IF (@EndDate IS NULL) BEGIN - RAISERROR('Setting @ToDate to: Now',0,0) WITH NOWAIT; + RAISERROR('Setting @EndDate to: Now',0,0) WITH NOWAIT; /* Get data right up to now */ - SET @ToDate = SYSDATETIMEOFFSET(); + SET @EndDate = SYSDATETIMEOFFSET(); END END -IF (@ToDate IS NULL) +IF (@EndDate IS NULL) BEGIN - /* Default to an hour of data or SYSDATETIMEOFFSET() if now is earlier than the hour added to @FromDate */ - IF(DATEADD(HOUR,1,@FromDate) < SYSDATETIMEOFFSET()) + /* Default to an hour of data or SYSDATETIMEOFFSET() if now is earlier than the hour added to @StartDate */ + IF(DATEADD(HOUR,1,@StartDate) < SYSDATETIMEOFFSET()) BEGIN - RAISERROR('@ToDate was NULL - Setting to return 1 hour of information, if you want more then set @ToDate aswell',0,0) WITH NOWAIT; - SET @ToDate = DATEADD(HOUR,1,@FromDate); + RAISERROR('@EndDate was NULL - Setting to return 1 hour of information, if you want more then set @EndDate aswell',0,0) WITH NOWAIT; + SET @EndDate = DATEADD(HOUR,1,@StartDate); END ELSE BEGIN - RAISERROR('@ToDate was NULL - Setting to SYSDATETIMEOFFSET()',0,0) WITH NOWAIT; - SET @ToDate = SYSDATETIMEOFFSET(); + RAISERROR('@EndDate was NULL - Setting to SYSDATETIMEOFFSET()',0,0) WITH NOWAIT; + SET @EndDate = SYSDATETIMEOFFSET(); END END @@ -189,7 +189,7 @@ BEGIN END /* Prompt the user for @BringThePain = 1 if they are searching a timeframe greater than 4 hours and they are using BlitzCacheSortorder = 'all' */ -IF(@BlitzCacheSortorder = 'all' AND DATEDIFF(HOUR,@FromDate,@ToDate) > 4 AND @BringThePain = 0) +IF(@BlitzCacheSortorder = 'all' AND DATEDIFF(HOUR,@StartDate,@EndDate) > 4 AND @BringThePain = 0) BEGIN RAISERROR('Wow! hold up now, are you sure you wanna do this? Are sure you want to query over 4 hours of data with @BlitzCacheSortorder set to ''all''? IF you do then set @BringThePain = 1 but I gotta warn you this might hurt a bit!',11,1) WITH NOWAIT; RETURN; @@ -199,8 +199,8 @@ END SELECT @Servername AS [ServerToReportOn], CAST(1 AS NVARCHAR(20)) + N' - '+ CAST(@MaxBlitzFirstPriority AS NVARCHAR(20)) AS [PrioritesToInclude], - @FromDate AS [FromDatetime], - @ToDate AS [ToDatetime];; + @StartDate AS [StartDatetime], + @EndDate AS [EndDatetime];; /* BlitzFirst data */ @@ -216,7 +216,7 @@ MAX(CheckDate) AS [LastOccurrence] FROM '+@FullOutputTableNameBlitzFirst+N' WHERE [ServerName] = @Servername AND [Priority] BETWEEN 1 AND @MaxBlitzFirstPriority -AND CheckDate BETWEEN @FromDate AND @ToDate +AND CheckDate BETWEEN @StartDate AND @EndDate AND [CheckID] > -1 GROUP BY [Priority],[FindingsGroup],[Finding]; @@ -250,7 +250,7 @@ SELECT FROM '+@FullOutputTableNameBlitzFirst+N' Findings WHERE [ServerName] = @Servername AND [Priority] BETWEEN 1 AND @MaxBlitzFirstPriority -AND [CheckDate] BETWEEN @FromDate AND @ToDate +AND [CheckDate] BETWEEN @StartDate AND @EndDate AND [CheckID] > -1 ORDER BY CheckDate ASC,[Priority] ASC OPTION (RECOMPILE, MAXDOP '+CAST(@Maxdop AS NVARCHAR(2))+N');'; @@ -280,12 +280,12 @@ END ELSE /* Table exists then run the query */ BEGIN EXEC sp_executesql @Sql, - N'@FromDate DATETIMEOFFSET(7), - @ToDate DATETIMEOFFSET(7), + N'@StartDate DATETIMEOFFSET(7), + @EndDate DATETIMEOFFSET(7), @Servername NVARCHAR(128), @MaxBlitzFirstPriority INT', - @FromDate=@FromDate, - @ToDate=@ToDate, + @StartDate=@StartDate, + @EndDate=@EndDate, @Servername=@Servername, @MaxBlitzFirstPriority = @MaxBlitzFirstPriority; END @@ -322,7 +322,7 @@ FROM ROW_NUMBER() OVER(PARTITION BY [CheckDate] ORDER BY [CheckDate] ASC,[wait_time_ms_delta] DESC) AS [WaitsRank] FROM '+@FullOutputTableNameWaitStats+N' AS [Waits] WHERE [ServerName] = @Servername - AND [CheckDate] BETWEEN @FromDate AND @ToDate + AND [CheckDate] BETWEEN @StartDate AND @EndDate ) TopWaits WHERE [WaitsRank] <= @WaitStatsTop ORDER BY @@ -353,12 +353,12 @@ END ELSE /* Table exists then run the query */ BEGIN EXEC sp_executesql @Sql, - N'@FromDate DATETIMEOFFSET(7), - @ToDate DATETIMEOFFSET(7), + N'@StartDate DATETIMEOFFSET(7), + @EndDate DATETIMEOFFSET(7), @Servername NVARCHAR(128), @WaitStatsTop TINYINT', - @FromDate=@FromDate, - @ToDate=@ToDate, + @StartDate=@StartDate, + @EndDate=@EndDate, @Servername=@Servername, @WaitStatsTop=@WaitStatsTop; END @@ -388,7 +388,7 @@ SUM([num_of_writes]) AS [num_of_writes], SUM([megabytes_written]) AS [megabytes_written] FROM '+@FullOutputTableNameFileStats+N' WHERE [ServerName] = @Servername -AND [CheckDate] BETWEEN @FromDate AND @ToDate +AND [CheckDate] BETWEEN @StartDate AND @EndDate ' +CASE WHEN @Databasename IS NOT NULL THEN N'AND [DatabaseName] IN (N''tempdb'',@Databasename) @@ -427,14 +427,14 @@ END ELSE /* Table exists then run the query */ BEGIN EXEC sp_executesql @Sql, - N'@FromDate DATETIMEOFFSET(7), - @ToDate DATETIMEOFFSET(7), + N'@StartDate DATETIMEOFFSET(7), + @EndDate DATETIMEOFFSET(7), @Servername NVARCHAR(128), @Databasename NVARCHAR(128), @ReadLatencyThreshold INT, @WriteLatencyThreshold INT', - @FromDate=@FromDate, - @ToDate=@ToDate, + @StartDate=@StartDate, + @EndDate=@EndDate, @Servername=@Servername, @Databasename = @Databasename, @ReadLatencyThreshold = @ReadLatencyThreshold, @@ -451,7 +451,7 @@ SELECT ,[instance_name] ,[cntr_value] FROM '+@FullOutputTableNamePerfmonStats+N' -WHERE CheckDate BETWEEN @FromDate AND @ToDate +WHERE CheckDate BETWEEN @StartDate AND @EndDate ORDER BY [CheckDate] ASC, [counter_name] ASC @@ -480,11 +480,11 @@ END ELSE /* Table exists then run the query */ BEGIN EXEC sp_executesql @Sql, - N'@FromDate DATETIMEOFFSET(7), - @ToDate DATETIMEOFFSET(7), + N'@StartDate DATETIMEOFFSET(7), + @EndDate DATETIMEOFFSET(7), @Servername NVARCHAR(128)', - @FromDate=@FromDate, - @ToDate=@ToDate, + @StartDate=@StartDate, + @EndDate=@EndDate, @Servername=@Servername; END @@ -498,7 +498,7 @@ FROM ' +@FullOutputTableNameBlitzCache +@NewLine +N'WHERE [ServerName] = @Servername -AND [CheckDate] BETWEEN @FromDate AND @ToDate +AND [CheckDate] BETWEEN @StartDate AND @EndDate ' +CASE WHEN @Databasename IS NOT NULL THEN N'AND [DatabaseName] = @Databasename @@ -624,7 +624,7 @@ CROSS APPLY ( ,'+[SortOptions].[Aliasname]+N'.[QueryPlanCost] FROM '+@FullOutputTableNameBlitzCache+N' AS '+[SortOptions].[Aliasname]+N' WHERE [ServerName] = @Servername - AND [CheckDate] BETWEEN @FromDate AND @ToDate + AND [CheckDate] BETWEEN @StartDate AND @EndDate AND ['+[SortOptions].[Aliasname]+N'].[CheckDate] = [CheckDates].[CheckDate] ' +CASE @@ -734,13 +734,13 @@ BEGIN N'@Servername NVARCHAR(128), @Databasename NVARCHAR(128), @BlitzCacheSortorder NVARCHAR(20), - @FromDate DATETIMEOFFSET(7), - @ToDate DATETIMEOFFSET(7)', + @StartDate DATETIMEOFFSET(7), + @EndDate DATETIMEOFFSET(7)', @Servername = @Servername, @Databasename = @Databasename, @BlitzCacheSortorder = @BlitzCacheSortorder, - @FromDate = @FromDate, - @ToDate = @ToDate; + @StartDate = @StartDate, + @EndDate = @EndDate; END @@ -845,7 +845,7 @@ SELECT [ServerName] ,[statement_end_offset] FROM '+@FullOutputTableNameBlitzWho+N' WHERE [ServerName] = @Servername - AND ([CheckDate] BETWEEN @FromDate AND @ToDate OR [start_time] BETWEEN CAST(@FromDate AS DATETIME) AND CAST(@ToDate AS DATETIME)) + AND ([CheckDate] BETWEEN @StartDate AND @EndDate OR [start_time] BETWEEN CAST(@StartDate AS DATETIME) AND CAST(@EndDate AS DATETIME)) ' +CASE WHEN @Databasename IS NOT NULL THEN N'AND [database_name] = @Databasename @@ -879,12 +879,12 @@ END ELSE BEGIN EXEC sp_executesql @Sql, - N'@FromDate DATETIMEOFFSET(7), - @ToDate DATETIMEOFFSET(7), + N'@StartDate DATETIMEOFFSET(7), + @EndDate DATETIMEOFFSET(7), @Servername NVARCHAR(128), @Databasename NVARCHAR(128)', - @FromDate=@FromDate, - @ToDate=@ToDate, + @StartDate=@StartDate, + @EndDate=@EndDate, @Servername=@Servername, @Databasename = @Databasename; END From e4de336b3bdfe800292feef987c89799709eb295 Mon Sep 17 00:00:00 2001 From: Adrian Buckman <33764798+Adedba@users.noreply.github.com> Date: Tue, 16 Feb 2021 22:08:06 +0000 Subject: [PATCH 102/662] Added sp_BlitzAnalysis to Readme.md #2713 Added sp_BlitzAnalysis to Readme.md --- README.md | 79 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 78 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 32016bff4..7a8d9cd93 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,8 @@ Navigation - [sp_BlitzInMemoryOLTP: Hekaton Analysis](#sp_blitzinmemoryoltp-hekaton-analysis) - [sp_BlitzLock: Deadlock Analysis](#sp_blitzlock-deadlock-analysis) - [sp_BlitzQueryStore: Like BlitzCache, for Query Store](#sp_blitzquerystore-query-store-sale) - - [sp_BlitzWho: What Queries are Running Now](#sp_blitzwho-what-queries-are-running-now) + - [sp_BlitzWho: What Queries are Running Now](#sp_blitzwho-what-queries-are-running-now) + - [sp_BlitzAnalysis: Query sp_BlitzFirst output tables](#sp_blitzanalysis-query-sp_BlitzFirst-output-tables) - Backups and Restores: - [sp_BlitzBackups: How Much Data Could You Lose](#sp_blitzbackups-how-much-data-could-you-lose) - [sp_AllNightLog: Back Up Faster to Lose Less Data](#sp_allnightlog-back-up-faster-to-lose-less-data) @@ -364,6 +365,82 @@ It's designed for query tuners, so it includes things like memory grants, degree [*Back to top*](#header1) +## sp_BlitzAnalysis: Query sp_BlitzFirst output tables + +Retrieves data from the output tables where you are storing your sp_BlitzFirst output. + +Parameters include: + +* @StartDate: When you want to start seeing data from , NULL will set @StartDate to 1 hour ago. +* @EndDate: When you want to see data up to, NULL will get an hour of data since @StartDate. +* @OutputDatabaseName: Specify the database name where where we can find your logged sp_BlitzFirst Output table data +* @OutputSchemaName: Schema which the sp_BlitzFirst Output tables belong to +* @OutputTableNameBlitzFirst: Table name where you are storing sp_BlitzFirst @OutputTableNameBlitzFirst output, we default to BlitzFirst - you can Set to NULL to skip lookups against this table +* @OutputTableNameFileStats: Table name where you are storing sp_BlitzFirst @OutputTableNameFileStats output, we default to BlitzFirst_FileStats - you can Set to NULL to skip lookups against this table. +* @OutputTableNamePerfmonStats: Table name where you are storing sp_BlitzFirst @OutputTableNamePerfmonStats output, we default to BlitzFirst_PerfmonStats - you can Set to NULL to skip lookups against this table. +* @OutputTableNameWaitStats: Table name where you are storing sp_BlitzFirst @OutputTableNameWaitStats output, we default to BlitzFirst_WaitStats - you can Set to NULL to skip lookups against this table. +* @OutputTableNameBlitzCache: Table name where you are storing sp_BlitzFirst @OutputTableNameBlitzCache output, we default to BlitzCache - you can Set to NULL to skip lookups against this table. +* @OutputTableNameBlitzWho: Table name where you are storing sp_BlitzFirst @OutputTableNameBlitzWho output, we default to BlitzWho - you can Set to NULL to skip lookups against this table. +* @MaxBlitzFirstPriority: Max priority to include in the results from your BlitzFirst table, Default: 249. +* @BlitzCacheSortorder: Controls the results returned from your BlitzCache table, you will get a TOP 5 per sort order per CheckDate, Default: 'cpu' Accepted values 'all' 'cpu' 'reads' 'writes' 'duration' 'executions' 'memory grant' 'spills'. +* @WaitStatsTop: Controls the Top X waits per CheckDate from your wait stats table, Default: 10. +* @ReadLatencyThreshold: Sets the threshold in ms to compare against io_stall_read_average_ms in your filestats table, Default: 100. +* @WriteLatencyThreshold: Sets the threshold in ms to compare against io_stall_write_average_ms in your filestats table, Default: 100. +* @BringThePain: If you are getting more than 4 hours of data from your BlitzCache table with @BlitzCacheSortorder set to 'all' you will need to set BringThePain to 1. +* @Maxdop: Control the degree of parallelism that the queries within this proc can use if they want to, Default = 1. +* @Debug: Show sp_BlitzAnalysis SQL commands in the messages tab as they execute. + +Example calls: + +Get information for the last hour from all sp_BlitzFirst output tables +...SQL +EXEC sp_BlitzAnalysis + @FromDate = NULL, + @ToDate = NULL, + @OutputDatabaseName = 'DBAtools', + @OutputSchemaName = 'dbo', + @OutputTableNameFileStats = N'BlitzFirst_FileStats', + @OutputTableNamePerfmonStats = N'BlitzFirst_PerfmonStats', + @OutputTableNameWaitStats = N'BlitzFirst_WaitStats', + @OutputTableNameBlitzCache = N'BlitzCache', + @OutputTableNameBlitzWho = N'BlitzWho'; +... + +Exclude specific tables e.g lets exclude PerfmonStats by setting to NULL, no lookup will occur against the table and a skipped message will appear in the resultset */ +...SQL +EXEC sp_BlitzAnalysis + @FromDate = NULL, + @ToDate = NULL, + @OutputDatabaseName = 'DBA', + @OutputSchemaName = 'Blitz', + @OutputTableNameFileStats = N'BlitzFirst_FileStats', + @OutputTableNamePerfmonStats = NULL, + @OutputTableNameWaitStats = N'BlitzFirst_WaitStats', + @OutputTableNameBlitzCache = N'BlitzCache', + @OutputTableNameBlitzWho = N'BlitzWho'; +... + +Known issues: +We are likely to be hitting some big tables here and some of these queries will require scans of the clustered indexes as there are no nonclustered indexes to cover the queries by default, keep this in mind if you are planning on running this in a production environment! + +I have noticed that the Perfmon query can ask for a big memory grant so be mindful when including this table with large volumes of data: + +SELECT + [ServerName] + ,[CheckDate] + ,[counter_name] + ,[object_name] + ,[instance_name] + ,[cntr_value] +FROM [dbo].[BlitzFirst_PerfmonStats_Actuals] +WHERE CheckDate BETWEEN @FromDate AND @ToDate +ORDER BY + [CheckDate] ASC, + [counter_name] ASC + +[*Back to top*](#header1) + + ## sp_BlitzBackups: How Much Data Could You Lose Checks your backups and reports estimated RPO and RTO based on historical data in msdb, or a centralized location for [msdb].dbo.backupset. From a7d6e978bd1d7df067734d99970189bce5288992 Mon Sep 17 00:00:00 2001 From: Adrian Buckman <33764798+Adedba@users.noreply.github.com> Date: Tue, 16 Feb 2021 22:17:58 +0000 Subject: [PATCH 103/662] Added correct SQL code tags in readme.md #2713 Added correct SQL code tags in readme.md --- README.md | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 7a8d9cd93..d33ad93cc 100644 --- a/README.md +++ b/README.md @@ -393,7 +393,8 @@ Parameters include: Example calls: Get information for the last hour from all sp_BlitzFirst output tables -...SQL + +```SQL EXEC sp_BlitzAnalysis @FromDate = NULL, @ToDate = NULL, @@ -404,10 +405,11 @@ EXEC sp_BlitzAnalysis @OutputTableNameWaitStats = N'BlitzFirst_WaitStats', @OutputTableNameBlitzCache = N'BlitzCache', @OutputTableNameBlitzWho = N'BlitzWho'; -... +``` + +Exclude specific tables e.g lets exclude PerfmonStats by setting to NULL, no lookup will occur against the table and a skipped message will appear in the resultset -Exclude specific tables e.g lets exclude PerfmonStats by setting to NULL, no lookup will occur against the table and a skipped message will appear in the resultset */ -...SQL +```SQL EXEC sp_BlitzAnalysis @FromDate = NULL, @ToDate = NULL, @@ -418,13 +420,14 @@ EXEC sp_BlitzAnalysis @OutputTableNameWaitStats = N'BlitzFirst_WaitStats', @OutputTableNameBlitzCache = N'BlitzCache', @OutputTableNameBlitzWho = N'BlitzWho'; -... +``` Known issues: We are likely to be hitting some big tables here and some of these queries will require scans of the clustered indexes as there are no nonclustered indexes to cover the queries by default, keep this in mind if you are planning on running this in a production environment! I have noticed that the Perfmon query can ask for a big memory grant so be mindful when including this table with large volumes of data: +```SQL SELECT [ServerName] ,[CheckDate] @@ -437,6 +440,7 @@ WHERE CheckDate BETWEEN @FromDate AND @ToDate ORDER BY [CheckDate] ASC, [counter_name] ASC +``` [*Back to top*](#header1) From bcb311549e551dffef107f90be7c3fc3d51e130d Mon Sep 17 00:00:00 2001 From: Adrian Buckman <33764798+Adedba@users.noreply.github.com> Date: Tue, 16 Feb 2021 22:21:45 +0000 Subject: [PATCH 104/662] fixed readme typo #2713 fixed readme databasename typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d33ad93cc..23710299f 100644 --- a/README.md +++ b/README.md @@ -413,7 +413,7 @@ Exclude specific tables e.g lets exclude PerfmonStats by setting to NULL, no loo EXEC sp_BlitzAnalysis @FromDate = NULL, @ToDate = NULL, - @OutputDatabaseName = 'DBA', + @OutputDatabaseName = 'DBAtools', @OutputSchemaName = 'Blitz', @OutputTableNameFileStats = N'BlitzFirst_FileStats', @OutputTableNamePerfmonStats = NULL, From 5ad3d82bc9724f58289a1de5a930ee9be4091360 Mon Sep 17 00:00:00 2001 From: Adrian Buckman <33764798+Adedba@users.noreply.github.com> Date: Wed, 17 Feb 2021 09:55:33 +0000 Subject: [PATCH 105/662] Tidied dynamic sql output formatting #2713 Tidied dynamic sql output formatting, removed unnecessary blank lines. --- sp_BlitzAnalysis.sql | 56 ++++++++++++++++++++------------------------ 1 file changed, 25 insertions(+), 31 deletions(-) diff --git a/sp_BlitzAnalysis.sql b/sp_BlitzAnalysis.sql index 66c65320c..44bfc36c0 100644 --- a/sp_BlitzAnalysis.sql +++ b/sp_BlitzAnalysis.sql @@ -1,7 +1,7 @@ SET ANSI_NULLS ON; SET QUOTED_IDENTIFIER ON -IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[sp_BlitzAnalysis]') AND type in (N'P', N'PC')) +IF NOT EXISTS (SELECT * FROM sys.objects WHERE [object_id] = OBJECT_ID(N'[dbo].[sp_BlitzAnalysis]') AND [type] in (N'P', N'PC')) BEGIN EXEC dbo.sp_executesql @statement = N'CREATE PROCEDURE [dbo].[sp_BlitzAnalysis] AS' END @@ -11,7 +11,7 @@ ALTER PROCEDURE [dbo].[sp_BlitzAnalysis] ( @Help TINYINT = 0, @StartDate DATETIMEOFFSET(7) = NULL, @EndDate DATETIMEOFFSET(7) = NULL, -@OutputDatabaseName NVARCHAR(256) = NULL, +@OutputDatabaseName NVARCHAR(256) = 'DBAtools', @OutputSchemaName NVARCHAR(256) = N'dbo', @OutputTableNameBlitzFirst NVARCHAR(256) = N'BlitzFirst', @OutputTableNameFileStats NVARCHAR(256) = N'BlitzFirst_FileStats', @@ -36,7 +36,7 @@ ALTER PROCEDURE [dbo].[sp_BlitzAnalysis] ( AS SET NOCOUNT ON; -SELECT @Version = '1.000', @VersionDate = '20210204'; +SELECT @Version = '1.000', @VersionDate = '20210216'; IF(@VersionCheckMode = 1) BEGIN @@ -80,7 +80,7 @@ DECLARE @FullOutputTableNameWaitStats NVARCHAR(1000); DECLARE @FullOutputTableNameBlitzCache NVARCHAR(1000); DECLARE @FullOutputTableNameBlitzWho NVARCHAR(1000); DECLARE @Sql NVARCHAR(MAX); -DECLARE @NewLine NVARCHAR(2) = CHAR(10)+ CHAR(13); +DECLARE @NewLine NVARCHAR(2) = CHAR(13); DECLARE @IncludeMemoryGrants BIT; DECLARE @IncludeSpills BIT; @@ -393,8 +393,7 @@ AND [CheckDate] BETWEEN @StartDate AND @EndDate +CASE WHEN @Databasename IS NOT NULL THEN N'AND [DatabaseName] IN (N''tempdb'',@Databasename) ' - ELSE N' -' + ELSE N'' END +N'GROUP BY [ServerName], @@ -496,22 +495,22 @@ SET @Sql = N'WITH CheckDates AS ( SELECT DISTINCT CheckDate FROM ' +@FullOutputTableNameBlitzCache ++N' +WHERE [ServerName] = @Servername +AND [CheckDate] BETWEEN @StartDate AND @EndDate' +@NewLine -+N'WHERE [ServerName] = @Servername -AND [CheckDate] BETWEEN @StartDate AND @EndDate -' +CASE - WHEN @Databasename IS NOT NULL THEN N'AND [DatabaseName] = @Databasename -' - ELSE N' -' + WHEN @Databasename IS NOT NULL THEN N'AND [DatabaseName] = @Databasename'+@NewLine + ELSE N'' END -+N')'; ++N')' +; + +SET @Sql += @NewLine; /* Append additional CTEs based on sortorder */ SET @Sql += ( SELECT CAST(N',' AS NVARCHAR(MAX)) -+@NewLine +[SortOptions].[Aliasname]+N' AS ( SELECT [ServerName] @@ -569,7 +568,7 @@ SELECT ,'+[SortOptions].[Aliasname]+N'.[QueryPlanCost] FROM CheckDates CROSS APPLY ( - SELECT TOP 5 + SELECT TOP (5) [ServerName] ,'+[SortOptions].[Aliasname]+N'.[CheckDate] ,'+QUOTENAME(UPPER([SortOptions].[Sortorder]),N'''')+N' AS [Sortorder] @@ -625,13 +624,11 @@ CROSS APPLY ( FROM '+@FullOutputTableNameBlitzCache+N' AS '+[SortOptions].[Aliasname]+N' WHERE [ServerName] = @Servername AND [CheckDate] BETWEEN @StartDate AND @EndDate - AND ['+[SortOptions].[Aliasname]+N'].[CheckDate] = [CheckDates].[CheckDate] - ' + AND ['+[SortOptions].[Aliasname]+N'].[CheckDate] = [CheckDates].[CheckDate]' + +@NewLine +CASE - WHEN @Databasename IS NOT NULL THEN N'AND ['+[SortOptions].[Aliasname]+N'].[DatabaseName] = @Databasename - ' - ELSE N' - ' + WHEN @Databasename IS NOT NULL THEN N'AND ['+[SortOptions].[Aliasname]+N'].[DatabaseName] = @Databasename'+@NewLine + ELSE N'' END +CASE WHEN [Sortorder] = N'cpu' THEN N'AND [TotalCPU] > 0' @@ -663,6 +660,7 @@ WHERE END = ISNULL(NULLIF(@BlitzCacheSortorder,N'all'),[SortOptions].[Sortorder]) FOR XML PATH(N''), TYPE).value(N'.[1]', N'NVARCHAR(MAX)'); +SET @Sql += @NewLine; /* Build the select statements to return the data after CTE declarations */ SET @Sql += ( @@ -692,14 +690,14 @@ FOR XML PATH(N''), TYPE).value(N'.[1]', N'NVARCHAR(MAX)'),1,11,N'') /* Append Order By */ SET @Sql += @NewLine -+'ORDER BY ++N'ORDER BY [Sortorder] ASC, [CheckDate] ASC, [TimeFrameRank] ASC'; /* Append OPTION(RECOMPILE, MAXDOP) to complete the statement */ SET @Sql += @NewLine -+'OPTION(RECOMPILE, MAXDOP '+CAST(@Maxdop AS NVARCHAR(2))+N');'; ++N'OPTION(RECOMPILE, MAXDOP '+CAST(@Maxdop AS NVARCHAR(2))+N');'; RAISERROR('Getting BlitzCache info from %s',0,0,@FullOutputTableNameBlitzCache) WITH NOWAIT; @@ -710,9 +708,8 @@ BEGIN PRINT SUBSTRING(@Sql, 8000, 12000); PRINT SUBSTRING(@Sql, 12000, 16000); PRINT SUBSTRING(@Sql, 16000, 20000); + PRINT SUBSTRING(@Sql, 20000, 24000); PRINT SUBSTRING(@Sql, 24000, 28000); - PRINT SUBSTRING(@Sql, 32000, 36000); - PRINT SUBSTRING(@Sql, 40000, 44000); END IF (OBJECT_ID(@FullOutputTableNameBlitzCache) IS NULL) @@ -850,8 +847,7 @@ SELECT [ServerName] +CASE WHEN @Databasename IS NOT NULL THEN N'AND [database_name] = @Databasename ' - ELSE N' - ' + ELSE N'' END +N'ORDER BY [CheckDate] ASC OPTION (RECOMPILE, MAXDOP '+CAST(@Maxdop AS NVARCHAR(2))+N');'; @@ -890,6 +886,4 @@ BEGIN END -GO - - +GO \ No newline at end of file From 8c1f01a389e2bf877e1965b2e99b4b5744ebe5a1 Mon Sep 17 00:00:00 2001 From: Tom Lovie Date: Wed, 17 Feb 2021 10:57:44 -0500 Subject: [PATCH 106/662] Added PlanGenerationNum to output for @expertmode=0 --- sp_BlitzCache.sql | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sp_BlitzCache.sql b/sp_BlitzCache.sql index 30a937ddc..4a1067d76 100644 --- a/sp_BlitzCache.sql +++ b/sp_BlitzCache.sql @@ -4933,7 +4933,8 @@ BEGIN PlanHandle AS [Plan Handle], SqlHandle AS [SQL Handle], COALESCE(SetOptions, '''') AS [SET Options], - QueryHash AS [Query Hash], + QueryHash AS [Query Hash], + PlanGenerationNum, [Remove Plan Handle From Cache]'; END; ELSE From b23326726cb1dc8bbf3a56f4883420fc0a917766 Mon Sep 17 00:00:00 2001 From: Tom Lovie Date: Wed, 17 Feb 2021 13:42:13 -0500 Subject: [PATCH 107/662] Added PlanGenerationNum to all internal tables as well. --- sp_BlitzCache.sql | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/sp_BlitzCache.sql b/sp_BlitzCache.sql index 4a1067d76..bfd471d30 100644 --- a/sp_BlitzCache.sql +++ b/sp_BlitzCache.sql @@ -6477,6 +6477,7 @@ IF OBJECT_ID('tempdb.. #bou_allsort') IS NULL SqlHandle VARBINARY(64), SetOptions VARCHAR(MAX), QueryHash BINARY(8), + PlanGenerationNum NVARCHAR(30), RemovePlanHandleFromCache NVARCHAR(200), Pattern NVARCHAR(20) ); @@ -6492,7 +6493,7 @@ SET @AllSortSql += N' INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, RemovePlanHandleFromCache ) + MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''cpu'', @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; @@ -6504,7 +6505,7 @@ SET @AllSortSql += N' INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, RemovePlanHandleFromCache ) + MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''reads'', @IgnoreSqlHandles = @ISH, @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; @@ -6516,7 +6517,7 @@ SET @AllSortSql += N' INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, RemovePlanHandleFromCache ) + MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''writes'', @IgnoreSqlHandles = @ISH, @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; @@ -6528,7 +6529,7 @@ SET @AllSortSql += N' INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, RemovePlanHandleFromCache ) + MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''duration'', @IgnoreSqlHandles = @ISH, @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; @@ -6540,7 +6541,7 @@ SET @AllSortSql += N' INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, RemovePlanHandleFromCache ) + MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''executions'', @IgnoreSqlHandles = @ISH, @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; @@ -6575,7 +6576,7 @@ SET @AllSortSql += N' INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, RemovePlanHandleFromCache ) + MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''memory grant'', @IgnoreSqlHandles = @ISH, @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; @@ -6624,7 +6625,7 @@ SET @AllSortSql += N' INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, RemovePlanHandleFromCache ) + MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''spills'', @IgnoreSqlHandles = @ISH, @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; @@ -6649,7 +6650,7 @@ SET @AllSortSql += N' SET @AllSortSql += N' SELECT DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters,ExecutionCount, ExecutionsPerMinute, ExecutionWeight, TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, RemovePlanHandleFromCache, Pattern + MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache, Pattern FROM #bou_allsort ORDER BY Id OPTION(RECOMPILE); '; @@ -6667,7 +6668,7 @@ SET @AllSortSql += N' INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, RemovePlanHandleFromCache ) + MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''avg cpu'', @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; @@ -6679,7 +6680,7 @@ SET @AllSortSql += N' INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, RemovePlanHandleFromCache ) + MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''avg reads'', @IgnoreSqlHandles = @ISH, @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; @@ -6691,7 +6692,7 @@ SET @AllSortSql += N' INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, RemovePlanHandleFromCache ) + MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''avg writes'', @IgnoreSqlHandles = @ISH, @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; @@ -6703,7 +6704,7 @@ SET @AllSortSql += N' INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, RemovePlanHandleFromCache ) + MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''avg duration'', @IgnoreSqlHandles = @ISH, @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; @@ -6715,7 +6716,7 @@ SET @AllSortSql += N' INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, RemovePlanHandleFromCache ) + MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''avg executions'', @IgnoreSqlHandles = @ISH, @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; @@ -6750,7 +6751,7 @@ SET @AllSortSql += N' INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, RemovePlanHandleFromCache ) + MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''avg memory grant'', @IgnoreSqlHandles = @ISH, @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; @@ -6799,7 +6800,7 @@ SET @AllSortSql += N' INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, RemovePlanHandleFromCache ) + MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''avg spills'', @IgnoreSqlHandles = @ISH, @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; @@ -6825,7 +6826,7 @@ SET @AllSortSql += N' SET @AllSortSql += N' SELECT DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters,ExecutionCount, ExecutionsPerMinute, ExecutionWeight, TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, RemovePlanHandleFromCache, Pattern + MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache, Pattern FROM #bou_allsort ORDER BY Id OPTION(RECOMPILE); '; @@ -7033,7 +7034,7 @@ BEGIN ALTER TABLE '+@OutputDatabaseName+'.'+@OutputSchemaName+'.'+@OutputTableName+' DROP COLUMN [PlanCreationTimeHours]; ALTER TABLE '+@OutputDatabaseName+'.'+@OutputSchemaName+'.'+@OutputTableName+' ADD [PlanCreationTimeHours] AS DATEDIFF(HOUR,CONVERT(DATETIMEOFFSET(7),[PlanCreationTime]),[CheckDate]); END '; - + IF @ValidOutputServer = 1 BEGIN SET @StringToExecute = REPLACE(@StringToExecute,''''+@OutputSchemaName+'''',''''''+@OutputSchemaName+''''''); From c405e290d3dd8ae6ca987691fd74eb2d3a2844c5 Mon Sep 17 00:00:00 2001 From: Adrian Buckman <33764798+Adedba@users.noreply.github.com> Date: Wed, 17 Feb 2021 22:36:09 +0000 Subject: [PATCH 108/662] Updated readme Added missing parameters. --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 23710299f..b0b08dccf 100644 --- a/README.md +++ b/README.md @@ -373,6 +373,8 @@ Parameters include: * @StartDate: When you want to start seeing data from , NULL will set @StartDate to 1 hour ago. * @EndDate: When you want to see data up to, NULL will get an hour of data since @StartDate. +* @Databasename: Filter results by database name where possible, Default: NULL which shows all. +* @Servername: Filter results by server name, Default: @@SERVERNAME. * @OutputDatabaseName: Specify the database name where where we can find your logged sp_BlitzFirst Output table data * @OutputSchemaName: Schema which the sp_BlitzFirst Output tables belong to * @OutputTableNameBlitzFirst: Table name where you are storing sp_BlitzFirst @OutputTableNameBlitzFirst output, we default to BlitzFirst - you can Set to NULL to skip lookups against this table From a2f83e92b9cc1e919e48779a3a646f5fc3c69610 Mon Sep 17 00:00:00 2001 From: Adrian Buckman <33764798+Adedba@users.noreply.github.com> Date: Wed, 17 Feb 2021 22:48:52 +0000 Subject: [PATCH 109/662] Fixed no findings output message for Blitzfirst priority Fixed no findings output message for Blitzfirst priority, the previous message was incorrect and did not state the max priority passed in by the user. --- sp_BlitzAnalysis.sql | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sp_BlitzAnalysis.sql b/sp_BlitzAnalysis.sql index 44bfc36c0..764fc93d5 100644 --- a/sp_BlitzAnalysis.sql +++ b/sp_BlitzAnalysis.sql @@ -36,7 +36,7 @@ ALTER PROCEDURE [dbo].[sp_BlitzAnalysis] ( AS SET NOCOUNT ON; -SELECT @Version = '1.000', @VersionDate = '20210216'; +SELECT @Version = '1.000', @VersionDate = '20210217'; IF(@VersionCheckMode = 1) BEGIN @@ -234,7 +234,7 @@ BEGIN END ELSE BEGIN - SELECT ''No findings with a priority greater than -1 found for this period''; + SELECT N''No findings with a priority between 1 and ''+CAST(@MaxBlitzFirstPriority AS NVARCHAR(10))+N'' found for this period''; END SELECT [ServerName] @@ -886,4 +886,4 @@ BEGIN END -GO \ No newline at end of file +GO From 32cc85c12ad426888b1213510e652ac142a4efb6 Mon Sep 17 00:00:00 2001 From: Adrian Buckman <33764798+Adedba@users.noreply.github.com> Date: Wed, 17 Feb 2021 22:52:27 +0000 Subject: [PATCH 110/662] Added missing servername predicate for the perfmon table Added missing servername predicate for the perfmon table - oops! --- sp_BlitzAnalysis.sql | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sp_BlitzAnalysis.sql b/sp_BlitzAnalysis.sql index 764fc93d5..2a0addae8 100644 --- a/sp_BlitzAnalysis.sql +++ b/sp_BlitzAnalysis.sql @@ -450,7 +450,8 @@ SELECT ,[instance_name] ,[cntr_value] FROM '+@FullOutputTableNamePerfmonStats+N' -WHERE CheckDate BETWEEN @StartDate AND @EndDate +WHERE [ServerName] = @Servername +AND CheckDate BETWEEN @StartDate AND @EndDate ORDER BY [CheckDate] ASC, [counter_name] ASC From 866a5a6422d000f8a13e0da7d21f0a35a7928e4b Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Mon, 22 Feb 2021 04:26:49 +0000 Subject: [PATCH 111/662] sp_BlitzFirst compilations threshold Only fire the compilation and recompilation warnings when we've had at least 75 compilations or recomps (because sp_BlitzFirst itself does around 50.) Working on #2770. --- sp_BlitzFirst.sql | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sp_BlitzFirst.sql b/sp_BlitzFirst.sql index c2b721508..dd14b27b2 100644 --- a/sp_BlitzFirst.sql +++ b/sp_BlitzFirst.sql @@ -2904,6 +2904,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, WHERE ps.Pass = 2 AND ps.object_name = @ServiceName + ':SQL Statistics' AND ps.counter_name = 'Batch Requests/sec' + AND psComp.value_delta > 75 /* Because sp_BlitzFirst does around 50 compilations and re-compilations */ AND (psComp.value_delta > (10 * @Seconds) OR psComp.value_delta > ps.value_delta) /* Either doing 10 compilations per second, or more compilations than queries */ AND (psComp.value_delta * 10) > ps.value_delta; /* Compilations are more than 10% of batch requests per second */ @@ -2930,6 +2931,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, WHERE ps.Pass = 2 AND ps.object_name = @ServiceName + ':SQL Statistics' AND ps.counter_name = 'Batch Requests/sec' + AND psComp.value_delta > 75 /* Because sp_BlitzFirst does around 50 compilations and re-compilations */ AND (psComp.value_delta > (10 * @Seconds) OR psComp.value_delta > ps.value_delta) /* Either doing 10 recompilations per second, or more recompilations than queries */ AND (psComp.value_delta * 10) > ps.value_delta; /* Recompilations are more than 10% of batch requests per second */ From 13dddb6df381e4c522e08bd36010cd3d40c1c5e9 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Mon, 22 Feb 2021 04:37:09 +0000 Subject: [PATCH 112/662] 2021_02 release - bumping version numbers Also adding sp_BlitzAnalysis to uninstall script, adding new updates to SqlServerVersions.sql. --- SqlServerVersions.sql | 2 ++ Uninstall.sql | 1 + sp_AllNightLog.sql | 2 +- sp_AllNightLog_Setup.sql | 2 +- sp_Blitz.sql | 2 +- sp_BlitzAnalysis.sql | 2 +- sp_BlitzBackups.sql | 2 +- sp_BlitzCache.sql | 2 +- sp_BlitzFirst.sql | 2 +- sp_BlitzInMemoryOLTP.sql | 2 +- sp_BlitzIndex.sql | 2 +- sp_BlitzLock.sql | 2 +- sp_BlitzQueryStore.sql | 2 +- sp_BlitzWho.sql | 2 +- sp_DatabaseRestore.sql | 2 +- sp_ineachdb.sql | 2 +- 16 files changed, 17 insertions(+), 14 deletions(-) diff --git a/SqlServerVersions.sql b/SqlServerVersions.sql index b265b6d98..dc20dd5fe 100644 --- a/SqlServerVersions.sql +++ b/SqlServerVersions.sql @@ -41,6 +41,7 @@ DELETE FROM dbo.SqlServerVersions; INSERT INTO dbo.SqlServerVersions (MajorVersionNumber, MinorVersionNumber, Branch, [Url], ReleaseDate, MainstreamSupportEndDate, ExtendedSupportEndDate, MajorVersionName, MinorVersionName) VALUES + (15, 4102, 'CU9', 'https://support.microsoft.com/en-us/help/5000642', '2021-02-11', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 9 '), (15, 4073, 'CU8 GDR', 'https://support.microsoft.com/en-us/help/4583459', '2021-01-12', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 8 GDR '), (15, 4073, 'CU8', 'https://support.microsoft.com/en-us/help/4577194', '2020-10-01', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 8 '), (15, 4063, 'CU7', 'https://support.microsoft.com/en-us/help/4570012', '2020-09-02', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 7 '), @@ -76,6 +77,7 @@ VALUES (14, 3008, 'RTM CU2', 'https://support.microsoft.com/en-us/help/4052574', '2017-11-28', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 2'), (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 '), + (13, 5882, 'SP2 CU16', 'https://support.microsoft.com/en-us/help/5000645', '2021-02-11', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 16'), (13, 5865, 'SP2 CU15 GDR', 'https://support.microsoft.com/en-us/help/4583461', '2021-01-12', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 15 GDR'), (13, 5850, 'SP2 CU15', 'https://support.microsoft.com/en-us/help/4577775', '2020-09-28', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 15'), (13, 5830, 'SP2 CU14', 'https://support.microsoft.com/en-us/help/4564903', '2020-08-06', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 14'), diff --git a/Uninstall.sql b/Uninstall.sql index ed6b2dc39..3a1b77533 100644 --- a/Uninstall.sql +++ b/Uninstall.sql @@ -17,6 +17,7 @@ IF OBJECT_ID('tempdb.dbo.#ToDelete') IS NOT NULL SELECT 'sp_AllNightLog' as ProcedureName INTO #ToDelete UNION SELECT 'sp_AllNightLog_Setup' as ProcedureName UNION SELECT 'sp_Blitz' as ProcedureName UNION +SELECT 'sp_BlitzAnalysis' as ProcedureName UNION SELECT 'sp_BlitzBackups' as ProcedureName UNION SELECT 'sp_BlitzCache' as ProcedureName UNION SELECT 'sp_BlitzFirst' as ProcedureName UNION diff --git a/sp_AllNightLog.sql b/sp_AllNightLog.sql index e7e10312a..7e142e1f7 100644 --- a/sp_AllNightLog.sql +++ b/sp_AllNightLog.sql @@ -30,7 +30,7 @@ SET NOCOUNT ON; BEGIN; -SELECT @Version = '8.0', @VersionDate = '20210117'; +SELECT @Version = '8.01', @VersionDate = '20210222'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_AllNightLog_Setup.sql b/sp_AllNightLog_Setup.sql index fd3231277..1d7faa8b3 100644 --- a/sp_AllNightLog_Setup.sql +++ b/sp_AllNightLog_Setup.sql @@ -36,7 +36,7 @@ SET NOCOUNT ON; BEGIN; -SELECT @Version = '8.0', @VersionDate = '20210117'; +SELECT @Version = '8.01', @VersionDate = '20210222'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_Blitz.sql b/sp_Blitz.sql index 763ca0bea..afaecc1e8 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -37,7 +37,7 @@ AS SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.0', @VersionDate = '20210117'; + SELECT @Version = '8.01', @VersionDate = '20210222'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) diff --git a/sp_BlitzAnalysis.sql b/sp_BlitzAnalysis.sql index 2a0addae8..e4e28b961 100644 --- a/sp_BlitzAnalysis.sql +++ b/sp_BlitzAnalysis.sql @@ -36,7 +36,7 @@ ALTER PROCEDURE [dbo].[sp_BlitzAnalysis] ( AS SET NOCOUNT ON; -SELECT @Version = '1.000', @VersionDate = '20210217'; +SELECT @Version = '8.01', @VersionDate = '20210222'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_BlitzBackups.sql b/sp_BlitzBackups.sql index 1ba5807bd..abe5f436a 100755 --- a/sp_BlitzBackups.sql +++ b/sp_BlitzBackups.sql @@ -23,7 +23,7 @@ AS SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.0', @VersionDate = '20210117'; + SELECT @Version = '8.01', @VersionDate = '20210222'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_BlitzCache.sql b/sp_BlitzCache.sql index bfd471d30..17d57b426 100644 --- a/sp_BlitzCache.sql +++ b/sp_BlitzCache.sql @@ -278,7 +278,7 @@ BEGIN SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.0', @VersionDate = '20210117'; +SELECT @Version = '8.01', @VersionDate = '20210222'; IF(@VersionCheckMode = 1) diff --git a/sp_BlitzFirst.sql b/sp_BlitzFirst.sql index dd14b27b2..85641df45 100644 --- a/sp_BlitzFirst.sql +++ b/sp_BlitzFirst.sql @@ -45,7 +45,7 @@ BEGIN SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.0', @VersionDate = '20210117'; +SELECT @Version = '8.01', @VersionDate = '20210222'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_BlitzInMemoryOLTP.sql b/sp_BlitzInMemoryOLTP.sql index 694698dab..e08427faf 100644 --- a/sp_BlitzInMemoryOLTP.sql +++ b/sp_BlitzInMemoryOLTP.sql @@ -82,7 +82,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI */ AS DECLARE @ScriptVersion VARCHAR(30); -SELECT @ScriptVersion = '1.8', @VersionDate = '20210117'; +SELECT @ScriptVersion = '1.8', @VersionDate = '20210222'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index 3e05b2f6a..d47eb23ba 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -45,7 +45,7 @@ AS SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.0', @VersionDate = '20210117'; +SELECT @Version = '8.01', @VersionDate = '20210222'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index 91ff6a052..392d24986 100644 --- a/sp_BlitzLock.sql +++ b/sp_BlitzLock.sql @@ -32,7 +32,7 @@ BEGIN SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.0', @VersionDate = '20210117'; +SELECT @Version = '8.01', @VersionDate = '20210222'; IF(@VersionCheckMode = 1) diff --git a/sp_BlitzQueryStore.sql b/sp_BlitzQueryStore.sql index aa601bece..6e84b8bb8 100644 --- a/sp_BlitzQueryStore.sql +++ b/sp_BlitzQueryStore.sql @@ -56,7 +56,7 @@ BEGIN /*First BEGIN*/ SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.0', @VersionDate = '20210117'; +SELECT @Version = '8.01', @VersionDate = '20210222'; IF(@VersionCheckMode = 1) BEGIN RETURN; diff --git a/sp_BlitzWho.sql b/sp_BlitzWho.sql index d3dcd66c2..6744bf0a5 100644 --- a/sp_BlitzWho.sql +++ b/sp_BlitzWho.sql @@ -29,7 +29,7 @@ BEGIN SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.0', @VersionDate = '20210117'; + SELECT @Version = '8.01', @VersionDate = '20210222'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_DatabaseRestore.sql b/sp_DatabaseRestore.sql index 843c86f83..7a9036fca 100755 --- a/sp_DatabaseRestore.sql +++ b/sp_DatabaseRestore.sql @@ -39,7 +39,7 @@ SET NOCOUNT ON; /*Versioning details*/ -SELECT @Version = '8.0', @VersionDate = '20210117'; +SELECT @Version = '8.01', @VersionDate = '20210222'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_ineachdb.sql b/sp_ineachdb.sql index 539bd6354..555ee9b80 100644 --- a/sp_ineachdb.sql +++ b/sp_ineachdb.sql @@ -34,7 +34,7 @@ AS BEGIN SET NOCOUNT ON; - SELECT @Version = '8.0', @VersionDate = '20210117'; + SELECT @Version = '8.01', @VersionDate = '20210222'; IF(@VersionCheckMode = 1) BEGIN From 568ab091e4744698ace6a7182545afc46e98a1a5 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Sun, 21 Feb 2021 20:48:38 -0800 Subject: [PATCH 113/662] 2021-02 release installer scripts --- Documentation/Development/Merge Blitz.ps1 | 13 +- Install-All-Scripts.sql | 39045 ++++++++++---------- Install-Core-Blitz-No-Query-Store.sql | 10883 +++--- Install-Core-Blitz-With-Query-Store.sql | 35601 +++++++++--------- 4 files changed, 42854 insertions(+), 42688 deletions(-) diff --git a/Documentation/Development/Merge Blitz.ps1 b/Documentation/Development/Merge Blitz.ps1 index 3109df3e5..d49045f71 100644 --- a/Documentation/Development/Merge Blitz.ps1 +++ b/Documentation/Development/Merge Blitz.ps1 @@ -1,32 +1,39 @@ #Set your file path $FilePath = "C:\temp\SQL-Server-First-Responder-Kit" $SqlVersionsPath = "$FilePath\SqlServerVersions.sql" +$BlitzFirstPath = "$FilePath\sp_BlitzFirst.sql" #All Core Blitz Without sp_BlitzQueryStore Get-ChildItem -Path "$FilePath" -Filter "sp_Blitz*.sql" | -Where-Object { $_.FullName -notlike "*sp_BlitzQueryStore.sql*" -and $_.FullName -notlike "*sp_BlitzInMemoryOLTP*"} | +Where-Object { $_.FullName -notlike "*sp_BlitzQueryStore.sql*" -and $_.FullName -notlike "*sp_BlitzInMemoryOLTP*" -and $_.FullName -notlike "*sp_BlitzFirst*" -and $_.FullName -notlike "*sp_BlitzAnalysis*"} | ForEach-Object { Get-Content $_.FullName } | Set-Content -Path "$FilePath\Install-Core-Blitz-No-Query-Store.sql" -Force #append script to (re-)create SqlServerVersions Table (https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2429) if ( test-path "$SqlVersionsPath") { Add-Content -Path "$FilePath\Install-Core-Blitz-No-Query-Store.sql" -Value (Get-Content -Path "$SqlVersionsPath")} +if ( test-path "$BlitzFirstPath") + { Add-Content -Path "$FilePath\Install-Core-Blitz-No-Query-Store.sql" -Value (Get-Content -Path "$BlitzFirstPath")} #All Core Blitz With sp_BlitzQueryStore Get-ChildItem -Path "$FilePath" -Filter "sp_Blitz*.sql" | -Where-Object { $_.FullName -notlike "*sp_BlitzInMemoryOLTP*"} | +Where-Object { $_.FullName -notlike "*sp_BlitzInMemoryOLTP*" -and $_.FullName -notlike "*sp_BlitzFirst*" -and $_.FullName -notlike "*sp_BlitzAnalysis*"} | ForEach-Object { Get-Content $_.FullName } | Set-Content -Path "$FilePath\Install-Core-Blitz-With-Query-Store.sql" -Force #append script to (re-)create SqlServerVersions Table (https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2429) if ( test-path "$SqlVersionsPath") { Add-Content -Path "$FilePath\Install-Core-Blitz-With-Query-Store.sql" -Value (Get-Content -Path "$SqlVersionsPath")} +if ( test-path "$BlitzFirstPath") + { Add-Content -Path "$FilePath\Install-Core-Blitz-With-Query-Store.sql" -Value (Get-Content -Path "$BlitzFirstPath")} #All Scripts Get-ChildItem -Path "$FilePath" -Filter "sp_*.sql" | -Where-Object { $_.FullName -notlike "*sp_BlitzInMemoryOLTP*"} | +Where-Object { $_.FullName -notlike "*sp_BlitzInMemoryOLTP*" -and $_.FullName -notlike "*sp_BlitzFirst*" -and $_.FullName -notlike "*sp_BlitzAnalysis*"} | ForEach-Object { Get-Content $_.FullName } | Set-Content -Path "$FilePath\Install-All-Scripts.sql" -Force #append script to (re-)create SqlServerVersions Table (https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2429) if ( test-path "$SqlVersionsPath") { Add-Content -Path "$FilePath\Install-All-Scripts.sql" -Value (Get-Content -Path "$SqlVersionsPath")} +if ( test-path "$BlitzFirstPath") + { Add-Content -Path "$FilePath\Install-All-Scripts.sql" -Value (Get-Content -Path "$BlitzFirstPath")} diff --git a/Install-All-Scripts.sql b/Install-All-Scripts.sql index fd026050c..af87b9405 100755 --- a/Install-All-Scripts.sql +++ b/Install-All-Scripts.sql @@ -30,7 +30,7 @@ SET NOCOUNT ON; BEGIN; -SELECT @Version = '8.0', @VersionDate = '20210117'; +SELECT @Version = '8.01', @VersionDate = '20210222'; IF(@VersionCheckMode = 1) BEGIN @@ -349,7 +349,7 @@ Pollster: SELECT 1 FROM msdbCentral.dbo.backup_worker bw WITH (READPAST) WHERE bw.last_log_backup_finish_time < '99991231' - AND bw.last_log_backup_start_time < DATEADD(MINUTE, -5, GETDATE()) + AND bw.last_log_backup_start_time < DATEADD(SECOND, (@rpo * -1), GETDATE()) AND EXISTS ( SELECT 1 FROM msdb.dbo.backupset b @@ -369,7 +369,7 @@ Pollster: bw.last_log_backup_start_time = '19000101' FROM msdbCentral.dbo.backup_worker bw WHERE bw.last_log_backup_finish_time < '99991231' - AND bw.last_log_backup_start_time < DATEADD(MINUTE, -5, GETDATE()) + AND bw.last_log_backup_start_time < DATEADD(SECOND, (@rpo * -1), GETDATE()) AND EXISTS ( SELECT 1 FROM msdb.dbo.backupset b @@ -583,7 +583,7 @@ DiskPollster: SELECT 1 FROM msdb.dbo.restore_worker rw WITH (READPAST) WHERE rw.last_log_restore_finish_time < '99991231' - AND rw.last_log_restore_start_time < DATEADD(MINUTE, -5, GETDATE()) + AND rw.last_log_restore_start_time < DATEADD(SECOND, (@rto * -1), GETDATE()) AND EXISTS ( SELECT 1 FROM msdb.dbo.restorehistory r @@ -603,7 +603,7 @@ DiskPollster: rw.last_log_restore_start_time = '19000101' FROM msdb.dbo.restore_worker rw WHERE rw.last_log_restore_finish_time < '99991231' - AND rw.last_log_restore_start_time < DATEADD(MINUTE, -5, GETDATE()) + AND rw.last_log_restore_start_time < DATEADD(SECOND, (@rto * -1), GETDATE()) AND EXISTS ( SELECT 1 FROM msdb.dbo.restorehistory r @@ -1524,7 +1524,7 @@ SET NOCOUNT ON; BEGIN; -SELECT @Version = '8.0', @VersionDate = '20210117'; +SELECT @Version = '8.01', @VersionDate = '20210222'; IF(@VersionCheckMode = 1) BEGIN @@ -2856,7 +2856,7 @@ AS SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.0', @VersionDate = '20210117'; + SELECT @Version = '8.01', @VersionDate = '20210222'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -9947,6 +9947,69 @@ IF @ProductVersionMajor >= 10 -- HAVING COUNT(DISTINCT o.object_id) > 0;'; --END; --of Check 220. + /*Check for the last good DBCC CHECKDB date */ + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 68 ) + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 68) WITH NOWAIT; + + EXEC sp_MSforeachdb N'USE [?]; + SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + INSERT #DBCCs + (ParentObject, + Object, + Field, + Value) + EXEC (''DBCC DBInfo() With TableResults, NO_INFOMSGS''); + UPDATE #DBCCs SET DbName = N''?'' WHERE DbName IS NULL OPTION (RECOMPILE);'; + + WITH DB2 + AS ( SELECT DISTINCT + Field , + Value , + DbName + FROM #DBCCs + INNER JOIN sys.databases d ON #DBCCs.DbName = d.name + WHERE Field = 'dbi_dbccLastKnownGood' + AND d.create_date < DATEADD(dd, -14, GETDATE()) + ) + INSERT INTO #BlitzResults + ( CheckID , + DatabaseName , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT 68 AS CheckID , + DB2.DbName AS DatabaseName , + 1 AS PRIORITY , + 'Reliability' AS FindingsGroup , + 'Last good DBCC CHECKDB over 2 weeks old' AS Finding , + 'https://BrentOzar.com/go/checkdb' AS URL , + 'Last successful CHECKDB: ' + + CASE DB2.Value + WHEN '1900-01-01 00:00:00.000' + THEN ' never.' + ELSE DB2.Value + END AS Details + FROM DB2 + WHERE DB2.DbName <> 'tempdb' + AND DB2.DbName NOT IN ( SELECT DISTINCT + DatabaseName + FROM + #SkipChecks + WHERE CheckID IS NULL OR CheckID = 68) + AND DB2.DbName NOT IN ( SELECT name + FROM sys.databases + WHERE is_read_only = 1) + AND CONVERT(DATETIME, DB2.Value, 121) < DATEADD(DD, + -14, + CURRENT_TIMESTAMP); + END; END; /* IF @CheckUserDatabaseObjects = 1 */ @@ -10376,69 +10439,6 @@ IF @ProductVersionMajor >= 10 WHERE s.service_account IS NULL AND ep.principal_id <> 1; END; - /*Check for the last good DBCC CHECKDB date */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 68 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 68) WITH NOWAIT; - - EXEC sp_MSforeachdb N'USE [?]; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - INSERT #DBCCs - (ParentObject, - Object, - Field, - Value) - EXEC (''DBCC DBInfo() With TableResults, NO_INFOMSGS''); - UPDATE #DBCCs SET DbName = N''?'' WHERE DbName IS NULL OPTION (RECOMPILE);'; - - WITH DB2 - AS ( SELECT DISTINCT - Field , - Value , - DbName - FROM #DBCCs - INNER JOIN sys.databases d ON #DBCCs.DbName = d.name - WHERE Field = 'dbi_dbccLastKnownGood' - AND d.create_date < DATEADD(dd, -14, GETDATE()) - ) - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 68 AS CheckID , - DB2.DbName AS DatabaseName , - 1 AS PRIORITY , - 'Reliability' AS FindingsGroup , - 'Last good DBCC CHECKDB over 2 weeks old' AS Finding , - 'https://BrentOzar.com/go/checkdb' AS URL , - 'Last successful CHECKDB: ' - + CASE DB2.Value - WHEN '1900-01-01 00:00:00.000' - THEN ' never.' - ELSE DB2.Value - END AS Details - FROM DB2 - WHERE DB2.DbName <> 'tempdb' - AND DB2.DbName NOT IN ( SELECT DISTINCT - DatabaseName - FROM - #SkipChecks - WHERE CheckID IS NULL OR CheckID = 68) - AND DB2.DbName NOT IN ( SELECT name - FROM sys.databases - WHERE is_read_only = 1) - AND CONVERT(DATETIME, DB2.Value, 121) < DATEADD(DD, - -14, - CURRENT_TIMESTAMP); - END; /*Verify that the servername is set */ IF NOT EXISTS ( SELECT 1 @@ -10560,21 +10560,29 @@ IF @ProductVersionMajor >= 10 SELECT 74 AS CheckID , 200 AS Priority , 'Informational' AS FindingsGroup , - 'TraceFlag On' AS Finding , + 'Trace Flag On' AS Finding , CASE WHEN [T].[TraceFlag] = '834' AND @ColumnStoreIndexesInUse = 1 THEN 'https://support.microsoft.com/en-us/kb/3210239' ELSE'https://www.BrentOzar.com/go/traceflags/' END AS URL , 'Trace flag ' + - CASE WHEN [T].[TraceFlag] = '2330' THEN ' 2330 enabled globally. Using this trace Flag disables missing index requests!' - WHEN [T].[TraceFlag] = '1211' THEN ' 1211 enabled globally. Using this Trace Flag disables lock escalation when you least expect it. No Bueno!' - WHEN [T].[TraceFlag] = '1224' THEN ' 1224 enabled globally. Using this Trace Flag disables lock escalation based on the number of locks being taken. You shouldn''t have done that, Dave.' - WHEN [T].[TraceFlag] = '652' THEN ' 652 enabled globally. Using this Trace Flag disables pre-fetching during index scans. If you hate slow queries, you should turn that off.' - WHEN [T].[TraceFlag] = '661' THEN ' 661 enabled globally. Using this Trace Flag disables ghost record removal. Who you gonna call? No one, turn that thing off.' - WHEN [T].[TraceFlag] = '1806' THEN ' 1806 enabled globally. Using this Trace Flag disables Instant File Initialization. I question your sanity.' - WHEN [T].[TraceFlag] = '3505' THEN ' 3505 enabled globally. Using this Trace Flag disables Checkpoints. Probably not the wisest idea.' - WHEN [T].[TraceFlag] = '8649' THEN ' 8649 enabled globally. Using this Trace Flag drops cost threshold for parallelism down to 0. I hope this is a dev server.' - WHEN [T].[TraceFlag] = '834' AND @ColumnStoreIndexesInUse = 1 THEN ' 834 is enabled globally. Using this Trace Flag with Columnstore Indexes is not a great idea.' - WHEN [T].[TraceFlag] = '8017' AND (CAST(SERVERPROPERTY('Edition') AS NVARCHAR(1000)) LIKE N'%Express%') THEN ' 8017 is enabled globally, which is the default for express edition.' - WHEN [T].[TraceFlag] = '8017' AND (CAST(SERVERPROPERTY('Edition') AS NVARCHAR(1000)) NOT LIKE N'%Express%') THEN ' 8017 is enabled globally. Using this Trace Flag disables creation schedulers for all logical processors. Not good.' + CASE WHEN [T].[TraceFlag] = '652' THEN '652 enabled globally, which disables pre-fetching during index scans. This is usually a very bad idea.' + WHEN [T].[TraceFlag] = '661' THEN '661 enabled globally, which disables ghost record removal, causing the database to grow in size. This is usually a very bad idea.' + WHEN [T].[TraceFlag] = '834' AND @ColumnStoreIndexesInUse = 1 THEN '834 is enabled globally, but you also have columnstore indexes. That combination is not recommended by Microsoft.' + WHEN [T].[TraceFlag] = '1117' THEN '1117 enabled globally, which grows all files in a filegroup at the same time.' + WHEN [T].[TraceFlag] = '1118' THEN '1118 enabled globally, which tries to reduce SGAM waits.' + WHEN [T].[TraceFlag] = '1211' THEN '1211 enabled globally, which disables lock escalation when you least expect it. This is usually a very bad idea.' + WHEN [T].[TraceFlag] = '1222' THEN '1222 enabled globally, which captures deadlock graphs in the error log.' + WHEN [T].[TraceFlag] = '1224' THEN '1224 enabled globally, which disables lock escalation until the server has memory pressure. This is usually a very bad idea.' + WHEN [T].[TraceFlag] = '1806' THEN '1806 enabled globally, which disables Instant File Initialization, causing restores and file growths to take longer. This is usually a very bad idea.' + WHEN [T].[TraceFlag] = '2330' THEN '2330 enabled globally, which disables missing index requests. This is usually a very bad idea.' + WHEN [T].[TraceFlag] = '2371' THEN '2371 enabled globally, which changes the auto update stats threshold.' + WHEN [T].[TraceFlag] = '3023' THEN '3023 enabled globally, which performs checksums by default when doing database backups.' + WHEN [T].[TraceFlag] = '3226' THEN '3226 enabled globally, which keeps the event log clean by not reporting successful backups.' + WHEN [T].[TraceFlag] = '3505' THEN '3505 enabled globally, which disables Checkpoints. This is usually a very bad idea.' + WHEN [T].[TraceFlag] = '4199' THEN '4199 enabled globally, which enables non-default Query Optimizer fixes, changing query plans from the default behaviors.' + WHEN [T].[TraceFlag] = '8048' THEN '8048 enabled globally, which tries to reduce CMEMTHREAD waits on servers with a lot of logical processors.' + WHEN [T].[TraceFlag] = '8017' AND (CAST(SERVERPROPERTY('Edition') AS NVARCHAR(1000)) LIKE N'%Express%') THEN '8017 is enabled globally, but this is the default for Express Edition.' + WHEN [T].[TraceFlag] = '8017' AND (CAST(SERVERPROPERTY('Edition') AS NVARCHAR(1000)) NOT LIKE N'%Express%') THEN '8017 is enabled globally, which disables the creation of schedulers for all logical processors.' + WHEN [T].[TraceFlag] = '8649' THEN '8649 enabled globally, which cost threshold for parallelism down to 0. This is usually a very bad idea.' ELSE [T].[TraceFlag] + ' is enabled globally.' END AS Details FROM #TraceStatus T; @@ -11644,7 +11652,7 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 250 AS Priority , ''Server Info'' AS FindingsGroup , ''Azure Managed Instance'' AS Finding , - ''https://www.BrenOzar.com/go/azurevm'' AS URL , + ''https://www.BrentOzar.com/go/azurevm'' AS URL , ''cpu_rate: '' + CAST(COALESCE(cpu_rate, 0) AS VARCHAR(20)) + '', memory_limit_mb: '' + CAST(COALESCE(memory_limit_mb, 0) AS NVARCHAR(20)) + '', process_memory_limit_mb: '' + CAST(COALESCE(process_memory_limit_mb, 0) AS NVARCHAR(20)) + @@ -11672,7 +11680,7 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 CREATE TABLE #services (cmdshell_output varchar(max)); INSERT INTO #services - EXEC xp_cmdshell 'net start' + EXEC /**/xp_cmdshell/**/ 'net start' /* added comments around command since some firewalls block this string TL 20210221 */ IF EXISTS (SELECT 1 FROM #services @@ -12285,7 +12293,7 @@ AS SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.0', @VersionDate = '20210117'; + SELECT @Version = '8.01', @VersionDate = '20210222'; IF(@VersionCheckMode = 1) BEGIN @@ -14064,7 +14072,7 @@ BEGIN SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.0', @VersionDate = '20210117'; +SELECT @Version = '8.01', @VersionDate = '20210222'; IF(@VersionCheckMode = 1) @@ -16123,7 +16131,7 @@ BEGIN WHEN N'writes' THEN N'AND total_logical_writes > 0' WHEN N'duration' THEN N'AND total_elapsed_time > 0' WHEN N'executions' THEN N'AND execution_count > 0' - WHEN N'compiles' THEN N'AND (age_minutes + age_minutes_lifetime) > 0' + /* WHEN N'compiles' THEN N'AND (age_minutes + age_minutes_lifetime) > 0' BGO 2021-01-24 commenting out for https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2772 */ WHEN N'memory grant' THEN N'AND max_grant_kb > 0' WHEN N'unused grant' THEN N'AND max_grant_kb > 0' WHEN N'spills' THEN N'AND max_spills > 0' @@ -16138,6 +16146,7 @@ BEGIN WHEN COALESCE(age_minutes, age_minutes_lifetime, 0) = 0 THEN 0 ELSE CAST((1.00 * execution_count / COALESCE(age_minutes, age_minutes_lifetime)) AS money) END > 0' + ELSE N' /* No minimum threshold set */ ' END; SET @sql += REPLACE(@body_where, 'cached_time', 'creation_time') ; @@ -18718,7 +18727,8 @@ BEGIN PlanHandle AS [Plan Handle], SqlHandle AS [SQL Handle], COALESCE(SetOptions, '''') AS [SET Options], - QueryHash AS [Query Hash], + QueryHash AS [Query Hash], + PlanGenerationNum, [Remove Plan Handle From Cache]'; END; ELSE @@ -20261,6 +20271,7 @@ IF OBJECT_ID('tempdb.. #bou_allsort') IS NULL SqlHandle VARBINARY(64), SetOptions VARCHAR(MAX), QueryHash BINARY(8), + PlanGenerationNum NVARCHAR(30), RemovePlanHandleFromCache NVARCHAR(200), Pattern NVARCHAR(20) ); @@ -20276,7 +20287,7 @@ SET @AllSortSql += N' INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, RemovePlanHandleFromCache ) + MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''cpu'', @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; @@ -20288,7 +20299,7 @@ SET @AllSortSql += N' INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, RemovePlanHandleFromCache ) + MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''reads'', @IgnoreSqlHandles = @ISH, @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; @@ -20300,7 +20311,7 @@ SET @AllSortSql += N' INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, RemovePlanHandleFromCache ) + MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''writes'', @IgnoreSqlHandles = @ISH, @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; @@ -20312,7 +20323,7 @@ SET @AllSortSql += N' INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, RemovePlanHandleFromCache ) + MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''duration'', @IgnoreSqlHandles = @ISH, @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; @@ -20324,7 +20335,7 @@ SET @AllSortSql += N' INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, RemovePlanHandleFromCache ) + MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''executions'', @IgnoreSqlHandles = @ISH, @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; @@ -20359,7 +20370,7 @@ SET @AllSortSql += N' INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, RemovePlanHandleFromCache ) + MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''memory grant'', @IgnoreSqlHandles = @ISH, @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; @@ -20408,7 +20419,7 @@ SET @AllSortSql += N' INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, RemovePlanHandleFromCache ) + MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''spills'', @IgnoreSqlHandles = @ISH, @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; @@ -20433,7 +20444,7 @@ SET @AllSortSql += N' SET @AllSortSql += N' SELECT DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters,ExecutionCount, ExecutionsPerMinute, ExecutionWeight, TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, RemovePlanHandleFromCache, Pattern + MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache, Pattern FROM #bou_allsort ORDER BY Id OPTION(RECOMPILE); '; @@ -20451,7 +20462,7 @@ SET @AllSortSql += N' INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, RemovePlanHandleFromCache ) + MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''avg cpu'', @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; @@ -20463,7 +20474,7 @@ SET @AllSortSql += N' INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, RemovePlanHandleFromCache ) + MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''avg reads'', @IgnoreSqlHandles = @ISH, @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; @@ -20475,7 +20486,7 @@ SET @AllSortSql += N' INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, RemovePlanHandleFromCache ) + MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''avg writes'', @IgnoreSqlHandles = @ISH, @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; @@ -20487,7 +20498,7 @@ SET @AllSortSql += N' INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, RemovePlanHandleFromCache ) + MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''avg duration'', @IgnoreSqlHandles = @ISH, @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; @@ -20499,7 +20510,7 @@ SET @AllSortSql += N' INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, RemovePlanHandleFromCache ) + MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''avg executions'', @IgnoreSqlHandles = @ISH, @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; @@ -20534,7 +20545,7 @@ SET @AllSortSql += N' INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, RemovePlanHandleFromCache ) + MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''avg memory grant'', @IgnoreSqlHandles = @ISH, @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; @@ -20583,7 +20594,7 @@ SET @AllSortSql += N' INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, RemovePlanHandleFromCache ) + MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''avg spills'', @IgnoreSqlHandles = @ISH, @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; @@ -20609,7 +20620,7 @@ SET @AllSortSql += N' SET @AllSortSql += N' SELECT DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters,ExecutionCount, ExecutionsPerMinute, ExecutionWeight, TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, RemovePlanHandleFromCache, Pattern + MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache, Pattern FROM #bou_allsort ORDER BY Id OPTION(RECOMPILE); '; @@ -20817,7 +20828,7 @@ BEGIN ALTER TABLE '+@OutputDatabaseName+'.'+@OutputSchemaName+'.'+@OutputTableName+' DROP COLUMN [PlanCreationTimeHours]; ALTER TABLE '+@OutputDatabaseName+'.'+@OutputSchemaName+'.'+@OutputTableName+' ADD [PlanCreationTimeHours] AS DATEDIFF(HOUR,CONVERT(DATETIMEOFFSET(7),[PlanCreationTime]),[CheckDate]); END '; - + IF @ValidOutputServer = 1 BEGIN SET @StringToExecute = REPLACE(@StringToExecute,''''+@OutputSchemaName+'''',''''''+@OutputSchemaName+''''''); @@ -21200,54 +21211,55 @@ END; /* End of writing results to table */ END; /*Final End*/ GO -IF OBJECT_ID('dbo.sp_BlitzFirst') IS NULL - EXEC ('CREATE PROCEDURE dbo.sp_BlitzFirst AS RETURN 0;'); +SET ANSI_NULLS ON; +SET ANSI_PADDING ON; +SET ANSI_WARNINGS ON; +SET ARITHABORT ON; +SET CONCAT_NULL_YIELDS_NULL ON; +SET QUOTED_IDENTIFIER ON; +SET STATISTICS IO OFF; +SET STATISTICS TIME OFF; GO +IF OBJECT_ID('dbo.sp_BlitzIndex') IS NULL + EXEC ('CREATE PROCEDURE dbo.sp_BlitzIndex AS RETURN 0;'); +GO -ALTER PROCEDURE [dbo].[sp_BlitzFirst] - @LogMessage NVARCHAR(4000) = NULL , - @Help TINYINT = 0 , - @AsOf DATETIMEOFFSET = NULL , - @ExpertMode TINYINT = 0 , - @Seconds INT = 5 , - @OutputType VARCHAR(20) = 'TABLE' , +ALTER PROCEDURE dbo.sp_BlitzIndex + @DatabaseName NVARCHAR(128) = NULL, /*Defaults to current DB if not specified*/ + @SchemaName NVARCHAR(128) = NULL, /*Requires table_name as well.*/ + @TableName NVARCHAR(128) = NULL, /*Requires schema_name as well.*/ + @Mode TINYINT=0, /*0=Diagnose, 1=Summarize, 2=Index Usage Detail, 3=Missing Index Detail, 4=Diagnose Details*/ + /*Note:@Mode doesn't matter if you're specifying schema_name and @TableName.*/ + @Filter TINYINT = 0, /* 0=no filter (default). 1=No low-usage warnings for objects with 0 reads. 2=Only warn for objects >= 500MB */ + /*Note:@Filter doesn't do anything unless @Mode=0*/ + @SkipPartitions BIT = 0, + @SkipStatistics BIT = 1, + @GetAllDatabases BIT = 0, + @BringThePain BIT = 0, + @IgnoreDatabases NVARCHAR(MAX) = NULL, /* Comma-delimited list of databases you want to skip */ + @ThresholdMB INT = 250 /* Number of megabytes that an object must be before we include it in basic results */, + @OutputType VARCHAR(20) = 'TABLE' , @OutputServerName NVARCHAR(256) = NULL , @OutputDatabaseName NVARCHAR(256) = NULL , @OutputSchemaName NVARCHAR(256) = NULL , @OutputTableName NVARCHAR(256) = NULL , - @OutputTableNameFileStats NVARCHAR(256) = NULL , - @OutputTableNamePerfmonStats NVARCHAR(256) = NULL , - @OutputTableNameWaitStats NVARCHAR(256) = NULL , - @OutputTableNameBlitzCache NVARCHAR(256) = NULL , - @OutputTableNameBlitzWho NVARCHAR(256) = NULL , - @OutputTableRetentionDays TINYINT = 7 , - @OutputXMLasNVARCHAR TINYINT = 0 , - @FilterPlansByDatabase VARCHAR(MAX) = NULL , - @CheckProcedureCache TINYINT = 0 , - @CheckServerInfo TINYINT = 1 , - @FileLatencyThresholdMS INT = 100 , - @SinceStartup TINYINT = 0 , - @ShowSleepingSPIDs TINYINT = 0 , - @BlitzCacheSkipAnalysis BIT = 1 , - @MemoryGrantThresholdPct DECIMAL(5,2) = 15.00, - @LogMessageCheckID INT = 38, - @LogMessagePriority TINYINT = 1, - @LogMessageFindingsGroup VARCHAR(50) = 'Logged Message', - @LogMessageFinding VARCHAR(200) = 'Logged from sp_BlitzFirst', - @LogMessageURL VARCHAR(200) = '', - @LogMessageCheckDate DATETIMEOFFSET = NULL, - @Debug BIT = 0, - @Version VARCHAR(30) = NULL OUTPUT, + @IncludeInactiveIndexes BIT = 0 /* Will skip indexes with no reads or writes */, + @ShowAllMissingIndexRequests BIT = 0 /*Will make all missing index requests show up*/, + @SortOrder NVARCHAR(50) = NULL, /* Only affects @Mode = 2. */ + @SortDirection NVARCHAR(4) = 'DESC', /* Only affects @Mode = 2. */ + @Help TINYINT = 0, + @Debug BIT = 0, + @Version VARCHAR(30) = NULL OUTPUT, @VersionDate DATETIME = NULL OUTPUT, @VersionCheckMode BIT = 0 - WITH EXECUTE AS CALLER, RECOMPILE +WITH RECOMPILE AS -BEGIN SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.0', @VersionDate = '20210117'; +SELECT @Version = '8.01', @VersionDate = '20210222'; +SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) BEGIN @@ -21257,32 +21269,28 @@ END; IF @Help = 1 BEGIN PRINT ' -sp_BlitzFirst from http://FirstResponderKit.org +/* +sp_BlitzIndex from http://FirstResponderKit.org -This script gives you a prioritized list of why your SQL Server is slow right now. - -This is not an overall health check - for that, check out sp_Blitz. +This script analyzes the design and performance of your indexes. To learn more, visit http://FirstResponderKit.org where you can download new versions for free, watch training videos on how it works, get more info on the findings, contribute your own code, and more. Known limitations of this version: - - Only Microsoft-supported versions of SQL Server. Sorry, 2005 and 2000. It - may work just fine on 2005, and if it does, hug your parents. Just don''t - file support issues if it breaks. - - If a temp table called #CustomPerfmonCounters exists for any other session, - but not our session, this stored proc will fail with an error saying the - temp table #CustomPerfmonCounters does not exist. - - @OutputServerName is not functional yet. - - If @OutputDatabaseName, SchemaName, TableName, etc are quoted with brackets, - the write to table may silently fail. Look, I never said I was good at this. + - Only Microsoft-supported versions of SQL Server. Sorry, 2005 and 2000. + - Index create statements are just to give you a rough idea of the syntax. It includes filters and fillfactor. + -- Example 1: index creates use ONLINE=? instead of ONLINE=ON / ONLINE=OFF. This is because it is important + for the user to understand if it is going to be offline and not just run a script. + -- Example 2: they do not include all the options the index may have been created with (padding, compression + filegroup/partition scheme etc.) + -- (The compression and filegroup index create syntax is not trivial because it is set at the partition + level and is not trivial to code.) + - Does not advise you about data modeling for clustered indexes and primary keys (primarily looks for signs of insanity.) Unknown limitations of this version: - - None. Like Zombo.com, the only limit is yourself. - -Changes - for the full list of improvements and fixes in this version, see: -https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/ + - We knew them once, but we forgot. MIT License @@ -21306,20947 +21314,20992 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - '; RETURN; END; /* @Help = 1 */ -RAISERROR('Setting up configuration variables',10,1) WITH NOWAIT; -DECLARE @StringToExecute NVARCHAR(MAX), - @ParmDefinitions NVARCHAR(4000), - @Parm1 NVARCHAR(4000), - @OurSessionID INT, - @LineFeed NVARCHAR(10), - @StockWarningHeader NVARCHAR(MAX) = N'', - @StockWarningFooter NVARCHAR(MAX) = N'', - @StockDetailsHeader NVARCHAR(MAX) = N'', - @StockDetailsFooter NVARCHAR(MAX) = N'', - @StartSampleTime DATETIMEOFFSET, - @FinishSampleTime DATETIMEOFFSET, - @FinishSampleTimeWaitFor DATETIME, - @AsOf1 DATETIMEOFFSET, - @AsOf2 DATETIMEOFFSET, - @ServiceName sysname, - @OutputTableNameFileStats_View NVARCHAR(256), - @OutputTableNamePerfmonStats_View NVARCHAR(256), - @OutputTableNamePerfmonStatsActuals_View NVARCHAR(256), - @OutputTableNameWaitStats_View NVARCHAR(256), - @OutputTableNameWaitStats_Categories NVARCHAR(256), - @OutputTableCleanupDate DATE, - @ObjectFullName NVARCHAR(2000), - @BlitzWho NVARCHAR(MAX) = N'EXEC dbo.sp_BlitzWho @ShowSleepingSPIDs = ' + CONVERT(NVARCHAR(1), @ShowSleepingSPIDs) + N';', - @BlitzCacheMinutesBack INT, - @UnquotedOutputServerName NVARCHAR(256) = @OutputServerName , - @UnquotedOutputDatabaseName NVARCHAR(256) = @OutputDatabaseName , - @UnquotedOutputSchemaName NVARCHAR(256) = @OutputSchemaName , - @LocalServerName NVARCHAR(128) = CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128)), - @dm_exec_query_statistics_xml BIT = 0; +DECLARE @ScriptVersionName NVARCHAR(50); +DECLARE @DaysUptime NUMERIC(23,2); +DECLARE @DatabaseID INT; +DECLARE @ObjectID INT; +DECLARE @dsql NVARCHAR(MAX); +DECLARE @params NVARCHAR(MAX); +DECLARE @msg NVARCHAR(4000); +DECLARE @ErrorSeverity INT; +DECLARE @ErrorState INT; +DECLARE @Rowcount BIGINT; +DECLARE @SQLServerProductVersion NVARCHAR(128); +DECLARE @SQLServerEdition INT; +DECLARE @FilterMB INT; +DECLARE @collation NVARCHAR(256); +DECLARE @NumDatabases INT; +DECLARE @LineFeed NVARCHAR(5); +DECLARE @DaysUptimeInsertValue NVARCHAR(256); +DECLARE @DatabaseToIgnore NVARCHAR(MAX); +DECLARE @ColumnList NVARCHAR(MAX); -/* Sanitize our inputs */ -SELECT - @OutputTableNameFileStats_View = QUOTENAME(@OutputTableNameFileStats + '_Deltas'), - @OutputTableNamePerfmonStats_View = QUOTENAME(@OutputTableNamePerfmonStats + '_Deltas'), - @OutputTableNamePerfmonStatsActuals_View = QUOTENAME(@OutputTableNamePerfmonStats + '_Actuals'), - @OutputTableNameWaitStats_View = QUOTENAME(@OutputTableNameWaitStats + '_Deltas'), - @OutputTableNameWaitStats_Categories = QUOTENAME(@OutputTableNameWaitStats + '_Categories'); +/* Let's get @SortOrder set to lower case here for comparisons later */ +SET @SortOrder = REPLACE(LOWER(@SortOrder), N' ', N'_'); +SET @SortDirection = LOWER(@SortDirection); -SELECT - @OutputDatabaseName = QUOTENAME(@OutputDatabaseName), - @OutputSchemaName = QUOTENAME(@OutputSchemaName), - @OutputTableName = QUOTENAME(@OutputTableName), - @OutputTableNameFileStats = QUOTENAME(@OutputTableNameFileStats), - @OutputTableNamePerfmonStats = QUOTENAME(@OutputTableNamePerfmonStats), - @OutputTableNameWaitStats = QUOTENAME(@OutputTableNameWaitStats), - @OutputTableCleanupDate = CAST( (DATEADD(DAY, -1 * @OutputTableRetentionDays, GETDATE() ) ) AS DATE), - /* @OutputTableNameBlitzCache = QUOTENAME(@OutputTableNameBlitzCache), We purposely don't sanitize this because sp_BlitzCache will */ - /* @OutputTableNameBlitzWho = QUOTENAME(@OutputTableNameBlitzWho), We purposely don't sanitize this because sp_BlitzWho will */ - @LineFeed = CHAR(13) + CHAR(10), - @OurSessionID = @@SPID, - @OutputType = UPPER(@OutputType); +SET @LineFeed = CHAR(13) + CHAR(10); +SELECT @SQLServerProductVersion = CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)); +SELECT @SQLServerEdition =CAST(SERVERPROPERTY('EngineEdition') AS INT); /* We default to online index creates where EngineEdition=3*/ +SET @FilterMB=250; +SELECT @ScriptVersionName = 'sp_BlitzIndex(TM) v' + @Version + ' - ' + DATENAME(MM, @VersionDate) + ' ' + RIGHT('0'+DATENAME(DD, @VersionDate),2) + ', ' + DATENAME(YY, @VersionDate); +SET @IgnoreDatabases = REPLACE(REPLACE(LTRIM(RTRIM(@IgnoreDatabases)), CHAR(10), ''), CHAR(13), ''); -IF(@OutputType = 'NONE' AND (@OutputTableName IS NULL OR @OutputSchemaName IS NULL OR @OutputDatabaseName IS NULL)) +RAISERROR(N'Starting run. %s', 0,1, @ScriptVersionName) WITH NOWAIT; + +IF(@OutputType NOT IN ('TABLE','NONE')) BEGIN - RAISERROR('This procedure should be called with a value for all @Output* parameters, as @OutputType is set to NONE',12,1); + RAISERROR('Invalid value for parameter @OutputType. Expected: (TABLE;NONE)',12,1); RETURN; END; - -IF UPPER(@OutputType) LIKE 'TOP 10%' SET @OutputType = 'Top10'; -IF @OutputType = 'Top10' SET @SinceStartup = 1; - -/* Logged Message - CheckID 38 */ -IF @LogMessage IS NOT NULL + +IF(@OutputType = 'NONE') +BEGIN + IF(@OutputTableName IS NULL OR @OutputSchemaName IS NULL OR @OutputDatabaseName IS NULL) BEGIN - - RAISERROR('Saving LogMessage to table',10,1) WITH NOWAIT; - - /* Try to set the output table parameters if they don't exist */ - IF @OutputSchemaName IS NULL AND @OutputTableName IS NULL AND @OutputDatabaseName IS NULL - BEGIN - SET @OutputSchemaName = N'[dbo]'; - SET @OutputTableName = N'[BlitzFirst]'; - - /* Look for the table in the current database */ - SELECT TOP 1 @OutputDatabaseName = QUOTENAME(TABLE_CATALOG) - FROM INFORMATION_SCHEMA.TABLES - WHERE TABLE_SCHEMA = 'dbo' AND TABLE_NAME = 'BlitzFirst'; - - IF @OutputDatabaseName IS NULL AND EXISTS (SELECT * FROM sys.databases WHERE name = 'DBAtools') - SET @OutputDatabaseName = '[DBAtools]'; - - END; - - IF @OutputDatabaseName IS NULL OR @OutputSchemaName IS NULL OR @OutputTableName IS NULL - OR NOT EXISTS ( SELECT * - FROM sys.databases - WHERE QUOTENAME([name]) = @OutputDatabaseName) - BEGIN - RAISERROR('We have a hard time logging a message without a valid @OutputDatabaseName, @OutputSchemaName, and @OutputTableName to log it to.', 0, 1) WITH NOWAIT; - RETURN; - END; - IF @LogMessageCheckDate IS NULL - SET @LogMessageCheckDate = SYSDATETIMEOFFSET(); - SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' - + @OutputSchemaName + ''') INSERT ' - + @OutputDatabaseName + '.' - + @OutputSchemaName + '.' - + @OutputTableName - + ' (ServerName, CheckDate, CheckID, Priority, FindingsGroup, Finding, Details, URL) VALUES( ' - + ' @SrvName, @LogMessageCheckDate, @LogMessageCheckID, @LogMessagePriority, @LogMessageFindingsGroup, @LogMessageFinding, @LogMessage, @LogMessageURL)'; - - EXECUTE sp_executesql @StringToExecute, - N'@SrvName NVARCHAR(128), @LogMessageCheckID INT, @LogMessagePriority TINYINT, @LogMessageFindingsGroup VARCHAR(50), @LogMessageFinding VARCHAR(200), @LogMessage NVARCHAR(4000), @LogMessageCheckDate DATETIMEOFFSET, @LogMessageURL VARCHAR(200)', - @LocalServerName, @LogMessageCheckID, @LogMessagePriority, @LogMessageFindingsGroup, @LogMessageFinding, @LogMessage, @LogMessageCheckDate, @LogMessageURL; - - RAISERROR('LogMessage saved to table. We have made a note of your activity. Keep up the good work.',10,1) WITH NOWAIT; - - RETURN; + RAISERROR('This procedure should be called with a value for @Output* parameters, as @OutputType is set to NONE',12,1); + RETURN; END; - -IF @SinceStartup = 1 - SELECT @Seconds = 0, @ExpertMode = 1; - - -IF @OutputType = 'SCHEMA' -BEGIN - SELECT FieldList = '[Priority] TINYINT, [FindingsGroup] VARCHAR(50), [Finding] VARCHAR(200), [URL] VARCHAR(200), [Details] NVARCHAR(4000), [HowToStopIt] NVARCHAR(MAX), [QueryPlan] XML, [QueryText] NVARCHAR(MAX)'; - + IF(@BringThePain = 1) + BEGIN + RAISERROR('Incompatible Parameters: @BringThePain set to 1 and @OutputType set to NONE',12,1); + RETURN; + END; + /* Eventually limit by mode + IF(@Mode not in (0,4)) + BEGIN + RAISERROR('Incompatible Parameters: @Mode set to %d and @OutputType set to NONE',12,1,@Mode); + RETURN; + END; + */ END; -ELSE IF @AsOf IS NOT NULL AND @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @OutputTableName IS NOT NULL -BEGIN - /* They want to look into the past. */ - SET @AsOf1= DATEADD(mi, -15, @AsOf); - SET @AsOf2= DATEADD(mi, +15, @AsOf); - - SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' - + @OutputSchemaName + ''') SELECT CheckDate, [Priority], [FindingsGroup], [Finding], [URL], CAST([Details] AS [XML]) AS Details,' - + '[HowToStopIt], [CheckID], [StartTime], [LoginName], [NTUserName], [OriginalLoginName], [ProgramName], [HostName], [DatabaseID],' - + '[DatabaseName], [OpenTransactionCount], [QueryPlan], [QueryText] FROM ' - + @OutputDatabaseName + '.' - + @OutputSchemaName + '.' - + @OutputTableName - + ' WHERE CheckDate >= @AsOf1' - + ' AND CheckDate <= @AsOf2' - + ' /*ORDER BY CheckDate, Priority , FindingsGroup , Finding , Details*/;'; - EXEC sp_executesql @StringToExecute, - N'@AsOf1 DATETIMEOFFSET, @AsOf2 DATETIMEOFFSET', - @AsOf1, @AsOf2 - -END; /* IF @AsOf IS NOT NULL AND @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @OutputTableName IS NOT NULL */ -ELSE IF @LogMessage IS NULL /* IF @OutputType = 'SCHEMA' */ -BEGIN - /* What's running right now? This is the first and last result set. */ - IF @SinceStartup = 0 AND @Seconds > 0 AND @ExpertMode = 1 AND @OutputType <> 'NONE' - BEGIN - IF OBJECT_ID('master.dbo.sp_BlitzWho') IS NULL AND OBJECT_ID('dbo.sp_BlitzWho') IS NULL - BEGIN - PRINT N'sp_BlitzWho is not installed in the current database_files. You can get a copy from http://FirstResponderKit.org'; - END; - ELSE - BEGIN - EXEC (@BlitzWho); - END; - END; /* IF @SinceStartup = 0 AND @Seconds > 0 AND @ExpertMode = 1 AND @OutputType <> 'NONE' - What's running right now? This is the first and last result set. */ +IF OBJECT_ID('tempdb..#IndexSanity') IS NOT NULL + DROP TABLE #IndexSanity; - /* Set start/finish times AFTER sp_BlitzWho runs. For more info: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2244 */ - IF @Seconds = 0 AND SERVERPROPERTY('Edition') = 'SQL Azure' - WITH WaitTimes AS ( - SELECT wait_type, wait_time_ms, - NTILE(3) OVER(ORDER BY wait_time_ms) AS grouper - FROM sys.dm_os_wait_stats w - WHERE wait_type IN ('DIRTY_PAGE_POLL','HADR_FILESTREAM_IOMGR_IOCOMPLETION','LAZYWRITER_SLEEP', - 'LOGMGR_QUEUE','REQUEST_FOR_DEADLOCK_SEARCH','XE_TIMER_EVENT') - ) - SELECT @StartSampleTime = DATEADD(mi, AVG(-wait_time_ms / 1000 / 60), SYSDATETIMEOFFSET()), @FinishSampleTime = SYSDATETIMEOFFSET() - FROM WaitTimes - WHERE grouper = 2; - ELSE IF @Seconds = 0 AND SERVERPROPERTY('Edition') <> 'SQL Azure' - SELECT @StartSampleTime = DATEADD(MINUTE,DATEDIFF(MINUTE, GETDATE(), GETUTCDATE()),create_date) , @FinishSampleTime = SYSDATETIMEOFFSET() - FROM sys.databases - WHERE database_id = 2; - ELSE - SELECT @StartSampleTime = SYSDATETIMEOFFSET(), - @FinishSampleTime = DATEADD(ss, @Seconds, SYSDATETIMEOFFSET()), - @FinishSampleTimeWaitFor = DATEADD(ss, @Seconds, GETDATE()); +IF OBJECT_ID('tempdb..#IndexPartitionSanity') IS NOT NULL + DROP TABLE #IndexPartitionSanity; +IF OBJECT_ID('tempdb..#IndexSanitySize') IS NOT NULL + DROP TABLE #IndexSanitySize; - RAISERROR('Now starting diagnostic analysis',10,1) WITH NOWAIT; +IF OBJECT_ID('tempdb..#IndexColumns') IS NOT NULL + DROP TABLE #IndexColumns; - /* - We start by creating #BlitzFirstResults. It's a temp table that will store - the results from our checks. Throughout the rest of this stored procedure, - we're running a series of checks looking for dangerous things inside the SQL - Server. When we find a problem, we insert rows into the temp table. At the - end, we return these results to the end user. +IF OBJECT_ID('tempdb..#MissingIndexes') IS NOT NULL + DROP TABLE #MissingIndexes; - #BlitzFirstResults has a CheckID field, but there's no Check table. As we do - checks, we insert data into this table, and we manually put in the CheckID. - We (Brent Ozar Unlimited) maintain a list of the checks by ID#. You can - download that from http://FirstResponderKit.org if you want to build - a tool that relies on the output of sp_BlitzFirst. - */ +IF OBJECT_ID('tempdb..#ForeignKeys') IS NOT NULL + DROP TABLE #ForeignKeys; +IF OBJECT_ID('tempdb..#BlitzIndexResults') IS NOT NULL + DROP TABLE #BlitzIndexResults; + +IF OBJECT_ID('tempdb..#IndexCreateTsql') IS NOT NULL + DROP TABLE #IndexCreateTsql; - IF OBJECT_ID('tempdb..#BlitzFirstResults') IS NOT NULL - DROP TABLE #BlitzFirstResults; - CREATE TABLE #BlitzFirstResults - ( - ID INT IDENTITY(1, 1) PRIMARY KEY CLUSTERED, - CheckID INT NOT NULL, - Priority TINYINT NOT NULL, - FindingsGroup VARCHAR(50) NOT NULL, - Finding VARCHAR(200) NOT NULL, - URL VARCHAR(200) NULL, - Details NVARCHAR(MAX) NULL, - HowToStopIt NVARCHAR(MAX) NULL, - QueryPlan [XML] NULL, - QueryText NVARCHAR(MAX) NULL, - StartTime DATETIMEOFFSET NULL, - LoginName NVARCHAR(128) NULL, - NTUserName NVARCHAR(128) NULL, - OriginalLoginName NVARCHAR(128) NULL, - ProgramName NVARCHAR(128) NULL, - HostName NVARCHAR(128) NULL, - DatabaseID INT NULL, - DatabaseName NVARCHAR(128) NULL, - OpenTransactionCount INT NULL, - QueryStatsNowID INT NULL, - QueryStatsFirstID INT NULL, - PlanHandle VARBINARY(64) NULL, - DetailsInt INT NULL, - QueryHash BINARY(8) - ); +IF OBJECT_ID('tempdb..#DatabaseList') IS NOT NULL + DROP TABLE #DatabaseList; - IF OBJECT_ID('tempdb..#WaitStats') IS NOT NULL - DROP TABLE #WaitStats; - CREATE TABLE #WaitStats (Pass TINYINT NOT NULL, wait_type NVARCHAR(60), wait_time_ms BIGINT, signal_wait_time_ms BIGINT, waiting_tasks_count BIGINT, SampleTime DATETIMEOFFSET); +IF OBJECT_ID('tempdb..#Statistics') IS NOT NULL + DROP TABLE #Statistics; - IF OBJECT_ID('tempdb..#FileStats') IS NOT NULL - DROP TABLE #FileStats; - CREATE TABLE #FileStats ( - ID INT IDENTITY(1, 1) PRIMARY KEY CLUSTERED, - Pass TINYINT NOT NULL, - SampleTime DATETIMEOFFSET NOT NULL, - DatabaseID INT NOT NULL, - FileID INT NOT NULL, - DatabaseName NVARCHAR(256) , - FileLogicalName NVARCHAR(256) , - TypeDesc NVARCHAR(60) , - SizeOnDiskMB BIGINT , - io_stall_read_ms BIGINT , - num_of_reads BIGINT , - bytes_read BIGINT , - io_stall_write_ms BIGINT , - num_of_writes BIGINT , - bytes_written BIGINT, - PhysicalName NVARCHAR(520) , - avg_stall_read_ms INT , - avg_stall_write_ms INT - ); +IF OBJECT_ID('tempdb..#PartitionCompressionInfo') IS NOT NULL + DROP TABLE #PartitionCompressionInfo; - IF OBJECT_ID('tempdb..#QueryStats') IS NOT NULL - DROP TABLE #QueryStats; - CREATE TABLE #QueryStats ( - ID INT IDENTITY(1, 1) PRIMARY KEY CLUSTERED, - Pass INT NOT NULL, - SampleTime DATETIMEOFFSET NOT NULL, - [sql_handle] VARBINARY(64), - statement_start_offset INT, - statement_end_offset INT, - plan_generation_num BIGINT, - plan_handle VARBINARY(64), - execution_count BIGINT, - total_worker_time BIGINT, - total_physical_reads BIGINT, - total_logical_writes BIGINT, - total_logical_reads BIGINT, - total_clr_time BIGINT, - total_elapsed_time BIGINT, - creation_time DATETIMEOFFSET, - query_hash BINARY(8), - query_plan_hash BINARY(8), - Points TINYINT - ); +IF OBJECT_ID('tempdb..#ComputedColumns') IS NOT NULL + DROP TABLE #ComputedColumns; + +IF OBJECT_ID('tempdb..#TraceStatus') IS NOT NULL + DROP TABLE #TraceStatus; - IF OBJECT_ID('tempdb..#PerfmonStats') IS NOT NULL - DROP TABLE #PerfmonStats; - CREATE TABLE #PerfmonStats ( - ID INT IDENTITY(1, 1) PRIMARY KEY CLUSTERED, - Pass TINYINT NOT NULL, - SampleTime DATETIMEOFFSET NOT NULL, - [object_name] NVARCHAR(128) NOT NULL, - [counter_name] NVARCHAR(128) NOT NULL, - [instance_name] NVARCHAR(128) NULL, - [cntr_value] BIGINT NULL, - [cntr_type] INT NOT NULL, - [value_delta] BIGINT NULL, - [value_per_second] DECIMAL(18,2) NULL - ); +IF OBJECT_ID('tempdb..#TemporalTables') IS NOT NULL + DROP TABLE #TemporalTables; - IF OBJECT_ID('tempdb..#PerfmonCounters') IS NOT NULL - DROP TABLE #PerfmonCounters; - CREATE TABLE #PerfmonCounters ( - ID INT IDENTITY(1, 1) PRIMARY KEY CLUSTERED, - [object_name] NVARCHAR(128) NOT NULL, - [counter_name] NVARCHAR(128) NOT NULL, - [instance_name] NVARCHAR(128) NULL - ); +IF OBJECT_ID('tempdb..#CheckConstraints') IS NOT NULL + DROP TABLE #CheckConstraints; - IF OBJECT_ID('tempdb..#FilterPlansByDatabase') IS NOT NULL - DROP TABLE #FilterPlansByDatabase; - CREATE TABLE #FilterPlansByDatabase (DatabaseID INT PRIMARY KEY CLUSTERED); +IF OBJECT_ID('tempdb..#FilteredIndexes') IS NOT NULL + DROP TABLE #FilteredIndexes; + +IF OBJECT_ID('tempdb..#Ignore_Databases') IS NOT NULL + DROP TABLE #Ignore_Databases - IF OBJECT_ID('tempdb..##WaitCategories') IS NULL - BEGIN - /* We reuse this one by default rather than recreate it every time. */ - CREATE TABLE ##WaitCategories - ( - WaitType NVARCHAR(60) PRIMARY KEY CLUSTERED, - WaitCategory NVARCHAR(128) NOT NULL, - Ignorable BIT DEFAULT 0 - ); - END; /* IF OBJECT_ID('tempdb..##WaitCategories') IS NULL */ - - IF OBJECT_ID ('tempdb..#checkversion') IS NOT NULL - DROP TABLE #checkversion; - CREATE TABLE #checkversion ( - version NVARCHAR(128), - common_version AS SUBSTRING(version, 1, CHARINDEX('.', version) + 1 ), - major AS PARSENAME(CONVERT(VARCHAR(32), version), 4), - minor AS PARSENAME(CONVERT(VARCHAR(32), version), 3), - build AS PARSENAME(CONVERT(VARCHAR(32), version), 2), - revision AS PARSENAME(CONVERT(VARCHAR(32), version), 1) - ); - - IF 527 <> (SELECT COALESCE(SUM(1),0) FROM ##WaitCategories) - BEGIN - TRUNCATE TABLE ##WaitCategories; - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('ASYNC_IO_COMPLETION','Other Disk IO',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('ASYNC_NETWORK_IO','Network IO',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BACKUPIO','Other Disk IO',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_CONNECTION_RECEIVE_TASK','Service Broker',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_DISPATCHER','Service Broker',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_ENDPOINT_STATE_MUTEX','Service Broker',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_EVENTHANDLER','Service Broker',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_FORWARDER','Service Broker',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_INIT','Service Broker',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_MASTERSTART','Service Broker',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_RECEIVE_WAITFOR','User Wait',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_REGISTERALLENDPOINTS','Service Broker',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_SERVICE','Service Broker',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_SHUTDOWN','Service Broker',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_START','Service Broker',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_TASK_SHUTDOWN','Service Broker',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_TASK_STOP','Service Broker',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_TASK_SUBMIT','Service Broker',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_TO_FLUSH','Service Broker',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_TRANSMISSION_OBJECT','Service Broker',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_TRANSMISSION_TABLE','Service Broker',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_TRANSMISSION_WORK','Service Broker',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_TRANSMITTER','Service Broker',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CHECKPOINT_QUEUE','Idle',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CHKPT','Tran Log IO',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CLR_AUTO_EVENT','SQL CLR',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CLR_CRST','SQL CLR',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CLR_JOIN','SQL CLR',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CLR_MANUAL_EVENT','SQL CLR',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CLR_MEMORY_SPY','SQL CLR',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CLR_MONITOR','SQL CLR',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CLR_RWLOCK_READER','SQL CLR',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CLR_RWLOCK_WRITER','SQL CLR',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CLR_SEMAPHORE','SQL CLR',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CLR_TASK_START','SQL CLR',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CLRHOST_STATE_ACCESS','SQL CLR',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CMEMPARTITIONED','Memory',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CMEMTHREAD','Memory',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CXPACKET','Parallelism',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CXCONSUMER','Parallelism',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DBMIRROR_DBM_EVENT','Mirroring',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DBMIRROR_DBM_MUTEX','Mirroring',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DBMIRROR_EVENTS_QUEUE','Mirroring',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DBMIRROR_SEND','Mirroring',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DBMIRROR_WORKER_QUEUE','Mirroring',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DBMIRRORING_CMD','Mirroring',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DIRTY_PAGE_POLL','Other',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DIRTY_PAGE_TABLE_LOCK','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DISPATCHER_QUEUE_SEMAPHORE','Other',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DPT_ENTRY_LOCK','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DTC','Transaction',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DTC_ABORT_REQUEST','Transaction',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DTC_RESOLVE','Transaction',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DTC_STATE','Transaction',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DTC_TMDOWN_REQUEST','Transaction',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DTC_WAITFOR_OUTCOME','Transaction',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DTCNEW_ENLIST','Transaction',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DTCNEW_PREPARE','Transaction',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DTCNEW_RECOVERY','Transaction',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DTCNEW_TM','Transaction',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DTCNEW_TRANSACTION_ENLISTMENT','Transaction',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DTCPNTSYNC','Transaction',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('EE_PMOLOCK','Memory',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('EXCHANGE','Parallelism',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('EXTERNAL_SCRIPT_NETWORK_IOF','Network IO',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FCB_REPLICA_READ','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FCB_REPLICA_WRITE','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_COMPROWSET_RWLOCK','Full Text Search',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_IFTS_RWLOCK','Full Text Search',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_IFTS_SCHEDULER_IDLE_WAIT','Idle',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_IFTSHC_MUTEX','Full Text Search',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_IFTSISM_MUTEX','Full Text Search',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_MASTER_MERGE','Full Text Search',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_MASTER_MERGE_COORDINATOR','Full Text Search',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_METADATA_MUTEX','Full Text Search',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_PROPERTYLIST_CACHE','Full Text Search',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_RESTART_CRAWL','Full Text Search',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FULLTEXT GATHERER','Full Text Search',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_AG_MUTEX','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_AR_CRITICAL_SECTION_ENTRY','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_AR_MANAGER_MUTEX','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_AR_UNLOAD_COMPLETED','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_ARCONTROLLER_NOTIFICATIONS_SUBSCRIBER_LIST','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_BACKUP_BULK_LOCK','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_BACKUP_QUEUE','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_CLUSAPI_CALL','Replication',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_COMPRESSED_CACHE_SYNC','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_CONNECTIVITY_INFO','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_DATABASE_FLOW_CONTROL','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_DATABASE_VERSIONING_STATE','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_DATABASE_WAIT_FOR_RECOVERY','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_DATABASE_WAIT_FOR_RESTART','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_DATABASE_WAIT_FOR_TRANSITION_TO_VERSIONING','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_DB_COMMAND','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_DB_OP_COMPLETION_SYNC','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_DB_OP_START_SYNC','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_DBR_SUBSCRIBER','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_DBR_SUBSCRIBER_FILTER_LIST','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_DBSEEDING','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_DBSEEDING_LIST','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_DBSTATECHANGE_SYNC','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_FABRIC_CALLBACK','Replication',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_FILESTREAM_BLOCK_FLUSH','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_FILESTREAM_FILE_CLOSE','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_FILESTREAM_FILE_REQUEST','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_FILESTREAM_IOMGR','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_FILESTREAM_IOMGR_IOCOMPLETION','Replication',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_FILESTREAM_MANAGER','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_FILESTREAM_PREPROC','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_GROUP_COMMIT','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_LOGCAPTURE_SYNC','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_LOGCAPTURE_WAIT','Replication',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_LOGPROGRESS_SYNC','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_NOTIFICATION_DEQUEUE','Replication',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_NOTIFICATION_WORKER_EXCLUSIVE_ACCESS','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_NOTIFICATION_WORKER_STARTUP_SYNC','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_NOTIFICATION_WORKER_TERMINATION_SYNC','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_PARTNER_SYNC','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_READ_ALL_NETWORKS','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_RECOVERY_WAIT_FOR_CONNECTION','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_RECOVERY_WAIT_FOR_UNDO','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_REPLICAINFO_SYNC','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_SEEDING_CANCELLATION','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_SEEDING_FILE_LIST','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_SEEDING_LIMIT_BACKUPS','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_SEEDING_SYNC_COMPLETION','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_SEEDING_TIMEOUT_TASK','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_SEEDING_WAIT_FOR_COMPLETION','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_SYNC_COMMIT','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_SYNCHRONIZING_THROTTLE','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_TDS_LISTENER_SYNC','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_TDS_LISTENER_SYNC_PROCESSING','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_THROTTLE_LOG_RATE_GOVERNOR','Log Rate Governor',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_TIMER_TASK','Replication',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_TRANSPORT_DBRLIST','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_TRANSPORT_FLOW_CONTROL','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_TRANSPORT_SESSION','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_WORK_POOL','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_WORK_QUEUE','Replication',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_XRF_STACK_ACCESS','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('INSTANCE_LOG_RATE_GOVERNOR','Log Rate Governor',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('IO_COMPLETION','Other Disk IO',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('IO_QUEUE_LIMIT','Other Disk IO',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('IO_RETRY','Other Disk IO',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LATCH_DT','Latch',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LATCH_EX','Latch',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LATCH_KP','Latch',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LATCH_NL','Latch',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LATCH_SH','Latch',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LATCH_UP','Latch',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LAZYWRITER_SLEEP','Idle',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_BU','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_BU_ABORT_BLOCKERS','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_BU_LOW_PRIORITY','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_IS','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_IS_ABORT_BLOCKERS','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_IS_LOW_PRIORITY','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_IU','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_IU_ABORT_BLOCKERS','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_IU_LOW_PRIORITY','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_IX','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_IX_ABORT_BLOCKERS','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_IX_LOW_PRIORITY','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RIn_NL','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RIn_NL_ABORT_BLOCKERS','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RIn_NL_LOW_PRIORITY','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RIn_S','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RIn_S_ABORT_BLOCKERS','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RIn_S_LOW_PRIORITY','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RIn_U','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RIn_U_ABORT_BLOCKERS','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RIn_U_LOW_PRIORITY','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RIn_X','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RIn_X_ABORT_BLOCKERS','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RIn_X_LOW_PRIORITY','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RS_S','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RS_S_ABORT_BLOCKERS','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RS_S_LOW_PRIORITY','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RS_U','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RS_U_ABORT_BLOCKERS','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RS_U_LOW_PRIORITY','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RX_S','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RX_S_ABORT_BLOCKERS','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RX_S_LOW_PRIORITY','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RX_U','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RX_U_ABORT_BLOCKERS','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RX_U_LOW_PRIORITY','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RX_X','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RX_X_ABORT_BLOCKERS','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RX_X_LOW_PRIORITY','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_S','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_S_ABORT_BLOCKERS','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_S_LOW_PRIORITY','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_SCH_M','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_SCH_M_ABORT_BLOCKERS','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_SCH_M_LOW_PRIORITY','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_SCH_S','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_SCH_S_ABORT_BLOCKERS','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_SCH_S_LOW_PRIORITY','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_SIU','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_SIU_ABORT_BLOCKERS','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_SIU_LOW_PRIORITY','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_SIX','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_SIX_ABORT_BLOCKERS','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_SIX_LOW_PRIORITY','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_U','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_U_ABORT_BLOCKERS','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_U_LOW_PRIORITY','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_UIX','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_UIX_ABORT_BLOCKERS','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_UIX_LOW_PRIORITY','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_X','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_X_ABORT_BLOCKERS','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_X_LOW_PRIORITY','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LOG_RATE_GOVERNOR','Tran Log IO',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LOGBUFFER','Tran Log IO',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LOGMGR','Tran Log IO',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LOGMGR_FLUSH','Tran Log IO',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LOGMGR_PMM_LOG','Tran Log IO',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LOGMGR_QUEUE','Idle',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LOGMGR_RESERVE_APPEND','Tran Log IO',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('MEMORY_ALLOCATION_EXT','Memory',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('MEMORY_GRANT_UPDATE','Memory',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('MSQL_XACT_MGR_MUTEX','Transaction',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('MSQL_XACT_MUTEX','Transaction',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('MSSEARCH','Full Text Search',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('NET_WAITFOR_PACKET','Network IO',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('ONDEMAND_TASK_QUEUE','Idle',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PAGEIOLATCH_DT','Buffer IO',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PAGEIOLATCH_EX','Buffer IO',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PAGEIOLATCH_KP','Buffer IO',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PAGEIOLATCH_NL','Buffer IO',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PAGEIOLATCH_SH','Buffer IO',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PAGEIOLATCH_UP','Buffer IO',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PAGELATCH_DT','Buffer Latch',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PAGELATCH_EX','Buffer Latch',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PAGELATCH_KP','Buffer Latch',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PAGELATCH_NL','Buffer Latch',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PAGELATCH_SH','Buffer Latch',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PAGELATCH_UP','Buffer Latch',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PARALLEL_REDO_DRAIN_WORKER','Replication',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PARALLEL_REDO_FLOW_CONTROL','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PARALLEL_REDO_LOG_CACHE','Replication',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PARALLEL_REDO_TRAN_LIST','Replication',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PARALLEL_REDO_TRAN_TURN','Replication',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PARALLEL_REDO_WORKER_SYNC','Replication',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PARALLEL_REDO_WORKER_WAIT_WORK','Replication',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('POOL_LOG_RATE_GOVERNOR','Log Rate Governor',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_ABR','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_CLOSEBACKUPMEDIA','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_CLOSEBACKUPTAPE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_CLOSEBACKUPVDIDEVICE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_CLUSAPI_CLUSTERRESOURCECONTROL','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_COCREATEINSTANCE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_COGETCLASSOBJECT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_CREATEACCESSOR','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_DELETEROWS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_GETCOMMANDTEXT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_GETDATA','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_GETNEXTROWS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_GETRESULT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_GETROWSBYBOOKMARK','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_LBFLUSH','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_LBLOCKREGION','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_LBREADAT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_LBSETSIZE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_LBSTAT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_LBUNLOCKREGION','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_LBWRITEAT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_QUERYINTERFACE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_RELEASE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_RELEASEACCESSOR','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_RELEASEROWS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_RELEASESESSION','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_RESTARTPOSITION','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_SEQSTRMREAD','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_SEQSTRMREADANDWRITE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_SETDATAFAILURE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_SETPARAMETERINFO','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_SETPARAMETERPROPERTIES','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_STRMLOCKREGION','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_STRMSEEKANDREAD','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_STRMSEEKANDWRITE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_STRMSETSIZE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_STRMSTAT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_STRMUNLOCKREGION','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_CONSOLEWRITE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_CREATEPARAM','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DEBUG','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DFSADDLINK','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DFSLINKEXISTCHECK','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DFSLINKHEALTHCHECK','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DFSREMOVELINK','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DFSREMOVEROOT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DFSROOTFOLDERCHECK','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DFSROOTINIT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DFSROOTSHARECHECK','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DTC_ABORT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DTC_ABORTREQUESTDONE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DTC_BEGINTRANSACTION','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DTC_COMMITREQUESTDONE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DTC_ENLIST','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DTC_PREPAREREQUESTDONE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_FILESIZEGET','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_FSAOLEDB_ABORTTRANSACTION','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_FSAOLEDB_COMMITTRANSACTION','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_FSAOLEDB_STARTTRANSACTION','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_FSRECOVER_UNCONDITIONALUNDO','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_GETRMINFO','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_HADR_LEASE_MECHANISM','Preemptive',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_HTTP_EVENT_WAIT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_HTTP_REQUEST','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_LOCKMONITOR','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_MSS_RELEASE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_ODBCOPS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OLE_UNINIT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OLEDB_ABORTORCOMMITTRAN','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OLEDB_ABORTTRAN','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OLEDB_GETDATASOURCE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OLEDB_GETLITERALINFO','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OLEDB_GETPROPERTIES','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OLEDB_GETPROPERTYINFO','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OLEDB_GETSCHEMALOCK','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OLEDB_JOINTRANSACTION','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OLEDB_RELEASE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OLEDB_SETPROPERTIES','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OLEDBOPS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_ACCEPTSECURITYCONTEXT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_ACQUIRECREDENTIALSHANDLE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_AUTHENTICATIONOPS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_AUTHORIZATIONOPS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_AUTHZGETINFORMATIONFROMCONTEXT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_AUTHZINITIALIZECONTEXTFROMSID','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_AUTHZINITIALIZERESOURCEMANAGER','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_BACKUPREAD','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_CLOSEHANDLE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_CLUSTEROPS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_COMOPS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_COMPLETEAUTHTOKEN','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_COPYFILE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_CREATEDIRECTORY','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_CREATEFILE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_CRYPTACQUIRECONTEXT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_CRYPTIMPORTKEY','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_CRYPTOPS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_DECRYPTMESSAGE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_DELETEFILE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_DELETESECURITYCONTEXT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_DEVICEIOCONTROL','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_DEVICEOPS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_DIRSVC_NETWORKOPS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_DISCONNECTNAMEDPIPE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_DOMAINSERVICESOPS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_DSGETDCNAME','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_DTCOPS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_ENCRYPTMESSAGE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_FILEOPS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_FINDFILE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_FLUSHFILEBUFFERS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_FORMATMESSAGE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_FREECREDENTIALSHANDLE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_FREELIBRARY','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_GENERICOPS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_GETADDRINFO','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_GETCOMPRESSEDFILESIZE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_GETDISKFREESPACE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_GETFILEATTRIBUTES','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_GETFILESIZE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_GETFINALFILEPATHBYHANDLE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_GETLONGPATHNAME','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_GETPROCADDRESS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_GETVOLUMENAMEFORVOLUMEMOUNTPOINT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_GETVOLUMEPATHNAME','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_INITIALIZESECURITYCONTEXT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_LIBRARYOPS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_LOADLIBRARY','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_LOGONUSER','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_LOOKUPACCOUNTSID','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_MESSAGEQUEUEOPS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_MOVEFILE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_NETGROUPGETUSERS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_NETLOCALGROUPGETMEMBERS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_NETUSERGETGROUPS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_NETUSERGETLOCALGROUPS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_NETUSERMODALSGET','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_NETVALIDATEPASSWORDPOLICY','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_NETVALIDATEPASSWORDPOLICYFREE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_OPENDIRECTORY','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_PDH_WMI_INIT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_PIPEOPS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_PROCESSOPS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_QUERYCONTEXTATTRIBUTES','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_QUERYREGISTRY','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_QUERYSECURITYCONTEXTTOKEN','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_REMOVEDIRECTORY','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_REPORTEVENT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_REVERTTOSELF','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_RSFXDEVICEOPS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_SECURITYOPS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_SERVICEOPS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_SETENDOFFILE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_SETFILEPOINTER','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_SETFILEVALIDDATA','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_SETNAMEDSECURITYINFO','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_SQLCLROPS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_SQMLAUNCH','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_VERIFYSIGNATURE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_VERIFYTRUST','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_VSSOPS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_WAITFORSINGLEOBJECT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_WINSOCKOPS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_WRITEFILE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_WRITEFILEGATHER','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_WSASETLASTERROR','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_REENLIST','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_RESIZELOG','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_ROLLFORWARDREDO','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_ROLLFORWARDUNDO','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_SB_STOPENDPOINT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_SERVER_STARTUP','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_SETRMINFO','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_SHAREDMEM_GETDATA','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_SNIOPEN','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_SOSHOST','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_SOSTESTING','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_SP_SERVER_DIAGNOSTICS','Preemptive',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_STARTRM','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_STREAMFCB_CHECKPOINT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_STREAMFCB_RECOVER','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_STRESSDRIVER','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_TESTING','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_TRANSIMPORT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_UNMARSHALPROPAGATIONTOKEN','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_VSS_CREATESNAPSHOT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_VSS_CREATEVOLUMESNAPSHOT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_XE_CALLBACKEXECUTE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_XE_CX_FILE_OPEN','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_XE_CX_HTTP_CALL','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_XE_DISPATCHER','Preemptive',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_XE_ENGINEINIT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_XE_GETTARGETSTATE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_XE_SESSIONCOMMIT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_XE_TARGETFINALIZE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_XE_TARGETINIT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_XE_TIMERRUN','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_XETESTING','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PWAIT_HADR_ACTION_COMPLETED','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PWAIT_HADR_CHANGE_NOTIFIER_TERMINATION_SYNC','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PWAIT_HADR_CLUSTER_INTEGRATION','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PWAIT_HADR_FAILOVER_COMPLETED','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PWAIT_HADR_JOIN','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PWAIT_HADR_OFFLINE_COMPLETED','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PWAIT_HADR_ONLINE_COMPLETED','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PWAIT_HADR_POST_ONLINE_COMPLETED','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PWAIT_HADR_SERVER_READY_CONNECTIONS','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PWAIT_HADR_WORKITEM_COMPLETED','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PWAIT_HADRSIM','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PWAIT_RESOURCE_SEMAPHORE_FT_PARALLEL_QUERY_SYNC','Full Text Search',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('QDS_ASYNC_QUEUE','Other',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('QDS_CLEANUP_STALE_QUERIES_TASK_MAIN_LOOP_SLEEP','Other',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('QDS_PERSIST_TASK_MAIN_LOOP_SLEEP','Other',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('QDS_SHUTDOWN_QUEUE','Other',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('QUERY_TRACEOUT','Tracing',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('REDO_THREAD_PENDING_WORK','Other',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('REPL_CACHE_ACCESS','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('REPL_HISTORYCACHE_ACCESS','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('REPL_SCHEMA_ACCESS','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('REPL_TRANFSINFO_ACCESS','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('REPL_TRANHASHTABLE_ACCESS','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('REPL_TRANTEXTINFO_ACCESS','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('REPLICA_WRITES','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('REQUEST_FOR_DEADLOCK_SEARCH','Idle',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('RESERVED_MEMORY_ALLOCATION_EXT','Memory',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('RESOURCE_SEMAPHORE','Memory',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('RESOURCE_SEMAPHORE_QUERY_COMPILE','Compilation',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_BPOOL_FLUSH','Idle',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_BUFFERPOOL_HELPLW','Idle',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_DBSTARTUP','Idle',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_DCOMSTARTUP','Idle',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_MASTERDBREADY','Idle',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_MASTERMDREADY','Idle',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_MASTERUPGRADED','Idle',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_MEMORYPOOL_ALLOCATEPAGES','Idle',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_MSDBSTARTUP','Idle',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_RETRY_VIRTUALALLOC','Idle',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_SYSTEMTASK','Idle',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_TASK','Idle',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_TEMPDBSTARTUP','Idle',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_WORKSPACE_ALLOCATEPAGE','Idle',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SOS_SCHEDULER_YIELD','CPU',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SOS_WORK_DISPATCHER','Idle',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SP_SERVER_DIAGNOSTICS_SLEEP','Other',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SQLCLR_APPDOMAIN','SQL CLR',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SQLCLR_ASSEMBLY','SQL CLR',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SQLCLR_DEADLOCK_DETECTION','SQL CLR',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SQLCLR_QUANTUM_PUNISHMENT','SQL CLR',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SQLTRACE_BUFFER_FLUSH','Idle',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SQLTRACE_FILE_BUFFER','Tracing',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SQLTRACE_FILE_READ_IO_COMPLETION','Tracing',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SQLTRACE_FILE_WRITE_IO_COMPLETION','Tracing',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SQLTRACE_INCREMENTAL_FLUSH_SLEEP','Idle',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SQLTRACE_PENDING_BUFFER_WRITERS','Tracing',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SQLTRACE_SHUTDOWN','Tracing',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SQLTRACE_WAIT_ENTRIES','Idle',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('THREADPOOL','Worker Thread',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('TRACE_EVTNOTIF','Tracing',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('TRACEWRITE','Tracing',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('TRAN_MARKLATCH_DT','Transaction',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('TRAN_MARKLATCH_EX','Transaction',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('TRAN_MARKLATCH_KP','Transaction',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('TRAN_MARKLATCH_NL','Transaction',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('TRAN_MARKLATCH_SH','Transaction',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('TRAN_MARKLATCH_UP','Transaction',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('TRANSACTION_MUTEX','Transaction',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('UCS_SESSION_REGISTRATION','Other',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('WAIT_FOR_RESULTS','User Wait',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('WAIT_XTP_OFFLINE_CKPT_NEW_LOG','Other',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('WAITFOR','User Wait',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('WRITE_COMPLETION','Other Disk IO',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('WRITELOG','Tran Log IO',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('XACT_OWN_TRANSACTION','Transaction',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('XACT_RECLAIM_SESSION','Transaction',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('XACTLOCKINFO','Transaction',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('XACTWORKSPACE_MUTEX','Transaction',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('XE_DISPATCHER_WAIT','Idle',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('XE_LIVE_TARGET_TVF','Other',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('XE_TIMER_EVENT','Idle',1); - END; /* IF SELECT SUM(1) FROM ##WaitCategories <> 527 */ - - - - IF OBJECT_ID('tempdb..#MasterFiles') IS NOT NULL - DROP TABLE #MasterFiles; - CREATE TABLE #MasterFiles (database_id INT, file_id INT, type_desc NVARCHAR(50), name NVARCHAR(255), physical_name NVARCHAR(255), size BIGINT); - /* Azure SQL Database doesn't have sys.master_files, so we have to build our own. */ - IF ((SERVERPROPERTY('Edition')) = 'SQL Azure' - AND (OBJECT_ID('sys.master_files') IS NULL)) - SET @StringToExecute = 'INSERT INTO #MasterFiles (database_id, file_id, type_desc, name, physical_name, size) SELECT DB_ID(), file_id, type_desc, name, physical_name, size FROM sys.database_files;'; - ELSE - SET @StringToExecute = 'INSERT INTO #MasterFiles (database_id, file_id, type_desc, name, physical_name, size) SELECT database_id, file_id, type_desc, name, physical_name, size FROM sys.master_files;'; - EXEC(@StringToExecute); - - IF @FilterPlansByDatabase IS NOT NULL - BEGIN - IF UPPER(LEFT(@FilterPlansByDatabase,4)) = 'USER' - BEGIN - INSERT INTO #FilterPlansByDatabase (DatabaseID) - SELECT database_id - FROM sys.databases - WHERE [name] NOT IN ('master', 'model', 'msdb', 'tempdb'); - END; - ELSE - BEGIN - SET @FilterPlansByDatabase = @FilterPlansByDatabase + ',' - ;WITH a AS - ( - SELECT CAST(1 AS BIGINT) f, CHARINDEX(',', @FilterPlansByDatabase) t, 1 SEQ - UNION ALL - SELECT t + 1, CHARINDEX(',', @FilterPlansByDatabase, t + 1), SEQ + 1 - FROM a - WHERE CHARINDEX(',', @FilterPlansByDatabase, t + 1) > 0 - ) - INSERT #FilterPlansByDatabase (DatabaseID) - SELECT DISTINCT db.database_id - FROM a - INNER JOIN sys.databases db ON LTRIM(RTRIM(SUBSTRING(@FilterPlansByDatabase, a.f, a.t - a.f))) = db.name - WHERE SUBSTRING(@FilterPlansByDatabase, f, t - f) IS NOT NULL - OPTION (MAXRECURSION 0); - END; - END; - - IF OBJECT_ID('tempdb..#ReadableDBs') IS NOT NULL - DROP TABLE #ReadableDBs; - CREATE TABLE #ReadableDBs ( - database_id INT - ); - - IF EXISTS (SELECT * FROM sys.all_objects o WHERE o.name = 'dm_hadr_database_replica_states') - BEGIN - RAISERROR('Checking for Read intent databases to exclude',0,0) WITH NOWAIT; - - SET @StringToExecute = 'INSERT INTO #ReadableDBs (database_id) SELECT DBs.database_id FROM sys.databases DBs INNER JOIN sys.availability_replicas Replicas ON DBs.replica_id = Replicas.replica_id WHERE replica_server_name NOT IN (SELECT DISTINCT primary_replica FROM sys.dm_hadr_availability_group_states States) AND Replicas.secondary_role_allow_connections_desc = ''READ_ONLY'' AND replica_server_name = @@SERVERNAME;'; - EXEC(@StringToExecute); - - END - - DECLARE @v DECIMAL(6,2), - @build INT, - @memGrantSortSupported BIT = 1; - - RAISERROR (N'Determining SQL Server version.',0,1) WITH NOWAIT; - - INSERT INTO #checkversion (version) - SELECT CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)) - OPTION (RECOMPILE); - - - SELECT @v = common_version , - @build = build - FROM #checkversion - OPTION (RECOMPILE); - - IF (@v < 11) - OR (@v = 11 AND @build < 6020) - OR (@v = 12 AND @build < 5000) - OR (@v = 13 AND @build < 1601) - SET @memGrantSortSupported = 0; - - IF EXISTS (SELECT * FROM sys.all_objects WHERE name = 'dm_exec_query_statistics_xml') - AND ((@v = 13 AND @build >= 5337) /* This DMF causes assertion errors: https://support.microsoft.com/en-us/help/4490136/fix-assertion-error-occurs-when-you-use-sys-dm-exec-query-statistics-x */ - OR (@v = 14 AND @build >= 3162) - OR (@v >= 15) - OR (@v <= 12)) /* Azure */ - SET @dm_exec_query_statistics_xml = 1; - - - SET @StockWarningHeader = '', - @StockDetailsHeader = @StockDetailsHeader + ''; - - /* Get the instance name to use as a Perfmon counter prefix. */ - IF CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) = 'SQL Azure' - SELECT TOP 1 @ServiceName = LEFT(object_name, (CHARINDEX(':', object_name) - 1)) - FROM sys.dm_os_performance_counters; - ELSE - BEGIN - SET @StringToExecute = 'INSERT INTO #PerfmonStats(object_name, Pass, SampleTime, counter_name, cntr_type) SELECT CASE WHEN @@SERVICENAME = ''MSSQLSERVER'' THEN ''SQLServer'' ELSE ''MSSQL$'' + @@SERVICENAME END, 0, SYSDATETIMEOFFSET(), ''stuffing'', 0 ;'; - EXEC(@StringToExecute); - SELECT @ServiceName = object_name FROM #PerfmonStats; - DELETE #PerfmonStats; - END; - - /* Build a list of queries that were run in the last 10 seconds. - We're looking for the death-by-a-thousand-small-cuts scenario - where a query is constantly running, and it doesn't have that - big of an impact individually, but it has a ton of impact - overall. We're going to build this list, and then after we - finish our @Seconds sample, we'll compare our plan cache to - this list to see what ran the most. */ - - /* Populate #QueryStats. SQL 2005 doesn't have query hash or query plan hash. */ - IF @CheckProcedureCache = 1 - BEGIN - RAISERROR('@CheckProcedureCache = 1, capturing first pass of plan cache',10,1) WITH NOWAIT; - IF @@VERSION LIKE 'Microsoft SQL Server 2005%' - BEGIN - IF @FilterPlansByDatabase IS NULL - BEGIN - SET @StringToExecute = N'INSERT INTO #QueryStats ([sql_handle], Pass, SampleTime, statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, Points) - SELECT [sql_handle], 1 AS Pass, SYSDATETIMEOFFSET(), statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, NULL AS query_hash, NULL AS query_plan_hash, 0 - FROM sys.dm_exec_query_stats qs - WHERE qs.last_execution_time >= (DATEADD(ss, -10, SYSDATETIMEOFFSET()));'; - END; - ELSE - BEGIN - SET @StringToExecute = N'INSERT INTO #QueryStats ([sql_handle], Pass, SampleTime, statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, Points) - SELECT [sql_handle], 1 AS Pass, SYSDATETIMEOFFSET(), statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, NULL AS query_hash, NULL AS query_plan_hash, 0 - FROM sys.dm_exec_query_stats qs - CROSS APPLY sys.dm_exec_plan_attributes(qs.plan_handle) AS attr - INNER JOIN #FilterPlansByDatabase dbs ON CAST(attr.value AS INT) = dbs.DatabaseID - WHERE qs.last_execution_time >= (DATEADD(ss, -10, SYSDATETIMEOFFSET())) - AND attr.attribute = ''dbid'';'; - END; - END; - ELSE - BEGIN - IF @FilterPlansByDatabase IS NULL - BEGIN - SET @StringToExecute = N'INSERT INTO #QueryStats ([sql_handle], Pass, SampleTime, statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, Points) - SELECT [sql_handle], 1 AS Pass, SYSDATETIMEOFFSET(), statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, 0 - FROM sys.dm_exec_query_stats qs - WHERE qs.last_execution_time >= (DATEADD(ss, -10, SYSDATETIMEOFFSET()));'; - END; - ELSE - BEGIN - SET @StringToExecute = N'INSERT INTO #QueryStats ([sql_handle], Pass, SampleTime, statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, Points) - SELECT [sql_handle], 1 AS Pass, SYSDATETIMEOFFSET(), statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, 0 - FROM sys.dm_exec_query_stats qs - CROSS APPLY sys.dm_exec_plan_attributes(qs.plan_handle) AS attr - INNER JOIN #FilterPlansByDatabase dbs ON CAST(attr.value AS INT) = dbs.DatabaseID - WHERE qs.last_execution_time >= (DATEADD(ss, -10, SYSDATETIMEOFFSET())) - AND attr.attribute = ''dbid'';'; - END; - END; - EXEC(@StringToExecute); - - /* Get the totals for the entire plan cache */ - INSERT INTO #QueryStats (Pass, SampleTime, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time) - SELECT -1 AS Pass, SYSDATETIMEOFFSET(), SUM(execution_count), SUM(total_worker_time), SUM(total_physical_reads), SUM(total_logical_writes), SUM(total_logical_reads), SUM(total_clr_time), SUM(total_elapsed_time), MIN(creation_time) - FROM sys.dm_exec_query_stats qs; - END; /*IF @CheckProcedureCache = 1 */ - - - IF EXISTS (SELECT * - FROM tempdb.sys.all_objects obj - INNER JOIN tempdb.sys.all_columns col1 ON obj.object_id = col1.object_id AND col1.name = 'object_name' - INNER JOIN tempdb.sys.all_columns col2 ON obj.object_id = col2.object_id AND col2.name = 'counter_name' - INNER JOIN tempdb.sys.all_columns col3 ON obj.object_id = col3.object_id AND col3.name = 'instance_name' - WHERE obj.name LIKE '%CustomPerfmonCounters%') - BEGIN - SET @StringToExecute = 'INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) SELECT [object_name],[counter_name],[instance_name] FROM #CustomPerfmonCounters'; - EXEC(@StringToExecute); - END; - ELSE - BEGIN - /* Add our default Perfmon counters */ - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Forwarded Records/sec', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Page compression attempts/sec', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Page Splits/sec', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Skipped Ghosted Records/sec', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Table Lock Escalations/sec', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Worktables Created/sec', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Availability Group','Active Hadr Threads','_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Availability Replica','Bytes Received from Replica/sec','_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Availability Replica','Bytes Sent to Replica/sec','_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Availability Replica','Bytes Sent to Transport/sec','_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Availability Replica','Flow Control Time (ms/sec)','_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Availability Replica','Flow Control/sec','_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Availability Replica','Resent Messages/sec','_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Availability Replica','Sends to Replica/sec','_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Page life expectancy', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Page reads/sec', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Page writes/sec', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Readahead pages/sec', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Target pages', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Total pages', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Databases','', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Active Transactions','_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Database Flow Control Delay', '_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Database Flow Controls/sec', '_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Group Commit Time', '_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Group Commits/Sec', '_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Log Apply Pending Queue', '_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Log Apply Ready Queue', '_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Log Compression Cache misses/sec', '_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Log remaining for undo', '_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Log Send Queue', '_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Recovery Queue', '_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Redo blocked/sec', '_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Redo Bytes Remaining', '_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Redone Bytes/sec', '_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Databases','Log Bytes Flushed/sec', '_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Databases','Log Growths', '_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Databases','Log Pool LogWriter Pushes/sec', '_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Databases','Log Shrinks', '_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Databases','Transactions/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Databases','Write Transactions/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Databases','XTP Memory Used (KB)',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Exec Statistics','Distributed Query', 'Execs in progress'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Exec Statistics','DTC calls', 'Execs in progress'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Exec Statistics','Extended Procedures', 'Execs in progress'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Exec Statistics','OLEDB calls', 'Execs in progress'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':General Statistics','Active Temp Tables', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':General Statistics','Logins/sec', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':General Statistics','Logouts/sec', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':General Statistics','Mars Deadlocks', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':General Statistics','Processes blocked', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Locks','Number of Deadlocks/sec', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Memory Manager','Memory Grants Pending', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Errors','Errors/sec', '_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','Batch Requests/sec', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','Forced Parameterizations/sec', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','Guided plan executions/sec', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','SQL Attention rate', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','SQL Compilations/sec', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','SQL Re-Compilations/sec', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Workload Group Stats','Query optimizations/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Workload Group Stats','Suboptimal plans/sec',NULL); - /* Below counters added by Jefferson Elias */ - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Worktables From Cache Base',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Worktables From Cache Ratio',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Database pages',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Free pages',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Stolen pages',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Memory Manager','Granted Workspace Memory (KB)',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Memory Manager','Maximum Workspace Memory (KB)',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Memory Manager','Target Server Memory (KB)',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Memory Manager','Total Server Memory (KB)',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Buffer cache hit ratio',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Buffer cache hit ratio base',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Checkpoint pages/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Free list stalls/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Lazy writes/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','Auto-Param Attempts/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','Failed Auto-Params/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','Safe Auto-Params/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','Unsafe Auto-Params/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Workfiles Created/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':General Statistics','User Connections',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Latches','Average Latch Wait Time (ms)',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Latches','Average Latch Wait Time Base',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Latches','Latch Waits/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Latches','Total Latch Wait Time (ms)',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Locks','Average Wait Time (ms)',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Locks','Average Wait Time Base',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Locks','Lock Requests/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Locks','Lock Timeouts/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Locks','Lock Wait Time (ms)',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Locks','Lock Waits/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Transactions','Longest Transaction Running Time',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Full Scans/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Index Searches/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Page lookups/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Cursor Manager by Type','Active cursors',NULL); - /* Below counters are for In-Memory OLTP (Hekaton), which have a different naming convention. - And yes, they actually hard-coded the version numbers into the counters, and SQL 2019 still says 2017, oddly. - For why, see: https://connect.microsoft.com/SQLServer/feedback/details/817216/xtp-perfmon-counters-should-appear-under-sql-server-perfmon-counter-group - */ - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP Cursors','Expired rows removed/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP Cursors','Expired rows touched/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP Garbage Collection','Rows processed/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP IO Governor','Io Issued/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP Phantom Processor','Phantom expired rows touched/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP Phantom Processor','Phantom rows touched/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP Transaction Log','Log bytes written/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP Transaction Log','Log records written/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP Transactions','Transactions aborted by user/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP Transactions','Transactions aborted/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP Transactions','Transactions created/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP Cursors','Expired rows removed/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP Cursors','Expired rows touched/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP Garbage Collection','Rows processed/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP IO Governor','Io Issued/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP Phantom Processor','Phantom expired rows touched/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP Phantom Processor','Phantom rows touched/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP Transaction Log','Log bytes written/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP Transaction Log','Log records written/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP Transactions','Transactions aborted by user/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP Transactions','Transactions aborted/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP Transactions','Transactions created/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP Cursors','Expired rows removed/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP Cursors','Expired rows touched/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP Garbage Collection','Rows processed/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP IO Governor','Io Issued/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP Phantom Processor','Phantom expired rows touched/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP Phantom Processor','Phantom rows touched/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP Transaction Log','Log bytes written/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP Transaction Log','Log records written/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP Transactions','Transactions aborted by user/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP Transactions','Transactions aborted/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP Transactions','Transactions created/sec',NULL); - END; - - /* Populate #FileStats, #PerfmonStats, #WaitStats with DMV data. - After we finish doing our checks, we'll take another sample and compare them. */ - RAISERROR('Capturing first pass of wait stats, perfmon counters, file stats',10,1) WITH NOWAIT; - INSERT #WaitStats(Pass, SampleTime, wait_type, wait_time_ms, signal_wait_time_ms, waiting_tasks_count) - SELECT - x.Pass, - x.SampleTime, - x.wait_type, - SUM(x.sum_wait_time_ms) AS sum_wait_time_ms, - SUM(x.sum_signal_wait_time_ms) AS sum_signal_wait_time_ms, - SUM(x.sum_waiting_tasks) AS sum_waiting_tasks - FROM ( - SELECT - 1 AS Pass, - CASE @Seconds WHEN 0 THEN @StartSampleTime ELSE SYSDATETIMEOFFSET() END AS SampleTime, - owt.wait_type, - CASE @Seconds WHEN 0 THEN 0 ELSE SUM(owt.wait_duration_ms) OVER (PARTITION BY owt.wait_type, owt.session_id) - - CASE WHEN @Seconds = 0 THEN 0 ELSE (@Seconds * 1000) END END AS sum_wait_time_ms, - 0 AS sum_signal_wait_time_ms, - 0 AS sum_waiting_tasks - FROM sys.dm_os_waiting_tasks owt - WHERE owt.session_id > 50 - AND owt.wait_duration_ms >= CASE @Seconds WHEN 0 THEN 0 ELSE @Seconds * 1000 END - UNION ALL - SELECT - 1 AS Pass, - CASE @Seconds WHEN 0 THEN @StartSampleTime ELSE SYSDATETIMEOFFSET() END AS SampleTime, - os.wait_type, - CASE @Seconds WHEN 0 THEN 0 ELSE SUM(os.wait_time_ms) OVER (PARTITION BY os.wait_type) END AS sum_wait_time_ms, - CASE @Seconds WHEN 0 THEN 0 ELSE SUM(os.signal_wait_time_ms) OVER (PARTITION BY os.wait_type ) END AS sum_signal_wait_time_ms, - CASE @Seconds WHEN 0 THEN 0 ELSE SUM(os.waiting_tasks_count) OVER (PARTITION BY os.wait_type) END AS sum_waiting_tasks - FROM sys.dm_os_wait_stats os - ) x - WHERE NOT EXISTS - ( - SELECT * - FROM ##WaitCategories AS wc - WHERE wc.WaitType = x.wait_type - AND wc.Ignorable = 1 - ) - GROUP BY x.Pass, x.SampleTime, x.wait_type - ORDER BY sum_wait_time_ms DESC; - - - INSERT INTO #FileStats (Pass, SampleTime, DatabaseID, FileID, DatabaseName, FileLogicalName, SizeOnDiskMB, io_stall_read_ms , - num_of_reads, [bytes_read] , io_stall_write_ms,num_of_writes, [bytes_written], PhysicalName, TypeDesc) - SELECT - 1 AS Pass, - CASE @Seconds WHEN 0 THEN @StartSampleTime ELSE SYSDATETIMEOFFSET() END AS SampleTime, - mf.[database_id], - mf.[file_id], - DB_NAME(vfs.database_id) AS [db_name], - mf.name + N' [' + mf.type_desc COLLATE SQL_Latin1_General_CP1_CI_AS + N']' AS file_logical_name , - CAST(( ( vfs.size_on_disk_bytes / 1024.0 ) / 1024.0 ) AS INT) AS size_on_disk_mb , - CASE @Seconds WHEN 0 THEN 0 ELSE vfs.io_stall_read_ms END , - CASE @Seconds WHEN 0 THEN 0 ELSE vfs.num_of_reads END , - CASE @Seconds WHEN 0 THEN 0 ELSE vfs.[num_of_bytes_read] END , - CASE @Seconds WHEN 0 THEN 0 ELSE vfs.io_stall_write_ms END , - CASE @Seconds WHEN 0 THEN 0 ELSE vfs.num_of_writes END , - CASE @Seconds WHEN 0 THEN 0 ELSE vfs.[num_of_bytes_written] END , - mf.physical_name, - mf.type_desc - FROM sys.dm_io_virtual_file_stats (NULL, NULL) AS vfs - INNER JOIN #MasterFiles AS mf ON vfs.file_id = mf.file_id - AND vfs.database_id = mf.database_id - WHERE vfs.num_of_reads > 0 - OR vfs.num_of_writes > 0; - - INSERT INTO #PerfmonStats (Pass, SampleTime, [object_name],[counter_name],[instance_name],[cntr_value],[cntr_type]) - SELECT 1 AS Pass, - CASE @Seconds WHEN 0 THEN @StartSampleTime ELSE SYSDATETIMEOFFSET() END AS SampleTime, RTRIM(dmv.object_name), RTRIM(dmv.counter_name), RTRIM(dmv.instance_name), CASE @Seconds WHEN 0 THEN 0 ELSE dmv.cntr_value END, dmv.cntr_type - FROM #PerfmonCounters counters - INNER JOIN sys.dm_os_performance_counters dmv ON counters.counter_name COLLATE SQL_Latin1_General_CP1_CI_AS = RTRIM(dmv.counter_name) COLLATE SQL_Latin1_General_CP1_CI_AS - AND counters.[object_name] COLLATE SQL_Latin1_General_CP1_CI_AS = RTRIM(dmv.[object_name]) COLLATE SQL_Latin1_General_CP1_CI_AS - AND (counters.[instance_name] IS NULL OR counters.[instance_name] COLLATE SQL_Latin1_General_CP1_CI_AS = RTRIM(dmv.[instance_name]) COLLATE SQL_Latin1_General_CP1_CI_AS); - - /* For Github #2743: */ - CREATE TABLE #TempdbOperationalStats (object_id BIGINT PRIMARY KEY CLUSTERED, - forwarded_fetch_count BIGINT); - INSERT INTO #TempdbOperationalStats (object_id, forwarded_fetch_count) - SELECT object_id, forwarded_fetch_count - FROM tempdb.sys.dm_db_index_operational_stats(DB_ID('tempdb'), NULL, NULL, NULL) os - WHERE os.database_id = DB_ID('tempdb') - AND os.forwarded_fetch_count > 100; - - - /* If they want to run sp_BlitzWho and export to table, go for it. */ - IF @OutputTableNameBlitzWho IS NOT NULL - AND @OutputDatabaseName IS NOT NULL - AND @OutputSchemaName IS NOT NULL - AND EXISTS ( SELECT * - FROM sys.databases - WHERE QUOTENAME([name]) = @OutputDatabaseName) - BEGIN - RAISERROR('Logging sp_BlitzWho to table',10,1) WITH NOWAIT; - EXEC sp_BlitzWho @OutputDatabaseName = @UnquotedOutputDatabaseName, @OutputSchemaName = @UnquotedOutputSchemaName, @OutputTableName = @OutputTableNameBlitzWho, @CheckDateOverride = @StartSampleTime; - END - - RAISERROR('Beginning investigatory queries',10,1) WITH NOWAIT; - - - /* Maintenance Tasks Running - Backup Running - CheckID 1 */ - IF @Seconds > 0 - BEGIN - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 1',10,1) WITH NOWAIT; - END - - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, QueryHash) - SELECT 1 AS CheckID, - 1 AS Priority, - 'Maintenance Tasks Running' AS FindingGroup, - 'Backup Running' AS Finding, - 'http://www.BrentOzar.com/askbrent/backups/' AS URL, - 'Backup of ' + DB_NAME(db.resource_database_id) + ' database (' + (SELECT CAST(CAST(SUM(size * 8.0 / 1024 / 1024) AS BIGINT) AS NVARCHAR) FROM #MasterFiles WHERE database_id = db.resource_database_id) + 'GB) ' + @LineFeed - + CAST(r.percent_complete AS NVARCHAR(100)) + '% complete, has been running since ' + CAST(r.start_time AS NVARCHAR(100)) + '. ' + @LineFeed - + CASE WHEN COALESCE(s.nt_user_name, s.login_name) IS NOT NULL THEN (' Login: ' + COALESCE(s.nt_user_name, s.login_name) + ' ') ELSE '' END AS Details, - 'KILL ' + CAST(r.session_id AS NVARCHAR(100)) + ';' AS HowToStopIt, - pl.query_plan AS QueryPlan, - r.start_time AS StartTime, - s.login_name AS LoginName, - s.nt_user_name AS NTUserName, - s.[program_name] AS ProgramName, - s.[host_name] AS HostName, - db.[resource_database_id] AS DatabaseID, - DB_NAME(db.resource_database_id) AS DatabaseName, - 0 AS OpenTransactionCount, - r.query_hash - FROM sys.dm_exec_requests r - INNER JOIN sys.dm_exec_connections c ON r.session_id = c.session_id - INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id - INNER JOIN ( - SELECT DISTINCT request_session_id, resource_database_id - FROM sys.dm_tran_locks - WHERE resource_type = N'DATABASE' - AND request_mode = N'S' - AND request_status = N'GRANT' - AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.session_id = db.request_session_id - CROSS APPLY sys.dm_exec_query_plan(r.plan_handle) pl - WHERE r.command LIKE 'BACKUP%' - AND r.start_time <= DATEADD(minute, -5, GETDATE()) - AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs); - END - - /* If there's a backup running, add details explaining how long full backup has been taking in the last month. */ - IF @Seconds > 0 AND CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) <> 'SQL Azure' - BEGIN - SET @StringToExecute = 'UPDATE #BlitzFirstResults SET Details = Details + '' Over the last 60 days, the full backup usually takes '' + CAST((SELECT AVG(DATEDIFF(mi, bs.backup_start_date, bs.backup_finish_date)) FROM msdb.dbo.backupset bs WHERE abr.DatabaseName = bs.database_name AND bs.type = ''D'' AND bs.backup_start_date > DATEADD(dd, -60, SYSDATETIMEOFFSET()) AND bs.backup_finish_date IS NOT NULL) AS NVARCHAR(100)) + '' minutes.'' FROM #BlitzFirstResults abr WHERE abr.CheckID = 1 AND EXISTS (SELECT * FROM msdb.dbo.backupset bs WHERE bs.type = ''D'' AND bs.backup_start_date > DATEADD(dd, -60, SYSDATETIMEOFFSET()) AND bs.backup_finish_date IS NOT NULL AND abr.DatabaseName = bs.database_name AND DATEDIFF(mi, bs.backup_start_date, bs.backup_finish_date) > 1)'; - EXEC(@StringToExecute); - END; - - - /* Maintenance Tasks Running - DBCC CHECK* Running - CheckID 2 */ - IF @Seconds > 0 AND EXISTS(SELECT * FROM sys.dm_exec_requests WHERE command LIKE 'DBCC%') - BEGIN - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 2',10,1) WITH NOWAIT; - END - - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, QueryHash) - SELECT 2 AS CheckID, - 1 AS Priority, - 'Maintenance Tasks Running' AS FindingGroup, - 'DBCC CHECK* Running' AS Finding, - 'http://www.BrentOzar.com/askbrent/dbcc/' AS URL, - 'Corruption check of ' + DB_NAME(db.resource_database_id) + ' database (' + (SELECT CAST(CAST(SUM(size * 8.0 / 1024 / 1024) AS BIGINT) AS NVARCHAR) FROM #MasterFiles WHERE database_id = db.resource_database_id) + 'GB) 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, - s.login_name AS LoginName, - s.nt_user_name AS NTUserName, - s.[program_name] AS ProgramName, - s.[host_name] AS HostName, - db.[resource_database_id] AS DatabaseID, - DB_NAME(db.resource_database_id) AS DatabaseName, - 0 AS OpenTransactionCount, - r.query_hash - FROM sys.dm_exec_requests r - INNER JOIN sys.dm_exec_connections c ON r.session_id = c.session_id - INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id - INNER JOIN (SELECT DISTINCT l.request_session_id, l.resource_database_id - FROM sys.dm_tran_locks l - INNER JOIN sys.databases d ON l.resource_database_id = d.database_id - WHERE l.resource_type = N'DATABASE' - AND l.request_mode = N'S' - AND l.request_status = N'GRANT' - AND l.request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.session_id = db.request_session_id - OUTER APPLY sys.dm_exec_query_plan(r.plan_handle) pl - OUTER APPLY sys.dm_exec_sql_text(r.sql_handle) AS t - WHERE r.command LIKE 'DBCC%' - AND CAST(t.text AS NVARCHAR(4000)) NOT LIKE '%dm_db_index_physical_stats%' - AND CAST(t.text AS NVARCHAR(4000)) NOT LIKE '%ALTER INDEX%' - AND CAST(t.text AS NVARCHAR(4000)) NOT LIKE '%fileproperty%' - AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs); - END - - /* Maintenance Tasks Running - Restore Running - CheckID 3 */ - IF @Seconds > 0 - BEGIN - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 3',10,1) WITH NOWAIT; - END - - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, QueryHash) - SELECT 3 AS CheckID, - 1 AS Priority, - 'Maintenance Tasks Running' AS FindingGroup, - 'Restore Running' AS Finding, - 'http://www.BrentOzar.com/askbrent/backups/' AS URL, - 'Restore of ' + DB_NAME(db.resource_database_id) + ' database (' + (SELECT CAST(CAST(SUM(size * 8.0 / 1024 / 1024) AS BIGINT) AS NVARCHAR) FROM #MasterFiles WHERE database_id = db.resource_database_id) + '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, - s.login_name AS LoginName, - s.nt_user_name AS NTUserName, - s.[program_name] AS ProgramName, - s.[host_name] AS HostName, - db.[resource_database_id] AS DatabaseID, - DB_NAME(db.resource_database_id) AS DatabaseName, - 0 AS OpenTransactionCount, - r.query_hash - FROM sys.dm_exec_requests r - INNER JOIN sys.dm_exec_connections c ON r.session_id = c.session_id - INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id - INNER JOIN ( - SELECT DISTINCT request_session_id, resource_database_id - FROM sys.dm_tran_locks - WHERE resource_type = N'DATABASE' - AND request_mode = N'S' - AND request_status = N'GRANT') AS db ON s.session_id = db.request_session_id - CROSS APPLY sys.dm_exec_query_plan(r.plan_handle) pl - WHERE r.command LIKE 'RESTORE%' - AND s.program_name <> 'SQL Server Log Shipping' - AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs); - END - - /* SQL Server Internal Maintenance - Database File Growing - CheckID 4 */ - IF @Seconds > 0 - BEGIN - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 4',10,1) WITH NOWAIT; - END - - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount) - SELECT 4 AS CheckID, - 1 AS Priority, - 'SQL Server Internal Maintenance' AS FindingGroup, - 'Database File Growing' AS Finding, - 'http://www.BrentOzar.com/go/instant' AS URL, - 'SQL Server is waiting for Windows to provide storage space for a database restore, a data file growth, or a log file growth. This task has been running since ' + CAST(r.start_time AS NVARCHAR(100)) + '.' + @LineFeed + 'Check the query plan (expert mode) to identify the database involved.' AS Details, - 'Unfortunately, you can''t stop this, but you can prevent it next time. Check out http://www.BrentOzar.com/go/instant for details.' AS HowToStopIt, - pl.query_plan AS QueryPlan, - r.start_time AS StartTime, - s.login_name AS LoginName, - s.nt_user_name AS NTUserName, - s.[program_name] AS ProgramName, - s.[host_name] AS HostName, - NULL AS DatabaseID, - NULL AS DatabaseName, - 0 AS OpenTransactionCount - FROM sys.dm_os_waiting_tasks t - INNER JOIN sys.dm_exec_connections c ON t.session_id = c.session_id - INNER JOIN sys.dm_exec_requests r ON t.session_id = r.session_id - INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id - CROSS APPLY sys.dm_exec_query_plan(r.plan_handle) pl - WHERE t.wait_type = 'PREEMPTIVE_OS_WRITEFILEGATHER' - AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs); - END - - /* Query Problems - Long-Running Query Blocking Others - CheckID 5 */ - IF SERVERPROPERTY('Edition') <> 'SQL Azure' AND @Seconds > 0 AND EXISTS(SELECT * FROM sys.dm_os_waiting_tasks WHERE wait_type LIKE 'LCK%' AND wait_duration_ms > 30000) - BEGIN - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 5',10,1) WITH NOWAIT; - END - - SET @StringToExecute = N'INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, QueryText, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, QueryHash) - SELECT 5 AS CheckID, - 1 AS Priority, - ''Query Problems'' AS FindingGroup, - ''Long-Running Query Blocking Others'' AS Finding, - ''http://www.BrentOzar.com/go/blocking'' AS URL, - ''Query in '' + COALESCE(DB_NAME(COALESCE((SELECT TOP 1 dbid FROM sys.dm_exec_sql_text(r.sql_handle)), - (SELECT TOP 1 t.dbid FROM master..sysprocesses spBlocker CROSS APPLY sys.dm_exec_sql_text(spBlocker.sql_handle) t WHERE spBlocker.spid = tBlocked.blocking_session_id))), ''(Unknown)'') + '' has a last request start time of '' + CAST(s.last_request_start_time AS NVARCHAR(100)) + ''. Query follows: ' - + @LineFeed + @LineFeed + - '''+ CAST(COALESCE((SELECT TOP 1 [text] FROM sys.dm_exec_sql_text(r.sql_handle)), - (SELECT TOP 1 [text] FROM master..sysprocesses spBlocker CROSS APPLY sys.dm_exec_sql_text(spBlocker.sql_handle) WHERE spBlocker.spid = tBlocked.blocking_session_id), '''') AS NVARCHAR(2000)) AS Details, - ''KILL '' + CAST(tBlocked.blocking_session_id AS NVARCHAR(100)) + '';'' AS HowToStopIt, - (SELECT TOP 1 query_plan FROM sys.dm_exec_query_plan(r.plan_handle)) AS QueryPlan, - COALESCE((SELECT TOP 1 [text] FROM sys.dm_exec_sql_text(r.sql_handle)), - (SELECT TOP 1 [text] FROM master..sysprocesses spBlocker CROSS APPLY sys.dm_exec_sql_text(spBlocker.sql_handle) WHERE spBlocker.spid = tBlocked.blocking_session_id)) AS QueryText, - r.start_time AS StartTime, - s.login_name AS LoginName, - s.nt_user_name AS NTUserName, - s.[program_name] AS ProgramName, - s.[host_name] AS HostName, - r.[database_id] AS DatabaseID, - DB_NAME(r.database_id) AS DatabaseName, - 0 AS OpenTransactionCount, - r.query_hash - FROM sys.dm_os_waiting_tasks tBlocked - INNER JOIN sys.dm_exec_sessions s ON tBlocked.blocking_session_id = s.session_id - LEFT OUTER JOIN sys.dm_exec_requests r ON s.session_id = r.session_id - INNER JOIN sys.dm_exec_connections c ON s.session_id = c.session_id - WHERE tBlocked.wait_type LIKE ''LCK%'' AND tBlocked.wait_duration_ms > 30000 - /* And the blocking session ID is not blocked by anyone else: */ - AND NOT EXISTS(SELECT * FROM sys.dm_os_waiting_tasks tBlocking WHERE s.session_id = tBlocking.session_id AND tBlocking.session_id <> tBlocking.blocking_session_id AND tBlocking.blocking_session_id IS NOT NULL) - AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs);'; - EXECUTE sp_executesql @StringToExecute; - END; - - /* Query Problems - Plan Cache Erased Recently - CheckID 7 */ - IF DATEADD(mi, -15, SYSDATETIME()) < (SELECT TOP 1 creation_time FROM sys.dm_exec_query_stats ORDER BY creation_time) - BEGIN - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 7',10,1) WITH NOWAIT; - END - - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) - SELECT TOP 1 7 AS CheckID, - 50 AS Priority, - 'Query Problems' AS FindingGroup, - 'Plan Cache Erased Recently' AS Finding, - 'http://www.BrentOzar.com/askbrent/plan-cache-erased-recently/' AS URL, - 'The oldest query in the plan cache was created at ' + CAST(creation_time AS NVARCHAR(50)) + '. ' + @LineFeed + @LineFeed - + 'This indicates that someone ran DBCC FREEPROCCACHE at that time,' + @LineFeed - + 'Giving SQL Server temporary amnesia. Now, as queries come in,' + @LineFeed - + 'SQL Server has to use a lot of CPU power in order to build execution' + @LineFeed - + 'plans and put them in cache again. This causes high CPU loads.' AS Details, - 'Find who did that, and stop them from doing it again.' AS HowToStopIt - FROM sys.dm_exec_query_stats - ORDER BY creation_time; - END; - - - /* Query Problems - Sleeping Query with Open Transactions - CheckID 8 */ - IF @Seconds > 0 - BEGIN - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 8',10,1) WITH NOWAIT; - END - - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, OpenTransactionCount) - SELECT 8 AS CheckID, - 50 AS Priority, - 'Query Problems' AS FindingGroup, - 'Sleeping Query with Open Transactions' AS Finding, - 'http://www.brentozar.com/askbrent/sleeping-query-with-open-transactions/' AS URL, - 'Database: ' + DB_NAME(db.resource_database_id) + @LineFeed + 'Host: ' + s.[host_name] + @LineFeed + 'Program: ' + s.[program_name] + @LineFeed + 'Asleep with open transactions and locks since ' + CAST(s.last_request_end_time AS NVARCHAR(100)) + '. ' AS Details, - 'KILL ' + CAST(s.session_id AS NVARCHAR(100)) + ';' AS HowToStopIt, - s.last_request_start_time AS StartTime, - s.login_name AS LoginName, - s.nt_user_name AS NTUserName, - s.[program_name] AS ProgramName, - s.[host_name] AS HostName, - db.[resource_database_id] AS DatabaseID, - DB_NAME(db.resource_database_id) AS DatabaseName, - (SELECT TOP 1 [text] FROM sys.dm_exec_sql_text(c.most_recent_sql_handle)) AS QueryText, - sessions_with_transactions.open_transaction_count AS OpenTransactionCount - FROM (SELECT session_id, SUM(open_transaction_count) AS open_transaction_count FROM sys.dm_exec_requests WHERE open_transaction_count > 0 GROUP BY session_id) AS sessions_with_transactions - INNER JOIN sys.dm_exec_sessions s ON sessions_with_transactions.session_id = s.session_id - INNER JOIN sys.dm_exec_connections c ON s.session_id = c.session_id - INNER JOIN ( - SELECT DISTINCT request_session_id, resource_database_id - FROM sys.dm_tran_locks - WHERE resource_type = N'DATABASE' - AND request_mode = N'S' - AND request_status = N'GRANT' - AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.session_id = db.request_session_id - WHERE s.status = 'sleeping' - AND s.last_request_end_time < DATEADD(ss, -10, SYSDATETIME()) - AND EXISTS(SELECT * FROM sys.dm_tran_locks WHERE request_session_id = s.session_id - AND NOT (resource_type = N'DATABASE' AND request_mode = N'S' AND request_status = N'GRANT' AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE')); - END - - /*Query Problems - Clients using implicit transactions - CheckID 37 */ - IF @Seconds > 0 - AND ( @@VERSION NOT LIKE 'Microsoft SQL Server 2005%' - AND @@VERSION NOT LIKE 'Microsoft SQL Server 2008%' - AND @@VERSION NOT LIKE 'Microsoft SQL Server 2008 R2%' ) - BEGIN - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 37',10,1) WITH NOWAIT; - END - - SET @StringToExecute = N'INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, OpenTransactionCount) - SELECT 37 AS CheckId, - 50 AS Priority, - ''Query Problems'' AS FindingsGroup, - ''Implicit Transactions'', - ''https://www.brentozar.com/go/ImplicitTransactions/'' AS URL, - ''Database: '' + DB_NAME(s.database_id) + '' '' + CHAR(13) + CHAR(10) + - ''Host: '' + s.[host_name] + '' '' + CHAR(13) + CHAR(10) + - ''Program: '' + s.[program_name] + '' '' + CHAR(13) + CHAR(10) + - CONVERT(NVARCHAR(10), s.open_transaction_count) + - '' open transactions since: '' + - CONVERT(NVARCHAR(30), tat.transaction_begin_time) + ''. '' - AS Details, - ''Run sp_BlitzWho and check the is_implicit_transaction column to spot the culprits. -If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, - tat.transaction_begin_time, - s.login_name, - s.nt_user_name, - s.program_name, - s.host_name, - s.database_id, - DB_NAME(s.database_id) AS DatabaseName, - NULL AS Querytext, - s.open_transaction_count AS OpenTransactionCount - FROM sys.dm_tran_active_transactions AS tat - LEFT JOIN sys.dm_tran_session_transactions AS tst - ON tst.transaction_id = tat.transaction_id - LEFT JOIN sys.dm_exec_sessions AS s - ON s.session_id = tst.session_id - WHERE tat.name = ''implicit_transaction''; - ' - EXECUTE sp_executesql @StringToExecute; - END; - - /* Query Problems - Query Rolling Back - CheckID 9 */ - IF @Seconds > 0 - BEGIN - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 9',10,1) WITH NOWAIT; - END - - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, QueryHash) - SELECT 9 AS CheckID, - 1 AS Priority, - 'Query Problems' AS FindingGroup, - 'Query Rolling Back' AS Finding, - 'http://www.BrentOzar.com/askbrent/rollback/' AS URL, - 'Rollback started at ' + CAST(r.start_time AS NVARCHAR(100)) + ', is ' + CAST(r.percent_complete AS NVARCHAR(100)) + '% complete.' AS Details, - 'Unfortunately, you can''t stop this. Whatever you do, don''t restart the server in an attempt to fix it - SQL Server will keep rolling back.' AS HowToStopIt, - r.start_time AS StartTime, - s.login_name AS LoginName, - s.nt_user_name AS NTUserName, - s.[program_name] AS ProgramName, - s.[host_name] AS HostName, - db.[resource_database_id] AS DatabaseID, - DB_NAME(db.resource_database_id) AS DatabaseName, - (SELECT TOP 1 [text] FROM sys.dm_exec_sql_text(c.most_recent_sql_handle)) AS QueryText, - r.query_hash - FROM sys.dm_exec_sessions s - INNER JOIN sys.dm_exec_connections c ON s.session_id = c.session_id - INNER JOIN sys.dm_exec_requests r ON s.session_id = r.session_id - LEFT OUTER JOIN ( - SELECT DISTINCT request_session_id, resource_database_id - FROM sys.dm_tran_locks - WHERE resource_type = N'DATABASE' - AND request_mode = N'S' - AND request_status = N'GRANT' - AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.session_id = db.request_session_id - WHERE r.status = 'rollback'; - END - - /* Server Performance - Too Much Free Memory - CheckID 34 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 34',10,1) WITH NOWAIT; - END - - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) - SELECT 34 AS CheckID, - 50 AS Priority, - 'Server Performance' AS FindingGroup, - 'Too Much Free Memory' AS Finding, - 'https://BrentOzar.com/go/freememory' AS URL, - CAST((CAST(cFree.cntr_value AS BIGINT) / 1024 / 1024 ) AS NVARCHAR(100)) + N'GB of free memory inside SQL Server''s buffer pool,' + @LineFeed + ' which is ' + CAST((CAST(cTotal.cntr_value AS BIGINT) / 1024 / 1024) AS NVARCHAR(100)) + N'GB. You would think lots of free memory would be good, but check out the URL for more information.' AS Details, - 'Run sp_BlitzCache @SortOrder = ''memory grant'' to find queries with huge memory grants and tune them.' AS HowToStopIt - FROM sys.dm_os_performance_counters cFree - INNER JOIN sys.dm_os_performance_counters cTotal ON cTotal.object_name LIKE N'%Memory Manager%' - AND cTotal.counter_name = N'Total Server Memory (KB) ' - WHERE cFree.object_name LIKE N'%Memory Manager%' - AND cFree.counter_name = N'Free Memory (KB) ' - AND CAST(cFree.cntr_value AS BIGINT) > 20480000000 - AND CAST(cTotal.cntr_value AS BIGINT) * .3 <= CAST(cFree.cntr_value AS BIGINT) - AND CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) NOT LIKE '%Standard%'; - - /* Server Performance - Target Memory Lower Than Max - CheckID 35 */ - IF SERVERPROPERTY('Edition') <> 'SQL Azure' - BEGIN - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 35',10,1) WITH NOWAIT; - END - - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) - SELECT 35 AS CheckID, - 10 AS Priority, - 'Server Performance' AS FindingGroup, - 'Target Memory Lower Than Max' AS Finding, - 'https://BrentOzar.com/go/target' AS URL, - N'Max server memory is ' + CAST(cMax.value_in_use AS NVARCHAR(50)) + N' MB but target server memory is only ' + CAST((CAST(cTarget.cntr_value AS BIGINT) / 1024) AS NVARCHAR(50)) + N' MB,' + @LineFeed - + N'indicating that SQL Server may be under external memory pressure or max server memory may be set too high.' AS Details, - 'Investigate what OS processes are using memory, and double-check the max server memory setting.' AS HowToStopIt - FROM sys.configurations cMax - INNER JOIN sys.dm_os_performance_counters cTarget ON cTarget.object_name LIKE N'%Memory Manager%' - AND cTarget.counter_name = N'Target Server Memory (KB) ' - WHERE cMax.name = 'max server memory (MB)' - AND CAST(cMax.value_in_use AS BIGINT) >= 1.5 * (CAST(cTarget.cntr_value AS BIGINT) / 1024) - AND CAST(cMax.value_in_use AS BIGINT) < 2147483647 /* Not set to default of unlimited */ - AND CAST(cTarget.cntr_value AS BIGINT) < .8 * (SELECT available_physical_memory_kb FROM sys.dm_os_sys_memory); /* Target memory less than 80% of physical memory (in case they set max too high) */ - END - - /* Server Info - Database Size, Total GB - CheckID 21 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 21',10,1) WITH NOWAIT; - END - - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) - SELECT 21 AS CheckID, - 251 AS Priority, - 'Server Info' AS FindingGroup, - 'Database Size, Total GB' AS Finding, - CAST(SUM (CAST(size AS BIGINT)*8./1024./1024.) AS VARCHAR(100)) AS Details, - SUM (CAST(size AS BIGINT))*8./1024./1024. AS DetailsInt, - 'http://www.BrentOzar.com/askbrent/' AS URL - FROM #MasterFiles - WHERE database_id > 4; - - /* Server Info - Database Count - CheckID 22 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 22',10,1) WITH NOWAIT; - END - - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) - SELECT 22 AS CheckID, - 251 AS Priority, - 'Server Info' AS FindingGroup, - 'Database Count' AS Finding, - CAST(SUM(1) AS VARCHAR(100)) AS Details, - SUM (1) AS DetailsInt, - 'http://www.BrentOzar.com/askbrent/' AS URL - FROM sys.databases - WHERE database_id > 4; - - /* Server Info - Memory Grants pending - CheckID 39 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 39',10,1) WITH NOWAIT; - END - - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) - SELECT 39 AS CheckID, - 50 AS Priority, - 'Server Performance' AS FindingGroup, - 'Memory Grants Pending' AS Finding, - CAST(PendingGrants.Details AS NVARCHAR(50)) AS Details, - PendingGrants.DetailsInt, - 'https://www.brentozar.com/blitz/memory-grants/' AS URL - FROM - ( - SELECT - COUNT(1) AS Details, - COUNT(1) AS DetailsInt - FROM sys.dm_exec_query_memory_grants AS Grants - WHERE queue_id IS NOT NULL - ) AS PendingGrants - WHERE PendingGrants.Details > 0; - - /* Server Info - Memory Grant/Workspace info - CheckID 40 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 40',10,1) WITH NOWAIT; - END - - DECLARE @MaxWorkspace BIGINT - SET @MaxWorkspace = (SELECT CAST(cntr_value AS BIGINT)/1024 FROM #PerfmonStats WHERE counter_name = N'Maximum Workspace Memory (KB)') - - IF (@MaxWorkspace IS NULL - OR @MaxWorkspace = 0) - BEGIN - SET @MaxWorkspace = 1 - END - - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) - SELECT 40 AS CheckID, - 251 AS Priority, - 'Server Info' AS FindingGroup, - 'Memory Grant/Workspace info' AS Finding, - + 'Grants Outstanding: ' + CAST((SELECT COUNT(*) FROM sys.dm_exec_query_memory_grants WHERE queue_id IS NULL) AS NVARCHAR(50)) + @LineFeed - + 'Total Granted(MB): ' + CAST(ISNULL(SUM(Grants.granted_memory_kb) / 1024, 0) AS NVARCHAR(50)) + @LineFeed - + 'Total WorkSpace(MB): ' + CAST(ISNULL(@MaxWorkspace, 0) AS NVARCHAR(50)) + @LineFeed - + 'Granted workspace: ' + CAST(ISNULL((CAST(SUM(Grants.granted_memory_kb) / 1024 AS MONEY) - / CAST(@MaxWorkspace AS MONEY)) * 100, 0) AS NVARCHAR(50)) + '%' + @LineFeed - + 'Oldest Grant in seconds: ' + CAST(ISNULL(DATEDIFF(SECOND, MIN(Grants.request_time), GETDATE()), 0) AS NVARCHAR(50)) AS Details, - (SELECT COUNT(*) FROM sys.dm_exec_query_memory_grants WHERE queue_id IS NULL) AS DetailsInt, - 'http://www.BrentOzar.com/askbrent/' AS URL - FROM sys.dm_exec_query_memory_grants AS Grants; - - /* Query Problems - Queries with high memory grants - CheckID 46 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 46',10,1) WITH NOWAIT; - END - - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, URL, QueryText, QueryPlan) - SELECT 46 AS CheckID, - 100 AS Priority, - 'Query Problems' AS FindingGroup, - 'Query with a memory grant exceeding ' - +CAST(@MemoryGrantThresholdPct AS NVARCHAR(15)) - +'%' AS Finding, - 'Granted size: '+ CAST(CAST(Grants.granted_memory_kb / 1024 AS INT) AS NVARCHAR(50)) - +N'MB ' - + @LineFeed - +N'Granted pct of max workspace: ' - + CAST(ISNULL((CAST(Grants.granted_memory_kb / 1024 AS MONEY) - / CAST(@MaxWorkspace AS MONEY)) * 100, 0) AS NVARCHAR(50)) + '%' - + @LineFeed - +N'SQLHandle: ' - +CONVERT(NVARCHAR(128),Grants.[sql_handle],1), - 'https://www.brentozar.com/memory-grants-sql-servers-public-toilet/' AS URL, - SQLText.[text], - QueryPlan.query_plan - FROM sys.dm_exec_query_memory_grants AS Grants - OUTER APPLY sys.dm_exec_sql_text(Grants.[sql_handle]) AS SQLText - OUTER APPLY sys.dm_exec_query_plan(Grants.[plan_handle]) AS QueryPlan - WHERE Grants.granted_memory_kb > ((@MemoryGrantThresholdPct/100.00)*(@MaxWorkspace*1024)); - - /* Query Problems - Memory Leak in USERSTORE_TOKENPERM Cache - CheckID 45 */ - IF EXISTS (SELECT * FROM sys.all_columns WHERE object_id = OBJECT_ID('sys.dm_os_memory_clerks') AND name = 'pages_kb') - BEGIN - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 45',10,1) WITH NOWAIT; - END - - /* SQL 2012+ version */ - SET @StringToExecute = N' - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, URL) - SELECT 45 AS CheckID, - 50 AS Priority, - ''Query Problems'' AS FindingsGroup, - ''Memory Leak in USERSTORE_TOKENPERM Cache'' AS Finding, - N''UserStore_TokenPerm clerk is using '' + CAST(CAST(SUM(CASE WHEN type = ''USERSTORE_TOKENPERM'' AND name = ''TokenAndPermUserStore'' THEN pages_kb * 1.0 ELSE 0.0 END) / 1024.0 / 1024.0 AS INT) AS NVARCHAR(100)) - + N''GB RAM, total buffer pool is '' + CAST(CAST(SUM(pages_kb) / 1024.0 / 1024.0 AS INT) AS NVARCHAR(100)) + N''GB.'' - AS details, - ''https://www.BrentOzar.com/go/userstore'' AS URL - FROM sys.dm_os_memory_clerks - HAVING SUM(CASE WHEN type = ''USERSTORE_TOKENPERM'' AND name = ''TokenAndPermUserStore'' THEN pages_kb * 1.0 ELSE 0.0 END) / SUM(pages_kb) >= 0.1 - AND SUM(pages_kb) / 1024.0 / 1024.0 >= 1; /* At least 1GB RAM overall */'; - EXEC sp_executesql @StringToExecute; - END - ELSE - BEGIN - /* Antiques Roadshow SQL 2008R2 - version */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 45 (Legacy version)',10,1) WITH NOWAIT; - END - - SET @StringToExecute = N' - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, URL) - SELECT 45 AS CheckID, - 50 AS Priority, - ''Performance'' AS FindingsGroup, - ''Memory Leak in USERSTORE_TOKENPERM Cache'' AS Finding, - N''UserStore_TokenPerm clerk is using '' + CAST(CAST(SUM(CASE WHEN type = ''USERSTORE_TOKENPERM'' AND name = ''TokenAndPermUserStore'' THEN single_pages_kb + multi_pages_kb * 1.0 ELSE 0.0 END) / 1024.0 / 1024.0 AS INT) AS NVARCHAR(100)) - + N''GB RAM, total buffer pool is '' + CAST(CAST(SUM(single_pages_kb + multi_pages_kb) / 1024.0 / 1024.0 AS INT) AS NVARCHAR(100)) + N''GB.'' - AS details, - ''https://www.BrentOzar.com/go/userstore'' AS URL - FROM sys.dm_os_memory_clerks - HAVING SUM(CASE WHEN type = ''USERSTORE_TOKENPERM'' AND name = ''TokenAndPermUserStore'' THEN single_pages_kb + multi_pages_kb * 1.0 ELSE 0.0 END) / SUM(single_pages_kb + multi_pages_kb) >= 0.1 - AND SUM(single_pages_kb + multi_pages_kb) / 1024.0 / 1024.0 >= 1; /* At least 1GB RAM overall */'; - EXEC sp_executesql @StringToExecute; - END - - - - IF @Seconds > 0 - BEGIN - - IF EXISTS ( SELECT 1/0 - FROM sys.all_objects AS ao - WHERE ao.name = 'dm_exec_query_profiles' ) - BEGIN - - IF EXISTS( SELECT 1/0 - FROM sys.dm_exec_requests AS r - JOIN sys.dm_exec_sessions AS s - ON r.session_id = s.session_id - WHERE s.host_name IS NOT NULL - AND r.total_elapsed_time > 5000 ) - BEGIN - - SET @StringToExecute = N' - DECLARE @bad_estimate TABLE - ( - session_id INT, - request_id INT, - estimate_inaccuracy BIT - ); - - INSERT @bad_estimate ( session_id, request_id, estimate_inaccuracy ) - SELECT x.session_id, - x.request_id, - x.estimate_inaccuracy - FROM ( - SELECT deqp.session_id, - deqp.request_id, - CASE WHEN deqp.row_count > ( deqp.estimate_row_count * 10000 ) - THEN 1 - ELSE 0 - END AS estimate_inaccuracy - FROM sys.dm_exec_query_profiles AS deqp - WHERE deqp.session_id <> @@SPID - ) AS x - WHERE x.estimate_inaccuracy = 1 - GROUP BY x.session_id, - x.request_id, - x.estimate_inaccuracy; - - DECLARE @parallelism_skew TABLE - ( - session_id INT, - request_id INT, - parallelism_skew BIT - ); - - INSERT @parallelism_skew ( session_id, request_id, parallelism_skew ) - SELECT y.session_id, - y.request_id, - y.parallelism_skew - FROM ( - SELECT x.session_id, - x.request_id, - x.node_id, - x.thread_id, - x.row_count, - x.sum_node_rows, - x.node_dop, - x.sum_node_rows / x.node_dop AS even_distribution, - x.row_count / (1. * ISNULL(NULLIF(x.sum_node_rows / x.node_dop, 0), 1)) AS skew_percent, - CASE - WHEN x.row_count > 10000 - AND x.row_count / (1. * ISNULL(NULLIF(x.sum_node_rows / x.node_dop, 0), 1)) > 2. - THEN 1 - WHEN x.row_count > 10000 - AND x.row_count / (1. * ISNULL(NULLIF(x.sum_node_rows / x.node_dop, 0), 1)) < 0.5 - THEN 1 - ELSE 0 - END AS parallelism_skew - FROM ( - SELECT deqp.session_id, - deqp.request_id, - deqp.node_id, - deqp.thread_id, - deqp.row_count, - SUM(deqp.row_count) - OVER ( PARTITION BY deqp.session_id, - deqp.request_id, - deqp.node_id - ORDER BY deqp.row_count - ROWS BETWEEN UNBOUNDED PRECEDING - AND UNBOUNDED FOLLOWING ) - AS sum_node_rows, - COUNT(*) - OVER ( PARTITION BY deqp.session_id, - deqp.request_id, - deqp.node_id - ORDER BY deqp.row_count - ROWS BETWEEN UNBOUNDED PRECEDING - AND UNBOUNDED FOLLOWING ) - AS node_dop - FROM sys.dm_exec_query_profiles AS deqp - WHERE deqp.thread_id > 0 - AND deqp.session_id <> @@SPID - AND EXISTS - ( - SELECT 1/0 - FROM sys.dm_exec_query_profiles AS deqp2 - WHERE deqp.session_id = deqp2.session_id - AND deqp.node_id = deqp2.node_id - AND deqp2.thread_id > 0 - GROUP BY deqp2.session_id, deqp2.node_id - HAVING COUNT(deqp2.node_id) > 1 - ) - ) AS x - ) AS y - WHERE y.parallelism_skew = 1 - GROUP BY y.session_id, - y.request_id, - y.parallelism_skew; - - /* Queries in dm_exec_query_profiles showing signs of poor cardinality estimates - CheckID 42 */ - IF (@Debug = 1) - BEGIN - RAISERROR(''Running CheckID 42'',10,1) WITH NOWAIT; - END - - INSERT INTO #BlitzFirstResults - (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, OpenTransactionCount, QueryHash, QueryPlan) - SELECT 42 AS CheckID, - 100 AS Priority, - ''Query Performance'' AS FindingsGroup, - ''Queries with 10000x cardinality misestimations'' AS Findings, - ''https://brentozar.com/go/skewedup'' AS URL, - ''The query on SPID '' - + RTRIM(b.session_id) - + '' has been running for '' - + RTRIM(r.total_elapsed_time / 1000) - + '' seconds, with a large cardinality misestimate'' AS Details, - ''No quick fix here: time to dig into the actual execution plan. '' AS HowToStopIt, - r.start_time, - s.login_name, - s.nt_user_name, - s.program_name, - s.host_name, - r.database_id, - DB_NAME(r.database_id), - dest.text, - s.open_transaction_count, - r.query_hash, '; - - IF @dm_exec_query_statistics_xml = 1 - SET @StringToExecute = @StringToExecute + N' COALESCE(qs_live.query_plan, qp.query_plan) AS query_plan '; - ELSE - SET @StringToExecute = @StringToExecute + N' qp.query_plan '; - - SET @StringToExecute = @StringToExecute + N' - FROM @bad_estimate AS b - JOIN sys.dm_exec_requests AS r - ON r.session_id = b.session_id - AND r.request_id = b.request_id - JOIN sys.dm_exec_sessions AS s - ON s.session_id = b.session_id - CROSS APPLY sys.dm_exec_sql_text(r.sql_handle) AS dest - CROSS APPLY sys.dm_exec_query_plan(r.plan_handle) AS qp '; - - IF EXISTS (SELECT * FROM sys.all_objects WHERE name = 'dm_exec_query_statistics_xml') - SET @StringToExecute = @StringToExecute + N' OUTER APPLY sys.dm_exec_query_statistics_xml(s.session_id) qs_live '; - - - SET @StringToExecute = @StringToExecute + N'; - - /* Queries in dm_exec_query_profiles showing signs of unbalanced parallelism - CheckID 43 */ - IF (@Debug = 1) - BEGIN - RAISERROR(''Running CheckID 43'',10,1) WITH NOWAIT; - END - - INSERT INTO #BlitzFirstResults - (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, OpenTransactionCount, QueryHash, QueryPlan) - SELECT 43 AS CheckID, - 100 AS Priority, - ''Query Performance'' AS FindingsGroup, - ''Queries with 10000x skewed parallelism'' AS Findings, - ''https://brentozar.com/go/skewedup'' AS URL, - ''The query on SPID '' - + RTRIM(p.session_id) - + '' has been running for '' - + RTRIM(r.total_elapsed_time / 1000) - + '' seconds, with a parallel threads doing uneven work.'' AS Details, - ''No quick fix here: time to dig into the actual execution plan. '' AS HowToStopIt, - r.start_time, - s.login_name, - s.nt_user_name, - s.program_name, - s.host_name, - r.database_id, - DB_NAME(r.database_id), - dest.text, - s.open_transaction_count, - r.query_hash, '; - - IF @dm_exec_query_statistics_xml = 1 - SET @StringToExecute = @StringToExecute + N' COALESCE(qs_live.query_plan, qp.query_plan) AS query_plan '; - ELSE - SET @StringToExecute = @StringToExecute + N' qp.query_plan '; - - SET @StringToExecute = @StringToExecute + N' - FROM @parallelism_skew AS p - JOIN sys.dm_exec_requests AS r - ON r.session_id = p.session_id - AND r.request_id = p.request_id - JOIN sys.dm_exec_sessions AS s - ON s.session_id = p.session_id - CROSS APPLY sys.dm_exec_sql_text(r.sql_handle) AS dest - CROSS APPLY sys.dm_exec_query_plan(r.plan_handle) AS qp '; - - IF EXISTS (SELECT * FROM sys.all_objects WHERE name = 'dm_exec_query_statistics_xml') - SET @StringToExecute = @StringToExecute + N' OUTER APPLY sys.dm_exec_query_statistics_xml(s.session_id) qs_live '; - - - SET @StringToExecute = @StringToExecute + N';'; - - EXECUTE sp_executesql @StringToExecute, N'@Debug BIT',@Debug = @Debug; - END - - END - END - - /* Server Performance - High CPU Utilization - CheckID 24 */ - IF @Seconds < 30 - BEGIN - /* If we're waiting less than 30 seconds, run this check now rather than wait til the end. - We get this data from the ring buffers, and it's only updated once per minute, so might - as well get it now - whereas if we're checking 30+ seconds, it might get updated by the - end of our sp_BlitzFirst session. */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 24',10,1) WITH NOWAIT; - END - - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) - SELECT 24, 50, 'Server Performance', 'High CPU Utilization', CAST(100 - SystemIdle AS NVARCHAR(20)) + N'%.', 100 - SystemIdle, 'http://www.BrentOzar.com/go/cpu' - FROM ( - SELECT record, - record.value('(./Record/SchedulerMonitorEvent/SystemHealth/SystemIdle)[1]', 'int') AS SystemIdle - FROM ( - SELECT TOP 1 CONVERT(XML, record) AS record - FROM sys.dm_os_ring_buffers - WHERE ring_buffer_type = N'RING_BUFFER_SCHEDULER_MONITOR' - AND record LIKE '%%' - ORDER BY timestamp DESC) AS rb - ) AS y - WHERE 100 - SystemIdle >= 50; - - /* CPU Utilization - CheckID 23 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 23',10,1) WITH NOWAIT; - END - - IF SERVERPROPERTY('Edition') <> 'SQL Azure' - WITH y - AS - ( - SELECT CONVERT(VARCHAR(5), 100 - ca.c.value('.', 'INT')) AS system_idle, - CONVERT(VARCHAR(30), rb.event_date) AS event_date, - CONVERT(VARCHAR(8000), rb.record) AS record - FROM - ( SELECT CONVERT(XML, dorb.record) AS record, - DATEADD(ms, ( ts.ms_ticks - dorb.timestamp ), GETDATE()) AS event_date - FROM sys.dm_os_ring_buffers AS dorb - CROSS JOIN - ( SELECT dosi.ms_ticks FROM sys.dm_os_sys_info AS dosi ) AS ts - WHERE dorb.ring_buffer_type = N'RING_BUFFER_SCHEDULER_MONITOR' - AND record LIKE '%%' ) AS rb - CROSS APPLY rb.record.nodes('/Record/SchedulerMonitorEvent/SystemHealth/SystemIdle') AS ca(c) - ) - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL, HowToStopIt) - SELECT TOP 1 - 23, - 250, - 'Server Info', - 'CPU Utilization', - y.system_idle + N'%. Ring buffer details: ' + CAST(y.record AS NVARCHAR(4000)), - y.system_idle , - 'http://www.BrentOzar.com/go/cpu', - STUFF(( SELECT TOP 2147483647 - CHAR(10) + CHAR(13) - + y2.system_idle - + '% ON ' - + y2.event_date - + ' Ring buffer details: ' - + y2.record - FROM y AS y2 - ORDER BY y2.event_date DESC - FOR XML PATH(N''), TYPE ).value(N'.[1]', N'VARCHAR(MAX)'), 1, 1, N'') AS query - FROM y - ORDER BY y.event_date DESC; - - - /* Highlight if non SQL processes are using >25% CPU - CheckID 28 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 28',10,1) WITH NOWAIT; - END - - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) - SELECT 28, 50, 'Server Performance', 'High CPU Utilization - Not SQL', CONVERT(NVARCHAR(100),100 - (y.SQLUsage + y.SystemIdle)) + N'% - Other Processes (not SQL Server) are using this much CPU. This may impact on the performance of your SQL Server instance', 100 - (y.SQLUsage + y.SystemIdle), 'http://www.BrentOzar.com/go/cpu' - FROM ( - SELECT record, - record.value('(./Record/SchedulerMonitorEvent/SystemHealth/SystemIdle)[1]', 'int') AS SystemIdle - ,record.value('(./Record/SchedulerMonitorEvent/SystemHealth/ProcessUtilization)[1]', 'int') AS SQLUsage - FROM ( - SELECT TOP 1 CONVERT(XML, record) AS record - FROM sys.dm_os_ring_buffers - WHERE ring_buffer_type = N'RING_BUFFER_SCHEDULER_MONITOR' - AND record LIKE '%%' - ORDER BY timestamp DESC) AS rb - ) AS y - WHERE 100 - (y.SQLUsage + y.SystemIdle) >= 25; - - END; /* IF @Seconds < 30 */ - - /* Query Problems - Statistics Updated Recently - CheckID 44 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 44',10,1) WITH NOWAIT; - END - - IF 20 >= (SELECT COUNT(*) FROM sys.databases WHERE name NOT IN ('master', 'model', 'msdb', 'tempdb')) - BEGIN - CREATE TABLE #UpdatedStats (HowToStopIt NVARCHAR(4000), RowsForSorting BIGINT); - IF EXISTS(SELECT * FROM sys.all_objects WHERE name = 'dm_db_stats_properties') - BEGIN - /* We don't want to hang around to obtain locks */ - SET LOCK_TIMEOUT 0; + RAISERROR (N'Create temp tables.',0,1) WITH NOWAIT; + CREATE TABLE #BlitzIndexResults + ( + blitz_result_id INT IDENTITY PRIMARY KEY, + check_id INT NOT NULL, + index_sanity_id INT NULL, + Priority INT NULL, + findings_group NVARCHAR(4000) NOT NULL, + finding NVARCHAR(200) NOT NULL, + [database_name] NVARCHAR(128) NULL, + URL NVARCHAR(200) NOT NULL, + details NVARCHAR(MAX) NOT NULL, + index_definition NVARCHAR(MAX) NOT NULL, + secret_columns NVARCHAR(MAX) NULL, + index_usage_summary NVARCHAR(MAX) NULL, + index_size_summary NVARCHAR(MAX) NULL, + create_tsql NVARCHAR(MAX) NULL, + more_info NVARCHAR(MAX)NULL + ); - EXEC sp_MSforeachdb N'USE [?]; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; SET LOCK_TIMEOUT 1000; - BEGIN TRY - INSERT INTO #UpdatedStats(HowToStopIt, RowsForSorting) - SELECT HowToStopIt = - QUOTENAME(DB_NAME()) + N''.'' + - QUOTENAME(SCHEMA_NAME(obj.schema_id)) + N''.'' + - QUOTENAME(obj.name) + - N'' statistic '' + QUOTENAME(stat.name) + - N'' was updated on '' + CONVERT(NVARCHAR(50), sp.last_updated, 121) + N'','' + - N'' had '' + CAST(sp.rows AS NVARCHAR(50)) + N'' rows, with '' + - CAST(sp.rows_sampled AS NVARCHAR(50)) + N'' rows sampled,'' + - N'' producing '' + CAST(sp.steps AS NVARCHAR(50)) + N'' steps in the histogram.'', - sp.rows - FROM sys.objects AS obj WITH (NOLOCK) - INNER JOIN sys.stats AS stat WITH (NOLOCK) ON stat.object_id = obj.object_id - CROSS APPLY sys.dm_db_stats_properties(stat.object_id, stat.stats_id) AS sp - WHERE sp.last_updated > DATEADD(MI, -15, GETDATE()) - AND obj.is_ms_shipped = 0 - AND ''[?]'' <> ''[tempdb]''; - END TRY - BEGIN CATCH - IF (ERROR_NUMBER() = 1222) - BEGIN - INSERT INTO #UpdatedStats(HowToStopIt, RowsForSorting) - SELECT HowToStopIt = - QUOTENAME(DB_NAME()) + - N'' No information could be retrieved as the lock timeout was exceeded,''+ - N'' this is likely due to an Index operation in Progress'', - -1 - END - ELSE - BEGIN - INSERT INTO #UpdatedStats(HowToStopIt, RowsForSorting) - SELECT HowToStopIt = - QUOTENAME(DB_NAME()) + - N'' No information could be retrieved as a result of error: ''+ - CAST(ERROR_NUMBER() AS NVARCHAR(10)) + - N'' with message: ''+ - CAST(ERROR_MESSAGE() AS NVARCHAR(128)), - -1 + CREATE TABLE #IndexSanity + ( + [index_sanity_id] INT IDENTITY PRIMARY KEY CLUSTERED, + [database_id] SMALLINT NOT NULL , + [object_id] INT NOT NULL , + [index_id] INT NOT NULL , + [index_type] TINYINT NOT NULL, + [database_name] NVARCHAR(128) NOT NULL , + [schema_name] NVARCHAR(128) NOT NULL , + [object_name] NVARCHAR(128) NOT NULL , + index_name NVARCHAR(128) NULL , + key_column_names NVARCHAR(MAX) NULL , + key_column_names_with_sort_order NVARCHAR(MAX) NULL , + key_column_names_with_sort_order_no_types NVARCHAR(MAX) NULL , + count_key_columns INT NULL , + include_column_names NVARCHAR(MAX) NULL , + include_column_names_no_types NVARCHAR(MAX) NULL , + count_included_columns INT NULL , + partition_key_column_name NVARCHAR(MAX) NULL, + filter_definition NVARCHAR(MAX) NOT NULL , + is_indexed_view BIT NOT NULL , + is_unique BIT NOT NULL , + is_primary_key BIT NOT NULL , + is_XML BIT NOT NULL, + is_spatial BIT NOT NULL, + is_NC_columnstore BIT NOT NULL, + is_CX_columnstore BIT NOT NULL, + is_in_memory_oltp BIT NOT NULL , + is_disabled BIT NOT NULL , + is_hypothetical BIT NOT NULL , + is_padded BIT NOT NULL , + fill_factor SMALLINT NOT NULL , + user_seeks BIGINT NOT NULL , + user_scans BIGINT NOT NULL , + user_lookups BIGINT NOT NULL , + user_updates BIGINT NULL , + last_user_seek DATETIME NULL , + last_user_scan DATETIME NULL , + last_user_lookup DATETIME NULL , + last_user_update DATETIME NULL , + is_referenced_by_foreign_key BIT DEFAULT(0), + secret_columns NVARCHAR(MAX) NULL, + count_secret_columns INT NULL, + create_date DATETIME NOT NULL, + modify_date DATETIME NOT NULL, + filter_columns_not_in_index NVARCHAR(MAX), + [db_schema_object_name] AS [schema_name] + N'.' + [object_name] , + [db_schema_object_indexid] AS [schema_name] + N'.' + [object_name] + + CASE WHEN [index_name] IS NOT NULL THEN N'.' + index_name + ELSE N'' + END + N' (' + CAST(index_id AS NVARCHAR(20)) + N')' , + first_key_column_name AS CASE WHEN count_key_columns > 1 + THEN LEFT(key_column_names, CHARINDEX(',', key_column_names, 0) - 1) + ELSE key_column_names + END , + index_definition AS + CASE WHEN partition_key_column_name IS NOT NULL + THEN N'[PARTITIONED BY:' + partition_key_column_name + N']' + ELSE '' + END + + CASE index_id + WHEN 0 THEN N'[HEAP] ' + WHEN 1 THEN N'[CX] ' + ELSE N'' END + CASE WHEN is_indexed_view = 1 THEN N'[VIEW] ' + ELSE N'' END + CASE WHEN is_primary_key = 1 THEN N'[PK] ' + ELSE N'' END + CASE WHEN is_XML = 1 THEN N'[XML] ' + ELSE N'' END + CASE WHEN is_spatial = 1 THEN N'[SPATIAL] ' + ELSE N'' END + CASE WHEN is_NC_columnstore = 1 THEN N'[COLUMNSTORE] ' + ELSE N'' END + CASE WHEN is_in_memory_oltp = 1 THEN N'[IN-MEMORY] ' + ELSE N'' END + CASE WHEN is_disabled = 1 THEN N'[DISABLED] ' + ELSE N'' END + CASE WHEN is_hypothetical = 1 THEN N'[HYPOTHETICAL] ' + ELSE N'' END + CASE WHEN is_unique = 1 AND is_primary_key = 0 THEN N'[UNIQUE] ' + ELSE N'' END + CASE WHEN count_key_columns > 0 THEN + N'[' + CAST(count_key_columns AS NVARCHAR(10)) + N' KEY' + + CASE WHEN count_key_columns > 1 THEN N'S' ELSE N'' END + + N'] ' + LTRIM(key_column_names_with_sort_order) + ELSE N'' END + CASE WHEN count_included_columns > 0 THEN + N' [' + CAST(count_included_columns AS NVARCHAR(10)) + N' INCLUDE' + + + CASE WHEN count_included_columns > 1 THEN N'S' ELSE N'' END + + N'] ' + include_column_names + ELSE N'' END + CASE WHEN filter_definition <> N'' THEN N' [FILTER] ' + filter_definition + ELSE N'' END , + [total_reads] AS user_seeks + user_scans + user_lookups, + [reads_per_write] AS CAST(CASE WHEN user_updates > 0 + THEN ( user_seeks + user_scans + user_lookups ) / (1.0 * user_updates) + ELSE 0 END AS MONEY) , + [index_usage_summary] AS + CASE WHEN is_spatial = 1 THEN N'Not Tracked' + WHEN is_disabled = 1 THEN N'Disabled' + ELSE N'Reads: ' + + REPLACE(CONVERT(NVARCHAR(30),CAST((user_seeks + user_scans + user_lookups) AS MONEY), 1), N'.00', N'') + + CASE WHEN user_seeks + user_scans + user_lookups > 0 THEN + N' (' + + RTRIM( + CASE WHEN user_seeks > 0 THEN REPLACE(CONVERT(NVARCHAR(30),CAST((user_seeks) AS MONEY), 1), N'.00', N'') + N' seek ' ELSE N'' END + + CASE WHEN user_scans > 0 THEN REPLACE(CONVERT(NVARCHAR(30),CAST((user_scans) AS MONEY), 1), N'.00', N'') + N' scan ' ELSE N'' END + + CASE WHEN user_lookups > 0 THEN REPLACE(CONVERT(NVARCHAR(30),CAST((user_lookups) AS MONEY), 1), N'.00', N'') + N' lookup' ELSE N'' END + ) + + N') ' + ELSE N' ' + END + + N'Writes: ' + + REPLACE(CONVERT(NVARCHAR(30),CAST(user_updates AS MONEY), 1), N'.00', N'') + END /* First "end" is about is_spatial */, + [more_info] AS + CASE WHEN is_in_memory_oltp = 1 + THEN N'EXEC dbo.sp_BlitzInMemoryOLTP @dbName=' + QUOTENAME([database_name],N'''') + + N', @tableName=' + QUOTENAME([object_name],N'''') + N';' + ELSE N'EXEC dbo.sp_BlitzIndex @DatabaseName=' + QUOTENAME([database_name],N'''') + + N', @SchemaName=' + QUOTENAME([schema_name],N'''') + N', @TableName=' + QUOTENAME([object_name],N'''') + N';' END - END CATCH'; - - /* Set timeout back to a default value of -1 */ - SET LOCK_TIMEOUT -1; - END; - - /* We mark timeout exceeded with a -1 so only show these IF there is statistics info that succeeded */ - IF EXISTS (SELECT * FROM #UpdatedStats WHERE RowsForSorting > -1) - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) - SELECT 44 AS CheckId, - 50 AS Priority, - 'Query Problems' AS FindingGroup, - 'Statistics Updated Recently' AS Finding, - 'http://www.BrentOzar.com/go/stats' AS URL, - 'In the last 15 minutes, statistics were updated. To see which ones, click the HowToStopIt column.' + @LineFeed + @LineFeed - + 'This effectively clears the plan cache for queries that involve these tables,' + @LineFeed - + 'which thereby causes parameter sniffing: those queries are now getting brand new' + @LineFeed - + 'query plans based on whatever parameters happen to call them next.' + @LineFeed + @LineFeed - + 'Be on the lookout for sudden parameter sniffing issues after this time range.', - HowToStopIt = (SELECT (SELECT HowToStopIt + NCHAR(10)) - FROM #UpdatedStats - ORDER BY RowsForSorting DESC - FOR XML PATH('')); - - END - - RAISERROR('Finished running investigatory queries',10,1) WITH NOWAIT; - - - /* End of checks. If we haven't waited @Seconds seconds, wait. */ - IF DATEADD(SECOND,1,SYSDATETIMEOFFSET()) < @FinishSampleTime - BEGIN - RAISERROR('Waiting to match @Seconds parameter',10,1) WITH NOWAIT; - WAITFOR TIME @FinishSampleTimeWaitFor; - END; - - RAISERROR('Capturing second pass of wait stats, perfmon counters, file stats',10,1) WITH NOWAIT; - /* Populate #FileStats, #PerfmonStats, #WaitStats with DMV data. In a second, we'll compare these. */ - INSERT #WaitStats(Pass, SampleTime, wait_type, wait_time_ms, signal_wait_time_ms, waiting_tasks_count) - SELECT - x.Pass, - x.SampleTime, - x.wait_type, - SUM(x.sum_wait_time_ms) AS sum_wait_time_ms, - SUM(x.sum_signal_wait_time_ms) AS sum_signal_wait_time_ms, - SUM(x.sum_waiting_tasks) AS sum_waiting_tasks - FROM ( - SELECT - 2 AS Pass, - SYSDATETIMEOFFSET() AS SampleTime, - owt.wait_type, - SUM(owt.wait_duration_ms) OVER (PARTITION BY owt.wait_type, owt.session_id) - - CASE WHEN @Seconds = 0 THEN 0 ELSE (@Seconds * 1000) END AS sum_wait_time_ms, - 0 AS sum_signal_wait_time_ms, - CASE @Seconds WHEN 0 THEN 0 ELSE 1 END AS sum_waiting_tasks - FROM sys.dm_os_waiting_tasks owt - WHERE owt.session_id > 50 - AND owt.wait_duration_ms >= CASE @Seconds WHEN 0 THEN 0 ELSE @Seconds * 1000 END - UNION ALL - SELECT - 2 AS Pass, - SYSDATETIMEOFFSET() AS SampleTime, - os.wait_type, - SUM(os.wait_time_ms) OVER (PARTITION BY os.wait_type) AS sum_wait_time_ms, - SUM(os.signal_wait_time_ms) OVER (PARTITION BY os.wait_type ) AS sum_signal_wait_time_ms, - SUM(os.waiting_tasks_count) OVER (PARTITION BY os.wait_type) AS sum_waiting_tasks - FROM sys.dm_os_wait_stats os - ) x - WHERE NOT EXISTS - ( - SELECT * - FROM ##WaitCategories AS wc - WHERE wc.WaitType = x.wait_type - AND wc.Ignorable = 1 - ) - GROUP BY x.Pass, x.SampleTime, x.wait_type - ORDER BY sum_wait_time_ms DESC; - - INSERT INTO #FileStats (Pass, SampleTime, DatabaseID, FileID, DatabaseName, FileLogicalName, SizeOnDiskMB, io_stall_read_ms , - num_of_reads, [bytes_read] , io_stall_write_ms,num_of_writes, [bytes_written], PhysicalName, TypeDesc, avg_stall_read_ms, avg_stall_write_ms) - SELECT 2 AS Pass, - SYSDATETIMEOFFSET() AS SampleTime, - mf.[database_id], - mf.[file_id], - DB_NAME(vfs.database_id) AS [db_name], - mf.name + N' [' + mf.type_desc COLLATE SQL_Latin1_General_CP1_CI_AS + N']' AS file_logical_name , - CAST(( ( vfs.size_on_disk_bytes / 1024.0 ) / 1024.0 ) AS INT) AS size_on_disk_mb , - vfs.io_stall_read_ms , - vfs.num_of_reads , - vfs.[num_of_bytes_read], - vfs.io_stall_write_ms , - vfs.num_of_writes , - vfs.[num_of_bytes_written], - mf.physical_name, - mf.type_desc, - 0, - 0 - FROM sys.dm_io_virtual_file_stats (NULL, NULL) AS vfs - INNER JOIN #MasterFiles AS mf ON vfs.file_id = mf.file_id - AND vfs.database_id = mf.database_id - WHERE vfs.num_of_reads > 0 - OR vfs.num_of_writes > 0; - - INSERT INTO #PerfmonStats (Pass, SampleTime, [object_name],[counter_name],[instance_name],[cntr_value],[cntr_type]) - SELECT 2 AS Pass, - SYSDATETIMEOFFSET() AS SampleTime, - RTRIM(dmv.object_name), RTRIM(dmv.counter_name), RTRIM(dmv.instance_name), dmv.cntr_value, dmv.cntr_type - FROM #PerfmonCounters counters - INNER JOIN sys.dm_os_performance_counters dmv ON counters.counter_name COLLATE SQL_Latin1_General_CP1_CI_AS = RTRIM(dmv.counter_name) COLLATE SQL_Latin1_General_CP1_CI_AS - AND counters.[object_name] COLLATE SQL_Latin1_General_CP1_CI_AS = RTRIM(dmv.[object_name]) COLLATE SQL_Latin1_General_CP1_CI_AS - AND (counters.[instance_name] IS NULL OR counters.[instance_name] COLLATE SQL_Latin1_General_CP1_CI_AS = RTRIM(dmv.[instance_name]) COLLATE SQL_Latin1_General_CP1_CI_AS); - - /* Set the latencies and averages. We could do this with a CTE, but we're not ambitious today. */ - UPDATE fNow - SET avg_stall_read_ms = ((fNow.io_stall_read_ms - fBase.io_stall_read_ms) / (fNow.num_of_reads - fBase.num_of_reads)) - FROM #FileStats fNow - INNER JOIN #FileStats fBase ON fNow.DatabaseID = fBase.DatabaseID AND fNow.FileID = fBase.FileID AND fNow.SampleTime > fBase.SampleTime AND fNow.num_of_reads > fBase.num_of_reads AND fNow.io_stall_read_ms > fBase.io_stall_read_ms - WHERE (fNow.num_of_reads - fBase.num_of_reads) > 0; - - UPDATE fNow - SET avg_stall_write_ms = ((fNow.io_stall_write_ms - fBase.io_stall_write_ms) / (fNow.num_of_writes - fBase.num_of_writes)) - FROM #FileStats fNow - INNER JOIN #FileStats fBase ON fNow.DatabaseID = fBase.DatabaseID AND fNow.FileID = fBase.FileID AND fNow.SampleTime > fBase.SampleTime AND fNow.num_of_writes > fBase.num_of_writes AND fNow.io_stall_write_ms > fBase.io_stall_write_ms - WHERE (fNow.num_of_writes - fBase.num_of_writes) > 0; - - UPDATE pNow - SET [value_delta] = pNow.cntr_value - pFirst.cntr_value, - [value_per_second] = ((1.0 * pNow.cntr_value - pFirst.cntr_value) / DATEDIFF(ss, pFirst.SampleTime, pNow.SampleTime)) - FROM #PerfmonStats pNow - INNER JOIN #PerfmonStats pFirst ON pFirst.[object_name] = pNow.[object_name] AND pFirst.counter_name = pNow.counter_name AND (pFirst.instance_name = pNow.instance_name OR (pFirst.instance_name IS NULL AND pNow.instance_name IS NULL)) - AND pNow.ID > pFirst.ID - WHERE DATEDIFF(ss, pFirst.SampleTime, pNow.SampleTime) > 0; - - - /* Query Stats - If we're within 10 seconds of our projected finish time, do the plan cache analysis. - CheckID 18 */ - IF DATEDIFF(ss, @FinishSampleTime, SYSDATETIMEOFFSET()) > 10 AND @CheckProcedureCache = 1 - BEGIN - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 18',10,1) WITH NOWAIT; - END - - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (18, 210, 'Query Stats', 'Plan Cache Analysis Skipped', 'http://www.BrentOzar.com/go/topqueries', - 'Due to excessive load, the plan cache analysis was skipped. To override this, use @ExpertMode = 1.'); - - END; - ELSE IF @CheckProcedureCache = 1 - BEGIN - - - RAISERROR('@CheckProcedureCache = 1, capturing second pass of plan cache',10,1) WITH NOWAIT; - - /* Populate #QueryStats. SQL 2005 doesn't have query hash or query plan hash. */ - IF @@VERSION LIKE 'Microsoft SQL Server 2005%' - BEGIN - IF @FilterPlansByDatabase IS NULL - BEGIN - SET @StringToExecute = N'INSERT INTO #QueryStats ([sql_handle], Pass, SampleTime, statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, Points) - SELECT [sql_handle], 2 AS Pass, SYSDATETIMEOFFSET(), statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, NULL AS query_hash, NULL AS query_plan_hash, 0 - FROM sys.dm_exec_query_stats qs - WHERE qs.last_execution_time >= @StartSampleTimeText;'; - END; - ELSE - BEGIN - SET @StringToExecute = N'INSERT INTO #QueryStats ([sql_handle], Pass, SampleTime, statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, Points) - SELECT [sql_handle], 2 AS Pass, SYSDATETIMEOFFSET(), statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, NULL AS query_hash, NULL AS query_plan_hash, 0 - FROM sys.dm_exec_query_stats qs - CROSS APPLY sys.dm_exec_plan_attributes(qs.plan_handle) AS attr - INNER JOIN #FilterPlansByDatabase dbs ON CAST(attr.value AS INT) = dbs.DatabaseID - WHERE qs.last_execution_time >= @StartSampleTimeText - AND attr.attribute = ''dbid'';'; - END; - END; - ELSE - BEGIN - IF @FilterPlansByDatabase IS NULL - BEGIN - SET @StringToExecute = N'INSERT INTO #QueryStats ([sql_handle], Pass, SampleTime, statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, Points) - SELECT [sql_handle], 2 AS Pass, SYSDATETIMEOFFSET(), statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, 0 - FROM sys.dm_exec_query_stats qs - WHERE qs.last_execution_time >= @StartSampleTimeText'; - END; - ELSE - BEGIN - SET @StringToExecute = N'INSERT INTO #QueryStats ([sql_handle], Pass, SampleTime, statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, Points) - SELECT [sql_handle], 2 AS Pass, SYSDATETIMEOFFSET(), statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, 0 - FROM sys.dm_exec_query_stats qs - CROSS APPLY sys.dm_exec_plan_attributes(qs.plan_handle) AS attr - INNER JOIN #FilterPlansByDatabase dbs ON CAST(attr.value AS INT) = dbs.DatabaseID - WHERE qs.last_execution_time >= @StartSampleTimeText - AND attr.attribute = ''dbid'';'; - END; - END; - /* Old version pre-2016/06/13: - IF @@VERSION LIKE 'Microsoft SQL Server 2005%' - SET @StringToExecute = N'INSERT INTO #QueryStats ([sql_handle], Pass, SampleTime, statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, Points) - SELECT [sql_handle], 2 AS Pass, SYSDATETIMEOFFSET(), statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, NULL AS query_hash, NULL AS query_plan_hash, 0 - FROM sys.dm_exec_query_stats qs - WHERE qs.last_execution_time >= @StartSampleTimeText;'; - ELSE - SET @StringToExecute = N'INSERT INTO #QueryStats ([sql_handle], Pass, SampleTime, statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, Points) - SELECT [sql_handle], 2 AS Pass, SYSDATETIMEOFFSET(), statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, 0 - FROM sys.dm_exec_query_stats qs - WHERE qs.last_execution_time >= @StartSampleTimeText;'; - */ - SET @ParmDefinitions = N'@StartSampleTimeText NVARCHAR(100)'; - SET @Parm1 = CONVERT(NVARCHAR(100), CAST(@StartSampleTime AS DATETIME), 127); - - EXECUTE sp_executesql @StringToExecute, @ParmDefinitions, @StartSampleTimeText = @Parm1; - - RAISERROR('@CheckProcedureCache = 1, totaling up plan cache metrics',10,1) WITH NOWAIT; - - /* Get the totals for the entire plan cache */ - INSERT INTO #QueryStats (Pass, SampleTime, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time) - SELECT 0 AS Pass, SYSDATETIMEOFFSET(), SUM(execution_count), SUM(total_worker_time), SUM(total_physical_reads), SUM(total_logical_writes), SUM(total_logical_reads), SUM(total_clr_time), SUM(total_elapsed_time), MIN(creation_time) - FROM sys.dm_exec_query_stats qs; - + ); + RAISERROR (N'Adding UQ index on #IndexSanity (database_id, object_id, index_id)',0,1) WITH NOWAIT; + IF NOT EXISTS(SELECT 1 FROM tempdb.sys.indexes WHERE name='uq_database_id_object_id_index_id') + CREATE UNIQUE INDEX uq_database_id_object_id_index_id ON #IndexSanity (database_id, object_id, index_id); - RAISERROR('@CheckProcedureCache = 1, so analyzing execution plans',10,1) WITH NOWAIT; - /* - Pick the most resource-intensive queries to review. Update the Points field - in #QueryStats - if a query is in the top 10 for logical reads, CPU time, - duration, or execution, add 1 to its points. - */ - WITH qsTop AS ( - SELECT TOP 10 qsNow.ID - FROM #QueryStats qsNow - INNER JOIN #QueryStats qsFirst ON qsNow.[sql_handle] = qsFirst.[sql_handle] AND qsNow.statement_start_offset = qsFirst.statement_start_offset AND qsNow.statement_end_offset = qsFirst.statement_end_offset AND qsNow.plan_generation_num = qsFirst.plan_generation_num AND qsNow.plan_handle = qsFirst.plan_handle AND qsFirst.Pass = 1 - WHERE qsNow.total_elapsed_time > qsFirst.total_elapsed_time - AND qsNow.Pass = 2 - AND qsNow.total_elapsed_time - qsFirst.total_elapsed_time > 1000000 /* Only queries with over 1 second of runtime */ - ORDER BY (qsNow.total_elapsed_time - COALESCE(qsFirst.total_elapsed_time, 0)) DESC) - UPDATE #QueryStats - SET Points = Points + 1 - FROM #QueryStats qs - INNER JOIN qsTop ON qs.ID = qsTop.ID; - WITH qsTop AS ( - SELECT TOP 10 qsNow.ID - FROM #QueryStats qsNow - INNER JOIN #QueryStats qsFirst ON qsNow.[sql_handle] = qsFirst.[sql_handle] AND qsNow.statement_start_offset = qsFirst.statement_start_offset AND qsNow.statement_end_offset = qsFirst.statement_end_offset AND qsNow.plan_generation_num = qsFirst.plan_generation_num AND qsNow.plan_handle = qsFirst.plan_handle AND qsFirst.Pass = 1 - WHERE qsNow.total_logical_reads > qsFirst.total_logical_reads - AND qsNow.Pass = 2 - AND qsNow.total_logical_reads - qsFirst.total_logical_reads > 1000 /* Only queries with over 1000 reads */ - ORDER BY (qsNow.total_logical_reads - COALESCE(qsFirst.total_logical_reads, 0)) DESC) - UPDATE #QueryStats - SET Points = Points + 1 - FROM #QueryStats qs - INNER JOIN qsTop ON qs.ID = qsTop.ID; + CREATE TABLE #IndexPartitionSanity + ( + [index_partition_sanity_id] INT IDENTITY, + [index_sanity_id] INT NULL , + [database_id] INT NOT NULL , + [object_id] INT NOT NULL , + [schema_name] NVARCHAR(128) NOT NULL, + [index_id] INT NOT NULL , + [partition_number] INT NOT NULL , + row_count BIGINT NOT NULL , + reserved_MB NUMERIC(29,2) NOT NULL , + reserved_LOB_MB NUMERIC(29,2) NOT NULL , + reserved_row_overflow_MB NUMERIC(29,2) NOT NULL , + reserved_dictionary_MB NUMERIC(29,2) NOT NULL , + leaf_insert_count BIGINT NULL , + leaf_delete_count BIGINT NULL , + leaf_update_count BIGINT NULL , + range_scan_count BIGINT NULL , + singleton_lookup_count BIGINT NULL , + forwarded_fetch_count BIGINT NULL , + lob_fetch_in_pages BIGINT NULL , + lob_fetch_in_bytes BIGINT NULL , + row_overflow_fetch_in_pages BIGINT NULL , + row_overflow_fetch_in_bytes BIGINT NULL , + row_lock_count BIGINT NULL , + row_lock_wait_count BIGINT NULL , + row_lock_wait_in_ms BIGINT NULL , + page_lock_count BIGINT NULL , + page_lock_wait_count BIGINT NULL , + page_lock_wait_in_ms BIGINT NULL , + index_lock_promotion_attempt_count BIGINT NULL , + index_lock_promotion_count BIGINT NULL, + data_compression_desc NVARCHAR(60) NULL, + page_latch_wait_count BIGINT NULL, + page_latch_wait_in_ms BIGINT NULL, + page_io_latch_wait_count BIGINT NULL, + page_io_latch_wait_in_ms BIGINT NULL, + lock_escalation_desc nvarchar(60) NULL + ); - WITH qsTop AS ( - SELECT TOP 10 qsNow.ID - FROM #QueryStats qsNow - INNER JOIN #QueryStats qsFirst ON qsNow.[sql_handle] = qsFirst.[sql_handle] AND qsNow.statement_start_offset = qsFirst.statement_start_offset AND qsNow.statement_end_offset = qsFirst.statement_end_offset AND qsNow.plan_generation_num = qsFirst.plan_generation_num AND qsNow.plan_handle = qsFirst.plan_handle AND qsFirst.Pass = 1 - WHERE qsNow.total_worker_time > qsFirst.total_worker_time - AND qsNow.Pass = 2 - AND qsNow.total_worker_time - qsFirst.total_worker_time > 1000000 /* Only queries with over 1 second of worker time */ - ORDER BY (qsNow.total_worker_time - COALESCE(qsFirst.total_worker_time, 0)) DESC) - UPDATE #QueryStats - SET Points = Points + 1 - FROM #QueryStats qs - INNER JOIN qsTop ON qs.ID = qsTop.ID; + CREATE TABLE #IndexSanitySize + ( + [index_sanity_size_id] INT IDENTITY NOT NULL , + [index_sanity_id] INT NULL , + [database_id] INT NOT NULL, + [schema_name] NVARCHAR(128) NOT NULL, + partition_count INT NOT NULL , + total_rows BIGINT NOT NULL , + total_reserved_MB NUMERIC(29,2) NOT NULL , + total_reserved_LOB_MB NUMERIC(29,2) NOT NULL , + total_reserved_row_overflow_MB NUMERIC(29,2) NOT NULL , + total_reserved_dictionary_MB NUMERIC(29,2) NOT NULL , + total_leaf_delete_count BIGINT NULL, + total_leaf_update_count BIGINT NULL, + total_range_scan_count BIGINT NULL, + total_singleton_lookup_count BIGINT NULL, + total_forwarded_fetch_count BIGINT NULL, + total_row_lock_count BIGINT NULL , + total_row_lock_wait_count BIGINT NULL , + total_row_lock_wait_in_ms BIGINT NULL , + avg_row_lock_wait_in_ms BIGINT NULL , + total_page_lock_count BIGINT NULL , + total_page_lock_wait_count BIGINT NULL , + total_page_lock_wait_in_ms BIGINT NULL , + avg_page_lock_wait_in_ms BIGINT NULL , + total_index_lock_promotion_attempt_count BIGINT NULL , + total_index_lock_promotion_count BIGINT NULL , + data_compression_desc NVARCHAR(4000) NULL, + page_latch_wait_count BIGINT NULL, + page_latch_wait_in_ms BIGINT NULL, + page_io_latch_wait_count BIGINT NULL, + page_io_latch_wait_in_ms BIGINT NULL, + lock_escalation_desc nvarchar(60) NULL, + index_size_summary AS ISNULL( + CASE WHEN partition_count > 1 + THEN N'[' + CAST(partition_count AS NVARCHAR(10)) + N' PARTITIONS] ' + ELSE N'' + END + REPLACE(CONVERT(NVARCHAR(30),CAST([total_rows] AS MONEY), 1), N'.00', N'') + N' rows; ' + + CASE WHEN total_reserved_MB > 1024 THEN + CAST(CAST(total_reserved_MB/1024. AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'GB' + ELSE + CAST(CAST(total_reserved_MB AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'MB' + END + + CASE WHEN total_reserved_LOB_MB > 1024 THEN + N'; ' + CAST(CAST(total_reserved_LOB_MB/1024. AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'GB ' + CASE WHEN total_reserved_dictionary_MB = 0 THEN N'LOB' ELSE N'Columnstore' END + WHEN total_reserved_LOB_MB > 0 THEN + N'; ' + CAST(CAST(total_reserved_LOB_MB AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'MB ' + CASE WHEN total_reserved_dictionary_MB = 0 THEN N'LOB' ELSE N'Columnstore' END + ELSE '' + END + + CASE WHEN total_reserved_row_overflow_MB > 1024 THEN + N'; ' + CAST(CAST(total_reserved_row_overflow_MB/1024. AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'GB Row Overflow' + WHEN total_reserved_row_overflow_MB > 0 THEN + N'; ' + CAST(CAST(total_reserved_row_overflow_MB AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'MB Row Overflow' + ELSE '' + END + + CASE WHEN total_reserved_dictionary_MB > 1024 THEN + N'; ' + CAST(CAST(total_reserved_dictionary_MB/1024. AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'GB Dictionaries' + WHEN total_reserved_dictionary_MB > 0 THEN + N'; ' + CAST(CAST(total_reserved_dictionary_MB AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'MB Dictionaries' + ELSE '' + END , + N'Error- NULL in computed column'), + index_op_stats AS ISNULL( + ( + REPLACE(CONVERT(NVARCHAR(30),CAST(total_singleton_lookup_count AS MONEY), 1),N'.00',N'') + N' singleton lookups; ' + + REPLACE(CONVERT(NVARCHAR(30),CAST(total_range_scan_count AS MONEY), 1),N'.00',N'') + N' scans/seeks; ' + + REPLACE(CONVERT(NVARCHAR(30),CAST(total_leaf_delete_count AS MONEY), 1),N'.00',N'') + N' deletes; ' + + REPLACE(CONVERT(NVARCHAR(30),CAST(total_leaf_update_count AS MONEY), 1),N'.00',N'') + N' updates; ' + + CASE WHEN ISNULL(total_forwarded_fetch_count,0) >0 THEN + REPLACE(CONVERT(NVARCHAR(30),CAST(total_forwarded_fetch_count AS MONEY), 1),N'.00',N'') + N' forward records fetched; ' + ELSE N'' END - WITH qsTop AS ( - SELECT TOP 10 qsNow.ID - FROM #QueryStats qsNow - INNER JOIN #QueryStats qsFirst ON qsNow.[sql_handle] = qsFirst.[sql_handle] AND qsNow.statement_start_offset = qsFirst.statement_start_offset AND qsNow.statement_end_offset = qsFirst.statement_end_offset AND qsNow.plan_generation_num = qsFirst.plan_generation_num AND qsNow.plan_handle = qsFirst.plan_handle AND qsFirst.Pass = 1 - WHERE qsNow.execution_count > qsFirst.execution_count - AND qsNow.Pass = 2 - AND (qsNow.total_elapsed_time - qsFirst.total_elapsed_time > 1000000 /* Only queries with over 1 second of runtime */ - OR qsNow.total_logical_reads - qsFirst.total_logical_reads > 1000 /* Only queries with over 1000 reads */ - OR qsNow.total_worker_time - qsFirst.total_worker_time > 1000000 /* Only queries with over 1 second of worker time */) - ORDER BY (qsNow.execution_count - COALESCE(qsFirst.execution_count, 0)) DESC) - UPDATE #QueryStats - SET Points = Points + 1 - FROM #QueryStats qs - INNER JOIN qsTop ON qs.ID = qsTop.ID; + /* rows will only be in this dmv when data is in memory for the table */ + ), N'Table metadata not in memory'), + index_lock_wait_summary AS ISNULL( + CASE WHEN total_row_lock_wait_count = 0 AND total_page_lock_wait_count = 0 AND + total_index_lock_promotion_attempt_count = 0 THEN N'0 lock waits; ' + + CASE WHEN lock_escalation_desc = N'DISABLE' THEN N'Lock escalation DISABLE.' + ELSE N'' + END + ELSE + CASE WHEN total_row_lock_wait_count > 0 THEN + N'Row lock waits: ' + REPLACE(CONVERT(NVARCHAR(30),CAST(total_row_lock_wait_count AS MONEY), 1), N'.00', N'') + + N'; total duration: ' + + CASE WHEN total_row_lock_wait_in_ms >= 60000 THEN /*More than 1 min*/ + REPLACE(CONVERT(NVARCHAR(30),CAST((total_row_lock_wait_in_ms/60000) AS MONEY), 1), N'.00', N'') + N' minutes; ' + ELSE + REPLACE(CONVERT(NVARCHAR(30),CAST(ISNULL(total_row_lock_wait_in_ms/1000,0) AS MONEY), 1), N'.00', N'') + N' seconds; ' + END + + N'avg duration: ' + + CASE WHEN avg_row_lock_wait_in_ms >= 60000 THEN /*More than 1 min*/ + REPLACE(CONVERT(NVARCHAR(30),CAST((avg_row_lock_wait_in_ms/60000) AS MONEY), 1), N'.00', N'') + N' minutes; ' + ELSE + REPLACE(CONVERT(NVARCHAR(30),CAST(ISNULL(avg_row_lock_wait_in_ms/1000,0) AS MONEY), 1), N'.00', N'') + N' seconds; ' + END + ELSE N'' + END + + CASE WHEN total_page_lock_wait_count > 0 THEN + N'Page lock waits: ' + REPLACE(CONVERT(NVARCHAR(30),CAST(total_page_lock_wait_count AS MONEY), 1), N'.00', N'') + + N'; total duration: ' + + CASE WHEN total_page_lock_wait_in_ms >= 60000 THEN /*More than 1 min*/ + REPLACE(CONVERT(NVARCHAR(30),CAST((total_page_lock_wait_in_ms/60000) AS MONEY), 1), N'.00', N'') + N' minutes; ' + ELSE + REPLACE(CONVERT(NVARCHAR(30),CAST(ISNULL(total_page_lock_wait_in_ms/1000,0) AS MONEY), 1), N'.00', N'') + N' seconds; ' + END + + N'avg duration: ' + + CASE WHEN avg_page_lock_wait_in_ms >= 60000 THEN /*More than 1 min*/ + REPLACE(CONVERT(NVARCHAR(30),CAST((avg_page_lock_wait_in_ms/60000) AS MONEY), 1), N'.00', N'') + N' minutes; ' + ELSE + REPLACE(CONVERT(NVARCHAR(30),CAST(ISNULL(avg_page_lock_wait_in_ms/1000,0) AS MONEY), 1), N'.00', N'') + N' seconds; ' + END + ELSE N'' + END + + CASE WHEN total_index_lock_promotion_attempt_count > 0 THEN + N'Lock escalation attempts: ' + REPLACE(CONVERT(NVARCHAR(30),CAST(total_index_lock_promotion_attempt_count AS MONEY), 1), N'.00', N'') + + N'; Actual Escalations: ' + REPLACE(CONVERT(NVARCHAR(30),CAST(ISNULL(total_index_lock_promotion_count,0) AS MONEY), 1), N'.00', N'') +N'; ' + ELSE N'' + END + + CASE WHEN lock_escalation_desc = N'DISABLE' THEN + N'Lock escalation is disabled.' + ELSE N'' + END + END + ,'Error- NULL in computed column') + ); - /* Query Stats - Most Resource-Intensive Queries - CheckID 17 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 17',10,1) WITH NOWAIT; - END + CREATE TABLE #IndexColumns + ( + [database_id] INT NOT NULL, + [schema_name] NVARCHAR(128), + [object_id] INT NOT NULL , + [index_id] INT NOT NULL , + [key_ordinal] INT NULL , + is_included_column BIT NULL , + is_descending_key BIT NULL , + [partition_ordinal] INT NULL , + column_name NVARCHAR(256) NOT NULL , + system_type_name NVARCHAR(256) NOT NULL, + max_length SMALLINT NOT NULL, + [precision] TINYINT NOT NULL, + [scale] TINYINT NOT NULL, + collation_name NVARCHAR(256) NULL, + is_nullable BIT NULL, + is_identity BIT NULL, + is_computed BIT NULL, + is_replicated BIT NULL, + is_sparse BIT NULL, + is_filestream BIT NULL, + seed_value DECIMAL(38,0) NULL, + increment_value DECIMAL(38,0) NULL , + last_value DECIMAL(38,0) NULL, + is_not_for_replication BIT NULL + ); + CREATE CLUSTERED INDEX CLIX_database_id_object_id_index_id ON #IndexColumns + (database_id, object_id, index_id); - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, QueryText, QueryStatsNowID, QueryStatsFirstID, PlanHandle, QueryHash) - SELECT 17, 210, 'Query Stats', 'Most Resource-Intensive Queries', 'http://www.BrentOzar.com/go/topqueries', - 'Query stats during the sample:' + @LineFeed + - 'Executions: ' + CAST(qsNow.execution_count - (COALESCE(qsFirst.execution_count, 0)) AS NVARCHAR(100)) + @LineFeed + - 'Elapsed Time: ' + CAST(qsNow.total_elapsed_time - (COALESCE(qsFirst.total_elapsed_time, 0)) AS NVARCHAR(100)) + @LineFeed + - 'CPU Time: ' + CAST(qsNow.total_worker_time - (COALESCE(qsFirst.total_worker_time, 0)) AS NVARCHAR(100)) + @LineFeed + - 'Logical Reads: ' + CAST(qsNow.total_logical_reads - (COALESCE(qsFirst.total_logical_reads, 0)) AS NVARCHAR(100)) + @LineFeed + - 'Logical Writes: ' + CAST(qsNow.total_logical_writes - (COALESCE(qsFirst.total_logical_writes, 0)) AS NVARCHAR(100)) + @LineFeed + - 'CLR Time: ' + CAST(qsNow.total_clr_time - (COALESCE(qsFirst.total_clr_time, 0)) AS NVARCHAR(100)) + @LineFeed + - @LineFeed + @LineFeed + 'Query stats since ' + CONVERT(NVARCHAR(100), qsNow.creation_time ,121) + @LineFeed + - 'Executions: ' + CAST(qsNow.execution_count AS NVARCHAR(100)) + - CASE qsTotal.execution_count WHEN 0 THEN '' ELSE (' - Percent of Server Total: ' + CAST(CAST(100.0 * qsNow.execution_count / qsTotal.execution_count AS DECIMAL(6,2)) AS NVARCHAR(100)) + '%') END + @LineFeed + - 'Elapsed Time: ' + CAST(qsNow.total_elapsed_time AS NVARCHAR(100)) + - CASE qsTotal.total_elapsed_time WHEN 0 THEN '' ELSE (' - Percent of Server Total: ' + CAST(CAST(100.0 * qsNow.total_elapsed_time / qsTotal.total_elapsed_time AS DECIMAL(6,2)) AS NVARCHAR(100)) + '%') END + @LineFeed + - 'CPU Time: ' + CAST(qsNow.total_worker_time AS NVARCHAR(100)) + - CASE qsTotal.total_worker_time WHEN 0 THEN '' ELSE (' - Percent of Server Total: ' + CAST(CAST(100.0 * qsNow.total_worker_time / qsTotal.total_worker_time AS DECIMAL(6,2)) AS NVARCHAR(100)) + '%') END + @LineFeed + - 'Logical Reads: ' + CAST(qsNow.total_logical_reads AS NVARCHAR(100)) + - CASE qsTotal.total_logical_reads WHEN 0 THEN '' ELSE (' - Percent of Server Total: ' + CAST(CAST(100.0 * qsNow.total_logical_reads / qsTotal.total_logical_reads AS DECIMAL(6,2)) AS NVARCHAR(100)) + '%') END + @LineFeed + - 'Logical Writes: ' + CAST(qsNow.total_logical_writes AS NVARCHAR(100)) + - CASE qsTotal.total_logical_writes WHEN 0 THEN '' ELSE (' - Percent of Server Total: ' + CAST(CAST(100.0 * qsNow.total_logical_writes / qsTotal.total_logical_writes AS DECIMAL(6,2)) AS NVARCHAR(100)) + '%') END + @LineFeed + - 'CLR Time: ' + CAST(qsNow.total_clr_time AS NVARCHAR(100)) + - CASE qsTotal.total_clr_time WHEN 0 THEN '' ELSE (' - Percent of Server Total: ' + CAST(CAST(100.0 * qsNow.total_clr_time / qsTotal.total_clr_time AS DECIMAL(6,2)) AS NVARCHAR(100)) + '%') END + @LineFeed + - --@LineFeed + @LineFeed + 'Query hash: ' + CAST(qsNow.query_hash AS NVARCHAR(100)) + @LineFeed + - --@LineFeed + @LineFeed + 'Query plan hash: ' + CAST(qsNow.query_plan_hash AS NVARCHAR(100)) + - @LineFeed AS Details, - 'See the URL for tuning tips on why this query may be consuming resources.' AS HowToStopIt, - qp.query_plan, - QueryText = SUBSTRING(st.text, - (qsNow.statement_start_offset / 2) + 1, - ((CASE qsNow.statement_end_offset - WHEN -1 THEN DATALENGTH(st.text) - ELSE qsNow.statement_end_offset - END - qsNow.statement_start_offset) / 2) + 1), - qsNow.ID AS QueryStatsNowID, - qsFirst.ID AS QueryStatsFirstID, - qsNow.plan_handle AS PlanHandle, - qsNow.query_hash - FROM #QueryStats qsNow - INNER JOIN #QueryStats qsTotal ON qsTotal.Pass = 0 - LEFT OUTER JOIN #QueryStats qsFirst ON qsNow.[sql_handle] = qsFirst.[sql_handle] AND qsNow.statement_start_offset = qsFirst.statement_start_offset AND qsNow.statement_end_offset = qsFirst.statement_end_offset AND qsNow.plan_generation_num = qsFirst.plan_generation_num AND qsNow.plan_handle = qsFirst.plan_handle AND qsFirst.Pass = 1 - CROSS APPLY sys.dm_exec_sql_text(qsNow.sql_handle) AS st - CROSS APPLY sys.dm_exec_query_plan(qsNow.plan_handle) AS qp - WHERE qsNow.Points > 0 AND st.text IS NOT NULL AND qp.query_plan IS NOT NULL; + CREATE TABLE #MissingIndexes + ([database_id] INT NOT NULL, + [object_id] INT NOT NULL, + [database_name] NVARCHAR(128) NOT NULL , + [schema_name] NVARCHAR(128) NOT NULL , + [table_name] NVARCHAR(128), + [statement] NVARCHAR(512) NOT NULL, + magic_benefit_number AS (( user_seeks + user_scans ) * avg_total_user_cost * avg_user_impact), + avg_total_user_cost NUMERIC(29,4) NOT NULL, + avg_user_impact NUMERIC(29,1) NOT NULL, + user_seeks BIGINT NOT NULL, + user_scans BIGINT NOT NULL, + unique_compiles BIGINT NULL, + equality_columns NVARCHAR(MAX), + equality_columns_with_data_type NVARCHAR(MAX), + inequality_columns NVARCHAR(MAX), + inequality_columns_with_data_type NVARCHAR(MAX), + included_columns NVARCHAR(MAX), + included_columns_with_data_type NVARCHAR(MAX), + is_low BIT, + [index_estimated_impact] AS + REPLACE(CONVERT(NVARCHAR(256),CAST(CAST( + (user_seeks + user_scans) + AS BIGINT) AS MONEY), 1), '.00', '') + N' use' + + CASE WHEN (user_seeks + user_scans) > 1 THEN N's' ELSE N'' END + +N'; Impact: ' + CAST(avg_user_impact AS NVARCHAR(30)) + + N'%; Avg query cost: ' + + CAST(avg_total_user_cost AS NVARCHAR(30)), + [missing_index_details] AS + CASE WHEN COALESCE(equality_columns_with_data_type,equality_columns) IS NOT NULL + THEN N'EQUALITY: ' + COALESCE(CAST(equality_columns_with_data_type AS NVARCHAR(MAX)), CAST(equality_columns AS NVARCHAR(MAX))) + N' ' + ELSE N'' END + - UPDATE #BlitzFirstResults - SET DatabaseID = CAST(attr.value AS INT), - DatabaseName = DB_NAME(CAST(attr.value AS INT)) - FROM #BlitzFirstResults - CROSS APPLY sys.dm_exec_plan_attributes(#BlitzFirstResults.PlanHandle) AS attr - WHERE attr.attribute = 'dbid'; + CASE WHEN COALESCE(inequality_columns_with_data_type,inequality_columns) IS NOT NULL + THEN N'INEQUALITY: ' + COALESCE(CAST(inequality_columns_with_data_type AS NVARCHAR(MAX)), CAST(inequality_columns AS NVARCHAR(MAX))) + N' ' + ELSE N'' END + + CASE WHEN COALESCE(included_columns_with_data_type,included_columns) IS NOT NULL + THEN N'INCLUDE: ' + COALESCE(CAST(included_columns_with_data_type AS NVARCHAR(MAX)), CAST(included_columns AS NVARCHAR(MAX))) + N' ' + ELSE N'' END, + [create_tsql] AS N'CREATE INDEX [' + + LEFT(REPLACE(REPLACE(REPLACE(REPLACE( + ISNULL(equality_columns,N'')+ + CASE WHEN equality_columns IS NOT NULL AND inequality_columns IS NOT NULL THEN N'_' ELSE N'' END + + ISNULL(inequality_columns,''),',','') + ,'[',''),']',''),' ','_') + + CASE WHEN included_columns IS NOT NULL THEN N'_Includes' ELSE N'' END, 128) + N'] ON ' + + [statement] + N' (' + ISNULL(equality_columns,N'') + + CASE WHEN equality_columns IS NOT NULL AND inequality_columns IS NOT NULL THEN N', ' ELSE N'' END + + CASE WHEN inequality_columns IS NOT NULL THEN inequality_columns ELSE N'' END + + ') ' + CASE WHEN included_columns IS NOT NULL THEN N' INCLUDE (' + included_columns + N')' ELSE N'' END + + N' WITH (' + + N'FILLFACTOR=100, ONLINE=?, SORT_IN_TEMPDB=?, DATA_COMPRESSION=?' + + N')' + + N';' + , + [more_info] AS N'EXEC dbo.sp_BlitzIndex @DatabaseName=' + QUOTENAME([database_name],'''') + + N', @SchemaName=' + QUOTENAME([schema_name],'''') + N', @TableName=' + QUOTENAME([table_name],'''') + N';', + [sample_query_plan] XML NULL + ); - END; /* IF DATEDIFF(ss, @FinishSampleTime, SYSDATETIMEOFFSET()) > 10 AND @CheckProcedureCache = 1 */ + CREATE TABLE #ForeignKeys ( + [database_id] INT NOT NULL, + [database_name] NVARCHAR(128) NOT NULL , + [schema_name] NVARCHAR(128) NOT NULL , + foreign_key_name NVARCHAR(256), + parent_object_id INT, + parent_object_name NVARCHAR(256), + referenced_object_id INT, + referenced_object_name NVARCHAR(256), + is_disabled BIT, + is_not_trusted BIT, + is_not_for_replication BIT, + parent_fk_columns NVARCHAR(MAX), + referenced_fk_columns NVARCHAR(MAX), + update_referential_action_desc NVARCHAR(16), + delete_referential_action_desc NVARCHAR(60) + ); + + CREATE TABLE #IndexCreateTsql ( + index_sanity_id INT NOT NULL, + create_tsql NVARCHAR(MAX) NOT NULL + ); + CREATE TABLE #DatabaseList ( + DatabaseName NVARCHAR(256), + secondary_role_allow_connections_desc NVARCHAR(50) - RAISERROR('Analyzing changes between first and second passes of DMVs',10,1) WITH NOWAIT; + ); - /* Wait Stats - CheckID 6 */ - /* Compare the current wait stats to the sample we took at the start, and insert the top 10 waits. */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 6',10,1) WITH NOWAIT; - END + CREATE TABLE #PartitionCompressionInfo ( + [index_sanity_id] INT NULL, + [partition_compression_detail] NVARCHAR(4000) NULL + ); - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, DetailsInt) - SELECT TOP 10 6 AS CheckID, - 200 AS Priority, - 'Wait Stats' AS FindingGroup, - wNow.wait_type AS Finding, /* IF YOU CHANGE THIS, STUFF WILL BREAK. Other checks look for wait type names in the Finding field. See checks 11, 12 as example. */ - N'https://www.sqlskills.com/help/waits/' + LOWER(wNow.wait_type) + '/' AS URL, - 'For ' + CAST(((wNow.wait_time_ms - COALESCE(wBase.wait_time_ms,0)) / 1000) AS NVARCHAR(100)) + ' seconds over the last ' + CASE @Seconds WHEN 0 THEN (CAST(DATEDIFF(dd,@StartSampleTime,@FinishSampleTime) AS NVARCHAR(10)) + ' days') ELSE (CAST(@Seconds AS NVARCHAR(10)) + ' seconds') END + ', SQL Server was waiting on this particular bottleneck.' + @LineFeed + @LineFeed AS Details, - 'See the URL for more details on how to mitigate this wait type.' AS HowToStopIt, - ((wNow.wait_time_ms - COALESCE(wBase.wait_time_ms,0)) / 1000) AS DetailsInt - FROM #WaitStats wNow - LEFT OUTER JOIN #WaitStats wBase ON wNow.wait_type = wBase.wait_type AND wNow.SampleTime > wBase.SampleTime - WHERE wNow.wait_time_ms > (wBase.wait_time_ms + (.5 * (DATEDIFF(ss,@StartSampleTime,@FinishSampleTime)) * 1000)) /* Only look for things we've actually waited on for half of the time or more */ - ORDER BY (wNow.wait_time_ms - COALESCE(wBase.wait_time_ms,0)) DESC; + CREATE TABLE #Statistics ( + database_id INT NOT NULL, + database_name NVARCHAR(256) NOT NULL, + table_name NVARCHAR(128) NULL, + schema_name NVARCHAR(128) NULL, + index_name NVARCHAR(128) NULL, + column_names NVARCHAR(MAX) NULL, + statistics_name NVARCHAR(128) NULL, + last_statistics_update DATETIME NULL, + days_since_last_stats_update INT NULL, + rows BIGINT NULL, + rows_sampled BIGINT NULL, + percent_sampled DECIMAL(18, 1) NULL, + histogram_steps INT NULL, + modification_counter BIGINT NULL, + percent_modifications DECIMAL(18, 1) NULL, + modifications_before_auto_update INT 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 + ); - /* Server Performance - Poison Wait Detected - CheckID 30 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 30',10,1) WITH NOWAIT; - END + CREATE TABLE #ComputedColumns + ( + index_sanity_id INT IDENTITY(1, 1) NOT NULL, + database_name NVARCHAR(128) NULL, + database_id INT NOT NULL, + table_name NVARCHAR(128) NOT NULL, + schema_name NVARCHAR(128) NOT NULL, + column_name NVARCHAR(128) NULL, + is_nullable BIT NULL, + definition NVARCHAR(MAX) NULL, + uses_database_collation BIT NOT NULL, + is_persisted BIT NOT NULL, + is_computed BIT NOT NULL, + is_function INT NOT NULL, + column_definition NVARCHAR(MAX) NULL + ); + + CREATE TABLE #TraceStatus + ( + TraceFlag NVARCHAR(10) , + status BIT , + Global BIT , + Session BIT + ); - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, DetailsInt) - SELECT 30 AS CheckID, - 10 AS Priority, - 'Server Performance' AS FindingGroup, - 'Poison Wait Detected: ' + wNow.wait_type AS Finding, - N'http://www.brentozar.com/go/poison/#' + wNow.wait_type AS URL, - 'For ' + CAST(((wNow.wait_time_ms - COALESCE(wBase.wait_time_ms,0)) / 1000) AS NVARCHAR(100)) + ' seconds over the last ' + CASE @Seconds WHEN 0 THEN (CAST(DATEDIFF(dd,@StartSampleTime,@FinishSampleTime) AS NVARCHAR(10)) + ' days') ELSE (CAST(@Seconds AS NVARCHAR(10)) + ' seconds') END + ', SQL Server was waiting on this particular bottleneck.' + @LineFeed + @LineFeed AS Details, - 'See the URL for more details on how to mitigate this wait type.' AS HowToStopIt, - ((wNow.wait_time_ms - COALESCE(wBase.wait_time_ms,0)) / 1000) AS DetailsInt - FROM #WaitStats wNow - LEFT OUTER JOIN #WaitStats wBase ON wNow.wait_type = wBase.wait_type AND wNow.SampleTime > wBase.SampleTime - WHERE wNow.wait_type IN ('IO_QUEUE_LIMIT', 'IO_RETRY', 'LOG_RATE_GOVERNOR', 'POOL_LOG_RATE_GOVERNOR', 'PREEMPTIVE_DEBUG', 'RESMGR_THROTTLED', 'RESOURCE_SEMAPHORE', 'RESOURCE_SEMAPHORE_QUERY_COMPILE','SE_REPL_CATCHUP_THROTTLE','SE_REPL_COMMIT_ACK','SE_REPL_COMMIT_TURN','SE_REPL_ROLLBACK_ACK','SE_REPL_SLOW_SECONDARY_THROTTLE','THREADPOOL') - AND wNow.wait_time_ms > (wBase.wait_time_ms + 1000); + CREATE TABLE #TemporalTables + ( + index_sanity_id INT IDENTITY(1, 1) NOT NULL, + database_name NVARCHAR(128) NOT NULL, + database_id INT NOT NULL, + schema_name NVARCHAR(128) NOT NULL, + table_name NVARCHAR(128) NOT NULL, + history_table_name NVARCHAR(128) NOT NULL, + history_schema_name NVARCHAR(128) NOT NULL, + start_column_name NVARCHAR(128) NOT NULL, + end_column_name NVARCHAR(128) NOT NULL, + period_name NVARCHAR(128) NOT NULL + ); + CREATE TABLE #CheckConstraints + ( + index_sanity_id INT IDENTITY(1, 1) NOT NULL, + database_name NVARCHAR(128) NULL, + database_id INT NOT NULL, + table_name NVARCHAR(128) NOT NULL, + schema_name NVARCHAR(128) NOT NULL, + constraint_name NVARCHAR(128) NULL, + is_disabled BIT NULL, + definition NVARCHAR(MAX) NULL, + uses_database_collation BIT NOT NULL, + is_not_trusted BIT NOT NULL, + is_function INT NOT NULL, + column_definition NVARCHAR(MAX) NULL + ); - /* Server Performance - Slow Data File Reads - CheckID 11 */ - IF EXISTS (SELECT * FROM #BlitzFirstResults WHERE Finding LIKE 'PAGEIOLATCH%') - BEGIN - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 11',10,1) WITH NOWAIT; - END + CREATE TABLE #FilteredIndexes + ( + index_sanity_id INT IDENTITY(1, 1) NOT NULL, + database_name NVARCHAR(128) NULL, + database_id INT NOT NULL, + schema_name NVARCHAR(128) NOT NULL, + table_name NVARCHAR(128) NOT NULL, + index_name NVARCHAR(128) NULL, + column_name NVARCHAR(128) NULL + ); - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, DatabaseID, DatabaseName) - SELECT TOP 10 11 AS CheckID, - 50 AS Priority, - 'Server Performance' AS FindingGroup, - 'Slow Data File Reads' AS Finding, - 'http://www.BrentOzar.com/go/slow/' AS URL, - 'Your server is experiencing PAGEIOLATCH% waits due to slow data file reads. This file is one of the reasons why.' + @LineFeed - + 'File: ' + fNow.PhysicalName + @LineFeed - + 'Number of reads during the sample: ' + CAST((fNow.num_of_reads - fBase.num_of_reads) AS NVARCHAR(20)) + @LineFeed - + 'Seconds spent waiting on storage for these reads: ' + CAST(((fNow.io_stall_read_ms - fBase.io_stall_read_ms) / 1000.0) AS NVARCHAR(20)) + @LineFeed - + 'Average read latency during the sample: ' + CAST(((fNow.io_stall_read_ms - fBase.io_stall_read_ms) / (fNow.num_of_reads - fBase.num_of_reads) ) AS NVARCHAR(20)) + ' milliseconds' + @LineFeed - + 'Microsoft guidance for data file read speed: 20ms or less.' + @LineFeed + @LineFeed AS Details, - 'See the URL for more details on how to mitigate this wait type.' AS HowToStopIt, - fNow.DatabaseID, - fNow.DatabaseName - FROM #FileStats fNow - INNER JOIN #FileStats fBase ON fNow.DatabaseID = fBase.DatabaseID AND fNow.FileID = fBase.FileID AND fNow.SampleTime > fBase.SampleTime AND fNow.num_of_reads > fBase.num_of_reads AND fNow.io_stall_read_ms > (fBase.io_stall_read_ms + 1000) - WHERE (fNow.io_stall_read_ms - fBase.io_stall_read_ms) / (fNow.num_of_reads - fBase.num_of_reads) >= @FileLatencyThresholdMS - AND fNow.TypeDesc = 'ROWS' - ORDER BY (fNow.io_stall_read_ms - fBase.io_stall_read_ms) / (fNow.num_of_reads - fBase.num_of_reads) DESC; - END; + CREATE TABLE #Ignore_Databases + ( + DatabaseName NVARCHAR(128), + Reason NVARCHAR(100) + ); - /* Server Performance - Slow Log File Writes - CheckID 12 */ - IF EXISTS (SELECT * FROM #BlitzFirstResults WHERE Finding LIKE 'WRITELOG%') - BEGIN - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 12',10,1) WITH NOWAIT; - END +/* Sanitize our inputs */ +SELECT + @OutputServerName = QUOTENAME(@OutputServerName), + @OutputDatabaseName = QUOTENAME(@OutputDatabaseName), + @OutputSchemaName = QUOTENAME(@OutputSchemaName), + @OutputTableName = QUOTENAME(@OutputTableName); + + +IF @GetAllDatabases = 1 + BEGIN + INSERT INTO #DatabaseList (DatabaseName) + SELECT DB_NAME(database_id) + FROM sys.databases + WHERE user_access_desc = 'MULTI_USER' + AND state_desc = 'ONLINE' + AND database_id > 4 + AND DB_NAME(database_id) NOT LIKE 'ReportServer%' + AND DB_NAME(database_id) NOT LIKE 'rdsadmin%' + AND is_distributor = 0 + OPTION ( RECOMPILE ); - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, DatabaseID, DatabaseName) - SELECT TOP 10 12 AS CheckID, - 50 AS Priority, - 'Server Performance' AS FindingGroup, - 'Slow Log File Writes' AS Finding, - 'http://www.BrentOzar.com/go/slow/' AS URL, - 'Your server is experiencing WRITELOG waits due to slow log file writes. This file is one of the reasons why.' + @LineFeed - + 'File: ' + fNow.PhysicalName + @LineFeed - + 'Number of writes during the sample: ' + CAST((fNow.num_of_writes - fBase.num_of_writes) AS NVARCHAR(20)) + @LineFeed - + 'Seconds spent waiting on storage for these writes: ' + CAST(((fNow.io_stall_write_ms - fBase.io_stall_write_ms) / 1000.0) AS NVARCHAR(20)) + @LineFeed - + 'Average write latency during the sample: ' + CAST(((fNow.io_stall_write_ms - fBase.io_stall_write_ms) / (fNow.num_of_writes - fBase.num_of_writes) ) AS NVARCHAR(20)) + ' milliseconds' + @LineFeed - + 'Microsoft guidance for log file write speed: 3ms or less.' + @LineFeed + @LineFeed AS Details, - 'See the URL for more details on how to mitigate this wait type.' AS HowToStopIt, - fNow.DatabaseID, - fNow.DatabaseName - FROM #FileStats fNow - INNER JOIN #FileStats fBase ON fNow.DatabaseID = fBase.DatabaseID AND fNow.FileID = fBase.FileID AND fNow.SampleTime > fBase.SampleTime AND fNow.num_of_writes > fBase.num_of_writes AND fNow.io_stall_write_ms > (fBase.io_stall_write_ms + 1000) - WHERE (fNow.io_stall_write_ms - fBase.io_stall_write_ms) / (fNow.num_of_writes - fBase.num_of_writes) >= @FileLatencyThresholdMS - AND fNow.TypeDesc = 'LOG' - ORDER BY (fNow.io_stall_write_ms - fBase.io_stall_write_ms) / (fNow.num_of_writes - fBase.num_of_writes) DESC; - END; + /* Skip non-readable databases in an AG - see Github issue #1160 */ + IF EXISTS (SELECT * FROM sys.all_objects o INNER JOIN sys.all_columns c ON o.object_id = c.object_id AND o.name = 'dm_hadr_availability_replica_states' AND c.name = 'role_desc') + BEGIN + SET @dsql = N'UPDATE #DatabaseList SET secondary_role_allow_connections_desc = ''NO'' WHERE DatabaseName IN ( + SELECT d.name + FROM sys.dm_hadr_availability_replica_states rs + INNER JOIN sys.databases d ON rs.replica_id = d.replica_id + INNER JOIN sys.availability_replicas r ON rs.replica_id = r.replica_id + WHERE rs.role_desc = ''SECONDARY'' + AND r.secondary_role_allow_connections_desc = ''NO'') + OPTION ( RECOMPILE );'; + EXEC sp_executesql @dsql; + IF EXISTS (SELECT * FROM #DatabaseList WHERE secondary_role_allow_connections_desc = 'NO') + BEGIN + INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, database_name, URL, details, index_definition, + index_usage_summary, index_size_summary ) + VALUES ( 1, + 0, + N'Skipped non-readable AG secondary databases.', + N'You are running this on an AG secondary, and some of your databases are configured as non-readable when this is a secondary node.', + N'To analyze those databases, run sp_BlitzIndex on the primary, or on a readable secondary.', + 'http://FirstResponderKit.org', '', '', '', '' + ); + END; + END; - /* SQL Server Internal Maintenance - Log File Growing - CheckID 13 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 13',10,1) WITH NOWAIT; - END + IF @IgnoreDatabases IS NOT NULL + AND LEN(@IgnoreDatabases) > 0 + BEGIN + RAISERROR(N'Setting up filter to ignore databases', 0, 1) WITH NOWAIT; + SET @DatabaseToIgnore = ''; - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) - SELECT 13 AS CheckID, - 1 AS Priority, - 'SQL Server Internal Maintenance' AS FindingGroup, - 'Log File Growing' AS Finding, - 'http://www.BrentOzar.com/askbrent/file-growing/' AS URL, - 'Number of growths during the sample: ' + CAST(ps.value_delta AS NVARCHAR(20)) + @LineFeed - + 'Determined by sampling Perfmon counter ' + ps.object_name + ' - ' + ps.counter_name + @LineFeed AS Details, - 'Pre-grow data and log files during maintenance windows so that they do not grow during production loads. See the URL for more details.' AS HowToStopIt - FROM #PerfmonStats ps - WHERE ps.Pass = 2 - AND object_name = @ServiceName + ':Databases' - AND counter_name = 'Log Growths' - AND value_delta > 0; + WHILE LEN(@IgnoreDatabases) > 0 + BEGIN + IF PATINDEX('%,%', @IgnoreDatabases) > 0 + BEGIN + SET @DatabaseToIgnore = SUBSTRING(@IgnoreDatabases, 0, PATINDEX('%,%',@IgnoreDatabases)) ; + + INSERT INTO #Ignore_Databases (DatabaseName, Reason) + SELECT LTRIM(RTRIM(@DatabaseToIgnore)), 'Specified in the @IgnoreDatabases parameter' + OPTION (RECOMPILE) ; + + SET @IgnoreDatabases = SUBSTRING(@IgnoreDatabases, LEN(@DatabaseToIgnore + ',') + 1, LEN(@IgnoreDatabases)) ; + END; + ELSE + BEGIN + SET @DatabaseToIgnore = @IgnoreDatabases ; + SET @IgnoreDatabases = NULL ; + INSERT INTO #Ignore_Databases (DatabaseName, Reason) + SELECT LTRIM(RTRIM(@DatabaseToIgnore)), 'Specified in the @IgnoreDatabases parameter' + OPTION (RECOMPILE) ; + END; + END; + + END - /* SQL Server Internal Maintenance - Log File Shrinking - CheckID 14 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 14',10,1) WITH NOWAIT; - END + END; +ELSE + BEGIN + INSERT INTO #DatabaseList + ( DatabaseName ) + SELECT CASE + WHEN @DatabaseName IS NULL OR @DatabaseName = N'' + THEN DB_NAME() + ELSE @DatabaseName END; + END; - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) - SELECT 14 AS CheckID, - 1 AS Priority, - 'SQL Server Internal Maintenance' AS FindingGroup, - 'Log File Shrinking' AS Finding, - 'http://www.BrentOzar.com/askbrent/file-shrinking/' AS URL, - 'Number of shrinks during the sample: ' + CAST(ps.value_delta AS NVARCHAR(20)) + @LineFeed - + 'Determined by sampling Perfmon counter ' + ps.object_name + ' - ' + ps.counter_name + @LineFeed AS Details, - 'Pre-grow data and log files during maintenance windows so that they do not grow during production loads. See the URL for more details.' AS HowToStopIt - FROM #PerfmonStats ps - WHERE ps.Pass = 2 - AND object_name = @ServiceName + ':Databases' - AND counter_name = 'Log Shrinks' - AND value_delta > 0; +SET @NumDatabases = (SELECT COUNT(*) FROM #DatabaseList AS D LEFT OUTER JOIN #Ignore_Databases AS I ON D.DatabaseName = I.DatabaseName WHERE I.DatabaseName IS NULL); +SET @msg = N'Number of databases to examine: ' + CAST(@NumDatabases AS NVARCHAR(50)); +RAISERROR (@msg,0,1) WITH NOWAIT; - /* Query Problems - Compilations/Sec High - CheckID 15 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 15',10,1) WITH NOWAIT; - END - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) - SELECT 15 AS CheckID, - 50 AS Priority, - 'Query Problems' AS FindingGroup, - 'Compilations/Sec High' AS Finding, - 'http://www.BrentOzar.com/askbrent/compilations/' AS URL, - 'Number of batch requests during the sample: ' + CAST(ps.value_delta AS NVARCHAR(20)) + @LineFeed - + 'Number of compilations during the sample: ' + CAST(psComp.value_delta AS NVARCHAR(20)) + @LineFeed - + 'For OLTP environments, Microsoft recommends that 90% of batch requests should hit the plan cache, and not be compiled from scratch. We are exceeding that threshold.' + @LineFeed AS Details, - 'To find the queries that are compiling, start with:' + @LineFeed - + 'sp_BlitzCache @SortOrder = ''recent compilations''' + @LineFeed - + 'If dynamic SQL or non-parameterized strings are involved, consider enabling Forced Parameterization. See the URL for more details.' AS HowToStopIt - FROM #PerfmonStats ps - INNER JOIN #PerfmonStats psComp ON psComp.Pass = 2 AND psComp.object_name = @ServiceName + ':SQL Statistics' AND psComp.counter_name = 'SQL Compilations/sec' AND psComp.value_delta > 0 - WHERE ps.Pass = 2 - AND ps.object_name = @ServiceName + ':SQL Statistics' - AND ps.counter_name = 'Batch Requests/sec' - AND ps.value_delta > (1000 * @Seconds) /* Ignore servers sitting idle */ - AND (psComp.value_delta * 10) > ps.value_delta; /* Compilations are more than 10% of batch requests per second */ - /* Query Problems - Re-Compilations/Sec High - CheckID 16 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 16',10,1) WITH NOWAIT; - END +/* Running on 50+ databases can take a reaaallly long time, so we want explicit permission to do so (and only after warning about it) */ - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) - SELECT 16 AS CheckID, - 50 AS Priority, - 'Query Problems' AS FindingGroup, - 'Re-Compilations/Sec High' AS Finding, - 'http://www.BrentOzar.com/askbrent/recompilations/' AS URL, - 'Number of batch requests during the sample: ' + CAST(ps.value_delta AS NVARCHAR(20)) + @LineFeed - + 'Number of recompilations during the sample: ' + CAST(psComp.value_delta AS NVARCHAR(20)) + @LineFeed - + 'More than 10% of our queries are being recompiled. This is typically due to statistics changing on objects.' + @LineFeed AS Details, - 'To find the queries that are being forced to recompile, start with:' + @LineFeed - + 'sp_BlitzCache @SortOrder = ''recent compilations''' + @LineFeed - + 'Examine those plans to find out which objects are changing so quickly that they hit the stats update threshold. See the URL for more details.' AS HowToStopIt - FROM #PerfmonStats ps - INNER JOIN #PerfmonStats psComp ON psComp.Pass = 2 AND psComp.object_name = @ServiceName + ':SQL Statistics' AND psComp.counter_name = 'SQL Re-Compilations/sec' AND psComp.value_delta > 0 - WHERE ps.Pass = 2 - AND ps.object_name = @ServiceName + ':SQL Statistics' - AND ps.counter_name = 'Batch Requests/sec' - AND ps.value_delta > (1000 * @Seconds) /* Ignore servers sitting idle */ - AND (psComp.value_delta * 10) > ps.value_delta; /* Recompilations are more than 10% of batch requests per second */ - /* Table Problems - Forwarded Fetches/Sec High - CheckID 29 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 29',10,1) WITH NOWAIT; - END +BEGIN TRY + IF @NumDatabases >= 50 AND @BringThePain != 1 AND @TableName IS NULL + BEGIN - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) - SELECT 29 AS CheckID, - 40 AS Priority, - 'Table Problems' AS FindingGroup, - 'Forwarded Fetches/Sec High' AS Finding, - 'https://BrentOzar.com/go/fetch/' AS URL, - CAST(ps.value_delta AS NVARCHAR(20)) + ' forwarded fetches (from SQLServer:Access Methods counter)' + @LineFeed - + 'Check your heaps: they need to be rebuilt, or they need a clustered index applied.' + @LineFeed AS Details, - 'Rebuild your heaps. If you use Ola Hallengren maintenance scripts, those do not rebuild heaps by default: https://www.brentozar.com/archive/2016/07/fix-forwarded-records/' AS HowToStopIt - FROM #PerfmonStats ps - INNER JOIN #PerfmonStats psComp ON psComp.Pass = 2 AND psComp.object_name = @ServiceName + ':Access Methods' AND psComp.counter_name = 'Forwarded Records/sec' AND psComp.value_delta > 100 - WHERE ps.Pass = 2 - AND ps.object_name = @ServiceName + ':Access Methods' - AND ps.counter_name = 'Forwarded Records/sec' - AND ps.value_delta > (100 * @Seconds); /* Ignore servers sitting idle */ + INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, URL, details, index_definition, + index_usage_summary, index_size_summary ) + VALUES ( -1, + 0 , + @ScriptVersionName, + CASE WHEN @GetAllDatabases = 1 THEN N'All Databases' ELSE N'Database ' + QUOTENAME(@DatabaseName) + N' as of ' + CONVERT(NVARCHAR(16), GETDATE(), 121) END, + N'From Your Community Volunteers', + N'http://FirstResponderKit.org', + N'', + N'', + N'' + ); + INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, database_name, URL, details, index_definition, + index_usage_summary, index_size_summary ) + VALUES ( 1, + 0, + N'You''re trying to run sp_BlitzIndex on a server with ' + CAST(@NumDatabases AS NVARCHAR(8)) + N' databases. ', + N'Running sp_BlitzIndex on a server with 50+ databases may cause temporary insanity for the server and/or user.', + N'If you''re sure you want to do this, run again with the parameter @BringThePain = 1.', + 'http://FirstResponderKit.org', + '', + '', + '', + '' + ); + + if(@OutputType <> 'NONE') + BEGIN + SELECT bir.blitz_result_id, + bir.check_id, + bir.index_sanity_id, + bir.Priority, + bir.findings_group, + bir.finding, + bir.database_name, + bir.URL, + bir.details, + bir.index_definition, + bir.secret_columns, + bir.index_usage_summary, + bir.index_size_summary, + bir.create_tsql, + bir.more_info + FROM #BlitzIndexResults AS bir; + RAISERROR('Running sp_BlitzIndex on a server with 50+ databases may cause temporary insanity for the server', 12, 1); + END; - /* Check for temp objects with high forwarded fetches. - This has to be done as dynamic SQL because we have to execute OBJECT_NAME inside TempDB. */ - IF @@ROWCOUNT > 0 - BEGIN - SET @StringToExecute = N'USE tempdb; - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) - SELECT TOP 10 29 AS CheckID, - 40 AS Priority, - ''Table Problems'' AS FindingGroup, - ''Forwarded Fetches/Sec High: TempDB Object'' AS Finding, - ''https://BrentOzar.com/go/fetch/'' AS URL, - CAST(COALESCE(os.forwarded_fetch_count,0) - COALESCE(os_prior.forwarded_fetch_count,0) AS NVARCHAR(20)) + '' forwarded fetches on '' + - CASE WHEN OBJECT_NAME(os.object_id) IS NULL THEN ''an unknown table '' - WHEN LEN(OBJECT_NAME(os.object_id)) < 50 THEN ''a table variable, internal identifier '' + OBJECT_NAME(os.object_id) - ELSE ''a temp table '' + OBJECT_NAME(os.object_id) - END AS Details, - ''Look through your source code to find the object creating these objects, and tune the creation and population to reduce fetches. See the URL for details.'' AS HowToStopIt - FROM tempdb.sys.dm_db_index_operational_stats(DB_ID(''tempdb''), NULL, NULL, NULL) os - LEFT OUTER JOIN #TempdbOperationalStats os_prior ON os.object_id = os_prior.object_id - AND os.forwarded_fetch_count > os_prior.forwarded_fetch_count - WHERE os.database_id = DB_ID(''tempdb'') - AND os.forwarded_fetch_count - COALESCE(os_prior.forwarded_fetch_count,0) > 100 - ORDER BY os.forwarded_fetch_count DESC;' + RETURN; - EXECUTE sp_executesql @StringToExecute; - END + END; +END TRY +BEGIN CATCH + RAISERROR (N'Failure to execute due to number of databases.', 0,1) WITH NOWAIT; - /* In-Memory OLTP - Garbage Collection in Progress - CheckID 31 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 31',10,1) WITH NOWAIT; - END + SELECT @msg = ERROR_MESSAGE(), + @ErrorSeverity = ERROR_SEVERITY(), + @ErrorState = ERROR_STATE(); - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) - SELECT 31 AS CheckID, - 50 AS Priority, - 'In-Memory OLTP' AS FindingGroup, - 'Garbage Collection in Progress' AS Finding, - 'https://BrentOzar.com/go/garbage/' AS URL, - CAST(ps.value_delta AS NVARCHAR(50)) + ' rows processed (from SQL Server YYYY XTP Garbage Collection:Rows processed/sec counter)' + @LineFeed - + 'This can happen due to memory pressure (causing In-Memory OLTP to shrink its footprint) or' + @LineFeed - + 'due to transactional workloads that constantly insert/delete data.' AS Details, - 'Sadly, you cannot choose when garbage collection occurs. This is one of the many gotchas of Hekaton. Learn more: http://nedotter.com/archive/2016/04/row-version-lifecycle-for-in-memory-oltp/' AS HowToStopIt - FROM #PerfmonStats ps - INNER JOIN #PerfmonStats psComp ON psComp.Pass = 2 AND psComp.object_name LIKE '%XTP Garbage Collection' AND psComp.counter_name = 'Rows processed/sec' AND psComp.value_delta > 100 - WHERE ps.Pass = 2 - AND ps.object_name LIKE '%XTP Garbage Collection' - AND ps.counter_name = 'Rows processed/sec' - AND ps.value_delta > (100 * @Seconds); /* Ignore servers sitting idle */ + RAISERROR (@msg, @ErrorSeverity, @ErrorState); + + WHILE @@trancount > 0 + ROLLBACK; - /* In-Memory OLTP - Transactions Aborted - CheckID 32 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 32',10,1) WITH NOWAIT; - END + RETURN; + END CATCH; - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) - SELECT 32 AS CheckID, - 100 AS Priority, - 'In-Memory OLTP' AS FindingGroup, - 'Transactions Aborted' AS Finding, - 'https://BrentOzar.com/go/aborted/' AS URL, - CAST(ps.value_delta AS NVARCHAR(50)) + ' transactions aborted (from SQL Server YYYY XTP Transactions:Transactions aborted/sec counter)' + @LineFeed - + 'This may indicate that data is changing, or causing folks to retry their transactions, thereby increasing load.' AS Details, - 'Dig into your In-Memory OLTP transactions to figure out which ones are failing and being retried.' AS HowToStopIt - FROM #PerfmonStats ps - INNER JOIN #PerfmonStats psComp ON psComp.Pass = 2 AND psComp.object_name LIKE '%XTP Transactions' AND psComp.counter_name = 'Transactions aborted/sec' AND psComp.value_delta > 100 - WHERE ps.Pass = 2 - AND ps.object_name LIKE '%XTP Transactions' - AND ps.counter_name = 'Transactions aborted/sec' - AND ps.value_delta > (10 * @Seconds); /* Ignore servers sitting idle */ - /* Query Problems - Suboptimal Plans/Sec High - CheckID 33 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 33',10,1) WITH NOWAIT; - END +RAISERROR (N'Checking partition counts to exclude databases with over 100 partitions',0,1) WITH NOWAIT; +IF @BringThePain = 0 AND @SkipPartitions = 0 AND @TableName IS NULL + BEGIN + DECLARE partition_cursor CURSOR FOR + SELECT dl.DatabaseName + FROM #DatabaseList dl + LEFT OUTER JOIN #Ignore_Databases i ON dl.DatabaseName = i.DatabaseName + WHERE COALESCE(dl.secondary_role_allow_connections_desc, 'OK') <> 'NO' + AND i.DatabaseName IS NULL - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) - SELECT 32 AS CheckID, - 100 AS Priority, - 'Query Problems' AS FindingGroup, - 'Suboptimal Plans/Sec High' AS Finding, - 'https://BrentOzar.com/go/suboptimal/' AS URL, - CAST(ps.value_delta AS NVARCHAR(50)) + ' plans reported in the ' + CAST(ps.instance_name AS NVARCHAR(100)) + ' workload group (from Workload GroupStats:Suboptimal plans/sec counter)' + @LineFeed - + 'Even if you are not using Resource Governor, it still tracks information about user queries, memory grants, etc.' AS Details, - 'Check out sp_BlitzCache to get more information about recent queries, or try sp_BlitzWho to see currently running queries.' AS HowToStopIt - FROM #PerfmonStats ps - INNER JOIN #PerfmonStats psComp ON psComp.Pass = 2 AND psComp.object_name = @ServiceName + ':Workload GroupStats' AND psComp.counter_name = 'Suboptimal plans/sec' AND psComp.value_delta > 100 - WHERE ps.Pass = 2 - AND ps.object_name = @ServiceName + ':Workload GroupStats' - AND ps.counter_name = 'Suboptimal plans/sec' - AND ps.value_delta > (10 * @Seconds); /* Ignore servers sitting idle */ + OPEN partition_cursor + FETCH NEXT FROM partition_cursor INTO @DatabaseName + + WHILE @@FETCH_STATUS = 0 + BEGIN + /* Count the total number of partitions */ + SET @dsql = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + SELECT @RowcountOUT = SUM(1) FROM ' + QUOTENAME(@DatabaseName) + '.sys.partitions WHERE partition_number > 1 OPTION ( RECOMPILE );'; + EXEC sp_executesql @dsql, N'@RowcountOUT BIGINT OUTPUT', @RowcountOUT = @Rowcount OUTPUT; + IF @Rowcount > 100 + BEGIN + RAISERROR (N'Skipping database %s because > 100 partitions were found. To check this database, you must set @BringThePain = 1.',0,1,@DatabaseName) WITH NOWAIT; + INSERT INTO #Ignore_Databases (DatabaseName, Reason) + SELECT @DatabaseName, 'Over 100 partitions found - use @BringThePain = 1 to analyze' + END; + FETCH NEXT FROM partition_cursor INTO @DatabaseName + END; + CLOSE partition_cursor + DEALLOCATE partition_cursor - /* Azure Performance - Database is Maxed Out - CheckID 41 */ - IF SERVERPROPERTY('Edition') = 'SQL Azure' - BEGIN - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 41',10,1) WITH NOWAIT; - END + END; - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) - SELECT 41 AS CheckID, - 10 AS Priority, - 'Azure Performance' AS FindingGroup, - 'Database is Maxed Out' AS Finding, - 'https://BrentOzar.com/go/maxedout' AS URL, - N'At ' + CONVERT(NVARCHAR(100), s.end_time ,121) + N', your database approached (or hit) your DTU limits:' + @LineFeed - + N'Average CPU percent: ' + CAST(avg_cpu_percent AS NVARCHAR(50)) + @LineFeed - + N'Average data IO percent: ' + CAST(avg_data_io_percent AS NVARCHAR(50)) + @LineFeed - + N'Average log write percent: ' + CAST(avg_log_write_percent AS NVARCHAR(50)) + @LineFeed - + N'Max worker percent: ' + CAST(max_worker_percent AS NVARCHAR(50)) + @LineFeed - + N'Max session percent: ' + CAST(max_session_percent AS NVARCHAR(50)) AS Details, - 'Tune your queries or indexes with sp_BlitzCache or sp_BlitzIndex, or consider upgrading to a higher DTU level.' AS HowToStopIt - FROM sys.dm_db_resource_stats s - WHERE s.end_time >= DATEADD(MI, -5, GETDATE()) - AND (avg_cpu_percent > 90 - OR avg_data_io_percent >= 90 - OR avg_log_write_percent >=90 - OR max_worker_percent >= 90 - OR max_session_percent >= 90); - END +INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, URL, details, index_definition, + index_usage_summary, index_size_summary ) +SELECT 1, 0 , + 'Database Skipped', + i.DatabaseName, + 'http://FirstResponderKit.org', + i.Reason, '', '', '' +FROM #Ignore_Databases i; - /* Server Info - Batch Requests per Sec - CheckID 19 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 19',10,1) WITH NOWAIT; - END - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, DetailsInt) - SELECT 19 AS CheckID, - 250 AS Priority, - 'Server Info' AS FindingGroup, - 'Batch Requests per Sec' AS Finding, - 'http://www.BrentOzar.com/go/measure' AS URL, - CAST(CAST(ps.value_delta AS MONEY) / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS NVARCHAR(20)) AS Details, - ps.value_delta / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS DetailsInt - FROM #PerfmonStats ps - INNER JOIN #PerfmonStats ps1 ON ps.object_name = ps1.object_name AND ps.counter_name = ps1.counter_name AND ps1.Pass = 1 - WHERE ps.Pass = 2 - AND ps.object_name = @ServiceName + ':SQL Statistics' - AND ps.counter_name = 'Batch Requests/sec'; +/* Last startup */ +SELECT @DaysUptime = CAST(DATEDIFF(HOUR, create_date, GETDATE()) / 24. AS NUMERIC (23,2)) +FROM sys.databases +WHERE database_id = 2; +IF @DaysUptime = 0 OR @DaysUptime IS NULL + SET @DaysUptime = .01; - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','SQL Compilations/sec', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','SQL Re-Compilations/sec', NULL); +SELECT @DaysUptimeInsertValue = 'Server: ' + (CONVERT(VARCHAR(256), (SERVERPROPERTY('ServerName')))) + ' Days Uptime: ' + RTRIM(@DaysUptime); - /* Server Info - SQL Compilations/sec - CheckID 25 */ - IF @ExpertMode = 1 - BEGIN - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 25',10,1) WITH NOWAIT; - END - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, DetailsInt) - SELECT 25 AS CheckID, - 250 AS Priority, - 'Server Info' AS FindingGroup, - 'SQL Compilations per Sec' AS Finding, - 'http://www.BrentOzar.com/go/measure' AS URL, - CAST(ps.value_delta / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS NVARCHAR(20)) AS Details, - ps.value_delta / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS DetailsInt - FROM #PerfmonStats ps - INNER JOIN #PerfmonStats ps1 ON ps.object_name = ps1.object_name AND ps.counter_name = ps1.counter_name AND ps1.Pass = 1 - WHERE ps.Pass = 2 - AND ps.object_name = @ServiceName + ':SQL Statistics' - AND ps.counter_name = 'SQL Compilations/sec'; - END +/* Permission granted or unnecessary? Ok, let's go! */ - /* Server Info - SQL Re-Compilations/sec - CheckID 26 */ - IF @ExpertMode = 1 - BEGIN - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 26',10,1) WITH NOWAIT; - END +RAISERROR (N'Starting loop through databases',0,1) WITH NOWAIT; +DECLARE c1 CURSOR +LOCAL FAST_FORWARD +FOR +SELECT dl.DatabaseName +FROM #DatabaseList dl +LEFT OUTER JOIN #Ignore_Databases i ON dl.DatabaseName = i.DatabaseName +WHERE COALESCE(dl.secondary_role_allow_connections_desc, 'OK') <> 'NO' + AND i.DatabaseName IS NULL +ORDER BY dl.DatabaseName; - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, DetailsInt) - SELECT 26 AS CheckID, - 250 AS Priority, - 'Server Info' AS FindingGroup, - 'SQL Re-Compilations per Sec' AS Finding, - 'http://www.BrentOzar.com/go/measure' AS URL, - CAST(ps.value_delta / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS NVARCHAR(20)) AS Details, - ps.value_delta / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS DetailsInt - FROM #PerfmonStats ps - INNER JOIN #PerfmonStats ps1 ON ps.object_name = ps1.object_name AND ps.counter_name = ps1.counter_name AND ps1.Pass = 1 - WHERE ps.Pass = 2 - AND ps.object_name = @ServiceName + ':SQL Statistics' - AND ps.counter_name = 'SQL Re-Compilations/sec'; - END +OPEN c1; +FETCH NEXT FROM c1 INTO @DatabaseName; + WHILE @@FETCH_STATUS = 0 - /* Server Info - Wait Time per Core per Sec - CheckID 20 */ - IF @Seconds > 0 - BEGIN; - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 20',10,1) WITH NOWAIT; - END; +BEGIN + + RAISERROR (@LineFeed, 0, 1) WITH NOWAIT; + RAISERROR (@LineFeed, 0, 1) WITH NOWAIT; + RAISERROR (@DatabaseName, 0, 1) WITH NOWAIT; - WITH waits1(SampleTime, waits_ms) AS (SELECT SampleTime, SUM(ws1.wait_time_ms) FROM #WaitStats ws1 WHERE ws1.Pass = 1 GROUP BY SampleTime), - waits2(SampleTime, waits_ms) AS (SELECT SampleTime, SUM(ws2.wait_time_ms) FROM #WaitStats ws2 WHERE ws2.Pass = 2 GROUP BY SampleTime), - cores(cpu_count) AS (SELECT SUM(1) FROM sys.dm_os_schedulers WHERE status = 'VISIBLE ONLINE' AND is_online = 1) - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, DetailsInt) - SELECT 20 AS CheckID, - 250 AS Priority, - 'Server Info' AS FindingGroup, - 'Wait Time per Core per Sec' AS Finding, - 'http://www.BrentOzar.com/go/measure' AS URL, - CAST((CAST(waits2.waits_ms - waits1.waits_ms AS MONEY)) / 1000 / i.cpu_count / DATEDIFF(ss, waits1.SampleTime, waits2.SampleTime) AS NVARCHAR(20)) AS Details, - (waits2.waits_ms - waits1.waits_ms) / 1000 / i.cpu_count / DATEDIFF(ss, waits1.SampleTime, waits2.SampleTime) AS DetailsInt - FROM cores i - CROSS JOIN waits1 - CROSS JOIN waits2; - END; +SELECT @DatabaseID = [database_id] +FROM sys.databases + WHERE [name] = @DatabaseName + AND user_access_desc='MULTI_USER' + AND state_desc = 'ONLINE'; - /* If we're waiting 30+ seconds, run these checks at the end. - We get this data from the ring buffers, and it's only updated once per minute, so might - as well get it now - whereas if we're checking 30+ seconds, it might get updated by the - end of our sp_BlitzFirst session. */ - IF @Seconds >= 30 +---------------------------------------- +--STEP 1: OBSERVE THE PATIENT +--This step puts index information into temp tables. +---------------------------------------- +BEGIN TRY BEGIN - /* Server Performance - High CPU Utilization CheckID 24 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 24',10,1) WITH NOWAIT; - END - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) - SELECT 24, 50, 'Server Performance', 'High CPU Utilization', CAST(100 - SystemIdle AS NVARCHAR(20)) + N'%. Ring buffer details: ' + CAST(record AS NVARCHAR(4000)), 100 - SystemIdle, 'http://www.BrentOzar.com/go/cpu' - FROM ( - SELECT record, - record.value('(./Record/SchedulerMonitorEvent/SystemHealth/SystemIdle)[1]', 'int') AS SystemIdle - FROM ( - SELECT TOP 1 CONVERT(XML, record) AS record - FROM sys.dm_os_ring_buffers - WHERE ring_buffer_type = N'RING_BUFFER_SCHEDULER_MONITOR' - AND record LIKE '%%' - ORDER BY timestamp DESC) AS rb - ) AS y - WHERE 100 - SystemIdle >= 50; - - /* Server Performance - CPU Utilization CheckID 23 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 23',10,1) WITH NOWAIT; - END + --Validate SQL Server Version - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) - SELECT 23, 250, 'Server Info', 'CPU Utilization', CAST(100 - SystemIdle AS NVARCHAR(20)) + N'%. Ring buffer details: ' + CAST(record AS NVARCHAR(4000)), 100 - SystemIdle, 'http://www.BrentOzar.com/go/cpu' - FROM ( - SELECT record, - record.value('(./Record/SchedulerMonitorEvent/SystemHealth/SystemIdle)[1]', 'int') AS SystemIdle - FROM ( - SELECT TOP 1 CONVERT(XML, record) AS record - FROM sys.dm_os_ring_buffers - WHERE ring_buffer_type = N'RING_BUFFER_SCHEDULER_MONITOR' - AND record LIKE '%%' - ORDER BY timestamp DESC) AS rb - ) AS y; + IF (SELECT LEFT(@SQLServerProductVersion, + CHARINDEX('.',@SQLServerProductVersion,0)-1 + )) <= 9 + BEGIN + SET @msg=N'sp_BlitzIndex is only supported on SQL Server 2008 and higher. The version of this instance is: ' + @SQLServerProductVersion; + RAISERROR(@msg,16,1); + END; - END; /* IF @Seconds >= 30 */ + --Short circuit here if database name does not exist. + IF @DatabaseName IS NULL OR @DatabaseID IS NULL + BEGIN + SET @msg='Database does not exist or is not online/multi-user: cannot proceed.'; + RAISERROR(@msg,16,1); + END; + --Validate parameters. + IF (@Mode NOT IN (0,1,2,3,4)) + BEGIN + SET @msg=N'Invalid @Mode parameter. 0=diagnose, 1=summarize, 2=index detail, 3=missing index detail, 4=diagnose detail'; + RAISERROR(@msg,16,1); + END; - /* If we didn't find anything, apologize. */ - IF NOT EXISTS (SELECT * FROM #BlitzFirstResults WHERE Priority < 250) - BEGIN + IF (@Mode <> 0 AND @TableName IS NOT NULL) + BEGIN + SET @msg=N'Setting the @Mode doesn''t change behavior if you supply @TableName. Use default @Mode=0 to see table detail.'; + RAISERROR(@msg,16,1); + END; - INSERT INTO #BlitzFirstResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - VALUES ( -1 , - 1 , - 'No Problems Found' , - 'From Your Community Volunteers' , - 'http://FirstResponderKit.org/' , - 'Try running our more in-depth checks with sp_Blitz, or there may not be an unusual SQL Server performance problem. ' - ); + IF ((@Mode <> 0 OR @TableName IS NOT NULL) AND @Filter <> 0) + BEGIN + SET @msg=N'@Filter only applies when @Mode=0 and @TableName is not specified. Please try again.'; + RAISERROR(@msg,16,1); + END; - END; /*IF NOT EXISTS (SELECT * FROM #BlitzFirstResults) */ + IF (@SchemaName IS NOT NULL AND @TableName IS NULL) + BEGIN + SET @msg='We can''t run against a whole schema! Specify a @TableName, or leave both NULL for diagnosis.'; + RAISERROR(@msg,16,1); + END; - /* Add credits for the nice folks who put so much time into building and maintaining this for free: */ - INSERT INTO #BlitzFirstResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - VALUES ( -1 , - 255 , - 'Thanks!' , - 'From Your Community Volunteers' , - 'http://FirstResponderKit.org/' , - 'To get help or add your own contributions, join us at http://FirstResponderKit.org.' - ); - INSERT INTO #BlitzFirstResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details + IF (@TableName IS NOT NULL AND @SchemaName IS NULL) + BEGIN + SET @SchemaName=N'dbo'; + SET @msg='@SchemaName wasn''t specified-- assuming schema=dbo.'; + RAISERROR(@msg,1,1) WITH NOWAIT; + END; - ) - VALUES ( -1 , - 0 , - 'sp_BlitzFirst ' + CAST(CONVERT(DATETIMEOFFSET, @VersionDate, 102) AS VARCHAR(100)), - 'From Your Community Volunteers' , - 'http://FirstResponderKit.org/' , - 'We hope you found this tool useful.' - ); + --If a table is specified, grab the object id. + --Short circuit if it doesn't exist. + IF @TableName IS NOT NULL + BEGIN + SET @dsql = N' + SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + SELECT @ObjectID= OBJECT_ID + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.objects AS so + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS sc on + so.schema_id=sc.schema_id + where so.type in (''U'', ''V'') + and so.name=' + QUOTENAME(@TableName,'''')+ N' + and sc.name=' + QUOTENAME(@SchemaName,'''')+ N' + /*Has a row in sys.indexes. This lets us get indexed views.*/ + and exists ( + SELECT si.name + FROM ' + QUOTENAME(@DatabaseName) + '.sys.indexes AS si + WHERE so.object_id=si.object_id) + OPTION (RECOMPILE);'; - /* Outdated sp_BlitzFirst - sp_BlitzFirst is Over 6 Months Old - CheckID 27 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 27',10,1) WITH NOWAIT; - END + SET @params='@ObjectID INT OUTPUT'; - IF DATEDIFF(MM, @VersionDate, SYSDATETIMEOFFSET()) > 6 + IF @dsql IS NULL + RAISERROR('@dsql is null',16,1); + + EXEC sp_executesql @dsql, @params, @ObjectID=@ObjectID OUTPUT; + + IF @ObjectID IS NULL BEGIN - INSERT INTO #BlitzFirstResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 27 AS CheckID , - 0 AS Priority , - 'Outdated sp_BlitzFirst' AS FindingsGroup , - 'sp_BlitzFirst is Over 6 Months Old' AS Finding , - 'http://FirstResponderKit.org/' AS URL , - 'Some things get better with age, like fine wine and your T-SQL. However, sp_BlitzFirst is not one of those things - time to go download the current one.' AS Details; + SET @msg=N'Oh, this is awkward. I can''t find the table or indexed view you''re looking for in that database.' + CHAR(10) + + N'Please check your parameters.'; + RAISERROR(@msg,1,1); + RETURN; END; + END; - IF @CheckServerInfo = 0 /* Github #1680 */ - BEGIN - DELETE #BlitzFirstResults - WHERE FindingsGroup = 'Server Info'; - END - - RAISERROR('Analysis finished, outputting results',10,1) WITH NOWAIT; + --set @collation + SELECT @collation=collation_name + FROM sys.databases + WHERE database_id=@DatabaseID; + --insert columns for clustered indexes and heaps + --collect info on identity columns for this one + SET @dsql = N'/* sp_BlitzIndex */ + SET LOCK_TIMEOUT 1000; /* To fix locking bug in sys.identity_columns. See Github issue #2176. */ + SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + SELECT ' + CAST(@DatabaseID AS NVARCHAR(16)) + ', + s.name, + si.object_id, + si.index_id, + sc.key_ordinal, + sc.is_included_column, + sc.is_descending_key, + sc.partition_ordinal, + c.name as column_name, + st.name as system_type_name, + c.max_length, + c.[precision], + c.[scale], + c.collation_name, + c.is_nullable, + c.is_identity, + c.is_computed, + c.is_replicated, + ' + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N'c.is_sparse' ELSE N'NULL as is_sparse' END + N', + ' + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N'c.is_filestream' ELSE N'NULL as is_filestream' END + N', + CAST(ic.seed_value AS DECIMAL(38,0)), + CAST(ic.increment_value AS DECIMAL(38,0)), + CAST(ic.last_value AS DECIMAL(38,0)), + ic.is_not_for_replication + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.indexes si + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns c ON + si.object_id=c.object_id + LEFT JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.index_columns sc ON + sc.object_id = si.object_id + and sc.index_id=si.index_id + AND sc.column_id=c.column_id + LEFT JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.identity_columns ic ON + c.object_id=ic.object_id and + c.column_id=ic.column_id + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.types st ON + c.system_type_id=st.system_type_id + AND c.user_type_id=st.user_type_id + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects AS so ON si.object_id = so.object_id + AND so.is_ms_shipped = 0 + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s ON s.schema_id = so.schema_id + WHERE si.index_id in (0,1) ' + + CASE WHEN @ObjectID IS NOT NULL + THEN N' AND si.object_id=' + CAST(@ObjectID AS NVARCHAR(30)) + ELSE N'' END + + N'OPTION (RECOMPILE);'; - /* If they want to run sp_BlitzCache and export to table, go for it. */ - IF @OutputTableNameBlitzCache IS NOT NULL - AND @OutputDatabaseName IS NOT NULL - AND @OutputSchemaName IS NOT NULL - AND EXISTS ( SELECT * - FROM sys.databases - WHERE QUOTENAME([name]) = @OutputDatabaseName) - BEGIN + IF @dsql IS NULL + RAISERROR('@dsql is null',16,1); + RAISERROR (N'Inserting data into #IndexColumns for clustered indexes and heaps',0,1) WITH NOWAIT; + IF @Debug = 1 + BEGIN + PRINT SUBSTRING(@dsql, 0, 4000); + PRINT SUBSTRING(@dsql, 4000, 8000); + PRINT SUBSTRING(@dsql, 8000, 12000); + PRINT SUBSTRING(@dsql, 12000, 16000); + PRINT SUBSTRING(@dsql, 16000, 20000); + PRINT SUBSTRING(@dsql, 20000, 24000); + PRINT SUBSTRING(@dsql, 24000, 28000); + PRINT SUBSTRING(@dsql, 28000, 32000); + PRINT SUBSTRING(@dsql, 32000, 36000); + PRINT SUBSTRING(@dsql, 36000, 40000); + END; + BEGIN TRY + INSERT #IndexColumns ( database_id, [schema_name], [object_id], index_id, key_ordinal, is_included_column, is_descending_key, partition_ordinal, + column_name, system_type_name, max_length, precision, scale, collation_name, is_nullable, is_identity, is_computed, + is_replicated, is_sparse, is_filestream, seed_value, increment_value, last_value, is_not_for_replication ) + EXEC sp_executesql @dsql; + END TRY + BEGIN CATCH + RAISERROR (N'Failure inserting data into #IndexColumns for clustered indexes and heaps.', 0,1) WITH NOWAIT; - RAISERROR('Calling sp_BlitzCache',10,1) WITH NOWAIT; + IF @dsql IS NOT NULL + BEGIN + SET @msg= 'Last @dsql: ' + @dsql; + RAISERROR(@msg, 0, 1) WITH NOWAIT; + END; + SELECT @msg = @DatabaseName + N' database failed to process. ' + ERROR_MESSAGE(), + @ErrorSeverity = 0, @ErrorState = ERROR_STATE(); + RAISERROR (@msg,@ErrorSeverity, @ErrorState )WITH NOWAIT; - /* If they have an newer version of sp_BlitzCache that supports @MinutesBack and @CheckDateOverride */ - IF EXISTS (SELECT * FROM sys.objects o - INNER JOIN sys.parameters pMB ON o.object_id = pMB.object_id AND pMB.name = '@MinutesBack' - INNER JOIN sys.parameters pCDO ON o.object_id = pCDO.object_id AND pCDO.name = '@CheckDateOverride' - WHERE o.name = 'sp_BlitzCache') - BEGIN - /* Get the most recent sp_BlitzCache execution before this one - don't use sp_BlitzFirst because user logs are added in there at any time */ - SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''' - + @OutputSchemaName + ''' AND QUOTENAME(TABLE_NAME) = ''' - + QUOTENAME(@OutputTableNameBlitzCache) + ''') SELECT TOP 1 @BlitzCacheMinutesBack = DATEDIFF(MI,CheckDate,SYSDATETIMEOFFSET()) FROM ' - + @OutputDatabaseName + '.' - + @OutputSchemaName + '.' - + QUOTENAME(@OutputTableNameBlitzCache) - + ' WHERE ServerName = ''' + CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128)) + ''' ORDER BY CheckDate DESC;'; - EXEC sp_executesql @StringToExecute, N'@BlitzCacheMinutesBack INT OUTPUT', @BlitzCacheMinutesBack OUTPUT; + WHILE @@trancount > 0 + ROLLBACK; - /* If there's no data, let's just analyze the last 15 minutes of the plan cache */ - IF @BlitzCacheMinutesBack IS NULL OR @BlitzCacheMinutesBack < 1 OR @BlitzCacheMinutesBack > 60 - SET @BlitzCacheMinutesBack = 15; + RETURN; + END CATCH; - EXEC sp_BlitzCache - @OutputDatabaseName = @UnquotedOutputDatabaseName, - @OutputSchemaName = @UnquotedOutputSchemaName, - @OutputTableName = @OutputTableNameBlitzCache, - @CheckDateOverride = @StartSampleTime, - @SortOrder = 'all', - @SkipAnalysis = @BlitzCacheSkipAnalysis, - @MinutesBack = @BlitzCacheMinutesBack, - @Debug = @Debug; - /* Delete history older than @OutputTableRetentionDays */ - SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' - + @OutputSchemaName + ''') DELETE ' - + @OutputDatabaseName + '.' - + @OutputSchemaName + '.' - + QUOTENAME(@OutputTableNameBlitzCache) - + ' WHERE ServerName = @SrvName AND CheckDate < @CheckDate;'; - EXEC sp_executesql @StringToExecute, - N'@SrvName NVARCHAR(128), @CheckDate date', - @LocalServerName, @OutputTableCleanupDate; + --insert columns for nonclustered indexes + --this uses a full join to sys.index_columns + --We don't collect info on identity columns here. They may be in NC indexes, but we just analyze identities in the base table. + SET @dsql = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + SELECT ' + CAST(@DatabaseID AS NVARCHAR(16)) + ', + s.name, + si.object_id, + si.index_id, + sc.key_ordinal, + sc.is_included_column, + sc.is_descending_key, + sc.partition_ordinal, + c.name as column_name, + st.name as system_type_name, + c.max_length, + c.[precision], + c.[scale], + c.collation_name, + c.is_nullable, + c.is_identity, + c.is_computed, + c.is_replicated, + ' + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N'c.is_sparse' ELSE N'NULL AS is_sparse' END + N', + ' + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N'c.is_filestream' ELSE N'NULL AS is_filestream' END + N' + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.indexes AS si + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns AS c ON + si.object_id=c.object_id + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.index_columns AS sc ON + sc.object_id = si.object_id + and sc.index_id=si.index_id + AND sc.column_id=c.column_id + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.types AS st ON + c.system_type_id=st.system_type_id + AND c.user_type_id=st.user_type_id + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects AS so ON si.object_id = so.object_id + AND so.is_ms_shipped = 0 + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s ON s.schema_id = so.schema_id + WHERE si.index_id not in (0,1) ' + + CASE WHEN @ObjectID IS NOT NULL + THEN N' AND si.object_id=' + CAST(@ObjectID AS NVARCHAR(30)) + ELSE N'' END + + N'OPTION (RECOMPILE);'; + IF @dsql IS NULL + RAISERROR('@dsql is null',16,1); + RAISERROR (N'Inserting data into #IndexColumns for nonclustered indexes',0,1) WITH NOWAIT; + IF @Debug = 1 + BEGIN + PRINT SUBSTRING(@dsql, 0, 4000); + PRINT SUBSTRING(@dsql, 4000, 8000); + PRINT SUBSTRING(@dsql, 8000, 12000); + PRINT SUBSTRING(@dsql, 12000, 16000); + PRINT SUBSTRING(@dsql, 16000, 20000); + PRINT SUBSTRING(@dsql, 20000, 24000); + PRINT SUBSTRING(@dsql, 24000, 28000); + PRINT SUBSTRING(@dsql, 28000, 32000); + PRINT SUBSTRING(@dsql, 32000, 36000); + PRINT SUBSTRING(@dsql, 36000, 40000); END; + INSERT #IndexColumns ( database_id, [schema_name], [object_id], index_id, key_ordinal, is_included_column, is_descending_key, partition_ordinal, + column_name, system_type_name, max_length, precision, scale, collation_name, is_nullable, is_identity, is_computed, + is_replicated, is_sparse, is_filestream ) + EXEC sp_executesql @dsql; + + SET @dsql = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + SELECT ' + CAST(@DatabaseID AS NVARCHAR(10)) + N' AS database_id, + so.object_id, + si.index_id, + si.type, + @i_DatabaseName AS database_name, + COALESCE(sc.NAME, ''Unknown'') AS [schema_name], + COALESCE(so.name, ''Unknown'') AS [object_name], + COALESCE(si.name, ''Unknown'') AS [index_name], + CASE WHEN so.[type] = CAST(''V'' AS CHAR(2)) THEN 1 ELSE 0 END, + si.is_unique, + si.is_primary_key, + CASE when si.type = 3 THEN 1 ELSE 0 END AS is_XML, + CASE when si.type = 4 THEN 1 ELSE 0 END AS is_spatial, + CASE when si.type = 6 THEN 1 ELSE 0 END AS is_NC_columnstore, + CASE when si.type = 5 then 1 else 0 end as is_CX_columnstore, + CASE when si.data_space_id = 0 then 1 else 0 end as is_in_memory_oltp, + si.is_disabled, + si.is_hypothetical, + si.is_padded, + si.fill_factor,' + + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N' + CASE WHEN si.filter_definition IS NOT NULL THEN si.filter_definition + ELSE N'''' + END AS filter_definition' ELSE N''''' AS filter_definition' END + N' + , ISNULL(us.user_seeks, 0), + ISNULL(us.user_scans, 0), + ISNULL(us.user_lookups, 0), + ISNULL(us.user_updates, 0), + us.last_user_seek, + us.last_user_scan, + us.last_user_lookup, + us.last_user_update, + so.create_date, + so.modify_date + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.indexes AS si WITH (NOLOCK) + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects AS so WITH (NOLOCK) ON si.object_id = so.object_id + AND so.is_ms_shipped = 0 /*Exclude objects shipped by Microsoft*/ + AND so.type <> ''TF'' /*Exclude table valued functions*/ + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas sc ON so.schema_id = sc.schema_id + LEFT JOIN sys.dm_db_index_usage_stats AS us WITH (NOLOCK) ON si.[object_id] = us.[object_id] + AND si.index_id = us.index_id + AND us.database_id = ' + CAST(@DatabaseID AS NVARCHAR(10)) + N' + WHERE si.[type] IN ( 0, 1, 2, 3, 4, 5, 6 ) + /* Heaps, clustered, nonclustered, XML, spatial, Cluster Columnstore, NC Columnstore */ ' + + CASE WHEN @TableName IS NOT NULL THEN N' and so.name=' + QUOTENAME(@TableName,N'''') + N' ' ELSE N'' END + + CASE WHEN ( @IncludeInactiveIndexes = 0 + AND @Mode IN (0, 4) + AND @TableName IS NULL ) + THEN N'AND ( us.user_seeks + us.user_scans + us.user_lookups + us.user_updates ) > 0' + ELSE N'' + END + + N'OPTION ( RECOMPILE ); + '; + IF @dsql IS NULL + RAISERROR('@dsql is null',16,1); - ELSE + RAISERROR (N'Inserting data into #IndexSanity',0,1) WITH NOWAIT; + IF @Debug = 1 BEGIN - /* No sp_BlitzCache found, or it's outdated - CheckID 36 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 36',10,1) WITH NOWAIT; - END - - INSERT INTO #BlitzFirstResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 36 AS CheckID , - 0 AS Priority , - 'Outdated or Missing sp_BlitzCache' AS FindingsGroup , - 'Update Your sp_BlitzCache' AS Finding , - 'http://FirstResponderKit.org/' AS URL , - 'You passed in @OutputTableNameBlitzCache, but we need a newer version of sp_BlitzCache in master or the current database.' AS Details; + PRINT SUBSTRING(@dsql, 0, 4000); + PRINT SUBSTRING(@dsql, 4000, 8000); + PRINT SUBSTRING(@dsql, 8000, 12000); + PRINT SUBSTRING(@dsql, 12000, 16000); + PRINT SUBSTRING(@dsql, 16000, 20000); + PRINT SUBSTRING(@dsql, 20000, 24000); + PRINT SUBSTRING(@dsql, 24000, 28000); + PRINT SUBSTRING(@dsql, 28000, 32000); + PRINT SUBSTRING(@dsql, 32000, 36000); + PRINT SUBSTRING(@dsql, 36000, 40000); END; + INSERT #IndexSanity ( [database_id], [object_id], [index_id], [index_type], [database_name], [schema_name], [object_name], + index_name, is_indexed_view, is_unique, is_primary_key, is_XML, is_spatial, is_NC_columnstore, is_CX_columnstore, is_in_memory_oltp, + is_disabled, is_hypothetical, is_padded, fill_factor, filter_definition, user_seeks, user_scans, + user_lookups, user_updates, last_user_seek, last_user_scan, last_user_lookup, last_user_update, + create_date, modify_date ) + EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; - RAISERROR('sp_BlitzCache Finished',10,1) WITH NOWAIT; - - END; /* End running sp_BlitzCache */ - /* @OutputTableName lets us export the results to a permanent table */ - IF @OutputDatabaseName IS NOT NULL - AND @OutputSchemaName IS NOT NULL - AND @OutputTableName IS NOT NULL - AND @OutputTableName NOT LIKE '#%' - AND EXISTS ( SELECT * - FROM sys.databases - WHERE QUOTENAME([name]) = @OutputDatabaseName) - BEGIN - SET @StringToExecute = 'USE ' - + @OutputDatabaseName - + '; IF EXISTS(SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' - + @OutputSchemaName - + ''') AND NOT EXISTS (SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''' - + @OutputSchemaName + ''' AND QUOTENAME(TABLE_NAME) = ''' - + @OutputTableName + ''') CREATE TABLE ' - + @OutputSchemaName + '.' - + @OutputTableName - + ' (ID INT IDENTITY(1,1) NOT NULL, - ServerName NVARCHAR(128), - CheckDate DATETIMEOFFSET, - CheckID INT NOT NULL, - Priority TINYINT NOT NULL, - FindingsGroup VARCHAR(50) NOT NULL, - Finding VARCHAR(200) NOT NULL, - URL VARCHAR(200) NOT NULL, - Details NVARCHAR(4000) NULL, - HowToStopIt [XML] NULL, - QueryPlan [XML] NULL, - QueryText NVARCHAR(MAX) NULL, - StartTime DATETIMEOFFSET NULL, - LoginName NVARCHAR(128) NULL, - NTUserName NVARCHAR(128) NULL, - OriginalLoginName NVARCHAR(128) NULL, - ProgramName NVARCHAR(128) NULL, - HostName NVARCHAR(128) NULL, - DatabaseID INT NULL, - DatabaseName NVARCHAR(128) NULL, - OpenTransactionCount INT NULL, - DetailsInt INT NULL, - QueryHash BINARY(8) NULL, - JoinKey AS ServerName + CAST(CheckDate AS NVARCHAR(50)), - PRIMARY KEY CLUSTERED (ID ASC));'; + RAISERROR (N'Checking partition count',0,1) WITH NOWAIT; + IF @BringThePain = 0 AND @SkipPartitions = 0 AND @TableName IS NULL + BEGIN + /* Count the total number of partitions */ + SET @dsql = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + SELECT @RowcountOUT = SUM(1) FROM ' + QUOTENAME(@DatabaseName) + '.sys.partitions WHERE partition_number > 1 OPTION ( RECOMPILE );'; + EXEC sp_executesql @dsql, N'@RowcountOUT BIGINT OUTPUT', @RowcountOUT = @Rowcount OUTPUT; + IF @Rowcount > 100 + BEGIN + RAISERROR (N'Setting @SkipPartitions = 1 because > 100 partitions were found. To check them, you must set @BringThePain = 1.',0,1) WITH NOWAIT; + SET @SkipPartitions = 1; + INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, URL, details, index_definition, + index_usage_summary, index_size_summary ) + VALUES ( 1, 0 , + 'Some Checks Were Skipped', + '@SkipPartitions Forced to 1', + 'http://FirstResponderKit.org', CAST(@Rowcount AS NVARCHAR(50)) + ' partitions found. To analyze them, use @BringThePain = 1.', 'We try to keep things quick - and warning, running @BringThePain = 1 can take tens of minutes.', '', '' + ); + END; + END; - EXEC(@StringToExecute); - /* If the table doesn't have the new QueryHash column, add it. See Github #2162. */ - SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableName; - SET @StringToExecute = N'IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns - WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''QueryHash'') - ALTER TABLE ' + @ObjectFullName + N' ADD QueryHash BINARY(8) NULL;'; - EXEC(@StringToExecute); - /* If the table doesn't have the new JoinKey computed column, add it. See Github #2164. */ - SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableName; - SET @StringToExecute = N'IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns - WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''JoinKey'') - ALTER TABLE ' + @ObjectFullName + N' ADD JoinKey AS ServerName + CAST(CheckDate AS NVARCHAR(50));'; - EXEC(@StringToExecute); + IF (@SkipPartitions = 0) + BEGIN + IF (SELECT LEFT(@SQLServerProductVersion, + CHARINDEX('.',@SQLServerProductVersion,0)-1 )) <= 2147483647 --Make change here + BEGIN + + RAISERROR (N'Preferring non-2012 syntax with LEFT JOIN to sys.dm_db_index_operational_stats',0,1) WITH NOWAIT; - SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' - + @OutputSchemaName + ''') INSERT ' - + @OutputDatabaseName + '.' - + @OutputSchemaName + '.' - + @OutputTableName - + ' (ServerName, CheckDate, CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, QueryText, StartTime, LoginName, NTUserName, OriginalLoginName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, DetailsInt, QueryHash) SELECT ' - + ' @SrvName, @CheckDate, CheckID, Priority, FindingsGroup, Finding, URL, LEFT(Details,4000), HowToStopIt, QueryPlan, QueryText, StartTime, LoginName, NTUserName, OriginalLoginName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, DetailsInt, QueryHash FROM #BlitzFirstResults ORDER BY Priority , FindingsGroup , Finding , Details'; - - EXEC sp_executesql @StringToExecute, - N'@SrvName NVARCHAR(128), @CheckDate datetimeoffset', - @LocalServerName, @StartSampleTime; + --NOTE: If you want to use the newer syntax for 2012+, you'll have to change 2147483647 to 11 on line ~819 + --This change was made because on a table with lots of paritions, the OUTER APPLY was crazy slow. + SET @dsql = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + SELECT ' + CAST(@DatabaseID AS NVARCHAR(10)) + N' AS database_id, + ps.object_id, + s.name, + ps.index_id, + ps.partition_number, + ps.row_count, + ps.reserved_page_count * 8. / 1024. AS reserved_MB, + ps.lob_reserved_page_count * 8. / 1024. AS reserved_LOB_MB, + ps.row_overflow_reserved_page_count * 8. / 1024. AS reserved_row_overflow_MB, + le.lock_escalation_desc, + ' + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N'par.data_compression_desc ' ELSE N'null as data_compression_desc ' END + N', + SUM(os.leaf_insert_count), + SUM(os.leaf_delete_count), + SUM(os.leaf_update_count), + SUM(os.range_scan_count), + SUM(os.singleton_lookup_count), + SUM(os.forwarded_fetch_count), + SUM(os.lob_fetch_in_pages), + SUM(os.lob_fetch_in_bytes), + SUM(os.row_overflow_fetch_in_pages), + SUM(os.row_overflow_fetch_in_bytes), + SUM(os.row_lock_count), + SUM(os.row_lock_wait_count), + SUM(os.row_lock_wait_in_ms), + SUM(os.page_lock_count), + SUM(os.page_lock_wait_count), + SUM(os.page_lock_wait_in_ms), + SUM(os.index_lock_promotion_attempt_count), + SUM(os.index_lock_promotion_count), + SUM(os.page_latch_wait_count), + SUM(os.page_latch_wait_in_ms), + SUM(os.page_io_latch_wait_count), + SUM(os.page_io_latch_wait_in_ms), '; - /* Delete history older than @OutputTableRetentionDays */ - SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' - + @OutputSchemaName + ''') DELETE ' - + @OutputDatabaseName + '.' - + @OutputSchemaName + '.' - + @OutputTableName - + ' WHERE ServerName = @SrvName AND CheckDate < @CheckDate ;'; - - EXEC sp_executesql @StringToExecute, - N'@SrvName NVARCHAR(128), @CheckDate date', - @LocalServerName, @OutputTableCleanupDate; + /* Get columnstore dictionary size - more info: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2585 */ + IF EXISTS (SELECT * FROM sys.all_objects WHERE name = 'column_store_dictionaries') + SET @dsql = @dsql + N' COALESCE((SELECT SUM (on_disk_size / 1024.0 / 1024) FROM ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_dictionaries dict WHERE dict.partition_id = ps.partition_id),0) AS reserved_dictionary_MB '; + ELSE + SET @dsql = @dsql + N' 0 AS reserved_dictionary_MB '; - END; - ELSE IF (SUBSTRING(@OutputTableName, 2, 2) = '##') - BEGIN - SET @StringToExecute = N' IF (OBJECT_ID(''tempdb..' - + @OutputTableName - + ''') IS NULL) CREATE TABLE ' - + @OutputTableName - + ' (ID INT IDENTITY(1,1) NOT NULL, - ServerName NVARCHAR(128), - CheckDate DATETIMEOFFSET, - CheckID INT NOT NULL, - Priority TINYINT NOT NULL, - FindingsGroup VARCHAR(50) NOT NULL, - Finding VARCHAR(200) NOT NULL, - URL VARCHAR(200) NOT NULL, - Details NVARCHAR(4000) NULL, - HowToStopIt [XML] NULL, - QueryPlan [XML] NULL, - QueryText NVARCHAR(MAX) NULL, - StartTime DATETIMEOFFSET NULL, - LoginName NVARCHAR(128) NULL, - NTUserName NVARCHAR(128) NULL, - OriginalLoginName NVARCHAR(128) NULL, - ProgramName NVARCHAR(128) NULL, - HostName NVARCHAR(128) NULL, - DatabaseID INT NULL, - DatabaseName NVARCHAR(128) NULL, - OpenTransactionCount INT NULL, - DetailsInt INT NULL, - QueryHash BINARY(8) NULL, - JoinKey AS ServerName + CAST(CheckDate AS NVARCHAR(50)), - PRIMARY KEY CLUSTERED (ID ASC));' - + ' INSERT ' - + @OutputTableName - + ' (ServerName, CheckDate, CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, QueryText, StartTime, LoginName, NTUserName, OriginalLoginName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, DetailsInt) SELECT ' - + ' @SrvName, @CheckDate, CheckID, Priority, FindingsGroup, Finding, URL, LEFT(Details,4000), HowToStopIt, QueryPlan, QueryText, StartTime, LoginName, NTUserName, OriginalLoginName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, DetailsInt FROM #BlitzFirstResults ORDER BY Priority , FindingsGroup , Finding , Details'; - - EXEC sp_executesql @StringToExecute, - N'@SrvName NVARCHAR(128), @CheckDate datetimeoffset', - @LocalServerName, @StartSampleTime; - END; - ELSE IF (SUBSTRING(@OutputTableName, 2, 1) = '#') - BEGIN - RAISERROR('Due to the nature of Dymamic SQL, only global (i.e. double pound (##)) temp tables are supported for @OutputTableName', 16, 0); - END; - /* @OutputTableNameFileStats lets us export the results to a permanent table */ - IF @OutputDatabaseName IS NOT NULL - AND @OutputSchemaName IS NOT NULL - AND @OutputTableNameFileStats IS NOT NULL - AND @OutputTableNameFileStats NOT LIKE '#%' - AND EXISTS ( SELECT * - FROM sys.databases - WHERE QUOTENAME([name]) = @OutputDatabaseName) - BEGIN - /* Create the table */ - SET @StringToExecute = 'USE ' - + @OutputDatabaseName - + '; IF EXISTS(SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' - + @OutputSchemaName - + ''') AND NOT EXISTS (SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''' - + @OutputSchemaName + ''' AND QUOTENAME(TABLE_NAME) = ''' - + @OutputTableNameFileStats + ''') CREATE TABLE ' - + @OutputSchemaName + '.' - + @OutputTableNameFileStats - + ' (ID INT IDENTITY(1,1) NOT NULL, - ServerName NVARCHAR(128), - CheckDate DATETIMEOFFSET, - DatabaseID INT NOT NULL, - FileID INT NOT NULL, - DatabaseName NVARCHAR(256) , - FileLogicalName NVARCHAR(256) , - TypeDesc NVARCHAR(60) , - SizeOnDiskMB BIGINT , - io_stall_read_ms BIGINT , - num_of_reads BIGINT , - bytes_read BIGINT , - io_stall_write_ms BIGINT , - num_of_writes BIGINT , - bytes_written BIGINT, - PhysicalName NVARCHAR(520) , - PRIMARY KEY CLUSTERED (ID ASC));'; + SET @dsql = @dsql + N' + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_partition_stats AS ps + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partitions AS par on ps.partition_id=par.partition_id + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects AS so ON ps.object_id = so.object_id + AND so.is_ms_shipped = 0 /*Exclude objects shipped by Microsoft*/ + AND so.type <> ''TF'' /*Exclude table valued functions*/ + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s ON s.schema_id = so.schema_id + LEFT JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_index_operational_stats(' + + CAST(@DatabaseID AS NVARCHAR(10)) + N', NULL, NULL,NULL) AS os ON + ps.object_id=os.object_id and ps.index_id=os.index_id and ps.partition_number=os.partition_number + OUTER APPLY (SELECT st.lock_escalation_desc + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.tables st + WHERE st.object_id = ps.object_id + AND ps.index_id < 2 ) le + WHERE 1=1 + ' + CASE WHEN @ObjectID IS NOT NULL THEN N'AND so.object_id=' + CAST(@ObjectID AS NVARCHAR(30)) + N' ' ELSE N' ' END + N' + ' + CASE WHEN @Filter = 2 THEN N'AND ps.reserved_page_count * 8./1024. > ' + CAST(@FilterMB AS NVARCHAR(5)) + N' ' ELSE N' ' END + N' + GROUP BY ps.object_id, + s.name, + ps.index_id, + ps.partition_number, + ps.partition_id, + ps.row_count, + ps.reserved_page_count, + ps.lob_reserved_page_count, + ps.row_overflow_reserved_page_count, + le.lock_escalation_desc, + ' + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N'par.data_compression_desc ' ELSE N'null as data_compression_desc ' END + N' + ORDER BY ps.object_id, ps.index_id, ps.partition_number + OPTION ( RECOMPILE ); + '; + END; + ELSE + BEGIN + RAISERROR (N'Using 2012 syntax to query sys.dm_db_index_operational_stats',0,1) WITH NOWAIT; + --This is the syntax that will be used if you change 2147483647 to 11 on line ~819. + --If you have a lot of paritions and this suddenly starts running for a long time, change it back. + SET @dsql = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + SELECT ' + CAST(@DatabaseID AS NVARCHAR(10)) + N' AS database_id, + ps.object_id, + s.name, + ps.index_id, + ps.partition_number, + ps.row_count, + ps.reserved_page_count * 8. / 1024. AS reserved_MB, + ps.lob_reserved_page_count * 8. / 1024. AS reserved_LOB_MB, + ps.row_overflow_reserved_page_count * 8. / 1024. AS reserved_row_overflow_MB, + le.lock_escalation_desc, + ' + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N'par.data_compression_desc ' ELSE N'null as data_compression_desc' END + N', + SUM(os.leaf_insert_count), + SUM(os.leaf_delete_count), + SUM(os.leaf_update_count), + SUM(os.range_scan_count), + SUM(os.singleton_lookup_count), + SUM(os.forwarded_fetch_count), + SUM(os.lob_fetch_in_pages), + SUM(os.lob_fetch_in_bytes), + SUM(os.row_overflow_fetch_in_pages), + SUM(os.row_overflow_fetch_in_bytes), + SUM(os.row_lock_count), + SUM(os.row_lock_wait_count), + SUM(os.row_lock_wait_in_ms), + SUM(os.page_lock_count), + SUM(os.page_lock_wait_count), + SUM(os.page_lock_wait_in_ms), + SUM(os.index_lock_promotion_attempt_count), + SUM(os.index_lock_promotion_count), + SUM(os.page_latch_wait_count), + SUM(os.page_latch_wait_in_ms), + SUM(os.page_io_latch_wait_count), + SUM(os.page_io_latch_wait_in_ms)'; - EXEC(@StringToExecute); + /* Get columnstore dictionary size - more info: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2585 */ + IF EXISTS (SELECT * FROM sys.all_objects WHERE name = 'column_store_dictionaries') + SET @dsql = @dsql + N' COALESCE((SELECT SUM (on_disk_size / 1024.0 / 1024) FROM ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_dictionaries dict WHERE dict.partition_id = ps.partition_id),0) AS reserved_dictionary_MB '; + ELSE + SET @dsql = @dsql + N' 0 AS reserved_dictionary_MB '; - SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableNameFileStats_View; - /* If the view exists without the most recently added columns, drop it. See Github #2162. */ - IF OBJECT_ID(@ObjectFullName) IS NOT NULL - BEGIN - SET @StringToExecute = N'USE ' + @OutputDatabaseName + N'; IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns - WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''JoinKey'') - DROP VIEW ' + @OutputSchemaName + N'.' + @OutputTableNameFileStats_View + N';'; + SET @dsql = @dsql + N' + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_partition_stats AS ps + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partitions AS par on ps.partition_id=par.partition_id + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects AS so ON ps.object_id = so.object_id + AND so.is_ms_shipped = 0 /*Exclude objects shipped by Microsoft*/ + AND so.type <> ''TF'' /*Exclude table valued functions*/ + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s ON s.schema_id = so.schema_id + OUTER APPLY ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_index_operational_stats(' + + CAST(@DatabaseID AS NVARCHAR(10)) + N', ps.object_id, ps.index_id,ps.partition_number) AS os + OUTER APPLY (SELECT st.lock_escalation_desc + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.tables st + WHERE st.object_id = ps.object_id + AND ps.index_id < 2 ) le + WHERE 1=1 + ' + CASE WHEN @ObjectID IS NOT NULL THEN N'AND so.object_id=' + CAST(@ObjectID AS NVARCHAR(30)) + N' ' ELSE N' ' END + N' + ' + CASE WHEN @Filter = 2 THEN N'AND ps.reserved_page_count * 8./1024. > ' + CAST(@FilterMB AS NVARCHAR(5)) + N' ' ELSE N' ' END + ' + GROUP BY ps.object_id, + s.name, + ps.index_id, + ps.partition_number, + ps.partition_id, + ps.row_count, + ps.reserved_page_count, + ps.lob_reserved_page_count, + ps.row_overflow_reserved_page_count, + le.lock_escalation_desc, + ' + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N'par.data_compression_desc ' ELSE N'null as data_compression_desc ' END + N' + ORDER BY ps.object_id, ps.index_id, ps.partition_number + OPTION ( RECOMPILE ); + '; + END; - EXEC(@StringToExecute); - END + IF @dsql IS NULL + RAISERROR('@dsql is null',16,1); - /* Create the view */ - IF OBJECT_ID(@ObjectFullName) IS NULL + RAISERROR (N'Inserting data into #IndexPartitionSanity',0,1) WITH NOWAIT; + IF @Debug = 1 BEGIN - SET @StringToExecute = 'USE ' - + @OutputDatabaseName - + '; EXEC (''CREATE VIEW ' - + @OutputSchemaName + '.' - + @OutputTableNameFileStats_View + ' AS ' + @LineFeed - + 'WITH RowDates as' + @LineFeed - + '(' + @LineFeed - + ' SELECT ' + @LineFeed - + ' ROW_NUMBER() OVER (ORDER BY [ServerName], [CheckDate]) ID,' + @LineFeed - + ' [CheckDate]' + @LineFeed - + ' FROM ' + @OutputSchemaName + '.' + @OutputTableNameFileStats + '' + @LineFeed - + ' GROUP BY [ServerName], [CheckDate]' + @LineFeed - + '),' + @LineFeed - + 'CheckDates as' + @LineFeed - + '(' + @LineFeed - + ' SELECT ThisDate.CheckDate,' + @LineFeed - + ' LastDate.CheckDate as PreviousCheckDate' + @LineFeed - + ' FROM RowDates ThisDate' + @LineFeed - + ' JOIN RowDates LastDate' + @LineFeed - + ' ON ThisDate.ID = LastDate.ID + 1' + @LineFeed - + ')' + @LineFeed - + ' SELECT f.ServerName,' + @LineFeed - + ' f.CheckDate,' + @LineFeed - + ' f.DatabaseID,' + @LineFeed - + ' f.DatabaseName,' + @LineFeed - + ' f.FileID,' + @LineFeed - + ' f.FileLogicalName,' + @LineFeed - + ' f.TypeDesc,' + @LineFeed - + ' f.PhysicalName,' + @LineFeed - + ' f.SizeOnDiskMB,' + @LineFeed - + ' DATEDIFF(ss, fPrior.CheckDate, f.CheckDate) AS ElapsedSeconds,' + @LineFeed - + ' (f.SizeOnDiskMB - fPrior.SizeOnDiskMB) AS SizeOnDiskMBgrowth,' + @LineFeed - + ' (f.io_stall_read_ms - fPrior.io_stall_read_ms) AS io_stall_read_ms,' + @LineFeed - + ' io_stall_read_ms_average = CASE' + @LineFeed - + ' WHEN(f.num_of_reads - fPrior.num_of_reads) = 0' + @LineFeed - + ' THEN 0' + @LineFeed - + ' ELSE(f.io_stall_read_ms - fPrior.io_stall_read_ms) / (f.num_of_reads - fPrior.num_of_reads)' + @LineFeed - + ' END,' + @LineFeed - + ' (f.num_of_reads - fPrior.num_of_reads) AS num_of_reads,' + @LineFeed - + ' (f.bytes_read - fPrior.bytes_read) / 1024.0 / 1024.0 AS megabytes_read,' + @LineFeed - + ' (f.io_stall_write_ms - fPrior.io_stall_write_ms) AS io_stall_write_ms,' + @LineFeed - + ' io_stall_write_ms_average = CASE' + @LineFeed - + ' WHEN(f.num_of_writes - fPrior.num_of_writes) = 0' + @LineFeed - + ' THEN 0' + @LineFeed - + ' ELSE(f.io_stall_write_ms - fPrior.io_stall_write_ms) / (f.num_of_writes - fPrior.num_of_writes)' + @LineFeed - + ' END,' + @LineFeed - + ' (f.num_of_writes - fPrior.num_of_writes) AS num_of_writes,' + @LineFeed - + ' (f.bytes_written - fPrior.bytes_written) / 1024.0 / 1024.0 AS megabytes_written, ' + @LineFeed - + ' f.ServerName + CAST(f.CheckDate AS NVARCHAR(50)) AS JoinKey' + @LineFeed - + ' FROM ' + @OutputSchemaName + '.' + @OutputTableNameFileStats + ' f' + @LineFeed - + ' INNER HASH JOIN CheckDates DATES ON f.CheckDate = DATES.CheckDate' + @LineFeed - + ' INNER JOIN ' + @OutputSchemaName + '.' + @OutputTableNameFileStats + ' fPrior ON f.ServerName = fPrior.ServerName' + @LineFeed - + ' AND f.DatabaseID = fPrior.DatabaseID' + @LineFeed - + ' AND f.FileID = fPrior.FileID' + @LineFeed - + ' AND fPrior.CheckDate = DATES.PreviousCheckDate' + @LineFeed - + '' + @LineFeed - + ' WHERE f.num_of_reads >= fPrior.num_of_reads' + @LineFeed - + ' AND f.num_of_writes >= fPrior.num_of_writes' + @LineFeed - + ' AND DATEDIFF(MI, fPrior.CheckDate, f.CheckDate) BETWEEN 1 AND 60;'')' - - EXEC(@StringToExecute); + PRINT SUBSTRING(@dsql, 0, 4000); + PRINT SUBSTRING(@dsql, 4000, 8000); + PRINT SUBSTRING(@dsql, 8000, 12000); + PRINT SUBSTRING(@dsql, 12000, 16000); + PRINT SUBSTRING(@dsql, 16000, 20000); + PRINT SUBSTRING(@dsql, 20000, 24000); + PRINT SUBSTRING(@dsql, 24000, 28000); + PRINT SUBSTRING(@dsql, 28000, 32000); + PRINT SUBSTRING(@dsql, 32000, 36000); + PRINT SUBSTRING(@dsql, 36000, 40000); END; + INSERT #IndexPartitionSanity ( [database_id], + [object_id], + [schema_name], + index_id, + partition_number, + row_count, + reserved_MB, + reserved_LOB_MB, + reserved_row_overflow_MB, + lock_escalation_desc, + data_compression_desc, + leaf_insert_count, + leaf_delete_count, + leaf_update_count, + range_scan_count, + singleton_lookup_count, + forwarded_fetch_count, + lob_fetch_in_pages, + lob_fetch_in_bytes, + row_overflow_fetch_in_pages, + row_overflow_fetch_in_bytes, + row_lock_count, + row_lock_wait_count, + row_lock_wait_in_ms, + page_lock_count, + page_lock_wait_count, + page_lock_wait_in_ms, + index_lock_promotion_attempt_count, + index_lock_promotion_count, + page_latch_wait_count, + page_latch_wait_in_ms, + page_io_latch_wait_count, + page_io_latch_wait_in_ms, + reserved_dictionary_MB) + EXEC sp_executesql @dsql; + + END; --End Check For @SkipPartitions = 0 - SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' - + @OutputSchemaName + ''') INSERT ' - + @OutputDatabaseName + '.' - + @OutputSchemaName + '.' - + @OutputTableNameFileStats - + ' (ServerName, CheckDate, DatabaseID, FileID, DatabaseName, FileLogicalName, TypeDesc, SizeOnDiskMB, io_stall_read_ms, num_of_reads, bytes_read, io_stall_write_ms, num_of_writes, bytes_written, PhysicalName) SELECT ' - + ' @SrvName, @CheckDate, DatabaseID, FileID, DatabaseName, FileLogicalName, TypeDesc, SizeOnDiskMB, io_stall_read_ms, num_of_reads, bytes_read, io_stall_write_ms, num_of_writes, bytes_written, PhysicalName FROM #FileStats WHERE Pass = 2'; - - EXEC sp_executesql @StringToExecute, - N'@SrvName NVARCHAR(128), @CheckDate datetimeoffset', - @LocalServerName, @StartSampleTime; - - /* Delete history older than @OutputTableRetentionDays */ - SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' - + @OutputSchemaName + ''') DELETE ' - + @OutputDatabaseName + '.' - + @OutputSchemaName + '.' - + @OutputTableNameFileStats - + ' WHERE ServerName = @SrvName AND CheckDate < @CheckDate ;'; - - EXEC sp_executesql @StringToExecute, - N'@SrvName NVARCHAR(128), @CheckDate date', - @LocalServerName, @OutputTableCleanupDate; - - END; - ELSE IF (SUBSTRING(@OutputTableNameFileStats, 2, 2) = '##') - BEGIN - SET @StringToExecute = N' IF (OBJECT_ID(''tempdb..' - + @OutputTableNameFileStats - + ''') IS NULL) CREATE TABLE ' - + @OutputTableNameFileStats - + ' (ID INT IDENTITY(1,1) NOT NULL, - ServerName NVARCHAR(128), - CheckDate DATETIMEOFFSET, - DatabaseID INT NOT NULL, - FileID INT NOT NULL, - DatabaseName NVARCHAR(256) , - FileLogicalName NVARCHAR(256) , - TypeDesc NVARCHAR(60) , - SizeOnDiskMB BIGINT , - io_stall_read_ms BIGINT , - num_of_reads BIGINT , - bytes_read BIGINT , - io_stall_write_ms BIGINT , - num_of_writes BIGINT , - bytes_written BIGINT, - PhysicalName NVARCHAR(520) , - DetailsInt INT NULL, - PRIMARY KEY CLUSTERED (ID ASC));' - + ' INSERT ' - + @OutputTableNameFileStats - + ' (ServerName, CheckDate, DatabaseID, FileID, DatabaseName, FileLogicalName, TypeDesc, SizeOnDiskMB, io_stall_read_ms, num_of_reads, bytes_read, io_stall_write_ms, num_of_writes, bytes_written, PhysicalName) SELECT ' - + ' @SrvName, @CheckDate, DatabaseID, FileID, DatabaseName, FileLogicalName, TypeDesc, SizeOnDiskMB, io_stall_read_ms, num_of_reads, bytes_read, io_stall_write_ms, num_of_writes, bytes_written, PhysicalName FROM #FileStats WHERE Pass = 2'; - EXEC sp_executesql @StringToExecute, - N'@SrvName NVARCHAR(128), @CheckDate datetimeoffset', - @LocalServerName, @StartSampleTime; - END; - ELSE IF (SUBSTRING(@OutputTableNameFileStats, 2, 1) = '#') - BEGIN - RAISERROR('Due to the nature of Dymamic SQL, only global (i.e. double pound (##)) temp tables are supported for @OutputTableName', 16, 0); - END; + RAISERROR (N'Inserting data into #MissingIndexes',0,1) WITH NOWAIT; + SET @dsql=N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' - /* @OutputTableNamePerfmonStats lets us export the results to a permanent table */ - IF @OutputDatabaseName IS NOT NULL - AND @OutputSchemaName IS NOT NULL - AND @OutputTableNamePerfmonStats IS NOT NULL - AND @OutputTableNamePerfmonStats NOT LIKE '#%' - AND EXISTS ( SELECT * - FROM sys.databases - WHERE QUOTENAME([name]) = @OutputDatabaseName) - BEGIN - /* Create the table */ - SET @StringToExecute = 'USE ' - + @OutputDatabaseName - + '; IF EXISTS(SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' - + @OutputSchemaName - + ''') AND NOT EXISTS (SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''' - + @OutputSchemaName + ''' AND QUOTENAME(TABLE_NAME) = ''' - + @OutputTableNamePerfmonStats + ''') CREATE TABLE ' - + @OutputSchemaName + '.' - + @OutputTableNamePerfmonStats - + ' (ID INT IDENTITY(1,1) NOT NULL, - ServerName NVARCHAR(128), - CheckDate DATETIMEOFFSET, - [object_name] NVARCHAR(128) NOT NULL, - [counter_name] NVARCHAR(128) NOT NULL, - [instance_name] NVARCHAR(128) NULL, - [cntr_value] BIGINT NULL, - [cntr_type] INT NOT NULL, - [value_delta] BIGINT NULL, - [value_per_second] DECIMAL(18,2) NULL, - PRIMARY KEY CLUSTERED (ID ASC));'; + SET @dsql = @dsql + 'WITH ColumnNamesWithDataTypes AS(SELECT id.index_handle,id.object_id,cn.IndexColumnType,STUFF((SELECT '', '' + cn_inner.ColumnName + '' '' + + N'' {'' + CASE WHEN ty.name IN ( ''varchar'', ''char'' ) THEN ty.name + ''('' + CASE WHEN co.max_length = -1 THEN ''max'' ELSE CAST(co.max_length AS VARCHAR(25)) END + '')'' + WHEN ty.name IN ( ''nvarchar'', ''nchar'' ) THEN ty.name + ''('' + CASE WHEN co.max_length = -1 THEN ''max'' ELSE CAST(co.max_length / 2 AS VARCHAR(25)) END + '')'' + WHEN ty.name IN ( ''decimal'', ''numeric'' ) THEN ty.name + ''('' + CAST(co.precision AS VARCHAR(25)) + '', '' + CAST(co.scale AS VARCHAR(25)) + '')'' + WHEN ty.name IN ( ''datetime2'' ) THEN ty.name + ''('' + CAST(co.scale AS VARCHAR(25)) + '')'' + ELSE ty.name END + ''}'' + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_details AS id_inner + CROSS APPLY( + SELECT LTRIM(RTRIM(v.value(''(./text())[1]'', ''varchar(max)''))) AS ColumnName, ''Equality'' AS IndexColumnType + FROM (VALUES (CONVERT(XML, N'''' + REPLACE((SELECT CAST(id_inner.equality_columns AS nvarchar(max)) FOR XML PATH('''')), N'','', N'''') + N''''))) x(n) + CROSS APPLY n.nodes(''x'') node(v) + UNION ALL + SELECT LTRIM(RTRIM(v.value(N''(./text())[1]'', ''varchar(max)''))) AS ColumnName, ''Inequality'' AS IndexColumnType + FROM (VALUES (CONVERT(XML, N'''' + REPLACE((SELECT CAST(id_inner.inequality_columns AS nvarchar(max)) FOR XML PATH('''')), N'','', N'''') + N''''))) x(n) + CROSS APPLY n.nodes(''x'') node(v) + UNION ALL + SELECT LTRIM(RTRIM(v.value(''(./text())[1]'', ''varchar(max)''))) AS ColumnName, ''Included'' AS IndexColumnType + FROM (VALUES (CONVERT(XML, N'''' + REPLACE((SELECT CAST(id_inner.included_columns AS nvarchar(max)) FOR XML PATH('''')), N'','', N'''') + N''''))) x(n) + CROSS APPLY n.nodes(''x'') node(v) + )AS cn_inner' + + /*split the string otherwise dsql cuts some of it out*/ + ' JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns AS co ON co.object_id = id_inner.object_id AND ''['' + co.name + '']'' = cn_inner.ColumnName + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.types AS ty ON ty.user_type_id = co.user_type_id + WHERE id_inner.index_handle = id.index_handle + AND id_inner.object_id = id.object_id + AND cn_inner.IndexColumnType = cn.IndexColumnType + FOR XML PATH('''') + ),1,1,'''') AS ReplaceColumnNames + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_details AS id + CROSS APPLY( + SELECT LTRIM(RTRIM(v.value(''(./text())[1]'', ''varchar(max)''))) AS ColumnName, ''Equality'' AS IndexColumnType + FROM (VALUES (CONVERT(XML, N'''' + REPLACE((SELECT CAST(id.equality_columns AS nvarchar(max)) FOR XML PATH('''')), N'','', N'''') + N''''))) x(n) + CROSS APPLY n.nodes(''x'') node(v) + UNION ALL + SELECT LTRIM(RTRIM(v.value(''(./text())[1]'', ''varchar(max)''))) AS ColumnName, ''Inequality'' AS IndexColumnType + FROM (VALUES (CONVERT(XML, N'''' + REPLACE((SELECT CAST(id.inequality_columns AS nvarchar(max)) FOR XML PATH('''')), N'','', N'''') + N''''))) x(n) + CROSS APPLY n.nodes(''x'') node(v) + UNION ALL + SELECT LTRIM(RTRIM(v.value(''(./text())[1]'', ''varchar(max)''))) AS ColumnName, ''Included'' AS IndexColumnType + FROM (VALUES (CONVERT(XML, N'''' + REPLACE((SELECT CAST(id.included_columns AS nvarchar(max)) FOR XML PATH('''')), N'','', N'''') + N''''))) x(n) + CROSS APPLY n.nodes(''x'') node(v) + )AS cn + GROUP BY id.index_handle,id.object_id,cn.IndexColumnType + ) + SELECT id.database_id, id.object_id, @i_DatabaseName, sc.[name], so.[name], id.statement , gs.avg_total_user_cost, + gs.avg_user_impact, gs.user_seeks, gs.user_scans, gs.unique_compiles, id.equality_columns, id.inequality_columns, id.included_columns, + ( + SELECT ColumnNamesWithDataTypes.ReplaceColumnNames + FROM ColumnNamesWithDataTypes WHERE ColumnNamesWithDataTypes.index_handle = id.index_handle + AND ColumnNamesWithDataTypes.object_id = id.object_id + AND ColumnNamesWithDataTypes.IndexColumnType = ''Equality'' + ) AS equality_columns_with_data_type + ,( + SELECT ColumnNamesWithDataTypes.ReplaceColumnNames + FROM ColumnNamesWithDataTypes WHERE ColumnNamesWithDataTypes.index_handle = id.index_handle + AND ColumnNamesWithDataTypes.object_id = id.object_id + AND ColumnNamesWithDataTypes.IndexColumnType = ''Inequality'' + ) AS inequality_columns_with_data_type + ,( + SELECT ColumnNamesWithDataTypes.ReplaceColumnNames + FROM ColumnNamesWithDataTypes WHERE ColumnNamesWithDataTypes.index_handle = id.index_handle + AND ColumnNamesWithDataTypes.object_id = id.object_id + AND ColumnNamesWithDataTypes.IndexColumnType = ''Included'' + ) AS included_columns_with_data_type ' - EXEC(@StringToExecute); + /* Github #2780 BGO Removing SQL Server 2019's new missing index sample plan because it doesn't work yet: + IF NOT EXISTS (SELECT * FROM sys.all_objects WHERE name = 'dm_db_missing_index_group_stats_query') + */ + SET @dsql = @dsql + N' , NULL AS sample_query_plan ' + /* Github #2780 BGO Removing SQL Server 2019's new missing index sample plan because it doesn't work yet: + ELSE + BEGIN + SET @dsql = @dsql + N' , sample_query_plan = (SELECT TOP 1 p.query_plan + FROM sys.dm_db_missing_index_group_stats gs + CROSS APPLY (SELECT TOP 1 s.plan_handle + FROM sys.dm_db_missing_index_group_stats_query q + INNER JOIN sys.dm_exec_query_stats s ON q.query_plan_hash = s.query_plan_hash + WHERE gs.group_handle = q.group_handle + ORDER BY (q.user_seeks + q.user_scans) DESC, s.total_logical_reads DESC) q2 + CROSS APPLY sys.dm_exec_query_plan(q2.plan_handle) p + WHERE gs.group_handle = gs.group_handle) ' + END + */ + - SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableNamePerfmonStats_View; + SET @dsql = @dsql + N'FROM ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_groups ig + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_details id ON ig.index_handle = id.index_handle + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_group_stats gs ON ig.index_group_handle = gs.group_handle + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects so on + id.object_id=so.object_id + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas sc on + so.schema_id=sc.schema_id + WHERE id.database_id = ' + CAST(@DatabaseID AS NVARCHAR(30)) + ' + ' + CASE WHEN @ObjectID IS NULL THEN N'' + ELSE N'and id.object_id=' + CAST(@ObjectID AS NVARCHAR(30)) + END + + N'OPTION (RECOMPILE);'; - /* If the view exists without the most recently added columns, drop it. See Github #2162. */ - IF OBJECT_ID(@ObjectFullName) IS NOT NULL + IF @dsql IS NULL + RAISERROR('@dsql is null',16,1); + IF @Debug = 1 BEGIN - SET @StringToExecute = N'USE ' + @OutputDatabaseName + N'; IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns - WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''JoinKey'') - DROP VIEW ' + @OutputSchemaName + N'.' + @OutputTableNamePerfmonStats_View + N';'; + PRINT SUBSTRING(@dsql, 0, 4000); + PRINT SUBSTRING(@dsql, 4000, 8000); + PRINT SUBSTRING(@dsql, 8000, 12000); + PRINT SUBSTRING(@dsql, 12000, 16000); + PRINT SUBSTRING(@dsql, 16000, 20000); + PRINT SUBSTRING(@dsql, 20000, 24000); + PRINT SUBSTRING(@dsql, 24000, 28000); + PRINT SUBSTRING(@dsql, 28000, 32000); + PRINT SUBSTRING(@dsql, 32000, 36000); + PRINT SUBSTRING(@dsql, 36000, 40000); + END; + INSERT #MissingIndexes ( [database_id], [object_id], [database_name], [schema_name], [table_name], [statement], avg_total_user_cost, + avg_user_impact, user_seeks, user_scans, unique_compiles, equality_columns, + inequality_columns, included_columns, equality_columns_with_data_type, inequality_columns_with_data_type, + included_columns_with_data_type, sample_query_plan) + EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; - EXEC(@StringToExecute); - END + SET @dsql = N' + SELECT DB_ID(N' + QUOTENAME(@DatabaseName,'''') + N') AS [database_id], + @i_DatabaseName AS database_name, + s.name, + fk_object.name AS foreign_key_name, + parent_object.[object_id] AS parent_object_id, + parent_object.name AS parent_object_name, + referenced_object.[object_id] AS referenced_object_id, + referenced_object.name AS referenced_object_name, + fk.is_disabled, + fk.is_not_trusted, + fk.is_not_for_replication, + parent.fk_columns, + referenced.fk_columns, + [update_referential_action_desc], + [delete_referential_action_desc] + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.foreign_keys fk + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects fk_object ON fk.object_id=fk_object.object_id + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects parent_object ON fk.parent_object_id=parent_object.object_id + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects referenced_object ON fk.referenced_object_id=referenced_object.object_id + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s ON fk.schema_id=s.schema_id + CROSS APPLY ( SELECT STUFF( (SELECT N'', '' + c_parent.name AS fk_columns + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.foreign_key_columns fkc + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns c_parent ON fkc.parent_object_id=c_parent.[object_id] + AND fkc.parent_column_id=c_parent.column_id + WHERE fk.parent_object_id=fkc.parent_object_id + AND fk.[object_id]=fkc.constraint_object_id + ORDER BY fkc.constraint_column_id + FOR XML PATH('''') , + TYPE).value(''.'', ''nvarchar(max)''), 1, 1, '''')/*This is how we remove the first comma*/ ) parent ( fk_columns ) + CROSS APPLY ( SELECT STUFF( (SELECT N'', '' + c_referenced.name AS fk_columns + FROM ' + QUOTENAME(@DatabaseName) + N'.sys. foreign_key_columns fkc + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns c_referenced ON fkc.referenced_object_id=c_referenced.[object_id] + AND fkc.referenced_column_id=c_referenced.column_id + WHERE fk.referenced_object_id=fkc.referenced_object_id + and fk.[object_id]=fkc.constraint_object_id + ORDER BY fkc.constraint_column_id /*order by col name, we don''t have anything better*/ + FOR XML PATH('''') , + TYPE).value(''.'', ''nvarchar(max)''), 1, 1, '''') ) referenced ( fk_columns ) + ' + CASE WHEN @ObjectID IS NOT NULL THEN + 'WHERE fk.parent_object_id=' + CAST(@ObjectID AS NVARCHAR(30)) + N' OR fk.referenced_object_id=' + CAST(@ObjectID AS NVARCHAR(30)) + N' ' + ELSE N' ' END + ' + ORDER BY parent_object_name, foreign_key_name + OPTION (RECOMPILE);'; + IF @dsql IS NULL + RAISERROR('@dsql is null',16,1); - /* Create the view */ - IF OBJECT_ID(@ObjectFullName) IS NULL + RAISERROR (N'Inserting data into #ForeignKeys',0,1) WITH NOWAIT; + IF @Debug = 1 BEGIN - SET @StringToExecute = 'USE ' - + @OutputDatabaseName - + '; EXEC (''CREATE VIEW ' - + @OutputSchemaName + '.' - + @OutputTableNamePerfmonStats_View + ' AS ' + @LineFeed - + 'WITH RowDates as' + @LineFeed - + '(' + @LineFeed - + ' SELECT ' + @LineFeed - + ' ROW_NUMBER() OVER (ORDER BY [ServerName], [CheckDate]) ID,' + @LineFeed - + ' [CheckDate]' + @LineFeed - + ' FROM ' + @OutputSchemaName + '.' +@OutputTableNamePerfmonStats + '' + @LineFeed - + ' GROUP BY [ServerName], [CheckDate]' + @LineFeed - + '),' + @LineFeed - + 'CheckDates as' + @LineFeed - + '(' + @LineFeed - + ' SELECT ThisDate.CheckDate,' + @LineFeed - + ' LastDate.CheckDate as PreviousCheckDate' + @LineFeed - + ' FROM RowDates ThisDate' + @LineFeed - + ' JOIN RowDates LastDate' + @LineFeed - + ' ON ThisDate.ID = LastDate.ID + 1' + @LineFeed - + ')' + @LineFeed - + 'SELECT' + @LineFeed - + ' pMon.[ServerName]' + @LineFeed - + ' ,pMon.[CheckDate]' + @LineFeed - + ' ,pMon.[object_name]' + @LineFeed - + ' ,pMon.[counter_name]' + @LineFeed - + ' ,pMon.[instance_name]' + @LineFeed - + ' ,DATEDIFF(SECOND,pMonPrior.[CheckDate],pMon.[CheckDate]) AS ElapsedSeconds' + @LineFeed - + ' ,pMon.[cntr_value]' + @LineFeed - + ' ,pMon.[cntr_type]' + @LineFeed - + ' ,(pMon.[cntr_value] - pMonPrior.[cntr_value]) AS cntr_delta' + @LineFeed - + ' ,(pMon.cntr_value - pMonPrior.cntr_value) * 1.0 / DATEDIFF(ss, pMonPrior.CheckDate, pMon.CheckDate) AS cntr_delta_per_second' + @LineFeed - + ' ,pMon.ServerName + CAST(pMon.CheckDate AS NVARCHAR(50)) AS JoinKey' + @LineFeed - + ' FROM ' + @OutputSchemaName + '.' +@OutputTableNamePerfmonStats + ' pMon' + @LineFeed - + ' INNER HASH JOIN CheckDates Dates' + @LineFeed - + ' ON Dates.CheckDate = pMon.CheckDate' + @LineFeed - + ' JOIN ' + @OutputSchemaName + '.' +@OutputTableNamePerfmonStats + ' pMonPrior' + @LineFeed - + ' ON Dates.PreviousCheckDate = pMonPrior.CheckDate' + @LineFeed - + ' AND pMon.[ServerName] = pMonPrior.[ServerName] ' + @LineFeed - + ' AND pMon.[object_name] = pMonPrior.[object_name] ' + @LineFeed - + ' AND pMon.[counter_name] = pMonPrior.[counter_name] ' + @LineFeed - + ' AND pMon.[instance_name] = pMonPrior.[instance_name]' + @LineFeed - + ' WHERE DATEDIFF(MI, pMonPrior.CheckDate, pMon.CheckDate) BETWEEN 1 AND 60;'')' + PRINT SUBSTRING(@dsql, 0, 4000); + PRINT SUBSTRING(@dsql, 4000, 8000); + PRINT SUBSTRING(@dsql, 8000, 12000); + PRINT SUBSTRING(@dsql, 12000, 16000); + PRINT SUBSTRING(@dsql, 16000, 20000); + PRINT SUBSTRING(@dsql, 20000, 24000); + PRINT SUBSTRING(@dsql, 24000, 28000); + PRINT SUBSTRING(@dsql, 28000, 32000); + PRINT SUBSTRING(@dsql, 32000, 36000); + PRINT SUBSTRING(@dsql, 36000, 40000); + END; + INSERT #ForeignKeys ( [database_id], [database_name], [schema_name], foreign_key_name, parent_object_id,parent_object_name, referenced_object_id, referenced_object_name, + is_disabled, is_not_trusted, is_not_for_replication, parent_fk_columns, referenced_fk_columns, + [update_referential_action_desc], [delete_referential_action_desc] ) + EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; - EXEC(@StringToExecute); - END - SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableNamePerfmonStatsActuals_View; + IF @SkipStatistics = 0 /* AND DB_NAME() = @DatabaseName /* Can only get stats in the current database - see https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/1947 */ */ + BEGIN + IF ((PARSENAME(@SQLServerProductVersion, 4) >= 12) + OR (PARSENAME(@SQLServerProductVersion, 4) = 11 AND PARSENAME(@SQLServerProductVersion, 2) >= 3000) + OR (PARSENAME(@SQLServerProductVersion, 4) = 10 AND PARSENAME(@SQLServerProductVersion, 3) = 50 AND PARSENAME(@SQLServerProductVersion, 2) >= 2500)) + BEGIN + RAISERROR (N'Gathering Statistics Info With Newer Syntax.',0,1) WITH NOWAIT; + SET @dsql=N'USE ' + @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, + 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) + 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, + ISNULL(i.name, ''System Or User Statistic'') AS index_name, + ca.column_names AS column_names, + s.name AS statistics_name, + CONVERT(DATETIME, ddsp.last_updated) AS last_statistics_update, + DATEDIFF(DAY, ddsp.last_updated, GETDATE()) AS days_since_last_stats_update, + ddsp.rows, + ddsp.rows_sampled, + CAST(ddsp.rows_sampled / ( 1. * NULLIF(ddsp.rows, 0) ) * 100 AS DECIMAL(18, 1)) AS percent_sampled, + ddsp.steps AS histogram_steps, + ddsp.modification_counter, + CASE WHEN ddsp.modification_counter > 0 + THEN CAST(ddsp.modification_counter / ( 1. * NULLIF(ddsp.rows, 0) ) * 100 AS DECIMAL(18, 1)) + ELSE ddsp.modification_counter + END AS percent_modifications, + CASE WHEN ddsp.rows < 500 THEN 500 + ELSE CAST(( ddsp.rows * .20 ) + 500 AS INT) + 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 + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.stats AS s + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects obj + ON s.object_id = obj.object_id + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas sch + ON sch.schema_id = obj.schema_id + LEFT JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.indexes AS i + ON i.object_id = s.object_id + AND i.index_id = s.stats_id + OUTER APPLY ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_stats_properties(s.object_id, s.stats_id) AS ddsp + CROSS APPLY ( SELECT STUFF((SELECT '', '' + c.name + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.stats_columns AS sc + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns AS c + ON sc.column_id = c.column_id AND sc.object_id = c.object_id + WHERE sc.stats_id = s.stats_id AND sc.object_id = s.object_id + ORDER BY sc.stats_column_id + FOR XML PATH(''''), TYPE).value(''.'', ''nvarchar(max)''), 1, 2, '''') + ) ca (column_names) + WHERE obj.is_ms_shipped = 0 + OPTION (RECOMPILE);'; + + IF @dsql IS NULL + RAISERROR('@dsql is null',16,1); - /* If the view exists without the most recently added columns, drop it. See Github #2162. */ - IF OBJECT_ID(@ObjectFullName) IS NOT NULL - BEGIN - SET @StringToExecute = N'USE ' + @OutputDatabaseName + N'; IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns - WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''JoinKey'') - DROP VIEW ' + @OutputSchemaName + N'.' + @OutputTableNamePerfmonStatsActuals_View + N';'; + RAISERROR (N'Inserting data into #Statistics',0,1) WITH NOWAIT; + IF @Debug = 1 + BEGIN + PRINT SUBSTRING(@dsql, 0, 4000); + PRINT SUBSTRING(@dsql, 4000, 8000); + PRINT SUBSTRING(@dsql, 8000, 12000); + PRINT SUBSTRING(@dsql, 12000, 16000); + PRINT SUBSTRING(@dsql, 16000, 20000); + PRINT SUBSTRING(@dsql, 20000, 24000); + PRINT SUBSTRING(@dsql, 24000, 28000); + PRINT SUBSTRING(@dsql, 28000, 32000); + PRINT SUBSTRING(@dsql, 32000, 36000); + PRINT SUBSTRING(@dsql, 36000, 40000); + END; + + EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; + END; + ELSE + BEGIN + RAISERROR (N'Gathering Statistics Info With Older Syntax.',0,1) WITH NOWAIT; + SET @dsql=N'USE ' + @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, 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) + 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, + ISNULL(i.name, ''System Or User Statistic'') AS index_name, + ca.column_names AS column_names, + s.name AS statistics_name, + CONVERT(DATETIME, STATS_DATE(s.object_id, s.stats_id)) AS last_statistics_update, + DATEDIFF(DAY, STATS_DATE(s.object_id, s.stats_id), GETDATE()) AS days_since_last_stats_update, + si.rowcnt, + si.rowmodctr, + CASE WHEN si.rowmodctr > 0 THEN CAST(si.rowmodctr / ( 1. * NULLIF(si.rowcnt, 0) ) * 100 AS DECIMAL(18, 1)) + ELSE si.rowmodctr + END AS percent_modifications, + CASE WHEN si.rowcnt < 500 THEN 500 + ELSE CAST(( si.rowcnt * .20 ) + 500 AS INT) + 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, + ' + + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' + THEN N's.has_filter, + s.filter_definition' + ELSE N'NULL AS has_filter, + NULL AS filter_definition' END + + N' + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.stats AS s + INNER HASH JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.sysindexes si + ON si.name = s.name AND s.object_id = si.id + INNER HASH JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects obj + ON s.object_id = obj.object_id + INNER HASH JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas sch + ON sch.schema_id = obj.schema_id + LEFT HASH JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.indexes AS i + ON i.object_id = s.object_id + AND i.index_id = s.stats_id + CROSS APPLY ( SELECT STUFF((SELECT '', '' + c.name + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.stats_columns AS sc + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns AS c + ON sc.column_id = c.column_id AND sc.object_id = c.object_id + WHERE sc.stats_id = s.stats_id AND sc.object_id = s.object_id + ORDER BY sc.stats_column_id + FOR XML PATH(''''), TYPE).value(''.'', ''nvarchar(max)''), 1, 2, '''') + ) ca (column_names) + WHERE obj.is_ms_shipped = 0 + AND si.rowcnt > 0 + OPTION (RECOMPILE);'; - EXEC(@StringToExecute); - END + IF @dsql IS NULL + RAISERROR('@dsql is null',16,1); - /* Create the second view */ - IF OBJECT_ID(@ObjectFullName) IS NULL - BEGIN - SET @StringToExecute = 'USE ' - + @OutputDatabaseName - + '; EXEC (''CREATE VIEW ' - + @OutputSchemaName + '.' - + @OutputTableNamePerfmonStatsActuals_View + ' AS ' + @LineFeed - + 'WITH PERF_AVERAGE_BULK AS' + @LineFeed - + '(' + @LineFeed - + ' SELECT ServerName,' + @LineFeed - + ' object_name,' + @LineFeed - + ' instance_name,' + @LineFeed - + ' counter_name,' + @LineFeed - + ' CASE WHEN CHARINDEX(''''('''', counter_name) = 0 THEN counter_name ELSE LEFT (counter_name, CHARINDEX(''''('''',counter_name)-1) END AS counter_join,' + @LineFeed - + ' CheckDate,' + @LineFeed - + ' cntr_delta' + @LineFeed - + ' FROM ' + @OutputSchemaName + '.' + @OutputTableNamePerfmonStats_View + @LineFeed - + ' WHERE cntr_type IN(1073874176)' + @LineFeed - + ' AND cntr_delta <> 0' + @LineFeed - + '),' + @LineFeed - + 'PERF_LARGE_RAW_BASE AS' + @LineFeed - + '(' + @LineFeed - + ' SELECT ServerName,' + @LineFeed - + ' object_name,' + @LineFeed - + ' instance_name,' + @LineFeed - + ' LEFT(counter_name, CHARINDEX(''''BASE'''', UPPER(counter_name))-1) AS counter_join,' + @LineFeed - + ' CheckDate,' + @LineFeed - + ' cntr_delta' + @LineFeed - + ' FROM ' + @OutputSchemaName + '.' + @OutputTableNamePerfmonStats_View + '' + @LineFeed - + ' WHERE cntr_type IN(1073939712)' + @LineFeed - + ' AND cntr_delta <> 0' + @LineFeed - + '),' + @LineFeed - + 'PERF_AVERAGE_FRACTION AS' + @LineFeed - + '(' + @LineFeed - + ' SELECT ServerName,' + @LineFeed - + ' object_name,' + @LineFeed - + ' instance_name,' + @LineFeed - + ' counter_name,' + @LineFeed - + ' counter_name AS counter_join,' + @LineFeed - + ' CheckDate,' + @LineFeed - + ' cntr_delta' + @LineFeed - + ' FROM ' + @OutputSchemaName + '.' + @OutputTableNamePerfmonStats_View + '' + @LineFeed - + ' WHERE cntr_type IN(537003264)' + @LineFeed - + ' AND cntr_delta <> 0' + @LineFeed - + '),' + @LineFeed - + 'PERF_COUNTER_BULK_COUNT AS' + @LineFeed - + '(' + @LineFeed - + ' SELECT ServerName,' + @LineFeed - + ' object_name,' + @LineFeed - + ' instance_name,' + @LineFeed - + ' counter_name,' + @LineFeed - + ' CheckDate,' + @LineFeed - + ' cntr_delta / ElapsedSeconds AS cntr_value' + @LineFeed - + ' FROM ' + @OutputSchemaName + '.' + @OutputTableNamePerfmonStats_View + '' + @LineFeed - + ' WHERE cntr_type IN(272696576, 272696320)' + @LineFeed - + ' AND cntr_delta <> 0' + @LineFeed - + '),' + @LineFeed - + 'PERF_COUNTER_RAWCOUNT AS' + @LineFeed - + '(' + @LineFeed - + ' SELECT ServerName,' + @LineFeed - + ' object_name,' + @LineFeed - + ' instance_name,' + @LineFeed - + ' counter_name,' + @LineFeed - + ' CheckDate,' + @LineFeed - + ' cntr_value' + @LineFeed - + ' FROM ' + @OutputSchemaName + '.' + @OutputTableNamePerfmonStats_View + '' + @LineFeed - + ' WHERE cntr_type IN(65792, 65536)' + @LineFeed - + ')' + @LineFeed - + '' + @LineFeed - + 'SELECT NUM.ServerName,' + @LineFeed - + ' NUM.object_name,' + @LineFeed - + ' NUM.counter_name,' + @LineFeed - + ' NUM.instance_name,' + @LineFeed - + ' NUM.CheckDate,' + @LineFeed - + ' NUM.cntr_delta / DEN.cntr_delta AS cntr_value,' + @LineFeed - + ' NUM.ServerName + CAST(NUM.CheckDate AS NVARCHAR(50)) AS JoinKey' + @LineFeed - + ' ' + @LineFeed - + 'FROM PERF_AVERAGE_BULK AS NUM' + @LineFeed - + ' JOIN PERF_LARGE_RAW_BASE AS DEN ON NUM.counter_join = DEN.counter_join' + @LineFeed - + ' AND NUM.CheckDate = DEN.CheckDate' + @LineFeed - + ' AND NUM.ServerName = DEN.ServerName' + @LineFeed - + ' AND NUM.object_name = DEN.object_name' + @LineFeed - + ' AND NUM.instance_name = DEN.instance_name' + @LineFeed - + ' AND DEN.cntr_delta <> 0' + @LineFeed - + '' + @LineFeed - + 'UNION ALL' + @LineFeed - + '' + @LineFeed - + 'SELECT NUM.ServerName,' + @LineFeed - + ' NUM.object_name,' + @LineFeed - + ' NUM.counter_name,' + @LineFeed - + ' NUM.instance_name,' + @LineFeed - + ' NUM.CheckDate,' + @LineFeed - + ' CAST((CAST(NUM.cntr_delta as DECIMAL(19)) / DEN.cntr_delta) as decimal(23,3)) AS cntr_value,' + @LineFeed - + ' NUM.ServerName + CAST(NUM.CheckDate AS NVARCHAR(50)) AS JoinKey' + @LineFeed - + 'FROM PERF_AVERAGE_FRACTION AS NUM' + @LineFeed - + ' JOIN PERF_LARGE_RAW_BASE AS DEN ON NUM.counter_join = DEN.counter_join' + @LineFeed - + ' AND NUM.CheckDate = DEN.CheckDate' + @LineFeed - + ' AND NUM.ServerName = DEN.ServerName' + @LineFeed - + ' AND NUM.object_name = DEN.object_name' + @LineFeed - + ' AND NUM.instance_name = DEN.instance_name' + @LineFeed - + ' AND DEN.cntr_delta <> 0' + @LineFeed - + 'UNION ALL' + @LineFeed - + '' + @LineFeed - + 'SELECT ServerName,' + @LineFeed - + ' object_name,' + @LineFeed - + ' counter_name,' + @LineFeed - + ' instance_name,' + @LineFeed - + ' CheckDate,' + @LineFeed - + ' cntr_value,' + @LineFeed - + ' ServerName + CAST(CheckDate AS NVARCHAR(50)) AS JoinKey' + @LineFeed - + 'FROM PERF_COUNTER_BULK_COUNT' + @LineFeed - + '' + @LineFeed - + 'UNION ALL' + @LineFeed - + '' + @LineFeed - + 'SELECT ServerName,' + @LineFeed - + ' object_name,' + @LineFeed - + ' counter_name,' + @LineFeed - + ' instance_name,' + @LineFeed - + ' CheckDate,' + @LineFeed - + ' cntr_value,' + @LineFeed - + ' ServerName + CAST(CheckDate AS NVARCHAR(50)) AS JoinKey' + @LineFeed - + 'FROM PERF_COUNTER_RAWCOUNT;'')'; + RAISERROR (N'Inserting data into #Statistics',0,1) WITH NOWAIT; + IF @Debug = 1 + BEGIN + PRINT SUBSTRING(@dsql, 0, 4000); + PRINT SUBSTRING(@dsql, 4000, 8000); + PRINT SUBSTRING(@dsql, 8000, 12000); + PRINT SUBSTRING(@dsql, 12000, 16000); + PRINT SUBSTRING(@dsql, 16000, 20000); + PRINT SUBSTRING(@dsql, 20000, 24000); + PRINT SUBSTRING(@dsql, 24000, 28000); + PRINT SUBSTRING(@dsql, 28000, 32000); + PRINT SUBSTRING(@dsql, 32000, 36000); + PRINT SUBSTRING(@dsql, 36000, 40000); + END; + + EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; + END; - EXEC(@StringToExecute); - END; + END; + IF (PARSENAME(@SQLServerProductVersion, 4) >= 10) + BEGIN + RAISERROR (N'Gathering Computed Column Info.',0,1) WITH NOWAIT; + SET @dsql=N'SELECT DB_ID(@i_DatabaseName) AS [database_id], + @i_DatabaseName AS database_name, + t.name AS table_name, + s.name AS schema_name, + c.name AS column_name, + cc.is_nullable, + cc.definition, + cc.uses_database_collation, + cc.is_persisted, + cc.is_computed, + CASE WHEN cc.definition LIKE ''%|].|[%'' ESCAPE ''|'' THEN 1 ELSE 0 END AS is_function, + ''ALTER TABLE '' + QUOTENAME(s.name) + ''.'' + QUOTENAME(t.name) + + '' ADD '' + QUOTENAME(c.name) + '' AS '' + cc.definition + + CASE WHEN is_persisted = 1 THEN '' PERSISTED'' ELSE '''' END + '';'' COLLATE DATABASE_DEFAULT AS [column_definition] + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.computed_columns AS cc + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns AS c + ON cc.object_id = c.object_id + AND cc.column_id = c.column_id + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.tables AS t + ON t.object_id = cc.object_id + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s + ON s.schema_id = t.schema_id + OPTION (RECOMPILE);'; - SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' - + @OutputSchemaName + ''') INSERT ' - + @OutputDatabaseName + '.' - + @OutputSchemaName + '.' - + @OutputTableNamePerfmonStats - + ' (ServerName, CheckDate, object_name, counter_name, instance_name, cntr_value, cntr_type, value_delta, value_per_second) SELECT ' - + ' @SrvName, @CheckDate, object_name, counter_name, instance_name, cntr_value, cntr_type, value_delta, value_per_second FROM #PerfmonStats WHERE Pass = 2'; + IF @dsql IS NULL RAISERROR('@dsql is null',16,1); - EXEC sp_executesql @StringToExecute, - N'@SrvName NVARCHAR(128), @CheckDate datetimeoffset', - @LocalServerName, @StartSampleTime; + INSERT #ComputedColumns + ( database_id, [database_name], table_name, schema_name, column_name, is_nullable, definition, + uses_database_collation, is_persisted, is_computed, is_function, column_definition ) + EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; - /* Delete history older than @OutputTableRetentionDays */ - SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' - + @OutputSchemaName + ''') DELETE ' - + @OutputDatabaseName + '.' - + @OutputSchemaName + '.' - + @OutputTableNamePerfmonStats - + ' WHERE ServerName = @SrvName AND CheckDate < @CheckDate ;'; + END; + + RAISERROR (N'Gathering Trace Flag Information',0,1) WITH NOWAIT; + INSERT #TraceStatus + EXEC ('DBCC TRACESTATUS(-1) WITH NO_INFOMSGS'); - EXEC sp_executesql @StringToExecute, - N'@SrvName NVARCHAR(128), @CheckDate date', - @LocalServerName, @OutputTableCleanupDate; + IF (PARSENAME(@SQLServerProductVersion, 4) >= 13) + BEGIN + RAISERROR (N'Gathering Temporal Table Info',0,1) WITH NOWAIT; + SET @dsql=N'SELECT ' + QUOTENAME(@DatabaseName,'''') + N' AS database_name, + DB_ID(N' + QUOTENAME(@DatabaseName,'''') + N') AS [database_id], + s.name AS schema_name, + t.name AS table_name, + oa.hsn as history_schema_name, + oa.htn AS history_table_name, + c1.name AS start_column_name, + c2.name AS end_column_name, + p.name AS period_name + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.periods AS p + INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.tables AS t + ON p.object_id = t.object_id + INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns AS c1 + ON t.object_id = c1.object_id + AND p.start_column_id = c1.column_id + INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns AS c2 + ON t.object_id = c2.object_id + AND p.end_column_id = c2.column_id + INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s + ON t.schema_id = s.schema_id + CROSS APPLY ( SELECT s2.name as hsn, t2.name htn + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.tables AS t2 + INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s2 + ON t2.schema_id = s2.schema_id + WHERE t2.object_id = t.history_table_id + AND t2.temporal_type = 1 /*History table*/ ) AS oa + WHERE t.temporal_type IN ( 2, 4 ) /*BOL currently points to these types, but has no definition for 4*/ + OPTION (RECOMPILE); + '; + + IF @dsql IS NULL + RAISERROR('@dsql is null',16,1); + + INSERT #TemporalTables ( database_name, database_id, schema_name, table_name, history_schema_name, + history_table_name, start_column_name, end_column_name, period_name ) + + EXEC sp_executesql @dsql; + SET @dsql=N'SELECT DB_ID(@i_DatabaseName) AS [database_id], + @i_DatabaseName AS database_name, + t.name AS table_name, + s.name AS schema_name, + cc.name AS constraint_name, + cc.is_disabled, + cc.definition, + cc.uses_database_collation, + cc.is_not_trusted, + CASE WHEN cc.definition LIKE ''%|].|[%'' ESCAPE ''|'' THEN 1 ELSE 0 END AS is_function, + ''ALTER TABLE '' + QUOTENAME(s.name) + ''.'' + QUOTENAME(t.name) + + '' ADD CONSTRAINT '' + QUOTENAME(cc.name) + '' CHECK '' + cc.definition + '';'' COLLATE DATABASE_DEFAULT AS [column_definition] + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.check_constraints AS cc + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.tables AS t + ON t.object_id = cc.parent_object_id + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s + ON s.schema_id = t.schema_id + OPTION (RECOMPILE);'; + + INSERT #CheckConstraints + ( database_id, [database_name], table_name, schema_name, constraint_name, is_disabled, definition, + uses_database_collation, is_not_trusted, is_function, column_definition ) + EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; - END; - ELSE IF (SUBSTRING(@OutputTableNamePerfmonStats, 2, 2) = '##') - BEGIN - SET @StringToExecute = N' IF (OBJECT_ID(''tempdb..' - + @OutputTableNamePerfmonStats - + ''') IS NULL) CREATE TABLE ' - + @OutputTableNamePerfmonStats - + ' (ID INT IDENTITY(1,1) NOT NULL, - ServerName NVARCHAR(128), - CheckDate DATETIMEOFFSET, - [object_name] NVARCHAR(128) NOT NULL, - [counter_name] NVARCHAR(128) NOT NULL, - [instance_name] NVARCHAR(128) NULL, - [cntr_value] BIGINT NULL, - [cntr_type] INT NOT NULL, - [value_delta] BIGINT NULL, - [value_per_second] DECIMAL(18,2) NULL, - PRIMARY KEY CLUSTERED (ID ASC));' - + ' INSERT ' - + @OutputTableNamePerfmonStats - + ' (ServerName, CheckDate, object_name, counter_name, instance_name, cntr_value, cntr_type, value_delta, value_per_second) SELECT ' - + CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128)) - + ' @SrvName, @CheckDate, object_name, counter_name, instance_name, cntr_value, cntr_type, value_delta, value_per_second FROM #PerfmonStats WHERE Pass = 2'; + SET @dsql=N'SELECT DB_ID(@i_DatabaseName) AS [database_id], + @i_DatabaseName AS database_name, + s.name AS missing_schema_name, + t.name AS missing_table_name, + i.name AS missing_index_name, + c.name AS missing_column_name + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.sql_expression_dependencies AS sed + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.tables AS t + ON t.object_id = sed.referenced_id + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s + ON t.schema_id = s.schema_id + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.indexes AS i + ON i.object_id = sed.referenced_id + AND i.index_id = sed.referencing_minor_id + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns AS c + ON c.object_id = sed.referenced_id + AND c.column_id = sed.referenced_minor_id + WHERE sed.referencing_class = 7 + AND sed.referenced_class = 1 + AND i.has_filter = 1 + AND NOT EXISTS ( SELECT 1/0 + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.index_columns AS ic + WHERE ic.index_id = sed.referencing_minor_id + AND ic.column_id = sed.referenced_minor_id + AND ic.object_id = sed.referenced_id ) + OPTION(RECOMPILE);' + + INSERT #FilteredIndexes ( database_id, database_name, schema_name, table_name, index_name, column_name ) + EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; + - EXEC sp_executesql @StringToExecute, - N'@SrvName NVARCHAR(128), @CheckDate datetimeoffset', - @LocalServerName, @StartSampleTime; - END; - ELSE IF (SUBSTRING(@OutputTableNamePerfmonStats, 2, 1) = '#') - BEGIN - RAISERROR('Due to the nature of Dymamic SQL, only global (i.e. double pound (##)) temp tables are supported for @OutputTableName', 16, 0); END; + +END; +END TRY +BEGIN CATCH + RAISERROR (N'Failure populating temp tables.', 0,1) WITH NOWAIT; + IF @dsql IS NOT NULL + BEGIN + SET @msg= 'Last @dsql: ' + @dsql; + RAISERROR(@msg, 0, 1) WITH NOWAIT; + END; - /* @OutputTableNameWaitStats lets us export the results to a permanent table */ - IF @OutputDatabaseName IS NOT NULL - AND @OutputSchemaName IS NOT NULL - AND @OutputTableNameWaitStats IS NOT NULL - AND @OutputTableNameWaitStats NOT LIKE '#%' - AND EXISTS ( SELECT * - FROM sys.databases - WHERE QUOTENAME([name]) = @OutputDatabaseName) - BEGIN - /* Create the table */ - SET @StringToExecute = 'USE ' - + @OutputDatabaseName - + '; IF EXISTS(SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' - + @OutputSchemaName - + ''') AND NOT EXISTS (SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''' - + @OutputSchemaName + ''' AND QUOTENAME(TABLE_NAME) = ''' - + @OutputTableNameWaitStats + ''') ' + @LineFeed - + 'BEGIN' + @LineFeed - + 'CREATE TABLE ' - + @OutputSchemaName + '.' - + @OutputTableNameWaitStats - + ' (ID INT IDENTITY(1,1) NOT NULL, - ServerName NVARCHAR(128), - CheckDate DATETIMEOFFSET, - wait_type NVARCHAR(60), - wait_time_ms BIGINT, - signal_wait_time_ms BIGINT, - waiting_tasks_count BIGINT , - PRIMARY KEY CLUSTERED (ID));' + @LineFeed - + 'CREATE NONCLUSTERED INDEX IX_ServerName_wait_type_CheckDate_Includes ON ' + @OutputSchemaName + '.' + @OutputTableNameWaitStats + @LineFeed - + '(ServerName, wait_type, CheckDate) INCLUDE (wait_time_ms, signal_wait_time_ms, waiting_tasks_count);' + @LineFeed - + 'END'; + SELECT @msg = @DatabaseName + N' database failed to process. ' + ERROR_MESSAGE(), @ErrorSeverity = ERROR_SEVERITY(), @ErrorState = ERROR_STATE(); + RAISERROR (@msg,@ErrorSeverity, @ErrorState )WITH NOWAIT; + + + WHILE @@trancount > 0 + ROLLBACK; - EXEC(@StringToExecute); + RETURN; +END CATCH; + FETCH NEXT FROM c1 INTO @DatabaseName; +END; +DEALLOCATE c1; - /* Create the wait stats category table */ - SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableNameWaitStats_Categories; - IF OBJECT_ID(@ObjectFullName) IS NULL - BEGIN - SET @StringToExecute = 'USE ' - + @OutputDatabaseName - + '; EXEC (''CREATE TABLE ' - + @OutputSchemaName + '.' - + @OutputTableNameWaitStats_Categories + ' (WaitType NVARCHAR(60) PRIMARY KEY CLUSTERED, WaitCategory NVARCHAR(128) NOT NULL, Ignorable BIT DEFAULT 0);'')'; - EXEC(@StringToExecute); - END; - /* Make sure the wait stats category table has the current number of rows */ - SET @StringToExecute = 'USE ' - + @OutputDatabaseName - + '; EXEC (''IF (SELECT COALESCE(SUM(1),0) FROM ' + @OutputSchemaName + '.' + @OutputTableNameWaitStats_Categories + ') <> (SELECT COALESCE(SUM(1),0) FROM ##WaitCategories)' + @LineFeed - + 'BEGIN ' + @LineFeed - + 'TRUNCATE TABLE ' + @OutputSchemaName + '.' + @OutputTableNameWaitStats_Categories + @LineFeed - + 'INSERT INTO ' + @OutputSchemaName + '.' + @OutputTableNameWaitStats_Categories + ' (WaitType, WaitCategory, Ignorable) SELECT WaitType, WaitCategory, Ignorable FROM ##WaitCategories;' + @LineFeed - + 'END'')'; - EXEC(@StringToExecute); - SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableNameWaitStats_View; +---------------------------------------- +--STEP 2: PREP THE TEMP TABLES +--EVERY QUERY AFTER THIS GOES AGAINST TEMP TABLES ONLY. +---------------------------------------- - /* If the view exists without the most recently added columns, drop it. See Github #2162. */ - IF OBJECT_ID(@ObjectFullName) IS NOT NULL - BEGIN - SET @StringToExecute = N'USE ' + @OutputDatabaseName + N'; IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns - WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''JoinKey'') - DROP VIEW ' + @OutputSchemaName + N'.' + @OutputTableNameWaitStats_View + N';'; +RAISERROR (N'Updating #IndexSanity.key_column_names',0,1) WITH NOWAIT; +UPDATE #IndexSanity +SET key_column_names = D1.key_column_names +FROM #IndexSanity si + CROSS APPLY ( SELECT RTRIM(STUFF( (SELECT N', ' + c.column_name + + N' {' + system_type_name + N' ' + + CASE max_length WHEN -1 THEN N'(max)' ELSE + CASE + WHEN system_type_name IN (N'char',N'varchar',N'binary',N'varbinary') THEN N'(' + CAST(max_length AS NVARCHAR(20)) + N')' + WHEN system_type_name IN (N'nchar',N'nvarchar') THEN N'(' + CAST(max_length/2 AS NVARCHAR(20)) + N')' + ELSE '' + END + END + + N'}' + AS col_definition + FROM #IndexColumns c + WHERE c.database_id= si.database_id + AND c.schema_name = si.schema_name + AND c.object_id = si.object_id + AND c.index_id = si.index_id + AND c.is_included_column = 0 /*Just Keys*/ + AND c.key_ordinal > 0 /*Ignore non-key columns, such as partitioning keys*/ + ORDER BY c.object_id, c.index_id, c.key_ordinal + FOR XML PATH('') ,TYPE).value('.', 'nvarchar(max)'), 1, 1, '')) + ) D1 ( key_column_names ); - EXEC(@StringToExecute); - END +RAISERROR (N'Updating #IndexSanity.partition_key_column_name',0,1) WITH NOWAIT; +UPDATE #IndexSanity +SET partition_key_column_name = D1.partition_key_column_name +FROM #IndexSanity si + CROSS APPLY ( SELECT RTRIM(STUFF( (SELECT N', ' + c.column_name AS col_definition + FROM #IndexColumns c + WHERE c.database_id= si.database_id + AND c.schema_name = si.schema_name + AND c.object_id = si.object_id + AND c.index_id = si.index_id + AND c.partition_ordinal <> 0 /*Just Partitioned Keys*/ + ORDER BY c.object_id, c.index_id, c.key_ordinal + FOR XML PATH('') , TYPE).value('.', 'nvarchar(max)'), 1, 1,''))) D1 + ( partition_key_column_name ); +RAISERROR (N'Updating #IndexSanity.key_column_names_with_sort_order',0,1) WITH NOWAIT; +UPDATE #IndexSanity +SET key_column_names_with_sort_order = D2.key_column_names_with_sort_order +FROM #IndexSanity si + CROSS APPLY ( SELECT RTRIM(STUFF( (SELECT N', ' + c.column_name + CASE c.is_descending_key + WHEN 1 THEN N' DESC' + ELSE N'' + END + + N' {' + system_type_name + N' ' + + CASE max_length WHEN -1 THEN N'(max)' ELSE + CASE + WHEN system_type_name IN (N'char',N'varchar',N'binary',N'varbinary') THEN N'(' + CAST(max_length AS NVARCHAR(20)) + N')' + WHEN system_type_name IN (N'nchar',N'nvarchar') THEN N'(' + CAST(max_length/2 AS NVARCHAR(20)) + N')' + ELSE '' + END + END + + N'}' + AS col_definition + FROM #IndexColumns c + WHERE c.database_id= si.database_id + AND c.schema_name = si.schema_name + AND c.object_id = si.object_id + AND c.index_id = si.index_id + AND c.is_included_column = 0 /*Just Keys*/ + AND c.key_ordinal > 0 /*Ignore non-key columns, such as partitioning keys*/ + ORDER BY c.object_id, c.index_id, c.key_ordinal + FOR XML PATH('') , TYPE).value('.', 'nvarchar(max)'), 1, 1, '')) + ) D2 ( key_column_names_with_sort_order ); - /* Create the wait stats view */ - IF OBJECT_ID(@ObjectFullName) IS NULL - BEGIN - SET @StringToExecute = 'USE ' - + @OutputDatabaseName - + '; EXEC (''CREATE VIEW ' - + @OutputSchemaName + '.' - + @OutputTableNameWaitStats_View + ' AS ' + @LineFeed - + 'WITH RowDates as' + @LineFeed - + '(' + @LineFeed - + ' SELECT ' + @LineFeed - + ' ROW_NUMBER() OVER (ORDER BY [ServerName], [CheckDate]) ID,' + @LineFeed - + ' [CheckDate]' + @LineFeed - + ' FROM ' + @OutputSchemaName + '.' + @OutputTableNameWaitStats + @LineFeed - + ' GROUP BY [ServerName], [CheckDate]' + @LineFeed - + '),' + @LineFeed - + 'CheckDates as' + @LineFeed - + '(' + @LineFeed - + ' SELECT ThisDate.CheckDate,' + @LineFeed - + ' LastDate.CheckDate as PreviousCheckDate' + @LineFeed - + ' FROM RowDates ThisDate' + @LineFeed - + ' JOIN RowDates LastDate' + @LineFeed - + ' ON ThisDate.ID = LastDate.ID + 1' + @LineFeed - + ')' + @LineFeed - + 'SELECT w.ServerName, w.CheckDate, w.wait_type, COALESCE(wc.WaitCategory, ''''Other'''') AS WaitCategory, COALESCE(wc.Ignorable,0) AS Ignorable' + @LineFeed - + ', DATEDIFF(ss, wPrior.CheckDate, w.CheckDate) AS ElapsedSeconds' + @LineFeed - + ', (w.wait_time_ms - wPrior.wait_time_ms) AS wait_time_ms_delta' + @LineFeed - + ', (w.wait_time_ms - wPrior.wait_time_ms) / 60000.0 AS wait_time_minutes_delta' + @LineFeed - + ', (w.wait_time_ms - wPrior.wait_time_ms) / 1000.0 / DATEDIFF(ss, wPrior.CheckDate, w.CheckDate) AS wait_time_minutes_per_minute' + @LineFeed - + ', (w.signal_wait_time_ms - wPrior.signal_wait_time_ms) AS signal_wait_time_ms_delta' + @LineFeed - + ', (w.waiting_tasks_count - wPrior.waiting_tasks_count) AS waiting_tasks_count_delta' + @LineFeed - + ', w.ServerName + CAST(w.CheckDate AS NVARCHAR(50)) AS JoinKey' + @LineFeed - + 'FROM ' + @OutputSchemaName + '.' + @OutputTableNameWaitStats + ' w' + @LineFeed - + 'INNER HASH JOIN CheckDates Dates' + @LineFeed - + 'ON Dates.CheckDate = w.CheckDate' + @LineFeed - + 'INNER JOIN ' + @OutputSchemaName + '.' + @OutputTableNameWaitStats + ' wPrior ON w.ServerName = wPrior.ServerName AND w.wait_type = wPrior.wait_type AND Dates.PreviousCheckDate = wPrior.CheckDate' + @LineFeed - + 'LEFT OUTER JOIN ' + @OutputSchemaName + '.' + @OutputTableNameWaitStats_Categories + ' wc ON w.wait_type = wc.WaitType' + @LineFeed - + 'WHERE DATEDIFF(MI, wPrior.CheckDate, w.CheckDate) BETWEEN 1 AND 60' + @LineFeed - + 'AND [w].[wait_time_ms] >= [wPrior].[wait_time_ms];'')' +RAISERROR (N'Updating #IndexSanity.key_column_names_with_sort_order_no_types (for create tsql)',0,1) WITH NOWAIT; +UPDATE #IndexSanity +SET key_column_names_with_sort_order_no_types = D2.key_column_names_with_sort_order_no_types +FROM #IndexSanity si + CROSS APPLY ( SELECT RTRIM(STUFF( (SELECT N', ' + QUOTENAME(c.column_name) + CASE c.is_descending_key + WHEN 1 THEN N' DESC' + ELSE N'' + END AS col_definition + FROM #IndexColumns c + WHERE c.database_id= si.database_id + AND c.schema_name = si.schema_name + AND c.object_id = si.object_id + AND c.index_id = si.index_id + AND c.is_included_column = 0 /*Just Keys*/ + AND c.key_ordinal > 0 /*Ignore non-key columns, such as partitioning keys*/ + ORDER BY c.object_id, c.index_id, c.key_ordinal + FOR XML PATH('') , TYPE).value('.', 'nvarchar(max)'), 1, 1, '')) + ) D2 ( key_column_names_with_sort_order_no_types ); + +RAISERROR (N'Updating #IndexSanity.include_column_names',0,1) WITH NOWAIT; +UPDATE #IndexSanity +SET include_column_names = D3.include_column_names +FROM #IndexSanity si + CROSS APPLY ( SELECT RTRIM(STUFF( (SELECT N', ' + c.column_name + + N' {' + system_type_name + N' ' + CAST(max_length AS NVARCHAR(50)) + N'}' + FROM #IndexColumns c + WHERE c.database_id= si.database_id + AND c.schema_name = si.schema_name + AND c.object_id = si.object_id + AND c.index_id = si.index_id + AND c.is_included_column = 1 /*Just includes*/ + ORDER BY c.column_name /*Order doesn't matter in includes, + this is here to make rows easy to compare.*/ + FOR XML PATH('') , TYPE).value('.', 'nvarchar(max)'), 1, 1, '')) + ) D3 ( include_column_names ); + +RAISERROR (N'Updating #IndexSanity.include_column_names_no_types (for create tsql)',0,1) WITH NOWAIT; +UPDATE #IndexSanity +SET include_column_names_no_types = D3.include_column_names_no_types +FROM #IndexSanity si + CROSS APPLY ( SELECT RTRIM(STUFF( (SELECT N', ' + QUOTENAME(c.column_name) + FROM #IndexColumns c + WHERE c.database_id= si.database_id + AND c.schema_name = si.schema_name + AND c.object_id = si.object_id + AND c.index_id = si.index_id + AND c.is_included_column = 1 /*Just includes*/ + ORDER BY c.column_name /*Order doesn't matter in includes, + this is here to make rows easy to compare.*/ + FOR XML PATH('') , TYPE).value('.', 'nvarchar(max)'), 1, 1, '')) + ) D3 ( include_column_names_no_types ); + +RAISERROR (N'Updating #IndexSanity.count_key_columns and count_include_columns',0,1) WITH NOWAIT; +UPDATE #IndexSanity +SET count_included_columns = D4.count_included_columns, + count_key_columns = D4.count_key_columns +FROM #IndexSanity si + CROSS APPLY ( SELECT SUM(CASE WHEN is_included_column = 'true' THEN 1 + ELSE 0 + END) AS count_included_columns, + SUM(CASE WHEN is_included_column = 'false' AND c.key_ordinal > 0 THEN 1 + ELSE 0 + END) AS count_key_columns + FROM #IndexColumns c + WHERE c.database_id= si.database_id + AND c.schema_name = si.schema_name + AND c.object_id = si.object_id + AND c.index_id = si.index_id + ) AS D4 ( count_included_columns, count_key_columns ); + +RAISERROR (N'Updating index_sanity_id on #IndexPartitionSanity',0,1) WITH NOWAIT; +UPDATE #IndexPartitionSanity +SET index_sanity_id = i.index_sanity_id +FROM #IndexPartitionSanity ps + JOIN #IndexSanity i ON ps.[object_id] = i.[object_id] + AND ps.index_id = i.index_id + AND i.database_id = ps.database_id + AND i.schema_name = ps.schema_name; + + +RAISERROR (N'Inserting data into #IndexSanitySize',0,1) WITH NOWAIT; +INSERT #IndexSanitySize ( [index_sanity_id], [database_id], [schema_name], [lock_escalation_desc], partition_count, total_rows, total_reserved_MB, + total_reserved_LOB_MB, total_reserved_row_overflow_MB, total_reserved_dictionary_MB, total_range_scan_count, + total_singleton_lookup_count, total_leaf_delete_count, total_leaf_update_count, + total_forwarded_fetch_count,total_row_lock_count, + total_row_lock_wait_count, total_row_lock_wait_in_ms, avg_row_lock_wait_in_ms, + total_page_lock_count, total_page_lock_wait_count, total_page_lock_wait_in_ms, + avg_page_lock_wait_in_ms, total_index_lock_promotion_attempt_count, + total_index_lock_promotion_count, data_compression_desc, + page_latch_wait_count, page_latch_wait_in_ms, page_io_latch_wait_count, page_io_latch_wait_in_ms) + SELECT index_sanity_id, ipp.database_id, ipp.schema_name, ipp.lock_escalation_desc, + COUNT(*), SUM(row_count), SUM(reserved_MB), + SUM(reserved_LOB_MB) - SUM(reserved_dictionary_MB), /* Subtract columnstore dictionaries from LOB data */ + SUM(reserved_row_overflow_MB), + SUM(reserved_dictionary_MB), + SUM(range_scan_count), + SUM(singleton_lookup_count), + SUM(leaf_delete_count), + SUM(leaf_update_count), + SUM(forwarded_fetch_count), + SUM(row_lock_count), + SUM(row_lock_wait_count), + SUM(row_lock_wait_in_ms), + CASE WHEN SUM(row_lock_wait_in_ms) > 0 THEN + SUM(row_lock_wait_in_ms)/(1.*SUM(row_lock_wait_count)) + ELSE 0 END AS avg_row_lock_wait_in_ms, + SUM(page_lock_count), + SUM(page_lock_wait_count), + SUM(page_lock_wait_in_ms), + CASE WHEN SUM(page_lock_wait_in_ms) > 0 THEN + SUM(page_lock_wait_in_ms)/(1.*SUM(page_lock_wait_count)) + ELSE 0 END AS avg_page_lock_wait_in_ms, + SUM(index_lock_promotion_attempt_count), + SUM(index_lock_promotion_count), + LEFT(MAX(data_compression_info.data_compression_rollup),4000), + SUM(page_latch_wait_count), + SUM(page_latch_wait_in_ms), + SUM(page_io_latch_wait_count), + SUM(page_io_latch_wait_in_ms) + FROM #IndexPartitionSanity ipp + /* individual partitions can have distinct compression settings, just roll them into a list here*/ + OUTER APPLY (SELECT STUFF(( + SELECT N', ' + data_compression_desc + FROM #IndexPartitionSanity ipp2 + WHERE ipp.[object_id]=ipp2.[object_id] + AND ipp.[index_id]=ipp2.[index_id] + AND ipp.database_id = ipp2.database_id + AND ipp.schema_name = ipp2.schema_name + ORDER BY ipp2.partition_number + FOR XML PATH(''),TYPE).value('.', 'nvarchar(max)'), 1, 1, '')) + data_compression_info(data_compression_rollup) + GROUP BY index_sanity_id, ipp.database_id, ipp.schema_name, ipp.lock_escalation_desc + ORDER BY index_sanity_id +OPTION ( RECOMPILE ); - EXEC(@StringToExecute); - END; +RAISERROR (N'Determining index usefulness',0,1) WITH NOWAIT; +UPDATE #MissingIndexes +SET is_low = CASE WHEN (user_seeks + user_scans) < 5000 + OR unique_compiles = 1 + THEN 1 + ELSE 0 + END; +RAISERROR (N'Updating #IndexSanity.referenced_by_foreign_key',0,1) WITH NOWAIT; +UPDATE #IndexSanity + SET is_referenced_by_foreign_key=1 +FROM #IndexSanity s +JOIN #ForeignKeys fk ON + s.object_id=fk.referenced_object_id + AND s.database_id=fk.database_id + AND LEFT(s.key_column_names,LEN(fk.referenced_fk_columns)) = fk.referenced_fk_columns; - SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' - + @OutputSchemaName + ''') INSERT ' - + @OutputDatabaseName + '.' - + @OutputSchemaName + '.' - + @OutputTableNameWaitStats - + ' (ServerName, CheckDate, wait_type, wait_time_ms, signal_wait_time_ms, waiting_tasks_count) SELECT ' - + ' @SrvName, @CheckDate, wait_type, wait_time_ms, signal_wait_time_ms, waiting_tasks_count FROM #WaitStats WHERE Pass = 2 AND wait_time_ms > 0 AND waiting_tasks_count > 0'; +RAISERROR (N'Update index_secret on #IndexSanity for NC indexes.',0,1) WITH NOWAIT; +UPDATE nc +SET secret_columns= + N'[' + + CASE tb.count_key_columns WHEN 0 THEN '1' ELSE CAST(tb.count_key_columns AS NVARCHAR(10)) END + + CASE nc.is_unique WHEN 1 THEN N' INCLUDE' ELSE N' KEY' END + + CASE WHEN tb.count_key_columns > 1 THEN N'S] ' ELSE N'] ' END + + CASE tb.index_id WHEN 0 THEN '[RID]' ELSE LTRIM(tb.key_column_names) + + /* Uniquifiers only needed on non-unique clustereds-- not heaps */ + CASE tb.is_unique WHEN 0 THEN ' [UNIQUIFIER]' ELSE N'' END + END + , count_secret_columns= + CASE tb.index_id WHEN 0 THEN 1 ELSE + tb.count_key_columns + + CASE tb.is_unique WHEN 0 THEN 1 ELSE 0 END + END +FROM #IndexSanity AS nc +JOIN #IndexSanity AS tb ON nc.object_id=tb.object_id + AND nc.database_id = tb.database_id + AND nc.schema_name = tb.schema_name + AND tb.index_id IN (0,1) +WHERE nc.index_id > 1; - EXEC sp_executesql @StringToExecute, - N'@SrvName NVARCHAR(128), @CheckDate datetimeoffset', - @LocalServerName, @StartSampleTime; +RAISERROR (N'Update index_secret on #IndexSanity for heaps and non-unique clustered.',0,1) WITH NOWAIT; +UPDATE tb +SET secret_columns= CASE tb.index_id WHEN 0 THEN '[RID]' ELSE '[UNIQUIFIER]' END + , count_secret_columns = 1 +FROM #IndexSanity AS tb +WHERE tb.index_id = 0 /*Heaps-- these have the RID */ + OR (tb.index_id=1 AND tb.is_unique=0); /* Non-unique CX: has uniquifer (when needed) */ - /* Delete history older than @OutputTableRetentionDays */ - SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' - + @OutputSchemaName + ''') DELETE ' - + @OutputDatabaseName + '.' - + @OutputSchemaName + '.' - + @OutputTableNameWaitStats - + ' WHERE ServerName = @SrvName AND CheckDate < @CheckDate ;'; - EXEC sp_executesql @StringToExecute, - N'@SrvName NVARCHAR(128), @CheckDate date', - @LocalServerName, @OutputTableCleanupDate; +RAISERROR (N'Populate #IndexCreateTsql.',0,1) WITH NOWAIT; +INSERT #IndexCreateTsql (index_sanity_id, create_tsql) +SELECT + index_sanity_id, + ISNULL ( + CASE index_id WHEN 0 THEN N'ALTER TABLE ' + QUOTENAME([database_name]) + N'.' + QUOTENAME([schema_name]) + N'.' + QUOTENAME([object_name]) + ' REBUILD;' + ELSE + CASE WHEN is_XML = 1 OR is_spatial = 1 OR is_in_memory_oltp = 1 THEN N'' /* Not even trying for these just yet...*/ + ELSE + CASE WHEN is_primary_key=1 THEN + N'ALTER TABLE ' + QUOTENAME([database_name]) + N'.' + QUOTENAME([schema_name]) + + N'.' + QUOTENAME([object_name]) + + N' ADD CONSTRAINT [' + + index_name + + N'] PRIMARY KEY ' + + CASE WHEN index_id=1 THEN N'CLUSTERED (' ELSE N'(' END + + key_column_names_with_sort_order_no_types + N' )' + WHEN is_CX_columnstore= 1 THEN + N'CREATE CLUSTERED COLUMNSTORE INDEX ' + QUOTENAME(index_name) + N' on ' + QUOTENAME([database_name]) + N'.' + QUOTENAME([schema_name]) + N'.' + QUOTENAME([object_name]) + ELSE /*Else not a PK or cx columnstore */ + N'CREATE ' + + CASE WHEN is_unique=1 THEN N'UNIQUE ' ELSE N'' END + + CASE WHEN index_id=1 THEN N'CLUSTERED ' ELSE N'' END + + CASE WHEN is_NC_columnstore=1 THEN N'NONCLUSTERED COLUMNSTORE ' + ELSE N'' END + + N'INDEX [' + + index_name + N'] ON ' + + QUOTENAME([database_name]) + N'.' + + QUOTENAME([schema_name]) + N'.' + QUOTENAME([object_name]) + + CASE WHEN is_NC_columnstore=1 THEN + N' (' + ISNULL(include_column_names_no_types,'') + N' )' + ELSE /*Else not columnstore */ + N' (' + ISNULL(key_column_names_with_sort_order_no_types,'') + N' )' + + CASE WHEN include_column_names_no_types IS NOT NULL THEN + N' INCLUDE (' + include_column_names_no_types + N')' + ELSE N'' + END + END /*End non-columnstore case */ + + CASE WHEN filter_definition <> N'' THEN N' WHERE ' + filter_definition ELSE N'' END + END /*End Non-PK index CASE */ + + CASE WHEN is_NC_columnstore=0 AND is_CX_columnstore=0 THEN + N' WITH (' + + N'FILLFACTOR=' + CASE fill_factor WHEN 0 THEN N'100' ELSE CAST(fill_factor AS NVARCHAR(5)) END + ', ' + + N'ONLINE=?, SORT_IN_TEMPDB=?, DATA_COMPRESSION=?' + + N')' + ELSE N'' END + + N';' + END /*End non-spatial and non-xml CASE */ + END, '[Unknown Error]') + AS create_tsql +FROM #IndexSanity; + +RAISERROR (N'Populate #PartitionCompressionInfo.',0,1) WITH NOWAIT; +WITH maps + AS + ( + SELECT ips.index_sanity_id, + ips.partition_number, + ips.data_compression_desc, + ips.partition_number - ROW_NUMBER() OVER ( PARTITION BY ips.index_sanity_id, ips.data_compression_desc + ORDER BY ips.partition_number ) AS rn + FROM #IndexPartitionSanity AS ips + ) +SELECT * +INTO #maps +FROM maps; - END; - ELSE IF (SUBSTRING(@OutputTableNameWaitStats, 2, 2) = '##') - BEGIN - SET @StringToExecute = N' IF (OBJECT_ID(''tempdb..' - + @OutputTableNameWaitStats - + ''') IS NULL) CREATE TABLE ' - + @OutputTableNameWaitStats - + ' (ID INT IDENTITY(1,1) NOT NULL, - ServerName NVARCHAR(128), - CheckDate DATETIMEOFFSET, - wait_type NVARCHAR(60), - wait_time_ms BIGINT, - signal_wait_time_ms BIGINT, - waiting_tasks_count BIGINT , - PRIMARY KEY CLUSTERED (ID ASC));' - + ' INSERT ' - + @OutputTableNameWaitStats - + ' (ServerName, CheckDate, wait_type, wait_time_ms, signal_wait_time_ms, waiting_tasks_count) SELECT ' - + ' @SrvName, @CheckDate, wait_type, wait_time_ms, signal_wait_time_ms, waiting_tasks_count FROM #WaitStats WHERE Pass = 2 AND wait_time_ms > 0 AND waiting_tasks_count > 0'; +WITH grps + AS + ( + SELECT MIN(maps.partition_number) AS MinKey, + MAX(maps.partition_number) AS MaxKey, + maps.index_sanity_id, + maps.data_compression_desc + FROM #maps AS maps + GROUP BY maps.rn, maps.index_sanity_id, maps.data_compression_desc + ) +SELECT * +INTO #grps +FROM grps; - EXEC sp_executesql @StringToExecute, - N'@SrvName NVARCHAR(128), @CheckDate datetimeoffset', - @LocalServerName, @StartSampleTime; - END; - ELSE IF (SUBSTRING(@OutputTableNameWaitStats, 2, 1) = '#') - BEGIN - RAISERROR('Due to the nature of Dymamic SQL, only global (i.e. double pound (##)) temp tables are supported for @OutputTableName', 16, 0); - END; +INSERT #PartitionCompressionInfo ( index_sanity_id, partition_compression_detail ) +SELECT DISTINCT + grps.index_sanity_id, + SUBSTRING( + ( STUFF( + ( SELECT N', ' + N' Partition' + + CASE + WHEN grps2.MinKey < grps2.MaxKey + THEN + + N's ' + CAST(grps2.MinKey AS NVARCHAR(10)) + N' - ' + + CAST(grps2.MaxKey AS NVARCHAR(10)) + N' use ' + grps2.data_compression_desc + ELSE + N' ' + CAST(grps2.MinKey AS NVARCHAR(10)) + N' uses ' + grps2.data_compression_desc + END AS Partitions + FROM #grps AS grps2 + WHERE grps2.index_sanity_id = grps.index_sanity_id + ORDER BY grps2.MinKey, grps2.MaxKey + FOR XML PATH(''), TYPE ).value('.', 'NVARCHAR(MAX)'), 1, 1, '')), 0, 8000) AS partition_compression_detail +FROM #grps AS grps; + +RAISERROR (N'Update #PartitionCompressionInfo.',0,1) WITH NOWAIT; +UPDATE sz +SET sz.data_compression_desc = pci.partition_compression_detail +FROM #IndexSanitySize sz +JOIN #PartitionCompressionInfo AS pci +ON pci.index_sanity_id = sz.index_sanity_id; +RAISERROR (N'Update #IndexSanity for filtered indexes with columns not in the index definition.',0,1) WITH NOWAIT; +UPDATE #IndexSanity +SET filter_columns_not_in_index = D1.filter_columns_not_in_index +FROM #IndexSanity si + CROSS APPLY ( SELECT RTRIM(STUFF( (SELECT N', ' + c.column_name AS col_definition + FROM #FilteredIndexes AS c + WHERE c.database_id= si.database_id + AND c.schema_name = si.schema_name + AND c.table_name = si.object_name + AND c.index_name = si.index_name + ORDER BY c.index_sanity_id + FOR XML PATH('') , TYPE).value('.', 'nvarchar(max)'), 1, 1,''))) D1 + ( filter_columns_not_in_index ); +IF @Debug = 1 +BEGIN + SELECT '#IndexSanity' AS table_name, * FROM #IndexSanity; + SELECT '#IndexPartitionSanity' AS table_name, * FROM #IndexPartitionSanity; + SELECT '#IndexSanitySize' AS table_name, * FROM #IndexSanitySize; + SELECT '#IndexColumns' AS table_name, * FROM #IndexColumns; + SELECT '#MissingIndexes' AS table_name, * FROM #MissingIndexes; + SELECT '#ForeignKeys' AS table_name, * FROM #ForeignKeys; + SELECT '#BlitzIndexResults' AS table_name, * FROM #BlitzIndexResults; + SELECT '#IndexCreateTsql' AS table_name, * FROM #IndexCreateTsql; + SELECT '#DatabaseList' AS table_name, * FROM #DatabaseList; + SELECT '#Statistics' AS table_name, * FROM #Statistics; + SELECT '#PartitionCompressionInfo' AS table_name, * FROM #PartitionCompressionInfo; + SELECT '#ComputedColumns' AS table_name, * FROM #ComputedColumns; + SELECT '#TraceStatus' AS table_name, * FROM #TraceStatus; + SELECT '#CheckConstraints' AS table_name, * FROM #CheckConstraints; + SELECT '#FilteredIndexes' AS table_name, * FROM #FilteredIndexes; +END - DECLARE @separator AS VARCHAR(1); - IF @OutputType = 'RSV' - SET @separator = CHAR(31); - ELSE - SET @separator = ','; - IF @OutputType = 'COUNT' AND @SinceStartup = 0 - BEGIN - SELECT COUNT(*) AS Warnings - FROM #BlitzFirstResults; - END; - ELSE - IF @OutputType = 'Opserver1' AND @SinceStartup = 0 - BEGIN +---------------------------------------- +--STEP 3: DIAGNOSE THE PATIENT +---------------------------------------- - SELECT r.[Priority] , - r.[FindingsGroup] , - r.[Finding] , - r.[URL] , - r.[Details], - r.[HowToStopIt] , - r.[CheckID] , - r.[StartTime], - r.[LoginName], - r.[NTUserName], - r.[OriginalLoginName], - r.[ProgramName], - r.[HostName], - r.[DatabaseID], - r.[DatabaseName], - r.[OpenTransactionCount], - r.[QueryPlan], - r.[QueryText], - qsNow.plan_handle AS PlanHandle, - qsNow.sql_handle AS SqlHandle, - qsNow.statement_start_offset AS StatementStartOffset, - qsNow.statement_end_offset AS StatementEndOffset, - [Executions] = qsNow.execution_count - (COALESCE(qsFirst.execution_count, 0)), - [ExecutionsPercent] = CAST(100.0 * (qsNow.execution_count - (COALESCE(qsFirst.execution_count, 0))) / (qsTotal.execution_count - qsTotalFirst.execution_count) AS DECIMAL(6,2)), - [Duration] = qsNow.total_elapsed_time - (COALESCE(qsFirst.total_elapsed_time, 0)), - [DurationPercent] = CAST(100.0 * (qsNow.total_elapsed_time - (COALESCE(qsFirst.total_elapsed_time, 0))) / (qsTotal.total_elapsed_time - qsTotalFirst.total_elapsed_time) AS DECIMAL(6,2)), - [CPU] = qsNow.total_worker_time - (COALESCE(qsFirst.total_worker_time, 0)), - [CPUPercent] = CAST(100.0 * (qsNow.total_worker_time - (COALESCE(qsFirst.total_worker_time, 0))) / (qsTotal.total_worker_time - qsTotalFirst.total_worker_time) AS DECIMAL(6,2)), - [Reads] = qsNow.total_logical_reads - (COALESCE(qsFirst.total_logical_reads, 0)), - [ReadsPercent] = CAST(100.0 * (qsNow.total_logical_reads - (COALESCE(qsFirst.total_logical_reads, 0))) / (qsTotal.total_logical_reads - qsTotalFirst.total_logical_reads) AS DECIMAL(6,2)), - [PlanCreationTime] = CONVERT(NVARCHAR(100), qsNow.creation_time ,121), - [TotalExecutions] = qsNow.execution_count, - [TotalExecutionsPercent] = CAST(100.0 * qsNow.execution_count / qsTotal.execution_count AS DECIMAL(6,2)), - [TotalDuration] = qsNow.total_elapsed_time, - [TotalDurationPercent] = CAST(100.0 * qsNow.total_elapsed_time / qsTotal.total_elapsed_time AS DECIMAL(6,2)), - [TotalCPU] = qsNow.total_worker_time, - [TotalCPUPercent] = CAST(100.0 * qsNow.total_worker_time / qsTotal.total_worker_time AS DECIMAL(6,2)), - [TotalReads] = qsNow.total_logical_reads, - [TotalReadsPercent] = CAST(100.0 * qsNow.total_logical_reads / qsTotal.total_logical_reads AS DECIMAL(6,2)), - r.[DetailsInt] - FROM #BlitzFirstResults r - LEFT OUTER JOIN #QueryStats qsTotal ON qsTotal.Pass = 0 - LEFT OUTER JOIN #QueryStats qsTotalFirst ON qsTotalFirst.Pass = -1 - LEFT OUTER JOIN #QueryStats qsNow ON r.QueryStatsNowID = qsNow.ID - LEFT OUTER JOIN #QueryStats qsFirst ON r.QueryStatsFirstID = qsFirst.ID - ORDER BY r.Priority , - r.FindingsGroup , - CASE - WHEN r.CheckID = 6 THEN DetailsInt - ELSE 0 - END DESC, - r.Finding, - r.ID; - END; - ELSE IF @OutputType IN ( 'CSV', 'RSV' ) AND @SinceStartup = 0 - BEGIN - SELECT Result = CAST([Priority] AS NVARCHAR(100)) - + @separator + CAST(CheckID AS NVARCHAR(100)) - + @separator + COALESCE([FindingsGroup], - '(N/A)') + @separator - + COALESCE([Finding], '(N/A)') + @separator - + COALESCE(DatabaseName, '(N/A)') + @separator - + COALESCE([URL], '(N/A)') + @separator - + COALESCE([Details], '(N/A)') - FROM #BlitzFirstResults - ORDER BY Priority , - FindingsGroup , - CASE - WHEN CheckID = 6 THEN DetailsInt - ELSE 0 - END DESC, - Finding, - Details; - END; - ELSE IF @OutputType = 'Top10' - BEGIN - /* Measure waits in hours */ - ;WITH max_batch AS ( - SELECT MAX(SampleTime) AS SampleTime - FROM #WaitStats - ) - SELECT TOP 10 - CAST(DATEDIFF(mi,wd1.SampleTime, wd2.SampleTime) / 60.0 AS DECIMAL(18,1)) AS [Hours Sample], - wd1.wait_type, - COALESCE(wcat.WaitCategory, 'Other') AS wait_category, - CAST(c.[Wait Time (Seconds)] / 60.0 / 60 AS DECIMAL(18,1)) AS [Wait Time (Hours)], - CAST((wd2.wait_time_ms - wd1.wait_time_ms) / 1000.0 / cores.cpu_count / DATEDIFF(ss, wd1.SampleTime, wd2.SampleTime) AS DECIMAL(18,1)) AS [Per Core Per Hour], - (wd2.waiting_tasks_count - wd1.waiting_tasks_count) AS [Number of Waits], - CASE WHEN (wd2.waiting_tasks_count - wd1.waiting_tasks_count) > 0 - THEN - CAST((wd2.wait_time_ms-wd1.wait_time_ms)/ - (1.0*(wd2.waiting_tasks_count - wd1.waiting_tasks_count)) AS NUMERIC(12,1)) - ELSE 0 END AS [Avg ms Per Wait] - FROM max_batch b - JOIN #WaitStats wd2 ON - wd2.SampleTime =b.SampleTime - JOIN #WaitStats wd1 ON - wd1.wait_type=wd2.wait_type AND - wd2.SampleTime > wd1.SampleTime - CROSS APPLY (SELECT SUM(1) AS cpu_count FROM sys.dm_os_schedulers WHERE status = 'VISIBLE ONLINE' AND is_online = 1) AS cores - CROSS APPLY (SELECT - CAST((wd2.wait_time_ms-wd1.wait_time_ms)/1000. AS NUMERIC(12,1)) AS [Wait Time (Seconds)], - CAST((wd2.signal_wait_time_ms - wd1.signal_wait_time_ms)/1000. AS NUMERIC(12,1)) AS [Signal Wait Time (Seconds)]) AS c - LEFT OUTER JOIN ##WaitCategories wcat ON wd1.wait_type = wcat.WaitType - WHERE (wd2.waiting_tasks_count - wd1.waiting_tasks_count) > 0 - AND wd2.wait_time_ms-wd1.wait_time_ms > 0 - ORDER BY [Wait Time (Seconds)] DESC; - END; - ELSE IF @ExpertMode = 0 AND @OutputType <> 'NONE' AND @OutputXMLasNVARCHAR = 0 AND @SinceStartup = 0 - BEGIN - SELECT [Priority] , - [FindingsGroup] , - [Finding] , - [URL] , - CAST(@StockDetailsHeader + [Details] + @StockDetailsFooter AS XML) AS Details, - CAST(@StockWarningHeader + HowToStopIt + @StockWarningFooter AS XML) AS HowToStopIt, - [QueryText], - [QueryPlan] - FROM #BlitzFirstResults - WHERE (@Seconds > 0 OR (Priority IN (0, 250, 251, 255))) /* For @Seconds = 0, filter out broken checks for now */ - ORDER BY Priority , - FindingsGroup , - CASE - WHEN CheckID = 6 THEN DetailsInt - ELSE 0 - END DESC, - Finding, - ID, - CAST(Details AS NVARCHAR(4000)); - END; - ELSE IF @OutputType <> 'NONE' AND @OutputXMLasNVARCHAR = 1 AND @SinceStartup = 0 - BEGIN - SELECT [Priority] , - [FindingsGroup] , - [Finding] , - [URL] , - CAST(@StockDetailsHeader + [Details] + @StockDetailsFooter AS NVARCHAR(MAX)) AS Details, - CAST([HowToStopIt] AS NVARCHAR(MAX)) AS HowToStopIt, - CAST([QueryText] AS NVARCHAR(MAX)) AS QueryText, - CAST([QueryPlan] AS NVARCHAR(MAX)) AS QueryPlan - FROM #BlitzFirstResults - WHERE (@Seconds > 0 OR (Priority IN (0, 250, 251, 255))) /* For @Seconds = 0, filter out broken checks for now */ - ORDER BY Priority , - FindingsGroup , - CASE - WHEN CheckID = 6 THEN DetailsInt - ELSE 0 - END DESC, - Finding, - ID, - CAST(Details AS NVARCHAR(4000)); - END; - ELSE IF @ExpertMode = 1 AND @OutputType <> 'NONE' - BEGIN - IF @SinceStartup = 0 - SELECT r.[Priority] , - r.[FindingsGroup] , - r.[Finding] , - r.[URL] , - CAST(@StockDetailsHeader + r.[Details] + @StockDetailsFooter AS XML) AS Details, - CAST(@StockWarningHeader + r.HowToStopIt + @StockWarningFooter AS XML) AS HowToStopIt, - r.[CheckID] , - r.[StartTime], - r.[LoginName], - r.[NTUserName], - r.[OriginalLoginName], - r.[ProgramName], - r.[HostName], - r.[DatabaseID], - r.[DatabaseName], - r.[OpenTransactionCount], - r.[QueryPlan], - r.[QueryText], - qsNow.plan_handle AS PlanHandle, - qsNow.sql_handle AS SqlHandle, - qsNow.statement_start_offset AS StatementStartOffset, - qsNow.statement_end_offset AS StatementEndOffset, - [Executions] = qsNow.execution_count - (COALESCE(qsFirst.execution_count, 0)), - [ExecutionsPercent] = CAST(100.0 * (qsNow.execution_count - (COALESCE(qsFirst.execution_count, 0))) / (qsTotal.execution_count - qsTotalFirst.execution_count) AS DECIMAL(6,2)), - [Duration] = qsNow.total_elapsed_time - (COALESCE(qsFirst.total_elapsed_time, 0)), - [DurationPercent] = CAST(100.0 * (qsNow.total_elapsed_time - (COALESCE(qsFirst.total_elapsed_time, 0))) / (qsTotal.total_elapsed_time - qsTotalFirst.total_elapsed_time) AS DECIMAL(6,2)), - [CPU] = qsNow.total_worker_time - (COALESCE(qsFirst.total_worker_time, 0)), - [CPUPercent] = CAST(100.0 * (qsNow.total_worker_time - (COALESCE(qsFirst.total_worker_time, 0))) / (qsTotal.total_worker_time - qsTotalFirst.total_worker_time) AS DECIMAL(6,2)), - [Reads] = qsNow.total_logical_reads - (COALESCE(qsFirst.total_logical_reads, 0)), - [ReadsPercent] = CAST(100.0 * (qsNow.total_logical_reads - (COALESCE(qsFirst.total_logical_reads, 0))) / (qsTotal.total_logical_reads - qsTotalFirst.total_logical_reads) AS DECIMAL(6,2)), - [PlanCreationTime] = CONVERT(NVARCHAR(100), qsNow.creation_time ,121), - [TotalExecutions] = qsNow.execution_count, - [TotalExecutionsPercent] = CAST(100.0 * qsNow.execution_count / qsTotal.execution_count AS DECIMAL(6,2)), - [TotalDuration] = qsNow.total_elapsed_time, - [TotalDurationPercent] = CAST(100.0 * qsNow.total_elapsed_time / qsTotal.total_elapsed_time AS DECIMAL(6,2)), - [TotalCPU] = qsNow.total_worker_time, - [TotalCPUPercent] = CAST(100.0 * qsNow.total_worker_time / qsTotal.total_worker_time AS DECIMAL(6,2)), - [TotalReads] = qsNow.total_logical_reads, - [TotalReadsPercent] = CAST(100.0 * qsNow.total_logical_reads / qsTotal.total_logical_reads AS DECIMAL(6,2)), - r.[DetailsInt] - FROM #BlitzFirstResults r - LEFT OUTER JOIN #QueryStats qsTotal ON qsTotal.Pass = 0 - LEFT OUTER JOIN #QueryStats qsTotalFirst ON qsTotalFirst.Pass = -1 - LEFT OUTER JOIN #QueryStats qsNow ON r.QueryStatsNowID = qsNow.ID - LEFT OUTER JOIN #QueryStats qsFirst ON r.QueryStatsFirstID = qsFirst.ID - WHERE (@Seconds > 0 OR (Priority IN (0, 250, 251, 255))) /* For @Seconds = 0, filter out broken checks for now */ - ORDER BY r.Priority , - r.FindingsGroup , - CASE - WHEN r.CheckID = 6 THEN DetailsInt - ELSE 0 - END DESC, - r.Finding, - r.ID, - CAST(r.Details AS NVARCHAR(4000)); +BEGIN TRY +---------------------------------------- +--If @TableName is specified, just return information for that table. +--The @Mode parameter doesn't matter if you're looking at a specific table. +---------------------------------------- +IF @TableName IS NOT NULL +BEGIN + RAISERROR(N'@TableName specified, giving detail only on that table.', 0,1) WITH NOWAIT; + + --We do a left join here in case this is a disabled NC. + --In that case, it won't have any size info/pages allocated. + + + WITH table_mode_cte AS ( + SELECT + s.db_schema_object_indexid, + s.key_column_names, + s.index_definition, + ISNULL(s.secret_columns,N'') AS secret_columns, + s.fill_factor, + s.index_usage_summary, + sz.index_op_stats, + ISNULL(sz.index_size_summary,'') /*disabled NCs will be null*/ AS index_size_summary, + partition_compression_detail , + ISNULL(sz.index_lock_wait_summary,'') AS index_lock_wait_summary, + s.is_referenced_by_foreign_key, + (SELECT COUNT(*) + FROM #ForeignKeys fk WHERE fk.parent_object_id=s.object_id + AND PATINDEX (fk.parent_fk_columns, s.key_column_names)=1) AS FKs_covered_by_index, + s.last_user_seek, + s.last_user_scan, + s.last_user_lookup, + s.last_user_update, + s.create_date, + s.modify_date, + sz.page_latch_wait_count, + CONVERT(VARCHAR(10), (sz.page_latch_wait_in_ms / 1000) / 86400) + ':' + CONVERT(VARCHAR(20), DATEADD(s, (sz.page_latch_wait_in_ms / 1000), 0), 108) AS page_latch_wait_time, + sz.page_io_latch_wait_count, + CONVERT(VARCHAR(10), (sz.page_io_latch_wait_in_ms / 1000) / 86400) + ':' + CONVERT(VARCHAR(20), DATEADD(s, (sz.page_io_latch_wait_in_ms / 1000), 0), 108) AS page_io_latch_wait_time, + ct.create_tsql, + CASE + WHEN s.is_primary_key = 1 AND s.index_definition <> '[HEAP]' + THEN N'--ALTER TABLE ' + QUOTENAME(s.[database_name]) + N'.' + QUOTENAME(s.[schema_name]) + N'.' + QUOTENAME(s.[object_name]) + + N' DROP CONSTRAINT ' + QUOTENAME(s.index_name) + N';' + WHEN s.is_primary_key = 0 AND s.index_definition <> '[HEAP]' + THEN N'--DROP INDEX '+ QUOTENAME(s.index_name) + N' ON ' + QUOTENAME(s.[database_name]) + N'.' + + QUOTENAME(s.[schema_name]) + N'.' + QUOTENAME(s.[object_name]) + N';' + ELSE N'' + END AS drop_tsql, + 1 AS display_order + FROM #IndexSanity s + LEFT JOIN #IndexSanitySize sz ON + s.index_sanity_id=sz.index_sanity_id + LEFT JOIN #IndexCreateTsql ct ON + s.index_sanity_id=ct.index_sanity_id + LEFT JOIN #PartitionCompressionInfo pci ON + pci.index_sanity_id = s.index_sanity_id + WHERE s.[object_id]=@ObjectID + UNION ALL + SELECT N'Database ' + QUOTENAME(@DatabaseName) + N' as of ' + CONVERT(NVARCHAR(16),GETDATE(),121) + + N' (' + @ScriptVersionName + ')' , + N'SQL Server First Responder Kit' , + N'http://FirstResponderKit.org' , + N'From Your Community Volunteers', + NULL,@DaysUptimeInsertValue,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL, + 0 AS display_order + ) + SELECT + db_schema_object_indexid AS [Details: db_schema.table.index(indexid)], + index_definition AS [Definition: [Property]] ColumnName {datatype maxbytes}], + secret_columns AS [Secret Columns], + fill_factor AS [Fillfactor], + index_usage_summary AS [Usage Stats], + index_op_stats AS [Op Stats], + index_size_summary AS [Size], + partition_compression_detail AS [Compression Type], + index_lock_wait_summary AS [Lock Waits], + is_referenced_by_foreign_key AS [Referenced by FK?], + FKs_covered_by_index AS [FK Covered by Index?], + last_user_seek AS [Last User Seek], + last_user_scan AS [Last User Scan], + last_user_lookup AS [Last User Lookup], + last_user_update AS [Last User Write], + create_date AS [Created], + modify_date AS [Last Modified], + page_latch_wait_count AS [Page Latch Wait Count], + page_latch_wait_time as [Page Latch Wait Time (D:H:M:S)], + page_io_latch_wait_count AS [Page IO Latch Wait Count], + page_io_latch_wait_time as [Page IO Latch Wait Time (D:H:M:S)], + create_tsql AS [Create TSQL], + drop_tsql AS [Drop TSQL] + FROM table_mode_cte + ORDER BY display_order ASC, key_column_names ASC + OPTION ( RECOMPILE ); - ------------------------- - --What happened: #WaitStats - ------------------------- - IF @Seconds = 0 - BEGIN - /* Measure waits in hours */ - ;WITH max_batch AS ( - SELECT MAX(SampleTime) AS SampleTime - FROM #WaitStats - ) - SELECT - 'WAIT STATS' AS Pattern, - b.SampleTime AS [Sample Ended], - CAST(DATEDIFF(mi,wd1.SampleTime, wd2.SampleTime) / 60.0 AS DECIMAL(18,1)) AS [Hours Sample], - wd1.wait_type, - COALESCE(wcat.WaitCategory, 'Other') AS wait_category, - CAST(c.[Wait Time (Seconds)] / 60.0 / 60 AS DECIMAL(18,1)) AS [Wait Time (Hours)], - CAST((wd2.wait_time_ms - wd1.wait_time_ms) / 1000.0 / cores.cpu_count / DATEDIFF(ss, wd1.SampleTime, wd2.SampleTime) AS DECIMAL(18,1)) AS [Per Core Per Hour], - CAST(c.[Signal Wait Time (Seconds)] / 60.0 / 60 AS DECIMAL(18,1)) AS [Signal Wait Time (Hours)], - CASE WHEN c.[Wait Time (Seconds)] > 0 - THEN CAST(100.*(c.[Signal Wait Time (Seconds)]/c.[Wait Time (Seconds)]) AS NUMERIC(4,1)) - ELSE 0 END AS [Percent Signal Waits], - (wd2.waiting_tasks_count - wd1.waiting_tasks_count) AS [Number of Waits], - CASE WHEN (wd2.waiting_tasks_count - wd1.waiting_tasks_count) > 0 - THEN - CAST((wd2.wait_time_ms-wd1.wait_time_ms)/ - (1.0*(wd2.waiting_tasks_count - wd1.waiting_tasks_count)) AS NUMERIC(12,1)) - ELSE 0 END AS [Avg ms Per Wait], - N'https://www.sqlskills.com/help/waits/' + LOWER(wd1.wait_type) + '/' AS URL - FROM max_batch b - JOIN #WaitStats wd2 ON - wd2.SampleTime =b.SampleTime - JOIN #WaitStats wd1 ON - wd1.wait_type=wd2.wait_type AND - wd2.SampleTime > wd1.SampleTime - CROSS APPLY (SELECT SUM(1) AS cpu_count FROM sys.dm_os_schedulers WHERE status = 'VISIBLE ONLINE' AND is_online = 1) AS cores - CROSS APPLY (SELECT - CAST((wd2.wait_time_ms-wd1.wait_time_ms)/1000. AS NUMERIC(12,1)) AS [Wait Time (Seconds)], - CAST((wd2.signal_wait_time_ms - wd1.signal_wait_time_ms)/1000. AS NUMERIC(12,1)) AS [Signal Wait Time (Seconds)]) AS c - LEFT OUTER JOIN ##WaitCategories wcat ON wd1.wait_type = wcat.WaitType - WHERE (wd2.waiting_tasks_count - wd1.waiting_tasks_count) > 0 - AND wd2.wait_time_ms-wd1.wait_time_ms > 0 - ORDER BY [Wait Time (Seconds)] DESC; - END; - ELSE - BEGIN - /* Measure waits in seconds */ - ;WITH max_batch AS ( - SELECT MAX(SampleTime) AS SampleTime - FROM #WaitStats - ) - SELECT - 'WAIT STATS' AS Pattern, - b.SampleTime AS [Sample Ended], - DATEDIFF(ss,wd1.SampleTime, wd2.SampleTime) AS [Seconds Sample], - wd1.wait_type, - COALESCE(wcat.WaitCategory, 'Other') AS wait_category, - c.[Wait Time (Seconds)], - CAST((CAST(wd2.wait_time_ms - wd1.wait_time_ms AS MONEY)) / 1000.0 / cores.cpu_count / DATEDIFF(ss, wd1.SampleTime, wd2.SampleTime) AS DECIMAL(18,1)) AS [Per Core Per Second], - c.[Signal Wait Time (Seconds)], - CASE WHEN c.[Wait Time (Seconds)] > 0 - THEN CAST(100.*(c.[Signal Wait Time (Seconds)]/c.[Wait Time (Seconds)]) AS NUMERIC(4,1)) - ELSE 0 END AS [Percent Signal Waits], - (wd2.waiting_tasks_count - wd1.waiting_tasks_count) AS [Number of Waits], - CASE WHEN (wd2.waiting_tasks_count - wd1.waiting_tasks_count) > 0 - THEN - CAST((wd2.wait_time_ms-wd1.wait_time_ms)/ - (1.0*(wd2.waiting_tasks_count - wd1.waiting_tasks_count)) AS NUMERIC(12,1)) - ELSE 0 END AS [Avg ms Per Wait], - N'https://www.sqlskills.com/help/waits/' + LOWER(wd1.wait_type) + '/' AS URL - FROM max_batch b - JOIN #WaitStats wd2 ON - wd2.SampleTime =b.SampleTime - JOIN #WaitStats wd1 ON - wd1.wait_type=wd2.wait_type AND - wd2.SampleTime > wd1.SampleTime - CROSS APPLY (SELECT SUM(1) AS cpu_count FROM sys.dm_os_schedulers WHERE status = 'VISIBLE ONLINE' AND is_online = 1) AS cores - CROSS APPLY (SELECT - CAST((wd2.wait_time_ms-wd1.wait_time_ms)/1000. AS NUMERIC(12,1)) AS [Wait Time (Seconds)], - CAST((wd2.signal_wait_time_ms - wd1.signal_wait_time_ms)/1000. AS NUMERIC(12,1)) AS [Signal Wait Time (Seconds)]) AS c - LEFT OUTER JOIN ##WaitCategories wcat ON wd1.wait_type = wcat.WaitType - WHERE (wd2.waiting_tasks_count - wd1.waiting_tasks_count) > 0 - AND wd2.wait_time_ms-wd1.wait_time_ms > 0 - ORDER BY [Wait Time (Seconds)] DESC; - END; + IF (SELECT TOP 1 [object_id] FROM #MissingIndexes mi) IS NOT NULL + BEGIN; - ------------------------- - --What happened: #FileStats - ------------------------- - WITH readstats AS ( - SELECT 'PHYSICAL READS' AS Pattern, - ROW_NUMBER() OVER (ORDER BY wd2.avg_stall_read_ms DESC) AS StallRank, - wd2.SampleTime AS [Sample Time], - DATEDIFF(ss,wd1.SampleTime, wd2.SampleTime) AS [Sample (seconds)], - wd1.DatabaseName , - wd1.FileLogicalName AS [File Name], - UPPER(SUBSTRING(wd1.PhysicalName, 1, 2)) AS [Drive] , - wd1.SizeOnDiskMB , - ( wd2.num_of_reads - wd1.num_of_reads ) AS [# Reads/Writes], - CASE WHEN wd2.num_of_reads - wd1.num_of_reads > 0 - THEN CAST(( wd2.bytes_read - wd1.bytes_read)/1024./1024. AS NUMERIC(21,1)) - ELSE 0 - END AS [MB Read/Written], - wd2.avg_stall_read_ms AS [Avg Stall (ms)], - wd1.PhysicalName AS [file physical name] - FROM #FileStats wd2 - JOIN #FileStats wd1 ON wd2.SampleTime > wd1.SampleTime - AND wd1.DatabaseID = wd2.DatabaseID - AND wd1.FileID = wd2.FileID - ), - writestats AS ( - SELECT - 'PHYSICAL WRITES' AS Pattern, - ROW_NUMBER() OVER (ORDER BY wd2.avg_stall_write_ms DESC) AS StallRank, - wd2.SampleTime AS [Sample Time], - DATEDIFF(ss,wd1.SampleTime, wd2.SampleTime) AS [Sample (seconds)], - wd1.DatabaseName , - wd1.FileLogicalName AS [File Name], - UPPER(SUBSTRING(wd1.PhysicalName, 1, 2)) AS [Drive] , - wd1.SizeOnDiskMB , - ( wd2.num_of_writes - wd1.num_of_writes ) AS [# Reads/Writes], - CASE WHEN wd2.num_of_writes - wd1.num_of_writes > 0 - THEN CAST(( wd2.bytes_written - wd1.bytes_written)/1024./1024. AS NUMERIC(21,1)) - ELSE 0 - END AS [MB Read/Written], - wd2.avg_stall_write_ms AS [Avg Stall (ms)], - wd1.PhysicalName AS [file physical name] - FROM #FileStats wd2 - JOIN #FileStats wd1 ON wd2.SampleTime > wd1.SampleTime - AND wd1.DatabaseID = wd2.DatabaseID - AND wd1.FileID = wd2.FileID - ) - SELECT - Pattern, [Sample Time], [Sample (seconds)], [File Name], [Drive], [# Reads/Writes],[MB Read/Written],[Avg Stall (ms)], [file physical name], [StallRank] - FROM readstats - WHERE StallRank <=20 AND [MB Read/Written] > 0 - UNION ALL - SELECT Pattern, [Sample Time], [Sample (seconds)], [File Name], [Drive], [# Reads/Writes],[MB Read/Written],[Avg Stall (ms)], [file physical name], [StallRank] - FROM writestats - WHERE StallRank <=20 AND [MB Read/Written] > 0 - ORDER BY Pattern, StallRank; + WITH create_date AS ( + SELECT i.database_id, + i.schema_name, + i.[object_id], + ISNULL(NULLIF(MAX(DATEDIFF(DAY, i.create_date, SYSDATETIME())), 0), 1) AS create_days + FROM #IndexSanity AS i + GROUP BY i.database_id, i.schema_name, i.object_id + ) + SELECT N'Missing index.' AS Finding , + N'https://www.brentozar.com/go/Indexaphobia' AS URL , + mi.[statement] + + ' Est. Benefit: ' + + CASE WHEN magic_benefit_number >= 922337203685477 THEN '>= 922,337,203,685,477' + ELSE REPLACE(CONVERT(NVARCHAR(256),CAST(CAST( + (magic_benefit_number / CASE WHEN cd.create_days < @DaysUptime THEN cd.create_days ELSE @DaysUptime END) + AS BIGINT) AS MONEY), 1), '.00', '') + END AS [Estimated Benefit], + missing_index_details AS [Missing Index Request] , + index_estimated_impact AS [Estimated Impact], + create_tsql AS [Create TSQL], + sample_query_plan AS [Sample Query Plan] + FROM #MissingIndexes mi + LEFT JOIN create_date AS cd + ON mi.[object_id] = cd.object_id + AND mi.database_id = cd.database_id + AND mi.schema_name = cd.schema_name + WHERE mi.[object_id] = @ObjectID + AND (@ShowAllMissingIndexRequests=1 + /* Minimum benefit threshold = 100k/day of uptime OR since table creation date, whichever is lower*/ + OR (magic_benefit_number / CASE WHEN cd.create_days < @DaysUptime THEN cd.create_days ELSE @DaysUptime END) >= 100000) + ORDER BY magic_benefit_number DESC + OPTION ( RECOMPILE ); + END; + ELSE + SELECT 'No missing indexes.' AS finding; + + SELECT + column_name AS [Column Name], + (SELECT COUNT(*) + FROM #IndexColumns c2 + WHERE c2.column_name=c.column_name + AND c2.key_ordinal IS NOT NULL) + + CASE WHEN c.index_id = 1 AND c.key_ordinal IS NOT NULL THEN + -1+ (SELECT COUNT(DISTINCT index_id) + FROM #IndexColumns c3 + WHERE c3.index_id NOT IN (0,1)) + ELSE 0 END + AS [Found In], + system_type_name + + CASE max_length WHEN -1 THEN N' (max)' ELSE + CASE + WHEN system_type_name IN (N'char',N'varchar',N'binary',N'varbinary') THEN N' (' + CAST(max_length AS NVARCHAR(20)) + N')' + WHEN system_type_name IN (N'nchar',N'nvarchar') THEN N' (' + CAST(max_length/2 AS NVARCHAR(20)) + N')' + ELSE '' + END + END + AS [Type], + CASE is_computed WHEN 1 THEN 'yes' ELSE '' END AS [Computed?], + max_length AS [Length (max bytes)], + [precision] AS [Prec], + [scale] AS [Scale], + CASE is_nullable WHEN 1 THEN 'yes' ELSE '' END AS [Nullable?], + CASE is_identity WHEN 1 THEN 'yes' ELSE '' END AS [Identity?], + CASE is_replicated WHEN 1 THEN 'yes' ELSE '' END AS [Replicated?], + CASE is_sparse WHEN 1 THEN 'yes' ELSE '' END AS [Sparse?], + CASE is_filestream WHEN 1 THEN 'yes' ELSE '' END AS [Filestream?], + collation_name AS [Collation] + FROM #IndexColumns AS c + WHERE index_id IN (0,1); + IF (SELECT TOP 1 parent_object_id FROM #ForeignKeys) IS NOT NULL + BEGIN + SELECT [database_name] + N':' + parent_object_name + N': ' + foreign_key_name AS [Foreign Key], + parent_fk_columns AS [Foreign Key Columns], + referenced_object_name AS [Referenced Table], + referenced_fk_columns AS [Referenced Table Columns], + is_disabled AS [Is Disabled?], + is_not_trusted AS [Not Trusted?], + is_not_for_replication [Not for Replication?], + [update_referential_action_desc] AS [Cascading Updates?], + [delete_referential_action_desc] AS [Cascading Deletes?] + FROM #ForeignKeys + ORDER BY [Foreign Key] + OPTION ( RECOMPILE ); + END; + ELSE + SELECT 'No foreign keys.' AS finding; - ------------------------- - --What happened: #PerfmonStats - ------------------------- + /* Show histograms for all stats on this table. More info: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/1900 */ + IF EXISTS (SELECT * FROM sys.all_objects WHERE name = 'dm_db_stats_histogram') + BEGIN + SET @dsql=N'SELECT s.name AS [Stat Name], c.name AS [Leading Column Name], hist.step_number AS [Step Number], + hist.range_high_key AS [Range High Key], hist.range_rows AS [Range Rows], + hist.equal_rows AS [Equal Rows], hist.distinct_range_rows AS [Distinct Range Rows], hist.average_range_rows AS [Average Range Rows], + s.auto_created AS [Auto-Created], s.user_created AS [User-Created], + props.last_updated AS [Last Updated], props.modification_counter AS [Modification Counter], props.rows AS [Table Rows], + props.rows_sampled AS [Rows Sampled], s.stats_id AS [StatsID] + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.stats AS s + INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.stats_columns sc ON s.object_id = sc.object_id AND s.stats_id = sc.stats_id AND sc.stats_column_id = 1 + INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns c ON sc.object_id = c.object_id AND sc.column_id = c.column_id + CROSS APPLY ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_stats_properties(s.object_id, s.stats_id) AS props + CROSS APPLY ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_stats_histogram(s.[object_id], s.stats_id) AS hist + WHERE s.object_id = @ObjectID + ORDER BY s.auto_created, s.user_created, s.name, hist.step_number;'; + EXEC sp_executesql @dsql, N'@ObjectID INT', @ObjectID; + END - SELECT 'PERFMON' AS Pattern, pLast.[object_name], pLast.counter_name, pLast.instance_name, - pFirst.SampleTime AS FirstSampleTime, pFirst.cntr_value AS FirstSampleValue, - pLast.SampleTime AS LastSampleTime, pLast.cntr_value AS LastSampleValue, - pLast.cntr_value - pFirst.cntr_value AS ValueDelta, - ((1.0 * pLast.cntr_value - pFirst.cntr_value) / DATEDIFF(ss, pFirst.SampleTime, pLast.SampleTime)) AS ValuePerSecond - FROM #PerfmonStats pLast - INNER JOIN #PerfmonStats pFirst ON pFirst.[object_name] = pLast.[object_name] AND pFirst.counter_name = pLast.counter_name AND (pFirst.instance_name = pLast.instance_name OR (pFirst.instance_name IS NULL AND pLast.instance_name IS NULL)) - AND pLast.ID > pFirst.ID - WHERE pLast.cntr_value <> pFirst.cntr_value - ORDER BY Pattern, pLast.[object_name], pLast.counter_name, pLast.instance_name; + /* Visualize columnstore index contents. More info: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2584 */ + IF 2 = (SELECT SUM(1) FROM sys.all_objects WHERE name IN ('column_store_row_groups','column_store_segments')) + BEGIN + RAISERROR(N'Visualizing columnstore index contents.', 0,1) WITH NOWAIT; + SET @dsql = N'USE ' + QUOTENAME(@DatabaseName) + N'; + IF EXISTS(SELECT * FROM ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_row_groups WHERE object_id = @ObjectID) + BEGIN + SET @ColumnList = N''''; + WITH DistinctColumns AS ( + SELECT DISTINCT QUOTENAME(c.name) AS column_name, c.column_id + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.partitions p + INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns c ON p.object_id = c.object_id + INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.index_columns ic on ic.column_id = c.column_id and ic.object_id = c.object_id AND ic.index_id = p.index_id + INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.types t ON c.system_type_id = t.system_type_id AND c.user_type_id = t.user_type_id + WHERE p.object_id = @ObjectID + AND EXISTS (SELECT * FROM ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_segments seg WHERE p.partition_id = seg.partition_id AND seg.column_id = ic.index_column_id) + AND p.data_compression IN (3,4) + ) + SELECT @ColumnList = @ColumnList + column_name + N'', '' + FROM DistinctColumns + ORDER BY column_id; + END'; - ------------------------- - --What happened: #QueryStats - ------------------------- - IF @CheckProcedureCache = 1 - BEGIN - - SELECT qsNow.*, qsFirst.* - FROM #QueryStats qsNow - INNER JOIN #QueryStats qsFirst ON qsNow.[sql_handle] = qsFirst.[sql_handle] AND qsNow.statement_start_offset = qsFirst.statement_start_offset AND qsNow.statement_end_offset = qsFirst.statement_end_offset AND qsNow.plan_generation_num = qsFirst.plan_generation_num AND qsNow.plan_handle = qsFirst.plan_handle AND qsFirst.Pass = 1 - WHERE qsNow.Pass = 2; - END; - ELSE + IF @Debug = 1 BEGIN - SELECT 'Plan Cache' AS [Pattern], 'Plan cache not analyzed' AS [Finding], 'Use @CheckProcedureCache = 1 or run sp_BlitzCache for more analysis' AS [More Info], CONVERT(XML, @StockDetailsHeader + 'firstresponderkit.org' + @StockDetailsFooter) AS [Details]; + PRINT SUBSTRING(@dsql, 0, 4000); + PRINT SUBSTRING(@dsql, 4000, 8000); + PRINT SUBSTRING(@dsql, 8000, 12000); + PRINT SUBSTRING(@dsql, 12000, 16000); + PRINT SUBSTRING(@dsql, 16000, 20000); + PRINT SUBSTRING(@dsql, 20000, 24000); + PRINT SUBSTRING(@dsql, 24000, 28000); + PRINT SUBSTRING(@dsql, 28000, 32000); + PRINT SUBSTRING(@dsql, 32000, 36000); + PRINT SUBSTRING(@dsql, 36000, 40000); END; - END; - DROP TABLE #BlitzFirstResults; + EXEC sp_executesql @dsql, N'@ObjectID INT, @ColumnList NVARCHAR(MAX) OUTPUT', @ObjectID, @ColumnList OUTPUT; - /* What's running right now? This is the first and last result set. */ - IF @SinceStartup = 0 AND @Seconds > 0 AND @ExpertMode = 1 AND @OutputType <> 'NONE' - BEGIN - IF OBJECT_ID('master.dbo.sp_BlitzWho') IS NULL AND OBJECT_ID('dbo.sp_BlitzWho') IS NULL + IF @Debug = 1 + SELECT @ColumnList AS ColumnstoreColumnList; + + IF @ColumnList <> '' BEGIN - PRINT N'sp_BlitzWho is not installed in the current database_files. You can get a copy from http://FirstResponderKit.org'; - END; - ELSE + /* Remove the trailing comma */ + SET @ColumnList = LEFT(@ColumnList, LEN(@ColumnList) - 1); + + SET @dsql = N'USE ' + QUOTENAME(@DatabaseName) + N'; SELECT partition_number, row_group_id, total_rows, deleted_rows, ' + @ColumnList + N' + FROM ( + SELECT c.name AS column_name, p.partition_number, + rg.row_group_id, rg.total_rows, rg.deleted_rows, + details = CAST(seg.min_data_id AS VARCHAR(20)) + '' to '' + CAST(seg.max_data_id AS VARCHAR(20)) + '', '' + CAST(CAST((seg.on_disk_size / 1024.0 / 1024) AS DECIMAL(18,0)) AS VARCHAR(20)) + '' MB'' + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_row_groups rg + INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns c ON rg.object_id = c.object_id + INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partitions p ON rg.object_id = p.object_id AND rg.partition_number = p.partition_number + INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.index_columns ic on ic.column_id = c.column_id AND ic.object_id = c.object_id AND ic.index_id = p.index_id + LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_segments seg ON p.partition_id = seg.partition_id AND ic.index_column_id = seg.column_id AND rg.row_group_id = seg.segment_id + WHERE rg.object_id = @ObjectID + ) AS x + PIVOT (MAX(details) FOR column_name IN ( ' + @ColumnList + N')) AS pivot1 + ORDER BY partition_number, row_group_id;'; + + IF @Debug = 1 + BEGIN + PRINT SUBSTRING(@dsql, 0, 4000); + PRINT SUBSTRING(@dsql, 4000, 8000); + PRINT SUBSTRING(@dsql, 8000, 12000); + PRINT SUBSTRING(@dsql, 12000, 16000); + PRINT SUBSTRING(@dsql, 16000, 20000); + PRINT SUBSTRING(@dsql, 20000, 24000); + PRINT SUBSTRING(@dsql, 24000, 28000); + PRINT SUBSTRING(@dsql, 28000, 32000); + PRINT SUBSTRING(@dsql, 32000, 36000); + PRINT SUBSTRING(@dsql, 36000, 40000); + END; + + IF @dsql IS NULL + RAISERROR('@dsql is null',16,1); + ELSE + EXEC sp_executesql @dsql, N'@ObjectID INT', @ObjectID; + END + ELSE /* No columns were found for this object */ BEGIN - EXEC (@BlitzWho); - END; - END; /* IF @SinceStartup = 0 AND @Seconds > 0 AND @ExpertMode = 1 AND @OutputType <> 'NONE' - What's running right now? This is the first and last result set. */ + SELECT N'No compressed columnstore rowgroups were found for this object.' AS Columnstore_Visualization + UNION ALL + SELECT N'SELECT * FROM ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_row_groups WHERE object_id = ' + CAST(@ObjectID AS NVARCHAR(100)); + END + RAISERROR(N'Done visualizing columnstore index contents.', 0,1) WITH NOWAIT; + END + +END; /* IF @TableName IS NOT NULL */ -END; /* IF @LogMessage IS NULL */ -END; /* ELSE IF @OutputType = 'SCHEMA' */ -SET NOCOUNT OFF; -GO -/* How to run it: -EXEC dbo.sp_BlitzFirst -With extra diagnostic info: -EXEC dbo.sp_BlitzFirst @ExpertMode = 1; -Saving output to tables: -EXEC sp_BlitzFirst - @OutputDatabaseName = 'DBAtools' -, @OutputSchemaName = 'dbo' -, @OutputTableName = 'BlitzFirst' -, @OutputTableNameFileStats = 'BlitzFirst_FileStats' -, @OutputTableNamePerfmonStats = 'BlitzFirst_PerfmonStats' -, @OutputTableNameWaitStats = 'BlitzFirst_WaitStats' -, @OutputTableNameBlitzCache = 'BlitzCache' -, @OutputTableNameBlitzWho = 'BlitzWho' -*/ -SET ANSI_NULLS ON; -SET ANSI_PADDING ON; -SET ANSI_WARNINGS ON; -SET ARITHABORT ON; -SET CONCAT_NULL_YIELDS_NULL ON; -SET QUOTED_IDENTIFIER ON; -SET STATISTICS IO OFF; -SET STATISTICS TIME OFF; -GO -IF OBJECT_ID('dbo.sp_BlitzIndex') IS NULL - EXEC ('CREATE PROCEDURE dbo.sp_BlitzIndex AS RETURN 0;'); -GO -ALTER PROCEDURE dbo.sp_BlitzIndex - @DatabaseName NVARCHAR(128) = NULL, /*Defaults to current DB if not specified*/ - @SchemaName NVARCHAR(128) = NULL, /*Requires table_name as well.*/ - @TableName NVARCHAR(128) = NULL, /*Requires schema_name as well.*/ - @Mode TINYINT=0, /*0=Diagnose, 1=Summarize, 2=Index Usage Detail, 3=Missing Index Detail, 4=Diagnose Details*/ - /*Note:@Mode doesn't matter if you're specifying schema_name and @TableName.*/ - @Filter TINYINT = 0, /* 0=no filter (default). 1=No low-usage warnings for objects with 0 reads. 2=Only warn for objects >= 500MB */ - /*Note:@Filter doesn't do anything unless @Mode=0*/ - @SkipPartitions BIT = 0, - @SkipStatistics BIT = 1, - @GetAllDatabases BIT = 0, - @BringThePain BIT = 0, - @IgnoreDatabases NVARCHAR(MAX) = NULL, /* Comma-delimited list of databases you want to skip */ - @ThresholdMB INT = 250 /* Number of megabytes that an object must be before we include it in basic results */, - @OutputType VARCHAR(20) = 'TABLE' , - @OutputServerName NVARCHAR(256) = NULL , - @OutputDatabaseName NVARCHAR(256) = NULL , - @OutputSchemaName NVARCHAR(256) = NULL , - @OutputTableName NVARCHAR(256) = NULL , - @IncludeInactiveIndexes BIT = 0 /* Will skip indexes with no reads or writes */, - @ShowAllMissingIndexRequests BIT = 0 /*Will make all missing index requests show up*/, - @SortOrder NVARCHAR(50) = NULL, /* Only affects @Mode = 2. */ - @SortDirection NVARCHAR(4) = 'DESC', /* Only affects @Mode = 2. */ - @Help TINYINT = 0, - @Debug BIT = 0, - @Version VARCHAR(30) = NULL OUTPUT, - @VersionDate DATETIME = NULL OUTPUT, - @VersionCheckMode BIT = 0 -WITH RECOMPILE -AS -SET NOCOUNT ON; -SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.0', @VersionDate = '20210117'; -SET @OutputType = UPPER(@OutputType); -IF(@VersionCheckMode = 1) -BEGIN - RETURN; -END; -IF @Help = 1 -BEGIN -PRINT ' -/* -sp_BlitzIndex from http://FirstResponderKit.org - -This script analyzes the design and performance of your indexes. -To learn more, visit http://FirstResponderKit.org where you can download new -versions for free, watch training videos on how it works, get more info on -the findings, contribute your own code, and more. -Known limitations of this version: - - Only Microsoft-supported versions of SQL Server. Sorry, 2005 and 2000. - - Index create statements are just to give you a rough idea of the syntax. It includes filters and fillfactor. - -- Example 1: index creates use ONLINE=? instead of ONLINE=ON / ONLINE=OFF. This is because it is important - for the user to understand if it is going to be offline and not just run a script. - -- Example 2: they do not include all the options the index may have been created with (padding, compression - filegroup/partition scheme etc.) - -- (The compression and filegroup index create syntax is not trivial because it is set at the partition - level and is not trivial to code.) - - Does not advise you about data modeling for clustered indexes and primary keys (primarily looks for signs of insanity.) -Unknown limitations of this version: - - We knew them once, but we forgot. -MIT License -Copyright (c) 2021 Brent Ozar Unlimited -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -'; -RETURN; -END; /* @Help = 1 */ -DECLARE @ScriptVersionName NVARCHAR(50); -DECLARE @DaysUptime NUMERIC(23,2); -DECLARE @DatabaseID INT; -DECLARE @ObjectID INT; -DECLARE @dsql NVARCHAR(MAX); -DECLARE @params NVARCHAR(MAX); -DECLARE @msg NVARCHAR(4000); -DECLARE @ErrorSeverity INT; -DECLARE @ErrorState INT; -DECLARE @Rowcount BIGINT; -DECLARE @SQLServerProductVersion NVARCHAR(128); -DECLARE @SQLServerEdition INT; -DECLARE @FilterMB INT; -DECLARE @collation NVARCHAR(256); -DECLARE @NumDatabases INT; -DECLARE @LineFeed NVARCHAR(5); -DECLARE @DaysUptimeInsertValue NVARCHAR(256); -DECLARE @DatabaseToIgnore NVARCHAR(MAX); -DECLARE @ColumnList NVARCHAR(MAX); -/* Let's get @SortOrder set to lower case here for comparisons later */ -SET @SortOrder = REPLACE(LOWER(@SortOrder), N' ', N'_'); -SET @SortDirection = LOWER(@SortDirection); -SET @LineFeed = CHAR(13) + CHAR(10); -SELECT @SQLServerProductVersion = CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)); -SELECT @SQLServerEdition =CAST(SERVERPROPERTY('EngineEdition') AS INT); /* We default to online index creates where EngineEdition=3*/ -SET @FilterMB=250; -SELECT @ScriptVersionName = 'sp_BlitzIndex(TM) v' + @Version + ' - ' + DATENAME(MM, @VersionDate) + ' ' + RIGHT('0'+DATENAME(DD, @VersionDate),2) + ', ' + DATENAME(YY, @VersionDate); -SET @IgnoreDatabases = REPLACE(REPLACE(LTRIM(RTRIM(@IgnoreDatabases)), CHAR(10), ''), CHAR(13), ''); -RAISERROR(N'Starting run. %s', 0,1, @ScriptVersionName) WITH NOWAIT; - -IF(@OutputType NOT IN ('TABLE','NONE')) -BEGIN - RAISERROR('Invalid value for parameter @OutputType. Expected: (TABLE;NONE)',12,1); - RETURN; -END; - -IF(@OutputType = 'NONE') -BEGIN - IF(@OutputTableName IS NULL OR @OutputSchemaName IS NULL OR @OutputDatabaseName IS NULL) - BEGIN - RAISERROR('This procedure should be called with a value for @Output* parameters, as @OutputType is set to NONE',12,1); - RETURN; - END; - IF(@BringThePain = 1) - BEGIN - RAISERROR('Incompatible Parameters: @BringThePain set to 1 and @OutputType set to NONE',12,1); - RETURN; - END; - /* Eventually limit by mode - IF(@Mode not in (0,4)) - BEGIN - RAISERROR('Incompatible Parameters: @Mode set to %d and @OutputType set to NONE',12,1,@Mode); - RETURN; - END; - */ -END; -IF OBJECT_ID('tempdb..#IndexSanity') IS NOT NULL - DROP TABLE #IndexSanity; -IF OBJECT_ID('tempdb..#IndexPartitionSanity') IS NOT NULL - DROP TABLE #IndexPartitionSanity; -IF OBJECT_ID('tempdb..#IndexSanitySize') IS NOT NULL - DROP TABLE #IndexSanitySize; -IF OBJECT_ID('tempdb..#IndexColumns') IS NOT NULL - DROP TABLE #IndexColumns; -IF OBJECT_ID('tempdb..#MissingIndexes') IS NOT NULL - DROP TABLE #MissingIndexes; -IF OBJECT_ID('tempdb..#ForeignKeys') IS NOT NULL - DROP TABLE #ForeignKeys; -IF OBJECT_ID('tempdb..#BlitzIndexResults') IS NOT NULL - DROP TABLE #BlitzIndexResults; - -IF OBJECT_ID('tempdb..#IndexCreateTsql') IS NOT NULL - DROP TABLE #IndexCreateTsql; +ELSE IF @Mode IN (0, 4) /* DIAGNOSE*//* IF @TableName IS NOT NULL, so @TableName must not be null */ +BEGIN; + IF @Mode IN (0, 4) /* DIAGNOSE priorities 1-100 */ + BEGIN; + RAISERROR(N'@Mode=0 or 4, running rules for priorities 1-100.', 0,1) WITH NOWAIT; -IF OBJECT_ID('tempdb..#DatabaseList') IS NOT NULL - DROP TABLE #DatabaseList; + ---------------------------------------- + --Multiple Index Personalities: Check_id 0-10 + ---------------------------------------- + RAISERROR('check_id 1: Duplicate keys', 0,1) WITH NOWAIT; + WITH duplicate_indexes + AS ( SELECT [object_id], key_column_names, database_id, [schema_name] + FROM #IndexSanity AS ip + WHERE index_type IN (1,2) /* Clustered, NC only*/ + AND is_hypothetical = 0 + AND is_disabled = 0 + AND is_primary_key = 0 + AND EXISTS ( + SELECT 1/0 + FROM #IndexSanitySize ips + WHERE ip.index_sanity_id = ips.index_sanity_id + AND ip.database_id = ips.database_id + AND ip.schema_name = ips.schema_name + AND ips.total_reserved_MB >= CASE + WHEN (@GetAllDatabases = 1 OR @Mode = 0) + THEN @ThresholdMB + ELSE ips.total_reserved_MB + END + ) + GROUP BY [object_id], key_column_names, database_id, [schema_name] + HAVING COUNT(*) > 1) + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 1 AS check_id, + ip.index_sanity_id, + 20 AS Priority, + 'Multiple Index Personalities' AS findings_group, + 'Duplicate keys' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/duplicateindex' AS URL, + N'Index Name: ' + ip.index_name + N' Table Name: ' + ip.db_schema_object_name AS details, + ip.index_definition, + ip.secret_columns, + ip.index_usage_summary, + ips.index_size_summary + FROM duplicate_indexes di + JOIN #IndexSanity ip ON di.[object_id] = ip.[object_id] + AND ip.database_id = di.database_id + AND ip.[schema_name] = di.[schema_name] + AND di.key_column_names = ip.key_column_names + JOIN #IndexSanitySize ips ON ip.index_sanity_id = ips.index_sanity_id + AND ip.database_id = ips.database_id + AND ip.schema_name = ips.schema_name + /* WHERE clause limits to only @ThresholdMB or larger duplicate indexes when getting all databases or using PainRelief mode */ + WHERE ips.total_reserved_MB >= CASE WHEN (@GetAllDatabases = 1 OR @Mode = 0) THEN @ThresholdMB ELSE ips.total_reserved_MB END + AND ip.is_primary_key = 0 + ORDER BY ips.total_rows DESC, ip.[schema_name], ip.[object_name], ip.key_column_names_with_sort_order + OPTION ( RECOMPILE ); -IF OBJECT_ID('tempdb..#Statistics') IS NOT NULL - DROP TABLE #Statistics; + RAISERROR('check_id 2: Keys w/ identical leading columns.', 0,1) WITH NOWAIT; + WITH borderline_duplicate_indexes + AS ( SELECT DISTINCT database_id, [object_id], first_key_column_name, key_column_names, + COUNT([object_id]) OVER ( PARTITION BY database_id, [object_id], first_key_column_name ) AS number_dupes + FROM #IndexSanity + WHERE index_type IN (1,2) /* Clustered, NC only*/ + AND is_hypothetical=0 + AND is_disabled=0 + AND is_primary_key = 0) + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 2 AS check_id, + ip.index_sanity_id, + 30 AS Priority, + 'Multiple Index Personalities' AS findings_group, + 'Borderline duplicate keys' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/duplicateindex' AS URL, + ip.db_schema_object_indexid AS details, + ip.index_definition, + ip.secret_columns, + ip.index_usage_summary, + ips.index_size_summary + FROM #IndexSanity AS ip + JOIN #IndexSanitySize ips ON ip.index_sanity_id = ips.index_sanity_id + WHERE EXISTS ( + SELECT di.[object_id] + FROM borderline_duplicate_indexes AS di + WHERE di.[object_id] = ip.[object_id] AND + di.database_id = ip.database_id AND + di.first_key_column_name = ip.first_key_column_name AND + di.key_column_names <> ip.key_column_names AND + di.number_dupes > 1 + ) + AND ip.is_primary_key = 0 + ORDER BY ips.total_rows DESC, ip.[schema_name], ip.[object_name], ip.key_column_names, ip.include_column_names + OPTION ( RECOMPILE ); -IF OBJECT_ID('tempdb..#PartitionCompressionInfo') IS NOT NULL - DROP TABLE #PartitionCompressionInfo; + ---------------------------------------- + --Aggressive Indexes: Check_id 10-19 + ---------------------------------------- -IF OBJECT_ID('tempdb..#ComputedColumns') IS NOT NULL - DROP TABLE #ComputedColumns; - -IF OBJECT_ID('tempdb..#TraceStatus') IS NOT NULL - DROP TABLE #TraceStatus; + RAISERROR(N'check_id 11: Total lock wait time > 5 minutes (row + page)', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 11 AS check_id, + i.index_sanity_id, + 70 AS Priority, + N'Aggressive ' + + CASE COALESCE((SELECT SUM(1) + FROM #IndexSanity iMe + INNER JOIN #IndexSanity iOthers + ON iMe.database_id = iOthers.database_id + AND iMe.object_id = iOthers.object_id + AND iOthers.index_id > 1 + WHERE i.index_sanity_id = iMe.index_sanity_id + AND iOthers.is_hypothetical = 0 + AND iOthers.is_disabled = 0 + ), 0) + WHEN 0 THEN N'Under-Indexing' + WHEN 1 THEN N'Under-Indexing' + WHEN 2 THEN N'Under-Indexing' + WHEN 3 THEN N'Under-Indexing' + WHEN 4 THEN N'Indexes' + WHEN 5 THEN N'Indexes' + WHEN 6 THEN N'Indexes' + WHEN 7 THEN N'Indexes' + WHEN 8 THEN N'Indexes' + WHEN 9 THEN N'Indexes' + ELSE N'Over-Indexing' + END AS findings_group, + N'Total lock wait time > 5 minutes (row + page)' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/AggressiveIndexes' AS URL, + (i.db_schema_object_indexid + N': ' + + sz.index_lock_wait_summary + N' NC indexes on table: ') COLLATE DATABASE_DEFAULT + + CAST(COALESCE((SELECT SUM(1) + FROM #IndexSanity iMe + INNER JOIN #IndexSanity iOthers + ON iMe.database_id = iOthers.database_id + AND iMe.object_id = iOthers.object_id + AND iOthers.index_id > 1 + WHERE i.index_sanity_id = iMe.index_sanity_id + AND iOthers.is_hypothetical = 0 + AND iOthers.is_disabled = 0 + ), 0) + AS NVARCHAR(30)) AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + sz.index_size_summary + FROM #IndexSanity AS i + JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id + WHERE (total_row_lock_wait_in_ms + total_page_lock_wait_in_ms) > 300000 + GROUP BY i.index_sanity_id, [database_name], i.db_schema_object_indexid, sz.index_lock_wait_summary, i.index_definition, i.secret_columns, i.index_usage_summary, sz.index_size_summary, sz.index_sanity_id + ORDER BY SUM(total_row_lock_wait_in_ms + total_page_lock_wait_in_ms) DESC, 4, [database_name], 8 + OPTION ( RECOMPILE ); -IF OBJECT_ID('tempdb..#TemporalTables') IS NOT NULL - DROP TABLE #TemporalTables; -IF OBJECT_ID('tempdb..#CheckConstraints') IS NOT NULL - DROP TABLE #CheckConstraints; -IF OBJECT_ID('tempdb..#FilteredIndexes') IS NOT NULL - DROP TABLE #FilteredIndexes; - -IF OBJECT_ID('tempdb..#Ignore_Databases') IS NOT NULL - DROP TABLE #Ignore_Databases + ---------------------------------------- + --Index Hoarder: Check_id 20-29 + ---------------------------------------- + RAISERROR(N'check_id 20: >= 10 NC indexes on any given table. Yes, 10 is an arbitrary number.', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 20 AS check_id, + MAX(i.index_sanity_id) AS index_sanity_id, + 10 AS Priority, + 'Index Hoarder' AS findings_group, + 'Many NC Indexes on a Single Table' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/IndexHoarder' AS URL, + CAST (COUNT(*) AS NVARCHAR(30)) + ' NC indexes on ' + i.db_schema_object_name AS details, + i.db_schema_object_name + ' (' + CAST (COUNT(*) AS NVARCHAR(30)) + ' indexes)' AS index_definition, + '' AS secret_columns, + REPLACE(CONVERT(NVARCHAR(30),CAST(SUM(total_reads) AS MONEY), 1), N'.00', N'') + N' reads (ALL); ' + + REPLACE(CONVERT(NVARCHAR(30),CAST(SUM(user_updates) AS MONEY), 1), N'.00', N'') + N' writes (ALL); ', + REPLACE(CONVERT(NVARCHAR(30),CAST(MAX(total_rows) AS MONEY), 1), N'.00', N'') + N' rows (MAX)' + + CASE WHEN SUM(total_reserved_MB) > 1024 THEN + N'; ' + CAST(CAST(SUM(total_reserved_MB)/1024. AS NUMERIC(29,1)) AS NVARCHAR(30)) + 'GB (ALL)' + WHEN SUM(total_reserved_MB) > 0 THEN + N'; ' + CAST(CAST(SUM(total_reserved_MB) AS NUMERIC(29,1)) AS NVARCHAR(30)) + 'MB (ALL)' + ELSE '' + END AS index_size_summary + FROM #IndexSanity i + JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id + WHERE index_id NOT IN ( 0, 1 ) + GROUP BY db_schema_object_name, [i].[database_name] + HAVING COUNT(*) >= 10 + ORDER BY i.db_schema_object_name DESC + OPTION ( RECOMPILE ); - RAISERROR (N'Create temp tables.',0,1) WITH NOWAIT; - CREATE TABLE #BlitzIndexResults - ( - blitz_result_id INT IDENTITY PRIMARY KEY, - check_id INT NOT NULL, - index_sanity_id INT NULL, - Priority INT NULL, - findings_group NVARCHAR(4000) NOT NULL, - finding NVARCHAR(200) NOT NULL, - [database_name] NVARCHAR(128) NULL, - URL NVARCHAR(200) NOT NULL, - details NVARCHAR(MAX) NOT NULL, - index_definition NVARCHAR(MAX) NOT NULL, - secret_columns NVARCHAR(MAX) NULL, - index_usage_summary NVARCHAR(MAX) NULL, - index_size_summary NVARCHAR(MAX) NULL, - create_tsql NVARCHAR(MAX) NULL, - more_info NVARCHAR(MAX)NULL - ); + RAISERROR(N'check_id 22: NC indexes with 0 reads. (Borderline) and >= 10,000 writes', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 22 AS check_id, + i.index_sanity_id, + 10 AS Priority, + N'Index Hoarder' AS findings_group, + N'Unused NC Index with High Writes' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/IndexHoarder' AS URL, + N'Reads: 0,' + + N' Writes: ' + + REPLACE(CONVERT(NVARCHAR(30), CAST((i.user_updates) AS MONEY), 1), N'.00', N'') + + N' on: ' + + i.db_schema_object_indexid + AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + sz.index_size_summary + FROM #IndexSanity AS i + JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id + WHERE i.total_reads=0 + AND i.user_updates >= 10000 + 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 + AND @Filter <> 1 /* 1 = "ignore unused */ + ORDER BY i.db_schema_object_indexid + OPTION ( RECOMPILE ); - CREATE TABLE #IndexSanity - ( - [index_sanity_id] INT IDENTITY PRIMARY KEY CLUSTERED, - [database_id] SMALLINT NOT NULL , - [object_id] INT NOT NULL , - [index_id] INT NOT NULL , - [index_type] TINYINT NOT NULL, - [database_name] NVARCHAR(128) NOT NULL , - [schema_name] NVARCHAR(128) NOT NULL , - [object_name] NVARCHAR(128) NOT NULL , - index_name NVARCHAR(128) NULL , - key_column_names NVARCHAR(MAX) NULL , - key_column_names_with_sort_order NVARCHAR(MAX) NULL , - key_column_names_with_sort_order_no_types NVARCHAR(MAX) NULL , - count_key_columns INT NULL , - include_column_names NVARCHAR(MAX) NULL , - include_column_names_no_types NVARCHAR(MAX) NULL , - count_included_columns INT NULL , - partition_key_column_name NVARCHAR(MAX) NULL, - filter_definition NVARCHAR(MAX) NOT NULL , - is_indexed_view BIT NOT NULL , - is_unique BIT NOT NULL , - is_primary_key BIT NOT NULL , - is_XML BIT NOT NULL, - is_spatial BIT NOT NULL, - is_NC_columnstore BIT NOT NULL, - is_CX_columnstore BIT NOT NULL, - is_in_memory_oltp BIT NOT NULL , - is_disabled BIT NOT NULL , - is_hypothetical BIT NOT NULL , - is_padded BIT NOT NULL , - fill_factor SMALLINT NOT NULL , - user_seeks BIGINT NOT NULL , - user_scans BIGINT NOT NULL , - user_lookups BIGINT NOT NULL , - user_updates BIGINT NULL , - last_user_seek DATETIME NULL , - last_user_scan DATETIME NULL , - last_user_lookup DATETIME NULL , - last_user_update DATETIME NULL , - is_referenced_by_foreign_key BIT DEFAULT(0), - secret_columns NVARCHAR(MAX) NULL, - count_secret_columns INT NULL, - create_date DATETIME NOT NULL, - modify_date DATETIME NOT NULL, - filter_columns_not_in_index NVARCHAR(MAX), - [db_schema_object_name] AS [schema_name] + N'.' + [object_name] , - [db_schema_object_indexid] AS [schema_name] + N'.' + [object_name] - + CASE WHEN [index_name] IS NOT NULL THEN N'.' + index_name - ELSE N'' - END + N' (' + CAST(index_id AS NVARCHAR(20)) + N')' , - first_key_column_name AS CASE WHEN count_key_columns > 1 - THEN LEFT(key_column_names, CHARINDEX(',', key_column_names, 0) - 1) - ELSE key_column_names - END , - index_definition AS - CASE WHEN partition_key_column_name IS NOT NULL - THEN N'[PARTITIONED BY:' + partition_key_column_name + N']' - ELSE '' - END + - CASE index_id - WHEN 0 THEN N'[HEAP] ' - WHEN 1 THEN N'[CX] ' - ELSE N'' END + CASE WHEN is_indexed_view = 1 THEN N'[VIEW] ' - ELSE N'' END + CASE WHEN is_primary_key = 1 THEN N'[PK] ' - ELSE N'' END + CASE WHEN is_XML = 1 THEN N'[XML] ' - ELSE N'' END + CASE WHEN is_spatial = 1 THEN N'[SPATIAL] ' - ELSE N'' END + CASE WHEN is_NC_columnstore = 1 THEN N'[COLUMNSTORE] ' - ELSE N'' END + CASE WHEN is_in_memory_oltp = 1 THEN N'[IN-MEMORY] ' - ELSE N'' END + CASE WHEN is_disabled = 1 THEN N'[DISABLED] ' - ELSE N'' END + CASE WHEN is_hypothetical = 1 THEN N'[HYPOTHETICAL] ' - ELSE N'' END + CASE WHEN is_unique = 1 AND is_primary_key = 0 THEN N'[UNIQUE] ' - ELSE N'' END + CASE WHEN count_key_columns > 0 THEN - N'[' + CAST(count_key_columns AS NVARCHAR(10)) + N' KEY' - + CASE WHEN count_key_columns > 1 THEN N'S' ELSE N'' END - + N'] ' + LTRIM(key_column_names_with_sort_order) - ELSE N'' END + CASE WHEN count_included_columns > 0 THEN - N' [' + CAST(count_included_columns AS NVARCHAR(10)) + N' INCLUDE' + - + CASE WHEN count_included_columns > 1 THEN N'S' ELSE N'' END - + N'] ' + include_column_names - ELSE N'' END + CASE WHEN filter_definition <> N'' THEN N' [FILTER] ' + filter_definition - ELSE N'' END , - [total_reads] AS user_seeks + user_scans + user_lookups, - [reads_per_write] AS CAST(CASE WHEN user_updates > 0 - THEN ( user_seeks + user_scans + user_lookups ) / (1.0 * user_updates) - ELSE 0 END AS MONEY) , - [index_usage_summary] AS - CASE WHEN is_spatial = 1 THEN N'Not Tracked' - WHEN is_disabled = 1 THEN N'Disabled' - ELSE N'Reads: ' + - REPLACE(CONVERT(NVARCHAR(30),CAST((user_seeks + user_scans + user_lookups) AS MONEY), 1), N'.00', N'') - + CASE WHEN user_seeks + user_scans + user_lookups > 0 THEN - N' (' - + RTRIM( - CASE WHEN user_seeks > 0 THEN REPLACE(CONVERT(NVARCHAR(30),CAST((user_seeks) AS MONEY), 1), N'.00', N'') + N' seek ' ELSE N'' END - + CASE WHEN user_scans > 0 THEN REPLACE(CONVERT(NVARCHAR(30),CAST((user_scans) AS MONEY), 1), N'.00', N'') + N' scan ' ELSE N'' END - + CASE WHEN user_lookups > 0 THEN REPLACE(CONVERT(NVARCHAR(30),CAST((user_lookups) AS MONEY), 1), N'.00', N'') + N' lookup' ELSE N'' END - ) - + N') ' - ELSE N' ' - END - + N'Writes: ' + - REPLACE(CONVERT(NVARCHAR(30),CAST(user_updates AS MONEY), 1), N'.00', N'') - END /* First "end" is about is_spatial */, - [more_info] AS - CASE WHEN is_in_memory_oltp = 1 - THEN N'EXEC dbo.sp_BlitzInMemoryOLTP @dbName=' + QUOTENAME([database_name],N'''') + - N', @tableName=' + QUOTENAME([object_name],N'''') + N';' - ELSE N'EXEC dbo.sp_BlitzIndex @DatabaseName=' + QUOTENAME([database_name],N'''') + - N', @SchemaName=' + QUOTENAME([schema_name],N'''') + N', @TableName=' + QUOTENAME([object_name],N'''') + N';' - END - ); - RAISERROR (N'Adding UQ index on #IndexSanity (database_id, object_id, index_id)',0,1) WITH NOWAIT; - IF NOT EXISTS(SELECT 1 FROM tempdb.sys.indexes WHERE name='uq_database_id_object_id_index_id') - CREATE UNIQUE INDEX uq_database_id_object_id_index_id ON #IndexSanity (database_id, object_id, index_id); + RAISERROR(N'check_id 34: Filtered index definition columns not in index definition', 0,1) WITH NOWAIT; + + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 34 AS check_id, + i.index_sanity_id, + 80 AS Priority, + N'Abnormal Psychology' AS findings_group, + N'Filter Columns Not In Index Definition' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/IndexFeatures' AS URL, + N'The index ' + + QUOTENAME(i.index_name) + + N' on [' + + i.db_schema_object_name + + N'] has a filter on [' + + i.filter_definition + + N'] but is missing [' + + LTRIM(i.filter_columns_not_in_index) + + N'] from the index definition.' + AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + sz.index_size_summary + FROM #IndexSanity i + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + WHERE i.filter_columns_not_in_index IS NOT NULL + ORDER BY i.db_schema_object_indexid + OPTION ( RECOMPILE ); + + ---------------------------------------- + --Self Loathing Indexes : Check_id 40-49 + ---------------------------------------- + + RAISERROR(N'check_id 40: Fillfactor in nonclustered 80 percent or less', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 40 AS check_id, + i.index_sanity_id, + 100 AS Priority, + N'Self Loathing Indexes' AS findings_group, + N'Low Fill Factor on Nonclustered Index' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/SelfLoathing' AS URL, + CAST(fill_factor AS NVARCHAR(10)) + N'% fill factor on ' + db_schema_object_indexid + N'. '+ + CASE WHEN (last_user_update IS NULL OR user_updates < 1) + THEN N'No writes have been made.' + ELSE + N'Last write was ' + CONVERT(NVARCHAR(16),last_user_update,121) + N' and ' + + CAST(user_updates AS NVARCHAR(25)) + N' updates have been made.' + END + AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + sz.index_size_summary + FROM #IndexSanity AS i + JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id + WHERE index_id > 1 + AND fill_factor BETWEEN 1 AND 80 OPTION ( RECOMPILE ); + + RAISERROR(N'check_id 40: Fillfactor in clustered 80 percent or less', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 40 AS check_id, + i.index_sanity_id, + 100 AS Priority, + N'Self Loathing Indexes' AS findings_group, + N'Low Fill Factor on Clustered Index' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/SelfLoathing' AS URL, + N'Fill factor on ' + db_schema_object_indexid + N' is ' + CAST(fill_factor AS NVARCHAR(10)) + N'%. '+ + CASE WHEN (last_user_update IS NULL OR user_updates < 1) + THEN N'No writes have been made.' + ELSE + N'Last write was ' + CONVERT(NVARCHAR(16),last_user_update,121) + N' and ' + + CAST(user_updates AS NVARCHAR(25)) + N' updates have been made.' + END + AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + sz.index_size_summary + FROM #IndexSanity AS i + JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id + WHERE index_id = 1 + AND fill_factor BETWEEN 1 AND 80 OPTION ( RECOMPILE ); - CREATE TABLE #IndexPartitionSanity - ( - [index_partition_sanity_id] INT IDENTITY, - [index_sanity_id] INT NULL , - [database_id] INT NOT NULL , - [object_id] INT NOT NULL , - [schema_name] NVARCHAR(128) NOT NULL, - [index_id] INT NOT NULL , - [partition_number] INT NOT NULL , - row_count BIGINT NOT NULL , - reserved_MB NUMERIC(29,2) NOT NULL , - reserved_LOB_MB NUMERIC(29,2) NOT NULL , - reserved_row_overflow_MB NUMERIC(29,2) NOT NULL , - reserved_dictionary_MB NUMERIC(29,2) NOT NULL , - leaf_insert_count BIGINT NULL , - leaf_delete_count BIGINT NULL , - leaf_update_count BIGINT NULL , - range_scan_count BIGINT NULL , - singleton_lookup_count BIGINT NULL , - forwarded_fetch_count BIGINT NULL , - lob_fetch_in_pages BIGINT NULL , - lob_fetch_in_bytes BIGINT NULL , - row_overflow_fetch_in_pages BIGINT NULL , - row_overflow_fetch_in_bytes BIGINT NULL , - row_lock_count BIGINT NULL , - row_lock_wait_count BIGINT NULL , - row_lock_wait_in_ms BIGINT NULL , - page_lock_count BIGINT NULL , - page_lock_wait_count BIGINT NULL , - page_lock_wait_in_ms BIGINT NULL , - index_lock_promotion_attempt_count BIGINT NULL , - index_lock_promotion_count BIGINT NULL, - data_compression_desc NVARCHAR(60) NULL, - page_latch_wait_count BIGINT NULL, - page_latch_wait_in_ms BIGINT NULL, - page_io_latch_wait_count BIGINT NULL, - page_io_latch_wait_in_ms BIGINT NULL, - lock_escalation_desc nvarchar(60) NULL - ); - CREATE TABLE #IndexSanitySize - ( - [index_sanity_size_id] INT IDENTITY NOT NULL , - [index_sanity_id] INT NULL , - [database_id] INT NOT NULL, - [schema_name] NVARCHAR(128) NOT NULL, - partition_count INT NOT NULL , - total_rows BIGINT NOT NULL , - total_reserved_MB NUMERIC(29,2) NOT NULL , - total_reserved_LOB_MB NUMERIC(29,2) NOT NULL , - total_reserved_row_overflow_MB NUMERIC(29,2) NOT NULL , - total_reserved_dictionary_MB NUMERIC(29,2) NOT NULL , - total_leaf_delete_count BIGINT NULL, - total_leaf_update_count BIGINT NULL, - total_range_scan_count BIGINT NULL, - total_singleton_lookup_count BIGINT NULL, - total_forwarded_fetch_count BIGINT NULL, - total_row_lock_count BIGINT NULL , - total_row_lock_wait_count BIGINT NULL , - total_row_lock_wait_in_ms BIGINT NULL , - avg_row_lock_wait_in_ms BIGINT NULL , - total_page_lock_count BIGINT NULL , - total_page_lock_wait_count BIGINT NULL , - total_page_lock_wait_in_ms BIGINT NULL , - avg_page_lock_wait_in_ms BIGINT NULL , - total_index_lock_promotion_attempt_count BIGINT NULL , - total_index_lock_promotion_count BIGINT NULL , - data_compression_desc NVARCHAR(4000) NULL, - page_latch_wait_count BIGINT NULL, - page_latch_wait_in_ms BIGINT NULL, - page_io_latch_wait_count BIGINT NULL, - page_io_latch_wait_in_ms BIGINT NULL, - lock_escalation_desc nvarchar(60) NULL, - index_size_summary AS ISNULL( - CASE WHEN partition_count > 1 - THEN N'[' + CAST(partition_count AS NVARCHAR(10)) + N' PARTITIONS] ' - ELSE N'' - END + REPLACE(CONVERT(NVARCHAR(30),CAST([total_rows] AS MONEY), 1), N'.00', N'') + N' rows; ' - + CASE WHEN total_reserved_MB > 1024 THEN - CAST(CAST(total_reserved_MB/1024. AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'GB' - ELSE - CAST(CAST(total_reserved_MB AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'MB' - END - + CASE WHEN total_reserved_LOB_MB > 1024 THEN - N'; ' + CAST(CAST(total_reserved_LOB_MB/1024. AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'GB ' + CASE WHEN total_reserved_dictionary_MB = 0 THEN N'LOB' ELSE N'Columnstore' END - WHEN total_reserved_LOB_MB > 0 THEN - N'; ' + CAST(CAST(total_reserved_LOB_MB AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'MB ' + CASE WHEN total_reserved_dictionary_MB = 0 THEN N'LOB' ELSE N'Columnstore' END - ELSE '' - END - + CASE WHEN total_reserved_row_overflow_MB > 1024 THEN - N'; ' + CAST(CAST(total_reserved_row_overflow_MB/1024. AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'GB Row Overflow' - WHEN total_reserved_row_overflow_MB > 0 THEN - N'; ' + CAST(CAST(total_reserved_row_overflow_MB AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'MB Row Overflow' - ELSE '' - END - + CASE WHEN total_reserved_dictionary_MB > 1024 THEN - N'; ' + CAST(CAST(total_reserved_dictionary_MB/1024. AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'GB Dictionaries' - WHEN total_reserved_dictionary_MB > 0 THEN - N'; ' + CAST(CAST(total_reserved_dictionary_MB AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'MB Dictionaries' - ELSE '' - END , - N'Error- NULL in computed column'), - index_op_stats AS ISNULL( - ( - REPLACE(CONVERT(NVARCHAR(30),CAST(total_singleton_lookup_count AS MONEY), 1),N'.00',N'') + N' singleton lookups; ' - + REPLACE(CONVERT(NVARCHAR(30),CAST(total_range_scan_count AS MONEY), 1),N'.00',N'') + N' scans/seeks; ' - + REPLACE(CONVERT(NVARCHAR(30),CAST(total_leaf_delete_count AS MONEY), 1),N'.00',N'') + N' deletes; ' - + REPLACE(CONVERT(NVARCHAR(30),CAST(total_leaf_update_count AS MONEY), 1),N'.00',N'') + N' updates; ' - + CASE WHEN ISNULL(total_forwarded_fetch_count,0) >0 THEN - REPLACE(CONVERT(NVARCHAR(30),CAST(total_forwarded_fetch_count AS MONEY), 1),N'.00',N'') + N' forward records fetched; ' - ELSE N'' END + RAISERROR(N'check_id 43: Heaps with forwarded records', 0,1) WITH NOWAIT; + WITH heaps_cte + AS ( SELECT [object_id], + [database_id], + [schema_name], + SUM(forwarded_fetch_count) AS forwarded_fetch_count, + SUM(leaf_delete_count) AS leaf_delete_count + FROM #IndexPartitionSanity + GROUP BY [object_id], + [database_id], + [schema_name] + HAVING SUM(forwarded_fetch_count) > 0) + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 43 AS check_id, + i.index_sanity_id, + 100 AS Priority, + N'Self Loathing Indexes' AS findings_group, + N'Heaps with Forwarded Fetches' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/SelfLoathing' AS URL, + CASE WHEN h.forwarded_fetch_count >= 922337203685477 THEN '>= 922,337,203,685,477' + WHEN @DaysUptime < 1 THEN CAST(h.forwarded_fetch_count AS NVARCHAR(256)) + N' forwarded fetches against heap: ' + db_schema_object_indexid + ELSE REPLACE(CONVERT(NVARCHAR(256),CAST(CAST( + (h.forwarded_fetch_count /*/@DaysUptime */) + AS BIGINT) AS MONEY), 1), '.00', '') + END + N' forwarded fetches per day against heap: ' + + db_schema_object_indexid AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + sz.index_size_summary + FROM #IndexSanity i + JOIN heaps_cte h ON i.[object_id] = h.[object_id] + AND i.[database_id] = h.[database_id] + AND i.[schema_name] = h.[schema_name] + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + WHERE i.index_id = 0 + AND h.forwarded_fetch_count / @DaysUptime > 1000 + AND sz.total_reserved_MB >= CASE WHEN NOT (@GetAllDatabases = 1 OR @Mode = 4) THEN @ThresholdMB ELSE sz.total_reserved_MB END + OPTION ( RECOMPILE ); - /* rows will only be in this dmv when data is in memory for the table */ - ), N'Table metadata not in memory'), - index_lock_wait_summary AS ISNULL( - CASE WHEN total_row_lock_wait_count = 0 AND total_page_lock_wait_count = 0 AND - total_index_lock_promotion_attempt_count = 0 THEN N'0 lock waits; ' - + CASE WHEN lock_escalation_desc = N'DISABLE' THEN N'Lock escalation DISABLE.' - ELSE N'' - END - ELSE - CASE WHEN total_row_lock_wait_count > 0 THEN - N'Row lock waits: ' + REPLACE(CONVERT(NVARCHAR(30),CAST(total_row_lock_wait_count AS MONEY), 1), N'.00', N'') - + N'; total duration: ' + - CASE WHEN total_row_lock_wait_in_ms >= 60000 THEN /*More than 1 min*/ - REPLACE(CONVERT(NVARCHAR(30),CAST((total_row_lock_wait_in_ms/60000) AS MONEY), 1), N'.00', N'') + N' minutes; ' - ELSE - REPLACE(CONVERT(NVARCHAR(30),CAST(ISNULL(total_row_lock_wait_in_ms/1000,0) AS MONEY), 1), N'.00', N'') + N' seconds; ' - END - + N'avg duration: ' + - CASE WHEN avg_row_lock_wait_in_ms >= 60000 THEN /*More than 1 min*/ - REPLACE(CONVERT(NVARCHAR(30),CAST((avg_row_lock_wait_in_ms/60000) AS MONEY), 1), N'.00', N'') + N' minutes; ' - ELSE - REPLACE(CONVERT(NVARCHAR(30),CAST(ISNULL(avg_row_lock_wait_in_ms/1000,0) AS MONEY), 1), N'.00', N'') + N' seconds; ' - END - ELSE N'' - END + - CASE WHEN total_page_lock_wait_count > 0 THEN - N'Page lock waits: ' + REPLACE(CONVERT(NVARCHAR(30),CAST(total_page_lock_wait_count AS MONEY), 1), N'.00', N'') - + N'; total duration: ' + - CASE WHEN total_page_lock_wait_in_ms >= 60000 THEN /*More than 1 min*/ - REPLACE(CONVERT(NVARCHAR(30),CAST((total_page_lock_wait_in_ms/60000) AS MONEY), 1), N'.00', N'') + N' minutes; ' - ELSE - REPLACE(CONVERT(NVARCHAR(30),CAST(ISNULL(total_page_lock_wait_in_ms/1000,0) AS MONEY), 1), N'.00', N'') + N' seconds; ' - END - + N'avg duration: ' + - CASE WHEN avg_page_lock_wait_in_ms >= 60000 THEN /*More than 1 min*/ - REPLACE(CONVERT(NVARCHAR(30),CAST((avg_page_lock_wait_in_ms/60000) AS MONEY), 1), N'.00', N'') + N' minutes; ' - ELSE - REPLACE(CONVERT(NVARCHAR(30),CAST(ISNULL(avg_page_lock_wait_in_ms/1000,0) AS MONEY), 1), N'.00', N'') + N' seconds; ' - END - ELSE N'' - END + - CASE WHEN total_index_lock_promotion_attempt_count > 0 THEN - N'Lock escalation attempts: ' + REPLACE(CONVERT(NVARCHAR(30),CAST(total_index_lock_promotion_attempt_count AS MONEY), 1), N'.00', N'') - + N'; Actual Escalations: ' + REPLACE(CONVERT(NVARCHAR(30),CAST(ISNULL(total_index_lock_promotion_count,0) AS MONEY), 1), N'.00', N'') +N'; ' - ELSE N'' - END + - CASE WHEN lock_escalation_desc = N'DISABLE' THEN - N'Lock escalation is disabled.' - ELSE N'' - END - END - ,'Error- NULL in computed column') - ); + RAISERROR(N'check_id 44: Large Heaps with reads or writes.', 0,1) WITH NOWAIT; + WITH heaps_cte + AS ( SELECT [object_id], + [database_id], + [schema_name], + SUM(forwarded_fetch_count) AS forwarded_fetch_count, + SUM(leaf_delete_count) AS leaf_delete_count + FROM #IndexPartitionSanity + GROUP BY [object_id], + [database_id], + [schema_name] + HAVING SUM(forwarded_fetch_count) > 0 + OR SUM(leaf_delete_count) > 0) + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 44 AS check_id, + i.index_sanity_id, + 100 AS Priority, + N'Self Loathing Indexes' AS findings_group, + N'Large Active Heap' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/SelfLoathing' AS URL, + N'Should this table be a heap? ' + db_schema_object_indexid AS details, + i.index_definition, + 'N/A' AS secret_columns, + i.index_usage_summary, + sz.index_size_summary + FROM #IndexSanity i + LEFT JOIN heaps_cte h ON i.[object_id] = h.[object_id] + AND i.[database_id] = h.[database_id] + AND i.[schema_name] = h.[schema_name] + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + WHERE i.index_id = 0 + AND (i.total_reads > 0 OR i.user_updates > 0) + AND sz.total_rows >= 100000 + AND h.[object_id] IS NULL /*don't duplicate the prior check.*/ + OPTION ( RECOMPILE ); - CREATE TABLE #IndexColumns - ( - [database_id] INT NOT NULL, - [schema_name] NVARCHAR(128), - [object_id] INT NOT NULL , - [index_id] INT NOT NULL , - [key_ordinal] INT NULL , - is_included_column BIT NULL , - is_descending_key BIT NULL , - [partition_ordinal] INT NULL , - column_name NVARCHAR(256) NOT NULL , - system_type_name NVARCHAR(256) NOT NULL, - max_length SMALLINT NOT NULL, - [precision] TINYINT NOT NULL, - [scale] TINYINT NOT NULL, - collation_name NVARCHAR(256) NULL, - is_nullable BIT NULL, - is_identity BIT NULL, - is_computed BIT NULL, - is_replicated BIT NULL, - is_sparse BIT NULL, - is_filestream BIT NULL, - seed_value DECIMAL(38,0) NULL, - increment_value DECIMAL(38,0) NULL , - last_value DECIMAL(38,0) NULL, - is_not_for_replication BIT NULL - ); - CREATE CLUSTERED INDEX CLIX_database_id_object_id_index_id ON #IndexColumns - (database_id, object_id, index_id); + RAISERROR(N'check_id 45: Medium Heaps with reads or writes.', 0,1) WITH NOWAIT; + WITH heaps_cte + AS ( SELECT [object_id], + [database_id], + [schema_name], + SUM(forwarded_fetch_count) AS forwarded_fetch_count, + SUM(leaf_delete_count) AS leaf_delete_count + FROM #IndexPartitionSanity + GROUP BY [object_id], + [database_id], + [schema_name] + HAVING SUM(forwarded_fetch_count) > 0 + OR SUM(leaf_delete_count) > 0) + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 45 AS check_id, + i.index_sanity_id, + 100 AS Priority, + N'Self Loathing Indexes' AS findings_group, + N'Medium Active heap' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/SelfLoathing' AS URL, + N'Should this table be a heap? ' + db_schema_object_indexid AS details, + i.index_definition, + 'N/A' AS secret_columns, + i.index_usage_summary, + sz.index_size_summary + FROM #IndexSanity i + LEFT JOIN heaps_cte h ON i.[object_id] = h.[object_id] + AND i.[database_id] = h.[database_id] + AND i.[schema_name] = h.[schema_name] + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + WHERE i.index_id = 0 + AND + (i.total_reads > 0 OR i.user_updates > 0) + AND sz.total_rows >= 10000 AND sz.total_rows < 100000 + AND h.[object_id] IS NULL /*don't duplicate the prior check.*/ + OPTION ( RECOMPILE ); - CREATE TABLE #MissingIndexes - ([database_id] INT NOT NULL, - [object_id] INT NOT NULL, - [database_name] NVARCHAR(128) NOT NULL , - [schema_name] NVARCHAR(128) NOT NULL , - [table_name] NVARCHAR(128), - [statement] NVARCHAR(512) NOT NULL, - magic_benefit_number AS (( user_seeks + user_scans ) * avg_total_user_cost * avg_user_impact), - avg_total_user_cost NUMERIC(29,4) NOT NULL, - avg_user_impact NUMERIC(29,1) NOT NULL, - user_seeks BIGINT NOT NULL, - user_scans BIGINT NOT NULL, - unique_compiles BIGINT NULL, - equality_columns NVARCHAR(MAX), - equality_columns_with_data_type NVARCHAR(MAX), - inequality_columns NVARCHAR(MAX), - inequality_columns_with_data_type NVARCHAR(MAX), - included_columns NVARCHAR(MAX), - included_columns_with_data_type NVARCHAR(MAX), - is_low BIT, - [index_estimated_impact] AS - REPLACE(CONVERT(NVARCHAR(256),CAST(CAST( - (user_seeks + user_scans) - AS BIGINT) AS MONEY), 1), '.00', '') + N' use' - + CASE WHEN (user_seeks + user_scans) > 1 THEN N's' ELSE N'' END - +N'; Impact: ' + CAST(avg_user_impact AS NVARCHAR(30)) - + N'%; Avg query cost: ' - + CAST(avg_total_user_cost AS NVARCHAR(30)), - [missing_index_details] AS - CASE WHEN COALESCE(equality_columns_with_data_type,equality_columns) IS NOT NULL - THEN N'EQUALITY: ' + COALESCE(CAST(equality_columns_with_data_type AS NVARCHAR(MAX)), CAST(equality_columns AS NVARCHAR(MAX))) + N' ' - ELSE N'' END + + RAISERROR(N'check_id 46: Small Heaps with reads or writes.', 0,1) WITH NOWAIT; + WITH heaps_cte + AS ( SELECT [object_id], + [database_id], + [schema_name], + SUM(forwarded_fetch_count) AS forwarded_fetch_count, + SUM(leaf_delete_count) AS leaf_delete_count + FROM #IndexPartitionSanity + GROUP BY [object_id], + [database_id], + [schema_name] + HAVING SUM(forwarded_fetch_count) > 0 + OR SUM(leaf_delete_count) > 0) + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 46 AS check_id, + i.index_sanity_id, + 100 AS Priority, + N'Self Loathing Indexes' AS findings_group, + N'Small Active heap' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/SelfLoathing' AS URL, + N'Should this table be a heap? ' + db_schema_object_indexid AS details, + i.index_definition, + 'N/A' AS secret_columns, + i.index_usage_summary, + sz.index_size_summary + FROM #IndexSanity i + LEFT JOIN heaps_cte h ON i.[object_id] = h.[object_id] + AND i.[database_id] = h.[database_id] + AND i.[schema_name] = h.[schema_name] + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + WHERE i.index_id = 0 + AND + (i.total_reads > 0 OR i.user_updates > 0) + AND sz.total_rows < 10000 + AND h.[object_id] IS NULL /*don't duplicate the prior check.*/ + OPTION ( RECOMPILE ); - CASE WHEN COALESCE(inequality_columns_with_data_type,inequality_columns) IS NOT NULL - THEN N'INEQUALITY: ' + COALESCE(CAST(inequality_columns_with_data_type AS NVARCHAR(MAX)), CAST(inequality_columns AS NVARCHAR(MAX))) + N' ' - ELSE N'' END + + RAISERROR(N'check_id 47: Heap with a Nonclustered Primary Key', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 47 AS check_id, + i.index_sanity_id, + 100 AS Priority, + N'Self Loathing Indexes' AS findings_group, + N'Heap with a Nonclustered Primary Key' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/SelfLoathing' AS URL, + db_schema_object_indexid + N' is a HEAP with a Nonclustered Primary Key' AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + sz.index_size_summary + FROM #IndexSanity i + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + WHERE i.index_type = 2 AND i.is_primary_key = 1 + AND EXISTS + ( + SELECT 1/0 + FROM #IndexSanity AS isa + WHERE i.database_id = isa.database_id + AND i.object_id = isa.object_id + AND isa.index_id = 0 + ) + OPTION ( RECOMPILE ); - CASE WHEN COALESCE(included_columns_with_data_type,included_columns) IS NOT NULL - THEN N'INCLUDE: ' + COALESCE(CAST(included_columns_with_data_type AS NVARCHAR(MAX)), CAST(included_columns AS NVARCHAR(MAX))) + N' ' - ELSE N'' END, - [create_tsql] AS N'CREATE INDEX [' - + LEFT(REPLACE(REPLACE(REPLACE(REPLACE( - ISNULL(equality_columns,N'')+ - CASE WHEN equality_columns IS NOT NULL AND inequality_columns IS NOT NULL THEN N'_' ELSE N'' END - + ISNULL(inequality_columns,''),',','') - ,'[',''),']',''),' ','_') - + CASE WHEN included_columns IS NOT NULL THEN N'_Includes' ELSE N'' END, 128) + N'] ON ' - + [statement] + N' (' + ISNULL(equality_columns,N'') - + CASE WHEN equality_columns IS NOT NULL AND inequality_columns IS NOT NULL THEN N', ' ELSE N'' END - + CASE WHEN inequality_columns IS NOT NULL THEN inequality_columns ELSE N'' END + - ') ' + CASE WHEN included_columns IS NOT NULL THEN N' INCLUDE (' + included_columns + N')' ELSE N'' END - + N' WITH (' - + N'FILLFACTOR=100, ONLINE=?, SORT_IN_TEMPDB=?, DATA_COMPRESSION=?' - + N')' - + N';' - , - [more_info] AS N'EXEC dbo.sp_BlitzIndex @DatabaseName=' + QUOTENAME([database_name],'''') + - N', @SchemaName=' + QUOTENAME([schema_name],'''') + N', @TableName=' + QUOTENAME([table_name],'''') + N';', - [sample_query_plan] XML NULL - ); + RAISERROR(N'check_id 48: Nonclustered indexes with a bad read to write ratio', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 48 AS check_id, + i.index_sanity_id, + 100 AS Priority, + N'Index Hoarder' AS findings_group, + N'NC index with High Writes:Reads' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/IndexHoarder' AS URL, + N'Reads: ' + + REPLACE(CONVERT(NVARCHAR(30), CAST((i.total_reads) AS MONEY), 1), N'.00', N'') + + N' Writes: ' + + REPLACE(CONVERT(NVARCHAR(30), CAST((i.user_updates) AS MONEY), 1), N'.00', N'') + + N' on: ' + + i.db_schema_object_indexid AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + sz.index_size_summary + FROM #IndexSanity i + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + WHERE i.total_reads > 0 /*Not totally unused*/ + AND i.user_updates >= 10000 /*Decent write activity*/ + AND i.total_reads < 10000 + AND ((i.total_reads * 10) < i.user_updates) /*10x more writes than reads*/ + 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 + ORDER BY i.db_schema_object_indexid + OPTION ( RECOMPILE ); - CREATE TABLE #ForeignKeys ( - [database_id] INT NOT NULL, - [database_name] NVARCHAR(128) NOT NULL , - [schema_name] NVARCHAR(128) NOT NULL , - foreign_key_name NVARCHAR(256), - parent_object_id INT, - parent_object_name NVARCHAR(256), - referenced_object_id INT, - referenced_object_name NVARCHAR(256), - is_disabled BIT, - is_not_trusted BIT, - is_not_for_replication BIT, - parent_fk_columns NVARCHAR(MAX), - referenced_fk_columns NVARCHAR(MAX), - update_referential_action_desc NVARCHAR(16), - delete_referential_action_desc NVARCHAR(60) - ); - - CREATE TABLE #IndexCreateTsql ( - index_sanity_id INT NOT NULL, - create_tsql NVARCHAR(MAX) NOT NULL - ); + ---------------------------------------- + --Indexaphobia + --Missing indexes with value >= 5 million: : Check_id 50-59 + ---------------------------------------- + RAISERROR(N'check_id 50: Indexaphobia.', 0,1) WITH NOWAIT; + WITH index_size_cte + AS ( SELECT i.database_id, + i.schema_name, + i.[object_id], + MAX(i.index_sanity_id) AS index_sanity_id, + ISNULL(NULLIF(MAX(DATEDIFF(DAY, i.create_date, SYSDATETIME())), 0), 1) AS create_days, + ISNULL ( + CAST(SUM(CASE WHEN index_id NOT IN (0,1) THEN 1 ELSE 0 END) + AS NVARCHAR(30))+ N' NC indexes exist (' + + CASE WHEN SUM(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) > 1024 + THEN CAST(CAST(SUM(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END )/1024. - CREATE TABLE #DatabaseList ( - DatabaseName NVARCHAR(256), - secondary_role_allow_connections_desc NVARCHAR(50) + AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'GB); ' + ELSE CAST(SUM(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) + AS NVARCHAR(30)) + N'MB); ' + END + + CASE WHEN MAX(sz.[total_rows]) >= 922337203685477 THEN '>= 922,337,203,685,477' + ELSE REPLACE(CONVERT(NVARCHAR(30),CAST(MAX(sz.[total_rows]) AS MONEY), 1), '.00', '') + END + + + N' Estimated Rows;' + ,N'') AS index_size_summary + FROM #IndexSanity AS i + LEFT JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id AND i.database_id = sz.database_id + WHERE i.is_hypothetical = 0 + AND i.is_disabled = 0 + GROUP BY i.database_id, i.schema_name, i.[object_id]) + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + index_usage_summary, index_size_summary, create_tsql, more_info ) + + SELECT check_id, t.index_sanity_id, t.check_id, t.findings_group, t.finding, t.[Database Name], t.URL, t.details, t.[definition], + index_estimated_impact, t.index_size_summary, create_tsql, more_info + FROM + ( + SELECT ROW_NUMBER() OVER (ORDER BY magic_benefit_number DESC) AS rownum, + 50 AS check_id, + sz.index_sanity_id, + 40 AS Priority, + N'Indexaphobia' AS findings_group, + N'High Value Missing Index' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/Indexaphobia' AS URL, + mi.[statement] + + N' Est. benefit per day: ' + + CASE WHEN magic_benefit_number >= 922337203685477 THEN '>= 922,337,203,685,477' + ELSE REPLACE(CONVERT(NVARCHAR(256),CAST(CAST( + (magic_benefit_number/@DaysUptime) + AS BIGINT) AS MONEY), 1), '.00', '') + END AS details, + missing_index_details AS [definition], + index_estimated_impact, + sz.index_size_summary, + mi.create_tsql, + mi.more_info, + magic_benefit_number, + mi.is_low + FROM #MissingIndexes mi + LEFT JOIN index_size_cte sz ON mi.[object_id] = sz.object_id + AND mi.database_id = sz.database_id + AND mi.schema_name = sz.schema_name + /* Minimum benefit threshold = 100k/day of uptime OR since table creation date, whichever is lower*/ + WHERE @ShowAllMissingIndexRequests=1 + OR ( @Mode = 4 AND (magic_benefit_number / CASE WHEN sz.create_days < @DaysUptime THEN sz.create_days ELSE @DaysUptime END) >= 100000 ) + OR (magic_benefit_number / CASE WHEN sz.create_days < @DaysUptime THEN sz.create_days ELSE @DaysUptime END) >= 100000 + ) AS t + WHERE t.rownum <= CASE WHEN (@Mode <> 4) THEN 20 ELSE t.rownum END + ORDER BY magic_benefit_number DESC + OPTION ( RECOMPILE ); - ); - CREATE TABLE #PartitionCompressionInfo ( - [index_sanity_id] INT NULL, - [partition_compression_detail] NVARCHAR(4000) NULL - ); - CREATE TABLE #Statistics ( - database_id INT NOT NULL, - database_name NVARCHAR(256) NOT NULL, - table_name NVARCHAR(128) NULL, - schema_name NVARCHAR(128) NULL, - index_name NVARCHAR(128) NULL, - column_names NVARCHAR(MAX) NULL, - statistics_name NVARCHAR(128) NULL, - last_statistics_update DATETIME NULL, - days_since_last_stats_update INT NULL, - rows BIGINT NULL, - rows_sampled BIGINT NULL, - percent_sampled DECIMAL(18, 1) NULL, - histogram_steps INT NULL, - modification_counter BIGINT NULL, - percent_modifications DECIMAL(18, 1) NULL, - modifications_before_auto_update INT 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 - ); + RAISERROR(N'check_id 68: Identity columns within 30 percent of the end of range', 0,1) WITH NOWAIT; + -- Allowed Ranges: + --int -2,147,483,648 to 2,147,483,647 + --smallint -32,768 to 32,768 + --tinyint 0 to 255 - CREATE TABLE #ComputedColumns - ( - index_sanity_id INT IDENTITY(1, 1) NOT NULL, - database_name NVARCHAR(128) NULL, - database_id INT NOT NULL, - table_name NVARCHAR(128) NOT NULL, - schema_name NVARCHAR(128) NOT NULL, - column_name NVARCHAR(128) NULL, - is_nullable BIT NULL, - definition NVARCHAR(MAX) NULL, - uses_database_collation BIT NOT NULL, - is_persisted BIT NOT NULL, - is_computed BIT NOT NULL, - is_function INT NOT NULL, - column_definition NVARCHAR(MAX) NULL - ); - - CREATE TABLE #TraceStatus - ( - TraceFlag NVARCHAR(10) , - status BIT , - Global BIT , - Session BIT - ); + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 68 AS check_id, + i.index_sanity_id, + 80 AS Priority, + N'Abnormal Psychology' AS findings_group, + N'Identity Column Within ' + + CAST (calc1.percent_remaining AS NVARCHAR(256)) + + N' Percent End of Range' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, + i.db_schema_object_name + N'.' + QUOTENAME(ic.column_name) + + N' is an identity with type ' + ic.system_type_name + + N', last value of ' + + ISNULL((CONVERT(NVARCHAR(256),CAST(ic.last_value AS DECIMAL(38,0)), 1)),N'NULL') + + N', seed of ' + + ISNULL((CONVERT(NVARCHAR(256),CAST(ic.seed_value AS DECIMAL(38,0)), 1)),N'NULL') + + N', increment of ' + CAST(ic.increment_value AS NVARCHAR(256)) + + N', and range of ' + + CASE ic.system_type_name WHEN 'int' THEN N'+/- 2,147,483,647' + WHEN 'smallint' THEN N'+/- 32,768' + WHEN 'tinyint' THEN N'0 to 255' + ELSE 'unknown' + END + AS details, + i.index_definition, + secret_columns, + ISNULL(i.index_usage_summary,''), + ISNULL(ip.index_size_summary,'') + FROM #IndexSanity i + JOIN #IndexColumns ic ON + i.object_id=ic.object_id + AND i.database_id = ic.database_id + AND i.schema_name = ic.schema_name + AND i.index_id IN (0,1) /* heaps and cx only */ + AND ic.is_identity=1 + AND ic.system_type_name IN ('tinyint', 'smallint', 'int') + JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id + CROSS APPLY ( + SELECT CAST(CASE WHEN ic.increment_value >= 0 + THEN + CASE ic.system_type_name + WHEN 'int' THEN (2147483647 - (ISNULL(ic.last_value,ic.seed_value) + ic.increment_value)) / 2147483647.*100 + WHEN 'smallint' THEN (32768 - (ISNULL(ic.last_value,ic.seed_value) + ic.increment_value)) / 32768.*100 + WHEN 'tinyint' THEN ( 255 - (ISNULL(ic.last_value,ic.seed_value) + ic.increment_value)) / 255.*100 + ELSE 999 + END + ELSE --ic.increment_value is negative + CASE ic.system_type_name + WHEN 'int' THEN ABS(-2147483647 - (ISNULL(ic.last_value,ic.seed_value) + ic.increment_value)) / 2147483647.*100 + WHEN 'smallint' THEN ABS(-32768 - (ISNULL(ic.last_value,ic.seed_value) + ic.increment_value)) / 32768.*100 + WHEN 'tinyint' THEN ABS( 0 - (ISNULL(ic.last_value,ic.seed_value) + ic.increment_value)) / 255.*100 + ELSE -1 + END + END AS NUMERIC(5,1)) AS percent_remaining + ) AS calc1 + WHERE i.index_id IN (1,0) + AND calc1.percent_remaining <= 30 + OPTION (RECOMPILE); - CREATE TABLE #TemporalTables - ( - index_sanity_id INT IDENTITY(1, 1) NOT NULL, - database_name NVARCHAR(128) NOT NULL, - database_id INT NOT NULL, - schema_name NVARCHAR(128) NOT NULL, - table_name NVARCHAR(128) NOT NULL, - history_table_name NVARCHAR(128) NOT NULL, - history_schema_name NVARCHAR(128) NOT NULL, - start_column_name NVARCHAR(128) NOT NULL, - end_column_name NVARCHAR(128) NOT NULL, - period_name NVARCHAR(128) NOT NULL - ); - CREATE TABLE #CheckConstraints - ( - index_sanity_id INT IDENTITY(1, 1) NOT NULL, - database_name NVARCHAR(128) NULL, - database_id INT NOT NULL, - table_name NVARCHAR(128) NOT NULL, - schema_name NVARCHAR(128) NOT NULL, - constraint_name NVARCHAR(128) NULL, - is_disabled BIT NULL, - definition NVARCHAR(MAX) NULL, - uses_database_collation BIT NOT NULL, - is_not_trusted BIT NOT NULL, - is_function INT NOT NULL, - column_definition NVARCHAR(MAX) NULL - ); + RAISERROR(N'check_id 72: Columnstore indexes with Trace Flag 834', 0,1) WITH NOWAIT; + IF EXISTS (SELECT * FROM #IndexSanity WHERE index_type IN (5,6)) + AND EXISTS (SELECT * FROM #TraceStatus WHERE TraceFlag = 834 AND status = 1) + BEGIN + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 72 AS check_id, + i.index_sanity_id, + 80 AS Priority, + N'Abnormal Psychology' AS findings_group, + 'Columnstore Indexes with Trace Flag 834' AS finding, + [database_name] AS [Database Name], + N'https://support.microsoft.com/en-us/kb/3210239' AS URL, + i.db_schema_object_indexid AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + ISNULL(sz.index_size_summary,'') AS index_size_summary + FROM #IndexSanity AS i + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + WHERE i.index_type IN (5,6) + OPTION ( RECOMPILE ); + END; - CREATE TABLE #FilteredIndexes - ( - index_sanity_id INT IDENTITY(1, 1) NOT NULL, - database_name NVARCHAR(128) NULL, - database_id INT NOT NULL, - schema_name NVARCHAR(128) NOT NULL, - table_name NVARCHAR(128) NOT NULL, - index_name NVARCHAR(128) NULL, - column_name NVARCHAR(128) NULL - ); + ---------------------------------------- + --Statistics Info: Check_id 90-99 + ---------------------------------------- - CREATE TABLE #Ignore_Databases - ( - DatabaseName NVARCHAR(128), - Reason NVARCHAR(100) - ); + RAISERROR(N'check_id 90: Outdated 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 ) + SELECT 90 AS check_id, + 90 AS Priority, + 'Functioning Statistaholics' AS findings_group, + 'Statistics Not Updated Recently', + s.database_name, + 'https://www.brentozar.com/go/stats' AS URL, + 'Statistics on this table were last updated ' + + CASE WHEN s.last_statistics_update IS NULL THEN N' NEVER ' + ELSE CONVERT(NVARCHAR(20), s.last_statistics_update) + + ' have had ' + CONVERT(NVARCHAR(100), s.modification_counter) + + ' modifications in that time, which is ' + + CONVERT(NVARCHAR(100), s.percent_modifications) + + '% of the table.' + END 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.last_statistics_update <= CONVERT(DATETIME, GETDATE() - 30) + AND s.percent_modifications >= 10. + AND s.rows >= 10000 + OPTION ( RECOMPILE ); -/* Sanitize our inputs */ -SELECT - @OutputServerName = QUOTENAME(@OutputServerName), - @OutputDatabaseName = QUOTENAME(@OutputDatabaseName), - @OutputSchemaName = QUOTENAME(@OutputSchemaName), - @OutputTableName = QUOTENAME(@OutputTableName); - - -IF @GetAllDatabases = 1 - BEGIN - INSERT INTO #DatabaseList (DatabaseName) - SELECT DB_NAME(database_id) - FROM sys.databases - WHERE user_access_desc = 'MULTI_USER' - AND state_desc = 'ONLINE' - AND database_id > 4 - AND DB_NAME(database_id) NOT LIKE 'ReportServer%' - AND DB_NAME(database_id) NOT LIKE 'rdsadmin%' - AND is_distributor = 0 + RAISERROR(N'check_id 91: Statistics with a low 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 91 AS check_id, + 90 AS Priority, + 'Functioning Statistaholics' AS findings_group, + 'Low Sampling Rates', + s.database_name, + 'https://www.brentozar.com/go/stats' AS URL, + 'Only ' + CONVERT(NVARCHAR(100), s.percent_sampled) + '% of the rows were sampled during the last statistics update. This may lead to poor cardinality estimates.' 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.rows BETWEEN 10000 AND 1000000 AND s.percent_sampled < 10) + OR (s.rows > 1000000 AND s.percent_sampled < 1) OPTION ( RECOMPILE ); - /* Skip non-readable databases in an AG - see Github issue #1160 */ - IF EXISTS (SELECT * FROM sys.all_objects o INNER JOIN sys.all_columns c ON o.object_id = c.object_id AND o.name = 'dm_hadr_availability_replica_states' AND c.name = 'role_desc') - BEGIN - SET @dsql = N'UPDATE #DatabaseList SET secondary_role_allow_connections_desc = ''NO'' WHERE DatabaseName IN ( - SELECT d.name - FROM sys.dm_hadr_availability_replica_states rs - INNER JOIN sys.databases d ON rs.replica_id = d.replica_id - INNER JOIN sys.availability_replicas r ON rs.replica_id = r.replica_id - WHERE rs.role_desc = ''SECONDARY'' - AND r.secondary_role_allow_connections_desc = ''NO'') - OPTION ( RECOMPILE );'; - EXEC sp_executesql @dsql; + 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 ) + SELECT 92 AS check_id, + 90 AS Priority, + 'Functioning Statistaholics' AS findings_group, + 'Statistics With NO RECOMPUTE', + s.database_name, + 'https://www.brentozar.com/go/stats' AS URL, + 'The statistic ' + QUOTENAME(s.statistics_name) + ' is set to not recompute. This can be helpful if data is really skewed, but harmful if you expect automatic statistics updates.' 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.no_recompute = 1 + OPTION ( RECOMPILE ); - IF EXISTS (SELECT * FROM #DatabaseList WHERE secondary_role_allow_connections_desc = 'NO') - BEGIN - INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, database_name, URL, details, index_definition, - index_usage_summary, index_size_summary ) - VALUES ( 1, - 0, - N'Skipped non-readable AG secondary databases.', - N'You are running this on an AG secondary, and some of your databases are configured as non-readable when this is a secondary node.', - N'To analyze those databases, run sp_BlitzIndex on the primary, or on a readable secondary.', - 'http://FirstResponderKit.org', '', '', '', '' - ); - END; - END; - IF @IgnoreDatabases IS NOT NULL - AND LEN(@IgnoreDatabases) > 0 - BEGIN - RAISERROR(N'Setting up filter to ignore databases', 0, 1) WITH NOWAIT; - SET @DatabaseToIgnore = ''; + 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 ) + SELECT 94 AS check_id, + 100 AS Priority, + 'Serial Forcer' AS findings_group, + 'Check Constraint with Scalar UDF' AS finding, + cc.database_name, + 'https://www.brentozar.com/go/computedscalar' AS URL, + 'The check constraint ' + QUOTENAME(cc.constraint_name) + ' on ' + QUOTENAME(cc.schema_name) + '.' + QUOTENAME(cc.table_name) + ' is based on ' + cc.definition + + '. That indicates it may reference a scalar function, or a CLR function with data access, which can cause all queries and maintenance to run serially.' AS details, + cc.column_definition, + 'N/A' AS secret_columns, + 'N/A' AS index_usage_summary, + 'N/A' AS index_size_summary + FROM #CheckConstraints AS cc + WHERE cc.is_function = 1 + OPTION ( RECOMPILE ); - WHILE LEN(@IgnoreDatabases) > 0 - BEGIN - IF PATINDEX('%,%', @IgnoreDatabases) > 0 - BEGIN - SET @DatabaseToIgnore = SUBSTRING(@IgnoreDatabases, 0, PATINDEX('%,%',@IgnoreDatabases)) ; - - INSERT INTO #Ignore_Databases (DatabaseName, Reason) - SELECT LTRIM(RTRIM(@DatabaseToIgnore)), 'Specified in the @IgnoreDatabases parameter' - OPTION (RECOMPILE) ; - - SET @IgnoreDatabases = SUBSTRING(@IgnoreDatabases, LEN(@DatabaseToIgnore + ',') + 1, LEN(@IgnoreDatabases)) ; - END; - ELSE - BEGIN - SET @DatabaseToIgnore = @IgnoreDatabases ; - SET @IgnoreDatabases = NULL ; + RAISERROR(N'check_id 99: Computed Columns 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 ) + SELECT 99 AS check_id, + 100 AS Priority, + 'Serial Forcer' AS findings_group, + 'Computed Column with Scalar UDF' AS finding, + cc.database_name, + 'https://www.brentozar.com/go/serialudf' AS URL, + 'The computed column ' + QUOTENAME(cc.column_name) + ' on ' + QUOTENAME(cc.schema_name) + '.' + QUOTENAME(cc.table_name) + ' is based on ' + cc.definition + + '. That indicates it may reference a scalar function, or a CLR function with data access, which can cause all queries and maintenance to run serially.' AS details, + cc.column_definition, + 'N/A' AS secret_columns, + 'N/A' AS index_usage_summary, + 'N/A' AS index_size_summary + FROM #ComputedColumns AS cc + WHERE cc.is_function = 1 + OPTION ( RECOMPILE ); - INSERT INTO #Ignore_Databases (DatabaseName, Reason) - SELECT LTRIM(RTRIM(@DatabaseToIgnore)), 'Specified in the @IgnoreDatabases parameter' - OPTION (RECOMPILE) ; - END; - END; - - END - END; -ELSE - BEGIN - INSERT INTO #DatabaseList - ( DatabaseName ) - SELECT CASE - WHEN @DatabaseName IS NULL OR @DatabaseName = N'' - THEN DB_NAME() - ELSE @DatabaseName END; - END; -SET @NumDatabases = (SELECT COUNT(*) FROM #DatabaseList AS D LEFT OUTER JOIN #Ignore_Databases AS I ON D.DatabaseName = I.DatabaseName WHERE I.DatabaseName IS NULL); -SET @msg = N'Number of databases to examine: ' + CAST(@NumDatabases AS NVARCHAR(50)); -RAISERROR (@msg,0,1) WITH NOWAIT; + END /* IF @Mode IN (0, 4) DIAGNOSE priorities 1-100 */ + -/* Running on 50+ databases can take a reaaallly long time, so we want explicit permission to do so (and only after warning about it) */ -BEGIN TRY - IF @NumDatabases >= 50 AND @BringThePain != 1 AND @TableName IS NULL - BEGIN - INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, URL, details, index_definition, - index_usage_summary, index_size_summary ) - VALUES ( -1, - 0 , - @ScriptVersionName, - CASE WHEN @GetAllDatabases = 1 THEN N'All Databases' ELSE N'Database ' + QUOTENAME(@DatabaseName) + N' as of ' + CONVERT(NVARCHAR(16), GETDATE(), 121) END, - N'From Your Community Volunteers', - N'http://FirstResponderKit.org', - N'', - N'', - N'' - ); - INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, database_name, URL, details, index_definition, - index_usage_summary, index_size_summary ) - VALUES ( 1, - 0, - N'You''re trying to run sp_BlitzIndex on a server with ' + CAST(@NumDatabases AS NVARCHAR(8)) + N' databases. ', - N'Running sp_BlitzIndex on a server with 50+ databases may cause temporary insanity for the server and/or user.', - N'If you''re sure you want to do this, run again with the parameter @BringThePain = 1.', - 'http://FirstResponderKit.org', - '', - '', - '', - '' - ); - - if(@OutputType <> 'NONE') - BEGIN - SELECT bir.blitz_result_id, - bir.check_id, - bir.index_sanity_id, - bir.Priority, - bir.findings_group, - bir.finding, - bir.database_name, - bir.URL, - bir.details, - bir.index_definition, - bir.secret_columns, - bir.index_usage_summary, - bir.index_size_summary, - bir.create_tsql, - bir.more_info - FROM #BlitzIndexResults AS bir; - RAISERROR('Running sp_BlitzIndex on a server with 50+ databases may cause temporary insanity for the server', 12, 1); - END; - RETURN; - END; -END TRY -BEGIN CATCH - RAISERROR (N'Failure to execute due to number of databases.', 0,1) WITH NOWAIT; - SELECT @msg = ERROR_MESSAGE(), - @ErrorSeverity = ERROR_SEVERITY(), - @ErrorState = ERROR_STATE(); - RAISERROR (@msg, @ErrorSeverity, @ErrorState); - - WHILE @@trancount > 0 - ROLLBACK; - RETURN; - END CATCH; -RAISERROR (N'Checking partition counts to exclude databases with over 100 partitions',0,1) WITH NOWAIT; -IF @BringThePain = 0 AND @SkipPartitions = 0 AND @TableName IS NULL - BEGIN - DECLARE partition_cursor CURSOR FOR - SELECT dl.DatabaseName - FROM #DatabaseList dl - LEFT OUTER JOIN #Ignore_Databases i ON dl.DatabaseName = i.DatabaseName - WHERE COALESCE(dl.secondary_role_allow_connections_desc, 'OK') <> 'NO' - AND i.DatabaseName IS NULL - OPEN partition_cursor - FETCH NEXT FROM partition_cursor INTO @DatabaseName - - WHILE @@FETCH_STATUS = 0 - BEGIN - /* Count the total number of partitions */ - SET @dsql = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @RowcountOUT = SUM(1) FROM ' + QUOTENAME(@DatabaseName) + '.sys.partitions WHERE partition_number > 1 OPTION ( RECOMPILE );'; - EXEC sp_executesql @dsql, N'@RowcountOUT BIGINT OUTPUT', @RowcountOUT = @Rowcount OUTPUT; - IF @Rowcount > 100 - BEGIN - RAISERROR (N'Skipping database %s because > 100 partitions were found. To check this database, you must set @BringThePain = 1.',0,1,@DatabaseName) WITH NOWAIT; - INSERT INTO #Ignore_Databases (DatabaseName, Reason) - SELECT @DatabaseName, 'Over 100 partitions found - use @BringThePain = 1 to analyze' - END; - FETCH NEXT FROM partition_cursor INTO @DatabaseName - END; - CLOSE partition_cursor - DEALLOCATE partition_cursor - END; -INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, URL, details, index_definition, - index_usage_summary, index_size_summary ) -SELECT 1, 0 , - 'Database Skipped', - i.DatabaseName, - 'http://FirstResponderKit.org', - i.Reason, '', '', '' -FROM #Ignore_Databases i; -/* Last startup */ -SELECT @DaysUptime = CAST(DATEDIFF(HOUR, create_date, GETDATE()) / 24. AS NUMERIC (23,2)) -FROM sys.databases -WHERE database_id = 2; -IF @DaysUptime = 0 OR @DaysUptime IS NULL - SET @DaysUptime = .01; -SELECT @DaysUptimeInsertValue = 'Server: ' + (CONVERT(VARCHAR(256), (SERVERPROPERTY('ServerName')))) + ' Days Uptime: ' + RTRIM(@DaysUptime); -/* Permission granted or unnecessary? Ok, let's go! */ -RAISERROR (N'Starting loop through databases',0,1) WITH NOWAIT; -DECLARE c1 CURSOR -LOCAL FAST_FORWARD -FOR -SELECT dl.DatabaseName -FROM #DatabaseList dl -LEFT OUTER JOIN #Ignore_Databases i ON dl.DatabaseName = i.DatabaseName -WHERE COALESCE(dl.secondary_role_allow_connections_desc, 'OK') <> 'NO' - AND i.DatabaseName IS NULL -ORDER BY dl.DatabaseName; -OPEN c1; -FETCH NEXT FROM c1 INTO @DatabaseName; - WHILE @@FETCH_STATUS = 0 + IF @Mode = 4 /* DIAGNOSE*/ + BEGIN; + RAISERROR(N'@Mode=4, running rules for priorities 101+.', 0,1) WITH NOWAIT; -BEGIN - - RAISERROR (@LineFeed, 0, 1) WITH NOWAIT; - RAISERROR (@LineFeed, 0, 1) WITH NOWAIT; - RAISERROR (@DatabaseName, 0, 1) WITH NOWAIT; + RAISERROR(N'check_id 21: More Than 5 Percent NC Indexes Are Unused', 0,1) WITH NOWAIT; + DECLARE @percent_NC_indexes_unused NUMERIC(29,1); + DECLARE @NC_indexes_unused_reserved_MB NUMERIC(29,1); -SELECT @DatabaseID = [database_id] -FROM sys.databases - WHERE [name] = @DatabaseName - AND user_access_desc='MULTI_USER' - AND state_desc = 'ONLINE'; + SELECT @percent_NC_indexes_unused = ( 100.00 * SUM(CASE + WHEN total_reads = 0 + THEN 1 + ELSE 0 + END) ) / COUNT(*), + @NC_indexes_unused_reserved_MB = SUM(CASE + WHEN total_reads = 0 + THEN sz.total_reserved_MB + ELSE 0 + END) + FROM #IndexSanity i + 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()) + 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, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 21 AS check_id, + MAX(i.index_sanity_id) AS index_sanity_id, + 150 AS Priority, + N'Index Hoarder' AS findings_group, + N'More Than 5 Percent NC Indexes Are Unused' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/IndexHoarder' AS URL, + CAST (@percent_NC_indexes_unused AS NVARCHAR(30)) + N' percent NC indexes (' + CAST(COUNT(*) AS NVARCHAR(10)) + N') unused. ' + + N'These take up ' + CAST (@NC_indexes_unused_reserved_MB AS NVARCHAR(30)) + N'MB of space.' AS details, + i.database_name + ' (' + CAST (COUNT(*) AS NVARCHAR(30)) + N' indexes)' AS index_definition, + '' AS secret_columns, + CAST(SUM(total_reads) AS NVARCHAR(256)) + N' reads (ALL); ' + + CAST(SUM([user_updates]) AS NVARCHAR(256)) + N' writes (ALL)' AS index_usage_summary, + + REPLACE(CONVERT(NVARCHAR(30),CAST(MAX([total_rows]) AS MONEY), 1), '.00', '') + N' rows (MAX)' + + CASE WHEN SUM(total_reserved_MB) > 1024 THEN + N'; ' + CAST(CAST(SUM(total_reserved_MB)/1024. AS NUMERIC(29,1)) AS NVARCHAR(30)) + 'GB (ALL)' + WHEN SUM(total_reserved_MB) > 0 THEN + N'; ' + CAST(CAST(SUM(total_reserved_MB) AS NUMERIC(29,1)) AS NVARCHAR(30)) + 'MB (ALL)' + ELSE '' + END AS index_size_summary + FROM #IndexSanity i + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + 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()) + GROUP BY i.database_name + OPTION ( RECOMPILE ); ----------------------------------------- ---STEP 1: OBSERVE THE PATIENT ---This step puts index information into temp tables. ----------------------------------------- -BEGIN TRY - BEGIN + RAISERROR(N'check_id 23: Indexes with 7 or more columns. (Borderline)', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 23 AS check_id, + i.index_sanity_id, + 150 AS Priority, + N'Index Hoarder' AS findings_group, + N'Borderline: Wide Indexes (7 or More Columns)' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/IndexHoarder' AS URL, + CAST(count_key_columns + count_included_columns AS NVARCHAR(10)) + ' columns on ' + + i.db_schema_object_indexid AS details, i.index_definition, + i.secret_columns, + i.index_usage_summary, + sz.index_size_summary + FROM #IndexSanity AS i + JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id + WHERE ( count_key_columns + count_included_columns ) >= 7 + OPTION ( RECOMPILE ); - --Validate SQL Server Version + RAISERROR(N'check_id 24: Wide clustered indexes (> 3 columns or > 16 bytes).', 0,1) WITH NOWAIT; + WITH count_columns AS ( + SELECT database_id, [object_id], + SUM(CASE max_length WHEN -1 THEN 0 ELSE max_length END) AS sum_max_length + FROM #IndexColumns ic + WHERE index_id IN (1,0) /*Heap or clustered only*/ + AND key_ordinal > 0 + GROUP BY database_id, object_id + ) + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 24 AS check_id, + i.index_sanity_id, + 150 AS Priority, + N'Index Hoarder' AS findings_group, + N'Wide Clustered Index (> 3 columns OR > 16 bytes)' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/IndexHoarder' AS URL, + CAST (i.count_key_columns AS NVARCHAR(10)) + N' columns with potential size of ' + + CAST(cc.sum_max_length AS NVARCHAR(10)) + + N' bytes in clustered index:' + i.db_schema_object_name + + N'. ' + + (SELECT CAST(COUNT(*) AS NVARCHAR(23)) + FROM #IndexSanity i2 + WHERE i2.[object_id]=i.[object_id] + AND i2.database_id = i.database_id + AND i2.index_id <> 1 + AND i2.is_disabled=0 + AND i2.is_hypothetical=0) + + N' NC indexes on the table.' + AS details, + i.index_definition, + secret_columns, + i.index_usage_summary, + ip.index_size_summary + FROM #IndexSanity i + JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id + JOIN count_columns AS cc ON i.[object_id]=cc.[object_id] + AND i.database_id = cc.database_id + WHERE index_id = 1 /* clustered only */ + AND + (count_key_columns > 3 /*More than three key columns.*/ + OR cc.sum_max_length > 16 /*More than 16 bytes in key */) + AND i.is_CX_columnstore = 0 + ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); - IF (SELECT LEFT(@SQLServerProductVersion, - CHARINDEX('.',@SQLServerProductVersion,0)-1 - )) <= 9 - BEGIN - SET @msg=N'sp_BlitzIndex is only supported on SQL Server 2008 and higher. The version of this instance is: ' + @SQLServerProductVersion; - RAISERROR(@msg,16,1); - END; + RAISERROR(N'check_id 25: Addicted to nullable columns.', 0,1) WITH NOWAIT; + WITH count_columns AS ( + SELECT [object_id], + [database_id], + [schema_name], + SUM(CASE is_nullable WHEN 1 THEN 0 ELSE 1 END) AS non_nullable_columns, + COUNT(*) AS total_columns + FROM #IndexColumns ic + WHERE index_id IN (1,0) /*Heap or clustered only*/ + GROUP BY [object_id], + [database_id], + [schema_name] + ) + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 25 AS check_id, + i.index_sanity_id, + 200 AS Priority, + N'Index Hoarder' AS findings_group, + N'Addicted to Nulls' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/IndexHoarder' AS URL, + i.db_schema_object_name + + N' allows null in ' + CAST((total_columns-non_nullable_columns) AS NVARCHAR(10)) + + N' of ' + CAST(total_columns AS NVARCHAR(10)) + + N' columns.' AS details, + i.index_definition, + secret_columns, + ISNULL(i.index_usage_summary,''), + ISNULL(ip.index_size_summary,'') + FROM #IndexSanity i + JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id + JOIN count_columns AS cc ON i.[object_id]=cc.[object_id] + AND cc.database_id = ip.database_id + AND cc.[schema_name] = ip.[schema_name] + WHERE i.index_id IN (1,0) + AND cc.non_nullable_columns < 2 + AND cc.total_columns > 3 + ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); - --Short circuit here if database name does not exist. - IF @DatabaseName IS NULL OR @DatabaseID IS NULL - BEGIN - SET @msg='Database does not exist or is not online/multi-user: cannot proceed.'; - RAISERROR(@msg,16,1); - END; + RAISERROR(N'check_id 26: Wide tables (35+ cols or > 2000 non-LOB bytes).', 0,1) WITH NOWAIT; + WITH count_columns AS ( + SELECT [object_id], + [database_id], + [schema_name], + SUM(CASE max_length WHEN -1 THEN 1 ELSE 0 END) AS count_lob_columns, + SUM(CASE max_length WHEN -1 THEN 0 ELSE max_length END) AS sum_max_length, + COUNT(*) AS total_columns + FROM #IndexColumns ic + WHERE index_id IN (1,0) /*Heap or clustered only*/ + GROUP BY [object_id], + [database_id], + [schema_name] + ) + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 26 AS check_id, + i.index_sanity_id, + 150 AS Priority, + N'Index Hoarder' AS findings_group, + N'Wide Tables: 35+ cols or > 2000 non-LOB bytes' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/IndexHoarder' AS URL, + i.db_schema_object_name + + N' has ' + CAST((total_columns) AS NVARCHAR(10)) + + N' total columns with a max possible width of ' + CAST(sum_max_length AS NVARCHAR(10)) + + N' bytes.' + + CASE WHEN count_lob_columns > 0 THEN CAST((count_lob_columns) AS NVARCHAR(10)) + + ' columns are LOB types.' ELSE '' + END + AS details, + i.index_definition, + secret_columns, + ISNULL(i.index_usage_summary,''), + ISNULL(ip.index_size_summary,'') + FROM #IndexSanity i + JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id + JOIN count_columns AS cc ON i.[object_id]=cc.[object_id] + AND cc.database_id = i.database_id + AND cc.[schema_name] = i.[schema_name] + WHERE i.index_id IN (1,0) + AND + (cc.total_columns >= 35 OR + cc.sum_max_length >= 2000) + ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); + + RAISERROR(N'check_id 27: Addicted to strings.', 0,1) WITH NOWAIT; + WITH count_columns AS ( + SELECT [object_id], + [database_id], + [schema_name], + SUM(CASE WHEN system_type_name IN ('varchar','nvarchar','char') OR max_length=-1 THEN 1 ELSE 0 END) AS string_or_LOB_columns, + COUNT(*) AS total_columns + FROM #IndexColumns ic + WHERE index_id IN (1,0) /*Heap or clustered only*/ + GROUP BY [object_id], + [database_id], + [schema_name] + ) + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 27 AS check_id, + i.index_sanity_id, + 200 AS Priority, + N'Index Hoarder' AS findings_group, + N'Addicted to strings' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/IndexHoarder' AS URL, + i.db_schema_object_name + + N' uses string or LOB types for ' + CAST((string_or_LOB_columns) AS NVARCHAR(10)) + + N' of ' + CAST(total_columns AS NVARCHAR(10)) + + N' columns. Check if data types are valid.' AS details, + i.index_definition, + secret_columns, + ISNULL(i.index_usage_summary,''), + ISNULL(ip.index_size_summary,'') + FROM #IndexSanity i + JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id + JOIN count_columns AS cc ON i.[object_id]=cc.[object_id] + AND cc.database_id = i.database_id + AND cc.[schema_name] = i.[schema_name] + CROSS APPLY (SELECT cc.total_columns - string_or_LOB_columns AS non_string_or_lob_columns) AS calc1 + WHERE i.index_id IN (1,0) + AND calc1.non_string_or_lob_columns <= 1 + AND cc.total_columns > 3 + ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); - --Validate parameters. - IF (@Mode NOT IN (0,1,2,3,4)) - BEGIN - SET @msg=N'Invalid @Mode parameter. 0=diagnose, 1=summarize, 2=index detail, 3=missing index detail, 4=diagnose detail'; - RAISERROR(@msg,16,1); - END; + RAISERROR(N'check_id 28: Non-unique clustered index.', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 28 AS check_id, + i.index_sanity_id, + 150 AS Priority, + N'Index Hoarder' AS findings_group, + N'Non-Unique Clustered JIndex' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/IndexHoarder' AS URL, + N'Uniquifiers will be required! Clustered index: ' + i.db_schema_object_name + + N' and all NC indexes. ' + + (SELECT CAST(COUNT(*) AS NVARCHAR(23)) + FROM #IndexSanity i2 + WHERE i2.[object_id]=i.[object_id] + AND i2.database_id = i.database_id + AND i2.index_id <> 1 + AND i2.is_disabled=0 + AND i2.is_hypothetical=0) + + N' NC indexes on the table.' + AS details, + i.index_definition, + secret_columns, + i.index_usage_summary, + ip.index_size_summary + FROM #IndexSanity i + JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id + WHERE index_id = 1 /* clustered only */ + AND is_unique=0 /* not unique */ + AND is_CX_columnstore=0 /* not a clustered columnstore-- no unique option on those */ + ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); - IF (@Mode <> 0 AND @TableName IS NOT NULL) - BEGIN - SET @msg=N'Setting the @Mode doesn''t change behavior if you supply @TableName. Use default @Mode=0 to see table detail.'; - RAISERROR(@msg,16,1); - END; + RAISERROR(N'check_id 29: NC indexes with 0 reads. (Borderline) and < 10,000 writes', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 29 AS check_id, + i.index_sanity_id, + 150 AS Priority, + N'Index Hoarder' AS findings_group, + N'Unused NC index with Low Writes' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/IndexHoarder' AS URL, + N'0 reads: ' + i.db_schema_object_indexid AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + sz.index_size_summary + FROM #IndexSanity AS i + JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id + WHERE i.total_reads=0 + AND i.user_updates < 10000 + 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()) + ORDER BY i.db_schema_object_indexid + OPTION ( RECOMPILE ); - IF ((@Mode <> 0 OR @TableName IS NOT NULL) AND @Filter <> 0) - BEGIN - SET @msg=N'@Filter only applies when @Mode=0 and @TableName is not specified. Please try again.'; - RAISERROR(@msg,16,1); - END; - IF (@SchemaName IS NOT NULL AND @TableName IS NULL) - BEGIN - SET @msg='We can''t run against a whole schema! Specify a @TableName, or leave both NULL for diagnosis.'; - RAISERROR(@msg,16,1); - END; + ---------------------------------------- + --Feature-Phobic Indexes: Check_id 30-39 + ---------------------------------------- + RAISERROR(N'check_id 30: No indexes with includes', 0,1) WITH NOWAIT; + /* This does not work the way you'd expect with @GetAllDatabases = 1. For details: + https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/825 + */ + + SELECT database_name, + SUM(CASE WHEN count_included_columns > 0 THEN 1 ELSE 0 END) AS number_indexes_with_includes, + 100.* SUM(CASE WHEN count_included_columns > 0 THEN 1 ELSE 0 END) / ( 1.0 * COUNT(*) ) AS percent_indexes_with_includes + INTO #index_includes + FROM #IndexSanity + WHERE is_hypothetical = 0 + AND is_disabled = 0 + AND NOT (@GetAllDatabases = 1 OR @Mode = 0) + GROUP BY database_name; + + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 30 AS check_id, + NULL AS index_sanity_id, + 250 AS Priority, + N'Feature-Phobic Indexes' AS findings_group, + database_name AS [Database Name], + N'No Indexes Use Includes' AS finding, 'https://www.brentozar.com/go/IndexFeatures' AS URL, + N'No Indexes Use Includes' AS details, + database_name + N' (Entire database)' AS index_definition, + N'' AS secret_columns, + N'N/A' AS index_usage_summary, + N'N/A' AS index_size_summary + FROM #index_includes + WHERE number_indexes_with_includes = 0 + OPTION ( RECOMPILE ); + + RAISERROR(N'check_id 31: < 3 percent of indexes have includes', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 31 AS check_id, + NULL AS index_sanity_id, + 250 AS Priority, + N'Feature-Phobic Indexes' AS findings_group, + N'Few Indexes Use Includes' AS findings, + database_name AS [Database Name], + N'https://www.brentozar.com/go/IndexFeatures' AS URL, + N'Only ' + CAST(percent_indexes_with_includes AS NVARCHAR(20)) + '% of indexes have includes' AS details, + N'Entire database' AS index_definition, + N'' AS secret_columns, + N'N/A' AS index_usage_summary, + N'N/A' AS index_size_summary + FROM #index_includes + WHERE number_indexes_with_includes > 0 AND percent_indexes_with_includes <= 3 + OPTION ( RECOMPILE ); + + RAISERROR(N'check_id 32: filtered indexes and indexed views', 0,1) WITH NOWAIT; + + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT DISTINCT + 32 AS check_id, + NULL AS index_sanity_id, + 250 AS Priority, + N'Feature-Phobic Indexes' AS findings_group, + N'No Filtered Indexes or Indexed Views' AS finding, + i.database_name AS [Database Name], + N'https://www.brentozar.com/go/IndexFeatures' AS URL, + N'These are NOT always needed-- but do you know when you would use them?' AS details, + i.database_name + N' (Entire database)' AS index_definition, + N'' AS secret_columns, + N'N/A' AS index_usage_summary, + N'N/A' AS index_size_summary + FROM #IndexSanity i + WHERE i.database_name NOT IN ( + SELECT database_name + FROM #IndexSanity + WHERE filter_definition <> '' ) + AND i.database_name NOT IN ( + SELECT database_name + FROM #IndexSanity + WHERE is_indexed_view = 1 ) + OPTION ( RECOMPILE ); + + RAISERROR(N'check_id 33: Potential filtered indexes based on column names.', 0,1) WITH NOWAIT; + + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 33 AS check_id, + i.index_sanity_id AS index_sanity_id, + 250 AS Priority, + N'Feature-Phobic Indexes' AS findings_group, + N'Potential Filtered Index (Based on Column Name)' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/IndexFeatures' AS URL, + N'A column name in this index suggests it might be a candidate for filtering (is%, %archive%, %active%, %flag%)' AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + sz.index_size_summary + FROM #IndexColumns ic + JOIN #IndexSanity i ON ic.[object_id]=i.[object_id] + AND ic.database_id =i.database_id + AND ic.schema_name = i.schema_name + AND ic.[index_id]=i.[index_id] + AND i.[index_id] > 1 /* non-clustered index */ + JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id + WHERE (column_name LIKE 'is%' + OR column_name LIKE '%archive%' + OR column_name LIKE '%active%' + OR column_name LIKE '%flag%') + OPTION ( RECOMPILE ); + RAISERROR(N'check_id 41: Hypothetical indexes ', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 41 AS check_id, + i.index_sanity_id, + 150 AS Priority, + N'Self Loathing Indexes' AS findings_group, + N'Hypothetical Index' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/SelfLoathing' AS URL, + N'Hypothetical Index: ' + db_schema_object_indexid AS details, + i.index_definition, + i.secret_columns, + N'' AS index_usage_summary, + N'' AS index_size_summary + FROM #IndexSanity AS i + WHERE is_hypothetical = 1 + OPTION ( RECOMPILE ); - IF (@TableName IS NOT NULL AND @SchemaName IS NULL) - BEGIN - SET @SchemaName=N'dbo'; - SET @msg='@SchemaName wasn''t specified-- assuming schema=dbo.'; - RAISERROR(@msg,1,1) WITH NOWAIT; - END; - --If a table is specified, grab the object id. - --Short circuit if it doesn't exist. - IF @TableName IS NOT NULL - BEGIN - SET @dsql = N' - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @ObjectID= OBJECT_ID - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.objects AS so - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS sc on - so.schema_id=sc.schema_id - where so.type in (''U'', ''V'') - and so.name=' + QUOTENAME(@TableName,'''')+ N' - and sc.name=' + QUOTENAME(@SchemaName,'''')+ N' - /*Has a row in sys.indexes. This lets us get indexed views.*/ - and exists ( - SELECT si.name - FROM ' + QUOTENAME(@DatabaseName) + '.sys.indexes AS si - WHERE so.object_id=si.object_id) - OPTION (RECOMPILE);'; + RAISERROR(N'check_id 42: Disabled indexes', 0,1) WITH NOWAIT; + --Note: disabled NC indexes will have O rows in #IndexSanitySize! + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 42 AS check_id, + index_sanity_id, + 150 AS Priority, + N'Self Loathing Indexes' AS findings_group, + N'Disabled Index' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/SelfLoathing' AS URL, + N'Disabled Index:' + db_schema_object_indexid AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + 'DISABLED' AS index_size_summary + FROM #IndexSanity AS i + WHERE is_disabled = 1 + OPTION ( RECOMPILE ); - SET @params='@ObjectID INT OUTPUT'; + RAISERROR(N'check_id 49: Heaps with deletes', 0,1) WITH NOWAIT; + WITH heaps_cte + AS ( SELECT [object_id], + [database_id], + [schema_name], + SUM(leaf_delete_count) AS leaf_delete_count + FROM #IndexPartitionSanity + GROUP BY [object_id], + [database_id], + [schema_name] + HAVING SUM(forwarded_fetch_count) < 1000 * @DaysUptime /* Only alert about indexes with no forwarded fetches - we already alerted about those in check_id 43 */ + AND SUM(leaf_delete_count) > 0) + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 49 AS check_id, + i.index_sanity_id, + 200 AS Priority, + N'Self Loathing Indexes' AS findings_group, + N'Heaps with Deletes' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/SelfLoathing' AS URL, + CAST(h.leaf_delete_count AS NVARCHAR(256)) + N' deletes against heap:' + + db_schema_object_indexid AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + sz.index_size_summary + FROM #IndexSanity i + JOIN heaps_cte h ON i.[object_id] = h.[object_id] + AND i.[database_id] = h.[database_id] + AND i.[schema_name] = h.[schema_name] + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + WHERE i.index_id = 0 + AND sz.total_reserved_MB >= CASE WHEN NOT (@GetAllDatabases = 1 OR @Mode = 4) THEN @ThresholdMB ELSE sz.total_reserved_MB END + OPTION ( RECOMPILE ); - IF @dsql IS NULL - RAISERROR('@dsql is null',16,1); + ---------------------------------------- + --Abnormal Psychology : Check_id 60-79 + ---------------------------------------- + RAISERROR(N'check_id 60: XML indexes', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 60 AS check_id, + i.index_sanity_id, + 150 AS Priority, + N'Abnormal Psychology' AS findings_group, + N'XML Index' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, + i.db_schema_object_indexid AS details, + i.index_definition, + i.secret_columns, + N'' AS index_usage_summary, + ISNULL(sz.index_size_summary,'') AS index_size_summary + FROM #IndexSanity AS i + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + WHERE i.is_XML = 1 + OPTION ( RECOMPILE ); - EXEC sp_executesql @dsql, @params, @ObjectID=@ObjectID OUTPUT; - - IF @ObjectID IS NULL - BEGIN - SET @msg=N'Oh, this is awkward. I can''t find the table or indexed view you''re looking for in that database.' + CHAR(10) + - N'Please check your parameters.'; - RAISERROR(@msg,1,1); - RETURN; - END; - END; + RAISERROR(N'check_id 61: Columnstore indexes', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 61 AS check_id, + i.index_sanity_id, + 150 AS Priority, + N'Abnormal Psychology' AS findings_group, + CASE WHEN i.is_NC_columnstore=1 + THEN N'NC Columnstore Index' + ELSE N'Clustered Columnstore Index' + END AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, + i.db_schema_object_indexid AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + ISNULL(sz.index_size_summary,'') AS index_size_summary + FROM #IndexSanity AS i + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + WHERE i.is_NC_columnstore = 1 OR i.is_CX_columnstore=1 + OPTION ( RECOMPILE ); - --set @collation - SELECT @collation=collation_name - FROM sys.databases - WHERE database_id=@DatabaseID; - --insert columns for clustered indexes and heaps - --collect info on identity columns for this one - SET @dsql = N'/* sp_BlitzIndex */ - SET LOCK_TIMEOUT 1000; /* To fix locking bug in sys.identity_columns. See Github issue #2176. */ - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT ' + CAST(@DatabaseID AS NVARCHAR(16)) + ', - s.name, - si.object_id, - si.index_id, - sc.key_ordinal, - sc.is_included_column, - sc.is_descending_key, - sc.partition_ordinal, - c.name as column_name, - st.name as system_type_name, - c.max_length, - c.[precision], - c.[scale], - c.collation_name, - c.is_nullable, - c.is_identity, - c.is_computed, - c.is_replicated, - ' + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N'c.is_sparse' ELSE N'NULL as is_sparse' END + N', - ' + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N'c.is_filestream' ELSE N'NULL as is_filestream' END + N', - CAST(ic.seed_value AS DECIMAL(38,0)), - CAST(ic.increment_value AS DECIMAL(38,0)), - CAST(ic.last_value AS DECIMAL(38,0)), - ic.is_not_for_replication - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.indexes si - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns c ON - si.object_id=c.object_id - LEFT JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.index_columns sc ON - sc.object_id = si.object_id - and sc.index_id=si.index_id - AND sc.column_id=c.column_id - LEFT JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.identity_columns ic ON - c.object_id=ic.object_id and - c.column_id=ic.column_id - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.types st ON - c.system_type_id=st.system_type_id - AND c.user_type_id=st.user_type_id - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects AS so ON si.object_id = so.object_id - AND so.is_ms_shipped = 0 - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s ON s.schema_id = so.schema_id - WHERE si.index_id in (0,1) ' - + CASE WHEN @ObjectID IS NOT NULL - THEN N' AND si.object_id=' + CAST(@ObjectID AS NVARCHAR(30)) - ELSE N'' END - + N'OPTION (RECOMPILE);'; + RAISERROR(N'check_id 62: Spatial indexes', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 62 AS check_id, + i.index_sanity_id, + 150 AS Priority, + N'Abnormal Psychology' AS findings_group, + N'Spatial Index' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, + i.db_schema_object_indexid AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + ISNULL(sz.index_size_summary,'') AS index_size_summary + FROM #IndexSanity AS i + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + WHERE i.is_spatial = 1 + OPTION ( RECOMPILE ); - IF @dsql IS NULL - RAISERROR('@dsql is null',16,1); + RAISERROR(N'check_id 63: Compressed indexes', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 63 AS check_id, + i.index_sanity_id, + 150 AS Priority, + N'Abnormal Psychology' AS findings_group, + N'Compressed Index' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, + i.db_schema_object_indexid + N'. COMPRESSION: ' + sz.data_compression_desc AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + ISNULL(sz.index_size_summary,'') AS index_size_summary + FROM #IndexSanity AS i + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + WHERE sz.data_compression_desc LIKE '%PAGE%' OR sz.data_compression_desc LIKE '%ROW%' + OPTION ( RECOMPILE ); - RAISERROR (N'Inserting data into #IndexColumns for clustered indexes and heaps',0,1) WITH NOWAIT; - IF @Debug = 1 - BEGIN - PRINT SUBSTRING(@dsql, 0, 4000); - PRINT SUBSTRING(@dsql, 4000, 8000); - PRINT SUBSTRING(@dsql, 8000, 12000); - PRINT SUBSTRING(@dsql, 12000, 16000); - PRINT SUBSTRING(@dsql, 16000, 20000); - PRINT SUBSTRING(@dsql, 20000, 24000); - PRINT SUBSTRING(@dsql, 24000, 28000); - PRINT SUBSTRING(@dsql, 28000, 32000); - PRINT SUBSTRING(@dsql, 32000, 36000); - PRINT SUBSTRING(@dsql, 36000, 40000); - END; - BEGIN TRY - INSERT #IndexColumns ( database_id, [schema_name], [object_id], index_id, key_ordinal, is_included_column, is_descending_key, partition_ordinal, - column_name, system_type_name, max_length, precision, scale, collation_name, is_nullable, is_identity, is_computed, - is_replicated, is_sparse, is_filestream, seed_value, increment_value, last_value, is_not_for_replication ) - EXEC sp_executesql @dsql; - END TRY - BEGIN CATCH - RAISERROR (N'Failure inserting data into #IndexColumns for clustered indexes and heaps.', 0,1) WITH NOWAIT; + RAISERROR(N'check_id 64: Partitioned', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 64 AS check_id, + i.index_sanity_id, + 150 AS Priority, + N'Abnormal Psychology' AS findings_group, + N'Partitioned Index' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, + i.db_schema_object_indexid AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + ISNULL(sz.index_size_summary,'') AS index_size_summary + FROM #IndexSanity AS i + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + WHERE i.partition_key_column_name IS NOT NULL + OPTION ( RECOMPILE ); - IF @dsql IS NOT NULL - BEGIN - SET @msg= 'Last @dsql: ' + @dsql; - RAISERROR(@msg, 0, 1) WITH NOWAIT; - END; + RAISERROR(N'check_id 65: Non-Aligned Partitioned', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 65 AS check_id, + i.index_sanity_id, + 150 AS Priority, + N'Abnormal Psychology' AS findings_group, + N'Non-Aligned Index on a Partitioned Table' AS finding, + i.[database_name] AS [Database Name], + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, + i.db_schema_object_indexid AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + ISNULL(sz.index_size_summary,'') AS index_size_summary + FROM #IndexSanity AS i + JOIN #IndexSanity AS iParent ON + i.[object_id]=iParent.[object_id] + AND i.database_id = iParent.database_id + AND i.schema_name = iParent.schema_name + AND iParent.index_id IN (0,1) /* could be a partitioned heap or clustered table */ + AND iParent.partition_key_column_name IS NOT NULL /* parent is partitioned*/ + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + WHERE i.partition_key_column_name IS NULL + OPTION ( RECOMPILE ); - SELECT @msg = @DatabaseName + N' database failed to process. ' + ERROR_MESSAGE(), - @ErrorSeverity = 0, @ErrorState = ERROR_STATE(); - RAISERROR (@msg,@ErrorSeverity, @ErrorState )WITH NOWAIT; + RAISERROR(N'check_id 66: Recently created tables/indexes (1 week)', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 66 AS check_id, + i.index_sanity_id, + 200 AS Priority, + N'Abnormal Psychology' AS findings_group, + N'Recently Created Tables/Indexes (1 week)' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, + i.db_schema_object_indexid + N' was created on ' + + CONVERT(NVARCHAR(16),i.create_date,121) + + N'. Tables/indexes which are dropped/created regularly require special methods for index tuning.' + AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + ISNULL(sz.index_size_summary,'') AS index_size_summary + FROM #IndexSanity AS i + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + WHERE i.create_date >= DATEADD(dd,-7,GETDATE()) + OPTION ( RECOMPILE ); - WHILE @@trancount > 0 - ROLLBACK; + RAISERROR(N'check_id 67: Recently modified tables/indexes (2 days)', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 67 AS check_id, + i.index_sanity_id, + 200 AS Priority, + N'Abnormal Psychology' AS findings_group, + N'Recently Modified Tables/Indexes (2 days)' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, + i.db_schema_object_indexid + N' was modified on ' + + CONVERT(NVARCHAR(16),i.modify_date,121) + + N'. A large amount of recently modified indexes may mean a lot of rebuilds are occurring each night.' + AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + ISNULL(sz.index_size_summary,'') AS index_size_summary + FROM #IndexSanity AS i + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + WHERE i.modify_date > DATEADD(dd,-2,GETDATE()) + AND /*Exclude recently created tables.*/ + i.create_date < DATEADD(dd,-7,GETDATE()) + OPTION ( RECOMPILE ); - RETURN; - END CATCH; + RAISERROR(N'check_id 69: Column collation does not match database collation', 0,1) WITH NOWAIT; + WITH count_columns AS ( + SELECT [object_id], + database_id, + schema_name, + COUNT(*) AS column_count + FROM #IndexColumns ic + WHERE index_id IN (1,0) /*Heap or clustered only*/ + AND collation_name <> @collation + GROUP BY [object_id], + database_id, + schema_name + ) + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 69 AS check_id, + i.index_sanity_id, + 150 AS Priority, + N'Abnormal Psychology' AS findings_group, + N'Column Collation Does Not Match Database Collation' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, + i.db_schema_object_name + + N' has ' + CAST(column_count AS NVARCHAR(20)) + + N' column' + CASE WHEN column_count > 1 THEN 's' ELSE '' END + + N' with a different collation than the db collation of ' + + @collation AS details, + i.index_definition, + secret_columns, + ISNULL(i.index_usage_summary,''), + ISNULL(ip.index_size_summary,'') + FROM #IndexSanity i + JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id + JOIN count_columns AS cc ON i.[object_id]=cc.[object_id] + AND cc.database_id = i.database_id + AND cc.schema_name = i.schema_name + WHERE i.index_id IN (1,0) + ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); + RAISERROR(N'check_id 70: Replicated columns', 0,1) WITH NOWAIT; + WITH count_columns AS ( + SELECT [object_id], + database_id, + schema_name, + COUNT(*) AS column_count, + SUM(CASE is_replicated WHEN 1 THEN 1 ELSE 0 END) AS replicated_column_count + FROM #IndexColumns ic + WHERE index_id IN (1,0) /*Heap or clustered only*/ + GROUP BY object_id, + database_id, + schema_name + ) + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 70 AS check_id, + i.index_sanity_id, + 200 AS Priority, + N'Abnormal Psychology' AS findings_group, + N'Replicated Columns' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, + i.db_schema_object_name + + N' has ' + CAST(replicated_column_count AS NVARCHAR(20)) + + N' out of ' + CAST(column_count AS NVARCHAR(20)) + + N' column' + CASE WHEN column_count > 1 THEN 's' ELSE '' END + + N' in one or more publications.' + AS details, + i.index_definition, + secret_columns, + ISNULL(i.index_usage_summary,''), + ISNULL(ip.index_size_summary,'') + FROM #IndexSanity i + JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id + JOIN count_columns AS cc ON i.[object_id]=cc.[object_id] + AND i.database_id = cc.database_id + AND i.schema_name = cc.schema_name + WHERE i.index_id IN (1,0) + AND replicated_column_count > 0 + ORDER BY i.db_schema_object_name DESC + OPTION ( RECOMPILE ); - --insert columns for nonclustered indexes - --this uses a full join to sys.index_columns - --We don't collect info on identity columns here. They may be in NC indexes, but we just analyze identities in the base table. - SET @dsql = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT ' + CAST(@DatabaseID AS NVARCHAR(16)) + ', - s.name, - si.object_id, - si.index_id, - sc.key_ordinal, - sc.is_included_column, - sc.is_descending_key, - sc.partition_ordinal, - c.name as column_name, - st.name as system_type_name, - c.max_length, - c.[precision], - c.[scale], - c.collation_name, - c.is_nullable, - c.is_identity, - c.is_computed, - c.is_replicated, - ' + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N'c.is_sparse' ELSE N'NULL AS is_sparse' END + N', - ' + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N'c.is_filestream' ELSE N'NULL AS is_filestream' END + N' - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.indexes AS si - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns AS c ON - si.object_id=c.object_id - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.index_columns AS sc ON - sc.object_id = si.object_id - and sc.index_id=si.index_id - AND sc.column_id=c.column_id - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.types AS st ON - c.system_type_id=st.system_type_id - AND c.user_type_id=st.user_type_id - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects AS so ON si.object_id = so.object_id - AND so.is_ms_shipped = 0 - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s ON s.schema_id = so.schema_id - WHERE si.index_id not in (0,1) ' - + CASE WHEN @ObjectID IS NOT NULL - THEN N' AND si.object_id=' + CAST(@ObjectID AS NVARCHAR(30)) - ELSE N'' END - + N'OPTION (RECOMPILE);'; + RAISERROR(N'check_id 71: Cascading updates or cascading deletes.', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary, more_info ) + SELECT 71 AS check_id, + NULL AS index_sanity_id, + 150 AS Priority, + N'Abnormal Psychology' AS findings_group, + N'Cascading Updates or Deletes' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, + N'Foreign Key ' + foreign_key_name + + N' on ' + QUOTENAME(parent_object_name) + N'(' + LTRIM(parent_fk_columns) + N')' + + N' referencing ' + QUOTENAME(referenced_object_name) + N'(' + LTRIM(referenced_fk_columns) + N')' + + N' has settings:' + + CASE [delete_referential_action_desc] WHEN N'NO_ACTION' THEN N'' ELSE N' ON DELETE ' +[delete_referential_action_desc] END + + CASE [update_referential_action_desc] WHEN N'NO_ACTION' THEN N'' ELSE N' ON UPDATE ' + [update_referential_action_desc] END + AS details, + [fk].[database_name] + AS index_definition, + N'N/A' AS secret_columns, + N'N/A' AS index_usage_summary, + N'N/A' AS index_size_summary, + (SELECT TOP 1 more_info FROM #IndexSanity i WHERE i.object_id=fk.parent_object_id AND i.database_id = fk.database_id AND i.schema_name = fk.schema_name) + AS more_info + FROM #ForeignKeys fk + WHERE ([delete_referential_action_desc] <> N'NO_ACTION' + OR [update_referential_action_desc] <> N'NO_ACTION') + OPTION ( RECOMPILE ); - IF @dsql IS NULL - RAISERROR('@dsql is null',16,1); - RAISERROR (N'Inserting data into #IndexColumns for nonclustered indexes',0,1) WITH NOWAIT; - IF @Debug = 1 - BEGIN - PRINT SUBSTRING(@dsql, 0, 4000); - PRINT SUBSTRING(@dsql, 4000, 8000); - PRINT SUBSTRING(@dsql, 8000, 12000); - PRINT SUBSTRING(@dsql, 12000, 16000); - PRINT SUBSTRING(@dsql, 16000, 20000); - PRINT SUBSTRING(@dsql, 20000, 24000); - PRINT SUBSTRING(@dsql, 24000, 28000); - PRINT SUBSTRING(@dsql, 28000, 32000); - PRINT SUBSTRING(@dsql, 32000, 36000); - PRINT SUBSTRING(@dsql, 36000, 40000); - END; - INSERT #IndexColumns ( database_id, [schema_name], [object_id], index_id, key_ordinal, is_included_column, is_descending_key, partition_ordinal, - column_name, system_type_name, max_length, precision, scale, collation_name, is_nullable, is_identity, is_computed, - is_replicated, is_sparse, is_filestream ) - EXEC sp_executesql @dsql; - - SET @dsql = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT ' + CAST(@DatabaseID AS NVARCHAR(10)) + N' AS database_id, - so.object_id, - si.index_id, - si.type, - @i_DatabaseName AS database_name, - COALESCE(sc.NAME, ''Unknown'') AS [schema_name], - COALESCE(so.name, ''Unknown'') AS [object_name], - COALESCE(si.name, ''Unknown'') AS [index_name], - CASE WHEN so.[type] = CAST(''V'' AS CHAR(2)) THEN 1 ELSE 0 END, - si.is_unique, - si.is_primary_key, - CASE when si.type = 3 THEN 1 ELSE 0 END AS is_XML, - CASE when si.type = 4 THEN 1 ELSE 0 END AS is_spatial, - CASE when si.type = 6 THEN 1 ELSE 0 END AS is_NC_columnstore, - CASE when si.type = 5 then 1 else 0 end as is_CX_columnstore, - CASE when si.data_space_id = 0 then 1 else 0 end as is_in_memory_oltp, - si.is_disabled, - si.is_hypothetical, - si.is_padded, - si.fill_factor,' - + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N' - CASE WHEN si.filter_definition IS NOT NULL THEN si.filter_definition - ELSE N'''' - END AS filter_definition' ELSE N''''' AS filter_definition' END + N' - , ISNULL(us.user_seeks, 0), - ISNULL(us.user_scans, 0), - ISNULL(us.user_lookups, 0), - ISNULL(us.user_updates, 0), - us.last_user_seek, - us.last_user_scan, - us.last_user_lookup, - us.last_user_update, - so.create_date, - so.modify_date - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.indexes AS si WITH (NOLOCK) - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects AS so WITH (NOLOCK) ON si.object_id = so.object_id - AND so.is_ms_shipped = 0 /*Exclude objects shipped by Microsoft*/ - AND so.type <> ''TF'' /*Exclude table valued functions*/ - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas sc ON so.schema_id = sc.schema_id - LEFT JOIN sys.dm_db_index_usage_stats AS us WITH (NOLOCK) ON si.[object_id] = us.[object_id] - AND si.index_id = us.index_id - AND us.database_id = ' + CAST(@DatabaseID AS NVARCHAR(10)) + N' - WHERE si.[type] IN ( 0, 1, 2, 3, 4, 5, 6 ) - /* Heaps, clustered, nonclustered, XML, spatial, Cluster Columnstore, NC Columnstore */ ' + - CASE WHEN @TableName IS NOT NULL THEN N' and so.name=' + QUOTENAME(@TableName,N'''') + N' ' ELSE N'' END + - CASE WHEN ( @IncludeInactiveIndexes = 0 - AND @Mode IN (0, 4) - AND @TableName IS NULL ) - THEN N'AND ( us.user_seeks + us.user_scans + us.user_lookups + us.user_updates ) > 0' - ELSE N'' - END - + N'OPTION ( RECOMPILE ); - '; - IF @dsql IS NULL - RAISERROR('@dsql is null',16,1); + RAISERROR(N'check_id 73: In-Memory OLTP', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 73 AS check_id, + i.index_sanity_id, + 150 AS Priority, + N'Abnormal Psychology' AS findings_group, + N'In-Memory OLTP' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, + i.db_schema_object_indexid AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + ISNULL(sz.index_size_summary,'') AS index_size_summary + FROM #IndexSanity AS i + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + WHERE i.is_in_memory_oltp = 1 + OPTION ( RECOMPILE ); - RAISERROR (N'Inserting data into #IndexSanity',0,1) WITH NOWAIT; - IF @Debug = 1 - BEGIN - PRINT SUBSTRING(@dsql, 0, 4000); - PRINT SUBSTRING(@dsql, 4000, 8000); - PRINT SUBSTRING(@dsql, 8000, 12000); - PRINT SUBSTRING(@dsql, 12000, 16000); - PRINT SUBSTRING(@dsql, 16000, 20000); - PRINT SUBSTRING(@dsql, 20000, 24000); - PRINT SUBSTRING(@dsql, 24000, 28000); - PRINT SUBSTRING(@dsql, 28000, 32000); - PRINT SUBSTRING(@dsql, 32000, 36000); - PRINT SUBSTRING(@dsql, 36000, 40000); - END; - INSERT #IndexSanity ( [database_id], [object_id], [index_id], [index_type], [database_name], [schema_name], [object_name], - index_name, is_indexed_view, is_unique, is_primary_key, is_XML, is_spatial, is_NC_columnstore, is_CX_columnstore, is_in_memory_oltp, - is_disabled, is_hypothetical, is_padded, fill_factor, filter_definition, user_seeks, user_scans, - user_lookups, user_updates, last_user_seek, last_user_scan, last_user_lookup, last_user_update, - create_date, modify_date ) - EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; + RAISERROR(N'check_id 74: Identity column with unusual seed', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 74 AS check_id, + i.index_sanity_id, + 200 AS Priority, + N'Abnormal Psychology' AS findings_group, + N'Identity Column Using a Negative Seed or Increment Other Than 1' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, + i.db_schema_object_name + N'.' + QUOTENAME(ic.column_name) + + N' is an identity with type ' + ic.system_type_name + + N', last value of ' + + ISNULL((CONVERT(NVARCHAR(256),CAST(ic.last_value AS DECIMAL(38,0)), 1)),N'NULL') + + N', seed of ' + + ISNULL((CONVERT(NVARCHAR(256),CAST(ic.seed_value AS DECIMAL(38,0)), 1)),N'NULL') + + N', increment of ' + CAST(ic.increment_value AS NVARCHAR(256)) + + N', and range of ' + + CASE ic.system_type_name WHEN 'int' THEN N'+/- 2,147,483,647' + WHEN 'smallint' THEN N'+/- 32,768' + WHEN 'tinyint' THEN N'0 to 255' + ELSE 'unknown' + END + AS details, + i.index_definition, + secret_columns, + ISNULL(i.index_usage_summary,''), + ISNULL(ip.index_size_summary,'') + FROM #IndexSanity i + JOIN #IndexColumns ic ON + i.object_id=ic.object_id + AND i.database_id = ic.database_id + AND i.schema_name = ic.schema_name + AND i.index_id IN (0,1) /* heaps and cx only */ + AND ic.is_identity=1 + AND ic.system_type_name IN ('tinyint', 'smallint', 'int') + JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id + WHERE i.index_id IN (1,0) + AND (ic.seed_value < 0 OR ic.increment_value <> 1) + ORDER BY finding, details DESC + OPTION ( RECOMPILE ); + ---------------------------------------- + --Workaholics: Check_id 80-89 + ---------------------------------------- - RAISERROR (N'Checking partition count',0,1) WITH NOWAIT; - IF @BringThePain = 0 AND @SkipPartitions = 0 AND @TableName IS NULL - BEGIN - /* Count the total number of partitions */ - SET @dsql = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @RowcountOUT = SUM(1) FROM ' + QUOTENAME(@DatabaseName) + '.sys.partitions WHERE partition_number > 1 OPTION ( RECOMPILE );'; - EXEC sp_executesql @dsql, N'@RowcountOUT BIGINT OUTPUT', @RowcountOUT = @Rowcount OUTPUT; - IF @Rowcount > 100 - BEGIN - RAISERROR (N'Setting @SkipPartitions = 1 because > 100 partitions were found. To check them, you must set @BringThePain = 1.',0,1) WITH NOWAIT; - SET @SkipPartitions = 1; - INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, URL, details, index_definition, - index_usage_summary, index_size_summary ) - VALUES ( 1, 0 , - 'Some Checks Were Skipped', - '@SkipPartitions Forced to 1', - 'http://FirstResponderKit.org', CAST(@Rowcount AS NVARCHAR(50)) + ' partitions found. To analyze them, use @BringThePain = 1.', 'We try to keep things quick - and warning, running @BringThePain = 1 can take tens of minutes.', '', '' - ); - END; - END; + RAISERROR(N'check_id 80: Most scanned indexes (index_usage_stats)', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + --Workaholics according to index_usage_stats + --This isn't perfect: it mentions the number of scans present in a plan + --A "scan" isn't necessarily a full scan, but hey, we gotta do the best with what we've got. + --in the case of things like indexed views, the operator might be in the plan but never executed + SELECT TOP 5 + 80 AS check_id, + i.index_sanity_id AS index_sanity_id, + 200 AS Priority, + N'Workaholics' AS findings_group, + N'Scan-a-lots (index-usage-stats)' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/Workaholics' AS URL, + REPLACE(CONVERT( NVARCHAR(50),CAST(i.user_scans AS MONEY),1),'.00','') + + N' scans against ' + i.db_schema_object_indexid + + N'. Latest scan: ' + ISNULL(CAST(i.last_user_scan AS NVARCHAR(128)),'?') + N'. ' + + N'ScanFactor=' + CAST(((i.user_scans * iss.total_reserved_MB)/1000000.) AS NVARCHAR(256)) AS details, + ISNULL(i.key_column_names_with_sort_order,'N/A') AS index_definition, + ISNULL(i.secret_columns,'') AS secret_columns, + i.index_usage_summary AS index_usage_summary, + iss.index_size_summary AS index_size_summary + FROM #IndexSanity i + JOIN #IndexSanitySize iss ON i.index_sanity_id=iss.index_sanity_id + WHERE ISNULL(i.user_scans,0) > 0 + ORDER BY i.user_scans * iss.total_reserved_MB DESC + OPTION ( RECOMPILE ); + RAISERROR(N'check_id 81: Top recent accesses (op stats)', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + --Workaholics according to index_operational_stats + --This isn't perfect either: range_scan_count contains full scans, partial scans, even seeks in nested loop ops + --But this can help bubble up some most-accessed tables + SELECT TOP 5 + 81 AS check_id, + i.index_sanity_id AS index_sanity_id, + 200 AS Priority, + N'Workaholics' AS findings_group, + N'Top Recent Accesses (index-op-stats)' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/Workaholics' AS URL, + ISNULL(REPLACE( + CONVERT(NVARCHAR(50),CAST((iss.total_range_scan_count + iss.total_singleton_lookup_count) AS MONEY),1), + N'.00',N'') + + N' uses of ' + i.db_schema_object_indexid + N'. ' + + REPLACE(CONVERT(NVARCHAR(50), CAST(iss.total_range_scan_count AS MONEY),1),N'.00',N'') + N' scans or seeks. ' + + REPLACE(CONVERT(NVARCHAR(50), CAST(iss.total_singleton_lookup_count AS MONEY), 1),N'.00',N'') + N' singleton lookups. ' + + N'OpStatsFactor=' + CAST(((((iss.total_range_scan_count + iss.total_singleton_lookup_count) * iss.total_reserved_MB))/1000000.) AS VARCHAR(256)),'') AS details, + ISNULL(i.key_column_names_with_sort_order,'N/A') AS index_definition, + ISNULL(i.secret_columns,'') AS secret_columns, + i.index_usage_summary AS index_usage_summary, + iss.index_size_summary AS index_size_summary + FROM #IndexSanity i + JOIN #IndexSanitySize iss ON i.index_sanity_id=iss.index_sanity_id + WHERE (ISNULL(iss.total_range_scan_count,0) > 0 OR ISNULL(iss.total_singleton_lookup_count,0) > 0) + ORDER BY ((iss.total_range_scan_count + iss.total_singleton_lookup_count) * iss.total_reserved_MB) DESC + OPTION ( RECOMPILE ); - IF (@SkipPartitions = 0) - BEGIN - IF (SELECT LEFT(@SQLServerProductVersion, - CHARINDEX('.',@SQLServerProductVersion,0)-1 )) <= 2147483647 --Make change here - BEGIN - - RAISERROR (N'Preferring non-2012 syntax with LEFT JOIN to sys.dm_db_index_operational_stats',0,1) WITH NOWAIT; - --NOTE: If you want to use the newer syntax for 2012+, you'll have to change 2147483647 to 11 on line ~819 - --This change was made because on a table with lots of paritions, the OUTER APPLY was crazy slow. - SET @dsql = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT ' + CAST(@DatabaseID AS NVARCHAR(10)) + N' AS database_id, - ps.object_id, - s.name, - ps.index_id, - ps.partition_number, - ps.row_count, - ps.reserved_page_count * 8. / 1024. AS reserved_MB, - ps.lob_reserved_page_count * 8. / 1024. AS reserved_LOB_MB, - ps.row_overflow_reserved_page_count * 8. / 1024. AS reserved_row_overflow_MB, - le.lock_escalation_desc, - ' + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N'par.data_compression_desc ' ELSE N'null as data_compression_desc ' END + N', - SUM(os.leaf_insert_count), - SUM(os.leaf_delete_count), - SUM(os.leaf_update_count), - SUM(os.range_scan_count), - SUM(os.singleton_lookup_count), - SUM(os.forwarded_fetch_count), - SUM(os.lob_fetch_in_pages), - SUM(os.lob_fetch_in_bytes), - SUM(os.row_overflow_fetch_in_pages), - SUM(os.row_overflow_fetch_in_bytes), - SUM(os.row_lock_count), - SUM(os.row_lock_wait_count), - SUM(os.row_lock_wait_in_ms), - SUM(os.page_lock_count), - SUM(os.page_lock_wait_count), - SUM(os.page_lock_wait_in_ms), - SUM(os.index_lock_promotion_attempt_count), - SUM(os.index_lock_promotion_count), - SUM(os.page_latch_wait_count), - SUM(os.page_latch_wait_in_ms), - SUM(os.page_io_latch_wait_count), - SUM(os.page_io_latch_wait_in_ms), '; - /* Get columnstore dictionary size - more info: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2585 */ - IF EXISTS (SELECT * FROM sys.all_objects WHERE name = 'column_store_dictionaries') - SET @dsql = @dsql + N' COALESCE((SELECT SUM (on_disk_size / 1024.0 / 1024) FROM ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_dictionaries dict WHERE dict.partition_id = ps.partition_id),0) AS reserved_dictionary_MB '; - ELSE - SET @dsql = @dsql + N' 0 AS reserved_dictionary_MB '; + RAISERROR(N'check_id 93: Statistics with filters', 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 93 AS check_id, + 200 AS Priority, + 'Functioning Statistaholics' AS findings_group, + 'Filter Fixation', + s.database_name, + 'https://www.brentozar.com/go/stats' AS URL, + 'The statistic ' + QUOTENAME(s.statistics_name) + ' is filtered on [' + s.filter_definition + ']. It could be part of a filtered index, or just a filtered statistic. This is purely informational.' 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_filter = 1 + OPTION ( RECOMPILE ); - SET @dsql = @dsql + N' - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_partition_stats AS ps - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partitions AS par on ps.partition_id=par.partition_id - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects AS so ON ps.object_id = so.object_id - AND so.is_ms_shipped = 0 /*Exclude objects shipped by Microsoft*/ - AND so.type <> ''TF'' /*Exclude table valued functions*/ - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s ON s.schema_id = so.schema_id - LEFT JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_index_operational_stats(' - + CAST(@DatabaseID AS NVARCHAR(10)) + N', NULL, NULL,NULL) AS os ON - ps.object_id=os.object_id and ps.index_id=os.index_id and ps.partition_number=os.partition_number - OUTER APPLY (SELECT st.lock_escalation_desc - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.tables st - WHERE st.object_id = ps.object_id - AND ps.index_id < 2 ) le - WHERE 1=1 - ' + CASE WHEN @ObjectID IS NOT NULL THEN N'AND so.object_id=' + CAST(@ObjectID AS NVARCHAR(30)) + N' ' ELSE N' ' END + N' - ' + CASE WHEN @Filter = 2 THEN N'AND ps.reserved_page_count * 8./1024. > ' + CAST(@FilterMB AS NVARCHAR(5)) + N' ' ELSE N' ' END + N' - GROUP BY ps.object_id, - s.name, - ps.index_id, - ps.partition_number, - ps.partition_id, - ps.row_count, - ps.reserved_page_count, - ps.lob_reserved_page_count, - ps.row_overflow_reserved_page_count, - le.lock_escalation_desc, - ' + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N'par.data_compression_desc ' ELSE N'null as data_compression_desc ' END + N' - ORDER BY ps.object_id, ps.index_id, ps.partition_number - OPTION ( RECOMPILE ); - '; - END; - ELSE - BEGIN - RAISERROR (N'Using 2012 syntax to query sys.dm_db_index_operational_stats',0,1) WITH NOWAIT; - --This is the syntax that will be used if you change 2147483647 to 11 on line ~819. - --If you have a lot of paritions and this suddenly starts running for a long time, change it back. - SET @dsql = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT ' + CAST(@DatabaseID AS NVARCHAR(10)) + N' AS database_id, - ps.object_id, - s.name, - ps.index_id, - ps.partition_number, - ps.row_count, - ps.reserved_page_count * 8. / 1024. AS reserved_MB, - ps.lob_reserved_page_count * 8. / 1024. AS reserved_LOB_MB, - ps.row_overflow_reserved_page_count * 8. / 1024. AS reserved_row_overflow_MB, - le.lock_escalation_desc, - ' + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N'par.data_compression_desc ' ELSE N'null as data_compression_desc' END + N', - SUM(os.leaf_insert_count), - SUM(os.leaf_delete_count), - SUM(os.leaf_update_count), - SUM(os.range_scan_count), - SUM(os.singleton_lookup_count), - SUM(os.forwarded_fetch_count), - SUM(os.lob_fetch_in_pages), - SUM(os.lob_fetch_in_bytes), - SUM(os.row_overflow_fetch_in_pages), - SUM(os.row_overflow_fetch_in_bytes), - SUM(os.row_lock_count), - SUM(os.row_lock_wait_count), - SUM(os.row_lock_wait_in_ms), - SUM(os.page_lock_count), - SUM(os.page_lock_wait_count), - SUM(os.page_lock_wait_in_ms), - SUM(os.index_lock_promotion_attempt_count), - SUM(os.index_lock_promotion_count), - SUM(os.page_latch_wait_count), - SUM(os.page_latch_wait_in_ms), - SUM(os.page_io_latch_wait_count), - SUM(os.page_io_latch_wait_in_ms)'; + RAISERROR(N'check_id 100: Computed Columns that are not Persisted.', 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 100 AS check_id, + 200 AS Priority, + 'Cold Calculators' AS findings_group, + 'Definition Defeatists' AS finding, + cc.database_name, + '' AS URL, + 'The computed column ' + QUOTENAME(cc.column_name) + ' on ' + QUOTENAME(cc.schema_name) + '.' + QUOTENAME(cc.table_name) + ' is not persisted, which means it will be calculated when a query runs.' + + 'You can change this with the following command, if the definition is deterministic: ALTER TABLE ' + QUOTENAME(cc.schema_name) + '.' + QUOTENAME(cc.table_name) + ' ALTER COLUMN ' + cc.column_name + + ' ADD PERSISTED' AS details, + cc.column_definition, + 'N/A' AS secret_columns, + 'N/A' AS index_usage_summary, + 'N/A' AS index_size_summary + FROM #ComputedColumns AS cc + WHERE cc.is_persisted = 0 + OPTION ( RECOMPILE ); - /* Get columnstore dictionary size - more info: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2585 */ - IF EXISTS (SELECT * FROM sys.all_objects WHERE name = 'column_store_dictionaries') - SET @dsql = @dsql + N' COALESCE((SELECT SUM (on_disk_size / 1024.0 / 1024) FROM ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_dictionaries dict WHERE dict.partition_id = ps.partition_id),0) AS reserved_dictionary_MB '; - ELSE - SET @dsql = @dsql + N' 0 AS reserved_dictionary_MB '; + RAISERROR(N'check_id 110: Temporal Tables.', 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 110 AS check_id, + 200 AS Priority, + 'Abnormal Psychology' AS findings_group, + 'Temporal Tables', + t.database_name, + '' AS URL, + 'The table ' + QUOTENAME(t.schema_name) + '.' + QUOTENAME(t.table_name) + ' is a temporal table, with rows versioned in ' + + QUOTENAME(t.history_schema_name) + '.' + QUOTENAME(t.history_table_name) + ' on History columns ' + QUOTENAME(t.start_column_name) + ' and ' + QUOTENAME(t.end_column_name) + '.' + AS details, + '' AS index_definition, + 'N/A' AS secret_columns, + 'N/A' AS index_usage_summary, + 'N/A' AS index_size_summary + FROM #TemporalTables AS t + OPTION ( RECOMPILE ); - SET @dsql = @dsql + N' - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_partition_stats AS ps - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partitions AS par on ps.partition_id=par.partition_id - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects AS so ON ps.object_id = so.object_id - AND so.is_ms_shipped = 0 /*Exclude objects shipped by Microsoft*/ - AND so.type <> ''TF'' /*Exclude table valued functions*/ - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s ON s.schema_id = so.schema_id - OUTER APPLY ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_index_operational_stats(' - + CAST(@DatabaseID AS NVARCHAR(10)) + N', ps.object_id, ps.index_id,ps.partition_number) AS os - OUTER APPLY (SELECT st.lock_escalation_desc - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.tables st - WHERE st.object_id = ps.object_id - AND ps.index_id < 2 ) le - WHERE 1=1 - ' + CASE WHEN @ObjectID IS NOT NULL THEN N'AND so.object_id=' + CAST(@ObjectID AS NVARCHAR(30)) + N' ' ELSE N' ' END + N' - ' + CASE WHEN @Filter = 2 THEN N'AND ps.reserved_page_count * 8./1024. > ' + CAST(@FilterMB AS NVARCHAR(5)) + N' ' ELSE N' ' END + ' - GROUP BY ps.object_id, - s.name, - ps.index_id, - ps.partition_number, - ps.partition_id, - ps.row_count, - ps.reserved_page_count, - ps.lob_reserved_page_count, - ps.row_overflow_reserved_page_count, - le.lock_escalation_desc, - ' + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N'par.data_compression_desc ' ELSE N'null as data_compression_desc ' END + N' - ORDER BY ps.object_id, ps.index_id, ps.partition_number - OPTION ( RECOMPILE ); - '; - END; - IF @dsql IS NULL - RAISERROR('@dsql is null',16,1); - RAISERROR (N'Inserting data into #IndexPartitionSanity',0,1) WITH NOWAIT; - IF @Debug = 1 - BEGIN - PRINT SUBSTRING(@dsql, 0, 4000); - PRINT SUBSTRING(@dsql, 4000, 8000); - PRINT SUBSTRING(@dsql, 8000, 12000); - PRINT SUBSTRING(@dsql, 12000, 16000); - PRINT SUBSTRING(@dsql, 16000, 20000); - PRINT SUBSTRING(@dsql, 20000, 24000); - PRINT SUBSTRING(@dsql, 24000, 28000); - PRINT SUBSTRING(@dsql, 28000, 32000); - PRINT SUBSTRING(@dsql, 32000, 36000); - PRINT SUBSTRING(@dsql, 36000, 40000); - END; - INSERT #IndexPartitionSanity ( [database_id], - [object_id], - [schema_name], - index_id, - partition_number, - row_count, - reserved_MB, - reserved_LOB_MB, - reserved_row_overflow_MB, - lock_escalation_desc, - data_compression_desc, - leaf_insert_count, - leaf_delete_count, - leaf_update_count, - range_scan_count, - singleton_lookup_count, - forwarded_fetch_count, - lob_fetch_in_pages, - lob_fetch_in_bytes, - row_overflow_fetch_in_pages, - row_overflow_fetch_in_bytes, - row_lock_count, - row_lock_wait_count, - row_lock_wait_in_ms, - page_lock_count, - page_lock_wait_count, - page_lock_wait_in_ms, - index_lock_promotion_attempt_count, - index_lock_promotion_count, - page_latch_wait_count, - page_latch_wait_in_ms, - page_io_latch_wait_count, - page_io_latch_wait_in_ms, - reserved_dictionary_MB) - EXEC sp_executesql @dsql; - - END; --End Check For @SkipPartitions = 0 + END /* IF @Mode = 4 */ - RAISERROR (N'Inserting data into #MissingIndexes',0,1) WITH NOWAIT; - SET @dsql=N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' - SET @dsql = @dsql + 'WITH ColumnNamesWithDataTypes AS(SELECT id.index_handle,id.object_id,cn.IndexColumnType,STUFF((SELECT '', '' + cn_inner.ColumnName + '' '' + - N'' {'' + CASE WHEN ty.name IN ( ''varchar'', ''char'' ) THEN ty.name + ''('' + CASE WHEN co.max_length = -1 THEN ''max'' ELSE CAST(co.max_length AS VARCHAR(25)) END + '')'' - WHEN ty.name IN ( ''nvarchar'', ''nchar'' ) THEN ty.name + ''('' + CASE WHEN co.max_length = -1 THEN ''max'' ELSE CAST(co.max_length / 2 AS VARCHAR(25)) END + '')'' - WHEN ty.name IN ( ''decimal'', ''numeric'' ) THEN ty.name + ''('' + CAST(co.precision AS VARCHAR(25)) + '', '' + CAST(co.scale AS VARCHAR(25)) + '')'' - WHEN ty.name IN ( ''datetime2'' ) THEN ty.name + ''('' + CAST(co.scale AS VARCHAR(25)) + '')'' - ELSE ty.name END + ''}'' - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_details AS id_inner - CROSS APPLY( - SELECT LTRIM(RTRIM(v.value(''(./text())[1]'', ''varchar(max)''))) AS ColumnName, ''Equality'' AS IndexColumnType - FROM (VALUES (CONVERT(XML, N'''' + REPLACE((SELECT CAST(id_inner.equality_columns AS nvarchar(max)) FOR XML PATH('''')), N'','', N'''') + N''''))) x(n) - CROSS APPLY n.nodes(''x'') node(v) - UNION ALL - SELECT LTRIM(RTRIM(v.value(N''(./text())[1]'', ''varchar(max)''))) AS ColumnName, ''Inequality'' AS IndexColumnType - FROM (VALUES (CONVERT(XML, N'''' + REPLACE((SELECT CAST(id_inner.inequality_columns AS nvarchar(max)) FOR XML PATH('''')), N'','', N'''') + N''''))) x(n) - CROSS APPLY n.nodes(''x'') node(v) - UNION ALL - SELECT LTRIM(RTRIM(v.value(''(./text())[1]'', ''varchar(max)''))) AS ColumnName, ''Included'' AS IndexColumnType - FROM (VALUES (CONVERT(XML, N'''' + REPLACE((SELECT CAST(id_inner.included_columns AS nvarchar(max)) FOR XML PATH('''')), N'','', N'''') + N''''))) x(n) - CROSS APPLY n.nodes(''x'') node(v) - )AS cn_inner' - + /*split the string otherwise dsql cuts some of it out*/ - ' JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns AS co ON co.object_id = id_inner.object_id AND ''['' + co.name + '']'' = cn_inner.ColumnName - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.types AS ty ON ty.user_type_id = co.user_type_id - WHERE id_inner.index_handle = id.index_handle - AND id_inner.object_id = id.object_id - AND cn_inner.IndexColumnType = cn.IndexColumnType - FOR XML PATH('''') - ),1,1,'''') AS ReplaceColumnNames - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_details AS id - CROSS APPLY( - SELECT LTRIM(RTRIM(v.value(''(./text())[1]'', ''varchar(max)''))) AS ColumnName, ''Equality'' AS IndexColumnType - FROM (VALUES (CONVERT(XML, N'''' + REPLACE((SELECT CAST(id.equality_columns AS nvarchar(max)) FOR XML PATH('''')), N'','', N'''') + N''''))) x(n) - CROSS APPLY n.nodes(''x'') node(v) - UNION ALL - SELECT LTRIM(RTRIM(v.value(''(./text())[1]'', ''varchar(max)''))) AS ColumnName, ''Inequality'' AS IndexColumnType - FROM (VALUES (CONVERT(XML, N'''' + REPLACE((SELECT CAST(id.inequality_columns AS nvarchar(max)) FOR XML PATH('''')), N'','', N'''') + N''''))) x(n) - CROSS APPLY n.nodes(''x'') node(v) - UNION ALL - SELECT LTRIM(RTRIM(v.value(''(./text())[1]'', ''varchar(max)''))) AS ColumnName, ''Included'' AS IndexColumnType - FROM (VALUES (CONVERT(XML, N'''' + REPLACE((SELECT CAST(id.included_columns AS nvarchar(max)) FOR XML PATH('''')), N'','', N'''') + N''''))) x(n) - CROSS APPLY n.nodes(''x'') node(v) - )AS cn - GROUP BY id.index_handle,id.object_id,cn.IndexColumnType - ) - SELECT id.database_id, id.object_id, @i_DatabaseName, sc.[name], so.[name], id.statement , gs.avg_total_user_cost, - gs.avg_user_impact, gs.user_seeks, gs.user_scans, gs.unique_compiles, id.equality_columns, id.inequality_columns, id.included_columns, - ( - SELECT ColumnNamesWithDataTypes.ReplaceColumnNames - FROM ColumnNamesWithDataTypes WHERE ColumnNamesWithDataTypes.index_handle = id.index_handle - AND ColumnNamesWithDataTypes.object_id = id.object_id - AND ColumnNamesWithDataTypes.IndexColumnType = ''Equality'' - ) AS equality_columns_with_data_type - ,( - SELECT ColumnNamesWithDataTypes.ReplaceColumnNames - FROM ColumnNamesWithDataTypes WHERE ColumnNamesWithDataTypes.index_handle = id.index_handle - AND ColumnNamesWithDataTypes.object_id = id.object_id - AND ColumnNamesWithDataTypes.IndexColumnType = ''Inequality'' - ) AS inequality_columns_with_data_type - ,( - SELECT ColumnNamesWithDataTypes.ReplaceColumnNames - FROM ColumnNamesWithDataTypes WHERE ColumnNamesWithDataTypes.index_handle = id.index_handle - AND ColumnNamesWithDataTypes.object_id = id.object_id - AND ColumnNamesWithDataTypes.IndexColumnType = ''Included'' - ) AS included_columns_with_data_type ' - IF NOT EXISTS (SELECT * FROM sys.all_objects WHERE name = 'dm_db_missing_index_group_stats_query') - SET @dsql = @dsql + N' , NULL AS sample_query_plan ' - ELSE - BEGIN - SET @dsql = @dsql + N' , sample_query_plan = (SELECT TOP 1 p.query_plan - FROM sys.dm_db_missing_index_group_stats gs - CROSS APPLY (SELECT TOP 1 s.plan_handle - FROM sys.dm_db_missing_index_group_stats_query q - INNER JOIN sys.dm_exec_query_stats s ON q.query_plan_hash = s.query_plan_hash - WHERE gs.group_handle = q.group_handle - ORDER BY (q.user_seeks + q.user_scans) DESC, s.total_logical_reads DESC) q2 - CROSS APPLY sys.dm_exec_query_plan(q2.plan_handle) p - WHERE gs.group_handle = gs.group_handle) ' - END - SET @dsql = @dsql + N'FROM ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_groups ig - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_details id ON ig.index_handle = id.index_handle - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_group_stats gs ON ig.index_group_handle = gs.group_handle - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects so on - id.object_id=so.object_id - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas sc on - so.schema_id=sc.schema_id - WHERE id.database_id = ' + CAST(@DatabaseID AS NVARCHAR(30)) + ' - ' + CASE WHEN @ObjectID IS NULL THEN N'' - ELSE N'and id.object_id=' + CAST(@ObjectID AS NVARCHAR(30)) - END + - N'OPTION (RECOMPILE);'; - IF @dsql IS NULL - RAISERROR('@dsql is null',16,1); - IF @Debug = 1 - BEGIN - PRINT SUBSTRING(@dsql, 0, 4000); - PRINT SUBSTRING(@dsql, 4000, 8000); - PRINT SUBSTRING(@dsql, 8000, 12000); - PRINT SUBSTRING(@dsql, 12000, 16000); - PRINT SUBSTRING(@dsql, 16000, 20000); - PRINT SUBSTRING(@dsql, 20000, 24000); - PRINT SUBSTRING(@dsql, 24000, 28000); - PRINT SUBSTRING(@dsql, 28000, 32000); - PRINT SUBSTRING(@dsql, 32000, 36000); - PRINT SUBSTRING(@dsql, 36000, 40000); - END; - INSERT #MissingIndexes ( [database_id], [object_id], [database_name], [schema_name], [table_name], [statement], avg_total_user_cost, - avg_user_impact, user_seeks, user_scans, unique_compiles, equality_columns, - inequality_columns, included_columns, equality_columns_with_data_type, inequality_columns_with_data_type, - included_columns_with_data_type, sample_query_plan) - EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; - SET @dsql = N' - SELECT DB_ID(N' + QUOTENAME(@DatabaseName,'''') + N') AS [database_id], - @i_DatabaseName AS database_name, - s.name, - fk_object.name AS foreign_key_name, - parent_object.[object_id] AS parent_object_id, - parent_object.name AS parent_object_name, - referenced_object.[object_id] AS referenced_object_id, - referenced_object.name AS referenced_object_name, - fk.is_disabled, - fk.is_not_trusted, - fk.is_not_for_replication, - parent.fk_columns, - referenced.fk_columns, - [update_referential_action_desc], - [delete_referential_action_desc] - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.foreign_keys fk - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects fk_object ON fk.object_id=fk_object.object_id - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects parent_object ON fk.parent_object_id=parent_object.object_id - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects referenced_object ON fk.referenced_object_id=referenced_object.object_id - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s ON fk.schema_id=s.schema_id - CROSS APPLY ( SELECT STUFF( (SELECT N'', '' + c_parent.name AS fk_columns - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.foreign_key_columns fkc - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns c_parent ON fkc.parent_object_id=c_parent.[object_id] - AND fkc.parent_column_id=c_parent.column_id - WHERE fk.parent_object_id=fkc.parent_object_id - AND fk.[object_id]=fkc.constraint_object_id - ORDER BY fkc.constraint_column_id - FOR XML PATH('''') , - TYPE).value(''.'', ''nvarchar(max)''), 1, 1, '''')/*This is how we remove the first comma*/ ) parent ( fk_columns ) - CROSS APPLY ( SELECT STUFF( (SELECT N'', '' + c_referenced.name AS fk_columns - FROM ' + QUOTENAME(@DatabaseName) + N'.sys. foreign_key_columns fkc - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns c_referenced ON fkc.referenced_object_id=c_referenced.[object_id] - AND fkc.referenced_column_id=c_referenced.column_id - WHERE fk.referenced_object_id=fkc.referenced_object_id - and fk.[object_id]=fkc.constraint_object_id - ORDER BY fkc.constraint_column_id /*order by col name, we don''t have anything better*/ - FOR XML PATH('''') , - TYPE).value(''.'', ''nvarchar(max)''), 1, 1, '''') ) referenced ( fk_columns ) - ' + CASE WHEN @ObjectID IS NOT NULL THEN - 'WHERE fk.parent_object_id=' + CAST(@ObjectID AS NVARCHAR(30)) + N' OR fk.referenced_object_id=' + CAST(@ObjectID AS NVARCHAR(30)) + N' ' - ELSE N' ' END + ' - ORDER BY parent_object_name, foreign_key_name - OPTION (RECOMPILE);'; - IF @dsql IS NULL - RAISERROR('@dsql is null',16,1); - RAISERROR (N'Inserting data into #ForeignKeys',0,1) WITH NOWAIT; - IF @Debug = 1 - BEGIN - PRINT SUBSTRING(@dsql, 0, 4000); - PRINT SUBSTRING(@dsql, 4000, 8000); - PRINT SUBSTRING(@dsql, 8000, 12000); - PRINT SUBSTRING(@dsql, 12000, 16000); - PRINT SUBSTRING(@dsql, 16000, 20000); - PRINT SUBSTRING(@dsql, 20000, 24000); - PRINT SUBSTRING(@dsql, 24000, 28000); - PRINT SUBSTRING(@dsql, 28000, 32000); - PRINT SUBSTRING(@dsql, 32000, 36000); - PRINT SUBSTRING(@dsql, 36000, 40000); - END; - INSERT #ForeignKeys ( [database_id], [database_name], [schema_name], foreign_key_name, parent_object_id,parent_object_name, referenced_object_id, referenced_object_name, - is_disabled, is_not_trusted, is_not_for_replication, parent_fk_columns, referenced_fk_columns, - [update_referential_action_desc], [delete_referential_action_desc] ) - EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; - IF @SkipStatistics = 0 /* AND DB_NAME() = @DatabaseName /* Can only get stats in the current database - see https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/1947 */ */ - BEGIN - IF ((PARSENAME(@SQLServerProductVersion, 4) >= 12) - OR (PARSENAME(@SQLServerProductVersion, 4) = 11 AND PARSENAME(@SQLServerProductVersion, 2) >= 3000) - OR (PARSENAME(@SQLServerProductVersion, 4) = 10 AND PARSENAME(@SQLServerProductVersion, 3) = 50 AND PARSENAME(@SQLServerProductVersion, 2) >= 2500)) + + + + + + + + + + + + + + + + + + RAISERROR(N'Insert a row to help people find help', 0,1) WITH NOWAIT; + IF DATEDIFF(MM, @VersionDate, GETDATE()) > 6 BEGIN - RAISERROR (N'Gathering Statistics Info With Newer Syntax.',0,1) WITH NOWAIT; - SET @dsql=N'USE ' + @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, - 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) - 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, - ISNULL(i.name, ''System Or User Statistic'') AS index_name, - ca.column_names AS column_names, - s.name AS statistics_name, - CONVERT(DATETIME, ddsp.last_updated) AS last_statistics_update, - DATEDIFF(DAY, ddsp.last_updated, GETDATE()) AS days_since_last_stats_update, - ddsp.rows, - ddsp.rows_sampled, - CAST(ddsp.rows_sampled / ( 1. * NULLIF(ddsp.rows, 0) ) * 100 AS DECIMAL(18, 1)) AS percent_sampled, - ddsp.steps AS histogram_steps, - ddsp.modification_counter, - CASE WHEN ddsp.modification_counter > 0 - THEN CAST(ddsp.modification_counter / ( 1. * NULLIF(ddsp.rows, 0) ) * 100 AS DECIMAL(18, 1)) - ELSE ddsp.modification_counter - END AS percent_modifications, - CASE WHEN ddsp.rows < 500 THEN 500 - ELSE CAST(( ddsp.rows * .20 ) + 500 AS INT) - 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 - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.stats AS s - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects obj - ON s.object_id = obj.object_id - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas sch - ON sch.schema_id = obj.schema_id - LEFT JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.indexes AS i - ON i.object_id = s.object_id - AND i.index_id = s.stats_id - OUTER APPLY ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_stats_properties(s.object_id, s.stats_id) AS ddsp - CROSS APPLY ( SELECT STUFF((SELECT '', '' + c.name - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.stats_columns AS sc - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns AS c - ON sc.column_id = c.column_id AND sc.object_id = c.object_id - WHERE sc.stats_id = s.stats_id AND sc.object_id = s.object_id - ORDER BY sc.stats_column_id - FOR XML PATH(''''), TYPE).value(''.'', ''nvarchar(max)''), 1, 2, '''') - ) ca (column_names) - WHERE obj.is_ms_shipped = 0 - OPTION (RECOMPILE);'; - - IF @dsql IS NULL - RAISERROR('@dsql is null',16,1); + INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, URL, details, index_definition, + index_usage_summary, index_size_summary ) + VALUES ( -1, 0 , + 'Outdated sp_BlitzIndex', 'sp_BlitzIndex is Over 6 Months Old', 'http://FirstResponderKit.org/', + 'Fine wine gets better with age, but this ' + @ScriptVersionName + ' is more like bad cheese. Time to get a new one.', + @DaysUptimeInsertValue,N'',N'' + ); + END; - RAISERROR (N'Inserting data into #Statistics',0,1) WITH NOWAIT; - IF @Debug = 1 - BEGIN - PRINT SUBSTRING(@dsql, 0, 4000); - PRINT SUBSTRING(@dsql, 4000, 8000); - PRINT SUBSTRING(@dsql, 8000, 12000); - PRINT SUBSTRING(@dsql, 12000, 16000); - PRINT SUBSTRING(@dsql, 16000, 20000); - PRINT SUBSTRING(@dsql, 20000, 24000); - PRINT SUBSTRING(@dsql, 24000, 28000); - PRINT SUBSTRING(@dsql, 28000, 32000); - PRINT SUBSTRING(@dsql, 32000, 36000); - PRINT SUBSTRING(@dsql, 36000, 40000); - END; - - EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; - END; - ELSE + IF EXISTS(SELECT * FROM #BlitzIndexResults) + BEGIN + INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, URL, details, index_definition, + index_usage_summary, index_size_summary ) + VALUES ( -1, 0 , + @ScriptVersionName, + CASE WHEN @GetAllDatabases = 1 THEN N'All Databases' ELSE N'Database ' + QUOTENAME(@DatabaseName) + N' as of ' + CONVERT(NVARCHAR(16),GETDATE(),121) END, + N'From Your Community Volunteers' , N'http://FirstResponderKit.org' , + @DaysUptimeInsertValue,N'',N'' + ); + END; + ELSE IF @Mode = 0 OR (@GetAllDatabases = 1 AND @Mode <> 4) + BEGIN + INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, URL, details, index_definition, + index_usage_summary, index_size_summary ) + VALUES ( -1, 0 , + @ScriptVersionName, + CASE WHEN @GetAllDatabases = 1 THEN N'All Databases' ELSE N'Database ' + QUOTENAME(@DatabaseName) + N' as of ' + CONVERT(NVARCHAR(16),GETDATE(),121) END, + N'From Your Community Volunteers' , N'http://FirstResponderKit.org' , + @DaysUptimeInsertValue, N'',N'' + ); + INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, URL, details, index_definition, + index_usage_summary, index_size_summary ) + VALUES ( 1, 0 , + N'No Major Problems Found', + N'Nice Work!', + N'http://FirstResponderKit.org', + N'Consider running with @Mode = 4 in individual databases (not all) for more detailed diagnostics.', + N'The default Mode 0 only looks for very serious index issues.', + @DaysUptimeInsertValue, N'' + ); + + END; + ELSE + BEGIN + INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, URL, details, index_definition, + index_usage_summary, index_size_summary ) + VALUES ( -1, 0 , + @ScriptVersionName, + CASE WHEN @GetAllDatabases = 1 THEN N'All Databases' ELSE N'Database ' + QUOTENAME(@DatabaseName) + N' as of ' + CONVERT(NVARCHAR(16),GETDATE(),121) END, + N'From Your Community Volunteers' , N'http://FirstResponderKit.org' , + @DaysUptimeInsertValue, N'',N'' + ); + INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, URL, details, index_definition, + index_usage_summary, index_size_summary ) + VALUES ( 1, 0 , + N'No Problems Found', + N'Nice job! Or more likely, you have a nearly empty database.', + N'http://FirstResponderKit.org', 'Time to go read some blog posts.', + @DaysUptimeInsertValue, N'', N'' + ); + + END; + + RAISERROR(N'Returning results.', 0,1) WITH NOWAIT; + + /*Return results.*/ + IF (@Mode = 0) + BEGIN + IF(@OutputType <> 'NONE') BEGIN - RAISERROR (N'Gathering Statistics Info With Older Syntax.',0,1) WITH NOWAIT; - SET @dsql=N'USE ' + @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, 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) - 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, - ISNULL(i.name, ''System Or User Statistic'') AS index_name, - ca.column_names AS column_names, - s.name AS statistics_name, - CONVERT(DATETIME, STATS_DATE(s.object_id, s.stats_id)) AS last_statistics_update, - DATEDIFF(DAY, STATS_DATE(s.object_id, s.stats_id), GETDATE()) AS days_since_last_stats_update, - si.rowcnt, - si.rowmodctr, - CASE WHEN si.rowmodctr > 0 THEN CAST(si.rowmodctr / ( 1. * NULLIF(si.rowcnt, 0) ) * 100 AS DECIMAL(18, 1)) - ELSE si.rowmodctr - END AS percent_modifications, - CASE WHEN si.rowcnt < 500 THEN 500 - ELSE CAST(( si.rowcnt * .20 ) + 500 AS INT) - 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, - ' - + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' - THEN N's.has_filter, - s.filter_definition' - ELSE N'NULL AS has_filter, - NULL AS filter_definition' END - + N' - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.stats AS s - INNER HASH JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.sysindexes si - ON si.name = s.name AND s.object_id = si.id - INNER HASH JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects obj - ON s.object_id = obj.object_id - INNER HASH JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas sch - ON sch.schema_id = obj.schema_id - LEFT HASH JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.indexes AS i - ON i.object_id = s.object_id - AND i.index_id = s.stats_id - CROSS APPLY ( SELECT STUFF((SELECT '', '' + c.name - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.stats_columns AS sc - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns AS c - ON sc.column_id = c.column_id AND sc.object_id = c.object_id - WHERE sc.stats_id = s.stats_id AND sc.object_id = s.object_id - ORDER BY sc.stats_column_id - FOR XML PATH(''''), TYPE).value(''.'', ''nvarchar(max)''), 1, 2, '''') - ) ca (column_names) - WHERE obj.is_ms_shipped = 0 - AND si.rowcnt > 0 - OPTION (RECOMPILE);'; + SELECT Priority, ISNULL(br.findings_group,N'') + + CASE WHEN ISNULL(br.finding,N'') <> N'' THEN N': ' ELSE N'' END + + br.finding AS [Finding], + br.[database_name] AS [Database Name], + br.details AS [Details: schema.table.index(indexid)], + br.index_definition AS [Definition: [Property]] ColumnName {datatype maxbytes}], + ISNULL(br.secret_columns,'') AS [Secret Columns], + br.index_usage_summary AS [Usage], + br.index_size_summary AS [Size], + COALESCE(br.more_info,sn.more_info,'') AS [More Info], + br.URL, + COALESCE(br.create_tsql,ts.create_tsql,'') AS [Create TSQL] + FROM #BlitzIndexResults br + LEFT JOIN #IndexSanity sn ON + br.index_sanity_id=sn.index_sanity_id + LEFT JOIN #IndexCreateTsql ts ON + br.index_sanity_id=ts.index_sanity_id + ORDER BY br.Priority ASC, br.check_id ASC, br.blitz_result_id ASC, br.findings_group ASC + OPTION (RECOMPILE); + END; - IF @dsql IS NULL - RAISERROR('@dsql is null',16,1); + END; + ELSE IF (@Mode = 4) + IF(@OutputType <> 'NONE') + BEGIN + SELECT Priority, ISNULL(br.findings_group,N'') + + CASE WHEN ISNULL(br.finding,N'') <> N'' THEN N': ' ELSE N'' END + + br.finding AS [Finding], + br.[database_name] AS [Database Name], + br.details AS [Details: schema.table.index(indexid)], + br.index_definition AS [Definition: [Property]] ColumnName {datatype maxbytes}], + ISNULL(br.secret_columns,'') AS [Secret Columns], + br.index_usage_summary AS [Usage], + br.index_size_summary AS [Size], + COALESCE(br.more_info,sn.more_info,'') AS [More Info], + br.URL, + COALESCE(br.create_tsql,ts.create_tsql,'') AS [Create TSQL] + FROM #BlitzIndexResults br + LEFT JOIN #IndexSanity sn ON + br.index_sanity_id=sn.index_sanity_id + LEFT JOIN #IndexCreateTsql ts ON + br.index_sanity_id=ts.index_sanity_id + ORDER BY br.Priority ASC, br.check_id ASC, br.blitz_result_id ASC, br.findings_group ASC + OPTION (RECOMPILE); + END; + +END /* End @Mode=0 or 4 (diagnose)*/ + +ELSE IF (@Mode=1) /*Summarize*/ + BEGIN + --This mode is to give some overall stats on the database. + IF(@OutputType <> 'NONE') + BEGIN + RAISERROR(N'@Mode=1, we are summarizing.', 0,1) WITH NOWAIT; + + SELECT DB_NAME(i.database_id) AS [Database Name], + CAST((COUNT(*)) AS NVARCHAR(256)) AS [Number Objects], + CAST(CAST(SUM(sz.total_reserved_MB)/ + 1024. AS NUMERIC(29,1)) AS NVARCHAR(500)) AS [All GB], + CAST(CAST(SUM(sz.total_reserved_LOB_MB)/ + 1024. AS NUMERIC(29,1)) AS NVARCHAR(500)) AS [LOB GB], + CAST(CAST(SUM(sz.total_reserved_row_overflow_MB)/ + 1024. AS NUMERIC(29,1)) AS NVARCHAR(500)) AS [Row Overflow GB], + CAST(SUM(CASE WHEN index_id=1 THEN 1 ELSE 0 END)AS NVARCHAR(50)) AS [Clustered Tables], + CAST(SUM(CASE WHEN index_id=1 THEN sz.total_reserved_MB ELSE 0 END) + /1024. AS NUMERIC(29,1)) AS [Clustered Tables GB], + SUM(CASE WHEN index_id NOT IN (0,1) THEN 1 ELSE 0 END) AS [NC Indexes], + CAST(SUM(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) + /1024. AS NUMERIC(29,1)) AS [NC Indexes GB], + CASE WHEN SUM(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) > 0 THEN + CAST(SUM(CASE WHEN index_id IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) + / SUM(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) AS NUMERIC(29,1)) + ELSE 0 END AS [ratio table: NC Indexes], + SUM(CASE WHEN index_id=0 THEN 1 ELSE 0 END) AS [Heaps], + CAST(SUM(CASE WHEN index_id=0 THEN sz.total_reserved_MB ELSE 0 END) + /1024. AS NUMERIC(29,1)) AS [Heaps GB], + SUM(CASE WHEN index_id IN (0,1) AND partition_key_column_name IS NOT NULL THEN 1 ELSE 0 END) AS [Partitioned Tables], + SUM(CASE WHEN index_id NOT IN (0,1) AND partition_key_column_name IS NOT NULL THEN 1 ELSE 0 END) AS [Partitioned NCs], + CAST(SUM(CASE WHEN partition_key_column_name IS NOT NULL THEN sz.total_reserved_MB ELSE 0 END)/1024. AS NUMERIC(29,1)) AS [Partitioned GB], + SUM(CASE WHEN filter_definition <> '' THEN 1 ELSE 0 END) AS [Filtered Indexes], + SUM(CASE WHEN is_indexed_view=1 THEN 1 ELSE 0 END) AS [Indexed Views], + MAX(total_rows) AS [Max Row Count], + CAST(MAX(CASE WHEN index_id IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) + /1024. AS NUMERIC(29,1)) AS [Max Table GB], + CAST(MAX(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) + /1024. AS NUMERIC(29,1)) AS [Max NC Index GB], + SUM(CASE WHEN index_id IN (0,1) AND sz.total_reserved_MB > 1024 THEN 1 ELSE 0 END) AS [Count Tables > 1GB], + SUM(CASE WHEN index_id IN (0,1) AND sz.total_reserved_MB > 10240 THEN 1 ELSE 0 END) AS [Count Tables > 10GB], + SUM(CASE WHEN index_id IN (0,1) AND sz.total_reserved_MB > 102400 THEN 1 ELSE 0 END) AS [Count Tables > 100GB], + SUM(CASE WHEN index_id NOT IN (0,1) AND sz.total_reserved_MB > 1024 THEN 1 ELSE 0 END) AS [Count NCs > 1GB], + SUM(CASE WHEN index_id NOT IN (0,1) AND sz.total_reserved_MB > 10240 THEN 1 ELSE 0 END) AS [Count NCs > 10GB], + SUM(CASE WHEN index_id NOT IN (0,1) AND sz.total_reserved_MB > 102400 THEN 1 ELSE 0 END) AS [Count NCs > 100GB], + MIN(create_date) AS [Oldest Create Date], + MAX(create_date) AS [Most Recent Create Date], + MAX(modify_date) AS [Most Recent Modify Date], + 1 AS [Display Order] + FROM #IndexSanity AS i + --left join here so we don't lose disabled nc indexes + LEFT JOIN #IndexSanitySize AS sz + ON i.index_sanity_id=sz.index_sanity_id + GROUP BY DB_NAME(i.database_id) + UNION ALL + SELECT CASE WHEN @GetAllDatabases = 1 THEN N'All Databases' ELSE N'Database ' + N' as of ' + CONVERT(NVARCHAR(16),GETDATE(),121) END, + @ScriptVersionName, + N'From Your Community Volunteers' , + N'http://FirstResponderKit.org' , + @DaysUptimeInsertValue, + NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL, + NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL, + NULL,NULL,0 AS display_order + ORDER BY [Display Order] ASC + OPTION (RECOMPILE); + END; + + END; /* End @Mode=1 (summarize)*/ + ELSE IF (@Mode=2) /*Index Detail*/ + BEGIN + --This mode just spits out all the detail without filters. + --This supports slicing AND dicing in Excel + RAISERROR(N'@Mode=2, here''s the details on existing indexes.', 0,1) WITH NOWAIT; - RAISERROR (N'Inserting data into #Statistics',0,1) WITH NOWAIT; - IF @Debug = 1 - BEGIN - PRINT SUBSTRING(@dsql, 0, 4000); - PRINT SUBSTRING(@dsql, 4000, 8000); - PRINT SUBSTRING(@dsql, 8000, 12000); - PRINT SUBSTRING(@dsql, 12000, 16000); - PRINT SUBSTRING(@dsql, 16000, 20000); - PRINT SUBSTRING(@dsql, 20000, 24000); - PRINT SUBSTRING(@dsql, 24000, 28000); - PRINT SUBSTRING(@dsql, 28000, 32000); - PRINT SUBSTRING(@dsql, 32000, 36000); - PRINT SUBSTRING(@dsql, 36000, 40000); - END; - - EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; + + /* Checks if @OutputServerName is populated with a valid linked server, and that the database name specified is valid */ + DECLARE @ValidOutputServer BIT; + DECLARE @ValidOutputLocation BIT; + DECLARE @LinkedServerDBCheck NVARCHAR(2000); + DECLARE @ValidLinkedServerDB INT; + DECLARE @tmpdbchk TABLE (cnt INT); + DECLARE @StringToExecute NVARCHAR(MAX); + + IF @OutputServerName IS NOT NULL + BEGIN + IF (SUBSTRING(@OutputTableName, 2, 1) = '#') + BEGIN + RAISERROR('Due to the nature of temporary tables, outputting to a linked server requires a permanent table.', 16, 0); + END; + ELSE IF EXISTS (SELECT server_id FROM sys.servers WHERE QUOTENAME([name]) = @OutputServerName) + BEGIN + SET @LinkedServerDBCheck = 'SELECT 1 WHERE EXISTS (SELECT * FROM '+@OutputServerName+'.master.sys.databases WHERE QUOTENAME([name]) = '''+@OutputDatabaseName+''')'; + INSERT INTO @tmpdbchk EXEC sys.sp_executesql @LinkedServerDBCheck; + SET @ValidLinkedServerDB = (SELECT COUNT(*) FROM @tmpdbchk); + IF (@ValidLinkedServerDB > 0) + BEGIN + SET @ValidOutputServer = 1; + SET @ValidOutputLocation = 1; + END; + ELSE + RAISERROR('The specified database was not found on the output server', 16, 0); + END; + ELSE + BEGIN + RAISERROR('The specified output server was not found', 16, 0); + END; END; - + ELSE + BEGIN + IF (SUBSTRING(@OutputTableName, 2, 2) = '##') + BEGIN + SET @StringToExecute = N' IF (OBJECT_ID(''[tempdb].[dbo].@@@OutputTableName@@@'') IS NOT NULL) DROP TABLE @@@OutputTableName@@@'; + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); + EXEC(@StringToExecute); + + SET @OutputServerName = QUOTENAME(CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128))); + SET @OutputDatabaseName = '[tempdb]'; + SET @OutputSchemaName = '[dbo]'; + SET @ValidOutputLocation = 1; + END; + ELSE IF (SUBSTRING(@OutputTableName, 2, 1) = '#') + BEGIN + RAISERROR('Due to the nature of Dymamic SQL, only global (i.e. double pound (##)) temp tables are supported for @OutputTableName', 16, 0); + END; + ELSE IF @OutputDatabaseName IS NOT NULL + AND @OutputSchemaName IS NOT NULL + AND @OutputTableName IS NOT NULL + AND EXISTS ( SELECT * + FROM sys.databases + WHERE QUOTENAME([name]) = @OutputDatabaseName) + BEGIN + SET @ValidOutputLocation = 1; + SET @OutputServerName = QUOTENAME(CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128))); + END; + ELSE IF @OutputDatabaseName IS NOT NULL + AND @OutputSchemaName IS NOT NULL + AND @OutputTableName IS NOT NULL + AND NOT EXISTS ( SELECT * + FROM sys.databases + WHERE QUOTENAME([name]) = @OutputDatabaseName) + BEGIN + RAISERROR('The specified output database was not found on this server', 16, 0); + END; + ELSE + BEGIN + SET @ValidOutputLocation = 0; + END; END; - - IF (PARSENAME(@SQLServerProductVersion, 4) >= 10) + + IF (@ValidOutputLocation = 0 AND @OutputType = 'NONE') + BEGIN + RAISERROR('Invalid output location and no output asked',12,1); + RETURN; + END; + + /* @OutputTableName lets us export the results to a permanent table */ + DECLARE @RunID UNIQUEIDENTIFIER; + SET @RunID = NEWID(); + + IF (@ValidOutputLocation = 1 AND COALESCE(@OutputServerName, @OutputDatabaseName, @OutputSchemaName, @OutputTableName) IS NOT NULL) BEGIN - RAISERROR (N'Gathering Computed Column Info.',0,1) WITH NOWAIT; - SET @dsql=N'SELECT DB_ID(@i_DatabaseName) AS [database_id], - @i_DatabaseName AS database_name, - t.name AS table_name, - s.name AS schema_name, - c.name AS column_name, - cc.is_nullable, - cc.definition, - cc.uses_database_collation, - cc.is_persisted, - cc.is_computed, - CASE WHEN cc.definition LIKE ''%|].|[%'' ESCAPE ''|'' THEN 1 ELSE 0 END AS is_function, - ''ALTER TABLE '' + QUOTENAME(s.name) + ''.'' + QUOTENAME(t.name) + - '' ADD '' + QUOTENAME(c.name) + '' AS '' + cc.definition + - CASE WHEN is_persisted = 1 THEN '' PERSISTED'' ELSE '''' END + '';'' COLLATE DATABASE_DEFAULT AS [column_definition] - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.computed_columns AS cc - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns AS c - ON cc.object_id = c.object_id - AND cc.column_id = c.column_id - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.tables AS t - ON t.object_id = cc.object_id - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s - ON s.schema_id = t.schema_id - OPTION (RECOMPILE);'; - - IF @dsql IS NULL RAISERROR('@dsql is null',16,1); - - INSERT #ComputedColumns - ( database_id, [database_name], table_name, schema_name, column_name, is_nullable, definition, - uses_database_collation, is_persisted, is_computed, is_function, column_definition ) - EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; - - END; + DECLARE @TableExists BIT; + DECLARE @SchemaExists BIT; + SET @StringToExecute = + N'SET @SchemaExists = 0; + SET @TableExists = 0; + IF EXISTS(SELECT * FROM @@@OutputServerName@@@.@@@OutputDatabaseName@@@.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''@@@OutputSchemaName@@@'') + SET @SchemaExists = 1 + IF EXISTS (SELECT * FROM @@@OutputServerName@@@.@@@OutputDatabaseName@@@.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''@@@OutputSchemaName@@@'' AND QUOTENAME(TABLE_NAME) = ''@@@OutputTableName@@@'') + SET @TableExists = 1'; + + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputServerName@@@', @OutputServerName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputSchemaName@@@', @OutputSchemaName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); + + EXEC sp_executesql @StringToExecute, N'@TableExists BIT OUTPUT, @SchemaExists BIT OUTPUT', @TableExists OUTPUT, @SchemaExists OUTPUT; + + IF @SchemaExists = 1 + BEGIN + IF @TableExists = 0 + BEGIN + SET @StringToExecute = + N'CREATE TABLE @@@OutputDatabaseName@@@.@@@OutputSchemaName@@@.@@@OutputTableName@@@ + ( + [id] INT IDENTITY(1,1) NOT NULL, + [run_id] UNIQUEIDENTIFIER, + [run_datetime] DATETIME, + [server_name] NVARCHAR(128), + [database_name] NVARCHAR(128), + [schema_name] NVARCHAR(128), + [table_name] NVARCHAR(128), + [index_name] NVARCHAR(128), + [Drop_Tsql] NVARCHAR(MAX), + [Create_Tsql] NVARCHAR(MAX), + [index_id] INT, + [db_schema_object_indexid] NVARCHAR(500), + [object_type] NVARCHAR(15), + [index_definition] NVARCHAR(MAX), + [key_column_names_with_sort_order] NVARCHAR(MAX), + [count_key_columns] INT, + [include_column_names] NVARCHAR(MAX), + [count_included_columns] INT, + [secret_columns] NVARCHAR(MAX), + [count_secret_columns] INT, + [partition_key_column_name] NVARCHAR(MAX), + [filter_definition] NVARCHAR(MAX), + [is_indexed_view] BIT, + [is_primary_key] BIT, + [is_XML] BIT, + [is_spatial] BIT, + [is_NC_columnstore] BIT, + [is_CX_columnstore] BIT, + [is_in_memory_oltp] BIT, + [is_disabled] BIT, + [is_hypothetical] BIT, + [is_padded] BIT, + [fill_factor] INT, + [is_referenced_by_foreign_key] BIT, + [last_user_seek] DATETIME, + [last_user_scan] DATETIME, + [last_user_lookup] DATETIME, + [last_user_update] DATETIME, + [total_reads] BIGINT, + [user_updates] BIGINT, + [reads_per_write] MONEY, + [index_usage_summary] NVARCHAR(200), + [total_singleton_lookup_count] BIGINT, + [total_range_scan_count] BIGINT, + [total_leaf_delete_count] BIGINT, + [total_leaf_update_count] BIGINT, + [index_op_stats] NVARCHAR(200), + [partition_count] INT, + [total_rows] BIGINT, + [total_reserved_MB] NUMERIC(29,2), + [total_reserved_LOB_MB] NUMERIC(29,2), + [total_reserved_row_overflow_MB] NUMERIC(29,2), + [index_size_summary] NVARCHAR(300), + [total_row_lock_count] BIGINT, + [total_row_lock_wait_count] BIGINT, + [total_row_lock_wait_in_ms] BIGINT, + [avg_row_lock_wait_in_ms] BIGINT, + [total_page_lock_count] BIGINT, + [total_page_lock_wait_count] BIGINT, + [total_page_lock_wait_in_ms] BIGINT, + [avg_page_lock_wait_in_ms] BIGINT, + [total_index_lock_promotion_attempt_count] BIGINT, + [total_index_lock_promotion_count] BIGINT, + [data_compression_desc] NVARCHAR(4000), + [page_latch_wait_count] BIGINT, + [page_latch_wait_in_ms] BIGINT, + [page_io_latch_wait_count] BIGINT, + [page_io_latch_wait_in_ms] BIGINT, + [create_date] DATETIME, + [modify_date] DATETIME, + [more_info] NVARCHAR(500), + [display_order] INT, + CONSTRAINT [PK_ID_@@@RunID@@@] PRIMARY KEY CLUSTERED ([id] ASC) + );'; + + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputSchemaName@@@', @OutputSchemaName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@RunID@@@', @RunID); + + IF @ValidOutputServer = 1 + BEGIN + SET @StringToExecute = REPLACE(@StringToExecute,'''',''''''); + EXEC('EXEC('''+@StringToExecute+''') AT ' + @OutputServerName); + END; + ELSE + BEGIN + EXEC(@StringToExecute); + END; + END; /* @TableExists = 0 */ + + SET @StringToExecute = + N'IF EXISTS(SELECT * FROM @@@OutputServerName@@@.@@@OutputDatabaseName@@@.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''@@@OutputSchemaName@@@'') + AND NOT EXISTS (SELECT * FROM @@@OutputServerName@@@.@@@OutputDatabaseName@@@.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''@@@OutputSchemaName@@@'' AND QUOTENAME(TABLE_NAME) = ''@@@OutputTableName@@@'') + SET @TableExists = 0 + ELSE + SET @TableExists = 1'; + + SET @TableExists = NULL; + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputServerName@@@', @OutputServerName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputSchemaName@@@', @OutputSchemaName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); - RAISERROR (N'Gathering Trace Flag Information',0,1) WITH NOWAIT; - INSERT #TraceStatus - EXEC ('DBCC TRACESTATUS(-1) WITH NO_INFOMSGS'); + EXEC sp_executesql @StringToExecute, N'@TableExists BIT OUTPUT', @TableExists OUTPUT; + + IF @TableExists = 1 + BEGIN + SET @StringToExecute = + N'INSERT @@@OutputServerName@@@.@@@OutputDatabaseName@@@.@@@OutputSchemaName@@@.@@@OutputTableName@@@ + ( + [run_id], + [run_datetime], + [server_name], + [database_name], + [schema_name], + [table_name], + [index_name], + [Drop_Tsql], + [Create_Tsql], + [index_id], + [db_schema_object_indexid], + [object_type], + [index_definition], + [key_column_names_with_sort_order], + [count_key_columns], + [include_column_names], + [count_included_columns], + [secret_columns], + [count_secret_columns], + [partition_key_column_name], + [filter_definition], + [is_indexed_view], + [is_primary_key], + [is_XML], + [is_spatial], + [is_NC_columnstore], + [is_CX_columnstore], + [is_in_memory_oltp], + [is_disabled], + [is_hypothetical], + [is_padded], + [fill_factor], + [is_referenced_by_foreign_key], + [last_user_seek], + [last_user_scan], + [last_user_lookup], + [last_user_update], + [total_reads], + [user_updates], + [reads_per_write], + [index_usage_summary], + [total_singleton_lookup_count], + [total_range_scan_count], + [total_leaf_delete_count], + [total_leaf_update_count], + [index_op_stats], + [partition_count], + [total_rows], + [total_reserved_MB], + [total_reserved_LOB_MB], + [total_reserved_row_overflow_MB], + [index_size_summary], + [total_row_lock_count], + [total_row_lock_wait_count], + [total_row_lock_wait_in_ms], + [avg_row_lock_wait_in_ms], + [total_page_lock_count], + [total_page_lock_wait_count], + [total_page_lock_wait_in_ms], + [avg_page_lock_wait_in_ms], + [total_index_lock_promotion_attempt_count], + [total_index_lock_promotion_count], + [data_compression_desc], + [page_latch_wait_count], + [page_latch_wait_in_ms], + [page_io_latch_wait_count], + [page_io_latch_wait_in_ms], + [create_date], + [modify_date], + [more_info], + [display_order] + ) + SELECT ''@@@RunID@@@'', + ''@@@GETDATE@@@'', + ''@@@LocalServerName@@@'', + -- Below should be a copy/paste of the real query + -- Make sure all quotes are escaped + i.[database_name] AS [Database Name], + i.[schema_name] AS [Schema Name], + i.[object_name] AS [Object Name], + ISNULL(i.index_name, '''') AS [Index Name], + CASE + WHEN i.is_primary_key = 1 AND i.index_definition <> ''[HEAP]'' + THEN N''-ALTER TABLE '' + QUOTENAME(i.[database_name]) + N''.'' + QUOTENAME(i.[schema_name]) + N''.'' + QUOTENAME(i.[object_name]) + + N'' DROP CONSTRAINT '' + QUOTENAME(i.index_name) + N'';'' + WHEN i.is_primary_key = 0 AND i.index_definition <> ''[HEAP]'' + THEN N''--DROP INDEX ''+ QUOTENAME(i.index_name) + N'' ON '' + QUOTENAME(i.[database_name]) + N''.'' + + QUOTENAME(i.[schema_name]) + N''.'' + QUOTENAME(i.[object_name]) + N'';'' + ELSE N'''' + END AS [Drop TSQL], + CASE + WHEN i.index_definition = ''[HEAP]'' THEN N'''' + ELSE N''--'' + ict.create_tsql END AS [Create TSQL], + CAST(i.index_id AS NVARCHAR(10))AS [Index ID], + db_schema_object_indexid AS [Details: schema.table.index(indexid)], + CASE WHEN index_id IN ( 1, 0 ) THEN ''TABLE'' + ELSE ''NonClustered'' + END AS [Object Type], + LEFT(index_definition,4000) AS [Definition: [Property]] ColumnName {datatype maxbytes}], + ISNULL(LTRIM(key_column_names_with_sort_order), '''') AS [Key Column Names With Sort], + ISNULL(count_key_columns, 0) AS [Count Key Columns], + ISNULL(include_column_names, '''') AS [Include Column Names], + ISNULL(count_included_columns,0) AS [Count Included Columns], + ISNULL(secret_columns,'''') AS [Secret Column Names], + ISNULL(count_secret_columns,0) AS [Count Secret Columns], + ISNULL(partition_key_column_name, '''') AS [Partition Key Column Name], + ISNULL(filter_definition, '''') AS [Filter Definition], + is_indexed_view AS [Is Indexed View], + is_primary_key AS [Is Primary Key], + is_XML AS [Is XML], + is_spatial AS [Is Spatial], + is_NC_columnstore AS [Is NC Columnstore], + is_CX_columnstore AS [Is CX Columnstore], + is_in_memory_oltp AS [Is In-Memory OLTP], + is_disabled AS [Is Disabled], + is_hypothetical AS [Is Hypothetical], + is_padded AS [Is Padded], + fill_factor AS [Fill Factor], + is_referenced_by_foreign_key AS [Is Reference by Foreign Key], + last_user_seek AS [Last User Seek], + last_user_scan AS [Last User Scan], + last_user_lookup AS [Last User Lookup], + last_user_update AS [Last User Update], + total_reads AS [Total Reads], + user_updates AS [User Updates], + reads_per_write AS [Reads Per Write], + index_usage_summary AS [Index Usage], + sz.total_singleton_lookup_count AS [Singleton Lookups], + sz.total_range_scan_count AS [Range Scans], + sz.total_leaf_delete_count AS [Leaf Deletes], + sz.total_leaf_update_count AS [Leaf Updates], + sz.index_op_stats AS [Index Op Stats], + sz.partition_count AS [Partition Count], + sz.total_rows AS [Rows], + sz.total_reserved_MB AS [Reserved MB], + sz.total_reserved_LOB_MB AS [Reserved LOB MB], + sz.total_reserved_row_overflow_MB AS [Reserved Row Overflow MB], + sz.index_size_summary AS [Index Size], + sz.total_row_lock_count AS [Row Lock Count], + sz.total_row_lock_wait_count AS [Row Lock Wait Count], + sz.total_row_lock_wait_in_ms AS [Row Lock Wait ms], + sz.avg_row_lock_wait_in_ms AS [Avg Row Lock Wait ms], + sz.total_page_lock_count AS [Page Lock Count], + sz.total_page_lock_wait_count AS [Page Lock Wait Count], + sz.total_page_lock_wait_in_ms AS [Page Lock Wait ms], + sz.avg_page_lock_wait_in_ms AS [Avg Page Lock Wait ms], + sz.total_index_lock_promotion_attempt_count AS [Lock Escalation Attempts], + sz.total_index_lock_promotion_count AS [Lock Escalations], + sz.data_compression_desc AS [Data Compression], + sz.page_latch_wait_count, + sz.page_latch_wait_in_ms, + sz.page_io_latch_wait_count, + sz.page_io_latch_wait_in_ms, + i.create_date AS [Create Date], + i.modify_date AS [Modify Date], + more_info AS [More Info], + 1 AS [Display Order] + FROM #IndexSanity AS i + LEFT JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id + LEFT JOIN #IndexCreateTsql AS ict ON i.index_sanity_id = ict.index_sanity_id + ORDER BY [Database Name], [Schema Name], [Object Name], [Index ID] + OPTION (RECOMPILE);'; + + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputServerName@@@', @OutputServerName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputSchemaName@@@', @OutputSchemaName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@RunID@@@', @RunID); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@GETDATE@@@', GETDATE()); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@LocalServerName@@@', CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128))); + EXEC(@StringToExecute); + END; /* @TableExists = 1 */ + ELSE + RAISERROR('Creation of the output table failed.', 16, 0); + END; /* @TableExists = 0 */ + ELSE + RAISERROR (N'Invalid schema name, data could not be saved.', 16, 0); + END; /* @ValidOutputLocation = 1 */ + ELSE + + IF(@OutputType <> 'NONE') + BEGIN + SELECT i.[database_name] AS [Database Name], + i.[schema_name] AS [Schema Name], + i.[object_name] AS [Object Name], + ISNULL(i.index_name, '') AS [Index Name], + CAST(i.index_id AS NVARCHAR(10))AS [Index ID], + db_schema_object_indexid AS [Details: schema.table.index(indexid)], + CASE WHEN index_id IN ( 1, 0 ) THEN 'TABLE' + ELSE 'NonClustered' + END AS [Object Type], + index_definition AS [Definition: [Property]] ColumnName {datatype maxbytes}], + ISNULL(LTRIM(key_column_names_with_sort_order), '') AS [Key Column Names With Sort], + ISNULL(count_key_columns, 0) AS [Count Key Columns], + ISNULL(include_column_names, '') AS [Include Column Names], + ISNULL(count_included_columns,0) AS [Count Included Columns], + ISNULL(secret_columns,'') AS [Secret Column Names], + ISNULL(count_secret_columns,0) AS [Count Secret Columns], + ISNULL(partition_key_column_name, '') AS [Partition Key Column Name], + ISNULL(filter_definition, '') AS [Filter Definition], + is_indexed_view AS [Is Indexed View], + is_primary_key AS [Is Primary Key], + is_XML AS [Is XML], + is_spatial AS [Is Spatial], + is_NC_columnstore AS [Is NC Columnstore], + is_CX_columnstore AS [Is CX Columnstore], + is_in_memory_oltp AS [Is In-Memory OLTP], + is_disabled AS [Is Disabled], + is_hypothetical AS [Is Hypothetical], + is_padded AS [Is Padded], + fill_factor AS [Fill Factor], + is_referenced_by_foreign_key AS [Is Reference by Foreign Key], + last_user_seek AS [Last User Seek], + last_user_scan AS [Last User Scan], + last_user_lookup AS [Last User Lookup], + last_user_update AS [Last User Update], + total_reads AS [Total Reads], + user_updates AS [User Updates], + reads_per_write AS [Reads Per Write], + index_usage_summary AS [Index Usage], + sz.total_singleton_lookup_count AS [Singleton Lookups], + sz.total_range_scan_count AS [Range Scans], + sz.total_leaf_delete_count AS [Leaf Deletes], + sz.total_leaf_update_count AS [Leaf Updates], + sz.index_op_stats AS [Index Op Stats], + sz.partition_count AS [Partition Count], + sz.total_rows AS [Rows], + sz.total_reserved_MB AS [Reserved MB], + sz.total_reserved_LOB_MB AS [Reserved LOB MB], + sz.total_reserved_row_overflow_MB AS [Reserved Row Overflow MB], + sz.index_size_summary AS [Index Size], + sz.total_row_lock_count AS [Row Lock Count], + sz.total_row_lock_wait_count AS [Row Lock Wait Count], + sz.total_row_lock_wait_in_ms AS [Row Lock Wait ms], + sz.avg_row_lock_wait_in_ms AS [Avg Row Lock Wait ms], + sz.total_page_lock_count AS [Page Lock Count], + sz.total_page_lock_wait_count AS [Page Lock Wait Count], + sz.total_page_lock_wait_in_ms AS [Page Lock Wait ms], + sz.avg_page_lock_wait_in_ms AS [Avg Page Lock Wait ms], + sz.total_index_lock_promotion_attempt_count AS [Lock Escalation Attempts], + sz.total_index_lock_promotion_count AS [Lock Escalations], + sz.page_latch_wait_count AS [Page Latch Wait Count], + sz.page_latch_wait_in_ms AS [Page Latch Wait ms], + sz.page_io_latch_wait_count AS [Page IO Latch Wait Count], + sz.page_io_latch_wait_in_ms as [Page IO Latch Wait ms], + sz.total_forwarded_fetch_count AS [Forwarded Fetches], + sz.data_compression_desc AS [Data Compression], + i.create_date AS [Create Date], + i.modify_date AS [Modify Date], + more_info AS [More Info], + CASE + WHEN i.is_primary_key = 1 AND i.index_definition <> '[HEAP]' + THEN N'--ALTER TABLE ' + QUOTENAME(i.[database_name]) + N'.' + QUOTENAME(i.[schema_name]) + N'.' + QUOTENAME(i.[object_name]) + + N' DROP CONSTRAINT ' + QUOTENAME(i.index_name) + N';' + WHEN i.is_primary_key = 0 AND i.index_definition <> '[HEAP]' + THEN N'--DROP INDEX '+ QUOTENAME(i.index_name) + N' ON ' + QUOTENAME(i.[database_name]) + N'.' + + QUOTENAME(i.[schema_name]) + N'.' + QUOTENAME(i.[object_name]) + N';' + ELSE N'' + END AS [Drop TSQL], + CASE + WHEN i.index_definition = '[HEAP]' THEN N'' + ELSE N'--' + ict.create_tsql END AS [Create TSQL], + 1 AS [Display Order] + FROM #IndexSanity AS i --left join here so we don't lose disabled nc indexes + LEFT JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id + LEFT JOIN #IndexCreateTsql AS ict ON i.index_sanity_id = ict.index_sanity_id + ORDER BY CASE WHEN @SortDirection = 'desc' THEN + CASE WHEN @SortOrder = N'rows' THEN sz.total_rows + WHEN @SortOrder = N'reserved_mb' THEN sz.total_reserved_MB + WHEN @SortOrder = N'size' THEN sz.total_reserved_MB + WHEN @SortOrder = N'reserved_lob_mb' THEN sz.total_reserved_LOB_MB + WHEN @SortOrder = N'lob' THEN sz.total_reserved_LOB_MB + WHEN @SortOrder = N'total_row_lock_wait_in_ms' THEN COALESCE(sz.total_row_lock_wait_in_ms,0) + WHEN @SortOrder = N'total_page_lock_wait_in_ms' THEN COALESCE(sz.total_page_lock_wait_in_ms,0) + WHEN @SortOrder = N'lock_time' THEN (COALESCE(sz.total_row_lock_wait_in_ms,0) + COALESCE(sz.total_page_lock_wait_in_ms,0)) + WHEN @SortOrder = N'total_reads' THEN total_reads + WHEN @SortOrder = N'reads' THEN total_reads + WHEN @SortOrder = N'user_updates' THEN user_updates + WHEN @SortOrder = N'writes' THEN user_updates + WHEN @SortOrder = N'reads_per_write' THEN reads_per_write + WHEN @SortOrder = N'ratio' THEN reads_per_write + WHEN @SortOrder = N'forward_fetches' THEN sz.total_forwarded_fetch_count + WHEN @SortOrder = N'fetches' THEN sz.total_forwarded_fetch_count + ELSE NULL END + ELSE 1 END + DESC, /* Shout out to DHutmacher */ + CASE WHEN @SortDirection = 'asc' THEN + CASE WHEN @SortOrder = N'rows' THEN sz.total_rows + WHEN @SortOrder = N'reserved_mb' THEN sz.total_reserved_MB + WHEN @SortOrder = N'size' THEN sz.total_reserved_MB + WHEN @SortOrder = N'reserved_lob_mb' THEN sz.total_reserved_LOB_MB + WHEN @SortOrder = N'lob' THEN sz.total_reserved_LOB_MB + WHEN @SortOrder = N'total_row_lock_wait_in_ms' THEN COALESCE(sz.total_row_lock_wait_in_ms,0) + WHEN @SortOrder = N'total_page_lock_wait_in_ms' THEN COALESCE(sz.total_page_lock_wait_in_ms,0) + WHEN @SortOrder = N'lock_time' THEN (COALESCE(sz.total_row_lock_wait_in_ms,0) + COALESCE(sz.total_page_lock_wait_in_ms,0)) + WHEN @SortOrder = N'total_reads' THEN total_reads + WHEN @SortOrder = N'reads' THEN total_reads + WHEN @SortOrder = N'user_updates' THEN user_updates + WHEN @SortOrder = N'writes' THEN user_updates + WHEN @SortOrder = N'reads_per_write' THEN reads_per_write + WHEN @SortOrder = N'ratio' THEN reads_per_write + WHEN @SortOrder = N'forward_fetches' THEN sz.total_forwarded_fetch_count + WHEN @SortOrder = N'fetches' THEN sz.total_forwarded_fetch_count + ELSE NULL END + ELSE 1 END + ASC, + i.[database_name], [Schema Name], [Object Name], [Index ID] + OPTION (RECOMPILE); + END; - IF (PARSENAME(@SQLServerProductVersion, 4) >= 13) - BEGIN - RAISERROR (N'Gathering Temporal Table Info',0,1) WITH NOWAIT; - SET @dsql=N'SELECT ' + QUOTENAME(@DatabaseName,'''') + N' AS database_name, - DB_ID(N' + QUOTENAME(@DatabaseName,'''') + N') AS [database_id], - s.name AS schema_name, - t.name AS table_name, - oa.hsn as history_schema_name, - oa.htn AS history_table_name, - c1.name AS start_column_name, - c2.name AS end_column_name, - p.name AS period_name - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.periods AS p - INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.tables AS t - ON p.object_id = t.object_id - INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns AS c1 - ON t.object_id = c1.object_id - AND p.start_column_id = c1.column_id - INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns AS c2 - ON t.object_id = c2.object_id - AND p.end_column_id = c2.column_id - INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s - ON t.schema_id = s.schema_id - CROSS APPLY ( SELECT s2.name as hsn, t2.name htn - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.tables AS t2 - INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s2 - ON t2.schema_id = s2.schema_id - WHERE t2.object_id = t.history_table_id - AND t2.temporal_type = 1 /*History table*/ ) AS oa - WHERE t.temporal_type IN ( 2, 4 ) /*BOL currently points to these types, but has no definition for 4*/ - OPTION (RECOMPILE); - '; - - IF @dsql IS NULL - RAISERROR('@dsql is null',16,1); - - INSERT #TemporalTables ( database_name, database_id, schema_name, table_name, history_schema_name, - history_table_name, start_column_name, end_column_name, period_name ) - - EXEC sp_executesql @dsql; + END; /* End @Mode=2 (index detail)*/ + ELSE IF (@Mode=3) /*Missing index Detail*/ + BEGIN + IF(@OutputType <> 'NONE') + BEGIN; + WITH create_date AS ( + SELECT i.database_id, + i.schema_name, + i.[object_id], + ISNULL(NULLIF(MAX(DATEDIFF(DAY, i.create_date, SYSDATETIME())), 0), 1) AS create_days + FROM #IndexSanity AS i + GROUP BY i.database_id, i.schema_name, i.object_id + ) + SELECT + mi.database_name AS [Database Name], + mi.[schema_name] AS [Schema], + mi.table_name AS [Table], + CAST((mi.magic_benefit_number / CASE WHEN cd.create_days < @DaysUptime THEN cd.create_days ELSE @DaysUptime END) AS BIGINT) + AS [Magic Benefit Number], + mi.missing_index_details AS [Missing Index Details], + mi.avg_total_user_cost AS [Avg Query Cost], + mi.avg_user_impact AS [Est Index Improvement], + mi.user_seeks AS [Seeks], + mi.user_scans AS [Scans], + mi.unique_compiles AS [Compiles], + mi.equality_columns_with_data_type AS [Equality Columns], + mi.inequality_columns_with_data_type AS [Inequality Columns], + mi.included_columns_with_data_type AS [Included Columns], + mi.index_estimated_impact AS [Estimated Impact], + mi.create_tsql AS [Create TSQL], + mi.more_info AS [More Info], + 1 AS [Display Order], + mi.is_low, + mi.sample_query_plan AS [Sample Query Plan] + FROM #MissingIndexes AS mi + LEFT JOIN create_date AS cd + ON mi.[object_id] = cd.object_id + AND mi.database_id = cd.database_id + AND mi.schema_name = cd.schema_name + /* Minimum benefit threshold = 100k/day of uptime OR since table creation date, whichever is lower*/ + WHERE @ShowAllMissingIndexRequests=1 + OR (mi.magic_benefit_number / CASE WHEN cd.create_days < @DaysUptime THEN cd.create_days ELSE @DaysUptime END) >= 100000 + UNION ALL + SELECT + @ScriptVersionName, + N'From Your Community Volunteers' , + N'http://FirstResponderKit.org' , + 100000000000, + @DaysUptimeInsertValue, + NULL,NULL,NULL,NULL,NULL,NULL,NULL, + NULL, NULL, NULL, NULL, 0 AS [Display Order], NULL AS is_low, NULL + ORDER BY [Display Order] ASC, [Magic Benefit Number] DESC + OPTION (RECOMPILE); + END; - SET @dsql=N'SELECT DB_ID(@i_DatabaseName) AS [database_id], - @i_DatabaseName AS database_name, - t.name AS table_name, - s.name AS schema_name, - cc.name AS constraint_name, - cc.is_disabled, - cc.definition, - cc.uses_database_collation, - cc.is_not_trusted, - CASE WHEN cc.definition LIKE ''%|].|[%'' ESCAPE ''|'' THEN 1 ELSE 0 END AS is_function, - ''ALTER TABLE '' + QUOTENAME(s.name) + ''.'' + QUOTENAME(t.name) + - '' ADD CONSTRAINT '' + QUOTENAME(cc.name) + '' CHECK '' + cc.definition + '';'' COLLATE DATABASE_DEFAULT AS [column_definition] - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.check_constraints AS cc - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.tables AS t - ON t.object_id = cc.parent_object_id - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s - ON s.schema_id = t.schema_id - OPTION (RECOMPILE);'; - - INSERT #CheckConstraints - ( database_id, [database_name], table_name, schema_name, constraint_name, is_disabled, definition, - uses_database_collation, is_not_trusted, is_function, column_definition ) - EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; + IF (@BringThePain = 1 + AND @DatabaseName IS NOT NULL + AND @GetAllDatabases = 0) + BEGIN - SET @dsql=N'SELECT DB_ID(@i_DatabaseName) AS [database_id], - @i_DatabaseName AS database_name, - s.name AS missing_schema_name, - t.name AS missing_table_name, - i.name AS missing_index_name, - c.name AS missing_column_name - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.sql_expression_dependencies AS sed - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.tables AS t - ON t.object_id = sed.referenced_id - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s - ON t.schema_id = s.schema_id - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.indexes AS i - ON i.object_id = sed.referenced_id - AND i.index_id = sed.referencing_minor_id - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns AS c - ON c.object_id = sed.referenced_id - AND c.column_id = sed.referenced_minor_id - WHERE sed.referencing_class = 7 - AND sed.referenced_class = 1 - AND i.has_filter = 1 - AND NOT EXISTS ( SELECT 1/0 - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.index_columns AS ic - WHERE ic.index_id = sed.referencing_minor_id - AND ic.column_id = sed.referenced_minor_id - AND ic.object_id = sed.referenced_id ) - OPTION(RECOMPILE);' + EXEC sp_BlitzCache @SortOrder = 'sp_BlitzIndex', @DatabaseName = @DatabaseName, @BringThePain = 1, @QueryFilter = 'statement', @HideSummary = 1; + + END; - INSERT #FilteredIndexes ( database_id, database_name, schema_name, table_name, index_name, column_name ) - EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; + END; /* End @Mode=3 (index detail)*/ - END; - -END; END TRY + BEGIN CATCH - RAISERROR (N'Failure populating temp tables.', 0,1) WITH NOWAIT; + RAISERROR (N'Failure analyzing temp tables.', 0,1) WITH NOWAIT; - IF @dsql IS NOT NULL - BEGIN - SET @msg= 'Last @dsql: ' + @dsql; - RAISERROR(@msg, 0, 1) WITH NOWAIT; - END; + SELECT @msg = ERROR_MESSAGE(), @ErrorSeverity = ERROR_SEVERITY(), @ErrorState = ERROR_STATE(); - SELECT @msg = @DatabaseName + N' database failed to process. ' + ERROR_MESSAGE(), @ErrorSeverity = ERROR_SEVERITY(), @ErrorState = ERROR_STATE(); - RAISERROR (@msg,@ErrorSeverity, @ErrorState )WITH NOWAIT; - + RAISERROR (@msg, + @ErrorSeverity, + @ErrorState + ); WHILE @@trancount > 0 ROLLBACK; RETURN; -END CATCH; - FETCH NEXT FROM c1 INTO @DatabaseName; -END; -DEALLOCATE c1; - - - - - + END CATCH; +GO +IF OBJECT_ID('dbo.sp_BlitzLock') IS NULL + EXEC ('CREATE PROCEDURE dbo.sp_BlitzLock AS RETURN 0;'); +GO ----------------------------------------- ---STEP 2: PREP THE TEMP TABLES ---EVERY QUERY AFTER THIS GOES AGAINST TEMP TABLES ONLY. ----------------------------------------- +ALTER PROCEDURE dbo.sp_BlitzLock +( + @Top INT = 2147483647, + @DatabaseName NVARCHAR(256) = NULL, + @StartDate DATETIME = '19000101', + @EndDate DATETIME = '99991231', + @ObjectName NVARCHAR(1000) = NULL, + @StoredProcName NVARCHAR(1000) = NULL, + @AppName NVARCHAR(256) = NULL, + @HostName NVARCHAR(256) = NULL, + @LoginName NVARCHAR(256) = NULL, + @EventSessionPath VARCHAR(256) = 'system_health*.xel', + @VictimsOnly BIT = 0, + @Debug BIT = 0, + @Help BIT = 0, + @Version VARCHAR(30) = NULL OUTPUT, + @VersionDate DATETIME = NULL OUTPUT, + @VersionCheckMode BIT = 0, + @OutputDatabaseName NVARCHAR(256) = NULL , + @OutputSchemaName NVARCHAR(256) = 'dbo' , --ditto as below + @OutputTableName NVARCHAR(256) = 'BlitzLock', --put a standard here no need to check later in the script + @ExportToExcel BIT = 0 +) +WITH RECOMPILE +AS +BEGIN -RAISERROR (N'Updating #IndexSanity.key_column_names',0,1) WITH NOWAIT; -UPDATE #IndexSanity -SET key_column_names = D1.key_column_names -FROM #IndexSanity si - CROSS APPLY ( SELECT RTRIM(STUFF( (SELECT N', ' + c.column_name - + N' {' + system_type_name + N' ' + - CASE max_length WHEN -1 THEN N'(max)' ELSE - CASE - WHEN system_type_name IN (N'char',N'varchar',N'binary',N'varbinary') THEN N'(' + CAST(max_length AS NVARCHAR(20)) + N')' - WHEN system_type_name IN (N'nchar',N'nvarchar') THEN N'(' + CAST(max_length/2 AS NVARCHAR(20)) + N')' - ELSE '' - END - END - + N'}' - AS col_definition - FROM #IndexColumns c - WHERE c.database_id= si.database_id - AND c.schema_name = si.schema_name - AND c.object_id = si.object_id - AND c.index_id = si.index_id - AND c.is_included_column = 0 /*Just Keys*/ - AND c.key_ordinal > 0 /*Ignore non-key columns, such as partitioning keys*/ - ORDER BY c.object_id, c.index_id, c.key_ordinal - FOR XML PATH('') ,TYPE).value('.', 'nvarchar(max)'), 1, 1, '')) - ) D1 ( key_column_names ); +SET NOCOUNT ON; +SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -RAISERROR (N'Updating #IndexSanity.partition_key_column_name',0,1) WITH NOWAIT; -UPDATE #IndexSanity -SET partition_key_column_name = D1.partition_key_column_name -FROM #IndexSanity si - CROSS APPLY ( SELECT RTRIM(STUFF( (SELECT N', ' + c.column_name AS col_definition - FROM #IndexColumns c - WHERE c.database_id= si.database_id - AND c.schema_name = si.schema_name - AND c.object_id = si.object_id - AND c.index_id = si.index_id - AND c.partition_ordinal <> 0 /*Just Partitioned Keys*/ - ORDER BY c.object_id, c.index_id, c.key_ordinal - FOR XML PATH('') , TYPE).value('.', 'nvarchar(max)'), 1, 1,''))) D1 - ( partition_key_column_name ); +SELECT @Version = '8.01', @VersionDate = '20210222'; -RAISERROR (N'Updating #IndexSanity.key_column_names_with_sort_order',0,1) WITH NOWAIT; -UPDATE #IndexSanity -SET key_column_names_with_sort_order = D2.key_column_names_with_sort_order -FROM #IndexSanity si - CROSS APPLY ( SELECT RTRIM(STUFF( (SELECT N', ' + c.column_name + CASE c.is_descending_key - WHEN 1 THEN N' DESC' - ELSE N'' - END - + N' {' + system_type_name + N' ' + - CASE max_length WHEN -1 THEN N'(max)' ELSE - CASE - WHEN system_type_name IN (N'char',N'varchar',N'binary',N'varbinary') THEN N'(' + CAST(max_length AS NVARCHAR(20)) + N')' - WHEN system_type_name IN (N'nchar',N'nvarchar') THEN N'(' + CAST(max_length/2 AS NVARCHAR(20)) + N')' - ELSE '' - END - END - + N'}' - AS col_definition - FROM #IndexColumns c - WHERE c.database_id= si.database_id - AND c.schema_name = si.schema_name - AND c.object_id = si.object_id - AND c.index_id = si.index_id - AND c.is_included_column = 0 /*Just Keys*/ - AND c.key_ordinal > 0 /*Ignore non-key columns, such as partitioning keys*/ - ORDER BY c.object_id, c.index_id, c.key_ordinal - FOR XML PATH('') , TYPE).value('.', 'nvarchar(max)'), 1, 1, '')) - ) D2 ( key_column_names_with_sort_order ); -RAISERROR (N'Updating #IndexSanity.key_column_names_with_sort_order_no_types (for create tsql)',0,1) WITH NOWAIT; -UPDATE #IndexSanity -SET key_column_names_with_sort_order_no_types = D2.key_column_names_with_sort_order_no_types -FROM #IndexSanity si - CROSS APPLY ( SELECT RTRIM(STUFF( (SELECT N', ' + QUOTENAME(c.column_name) + CASE c.is_descending_key - WHEN 1 THEN N' DESC' - ELSE N'' - END AS col_definition - FROM #IndexColumns c - WHERE c.database_id= si.database_id - AND c.schema_name = si.schema_name - AND c.object_id = si.object_id - AND c.index_id = si.index_id - AND c.is_included_column = 0 /*Just Keys*/ - AND c.key_ordinal > 0 /*Ignore non-key columns, such as partitioning keys*/ - ORDER BY c.object_id, c.index_id, c.key_ordinal - FOR XML PATH('') , TYPE).value('.', 'nvarchar(max)'), 1, 1, '')) - ) D2 ( key_column_names_with_sort_order_no_types ); +IF(@VersionCheckMode = 1) +BEGIN + RETURN; +END; + IF @Help = 1 + BEGIN + PRINT ' + /* + sp_BlitzLock from http://FirstResponderKit.org + + This script checks for and analyzes deadlocks from the system health session or a custom extended event path -RAISERROR (N'Updating #IndexSanity.include_column_names',0,1) WITH NOWAIT; -UPDATE #IndexSanity -SET include_column_names = D3.include_column_names -FROM #IndexSanity si - CROSS APPLY ( SELECT RTRIM(STUFF( (SELECT N', ' + c.column_name - + N' {' + system_type_name + N' ' + CAST(max_length AS NVARCHAR(50)) + N'}' - FROM #IndexColumns c - WHERE c.database_id= si.database_id - AND c.schema_name = si.schema_name - AND c.object_id = si.object_id - AND c.index_id = si.index_id - AND c.is_included_column = 1 /*Just includes*/ - ORDER BY c.column_name /*Order doesn't matter in includes, - this is here to make rows easy to compare.*/ - FOR XML PATH('') , TYPE).value('.', 'nvarchar(max)'), 1, 1, '')) - ) D3 ( include_column_names ); + Variables you can use: + @Top: Use if you want to limit the number of deadlocks to return. + This is ordered by event date ascending -RAISERROR (N'Updating #IndexSanity.include_column_names_no_types (for create tsql)',0,1) WITH NOWAIT; -UPDATE #IndexSanity -SET include_column_names_no_types = D3.include_column_names_no_types -FROM #IndexSanity si - CROSS APPLY ( SELECT RTRIM(STUFF( (SELECT N', ' + QUOTENAME(c.column_name) - FROM #IndexColumns c - WHERE c.database_id= si.database_id - AND c.schema_name = si.schema_name - AND c.object_id = si.object_id - AND c.index_id = si.index_id - AND c.is_included_column = 1 /*Just includes*/ - ORDER BY c.column_name /*Order doesn't matter in includes, - this is here to make rows easy to compare.*/ - FOR XML PATH('') , TYPE).value('.', 'nvarchar(max)'), 1, 1, '')) - ) D3 ( include_column_names_no_types ); + @DatabaseName: If you want to filter to a specific database -RAISERROR (N'Updating #IndexSanity.count_key_columns and count_include_columns',0,1) WITH NOWAIT; -UPDATE #IndexSanity -SET count_included_columns = D4.count_included_columns, - count_key_columns = D4.count_key_columns -FROM #IndexSanity si - CROSS APPLY ( SELECT SUM(CASE WHEN is_included_column = 'true' THEN 1 - ELSE 0 - END) AS count_included_columns, - SUM(CASE WHEN is_included_column = 'false' AND c.key_ordinal > 0 THEN 1 - ELSE 0 - END) AS count_key_columns - FROM #IndexColumns c - WHERE c.database_id= si.database_id - AND c.schema_name = si.schema_name - AND c.object_id = si.object_id - AND c.index_id = si.index_id - ) AS D4 ( count_included_columns, count_key_columns ); + @StartDate: The date you want to start searching on. -RAISERROR (N'Updating index_sanity_id on #IndexPartitionSanity',0,1) WITH NOWAIT; -UPDATE #IndexPartitionSanity -SET index_sanity_id = i.index_sanity_id -FROM #IndexPartitionSanity ps - JOIN #IndexSanity i ON ps.[object_id] = i.[object_id] - AND ps.index_id = i.index_id - AND i.database_id = ps.database_id - AND i.schema_name = ps.schema_name; + @EndDate: The date you want to stop searching on. + @ObjectName: If you want to filter to a specific able. + The object name has to be fully qualified ''Database.Schema.Table'' -RAISERROR (N'Inserting data into #IndexSanitySize',0,1) WITH NOWAIT; -INSERT #IndexSanitySize ( [index_sanity_id], [database_id], [schema_name], [lock_escalation_desc], partition_count, total_rows, total_reserved_MB, - total_reserved_LOB_MB, total_reserved_row_overflow_MB, total_reserved_dictionary_MB, total_range_scan_count, - total_singleton_lookup_count, total_leaf_delete_count, total_leaf_update_count, - total_forwarded_fetch_count,total_row_lock_count, - total_row_lock_wait_count, total_row_lock_wait_in_ms, avg_row_lock_wait_in_ms, - total_page_lock_count, total_page_lock_wait_count, total_page_lock_wait_in_ms, - avg_page_lock_wait_in_ms, total_index_lock_promotion_attempt_count, - total_index_lock_promotion_count, data_compression_desc, - page_latch_wait_count, page_latch_wait_in_ms, page_io_latch_wait_count, page_io_latch_wait_in_ms) - SELECT index_sanity_id, ipp.database_id, ipp.schema_name, ipp.lock_escalation_desc, - COUNT(*), SUM(row_count), SUM(reserved_MB), - SUM(reserved_LOB_MB) - SUM(reserved_dictionary_MB), /* Subtract columnstore dictionaries from LOB data */ - SUM(reserved_row_overflow_MB), - SUM(reserved_dictionary_MB), - SUM(range_scan_count), - SUM(singleton_lookup_count), - SUM(leaf_delete_count), - SUM(leaf_update_count), - SUM(forwarded_fetch_count), - SUM(row_lock_count), - SUM(row_lock_wait_count), - SUM(row_lock_wait_in_ms), - CASE WHEN SUM(row_lock_wait_in_ms) > 0 THEN - SUM(row_lock_wait_in_ms)/(1.*SUM(row_lock_wait_count)) - ELSE 0 END AS avg_row_lock_wait_in_ms, - SUM(page_lock_count), - SUM(page_lock_wait_count), - SUM(page_lock_wait_in_ms), - CASE WHEN SUM(page_lock_wait_in_ms) > 0 THEN - SUM(page_lock_wait_in_ms)/(1.*SUM(page_lock_wait_count)) - ELSE 0 END AS avg_page_lock_wait_in_ms, - SUM(index_lock_promotion_attempt_count), - SUM(index_lock_promotion_count), - LEFT(MAX(data_compression_info.data_compression_rollup),4000), - SUM(page_latch_wait_count), - SUM(page_latch_wait_in_ms), - SUM(page_io_latch_wait_count), - SUM(page_io_latch_wait_in_ms) - FROM #IndexPartitionSanity ipp - /* individual partitions can have distinct compression settings, just roll them into a list here*/ - OUTER APPLY (SELECT STUFF(( - SELECT N', ' + data_compression_desc - FROM #IndexPartitionSanity ipp2 - WHERE ipp.[object_id]=ipp2.[object_id] - AND ipp.[index_id]=ipp2.[index_id] - AND ipp.database_id = ipp2.database_id - AND ipp.schema_name = ipp2.schema_name - ORDER BY ipp2.partition_number - FOR XML PATH(''),TYPE).value('.', 'nvarchar(max)'), 1, 1, '')) - data_compression_info(data_compression_rollup) - GROUP BY index_sanity_id, ipp.database_id, ipp.schema_name, ipp.lock_escalation_desc - ORDER BY index_sanity_id -OPTION ( RECOMPILE ); + @StoredProcName: If you want to search for a single stored proc + The proc name has to be fully qualified ''Database.Schema.Sproc'' + + @AppName: If you want to filter to a specific application + + @HostName: If you want to filter to a specific host + + @LoginName: If you want to filter to a specific login -RAISERROR (N'Determining index usefulness',0,1) WITH NOWAIT; -UPDATE #MissingIndexes -SET is_low = CASE WHEN (user_seeks + user_scans) < 5000 - OR unique_compiles = 1 - THEN 1 - ELSE 0 - END; + @EventSessionPath: If you want to point this at an XE session rather than the system health session. + + @OutputDatabaseName: If you want to output information to a specific database + @OutputSchemaName: Specify a schema name to output information to a specific Schema + @OutputTableName: Specify table name to to output information to a specific table + + To learn more, visit http://FirstResponderKit.org where you can download new + versions for free, watch training videos on how it works, get more info on + the findings, contribute your own code, and more. -RAISERROR (N'Updating #IndexSanity.referenced_by_foreign_key',0,1) WITH NOWAIT; -UPDATE #IndexSanity - SET is_referenced_by_foreign_key=1 -FROM #IndexSanity s -JOIN #ForeignKeys fk ON - s.object_id=fk.referenced_object_id - AND s.database_id=fk.database_id - AND LEFT(s.key_column_names,LEN(fk.referenced_fk_columns)) = fk.referenced_fk_columns; + Known limitations of this version: + - Only SQL Server 2012 and newer is supported + - If your tables have weird characters in them (https://en.wikipedia.org/wiki/List_of_XML_and_HTML_character_entity_references) you may get errors trying to parse the XML. + I took a long look at this one, and: + 1) Trying to account for all the weird places these could crop up is a losing effort. + 2) Replace is slow af on lots of XML. + - Your mom. -RAISERROR (N'Update index_secret on #IndexSanity for NC indexes.',0,1) WITH NOWAIT; -UPDATE nc -SET secret_columns= - N'[' + - CASE tb.count_key_columns WHEN 0 THEN '1' ELSE CAST(tb.count_key_columns AS NVARCHAR(10)) END + - CASE nc.is_unique WHEN 1 THEN N' INCLUDE' ELSE N' KEY' END + - CASE WHEN tb.count_key_columns > 1 THEN N'S] ' ELSE N'] ' END + - CASE tb.index_id WHEN 0 THEN '[RID]' ELSE LTRIM(tb.key_column_names) + - /* Uniquifiers only needed on non-unique clustereds-- not heaps */ - CASE tb.is_unique WHEN 0 THEN ' [UNIQUIFIER]' ELSE N'' END - END - , count_secret_columns= - CASE tb.index_id WHEN 0 THEN 1 ELSE - tb.count_key_columns + - CASE tb.is_unique WHEN 0 THEN 1 ELSE 0 END - END -FROM #IndexSanity AS nc -JOIN #IndexSanity AS tb ON nc.object_id=tb.object_id - AND nc.database_id = tb.database_id - AND nc.schema_name = tb.schema_name - AND tb.index_id IN (0,1) -WHERE nc.index_id > 1; -RAISERROR (N'Update index_secret on #IndexSanity for heaps and non-unique clustered.',0,1) WITH NOWAIT; -UPDATE tb -SET secret_columns= CASE tb.index_id WHEN 0 THEN '[RID]' ELSE '[UNIQUIFIER]' END - , count_secret_columns = 1 -FROM #IndexSanity AS tb -WHERE tb.index_id = 0 /*Heaps-- these have the RID */ - OR (tb.index_id=1 AND tb.is_unique=0); /* Non-unique CX: has uniquifer (when needed) */ -RAISERROR (N'Populate #IndexCreateTsql.',0,1) WITH NOWAIT; -INSERT #IndexCreateTsql (index_sanity_id, create_tsql) -SELECT - index_sanity_id, - ISNULL ( - CASE index_id WHEN 0 THEN N'ALTER TABLE ' + QUOTENAME([database_name]) + N'.' + QUOTENAME([schema_name]) + N'.' + QUOTENAME([object_name]) + ' REBUILD;' - ELSE - CASE WHEN is_XML = 1 OR is_spatial = 1 OR is_in_memory_oltp = 1 THEN N'' /* Not even trying for these just yet...*/ - ELSE - CASE WHEN is_primary_key=1 THEN - N'ALTER TABLE ' + QUOTENAME([database_name]) + N'.' + QUOTENAME([schema_name]) + - N'.' + QUOTENAME([object_name]) + - N' ADD CONSTRAINT [' + - index_name + - N'] PRIMARY KEY ' + - CASE WHEN index_id=1 THEN N'CLUSTERED (' ELSE N'(' END + - key_column_names_with_sort_order_no_types + N' )' - WHEN is_CX_columnstore= 1 THEN - N'CREATE CLUSTERED COLUMNSTORE INDEX ' + QUOTENAME(index_name) + N' on ' + QUOTENAME([database_name]) + N'.' + QUOTENAME([schema_name]) + N'.' + QUOTENAME([object_name]) - ELSE /*Else not a PK or cx columnstore */ - N'CREATE ' + - CASE WHEN is_unique=1 THEN N'UNIQUE ' ELSE N'' END + - CASE WHEN index_id=1 THEN N'CLUSTERED ' ELSE N'' END + - CASE WHEN is_NC_columnstore=1 THEN N'NONCLUSTERED COLUMNSTORE ' - ELSE N'' END + - N'INDEX [' - + index_name + N'] ON ' + - QUOTENAME([database_name]) + N'.' + - QUOTENAME([schema_name]) + N'.' + QUOTENAME([object_name]) + - CASE WHEN is_NC_columnstore=1 THEN - N' (' + ISNULL(include_column_names_no_types,'') + N' )' - ELSE /*Else not columnstore */ - N' (' + ISNULL(key_column_names_with_sort_order_no_types,'') + N' )' - + CASE WHEN include_column_names_no_types IS NOT NULL THEN - N' INCLUDE (' + include_column_names_no_types + N')' - ELSE N'' - END - END /*End non-columnstore case */ - + CASE WHEN filter_definition <> N'' THEN N' WHERE ' + filter_definition ELSE N'' END - END /*End Non-PK index CASE */ - + CASE WHEN is_NC_columnstore=0 AND is_CX_columnstore=0 THEN - N' WITH (' - + N'FILLFACTOR=' + CASE fill_factor WHEN 0 THEN N'100' ELSE CAST(fill_factor AS NVARCHAR(5)) END + ', ' - + N'ONLINE=?, SORT_IN_TEMPDB=?, DATA_COMPRESSION=?' - + N')' - ELSE N'' END - + N';' - END /*End non-spatial and non-xml CASE */ - END, '[Unknown Error]') - AS create_tsql -FROM #IndexSanity; - -RAISERROR (N'Populate #PartitionCompressionInfo.',0,1) WITH NOWAIT; -WITH maps - AS - ( - SELECT ips.index_sanity_id, - ips.partition_number, - ips.data_compression_desc, - ips.partition_number - ROW_NUMBER() OVER ( PARTITION BY ips.index_sanity_id, ips.data_compression_desc - ORDER BY ips.partition_number ) AS rn - FROM #IndexPartitionSanity AS ips - ) -SELECT * -INTO #maps -FROM maps; + Unknown limitations of this version: + - None. (If we knew them, they would be known. Duh.) -WITH grps - AS - ( - SELECT MIN(maps.partition_number) AS MinKey, - MAX(maps.partition_number) AS MaxKey, - maps.index_sanity_id, - maps.data_compression_desc - FROM #maps AS maps - GROUP BY maps.rn, maps.index_sanity_id, maps.data_compression_desc - ) -SELECT * -INTO #grps -FROM grps; -INSERT #PartitionCompressionInfo ( index_sanity_id, partition_compression_detail ) -SELECT DISTINCT - grps.index_sanity_id, - SUBSTRING( - ( STUFF( - ( SELECT N', ' + N' Partition' - + CASE - WHEN grps2.MinKey < grps2.MaxKey - THEN - + N's ' + CAST(grps2.MinKey AS NVARCHAR(10)) + N' - ' - + CAST(grps2.MaxKey AS NVARCHAR(10)) + N' use ' + grps2.data_compression_desc - ELSE - N' ' + CAST(grps2.MinKey AS NVARCHAR(10)) + N' uses ' + grps2.data_compression_desc - END AS Partitions - FROM #grps AS grps2 - WHERE grps2.index_sanity_id = grps.index_sanity_id - ORDER BY grps2.MinKey, grps2.MaxKey - FOR XML PATH(''), TYPE ).value('.', 'NVARCHAR(MAX)'), 1, 1, '')), 0, 8000) AS partition_compression_detail -FROM #grps AS grps; - -RAISERROR (N'Update #PartitionCompressionInfo.',0,1) WITH NOWAIT; -UPDATE sz -SET sz.data_compression_desc = pci.partition_compression_detail -FROM #IndexSanitySize sz -JOIN #PartitionCompressionInfo AS pci -ON pci.index_sanity_id = sz.index_sanity_id; + MIT License + + Copyright (c) 2021 Brent Ozar Unlimited -RAISERROR (N'Update #IndexSanity for filtered indexes with columns not in the index definition.',0,1) WITH NOWAIT; -UPDATE #IndexSanity -SET filter_columns_not_in_index = D1.filter_columns_not_in_index -FROM #IndexSanity si - CROSS APPLY ( SELECT RTRIM(STUFF( (SELECT N', ' + c.column_name AS col_definition - FROM #FilteredIndexes AS c - WHERE c.database_id= si.database_id - AND c.schema_name = si.schema_name - AND c.table_name = si.object_name - AND c.index_name = si.index_name - ORDER BY c.index_sanity_id - FOR XML PATH('') , TYPE).value('.', 'nvarchar(max)'), 1, 1,''))) D1 - ( filter_columns_not_in_index ); + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. -IF @Debug = 1 -BEGIN - SELECT '#IndexSanity' AS table_name, * FROM #IndexSanity; - SELECT '#IndexPartitionSanity' AS table_name, * FROM #IndexPartitionSanity; - SELECT '#IndexSanitySize' AS table_name, * FROM #IndexSanitySize; - SELECT '#IndexColumns' AS table_name, * FROM #IndexColumns; - SELECT '#MissingIndexes' AS table_name, * FROM #MissingIndexes; - SELECT '#ForeignKeys' AS table_name, * FROM #ForeignKeys; - SELECT '#BlitzIndexResults' AS table_name, * FROM #BlitzIndexResults; - SELECT '#IndexCreateTsql' AS table_name, * FROM #IndexCreateTsql; - SELECT '#DatabaseList' AS table_name, * FROM #DatabaseList; - SELECT '#Statistics' AS table_name, * FROM #Statistics; - SELECT '#PartitionCompressionInfo' AS table_name, * FROM #PartitionCompressionInfo; - SELECT '#ComputedColumns' AS table_name, * FROM #ComputedColumns; - SELECT '#TraceStatus' AS table_name, * FROM #TraceStatus; - SELECT '#CheckConstraints' AS table_name, * FROM #CheckConstraints; - SELECT '#FilteredIndexes' AS table_name, * FROM #FilteredIndexes; -END + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + */'; + RETURN; + END; /* @Help = 1 */ + + DECLARE @ProductVersion NVARCHAR(128); + DECLARE @ProductVersionMajor FLOAT; + DECLARE @ProductVersionMinor INT; ----------------------------------------- ---STEP 3: DIAGNOSE THE PATIENT ----------------------------------------- + SET @ProductVersion = CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)); + SELECT @ProductVersionMajor = SUBSTRING(@ProductVersion, 1, CHARINDEX('.', @ProductVersion) + 1), + @ProductVersionMinor = PARSENAME(CONVERT(VARCHAR(32), @ProductVersion), 2); -BEGIN TRY ----------------------------------------- ---If @TableName is specified, just return information for that table. ---The @Mode parameter doesn't matter if you're looking at a specific table. ----------------------------------------- -IF @TableName IS NOT NULL -BEGIN - RAISERROR(N'@TableName specified, giving detail only on that table.', 0,1) WITH NOWAIT; - --We do a left join here in case this is a disabled NC. - --In that case, it won't have any size info/pages allocated. - - - WITH table_mode_cte AS ( - SELECT - s.db_schema_object_indexid, - s.key_column_names, - s.index_definition, - ISNULL(s.secret_columns,N'') AS secret_columns, - s.fill_factor, - s.index_usage_summary, - sz.index_op_stats, - ISNULL(sz.index_size_summary,'') /*disabled NCs will be null*/ AS index_size_summary, - partition_compression_detail , - ISNULL(sz.index_lock_wait_summary,'') AS index_lock_wait_summary, - s.is_referenced_by_foreign_key, - (SELECT COUNT(*) - FROM #ForeignKeys fk WHERE fk.parent_object_id=s.object_id - AND PATINDEX (fk.parent_fk_columns, s.key_column_names)=1) AS FKs_covered_by_index, - s.last_user_seek, - s.last_user_scan, - s.last_user_lookup, - s.last_user_update, - s.create_date, - s.modify_date, - sz.page_latch_wait_count, - CONVERT(VARCHAR(10), (sz.page_latch_wait_in_ms / 1000) / 86400) + ':' + CONVERT(VARCHAR(20), DATEADD(s, (sz.page_latch_wait_in_ms / 1000), 0), 108) AS page_latch_wait_time, - sz.page_io_latch_wait_count, - CONVERT(VARCHAR(10), (sz.page_io_latch_wait_in_ms / 1000) / 86400) + ':' + CONVERT(VARCHAR(20), DATEADD(s, (sz.page_io_latch_wait_in_ms / 1000), 0), 108) AS page_io_latch_wait_time, - ct.create_tsql, - CASE - WHEN s.is_primary_key = 1 AND s.index_definition <> '[HEAP]' - THEN N'--ALTER TABLE ' + QUOTENAME(s.[database_name]) + N'.' + QUOTENAME(s.[schema_name]) + N'.' + QUOTENAME(s.[object_name]) - + N' DROP CONSTRAINT ' + QUOTENAME(s.index_name) + N';' - WHEN s.is_primary_key = 0 AND s.index_definition <> '[HEAP]' - THEN N'--DROP INDEX '+ QUOTENAME(s.index_name) + N' ON ' + QUOTENAME(s.[database_name]) + N'.' + - QUOTENAME(s.[schema_name]) + N'.' + QUOTENAME(s.[object_name]) + N';' - ELSE N'' - END AS drop_tsql, - 1 AS display_order - FROM #IndexSanity s - LEFT JOIN #IndexSanitySize sz ON - s.index_sanity_id=sz.index_sanity_id - LEFT JOIN #IndexCreateTsql ct ON - s.index_sanity_id=ct.index_sanity_id - LEFT JOIN #PartitionCompressionInfo pci ON - pci.index_sanity_id = s.index_sanity_id - WHERE s.[object_id]=@ObjectID - UNION ALL - SELECT N'Database ' + QUOTENAME(@DatabaseName) + N' as of ' + CONVERT(NVARCHAR(16),GETDATE(),121) + - N' (' + @ScriptVersionName + ')' , - N'SQL Server First Responder Kit' , - N'http://FirstResponderKit.org' , - N'From Your Community Volunteers', - NULL,@DaysUptimeInsertValue,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL, - 0 AS display_order - ) - SELECT - db_schema_object_indexid AS [Details: db_schema.table.index(indexid)], - index_definition AS [Definition: [Property]] ColumnName {datatype maxbytes}], - secret_columns AS [Secret Columns], - fill_factor AS [Fillfactor], - index_usage_summary AS [Usage Stats], - index_op_stats AS [Op Stats], - index_size_summary AS [Size], - partition_compression_detail AS [Compression Type], - index_lock_wait_summary AS [Lock Waits], - is_referenced_by_foreign_key AS [Referenced by FK?], - FKs_covered_by_index AS [FK Covered by Index?], - last_user_seek AS [Last User Seek], - last_user_scan AS [Last User Scan], - last_user_lookup AS [Last User Lookup], - last_user_update AS [Last User Write], - create_date AS [Created], - modify_date AS [Last Modified], - page_latch_wait_count AS [Page Latch Wait Count], - page_latch_wait_time as [Page Latch Wait Time (D:H:M:S)], - page_io_latch_wait_count AS [Page IO Latch Wait Count], - page_io_latch_wait_time as [Page IO Latch Wait Time (D:H:M:S)], - create_tsql AS [Create TSQL], - drop_tsql AS [Drop TSQL] - FROM table_mode_cte - ORDER BY display_order ASC, key_column_names ASC - OPTION ( RECOMPILE ); + IF @ProductVersionMajor < 11.0 + BEGIN + RAISERROR( + 'sp_BlitzLock will throw a bunch of angry errors on versions of SQL Server earlier than 2012.', + 0, + 1) WITH NOWAIT; + RETURN; + END; + + IF ((SELECT SERVERPROPERTY ('EDITION')) = 'SQL Azure' + AND + LOWER(@EventSessionPath) NOT LIKE 'http%') + BEGIN + RAISERROR( + 'The default storage path doesn''t work in Azure SQLDB/Managed instances. +You need to use an Azure storage account, and the path has to look like this: https://StorageAccount.blob.core.windows.net/Container/FileName.xel', + 0, + 1) WITH NOWAIT; + RETURN; + END; - IF (SELECT TOP 1 [object_id] FROM #MissingIndexes mi) IS NOT NULL - BEGIN; - WITH create_date AS ( - SELECT i.database_id, - i.schema_name, - i.[object_id], - ISNULL(NULLIF(MAX(DATEDIFF(DAY, i.create_date, SYSDATETIME())), 0), 1) AS create_days - FROM #IndexSanity AS i - GROUP BY i.database_id, i.schema_name, i.object_id - ) - SELECT N'Missing index.' AS Finding , - N'https://www.brentozar.com/go/Indexaphobia' AS URL , - mi.[statement] + - ' Est. Benefit: ' - + CASE WHEN magic_benefit_number >= 922337203685477 THEN '>= 922,337,203,685,477' - ELSE REPLACE(CONVERT(NVARCHAR(256),CAST(CAST( - (magic_benefit_number / CASE WHEN cd.create_days < @DaysUptime THEN cd.create_days ELSE @DaysUptime END) - AS BIGINT) AS MONEY), 1), '.00', '') - END AS [Estimated Benefit], - missing_index_details AS [Missing Index Request] , - index_estimated_impact AS [Estimated Impact], - create_tsql AS [Create TSQL], - sample_query_plan AS [Sample Query Plan] - FROM #MissingIndexes mi - LEFT JOIN create_date AS cd - ON mi.[object_id] = cd.object_id - AND mi.database_id = cd.database_id - AND mi.schema_name = cd.schema_name - WHERE mi.[object_id] = @ObjectID - AND (@ShowAllMissingIndexRequests=1 - /* Minimum benefit threshold = 100k/day of uptime OR since table creation date, whichever is lower*/ - OR (magic_benefit_number / CASE WHEN cd.create_days < @DaysUptime THEN cd.create_days ELSE @DaysUptime END) >= 100000) - ORDER BY magic_benefit_number DESC - OPTION ( RECOMPILE ); - END; - ELSE - SELECT 'No missing indexes.' AS finding; + IF @Top IS NULL + SET @Top = 2147483647; - SELECT - column_name AS [Column Name], - (SELECT COUNT(*) - FROM #IndexColumns c2 - WHERE c2.column_name=c.column_name - AND c2.key_ordinal IS NOT NULL) - + CASE WHEN c.index_id = 1 AND c.key_ordinal IS NOT NULL THEN - -1+ (SELECT COUNT(DISTINCT index_id) - FROM #IndexColumns c3 - WHERE c3.index_id NOT IN (0,1)) - ELSE 0 END - AS [Found In], - system_type_name + - CASE max_length WHEN -1 THEN N' (max)' ELSE - CASE - WHEN system_type_name IN (N'char',N'varchar',N'binary',N'varbinary') THEN N' (' + CAST(max_length AS NVARCHAR(20)) + N')' - WHEN system_type_name IN (N'nchar',N'nvarchar') THEN N' (' + CAST(max_length/2 AS NVARCHAR(20)) + N')' - ELSE '' - END - END - AS [Type], - CASE is_computed WHEN 1 THEN 'yes' ELSE '' END AS [Computed?], - max_length AS [Length (max bytes)], - [precision] AS [Prec], - [scale] AS [Scale], - CASE is_nullable WHEN 1 THEN 'yes' ELSE '' END AS [Nullable?], - CASE is_identity WHEN 1 THEN 'yes' ELSE '' END AS [Identity?], - CASE is_replicated WHEN 1 THEN 'yes' ELSE '' END AS [Replicated?], - CASE is_sparse WHEN 1 THEN 'yes' ELSE '' END AS [Sparse?], - CASE is_filestream WHEN 1 THEN 'yes' ELSE '' END AS [Filestream?], - collation_name AS [Collation] - FROM #IndexColumns AS c - WHERE index_id IN (0,1); + IF @StartDate IS NULL + SET @StartDate = '19000101'; - IF (SELECT TOP 1 parent_object_id FROM #ForeignKeys) IS NOT NULL - BEGIN - SELECT [database_name] + N':' + parent_object_name + N': ' + foreign_key_name AS [Foreign Key], - parent_fk_columns AS [Foreign Key Columns], - referenced_object_name AS [Referenced Table], - referenced_fk_columns AS [Referenced Table Columns], - is_disabled AS [Is Disabled?], - is_not_trusted AS [Not Trusted?], - is_not_for_replication [Not for Replication?], - [update_referential_action_desc] AS [Cascading Updates?], - [delete_referential_action_desc] AS [Cascading Deletes?] - FROM #ForeignKeys - ORDER BY [Foreign Key] - OPTION ( RECOMPILE ); - END; - ELSE - SELECT 'No foreign keys.' AS finding; + IF @EndDate IS NULL + SET @EndDate = '99991231'; + - /* Show histograms for all stats on this table. More info: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/1900 */ - IF EXISTS (SELECT * FROM sys.all_objects WHERE name = 'dm_db_stats_histogram') - BEGIN - SET @dsql=N'SELECT s.name AS [Stat Name], c.name AS [Leading Column Name], hist.step_number AS [Step Number], - hist.range_high_key AS [Range High Key], hist.range_rows AS [Range Rows], - hist.equal_rows AS [Equal Rows], hist.distinct_range_rows AS [Distinct Range Rows], hist.average_range_rows AS [Average Range Rows], - s.auto_created AS [Auto-Created], s.user_created AS [User-Created], - props.last_updated AS [Last Updated], props.modification_counter AS [Modification Counter], props.rows AS [Table Rows], - props.rows_sampled AS [Rows Sampled], s.stats_id AS [StatsID] - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.stats AS s - INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.stats_columns sc ON s.object_id = sc.object_id AND s.stats_id = sc.stats_id AND sc.stats_column_id = 1 - INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns c ON sc.object_id = c.object_id AND sc.column_id = c.column_id - CROSS APPLY ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_stats_properties(s.object_id, s.stats_id) AS props - CROSS APPLY ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_stats_histogram(s.[object_id], s.stats_id) AS hist - WHERE s.object_id = @ObjectID - ORDER BY s.auto_created, s.user_created, s.name, hist.step_number;'; - EXEC sp_executesql @dsql, N'@ObjectID INT', @ObjectID; - END + IF OBJECT_ID('tempdb..#deadlock_data') IS NOT NULL + DROP TABLE #deadlock_data; - /* Visualize columnstore index contents. More info: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2584 */ - IF 2 = (SELECT SUM(1) FROM sys.all_objects WHERE name IN ('column_store_row_groups','column_store_segments')) - BEGIN - RAISERROR(N'Visualizing columnstore index contents.', 0,1) WITH NOWAIT; + IF OBJECT_ID('tempdb..#deadlock_process') IS NOT NULL + DROP TABLE #deadlock_process; - SET @dsql = N'USE ' + QUOTENAME(@DatabaseName) + N'; - IF EXISTS(SELECT * FROM ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_row_groups WHERE object_id = @ObjectID) - BEGIN - SET @ColumnList = N''''; - WITH DistinctColumns AS ( - SELECT DISTINCT QUOTENAME(c.name) AS column_name, c.column_id - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.partitions p - INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns c ON p.object_id = c.object_id - INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.index_columns ic on ic.column_id = c.column_id and ic.object_id = c.object_id AND ic.index_id = p.index_id - INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.types t ON c.system_type_id = t.system_type_id AND c.user_type_id = t.user_type_id - WHERE p.object_id = @ObjectID - AND EXISTS (SELECT * FROM ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_segments seg WHERE p.partition_id = seg.partition_id AND seg.column_id = ic.index_column_id) - AND p.data_compression IN (3,4) - ) - SELECT @ColumnList = @ColumnList + column_name + N'', '' - FROM DistinctColumns - ORDER BY column_id; - END'; + IF OBJECT_ID('tempdb..#deadlock_stack') IS NOT NULL + DROP TABLE #deadlock_stack; - IF @Debug = 1 - BEGIN - PRINT SUBSTRING(@dsql, 0, 4000); - PRINT SUBSTRING(@dsql, 4000, 8000); - PRINT SUBSTRING(@dsql, 8000, 12000); - PRINT SUBSTRING(@dsql, 12000, 16000); - PRINT SUBSTRING(@dsql, 16000, 20000); - PRINT SUBSTRING(@dsql, 20000, 24000); - PRINT SUBSTRING(@dsql, 24000, 28000); - PRINT SUBSTRING(@dsql, 28000, 32000); - PRINT SUBSTRING(@dsql, 32000, 36000); - PRINT SUBSTRING(@dsql, 36000, 40000); - END; + IF OBJECT_ID('tempdb..#deadlock_resource') IS NOT NULL + DROP TABLE #deadlock_resource; - EXEC sp_executesql @dsql, N'@ObjectID INT, @ColumnList NVARCHAR(MAX) OUTPUT', @ObjectID, @ColumnList OUTPUT; + IF OBJECT_ID('tempdb..#deadlock_owner_waiter') IS NOT NULL + DROP TABLE #deadlock_owner_waiter; - IF @Debug = 1 - SELECT @ColumnList AS ColumnstoreColumnList; + IF OBJECT_ID('tempdb..#deadlock_findings') IS NOT NULL + DROP TABLE #deadlock_findings; - IF @ColumnList <> '' - BEGIN - /* Remove the trailing comma */ - SET @ColumnList = LEFT(@ColumnList, LEN(@ColumnList) - 1); + CREATE TABLE #deadlock_findings + ( + id INT IDENTITY(1, 1) PRIMARY KEY CLUSTERED, + check_id INT NOT NULL, + database_name NVARCHAR(256), + object_name NVARCHAR(1000), + finding_group NVARCHAR(100), + finding NVARCHAR(4000) + ); - SET @dsql = N'USE ' + QUOTENAME(@DatabaseName) + N'; SELECT partition_number, row_group_id, total_rows, deleted_rows, ' + @ColumnList + N' - FROM ( - SELECT c.name AS column_name, p.partition_number, - rg.row_group_id, rg.total_rows, rg.deleted_rows, - details = CAST(seg.min_data_id AS VARCHAR(20)) + '' to '' + CAST(seg.max_data_id AS VARCHAR(20)) + '', '' + CAST(CAST((seg.on_disk_size / 1024.0 / 1024) AS DECIMAL(18,0)) AS VARCHAR(20)) + '' MB'' - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_row_groups rg - INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns c ON rg.object_id = c.object_id - INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partitions p ON rg.object_id = p.object_id AND rg.partition_number = p.partition_number - INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.index_columns ic on ic.column_id = c.column_id AND ic.object_id = c.object_id AND ic.index_id = p.index_id - LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_segments seg ON p.partition_id = seg.partition_id AND ic.index_column_id = seg.column_id AND rg.row_group_id = seg.segment_id - WHERE rg.object_id = @ObjectID - ) AS x - PIVOT (MAX(details) FOR column_name IN ( ' + @ColumnList + N')) AS pivot1 - ORDER BY partition_number, row_group_id;'; - - IF @Debug = 1 + DECLARE @d VARCHAR(40), @StringToExecute NVARCHAR(4000),@StringToExecuteParams NVARCHAR(500),@r NVARCHAR(200),@OutputTableFindings NVARCHAR(100),@DeadlockCount INT; + DECLARE @ServerName NVARCHAR(256) + DECLARE @OutputDatabaseCheck BIT; + SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + SET @OutputTableFindings = '[BlitzLockFindings]' + SET @ServerName = (select @@ServerName) + if(@OutputDatabaseName is not null) + BEGIN --if databaseName is set do some sanity checks and put [] around def. + if( (select name from sys.databases where name=@OutputDatabaseName) is null ) --if database is invalid raiserror and set bitcheck BEGIN - PRINT SUBSTRING(@dsql, 0, 4000); - PRINT SUBSTRING(@dsql, 4000, 8000); - PRINT SUBSTRING(@dsql, 8000, 12000); - PRINT SUBSTRING(@dsql, 12000, 16000); - PRINT SUBSTRING(@dsql, 16000, 20000); - PRINT SUBSTRING(@dsql, 20000, 24000); - PRINT SUBSTRING(@dsql, 24000, 28000); - PRINT SUBSTRING(@dsql, 28000, 32000); - PRINT SUBSTRING(@dsql, 32000, 36000); - PRINT SUBSTRING(@dsql, 36000, 40000); - END; - - IF @dsql IS NULL - RAISERROR('@dsql is null',16,1); + RAISERROR('Database Name for output of table is invalid please correct, Output to Table will not be preformed', 0, 1, @d) WITH NOWAIT; + set @OutputDatabaseCheck = -1 -- -1 invalid/false, 0 = good/true + END ELSE - EXEC sp_executesql @dsql, N'@ObjectID INT', @ObjectID; - END - ELSE /* No columns were found for this object */ - BEGIN - SELECT N'No compressed columnstore rowgroups were found for this object.' AS Columnstore_Visualization - UNION ALL - SELECT N'SELECT * FROM ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_row_groups WHERE object_id = ' + CAST(@ObjectID AS NVARCHAR(100)); + BEGIN + set @OutputDatabaseCheck = 0 + select @StringToExecute = N'select @r = name from ' + '' + @OutputDatabaseName + + '' + '.sys.objects where type_desc=''USER_TABLE'' and name=' + '''' + @OutputTableName + '''', + @StringToExecuteParams = N'@OutputDatabaseName NVARCHAR(200),@OutputTableName NVARCHAR(200),@r NVARCHAR(200) OUTPUT' + exec sp_executesql @StringToExecute,@StringToExecuteParams,@OutputDatabaseName,@OutputTableName,@r OUTPUT + --put covers around all before. + SELECT @OutputDatabaseName = QUOTENAME(@OutputDatabaseName), + @OutputTableName = QUOTENAME(@OutputTableName), + @OutputSchemaName = QUOTENAME(@OutputSchemaName) + if(@r is null) --if it is null there is no table, create it from above execution + BEGIN + select @StringToExecute = N'use ' + @OutputDatabaseName + ';create table ' + @OutputSchemaName + '.' + @OutputTableName + ' ( + ServerName NVARCHAR(256), + deadlock_type NVARCHAR(256), + event_date datetime, + database_name NVARCHAR(256), + deadlock_group NVARCHAR(256), + query XML, + object_names XML, + isolation_level NVARCHAR(256), + owner_mode NVARCHAR(256), + waiter_mode NVARCHAR(256), + transaction_count bigint, + login_name NVARCHAR(256), + host_name NVARCHAR(256), + client_app NVARCHAR(256), + wait_time BIGINT, + priority smallint, + log_used BIGINT, + last_tran_started datetime, + last_batch_started datetime, + last_batch_completed datetime, + transaction_name NVARCHAR(256), + owner_waiter_type NVARCHAR(256), + owner_activity NVARCHAR(256), + owner_waiter_activity NVARCHAR(256), + owner_merging NVARCHAR(256), + owner_spilling NVARCHAR(256), + owner_waiting_to_close NVARCHAR(256), + waiter_waiter_type NVARCHAR(256), + waiter_owner_activity NVARCHAR(256), + waiter_waiter_activity NVARCHAR(256), + waiter_merging NVARCHAR(256), + waiter_spilling NVARCHAR(256), + waiter_waiting_to_close NVARCHAR(256), + deadlock_graph XML)', + @StringToExecuteParams = N'@OutputDatabaseName NVARCHAR(200),@OutputSchemaName NVARCHAR(100),@OutputTableName NVARCHAR(200)' + exec sp_executesql @StringToExecute, @StringToExecuteParams,@OutputDatabaseName,@OutputSchemaName,@OutputTableName + --table created. + select @StringToExecute = N'select @r = name from ' + '' + @OutputDatabaseName + + '' + '.sys.objects where type_desc=''USER_TABLE'' and name=''BlitzLockFindings''', + @StringToExecuteParams = N'@OutputDatabaseName NVARCHAR(200),@r NVARCHAR(200) OUTPUT' + exec sp_executesql @StringToExecute,@StringToExecuteParams,@OutputDatabaseName,@r OUTPUT + if(@r is null) --if table does not excist + BEGIN + select @OutputTableFindings=N'[BlitzLockFindings]', + @StringToExecute = N'use ' + @OutputDatabaseName + ';create table ' + @OutputSchemaName + '.' + @OutputTableFindings + ' ( + ServerName NVARCHAR(256), + check_id INT, + database_name NVARCHAR(256), + object_name NVARCHAR(1000), + finding_group NVARCHAR(100), + finding NVARCHAR(4000))', + @StringToExecuteParams = N'@OutputDatabaseName NVARCHAR(200),@OutputSchemaName NVARCHAR(100),@OutputTableFindings NVARCHAR(200)' + exec sp_executesql @StringToExecute, @StringToExecuteParams, @OutputDatabaseName,@OutputSchemaName,@OutputTableFindings + + END + + END + --create synonym for deadlockfindings. + if((select name from sys.objects where name='DeadlockFindings' and type_desc='SYNONYM')IS NOT NULL) + BEGIN + RAISERROR('found synonym', 0, 1) WITH NOWAIT; + drop synonym DeadlockFindings; + END + set @StringToExecute = 'CREATE SYNONYM DeadlockFindings FOR ' + @OutputDatabaseName + '.' + @OutputSchemaName + '.' + @OutputTableFindings; + exec sp_executesql @StringToExecute + + --create synonym for deadlock table. + if((select name from sys.objects where name='DeadLockTbl' and type_desc='SYNONYM') IS NOT NULL) + BEGIN + drop SYNONYM DeadLockTbl; + END + set @StringToExecute = 'CREATE SYNONYM DeadLockTbl FOR ' + @OutputDatabaseName + '.' + @OutputSchemaName + '.' + @OutputTableName; + exec sp_executesql @StringToExecute + + END END - RAISERROR(N'Done visualizing columnstore index contents.', 0,1) WITH NOWAIT; - END + -END; /* IF @TableName IS NOT NULL */ + CREATE TABLE #t (id INT NOT NULL); + + /* WITH ROWCOUNT doesn't work on Amazon RDS - see: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2037 */ + IF LEFT(CAST(SERVERPROPERTY('ComputerNamePhysicalNetBIOS') AS VARCHAR(8000)), 8) <> 'EC2AMAZ-' + AND LEFT(CAST(SERVERPROPERTY('MachineName') AS VARCHAR(8000)), 8) <> 'EC2AMAZ-' + AND LEFT(CAST(SERVERPROPERTY('ServerName') AS VARCHAR(8000)), 8) <> 'EC2AMAZ-' + AND db_id('rdsadmin') IS NULL + BEGIN; + BEGIN TRY; + UPDATE STATISTICS #t WITH ROWCOUNT = 100000000, PAGECOUNT = 100000000; + END TRY + BEGIN CATCH; + /* Misleading error returned, if run without permissions to update statistics the error returned is "Cannot find object". + Catching specific error, and returning message with better info. If any other error is returned, then throw as normal */ + IF (ERROR_NUMBER() = 1088) + BEGIN; + SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + RAISERROR('Cannot run UPDATE STATISTICS on temp table without db_owner or sysadmin permissions %s', 0, 1, @d) WITH NOWAIT; + END; + ELSE + BEGIN; + THROW; + END; + END CATCH; + END; + /*Grab the initial set of XML to parse*/ + SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + RAISERROR('Grab the initial set of XML to parse at %s', 0, 1, @d) WITH NOWAIT; + WITH xml + AS ( SELECT CONVERT(XML, event_data) AS deadlock_xml + FROM sys.fn_xe_file_target_read_file(@EventSessionPath, NULL, NULL, NULL) ) + SELECT ISNULL(xml.deadlock_xml, '') AS deadlock_xml + INTO #deadlock_data + FROM xml + LEFT JOIN #t AS t + ON 1 = 1 + CROSS APPLY xml.deadlock_xml.nodes('/event/@name') AS x(c) + WHERE 1 = 1 + AND x.c.exist('/event/@name[ .= "xml_deadlock_report"]') = 1 + AND CONVERT(datetime, SWITCHOFFSET(CONVERT(datetimeoffset, xml.deadlock_xml.value('(/event/@timestamp)[1]', 'datetime') ), DATENAME(TzOffset, SYSDATETIMEOFFSET()))) > @StartDate + AND CONVERT(datetime, SWITCHOFFSET(CONVERT(datetimeoffset, xml.deadlock_xml.value('(/event/@timestamp)[1]', 'datetime') ), DATENAME(TzOffset, SYSDATETIMEOFFSET()))) < @EndDate + ORDER BY xml.deadlock_xml.value('(/event/@timestamp)[1]', 'datetime') DESC + OPTION ( RECOMPILE ); + /*Optimization: if we got back more rows than @Top, remove them. This seems to be better than using @Top in the query above as that results in excessive memory grant*/ + SET @DeadlockCount = @@ROWCOUNT + IF( @Top < @DeadlockCount ) BEGIN + WITH T + AS ( + SELECT TOP ( @DeadlockCount - @Top) * + FROM #deadlock_data + ORDER BY #deadlock_data.deadlock_xml.value('(/event/@timestamp)[1]', 'datetime') ASC) + DELETE FROM T + END + /*Parse process and input buffer XML*/ + SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + RAISERROR('Parse process and input buffer XML %s', 0, 1, @d) WITH NOWAIT; + SELECT q.event_date, + q.victim_id, + CONVERT(BIT, q.is_parallel) AS is_parallel, + q.deadlock_graph, + q.id, + q.database_id, + q.priority, + q.log_used, + q.wait_resource, + q.wait_time, + q.transaction_name, + q.last_tran_started, + q.last_batch_started, + q.last_batch_completed, + q.lock_mode, + q.transaction_count, + q.client_app, + q.host_name, + q.login_name, + q.isolation_level, + q.process_xml, + ISNULL(ca2.ib.query('.'), '') AS input_buffer + INTO #deadlock_process + FROM ( SELECT dd.deadlock_xml, + CONVERT(DATETIME2(7), SWITCHOFFSET(CONVERT(datetimeoffset, dd.event_date ), DATENAME(TzOffset, SYSDATETIMEOFFSET()))) AS event_date, + dd.victim_id, + CONVERT(tinyint, dd.is_parallel) + CONVERT(tinyint, dd.is_parallel_batch) AS is_parallel, + dd.deadlock_graph, + ca.dp.value('@id', 'NVARCHAR(256)') AS id, + ca.dp.value('@currentdb', 'BIGINT') AS database_id, + ca.dp.value('@priority', 'SMALLINT') AS priority, + ca.dp.value('@logused', 'BIGINT') AS log_used, + ca.dp.value('@waitresource', 'NVARCHAR(256)') AS wait_resource, + ca.dp.value('@waittime', 'BIGINT') AS wait_time, + ca.dp.value('@transactionname', 'NVARCHAR(256)') AS transaction_name, + ca.dp.value('@lasttranstarted', 'DATETIME2(7)') AS last_tran_started, + ca.dp.value('@lastbatchstarted', 'DATETIME2(7)') AS last_batch_started, + ca.dp.value('@lastbatchcompleted', 'DATETIME2(7)') AS last_batch_completed, + ca.dp.value('@lockMode', 'NVARCHAR(256)') AS lock_mode, + ca.dp.value('@trancount', 'BIGINT') AS transaction_count, + ca.dp.value('@clientapp', 'NVARCHAR(256)') AS client_app, + ca.dp.value('@hostname', 'NVARCHAR(256)') AS host_name, + ca.dp.value('@loginname', 'NVARCHAR(256)') AS login_name, + ca.dp.value('@isolationlevel', 'NVARCHAR(256)') AS isolation_level, + ISNULL(ca.dp.query('.'), '') AS process_xml + FROM ( SELECT d1.deadlock_xml, + d1.deadlock_xml.value('(event/@timestamp)[1]', 'DATETIME2') AS event_date, + d1.deadlock_xml.value('(//deadlock/victim-list/victimProcess/@id)[1]', 'NVARCHAR(256)') AS victim_id, + d1.deadlock_xml.exist('//deadlock/resource-list/exchangeEvent') AS is_parallel, + d1.deadlock_xml.exist('//deadlock/resource-list/SyncPoint') AS is_parallel_batch, + d1.deadlock_xml.query('/event/data/value/deadlock') AS deadlock_graph + FROM #deadlock_data AS d1 ) AS dd + CROSS APPLY dd.deadlock_xml.nodes('//deadlock/process-list/process') AS ca(dp) + WHERE (ca.dp.value('@currentdb', 'BIGINT') = DB_ID(@DatabaseName) OR @DatabaseName IS NULL) + AND (ca.dp.value('@clientapp', 'NVARCHAR(256)') = @AppName OR @AppName IS NULL) + AND (ca.dp.value('@hostname', 'NVARCHAR(256)') = @HostName OR @HostName IS NULL) + AND (ca.dp.value('@loginname', 'NVARCHAR(256)') = @LoginName OR @LoginName IS NULL) + ) AS q + CROSS APPLY q.deadlock_xml.nodes('//deadlock/process-list/process/inputbuf') AS ca2(ib) + OPTION ( RECOMPILE ); + /*Parse execution stack XML*/ + SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + RAISERROR('Parse execution stack XML %s', 0, 1, @d) WITH NOWAIT; + SELECT DISTINCT + dp.id, + dp.event_date, + ca.dp.value('@procname', 'NVARCHAR(1000)') AS proc_name, + ca.dp.value('@sqlhandle', 'NVARCHAR(128)') AS sql_handle + INTO #deadlock_stack + FROM #deadlock_process AS dp + CROSS APPLY dp.process_xml.nodes('//executionStack/frame') AS ca(dp) + WHERE (ca.dp.value('@procname', 'NVARCHAR(256)') = @StoredProcName OR @StoredProcName IS NULL) + OPTION ( RECOMPILE ); + /*Grab the full resource list*/ + SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + RAISERROR('Grab the full resource list %s', 0, 1, @d) WITH NOWAIT; + SELECT + CONVERT(DATETIME2(7), SWITCHOFFSET(CONVERT(datetimeoffset, dr.event_date), DATENAME(TzOffset, SYSDATETIMEOFFSET()))) AS event_date, + dr.victim_id, + dr.resource_xml + INTO #deadlock_resource + FROM + ( + SELECT dd.deadlock_xml.value('(event/@timestamp)[1]', 'DATETIME2') AS event_date, + dd.deadlock_xml.value('(//deadlock/victim-list/victimProcess/@id)[1]', 'NVARCHAR(256)') AS victim_id, + ISNULL(ca.dp.query('.'), '') AS resource_xml + FROM #deadlock_data AS dd + CROSS APPLY dd.deadlock_xml.nodes('//deadlock/resource-list') AS ca(dp) + ) AS dr + OPTION ( RECOMPILE ); + /*Parse object locks*/ + SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + RAISERROR('Parse object locks %s', 0, 1, @d) WITH NOWAIT; + SELECT DISTINCT + ca.event_date, + ca.database_id, + ca.object_name, + ca.lock_mode, + ca.index_name, + ca.associatedObjectId, + w.l.value('@id', 'NVARCHAR(256)') AS waiter_id, + w.l.value('@mode', 'NVARCHAR(256)') AS waiter_mode, + o.l.value('@id', 'NVARCHAR(256)') AS owner_id, + o.l.value('@mode', 'NVARCHAR(256)') AS owner_mode, + N'OBJECT' AS lock_type + INTO #deadlock_owner_waiter + FROM ( + SELECT dr.event_date, + ca.dr.value('@dbid', 'BIGINT') AS database_id, + ca.dr.value('@objectname', 'NVARCHAR(256)') AS object_name, + ca.dr.value('@mode', 'NVARCHAR(256)') AS lock_mode, + ca.dr.value('@indexname', 'NVARCHAR(256)') AS index_name, + ca.dr.value('@associatedObjectId', 'BIGINT') AS associatedObjectId, + ca.dr.query('.') AS dr + FROM #deadlock_resource AS dr + CROSS APPLY dr.resource_xml.nodes('//resource-list/objectlock') AS ca(dr) + ) AS ca + CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l) + CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l) + WHERE (ca.object_name = @ObjectName OR @ObjectName IS NULL) + OPTION ( RECOMPILE ); + /*Parse page locks*/ + SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + RAISERROR('Parse page locks %s', 0, 1, @d) WITH NOWAIT; + INSERT #deadlock_owner_waiter WITH(TABLOCKX) + SELECT DISTINCT + ca.event_date, + ca.database_id, + ca.object_name, + ca.lock_mode, + ca.index_name, + ca.associatedObjectId, + w.l.value('@id', 'NVARCHAR(256)') AS waiter_id, + w.l.value('@mode', 'NVARCHAR(256)') AS waiter_mode, + o.l.value('@id', 'NVARCHAR(256)') AS owner_id, + o.l.value('@mode', 'NVARCHAR(256)') AS owner_mode, + N'PAGE' AS lock_type + FROM ( + SELECT dr.event_date, + ca.dr.value('@dbid', 'BIGINT') AS database_id, + ca.dr.value('@objectname', 'NVARCHAR(256)') AS object_name, + ca.dr.value('@mode', 'NVARCHAR(256)') AS lock_mode, + ca.dr.value('@indexname', 'NVARCHAR(256)') AS index_name, + ca.dr.value('@associatedObjectId', 'BIGINT') AS associatedObjectId, + ca.dr.query('.') AS dr + FROM #deadlock_resource AS dr + CROSS APPLY dr.resource_xml.nodes('//resource-list/pagelock') AS ca(dr) + ) AS ca + CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l) + CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l) + OPTION ( RECOMPILE ); + /*Parse key locks*/ + SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + RAISERROR('Parse key locks %s', 0, 1, @d) WITH NOWAIT; + INSERT #deadlock_owner_waiter WITH(TABLOCKX) + SELECT DISTINCT + ca.event_date, + ca.database_id, + ca.object_name, + ca.lock_mode, + ca.index_name, + ca.associatedObjectId, + w.l.value('@id', 'NVARCHAR(256)') AS waiter_id, + w.l.value('@mode', 'NVARCHAR(256)') AS waiter_mode, + o.l.value('@id', 'NVARCHAR(256)') AS owner_id, + o.l.value('@mode', 'NVARCHAR(256)') AS owner_mode, + N'KEY' AS lock_type + FROM ( + SELECT dr.event_date, + ca.dr.value('@dbid', 'BIGINT') AS database_id, + ca.dr.value('@objectname', 'NVARCHAR(256)') AS object_name, + ca.dr.value('@mode', 'NVARCHAR(256)') AS lock_mode, + ca.dr.value('@indexname', 'NVARCHAR(256)') AS index_name, + ca.dr.value('@associatedObjectId', 'BIGINT') AS associatedObjectId, + ca.dr.query('.') AS dr + FROM #deadlock_resource AS dr + CROSS APPLY dr.resource_xml.nodes('//resource-list/keylock') AS ca(dr) + ) AS ca + CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l) + CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l) + OPTION ( RECOMPILE ); + /*Parse RID locks*/ + SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + RAISERROR('Parse RID locks %s', 0, 1, @d) WITH NOWAIT; + INSERT #deadlock_owner_waiter WITH(TABLOCKX) + SELECT DISTINCT + ca.event_date, + ca.database_id, + ca.object_name, + ca.lock_mode, + ca.index_name, + ca.associatedObjectId, + w.l.value('@id', 'NVARCHAR(256)') AS waiter_id, + w.l.value('@mode', 'NVARCHAR(256)') AS waiter_mode, + o.l.value('@id', 'NVARCHAR(256)') AS owner_id, + o.l.value('@mode', 'NVARCHAR(256)') AS owner_mode, + N'RID' AS lock_type + FROM ( + SELECT dr.event_date, + ca.dr.value('@dbid', 'BIGINT') AS database_id, + ca.dr.value('@objectname', 'NVARCHAR(256)') AS object_name, + ca.dr.value('@mode', 'NVARCHAR(256)') AS lock_mode, + ca.dr.value('@indexname', 'NVARCHAR(256)') AS index_name, + ca.dr.value('@associatedObjectId', 'BIGINT') AS associatedObjectId, + ca.dr.query('.') AS dr + FROM #deadlock_resource AS dr + CROSS APPLY dr.resource_xml.nodes('//resource-list/ridlock') AS ca(dr) + ) AS ca + CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l) + CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l) + OPTION ( RECOMPILE ); + /*Parse row group locks*/ + SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + RAISERROR('Parse row group locks %s', 0, 1, @d) WITH NOWAIT; + INSERT #deadlock_owner_waiter WITH(TABLOCKX) + SELECT DISTINCT + ca.event_date, + ca.database_id, + ca.object_name, + ca.lock_mode, + ca.index_name, + ca.associatedObjectId, + w.l.value('@id', 'NVARCHAR(256)') AS waiter_id, + w.l.value('@mode', 'NVARCHAR(256)') AS waiter_mode, + o.l.value('@id', 'NVARCHAR(256)') AS owner_id, + o.l.value('@mode', 'NVARCHAR(256)') AS owner_mode, + N'ROWGROUP' AS lock_type + FROM ( + SELECT dr.event_date, + ca.dr.value('@dbid', 'BIGINT') AS database_id, + ca.dr.value('@objectname', 'NVARCHAR(256)') AS object_name, + ca.dr.value('@mode', 'NVARCHAR(256)') AS lock_mode, + ca.dr.value('@indexname', 'NVARCHAR(256)') AS index_name, + ca.dr.value('@associatedObjectId', 'BIGINT') AS associatedObjectId, + ca.dr.query('.') AS dr + FROM #deadlock_resource AS dr + CROSS APPLY dr.resource_xml.nodes('//resource-list/rowgrouplock') AS ca(dr) + ) AS ca + CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l) + CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l) + OPTION ( RECOMPILE ); + UPDATE d + SET d.index_name = d.object_name + + '.HEAP' + FROM #deadlock_owner_waiter AS d + WHERE lock_type IN (N'HEAP', N'RID') + OPTION(RECOMPILE); + /*Parse parallel deadlocks*/ + SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + RAISERROR('Parse parallel deadlocks %s', 0, 1, @d) WITH NOWAIT; + SELECT DISTINCT + ca.id, + ca.event_date, + ca.wait_type, + ca.node_id, + ca.waiter_type, + ca.owner_activity, + ca.waiter_activity, + ca.merging, + ca.spilling, + ca.waiting_to_close, + w.l.value('@id', 'NVARCHAR(256)') AS waiter_id, + o.l.value('@id', 'NVARCHAR(256)') AS owner_id + INTO #deadlock_resource_parallel + FROM ( + SELECT dr.event_date, + ca.dr.value('@id', 'NVARCHAR(256)') AS id, + ca.dr.value('@WaitType', 'NVARCHAR(256)') AS wait_type, + ca.dr.value('@nodeId', 'BIGINT') AS node_id, + /* These columns are in 2017 CU5 ONLY */ + ca.dr.value('@waiterType', 'NVARCHAR(256)') AS waiter_type, + ca.dr.value('@ownerActivity', 'NVARCHAR(256)') AS owner_activity, + ca.dr.value('@waiterActivity', 'NVARCHAR(256)') AS waiter_activity, + ca.dr.value('@merging', 'NVARCHAR(256)') AS merging, + ca.dr.value('@spilling', 'NVARCHAR(256)') AS spilling, + ca.dr.value('@waitingToClose', 'NVARCHAR(256)') AS waiting_to_close, + /* */ + ca.dr.query('.') AS dr + FROM #deadlock_resource AS dr + CROSS APPLY dr.resource_xml.nodes('//resource-list/exchangeEvent') AS ca(dr) + ) AS ca + CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l) + CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l) + OPTION ( RECOMPILE ); + /*Get rid of parallel noise*/ + SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + RAISERROR('Get rid of parallel noise %s', 0, 1, @d) WITH NOWAIT; + WITH c + AS + ( + SELECT *, ROW_NUMBER() OVER ( PARTITION BY drp.owner_id, drp.waiter_id ORDER BY drp.event_date ) AS rn + FROM #deadlock_resource_parallel AS drp + ) + DELETE FROM c + WHERE c.rn > 1 + OPTION ( RECOMPILE ); + /*Get rid of nonsense*/ + SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + RAISERROR('Get rid of nonsense %s', 0, 1, @d) WITH NOWAIT; + DELETE dow + FROM #deadlock_owner_waiter AS dow + WHERE dow.owner_id = dow.waiter_id + OPTION ( RECOMPILE ); + /*Add some nonsense*/ + ALTER TABLE #deadlock_process + ADD waiter_mode NVARCHAR(256), + owner_mode NVARCHAR(256), + is_victim AS CONVERT(BIT, CASE WHEN id = victim_id THEN 1 ELSE 0 END); + /*Update some nonsense*/ + SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + RAISERROR('Update some nonsense part 1 %s', 0, 1, @d) WITH NOWAIT; + UPDATE dp + SET dp.owner_mode = dow.owner_mode + FROM #deadlock_process AS dp + JOIN #deadlock_owner_waiter AS dow + ON dp.id = dow.owner_id + AND dp.event_date = dow.event_date + WHERE dp.is_victim = 0 + OPTION ( RECOMPILE ); + SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + RAISERROR('Update some nonsense part 2 %s', 0, 1, @d) WITH NOWAIT; + UPDATE dp + SET dp.waiter_mode = dow.waiter_mode + FROM #deadlock_process AS dp + JOIN #deadlock_owner_waiter AS dow + ON dp.victim_id = dow.waiter_id + AND dp.event_date = dow.event_date + WHERE dp.is_victim = 1 + OPTION ( RECOMPILE ); + /*Get Agent Job and Step names*/ + SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + RAISERROR('Get Agent Job and Step names %s', 0, 1, @d) WITH NOWAIT; + SELECT *, + CONVERT(UNIQUEIDENTIFIER, + CONVERT(XML, '').value('xs:hexBinary(substring(sql:column("x.job_id"), 0) )', 'BINARY(16)') + ) AS job_id_guid + INTO #agent_job + FROM ( + SELECT dp.event_date, + dp.victim_id, + dp.id, + dp.database_id, + dp.client_app, + SUBSTRING(dp.client_app, + CHARINDEX('0x', dp.client_app) + LEN('0x'), + 32 + ) AS job_id, + SUBSTRING(dp.client_app, + CHARINDEX(': Step ', dp.client_app) + LEN(': Step '), + CHARINDEX(')', dp.client_app, CHARINDEX(': Step ', dp.client_app)) + - (CHARINDEX(': Step ', dp.client_app) + + LEN(': Step ')) + ) AS step_id + FROM #deadlock_process AS dp + WHERE dp.client_app LIKE 'SQLAgent - %' + ) AS x + OPTION ( RECOMPILE ); + ALTER TABLE #agent_job ADD job_name NVARCHAR(256), + step_name NVARCHAR(256); -ELSE IF @Mode IN (0, 4) /* DIAGNOSE*//* IF @TableName IS NOT NULL, so @TableName must not be null */ -BEGIN; - IF @Mode IN (0, 4) /* DIAGNOSE priorities 1-100 */ - BEGIN; - RAISERROR(N'@Mode=0 or 4, running rules for priorities 1-100.', 0,1) WITH NOWAIT; + IF SERVERPROPERTY('EngineEdition') NOT IN (5, 6) /* Azure SQL DB doesn't support querying jobs */ + AND NOT (LEFT(CAST(SERVERPROPERTY('ComputerNamePhysicalNetBIOS') AS VARCHAR(8000)), 8) = 'EC2AMAZ-' /* Neither does Amazon RDS Express Edition */ + AND LEFT(CAST(SERVERPROPERTY('MachineName') AS VARCHAR(8000)), 8) = 'EC2AMAZ-' + AND LEFT(CAST(SERVERPROPERTY('ServerName') AS VARCHAR(8000)), 8) = 'EC2AMAZ-' + AND db_id('rdsadmin') IS NOT NULL + AND EXISTS(SELECT * FROM master.sys.all_objects WHERE name IN ('rds_startup_tasks', 'rds_help_revlogin', 'rds_hexadecimal', 'rds_failover_tracking', 'rds_database_tracking', 'rds_track_change')) + ) + BEGIN + SET @StringToExecute = N'UPDATE aj + SET aj.job_name = j.name, + aj.step_name = s.step_name + FROM msdb.dbo.sysjobs AS j + JOIN msdb.dbo.sysjobsteps AS s + ON j.job_id = s.job_id + JOIN #agent_job AS aj + ON aj.job_id_guid = j.job_id + AND aj.step_id = s.step_id + OPTION ( RECOMPILE );'; + EXEC(@StringToExecute); + END - ---------------------------------------- - --Multiple Index Personalities: Check_id 0-10 - ---------------------------------------- - RAISERROR('check_id 1: Duplicate keys', 0,1) WITH NOWAIT; - WITH duplicate_indexes - AS ( SELECT [object_id], key_column_names, database_id, [schema_name] - FROM #IndexSanity AS ip - WHERE index_type IN (1,2) /* Clustered, NC only*/ - AND is_hypothetical = 0 - AND is_disabled = 0 - AND is_primary_key = 0 - AND EXISTS ( - SELECT 1/0 - FROM #IndexSanitySize ips - WHERE ip.index_sanity_id = ips.index_sanity_id - AND ip.database_id = ips.database_id - AND ip.schema_name = ips.schema_name - AND ips.total_reserved_MB >= CASE - WHEN (@GetAllDatabases = 1 OR @Mode = 0) - THEN @ThresholdMB - ELSE ips.total_reserved_MB - END - ) - GROUP BY [object_id], key_column_names, database_id, [schema_name] - HAVING COUNT(*) > 1) - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 1 AS check_id, - ip.index_sanity_id, - 20 AS Priority, - 'Multiple Index Personalities' AS findings_group, - 'Duplicate keys' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/duplicateindex' AS URL, - N'Index Name: ' + ip.index_name + N' Table Name: ' + ip.db_schema_object_name AS details, - ip.index_definition, - ip.secret_columns, - ip.index_usage_summary, - ips.index_size_summary - FROM duplicate_indexes di - JOIN #IndexSanity ip ON di.[object_id] = ip.[object_id] - AND ip.database_id = di.database_id - AND ip.[schema_name] = di.[schema_name] - AND di.key_column_names = ip.key_column_names - JOIN #IndexSanitySize ips ON ip.index_sanity_id = ips.index_sanity_id - AND ip.database_id = ips.database_id - AND ip.schema_name = ips.schema_name - /* WHERE clause limits to only @ThresholdMB or larger duplicate indexes when getting all databases or using PainRelief mode */ - WHERE ips.total_reserved_MB >= CASE WHEN (@GetAllDatabases = 1 OR @Mode = 0) THEN @ThresholdMB ELSE ips.total_reserved_MB END - AND ip.is_primary_key = 0 - ORDER BY ip.object_id, ip.key_column_names_with_sort_order - OPTION ( RECOMPILE ); + UPDATE dp + SET dp.client_app = + CASE WHEN dp.client_app LIKE N'SQLAgent - %' + THEN N'SQLAgent - Job: ' + + aj.job_name + + N' Step: ' + + aj.step_name + ELSE dp.client_app + END + FROM #deadlock_process AS dp + JOIN #agent_job AS aj + ON dp.event_date = aj.event_date + AND dp.victim_id = aj.victim_id + AND dp.id = aj.id + OPTION ( RECOMPILE ); - RAISERROR('check_id 2: Keys w/ identical leading columns.', 0,1) WITH NOWAIT; - WITH borderline_duplicate_indexes - AS ( SELECT DISTINCT database_id, [object_id], first_key_column_name, key_column_names, - COUNT([object_id]) OVER ( PARTITION BY database_id, [object_id], first_key_column_name ) AS number_dupes - FROM #IndexSanity - WHERE index_type IN (1,2) /* Clustered, NC only*/ - AND is_hypothetical=0 - AND is_disabled=0 - AND is_primary_key = 0) - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 2 AS check_id, - ip.index_sanity_id, - 30 AS Priority, - 'Multiple Index Personalities' AS findings_group, - 'Borderline duplicate keys' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/duplicateindex' AS URL, - ip.db_schema_object_indexid AS details, - ip.index_definition, - ip.secret_columns, - ip.index_usage_summary, - ips.index_size_summary - FROM #IndexSanity AS ip - JOIN #IndexSanitySize ips ON ip.index_sanity_id = ips.index_sanity_id - WHERE EXISTS ( - SELECT di.[object_id] - FROM borderline_duplicate_indexes AS di - WHERE di.[object_id] = ip.[object_id] AND - di.database_id = ip.database_id AND - di.first_key_column_name = ip.first_key_column_name AND - di.key_column_names <> ip.key_column_names AND - di.number_dupes > 1 - ) - AND ip.is_primary_key = 0 - ORDER BY ip.[schema_name], ip.[object_name], ip.key_column_names, ip.include_column_names - OPTION ( RECOMPILE ); + /*Get each and every table of all databases*/ + DECLARE @sysAssObjId AS TABLE (database_id bigint, partition_id bigint, schema_name varchar(255), table_name varchar(255)); + INSERT into @sysAssObjId EXECUTE sp_MSforeachdb + N'USE [?]; + SELECT DB_ID() as database_id, p.partition_id, s.name as schema_name, t.name as table_name + FROM sys.partitions p + LEFT JOIN sys.tables t ON t.object_id = p.object_id + LEFT JOIN sys.schemas s ON s.schema_id = t.schema_id + WHERE s.name is not NULL AND t.name is not NULL'; - ---------------------------------------- - --Aggressive Indexes: Check_id 10-19 - ---------------------------------------- - RAISERROR(N'check_id 11: Total lock wait time > 5 minutes (row + page) with long average waits', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 11 AS check_id, - i.index_sanity_id, - 70 AS Priority, - N'Aggressive ' - + CASE COALESCE((SELECT SUM(1) - FROM #IndexSanity iMe - INNER JOIN #IndexSanity iOthers - ON iMe.database_id = iOthers.database_id - AND iMe.object_id = iOthers.object_id - AND iOthers.index_id > 1 - WHERE i.index_sanity_id = iMe.index_sanity_id - AND iOthers.is_hypothetical = 0 - AND iOthers.is_disabled = 0 - ), 0) - WHEN 0 THEN N'Under-Indexing' - WHEN 1 THEN N'Under-Indexing' - WHEN 2 THEN N'Under-Indexing' - WHEN 3 THEN N'Under-Indexing' - WHEN 4 THEN N'Indexes' - WHEN 5 THEN N'Indexes' - WHEN 6 THEN N'Indexes' - WHEN 7 THEN N'Indexes' - WHEN 8 THEN N'Indexes' - WHEN 9 THEN N'Indexes' - ELSE N'Over-Indexing' - END AS findings_group, - N'Total lock wait time > 5 minutes (row + page) with long average waits' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/AggressiveIndexes' AS URL, - (i.db_schema_object_indexid + N': ' + - sz.index_lock_wait_summary + N' NC indexes on table: ') COLLATE DATABASE_DEFAULT + - CAST(COALESCE((SELECT SUM(1) - FROM #IndexSanity iMe - INNER JOIN #IndexSanity iOthers - ON iMe.database_id = iOthers.database_id - AND iMe.object_id = iOthers.object_id - AND iOthers.index_id > 1 - WHERE i.index_sanity_id = iMe.index_sanity_id - AND iOthers.is_hypothetical = 0 - AND iOthers.is_disabled = 0 - ), 0) - AS NVARCHAR(30)) AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - sz.index_size_summary - FROM #IndexSanity AS i - JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id - WHERE (total_row_lock_wait_in_ms + total_page_lock_wait_in_ms) > 300000 - AND (sz.avg_page_lock_wait_in_ms + sz.avg_row_lock_wait_in_ms) > 5000 - GROUP BY i.index_sanity_id, [database_name], i.db_schema_object_indexid, sz.index_lock_wait_summary, i.index_definition, i.secret_columns, i.index_usage_summary, sz.index_size_summary, sz.index_sanity_id - ORDER BY 4, [database_name], 8 - OPTION ( RECOMPILE ); + /*Begin checks based on parsed values*/ - RAISERROR(N'check_id 12: Total lock wait time > 5 minutes (row + page) with short average waits', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 12 AS check_id, - i.index_sanity_id, - 70 AS Priority, - N'Aggressive ' - + CASE COALESCE((SELECT SUM(1) - FROM #IndexSanity iMe - INNER JOIN #IndexSanity iOthers - ON iMe.database_id = iOthers.database_id - AND iMe.object_id = iOthers.object_id - AND iOthers.index_id > 1 - WHERE i.index_sanity_id = iMe.index_sanity_id - AND iOthers.is_hypothetical = 0 - AND iOthers.is_disabled = 0 - ), 0) - WHEN 0 THEN N'Under-Indexing' - WHEN 1 THEN N'Under-Indexing' - WHEN 2 THEN N'Under-Indexing' - WHEN 3 THEN N'Under-Indexing' - WHEN 4 THEN N'Indexes' - WHEN 5 THEN N'Indexes' - WHEN 6 THEN N'Indexes' - WHEN 7 THEN N'Indexes' - WHEN 8 THEN N'Indexes' - WHEN 9 THEN N'Indexes' - ELSE N'Over-Indexing' - END AS findings_group, - N'Total lock wait time > 5 minutes (row + page) with short average waits' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/AggressiveIndexes' AS URL, - (i.db_schema_object_indexid + N': ' + - sz.index_lock_wait_summary + N' NC indexes on table: ') COLLATE DATABASE_DEFAULT + - CAST(COALESCE((SELECT SUM(1) - FROM #IndexSanity iMe - INNER JOIN #IndexSanity iOthers - ON iMe.database_id = iOthers.database_id - AND iMe.object_id = iOthers.object_id - AND iOthers.index_id > 1 - WHERE i.index_sanity_id = iMe.index_sanity_id - AND iOthers.is_hypothetical = 0 - AND iOthers.is_disabled = 0 - ),0) - AS NVARCHAR(30)) AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - sz.index_size_summary - FROM #IndexSanity AS i - JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id - WHERE (total_row_lock_wait_in_ms + total_page_lock_wait_in_ms) > 300000 - AND (sz.avg_page_lock_wait_in_ms + sz.avg_row_lock_wait_in_ms) < 5000 - GROUP BY i.index_sanity_id, [database_name], i.db_schema_object_indexid, sz.index_lock_wait_summary, i.index_definition, i.secret_columns, i.index_usage_summary, sz.index_size_summary, sz.index_sanity_id - ORDER BY 4, [database_name], 8 - OPTION ( RECOMPILE ); + /*Check 1 is deadlocks by database*/ + SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + RAISERROR('Check 1 %s', 0, 1, @d) WITH NOWAIT; + INSERT #deadlock_findings WITH (TABLOCKX) + ( check_id, database_name, object_name, finding_group, finding ) + SELECT 1 AS check_id, + DB_NAME(dp.database_id) AS database_name, + '-' AS object_name, + 'Total database locks' AS finding_group, + 'This database had ' + + CONVERT(NVARCHAR(20), COUNT_BIG(DISTINCT dp.event_date)) + + ' deadlocks.' + FROM #deadlock_process AS dp + WHERE 1 = 1 + AND (DB_NAME(dp.database_id) = @DatabaseName OR @DatabaseName IS NULL) + AND (dp.event_date >= @StartDate OR @StartDate IS NULL) + AND (dp.event_date < @EndDate OR @EndDate IS NULL) + AND (dp.client_app = @AppName OR @AppName IS NULL) + AND (dp.host_name = @HostName OR @HostName IS NULL) + AND (dp.login_name = @LoginName OR @LoginName IS NULL) + GROUP BY DB_NAME(dp.database_id) + OPTION ( RECOMPILE ); + /*Check 2 is deadlocks by object*/ + SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + RAISERROR('Check 2 objects %s', 0, 1, @d) WITH NOWAIT; + INSERT #deadlock_findings WITH (TABLOCKX) + ( check_id, database_name, object_name, finding_group, finding ) + SELECT 2 AS check_id, + ISNULL(DB_NAME(dow.database_id), 'UNKNOWN') AS database_name, + ISNULL(dow.object_name, 'UNKNOWN') AS object_name, + 'Total object deadlocks' AS finding_group, + 'This object was involved in ' + + CONVERT(NVARCHAR(20), COUNT_BIG(DISTINCT dow.event_date)) + + ' deadlock(s).' + FROM #deadlock_owner_waiter AS dow + WHERE 1 = 1 + AND (DB_NAME(dow.database_id) = @DatabaseName OR @DatabaseName IS NULL) + AND (dow.event_date >= @StartDate OR @StartDate IS NULL) + AND (dow.event_date < @EndDate OR @EndDate IS NULL) + AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) + GROUP BY DB_NAME(dow.database_id), dow.object_name + OPTION ( RECOMPILE ); - ---------------------------------------- - --Index Hoarder: Check_id 20-29 - ---------------------------------------- - RAISERROR(N'check_id 20: >= 10 NC indexes on any given table. Yes, 10 is an arbitrary number.', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 20 AS check_id, - MAX(i.index_sanity_id) AS index_sanity_id, - 10 AS Priority, - 'Index Hoarder' AS findings_group, - 'Many NC Indexes on a Single Table' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/IndexHoarder' AS URL, - CAST (COUNT(*) AS NVARCHAR(30)) + ' NC indexes on ' + i.db_schema_object_name AS details, - i.db_schema_object_name + ' (' + CAST (COUNT(*) AS NVARCHAR(30)) + ' indexes)' AS index_definition, - '' AS secret_columns, - REPLACE(CONVERT(NVARCHAR(30),CAST(SUM(total_reads) AS MONEY), 1), N'.00', N'') + N' reads (ALL); ' - + REPLACE(CONVERT(NVARCHAR(30),CAST(SUM(user_updates) AS MONEY), 1), N'.00', N'') + N' writes (ALL); ', - REPLACE(CONVERT(NVARCHAR(30),CAST(MAX(total_rows) AS MONEY), 1), N'.00', N'') + N' rows (MAX)' - + CASE WHEN SUM(total_reserved_MB) > 1024 THEN - N'; ' + CAST(CAST(SUM(total_reserved_MB)/1024. AS NUMERIC(29,1)) AS NVARCHAR(30)) + 'GB (ALL)' - WHEN SUM(total_reserved_MB) > 0 THEN - N'; ' + CAST(CAST(SUM(total_reserved_MB) AS NUMERIC(29,1)) AS NVARCHAR(30)) + 'MB (ALL)' - ELSE '' - END AS index_size_summary - FROM #IndexSanity i - JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id - WHERE index_id NOT IN ( 0, 1 ) - GROUP BY db_schema_object_name, [i].[database_name] - HAVING COUNT(*) >= 10 - ORDER BY i.db_schema_object_name DESC - OPTION ( RECOMPILE ); + /*Check 2 continuation, number of locks per index*/ + SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + RAISERROR('Check 2 indexes %s', 0, 1, @d) WITH NOWAIT; + INSERT #deadlock_findings WITH (TABLOCKX) + ( check_id, database_name, object_name, finding_group, finding ) + SELECT 2 AS check_id, + ISNULL(DB_NAME(dow.database_id), 'UNKNOWN') AS database_name, + dow.index_name AS index_name, + 'Total index deadlocks' AS finding_group, + 'This index was involved in ' + + CONVERT(NVARCHAR(20), COUNT_BIG(DISTINCT dow.event_date)) + + ' deadlock(s).' + FROM #deadlock_owner_waiter AS dow + WHERE 1 = 1 + AND (DB_NAME(dow.database_id) = @DatabaseName OR @DatabaseName IS NULL) + AND (dow.event_date >= @StartDate OR @StartDate IS NULL) + AND (dow.event_date < @EndDate OR @EndDate IS NULL) + AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) + AND dow.lock_type NOT IN (N'HEAP', N'RID') + AND dow.index_name is not null + GROUP BY DB_NAME(dow.database_id), dow.index_name + OPTION ( RECOMPILE ); - RAISERROR(N'check_id 22: NC indexes with 0 reads. (Borderline) and >= 10,000 writes', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 22 AS check_id, - i.index_sanity_id, - 10 AS Priority, - N'Index Hoarder' AS findings_group, - N'Unused NC Index with High Writes' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/IndexHoarder' AS URL, - N'Reads: 0,' - + N' Writes: ' - + REPLACE(CONVERT(NVARCHAR(30), CAST((i.user_updates) AS MONEY), 1), N'.00', N'') - + N' on: ' - + i.db_schema_object_indexid - AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - sz.index_size_summary - FROM #IndexSanity AS i - JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.total_reads=0 - AND i.user_updates >= 10000 - 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 - AND @Filter <> 1 /* 1 = "ignore unused */ - ORDER BY i.db_schema_object_indexid - OPTION ( RECOMPILE ); + /*Check 2 continuation, number of locks per heap*/ + SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + RAISERROR('Check 2 heaps %s', 0, 1, @d) WITH NOWAIT; + INSERT #deadlock_findings WITH (TABLOCKX) + ( check_id, database_name, object_name, finding_group, finding ) + SELECT 2 AS check_id, + ISNULL(DB_NAME(dow.database_id), 'UNKNOWN') AS database_name, + dow.index_name AS index_name, + 'Total heap deadlocks' AS finding_group, + 'This heap was involved in ' + + CONVERT(NVARCHAR(20), COUNT_BIG(DISTINCT dow.event_date)) + + ' deadlock(s).' + FROM #deadlock_owner_waiter AS dow + WHERE 1 = 1 + AND (DB_NAME(dow.database_id) = @DatabaseName OR @DatabaseName IS NULL) + AND (dow.event_date >= @StartDate OR @StartDate IS NULL) + AND (dow.event_date < @EndDate OR @EndDate IS NULL) + AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) + AND dow.lock_type IN (N'HEAP', N'RID') + GROUP BY DB_NAME(dow.database_id), dow.index_name + OPTION ( RECOMPILE ); + - RAISERROR(N'check_id 34: Filtered index definition columns not in index definition', 0,1) WITH NOWAIT; - - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 34 AS check_id, - i.index_sanity_id, - 80 AS Priority, - N'Abnormal Psychology' AS findings_group, - N'Filter Columns Not In Index Definition' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/IndexFeatures' AS URL, - N'The index ' - + QUOTENAME(i.index_name) - + N' on [' - + i.db_schema_object_name - + N'] has a filter on [' - + i.filter_definition - + N'] but is missing [' - + LTRIM(i.filter_columns_not_in_index) - + N'] from the index definition.' - AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - sz.index_size_summary - FROM #IndexSanity i - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.filter_columns_not_in_index IS NOT NULL - ORDER BY i.db_schema_object_indexid - OPTION ( RECOMPILE ); - - ---------------------------------------- - --Self Loathing Indexes : Check_id 40-49 - ---------------------------------------- - - RAISERROR(N'check_id 40: Fillfactor in nonclustered 80 percent or less', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 40 AS check_id, - i.index_sanity_id, - 100 AS Priority, - N'Self Loathing Indexes' AS findings_group, - N'Low Fill Factor on Nonclustered Index' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/SelfLoathing' AS URL, - CAST(fill_factor AS NVARCHAR(10)) + N'% fill factor on ' + db_schema_object_indexid + N'. '+ - CASE WHEN (last_user_update IS NULL OR user_updates < 1) - THEN N'No writes have been made.' - ELSE - N'Last write was ' + CONVERT(NVARCHAR(16),last_user_update,121) + N' and ' + - CAST(user_updates AS NVARCHAR(25)) + N' updates have been made.' - END - AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - sz.index_size_summary - FROM #IndexSanity AS i - JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id - WHERE index_id > 1 - AND fill_factor BETWEEN 1 AND 80 OPTION ( RECOMPILE ); + /*Check 3 looks for Serializable locking*/ + SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + RAISERROR('Check 3 %s', 0, 1, @d) WITH NOWAIT; + INSERT #deadlock_findings WITH (TABLOCKX) + ( check_id, database_name, object_name, finding_group, finding ) + SELECT 3 AS check_id, + DB_NAME(dp.database_id) AS database_name, + '-' AS object_name, + 'Serializable locking' AS finding_group, + 'This database has had ' + + CONVERT(NVARCHAR(20), COUNT_BIG(*)) + + ' instances of serializable deadlocks.' + AS finding + FROM #deadlock_process AS dp + WHERE dp.isolation_level LIKE 'serializable%' + AND (DB_NAME(dp.database_id) = @DatabaseName OR @DatabaseName IS NULL) + AND (dp.event_date >= @StartDate OR @StartDate IS NULL) + AND (dp.event_date < @EndDate OR @EndDate IS NULL) + AND (dp.client_app = @AppName OR @AppName IS NULL) + AND (dp.host_name = @HostName OR @HostName IS NULL) + AND (dp.login_name = @LoginName OR @LoginName IS NULL) + GROUP BY DB_NAME(dp.database_id) + OPTION ( RECOMPILE ); - RAISERROR(N'check_id 40: Fillfactor in clustered 80 percent or less', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 40 AS check_id, - i.index_sanity_id, - 100 AS Priority, - N'Self Loathing Indexes' AS findings_group, - N'Low Fill Factor on Clustered Index' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/SelfLoathing' AS URL, - N'Fill factor on ' + db_schema_object_indexid + N' is ' + CAST(fill_factor AS NVARCHAR(10)) + N'%. '+ - CASE WHEN (last_user_update IS NULL OR user_updates < 1) - THEN N'No writes have been made.' - ELSE - N'Last write was ' + CONVERT(NVARCHAR(16),last_user_update,121) + N' and ' + - CAST(user_updates AS NVARCHAR(25)) + N' updates have been made.' - END - AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - sz.index_size_summary - FROM #IndexSanity AS i - JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id - WHERE index_id = 1 - AND fill_factor BETWEEN 1 AND 80 OPTION ( RECOMPILE ); + /*Check 4 looks for Repeatable Read locking*/ + SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + RAISERROR('Check 4 %s', 0, 1, @d) WITH NOWAIT; + INSERT #deadlock_findings WITH (TABLOCKX) + ( check_id, database_name, object_name, finding_group, finding ) + SELECT 4 AS check_id, + DB_NAME(dp.database_id) AS database_name, + '-' AS object_name, + 'Repeatable Read locking' AS finding_group, + 'This database has had ' + + CONVERT(NVARCHAR(20), COUNT_BIG(*)) + + ' instances of repeatable read deadlocks.' + AS finding + FROM #deadlock_process AS dp + WHERE dp.isolation_level LIKE 'repeatable read%' + AND (DB_NAME(dp.database_id) = @DatabaseName OR @DatabaseName IS NULL) + AND (dp.event_date >= @StartDate OR @StartDate IS NULL) + AND (dp.event_date < @EndDate OR @EndDate IS NULL) + AND (dp.client_app = @AppName OR @AppName IS NULL) + AND (dp.host_name = @HostName OR @HostName IS NULL) + AND (dp.login_name = @LoginName OR @LoginName IS NULL) + GROUP BY DB_NAME(dp.database_id) + OPTION ( RECOMPILE ); - RAISERROR(N'check_id 43: Heaps with forwarded records', 0,1) WITH NOWAIT; - WITH heaps_cte - AS ( SELECT [object_id], - [database_id], - [schema_name], - SUM(forwarded_fetch_count) AS forwarded_fetch_count, - SUM(leaf_delete_count) AS leaf_delete_count - FROM #IndexPartitionSanity - GROUP BY [object_id], - [database_id], - [schema_name] - HAVING SUM(forwarded_fetch_count) > 0) - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 43 AS check_id, - i.index_sanity_id, - 100 AS Priority, - N'Self Loathing Indexes' AS findings_group, - N'Heaps with Forwarded Fetches' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/SelfLoathing' AS URL, - CASE WHEN h.forwarded_fetch_count >= 922337203685477 THEN '>= 922,337,203,685,477' - WHEN @DaysUptime < 1 THEN CAST(h.forwarded_fetch_count AS NVARCHAR(256)) + N' forwarded fetches against heap: ' + db_schema_object_indexid - ELSE REPLACE(CONVERT(NVARCHAR(256),CAST(CAST( - (h.forwarded_fetch_count /*/@DaysUptime */) - AS BIGINT) AS MONEY), 1), '.00', '') - END + N' forwarded fetches per day against heap: ' - + db_schema_object_indexid AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - sz.index_size_summary - FROM #IndexSanity i - JOIN heaps_cte h ON i.[object_id] = h.[object_id] - AND i.[database_id] = h.[database_id] - AND i.[schema_name] = h.[schema_name] - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.index_id = 0 - AND h.forwarded_fetch_count / @DaysUptime > 1000 - AND sz.total_reserved_MB >= CASE WHEN NOT (@GetAllDatabases = 1 OR @Mode = 4) THEN @ThresholdMB ELSE sz.total_reserved_MB END - OPTION ( RECOMPILE ); - RAISERROR(N'check_id 44: Large Heaps with reads or writes.', 0,1) WITH NOWAIT; - WITH heaps_cte - AS ( SELECT [object_id], - [database_id], - [schema_name], - SUM(forwarded_fetch_count) AS forwarded_fetch_count, - SUM(leaf_delete_count) AS leaf_delete_count - FROM #IndexPartitionSanity - GROUP BY [object_id], - [database_id], - [schema_name] - HAVING SUM(forwarded_fetch_count) > 0 - OR SUM(leaf_delete_count) > 0) - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 44 AS check_id, - i.index_sanity_id, - 100 AS Priority, - N'Self Loathing Indexes' AS findings_group, - N'Large Active Heap' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/SelfLoathing' AS URL, - N'Should this table be a heap? ' + db_schema_object_indexid AS details, - i.index_definition, - 'N/A' AS secret_columns, - i.index_usage_summary, - sz.index_size_summary - FROM #IndexSanity i - LEFT JOIN heaps_cte h ON i.[object_id] = h.[object_id] - AND i.[database_id] = h.[database_id] - AND i.[schema_name] = h.[schema_name] - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.index_id = 0 - AND (i.total_reads > 0 OR i.user_updates > 0) - AND sz.total_rows >= 100000 - AND h.[object_id] IS NULL /*don't duplicate the prior check.*/ - OPTION ( RECOMPILE ); + /*Check 5 breaks down app, host, and login information*/ + SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + RAISERROR('Check 5 %s', 0, 1, @d) WITH NOWAIT; + INSERT #deadlock_findings WITH (TABLOCKX) + ( check_id, database_name, object_name, finding_group, finding ) + SELECT 5 AS check_id, + DB_NAME(dp.database_id) AS database_name, + '-' AS object_name, + 'Login, App, and Host locking' AS finding_group, + 'This database has had ' + + CONVERT(NVARCHAR(20), COUNT_BIG(DISTINCT dp.event_date)) + + ' instances of deadlocks involving the login ' + + ISNULL(dp.login_name, 'UNKNOWN') + + ' from the application ' + + ISNULL(dp.client_app, 'UNKNOWN') + + ' on host ' + + ISNULL(dp.host_name, 'UNKNOWN') + AS finding + FROM #deadlock_process AS dp + WHERE 1 = 1 + AND (DB_NAME(dp.database_id) = @DatabaseName OR @DatabaseName IS NULL) + AND (dp.event_date >= @StartDate OR @StartDate IS NULL) + AND (dp.event_date < @EndDate OR @EndDate IS NULL) + AND (dp.client_app = @AppName OR @AppName IS NULL) + AND (dp.host_name = @HostName OR @HostName IS NULL) + AND (dp.login_name = @LoginName OR @LoginName IS NULL) + GROUP BY DB_NAME(dp.database_id), dp.login_name, dp.client_app, dp.host_name + OPTION ( RECOMPILE ); - RAISERROR(N'check_id 45: Medium Heaps with reads or writes.', 0,1) WITH NOWAIT; - WITH heaps_cte - AS ( SELECT [object_id], - [database_id], - [schema_name], - SUM(forwarded_fetch_count) AS forwarded_fetch_count, - SUM(leaf_delete_count) AS leaf_delete_count - FROM #IndexPartitionSanity - GROUP BY [object_id], - [database_id], - [schema_name] - HAVING SUM(forwarded_fetch_count) > 0 - OR SUM(leaf_delete_count) > 0) - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 45 AS check_id, - i.index_sanity_id, - 100 AS Priority, - N'Self Loathing Indexes' AS findings_group, - N'Medium Active heap' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/SelfLoathing' AS URL, - N'Should this table be a heap? ' + db_schema_object_indexid AS details, - i.index_definition, - 'N/A' AS secret_columns, - i.index_usage_summary, - sz.index_size_summary - FROM #IndexSanity i - LEFT JOIN heaps_cte h ON i.[object_id] = h.[object_id] - AND i.[database_id] = h.[database_id] - AND i.[schema_name] = h.[schema_name] - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.index_id = 0 - AND - (i.total_reads > 0 OR i.user_updates > 0) - AND sz.total_rows >= 10000 AND sz.total_rows < 100000 - AND h.[object_id] IS NULL /*don't duplicate the prior check.*/ - OPTION ( RECOMPILE ); - RAISERROR(N'check_id 46: Small Heaps with reads or writes.', 0,1) WITH NOWAIT; - WITH heaps_cte - AS ( SELECT [object_id], - [database_id], - [schema_name], - SUM(forwarded_fetch_count) AS forwarded_fetch_count, - SUM(leaf_delete_count) AS leaf_delete_count - FROM #IndexPartitionSanity - GROUP BY [object_id], - [database_id], - [schema_name] - HAVING SUM(forwarded_fetch_count) > 0 - OR SUM(leaf_delete_count) > 0) - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 46 AS check_id, - i.index_sanity_id, - 100 AS Priority, - N'Self Loathing Indexes' AS findings_group, - N'Small Active heap' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/SelfLoathing' AS URL, - N'Should this table be a heap? ' + db_schema_object_indexid AS details, - i.index_definition, - 'N/A' AS secret_columns, - i.index_usage_summary, - sz.index_size_summary - FROM #IndexSanity i - LEFT JOIN heaps_cte h ON i.[object_id] = h.[object_id] - AND i.[database_id] = h.[database_id] - AND i.[schema_name] = h.[schema_name] - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.index_id = 0 - AND - (i.total_reads > 0 OR i.user_updates > 0) - AND sz.total_rows < 10000 - AND h.[object_id] IS NULL /*don't duplicate the prior check.*/ - OPTION ( RECOMPILE ); + /*Check 6 breaks down the types of locks (object, page, key, etc.)*/ + SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + RAISERROR('Check 6 %s', 0, 1, @d) WITH NOWAIT; + WITH lock_types AS ( + SELECT DB_NAME(dp.database_id) AS database_name, + dow.object_name, + SUBSTRING(dp.wait_resource, 1, CHARINDEX(':', dp.wait_resource) -1) AS lock, + CONVERT(NVARCHAR(20), COUNT_BIG(DISTINCT dp.id)) AS lock_count + FROM #deadlock_process AS dp + JOIN #deadlock_owner_waiter AS dow + ON dp.id = dow.owner_id + AND dp.event_date = dow.event_date + WHERE 1 = 1 + AND (DB_NAME(dp.database_id) = @DatabaseName OR @DatabaseName IS NULL) + AND (dp.event_date >= @StartDate OR @StartDate IS NULL) + AND (dp.event_date < @EndDate OR @EndDate IS NULL) + AND (dp.client_app = @AppName OR @AppName IS NULL) + AND (dp.host_name = @HostName OR @HostName IS NULL) + AND (dp.login_name = @LoginName OR @LoginName IS NULL) + AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) + AND dow.object_name IS NOT NULL + GROUP BY DB_NAME(dp.database_id), SUBSTRING(dp.wait_resource, 1, CHARINDEX(':', dp.wait_resource) - 1), dow.object_name + ) + INSERT #deadlock_findings WITH (TABLOCKX) + ( check_id, database_name, object_name, finding_group, finding ) + SELECT DISTINCT 6 AS check_id, + lt.database_name, + lt.object_name, + 'Types of locks by object' AS finding_group, + 'This object has had ' + + STUFF((SELECT DISTINCT N', ' + lt2.lock_count + ' ' + lt2.lock + FROM lock_types AS lt2 + WHERE lt2.database_name = lt.database_name + AND lt2.object_name = lt.object_name + FOR XML PATH(N''), TYPE).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 1, N'') + + ' locks' + FROM lock_types AS lt + OPTION ( RECOMPILE ); - RAISERROR(N'check_id 47: Heap with a Nonclustered Primary Key', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 47 AS check_id, - i.index_sanity_id, - 100 AS Priority, - N'Self Loathing Indexes' AS findings_group, - N'Heap with a Nonclustered Primary Key' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/SelfLoathing' AS URL, - db_schema_object_indexid + N' is a HEAP with a Nonclustered Primary Key' AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - sz.index_size_summary - FROM #IndexSanity i - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.index_type = 2 AND i.is_primary_key = 1 - AND EXISTS - ( - SELECT 1/0 - FROM #IndexSanity AS isa - WHERE i.database_id = isa.database_id - AND i.object_id = isa.object_id - AND isa.index_id = 0 - ) - OPTION ( RECOMPILE ); - RAISERROR(N'check_id 48: Nonclustered indexes with a bad read to write ratio', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 48 AS check_id, - i.index_sanity_id, - 100 AS Priority, - N'Index Hoarder' AS findings_group, - N'NC index with High Writes:Reads' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/IndexHoarder' AS URL, - N'Reads: ' - + REPLACE(CONVERT(NVARCHAR(30), CAST((i.total_reads) AS MONEY), 1), N'.00', N'') - + N' Writes: ' - + REPLACE(CONVERT(NVARCHAR(30), CAST((i.user_updates) AS MONEY), 1), N'.00', N'') - + N' on: ' - + i.db_schema_object_indexid AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - sz.index_size_summary - FROM #IndexSanity i - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.total_reads > 0 /*Not totally unused*/ - AND i.user_updates >= 10000 /*Decent write activity*/ - AND i.total_reads < 10000 - AND ((i.total_reads * 10) < i.user_updates) /*10x more writes than reads*/ - 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 - ORDER BY i.db_schema_object_indexid - OPTION ( RECOMPILE ); + /*Check 7 gives you more info queries for sp_BlitzCache & BlitzQueryStore*/ + SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + RAISERROR('Check 7 part 1 %s', 0, 1, @d) WITH NOWAIT; + WITH deadlock_stack AS ( + SELECT DISTINCT + ds.id, + ds.proc_name, + ds.event_date, + PARSENAME(ds.proc_name, 3) AS database_name, + PARSENAME(ds.proc_name, 2) AS schema_name, + PARSENAME(ds.proc_name, 1) AS proc_only_name, + '''' + STUFF((SELECT DISTINCT N',' + ds2.sql_handle + FROM #deadlock_stack AS ds2 + WHERE ds2.id = ds.id + AND ds2.event_date = ds.event_date + FOR XML PATH(N''), TYPE).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 1, N'') + '''' AS sql_handle_csv + FROM #deadlock_stack AS ds + GROUP BY PARSENAME(ds.proc_name, 3), + PARSENAME(ds.proc_name, 2), + PARSENAME(ds.proc_name, 1), + ds.id, + ds.proc_name, + ds.event_date + ) + INSERT #deadlock_findings WITH (TABLOCKX) + ( check_id, database_name, object_name, finding_group, finding ) + SELECT DISTINCT 7 AS check_id, + ISNULL(DB_NAME(dow.database_id), 'UNKNOWN') AS database_name, + ds.proc_name AS object_name, + 'More Info - Query' AS finding_group, + 'EXEC sp_BlitzCache ' + + CASE WHEN ds.proc_name = 'adhoc' + THEN ' @OnlySqlHandles = ' + ds.sql_handle_csv + ELSE '@StoredProcName = ' + + QUOTENAME(ds.proc_only_name, '''') + END + + ';' AS finding + FROM deadlock_stack AS ds + JOIN #deadlock_owner_waiter AS dow + ON dow.owner_id = ds.id + AND dow.event_date = ds.event_date + WHERE 1 = 1 + AND (DB_NAME(dow.database_id) = @DatabaseName OR @DatabaseName IS NULL) + AND (dow.event_date >= @StartDate OR @StartDate IS NULL) + AND (dow.event_date < @EndDate OR @EndDate IS NULL) + AND (dow.object_name = @StoredProcName OR @StoredProcName IS NULL) + OPTION ( RECOMPILE ); - ---------------------------------------- - --Indexaphobia - --Missing indexes with value >= 5 million: : Check_id 50-59 - ---------------------------------------- - RAISERROR(N'check_id 50: Indexaphobia.', 0,1) WITH NOWAIT; - WITH index_size_cte - AS ( SELECT i.database_id, - i.schema_name, - i.[object_id], - MAX(i.index_sanity_id) AS index_sanity_id, - ISNULL(NULLIF(MAX(DATEDIFF(DAY, i.create_date, SYSDATETIME())), 0), 1) AS create_days, - ISNULL ( - CAST(SUM(CASE WHEN index_id NOT IN (0,1) THEN 1 ELSE 0 END) - AS NVARCHAR(30))+ N' NC indexes exist (' + - CASE WHEN SUM(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) > 1024 - THEN CAST(CAST(SUM(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END )/1024. + IF @ProductVersionMajor >= 13 + BEGIN + SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + RAISERROR('Check 7 part 2 %s', 0, 1, @d) WITH NOWAIT; + WITH deadlock_stack AS ( + SELECT DISTINCT + ds.id, + ds.sql_handle, + ds.proc_name, + ds.event_date, + PARSENAME(ds.proc_name, 3) AS database_name, + PARSENAME(ds.proc_name, 2) AS schema_name, + PARSENAME(ds.proc_name, 1) AS proc_only_name + FROM #deadlock_stack AS ds + ) + INSERT #deadlock_findings WITH (TABLOCKX) + ( check_id, database_name, object_name, finding_group, finding ) + SELECT DISTINCT 7 AS check_id, + DB_NAME(dow.database_id) AS database_name, + ds.proc_name AS object_name, + 'More Info - Query' AS finding_group, + 'EXEC sp_BlitzQueryStore ' + + '@DatabaseName = ' + + QUOTENAME(ds.database_name, '''') + + ', ' + + '@StoredProcName = ' + + QUOTENAME(ds.proc_only_name, '''') + + ';' AS finding + FROM deadlock_stack AS ds + JOIN #deadlock_owner_waiter AS dow + ON dow.owner_id = ds.id + AND dow.event_date = ds.event_date + WHERE ds.proc_name <> 'adhoc' + AND (DB_NAME(dow.database_id) = @DatabaseName OR @DatabaseName IS NULL) + AND (dow.event_date >= @StartDate OR @StartDate IS NULL) + AND (dow.event_date < @EndDate OR @EndDate IS NULL) + AND (dow.object_name = @StoredProcName OR @StoredProcName IS NULL) + OPTION ( RECOMPILE ); + END; + + + /*Check 8 gives you stored proc deadlock counts*/ + SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + RAISERROR('Check 8 %s', 0, 1, @d) WITH NOWAIT; + INSERT #deadlock_findings WITH (TABLOCKX) + ( check_id, database_name, object_name, finding_group, finding ) + SELECT 8 AS check_id, + DB_NAME(dp.database_id) AS database_name, + ds.proc_name, + 'Stored Procedure Deadlocks', + 'The stored procedure ' + + PARSENAME(ds.proc_name, 2) + + '.' + + PARSENAME(ds.proc_name, 1) + + ' has been involved in ' + + CONVERT(NVARCHAR(10), COUNT_BIG(DISTINCT ds.id)) + + ' deadlocks.' + FROM #deadlock_stack AS ds + JOIN #deadlock_process AS dp + ON dp.id = ds.id + AND ds.event_date = dp.event_date + WHERE ds.proc_name <> 'adhoc' + AND (ds.proc_name = @StoredProcName OR @StoredProcName IS NULL) + AND (DB_NAME(dp.database_id) = @DatabaseName OR @DatabaseName IS NULL) + AND (dp.event_date >= @StartDate OR @StartDate IS NULL) + AND (dp.event_date < @EndDate OR @EndDate IS NULL) + AND (dp.client_app = @AppName OR @AppName IS NULL) + AND (dp.host_name = @HostName OR @HostName IS NULL) + AND (dp.login_name = @LoginName OR @LoginName IS NULL) + GROUP BY DB_NAME(dp.database_id), ds.proc_name + OPTION(RECOMPILE); + + + /*Check 9 gives you more info queries for sp_BlitzIndex */ + SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + RAISERROR('Check 9 %s', 0, 1, @d) WITH NOWAIT; + WITH bi AS ( + SELECT DISTINCT + dow.object_name, + DB_NAME(dow.database_id) as database_name, + a.schema_name AS schema_name, + a.table_name AS table_name + FROM #deadlock_owner_waiter AS dow + LEFT JOIN @sysAssObjId a ON a.database_id=dow.database_id AND a.partition_id = dow.associatedObjectId + WHERE 1 = 1 + AND (DB_NAME(dow.database_id) = @DatabaseName OR @DatabaseName IS NULL) + AND (dow.event_date >= @StartDate OR @StartDate IS NULL) + AND (dow.event_date < @EndDate OR @EndDate IS NULL) + AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) + AND dow.object_name IS NOT NULL + ) + INSERT #deadlock_findings WITH (TABLOCKX) + ( check_id, database_name, object_name, finding_group, finding ) + SELECT 9 AS check_id, + bi.database_name, + bi.object_name, + 'More Info - Table' AS finding_group, + 'EXEC sp_BlitzIndex ' + + '@DatabaseName = ' + QUOTENAME(bi.database_name, '''') + + ', @SchemaName = ' + QUOTENAME(bi.schema_name, '''') + + ', @TableName = ' + QUOTENAME(bi.table_name, '''') + + ';' AS finding + FROM bi + OPTION ( RECOMPILE ); + + /*Check 10 gets total deadlock wait time per object*/ + SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + RAISERROR('Check 10 %s', 0, 1, @d) WITH NOWAIT; + WITH chopsuey AS ( + SELECT DISTINCT + PARSENAME(dow.object_name, 3) AS database_name, + dow.object_name, + CONVERT(VARCHAR(10), (SUM(DISTINCT dp.wait_time) / 1000) / 86400) AS wait_days, + CONVERT(VARCHAR(20), DATEADD(SECOND, (SUM(DISTINCT dp.wait_time) / 1000), 0), 108) AS wait_time_hms + FROM #deadlock_owner_waiter AS dow + JOIN #deadlock_process AS dp + ON (dp.id = dow.owner_id OR dp.victim_id = dow.waiter_id) + AND dp.event_date = dow.event_date + WHERE 1 = 1 + AND (DB_NAME(dow.database_id) = @DatabaseName OR @DatabaseName IS NULL) + AND (dow.event_date >= @StartDate OR @StartDate IS NULL) + AND (dow.event_date < @EndDate OR @EndDate IS NULL) + AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) + AND (dp.client_app = @AppName OR @AppName IS NULL) + AND (dp.host_name = @HostName OR @HostName IS NULL) + AND (dp.login_name = @LoginName OR @LoginName IS NULL) + GROUP BY PARSENAME(dow.object_name, 3), dow.object_name + ) + INSERT #deadlock_findings WITH (TABLOCKX) + ( check_id, database_name, object_name, finding_group, finding ) + SELECT 10 AS check_id, + cs.database_name, + cs.object_name, + 'Total object deadlock wait time' AS finding_group, + 'This object has had ' + + CONVERT(VARCHAR(10), cs.wait_days) + + ':' + CONVERT(VARCHAR(20), cs.wait_time_hms, 108) + + ' [d/h/m/s] of deadlock wait time.' AS finding + FROM chopsuey AS cs + WHERE cs.object_name IS NOT NULL + OPTION ( RECOMPILE ); - AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'GB); ' - ELSE CAST(SUM(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) - AS NVARCHAR(30)) + N'MB); ' - END + - CASE WHEN MAX(sz.[total_rows]) >= 922337203685477 THEN '>= 922,337,203,685,477' - ELSE REPLACE(CONVERT(NVARCHAR(30),CAST(MAX(sz.[total_rows]) AS MONEY), 1), '.00', '') - END + - + N' Estimated Rows;' - ,N'') AS index_size_summary - FROM #IndexSanity AS i - LEFT JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id AND i.database_id = sz.database_id - WHERE i.is_hypothetical = 0 - AND i.is_disabled = 0 - GROUP BY i.database_id, i.schema_name, i.[object_id]) - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - index_usage_summary, index_size_summary, create_tsql, more_info ) - - SELECT check_id, t.index_sanity_id, t.check_id, t.findings_group, t.finding, t.[Database Name], t.URL, t.details, t.[definition], - index_estimated_impact, t.index_size_summary, create_tsql, more_info - FROM - ( - SELECT ROW_NUMBER() OVER (ORDER BY magic_benefit_number DESC) AS rownum, - 50 AS check_id, - sz.index_sanity_id, - 40 AS Priority, - N'Indexaphobia' AS findings_group, - N'High Value Missing Index' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/Indexaphobia' AS URL, - mi.[statement] + - N' Est. benefit per day: ' + - CASE WHEN magic_benefit_number >= 922337203685477 THEN '>= 922,337,203,685,477' - ELSE REPLACE(CONVERT(NVARCHAR(256),CAST(CAST( - (magic_benefit_number/@DaysUptime) - AS BIGINT) AS MONEY), 1), '.00', '') - END AS details, - missing_index_details AS [definition], - index_estimated_impact, - sz.index_size_summary, - mi.create_tsql, - mi.more_info, - magic_benefit_number, - mi.is_low - FROM #MissingIndexes mi - LEFT JOIN index_size_cte sz ON mi.[object_id] = sz.object_id - AND mi.database_id = sz.database_id - AND mi.schema_name = sz.schema_name - /* Minimum benefit threshold = 100k/day of uptime OR since table creation date, whichever is lower*/ - WHERE @ShowAllMissingIndexRequests=1 - OR ( @Mode = 4 AND (magic_benefit_number / CASE WHEN sz.create_days < @DaysUptime THEN sz.create_days ELSE @DaysUptime END) >= 100000 ) - OR (magic_benefit_number / CASE WHEN sz.create_days < @DaysUptime THEN sz.create_days ELSE @DaysUptime END) >= 100000 - ) AS t - WHERE t.rownum <= CASE WHEN (@Mode <> 4) THEN 20 ELSE t.rownum END - ORDER BY magic_benefit_number DESC - OPTION ( RECOMPILE ); + /*Check 11 gets total deadlock wait time per database*/ + SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + RAISERROR('Check 11 %s', 0, 1, @d) WITH NOWAIT; + WITH wait_time AS ( + SELECT DB_NAME(dp.database_id) AS database_name, + SUM(CONVERT(BIGINT, dp.wait_time)) AS total_wait_time_ms + FROM #deadlock_process AS dp + WHERE 1 = 1 + AND (DB_NAME(dp.database_id) = @DatabaseName OR @DatabaseName IS NULL) + AND (dp.event_date >= @StartDate OR @StartDate IS NULL) + AND (dp.event_date < @EndDate OR @EndDate IS NULL) + AND (dp.client_app = @AppName OR @AppName IS NULL) + AND (dp.host_name = @HostName OR @HostName IS NULL) + AND (dp.login_name = @LoginName OR @LoginName IS NULL) + GROUP BY DB_NAME(dp.database_id) + ) + INSERT #deadlock_findings WITH (TABLOCKX) + ( check_id, database_name, object_name, finding_group, finding ) + SELECT 11 AS check_id, + wt.database_name, + '-' AS object_name, + 'Total database deadlock wait time' AS finding_group, + 'This database has had ' + + CONVERT(VARCHAR(10), (SUM(DISTINCT wt.total_wait_time_ms) / 1000) / 86400) + + ':' + CONVERT(VARCHAR(20), DATEADD(SECOND, (SUM(DISTINCT wt.total_wait_time_ms) / 1000), 0), 108) + + ' [d/h/m/s] of deadlock wait time.' + FROM wait_time AS wt + GROUP BY wt.database_name + OPTION ( RECOMPILE ); + /*Check 12 gets total deadlock wait time for SQL Agent*/ + SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + RAISERROR('Check 12 %s', 0, 1, @d) WITH NOWAIT; + INSERT #deadlock_findings WITH (TABLOCKX) + ( check_id, database_name, object_name, finding_group, finding ) + SELECT 12, + DB_NAME(aj.database_id), + 'SQLAgent - Job: ' + + aj.job_name + + ' Step: ' + + aj.step_name, + 'Agent Job Deadlocks', + RTRIM(COUNT(*)) + ' deadlocks from this Agent Job and Step' + FROM #agent_job AS aj + GROUP BY DB_NAME(aj.database_id), aj.job_name, aj.step_name + OPTION ( RECOMPILE ); + /*Check 13 is total parallel deadlocks*/ + SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + RAISERROR('Check 13 %s', 0, 1, @d) WITH NOWAIT; + INSERT #deadlock_findings WITH (TABLOCKX) + ( check_id, database_name, object_name, finding_group, finding ) + SELECT 13 AS check_id, + N'-' AS database_name, + '-' AS object_name, + 'Total parallel deadlocks' AS finding_group, + 'There have been ' + + CONVERT(NVARCHAR(20), COUNT_BIG(DISTINCT drp.event_date)) + + ' parallel deadlocks.' + FROM #deadlock_resource_parallel AS drp + WHERE 1 = 1 + HAVING COUNT_BIG(DISTINCT drp.event_date) > 0 + OPTION ( RECOMPILE ); - RAISERROR(N'check_id 68: Identity columns within 30 percent of the end of range', 0,1) WITH NOWAIT; - -- Allowed Ranges: - --int -2,147,483,648 to 2,147,483,647 - --smallint -32,768 to 32,768 - --tinyint 0 to 255 + /*Thank you goodnight*/ + INSERT #deadlock_findings WITH (TABLOCKX) + ( check_id, database_name, object_name, finding_group, finding ) + VALUES ( -1, + N'sp_BlitzLock ' + CAST(CONVERT(DATETIME, @VersionDate, 102) AS VARCHAR(100)), + N'SQL Server First Responder Kit', + N'http://FirstResponderKit.org/', + N'To get help or add your own contributions, join us at http://FirstResponderKit.org.'); - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 68 AS check_id, - i.index_sanity_id, - 80 AS Priority, - N'Abnormal Psychology' AS findings_group, - N'Identity Column Within ' + - CAST (calc1.percent_remaining AS NVARCHAR(256)) - + N' Percent End of Range' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, - i.db_schema_object_name + N'.' + QUOTENAME(ic.column_name) - + N' is an identity with type ' + ic.system_type_name - + N', last value of ' - + ISNULL((CONVERT(NVARCHAR(256),CAST(ic.last_value AS DECIMAL(38,0)), 1)),N'NULL') - + N', seed of ' - + ISNULL((CONVERT(NVARCHAR(256),CAST(ic.seed_value AS DECIMAL(38,0)), 1)),N'NULL') - + N', increment of ' + CAST(ic.increment_value AS NVARCHAR(256)) - + N', and range of ' + - CASE ic.system_type_name WHEN 'int' THEN N'+/- 2,147,483,647' - WHEN 'smallint' THEN N'+/- 32,768' - WHEN 'tinyint' THEN N'0 to 255' - ELSE 'unknown' - END - AS details, - i.index_definition, - secret_columns, - ISNULL(i.index_usage_summary,''), - ISNULL(ip.index_size_summary,'') - FROM #IndexSanity i - JOIN #IndexColumns ic ON - i.object_id=ic.object_id - AND i.database_id = ic.database_id - AND i.schema_name = ic.schema_name - AND i.index_id IN (0,1) /* heaps and cx only */ - AND ic.is_identity=1 - AND ic.system_type_name IN ('tinyint', 'smallint', 'int') - JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id - CROSS APPLY ( - SELECT CAST(CASE WHEN ic.increment_value >= 0 - THEN - CASE ic.system_type_name - WHEN 'int' THEN (2147483647 - (ISNULL(ic.last_value,ic.seed_value) + ic.increment_value)) / 2147483647.*100 - WHEN 'smallint' THEN (32768 - (ISNULL(ic.last_value,ic.seed_value) + ic.increment_value)) / 32768.*100 - WHEN 'tinyint' THEN ( 255 - (ISNULL(ic.last_value,ic.seed_value) + ic.increment_value)) / 255.*100 - ELSE 999 - END - ELSE --ic.increment_value is negative - CASE ic.system_type_name - WHEN 'int' THEN ABS(-2147483647 - (ISNULL(ic.last_value,ic.seed_value) + ic.increment_value)) / 2147483647.*100 - WHEN 'smallint' THEN ABS(-32768 - (ISNULL(ic.last_value,ic.seed_value) + ic.increment_value)) / 32768.*100 - WHEN 'tinyint' THEN ABS( 0 - (ISNULL(ic.last_value,ic.seed_value) + ic.increment_value)) / 255.*100 - ELSE -1 - END - END AS NUMERIC(5,1)) AS percent_remaining - ) AS calc1 - WHERE i.index_id IN (1,0) - AND calc1.percent_remaining <= 30 - OPTION (RECOMPILE); + - RAISERROR(N'check_id 72: Columnstore indexes with Trace Flag 834', 0,1) WITH NOWAIT; - IF EXISTS (SELECT * FROM #IndexSanity WHERE index_type IN (5,6)) - AND EXISTS (SELECT * FROM #TraceStatus WHERE TraceFlag = 834 AND status = 1) - BEGIN - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 72 AS check_id, - i.index_sanity_id, - 80 AS Priority, - N'Abnormal Psychology' AS findings_group, - 'Columnstore Indexes with Trace Flag 834' AS finding, - [database_name] AS [Database Name], - N'https://support.microsoft.com/en-us/kb/3210239' AS URL, - i.db_schema_object_indexid AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - ISNULL(sz.index_size_summary,'') AS index_size_summary - FROM #IndexSanity AS i - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.index_type IN (5,6) - OPTION ( RECOMPILE ); - END; + /*Results*/ + /*Break in case of emergency*/ + --CREATE CLUSTERED INDEX cx_whatever ON #deadlock_process (event_date, id); + --CREATE CLUSTERED INDEX cx_whatever ON #deadlock_resource_parallel (event_date, owner_id); + --CREATE CLUSTERED INDEX cx_whatever ON #deadlock_owner_waiter (event_date, owner_id, waiter_id); + IF(@OutputDatabaseCheck = 0) + BEGIN + + SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + RAISERROR('Results 1 %s', 0, 1, @d) WITH NOWAIT; + WITH deadlocks + AS ( SELECT N'Regular Deadlock' AS deadlock_type, + dp.event_date, + dp.id, + dp.victim_id, + dp.database_id, + dp.priority, + dp.log_used, + dp.wait_resource COLLATE DATABASE_DEFAULT AS wait_resource, + CONVERT( + XML, + STUFF(( SELECT DISTINCT NCHAR(10) + + N' ' + + ISNULL(c.object_name, N'') + + N' ' COLLATE DATABASE_DEFAULT AS object_name + FROM #deadlock_owner_waiter AS c + WHERE ( dp.id = c.owner_id + OR dp.victim_id = c.waiter_id ) + AND CONVERT(DATE, dp.event_date) = CONVERT(DATE, c.event_date) + FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(4000)'), + 1, 1, N'')) AS object_names, + dp.wait_time, + dp.transaction_name, + dp.last_tran_started, + dp.last_batch_started, + dp.last_batch_completed, + dp.lock_mode, + dp.transaction_count, + dp.client_app, + dp.host_name, + dp.login_name, + dp.isolation_level, + dp.process_xml.value('(//process/inputbuf/text())[1]', 'NVARCHAR(MAX)') AS inputbuf, + ROW_NUMBER() OVER ( PARTITION BY dp.event_date, dp.id ORDER BY dp.event_date ) AS dn, + DENSE_RANK() OVER ( ORDER BY dp.event_date ) AS en, + ROW_NUMBER() OVER ( PARTITION BY dp.event_date ORDER BY dp.event_date ) -1 AS qn, + dp.is_victim, + ISNULL(dp.owner_mode, '-') AS owner_mode, + NULL AS owner_waiter_type, + NULL AS owner_activity, + NULL AS owner_waiter_activity, + NULL AS owner_merging, + NULL AS owner_spilling, + NULL AS owner_waiting_to_close, + ISNULL(dp.waiter_mode, '-') AS waiter_mode, + NULL AS waiter_waiter_type, + NULL AS waiter_owner_activity, + NULL AS waiter_waiter_activity, + NULL AS waiter_merging, + NULL AS waiter_spilling, + NULL AS waiter_waiting_to_close, + dp.deadlock_graph + FROM #deadlock_process AS dp + WHERE dp.victim_id IS NOT NULL + AND dp.is_parallel = 0 + + UNION ALL + + SELECT N'Parallel Deadlock' AS deadlock_type, + dp.event_date, + dp.id, + dp.victim_id, + dp.database_id, + dp.priority, + dp.log_used, + dp.wait_resource COLLATE DATABASE_DEFAULT, + CASE WHEN @ExportToExcel = 0 THEN + CONVERT( + XML, + STUFF(( SELECT DISTINCT NCHAR(10) + + N' ' + + ISNULL(c.object_name, N'') + + N' ' COLLATE DATABASE_DEFAULT AS object_name + FROM #deadlock_owner_waiter AS c + WHERE ( dp.id = c.owner_id + OR dp.victim_id = c.waiter_id ) + AND CONVERT(DATE, dp.event_date) = CONVERT(DATE, c.event_date) + FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(4000)'), + 1, 1, N'')) + ELSE NULL END AS object_names, + dp.wait_time, + dp.transaction_name, + dp.last_tran_started, + dp.last_batch_started, + dp.last_batch_completed, + dp.lock_mode, + dp.transaction_count, + dp.client_app, + dp.host_name, + dp.login_name, + dp.isolation_level, + dp.process_xml.value('(//process/inputbuf/text())[1]', 'NVARCHAR(MAX)') AS inputbuf, + ROW_NUMBER() OVER ( PARTITION BY dp.event_date, dp.id ORDER BY dp.event_date ) AS dn, + DENSE_RANK() OVER ( ORDER BY dp.event_date ) AS en, + ROW_NUMBER() OVER ( PARTITION BY dp.event_date ORDER BY dp.event_date ) -1 AS qn, + 1 AS is_victim, + cao.wait_type COLLATE DATABASE_DEFAULT AS owner_mode, + cao.waiter_type AS owner_waiter_type, + cao.owner_activity AS owner_activity, + cao.waiter_activity AS owner_waiter_activity, + cao.merging AS owner_merging, + cao.spilling AS owner_spilling, + cao.waiting_to_close AS owner_waiting_to_close, + caw.wait_type COLLATE DATABASE_DEFAULT AS waiter_mode, + caw.waiter_type AS waiter_waiter_type, + caw.owner_activity AS waiter_owner_activity, + caw.waiter_activity AS waiter_waiter_activity, + caw.merging AS waiter_merging, + caw.spilling AS waiter_spilling, + caw.waiting_to_close AS waiter_waiting_to_close, + dp.deadlock_graph + FROM #deadlock_process AS dp + CROSS APPLY (SELECT TOP 1 * FROM #deadlock_resource_parallel AS drp WHERE drp.owner_id = dp.id AND drp.wait_type = 'e_waitPipeNewRow' ORDER BY drp.event_date) AS cao + CROSS APPLY (SELECT TOP 1 * FROM #deadlock_resource_parallel AS drp WHERE drp.owner_id = dp.id AND drp.wait_type = 'e_waitPipeGetRow' ORDER BY drp.event_date) AS caw + WHERE dp.is_parallel = 1 ) + insert into DeadLockTbl ( + ServerName, + deadlock_type, + event_date, + database_name, + deadlock_group, + query, + object_names, + isolation_level, + owner_mode, + waiter_mode, + transaction_count, + login_name, + host_name, + client_app, + wait_time, + priority, + log_used, + last_tran_started, + last_batch_started, + last_batch_completed, + transaction_name, + owner_waiter_type, + owner_activity, + owner_waiter_activity, + owner_merging, + owner_spilling, + owner_waiting_to_close, + waiter_waiter_type, + waiter_owner_activity, + waiter_waiter_activity, + waiter_merging, + waiter_spilling, + waiter_waiting_to_close, + deadlock_graph + ) + SELECT @ServerName, + d.deadlock_type, + d.event_date, + DB_NAME(d.database_id) AS database_name, + 'Deadlock #' + + CONVERT(NVARCHAR(10), d.en) + + ', Query #' + + CASE WHEN d.qn = 0 THEN N'1' ELSE CONVERT(NVARCHAR(10), d.qn) END + + CASE WHEN d.is_victim = 1 THEN ' - VICTIM' ELSE '' END + AS deadlock_group, + CONVERT(XML, N'') AS query, + d.object_names, + d.isolation_level, + d.owner_mode, + d.waiter_mode, + d.transaction_count, + d.login_name, + d.host_name, + d.client_app, + d.wait_time, + d.priority, + d.log_used, + d.last_tran_started, + d.last_batch_started, + d.last_batch_completed, + d.transaction_name, + /*These columns will be NULL for regular (non-parallel) deadlocks*/ + d.owner_waiter_type, + d.owner_activity, + d.owner_waiter_activity, + d.owner_merging, + d.owner_spilling, + d.owner_waiting_to_close, + d.waiter_waiter_type, + d.waiter_owner_activity, + d.waiter_waiter_activity, + d.waiter_merging, + d.waiter_spilling, + d.waiter_waiting_to_close, + d.deadlock_graph + FROM deadlocks AS d + WHERE d.dn = 1 + AND (is_victim = @VictimsOnly OR @VictimsOnly = 0) + AND d.en < CASE WHEN d.deadlock_type = N'Parallel Deadlock' THEN 2 ELSE 2147483647 END + AND (DB_NAME(d.database_id) = @DatabaseName OR @DatabaseName IS NULL) + AND (d.event_date >= @StartDate OR @StartDate IS NULL) + AND (d.event_date < @EndDate OR @EndDate IS NULL) + AND (CONVERT(NVARCHAR(MAX), d.object_names) LIKE '%' + @ObjectName + '%' OR @ObjectName IS NULL) + AND (d.client_app = @AppName OR @AppName IS NULL) + AND (d.host_name = @HostName OR @HostName IS NULL) + AND (d.login_name = @LoginName OR @LoginName IS NULL) + ORDER BY d.event_date, is_victim DESC + OPTION ( RECOMPILE ); + + drop SYNONYM DeadLockTbl; --done insert into blitzlock table going to insert into findings table first create synonym. - ---------------------------------------- - --Statistics Info: Check_id 90-99 - ---------------------------------------- + -- RAISERROR('att deadlock findings', 0, 1) WITH NOWAIT; + - RAISERROR(N'check_id 90: Outdated 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 ) - SELECT 90 AS check_id, - 90 AS Priority, - 'Functioning Statistaholics' AS findings_group, - 'Statistics Not Updated Recently', - s.database_name, - 'https://www.brentozar.com/go/stats' AS URL, - 'Statistics on this table were last updated ' + - CASE WHEN s.last_statistics_update IS NULL THEN N' NEVER ' - ELSE CONVERT(NVARCHAR(20), s.last_statistics_update) + - ' have had ' + CONVERT(NVARCHAR(100), s.modification_counter) + - ' modifications in that time, which is ' + - CONVERT(NVARCHAR(100), s.percent_modifications) + - '% of the table.' - END 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.last_statistics_update <= CONVERT(DATETIME, GETDATE() - 30) - AND s.percent_modifications >= 10. - AND s.rows >= 10000 - OPTION ( RECOMPILE ); + SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + RAISERROR('Findings %s', 0, 1, @d) WITH NOWAIT; - RAISERROR(N'check_id 91: Statistics with a low 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 91 AS check_id, - 90 AS Priority, - 'Functioning Statistaholics' AS findings_group, - 'Low Sampling Rates', - s.database_name, - 'https://www.brentozar.com/go/stats' AS URL, - 'Only ' + CONVERT(NVARCHAR(100), s.percent_sampled) + '% of the rows were sampled during the last statistics update. This may lead to poor cardinality estimates.' 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.rows BETWEEN 10000 AND 1000000 AND s.percent_sampled < 10) - OR (s.rows > 1000000 AND s.percent_sampled < 1) - OPTION ( RECOMPILE ); + Insert into DeadlockFindings (ServerName,check_id,database_name,object_name,finding_group,finding) + SELECT @ServerName,df.check_id, df.database_name, df.object_name, df.finding_group, df.finding + FROM #deadlock_findings AS df + ORDER BY df.check_id + 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 ) - SELECT 92 AS check_id, - 90 AS Priority, - 'Functioning Statistaholics' AS findings_group, - 'Statistics With NO RECOMPUTE', - s.database_name, - 'https://www.brentozar.com/go/stats' AS URL, - 'The statistic ' + QUOTENAME(s.statistics_name) + ' is set to not recompute. This can be helpful if data is really skewed, but harmful if you expect automatic statistics updates.' 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.no_recompute = 1 - OPTION ( RECOMPILE ); + drop SYNONYM DeadlockFindings; --done with inserting. +END +ELSE --Output to database is not set output to client app + BEGIN + SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + RAISERROR('Results 1 %s', 0, 1, @d) WITH NOWAIT; + WITH deadlocks + AS ( SELECT N'Regular Deadlock' AS deadlock_type, + dp.event_date, + dp.id, + dp.victim_id, + dp.database_id, + dp.priority, + dp.log_used, + dp.wait_resource COLLATE DATABASE_DEFAULT AS wait_resource, + CASE WHEN @ExportToExcel = 0 THEN + CONVERT( + XML, + STUFF(( SELECT DISTINCT NCHAR(10) + + N' ' + + ISNULL(c.object_name, N'') + + N' ' COLLATE DATABASE_DEFAULT AS object_name + FROM #deadlock_owner_waiter AS c + WHERE ( dp.id = c.owner_id + OR dp.victim_id = c.waiter_id ) + AND CONVERT(DATE, dp.event_date) = CONVERT(DATE, c.event_date) + FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(4000)'), + 1, 1, N'')) + ELSE NULL END AS object_names, + dp.wait_time, + dp.transaction_name, + dp.last_tran_started, + dp.last_batch_started, + dp.last_batch_completed, + dp.lock_mode, + dp.transaction_count, + dp.client_app, + dp.host_name, + dp.login_name, + dp.isolation_level, + dp.process_xml.value('(//process/inputbuf/text())[1]', 'NVARCHAR(MAX)') AS inputbuf, + ROW_NUMBER() OVER ( PARTITION BY dp.event_date, dp.id ORDER BY dp.event_date ) AS dn, + DENSE_RANK() OVER ( ORDER BY dp.event_date ) AS en, + ROW_NUMBER() OVER ( PARTITION BY dp.event_date ORDER BY dp.event_date ) -1 AS qn, + dp.is_victim, + ISNULL(dp.owner_mode, '-') AS owner_mode, + NULL AS owner_waiter_type, + NULL AS owner_activity, + NULL AS owner_waiter_activity, + NULL AS owner_merging, + NULL AS owner_spilling, + NULL AS owner_waiting_to_close, + ISNULL(dp.waiter_mode, '-') AS waiter_mode, + NULL AS waiter_waiter_type, + NULL AS waiter_owner_activity, + NULL AS waiter_waiter_activity, + NULL AS waiter_merging, + NULL AS waiter_spilling, + NULL AS waiter_waiting_to_close, + dp.deadlock_graph + FROM #deadlock_process AS dp + WHERE dp.victim_id IS NOT NULL + AND dp.is_parallel = 0 + UNION ALL + + SELECT N'Parallel Deadlock' AS deadlock_type, + dp.event_date, + dp.id, + dp.victim_id, + dp.database_id, + dp.priority, + dp.log_used, + dp.wait_resource COLLATE DATABASE_DEFAULT, + CASE WHEN @ExportToExcel = 0 THEN + CONVERT( + XML, + STUFF(( SELECT DISTINCT NCHAR(10) + + N' ' + + ISNULL(c.object_name, N'') + + N' ' COLLATE DATABASE_DEFAULT AS object_name + FROM #deadlock_owner_waiter AS c + WHERE ( dp.id = c.owner_id + OR dp.victim_id = c.waiter_id ) + AND CONVERT(DATE, dp.event_date) = CONVERT(DATE, c.event_date) + FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(4000)'), + 1, 1, N'')) + ELSE NULL END AS object_names, + dp.wait_time, + dp.transaction_name, + dp.last_tran_started, + dp.last_batch_started, + dp.last_batch_completed, + dp.lock_mode, + dp.transaction_count, + dp.client_app, + dp.host_name, + dp.login_name, + dp.isolation_level, + dp.process_xml.value('(//process/inputbuf/text())[1]', 'NVARCHAR(MAX)') AS inputbuf, + ROW_NUMBER() OVER ( PARTITION BY dp.event_date, dp.id ORDER BY dp.event_date ) AS dn, + DENSE_RANK() OVER ( ORDER BY dp.event_date ) AS en, + ROW_NUMBER() OVER ( PARTITION BY dp.event_date ORDER BY dp.event_date ) -1 AS qn, + 1 AS is_victim, + cao.wait_type COLLATE DATABASE_DEFAULT AS owner_mode, + cao.waiter_type AS owner_waiter_type, + cao.owner_activity AS owner_activity, + cao.waiter_activity AS owner_waiter_activity, + cao.merging AS owner_merging, + cao.spilling AS owner_spilling, + cao.waiting_to_close AS owner_waiting_to_close, + caw.wait_type COLLATE DATABASE_DEFAULT AS waiter_mode, + caw.waiter_type AS waiter_waiter_type, + caw.owner_activity AS waiter_owner_activity, + caw.waiter_activity AS waiter_waiter_activity, + caw.merging AS waiter_merging, + caw.spilling AS waiter_spilling, + caw.waiting_to_close AS waiter_waiting_to_close, + dp.deadlock_graph + FROM #deadlock_process AS dp + OUTER APPLY (SELECT TOP 1 * FROM #deadlock_resource_parallel AS drp WHERE drp.owner_id = dp.id AND drp.wait_type IN ('e_waitPortOpen', 'e_waitPipeNewRow') ORDER BY drp.event_date) AS cao + OUTER APPLY (SELECT TOP 1 * FROM #deadlock_resource_parallel AS drp WHERE drp.owner_id = dp.id AND drp.wait_type IN ('e_waitPortOpen', 'e_waitPipeGetRow') ORDER BY drp.event_date) AS caw + WHERE dp.is_parallel = 1 + ) + SELECT d.deadlock_type, + d.event_date, + DB_NAME(d.database_id) AS database_name, + 'Deadlock #' + + CONVERT(NVARCHAR(10), d.en) + + ', Query #' + + CASE WHEN d.qn = 0 THEN N'1' ELSE CONVERT(NVARCHAR(10), d.qn) END + + CASE WHEN d.is_victim = 1 THEN ' - VICTIM' ELSE '' END + AS deadlock_group, + CASE WHEN @ExportToExcel = 0 THEN CONVERT(XML, N'') + ELSE SUBSTRING(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(LTRIM(RTRIM(d.inputbuf)),' ','<>'),'><',''),NCHAR(10), ' '),NCHAR(13), ' '),'<>',' '), 1, 32000) END AS query, + d.object_names, + d.isolation_level, + d.owner_mode, + d.waiter_mode, + d.transaction_count, + d.login_name, + d.host_name, + d.client_app, + d.wait_time, + d.priority, + d.log_used, + d.last_tran_started, + d.last_batch_started, + d.last_batch_completed, + d.transaction_name, + /*These columns will be NULL for regular (non-parallel) deadlocks*/ + d.owner_waiter_type, + d.owner_activity, + d.owner_waiter_activity, + d.owner_merging, + d.owner_spilling, + d.owner_waiting_to_close, + d.waiter_waiter_type, + d.waiter_owner_activity, + d.waiter_waiter_activity, + d.waiter_merging, + d.waiter_spilling, + d.waiter_waiting_to_close, + CASE WHEN @ExportToExcel = 0 THEN d.deadlock_graph ELSE NULL END AS deadlock_graph + FROM deadlocks AS d + WHERE d.dn = 1 + AND (is_victim = @VictimsOnly OR @VictimsOnly = 0) + AND d.en < CASE WHEN d.deadlock_type = N'Parallel Deadlock' THEN 2 ELSE 2147483647 END + AND (DB_NAME(d.database_id) = @DatabaseName OR @DatabaseName IS NULL) + AND (d.event_date >= @StartDate OR @StartDate IS NULL) + AND (d.event_date < @EndDate OR @EndDate IS NULL) + AND (CONVERT(NVARCHAR(MAX), d.object_names) LIKE '%' + @ObjectName + '%' OR @ObjectName IS NULL) + AND (d.client_app = @AppName OR @AppName IS NULL) + AND (d.host_name = @HostName OR @HostName IS NULL) + AND (d.login_name = @LoginName OR @LoginName IS NULL) + ORDER BY d.event_date, is_victim DESC + OPTION ( RECOMPILE ); + + SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + RAISERROR('Findings %s', 0, 1, @d) WITH NOWAIT; + SELECT df.check_id, df.database_name, df.object_name, df.finding_group, df.finding + FROM #deadlock_findings AS df + ORDER BY df.check_id + 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 ) - SELECT 94 AS check_id, - 100 AS Priority, - 'Serial Forcer' AS findings_group, - 'Check Constraint with Scalar UDF' AS finding, - cc.database_name, - 'https://www.brentozar.com/go/computedscalar' AS URL, - 'The check constraint ' + QUOTENAME(cc.constraint_name) + ' on ' + QUOTENAME(cc.schema_name) + '.' + QUOTENAME(cc.table_name) + ' is based on ' + cc.definition - + '. That indicates it may reference a scalar function, or a CLR function with data access, which can cause all queries and maintenance to run serially.' AS details, - cc.column_definition, - 'N/A' AS secret_columns, - 'N/A' AS index_usage_summary, - 'N/A' AS index_size_summary - FROM #CheckConstraints AS cc - WHERE cc.is_function = 1 - OPTION ( RECOMPILE ); + SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + RAISERROR('Done %s', 0, 1, @d) WITH NOWAIT; + END --done with output to client app. - RAISERROR(N'check_id 99: Computed Columns 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 ) - SELECT 99 AS check_id, - 100 AS Priority, - 'Serial Forcer' AS findings_group, - 'Computed Column with Scalar UDF' AS finding, - cc.database_name, - 'https://www.brentozar.com/go/serialudf' AS URL, - 'The computed column ' + QUOTENAME(cc.column_name) + ' on ' + QUOTENAME(cc.schema_name) + '.' + QUOTENAME(cc.table_name) + ' is based on ' + cc.definition - + '. That indicates it may reference a scalar function, or a CLR function with data access, which can cause all queries and maintenance to run serially.' AS details, - cc.column_definition, - 'N/A' AS secret_columns, - 'N/A' AS index_usage_summary, - 'N/A' AS index_size_summary - FROM #ComputedColumns AS cc - WHERE cc.is_function = 1 - OPTION ( RECOMPILE ); + IF @Debug = 1 + BEGIN - END /* IF @Mode IN (0, 4) DIAGNOSE priorities 1-100 */ + SELECT '#deadlock_data' AS table_name, * + FROM #deadlock_data AS dd + OPTION ( RECOMPILE ); + SELECT '#deadlock_resource' AS table_name, * + FROM #deadlock_resource AS dr + OPTION ( RECOMPILE ); + SELECT '#deadlock_resource_parallel' AS table_name, * + FROM #deadlock_resource_parallel AS drp + OPTION ( RECOMPILE ); + SELECT '#deadlock_owner_waiter' AS table_name, * + FROM #deadlock_owner_waiter AS dow + OPTION ( RECOMPILE ); + SELECT '#deadlock_process' AS table_name, * + FROM #deadlock_process AS dp + OPTION ( RECOMPILE ); + SELECT '#deadlock_stack' AS table_name, * + FROM #deadlock_stack AS ds + OPTION ( RECOMPILE ); + + END; -- End debug + END; --Final End +GO +SET ANSI_NULLS ON; +SET ANSI_PADDING ON; +SET ANSI_WARNINGS ON; +SET ARITHABORT ON; +SET CONCAT_NULL_YIELDS_NULL ON; +SET QUOTED_IDENTIFIER ON; +SET STATISTICS IO OFF; +SET STATISTICS TIME OFF; +GO +DECLARE @msg NVARCHAR(MAX) = N''; + -- Must be a compatible, on-prem version of SQL (2016+) +IF ( (SELECT CONVERT(NVARCHAR(128), SERVERPROPERTY ('EDITION'))) <> 'SQL Azure' + AND (SELECT PARSENAME(CONVERT(NVARCHAR(128), SERVERPROPERTY ('PRODUCTVERSION')), 4)) < 13 + ) + -- or Azure Database (not Azure Data Warehouse), running at database compat level 130+ +OR ( (SELECT CONVERT(NVARCHAR(128), SERVERPROPERTY ('EDITION'))) = 'SQL Azure' + AND (SELECT SERVERPROPERTY ('ENGINEEDITION')) NOT IN (5,8) + AND (SELECT [compatibility_level] FROM sys.databases WHERE [name] = DB_NAME()) < 130 + ) +BEGIN + SELECT @msg = N'Sorry, sp_BlitzQueryStore doesn''t work on versions of SQL prior to 2016, or Azure Database compatibility < 130.' + REPLICATE(CHAR(13), 7933); + PRINT @msg; + RETURN; +END; +IF OBJECT_ID('dbo.sp_BlitzQueryStore') IS NULL + EXEC ('CREATE PROCEDURE dbo.sp_BlitzQueryStore AS RETURN 0;'); +GO +ALTER PROCEDURE dbo.sp_BlitzQueryStore + @Help BIT = 0, + @DatabaseName NVARCHAR(128) = NULL , + @Top INT = 3, + @StartDate DATETIME2 = NULL, + @EndDate DATETIME2 = NULL, + @MinimumExecutionCount INT = NULL, + @DurationFilter DECIMAL(38,4) = NULL , + @StoredProcName NVARCHAR(128) = NULL, + @Failed BIT = 0, + @PlanIdFilter INT = NULL, + @QueryIdFilter INT = NULL, + @ExportToExcel BIT = 0, + @HideSummary BIT = 0 , + @SkipXML BIT = 0, + @Debug BIT = 0, + @ExpertMode BIT = 0, + @Version VARCHAR(30) = NULL OUTPUT, + @VersionDate DATETIME = NULL OUTPUT, + @VersionCheckMode BIT = 0 +WITH RECOMPILE +AS +BEGIN /*First BEGIN*/ +SET NOCOUNT ON; +SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; +SELECT @Version = '8.01', @VersionDate = '20210222'; +IF(@VersionCheckMode = 1) +BEGIN + RETURN; +END; +DECLARE /*Variables for the variable Gods*/ + @msg NVARCHAR(MAX) = N'', --Used to format RAISERROR messages in some places + @sql_select NVARCHAR(MAX) = N'', --Used to hold SELECT statements for dynamic SQL + @sql_where NVARCHAR(MAX) = N'', -- Used to hold WHERE clause for dynamic SQL + @duration_filter_ms DECIMAL(38,4) = (@DurationFilter * 1000.), --We accept Duration in seconds, but we filter in milliseconds (this is grandfathered from sp_BlitzCache) + @execution_threshold INT = 1000, --Threshold at which we consider a query to be frequently executed + @ctp_threshold_pct TINYINT = 10, --Percentage of CTFP at which we consider a query to be near parallel + @long_running_query_warning_seconds BIGINT = 300 * 1000 ,--Number of seconds (converted to milliseconds) at which a query is considered long running + @memory_grant_warning_percent INT = 10,--Percent of memory grant used compared to what's granted; used to trigger unused memory grant warning + @ctp INT,--Holds the CTFP value for the server + @min_memory_per_query INT,--Holds the server configuration value for min memory per query + @cr NVARCHAR(1) = NCHAR(13),--Special character + @lf NVARCHAR(1) = NCHAR(10),--Special character + @tab NVARCHAR(1) = NCHAR(9),--Special character + @error_severity INT,--Holds error info for try/catch blocks + @error_state INT,--Holds error info for try/catch blocks + @sp_params NVARCHAR(MAX) = N'@sp_Top INT, @sp_StartDate DATETIME2, @sp_EndDate DATETIME2, @sp_MinimumExecutionCount INT, @sp_MinDuration INT, @sp_StoredProcName NVARCHAR(128), @sp_PlanIdFilter INT, @sp_QueryIdFilter INT',--Holds parameters used in dynamic SQL + @is_azure_db BIT = 0, --Are we using Azure? I'm not. You might be. That's cool. + @compatibility_level TINYINT = 0, --Some functionality (T-SQL) isn't available in lower compat levels. We can use this to weed out those issues as we go. + @log_size_mb DECIMAL(38,2) = 0, + @avg_tempdb_data_file DECIMAL(38,2) = 0; +/*Grabs CTFP setting*/ +SELECT @ctp = NULLIF(CAST(value AS INT), 0) +FROM sys.configurations +WHERE name = N'cost threshold for parallelism' +OPTION (RECOMPILE); +/*Grabs min query memory setting*/ +SELECT @min_memory_per_query = CONVERT(INT, c.value) +FROM sys.configurations AS c +WHERE c.name = N'min memory per query (KB)' +OPTION (RECOMPILE); +/*Check if this is Azure first*/ +IF (SELECT CONVERT(NVARCHAR(128), SERVERPROPERTY ('EDITION'))) <> 'SQL Azure' + BEGIN + /*Grabs log size for datbase*/ + SELECT @log_size_mb = AVG(((mf.size * 8) / 1024.)) + FROM sys.master_files AS mf + WHERE mf.database_id = DB_ID(@DatabaseName) + AND mf.type_desc = 'LOG'; + + /*Grab avg tempdb file size*/ + SELECT @avg_tempdb_data_file = AVG(((mf.size * 8) / 1024.)) + FROM sys.master_files AS mf + WHERE mf.database_id = DB_ID('tempdb') + AND mf.type_desc = 'ROWS'; + END; +/*Help section*/ +IF @Help = 1 + BEGIN + + SELECT N'You have requested assistance. It will arrive as soon as humanly possible.' AS [Take four red capsules, help is on the way]; + PRINT N' + sp_BlitzQueryStore from http://FirstResponderKit.org + + This script displays your most resource-intensive queries from the Query Store, + and points to ways you can tune these queries to make them faster. + + + To learn more, visit http://FirstResponderKit.org where you can download new + versions for free, watch training videos on how it works, get more info on + the findings, contribute your own code, and more. + + Known limitations of this version: + - This query will not run on SQL Server versions less than 2016. + - This query will not run on Azure Databases with compatibility less than 130. + - This query will not run on Azure Data Warehouse. + Unknown limitations of this version: + - Could be tickling + + + MIT License + + Copyright (c) 2021 Brent Ozar Unlimited + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + '; + RETURN; +END; +/*Making sure your version is copasetic*/ +IF ( (SELECT CONVERT(NVARCHAR(128), SERVERPROPERTY ('EDITION'))) = 'SQL Azure' ) + BEGIN + SET @is_azure_db = 1; - IF @Mode = 4 /* DIAGNOSE*/ - BEGIN; - RAISERROR(N'@Mode=4, running rules for priorities 101+.', 0,1) WITH NOWAIT; + IF ( (SELECT SERVERPROPERTY ('ENGINEEDITION')) NOT IN (5,8) + OR (SELECT [compatibility_level] FROM sys.databases WHERE [name] = DB_NAME()) < 130 + ) + BEGIN + SELECT @msg = N'Sorry, sp_BlitzQueryStore doesn''t work on Azure Data Warehouse, or Azure Databases with DB compatibility < 130.' + REPLICATE(CHAR(13), 7933); + PRINT @msg; + RETURN; + END; + END; +ELSE IF ( (SELECT PARSENAME(CONVERT(NVARCHAR(128), SERVERPROPERTY ('PRODUCTVERSION')), 4) ) < 13 ) + BEGIN + SELECT @msg = N'Sorry, sp_BlitzQueryStore doesn''t work on versions of SQL prior to 2016.' + REPLICATE(CHAR(13), 7933); + PRINT @msg; + RETURN; + END; - RAISERROR(N'check_id 21: More Than 5 Percent NC Indexes Are Unused', 0,1) WITH NOWAIT; - DECLARE @percent_NC_indexes_unused NUMERIC(29,1); - DECLARE @NC_indexes_unused_reserved_MB NUMERIC(29,1); +/*Making sure at least one database uses QS*/ +IF ( SELECT COUNT(*) + FROM sys.databases AS d + WHERE d.is_query_store_on = 1 + AND d.user_access_desc='MULTI_USER' + AND d.state_desc = 'ONLINE' + AND d.name NOT IN ('master', 'model', 'msdb', 'tempdb', '32767') + AND d.is_distributor = 0 ) = 0 + BEGIN + SELECT @msg = N'You don''t currently have any databases with Query Store enabled.' + REPLICATE(CHAR(13), 7933); + PRINT @msg; + RETURN; + END; + +/*Making sure your databases are using QDS.*/ +RAISERROR('Checking database validity', 0, 1) WITH NOWAIT; - SELECT @percent_NC_indexes_unused = ( 100.00 * SUM(CASE - WHEN total_reads = 0 - THEN 1 - ELSE 0 - END) ) / COUNT(*), - @NC_indexes_unused_reserved_MB = SUM(CASE - WHEN total_reads = 0 - THEN sz.total_reserved_MB - ELSE 0 - END) - FROM #IndexSanity i - 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()) - 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, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 21 AS check_id, - MAX(i.index_sanity_id) AS index_sanity_id, - 150 AS Priority, - N'Index Hoarder' AS findings_group, - N'More Than 5 Percent NC Indexes Are Unused' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/IndexHoarder' AS URL, - CAST (@percent_NC_indexes_unused AS NVARCHAR(30)) + N' percent NC indexes (' + CAST(COUNT(*) AS NVARCHAR(10)) + N') unused. ' + - N'These take up ' + CAST (@NC_indexes_unused_reserved_MB AS NVARCHAR(30)) + N'MB of space.' AS details, - i.database_name + ' (' + CAST (COUNT(*) AS NVARCHAR(30)) + N' indexes)' AS index_definition, - '' AS secret_columns, - CAST(SUM(total_reads) AS NVARCHAR(256)) + N' reads (ALL); ' - + CAST(SUM([user_updates]) AS NVARCHAR(256)) + N' writes (ALL)' AS index_usage_summary, - - REPLACE(CONVERT(NVARCHAR(30),CAST(MAX([total_rows]) AS MONEY), 1), '.00', '') + N' rows (MAX)' - + CASE WHEN SUM(total_reserved_MB) > 1024 THEN - N'; ' + CAST(CAST(SUM(total_reserved_MB)/1024. AS NUMERIC(29,1)) AS NVARCHAR(30)) + 'GB (ALL)' - WHEN SUM(total_reserved_MB) > 0 THEN - N'; ' + CAST(CAST(SUM(total_reserved_MB) AS NUMERIC(29,1)) AS NVARCHAR(30)) + 'MB (ALL)' - ELSE '' - END AS index_size_summary - FROM #IndexSanity i - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - 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()) - GROUP BY i.database_name - OPTION ( RECOMPILE ); +IF (@is_azure_db = 1) + SET @DatabaseName = DB_NAME(); +ELSE +BEGIN - RAISERROR(N'check_id 23: Indexes with 7 or more columns. (Borderline)', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 23 AS check_id, - i.index_sanity_id, - 150 AS Priority, - N'Index Hoarder' AS findings_group, - N'Borderline: Wide Indexes (7 or More Columns)' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/IndexHoarder' AS URL, - CAST(count_key_columns + count_included_columns AS NVARCHAR(10)) + ' columns on ' - + i.db_schema_object_indexid AS details, i.index_definition, - i.secret_columns, - i.index_usage_summary, - sz.index_size_summary - FROM #IndexSanity AS i - JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id - WHERE ( count_key_columns + count_included_columns ) >= 7 - OPTION ( RECOMPILE ); + /*If we're on Azure we don't need to check all this @DatabaseName stuff...*/ - RAISERROR(N'check_id 24: Wide clustered indexes (> 3 columns or > 16 bytes).', 0,1) WITH NOWAIT; - WITH count_columns AS ( - SELECT database_id, [object_id], - SUM(CASE max_length WHEN -1 THEN 0 ELSE max_length END) AS sum_max_length - FROM #IndexColumns ic - WHERE index_id IN (1,0) /*Heap or clustered only*/ - AND key_ordinal > 0 - GROUP BY database_id, object_id - ) - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 24 AS check_id, - i.index_sanity_id, - 150 AS Priority, - N'Index Hoarder' AS findings_group, - N'Wide Clustered Index (> 3 columns OR > 16 bytes)' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/IndexHoarder' AS URL, - CAST (i.count_key_columns AS NVARCHAR(10)) + N' columns with potential size of ' - + CAST(cc.sum_max_length AS NVARCHAR(10)) - + N' bytes in clustered index:' + i.db_schema_object_name - + N'. ' + - (SELECT CAST(COUNT(*) AS NVARCHAR(23)) - FROM #IndexSanity i2 - WHERE i2.[object_id]=i.[object_id] - AND i2.database_id = i.database_id - AND i2.index_id <> 1 - AND i2.is_disabled=0 - AND i2.is_hypothetical=0) - + N' NC indexes on the table.' - AS details, - i.index_definition, - secret_columns, - i.index_usage_summary, - ip.index_size_summary - FROM #IndexSanity i - JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id - JOIN count_columns AS cc ON i.[object_id]=cc.[object_id] - AND i.database_id = cc.database_id - WHERE index_id = 1 /* clustered only */ - AND - (count_key_columns > 3 /*More than three key columns.*/ - OR cc.sum_max_length > 16 /*More than 16 bytes in key */) - AND i.is_CX_columnstore = 0 - ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); + SET @DatabaseName = LTRIM(RTRIM(@DatabaseName)); - RAISERROR(N'check_id 25: Addicted to nullable columns.', 0,1) WITH NOWAIT; - WITH count_columns AS ( - SELECT [object_id], - [database_id], - [schema_name], - SUM(CASE is_nullable WHEN 1 THEN 0 ELSE 1 END) AS non_nullable_columns, - COUNT(*) AS total_columns - FROM #IndexColumns ic - WHERE index_id IN (1,0) /*Heap or clustered only*/ - GROUP BY [object_id], - [database_id], - [schema_name] - ) - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 25 AS check_id, - i.index_sanity_id, - 200 AS Priority, - N'Index Hoarder' AS findings_group, - N'Addicted to Nulls' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/IndexHoarder' AS URL, - i.db_schema_object_name - + N' allows null in ' + CAST((total_columns-non_nullable_columns) AS NVARCHAR(10)) - + N' of ' + CAST(total_columns AS NVARCHAR(10)) - + N' columns.' AS details, - i.index_definition, - secret_columns, - ISNULL(i.index_usage_summary,''), - ISNULL(ip.index_size_summary,'') - FROM #IndexSanity i - JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id - JOIN count_columns AS cc ON i.[object_id]=cc.[object_id] - AND cc.database_id = ip.database_id - AND cc.[schema_name] = ip.[schema_name] - WHERE i.index_id IN (1,0) - AND cc.non_nullable_columns < 2 - AND cc.total_columns > 3 - ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); + /*Did you set @DatabaseName?*/ + RAISERROR('Making sure [%s] isn''t NULL', 0, 1, @DatabaseName) WITH NOWAIT; + IF (@DatabaseName IS NULL) + BEGIN + RAISERROR('@DatabaseName cannot be NULL', 0, 1) WITH NOWAIT; + RETURN; + END; - RAISERROR(N'check_id 26: Wide tables (35+ cols or > 2000 non-LOB bytes).', 0,1) WITH NOWAIT; - WITH count_columns AS ( - SELECT [object_id], - [database_id], - [schema_name], - SUM(CASE max_length WHEN -1 THEN 1 ELSE 0 END) AS count_lob_columns, - SUM(CASE max_length WHEN -1 THEN 0 ELSE max_length END) AS sum_max_length, - COUNT(*) AS total_columns - FROM #IndexColumns ic - WHERE index_id IN (1,0) /*Heap or clustered only*/ - GROUP BY [object_id], - [database_id], - [schema_name] - ) - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 26 AS check_id, - i.index_sanity_id, - 150 AS Priority, - N'Index Hoarder' AS findings_group, - N'Wide Tables: 35+ cols or > 2000 non-LOB bytes' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/IndexHoarder' AS URL, - i.db_schema_object_name - + N' has ' + CAST((total_columns) AS NVARCHAR(10)) - + N' total columns with a max possible width of ' + CAST(sum_max_length AS NVARCHAR(10)) - + N' bytes.' + - CASE WHEN count_lob_columns > 0 THEN CAST((count_lob_columns) AS NVARCHAR(10)) - + ' columns are LOB types.' ELSE '' - END - AS details, - i.index_definition, - secret_columns, - ISNULL(i.index_usage_summary,''), - ISNULL(ip.index_size_summary,'') - FROM #IndexSanity i - JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id - JOIN count_columns AS cc ON i.[object_id]=cc.[object_id] - AND cc.database_id = i.database_id - AND cc.[schema_name] = i.[schema_name] - WHERE i.index_id IN (1,0) - AND - (cc.total_columns >= 35 OR - cc.sum_max_length >= 2000) - ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 27: Addicted to strings.', 0,1) WITH NOWAIT; - WITH count_columns AS ( - SELECT [object_id], - [database_id], - [schema_name], - SUM(CASE WHEN system_type_name IN ('varchar','nvarchar','char') OR max_length=-1 THEN 1 ELSE 0 END) AS string_or_LOB_columns, - COUNT(*) AS total_columns - FROM #IndexColumns ic - WHERE index_id IN (1,0) /*Heap or clustered only*/ - GROUP BY [object_id], - [database_id], - [schema_name] - ) - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 27 AS check_id, - i.index_sanity_id, - 200 AS Priority, - N'Index Hoarder' AS findings_group, - N'Addicted to strings' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/IndexHoarder' AS URL, - i.db_schema_object_name - + N' uses string or LOB types for ' + CAST((string_or_LOB_columns) AS NVARCHAR(10)) - + N' of ' + CAST(total_columns AS NVARCHAR(10)) - + N' columns. Check if data types are valid.' AS details, - i.index_definition, - secret_columns, - ISNULL(i.index_usage_summary,''), - ISNULL(ip.index_size_summary,'') - FROM #IndexSanity i - JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id - JOIN count_columns AS cc ON i.[object_id]=cc.[object_id] - AND cc.database_id = i.database_id - AND cc.[schema_name] = i.[schema_name] - CROSS APPLY (SELECT cc.total_columns - string_or_LOB_columns AS non_string_or_lob_columns) AS calc1 - WHERE i.index_id IN (1,0) - AND calc1.non_string_or_lob_columns <= 1 - AND cc.total_columns > 3 - ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); + /*Does the database exist?*/ + RAISERROR('Making sure [%s] exists', 0, 1, @DatabaseName) WITH NOWAIT; + IF ((DB_ID(@DatabaseName)) IS NULL) + BEGIN + RAISERROR('The @DatabaseName you specified ([%s]) does not exist. Please check the name and try again.', 0, 1, @DatabaseName) WITH NOWAIT; + RETURN; + END; - RAISERROR(N'check_id 28: Non-unique clustered index.', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 28 AS check_id, - i.index_sanity_id, - 150 AS Priority, - N'Index Hoarder' AS findings_group, - N'Non-Unique Clustered JIndex' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/IndexHoarder' AS URL, - N'Uniquifiers will be required! Clustered index: ' + i.db_schema_object_name - + N' and all NC indexes. ' + - (SELECT CAST(COUNT(*) AS NVARCHAR(23)) - FROM #IndexSanity i2 - WHERE i2.[object_id]=i.[object_id] - AND i2.database_id = i.database_id - AND i2.index_id <> 1 - AND i2.is_disabled=0 - AND i2.is_hypothetical=0) - + N' NC indexes on the table.' - AS details, - i.index_definition, - secret_columns, - i.index_usage_summary, - ip.index_size_summary - FROM #IndexSanity i - JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id - WHERE index_id = 1 /* clustered only */ - AND is_unique=0 /* not unique */ - AND is_CX_columnstore=0 /* not a clustered columnstore-- no unique option on those */ - ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); + /*Is it online?*/ + RAISERROR('Making sure [%s] is online', 0, 1, @DatabaseName) WITH NOWAIT; + IF (DATABASEPROPERTYEX(@DatabaseName, 'Collation')) IS NULL + BEGIN + RAISERROR('The @DatabaseName you specified ([%s]) is not readable. Please check the name and try again. Better yet, check your server.', 0, 1, @DatabaseName); + RETURN; + END; +END; - RAISERROR(N'check_id 29: NC indexes with 0 reads. (Borderline) and < 10,000 writes', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 29 AS check_id, - i.index_sanity_id, - 150 AS Priority, - N'Index Hoarder' AS findings_group, - N'Unused NC index with Low Writes' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/IndexHoarder' AS URL, - N'0 reads: ' + i.db_schema_object_indexid AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - sz.index_size_summary - FROM #IndexSanity AS i - JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.total_reads=0 - AND i.user_updates < 10000 - 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()) - ORDER BY i.db_schema_object_indexid - OPTION ( RECOMPILE ); +/*Does it have Query Store enabled?*/ +RAISERROR('Making sure [%s] has Query Store enabled', 0, 1, @DatabaseName) WITH NOWAIT; +IF + ( SELECT [d].[name] + FROM [sys].[databases] AS d + WHERE [d].[is_query_store_on] = 1 + AND [d].[user_access_desc]='MULTI_USER' + AND [d].[state_desc] = 'ONLINE' + AND [d].[database_id] = (SELECT database_id FROM sys.databases WHERE name = @DatabaseName) + ) IS NULL +BEGIN + RAISERROR('The @DatabaseName you specified ([%s]) does not have the Query Store enabled. Please check the name or settings, and try again.', 0, 1, @DatabaseName) WITH NOWAIT; + RETURN; +END; - ---------------------------------------- - --Feature-Phobic Indexes: Check_id 30-39 - ---------------------------------------- - RAISERROR(N'check_id 30: No indexes with includes', 0,1) WITH NOWAIT; - /* This does not work the way you'd expect with @GetAllDatabases = 1. For details: - https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/825 - */ +/*Check database compat level*/ - SELECT database_name, - SUM(CASE WHEN count_included_columns > 0 THEN 1 ELSE 0 END) AS number_indexes_with_includes, - 100.* SUM(CASE WHEN count_included_columns > 0 THEN 1 ELSE 0 END) / ( 1.0 * COUNT(*) ) AS percent_indexes_with_includes - INTO #index_includes - FROM #IndexSanity - WHERE is_hypothetical = 0 - AND is_disabled = 0 - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) - GROUP BY database_name; +RAISERROR('Checking database compatibility level', 0, 1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 30 AS check_id, - NULL AS index_sanity_id, - 250 AS Priority, - N'Feature-Phobic Indexes' AS findings_group, - database_name AS [Database Name], - N'No Indexes Use Includes' AS finding, 'https://www.brentozar.com/go/IndexFeatures' AS URL, - N'No Indexes Use Includes' AS details, - database_name + N' (Entire database)' AS index_definition, - N'' AS secret_columns, - N'N/A' AS index_usage_summary, - N'N/A' AS index_size_summary - FROM #index_includes - WHERE number_indexes_with_includes = 0 - OPTION ( RECOMPILE ); +SELECT @compatibility_level = d.compatibility_level +FROM sys.databases AS d +WHERE d.name = @DatabaseName; - RAISERROR(N'check_id 31: < 3 percent of indexes have includes', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 31 AS check_id, - NULL AS index_sanity_id, - 250 AS Priority, - N'Feature-Phobic Indexes' AS findings_group, - N'Few Indexes Use Includes' AS findings, - database_name AS [Database Name], - N'https://www.brentozar.com/go/IndexFeatures' AS URL, - N'Only ' + CAST(percent_indexes_with_includes AS NVARCHAR(20)) + '% of indexes have includes' AS details, - N'Entire database' AS index_definition, - N'' AS secret_columns, - N'N/A' AS index_usage_summary, - N'N/A' AS index_size_summary - FROM #index_includes - WHERE number_indexes_with_includes > 0 AND percent_indexes_with_includes <= 3 - OPTION ( RECOMPILE ); +RAISERROR('The @DatabaseName you specified ([%s])is running in compatibility level ([%d]).', 0, 1, @DatabaseName, @compatibility_level) WITH NOWAIT; - RAISERROR(N'check_id 32: filtered indexes and indexed views', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT DISTINCT - 32 AS check_id, - NULL AS index_sanity_id, - 250 AS Priority, - N'Feature-Phobic Indexes' AS findings_group, - N'No Filtered Indexes or Indexed Views' AS finding, - i.database_name AS [Database Name], - N'https://www.brentozar.com/go/IndexFeatures' AS URL, - N'These are NOT always needed-- but do you know when you would use them?' AS details, - i.database_name + N' (Entire database)' AS index_definition, - N'' AS secret_columns, - N'N/A' AS index_usage_summary, - N'N/A' AS index_size_summary - FROM #IndexSanity i - WHERE i.database_name NOT IN ( - SELECT database_name - FROM #IndexSanity - WHERE filter_definition <> '' ) - AND i.database_name NOT IN ( - SELECT database_name - FROM #IndexSanity - WHERE is_indexed_view = 1 ) - OPTION ( RECOMPILE ); +/*Making sure top is set to something if NULL*/ +IF ( @Top IS NULL ) + BEGIN + SET @Top = 3; + END; - RAISERROR(N'check_id 33: Potential filtered indexes based on column names.', 0,1) WITH NOWAIT; +/* +This section determines if you have the Query Store wait stats DMV +*/ - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 33 AS check_id, - i.index_sanity_id AS index_sanity_id, - 250 AS Priority, - N'Feature-Phobic Indexes' AS findings_group, - N'Potential Filtered Index (Based on Column Name)' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/IndexFeatures' AS URL, - N'A column name in this index suggests it might be a candidate for filtering (is%, %archive%, %active%, %flag%)' AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - sz.index_size_summary - FROM #IndexColumns ic - JOIN #IndexSanity i ON ic.[object_id]=i.[object_id] - AND ic.database_id =i.database_id - AND ic.schema_name = i.schema_name - AND ic.[index_id]=i.[index_id] - AND i.[index_id] > 1 /* non-clustered index */ - JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id - WHERE (column_name LIKE 'is%' - OR column_name LIKE '%archive%' - OR column_name LIKE '%active%' - OR column_name LIKE '%flag%') - OPTION ( RECOMPILE ); +RAISERROR('Checking for query_store_wait_stats', 0, 1) WITH NOWAIT; - RAISERROR(N'check_id 41: Hypothetical indexes ', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 41 AS check_id, - i.index_sanity_id, - 150 AS Priority, - N'Self Loathing Indexes' AS findings_group, - N'Hypothetical Index' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/SelfLoathing' AS URL, - N'Hypothetical Index: ' + db_schema_object_indexid AS details, - i.index_definition, - i.secret_columns, - N'' AS index_usage_summary, - N'' AS index_size_summary - FROM #IndexSanity AS i - WHERE is_hypothetical = 1 - OPTION ( RECOMPILE ); +DECLARE @ws_out INT, + @waitstats BIT, + @ws_sql NVARCHAR(MAX) = N'SELECT @i_out = COUNT(*) FROM ' + QUOTENAME(@DatabaseName) + N'.sys.all_objects WHERE name = ''query_store_wait_stats'' OPTION (RECOMPILE);', + @ws_params NVARCHAR(MAX) = N'@i_out INT OUTPUT'; +EXEC sys.sp_executesql @ws_sql, @ws_params, @i_out = @ws_out OUTPUT; - RAISERROR(N'check_id 42: Disabled indexes', 0,1) WITH NOWAIT; - --Note: disabled NC indexes will have O rows in #IndexSanitySize! - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 42 AS check_id, - index_sanity_id, - 150 AS Priority, - N'Self Loathing Indexes' AS findings_group, - N'Disabled Index' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/SelfLoathing' AS URL, - N'Disabled Index:' + db_schema_object_indexid AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - 'DISABLED' AS index_size_summary - FROM #IndexSanity AS i - WHERE is_disabled = 1 - OPTION ( RECOMPILE ); +SELECT @waitstats = CASE @ws_out WHEN 0 THEN 0 ELSE 1 END; - RAISERROR(N'check_id 49: Heaps with deletes', 0,1) WITH NOWAIT; - WITH heaps_cte - AS ( SELECT [object_id], - [database_id], - [schema_name], - SUM(leaf_delete_count) AS leaf_delete_count - FROM #IndexPartitionSanity - GROUP BY [object_id], - [database_id], - [schema_name] - HAVING SUM(forwarded_fetch_count) < 1000 * @DaysUptime /* Only alert about indexes with no forwarded fetches - we already alerted about those in check_id 43 */ - AND SUM(leaf_delete_count) > 0) - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 49 AS check_id, - i.index_sanity_id, - 200 AS Priority, - N'Self Loathing Indexes' AS findings_group, - N'Heaps with Deletes' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/SelfLoathing' AS URL, - CAST(h.leaf_delete_count AS NVARCHAR(256)) + N' deletes against heap:' - + db_schema_object_indexid AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - sz.index_size_summary - FROM #IndexSanity i - JOIN heaps_cte h ON i.[object_id] = h.[object_id] - AND i.[database_id] = h.[database_id] - AND i.[schema_name] = h.[schema_name] - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.index_id = 0 - AND sz.total_reserved_MB >= CASE WHEN NOT (@GetAllDatabases = 1 OR @Mode = 4) THEN @ThresholdMB ELSE sz.total_reserved_MB END - OPTION ( RECOMPILE ); +SET @msg = N'Wait stats DMV ' + CASE @waitstats + WHEN 0 THEN N' does not exist, skipping.' + WHEN 1 THEN N' exists, will analyze.' + END; +RAISERROR(@msg, 0, 1) WITH NOWAIT; - ---------------------------------------- - --Abnormal Psychology : Check_id 60-79 - ---------------------------------------- - RAISERROR(N'check_id 60: XML indexes', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 60 AS check_id, - i.index_sanity_id, - 150 AS Priority, - N'Abnormal Psychology' AS findings_group, - N'XML Index' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, - i.db_schema_object_indexid AS details, - i.index_definition, - i.secret_columns, - N'' AS index_usage_summary, - ISNULL(sz.index_size_summary,'') AS index_size_summary - FROM #IndexSanity AS i - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.is_XML = 1 - OPTION ( RECOMPILE ); +/* +This section determines if you have some additional columns present in 2017, in case they get back ported. +*/ - RAISERROR(N'check_id 61: Columnstore indexes', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 61 AS check_id, - i.index_sanity_id, - 150 AS Priority, - N'Abnormal Psychology' AS findings_group, - CASE WHEN i.is_NC_columnstore=1 - THEN N'NC Columnstore Index' - ELSE N'Clustered Columnstore Index' - END AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, - i.db_schema_object_indexid AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - ISNULL(sz.index_size_summary,'') AS index_size_summary - FROM #IndexSanity AS i - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.is_NC_columnstore = 1 OR i.is_CX_columnstore=1 - OPTION ( RECOMPILE ); +RAISERROR('Checking for new columns in query_store_runtime_stats', 0, 1) WITH NOWAIT; +DECLARE @nc_out INT, + @new_columns BIT, + @nc_sql NVARCHAR(MAX) = N'SELECT @i_out = COUNT(*) + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.all_columns AS ac + WHERE OBJECT_NAME(object_id) = ''query_store_runtime_stats'' + AND ac.name IN ( + ''avg_num_physical_io_reads'', + ''last_num_physical_io_reads'', + ''min_num_physical_io_reads'', + ''max_num_physical_io_reads'', + ''avg_log_bytes_used'', + ''last_log_bytes_used'', + ''min_log_bytes_used'', + ''max_log_bytes_used'', + ''avg_tempdb_space_used'', + ''last_tempdb_space_used'', + ''min_tempdb_space_used'', + ''max_tempdb_space_used'' + ) OPTION (RECOMPILE);', + @nc_params NVARCHAR(MAX) = N'@i_out INT OUTPUT'; - RAISERROR(N'check_id 62: Spatial indexes', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 62 AS check_id, - i.index_sanity_id, - 150 AS Priority, - N'Abnormal Psychology' AS findings_group, - N'Spatial Index' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, - i.db_schema_object_indexid AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - ISNULL(sz.index_size_summary,'') AS index_size_summary - FROM #IndexSanity AS i - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.is_spatial = 1 - OPTION ( RECOMPILE ); +EXEC sys.sp_executesql @nc_sql, @ws_params, @i_out = @nc_out OUTPUT; - RAISERROR(N'check_id 63: Compressed indexes', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 63 AS check_id, - i.index_sanity_id, - 150 AS Priority, - N'Abnormal Psychology' AS findings_group, - N'Compressed Index' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, - i.db_schema_object_indexid + N'. COMPRESSION: ' + sz.data_compression_desc AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - ISNULL(sz.index_size_summary,'') AS index_size_summary - FROM #IndexSanity AS i - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE sz.data_compression_desc LIKE '%PAGE%' OR sz.data_compression_desc LIKE '%ROW%' - OPTION ( RECOMPILE ); +SELECT @new_columns = CASE @nc_out WHEN 12 THEN 1 ELSE 0 END; - RAISERROR(N'check_id 64: Partitioned', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 64 AS check_id, - i.index_sanity_id, - 150 AS Priority, - N'Abnormal Psychology' AS findings_group, - N'Partitioned Index' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, - i.db_schema_object_indexid AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - ISNULL(sz.index_size_summary,'') AS index_size_summary - FROM #IndexSanity AS i - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.partition_key_column_name IS NOT NULL - OPTION ( RECOMPILE ); +SET @msg = N'New query_store_runtime_stats columns ' + CASE @new_columns + WHEN 0 THEN N' do not exist, skipping.' + WHEN 1 THEN N' exist, will analyze.' + END; +RAISERROR(@msg, 0, 1) WITH NOWAIT; - RAISERROR(N'check_id 65: Non-Aligned Partitioned', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 65 AS check_id, - i.index_sanity_id, - 150 AS Priority, - N'Abnormal Psychology' AS findings_group, - N'Non-Aligned Index on a Partitioned Table' AS finding, - i.[database_name] AS [Database Name], - N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, - i.db_schema_object_indexid AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - ISNULL(sz.index_size_summary,'') AS index_size_summary - FROM #IndexSanity AS i - JOIN #IndexSanity AS iParent ON - i.[object_id]=iParent.[object_id] - AND i.database_id = iParent.database_id - AND i.schema_name = iParent.schema_name - AND iParent.index_id IN (0,1) /* could be a partitioned heap or clustered table */ - AND iParent.partition_key_column_name IS NOT NULL /* parent is partitioned*/ - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.partition_key_column_name IS NULL - OPTION ( RECOMPILE ); + +/* +These are the temp tables we use +*/ - RAISERROR(N'check_id 66: Recently created tables/indexes (1 week)', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 66 AS check_id, - i.index_sanity_id, - 200 AS Priority, - N'Abnormal Psychology' AS findings_group, - N'Recently Created Tables/Indexes (1 week)' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, - i.db_schema_object_indexid + N' was created on ' + - CONVERT(NVARCHAR(16),i.create_date,121) + - N'. Tables/indexes which are dropped/created regularly require special methods for index tuning.' - AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - ISNULL(sz.index_size_summary,'') AS index_size_summary - FROM #IndexSanity AS i - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.create_date >= DATEADD(dd,-7,GETDATE()) - OPTION ( RECOMPILE ); - RAISERROR(N'check_id 67: Recently modified tables/indexes (2 days)', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 67 AS check_id, - i.index_sanity_id, - 200 AS Priority, - N'Abnormal Psychology' AS findings_group, - N'Recently Modified Tables/Indexes (2 days)' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, - i.db_schema_object_indexid + N' was modified on ' + - CONVERT(NVARCHAR(16),i.modify_date,121) + - N'. A large amount of recently modified indexes may mean a lot of rebuilds are occurring each night.' - AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - ISNULL(sz.index_size_summary,'') AS index_size_summary - FROM #IndexSanity AS i - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.modify_date > DATEADD(dd,-2,GETDATE()) - AND /*Exclude recently created tables.*/ - i.create_date < DATEADD(dd,-7,GETDATE()) - OPTION ( RECOMPILE ); +/* +This one holds the grouped data that helps use figure out which periods to examine +*/ - RAISERROR(N'check_id 69: Column collation does not match database collation', 0,1) WITH NOWAIT; - WITH count_columns AS ( - SELECT [object_id], - database_id, - schema_name, - COUNT(*) AS column_count - FROM #IndexColumns ic - WHERE index_id IN (1,0) /*Heap or clustered only*/ - AND collation_name <> @collation - GROUP BY [object_id], - database_id, - schema_name - ) - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 69 AS check_id, - i.index_sanity_id, - 150 AS Priority, - N'Abnormal Psychology' AS findings_group, - N'Column Collation Does Not Match Database Collation' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, - i.db_schema_object_name - + N' has ' + CAST(column_count AS NVARCHAR(20)) - + N' column' + CASE WHEN column_count > 1 THEN 's' ELSE '' END - + N' with a different collation than the db collation of ' - + @collation AS details, - i.index_definition, - secret_columns, - ISNULL(i.index_usage_summary,''), - ISNULL(ip.index_size_summary,'') - FROM #IndexSanity i - JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id - JOIN count_columns AS cc ON i.[object_id]=cc.[object_id] - AND cc.database_id = i.database_id - AND cc.schema_name = i.schema_name - WHERE i.index_id IN (1,0) - ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); +RAISERROR(N'Creating temp tables', 0, 1) WITH NOWAIT; - RAISERROR(N'check_id 70: Replicated columns', 0,1) WITH NOWAIT; - WITH count_columns AS ( - SELECT [object_id], - database_id, - schema_name, - COUNT(*) AS column_count, - SUM(CASE is_replicated WHEN 1 THEN 1 ELSE 0 END) AS replicated_column_count - FROM #IndexColumns ic - WHERE index_id IN (1,0) /*Heap or clustered only*/ - GROUP BY object_id, - database_id, - schema_name - ) - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 70 AS check_id, - i.index_sanity_id, - 200 AS Priority, - N'Abnormal Psychology' AS findings_group, - N'Replicated Columns' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, - i.db_schema_object_name - + N' has ' + CAST(replicated_column_count AS NVARCHAR(20)) - + N' out of ' + CAST(column_count AS NVARCHAR(20)) - + N' column' + CASE WHEN column_count > 1 THEN 's' ELSE '' END - + N' in one or more publications.' - AS details, - i.index_definition, - secret_columns, - ISNULL(i.index_usage_summary,''), - ISNULL(ip.index_size_summary,'') - FROM #IndexSanity i - JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id - JOIN count_columns AS cc ON i.[object_id]=cc.[object_id] - AND i.database_id = cc.database_id - AND i.schema_name = cc.schema_name - WHERE i.index_id IN (1,0) - AND replicated_column_count > 0 - ORDER BY i.db_schema_object_name DESC - OPTION ( RECOMPILE ); +DROP TABLE IF EXISTS #grouped_interval; - RAISERROR(N'check_id 71: Cascading updates or cascading deletes.', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary, more_info ) - SELECT 71 AS check_id, - NULL AS index_sanity_id, - 150 AS Priority, - N'Abnormal Psychology' AS findings_group, - N'Cascading Updates or Deletes' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, - N'Foreign Key ' + foreign_key_name + - N' on ' + QUOTENAME(parent_object_name) + N'(' + LTRIM(parent_fk_columns) + N')' - + N' referencing ' + QUOTENAME(referenced_object_name) + N'(' + LTRIM(referenced_fk_columns) + N')' - + N' has settings:' - + CASE [delete_referential_action_desc] WHEN N'NO_ACTION' THEN N'' ELSE N' ON DELETE ' +[delete_referential_action_desc] END - + CASE [update_referential_action_desc] WHEN N'NO_ACTION' THEN N'' ELSE N' ON UPDATE ' + [update_referential_action_desc] END - AS details, - [fk].[database_name] - AS index_definition, - N'N/A' AS secret_columns, - N'N/A' AS index_usage_summary, - N'N/A' AS index_size_summary, - (SELECT TOP 1 more_info FROM #IndexSanity i WHERE i.object_id=fk.parent_object_id AND i.database_id = fk.database_id AND i.schema_name = fk.schema_name) - AS more_info - FROM #ForeignKeys fk - WHERE ([delete_referential_action_desc] <> N'NO_ACTION' - OR [update_referential_action_desc] <> N'NO_ACTION') - OPTION ( RECOMPILE ); +CREATE TABLE #grouped_interval +( + flat_date DATE NULL, + start_range DATETIME NULL, + end_range DATETIME NULL, + total_avg_duration_ms DECIMAL(38, 2) NULL, + total_avg_cpu_time_ms DECIMAL(38, 2) NULL, + total_avg_logical_io_reads_mb DECIMAL(38, 2) NULL, + total_avg_physical_io_reads_mb DECIMAL(38, 2) NULL, + total_avg_logical_io_writes_mb DECIMAL(38, 2) NULL, + total_avg_query_max_used_memory_mb DECIMAL(38, 2) NULL, + total_rowcount DECIMAL(38, 2) NULL, + total_count_executions BIGINT NULL, + total_avg_log_bytes_mb DECIMAL(38, 2) NULL, + total_avg_tempdb_space DECIMAL(38, 2) NULL, + total_max_duration_ms DECIMAL(38, 2) NULL, + total_max_cpu_time_ms DECIMAL(38, 2) NULL, + total_max_logical_io_reads_mb DECIMAL(38, 2) NULL, + total_max_physical_io_reads_mb DECIMAL(38, 2) NULL, + total_max_logical_io_writes_mb DECIMAL(38, 2) NULL, + total_max_query_max_used_memory_mb DECIMAL(38, 2) NULL, + total_max_log_bytes_mb DECIMAL(38, 2) NULL, + total_max_tempdb_space DECIMAL(38, 2) NULL, + INDEX gi_ix_dates CLUSTERED (start_range, end_range) +); - RAISERROR(N'check_id 73: In-Memory OLTP', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 73 AS check_id, - i.index_sanity_id, - 150 AS Priority, - N'Abnormal Psychology' AS findings_group, - N'In-Memory OLTP' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, - i.db_schema_object_indexid AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - ISNULL(sz.index_size_summary,'') AS index_size_summary - FROM #IndexSanity AS i - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.is_in_memory_oltp = 1 - OPTION ( RECOMPILE ); +/* +These are the plans we focus on based on what we find in the grouped intervals +*/ +DROP TABLE IF EXISTS #working_plans; - RAISERROR(N'check_id 74: Identity column with unusual seed', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 74 AS check_id, - i.index_sanity_id, - 200 AS Priority, - N'Abnormal Psychology' AS findings_group, - N'Identity Column Using a Negative Seed or Increment Other Than 1' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, - i.db_schema_object_name + N'.' + QUOTENAME(ic.column_name) - + N' is an identity with type ' + ic.system_type_name - + N', last value of ' - + ISNULL((CONVERT(NVARCHAR(256),CAST(ic.last_value AS DECIMAL(38,0)), 1)),N'NULL') - + N', seed of ' - + ISNULL((CONVERT(NVARCHAR(256),CAST(ic.seed_value AS DECIMAL(38,0)), 1)),N'NULL') - + N', increment of ' + CAST(ic.increment_value AS NVARCHAR(256)) - + N', and range of ' + - CASE ic.system_type_name WHEN 'int' THEN N'+/- 2,147,483,647' - WHEN 'smallint' THEN N'+/- 32,768' - WHEN 'tinyint' THEN N'0 to 255' - ELSE 'unknown' - END - AS details, - i.index_definition, - secret_columns, - ISNULL(i.index_usage_summary,''), - ISNULL(ip.index_size_summary,'') - FROM #IndexSanity i - JOIN #IndexColumns ic ON - i.object_id=ic.object_id - AND i.database_id = ic.database_id - AND i.schema_name = ic.schema_name - AND i.index_id IN (0,1) /* heaps and cx only */ - AND ic.is_identity=1 - AND ic.system_type_name IN ('tinyint', 'smallint', 'int') - JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id - WHERE i.index_id IN (1,0) - AND (ic.seed_value < 0 OR ic.increment_value <> 1) - ORDER BY finding, details DESC - OPTION ( RECOMPILE ); +CREATE TABLE #working_plans +( + plan_id BIGINT, + query_id BIGINT, + pattern NVARCHAR(258), + INDEX wp_ix_ids CLUSTERED (plan_id, query_id) +); - ---------------------------------------- - --Workaholics: Check_id 80-89 - ---------------------------------------- - RAISERROR(N'check_id 80: Most scanned indexes (index_usage_stats)', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) +/* +These are the gathered metrics we get from query store to generate some warnings and help you find your worst offenders +*/ +DROP TABLE IF EXISTS #working_metrics; - --Workaholics according to index_usage_stats - --This isn't perfect: it mentions the number of scans present in a plan - --A "scan" isn't necessarily a full scan, but hey, we gotta do the best with what we've got. - --in the case of things like indexed views, the operator might be in the plan but never executed - SELECT TOP 5 - 80 AS check_id, - i.index_sanity_id AS index_sanity_id, - 200 AS Priority, - N'Workaholics' AS findings_group, - N'Scan-a-lots (index-usage-stats)' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/Workaholics' AS URL, - REPLACE(CONVERT( NVARCHAR(50),CAST(i.user_scans AS MONEY),1),'.00','') - + N' scans against ' + i.db_schema_object_indexid - + N'. Latest scan: ' + ISNULL(CAST(i.last_user_scan AS NVARCHAR(128)),'?') + N'. ' - + N'ScanFactor=' + CAST(((i.user_scans * iss.total_reserved_MB)/1000000.) AS NVARCHAR(256)) AS details, - ISNULL(i.key_column_names_with_sort_order,'N/A') AS index_definition, - ISNULL(i.secret_columns,'') AS secret_columns, - i.index_usage_summary AS index_usage_summary, - iss.index_size_summary AS index_size_summary - FROM #IndexSanity i - JOIN #IndexSanitySize iss ON i.index_sanity_id=iss.index_sanity_id - WHERE ISNULL(i.user_scans,0) > 0 - ORDER BY i.user_scans * iss.total_reserved_MB DESC - OPTION ( RECOMPILE ); +CREATE TABLE #working_metrics +( + database_name NVARCHAR(258), + plan_id BIGINT, + query_id BIGINT, + query_id_all_plan_ids VARCHAR(8000), + /*these columns are from query_store_query*/ + proc_or_function_name NVARCHAR(258), + batch_sql_handle VARBINARY(64), + query_hash BINARY(8), + query_parameterization_type_desc NVARCHAR(258), + parameter_sniffing_symptoms NVARCHAR(4000), + count_compiles BIGINT, + avg_compile_duration DECIMAL(38,2), + last_compile_duration DECIMAL(38,2), + avg_bind_duration DECIMAL(38,2), + last_bind_duration DECIMAL(38,2), + avg_bind_cpu_time DECIMAL(38,2), + last_bind_cpu_time DECIMAL(38,2), + avg_optimize_duration DECIMAL(38,2), + last_optimize_duration DECIMAL(38,2), + avg_optimize_cpu_time DECIMAL(38,2), + last_optimize_cpu_time DECIMAL(38,2), + avg_compile_memory_kb DECIMAL(38,2), + last_compile_memory_kb DECIMAL(38,2), + /*These come from query_store_runtime_stats*/ + execution_type_desc NVARCHAR(128), + first_execution_time DATETIME2, + last_execution_time DATETIME2, + count_executions BIGINT, + avg_duration DECIMAL(38,2) , + last_duration DECIMAL(38,2), + min_duration DECIMAL(38,2), + max_duration DECIMAL(38,2), + avg_cpu_time DECIMAL(38,2), + last_cpu_time DECIMAL(38,2), + min_cpu_time DECIMAL(38,2), + max_cpu_time DECIMAL(38,2), + avg_logical_io_reads DECIMAL(38,2), + last_logical_io_reads DECIMAL(38,2), + min_logical_io_reads DECIMAL(38,2), + max_logical_io_reads DECIMAL(38,2), + avg_logical_io_writes DECIMAL(38,2), + last_logical_io_writes DECIMAL(38,2), + min_logical_io_writes DECIMAL(38,2), + max_logical_io_writes DECIMAL(38,2), + avg_physical_io_reads DECIMAL(38,2), + last_physical_io_reads DECIMAL(38,2), + min_physical_io_reads DECIMAL(38,2), + max_physical_io_reads DECIMAL(38,2), + avg_clr_time DECIMAL(38,2), + last_clr_time DECIMAL(38,2), + min_clr_time DECIMAL(38,2), + max_clr_time DECIMAL(38,2), + avg_dop BIGINT, + last_dop BIGINT, + min_dop BIGINT, + max_dop BIGINT, + avg_query_max_used_memory DECIMAL(38,2), + last_query_max_used_memory DECIMAL(38,2), + min_query_max_used_memory DECIMAL(38,2), + max_query_max_used_memory DECIMAL(38,2), + avg_rowcount DECIMAL(38,2), + last_rowcount DECIMAL(38,2), + min_rowcount DECIMAL(38,2), + max_rowcount DECIMAL(38,2), + /*These are 2017 only, AFAIK*/ + avg_num_physical_io_reads DECIMAL(38,2), + last_num_physical_io_reads DECIMAL(38,2), + min_num_physical_io_reads DECIMAL(38,2), + max_num_physical_io_reads DECIMAL(38,2), + avg_log_bytes_used DECIMAL(38,2), + last_log_bytes_used DECIMAL(38,2), + min_log_bytes_used DECIMAL(38,2), + max_log_bytes_used DECIMAL(38,2), + avg_tempdb_space_used DECIMAL(38,2), + last_tempdb_space_used DECIMAL(38,2), + min_tempdb_space_used DECIMAL(38,2), + max_tempdb_space_used DECIMAL(38,2), + /*These are computed columns to make some stuff easier down the line*/ + total_compile_duration AS avg_compile_duration * count_compiles, + total_bind_duration AS avg_bind_duration * count_compiles, + total_bind_cpu_time AS avg_bind_cpu_time * count_compiles, + total_optimize_duration AS avg_optimize_duration * count_compiles, + total_optimize_cpu_time AS avg_optimize_cpu_time * count_compiles, + total_compile_memory_kb AS avg_compile_memory_kb * count_compiles, + total_duration AS avg_duration * count_executions, + total_cpu_time AS avg_cpu_time * count_executions, + total_logical_io_reads AS avg_logical_io_reads * count_executions, + total_logical_io_writes AS avg_logical_io_writes * count_executions, + total_physical_io_reads AS avg_physical_io_reads * count_executions, + total_clr_time AS avg_clr_time * count_executions, + total_query_max_used_memory AS avg_query_max_used_memory * count_executions, + total_rowcount AS avg_rowcount * count_executions, + total_num_physical_io_reads AS avg_num_physical_io_reads * count_executions, + total_log_bytes_used AS avg_log_bytes_used * count_executions, + total_tempdb_space_used AS avg_tempdb_space_used * count_executions, + xpm AS NULLIF(count_executions, 0) / NULLIF(DATEDIFF(MINUTE, first_execution_time, last_execution_time), 0), + percent_memory_grant_used AS CONVERT(MONEY, ISNULL(NULLIF(( max_query_max_used_memory * 1.00 ), 0) / NULLIF(min_query_max_used_memory, 0), 0) * 100.), + INDEX wm_ix_ids CLUSTERED (plan_id, query_id, query_hash) +); - RAISERROR(N'check_id 81: Top recent accesses (op stats)', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - --Workaholics according to index_operational_stats - --This isn't perfect either: range_scan_count contains full scans, partial scans, even seeks in nested loop ops - --But this can help bubble up some most-accessed tables - SELECT TOP 5 - 81 AS check_id, - i.index_sanity_id AS index_sanity_id, - 200 AS Priority, - N'Workaholics' AS findings_group, - N'Top Recent Accesses (index-op-stats)' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/Workaholics' AS URL, - ISNULL(REPLACE( - CONVERT(NVARCHAR(50),CAST((iss.total_range_scan_count + iss.total_singleton_lookup_count) AS MONEY),1), - N'.00',N'') - + N' uses of ' + i.db_schema_object_indexid + N'. ' - + REPLACE(CONVERT(NVARCHAR(50), CAST(iss.total_range_scan_count AS MONEY),1),N'.00',N'') + N' scans or seeks. ' - + REPLACE(CONVERT(NVARCHAR(50), CAST(iss.total_singleton_lookup_count AS MONEY), 1),N'.00',N'') + N' singleton lookups. ' - + N'OpStatsFactor=' + CAST(((((iss.total_range_scan_count + iss.total_singleton_lookup_count) * iss.total_reserved_MB))/1000000.) AS VARCHAR(256)),'') AS details, - ISNULL(i.key_column_names_with_sort_order,'N/A') AS index_definition, - ISNULL(i.secret_columns,'') AS secret_columns, - i.index_usage_summary AS index_usage_summary, - iss.index_size_summary AS index_size_summary - FROM #IndexSanity i - JOIN #IndexSanitySize iss ON i.index_sanity_id=iss.index_sanity_id - WHERE (ISNULL(iss.total_range_scan_count,0) > 0 OR ISNULL(iss.total_singleton_lookup_count,0) > 0) - ORDER BY ((iss.total_range_scan_count + iss.total_singleton_lookup_count) * iss.total_reserved_MB) DESC - OPTION ( RECOMPILE ); +/* +This is where we store some additional metrics, along with the query plan and text +*/ +DROP TABLE IF EXISTS #working_plan_text; +CREATE TABLE #working_plan_text +( + database_name NVARCHAR(258), + plan_id BIGINT, + query_id BIGINT, + /*These are from query_store_plan*/ + plan_group_id BIGINT, + engine_version NVARCHAR(64), + compatibility_level INT, + query_plan_hash BINARY(8), + query_plan_xml XML, + is_online_index_plan BIT, + is_trivial_plan BIT, + is_parallel_plan BIT, + is_forced_plan BIT, + is_natively_compiled BIT, + force_failure_count BIGINT, + last_force_failure_reason_desc NVARCHAR(258), + count_compiles BIGINT, + initial_compile_start_time DATETIME2, + last_compile_start_time DATETIME2, + last_execution_time DATETIME2, + avg_compile_duration DECIMAL(38,2), + last_compile_duration BIGINT, + /*These are from query_store_query*/ + query_sql_text NVARCHAR(MAX), + statement_sql_handle VARBINARY(64), + is_part_of_encrypted_module BIT, + has_restricted_text BIT, + /*This is from query_context_settings*/ + context_settings NVARCHAR(512), + /*This is from #working_plans*/ + pattern NVARCHAR(512), + top_three_waits NVARCHAR(MAX), + INDEX wpt_ix_ids CLUSTERED (plan_id, query_id, query_plan_hash) +); - RAISERROR(N'check_id 93: Statistics with filters', 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 93 AS check_id, - 200 AS Priority, - 'Functioning Statistaholics' AS findings_group, - 'Filter Fixation', - s.database_name, - 'https://www.brentozar.com/go/stats' AS URL, - 'The statistic ' + QUOTENAME(s.statistics_name) + ' is filtered on [' + s.filter_definition + ']. It could be part of a filtered index, or just a filtered statistic. This is purely informational.' 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_filter = 1 - OPTION ( RECOMPILE ); +/* +This is where we store warnings that we generate from the XML and metrics +*/ +DROP TABLE IF EXISTS #working_warnings; - RAISERROR(N'check_id 100: Computed Columns that are not Persisted.', 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 100 AS check_id, - 200 AS Priority, - 'Cold Calculators' AS findings_group, - 'Definition Defeatists' AS finding, - cc.database_name, - '' AS URL, - 'The computed column ' + QUOTENAME(cc.column_name) + ' on ' + QUOTENAME(cc.schema_name) + '.' + QUOTENAME(cc.table_name) + ' is not persisted, which means it will be calculated when a query runs.' + - 'You can change this with the following command, if the definition is deterministic: ALTER TABLE ' + QUOTENAME(cc.schema_name) + '.' + QUOTENAME(cc.table_name) + ' ALTER COLUMN ' + cc.column_name + - ' ADD PERSISTED' AS details, - cc.column_definition, - 'N/A' AS secret_columns, - 'N/A' AS index_usage_summary, - 'N/A' AS index_size_summary - FROM #ComputedColumns AS cc - WHERE cc.is_persisted = 0 - OPTION ( RECOMPILE ); +CREATE TABLE #working_warnings +( + plan_id BIGINT, + query_id BIGINT, + query_hash BINARY(8), + sql_handle VARBINARY(64), + proc_or_function_name NVARCHAR(258), + plan_multiple_plans BIT, + is_forced_plan BIT, + is_forced_parameterized BIT, + is_cursor BIT, + is_optimistic_cursor BIT, + is_forward_only_cursor BIT, + is_fast_forward_cursor BIT, + is_cursor_dynamic BIT, + is_parallel BIT, + is_forced_serial BIT, + is_key_lookup_expensive BIT, + key_lookup_cost FLOAT, + is_remote_query_expensive BIT, + remote_query_cost FLOAT, + frequent_execution BIT, + parameter_sniffing BIT, + unparameterized_query BIT, + near_parallel BIT, + plan_warnings BIT, + long_running BIT, + downlevel_estimator BIT, + implicit_conversions BIT, + tvf_estimate BIT, + compile_timeout BIT, + compile_memory_limit_exceeded BIT, + warning_no_join_predicate BIT, + query_cost FLOAT, + missing_index_count INT, + unmatched_index_count INT, + is_trivial BIT, + trace_flags_session NVARCHAR(1000), + is_unused_grant BIT, + function_count INT, + clr_function_count INT, + is_table_variable BIT, + no_stats_warning BIT, + relop_warnings BIT, + is_table_scan BIT, + backwards_scan BIT, + forced_index BIT, + forced_seek BIT, + forced_scan BIT, + columnstore_row_mode BIT, + is_computed_scalar BIT , + is_sort_expensive BIT, + sort_cost FLOAT, + is_computed_filter BIT, + op_name NVARCHAR(100) NULL, + index_insert_count INT NULL, + index_update_count INT NULL, + index_delete_count INT NULL, + cx_insert_count INT NULL, + cx_update_count INT NULL, + cx_delete_count INT NULL, + table_insert_count INT NULL, + table_update_count INT NULL, + table_delete_count INT NULL, + index_ops AS (index_insert_count + index_update_count + index_delete_count + + cx_insert_count + cx_update_count + cx_delete_count + + table_insert_count + table_update_count + table_delete_count), + is_row_level BIT, + is_spatial BIT, + index_dml BIT, + table_dml BIT, + long_running_low_cpu BIT, + low_cost_high_cpu BIT, + stale_stats BIT, + is_adaptive BIT, + is_slow_plan BIT, + is_compile_more BIT, + index_spool_cost FLOAT, + index_spool_rows FLOAT, + is_spool_expensive BIT, + is_spool_more_rows BIT, + estimated_rows FLOAT, + is_bad_estimate BIT, + is_big_log BIT, + is_big_tempdb BIT, + is_paul_white_electric BIT, + is_row_goal BIT, + is_mstvf BIT, + is_mm_join BIT, + is_nonsargable BIT, + busy_loops BIT, + tvf_join BIT, + implicit_conversion_info XML, + cached_execution_parameters XML, + missing_indexes XML, + warnings NVARCHAR(4000) + INDEX ww_ix_ids CLUSTERED (plan_id, query_id, query_hash, sql_handle) +); - RAISERROR(N'check_id 110: Temporal Tables.', 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 110 AS check_id, - 200 AS Priority, - 'Abnormal Psychology' AS findings_group, - 'Temporal Tables', - t.database_name, - '' AS URL, - 'The table ' + QUOTENAME(t.schema_name) + '.' + QUOTENAME(t.table_name) + ' is a temporal table, with rows versioned in ' - + QUOTENAME(t.history_schema_name) + '.' + QUOTENAME(t.history_table_name) + ' on History columns ' + QUOTENAME(t.start_column_name) + ' and ' + QUOTENAME(t.end_column_name) + '.' - AS details, - '' AS index_definition, - 'N/A' AS secret_columns, - 'N/A' AS index_usage_summary, - 'N/A' AS index_size_summary - FROM #TemporalTables AS t - OPTION ( RECOMPILE ); +DROP TABLE IF EXISTS #working_wait_stats; + +CREATE TABLE #working_wait_stats +( + plan_id BIGINT, + wait_category TINYINT, + wait_category_desc NVARCHAR(258), + total_query_wait_time_ms BIGINT, + avg_query_wait_time_ms DECIMAL(38, 2), + last_query_wait_time_ms BIGINT, + min_query_wait_time_ms BIGINT, + max_query_wait_time_ms BIGINT, + wait_category_mapped AS CASE wait_category + WHEN 0 THEN N'UNKNOWN' + WHEN 1 THEN N'SOS_SCHEDULER_YIELD' + WHEN 2 THEN N'THREADPOOL' + WHEN 3 THEN N'LCK_M_%' + WHEN 4 THEN N'LATCH_%' + WHEN 5 THEN N'PAGELATCH_%' + WHEN 6 THEN N'PAGEIOLATCH_%' + WHEN 7 THEN N'RESOURCE_SEMAPHORE_QUERY_COMPILE' + WHEN 8 THEN N'CLR%, SQLCLR%' + WHEN 9 THEN N'DBMIRROR%' + WHEN 10 THEN N'XACT%, DTC%, TRAN_MARKLATCH_%, MSQL_XACT_%, TRANSACTION_MUTEX' + WHEN 11 THEN N'SLEEP_%, LAZYWRITER_SLEEP, SQLTRACE_BUFFER_FLUSH, SQLTRACE_INCREMENTAL_FLUSH_SLEEP, SQLTRACE_WAIT_ENTRIES, FT_IFTS_SCHEDULER_IDLE_WAIT, XE_DISPATCHER_WAIT, REQUEST_FOR_DEADLOCK_SEARCH, LOGMGR_QUEUE, ONDEMAND_TASK_QUEUE, CHECKPOINT_QUEUE, XE_TIMER_EVENT' + WHEN 12 THEN N'PREEMPTIVE_%' + WHEN 13 THEN N'BROKER_% (but not BROKER_RECEIVE_WAITFOR)' + WHEN 14 THEN N'LOGMGR, LOGBUFFER, LOGMGR_RESERVE_APPEND, LOGMGR_FLUSH, LOGMGR_PMM_LOG, CHKPT, WRITELOG' + WHEN 15 THEN N'ASYNC_NETWORK_IO, NET_WAITFOR_PACKET, PROXY_NETWORK_IO, EXTERNAL_SCRIPT_NETWORK_IOF' + WHEN 16 THEN N'CXPACKET, EXCHANGE, CXCONSUMER' + WHEN 17 THEN N'RESOURCE_SEMAPHORE, CMEMTHREAD, CMEMPARTITIONED, EE_PMOLOCK, MEMORY_ALLOCATION_EXT, RESERVED_MEMORY_ALLOCATION_EXT, MEMORY_GRANT_UPDATE' + WHEN 18 THEN N'WAITFOR, WAIT_FOR_RESULTS, BROKER_RECEIVE_WAITFOR' + WHEN 19 THEN N'TRACEWRITE, SQLTRACE_LOCK, SQLTRACE_FILE_BUFFER, SQLTRACE_FILE_WRITE_IO_COMPLETION, SQLTRACE_FILE_READ_IO_COMPLETION, SQLTRACE_PENDING_BUFFER_WRITERS, SQLTRACE_SHUTDOWN, QUERY_TRACEOUT, TRACE_EVTNOTIFF' + WHEN 20 THEN N'FT_RESTART_CRAWL, FULLTEXT GATHERER, MSSEARCH, FT_METADATA_MUTEX, FT_IFTSHC_MUTEX, FT_IFTSISM_MUTEX, FT_IFTS_RWLOCK, FT_COMPROWSET_RWLOCK, FT_MASTER_MERGE, FT_PROPERTYLIST_CACHE, FT_MASTER_MERGE_COORDINATOR, PWAIT_RESOURCE_SEMAPHORE_FT_PARALLEL_QUERY_SYNC' + WHEN 21 THEN N'ASYNC_IO_COMPLETION, IO_COMPLETION, BACKUPIO, WRITE_COMPLETION, IO_QUEUE_LIMIT, IO_RETRY' + WHEN 22 THEN N'SE_REPL_%, REPL_%, HADR_% (but not HADR_THROTTLE_LOG_RATE_GOVERNOR), PWAIT_HADR_%, REPLICA_WRITES, FCB_REPLICA_WRITE, FCB_REPLICA_READ, PWAIT_HADRSIM' + WHEN 23 THEN N'LOG_RATE_GOVERNOR, POOL_LOG_RATE_GOVERNOR, HADR_THROTTLE_LOG_RATE_GOVERNOR, INSTANCE_LOG_RATE_GOVERNOR' + END, + INDEX wws_ix_ids CLUSTERED ( plan_id) +); + +/* +The next three tables hold plan XML parsed out to different degrees +*/ +DROP TABLE IF EXISTS #statements; +CREATE TABLE #statements +( + plan_id BIGINT, + query_id BIGINT, + query_hash BINARY(8), + sql_handle VARBINARY(64), + statement XML, + is_cursor BIT + INDEX s_ix_ids CLUSTERED (plan_id, query_id, query_hash, sql_handle) +); - END /* IF @Mode = 4 */ +DROP TABLE IF EXISTS #query_plan; +CREATE TABLE #query_plan +( + plan_id BIGINT, + query_id BIGINT, + query_hash BINARY(8), + sql_handle VARBINARY(64), + query_plan XML, + INDEX qp_ix_ids CLUSTERED (plan_id, query_id, query_hash, sql_handle) +); +DROP TABLE IF EXISTS #relop; +CREATE TABLE #relop +( + plan_id BIGINT, + query_id BIGINT, + query_hash BINARY(8), + sql_handle VARBINARY(64), + relop XML, + INDEX ix_ids CLUSTERED (plan_id, query_id, query_hash, sql_handle) +); +DROP TABLE IF EXISTS #plan_cost; +CREATE TABLE #plan_cost +( + query_plan_cost DECIMAL(38,2), + sql_handle VARBINARY(64), + plan_id INT, + INDEX px_ix_ids CLUSTERED (sql_handle, plan_id) +); +DROP TABLE IF EXISTS #est_rows; +CREATE TABLE #est_rows +( + estimated_rows DECIMAL(38,2), + query_hash BINARY(8), + INDEX px_ix_ids CLUSTERED (query_hash) +); +DROP TABLE IF EXISTS #stats_agg; +CREATE TABLE #stats_agg +( + sql_handle VARBINARY(64), + last_update DATETIME2, + modification_count BIGINT, + sampling_percent DECIMAL(38, 2), + [statistics] NVARCHAR(258), + [table] NVARCHAR(258), + [schema] NVARCHAR(258), + [database] NVARCHAR(258), + INDEX sa_ix_ids CLUSTERED (sql_handle) +); +DROP TABLE IF EXISTS #trace_flags; +CREATE TABLE #trace_flags +( + sql_handle VARBINARY(54), + global_trace_flags NVARCHAR(4000), + session_trace_flags NVARCHAR(4000), + INDEX tf_ix_ids CLUSTERED (sql_handle) +); +DROP TABLE IF EXISTS #warning_results; +CREATE TABLE #warning_results +( + ID INT IDENTITY(1,1) PRIMARY KEY CLUSTERED, + CheckID INT, + Priority TINYINT, + FindingsGroup NVARCHAR(50), + Finding NVARCHAR(200), + URL NVARCHAR(200), + Details NVARCHAR(4000) +); +/*These next three tables hold information about implicit conversion and cached parameters */ +DROP TABLE IF EXISTS #stored_proc_info; +CREATE TABLE #stored_proc_info +( + sql_handle VARBINARY(64), + query_hash BINARY(8), + variable_name NVARCHAR(258), + variable_datatype NVARCHAR(258), + converted_column_name NVARCHAR(258), + compile_time_value NVARCHAR(258), + proc_name NVARCHAR(1000), + column_name NVARCHAR(4000), + converted_to NVARCHAR(258), + set_options NVARCHAR(1000) + INDEX tf_ix_ids CLUSTERED (sql_handle, query_hash) +); +DROP TABLE IF EXISTS #variable_info; +CREATE TABLE #variable_info +( + query_hash BINARY(8), + sql_handle VARBINARY(64), + proc_name NVARCHAR(1000), + variable_name NVARCHAR(258), + variable_datatype NVARCHAR(258), + compile_time_value NVARCHAR(258), + INDEX vif_ix_ids CLUSTERED (sql_handle, query_hash) +); +DROP TABLE IF EXISTS #conversion_info; +CREATE TABLE #conversion_info +( + query_hash BINARY(8), + sql_handle VARBINARY(64), + proc_name NVARCHAR(128), + expression NVARCHAR(4000), + at_charindex AS CHARINDEX('@', expression), + bracket_charindex AS CHARINDEX(']', expression, CHARINDEX('@', expression)) - CHARINDEX('@', expression), + comma_charindex AS CHARINDEX(',', expression) + 1, + second_comma_charindex AS + CHARINDEX(',', expression, CHARINDEX(',', expression) + 1) - CHARINDEX(',', expression) - 1, + equal_charindex AS CHARINDEX('=', expression) + 1, + paren_charindex AS CHARINDEX('(', expression) + 1, + comma_paren_charindex AS + CHARINDEX(',', expression, CHARINDEX('(', expression) + 1) - CHARINDEX('(', expression) - 1, + convert_implicit_charindex AS CHARINDEX('=CONVERT_IMPLICIT', expression), + INDEX cif_ix_ids CLUSTERED (sql_handle, query_hash) +); +/* These tables support the Missing Index details clickable*/ - - RAISERROR(N'Insert a row to help people find help', 0,1) WITH NOWAIT; - IF DATEDIFF(MM, @VersionDate, GETDATE()) > 6 - BEGIN - INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, URL, details, index_definition, - index_usage_summary, index_size_summary ) - VALUES ( -1, 0 , - 'Outdated sp_BlitzIndex', 'sp_BlitzIndex is Over 6 Months Old', 'http://FirstResponderKit.org/', - 'Fine wine gets better with age, but this ' + @ScriptVersionName + ' is more like bad cheese. Time to get a new one.', - @DaysUptimeInsertValue,N'',N'' - ); - END; - IF EXISTS(SELECT * FROM #BlitzIndexResults) - BEGIN - INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, URL, details, index_definition, - index_usage_summary, index_size_summary ) - VALUES ( -1, 0 , - @ScriptVersionName, - CASE WHEN @GetAllDatabases = 1 THEN N'All Databases' ELSE N'Database ' + QUOTENAME(@DatabaseName) + N' as of ' + CONVERT(NVARCHAR(16),GETDATE(),121) END, - N'From Your Community Volunteers' , N'http://FirstResponderKit.org' , - @DaysUptimeInsertValue,N'',N'' - ); - END; - ELSE IF @Mode = 0 OR (@GetAllDatabases = 1 AND @Mode <> 4) - BEGIN - INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, URL, details, index_definition, - index_usage_summary, index_size_summary ) - VALUES ( -1, 0 , - @ScriptVersionName, - CASE WHEN @GetAllDatabases = 1 THEN N'All Databases' ELSE N'Database ' + QUOTENAME(@DatabaseName) + N' as of ' + CONVERT(NVARCHAR(16),GETDATE(),121) END, - N'From Your Community Volunteers' , N'http://FirstResponderKit.org' , - @DaysUptimeInsertValue, N'',N'' - ); - INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, URL, details, index_definition, - index_usage_summary, index_size_summary ) - VALUES ( 1, 0 , - N'No Major Problems Found', - N'Nice Work!', - N'http://FirstResponderKit.org', - N'Consider running with @Mode = 4 in individual databases (not all) for more detailed diagnostics.', - N'The default Mode 0 only looks for very serious index issues.', - @DaysUptimeInsertValue, N'' - ); +DROP TABLE IF EXISTS #missing_index_xml; - END; - ELSE - BEGIN - INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, URL, details, index_definition, - index_usage_summary, index_size_summary ) - VALUES ( -1, 0 , - @ScriptVersionName, - CASE WHEN @GetAllDatabases = 1 THEN N'All Databases' ELSE N'Database ' + QUOTENAME(@DatabaseName) + N' as of ' + CONVERT(NVARCHAR(16),GETDATE(),121) END, - N'From Your Community Volunteers' , N'http://FirstResponderKit.org' , - @DaysUptimeInsertValue, N'',N'' - ); - INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, URL, details, index_definition, - index_usage_summary, index_size_summary ) - VALUES ( 1, 0 , - N'No Problems Found', - N'Nice job! Or more likely, you have a nearly empty database.', - N'http://FirstResponderKit.org', 'Time to go read some blog posts.', - @DaysUptimeInsertValue, N'', N'' - ); +CREATE TABLE #missing_index_xml +( + query_hash BINARY(8), + sql_handle VARBINARY(64), + impact FLOAT, + index_xml XML, + INDEX mix_ix_ids CLUSTERED (sql_handle, query_hash) +); - END; +DROP TABLE IF EXISTS #missing_index_schema; - RAISERROR(N'Returning results.', 0,1) WITH NOWAIT; - - /*Return results.*/ - IF (@Mode = 0) - BEGIN - IF(@OutputType <> 'NONE') - BEGIN - SELECT Priority, ISNULL(br.findings_group,N'') + - CASE WHEN ISNULL(br.finding,N'') <> N'' THEN N': ' ELSE N'' END - + br.finding AS [Finding], - br.[database_name] AS [Database Name], - br.details AS [Details: schema.table.index(indexid)], - br.index_definition AS [Definition: [Property]] ColumnName {datatype maxbytes}], - ISNULL(br.secret_columns,'') AS [Secret Columns], - br.index_usage_summary AS [Usage], - br.index_size_summary AS [Size], - COALESCE(br.more_info,sn.more_info,'') AS [More Info], - br.URL, - COALESCE(br.create_tsql,ts.create_tsql,'') AS [Create TSQL] - FROM #BlitzIndexResults br - LEFT JOIN #IndexSanity sn ON - br.index_sanity_id=sn.index_sanity_id - LEFT JOIN #IndexCreateTsql ts ON - br.index_sanity_id=ts.index_sanity_id - ORDER BY br.Priority ASC, br.check_id ASC, br.blitz_result_id ASC, br.findings_group ASC - OPTION (RECOMPILE); - END; +CREATE TABLE #missing_index_schema +( + query_hash BINARY(8), + sql_handle VARBINARY(64), + impact FLOAT, + database_name NVARCHAR(128), + schema_name NVARCHAR(128), + table_name NVARCHAR(128), + index_xml XML, + INDEX mis_ix_ids CLUSTERED (sql_handle, query_hash) +); - END; - ELSE IF (@Mode = 4) - IF(@OutputType <> 'NONE') - BEGIN - SELECT Priority, ISNULL(br.findings_group,N'') + - CASE WHEN ISNULL(br.finding,N'') <> N'' THEN N': ' ELSE N'' END - + br.finding AS [Finding], - br.[database_name] AS [Database Name], - br.details AS [Details: schema.table.index(indexid)], - br.index_definition AS [Definition: [Property]] ColumnName {datatype maxbytes}], - ISNULL(br.secret_columns,'') AS [Secret Columns], - br.index_usage_summary AS [Usage], - br.index_size_summary AS [Size], - COALESCE(br.more_info,sn.more_info,'') AS [More Info], - br.URL, - COALESCE(br.create_tsql,ts.create_tsql,'') AS [Create TSQL] - FROM #BlitzIndexResults br - LEFT JOIN #IndexSanity sn ON - br.index_sanity_id=sn.index_sanity_id - LEFT JOIN #IndexCreateTsql ts ON - br.index_sanity_id=ts.index_sanity_id - ORDER BY br.Priority ASC, br.check_id ASC, br.blitz_result_id ASC, br.findings_group ASC - OPTION (RECOMPILE); - END; -END /* End @Mode=0 or 4 (diagnose)*/ +DROP TABLE IF EXISTS #missing_index_usage; -ELSE IF (@Mode=1) /*Summarize*/ - BEGIN - --This mode is to give some overall stats on the database. - IF(@OutputType <> 'NONE') - BEGIN - RAISERROR(N'@Mode=1, we are summarizing.', 0,1) WITH NOWAIT; +CREATE TABLE #missing_index_usage +( + query_hash BINARY(8), + sql_handle VARBINARY(64), + impact FLOAT, + database_name NVARCHAR(128), + schema_name NVARCHAR(128), + table_name NVARCHAR(128), + usage NVARCHAR(128), + index_xml XML, + INDEX miu_ix_ids CLUSTERED (sql_handle, query_hash) +); - SELECT DB_NAME(i.database_id) AS [Database Name], - CAST((COUNT(*)) AS NVARCHAR(256)) AS [Number Objects], - CAST(CAST(SUM(sz.total_reserved_MB)/ - 1024. AS NUMERIC(29,1)) AS NVARCHAR(500)) AS [All GB], - CAST(CAST(SUM(sz.total_reserved_LOB_MB)/ - 1024. AS NUMERIC(29,1)) AS NVARCHAR(500)) AS [LOB GB], - CAST(CAST(SUM(sz.total_reserved_row_overflow_MB)/ - 1024. AS NUMERIC(29,1)) AS NVARCHAR(500)) AS [Row Overflow GB], - CAST(SUM(CASE WHEN index_id=1 THEN 1 ELSE 0 END)AS NVARCHAR(50)) AS [Clustered Tables], - CAST(SUM(CASE WHEN index_id=1 THEN sz.total_reserved_MB ELSE 0 END) - /1024. AS NUMERIC(29,1)) AS [Clustered Tables GB], - SUM(CASE WHEN index_id NOT IN (0,1) THEN 1 ELSE 0 END) AS [NC Indexes], - CAST(SUM(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) - /1024. AS NUMERIC(29,1)) AS [NC Indexes GB], - CASE WHEN SUM(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) > 0 THEN - CAST(SUM(CASE WHEN index_id IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) - / SUM(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) AS NUMERIC(29,1)) - ELSE 0 END AS [ratio table: NC Indexes], - SUM(CASE WHEN index_id=0 THEN 1 ELSE 0 END) AS [Heaps], - CAST(SUM(CASE WHEN index_id=0 THEN sz.total_reserved_MB ELSE 0 END) - /1024. AS NUMERIC(29,1)) AS [Heaps GB], - SUM(CASE WHEN index_id IN (0,1) AND partition_key_column_name IS NOT NULL THEN 1 ELSE 0 END) AS [Partitioned Tables], - SUM(CASE WHEN index_id NOT IN (0,1) AND partition_key_column_name IS NOT NULL THEN 1 ELSE 0 END) AS [Partitioned NCs], - CAST(SUM(CASE WHEN partition_key_column_name IS NOT NULL THEN sz.total_reserved_MB ELSE 0 END)/1024. AS NUMERIC(29,1)) AS [Partitioned GB], - SUM(CASE WHEN filter_definition <> '' THEN 1 ELSE 0 END) AS [Filtered Indexes], - SUM(CASE WHEN is_indexed_view=1 THEN 1 ELSE 0 END) AS [Indexed Views], - MAX(total_rows) AS [Max Row Count], - CAST(MAX(CASE WHEN index_id IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) - /1024. AS NUMERIC(29,1)) AS [Max Table GB], - CAST(MAX(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) - /1024. AS NUMERIC(29,1)) AS [Max NC Index GB], - SUM(CASE WHEN index_id IN (0,1) AND sz.total_reserved_MB > 1024 THEN 1 ELSE 0 END) AS [Count Tables > 1GB], - SUM(CASE WHEN index_id IN (0,1) AND sz.total_reserved_MB > 10240 THEN 1 ELSE 0 END) AS [Count Tables > 10GB], - SUM(CASE WHEN index_id IN (0,1) AND sz.total_reserved_MB > 102400 THEN 1 ELSE 0 END) AS [Count Tables > 100GB], - SUM(CASE WHEN index_id NOT IN (0,1) AND sz.total_reserved_MB > 1024 THEN 1 ELSE 0 END) AS [Count NCs > 1GB], - SUM(CASE WHEN index_id NOT IN (0,1) AND sz.total_reserved_MB > 10240 THEN 1 ELSE 0 END) AS [Count NCs > 10GB], - SUM(CASE WHEN index_id NOT IN (0,1) AND sz.total_reserved_MB > 102400 THEN 1 ELSE 0 END) AS [Count NCs > 100GB], - MIN(create_date) AS [Oldest Create Date], - MAX(create_date) AS [Most Recent Create Date], - MAX(modify_date) AS [Most Recent Modify Date], - 1 AS [Display Order] - FROM #IndexSanity AS i - --left join here so we don't lose disabled nc indexes - LEFT JOIN #IndexSanitySize AS sz - ON i.index_sanity_id=sz.index_sanity_id - GROUP BY DB_NAME(i.database_id) - UNION ALL - SELECT CASE WHEN @GetAllDatabases = 1 THEN N'All Databases' ELSE N'Database ' + N' as of ' + CONVERT(NVARCHAR(16),GETDATE(),121) END, - @ScriptVersionName, - N'From Your Community Volunteers' , - N'http://FirstResponderKit.org' , - @DaysUptimeInsertValue, - NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL, - NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL, - NULL,NULL,0 AS display_order - ORDER BY [Display Order] ASC - OPTION (RECOMPILE); - END; - - END; /* End @Mode=1 (summarize)*/ - ELSE IF (@Mode=2) /*Index Detail*/ - BEGIN - --This mode just spits out all the detail without filters. - --This supports slicing AND dicing in Excel - RAISERROR(N'@Mode=2, here''s the details on existing indexes.', 0,1) WITH NOWAIT; +DROP TABLE IF EXISTS #missing_index_detail; - - /* Checks if @OutputServerName is populated with a valid linked server, and that the database name specified is valid */ - DECLARE @ValidOutputServer BIT; - DECLARE @ValidOutputLocation BIT; - DECLARE @LinkedServerDBCheck NVARCHAR(2000); - DECLARE @ValidLinkedServerDB INT; - DECLARE @tmpdbchk TABLE (cnt INT); - DECLARE @StringToExecute NVARCHAR(MAX); - - IF @OutputServerName IS NOT NULL - BEGIN - IF (SUBSTRING(@OutputTableName, 2, 1) = '#') - BEGIN - RAISERROR('Due to the nature of temporary tables, outputting to a linked server requires a permanent table.', 16, 0); - END; - ELSE IF EXISTS (SELECT server_id FROM sys.servers WHERE QUOTENAME([name]) = @OutputServerName) - BEGIN - SET @LinkedServerDBCheck = 'SELECT 1 WHERE EXISTS (SELECT * FROM '+@OutputServerName+'.master.sys.databases WHERE QUOTENAME([name]) = '''+@OutputDatabaseName+''')'; - INSERT INTO @tmpdbchk EXEC sys.sp_executesql @LinkedServerDBCheck; - SET @ValidLinkedServerDB = (SELECT COUNT(*) FROM @tmpdbchk); - IF (@ValidLinkedServerDB > 0) - BEGIN - SET @ValidOutputServer = 1; - SET @ValidOutputLocation = 1; - END; - ELSE - RAISERROR('The specified database was not found on the output server', 16, 0); - END; - ELSE - BEGIN - RAISERROR('The specified output server was not found', 16, 0); - END; - END; - ELSE - BEGIN - IF (SUBSTRING(@OutputTableName, 2, 2) = '##') - BEGIN - SET @StringToExecute = N' IF (OBJECT_ID(''[tempdb].[dbo].@@@OutputTableName@@@'') IS NOT NULL) DROP TABLE @@@OutputTableName@@@'; - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); - EXEC(@StringToExecute); - - SET @OutputServerName = QUOTENAME(CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128))); - SET @OutputDatabaseName = '[tempdb]'; - SET @OutputSchemaName = '[dbo]'; - SET @ValidOutputLocation = 1; - END; - ELSE IF (SUBSTRING(@OutputTableName, 2, 1) = '#') - BEGIN - RAISERROR('Due to the nature of Dymamic SQL, only global (i.e. double pound (##)) temp tables are supported for @OutputTableName', 16, 0); - END; - ELSE IF @OutputDatabaseName IS NOT NULL - AND @OutputSchemaName IS NOT NULL - AND @OutputTableName IS NOT NULL - AND EXISTS ( SELECT * - FROM sys.databases - WHERE QUOTENAME([name]) = @OutputDatabaseName) - BEGIN - SET @ValidOutputLocation = 1; - SET @OutputServerName = QUOTENAME(CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128))); - END; - ELSE IF @OutputDatabaseName IS NOT NULL - AND @OutputSchemaName IS NOT NULL - AND @OutputTableName IS NOT NULL - AND NOT EXISTS ( SELECT * - FROM sys.databases - WHERE QUOTENAME([name]) = @OutputDatabaseName) - BEGIN - RAISERROR('The specified output database was not found on this server', 16, 0); - END; - ELSE - BEGIN - SET @ValidOutputLocation = 0; - END; - END; - - IF (@ValidOutputLocation = 0 AND @OutputType = 'NONE') - BEGIN - RAISERROR('Invalid output location and no output asked',12,1); - RETURN; - END; - - /* @OutputTableName lets us export the results to a permanent table */ - DECLARE @RunID UNIQUEIDENTIFIER; - SET @RunID = NEWID(); - - IF (@ValidOutputLocation = 1 AND COALESCE(@OutputServerName, @OutputDatabaseName, @OutputSchemaName, @OutputTableName) IS NOT NULL) - BEGIN - DECLARE @TableExists BIT; - DECLARE @SchemaExists BIT; - SET @StringToExecute = - N'SET @SchemaExists = 0; - SET @TableExists = 0; - IF EXISTS(SELECT * FROM @@@OutputServerName@@@.@@@OutputDatabaseName@@@.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''@@@OutputSchemaName@@@'') - SET @SchemaExists = 1 - IF EXISTS (SELECT * FROM @@@OutputServerName@@@.@@@OutputDatabaseName@@@.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''@@@OutputSchemaName@@@'' AND QUOTENAME(TABLE_NAME) = ''@@@OutputTableName@@@'') - SET @TableExists = 1'; - - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputServerName@@@', @OutputServerName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputSchemaName@@@', @OutputSchemaName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); - - EXEC sp_executesql @StringToExecute, N'@TableExists BIT OUTPUT, @SchemaExists BIT OUTPUT', @TableExists OUTPUT, @SchemaExists OUTPUT; - - IF @SchemaExists = 1 - BEGIN - IF @TableExists = 0 - BEGIN - SET @StringToExecute = - N'CREATE TABLE @@@OutputDatabaseName@@@.@@@OutputSchemaName@@@.@@@OutputTableName@@@ - ( - [id] INT IDENTITY(1,1) NOT NULL, - [run_id] UNIQUEIDENTIFIER, - [run_datetime] DATETIME, - [server_name] NVARCHAR(128), - [database_name] NVARCHAR(128), - [schema_name] NVARCHAR(128), - [table_name] NVARCHAR(128), - [index_name] NVARCHAR(128), - [Drop_Tsql] NVARCHAR(MAX), - [Create_Tsql] NVARCHAR(MAX), - [index_id] INT, - [db_schema_object_indexid] NVARCHAR(500), - [object_type] NVARCHAR(15), - [index_definition] NVARCHAR(MAX), - [key_column_names_with_sort_order] NVARCHAR(MAX), - [count_key_columns] INT, - [include_column_names] NVARCHAR(MAX), - [count_included_columns] INT, - [secret_columns] NVARCHAR(MAX), - [count_secret_columns] INT, - [partition_key_column_name] NVARCHAR(MAX), - [filter_definition] NVARCHAR(MAX), - [is_indexed_view] BIT, - [is_primary_key] BIT, - [is_XML] BIT, - [is_spatial] BIT, - [is_NC_columnstore] BIT, - [is_CX_columnstore] BIT, - [is_in_memory_oltp] BIT, - [is_disabled] BIT, - [is_hypothetical] BIT, - [is_padded] BIT, - [fill_factor] INT, - [is_referenced_by_foreign_key] BIT, - [last_user_seek] DATETIME, - [last_user_scan] DATETIME, - [last_user_lookup] DATETIME, - [last_user_update] DATETIME, - [total_reads] BIGINT, - [user_updates] BIGINT, - [reads_per_write] MONEY, - [index_usage_summary] NVARCHAR(200), - [total_singleton_lookup_count] BIGINT, - [total_range_scan_count] BIGINT, - [total_leaf_delete_count] BIGINT, - [total_leaf_update_count] BIGINT, - [index_op_stats] NVARCHAR(200), - [partition_count] INT, - [total_rows] BIGINT, - [total_reserved_MB] NUMERIC(29,2), - [total_reserved_LOB_MB] NUMERIC(29,2), - [total_reserved_row_overflow_MB] NUMERIC(29,2), - [index_size_summary] NVARCHAR(300), - [total_row_lock_count] BIGINT, - [total_row_lock_wait_count] BIGINT, - [total_row_lock_wait_in_ms] BIGINT, - [avg_row_lock_wait_in_ms] BIGINT, - [total_page_lock_count] BIGINT, - [total_page_lock_wait_count] BIGINT, - [total_page_lock_wait_in_ms] BIGINT, - [avg_page_lock_wait_in_ms] BIGINT, - [total_index_lock_promotion_attempt_count] BIGINT, - [total_index_lock_promotion_count] BIGINT, - [data_compression_desc] NVARCHAR(4000), - [page_latch_wait_count] BIGINT, - [page_latch_wait_in_ms] BIGINT, - [page_io_latch_wait_count] BIGINT, - [page_io_latch_wait_in_ms] BIGINT, - [create_date] DATETIME, - [modify_date] DATETIME, - [more_info] NVARCHAR(500), - [display_order] INT, - CONSTRAINT [PK_ID_@@@RunID@@@] PRIMARY KEY CLUSTERED ([id] ASC) - );'; - - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputSchemaName@@@', @OutputSchemaName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@RunID@@@', @RunID); - - IF @ValidOutputServer = 1 - BEGIN - SET @StringToExecute = REPLACE(@StringToExecute,'''',''''''); - EXEC('EXEC('''+@StringToExecute+''') AT ' + @OutputServerName); - END; - ELSE - BEGIN - EXEC(@StringToExecute); - END; - END; /* @TableExists = 0 */ - - SET @StringToExecute = - N'IF EXISTS(SELECT * FROM @@@OutputServerName@@@.@@@OutputDatabaseName@@@.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''@@@OutputSchemaName@@@'') - AND NOT EXISTS (SELECT * FROM @@@OutputServerName@@@.@@@OutputDatabaseName@@@.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''@@@OutputSchemaName@@@'' AND QUOTENAME(TABLE_NAME) = ''@@@OutputTableName@@@'') - SET @TableExists = 0 - ELSE - SET @TableExists = 1'; - - SET @TableExists = NULL; - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputServerName@@@', @OutputServerName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputSchemaName@@@', @OutputSchemaName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); - - EXEC sp_executesql @StringToExecute, N'@TableExists BIT OUTPUT', @TableExists OUTPUT; - - IF @TableExists = 1 - BEGIN - SET @StringToExecute = - N'INSERT @@@OutputServerName@@@.@@@OutputDatabaseName@@@.@@@OutputSchemaName@@@.@@@OutputTableName@@@ - ( - [run_id], - [run_datetime], - [server_name], - [database_name], - [schema_name], - [table_name], - [index_name], - [Drop_Tsql], - [Create_Tsql], - [index_id], - [db_schema_object_indexid], - [object_type], - [index_definition], - [key_column_names_with_sort_order], - [count_key_columns], - [include_column_names], - [count_included_columns], - [secret_columns], - [count_secret_columns], - [partition_key_column_name], - [filter_definition], - [is_indexed_view], - [is_primary_key], - [is_XML], - [is_spatial], - [is_NC_columnstore], - [is_CX_columnstore], - [is_in_memory_oltp], - [is_disabled], - [is_hypothetical], - [is_padded], - [fill_factor], - [is_referenced_by_foreign_key], - [last_user_seek], - [last_user_scan], - [last_user_lookup], - [last_user_update], - [total_reads], - [user_updates], - [reads_per_write], - [index_usage_summary], - [total_singleton_lookup_count], - [total_range_scan_count], - [total_leaf_delete_count], - [total_leaf_update_count], - [index_op_stats], - [partition_count], - [total_rows], - [total_reserved_MB], - [total_reserved_LOB_MB], - [total_reserved_row_overflow_MB], - [index_size_summary], - [total_row_lock_count], - [total_row_lock_wait_count], - [total_row_lock_wait_in_ms], - [avg_row_lock_wait_in_ms], - [total_page_lock_count], - [total_page_lock_wait_count], - [total_page_lock_wait_in_ms], - [avg_page_lock_wait_in_ms], - [total_index_lock_promotion_attempt_count], - [total_index_lock_promotion_count], - [data_compression_desc], - [page_latch_wait_count], - [page_latch_wait_in_ms], - [page_io_latch_wait_count], - [page_io_latch_wait_in_ms], - [create_date], - [modify_date], - [more_info], - [display_order] - ) - SELECT ''@@@RunID@@@'', - ''@@@GETDATE@@@'', - ''@@@LocalServerName@@@'', - -- Below should be a copy/paste of the real query - -- Make sure all quotes are escaped - i.[database_name] AS [Database Name], - i.[schema_name] AS [Schema Name], - i.[object_name] AS [Object Name], - ISNULL(i.index_name, '''') AS [Index Name], - CASE - WHEN i.is_primary_key = 1 AND i.index_definition <> ''[HEAP]'' - THEN N''-ALTER TABLE '' + QUOTENAME(i.[database_name]) + N''.'' + QUOTENAME(i.[schema_name]) + N''.'' + QUOTENAME(i.[object_name]) + - N'' DROP CONSTRAINT '' + QUOTENAME(i.index_name) + N'';'' - WHEN i.is_primary_key = 0 AND i.index_definition <> ''[HEAP]'' - THEN N''--DROP INDEX ''+ QUOTENAME(i.index_name) + N'' ON '' + QUOTENAME(i.[database_name]) + N''.'' + - QUOTENAME(i.[schema_name]) + N''.'' + QUOTENAME(i.[object_name]) + N'';'' - ELSE N'''' - END AS [Drop TSQL], - CASE - WHEN i.index_definition = ''[HEAP]'' THEN N'''' - ELSE N''--'' + ict.create_tsql END AS [Create TSQL], - CAST(i.index_id AS NVARCHAR(10))AS [Index ID], - db_schema_object_indexid AS [Details: schema.table.index(indexid)], - CASE WHEN index_id IN ( 1, 0 ) THEN ''TABLE'' - ELSE ''NonClustered'' - END AS [Object Type], - LEFT(index_definition,4000) AS [Definition: [Property]] ColumnName {datatype maxbytes}], - ISNULL(LTRIM(key_column_names_with_sort_order), '''') AS [Key Column Names With Sort], - ISNULL(count_key_columns, 0) AS [Count Key Columns], - ISNULL(include_column_names, '''') AS [Include Column Names], - ISNULL(count_included_columns,0) AS [Count Included Columns], - ISNULL(secret_columns,'''') AS [Secret Column Names], - ISNULL(count_secret_columns,0) AS [Count Secret Columns], - ISNULL(partition_key_column_name, '''') AS [Partition Key Column Name], - ISNULL(filter_definition, '''') AS [Filter Definition], - is_indexed_view AS [Is Indexed View], - is_primary_key AS [Is Primary Key], - is_XML AS [Is XML], - is_spatial AS [Is Spatial], - is_NC_columnstore AS [Is NC Columnstore], - is_CX_columnstore AS [Is CX Columnstore], - is_in_memory_oltp AS [Is In-Memory OLTP], - is_disabled AS [Is Disabled], - is_hypothetical AS [Is Hypothetical], - is_padded AS [Is Padded], - fill_factor AS [Fill Factor], - is_referenced_by_foreign_key AS [Is Reference by Foreign Key], - last_user_seek AS [Last User Seek], - last_user_scan AS [Last User Scan], - last_user_lookup AS [Last User Lookup], - last_user_update AS [Last User Update], - total_reads AS [Total Reads], - user_updates AS [User Updates], - reads_per_write AS [Reads Per Write], - index_usage_summary AS [Index Usage], - sz.total_singleton_lookup_count AS [Singleton Lookups], - sz.total_range_scan_count AS [Range Scans], - sz.total_leaf_delete_count AS [Leaf Deletes], - sz.total_leaf_update_count AS [Leaf Updates], - sz.index_op_stats AS [Index Op Stats], - sz.partition_count AS [Partition Count], - sz.total_rows AS [Rows], - sz.total_reserved_MB AS [Reserved MB], - sz.total_reserved_LOB_MB AS [Reserved LOB MB], - sz.total_reserved_row_overflow_MB AS [Reserved Row Overflow MB], - sz.index_size_summary AS [Index Size], - sz.total_row_lock_count AS [Row Lock Count], - sz.total_row_lock_wait_count AS [Row Lock Wait Count], - sz.total_row_lock_wait_in_ms AS [Row Lock Wait ms], - sz.avg_row_lock_wait_in_ms AS [Avg Row Lock Wait ms], - sz.total_page_lock_count AS [Page Lock Count], - sz.total_page_lock_wait_count AS [Page Lock Wait Count], - sz.total_page_lock_wait_in_ms AS [Page Lock Wait ms], - sz.avg_page_lock_wait_in_ms AS [Avg Page Lock Wait ms], - sz.total_index_lock_promotion_attempt_count AS [Lock Escalation Attempts], - sz.total_index_lock_promotion_count AS [Lock Escalations], - sz.data_compression_desc AS [Data Compression], - sz.page_latch_wait_count, - sz.page_latch_wait_in_ms, - sz.page_io_latch_wait_count, - sz.page_io_latch_wait_in_ms, - i.create_date AS [Create Date], - i.modify_date AS [Modify Date], - more_info AS [More Info], - 1 AS [Display Order] - FROM #IndexSanity AS i - LEFT JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id - LEFT JOIN #IndexCreateTsql AS ict ON i.index_sanity_id = ict.index_sanity_id - ORDER BY [Database Name], [Schema Name], [Object Name], [Index ID] - OPTION (RECOMPILE);'; +CREATE TABLE #missing_index_detail +( + query_hash BINARY(8), + sql_handle VARBINARY(64), + impact FLOAT, + database_name NVARCHAR(128), + schema_name NVARCHAR(128), + table_name NVARCHAR(128), + usage NVARCHAR(128), + column_name NVARCHAR(128), + INDEX mid_ix_ids CLUSTERED (sql_handle, query_hash) +); + + +DROP TABLE IF EXISTS #missing_index_pretty; + +CREATE TABLE #missing_index_pretty +( + query_hash BINARY(8), + sql_handle VARBINARY(64), + impact FLOAT, + database_name NVARCHAR(128), + schema_name NVARCHAR(128), + table_name NVARCHAR(128), + equality NVARCHAR(MAX), + inequality NVARCHAR(MAX), + [include] NVARCHAR(MAX), + is_spool BIT, + details AS N'/* ' + + CHAR(10) + + CASE is_spool + WHEN 0 + THEN N'The Query Processor estimates that implementing the ' + ELSE N'We estimate that implementing the ' + END + + CONVERT(NVARCHAR(30), impact) + + '%.' + + CHAR(10) + + N'*/' + + CHAR(10) + CHAR(13) + + N'/* ' + + CHAR(10) + + N'USE ' + + database_name + + CHAR(10) + + N'GO' + + CHAR(10) + CHAR(13) + + N'CREATE NONCLUSTERED INDEX ix_' + + ISNULL(REPLACE(REPLACE(REPLACE(equality,'[', ''), ']', ''), ', ', '_'), '') + + ISNULL(REPLACE(REPLACE(REPLACE(inequality,'[', ''), ']', ''), ', ', '_'), '') + + CASE WHEN [include] IS NOT NULL THEN + N'_Includes' ELSE N'' END + + CHAR(10) + + N' ON ' + + schema_name + + N'.' + + table_name + + N' (' + + + CASE WHEN equality IS NOT NULL + THEN equality + + CASE WHEN inequality IS NOT NULL + THEN N', ' + inequality + ELSE N'' + END + ELSE inequality + END + + N')' + + CHAR(10) + + CASE WHEN include IS NOT NULL + THEN N'INCLUDE (' + include + N') WITH (FILLFACTOR=100, ONLINE=?, SORT_IN_TEMPDB=?, DATA_COMPRESSION=?);' + ELSE N' WITH (FILLFACTOR=100, ONLINE=?, SORT_IN_TEMPDB=?, DATA_COMPRESSION=?);' + END + + CHAR(10) + + N'GO' + + CHAR(10) + + N'*/', + INDEX mip_ix_ids CLUSTERED (sql_handle, query_hash) +); + +DROP TABLE IF EXISTS #index_spool_ugly; + +CREATE TABLE #index_spool_ugly +( + query_hash BINARY(8), + sql_handle VARBINARY(64), + impact FLOAT, + database_name NVARCHAR(128), + schema_name NVARCHAR(128), + table_name NVARCHAR(128), + equality NVARCHAR(MAX), + inequality NVARCHAR(MAX), + [include] NVARCHAR(MAX), + INDEX isu_ix_ids CLUSTERED (sql_handle, query_hash) +); + + +/*Sets up WHERE clause that gets used quite a bit*/ + +--Date stuff +--If they're both NULL, we'll just look at the last 7 days +IF (@StartDate IS NULL AND @EndDate IS NULL) + BEGIN + RAISERROR(N'@StartDate and @EndDate are NULL, checking last 7 days', 0, 1) WITH NOWAIT; + SET @sql_where += N' AND qsrs.last_execution_time >= DATEADD(DAY, -7, DATEDIFF(DAY, 0, SYSDATETIME() )) + '; + END; + +--Hey, that's nice of me +IF @StartDate IS NOT NULL + BEGIN + RAISERROR(N'Setting start date filter', 0, 1) WITH NOWAIT; + SET @sql_where += N' AND qsrs.last_execution_time >= @sp_StartDate + '; + END; + +--Alright, sensible +IF @EndDate IS NOT NULL + BEGIN + RAISERROR(N'Setting end date filter', 0, 1) WITH NOWAIT; + SET @sql_where += N' AND qsrs.last_execution_time < @sp_EndDate + '; + END; + +--C'mon, why would you do that? +IF (@StartDate IS NULL AND @EndDate IS NOT NULL) + BEGIN + RAISERROR(N'Setting reasonable start date filter', 0, 1) WITH NOWAIT; + SET @sql_where += N' AND qsrs.last_execution_time >= DATEADD(DAY, -7, @sp_EndDate) + '; + END; + +--Jeez, abusive +IF (@StartDate IS NOT NULL AND @EndDate IS NULL) + BEGIN + RAISERROR(N'Setting reasonable end date filter', 0, 1) WITH NOWAIT; + SET @sql_where += N' AND qsrs.last_execution_time < DATEADD(DAY, 7, @sp_StartDate) + '; + END; + +--I care about minimum execution counts +IF @MinimumExecutionCount IS NOT NULL + BEGIN + RAISERROR(N'Setting execution filter', 0, 1) WITH NOWAIT; + SET @sql_where += N' AND qsrs.count_executions >= @sp_MinimumExecutionCount + '; + END; + +--You care about stored proc names +IF @StoredProcName IS NOT NULL + BEGIN + RAISERROR(N'Setting stored proc filter', 0, 1) WITH NOWAIT; + SET @sql_where += N' AND object_name(qsq.object_id, DB_ID(' + QUOTENAME(@DatabaseName, '''') + N')) = @sp_StoredProcName + '; + END; + +--I will always love you, but hopefully this query will eventually end +IF @DurationFilter IS NOT NULL + BEGIN + RAISERROR(N'Setting duration filter', 0, 1) WITH NOWAIT; + SET @sql_where += N' AND (qsrs.avg_duration / 1000.) >= @sp_MinDuration + '; + END; + +--I don't know why you'd go looking for failed queries, but hey +IF (@Failed = 0 OR @Failed IS NULL) + BEGIN + RAISERROR(N'Setting failed query filter to 0', 0, 1) WITH NOWAIT; + SET @sql_where += N' AND qsrs.execution_type = 0 + '; + END; +IF (@Failed = 1) + BEGIN + RAISERROR(N'Setting failed query filter to 3, 4', 0, 1) WITH NOWAIT; + SET @sql_where += N' AND qsrs.execution_type IN (3, 4) + '; + END; + +/*Filtering for plan_id or query_id*/ +IF (@PlanIdFilter IS NOT NULL) + BEGIN + RAISERROR(N'Setting plan_id filter', 0, 1) WITH NOWAIT; + SET @sql_where += N' AND qsp.plan_id = @sp_PlanIdFilter + '; + END; + +IF (@QueryIdFilter IS NOT NULL) + BEGIN + RAISERROR(N'Setting query_id filter', 0, 1) WITH NOWAIT; + SET @sql_where += N' AND qsq.query_id = @sp_QueryIdFilter + '; + END; + +IF @Debug = 1 + RAISERROR(N'Starting WHERE clause:', 0, 1) WITH NOWAIT; + PRINT @sql_where; + +IF @sql_where IS NULL + BEGIN + RAISERROR(N'@sql_where is NULL', 0, 1) WITH NOWAIT; + RETURN; + END; + +IF (@ExportToExcel = 1 OR @SkipXML = 1) + BEGIN + RAISERROR(N'Exporting to Excel or skipping XML, hiding summary', 0, 1) WITH NOWAIT; + SET @HideSummary = 1; + END; + +IF @StoredProcName IS NOT NULL + BEGIN - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputServerName@@@', @OutputServerName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputSchemaName@@@', @OutputSchemaName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@RunID@@@', @RunID); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@GETDATE@@@', GETDATE()); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@LocalServerName@@@', CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128))); - EXEC(@StringToExecute); - END; /* @TableExists = 1 */ - ELSE - RAISERROR('Creation of the output table failed.', 16, 0); - END; /* @TableExists = 0 */ - ELSE - RAISERROR (N'Invalid schema name, data could not be saved.', 16, 0); - END; /* @ValidOutputLocation = 1 */ - ELSE + DECLARE @sql NVARCHAR(MAX); + DECLARE @out INT; + DECLARE @proc_params NVARCHAR(MAX) = N'@sp_StartDate DATETIME2, @sp_EndDate DATETIME2, @sp_MinimumExecutionCount INT, @sp_MinDuration INT, @sp_StoredProcName NVARCHAR(128), @sp_PlanIdFilter INT, @sp_QueryIdFilter INT, @i_out INT OUTPUT'; - IF(@OutputType <> 'NONE') - BEGIN - SELECT i.[database_name] AS [Database Name], - i.[schema_name] AS [Schema Name], - i.[object_name] AS [Object Name], - ISNULL(i.index_name, '') AS [Index Name], - CAST(i.index_id AS NVARCHAR(10))AS [Index ID], - db_schema_object_indexid AS [Details: schema.table.index(indexid)], - CASE WHEN index_id IN ( 1, 0 ) THEN 'TABLE' - ELSE 'NonClustered' - END AS [Object Type], - index_definition AS [Definition: [Property]] ColumnName {datatype maxbytes}], - ISNULL(LTRIM(key_column_names_with_sort_order), '') AS [Key Column Names With Sort], - ISNULL(count_key_columns, 0) AS [Count Key Columns], - ISNULL(include_column_names, '') AS [Include Column Names], - ISNULL(count_included_columns,0) AS [Count Included Columns], - ISNULL(secret_columns,'') AS [Secret Column Names], - ISNULL(count_secret_columns,0) AS [Count Secret Columns], - ISNULL(partition_key_column_name, '') AS [Partition Key Column Name], - ISNULL(filter_definition, '') AS [Filter Definition], - is_indexed_view AS [Is Indexed View], - is_primary_key AS [Is Primary Key], - is_XML AS [Is XML], - is_spatial AS [Is Spatial], - is_NC_columnstore AS [Is NC Columnstore], - is_CX_columnstore AS [Is CX Columnstore], - is_in_memory_oltp AS [Is In-Memory OLTP], - is_disabled AS [Is Disabled], - is_hypothetical AS [Is Hypothetical], - is_padded AS [Is Padded], - fill_factor AS [Fill Factor], - is_referenced_by_foreign_key AS [Is Reference by Foreign Key], - last_user_seek AS [Last User Seek], - last_user_scan AS [Last User Scan], - last_user_lookup AS [Last User Lookup], - last_user_update AS [Last User Update], - total_reads AS [Total Reads], - user_updates AS [User Updates], - reads_per_write AS [Reads Per Write], - index_usage_summary AS [Index Usage], - sz.total_singleton_lookup_count AS [Singleton Lookups], - sz.total_range_scan_count AS [Range Scans], - sz.total_leaf_delete_count AS [Leaf Deletes], - sz.total_leaf_update_count AS [Leaf Updates], - sz.index_op_stats AS [Index Op Stats], - sz.partition_count AS [Partition Count], - sz.total_rows AS [Rows], - sz.total_reserved_MB AS [Reserved MB], - sz.total_reserved_LOB_MB AS [Reserved LOB MB], - sz.total_reserved_row_overflow_MB AS [Reserved Row Overflow MB], - sz.index_size_summary AS [Index Size], - sz.total_row_lock_count AS [Row Lock Count], - sz.total_row_lock_wait_count AS [Row Lock Wait Count], - sz.total_row_lock_wait_in_ms AS [Row Lock Wait ms], - sz.avg_row_lock_wait_in_ms AS [Avg Row Lock Wait ms], - sz.total_page_lock_count AS [Page Lock Count], - sz.total_page_lock_wait_count AS [Page Lock Wait Count], - sz.total_page_lock_wait_in_ms AS [Page Lock Wait ms], - sz.avg_page_lock_wait_in_ms AS [Avg Page Lock Wait ms], - sz.total_index_lock_promotion_attempt_count AS [Lock Escalation Attempts], - sz.total_index_lock_promotion_count AS [Lock Escalations], - sz.page_latch_wait_count AS [Page Latch Wait Count], - sz.page_latch_wait_in_ms AS [Page Latch Wait ms], - sz.page_io_latch_wait_count AS [Page IO Latch Wait Count], - sz.page_io_latch_wait_in_ms as [Page IO Latch Wait ms], - sz.total_forwarded_fetch_count AS [Forwarded Fetches], - sz.data_compression_desc AS [Data Compression], - i.create_date AS [Create Date], - i.modify_date AS [Modify Date], - more_info AS [More Info], - CASE - WHEN i.is_primary_key = 1 AND i.index_definition <> '[HEAP]' - THEN N'--ALTER TABLE ' + QUOTENAME(i.[database_name]) + N'.' + QUOTENAME(i.[schema_name]) + N'.' + QUOTENAME(i.[object_name]) - + N' DROP CONSTRAINT ' + QUOTENAME(i.index_name) + N';' - WHEN i.is_primary_key = 0 AND i.index_definition <> '[HEAP]' - THEN N'--DROP INDEX '+ QUOTENAME(i.index_name) + N' ON ' + QUOTENAME(i.[database_name]) + N'.' + - QUOTENAME(i.[schema_name]) + N'.' + QUOTENAME(i.[object_name]) + N';' - ELSE N'' - END AS [Drop TSQL], - CASE - WHEN i.index_definition = '[HEAP]' THEN N'' - ELSE N'--' + ict.create_tsql END AS [Create TSQL], - 1 AS [Display Order] - FROM #IndexSanity AS i --left join here so we don't lose disabled nc indexes - LEFT JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id - LEFT JOIN #IndexCreateTsql AS ict ON i.index_sanity_id = ict.index_sanity_id - ORDER BY CASE WHEN @SortDirection = 'desc' THEN - CASE WHEN @SortOrder = N'rows' THEN sz.total_rows - WHEN @SortOrder = N'reserved_mb' THEN sz.total_reserved_MB - WHEN @SortOrder = N'size' THEN sz.total_reserved_MB - WHEN @SortOrder = N'reserved_lob_mb' THEN sz.total_reserved_LOB_MB - WHEN @SortOrder = N'lob' THEN sz.total_reserved_LOB_MB - WHEN @SortOrder = N'total_row_lock_wait_in_ms' THEN COALESCE(sz.total_row_lock_wait_in_ms,0) - WHEN @SortOrder = N'total_page_lock_wait_in_ms' THEN COALESCE(sz.total_page_lock_wait_in_ms,0) - WHEN @SortOrder = N'lock_time' THEN (COALESCE(sz.total_row_lock_wait_in_ms,0) + COALESCE(sz.total_page_lock_wait_in_ms,0)) - WHEN @SortOrder = N'total_reads' THEN total_reads - WHEN @SortOrder = N'reads' THEN total_reads - WHEN @SortOrder = N'user_updates' THEN user_updates - WHEN @SortOrder = N'writes' THEN user_updates - WHEN @SortOrder = N'reads_per_write' THEN reads_per_write - WHEN @SortOrder = N'ratio' THEN reads_per_write - WHEN @SortOrder = N'forward_fetches' THEN sz.total_forwarded_fetch_count - WHEN @SortOrder = N'fetches' THEN sz.total_forwarded_fetch_count - ELSE NULL END - ELSE 1 END - DESC, /* Shout out to DHutmacher */ - CASE WHEN @SortDirection = 'asc' THEN - CASE WHEN @SortOrder = N'rows' THEN sz.total_rows - WHEN @SortOrder = N'reserved_mb' THEN sz.total_reserved_MB - WHEN @SortOrder = N'size' THEN sz.total_reserved_MB - WHEN @SortOrder = N'reserved_lob_mb' THEN sz.total_reserved_LOB_MB - WHEN @SortOrder = N'lob' THEN sz.total_reserved_LOB_MB - WHEN @SortOrder = N'total_row_lock_wait_in_ms' THEN COALESCE(sz.total_row_lock_wait_in_ms,0) - WHEN @SortOrder = N'total_page_lock_wait_in_ms' THEN COALESCE(sz.total_page_lock_wait_in_ms,0) - WHEN @SortOrder = N'lock_time' THEN (COALESCE(sz.total_row_lock_wait_in_ms,0) + COALESCE(sz.total_page_lock_wait_in_ms,0)) - WHEN @SortOrder = N'total_reads' THEN total_reads - WHEN @SortOrder = N'reads' THEN total_reads - WHEN @SortOrder = N'user_updates' THEN user_updates - WHEN @SortOrder = N'writes' THEN user_updates - WHEN @SortOrder = N'reads_per_write' THEN reads_per_write - WHEN @SortOrder = N'ratio' THEN reads_per_write - WHEN @SortOrder = N'forward_fetches' THEN sz.total_forwarded_fetch_count - WHEN @SortOrder = N'fetches' THEN sz.total_forwarded_fetch_count - ELSE NULL END - ELSE 1 END - ASC, - i.[database_name], [Schema Name], [Object Name], [Index ID] - OPTION (RECOMPILE); - END; + + SET @sql = N'SELECT @i_out = COUNT(*) + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp + ON qsp.plan_id = qsrs.plan_id + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq + ON qsq.query_id = qsp.query_id + WHERE 1 = 1 + AND qsq.is_internal_query = 0 + AND qsp.query_plan IS NOT NULL + '; + + SET @sql += @sql_where; + + EXEC sys.sp_executesql @sql, + @proc_params, + @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter, @i_out = @out OUTPUT; + + IF @out = 0 + BEGIN + + SET @msg = N'We couldn''t find the Stored Procedure ' + QUOTENAME(@StoredProcName) + N' in the Query Store views for ' + QUOTENAME(@DatabaseName) + N' between ' + CONVERT(NVARCHAR(30), ISNULL(@StartDate, DATEADD(DAY, -7, DATEDIFF(DAY, 0, SYSDATETIME() ))) ) + N' and ' + CONVERT(NVARCHAR(30), ISNULL(@EndDate, SYSDATETIME())) + + '. Try removing schema prefixes or adjusting dates. If it was executed from a different database context, try searching there instead.'; + RAISERROR(@msg, 0, 1) WITH NOWAIT; + + SELECT @msg AS [Blue Flowers, Blue Flowers, Blue Flowers]; + + RETURN; + + END; + + END; + + + + +/* +This is our grouped interval query. + +By default, it looks at queries: + In the last 7 days + That aren't system queries + That have a query plan (some won't, if nested level is > 128, along with other reasons) + And haven't failed + This stuff, along with some other options, will be configurable in the stored proc + +*/ + +IF @sql_where IS NOT NULL +BEGIN TRY + BEGIN + + RAISERROR(N'Populating temp tables', 0, 1) WITH NOWAIT; + +RAISERROR(N'Gathering intervals', 0, 1) WITH NOWAIT; + +SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; +SET @sql_select += N' +SELECT CONVERT(DATE, qsrs.last_execution_time) AS flat_date, + MIN(DATEADD(HOUR, DATEDIFF(HOUR, 0, qsrs.last_execution_time), 0)) AS start_range, + MAX(DATEADD(HOUR, DATEDIFF(HOUR, 0, qsrs.last_execution_time) + 1, 0)) AS end_range, + SUM(qsrs.avg_duration / 1000.) / SUM(qsrs.count_executions) AS total_avg_duration_ms, + SUM(qsrs.avg_cpu_time / 1000.) / SUM(qsrs.count_executions) AS total_avg_cpu_time_ms, + SUM((qsrs.avg_logical_io_reads * 8 ) / 1024.) / SUM(qsrs.count_executions) AS total_avg_logical_io_reads_mb, + SUM((qsrs.avg_physical_io_reads* 8 ) / 1024.) / SUM(qsrs.count_executions) AS total_avg_physical_io_reads_mb, + SUM((qsrs.avg_logical_io_writes* 8 ) / 1024.) / SUM(qsrs.count_executions) AS total_avg_logical_io_writes_mb, + SUM((qsrs.avg_query_max_used_memory * 8 ) / 1024.) / SUM(qsrs.count_executions) AS total_avg_query_max_used_memory_mb, + SUM(qsrs.avg_rowcount) AS total_rowcount, + SUM(qsrs.count_executions) AS total_count_executions, + SUM(qsrs.max_duration / 1000.) AS total_max_duration_ms, + SUM(qsrs.max_cpu_time / 1000.) AS total_max_cpu_time_ms, + SUM((qsrs.max_logical_io_reads * 8 ) / 1024.) AS total_max_logical_io_reads_mb, + SUM((qsrs.max_physical_io_reads* 8 ) / 1024.) AS total_max_physical_io_reads_mb, + SUM((qsrs.max_logical_io_writes* 8 ) / 1024.) AS total_max_logical_io_writes_mb, + SUM((qsrs.max_query_max_used_memory * 8 ) / 1024.) AS total_max_query_max_used_memory_mb '; + IF @new_columns = 1 + BEGIN + SET @sql_select += N', + SUM((qsrs.avg_log_bytes_used) / 1048576.) / SUM(qsrs.count_executions) AS total_avg_log_bytes_mb, + SUM(qsrs.avg_tempdb_space_used) / SUM(qsrs.count_executions) AS total_avg_tempdb_space, + SUM((qsrs.max_log_bytes_used) / 1048576.) AS total_max_log_bytes_mb, + SUM(qsrs.max_tempdb_space_used) AS total_max_tempdb_space + '; + END; + IF @new_columns = 0 + BEGIN + SET @sql_select += N', + NULL AS total_avg_log_bytes_mb, + NULL AS total_avg_tempdb_space, + NULL AS total_max_log_bytes_mb, + NULL AS total_max_tempdb_space + '; + END; + + +SET @sql_select += N'FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp + ON qsp.plan_id = qsrs.plan_id + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq + ON qsq.query_id = qsp.query_id + WHERE 1 = 1 + AND qsq.is_internal_query = 0 + AND qsp.query_plan IS NOT NULL + '; + + +SET @sql_select += @sql_where; + +SET @sql_select += + N'GROUP BY CONVERT(DATE, qsrs.last_execution_time) + OPTION (RECOMPILE); + '; + +IF @Debug = 1 + PRINT @sql_select; + +IF @sql_select IS NULL + BEGIN + RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; + RETURN; + END; + +INSERT #grouped_interval WITH (TABLOCK) + ( flat_date, start_range, end_range, total_avg_duration_ms, + total_avg_cpu_time_ms, total_avg_logical_io_reads_mb, total_avg_physical_io_reads_mb, + total_avg_logical_io_writes_mb, total_avg_query_max_used_memory_mb, total_rowcount, + total_count_executions, total_max_duration_ms, total_max_cpu_time_ms, total_max_logical_io_reads_mb, + total_max_physical_io_reads_mb, total_max_logical_io_writes_mb, total_max_query_max_used_memory_mb, + total_avg_log_bytes_mb, total_avg_tempdb_space, total_max_log_bytes_mb, total_max_tempdb_space ) + +EXEC sys.sp_executesql @stmt = @sql_select, + @params = @sp_params, + @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; + + +/* +The next group of queries looks at plans in the ranges we found in the grouped interval query + +We take the highest value from each metric (duration, cpu, etc) and find the top plans by that metric in the range + +They insert into the #working_plans table +*/ + + + +/*Get longest duration plans*/ + +RAISERROR(N'Gathering longest duration plans', 0, 1) WITH NOWAIT; + +SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; +SET @sql_select += N' +WITH duration_max +AS ( SELECT TOP 1 + gi.start_range, + gi.end_range + FROM #grouped_interval AS gi + ORDER BY gi.total_avg_duration_ms DESC ) +INSERT #working_plans WITH (TABLOCK) + ( plan_id, query_id, pattern ) +SELECT TOP ( @sp_Top ) + qsp.plan_id, qsp.query_id, ''avg duration'' +FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp +JOIN duration_max AS dm +ON qsp.last_execution_time >= dm.start_range + AND qsp.last_execution_time < dm.end_range +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs +ON qsrs.plan_id = qsp.plan_id +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq +ON qsq.query_id = qsp.query_id +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt +ON qsqt.query_text_id = qsq.query_text_id +WHERE 1 = 1 + AND qsq.is_internal_query = 0 + AND qsp.query_plan IS NOT NULL + '; - END; /* End @Mode=2 (index detail)*/ - ELSE IF (@Mode=3) /*Missing index Detail*/ +SET @sql_select += @sql_where; + +SET @sql_select += N'ORDER BY qsrs.avg_duration DESC + OPTION (RECOMPILE); + '; + +IF @Debug = 1 + PRINT @sql_select; + +IF @sql_select IS NULL BEGIN - IF(@OutputType <> 'NONE') - BEGIN; - WITH create_date AS ( - SELECT i.database_id, - i.schema_name, - i.[object_id], - ISNULL(NULLIF(MAX(DATEDIFF(DAY, i.create_date, SYSDATETIME())), 0), 1) AS create_days - FROM #IndexSanity AS i - GROUP BY i.database_id, i.schema_name, i.object_id - ) - SELECT - mi.database_name AS [Database Name], - mi.[schema_name] AS [Schema], - mi.table_name AS [Table], - CAST((mi.magic_benefit_number / CASE WHEN cd.create_days < @DaysUptime THEN cd.create_days ELSE @DaysUptime END) AS BIGINT) - AS [Magic Benefit Number], - mi.missing_index_details AS [Missing Index Details], - mi.avg_total_user_cost AS [Avg Query Cost], - mi.avg_user_impact AS [Est Index Improvement], - mi.user_seeks AS [Seeks], - mi.user_scans AS [Scans], - mi.unique_compiles AS [Compiles], - mi.equality_columns_with_data_type AS [Equality Columns], - mi.inequality_columns_with_data_type AS [Inequality Columns], - mi.included_columns_with_data_type AS [Included Columns], - mi.index_estimated_impact AS [Estimated Impact], - mi.create_tsql AS [Create TSQL], - mi.more_info AS [More Info], - 1 AS [Display Order], - mi.is_low, - mi.sample_query_plan AS [Sample Query Plan] - FROM #MissingIndexes AS mi - LEFT JOIN create_date AS cd - ON mi.[object_id] = cd.object_id - AND mi.database_id = cd.database_id - AND mi.schema_name = cd.schema_name - /* Minimum benefit threshold = 100k/day of uptime OR since table creation date, whichever is lower*/ - WHERE @ShowAllMissingIndexRequests=1 - OR (mi.magic_benefit_number / CASE WHEN cd.create_days < @DaysUptime THEN cd.create_days ELSE @DaysUptime END) >= 100000 - UNION ALL - SELECT - @ScriptVersionName, - N'From Your Community Volunteers' , - N'http://FirstResponderKit.org' , - 100000000000, - @DaysUptimeInsertValue, - NULL,NULL,NULL,NULL,NULL,NULL,NULL, - NULL, NULL, NULL, NULL, 0 AS [Display Order], NULL AS is_low, NULL - ORDER BY [Display Order] ASC, [Magic Benefit Number] DESC - OPTION (RECOMPILE); - END; + RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; + RETURN; + END; - IF (@BringThePain = 1 - AND @DatabaseName IS NOT NULL - AND @GetAllDatabases = 0) +EXEC sys.sp_executesql @stmt = @sql_select, + @params = @sp_params, + @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; - BEGIN - EXEC sp_BlitzCache @SortOrder = 'sp_BlitzIndex', @DatabaseName = @DatabaseName, @BringThePain = 1, @QueryFilter = 'statement', @HideSummary = 1; - - END; +SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; +SET @sql_select += N' +WITH duration_max +AS ( SELECT TOP 1 + gi.start_range, + gi.end_range + FROM #grouped_interval AS gi + ORDER BY gi.total_max_duration_ms DESC ) +INSERT #working_plans WITH (TABLOCK) + ( plan_id, query_id, pattern ) +SELECT TOP ( @sp_Top ) + qsp.plan_id, qsp.query_id, ''max duration'' +FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp +JOIN duration_max AS dm +ON qsp.last_execution_time >= dm.start_range + AND qsp.last_execution_time < dm.end_range +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs +ON qsrs.plan_id = qsp.plan_id +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq +ON qsq.query_id = qsp.query_id +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt +ON qsqt.query_text_id = qsq.query_text_id +WHERE 1 = 1 + AND qsq.is_internal_query = 0 + AND qsp.query_plan IS NOT NULL + '; +SET @sql_select += @sql_where; - END; /* End @Mode=3 (index detail)*/ +SET @sql_select += N'ORDER BY qsrs.max_duration DESC + OPTION (RECOMPILE); + '; -END TRY +IF @Debug = 1 + PRINT @sql_select; -BEGIN CATCH - RAISERROR (N'Failure analyzing temp tables.', 0,1) WITH NOWAIT; +IF @sql_select IS NULL + BEGIN + RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; + RETURN; + END; - SELECT @msg = ERROR_MESSAGE(), @ErrorSeverity = ERROR_SEVERITY(), @ErrorState = ERROR_STATE(); +EXEC sys.sp_executesql @stmt = @sql_select, + @params = @sp_params, + @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; - RAISERROR (@msg, - @ErrorSeverity, - @ErrorState - ); - - WHILE @@trancount > 0 - ROLLBACK; +/*Get longest cpu plans*/ + +RAISERROR(N'Gathering highest cpu plans', 0, 1) WITH NOWAIT; + +SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; +SET @sql_select += N' +WITH cpu_max +AS ( SELECT TOP 1 + gi.start_range, + gi.end_range + FROM #grouped_interval AS gi + ORDER BY gi.total_avg_cpu_time_ms DESC ) +INSERT #working_plans WITH (TABLOCK) + ( plan_id, query_id, pattern ) +SELECT TOP ( @sp_Top ) + qsp.plan_id, qsp.query_id, ''avg cpu'' +FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp +JOIN cpu_max AS dm +ON qsp.last_execution_time >= dm.start_range + AND qsp.last_execution_time < dm.end_range +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs +ON qsrs.plan_id = qsp.plan_id +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq +ON qsq.query_id = qsp.query_id +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt +ON qsqt.query_text_id = qsq.query_text_id +WHERE 1 = 1 + AND qsq.is_internal_query = 0 + AND qsp.query_plan IS NOT NULL + '; + +SET @sql_select += @sql_where; + +SET @sql_select += N'ORDER BY qsrs.avg_cpu_time DESC + OPTION (RECOMPILE); + '; + +IF @Debug = 1 + PRINT @sql_select; + +IF @sql_select IS NULL + BEGIN + RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; RETURN; - END CATCH; -GO -IF OBJECT_ID('dbo.sp_BlitzLock') IS NULL - EXEC ('CREATE PROCEDURE dbo.sp_BlitzLock AS RETURN 0;'); -GO + END; -ALTER PROCEDURE dbo.sp_BlitzLock -( - @Top INT = 2147483647, - @DatabaseName NVARCHAR(256) = NULL, - @StartDate DATETIME = '19000101', - @EndDate DATETIME = '99991231', - @ObjectName NVARCHAR(1000) = NULL, - @StoredProcName NVARCHAR(1000) = NULL, - @AppName NVARCHAR(256) = NULL, - @HostName NVARCHAR(256) = NULL, - @LoginName NVARCHAR(256) = NULL, - @EventSessionPath VARCHAR(256) = 'system_health*.xel', - @VictimsOnly BIT = 0, - @Debug BIT = 0, - @Help BIT = 0, - @Version VARCHAR(30) = NULL OUTPUT, - @VersionDate DATETIME = NULL OUTPUT, - @VersionCheckMode BIT = 0, - @OutputDatabaseName NVARCHAR(256) = NULL , - @OutputSchemaName NVARCHAR(256) = 'dbo' , --ditto as below - @OutputTableName NVARCHAR(256) = 'BlitzLock', --put a standard here no need to check later in the script - @ExportToExcel BIT = 0 -) -WITH RECOMPILE -AS -BEGIN +EXEC sys.sp_executesql @stmt = @sql_select, + @params = @sp_params, + @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; -SET NOCOUNT ON; -SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.0', @VersionDate = '20210117'; +SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; +SET @sql_select += N' +WITH cpu_max +AS ( SELECT TOP 1 + gi.start_range, + gi.end_range + FROM #grouped_interval AS gi + ORDER BY gi.total_max_cpu_time_ms DESC ) +INSERT #working_plans WITH (TABLOCK) + ( plan_id, query_id, pattern ) +SELECT TOP ( @sp_Top ) + qsp.plan_id, qsp.query_id, ''max cpu'' +FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp +JOIN cpu_max AS dm +ON qsp.last_execution_time >= dm.start_range + AND qsp.last_execution_time < dm.end_range +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs +ON qsrs.plan_id = qsp.plan_id +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq +ON qsq.query_id = qsp.query_id +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt +ON qsqt.query_text_id = qsq.query_text_id +WHERE 1 = 1 + AND qsq.is_internal_query = 0 + AND qsp.query_plan IS NOT NULL + '; +SET @sql_select += @sql_where; -IF(@VersionCheckMode = 1) -BEGIN - RETURN; -END; - IF @Help = 1 - BEGIN - PRINT ' - /* - sp_BlitzLock from http://FirstResponderKit.org - - This script checks for and analyzes deadlocks from the system health session or a custom extended event path +SET @sql_select += N'ORDER BY qsrs.max_cpu_time DESC + OPTION (RECOMPILE); + '; - Variables you can use: - @Top: Use if you want to limit the number of deadlocks to return. - This is ordered by event date ascending +IF @Debug = 1 + PRINT @sql_select; - @DatabaseName: If you want to filter to a specific database +IF @sql_select IS NULL + BEGIN + RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; + RETURN; + END; - @StartDate: The date you want to start searching on. +EXEC sys.sp_executesql @stmt = @sql_select, + @params = @sp_params, + @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; - @EndDate: The date you want to stop searching on. - @ObjectName: If you want to filter to a specific able. - The object name has to be fully qualified ''Database.Schema.Table'' +/*Get highest logical read plans*/ - @StoredProcName: If you want to search for a single stored proc - The proc name has to be fully qualified ''Database.Schema.Sproc'' - - @AppName: If you want to filter to a specific application - - @HostName: If you want to filter to a specific host - - @LoginName: If you want to filter to a specific login +RAISERROR(N'Gathering highest logical read plans', 0, 1) WITH NOWAIT; - @EventSessionPath: If you want to point this at an XE session rather than the system health session. - - @OutputDatabaseName: If you want to output information to a specific database - @OutputSchemaName: Specify a schema name to output information to a specific Schema - @OutputTableName: Specify table name to to output information to a specific table - - To learn more, visit http://FirstResponderKit.org where you can download new - versions for free, watch training videos on how it works, get more info on - the findings, contribute your own code, and more. +SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; +SET @sql_select += N' +WITH logical_reads_max +AS ( SELECT TOP 1 + gi.start_range, + gi.end_range + FROM #grouped_interval AS gi + ORDER BY gi.total_avg_logical_io_reads_mb DESC ) +INSERT #working_plans WITH (TABLOCK) + ( plan_id, query_id, pattern ) +SELECT TOP ( @sp_Top ) + qsp.plan_id, qsp.query_id, ''avg logical reads'' +FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp +JOIN logical_reads_max AS dm +ON qsp.last_execution_time >= dm.start_range + AND qsp.last_execution_time < dm.end_range +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs +ON qsrs.plan_id = qsp.plan_id +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq +ON qsq.query_id = qsp.query_id +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt +ON qsqt.query_text_id = qsq.query_text_id +WHERE 1 = 1 + AND qsq.is_internal_query = 0 + AND qsp.query_plan IS NOT NULL + '; + +SET @sql_select += @sql_where; + +SET @sql_select += N'ORDER BY qsrs.avg_logical_io_reads DESC + OPTION (RECOMPILE); + '; + +IF @Debug = 1 + PRINT @sql_select; + +IF @sql_select IS NULL + BEGIN + RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; + RETURN; + END; + +EXEC sys.sp_executesql @stmt = @sql_select, + @params = @sp_params, + @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; + + +SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; +SET @sql_select += N' +WITH logical_reads_max +AS ( SELECT TOP 1 + gi.start_range, + gi.end_range + FROM #grouped_interval AS gi + ORDER BY gi.total_max_logical_io_reads_mb DESC ) +INSERT #working_plans WITH (TABLOCK) + ( plan_id, query_id, pattern ) +SELECT TOP ( @sp_Top ) + qsp.plan_id, qsp.query_id, ''max logical reads'' +FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp +JOIN logical_reads_max AS dm +ON qsp.last_execution_time >= dm.start_range + AND qsp.last_execution_time < dm.end_range +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs +ON qsrs.plan_id = qsp.plan_id +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq +ON qsq.query_id = qsp.query_id +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt +ON qsqt.query_text_id = qsq.query_text_id +WHERE 1 = 1 + AND qsq.is_internal_query = 0 + AND qsp.query_plan IS NOT NULL + '; + +SET @sql_select += @sql_where; + +SET @sql_select += N'ORDER BY qsrs.max_logical_io_reads DESC + OPTION (RECOMPILE); + '; + +IF @Debug = 1 + PRINT @sql_select; + +IF @sql_select IS NULL + BEGIN + RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; + RETURN; + END; + +EXEC sys.sp_executesql @stmt = @sql_select, + @params = @sp_params, + @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; + + +/*Get highest physical read plans*/ + +RAISERROR(N'Gathering highest physical read plans', 0, 1) WITH NOWAIT; + +SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; +SET @sql_select += N' +WITH physical_read_max +AS ( SELECT TOP 1 + gi.start_range, + gi.end_range + FROM #grouped_interval AS gi + ORDER BY gi.total_avg_physical_io_reads_mb DESC ) +INSERT #working_plans WITH (TABLOCK) + ( plan_id, query_id, pattern ) +SELECT TOP ( @sp_Top ) + qsp.plan_id, qsp.query_id, ''avg physical reads'' +FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp +JOIN physical_read_max AS dm +ON qsp.last_execution_time >= dm.start_range + AND qsp.last_execution_time < dm.end_range +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs +ON qsrs.plan_id = qsp.plan_id +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq +ON qsq.query_id = qsp.query_id +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt +ON qsqt.query_text_id = qsq.query_text_id +WHERE 1 = 1 + AND qsq.is_internal_query = 0 + AND qsp.query_plan IS NOT NULL + '; + +SET @sql_select += @sql_where; + +SET @sql_select += N'ORDER BY qsrs.avg_physical_io_reads DESC + OPTION (RECOMPILE); + '; + +IF @Debug = 1 + PRINT @sql_select; + +IF @sql_select IS NULL + BEGIN + RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; + RETURN; + END; + +EXEC sys.sp_executesql @stmt = @sql_select, + @params = @sp_params, + @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; - Known limitations of this version: - - Only SQL Server 2012 and newer is supported - - If your tables have weird characters in them (https://en.wikipedia.org/wiki/List_of_XML_and_HTML_character_entity_references) you may get errors trying to parse the XML. - I took a long look at this one, and: - 1) Trying to account for all the weird places these could crop up is a losing effort. - 2) Replace is slow af on lots of XML. - - Your mom. +SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; +SET @sql_select += N' +WITH physical_read_max +AS ( SELECT TOP 1 + gi.start_range, + gi.end_range + FROM #grouped_interval AS gi + ORDER BY gi.total_max_physical_io_reads_mb DESC ) +INSERT #working_plans WITH (TABLOCK) + ( plan_id, query_id, pattern ) +SELECT TOP ( @sp_Top ) + qsp.plan_id, qsp.query_id, ''max physical reads'' +FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp +JOIN physical_read_max AS dm +ON qsp.last_execution_time >= dm.start_range + AND qsp.last_execution_time < dm.end_range +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs +ON qsrs.plan_id = qsp.plan_id +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq +ON qsq.query_id = qsp.query_id +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt +ON qsqt.query_text_id = qsq.query_text_id +WHERE 1 = 1 + AND qsq.is_internal_query = 0 + AND qsp.query_plan IS NOT NULL + '; + +SET @sql_select += @sql_where; + +SET @sql_select += N'ORDER BY qsrs.max_physical_io_reads DESC + OPTION (RECOMPILE); + '; +IF @Debug = 1 + PRINT @sql_select; + +IF @sql_select IS NULL + BEGIN + RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; + RETURN; + END; + +EXEC sys.sp_executesql @stmt = @sql_select, + @params = @sp_params, + @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; + + +/*Get highest logical write plans*/ + +RAISERROR(N'Gathering highest write plans', 0, 1) WITH NOWAIT; +SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; +SET @sql_select += N' +WITH logical_writes_max +AS ( SELECT TOP 1 + gi.start_range, + gi.end_range + FROM #grouped_interval AS gi + ORDER BY gi.total_avg_logical_io_writes_mb DESC ) +INSERT #working_plans WITH (TABLOCK) + ( plan_id, query_id, pattern ) +SELECT TOP ( @sp_Top ) + qsp.plan_id, qsp.query_id, ''avg writes'' +FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp +JOIN logical_writes_max AS dm +ON qsp.last_execution_time >= dm.start_range + AND qsp.last_execution_time < dm.end_range +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs +ON qsrs.plan_id = qsp.plan_id +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq +ON qsq.query_id = qsp.query_id +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt +ON qsqt.query_text_id = qsq.query_text_id +WHERE 1 = 1 + AND qsq.is_internal_query = 0 + AND qsp.query_plan IS NOT NULL + '; - Unknown limitations of this version: - - None. (If we knew them, they would be known. Duh.) +SET @sql_select += @sql_where; +SET @sql_select += N'ORDER BY qsrs.avg_logical_io_writes DESC + OPTION (RECOMPILE); + '; - MIT License - - Copyright (c) 2021 Brent Ozar Unlimited +IF @Debug = 1 + PRINT @sql_select; - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: +IF @sql_select IS NULL + BEGIN + RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; + RETURN; + END; - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. +EXEC sys.sp_executesql @stmt = @sql_select, + @params = @sp_params, + @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE. - */'; - RETURN; - END; /* @Help = 1 */ - - DECLARE @ProductVersion NVARCHAR(128); - DECLARE @ProductVersionMajor FLOAT; - DECLARE @ProductVersionMinor INT; +SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; +SET @sql_select += N' +WITH logical_writes_max +AS ( SELECT TOP 1 + gi.start_range, + gi.end_range + FROM #grouped_interval AS gi + ORDER BY gi.total_max_logical_io_writes_mb DESC ) +INSERT #working_plans WITH (TABLOCK) + ( plan_id, query_id, pattern ) +SELECT TOP ( @sp_Top ) + qsp.plan_id, qsp.query_id, ''max writes'' +FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp +JOIN logical_writes_max AS dm +ON qsp.last_execution_time >= dm.start_range + AND qsp.last_execution_time < dm.end_range +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs +ON qsrs.plan_id = qsp.plan_id +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq +ON qsq.query_id = qsp.query_id +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt +ON qsqt.query_text_id = qsq.query_text_id +WHERE 1 = 1 + AND qsq.is_internal_query = 0 + AND qsp.query_plan IS NOT NULL + '; - SET @ProductVersion = CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)); +SET @sql_select += @sql_where; - SELECT @ProductVersionMajor = SUBSTRING(@ProductVersion, 1, CHARINDEX('.', @ProductVersion) + 1), - @ProductVersionMinor = PARSENAME(CONVERT(VARCHAR(32), @ProductVersion), 2); +SET @sql_select += N'ORDER BY qsrs.max_logical_io_writes DESC + OPTION (RECOMPILE); + '; +IF @Debug = 1 + PRINT @sql_select; - IF @ProductVersionMajor < 11.0 - BEGIN - RAISERROR( - 'sp_BlitzLock will throw a bunch of angry errors on versions of SQL Server earlier than 2012.', - 0, - 1) WITH NOWAIT; - RETURN; - END; - - IF ((SELECT SERVERPROPERTY ('EDITION')) = 'SQL Azure' - AND - LOWER(@EventSessionPath) NOT LIKE 'http%') - BEGIN - RAISERROR( - 'The default storage path doesn''t work in Azure SQLDB/Managed instances. -You need to use an Azure storage account, and the path has to look like this: https://StorageAccount.blob.core.windows.net/Container/FileName.xel', - 0, - 1) WITH NOWAIT; - RETURN; - END; +IF @sql_select IS NULL + BEGIN + RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; + RETURN; + END; +EXEC sys.sp_executesql @stmt = @sql_select, + @params = @sp_params, + @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; - IF @Top IS NULL - SET @Top = 2147483647; - IF @StartDate IS NULL - SET @StartDate = '19000101'; +/*Get highest memory use plans*/ - IF @EndDate IS NULL - SET @EndDate = '99991231'; - +RAISERROR(N'Gathering highest memory use plans', 0, 1) WITH NOWAIT; - IF OBJECT_ID('tempdb..#deadlock_data') IS NOT NULL - DROP TABLE #deadlock_data; +SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; +SET @sql_select += N' +WITH memory_max +AS ( SELECT TOP 1 + gi.start_range, + gi.end_range + FROM #grouped_interval AS gi + ORDER BY gi.total_avg_query_max_used_memory_mb DESC ) +INSERT #working_plans WITH (TABLOCK) + ( plan_id, query_id, pattern ) +SELECT TOP ( @sp_Top ) + qsp.plan_id, qsp.query_id, ''avg memory'' +FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp +JOIN memory_max AS dm +ON qsp.last_execution_time >= dm.start_range + AND qsp.last_execution_time < dm.end_range +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs +ON qsrs.plan_id = qsp.plan_id +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq +ON qsq.query_id = qsp.query_id +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt +ON qsqt.query_text_id = qsq.query_text_id +WHERE 1 = 1 + AND qsq.is_internal_query = 0 + AND qsp.query_plan IS NOT NULL + '; - IF OBJECT_ID('tempdb..#deadlock_process') IS NOT NULL - DROP TABLE #deadlock_process; +SET @sql_select += @sql_where; - IF OBJECT_ID('tempdb..#deadlock_stack') IS NOT NULL - DROP TABLE #deadlock_stack; +SET @sql_select += N'ORDER BY qsrs.avg_query_max_used_memory DESC + OPTION (RECOMPILE); + '; - IF OBJECT_ID('tempdb..#deadlock_resource') IS NOT NULL - DROP TABLE #deadlock_resource; +IF @Debug = 1 + PRINT @sql_select; - IF OBJECT_ID('tempdb..#deadlock_owner_waiter') IS NOT NULL - DROP TABLE #deadlock_owner_waiter; +IF @sql_select IS NULL + BEGIN + RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; + RETURN; + END; - IF OBJECT_ID('tempdb..#deadlock_findings') IS NOT NULL - DROP TABLE #deadlock_findings; +EXEC sys.sp_executesql @stmt = @sql_select, + @params = @sp_params, + @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; - CREATE TABLE #deadlock_findings - ( - id INT IDENTITY(1, 1) PRIMARY KEY CLUSTERED, - check_id INT NOT NULL, - database_name NVARCHAR(256), - object_name NVARCHAR(1000), - finding_group NVARCHAR(100), - finding NVARCHAR(4000) - ); +SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; +SET @sql_select += N' +WITH memory_max +AS ( SELECT TOP 1 + gi.start_range, + gi.end_range + FROM #grouped_interval AS gi + ORDER BY gi.total_max_query_max_used_memory_mb DESC ) +INSERT #working_plans WITH (TABLOCK) + ( plan_id, query_id, pattern ) +SELECT TOP ( @sp_Top ) + qsp.plan_id, qsp.query_id, ''max memory'' +FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp +JOIN memory_max AS dm +ON qsp.last_execution_time >= dm.start_range + AND qsp.last_execution_time < dm.end_range +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs +ON qsrs.plan_id = qsp.plan_id +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq +ON qsq.query_id = qsp.query_id +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt +ON qsqt.query_text_id = qsq.query_text_id +WHERE 1 = 1 + AND qsq.is_internal_query = 0 + AND qsp.query_plan IS NOT NULL + '; - DECLARE @d VARCHAR(40), @StringToExecute NVARCHAR(4000),@StringToExecuteParams NVARCHAR(500),@r NVARCHAR(200),@OutputTableFindings NVARCHAR(100),@DeadlockCount INT; - DECLARE @ServerName NVARCHAR(256) - DECLARE @OutputDatabaseCheck BIT; - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - SET @OutputTableFindings = '[BlitzLockFindings]' - SET @ServerName = (select @@ServerName) - if(@OutputDatabaseName is not null) - BEGIN --if databaseName is set do some sanity checks and put [] around def. - if( (select name from sys.databases where name=@OutputDatabaseName) is null ) --if database is invalid raiserror and set bitcheck - BEGIN - RAISERROR('Database Name for output of table is invalid please correct, Output to Table will not be preformed', 0, 1, @d) WITH NOWAIT; - set @OutputDatabaseCheck = -1 -- -1 invalid/false, 0 = good/true - END - ELSE - BEGIN - set @OutputDatabaseCheck = 0 - select @StringToExecute = N'select @r = name from ' + '' + @OutputDatabaseName + - '' + '.sys.objects where type_desc=''USER_TABLE'' and name=' + '''' + @OutputTableName + '''', - @StringToExecuteParams = N'@OutputDatabaseName NVARCHAR(200),@OutputTableName NVARCHAR(200),@r NVARCHAR(200) OUTPUT' - exec sp_executesql @StringToExecute,@StringToExecuteParams,@OutputDatabaseName,@OutputTableName,@r OUTPUT - --put covers around all before. - SELECT @OutputDatabaseName = QUOTENAME(@OutputDatabaseName), - @OutputTableName = QUOTENAME(@OutputTableName), - @OutputSchemaName = QUOTENAME(@OutputSchemaName) - if(@r is null) --if it is null there is no table, create it from above execution - BEGIN - select @StringToExecute = N'use ' + @OutputDatabaseName + ';create table ' + @OutputSchemaName + '.' + @OutputTableName + ' ( - ServerName NVARCHAR(256), - deadlock_type NVARCHAR(256), - event_date datetime, - database_name NVARCHAR(256), - deadlock_group NVARCHAR(256), - query XML, - object_names XML, - isolation_level NVARCHAR(256), - owner_mode NVARCHAR(256), - waiter_mode NVARCHAR(256), - transaction_count bigint, - login_name NVARCHAR(256), - host_name NVARCHAR(256), - client_app NVARCHAR(256), - wait_time BIGINT, - priority smallint, - log_used BIGINT, - last_tran_started datetime, - last_batch_started datetime, - last_batch_completed datetime, - transaction_name NVARCHAR(256), - owner_waiter_type NVARCHAR(256), - owner_activity NVARCHAR(256), - owner_waiter_activity NVARCHAR(256), - owner_merging NVARCHAR(256), - owner_spilling NVARCHAR(256), - owner_waiting_to_close NVARCHAR(256), - waiter_waiter_type NVARCHAR(256), - waiter_owner_activity NVARCHAR(256), - waiter_waiter_activity NVARCHAR(256), - waiter_merging NVARCHAR(256), - waiter_spilling NVARCHAR(256), - waiter_waiting_to_close NVARCHAR(256), - deadlock_graph XML)', - @StringToExecuteParams = N'@OutputDatabaseName NVARCHAR(200),@OutputSchemaName NVARCHAR(100),@OutputTableName NVARCHAR(200)' - exec sp_executesql @StringToExecute, @StringToExecuteParams,@OutputDatabaseName,@OutputSchemaName,@OutputTableName - --table created. - select @StringToExecute = N'select @r = name from ' + '' + @OutputDatabaseName + - '' + '.sys.objects where type_desc=''USER_TABLE'' and name=''BlitzLockFindings''', - @StringToExecuteParams = N'@OutputDatabaseName NVARCHAR(200),@r NVARCHAR(200) OUTPUT' - exec sp_executesql @StringToExecute,@StringToExecuteParams,@OutputDatabaseName,@r OUTPUT - if(@r is null) --if table does not excist - BEGIN - select @OutputTableFindings=N'[BlitzLockFindings]', - @StringToExecute = N'use ' + @OutputDatabaseName + ';create table ' + @OutputSchemaName + '.' + @OutputTableFindings + ' ( - ServerName NVARCHAR(256), - check_id INT, - database_name NVARCHAR(256), - object_name NVARCHAR(1000), - finding_group NVARCHAR(100), - finding NVARCHAR(4000))', - @StringToExecuteParams = N'@OutputDatabaseName NVARCHAR(200),@OutputSchemaName NVARCHAR(100),@OutputTableFindings NVARCHAR(200)' - exec sp_executesql @StringToExecute, @StringToExecuteParams, @OutputDatabaseName,@OutputSchemaName,@OutputTableFindings - - END +SET @sql_select += @sql_where; - END - --create synonym for deadlockfindings. - if((select name from sys.objects where name='DeadlockFindings' and type_desc='SYNONYM')IS NOT NULL) - BEGIN - RAISERROR('found synonym', 0, 1) WITH NOWAIT; - drop synonym DeadlockFindings; - END - set @StringToExecute = 'CREATE SYNONYM DeadlockFindings FOR ' + @OutputDatabaseName + '.' + @OutputSchemaName + '.' + @OutputTableFindings; - exec sp_executesql @StringToExecute - - --create synonym for deadlock table. - if((select name from sys.objects where name='DeadLockTbl' and type_desc='SYNONYM') IS NOT NULL) - BEGIN - drop SYNONYM DeadLockTbl; - END - set @StringToExecute = 'CREATE SYNONYM DeadLockTbl FOR ' + @OutputDatabaseName + '.' + @OutputSchemaName + '.' + @OutputTableName; - exec sp_executesql @StringToExecute - - END - END - +SET @sql_select += N'ORDER BY qsrs.max_query_max_used_memory DESC + OPTION (RECOMPILE); + '; - CREATE TABLE #t (id INT NOT NULL); +IF @Debug = 1 + PRINT @sql_select; - /* WITH ROWCOUNT doesn't work on Amazon RDS - see: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2037 */ - IF LEFT(CAST(SERVERPROPERTY('ComputerNamePhysicalNetBIOS') AS VARCHAR(8000)), 8) <> 'EC2AMAZ-' - AND LEFT(CAST(SERVERPROPERTY('MachineName') AS VARCHAR(8000)), 8) <> 'EC2AMAZ-' - AND LEFT(CAST(SERVERPROPERTY('ServerName') AS VARCHAR(8000)), 8) <> 'EC2AMAZ-' - AND db_id('rdsadmin') IS NULL - BEGIN; - BEGIN TRY; - UPDATE STATISTICS #t WITH ROWCOUNT = 100000000, PAGECOUNT = 100000000; - END TRY - BEGIN CATCH; - /* Misleading error returned, if run without permissions to update statistics the error returned is "Cannot find object". - Catching specific error, and returning message with better info. If any other error is returned, then throw as normal */ - IF (ERROR_NUMBER() = 1088) - BEGIN; - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Cannot run UPDATE STATISTICS on temp table without db_owner or sysadmin permissions %s', 0, 1, @d) WITH NOWAIT; - END; - ELSE - BEGIN; - THROW; - END; - END CATCH; - END; +IF @sql_select IS NULL + BEGIN + RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; + RETURN; + END; - /*Grab the initial set of XML to parse*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Grab the initial set of XML to parse at %s', 0, 1, @d) WITH NOWAIT; - WITH xml - AS ( SELECT CONVERT(XML, event_data) AS deadlock_xml - FROM sys.fn_xe_file_target_read_file(@EventSessionPath, NULL, NULL, NULL) ) - SELECT ISNULL(xml.deadlock_xml, '') AS deadlock_xml - INTO #deadlock_data - FROM xml - LEFT JOIN #t AS t - ON 1 = 1 - CROSS APPLY xml.deadlock_xml.nodes('/event/@name') AS x(c) - WHERE 1 = 1 - AND x.c.exist('/event/@name[ .= "xml_deadlock_report"]') = 1 - AND CONVERT(datetime, SWITCHOFFSET(CONVERT(datetimeoffset, xml.deadlock_xml.value('(/event/@timestamp)[1]', 'datetime') ), DATENAME(TzOffset, SYSDATETIMEOFFSET()))) > @StartDate - AND CONVERT(datetime, SWITCHOFFSET(CONVERT(datetimeoffset, xml.deadlock_xml.value('(/event/@timestamp)[1]', 'datetime') ), DATENAME(TzOffset, SYSDATETIMEOFFSET()))) < @EndDate - ORDER BY xml.deadlock_xml.value('(/event/@timestamp)[1]', 'datetime') DESC - OPTION ( RECOMPILE ); +EXEC sys.sp_executesql @stmt = @sql_select, + @params = @sp_params, + @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; - /*Optimization: if we got back more rows than @Top, remove them. This seems to be better than using @Top in the query above as that results in excessive memory grant*/ - SET @DeadlockCount = @@ROWCOUNT - IF( @Top < @DeadlockCount ) BEGIN - WITH T - AS ( - SELECT TOP ( @DeadlockCount - @Top) * - FROM #deadlock_data - ORDER BY #deadlock_data.deadlock_xml.value('(/event/@timestamp)[1]', 'datetime') ASC) - DELETE FROM T - END - /*Parse process and input buffer XML*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Parse process and input buffer XML %s', 0, 1, @d) WITH NOWAIT; - SELECT q.event_date, - q.victim_id, - CONVERT(BIT, q.is_parallel) AS is_parallel, - q.deadlock_graph, - q.id, - q.database_id, - q.priority, - q.log_used, - q.wait_resource, - q.wait_time, - q.transaction_name, - q.last_tran_started, - q.last_batch_started, - q.last_batch_completed, - q.lock_mode, - q.transaction_count, - q.client_app, - q.host_name, - q.login_name, - q.isolation_level, - q.process_xml, - ISNULL(ca2.ib.query('.'), '') AS input_buffer - INTO #deadlock_process - FROM ( SELECT dd.deadlock_xml, - CONVERT(DATETIME2(7), SWITCHOFFSET(CONVERT(datetimeoffset, dd.event_date ), DATENAME(TzOffset, SYSDATETIMEOFFSET()))) AS event_date, - dd.victim_id, - CONVERT(tinyint, dd.is_parallel) + CONVERT(tinyint, dd.is_parallel_batch) AS is_parallel, - dd.deadlock_graph, - ca.dp.value('@id', 'NVARCHAR(256)') AS id, - ca.dp.value('@currentdb', 'BIGINT') AS database_id, - ca.dp.value('@priority', 'SMALLINT') AS priority, - ca.dp.value('@logused', 'BIGINT') AS log_used, - ca.dp.value('@waitresource', 'NVARCHAR(256)') AS wait_resource, - ca.dp.value('@waittime', 'BIGINT') AS wait_time, - ca.dp.value('@transactionname', 'NVARCHAR(256)') AS transaction_name, - ca.dp.value('@lasttranstarted', 'DATETIME2(7)') AS last_tran_started, - ca.dp.value('@lastbatchstarted', 'DATETIME2(7)') AS last_batch_started, - ca.dp.value('@lastbatchcompleted', 'DATETIME2(7)') AS last_batch_completed, - ca.dp.value('@lockMode', 'NVARCHAR(256)') AS lock_mode, - ca.dp.value('@trancount', 'BIGINT') AS transaction_count, - ca.dp.value('@clientapp', 'NVARCHAR(256)') AS client_app, - ca.dp.value('@hostname', 'NVARCHAR(256)') AS host_name, - ca.dp.value('@loginname', 'NVARCHAR(256)') AS login_name, - ca.dp.value('@isolationlevel', 'NVARCHAR(256)') AS isolation_level, - ISNULL(ca.dp.query('.'), '') AS process_xml - FROM ( SELECT d1.deadlock_xml, - d1.deadlock_xml.value('(event/@timestamp)[1]', 'DATETIME2') AS event_date, - d1.deadlock_xml.value('(//deadlock/victim-list/victimProcess/@id)[1]', 'NVARCHAR(256)') AS victim_id, - d1.deadlock_xml.exist('//deadlock/resource-list/exchangeEvent') AS is_parallel, - d1.deadlock_xml.exist('//deadlock/resource-list/SyncPoint') AS is_parallel_batch, - d1.deadlock_xml.query('/event/data/value/deadlock') AS deadlock_graph - FROM #deadlock_data AS d1 ) AS dd - CROSS APPLY dd.deadlock_xml.nodes('//deadlock/process-list/process') AS ca(dp) - WHERE (ca.dp.value('@currentdb', 'BIGINT') = DB_ID(@DatabaseName) OR @DatabaseName IS NULL) - AND (ca.dp.value('@clientapp', 'NVARCHAR(256)') = @AppName OR @AppName IS NULL) - AND (ca.dp.value('@hostname', 'NVARCHAR(256)') = @HostName OR @HostName IS NULL) - AND (ca.dp.value('@loginname', 'NVARCHAR(256)') = @LoginName OR @LoginName IS NULL) - ) AS q - CROSS APPLY q.deadlock_xml.nodes('//deadlock/process-list/process/inputbuf') AS ca2(ib) - OPTION ( RECOMPILE ); +/*Get highest row count plans*/ +RAISERROR(N'Gathering highest row count plans', 0, 1) WITH NOWAIT; - /*Parse execution stack XML*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Parse execution stack XML %s', 0, 1, @d) WITH NOWAIT; - SELECT DISTINCT - dp.id, - dp.event_date, - ca.dp.value('@procname', 'NVARCHAR(1000)') AS proc_name, - ca.dp.value('@sqlhandle', 'NVARCHAR(128)') AS sql_handle - INTO #deadlock_stack - FROM #deadlock_process AS dp - CROSS APPLY dp.process_xml.nodes('//executionStack/frame') AS ca(dp) - WHERE (ca.dp.value('@procname', 'NVARCHAR(256)') = @StoredProcName OR @StoredProcName IS NULL) - OPTION ( RECOMPILE ); +SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; +SET @sql_select += N' +WITH rowcount_max +AS ( SELECT TOP 1 + gi.start_range, + gi.end_range + FROM #grouped_interval AS gi + ORDER BY gi.total_rowcount DESC ) +INSERT #working_plans WITH (TABLOCK) + ( plan_id, query_id, pattern ) +SELECT TOP ( @sp_Top ) + qsp.plan_id, qsp.query_id, ''avg rows'' +FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp +JOIN rowcount_max AS dm +ON qsp.last_execution_time >= dm.start_range + AND qsp.last_execution_time < dm.end_range +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs +ON qsrs.plan_id = qsp.plan_id +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq +ON qsq.query_id = qsp.query_id +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt +ON qsqt.query_text_id = qsq.query_text_id +WHERE 1 = 1 + AND qsq.is_internal_query = 0 + AND qsp.query_plan IS NOT NULL + '; +SET @sql_select += @sql_where; +SET @sql_select += N'ORDER BY qsrs.avg_rowcount DESC + OPTION (RECOMPILE); + '; - /*Grab the full resource list*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Grab the full resource list %s', 0, 1, @d) WITH NOWAIT; - SELECT - CONVERT(DATETIME2(7), SWITCHOFFSET(CONVERT(datetimeoffset, dr.event_date), DATENAME(TzOffset, SYSDATETIMEOFFSET()))) AS event_date, - dr.victim_id, - dr.resource_xml - INTO #deadlock_resource - FROM - ( - SELECT dd.deadlock_xml.value('(event/@timestamp)[1]', 'DATETIME2') AS event_date, - dd.deadlock_xml.value('(//deadlock/victim-list/victimProcess/@id)[1]', 'NVARCHAR(256)') AS victim_id, - ISNULL(ca.dp.query('.'), '') AS resource_xml - FROM #deadlock_data AS dd - CROSS APPLY dd.deadlock_xml.nodes('//deadlock/resource-list') AS ca(dp) - ) AS dr - OPTION ( RECOMPILE ); +IF @Debug = 1 + PRINT @sql_select; +IF @sql_select IS NULL + BEGIN + RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; + RETURN; + END; - /*Parse object locks*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Parse object locks %s', 0, 1, @d) WITH NOWAIT; - SELECT DISTINCT - ca.event_date, - ca.database_id, - ca.object_name, - ca.lock_mode, - ca.index_name, - ca.associatedObjectId, - w.l.value('@id', 'NVARCHAR(256)') AS waiter_id, - w.l.value('@mode', 'NVARCHAR(256)') AS waiter_mode, - o.l.value('@id', 'NVARCHAR(256)') AS owner_id, - o.l.value('@mode', 'NVARCHAR(256)') AS owner_mode, - N'OBJECT' AS lock_type - INTO #deadlock_owner_waiter - FROM ( - SELECT dr.event_date, - ca.dr.value('@dbid', 'BIGINT') AS database_id, - ca.dr.value('@objectname', 'NVARCHAR(256)') AS object_name, - ca.dr.value('@mode', 'NVARCHAR(256)') AS lock_mode, - ca.dr.value('@indexname', 'NVARCHAR(256)') AS index_name, - ca.dr.value('@associatedObjectId', 'BIGINT') AS associatedObjectId, - ca.dr.query('.') AS dr - FROM #deadlock_resource AS dr - CROSS APPLY dr.resource_xml.nodes('//resource-list/objectlock') AS ca(dr) - ) AS ca - CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l) - CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l) - WHERE (ca.object_name = @ObjectName OR @ObjectName IS NULL) - OPTION ( RECOMPILE ); +EXEC sys.sp_executesql @stmt = @sql_select, + @params = @sp_params, + @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; +IF @new_columns = 1 +BEGIN - /*Parse page locks*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Parse page locks %s', 0, 1, @d) WITH NOWAIT; - INSERT #deadlock_owner_waiter WITH(TABLOCKX) - SELECT DISTINCT - ca.event_date, - ca.database_id, - ca.object_name, - ca.lock_mode, - ca.index_name, - ca.associatedObjectId, - w.l.value('@id', 'NVARCHAR(256)') AS waiter_id, - w.l.value('@mode', 'NVARCHAR(256)') AS waiter_mode, - o.l.value('@id', 'NVARCHAR(256)') AS owner_id, - o.l.value('@mode', 'NVARCHAR(256)') AS owner_mode, - N'PAGE' AS lock_type - FROM ( - SELECT dr.event_date, - ca.dr.value('@dbid', 'BIGINT') AS database_id, - ca.dr.value('@objectname', 'NVARCHAR(256)') AS object_name, - ca.dr.value('@mode', 'NVARCHAR(256)') AS lock_mode, - ca.dr.value('@indexname', 'NVARCHAR(256)') AS index_name, - ca.dr.value('@associatedObjectId', 'BIGINT') AS associatedObjectId, - ca.dr.query('.') AS dr - FROM #deadlock_resource AS dr - CROSS APPLY dr.resource_xml.nodes('//resource-list/pagelock') AS ca(dr) - ) AS ca - CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l) - CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l) - OPTION ( RECOMPILE ); +RAISERROR(N'Gathering new 2017 new column info...', 0, 1) WITH NOWAIT; +/*Get highest log byte count plans*/ - /*Parse key locks*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Parse key locks %s', 0, 1, @d) WITH NOWAIT; - INSERT #deadlock_owner_waiter WITH(TABLOCKX) - SELECT DISTINCT - ca.event_date, - ca.database_id, - ca.object_name, - ca.lock_mode, - ca.index_name, - ca.associatedObjectId, - w.l.value('@id', 'NVARCHAR(256)') AS waiter_id, - w.l.value('@mode', 'NVARCHAR(256)') AS waiter_mode, - o.l.value('@id', 'NVARCHAR(256)') AS owner_id, - o.l.value('@mode', 'NVARCHAR(256)') AS owner_mode, - N'KEY' AS lock_type - FROM ( - SELECT dr.event_date, - ca.dr.value('@dbid', 'BIGINT') AS database_id, - ca.dr.value('@objectname', 'NVARCHAR(256)') AS object_name, - ca.dr.value('@mode', 'NVARCHAR(256)') AS lock_mode, - ca.dr.value('@indexname', 'NVARCHAR(256)') AS index_name, - ca.dr.value('@associatedObjectId', 'BIGINT') AS associatedObjectId, - ca.dr.query('.') AS dr - FROM #deadlock_resource AS dr - CROSS APPLY dr.resource_xml.nodes('//resource-list/keylock') AS ca(dr) - ) AS ca - CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l) - CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l) - OPTION ( RECOMPILE ); +RAISERROR(N'Gathering highest log byte use plans', 0, 1) WITH NOWAIT; +SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; +SET @sql_select += N' +WITH rowcount_max +AS ( SELECT TOP 1 + gi.start_range, + gi.end_range + FROM #grouped_interval AS gi + ORDER BY gi.total_avg_log_bytes_mb DESC ) +INSERT #working_plans WITH (TABLOCK) + ( plan_id, query_id, pattern ) +SELECT TOP ( @sp_Top ) + qsp.plan_id, qsp.query_id, ''avg log bytes'' +FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp +JOIN rowcount_max AS dm +ON qsp.last_execution_time >= dm.start_range + AND qsp.last_execution_time < dm.end_range +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs +ON qsrs.plan_id = qsp.plan_id +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq +ON qsq.query_id = qsp.query_id +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt +ON qsqt.query_text_id = qsq.query_text_id +WHERE 1 = 1 + AND qsq.is_internal_query = 0 + AND qsp.query_plan IS NOT NULL + '; - /*Parse RID locks*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Parse RID locks %s', 0, 1, @d) WITH NOWAIT; - INSERT #deadlock_owner_waiter WITH(TABLOCKX) - SELECT DISTINCT - ca.event_date, - ca.database_id, - ca.object_name, - ca.lock_mode, - ca.index_name, - ca.associatedObjectId, - w.l.value('@id', 'NVARCHAR(256)') AS waiter_id, - w.l.value('@mode', 'NVARCHAR(256)') AS waiter_mode, - o.l.value('@id', 'NVARCHAR(256)') AS owner_id, - o.l.value('@mode', 'NVARCHAR(256)') AS owner_mode, - N'RID' AS lock_type - FROM ( - SELECT dr.event_date, - ca.dr.value('@dbid', 'BIGINT') AS database_id, - ca.dr.value('@objectname', 'NVARCHAR(256)') AS object_name, - ca.dr.value('@mode', 'NVARCHAR(256)') AS lock_mode, - ca.dr.value('@indexname', 'NVARCHAR(256)') AS index_name, - ca.dr.value('@associatedObjectId', 'BIGINT') AS associatedObjectId, - ca.dr.query('.') AS dr - FROM #deadlock_resource AS dr - CROSS APPLY dr.resource_xml.nodes('//resource-list/ridlock') AS ca(dr) - ) AS ca - CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l) - CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l) - OPTION ( RECOMPILE ); +SET @sql_select += @sql_where; +SET @sql_select += N'ORDER BY qsrs.avg_log_bytes_used DESC + OPTION (RECOMPILE); + '; - /*Parse row group locks*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Parse row group locks %s', 0, 1, @d) WITH NOWAIT; - INSERT #deadlock_owner_waiter WITH(TABLOCKX) - SELECT DISTINCT - ca.event_date, - ca.database_id, - ca.object_name, - ca.lock_mode, - ca.index_name, - ca.associatedObjectId, - w.l.value('@id', 'NVARCHAR(256)') AS waiter_id, - w.l.value('@mode', 'NVARCHAR(256)') AS waiter_mode, - o.l.value('@id', 'NVARCHAR(256)') AS owner_id, - o.l.value('@mode', 'NVARCHAR(256)') AS owner_mode, - N'ROWGROUP' AS lock_type - FROM ( - SELECT dr.event_date, - ca.dr.value('@dbid', 'BIGINT') AS database_id, - ca.dr.value('@objectname', 'NVARCHAR(256)') AS object_name, - ca.dr.value('@mode', 'NVARCHAR(256)') AS lock_mode, - ca.dr.value('@indexname', 'NVARCHAR(256)') AS index_name, - ca.dr.value('@associatedObjectId', 'BIGINT') AS associatedObjectId, - ca.dr.query('.') AS dr - FROM #deadlock_resource AS dr - CROSS APPLY dr.resource_xml.nodes('//resource-list/rowgrouplock') AS ca(dr) - ) AS ca - CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l) - CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l) - OPTION ( RECOMPILE ); +IF @Debug = 1 + PRINT @sql_select; - UPDATE d - SET d.index_name = d.object_name - + '.HEAP' - FROM #deadlock_owner_waiter AS d - WHERE lock_type IN (N'HEAP', N'RID') - OPTION(RECOMPILE); +IF @sql_select IS NULL + BEGIN + RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; + RETURN; + END; - /*Parse parallel deadlocks*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Parse parallel deadlocks %s', 0, 1, @d) WITH NOWAIT; - SELECT DISTINCT - ca.id, - ca.event_date, - ca.wait_type, - ca.node_id, - ca.waiter_type, - ca.owner_activity, - ca.waiter_activity, - ca.merging, - ca.spilling, - ca.waiting_to_close, - w.l.value('@id', 'NVARCHAR(256)') AS waiter_id, - o.l.value('@id', 'NVARCHAR(256)') AS owner_id - INTO #deadlock_resource_parallel - FROM ( - SELECT dr.event_date, - ca.dr.value('@id', 'NVARCHAR(256)') AS id, - ca.dr.value('@WaitType', 'NVARCHAR(256)') AS wait_type, - ca.dr.value('@nodeId', 'BIGINT') AS node_id, - /* These columns are in 2017 CU5 ONLY */ - ca.dr.value('@waiterType', 'NVARCHAR(256)') AS waiter_type, - ca.dr.value('@ownerActivity', 'NVARCHAR(256)') AS owner_activity, - ca.dr.value('@waiterActivity', 'NVARCHAR(256)') AS waiter_activity, - ca.dr.value('@merging', 'NVARCHAR(256)') AS merging, - ca.dr.value('@spilling', 'NVARCHAR(256)') AS spilling, - ca.dr.value('@waitingToClose', 'NVARCHAR(256)') AS waiting_to_close, - /* */ - ca.dr.query('.') AS dr - FROM #deadlock_resource AS dr - CROSS APPLY dr.resource_xml.nodes('//resource-list/exchangeEvent') AS ca(dr) - ) AS ca - CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l) - CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l) - OPTION ( RECOMPILE ); +EXEC sys.sp_executesql @stmt = @sql_select, + @params = @sp_params, + @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; +RAISERROR(N'Gathering highest log byte use plans', 0, 1) WITH NOWAIT; - /*Get rid of parallel noise*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Get rid of parallel noise %s', 0, 1, @d) WITH NOWAIT; - WITH c - AS - ( - SELECT *, ROW_NUMBER() OVER ( PARTITION BY drp.owner_id, drp.waiter_id ORDER BY drp.event_date ) AS rn - FROM #deadlock_resource_parallel AS drp - ) - DELETE FROM c - WHERE c.rn > 1 - OPTION ( RECOMPILE ); +SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; +SET @sql_select += N' +WITH rowcount_max +AS ( SELECT TOP 1 + gi.start_range, + gi.end_range + FROM #grouped_interval AS gi + ORDER BY gi.total_max_log_bytes_mb DESC ) +INSERT #working_plans WITH (TABLOCK) + ( plan_id, query_id, pattern ) +SELECT TOP ( @sp_Top ) + qsp.plan_id, qsp.query_id, ''max log bytes'' +FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp +JOIN rowcount_max AS dm +ON qsp.last_execution_time >= dm.start_range + AND qsp.last_execution_time < dm.end_range +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs +ON qsrs.plan_id = qsp.plan_id +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq +ON qsq.query_id = qsp.query_id +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt +ON qsqt.query_text_id = qsq.query_text_id +WHERE 1 = 1 + AND qsq.is_internal_query = 0 + AND qsp.query_plan IS NOT NULL + '; +SET @sql_select += @sql_where; - /*Get rid of nonsense*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Get rid of nonsense %s', 0, 1, @d) WITH NOWAIT; - DELETE dow - FROM #deadlock_owner_waiter AS dow - WHERE dow.owner_id = dow.waiter_id - OPTION ( RECOMPILE ); +SET @sql_select += N'ORDER BY qsrs.max_log_bytes_used DESC + OPTION (RECOMPILE); + '; - /*Add some nonsense*/ - ALTER TABLE #deadlock_process - ADD waiter_mode NVARCHAR(256), - owner_mode NVARCHAR(256), - is_victim AS CONVERT(BIT, CASE WHEN id = victim_id THEN 1 ELSE 0 END); +IF @Debug = 1 + PRINT @sql_select; - /*Update some nonsense*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Update some nonsense part 1 %s', 0, 1, @d) WITH NOWAIT; - UPDATE dp - SET dp.owner_mode = dow.owner_mode - FROM #deadlock_process AS dp - JOIN #deadlock_owner_waiter AS dow - ON dp.id = dow.owner_id - AND dp.event_date = dow.event_date - WHERE dp.is_victim = 0 - OPTION ( RECOMPILE ); +IF @sql_select IS NULL + BEGIN + RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; + RETURN; + END; - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Update some nonsense part 2 %s', 0, 1, @d) WITH NOWAIT; - UPDATE dp - SET dp.waiter_mode = dow.waiter_mode - FROM #deadlock_process AS dp - JOIN #deadlock_owner_waiter AS dow - ON dp.victim_id = dow.waiter_id - AND dp.event_date = dow.event_date - WHERE dp.is_victim = 1 - OPTION ( RECOMPILE ); +EXEC sys.sp_executesql @stmt = @sql_select, + @params = @sp_params, + @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; - /*Get Agent Job and Step names*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Get Agent Job and Step names %s', 0, 1, @d) WITH NOWAIT; - SELECT *, - CONVERT(UNIQUEIDENTIFIER, - CONVERT(XML, '').value('xs:hexBinary(substring(sql:column("x.job_id"), 0) )', 'BINARY(16)') - ) AS job_id_guid - INTO #agent_job - FROM ( - SELECT dp.event_date, - dp.victim_id, - dp.id, - dp.database_id, - dp.client_app, - SUBSTRING(dp.client_app, - CHARINDEX('0x', dp.client_app) + LEN('0x'), - 32 - ) AS job_id, - SUBSTRING(dp.client_app, - CHARINDEX(': Step ', dp.client_app) + LEN(': Step '), - CHARINDEX(')', dp.client_app, CHARINDEX(': Step ', dp.client_app)) - - (CHARINDEX(': Step ', dp.client_app) - + LEN(': Step ')) - ) AS step_id - FROM #deadlock_process AS dp - WHERE dp.client_app LIKE 'SQLAgent - %' - ) AS x - OPTION ( RECOMPILE ); +/*Get highest tempdb use plans*/ - ALTER TABLE #agent_job ADD job_name NVARCHAR(256), - step_name NVARCHAR(256); +RAISERROR(N'Gathering highest tempdb use plans', 0, 1) WITH NOWAIT; - IF SERVERPROPERTY('EngineEdition') NOT IN (5, 6) /* Azure SQL DB doesn't support querying jobs */ - AND NOT (LEFT(CAST(SERVERPROPERTY('ComputerNamePhysicalNetBIOS') AS VARCHAR(8000)), 8) = 'EC2AMAZ-' /* Neither does Amazon RDS Express Edition */ - AND LEFT(CAST(SERVERPROPERTY('MachineName') AS VARCHAR(8000)), 8) = 'EC2AMAZ-' - AND LEFT(CAST(SERVERPROPERTY('ServerName') AS VARCHAR(8000)), 8) = 'EC2AMAZ-' - AND db_id('rdsadmin') IS NOT NULL - AND EXISTS(SELECT * FROM master.sys.all_objects WHERE name IN ('rds_startup_tasks', 'rds_help_revlogin', 'rds_hexadecimal', 'rds_failover_tracking', 'rds_database_tracking', 'rds_track_change')) - ) - BEGIN - SET @StringToExecute = N'UPDATE aj - SET aj.job_name = j.name, - aj.step_name = s.step_name - FROM msdb.dbo.sysjobs AS j - JOIN msdb.dbo.sysjobsteps AS s - ON j.job_id = s.job_id - JOIN #agent_job AS aj - ON aj.job_id_guid = j.job_id - AND aj.step_id = s.step_id - OPTION ( RECOMPILE );'; - EXEC(@StringToExecute); - END +SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; +SET @sql_select += N' +WITH rowcount_max +AS ( SELECT TOP 1 + gi.start_range, + gi.end_range + FROM #grouped_interval AS gi + ORDER BY gi.total_avg_tempdb_space DESC ) +INSERT #working_plans WITH (TABLOCK) + ( plan_id, query_id, pattern ) +SELECT TOP ( @sp_Top ) + qsp.plan_id, qsp.query_id, ''avg tempdb space'' +FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp +JOIN rowcount_max AS dm +ON qsp.last_execution_time >= dm.start_range + AND qsp.last_execution_time < dm.end_range +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs +ON qsrs.plan_id = qsp.plan_id +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq +ON qsq.query_id = qsp.query_id +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt +ON qsqt.query_text_id = qsq.query_text_id +WHERE 1 = 1 + AND qsq.is_internal_query = 0 + AND qsp.query_plan IS NOT NULL + '; - UPDATE dp - SET dp.client_app = - CASE WHEN dp.client_app LIKE N'SQLAgent - %' - THEN N'SQLAgent - Job: ' - + aj.job_name - + N' Step: ' - + aj.step_name - ELSE dp.client_app - END - FROM #deadlock_process AS dp - JOIN #agent_job AS aj - ON dp.event_date = aj.event_date - AND dp.victim_id = aj.victim_id - AND dp.id = aj.id - OPTION ( RECOMPILE ); +SET @sql_select += @sql_where; - /*Get each and every table of all databases*/ - DECLARE @sysAssObjId AS TABLE (database_id bigint, partition_id bigint, schema_name varchar(255), table_name varchar(255)); - INSERT into @sysAssObjId EXECUTE sp_MSforeachdb - N'USE [?]; - SELECT DB_ID() as database_id, p.partition_id, s.name as schema_name, t.name as table_name - FROM sys.partitions p - LEFT JOIN sys.tables t ON t.object_id = p.object_id - LEFT JOIN sys.schemas s ON s.schema_id = t.schema_id - WHERE s.name is not NULL AND t.name is not NULL'; +SET @sql_select += N'ORDER BY qsrs.avg_tempdb_space_used DESC + OPTION (RECOMPILE); + '; +IF @Debug = 1 + PRINT @sql_select; - /*Begin checks based on parsed values*/ +IF @sql_select IS NULL + BEGIN + RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; + RETURN; + END; - /*Check 1 is deadlocks by database*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Check 1 %s', 0, 1, @d) WITH NOWAIT; - INSERT #deadlock_findings WITH (TABLOCKX) - ( check_id, database_name, object_name, finding_group, finding ) - SELECT 1 AS check_id, - DB_NAME(dp.database_id) AS database_name, - '-' AS object_name, - 'Total database locks' AS finding_group, - 'This database had ' - + CONVERT(NVARCHAR(20), COUNT_BIG(DISTINCT dp.event_date)) - + ' deadlocks.' - FROM #deadlock_process AS dp - WHERE 1 = 1 - AND (DB_NAME(dp.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (dp.event_date >= @StartDate OR @StartDate IS NULL) - AND (dp.event_date < @EndDate OR @EndDate IS NULL) - AND (dp.client_app = @AppName OR @AppName IS NULL) - AND (dp.host_name = @HostName OR @HostName IS NULL) - AND (dp.login_name = @LoginName OR @LoginName IS NULL) - GROUP BY DB_NAME(dp.database_id) - OPTION ( RECOMPILE ); +EXEC sys.sp_executesql @stmt = @sql_select, + @params = @sp_params, + @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; - /*Check 2 is deadlocks by object*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Check 2 objects %s', 0, 1, @d) WITH NOWAIT; - INSERT #deadlock_findings WITH (TABLOCKX) - ( check_id, database_name, object_name, finding_group, finding ) - SELECT 2 AS check_id, - ISNULL(DB_NAME(dow.database_id), 'UNKNOWN') AS database_name, - ISNULL(dow.object_name, 'UNKNOWN') AS object_name, - 'Total object deadlocks' AS finding_group, - 'This object was involved in ' - + CONVERT(NVARCHAR(20), COUNT_BIG(DISTINCT dow.event_date)) - + ' deadlock(s).' - FROM #deadlock_owner_waiter AS dow - WHERE 1 = 1 - AND (DB_NAME(dow.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (dow.event_date >= @StartDate OR @StartDate IS NULL) - AND (dow.event_date < @EndDate OR @EndDate IS NULL) - AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) - GROUP BY DB_NAME(dow.database_id), dow.object_name - OPTION ( RECOMPILE ); +SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; +SET @sql_select += N' +WITH rowcount_max +AS ( SELECT TOP 1 + gi.start_range, + gi.end_range + FROM #grouped_interval AS gi + ORDER BY gi.total_max_tempdb_space DESC ) +INSERT #working_plans WITH (TABLOCK) + ( plan_id, query_id, pattern ) +SELECT TOP ( @sp_Top ) + qsp.plan_id, qsp.query_id, ''max tempdb space'' +FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp +JOIN rowcount_max AS dm +ON qsp.last_execution_time >= dm.start_range + AND qsp.last_execution_time < dm.end_range +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs +ON qsrs.plan_id = qsp.plan_id +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq +ON qsq.query_id = qsp.query_id +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt +ON qsqt.query_text_id = qsq.query_text_id +WHERE 1 = 1 + AND qsq.is_internal_query = 0 + AND qsp.query_plan IS NOT NULL + '; - /*Check 2 continuation, number of locks per index*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Check 2 indexes %s', 0, 1, @d) WITH NOWAIT; - INSERT #deadlock_findings WITH (TABLOCKX) - ( check_id, database_name, object_name, finding_group, finding ) - SELECT 2 AS check_id, - ISNULL(DB_NAME(dow.database_id), 'UNKNOWN') AS database_name, - dow.index_name AS index_name, - 'Total index deadlocks' AS finding_group, - 'This index was involved in ' - + CONVERT(NVARCHAR(20), COUNT_BIG(DISTINCT dow.event_date)) - + ' deadlock(s).' - FROM #deadlock_owner_waiter AS dow - WHERE 1 = 1 - AND (DB_NAME(dow.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (dow.event_date >= @StartDate OR @StartDate IS NULL) - AND (dow.event_date < @EndDate OR @EndDate IS NULL) - AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) - AND dow.lock_type NOT IN (N'HEAP', N'RID') - AND dow.index_name is not null - GROUP BY DB_NAME(dow.database_id), dow.index_name - OPTION ( RECOMPILE ); +SET @sql_select += @sql_where; +SET @sql_select += N'ORDER BY qsrs.max_tempdb_space_used DESC + OPTION (RECOMPILE); + '; - /*Check 2 continuation, number of locks per heap*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Check 2 heaps %s', 0, 1, @d) WITH NOWAIT; - INSERT #deadlock_findings WITH (TABLOCKX) - ( check_id, database_name, object_name, finding_group, finding ) - SELECT 2 AS check_id, - ISNULL(DB_NAME(dow.database_id), 'UNKNOWN') AS database_name, - dow.index_name AS index_name, - 'Total heap deadlocks' AS finding_group, - 'This heap was involved in ' - + CONVERT(NVARCHAR(20), COUNT_BIG(DISTINCT dow.event_date)) - + ' deadlock(s).' - FROM #deadlock_owner_waiter AS dow - WHERE 1 = 1 - AND (DB_NAME(dow.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (dow.event_date >= @StartDate OR @StartDate IS NULL) - AND (dow.event_date < @EndDate OR @EndDate IS NULL) - AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) - AND dow.lock_type IN (N'HEAP', N'RID') - GROUP BY DB_NAME(dow.database_id), dow.index_name - OPTION ( RECOMPILE ); - +IF @Debug = 1 + PRINT @sql_select; - /*Check 3 looks for Serializable locking*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Check 3 %s', 0, 1, @d) WITH NOWAIT; - INSERT #deadlock_findings WITH (TABLOCKX) - ( check_id, database_name, object_name, finding_group, finding ) - SELECT 3 AS check_id, - DB_NAME(dp.database_id) AS database_name, - '-' AS object_name, - 'Serializable locking' AS finding_group, - 'This database has had ' + - CONVERT(NVARCHAR(20), COUNT_BIG(*)) + - ' instances of serializable deadlocks.' - AS finding - FROM #deadlock_process AS dp - WHERE dp.isolation_level LIKE 'serializable%' - AND (DB_NAME(dp.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (dp.event_date >= @StartDate OR @StartDate IS NULL) - AND (dp.event_date < @EndDate OR @EndDate IS NULL) - AND (dp.client_app = @AppName OR @AppName IS NULL) - AND (dp.host_name = @HostName OR @HostName IS NULL) - AND (dp.login_name = @LoginName OR @LoginName IS NULL) - GROUP BY DB_NAME(dp.database_id) - OPTION ( RECOMPILE ); +IF @sql_select IS NULL + BEGIN + RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; + RETURN; + END; +EXEC sys.sp_executesql @stmt = @sql_select, + @params = @sp_params, + @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; - /*Check 4 looks for Repeatable Read locking*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Check 4 %s', 0, 1, @d) WITH NOWAIT; - INSERT #deadlock_findings WITH (TABLOCKX) - ( check_id, database_name, object_name, finding_group, finding ) - SELECT 4 AS check_id, - DB_NAME(dp.database_id) AS database_name, - '-' AS object_name, - 'Repeatable Read locking' AS finding_group, - 'This database has had ' + - CONVERT(NVARCHAR(20), COUNT_BIG(*)) + - ' instances of repeatable read deadlocks.' - AS finding - FROM #deadlock_process AS dp - WHERE dp.isolation_level LIKE 'repeatable read%' - AND (DB_NAME(dp.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (dp.event_date >= @StartDate OR @StartDate IS NULL) - AND (dp.event_date < @EndDate OR @EndDate IS NULL) - AND (dp.client_app = @AppName OR @AppName IS NULL) - AND (dp.host_name = @HostName OR @HostName IS NULL) - AND (dp.login_name = @LoginName OR @LoginName IS NULL) - GROUP BY DB_NAME(dp.database_id) - OPTION ( RECOMPILE ); +END; - /*Check 5 breaks down app, host, and login information*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Check 5 %s', 0, 1, @d) WITH NOWAIT; - INSERT #deadlock_findings WITH (TABLOCKX) - ( check_id, database_name, object_name, finding_group, finding ) - SELECT 5 AS check_id, - DB_NAME(dp.database_id) AS database_name, - '-' AS object_name, - 'Login, App, and Host locking' AS finding_group, - 'This database has had ' + - CONVERT(NVARCHAR(20), COUNT_BIG(DISTINCT dp.event_date)) + - ' instances of deadlocks involving the login ' + - ISNULL(dp.login_name, 'UNKNOWN') + - ' from the application ' + - ISNULL(dp.client_app, 'UNKNOWN') + - ' on host ' + - ISNULL(dp.host_name, 'UNKNOWN') - AS finding - FROM #deadlock_process AS dp - WHERE 1 = 1 - AND (DB_NAME(dp.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (dp.event_date >= @StartDate OR @StartDate IS NULL) - AND (dp.event_date < @EndDate OR @EndDate IS NULL) - AND (dp.client_app = @AppName OR @AppName IS NULL) - AND (dp.host_name = @HostName OR @HostName IS NULL) - AND (dp.login_name = @LoginName OR @LoginName IS NULL) - GROUP BY DB_NAME(dp.database_id), dp.login_name, dp.client_app, dp.host_name - OPTION ( RECOMPILE ); +/* +This rolls up the different patterns we find before deduplicating. - /*Check 6 breaks down the types of locks (object, page, key, etc.)*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Check 6 %s', 0, 1, @d) WITH NOWAIT; - WITH lock_types AS ( - SELECT DB_NAME(dp.database_id) AS database_name, - dow.object_name, - SUBSTRING(dp.wait_resource, 1, CHARINDEX(':', dp.wait_resource) -1) AS lock, - CONVERT(NVARCHAR(20), COUNT_BIG(DISTINCT dp.id)) AS lock_count - FROM #deadlock_process AS dp - JOIN #deadlock_owner_waiter AS dow - ON dp.id = dow.owner_id - AND dp.event_date = dow.event_date - WHERE 1 = 1 - AND (DB_NAME(dp.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (dp.event_date >= @StartDate OR @StartDate IS NULL) - AND (dp.event_date < @EndDate OR @EndDate IS NULL) - AND (dp.client_app = @AppName OR @AppName IS NULL) - AND (dp.host_name = @HostName OR @HostName IS NULL) - AND (dp.login_name = @LoginName OR @LoginName IS NULL) - AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) - AND dow.object_name IS NOT NULL - GROUP BY DB_NAME(dp.database_id), SUBSTRING(dp.wait_resource, 1, CHARINDEX(':', dp.wait_resource) - 1), dow.object_name - ) - INSERT #deadlock_findings WITH (TABLOCKX) - ( check_id, database_name, object_name, finding_group, finding ) - SELECT DISTINCT 6 AS check_id, - lt.database_name, - lt.object_name, - 'Types of locks by object' AS finding_group, - 'This object has had ' + - STUFF((SELECT DISTINCT N', ' + lt2.lock_count + ' ' + lt2.lock - FROM lock_types AS lt2 - WHERE lt2.database_name = lt.database_name - AND lt2.object_name = lt.object_name - FOR XML PATH(N''), TYPE).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 1, N'') - + ' locks' - FROM lock_types AS lt - OPTION ( RECOMPILE ); +The point of this is so we know if a query was gathered by one or more of the search queries +*/ - /*Check 7 gives you more info queries for sp_BlitzCache & BlitzQueryStore*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Check 7 part 1 %s', 0, 1, @d) WITH NOWAIT; - WITH deadlock_stack AS ( - SELECT DISTINCT - ds.id, - ds.proc_name, - ds.event_date, - PARSENAME(ds.proc_name, 3) AS database_name, - PARSENAME(ds.proc_name, 2) AS schema_name, - PARSENAME(ds.proc_name, 1) AS proc_only_name, - '''' + STUFF((SELECT DISTINCT N',' + ds2.sql_handle - FROM #deadlock_stack AS ds2 - WHERE ds2.id = ds.id - AND ds2.event_date = ds.event_date - FOR XML PATH(N''), TYPE).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 1, N'') + '''' AS sql_handle_csv - FROM #deadlock_stack AS ds - GROUP BY PARSENAME(ds.proc_name, 3), - PARSENAME(ds.proc_name, 2), - PARSENAME(ds.proc_name, 1), - ds.id, - ds.proc_name, - ds.event_date - ) - INSERT #deadlock_findings WITH (TABLOCKX) - ( check_id, database_name, object_name, finding_group, finding ) - SELECT DISTINCT 7 AS check_id, - ISNULL(DB_NAME(dow.database_id), 'UNKNOWN') AS database_name, - ds.proc_name AS object_name, - 'More Info - Query' AS finding_group, - 'EXEC sp_BlitzCache ' + - CASE WHEN ds.proc_name = 'adhoc' - THEN ' @OnlySqlHandles = ' + ds.sql_handle_csv - ELSE '@StoredProcName = ' + - QUOTENAME(ds.proc_only_name, '''') - END + - ';' AS finding - FROM deadlock_stack AS ds - JOIN #deadlock_owner_waiter AS dow - ON dow.owner_id = ds.id - AND dow.event_date = ds.event_date - WHERE 1 = 1 - AND (DB_NAME(dow.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (dow.event_date >= @StartDate OR @StartDate IS NULL) - AND (dow.event_date < @EndDate OR @EndDate IS NULL) - AND (dow.object_name = @StoredProcName OR @StoredProcName IS NULL) - OPTION ( RECOMPILE ); +RAISERROR(N'Updating patterns', 0, 1) WITH NOWAIT; - IF @ProductVersionMajor >= 13 - BEGIN - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Check 7 part 2 %s', 0, 1, @d) WITH NOWAIT; - WITH deadlock_stack AS ( - SELECT DISTINCT - ds.id, - ds.sql_handle, - ds.proc_name, - ds.event_date, - PARSENAME(ds.proc_name, 3) AS database_name, - PARSENAME(ds.proc_name, 2) AS schema_name, - PARSENAME(ds.proc_name, 1) AS proc_only_name - FROM #deadlock_stack AS ds - ) - INSERT #deadlock_findings WITH (TABLOCKX) - ( check_id, database_name, object_name, finding_group, finding ) - SELECT DISTINCT 7 AS check_id, - DB_NAME(dow.database_id) AS database_name, - ds.proc_name AS object_name, - 'More Info - Query' AS finding_group, - 'EXEC sp_BlitzQueryStore ' - + '@DatabaseName = ' - + QUOTENAME(ds.database_name, '''') - + ', ' - + '@StoredProcName = ' - + QUOTENAME(ds.proc_only_name, '''') - + ';' AS finding - FROM deadlock_stack AS ds - JOIN #deadlock_owner_waiter AS dow - ON dow.owner_id = ds.id - AND dow.event_date = ds.event_date - WHERE ds.proc_name <> 'adhoc' - AND (DB_NAME(dow.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (dow.event_date >= @StartDate OR @StartDate IS NULL) - AND (dow.event_date < @EndDate OR @EndDate IS NULL) - AND (dow.object_name = @StoredProcName OR @StoredProcName IS NULL) - OPTION ( RECOMPILE ); - END; - +WITH patterns AS ( +SELECT wp.plan_id, wp.query_id, + pattern_path = STUFF((SELECT DISTINCT N', ' + wp2.pattern + FROM #working_plans AS wp2 + WHERE wp.plan_id = wp2.plan_id + AND wp.query_id = wp2.query_id + FOR XML PATH(N''), TYPE).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 2, N'') +FROM #working_plans AS wp +) +UPDATE wp +SET wp.pattern = patterns.pattern_path +FROM #working_plans AS wp +JOIN patterns +ON wp.plan_id = patterns.plan_id +AND wp.query_id = patterns.query_id +OPTION (RECOMPILE); - /*Check 8 gives you stored proc deadlock counts*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Check 8 %s', 0, 1, @d) WITH NOWAIT; - INSERT #deadlock_findings WITH (TABLOCKX) - ( check_id, database_name, object_name, finding_group, finding ) - SELECT 8 AS check_id, - DB_NAME(dp.database_id) AS database_name, - ds.proc_name, - 'Stored Procedure Deadlocks', - 'The stored procedure ' - + PARSENAME(ds.proc_name, 2) - + '.' - + PARSENAME(ds.proc_name, 1) - + ' has been involved in ' - + CONVERT(NVARCHAR(10), COUNT_BIG(DISTINCT ds.id)) - + ' deadlocks.' - FROM #deadlock_stack AS ds - JOIN #deadlock_process AS dp - ON dp.id = ds.id - AND ds.event_date = dp.event_date - WHERE ds.proc_name <> 'adhoc' - AND (ds.proc_name = @StoredProcName OR @StoredProcName IS NULL) - AND (DB_NAME(dp.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (dp.event_date >= @StartDate OR @StartDate IS NULL) - AND (dp.event_date < @EndDate OR @EndDate IS NULL) - AND (dp.client_app = @AppName OR @AppName IS NULL) - AND (dp.host_name = @HostName OR @HostName IS NULL) - AND (dp.login_name = @LoginName OR @LoginName IS NULL) - GROUP BY DB_NAME(dp.database_id), ds.proc_name - OPTION(RECOMPILE); +/* +This dedupes our results so we hopefully don't double-work the same plan +*/ - /*Check 9 gives you more info queries for sp_BlitzIndex */ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Check 9 %s', 0, 1, @d) WITH NOWAIT; - WITH bi AS ( - SELECT DISTINCT - dow.object_name, - DB_NAME(dow.database_id) as database_name, - a.schema_name AS schema_name, - a.table_name AS table_name - FROM #deadlock_owner_waiter AS dow - LEFT JOIN @sysAssObjId a ON a.database_id=dow.database_id AND a.partition_id = dow.associatedObjectId - WHERE 1 = 1 - AND (DB_NAME(dow.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (dow.event_date >= @StartDate OR @StartDate IS NULL) - AND (dow.event_date < @EndDate OR @EndDate IS NULL) - AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) - AND dow.object_name IS NOT NULL - ) - INSERT #deadlock_findings WITH (TABLOCKX) - ( check_id, database_name, object_name, finding_group, finding ) - SELECT 9 AS check_id, - bi.database_name, - bi.object_name, - 'More Info - Table' AS finding_group, - 'EXEC sp_BlitzIndex ' + - '@DatabaseName = ' + QUOTENAME(bi.database_name, '''') + - ', @SchemaName = ' + QUOTENAME(bi.schema_name, '''') + - ', @TableName = ' + QUOTENAME(bi.table_name, '''') + - ';' AS finding - FROM bi - OPTION ( RECOMPILE ); +RAISERROR(N'Deduplicating gathered plans', 0, 1) WITH NOWAIT; - /*Check 10 gets total deadlock wait time per object*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Check 10 %s', 0, 1, @d) WITH NOWAIT; - WITH chopsuey AS ( - SELECT DISTINCT - PARSENAME(dow.object_name, 3) AS database_name, - dow.object_name, - CONVERT(VARCHAR(10), (SUM(DISTINCT dp.wait_time) / 1000) / 86400) AS wait_days, - CONVERT(VARCHAR(20), DATEADD(SECOND, (SUM(DISTINCT dp.wait_time) / 1000), 0), 108) AS wait_time_hms - FROM #deadlock_owner_waiter AS dow - JOIN #deadlock_process AS dp - ON (dp.id = dow.owner_id OR dp.victim_id = dow.waiter_id) - AND dp.event_date = dow.event_date - WHERE 1 = 1 - AND (DB_NAME(dow.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (dow.event_date >= @StartDate OR @StartDate IS NULL) - AND (dow.event_date < @EndDate OR @EndDate IS NULL) - AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) - AND (dp.client_app = @AppName OR @AppName IS NULL) - AND (dp.host_name = @HostName OR @HostName IS NULL) - AND (dp.login_name = @LoginName OR @LoginName IS NULL) - GROUP BY PARSENAME(dow.object_name, 3), dow.object_name - ) - INSERT #deadlock_findings WITH (TABLOCKX) - ( check_id, database_name, object_name, finding_group, finding ) - SELECT 10 AS check_id, - cs.database_name, - cs.object_name, - 'Total object deadlock wait time' AS finding_group, - 'This object has had ' - + CONVERT(VARCHAR(10), cs.wait_days) - + ':' + CONVERT(VARCHAR(20), cs.wait_time_hms, 108) - + ' [d/h/m/s] of deadlock wait time.' AS finding - FROM chopsuey AS cs - WHERE cs.object_name IS NOT NULL - OPTION ( RECOMPILE ); +WITH dedupe AS ( +SELECT * , ROW_NUMBER() OVER (PARTITION BY wp.plan_id ORDER BY wp.plan_id) AS dupes +FROM #working_plans AS wp +) +DELETE dedupe +WHERE dedupe.dupes > 1 +OPTION (RECOMPILE); - /*Check 11 gets total deadlock wait time per database*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Check 11 %s', 0, 1, @d) WITH NOWAIT; - WITH wait_time AS ( - SELECT DB_NAME(dp.database_id) AS database_name, - SUM(CONVERT(BIGINT, dp.wait_time)) AS total_wait_time_ms - FROM #deadlock_process AS dp - WHERE 1 = 1 - AND (DB_NAME(dp.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (dp.event_date >= @StartDate OR @StartDate IS NULL) - AND (dp.event_date < @EndDate OR @EndDate IS NULL) - AND (dp.client_app = @AppName OR @AppName IS NULL) - AND (dp.host_name = @HostName OR @HostName IS NULL) - AND (dp.login_name = @LoginName OR @LoginName IS NULL) - GROUP BY DB_NAME(dp.database_id) - ) - INSERT #deadlock_findings WITH (TABLOCKX) - ( check_id, database_name, object_name, finding_group, finding ) - SELECT 11 AS check_id, - wt.database_name, - '-' AS object_name, - 'Total database deadlock wait time' AS finding_group, - 'This database has had ' - + CONVERT(VARCHAR(10), (SUM(DISTINCT wt.total_wait_time_ms) / 1000) / 86400) - + ':' + CONVERT(VARCHAR(20), DATEADD(SECOND, (SUM(DISTINCT wt.total_wait_time_ms) / 1000), 0), 108) - + ' [d/h/m/s] of deadlock wait time.' - FROM wait_time AS wt - GROUP BY wt.database_name - OPTION ( RECOMPILE ); +SET @msg = N'Removed ' + CONVERT(NVARCHAR(10), @@ROWCOUNT) + N' duplicate plan_ids.'; +RAISERROR(@msg, 0, 1) WITH NOWAIT; + + +/* +This gathers data for the #working_metrics table +*/ + + +RAISERROR(N'Collecting worker metrics', 0, 1) WITH NOWAIT; + +SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; +SET @sql_select += N' +SELECT ' + QUOTENAME(@DatabaseName, '''') + N' AS database_name, wp.plan_id, wp.query_id, + QUOTENAME(object_schema_name(qsq.object_id, DB_ID(' + QUOTENAME(@DatabaseName, '''') + N'))) + ''.'' + + QUOTENAME(object_name(qsq.object_id, DB_ID(' + QUOTENAME(@DatabaseName, '''') + N'))) AS proc_or_function_name, + qsq.batch_sql_handle, qsq.query_hash, qsq.query_parameterization_type_desc, qsq.count_compiles, + (qsq.avg_compile_duration / 1000.), + (qsq.last_compile_duration / 1000.), + (qsq.avg_bind_duration / 1000.), + (qsq.last_bind_duration / 1000.), + (qsq.avg_bind_cpu_time / 1000.), + (qsq.last_bind_cpu_time / 1000.), + (qsq.avg_optimize_duration / 1000.), + (qsq.last_optimize_duration / 1000.), + (qsq.avg_optimize_cpu_time / 1000.), + (qsq.last_optimize_cpu_time / 1000.), + (qsq.avg_compile_memory_kb / 1024.), + (qsq.last_compile_memory_kb / 1024.), + qsrs.execution_type_desc, qsrs.first_execution_time, qsrs.last_execution_time, qsrs.count_executions, + (qsrs.avg_duration / 1000.), + (qsrs.last_duration / 1000.), + (qsrs.min_duration / 1000.), + (qsrs.max_duration / 1000.), + (qsrs.avg_cpu_time / 1000.), + (qsrs.last_cpu_time / 1000.), + (qsrs.min_cpu_time / 1000.), + (qsrs.max_cpu_time / 1000.), + ((qsrs.avg_logical_io_reads * 8 ) / 1024.), + ((qsrs.last_logical_io_reads * 8 ) / 1024.), + ((qsrs.min_logical_io_reads * 8 ) / 1024.), + ((qsrs.max_logical_io_reads * 8 ) / 1024.), + ((qsrs.avg_logical_io_writes * 8 ) / 1024.), + ((qsrs.last_logical_io_writes * 8 ) / 1024.), + ((qsrs.min_logical_io_writes * 8 ) / 1024.), + ((qsrs.max_logical_io_writes * 8 ) / 1024.), + ((qsrs.avg_physical_io_reads * 8 ) / 1024.), + ((qsrs.last_physical_io_reads * 8 ) / 1024.), + ((qsrs.min_physical_io_reads * 8 ) / 1024.), + ((qsrs.max_physical_io_reads * 8 ) / 1024.), + (qsrs.avg_clr_time / 1000.), + (qsrs.last_clr_time / 1000.), + (qsrs.min_clr_time / 1000.), + (qsrs.max_clr_time / 1000.), + qsrs.avg_dop, qsrs.last_dop, qsrs.min_dop, qsrs.max_dop, + ((qsrs.avg_query_max_used_memory * 8 ) / 1024.), + ((qsrs.last_query_max_used_memory * 8 ) / 1024.), + ((qsrs.min_query_max_used_memory * 8 ) / 1024.), + ((qsrs.max_query_max_used_memory * 8 ) / 1024.), + qsrs.avg_rowcount, qsrs.last_rowcount, qsrs.min_rowcount, qsrs.max_rowcount,'; + + IF @new_columns = 1 + BEGIN + SET @sql_select += N' + qsrs.avg_num_physical_io_reads, qsrs.last_num_physical_io_reads, qsrs.min_num_physical_io_reads, qsrs.max_num_physical_io_reads, + (qsrs.avg_log_bytes_used / 100000000), + (qsrs.last_log_bytes_used / 100000000), + (qsrs.min_log_bytes_used / 100000000), + (qsrs.max_log_bytes_used / 100000000), + ((qsrs.avg_tempdb_space_used * 8 ) / 1024.), + ((qsrs.last_tempdb_space_used * 8 ) / 1024.), + ((qsrs.min_tempdb_space_used * 8 ) / 1024.), + ((qsrs.max_tempdb_space_used * 8 ) / 1024.) + '; + END; + IF @new_columns = 0 + BEGIN + SET @sql_select += N' + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL + '; + END; +SET @sql_select += +N'FROM #working_plans AS wp +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq +ON wp.query_id = qsq.query_id +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs +ON qsrs.plan_id = wp.plan_id +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp +ON qsp.plan_id = wp.plan_id +AND qsp.query_id = wp.query_id +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt +ON qsqt.query_text_id = qsq.query_text_id +WHERE 1 = 1 + AND qsq.is_internal_query = 0 + AND qsp.query_plan IS NOT NULL + '; - /*Check 12 gets total deadlock wait time for SQL Agent*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Check 12 %s', 0, 1, @d) WITH NOWAIT; - INSERT #deadlock_findings WITH (TABLOCKX) - ( check_id, database_name, object_name, finding_group, finding ) - SELECT 12, - DB_NAME(aj.database_id), - 'SQLAgent - Job: ' - + aj.job_name - + ' Step: ' - + aj.step_name, - 'Agent Job Deadlocks', - RTRIM(COUNT(*)) + ' deadlocks from this Agent Job and Step' - FROM #agent_job AS aj - GROUP BY DB_NAME(aj.database_id), aj.job_name, aj.step_name - OPTION ( RECOMPILE ); +SET @sql_select += @sql_where; - /*Check 13 is total parallel deadlocks*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Check 13 %s', 0, 1, @d) WITH NOWAIT; - INSERT #deadlock_findings WITH (TABLOCKX) - ( check_id, database_name, object_name, finding_group, finding ) - SELECT 13 AS check_id, - N'-' AS database_name, - '-' AS object_name, - 'Total parallel deadlocks' AS finding_group, - 'There have been ' - + CONVERT(NVARCHAR(20), COUNT_BIG(DISTINCT drp.event_date)) - + ' parallel deadlocks.' - FROM #deadlock_resource_parallel AS drp - WHERE 1 = 1 - HAVING COUNT_BIG(DISTINCT drp.event_date) > 0 - OPTION ( RECOMPILE ); +SET @sql_select += N'OPTION (RECOMPILE); + '; - /*Thank you goodnight*/ - INSERT #deadlock_findings WITH (TABLOCKX) - ( check_id, database_name, object_name, finding_group, finding ) - VALUES ( -1, - N'sp_BlitzLock ' + CAST(CONVERT(DATETIME, @VersionDate, 102) AS VARCHAR(100)), - N'SQL Server First Responder Kit', - N'http://FirstResponderKit.org/', - N'To get help or add your own contributions, join us at http://FirstResponderKit.org.'); +IF @Debug = 1 + PRINT @sql_select; - +IF @sql_select IS NULL + BEGIN + RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; + RETURN; + END; +INSERT #working_metrics WITH (TABLOCK) + ( database_name, plan_id, query_id, + proc_or_function_name, + batch_sql_handle, query_hash, query_parameterization_type_desc, count_compiles, + avg_compile_duration, last_compile_duration, avg_bind_duration, last_bind_duration, avg_bind_cpu_time, last_bind_cpu_time, avg_optimize_duration, + last_optimize_duration, avg_optimize_cpu_time, last_optimize_cpu_time, avg_compile_memory_kb, last_compile_memory_kb, execution_type_desc, + first_execution_time, last_execution_time, count_executions, avg_duration, last_duration, min_duration, max_duration, avg_cpu_time, last_cpu_time, + min_cpu_time, max_cpu_time, avg_logical_io_reads, last_logical_io_reads, min_logical_io_reads, max_logical_io_reads, avg_logical_io_writes, + last_logical_io_writes, min_logical_io_writes, max_logical_io_writes, avg_physical_io_reads, last_physical_io_reads, min_physical_io_reads, + max_physical_io_reads, avg_clr_time, last_clr_time, min_clr_time, max_clr_time, avg_dop, last_dop, min_dop, max_dop, avg_query_max_used_memory, + last_query_max_used_memory, min_query_max_used_memory, max_query_max_used_memory, avg_rowcount, last_rowcount, min_rowcount, max_rowcount, + /* 2017 only columns */ + avg_num_physical_io_reads, last_num_physical_io_reads, min_num_physical_io_reads, max_num_physical_io_reads, + avg_log_bytes_used, last_log_bytes_used, min_log_bytes_used, max_log_bytes_used, + avg_tempdb_space_used, last_tempdb_space_used, min_tempdb_space_used, max_tempdb_space_used ) - /*Results*/ - /*Break in case of emergency*/ - --CREATE CLUSTERED INDEX cx_whatever ON #deadlock_process (event_date, id); - --CREATE CLUSTERED INDEX cx_whatever ON #deadlock_resource_parallel (event_date, owner_id); - --CREATE CLUSTERED INDEX cx_whatever ON #deadlock_owner_waiter (event_date, owner_id, waiter_id); - IF(@OutputDatabaseCheck = 0) - BEGIN - - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Results 1 %s', 0, 1, @d) WITH NOWAIT; - WITH deadlocks - AS ( SELECT N'Regular Deadlock' AS deadlock_type, - dp.event_date, - dp.id, - dp.victim_id, - dp.database_id, - dp.priority, - dp.log_used, - dp.wait_resource COLLATE DATABASE_DEFAULT AS wait_resource, - CONVERT( - XML, - STUFF(( SELECT DISTINCT NCHAR(10) - + N' ' - + ISNULL(c.object_name, N'') - + N' ' COLLATE DATABASE_DEFAULT AS object_name - FROM #deadlock_owner_waiter AS c - WHERE ( dp.id = c.owner_id - OR dp.victim_id = c.waiter_id ) - AND CONVERT(DATE, dp.event_date) = CONVERT(DATE, c.event_date) - FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(4000)'), - 1, 1, N'')) AS object_names, - dp.wait_time, - dp.transaction_name, - dp.last_tran_started, - dp.last_batch_started, - dp.last_batch_completed, - dp.lock_mode, - dp.transaction_count, - dp.client_app, - dp.host_name, - dp.login_name, - dp.isolation_level, - dp.process_xml.value('(//process/inputbuf/text())[1]', 'NVARCHAR(MAX)') AS inputbuf, - ROW_NUMBER() OVER ( PARTITION BY dp.event_date, dp.id ORDER BY dp.event_date ) AS dn, - DENSE_RANK() OVER ( ORDER BY dp.event_date ) AS en, - ROW_NUMBER() OVER ( PARTITION BY dp.event_date ORDER BY dp.event_date ) -1 AS qn, - dp.is_victim, - ISNULL(dp.owner_mode, '-') AS owner_mode, - NULL AS owner_waiter_type, - NULL AS owner_activity, - NULL AS owner_waiter_activity, - NULL AS owner_merging, - NULL AS owner_spilling, - NULL AS owner_waiting_to_close, - ISNULL(dp.waiter_mode, '-') AS waiter_mode, - NULL AS waiter_waiter_type, - NULL AS waiter_owner_activity, - NULL AS waiter_waiter_activity, - NULL AS waiter_merging, - NULL AS waiter_spilling, - NULL AS waiter_waiting_to_close, - dp.deadlock_graph - FROM #deadlock_process AS dp - WHERE dp.victim_id IS NOT NULL - AND dp.is_parallel = 0 - - UNION ALL - - SELECT N'Parallel Deadlock' AS deadlock_type, - dp.event_date, - dp.id, - dp.victim_id, - dp.database_id, - dp.priority, - dp.log_used, - dp.wait_resource COLLATE DATABASE_DEFAULT, - CASE WHEN @ExportToExcel = 0 THEN - CONVERT( - XML, - STUFF(( SELECT DISTINCT NCHAR(10) - + N' ' - + ISNULL(c.object_name, N'') - + N' ' COLLATE DATABASE_DEFAULT AS object_name - FROM #deadlock_owner_waiter AS c - WHERE ( dp.id = c.owner_id - OR dp.victim_id = c.waiter_id ) - AND CONVERT(DATE, dp.event_date) = CONVERT(DATE, c.event_date) - FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(4000)'), - 1, 1, N'')) - ELSE NULL END AS object_names, - dp.wait_time, - dp.transaction_name, - dp.last_tran_started, - dp.last_batch_started, - dp.last_batch_completed, - dp.lock_mode, - dp.transaction_count, - dp.client_app, - dp.host_name, - dp.login_name, - dp.isolation_level, - dp.process_xml.value('(//process/inputbuf/text())[1]', 'NVARCHAR(MAX)') AS inputbuf, - ROW_NUMBER() OVER ( PARTITION BY dp.event_date, dp.id ORDER BY dp.event_date ) AS dn, - DENSE_RANK() OVER ( ORDER BY dp.event_date ) AS en, - ROW_NUMBER() OVER ( PARTITION BY dp.event_date ORDER BY dp.event_date ) -1 AS qn, - 1 AS is_victim, - cao.wait_type COLLATE DATABASE_DEFAULT AS owner_mode, - cao.waiter_type AS owner_waiter_type, - cao.owner_activity AS owner_activity, - cao.waiter_activity AS owner_waiter_activity, - cao.merging AS owner_merging, - cao.spilling AS owner_spilling, - cao.waiting_to_close AS owner_waiting_to_close, - caw.wait_type COLLATE DATABASE_DEFAULT AS waiter_mode, - caw.waiter_type AS waiter_waiter_type, - caw.owner_activity AS waiter_owner_activity, - caw.waiter_activity AS waiter_waiter_activity, - caw.merging AS waiter_merging, - caw.spilling AS waiter_spilling, - caw.waiting_to_close AS waiter_waiting_to_close, - dp.deadlock_graph - FROM #deadlock_process AS dp - CROSS APPLY (SELECT TOP 1 * FROM #deadlock_resource_parallel AS drp WHERE drp.owner_id = dp.id AND drp.wait_type = 'e_waitPipeNewRow' ORDER BY drp.event_date) AS cao - CROSS APPLY (SELECT TOP 1 * FROM #deadlock_resource_parallel AS drp WHERE drp.owner_id = dp.id AND drp.wait_type = 'e_waitPipeGetRow' ORDER BY drp.event_date) AS caw - WHERE dp.is_parallel = 1 ) - insert into DeadLockTbl ( - ServerName, - deadlock_type, - event_date, - database_name, - deadlock_group, - query, - object_names, - isolation_level, - owner_mode, - waiter_mode, - transaction_count, - login_name, - host_name, - client_app, - wait_time, - priority, - log_used, - last_tran_started, - last_batch_started, - last_batch_completed, - transaction_name, - owner_waiter_type, - owner_activity, - owner_waiter_activity, - owner_merging, - owner_spilling, - owner_waiting_to_close, - waiter_waiter_type, - waiter_owner_activity, - waiter_waiter_activity, - waiter_merging, - waiter_spilling, - waiter_waiting_to_close, - deadlock_graph - ) - SELECT @ServerName, - d.deadlock_type, - d.event_date, - DB_NAME(d.database_id) AS database_name, - 'Deadlock #' - + CONVERT(NVARCHAR(10), d.en) - + ', Query #' - + CASE WHEN d.qn = 0 THEN N'1' ELSE CONVERT(NVARCHAR(10), d.qn) END - + CASE WHEN d.is_victim = 1 THEN ' - VICTIM' ELSE '' END - AS deadlock_group, - CONVERT(XML, N'') AS query, - d.object_names, - d.isolation_level, - d.owner_mode, - d.waiter_mode, - d.transaction_count, - d.login_name, - d.host_name, - d.client_app, - d.wait_time, - d.priority, - d.log_used, - d.last_tran_started, - d.last_batch_started, - d.last_batch_completed, - d.transaction_name, - /*These columns will be NULL for regular (non-parallel) deadlocks*/ - d.owner_waiter_type, - d.owner_activity, - d.owner_waiter_activity, - d.owner_merging, - d.owner_spilling, - d.owner_waiting_to_close, - d.waiter_waiter_type, - d.waiter_owner_activity, - d.waiter_waiter_activity, - d.waiter_merging, - d.waiter_spilling, - d.waiter_waiting_to_close, - d.deadlock_graph - FROM deadlocks AS d - WHERE d.dn = 1 - AND (is_victim = @VictimsOnly OR @VictimsOnly = 0) - AND d.en < CASE WHEN d.deadlock_type = N'Parallel Deadlock' THEN 2 ELSE 2147483647 END - AND (DB_NAME(d.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (d.event_date >= @StartDate OR @StartDate IS NULL) - AND (d.event_date < @EndDate OR @EndDate IS NULL) - AND (CONVERT(NVARCHAR(MAX), d.object_names) LIKE '%' + @ObjectName + '%' OR @ObjectName IS NULL) - AND (d.client_app = @AppName OR @AppName IS NULL) - AND (d.host_name = @HostName OR @HostName IS NULL) - AND (d.login_name = @LoginName OR @LoginName IS NULL) - ORDER BY d.event_date, is_victim DESC - OPTION ( RECOMPILE ); - - drop SYNONYM DeadLockTbl; --done insert into blitzlock table going to insert into findings table first create synonym. +EXEC sys.sp_executesql @stmt = @sql_select, + @params = @sp_params, + @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; - -- RAISERROR('att deadlock findings', 0, 1) WITH NOWAIT; - - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Findings %s', 0, 1, @d) WITH NOWAIT; - Insert into DeadlockFindings (ServerName,check_id,database_name,object_name,finding_group,finding) - SELECT @ServerName,df.check_id, df.database_name, df.object_name, df.finding_group, df.finding - FROM #deadlock_findings AS df - ORDER BY df.check_id - OPTION ( RECOMPILE ); +/*This just helps us classify our queries*/ +UPDATE #working_metrics +SET proc_or_function_name = N'Statement' +WHERE proc_or_function_name IS NULL +OPTION(RECOMPILE); - drop SYNONYM DeadlockFindings; --done with inserting. -END -ELSE --Output to database is not set output to client app - BEGIN - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Results 1 %s', 0, 1, @d) WITH NOWAIT; - WITH deadlocks - AS ( SELECT N'Regular Deadlock' AS deadlock_type, - dp.event_date, - dp.id, - dp.victim_id, - dp.database_id, - dp.priority, - dp.log_used, - dp.wait_resource COLLATE DATABASE_DEFAULT AS wait_resource, - CASE WHEN @ExportToExcel = 0 THEN - CONVERT( - XML, - STUFF(( SELECT DISTINCT NCHAR(10) - + N' ' - + ISNULL(c.object_name, N'') - + N' ' COLLATE DATABASE_DEFAULT AS object_name - FROM #deadlock_owner_waiter AS c - WHERE ( dp.id = c.owner_id - OR dp.victim_id = c.waiter_id ) - AND CONVERT(DATE, dp.event_date) = CONVERT(DATE, c.event_date) - FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(4000)'), - 1, 1, N'')) - ELSE NULL END AS object_names, - dp.wait_time, - dp.transaction_name, - dp.last_tran_started, - dp.last_batch_started, - dp.last_batch_completed, - dp.lock_mode, - dp.transaction_count, - dp.client_app, - dp.host_name, - dp.login_name, - dp.isolation_level, - dp.process_xml.value('(//process/inputbuf/text())[1]', 'NVARCHAR(MAX)') AS inputbuf, - ROW_NUMBER() OVER ( PARTITION BY dp.event_date, dp.id ORDER BY dp.event_date ) AS dn, - DENSE_RANK() OVER ( ORDER BY dp.event_date ) AS en, - ROW_NUMBER() OVER ( PARTITION BY dp.event_date ORDER BY dp.event_date ) -1 AS qn, - dp.is_victim, - ISNULL(dp.owner_mode, '-') AS owner_mode, - NULL AS owner_waiter_type, - NULL AS owner_activity, - NULL AS owner_waiter_activity, - NULL AS owner_merging, - NULL AS owner_spilling, - NULL AS owner_waiting_to_close, - ISNULL(dp.waiter_mode, '-') AS waiter_mode, - NULL AS waiter_waiter_type, - NULL AS waiter_owner_activity, - NULL AS waiter_waiter_activity, - NULL AS waiter_merging, - NULL AS waiter_spilling, - NULL AS waiter_waiting_to_close, - dp.deadlock_graph - FROM #deadlock_process AS dp - WHERE dp.victim_id IS NOT NULL - AND dp.is_parallel = 0 +SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; +SET @sql_select += N' + WITH patterns AS ( + SELECT query_id, planid_path = STUFF((SELECT DISTINCT N'', '' + RTRIM(qsp2.plan_id) + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp2 + WHERE qsp.query_id = qsp2.query_id + FOR XML PATH(N''''), TYPE).value(N''.[1]'', N''NVARCHAR(MAX)''), 1, 2, N'''') + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp + ) + UPDATE wm + SET wm.query_id_all_plan_ids = patterns.planid_path + FROM #working_metrics AS wm + JOIN patterns + ON wm.query_id = patterns.query_id + OPTION (RECOMPILE); +' - UNION ALL - - SELECT N'Parallel Deadlock' AS deadlock_type, - dp.event_date, - dp.id, - dp.victim_id, - dp.database_id, - dp.priority, - dp.log_used, - dp.wait_resource COLLATE DATABASE_DEFAULT, - CASE WHEN @ExportToExcel = 0 THEN - CONVERT( - XML, - STUFF(( SELECT DISTINCT NCHAR(10) - + N' ' - + ISNULL(c.object_name, N'') - + N' ' COLLATE DATABASE_DEFAULT AS object_name - FROM #deadlock_owner_waiter AS c - WHERE ( dp.id = c.owner_id - OR dp.victim_id = c.waiter_id ) - AND CONVERT(DATE, dp.event_date) = CONVERT(DATE, c.event_date) - FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(4000)'), - 1, 1, N'')) - ELSE NULL END AS object_names, - dp.wait_time, - dp.transaction_name, - dp.last_tran_started, - dp.last_batch_started, - dp.last_batch_completed, - dp.lock_mode, - dp.transaction_count, - dp.client_app, - dp.host_name, - dp.login_name, - dp.isolation_level, - dp.process_xml.value('(//process/inputbuf/text())[1]', 'NVARCHAR(MAX)') AS inputbuf, - ROW_NUMBER() OVER ( PARTITION BY dp.event_date, dp.id ORDER BY dp.event_date ) AS dn, - DENSE_RANK() OVER ( ORDER BY dp.event_date ) AS en, - ROW_NUMBER() OVER ( PARTITION BY dp.event_date ORDER BY dp.event_date ) -1 AS qn, - 1 AS is_victim, - cao.wait_type COLLATE DATABASE_DEFAULT AS owner_mode, - cao.waiter_type AS owner_waiter_type, - cao.owner_activity AS owner_activity, - cao.waiter_activity AS owner_waiter_activity, - cao.merging AS owner_merging, - cao.spilling AS owner_spilling, - cao.waiting_to_close AS owner_waiting_to_close, - caw.wait_type COLLATE DATABASE_DEFAULT AS waiter_mode, - caw.waiter_type AS waiter_waiter_type, - caw.owner_activity AS waiter_owner_activity, - caw.waiter_activity AS waiter_waiter_activity, - caw.merging AS waiter_merging, - caw.spilling AS waiter_spilling, - caw.waiting_to_close AS waiter_waiting_to_close, - dp.deadlock_graph - FROM #deadlock_process AS dp - OUTER APPLY (SELECT TOP 1 * FROM #deadlock_resource_parallel AS drp WHERE drp.owner_id = dp.id AND drp.wait_type IN ('e_waitPortOpen', 'e_waitPipeNewRow') ORDER BY drp.event_date) AS cao - OUTER APPLY (SELECT TOP 1 * FROM #deadlock_resource_parallel AS drp WHERE drp.owner_id = dp.id AND drp.wait_type IN ('e_waitPortOpen', 'e_waitPipeGetRow') ORDER BY drp.event_date) AS caw - WHERE dp.is_parallel = 1 - ) - SELECT d.deadlock_type, - d.event_date, - DB_NAME(d.database_id) AS database_name, - 'Deadlock #' - + CONVERT(NVARCHAR(10), d.en) - + ', Query #' - + CASE WHEN d.qn = 0 THEN N'1' ELSE CONVERT(NVARCHAR(10), d.qn) END - + CASE WHEN d.is_victim = 1 THEN ' - VICTIM' ELSE '' END - AS deadlock_group, - CASE WHEN @ExportToExcel = 0 THEN CONVERT(XML, N'') - ELSE SUBSTRING(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(LTRIM(RTRIM(d.inputbuf)),' ','<>'),'><',''),NCHAR(10), ' '),NCHAR(13), ' '),'<>',' '), 1, 32000) END AS query, - d.object_names, - d.isolation_level, - d.owner_mode, - d.waiter_mode, - d.transaction_count, - d.login_name, - d.host_name, - d.client_app, - d.wait_time, - d.priority, - d.log_used, - d.last_tran_started, - d.last_batch_started, - d.last_batch_completed, - d.transaction_name, - /*These columns will be NULL for regular (non-parallel) deadlocks*/ - d.owner_waiter_type, - d.owner_activity, - d.owner_waiter_activity, - d.owner_merging, - d.owner_spilling, - d.owner_waiting_to_close, - d.waiter_waiter_type, - d.waiter_owner_activity, - d.waiter_waiter_activity, - d.waiter_merging, - d.waiter_spilling, - d.waiter_waiting_to_close, - CASE WHEN @ExportToExcel = 0 THEN d.deadlock_graph ELSE NULL END AS deadlock_graph - FROM deadlocks AS d - WHERE d.dn = 1 - AND (is_victim = @VictimsOnly OR @VictimsOnly = 0) - AND d.en < CASE WHEN d.deadlock_type = N'Parallel Deadlock' THEN 2 ELSE 2147483647 END - AND (DB_NAME(d.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (d.event_date >= @StartDate OR @StartDate IS NULL) - AND (d.event_date < @EndDate OR @EndDate IS NULL) - AND (CONVERT(NVARCHAR(MAX), d.object_names) LIKE '%' + @ObjectName + '%' OR @ObjectName IS NULL) - AND (d.client_app = @AppName OR @AppName IS NULL) - AND (d.host_name = @HostName OR @HostName IS NULL) - AND (d.login_name = @LoginName OR @LoginName IS NULL) - ORDER BY d.event_date, is_victim DESC - OPTION ( RECOMPILE ); - - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Findings %s', 0, 1, @d) WITH NOWAIT; - SELECT df.check_id, df.database_name, df.object_name, df.finding_group, df.finding - FROM #deadlock_findings AS df - ORDER BY df.check_id - OPTION ( RECOMPILE ); +EXEC sys.sp_executesql @stmt = @sql_select; + +/* +This gathers data for the #working_plan_text table +*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Done %s', 0, 1, @d) WITH NOWAIT; - END --done with output to client app. +RAISERROR(N'Gathering working plans', 0, 1) WITH NOWAIT; +SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; +SET @sql_select += N' +SELECT ' + QUOTENAME(@DatabaseName, '''') + N' AS database_name, wp.plan_id, wp.query_id, + qsp.plan_group_id, qsp.engine_version, qsp.compatibility_level, qsp.query_plan_hash, TRY_CONVERT(XML, qsp.query_plan), qsp.is_online_index_plan, qsp.is_trivial_plan, + qsp.is_parallel_plan, qsp.is_forced_plan, qsp.is_natively_compiled, qsp.force_failure_count, qsp.last_force_failure_reason_desc, qsp.count_compiles, + qsp.initial_compile_start_time, qsp.last_compile_start_time, qsp.last_execution_time, + (qsp.avg_compile_duration / 1000.), + (qsp.last_compile_duration / 1000.), + qsqt.query_sql_text, qsqt.statement_sql_handle, qsqt.is_part_of_encrypted_module, qsqt.has_restricted_text +FROM #working_plans AS wp +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp +ON qsp.plan_id = wp.plan_id + AND qsp.query_id = wp.query_id +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq +ON wp.query_id = qsq.query_id +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt +ON qsqt.query_text_id = qsq.query_text_id +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs +ON qsrs.plan_id = wp.plan_id +WHERE 1 = 1 + AND qsq.is_internal_query = 0 + AND qsp.query_plan IS NOT NULL + '; - IF @Debug = 1 - BEGIN +SET @sql_select += @sql_where; - SELECT '#deadlock_data' AS table_name, * - FROM #deadlock_data AS dd - OPTION ( RECOMPILE ); +SET @sql_select += N'OPTION (RECOMPILE); + '; - SELECT '#deadlock_resource' AS table_name, * - FROM #deadlock_resource AS dr - OPTION ( RECOMPILE ); +IF @Debug = 1 + PRINT @sql_select; - SELECT '#deadlock_resource_parallel' AS table_name, * - FROM #deadlock_resource_parallel AS drp - OPTION ( RECOMPILE ); +IF @sql_select IS NULL + BEGIN + RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; + RETURN; + END; - SELECT '#deadlock_owner_waiter' AS table_name, * - FROM #deadlock_owner_waiter AS dow - OPTION ( RECOMPILE ); +INSERT #working_plan_text WITH (TABLOCK) + ( database_name, plan_id, query_id, + plan_group_id, engine_version, compatibility_level, query_plan_hash, query_plan_xml, is_online_index_plan, is_trivial_plan, + is_parallel_plan, is_forced_plan, is_natively_compiled, force_failure_count, last_force_failure_reason_desc, count_compiles, + initial_compile_start_time, last_compile_start_time, last_execution_time, avg_compile_duration, last_compile_duration, + query_sql_text, statement_sql_handle, is_part_of_encrypted_module, has_restricted_text ) - SELECT '#deadlock_process' AS table_name, * - FROM #deadlock_process AS dp - OPTION ( RECOMPILE ); +EXEC sys.sp_executesql @stmt = @sql_select, + @params = @sp_params, + @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; - SELECT '#deadlock_stack' AS table_name, * - FROM #deadlock_stack AS ds - OPTION ( RECOMPILE ); - - END; -- End debug - END; --Final End -GO -SET ANSI_NULLS ON; -SET ANSI_PADDING ON; -SET ANSI_WARNINGS ON; -SET ARITHABORT ON; -SET CONCAT_NULL_YIELDS_NULL ON; -SET QUOTED_IDENTIFIER ON; -SET STATISTICS IO OFF; -SET STATISTICS TIME OFF; -GO +/* +This gets us context settings for our queries and adds it to the #working_plan_text table +*/ -DECLARE @msg NVARCHAR(MAX) = N''; +RAISERROR(N'Gathering context settings', 0, 1) WITH NOWAIT; - -- Must be a compatible, on-prem version of SQL (2016+) -IF ( (SELECT CONVERT(NVARCHAR(128), SERVERPROPERTY ('EDITION'))) <> 'SQL Azure' - AND (SELECT PARSENAME(CONVERT(NVARCHAR(128), SERVERPROPERTY ('PRODUCTVERSION')), 4)) < 13 - ) - -- or Azure Database (not Azure Data Warehouse), running at database compat level 130+ -OR ( (SELECT CONVERT(NVARCHAR(128), SERVERPROPERTY ('EDITION'))) = 'SQL Azure' - AND (SELECT SERVERPROPERTY ('ENGINEEDITION')) NOT IN (5,8) - AND (SELECT [compatibility_level] FROM sys.databases WHERE [name] = DB_NAME()) < 130 - ) -BEGIN - SELECT @msg = N'Sorry, sp_BlitzQueryStore doesn''t work on versions of SQL prior to 2016, or Azure Database compatibility < 130.' + REPLICATE(CHAR(13), 7933); - PRINT @msg; - RETURN; -END; +SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; +SET @sql_select += N' +UPDATE wp +SET wp.context_settings = SUBSTRING( + CASE WHEN (CAST(qcs.set_options AS INT) & 1 = 1) THEN '', ANSI_PADDING'' ELSE '''' END + + CASE WHEN (CAST(qcs.set_options AS INT) & 8 = 8) THEN '', CONCAT_NULL_YIELDS_NULL'' ELSE '''' END + + CASE WHEN (CAST(qcs.set_options AS INT) & 16 = 16) THEN '', ANSI_WARNINGS'' ELSE '''' END + + CASE WHEN (CAST(qcs.set_options AS INT) & 32 = 32) THEN '', ANSI_NULLS'' ELSE '''' END + + CASE WHEN (CAST(qcs.set_options AS INT) & 64 = 64) THEN '', QUOTED_IDENTIFIER'' ELSE '''' END + + CASE WHEN (CAST(qcs.set_options AS INT) & 4096 = 4096) THEN '', ARITH_ABORT'' ELSE '''' END + + CASE WHEN (CAST(qcs.set_options AS INT) & 8192 = 8192) THEN '', NUMERIC_ROUNDABORT'' ELSE '''' END + , 2, 200000) +FROM #working_plan_text wp +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq +ON wp.query_id = qsq.query_id +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_context_settings AS qcs +ON qcs.context_settings_id = qsq.context_settings_id +OPTION (RECOMPILE); +'; -IF OBJECT_ID('dbo.sp_BlitzQueryStore') IS NULL - EXEC ('CREATE PROCEDURE dbo.sp_BlitzQueryStore AS RETURN 0;'); -GO +IF @Debug = 1 + PRINT @sql_select; -ALTER PROCEDURE dbo.sp_BlitzQueryStore - @Help BIT = 0, - @DatabaseName NVARCHAR(128) = NULL , - @Top INT = 3, - @StartDate DATETIME2 = NULL, - @EndDate DATETIME2 = NULL, - @MinimumExecutionCount INT = NULL, - @DurationFilter DECIMAL(38,4) = NULL , - @StoredProcName NVARCHAR(128) = NULL, - @Failed BIT = 0, - @PlanIdFilter INT = NULL, - @QueryIdFilter INT = NULL, - @ExportToExcel BIT = 0, - @HideSummary BIT = 0 , - @SkipXML BIT = 0, - @Debug BIT = 0, - @ExpertMode BIT = 0, - @Version VARCHAR(30) = NULL OUTPUT, - @VersionDate DATETIME = NULL OUTPUT, - @VersionCheckMode BIT = 0 -WITH RECOMPILE -AS -BEGIN /*First BEGIN*/ +IF @sql_select IS NULL + BEGIN + RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; + RETURN; + END; -SET NOCOUNT ON; -SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; +EXEC sys.sp_executesql @stmt = @sql_select; -SELECT @Version = '8.0', @VersionDate = '20210117'; -IF(@VersionCheckMode = 1) -BEGIN - RETURN; -END; +/*This adds the patterns we found from each interval to the #working_plan_text table*/ -DECLARE /*Variables for the variable Gods*/ - @msg NVARCHAR(MAX) = N'', --Used to format RAISERROR messages in some places - @sql_select NVARCHAR(MAX) = N'', --Used to hold SELECT statements for dynamic SQL - @sql_where NVARCHAR(MAX) = N'', -- Used to hold WHERE clause for dynamic SQL - @duration_filter_ms DECIMAL(38,4) = (@DurationFilter * 1000.), --We accept Duration in seconds, but we filter in milliseconds (this is grandfathered from sp_BlitzCache) - @execution_threshold INT = 1000, --Threshold at which we consider a query to be frequently executed - @ctp_threshold_pct TINYINT = 10, --Percentage of CTFP at which we consider a query to be near parallel - @long_running_query_warning_seconds BIGINT = 300 * 1000 ,--Number of seconds (converted to milliseconds) at which a query is considered long running - @memory_grant_warning_percent INT = 10,--Percent of memory grant used compared to what's granted; used to trigger unused memory grant warning - @ctp INT,--Holds the CTFP value for the server - @min_memory_per_query INT,--Holds the server configuration value for min memory per query - @cr NVARCHAR(1) = NCHAR(13),--Special character - @lf NVARCHAR(1) = NCHAR(10),--Special character - @tab NVARCHAR(1) = NCHAR(9),--Special character - @error_severity INT,--Holds error info for try/catch blocks - @error_state INT,--Holds error info for try/catch blocks - @sp_params NVARCHAR(MAX) = N'@sp_Top INT, @sp_StartDate DATETIME2, @sp_EndDate DATETIME2, @sp_MinimumExecutionCount INT, @sp_MinDuration INT, @sp_StoredProcName NVARCHAR(128), @sp_PlanIdFilter INT, @sp_QueryIdFilter INT',--Holds parameters used in dynamic SQL - @is_azure_db BIT = 0, --Are we using Azure? I'm not. You might be. That's cool. - @compatibility_level TINYINT = 0, --Some functionality (T-SQL) isn't available in lower compat levels. We can use this to weed out those issues as we go. - @log_size_mb DECIMAL(38,2) = 0, - @avg_tempdb_data_file DECIMAL(38,2) = 0; +RAISERROR(N'Add patterns to working plans', 0, 1) WITH NOWAIT; -/*Grabs CTFP setting*/ -SELECT @ctp = NULLIF(CAST(value AS INT), 0) -FROM sys.configurations -WHERE name = N'cost threshold for parallelism' +UPDATE wpt +SET wpt.pattern = wp.pattern +FROM #working_plans AS wp +JOIN #working_plan_text AS wpt +ON wpt.plan_id = wp.plan_id +AND wpt.query_id = wp.query_id OPTION (RECOMPILE); -/*Grabs min query memory setting*/ -SELECT @min_memory_per_query = CONVERT(INT, c.value) -FROM sys.configurations AS c -WHERE c.name = N'min memory per query (KB)' +/*This cleans up query text a bit*/ + +RAISERROR(N'Clean awkward characters from query text', 0, 1) WITH NOWAIT; + +UPDATE b +SET b.query_sql_text = REPLACE(REPLACE(REPLACE(b.query_sql_text, @cr, ' '), @lf, ' '), @tab, ' ') +FROM #working_plan_text AS b OPTION (RECOMPILE); -/*Check if this is Azure first*/ -IF (SELECT CONVERT(NVARCHAR(128), SERVERPROPERTY ('EDITION'))) <> 'SQL Azure' - BEGIN - /*Grabs log size for datbase*/ - SELECT @log_size_mb = AVG(((mf.size * 8) / 1024.)) - FROM sys.master_files AS mf - WHERE mf.database_id = DB_ID(@DatabaseName) - AND mf.type_desc = 'LOG'; - - /*Grab avg tempdb file size*/ - SELECT @avg_tempdb_data_file = AVG(((mf.size * 8) / 1024.)) - FROM sys.master_files AS mf - WHERE mf.database_id = DB_ID('tempdb') - AND mf.type_desc = 'ROWS'; - END; -/*Help section*/ +/*This populates #working_wait_stats when available*/ -IF @Help = 1 - BEGIN - - SELECT N'You have requested assistance. It will arrive as soon as humanly possible.' AS [Take four red capsules, help is on the way]; +IF @waitstats = 1 - PRINT N' - sp_BlitzQueryStore from http://FirstResponderKit.org - - This script displays your most resource-intensive queries from the Query Store, - and points to ways you can tune these queries to make them faster. - + BEGIN - To learn more, visit http://FirstResponderKit.org where you can download new - versions for free, watch training videos on how it works, get more info on - the findings, contribute your own code, and more. + RAISERROR(N'Collecting wait stats info', 0, 1) WITH NOWAIT; - Known limitations of this version: - - This query will not run on SQL Server versions less than 2016. - - This query will not run on Azure Databases with compatibility less than 130. - - This query will not run on Azure Data Warehouse. - - Unknown limitations of this version: - - Could be tickling + SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; + SET @sql_select += N' + SELECT qws.plan_id, + qws.wait_category, + qws.wait_category_desc, + SUM(qws.total_query_wait_time_ms) AS total_query_wait_time_ms, + SUM(qws.avg_query_wait_time_ms) AS avg_query_wait_time_ms, + SUM(qws.last_query_wait_time_ms) AS last_query_wait_time_ms, + SUM(qws.min_query_wait_time_ms) AS min_query_wait_time_ms, + SUM(qws.max_query_wait_time_ms) AS max_query_wait_time_ms + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_wait_stats qws + JOIN #working_plans AS wp + ON qws.plan_id = wp.plan_id + GROUP BY qws.plan_id, qws.wait_category, qws.wait_category_desc + HAVING SUM(qws.min_query_wait_time_ms) >= 5 + OPTION (RECOMPILE); + '; + + IF @Debug = 1 + PRINT @sql_select; + + IF @sql_select IS NULL + BEGIN + RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; + RETURN; + END; + + INSERT #working_wait_stats WITH (TABLOCK) + ( plan_id, wait_category, wait_category_desc, total_query_wait_time_ms, avg_query_wait_time_ms, last_query_wait_time_ms, min_query_wait_time_ms, max_query_wait_time_ms ) + + EXEC sys.sp_executesql @stmt = @sql_select; - MIT License - Copyright (c) 2021 Brent Ozar Unlimited + /*This updates #working_plan_text with the top three waits from the wait stats DMV*/ - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: + RAISERROR(N'Update working_plan_text with top three waits', 0, 1) WITH NOWAIT; - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE. - '; - RETURN; + UPDATE wpt + SET wpt.top_three_waits = x.top_three_waits + FROM #working_plan_text AS wpt + JOIN ( + SELECT wws.plan_id, + top_three_waits = STUFF((SELECT TOP 3 N', ' + wws2.wait_category_desc + N' (' + CONVERT(NVARCHAR(20), SUM(CONVERT(BIGINT, wws2.avg_query_wait_time_ms))) + N' ms) ' + FROM #working_wait_stats AS wws2 + WHERE wws.plan_id = wws2.plan_id + GROUP BY wws2.wait_category_desc + ORDER BY SUM(wws2.avg_query_wait_time_ms) DESC + FOR XML PATH(N''), TYPE).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 2, N'') + FROM #working_wait_stats AS wws + GROUP BY wws.plan_id + ) AS x + ON x.plan_id = wpt.plan_id + OPTION (RECOMPILE); END; -/*Making sure your version is copasetic*/ -IF ( (SELECT CONVERT(NVARCHAR(128), SERVERPROPERTY ('EDITION'))) = 'SQL Azure' ) - BEGIN - SET @is_azure_db = 1; +/*End wait stats population*/ - IF ( (SELECT SERVERPROPERTY ('ENGINEEDITION')) NOT IN (5,8) - OR (SELECT [compatibility_level] FROM sys.databases WHERE [name] = DB_NAME()) < 130 - ) - BEGIN - SELECT @msg = N'Sorry, sp_BlitzQueryStore doesn''t work on Azure Data Warehouse, or Azure Databases with DB compatibility < 130.' + REPLICATE(CHAR(13), 7933); - PRINT @msg; - RETURN; - END; - END; -ELSE IF ( (SELECT PARSENAME(CONVERT(NVARCHAR(128), SERVERPROPERTY ('PRODUCTVERSION')), 4) ) < 13 ) - BEGIN - SELECT @msg = N'Sorry, sp_BlitzQueryStore doesn''t work on versions of SQL prior to 2016.' + REPLICATE(CHAR(13), 7933); - PRINT @msg; - RETURN; - END; +UPDATE #working_plan_text +SET top_three_waits = CASE + WHEN @waitstats = 0 + THEN N'The query store waits stats DMV is not available' + ELSE N'No Significant waits detected!' + END +WHERE top_three_waits IS NULL +OPTION(RECOMPILE); -/*Making sure at least one database uses QS*/ -IF ( SELECT COUNT(*) - FROM sys.databases AS d - WHERE d.is_query_store_on = 1 - AND d.user_access_desc='MULTI_USER' - AND d.state_desc = 'ONLINE' - AND d.name NOT IN ('master', 'model', 'msdb', 'tempdb', '32767') - AND d.is_distributor = 0 ) = 0 - BEGIN - SELECT @msg = N'You don''t currently have any databases with Query Store enabled.' + REPLICATE(CHAR(13), 7933); - PRINT @msg; - RETURN; - END; - -/*Making sure your databases are using QDS.*/ -RAISERROR('Checking database validity', 0, 1) WITH NOWAIT; +END; +END TRY +BEGIN CATCH + RAISERROR (N'Failure populating temp tables.', 0,1) WITH NOWAIT; -IF (@is_azure_db = 1) - SET @DatabaseName = DB_NAME(); -ELSE -BEGIN + IF @sql_select IS NOT NULL + BEGIN + SET @msg = N'Last @sql_select: ' + @sql_select; + RAISERROR(@msg, 0, 1) WITH NOWAIT; + END; - /*If we're on Azure we don't need to check all this @DatabaseName stuff...*/ + SELECT @msg = @DatabaseName + N' database failed to process. ' + ERROR_MESSAGE(), @error_severity = ERROR_SEVERITY(), @error_state = ERROR_STATE(); + RAISERROR (@msg, @error_severity, @error_state) WITH NOWAIT; + + + WHILE @@TRANCOUNT > 0 + ROLLBACK; - SET @DatabaseName = LTRIM(RTRIM(@DatabaseName)); + RETURN; +END CATCH; - /*Did you set @DatabaseName?*/ - RAISERROR('Making sure [%s] isn''t NULL', 0, 1, @DatabaseName) WITH NOWAIT; - IF (@DatabaseName IS NULL) - BEGIN - RAISERROR('@DatabaseName cannot be NULL', 0, 1) WITH NOWAIT; - RETURN; - END; +IF (@SkipXML = 0) +BEGIN TRY +BEGIN - /*Does the database exist?*/ - RAISERROR('Making sure [%s] exists', 0, 1, @DatabaseName) WITH NOWAIT; - IF ((DB_ID(@DatabaseName)) IS NULL) - BEGIN - RAISERROR('The @DatabaseName you specified ([%s]) does not exist. Please check the name and try again.', 0, 1, @DatabaseName) WITH NOWAIT; - RETURN; - END; +/* +This sets up the #working_warnings table with the IDs we're interested in so we can tie warnings back to them +*/ - /*Is it online?*/ - RAISERROR('Making sure [%s] is online', 0, 1, @DatabaseName) WITH NOWAIT; - IF (DATABASEPROPERTYEX(@DatabaseName, 'Collation')) IS NULL - BEGIN - RAISERROR('The @DatabaseName you specified ([%s]) is not readable. Please check the name and try again. Better yet, check your server.', 0, 1, @DatabaseName); - RETURN; - END; -END; +RAISERROR(N'Populate working warnings table with gathered plans', 0, 1) WITH NOWAIT; + + +SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; +SET @sql_select += N' +SELECT DISTINCT wp.plan_id, wp.query_id, qsq.query_hash, qsqt.statement_sql_handle +FROM #working_plans AS wp +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp +ON qsp.plan_id = wp.plan_id + AND qsp.query_id = wp.query_id +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq +ON wp.query_id = qsq.query_id +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt +ON qsqt.query_text_id = qsq.query_text_id +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs +ON qsrs.plan_id = wp.plan_id +WHERE 1 = 1 + AND qsq.is_internal_query = 0 + AND qsp.query_plan IS NOT NULL + '; + +SET @sql_select += @sql_where; + +SET @sql_select += N'OPTION (RECOMPILE); + '; + +IF @Debug = 1 + PRINT @sql_select; + +IF @sql_select IS NULL + BEGIN + RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; + RETURN; + END; + +INSERT #working_warnings WITH (TABLOCK) + ( plan_id, query_id, query_hash, sql_handle ) +EXEC sys.sp_executesql @stmt = @sql_select, + @params = @sp_params, + @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; + +/* +This looks for queries in the query stores that we picked up from an internal that have multiple plans in cache + +This and several of the following queries all replaced XML parsing to find plan attributes. Sweet. + +Thanks, Query Store +*/ + +RAISERROR(N'Populating object name in #working_warnings', 0, 1) WITH NOWAIT; +UPDATE w +SET w.proc_or_function_name = ISNULL(wm.proc_or_function_name, N'Statement') +FROM #working_warnings AS w +JOIN #working_metrics AS wm +ON w.plan_id = wm.plan_id + AND w.query_id = wm.query_id +OPTION (RECOMPILE); + + +RAISERROR(N'Checking for multiple plans', 0, 1) WITH NOWAIT; + +SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; +SET @sql_select += N' +UPDATE ww +SET ww.plan_multiple_plans = 1 +FROM #working_warnings AS ww +JOIN +( +SELECT wp.query_id, COUNT(qsp.plan_id) AS plans +FROM #working_plans AS wp +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp +ON qsp.plan_id = wp.plan_id + AND qsp.query_id = wp.query_id +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq +ON wp.query_id = qsq.query_id +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt +ON qsqt.query_text_id = qsq.query_text_id +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs +ON qsrs.plan_id = wp.plan_id +WHERE 1 = 1 + AND qsq.is_internal_query = 0 + AND qsp.query_plan IS NOT NULL + '; + +SET @sql_select += @sql_where; + +SET @sql_select += +N'GROUP BY wp.query_id + HAVING COUNT(qsp.plan_id) > 1 +) AS x + ON ww.query_id = x.query_id +OPTION (RECOMPILE); +'; + +IF @Debug = 1 + PRINT @sql_select; + +IF @sql_select IS NULL + BEGIN + RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; + RETURN; + END; + +EXEC sys.sp_executesql @stmt = @sql_select, + @params = @sp_params, + @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; -/*Does it have Query Store enabled?*/ -RAISERROR('Making sure [%s] has Query Store enabled', 0, 1, @DatabaseName) WITH NOWAIT; -IF +/* +This looks for forced plans +*/ - ( SELECT [d].[name] - FROM [sys].[databases] AS d - WHERE [d].[is_query_store_on] = 1 - AND [d].[user_access_desc]='MULTI_USER' - AND [d].[state_desc] = 'ONLINE' - AND [d].[database_id] = (SELECT database_id FROM sys.databases WHERE name = @DatabaseName) - ) IS NULL -BEGIN - RAISERROR('The @DatabaseName you specified ([%s]) does not have the Query Store enabled. Please check the name or settings, and try again.', 0, 1, @DatabaseName) WITH NOWAIT; - RETURN; -END; +RAISERROR(N'Checking for forced plans', 0, 1) WITH NOWAIT; -/*Check database compat level*/ +UPDATE ww +SET ww.is_forced_plan = 1 +FROM #working_warnings AS ww +JOIN #working_plan_text AS wp +ON ww.plan_id = wp.plan_id + AND ww.query_id = wp.query_id + AND wp.is_forced_plan = 1 +OPTION (RECOMPILE); -RAISERROR('Checking database compatibility level', 0, 1) WITH NOWAIT; -SELECT @compatibility_level = d.compatibility_level -FROM sys.databases AS d -WHERE d.name = @DatabaseName; +/* +This looks for forced parameterization +*/ -RAISERROR('The @DatabaseName you specified ([%s])is running in compatibility level ([%d]).', 0, 1, @DatabaseName, @compatibility_level) WITH NOWAIT; +RAISERROR(N'Checking for forced parameterization', 0, 1) WITH NOWAIT; +UPDATE ww +SET ww.is_forced_parameterized = 1 +FROM #working_warnings AS ww +JOIN #working_metrics AS wm +ON ww.plan_id = wm.plan_id + AND ww.query_id = wm.query_id + AND wm.query_parameterization_type_desc = 'Forced' +OPTION (RECOMPILE); -/*Making sure top is set to something if NULL*/ -IF ( @Top IS NULL ) - BEGIN - SET @Top = 3; - END; /* -This section determines if you have the Query Store wait stats DMV +This looks for unparameterized queries */ -RAISERROR('Checking for query_store_wait_stats', 0, 1) WITH NOWAIT; +RAISERROR(N'Checking for unparameterized plans', 0, 1) WITH NOWAIT; -DECLARE @ws_out INT, - @waitstats BIT, - @ws_sql NVARCHAR(MAX) = N'SELECT @i_out = COUNT(*) FROM ' + QUOTENAME(@DatabaseName) + N'.sys.all_objects WHERE name = ''query_store_wait_stats'' OPTION (RECOMPILE);', - @ws_params NVARCHAR(MAX) = N'@i_out INT OUTPUT'; +UPDATE ww +SET ww.unparameterized_query = 1 +FROM #working_warnings AS ww +JOIN #working_metrics AS wm +ON ww.plan_id = wm.plan_id + AND ww.query_id = wm.query_id + AND wm.query_parameterization_type_desc = 'None' + AND ww.proc_or_function_name = 'Statement' +OPTION (RECOMPILE); -EXEC sys.sp_executesql @ws_sql, @ws_params, @i_out = @ws_out OUTPUT; -SELECT @waitstats = CASE @ws_out WHEN 0 THEN 0 ELSE 1 END; +/* +This looks for cursors +*/ -SET @msg = N'Wait stats DMV ' + CASE @waitstats - WHEN 0 THEN N' does not exist, skipping.' - WHEN 1 THEN N' exists, will analyze.' - END; -RAISERROR(@msg, 0, 1) WITH NOWAIT; +RAISERROR(N'Checking for cursors', 0, 1) WITH NOWAIT; +UPDATE ww +SET ww.is_cursor = 1 +FROM #working_warnings AS ww +JOIN #working_plan_text AS wp +ON ww.plan_id = wp.plan_id + AND ww.query_id = wp.query_id + AND wp.plan_group_id > 0 +OPTION (RECOMPILE); + + +UPDATE ww +SET ww.is_cursor = 1 +FROM #working_warnings AS ww +JOIN #working_plan_text AS wp +ON ww.plan_id = wp.plan_id + AND ww.query_id = wp.query_id +WHERE ww.query_hash = 0x0000000000000000 +OR wp.query_plan_hash = 0x0000000000000000 +OPTION (RECOMPILE); /* -This section determines if you have some additional columns present in 2017, in case they get back ported. +This looks for parallel plans */ +UPDATE ww +SET ww.is_parallel = 1 +FROM #working_warnings AS ww +JOIN #working_plan_text AS wp +ON ww.plan_id = wp.plan_id + AND ww.query_id = wp.query_id + AND wp.is_parallel_plan = 1 +OPTION (RECOMPILE); -RAISERROR('Checking for new columns in query_store_runtime_stats', 0, 1) WITH NOWAIT; +/*This looks for old CE*/ -DECLARE @nc_out INT, - @new_columns BIT, - @nc_sql NVARCHAR(MAX) = N'SELECT @i_out = COUNT(*) - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.all_columns AS ac - WHERE OBJECT_NAME(object_id) = ''query_store_runtime_stats'' - AND ac.name IN ( - ''avg_num_physical_io_reads'', - ''last_num_physical_io_reads'', - ''min_num_physical_io_reads'', - ''max_num_physical_io_reads'', - ''avg_log_bytes_used'', - ''last_log_bytes_used'', - ''min_log_bytes_used'', - ''max_log_bytes_used'', - ''avg_tempdb_space_used'', - ''last_tempdb_space_used'', - ''min_tempdb_space_used'', - ''max_tempdb_space_used'' - ) OPTION (RECOMPILE);', - @nc_params NVARCHAR(MAX) = N'@i_out INT OUTPUT'; +RAISERROR(N'Checking for legacy CE', 0, 1) WITH NOWAIT; -EXEC sys.sp_executesql @nc_sql, @ws_params, @i_out = @nc_out OUTPUT; +UPDATE w +SET w.downlevel_estimator = 1 +FROM #working_warnings AS w +JOIN #working_plan_text AS wpt +ON w.plan_id = wpt.plan_id +AND w.query_id = wpt.query_id +/*PLEASE DON'T TELL ANYONE I DID THIS*/ +WHERE PARSENAME(wpt.engine_version, 4) < PARSENAME(CONVERT(VARCHAR(128), SERVERPROPERTY ('PRODUCTVERSION')), 4) +OPTION (RECOMPILE); +/*NO SERIOUSLY THIS IS A HORRIBLE IDEA*/ -SELECT @new_columns = CASE @nc_out WHEN 12 THEN 1 ELSE 0 END; -SET @msg = N'New query_store_runtime_stats columns ' + CASE @new_columns - WHEN 0 THEN N' do not exist, skipping.' - WHEN 1 THEN N' exist, will analyze.' - END; -RAISERROR(@msg, 0, 1) WITH NOWAIT; +/*Plans that compile 2x more than they execute*/ - -/* -These are the temp tables we use -*/ +RAISERROR(N'Checking for plans that compile 2x more than they execute', 0, 1) WITH NOWAIT; +UPDATE ww +SET ww.is_compile_more = 1 +FROM #working_warnings AS ww +JOIN #working_metrics AS wm +ON ww.plan_id = wm.plan_id + AND ww.query_id = wm.query_id + AND wm.count_compiles > (wm.count_executions * 2) +OPTION (RECOMPILE); -/* -This one holds the grouped data that helps use figure out which periods to examine -*/ +/*Plans that compile 2x more than they execute*/ -RAISERROR(N'Creating temp tables', 0, 1) WITH NOWAIT; +RAISERROR(N'Checking for plans that take more than 5 seconds to bind, compile, or optimize', 0, 1) WITH NOWAIT; -DROP TABLE IF EXISTS #grouped_interval; +UPDATE ww +SET ww.is_slow_plan = 1 +FROM #working_warnings AS ww +JOIN #working_metrics AS wm +ON ww.plan_id = wm.plan_id + AND ww.query_id = wm.query_id + AND (wm.avg_bind_duration > 5000 + OR + wm.avg_compile_duration > 5000 + OR + wm.avg_optimize_duration > 5000 + OR + wm.avg_optimize_cpu_time > 5000) +OPTION (RECOMPILE); -CREATE TABLE #grouped_interval -( - flat_date DATE NULL, - start_range DATETIME NULL, - end_range DATETIME NULL, - total_avg_duration_ms DECIMAL(38, 2) NULL, - total_avg_cpu_time_ms DECIMAL(38, 2) NULL, - total_avg_logical_io_reads_mb DECIMAL(38, 2) NULL, - total_avg_physical_io_reads_mb DECIMAL(38, 2) NULL, - total_avg_logical_io_writes_mb DECIMAL(38, 2) NULL, - total_avg_query_max_used_memory_mb DECIMAL(38, 2) NULL, - total_rowcount DECIMAL(38, 2) NULL, - total_count_executions BIGINT NULL, - total_avg_log_bytes_mb DECIMAL(38, 2) NULL, - total_avg_tempdb_space DECIMAL(38, 2) NULL, - total_max_duration_ms DECIMAL(38, 2) NULL, - total_max_cpu_time_ms DECIMAL(38, 2) NULL, - total_max_logical_io_reads_mb DECIMAL(38, 2) NULL, - total_max_physical_io_reads_mb DECIMAL(38, 2) NULL, - total_max_logical_io_writes_mb DECIMAL(38, 2) NULL, - total_max_query_max_used_memory_mb DECIMAL(38, 2) NULL, - total_max_log_bytes_mb DECIMAL(38, 2) NULL, - total_max_tempdb_space DECIMAL(38, 2) NULL, - INDEX gi_ix_dates CLUSTERED (start_range, end_range) -); /* -These are the plans we focus on based on what we find in the grouped intervals +This parses the XML from our top plans into smaller chunks for easier consumption */ -DROP TABLE IF EXISTS #working_plans; - -CREATE TABLE #working_plans -( - plan_id BIGINT, - query_id BIGINT, - pattern NVARCHAR(258), - INDEX wp_ix_ids CLUSTERED (plan_id, query_id) -); +RAISERROR(N'Begin XML nodes parsing', 0, 1) WITH NOWAIT; -/* -These are the gathered metrics we get from query store to generate some warnings and help you find your worst offenders -*/ -DROP TABLE IF EXISTS #working_metrics; +RAISERROR(N'Inserting #statements', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +INSERT #statements WITH (TABLOCK) ( plan_id, query_id, query_hash, sql_handle, statement, is_cursor ) + SELECT ww.plan_id, ww.query_id, ww.query_hash, ww.sql_handle, q.n.query('.') AS statement, 0 AS is_cursor + FROM #working_warnings AS ww + JOIN #working_plan_text AS wp + ON ww.plan_id = wp.plan_id + AND ww.query_id = wp.query_id + CROSS APPLY wp.query_plan_xml.nodes('//p:StmtSimple') AS q(n) +OPTION (RECOMPILE); -CREATE TABLE #working_metrics -( - database_name NVARCHAR(258), - plan_id BIGINT, - query_id BIGINT, - query_id_all_plan_ids VARCHAR(8000), - /*these columns are from query_store_query*/ - proc_or_function_name NVARCHAR(258), - batch_sql_handle VARBINARY(64), - query_hash BINARY(8), - query_parameterization_type_desc NVARCHAR(258), - parameter_sniffing_symptoms NVARCHAR(4000), - count_compiles BIGINT, - avg_compile_duration DECIMAL(38,2), - last_compile_duration DECIMAL(38,2), - avg_bind_duration DECIMAL(38,2), - last_bind_duration DECIMAL(38,2), - avg_bind_cpu_time DECIMAL(38,2), - last_bind_cpu_time DECIMAL(38,2), - avg_optimize_duration DECIMAL(38,2), - last_optimize_duration DECIMAL(38,2), - avg_optimize_cpu_time DECIMAL(38,2), - last_optimize_cpu_time DECIMAL(38,2), - avg_compile_memory_kb DECIMAL(38,2), - last_compile_memory_kb DECIMAL(38,2), - /*These come from query_store_runtime_stats*/ - execution_type_desc NVARCHAR(128), - first_execution_time DATETIME2, - last_execution_time DATETIME2, - count_executions BIGINT, - avg_duration DECIMAL(38,2) , - last_duration DECIMAL(38,2), - min_duration DECIMAL(38,2), - max_duration DECIMAL(38,2), - avg_cpu_time DECIMAL(38,2), - last_cpu_time DECIMAL(38,2), - min_cpu_time DECIMAL(38,2), - max_cpu_time DECIMAL(38,2), - avg_logical_io_reads DECIMAL(38,2), - last_logical_io_reads DECIMAL(38,2), - min_logical_io_reads DECIMAL(38,2), - max_logical_io_reads DECIMAL(38,2), - avg_logical_io_writes DECIMAL(38,2), - last_logical_io_writes DECIMAL(38,2), - min_logical_io_writes DECIMAL(38,2), - max_logical_io_writes DECIMAL(38,2), - avg_physical_io_reads DECIMAL(38,2), - last_physical_io_reads DECIMAL(38,2), - min_physical_io_reads DECIMAL(38,2), - max_physical_io_reads DECIMAL(38,2), - avg_clr_time DECIMAL(38,2), - last_clr_time DECIMAL(38,2), - min_clr_time DECIMAL(38,2), - max_clr_time DECIMAL(38,2), - avg_dop BIGINT, - last_dop BIGINT, - min_dop BIGINT, - max_dop BIGINT, - avg_query_max_used_memory DECIMAL(38,2), - last_query_max_used_memory DECIMAL(38,2), - min_query_max_used_memory DECIMAL(38,2), - max_query_max_used_memory DECIMAL(38,2), - avg_rowcount DECIMAL(38,2), - last_rowcount DECIMAL(38,2), - min_rowcount DECIMAL(38,2), - max_rowcount DECIMAL(38,2), - /*These are 2017 only, AFAIK*/ - avg_num_physical_io_reads DECIMAL(38,2), - last_num_physical_io_reads DECIMAL(38,2), - min_num_physical_io_reads DECIMAL(38,2), - max_num_physical_io_reads DECIMAL(38,2), - avg_log_bytes_used DECIMAL(38,2), - last_log_bytes_used DECIMAL(38,2), - min_log_bytes_used DECIMAL(38,2), - max_log_bytes_used DECIMAL(38,2), - avg_tempdb_space_used DECIMAL(38,2), - last_tempdb_space_used DECIMAL(38,2), - min_tempdb_space_used DECIMAL(38,2), - max_tempdb_space_used DECIMAL(38,2), - /*These are computed columns to make some stuff easier down the line*/ - total_compile_duration AS avg_compile_duration * count_compiles, - total_bind_duration AS avg_bind_duration * count_compiles, - total_bind_cpu_time AS avg_bind_cpu_time * count_compiles, - total_optimize_duration AS avg_optimize_duration * count_compiles, - total_optimize_cpu_time AS avg_optimize_cpu_time * count_compiles, - total_compile_memory_kb AS avg_compile_memory_kb * count_compiles, - total_duration AS avg_duration * count_executions, - total_cpu_time AS avg_cpu_time * count_executions, - total_logical_io_reads AS avg_logical_io_reads * count_executions, - total_logical_io_writes AS avg_logical_io_writes * count_executions, - total_physical_io_reads AS avg_physical_io_reads * count_executions, - total_clr_time AS avg_clr_time * count_executions, - total_query_max_used_memory AS avg_query_max_used_memory * count_executions, - total_rowcount AS avg_rowcount * count_executions, - total_num_physical_io_reads AS avg_num_physical_io_reads * count_executions, - total_log_bytes_used AS avg_log_bytes_used * count_executions, - total_tempdb_space_used AS avg_tempdb_space_used * count_executions, - xpm AS NULLIF(count_executions, 0) / NULLIF(DATEDIFF(MINUTE, first_execution_time, last_execution_time), 0), - percent_memory_grant_used AS CONVERT(MONEY, ISNULL(NULLIF(( max_query_max_used_memory * 1.00 ), 0) / NULLIF(min_query_max_used_memory, 0), 0) * 100.), - INDEX wm_ix_ids CLUSTERED (plan_id, query_id, query_hash) -); +RAISERROR(N'Inserting parsed cursor XML to #statements', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +INSERT #statements WITH (TABLOCK) ( plan_id, query_id, query_hash, sql_handle, statement, is_cursor ) + SELECT ww.plan_id, ww.query_id, ww.query_hash, ww.sql_handle, q.n.query('.') AS statement, 1 AS is_cursor + FROM #working_warnings AS ww + JOIN #working_plan_text AS wp + ON ww.plan_id = wp.plan_id + AND ww.query_id = wp.query_id + CROSS APPLY wp.query_plan_xml.nodes('//p:StmtCursor') AS q(n) +OPTION (RECOMPILE); +RAISERROR(N'Inserting to #query_plan', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +INSERT #query_plan WITH (TABLOCK) ( plan_id, query_id, query_hash, sql_handle, query_plan ) +SELECT s.plan_id, s.query_id, s.query_hash, s.sql_handle, q.n.query('.') AS query_plan +FROM #statements AS s + CROSS APPLY s.statement.nodes('//p:QueryPlan') AS q(n) +OPTION (RECOMPILE); -/* -This is where we store some additional metrics, along with the query plan and text -*/ -DROP TABLE IF EXISTS #working_plan_text; +RAISERROR(N'Inserting to #relop', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +INSERT #relop WITH (TABLOCK) ( plan_id, query_id, query_hash, sql_handle, relop) +SELECT qp.plan_id, qp.query_id, qp.query_hash, qp.sql_handle, q.n.query('.') AS relop +FROM #query_plan qp + CROSS APPLY qp.query_plan.nodes('//p:RelOp') AS q(n) +OPTION (RECOMPILE); -CREATE TABLE #working_plan_text -( - database_name NVARCHAR(258), - plan_id BIGINT, - query_id BIGINT, - /*These are from query_store_plan*/ - plan_group_id BIGINT, - engine_version NVARCHAR(64), - compatibility_level INT, - query_plan_hash BINARY(8), - query_plan_xml XML, - is_online_index_plan BIT, - is_trivial_plan BIT, - is_parallel_plan BIT, - is_forced_plan BIT, - is_natively_compiled BIT, - force_failure_count BIGINT, - last_force_failure_reason_desc NVARCHAR(258), - count_compiles BIGINT, - initial_compile_start_time DATETIME2, - last_compile_start_time DATETIME2, - last_execution_time DATETIME2, - avg_compile_duration DECIMAL(38,2), - last_compile_duration BIGINT, - /*These are from query_store_query*/ - query_sql_text NVARCHAR(MAX), - statement_sql_handle VARBINARY(64), - is_part_of_encrypted_module BIT, - has_restricted_text BIT, - /*This is from query_context_settings*/ - context_settings NVARCHAR(512), - /*This is from #working_plans*/ - pattern NVARCHAR(512), - top_three_waits NVARCHAR(MAX), - INDEX wpt_ix_ids CLUSTERED (plan_id, query_id, query_plan_hash) -); +-- statement level checks -/* -This is where we store warnings that we generate from the XML and metrics -*/ -DROP TABLE IF EXISTS #working_warnings; +RAISERROR(N'Performing compile timeout checks', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +UPDATE b +SET b.compile_timeout = 1 +FROM #statements s +JOIN #working_warnings AS b +ON s.query_hash = b.query_hash +WHERE s.statement.exist('/p:StmtSimple/@StatementOptmEarlyAbortReason[.="TimeOut"]') = 1 +OPTION (RECOMPILE); -CREATE TABLE #working_warnings -( - plan_id BIGINT, - query_id BIGINT, - query_hash BINARY(8), - sql_handle VARBINARY(64), - proc_or_function_name NVARCHAR(258), - plan_multiple_plans BIT, - is_forced_plan BIT, - is_forced_parameterized BIT, - is_cursor BIT, - is_optimistic_cursor BIT, - is_forward_only_cursor BIT, - is_fast_forward_cursor BIT, - is_cursor_dynamic BIT, - is_parallel BIT, - is_forced_serial BIT, - is_key_lookup_expensive BIT, - key_lookup_cost FLOAT, - is_remote_query_expensive BIT, - remote_query_cost FLOAT, - frequent_execution BIT, - parameter_sniffing BIT, - unparameterized_query BIT, - near_parallel BIT, - plan_warnings BIT, - long_running BIT, - downlevel_estimator BIT, - implicit_conversions BIT, - tvf_estimate BIT, - compile_timeout BIT, - compile_memory_limit_exceeded BIT, - warning_no_join_predicate BIT, - query_cost FLOAT, - missing_index_count INT, - unmatched_index_count INT, - is_trivial BIT, - trace_flags_session NVARCHAR(1000), - is_unused_grant BIT, - function_count INT, - clr_function_count INT, - is_table_variable BIT, - no_stats_warning BIT, - relop_warnings BIT, - is_table_scan BIT, - backwards_scan BIT, - forced_index BIT, - forced_seek BIT, - forced_scan BIT, - columnstore_row_mode BIT, - is_computed_scalar BIT , - is_sort_expensive BIT, - sort_cost FLOAT, - is_computed_filter BIT, - op_name NVARCHAR(100) NULL, - index_insert_count INT NULL, - index_update_count INT NULL, - index_delete_count INT NULL, - cx_insert_count INT NULL, - cx_update_count INT NULL, - cx_delete_count INT NULL, - table_insert_count INT NULL, - table_update_count INT NULL, - table_delete_count INT NULL, - index_ops AS (index_insert_count + index_update_count + index_delete_count + - cx_insert_count + cx_update_count + cx_delete_count + - table_insert_count + table_update_count + table_delete_count), - is_row_level BIT, - is_spatial BIT, - index_dml BIT, - table_dml BIT, - long_running_low_cpu BIT, - low_cost_high_cpu BIT, - stale_stats BIT, - is_adaptive BIT, - is_slow_plan BIT, - is_compile_more BIT, - index_spool_cost FLOAT, - index_spool_rows FLOAT, - is_spool_expensive BIT, - is_spool_more_rows BIT, - estimated_rows FLOAT, - is_bad_estimate BIT, - is_big_log BIT, - is_big_tempdb BIT, - is_paul_white_electric BIT, - is_row_goal BIT, - is_mstvf BIT, - is_mm_join BIT, - is_nonsargable BIT, - busy_loops BIT, - tvf_join BIT, - implicit_conversion_info XML, - cached_execution_parameters XML, - missing_indexes XML, - warnings NVARCHAR(4000) - INDEX ww_ix_ids CLUSTERED (plan_id, query_id, query_hash, sql_handle) -); +RAISERROR(N'Performing compile memory limit exceeded checks', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +UPDATE b +SET b.compile_memory_limit_exceeded = 1 +FROM #statements s +JOIN #working_warnings AS b +ON s.query_hash = b.query_hash +WHERE s.statement.exist('/p:StmtSimple/@StatementOptmEarlyAbortReason[.="MemoryLimitExceeded"]') = 1 +OPTION (RECOMPILE); -DROP TABLE IF EXISTS #working_wait_stats; +IF @ExpertMode > 0 +BEGIN +RAISERROR(N'Performing index DML checks', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p), +index_dml AS ( + SELECT s.query_hash, + index_dml = CASE WHEN s.statement.exist('//p:StmtSimple/@StatementType[.="CREATE INDEX"]') = 1 THEN 1 + WHEN s.statement.exist('//p:StmtSimple/@StatementType[.="DROP INDEX"]') = 1 THEN 1 + END + FROM #statements s + ) + UPDATE b + SET b.index_dml = i.index_dml + FROM #working_warnings AS b + JOIN index_dml i + ON i.query_hash = b.query_hash + WHERE i.index_dml = 1 +OPTION (RECOMPILE); -CREATE TABLE #working_wait_stats -( - plan_id BIGINT, - wait_category TINYINT, - wait_category_desc NVARCHAR(258), - total_query_wait_time_ms BIGINT, - avg_query_wait_time_ms DECIMAL(38, 2), - last_query_wait_time_ms BIGINT, - min_query_wait_time_ms BIGINT, - max_query_wait_time_ms BIGINT, - wait_category_mapped AS CASE wait_category - WHEN 0 THEN N'UNKNOWN' - WHEN 1 THEN N'SOS_SCHEDULER_YIELD' - WHEN 2 THEN N'THREADPOOL' - WHEN 3 THEN N'LCK_M_%' - WHEN 4 THEN N'LATCH_%' - WHEN 5 THEN N'PAGELATCH_%' - WHEN 6 THEN N'PAGEIOLATCH_%' - WHEN 7 THEN N'RESOURCE_SEMAPHORE_QUERY_COMPILE' - WHEN 8 THEN N'CLR%, SQLCLR%' - WHEN 9 THEN N'DBMIRROR%' - WHEN 10 THEN N'XACT%, DTC%, TRAN_MARKLATCH_%, MSQL_XACT_%, TRANSACTION_MUTEX' - WHEN 11 THEN N'SLEEP_%, LAZYWRITER_SLEEP, SQLTRACE_BUFFER_FLUSH, SQLTRACE_INCREMENTAL_FLUSH_SLEEP, SQLTRACE_WAIT_ENTRIES, FT_IFTS_SCHEDULER_IDLE_WAIT, XE_DISPATCHER_WAIT, REQUEST_FOR_DEADLOCK_SEARCH, LOGMGR_QUEUE, ONDEMAND_TASK_QUEUE, CHECKPOINT_QUEUE, XE_TIMER_EVENT' - WHEN 12 THEN N'PREEMPTIVE_%' - WHEN 13 THEN N'BROKER_% (but not BROKER_RECEIVE_WAITFOR)' - WHEN 14 THEN N'LOGMGR, LOGBUFFER, LOGMGR_RESERVE_APPEND, LOGMGR_FLUSH, LOGMGR_PMM_LOG, CHKPT, WRITELOG' - WHEN 15 THEN N'ASYNC_NETWORK_IO, NET_WAITFOR_PACKET, PROXY_NETWORK_IO, EXTERNAL_SCRIPT_NETWORK_IOF' - WHEN 16 THEN N'CXPACKET, EXCHANGE, CXCONSUMER' - WHEN 17 THEN N'RESOURCE_SEMAPHORE, CMEMTHREAD, CMEMPARTITIONED, EE_PMOLOCK, MEMORY_ALLOCATION_EXT, RESERVED_MEMORY_ALLOCATION_EXT, MEMORY_GRANT_UPDATE' - WHEN 18 THEN N'WAITFOR, WAIT_FOR_RESULTS, BROKER_RECEIVE_WAITFOR' - WHEN 19 THEN N'TRACEWRITE, SQLTRACE_LOCK, SQLTRACE_FILE_BUFFER, SQLTRACE_FILE_WRITE_IO_COMPLETION, SQLTRACE_FILE_READ_IO_COMPLETION, SQLTRACE_PENDING_BUFFER_WRITERS, SQLTRACE_SHUTDOWN, QUERY_TRACEOUT, TRACE_EVTNOTIFF' - WHEN 20 THEN N'FT_RESTART_CRAWL, FULLTEXT GATHERER, MSSEARCH, FT_METADATA_MUTEX, FT_IFTSHC_MUTEX, FT_IFTSISM_MUTEX, FT_IFTS_RWLOCK, FT_COMPROWSET_RWLOCK, FT_MASTER_MERGE, FT_PROPERTYLIST_CACHE, FT_MASTER_MERGE_COORDINATOR, PWAIT_RESOURCE_SEMAPHORE_FT_PARALLEL_QUERY_SYNC' - WHEN 21 THEN N'ASYNC_IO_COMPLETION, IO_COMPLETION, BACKUPIO, WRITE_COMPLETION, IO_QUEUE_LIMIT, IO_RETRY' - WHEN 22 THEN N'SE_REPL_%, REPL_%, HADR_% (but not HADR_THROTTLE_LOG_RATE_GOVERNOR), PWAIT_HADR_%, REPLICA_WRITES, FCB_REPLICA_WRITE, FCB_REPLICA_READ, PWAIT_HADRSIM' - WHEN 23 THEN N'LOG_RATE_GOVERNOR, POOL_LOG_RATE_GOVERNOR, HADR_THROTTLE_LOG_RATE_GOVERNOR, INSTANCE_LOG_RATE_GOVERNOR' - END, - INDEX wws_ix_ids CLUSTERED ( plan_id) -); +RAISERROR(N'Performing table DML checks', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p), +table_dml AS ( + SELECT s.query_hash, + table_dml = CASE WHEN s.statement.exist('//p:StmtSimple/@StatementType[.="CREATE TABLE"]') = 1 THEN 1 + WHEN s.statement.exist('//p:StmtSimple/@StatementType[.="DROP OBJECT"]') = 1 THEN 1 + END + FROM #statements AS s + ) + UPDATE b + SET b.table_dml = t.table_dml + FROM #working_warnings AS b + JOIN table_dml t + ON t.query_hash = b.query_hash + WHERE t.table_dml = 1 +OPTION (RECOMPILE); +END; -/* -The next three tables hold plan XML parsed out to different degrees -*/ -DROP TABLE IF EXISTS #statements; -CREATE TABLE #statements -( - plan_id BIGINT, - query_id BIGINT, - query_hash BINARY(8), - sql_handle VARBINARY(64), - statement XML, - is_cursor BIT - INDEX s_ix_ids CLUSTERED (plan_id, query_id, query_hash, sql_handle) -); +RAISERROR(N'Gathering trivial plans', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES ( 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) +UPDATE b +SET b.is_trivial = 1 +FROM #working_warnings AS b +JOIN ( +SELECT s.sql_handle +FROM #statements AS s +JOIN ( SELECT r.sql_handle + FROM #relop AS r + WHERE r.relop.exist('//p:RelOp[contains(@LogicalOp, "Scan")]') = 1 ) AS r + ON r.sql_handle = s.sql_handle +WHERE s.statement.exist('//p:StmtSimple[@StatementOptmLevel[.="TRIVIAL"]]/p:QueryPlan/p:ParameterList') = 1 +) AS s +ON b.sql_handle = s.sql_handle +OPTION (RECOMPILE); +IF @ExpertMode > 0 +BEGIN +RAISERROR(N'Gathering row estimates', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES ('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) +INSERT #est_rows (query_hash, estimated_rows) +SELECT DISTINCT + CONVERT(BINARY(8), RIGHT('0000000000000000' + SUBSTRING(c.n.value('@QueryHash', 'VARCHAR(18)'), 3, 18), 16), 2) AS query_hash, + c.n.value('(/p:StmtSimple/@StatementEstRows)[1]', 'FLOAT') AS estimated_rows +FROM #statements AS s +CROSS APPLY s.statement.nodes('/p:StmtSimple') AS c(n) +WHERE c.n.exist('/p:StmtSimple[@StatementEstRows > 0]') = 1; -DROP TABLE IF EXISTS #query_plan; + UPDATE b + SET b.estimated_rows = er.estimated_rows + FROM #working_warnings AS b + JOIN #est_rows er + ON er.query_hash = b.query_hash + OPTION (RECOMPILE); +END; -CREATE TABLE #query_plan -( - plan_id BIGINT, - query_id BIGINT, - query_hash BINARY(8), - sql_handle VARBINARY(64), - query_plan XML, - INDEX qp_ix_ids CLUSTERED (plan_id, query_id, query_hash, sql_handle) -); +/*Begin plan cost calculations*/ +RAISERROR(N'Gathering statement costs', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +INSERT #plan_cost WITH (TABLOCK) + ( query_plan_cost, sql_handle, plan_id ) +SELECT DISTINCT + s.statement.value('sum(/p:StmtSimple/@StatementSubTreeCost)', 'float') query_plan_cost, + s.sql_handle, + s.plan_id +FROM #statements s +OUTER APPLY s.statement.nodes('/p:StmtSimple') AS q(n) +WHERE s.statement.value('sum(/p:StmtSimple/@StatementSubTreeCost)', 'float') > 0 +OPTION (RECOMPILE); -DROP TABLE IF EXISTS #relop; -CREATE TABLE #relop -( - plan_id BIGINT, - query_id BIGINT, - query_hash BINARY(8), - sql_handle VARBINARY(64), - relop XML, - INDEX ix_ids CLUSTERED (plan_id, query_id, query_hash, sql_handle) -); +RAISERROR(N'Updating statement costs', 0, 1) WITH NOWAIT; +WITH pc AS ( + SELECT SUM(DISTINCT pc.query_plan_cost) AS queryplancostsum, pc.sql_handle, pc.plan_id + FROM #plan_cost AS pc + GROUP BY pc.sql_handle, pc.plan_id + ) + UPDATE b + SET b.query_cost = ISNULL(pc.queryplancostsum, 0) + FROM #working_warnings AS b + JOIN pc + ON pc.sql_handle = b.sql_handle + AND pc.plan_id = b.plan_id +OPTION (RECOMPILE); -DROP TABLE IF EXISTS #plan_cost; +/*End plan cost calculations*/ -CREATE TABLE #plan_cost -( - query_plan_cost DECIMAL(38,2), - sql_handle VARBINARY(64), - plan_id INT, - INDEX px_ix_ids CLUSTERED (sql_handle, plan_id) -); +RAISERROR(N'Checking for plan warnings', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +UPDATE b +SET b.plan_warnings = 1 +FROM #query_plan qp +JOIN #working_warnings b +ON qp.sql_handle = b.sql_handle +AND qp.query_plan.exist('/p:QueryPlan/p:Warnings') = 1 +OPTION (RECOMPILE); -DROP TABLE IF EXISTS #est_rows; -CREATE TABLE #est_rows -( - estimated_rows DECIMAL(38,2), - query_hash BINARY(8), - INDEX px_ix_ids CLUSTERED (query_hash) -); +RAISERROR(N'Checking for implicit conversion', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +UPDATE b +SET b.implicit_conversions = 1 +FROM #query_plan qp +JOIN #working_warnings b +ON qp.sql_handle = b.sql_handle +AND qp.query_plan.exist('/p:QueryPlan/p:Warnings/p:PlanAffectingConvert/@Expression[contains(., "CONVERT_IMPLICIT")]') = 1 +OPTION (RECOMPILE); +IF @ExpertMode > 0 +BEGIN +RAISERROR(N'Performing busy loops checks', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +UPDATE p +SET busy_loops = CASE WHEN (x.estimated_executions / 100.0) > x.estimated_rows THEN 1 END +FROM #working_warnings p + JOIN ( + SELECT qs.sql_handle, + relop.value('sum(/p:RelOp/@EstimateRows)', 'float') AS estimated_rows , + relop.value('sum(/p:RelOp/@EstimateRewinds)', 'float') + relop.value('sum(/p:RelOp/@EstimateRebinds)', 'float') + 1.0 AS estimated_executions + FROM #relop qs + ) AS x ON p.sql_handle = x.sql_handle +OPTION (RECOMPILE); +END; -DROP TABLE IF EXISTS #stats_agg; -CREATE TABLE #stats_agg -( - sql_handle VARBINARY(64), - last_update DATETIME2, - modification_count BIGINT, - sampling_percent DECIMAL(38, 2), - [statistics] NVARCHAR(258), - [table] NVARCHAR(258), - [schema] NVARCHAR(258), - [database] NVARCHAR(258), - INDEX sa_ix_ids CLUSTERED (sql_handle) -); +RAISERROR(N'Performing TVF join check', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +UPDATE p +SET p.tvf_join = CASE WHEN x.tvf_join = 1 THEN 1 END +FROM #working_warnings p + JOIN ( + SELECT r.sql_handle, + 1 AS tvf_join + FROM #relop AS r + WHERE r.relop.exist('//p:RelOp[(@LogicalOp[.="Table-valued function"])]') = 1 + AND r.relop.exist('//p:RelOp[contains(@LogicalOp, "Join")]') = 1 + ) AS x ON p.sql_handle = x.sql_handle +OPTION (RECOMPILE); +IF @ExpertMode > 0 +BEGIN +RAISERROR(N'Checking for operator warnings', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +, x AS ( +SELECT r.sql_handle, + c.n.exist('//p:Warnings[(@NoJoinPredicate[.="1"])]') AS warning_no_join_predicate, + c.n.exist('//p:ColumnsWithNoStatistics') AS no_stats_warning , + c.n.exist('//p:Warnings') AS relop_warnings +FROM #relop AS r +CROSS APPLY r.relop.nodes('/p:RelOp/p:Warnings') AS c(n) +) +UPDATE b +SET b.warning_no_join_predicate = x.warning_no_join_predicate, + b.no_stats_warning = x.no_stats_warning, + b.relop_warnings = x.relop_warnings +FROM #working_warnings b +JOIN x ON x.sql_handle = b.sql_handle +OPTION (RECOMPILE); +END; -DROP TABLE IF EXISTS #trace_flags; -CREATE TABLE #trace_flags -( - sql_handle VARBINARY(54), - global_trace_flags NVARCHAR(4000), - session_trace_flags NVARCHAR(4000), - INDEX tf_ix_ids CLUSTERED (sql_handle) -); +RAISERROR(N'Checking for table variables', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +, x AS ( +SELECT r.sql_handle, + c.n.value('substring(@Table, 2, 1)','VARCHAR(100)') AS first_char +FROM #relop r +CROSS APPLY r.relop.nodes('//p:Object') AS c(n) +) +UPDATE b +SET b.is_table_variable = 1 +FROM #working_warnings b +JOIN x ON x.sql_handle = b.sql_handle +JOIN #working_metrics AS wm +ON b.plan_id = wm.plan_id +AND b.query_id = wm.query_id +AND wm.batch_sql_handle IS NOT NULL +WHERE x.first_char = '@' +OPTION (RECOMPILE); -DROP TABLE IF EXISTS #warning_results; +IF @ExpertMode > 0 +BEGIN +RAISERROR(N'Checking for functions', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +, x AS ( +SELECT r.sql_handle, + n.fn.value('count(distinct-values(//p:UserDefinedFunction[not(@IsClrFunction)]))', 'INT') AS function_count, + n.fn.value('count(distinct-values(//p:UserDefinedFunction[@IsClrFunction = "1"]))', 'INT') AS clr_function_count +FROM #relop r +CROSS APPLY r.relop.nodes('/p:RelOp/p:ComputeScalar/p:DefinedValues/p:DefinedValue/p:ScalarOperator') n(fn) +) +UPDATE b +SET b.function_count = x.function_count, + b.clr_function_count = x.clr_function_count +FROM #working_warnings b +JOIN x ON x.sql_handle = b.sql_handle +OPTION (RECOMPILE); +END; -CREATE TABLE #warning_results -( - ID INT IDENTITY(1,1) PRIMARY KEY CLUSTERED, - CheckID INT, - Priority TINYINT, - FindingsGroup NVARCHAR(50), - Finding NVARCHAR(200), - URL NVARCHAR(200), - Details NVARCHAR(4000) -); -/*These next three tables hold information about implicit conversion and cached parameters */ -DROP TABLE IF EXISTS #stored_proc_info; +RAISERROR(N'Checking for expensive key lookups', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +UPDATE b +SET b.key_lookup_cost = x.key_lookup_cost +FROM #working_warnings b +JOIN ( +SELECT + r.sql_handle, + MAX(r.relop.value('sum(/p:RelOp/@EstimatedTotalSubtreeCost)', 'float')) AS key_lookup_cost +FROM #relop r +WHERE r.relop.exist('/p:RelOp/p:IndexScan[(@Lookup[.="1"])]') = 1 +GROUP BY r.sql_handle +) AS x ON x.sql_handle = b.sql_handle +OPTION (RECOMPILE); -CREATE TABLE #stored_proc_info -( - sql_handle VARBINARY(64), - query_hash BINARY(8), - variable_name NVARCHAR(258), - variable_datatype NVARCHAR(258), - converted_column_name NVARCHAR(258), - compile_time_value NVARCHAR(258), - proc_name NVARCHAR(1000), - column_name NVARCHAR(4000), - converted_to NVARCHAR(258), - set_options NVARCHAR(1000) - INDEX tf_ix_ids CLUSTERED (sql_handle, query_hash) -); -DROP TABLE IF EXISTS #variable_info; +RAISERROR(N'Checking for expensive remote queries', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +UPDATE b +SET b.remote_query_cost = x.remote_query_cost +FROM #working_warnings b +JOIN ( +SELECT + r.sql_handle, + MAX(r.relop.value('sum(/p:RelOp/@EstimatedTotalSubtreeCost)', 'float')) AS remote_query_cost +FROM #relop r +WHERE r.relop.exist('/p:RelOp[(@PhysicalOp[contains(., "Remote")])]') = 1 +GROUP BY r.sql_handle +) AS x ON x.sql_handle = b.sql_handle +OPTION (RECOMPILE); -CREATE TABLE #variable_info -( - query_hash BINARY(8), - sql_handle VARBINARY(64), - proc_name NVARCHAR(1000), - variable_name NVARCHAR(258), - variable_datatype NVARCHAR(258), - compile_time_value NVARCHAR(258), - INDEX vif_ix_ids CLUSTERED (sql_handle, query_hash) -); -DROP TABLE IF EXISTS #conversion_info; +RAISERROR(N'Checking for expensive sorts', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +UPDATE b +SET sort_cost = y.max_sort_cost +FROM #working_warnings b +JOIN ( + SELECT x.sql_handle, MAX((x.sort_io + x.sort_cpu)) AS max_sort_cost + FROM ( + SELECT + qs.sql_handle, + relop.value('sum(/p:RelOp/@EstimateIO)', 'float') AS sort_io, + relop.value('sum(/p:RelOp/@EstimateCPU)', 'float') AS sort_cpu + FROM #relop qs + WHERE [relop].exist('/p:RelOp[(@PhysicalOp[.="Sort"])]') = 1 + ) AS x + GROUP BY x.sql_handle + ) AS y +ON b.sql_handle = y.sql_handle +OPTION (RECOMPILE); -CREATE TABLE #conversion_info -( - query_hash BINARY(8), - sql_handle VARBINARY(64), - proc_name NVARCHAR(128), - expression NVARCHAR(4000), - at_charindex AS CHARINDEX('@', expression), - bracket_charindex AS CHARINDEX(']', expression, CHARINDEX('@', expression)) - CHARINDEX('@', expression), - comma_charindex AS CHARINDEX(',', expression) + 1, - second_comma_charindex AS - CHARINDEX(',', expression, CHARINDEX(',', expression) + 1) - CHARINDEX(',', expression) - 1, - equal_charindex AS CHARINDEX('=', expression) + 1, - paren_charindex AS CHARINDEX('(', expression) + 1, - comma_paren_charindex AS - CHARINDEX(',', expression, CHARINDEX('(', expression) + 1) - CHARINDEX('(', expression) - 1, - convert_implicit_charindex AS CHARINDEX('=CONVERT_IMPLICIT', expression), - INDEX cif_ix_ids CLUSTERED (sql_handle, query_hash) -); +IF NOT EXISTS(SELECT 1/0 FROM #statements AS s WHERE s.is_cursor = 1) +BEGIN -/* These tables support the Missing Index details clickable*/ +RAISERROR(N'No cursor plans found, skipping', 0, 1) WITH NOWAIT; +END -DROP TABLE IF EXISTS #missing_index_xml; +IF EXISTS(SELECT 1/0 FROM #statements AS s WHERE s.is_cursor = 1) +BEGIN -CREATE TABLE #missing_index_xml -( - query_hash BINARY(8), - sql_handle VARBINARY(64), - impact FLOAT, - index_xml XML, - INDEX mix_ix_ids CLUSTERED (sql_handle, query_hash) -); +RAISERROR(N'Cursor plans found, investigating', 0, 1) WITH NOWAIT; -DROP TABLE IF EXISTS #missing_index_schema; +RAISERROR(N'Checking for Optimistic cursors', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +UPDATE b +SET b.is_optimistic_cursor = 1 +FROM #working_warnings b +JOIN #statements AS s +ON b.sql_handle = s.sql_handle +CROSS APPLY s.statement.nodes('/p:StmtCursor') AS n1(fn) +WHERE n1.fn.exist('//p:CursorPlan/@CursorConcurrency[.="Optimistic"]') = 1 +AND s.is_cursor = 1 +OPTION (RECOMPILE); -CREATE TABLE #missing_index_schema -( - query_hash BINARY(8), - sql_handle VARBINARY(64), - impact FLOAT, - database_name NVARCHAR(128), - schema_name NVARCHAR(128), - table_name NVARCHAR(128), - index_xml XML, - INDEX mis_ix_ids CLUSTERED (sql_handle, query_hash) -); +RAISERROR(N'Checking if cursor is Forward Only', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +UPDATE b +SET b.is_forward_only_cursor = 1 +FROM #working_warnings b +JOIN #statements AS s +ON b.sql_handle = s.sql_handle +CROSS APPLY s.statement.nodes('/p:StmtCursor') AS n1(fn) +WHERE n1.fn.exist('//p:CursorPlan/@ForwardOnly[.="true"]') = 1 +AND s.is_cursor = 1 +OPTION (RECOMPILE); -DROP TABLE IF EXISTS #missing_index_usage; -CREATE TABLE #missing_index_usage -( - query_hash BINARY(8), - sql_handle VARBINARY(64), - impact FLOAT, - database_name NVARCHAR(128), - schema_name NVARCHAR(128), - table_name NVARCHAR(128), - usage NVARCHAR(128), - index_xml XML, - INDEX miu_ix_ids CLUSTERED (sql_handle, query_hash) -); +RAISERROR(N'Checking if cursor is Fast Forward', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +UPDATE b +SET b.is_fast_forward_cursor = 1 +FROM #working_warnings b +JOIN #statements AS s +ON b.sql_handle = s.sql_handle +CROSS APPLY s.statement.nodes('/p:StmtCursor') AS n1(fn) +WHERE n1.fn.exist('//p:CursorPlan/@CursorActualType[.="FastForward"]') = 1 +AND s.is_cursor = 1 +OPTION (RECOMPILE); -DROP TABLE IF EXISTS #missing_index_detail; -CREATE TABLE #missing_index_detail -( - query_hash BINARY(8), - sql_handle VARBINARY(64), - impact FLOAT, - database_name NVARCHAR(128), - schema_name NVARCHAR(128), - table_name NVARCHAR(128), - usage NVARCHAR(128), - column_name NVARCHAR(128), - INDEX mid_ix_ids CLUSTERED (sql_handle, query_hash) -); +RAISERROR(N'Checking for Dynamic cursors', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +UPDATE b +SET b.is_cursor_dynamic = 1 +FROM #working_warnings b +JOIN #statements AS s +ON b.sql_handle = s.sql_handle +CROSS APPLY s.statement.nodes('/p:StmtCursor') AS n1(fn) +WHERE n1.fn.exist('//p:CursorPlan/@CursorActualType[.="Dynamic"]') = 1 +AND s.is_cursor = 1 +OPTION (RECOMPILE); +END -DROP TABLE IF EXISTS #missing_index_pretty; +IF @ExpertMode > 0 +BEGIN +RAISERROR(N'Checking for bad scans and plan forcing', 0, 1) WITH NOWAIT; +;WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +UPDATE b +SET +b.is_table_scan = x.is_table_scan, +b.backwards_scan = x.backwards_scan, +b.forced_index = x.forced_index, +b.forced_seek = x.forced_seek, +b.forced_scan = x.forced_scan +FROM #working_warnings b +JOIN ( +SELECT + r.sql_handle, + 0 AS is_table_scan, + q.n.exist('@ScanDirection[.="BACKWARD"]') AS backwards_scan, + q.n.value('@ForcedIndex', 'bit') AS forced_index, + q.n.value('@ForceSeek', 'bit') AS forced_seek, + q.n.value('@ForceScan', 'bit') AS forced_scan +FROM #relop r +CROSS APPLY r.relop.nodes('//p:IndexScan') AS q(n) +UNION ALL +SELECT + r.sql_handle, + 1 AS is_table_scan, + q.n.exist('@ScanDirection[.="BACKWARD"]') AS backwards_scan, + q.n.value('@ForcedIndex', 'bit') AS forced_index, + q.n.value('@ForceSeek', 'bit') AS forced_seek, + q.n.value('@ForceScan', 'bit') AS forced_scan +FROM #relop r +CROSS APPLY r.relop.nodes('//p:TableScan') AS q(n) +) AS x ON b.sql_handle = x.sql_handle +OPTION (RECOMPILE); +END; -CREATE TABLE #missing_index_pretty -( - query_hash BINARY(8), - sql_handle VARBINARY(64), - impact FLOAT, - database_name NVARCHAR(128), - schema_name NVARCHAR(128), - table_name NVARCHAR(128), - equality NVARCHAR(MAX), - inequality NVARCHAR(MAX), - [include] NVARCHAR(MAX), - is_spool BIT, - details AS N'/* ' - + CHAR(10) - + CASE is_spool - WHEN 0 - THEN N'The Query Processor estimates that implementing the ' - ELSE N'We estimate that implementing the ' - END - + CONVERT(NVARCHAR(30), impact) - + '%.' - + CHAR(10) - + N'*/' - + CHAR(10) + CHAR(13) - + N'/* ' - + CHAR(10) - + N'USE ' - + database_name - + CHAR(10) - + N'GO' - + CHAR(10) + CHAR(13) - + N'CREATE NONCLUSTERED INDEX ix_' - + ISNULL(REPLACE(REPLACE(REPLACE(equality,'[', ''), ']', ''), ', ', '_'), '') - + ISNULL(REPLACE(REPLACE(REPLACE(inequality,'[', ''), ']', ''), ', ', '_'), '') - + CASE WHEN [include] IS NOT NULL THEN + N'_Includes' ELSE N'' END - + CHAR(10) - + N' ON ' - + schema_name - + N'.' - + table_name - + N' (' + - + CASE WHEN equality IS NOT NULL - THEN equality - + CASE WHEN inequality IS NOT NULL - THEN N', ' + inequality - ELSE N'' - END - ELSE inequality - END - + N')' - + CHAR(10) - + CASE WHEN include IS NOT NULL - THEN N'INCLUDE (' + include + N') WITH (FILLFACTOR=100, ONLINE=?, SORT_IN_TEMPDB=?, DATA_COMPRESSION=?);' - ELSE N' WITH (FILLFACTOR=100, ONLINE=?, SORT_IN_TEMPDB=?, DATA_COMPRESSION=?);' - END - + CHAR(10) - + N'GO' - + CHAR(10) - + N'*/', - INDEX mip_ix_ids CLUSTERED (sql_handle, query_hash) -); -DROP TABLE IF EXISTS #index_spool_ugly; +IF @ExpertMode > 0 +BEGIN +RAISERROR(N'Checking for computed columns that reference scalar UDFs', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +UPDATE b +SET b.is_computed_scalar = x.computed_column_function +FROM #working_warnings b +JOIN ( +SELECT r.sql_handle, + n.fn.value('count(distinct-values(//p:UserDefinedFunction[not(@IsClrFunction)]))', 'INT') AS computed_column_function +FROM #relop r +CROSS APPLY r.relop.nodes('/p:RelOp/p:ComputeScalar/p:DefinedValues/p:DefinedValue/p:ScalarOperator') n(fn) +WHERE n.fn.exist('/p:RelOp/p:ComputeScalar/p:DefinedValues/p:DefinedValue/p:ColumnReference[(@ComputedColumn[.="1"])]') = 1 +) AS x ON x.sql_handle = b.sql_handle +OPTION (RECOMPILE); +END; -CREATE TABLE #index_spool_ugly -( - query_hash BINARY(8), - sql_handle VARBINARY(64), - impact FLOAT, - database_name NVARCHAR(128), - schema_name NVARCHAR(128), - table_name NVARCHAR(128), - equality NVARCHAR(MAX), - inequality NVARCHAR(MAX), - [include] NVARCHAR(MAX), - INDEX isu_ix_ids CLUSTERED (sql_handle, query_hash) -); +RAISERROR(N'Checking for filters that reference scalar UDFs', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +UPDATE b +SET b.is_computed_filter = x.filter_function +FROM #working_warnings b +JOIN ( +SELECT +r.sql_handle, +c.n.value('count(distinct-values(//p:UserDefinedFunction[not(@IsClrFunction)]))', 'INT') AS filter_function +FROM #relop AS r +CROSS APPLY r.relop.nodes('/p:RelOp/p:Filter/p:Predicate/p:ScalarOperator/p:Compare/p:ScalarOperator/p:UserDefinedFunction') c(n) +) x ON x.sql_handle = b.sql_handle +OPTION (RECOMPILE); -/*Sets up WHERE clause that gets used quite a bit*/ ---Date stuff ---If they're both NULL, we'll just look at the last 7 days -IF (@StartDate IS NULL AND @EndDate IS NULL) - BEGIN - RAISERROR(N'@StartDate and @EndDate are NULL, checking last 7 days', 0, 1) WITH NOWAIT; - SET @sql_where += N' AND qsrs.last_execution_time >= DATEADD(DAY, -7, DATEDIFF(DAY, 0, SYSDATETIME() )) - '; - END; +IF @ExpertMode > 0 +BEGIN +RAISERROR(N'Checking modification queries that hit lots of indexes', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p), +IndexOps AS +( + SELECT + r.query_hash, + c.n.value('@PhysicalOp', 'VARCHAR(100)') AS op_name, + c.n.exist('@PhysicalOp[.="Index Insert"]') AS ii, + c.n.exist('@PhysicalOp[.="Index Update"]') AS iu, + c.n.exist('@PhysicalOp[.="Index Delete"]') AS id, + c.n.exist('@PhysicalOp[.="Clustered Index Insert"]') AS cii, + c.n.exist('@PhysicalOp[.="Clustered Index Update"]') AS ciu, + c.n.exist('@PhysicalOp[.="Clustered Index Delete"]') AS cid, + c.n.exist('@PhysicalOp[.="Table Insert"]') AS ti, + c.n.exist('@PhysicalOp[.="Table Update"]') AS tu, + c.n.exist('@PhysicalOp[.="Table Delete"]') AS td + FROM #relop AS r + CROSS APPLY r.relop.nodes('/p:RelOp') c(n) + OUTER APPLY r.relop.nodes('/p:RelOp/p:ScalarInsert/p:Object') q(n) + OUTER APPLY r.relop.nodes('/p:RelOp/p:Update/p:Object') o2(n) + OUTER APPLY r.relop.nodes('/p:RelOp/p:SimpleUpdate/p:Object') o3(n) +), iops AS +( + SELECT ios.query_hash, + SUM(CONVERT(TINYINT, ios.ii)) AS index_insert_count, + SUM(CONVERT(TINYINT, ios.iu)) AS index_update_count, + SUM(CONVERT(TINYINT, ios.id)) AS index_delete_count, + SUM(CONVERT(TINYINT, ios.cii)) AS cx_insert_count, + SUM(CONVERT(TINYINT, ios.ciu)) AS cx_update_count, + SUM(CONVERT(TINYINT, ios.cid)) AS cx_delete_count, + SUM(CONVERT(TINYINT, ios.ti)) AS table_insert_count, + SUM(CONVERT(TINYINT, ios.tu)) AS table_update_count, + SUM(CONVERT(TINYINT, ios.td)) AS table_delete_count + FROM IndexOps AS ios + WHERE ios.op_name IN ('Index Insert', 'Index Delete', 'Index Update', + 'Clustered Index Insert', 'Clustered Index Delete', 'Clustered Index Update', + 'Table Insert', 'Table Delete', 'Table Update') + GROUP BY ios.query_hash) +UPDATE b +SET b.index_insert_count = iops.index_insert_count, + b.index_update_count = iops.index_update_count, + b.index_delete_count = iops.index_delete_count, + b.cx_insert_count = iops.cx_insert_count, + b.cx_update_count = iops.cx_update_count, + b.cx_delete_count = iops.cx_delete_count, + b.table_insert_count = iops.table_insert_count, + b.table_update_count = iops.table_update_count, + b.table_delete_count = iops.table_delete_count +FROM #working_warnings AS b +JOIN iops ON iops.query_hash = b.query_hash +OPTION (RECOMPILE); +END; ---Hey, that's nice of me -IF @StartDate IS NOT NULL - BEGIN - RAISERROR(N'Setting start date filter', 0, 1) WITH NOWAIT; - SET @sql_where += N' AND qsrs.last_execution_time >= @sp_StartDate - '; - END; ---Alright, sensible -IF @EndDate IS NOT NULL - BEGIN - RAISERROR(N'Setting end date filter', 0, 1) WITH NOWAIT; - SET @sql_where += N' AND qsrs.last_execution_time < @sp_EndDate - '; - END; +IF @ExpertMode > 0 +BEGIN +RAISERROR(N'Checking for Spatial index use', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +UPDATE b +SET b.is_spatial = x.is_spatial +FROM #working_warnings AS b +JOIN ( +SELECT r.sql_handle, + 1 AS is_spatial +FROM #relop r +CROSS APPLY r.relop.nodes('/p:RelOp//p:Object') n(fn) +WHERE n.fn.exist('(@IndexKind[.="Spatial"])') = 1 +) AS x ON x.sql_handle = b.sql_handle +OPTION (RECOMPILE); +END; ---C'mon, why would you do that? -IF (@StartDate IS NULL AND @EndDate IS NOT NULL) - BEGIN - RAISERROR(N'Setting reasonable start date filter', 0, 1) WITH NOWAIT; - SET @sql_where += N' AND qsrs.last_execution_time >= DATEADD(DAY, -7, @sp_EndDate) - '; - END; +RAISERROR(N'Checking for forced serialization', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +UPDATE b +SET b.is_forced_serial = 1 +FROM #query_plan qp +JOIN #working_warnings AS b +ON qp.sql_handle = b.sql_handle +AND b.is_parallel IS NULL +AND qp.query_plan.exist('/p:QueryPlan/@NonParallelPlanReason') = 1 +OPTION (RECOMPILE); ---Jeez, abusive -IF (@StartDate IS NOT NULL AND @EndDate IS NULL) - BEGIN - RAISERROR(N'Setting reasonable end date filter', 0, 1) WITH NOWAIT; - SET @sql_where += N' AND qsrs.last_execution_time < DATEADD(DAY, 7, @sp_StartDate) - '; - END; ---I care about minimum execution counts -IF @MinimumExecutionCount IS NOT NULL - BEGIN - RAISERROR(N'Setting execution filter', 0, 1) WITH NOWAIT; - SET @sql_where += N' AND qsrs.count_executions >= @sp_MinimumExecutionCount - '; - END; +IF @ExpertMode > 0 +BEGIN +RAISERROR(N'Checking for ColumnStore queries operating in Row Mode instead of Batch Mode', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +UPDATE b +SET b.columnstore_row_mode = x.is_row_mode +FROM #working_warnings AS b +JOIN ( +SELECT + r.sql_handle, + r.relop.exist('/p:RelOp[(@EstimatedExecutionMode[.="Row"])]') AS is_row_mode +FROM #relop r +WHERE r.relop.exist('/p:RelOp/p:IndexScan[(@Storage[.="ColumnStore"])]') = 1 +) AS x ON x.sql_handle = b.sql_handle +OPTION (RECOMPILE); +END; ---You care about stored proc names -IF @StoredProcName IS NOT NULL - BEGIN - RAISERROR(N'Setting stored proc filter', 0, 1) WITH NOWAIT; - SET @sql_where += N' AND object_name(qsq.object_id, DB_ID(' + QUOTENAME(@DatabaseName, '''') + N')) = @sp_StoredProcName - '; - END; ---I will always love you, but hopefully this query will eventually end -IF @DurationFilter IS NOT NULL - BEGIN - RAISERROR(N'Setting duration filter', 0, 1) WITH NOWAIT; - SET @sql_where += N' AND (qsrs.avg_duration / 1000.) >= @sp_MinDuration - '; - END; +IF @ExpertMode > 0 +BEGIN +RAISERROR('Checking for row level security only', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +UPDATE b +SET b.is_row_level = 1 +FROM #working_warnings b +JOIN #statements s +ON s.query_hash = b.query_hash +WHERE s.statement.exist('/p:StmtSimple/@SecurityPolicyApplied[.="true"]') = 1 +OPTION (RECOMPILE); +END; ---I don't know why you'd go looking for failed queries, but hey -IF (@Failed = 0 OR @Failed IS NULL) - BEGIN - RAISERROR(N'Setting failed query filter to 0', 0, 1) WITH NOWAIT; - SET @sql_where += N' AND qsrs.execution_type = 0 - '; - END; -IF (@Failed = 1) - BEGIN - RAISERROR(N'Setting failed query filter to 3, 4', 0, 1) WITH NOWAIT; - SET @sql_where += N' AND qsrs.execution_type IN (3, 4) - '; - END; -/*Filtering for plan_id or query_id*/ -IF (@PlanIdFilter IS NOT NULL) - BEGIN - RAISERROR(N'Setting plan_id filter', 0, 1) WITH NOWAIT; - SET @sql_where += N' AND qsp.plan_id = @sp_PlanIdFilter - '; - END; +IF @ExpertMode > 0 +BEGIN +RAISERROR('Checking for wonky Index Spools', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES ( + 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) +, selects +AS ( SELECT s.plan_id, s.query_id + FROM #statements AS s + WHERE s.statement.exist('/p:StmtSimple/@StatementType[.="SELECT"]') = 1 ) +, spools +AS ( SELECT DISTINCT r.plan_id, + r.query_id, + c.n.value('@EstimateRows', 'FLOAT') AS estimated_rows, + c.n.value('@EstimateIO', 'FLOAT') AS estimated_io, + c.n.value('@EstimateCPU', 'FLOAT') AS estimated_cpu, + c.n.value('@EstimateRebinds', 'FLOAT') AS estimated_rebinds +FROM #relop AS r +JOIN selects AS s +ON s.plan_id = r.plan_id + AND s.query_id = r.query_id +CROSS APPLY r.relop.nodes('/p:RelOp') AS c(n) +WHERE r.relop.exist('/p:RelOp[@PhysicalOp="Index Spool" and @LogicalOp="Eager Spool"]') = 1 +) +UPDATE ww + SET ww.index_spool_rows = sp.estimated_rows, + ww.index_spool_cost = ((sp.estimated_io * sp.estimated_cpu) * CASE WHEN sp.estimated_rebinds < 1 THEN 1 ELSE sp.estimated_rebinds END) -IF (@QueryIdFilter IS NOT NULL) - BEGIN - RAISERROR(N'Setting query_id filter', 0, 1) WITH NOWAIT; - SET @sql_where += N' AND qsq.query_id = @sp_QueryIdFilter - '; - END; +FROM #working_warnings ww +JOIN spools sp +ON ww.plan_id = sp.plan_id +AND ww.query_id = sp.query_id +OPTION (RECOMPILE); +END; -IF @Debug = 1 - RAISERROR(N'Starting WHERE clause:', 0, 1) WITH NOWAIT; - PRINT @sql_where; -IF @sql_where IS NULL - BEGIN - RAISERROR(N'@sql_where is NULL', 0, 1) WITH NOWAIT; - RETURN; - END; +IF (PARSENAME(CONVERT(VARCHAR(128), SERVERPROPERTY ('PRODUCTVERSION')), 4)) >= 14 +OR ((PARSENAME(CONVERT(VARCHAR(128), SERVERPROPERTY ('PRODUCTVERSION')), 4)) = 13 + AND PARSENAME(CONVERT(VARCHAR(128), SERVERPROPERTY ('PRODUCTVERSION')), 2) >= 5026) -IF (@ExportToExcel = 1 OR @SkipXML = 1) - BEGIN - RAISERROR(N'Exporting to Excel or skipping XML, hiding summary', 0, 1) WITH NOWAIT; - SET @HideSummary = 1; - END; +BEGIN -IF @StoredProcName IS NOT NULL - BEGIN - - DECLARE @sql NVARCHAR(MAX); - DECLARE @out INT; - DECLARE @proc_params NVARCHAR(MAX) = N'@sp_StartDate DATETIME2, @sp_EndDate DATETIME2, @sp_MinimumExecutionCount INT, @sp_MinDuration INT, @sp_StoredProcName NVARCHAR(128), @sp_PlanIdFilter INT, @sp_QueryIdFilter INT, @i_out INT OUTPUT'; - - - SET @sql = N'SELECT @i_out = COUNT(*) - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp - ON qsp.plan_id = qsrs.plan_id - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq - ON qsq.query_id = qsp.query_id - WHERE 1 = 1 - AND qsq.is_internal_query = 0 - AND qsp.query_plan IS NOT NULL - '; - - SET @sql += @sql_where; +RAISERROR(N'Beginning 2017 and 2016 SP2 specfic checks', 0, 1) WITH NOWAIT; - EXEC sys.sp_executesql @sql, - @proc_params, - @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter, @i_out = @out OUTPUT; - - IF @out = 0 - BEGIN +IF @ExpertMode > 0 +BEGIN +RAISERROR('Gathering stats information', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +INSERT #stats_agg WITH (TABLOCK) + (sql_handle, last_update, modification_count, sampling_percent, [statistics], [table], [schema], [database]) +SELECT qp.sql_handle, + x.c.value('@LastUpdate', 'DATETIME2(7)') AS LastUpdate, + x.c.value('@ModificationCount', 'BIGINT') AS ModificationCount, + x.c.value('@SamplingPercent', 'FLOAT') AS SamplingPercent, + x.c.value('@Statistics', 'NVARCHAR(258)') AS [Statistics], + x.c.value('@Table', 'NVARCHAR(258)') AS [Table], + x.c.value('@Schema', 'NVARCHAR(258)') AS [Schema], + x.c.value('@Database', 'NVARCHAR(258)') AS [Database] +FROM #query_plan AS qp +CROSS APPLY qp.query_plan.nodes('//p:OptimizerStatsUsage/p:StatisticsInfo') x (c) +OPTION (RECOMPILE); - SET @msg = N'We couldn''t find the Stored Procedure ' + QUOTENAME(@StoredProcName) + N' in the Query Store views for ' + QUOTENAME(@DatabaseName) + N' between ' + CONVERT(NVARCHAR(30), ISNULL(@StartDate, DATEADD(DAY, -7, DATEDIFF(DAY, 0, SYSDATETIME() ))) ) + N' and ' + CONVERT(NVARCHAR(30), ISNULL(@EndDate, SYSDATETIME())) + - '. Try removing schema prefixes or adjusting dates. If it was executed from a different database context, try searching there instead.'; - RAISERROR(@msg, 0, 1) WITH NOWAIT; - SELECT @msg AS [Blue Flowers, Blue Flowers, Blue Flowers]; - - RETURN; - - END; - - END; +RAISERROR('Checking for stale stats', 0, 1) WITH NOWAIT; +WITH stale_stats AS ( + SELECT sa.sql_handle + FROM #stats_agg AS sa + GROUP BY sa.sql_handle + HAVING MAX(sa.last_update) <= DATEADD(DAY, -7, SYSDATETIME()) + AND AVG(sa.modification_count) >= 100000 +) +UPDATE b +SET b.stale_stats = 1 +FROM #working_warnings AS b +JOIN stale_stats os +ON b.sql_handle = os.sql_handle +OPTION (RECOMPILE); +END; +IF (PARSENAME(CONVERT(VARCHAR(128), SERVERPROPERTY ('PRODUCTVERSION')), 4)) >= 14 + AND @ExpertMode > 0 +BEGIN +RAISERROR(N'Checking for Adaptive Joins', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p), +aj AS ( + SELECT r.sql_handle + FROM #relop AS r + CROSS APPLY r.relop.nodes('//p:RelOp') x(c) + WHERE x.c.exist('@IsAdaptive[.=1]') = 1 +) +UPDATE b +SET b.is_adaptive = 1 +FROM #working_warnings AS b +JOIN aj +ON b.sql_handle = aj.sql_handle +OPTION (RECOMPILE); +END; -/* -This is our grouped interval query. +IF @ExpertMode > 0 +BEGIN; +RAISERROR(N'Checking for Row Goals', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p), +row_goals AS( +SELECT qs.query_hash +FROM #relop qs +WHERE relop.value('sum(/p:RelOp/@EstimateRowsWithoutRowGoal)', 'float') > 0 +) +UPDATE b +SET b.is_row_goal = 1 +FROM #working_warnings b +JOIN row_goals +ON b.query_hash = row_goals.query_hash +OPTION (RECOMPILE); +END; -By default, it looks at queries: - In the last 7 days - That aren't system queries - That have a query plan (some won't, if nested level is > 128, along with other reasons) - And haven't failed - This stuff, along with some other options, will be configurable in the stored proc +END; -*/ -IF @sql_where IS NOT NULL -BEGIN TRY - BEGIN +RAISERROR(N'Performing query level checks', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +UPDATE b +SET b.missing_index_count = query_plan.value('count(//p:QueryPlan/p:MissingIndexes/p:MissingIndexGroup)', 'int') , + b.unmatched_index_count = CASE WHEN is_trivial <> 1 THEN query_plan.value('count(//p:QueryPlan/p:UnmatchedIndexes/p:Parameterization/p:Object)', 'int') END +FROM #query_plan qp +JOIN #working_warnings AS b +ON b.query_hash = qp.query_hash +OPTION (RECOMPILE); - RAISERROR(N'Populating temp tables', 0, 1) WITH NOWAIT; -RAISERROR(N'Gathering intervals', 0, 1) WITH NOWAIT; +RAISERROR(N'Trace flag checks', 0, 1) WITH NOWAIT; +;WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +, tf_pretty AS ( +SELECT qp.sql_handle, + q.n.value('@Value', 'INT') AS trace_flag, + q.n.value('@Scope', 'VARCHAR(10)') AS scope +FROM #query_plan qp +CROSS APPLY qp.query_plan.nodes('/p:QueryPlan/p:TraceFlags/p:TraceFlag') AS q(n) +) +INSERT #trace_flags WITH (TABLOCK) + (sql_handle, global_trace_flags, session_trace_flags ) +SELECT DISTINCT tf1.sql_handle , + STUFF(( + SELECT DISTINCT N', ' + CONVERT(NVARCHAR(5), tf2.trace_flag) + FROM tf_pretty AS tf2 + WHERE tf1.sql_handle = tf2.sql_handle + AND tf2.scope = 'Global' + FOR XML PATH(N'')), 1, 2, N'' + ) AS global_trace_flags, + STUFF(( + SELECT DISTINCT N', ' + CONVERT(NVARCHAR(5), tf2.trace_flag) + FROM tf_pretty AS tf2 + WHERE tf1.sql_handle = tf2.sql_handle + AND tf2.scope = 'Session' + FOR XML PATH(N'')), 1, 2, N'' + ) AS session_trace_flags +FROM tf_pretty AS tf1 +OPTION (RECOMPILE); -SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; -SET @sql_select += N' -SELECT CONVERT(DATE, qsrs.last_execution_time) AS flat_date, - MIN(DATEADD(HOUR, DATEDIFF(HOUR, 0, qsrs.last_execution_time), 0)) AS start_range, - MAX(DATEADD(HOUR, DATEDIFF(HOUR, 0, qsrs.last_execution_time) + 1, 0)) AS end_range, - SUM(qsrs.avg_duration / 1000.) / SUM(qsrs.count_executions) AS total_avg_duration_ms, - SUM(qsrs.avg_cpu_time / 1000.) / SUM(qsrs.count_executions) AS total_avg_cpu_time_ms, - SUM((qsrs.avg_logical_io_reads * 8 ) / 1024.) / SUM(qsrs.count_executions) AS total_avg_logical_io_reads_mb, - SUM((qsrs.avg_physical_io_reads* 8 ) / 1024.) / SUM(qsrs.count_executions) AS total_avg_physical_io_reads_mb, - SUM((qsrs.avg_logical_io_writes* 8 ) / 1024.) / SUM(qsrs.count_executions) AS total_avg_logical_io_writes_mb, - SUM((qsrs.avg_query_max_used_memory * 8 ) / 1024.) / SUM(qsrs.count_executions) AS total_avg_query_max_used_memory_mb, - SUM(qsrs.avg_rowcount) AS total_rowcount, - SUM(qsrs.count_executions) AS total_count_executions, - SUM(qsrs.max_duration / 1000.) AS total_max_duration_ms, - SUM(qsrs.max_cpu_time / 1000.) AS total_max_cpu_time_ms, - SUM((qsrs.max_logical_io_reads * 8 ) / 1024.) AS total_max_logical_io_reads_mb, - SUM((qsrs.max_physical_io_reads* 8 ) / 1024.) AS total_max_physical_io_reads_mb, - SUM((qsrs.max_logical_io_writes* 8 ) / 1024.) AS total_max_logical_io_writes_mb, - SUM((qsrs.max_query_max_used_memory * 8 ) / 1024.) AS total_max_query_max_used_memory_mb '; - IF @new_columns = 1 - BEGIN - SET @sql_select += N', - SUM((qsrs.avg_log_bytes_used) / 1048576.) / SUM(qsrs.count_executions) AS total_avg_log_bytes_mb, - SUM(qsrs.avg_tempdb_space_used) / SUM(qsrs.count_executions) AS total_avg_tempdb_space, - SUM((qsrs.max_log_bytes_used) / 1048576.) AS total_max_log_bytes_mb, - SUM(qsrs.max_tempdb_space_used) AS total_max_tempdb_space - '; - END; - IF @new_columns = 0 - BEGIN - SET @sql_select += N', - NULL AS total_avg_log_bytes_mb, - NULL AS total_avg_tempdb_space, - NULL AS total_max_log_bytes_mb, - NULL AS total_max_tempdb_space - '; - END; +UPDATE b +SET b.trace_flags_session = tf.session_trace_flags +FROM #working_warnings AS b +JOIN #trace_flags tf +ON tf.sql_handle = b.sql_handle +OPTION (RECOMPILE); -SET @sql_select += N'FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp - ON qsp.plan_id = qsrs.plan_id - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq - ON qsq.query_id = qsp.query_id - WHERE 1 = 1 - AND qsq.is_internal_query = 0 - AND qsp.query_plan IS NOT NULL - '; +RAISERROR(N'Checking for MSTVFs', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +UPDATE b +SET b.is_mstvf = 1 +FROM #relop AS r +JOIN #working_warnings AS b +ON b.sql_handle = r.sql_handle +WHERE r.relop.exist('/p:RelOp[(@EstimateRows="100" or @EstimateRows="1") and @LogicalOp="Table-valued function"]') = 1 +OPTION (RECOMPILE); +IF @ExpertMode > 0 +BEGIN +RAISERROR(N'Checking for many to many merge joins', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +UPDATE b +SET b.is_mm_join = 1 +FROM #relop AS r +JOIN #working_warnings AS b +ON b.sql_handle = r.sql_handle +WHERE r.relop.exist('/p:RelOp/p:Merge/@ManyToMany[.="1"]') = 1 +OPTION (RECOMPILE); +END; -SET @sql_select += @sql_where; -SET @sql_select += - N'GROUP BY CONVERT(DATE, qsrs.last_execution_time) - OPTION (RECOMPILE); - '; +IF @ExpertMode > 0 +BEGIN +RAISERROR(N'Is Paul White Electric?', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p), +is_paul_white_electric AS ( +SELECT 1 AS [is_paul_white_electric], +r.sql_handle +FROM #relop AS r +CROSS APPLY r.relop.nodes('//p:RelOp') c(n) +WHERE c.n.exist('@PhysicalOp[.="Switch"]') = 1 +) +UPDATE b +SET b.is_paul_white_electric = ipwe.is_paul_white_electric +FROM #working_warnings AS b +JOIN is_paul_white_electric ipwe +ON ipwe.sql_handle = b.sql_handle +OPTION (RECOMPILE); +END; -IF @Debug = 1 - PRINT @sql_select; -IF @sql_select IS NULL - BEGIN - RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; - RETURN; - END; -INSERT #grouped_interval WITH (TABLOCK) - ( flat_date, start_range, end_range, total_avg_duration_ms, - total_avg_cpu_time_ms, total_avg_logical_io_reads_mb, total_avg_physical_io_reads_mb, - total_avg_logical_io_writes_mb, total_avg_query_max_used_memory_mb, total_rowcount, - total_count_executions, total_max_duration_ms, total_max_cpu_time_ms, total_max_logical_io_reads_mb, - total_max_physical_io_reads_mb, total_max_logical_io_writes_mb, total_max_query_max_used_memory_mb, - total_avg_log_bytes_mb, total_avg_tempdb_space, total_max_log_bytes_mb, total_max_tempdb_space ) +RAISERROR(N'Checking for non-sargable predicates', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES ( 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) +, nsarg + AS ( SELECT r.query_hash, 1 AS fn, 0 AS jo, 0 AS lk + FROM #relop AS r + CROSS APPLY r.relop.nodes('/p:RelOp/p:IndexScan/p:Predicate/p:ScalarOperator/p:Compare/p:ScalarOperator') AS ca(x) + WHERE ( ca.x.exist('//p:ScalarOperator/p:Intrinsic/@FunctionName') = 1 + OR ca.x.exist('//p:ScalarOperator/p:IF') = 1 ) + UNION ALL + SELECT r.query_hash, 0 AS fn, 1 AS jo, 0 AS lk + FROM #relop AS r + CROSS APPLY r.relop.nodes('/p:RelOp//p:ScalarOperator') AS ca(x) + WHERE r.relop.exist('/p:RelOp[contains(@LogicalOp, "Join")]') = 1 + AND ca.x.exist('//p:ScalarOperator[contains(@ScalarString, "Expr")]') = 1 + UNION ALL + SELECT r.query_hash, 0 AS fn, 0 AS jo, 1 AS lk + FROM #relop AS r + CROSS APPLY r.relop.nodes('/p:RelOp/p:IndexScan/p:Predicate/p:ScalarOperator') AS ca(x) + CROSS APPLY ca.x.nodes('//p:Const') AS co(x) + WHERE ca.x.exist('//p:ScalarOperator/p:Intrinsic/@FunctionName[.="like"]') = 1 + AND ( ( co.x.value('substring(@ConstValue, 1, 1)', 'VARCHAR(100)') <> 'N' + AND co.x.value('substring(@ConstValue, 2, 1)', 'VARCHAR(100)') = '%' ) + OR ( co.x.value('substring(@ConstValue, 1, 1)', 'VARCHAR(100)') = 'N' + AND co.x.value('substring(@ConstValue, 3, 1)', 'VARCHAR(100)') = '%' ))), + d_nsarg + AS ( SELECT DISTINCT + nsarg.query_hash + FROM nsarg + WHERE nsarg.fn = 1 + OR nsarg.jo = 1 + OR nsarg.lk = 1 ) +UPDATE b +SET b.is_nonsargable = 1 +FROM d_nsarg AS d +JOIN #working_warnings AS b + ON b.query_hash = d.query_hash +OPTION ( RECOMPILE ); -EXEC sys.sp_executesql @stmt = @sql_select, - @params = @sp_params, - @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; + RAISERROR(N'Getting information about implicit conversions and stored proc parameters', 0, 1) WITH NOWAIT; -/* -The next group of queries looks at plans in the ranges we found in the grouped interval query + RAISERROR(N'Getting variable info', 0, 1) WITH NOWAIT; + WITH XMLNAMESPACES ( 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) + INSERT #variable_info ( query_hash, sql_handle, proc_name, variable_name, variable_datatype, compile_time_value ) + SELECT DISTINCT + qp.query_hash, + qp.sql_handle, + b.proc_or_function_name AS proc_name, + q.n.value('@Column', 'NVARCHAR(258)') AS variable_name, + q.n.value('@ParameterDataType', 'NVARCHAR(258)') AS variable_datatype, + q.n.value('@ParameterCompiledValue', 'NVARCHAR(258)') AS compile_time_value + FROM #query_plan AS qp + JOIN #working_warnings AS b + ON (b.query_hash = qp.query_hash AND b.proc_or_function_name = 'adhoc') + OR (b.sql_handle = qp.sql_handle AND b.proc_or_function_name <> 'adhoc') + CROSS APPLY qp.query_plan.nodes('//p:QueryPlan/p:ParameterList/p:ColumnReference') AS q(n) + OPTION (RECOMPILE); -We take the highest value from each metric (duration, cpu, etc) and find the top plans by that metric in the range + RAISERROR(N'Getting conversion info', 0, 1) WITH NOWAIT; + WITH XMLNAMESPACES ( 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) + INSERT #conversion_info ( query_hash, sql_handle, proc_name, expression ) + SELECT DISTINCT + qp.query_hash, + qp.sql_handle, + b.proc_or_function_name AS proc_name, + qq.c.value('@Expression', 'NVARCHAR(4000)') AS expression + FROM #query_plan AS qp + JOIN #working_warnings AS b + ON (b.query_hash = qp.query_hash AND b.proc_or_function_name = 'adhoc') + OR (b.sql_handle = qp.sql_handle AND b.proc_or_function_name <> 'adhoc') + CROSS APPLY qp.query_plan.nodes('//p:QueryPlan/p:Warnings/p:PlanAffectingConvert') AS qq(c) + WHERE qq.c.exist('@ConvertIssue[.="Seek Plan"]') = 1 + AND b.implicit_conversions = 1 + OPTION (RECOMPILE); -They insert into the #working_plans table -*/ + RAISERROR(N'Parsing conversion info', 0, 1) WITH NOWAIT; + INSERT #stored_proc_info ( sql_handle, query_hash, proc_name, variable_name, variable_datatype, converted_column_name, column_name, converted_to, compile_time_value ) + SELECT ci.sql_handle, + ci.query_hash, + ci.proc_name, + CASE WHEN ci.at_charindex > 0 + AND ci.bracket_charindex > 0 + THEN SUBSTRING(ci.expression, ci.at_charindex, ci.bracket_charindex) + ELSE N'**no_variable**' + END AS variable_name, + N'**no_variable**' AS variable_datatype, + CASE WHEN ci.at_charindex = 0 + AND ci.comma_charindex > 0 + AND ci.second_comma_charindex > 0 + THEN SUBSTRING(ci.expression, ci.comma_charindex, ci.second_comma_charindex) + ELSE N'**no_column**' + END AS converted_column_name, + CASE WHEN ci.at_charindex = 0 + AND ci.equal_charindex > 0 + AND ci.convert_implicit_charindex = 0 + THEN SUBSTRING(ci.expression, ci.equal_charindex, 4000) + WHEN ci.at_charindex = 0 + AND (ci.equal_charindex -1) > 0 + AND ci.convert_implicit_charindex > 0 + THEN SUBSTRING(ci.expression, 0, ci.equal_charindex -1) + WHEN ci.at_charindex > 0 + AND ci.comma_charindex > 0 + AND ci.second_comma_charindex > 0 + THEN SUBSTRING(ci.expression, ci.comma_charindex, ci.second_comma_charindex) + ELSE N'**no_column **' + END AS column_name, + CASE WHEN ci.paren_charindex > 0 + 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 + 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 + FROM #conversion_info AS ci + OPTION (RECOMPILE); + RAISERROR(N'Updating variables inserted procs', 0, 1) WITH NOWAIT; + UPDATE sp + SET sp.variable_datatype = vi.variable_datatype, + sp.compile_time_value = vi.compile_time_value + FROM #stored_proc_info AS sp + JOIN #variable_info AS vi + ON (sp.proc_name = 'adhoc' AND sp.query_hash = vi.query_hash) + OR (sp.proc_name <> 'adhoc' AND sp.sql_handle = vi.sql_handle) + AND sp.variable_name = vi.variable_name + OPTION (RECOMPILE); + + + RAISERROR(N'Inserting variables for other procs', 0, 1) WITH NOWAIT; + INSERT #stored_proc_info + ( sql_handle, query_hash, variable_name, variable_datatype, compile_time_value, proc_name ) + SELECT vi.sql_handle, vi.query_hash, vi.variable_name, vi.variable_datatype, vi.compile_time_value, vi.proc_name + FROM #variable_info AS vi + WHERE NOT EXISTS + ( + SELECT * + FROM #stored_proc_info AS sp + WHERE (sp.proc_name = 'adhoc' AND sp.query_hash = vi.query_hash) + OR (sp.proc_name <> 'adhoc' AND sp.sql_handle = vi.sql_handle) + ) + OPTION (RECOMPILE); + + RAISERROR(N'Updating procs', 0, 1) WITH NOWAIT; + UPDATE s + SET s.variable_datatype = CASE WHEN s.variable_datatype LIKE '%(%)%' + THEN LEFT(s.variable_datatype, CHARINDEX('(', s.variable_datatype) - 1) + ELSE s.variable_datatype + END, + s.converted_to = CASE WHEN s.converted_to LIKE '%(%)%' + THEN LEFT(s.converted_to, CHARINDEX('(', s.converted_to) - 1) + ELSE s.converted_to + END, + s.compile_time_value = CASE WHEN s.compile_time_value LIKE '%(%)%' + THEN SUBSTRING(s.compile_time_value, + CHARINDEX('(', s.compile_time_value) + 1, + CHARINDEX(')', s.compile_time_value) - 1 - CHARINDEX('(', s.compile_time_value) + ) + WHEN variable_datatype NOT IN ('bit', 'tinyint', 'smallint', 'int', 'bigint') + AND s.variable_datatype NOT LIKE '%binary%' + AND s.compile_time_value NOT LIKE 'N''%''' + AND s.compile_time_value NOT LIKE '''%''' + AND s.compile_time_value <> s.column_name + AND s.compile_time_value <> '**idk_man**' + THEN QUOTENAME(compile_time_value, '''') + ELSE s.compile_time_value + END + FROM #stored_proc_info AS s + OPTION (RECOMPILE); + + RAISERROR(N'Updating SET options', 0, 1) WITH NOWAIT; + WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) + UPDATE s + SET set_options = set_options.ansi_set_options + FROM #stored_proc_info AS s + JOIN ( + SELECT x.sql_handle, + N'SET ANSI_NULLS ' + CASE WHEN [ANSI_NULLS] = 'true' THEN N'ON ' ELSE N'OFF ' END + NCHAR(10) + + N'SET ANSI_PADDING ' + CASE WHEN [ANSI_PADDING] = 'true' THEN N'ON ' ELSE N'OFF ' END + NCHAR(10) + + N'SET ANSI_WARNINGS ' + CASE WHEN [ANSI_WARNINGS] = 'true' THEN N'ON ' ELSE N'OFF ' END + NCHAR(10) + + N'SET ARITHABORT ' + CASE WHEN [ARITHABORT] = 'true' THEN N'ON ' ELSE N' OFF ' END + NCHAR(10) + + N'SET CONCAT_NULL_YIELDS_NULL ' + CASE WHEN [CONCAT_NULL_YIELDS_NULL] = 'true' THEN N'ON ' ELSE N'OFF ' END + NCHAR(10) + + N'SET NUMERIC_ROUNDABORT ' + CASE WHEN [NUMERIC_ROUNDABORT] = 'true' THEN N'ON ' ELSE N'OFF ' END + NCHAR(10) + + N'SET QUOTED_IDENTIFIER ' + CASE WHEN [QUOTED_IDENTIFIER] = 'true' THEN N'ON ' ELSE N'OFF ' + NCHAR(10) END AS [ansi_set_options] + FROM ( + SELECT + s.sql_handle, + so.o.value('@ANSI_NULLS', 'NVARCHAR(20)') AS [ANSI_NULLS], + so.o.value('@ANSI_PADDING', 'NVARCHAR(20)') AS [ANSI_PADDING], + so.o.value('@ANSI_WARNINGS', 'NVARCHAR(20)') AS [ANSI_WARNINGS], + so.o.value('@ARITHABORT', 'NVARCHAR(20)') AS [ARITHABORT], + so.o.value('@CONCAT_NULL_YIELDS_NULL', 'NVARCHAR(20)') AS [CONCAT_NULL_YIELDS_NULL], + so.o.value('@NUMERIC_ROUNDABORT', 'NVARCHAR(20)') AS [NUMERIC_ROUNDABORT], + so.o.value('@QUOTED_IDENTIFIER', 'NVARCHAR(20)') AS [QUOTED_IDENTIFIER] + FROM #statements AS s + CROSS APPLY s.statement.nodes('//p:StatementSetOptions') AS so(o) + ) AS x + ) AS set_options ON set_options.sql_handle = s.sql_handle + OPTION(RECOMPILE); -/*Get longest duration plans*/ -RAISERROR(N'Gathering longest duration plans', 0, 1) WITH NOWAIT; + RAISERROR(N'Updating conversion XML', 0, 1) WITH NOWAIT; + WITH precheck AS ( + SELECT spi.sql_handle, + spi.proc_name, + (SELECT CASE WHEN spi.proc_name <> 'Statement' + THEN N'The stored procedure ' + spi.proc_name + ELSE N'This ad hoc statement' + END + + N' had the following implicit conversions: ' + + CHAR(10) + + STUFF(( + SELECT DISTINCT + @cr + @lf + + CASE WHEN spi2.variable_name <> N'**no_variable**' + THEN N'The variable ' + WHEN spi2.variable_name = N'**no_variable**' AND (spi2.column_name = spi2.converted_column_name OR spi2.column_name LIKE '%CONVERT_IMPLICIT%') + THEN N'The compiled value ' + WHEN spi2.column_name LIKE '%Expr%' + THEN 'The expression ' + ELSE N'The column ' + END + + CASE WHEN spi2.variable_name <> N'**no_variable**' + THEN spi2.variable_name + WHEN spi2.variable_name = N'**no_variable**' AND (spi2.column_name = spi2.converted_column_name OR spi2.column_name LIKE '%CONVERT_IMPLICIT%') + THEN spi2.compile_time_value + + ELSE spi2.column_name + END + + N' has a data type of ' + + CASE WHEN spi2.variable_datatype = N'**no_variable**' THEN spi2.converted_to + ELSE spi2.variable_datatype + END + + N' which caused implicit conversion on the column ' + + CASE WHEN spi2.column_name LIKE N'%CONVERT_IMPLICIT%' + THEN spi2.converted_column_name + WHEN spi2.column_name = N'**no_column**' + THEN spi2.converted_column_name + WHEN spi2.converted_column_name = N'**no_column**' + THEN spi2.column_name + WHEN spi2.column_name <> spi2.converted_column_name + THEN spi2.converted_column_name + ELSE spi2.column_name + END + + CASE WHEN spi2.variable_name = N'**no_variable**' AND (spi2.column_name = spi2.converted_column_name OR spi2.column_name LIKE '%CONVERT_IMPLICIT%') + THEN N'' + WHEN spi2.column_name LIKE '%Expr%' + THEN N'' + WHEN spi2.compile_time_value NOT IN ('**declared in proc**', '**idk_man**') + AND spi2.compile_time_value <> spi2.column_name + THEN ' with the value ' + RTRIM(spi2.compile_time_value) + ELSE N'' + END + + '.' + FROM #stored_proc_info AS spi2 + WHERE spi.sql_handle = spi2.sql_handle + FOR XML PATH(N''), TYPE).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 1, N'') + AS [processing-instruction(ClickMe)] FOR XML PATH(''), TYPE ) + AS implicit_conversion_info + FROM #stored_proc_info AS spi + GROUP BY spi.sql_handle, spi.proc_name + ) + UPDATE b + SET b.implicit_conversion_info = pk.implicit_conversion_info + FROM #working_warnings AS b + JOIN precheck AS pk + ON pk.sql_handle = b.sql_handle + OPTION (RECOMPILE); -SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; -SET @sql_select += N' -WITH duration_max -AS ( SELECT TOP 1 - gi.start_range, - gi.end_range - FROM #grouped_interval AS gi - ORDER BY gi.total_avg_duration_ms DESC ) -INSERT #working_plans WITH (TABLOCK) - ( plan_id, query_id, pattern ) -SELECT TOP ( @sp_Top ) - qsp.plan_id, qsp.query_id, ''avg duration'' -FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp -JOIN duration_max AS dm -ON qsp.last_execution_time >= dm.start_range - AND qsp.last_execution_time < dm.end_range -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs -ON qsrs.plan_id = qsp.plan_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq -ON qsq.query_id = qsp.query_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt -ON qsqt.query_text_id = qsq.query_text_id -WHERE 1 = 1 - AND qsq.is_internal_query = 0 - AND qsp.query_plan IS NOT NULL - '; + RAISERROR(N'Updating cached parameter XML for procs', 0, 1) WITH NOWAIT; + WITH precheck AS ( + SELECT spi.sql_handle, + spi.proc_name, + (SELECT set_options + + @cr + @lf + + @cr + @lf + + N'EXEC ' + + spi.proc_name + + N' ' + + STUFF(( + SELECT DISTINCT N', ' + + CASE WHEN spi2.variable_name <> N'**no_variable**' AND spi2.compile_time_value <> N'**idk_man**' + THEN spi2.variable_name + N' = ' + ELSE @cr + @lf + N' We could not find any cached parameter values for this stored proc. ' + END + + CASE WHEN spi2.variable_name = N'**no_variable**' OR spi2.compile_time_value = N'**idk_man**' + THEN @cr + @lf + N' Possible reasons include declared variables inside the procedure, recompile hints, etc. ' + WHEN spi2.compile_time_value = N'NULL' + THEN spi2.compile_time_value + ELSE RTRIM(spi2.compile_time_value) + END + FROM #stored_proc_info AS spi2 + WHERE spi.sql_handle = spi2.sql_handle + AND spi2.proc_name <> N'Statement' + FOR XML PATH(N''), TYPE).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 1, N'') + AS [processing-instruction(ClickMe)] FOR XML PATH(''), TYPE ) + AS cached_execution_parameters + FROM #stored_proc_info AS spi + GROUP BY spi.sql_handle, spi.proc_name, set_options + ) + UPDATE b + SET b.cached_execution_parameters = pk.cached_execution_parameters + FROM #working_warnings AS b + JOIN precheck AS pk + ON pk.sql_handle = b.sql_handle + WHERE b.proc_or_function_name <> N'Statement' + OPTION (RECOMPILE); -SET @sql_select += @sql_where; -SET @sql_select += N'ORDER BY qsrs.avg_duration DESC - OPTION (RECOMPILE); - '; + RAISERROR(N'Updating cached parameter XML for statements', 0, 1) WITH NOWAIT; + WITH precheck AS ( + SELECT spi.sql_handle, + spi.proc_name, + (SELECT + set_options + + @cr + @lf + + @cr + @lf + + N' See QueryText column for full query text' + + @cr + @lf + + @cr + @lf + + STUFF(( + SELECT DISTINCT N', ' + + CASE WHEN spi2.variable_name <> N'**no_variable**' AND spi2.compile_time_value <> N'**idk_man**' + THEN spi2.variable_name + N' = ' + ELSE + @cr + @lf + N' We could not find any cached parameter values for this stored proc. ' + END + + CASE WHEN spi2.variable_name = N'**no_variable**' OR spi2.compile_time_value = N'**idk_man**' + THEN + @cr + @lf + N' Possible reasons include declared variables inside the procedure, recompile hints, etc. ' + WHEN spi2.compile_time_value = N'NULL' + THEN spi2.compile_time_value + ELSE RTRIM(spi2.compile_time_value) + END + FROM #stored_proc_info AS spi2 + WHERE spi.sql_handle = spi2.sql_handle + AND spi2.proc_name = N'Statement' + AND spi2.variable_name NOT LIKE N'%msparam%' + FOR XML PATH(N''), TYPE).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 1, N'') + AS [processing-instruction(ClickMe)] FOR XML PATH(''), TYPE ) + AS cached_execution_parameters + FROM #stored_proc_info AS spi + GROUP BY spi.sql_handle, spi.proc_name, spi.set_options + ) + UPDATE b + SET b.cached_execution_parameters = pk.cached_execution_parameters + FROM #working_warnings AS b + JOIN precheck AS pk + ON pk.sql_handle = b.sql_handle + WHERE b.proc_or_function_name = N'Statement' + OPTION (RECOMPILE); + -IF @Debug = 1 - PRINT @sql_select; +RAISERROR(N'Filling in implicit conversion info', 0, 1) WITH NOWAIT; +UPDATE b +SET b.implicit_conversion_info = CASE WHEN b.implicit_conversion_info IS NULL + OR CONVERT(NVARCHAR(MAX), b.implicit_conversion_info) = N'' + THEN N'' + ELSE b.implicit_conversion_info + END, + b.cached_execution_parameters = CASE WHEN b.cached_execution_parameters IS NULL + OR CONVERT(NVARCHAR(MAX), b.cached_execution_parameters) = N'' + THEN N'' + ELSE b.cached_execution_parameters + END +FROM #working_warnings AS b +OPTION (RECOMPILE); -IF @sql_select IS NULL - BEGIN - RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; - RETURN; - END; +/*End implicit conversion and parameter info*/ -EXEC sys.sp_executesql @stmt = @sql_select, - @params = @sp_params, - @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; +/*Begin Missing Index*/ +IF EXISTS ( SELECT 1/0 + FROM #working_warnings AS ww + WHERE ww.missing_index_count > 0 + OR ww.index_spool_cost > 0 + OR ww.index_spool_rows > 0 ) + + BEGIN + + RAISERROR(N'Inserting to #missing_index_xml', 0, 1) WITH NOWAIT; + WITH XMLNAMESPACES ( 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) + INSERT #missing_index_xml + SELECT qp.query_hash, + qp.sql_handle, + c.mg.value('@Impact', 'FLOAT') AS Impact, + c.mg.query('.') AS cmg + FROM #query_plan AS qp + CROSS APPLY qp.query_plan.nodes('//p:MissingIndexes/p:MissingIndexGroup') AS c(mg) + WHERE qp.query_hash IS NOT NULL + AND c.mg.value('@Impact', 'FLOAT') > 70.0 + OPTION (RECOMPILE); + RAISERROR(N'Inserting to #missing_index_schema', 0, 1) WITH NOWAIT; + WITH XMLNAMESPACES ( 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) + INSERT #missing_index_schema + SELECT mix.query_hash, mix.sql_handle, mix.impact, + c.mi.value('@Database', 'NVARCHAR(128)'), + c.mi.value('@Schema', 'NVARCHAR(128)'), + c.mi.value('@Table', 'NVARCHAR(128)'), + c.mi.query('.') + FROM #missing_index_xml AS mix + CROSS APPLY mix.index_xml.nodes('//p:MissingIndex') AS c(mi) + OPTION (RECOMPILE); + + RAISERROR(N'Inserting to #missing_index_usage', 0, 1) WITH NOWAIT; + WITH XMLNAMESPACES ( 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) + INSERT #missing_index_usage + SELECT ms.query_hash, ms.sql_handle, ms.impact, ms.database_name, ms.schema_name, ms.table_name, + c.cg.value('@Usage', 'NVARCHAR(128)'), + c.cg.query('.') + FROM #missing_index_schema ms + CROSS APPLY ms.index_xml.nodes('//p:ColumnGroup') AS c(cg) + OPTION (RECOMPILE); + + RAISERROR(N'Inserting to #missing_index_detail', 0, 1) WITH NOWAIT; + WITH XMLNAMESPACES ( 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) + INSERT #missing_index_detail + SELECT miu.query_hash, + miu.sql_handle, + miu.impact, + miu.database_name, + miu.schema_name, + miu.table_name, + miu.usage, + c.c.value('@Name', 'NVARCHAR(128)') + FROM #missing_index_usage AS miu + CROSS APPLY miu.index_xml.nodes('//p:Column') AS c(c) + OPTION (RECOMPILE); + + RAISERROR(N'Inserting to #missing_index_pretty', 0, 1) WITH NOWAIT; + INSERT #missing_index_pretty + SELECT DISTINCT m.query_hash, m.sql_handle, m.impact, m.database_name, m.schema_name, m.table_name + , STUFF(( SELECT DISTINCT N', ' + ISNULL(m2.column_name, '') AS column_name + FROM #missing_index_detail AS m2 + WHERE m2.usage = 'EQUALITY' + AND m.query_hash = m2.query_hash + AND m.sql_handle = m2.sql_handle + AND m.impact = m2.impact + AND m.database_name = m2.database_name + AND m.schema_name = m2.schema_name + AND m.table_name = m2.table_name + FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 2, N'') AS equality + , STUFF(( SELECT DISTINCT N', ' + ISNULL(m2.column_name, '') AS column_name + FROM #missing_index_detail AS m2 + WHERE m2.usage = 'INEQUALITY' + AND m.query_hash = m2.query_hash + AND m.sql_handle = m2.sql_handle + AND m.impact = m2.impact + AND m.database_name = m2.database_name + AND m.schema_name = m2.schema_name + AND m.table_name = m2.table_name + FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 2, N'') AS inequality + , STUFF(( SELECT DISTINCT N', ' + ISNULL(m2.column_name, '') AS column_name + FROM #missing_index_detail AS m2 + WHERE m2.usage = 'INCLUDE' + AND m.query_hash = m2.query_hash + AND m.sql_handle = m2.sql_handle + AND m.impact = m2.impact + AND m.database_name = m2.database_name + AND m.schema_name = m2.schema_name + AND m.table_name = m2.table_name + FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 2, N'') AS [include], + 0 AS is_spool + FROM #missing_index_detail AS m + GROUP BY m.query_hash, m.sql_handle, m.impact, m.database_name, m.schema_name, m.table_name + OPTION (RECOMPILE); -SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; -SET @sql_select += N' -WITH duration_max -AS ( SELECT TOP 1 - gi.start_range, - gi.end_range - FROM #grouped_interval AS gi - ORDER BY gi.total_max_duration_ms DESC ) -INSERT #working_plans WITH (TABLOCK) - ( plan_id, query_id, pattern ) -SELECT TOP ( @sp_Top ) - qsp.plan_id, qsp.query_id, ''max duration'' -FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp -JOIN duration_max AS dm -ON qsp.last_execution_time >= dm.start_range - AND qsp.last_execution_time < dm.end_range -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs -ON qsrs.plan_id = qsp.plan_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq -ON qsq.query_id = qsp.query_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt -ON qsqt.query_text_id = qsq.query_text_id -WHERE 1 = 1 - AND qsq.is_internal_query = 0 - AND qsp.query_plan IS NOT NULL - '; + RAISERROR(N'Inserting to #index_spool_ugly', 0, 1) WITH NOWAIT; + WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) + INSERT #index_spool_ugly + (query_hash, sql_handle, impact, database_name, schema_name, table_name, equality, inequality, include) + SELECT r.query_hash, + r.sql_handle, + (c.n.value('@EstimateIO', 'FLOAT') + (c.n.value('@EstimateCPU', 'FLOAT'))) + / ( 1 * NULLIF(ww.query_cost, 0)) * 100 AS impact, + o.n.value('@Database', 'NVARCHAR(128)') AS output_database, + o.n.value('@Schema', 'NVARCHAR(128)') AS output_schema, + o.n.value('@Table', 'NVARCHAR(128)') AS output_table, + k.n.value('@Column', 'NVARCHAR(128)') AS range_column, + e.n.value('@Column', 'NVARCHAR(128)') AS expression_column, + o.n.value('@Column', 'NVARCHAR(128)') AS output_column + FROM #relop AS r + JOIN #working_warnings AS ww + ON ww.query_hash = r.query_hash + CROSS APPLY r.relop.nodes('/p:RelOp') AS c(n) + CROSS APPLY r.relop.nodes('/p:RelOp/p:OutputList/p:ColumnReference') AS o(n) + OUTER APPLY r.relop.nodes('/p:RelOp/p:Spool/p:SeekPredicateNew/p:SeekKeys/p:Prefix/p:RangeColumns/p:ColumnReference') AS k(n) + OUTER APPLY r.relop.nodes('/p:RelOp/p:Spool/p:SeekPredicateNew/p:SeekKeys/p:Prefix/p:RangeExpressions/p:ColumnReference') AS e(n) + WHERE r.relop.exist('/p:RelOp[@PhysicalOp="Index Spool" and @LogicalOp="Eager Spool"]') = 1 + + RAISERROR(N'Inserting to spools to #missing_index_pretty', 0, 1) WITH NOWAIT; + INSERT #missing_index_pretty + (query_hash, sql_handle, impact, database_name, schema_name, table_name, equality, inequality, include, is_spool) + SELECT DISTINCT + isu.query_hash, + isu.sql_handle, + isu.impact, + isu.database_name, + isu.schema_name, + isu.table_name + , STUFF(( SELECT DISTINCT N', ' + ISNULL(isu2.equality, '') AS column_name + FROM #index_spool_ugly AS isu2 + WHERE isu2.equality IS NOT NULL + AND isu.query_hash = isu2.query_hash + AND isu.sql_handle = isu2.sql_handle + AND isu.impact = isu2.impact + AND isu.database_name = isu2.database_name + AND isu.schema_name = isu2.schema_name + AND isu.table_name = isu2.table_name + FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 2, N'') AS equality + , STUFF(( SELECT DISTINCT N', ' + ISNULL(isu2.inequality, '') AS column_name + FROM #index_spool_ugly AS isu2 + WHERE isu2.inequality IS NOT NULL + AND isu.query_hash = isu2.query_hash + AND isu.sql_handle = isu2.sql_handle + AND isu.impact = isu2.impact + AND isu.database_name = isu2.database_name + AND isu.schema_name = isu2.schema_name + AND isu.table_name = isu2.table_name + FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 2, N'') AS inequality + , STUFF(( SELECT DISTINCT N', ' + ISNULL(isu2.include, '') AS column_name + FROM #index_spool_ugly AS isu2 + WHERE isu2.include IS NOT NULL + AND isu.query_hash = isu2.query_hash + AND isu.sql_handle = isu2.sql_handle + AND isu.impact = isu2.impact + AND isu.database_name = isu2.database_name + AND isu.schema_name = isu2.schema_name + AND isu.table_name = isu2.table_name + FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 2, N'') AS include, + 1 AS is_spool + FROM #index_spool_ugly AS isu -SET @sql_select += @sql_where; -SET @sql_select += N'ORDER BY qsrs.max_duration DESC - OPTION (RECOMPILE); - '; + RAISERROR(N'Updating missing index information', 0, 1) WITH NOWAIT; + WITH missing AS ( + SELECT DISTINCT + mip.query_hash, + mip.sql_handle, + N'' + AS full_details + FROM #missing_index_pretty AS mip + GROUP BY mip.query_hash, mip.sql_handle, mip.impact + ) + UPDATE ww + SET ww.missing_indexes = m.full_details + FROM #working_warnings AS ww + JOIN missing AS m + ON m.sql_handle = ww.sql_handle + OPTION (RECOMPILE); -IF @Debug = 1 - PRINT @sql_select; + RAISERROR(N'Filling in missing index blanks', 0, 1) WITH NOWAIT; + UPDATE ww + SET ww.missing_indexes = + CASE WHEN ww.missing_indexes IS NULL + THEN '' + ELSE ww.missing_indexes + END + FROM #working_warnings AS ww + OPTION (RECOMPILE); -IF @sql_select IS NULL - BEGIN - RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; - RETURN; - END; +END +/*End Missing Index*/ -EXEC sys.sp_executesql @stmt = @sql_select, - @params = @sp_params, - @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; +RAISERROR(N'General query dispositions: frequent executions, long running, etc.', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +UPDATE b +SET b.frequent_execution = CASE WHEN wm.xpm > @execution_threshold THEN 1 END , + b.near_parallel = CASE WHEN b.query_cost BETWEEN @ctp * (1 - (@ctp_threshold_pct / 100.0)) AND @ctp THEN 1 END, + b.long_running = CASE WHEN wm.avg_duration > @long_running_query_warning_seconds THEN 1 + WHEN wm.max_duration > @long_running_query_warning_seconds THEN 1 + WHEN wm.avg_cpu_time > @long_running_query_warning_seconds THEN 1 + WHEN wm.max_cpu_time > @long_running_query_warning_seconds THEN 1 END, + b.is_key_lookup_expensive = CASE WHEN b.query_cost >= (@ctp / 2) AND b.key_lookup_cost >= b.query_cost * .5 THEN 1 END, + b.is_sort_expensive = CASE WHEN b.query_cost >= (@ctp / 2) AND b.sort_cost >= b.query_cost * .5 THEN 1 END, + b.is_remote_query_expensive = CASE WHEN b.remote_query_cost >= b.query_cost * .05 THEN 1 END, + b.is_unused_grant = CASE WHEN percent_memory_grant_used <= @memory_grant_warning_percent AND min_query_max_used_memory > @min_memory_per_query THEN 1 END, + b.long_running_low_cpu = CASE WHEN wm.avg_duration > wm.avg_cpu_time * 4 AND avg_cpu_time < 500. THEN 1 END, + b.low_cost_high_cpu = CASE WHEN b.query_cost < 10 AND wm.avg_cpu_time > 5000. THEN 1 END, + b.is_spool_expensive = CASE WHEN b.query_cost > (@ctp / 2) AND b.index_spool_cost >= b.query_cost * .1 THEN 1 END, + b.is_spool_more_rows = CASE WHEN b.index_spool_rows >= wm.min_rowcount THEN 1 END, + b.is_bad_estimate = CASE WHEN wm.avg_rowcount > 0 AND (b.estimated_rows * 1000 < wm.avg_rowcount OR b.estimated_rows > wm.avg_rowcount * 1000) THEN 1 END, + b.is_big_log = CASE WHEN wm.avg_log_bytes_used >= (@log_size_mb / 2.) THEN 1 END, + b.is_big_tempdb = CASE WHEN wm.avg_tempdb_space_used >= (@avg_tempdb_data_file / 2.) THEN 1 END +FROM #working_warnings AS b +JOIN #working_metrics AS wm +ON b.plan_id = wm.plan_id +AND b.query_id = wm.query_id +JOIN #working_plan_text AS wpt +ON b.plan_id = wpt.plan_id +AND b.query_id = wpt.query_id +OPTION (RECOMPILE); -/*Get longest cpu plans*/ -RAISERROR(N'Gathering highest cpu plans', 0, 1) WITH NOWAIT; +RAISERROR('Populating Warnings column', 0, 1) WITH NOWAIT; +/* Populate warnings */ +UPDATE b +SET b.warnings = SUBSTRING( + CASE WHEN b.warning_no_join_predicate = 1 THEN ', No Join Predicate' ELSE '' END + + CASE WHEN b.compile_timeout = 1 THEN ', Compilation Timeout' ELSE '' END + + CASE WHEN b.compile_memory_limit_exceeded = 1 THEN ', Compile Memory Limit Exceeded' ELSE '' END + + CASE WHEN b.is_forced_plan = 1 THEN ', Forced Plan' ELSE '' END + + CASE WHEN b.is_forced_parameterized = 1 THEN ', Forced Parameterization' ELSE '' END + + CASE WHEN b.unparameterized_query = 1 THEN ', Unparameterized Query' ELSE '' END + + CASE WHEN b.missing_index_count > 0 THEN ', Missing Indexes (' + CAST(b.missing_index_count AS NVARCHAR(3)) + ')' ELSE '' END + + CASE WHEN b.unmatched_index_count > 0 THEN ', Unmatched Indexes (' + CAST(b.unmatched_index_count AS NVARCHAR(3)) + ')' ELSE '' END + + CASE WHEN b.is_cursor = 1 THEN ', Cursor' + + CASE WHEN b.is_optimistic_cursor = 1 THEN '; optimistic' ELSE '' END + + CASE WHEN b.is_forward_only_cursor = 0 THEN '; not forward only' ELSE '' END + + CASE WHEN b.is_cursor_dynamic = 1 THEN '; dynamic' ELSE '' END + + CASE WHEN b.is_fast_forward_cursor = 1 THEN '; fast forward' ELSE '' END + ELSE '' END + + CASE WHEN b.is_parallel = 1 THEN ', Parallel' ELSE '' END + + CASE WHEN b.near_parallel = 1 THEN ', Nearly Parallel' ELSE '' END + + CASE WHEN b.frequent_execution = 1 THEN ', Frequent Execution' ELSE '' END + + CASE WHEN b.plan_warnings = 1 THEN ', Plan Warnings' ELSE '' END + + CASE WHEN b.parameter_sniffing = 1 THEN ', Parameter Sniffing' ELSE '' END + + CASE WHEN b.long_running = 1 THEN ', Long Running Query' ELSE '' END + + CASE WHEN b.downlevel_estimator = 1 THEN ', Downlevel CE' ELSE '' END + + CASE WHEN b.implicit_conversions = 1 THEN ', Implicit Conversions' ELSE '' END + + CASE WHEN b.plan_multiple_plans = 1 THEN ', Multiple Plans' ELSE '' END + + CASE WHEN b.is_trivial = 1 THEN ', Trivial Plans' ELSE '' END + + CASE WHEN b.is_forced_serial = 1 THEN ', Forced Serialization' ELSE '' END + + CASE WHEN b.is_key_lookup_expensive = 1 THEN ', Expensive Key Lookup' ELSE '' END + + CASE WHEN b.is_remote_query_expensive = 1 THEN ', Expensive Remote Query' ELSE '' END + + CASE WHEN b.trace_flags_session IS NOT NULL THEN ', Session Level Trace Flag(s) Enabled: ' + b.trace_flags_session ELSE '' END + + CASE WHEN b.is_unused_grant = 1 THEN ', Unused Memory Grant' ELSE '' END + + CASE WHEN b.function_count > 0 THEN ', Calls ' + CONVERT(VARCHAR(10), b.function_count) + ' function(s)' ELSE '' END + + CASE WHEN b.clr_function_count > 0 THEN ', Calls ' + CONVERT(VARCHAR(10), b.clr_function_count) + ' CLR function(s)' ELSE '' END + + CASE WHEN b.is_table_variable = 1 THEN ', Table Variables' ELSE '' END + + CASE WHEN b.no_stats_warning = 1 THEN ', Columns With No Statistics' ELSE '' END + + CASE WHEN b.relop_warnings = 1 THEN ', Operator Warnings' ELSE '' END + + CASE WHEN b.is_table_scan = 1 THEN ', Table Scans' ELSE '' END + + CASE WHEN b.backwards_scan = 1 THEN ', Backwards Scans' ELSE '' END + + CASE WHEN b.forced_index = 1 THEN ', Forced Indexes' ELSE '' END + + CASE WHEN b.forced_seek = 1 THEN ', Forced Seeks' ELSE '' END + + CASE WHEN b.forced_scan = 1 THEN ', Forced Scans' ELSE '' END + + CASE WHEN b.columnstore_row_mode = 1 THEN ', ColumnStore Row Mode ' ELSE '' END + + CASE WHEN b.is_computed_scalar = 1 THEN ', Computed Column UDF ' ELSE '' END + + CASE WHEN b.is_sort_expensive = 1 THEN ', Expensive Sort' ELSE '' END + + CASE WHEN b.is_computed_filter = 1 THEN ', Filter UDF' ELSE '' END + + CASE WHEN b.index_ops >= 5 THEN ', >= 5 Indexes Modified' ELSE '' END + + CASE WHEN b.is_row_level = 1 THEN ', Row Level Security' ELSE '' END + + CASE WHEN b.is_spatial = 1 THEN ', Spatial Index' ELSE '' END + + CASE WHEN b.index_dml = 1 THEN ', Index DML' ELSE '' END + + CASE WHEN b.table_dml = 1 THEN ', Table DML' ELSE '' END + + CASE WHEN b.low_cost_high_cpu = 1 THEN ', Low Cost High CPU' ELSE '' END + + CASE WHEN b.long_running_low_cpu = 1 THEN + ', Long Running With Low CPU' ELSE '' END + + CASE WHEN b.stale_stats = 1 THEN + ', Statistics used have > 100k modifications in the last 7 days' ELSE '' END + + CASE WHEN b.is_adaptive = 1 THEN + ', Adaptive Joins' ELSE '' END + + CASE WHEN b.is_spool_expensive = 1 THEN + ', Expensive Index Spool' ELSE '' END + + CASE WHEN b.is_spool_more_rows = 1 THEN + ', Large Index Row Spool' ELSE '' END + + CASE WHEN b.is_bad_estimate = 1 THEN + ', Row estimate mismatch' ELSE '' END + + CASE WHEN b.is_big_log = 1 THEN + ', High log use' ELSE '' END + + CASE WHEN b.is_big_tempdb = 1 THEN ', High tempdb use' ELSE '' END + + CASE WHEN b.is_paul_white_electric = 1 THEN ', SWITCH!' ELSE '' END + + CASE WHEN b.is_row_goal = 1 THEN ', Row Goals' ELSE '' END + + CASE WHEN b.is_mstvf = 1 THEN ', MSTVFs' ELSE '' END + + CASE WHEN b.is_mm_join = 1 THEN ', Many to Many Merge' ELSE '' END + + CASE WHEN b.is_nonsargable = 1 THEN ', non-SARGables' ELSE '' END + , 2, 200000) +FROM #working_warnings b +OPTION (RECOMPILE); -SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; -SET @sql_select += N' -WITH cpu_max -AS ( SELECT TOP 1 - gi.start_range, - gi.end_range - FROM #grouped_interval AS gi - ORDER BY gi.total_avg_cpu_time_ms DESC ) -INSERT #working_plans WITH (TABLOCK) - ( plan_id, query_id, pattern ) -SELECT TOP ( @sp_Top ) - qsp.plan_id, qsp.query_id, ''avg cpu'' -FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp -JOIN cpu_max AS dm -ON qsp.last_execution_time >= dm.start_range - AND qsp.last_execution_time < dm.end_range -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs -ON qsrs.plan_id = qsp.plan_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq -ON qsq.query_id = qsp.query_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt -ON qsqt.query_text_id = qsq.query_text_id -WHERE 1 = 1 - AND qsq.is_internal_query = 0 - AND qsp.query_plan IS NOT NULL - '; -SET @sql_select += @sql_where; +END; +END TRY +BEGIN CATCH + RAISERROR (N'Failure generating warnings.', 0,1) WITH NOWAIT; -SET @sql_select += N'ORDER BY qsrs.avg_cpu_time DESC - OPTION (RECOMPILE); - '; + IF @sql_select IS NOT NULL + BEGIN + SET @msg = N'Last @sql_select: ' + @sql_select; + RAISERROR(@msg, 0, 1) WITH NOWAIT; + END; -IF @Debug = 1 - PRINT @sql_select; + SELECT @msg = @DatabaseName + N' database failed to process. ' + ERROR_MESSAGE(), @error_severity = ERROR_SEVERITY(), @error_state = ERROR_STATE(); + RAISERROR (@msg, @error_severity, @error_state) WITH NOWAIT; + + + WHILE @@TRANCOUNT > 0 + ROLLBACK; -IF @sql_select IS NULL - BEGIN - RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; RETURN; - END; +END CATCH; -EXEC sys.sp_executesql @stmt = @sql_select, - @params = @sp_params, - @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; +BEGIN TRY +BEGIN -SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; -SET @sql_select += N' -WITH cpu_max -AS ( SELECT TOP 1 - gi.start_range, - gi.end_range - FROM #grouped_interval AS gi - ORDER BY gi.total_max_cpu_time_ms DESC ) -INSERT #working_plans WITH (TABLOCK) - ( plan_id, query_id, pattern ) -SELECT TOP ( @sp_Top ) - qsp.plan_id, qsp.query_id, ''max cpu'' -FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp -JOIN cpu_max AS dm -ON qsp.last_execution_time >= dm.start_range - AND qsp.last_execution_time < dm.end_range -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs -ON qsrs.plan_id = qsp.plan_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq -ON qsq.query_id = qsp.query_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt -ON qsqt.query_text_id = qsq.query_text_id -WHERE 1 = 1 - AND qsq.is_internal_query = 0 - AND qsp.query_plan IS NOT NULL - '; +RAISERROR(N'Checking for parameter sniffing symptoms', 0, 1) WITH NOWAIT; + +UPDATE b +SET b.parameter_sniffing_symptoms = +CASE WHEN b.count_executions < 2 THEN 'Too few executions to compare (< 2).' + ELSE + SUBSTRING( + /*Duration*/ + CASE WHEN (b.min_duration * 100) < (b.avg_duration) THEN ', Fast sometimes' ELSE '' END + + CASE WHEN (b.max_duration) > (b.avg_duration * 100) THEN ', Slow sometimes' ELSE '' END + + CASE WHEN (b.last_duration * 100) < (b.avg_duration) THEN ', Fast last run' ELSE '' END + + CASE WHEN (b.last_duration) > (b.avg_duration * 100) THEN ', Slow last run' ELSE '' END + + /*CPU*/ + CASE WHEN (b.min_cpu_time / b.avg_dop) * 100 < (b.avg_cpu_time / b.avg_dop) THEN ', Low CPU sometimes' ELSE '' END + + CASE WHEN (b.max_cpu_time / b.max_dop) > (b.avg_cpu_time / b.avg_dop) * 100 THEN ', High CPU sometimes' ELSE '' END + + CASE WHEN (b.last_cpu_time / b.last_dop) * 100 < (b.avg_cpu_time / b.avg_dop) THEN ', Low CPU last run' ELSE '' END + + CASE WHEN (b.last_cpu_time / b.last_dop) > (b.avg_cpu_time / b.avg_dop) * 100 THEN ', High CPU last run' ELSE '' END + + /*Logical Reads*/ + CASE WHEN (b.min_logical_io_reads * 100) < (b.avg_logical_io_reads) THEN ', Low reads sometimes' ELSE '' END + + CASE WHEN (b.max_logical_io_reads) > (b.avg_logical_io_reads * 100) THEN ', High reads sometimes' ELSE '' END + + CASE WHEN (b.last_logical_io_reads * 100) < (b.avg_logical_io_reads) THEN ', Low reads last run' ELSE '' END + + CASE WHEN (b.last_logical_io_reads) > (b.avg_logical_io_reads * 100) THEN ', High reads last run' ELSE '' END + + /*Logical Writes*/ + CASE WHEN (b.min_logical_io_writes * 100) < (b.avg_logical_io_writes) THEN ', Low writes sometimes' ELSE '' END + + CASE WHEN (b.max_logical_io_writes) > (b.avg_logical_io_writes * 100) THEN ', High writes sometimes' ELSE '' END + + CASE WHEN (b.last_logical_io_writes * 100) < (b.avg_logical_io_writes) THEN ', Low writes last run' ELSE '' END + + CASE WHEN (b.last_logical_io_writes) > (b.avg_logical_io_writes * 100) THEN ', High writes last run' ELSE '' END + + /*Physical Reads*/ + CASE WHEN (b.min_physical_io_reads * 100) < (b.avg_physical_io_reads) THEN ', Low physical reads sometimes' ELSE '' END + + CASE WHEN (b.max_physical_io_reads) > (b.avg_physical_io_reads * 100) THEN ', High physical reads sometimes' ELSE '' END + + CASE WHEN (b.last_physical_io_reads * 100) < (b.avg_physical_io_reads) THEN ', Low physical reads last run' ELSE '' END + + CASE WHEN (b.last_physical_io_reads) > (b.avg_physical_io_reads * 100) THEN ', High physical reads last run' ELSE '' END + + /*Memory*/ + CASE WHEN (b.min_query_max_used_memory * 100) < (b.avg_query_max_used_memory) THEN ', Low memory sometimes' ELSE '' END + + CASE WHEN (b.max_query_max_used_memory) > (b.avg_query_max_used_memory * 100) THEN ', High memory sometimes' ELSE '' END + + CASE WHEN (b.last_query_max_used_memory * 100) < (b.avg_query_max_used_memory) THEN ', Low memory last run' ELSE '' END + + CASE WHEN (b.last_query_max_used_memory) > (b.avg_query_max_used_memory * 100) THEN ', High memory last run' ELSE '' END + + /*Duration*/ + CASE WHEN b.min_rowcount * 100 < b.avg_rowcount THEN ', Low row count sometimes' ELSE '' END + + CASE WHEN b.max_rowcount > b.avg_rowcount * 100 THEN ', High row count sometimes' ELSE '' END + + CASE WHEN b.last_rowcount * 100 < b.avg_rowcount THEN ', Low row count run' ELSE '' END + + CASE WHEN b.last_rowcount > b.avg_rowcount * 100 THEN ', High row count last run' ELSE '' END + + /*DOP*/ + CASE WHEN b.min_dop <> b.max_dop THEN ', Serial sometimes' ELSE '' END + + CASE WHEN b.min_dop <> b.max_dop AND b.last_dop = 1 THEN ', Serial last run' ELSE '' END + + CASE WHEN b.min_dop <> b.max_dop AND b.last_dop > 1 THEN ', Parallel last run' ELSE '' END + + /*tempdb*/ + CASE WHEN b.min_tempdb_space_used * 100 < b.avg_tempdb_space_used THEN ', Low tempdb sometimes' ELSE '' END + + CASE WHEN b.max_tempdb_space_used > b.avg_tempdb_space_used * 100 THEN ', High tempdb sometimes' ELSE '' END + + CASE WHEN b.last_tempdb_space_used * 100 < b.avg_tempdb_space_used THEN ', Low tempdb run' ELSE '' END + + CASE WHEN b.last_tempdb_space_used > b.avg_tempdb_space_used * 100 THEN ', High tempdb last run' ELSE '' END + + /*tlog*/ + CASE WHEN b.min_log_bytes_used * 100 < b.avg_log_bytes_used THEN ', Low log use sometimes' ELSE '' END + + CASE WHEN b.max_log_bytes_used > b.avg_log_bytes_used * 100 THEN ', High log use sometimes' ELSE '' END + + CASE WHEN b.last_log_bytes_used * 100 < b.avg_log_bytes_used THEN ', Low log use run' ELSE '' END + + CASE WHEN b.last_log_bytes_used > b.avg_log_bytes_used * 100 THEN ', High log use last run' ELSE '' END + , 2, 200000) + END +FROM #working_metrics AS b +OPTION (RECOMPILE); -SET @sql_select += @sql_where; +END; +END TRY +BEGIN CATCH + RAISERROR (N'Failure analyzing parameter sniffing', 0,1) WITH NOWAIT; -SET @sql_select += N'ORDER BY qsrs.max_cpu_time DESC - OPTION (RECOMPILE); - '; + IF @sql_select IS NOT NULL + BEGIN + SET @msg = N'Last @sql_select: ' + @sql_select; + RAISERROR(@msg, 0, 1) WITH NOWAIT; + END; -IF @Debug = 1 - PRINT @sql_select; + SELECT @msg = @DatabaseName + N' database failed to process. ' + ERROR_MESSAGE(), @error_severity = ERROR_SEVERITY(), @error_state = ERROR_STATE(); + RAISERROR (@msg, @error_severity, @error_state) WITH NOWAIT; + + + WHILE @@TRANCOUNT > 0 + ROLLBACK; -IF @sql_select IS NULL - BEGIN - RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; RETURN; - END; - -EXEC sys.sp_executesql @stmt = @sql_select, - @params = @sp_params, - @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; - +END CATCH; -/*Get highest logical read plans*/ +BEGIN TRY -RAISERROR(N'Gathering highest logical read plans', 0, 1) WITH NOWAIT; +BEGIN -SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; -SET @sql_select += N' -WITH logical_reads_max -AS ( SELECT TOP 1 - gi.start_range, - gi.end_range - FROM #grouped_interval AS gi - ORDER BY gi.total_avg_logical_io_reads_mb DESC ) -INSERT #working_plans WITH (TABLOCK) - ( plan_id, query_id, pattern ) -SELECT TOP ( @sp_Top ) - qsp.plan_id, qsp.query_id, ''avg logical reads'' -FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp -JOIN logical_reads_max AS dm -ON qsp.last_execution_time >= dm.start_range - AND qsp.last_execution_time < dm.end_range -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs -ON qsrs.plan_id = qsp.plan_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq -ON qsq.query_id = qsp.query_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt -ON qsqt.query_text_id = qsq.query_text_id -WHERE 1 = 1 - AND qsq.is_internal_query = 0 - AND qsp.query_plan IS NOT NULL - '; +IF (@Failed = 0 AND @ExportToExcel = 0 AND @SkipXML = 0) +BEGIN -SET @sql_select += @sql_where; +RAISERROR(N'Returning regular results', 0, 1) WITH NOWAIT; -SET @sql_select += N'ORDER BY qsrs.avg_logical_io_reads DESC - OPTION (RECOMPILE); - '; +WITH x AS ( +SELECT wpt.database_name, ww.query_cost, wm.plan_id, wm.query_id, wm.query_id_all_plan_ids, wpt.query_sql_text, wm.proc_or_function_name, wpt.query_plan_xml, ww.warnings, wpt.pattern, + wm.parameter_sniffing_symptoms, wpt.top_three_waits, ww.missing_indexes, ww.implicit_conversion_info, ww.cached_execution_parameters, wm.count_executions, wm.count_compiles, wm.total_cpu_time, wm.avg_cpu_time, + wm.total_duration, wm.avg_duration, wm.total_logical_io_reads, wm.avg_logical_io_reads, + wm.total_physical_io_reads, wm.avg_physical_io_reads, wm.total_logical_io_writes, wm.avg_logical_io_writes, wm.total_rowcount, wm.avg_rowcount, + wm.total_query_max_used_memory, wm.avg_query_max_used_memory, wm.total_tempdb_space_used, wm.avg_tempdb_space_used, + wm.total_log_bytes_used, wm.avg_log_bytes_used, wm.total_num_physical_io_reads, wm.avg_num_physical_io_reads, + wm.first_execution_time, wm.last_execution_time, wpt.last_force_failure_reason_desc, wpt.context_settings, ROW_NUMBER() OVER (PARTITION BY wm.plan_id, wm.query_id, wm.last_execution_time ORDER BY wm.plan_id) AS rn +FROM #working_plan_text AS wpt +JOIN #working_warnings AS ww + ON wpt.plan_id = ww.plan_id + AND wpt.query_id = ww.query_id +JOIN #working_metrics AS wm + ON wpt.plan_id = wm.plan_id + AND wpt.query_id = wm.query_id +) +SELECT * +FROM x +WHERE x.rn = 1 +ORDER BY x.last_execution_time +OPTION (RECOMPILE); -IF @Debug = 1 - PRINT @sql_select; +END; -IF @sql_select IS NULL - BEGIN - RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; - RETURN; - END; +IF (@Failed = 1 AND @ExportToExcel = 0 AND @SkipXML = 0) +BEGIN -EXEC sys.sp_executesql @stmt = @sql_select, - @params = @sp_params, - @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; +RAISERROR(N'Returning results for failed queries', 0, 1) WITH NOWAIT; +WITH x AS ( +SELECT wpt.database_name, ww.query_cost, wm.plan_id, wm.query_id, wm.query_id_all_plan_ids, wpt.query_sql_text, wm.proc_or_function_name, wpt.query_plan_xml, ww.warnings, wpt.pattern, + wm.parameter_sniffing_symptoms, wpt.last_force_failure_reason_desc, wpt.top_three_waits, ww.missing_indexes, ww.implicit_conversion_info, ww.cached_execution_parameters, + wm.count_executions, wm.count_compiles, wm.total_cpu_time, wm.avg_cpu_time, + wm.total_duration, wm.avg_duration, wm.total_logical_io_reads, wm.avg_logical_io_reads, + wm.total_physical_io_reads, wm.avg_physical_io_reads, wm.total_logical_io_writes, wm.avg_logical_io_writes, wm.total_rowcount, wm.avg_rowcount, + wm.total_query_max_used_memory, wm.avg_query_max_used_memory, wm.total_tempdb_space_used, wm.avg_tempdb_space_used, + wm.total_log_bytes_used, wm.avg_log_bytes_used, wm.total_num_physical_io_reads, wm.avg_num_physical_io_reads, + wm.first_execution_time, wm.last_execution_time, wpt.context_settings, ROW_NUMBER() OVER (PARTITION BY wm.plan_id, wm.query_id, wm.last_execution_time ORDER BY wm.plan_id) AS rn +FROM #working_plan_text AS wpt +JOIN #working_warnings AS ww + ON wpt.plan_id = ww.plan_id + AND wpt.query_id = ww.query_id +JOIN #working_metrics AS wm + ON wpt.plan_id = wm.plan_id + AND wpt.query_id = wm.query_id +) +SELECT * +FROM x +WHERE x.rn = 1 +ORDER BY x.last_execution_time +OPTION (RECOMPILE); -SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; -SET @sql_select += N' -WITH logical_reads_max -AS ( SELECT TOP 1 - gi.start_range, - gi.end_range - FROM #grouped_interval AS gi - ORDER BY gi.total_max_logical_io_reads_mb DESC ) -INSERT #working_plans WITH (TABLOCK) - ( plan_id, query_id, pattern ) -SELECT TOP ( @sp_Top ) - qsp.plan_id, qsp.query_id, ''max logical reads'' -FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp -JOIN logical_reads_max AS dm -ON qsp.last_execution_time >= dm.start_range - AND qsp.last_execution_time < dm.end_range -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs -ON qsrs.plan_id = qsp.plan_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq -ON qsq.query_id = qsp.query_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt -ON qsqt.query_text_id = qsq.query_text_id -WHERE 1 = 1 - AND qsq.is_internal_query = 0 - AND qsp.query_plan IS NOT NULL - '; +END; -SET @sql_select += @sql_where; +IF (@ExportToExcel = 1 AND @SkipXML = 0) +BEGIN -SET @sql_select += N'ORDER BY qsrs.max_logical_io_reads DESC - OPTION (RECOMPILE); - '; +RAISERROR(N'Returning results for Excel export', 0, 1) WITH NOWAIT; -IF @Debug = 1 - PRINT @sql_select; +UPDATE #working_plan_text +SET query_sql_text = SUBSTRING(REPLACE(REPLACE(REPLACE(LTRIM(RTRIM(query_sql_text)),' ','<>'),'><',''),'<>',' '), 1, 31000) +OPTION (RECOMPILE); -IF @sql_select IS NULL - BEGIN - RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; - RETURN; - END; +WITH x AS ( +SELECT wpt.database_name, ww.query_cost, wm.plan_id, wm.query_id, wm.query_id_all_plan_ids, wpt.query_sql_text, wm.proc_or_function_name, ww.warnings, wpt.pattern, + wm.parameter_sniffing_symptoms, wpt.last_force_failure_reason_desc, wpt.top_three_waits, wm.count_executions, wm.count_compiles, wm.total_cpu_time, wm.avg_cpu_time, + wm.total_duration, wm.avg_duration, wm.total_logical_io_reads, wm.avg_logical_io_reads, + wm.total_physical_io_reads, wm.avg_physical_io_reads, wm.total_logical_io_writes, wm.avg_logical_io_writes, wm.total_rowcount, wm.avg_rowcount, + wm.total_query_max_used_memory, wm.avg_query_max_used_memory, wm.total_tempdb_space_used, wm.avg_tempdb_space_used, + wm.total_log_bytes_used, wm.avg_log_bytes_used, wm.total_num_physical_io_reads, wm.avg_num_physical_io_reads, + wm.first_execution_time, wm.last_execution_time, wpt.context_settings, ROW_NUMBER() OVER (PARTITION BY wm.plan_id, wm.query_id, wm.last_execution_time ORDER BY wm.plan_id) AS rn +FROM #working_plan_text AS wpt +JOIN #working_warnings AS ww + ON wpt.plan_id = ww.plan_id + AND wpt.query_id = ww.query_id +JOIN #working_metrics AS wm + ON wpt.plan_id = wm.plan_id + AND wpt.query_id = wm.query_id +) +SELECT * +FROM x +WHERE x.rn = 1 +ORDER BY x.last_execution_time +OPTION (RECOMPILE); -EXEC sys.sp_executesql @stmt = @sql_select, - @params = @sp_params, - @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; +END; +IF (@ExportToExcel = 0 AND @SkipXML = 1) +BEGIN -/*Get highest physical read plans*/ +RAISERROR(N'Returning results for skipped XML', 0, 1) WITH NOWAIT; -RAISERROR(N'Gathering highest physical read plans', 0, 1) WITH NOWAIT; +WITH x AS ( +SELECT wpt.database_name, wm.plan_id, wm.query_id, wm.query_id_all_plan_ids, wpt.query_sql_text, wpt.query_plan_xml, wpt.pattern, + wm.parameter_sniffing_symptoms, wpt.top_three_waits, wm.count_executions, wm.count_compiles, wm.total_cpu_time, wm.avg_cpu_time, + wm.total_duration, wm.avg_duration, wm.total_logical_io_reads, wm.avg_logical_io_reads, + wm.total_physical_io_reads, wm.avg_physical_io_reads, wm.total_logical_io_writes, wm.avg_logical_io_writes, wm.total_rowcount, wm.avg_rowcount, + wm.total_query_max_used_memory, wm.avg_query_max_used_memory, wm.total_tempdb_space_used, wm.avg_tempdb_space_used, + wm.total_log_bytes_used, wm.avg_log_bytes_used, wm.total_num_physical_io_reads, wm.avg_num_physical_io_reads, + wm.first_execution_time, wm.last_execution_time, wpt.last_force_failure_reason_desc, wpt.context_settings, ROW_NUMBER() OVER (PARTITION BY wm.plan_id, wm.query_id, wm.last_execution_time ORDER BY wm.plan_id) AS rn +FROM #working_plan_text AS wpt +JOIN #working_metrics AS wm + ON wpt.plan_id = wm.plan_id + AND wpt.query_id = wm.query_id +) +SELECT * +FROM x +WHERE x.rn = 1 +ORDER BY x.last_execution_time +OPTION (RECOMPILE); -SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; -SET @sql_select += N' -WITH physical_read_max -AS ( SELECT TOP 1 - gi.start_range, - gi.end_range - FROM #grouped_interval AS gi - ORDER BY gi.total_avg_physical_io_reads_mb DESC ) -INSERT #working_plans WITH (TABLOCK) - ( plan_id, query_id, pattern ) -SELECT TOP ( @sp_Top ) - qsp.plan_id, qsp.query_id, ''avg physical reads'' -FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp -JOIN physical_read_max AS dm -ON qsp.last_execution_time >= dm.start_range - AND qsp.last_execution_time < dm.end_range -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs -ON qsrs.plan_id = qsp.plan_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq -ON qsq.query_id = qsp.query_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt -ON qsqt.query_text_id = qsq.query_text_id -WHERE 1 = 1 - AND qsq.is_internal_query = 0 - AND qsp.query_plan IS NOT NULL - '; +END; -SET @sql_select += @sql_where; +END; +END TRY +BEGIN CATCH + RAISERROR (N'Failure returning results', 0,1) WITH NOWAIT; -SET @sql_select += N'ORDER BY qsrs.avg_physical_io_reads DESC - OPTION (RECOMPILE); - '; + IF @sql_select IS NOT NULL + BEGIN + SET @msg = N'Last @sql_select: ' + @sql_select; + RAISERROR(@msg, 0, 1) WITH NOWAIT; + END; -IF @Debug = 1 - PRINT @sql_select; + SELECT @msg = @DatabaseName + N' database failed to process. ' + ERROR_MESSAGE(), @error_severity = ERROR_SEVERITY(), @error_state = ERROR_STATE(); + RAISERROR (@msg, @error_severity, @error_state) WITH NOWAIT; + + + WHILE @@TRANCOUNT > 0 + ROLLBACK; -IF @sql_select IS NULL - BEGIN - RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; RETURN; - END; - -EXEC sys.sp_executesql @stmt = @sql_select, - @params = @sp_params, - @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; +END CATCH; +BEGIN TRY +BEGIN -SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; -SET @sql_select += N' -WITH physical_read_max -AS ( SELECT TOP 1 - gi.start_range, - gi.end_range - FROM #grouped_interval AS gi - ORDER BY gi.total_max_physical_io_reads_mb DESC ) -INSERT #working_plans WITH (TABLOCK) - ( plan_id, query_id, pattern ) -SELECT TOP ( @sp_Top ) - qsp.plan_id, qsp.query_id, ''max physical reads'' -FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp -JOIN physical_read_max AS dm -ON qsp.last_execution_time >= dm.start_range - AND qsp.last_execution_time < dm.end_range -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs -ON qsrs.plan_id = qsp.plan_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq -ON qsq.query_id = qsp.query_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt -ON qsqt.query_text_id = qsq.query_text_id -WHERE 1 = 1 - AND qsq.is_internal_query = 0 - AND qsp.query_plan IS NOT NULL - '; +IF (@ExportToExcel = 0 AND @HideSummary = 0 AND @SkipXML = 0) +BEGIN + RAISERROR('Building query plan summary data.', 0, 1) WITH NOWAIT; -SET @sql_select += @sql_where; + /* Build summary data */ + IF EXISTS (SELECT 1/0 + FROM #working_warnings + WHERE frequent_execution = 1 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 1, + 100, + 'Execution Pattern', + 'Frequently Executed Queries', + 'http://brentozar.com/blitzcache/frequently-executed-queries/', + 'Queries are being executed more than ' + + CAST (@execution_threshold AS VARCHAR(5)) + + ' times per minute. This can put additional load on the server, even when queries are lightweight.') ; -SET @sql_select += N'ORDER BY qsrs.max_physical_io_reads DESC - OPTION (RECOMPILE); - '; + IF EXISTS (SELECT 1/0 + FROM #working_warnings + WHERE parameter_sniffing = 1 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 2, + 50, + 'Parameterization', + 'Parameter Sniffing', + 'http://brentozar.com/blitzcache/parameter-sniffing/', + 'There are signs of parameter sniffing (wide variance in rows return or time to execute). Investigate query patterns and tune code appropriately.') ; -IF @Debug = 1 - PRINT @sql_select; + /* Forced execution plans */ + IF EXISTS (SELECT 1/0 + FROM #working_warnings + WHERE is_forced_plan = 1 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 3, + 5, + 'Parameterization', + 'Forced Plans', + 'http://brentozar.com/blitzcache/forced-plans/', + 'Execution plans have been compiled with forced plans, either through FORCEPLAN, plan guides, or forced parameterization. This will make general tuning efforts less effective.'); -IF @sql_select IS NULL - BEGIN - RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; - RETURN; - END; + IF EXISTS (SELECT 1/0 + FROM #working_warnings + WHERE is_cursor = 1 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 4, + 200, + 'Cursors', + 'Cursors', + 'http://brentozar.com/blitzcache/cursors-found-slow-queries/', + 'There are cursors in the plan cache. This is neither good nor bad, but it is a thing. Cursors are weird in SQL Server.'); -EXEC sys.sp_executesql @stmt = @sql_select, - @params = @sp_params, - @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; + IF EXISTS (SELECT 1/0 + FROM #working_warnings + WHERE is_cursor = 1 + AND is_optimistic_cursor = 1 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 4, + 200, + 'Cursors', + 'Optimistic Cursors', + 'http://brentozar.com/blitzcache/cursors-found-slow-queries/', + 'There are optimistic cursors in the plan cache, which can harm performance.'); + IF EXISTS (SELECT 1/0 + FROM #working_warnings + WHERE is_cursor = 1 + AND is_forward_only_cursor = 0 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 4, + 200, + 'Cursors', + 'Non-forward Only Cursors', + 'http://brentozar.com/blitzcache/cursors-found-slow-queries/', + 'There are non-forward only cursors in the plan cache, which can harm performance.'); -/*Get highest logical write plans*/ + IF EXISTS (SELECT 1/0 + FROM #working_warnings + WHERE is_cursor = 1 + AND is_cursor_dynamic = 1 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (4, + 200, + 'Cursors', + 'Dynamic Cursors', + 'http://brentozar.com/blitzcache/cursors-found-slow-queries/', + 'Dynamic Cursors inhibit parallelism!.'); -RAISERROR(N'Gathering highest write plans', 0, 1) WITH NOWAIT; + IF EXISTS (SELECT 1/0 + FROM #working_warnings + WHERE is_cursor = 1 + AND is_fast_forward_cursor = 1 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (4, + 200, + 'Cursors', + 'Fast Forward Cursors', + 'http://brentozar.com/blitzcache/cursors-found-slow-queries/', + 'Fast forward cursors inhibit parallelism!.'); + + IF EXISTS (SELECT 1/0 + FROM #working_warnings + WHERE is_forced_parameterized = 1 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 5, + 50, + 'Parameterization', + 'Forced Parameterization', + 'http://brentozar.com/blitzcache/forced-parameterization/', + 'Execution plans have been compiled with forced parameterization.') ; -SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; -SET @sql_select += N' -WITH logical_writes_max -AS ( SELECT TOP 1 - gi.start_range, - gi.end_range - FROM #grouped_interval AS gi - ORDER BY gi.total_avg_logical_io_writes_mb DESC ) -INSERT #working_plans WITH (TABLOCK) - ( plan_id, query_id, pattern ) -SELECT TOP ( @sp_Top ) - qsp.plan_id, qsp.query_id, ''avg writes'' -FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp -JOIN logical_writes_max AS dm -ON qsp.last_execution_time >= dm.start_range - AND qsp.last_execution_time < dm.end_range -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs -ON qsrs.plan_id = qsp.plan_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq -ON qsq.query_id = qsp.query_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt -ON qsqt.query_text_id = qsq.query_text_id -WHERE 1 = 1 - AND qsq.is_internal_query = 0 - AND qsp.query_plan IS NOT NULL - '; + IF EXISTS (SELECT 1/0 + FROM #working_warnings p + WHERE p.is_parallel = 1 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 6, + 200, + 'Execution Plans', + 'Parallelism', + 'http://brentozar.com/blitzcache/parallel-plans-detected/', + 'Parallel plans detected. These warrant investigation, but are neither good nor bad.') ; -SET @sql_select += @sql_where; + IF EXISTS (SELECT 1/0 + FROM #working_warnings p + WHERE p.near_parallel = 1 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 7, + 200, + 'Execution Plans', + 'Nearly Parallel', + 'http://brentozar.com/blitzcache/query-cost-near-cost-threshold-parallelism/', + 'Queries near the cost threshold for parallelism. These may go parallel when you least expect it.') ; -SET @sql_select += N'ORDER BY qsrs.avg_logical_io_writes DESC - OPTION (RECOMPILE); - '; + IF EXISTS (SELECT 1/0 + FROM #working_warnings p + WHERE p.plan_warnings = 1 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 8, + 50, + 'Execution Plans', + 'Query Plan Warnings', + 'http://brentozar.com/blitzcache/query-plan-warnings/', + 'Warnings detected in execution plans. SQL Server is telling you that something bad is going on that requires your attention.') ; -IF @Debug = 1 - PRINT @sql_select; + IF EXISTS (SELECT 1/0 + FROM #working_warnings p + WHERE p.long_running = 1 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 9, + 50, + 'Performance', + 'Long Running Queries', + 'http://brentozar.com/blitzcache/long-running-queries/', + 'Long running queries have been found. These are queries with an average duration longer than ' + + CAST(@long_running_query_warning_seconds / 1000 / 1000 AS VARCHAR(5)) + + ' second(s). These queries should be investigated for additional tuning options.') ; -IF @sql_select IS NULL - BEGIN - RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; - RETURN; - END; + IF EXISTS (SELECT 1/0 + FROM #working_warnings p + WHERE p.missing_index_count > 0 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 10, + 50, + 'Performance', + 'Missing Index Request', + 'http://brentozar.com/blitzcache/missing-index-request/', + 'Queries found with missing indexes.'); -EXEC sys.sp_executesql @stmt = @sql_select, - @params = @sp_params, - @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; + IF EXISTS (SELECT 1/0 + FROM #working_warnings p + WHERE p.downlevel_estimator = 1 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 13, + 200, + 'Cardinality', + 'Legacy Cardinality Estimator in Use', + 'http://brentozar.com/blitzcache/legacy-cardinality-estimator/', + 'A legacy cardinality estimator is being used by one or more queries. Investigate whether you need to be using this cardinality estimator. This may be caused by compatibility levels, global trace flags, or query level trace flags.'); + IF EXISTS (SELECT 1/0 + FROM #working_warnings p + WHERE p.implicit_conversions = 1 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 14, + 50, + 'Performance', + 'Implicit Conversions', + 'http://brentozar.com/go/implicit', + 'One or more queries are comparing two fields that are not of the same data type.') ; -SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; -SET @sql_select += N' -WITH logical_writes_max -AS ( SELECT TOP 1 - gi.start_range, - gi.end_range - FROM #grouped_interval AS gi - ORDER BY gi.total_max_logical_io_writes_mb DESC ) -INSERT #working_plans WITH (TABLOCK) - ( plan_id, query_id, pattern ) -SELECT TOP ( @sp_Top ) - qsp.plan_id, qsp.query_id, ''max writes'' -FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp -JOIN logical_writes_max AS dm -ON qsp.last_execution_time >= dm.start_range - AND qsp.last_execution_time < dm.end_range -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs -ON qsrs.plan_id = qsp.plan_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq -ON qsq.query_id = qsp.query_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt -ON qsqt.query_text_id = qsq.query_text_id -WHERE 1 = 1 - AND qsq.is_internal_query = 0 - AND qsp.query_plan IS NOT NULL - '; + IF EXISTS (SELECT 1/0 + FROM #working_warnings p + WHERE busy_loops = 1 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 16, + 100, + 'Performance', + 'Busy Loops', + 'http://brentozar.com/blitzcache/busy-loops/', + 'Operations have been found that are executed 100 times more often than the number of rows returned by each iteration. This is an indicator that something is off in query execution.'); -SET @sql_select += @sql_where; + IF EXISTS (SELECT 1/0 + FROM #working_warnings p + WHERE tvf_join = 1 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 17, + 50, + 'Performance', + 'Joining to table valued functions', + 'http://brentozar.com/blitzcache/tvf-join/', + 'Execution plans have been found that join to table valued functions (TVFs). TVFs produce inaccurate estimates of the number of rows returned and can lead to any number of query plan problems.'); -SET @sql_select += N'ORDER BY qsrs.max_logical_io_writes DESC - OPTION (RECOMPILE); - '; + IF EXISTS (SELECT 1/0 + FROM #working_warnings + WHERE compile_timeout = 1 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 18, + 50, + 'Execution Plans', + 'Compilation timeout', + 'http://brentozar.com/blitzcache/compilation-timeout/', + 'Query compilation timed out for one or more queries. SQL Server did not find a plan that meets acceptable performance criteria in the time allotted so the best guess was returned. There is a very good chance that this plan isn''t even below average - it''s probably terrible.'); -IF @Debug = 1 - PRINT @sql_select; + IF EXISTS (SELECT 1/0 + FROM #working_warnings + WHERE compile_memory_limit_exceeded = 1 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 19, + 50, + 'Execution Plans', + 'Compilation memory limit exceeded', + 'http://brentozar.com/blitzcache/compile-memory-limit-exceeded/', + 'The optimizer has a limited amount of memory available. One or more queries are complex enough that SQL Server was unable to allocate enough memory to fully optimize the query. A best fit plan was found, and it''s probably terrible.'); -IF @sql_select IS NULL - BEGIN - RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; - RETURN; - END; + IF EXISTS (SELECT 1/0 + FROM #working_warnings + WHERE warning_no_join_predicate = 1 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 20, + 10, + 'Execution Plans', + 'No join predicate', + 'http://brentozar.com/blitzcache/no-join-predicate/', + 'Operators in a query have no join predicate. This means that all rows from one table will be matched with all rows from anther table producing a Cartesian product. That''s a whole lot of rows. This may be your goal, but it''s important to investigate why this is happening.'); -EXEC sys.sp_executesql @stmt = @sql_select, - @params = @sp_params, - @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; + IF EXISTS (SELECT 1/0 + FROM #working_warnings + WHERE plan_multiple_plans = 1 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 21, + 200, + 'Execution Plans', + 'Multiple execution plans', + 'http://brentozar.com/blitzcache/multiple-plans/', + 'Queries exist with multiple execution plans (as determined by query_plan_hash). Investigate possible ways to parameterize these queries or otherwise reduce the plan count.'); + IF EXISTS (SELECT 1/0 + FROM #working_warnings + WHERE unmatched_index_count > 0 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 22, + 100, + 'Performance', + 'Unmatched indexes', + 'http://brentozar.com/blitzcache/unmatched-indexes', + 'An index could have been used, but SQL Server chose not to use it - likely due to parameterization and filtered indexes.'); -/*Get highest memory use plans*/ + IF EXISTS (SELECT 1/0 + FROM #working_warnings + WHERE unparameterized_query = 1 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 23, + 100, + 'Parameterization', + 'Unparameterized queries', + 'http://brentozar.com/blitzcache/unparameterized-queries', + 'Unparameterized queries found. These could be ad hoc queries, data exploration, or queries using "OPTIMIZE FOR UNKNOWN".'); -RAISERROR(N'Gathering highest memory use plans', 0, 1) WITH NOWAIT; + IF EXISTS (SELECT 1/0 + FROM #working_warnings + WHERE is_trivial = 1 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 24, + 100, + 'Execution Plans', + 'Trivial Plans', + 'http://brentozar.com/blitzcache/trivial-plans', + 'Trivial plans get almost no optimization. If you''re finding these in the top worst queries, something may be going wrong.'); + + IF EXISTS (SELECT 1/0 + FROM #working_warnings p + WHERE p.is_forced_serial= 1 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 25, + 10, + 'Execution Plans', + 'Forced Serialization', + 'http://www.brentozar.com/blitzcache/forced-serialization/', + 'Something in your plan is forcing a serial query. Further investigation is needed if this is not by design.') ; -SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; -SET @sql_select += N' -WITH memory_max -AS ( SELECT TOP 1 - gi.start_range, - gi.end_range - FROM #grouped_interval AS gi - ORDER BY gi.total_avg_query_max_used_memory_mb DESC ) -INSERT #working_plans WITH (TABLOCK) - ( plan_id, query_id, pattern ) -SELECT TOP ( @sp_Top ) - qsp.plan_id, qsp.query_id, ''avg memory'' -FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp -JOIN memory_max AS dm -ON qsp.last_execution_time >= dm.start_range - AND qsp.last_execution_time < dm.end_range -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs -ON qsrs.plan_id = qsp.plan_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq -ON qsq.query_id = qsp.query_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt -ON qsqt.query_text_id = qsq.query_text_id -WHERE 1 = 1 - AND qsq.is_internal_query = 0 - AND qsp.query_plan IS NOT NULL - '; + IF EXISTS (SELECT 1/0 + FROM #working_warnings p + WHERE p.is_key_lookup_expensive= 1 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 26, + 100, + 'Execution Plans', + 'Expensive Key Lookups', + 'http://www.brentozar.com/blitzcache/expensive-key-lookups/', + 'There''s a key lookup in your plan that costs >=50% of the total plan cost.') ; -SET @sql_select += @sql_where; + IF EXISTS (SELECT 1/0 + FROM #working_warnings p + WHERE p.is_remote_query_expensive= 1 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 28, + 100, + 'Execution Plans', + 'Expensive Remote Query', + 'http://www.brentozar.com/blitzcache/expensive-remote-query/', + 'There''s a remote query in your plan that costs >=50% of the total plan cost.') ; -SET @sql_select += N'ORDER BY qsrs.avg_query_max_used_memory DESC - OPTION (RECOMPILE); - '; + IF EXISTS (SELECT 1/0 + FROM #working_warnings p + WHERE p.trace_flags_session IS NOT NULL + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 29, + 100, + 'Trace Flags', + 'Session Level Trace Flags Enabled', + 'https://www.brentozar.com/blitz/trace-flags-enabled-globally/', + 'Someone is enabling session level Trace Flags in a query.') ; -IF @Debug = 1 - PRINT @sql_select; + IF EXISTS (SELECT 1/0 + FROM #working_warnings p + WHERE p.is_unused_grant IS NOT NULL + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 30, + 100, + 'Unused memory grants', + 'Queries are asking for more memory than they''re using', + 'https://www.brentozar.com/blitzcache/unused-memory-grants/', + 'Queries have large unused memory grants. This can cause concurrency issues, if queries are waiting a long time to get memory to run.') ; -IF @sql_select IS NULL - BEGIN - RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; - RETURN; - END; + IF EXISTS (SELECT 1/0 + FROM #working_warnings p + WHERE p.function_count > 0 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 31, + 100, + 'Compute Scalar That References A Function', + 'This could be trouble if you''re using Scalar Functions or MSTVFs', + 'https://www.brentozar.com/blitzcache/compute-scalar-functions/', + 'Both of these will force queries to run serially, run at least once per row, and may result in poor cardinality estimates.') ; -EXEC sys.sp_executesql @stmt = @sql_select, - @params = @sp_params, - @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; + IF EXISTS (SELECT 1/0 + FROM #working_warnings p + WHERE p.clr_function_count > 0 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 32, + 100, + 'Compute Scalar That References A CLR Function', + 'This could be trouble if your CLR functions perform data access', + 'https://www.brentozar.com/blitzcache/compute-scalar-functions/', + 'May force queries to run serially, run at least once per row, and may result in poor cardinlity estimates.') ; -SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; -SET @sql_select += N' -WITH memory_max -AS ( SELECT TOP 1 - gi.start_range, - gi.end_range - FROM #grouped_interval AS gi - ORDER BY gi.total_max_query_max_used_memory_mb DESC ) -INSERT #working_plans WITH (TABLOCK) - ( plan_id, query_id, pattern ) -SELECT TOP ( @sp_Top ) - qsp.plan_id, qsp.query_id, ''max memory'' -FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp -JOIN memory_max AS dm -ON qsp.last_execution_time >= dm.start_range - AND qsp.last_execution_time < dm.end_range -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs -ON qsrs.plan_id = qsp.plan_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq -ON qsq.query_id = qsp.query_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt -ON qsqt.query_text_id = qsq.query_text_id -WHERE 1 = 1 - AND qsq.is_internal_query = 0 - AND qsp.query_plan IS NOT NULL - '; -SET @sql_select += @sql_where; + IF EXISTS (SELECT 1/0 + FROM #working_warnings p + WHERE p.is_table_variable = 1 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 33, + 100, + 'Table Variables detected', + 'Beware nasty side effects', + 'https://www.brentozar.com/blitzcache/table-variables/', + 'All modifications are single threaded, and selects have really low row estimates.') ; -SET @sql_select += N'ORDER BY qsrs.max_query_max_used_memory DESC - OPTION (RECOMPILE); - '; + IF EXISTS (SELECT 1/0 + FROM #working_warnings p + WHERE p.no_stats_warning = 1 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 35, + 100, + 'Columns with no statistics', + 'Poor cardinality estimates may ensue', + 'https://www.brentozar.com/blitzcache/columns-no-statistics/', + 'Sometimes this happens with indexed views, other times because auto create stats is turned off.') ; -IF @Debug = 1 - PRINT @sql_select; + IF EXISTS (SELECT 1/0 + FROM #working_warnings p + WHERE p.relop_warnings = 1 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 36, + 100, + 'Operator Warnings', + 'SQL is throwing operator level plan warnings', + 'http://brentozar.com/blitzcache/query-plan-warnings/', + 'Check the plan for more details.') ; -IF @sql_select IS NULL - BEGIN - RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; - RETURN; - END; + IF EXISTS (SELECT 1/0 + FROM #working_warnings p + WHERE p.is_table_scan = 1 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 37, + 100, + 'Table Scans', + 'Your database has HEAPs', + 'https://www.brentozar.com/archive/2012/05/video-heaps/', + 'This may not be a problem. Run sp_BlitzIndex for more information.') ; + + IF EXISTS (SELECT 1/0 + FROM #working_warnings p + WHERE p.backwards_scan = 1 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 38, + 100, + 'Backwards Scans', + 'Indexes are being read backwards', + 'https://www.brentozar.com/blitzcache/backwards-scans/', + 'This isn''t always a problem. They can cause serial zones in plans, and may need an index to match sort order.') ; -EXEC sys.sp_executesql @stmt = @sql_select, - @params = @sp_params, - @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; + IF EXISTS (SELECT 1/0 + FROM #working_warnings p + WHERE p.forced_index = 1 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 39, + 100, + 'Index forcing', + 'Someone is using hints to force index usage', + 'https://www.brentozar.com/blitzcache/optimizer-forcing/', + 'This can cause inefficient plans, and will prevent missing index requests.') ; + IF EXISTS (SELECT 1/0 + FROM #working_warnings p + WHERE p.forced_seek = 1 + OR p.forced_scan = 1 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 40, + 100, + 'Seek/Scan forcing', + 'Someone is using hints to force index seeks/scans', + 'https://www.brentozar.com/blitzcache/optimizer-forcing/', + 'This can cause inefficient plans by taking seek vs scan choice away from the optimizer.') ; -/*Get highest row count plans*/ + IF EXISTS (SELECT 1/0 + FROM #working_warnings p + WHERE p.columnstore_row_mode = 1 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 41, + 100, + 'ColumnStore indexes operating in Row Mode', + 'Batch Mode is optimal for ColumnStore indexes', + 'https://www.brentozar.com/blitzcache/columnstore-indexes-operating-row-mode/', + 'ColumnStore indexes operating in Row Mode indicate really poor query choices.') ; -RAISERROR(N'Gathering highest row count plans', 0, 1) WITH NOWAIT; + IF EXISTS (SELECT 1/0 + FROM #working_warnings p + WHERE p.is_computed_scalar = 1 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 42, + 50, + 'Computed Columns Referencing Scalar UDFs', + 'This makes a whole lot of stuff run serially', + 'https://www.brentozar.com/blitzcache/computed-columns-referencing-functions/', + 'This can cause a whole mess of bad serializartion problems.') ; -SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; -SET @sql_select += N' -WITH rowcount_max -AS ( SELECT TOP 1 - gi.start_range, - gi.end_range - FROM #grouped_interval AS gi - ORDER BY gi.total_rowcount DESC ) -INSERT #working_plans WITH (TABLOCK) - ( plan_id, query_id, pattern ) -SELECT TOP ( @sp_Top ) - qsp.plan_id, qsp.query_id, ''avg rows'' -FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp -JOIN rowcount_max AS dm -ON qsp.last_execution_time >= dm.start_range - AND qsp.last_execution_time < dm.end_range -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs -ON qsrs.plan_id = qsp.plan_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq -ON qsq.query_id = qsp.query_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt -ON qsqt.query_text_id = qsq.query_text_id -WHERE 1 = 1 - AND qsq.is_internal_query = 0 - AND qsp.query_plan IS NOT NULL - '; + IF EXISTS (SELECT 1/0 + FROM #working_warnings p + WHERE p.is_sort_expensive = 1 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 43, + 100, + 'Execution Plans', + 'Expensive Sort', + 'http://www.brentozar.com/blitzcache/expensive-sorts/', + 'There''s a sort in your plan that costs >=50% of the total plan cost.') ; -SET @sql_select += @sql_where; + IF EXISTS (SELECT 1/0 + FROM #working_warnings p + WHERE p.is_computed_filter = 1 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 44, + 50, + 'Filters Referencing Scalar UDFs', + 'This forces serialization', + 'https://www.brentozar.com/blitzcache/compute-scalar-functions/', + 'Someone put a Scalar UDF in the WHERE clause!') ; -SET @sql_select += N'ORDER BY qsrs.avg_rowcount DESC - OPTION (RECOMPILE); - '; + IF EXISTS (SELECT 1/0 + FROM #working_warnings p + WHERE p.index_ops >= 5 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 45, + 100, + 'Many Indexes Modified', + 'Write Queries Are Hitting >= 5 Indexes', + 'https://www.brentozar.com/blitzcache/many-indexes-modified/', + 'This can cause lots of hidden I/O -- Run sp_BlitzIndex for more information.') ; -IF @Debug = 1 - PRINT @sql_select; + IF EXISTS (SELECT 1/0 + FROM #working_warnings p + WHERE p.is_row_level = 1 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 46, + 100, + 'Plan Confusion', + 'Row Level Security is in use', + 'https://www.brentozar.com/blitzcache/row-level-security/', + 'You may see a lot of confusing junk in your query plan.') ; -IF @sql_select IS NULL - BEGIN - RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; - RETURN; - END; + IF EXISTS (SELECT 1/0 + FROM #working_warnings p + WHERE p.is_spatial = 1 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 47, + 200, + 'Spatial Abuse', + 'You hit a Spatial Index', + 'https://www.brentozar.com/blitzcache/spatial-indexes/', + 'Purely informational.') ; -EXEC sys.sp_executesql @stmt = @sql_select, - @params = @sp_params, - @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; + IF EXISTS (SELECT 1/0 + FROM #working_warnings p + WHERE p.index_dml = 1 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 48, + 150, + 'Index DML', + 'Indexes were created or dropped', + 'https://www.brentozar.com/blitzcache/index-dml/', + 'This can cause recompiles and stuff.') ; + IF EXISTS (SELECT 1/0 + FROM #working_warnings p + WHERE p.table_dml = 1 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 49, + 150, + 'Table DML', + 'Tables were created or dropped', + 'https://www.brentozar.com/blitzcache/table-dml/', + 'This can cause recompiles and stuff.') ; -IF @new_columns = 1 -BEGIN + IF EXISTS (SELECT 1/0 + FROM #working_warnings p + WHERE p.long_running_low_cpu = 1 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 50, + 150, + 'Long Running Low CPU', + 'You have a query that runs for much longer than it uses CPU', + 'https://www.brentozar.com/blitzcache/long-running-low-cpu/', + 'This can be a sign of blocking, linked servers, or poor client application code (ASYNC_NETWORK_IO).') ; -RAISERROR(N'Gathering new 2017 new column info...', 0, 1) WITH NOWAIT; + IF EXISTS (SELECT 1/0 + FROM #working_warnings p + WHERE p.low_cost_high_cpu = 1 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 51, + 150, + 'Low Cost Query With High CPU', + 'You have a low cost query that uses a lot of CPU', + 'https://www.brentozar.com/blitzcache/low-cost-high-cpu/', + 'This can be a sign of functions or Dynamic SQL that calls black-box code.') ; -/*Get highest log byte count plans*/ + IF EXISTS (SELECT 1/0 + FROM #working_warnings p + WHERE p.stale_stats = 1 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 52, + 150, + 'Biblical Statistics', + 'Statistics used in queries are >7 days old with >100k modifications', + 'https://www.brentozar.com/blitzcache/stale-statistics/', + 'Ever heard of updating statistics?') ; -RAISERROR(N'Gathering highest log byte use plans', 0, 1) WITH NOWAIT; + IF EXISTS (SELECT 1/0 + FROM #working_warnings p + WHERE p.is_adaptive = 1 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 53, + 150, + 'Adaptive joins', + 'This is pretty cool -- you''re living in the future.', + 'https://www.brentozar.com/blitzcache/adaptive-joins/', + 'Joe Sack rules.') ; -SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; -SET @sql_select += N' -WITH rowcount_max -AS ( SELECT TOP 1 - gi.start_range, - gi.end_range - FROM #grouped_interval AS gi - ORDER BY gi.total_avg_log_bytes_mb DESC ) -INSERT #working_plans WITH (TABLOCK) - ( plan_id, query_id, pattern ) -SELECT TOP ( @sp_Top ) - qsp.plan_id, qsp.query_id, ''avg log bytes'' -FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp -JOIN rowcount_max AS dm -ON qsp.last_execution_time >= dm.start_range - AND qsp.last_execution_time < dm.end_range -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs -ON qsrs.plan_id = qsp.plan_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq -ON qsq.query_id = qsp.query_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt -ON qsqt.query_text_id = qsq.query_text_id -WHERE 1 = 1 - AND qsq.is_internal_query = 0 - AND qsp.query_plan IS NOT NULL - '; + IF EXISTS (SELECT 1/0 + FROM #working_warnings p + WHERE p.is_spool_expensive = 1 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 54, + 150, + 'Expensive Index Spool', + 'You have an index spool, this is usually a sign that there''s an index missing somewhere.', + 'https://www.brentozar.com/blitzcache/eager-index-spools/', + 'Check operator predicates and output for index definition guidance') ; -SET @sql_select += @sql_where; + IF EXISTS (SELECT 1/0 + FROM #working_warnings p + WHERE p.is_spool_more_rows = 1 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 55, + 150, + 'Index Spools Many Rows', + 'You have an index spool that spools more rows than the query returns', + 'https://www.brentozar.com/blitzcache/eager-index-spools/', + 'Check operator predicates and output for index definition guidance') ; -SET @sql_select += N'ORDER BY qsrs.avg_log_bytes_used DESC - OPTION (RECOMPILE); - '; + IF EXISTS (SELECT 1/0 + FROM #working_warnings p + WHERE p.is_bad_estimate = 1 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 56, + 100, + 'Potentially bad cardinality estimates', + 'Estimated rows are different from average rows by a factor of 10000', + 'https://www.brentozar.com/blitzcache/bad-estimates/', + 'This may indicate a performance problem if mismatches occur regularly') ; -IF @Debug = 1 - PRINT @sql_select; + IF EXISTS (SELECT 1/0 + FROM #working_warnings p + WHERE p.is_big_log = 1 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 57, + 100, + 'High transaction log use', + 'This query on average uses more than half of the transaction log', + 'http://michaeljswart.com/2014/09/take-care-when-scripting-batches/', + 'This is probably a sign that you need to start batching queries') ; + + IF EXISTS (SELECT 1/0 + FROM #working_warnings p + WHERE p.is_big_tempdb = 1 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 58, + 100, + 'High tempdb use', + 'This query uses more than half of a data file on average', + 'No URL yet', + 'You should take a look at tempdb waits to see if you''re having problems') ; + + IF EXISTS (SELECT 1/0 + FROM #working_warnings p + WHERE p.is_row_goal = 1 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (59, + 200, + 'Row Goals', + 'This query had row goals introduced', + 'https://www.brentozar.com/archive/2018/01/sql-server-2017-cu3-adds-optimizer-row-goal-information-query-plans/', + 'This can be good or bad, and should be investigated for high read queries') ; -IF @sql_select IS NULL - BEGIN - RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; - RETURN; - END; + IF EXISTS (SELECT 1/0 + FROM #working_warnings p + WHERE p.is_mstvf = 1 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 60, + 100, + 'MSTVFs', + 'These have many of the same problems scalar UDFs have', + 'http://brentozar.com/blitzcache/tvf-join/', + 'Execution plans have been found that join to table valued functions (TVFs). TVFs produce inaccurate estimates of the number of rows returned and can lead to any number of query plan problems.'); -EXEC sys.sp_executesql @stmt = @sql_select, - @params = @sp_params, - @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; + IF EXISTS (SELECT 1/0 + FROM #working_warnings p + WHERE p.is_mstvf = 1 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (61, + 100, + 'Many to Many Merge', + 'These use secret worktables that could be doing lots of reads', + 'https://www.brentozar.com/archive/2018/04/many-mysteries-merge-joins/', + 'Occurs when join inputs aren''t known to be unique. Can be really bad when parallel.'); -RAISERROR(N'Gathering highest log byte use plans', 0, 1) WITH NOWAIT; + IF EXISTS (SELECT 1/0 + FROM #working_warnings p + WHERE p.is_nonsargable = 1 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (62, + 50, + 'Non-SARGable queries', + 'Queries may be using', + 'https://www.brentozar.com/blitzcache/non-sargable-predicates/', + 'Occurs when join inputs aren''t known to be unique. Can be really bad when parallel.'); + + IF EXISTS (SELECT 1/0 + FROM #working_warnings p + WHERE p.is_paul_white_electric = 1 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 998, + 200, + 'Is Paul White Electric?', + 'This query has a Switch operator in it!', + 'https://www.sql.kiwi/2013/06/hello-operator-my-switch-is-bored.html', + 'You should email this query plan to Paul: SQLkiwi at gmail dot com') ; + + + + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + SELECT + 999, + 200, + 'Database Level Statistics', + 'The database ' + sa.[database] + ' last had a stats update on ' + CONVERT(NVARCHAR(10), CONVERT(DATE, MAX(sa.last_update))) + ' and has ' + CONVERT(NVARCHAR(10), AVG(sa.modification_count)) + ' modifications on average.' AS Finding, + 'https://www.brentozar.com/blitzcache/stale-statistics/' AS URL, + 'Consider updating statistics more frequently,' AS Details + FROM #stats_agg AS sa + GROUP BY sa.[database] + HAVING MAX(sa.last_update) <= DATEADD(DAY, -7, SYSDATETIME()) + AND AVG(sa.modification_count) >= 100000; -SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; -SET @sql_select += N' -WITH rowcount_max -AS ( SELECT TOP 1 - gi.start_range, - gi.end_range - FROM #grouped_interval AS gi - ORDER BY gi.total_max_log_bytes_mb DESC ) -INSERT #working_plans WITH (TABLOCK) - ( plan_id, query_id, pattern ) -SELECT TOP ( @sp_Top ) - qsp.plan_id, qsp.query_id, ''max log bytes'' -FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp -JOIN rowcount_max AS dm -ON qsp.last_execution_time >= dm.start_range - AND qsp.last_execution_time < dm.end_range -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs -ON qsrs.plan_id = qsp.plan_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq -ON qsq.query_id = qsp.query_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt -ON qsqt.query_text_id = qsq.query_text_id -WHERE 1 = 1 - AND qsq.is_internal_query = 0 - AND qsp.query_plan IS NOT NULL - '; + + IF EXISTS (SELECT 1/0 + FROM #trace_flags AS tf + WHERE tf.global_trace_flags IS NOT NULL + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 1000, + 255, + 'Global Trace Flags Enabled', + 'You have Global Trace Flags enabled on your server', + 'https://www.brentozar.com/blitz/trace-flags-enabled-globally/', + 'You have the following Global Trace Flags enabled: ' + (SELECT TOP 1 tf.global_trace_flags FROM #trace_flags AS tf WHERE tf.global_trace_flags IS NOT NULL)) ; -SET @sql_select += @sql_where; -SET @sql_select += N'ORDER BY qsrs.max_log_bytes_used DESC - OPTION (RECOMPILE); - '; + /* + Return worsts + */ + WITH worsts AS ( + SELECT gi.flat_date, + gi.start_range, + gi.end_range, + gi.total_avg_duration_ms, + gi.total_avg_cpu_time_ms, + gi.total_avg_logical_io_reads_mb, + gi.total_avg_physical_io_reads_mb, + gi.total_avg_logical_io_writes_mb, + gi.total_avg_query_max_used_memory_mb, + gi.total_rowcount, + gi.total_avg_log_bytes_mb, + gi.total_avg_tempdb_space, + gi.total_max_duration_ms, + gi.total_max_cpu_time_ms, + gi.total_max_logical_io_reads_mb, + gi.total_max_physical_io_reads_mb, + gi.total_max_logical_io_writes_mb, + gi.total_max_query_max_used_memory_mb, + gi.total_max_log_bytes_mb, + gi.total_max_tempdb_space, + CONVERT(NVARCHAR(20), gi.flat_date) AS worst_date, + CASE WHEN DATEPART(HOUR, gi.start_range) = 0 THEN ' midnight ' + WHEN DATEPART(HOUR, gi.start_range) <= 12 THEN CONVERT(NVARCHAR(3), DATEPART(HOUR, gi.start_range)) + 'am ' + WHEN DATEPART(HOUR, gi.start_range) > 12 THEN CONVERT(NVARCHAR(3), DATEPART(HOUR, gi.start_range) -12) + 'pm ' + END AS worst_start_time, + CASE WHEN DATEPART(HOUR, gi.end_range) = 0 THEN ' midnight ' + WHEN DATEPART(HOUR, gi.end_range) <= 12 THEN CONVERT(NVARCHAR(3), DATEPART(HOUR, gi.end_range)) + 'am ' + WHEN DATEPART(HOUR, gi.end_range) > 12 THEN CONVERT(NVARCHAR(3), DATEPART(HOUR, gi.end_range) -12) + 'pm ' + END AS worst_end_time + FROM #grouped_interval AS gi + ), /*averages*/ + duration_worst AS ( + SELECT TOP 1 'Your worst avg duration range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg + FROM worsts + ORDER BY worsts.total_avg_duration_ms DESC + ), + cpu_worst AS ( + SELECT TOP 1 'Your worst avg cpu range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg + FROM worsts + ORDER BY worsts.total_avg_cpu_time_ms DESC + ), + logical_reads_worst AS ( + SELECT TOP 1 'Your worst avg logical read range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg + FROM worsts + ORDER BY worsts.total_avg_logical_io_reads_mb DESC + ), + physical_reads_worst AS ( + SELECT TOP 1 'Your worst avg physical read range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg + FROM worsts + ORDER BY worsts.total_avg_physical_io_reads_mb DESC + ), + logical_writes_worst AS ( + SELECT TOP 1 'Your worst avg logical write range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg + FROM worsts + ORDER BY worsts.total_avg_logical_io_writes_mb DESC + ), + memory_worst AS ( + SELECT TOP 1 'Your worst avg memory range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg + FROM worsts + ORDER BY worsts.total_avg_query_max_used_memory_mb DESC + ), + rowcount_worst AS ( + SELECT TOP 1 'Your worst avg row count range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg + FROM worsts + ORDER BY worsts.total_rowcount DESC + ), + logbytes_worst AS ( + SELECT TOP 1 'Your worst avg log bytes range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg + FROM worsts + ORDER BY worsts.total_avg_log_bytes_mb DESC + ), + tempdb_worst AS ( + SELECT TOP 1 'Your worst avg tempdb range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg + FROM worsts + ORDER BY worsts.total_avg_tempdb_space DESC + )/*maxes*/, + max_duration_worst AS ( + SELECT TOP 1 'Your worst max duration range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg + FROM worsts + ORDER BY worsts.total_max_duration_ms DESC + ), + max_cpu_worst AS ( + SELECT TOP 1 'Your worst max cpu range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg + FROM worsts + ORDER BY worsts.total_max_cpu_time_ms DESC + ), + max_logical_reads_worst AS ( + SELECT TOP 1 'Your worst max logical read range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg + FROM worsts + ORDER BY worsts.total_max_logical_io_reads_mb DESC + ), + max_physical_reads_worst AS ( + SELECT TOP 1 'Your worst max physical read range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg + FROM worsts + ORDER BY worsts.total_max_physical_io_reads_mb DESC + ), + max_logical_writes_worst AS ( + SELECT TOP 1 'Your worst max logical write range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg + FROM worsts + ORDER BY worsts.total_max_logical_io_writes_mb DESC + ), + max_memory_worst AS ( + SELECT TOP 1 'Your worst max memory range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg + FROM worsts + ORDER BY worsts.total_max_query_max_used_memory_mb DESC + ), + max_logbytes_worst AS ( + SELECT TOP 1 'Your worst max log bytes range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg + FROM worsts + ORDER BY worsts.total_max_log_bytes_mb DESC + ), + max_tempdb_worst AS ( + SELECT TOP 1 'Your worst max tempdb range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg + FROM worsts + ORDER BY worsts.total_max_tempdb_space DESC + ) + INSERT #warning_results ( CheckID, Priority, FindingsGroup, Finding, URL, Details ) + /*averages*/ + SELECT 1002, 255, 'Worsts', 'Worst Avg Duration', 'N/A', duration_worst.msg + FROM duration_worst + UNION ALL + SELECT 1002, 255, 'Worsts', 'Worst Avg CPU', 'N/A', cpu_worst.msg + FROM cpu_worst + UNION ALL + SELECT 1002, 255, 'Worsts', 'Worst Avg Logical Reads', 'N/A', logical_reads_worst.msg + FROM logical_reads_worst + UNION ALL + SELECT 1002, 255, 'Worsts', 'Worst Avg Physical Reads', 'N/A', physical_reads_worst.msg + FROM physical_reads_worst + UNION ALL + SELECT 1002, 255, 'Worsts', 'Worst Avg Logical Writes', 'N/A', logical_writes_worst.msg + FROM logical_writes_worst + UNION ALL + SELECT 1002, 255, 'Worsts', 'Worst Avg Memory', 'N/A', memory_worst.msg + FROM memory_worst + UNION ALL + SELECT 1002, 255, 'Worsts', 'Worst Row Counts', 'N/A', rowcount_worst.msg + FROM rowcount_worst + UNION ALL + SELECT 1002, 255, 'Worsts', 'Worst Avg Log Bytes', 'N/A', logbytes_worst.msg + FROM logbytes_worst + UNION ALL + SELECT 1002, 255, 'Worsts', 'Worst Avg tempdb', 'N/A', tempdb_worst.msg + FROM tempdb_worst + UNION ALL + /*maxes*/ + SELECT 1002, 255, 'Worsts', 'Worst Max Duration', 'N/A', max_duration_worst.msg + FROM max_duration_worst + UNION ALL + SELECT 1002, 255, 'Worsts', 'Worst Max CPU', 'N/A', max_cpu_worst.msg + FROM max_cpu_worst + UNION ALL + SELECT 1002, 255, 'Worsts', 'Worst Max Logical Reads', 'N/A', max_logical_reads_worst.msg + FROM max_logical_reads_worst + UNION ALL + SELECT 1002, 255, 'Worsts', 'Worst Max Physical Reads', 'N/A', max_physical_reads_worst.msg + FROM max_physical_reads_worst + UNION ALL + SELECT 1002, 255, 'Worsts', 'Worst Max Logical Writes', 'N/A', max_logical_writes_worst.msg + FROM max_logical_writes_worst + UNION ALL + SELECT 1002, 255, 'Worsts', 'Worst Max Memory', 'N/A', max_memory_worst.msg + FROM max_memory_worst + UNION ALL + SELECT 1002, 255, 'Worsts', 'Worst Max Log Bytes', 'N/A', max_logbytes_worst.msg + FROM max_logbytes_worst + UNION ALL + SELECT 1002, 255, 'Worsts', 'Worst Max tempdb', 'N/A', max_tempdb_worst.msg + FROM max_tempdb_worst + OPTION (RECOMPILE); + -IF @Debug = 1 - PRINT @sql_select; + IF NOT EXISTS (SELECT 1/0 + FROM #warning_results AS bcr + WHERE bcr.Priority = 2147483646 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (2147483646, + 255, + 'Need more help?' , + 'Paste your plan on the internet!', + 'http://pastetheplan.com', + 'This makes it easy to share plans and post them to Q&A sites like https://dba.stackexchange.com/!') ; -IF @sql_select IS NULL - BEGIN - RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; - RETURN; - END; -EXEC sys.sp_executesql @stmt = @sql_select, - @params = @sp_params, - @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; + IF NOT EXISTS (SELECT 1/0 + FROM #warning_results AS bcr + WHERE bcr.Priority = 2147483647 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 2147483647, + 255, + 'Thanks for using sp_BlitzQueryStore!' , + 'From Your Community Volunteers', + 'http://FirstResponderKit.org', + 'We hope you found this tool useful. Current version: ' + @Version + ' released on ' + CONVERT(NVARCHAR(30), @VersionDate) + '.') ; + + + SELECT Priority, + FindingsGroup, + Finding, + URL, + Details, + CheckID + FROM #warning_results + GROUP BY Priority, + FindingsGroup, + Finding, + URL, + Details, + CheckID + ORDER BY Priority ASC, FindingsGroup, Finding, CheckID ASC + OPTION (RECOMPILE); -/*Get highest tempdb use plans*/ -RAISERROR(N'Gathering highest tempdb use plans', 0, 1) WITH NOWAIT; -SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; -SET @sql_select += N' -WITH rowcount_max -AS ( SELECT TOP 1 - gi.start_range, - gi.end_range - FROM #grouped_interval AS gi - ORDER BY gi.total_avg_tempdb_space DESC ) -INSERT #working_plans WITH (TABLOCK) - ( plan_id, query_id, pattern ) -SELECT TOP ( @sp_Top ) - qsp.plan_id, qsp.query_id, ''avg tempdb space'' -FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp -JOIN rowcount_max AS dm -ON qsp.last_execution_time >= dm.start_range - AND qsp.last_execution_time < dm.end_range -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs -ON qsrs.plan_id = qsp.plan_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq -ON qsq.query_id = qsp.query_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt -ON qsqt.query_text_id = qsq.query_text_id -WHERE 1 = 1 - AND qsq.is_internal_query = 0 - AND qsp.query_plan IS NOT NULL - '; +END; -SET @sql_select += @sql_where; +END; +END TRY +BEGIN CATCH + RAISERROR (N'Failure returning warnings', 0,1) WITH NOWAIT; -SET @sql_select += N'ORDER BY qsrs.avg_tempdb_space_used DESC - OPTION (RECOMPILE); - '; + IF @sql_select IS NOT NULL + BEGIN + SET @msg = N'Last @sql_select: ' + @sql_select; + RAISERROR(@msg, 0, 1) WITH NOWAIT; + END; -IF @Debug = 1 - PRINT @sql_select; + SELECT @msg = @DatabaseName + N' database failed to process. ' + ERROR_MESSAGE(), @error_severity = ERROR_SEVERITY(), @error_state = ERROR_STATE(); + RAISERROR (@msg, @error_severity, @error_state) WITH NOWAIT; + + + WHILE @@TRANCOUNT > 0 + ROLLBACK; -IF @sql_select IS NULL - BEGIN - RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; RETURN; - END; - -EXEC sys.sp_executesql @stmt = @sql_select, - @params = @sp_params, - @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; - -SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; -SET @sql_select += N' -WITH rowcount_max -AS ( SELECT TOP 1 - gi.start_range, - gi.end_range - FROM #grouped_interval AS gi - ORDER BY gi.total_max_tempdb_space DESC ) -INSERT #working_plans WITH (TABLOCK) - ( plan_id, query_id, pattern ) -SELECT TOP ( @sp_Top ) - qsp.plan_id, qsp.query_id, ''max tempdb space'' -FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp -JOIN rowcount_max AS dm -ON qsp.last_execution_time >= dm.start_range - AND qsp.last_execution_time < dm.end_range -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs -ON qsrs.plan_id = qsp.plan_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq -ON qsq.query_id = qsp.query_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt -ON qsqt.query_text_id = qsq.query_text_id -WHERE 1 = 1 - AND qsq.is_internal_query = 0 - AND qsp.query_plan IS NOT NULL - '; +END CATCH; -SET @sql_select += @sql_where; +IF @Debug = 1 -SET @sql_select += N'ORDER BY qsrs.max_tempdb_space_used DESC - OPTION (RECOMPILE); - '; +BEGIN TRY -IF @Debug = 1 - PRINT @sql_select; +BEGIN -IF @sql_select IS NULL - BEGIN - RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; - RETURN; - END; +RAISERROR(N'Returning debugging data from temp tables', 0, 1) WITH NOWAIT; -EXEC sys.sp_executesql @stmt = @sql_select, - @params = @sp_params, - @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; +--Table content debugging +SELECT '#working_metrics' AS table_name, * +FROM #working_metrics AS wm +OPTION (RECOMPILE); -END; +SELECT '#working_plan_text' AS table_name, * +FROM #working_plan_text AS wpt +OPTION (RECOMPILE); +SELECT '#working_warnings' AS table_name, * +FROM #working_warnings AS ww +OPTION (RECOMPILE); -/* -This rolls up the different patterns we find before deduplicating. +SELECT '#working_wait_stats' AS table_name, * +FROM #working_wait_stats wws +OPTION (RECOMPILE); -The point of this is so we know if a query was gathered by one or more of the search queries +SELECT '#grouped_interval' AS table_name, * +FROM #grouped_interval +OPTION (RECOMPILE); -*/ +SELECT '#working_plans' AS table_name, * +FROM #working_plans +OPTION (RECOMPILE); -RAISERROR(N'Updating patterns', 0, 1) WITH NOWAIT; +SELECT '#stats_agg' AS table_name, * +FROM #stats_agg +OPTION (RECOMPILE); -WITH patterns AS ( -SELECT wp.plan_id, wp.query_id, - pattern_path = STUFF((SELECT DISTINCT N', ' + wp2.pattern - FROM #working_plans AS wp2 - WHERE wp.plan_id = wp2.plan_id - AND wp.query_id = wp2.query_id - FOR XML PATH(N''), TYPE).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 2, N'') -FROM #working_plans AS wp -) -UPDATE wp -SET wp.pattern = patterns.pattern_path -FROM #working_plans AS wp -JOIN patterns -ON wp.plan_id = patterns.plan_id -AND wp.query_id = patterns.query_id +SELECT '#trace_flags' AS table_name, * +FROM #trace_flags OPTION (RECOMPILE); +SELECT '#statements' AS table_name, * +FROM #statements AS s +OPTION (RECOMPILE); -/* -This dedupes our results so we hopefully don't double-work the same plan -*/ +SELECT '#query_plan' AS table_name, * +FROM #query_plan AS qp +OPTION (RECOMPILE); -RAISERROR(N'Deduplicating gathered plans', 0, 1) WITH NOWAIT; +SELECT '#relop' AS table_name, * +FROM #relop AS r +OPTION (RECOMPILE); -WITH dedupe AS ( -SELECT * , ROW_NUMBER() OVER (PARTITION BY wp.plan_id ORDER BY wp.plan_id) AS dupes -FROM #working_plans AS wp -) -DELETE dedupe -WHERE dedupe.dupes > 1 +SELECT '#plan_cost' AS table_name, * +FROM #plan_cost AS pc OPTION (RECOMPILE); -SET @msg = N'Removed ' + CONVERT(NVARCHAR(10), @@ROWCOUNT) + N' duplicate plan_ids.'; -RAISERROR(@msg, 0, 1) WITH NOWAIT; +SELECT '#est_rows' AS table_name, * +FROM #est_rows AS er +OPTION (RECOMPILE); +SELECT '#stored_proc_info' AS table_name, * +FROM #stored_proc_info AS spi +OPTION(RECOMPILE); -/* -This gathers data for the #working_metrics table -*/ +SELECT '#conversion_info' AS table_name, * +FROM #conversion_info AS ci +OPTION ( RECOMPILE ); +SELECT '#variable_info' AS table_name, * +FROM #variable_info AS vi +OPTION ( RECOMPILE ); -RAISERROR(N'Collecting worker metrics', 0, 1) WITH NOWAIT; +SELECT '#missing_index_xml' AS table_name, * +FROM #missing_index_xml +OPTION ( RECOMPILE ); -SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; -SET @sql_select += N' -SELECT ' + QUOTENAME(@DatabaseName, '''') + N' AS database_name, wp.plan_id, wp.query_id, - QUOTENAME(object_schema_name(qsq.object_id, DB_ID(' + QUOTENAME(@DatabaseName, '''') + N'))) + ''.'' + - QUOTENAME(object_name(qsq.object_id, DB_ID(' + QUOTENAME(@DatabaseName, '''') + N'))) AS proc_or_function_name, - qsq.batch_sql_handle, qsq.query_hash, qsq.query_parameterization_type_desc, qsq.count_compiles, - (qsq.avg_compile_duration / 1000.), - (qsq.last_compile_duration / 1000.), - (qsq.avg_bind_duration / 1000.), - (qsq.last_bind_duration / 1000.), - (qsq.avg_bind_cpu_time / 1000.), - (qsq.last_bind_cpu_time / 1000.), - (qsq.avg_optimize_duration / 1000.), - (qsq.last_optimize_duration / 1000.), - (qsq.avg_optimize_cpu_time / 1000.), - (qsq.last_optimize_cpu_time / 1000.), - (qsq.avg_compile_memory_kb / 1024.), - (qsq.last_compile_memory_kb / 1024.), - qsrs.execution_type_desc, qsrs.first_execution_time, qsrs.last_execution_time, qsrs.count_executions, - (qsrs.avg_duration / 1000.), - (qsrs.last_duration / 1000.), - (qsrs.min_duration / 1000.), - (qsrs.max_duration / 1000.), - (qsrs.avg_cpu_time / 1000.), - (qsrs.last_cpu_time / 1000.), - (qsrs.min_cpu_time / 1000.), - (qsrs.max_cpu_time / 1000.), - ((qsrs.avg_logical_io_reads * 8 ) / 1024.), - ((qsrs.last_logical_io_reads * 8 ) / 1024.), - ((qsrs.min_logical_io_reads * 8 ) / 1024.), - ((qsrs.max_logical_io_reads * 8 ) / 1024.), - ((qsrs.avg_logical_io_writes * 8 ) / 1024.), - ((qsrs.last_logical_io_writes * 8 ) / 1024.), - ((qsrs.min_logical_io_writes * 8 ) / 1024.), - ((qsrs.max_logical_io_writes * 8 ) / 1024.), - ((qsrs.avg_physical_io_reads * 8 ) / 1024.), - ((qsrs.last_physical_io_reads * 8 ) / 1024.), - ((qsrs.min_physical_io_reads * 8 ) / 1024.), - ((qsrs.max_physical_io_reads * 8 ) / 1024.), - (qsrs.avg_clr_time / 1000.), - (qsrs.last_clr_time / 1000.), - (qsrs.min_clr_time / 1000.), - (qsrs.max_clr_time / 1000.), - qsrs.avg_dop, qsrs.last_dop, qsrs.min_dop, qsrs.max_dop, - ((qsrs.avg_query_max_used_memory * 8 ) / 1024.), - ((qsrs.last_query_max_used_memory * 8 ) / 1024.), - ((qsrs.min_query_max_used_memory * 8 ) / 1024.), - ((qsrs.max_query_max_used_memory * 8 ) / 1024.), - qsrs.avg_rowcount, qsrs.last_rowcount, qsrs.min_rowcount, qsrs.max_rowcount,'; - - IF @new_columns = 1 - BEGIN - SET @sql_select += N' - qsrs.avg_num_physical_io_reads, qsrs.last_num_physical_io_reads, qsrs.min_num_physical_io_reads, qsrs.max_num_physical_io_reads, - (qsrs.avg_log_bytes_used / 100000000), - (qsrs.last_log_bytes_used / 100000000), - (qsrs.min_log_bytes_used / 100000000), - (qsrs.max_log_bytes_used / 100000000), - ((qsrs.avg_tempdb_space_used * 8 ) / 1024.), - ((qsrs.last_tempdb_space_used * 8 ) / 1024.), - ((qsrs.min_tempdb_space_used * 8 ) / 1024.), - ((qsrs.max_tempdb_space_used * 8 ) / 1024.) - '; - END; - IF @new_columns = 0 - BEGIN - SET @sql_select += N' - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL - '; - END; -SET @sql_select += -N'FROM #working_plans AS wp -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq -ON wp.query_id = qsq.query_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs -ON qsrs.plan_id = wp.plan_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp -ON qsp.plan_id = wp.plan_id -AND qsp.query_id = wp.query_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt -ON qsqt.query_text_id = qsq.query_text_id -WHERE 1 = 1 - AND qsq.is_internal_query = 0 - AND qsp.query_plan IS NOT NULL - '; +SELECT '#missing_index_schema' AS table_name, * +FROM #missing_index_schema +OPTION ( RECOMPILE ); -SET @sql_select += @sql_where; +SELECT '#missing_index_usage' AS table_name, * +FROM #missing_index_usage +OPTION ( RECOMPILE ); -SET @sql_select += N'OPTION (RECOMPILE); - '; +SELECT '#missing_index_detail' AS table_name, * +FROM #missing_index_detail +OPTION ( RECOMPILE ); -IF @Debug = 1 - PRINT @sql_select; +SELECT '#missing_index_pretty' AS table_name, * +FROM #missing_index_pretty +OPTION ( RECOMPILE ); -IF @sql_select IS NULL - BEGIN - RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; - RETURN; - END; +END; -INSERT #working_metrics WITH (TABLOCK) - ( database_name, plan_id, query_id, - proc_or_function_name, - batch_sql_handle, query_hash, query_parameterization_type_desc, count_compiles, - avg_compile_duration, last_compile_duration, avg_bind_duration, last_bind_duration, avg_bind_cpu_time, last_bind_cpu_time, avg_optimize_duration, - last_optimize_duration, avg_optimize_cpu_time, last_optimize_cpu_time, avg_compile_memory_kb, last_compile_memory_kb, execution_type_desc, - first_execution_time, last_execution_time, count_executions, avg_duration, last_duration, min_duration, max_duration, avg_cpu_time, last_cpu_time, - min_cpu_time, max_cpu_time, avg_logical_io_reads, last_logical_io_reads, min_logical_io_reads, max_logical_io_reads, avg_logical_io_writes, - last_logical_io_writes, min_logical_io_writes, max_logical_io_writes, avg_physical_io_reads, last_physical_io_reads, min_physical_io_reads, - max_physical_io_reads, avg_clr_time, last_clr_time, min_clr_time, max_clr_time, avg_dop, last_dop, min_dop, max_dop, avg_query_max_used_memory, - last_query_max_used_memory, min_query_max_used_memory, max_query_max_used_memory, avg_rowcount, last_rowcount, min_rowcount, max_rowcount, - /* 2017 only columns */ - avg_num_physical_io_reads, last_num_physical_io_reads, min_num_physical_io_reads, max_num_physical_io_reads, - avg_log_bytes_used, last_log_bytes_used, min_log_bytes_used, max_log_bytes_used, - avg_tempdb_space_used, last_tempdb_space_used, min_tempdb_space_used, max_tempdb_space_used ) +END TRY +BEGIN CATCH + RAISERROR (N'Failure returning debug temp tables', 0,1) WITH NOWAIT; -EXEC sys.sp_executesql @stmt = @sql_select, - @params = @sp_params, - @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; + IF @sql_select IS NOT NULL + BEGIN + SET @msg = N'Last @sql_select: ' + @sql_select; + RAISERROR(@msg, 0, 1) WITH NOWAIT; + END; + SELECT @msg = @DatabaseName + N' database failed to process. ' + ERROR_MESSAGE(), @error_severity = ERROR_SEVERITY(), @error_state = ERROR_STATE(); + RAISERROR (@msg, @error_severity, @error_state) WITH NOWAIT; + + + WHILE @@TRANCOUNT > 0 + ROLLBACK; + RETURN; +END CATCH; -/*This just helps us classify our queries*/ -UPDATE #working_metrics -SET proc_or_function_name = N'Statement' -WHERE proc_or_function_name IS NULL -OPTION(RECOMPILE); +/* +Ways to run this thing -SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; -SET @sql_select += N' - WITH patterns AS ( - SELECT query_id, planid_path = STUFF((SELECT DISTINCT N'', '' + RTRIM(qsp2.plan_id) - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp2 - WHERE qsp.query_id = qsp2.query_id - FOR XML PATH(N''''), TYPE).value(N''.[1]'', N''NVARCHAR(MAX)''), 1, 2, N'''') - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp - ) - UPDATE wm - SET wm.query_id_all_plan_ids = patterns.planid_path - FROM #working_metrics AS wm - JOIN patterns - ON wm.query_id = patterns.query_id - OPTION (RECOMPILE); -' +--Debug +EXEC sp_BlitzQueryStore @DatabaseName = 'StackOverflow', @Debug = 1 -EXEC sys.sp_executesql @stmt = @sql_select; +--Get the top 1 +EXEC sp_BlitzQueryStore @DatabaseName = 'StackOverflow', @Top = 1, @Debug = 1 -/* -This gathers data for the #working_plan_text table -*/ +--Use a StartDate +EXEC sp_BlitzQueryStore @DatabaseName = 'StackOverflow', @Top = 1, @StartDate = '20170527' + +--Use an EndDate +EXEC sp_BlitzQueryStore @DatabaseName = 'StackOverflow', @Top = 1, @EndDate = '20170527' + +--Use Both +EXEC sp_BlitzQueryStore @DatabaseName = 'StackOverflow', @Top = 1, @StartDate = '20170526', @EndDate = '20170527' +--Set a minimum execution count +EXEC sp_BlitzQueryStore @DatabaseName = 'StackOverflow', @Top = 1, @MinimumExecutionCount = 10 -RAISERROR(N'Gathering working plans', 0, 1) WITH NOWAIT; +--Set a duration minimum +EXEC sp_BlitzQueryStore @DatabaseName = 'StackOverflow', @Top = 1, @DurationFilter = 5 -SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; -SET @sql_select += N' -SELECT ' + QUOTENAME(@DatabaseName, '''') + N' AS database_name, wp.plan_id, wp.query_id, - qsp.plan_group_id, qsp.engine_version, qsp.compatibility_level, qsp.query_plan_hash, TRY_CONVERT(XML, qsp.query_plan), qsp.is_online_index_plan, qsp.is_trivial_plan, - qsp.is_parallel_plan, qsp.is_forced_plan, qsp.is_natively_compiled, qsp.force_failure_count, qsp.last_force_failure_reason_desc, qsp.count_compiles, - qsp.initial_compile_start_time, qsp.last_compile_start_time, qsp.last_execution_time, - (qsp.avg_compile_duration / 1000.), - (qsp.last_compile_duration / 1000.), - qsqt.query_sql_text, qsqt.statement_sql_handle, qsqt.is_part_of_encrypted_module, qsqt.has_restricted_text -FROM #working_plans AS wp -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp -ON qsp.plan_id = wp.plan_id - AND qsp.query_id = wp.query_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq -ON wp.query_id = qsq.query_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt -ON qsqt.query_text_id = qsq.query_text_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs -ON qsrs.plan_id = wp.plan_id -WHERE 1 = 1 - AND qsq.is_internal_query = 0 - AND qsp.query_plan IS NOT NULL - '; +--Look for a stored procedure name (that doesn't exist!) +EXEC sp_BlitzQueryStore @DatabaseName = 'StackOverflow', @Top = 1, @StoredProcName = 'blah' -SET @sql_select += @sql_where; +--Look for a stored procedure name that does (at least On My Computer®) +EXEC sp_BlitzQueryStore @DatabaseName = 'StackOverflow', @Top = 1, @StoredProcName = 'UserReportExtended' -SET @sql_select += N'OPTION (RECOMPILE); - '; +--Look for failed queries +EXEC sp_BlitzQueryStore @DatabaseName = 'StackOverflow', @Top = 1, @Failed = 1 -IF @Debug = 1 - PRINT @sql_select; +--Filter by plan_id +EXEC sp_BlitzQueryStore @DatabaseName = 'StackOverflow', @PlanIdFilter = 3356 -IF @sql_select IS NULL - BEGIN - RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; - RETURN; - END; +--Filter by query_id +EXEC sp_BlitzQueryStore @DatabaseName = 'StackOverflow', @QueryIdFilter = 2958 -INSERT #working_plan_text WITH (TABLOCK) - ( database_name, plan_id, query_id, - plan_group_id, engine_version, compatibility_level, query_plan_hash, query_plan_xml, is_online_index_plan, is_trivial_plan, - is_parallel_plan, is_forced_plan, is_natively_compiled, force_failure_count, last_force_failure_reason_desc, count_compiles, - initial_compile_start_time, last_compile_start_time, last_execution_time, avg_compile_duration, last_compile_duration, - query_sql_text, statement_sql_handle, is_part_of_encrypted_module, has_restricted_text ) +*/ -EXEC sys.sp_executesql @stmt = @sql_select, - @params = @sp_params, - @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; +END; +GO +IF OBJECT_ID('dbo.sp_BlitzWho') IS NULL + EXEC ('CREATE PROCEDURE dbo.sp_BlitzWho AS RETURN 0;') +GO +ALTER PROCEDURE dbo.sp_BlitzWho + @Help TINYINT = 0 , + @ShowSleepingSPIDs TINYINT = 0, + @ExpertMode BIT = 0, + @Debug BIT = 0, + @OutputDatabaseName NVARCHAR(256) = NULL , + @OutputSchemaName NVARCHAR(256) = NULL , + @OutputTableName NVARCHAR(256) = NULL , + @OutputTableRetentionDays TINYINT = 3 , + @MinElapsedSeconds INT = 0 , + @MinCPUTime INT = 0 , + @MinLogicalReads INT = 0 , + @MinPhysicalReads INT = 0 , + @MinWrites INT = 0 , + @MinTempdbMB INT = 0 , + @MinRequestedMemoryKB INT = 0 , + @MinBlockingSeconds INT = 0 , + @CheckDateOverride DATETIMEOFFSET = NULL, + @Version VARCHAR(30) = NULL OUTPUT, + @VersionDate DATETIME = NULL OUTPUT, + @VersionCheckMode BIT = 0, + @SortOrder NVARCHAR(256) = N'elapsed time' +AS +BEGIN + SET NOCOUNT ON; + SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + + SELECT @Version = '8.01', @VersionDate = '20210222'; + + IF(@VersionCheckMode = 1) + BEGIN + RETURN; + END; -/* -This gets us context settings for our queries and adds it to the #working_plan_text table -*/ -RAISERROR(N'Gathering context settings', 0, 1) WITH NOWAIT; -SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; -SET @sql_select += N' -UPDATE wp -SET wp.context_settings = SUBSTRING( - CASE WHEN (CAST(qcs.set_options AS INT) & 1 = 1) THEN '', ANSI_PADDING'' ELSE '''' END + - CASE WHEN (CAST(qcs.set_options AS INT) & 8 = 8) THEN '', CONCAT_NULL_YIELDS_NULL'' ELSE '''' END + - CASE WHEN (CAST(qcs.set_options AS INT) & 16 = 16) THEN '', ANSI_WARNINGS'' ELSE '''' END + - CASE WHEN (CAST(qcs.set_options AS INT) & 32 = 32) THEN '', ANSI_NULLS'' ELSE '''' END + - CASE WHEN (CAST(qcs.set_options AS INT) & 64 = 64) THEN '', QUOTED_IDENTIFIER'' ELSE '''' END + - CASE WHEN (CAST(qcs.set_options AS INT) & 4096 = 4096) THEN '', ARITH_ABORT'' ELSE '''' END + - CASE WHEN (CAST(qcs.set_options AS INT) & 8192 = 8192) THEN '', NUMERIC_ROUNDABORT'' ELSE '''' END - , 2, 200000) -FROM #working_plan_text wp -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq -ON wp.query_id = qsq.query_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_context_settings AS qcs -ON qcs.context_settings_id = qsq.context_settings_id -OPTION (RECOMPILE); -'; + IF @Help = 1 + BEGIN + PRINT ' +sp_BlitzWho from http://FirstResponderKit.org -IF @Debug = 1 - PRINT @sql_select; +This script gives you a snapshot of everything currently executing on your SQL Server. -IF @sql_select IS NULL - BEGIN - RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; - RETURN; - END; +To learn more, visit http://FirstResponderKit.org where you can download new +versions for free, watch training videos on how it works, get more info on +the findings, contribute your own code, and more. -EXEC sys.sp_executesql @stmt = @sql_select; +Known limitations of this version: + - Only Microsoft-supported versions of SQL Server. Sorry, 2005 and 2000. + - Outputting to table is only supported with SQL Server 2012 and higher. + - If @OutputDatabaseName and @OutputSchemaName are populated, the database and + schema must already exist. We will not create them, only the table. + +MIT License +Copyright (c) 2021 Brent Ozar Unlimited -/*This adds the patterns we found from each interval to the #working_plan_text table*/ +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: -RAISERROR(N'Add patterns to working plans', 0, 1) WITH NOWAIT; +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. -UPDATE wpt -SET wpt.pattern = wp.pattern -FROM #working_plans AS wp -JOIN #working_plan_text AS wpt -ON wpt.plan_id = wp.plan_id -AND wpt.query_id = wp.query_id -OPTION (RECOMPILE); +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +'; +RETURN; +END; /* @Help = 1 */ -/*This cleans up query text a bit*/ +/* Get the major and minor build numbers */ +DECLARE @ProductVersion NVARCHAR(128) + ,@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]) + ,@EnhanceFlag BIT = 0 + ,@BlockingCheck NVARCHAR(MAX) + ,@StringToSelect NVARCHAR(MAX) + ,@StringToExecute NVARCHAR(MAX) + ,@OutputTableCleanupDate DATE + ,@SessionWaits BIT = 0 + ,@SessionWaitsSQL NVARCHAR(MAX) = + N'LEFT JOIN ( SELECT DISTINCT + wait.session_id , + ( SELECT TOP 5 waitwait.wait_type + N'' ('' + + CAST(MAX(waitwait.wait_time_ms) AS NVARCHAR(128)) + + N'' ms), '' + FROM sys.dm_exec_session_wait_stats AS waitwait + WHERE waitwait.session_id = wait.session_id + GROUP BY waitwait.wait_type + HAVING SUM(waitwait.wait_time_ms) > 5 + ORDER BY 1 + FOR + XML PATH('''') ) AS session_wait_info + FROM sys.dm_exec_session_wait_stats AS wait ) AS wt2 + ON s.session_id = wt2.session_id + LEFT JOIN sys.dm_exec_query_stats AS session_stats + ON r.sql_handle = session_stats.sql_handle + AND r.plan_handle = session_stats.plan_handle + AND r.statement_start_offset = session_stats.statement_start_offset + AND r.statement_end_offset = session_stats.statement_end_offset' + ,@QueryStatsXMLselect NVARCHAR(MAX) = N' CAST(COALESCE(qs_live.query_plan, '''') AS XML) AS live_query_plan , ' + ,@QueryStatsXMLSQL NVARCHAR(MAX) = N'OUTER APPLY sys.dm_exec_query_statistics_xml(s.session_id) qs_live' + ,@ObjectFullName NVARCHAR(2000) + ,@OutputTableNameQueryStats_View NVARCHAR(256) + ,@LineFeed NVARCHAR(MAX) /* Had to set as MAX up from 10 as it was truncating the view creation*/; -RAISERROR(N'Clean awkward characters from query text', 0, 1) WITH NOWAIT; +/* Let's get @SortOrder set to lower case here for comparisons later */ +SET @SortOrder = REPLACE(LOWER(@SortOrder), N' ', N'_'); -UPDATE b -SET b.query_sql_text = REPLACE(REPLACE(REPLACE(b.query_sql_text, @cr, ' '), @lf, ' '), @tab, ' ') -FROM #working_plan_text AS b -OPTION (RECOMPILE); +SET @ProductVersion = CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)); +SELECT @ProductVersionMajor = SUBSTRING(@ProductVersion, 1,CHARINDEX('.', @ProductVersion) + 1 ), + @ProductVersionMinor = PARSENAME(CONVERT(VARCHAR(32), @ProductVersion), 2) +IF EXISTS (SELECT * FROM sys.all_columns WHERE object_id = OBJECT_ID('sys.dm_exec_query_statistics_xml') AND name = 'query_plan') + BEGIN + SET @QueryStatsXMLselect = N' CAST(COALESCE(qs_live.query_plan, '''') AS XML) AS live_query_plan , '; + SET @QueryStatsXMLSQL = N'OUTER APPLY sys.dm_exec_query_statistics_xml(s.session_id) qs_live'; + END + ELSE + BEGIN + SET @QueryStatsXMLselect = N' NULL AS live_query_plan , '; + SET @QueryStatsXMLSQL = N' '; + END +SELECT + @OutputTableNameQueryStats_View = QUOTENAME(@OutputTableName + '_Deltas'), + @OutputDatabaseName = QUOTENAME(@OutputDatabaseName), + @OutputSchemaName = QUOTENAME(@OutputSchemaName), + @OutputTableName = QUOTENAME(@OutputTableName), + @LineFeed = CHAR(13) + CHAR(10); -/*This populates #working_wait_stats when available*/ +IF @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @OutputTableName IS NOT NULL + AND EXISTS ( SELECT * + FROM sys.databases + WHERE QUOTENAME([name]) = @OutputDatabaseName) + BEGIN + SET @ExpertMode = 1; /* Force ExpertMode when we're logging to table */ -IF @waitstats = 1 + /* Create the table if it doesn't exist */ + SET @StringToExecute = N'USE ' + + @OutputDatabaseName + + N'; IF EXISTS(SELECT * FROM ' + + @OutputDatabaseName + + N'.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' + + @OutputSchemaName + + N''') AND NOT EXISTS (SELECT * FROM ' + + @OutputDatabaseName + + N'.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''' + + @OutputSchemaName + N''' AND QUOTENAME(TABLE_NAME) = ''' + + @OutputTableName + N''') CREATE TABLE ' + + @OutputSchemaName + N'.' + + @OutputTableName + + N'('; + SET @StringToExecute = @StringToExecute + N' + ID INT IDENTITY(1,1) NOT NULL, + ServerName NVARCHAR(128) NOT NULL, + CheckDate DATETIMEOFFSET NOT NULL, + [elapsed_time] [varchar](41) NULL, + [session_id] [smallint] NOT NULL, + [database_name] [nvarchar](128) NULL, + [query_text] [nvarchar](max) NULL, + [query_plan] [xml] NULL, + [live_query_plan] [xml] NULL, + [query_cost] [float] NULL, + [status] [nvarchar](30) NOT NULL, + [wait_info] [nvarchar](max) NULL, + [top_session_waits] [nvarchar](max) NULL, + [blocking_session_id] [smallint] NULL, + [open_transaction_count] [int] NULL, + [is_implicit_transaction] [int] NOT NULL, + [nt_domain] [nvarchar](128) NULL, + [host_name] [nvarchar](128) NULL, + [login_name] [nvarchar](128) NOT NULL, + [nt_user_name] [nvarchar](128) NULL, + [program_name] [nvarchar](128) NULL, + [fix_parameter_sniffing] [nvarchar](150) NULL, + [client_interface_name] [nvarchar](32) NULL, + [login_time] [datetime] NOT NULL, + [start_time] [datetime] NULL, + [request_time] [datetime] NULL, + [request_cpu_time] [int] NULL, + [request_logical_reads] [bigint] NULL, + [request_writes] [bigint] NULL, + [request_physical_reads] [bigint] NULL, + [session_cpu] [int] NOT NULL, + [session_logical_reads] [bigint] NOT NULL, + [session_physical_reads] [bigint] NOT NULL, + [session_writes] [bigint] NOT NULL, + [tempdb_allocations_mb] [decimal](38, 2) NULL, + [memory_usage] [int] NOT NULL, + [estimated_completion_time] [bigint] NULL, + [percent_complete] [real] NULL, + [deadlock_priority] [int] NULL, + [transaction_isolation_level] [varchar](33) NOT NULL, + [degree_of_parallelism] [smallint] NULL, + [last_dop] [bigint] NULL, + [min_dop] [bigint] NULL, + [max_dop] [bigint] NULL, + [last_grant_kb] [bigint] NULL, + [min_grant_kb] [bigint] NULL, + [max_grant_kb] [bigint] NULL, + [last_used_grant_kb] [bigint] NULL, + [min_used_grant_kb] [bigint] NULL, + [max_used_grant_kb] [bigint] NULL, + [last_ideal_grant_kb] [bigint] NULL, + [min_ideal_grant_kb] [bigint] NULL, + [max_ideal_grant_kb] [bigint] NULL, + [last_reserved_threads] [bigint] NULL, + [min_reserved_threads] [bigint] NULL, + [max_reserved_threads] [bigint] NULL, + [last_used_threads] [bigint] NULL, + [min_used_threads] [bigint] NULL, + [max_used_threads] [bigint] NULL, + [grant_time] [varchar](20) NULL, + [requested_memory_kb] [bigint] NULL, + [grant_memory_kb] [bigint] NULL, + [is_request_granted] [varchar](39) NOT NULL, + [required_memory_kb] [bigint] NULL, + [query_memory_grant_used_memory_kb] [bigint] NULL, + [ideal_memory_kb] [bigint] NULL, + [is_small] [bit] NULL, + [timeout_sec] [int] NULL, + [resource_semaphore_id] [smallint] NULL, + [wait_order] [varchar](20) NULL, + [wait_time_ms] [varchar](20) NULL, + [next_candidate_for_memory_grant] [varchar](3) NOT NULL, + [target_memory_kb] [bigint] NULL, + [max_target_memory_kb] [varchar](30) NULL, + [total_memory_kb] [bigint] NULL, + [available_memory_kb] [bigint] NULL, + [granted_memory_kb] [bigint] NULL, + [query_resource_semaphore_used_memory_kb] [bigint] NULL, + [grantee_count] [int] NULL, + [waiter_count] [int] NULL, + [timeout_error_count] [bigint] NULL, + [forced_grant_count] [varchar](30) NULL, + [workload_group_name] [sysname] NULL, + [resource_pool_name] [sysname] NULL, + [context_info] [varchar](128) NULL, + [query_hash] [binary](8) NULL, + [query_plan_hash] [binary](8) NULL, + [sql_handle] [varbinary] (64) NULL, + [plan_handle] [varbinary] (64) NULL, + [statement_start_offset] INT NULL, + [statement_end_offset] INT NULL, + JoinKey AS ServerName + CAST(CheckDate AS NVARCHAR(50)), + PRIMARY KEY CLUSTERED (ID ASC));'; + IF @Debug = 1 + BEGIN + PRINT CONVERT(VARCHAR(8000), SUBSTRING(@StringToExecute, 0, 8000)) + PRINT CONVERT(VARCHAR(8000), SUBSTRING(@StringToExecute, 8000, 16000)) + END + EXEC(@StringToExecute); - BEGIN - - RAISERROR(N'Collecting wait stats info', 0, 1) WITH NOWAIT; - - - SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; - SET @sql_select += N' - SELECT qws.plan_id, - qws.wait_category, - qws.wait_category_desc, - SUM(qws.total_query_wait_time_ms) AS total_query_wait_time_ms, - SUM(qws.avg_query_wait_time_ms) AS avg_query_wait_time_ms, - SUM(qws.last_query_wait_time_ms) AS last_query_wait_time_ms, - SUM(qws.min_query_wait_time_ms) AS min_query_wait_time_ms, - SUM(qws.max_query_wait_time_ms) AS max_query_wait_time_ms - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_wait_stats qws - JOIN #working_plans AS wp - ON qws.plan_id = wp.plan_id - GROUP BY qws.plan_id, qws.wait_category, qws.wait_category_desc - HAVING SUM(qws.min_query_wait_time_ms) >= 5 - OPTION (RECOMPILE); - '; - - IF @Debug = 1 - PRINT @sql_select; - - IF @sql_select IS NULL - BEGIN - RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; - RETURN; - END; - - INSERT #working_wait_stats WITH (TABLOCK) - ( plan_id, wait_category, wait_category_desc, total_query_wait_time_ms, avg_query_wait_time_ms, last_query_wait_time_ms, min_query_wait_time_ms, max_query_wait_time_ms ) - - EXEC sys.sp_executesql @stmt = @sql_select; - - - /*This updates #working_plan_text with the top three waits from the wait stats DMV*/ - - RAISERROR(N'Update working_plan_text with top three waits', 0, 1) WITH NOWAIT; - - - UPDATE wpt - SET wpt.top_three_waits = x.top_three_waits - FROM #working_plan_text AS wpt - JOIN ( - SELECT wws.plan_id, - top_three_waits = STUFF((SELECT TOP 3 N', ' + wws2.wait_category_desc + N' (' + CONVERT(NVARCHAR(20), SUM(CONVERT(BIGINT, wws2.avg_query_wait_time_ms))) + N' ms) ' - FROM #working_wait_stats AS wws2 - WHERE wws.plan_id = wws2.plan_id - GROUP BY wws2.wait_category_desc - ORDER BY SUM(wws2.avg_query_wait_time_ms) DESC - FOR XML PATH(N''), TYPE).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 2, N'') - FROM #working_wait_stats AS wws - GROUP BY wws.plan_id - ) AS x - ON x.plan_id = wpt.plan_id - OPTION (RECOMPILE); + /* If the table doesn't have the new JoinKey computed column, add it. See Github #2162. */ + SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableName; + SET @StringToExecute = N'IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns + WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''JoinKey'') + ALTER TABLE ' + @ObjectFullName + N' ADD JoinKey AS ServerName + CAST(CheckDate AS NVARCHAR(50));'; + EXEC(@StringToExecute); -END; + /* Delete history older than @OutputTableRetentionDays */ + SET @OutputTableCleanupDate = CAST( (DATEADD(DAY, -1 * @OutputTableRetentionDays, GETDATE() ) ) AS DATE); + SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' + + @OutputDatabaseName + + N'.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' + + @OutputSchemaName + N''') DELETE ' + + @OutputDatabaseName + '.' + + @OutputSchemaName + '.' + + @OutputTableName + + N' WHERE ServerName = @SrvName AND CheckDate < @CheckDate;'; + IF @Debug = 1 + BEGIN + PRINT CONVERT(VARCHAR(8000), SUBSTRING(@StringToExecute, 0, 8000)) + PRINT CONVERT(VARCHAR(8000), SUBSTRING(@StringToExecute, 8000, 16000)) + END + EXEC sp_executesql @StringToExecute, + N'@SrvName NVARCHAR(128), @CheckDate date', + @@SERVERNAME, @OutputTableCleanupDate; -/*End wait stats population*/ + SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableNameQueryStats_View; + + /* Create the view */ + IF OBJECT_ID(@ObjectFullName) IS NULL + BEGIN + SET @StringToExecute = N'USE ' + + @OutputDatabaseName + + N'; EXEC (''CREATE VIEW ' + + @OutputSchemaName + '.' + + @OutputTableNameQueryStats_View + N' AS ' + @LineFeed + + N'WITH MaxQueryDuration AS ' + @LineFeed + + N'( ' + @LineFeed + + N' SELECT ' + @LineFeed + + N' MIN([ID]) AS [MinID], ' + @LineFeed + + N' MAX([ID]) AS [MaxID] ' + @LineFeed + + N' FROM ' + @OutputSchemaName + '.' + @OutputTableName + '' + @LineFeed + + N' GROUP BY [ServerName], ' + @LineFeed + + N' [session_id], ' + @LineFeed + + N' [database_name], ' + @LineFeed + + N' [request_time], ' + @LineFeed + + N' [start_time], ' + @LineFeed + + N' [sql_handle] ' + @LineFeed + + N') ' + @LineFeed + + N'SELECT ' + @LineFeed + + N' [ID], ' + @LineFeed + + N' [ServerName], ' + @LineFeed + + N' [CheckDate], ' + @LineFeed + + N' [elapsed_time], ' + @LineFeed + + N' [session_id], ' + @LineFeed + + N' [database_name], ' + @LineFeed + + N' [query_text_snippet], ' + @LineFeed + + N' [query_plan], ' + @LineFeed + + N' [live_query_plan], ' + @LineFeed + + N' [query_cost], ' + @LineFeed + + N' [status], ' + @LineFeed + + N' [wait_info], ' + @LineFeed + + N' [top_session_waits], ' + @LineFeed + + N' [blocking_session_id], ' + @LineFeed + + N' [open_transaction_count], ' + @LineFeed + + N' [is_implicit_transaction], ' + @LineFeed + + N' [nt_domain], ' + @LineFeed + + N' [host_name], ' + @LineFeed + + N' [login_name], ' + @LineFeed + + N' [nt_user_name], ' + @LineFeed + + N' [program_name], ' + @LineFeed + + N' [fix_parameter_sniffing], ' + @LineFeed + + N' [client_interface_name], ' + @LineFeed + + N' [login_time], ' + @LineFeed + + N' [start_time], ' + @LineFeed + + N' [request_time], ' + @LineFeed + + N' [request_cpu_time], ' + @LineFeed + + N' [degree_of_parallelism], ' + @LineFeed + + N' [request_logical_reads], ' + @LineFeed + + N' [Logical_Reads_MB], ' + @LineFeed + + N' [request_writes], ' + @LineFeed + + N' [Logical_Writes_MB], ' + @LineFeed + + N' [request_physical_reads], ' + @LineFeed + + N' [Physical_reads_MB], ' + @LineFeed + + N' [session_cpu], ' + @LineFeed + + N' [session_logical_reads], ' + @LineFeed + + N' [session_logical_reads_MB], ' + @LineFeed + + N' [session_physical_reads], ' + @LineFeed + + N' [session_physical_reads_MB], ' + @LineFeed + + N' [session_writes], ' + @LineFeed + + N' [session_writes_MB], ' + @LineFeed + + N' [tempdb_allocations_mb], ' + @LineFeed + + N' [memory_usage], ' + @LineFeed + + N' [estimated_completion_time], ' + @LineFeed + + N' [percent_complete], ' + @LineFeed + + N' [deadlock_priority], ' + @LineFeed + + N' [transaction_isolation_level], ' + @LineFeed + + N' [last_dop], ' + @LineFeed + + N' [min_dop], ' + @LineFeed + + N' [max_dop], ' + @LineFeed + + N' [last_grant_kb], ' + @LineFeed + + N' [min_grant_kb], ' + @LineFeed + + N' [max_grant_kb], ' + @LineFeed + + N' [last_used_grant_kb], ' + @LineFeed + + N' [min_used_grant_kb], ' + @LineFeed + + N' [max_used_grant_kb], ' + @LineFeed + + N' [last_ideal_grant_kb], ' + @LineFeed + + N' [min_ideal_grant_kb], ' + @LineFeed + + N' [max_ideal_grant_kb], ' + @LineFeed + + N' [last_reserved_threads], ' + @LineFeed + + N' [min_reserved_threads], ' + @LineFeed + + N' [max_reserved_threads], ' + @LineFeed + + N' [last_used_threads], ' + @LineFeed + + N' [min_used_threads], ' + @LineFeed + + N' [max_used_threads], ' + @LineFeed + + N' [grant_time], ' + @LineFeed + + N' [requested_memory_kb], ' + @LineFeed + + N' [grant_memory_kb], ' + @LineFeed + + N' [is_request_granted], ' + @LineFeed + + N' [required_memory_kb], ' + @LineFeed + + N' [query_memory_grant_used_memory_kb], ' + @LineFeed + + N' [ideal_memory_kb], ' + @LineFeed + + N' [is_small], ' + @LineFeed + + N' [timeout_sec], ' + @LineFeed + + N' [resource_semaphore_id], ' + @LineFeed + + N' [wait_order], ' + @LineFeed + + N' [wait_time_ms], ' + @LineFeed + + N' [next_candidate_for_memory_grant], ' + @LineFeed + + N' [target_memory_kb], ' + @LineFeed + + N' [max_target_memory_kb], ' + @LineFeed + + N' [total_memory_kb], ' + @LineFeed + + N' [available_memory_kb], ' + @LineFeed + + N' [granted_memory_kb], ' + @LineFeed + + N' [query_resource_semaphore_used_memory_kb], ' + @LineFeed + + N' [grantee_count], ' + @LineFeed + + N' [waiter_count], ' + @LineFeed + + N' [timeout_error_count], ' + @LineFeed + + N' [forced_grant_count], ' + @LineFeed + + N' [workload_group_name], ' + @LineFeed + + N' [resource_pool_name], ' + @LineFeed + + N' [context_info], ' + @LineFeed + + N' [query_hash], ' + @LineFeed + + N' [query_plan_hash], ' + @LineFeed + + N' [sql_handle], ' + @LineFeed + + N' [plan_handle], ' + @LineFeed + + N' [statement_start_offset], ' + @LineFeed + + N' [statement_end_offset] ' + @LineFeed + + N' FROM ' + @LineFeed + + N' ( ' + @LineFeed + + N' SELECT ' + @LineFeed + + N' [ID], ' + @LineFeed + + N' [ServerName], ' + @LineFeed + + N' [CheckDate], ' + @LineFeed + + N' [elapsed_time], ' + @LineFeed + + N' [session_id], ' + @LineFeed + + N' [database_name], ' + @LineFeed + + N' /* Truncate the query text to aid performance of painting the rows in SSMS */ ' + @LineFeed + + N' CAST([query_text] AS NVARCHAR(1000)) AS [query_text_snippet], ' + @LineFeed + + N' [query_plan], ' + @LineFeed + + N' [live_query_plan], ' + @LineFeed + + N' [query_cost], ' + @LineFeed + + N' [status], ' + @LineFeed + + N' [wait_info], ' + @LineFeed + + N' [top_session_waits], ' + @LineFeed + + N' [blocking_session_id], ' + @LineFeed + + N' [open_transaction_count], ' + @LineFeed + + N' [is_implicit_transaction], ' + @LineFeed + + N' [nt_domain], ' + @LineFeed + + N' [host_name], ' + @LineFeed + + N' [login_name], ' + @LineFeed + + N' [nt_user_name], ' + @LineFeed + + N' [program_name], ' + @LineFeed + + N' [fix_parameter_sniffing], ' + @LineFeed + + N' [client_interface_name], ' + @LineFeed + + N' [login_time], ' + @LineFeed + + N' [start_time], ' + @LineFeed + + N' [request_time], ' + @LineFeed + + N' [request_cpu_time], ' + @LineFeed + + N' [degree_of_parallelism], ' + @LineFeed + + N' [request_logical_reads], ' + @LineFeed + + N' ((CAST([request_logical_reads] AS MONEY)* 8)/ 1024) [Logical_Reads_MB], ' + @LineFeed + + N' [request_writes], ' + @LineFeed + + N' ((CAST([request_writes] AS MONEY)* 8)/ 1024) [Logical_Writes_MB], ' + @LineFeed + + N' [request_physical_reads], ' + @LineFeed + + N' ((CAST([request_physical_reads] AS MONEY)* 8)/ 1024) [Physical_reads_MB], ' + @LineFeed + + N' [session_cpu], ' + @LineFeed + + N' [session_logical_reads], ' + @LineFeed + + N' ((CAST([session_logical_reads] AS MONEY)* 8)/ 1024) [session_logical_reads_MB], ' + @LineFeed + + N' [session_physical_reads], ' + @LineFeed + + N' ((CAST([session_physical_reads] AS MONEY)* 8)/ 1024) [session_physical_reads_MB], ' + @LineFeed + + N' [session_writes], ' + @LineFeed + + N' ((CAST([session_writes] AS MONEY)* 8)/ 1024) [session_writes_MB], ' + @LineFeed + + N' [tempdb_allocations_mb], ' + @LineFeed + + N' [memory_usage], ' + @LineFeed + + N' [estimated_completion_time], ' + @LineFeed + + N' [percent_complete], ' + @LineFeed + + N' [deadlock_priority], ' + @LineFeed + + N' [transaction_isolation_level], ' + @LineFeed + + N' [last_dop], ' + @LineFeed + + N' [min_dop], ' + @LineFeed + + N' [max_dop], ' + @LineFeed + + N' [last_grant_kb], ' + @LineFeed + + N' [min_grant_kb], ' + @LineFeed + + N' [max_grant_kb], ' + @LineFeed + + N' [last_used_grant_kb], ' + @LineFeed + + N' [min_used_grant_kb], ' + @LineFeed + + N' [max_used_grant_kb], ' + @LineFeed + + N' [last_ideal_grant_kb], ' + @LineFeed + + N' [min_ideal_grant_kb], ' + @LineFeed + + N' [max_ideal_grant_kb], ' + @LineFeed + + N' [last_reserved_threads], ' + @LineFeed + + N' [min_reserved_threads], ' + @LineFeed + + N' [max_reserved_threads], ' + @LineFeed + + N' [last_used_threads], ' + @LineFeed + + N' [min_used_threads], ' + @LineFeed + + N' [max_used_threads], ' + @LineFeed + + N' [grant_time], ' + @LineFeed + + N' [requested_memory_kb], ' + @LineFeed + + N' [grant_memory_kb], ' + @LineFeed + + N' [is_request_granted], ' + @LineFeed + + N' [required_memory_kb], ' + @LineFeed + + N' [query_memory_grant_used_memory_kb], ' + @LineFeed + + N' [ideal_memory_kb], ' + @LineFeed + + N' [is_small], ' + @LineFeed + + N' [timeout_sec], ' + @LineFeed + + N' [resource_semaphore_id], ' + @LineFeed + + N' [wait_order], ' + @LineFeed + + N' [wait_time_ms], ' + @LineFeed + + N' [next_candidate_for_memory_grant], ' + @LineFeed + + N' [target_memory_kb], ' + @LineFeed + + N' [max_target_memory_kb], ' + @LineFeed + + N' [total_memory_kb], ' + @LineFeed + + N' [available_memory_kb], ' + @LineFeed + + N' [granted_memory_kb], ' + @LineFeed + + N' [query_resource_semaphore_used_memory_kb], ' + @LineFeed + + N' [grantee_count], ' + @LineFeed + + N' [waiter_count], ' + @LineFeed + + N' [timeout_error_count], ' + @LineFeed + + N' [forced_grant_count], ' + @LineFeed + + N' [workload_group_name], ' + @LineFeed + + N' [resource_pool_name], ' + @LineFeed + + N' [context_info], ' + @LineFeed + + N' [query_hash], ' + @LineFeed + + N' [query_plan_hash], ' + @LineFeed + + N' [sql_handle], ' + @LineFeed + + N' [plan_handle], ' + @LineFeed + + N' [statement_start_offset], ' + @LineFeed + + N' [statement_end_offset] ' + @LineFeed + + N' FROM ' + @OutputSchemaName + '.' + @OutputTableName + '' + @LineFeed + + N' ) AS [BlitzWho] ' + @LineFeed + + N'INNER JOIN [MaxQueryDuration] ON [BlitzWho].[ID] = [MaxQueryDuration].[MaxID]; ' + @LineFeed + + N''');' -UPDATE #working_plan_text -SET top_three_waits = CASE - WHEN @waitstats = 0 - THEN N'The query store waits stats DMV is not available' - ELSE N'No Significant waits detected!' - END -WHERE top_three_waits IS NULL -OPTION(RECOMPILE); + IF @Debug = 1 + BEGIN + PRINT CONVERT(VARCHAR(8000), SUBSTRING(@StringToExecute, 0, 8000)) + PRINT CONVERT(VARCHAR(8000), SUBSTRING(@StringToExecute, 8000, 16000)) + END -END; -END TRY -BEGIN CATCH - RAISERROR (N'Failure populating temp tables.', 0,1) WITH NOWAIT; + EXEC(@StringToExecute); + END; - IF @sql_select IS NOT NULL - BEGIN - SET @msg = N'Last @sql_select: ' + @sql_select; - RAISERROR(@msg, 0, 1) WITH NOWAIT; - END; + END - SELECT @msg = @DatabaseName + N' database failed to process. ' + ERROR_MESSAGE(), @error_severity = ERROR_SEVERITY(), @error_state = ERROR_STATE(); - RAISERROR (@msg, @error_severity, @error_state) WITH NOWAIT; - - - WHILE @@TRANCOUNT > 0 - ROLLBACK; + IF OBJECT_ID('tempdb..#WhoReadableDBs') IS NOT NULL + DROP TABLE #WhoReadableDBs; - RETURN; -END CATCH; +CREATE TABLE #WhoReadableDBs +( +database_id INT +); -IF (@SkipXML = 0) -BEGIN TRY +IF EXISTS (SELECT * FROM sys.all_objects o WHERE o.name = 'dm_hadr_database_replica_states') BEGIN + RAISERROR('Checking for Read intent databases to exclude',0,0) WITH NOWAIT; -/* -This sets up the #working_warnings table with the IDs we're interested in so we can tie warnings back to them -*/ - -RAISERROR(N'Populate working warnings table with gathered plans', 0, 1) WITH NOWAIT; - - -SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; -SET @sql_select += N' -SELECT DISTINCT wp.plan_id, wp.query_id, qsq.query_hash, qsqt.statement_sql_handle -FROM #working_plans AS wp -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp -ON qsp.plan_id = wp.plan_id - AND qsp.query_id = wp.query_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq -ON wp.query_id = qsq.query_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt -ON qsqt.query_text_id = qsq.query_text_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs -ON qsrs.plan_id = wp.plan_id -WHERE 1 = 1 - AND qsq.is_internal_query = 0 - AND qsp.query_plan IS NOT NULL - '; - -SET @sql_select += @sql_where; - -SET @sql_select += N'OPTION (RECOMPILE); - '; + EXEC('INSERT INTO #WhoReadableDBs (database_id) SELECT DBs.database_id FROM sys.databases DBs INNER JOIN sys.availability_replicas Replicas ON DBs.replica_id = Replicas.replica_id WHERE replica_server_name NOT IN (SELECT DISTINCT primary_replica FROM sys.dm_hadr_availability_group_states States) AND Replicas.secondary_role_allow_connections_desc = ''READ_ONLY'' AND replica_server_name = @@SERVERNAME;'); +END -IF @Debug = 1 - PRINT @sql_select; +SELECT @BlockingCheck = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + + DECLARE @blocked TABLE + ( + dbid SMALLINT NOT NULL, + last_batch DATETIME NOT NULL, + open_tran SMALLINT NOT NULL, + sql_handle BINARY(20) NOT NULL, + session_id SMALLINT NOT NULL, + blocking_session_id SMALLINT NOT NULL, + lastwaittype NCHAR(32) NOT NULL, + waittime BIGINT NOT NULL, + cpu INT NOT NULL, + physical_io BIGINT NOT NULL, + memusage INT NOT NULL + ); + + INSERT @blocked ( dbid, last_batch, open_tran, sql_handle, session_id, blocking_session_id, lastwaittype, waittime, cpu, physical_io, memusage ) + SELECT + sys1.dbid, sys1.last_batch, sys1.open_tran, sys1.sql_handle, + sys2.spid AS session_id, sys2.blocked AS blocking_session_id, sys2.lastwaittype, sys2.waittime, sys2.cpu, sys2.physical_io, sys2.memusage + FROM sys.sysprocesses AS sys1 + JOIN sys.sysprocesses AS sys2 + ON sys1.spid = sys2.blocked;'; -IF @sql_select IS NULL +IF @ProductVersionMajor > 9 and @ProductVersionMajor < 11 +BEGIN + /* Think of the StringToExecute as starting with this, but we'll set this up later depending on whether we're doing an insert or a select: + SELECT @StringToExecute = N'SELECT GETDATE() AS run_date , + */ + SET @StringToExecute = N'COALESCE( RIGHT(''00'' + CONVERT(VARCHAR(20), (ABS(r.total_elapsed_time) / 1000) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), (DATEADD(SECOND, (r.total_elapsed_time / 1000), 0) + DATEADD(MILLISECOND, (r.total_elapsed_time % 1000), 0)), 114), RIGHT(''00'' + CONVERT(VARCHAR(20), DATEDIFF(SECOND, s.last_request_start_time, GETDATE()) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), DATEADD(SECOND, DATEDIFF(SECOND, s.last_request_start_time, GETDATE()), 0), 114) ) AS [elapsed_time] , + s.session_id , + COALESCE(DB_NAME(r.database_id), DB_NAME(blocked.dbid), ''N/A'') AS database_name, + ISNULL(SUBSTRING(dest.text, + ( query_stats.statement_start_offset / 2 ) + 1, + ( ( CASE query_stats.statement_end_offset + WHEN -1 THEN DATALENGTH(dest.text) + ELSE query_stats.statement_end_offset + END - query_stats.statement_start_offset ) + / 2 ) + 1), dest.text) AS query_text , + derp.query_plan , + qmg.query_cost , + s.status , + CASE + WHEN s.status <> ''sleeping'' THEN COALESCE(wt.wait_info, RTRIM(blocked.lastwaittype) + '' ('' + CONVERT(VARCHAR(10), blocked.waittime) + '')'' ) + ELSE NULL + END AS wait_info , + CASE WHEN r.blocking_session_id <> 0 AND blocked.session_id IS NULL + THEN r.blocking_session_id + WHEN r.blocking_session_id <> 0 AND s.session_id <> blocked.blocking_session_id + THEN blocked.blocking_session_id + WHEN r.blocking_session_id = 0 AND s.session_id = blocked.session_id + THEN blocked.blocking_session_id + WHEN r.blocking_session_id <> 0 AND s.session_id = blocked.blocking_session_id + THEN r.blocking_session_id + ELSE NULL + END AS blocking_session_id, + COALESCE(r.open_transaction_count, blocked.open_tran) AS open_transaction_count , + CASE WHEN EXISTS ( SELECT 1 + FROM sys.dm_tran_active_transactions AS tat + JOIN sys.dm_tran_session_transactions AS tst + ON tst.transaction_id = tat.transaction_id + WHERE tat.name = ''implicit_transaction'' + AND s.session_id = tst.session_id + ) THEN 1 + ELSE 0 + END AS is_implicit_transaction , + s.nt_domain , + s.host_name , + s.login_name , + s.nt_user_name ,' + IF @Platform = 'NonAzure' + BEGIN + SET @StringToExecute += + N'program_name = COALESCE(( + SELECT REPLACE(program_name,Substring(program_name,30,34),''"''+j.name+''"'') + FROM msdb.dbo.sysjobs j WHERE Substring(program_name,32,32) = CONVERT(char(32),CAST(j.job_id AS binary(16)),2) + ),s.program_name)' + END + ELSE + BEGIN + SET @StringToExecute += N's.program_name' + END + + IF @ExpertMode = 1 BEGIN - RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; - RETURN; - END; - -INSERT #working_warnings WITH (TABLOCK) - ( plan_id, query_id, query_hash, sql_handle ) -EXEC sys.sp_executesql @stmt = @sql_select, - @params = @sp_params, - @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; - -/* -This looks for queries in the query stores that we picked up from an internal that have multiple plans in cache - -This and several of the following queries all replaced XML parsing to find plan attributes. Sweet. - -Thanks, Query Store -*/ - -RAISERROR(N'Populating object name in #working_warnings', 0, 1) WITH NOWAIT; -UPDATE w -SET w.proc_or_function_name = ISNULL(wm.proc_or_function_name, N'Statement') -FROM #working_warnings AS w -JOIN #working_metrics AS wm -ON w.plan_id = wm.plan_id - AND w.query_id = wm.query_id -OPTION (RECOMPILE); - - -RAISERROR(N'Checking for multiple plans', 0, 1) WITH NOWAIT; + SET @StringToExecute += + N', + ''DBCC FREEPROCCACHE ('' + CONVERT(NVARCHAR(128), r.plan_handle, 1) + '');'' AS fix_parameter_sniffing, + s.client_interface_name , + s.login_time , + r.start_time , + qmg.request_time , + COALESCE(r.cpu_time, s.cpu_time) AS request_cpu_time, + COALESCE(r.logical_reads, s.logical_reads) AS request_logical_reads, + COALESCE(r.writes, s.writes) AS request_writes, + COALESCE(r.reads, s.reads) AS request_physical_reads , + s.cpu_time AS session_cpu, + s.logical_reads AS session_logical_reads, + s.reads AS session_physical_reads , + s.writes AS session_writes, + tempdb_allocations.tempdb_allocations_mb, + s.memory_usage , + r.estimated_completion_time , + r.percent_complete , + r.deadlock_priority , + CASE + WHEN s.transaction_isolation_level = 0 THEN ''Unspecified'' + WHEN s.transaction_isolation_level = 1 THEN ''Read Uncommitted'' + WHEN s.transaction_isolation_level = 2 AND EXISTS (SELECT 1 FROM sys.databases WHERE name = DB_NAME(r.database_id) AND is_read_committed_snapshot_on = 1) THEN ''Read Committed Snapshot Isolation'' + WHEN s.transaction_isolation_level = 2 THEN ''Read Committed'' + WHEN s.transaction_isolation_level = 3 THEN ''Repeatable Read'' + WHEN s.transaction_isolation_level = 4 THEN ''Serializable'' + WHEN s.transaction_isolation_level = 5 THEN ''Snapshot'' + ELSE ''WHAT HAVE YOU DONE?'' + END AS transaction_isolation_level , + qmg.dop AS degree_of_parallelism , + COALESCE(CAST(qmg.grant_time AS VARCHAR(20)), ''N/A'') AS grant_time , + qmg.requested_memory_kb , + qmg.granted_memory_kb AS grant_memory_kb, + CASE WHEN qmg.grant_time IS NULL THEN ''N/A'' + WHEN qmg.requested_memory_kb < qmg.granted_memory_kb + THEN ''Query Granted Less Than Query Requested'' + ELSE ''Memory Request Granted'' + END AS is_request_granted , + qmg.required_memory_kb , + qmg.used_memory_kb AS query_memory_grant_used_memory_kb, + qmg.ideal_memory_kb , + qmg.is_small , + qmg.timeout_sec , + qmg.resource_semaphore_id , + COALESCE(CAST(qmg.wait_order AS VARCHAR(20)), ''N/A'') AS wait_order , + COALESCE(CAST(qmg.wait_time_ms AS VARCHAR(20)), + ''N/A'') AS wait_time_ms , + CASE qmg.is_next_candidate + WHEN 0 THEN ''No'' + WHEN 1 THEN ''Yes'' + ELSE ''N/A'' + END AS next_candidate_for_memory_grant , + qrs.target_memory_kb , + COALESCE(CAST(qrs.max_target_memory_kb AS VARCHAR(20)), + ''Small Query Resource Semaphore'') AS max_target_memory_kb , + qrs.total_memory_kb , + qrs.available_memory_kb , + qrs.granted_memory_kb , + qrs.used_memory_kb AS query_resource_semaphore_used_memory_kb, + qrs.grantee_count , + qrs.waiter_count , + qrs.timeout_error_count , + COALESCE(CAST(qrs.forced_grant_count AS VARCHAR(20)), + ''Small Query Resource Semaphore'') AS forced_grant_count, + wg.name AS workload_group_name , + rp.name AS resource_pool_name, + CONVERT(VARCHAR(128), r.context_info) AS context_info + ' + END /* IF @ExpertMode = 1 */ + + SET @StringToExecute += + N'FROM sys.dm_exec_sessions AS s + LEFT JOIN sys.dm_exec_requests AS r + ON r.session_id = s.session_id + LEFT JOIN ( SELECT DISTINCT + wait.session_id , + ( SELECT waitwait.wait_type + N'' ('' + + CAST(MAX(waitwait.wait_duration_ms) AS NVARCHAR(128)) + + N'' ms) '' + FROM sys.dm_os_waiting_tasks AS waitwait + WHERE waitwait.session_id = wait.session_id + GROUP BY waitwait.wait_type + ORDER BY SUM(waitwait.wait_duration_ms) DESC + FOR + XML PATH('''') ) AS wait_info + FROM sys.dm_os_waiting_tasks AS wait ) AS wt + ON s.session_id = wt.session_id + LEFT JOIN sys.dm_exec_query_stats AS query_stats + ON r.sql_handle = query_stats.sql_handle + AND r.plan_handle = query_stats.plan_handle + AND r.statement_start_offset = query_stats.statement_start_offset + AND r.statement_end_offset = query_stats.statement_end_offset + LEFT JOIN sys.dm_exec_query_memory_grants qmg + ON r.session_id = qmg.session_id + AND r.request_id = qmg.request_id + LEFT JOIN sys.dm_exec_query_resource_semaphores qrs + ON qmg.resource_semaphore_id = qrs.resource_semaphore_id + AND qmg.pool_id = qrs.pool_id + LEFT JOIN sys.resource_governor_workload_groups wg + ON s.group_id = wg.group_id + LEFT JOIN sys.resource_governor_resource_pools rp + ON wg.pool_id = rp.pool_id + OUTER APPLY ( + SELECT TOP 1 + b.dbid, b.last_batch, b.open_tran, b.sql_handle, + b.session_id, b.blocking_session_id, b.lastwaittype, b.waittime + FROM @blocked b + WHERE (s.session_id = b.session_id + OR s.session_id = b.blocking_session_id) + ) AS blocked + OUTER APPLY sys.dm_exec_sql_text(COALESCE(r.sql_handle, blocked.sql_handle)) AS dest + OUTER APPLY sys.dm_exec_query_plan(r.plan_handle) AS derp + OUTER APPLY ( + SELECT CONVERT(DECIMAL(38,2), SUM( (((tsu.user_objects_alloc_page_count - user_objects_dealloc_page_count) * 8) / 1024.)) ) AS tempdb_allocations_mb + FROM sys.dm_db_task_space_usage tsu + WHERE tsu.request_id = r.request_id + AND tsu.session_id = r.session_id + AND tsu.session_id = s.session_id + ) as tempdb_allocations + WHERE s.session_id <> @@SPID + AND s.host_name IS NOT NULL + ' + + CASE WHEN @ShowSleepingSPIDs = 0 THEN + N' AND COALESCE(DB_NAME(r.database_id), DB_NAME(blocked.dbid)) IS NOT NULL' + WHEN @ShowSleepingSPIDs = 1 THEN + N' OR COALESCE(r.open_transaction_count, blocked.open_tran) >= 1' + ELSE N'' END; +END /* IF @ProductVersionMajor > 9 and @ProductVersionMajor < 11 */ -SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; -SET @sql_select += N' -UPDATE ww -SET ww.plan_multiple_plans = 1 -FROM #working_warnings AS ww -JOIN -( -SELECT wp.query_id, COUNT(qsp.plan_id) AS plans -FROM #working_plans AS wp -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp -ON qsp.plan_id = wp.plan_id - AND qsp.query_id = wp.query_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq -ON wp.query_id = qsq.query_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt -ON qsqt.query_text_id = qsq.query_text_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs -ON qsrs.plan_id = wp.plan_id -WHERE 1 = 1 - AND qsq.is_internal_query = 0 - AND qsp.query_plan IS NOT NULL - '; +IF @ProductVersionMajor >= 11 + BEGIN + SELECT @EnhanceFlag = + CASE WHEN @ProductVersionMajor = 11 AND @ProductVersionMinor >= 6020 THEN 1 + WHEN @ProductVersionMajor = 12 AND @ProductVersionMinor >= 5000 THEN 1 + WHEN @ProductVersionMajor = 13 AND @ProductVersionMinor >= 1601 THEN 1 + WHEN @ProductVersionMajor > 13 THEN 1 + ELSE 0 + END -SET @sql_select += @sql_where; -SET @sql_select += -N'GROUP BY wp.query_id - HAVING COUNT(qsp.plan_id) > 1 -) AS x - ON ww.query_id = x.query_id -OPTION (RECOMPILE); -'; + IF OBJECT_ID('sys.dm_exec_session_wait_stats') IS NOT NULL + BEGIN + SET @SessionWaits = 1 + END -IF @Debug = 1 - PRINT @sql_select; + /* Think of the StringToExecute as starting with this, but we'll set this up later depending on whether we're doing an insert or a select: + SELECT @StringToExecute = N'SELECT GETDATE() AS run_date , + */ + SELECT @StringToExecute = N'COALESCE( RIGHT(''00'' + CONVERT(VARCHAR(20), (ABS(r.total_elapsed_time) / 1000) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), (DATEADD(SECOND, (r.total_elapsed_time / 1000), 0) + DATEADD(MILLISECOND, (r.total_elapsed_time % 1000), 0)), 114), RIGHT(''00'' + CONVERT(VARCHAR(20), DATEDIFF(SECOND, s.last_request_start_time, GETDATE()) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), DATEADD(SECOND, DATEDIFF(SECOND, s.last_request_start_time, GETDATE()), 0), 114) ) AS [elapsed_time] , + s.session_id , + COALESCE(DB_NAME(r.database_id), DB_NAME(blocked.dbid), ''N/A'') AS database_name, + ISNULL(SUBSTRING(dest.text, + ( query_stats.statement_start_offset / 2 ) + 1, + ( ( CASE query_stats.statement_end_offset + WHEN -1 THEN DATALENGTH(dest.text) + ELSE query_stats.statement_end_offset + END - query_stats.statement_start_offset ) + / 2 ) + 1), dest.text) AS query_text , + derp.query_plan ,' + + @QueryStatsXMLselect + +' + qmg.query_cost , + s.status , + CASE + WHEN s.status <> ''sleeping'' THEN COALESCE(wt.wait_info, RTRIM(blocked.lastwaittype) + '' ('' + CONVERT(VARCHAR(10), blocked.waittime) + '')'' ) + ELSE NULL + END AS wait_info ,' + + + CASE @SessionWaits + WHEN 1 THEN + N'SUBSTRING(wt2.session_wait_info, 0, LEN(wt2.session_wait_info) ) AS top_session_waits ,' + ELSE N' NULL AS top_session_waits ,' + END + + + N'CASE WHEN r.blocking_session_id <> 0 AND blocked.session_id IS NULL + THEN r.blocking_session_id + WHEN r.blocking_session_id <> 0 AND s.session_id <> blocked.blocking_session_id + THEN blocked.blocking_session_id + WHEN r.blocking_session_id = 0 AND s.session_id = blocked.session_id + THEN blocked.blocking_session_id + WHEN r.blocking_session_id <> 0 AND s.session_id = blocked.blocking_session_id + THEN r.blocking_session_id + ELSE NULL + END AS blocking_session_id, + COALESCE(r.open_transaction_count, blocked.open_tran) AS open_transaction_count , + CASE WHEN EXISTS ( SELECT 1 + FROM sys.dm_tran_active_transactions AS tat + JOIN sys.dm_tran_session_transactions AS tst + ON tst.transaction_id = tat.transaction_id + WHERE tat.name = ''implicit_transaction'' + AND s.session_id = tst.session_id + ) THEN 1 + ELSE 0 + END AS is_implicit_transaction , + s.nt_domain , + s.host_name , + s.login_name , + s.nt_user_name ,' + IF @Platform = 'NonAzure' + BEGIN + SET @StringToExecute += + N'program_name = COALESCE(( + SELECT REPLACE(program_name,Substring(program_name,30,34),''"''+j.name+''"'') + FROM msdb.dbo.sysjobs j WHERE Substring(program_name,32,32) = CONVERT(char(32),CAST(j.job_id AS binary(16)),2) + ),s.program_name)' + END + ELSE + BEGIN + SET @StringToExecute += N's.program_name' + END -IF @sql_select IS NULL + IF @ExpertMode = 1 /* We show more columns in expert mode, so the SELECT gets longer */ BEGIN - RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; - RETURN; - END; - -EXEC sys.sp_executesql @stmt = @sql_select, - @params = @sp_params, - @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; - -/* -This looks for forced plans -*/ - -RAISERROR(N'Checking for forced plans', 0, 1) WITH NOWAIT; - -UPDATE ww -SET ww.is_forced_plan = 1 -FROM #working_warnings AS ww -JOIN #working_plan_text AS wp -ON ww.plan_id = wp.plan_id - AND ww.query_id = wp.query_id - AND wp.is_forced_plan = 1 -OPTION (RECOMPILE); - - -/* -This looks for forced parameterization -*/ - -RAISERROR(N'Checking for forced parameterization', 0, 1) WITH NOWAIT; - -UPDATE ww -SET ww.is_forced_parameterized = 1 -FROM #working_warnings AS ww -JOIN #working_metrics AS wm -ON ww.plan_id = wm.plan_id - AND ww.query_id = wm.query_id - AND wm.query_parameterization_type_desc = 'Forced' -OPTION (RECOMPILE); - - -/* -This looks for unparameterized queries -*/ - -RAISERROR(N'Checking for unparameterized plans', 0, 1) WITH NOWAIT; + SET @StringToExecute += + N', ''DBCC FREEPROCCACHE ('' + CONVERT(NVARCHAR(128), r.plan_handle, 1) + '');'' AS fix_parameter_sniffing, + s.client_interface_name , + s.login_time , + r.start_time , + qmg.request_time , + COALESCE(r.cpu_time, s.cpu_time) AS request_cpu_time, + COALESCE(r.logical_reads, s.logical_reads) AS request_logical_reads, + COALESCE(r.writes, s.writes) AS request_writes, + COALESCE(r.reads, s.reads) AS request_physical_reads , + s.cpu_time AS session_cpu, + s.logical_reads AS session_logical_reads, + s.reads AS session_physical_reads , + s.writes AS session_writes, + tempdb_allocations.tempdb_allocations_mb, + s.memory_usage , + r.estimated_completion_time , + r.percent_complete , + r.deadlock_priority , + CASE + WHEN s.transaction_isolation_level = 0 THEN ''Unspecified'' + WHEN s.transaction_isolation_level = 1 THEN ''Read Uncommitted'' + WHEN s.transaction_isolation_level = 2 AND EXISTS (SELECT 1 FROM sys.databases WHERE name = DB_NAME(r.database_id) AND is_read_committed_snapshot_on = 1) THEN ''Read Committed Snapshot Isolation'' + WHEN s.transaction_isolation_level = 2 THEN ''Read Committed'' + WHEN s.transaction_isolation_level = 3 THEN ''Repeatable Read'' + WHEN s.transaction_isolation_level = 4 THEN ''Serializable'' + WHEN s.transaction_isolation_level = 5 THEN ''Snapshot'' + ELSE ''WHAT HAVE YOU DONE?'' + END AS transaction_isolation_level , + qmg.dop AS degree_of_parallelism , ' + + + CASE @EnhanceFlag + WHEN 1 THEN N'query_stats.last_dop, + query_stats.min_dop, + query_stats.max_dop, + query_stats.last_grant_kb, + query_stats.min_grant_kb, + query_stats.max_grant_kb, + query_stats.last_used_grant_kb, + query_stats.min_used_grant_kb, + query_stats.max_used_grant_kb, + query_stats.last_ideal_grant_kb, + query_stats.min_ideal_grant_kb, + query_stats.max_ideal_grant_kb, + query_stats.last_reserved_threads, + query_stats.min_reserved_threads, + query_stats.max_reserved_threads, + query_stats.last_used_threads, + query_stats.min_used_threads, + query_stats.max_used_threads,' + ELSE N' NULL AS last_dop, + NULL AS min_dop, + NULL AS max_dop, + NULL AS last_grant_kb, + NULL AS min_grant_kb, + NULL AS max_grant_kb, + NULL AS last_used_grant_kb, + NULL AS min_used_grant_kb, + NULL AS max_used_grant_kb, + NULL AS last_ideal_grant_kb, + NULL AS min_ideal_grant_kb, + NULL AS max_ideal_grant_kb, + NULL AS last_reserved_threads, + NULL AS min_reserved_threads, + NULL AS max_reserved_threads, + NULL AS last_used_threads, + NULL AS min_used_threads, + NULL AS max_used_threads,' + END -UPDATE ww -SET ww.unparameterized_query = 1 -FROM #working_warnings AS ww -JOIN #working_metrics AS wm -ON ww.plan_id = wm.plan_id - AND ww.query_id = wm.query_id - AND wm.query_parameterization_type_desc = 'None' - AND ww.proc_or_function_name = 'Statement' -OPTION (RECOMPILE); + SET @StringToExecute += + N' + COALESCE(CAST(qmg.grant_time AS VARCHAR(20)), ''Memory Not Granted'') AS grant_time , + qmg.requested_memory_kb , + qmg.granted_memory_kb AS grant_memory_kb, + CASE WHEN qmg.grant_time IS NULL THEN ''N/A'' + WHEN qmg.requested_memory_kb < qmg.granted_memory_kb + THEN ''Query Granted Less Than Query Requested'' + ELSE ''Memory Request Granted'' + END AS is_request_granted , + qmg.required_memory_kb , + qmg.used_memory_kb AS query_memory_grant_used_memory_kb, + qmg.ideal_memory_kb , + qmg.is_small , + qmg.timeout_sec , + qmg.resource_semaphore_id , + COALESCE(CAST(qmg.wait_order AS VARCHAR(20)), ''N/A'') AS wait_order , + COALESCE(CAST(qmg.wait_time_ms AS VARCHAR(20)), + ''N/A'') AS wait_time_ms , + CASE qmg.is_next_candidate + WHEN 0 THEN ''No'' + WHEN 1 THEN ''Yes'' + ELSE ''N/A'' + END AS next_candidate_for_memory_grant , + qrs.target_memory_kb , + COALESCE(CAST(qrs.max_target_memory_kb AS VARCHAR(20)), + ''Small Query Resource Semaphore'') AS max_target_memory_kb , + qrs.total_memory_kb , + qrs.available_memory_kb , + qrs.granted_memory_kb , + qrs.used_memory_kb AS query_resource_semaphore_used_memory_kb, + qrs.grantee_count , + qrs.waiter_count , + qrs.timeout_error_count , + COALESCE(CAST(qrs.forced_grant_count AS VARCHAR(20)), + ''Small Query Resource Semaphore'') AS forced_grant_count, + wg.name AS workload_group_name, + rp.name AS resource_pool_name, + CONVERT(VARCHAR(128), r.context_info) AS context_info, + r.query_hash, r.query_plan_hash, r.sql_handle, r.plan_handle, r.statement_start_offset, r.statement_end_offset ' + END /* IF @ExpertMode = 1 */ + + SET @StringToExecute += + N' FROM sys.dm_exec_sessions AS s + LEFT JOIN sys.dm_exec_requests AS r + ON r.session_id = s.session_id + LEFT JOIN ( SELECT DISTINCT + wait.session_id , + ( SELECT waitwait.wait_type + N'' ('' + + CAST(MAX(waitwait.wait_duration_ms) AS NVARCHAR(128)) + + N'' ms) '' + FROM sys.dm_os_waiting_tasks AS waitwait + WHERE waitwait.session_id = wait.session_id + GROUP BY waitwait.wait_type + ORDER BY SUM(waitwait.wait_duration_ms) DESC + FOR + XML PATH('''') ) AS wait_info + FROM sys.dm_os_waiting_tasks AS wait ) AS wt + ON s.session_id = wt.session_id + LEFT JOIN sys.dm_exec_query_stats AS query_stats + ON r.sql_handle = query_stats.sql_handle + AND r.plan_handle = query_stats.plan_handle + AND r.statement_start_offset = query_stats.statement_start_offset + AND r.statement_end_offset = query_stats.statement_end_offset + ' + + + CASE @SessionWaits + WHEN 1 THEN @SessionWaitsSQL + ELSE N'' + END + + + N' + LEFT JOIN sys.dm_exec_query_memory_grants qmg + ON r.session_id = qmg.session_id + AND r.request_id = qmg.request_id + LEFT JOIN sys.dm_exec_query_resource_semaphores qrs + ON qmg.resource_semaphore_id = qrs.resource_semaphore_id + AND qmg.pool_id = qrs.pool_id + LEFT JOIN sys.resource_governor_workload_groups wg + ON s.group_id = wg.group_id + LEFT JOIN sys.resource_governor_resource_pools rp + ON wg.pool_id = rp.pool_id + OUTER APPLY ( + SELECT TOP 1 + b.dbid, b.last_batch, b.open_tran, b.sql_handle, + b.session_id, b.blocking_session_id, b.lastwaittype, b.waittime + FROM @blocked b + WHERE (s.session_id = b.session_id + OR s.session_id = b.blocking_session_id) + ) AS blocked + OUTER APPLY sys.dm_exec_sql_text(COALESCE(r.sql_handle, blocked.sql_handle)) AS dest + OUTER APPLY sys.dm_exec_query_plan(r.plan_handle) AS derp + OUTER APPLY ( + SELECT CONVERT(DECIMAL(38,2), SUM( (((tsu.user_objects_alloc_page_count - user_objects_dealloc_page_count) * 8) / 1024.)) ) AS tempdb_allocations_mb + FROM sys.dm_db_task_space_usage tsu + WHERE tsu.request_id = r.request_id + AND tsu.session_id = r.session_id + AND tsu.session_id = s.session_id + ) as tempdb_allocations + ' + + @QueryStatsXMLSQL + + + N' + WHERE s.session_id <> @@SPID + AND s.host_name IS NOT NULL + AND r.database_id NOT IN (SELECT database_id FROM #WhoReadableDBs) + ' + + CASE WHEN @ShowSleepingSPIDs = 0 THEN + N' AND COALESCE(DB_NAME(r.database_id), DB_NAME(blocked.dbid)) IS NOT NULL' + WHEN @ShowSleepingSPIDs = 1 THEN + N' OR COALESCE(r.open_transaction_count, blocked.open_tran) >= 1' + ELSE N'' END; -/* -This looks for cursors -*/ +END /* IF @ProductVersionMajor >= 11 */ -RAISERROR(N'Checking for cursors', 0, 1) WITH NOWAIT; -UPDATE ww -SET ww.is_cursor = 1 -FROM #working_warnings AS ww -JOIN #working_plan_text AS wp -ON ww.plan_id = wp.plan_id - AND ww.query_id = wp.query_id - AND wp.plan_group_id > 0 -OPTION (RECOMPILE); +IF (@MinElapsedSeconds + @MinCPUTime + @MinLogicalReads + @MinPhysicalReads + @MinWrites + @MinTempdbMB + @MinRequestedMemoryKB + @MinBlockingSeconds) > 0 + BEGIN + /* They're filtering for something, so set up a where clause that will let any (not all combined) of the min triggers work: */ + SET @StringToExecute += N' AND (1 = 0 '; + IF @MinElapsedSeconds > 0 + SET @StringToExecute += N' OR ABS(COALESCE(r.total_elapsed_time,0)) / 1000 >= ' + CAST(@MinElapsedSeconds AS NVARCHAR(20)); + IF @MinCPUTime > 0 + SET @StringToExecute += N' OR COALESCE(r.cpu_time, s.cpu_time,0) / 1000 >= ' + CAST(@MinCPUTime AS NVARCHAR(20)); + IF @MinLogicalReads > 0 + SET @StringToExecute += N' OR COALESCE(r.logical_reads, s.logical_reads,0) >= ' + CAST(@MinLogicalReads AS NVARCHAR(20)); + IF @MinPhysicalReads > 0 + SET @StringToExecute += N' OR COALESCE(s.reads,0) >= ' + CAST(@MinPhysicalReads AS NVARCHAR(20)); + IF @MinWrites > 0 + SET @StringToExecute += N' OR COALESCE(r.writes, s.writes,0) >= ' + CAST(@MinWrites AS NVARCHAR(20)); + IF @MinTempdbMB > 0 + SET @StringToExecute += N' OR COALESCE(tempdb_allocations.tempdb_allocations_mb,0) >= ' + CAST(@MinTempdbMB AS NVARCHAR(20)); + IF @MinRequestedMemoryKB > 0 + SET @StringToExecute += N' OR COALESCE(qmg.requested_memory_kb,0) >= ' + CAST(@MinRequestedMemoryKB AS NVARCHAR(20)); + /* Blocking is a little different - we're going to return ALL of the queries if we meet the blocking threshold. */ + IF @MinBlockingSeconds > 0 + SET @StringToExecute += N' OR (SELECT SUM(waittime / 1000) FROM @blocked) >= ' + CAST(@MinBlockingSeconds AS NVARCHAR(20)); + SET @StringToExecute += N' ) '; + END +SET @StringToExecute += + N' ORDER BY ' + CASE WHEN @SortOrder = 'session_id' THEN '[session_id] DESC' + WHEN @SortOrder = 'query_cost' THEN '[query_cost] DESC' + WHEN @SortOrder = 'database_name' THEN '[database_name] ASC' + WHEN @SortOrder = 'open_transaction_count' THEN '[open_transaction_count] DESC' + WHEN @SortOrder = 'is_implicit_transaction' THEN '[is_implicit_transaction] DESC' + WHEN @SortOrder = 'login_name' THEN '[login_name] ASC' + WHEN @SortOrder = 'program_name' THEN '[program_name] ASC' + WHEN @SortOrder = 'client_interface_name' THEN '[client_interface_name] ASC' + WHEN @SortOrder = 'request_cpu_time' THEN 'COALESCE(r.cpu_time, s.cpu_time) DESC' + WHEN @SortOrder = 'request_logical_reads' THEN 'COALESCE(r.logical_reads, s.logical_reads) DESC' + WHEN @SortOrder = 'request_writes' THEN 'COALESCE(r.writes, s.writes) DESC' + WHEN @SortOrder = 'request_physical_reads' THEN 'COALESCE(r.reads, s.reads) DESC ' + WHEN @SortOrder = 'session_cpu' THEN 's.cpu_time DESC' + WHEN @SortOrder = 'session_logical_reads' THEN 's.logical_reads DESC' + WHEN @SortOrder = 'session_physical_reads' THEN 's.reads DESC' + WHEN @SortOrder = 'session_writes' THEN 's.writes DESC' + WHEN @SortOrder = 'tempdb_allocations_mb' THEN '[tempdb_allocations_mb] DESC' + WHEN @SortOrder = 'memory_usage' THEN '[memory_usage] DESC' + WHEN @SortOrder = 'deadlock_priority' THEN 'r.deadlock_priority DESC' + WHEN @SortOrder = 'transaction_isolation_level' THEN 'r.[transaction_isolation_level] DESC' + WHEN @SortOrder = 'requested_memory_kb' THEN '[requested_memory_kb] DESC' + WHEN @SortOrder = 'grant_memory_kb' THEN 'qmg.granted_memory_kb DESC' + WHEN @SortOrder = 'grant' THEN 'qmg.granted_memory_kb DESC' + WHEN @SortOrder = 'query_memory_grant_used_memory_kb' THEN 'qmg.used_memory_kb DESC' + WHEN @SortOrder = 'ideal_memory_kb' THEN '[ideal_memory_kb] DESC' + WHEN @SortOrder = 'workload_group_name' THEN 'wg.name ASC' + WHEN @SortOrder = 'resource_pool_name' THEN 'rp.name ASC' + ELSE '[elapsed_time] DESC' + END + ' + '; -UPDATE ww -SET ww.is_cursor = 1 -FROM #working_warnings AS ww -JOIN #working_plan_text AS wp -ON ww.plan_id = wp.plan_id - AND ww.query_id = wp.query_id -WHERE ww.query_hash = 0x0000000000000000 -OR wp.query_plan_hash = 0x0000000000000000 -OPTION (RECOMPILE); -/* -This looks for parallel plans -*/ -UPDATE ww -SET ww.is_parallel = 1 -FROM #working_warnings AS ww -JOIN #working_plan_text AS wp -ON ww.plan_id = wp.plan_id - AND ww.query_id = wp.query_id - AND wp.is_parallel_plan = 1 -OPTION (RECOMPILE); +IF @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @OutputTableName IS NOT NULL + AND EXISTS ( SELECT * + FROM sys.databases + WHERE QUOTENAME([name]) = @OutputDatabaseName) + BEGIN + SET @StringToExecute = N'USE ' + + @OutputDatabaseName + N'; ' + + @BlockingCheck + + + ' INSERT INTO ' + + @OutputSchemaName + N'.' + + @OutputTableName + + N'(ServerName + ,CheckDate + ,[elapsed_time] + ,[session_id] + ,[database_name] + ,[query_text] + ,[query_plan]' + + CASE WHEN @ProductVersionMajor >= 11 THEN N',[live_query_plan]' ELSE N'' END + N' + ,[query_cost] + ,[status] + ,[wait_info]' + + CASE WHEN @ProductVersionMajor >= 11 THEN N',[top_session_waits]' ELSE N'' END + N' + ,[blocking_session_id] + ,[open_transaction_count] + ,[is_implicit_transaction] + ,[nt_domain] + ,[host_name] + ,[login_name] + ,[nt_user_name] + ,[program_name] + ,[fix_parameter_sniffing] + ,[client_interface_name] + ,[login_time] + ,[start_time] + ,[request_time] + ,[request_cpu_time] + ,[request_logical_reads] + ,[request_writes] + ,[request_physical_reads] + ,[session_cpu] + ,[session_logical_reads] + ,[session_physical_reads] + ,[session_writes] + ,[tempdb_allocations_mb] + ,[memory_usage] + ,[estimated_completion_time] + ,[percent_complete] + ,[deadlock_priority] + ,[transaction_isolation_level] + ,[degree_of_parallelism]' + + CASE WHEN @ProductVersionMajor >= 11 THEN N' + ,[last_dop] + ,[min_dop] + ,[max_dop] + ,[last_grant_kb] + ,[min_grant_kb] + ,[max_grant_kb] + ,[last_used_grant_kb] + ,[min_used_grant_kb] + ,[max_used_grant_kb] + ,[last_ideal_grant_kb] + ,[min_ideal_grant_kb] + ,[max_ideal_grant_kb] + ,[last_reserved_threads] + ,[min_reserved_threads] + ,[max_reserved_threads] + ,[last_used_threads] + ,[min_used_threads] + ,[max_used_threads]' ELSE N'' END + N' + ,[grant_time] + ,[requested_memory_kb] + ,[grant_memory_kb] + ,[is_request_granted] + ,[required_memory_kb] + ,[query_memory_grant_used_memory_kb] + ,[ideal_memory_kb] + ,[is_small] + ,[timeout_sec] + ,[resource_semaphore_id] + ,[wait_order] + ,[wait_time_ms] + ,[next_candidate_for_memory_grant] + ,[target_memory_kb] + ,[max_target_memory_kb] + ,[total_memory_kb] + ,[available_memory_kb] + ,[granted_memory_kb] + ,[query_resource_semaphore_used_memory_kb] + ,[grantee_count] + ,[waiter_count] + ,[timeout_error_count] + ,[forced_grant_count] + ,[workload_group_name] + ,[resource_pool_name] + ,[context_info]' + + CASE WHEN @ProductVersionMajor >= 11 THEN N' + ,[query_hash] + ,[query_plan_hash] + ,[sql_handle] + ,[plan_handle] + ,[statement_start_offset] + ,[statement_end_offset]' ELSE N'' END + N' +) + SELECT @@SERVERNAME, COALESCE(@CheckDateOverride, SYSDATETIMEOFFSET()) AS CheckDate , ' + + @StringToExecute; + END +ELSE + SET @StringToExecute = @BlockingCheck + N' SELECT GETDATE() AS run_date , ' + @StringToExecute; -/*This looks for old CE*/ +/* If the server has > 50GB of memory, add a max grant hint to avoid getting a giant grant */ +IF (@ProductVersionMajor = 11 AND @ProductVersionMinor >= 6020) + OR (@ProductVersionMajor = 12 AND @ProductVersionMinor >= 5000 ) + OR (@ProductVersionMajor >= 13 ) + AND 50000000 < (SELECT cntr_value + FROM sys.dm_os_performance_counters + WHERE object_name LIKE '%:Memory Manager%' + AND counter_name LIKE 'Target Server Memory (KB)%') + BEGIN + SET @StringToExecute = @StringToExecute + N' OPTION (MAX_GRANT_PERCENT = 1, RECOMPILE) '; + END +ELSE + BEGIN + SET @StringToExecute = @StringToExecute + N' OPTION (RECOMPILE) '; + END -RAISERROR(N'Checking for legacy CE', 0, 1) WITH NOWAIT; +/* Be good: */ +SET @StringToExecute = @StringToExecute + N' ; '; -UPDATE w -SET w.downlevel_estimator = 1 -FROM #working_warnings AS w -JOIN #working_plan_text AS wpt -ON w.plan_id = wpt.plan_id -AND w.query_id = wpt.query_id -/*PLEASE DON'T TELL ANYONE I DID THIS*/ -WHERE PARSENAME(wpt.engine_version, 4) < PARSENAME(CONVERT(VARCHAR(128), SERVERPROPERTY ('PRODUCTVERSION')), 4) -OPTION (RECOMPILE); -/*NO SERIOUSLY THIS IS A HORRIBLE IDEA*/ +IF @Debug = 1 + BEGIN + PRINT CONVERT(VARCHAR(8000), SUBSTRING(@StringToExecute, 0, 8000)) + PRINT CONVERT(VARCHAR(8000), SUBSTRING(@StringToExecute, 8000, 16000)) + END -/*Plans that compile 2x more than they execute*/ +EXEC sp_executesql @StringToExecute, + N'@CheckDateOverride DATETIMEOFFSET', + @CheckDateOverride; -RAISERROR(N'Checking for plans that compile 2x more than they execute', 0, 1) WITH NOWAIT; +END +GO +IF OBJECT_ID('dbo.sp_DatabaseRestore') IS NULL + EXEC ('CREATE PROCEDURE dbo.sp_DatabaseRestore AS RETURN 0;'); +GO +ALTER PROCEDURE [dbo].[sp_DatabaseRestore] + @Database NVARCHAR(128) = NULL, + @RestoreDatabaseName NVARCHAR(128) = NULL, + @BackupPathFull NVARCHAR(260) = NULL, + @BackupPathDiff NVARCHAR(260) = NULL, + @BackupPathLog NVARCHAR(260) = NULL, + @MoveFiles BIT = 1, + @MoveDataDrive NVARCHAR(260) = NULL, + @MoveLogDrive NVARCHAR(260) = NULL, + @MoveFilestreamDrive NVARCHAR(260) = NULL, + @BufferCount INT = NULL, + @MaxTransferSize INT = NULL, + @BlockSize INT = NULL, + @TestRestore BIT = 0, + @RunCheckDB BIT = 0, + @RestoreDiff BIT = 0, + @ContinueLogs BIT = 0, + @StandbyMode BIT = 0, + @StandbyUndoPath NVARCHAR(MAX) = NULL, + @RunRecovery BIT = 0, + @ForceSimpleRecovery BIT = 0, + @ExistingDBAction tinyint = 0, + @StopAt NVARCHAR(14) = NULL, + @OnlyLogsAfter NVARCHAR(14) = NULL, + @SimpleFolderEnumeration BIT = 0, + @SkipBackupsAlreadyInMsdb BIT = 0, + @DatabaseOwner sysname = NULL, + @Execute CHAR(1) = Y, + @Debug INT = 0, + @Help BIT = 0, + @Version VARCHAR(30) = NULL OUTPUT, + @VersionDate DATETIME = NULL OUTPUT, + @VersionCheckMode BIT = 0 +AS +SET NOCOUNT ON; -UPDATE ww -SET ww.is_compile_more = 1 -FROM #working_warnings AS ww -JOIN #working_metrics AS wm -ON ww.plan_id = wm.plan_id - AND ww.query_id = wm.query_id - AND wm.count_compiles > (wm.count_executions * 2) -OPTION (RECOMPILE); +/*Versioning details*/ -/*Plans that compile 2x more than they execute*/ +SELECT @Version = '8.01', @VersionDate = '20210222'; -RAISERROR(N'Checking for plans that take more than 5 seconds to bind, compile, or optimize', 0, 1) WITH NOWAIT; +IF(@VersionCheckMode = 1) +BEGIN + RETURN; +END; -UPDATE ww -SET ww.is_slow_plan = 1 -FROM #working_warnings AS ww -JOIN #working_metrics AS wm -ON ww.plan_id = wm.plan_id - AND ww.query_id = wm.query_id - AND (wm.avg_bind_duration > 5000 - OR - wm.avg_compile_duration > 5000 - OR - wm.avg_optimize_duration > 5000 - OR - wm.avg_optimize_cpu_time > 5000) -OPTION (RECOMPILE); + +IF @Help = 1 +BEGIN + PRINT ' + /* + sp_DatabaseRestore from http://FirstResponderKit.org + + This script will restore a database from a given file path. + + To learn more, visit http://FirstResponderKit.org where you can download new + versions for free, watch training videos on how it works, get more info on + the findings, contribute your own code, and more. + + Known limitations of this version: + - Only Microsoft-supported versions of SQL Server. Sorry, 2005 and 2000. + - Tastes awful with marmite. + + Unknown limitations of this version: + - None. (If we knew them, they would be known. Duh.) + + Changes - for the full list of improvements and fixes in this version, see: + https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/ + + MIT License + + Copyright (c) 2021 Brent Ozar Unlimited + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + + */ + '; + + PRINT ' + /* + EXEC dbo.sp_DatabaseRestore + @Database = ''LogShipMe'', + @BackupPathFull = ''D:\Backup\SQL2016PROD1A\LogShipMe\FULL\'', + @BackupPathLog = ''D:\Backup\SQL2016PROD1A\LogShipMe\LOG\'', + @ContinueLogs = 0, + @RunRecovery = 0; + + EXEC dbo.sp_DatabaseRestore + @Database = ''LogShipMe'', + @BackupPathFull = ''D:\Backup\SQL2016PROD1A\LogShipMe\FULL\'', + @BackupPathLog = ''D:\Backup\SQL2016PROD1A\LogShipMe\LOG\'', + @ContinueLogs = 1, + @RunRecovery = 0; + + EXEC dbo.sp_DatabaseRestore + @Database = ''LogShipMe'', + @BackupPathFull = ''D:\Backup\SQL2016PROD1A\LogShipMe\FULL\'', + @BackupPathLog = ''D:\Backup\SQL2016PROD1A\LogShipMe\LOG\'', + @ContinueLogs = 1, + @RunRecovery = 1; + + EXEC dbo.sp_DatabaseRestore + @Database = ''LogShipMe'', + @BackupPathFull = ''D:\Backup\SQL2016PROD1A\LogShipMe\FULL\'', + @BackupPathLog = ''D:\Backup\SQL2016PROD1A\LogShipMe\LOG\'', + @ContinueLogs = 0, + @RunRecovery = 1; + + EXEC dbo.sp_DatabaseRestore + @Database = ''LogShipMe'', + @BackupPathFull = ''D:\Backup\SQL2016PROD1A\LogShipMe\FULL\'', + @BackupPathDiff = ''D:\Backup\SQL2016PROD1A\LogShipMe\DIFF\'', + @BackupPathLog = ''D:\Backup\SQL2016PROD1A\LogShipMe\LOG\'', + @RestoreDiff = 1, + @ContinueLogs = 0, + @RunRecovery = 1; + + EXEC dbo.sp_DatabaseRestore + @Database = ''LogShipMe'', + @BackupPathFull = ''\\StorageServer\LogShipMe\FULL\'', + @BackupPathDiff = ''\\StorageServer\LogShipMe\DIFF\'', + @BackupPathLog = ''\\StorageServer\LogShipMe\LOG\'', + @RestoreDiff = 1, + @ContinueLogs = 0, + @RunRecovery = 1, + @TestRestore = 1, + @RunCheckDB = 1, + @Debug = 0; + EXEC dbo.sp_DatabaseRestore + @Database = ''LogShipMe'', + @BackupPathFull = ''\\StorageServer\LogShipMe\FULL\'', + @BackupPathLog = ''\\StorageServer\LogShipMe\LOG\'', + @StandbyMode = 1, + @StandbyUndoPath = ''D:\Data\'', + @ContinueLogs = 1, + @RunRecovery = 0, + @Debug = 0; + -- Restore from stripped backup set when multiple paths are used. This example will restore stripped full backup set along with stripped transactional logs set from multiple backup paths + EXEC dbo.sp_DatabaseRestore + @Database = ''DBA'', + @BackupPathFull = ''D:\Backup1\DBA\FULL,D:\Backup2\DBA\FULL'', + @BackupPathLog = ''D:\Backup1\DBA\LOG,D:\Backup2\DBA\LOG'', + @StandbyMode = 0, + @ContinueLogs = 1, + @RunRecovery = 0, + @Debug = 0; + + --This example will restore the latest differential backup, and stop transaction logs at the specified date time. It will execute and print debug information. + EXEC dbo.sp_DatabaseRestore + @Database = ''DBA'', + @BackupPathFull = ''\\StorageServer\LogShipMe\FULL\'', + @BackupPathDiff = ''\\StorageServer\LogShipMe\DIFF\'', + @BackupPathLog = ''\\StorageServer\LogShipMe\LOG\'', + @RestoreDiff = 1, + @ContinueLogs = 0, + @RunRecovery = 1, + @StopAt = ''20170508201501'', + @Debug = 1; -/* -This parses the XML from our top plans into smaller chunks for easier consumption -*/ + --This example NOT execute the restore. Commands will be printed in a copy/paste ready format only + EXEC dbo.sp_DatabaseRestore + @Database = ''DBA'', + @BackupPathFull = ''\\StorageServer\LogShipMe\FULL\'', + @BackupPathDiff = ''\\StorageServer\LogShipMe\DIFF\'', + @BackupPathLog = ''\\StorageServer\LogShipMe\LOG\'', + @RestoreDiff = 1, + @ContinueLogs = 0, + @RunRecovery = 1, + @TestRestore = 1, + @RunCheckDB = 1, + @Debug = 0, + @Execute = ''N''; + '; + + RETURN; +END; -RAISERROR(N'Begin XML nodes parsing', 0, 1) WITH NOWAIT; +-- Get the SQL Server version number because the columns returned by RESTORE commands vary by version +-- Based on: https://www.brentozar.com/archive/2015/05/sql-server-version-detection/ +-- Need to capture BuildVersion because RESTORE HEADERONLY changed with 2014 CU1, not RTM +DECLARE @ProductVersion AS NVARCHAR(20) = CAST(SERVERPROPERTY ('productversion') AS NVARCHAR(20)); +DECLARE @MajorVersion AS SMALLINT = CAST(PARSENAME(@ProductVersion, 4) AS SMALLINT); +DECLARE @MinorVersion AS SMALLINT = CAST(PARSENAME(@ProductVersion, 3) AS SMALLINT); +DECLARE @BuildVersion AS SMALLINT = CAST(PARSENAME(@ProductVersion, 2) AS SMALLINT); -RAISERROR(N'Inserting #statements', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -INSERT #statements WITH (TABLOCK) ( plan_id, query_id, query_hash, sql_handle, statement, is_cursor ) - SELECT ww.plan_id, ww.query_id, ww.query_hash, ww.sql_handle, q.n.query('.') AS statement, 0 AS is_cursor - FROM #working_warnings AS ww - JOIN #working_plan_text AS wp - ON ww.plan_id = wp.plan_id - AND ww.query_id = wp.query_id - CROSS APPLY wp.query_plan_xml.nodes('//p:StmtSimple') AS q(n) -OPTION (RECOMPILE); +IF @MajorVersion < 10 +BEGIN + RAISERROR('Sorry, DatabaseRestore doesn''t work on versions of SQL prior to 2008.', 15, 1); + RETURN; +END; -RAISERROR(N'Inserting parsed cursor XML to #statements', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -INSERT #statements WITH (TABLOCK) ( plan_id, query_id, query_hash, sql_handle, statement, is_cursor ) - SELECT ww.plan_id, ww.query_id, ww.query_hash, ww.sql_handle, q.n.query('.') AS statement, 1 AS is_cursor - FROM #working_warnings AS ww - JOIN #working_plan_text AS wp - ON ww.plan_id = wp.plan_id - AND ww.query_id = wp.query_id - CROSS APPLY wp.query_plan_xml.nodes('//p:StmtCursor') AS q(n) -OPTION (RECOMPILE); -RAISERROR(N'Inserting to #query_plan', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -INSERT #query_plan WITH (TABLOCK) ( plan_id, query_id, query_hash, sql_handle, query_plan ) -SELECT s.plan_id, s.query_id, s.query_hash, s.sql_handle, q.n.query('.') AS query_plan -FROM #statements AS s - CROSS APPLY s.statement.nodes('//p:QueryPlan') AS q(n) -OPTION (RECOMPILE); +DECLARE @cmd NVARCHAR(4000) = N'', --Holds xp_cmdshell command + @sql NVARCHAR(MAX) = N'', --Holds executable SQL commands + @LastFullBackup NVARCHAR(500) = N'', --Last full backup name + @LastDiffBackup NVARCHAR(500) = N'', --Last diff backup name + @LastDiffBackupDateTime NVARCHAR(500) = N'', --Last diff backup date + @BackupFile NVARCHAR(500) = N'', --Name of backup file + @BackupDateTime AS CHAR(15) = N'', --Used for comparisons to generate ordered backup files/create a stopat point + @FullLastLSN NUMERIC(25, 0), --LSN for full + @DiffLastLSN NUMERIC(25, 0), --LSN for diff + @HeadersSQL AS NVARCHAR(4000) = N'', --Dynamic insert into #Headers table (deals with varying results from RESTORE FILELISTONLY across different versions) + @MoveOption AS NVARCHAR(MAX) = N'', --If you need to move restored files to a different directory + @LogRecoveryOption AS NVARCHAR(MAX) = N'', --Holds the option to cause logs to be restored in standby mode or with no recovery + @DatabaseLastLSN NUMERIC(25, 0), --redo_start_lsn of the current database + @i TINYINT = 1, --Maintains loop to continue logs + @LogRestoreRanking SMALLINT = 1, --Holds Log iteration # when multiple paths & backup files are being stripped + @LogFirstLSN NUMERIC(25, 0), --Holds first LSN in log backup headers + @LogLastLSN NUMERIC(25, 0), --Holds last LSN in log backup headers + @LogLastNameInMsdbAS NVARCHAR(MAX) = N'', -- Holds last TRN file name already restored + @FileListParamSQL NVARCHAR(4000) = N'', --Holds INSERT list for #FileListParameters + @BackupParameters NVARCHAR(500) = N'', --Used to save BlockSize, MaxTransferSize and BufferCount + @RestoreDatabaseID SMALLINT, --Holds DB_ID of @RestoreDatabaseName + @UnquotedRestoreDatabaseName nvarchar(128); --Holds the unquoted @RestoreDatabaseName -RAISERROR(N'Inserting to #relop', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -INSERT #relop WITH (TABLOCK) ( plan_id, query_id, query_hash, sql_handle, relop) -SELECT qp.plan_id, qp.query_id, qp.query_hash, qp.sql_handle, q.n.query('.') AS relop -FROM #query_plan qp - CROSS APPLY qp.query_plan.nodes('//p:RelOp') AS q(n) -OPTION (RECOMPILE); +DECLARE @FileListSimple TABLE ( + BackupFile NVARCHAR(255) NOT NULL, + depth int NOT NULL, + [file] int NOT NULL +); +DECLARE @FileList TABLE ( + BackupPath NVARCHAR(255) NULL, + BackupFile NVARCHAR(255) NULL +); --- statement level checks +DECLARE @PathItem TABLE ( + PathItem NVARCHAR(512) +); -RAISERROR(N'Performing compile timeout checks', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET b.compile_timeout = 1 -FROM #statements s -JOIN #working_warnings AS b -ON s.query_hash = b.query_hash -WHERE s.statement.exist('/p:StmtSimple/@StatementOptmEarlyAbortReason[.="TimeOut"]') = 1 -OPTION (RECOMPILE); +IF OBJECT_ID(N'tempdb..#FileListParameters') IS NOT NULL DROP TABLE #FileListParameters; +CREATE TABLE #FileListParameters +( + LogicalName NVARCHAR(128) NOT NULL, + PhysicalName NVARCHAR(260) NOT NULL, + [Type] CHAR(1) NOT NULL, + FileGroupName NVARCHAR(120) NULL, + Size NUMERIC(20, 0) NOT NULL, + MaxSize NUMERIC(20, 0) NOT NULL, + FileID BIGINT NULL, + CreateLSN NUMERIC(25, 0) NULL, + DropLSN NUMERIC(25, 0) NULL, + UniqueID UNIQUEIDENTIFIER NULL, + ReadOnlyLSN NUMERIC(25, 0) NULL, + ReadWriteLSN NUMERIC(25, 0) NULL, + BackupSizeInBytes BIGINT NULL, + SourceBlockSize INT NULL, + FileGroupID INT NULL, + LogGroupGUID UNIQUEIDENTIFIER NULL, + DifferentialBaseLSN NUMERIC(25, 0) NULL, + DifferentialBaseGUID UNIQUEIDENTIFIER NULL, + IsReadOnly BIT NULL, + IsPresent BIT NULL, + TDEThumbprint VARBINARY(32) NULL, + SnapshotUrl NVARCHAR(360) NULL +); -RAISERROR(N'Performing compile memory limit exceeded checks', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET b.compile_memory_limit_exceeded = 1 -FROM #statements s -JOIN #working_warnings AS b -ON s.query_hash = b.query_hash -WHERE s.statement.exist('/p:StmtSimple/@StatementOptmEarlyAbortReason[.="MemoryLimitExceeded"]') = 1 -OPTION (RECOMPILE); +IF OBJECT_ID(N'tempdb..#Headers') IS NOT NULL DROP TABLE #Headers; +CREATE TABLE #Headers +( + BackupName NVARCHAR(256), + BackupDescription NVARCHAR(256), + BackupType NVARCHAR(256), + ExpirationDate NVARCHAR(256), + Compressed NVARCHAR(256), + Position NVARCHAR(256), + DeviceType NVARCHAR(256), + UserName NVARCHAR(256), + ServerName NVARCHAR(256), + DatabaseName NVARCHAR(256), + DatabaseVersion NVARCHAR(256), + DatabaseCreationDate NVARCHAR(256), + BackupSize NVARCHAR(256), + FirstLSN NVARCHAR(256), + LastLSN NVARCHAR(256), + CheckpointLSN NVARCHAR(256), + DatabaseBackupLSN NVARCHAR(256), + BackupStartDate NVARCHAR(256), + BackupFinishDate NVARCHAR(256), + SortOrder NVARCHAR(256), + [CodePage] NVARCHAR(256), + UnicodeLocaleId NVARCHAR(256), + UnicodeComparisonStyle NVARCHAR(256), + CompatibilityLevel NVARCHAR(256), + SoftwareVendorId NVARCHAR(256), + SoftwareVersionMajor NVARCHAR(256), + SoftwareVersionMinor NVARCHAR(256), + SoftwareVersionBuild NVARCHAR(256), + MachineName NVARCHAR(256), + Flags NVARCHAR(256), + BindingID NVARCHAR(256), + RecoveryForkID NVARCHAR(256), + Collation NVARCHAR(256), + FamilyGUID NVARCHAR(256), + HasBulkLoggedData NVARCHAR(256), + IsSnapshot NVARCHAR(256), + IsReadOnly NVARCHAR(256), + IsSingleUser NVARCHAR(256), + HasBackupChecksums NVARCHAR(256), + IsDamaged NVARCHAR(256), + BeginsLogChain NVARCHAR(256), + HasIncompleteMetaData NVARCHAR(256), + IsForceOffline NVARCHAR(256), + IsCopyOnly NVARCHAR(256), + FirstRecoveryForkID NVARCHAR(256), + ForkPointLSN NVARCHAR(256), + RecoveryModel NVARCHAR(256), + DifferentialBaseLSN NVARCHAR(256), + DifferentialBaseGUID NVARCHAR(256), + BackupTypeDescription NVARCHAR(256), + BackupSetGUID NVARCHAR(256), + CompressedBackupSize NVARCHAR(256), + Containment NVARCHAR(256), + KeyAlgorithm NVARCHAR(32), + EncryptorThumbprint VARBINARY(20), + EncryptorType NVARCHAR(32), + -- + -- Seq added to retain order by + -- + Seq INT NOT NULL IDENTITY(1, 1) +); -IF @ExpertMode > 0 +/* +Correct paths in case people forget a final "\" +*/ +/*Full*/ +IF (SELECT RIGHT(@BackupPathFull, 1)) <> '\' AND CHARINDEX('\', @BackupPathFull) > 0 --Has to end in a '\' BEGIN -RAISERROR(N'Performing index DML checks', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p), -index_dml AS ( - SELECT s.query_hash, - index_dml = CASE WHEN s.statement.exist('//p:StmtSimple/@StatementType[.="CREATE INDEX"]') = 1 THEN 1 - WHEN s.statement.exist('//p:StmtSimple/@StatementType[.="DROP INDEX"]') = 1 THEN 1 - END - FROM #statements s - ) - UPDATE b - SET b.index_dml = i.index_dml - FROM #working_warnings AS b - JOIN index_dml i - ON i.query_hash = b.query_hash - WHERE i.index_dml = 1 -OPTION (RECOMPILE); - - -RAISERROR(N'Performing table DML checks', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p), -table_dml AS ( - SELECT s.query_hash, - table_dml = CASE WHEN s.statement.exist('//p:StmtSimple/@StatementType[.="CREATE TABLE"]') = 1 THEN 1 - WHEN s.statement.exist('//p:StmtSimple/@StatementType[.="DROP OBJECT"]') = 1 THEN 1 - END - FROM #statements AS s - ) - UPDATE b - SET b.table_dml = t.table_dml - FROM #working_warnings AS b - JOIN table_dml t - ON t.query_hash = b.query_hash - WHERE t.table_dml = 1 -OPTION (RECOMPILE); + IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Fixing @BackupPathFull to add a "\"', 0, 1) WITH NOWAIT; + SET @BackupPathFull += N'\'; END; - - -RAISERROR(N'Gathering trivial plans', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES ( 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) -UPDATE b -SET b.is_trivial = 1 -FROM #working_warnings AS b -JOIN ( -SELECT s.sql_handle -FROM #statements AS s -JOIN ( SELECT r.sql_handle - FROM #relop AS r - WHERE r.relop.exist('//p:RelOp[contains(@LogicalOp, "Scan")]') = 1 ) AS r - ON r.sql_handle = s.sql_handle -WHERE s.statement.exist('//p:StmtSimple[@StatementOptmLevel[.="TRIVIAL"]]/p:QueryPlan/p:ParameterList') = 1 -) AS s -ON b.sql_handle = s.sql_handle -OPTION (RECOMPILE); - -IF @ExpertMode > 0 +ELSE IF (SELECT RIGHT(@BackupPathFull, 1)) <> '/' AND CHARINDEX('/', @BackupPathFull) > 0 --Has to end in a '/' +BEGIN + IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Fixing @BackupPathFull to add a "/"', 0, 1) WITH NOWAIT; + SET @BackupPathFull += N'/'; +END; +/*Diff*/ +IF (SELECT RIGHT(@BackupPathDiff, 1)) <> '\' AND CHARINDEX('\', @BackupPathDiff) > 0 --Has to end in a '\' +BEGIN + IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Fixing @BackupPathDiff to add a "\"', 0, 1) WITH NOWAIT; + SET @BackupPathDiff += N'\'; +END; +ELSE IF (SELECT RIGHT(@BackupPathDiff, 1)) <> '/' AND CHARINDEX('/', @BackupPathDiff) > 0 --Has to end in a '/' +BEGIN + IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Fixing @BackupPathDiff to add a "/"', 0, 1) WITH NOWAIT; + SET @BackupPathDiff += N'/'; +END; +/*Log*/ +IF (SELECT RIGHT(@BackupPathLog, 1)) <> '\' AND CHARINDEX('\', @BackupPathLog) > 0 --Has to end in a '\' +BEGIN + IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Fixing @BackupPathLog to add a "\"', 0, 1) WITH NOWAIT; + SET @BackupPathLog += N'\'; +END; +ELSE IF (SELECT RIGHT(@BackupPathLog, 1)) <> '/' AND CHARINDEX('/', @BackupPathLog) > 0 --Has to end in a '/' +BEGIN + IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Fixing @BackupPathLog to add a "/"', 0, 1) WITH NOWAIT; + SET @BackupPathLog += N'/'; +END; +/*Move Data File*/ +IF NULLIF(@MoveDataDrive, '') IS NULL +BEGIN + IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Getting default data drive for @MoveDataDrive', 0, 1) WITH NOWAIT; + SET @MoveDataDrive = CAST(SERVERPROPERTY('InstanceDefaultDataPath') AS nvarchar(260)); +END; +IF (SELECT RIGHT(@MoveDataDrive, 1)) <> '\' AND CHARINDEX('\', @MoveDataDrive) > 0 --Has to end in a '\' +BEGIN + IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Fixing @MoveDataDrive to add a "\"', 0, 1) WITH NOWAIT; + SET @MoveDataDrive += N'\'; +END; +ELSE IF (SELECT RIGHT(@MoveDataDrive, 1)) <> '/' AND CHARINDEX('/', @MoveDataDrive) > 0 --Has to end in a '/' +BEGIN + IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Fixing @MoveDataDrive to add a "/"', 0, 1) WITH NOWAIT; + SET @MoveDataDrive += N'/'; +END; +/*Move Log File*/ +IF NULLIF(@MoveLogDrive, '') IS NULL +BEGIN + IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Getting default log drive for @MoveLogDrive', 0, 1) WITH NOWAIT; + SET @MoveLogDrive = CAST(SERVERPROPERTY('InstanceDefaultLogPath') AS nvarchar(260)); +END; +IF (SELECT RIGHT(@MoveLogDrive, 1)) <> '\' AND CHARINDEX('\', @MoveLogDrive) > 0 --Has to end in a '\' +BEGIN + IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Fixing @MoveLogDrive to add a "\"', 0, 1) WITH NOWAIT; + SET @MoveLogDrive += N'\'; +END; +ELSE IF (SELECT RIGHT(@MoveLogDrive, 1)) <> '/' AND CHARINDEX('/', @MoveLogDrive) > 0 --Has to end in a '/' +BEGIN + IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Fixing@MoveLogDrive to add a "/"', 0, 1) WITH NOWAIT; + SET @MoveLogDrive += N'/'; +END; +/*Move Filestream File*/ +IF NULLIF(@MoveFilestreamDrive, '') IS NULL +BEGIN + IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Setting default data drive for @MoveFilestreamDrive', 0, 1) WITH NOWAIT; + SET @MoveFilestreamDrive = CAST(SERVERPROPERTY('InstanceDefaultDataPath') AS nvarchar(260)); +END; +IF (SELECT RIGHT(@MoveFilestreamDrive, 1)) <> '\' AND CHARINDEX('\', @MoveFilestreamDrive) > 0 --Has to end in a '\' +BEGIN + IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Fixing @MoveFilestreamDrive to add a "\"', 0, 1) WITH NOWAIT; + SET @MoveFilestreamDrive += N'\'; +END; +ELSE IF (SELECT RIGHT(@MoveFilestreamDrive, 1)) <> '/' AND CHARINDEX('/', @MoveFilestreamDrive) > 0 --Has to end in a '/' BEGIN -RAISERROR(N'Gathering row estimates', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES ('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) -INSERT #est_rows (query_hash, estimated_rows) -SELECT DISTINCT - CONVERT(BINARY(8), RIGHT('0000000000000000' + SUBSTRING(c.n.value('@QueryHash', 'VARCHAR(18)'), 3, 18), 16), 2) AS query_hash, - c.n.value('(/p:StmtSimple/@StatementEstRows)[1]', 'FLOAT') AS estimated_rows -FROM #statements AS s -CROSS APPLY s.statement.nodes('/p:StmtSimple') AS c(n) -WHERE c.n.exist('/p:StmtSimple[@StatementEstRows > 0]') = 1; - - UPDATE b - SET b.estimated_rows = er.estimated_rows - FROM #working_warnings AS b - JOIN #est_rows er - ON er.query_hash = b.query_hash - OPTION (RECOMPILE); + IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Fixing @MoveFilestreamDrive to add a "/"', 0, 1) WITH NOWAIT; + SET @MoveFilestreamDrive += N'/'; END; - - -/*Begin plan cost calculations*/ -RAISERROR(N'Gathering statement costs', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -INSERT #plan_cost WITH (TABLOCK) - ( query_plan_cost, sql_handle, plan_id ) -SELECT DISTINCT - s.statement.value('sum(/p:StmtSimple/@StatementSubTreeCost)', 'float') query_plan_cost, - s.sql_handle, - s.plan_id -FROM #statements s -OUTER APPLY s.statement.nodes('/p:StmtSimple') AS q(n) -WHERE s.statement.value('sum(/p:StmtSimple/@StatementSubTreeCost)', 'float') > 0 -OPTION (RECOMPILE); - - -RAISERROR(N'Updating statement costs', 0, 1) WITH NOWAIT; -WITH pc AS ( - SELECT SUM(DISTINCT pc.query_plan_cost) AS queryplancostsum, pc.sql_handle, pc.plan_id - FROM #plan_cost AS pc - GROUP BY pc.sql_handle, pc.plan_id - ) - UPDATE b - SET b.query_cost = ISNULL(pc.queryplancostsum, 0) - FROM #working_warnings AS b - JOIN pc - ON pc.sql_handle = b.sql_handle - AND pc.plan_id = b.plan_id -OPTION (RECOMPILE); - - -/*End plan cost calculations*/ - - -RAISERROR(N'Checking for plan warnings', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET b.plan_warnings = 1 -FROM #query_plan qp -JOIN #working_warnings b -ON qp.sql_handle = b.sql_handle -AND qp.query_plan.exist('/p:QueryPlan/p:Warnings') = 1 -OPTION (RECOMPILE); - - -RAISERROR(N'Checking for implicit conversion', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET b.implicit_conversions = 1 -FROM #query_plan qp -JOIN #working_warnings b -ON qp.sql_handle = b.sql_handle -AND qp.query_plan.exist('/p:QueryPlan/p:Warnings/p:PlanAffectingConvert/@Expression[contains(., "CONVERT_IMPLICIT")]') = 1 -OPTION (RECOMPILE); - -IF @ExpertMode > 0 +/*Standby Undo File*/ +IF (SELECT RIGHT(@StandbyUndoPath, 1)) <> '\' AND CHARINDEX('\', @StandbyUndoPath) > 0 --Has to end in a '\' BEGIN -RAISERROR(N'Performing busy loops checks', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE p -SET busy_loops = CASE WHEN (x.estimated_executions / 100.0) > x.estimated_rows THEN 1 END -FROM #working_warnings p - JOIN ( - SELECT qs.sql_handle, - relop.value('sum(/p:RelOp/@EstimateRows)', 'float') AS estimated_rows , - relop.value('sum(/p:RelOp/@EstimateRewinds)', 'float') + relop.value('sum(/p:RelOp/@EstimateRebinds)', 'float') + 1.0 AS estimated_executions - FROM #relop qs - ) AS x ON p.sql_handle = x.sql_handle -OPTION (RECOMPILE); -END; - - -RAISERROR(N'Performing TVF join check', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE p -SET p.tvf_join = CASE WHEN x.tvf_join = 1 THEN 1 END -FROM #working_warnings p - JOIN ( - SELECT r.sql_handle, - 1 AS tvf_join - FROM #relop AS r - WHERE r.relop.exist('//p:RelOp[(@LogicalOp[.="Table-valued function"])]') = 1 - AND r.relop.exist('//p:RelOp[contains(@LogicalOp, "Join")]') = 1 - ) AS x ON p.sql_handle = x.sql_handle -OPTION (RECOMPILE); - -IF @ExpertMode > 0 + IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Fixing @StandbyUndoPath to add a "\"', 0, 1) WITH NOWAIT; + SET @StandbyUndoPath += N'\'; +END; +ELSE IF (SELECT RIGHT(@StandbyUndoPath, 1)) <> '/' AND CHARINDEX('/', @StandbyUndoPath) > 0 --Has to end in a '/' BEGIN -RAISERROR(N'Checking for operator warnings', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -, x AS ( -SELECT r.sql_handle, - c.n.exist('//p:Warnings[(@NoJoinPredicate[.="1"])]') AS warning_no_join_predicate, - c.n.exist('//p:ColumnsWithNoStatistics') AS no_stats_warning , - c.n.exist('//p:Warnings') AS relop_warnings -FROM #relop AS r -CROSS APPLY r.relop.nodes('/p:RelOp/p:Warnings') AS c(n) -) -UPDATE b -SET b.warning_no_join_predicate = x.warning_no_join_predicate, - b.no_stats_warning = x.no_stats_warning, - b.relop_warnings = x.relop_warnings -FROM #working_warnings b -JOIN x ON x.sql_handle = b.sql_handle -OPTION (RECOMPILE); -END; - - -RAISERROR(N'Checking for table variables', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -, x AS ( -SELECT r.sql_handle, - c.n.value('substring(@Table, 2, 1)','VARCHAR(100)') AS first_char -FROM #relop r -CROSS APPLY r.relop.nodes('//p:Object') AS c(n) -) -UPDATE b -SET b.is_table_variable = 1 -FROM #working_warnings b -JOIN x ON x.sql_handle = b.sql_handle -JOIN #working_metrics AS wm -ON b.plan_id = wm.plan_id -AND b.query_id = wm.query_id -AND wm.batch_sql_handle IS NOT NULL -WHERE x.first_char = '@' -OPTION (RECOMPILE); + IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Fixing @StandbyUndoPath to add a "/"', 0, 1) WITH NOWAIT; + SET @StandbyUndoPath += N'/'; +END; +IF @RestoreDatabaseName IS NULL OR @RestoreDatabaseName LIKE N'' /*use LIKE instead of =, otherwise N'' = N' '. See: https://www.brentozar.com/archive/2017/04/surprising-behavior-trailing-spaces/ */ +BEGIN + SET @RestoreDatabaseName = @Database; +END; -IF @ExpertMode > 0 +/*check input parameters*/ +IF NOT @MaxTransferSize IS NULL BEGIN -RAISERROR(N'Checking for functions', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -, x AS ( -SELECT r.sql_handle, - n.fn.value('count(distinct-values(//p:UserDefinedFunction[not(@IsClrFunction)]))', 'INT') AS function_count, - n.fn.value('count(distinct-values(//p:UserDefinedFunction[@IsClrFunction = "1"]))', 'INT') AS clr_function_count -FROM #relop r -CROSS APPLY r.relop.nodes('/p:RelOp/p:ComputeScalar/p:DefinedValues/p:DefinedValue/p:ScalarOperator') n(fn) -) -UPDATE b -SET b.function_count = x.function_count, - b.clr_function_count = x.clr_function_count -FROM #working_warnings b -JOIN x ON x.sql_handle = b.sql_handle -OPTION (RECOMPILE); + IF @MaxTransferSize > 4194304 + BEGIN + RAISERROR('@MaxTransferSize can not be greater then 4194304', 0, 1) WITH NOWAIT; + END + + IF @MaxTransferSize % 64 <> 0 + BEGIN + RAISERROR('@MaxTransferSize has to be a multiple of 65536', 0, 1) WITH NOWAIT; + END END; +IF NOT @BlockSize IS NULL +BEGIN + IF @BlockSize NOT IN (512, 1024, 2048, 4096, 8192, 16384, 32768, 65536) + BEGIN + RAISERROR('Supported values for @BlockSize are 512, 1024, 2048, 4096, 8192, 16384, 32768, and 65536', 0, 1) WITH NOWAIT; + END +END -RAISERROR(N'Checking for expensive key lookups', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET b.key_lookup_cost = x.key_lookup_cost -FROM #working_warnings b -JOIN ( -SELECT - r.sql_handle, - MAX(r.relop.value('sum(/p:RelOp/@EstimatedTotalSubtreeCost)', 'float')) AS key_lookup_cost -FROM #relop r -WHERE r.relop.exist('/p:RelOp/p:IndexScan[(@Lookup[.="1"])]') = 1 -GROUP BY r.sql_handle -) AS x ON x.sql_handle = b.sql_handle -OPTION (RECOMPILE); +SET @RestoreDatabaseID = DB_ID(@RestoreDatabaseName); +SET @RestoreDatabaseName = QUOTENAME(@RestoreDatabaseName); +SET @UnquotedRestoreDatabaseName = PARSENAME(@RestoreDatabaseName,1); +--If xp_cmdshell is disabled, force use of xp_dirtree +IF NOT EXISTS (SELECT * FROM sys.configurations WHERE name = 'xp_cmdshell' AND value_in_use = 1) + SET @SimpleFolderEnumeration = 1; -RAISERROR(N'Checking for expensive remote queries', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET b.remote_query_cost = x.remote_query_cost -FROM #working_warnings b -JOIN ( -SELECT - r.sql_handle, - MAX(r.relop.value('sum(/p:RelOp/@EstimatedTotalSubtreeCost)', 'float')) AS remote_query_cost -FROM #relop r -WHERE r.relop.exist('/p:RelOp[(@PhysicalOp[contains(., "Remote")])]') = 1 -GROUP BY r.sql_handle -) AS x ON x.sql_handle = b.sql_handle -OPTION (RECOMPILE); +SET @HeadersSQL = +N'INSERT INTO #Headers WITH (TABLOCK) + (BackupName, BackupDescription, BackupType, ExpirationDate, Compressed, Position, DeviceType, UserName, ServerName + ,DatabaseName, DatabaseVersion, DatabaseCreationDate, BackupSize, FirstLSN, LastLSN, CheckpointLSN, DatabaseBackupLSN + ,BackupStartDate, BackupFinishDate, SortOrder, CodePage, UnicodeLocaleId, UnicodeComparisonStyle, CompatibilityLevel + ,SoftwareVendorId, SoftwareVersionMajor, SoftwareVersionMinor, SoftwareVersionBuild, MachineName, Flags, BindingID + ,RecoveryForkID, Collation, FamilyGUID, HasBulkLoggedData, IsSnapshot, IsReadOnly, IsSingleUser, HasBackupChecksums + ,IsDamaged, BeginsLogChain, HasIncompleteMetaData, IsForceOffline, IsCopyOnly, FirstRecoveryForkID, ForkPointLSN + ,RecoveryModel, DifferentialBaseLSN, DifferentialBaseGUID, BackupTypeDescription, BackupSetGUID, CompressedBackupSize'; + +IF @MajorVersion >= 11 + SET @HeadersSQL += NCHAR(13) + NCHAR(10) + N', Containment'; +IF @MajorVersion >= 13 OR (@MajorVersion = 12 AND @BuildVersion >= 2342) + SET @HeadersSQL += N', KeyAlgorithm, EncryptorThumbprint, EncryptorType'; -RAISERROR(N'Checking for expensive sorts', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET sort_cost = y.max_sort_cost -FROM #working_warnings b -JOIN ( - SELECT x.sql_handle, MAX((x.sort_io + x.sort_cpu)) AS max_sort_cost - FROM ( - SELECT - qs.sql_handle, - relop.value('sum(/p:RelOp/@EstimateIO)', 'float') AS sort_io, - relop.value('sum(/p:RelOp/@EstimateCPU)', 'float') AS sort_cpu - FROM #relop qs - WHERE [relop].exist('/p:RelOp[(@PhysicalOp[.="Sort"])]') = 1 - ) AS x - GROUP BY x.sql_handle - ) AS y -ON b.sql_handle = y.sql_handle -OPTION (RECOMPILE); +SET @HeadersSQL += N')' + NCHAR(13) + NCHAR(10); +SET @HeadersSQL += N'EXEC (''RESTORE HEADERONLY FROM DISK=''''{Path}'''''')'; -IF NOT EXISTS(SELECT 1/0 FROM #statements AS s WHERE s.is_cursor = 1) +IF @BackupPathFull IS NOT NULL BEGIN + DECLARE @CurrentBackupPathFull NVARCHAR(255); -RAISERROR(N'No cursor plans found, skipping', 0, 1) WITH NOWAIT; + -- Split CSV string logic has taken from Ola Hallengren's :) + WITH BackupPaths ( + StartPosition, EndPosition, PathItem + ) + AS ( + SELECT 1 AS StartPosition, + ISNULL( NULLIF( CHARINDEX( ',', @BackupPathFull, 1 ), 0 ), LEN( @BackupPathFull ) + 1 ) AS EndPosition, + SUBSTRING( @BackupPathFull, 1, ISNULL( NULLIF( CHARINDEX( ',', @BackupPathFull, 1 ), 0 ), LEN( @BackupPathFull ) + 1 ) - 1 ) AS PathItem + WHERE @BackupPathFull IS NOT NULL + UNION ALL + SELECT CAST( EndPosition AS INT ) + 1 AS StartPosition, + ISNULL( NULLIF( CHARINDEX( ',', @BackupPathFull, EndPosition + 1 ), 0 ), LEN( @BackupPathFull ) + 1 ) AS EndPosition, + SUBSTRING( @BackupPathFull, EndPosition + 1, ISNULL( NULLIF( CHARINDEX( ',', @BackupPathFull, EndPosition + 1 ), 0 ), LEN( @BackupPathFull ) + 1 ) - EndPosition - 1 ) AS PathItem + FROM BackupPaths + WHERE EndPosition < LEN( @BackupPathFull ) + 1 + ) + INSERT INTO @PathItem + SELECT CASE RIGHT( PathItem, 1 ) WHEN '\' THEN PathItem ELSE PathItem + '\' END FROM BackupPaths; -END + WHILE 1 = 1 + BEGIN -IF EXISTS(SELECT 1/0 FROM #statements AS s WHERE s.is_cursor = 1) -BEGIN + SELECT TOP 1 @CurrentBackupPathFull = PathItem FROM @PathItem + WHERE PathItem > COALESCE( @CurrentBackupPathFull, '' ) ORDER BY PathItem; + IF @@rowcount = 0 BREAK; -RAISERROR(N'Cursor plans found, investigating', 0, 1) WITH NOWAIT; + IF @SimpleFolderEnumeration = 1 + BEGIN -- Get list of files + INSERT INTO @FileListSimple (BackupFile, depth, [file]) EXEC master.sys.xp_dirtree @CurrentBackupPathFull, 1, 1; + INSERT @FileList (BackupPath,BackupFile) SELECT @CurrentBackupPathFull, BackupFile FROM @FileListSimple; + DELETE FROM @FileListSimple; + END + ELSE + BEGIN + SET @cmd = N'DIR /b "' + @CurrentBackupPathFull + N'"'; + IF @Debug = 1 + BEGIN + IF @cmd IS NULL PRINT '@cmd is NULL for @CurrentBackupPathFull'; + PRINT @cmd; + END; + INSERT INTO @FileList (BackupFile) EXEC master.sys.xp_cmdshell @cmd; + UPDATE @FileList SET BackupPath = @CurrentBackupPathFull + WHERE BackupPath IS NULL; + END; + + IF @Debug = 1 + BEGIN + SELECT BackupPath, BackupFile FROM @FileList; + END; + IF @SimpleFolderEnumeration = 1 + BEGIN + /*Check what we can*/ + IF NOT EXISTS (SELECT * FROM @FileList) + BEGIN + RAISERROR('(FULL) No rows were returned for that database in path %s', 16, 1, @CurrentBackupPathFull) WITH NOWAIT; + RETURN; + END; + END + ELSE + BEGIN + /*Full Sanity check folders*/ + IF ( + SELECT COUNT(*) + FROM @FileList AS fl + WHERE fl.BackupFile = 'The system cannot find the path specified.' + OR fl.BackupFile = 'File Not Found' + ) = 1 + BEGIN + RAISERROR('(FULL) No rows or bad value for path %s', 16, 1, @CurrentBackupPathFull) WITH NOWAIT; + RETURN; + END; + IF ( + SELECT COUNT(*) + FROM @FileList AS fl + WHERE fl.BackupFile = 'Access is denied.' + ) = 1 + BEGIN + RAISERROR('(FULL) Access is denied to %s', 16, 1, @CurrentBackupPathFull) WITH NOWAIT; + RETURN; + END; + IF ( + SELECT COUNT(*) + FROM @FileList AS fl + ) = 1 + AND + ( + SELECT COUNT(*) + FROM @FileList AS fl + WHERE fl.BackupFile IS NULL + ) = 1 + BEGIN + RAISERROR('(FULL) Empty directory %s', 16, 1, @CurrentBackupPathFull) WITH NOWAIT; + RETURN; + END + IF ( + SELECT COUNT(*) + FROM @FileList AS fl + WHERE fl.BackupFile = 'The user name or password is incorrect.' + ) = 1 + BEGIN + RAISERROR('(FULL) Incorrect user name or password for %s', 16, 1, @CurrentBackupPathFull) WITH NOWAIT; + RETURN; + END; + END; + END + /*End folder sanity check*/ -RAISERROR(N'Checking for Optimistic cursors', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET b.is_optimistic_cursor = 1 -FROM #working_warnings b -JOIN #statements AS s -ON b.sql_handle = s.sql_handle -CROSS APPLY s.statement.nodes('/p:StmtCursor') AS n1(fn) -WHERE n1.fn.exist('//p:CursorPlan/@CursorConcurrency[.="Optimistic"]') = 1 -AND s.is_cursor = 1 -OPTION (RECOMPILE); + IF @StopAt IS NOT NULL + BEGIN + DELETE + FROM @FileList + WHERE BackupFile LIKE N'%.bak' + AND + BackupFile LIKE N'%' + @Database + N'%' + AND + (REPLACE( RIGHT( REPLACE( BackupFile, RIGHT( BackupFile, PATINDEX( '%_[0-9][0-9]%', REVERSE( BackupFile ) ) ), '' ), 16 ), '_', '' ) > @StopAt); + END + + -- Find latest full backup + SELECT @LastFullBackup = MAX(BackupFile) + FROM @FileList + WHERE BackupFile LIKE N'%.bak' + AND + BackupFile LIKE N'%' + @Database + N'%' + AND + (@StopAt IS NULL OR REPLACE( RIGHT( REPLACE( @LastFullBackup, RIGHT( @LastFullBackup, PATINDEX( '%_[0-9][0-9]%', REVERSE( @LastFullBackup ) ) ), '' ), 16 ), '_', '' ) <= @StopAt); + /* To get all backups that belong to the same set we can do two things: + 1. RESTORE HEADERONLY of ALL backup files in the folder and look for BackupSetGUID. + Backups that belong to the same split will have the same BackupSetGUID. + 2. Olla Hallengren's solution appends file index at the end of the name: + SQLSERVER1_TEST_DB_FULL_20180703_213211_1.bak + SQLSERVER1_TEST_DB_FULL_20180703_213211_2.bak + SQLSERVER1_TEST_DB_FULL_20180703_213211_N.bak + We can and find all related files with the same timestamp but different index. + This option is simpler and requires less changes to this procedure */ -RAISERROR(N'Checking if cursor is Forward Only', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET b.is_forward_only_cursor = 1 -FROM #working_warnings b -JOIN #statements AS s -ON b.sql_handle = s.sql_handle -CROSS APPLY s.statement.nodes('/p:StmtCursor') AS n1(fn) -WHERE n1.fn.exist('//p:CursorPlan/@ForwardOnly[.="true"]') = 1 -AND s.is_cursor = 1 -OPTION (RECOMPILE); + IF @LastFullBackup IS NULL + BEGIN + RAISERROR('No backups for "%s" found in "%s"', 16, 1, @Database, @BackupPathFull) WITH NOWAIT; + RETURN; + END; + + SELECT BackupPath, BackupFile INTO #SplitFullBackups + FROM @FileList + WHERE LEFT( BackupFile, LEN( BackupFile ) - PATINDEX( '%[_]%', REVERSE( BackupFile ) ) ) = LEFT( @LastFullBackup, LEN( @LastFullBackup ) - PATINDEX( '%[_]%', REVERSE( @LastFullBackup ) ) ) + AND PATINDEX( '%[_]%', REVERSE( @LastFullBackup ) ) <= 7 -- there is a 1 or 2 digit index at the end of the string which indicates split backups. Ola only supports up to 64 file split. + ORDER BY REPLACE( RIGHT( REPLACE( BackupFile, RIGHT( BackupFile, PATINDEX( '%_[0-9][0-9]%', REVERSE( BackupFile ) ) ), '' ), 16 ), '_', '' ) DESC; + -- File list can be obtained by running RESTORE FILELISTONLY of any file from the given BackupSet therefore we do not have to cater for split backups when building @FileListParamSQL -RAISERROR(N'Checking if cursor is Fast Forward', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET b.is_fast_forward_cursor = 1 -FROM #working_warnings b -JOIN #statements AS s -ON b.sql_handle = s.sql_handle -CROSS APPLY s.statement.nodes('/p:StmtCursor') AS n1(fn) -WHERE n1.fn.exist('//p:CursorPlan/@CursorActualType[.="FastForward"]') = 1 -AND s.is_cursor = 1 -OPTION (RECOMPILE); + SET @FileListParamSQL = + N'INSERT INTO #FileListParameters WITH (TABLOCK) + (LogicalName, PhysicalName, Type, FileGroupName, Size, MaxSize, FileID, CreateLSN, DropLSN + ,UniqueID, ReadOnlyLSN, ReadWriteLSN, BackupSizeInBytes, SourceBlockSize, FileGroupID, LogGroupGUID + ,DifferentialBaseLSN, DifferentialBaseGUID, IsReadOnly, IsPresent, TDEThumbprint'; + IF @MajorVersion >= 13 + BEGIN + SET @FileListParamSQL += N', SnapshotUrl'; + END; -RAISERROR(N'Checking for Dynamic cursors', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET b.is_cursor_dynamic = 1 -FROM #working_warnings b -JOIN #statements AS s -ON b.sql_handle = s.sql_handle -CROSS APPLY s.statement.nodes('/p:StmtCursor') AS n1(fn) -WHERE n1.fn.exist('//p:CursorPlan/@CursorActualType[.="Dynamic"]') = 1 -AND s.is_cursor = 1 -OPTION (RECOMPILE); + SET @FileListParamSQL += N')' + NCHAR(13) + NCHAR(10); + SET @FileListParamSQL += N'EXEC (''RESTORE FILELISTONLY FROM DISK=''''{Path}'''''')'; -END + -- get the TOP record to use in "Restore HeaderOnly/FileListOnly" statement as well as Non-Split Backups Restore Command + SELECT TOP 1 @CurrentBackupPathFull = BackupPath, @LastFullBackup = BackupFile + FROM @FileList + ORDER BY REPLACE( RIGHT( REPLACE( BackupFile, RIGHT( BackupFile, PATINDEX( '%_[0-9][0-9]%', REVERSE( BackupFile ) ) ), '' ), 16 ), '_', '' ) DESC; -IF @ExpertMode > 0 -BEGIN -RAISERROR(N'Checking for bad scans and plan forcing', 0, 1) WITH NOWAIT; -;WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET -b.is_table_scan = x.is_table_scan, -b.backwards_scan = x.backwards_scan, -b.forced_index = x.forced_index, -b.forced_seek = x.forced_seek, -b.forced_scan = x.forced_scan -FROM #working_warnings b -JOIN ( -SELECT - r.sql_handle, - 0 AS is_table_scan, - q.n.exist('@ScanDirection[.="BACKWARD"]') AS backwards_scan, - q.n.value('@ForcedIndex', 'bit') AS forced_index, - q.n.value('@ForceSeek', 'bit') AS forced_seek, - q.n.value('@ForceScan', 'bit') AS forced_scan -FROM #relop r -CROSS APPLY r.relop.nodes('//p:IndexScan') AS q(n) -UNION ALL -SELECT - r.sql_handle, - 1 AS is_table_scan, - q.n.exist('@ScanDirection[.="BACKWARD"]') AS backwards_scan, - q.n.value('@ForcedIndex', 'bit') AS forced_index, - q.n.value('@ForceSeek', 'bit') AS forced_seek, - q.n.value('@ForceScan', 'bit') AS forced_scan -FROM #relop r -CROSS APPLY r.relop.nodes('//p:TableScan') AS q(n) -) AS x ON b.sql_handle = x.sql_handle -OPTION (RECOMPILE); -END; + SET @sql = REPLACE(@FileListParamSQL, N'{Path}', @CurrentBackupPathFull + @LastFullBackup); + + IF @Debug = 1 + BEGIN + IF @sql IS NULL PRINT '@sql is NULL for INSERT to #FileListParameters: @BackupPathFull + @LastFullBackup'; + PRINT @sql; + END; + EXEC (@sql); + IF @Debug = 1 + BEGIN + SELECT '#FileListParameters' AS table_name, * FROM #FileListParameters; + SELECT '@FileList' AS table_name, BackupPath, BackupFile FROM @FileList WHERE BackupFile IS NOT NULL; + END -IF @ExpertMode > 0 -BEGIN -RAISERROR(N'Checking for computed columns that reference scalar UDFs', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET b.is_computed_scalar = x.computed_column_function -FROM #working_warnings b -JOIN ( -SELECT r.sql_handle, - n.fn.value('count(distinct-values(//p:UserDefinedFunction[not(@IsClrFunction)]))', 'INT') AS computed_column_function -FROM #relop r -CROSS APPLY r.relop.nodes('/p:RelOp/p:ComputeScalar/p:DefinedValues/p:DefinedValue/p:ScalarOperator') n(fn) -WHERE n.fn.exist('/p:RelOp/p:ComputeScalar/p:DefinedValues/p:DefinedValue/p:ColumnReference[(@ComputedColumn[.="1"])]') = 1 -) AS x ON x.sql_handle = b.sql_handle -OPTION (RECOMPILE); -END; + --get the backup completed data so we can apply tlogs from that point forwards + SET @sql = REPLACE(@HeadersSQL, N'{Path}', @CurrentBackupPathFull + @LastFullBackup); + IF @Debug = 1 + BEGIN + IF @sql IS NULL PRINT '@sql is NULL for get backup completed data: @BackupPathFull, @LastFullBackup'; + PRINT @sql; + END; + EXECUTE (@sql); + IF @Debug = 1 + BEGIN + SELECT '#Headers' AS table_name, @LastFullBackup AS FullBackupFile, * FROM #Headers + END; -RAISERROR(N'Checking for filters that reference scalar UDFs', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET b.is_computed_filter = x.filter_function -FROM #working_warnings b -JOIN ( -SELECT -r.sql_handle, -c.n.value('count(distinct-values(//p:UserDefinedFunction[not(@IsClrFunction)]))', 'INT') AS filter_function -FROM #relop AS r -CROSS APPLY r.relop.nodes('/p:RelOp/p:Filter/p:Predicate/p:ScalarOperator/p:Compare/p:ScalarOperator/p:UserDefinedFunction') c(n) -) x ON x.sql_handle = b.sql_handle -OPTION (RECOMPILE); + --Ensure we are looking at the expected backup, but only if we expect to restore a FULL backups + IF NOT EXISTS (SELECT * FROM #Headers h WHERE h.DatabaseName = @Database) + BEGIN + RAISERROR('Backupfile "%s" does not match @Database parameter "%s"', 16, 1, @LastFullBackup, @Database) WITH NOWAIT; + RETURN; + END; + IF NOT @BufferCount IS NULL + BEGIN + SET @BackupParameters += N', BufferCount=' + cast(@BufferCount as NVARCHAR(10)) + END -IF @ExpertMode > 0 -BEGIN -RAISERROR(N'Checking modification queries that hit lots of indexes', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p), -IndexOps AS -( - SELECT - r.query_hash, - c.n.value('@PhysicalOp', 'VARCHAR(100)') AS op_name, - c.n.exist('@PhysicalOp[.="Index Insert"]') AS ii, - c.n.exist('@PhysicalOp[.="Index Update"]') AS iu, - c.n.exist('@PhysicalOp[.="Index Delete"]') AS id, - c.n.exist('@PhysicalOp[.="Clustered Index Insert"]') AS cii, - c.n.exist('@PhysicalOp[.="Clustered Index Update"]') AS ciu, - c.n.exist('@PhysicalOp[.="Clustered Index Delete"]') AS cid, - c.n.exist('@PhysicalOp[.="Table Insert"]') AS ti, - c.n.exist('@PhysicalOp[.="Table Update"]') AS tu, - c.n.exist('@PhysicalOp[.="Table Delete"]') AS td - FROM #relop AS r - CROSS APPLY r.relop.nodes('/p:RelOp') c(n) - OUTER APPLY r.relop.nodes('/p:RelOp/p:ScalarInsert/p:Object') q(n) - OUTER APPLY r.relop.nodes('/p:RelOp/p:Update/p:Object') o2(n) - OUTER APPLY r.relop.nodes('/p:RelOp/p:SimpleUpdate/p:Object') o3(n) -), iops AS -( - SELECT ios.query_hash, - SUM(CONVERT(TINYINT, ios.ii)) AS index_insert_count, - SUM(CONVERT(TINYINT, ios.iu)) AS index_update_count, - SUM(CONVERT(TINYINT, ios.id)) AS index_delete_count, - SUM(CONVERT(TINYINT, ios.cii)) AS cx_insert_count, - SUM(CONVERT(TINYINT, ios.ciu)) AS cx_update_count, - SUM(CONVERT(TINYINT, ios.cid)) AS cx_delete_count, - SUM(CONVERT(TINYINT, ios.ti)) AS table_insert_count, - SUM(CONVERT(TINYINT, ios.tu)) AS table_update_count, - SUM(CONVERT(TINYINT, ios.td)) AS table_delete_count - FROM IndexOps AS ios - WHERE ios.op_name IN ('Index Insert', 'Index Delete', 'Index Update', - 'Clustered Index Insert', 'Clustered Index Delete', 'Clustered Index Update', - 'Table Insert', 'Table Delete', 'Table Update') - GROUP BY ios.query_hash) -UPDATE b -SET b.index_insert_count = iops.index_insert_count, - b.index_update_count = iops.index_update_count, - b.index_delete_count = iops.index_delete_count, - b.cx_insert_count = iops.cx_insert_count, - b.cx_update_count = iops.cx_update_count, - b.cx_delete_count = iops.cx_delete_count, - b.table_insert_count = iops.table_insert_count, - b.table_update_count = iops.table_update_count, - b.table_delete_count = iops.table_delete_count -FROM #working_warnings AS b -JOIN iops ON iops.query_hash = b.query_hash -OPTION (RECOMPILE); -END; + IF NOT @MaxTransferSize IS NULL + BEGIN + SET @BackupParameters += N', MaxTransferSize=' + cast(@MaxTransferSize as NVARCHAR(7)) + END + IF NOT @BlockSize IS NULL + BEGIN + SET @BackupParameters += N', BlockSize=' + cast(@BlockSize as NVARCHAR(5)) + END -IF @ExpertMode > 0 -BEGIN -RAISERROR(N'Checking for Spatial index use', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET b.is_spatial = x.is_spatial -FROM #working_warnings AS b -JOIN ( -SELECT r.sql_handle, - 1 AS is_spatial -FROM #relop r -CROSS APPLY r.relop.nodes('/p:RelOp//p:Object') n(fn) -WHERE n.fn.exist('(@IndexKind[.="Spatial"])') = 1 -) AS x ON x.sql_handle = b.sql_handle -OPTION (RECOMPILE); -END; + IF @MoveFiles = 1 + BEGIN + IF @Execute = 'Y' RAISERROR('@MoveFiles = 1, adjusting paths', 0, 1) WITH NOWAIT; -RAISERROR(N'Checking for forced serialization', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET b.is_forced_serial = 1 -FROM #query_plan qp -JOIN #working_warnings AS b -ON qp.sql_handle = b.sql_handle -AND b.is_parallel IS NULL -AND qp.query_plan.exist('/p:QueryPlan/@NonParallelPlanReason') = 1 -OPTION (RECOMPILE); + WITH Files + AS ( + SELECT + CASE + WHEN Type = 'D' THEN @MoveDataDrive + WHEN Type = 'L' THEN @MoveLogDrive + WHEN Type = 'S' THEN @MoveFilestreamDrive + END + CASE + WHEN @Database = @RestoreDatabaseName THEN REVERSE(LEFT(REVERSE(PhysicalName), CHARINDEX('\', REVERSE(PhysicalName), 1) -1)) + ELSE REPLACE(REVERSE(LEFT(REVERSE(PhysicalName), CHARINDEX('\', REVERSE(PhysicalName), 1) -1)), @Database, SUBSTRING(@RestoreDatabaseName, 2, LEN(@RestoreDatabaseName) -2)) + END AS TargetPhysicalName, + PhysicalName, + LogicalName + FROM #FileListParameters) + SELECT @MoveOption = @MoveOption + N', MOVE ''' + Files.LogicalName + N''' TO ''' + Files.TargetPhysicalName + '''' + FROM Files + WHERE Files.TargetPhysicalName <> Files.PhysicalName; + + IF @Debug = 1 PRINT @MoveOption + END; + + /*Process @ExistingDBAction flag */ + IF @ExistingDBAction BETWEEN 1 AND 4 + BEGIN + IF @RestoreDatabaseID IS NOT NULL + BEGIN + IF @ExistingDBAction = 1 + BEGIN + RAISERROR('Setting single user', 0, 1) WITH NOWAIT; + SET @sql = N'ALTER DATABASE ' + @RestoreDatabaseName + ' SET SINGLE_USER WITH ROLLBACK IMMEDIATE; ' + NCHAR(13); + IF @Debug = 1 OR @Execute = 'N' + BEGIN + IF @sql IS NULL PRINT '@sql is NULL for SINGLE_USER'; + PRINT @sql; + END; + IF @Debug IN (0, 1) AND @Execute = 'Y' AND DATABASEPROPERTYEX(@RestoreDatabaseName,'STATUS') != 'RESTORING' + EXECUTE @sql = [dbo].[CommandExecute] @DatabaseContext=N'master', @Command = @sql, @CommandType = 'ALTER DATABASE SINGLE_USER', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y'; + END + IF @ExistingDBAction IN (2, 3) + BEGIN + RAISERROR('Killing connections', 0, 1) WITH NOWAIT; + SET @sql = N'/* Kill connections */' + NCHAR(13); + SELECT + @sql = @sql + N'KILL ' + CAST(spid as nvarchar(5)) + N';' + NCHAR(13) + FROM + --database_ID was only added to sys.dm_exec_sessions in SQL Server 2012 but we need to support older + sys.sysprocesses + WHERE + dbid = @RestoreDatabaseID; + IF @Debug = 1 OR @Execute = 'N' + BEGIN + IF @sql IS NULL PRINT '@sql is NULL for Kill connections'; + PRINT @sql; + END; + IF @Debug IN (0, 1) AND @Execute = 'Y' + EXECUTE @sql = [dbo].[CommandExecute] @DatabaseContext=N'master', @Command = @sql, @CommandType = 'KILL CONNECTIONS', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y'; + END + IF @ExistingDBAction = 3 + BEGIN + RAISERROR('Dropping database', 0, 1) WITH NOWAIT; + + SET @sql = N'DROP DATABASE ' + @RestoreDatabaseName + NCHAR(13); + IF @Debug = 1 OR @Execute = 'N' + BEGIN + IF @sql IS NULL PRINT '@sql is NULL for DROP DATABASE'; + PRINT @sql; + END; + IF @Debug IN (0, 1) AND @Execute = 'Y' + EXECUTE @sql = [dbo].[CommandExecute] @DatabaseContext=N'master', @Command = @sql, @CommandType = 'DROP DATABASE', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y'; + END + IF @ExistingDBAction = 4 + BEGIN + RAISERROR ('Offlining database', 0, 1) WITH NOWAIT; + SET @sql = N'ALTER DATABASE ' + @RestoreDatabaseName + SPACE( 1 ) + 'SET OFFLINE WITH ROLLBACK IMMEDIATE'; + IF @Debug = 1 OR @Execute = 'N' + BEGIN + IF @sql IS NULL PRINT '@sql is NULL for Offline database'; + PRINT @sql; + END; + IF @Debug IN (0, 1) AND @Execute = 'Y' AND DATABASEPROPERTYEX(@RestoreDatabaseName,'STATUS') != 'RESTORING' + EXECUTE @sql = [dbo].[CommandExecute] @DatabaseContext=N'master', @Command = @sql, @CommandType = 'OFFLINE DATABASE', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y'; + END; + END + ELSE + RAISERROR('@ExistingDBAction > 0, but no existing @RestoreDatabaseName', 0, 1) WITH NOWAIT; + END + ELSE + IF @Execute = 'Y' OR @Debug = 1 RAISERROR('@ExistingDBAction %u so do nothing', 0, 1, @ExistingDBAction) WITH NOWAIT; -IF @ExpertMode > 0 -BEGIN -RAISERROR(N'Checking for ColumnStore queries operating in Row Mode instead of Batch Mode', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET b.columnstore_row_mode = x.is_row_mode -FROM #working_warnings AS b -JOIN ( -SELECT - r.sql_handle, - r.relop.exist('/p:RelOp[(@EstimatedExecutionMode[.="Row"])]') AS is_row_mode -FROM #relop r -WHERE r.relop.exist('/p:RelOp/p:IndexScan[(@Storage[.="ColumnStore"])]') = 1 -) AS x ON x.sql_handle = b.sql_handle -OPTION (RECOMPILE); -END; + IF @ContinueLogs = 0 + BEGIN + IF @Execute = 'Y' RAISERROR('@ContinueLogs set to 0', 0, 1) WITH NOWAIT; + + /* now take split backups into account */ + IF (SELECT COUNT(*) FROM #SplitFullBackups) > 0 + BEGIN + RAISERROR('Split backups found', 0, 1) WITH NOWAIT; + SET @sql = N'RESTORE DATABASE ' + @RestoreDatabaseName + N' FROM ' + + STUFF( + (SELECT CHAR( 10 ) + ',DISK=''' + BackupPath + BackupFile + '''' + FROM #SplitFullBackups + ORDER BY BackupFile + FOR XML PATH ('')), + 1, + 2, + '') + N' WITH NORECOVERY, REPLACE' + @BackupParameters + @MoveOption + NCHAR(13) + NCHAR(10); + END; + ELSE + BEGIN + SET @sql = N'RESTORE DATABASE ' + @RestoreDatabaseName + N' FROM DISK = ''' + @CurrentBackupPathFull + @LastFullBackup + N''' WITH NORECOVERY, REPLACE' + @BackupParameters + @MoveOption + NCHAR(13) + NCHAR(10); + END + IF (@StandbyMode = 1) + BEGIN + IF (@StandbyUndoPath IS NULL) + BEGIN + IF @Execute = 'Y' OR @Debug = 1 RAISERROR('The file path of the undo file for standby mode was not specified. The database will not be restored in standby mode.', 0, 1) WITH NOWAIT; + END + ELSE IF (SELECT COUNT(*) FROM #SplitFullBackups) > 0 + BEGIN + SET @sql = @sql + ', STANDBY = ''' + @StandbyUndoPath + @Database + 'Undo.ldf''' + NCHAR(13) + NCHAR(10); + END + ELSE + BEGIN + SET @sql = N'RESTORE DATABASE ' + @RestoreDatabaseName + N' FROM DISK = ''' + @CurrentBackupPathFull + @LastFullBackup + N''' WITH REPLACE' + @BackupParameters + @MoveOption + N' , STANDBY = ''' + @StandbyUndoPath + @Database + 'Undo.ldf''' + NCHAR(13) + NCHAR(10); + END + END; + IF @Debug = 1 OR @Execute = 'N' + BEGIN + IF @sql IS NULL PRINT '@sql is NULL for RESTORE DATABASE: @BackupPathFull, @LastFullBackup, @MoveOption'; + PRINT @sql; + END; + + IF @Debug IN (0, 1) AND @Execute = 'Y' + EXECUTE @sql = [dbo].[CommandExecute] @DatabaseContext=N'master', @Command = @sql, @CommandType = 'RESTORE DATABASE', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y'; -IF @ExpertMode > 0 -BEGIN -RAISERROR('Checking for row level security only', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET b.is_row_level = 1 -FROM #working_warnings b -JOIN #statements s -ON s.query_hash = b.query_hash -WHERE s.statement.exist('/p:StmtSimple/@SecurityPolicyApplied[.="true"]') = 1 -OPTION (RECOMPILE); -END; + -- We already loaded #Headers above + --setting the @BackupDateTime to a numeric string so that it can be used in comparisons + SET @BackupDateTime = REPLACE( RIGHT( REPLACE( @LastFullBackup, RIGHT( @LastFullBackup, PATINDEX( '%_[0-9][0-9]%', REVERSE( @LastFullBackup ) ) ), '' ), 16 ), '_', '' ); + + SELECT @FullLastLSN = CAST(LastLSN AS NUMERIC(25, 0)) FROM #Headers WHERE BackupType = 1; + IF @Debug = 1 + BEGIN + IF @BackupDateTime IS NULL PRINT '@BackupDateTime is NULL for REPLACE: @LastFullBackup'; + PRINT @BackupDateTime; + END; + + END; + ELSE + BEGIN + + SELECT @DatabaseLastLSN = CAST(f.redo_start_lsn AS NUMERIC(25, 0)) + FROM master.sys.databases d + JOIN master.sys.master_files f ON d.database_id = f.database_id + WHERE d.name = SUBSTRING(@RestoreDatabaseName, 2, LEN(@RestoreDatabaseName) - 2) AND f.file_id = 1; + + END; +END; -IF @ExpertMode > 0 +IF @BackupPathFull IS NULL AND @ContinueLogs = 1 BEGIN -RAISERROR('Checking for wonky Index Spools', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES ( - 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) -, selects -AS ( SELECT s.plan_id, s.query_id - FROM #statements AS s - WHERE s.statement.exist('/p:StmtSimple/@StatementType[.="SELECT"]') = 1 ) -, spools -AS ( SELECT DISTINCT r.plan_id, - r.query_id, - c.n.value('@EstimateRows', 'FLOAT') AS estimated_rows, - c.n.value('@EstimateIO', 'FLOAT') AS estimated_io, - c.n.value('@EstimateCPU', 'FLOAT') AS estimated_cpu, - c.n.value('@EstimateRebinds', 'FLOAT') AS estimated_rebinds -FROM #relop AS r -JOIN selects AS s -ON s.plan_id = r.plan_id - AND s.query_id = r.query_id -CROSS APPLY r.relop.nodes('/p:RelOp') AS c(n) -WHERE r.relop.exist('/p:RelOp[@PhysicalOp="Index Spool" and @LogicalOp="Eager Spool"]') = 1 -) -UPDATE ww - SET ww.index_spool_rows = sp.estimated_rows, - ww.index_spool_cost = ((sp.estimated_io * sp.estimated_cpu) * CASE WHEN sp.estimated_rebinds < 1 THEN 1 ELSE sp.estimated_rebinds END) - -FROM #working_warnings ww -JOIN spools sp -ON ww.plan_id = sp.plan_id -AND ww.query_id = sp.query_id -OPTION (RECOMPILE); + + SELECT @DatabaseLastLSN = CAST(f.redo_start_lsn AS NUMERIC(25, 0)) + FROM master.sys.databases d + JOIN master.sys.master_files f ON d.database_id = f.database_id + WHERE d.name = SUBSTRING(@RestoreDatabaseName, 2, LEN(@RestoreDatabaseName) - 2) AND f.file_id = 1; + END; +IF @BackupPathDiff IS NOT NULL +BEGIN + DELETE FROM @FileList; + DELETE FROM @FileListSimple; + DELETE FROM @PathItem; -IF (PARSENAME(CONVERT(VARCHAR(128), SERVERPROPERTY ('PRODUCTVERSION')), 4)) >= 14 -OR ((PARSENAME(CONVERT(VARCHAR(128), SERVERPROPERTY ('PRODUCTVERSION')), 4)) = 13 - AND PARSENAME(CONVERT(VARCHAR(128), SERVERPROPERTY ('PRODUCTVERSION')), 2) >= 5026) + DECLARE @CurrentBackupPathDiff NVARCHAR(512); + -- Split CSV string logic has taken from Ola Hallengren's :) + WITH BackupPaths ( + StartPosition, EndPosition, PathItem + ) + AS ( + SELECT 1 AS StartPosition, + ISNULL( NULLIF( CHARINDEX( ',', @BackupPathDiff, 1 ), 0 ), LEN( @BackupPathDiff ) + 1 ) AS EndPosition, + SUBSTRING( @BackupPathDiff, 1, ISNULL( NULLIF( CHARINDEX( ',', @BackupPathDiff, 1 ), 0 ), LEN( @BackupPathDiff ) + 1 ) - 1 ) AS PathItem + WHERE @BackupPathDiff IS NOT NULL + UNION ALL + SELECT CAST( EndPosition AS INT ) + 1 AS StartPosition, + ISNULL( NULLIF( CHARINDEX( ',', @BackupPathDiff, EndPosition + 1 ), 0 ), LEN( @BackupPathDiff ) + 1 ) AS EndPosition, + SUBSTRING( @BackupPathDiff, EndPosition + 1, ISNULL( NULLIF( CHARINDEX( ',', @BackupPathDiff, EndPosition + 1 ), 0 ), LEN( @BackupPathDiff ) + 1 ) - EndPosition - 1 ) AS PathItem + FROM BackupPaths + WHERE EndPosition < LEN( @BackupPathDiff ) + 1 + ) + INSERT INTO @PathItem + SELECT CASE RIGHT( PathItem, 1 ) WHEN '\' THEN PathItem ELSE PathItem + '\' END FROM BackupPaths; -BEGIN + WHILE 1 = 1 + BEGIN -RAISERROR(N'Beginning 2017 and 2016 SP2 specfic checks', 0, 1) WITH NOWAIT; + SELECT TOP 1 @CurrentBackupPathDiff = PathItem FROM @PathItem + WHERE PathItem > COALESCE( @CurrentBackupPathDiff, '' ) ORDER BY PathItem; + IF @@rowcount = 0 BREAK; -IF @ExpertMode > 0 -BEGIN -RAISERROR('Gathering stats information', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -INSERT #stats_agg WITH (TABLOCK) - (sql_handle, last_update, modification_count, sampling_percent, [statistics], [table], [schema], [database]) -SELECT qp.sql_handle, - x.c.value('@LastUpdate', 'DATETIME2(7)') AS LastUpdate, - x.c.value('@ModificationCount', 'BIGINT') AS ModificationCount, - x.c.value('@SamplingPercent', 'FLOAT') AS SamplingPercent, - x.c.value('@Statistics', 'NVARCHAR(258)') AS [Statistics], - x.c.value('@Table', 'NVARCHAR(258)') AS [Table], - x.c.value('@Schema', 'NVARCHAR(258)') AS [Schema], - x.c.value('@Database', 'NVARCHAR(258)') AS [Database] -FROM #query_plan AS qp -CROSS APPLY qp.query_plan.nodes('//p:OptimizerStatsUsage/p:StatisticsInfo') x (c) -OPTION (RECOMPILE); + IF @SimpleFolderEnumeration = 1 + BEGIN -- Get list of files + INSERT INTO @FileListSimple (BackupFile, depth, [file]) EXEC master.sys.xp_dirtree @CurrentBackupPathDiff, 1, 1; + INSERT @FileList (BackupPath,BackupFile) SELECT @CurrentBackupPathDiff, BackupFile FROM @FileListSimple; + DELETE FROM @FileListSimple; + END + ELSE + BEGIN + SET @cmd = N'DIR /b "' + @CurrentBackupPathDiff + N'"'; + IF @Debug = 1 + BEGIN + IF @cmd IS NULL PRINT '@cmd is NULL for @CurrentBackupPathDiff'; + PRINT @cmd; + END; + INSERT INTO @FileList (BackupFile) EXEC master.sys.xp_cmdshell @cmd; + UPDATE @FileList SET BackupPath = @CurrentBackupPathDiff WHERE BackupPath IS NULL; + END; + + IF @Debug = 1 + BEGIN + SELECT BackupPath,BackupFile FROM @FileList WHERE BackupFile IS NOT NULL; + END; + IF @SimpleFolderEnumeration = 0 + BEGIN + /*Full Sanity check folders*/ + IF ( + SELECT COUNT(*) + FROM @FileList AS fl + WHERE fl.BackupFile = 'The system cannot find the path specified.' + ) = 1 + BEGIN + RAISERROR('(DIFF) Bad value for path %s', 16, 1, @CurrentBackupPathDiff) WITH NOWAIT; + RETURN; + END; + IF ( + SELECT COUNT(*) + FROM @FileList AS fl + WHERE fl.BackupFile = 'Access is denied.' + ) = 1 + BEGIN + RAISERROR('(DIFF) Access is denied to %s', 16, 1, @CurrentBackupPathDiff) WITH NOWAIT; + RETURN; + END; + IF ( + SELECT COUNT(*) + FROM @FileList AS fl + WHERE fl.BackupFile = 'The user name or password is incorrect.' + ) = 1 + BEGIN + RAISERROR('(DIFF) Incorrect user name or password for %s', 16, 1, @CurrentBackupPathDiff) WITH NOWAIT; + RETURN; + END; + END; + END + /*End folder sanity check*/ + -- Find latest diff backup + SELECT @LastDiffBackup = MAX(BackupFile) + FROM @FileList + WHERE BackupFile LIKE N'%.bak' + AND + BackupFile LIKE N'%' + @Database + '%' + AND + (@StopAt IS NULL OR REPLACE( RIGHT( REPLACE( @LastDiffBackup, RIGHT( @LastDiffBackup, PATINDEX( '%_[0-9][0-9]%', REVERSE( @LastDiffBackup ) ) ), '' ), 16 ), '_', '' ) <= @StopAt); + -- Load FileList data into Temp Table sorted by DateTime Stamp desc + SELECT BackupPath, BackupFile INTO #SplitDiffBackups + FROM @FileList + WHERE LEFT( BackupFile, LEN( BackupFile ) - PATINDEX( '%[_]%', REVERSE( BackupFile ) ) ) = LEFT( @LastDiffBackup, LEN( @LastDiffBackup ) - PATINDEX( '%[_]%', REVERSE( @LastDiffBackup ) ) ) + AND PATINDEX( '%[_]%', REVERSE( @LastDiffBackup ) ) <= 7 -- there is a 1 or 2 digit index at the end of the string which indicates split backups. Olla only supports up to 64 file split. + ORDER BY REPLACE( RIGHT( REPLACE( BackupFile, RIGHT( BackupFile, PATINDEX( '%_[0-9][0-9]%', REVERSE( BackupFile ) ) ), '' ), 16 ), '_', '' ) DESC; -RAISERROR('Checking for stale stats', 0, 1) WITH NOWAIT; -WITH stale_stats AS ( - SELECT sa.sql_handle - FROM #stats_agg AS sa - GROUP BY sa.sql_handle - HAVING MAX(sa.last_update) <= DATEADD(DAY, -7, SYSDATETIME()) - AND AVG(sa.modification_count) >= 100000 -) -UPDATE b -SET b.stale_stats = 1 -FROM #working_warnings AS b -JOIN stale_stats os -ON b.sql_handle = os.sql_handle -OPTION (RECOMPILE); -END; + --No file = no backup to restore + SET @LastDiffBackupDateTime = REPLACE( RIGHT( REPLACE( @LastDiffBackup, RIGHT( @LastDiffBackup, PATINDEX( '%_[0-9][0-9]%', REVERSE( @LastDiffBackup ) ) ), '' ), 16 ), '_', '' ); + + -- Get the TOP record to use in "Restore HeaderOnly/FileListOnly" statement as well as non-split backups + SELECT TOP 1 @CurrentBackupPathDiff = BackupPath, @LastDiffBackup = BackupFile FROM @FileList + ORDER BY REPLACE( RIGHT( REPLACE( BackupFile, RIGHT( BackupFile, PATINDEX( '%_[0-9][0-9]%', REVERSE( BackupFile ) ) ), '' ), 16 ), '_', '' ) DESC; + IF @RestoreDiff = 1 AND @BackupDateTime < @LastDiffBackupDateTime + BEGIN -IF (PARSENAME(CONVERT(VARCHAR(128), SERVERPROPERTY ('PRODUCTVERSION')), 4)) >= 14 - AND @ExpertMode > 0 -BEGIN -RAISERROR(N'Checking for Adaptive Joins', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p), -aj AS ( - SELECT r.sql_handle - FROM #relop AS r - CROSS APPLY r.relop.nodes('//p:RelOp') x(c) - WHERE x.c.exist('@IsAdaptive[.=1]') = 1 -) -UPDATE b -SET b.is_adaptive = 1 -FROM #working_warnings AS b -JOIN aj -ON b.sql_handle = aj.sql_handle -OPTION (RECOMPILE); -END; + IF (SELECT COUNT(*) FROM #SplitDiffBackups) > 0 + BEGIN + RAISERROR ('Split backups found', 0, 1) WITH NOWAIT; + SET @sql = N'RESTORE DATABASE ' + @RestoreDatabaseName + N' FROM ' + + STUFF( + (SELECT CHAR( 10 ) + ',DISK=''' + BackupPath + BackupFile + '''' + FROM #SplitDiffBackups + ORDER BY BackupFile + FOR XML PATH ('')), + 1, + 2, + '' ) + N' WITH NORECOVERY, REPLACE' + @BackupParameters + @MoveOption + NCHAR(13) + NCHAR(10); + END; + ELSE + SET @sql = N'RESTORE DATABASE ' + @RestoreDatabaseName + N' FROM DISK = ''' + @CurrentBackupPathDiff + @LastDiffBackup + N''' WITH NORECOVERY' + @BackupParameters + @MoveOption + NCHAR(13) + NCHAR(10); + IF (@StandbyMode = 1) + BEGIN + IF (@StandbyUndoPath IS NULL) + BEGIN + IF @Execute = 'Y' OR @Debug = 1 RAISERROR('The file path of the undo file for standby mode was not specified. The database will not be restored in standby mode.', 0, 1) WITH NOWAIT; + END + ELSE IF (SELECT COUNT(*) FROM #SplitDiffBackups) > 0 + SET @sql = @sql + ', STANDBY = ''' + @StandbyUndoPath + @Database + 'Undo.ldf''' + NCHAR(13) + NCHAR(10); + ELSE + SET @sql = N'RESTORE DATABASE ' + @RestoreDatabaseName + N' FROM DISK = ''' + @BackupPathDiff + @LastDiffBackup + N''' WITH STANDBY = ''' + @StandbyUndoPath + @Database + 'Undo.ldf''' + @BackupParameters + @MoveOption + NCHAR(13) + NCHAR(10); + END; + IF @Debug = 1 OR @Execute = 'N' + BEGIN + IF @sql IS NULL PRINT '@sql is NULL for RESTORE DATABASE: @BackupPathDiff, @LastDiffBackup'; + PRINT @sql; + END; + IF @Debug IN (0, 1) AND @Execute = 'Y' + EXECUTE @sql = [dbo].[CommandExecute] @DatabaseContext=N'master', @Command = @sql, @CommandType = 'RESTORE DATABASE', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y'; + + --get the backup completed data so we can apply tlogs from that point forwards + SET @sql = REPLACE(@HeadersSQL, N'{Path}', @CurrentBackupPathDiff + @LastDiffBackup); + + IF @Debug = 1 + BEGIN + IF @sql IS NULL PRINT '@sql is NULL for REPLACE: @CurrentBackupPathDiff, @LastDiffBackup'; + PRINT @sql; + END; + + EXECUTE (@sql); + IF @Debug = 1 + BEGIN + SELECT '#Headers' AS table_name, @LastDiffBackup AS DiffbackupFile, * FROM #Headers AS h WHERE h.BackupType = 5; + END + + --set the @BackupDateTime to the date time on the most recent differential + SET @BackupDateTime = ISNULL( @LastDiffBackupDateTime, @BackupDateTime ); + IF @Debug = 1 + BEGIN + IF @BackupDateTime IS NULL PRINT '@BackupDateTime is NULL for REPLACE: @LastDiffBackupDateTime'; + PRINT @BackupDateTime; + END; + SELECT @DiffLastLSN = CAST(LastLSN AS NUMERIC(25, 0)) + FROM #Headers + WHERE BackupType = 5; + END; -IF @ExpertMode > 0 -BEGIN; -RAISERROR(N'Checking for Row Goals', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p), -row_goals AS( -SELECT qs.query_hash -FROM #relop qs -WHERE relop.value('sum(/p:RelOp/@EstimateRowsWithoutRowGoal)', 'float') > 0 -) -UPDATE b -SET b.is_row_goal = 1 -FROM #working_warnings b -JOIN row_goals -ON b.query_hash = row_goals.query_hash -OPTION (RECOMPILE); -END; + IF @DiffLastLSN IS NULL + BEGIN + SET @DiffLastLSN=@FullLastLSN + END +END -END; +IF @BackupPathLog IS NOT NULL +BEGIN + DELETE FROM @FileList; + DELETE FROM @FileListSimple; + DELETE FROM @PathItem; -RAISERROR(N'Performing query level checks', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET b.missing_index_count = query_plan.value('count(//p:QueryPlan/p:MissingIndexes/p:MissingIndexGroup)', 'int') , - b.unmatched_index_count = CASE WHEN is_trivial <> 1 THEN query_plan.value('count(//p:QueryPlan/p:UnmatchedIndexes/p:Parameterization/p:Object)', 'int') END -FROM #query_plan qp -JOIN #working_warnings AS b -ON b.query_hash = qp.query_hash -OPTION (RECOMPILE); + DECLARE @CurrentBackupPathLog NVARCHAR(512); + -- Split CSV string logic has taken from Ola Hallengren's :) + WITH BackupPaths ( + StartPosition, EndPosition, PathItem + ) + AS ( + SELECT 1 AS StartPosition, + ISNULL( NULLIF( CHARINDEX( ',', @BackupPathLog, 1 ), 0 ), LEN( @BackupPathLog ) + 1 ) AS EndPosition, + SUBSTRING( @BackupPathLog, 1, ISNULL( NULLIF( CHARINDEX( ',', @BackupPathLog, 1 ), 0 ), LEN( @BackupPathLog ) + 1 ) - 1 ) AS PathItem + WHERE @BackupPathLog IS NOT NULL + UNION ALL + SELECT CAST( EndPosition AS INT ) + 1 AS StartPosition, + ISNULL( NULLIF( CHARINDEX( ',', @BackupPathLog, EndPosition + 1 ), 0 ), LEN( @BackupPathLog ) + 1 ) AS EndPosition, + SUBSTRING( @BackupPathLog, EndPosition + 1, ISNULL( NULLIF( CHARINDEX( ',', @BackupPathLog, EndPosition + 1 ), 0 ), LEN( @BackupPathLog ) + 1 ) - EndPosition - 1 ) AS PathItem + FROM BackupPaths + WHERE EndPosition < LEN( @BackupPathLog ) + 1 + ) + INSERT INTO @PathItem + SELECT CASE RIGHT( PathItem, 1 ) WHEN '\' THEN PathItem ELSE PathItem + '\' END FROM BackupPaths; + WHILE 1 = 1 + BEGIN + SELECT TOP 1 @CurrentBackupPathLog = PathItem FROM @PathItem + WHERE PathItem > COALESCE( @CurrentBackupPathLog, '' ) ORDER BY PathItem; + IF @@rowcount = 0 BREAK; -RAISERROR(N'Trace flag checks', 0, 1) WITH NOWAIT; -;WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -, tf_pretty AS ( -SELECT qp.sql_handle, - q.n.value('@Value', 'INT') AS trace_flag, - q.n.value('@Scope', 'VARCHAR(10)') AS scope -FROM #query_plan qp -CROSS APPLY qp.query_plan.nodes('/p:QueryPlan/p:TraceFlags/p:TraceFlag') AS q(n) -) -INSERT #trace_flags WITH (TABLOCK) - (sql_handle, global_trace_flags, session_trace_flags ) -SELECT DISTINCT tf1.sql_handle , - STUFF(( - SELECT DISTINCT N', ' + CONVERT(NVARCHAR(5), tf2.trace_flag) - FROM tf_pretty AS tf2 - WHERE tf1.sql_handle = tf2.sql_handle - AND tf2.scope = 'Global' - FOR XML PATH(N'')), 1, 2, N'' - ) AS global_trace_flags, - STUFF(( - SELECT DISTINCT N', ' + CONVERT(NVARCHAR(5), tf2.trace_flag) - FROM tf_pretty AS tf2 - WHERE tf1.sql_handle = tf2.sql_handle - AND tf2.scope = 'Session' - FOR XML PATH(N'')), 1, 2, N'' - ) AS session_trace_flags -FROM tf_pretty AS tf1 -OPTION (RECOMPILE); + IF @SimpleFolderEnumeration = 1 + BEGIN -- Get list of files + INSERT INTO @FileListSimple (BackupFile, depth, [file]) EXEC master.sys.xp_dirtree @BackupPathLog, 1, 1; + INSERT @FileList (BackupPath, BackupFile) SELECT @CurrentBackupPathLog, BackupFile FROM @FileListSimple; + DELETE FROM @FileListSimple; + END + ELSE + BEGIN + SET @cmd = N'DIR /b "' + @CurrentBackupPathLog + N'"'; + IF @Debug = 1 + BEGIN + IF @cmd IS NULL PRINT '@cmd is NULL for @CurrentBackupPathLog'; + PRINT @cmd; + END; + INSERT INTO @FileList (BackupFile) EXEC master.sys.xp_cmdshell @cmd; + UPDATE @FileList SET BackupPath = @CurrentBackupPathLog + WHERE BackupPath IS NULL; + END; + + IF @SimpleFolderEnumeration = 1 + BEGIN + /*Check what we can*/ + IF NOT EXISTS (SELECT * FROM @FileList) + BEGIN + RAISERROR('(LOG) No rows were returned for that database %s in path %s', 16, 1, @Database, @CurrentBackupPathLog) WITH NOWAIT; + RETURN; + END; + END + ELSE + BEGIN + /*Full Sanity check folders*/ + IF ( + SELECT COUNT(*) + FROM @FileList AS fl + WHERE fl.BackupFile = 'The system cannot find the path specified.' + OR fl.BackupFile = 'File Not Found' + ) = 1 + BEGIN + RAISERROR('(LOG) No rows or bad value for path %s', 16, 1, @CurrentBackupPathLog) WITH NOWAIT; + RETURN; + END; -UPDATE b -SET b.trace_flags_session = tf.session_trace_flags -FROM #working_warnings AS b -JOIN #trace_flags tf -ON tf.sql_handle = b.sql_handle -OPTION (RECOMPILE); + IF ( + SELECT COUNT(*) + FROM @FileList AS fl + WHERE fl.BackupFile = 'Access is denied.' + ) = 1 + BEGIN + RAISERROR('(LOG) Access is denied to %s', 16, 1, @CurrentBackupPathLog) WITH NOWAIT; + RETURN; + END; + IF ( + SELECT COUNT(*) + FROM @FileList AS fl + ) = 1 + AND + ( + SELECT COUNT(*) + FROM @FileList AS fl + WHERE fl.BackupFile IS NULL + ) = 1 + BEGIN + RAISERROR('(LOG) Empty directory %s', 16, 1, @CurrentBackupPathLog) WITH NOWAIT; + RETURN; + END -RAISERROR(N'Checking for MSTVFs', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET b.is_mstvf = 1 -FROM #relop AS r -JOIN #working_warnings AS b -ON b.sql_handle = r.sql_handle -WHERE r.relop.exist('/p:RelOp[(@EstimateRows="100" or @EstimateRows="1") and @LogicalOp="Table-valued function"]') = 1 -OPTION (RECOMPILE); + IF ( + SELECT COUNT(*) + FROM @FileList AS fl + WHERE fl.BackupFile = 'The user name or password is incorrect.' + ) = 1 + BEGIN + RAISERROR('(LOG) Incorrect user name or password for %s', 16, 1, @CurrentBackupPathLog) WITH NOWAIT; + RETURN; + END; + END; + END + /*End folder sanity check*/ -IF @ExpertMode > 0 +IF @Debug = 1 +BEGIN + SELECT * FROM @FileList WHERE BackupFile IS NOT NULL; +END + +IF @SkipBackupsAlreadyInMsdb = 1 BEGIN -RAISERROR(N'Checking for many to many merge joins', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET b.is_mm_join = 1 -FROM #relop AS r -JOIN #working_warnings AS b -ON b.sql_handle = r.sql_handle -WHERE r.relop.exist('/p:RelOp/p:Merge/@ManyToMany[.="1"]') = 1 -OPTION (RECOMPILE); -END; + SELECT TOP 1 @LogLastNameInMsdbAS = bf.physical_device_name + FROM msdb.dbo.backupmediafamily bf + INNER JOIN msdb.dbo.backupset bs ON bs.media_set_id = bf.media_set_id + INNER JOIN msdb.dbo.restorehistory rh ON rh.backup_set_id = bs.backup_set_id + WHERE physical_device_name like @BackupPathLog + '%' + AND rh.destination_database_name = @UnquotedRestoreDatabaseName + ORDER BY physical_device_name DESC + + IF @Debug = 1 + BEGIN + SELECT 'Keeping LOG backups with name > : ' + @LogLastNameInMsdbAS + END + + DELETE fl + FROM @FileList AS fl + WHERE fl.BackupPath + fl.BackupFile <= @LogLastNameInMsdbAS +END -IF @ExpertMode > 0 -BEGIN -RAISERROR(N'Is Paul White Electric?', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p), -is_paul_white_electric AS ( -SELECT 1 AS [is_paul_white_electric], -r.sql_handle -FROM #relop AS r -CROSS APPLY r.relop.nodes('//p:RelOp') c(n) -WHERE c.n.exist('@PhysicalOp[.="Switch"]') = 1 -) -UPDATE b -SET b.is_paul_white_electric = ipwe.is_paul_white_electric -FROM #working_warnings AS b -JOIN is_paul_white_electric ipwe -ON ipwe.sql_handle = b.sql_handle -OPTION (RECOMPILE); -END; +IF (@OnlyLogsAfter IS NOT NULL) +BEGIN + + IF @Execute = 'Y' OR @Debug = 1 RAISERROR('@OnlyLogsAfter is NOT NULL, deleting from @FileList', 0, 1) WITH NOWAIT; + + DELETE fl + FROM @FileList AS fl + WHERE BackupFile LIKE N'%.trn' + AND BackupFile LIKE N'%' + @Database + N'%' + AND REPLACE( RIGHT( REPLACE( fl.BackupFile, RIGHT( fl.BackupFile, PATINDEX( '%_[0-9][0-9]%', REVERSE( fl.BackupFile ) ) ), '' ), 16 ), '_', '' ) < @OnlyLogsAfter; + +END -RAISERROR(N'Checking for non-sargable predicates', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES ( 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) -, nsarg - AS ( SELECT r.query_hash, 1 AS fn, 0 AS jo, 0 AS lk - FROM #relop AS r - CROSS APPLY r.relop.nodes('/p:RelOp/p:IndexScan/p:Predicate/p:ScalarOperator/p:Compare/p:ScalarOperator') AS ca(x) - WHERE ( ca.x.exist('//p:ScalarOperator/p:Intrinsic/@FunctionName') = 1 - OR ca.x.exist('//p:ScalarOperator/p:IF') = 1 ) - UNION ALL - SELECT r.query_hash, 0 AS fn, 1 AS jo, 0 AS lk - FROM #relop AS r - CROSS APPLY r.relop.nodes('/p:RelOp//p:ScalarOperator') AS ca(x) - WHERE r.relop.exist('/p:RelOp[contains(@LogicalOp, "Join")]') = 1 - AND ca.x.exist('//p:ScalarOperator[contains(@ScalarString, "Expr")]') = 1 - UNION ALL - SELECT r.query_hash, 0 AS fn, 0 AS jo, 1 AS lk - FROM #relop AS r - CROSS APPLY r.relop.nodes('/p:RelOp/p:IndexScan/p:Predicate/p:ScalarOperator') AS ca(x) - CROSS APPLY ca.x.nodes('//p:Const') AS co(x) - WHERE ca.x.exist('//p:ScalarOperator/p:Intrinsic/@FunctionName[.="like"]') = 1 - AND ( ( co.x.value('substring(@ConstValue, 1, 1)', 'VARCHAR(100)') <> 'N' - AND co.x.value('substring(@ConstValue, 2, 1)', 'VARCHAR(100)') = '%' ) - OR ( co.x.value('substring(@ConstValue, 1, 1)', 'VARCHAR(100)') = 'N' - AND co.x.value('substring(@ConstValue, 3, 1)', 'VARCHAR(100)') = '%' ))), - d_nsarg - AS ( SELECT DISTINCT - nsarg.query_hash - FROM nsarg - WHERE nsarg.fn = 1 - OR nsarg.jo = 1 - OR nsarg.lk = 1 ) -UPDATE b -SET b.is_nonsargable = 1 -FROM d_nsarg AS d -JOIN #working_warnings AS b - ON b.query_hash = d.query_hash -OPTION ( RECOMPILE ); - RAISERROR(N'Getting information about implicit conversions and stored proc parameters', 0, 1) WITH NOWAIT; +-- Check for log backups +IF(@StopAt IS NULL AND @OnlyLogsAfter IS NULL) + BEGIN + DELETE FROM @FileList + WHERE BackupFile LIKE N'%.trn' + AND BackupFile LIKE N'%' + @Database + N'%' + AND NOT (@ContinueLogs = 1 OR (@ContinueLogs = 0 AND REPLACE( RIGHT( REPLACE( BackupFile, RIGHT( BackupFile, PATINDEX( '%_[0-9][0-9]%', REVERSE( BackupFile ) ) ), '' ), 16 ), '_', '' ) >= @BackupDateTime)); + END; - RAISERROR(N'Getting variable info', 0, 1) WITH NOWAIT; - WITH XMLNAMESPACES ( 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) - INSERT #variable_info ( query_hash, sql_handle, proc_name, variable_name, variable_datatype, compile_time_value ) - SELECT DISTINCT - qp.query_hash, - qp.sql_handle, - b.proc_or_function_name AS proc_name, - q.n.value('@Column', 'NVARCHAR(258)') AS variable_name, - q.n.value('@ParameterDataType', 'NVARCHAR(258)') AS variable_datatype, - q.n.value('@ParameterCompiledValue', 'NVARCHAR(258)') AS compile_time_value - FROM #query_plan AS qp - JOIN #working_warnings AS b - ON (b.query_hash = qp.query_hash AND b.proc_or_function_name = 'adhoc') - OR (b.sql_handle = qp.sql_handle AND b.proc_or_function_name <> 'adhoc') - CROSS APPLY qp.query_plan.nodes('//p:QueryPlan/p:ParameterList/p:ColumnReference') AS q(n) - OPTION (RECOMPILE); - RAISERROR(N'Getting conversion info', 0, 1) WITH NOWAIT; - WITH XMLNAMESPACES ( 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) - INSERT #conversion_info ( query_hash, sql_handle, proc_name, expression ) - SELECT DISTINCT - qp.query_hash, - qp.sql_handle, - b.proc_or_function_name AS proc_name, - qq.c.value('@Expression', 'NVARCHAR(4000)') AS expression - FROM #query_plan AS qp - JOIN #working_warnings AS b - ON (b.query_hash = qp.query_hash AND b.proc_or_function_name = 'adhoc') - OR (b.sql_handle = qp.sql_handle AND b.proc_or_function_name <> 'adhoc') - CROSS APPLY qp.query_plan.nodes('//p:QueryPlan/p:Warnings/p:PlanAffectingConvert') AS qq(c) - WHERE qq.c.exist('@ConvertIssue[.="Seek Plan"]') = 1 - AND b.implicit_conversions = 1 - OPTION (RECOMPILE); +IF (@StopAt IS NULL AND @OnlyLogsAfter IS NOT NULL) + BEGIN + DELETE FROM @FileList + WHERE BackupFile LIKE N'%.trn' + AND BackupFile LIKE N'%' + @Database + N'%' + AND NOT (@ContinueLogs = 1 OR (@ContinueLogs = 0 AND REPLACE( RIGHT( REPLACE( BackupFile, RIGHT( BackupFile, PATINDEX( '%_[0-9][0-9]%', REVERSE( BackupFile ) ) ), '' ), 16 ), '_', '' ) >= @OnlyLogsAfter)); + END; - RAISERROR(N'Parsing conversion info', 0, 1) WITH NOWAIT; - INSERT #stored_proc_info ( sql_handle, query_hash, proc_name, variable_name, variable_datatype, converted_column_name, column_name, converted_to, compile_time_value ) - SELECT ci.sql_handle, - ci.query_hash, - ci.proc_name, - CASE WHEN ci.at_charindex > 0 - AND ci.bracket_charindex > 0 - THEN SUBSTRING(ci.expression, ci.at_charindex, ci.bracket_charindex) - ELSE N'**no_variable**' - END AS variable_name, - N'**no_variable**' AS variable_datatype, - CASE WHEN ci.at_charindex = 0 - AND ci.comma_charindex > 0 - AND ci.second_comma_charindex > 0 - THEN SUBSTRING(ci.expression, ci.comma_charindex, ci.second_comma_charindex) - ELSE N'**no_column**' - END AS converted_column_name, - CASE WHEN ci.at_charindex = 0 - AND ci.equal_charindex > 0 - AND ci.convert_implicit_charindex = 0 - THEN SUBSTRING(ci.expression, ci.equal_charindex, 4000) - WHEN ci.at_charindex = 0 - AND (ci.equal_charindex -1) > 0 - AND ci.convert_implicit_charindex > 0 - THEN SUBSTRING(ci.expression, 0, ci.equal_charindex -1) - WHEN ci.at_charindex > 0 - AND ci.comma_charindex > 0 - AND ci.second_comma_charindex > 0 - THEN SUBSTRING(ci.expression, ci.comma_charindex, ci.second_comma_charindex) - ELSE N'**no_column **' - END AS column_name, - CASE WHEN ci.paren_charindex > 0 - 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 - 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 - FROM #conversion_info AS ci - OPTION (RECOMPILE); - RAISERROR(N'Updating variables inserted procs', 0, 1) WITH NOWAIT; - UPDATE sp - SET sp.variable_datatype = vi.variable_datatype, - sp.compile_time_value = vi.compile_time_value - FROM #stored_proc_info AS sp - JOIN #variable_info AS vi - ON (sp.proc_name = 'adhoc' AND sp.query_hash = vi.query_hash) - OR (sp.proc_name <> 'adhoc' AND sp.sql_handle = vi.sql_handle) - AND sp.variable_name = vi.variable_name - OPTION (RECOMPILE); - - - RAISERROR(N'Inserting variables for other procs', 0, 1) WITH NOWAIT; - INSERT #stored_proc_info - ( sql_handle, query_hash, variable_name, variable_datatype, compile_time_value, proc_name ) - SELECT vi.sql_handle, vi.query_hash, vi.variable_name, vi.variable_datatype, vi.compile_time_value, vi.proc_name - FROM #variable_info AS vi - WHERE NOT EXISTS - ( - SELECT * - FROM #stored_proc_info AS sp - WHERE (sp.proc_name = 'adhoc' AND sp.query_hash = vi.query_hash) - OR (sp.proc_name <> 'adhoc' AND sp.sql_handle = vi.sql_handle) - ) - OPTION (RECOMPILE); - - RAISERROR(N'Updating procs', 0, 1) WITH NOWAIT; - UPDATE s - SET s.variable_datatype = CASE WHEN s.variable_datatype LIKE '%(%)%' - THEN LEFT(s.variable_datatype, CHARINDEX('(', s.variable_datatype) - 1) - ELSE s.variable_datatype - END, - s.converted_to = CASE WHEN s.converted_to LIKE '%(%)%' - THEN LEFT(s.converted_to, CHARINDEX('(', s.converted_to) - 1) - ELSE s.converted_to - END, - s.compile_time_value = CASE WHEN s.compile_time_value LIKE '%(%)%' - THEN SUBSTRING(s.compile_time_value, - CHARINDEX('(', s.compile_time_value) + 1, - CHARINDEX(')', s.compile_time_value) - 1 - CHARINDEX('(', s.compile_time_value) - ) - WHEN variable_datatype NOT IN ('bit', 'tinyint', 'smallint', 'int', 'bigint') - AND s.variable_datatype NOT LIKE '%binary%' - AND s.compile_time_value NOT LIKE 'N''%''' - AND s.compile_time_value NOT LIKE '''%''' - AND s.compile_time_value <> s.column_name - AND s.compile_time_value <> '**idk_man**' - THEN QUOTENAME(compile_time_value, '''') - ELSE s.compile_time_value - END - FROM #stored_proc_info AS s - OPTION (RECOMPILE); +IF (@StopAt IS NOT NULL AND @OnlyLogsAfter IS NULL) + BEGIN + DELETE FROM @FileList + WHERE BackupFile LIKE N'%.trn' + AND BackupFile LIKE N'%' + @Database + N'%' + AND NOT (@ContinueLogs = 1 OR (@ContinueLogs = 0 AND REPLACE( RIGHT( REPLACE( BackupFile, RIGHT( BackupFile, PATINDEX( '%_[0-9][0-9]%', REVERSE( BackupFile ) ) ), '' ), 16 ), '_', '' ) >= @BackupDateTime) AND REPLACE( RIGHT( REPLACE( BackupFile, RIGHT( BackupFile, PATINDEX( '%_[0-9][0-9]%', REVERSE( BackupFile ) ) ), '' ), 16 ), '_', '' ) <= @StopAt) + AND NOT ((@ContinueLogs = 1 AND REPLACE( RIGHT( REPLACE( BackupFile, RIGHT( BackupFile, PATINDEX( '%_[0-9][0-9]%', REVERSE( BackupFile ) ) ), '' ), 16 ), '_', '' ) <= @StopAt) OR (@ContinueLogs = 0 AND REPLACE( RIGHT( REPLACE( BackupFile, RIGHT( BackupFile, PATINDEX( '%_[0-9][0-9]%', REVERSE( BackupFile ) ) ), '' ), 16 ), '_', '' ) >= @BackupDateTime) AND REPLACE( RIGHT( REPLACE( BackupFile, RIGHT( BackupFile, PATINDEX( '%_[0-9][0-9]%', REVERSE( BackupFile ) ) ), '' ), 16 ), '_', '' ) <= @StopAt) + END; + +IF (@StopAt IS NOT NULL AND @OnlyLogsAfter IS NOT NULL) + BEGIN + DECLARE BackupFiles CURSOR FOR + SELECT BackupFile + FROM @FileList + WHERE BackupFile LIKE N'%.trn' + AND BackupFile LIKE N'%' + @Database + N'%' + AND (@ContinueLogs = 1 OR (@ContinueLogs = 0 AND REPLACE(LEFT(RIGHT(BackupFile, 19), 15),'_','') >= @BackupDateTime) AND REPLACE(LEFT(RIGHT(BackupFile, 19), 15),'_','') <= @StopAt) + AND ((@ContinueLogs = 1 AND REPLACE(LEFT(RIGHT(BackupFile, 19), 15),'_','') <= @StopAt) OR (@ContinueLogs = 0 AND REPLACE(LEFT(RIGHT(BackupFile, 19), 15),'_','') >= @BackupDateTime) AND REPLACE(LEFT(RIGHT(BackupFile, 19), 15),'_','') <= @StopAt) + AND (@ContinueLogs = 1 OR (@ContinueLogs = 0 AND REPLACE(LEFT(RIGHT(BackupFile, 19), 15),'_','') >= @OnlyLogsAfter)) + ORDER BY BackupFile; - RAISERROR(N'Updating SET options', 0, 1) WITH NOWAIT; - WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) - UPDATE s - SET set_options = set_options.ansi_set_options - FROM #stored_proc_info AS s - JOIN ( - SELECT x.sql_handle, - N'SET ANSI_NULLS ' + CASE WHEN [ANSI_NULLS] = 'true' THEN N'ON ' ELSE N'OFF ' END + NCHAR(10) + - N'SET ANSI_PADDING ' + CASE WHEN [ANSI_PADDING] = 'true' THEN N'ON ' ELSE N'OFF ' END + NCHAR(10) + - N'SET ANSI_WARNINGS ' + CASE WHEN [ANSI_WARNINGS] = 'true' THEN N'ON ' ELSE N'OFF ' END + NCHAR(10) + - N'SET ARITHABORT ' + CASE WHEN [ARITHABORT] = 'true' THEN N'ON ' ELSE N' OFF ' END + NCHAR(10) + - N'SET CONCAT_NULL_YIELDS_NULL ' + CASE WHEN [CONCAT_NULL_YIELDS_NULL] = 'true' THEN N'ON ' ELSE N'OFF ' END + NCHAR(10) + - N'SET NUMERIC_ROUNDABORT ' + CASE WHEN [NUMERIC_ROUNDABORT] = 'true' THEN N'ON ' ELSE N'OFF ' END + NCHAR(10) + - N'SET QUOTED_IDENTIFIER ' + CASE WHEN [QUOTED_IDENTIFIER] = 'true' THEN N'ON ' ELSE N'OFF ' + NCHAR(10) END AS [ansi_set_options] - FROM ( - SELECT - s.sql_handle, - so.o.value('@ANSI_NULLS', 'NVARCHAR(20)') AS [ANSI_NULLS], - so.o.value('@ANSI_PADDING', 'NVARCHAR(20)') AS [ANSI_PADDING], - so.o.value('@ANSI_WARNINGS', 'NVARCHAR(20)') AS [ANSI_WARNINGS], - so.o.value('@ARITHABORT', 'NVARCHAR(20)') AS [ARITHABORT], - so.o.value('@CONCAT_NULL_YIELDS_NULL', 'NVARCHAR(20)') AS [CONCAT_NULL_YIELDS_NULL], - so.o.value('@NUMERIC_ROUNDABORT', 'NVARCHAR(20)') AS [NUMERIC_ROUNDABORT], - so.o.value('@QUOTED_IDENTIFIER', 'NVARCHAR(20)') AS [QUOTED_IDENTIFIER] - FROM #statements AS s - CROSS APPLY s.statement.nodes('//p:StatementSetOptions') AS so(o) - ) AS x - ) AS set_options ON set_options.sql_handle = s.sql_handle - OPTION(RECOMPILE); + OPEN BackupFiles; + END; - RAISERROR(N'Updating conversion XML', 0, 1) WITH NOWAIT; - WITH precheck AS ( - SELECT spi.sql_handle, - spi.proc_name, - (SELECT CASE WHEN spi.proc_name <> 'Statement' - THEN N'The stored procedure ' + spi.proc_name - ELSE N'This ad hoc statement' - END - + N' had the following implicit conversions: ' - + CHAR(10) - + STUFF(( - SELECT DISTINCT - @cr + @lf - + CASE WHEN spi2.variable_name <> N'**no_variable**' - THEN N'The variable ' - WHEN spi2.variable_name = N'**no_variable**' AND (spi2.column_name = spi2.converted_column_name OR spi2.column_name LIKE '%CONVERT_IMPLICIT%') - THEN N'The compiled value ' - WHEN spi2.column_name LIKE '%Expr%' - THEN 'The expression ' - ELSE N'The column ' - END - + CASE WHEN spi2.variable_name <> N'**no_variable**' - THEN spi2.variable_name - WHEN spi2.variable_name = N'**no_variable**' AND (spi2.column_name = spi2.converted_column_name OR spi2.column_name LIKE '%CONVERT_IMPLICIT%') - THEN spi2.compile_time_value +IF (@StandbyMode = 1) + BEGIN + IF (@StandbyUndoPath IS NULL) + BEGIN + IF @Execute = 'Y' OR @Debug = 1 RAISERROR('The file path of the undo file for standby mode was not specified. Logs will not be restored in standby mode.', 0, 1) WITH NOWAIT; + END; + ELSE + SET @LogRecoveryOption = N'STANDBY = ''' + @StandbyUndoPath + @Database + 'Undo.ldf'''; + END; + +IF (@LogRecoveryOption = N'') + BEGIN + SET @LogRecoveryOption = N'NORECOVERY'; + END; + + -- Group Ordering based on Backup File Name excluding Index {#} to construct coma separated string in "Restore Log" Command +SELECT BackupPath,BackupFile,DENSE_RANK() OVER (ORDER BY REPLACE( RIGHT( REPLACE( BackupFile, RIGHT( BackupFile, PATINDEX( '%_[0-9][0-9]%', REVERSE( BackupFile ) ) ), '' ), 16 ), '_', '' )) AS DenseRank INTO #SplitLogBackups +FROM @FileList +WHERE BackupFile IS NOT NULL; + +-- Loop through all the files for the database + WHILE 1 = 1 + BEGIN + + -- Get the TOP record to use in "Restore HeaderOnly/FileListOnly" statement + SELECT TOP 1 @CurrentBackupPathLog = BackupPath, @BackupFile = BackupFile FROM #SplitLogBackups WHERE DenseRank = @LogRestoreRanking; + IF @@rowcount = 0 BREAK; + + IF @i = 1 + + BEGIN + SET @sql = REPLACE(@HeadersSQL, N'{Path}', @CurrentBackupPathLog + @BackupFile); + + IF @Debug = 1 + BEGIN + IF @sql IS NULL PRINT '@sql is NULL for REPLACE: @HeadersSQL, @CurrentBackupPathLog, @BackupFile'; + PRINT @sql; + END; + + EXECUTE (@sql); + + SELECT TOP 1 @LogFirstLSN = CAST(FirstLSN AS NUMERIC(25, 0)), + @LogLastLSN = CAST(LastLSN AS NUMERIC(25, 0)) + FROM #Headers + WHERE BackupType = 2; - ELSE spi2.column_name - END - + N' has a data type of ' - + CASE WHEN spi2.variable_datatype = N'**no_variable**' THEN spi2.converted_to - ELSE spi2.variable_datatype - END - + N' which caused implicit conversion on the column ' - + CASE WHEN spi2.column_name LIKE N'%CONVERT_IMPLICIT%' - THEN spi2.converted_column_name - WHEN spi2.column_name = N'**no_column**' - THEN spi2.converted_column_name - WHEN spi2.converted_column_name = N'**no_column**' - THEN spi2.column_name - WHEN spi2.column_name <> spi2.converted_column_name - THEN spi2.converted_column_name - ELSE spi2.column_name - END - + CASE WHEN spi2.variable_name = N'**no_variable**' AND (spi2.column_name = spi2.converted_column_name OR spi2.column_name LIKE '%CONVERT_IMPLICIT%') - THEN N'' - WHEN spi2.column_name LIKE '%Expr%' - THEN N'' - WHEN spi2.compile_time_value NOT IN ('**declared in proc**', '**idk_man**') - AND spi2.compile_time_value <> spi2.column_name - THEN ' with the value ' + RTRIM(spi2.compile_time_value) - ELSE N'' - END - + '.' - FROM #stored_proc_info AS spi2 - WHERE spi.sql_handle = spi2.sql_handle - FOR XML PATH(N''), TYPE).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 1, N'') - AS [processing-instruction(ClickMe)] FOR XML PATH(''), TYPE ) - AS implicit_conversion_info - FROM #stored_proc_info AS spi - GROUP BY spi.sql_handle, spi.proc_name - ) - UPDATE b - SET b.implicit_conversion_info = pk.implicit_conversion_info - FROM #working_warnings AS b - JOIN precheck AS pk - ON pk.sql_handle = b.sql_handle - OPTION (RECOMPILE); + IF (@ContinueLogs = 0 AND @LogFirstLSN <= @FullLastLSN AND @FullLastLSN <= @LogLastLSN AND @RestoreDiff = 0) OR (@ContinueLogs = 1 AND @LogFirstLSN <= @DatabaseLastLSN AND @DatabaseLastLSN < @LogLastLSN AND @RestoreDiff = 0) + SET @i = 2; + + IF (@ContinueLogs = 0 AND @LogFirstLSN <= @DiffLastLSN AND @DiffLastLSN <= @LogLastLSN AND @RestoreDiff = 1) OR (@ContinueLogs = 1 AND @LogFirstLSN <= @DatabaseLastLSN AND @DatabaseLastLSN < @LogLastLSN AND @RestoreDiff = 1) + SET @i = 2; + + DELETE FROM #Headers WHERE BackupType = 2; - RAISERROR(N'Updating cached parameter XML for procs', 0, 1) WITH NOWAIT; - WITH precheck AS ( - SELECT spi.sql_handle, - spi.proc_name, - (SELECT set_options - + @cr + @lf - + @cr + @lf - + N'EXEC ' - + spi.proc_name - + N' ' - + STUFF(( - SELECT DISTINCT N', ' - + CASE WHEN spi2.variable_name <> N'**no_variable**' AND spi2.compile_time_value <> N'**idk_man**' - THEN spi2.variable_name + N' = ' - ELSE @cr + @lf + N' We could not find any cached parameter values for this stored proc. ' - END - + CASE WHEN spi2.variable_name = N'**no_variable**' OR spi2.compile_time_value = N'**idk_man**' - THEN @cr + @lf + N' Possible reasons include declared variables inside the procedure, recompile hints, etc. ' - WHEN spi2.compile_time_value = N'NULL' - THEN spi2.compile_time_value - ELSE RTRIM(spi2.compile_time_value) - END - FROM #stored_proc_info AS spi2 - WHERE spi.sql_handle = spi2.sql_handle - AND spi2.proc_name <> N'Statement' - FOR XML PATH(N''), TYPE).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 1, N'') - AS [processing-instruction(ClickMe)] FOR XML PATH(''), TYPE ) - AS cached_execution_parameters - FROM #stored_proc_info AS spi - GROUP BY spi.sql_handle, spi.proc_name, set_options - ) - UPDATE b - SET b.cached_execution_parameters = pk.cached_execution_parameters - FROM #working_warnings AS b - JOIN precheck AS pk - ON pk.sql_handle = b.sql_handle - WHERE b.proc_or_function_name <> N'Statement' - OPTION (RECOMPILE); + END; - RAISERROR(N'Updating cached parameter XML for statements', 0, 1) WITH NOWAIT; - WITH precheck AS ( - SELECT spi.sql_handle, - spi.proc_name, - (SELECT - set_options - + @cr + @lf - + @cr + @lf - + N' See QueryText column for full query text' - + @cr + @lf - + @cr + @lf - + STUFF(( - SELECT DISTINCT N', ' - + CASE WHEN spi2.variable_name <> N'**no_variable**' AND spi2.compile_time_value <> N'**idk_man**' - THEN spi2.variable_name + N' = ' - ELSE + @cr + @lf + N' We could not find any cached parameter values for this stored proc. ' - END - + CASE WHEN spi2.variable_name = N'**no_variable**' OR spi2.compile_time_value = N'**idk_man**' - THEN + @cr + @lf + N' Possible reasons include declared variables inside the procedure, recompile hints, etc. ' - WHEN spi2.compile_time_value = N'NULL' - THEN spi2.compile_time_value - ELSE RTRIM(spi2.compile_time_value) - END - FROM #stored_proc_info AS spi2 - WHERE spi.sql_handle = spi2.sql_handle - AND spi2.proc_name = N'Statement' - AND spi2.variable_name NOT LIKE N'%msparam%' - FOR XML PATH(N''), TYPE).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 1, N'') - AS [processing-instruction(ClickMe)] FOR XML PATH(''), TYPE ) - AS cached_execution_parameters - FROM #stored_proc_info AS spi - GROUP BY spi.sql_handle, spi.proc_name, spi.set_options - ) - UPDATE b - SET b.cached_execution_parameters = pk.cached_execution_parameters - FROM #working_warnings AS b - JOIN precheck AS pk - ON pk.sql_handle = b.sql_handle - WHERE b.proc_or_function_name = N'Statement' - OPTION (RECOMPILE); + IF @i = 1 + BEGIN + IF @Debug = 1 RAISERROR('No Log to Restore', 0, 1) WITH NOWAIT; + END + IF @i = 2 + BEGIN + IF @Execute = 'Y' RAISERROR('@i set to 2, restoring logs', 0, 1) WITH NOWAIT; -RAISERROR(N'Filling in implicit conversion info', 0, 1) WITH NOWAIT; -UPDATE b -SET b.implicit_conversion_info = CASE WHEN b.implicit_conversion_info IS NULL - OR CONVERT(NVARCHAR(MAX), b.implicit_conversion_info) = N'' - THEN N'' - ELSE b.implicit_conversion_info - END, - b.cached_execution_parameters = CASE WHEN b.cached_execution_parameters IS NULL - OR CONVERT(NVARCHAR(MAX), b.cached_execution_parameters) = N'' - THEN N'' - ELSE b.cached_execution_parameters - END -FROM #working_warnings AS b -OPTION (RECOMPILE); + IF (SELECT COUNT( * ) FROM #SplitLogBackups WHERE DenseRank = @LogRestoreRanking) > 1 + BEGIN + RAISERROR ('Split backups found', 0, 1) WITH NOWAIT; + SET @sql = N'RESTORE LOG ' + @RestoreDatabaseName + N' FROM ' + + STUFF( + (SELECT CHAR( 10 ) + ',DISK=''' + BackupPath + BackupFile + '''' + FROM #SplitLogBackups + WHERE DenseRank = @LogRestoreRanking + ORDER BY BackupFile + FOR XML PATH ('')), + 1, + 2, + '' ) + N' WITH ' + @LogRecoveryOption + NCHAR(13) + NCHAR(10); + END; + ELSE + SET @sql = N'RESTORE LOG ' + @RestoreDatabaseName + N' FROM DISK = ''' + @CurrentBackupPathLog + @BackupFile + N''' WITH ' + @LogRecoveryOption + NCHAR(13) + NCHAR(10); + + IF @Debug = 1 OR @Execute = 'N' + BEGIN + IF @sql IS NULL PRINT '@sql is NULL for RESTORE LOG: @RestoreDatabaseName, @CurrentBackupPathLog, @BackupFile'; + PRINT @sql; + END; + + IF @Debug IN (0, 1) AND @Execute = 'Y' + EXECUTE @sql = [dbo].[CommandExecute] @DatabaseContext=N'master', @Command = @sql, @CommandType = 'RESTORE LOG', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y'; + END; + + SET @LogRestoreRanking += 1; + END; -/*End implicit conversion and parameter info*/ -/*Begin Missing Index*/ -IF EXISTS ( SELECT 1/0 - FROM #working_warnings AS ww - WHERE ww.missing_index_count > 0 - OR ww.index_spool_cost > 0 - OR ww.index_spool_rows > 0 ) - - BEGIN - - RAISERROR(N'Inserting to #missing_index_xml', 0, 1) WITH NOWAIT; - WITH XMLNAMESPACES ( 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) - INSERT #missing_index_xml - SELECT qp.query_hash, - qp.sql_handle, - c.mg.value('@Impact', 'FLOAT') AS Impact, - c.mg.query('.') AS cmg - FROM #query_plan AS qp - CROSS APPLY qp.query_plan.nodes('//p:MissingIndexes/p:MissingIndexGroup') AS c(mg) - WHERE qp.query_hash IS NOT NULL - AND c.mg.value('@Impact', 'FLOAT') > 70.0 - OPTION (RECOMPILE); + IF @Debug = 1 + BEGIN + SELECT '#SplitLogBackups' AS table_name, BackupPath, BackupFile FROM #SplitLogBackups; + END +END - RAISERROR(N'Inserting to #missing_index_schema', 0, 1) WITH NOWAIT; - WITH XMLNAMESPACES ( 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) - INSERT #missing_index_schema - SELECT mix.query_hash, mix.sql_handle, mix.impact, - c.mi.value('@Database', 'NVARCHAR(128)'), - c.mi.value('@Schema', 'NVARCHAR(128)'), - c.mi.value('@Table', 'NVARCHAR(128)'), - c.mi.query('.') - FROM #missing_index_xml AS mix - CROSS APPLY mix.index_xml.nodes('//p:MissingIndex') AS c(mi) - OPTION (RECOMPILE); - - RAISERROR(N'Inserting to #missing_index_usage', 0, 1) WITH NOWAIT; - WITH XMLNAMESPACES ( 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) - INSERT #missing_index_usage - SELECT ms.query_hash, ms.sql_handle, ms.impact, ms.database_name, ms.schema_name, ms.table_name, - c.cg.value('@Usage', 'NVARCHAR(128)'), - c.cg.query('.') - FROM #missing_index_schema ms - CROSS APPLY ms.index_xml.nodes('//p:ColumnGroup') AS c(cg) - OPTION (RECOMPILE); - - RAISERROR(N'Inserting to #missing_index_detail', 0, 1) WITH NOWAIT; - WITH XMLNAMESPACES ( 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) - INSERT #missing_index_detail - SELECT miu.query_hash, - miu.sql_handle, - miu.impact, - miu.database_name, - miu.schema_name, - miu.table_name, - miu.usage, - c.c.value('@Name', 'NVARCHAR(128)') - FROM #missing_index_usage AS miu - CROSS APPLY miu.index_xml.nodes('//p:Column') AS c(c) - OPTION (RECOMPILE); - - RAISERROR(N'Inserting to #missing_index_pretty', 0, 1) WITH NOWAIT; - INSERT #missing_index_pretty - SELECT DISTINCT m.query_hash, m.sql_handle, m.impact, m.database_name, m.schema_name, m.table_name - , STUFF(( SELECT DISTINCT N', ' + ISNULL(m2.column_name, '') AS column_name - FROM #missing_index_detail AS m2 - WHERE m2.usage = 'EQUALITY' - AND m.query_hash = m2.query_hash - AND m.sql_handle = m2.sql_handle - AND m.impact = m2.impact - AND m.database_name = m2.database_name - AND m.schema_name = m2.schema_name - AND m.table_name = m2.table_name - FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 2, N'') AS equality - , STUFF(( SELECT DISTINCT N', ' + ISNULL(m2.column_name, '') AS column_name - FROM #missing_index_detail AS m2 - WHERE m2.usage = 'INEQUALITY' - AND m.query_hash = m2.query_hash - AND m.sql_handle = m2.sql_handle - AND m.impact = m2.impact - AND m.database_name = m2.database_name - AND m.schema_name = m2.schema_name - AND m.table_name = m2.table_name - FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 2, N'') AS inequality - , STUFF(( SELECT DISTINCT N', ' + ISNULL(m2.column_name, '') AS column_name - FROM #missing_index_detail AS m2 - WHERE m2.usage = 'INCLUDE' - AND m.query_hash = m2.query_hash - AND m.sql_handle = m2.sql_handle - AND m.impact = m2.impact - AND m.database_name = m2.database_name - AND m.schema_name = m2.schema_name - AND m.table_name = m2.table_name - FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 2, N'') AS [include], - 0 AS is_spool - FROM #missing_index_detail AS m - GROUP BY m.query_hash, m.sql_handle, m.impact, m.database_name, m.schema_name, m.table_name - OPTION (RECOMPILE); +-- Put database in a useable state +IF @RunRecovery = 1 + BEGIN + SET @sql = N'RESTORE DATABASE ' + @RestoreDatabaseName + N' WITH RECOVERY' + NCHAR(13); - RAISERROR(N'Inserting to #index_spool_ugly', 0, 1) WITH NOWAIT; - WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) - INSERT #index_spool_ugly - (query_hash, sql_handle, impact, database_name, schema_name, table_name, equality, inequality, include) - SELECT r.query_hash, - r.sql_handle, - (c.n.value('@EstimateIO', 'FLOAT') + (c.n.value('@EstimateCPU', 'FLOAT'))) - / ( 1 * NULLIF(ww.query_cost, 0)) * 100 AS impact, - o.n.value('@Database', 'NVARCHAR(128)') AS output_database, - o.n.value('@Schema', 'NVARCHAR(128)') AS output_schema, - o.n.value('@Table', 'NVARCHAR(128)') AS output_table, - k.n.value('@Column', 'NVARCHAR(128)') AS range_column, - e.n.value('@Column', 'NVARCHAR(128)') AS expression_column, - o.n.value('@Column', 'NVARCHAR(128)') AS output_column - FROM #relop AS r - JOIN #working_warnings AS ww - ON ww.query_hash = r.query_hash - CROSS APPLY r.relop.nodes('/p:RelOp') AS c(n) - CROSS APPLY r.relop.nodes('/p:RelOp/p:OutputList/p:ColumnReference') AS o(n) - OUTER APPLY r.relop.nodes('/p:RelOp/p:Spool/p:SeekPredicateNew/p:SeekKeys/p:Prefix/p:RangeColumns/p:ColumnReference') AS k(n) - OUTER APPLY r.relop.nodes('/p:RelOp/p:Spool/p:SeekPredicateNew/p:SeekKeys/p:Prefix/p:RangeExpressions/p:ColumnReference') AS e(n) - WHERE r.relop.exist('/p:RelOp[@PhysicalOp="Index Spool" and @LogicalOp="Eager Spool"]') = 1 - - RAISERROR(N'Inserting to spools to #missing_index_pretty', 0, 1) WITH NOWAIT; - INSERT #missing_index_pretty - (query_hash, sql_handle, impact, database_name, schema_name, table_name, equality, inequality, include, is_spool) - SELECT DISTINCT - isu.query_hash, - isu.sql_handle, - isu.impact, - isu.database_name, - isu.schema_name, - isu.table_name - , STUFF(( SELECT DISTINCT N', ' + ISNULL(isu2.equality, '') AS column_name - FROM #index_spool_ugly AS isu2 - WHERE isu2.equality IS NOT NULL - AND isu.query_hash = isu2.query_hash - AND isu.sql_handle = isu2.sql_handle - AND isu.impact = isu2.impact - AND isu.database_name = isu2.database_name - AND isu.schema_name = isu2.schema_name - AND isu.table_name = isu2.table_name - FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 2, N'') AS equality - , STUFF(( SELECT DISTINCT N', ' + ISNULL(isu2.inequality, '') AS column_name - FROM #index_spool_ugly AS isu2 - WHERE isu2.inequality IS NOT NULL - AND isu.query_hash = isu2.query_hash - AND isu.sql_handle = isu2.sql_handle - AND isu.impact = isu2.impact - AND isu.database_name = isu2.database_name - AND isu.schema_name = isu2.schema_name - AND isu.table_name = isu2.table_name - FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 2, N'') AS inequality - , STUFF(( SELECT DISTINCT N', ' + ISNULL(isu2.include, '') AS column_name - FROM #index_spool_ugly AS isu2 - WHERE isu2.include IS NOT NULL - AND isu.query_hash = isu2.query_hash - AND isu.sql_handle = isu2.sql_handle - AND isu.impact = isu2.impact - AND isu.database_name = isu2.database_name - AND isu.schema_name = isu2.schema_name - AND isu.table_name = isu2.table_name - FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 2, N'') AS include, - 1 AS is_spool - FROM #index_spool_ugly AS isu + IF @Debug = 1 OR @Execute = 'N' + BEGIN + IF @sql IS NULL PRINT '@sql is NULL for RESTORE DATABASE: @RestoreDatabaseName'; + PRINT @sql; + END; + IF @Debug IN (0, 1) AND @Execute = 'Y' + EXECUTE @sql = [dbo].[CommandExecute] @DatabaseContext=N'master', @Command = @sql, @CommandType = 'RECOVER DATABASE', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y'; + END; - RAISERROR(N'Updating missing index information', 0, 1) WITH NOWAIT; - WITH missing AS ( - SELECT DISTINCT - mip.query_hash, - mip.sql_handle, - N'' - AS full_details - FROM #missing_index_pretty AS mip - GROUP BY mip.query_hash, mip.sql_handle, mip.impact - ) - UPDATE ww - SET ww.missing_indexes = m.full_details - FROM #working_warnings AS ww - JOIN missing AS m - ON m.sql_handle = ww.sql_handle - OPTION (RECOMPILE); +-- Ensure simple recovery model +IF @ForceSimpleRecovery = 1 + BEGIN + SET @sql = N'ALTER DATABASE ' + @RestoreDatabaseName + N' SET RECOVERY SIMPLE' + NCHAR(13); + + IF @Debug = 1 OR @Execute = 'N' + BEGIN + IF @sql IS NULL PRINT '@sql is NULL for SET RECOVERY SIMPLE: @RestoreDatabaseName'; + PRINT @sql; + END; + + IF @Debug IN (0, 1) AND @Execute = 'Y' + EXECUTE @sql = [dbo].[CommandExecute] @DatabaseContext=N'master', @Command = @sql, @CommandType = 'SIMPLE LOGGING', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y'; + END; + + -- Run checkdb against this database +IF @RunCheckDB = 1 + BEGIN + SET @sql = N'DBCC CHECKDB (' + @RestoreDatabaseName + N') WITH NO_INFOMSGS, ALL_ERRORMSGS, DATA_PURITY;'; + + IF @Debug = 1 OR @Execute = 'N' + BEGIN + IF @sql IS NULL PRINT '@sql is NULL for Run Integrity Check: @RestoreDatabaseName'; + PRINT @sql; + END; + + IF @Debug IN (0, 1) AND @Execute = 'Y' + EXECUTE @sql = [dbo].[CommandExecute] @DatabaseContext=N'master', @Command = @sql, @CommandType = 'DBCC CHECKDB', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y'; + END; - RAISERROR(N'Filling in missing index blanks', 0, 1) WITH NOWAIT; - UPDATE ww - SET ww.missing_indexes = - CASE WHEN ww.missing_indexes IS NULL - THEN '' - ELSE ww.missing_indexes + +IF @DatabaseOwner IS NOT NULL + BEGIN + IF @RunRecovery = 1 + BEGIN + IF EXISTS (SELECT * FROM master.dbo.syslogins WHERE syslogins.loginname = @DatabaseOwner) + BEGIN + SET @sql = N'ALTER AUTHORIZATION ON DATABASE::' + @RestoreDatabaseName + ' TO [' + @DatabaseOwner + ']'; + + IF @Debug = 1 OR @Execute = 'N' + BEGIN + IF @sql IS NULL PRINT '@sql is NULL for Set Database Owner'; + PRINT @sql; + END; + + IF @Debug IN (0, 1) AND @Execute = 'Y' + EXECUTE @sql = [dbo].[CommandExecute] @DatabaseContext=N'master',@Command = @sql, @CommandType = 'ALTER AUTHORIZATION', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y'; + END + ELSE + BEGIN + PRINT @DatabaseOwner + ' is not a valid Login. Database Owner not set.'; + END END - FROM #working_warnings AS ww - OPTION (RECOMPILE); + ELSE + BEGIN + PRINT @RestoreDatabaseName + ' is still in Recovery, so we are unable to change the database owner to [' + @DatabaseOwner + '].'; + END + END; -END -/*End Missing Index*/ + -- If test restore then blow the database away (be careful) +IF @TestRestore = 1 + BEGIN + SET @sql = N'DROP DATABASE ' + @RestoreDatabaseName + NCHAR(13); + + IF @Debug = 1 OR @Execute = 'N' + BEGIN + IF @sql IS NULL PRINT '@sql is NULL for DROP DATABASE: @RestoreDatabaseName'; + PRINT @sql; + END; + + IF @Debug IN (0, 1) AND @Execute = 'Y' + EXECUTE @sql = [dbo].[CommandExecute] @DatabaseContext=N'master',@Command = @sql, @CommandType = 'DROP DATABASE', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y'; -RAISERROR(N'General query dispositions: frequent executions, long running, etc.', 0, 1) WITH NOWAIT; + END; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET b.frequent_execution = CASE WHEN wm.xpm > @execution_threshold THEN 1 END , - b.near_parallel = CASE WHEN b.query_cost BETWEEN @ctp * (1 - (@ctp_threshold_pct / 100.0)) AND @ctp THEN 1 END, - b.long_running = CASE WHEN wm.avg_duration > @long_running_query_warning_seconds THEN 1 - WHEN wm.max_duration > @long_running_query_warning_seconds THEN 1 - WHEN wm.avg_cpu_time > @long_running_query_warning_seconds THEN 1 - WHEN wm.max_cpu_time > @long_running_query_warning_seconds THEN 1 END, - b.is_key_lookup_expensive = CASE WHEN b.query_cost >= (@ctp / 2) AND b.key_lookup_cost >= b.query_cost * .5 THEN 1 END, - b.is_sort_expensive = CASE WHEN b.query_cost >= (@ctp / 2) AND b.sort_cost >= b.query_cost * .5 THEN 1 END, - b.is_remote_query_expensive = CASE WHEN b.remote_query_cost >= b.query_cost * .05 THEN 1 END, - b.is_unused_grant = CASE WHEN percent_memory_grant_used <= @memory_grant_warning_percent AND min_query_max_used_memory > @min_memory_per_query THEN 1 END, - b.long_running_low_cpu = CASE WHEN wm.avg_duration > wm.avg_cpu_time * 4 AND avg_cpu_time < 500. THEN 1 END, - b.low_cost_high_cpu = CASE WHEN b.query_cost < 10 AND wm.avg_cpu_time > 5000. THEN 1 END, - b.is_spool_expensive = CASE WHEN b.query_cost > (@ctp / 2) AND b.index_spool_cost >= b.query_cost * .1 THEN 1 END, - b.is_spool_more_rows = CASE WHEN b.index_spool_rows >= wm.min_rowcount THEN 1 END, - b.is_bad_estimate = CASE WHEN wm.avg_rowcount > 0 AND (b.estimated_rows * 1000 < wm.avg_rowcount OR b.estimated_rows > wm.avg_rowcount * 1000) THEN 1 END, - b.is_big_log = CASE WHEN wm.avg_log_bytes_used >= (@log_size_mb / 2.) THEN 1 END, - b.is_big_tempdb = CASE WHEN wm.avg_tempdb_space_used >= (@avg_tempdb_data_file / 2.) THEN 1 END -FROM #working_warnings AS b -JOIN #working_metrics AS wm -ON b.plan_id = wm.plan_id -AND b.query_id = wm.query_id -JOIN #working_plan_text AS wpt -ON b.plan_id = wpt.plan_id -AND b.query_id = wpt.query_id -OPTION (RECOMPILE); +-- Clean-Up Tempdb Objects +IF OBJECT_ID( 'tempdb..#SplitFullBackups' ) IS NOT NULL DROP TABLE #SplitFullBackups; +IF OBJECT_ID( 'tempdb..#SplitDiffBackups' ) IS NOT NULL DROP TABLE #SplitDiffBackups; +IF OBJECT_ID( 'tempdb..#SplitLogBackups' ) IS NOT NULL DROP TABLE #SplitLogBackups; +GO +IF OBJECT_ID('dbo.sp_ineachdb') IS NULL + EXEC ('CREATE PROCEDURE dbo.sp_ineachdb AS RETURN 0') +GO +ALTER PROCEDURE [dbo].[sp_ineachdb] + -- mssqltips.com/sqlservertip/5694/execute-a-command-in-the-context-of-each-database-in-sql-server--part-2/ + @command nvarchar(max) = NULL, + @replace_character nchar(1) = N'?', + @print_dbname bit = 0, + @select_dbname bit = 0, + @print_command bit = 0, + @print_command_only bit = 0, + @suppress_quotename bit = 0, -- use with caution + @system_only bit = 0, + @user_only bit = 0, + @name_pattern nvarchar(300) = N'%', + @database_list nvarchar(max) = NULL, + @exclude_pattern nvarchar(300) = NULL, + @exclude_list nvarchar(max) = NULL, + @recovery_model_desc nvarchar(120) = NULL, + @compatibility_level tinyint = NULL, + @state_desc nvarchar(120) = N'ONLINE', + @is_read_only bit = 0, + @is_auto_close_on bit = NULL, + @is_auto_shrink_on bit = NULL, + @is_broker_enabled bit = NULL, + @user_access nvarchar(128) = NULL, + @Help BIT = 0, + @Version VARCHAR(30) = NULL OUTPUT, + @VersionDate DATETIME = NULL OUTPUT, + @VersionCheckMode BIT = 0 +-- WITH EXECUTE AS OWNER – maybe not a great idea, depending on the security of your system +AS +BEGIN + SET NOCOUNT ON; -RAISERROR('Populating Warnings column', 0, 1) WITH NOWAIT; -/* Populate warnings */ -UPDATE b -SET b.warnings = SUBSTRING( - CASE WHEN b.warning_no_join_predicate = 1 THEN ', No Join Predicate' ELSE '' END + - CASE WHEN b.compile_timeout = 1 THEN ', Compilation Timeout' ELSE '' END + - CASE WHEN b.compile_memory_limit_exceeded = 1 THEN ', Compile Memory Limit Exceeded' ELSE '' END + - CASE WHEN b.is_forced_plan = 1 THEN ', Forced Plan' ELSE '' END + - CASE WHEN b.is_forced_parameterized = 1 THEN ', Forced Parameterization' ELSE '' END + - CASE WHEN b.unparameterized_query = 1 THEN ', Unparameterized Query' ELSE '' END + - CASE WHEN b.missing_index_count > 0 THEN ', Missing Indexes (' + CAST(b.missing_index_count AS NVARCHAR(3)) + ')' ELSE '' END + - CASE WHEN b.unmatched_index_count > 0 THEN ', Unmatched Indexes (' + CAST(b.unmatched_index_count AS NVARCHAR(3)) + ')' ELSE '' END + - CASE WHEN b.is_cursor = 1 THEN ', Cursor' - + CASE WHEN b.is_optimistic_cursor = 1 THEN '; optimistic' ELSE '' END - + CASE WHEN b.is_forward_only_cursor = 0 THEN '; not forward only' ELSE '' END - + CASE WHEN b.is_cursor_dynamic = 1 THEN '; dynamic' ELSE '' END - + CASE WHEN b.is_fast_forward_cursor = 1 THEN '; fast forward' ELSE '' END - ELSE '' END + - CASE WHEN b.is_parallel = 1 THEN ', Parallel' ELSE '' END + - CASE WHEN b.near_parallel = 1 THEN ', Nearly Parallel' ELSE '' END + - CASE WHEN b.frequent_execution = 1 THEN ', Frequent Execution' ELSE '' END + - CASE WHEN b.plan_warnings = 1 THEN ', Plan Warnings' ELSE '' END + - CASE WHEN b.parameter_sniffing = 1 THEN ', Parameter Sniffing' ELSE '' END + - CASE WHEN b.long_running = 1 THEN ', Long Running Query' ELSE '' END + - CASE WHEN b.downlevel_estimator = 1 THEN ', Downlevel CE' ELSE '' END + - CASE WHEN b.implicit_conversions = 1 THEN ', Implicit Conversions' ELSE '' END + - CASE WHEN b.plan_multiple_plans = 1 THEN ', Multiple Plans' ELSE '' END + - CASE WHEN b.is_trivial = 1 THEN ', Trivial Plans' ELSE '' END + - CASE WHEN b.is_forced_serial = 1 THEN ', Forced Serialization' ELSE '' END + - CASE WHEN b.is_key_lookup_expensive = 1 THEN ', Expensive Key Lookup' ELSE '' END + - CASE WHEN b.is_remote_query_expensive = 1 THEN ', Expensive Remote Query' ELSE '' END + - CASE WHEN b.trace_flags_session IS NOT NULL THEN ', Session Level Trace Flag(s) Enabled: ' + b.trace_flags_session ELSE '' END + - CASE WHEN b.is_unused_grant = 1 THEN ', Unused Memory Grant' ELSE '' END + - CASE WHEN b.function_count > 0 THEN ', Calls ' + CONVERT(VARCHAR(10), b.function_count) + ' function(s)' ELSE '' END + - CASE WHEN b.clr_function_count > 0 THEN ', Calls ' + CONVERT(VARCHAR(10), b.clr_function_count) + ' CLR function(s)' ELSE '' END + - CASE WHEN b.is_table_variable = 1 THEN ', Table Variables' ELSE '' END + - CASE WHEN b.no_stats_warning = 1 THEN ', Columns With No Statistics' ELSE '' END + - CASE WHEN b.relop_warnings = 1 THEN ', Operator Warnings' ELSE '' END + - CASE WHEN b.is_table_scan = 1 THEN ', Table Scans' ELSE '' END + - CASE WHEN b.backwards_scan = 1 THEN ', Backwards Scans' ELSE '' END + - CASE WHEN b.forced_index = 1 THEN ', Forced Indexes' ELSE '' END + - CASE WHEN b.forced_seek = 1 THEN ', Forced Seeks' ELSE '' END + - CASE WHEN b.forced_scan = 1 THEN ', Forced Scans' ELSE '' END + - CASE WHEN b.columnstore_row_mode = 1 THEN ', ColumnStore Row Mode ' ELSE '' END + - CASE WHEN b.is_computed_scalar = 1 THEN ', Computed Column UDF ' ELSE '' END + - CASE WHEN b.is_sort_expensive = 1 THEN ', Expensive Sort' ELSE '' END + - CASE WHEN b.is_computed_filter = 1 THEN ', Filter UDF' ELSE '' END + - CASE WHEN b.index_ops >= 5 THEN ', >= 5 Indexes Modified' ELSE '' END + - CASE WHEN b.is_row_level = 1 THEN ', Row Level Security' ELSE '' END + - CASE WHEN b.is_spatial = 1 THEN ', Spatial Index' ELSE '' END + - CASE WHEN b.index_dml = 1 THEN ', Index DML' ELSE '' END + - CASE WHEN b.table_dml = 1 THEN ', Table DML' ELSE '' END + - CASE WHEN b.low_cost_high_cpu = 1 THEN ', Low Cost High CPU' ELSE '' END + - CASE WHEN b.long_running_low_cpu = 1 THEN + ', Long Running With Low CPU' ELSE '' END + - CASE WHEN b.stale_stats = 1 THEN + ', Statistics used have > 100k modifications in the last 7 days' ELSE '' END + - CASE WHEN b.is_adaptive = 1 THEN + ', Adaptive Joins' ELSE '' END + - CASE WHEN b.is_spool_expensive = 1 THEN + ', Expensive Index Spool' ELSE '' END + - CASE WHEN b.is_spool_more_rows = 1 THEN + ', Large Index Row Spool' ELSE '' END + - CASE WHEN b.is_bad_estimate = 1 THEN + ', Row estimate mismatch' ELSE '' END + - CASE WHEN b.is_big_log = 1 THEN + ', High log use' ELSE '' END + - CASE WHEN b.is_big_tempdb = 1 THEN ', High tempdb use' ELSE '' END + - CASE WHEN b.is_paul_white_electric = 1 THEN ', SWITCH!' ELSE '' END + - CASE WHEN b.is_row_goal = 1 THEN ', Row Goals' ELSE '' END + - CASE WHEN b.is_mstvf = 1 THEN ', MSTVFs' ELSE '' END + - CASE WHEN b.is_mm_join = 1 THEN ', Many to Many Merge' ELSE '' END + - CASE WHEN b.is_nonsargable = 1 THEN ', non-SARGables' ELSE '' END - , 2, 200000) -FROM #working_warnings b -OPTION (RECOMPILE); + SELECT @Version = '8.01', @VersionDate = '20210222'; + + IF(@VersionCheckMode = 1) + BEGIN + RETURN; + END; + + IF @Help = 1 + BEGIN + + PRINT ' + /* + sp_ineachdb from http://FirstResponderKit.org + + This script will execute a command against multiple databases. + + To learn more, visit http://FirstResponderKit.org where you can download new + versions for free, watch training videos on how it works, get more info on + the findings, contribute your own code, and more. + + Known limitations of this version: + - Only Microsoft-supported versions of SQL Server. Sorry, 2005 and 2000. + - Tastes awful with marmite. + + Unknown limitations of this version: + - None. (If we knew them, they would be known. Duh.) + + Changes - for the full list of improvements and fixes in this version, see: + https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/ + + MIT License + + Copyright (c) 2021 Brent Ozar Unlimited + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + + */ + '; + + RETURN -1; + END -END; -END TRY -BEGIN CATCH - RAISERROR (N'Failure generating warnings.', 0,1) WITH NOWAIT; + DECLARE @exec nvarchar(150), + @sx nvarchar(18) = N'.sys.sp_executesql', + @db sysname, + @dbq sysname, + @cmd nvarchar(max), + @thisdb sysname, + @cr char(2) = CHAR(13) + CHAR(10), + @SQLVersion AS tinyint = (@@microsoftversion / 0x1000000) & 0xff, -- Stores the SQL Server Version Number(8(2000),9(2005),10(2008 & 2008R2),11(2012),12(2014),13(2016),14(2017),15(2019) + @ServerName AS sysname = CONVERT(sysname, SERVERPROPERTY('ServerName')), -- Stores the SQL Server Instance name. + @NoSpaces nvarchar(20) = N'%[^' + CHAR(9) + CHAR(32) + CHAR(10) + CHAR(13) + N']%'; --Pattern for PATINDEX - IF @sql_select IS NOT NULL - BEGIN - SET @msg = N'Last @sql_select: ' + @sql_select; - RAISERROR(@msg, 0, 1) WITH NOWAIT; - END; - SELECT @msg = @DatabaseName + N' database failed to process. ' + ERROR_MESSAGE(), @error_severity = ERROR_SEVERITY(), @error_state = ERROR_STATE(); - RAISERROR (@msg, @error_severity, @error_state) WITH NOWAIT; - - - WHILE @@TRANCOUNT > 0 - ROLLBACK; + CREATE TABLE #ineachdb(id int, name nvarchar(512), is_distributor bit); - RETURN; -END CATCH; +/* + -- first, let's limit to only DBs the caller is interested in + IF @database_list > N'' + -- comma-separated list of potentially valid/invalid/quoted/unquoted names + BEGIN + ;WITH n(n) AS (SELECT 1 UNION ALL SELECT n+1 FROM n WHERE n <= LEN(@database_list)), + names AS + ( + SELECT name = LTRIM(RTRIM(PARSENAME(SUBSTRING(@database_list, n, + CHARINDEX(N',', @database_list + N',', n) - n), 1))) + FROM n + WHERE SUBSTRING(N',' + @database_list, n, 1) = N',' + ) + INSERT #ineachdb(id,name,is_distributor) + SELECT d.database_id, d.name, d.is_distributor + FROM sys.databases AS d + WHERE EXISTS (SELECT 1 FROM names WHERE name = d.name) + OPTION (MAXRECURSION 0); + END + ELSE + BEGIN + INSERT #ineachdb(id,name,is_distributor) SELECT database_id, name, is_distributor FROM sys.databases; + END -BEGIN TRY -BEGIN + -- now delete any that have been explicitly excluded - exclude trumps include + IF @exclude_list > N'' + -- comma-separated list of potentially valid/invalid/quoted/unquoted names + BEGIN + ;WITH n(n) AS (SELECT 1 UNION ALL SELECT n+1 FROM n WHERE n <= LEN(@exclude_list)), + names AS + ( + SELECT name = LTRIM(RTRIM(PARSENAME(SUBSTRING(@exclude_list, n, + CHARINDEX(N',', @exclude_list + N',', n) - n), 1))) + FROM n + WHERE SUBSTRING(N',' + @exclude_list, n, 1) = N',' + ) + DELETE d + FROM #ineachdb AS d + INNER JOIN names + ON names.name = d.name + OPTION (MAXRECURSION 0); + END +*/ -RAISERROR(N'Checking for parameter sniffing symptoms', 0, 1) WITH NOWAIT; +/* +@database_list and @exclude_list are are processed at the same time +1)Read the list searching for a comma or [ +2)If we find a comma, save the name +3)If we find a [, we begin to accumulate the result until we reach closing ], (jumping over escaped ]]). +4)Finally, tabs, line breaks and spaces are removed from unquoted names +*/ +WITH C +AS (SELECT V.SrcList + , CAST('' AS nvarchar(MAX)) AS Name + , V.DBList + , 0 AS InBracket + , 0 AS Quoted + FROM (VALUES ('In', @database_list + ','), ('Out', @exclude_list + ',')) AS V (SrcList, DBList) + UNION ALL + SELECT C.SrcList +-- , IIF(V.Found = '[', '', SUBSTRING(C.DBList, 1, V.Place - 1))/*remove initial [*/ + , CASE WHEN V.Found = '[' THEN '' ELSE SUBSTRING(C.DBList, 1, V.Place - 1) END /*remove initial [*/ + , STUFF(C.DBList, 1, V.Place, '') +-- , IIF(V.Found = '[', 1, 0) + ,Case WHEN V.Found = '[' THEN 1 ELSE 0 END + , 0 + FROM C + CROSS APPLY + ( VALUES (PATINDEX('%[,[]%', C.DBList), SUBSTRING(C.DBList, PATINDEX('%[,[]%', C.DBList), 1))) AS V (Place, Found) + WHERE C.DBList > '' + AND C.InBracket = 0 + UNION ALL + SELECT C.SrcList +-- , CONCAT(C.Name, SUBSTRING(C.DBList, 1, V.Place + W.DoubleBracket - 1)) /*Accumulates only one ] if escaped]] or none if end]*/ + , ISNULL(C.Name,'') + ISNULL(SUBSTRING(C.DBList, 1, V.Place + W.DoubleBracket - 1),'') /*Accumulates only one ] if escaped]] or none if end]*/ + , STUFF(C.DBList, 1, V.Place + W.DoubleBracket, '') + , W.DoubleBracket + , 1 + FROM C + CROSS APPLY (VALUES (CHARINDEX(']', C.DBList))) AS V (Place) + -- CROSS APPLY (VALUES (IIF(SUBSTRING(C.DBList, V.Place + 1, 1) = ']', 1, 0))) AS W (DoubleBracket) + CROSS APPLY (VALUES (CASE WHEN SUBSTRING(C.DBList, V.Place + 1, 1) = ']' THEN 1 ELSE 0 END)) AS W (DoubleBracket) + WHERE C.DBList > '' + AND C.InBracket = 1) + , F +AS (SELECT C.SrcList + , CASE WHEN C.Quoted = 0 THEN + SUBSTRING(C.Name, PATINDEX(@NoSpaces, Name), DATALENGTH (Name)/2 - PATINDEX(@NoSpaces, Name) - PATINDEX(@NoSpaces, REVERSE(Name))+2) + ELSE C.Name END + AS name + FROM C + WHERE C.InBracket = 0 + AND C.Name > '') +INSERT #ineachdb(id,name,is_distributor) +SELECT d.database_id + , d.name + , d.is_distributor +FROM sys.databases AS d +WHERE ( EXISTS (SELECT NULL FROM F WHERE F.name = d.name AND F.SrcList = 'In') + OR @database_list IS NULL) + AND NOT EXISTS (SELECT NULL FROM F WHERE F.name = d.name AND F.SrcList = 'Out') +OPTION (MAXRECURSION 0); +; + -- next, let's delete any that *don't* match various criteria passed in + DELETE dbs FROM #ineachdb AS dbs + WHERE (@system_only = 1 AND (id NOT IN (1,2,3,4) AND is_distributor <> 1)) + OR (@user_only = 1 AND (id IN (1,2,3,4) OR is_distributor = 1)) + OR name NOT LIKE @name_pattern + OR name LIKE @exclude_pattern + OR EXISTS + ( + SELECT 1 + FROM sys.databases AS d + WHERE d.database_id = dbs.id + AND NOT + ( + recovery_model_desc = COALESCE(@recovery_model_desc, recovery_model_desc) + AND compatibility_level = COALESCE(@compatibility_level, compatibility_level) + AND is_read_only = COALESCE(@is_read_only, is_read_only) + AND is_auto_close_on = COALESCE(@is_auto_close_on, is_auto_close_on) + AND is_auto_shrink_on = COALESCE(@is_auto_shrink_on, is_auto_shrink_on) + AND is_broker_enabled = COALESCE(@is_broker_enabled, is_broker_enabled) + ) + ); -UPDATE b -SET b.parameter_sniffing_symptoms = -CASE WHEN b.count_executions < 2 THEN 'Too few executions to compare (< 2).' - ELSE - SUBSTRING( - /*Duration*/ - CASE WHEN (b.min_duration * 100) < (b.avg_duration) THEN ', Fast sometimes' ELSE '' END + - CASE WHEN (b.max_duration) > (b.avg_duration * 100) THEN ', Slow sometimes' ELSE '' END + - CASE WHEN (b.last_duration * 100) < (b.avg_duration) THEN ', Fast last run' ELSE '' END + - CASE WHEN (b.last_duration) > (b.avg_duration * 100) THEN ', Slow last run' ELSE '' END + - /*CPU*/ - CASE WHEN (b.min_cpu_time / b.avg_dop) * 100 < (b.avg_cpu_time / b.avg_dop) THEN ', Low CPU sometimes' ELSE '' END + - CASE WHEN (b.max_cpu_time / b.max_dop) > (b.avg_cpu_time / b.avg_dop) * 100 THEN ', High CPU sometimes' ELSE '' END + - CASE WHEN (b.last_cpu_time / b.last_dop) * 100 < (b.avg_cpu_time / b.avg_dop) THEN ', Low CPU last run' ELSE '' END + - CASE WHEN (b.last_cpu_time / b.last_dop) > (b.avg_cpu_time / b.avg_dop) * 100 THEN ', High CPU last run' ELSE '' END + - /*Logical Reads*/ - CASE WHEN (b.min_logical_io_reads * 100) < (b.avg_logical_io_reads) THEN ', Low reads sometimes' ELSE '' END + - CASE WHEN (b.max_logical_io_reads) > (b.avg_logical_io_reads * 100) THEN ', High reads sometimes' ELSE '' END + - CASE WHEN (b.last_logical_io_reads * 100) < (b.avg_logical_io_reads) THEN ', Low reads last run' ELSE '' END + - CASE WHEN (b.last_logical_io_reads) > (b.avg_logical_io_reads * 100) THEN ', High reads last run' ELSE '' END + - /*Logical Writes*/ - CASE WHEN (b.min_logical_io_writes * 100) < (b.avg_logical_io_writes) THEN ', Low writes sometimes' ELSE '' END + - CASE WHEN (b.max_logical_io_writes) > (b.avg_logical_io_writes * 100) THEN ', High writes sometimes' ELSE '' END + - CASE WHEN (b.last_logical_io_writes * 100) < (b.avg_logical_io_writes) THEN ', Low writes last run' ELSE '' END + - CASE WHEN (b.last_logical_io_writes) > (b.avg_logical_io_writes * 100) THEN ', High writes last run' ELSE '' END + - /*Physical Reads*/ - CASE WHEN (b.min_physical_io_reads * 100) < (b.avg_physical_io_reads) THEN ', Low physical reads sometimes' ELSE '' END + - CASE WHEN (b.max_physical_io_reads) > (b.avg_physical_io_reads * 100) THEN ', High physical reads sometimes' ELSE '' END + - CASE WHEN (b.last_physical_io_reads * 100) < (b.avg_physical_io_reads) THEN ', Low physical reads last run' ELSE '' END + - CASE WHEN (b.last_physical_io_reads) > (b.avg_physical_io_reads * 100) THEN ', High physical reads last run' ELSE '' END + - /*Memory*/ - CASE WHEN (b.min_query_max_used_memory * 100) < (b.avg_query_max_used_memory) THEN ', Low memory sometimes' ELSE '' END + - CASE WHEN (b.max_query_max_used_memory) > (b.avg_query_max_used_memory * 100) THEN ', High memory sometimes' ELSE '' END + - CASE WHEN (b.last_query_max_used_memory * 100) < (b.avg_query_max_used_memory) THEN ', Low memory last run' ELSE '' END + - CASE WHEN (b.last_query_max_used_memory) > (b.avg_query_max_used_memory * 100) THEN ', High memory last run' ELSE '' END + - /*Duration*/ - CASE WHEN b.min_rowcount * 100 < b.avg_rowcount THEN ', Low row count sometimes' ELSE '' END + - CASE WHEN b.max_rowcount > b.avg_rowcount * 100 THEN ', High row count sometimes' ELSE '' END + - CASE WHEN b.last_rowcount * 100 < b.avg_rowcount THEN ', Low row count run' ELSE '' END + - CASE WHEN b.last_rowcount > b.avg_rowcount * 100 THEN ', High row count last run' ELSE '' END + - /*DOP*/ - CASE WHEN b.min_dop <> b.max_dop THEN ', Serial sometimes' ELSE '' END + - CASE WHEN b.min_dop <> b.max_dop AND b.last_dop = 1 THEN ', Serial last run' ELSE '' END + - CASE WHEN b.min_dop <> b.max_dop AND b.last_dop > 1 THEN ', Parallel last run' ELSE '' END + - /*tempdb*/ - CASE WHEN b.min_tempdb_space_used * 100 < b.avg_tempdb_space_used THEN ', Low tempdb sometimes' ELSE '' END + - CASE WHEN b.max_tempdb_space_used > b.avg_tempdb_space_used * 100 THEN ', High tempdb sometimes' ELSE '' END + - CASE WHEN b.last_tempdb_space_used * 100 < b.avg_tempdb_space_used THEN ', Low tempdb run' ELSE '' END + - CASE WHEN b.last_tempdb_space_used > b.avg_tempdb_space_used * 100 THEN ', High tempdb last run' ELSE '' END + - /*tlog*/ - CASE WHEN b.min_log_bytes_used * 100 < b.avg_log_bytes_used THEN ', Low log use sometimes' ELSE '' END + - CASE WHEN b.max_log_bytes_used > b.avg_log_bytes_used * 100 THEN ', High log use sometimes' ELSE '' END + - CASE WHEN b.last_log_bytes_used * 100 < b.avg_log_bytes_used THEN ', Low log use run' ELSE '' END + - CASE WHEN b.last_log_bytes_used > b.avg_log_bytes_used * 100 THEN ', High log use last run' ELSE '' END - , 2, 200000) - END -FROM #working_metrics AS b -OPTION (RECOMPILE); + -- if a user access is specified, remove any that are NOT in that state + IF @user_access IN (N'SINGLE_USER', N'MULTI_USER', N'RESTRICTED_USER') + BEGIN + DELETE #ineachdb WHERE + CONVERT(nvarchar(128), DATABASEPROPERTYEX(name, 'UserAccess')) <> @user_access; + END -END; -END TRY -BEGIN CATCH - RAISERROR (N'Failure analyzing parameter sniffing', 0,1) WITH NOWAIT; + -- finally, remove any that are not *fully* online or we can't access + DELETE dbs FROM #ineachdb AS dbs + WHERE EXISTS + ( + SELECT 1 FROM sys.databases + WHERE database_id = dbs.id + AND + ( + @state_desc = N'ONLINE' AND + ( + [state] & 992 <> 0 -- inaccessible + OR state_desc <> N'ONLINE' -- not online + OR HAS_DBACCESS(name) = 0 -- don't have access + OR DATABASEPROPERTYEX(name, 'Collation') IS NULL -- not fully online. See "status" here: + -- https://docs.microsoft.com/en-us/sql/t-sql/functions/databasepropertyex-transact-sql + ) + OR (@state_desc <> N'ONLINE' AND state_desc <> @state_desc) + ) + ); + + -- from Andy Mallon / First Responders Kit. Make sure that if we're an + -- AG secondary, we skip any database where allow connections is off + IF @SQLVersion >= 11 AND 3 = (SELECT COUNT(*) FROM sys.all_objects WHERE name IN('availability_replicas','dm_hadr_availability_group_states','dm_hadr_database_replica_states')) + BEGIN + DELETE dbs FROM #ineachdb AS dbs + WHERE EXISTS + ( + SELECT 1 FROM sys.dm_hadr_database_replica_states AS drs + INNER JOIN sys.availability_replicas AS ar + ON ar.replica_id = drs.replica_id + INNER JOIN sys.dm_hadr_availability_group_states ags + ON ags.group_id = ar.group_id + WHERE drs.database_id = dbs.id + AND ar.secondary_role_allow_connections = 0 + AND ags.primary_replica <> @ServerName + ); + END - IF @sql_select IS NOT NULL - BEGIN - SET @msg = N'Last @sql_select: ' + @sql_select; - RAISERROR(@msg, 0, 1) WITH NOWAIT; - END; + -- Well, if we deleted them all... + IF NOT EXISTS (SELECT 1 FROM #ineachdb) + BEGIN + RAISERROR(N'No databases to process.', 1, 0); + RETURN; + END - SELECT @msg = @DatabaseName + N' database failed to process. ' + ERROR_MESSAGE(), @error_severity = ERROR_SEVERITY(), @error_state = ERROR_STATE(); - RAISERROR (@msg, @error_severity, @error_state) WITH NOWAIT; - - - WHILE @@TRANCOUNT > 0 - ROLLBACK; + -- ok, now, let's go through what we have left + DECLARE dbs CURSOR LOCAL FAST_FORWARD + FOR SELECT DB_NAME(id), QUOTENAME(DB_NAME(id)) + FROM #ineachdb; - RETURN; -END CATCH; + OPEN dbs; -BEGIN TRY + FETCH NEXT FROM dbs INTO @db, @dbq; -BEGIN + DECLARE @msg1 nvarchar(512) = N'Could not run against %s : %s.', + @msg2 nvarchar(max); -IF (@Failed = 0 AND @ExportToExcel = 0 AND @SkipXML = 0) -BEGIN + WHILE @@FETCH_STATUS <> -1 + BEGIN + SET @thisdb = CASE WHEN @suppress_quotename = 1 THEN @db ELSE @dbq END; + SET @cmd = REPLACE(@command, @replace_character, REPLACE(@thisdb,'''','''''')); -RAISERROR(N'Returning regular results', 0, 1) WITH NOWAIT; + BEGIN TRY + IF @print_dbname = 1 + BEGIN + PRINT N'/* ' + @thisdb + N' */'; + END -WITH x AS ( -SELECT wpt.database_name, ww.query_cost, wm.plan_id, wm.query_id, wm.query_id_all_plan_ids, wpt.query_sql_text, wm.proc_or_function_name, wpt.query_plan_xml, ww.warnings, wpt.pattern, - wm.parameter_sniffing_symptoms, wpt.top_three_waits, ww.missing_indexes, ww.implicit_conversion_info, ww.cached_execution_parameters, wm.count_executions, wm.count_compiles, wm.total_cpu_time, wm.avg_cpu_time, - wm.total_duration, wm.avg_duration, wm.total_logical_io_reads, wm.avg_logical_io_reads, - wm.total_physical_io_reads, wm.avg_physical_io_reads, wm.total_logical_io_writes, wm.avg_logical_io_writes, wm.total_rowcount, wm.avg_rowcount, - wm.total_query_max_used_memory, wm.avg_query_max_used_memory, wm.total_tempdb_space_used, wm.avg_tempdb_space_used, - wm.total_log_bytes_used, wm.avg_log_bytes_used, wm.total_num_physical_io_reads, wm.avg_num_physical_io_reads, - wm.first_execution_time, wm.last_execution_time, wpt.last_force_failure_reason_desc, wpt.context_settings, ROW_NUMBER() OVER (PARTITION BY wm.plan_id, wm.query_id, wm.last_execution_time ORDER BY wm.plan_id) AS rn -FROM #working_plan_text AS wpt -JOIN #working_warnings AS ww - ON wpt.plan_id = ww.plan_id - AND wpt.query_id = ww.query_id -JOIN #working_metrics AS wm - ON wpt.plan_id = wm.plan_id - AND wpt.query_id = wm.query_id -) -SELECT * -FROM x -WHERE x.rn = 1 -ORDER BY x.last_execution_time -OPTION (RECOMPILE); + IF @select_dbname = 1 + BEGIN + SELECT [ineachdb current database] = @thisdb; + END -END; + IF 1 IN (@print_command, @print_command_only) + BEGIN + PRINT N'/* For ' + @thisdb + ': */' + @cr + @cr + @cmd + @cr + @cr; + END -IF (@Failed = 1 AND @ExportToExcel = 0 AND @SkipXML = 0) -BEGIN + IF COALESCE(@print_command_only,0) = 0 + BEGIN + SET @exec = @dbq + @sx; + EXEC @exec @cmd; + END + END TRY -RAISERROR(N'Returning results for failed queries', 0, 1) WITH NOWAIT; + BEGIN CATCH + SET @msg2 = ERROR_MESSAGE(); + RAISERROR(@msg1, 1, 0, @db, @msg2); + END CATCH -WITH x AS ( -SELECT wpt.database_name, ww.query_cost, wm.plan_id, wm.query_id, wm.query_id_all_plan_ids, wpt.query_sql_text, wm.proc_or_function_name, wpt.query_plan_xml, ww.warnings, wpt.pattern, - wm.parameter_sniffing_symptoms, wpt.last_force_failure_reason_desc, wpt.top_three_waits, ww.missing_indexes, ww.implicit_conversion_info, ww.cached_execution_parameters, - wm.count_executions, wm.count_compiles, wm.total_cpu_time, wm.avg_cpu_time, - wm.total_duration, wm.avg_duration, wm.total_logical_io_reads, wm.avg_logical_io_reads, - wm.total_physical_io_reads, wm.avg_physical_io_reads, wm.total_logical_io_writes, wm.avg_logical_io_writes, wm.total_rowcount, wm.avg_rowcount, - wm.total_query_max_used_memory, wm.avg_query_max_used_memory, wm.total_tempdb_space_used, wm.avg_tempdb_space_used, - wm.total_log_bytes_used, wm.avg_log_bytes_used, wm.total_num_physical_io_reads, wm.avg_num_physical_io_reads, - wm.first_execution_time, wm.last_execution_time, wpt.context_settings, ROW_NUMBER() OVER (PARTITION BY wm.plan_id, wm.query_id, wm.last_execution_time ORDER BY wm.plan_id) AS rn -FROM #working_plan_text AS wpt -JOIN #working_warnings AS ww - ON wpt.plan_id = ww.plan_id - AND wpt.query_id = ww.query_id -JOIN #working_metrics AS wm - ON wpt.plan_id = wm.plan_id - AND wpt.query_id = wm.query_id -) -SELECT * -FROM x -WHERE x.rn = 1 -ORDER BY x.last_execution_time -OPTION (RECOMPILE); + FETCH NEXT FROM dbs INTO @db, @dbq; + END -END; + CLOSE dbs; + DEALLOCATE dbs; +END +GO -IF (@ExportToExcel = 1 AND @SkipXML = 0) +IF (OBJECT_ID('dbo.SqlServerVersions') IS NULL) BEGIN -RAISERROR(N'Returning results for Excel export', 0, 1) WITH NOWAIT; + CREATE TABLE dbo.SqlServerVersions + ( + MajorVersionNumber tinyint not null, + MinorVersionNumber smallint not null, + Branch varchar(34) not null, + [Url] varchar(99) not null, + ReleaseDate date not null, + MainstreamSupportEndDate date not null, + ExtendedSupportEndDate date not null, + MajorVersionName varchar(19) not null, + MinorVersionName varchar(67) not null, -UPDATE #working_plan_text -SET query_sql_text = SUBSTRING(REPLACE(REPLACE(REPLACE(LTRIM(RTRIM(query_sql_text)),' ','<>'),'><',''),'<>',' '), 1, 31000) -OPTION (RECOMPILE); + CONSTRAINT PK_SqlServerVersions PRIMARY KEY CLUSTERED + ( + MajorVersionNumber ASC, + MinorVersionNumber ASC, + ReleaseDate ASC + ) + ); + + EXEC sys.sp_addextendedproperty @name=N'Description', @value=N'The major version number.' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'SqlServerVersions', @level2type=N'COLUMN',@level2name=N'MajorVersionNumber' + EXEC sys.sp_addextendedproperty @name=N'Description', @value=N'The minor version number.' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'SqlServerVersions', @level2type=N'COLUMN',@level2name=N'MinorVersionNumber' + EXEC sys.sp_addextendedproperty @name=N'Description', @value=N'The update level of the build. CU indicates a cumulative update. SP indicates a service pack. RTM indicates Release To Manufacturer. GDR indicates a General Distribution Release. QFE indicates Quick Fix Engineering (aka hotfix).' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'SqlServerVersions', @level2type=N'COLUMN',@level2name=N'Branch' + EXEC sys.sp_addextendedproperty @name=N'Description', @value=N'A link to the KB article for a version.' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'SqlServerVersions', @level2type=N'COLUMN',@level2name=N'Url' + EXEC sys.sp_addextendedproperty @name=N'Description', @value=N'The date the version was publicly released.' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'SqlServerVersions', @level2type=N'COLUMN',@level2name=N'ReleaseDate' + EXEC sys.sp_addextendedproperty @name=N'Description', @value=N'The date main stream Microsoft support ends for the version.' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'SqlServerVersions', @level2type=N'COLUMN',@level2name=N'MainstreamSupportEndDate' + EXEC sys.sp_addextendedproperty @name=N'Description', @value=N'The date extended Microsoft support ends for the version.' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'SqlServerVersions', @level2type=N'COLUMN',@level2name=N'ExtendedSupportEndDate' + EXEC sys.sp_addextendedproperty @name=N'Description', @value=N'The major version name.' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'SqlServerVersions', @level2type=N'COLUMN',@level2name=N'MajorVersionName' + EXEC sys.sp_addextendedproperty @name=N'Description', @value=N'The minor version name.' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'SqlServerVersions', @level2type=N'COLUMN',@level2name=N'MinorVersionName' + EXEC sys.sp_addextendedproperty @name=N'Description', @value=N'A reference for SQL Server major and minor versions.' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'SqlServerVersions' -WITH x AS ( -SELECT wpt.database_name, ww.query_cost, wm.plan_id, wm.query_id, wm.query_id_all_plan_ids, wpt.query_sql_text, wm.proc_or_function_name, ww.warnings, wpt.pattern, - wm.parameter_sniffing_symptoms, wpt.last_force_failure_reason_desc, wpt.top_three_waits, wm.count_executions, wm.count_compiles, wm.total_cpu_time, wm.avg_cpu_time, - wm.total_duration, wm.avg_duration, wm.total_logical_io_reads, wm.avg_logical_io_reads, - wm.total_physical_io_reads, wm.avg_physical_io_reads, wm.total_logical_io_writes, wm.avg_logical_io_writes, wm.total_rowcount, wm.avg_rowcount, - wm.total_query_max_used_memory, wm.avg_query_max_used_memory, wm.total_tempdb_space_used, wm.avg_tempdb_space_used, - wm.total_log_bytes_used, wm.avg_log_bytes_used, wm.total_num_physical_io_reads, wm.avg_num_physical_io_reads, - wm.first_execution_time, wm.last_execution_time, wpt.context_settings, ROW_NUMBER() OVER (PARTITION BY wm.plan_id, wm.query_id, wm.last_execution_time ORDER BY wm.plan_id) AS rn -FROM #working_plan_text AS wpt -JOIN #working_warnings AS ww - ON wpt.plan_id = ww.plan_id - AND wpt.query_id = ww.query_id -JOIN #working_metrics AS wm - ON wpt.plan_id = wm.plan_id - AND wpt.query_id = wm.query_id -) -SELECT * -FROM x -WHERE x.rn = 1 -ORDER BY x.last_execution_time -OPTION (RECOMPILE); +END; +GO + +DELETE FROM dbo.SqlServerVersions; + +INSERT INTO dbo.SqlServerVersions + (MajorVersionNumber, MinorVersionNumber, Branch, [Url], ReleaseDate, MainstreamSupportEndDate, ExtendedSupportEndDate, MajorVersionName, MinorVersionName) +VALUES + (15, 4102, 'CU9', 'https://support.microsoft.com/en-us/help/5000642', '2021-02-11', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 9 '), + (15, 4073, 'CU8 GDR', 'https://support.microsoft.com/en-us/help/4583459', '2021-01-12', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 8 GDR '), + (15, 4073, 'CU8', 'https://support.microsoft.com/en-us/help/4577194', '2020-10-01', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 8 '), + (15, 4063, 'CU7', 'https://support.microsoft.com/en-us/help/4570012', '2020-09-02', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 7 '), + (15, 4053, 'CU6', 'https://support.microsoft.com/en-us/help/4563110', '2020-08-04', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 6 '), + (15, 4043, 'CU5', 'https://support.microsoft.com/en-us/help/4548597', '2020-06-22', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 5 '), + (15, 4033, 'CU4', 'https://support.microsoft.com/en-us/help/4548597', '2020-03-31', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 4 '), + (15, 4023, 'CU3', 'https://support.microsoft.com/en-us/help/4538853', '2020-03-12', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 3 '), + (15, 4013, 'CU2', 'https://support.microsoft.com/en-us/help/4536075', '2020-02-13', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 2 '), + (15, 4003, 'CU1', 'https://support.microsoft.com/en-us/help/4527376', '2020-01-07', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 1 '), + (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 '), + (14, 3370, 'RTM CU22 GDR', 'https://support.microsoft.com/en-us/help/4583457', '2021-01-12', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 22 GDR'), + (14, 3356, 'RTM CU22', 'https://support.microsoft.com/en-us/help/4577467', '2020-09-10', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 22'), + (14, 3335, 'RTM CU21', 'https://support.microsoft.com/en-us/help/4557397', '2020-07-01', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 21'), + (14, 3294, 'RTM CU20', 'https://support.microsoft.com/en-us/help/4541283', '2020-04-07', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 20'), + (14, 3257, 'RTM CU19', 'https://support.microsoft.com/en-us/help/4535007', '2020-02-05', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 19'), + (14, 3257, 'RTM CU18', 'https://support.microsoft.com/en-us/help/4527377', '2019-12-09', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 18'), + (14, 3238, 'RTM CU17', 'https://support.microsoft.com/en-us/help/4515579', '2019-10-08', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 17'), + (14, 3223, 'RTM CU16', 'https://support.microsoft.com/en-us/help/4508218', '2019-08-01', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 16'), + (14, 3162, 'RTM CU15', 'https://support.microsoft.com/en-us/help/4498951', '2019-05-24', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 15'), + (14, 3076, 'RTM CU14', 'https://support.microsoft.com/en-us/help/4484710', '2019-03-25', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 14'), + (14, 3048, 'RTM CU13', 'https://support.microsoft.com/en-us/help/4466404', '2018-12-18', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 13'), + (14, 3045, 'RTM CU12', 'https://support.microsoft.com/en-us/help/4464082', '2018-10-24', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 12'), + (14, 3038, 'RTM CU11', 'https://support.microsoft.com/en-us/help/4462262', '2018-09-20', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 11'), + (14, 3037, 'RTM CU10', 'https://support.microsoft.com/en-us/help/4524334', '2018-08-27', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 10'), + (14, 3030, 'RTM CU9', 'https://support.microsoft.com/en-us/help/4515435', '2018-07-18', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 9'), + (14, 3029, 'RTM CU8', 'https://support.microsoft.com/en-us/help/4338363', '2018-06-21', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 8'), + (14, 3026, 'RTM CU7', 'https://support.microsoft.com/en-us/help/4229789', '2018-05-23', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 7'), + (14, 3025, 'RTM CU6', 'https://support.microsoft.com/en-us/help/4101464', '2018-04-17', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 6'), + (14, 3023, 'RTM CU5', 'https://support.microsoft.com/en-us/help/4092643', '2018-03-20', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 5'), + (14, 3022, 'RTM CU4', 'https://support.microsoft.com/en-us/help/4056498', '2018-02-20', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 4'), + (14, 3015, 'RTM CU3', 'https://support.microsoft.com/en-us/help/4052987', '2018-01-04', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 3'), + (14, 3008, 'RTM CU2', 'https://support.microsoft.com/en-us/help/4052574', '2017-11-28', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 2'), + (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 '), + (13, 5882, 'SP2 CU16', 'https://support.microsoft.com/en-us/help/5000645', '2021-02-11', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 16'), + (13, 5865, 'SP2 CU15 GDR', 'https://support.microsoft.com/en-us/help/4583461', '2021-01-12', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 15 GDR'), + (13, 5850, 'SP2 CU15', 'https://support.microsoft.com/en-us/help/4577775', '2020-09-28', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 15'), + (13, 5830, 'SP2 CU14', 'https://support.microsoft.com/en-us/help/4564903', '2020-08-06', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 14'), + (13, 5820, 'SP2 CU13', 'https://support.microsoft.com/en-us/help/4549825', '2020-05-28', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 13'), + (13, 5698, 'SP2 CU12', 'https://support.microsoft.com/en-us/help/4536648', '2020-02-25', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 12'), + (13, 5598, 'SP2 CU11', 'https://support.microsoft.com/en-us/help/4527378', '2019-12-09', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 11'), + (13, 5492, 'SP2 CU10', 'https://support.microsoft.com/en-us/help/4505830', '2019-10-08', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 10'), + (13, 5479, 'SP2 CU9', 'https://support.microsoft.com/en-us/help/4505830', '2019-09-30', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 9'), + (13, 5426, 'SP2 CU8', 'https://support.microsoft.com/en-us/help/4505830', '2019-07-31', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 8'), + (13, 5337, 'SP2 CU7', 'https://support.microsoft.com/en-us/help/4495256', '2019-05-23', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 7'), + (13, 5292, 'SP2 CU6', 'https://support.microsoft.com/en-us/help/4488536', '2019-03-19', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 6'), + (13, 5264, 'SP2 CU5', 'https://support.microsoft.com/en-us/help/4475776', '2019-01-23', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 5'), + (13, 5233, 'SP2 CU4', 'https://support.microsoft.com/en-us/help/4464106', '2018-11-13', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 4'), + (13, 5216, 'SP2 CU3', 'https://support.microsoft.com/en-us/help/4458871', '2018-09-20', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 3'), + (13, 5201, 'SP2 CU2 + Security Update', 'https://support.microsoft.com/en-us/help/4458621', '2018-08-21', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 2 + Security Update'), + (13, 5153, 'SP2 CU2', 'https://support.microsoft.com/en-us/help/4340355', '2018-07-16', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 2'), + (13, 5149, 'SP2 CU1', 'https://support.microsoft.com/en-us/help/4135048', '2018-05-30', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 1'), + (13, 5103, 'SP2 GDR', 'https://support.microsoft.com/en-us/help/4583460', '2021-01-12', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 GDR'), + (13, 5026, 'SP2 ', 'https://support.microsoft.com/en-us/help/4052908', '2018-04-24', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 '), + (13, 4574, 'SP1 CU15', 'https://support.microsoft.com/en-us/help/4495257', '2019-05-16', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 15'), + (13, 4560, 'SP1 CU14', 'https://support.microsoft.com/en-us/help/4488535', '2019-03-19', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 14'), + (13, 4550, 'SP1 CU13', 'https://support.microsoft.com/en-us/help/4475775', '2019-01-23', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 13'), + (13, 4541, 'SP1 CU12', 'https://support.microsoft.com/en-us/help/4464343', '2018-11-13', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 12'), + (13, 4528, 'SP1 CU11', 'https://support.microsoft.com/en-us/help/4459676', '2018-09-17', '2019-07-09', '2019-07-09', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 11'), + (13, 4514, 'SP1 CU10', 'https://support.microsoft.com/en-us/help/4341569', '2018-07-16', '2019-07-09', '2019-07-09', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 10'), + (13, 4502, 'SP1 CU9', 'https://support.microsoft.com/en-us/help/4100997', '2018-05-30', '2019-07-09', '2019-07-09', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 9'), + (13, 4474, 'SP1 CU8', 'https://support.microsoft.com/en-us/help/4077064', '2018-03-19', '2019-07-09', '2019-07-09', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 8'), + (13, 4466, 'SP1 CU7', 'https://support.microsoft.com/en-us/help/4057119', '2018-01-04', '2019-07-09', '2019-07-09', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 7'), + (13, 4457, 'SP1 CU6', 'https://support.microsoft.com/en-us/help/4037354', '2017-11-20', '2019-07-09', '2019-07-09', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 6'), + (13, 4451, 'SP1 CU5', 'https://support.microsoft.com/en-us/help/4024305', '2017-09-18', '2019-07-09', '2019-07-09', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 5'), + (13, 4446, 'SP1 CU4', 'https://support.microsoft.com/en-us/help/4024305', '2017-08-08', '2019-07-09', '2019-07-09', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 4'), + (13, 4435, 'SP1 CU3', 'https://support.microsoft.com/en-us/help/4019916', '2017-05-15', '2019-07-09', '2019-07-09', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 3'), + (13, 4422, 'SP1 CU2', 'https://support.microsoft.com/en-us/help/4013106', '2017-03-20', '2019-07-09', '2019-07-09', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 2'), + (13, 4411, 'SP1 CU1', 'https://support.microsoft.com/en-us/help/3208177', '2017-01-17', '2019-07-09', '2019-07-09', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 1'), + (13, 4224, 'SP1 CU10 + Security Update', 'https://support.microsoft.com/en-us/help/4458842', '2018-08-22', '2019-07-09', '2019-07-09', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 10 + Security Update'), + (13, 4001, 'SP1 ', 'https://support.microsoft.com/en-us/help/3182545 ', '2016-11-16', '2019-07-09', '2019-07-09', 'SQL Server 2016', 'Service Pack 1 '), + (13, 2216, 'RTM CU9', 'https://support.microsoft.com/en-us/help/4037357', '2017-11-20', '2018-01-09', '2018-01-09', 'SQL Server 2016', 'RTM Cumulative Update 9'), + (13, 2213, 'RTM CU8', 'https://support.microsoft.com/en-us/help/4024304', '2017-09-18', '2018-01-09', '2018-01-09', 'SQL Server 2016', 'RTM Cumulative Update 8'), + (13, 2210, 'RTM CU7', 'https://support.microsoft.com/en-us/help/4024304', '2017-08-08', '2018-01-09', '2018-01-09', 'SQL Server 2016', 'RTM Cumulative Update 7'), + (13, 2204, 'RTM CU6', 'https://support.microsoft.com/en-us/help/4019914', '2017-05-15', '2018-01-09', '2018-01-09', 'SQL Server 2016', 'RTM Cumulative Update 6'), + (13, 2197, 'RTM CU5', 'https://support.microsoft.com/en-us/help/4013105', '2017-03-20', '2018-01-09', '2018-01-09', 'SQL Server 2016', 'RTM Cumulative Update 5'), + (13, 2193, 'RTM CU4', 'https://support.microsoft.com/en-us/help/3205052 ', '2017-01-17', '2018-01-09', '2018-01-09', 'SQL Server 2016', 'RTM Cumulative Update 4'), + (13, 2186, 'RTM CU3', 'https://support.microsoft.com/en-us/help/3205413 ', '2016-11-16', '2018-01-09', '2018-01-09', 'SQL Server 2016', 'RTM Cumulative Update 3'), + (13, 2164, 'RTM CU2', 'https://support.microsoft.com/en-us/help/3182270 ', '2016-09-22', '2018-01-09', '2018-01-09', 'SQL Server 2016', 'RTM Cumulative Update 2'), + (13, 2149, 'RTM CU1', 'https://support.microsoft.com/en-us/help/3164674 ', '2016-07-25', '2018-01-09', '2018-01-09', 'SQL Server 2016', 'RTM Cumulative Update 1'), + (13, 1601, 'RTM ', '', '2016-06-01', '2019-01-09', '2019-01-09', 'SQL Server 2016', 'RTM '), + (12, 6433, 'SP3 CU4 GDR', 'https://support.microsoft.com/en-us/help/4583462', '2021-01-12', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 4 GDR'), + (12, 6372, 'SP3 CU4 GDR', 'https://support.microsoft.com/en-us/help/4535288', '2020-02-11', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 4 GDR'), + (12, 6329, 'SP3 CU4', 'https://support.microsoft.com/en-us/help/4500181', '2019-07-29', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 4'), + (12, 6259, 'SP3 CU3', 'https://support.microsoft.com/en-us/help/4491539', '2019-04-16', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 3'), + (12, 6214, 'SP3 CU2', 'https://support.microsoft.com/en-us/help/4482960', '2019-02-19', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 2'), + (12, 6205, 'SP3 CU1', 'https://support.microsoft.com/en-us/help/4470220', '2018-12-12', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 1'), + (12, 6164, 'SP3 GDR', 'https://support.microsoft.com/en-us/help/4583463', '2021-01-12', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 GDR'), + (12, 6024, 'SP3 ', 'https://support.microsoft.com/en-us/help/4022619', '2018-10-30', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 '), + (12, 5687, 'SP2 CU18', 'https://support.microsoft.com/en-us/help/4500180', '2019-07-29', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 18'), + (12, 5632, 'SP2 CU17', 'https://support.microsoft.com/en-us/help/4491540', '2019-04-16', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 17'), + (12, 5626, 'SP2 CU16', 'https://support.microsoft.com/en-us/help/4482967', '2019-02-19', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 16'), + (12, 5605, 'SP2 CU15', 'https://support.microsoft.com/en-us/help/4469137', '2018-12-12', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 15'), + (12, 5600, 'SP2 CU14', 'https://support.microsoft.com/en-us/help/4459860', '2018-10-15', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 14'), + (12, 5590, 'SP2 CU13', 'https://support.microsoft.com/en-us/help/4456287', '2018-08-27', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 13'), + (12, 5589, 'SP2 CU12', 'https://support.microsoft.com/en-us/help/4130489', '2018-06-18', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 12'), + (12, 5579, 'SP2 CU11', 'https://support.microsoft.com/en-us/help/4077063', '2018-03-19', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 11'), + (12, 5571, 'SP2 CU10', 'https://support.microsoft.com/en-us/help/4052725', '2018-01-16', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 10'), + (12, 5563, 'SP2 CU9', 'https://support.microsoft.com/en-us/help/4055557', '2017-12-18', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 9'), + (12, 5557, 'SP2 CU8', 'https://support.microsoft.com/en-us/help/4037356', '2017-10-16', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 8'), + (12, 5556, 'SP2 CU7', 'https://support.microsoft.com/en-us/help/4032541', '2017-08-28', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 7'), + (12, 5553, 'SP2 CU6', 'https://support.microsoft.com/en-us/help/4019094', '2017-08-08', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 6'), + (12, 5546, 'SP2 CU5', 'https://support.microsoft.com/en-us/help/4013098', '2017-04-17', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 5'), + (12, 5540, 'SP2 CU4', 'https://support.microsoft.com/en-us/help/4010394', '2017-02-21', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 4'), + (12, 5538, 'SP2 CU3', 'https://support.microsoft.com/en-us/help/3204388 ', '2016-12-19', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 3'), + (12, 5522, 'SP2 CU2', 'https://support.microsoft.com/en-us/help/3188778 ', '2016-10-17', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 2'), + (12, 5511, 'SP2 CU1', 'https://support.microsoft.com/en-us/help/3178925 ', '2016-08-25', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 1'), + (12, 5000, 'SP2 ', 'https://support.microsoft.com/en-us/help/3171021 ', '2016-07-11', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 '), + (12, 4522, 'SP1 CU13', 'https://support.microsoft.com/en-us/help/4019099', '2017-08-08', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 Cumulative Update 13'), + (12, 4511, 'SP1 CU12', 'https://support.microsoft.com/en-us/help/4017793', '2017-04-17', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 Cumulative Update 12'), + (12, 4502, 'SP1 CU11', 'https://support.microsoft.com/en-us/help/4010392', '2017-02-21', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 Cumulative Update 11'), + (12, 4491, 'SP1 CU10', 'https://support.microsoft.com/en-us/help/3204399 ', '2016-12-19', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 Cumulative Update 10'), + (12, 4474, 'SP1 CU9', 'https://support.microsoft.com/en-us/help/3186964 ', '2016-10-17', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 Cumulative Update 9'), + (12, 4468, 'SP1 CU8', 'https://support.microsoft.com/en-us/help/3174038 ', '2016-08-15', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 Cumulative Update 8'), + (12, 4459, 'SP1 CU7', 'https://support.microsoft.com/en-us/help/3162659 ', '2016-06-20', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 Cumulative Update 7'), + (12, 4457, 'SP1 CU6', 'https://support.microsoft.com/en-us/help/3167392 ', '2016-05-30', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 Cumulative Update 6'), + (12, 4449, 'SP1 CU6', 'https://support.microsoft.com/en-us/help/3144524', '2016-04-18', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 Cumulative Update 6'), + (12, 4438, 'SP1 CU5', 'https://support.microsoft.com/en-us/help/3130926', '2016-02-22', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 Cumulative Update 5'), + (12, 4436, 'SP1 CU4', 'https://support.microsoft.com/en-us/help/3106660', '2015-12-21', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 Cumulative Update 4'), + (12, 4427, 'SP1 CU3', 'https://support.microsoft.com/en-us/help/3094221', '2015-10-19', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 Cumulative Update 3'), + (12, 4422, 'SP1 CU2', 'https://support.microsoft.com/en-us/help/3075950', '2015-08-17', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 Cumulative Update 2'), + (12, 4416, 'SP1 CU1', 'https://support.microsoft.com/en-us/help/3067839', '2015-06-19', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 Cumulative Update 1'), + (12, 4213, 'SP1 MS15-058: GDR Security Update', 'https://support.microsoft.com/en-us/help/3070446', '2015-07-14', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 MS15-058: GDR Security Update'), + (12, 4100, 'SP1 ', 'https://support.microsoft.com/en-us/help/3058865', '2015-05-04', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 '), + (12, 2569, 'RTM CU14', 'https://support.microsoft.com/en-us/help/3158271 ', '2016-06-20', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM Cumulative Update 14'), + (12, 2568, 'RTM CU13', 'https://support.microsoft.com/en-us/help/3144517', '2016-04-18', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM Cumulative Update 13'), + (12, 2564, 'RTM CU12', 'https://support.microsoft.com/en-us/help/3130923', '2016-02-22', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM Cumulative Update 12'), + (12, 2560, 'RTM CU11', 'https://support.microsoft.com/en-us/help/3106659', '2015-12-21', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM Cumulative Update 11'), + (12, 2556, 'RTM CU10', 'https://support.microsoft.com/en-us/help/3094220', '2015-10-19', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM Cumulative Update 10'), + (12, 2553, 'RTM CU9', 'https://support.microsoft.com/en-us/help/3075949', '2015-08-17', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM Cumulative Update 9'), + (12, 2548, 'RTM MS15-058: QFE Security Update', 'https://support.microsoft.com/en-us/help/3045323', '2015-07-14', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM MS15-058: QFE Security Update'), + (12, 2546, 'RTM CU8', 'https://support.microsoft.com/en-us/help/3067836', '2015-06-19', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM Cumulative Update 8'), + (12, 2495, 'RTM CU7', 'https://support.microsoft.com/en-us/help/3046038', '2015-04-20', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM Cumulative Update 7'), + (12, 2480, 'RTM CU6', 'https://support.microsoft.com/en-us/help/3031047', '2015-02-16', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM Cumulative Update 6'), + (12, 2456, 'RTM CU5', 'https://support.microsoft.com/en-us/help/3011055', '2014-12-17', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM Cumulative Update 5'), + (12, 2430, 'RTM CU4', 'https://support.microsoft.com/en-us/help/2999197', '2014-10-21', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM Cumulative Update 4'), + (12, 2402, 'RTM CU3', 'https://support.microsoft.com/en-us/help/2984923', '2014-08-18', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM Cumulative Update 3'), + (12, 2381, 'RTM MS14-044: QFE Security Update', 'https://support.microsoft.com/en-us/help/2977316', '2014-08-12', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM MS14-044: QFE Security Update'), + (12, 2370, 'RTM CU2', 'https://support.microsoft.com/en-us/help/2967546', '2014-06-27', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM Cumulative Update 2'), + (12, 2342, 'RTM CU1', 'https://support.microsoft.com/en-us/help/2931693', '2014-04-21', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM Cumulative Update 1'), + (12, 2269, 'RTM MS15-058: GDR Security Update ', 'https://support.microsoft.com/en-us/help/3045324', '2015-07-14', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM MS15-058: GDR Security Update '), + (12, 2254, 'RTM MS14-044: GDR Security Update', 'https://support.microsoft.com/en-us/help/2977315', '2014-08-12', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM MS14-044: GDR Security Update'), + (12, 2000, 'RTM ', '', '2014-04-01', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM '), + (11, 7507, 'SP4 GDR Security Update', 'https://support.microsoft.com/en-us/help/4583465', '2021-01-12', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'Service Pack 4 GDR Security Update for CVE-2021-1636'), + (11, 7493, 'SP4 GDR Security Update', 'https://support.microsoft.com/en-us/help/4532098', '2020-02-11', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'Service Pack 4 GDR Security Update for CVE-2020-0618'), + (11, 7469, 'SP4 On-Demand Hotfix Update', 'https://support.microsoft.com/en-us/help/4091266', '2018-03-28', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'Service Pack 4 SP4 On-Demand Hotfix Update'), + (11, 7462, 'SP4 ADV180002: GDR Security Update', 'https://support.microsoft.com/en-us/help/4057116', '2018-01-12', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'Service Pack 4 ADV180002: GDR Security Update'), + (11, 7001, 'SP4 ', 'https://support.microsoft.com/en-us/help/4018073', '2017-10-02', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'Service Pack 4 '), + (11, 6607, 'SP3 CU10', 'https://support.microsoft.com/en-us/help/4025925', '2017-08-08', '2018-10-09', '2018-10-09', 'SQL Server 2012', 'Service Pack 3 Cumulative Update 10'), + (11, 6598, 'SP3 CU9', 'https://support.microsoft.com/en-us/help/4016762', '2017-05-15', '2018-10-09', '2018-10-09', 'SQL Server 2012', 'Service Pack 3 Cumulative Update 9'), + (11, 6594, 'SP3 CU8', 'https://support.microsoft.com/en-us/help/3205051 ', '2017-03-20', '2018-10-09', '2018-10-09', 'SQL Server 2012', 'Service Pack 3 Cumulative Update 8'), + (11, 6579, 'SP3 CU7', 'https://support.microsoft.com/en-us/help/3205051 ', '2017-01-17', '2018-10-09', '2018-10-09', 'SQL Server 2012', 'Service Pack 3 Cumulative Update 7'), + (11, 6567, 'SP3 CU6', 'https://support.microsoft.com/en-us/help/3194992 ', '2016-11-17', '2018-10-09', '2018-10-09', 'SQL Server 2012', 'Service Pack 3 Cumulative Update 6'), + (11, 6544, 'SP3 CU5', 'https://support.microsoft.com/en-us/help/3180915 ', '2016-09-19', '2018-10-09', '2018-10-09', 'SQL Server 2012', 'Service Pack 3 Cumulative Update 5'), + (11, 6540, 'SP3 CU4', 'https://support.microsoft.com/en-us/help/3165264 ', '2016-07-18', '2018-10-09', '2018-10-09', 'SQL Server 2012', 'Service Pack 3 Cumulative Update 4'), + (11, 6537, 'SP3 CU3', 'https://support.microsoft.com/en-us/help/3152635 ', '2016-05-16', '2018-10-09', '2018-10-09', 'SQL Server 2012', 'Service Pack 3 Cumulative Update 3'), + (11, 6523, 'SP3 CU2', 'https://support.microsoft.com/en-us/help/3137746', '2016-03-21', '2018-10-09', '2018-10-09', 'SQL Server 2012', 'Service Pack 3 Cumulative Update 2'), + (11, 6518, 'SP3 CU1', 'https://support.microsoft.com/en-us/help/3123299', '2016-01-19', '2018-10-09', '2018-10-09', 'SQL Server 2012', 'Service Pack 3 Cumulative Update 1'), + (11, 6020, 'SP3 ', 'https://support.microsoft.com/en-us/help/3072779', '2015-11-20', '2018-10-09', '2018-10-09', 'SQL Server 2012', 'Service Pack 3 '), + (11, 5678, 'SP2 CU16', 'https://support.microsoft.com/en-us/help/3205416 ', '2016-11-17', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 16'), + (11, 5676, 'SP2 CU15', 'https://support.microsoft.com/en-us/help/3205416 ', '2016-11-17', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 15'), + (11, 5657, 'SP2 CU14', 'https://support.microsoft.com/en-us/help/3180914 ', '2016-09-19', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 14'), + (11, 5655, 'SP2 CU13', 'https://support.microsoft.com/en-us/help/3165266 ', '2016-07-18', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 13'), + (11, 5649, 'SP2 CU12', 'https://support.microsoft.com/en-us/help/3152637 ', '2016-05-16', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 12'), + (11, 5646, 'SP2 CU11', 'https://support.microsoft.com/en-us/help/3137745', '2016-03-21', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 11'), + (11, 5644, 'SP2 CU10', 'https://support.microsoft.com/en-us/help/3120313', '2016-01-19', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 10'), + (11, 5641, 'SP2 CU9', 'https://support.microsoft.com/en-us/help/3098512', '2015-11-16', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 9'), + (11, 5634, 'SP2 CU8', 'https://support.microsoft.com/en-us/help/3082561', '2015-09-21', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 8'), + (11, 5623, 'SP2 CU7', 'https://support.microsoft.com/en-us/help/3072100', '2015-07-20', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 7'), + (11, 5613, 'SP2 MS15-058: QFE Security Update', 'https://support.microsoft.com/en-us/help/3045319', '2015-07-14', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 MS15-058: QFE Security Update'), + (11, 5592, 'SP2 CU6', 'https://support.microsoft.com/en-us/help/3052468', '2015-05-18', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 6'), + (11, 5582, 'SP2 CU5', 'https://support.microsoft.com/en-us/help/3037255', '2015-03-16', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 5'), + (11, 5569, 'SP2 CU4', 'https://support.microsoft.com/en-us/help/3007556', '2015-01-19', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 4'), + (11, 5556, 'SP2 CU3', 'https://support.microsoft.com/en-us/help/3002049', '2014-11-17', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 3'), + (11, 5548, 'SP2 CU2', 'https://support.microsoft.com/en-us/help/2983175', '2014-09-15', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 2'), + (11, 5532, 'SP2 CU1', 'https://support.microsoft.com/en-us/help/2976982', '2014-07-23', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 1'), + (11, 5343, 'SP2 MS15-058: GDR Security Update', 'https://support.microsoft.com/en-us/help/3045321', '2015-07-14', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 MS15-058: GDR Security Update'), + (11, 5058, 'SP2 ', 'https://support.microsoft.com/en-us/help/2958429', '2014-06-10', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 '), + (11, 3513, 'SP1 MS15-058: QFE Security Update', 'https://support.microsoft.com/en-us/help/3045317', '2015-07-14', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 MS15-058: QFE Security Update'), + (11, 3482, 'SP1 CU13', 'https://support.microsoft.com/en-us/help/3002044', '2014-11-17', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 Cumulative Update 13'), + (11, 3470, 'SP1 CU12', 'https://support.microsoft.com/en-us/help/2991533', '2014-09-15', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 Cumulative Update 12'), + (11, 3460, 'SP1 MS14-044: QFE Security Update ', 'https://support.microsoft.com/en-us/help/2977325', '2014-08-12', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 MS14-044: QFE Security Update '), + (11, 3449, 'SP1 CU11', 'https://support.microsoft.com/en-us/help/2975396', '2014-07-21', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 Cumulative Update 11'), + (11, 3431, 'SP1 CU10', 'https://support.microsoft.com/en-us/help/2954099', '2014-05-19', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 Cumulative Update 10'), + (11, 3412, 'SP1 CU9', 'https://support.microsoft.com/en-us/help/2931078', '2014-03-17', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 Cumulative Update 9'), + (11, 3401, 'SP1 CU8', 'https://support.microsoft.com/en-us/help/2917531', '2014-01-20', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 Cumulative Update 8'), + (11, 3393, 'SP1 CU7', 'https://support.microsoft.com/en-us/help/2894115', '2013-11-18', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 Cumulative Update 7'), + (11, 3381, 'SP1 CU6', 'https://support.microsoft.com/en-us/help/2874879', '2013-09-16', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 Cumulative Update 6'), + (11, 3373, 'SP1 CU5', 'https://support.microsoft.com/en-us/help/2861107', '2013-07-15', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 Cumulative Update 5'), + (11, 3368, 'SP1 CU4', 'https://support.microsoft.com/en-us/help/2833645', '2013-05-30', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 Cumulative Update 4'), + (11, 3349, 'SP1 CU3', 'https://support.microsoft.com/en-us/help/2812412', '2013-03-18', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 Cumulative Update 3'), + (11, 3339, 'SP1 CU2', 'https://support.microsoft.com/en-us/help/2790947', '2013-01-21', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 Cumulative Update 2'), + (11, 3321, 'SP1 CU1', 'https://support.microsoft.com/en-us/help/2765331', '2012-11-20', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 Cumulative Update 1'), + (11, 3156, 'SP1 MS15-058: GDR Security Update', 'https://support.microsoft.com/en-us/help/3045318', '2015-07-14', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 MS15-058: GDR Security Update'), + (11, 3153, 'SP1 MS14-044: GDR Security Update', 'https://support.microsoft.com/en-us/help/2977326', '2014-08-12', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 MS14-044: GDR Security Update'), + (11, 3000, 'SP1 ', 'https://support.microsoft.com/en-us/help/2674319', '2012-11-07', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 '), + (11, 2424, 'RTM CU11', 'https://support.microsoft.com/en-us/help/2908007', '2013-12-16', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'RTM Cumulative Update 11'), + (11, 2420, 'RTM CU10', 'https://support.microsoft.com/en-us/help/2891666', '2013-10-21', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'RTM Cumulative Update 10'), + (11, 2419, 'RTM CU9', 'https://support.microsoft.com/en-us/help/2867319', '2013-08-20', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'RTM Cumulative Update 9'), + (11, 2410, 'RTM CU8', 'https://support.microsoft.com/en-us/help/2844205', '2013-06-17', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'RTM Cumulative Update 8'), + (11, 2405, 'RTM CU7', 'https://support.microsoft.com/en-us/help/2823247', '2013-04-15', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'RTM Cumulative Update 7'), + (11, 2401, 'RTM CU6', 'https://support.microsoft.com/en-us/help/2728897', '2013-02-18', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'RTM Cumulative Update 6'), + (11, 2395, 'RTM CU5', 'https://support.microsoft.com/en-us/help/2777772', '2012-12-17', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'RTM Cumulative Update 5'), + (11, 2383, 'RTM CU4', 'https://support.microsoft.com/en-us/help/2758687', '2012-10-15', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'RTM Cumulative Update 4'), + (11, 2376, 'RTM MS12-070: QFE Security Update', 'https://support.microsoft.com/en-us/help/2716441', '2012-10-09', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'RTM MS12-070: QFE Security Update'), + (11, 2332, 'RTM CU3', 'https://support.microsoft.com/en-us/help/2723749', '2012-08-31', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'RTM Cumulative Update 3'), + (11, 2325, 'RTM CU2', 'https://support.microsoft.com/en-us/help/2703275', '2012-06-18', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'RTM Cumulative Update 2'), + (11, 2316, 'RTM CU1', 'https://support.microsoft.com/en-us/help/2679368', '2012-04-12', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'RTM Cumulative Update 1'), + (11, 2218, 'RTM MS12-070: GDR Security Update', 'https://support.microsoft.com/en-us/help/2716442', '2012-10-09', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'RTM MS12-070: GDR Security Update'), + (11, 2100, 'RTM ', '', '2012-03-06', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'RTM '), + (10, 6529, 'SP3 MS15-058: QFE Security Update', 'https://support.microsoft.com/en-us/help/3045314', '2015-07-14', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'Service Pack 3 MS15-058: QFE Security Update'), + (10, 6220, 'SP3 MS15-058: QFE Security Update', 'https://support.microsoft.com/en-us/help/3045316', '2015-07-14', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'Service Pack 3 MS15-058: QFE Security Update'), + (10, 6000, 'SP3 ', 'https://support.microsoft.com/en-us/help/2979597', '2014-09-26', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'Service Pack 3 '), + (10, 4339, 'SP2 MS15-058: QFE Security Update', 'https://support.microsoft.com/en-us/help/3045312', '2015-07-14', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 MS15-058: QFE Security Update'), + (10, 4321, 'SP2 MS14-044: QFE Security Update', 'https://support.microsoft.com/en-us/help/2977319', '2014-08-14', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 MS14-044: QFE Security Update'), + (10, 4319, 'SP2 CU13', 'https://support.microsoft.com/en-us/help/2967540', '2014-06-30', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 Cumulative Update 13'), + (10, 4305, 'SP2 CU12', 'https://support.microsoft.com/en-us/help/2938478', '2014-04-21', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 Cumulative Update 12'), + (10, 4302, 'SP2 CU11', 'https://support.microsoft.com/en-us/help/2926028', '2014-02-18', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 Cumulative Update 11'), + (10, 4297, 'SP2 CU10', 'https://support.microsoft.com/en-us/help/2908087', '2013-12-17', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 Cumulative Update 10'), + (10, 4295, 'SP2 CU9', 'https://support.microsoft.com/en-us/help/2887606', '2013-10-28', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 Cumulative Update 9'), + (10, 4290, 'SP2 CU8', 'https://support.microsoft.com/en-us/help/2871401', '2013-08-22', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 Cumulative Update 8'), + (10, 4285, 'SP2 CU7', 'https://support.microsoft.com/en-us/help/2844090', '2013-06-17', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 Cumulative Update 7'), + (10, 4279, 'SP2 CU6', 'https://support.microsoft.com/en-us/help/2830140', '2013-04-15', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 Cumulative Update 6'), + (10, 4276, 'SP2 CU5', 'https://support.microsoft.com/en-us/help/2797460', '2013-02-18', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 Cumulative Update 5'), + (10, 4270, 'SP2 CU4', 'https://support.microsoft.com/en-us/help/2777358', '2012-12-17', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 Cumulative Update 4'), + (10, 4266, 'SP2 CU3', 'https://support.microsoft.com/en-us/help/2754552', '2012-10-15', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 Cumulative Update 3'), + (10, 4263, 'SP2 CU2', 'https://support.microsoft.com/en-us/help/2740411', '2012-08-31', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 Cumulative Update 2'), + (10, 4260, 'SP2 CU1', 'https://support.microsoft.com/en-us/help/2720425', '2012-07-24', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 Cumulative Update 1'), + (10, 4042, 'SP2 MS15-058: GDR Security Update', 'https://support.microsoft.com/en-us/help/3045313', '2015-07-14', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 MS15-058: GDR Security Update'), + (10, 4033, 'SP2 MS14-044: GDR Security Update', 'https://support.microsoft.com/en-us/help/2977320', '2014-08-12', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 MS14-044: GDR Security Update'), + (10, 4000, 'SP2 ', 'https://support.microsoft.com/en-us/help/2630458', '2012-07-26', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 '), + (10, 2881, 'SP1 CU14', 'https://support.microsoft.com/en-us/help/2868244', '2013-08-08', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 Cumulative Update 14'), + (10, 2876, 'SP1 CU13', 'https://support.microsoft.com/en-us/help/2855792', '2013-06-17', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 Cumulative Update 13'), + (10, 2874, 'SP1 CU12', 'https://support.microsoft.com/en-us/help/2828727', '2013-04-15', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 Cumulative Update 12'), + (10, 2869, 'SP1 CU11', 'https://support.microsoft.com/en-us/help/2812683', '2013-02-18', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 Cumulative Update 11'), + (10, 2868, 'SP1 CU10', 'https://support.microsoft.com/en-us/help/2783135', '2012-12-17', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 Cumulative Update 10'), + (10, 2866, 'SP1 CU9', 'https://support.microsoft.com/en-us/help/2756574', '2012-10-15', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 Cumulative Update 9'), + (10, 2861, 'SP1 MS12-070: QFE Security Update', 'https://support.microsoft.com/en-us/help/2716439', '2012-10-09', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 MS12-070: QFE Security Update'), + (10, 2822, 'SP1 CU8', 'https://support.microsoft.com/en-us/help/2723743', '2012-08-31', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 Cumulative Update 8'), + (10, 2817, 'SP1 CU7', 'https://support.microsoft.com/en-us/help/2703282', '2012-06-18', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 Cumulative Update 7'), + (10, 2811, 'SP1 CU6', 'https://support.microsoft.com/en-us/help/2679367', '2012-04-16', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 Cumulative Update 6'), + (10, 2806, 'SP1 CU5', 'https://support.microsoft.com/en-us/help/2659694', '2012-02-22', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 Cumulative Update 5'), + (10, 2796, 'SP1 CU4', 'https://support.microsoft.com/en-us/help/2633146', '2011-12-19', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 Cumulative Update 4'), + (10, 2789, 'SP1 CU3', 'https://support.microsoft.com/en-us/help/2591748', '2011-10-17', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 Cumulative Update 3'), + (10, 2772, 'SP1 CU2', 'https://support.microsoft.com/en-us/help/2567714', '2011-08-15', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 Cumulative Update 2'), + (10, 2769, 'SP1 CU1', 'https://support.microsoft.com/en-us/help/2544793', '2011-07-18', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 Cumulative Update 1'), + (10, 2550, 'SP1 MS12-070: GDR Security Update', 'https://support.microsoft.com/en-us/help/2754849', '2012-10-09', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 MS12-070: GDR Security Update'), + (10, 2500, 'SP1 ', 'https://support.microsoft.com/en-us/help/2528583', '2011-07-12', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 '), + (10, 1815, 'RTM CU13', 'https://support.microsoft.com/en-us/help/2679366', '2012-04-16', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM Cumulative Update 13'), + (10, 1810, 'RTM CU12', 'https://support.microsoft.com/en-us/help/2659692', '2012-02-21', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM Cumulative Update 12'), + (10, 1809, 'RTM CU11', 'https://support.microsoft.com/en-us/help/2633145', '2011-12-19', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM Cumulative Update 11'), + (10, 1807, 'RTM CU10', 'https://support.microsoft.com/en-us/help/2591746', '2011-10-17', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM Cumulative Update 10'), + (10, 1804, 'RTM CU9', 'https://support.microsoft.com/en-us/help/2567713', '2011-08-15', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM Cumulative Update 9'), + (10, 1797, 'RTM CU8', 'https://support.microsoft.com/en-us/help/2534352', '2011-06-20', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM Cumulative Update 8'), + (10, 1790, 'RTM MS11-049: QFE Security Update', 'https://support.microsoft.com/en-us/help/2494086', '2011-06-14', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM MS11-049: QFE Security Update'), + (10, 1777, 'RTM CU7', 'https://support.microsoft.com/en-us/help/2507770', '2011-04-18', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM Cumulative Update 7'), + (10, 1765, 'RTM CU6', 'https://support.microsoft.com/en-us/help/2489376', '2011-02-21', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM Cumulative Update 6'), + (10, 1753, 'RTM CU5', 'https://support.microsoft.com/en-us/help/2438347', '2010-12-20', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM Cumulative Update 5'), + (10, 1746, 'RTM CU4', 'https://support.microsoft.com/en-us/help/2345451', '2010-10-18', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM Cumulative Update 4'), + (10, 1734, 'RTM CU3', 'https://support.microsoft.com/en-us/help/2261464', '2010-08-16', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM Cumulative Update 3'), + (10, 1720, 'RTM CU2', 'https://support.microsoft.com/en-us/help/2072493', '2010-06-21', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM Cumulative Update 2'), + (10, 1702, 'RTM CU1', 'https://support.microsoft.com/en-us/help/981355', '2010-05-18', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM Cumulative Update 1'), + (10, 1617, 'RTM MS11-049: GDR Security Update', 'https://support.microsoft.com/en-us/help/2494088', '2011-06-14', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM MS11-049: GDR Security Update'), + (10, 1600, 'RTM ', '', '2010-05-10', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM '), + (10, 6535, 'SP3 MS15-058: QFE Security Update', 'https://support.microsoft.com/en-us/help/3045308', '2015-07-14', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 MS15-058: QFE Security Update'), + (10, 6241, 'SP3 MS15-058: GDR Security Update', 'https://support.microsoft.com/en-us/help/3045311', '2015-07-14', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 MS15-058: GDR Security Update'), + (10, 5890, 'SP3 MS15-058: QFE Security Update', 'https://support.microsoft.com/en-us/help/3045303', '2015-07-14', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 MS15-058: QFE Security Update'), + (10, 5869, 'SP3 MS14-044: QFE Security Update', 'https://support.microsoft.com/en-us/help/2984340, https://support.microsoft.com/en-us/help/2977322', '2014-08-12', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 MS14-044: QFE Security Update'), + (10, 5861, 'SP3 CU17', 'https://support.microsoft.com/en-us/help/2958696', '2014-05-19', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 17'), + (10, 5852, 'SP3 CU16', 'https://support.microsoft.com/en-us/help/2936421', '2014-03-17', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 16'), + (10, 5850, 'SP3 CU15', 'https://support.microsoft.com/en-us/help/2923520', '2014-01-20', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 15'), + (10, 5848, 'SP3 CU14', 'https://support.microsoft.com/en-us/help/2893410', '2013-11-18', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 14'), + (10, 5846, 'SP3 CU13', 'https://support.microsoft.com/en-us/help/2880350', '2013-09-16', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 13'), + (10, 5844, 'SP3 CU12', 'https://support.microsoft.com/en-us/help/2863205', '2013-07-15', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 12'), + (10, 5840, 'SP3 CU11', 'https://support.microsoft.com/en-us/help/2834048', '2013-05-20', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 11'), + (10, 5835, 'SP3 CU10', 'https://support.microsoft.com/en-us/help/2814783', '2013-03-18', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 10'), + (10, 5829, 'SP3 CU9', 'https://support.microsoft.com/en-us/help/2799883', '2013-01-21', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 9'), + (10, 5828, 'SP3 CU8', 'https://support.microsoft.com/en-us/help/2771833', '2012-11-19', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 8'), + (10, 5826, 'SP3 MS12-070: QFE Security Update', 'https://support.microsoft.com/en-us/help/2716435', '2012-10-09', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 MS12-070: QFE Security Update'), + (10, 5794, 'SP3 CU7', 'https://support.microsoft.com/en-us/help/2738350', '2012-09-17', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 7'), + (10, 5788, 'SP3 CU6', 'https://support.microsoft.com/en-us/help/2715953', '2012-07-16', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 6'), + (10, 5785, 'SP3 CU5', 'https://support.microsoft.com/en-us/help/2696626', '2012-05-21', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 5'), + (10, 5775, 'SP3 CU4', 'https://support.microsoft.com/en-us/help/2673383', '2012-03-19', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 4'), + (10, 5770, 'SP3 CU3', 'https://support.microsoft.com/en-us/help/2648098', '2012-01-16', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 3'), + (10, 5768, 'SP3 CU2', 'https://support.microsoft.com/en-us/help/2633143', '2011-11-21', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 2'), + (10, 5766, 'SP3 CU1', 'https://support.microsoft.com/en-us/help/2617146', '2011-10-17', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 1'), + (10, 5538, 'SP3 MS15-058: GDR Security Update', 'https://support.microsoft.com/en-us/help/3045305', '2015-07-14', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 MS15-058: GDR Security Update'), + (10, 5520, 'SP3 MS14-044: GDR Security Update', 'https://support.microsoft.com/en-us/help/2977321', '2014-08-12', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 MS14-044: GDR Security Update'), + (10, 5512, 'SP3 MS12-070: GDR Security Update', 'https://support.microsoft.com/en-us/help/2716436', '2012-10-09', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 MS12-070: GDR Security Update'), + (10, 5500, 'SP3 ', 'https://support.microsoft.com/en-us/help/2546951', '2011-10-06', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 '), + (10, 4371, 'SP2 MS12-070: QFE Security Update', 'https://support.microsoft.com/en-us/help/2716433', '2012-10-09', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 MS12-070: QFE Security Update'), + (10, 4333, 'SP2 CU11', 'https://support.microsoft.com/en-us/help/2715951', '2012-07-16', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 Cumulative Update 11'), + (10, 4332, 'SP2 CU10', 'https://support.microsoft.com/en-us/help/2696625', '2012-05-21', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 Cumulative Update 10'), + (10, 4330, 'SP2 CU9', 'https://support.microsoft.com/en-us/help/2673382', '2012-03-19', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 Cumulative Update 9'), + (10, 4326, 'SP2 CU8', 'https://support.microsoft.com/en-us/help/2648096', '2012-01-16', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 Cumulative Update 8'), + (10, 4323, 'SP2 CU7', 'https://support.microsoft.com/en-us/help/2617148', '2011-11-21', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 Cumulative Update 7'), + (10, 4321, 'SP2 CU6', 'https://support.microsoft.com/en-us/help/2582285', '2011-09-19', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 Cumulative Update 6'), + (10, 4316, 'SP2 CU5', 'https://support.microsoft.com/en-us/help/2555408', '2011-07-18', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 Cumulative Update 5'), + (10, 4311, 'SP2 MS11-049: QFE Security Update', 'https://support.microsoft.com/en-us/help/2494094', '2011-06-14', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 MS11-049: QFE Security Update'), + (10, 4285, 'SP2 CU4', 'https://support.microsoft.com/en-us/help/2527180', '2011-05-16', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 Cumulative Update 4'), + (10, 4279, 'SP2 CU3', 'https://support.microsoft.com/en-us/help/2498535', '2011-03-17', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 Cumulative Update 3'), + (10, 4272, 'SP2 CU2', 'https://support.microsoft.com/en-us/help/2467239', '2011-01-17', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 Cumulative Update 2'), + (10, 4266, 'SP2 CU1', 'https://support.microsoft.com/en-us/help/2289254', '2010-11-15', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 Cumulative Update 1'), + (10, 4067, 'SP2 MS12-070: GDR Security Update', 'https://support.microsoft.com/en-us/help/2716434', '2012-10-09', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 MS12-070: GDR Security Update'), + (10, 4064, 'SP2 MS11-049: GDR Security Update', 'https://support.microsoft.com/en-us/help/2494089', '2011-06-14', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 MS11-049: GDR Security Update'), + (10, 4000, 'SP2 ', 'https://support.microsoft.com/en-us/help/2285068', '2010-09-29', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 '), + (10, 2850, 'SP1 CU16', 'https://support.microsoft.com/en-us/help/2582282', '2011-09-19', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 16'), + (10, 2847, 'SP1 CU15', 'https://support.microsoft.com/en-us/help/2555406', '2011-07-18', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 15'), + (10, 2841, 'SP1 MS11-049: QFE Security Update', 'https://support.microsoft.com/en-us/help/2494100', '2011-06-14', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 MS11-049: QFE Security Update'), + (10, 2821, 'SP1 CU14', 'https://support.microsoft.com/en-us/help/2527187', '2011-05-16', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 14'), + (10, 2816, 'SP1 CU13', 'https://support.microsoft.com/en-us/help/2497673', '2011-03-17', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 13'), + (10, 2808, 'SP1 CU12', 'https://support.microsoft.com/en-us/help/2467236', '2011-01-17', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 12'), + (10, 2804, 'SP1 CU11', 'https://support.microsoft.com/en-us/help/2413738', '2010-11-15', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 11'), + (10, 2799, 'SP1 CU10', 'https://support.microsoft.com/en-us/help/2279604', '2010-09-20', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 10'), + (10, 2789, 'SP1 CU9', 'https://support.microsoft.com/en-us/help/2083921', '2010-07-19', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 9'), + (10, 2775, 'SP1 CU8', 'https://support.microsoft.com/en-us/help/981702', '2010-05-17', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 8'), + (10, 2766, 'SP1 CU7', 'https://support.microsoft.com/en-us/help/979065', '2010-03-26', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 7'), + (10, 2757, 'SP1 CU6', 'https://support.microsoft.com/en-us/help/977443', '2010-01-18', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 6'), + (10, 2746, 'SP1 CU5', 'https://support.microsoft.com/en-us/help/975977', '2009-11-16', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 5'), + (10, 2734, 'SP1 CU4', 'https://support.microsoft.com/en-us/help/973602', '2009-09-21', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 4'), + (10, 2723, 'SP1 CU3', 'https://support.microsoft.com/en-us/help/971491', '2009-07-20', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 3'), + (10, 2714, 'SP1 CU2', 'https://support.microsoft.com/en-us/help/970315', '2009-05-18', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 2'), + (10, 2710, 'SP1 CU1', 'https://support.microsoft.com/en-us/help/969099', '2009-04-16', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 1'), + (10, 2573, 'SP1 MS11-049: GDR Security update', 'https://support.microsoft.com/en-us/help/2494096', '2011-06-14', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 MS11-049: GDR Security update'), + (10, 2531, 'SP1 ', '', '2009-04-01', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 '), + (10, 1835, 'RTM CU10', 'https://support.microsoft.com/en-us/help/979064', '2010-03-15', '2014-07-08', '2019-07-09', 'SQL Server 2008', 'RTM Cumulative Update 10'), + (10, 1828, 'RTM CU9', 'https://support.microsoft.com/en-us/help/977444', '2010-01-18', '2014-07-08', '2019-07-09', 'SQL Server 2008', 'RTM Cumulative Update 9'), + (10, 1823, 'RTM CU8', 'https://support.microsoft.com/en-us/help/975976', '2009-11-16', '2014-07-08', '2019-07-09', 'SQL Server 2008', 'RTM Cumulative Update 8'), + (10, 1818, 'RTM CU7', 'https://support.microsoft.com/en-us/help/973601', '2009-09-21', '2014-07-08', '2019-07-09', 'SQL Server 2008', 'RTM Cumulative Update 7'), + (10, 1812, 'RTM CU6', 'https://support.microsoft.com/en-us/help/971490', '2009-07-20', '2014-07-08', '2019-07-09', 'SQL Server 2008', 'RTM Cumulative Update 6'), + (10, 1806, 'RTM CU5', 'https://support.microsoft.com/en-us/help/969531', '2009-05-18', '2014-07-08', '2019-07-09', 'SQL Server 2008', 'RTM Cumulative Update 5'), + (10, 1798, 'RTM CU4', 'https://support.microsoft.com/en-us/help/963036', '2009-03-16', '2014-07-08', '2019-07-09', 'SQL Server 2008', 'RTM Cumulative Update 4'), + (10, 1787, 'RTM CU3', 'https://support.microsoft.com/en-us/help/960484', '2009-01-19', '2014-07-08', '2019-07-09', 'SQL Server 2008', 'RTM Cumulative Update 3'), + (10, 1779, 'RTM CU2', 'https://support.microsoft.com/en-us/help/958186', '2008-11-19', '2014-07-08', '2019-07-09', 'SQL Server 2008', 'RTM Cumulative Update 2'), + (10, 1763, 'RTM CU1', 'https://support.microsoft.com/en-us/help/956717', '2008-09-22', '2014-07-08', '2019-07-09', 'SQL Server 2008', 'RTM Cumulative Update 1'), + (10, 1600, 'RTM ', '', '2008-08-06', '2014-07-08', '2019-07-09', 'SQL Server 2008', 'RTM ') +; +GO +IF OBJECT_ID('dbo.sp_BlitzFirst') IS NULL + EXEC ('CREATE PROCEDURE dbo.sp_BlitzFirst AS RETURN 0;'); +GO -END; -IF (@ExportToExcel = 0 AND @SkipXML = 1) +ALTER PROCEDURE [dbo].[sp_BlitzFirst] + @LogMessage NVARCHAR(4000) = NULL , + @Help TINYINT = 0 , + @AsOf DATETIMEOFFSET = NULL , + @ExpertMode TINYINT = 0 , + @Seconds INT = 5 , + @OutputType VARCHAR(20) = 'TABLE' , + @OutputServerName NVARCHAR(256) = NULL , + @OutputDatabaseName NVARCHAR(256) = NULL , + @OutputSchemaName NVARCHAR(256) = NULL , + @OutputTableName NVARCHAR(256) = NULL , + @OutputTableNameFileStats NVARCHAR(256) = NULL , + @OutputTableNamePerfmonStats NVARCHAR(256) = NULL , + @OutputTableNameWaitStats NVARCHAR(256) = NULL , + @OutputTableNameBlitzCache NVARCHAR(256) = NULL , + @OutputTableNameBlitzWho NVARCHAR(256) = NULL , + @OutputTableRetentionDays TINYINT = 7 , + @OutputXMLasNVARCHAR TINYINT = 0 , + @FilterPlansByDatabase VARCHAR(MAX) = NULL , + @CheckProcedureCache TINYINT = 0 , + @CheckServerInfo TINYINT = 1 , + @FileLatencyThresholdMS INT = 100 , + @SinceStartup TINYINT = 0 , + @ShowSleepingSPIDs TINYINT = 0 , + @BlitzCacheSkipAnalysis BIT = 1 , + @MemoryGrantThresholdPct DECIMAL(5,2) = 15.00, + @LogMessageCheckID INT = 38, + @LogMessagePriority TINYINT = 1, + @LogMessageFindingsGroup VARCHAR(50) = 'Logged Message', + @LogMessageFinding VARCHAR(200) = 'Logged from sp_BlitzFirst', + @LogMessageURL VARCHAR(200) = '', + @LogMessageCheckDate DATETIMEOFFSET = NULL, + @Debug BIT = 0, + @Version VARCHAR(30) = NULL OUTPUT, + @VersionDate DATETIME = NULL OUTPUT, + @VersionCheckMode BIT = 0 + WITH EXECUTE AS CALLER, RECOMPILE +AS BEGIN +SET NOCOUNT ON; +SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -RAISERROR(N'Returning results for skipped XML', 0, 1) WITH NOWAIT; - -WITH x AS ( -SELECT wpt.database_name, wm.plan_id, wm.query_id, wm.query_id_all_plan_ids, wpt.query_sql_text, wpt.query_plan_xml, wpt.pattern, - wm.parameter_sniffing_symptoms, wpt.top_three_waits, wm.count_executions, wm.count_compiles, wm.total_cpu_time, wm.avg_cpu_time, - wm.total_duration, wm.avg_duration, wm.total_logical_io_reads, wm.avg_logical_io_reads, - wm.total_physical_io_reads, wm.avg_physical_io_reads, wm.total_logical_io_writes, wm.avg_logical_io_writes, wm.total_rowcount, wm.avg_rowcount, - wm.total_query_max_used_memory, wm.avg_query_max_used_memory, wm.total_tempdb_space_used, wm.avg_tempdb_space_used, - wm.total_log_bytes_used, wm.avg_log_bytes_used, wm.total_num_physical_io_reads, wm.avg_num_physical_io_reads, - wm.first_execution_time, wm.last_execution_time, wpt.last_force_failure_reason_desc, wpt.context_settings, ROW_NUMBER() OVER (PARTITION BY wm.plan_id, wm.query_id, wm.last_execution_time ORDER BY wm.plan_id) AS rn -FROM #working_plan_text AS wpt -JOIN #working_metrics AS wm - ON wpt.plan_id = wm.plan_id - AND wpt.query_id = wm.query_id -) -SELECT * -FROM x -WHERE x.rn = 1 -ORDER BY x.last_execution_time -OPTION (RECOMPILE); - -END; +SELECT @Version = '8.01', @VersionDate = '20210222'; +IF(@VersionCheckMode = 1) +BEGIN + RETURN; END; -END TRY -BEGIN CATCH - RAISERROR (N'Failure returning results', 0,1) WITH NOWAIT; - - IF @sql_select IS NOT NULL - BEGIN - SET @msg = N'Last @sql_select: ' + @sql_select; - RAISERROR(@msg, 0, 1) WITH NOWAIT; - END; - - SELECT @msg = @DatabaseName + N' database failed to process. ' + ERROR_MESSAGE(), @error_severity = ERROR_SEVERITY(), @error_state = ERROR_STATE(); - RAISERROR (@msg, @error_severity, @error_state) WITH NOWAIT; - - - WHILE @@TRANCOUNT > 0 - ROLLBACK; - - RETURN; -END CATCH; -BEGIN TRY -BEGIN - -IF (@ExportToExcel = 0 AND @HideSummary = 0 AND @SkipXML = 0) +IF @Help = 1 BEGIN - RAISERROR('Building query plan summary data.', 0, 1) WITH NOWAIT; - - /* Build summary data */ - IF EXISTS (SELECT 1/0 - FROM #working_warnings - WHERE frequent_execution = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 1, - 100, - 'Execution Pattern', - 'Frequently Executed Queries', - 'http://brentozar.com/blitzcache/frequently-executed-queries/', - 'Queries are being executed more than ' - + CAST (@execution_threshold AS VARCHAR(5)) - + ' times per minute. This can put additional load on the server, even when queries are lightweight.') ; - - IF EXISTS (SELECT 1/0 - FROM #working_warnings - WHERE parameter_sniffing = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 2, - 50, - 'Parameterization', - 'Parameter Sniffing', - 'http://brentozar.com/blitzcache/parameter-sniffing/', - 'There are signs of parameter sniffing (wide variance in rows return or time to execute). Investigate query patterns and tune code appropriately.') ; - - /* Forced execution plans */ - IF EXISTS (SELECT 1/0 - FROM #working_warnings - WHERE is_forced_plan = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 3, - 5, - 'Parameterization', - 'Forced Plans', - 'http://brentozar.com/blitzcache/forced-plans/', - 'Execution plans have been compiled with forced plans, either through FORCEPLAN, plan guides, or forced parameterization. This will make general tuning efforts less effective.'); - - IF EXISTS (SELECT 1/0 - FROM #working_warnings - WHERE is_cursor = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 4, - 200, - 'Cursors', - 'Cursors', - 'http://brentozar.com/blitzcache/cursors-found-slow-queries/', - 'There are cursors in the plan cache. This is neither good nor bad, but it is a thing. Cursors are weird in SQL Server.'); - - IF EXISTS (SELECT 1/0 - FROM #working_warnings - WHERE is_cursor = 1 - AND is_optimistic_cursor = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 4, - 200, - 'Cursors', - 'Optimistic Cursors', - 'http://brentozar.com/blitzcache/cursors-found-slow-queries/', - 'There are optimistic cursors in the plan cache, which can harm performance.'); - - IF EXISTS (SELECT 1/0 - FROM #working_warnings - WHERE is_cursor = 1 - AND is_forward_only_cursor = 0 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 4, - 200, - 'Cursors', - 'Non-forward Only Cursors', - 'http://brentozar.com/blitzcache/cursors-found-slow-queries/', - 'There are non-forward only cursors in the plan cache, which can harm performance.'); - - IF EXISTS (SELECT 1/0 - FROM #working_warnings - WHERE is_cursor = 1 - AND is_cursor_dynamic = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (4, - 200, - 'Cursors', - 'Dynamic Cursors', - 'http://brentozar.com/blitzcache/cursors-found-slow-queries/', - 'Dynamic Cursors inhibit parallelism!.'); - - IF EXISTS (SELECT 1/0 - FROM #working_warnings - WHERE is_cursor = 1 - AND is_fast_forward_cursor = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (4, - 200, - 'Cursors', - 'Fast Forward Cursors', - 'http://brentozar.com/blitzcache/cursors-found-slow-queries/', - 'Fast forward cursors inhibit parallelism!.'); - - IF EXISTS (SELECT 1/0 - FROM #working_warnings - WHERE is_forced_parameterized = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 5, - 50, - 'Parameterization', - 'Forced Parameterization', - 'http://brentozar.com/blitzcache/forced-parameterization/', - 'Execution plans have been compiled with forced parameterization.') ; - - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.is_parallel = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 6, - 200, - 'Execution Plans', - 'Parallelism', - 'http://brentozar.com/blitzcache/parallel-plans-detected/', - 'Parallel plans detected. These warrant investigation, but are neither good nor bad.') ; - - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.near_parallel = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 7, - 200, - 'Execution Plans', - 'Nearly Parallel', - 'http://brentozar.com/blitzcache/query-cost-near-cost-threshold-parallelism/', - 'Queries near the cost threshold for parallelism. These may go parallel when you least expect it.') ; - - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.plan_warnings = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 8, - 50, - 'Execution Plans', - 'Query Plan Warnings', - 'http://brentozar.com/blitzcache/query-plan-warnings/', - 'Warnings detected in execution plans. SQL Server is telling you that something bad is going on that requires your attention.') ; - - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.long_running = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 9, - 50, - 'Performance', - 'Long Running Queries', - 'http://brentozar.com/blitzcache/long-running-queries/', - 'Long running queries have been found. These are queries with an average duration longer than ' - + CAST(@long_running_query_warning_seconds / 1000 / 1000 AS VARCHAR(5)) - + ' second(s). These queries should be investigated for additional tuning options.') ; - - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.missing_index_count > 0 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 10, - 50, - 'Performance', - 'Missing Index Request', - 'http://brentozar.com/blitzcache/missing-index-request/', - 'Queries found with missing indexes.'); - - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.downlevel_estimator = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 13, - 200, - 'Cardinality', - 'Legacy Cardinality Estimator in Use', - 'http://brentozar.com/blitzcache/legacy-cardinality-estimator/', - 'A legacy cardinality estimator is being used by one or more queries. Investigate whether you need to be using this cardinality estimator. This may be caused by compatibility levels, global trace flags, or query level trace flags.'); - - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.implicit_conversions = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 14, - 50, - 'Performance', - 'Implicit Conversions', - 'http://brentozar.com/go/implicit', - 'One or more queries are comparing two fields that are not of the same data type.') ; - - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE busy_loops = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 16, - 100, - 'Performance', - 'Busy Loops', - 'http://brentozar.com/blitzcache/busy-loops/', - 'Operations have been found that are executed 100 times more often than the number of rows returned by each iteration. This is an indicator that something is off in query execution.'); - - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE tvf_join = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 17, - 50, - 'Performance', - 'Joining to table valued functions', - 'http://brentozar.com/blitzcache/tvf-join/', - 'Execution plans have been found that join to table valued functions (TVFs). TVFs produce inaccurate estimates of the number of rows returned and can lead to any number of query plan problems.'); - - IF EXISTS (SELECT 1/0 - FROM #working_warnings - WHERE compile_timeout = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 18, - 50, - 'Execution Plans', - 'Compilation timeout', - 'http://brentozar.com/blitzcache/compilation-timeout/', - 'Query compilation timed out for one or more queries. SQL Server did not find a plan that meets acceptable performance criteria in the time allotted so the best guess was returned. There is a very good chance that this plan isn''t even below average - it''s probably terrible.'); - - IF EXISTS (SELECT 1/0 - FROM #working_warnings - WHERE compile_memory_limit_exceeded = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 19, - 50, - 'Execution Plans', - 'Compilation memory limit exceeded', - 'http://brentozar.com/blitzcache/compile-memory-limit-exceeded/', - 'The optimizer has a limited amount of memory available. One or more queries are complex enough that SQL Server was unable to allocate enough memory to fully optimize the query. A best fit plan was found, and it''s probably terrible.'); - - IF EXISTS (SELECT 1/0 - FROM #working_warnings - WHERE warning_no_join_predicate = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 20, - 10, - 'Execution Plans', - 'No join predicate', - 'http://brentozar.com/blitzcache/no-join-predicate/', - 'Operators in a query have no join predicate. This means that all rows from one table will be matched with all rows from anther table producing a Cartesian product. That''s a whole lot of rows. This may be your goal, but it''s important to investigate why this is happening.'); +PRINT ' +sp_BlitzFirst from http://FirstResponderKit.org + +This script gives you a prioritized list of why your SQL Server is slow right now. - IF EXISTS (SELECT 1/0 - FROM #working_warnings - WHERE plan_multiple_plans = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 21, - 200, - 'Execution Plans', - 'Multiple execution plans', - 'http://brentozar.com/blitzcache/multiple-plans/', - 'Queries exist with multiple execution plans (as determined by query_plan_hash). Investigate possible ways to parameterize these queries or otherwise reduce the plan count.'); +This is not an overall health check - for that, check out sp_Blitz. - IF EXISTS (SELECT 1/0 - FROM #working_warnings - WHERE unmatched_index_count > 0 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 22, - 100, - 'Performance', - 'Unmatched indexes', - 'http://brentozar.com/blitzcache/unmatched-indexes', - 'An index could have been used, but SQL Server chose not to use it - likely due to parameterization and filtered indexes.'); +To learn more, visit http://FirstResponderKit.org where you can download new +versions for free, watch training videos on how it works, get more info on +the findings, contribute your own code, and more. - IF EXISTS (SELECT 1/0 - FROM #working_warnings - WHERE unparameterized_query = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 23, - 100, - 'Parameterization', - 'Unparameterized queries', - 'http://brentozar.com/blitzcache/unparameterized-queries', - 'Unparameterized queries found. These could be ad hoc queries, data exploration, or queries using "OPTIMIZE FOR UNKNOWN".'); +Known limitations of this version: + - Only Microsoft-supported versions of SQL Server. Sorry, 2005 and 2000. It + may work just fine on 2005, and if it does, hug your parents. Just don''t + file support issues if it breaks. + - If a temp table called #CustomPerfmonCounters exists for any other session, + but not our session, this stored proc will fail with an error saying the + temp table #CustomPerfmonCounters does not exist. + - @OutputServerName is not functional yet. + - If @OutputDatabaseName, SchemaName, TableName, etc are quoted with brackets, + the write to table may silently fail. Look, I never said I was good at this. - IF EXISTS (SELECT 1/0 - FROM #working_warnings - WHERE is_trivial = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 24, - 100, - 'Execution Plans', - 'Trivial Plans', - 'http://brentozar.com/blitzcache/trivial-plans', - 'Trivial plans get almost no optimization. If you''re finding these in the top worst queries, something may be going wrong.'); - - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.is_forced_serial= 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 25, - 10, - 'Execution Plans', - 'Forced Serialization', - 'http://www.brentozar.com/blitzcache/forced-serialization/', - 'Something in your plan is forcing a serial query. Further investigation is needed if this is not by design.') ; +Unknown limitations of this version: + - None. Like Zombo.com, the only limit is yourself. - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.is_key_lookup_expensive= 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 26, - 100, - 'Execution Plans', - 'Expensive Key Lookups', - 'http://www.brentozar.com/blitzcache/expensive-key-lookups/', - 'There''s a key lookup in your plan that costs >=50% of the total plan cost.') ; +Changes - for the full list of improvements and fixes in this version, see: +https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/ - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.is_remote_query_expensive= 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 28, - 100, - 'Execution Plans', - 'Expensive Remote Query', - 'http://www.brentozar.com/blitzcache/expensive-remote-query/', - 'There''s a remote query in your plan that costs >=50% of the total plan cost.') ; - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.trace_flags_session IS NOT NULL - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 29, - 100, - 'Trace Flags', - 'Session Level Trace Flags Enabled', - 'https://www.brentozar.com/blitz/trace-flags-enabled-globally/', - 'Someone is enabling session level Trace Flags in a query.') ; +MIT License - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.is_unused_grant IS NOT NULL - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 30, - 100, - 'Unused memory grants', - 'Queries are asking for more memory than they''re using', - 'https://www.brentozar.com/blitzcache/unused-memory-grants/', - 'Queries have large unused memory grants. This can cause concurrency issues, if queries are waiting a long time to get memory to run.') ; +Copyright (c) 2021 Brent Ozar Unlimited - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.function_count > 0 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 31, - 100, - 'Compute Scalar That References A Function', - 'This could be trouble if you''re using Scalar Functions or MSTVFs', - 'https://www.brentozar.com/blitzcache/compute-scalar-functions/', - 'Both of these will force queries to run serially, run at least once per row, and may result in poor cardinality estimates.') ; +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.clr_function_count > 0 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 32, - 100, - 'Compute Scalar That References A CLR Function', - 'This could be trouble if your CLR functions perform data access', - 'https://www.brentozar.com/blitzcache/compute-scalar-functions/', - 'May force queries to run serially, run at least once per row, and may result in poor cardinlity estimates.') ; +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.is_table_variable = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 33, - 100, - 'Table Variables detected', - 'Beware nasty side effects', - 'https://www.brentozar.com/blitzcache/table-variables/', - 'All modifications are single threaded, and selects have really low row estimates.') ; +'; +RETURN; +END; /* @Help = 1 */ - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.no_stats_warning = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 35, - 100, - 'Columns with no statistics', - 'Poor cardinality estimates may ensue', - 'https://www.brentozar.com/blitzcache/columns-no-statistics/', - 'Sometimes this happens with indexed views, other times because auto create stats is turned off.') ; +RAISERROR('Setting up configuration variables',10,1) WITH NOWAIT; +DECLARE @StringToExecute NVARCHAR(MAX), + @ParmDefinitions NVARCHAR(4000), + @Parm1 NVARCHAR(4000), + @OurSessionID INT, + @LineFeed NVARCHAR(10), + @StockWarningHeader NVARCHAR(MAX) = N'', + @StockWarningFooter NVARCHAR(MAX) = N'', + @StockDetailsHeader NVARCHAR(MAX) = N'', + @StockDetailsFooter NVARCHAR(MAX) = N'', + @StartSampleTime DATETIMEOFFSET, + @FinishSampleTime DATETIMEOFFSET, + @FinishSampleTimeWaitFor DATETIME, + @AsOf1 DATETIMEOFFSET, + @AsOf2 DATETIMEOFFSET, + @ServiceName sysname, + @OutputTableNameFileStats_View NVARCHAR(256), + @OutputTableNamePerfmonStats_View NVARCHAR(256), + @OutputTableNamePerfmonStatsActuals_View NVARCHAR(256), + @OutputTableNameWaitStats_View NVARCHAR(256), + @OutputTableNameWaitStats_Categories NVARCHAR(256), + @OutputTableCleanupDate DATE, + @ObjectFullName NVARCHAR(2000), + @BlitzWho NVARCHAR(MAX) = N'EXEC dbo.sp_BlitzWho @ShowSleepingSPIDs = ' + CONVERT(NVARCHAR(1), @ShowSleepingSPIDs) + N';', + @BlitzCacheMinutesBack INT, + @UnquotedOutputServerName NVARCHAR(256) = @OutputServerName , + @UnquotedOutputDatabaseName NVARCHAR(256) = @OutputDatabaseName , + @UnquotedOutputSchemaName NVARCHAR(256) = @OutputSchemaName , + @LocalServerName NVARCHAR(128) = CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128)), + @dm_exec_query_statistics_xml BIT = 0; - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.relop_warnings = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 36, - 100, - 'Operator Warnings', - 'SQL is throwing operator level plan warnings', - 'http://brentozar.com/blitzcache/query-plan-warnings/', - 'Check the plan for more details.') ; +/* Sanitize our inputs */ +SELECT + @OutputTableNameFileStats_View = QUOTENAME(@OutputTableNameFileStats + '_Deltas'), + @OutputTableNamePerfmonStats_View = QUOTENAME(@OutputTableNamePerfmonStats + '_Deltas'), + @OutputTableNamePerfmonStatsActuals_View = QUOTENAME(@OutputTableNamePerfmonStats + '_Actuals'), + @OutputTableNameWaitStats_View = QUOTENAME(@OutputTableNameWaitStats + '_Deltas'), + @OutputTableNameWaitStats_Categories = QUOTENAME(@OutputTableNameWaitStats + '_Categories'); - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.is_table_scan = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 37, - 100, - 'Table Scans', - 'Your database has HEAPs', - 'https://www.brentozar.com/archive/2012/05/video-heaps/', - 'This may not be a problem. Run sp_BlitzIndex for more information.') ; - - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.backwards_scan = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 38, - 100, - 'Backwards Scans', - 'Indexes are being read backwards', - 'https://www.brentozar.com/blitzcache/backwards-scans/', - 'This isn''t always a problem. They can cause serial zones in plans, and may need an index to match sort order.') ; +SELECT + @OutputDatabaseName = QUOTENAME(@OutputDatabaseName), + @OutputSchemaName = QUOTENAME(@OutputSchemaName), + @OutputTableName = QUOTENAME(@OutputTableName), + @OutputTableNameFileStats = QUOTENAME(@OutputTableNameFileStats), + @OutputTableNamePerfmonStats = QUOTENAME(@OutputTableNamePerfmonStats), + @OutputTableNameWaitStats = QUOTENAME(@OutputTableNameWaitStats), + @OutputTableCleanupDate = CAST( (DATEADD(DAY, -1 * @OutputTableRetentionDays, GETDATE() ) ) AS DATE), + /* @OutputTableNameBlitzCache = QUOTENAME(@OutputTableNameBlitzCache), We purposely don't sanitize this because sp_BlitzCache will */ + /* @OutputTableNameBlitzWho = QUOTENAME(@OutputTableNameBlitzWho), We purposely don't sanitize this because sp_BlitzWho will */ + @LineFeed = CHAR(13) + CHAR(10), + @OurSessionID = @@SPID, + @OutputType = UPPER(@OutputType); - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.forced_index = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 39, - 100, - 'Index forcing', - 'Someone is using hints to force index usage', - 'https://www.brentozar.com/blitzcache/optimizer-forcing/', - 'This can cause inefficient plans, and will prevent missing index requests.') ; +IF(@OutputType = 'NONE' AND (@OutputTableName IS NULL OR @OutputSchemaName IS NULL OR @OutputDatabaseName IS NULL)) +BEGIN + RAISERROR('This procedure should be called with a value for all @Output* parameters, as @OutputType is set to NONE',12,1); + RETURN; +END; - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.forced_seek = 1 - OR p.forced_scan = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 40, - 100, - 'Seek/Scan forcing', - 'Someone is using hints to force index seeks/scans', - 'https://www.brentozar.com/blitzcache/optimizer-forcing/', - 'This can cause inefficient plans by taking seek vs scan choice away from the optimizer.') ; +IF UPPER(@OutputType) LIKE 'TOP 10%' SET @OutputType = 'Top10'; +IF @OutputType = 'Top10' SET @SinceStartup = 1; - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.columnstore_row_mode = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 41, - 100, - 'ColumnStore indexes operating in Row Mode', - 'Batch Mode is optimal for ColumnStore indexes', - 'https://www.brentozar.com/blitzcache/columnstore-indexes-operating-row-mode/', - 'ColumnStore indexes operating in Row Mode indicate really poor query choices.') ; +/* Logged Message - CheckID 38 */ +IF @LogMessage IS NOT NULL + BEGIN - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.is_computed_scalar = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 42, - 50, - 'Computed Columns Referencing Scalar UDFs', - 'This makes a whole lot of stuff run serially', - 'https://www.brentozar.com/blitzcache/computed-columns-referencing-functions/', - 'This can cause a whole mess of bad serializartion problems.') ; + RAISERROR('Saving LogMessage to table',10,1) WITH NOWAIT; - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.is_sort_expensive = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 43, - 100, - 'Execution Plans', - 'Expensive Sort', - 'http://www.brentozar.com/blitzcache/expensive-sorts/', - 'There''s a sort in your plan that costs >=50% of the total plan cost.') ; + /* Try to set the output table parameters if they don't exist */ + IF @OutputSchemaName IS NULL AND @OutputTableName IS NULL AND @OutputDatabaseName IS NULL + BEGIN + SET @OutputSchemaName = N'[dbo]'; + SET @OutputTableName = N'[BlitzFirst]'; - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.is_computed_filter = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 44, - 50, - 'Filters Referencing Scalar UDFs', - 'This forces serialization', - 'https://www.brentozar.com/blitzcache/compute-scalar-functions/', - 'Someone put a Scalar UDF in the WHERE clause!') ; + /* Look for the table in the current database */ + SELECT TOP 1 @OutputDatabaseName = QUOTENAME(TABLE_CATALOG) + FROM INFORMATION_SCHEMA.TABLES + WHERE TABLE_SCHEMA = 'dbo' AND TABLE_NAME = 'BlitzFirst'; - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.index_ops >= 5 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 45, - 100, - 'Many Indexes Modified', - 'Write Queries Are Hitting >= 5 Indexes', - 'https://www.brentozar.com/blitzcache/many-indexes-modified/', - 'This can cause lots of hidden I/O -- Run sp_BlitzIndex for more information.') ; + IF @OutputDatabaseName IS NULL AND EXISTS (SELECT * FROM sys.databases WHERE name = 'DBAtools') + SET @OutputDatabaseName = '[DBAtools]'; - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.is_row_level = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 46, - 100, - 'Plan Confusion', - 'Row Level Security is in use', - 'https://www.brentozar.com/blitzcache/row-level-security/', - 'You may see a lot of confusing junk in your query plan.') ; + END; - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.is_spatial = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 47, - 200, - 'Spatial Abuse', - 'You hit a Spatial Index', - 'https://www.brentozar.com/blitzcache/spatial-indexes/', - 'Purely informational.') ; + IF @OutputDatabaseName IS NULL OR @OutputSchemaName IS NULL OR @OutputTableName IS NULL + OR NOT EXISTS ( SELECT * + FROM sys.databases + WHERE QUOTENAME([name]) = @OutputDatabaseName) + BEGIN + RAISERROR('We have a hard time logging a message without a valid @OutputDatabaseName, @OutputSchemaName, and @OutputTableName to log it to.', 0, 1) WITH NOWAIT; + RETURN; + END; + IF @LogMessageCheckDate IS NULL + SET @LogMessageCheckDate = SYSDATETIMEOFFSET(); + SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' + + @OutputDatabaseName + + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' + + @OutputSchemaName + ''') INSERT ' + + @OutputDatabaseName + '.' + + @OutputSchemaName + '.' + + @OutputTableName + + ' (ServerName, CheckDate, CheckID, Priority, FindingsGroup, Finding, Details, URL) VALUES( ' + + ' @SrvName, @LogMessageCheckDate, @LogMessageCheckID, @LogMessagePriority, @LogMessageFindingsGroup, @LogMessageFinding, @LogMessage, @LogMessageURL)'; - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.index_dml = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 48, - 150, - 'Index DML', - 'Indexes were created or dropped', - 'https://www.brentozar.com/blitzcache/index-dml/', - 'This can cause recompiles and stuff.') ; + EXECUTE sp_executesql @StringToExecute, + N'@SrvName NVARCHAR(128), @LogMessageCheckID INT, @LogMessagePriority TINYINT, @LogMessageFindingsGroup VARCHAR(50), @LogMessageFinding VARCHAR(200), @LogMessage NVARCHAR(4000), @LogMessageCheckDate DATETIMEOFFSET, @LogMessageURL VARCHAR(200)', + @LocalServerName, @LogMessageCheckID, @LogMessagePriority, @LogMessageFindingsGroup, @LogMessageFinding, @LogMessage, @LogMessageCheckDate, @LogMessageURL; - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.table_dml = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 49, - 150, - 'Table DML', - 'Tables were created or dropped', - 'https://www.brentozar.com/blitzcache/table-dml/', - 'This can cause recompiles and stuff.') ; + RAISERROR('LogMessage saved to table. We have made a note of your activity. Keep up the good work.',10,1) WITH NOWAIT; - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.long_running_low_cpu = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 50, - 150, - 'Long Running Low CPU', - 'You have a query that runs for much longer than it uses CPU', - 'https://www.brentozar.com/blitzcache/long-running-low-cpu/', - 'This can be a sign of blocking, linked servers, or poor client application code (ASYNC_NETWORK_IO).') ; + RETURN; + END; - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.low_cost_high_cpu = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 51, - 150, - 'Low Cost Query With High CPU', - 'You have a low cost query that uses a lot of CPU', - 'https://www.brentozar.com/blitzcache/low-cost-high-cpu/', - 'This can be a sign of functions or Dynamic SQL that calls black-box code.') ; +IF @SinceStartup = 1 + SELECT @Seconds = 0, @ExpertMode = 1; - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.stale_stats = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 52, - 150, - 'Biblical Statistics', - 'Statistics used in queries are >7 days old with >100k modifications', - 'https://www.brentozar.com/blitzcache/stale-statistics/', - 'Ever heard of updating statistics?') ; - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.is_adaptive = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 53, - 150, - 'Adaptive joins', - 'This is pretty cool -- you''re living in the future.', - 'https://www.brentozar.com/blitzcache/adaptive-joins/', - 'Joe Sack rules.') ; +IF @OutputType = 'SCHEMA' +BEGIN + SELECT FieldList = '[Priority] TINYINT, [FindingsGroup] VARCHAR(50), [Finding] VARCHAR(200), [URL] VARCHAR(200), [Details] NVARCHAR(4000), [HowToStopIt] NVARCHAR(MAX), [QueryPlan] XML, [QueryText] NVARCHAR(MAX)'; - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.is_spool_expensive = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 54, - 150, - 'Expensive Index Spool', - 'You have an index spool, this is usually a sign that there''s an index missing somewhere.', - 'https://www.brentozar.com/blitzcache/eager-index-spools/', - 'Check operator predicates and output for index definition guidance') ; +END; +ELSE IF @AsOf IS NOT NULL AND @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @OutputTableName IS NOT NULL +BEGIN + /* They want to look into the past. */ + SET @AsOf1= DATEADD(mi, -15, @AsOf); + SET @AsOf2= DATEADD(mi, +15, @AsOf); - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.is_spool_more_rows = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 55, - 150, - 'Index Spools Many Rows', - 'You have an index spool that spools more rows than the query returns', - 'https://www.brentozar.com/blitzcache/eager-index-spools/', - 'Check operator predicates and output for index definition guidance') ; + SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' + + @OutputDatabaseName + + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' + + @OutputSchemaName + ''') SELECT CheckDate, [Priority], [FindingsGroup], [Finding], [URL], CAST([Details] AS [XML]) AS Details,' + + '[HowToStopIt], [CheckID], [StartTime], [LoginName], [NTUserName], [OriginalLoginName], [ProgramName], [HostName], [DatabaseID],' + + '[DatabaseName], [OpenTransactionCount], [QueryPlan], [QueryText] FROM ' + + @OutputDatabaseName + '.' + + @OutputSchemaName + '.' + + @OutputTableName + + ' WHERE CheckDate >= @AsOf1' + + ' AND CheckDate <= @AsOf2' + + ' /*ORDER BY CheckDate, Priority , FindingsGroup , Finding , Details*/;'; + EXEC sp_executesql @StringToExecute, + N'@AsOf1 DATETIMEOFFSET, @AsOf2 DATETIMEOFFSET', + @AsOf1, @AsOf2 - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.is_bad_estimate = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 56, - 100, - 'Potentially bad cardinality estimates', - 'Estimated rows are different from average rows by a factor of 10000', - 'https://www.brentozar.com/blitzcache/bad-estimates/', - 'This may indicate a performance problem if mismatches occur regularly') ; - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.is_big_log = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 57, - 100, - 'High transaction log use', - 'This query on average uses more than half of the transaction log', - 'http://michaeljswart.com/2014/09/take-care-when-scripting-batches/', - 'This is probably a sign that you need to start batching queries') ; - - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.is_big_tempdb = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 58, - 100, - 'High tempdb use', - 'This query uses more than half of a data file on average', - 'No URL yet', - 'You should take a look at tempdb waits to see if you''re having problems') ; - - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.is_row_goal = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (59, - 200, - 'Row Goals', - 'This query had row goals introduced', - 'https://www.brentozar.com/archive/2018/01/sql-server-2017-cu3-adds-optimizer-row-goal-information-query-plans/', - 'This can be good or bad, and should be investigated for high read queries') ; +END; /* IF @AsOf IS NOT NULL AND @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @OutputTableName IS NOT NULL */ +ELSE IF @LogMessage IS NULL /* IF @OutputType = 'SCHEMA' */ +BEGIN + /* What's running right now? This is the first and last result set. */ + IF @SinceStartup = 0 AND @Seconds > 0 AND @ExpertMode = 1 AND @OutputType <> 'NONE' + BEGIN + IF OBJECT_ID('master.dbo.sp_BlitzWho') IS NULL AND OBJECT_ID('dbo.sp_BlitzWho') IS NULL + BEGIN + PRINT N'sp_BlitzWho is not installed in the current database_files. You can get a copy from http://FirstResponderKit.org'; + END; + ELSE + BEGIN + EXEC (@BlitzWho); + END; + END; /* IF @SinceStartup = 0 AND @Seconds > 0 AND @ExpertMode = 1 AND @OutputType <> 'NONE' - What's running right now? This is the first and last result set. */ + + /* Set start/finish times AFTER sp_BlitzWho runs. For more info: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2244 */ + IF @Seconds = 0 AND SERVERPROPERTY('Edition') = 'SQL Azure' + WITH WaitTimes AS ( + SELECT wait_type, wait_time_ms, + NTILE(3) OVER(ORDER BY wait_time_ms) AS grouper + FROM sys.dm_os_wait_stats w + WHERE wait_type IN ('DIRTY_PAGE_POLL','HADR_FILESTREAM_IOMGR_IOCOMPLETION','LAZYWRITER_SLEEP', + 'LOGMGR_QUEUE','REQUEST_FOR_DEADLOCK_SEARCH','XE_TIMER_EVENT') + ) + SELECT @StartSampleTime = DATEADD(mi, AVG(-wait_time_ms / 1000 / 60), SYSDATETIMEOFFSET()), @FinishSampleTime = SYSDATETIMEOFFSET() + FROM WaitTimes + WHERE grouper = 2; + ELSE IF @Seconds = 0 AND SERVERPROPERTY('Edition') <> 'SQL Azure' + SELECT @StartSampleTime = DATEADD(MINUTE,DATEDIFF(MINUTE, GETDATE(), GETUTCDATE()),create_date) , @FinishSampleTime = SYSDATETIMEOFFSET() + FROM sys.databases + WHERE database_id = 2; + ELSE + SELECT @StartSampleTime = SYSDATETIMEOFFSET(), + @FinishSampleTime = DATEADD(ss, @Seconds, SYSDATETIMEOFFSET()), + @FinishSampleTimeWaitFor = DATEADD(ss, @Seconds, GETDATE()); - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.is_mstvf = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 60, - 100, - 'MSTVFs', - 'These have many of the same problems scalar UDFs have', - 'http://brentozar.com/blitzcache/tvf-join/', - 'Execution plans have been found that join to table valued functions (TVFs). TVFs produce inaccurate estimates of the number of rows returned and can lead to any number of query plan problems.'); - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.is_mstvf = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (61, - 100, - 'Many to Many Merge', - 'These use secret worktables that could be doing lots of reads', - 'https://www.brentozar.com/archive/2018/04/many-mysteries-merge-joins/', - 'Occurs when join inputs aren''t known to be unique. Can be really bad when parallel.'); + RAISERROR('Now starting diagnostic analysis',10,1) WITH NOWAIT; - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.is_nonsargable = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (62, - 50, - 'Non-SARGable queries', - 'Queries may be using', - 'https://www.brentozar.com/blitzcache/non-sargable-predicates/', - 'Occurs when join inputs aren''t known to be unique. Can be really bad when parallel.'); - - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.is_paul_white_electric = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 998, - 200, - 'Is Paul White Electric?', - 'This query has a Switch operator in it!', - 'https://www.sql.kiwi/2013/06/hello-operator-my-switch-is-bored.html', - 'You should email this query plan to Paul: SQLkiwi at gmail dot com') ; - - - - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - SELECT - 999, - 200, - 'Database Level Statistics', - 'The database ' + sa.[database] + ' last had a stats update on ' + CONVERT(NVARCHAR(10), CONVERT(DATE, MAX(sa.last_update))) + ' and has ' + CONVERT(NVARCHAR(10), AVG(sa.modification_count)) + ' modifications on average.' AS Finding, - 'https://www.brentozar.com/blitzcache/stale-statistics/' AS URL, - 'Consider updating statistics more frequently,' AS Details - FROM #stats_agg AS sa - GROUP BY sa.[database] - HAVING MAX(sa.last_update) <= DATEADD(DAY, -7, SYSDATETIME()) - AND AVG(sa.modification_count) >= 100000; + /* + We start by creating #BlitzFirstResults. It's a temp table that will store + the results from our checks. Throughout the rest of this stored procedure, + we're running a series of checks looking for dangerous things inside the SQL + Server. When we find a problem, we insert rows into the temp table. At the + end, we return these results to the end user. - - IF EXISTS (SELECT 1/0 - FROM #trace_flags AS tf - WHERE tf.global_trace_flags IS NOT NULL - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 1000, - 255, - 'Global Trace Flags Enabled', - 'You have Global Trace Flags enabled on your server', - 'https://www.brentozar.com/blitz/trace-flags-enabled-globally/', - 'You have the following Global Trace Flags enabled: ' + (SELECT TOP 1 tf.global_trace_flags FROM #trace_flags AS tf WHERE tf.global_trace_flags IS NOT NULL)) ; + #BlitzFirstResults has a CheckID field, but there's no Check table. As we do + checks, we insert data into this table, and we manually put in the CheckID. + We (Brent Ozar Unlimited) maintain a list of the checks by ID#. You can + download that from http://FirstResponderKit.org if you want to build + a tool that relies on the output of sp_BlitzFirst. + */ - /* - Return worsts - */ - WITH worsts AS ( - SELECT gi.flat_date, - gi.start_range, - gi.end_range, - gi.total_avg_duration_ms, - gi.total_avg_cpu_time_ms, - gi.total_avg_logical_io_reads_mb, - gi.total_avg_physical_io_reads_mb, - gi.total_avg_logical_io_writes_mb, - gi.total_avg_query_max_used_memory_mb, - gi.total_rowcount, - gi.total_avg_log_bytes_mb, - gi.total_avg_tempdb_space, - gi.total_max_duration_ms, - gi.total_max_cpu_time_ms, - gi.total_max_logical_io_reads_mb, - gi.total_max_physical_io_reads_mb, - gi.total_max_logical_io_writes_mb, - gi.total_max_query_max_used_memory_mb, - gi.total_max_log_bytes_mb, - gi.total_max_tempdb_space, - CONVERT(NVARCHAR(20), gi.flat_date) AS worst_date, - CASE WHEN DATEPART(HOUR, gi.start_range) = 0 THEN ' midnight ' - WHEN DATEPART(HOUR, gi.start_range) <= 12 THEN CONVERT(NVARCHAR(3), DATEPART(HOUR, gi.start_range)) + 'am ' - WHEN DATEPART(HOUR, gi.start_range) > 12 THEN CONVERT(NVARCHAR(3), DATEPART(HOUR, gi.start_range) -12) + 'pm ' - END AS worst_start_time, - CASE WHEN DATEPART(HOUR, gi.end_range) = 0 THEN ' midnight ' - WHEN DATEPART(HOUR, gi.end_range) <= 12 THEN CONVERT(NVARCHAR(3), DATEPART(HOUR, gi.end_range)) + 'am ' - WHEN DATEPART(HOUR, gi.end_range) > 12 THEN CONVERT(NVARCHAR(3), DATEPART(HOUR, gi.end_range) -12) + 'pm ' - END AS worst_end_time - FROM #grouped_interval AS gi - ), /*averages*/ - duration_worst AS ( - SELECT TOP 1 'Your worst avg duration range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg - FROM worsts - ORDER BY worsts.total_avg_duration_ms DESC - ), - cpu_worst AS ( - SELECT TOP 1 'Your worst avg cpu range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg - FROM worsts - ORDER BY worsts.total_avg_cpu_time_ms DESC - ), - logical_reads_worst AS ( - SELECT TOP 1 'Your worst avg logical read range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg - FROM worsts - ORDER BY worsts.total_avg_logical_io_reads_mb DESC - ), - physical_reads_worst AS ( - SELECT TOP 1 'Your worst avg physical read range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg - FROM worsts - ORDER BY worsts.total_avg_physical_io_reads_mb DESC - ), - logical_writes_worst AS ( - SELECT TOP 1 'Your worst avg logical write range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg - FROM worsts - ORDER BY worsts.total_avg_logical_io_writes_mb DESC - ), - memory_worst AS ( - SELECT TOP 1 'Your worst avg memory range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg - FROM worsts - ORDER BY worsts.total_avg_query_max_used_memory_mb DESC - ), - rowcount_worst AS ( - SELECT TOP 1 'Your worst avg row count range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg - FROM worsts - ORDER BY worsts.total_rowcount DESC - ), - logbytes_worst AS ( - SELECT TOP 1 'Your worst avg log bytes range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg - FROM worsts - ORDER BY worsts.total_avg_log_bytes_mb DESC - ), - tempdb_worst AS ( - SELECT TOP 1 'Your worst avg tempdb range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg - FROM worsts - ORDER BY worsts.total_avg_tempdb_space DESC - )/*maxes*/, - max_duration_worst AS ( - SELECT TOP 1 'Your worst max duration range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg - FROM worsts - ORDER BY worsts.total_max_duration_ms DESC - ), - max_cpu_worst AS ( - SELECT TOP 1 'Your worst max cpu range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg - FROM worsts - ORDER BY worsts.total_max_cpu_time_ms DESC - ), - max_logical_reads_worst AS ( - SELECT TOP 1 'Your worst max logical read range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg - FROM worsts - ORDER BY worsts.total_max_logical_io_reads_mb DESC - ), - max_physical_reads_worst AS ( - SELECT TOP 1 'Your worst max physical read range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg - FROM worsts - ORDER BY worsts.total_max_physical_io_reads_mb DESC - ), - max_logical_writes_worst AS ( - SELECT TOP 1 'Your worst max logical write range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg - FROM worsts - ORDER BY worsts.total_max_logical_io_writes_mb DESC - ), - max_memory_worst AS ( - SELECT TOP 1 'Your worst max memory range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg - FROM worsts - ORDER BY worsts.total_max_query_max_used_memory_mb DESC - ), - max_logbytes_worst AS ( - SELECT TOP 1 'Your worst max log bytes range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg - FROM worsts - ORDER BY worsts.total_max_log_bytes_mb DESC - ), - max_tempdb_worst AS ( - SELECT TOP 1 'Your worst max tempdb range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg - FROM worsts - ORDER BY worsts.total_max_tempdb_space DESC - ) - INSERT #warning_results ( CheckID, Priority, FindingsGroup, Finding, URL, Details ) - /*averages*/ - SELECT 1002, 255, 'Worsts', 'Worst Avg Duration', 'N/A', duration_worst.msg - FROM duration_worst - UNION ALL - SELECT 1002, 255, 'Worsts', 'Worst Avg CPU', 'N/A', cpu_worst.msg - FROM cpu_worst - UNION ALL - SELECT 1002, 255, 'Worsts', 'Worst Avg Logical Reads', 'N/A', logical_reads_worst.msg - FROM logical_reads_worst - UNION ALL - SELECT 1002, 255, 'Worsts', 'Worst Avg Physical Reads', 'N/A', physical_reads_worst.msg - FROM physical_reads_worst - UNION ALL - SELECT 1002, 255, 'Worsts', 'Worst Avg Logical Writes', 'N/A', logical_writes_worst.msg - FROM logical_writes_worst - UNION ALL - SELECT 1002, 255, 'Worsts', 'Worst Avg Memory', 'N/A', memory_worst.msg - FROM memory_worst - UNION ALL - SELECT 1002, 255, 'Worsts', 'Worst Row Counts', 'N/A', rowcount_worst.msg - FROM rowcount_worst - UNION ALL - SELECT 1002, 255, 'Worsts', 'Worst Avg Log Bytes', 'N/A', logbytes_worst.msg - FROM logbytes_worst - UNION ALL - SELECT 1002, 255, 'Worsts', 'Worst Avg tempdb', 'N/A', tempdb_worst.msg - FROM tempdb_worst - UNION ALL - /*maxes*/ - SELECT 1002, 255, 'Worsts', 'Worst Max Duration', 'N/A', max_duration_worst.msg - FROM max_duration_worst - UNION ALL - SELECT 1002, 255, 'Worsts', 'Worst Max CPU', 'N/A', max_cpu_worst.msg - FROM max_cpu_worst - UNION ALL - SELECT 1002, 255, 'Worsts', 'Worst Max Logical Reads', 'N/A', max_logical_reads_worst.msg - FROM max_logical_reads_worst - UNION ALL - SELECT 1002, 255, 'Worsts', 'Worst Max Physical Reads', 'N/A', max_physical_reads_worst.msg - FROM max_physical_reads_worst - UNION ALL - SELECT 1002, 255, 'Worsts', 'Worst Max Logical Writes', 'N/A', max_logical_writes_worst.msg - FROM max_logical_writes_worst - UNION ALL - SELECT 1002, 255, 'Worsts', 'Worst Max Memory', 'N/A', max_memory_worst.msg - FROM max_memory_worst - UNION ALL - SELECT 1002, 255, 'Worsts', 'Worst Max Log Bytes', 'N/A', max_logbytes_worst.msg - FROM max_logbytes_worst - UNION ALL - SELECT 1002, 255, 'Worsts', 'Worst Max tempdb', 'N/A', max_tempdb_worst.msg - FROM max_tempdb_worst - OPTION (RECOMPILE); + IF OBJECT_ID('tempdb..#BlitzFirstResults') IS NOT NULL + DROP TABLE #BlitzFirstResults; + CREATE TABLE #BlitzFirstResults + ( + ID INT IDENTITY(1, 1) PRIMARY KEY CLUSTERED, + CheckID INT NOT NULL, + Priority TINYINT NOT NULL, + FindingsGroup VARCHAR(50) NOT NULL, + Finding VARCHAR(200) NOT NULL, + URL VARCHAR(200) NULL, + Details NVARCHAR(MAX) NULL, + HowToStopIt NVARCHAR(MAX) NULL, + QueryPlan [XML] NULL, + QueryText NVARCHAR(MAX) NULL, + StartTime DATETIMEOFFSET NULL, + LoginName NVARCHAR(128) NULL, + NTUserName NVARCHAR(128) NULL, + OriginalLoginName NVARCHAR(128) NULL, + ProgramName NVARCHAR(128) NULL, + HostName NVARCHAR(128) NULL, + DatabaseID INT NULL, + DatabaseName NVARCHAR(128) NULL, + OpenTransactionCount INT NULL, + QueryStatsNowID INT NULL, + QueryStatsFirstID INT NULL, + PlanHandle VARBINARY(64) NULL, + DetailsInt INT NULL, + QueryHash BINARY(8) + ); + IF OBJECT_ID('tempdb..#WaitStats') IS NOT NULL + DROP TABLE #WaitStats; + CREATE TABLE #WaitStats (Pass TINYINT NOT NULL, wait_type NVARCHAR(60), wait_time_ms BIGINT, signal_wait_time_ms BIGINT, waiting_tasks_count BIGINT, SampleTime DATETIMEOFFSET); - IF NOT EXISTS (SELECT 1/0 - FROM #warning_results AS bcr - WHERE bcr.Priority = 2147483646 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (2147483646, - 255, - 'Need more help?' , - 'Paste your plan on the internet!', - 'http://pastetheplan.com', - 'This makes it easy to share plans and post them to Q&A sites like https://dba.stackexchange.com/!') ; + IF OBJECT_ID('tempdb..#FileStats') IS NOT NULL + DROP TABLE #FileStats; + CREATE TABLE #FileStats ( + ID INT IDENTITY(1, 1) PRIMARY KEY CLUSTERED, + Pass TINYINT NOT NULL, + SampleTime DATETIMEOFFSET NOT NULL, + DatabaseID INT NOT NULL, + FileID INT NOT NULL, + DatabaseName NVARCHAR(256) , + FileLogicalName NVARCHAR(256) , + TypeDesc NVARCHAR(60) , + SizeOnDiskMB BIGINT , + io_stall_read_ms BIGINT , + num_of_reads BIGINT , + bytes_read BIGINT , + io_stall_write_ms BIGINT , + num_of_writes BIGINT , + bytes_written BIGINT, + PhysicalName NVARCHAR(520) , + avg_stall_read_ms INT , + avg_stall_write_ms INT + ); + IF OBJECT_ID('tempdb..#QueryStats') IS NOT NULL + DROP TABLE #QueryStats; + CREATE TABLE #QueryStats ( + ID INT IDENTITY(1, 1) PRIMARY KEY CLUSTERED, + Pass INT NOT NULL, + SampleTime DATETIMEOFFSET NOT NULL, + [sql_handle] VARBINARY(64), + statement_start_offset INT, + statement_end_offset INT, + plan_generation_num BIGINT, + plan_handle VARBINARY(64), + execution_count BIGINT, + total_worker_time BIGINT, + total_physical_reads BIGINT, + total_logical_writes BIGINT, + total_logical_reads BIGINT, + total_clr_time BIGINT, + total_elapsed_time BIGINT, + creation_time DATETIMEOFFSET, + query_hash BINARY(8), + query_plan_hash BINARY(8), + Points TINYINT + ); - IF NOT EXISTS (SELECT 1/0 - FROM #warning_results AS bcr - WHERE bcr.Priority = 2147483647 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 2147483647, - 255, - 'Thanks for using sp_BlitzQueryStore!' , - 'From Your Community Volunteers', - 'http://FirstResponderKit.org', - 'We hope you found this tool useful. Current version: ' + @Version + ' released on ' + CONVERT(NVARCHAR(30), @VersionDate) + '.') ; - + IF OBJECT_ID('tempdb..#PerfmonStats') IS NOT NULL + DROP TABLE #PerfmonStats; + CREATE TABLE #PerfmonStats ( + ID INT IDENTITY(1, 1) PRIMARY KEY CLUSTERED, + Pass TINYINT NOT NULL, + SampleTime DATETIMEOFFSET NOT NULL, + [object_name] NVARCHAR(128) NOT NULL, + [counter_name] NVARCHAR(128) NOT NULL, + [instance_name] NVARCHAR(128) NULL, + [cntr_value] BIGINT NULL, + [cntr_type] INT NOT NULL, + [value_delta] BIGINT NULL, + [value_per_second] DECIMAL(18,2) NULL + ); - - SELECT Priority, - FindingsGroup, - Finding, - URL, - Details, - CheckID - FROM #warning_results - GROUP BY Priority, - FindingsGroup, - Finding, - URL, - Details, - CheckID - ORDER BY Priority ASC, FindingsGroup, Finding, CheckID ASC - OPTION (RECOMPILE); + IF OBJECT_ID('tempdb..#PerfmonCounters') IS NOT NULL + DROP TABLE #PerfmonCounters; + CREATE TABLE #PerfmonCounters ( + ID INT IDENTITY(1, 1) PRIMARY KEY CLUSTERED, + [object_name] NVARCHAR(128) NOT NULL, + [counter_name] NVARCHAR(128) NOT NULL, + [instance_name] NVARCHAR(128) NULL + ); + + IF OBJECT_ID('tempdb..#FilterPlansByDatabase') IS NOT NULL + DROP TABLE #FilterPlansByDatabase; + CREATE TABLE #FilterPlansByDatabase (DatabaseID INT PRIMARY KEY CLUSTERED); + + IF OBJECT_ID('tempdb..##WaitCategories') IS NULL + BEGIN + /* We reuse this one by default rather than recreate it every time. */ + CREATE TABLE ##WaitCategories + ( + WaitType NVARCHAR(60) PRIMARY KEY CLUSTERED, + WaitCategory NVARCHAR(128) NOT NULL, + Ignorable BIT DEFAULT 0 + ); + END; /* IF OBJECT_ID('tempdb..##WaitCategories') IS NULL */ + + IF OBJECT_ID ('tempdb..#checkversion') IS NOT NULL + DROP TABLE #checkversion; + CREATE TABLE #checkversion ( + version NVARCHAR(128), + common_version AS SUBSTRING(version, 1, CHARINDEX('.', version) + 1 ), + major AS PARSENAME(CONVERT(VARCHAR(32), version), 4), + minor AS PARSENAME(CONVERT(VARCHAR(32), version), 3), + build AS PARSENAME(CONVERT(VARCHAR(32), version), 2), + revision AS PARSENAME(CONVERT(VARCHAR(32), version), 1) + ); + IF 527 <> (SELECT COALESCE(SUM(1),0) FROM ##WaitCategories) + BEGIN + TRUNCATE TABLE ##WaitCategories; + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('ASYNC_IO_COMPLETION','Other Disk IO',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('ASYNC_NETWORK_IO','Network IO',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BACKUPIO','Other Disk IO',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_CONNECTION_RECEIVE_TASK','Service Broker',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_DISPATCHER','Service Broker',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_ENDPOINT_STATE_MUTEX','Service Broker',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_EVENTHANDLER','Service Broker',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_FORWARDER','Service Broker',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_INIT','Service Broker',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_MASTERSTART','Service Broker',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_RECEIVE_WAITFOR','User Wait',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_REGISTERALLENDPOINTS','Service Broker',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_SERVICE','Service Broker',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_SHUTDOWN','Service Broker',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_START','Service Broker',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_TASK_SHUTDOWN','Service Broker',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_TASK_STOP','Service Broker',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_TASK_SUBMIT','Service Broker',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_TO_FLUSH','Service Broker',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_TRANSMISSION_OBJECT','Service Broker',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_TRANSMISSION_TABLE','Service Broker',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_TRANSMISSION_WORK','Service Broker',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_TRANSMITTER','Service Broker',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CHECKPOINT_QUEUE','Idle',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CHKPT','Tran Log IO',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CLR_AUTO_EVENT','SQL CLR',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CLR_CRST','SQL CLR',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CLR_JOIN','SQL CLR',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CLR_MANUAL_EVENT','SQL CLR',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CLR_MEMORY_SPY','SQL CLR',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CLR_MONITOR','SQL CLR',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CLR_RWLOCK_READER','SQL CLR',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CLR_RWLOCK_WRITER','SQL CLR',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CLR_SEMAPHORE','SQL CLR',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CLR_TASK_START','SQL CLR',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CLRHOST_STATE_ACCESS','SQL CLR',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CMEMPARTITIONED','Memory',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CMEMTHREAD','Memory',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CXPACKET','Parallelism',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CXCONSUMER','Parallelism',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DBMIRROR_DBM_EVENT','Mirroring',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DBMIRROR_DBM_MUTEX','Mirroring',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DBMIRROR_EVENTS_QUEUE','Mirroring',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DBMIRROR_SEND','Mirroring',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DBMIRROR_WORKER_QUEUE','Mirroring',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DBMIRRORING_CMD','Mirroring',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DIRTY_PAGE_POLL','Other',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DIRTY_PAGE_TABLE_LOCK','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DISPATCHER_QUEUE_SEMAPHORE','Other',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DPT_ENTRY_LOCK','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DTC','Transaction',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DTC_ABORT_REQUEST','Transaction',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DTC_RESOLVE','Transaction',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DTC_STATE','Transaction',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DTC_TMDOWN_REQUEST','Transaction',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DTC_WAITFOR_OUTCOME','Transaction',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DTCNEW_ENLIST','Transaction',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DTCNEW_PREPARE','Transaction',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DTCNEW_RECOVERY','Transaction',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DTCNEW_TM','Transaction',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DTCNEW_TRANSACTION_ENLISTMENT','Transaction',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DTCPNTSYNC','Transaction',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('EE_PMOLOCK','Memory',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('EXCHANGE','Parallelism',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('EXTERNAL_SCRIPT_NETWORK_IOF','Network IO',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FCB_REPLICA_READ','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FCB_REPLICA_WRITE','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_COMPROWSET_RWLOCK','Full Text Search',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_IFTS_RWLOCK','Full Text Search',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_IFTS_SCHEDULER_IDLE_WAIT','Idle',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_IFTSHC_MUTEX','Full Text Search',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_IFTSISM_MUTEX','Full Text Search',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_MASTER_MERGE','Full Text Search',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_MASTER_MERGE_COORDINATOR','Full Text Search',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_METADATA_MUTEX','Full Text Search',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_PROPERTYLIST_CACHE','Full Text Search',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_RESTART_CRAWL','Full Text Search',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FULLTEXT GATHERER','Full Text Search',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_AG_MUTEX','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_AR_CRITICAL_SECTION_ENTRY','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_AR_MANAGER_MUTEX','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_AR_UNLOAD_COMPLETED','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_ARCONTROLLER_NOTIFICATIONS_SUBSCRIBER_LIST','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_BACKUP_BULK_LOCK','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_BACKUP_QUEUE','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_CLUSAPI_CALL','Replication',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_COMPRESSED_CACHE_SYNC','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_CONNECTIVITY_INFO','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_DATABASE_FLOW_CONTROL','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_DATABASE_VERSIONING_STATE','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_DATABASE_WAIT_FOR_RECOVERY','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_DATABASE_WAIT_FOR_RESTART','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_DATABASE_WAIT_FOR_TRANSITION_TO_VERSIONING','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_DB_COMMAND','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_DB_OP_COMPLETION_SYNC','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_DB_OP_START_SYNC','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_DBR_SUBSCRIBER','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_DBR_SUBSCRIBER_FILTER_LIST','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_DBSEEDING','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_DBSEEDING_LIST','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_DBSTATECHANGE_SYNC','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_FABRIC_CALLBACK','Replication',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_FILESTREAM_BLOCK_FLUSH','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_FILESTREAM_FILE_CLOSE','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_FILESTREAM_FILE_REQUEST','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_FILESTREAM_IOMGR','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_FILESTREAM_IOMGR_IOCOMPLETION','Replication',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_FILESTREAM_MANAGER','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_FILESTREAM_PREPROC','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_GROUP_COMMIT','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_LOGCAPTURE_SYNC','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_LOGCAPTURE_WAIT','Replication',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_LOGPROGRESS_SYNC','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_NOTIFICATION_DEQUEUE','Replication',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_NOTIFICATION_WORKER_EXCLUSIVE_ACCESS','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_NOTIFICATION_WORKER_STARTUP_SYNC','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_NOTIFICATION_WORKER_TERMINATION_SYNC','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_PARTNER_SYNC','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_READ_ALL_NETWORKS','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_RECOVERY_WAIT_FOR_CONNECTION','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_RECOVERY_WAIT_FOR_UNDO','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_REPLICAINFO_SYNC','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_SEEDING_CANCELLATION','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_SEEDING_FILE_LIST','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_SEEDING_LIMIT_BACKUPS','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_SEEDING_SYNC_COMPLETION','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_SEEDING_TIMEOUT_TASK','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_SEEDING_WAIT_FOR_COMPLETION','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_SYNC_COMMIT','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_SYNCHRONIZING_THROTTLE','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_TDS_LISTENER_SYNC','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_TDS_LISTENER_SYNC_PROCESSING','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_THROTTLE_LOG_RATE_GOVERNOR','Log Rate Governor',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_TIMER_TASK','Replication',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_TRANSPORT_DBRLIST','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_TRANSPORT_FLOW_CONTROL','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_TRANSPORT_SESSION','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_WORK_POOL','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_WORK_QUEUE','Replication',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_XRF_STACK_ACCESS','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('INSTANCE_LOG_RATE_GOVERNOR','Log Rate Governor',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('IO_COMPLETION','Other Disk IO',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('IO_QUEUE_LIMIT','Other Disk IO',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('IO_RETRY','Other Disk IO',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LATCH_DT','Latch',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LATCH_EX','Latch',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LATCH_KP','Latch',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LATCH_NL','Latch',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LATCH_SH','Latch',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LATCH_UP','Latch',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LAZYWRITER_SLEEP','Idle',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_BU','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_BU_ABORT_BLOCKERS','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_BU_LOW_PRIORITY','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_IS','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_IS_ABORT_BLOCKERS','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_IS_LOW_PRIORITY','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_IU','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_IU_ABORT_BLOCKERS','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_IU_LOW_PRIORITY','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_IX','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_IX_ABORT_BLOCKERS','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_IX_LOW_PRIORITY','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RIn_NL','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RIn_NL_ABORT_BLOCKERS','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RIn_NL_LOW_PRIORITY','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RIn_S','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RIn_S_ABORT_BLOCKERS','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RIn_S_LOW_PRIORITY','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RIn_U','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RIn_U_ABORT_BLOCKERS','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RIn_U_LOW_PRIORITY','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RIn_X','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RIn_X_ABORT_BLOCKERS','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RIn_X_LOW_PRIORITY','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RS_S','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RS_S_ABORT_BLOCKERS','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RS_S_LOW_PRIORITY','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RS_U','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RS_U_ABORT_BLOCKERS','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RS_U_LOW_PRIORITY','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RX_S','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RX_S_ABORT_BLOCKERS','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RX_S_LOW_PRIORITY','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RX_U','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RX_U_ABORT_BLOCKERS','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RX_U_LOW_PRIORITY','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RX_X','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RX_X_ABORT_BLOCKERS','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RX_X_LOW_PRIORITY','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_S','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_S_ABORT_BLOCKERS','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_S_LOW_PRIORITY','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_SCH_M','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_SCH_M_ABORT_BLOCKERS','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_SCH_M_LOW_PRIORITY','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_SCH_S','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_SCH_S_ABORT_BLOCKERS','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_SCH_S_LOW_PRIORITY','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_SIU','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_SIU_ABORT_BLOCKERS','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_SIU_LOW_PRIORITY','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_SIX','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_SIX_ABORT_BLOCKERS','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_SIX_LOW_PRIORITY','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_U','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_U_ABORT_BLOCKERS','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_U_LOW_PRIORITY','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_UIX','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_UIX_ABORT_BLOCKERS','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_UIX_LOW_PRIORITY','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_X','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_X_ABORT_BLOCKERS','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_X_LOW_PRIORITY','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LOG_RATE_GOVERNOR','Tran Log IO',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LOGBUFFER','Tran Log IO',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LOGMGR','Tran Log IO',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LOGMGR_FLUSH','Tran Log IO',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LOGMGR_PMM_LOG','Tran Log IO',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LOGMGR_QUEUE','Idle',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LOGMGR_RESERVE_APPEND','Tran Log IO',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('MEMORY_ALLOCATION_EXT','Memory',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('MEMORY_GRANT_UPDATE','Memory',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('MSQL_XACT_MGR_MUTEX','Transaction',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('MSQL_XACT_MUTEX','Transaction',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('MSSEARCH','Full Text Search',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('NET_WAITFOR_PACKET','Network IO',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('ONDEMAND_TASK_QUEUE','Idle',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PAGEIOLATCH_DT','Buffer IO',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PAGEIOLATCH_EX','Buffer IO',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PAGEIOLATCH_KP','Buffer IO',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PAGEIOLATCH_NL','Buffer IO',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PAGEIOLATCH_SH','Buffer IO',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PAGEIOLATCH_UP','Buffer IO',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PAGELATCH_DT','Buffer Latch',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PAGELATCH_EX','Buffer Latch',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PAGELATCH_KP','Buffer Latch',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PAGELATCH_NL','Buffer Latch',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PAGELATCH_SH','Buffer Latch',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PAGELATCH_UP','Buffer Latch',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PARALLEL_REDO_DRAIN_WORKER','Replication',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PARALLEL_REDO_FLOW_CONTROL','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PARALLEL_REDO_LOG_CACHE','Replication',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PARALLEL_REDO_TRAN_LIST','Replication',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PARALLEL_REDO_TRAN_TURN','Replication',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PARALLEL_REDO_WORKER_SYNC','Replication',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PARALLEL_REDO_WORKER_WAIT_WORK','Replication',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('POOL_LOG_RATE_GOVERNOR','Log Rate Governor',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_ABR','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_CLOSEBACKUPMEDIA','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_CLOSEBACKUPTAPE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_CLOSEBACKUPVDIDEVICE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_CLUSAPI_CLUSTERRESOURCECONTROL','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_COCREATEINSTANCE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_COGETCLASSOBJECT','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_CREATEACCESSOR','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_DELETEROWS','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_GETCOMMANDTEXT','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_GETDATA','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_GETNEXTROWS','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_GETRESULT','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_GETROWSBYBOOKMARK','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_LBFLUSH','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_LBLOCKREGION','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_LBREADAT','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_LBSETSIZE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_LBSTAT','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_LBUNLOCKREGION','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_LBWRITEAT','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_QUERYINTERFACE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_RELEASE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_RELEASEACCESSOR','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_RELEASEROWS','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_RELEASESESSION','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_RESTARTPOSITION','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_SEQSTRMREAD','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_SEQSTRMREADANDWRITE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_SETDATAFAILURE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_SETPARAMETERINFO','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_SETPARAMETERPROPERTIES','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_STRMLOCKREGION','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_STRMSEEKANDREAD','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_STRMSEEKANDWRITE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_STRMSETSIZE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_STRMSTAT','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_STRMUNLOCKREGION','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_CONSOLEWRITE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_CREATEPARAM','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DEBUG','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DFSADDLINK','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DFSLINKEXISTCHECK','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DFSLINKHEALTHCHECK','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DFSREMOVELINK','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DFSREMOVEROOT','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DFSROOTFOLDERCHECK','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DFSROOTINIT','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DFSROOTSHARECHECK','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DTC_ABORT','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DTC_ABORTREQUESTDONE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DTC_BEGINTRANSACTION','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DTC_COMMITREQUESTDONE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DTC_ENLIST','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DTC_PREPAREREQUESTDONE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_FILESIZEGET','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_FSAOLEDB_ABORTTRANSACTION','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_FSAOLEDB_COMMITTRANSACTION','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_FSAOLEDB_STARTTRANSACTION','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_FSRECOVER_UNCONDITIONALUNDO','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_GETRMINFO','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_HADR_LEASE_MECHANISM','Preemptive',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_HTTP_EVENT_WAIT','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_HTTP_REQUEST','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_LOCKMONITOR','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_MSS_RELEASE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_ODBCOPS','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OLE_UNINIT','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OLEDB_ABORTORCOMMITTRAN','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OLEDB_ABORTTRAN','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OLEDB_GETDATASOURCE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OLEDB_GETLITERALINFO','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OLEDB_GETPROPERTIES','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OLEDB_GETPROPERTYINFO','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OLEDB_GETSCHEMALOCK','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OLEDB_JOINTRANSACTION','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OLEDB_RELEASE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OLEDB_SETPROPERTIES','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OLEDBOPS','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_ACCEPTSECURITYCONTEXT','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_ACQUIRECREDENTIALSHANDLE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_AUTHENTICATIONOPS','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_AUTHORIZATIONOPS','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_AUTHZGETINFORMATIONFROMCONTEXT','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_AUTHZINITIALIZECONTEXTFROMSID','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_AUTHZINITIALIZERESOURCEMANAGER','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_BACKUPREAD','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_CLOSEHANDLE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_CLUSTEROPS','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_COMOPS','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_COMPLETEAUTHTOKEN','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_COPYFILE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_CREATEDIRECTORY','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_CREATEFILE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_CRYPTACQUIRECONTEXT','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_CRYPTIMPORTKEY','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_CRYPTOPS','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_DECRYPTMESSAGE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_DELETEFILE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_DELETESECURITYCONTEXT','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_DEVICEIOCONTROL','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_DEVICEOPS','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_DIRSVC_NETWORKOPS','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_DISCONNECTNAMEDPIPE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_DOMAINSERVICESOPS','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_DSGETDCNAME','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_DTCOPS','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_ENCRYPTMESSAGE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_FILEOPS','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_FINDFILE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_FLUSHFILEBUFFERS','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_FORMATMESSAGE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_FREECREDENTIALSHANDLE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_FREELIBRARY','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_GENERICOPS','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_GETADDRINFO','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_GETCOMPRESSEDFILESIZE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_GETDISKFREESPACE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_GETFILEATTRIBUTES','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_GETFILESIZE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_GETFINALFILEPATHBYHANDLE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_GETLONGPATHNAME','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_GETPROCADDRESS','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_GETVOLUMENAMEFORVOLUMEMOUNTPOINT','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_GETVOLUMEPATHNAME','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_INITIALIZESECURITYCONTEXT','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_LIBRARYOPS','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_LOADLIBRARY','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_LOGONUSER','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_LOOKUPACCOUNTSID','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_MESSAGEQUEUEOPS','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_MOVEFILE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_NETGROUPGETUSERS','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_NETLOCALGROUPGETMEMBERS','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_NETUSERGETGROUPS','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_NETUSERGETLOCALGROUPS','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_NETUSERMODALSGET','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_NETVALIDATEPASSWORDPOLICY','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_NETVALIDATEPASSWORDPOLICYFREE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_OPENDIRECTORY','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_PDH_WMI_INIT','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_PIPEOPS','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_PROCESSOPS','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_QUERYCONTEXTATTRIBUTES','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_QUERYREGISTRY','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_QUERYSECURITYCONTEXTTOKEN','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_REMOVEDIRECTORY','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_REPORTEVENT','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_REVERTTOSELF','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_RSFXDEVICEOPS','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_SECURITYOPS','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_SERVICEOPS','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_SETENDOFFILE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_SETFILEPOINTER','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_SETFILEVALIDDATA','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_SETNAMEDSECURITYINFO','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_SQLCLROPS','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_SQMLAUNCH','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_VERIFYSIGNATURE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_VERIFYTRUST','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_VSSOPS','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_WAITFORSINGLEOBJECT','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_WINSOCKOPS','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_WRITEFILE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_WRITEFILEGATHER','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_WSASETLASTERROR','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_REENLIST','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_RESIZELOG','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_ROLLFORWARDREDO','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_ROLLFORWARDUNDO','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_SB_STOPENDPOINT','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_SERVER_STARTUP','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_SETRMINFO','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_SHAREDMEM_GETDATA','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_SNIOPEN','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_SOSHOST','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_SOSTESTING','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_SP_SERVER_DIAGNOSTICS','Preemptive',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_STARTRM','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_STREAMFCB_CHECKPOINT','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_STREAMFCB_RECOVER','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_STRESSDRIVER','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_TESTING','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_TRANSIMPORT','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_UNMARSHALPROPAGATIONTOKEN','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_VSS_CREATESNAPSHOT','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_VSS_CREATEVOLUMESNAPSHOT','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_XE_CALLBACKEXECUTE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_XE_CX_FILE_OPEN','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_XE_CX_HTTP_CALL','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_XE_DISPATCHER','Preemptive',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_XE_ENGINEINIT','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_XE_GETTARGETSTATE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_XE_SESSIONCOMMIT','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_XE_TARGETFINALIZE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_XE_TARGETINIT','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_XE_TIMERRUN','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_XETESTING','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PWAIT_HADR_ACTION_COMPLETED','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PWAIT_HADR_CHANGE_NOTIFIER_TERMINATION_SYNC','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PWAIT_HADR_CLUSTER_INTEGRATION','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PWAIT_HADR_FAILOVER_COMPLETED','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PWAIT_HADR_JOIN','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PWAIT_HADR_OFFLINE_COMPLETED','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PWAIT_HADR_ONLINE_COMPLETED','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PWAIT_HADR_POST_ONLINE_COMPLETED','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PWAIT_HADR_SERVER_READY_CONNECTIONS','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PWAIT_HADR_WORKITEM_COMPLETED','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PWAIT_HADRSIM','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PWAIT_RESOURCE_SEMAPHORE_FT_PARALLEL_QUERY_SYNC','Full Text Search',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('QDS_ASYNC_QUEUE','Other',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('QDS_CLEANUP_STALE_QUERIES_TASK_MAIN_LOOP_SLEEP','Other',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('QDS_PERSIST_TASK_MAIN_LOOP_SLEEP','Other',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('QDS_SHUTDOWN_QUEUE','Other',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('QUERY_TRACEOUT','Tracing',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('REDO_THREAD_PENDING_WORK','Other',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('REPL_CACHE_ACCESS','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('REPL_HISTORYCACHE_ACCESS','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('REPL_SCHEMA_ACCESS','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('REPL_TRANFSINFO_ACCESS','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('REPL_TRANHASHTABLE_ACCESS','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('REPL_TRANTEXTINFO_ACCESS','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('REPLICA_WRITES','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('REQUEST_FOR_DEADLOCK_SEARCH','Idle',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('RESERVED_MEMORY_ALLOCATION_EXT','Memory',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('RESOURCE_SEMAPHORE','Memory',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('RESOURCE_SEMAPHORE_QUERY_COMPILE','Compilation',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_BPOOL_FLUSH','Idle',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_BUFFERPOOL_HELPLW','Idle',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_DBSTARTUP','Idle',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_DCOMSTARTUP','Idle',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_MASTERDBREADY','Idle',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_MASTERMDREADY','Idle',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_MASTERUPGRADED','Idle',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_MEMORYPOOL_ALLOCATEPAGES','Idle',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_MSDBSTARTUP','Idle',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_RETRY_VIRTUALALLOC','Idle',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_SYSTEMTASK','Idle',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_TASK','Idle',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_TEMPDBSTARTUP','Idle',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_WORKSPACE_ALLOCATEPAGE','Idle',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SOS_SCHEDULER_YIELD','CPU',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SOS_WORK_DISPATCHER','Idle',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SP_SERVER_DIAGNOSTICS_SLEEP','Other',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SQLCLR_APPDOMAIN','SQL CLR',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SQLCLR_ASSEMBLY','SQL CLR',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SQLCLR_DEADLOCK_DETECTION','SQL CLR',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SQLCLR_QUANTUM_PUNISHMENT','SQL CLR',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SQLTRACE_BUFFER_FLUSH','Idle',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SQLTRACE_FILE_BUFFER','Tracing',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SQLTRACE_FILE_READ_IO_COMPLETION','Tracing',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SQLTRACE_FILE_WRITE_IO_COMPLETION','Tracing',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SQLTRACE_INCREMENTAL_FLUSH_SLEEP','Idle',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SQLTRACE_PENDING_BUFFER_WRITERS','Tracing',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SQLTRACE_SHUTDOWN','Tracing',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SQLTRACE_WAIT_ENTRIES','Idle',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('THREADPOOL','Worker Thread',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('TRACE_EVTNOTIF','Tracing',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('TRACEWRITE','Tracing',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('TRAN_MARKLATCH_DT','Transaction',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('TRAN_MARKLATCH_EX','Transaction',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('TRAN_MARKLATCH_KP','Transaction',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('TRAN_MARKLATCH_NL','Transaction',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('TRAN_MARKLATCH_SH','Transaction',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('TRAN_MARKLATCH_UP','Transaction',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('TRANSACTION_MUTEX','Transaction',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('UCS_SESSION_REGISTRATION','Other',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('WAIT_FOR_RESULTS','User Wait',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('WAIT_XTP_OFFLINE_CKPT_NEW_LOG','Other',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('WAITFOR','User Wait',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('WRITE_COMPLETION','Other Disk IO',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('WRITELOG','Tran Log IO',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('XACT_OWN_TRANSACTION','Transaction',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('XACT_RECLAIM_SESSION','Transaction',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('XACTLOCKINFO','Transaction',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('XACTWORKSPACE_MUTEX','Transaction',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('XE_DISPATCHER_WAIT','Idle',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('XE_LIVE_TARGET_TVF','Other',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('XE_TIMER_EVENT','Idle',1); + END; /* IF SELECT SUM(1) FROM ##WaitCategories <> 527 */ -END; -END; -END TRY -BEGIN CATCH - RAISERROR (N'Failure returning warnings', 0,1) WITH NOWAIT; + IF OBJECT_ID('tempdb..#MasterFiles') IS NOT NULL + DROP TABLE #MasterFiles; + CREATE TABLE #MasterFiles (database_id INT, file_id INT, type_desc NVARCHAR(50), name NVARCHAR(255), physical_name NVARCHAR(255), size BIGINT); + /* Azure SQL Database doesn't have sys.master_files, so we have to build our own. */ + IF ((SERVERPROPERTY('Edition')) = 'SQL Azure' + AND (OBJECT_ID('sys.master_files') IS NULL)) + SET @StringToExecute = 'INSERT INTO #MasterFiles (database_id, file_id, type_desc, name, physical_name, size) SELECT DB_ID(), file_id, type_desc, name, physical_name, size FROM sys.database_files;'; + ELSE + SET @StringToExecute = 'INSERT INTO #MasterFiles (database_id, file_id, type_desc, name, physical_name, size) SELECT database_id, file_id, type_desc, name, physical_name, size FROM sys.master_files;'; + EXEC(@StringToExecute); - IF @sql_select IS NOT NULL + IF @FilterPlansByDatabase IS NOT NULL BEGIN - SET @msg = N'Last @sql_select: ' + @sql_select; - RAISERROR(@msg, 0, 1) WITH NOWAIT; + IF UPPER(LEFT(@FilterPlansByDatabase,4)) = 'USER' + BEGIN + INSERT INTO #FilterPlansByDatabase (DatabaseID) + SELECT database_id + FROM sys.databases + WHERE [name] NOT IN ('master', 'model', 'msdb', 'tempdb'); + END; + ELSE + BEGIN + SET @FilterPlansByDatabase = @FilterPlansByDatabase + ',' + ;WITH a AS + ( + SELECT CAST(1 AS BIGINT) f, CHARINDEX(',', @FilterPlansByDatabase) t, 1 SEQ + UNION ALL + SELECT t + 1, CHARINDEX(',', @FilterPlansByDatabase, t + 1), SEQ + 1 + FROM a + WHERE CHARINDEX(',', @FilterPlansByDatabase, t + 1) > 0 + ) + INSERT #FilterPlansByDatabase (DatabaseID) + SELECT DISTINCT db.database_id + FROM a + INNER JOIN sys.databases db ON LTRIM(RTRIM(SUBSTRING(@FilterPlansByDatabase, a.f, a.t - a.f))) = db.name + WHERE SUBSTRING(@FilterPlansByDatabase, f, t - f) IS NOT NULL + OPTION (MAXRECURSION 0); + END; END; - SELECT @msg = @DatabaseName + N' database failed to process. ' + ERROR_MESSAGE(), @error_severity = ERROR_SEVERITY(), @error_state = ERROR_STATE(); - RAISERROR (@msg, @error_severity, @error_state) WITH NOWAIT; - - - WHILE @@TRANCOUNT > 0 - ROLLBACK; + IF OBJECT_ID('tempdb..#ReadableDBs') IS NOT NULL + DROP TABLE #ReadableDBs; + CREATE TABLE #ReadableDBs ( + database_id INT + ); - RETURN; -END CATCH; + IF EXISTS (SELECT * FROM sys.all_objects o WHERE o.name = 'dm_hadr_database_replica_states') + BEGIN + RAISERROR('Checking for Read intent databases to exclude',0,0) WITH NOWAIT; + + SET @StringToExecute = 'INSERT INTO #ReadableDBs (database_id) SELECT DBs.database_id FROM sys.databases DBs INNER JOIN sys.availability_replicas Replicas ON DBs.replica_id = Replicas.replica_id WHERE replica_server_name NOT IN (SELECT DISTINCT primary_replica FROM sys.dm_hadr_availability_group_states States) AND Replicas.secondary_role_allow_connections_desc = ''READ_ONLY'' AND replica_server_name = @@SERVERNAME;'; + EXEC(@StringToExecute); + + END -IF @Debug = 1 + DECLARE @v DECIMAL(6,2), + @build INT, + @memGrantSortSupported BIT = 1; -BEGIN TRY + RAISERROR (N'Determining SQL Server version.',0,1) WITH NOWAIT; -BEGIN + INSERT INTO #checkversion (version) + SELECT CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)) + OPTION (RECOMPILE); -RAISERROR(N'Returning debugging data from temp tables', 0, 1) WITH NOWAIT; ---Table content debugging + SELECT @v = common_version , + @build = build + FROM #checkversion + OPTION (RECOMPILE); -SELECT '#working_metrics' AS table_name, * -FROM #working_metrics AS wm -OPTION (RECOMPILE); + IF (@v < 11) + OR (@v = 11 AND @build < 6020) + OR (@v = 12 AND @build < 5000) + OR (@v = 13 AND @build < 1601) + SET @memGrantSortSupported = 0; -SELECT '#working_plan_text' AS table_name, * -FROM #working_plan_text AS wpt -OPTION (RECOMPILE); + IF EXISTS (SELECT * FROM sys.all_objects WHERE name = 'dm_exec_query_statistics_xml') + AND ((@v = 13 AND @build >= 5337) /* This DMF causes assertion errors: https://support.microsoft.com/en-us/help/4490136/fix-assertion-error-occurs-when-you-use-sys-dm-exec-query-statistics-x */ + OR (@v = 14 AND @build >= 3162) + OR (@v >= 15) + OR (@v <= 12)) /* Azure */ + SET @dm_exec_query_statistics_xml = 1; -SELECT '#working_warnings' AS table_name, * -FROM #working_warnings AS ww -OPTION (RECOMPILE); -SELECT '#working_wait_stats' AS table_name, * -FROM #working_wait_stats wws -OPTION (RECOMPILE); + SET @StockWarningHeader = '', + @StockDetailsHeader = @StockDetailsHeader + ''; -SELECT '#working_plans' AS table_name, * -FROM #working_plans -OPTION (RECOMPILE); + /* Get the instance name to use as a Perfmon counter prefix. */ + IF CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) = 'SQL Azure' + SELECT TOP 1 @ServiceName = LEFT(object_name, (CHARINDEX(':', object_name) - 1)) + FROM sys.dm_os_performance_counters; + ELSE + BEGIN + SET @StringToExecute = 'INSERT INTO #PerfmonStats(object_name, Pass, SampleTime, counter_name, cntr_type) SELECT CASE WHEN @@SERVICENAME = ''MSSQLSERVER'' THEN ''SQLServer'' ELSE ''MSSQL$'' + @@SERVICENAME END, 0, SYSDATETIMEOFFSET(), ''stuffing'', 0 ;'; + EXEC(@StringToExecute); + SELECT @ServiceName = object_name FROM #PerfmonStats; + DELETE #PerfmonStats; + END; -SELECT '#stats_agg' AS table_name, * -FROM #stats_agg -OPTION (RECOMPILE); + /* Build a list of queries that were run in the last 10 seconds. + We're looking for the death-by-a-thousand-small-cuts scenario + where a query is constantly running, and it doesn't have that + big of an impact individually, but it has a ton of impact + overall. We're going to build this list, and then after we + finish our @Seconds sample, we'll compare our plan cache to + this list to see what ran the most. */ -SELECT '#trace_flags' AS table_name, * -FROM #trace_flags -OPTION (RECOMPILE); + /* Populate #QueryStats. SQL 2005 doesn't have query hash or query plan hash. */ + IF @CheckProcedureCache = 1 + BEGIN + RAISERROR('@CheckProcedureCache = 1, capturing first pass of plan cache',10,1) WITH NOWAIT; + IF @@VERSION LIKE 'Microsoft SQL Server 2005%' + BEGIN + IF @FilterPlansByDatabase IS NULL + BEGIN + SET @StringToExecute = N'INSERT INTO #QueryStats ([sql_handle], Pass, SampleTime, statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, Points) + SELECT [sql_handle], 1 AS Pass, SYSDATETIMEOFFSET(), statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, NULL AS query_hash, NULL AS query_plan_hash, 0 + FROM sys.dm_exec_query_stats qs + WHERE qs.last_execution_time >= (DATEADD(ss, -10, SYSDATETIMEOFFSET()));'; + END; + ELSE + BEGIN + SET @StringToExecute = N'INSERT INTO #QueryStats ([sql_handle], Pass, SampleTime, statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, Points) + SELECT [sql_handle], 1 AS Pass, SYSDATETIMEOFFSET(), statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, NULL AS query_hash, NULL AS query_plan_hash, 0 + FROM sys.dm_exec_query_stats qs + CROSS APPLY sys.dm_exec_plan_attributes(qs.plan_handle) AS attr + INNER JOIN #FilterPlansByDatabase dbs ON CAST(attr.value AS INT) = dbs.DatabaseID + WHERE qs.last_execution_time >= (DATEADD(ss, -10, SYSDATETIMEOFFSET())) + AND attr.attribute = ''dbid'';'; + END; + END; + ELSE + BEGIN + IF @FilterPlansByDatabase IS NULL + BEGIN + SET @StringToExecute = N'INSERT INTO #QueryStats ([sql_handle], Pass, SampleTime, statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, Points) + SELECT [sql_handle], 1 AS Pass, SYSDATETIMEOFFSET(), statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, 0 + FROM sys.dm_exec_query_stats qs + WHERE qs.last_execution_time >= (DATEADD(ss, -10, SYSDATETIMEOFFSET()));'; + END; + ELSE + BEGIN + SET @StringToExecute = N'INSERT INTO #QueryStats ([sql_handle], Pass, SampleTime, statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, Points) + SELECT [sql_handle], 1 AS Pass, SYSDATETIMEOFFSET(), statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, 0 + FROM sys.dm_exec_query_stats qs + CROSS APPLY sys.dm_exec_plan_attributes(qs.plan_handle) AS attr + INNER JOIN #FilterPlansByDatabase dbs ON CAST(attr.value AS INT) = dbs.DatabaseID + WHERE qs.last_execution_time >= (DATEADD(ss, -10, SYSDATETIMEOFFSET())) + AND attr.attribute = ''dbid'';'; + END; + END; + EXEC(@StringToExecute); -SELECT '#statements' AS table_name, * -FROM #statements AS s -OPTION (RECOMPILE); + /* Get the totals for the entire plan cache */ + INSERT INTO #QueryStats (Pass, SampleTime, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time) + SELECT -1 AS Pass, SYSDATETIMEOFFSET(), SUM(execution_count), SUM(total_worker_time), SUM(total_physical_reads), SUM(total_logical_writes), SUM(total_logical_reads), SUM(total_clr_time), SUM(total_elapsed_time), MIN(creation_time) + FROM sys.dm_exec_query_stats qs; + END; /*IF @CheckProcedureCache = 1 */ -SELECT '#query_plan' AS table_name, * -FROM #query_plan AS qp -OPTION (RECOMPILE); -SELECT '#relop' AS table_name, * -FROM #relop AS r -OPTION (RECOMPILE); + IF EXISTS (SELECT * + FROM tempdb.sys.all_objects obj + INNER JOIN tempdb.sys.all_columns col1 ON obj.object_id = col1.object_id AND col1.name = 'object_name' + INNER JOIN tempdb.sys.all_columns col2 ON obj.object_id = col2.object_id AND col2.name = 'counter_name' + INNER JOIN tempdb.sys.all_columns col3 ON obj.object_id = col3.object_id AND col3.name = 'instance_name' + WHERE obj.name LIKE '%CustomPerfmonCounters%') + BEGIN + SET @StringToExecute = 'INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) SELECT [object_name],[counter_name],[instance_name] FROM #CustomPerfmonCounters'; + EXEC(@StringToExecute); + END; + ELSE + BEGIN + /* Add our default Perfmon counters */ + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Forwarded Records/sec', NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Page compression attempts/sec', NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Page Splits/sec', NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Skipped Ghosted Records/sec', NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Table Lock Escalations/sec', NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Worktables Created/sec', NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Availability Group','Active Hadr Threads','_Total'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Availability Replica','Bytes Received from Replica/sec','_Total'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Availability Replica','Bytes Sent to Replica/sec','_Total'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Availability Replica','Bytes Sent to Transport/sec','_Total'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Availability Replica','Flow Control Time (ms/sec)','_Total'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Availability Replica','Flow Control/sec','_Total'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Availability Replica','Resent Messages/sec','_Total'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Availability Replica','Sends to Replica/sec','_Total'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Page life expectancy', NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Page reads/sec', NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Page writes/sec', NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Readahead pages/sec', NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Target pages', NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Total pages', NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Databases','', NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Active Transactions','_Total'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Database Flow Control Delay', '_Total'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Database Flow Controls/sec', '_Total'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Group Commit Time', '_Total'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Group Commits/Sec', '_Total'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Log Apply Pending Queue', '_Total'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Log Apply Ready Queue', '_Total'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Log Compression Cache misses/sec', '_Total'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Log remaining for undo', '_Total'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Log Send Queue', '_Total'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Recovery Queue', '_Total'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Redo blocked/sec', '_Total'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Redo Bytes Remaining', '_Total'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Redone Bytes/sec', '_Total'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Databases','Log Bytes Flushed/sec', '_Total'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Databases','Log Growths', '_Total'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Databases','Log Pool LogWriter Pushes/sec', '_Total'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Databases','Log Shrinks', '_Total'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Databases','Transactions/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Databases','Write Transactions/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Databases','XTP Memory Used (KB)',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Exec Statistics','Distributed Query', 'Execs in progress'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Exec Statistics','DTC calls', 'Execs in progress'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Exec Statistics','Extended Procedures', 'Execs in progress'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Exec Statistics','OLEDB calls', 'Execs in progress'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':General Statistics','Active Temp Tables', NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':General Statistics','Logins/sec', NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':General Statistics','Logouts/sec', NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':General Statistics','Mars Deadlocks', NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':General Statistics','Processes blocked', NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Locks','Number of Deadlocks/sec', NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Memory Manager','Memory Grants Pending', NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Errors','Errors/sec', '_Total'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','Batch Requests/sec', NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','Forced Parameterizations/sec', NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','Guided plan executions/sec', NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','SQL Attention rate', NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','SQL Compilations/sec', NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','SQL Re-Compilations/sec', NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Workload Group Stats','Query optimizations/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Workload Group Stats','Suboptimal plans/sec',NULL); + /* Below counters added by Jefferson Elias */ + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Worktables From Cache Base',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Worktables From Cache Ratio',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Database pages',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Free pages',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Stolen pages',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Memory Manager','Granted Workspace Memory (KB)',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Memory Manager','Maximum Workspace Memory (KB)',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Memory Manager','Target Server Memory (KB)',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Memory Manager','Total Server Memory (KB)',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Buffer cache hit ratio',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Buffer cache hit ratio base',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Checkpoint pages/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Free list stalls/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Lazy writes/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','Auto-Param Attempts/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','Failed Auto-Params/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','Safe Auto-Params/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','Unsafe Auto-Params/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Workfiles Created/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':General Statistics','User Connections',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Latches','Average Latch Wait Time (ms)',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Latches','Average Latch Wait Time Base',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Latches','Latch Waits/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Latches','Total Latch Wait Time (ms)',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Locks','Average Wait Time (ms)',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Locks','Average Wait Time Base',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Locks','Lock Requests/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Locks','Lock Timeouts/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Locks','Lock Wait Time (ms)',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Locks','Lock Waits/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Transactions','Longest Transaction Running Time',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Full Scans/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Index Searches/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Page lookups/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Cursor Manager by Type','Active cursors',NULL); + /* Below counters are for In-Memory OLTP (Hekaton), which have a different naming convention. + And yes, they actually hard-coded the version numbers into the counters, and SQL 2019 still says 2017, oddly. + For why, see: https://connect.microsoft.com/SQLServer/feedback/details/817216/xtp-perfmon-counters-should-appear-under-sql-server-perfmon-counter-group + */ + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP Cursors','Expired rows removed/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP Cursors','Expired rows touched/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP Garbage Collection','Rows processed/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP IO Governor','Io Issued/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP Phantom Processor','Phantom expired rows touched/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP Phantom Processor','Phantom rows touched/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP Transaction Log','Log bytes written/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP Transaction Log','Log records written/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP Transactions','Transactions aborted by user/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP Transactions','Transactions aborted/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP Transactions','Transactions created/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP Cursors','Expired rows removed/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP Cursors','Expired rows touched/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP Garbage Collection','Rows processed/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP IO Governor','Io Issued/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP Phantom Processor','Phantom expired rows touched/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP Phantom Processor','Phantom rows touched/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP Transaction Log','Log bytes written/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP Transaction Log','Log records written/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP Transactions','Transactions aborted by user/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP Transactions','Transactions aborted/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP Transactions','Transactions created/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP Cursors','Expired rows removed/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP Cursors','Expired rows touched/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP Garbage Collection','Rows processed/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP IO Governor','Io Issued/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP Phantom Processor','Phantom expired rows touched/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP Phantom Processor','Phantom rows touched/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP Transaction Log','Log bytes written/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP Transaction Log','Log records written/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP Transactions','Transactions aborted by user/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP Transactions','Transactions aborted/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP Transactions','Transactions created/sec',NULL); + END; -SELECT '#plan_cost' AS table_name, * -FROM #plan_cost AS pc -OPTION (RECOMPILE); + /* Populate #FileStats, #PerfmonStats, #WaitStats with DMV data. + After we finish doing our checks, we'll take another sample and compare them. */ + RAISERROR('Capturing first pass of wait stats, perfmon counters, file stats',10,1) WITH NOWAIT; + INSERT #WaitStats(Pass, SampleTime, wait_type, wait_time_ms, signal_wait_time_ms, waiting_tasks_count) + SELECT + x.Pass, + x.SampleTime, + x.wait_type, + SUM(x.sum_wait_time_ms) AS sum_wait_time_ms, + SUM(x.sum_signal_wait_time_ms) AS sum_signal_wait_time_ms, + SUM(x.sum_waiting_tasks) AS sum_waiting_tasks + FROM ( + SELECT + 1 AS Pass, + CASE @Seconds WHEN 0 THEN @StartSampleTime ELSE SYSDATETIMEOFFSET() END AS SampleTime, + owt.wait_type, + CASE @Seconds WHEN 0 THEN 0 ELSE SUM(owt.wait_duration_ms) OVER (PARTITION BY owt.wait_type, owt.session_id) + - CASE WHEN @Seconds = 0 THEN 0 ELSE (@Seconds * 1000) END END AS sum_wait_time_ms, + 0 AS sum_signal_wait_time_ms, + 0 AS sum_waiting_tasks + FROM sys.dm_os_waiting_tasks owt + WHERE owt.session_id > 50 + AND owt.wait_duration_ms >= CASE @Seconds WHEN 0 THEN 0 ELSE @Seconds * 1000 END + UNION ALL + SELECT + 1 AS Pass, + CASE @Seconds WHEN 0 THEN @StartSampleTime ELSE SYSDATETIMEOFFSET() END AS SampleTime, + os.wait_type, + CASE @Seconds WHEN 0 THEN 0 ELSE SUM(os.wait_time_ms) OVER (PARTITION BY os.wait_type) END AS sum_wait_time_ms, + CASE @Seconds WHEN 0 THEN 0 ELSE SUM(os.signal_wait_time_ms) OVER (PARTITION BY os.wait_type ) END AS sum_signal_wait_time_ms, + CASE @Seconds WHEN 0 THEN 0 ELSE SUM(os.waiting_tasks_count) OVER (PARTITION BY os.wait_type) END AS sum_waiting_tasks + FROM sys.dm_os_wait_stats os + ) x + WHERE NOT EXISTS + ( + SELECT * + FROM ##WaitCategories AS wc + WHERE wc.WaitType = x.wait_type + AND wc.Ignorable = 1 + ) + GROUP BY x.Pass, x.SampleTime, x.wait_type + ORDER BY sum_wait_time_ms DESC; -SELECT '#est_rows' AS table_name, * -FROM #est_rows AS er -OPTION (RECOMPILE); -SELECT '#stored_proc_info' AS table_name, * -FROM #stored_proc_info AS spi -OPTION(RECOMPILE); + INSERT INTO #FileStats (Pass, SampleTime, DatabaseID, FileID, DatabaseName, FileLogicalName, SizeOnDiskMB, io_stall_read_ms , + num_of_reads, [bytes_read] , io_stall_write_ms,num_of_writes, [bytes_written], PhysicalName, TypeDesc) + SELECT + 1 AS Pass, + CASE @Seconds WHEN 0 THEN @StartSampleTime ELSE SYSDATETIMEOFFSET() END AS SampleTime, + mf.[database_id], + mf.[file_id], + DB_NAME(vfs.database_id) AS [db_name], + mf.name + N' [' + mf.type_desc COLLATE SQL_Latin1_General_CP1_CI_AS + N']' AS file_logical_name , + CAST(( ( vfs.size_on_disk_bytes / 1024.0 ) / 1024.0 ) AS INT) AS size_on_disk_mb , + CASE @Seconds WHEN 0 THEN 0 ELSE vfs.io_stall_read_ms END , + CASE @Seconds WHEN 0 THEN 0 ELSE vfs.num_of_reads END , + CASE @Seconds WHEN 0 THEN 0 ELSE vfs.[num_of_bytes_read] END , + CASE @Seconds WHEN 0 THEN 0 ELSE vfs.io_stall_write_ms END , + CASE @Seconds WHEN 0 THEN 0 ELSE vfs.num_of_writes END , + CASE @Seconds WHEN 0 THEN 0 ELSE vfs.[num_of_bytes_written] END , + mf.physical_name, + mf.type_desc + FROM sys.dm_io_virtual_file_stats (NULL, NULL) AS vfs + INNER JOIN #MasterFiles AS mf ON vfs.file_id = mf.file_id + AND vfs.database_id = mf.database_id + WHERE vfs.num_of_reads > 0 + OR vfs.num_of_writes > 0; -SELECT '#conversion_info' AS table_name, * -FROM #conversion_info AS ci -OPTION ( RECOMPILE ); + INSERT INTO #PerfmonStats (Pass, SampleTime, [object_name],[counter_name],[instance_name],[cntr_value],[cntr_type]) + SELECT 1 AS Pass, + CASE @Seconds WHEN 0 THEN @StartSampleTime ELSE SYSDATETIMEOFFSET() END AS SampleTime, RTRIM(dmv.object_name), RTRIM(dmv.counter_name), RTRIM(dmv.instance_name), CASE @Seconds WHEN 0 THEN 0 ELSE dmv.cntr_value END, dmv.cntr_type + FROM #PerfmonCounters counters + INNER JOIN sys.dm_os_performance_counters dmv ON counters.counter_name COLLATE SQL_Latin1_General_CP1_CI_AS = RTRIM(dmv.counter_name) COLLATE SQL_Latin1_General_CP1_CI_AS + AND counters.[object_name] COLLATE SQL_Latin1_General_CP1_CI_AS = RTRIM(dmv.[object_name]) COLLATE SQL_Latin1_General_CP1_CI_AS + AND (counters.[instance_name] IS NULL OR counters.[instance_name] COLLATE SQL_Latin1_General_CP1_CI_AS = RTRIM(dmv.[instance_name]) COLLATE SQL_Latin1_General_CP1_CI_AS); -SELECT '#variable_info' AS table_name, * -FROM #variable_info AS vi -OPTION ( RECOMPILE ); + /* For Github #2743: */ + CREATE TABLE #TempdbOperationalStats (object_id BIGINT PRIMARY KEY CLUSTERED, + forwarded_fetch_count BIGINT); + INSERT INTO #TempdbOperationalStats (object_id, forwarded_fetch_count) + SELECT object_id, forwarded_fetch_count + FROM tempdb.sys.dm_db_index_operational_stats(DB_ID('tempdb'), NULL, NULL, NULL) os + WHERE os.database_id = DB_ID('tempdb') + AND os.forwarded_fetch_count > 100; -SELECT '#missing_index_xml' AS table_name, * -FROM #missing_index_xml -OPTION ( RECOMPILE ); -SELECT '#missing_index_schema' AS table_name, * -FROM #missing_index_schema -OPTION ( RECOMPILE ); + /* If they want to run sp_BlitzWho and export to table, go for it. */ + IF @OutputTableNameBlitzWho IS NOT NULL + AND @OutputDatabaseName IS NOT NULL + AND @OutputSchemaName IS NOT NULL + AND EXISTS ( SELECT * + FROM sys.databases + WHERE QUOTENAME([name]) = @OutputDatabaseName) + BEGIN + RAISERROR('Logging sp_BlitzWho to table',10,1) WITH NOWAIT; + EXEC sp_BlitzWho @OutputDatabaseName = @UnquotedOutputDatabaseName, @OutputSchemaName = @UnquotedOutputSchemaName, @OutputTableName = @OutputTableNameBlitzWho, @CheckDateOverride = @StartSampleTime, @OutputTableRetentionDays = @OutputTableRetentionDays; + END -SELECT '#missing_index_usage' AS table_name, * -FROM #missing_index_usage -OPTION ( RECOMPILE ); + RAISERROR('Beginning investigatory queries',10,1) WITH NOWAIT; -SELECT '#missing_index_detail' AS table_name, * -FROM #missing_index_detail -OPTION ( RECOMPILE ); -SELECT '#missing_index_pretty' AS table_name, * -FROM #missing_index_pretty -OPTION ( RECOMPILE ); + /* Maintenance Tasks Running - Backup Running - CheckID 1 */ + IF @Seconds > 0 + BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 1',10,1) WITH NOWAIT; + END -END; + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, QueryHash) + SELECT 1 AS CheckID, + 1 AS Priority, + 'Maintenance Tasks Running' AS FindingGroup, + 'Backup Running' AS Finding, + 'http://www.BrentOzar.com/askbrent/backups/' AS URL, + 'Backup of ' + DB_NAME(db.resource_database_id) + ' database (' + (SELECT CAST(CAST(SUM(size * 8.0 / 1024 / 1024) AS BIGINT) AS NVARCHAR) FROM #MasterFiles WHERE database_id = db.resource_database_id) + 'GB) ' + @LineFeed + + CAST(r.percent_complete AS NVARCHAR(100)) + '% complete, has been running since ' + CAST(r.start_time AS NVARCHAR(100)) + '. ' + @LineFeed + + CASE WHEN COALESCE(s.nt_user_name, s.login_name) IS NOT NULL THEN (' Login: ' + COALESCE(s.nt_user_name, s.login_name) + ' ') ELSE '' END AS Details, + 'KILL ' + CAST(r.session_id AS NVARCHAR(100)) + ';' AS HowToStopIt, + pl.query_plan AS QueryPlan, + r.start_time AS StartTime, + s.login_name AS LoginName, + s.nt_user_name AS NTUserName, + s.[program_name] AS ProgramName, + s.[host_name] AS HostName, + db.[resource_database_id] AS DatabaseID, + DB_NAME(db.resource_database_id) AS DatabaseName, + 0 AS OpenTransactionCount, + r.query_hash + FROM sys.dm_exec_requests r + INNER JOIN sys.dm_exec_connections c ON r.session_id = c.session_id + INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id + INNER JOIN ( + SELECT DISTINCT request_session_id, resource_database_id + FROM sys.dm_tran_locks + WHERE resource_type = N'DATABASE' + AND request_mode = N'S' + AND request_status = N'GRANT' + AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.session_id = db.request_session_id + CROSS APPLY sys.dm_exec_query_plan(r.plan_handle) pl + WHERE r.command LIKE 'BACKUP%' + AND r.start_time <= DATEADD(minute, -5, GETDATE()) + AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs); + END -END TRY -BEGIN CATCH - RAISERROR (N'Failure returning debug temp tables', 0,1) WITH NOWAIT; + /* If there's a backup running, add details explaining how long full backup has been taking in the last month. */ + IF @Seconds > 0 AND CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) <> 'SQL Azure' + BEGIN + SET @StringToExecute = 'UPDATE #BlitzFirstResults SET Details = Details + '' Over the last 60 days, the full backup usually takes '' + CAST((SELECT AVG(DATEDIFF(mi, bs.backup_start_date, bs.backup_finish_date)) FROM msdb.dbo.backupset bs WHERE abr.DatabaseName = bs.database_name AND bs.type = ''D'' AND bs.backup_start_date > DATEADD(dd, -60, SYSDATETIMEOFFSET()) AND bs.backup_finish_date IS NOT NULL) AS NVARCHAR(100)) + '' minutes.'' FROM #BlitzFirstResults abr WHERE abr.CheckID = 1 AND EXISTS (SELECT * FROM msdb.dbo.backupset bs WHERE bs.type = ''D'' AND bs.backup_start_date > DATEADD(dd, -60, SYSDATETIMEOFFSET()) AND bs.backup_finish_date IS NOT NULL AND abr.DatabaseName = bs.database_name AND DATEDIFF(mi, bs.backup_start_date, bs.backup_finish_date) > 1)'; + EXEC(@StringToExecute); + END; - IF @sql_select IS NOT NULL - BEGIN - SET @msg = N'Last @sql_select: ' + @sql_select; - RAISERROR(@msg, 0, 1) WITH NOWAIT; - END; - SELECT @msg = @DatabaseName + N' database failed to process. ' + ERROR_MESSAGE(), @error_severity = ERROR_SEVERITY(), @error_state = ERROR_STATE(); - RAISERROR (@msg, @error_severity, @error_state) WITH NOWAIT; - - - WHILE @@TRANCOUNT > 0 - ROLLBACK; + /* Maintenance Tasks Running - DBCC CHECK* Running - CheckID 2 */ + IF @Seconds > 0 AND EXISTS(SELECT * FROM sys.dm_exec_requests WHERE command LIKE 'DBCC%') + BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 2',10,1) WITH NOWAIT; + END - RETURN; -END CATCH; + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, QueryHash) + SELECT 2 AS CheckID, + 1 AS Priority, + 'Maintenance Tasks Running' AS FindingGroup, + 'DBCC CHECK* Running' AS Finding, + 'http://www.BrentOzar.com/askbrent/dbcc/' AS URL, + 'Corruption check of ' + DB_NAME(db.resource_database_id) + ' database (' + (SELECT CAST(CAST(SUM(size * 8.0 / 1024 / 1024) AS BIGINT) AS NVARCHAR) FROM #MasterFiles WHERE database_id = db.resource_database_id) + 'GB) 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, + s.login_name AS LoginName, + s.nt_user_name AS NTUserName, + s.[program_name] AS ProgramName, + s.[host_name] AS HostName, + db.[resource_database_id] AS DatabaseID, + DB_NAME(db.resource_database_id) AS DatabaseName, + 0 AS OpenTransactionCount, + r.query_hash + FROM sys.dm_exec_requests r + INNER JOIN sys.dm_exec_connections c ON r.session_id = c.session_id + INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id + INNER JOIN (SELECT DISTINCT l.request_session_id, l.resource_database_id + FROM sys.dm_tran_locks l + INNER JOIN sys.databases d ON l.resource_database_id = d.database_id + WHERE l.resource_type = N'DATABASE' + AND l.request_mode = N'S' + AND l.request_status = N'GRANT' + AND l.request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.session_id = db.request_session_id + OUTER APPLY sys.dm_exec_query_plan(r.plan_handle) pl + OUTER APPLY sys.dm_exec_sql_text(r.sql_handle) AS t + WHERE r.command LIKE 'DBCC%' + AND CAST(t.text AS NVARCHAR(4000)) NOT LIKE '%dm_db_index_physical_stats%' + AND CAST(t.text AS NVARCHAR(4000)) NOT LIKE '%ALTER INDEX%' + AND CAST(t.text AS NVARCHAR(4000)) NOT LIKE '%fileproperty%' + AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs); + END -/* -Ways to run this thing + /* Maintenance Tasks Running - Restore Running - CheckID 3 */ + IF @Seconds > 0 + BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 3',10,1) WITH NOWAIT; + END ---Debug -EXEC sp_BlitzQueryStore @DatabaseName = 'StackOverflow', @Debug = 1 + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, QueryHash) + SELECT 3 AS CheckID, + 1 AS Priority, + 'Maintenance Tasks Running' AS FindingGroup, + 'Restore Running' AS Finding, + 'http://www.BrentOzar.com/askbrent/backups/' AS URL, + 'Restore of ' + DB_NAME(db.resource_database_id) + ' database (' + (SELECT CAST(CAST(SUM(size * 8.0 / 1024 / 1024) AS BIGINT) AS NVARCHAR) FROM #MasterFiles WHERE database_id = db.resource_database_id) + '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, + s.login_name AS LoginName, + s.nt_user_name AS NTUserName, + s.[program_name] AS ProgramName, + s.[host_name] AS HostName, + db.[resource_database_id] AS DatabaseID, + DB_NAME(db.resource_database_id) AS DatabaseName, + 0 AS OpenTransactionCount, + r.query_hash + FROM sys.dm_exec_requests r + INNER JOIN sys.dm_exec_connections c ON r.session_id = c.session_id + INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id + INNER JOIN ( + SELECT DISTINCT request_session_id, resource_database_id + FROM sys.dm_tran_locks + WHERE resource_type = N'DATABASE' + AND request_mode = N'S' + AND request_status = N'GRANT') AS db ON s.session_id = db.request_session_id + CROSS APPLY sys.dm_exec_query_plan(r.plan_handle) pl + WHERE r.command LIKE 'RESTORE%' + AND s.program_name <> 'SQL Server Log Shipping' + AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs); + END ---Get the top 1 -EXEC sp_BlitzQueryStore @DatabaseName = 'StackOverflow', @Top = 1, @Debug = 1 + /* SQL Server Internal Maintenance - Database File Growing - CheckID 4 */ + IF @Seconds > 0 + BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 4',10,1) WITH NOWAIT; + END ---Use a StartDate -EXEC sp_BlitzQueryStore @DatabaseName = 'StackOverflow', @Top = 1, @StartDate = '20170527' - ---Use an EndDate -EXEC sp_BlitzQueryStore @DatabaseName = 'StackOverflow', @Top = 1, @EndDate = '20170527' - ---Use Both -EXEC sp_BlitzQueryStore @DatabaseName = 'StackOverflow', @Top = 1, @StartDate = '20170526', @EndDate = '20170527' + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount) + SELECT 4 AS CheckID, + 1 AS Priority, + 'SQL Server Internal Maintenance' AS FindingGroup, + 'Database File Growing' AS Finding, + 'http://www.BrentOzar.com/go/instant' AS URL, + 'SQL Server is waiting for Windows to provide storage space for a database restore, a data file growth, or a log file growth. This task has been running since ' + CAST(r.start_time AS NVARCHAR(100)) + '.' + @LineFeed + 'Check the query plan (expert mode) to identify the database involved.' AS Details, + 'Unfortunately, you can''t stop this, but you can prevent it next time. Check out http://www.BrentOzar.com/go/instant for details.' AS HowToStopIt, + pl.query_plan AS QueryPlan, + r.start_time AS StartTime, + s.login_name AS LoginName, + s.nt_user_name AS NTUserName, + s.[program_name] AS ProgramName, + s.[host_name] AS HostName, + NULL AS DatabaseID, + NULL AS DatabaseName, + 0 AS OpenTransactionCount + FROM sys.dm_os_waiting_tasks t + INNER JOIN sys.dm_exec_connections c ON t.session_id = c.session_id + INNER JOIN sys.dm_exec_requests r ON t.session_id = r.session_id + INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id + CROSS APPLY sys.dm_exec_query_plan(r.plan_handle) pl + WHERE t.wait_type = 'PREEMPTIVE_OS_WRITEFILEGATHER' + AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs); + END ---Set a minimum execution count -EXEC sp_BlitzQueryStore @DatabaseName = 'StackOverflow', @Top = 1, @MinimumExecutionCount = 10 + /* Query Problems - Long-Running Query Blocking Others - CheckID 5 */ + IF SERVERPROPERTY('Edition') <> 'SQL Azure' AND @Seconds > 0 AND EXISTS(SELECT * FROM sys.dm_os_waiting_tasks WHERE wait_type LIKE 'LCK%' AND wait_duration_ms > 30000) + BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 5',10,1) WITH NOWAIT; + END ---Set a duration minimum -EXEC sp_BlitzQueryStore @DatabaseName = 'StackOverflow', @Top = 1, @DurationFilter = 5 + SET @StringToExecute = N'INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, QueryText, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, QueryHash) + SELECT 5 AS CheckID, + 1 AS Priority, + ''Query Problems'' AS FindingGroup, + ''Long-Running Query Blocking Others'' AS Finding, + ''http://www.BrentOzar.com/go/blocking'' AS URL, + ''Query in '' + COALESCE(DB_NAME(COALESCE((SELECT TOP 1 dbid FROM sys.dm_exec_sql_text(r.sql_handle)), + (SELECT TOP 1 t.dbid FROM master..sysprocesses spBlocker CROSS APPLY sys.dm_exec_sql_text(spBlocker.sql_handle) t WHERE spBlocker.spid = tBlocked.blocking_session_id))), ''(Unknown)'') + '' has a last request start time of '' + CAST(s.last_request_start_time AS NVARCHAR(100)) + ''. Query follows: ' + + @LineFeed + @LineFeed + + '''+ CAST(COALESCE((SELECT TOP 1 [text] FROM sys.dm_exec_sql_text(r.sql_handle)), + (SELECT TOP 1 [text] FROM master..sysprocesses spBlocker CROSS APPLY sys.dm_exec_sql_text(spBlocker.sql_handle) WHERE spBlocker.spid = tBlocked.blocking_session_id), '''') AS NVARCHAR(2000)) AS Details, + ''KILL '' + CAST(tBlocked.blocking_session_id AS NVARCHAR(100)) + '';'' AS HowToStopIt, + (SELECT TOP 1 query_plan FROM sys.dm_exec_query_plan(r.plan_handle)) AS QueryPlan, + COALESCE((SELECT TOP 1 [text] FROM sys.dm_exec_sql_text(r.sql_handle)), + (SELECT TOP 1 [text] FROM master..sysprocesses spBlocker CROSS APPLY sys.dm_exec_sql_text(spBlocker.sql_handle) WHERE spBlocker.spid = tBlocked.blocking_session_id)) AS QueryText, + r.start_time AS StartTime, + s.login_name AS LoginName, + s.nt_user_name AS NTUserName, + s.[program_name] AS ProgramName, + s.[host_name] AS HostName, + r.[database_id] AS DatabaseID, + DB_NAME(r.database_id) AS DatabaseName, + 0 AS OpenTransactionCount, + r.query_hash + FROM sys.dm_os_waiting_tasks tBlocked + INNER JOIN sys.dm_exec_sessions s ON tBlocked.blocking_session_id = s.session_id + LEFT OUTER JOIN sys.dm_exec_requests r ON s.session_id = r.session_id + INNER JOIN sys.dm_exec_connections c ON s.session_id = c.session_id + WHERE tBlocked.wait_type LIKE ''LCK%'' AND tBlocked.wait_duration_ms > 30000 + /* And the blocking session ID is not blocked by anyone else: */ + AND NOT EXISTS(SELECT * FROM sys.dm_os_waiting_tasks tBlocking WHERE s.session_id = tBlocking.session_id AND tBlocking.session_id <> tBlocking.blocking_session_id AND tBlocking.blocking_session_id IS NOT NULL) + AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs);'; + EXECUTE sp_executesql @StringToExecute; + END; + + /* Query Problems - Plan Cache Erased Recently - CheckID 7 */ + IF DATEADD(mi, -15, SYSDATETIME()) < (SELECT TOP 1 creation_time FROM sys.dm_exec_query_stats ORDER BY creation_time) + BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 7',10,1) WITH NOWAIT; + END ---Look for a stored procedure name (that doesn't exist!) -EXEC sp_BlitzQueryStore @DatabaseName = 'StackOverflow', @Top = 1, @StoredProcName = 'blah' + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) + SELECT TOP 1 7 AS CheckID, + 50 AS Priority, + 'Query Problems' AS FindingGroup, + 'Plan Cache Erased Recently' AS Finding, + 'http://www.BrentOzar.com/askbrent/plan-cache-erased-recently/' AS URL, + 'The oldest query in the plan cache was created at ' + CAST(creation_time AS NVARCHAR(50)) + '. ' + @LineFeed + @LineFeed + + 'This indicates that someone ran DBCC FREEPROCCACHE at that time,' + @LineFeed + + 'Giving SQL Server temporary amnesia. Now, as queries come in,' + @LineFeed + + 'SQL Server has to use a lot of CPU power in order to build execution' + @LineFeed + + 'plans and put them in cache again. This causes high CPU loads.' AS Details, + 'Find who did that, and stop them from doing it again.' AS HowToStopIt + FROM sys.dm_exec_query_stats + ORDER BY creation_time; + END; ---Look for a stored procedure name that does (at least On My Computer®) -EXEC sp_BlitzQueryStore @DatabaseName = 'StackOverflow', @Top = 1, @StoredProcName = 'UserReportExtended' ---Look for failed queries -EXEC sp_BlitzQueryStore @DatabaseName = 'StackOverflow', @Top = 1, @Failed = 1 + /* Query Problems - Sleeping Query with Open Transactions - CheckID 8 */ + IF @Seconds > 0 + BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 8',10,1) WITH NOWAIT; + END ---Filter by plan_id -EXEC sp_BlitzQueryStore @DatabaseName = 'StackOverflow', @PlanIdFilter = 3356 + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, OpenTransactionCount) + SELECT 8 AS CheckID, + 50 AS Priority, + 'Query Problems' AS FindingGroup, + 'Sleeping Query with Open Transactions' AS Finding, + 'http://www.brentozar.com/askbrent/sleeping-query-with-open-transactions/' AS URL, + 'Database: ' + DB_NAME(db.resource_database_id) + @LineFeed + 'Host: ' + s.[host_name] + @LineFeed + 'Program: ' + s.[program_name] + @LineFeed + 'Asleep with open transactions and locks since ' + CAST(s.last_request_end_time AS NVARCHAR(100)) + '. ' AS Details, + 'KILL ' + CAST(s.session_id AS NVARCHAR(100)) + ';' AS HowToStopIt, + s.last_request_start_time AS StartTime, + s.login_name AS LoginName, + s.nt_user_name AS NTUserName, + s.[program_name] AS ProgramName, + s.[host_name] AS HostName, + db.[resource_database_id] AS DatabaseID, + DB_NAME(db.resource_database_id) AS DatabaseName, + (SELECT TOP 1 [text] FROM sys.dm_exec_sql_text(c.most_recent_sql_handle)) AS QueryText, + sessions_with_transactions.open_transaction_count AS OpenTransactionCount + FROM (SELECT session_id, SUM(open_transaction_count) AS open_transaction_count FROM sys.dm_exec_requests WHERE open_transaction_count > 0 GROUP BY session_id) AS sessions_with_transactions + INNER JOIN sys.dm_exec_sessions s ON sessions_with_transactions.session_id = s.session_id + INNER JOIN sys.dm_exec_connections c ON s.session_id = c.session_id + INNER JOIN ( + SELECT DISTINCT request_session_id, resource_database_id + FROM sys.dm_tran_locks + WHERE resource_type = N'DATABASE' + AND request_mode = N'S' + AND request_status = N'GRANT' + AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.session_id = db.request_session_id + WHERE s.status = 'sleeping' + AND s.last_request_end_time < DATEADD(ss, -10, SYSDATETIME()) + AND EXISTS(SELECT * FROM sys.dm_tran_locks WHERE request_session_id = s.session_id + AND NOT (resource_type = N'DATABASE' AND request_mode = N'S' AND request_status = N'GRANT' AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE')); + END ---Filter by query_id -EXEC sp_BlitzQueryStore @DatabaseName = 'StackOverflow', @QueryIdFilter = 2958 + /*Query Problems - Clients using implicit transactions - CheckID 37 */ + IF @Seconds > 0 + AND ( @@VERSION NOT LIKE 'Microsoft SQL Server 2005%' + AND @@VERSION NOT LIKE 'Microsoft SQL Server 2008%' + AND @@VERSION NOT LIKE 'Microsoft SQL Server 2008 R2%' ) + BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 37',10,1) WITH NOWAIT; + END -*/ + SET @StringToExecute = N'INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, OpenTransactionCount) + SELECT 37 AS CheckId, + 50 AS Priority, + ''Query Problems'' AS FindingsGroup, + ''Implicit Transactions'', + ''https://www.brentozar.com/go/ImplicitTransactions/'' AS URL, + ''Database: '' + DB_NAME(s.database_id) + '' '' + CHAR(13) + CHAR(10) + + ''Host: '' + s.[host_name] + '' '' + CHAR(13) + CHAR(10) + + ''Program: '' + s.[program_name] + '' '' + CHAR(13) + CHAR(10) + + CONVERT(NVARCHAR(10), s.open_transaction_count) + + '' open transactions since: '' + + CONVERT(NVARCHAR(30), tat.transaction_begin_time) + ''. '' + AS Details, + ''Run sp_BlitzWho and check the is_implicit_transaction column to spot the culprits. +If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, + tat.transaction_begin_time, + s.login_name, + s.nt_user_name, + s.program_name, + s.host_name, + s.database_id, + DB_NAME(s.database_id) AS DatabaseName, + NULL AS Querytext, + s.open_transaction_count AS OpenTransactionCount + FROM sys.dm_tran_active_transactions AS tat + LEFT JOIN sys.dm_tran_session_transactions AS tst + ON tst.transaction_id = tat.transaction_id + LEFT JOIN sys.dm_exec_sessions AS s + ON s.session_id = tst.session_id + WHERE tat.name = ''implicit_transaction''; + ' + EXECUTE sp_executesql @StringToExecute; + END; -END; + /* Query Problems - Query Rolling Back - CheckID 9 */ + IF @Seconds > 0 + BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 9',10,1) WITH NOWAIT; + END -GO -IF OBJECT_ID('dbo.sp_BlitzWho') IS NULL - EXEC ('CREATE PROCEDURE dbo.sp_BlitzWho AS RETURN 0;') -GO + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, QueryHash) + SELECT 9 AS CheckID, + 1 AS Priority, + 'Query Problems' AS FindingGroup, + 'Query Rolling Back' AS Finding, + 'http://www.BrentOzar.com/askbrent/rollback/' AS URL, + 'Rollback started at ' + CAST(r.start_time AS NVARCHAR(100)) + ', is ' + CAST(r.percent_complete AS NVARCHAR(100)) + '% complete.' AS Details, + 'Unfortunately, you can''t stop this. Whatever you do, don''t restart the server in an attempt to fix it - SQL Server will keep rolling back.' AS HowToStopIt, + r.start_time AS StartTime, + s.login_name AS LoginName, + s.nt_user_name AS NTUserName, + s.[program_name] AS ProgramName, + s.[host_name] AS HostName, + db.[resource_database_id] AS DatabaseID, + DB_NAME(db.resource_database_id) AS DatabaseName, + (SELECT TOP 1 [text] FROM sys.dm_exec_sql_text(c.most_recent_sql_handle)) AS QueryText, + r.query_hash + FROM sys.dm_exec_sessions s + INNER JOIN sys.dm_exec_connections c ON s.session_id = c.session_id + INNER JOIN sys.dm_exec_requests r ON s.session_id = r.session_id + LEFT OUTER JOIN ( + SELECT DISTINCT request_session_id, resource_database_id + FROM sys.dm_tran_locks + WHERE resource_type = N'DATABASE' + AND request_mode = N'S' + AND request_status = N'GRANT' + AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.session_id = db.request_session_id + WHERE r.status = 'rollback'; + END -ALTER PROCEDURE dbo.sp_BlitzWho - @Help TINYINT = 0 , - @ShowSleepingSPIDs TINYINT = 0, - @ExpertMode BIT = 0, - @Debug BIT = 0, - @OutputDatabaseName NVARCHAR(256) = NULL , - @OutputSchemaName NVARCHAR(256) = NULL , - @OutputTableName NVARCHAR(256) = NULL , - @OutputTableRetentionDays TINYINT = 3 , - @MinElapsedSeconds INT = 0 , - @MinCPUTime INT = 0 , - @MinLogicalReads INT = 0 , - @MinPhysicalReads INT = 0 , - @MinWrites INT = 0 , - @MinTempdbMB INT = 0 , - @MinRequestedMemoryKB INT = 0 , - @MinBlockingSeconds INT = 0 , - @CheckDateOverride DATETIMEOFFSET = NULL, - @Version VARCHAR(30) = NULL OUTPUT, - @VersionDate DATETIME = NULL OUTPUT, - @VersionCheckMode BIT = 0, - @SortOrder NVARCHAR(256) = N'elapsed time' -AS -BEGIN - SET NOCOUNT ON; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - - SELECT @Version = '8.0', @VersionDate = '20210117'; - - IF(@VersionCheckMode = 1) + IF @Seconds > 0 BEGIN - RETURN; - END; + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) + SELECT + 47 AS CheckId, + 50 AS Priority, + 'Query Problems' AS FindingsGroup, + 'High Percentage Of Runnable Queries' AS Finding, + 'https://erikdarlingdata.com/go/RunnableQueue/' AS URL, + 'On the ' + + CASE WHEN y.pass = 1 + THEN '1st' + ELSE '2nd' + END + + ' pass, ' + + RTRIM(y.runnable_pct) + + '% of your queries were waiting to get on a CPU to run. ' + + ' This can indicate CPU pressure.' + FROM + ( + SELECT + 1 AS pass, + x.total, + x.runnable, + CONVERT(decimal(5,2), + ( + x.runnable / + (1. * NULLIF(x.total, 0)) + ) + ) * 100. AS runnable_pct + FROM + ( + SELECT + COUNT_BIG(*) AS total, + SUM(CASE WHEN status = 'runnable' + THEN 1 + ELSE 0 + END) AS runnable + FROM sys.dm_exec_requests + WHERE session_id > 50 + ) AS x + ) AS y + WHERE y.runnable_pct > 20.; + END + /* Server Performance - Too Much Free Memory - CheckID 34 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 34',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) + SELECT 34 AS CheckID, + 50 AS Priority, + 'Server Performance' AS FindingGroup, + 'Too Much Free Memory' AS Finding, + 'https://BrentOzar.com/go/freememory' AS URL, + CAST((CAST(cFree.cntr_value AS BIGINT) / 1024 / 1024 ) AS NVARCHAR(100)) + N'GB of free memory inside SQL Server''s buffer pool,' + @LineFeed + ' which is ' + CAST((CAST(cTotal.cntr_value AS BIGINT) / 1024 / 1024) AS NVARCHAR(100)) + N'GB. You would think lots of free memory would be good, but check out the URL for more information.' AS Details, + 'Run sp_BlitzCache @SortOrder = ''memory grant'' to find queries with huge memory grants and tune them.' AS HowToStopIt + FROM sys.dm_os_performance_counters cFree + INNER JOIN sys.dm_os_performance_counters cTotal ON cTotal.object_name LIKE N'%Memory Manager%' + AND cTotal.counter_name = N'Total Server Memory (KB) ' + WHERE cFree.object_name LIKE N'%Memory Manager%' + AND cFree.counter_name = N'Free Memory (KB) ' + AND CAST(cFree.cntr_value AS BIGINT) > 20480000000 + AND CAST(cTotal.cntr_value AS BIGINT) * .3 <= CAST(cFree.cntr_value AS BIGINT) + AND CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) NOT LIKE '%Standard%'; - IF @Help = 1 + /* Server Performance - Target Memory Lower Than Max - CheckID 35 */ + IF SERVERPROPERTY('Edition') <> 'SQL Azure' BEGIN - PRINT ' -sp_BlitzWho from http://FirstResponderKit.org + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 35',10,1) WITH NOWAIT; + END -This script gives you a snapshot of everything currently executing on your SQL Server. + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) + SELECT 35 AS CheckID, + 10 AS Priority, + 'Server Performance' AS FindingGroup, + 'Target Memory Lower Than Max' AS Finding, + 'https://BrentOzar.com/go/target' AS URL, + N'Max server memory is ' + CAST(cMax.value_in_use AS NVARCHAR(50)) + N' MB but target server memory is only ' + CAST((CAST(cTarget.cntr_value AS BIGINT) / 1024) AS NVARCHAR(50)) + N' MB,' + @LineFeed + + N'indicating that SQL Server may be under external memory pressure or max server memory may be set too high.' AS Details, + 'Investigate what OS processes are using memory, and double-check the max server memory setting.' AS HowToStopIt + FROM sys.configurations cMax + INNER JOIN sys.dm_os_performance_counters cTarget ON cTarget.object_name LIKE N'%Memory Manager%' + AND cTarget.counter_name = N'Target Server Memory (KB) ' + WHERE cMax.name = 'max server memory (MB)' + AND CAST(cMax.value_in_use AS BIGINT) >= 1.5 * (CAST(cTarget.cntr_value AS BIGINT) / 1024) + AND CAST(cMax.value_in_use AS BIGINT) < 2147483647 /* Not set to default of unlimited */ + AND CAST(cTarget.cntr_value AS BIGINT) < .8 * (SELECT available_physical_memory_kb FROM sys.dm_os_sys_memory); /* Target memory less than 80% of physical memory (in case they set max too high) */ + END -To learn more, visit http://FirstResponderKit.org where you can download new -versions for free, watch training videos on how it works, get more info on -the findings, contribute your own code, and more. + /* Server Info - Database Size, Total GB - CheckID 21 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 21',10,1) WITH NOWAIT; + END -Known limitations of this version: - - Only Microsoft-supported versions of SQL Server. Sorry, 2005 and 2000. - - Outputting to table is only supported with SQL Server 2012 and higher. - - If @OutputDatabaseName and @OutputSchemaName are populated, the database and - schema must already exist. We will not create them, only the table. - -MIT License + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) + SELECT 21 AS CheckID, + 251 AS Priority, + 'Server Info' AS FindingGroup, + 'Database Size, Total GB' AS Finding, + CAST(SUM (CAST(size AS BIGINT)*8./1024./1024.) AS VARCHAR(100)) AS Details, + SUM (CAST(size AS BIGINT))*8./1024./1024. AS DetailsInt, + 'http://www.BrentOzar.com/askbrent/' AS URL + FROM #MasterFiles + WHERE database_id > 4; -Copyright (c) 2021 Brent Ozar Unlimited + /* Server Info - Database Count - CheckID 22 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 22',10,1) WITH NOWAIT; + END -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) + SELECT 22 AS CheckID, + 251 AS Priority, + 'Server Info' AS FindingGroup, + 'Database Count' AS Finding, + CAST(SUM(1) AS VARCHAR(100)) AS Details, + SUM (1) AS DetailsInt, + 'http://www.BrentOzar.com/askbrent/' AS URL + FROM sys.databases + WHERE database_id > 4; -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. + /* Server Info - Memory Grants pending - CheckID 39 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 39',10,1) WITH NOWAIT; + END -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -'; -RETURN; -END; /* @Help = 1 */ + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) + SELECT 39 AS CheckID, + 50 AS Priority, + 'Server Performance' AS FindingGroup, + 'Memory Grants Pending' AS Finding, + CAST(PendingGrants.Details AS NVARCHAR(50)) AS Details, + PendingGrants.DetailsInt, + 'https://www.brentozar.com/blitz/memory-grants/' AS URL + FROM + ( + SELECT + COUNT(1) AS Details, + COUNT(1) AS DetailsInt + FROM sys.dm_exec_query_memory_grants AS Grants + WHERE queue_id IS NOT NULL + ) AS PendingGrants + WHERE PendingGrants.Details > 0; -/* Get the major and minor build numbers */ -DECLARE @ProductVersion NVARCHAR(128) - ,@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]) - ,@EnhanceFlag BIT = 0 - ,@BlockingCheck NVARCHAR(MAX) - ,@StringToSelect NVARCHAR(MAX) - ,@StringToExecute NVARCHAR(MAX) - ,@OutputTableCleanupDate DATE - ,@SessionWaits BIT = 0 - ,@SessionWaitsSQL NVARCHAR(MAX) = - N'LEFT JOIN ( SELECT DISTINCT - wait.session_id , - ( SELECT TOP 5 waitwait.wait_type + N'' ('' - + CAST(MAX(waitwait.wait_time_ms) AS NVARCHAR(128)) - + N'' ms), '' - FROM sys.dm_exec_session_wait_stats AS waitwait - WHERE waitwait.session_id = wait.session_id - GROUP BY waitwait.wait_type - HAVING SUM(waitwait.wait_time_ms) > 5 - ORDER BY 1 - FOR - XML PATH('''') ) AS session_wait_info - FROM sys.dm_exec_session_wait_stats AS wait ) AS wt2 - ON s.session_id = wt2.session_id - LEFT JOIN sys.dm_exec_query_stats AS session_stats - ON r.sql_handle = session_stats.sql_handle - AND r.plan_handle = session_stats.plan_handle - AND r.statement_start_offset = session_stats.statement_start_offset - AND r.statement_end_offset = session_stats.statement_end_offset' - ,@QueryStatsXMLselect NVARCHAR(MAX) = N' CAST(COALESCE(qs_live.query_plan, '''') AS XML) AS live_query_plan , ' - ,@QueryStatsXMLSQL NVARCHAR(MAX) = N'OUTER APPLY sys.dm_exec_query_statistics_xml(s.session_id) qs_live' - ,@ObjectFullName NVARCHAR(2000) - ,@OutputTableNameQueryStats_View NVARCHAR(256) - ,@LineFeed NVARCHAR(MAX) /* Had to set as MAX up from 10 as it was truncating the view creation*/; + /* Server Info - Memory Grant/Workspace info - CheckID 40 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 40',10,1) WITH NOWAIT; + END + + DECLARE @MaxWorkspace BIGINT + SET @MaxWorkspace = (SELECT CAST(cntr_value AS BIGINT)/1024 FROM #PerfmonStats WHERE counter_name = N'Maximum Workspace Memory (KB)') + + IF (@MaxWorkspace IS NULL + OR @MaxWorkspace = 0) + BEGIN + SET @MaxWorkspace = 1 + END -/* Let's get @SortOrder set to lower case here for comparisons later */ -SET @SortOrder = REPLACE(LOWER(@SortOrder), N' ', N'_'); + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) + SELECT 40 AS CheckID, + 251 AS Priority, + 'Server Info' AS FindingGroup, + 'Memory Grant/Workspace info' AS Finding, + + 'Grants Outstanding: ' + CAST((SELECT COUNT(*) FROM sys.dm_exec_query_memory_grants WHERE queue_id IS NULL) AS NVARCHAR(50)) + @LineFeed + + 'Total Granted(MB): ' + CAST(ISNULL(SUM(Grants.granted_memory_kb) / 1024, 0) AS NVARCHAR(50)) + @LineFeed + + 'Total WorkSpace(MB): ' + CAST(ISNULL(@MaxWorkspace, 0) AS NVARCHAR(50)) + @LineFeed + + 'Granted workspace: ' + CAST(ISNULL((CAST(SUM(Grants.granted_memory_kb) / 1024 AS MONEY) + / CAST(@MaxWorkspace AS MONEY)) * 100, 0) AS NVARCHAR(50)) + '%' + @LineFeed + + 'Oldest Grant in seconds: ' + CAST(ISNULL(DATEDIFF(SECOND, MIN(Grants.request_time), GETDATE()), 0) AS NVARCHAR(50)) AS Details, + (SELECT COUNT(*) FROM sys.dm_exec_query_memory_grants WHERE queue_id IS NULL) AS DetailsInt, + 'http://www.BrentOzar.com/askbrent/' AS URL + FROM sys.dm_exec_query_memory_grants AS Grants; -SET @ProductVersion = CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)); -SELECT @ProductVersionMajor = SUBSTRING(@ProductVersion, 1,CHARINDEX('.', @ProductVersion) + 1 ), - @ProductVersionMinor = PARSENAME(CONVERT(VARCHAR(32), @ProductVersion), 2) -IF EXISTS (SELECT * FROM sys.all_columns WHERE object_id = OBJECT_ID('sys.dm_exec_query_statistics_xml') AND name = 'query_plan') - BEGIN - SET @QueryStatsXMLselect = N' CAST(COALESCE(qs_live.query_plan, '''') AS XML) AS live_query_plan , '; - SET @QueryStatsXMLSQL = N'OUTER APPLY sys.dm_exec_query_statistics_xml(s.session_id) qs_live'; - END - ELSE - BEGIN - SET @QueryStatsXMLselect = N' NULL AS live_query_plan , '; - SET @QueryStatsXMLSQL = N' '; - END + /* Query Problems - Queries with high memory grants - CheckID 46 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 46',10,1) WITH NOWAIT; + END -SELECT - @OutputTableNameQueryStats_View = QUOTENAME(@OutputTableName + '_Deltas'), - @OutputDatabaseName = QUOTENAME(@OutputDatabaseName), - @OutputSchemaName = QUOTENAME(@OutputSchemaName), - @OutputTableName = QUOTENAME(@OutputTableName), - @LineFeed = CHAR(13) + CHAR(10); + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, URL, QueryText, QueryPlan) + SELECT 46 AS CheckID, + 100 AS Priority, + 'Query Problems' AS FindingGroup, + 'Query with a memory grant exceeding ' + +CAST(@MemoryGrantThresholdPct AS NVARCHAR(15)) + +'%' AS Finding, + 'Granted size: '+ CAST(CAST(Grants.granted_memory_kb / 1024 AS INT) AS NVARCHAR(50)) + +N'MB ' + + @LineFeed + +N'Granted pct of max workspace: ' + + CAST(ISNULL((CAST(Grants.granted_memory_kb / 1024 AS MONEY) + / CAST(@MaxWorkspace AS MONEY)) * 100, 0) AS NVARCHAR(50)) + '%' + + @LineFeed + +N'SQLHandle: ' + +CONVERT(NVARCHAR(128),Grants.[sql_handle],1), + 'https://www.brentozar.com/memory-grants-sql-servers-public-toilet/' AS URL, + SQLText.[text], + QueryPlan.query_plan + FROM sys.dm_exec_query_memory_grants AS Grants + OUTER APPLY sys.dm_exec_sql_text(Grants.[sql_handle]) AS SQLText + OUTER APPLY sys.dm_exec_query_plan(Grants.[plan_handle]) AS QueryPlan + WHERE Grants.granted_memory_kb > ((@MemoryGrantThresholdPct/100.00)*(@MaxWorkspace*1024)); -IF @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @OutputTableName IS NOT NULL - AND EXISTS ( SELECT * - FROM sys.databases - WHERE QUOTENAME([name]) = @OutputDatabaseName) - BEGIN - SET @ExpertMode = 1; /* Force ExpertMode when we're logging to table */ + /* Query Problems - Memory Leak in USERSTORE_TOKENPERM Cache - CheckID 45 */ + IF EXISTS (SELECT * FROM sys.all_columns WHERE object_id = OBJECT_ID('sys.dm_os_memory_clerks') AND name = 'pages_kb') + BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 45',10,1) WITH NOWAIT; + END - /* Create the table if it doesn't exist */ - SET @StringToExecute = N'USE ' - + @OutputDatabaseName - + N'; IF EXISTS(SELECT * FROM ' - + @OutputDatabaseName - + N'.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' - + @OutputSchemaName - + N''') AND NOT EXISTS (SELECT * FROM ' - + @OutputDatabaseName - + N'.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''' - + @OutputSchemaName + N''' AND QUOTENAME(TABLE_NAME) = ''' - + @OutputTableName + N''') CREATE TABLE ' - + @OutputSchemaName + N'.' - + @OutputTableName - + N'('; - SET @StringToExecute = @StringToExecute + N' - ID INT IDENTITY(1,1) NOT NULL, - ServerName NVARCHAR(128) NOT NULL, - CheckDate DATETIMEOFFSET NOT NULL, - [elapsed_time] [varchar](41) NULL, - [session_id] [smallint] NOT NULL, - [database_name] [nvarchar](128) NULL, - [query_text] [nvarchar](max) NULL, - [query_plan] [xml] NULL, - [live_query_plan] [xml] NULL, - [query_cost] [float] NULL, - [status] [nvarchar](30) NOT NULL, - [wait_info] [nvarchar](max) NULL, - [top_session_waits] [nvarchar](max) NULL, - [blocking_session_id] [smallint] NULL, - [open_transaction_count] [int] NULL, - [is_implicit_transaction] [int] NOT NULL, - [nt_domain] [nvarchar](128) NULL, - [host_name] [nvarchar](128) NULL, - [login_name] [nvarchar](128) NOT NULL, - [nt_user_name] [nvarchar](128) NULL, - [program_name] [nvarchar](128) NULL, - [fix_parameter_sniffing] [nvarchar](150) NULL, - [client_interface_name] [nvarchar](32) NULL, - [login_time] [datetime] NOT NULL, - [start_time] [datetime] NULL, - [request_time] [datetime] NULL, - [request_cpu_time] [int] NULL, - [request_logical_reads] [bigint] NULL, - [request_writes] [bigint] NULL, - [request_physical_reads] [bigint] NULL, - [session_cpu] [int] NOT NULL, - [session_logical_reads] [bigint] NOT NULL, - [session_physical_reads] [bigint] NOT NULL, - [session_writes] [bigint] NOT NULL, - [tempdb_allocations_mb] [decimal](38, 2) NULL, - [memory_usage] [int] NOT NULL, - [estimated_completion_time] [bigint] NULL, - [percent_complete] [real] NULL, - [deadlock_priority] [int] NULL, - [transaction_isolation_level] [varchar](33) NOT NULL, - [degree_of_parallelism] [smallint] NULL, - [last_dop] [bigint] NULL, - [min_dop] [bigint] NULL, - [max_dop] [bigint] NULL, - [last_grant_kb] [bigint] NULL, - [min_grant_kb] [bigint] NULL, - [max_grant_kb] [bigint] NULL, - [last_used_grant_kb] [bigint] NULL, - [min_used_grant_kb] [bigint] NULL, - [max_used_grant_kb] [bigint] NULL, - [last_ideal_grant_kb] [bigint] NULL, - [min_ideal_grant_kb] [bigint] NULL, - [max_ideal_grant_kb] [bigint] NULL, - [last_reserved_threads] [bigint] NULL, - [min_reserved_threads] [bigint] NULL, - [max_reserved_threads] [bigint] NULL, - [last_used_threads] [bigint] NULL, - [min_used_threads] [bigint] NULL, - [max_used_threads] [bigint] NULL, - [grant_time] [varchar](20) NULL, - [requested_memory_kb] [bigint] NULL, - [grant_memory_kb] [bigint] NULL, - [is_request_granted] [varchar](39) NOT NULL, - [required_memory_kb] [bigint] NULL, - [query_memory_grant_used_memory_kb] [bigint] NULL, - [ideal_memory_kb] [bigint] NULL, - [is_small] [bit] NULL, - [timeout_sec] [int] NULL, - [resource_semaphore_id] [smallint] NULL, - [wait_order] [varchar](20) NULL, - [wait_time_ms] [varchar](20) NULL, - [next_candidate_for_memory_grant] [varchar](3) NOT NULL, - [target_memory_kb] [bigint] NULL, - [max_target_memory_kb] [varchar](30) NULL, - [total_memory_kb] [bigint] NULL, - [available_memory_kb] [bigint] NULL, - [granted_memory_kb] [bigint] NULL, - [query_resource_semaphore_used_memory_kb] [bigint] NULL, - [grantee_count] [int] NULL, - [waiter_count] [int] NULL, - [timeout_error_count] [bigint] NULL, - [forced_grant_count] [varchar](30) NULL, - [workload_group_name] [sysname] NULL, - [resource_pool_name] [sysname] NULL, - [context_info] [varchar](128) NULL, - [query_hash] [binary](8) NULL, - [query_plan_hash] [binary](8) NULL, - [sql_handle] [varbinary] (64) NULL, - [plan_handle] [varbinary] (64) NULL, - [statement_start_offset] INT NULL, - [statement_end_offset] INT NULL, - JoinKey AS ServerName + CAST(CheckDate AS NVARCHAR(50)), - PRIMARY KEY CLUSTERED (ID ASC));'; - IF @Debug = 1 - BEGIN - PRINT CONVERT(VARCHAR(8000), SUBSTRING(@StringToExecute, 0, 8000)) - PRINT CONVERT(VARCHAR(8000), SUBSTRING(@StringToExecute, 8000, 16000)) - END - EXEC(@StringToExecute); + /* SQL 2012+ version */ + SET @StringToExecute = N' + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, URL) + SELECT 45 AS CheckID, + 50 AS Priority, + ''Query Problems'' AS FindingsGroup, + ''Memory Leak in USERSTORE_TOKENPERM Cache'' AS Finding, + N''UserStore_TokenPerm clerk is using '' + CAST(CAST(SUM(CASE WHEN type = ''USERSTORE_TOKENPERM'' AND name = ''TokenAndPermUserStore'' THEN pages_kb * 1.0 ELSE 0.0 END) / 1024.0 / 1024.0 AS INT) AS NVARCHAR(100)) + + N''GB RAM, total buffer pool is '' + CAST(CAST(SUM(pages_kb) / 1024.0 / 1024.0 AS INT) AS NVARCHAR(100)) + N''GB.'' + AS details, + ''https://www.BrentOzar.com/go/userstore'' AS URL + FROM sys.dm_os_memory_clerks + HAVING SUM(CASE WHEN type = ''USERSTORE_TOKENPERM'' AND name = ''TokenAndPermUserStore'' THEN pages_kb * 1.0 ELSE 0.0 END) / SUM(pages_kb) >= 0.1 + AND SUM(pages_kb) / 1024.0 / 1024.0 >= 1; /* At least 1GB RAM overall */'; + EXEC sp_executesql @StringToExecute; + END + ELSE + BEGIN + /* Antiques Roadshow SQL 2008R2 - version */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 45 (Legacy version)',10,1) WITH NOWAIT; + END - /* If the table doesn't have the new JoinKey computed column, add it. See Github #2162. */ - SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableName; - SET @StringToExecute = N'IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns - WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''JoinKey'') - ALTER TABLE ' + @ObjectFullName + N' ADD JoinKey AS ServerName + CAST(CheckDate AS NVARCHAR(50));'; - EXEC(@StringToExecute); + SET @StringToExecute = N' + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, URL) + SELECT 45 AS CheckID, + 50 AS Priority, + ''Performance'' AS FindingsGroup, + ''Memory Leak in USERSTORE_TOKENPERM Cache'' AS Finding, + N''UserStore_TokenPerm clerk is using '' + CAST(CAST(SUM(CASE WHEN type = ''USERSTORE_TOKENPERM'' AND name = ''TokenAndPermUserStore'' THEN single_pages_kb + multi_pages_kb * 1.0 ELSE 0.0 END) / 1024.0 / 1024.0 AS INT) AS NVARCHAR(100)) + + N''GB RAM, total buffer pool is '' + CAST(CAST(SUM(single_pages_kb + multi_pages_kb) / 1024.0 / 1024.0 AS INT) AS NVARCHAR(100)) + N''GB.'' + AS details, + ''https://www.BrentOzar.com/go/userstore'' AS URL + FROM sys.dm_os_memory_clerks + HAVING SUM(CASE WHEN type = ''USERSTORE_TOKENPERM'' AND name = ''TokenAndPermUserStore'' THEN single_pages_kb + multi_pages_kb * 1.0 ELSE 0.0 END) / SUM(single_pages_kb + multi_pages_kb) >= 0.1 + AND SUM(single_pages_kb + multi_pages_kb) / 1024.0 / 1024.0 >= 1; /* At least 1GB RAM overall */'; + EXEC sp_executesql @StringToExecute; + END - /* Delete history older than @OutputTableRetentionDays */ - SET @OutputTableCleanupDate = CAST( (DATEADD(DAY, -1 * @OutputTableRetentionDays, GETDATE() ) ) AS DATE); - SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' - + @OutputDatabaseName - + N'.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' - + @OutputSchemaName + N''') DELETE ' - + @OutputDatabaseName + '.' - + @OutputSchemaName + '.' - + @OutputTableName - + N' WHERE ServerName = @SrvName AND CheckDate < @CheckDate;'; - IF @Debug = 1 - BEGIN - PRINT CONVERT(VARCHAR(8000), SUBSTRING(@StringToExecute, 0, 8000)) - PRINT CONVERT(VARCHAR(8000), SUBSTRING(@StringToExecute, 8000, 16000)) - END - EXEC sp_executesql @StringToExecute, - N'@SrvName NVARCHAR(128), @CheckDate date', - @@SERVERNAME, @OutputTableCleanupDate; - SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableNameQueryStats_View; - /* Create the view */ - IF OBJECT_ID(@ObjectFullName) IS NULL - BEGIN - SET @StringToExecute = N'USE ' - + @OutputDatabaseName - + N'; EXEC (''CREATE VIEW ' - + @OutputSchemaName + '.' - + @OutputTableNameQueryStats_View + N' AS ' + @LineFeed - + N'WITH MaxQueryDuration AS ' + @LineFeed - + N'( ' + @LineFeed - + N' SELECT ' + @LineFeed - + N' MIN([ID]) AS [MinID], ' + @LineFeed - + N' MAX([ID]) AS [MaxID] ' + @LineFeed - + N' FROM ' + @OutputSchemaName + '.' + @OutputTableName + '' + @LineFeed - + N' GROUP BY [ServerName], ' + @LineFeed - + N' [session_id], ' + @LineFeed - + N' [database_name], ' + @LineFeed - + N' [request_time], ' + @LineFeed - + N' [start_time], ' + @LineFeed - + N' [sql_handle] ' + @LineFeed - + N') ' + @LineFeed - + N'SELECT ' + @LineFeed - + N' [ID], ' + @LineFeed - + N' [ServerName], ' + @LineFeed - + N' [CheckDate], ' + @LineFeed - + N' [elapsed_time], ' + @LineFeed - + N' [session_id], ' + @LineFeed - + N' [database_name], ' + @LineFeed - + N' [query_text_snippet], ' + @LineFeed - + N' [query_plan], ' + @LineFeed - + N' [live_query_plan], ' + @LineFeed - + N' [query_cost], ' + @LineFeed - + N' [status], ' + @LineFeed - + N' [wait_info], ' + @LineFeed - + N' [top_session_waits], ' + @LineFeed - + N' [blocking_session_id], ' + @LineFeed - + N' [open_transaction_count], ' + @LineFeed - + N' [is_implicit_transaction], ' + @LineFeed - + N' [nt_domain], ' + @LineFeed - + N' [host_name], ' + @LineFeed - + N' [login_name], ' + @LineFeed - + N' [nt_user_name], ' + @LineFeed - + N' [program_name], ' + @LineFeed - + N' [fix_parameter_sniffing], ' + @LineFeed - + N' [client_interface_name], ' + @LineFeed - + N' [login_time], ' + @LineFeed - + N' [start_time], ' + @LineFeed - + N' [request_time], ' + @LineFeed - + N' [request_cpu_time], ' + @LineFeed - + N' [degree_of_parallelism], ' + @LineFeed - + N' [request_logical_reads], ' + @LineFeed - + N' [Logical_Reads_MB], ' + @LineFeed - + N' [request_writes], ' + @LineFeed - + N' [Logical_Writes_MB], ' + @LineFeed - + N' [request_physical_reads], ' + @LineFeed - + N' [Physical_reads_MB], ' + @LineFeed - + N' [session_cpu], ' + @LineFeed - + N' [session_logical_reads], ' + @LineFeed - + N' [session_logical_reads_MB], ' + @LineFeed - + N' [session_physical_reads], ' + @LineFeed - + N' [session_physical_reads_MB], ' + @LineFeed - + N' [session_writes], ' + @LineFeed - + N' [session_writes_MB], ' + @LineFeed - + N' [tempdb_allocations_mb], ' + @LineFeed - + N' [memory_usage], ' + @LineFeed - + N' [estimated_completion_time], ' + @LineFeed - + N' [percent_complete], ' + @LineFeed - + N' [deadlock_priority], ' + @LineFeed - + N' [transaction_isolation_level], ' + @LineFeed - + N' [last_dop], ' + @LineFeed - + N' [min_dop], ' + @LineFeed - + N' [max_dop], ' + @LineFeed - + N' [last_grant_kb], ' + @LineFeed - + N' [min_grant_kb], ' + @LineFeed - + N' [max_grant_kb], ' + @LineFeed - + N' [last_used_grant_kb], ' + @LineFeed - + N' [min_used_grant_kb], ' + @LineFeed - + N' [max_used_grant_kb], ' + @LineFeed - + N' [last_ideal_grant_kb], ' + @LineFeed - + N' [min_ideal_grant_kb], ' + @LineFeed - + N' [max_ideal_grant_kb], ' + @LineFeed - + N' [last_reserved_threads], ' + @LineFeed - + N' [min_reserved_threads], ' + @LineFeed - + N' [max_reserved_threads], ' + @LineFeed - + N' [last_used_threads], ' + @LineFeed - + N' [min_used_threads], ' + @LineFeed - + N' [max_used_threads], ' + @LineFeed - + N' [grant_time], ' + @LineFeed - + N' [requested_memory_kb], ' + @LineFeed - + N' [grant_memory_kb], ' + @LineFeed - + N' [is_request_granted], ' + @LineFeed - + N' [required_memory_kb], ' + @LineFeed - + N' [query_memory_grant_used_memory_kb], ' + @LineFeed - + N' [ideal_memory_kb], ' + @LineFeed - + N' [is_small], ' + @LineFeed - + N' [timeout_sec], ' + @LineFeed - + N' [resource_semaphore_id], ' + @LineFeed - + N' [wait_order], ' + @LineFeed - + N' [wait_time_ms], ' + @LineFeed - + N' [next_candidate_for_memory_grant], ' + @LineFeed - + N' [target_memory_kb], ' + @LineFeed - + N' [max_target_memory_kb], ' + @LineFeed - + N' [total_memory_kb], ' + @LineFeed - + N' [available_memory_kb], ' + @LineFeed - + N' [granted_memory_kb], ' + @LineFeed - + N' [query_resource_semaphore_used_memory_kb], ' + @LineFeed - + N' [grantee_count], ' + @LineFeed - + N' [waiter_count], ' + @LineFeed - + N' [timeout_error_count], ' + @LineFeed - + N' [forced_grant_count], ' + @LineFeed - + N' [workload_group_name], ' + @LineFeed - + N' [resource_pool_name], ' + @LineFeed - + N' [context_info], ' + @LineFeed - + N' [query_hash], ' + @LineFeed - + N' [query_plan_hash], ' + @LineFeed - + N' [sql_handle], ' + @LineFeed - + N' [plan_handle], ' + @LineFeed - + N' [statement_start_offset], ' + @LineFeed - + N' [statement_end_offset] ' + @LineFeed - + N' FROM ' + @LineFeed - + N' ( ' + @LineFeed - + N' SELECT ' + @LineFeed - + N' [ID], ' + @LineFeed - + N' [ServerName], ' + @LineFeed - + N' [CheckDate], ' + @LineFeed - + N' [elapsed_time], ' + @LineFeed - + N' [session_id], ' + @LineFeed - + N' [database_name], ' + @LineFeed - + N' /* Truncate the query text to aid performance of painting the rows in SSMS */ ' + @LineFeed - + N' CAST([query_text] AS NVARCHAR(1000)) AS [query_text_snippet], ' + @LineFeed - + N' [query_plan], ' + @LineFeed - + N' [live_query_plan], ' + @LineFeed - + N' [query_cost], ' + @LineFeed - + N' [status], ' + @LineFeed - + N' [wait_info], ' + @LineFeed - + N' [top_session_waits], ' + @LineFeed - + N' [blocking_session_id], ' + @LineFeed - + N' [open_transaction_count], ' + @LineFeed - + N' [is_implicit_transaction], ' + @LineFeed - + N' [nt_domain], ' + @LineFeed - + N' [host_name], ' + @LineFeed - + N' [login_name], ' + @LineFeed - + N' [nt_user_name], ' + @LineFeed - + N' [program_name], ' + @LineFeed - + N' [fix_parameter_sniffing], ' + @LineFeed - + N' [client_interface_name], ' + @LineFeed - + N' [login_time], ' + @LineFeed - + N' [start_time], ' + @LineFeed - + N' [request_time], ' + @LineFeed - + N' [request_cpu_time], ' + @LineFeed - + N' [degree_of_parallelism], ' + @LineFeed - + N' [request_logical_reads], ' + @LineFeed - + N' ((CAST([request_logical_reads] AS MONEY)* 8)/ 1024) [Logical_Reads_MB], ' + @LineFeed - + N' [request_writes], ' + @LineFeed - + N' ((CAST([request_writes] AS MONEY)* 8)/ 1024) [Logical_Writes_MB], ' + @LineFeed - + N' [request_physical_reads], ' + @LineFeed - + N' ((CAST([request_physical_reads] AS MONEY)* 8)/ 1024) [Physical_reads_MB], ' + @LineFeed - + N' [session_cpu], ' + @LineFeed - + N' [session_logical_reads], ' + @LineFeed - + N' ((CAST([session_logical_reads] AS MONEY)* 8)/ 1024) [session_logical_reads_MB], ' + @LineFeed - + N' [session_physical_reads], ' + @LineFeed - + N' ((CAST([session_physical_reads] AS MONEY)* 8)/ 1024) [session_physical_reads_MB], ' + @LineFeed - + N' [session_writes], ' + @LineFeed - + N' ((CAST([session_writes] AS MONEY)* 8)/ 1024) [session_writes_MB], ' + @LineFeed - + N' [tempdb_allocations_mb], ' + @LineFeed - + N' [memory_usage], ' + @LineFeed - + N' [estimated_completion_time], ' + @LineFeed - + N' [percent_complete], ' + @LineFeed - + N' [deadlock_priority], ' + @LineFeed - + N' [transaction_isolation_level], ' + @LineFeed - + N' [last_dop], ' + @LineFeed - + N' [min_dop], ' + @LineFeed - + N' [max_dop], ' + @LineFeed - + N' [last_grant_kb], ' + @LineFeed - + N' [min_grant_kb], ' + @LineFeed - + N' [max_grant_kb], ' + @LineFeed - + N' [last_used_grant_kb], ' + @LineFeed - + N' [min_used_grant_kb], ' + @LineFeed - + N' [max_used_grant_kb], ' + @LineFeed - + N' [last_ideal_grant_kb], ' + @LineFeed - + N' [min_ideal_grant_kb], ' + @LineFeed - + N' [max_ideal_grant_kb], ' + @LineFeed - + N' [last_reserved_threads], ' + @LineFeed - + N' [min_reserved_threads], ' + @LineFeed - + N' [max_reserved_threads], ' + @LineFeed - + N' [last_used_threads], ' + @LineFeed - + N' [min_used_threads], ' + @LineFeed - + N' [max_used_threads], ' + @LineFeed - + N' [grant_time], ' + @LineFeed - + N' [requested_memory_kb], ' + @LineFeed - + N' [grant_memory_kb], ' + @LineFeed - + N' [is_request_granted], ' + @LineFeed - + N' [required_memory_kb], ' + @LineFeed - + N' [query_memory_grant_used_memory_kb], ' + @LineFeed - + N' [ideal_memory_kb], ' + @LineFeed - + N' [is_small], ' + @LineFeed - + N' [timeout_sec], ' + @LineFeed - + N' [resource_semaphore_id], ' + @LineFeed - + N' [wait_order], ' + @LineFeed - + N' [wait_time_ms], ' + @LineFeed - + N' [next_candidate_for_memory_grant], ' + @LineFeed - + N' [target_memory_kb], ' + @LineFeed - + N' [max_target_memory_kb], ' + @LineFeed - + N' [total_memory_kb], ' + @LineFeed - + N' [available_memory_kb], ' + @LineFeed - + N' [granted_memory_kb], ' + @LineFeed - + N' [query_resource_semaphore_used_memory_kb], ' + @LineFeed - + N' [grantee_count], ' + @LineFeed - + N' [waiter_count], ' + @LineFeed - + N' [timeout_error_count], ' + @LineFeed - + N' [forced_grant_count], ' + @LineFeed - + N' [workload_group_name], ' + @LineFeed - + N' [resource_pool_name], ' + @LineFeed - + N' [context_info], ' + @LineFeed - + N' [query_hash], ' + @LineFeed - + N' [query_plan_hash], ' + @LineFeed - + N' [sql_handle], ' + @LineFeed - + N' [plan_handle], ' + @LineFeed - + N' [statement_start_offset], ' + @LineFeed - + N' [statement_end_offset] ' + @LineFeed - + N' FROM ' + @OutputSchemaName + '.' + @OutputTableName + '' + @LineFeed - + N' ) AS [BlitzWho] ' + @LineFeed - + N'INNER JOIN [MaxQueryDuration] ON [BlitzWho].[ID] = [MaxQueryDuration].[MaxID]; ' + @LineFeed - + N''');' + IF @Seconds > 0 + BEGIN + + IF EXISTS ( SELECT 1/0 + FROM sys.all_objects AS ao + WHERE ao.name = 'dm_exec_query_profiles' ) + BEGIN + + IF EXISTS( SELECT 1/0 + FROM sys.dm_exec_requests AS r + JOIN sys.dm_exec_sessions AS s + ON r.session_id = s.session_id + WHERE s.host_name IS NOT NULL + AND r.total_elapsed_time > 5000 ) + BEGIN + + SET @StringToExecute = N' + DECLARE @bad_estimate TABLE + ( + session_id INT, + request_id INT, + estimate_inaccuracy BIT + ); + + INSERT @bad_estimate ( session_id, request_id, estimate_inaccuracy ) + SELECT x.session_id, + x.request_id, + x.estimate_inaccuracy + FROM ( + SELECT deqp.session_id, + deqp.request_id, + CASE WHEN deqp.row_count > ( deqp.estimate_row_count * 10000 ) + THEN 1 + ELSE 0 + END AS estimate_inaccuracy + FROM sys.dm_exec_query_profiles AS deqp + WHERE deqp.session_id <> @@SPID + ) AS x + WHERE x.estimate_inaccuracy = 1 + GROUP BY x.session_id, + x.request_id, + x.estimate_inaccuracy; + + DECLARE @parallelism_skew TABLE + ( + session_id INT, + request_id INT, + parallelism_skew BIT + ); + + INSERT @parallelism_skew ( session_id, request_id, parallelism_skew ) + SELECT y.session_id, + y.request_id, + y.parallelism_skew + FROM ( + SELECT x.session_id, + x.request_id, + x.node_id, + x.thread_id, + x.row_count, + x.sum_node_rows, + x.node_dop, + x.sum_node_rows / x.node_dop AS even_distribution, + x.row_count / (1. * ISNULL(NULLIF(x.sum_node_rows / x.node_dop, 0), 1)) AS skew_percent, + CASE + WHEN x.row_count > 10000 + AND x.row_count / (1. * ISNULL(NULLIF(x.sum_node_rows / x.node_dop, 0), 1)) > 2. + THEN 1 + WHEN x.row_count > 10000 + AND x.row_count / (1. * ISNULL(NULLIF(x.sum_node_rows / x.node_dop, 0), 1)) < 0.5 + THEN 1 + ELSE 0 + END AS parallelism_skew + FROM ( + SELECT deqp.session_id, + deqp.request_id, + deqp.node_id, + deqp.thread_id, + deqp.row_count, + SUM(deqp.row_count) + OVER ( PARTITION BY deqp.session_id, + deqp.request_id, + deqp.node_id + ORDER BY deqp.row_count + ROWS BETWEEN UNBOUNDED PRECEDING + AND UNBOUNDED FOLLOWING ) + AS sum_node_rows, + COUNT(*) + OVER ( PARTITION BY deqp.session_id, + deqp.request_id, + deqp.node_id + ORDER BY deqp.row_count + ROWS BETWEEN UNBOUNDED PRECEDING + AND UNBOUNDED FOLLOWING ) + AS node_dop + FROM sys.dm_exec_query_profiles AS deqp + WHERE deqp.thread_id > 0 + AND deqp.session_id <> @@SPID + AND EXISTS + ( + SELECT 1/0 + FROM sys.dm_exec_query_profiles AS deqp2 + WHERE deqp.session_id = deqp2.session_id + AND deqp.node_id = deqp2.node_id + AND deqp2.thread_id > 0 + GROUP BY deqp2.session_id, deqp2.node_id + HAVING COUNT(deqp2.node_id) > 1 + ) + ) AS x + ) AS y + WHERE y.parallelism_skew = 1 + GROUP BY y.session_id, + y.request_id, + y.parallelism_skew; + + /* Queries in dm_exec_query_profiles showing signs of poor cardinality estimates - CheckID 42 */ + IF (@Debug = 1) + BEGIN + RAISERROR(''Running CheckID 42'',10,1) WITH NOWAIT; + END - IF @Debug = 1 - BEGIN - PRINT CONVERT(VARCHAR(8000), SUBSTRING(@StringToExecute, 0, 8000)) - PRINT CONVERT(VARCHAR(8000), SUBSTRING(@StringToExecute, 8000, 16000)) - END + INSERT INTO #BlitzFirstResults + (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, OpenTransactionCount, QueryHash, QueryPlan) + SELECT 42 AS CheckID, + 100 AS Priority, + ''Query Performance'' AS FindingsGroup, + ''Queries with 10000x cardinality misestimations'' AS Findings, + ''https://brentozar.com/go/skewedup'' AS URL, + ''The query on SPID '' + + RTRIM(b.session_id) + + '' has been running for '' + + RTRIM(r.total_elapsed_time / 1000) + + '' seconds, with a large cardinality misestimate'' AS Details, + ''No quick fix here: time to dig into the actual execution plan. '' AS HowToStopIt, + r.start_time, + s.login_name, + s.nt_user_name, + s.program_name, + s.host_name, + r.database_id, + DB_NAME(r.database_id), + dest.text, + s.open_transaction_count, + r.query_hash, '; - EXEC(@StringToExecute); - END; + IF @dm_exec_query_statistics_xml = 1 + SET @StringToExecute = @StringToExecute + N' COALESCE(qs_live.query_plan, qp.query_plan) AS query_plan '; + ELSE + SET @StringToExecute = @StringToExecute + N' qp.query_plan '; - END + SET @StringToExecute = @StringToExecute + N' + FROM @bad_estimate AS b + JOIN sys.dm_exec_requests AS r + ON r.session_id = b.session_id + AND r.request_id = b.request_id + JOIN sys.dm_exec_sessions AS s + ON s.session_id = b.session_id + CROSS APPLY sys.dm_exec_sql_text(r.sql_handle) AS dest + CROSS APPLY sys.dm_exec_query_plan(r.plan_handle) AS qp '; - IF OBJECT_ID('tempdb..#WhoReadableDBs') IS NOT NULL - DROP TABLE #WhoReadableDBs; + IF EXISTS (SELECT * FROM sys.all_objects WHERE name = 'dm_exec_query_statistics_xml') + SET @StringToExecute = @StringToExecute + N' OUTER APPLY sys.dm_exec_query_statistics_xml(s.session_id) qs_live '; + + + SET @StringToExecute = @StringToExecute + N'; -CREATE TABLE #WhoReadableDBs -( -database_id INT -); + /* Queries in dm_exec_query_profiles showing signs of unbalanced parallelism - CheckID 43 */ + IF (@Debug = 1) + BEGIN + RAISERROR(''Running CheckID 43'',10,1) WITH NOWAIT; + END -IF EXISTS (SELECT * FROM sys.all_objects o WHERE o.name = 'dm_hadr_database_replica_states') -BEGIN - RAISERROR('Checking for Read intent databases to exclude',0,0) WITH NOWAIT; + INSERT INTO #BlitzFirstResults + (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, OpenTransactionCount, QueryHash, QueryPlan) + SELECT 43 AS CheckID, + 100 AS Priority, + ''Query Performance'' AS FindingsGroup, + ''Queries with 10000x skewed parallelism'' AS Findings, + ''https://brentozar.com/go/skewedup'' AS URL, + ''The query on SPID '' + + RTRIM(p.session_id) + + '' has been running for '' + + RTRIM(r.total_elapsed_time / 1000) + + '' seconds, with a parallel threads doing uneven work.'' AS Details, + ''No quick fix here: time to dig into the actual execution plan. '' AS HowToStopIt, + r.start_time, + s.login_name, + s.nt_user_name, + s.program_name, + s.host_name, + r.database_id, + DB_NAME(r.database_id), + dest.text, + s.open_transaction_count, + r.query_hash, '; - EXEC('INSERT INTO #WhoReadableDBs (database_id) SELECT DBs.database_id FROM sys.databases DBs INNER JOIN sys.availability_replicas Replicas ON DBs.replica_id = Replicas.replica_id WHERE replica_server_name NOT IN (SELECT DISTINCT primary_replica FROM sys.dm_hadr_availability_group_states States) AND Replicas.secondary_role_allow_connections_desc = ''READ_ONLY'' AND replica_server_name = @@SERVERNAME;'); -END + IF @dm_exec_query_statistics_xml = 1 + SET @StringToExecute = @StringToExecute + N' COALESCE(qs_live.query_plan, qp.query_plan) AS query_plan '; + ELSE + SET @StringToExecute = @StringToExecute + N' qp.query_plan '; -SELECT @BlockingCheck = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + SET @StringToExecute = @StringToExecute + N' + FROM @parallelism_skew AS p + JOIN sys.dm_exec_requests AS r + ON r.session_id = p.session_id + AND r.request_id = p.request_id + JOIN sys.dm_exec_sessions AS s + ON s.session_id = p.session_id + CROSS APPLY sys.dm_exec_sql_text(r.sql_handle) AS dest + CROSS APPLY sys.dm_exec_query_plan(r.plan_handle) AS qp '; + + IF EXISTS (SELECT * FROM sys.all_objects WHERE name = 'dm_exec_query_statistics_xml') + SET @StringToExecute = @StringToExecute + N' OUTER APPLY sys.dm_exec_query_statistics_xml(s.session_id) qs_live '; + + + SET @StringToExecute = @StringToExecute + N';'; + + EXECUTE sp_executesql @StringToExecute, N'@Debug BIT',@Debug = @Debug; + END - DECLARE @blocked TABLE - ( - dbid SMALLINT NOT NULL, - last_batch DATETIME NOT NULL, - open_tran SMALLINT NOT NULL, - sql_handle BINARY(20) NOT NULL, - session_id SMALLINT NOT NULL, - blocking_session_id SMALLINT NOT NULL, - lastwaittype NCHAR(32) NOT NULL, - waittime BIGINT NOT NULL, - cpu INT NOT NULL, - physical_io BIGINT NOT NULL, - memusage INT NOT NULL - ); - - INSERT @blocked ( dbid, last_batch, open_tran, sql_handle, session_id, blocking_session_id, lastwaittype, waittime, cpu, physical_io, memusage ) - SELECT - sys1.dbid, sys1.last_batch, sys1.open_tran, sys1.sql_handle, - sys2.spid AS session_id, sys2.blocked AS blocking_session_id, sys2.lastwaittype, sys2.waittime, sys2.cpu, sys2.physical_io, sys2.memusage - FROM sys.sysprocesses AS sys1 - JOIN sys.sysprocesses AS sys2 - ON sys1.spid = sys2.blocked;'; + END + END -IF @ProductVersionMajor > 9 and @ProductVersionMajor < 11 -BEGIN - /* Think of the StringToExecute as starting with this, but we'll set this up later depending on whether we're doing an insert or a select: - SELECT @StringToExecute = N'SELECT GETDATE() AS run_date , - */ - SET @StringToExecute = N'COALESCE( RIGHT(''00'' + CONVERT(VARCHAR(20), (ABS(r.total_elapsed_time) / 1000) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), (DATEADD(SECOND, (r.total_elapsed_time / 1000), 0) + DATEADD(MILLISECOND, (r.total_elapsed_time % 1000), 0)), 114), RIGHT(''00'' + CONVERT(VARCHAR(20), DATEDIFF(SECOND, s.last_request_start_time, GETDATE()) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), DATEADD(SECOND, DATEDIFF(SECOND, s.last_request_start_time, GETDATE()), 0), 114) ) AS [elapsed_time] , - s.session_id , - COALESCE(DB_NAME(r.database_id), DB_NAME(blocked.dbid), ''N/A'') AS database_name, - ISNULL(SUBSTRING(dest.text, - ( query_stats.statement_start_offset / 2 ) + 1, - ( ( CASE query_stats.statement_end_offset - WHEN -1 THEN DATALENGTH(dest.text) - ELSE query_stats.statement_end_offset - END - query_stats.statement_start_offset ) - / 2 ) + 1), dest.text) AS query_text , - derp.query_plan , - qmg.query_cost , - s.status , - CASE - WHEN s.status <> ''sleeping'' THEN COALESCE(wt.wait_info, RTRIM(blocked.lastwaittype) + '' ('' + CONVERT(VARCHAR(10), blocked.waittime) + '')'' ) - ELSE NULL - END AS wait_info , - CASE WHEN r.blocking_session_id <> 0 AND blocked.session_id IS NULL - THEN r.blocking_session_id - WHEN r.blocking_session_id <> 0 AND s.session_id <> blocked.blocking_session_id - THEN blocked.blocking_session_id - WHEN r.blocking_session_id = 0 AND s.session_id = blocked.session_id - THEN blocked.blocking_session_id - WHEN r.blocking_session_id <> 0 AND s.session_id = blocked.blocking_session_id - THEN r.blocking_session_id - ELSE NULL - END AS blocking_session_id, - COALESCE(r.open_transaction_count, blocked.open_tran) AS open_transaction_count , - CASE WHEN EXISTS ( SELECT 1 - FROM sys.dm_tran_active_transactions AS tat - JOIN sys.dm_tran_session_transactions AS tst - ON tst.transaction_id = tat.transaction_id - WHERE tat.name = ''implicit_transaction'' - AND s.session_id = tst.session_id - ) THEN 1 - ELSE 0 - END AS is_implicit_transaction , - s.nt_domain , - s.host_name , - s.login_name , - s.nt_user_name ,' - IF @Platform = 'NonAzure' + /* Server Performance - High CPU Utilization - CheckID 24 */ + IF @Seconds < 30 + BEGIN + /* If we're waiting less than 30 seconds, run this check now rather than wait til the end. + We get this data from the ring buffers, and it's only updated once per minute, so might + as well get it now - whereas if we're checking 30+ seconds, it might get updated by the + end of our sp_BlitzFirst session. */ + IF (@Debug = 1) BEGIN - SET @StringToExecute += - N'program_name = COALESCE(( - SELECT REPLACE(program_name,Substring(program_name,30,34),''"''+j.name+''"'') - FROM msdb.dbo.sysjobs j WHERE Substring(program_name,32,32) = CONVERT(char(32),CAST(j.job_id AS binary(16)),2) - ),s.program_name)' + RAISERROR('Running CheckID 24',10,1) WITH NOWAIT; END - ELSE + + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) + SELECT 24, 50, 'Server Performance', 'High CPU Utilization', CAST(100 - SystemIdle AS NVARCHAR(20)) + N'%.', 100 - SystemIdle, 'http://www.BrentOzar.com/go/cpu' + FROM ( + SELECT record, + record.value('(./Record/SchedulerMonitorEvent/SystemHealth/SystemIdle)[1]', 'int') AS SystemIdle + FROM ( + SELECT TOP 1 CONVERT(XML, record) AS record + FROM sys.dm_os_ring_buffers + WHERE ring_buffer_type = N'RING_BUFFER_SCHEDULER_MONITOR' + AND record LIKE '%%' + ORDER BY timestamp DESC) AS rb + ) AS y + WHERE 100 - SystemIdle >= 50; + + /* CPU Utilization - CheckID 23 */ + IF (@Debug = 1) BEGIN - SET @StringToExecute += N's.program_name' + RAISERROR('Running CheckID 23',10,1) WITH NOWAIT; END - - IF @ExpertMode = 1 - BEGIN - SET @StringToExecute += - N', - ''DBCC FREEPROCCACHE ('' + CONVERT(NVARCHAR(128), r.plan_handle, 1) + '');'' AS fix_parameter_sniffing, - s.client_interface_name , - s.login_time , - r.start_time , - qmg.request_time , - COALESCE(r.cpu_time, s.cpu_time) AS request_cpu_time, - COALESCE(r.logical_reads, s.logical_reads) AS request_logical_reads, - COALESCE(r.writes, s.writes) AS request_writes, - COALESCE(r.reads, s.reads) AS request_physical_reads , - s.cpu_time AS session_cpu, - s.logical_reads AS session_logical_reads, - s.reads AS session_physical_reads , - s.writes AS session_writes, - tempdb_allocations.tempdb_allocations_mb, - s.memory_usage , - r.estimated_completion_time , - r.percent_complete , - r.deadlock_priority , - CASE - WHEN s.transaction_isolation_level = 0 THEN ''Unspecified'' - WHEN s.transaction_isolation_level = 1 THEN ''Read Uncommitted'' - WHEN s.transaction_isolation_level = 2 AND EXISTS (SELECT 1 FROM sys.databases WHERE name = DB_NAME(r.database_id) AND is_read_committed_snapshot_on = 1) THEN ''Read Committed Snapshot Isolation'' - WHEN s.transaction_isolation_level = 2 THEN ''Read Committed'' - WHEN s.transaction_isolation_level = 3 THEN ''Repeatable Read'' - WHEN s.transaction_isolation_level = 4 THEN ''Serializable'' - WHEN s.transaction_isolation_level = 5 THEN ''Snapshot'' - ELSE ''WHAT HAVE YOU DONE?'' - END AS transaction_isolation_level , - qmg.dop AS degree_of_parallelism , - COALESCE(CAST(qmg.grant_time AS VARCHAR(20)), ''N/A'') AS grant_time , - qmg.requested_memory_kb , - qmg.granted_memory_kb AS grant_memory_kb, - CASE WHEN qmg.grant_time IS NULL THEN ''N/A'' - WHEN qmg.requested_memory_kb < qmg.granted_memory_kb - THEN ''Query Granted Less Than Query Requested'' - ELSE ''Memory Request Granted'' - END AS is_request_granted , - qmg.required_memory_kb , - qmg.used_memory_kb AS query_memory_grant_used_memory_kb, - qmg.ideal_memory_kb , - qmg.is_small , - qmg.timeout_sec , - qmg.resource_semaphore_id , - COALESCE(CAST(qmg.wait_order AS VARCHAR(20)), ''N/A'') AS wait_order , - COALESCE(CAST(qmg.wait_time_ms AS VARCHAR(20)), - ''N/A'') AS wait_time_ms , - CASE qmg.is_next_candidate - WHEN 0 THEN ''No'' - WHEN 1 THEN ''Yes'' - ELSE ''N/A'' - END AS next_candidate_for_memory_grant , - qrs.target_memory_kb , - COALESCE(CAST(qrs.max_target_memory_kb AS VARCHAR(20)), - ''Small Query Resource Semaphore'') AS max_target_memory_kb , - qrs.total_memory_kb , - qrs.available_memory_kb , - qrs.granted_memory_kb , - qrs.used_memory_kb AS query_resource_semaphore_used_memory_kb, - qrs.grantee_count , - qrs.waiter_count , - qrs.timeout_error_count , - COALESCE(CAST(qrs.forced_grant_count AS VARCHAR(20)), - ''Small Query Resource Semaphore'') AS forced_grant_count, - wg.name AS workload_group_name , - rp.name AS resource_pool_name, - CONVERT(VARCHAR(128), r.context_info) AS context_info - ' - END /* IF @ExpertMode = 1 */ - - SET @StringToExecute += - N'FROM sys.dm_exec_sessions AS s - LEFT JOIN sys.dm_exec_requests AS r - ON r.session_id = s.session_id - LEFT JOIN ( SELECT DISTINCT - wait.session_id , - ( SELECT waitwait.wait_type + N'' ('' - + CAST(MAX(waitwait.wait_duration_ms) AS NVARCHAR(128)) - + N'' ms) '' - FROM sys.dm_os_waiting_tasks AS waitwait - WHERE waitwait.session_id = wait.session_id - GROUP BY waitwait.wait_type - ORDER BY SUM(waitwait.wait_duration_ms) DESC - FOR - XML PATH('''') ) AS wait_info - FROM sys.dm_os_waiting_tasks AS wait ) AS wt - ON s.session_id = wt.session_id - LEFT JOIN sys.dm_exec_query_stats AS query_stats - ON r.sql_handle = query_stats.sql_handle - AND r.plan_handle = query_stats.plan_handle - AND r.statement_start_offset = query_stats.statement_start_offset - AND r.statement_end_offset = query_stats.statement_end_offset - LEFT JOIN sys.dm_exec_query_memory_grants qmg - ON r.session_id = qmg.session_id - AND r.request_id = qmg.request_id - LEFT JOIN sys.dm_exec_query_resource_semaphores qrs - ON qmg.resource_semaphore_id = qrs.resource_semaphore_id - AND qmg.pool_id = qrs.pool_id - LEFT JOIN sys.resource_governor_workload_groups wg - ON s.group_id = wg.group_id - LEFT JOIN sys.resource_governor_resource_pools rp - ON wg.pool_id = rp.pool_id - OUTER APPLY ( - SELECT TOP 1 - b.dbid, b.last_batch, b.open_tran, b.sql_handle, - b.session_id, b.blocking_session_id, b.lastwaittype, b.waittime - FROM @blocked b - WHERE (s.session_id = b.session_id - OR s.session_id = b.blocking_session_id) - ) AS blocked - OUTER APPLY sys.dm_exec_sql_text(COALESCE(r.sql_handle, blocked.sql_handle)) AS dest - OUTER APPLY sys.dm_exec_query_plan(r.plan_handle) AS derp - OUTER APPLY ( - SELECT CONVERT(DECIMAL(38,2), SUM( (((tsu.user_objects_alloc_page_count - user_objects_dealloc_page_count) * 8) / 1024.)) ) AS tempdb_allocations_mb - FROM sys.dm_db_task_space_usage tsu - WHERE tsu.request_id = r.request_id - AND tsu.session_id = r.session_id - AND tsu.session_id = s.session_id - ) as tempdb_allocations - WHERE s.session_id <> @@SPID - AND s.host_name IS NOT NULL - ' - + CASE WHEN @ShowSleepingSPIDs = 0 THEN - N' AND COALESCE(DB_NAME(r.database_id), DB_NAME(blocked.dbid)) IS NOT NULL' - WHEN @ShowSleepingSPIDs = 1 THEN - N' OR COALESCE(r.open_transaction_count, blocked.open_tran) >= 1' - ELSE N'' END; -END /* IF @ProductVersionMajor > 9 and @ProductVersionMajor < 11 */ -IF @ProductVersionMajor >= 11 - BEGIN - SELECT @EnhanceFlag = - CASE WHEN @ProductVersionMajor = 11 AND @ProductVersionMinor >= 6020 THEN 1 - WHEN @ProductVersionMajor = 12 AND @ProductVersionMinor >= 5000 THEN 1 - WHEN @ProductVersionMajor = 13 AND @ProductVersionMinor >= 1601 THEN 1 - WHEN @ProductVersionMajor > 13 THEN 1 - ELSE 0 - END + IF SERVERPROPERTY('Edition') <> 'SQL Azure' + WITH y + AS + ( + SELECT CONVERT(VARCHAR(5), 100 - ca.c.value('.', 'INT')) AS system_idle, + CONVERT(VARCHAR(30), rb.event_date) AS event_date, + CONVERT(VARCHAR(8000), rb.record) AS record + FROM + ( SELECT CONVERT(XML, dorb.record) AS record, + DATEADD(ms, ( ts.ms_ticks - dorb.timestamp ), GETDATE()) AS event_date + FROM sys.dm_os_ring_buffers AS dorb + CROSS JOIN + ( SELECT dosi.ms_ticks FROM sys.dm_os_sys_info AS dosi ) AS ts + WHERE dorb.ring_buffer_type = N'RING_BUFFER_SCHEDULER_MONITOR' + AND record LIKE '%%' ) AS rb + CROSS APPLY rb.record.nodes('/Record/SchedulerMonitorEvent/SystemHealth/SystemIdle') AS ca(c) + ) + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL, HowToStopIt) + SELECT TOP 1 + 23, + 250, + 'Server Info', + 'CPU Utilization', + y.system_idle + N'%. Ring buffer details: ' + CAST(y.record AS NVARCHAR(4000)), + y.system_idle , + 'http://www.BrentOzar.com/go/cpu', + STUFF(( SELECT TOP 2147483647 + CHAR(10) + CHAR(13) + + y2.system_idle + + '% ON ' + + y2.event_date + + ' Ring buffer details: ' + + y2.record + FROM y AS y2 + ORDER BY y2.event_date DESC + FOR XML PATH(N''), TYPE ).value(N'.[1]', N'VARCHAR(MAX)'), 1, 1, N'') AS query + FROM y + ORDER BY y.event_date DESC; + + + /* Highlight if non SQL processes are using >25% CPU - CheckID 28 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 28',10,1) WITH NOWAIT; + END + + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) + SELECT 28, 50, 'Server Performance', 'High CPU Utilization - Not SQL', CONVERT(NVARCHAR(100),100 - (y.SQLUsage + y.SystemIdle)) + N'% - Other Processes (not SQL Server) are using this much CPU. This may impact on the performance of your SQL Server instance', 100 - (y.SQLUsage + y.SystemIdle), 'http://www.BrentOzar.com/go/cpu' + FROM ( + SELECT record, + record.value('(./Record/SchedulerMonitorEvent/SystemHealth/SystemIdle)[1]', 'int') AS SystemIdle + ,record.value('(./Record/SchedulerMonitorEvent/SystemHealth/ProcessUtilization)[1]', 'int') AS SQLUsage + FROM ( + SELECT TOP 1 CONVERT(XML, record) AS record + FROM sys.dm_os_ring_buffers + WHERE ring_buffer_type = N'RING_BUFFER_SCHEDULER_MONITOR' + AND record LIKE '%%' + ORDER BY timestamp DESC) AS rb + ) AS y + WHERE 100 - (y.SQLUsage + y.SystemIdle) >= 25; + + END; /* IF @Seconds < 30 */ + + /* Query Problems - Statistics Updated Recently - CheckID 44 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 44',10,1) WITH NOWAIT; + END + + IF 20 >= (SELECT COUNT(*) FROM sys.databases WHERE name NOT IN ('master', 'model', 'msdb', 'tempdb')) + BEGIN + CREATE TABLE #UpdatedStats (HowToStopIt NVARCHAR(4000), RowsForSorting BIGINT); + IF EXISTS(SELECT * FROM sys.all_objects WHERE name = 'dm_db_stats_properties') + BEGIN + /* We don't want to hang around to obtain locks */ + SET LOCK_TIMEOUT 0; + + EXEC sp_MSforeachdb N'USE [?]; + SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; SET LOCK_TIMEOUT 1000; + BEGIN TRY + INSERT INTO #UpdatedStats(HowToStopIt, RowsForSorting) + SELECT HowToStopIt = + QUOTENAME(DB_NAME()) + N''.'' + + QUOTENAME(SCHEMA_NAME(obj.schema_id)) + N''.'' + + QUOTENAME(obj.name) + + N'' statistic '' + QUOTENAME(stat.name) + + N'' was updated on '' + CONVERT(NVARCHAR(50), sp.last_updated, 121) + N'','' + + N'' had '' + CAST(sp.rows AS NVARCHAR(50)) + N'' rows, with '' + + CAST(sp.rows_sampled AS NVARCHAR(50)) + N'' rows sampled,'' + + N'' producing '' + CAST(sp.steps AS NVARCHAR(50)) + N'' steps in the histogram.'', + sp.rows + FROM sys.objects AS obj WITH (NOLOCK) + INNER JOIN sys.stats AS stat WITH (NOLOCK) ON stat.object_id = obj.object_id + CROSS APPLY sys.dm_db_stats_properties(stat.object_id, stat.stats_id) AS sp + WHERE sp.last_updated > DATEADD(MI, -15, GETDATE()) + AND obj.is_ms_shipped = 0 + AND ''[?]'' <> ''[tempdb]''; + END TRY + BEGIN CATCH + IF (ERROR_NUMBER() = 1222) + BEGIN + INSERT INTO #UpdatedStats(HowToStopIt, RowsForSorting) + SELECT HowToStopIt = + QUOTENAME(DB_NAME()) + + N'' No information could be retrieved as the lock timeout was exceeded,''+ + N'' this is likely due to an Index operation in Progress'', + -1 + END + ELSE + BEGIN + INSERT INTO #UpdatedStats(HowToStopIt, RowsForSorting) + SELECT HowToStopIt = + QUOTENAME(DB_NAME()) + + N'' No information could be retrieved as a result of error: ''+ + CAST(ERROR_NUMBER() AS NVARCHAR(10)) + + N'' with message: ''+ + CAST(ERROR_MESSAGE() AS NVARCHAR(128)), + -1 + END + END CATCH'; + /* Set timeout back to a default value of -1 */ + SET LOCK_TIMEOUT -1; + END; + + /* We mark timeout exceeded with a -1 so only show these IF there is statistics info that succeeded */ + IF EXISTS (SELECT * FROM #UpdatedStats WHERE RowsForSorting > -1) + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) + SELECT 44 AS CheckId, + 50 AS Priority, + 'Query Problems' AS FindingGroup, + 'Statistics Updated Recently' AS Finding, + 'http://www.BrentOzar.com/go/stats' AS URL, + 'In the last 15 minutes, statistics were updated. To see which ones, click the HowToStopIt column.' + @LineFeed + @LineFeed + + 'This effectively clears the plan cache for queries that involve these tables,' + @LineFeed + + 'which thereby causes parameter sniffing: those queries are now getting brand new' + @LineFeed + + 'query plans based on whatever parameters happen to call them next.' + @LineFeed + @LineFeed + + 'Be on the lookout for sudden parameter sniffing issues after this time range.', + HowToStopIt = (SELECT (SELECT HowToStopIt + NCHAR(10)) + FROM #UpdatedStats + ORDER BY RowsForSorting DESC + FOR XML PATH('')); - IF OBJECT_ID('sys.dm_exec_session_wait_stats') IS NOT NULL - BEGIN - SET @SessionWaits = 1 - END + END - /* Think of the StringToExecute as starting with this, but we'll set this up later depending on whether we're doing an insert or a select: - SELECT @StringToExecute = N'SELECT GETDATE() AS run_date , - */ - SELECT @StringToExecute = N'COALESCE( RIGHT(''00'' + CONVERT(VARCHAR(20), (ABS(r.total_elapsed_time) / 1000) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), (DATEADD(SECOND, (r.total_elapsed_time / 1000), 0) + DATEADD(MILLISECOND, (r.total_elapsed_time % 1000), 0)), 114), RIGHT(''00'' + CONVERT(VARCHAR(20), DATEDIFF(SECOND, s.last_request_start_time, GETDATE()) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), DATEADD(SECOND, DATEDIFF(SECOND, s.last_request_start_time, GETDATE()), 0), 114) ) AS [elapsed_time] , - s.session_id , - COALESCE(DB_NAME(r.database_id), DB_NAME(blocked.dbid), ''N/A'') AS database_name, - ISNULL(SUBSTRING(dest.text, - ( query_stats.statement_start_offset / 2 ) + 1, - ( ( CASE query_stats.statement_end_offset - WHEN -1 THEN DATALENGTH(dest.text) - ELSE query_stats.statement_end_offset - END - query_stats.statement_start_offset ) - / 2 ) + 1), dest.text) AS query_text , - derp.query_plan ,' - + @QueryStatsXMLselect - +' - qmg.query_cost , - s.status , - CASE - WHEN s.status <> ''sleeping'' THEN COALESCE(wt.wait_info, RTRIM(blocked.lastwaittype) + '' ('' + CONVERT(VARCHAR(10), blocked.waittime) + '')'' ) - ELSE NULL - END AS wait_info ,' - + - CASE @SessionWaits - WHEN 1 THEN + N'SUBSTRING(wt2.session_wait_info, 0, LEN(wt2.session_wait_info) ) AS top_session_waits ,' - ELSE N' NULL AS top_session_waits ,' - END - + - N'CASE WHEN r.blocking_session_id <> 0 AND blocked.session_id IS NULL - THEN r.blocking_session_id - WHEN r.blocking_session_id <> 0 AND s.session_id <> blocked.blocking_session_id - THEN blocked.blocking_session_id - WHEN r.blocking_session_id = 0 AND s.session_id = blocked.session_id - THEN blocked.blocking_session_id - WHEN r.blocking_session_id <> 0 AND s.session_id = blocked.blocking_session_id - THEN r.blocking_session_id - ELSE NULL - END AS blocking_session_id, - COALESCE(r.open_transaction_count, blocked.open_tran) AS open_transaction_count , - CASE WHEN EXISTS ( SELECT 1 - FROM sys.dm_tran_active_transactions AS tat - JOIN sys.dm_tran_session_transactions AS tst - ON tst.transaction_id = tat.transaction_id - WHERE tat.name = ''implicit_transaction'' - AND s.session_id = tst.session_id - ) THEN 1 - ELSE 0 - END AS is_implicit_transaction , - s.nt_domain , - s.host_name , - s.login_name , - s.nt_user_name ,' - IF @Platform = 'NonAzure' - BEGIN - SET @StringToExecute += - N'program_name = COALESCE(( - SELECT REPLACE(program_name,Substring(program_name,30,34),''"''+j.name+''"'') - FROM msdb.dbo.sysjobs j WHERE Substring(program_name,32,32) = CONVERT(char(32),CAST(j.job_id AS binary(16)),2) - ),s.program_name)' - END + RAISERROR('Finished running investigatory queries',10,1) WITH NOWAIT; + + + /* End of checks. If we haven't waited @Seconds seconds, wait. */ + IF DATEADD(SECOND,1,SYSDATETIMEOFFSET()) < @FinishSampleTime + BEGIN + RAISERROR('Waiting to match @Seconds parameter',10,1) WITH NOWAIT; + WAITFOR TIME @FinishSampleTimeWaitFor; + END; + + RAISERROR('Capturing second pass of wait stats, perfmon counters, file stats',10,1) WITH NOWAIT; + /* Populate #FileStats, #PerfmonStats, #WaitStats with DMV data. In a second, we'll compare these. */ + INSERT #WaitStats(Pass, SampleTime, wait_type, wait_time_ms, signal_wait_time_ms, waiting_tasks_count) + SELECT + x.Pass, + x.SampleTime, + x.wait_type, + SUM(x.sum_wait_time_ms) AS sum_wait_time_ms, + SUM(x.sum_signal_wait_time_ms) AS sum_signal_wait_time_ms, + SUM(x.sum_waiting_tasks) AS sum_waiting_tasks + FROM ( + SELECT + 2 AS Pass, + SYSDATETIMEOFFSET() AS SampleTime, + owt.wait_type, + SUM(owt.wait_duration_ms) OVER (PARTITION BY owt.wait_type, owt.session_id) + - CASE WHEN @Seconds = 0 THEN 0 ELSE (@Seconds * 1000) END AS sum_wait_time_ms, + 0 AS sum_signal_wait_time_ms, + CASE @Seconds WHEN 0 THEN 0 ELSE 1 END AS sum_waiting_tasks + FROM sys.dm_os_waiting_tasks owt + WHERE owt.session_id > 50 + AND owt.wait_duration_ms >= CASE @Seconds WHEN 0 THEN 0 ELSE @Seconds * 1000 END + UNION ALL + SELECT + 2 AS Pass, + SYSDATETIMEOFFSET() AS SampleTime, + os.wait_type, + SUM(os.wait_time_ms) OVER (PARTITION BY os.wait_type) AS sum_wait_time_ms, + SUM(os.signal_wait_time_ms) OVER (PARTITION BY os.wait_type ) AS sum_signal_wait_time_ms, + SUM(os.waiting_tasks_count) OVER (PARTITION BY os.wait_type) AS sum_waiting_tasks + FROM sys.dm_os_wait_stats os + ) x + WHERE NOT EXISTS + ( + SELECT * + FROM ##WaitCategories AS wc + WHERE wc.WaitType = x.wait_type + AND wc.Ignorable = 1 + ) + GROUP BY x.Pass, x.SampleTime, x.wait_type + ORDER BY sum_wait_time_ms DESC; + + INSERT INTO #FileStats (Pass, SampleTime, DatabaseID, FileID, DatabaseName, FileLogicalName, SizeOnDiskMB, io_stall_read_ms , + num_of_reads, [bytes_read] , io_stall_write_ms,num_of_writes, [bytes_written], PhysicalName, TypeDesc, avg_stall_read_ms, avg_stall_write_ms) + SELECT 2 AS Pass, + SYSDATETIMEOFFSET() AS SampleTime, + mf.[database_id], + mf.[file_id], + DB_NAME(vfs.database_id) AS [db_name], + mf.name + N' [' + mf.type_desc COLLATE SQL_Latin1_General_CP1_CI_AS + N']' AS file_logical_name , + CAST(( ( vfs.size_on_disk_bytes / 1024.0 ) / 1024.0 ) AS INT) AS size_on_disk_mb , + vfs.io_stall_read_ms , + vfs.num_of_reads , + vfs.[num_of_bytes_read], + vfs.io_stall_write_ms , + vfs.num_of_writes , + vfs.[num_of_bytes_written], + mf.physical_name, + mf.type_desc, + 0, + 0 + FROM sys.dm_io_virtual_file_stats (NULL, NULL) AS vfs + INNER JOIN #MasterFiles AS mf ON vfs.file_id = mf.file_id + AND vfs.database_id = mf.database_id + WHERE vfs.num_of_reads > 0 + OR vfs.num_of_writes > 0; + + INSERT INTO #PerfmonStats (Pass, SampleTime, [object_name],[counter_name],[instance_name],[cntr_value],[cntr_type]) + SELECT 2 AS Pass, + SYSDATETIMEOFFSET() AS SampleTime, + RTRIM(dmv.object_name), RTRIM(dmv.counter_name), RTRIM(dmv.instance_name), dmv.cntr_value, dmv.cntr_type + FROM #PerfmonCounters counters + INNER JOIN sys.dm_os_performance_counters dmv ON counters.counter_name COLLATE SQL_Latin1_General_CP1_CI_AS = RTRIM(dmv.counter_name) COLLATE SQL_Latin1_General_CP1_CI_AS + AND counters.[object_name] COLLATE SQL_Latin1_General_CP1_CI_AS = RTRIM(dmv.[object_name]) COLLATE SQL_Latin1_General_CP1_CI_AS + AND (counters.[instance_name] IS NULL OR counters.[instance_name] COLLATE SQL_Latin1_General_CP1_CI_AS = RTRIM(dmv.[instance_name]) COLLATE SQL_Latin1_General_CP1_CI_AS); + + /* Set the latencies and averages. We could do this with a CTE, but we're not ambitious today. */ + UPDATE fNow + SET avg_stall_read_ms = ((fNow.io_stall_read_ms - fBase.io_stall_read_ms) / (fNow.num_of_reads - fBase.num_of_reads)) + FROM #FileStats fNow + INNER JOIN #FileStats fBase ON fNow.DatabaseID = fBase.DatabaseID AND fNow.FileID = fBase.FileID AND fNow.SampleTime > fBase.SampleTime AND fNow.num_of_reads > fBase.num_of_reads AND fNow.io_stall_read_ms > fBase.io_stall_read_ms + WHERE (fNow.num_of_reads - fBase.num_of_reads) > 0; + + UPDATE fNow + SET avg_stall_write_ms = ((fNow.io_stall_write_ms - fBase.io_stall_write_ms) / (fNow.num_of_writes - fBase.num_of_writes)) + FROM #FileStats fNow + INNER JOIN #FileStats fBase ON fNow.DatabaseID = fBase.DatabaseID AND fNow.FileID = fBase.FileID AND fNow.SampleTime > fBase.SampleTime AND fNow.num_of_writes > fBase.num_of_writes AND fNow.io_stall_write_ms > fBase.io_stall_write_ms + WHERE (fNow.num_of_writes - fBase.num_of_writes) > 0; + + UPDATE pNow + SET [value_delta] = pNow.cntr_value - pFirst.cntr_value, + [value_per_second] = ((1.0 * pNow.cntr_value - pFirst.cntr_value) / DATEDIFF(ss, pFirst.SampleTime, pNow.SampleTime)) + FROM #PerfmonStats pNow + INNER JOIN #PerfmonStats pFirst ON pFirst.[object_name] = pNow.[object_name] AND pFirst.counter_name = pNow.counter_name AND (pFirst.instance_name = pNow.instance_name OR (pFirst.instance_name IS NULL AND pNow.instance_name IS NULL)) + AND pNow.ID > pFirst.ID + WHERE DATEDIFF(ss, pFirst.SampleTime, pNow.SampleTime) > 0; + + + /* Query Stats - If we're within 10 seconds of our projected finish time, do the plan cache analysis. - CheckID 18 */ + IF DATEDIFF(ss, @FinishSampleTime, SYSDATETIMEOFFSET()) > 10 AND @CheckProcedureCache = 1 + BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 18',10,1) WITH NOWAIT; + END + + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (18, 210, 'Query Stats', 'Plan Cache Analysis Skipped', 'http://www.BrentOzar.com/go/topqueries', + 'Due to excessive load, the plan cache analysis was skipped. To override this, use @ExpertMode = 1.'); + + END; + ELSE IF @CheckProcedureCache = 1 + BEGIN + + + RAISERROR('@CheckProcedureCache = 1, capturing second pass of plan cache',10,1) WITH NOWAIT; + + /* Populate #QueryStats. SQL 2005 doesn't have query hash or query plan hash. */ + IF @@VERSION LIKE 'Microsoft SQL Server 2005%' + BEGIN + IF @FilterPlansByDatabase IS NULL + BEGIN + SET @StringToExecute = N'INSERT INTO #QueryStats ([sql_handle], Pass, SampleTime, statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, Points) + SELECT [sql_handle], 2 AS Pass, SYSDATETIMEOFFSET(), statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, NULL AS query_hash, NULL AS query_plan_hash, 0 + FROM sys.dm_exec_query_stats qs + WHERE qs.last_execution_time >= @StartSampleTimeText;'; + END; + ELSE + BEGIN + SET @StringToExecute = N'INSERT INTO #QueryStats ([sql_handle], Pass, SampleTime, statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, Points) + SELECT [sql_handle], 2 AS Pass, SYSDATETIMEOFFSET(), statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, NULL AS query_hash, NULL AS query_plan_hash, 0 + FROM sys.dm_exec_query_stats qs + CROSS APPLY sys.dm_exec_plan_attributes(qs.plan_handle) AS attr + INNER JOIN #FilterPlansByDatabase dbs ON CAST(attr.value AS INT) = dbs.DatabaseID + WHERE qs.last_execution_time >= @StartSampleTimeText + AND attr.attribute = ''dbid'';'; + END; + END; ELSE - BEGIN - SET @StringToExecute += N's.program_name' - END + BEGIN + IF @FilterPlansByDatabase IS NULL + BEGIN + SET @StringToExecute = N'INSERT INTO #QueryStats ([sql_handle], Pass, SampleTime, statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, Points) + SELECT [sql_handle], 2 AS Pass, SYSDATETIMEOFFSET(), statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, 0 + FROM sys.dm_exec_query_stats qs + WHERE qs.last_execution_time >= @StartSampleTimeText'; + END; + ELSE + BEGIN + SET @StringToExecute = N'INSERT INTO #QueryStats ([sql_handle], Pass, SampleTime, statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, Points) + SELECT [sql_handle], 2 AS Pass, SYSDATETIMEOFFSET(), statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, 0 + FROM sys.dm_exec_query_stats qs + CROSS APPLY sys.dm_exec_plan_attributes(qs.plan_handle) AS attr + INNER JOIN #FilterPlansByDatabase dbs ON CAST(attr.value AS INT) = dbs.DatabaseID + WHERE qs.last_execution_time >= @StartSampleTimeText + AND attr.attribute = ''dbid'';'; + END; + END; + /* Old version pre-2016/06/13: + IF @@VERSION LIKE 'Microsoft SQL Server 2005%' + SET @StringToExecute = N'INSERT INTO #QueryStats ([sql_handle], Pass, SampleTime, statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, Points) + SELECT [sql_handle], 2 AS Pass, SYSDATETIMEOFFSET(), statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, NULL AS query_hash, NULL AS query_plan_hash, 0 + FROM sys.dm_exec_query_stats qs + WHERE qs.last_execution_time >= @StartSampleTimeText;'; + ELSE + SET @StringToExecute = N'INSERT INTO #QueryStats ([sql_handle], Pass, SampleTime, statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, Points) + SELECT [sql_handle], 2 AS Pass, SYSDATETIMEOFFSET(), statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, 0 + FROM sys.dm_exec_query_stats qs + WHERE qs.last_execution_time >= @StartSampleTimeText;'; + */ + SET @ParmDefinitions = N'@StartSampleTimeText NVARCHAR(100)'; + SET @Parm1 = CONVERT(NVARCHAR(100), CAST(@StartSampleTime AS DATETIME), 127); - IF @ExpertMode = 1 /* We show more columns in expert mode, so the SELECT gets longer */ - BEGIN - SET @StringToExecute += - N', ''DBCC FREEPROCCACHE ('' + CONVERT(NVARCHAR(128), r.plan_handle, 1) + '');'' AS fix_parameter_sniffing, - s.client_interface_name , - s.login_time , - r.start_time , - qmg.request_time , - COALESCE(r.cpu_time, s.cpu_time) AS request_cpu_time, - COALESCE(r.logical_reads, s.logical_reads) AS request_logical_reads, - COALESCE(r.writes, s.writes) AS request_writes, - COALESCE(r.reads, s.reads) AS request_physical_reads , - s.cpu_time AS session_cpu, - s.logical_reads AS session_logical_reads, - s.reads AS session_physical_reads , - s.writes AS session_writes, - tempdb_allocations.tempdb_allocations_mb, - s.memory_usage , - r.estimated_completion_time , - r.percent_complete , - r.deadlock_priority , - CASE - WHEN s.transaction_isolation_level = 0 THEN ''Unspecified'' - WHEN s.transaction_isolation_level = 1 THEN ''Read Uncommitted'' - WHEN s.transaction_isolation_level = 2 AND EXISTS (SELECT 1 FROM sys.databases WHERE name = DB_NAME(r.database_id) AND is_read_committed_snapshot_on = 1) THEN ''Read Committed Snapshot Isolation'' - WHEN s.transaction_isolation_level = 2 THEN ''Read Committed'' - WHEN s.transaction_isolation_level = 3 THEN ''Repeatable Read'' - WHEN s.transaction_isolation_level = 4 THEN ''Serializable'' - WHEN s.transaction_isolation_level = 5 THEN ''Snapshot'' - ELSE ''WHAT HAVE YOU DONE?'' - END AS transaction_isolation_level , - qmg.dop AS degree_of_parallelism , ' - + - CASE @EnhanceFlag - WHEN 1 THEN N'query_stats.last_dop, - query_stats.min_dop, - query_stats.max_dop, - query_stats.last_grant_kb, - query_stats.min_grant_kb, - query_stats.max_grant_kb, - query_stats.last_used_grant_kb, - query_stats.min_used_grant_kb, - query_stats.max_used_grant_kb, - query_stats.last_ideal_grant_kb, - query_stats.min_ideal_grant_kb, - query_stats.max_ideal_grant_kb, - query_stats.last_reserved_threads, - query_stats.min_reserved_threads, - query_stats.max_reserved_threads, - query_stats.last_used_threads, - query_stats.min_used_threads, - query_stats.max_used_threads,' - ELSE N' NULL AS last_dop, - NULL AS min_dop, - NULL AS max_dop, - NULL AS last_grant_kb, - NULL AS min_grant_kb, - NULL AS max_grant_kb, - NULL AS last_used_grant_kb, - NULL AS min_used_grant_kb, - NULL AS max_used_grant_kb, - NULL AS last_ideal_grant_kb, - NULL AS min_ideal_grant_kb, - NULL AS max_ideal_grant_kb, - NULL AS last_reserved_threads, - NULL AS min_reserved_threads, - NULL AS max_reserved_threads, - NULL AS last_used_threads, - NULL AS min_used_threads, - NULL AS max_used_threads,' - END + EXECUTE sp_executesql @StringToExecute, @ParmDefinitions, @StartSampleTimeText = @Parm1; - SET @StringToExecute += - N' - COALESCE(CAST(qmg.grant_time AS VARCHAR(20)), ''Memory Not Granted'') AS grant_time , - qmg.requested_memory_kb , - qmg.granted_memory_kb AS grant_memory_kb, - CASE WHEN qmg.grant_time IS NULL THEN ''N/A'' - WHEN qmg.requested_memory_kb < qmg.granted_memory_kb - THEN ''Query Granted Less Than Query Requested'' - ELSE ''Memory Request Granted'' - END AS is_request_granted , - qmg.required_memory_kb , - qmg.used_memory_kb AS query_memory_grant_used_memory_kb, - qmg.ideal_memory_kb , - qmg.is_small , - qmg.timeout_sec , - qmg.resource_semaphore_id , - COALESCE(CAST(qmg.wait_order AS VARCHAR(20)), ''N/A'') AS wait_order , - COALESCE(CAST(qmg.wait_time_ms AS VARCHAR(20)), - ''N/A'') AS wait_time_ms , - CASE qmg.is_next_candidate - WHEN 0 THEN ''No'' - WHEN 1 THEN ''Yes'' - ELSE ''N/A'' - END AS next_candidate_for_memory_grant , - qrs.target_memory_kb , - COALESCE(CAST(qrs.max_target_memory_kb AS VARCHAR(20)), - ''Small Query Resource Semaphore'') AS max_target_memory_kb , - qrs.total_memory_kb , - qrs.available_memory_kb , - qrs.granted_memory_kb , - qrs.used_memory_kb AS query_resource_semaphore_used_memory_kb, - qrs.grantee_count , - qrs.waiter_count , - qrs.timeout_error_count , - COALESCE(CAST(qrs.forced_grant_count AS VARCHAR(20)), - ''Small Query Resource Semaphore'') AS forced_grant_count, - wg.name AS workload_group_name, - rp.name AS resource_pool_name, - CONVERT(VARCHAR(128), r.context_info) AS context_info, - r.query_hash, r.query_plan_hash, r.sql_handle, r.plan_handle, r.statement_start_offset, r.statement_end_offset ' - END /* IF @ExpertMode = 1 */ - - SET @StringToExecute += - N' FROM sys.dm_exec_sessions AS s - LEFT JOIN sys.dm_exec_requests AS r - ON r.session_id = s.session_id - LEFT JOIN ( SELECT DISTINCT - wait.session_id , - ( SELECT waitwait.wait_type + N'' ('' - + CAST(MAX(waitwait.wait_duration_ms) AS NVARCHAR(128)) - + N'' ms) '' - FROM sys.dm_os_waiting_tasks AS waitwait - WHERE waitwait.session_id = wait.session_id - GROUP BY waitwait.wait_type - ORDER BY SUM(waitwait.wait_duration_ms) DESC - FOR - XML PATH('''') ) AS wait_info - FROM sys.dm_os_waiting_tasks AS wait ) AS wt - ON s.session_id = wt.session_id - LEFT JOIN sys.dm_exec_query_stats AS query_stats - ON r.sql_handle = query_stats.sql_handle - AND r.plan_handle = query_stats.plan_handle - AND r.statement_start_offset = query_stats.statement_start_offset - AND r.statement_end_offset = query_stats.statement_end_offset - ' - + - CASE @SessionWaits - WHEN 1 THEN @SessionWaitsSQL - ELSE N'' - END - + - N' - LEFT JOIN sys.dm_exec_query_memory_grants qmg - ON r.session_id = qmg.session_id - AND r.request_id = qmg.request_id - LEFT JOIN sys.dm_exec_query_resource_semaphores qrs - ON qmg.resource_semaphore_id = qrs.resource_semaphore_id - AND qmg.pool_id = qrs.pool_id - LEFT JOIN sys.resource_governor_workload_groups wg - ON s.group_id = wg.group_id - LEFT JOIN sys.resource_governor_resource_pools rp - ON wg.pool_id = rp.pool_id - OUTER APPLY ( - SELECT TOP 1 - b.dbid, b.last_batch, b.open_tran, b.sql_handle, - b.session_id, b.blocking_session_id, b.lastwaittype, b.waittime - FROM @blocked b - WHERE (s.session_id = b.session_id - OR s.session_id = b.blocking_session_id) - ) AS blocked - OUTER APPLY sys.dm_exec_sql_text(COALESCE(r.sql_handle, blocked.sql_handle)) AS dest - OUTER APPLY sys.dm_exec_query_plan(r.plan_handle) AS derp - OUTER APPLY ( - SELECT CONVERT(DECIMAL(38,2), SUM( (((tsu.user_objects_alloc_page_count - user_objects_dealloc_page_count) * 8) / 1024.)) ) AS tempdb_allocations_mb - FROM sys.dm_db_task_space_usage tsu - WHERE tsu.request_id = r.request_id - AND tsu.session_id = r.session_id - AND tsu.session_id = s.session_id - ) as tempdb_allocations - ' - + @QueryStatsXMLSQL - + - N' - WHERE s.session_id <> @@SPID - AND s.host_name IS NOT NULL - AND r.database_id NOT IN (SELECT database_id FROM #WhoReadableDBs) - ' - + CASE WHEN @ShowSleepingSPIDs = 0 THEN - N' AND COALESCE(DB_NAME(r.database_id), DB_NAME(blocked.dbid)) IS NOT NULL' - WHEN @ShowSleepingSPIDs = 1 THEN - N' OR COALESCE(r.open_transaction_count, blocked.open_tran) >= 1' - ELSE N'' END; + RAISERROR('@CheckProcedureCache = 1, totaling up plan cache metrics',10,1) WITH NOWAIT; + + /* Get the totals for the entire plan cache */ + INSERT INTO #QueryStats (Pass, SampleTime, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time) + SELECT 0 AS Pass, SYSDATETIMEOFFSET(), SUM(execution_count), SUM(total_worker_time), SUM(total_physical_reads), SUM(total_logical_writes), SUM(total_logical_reads), SUM(total_clr_time), SUM(total_elapsed_time), MIN(creation_time) + FROM sys.dm_exec_query_stats qs; -END /* IF @ProductVersionMajor >= 11 */ + RAISERROR('@CheckProcedureCache = 1, so analyzing execution plans',10,1) WITH NOWAIT; + /* + Pick the most resource-intensive queries to review. Update the Points field + in #QueryStats - if a query is in the top 10 for logical reads, CPU time, + duration, or execution, add 1 to its points. + */ + WITH qsTop AS ( + SELECT TOP 10 qsNow.ID + FROM #QueryStats qsNow + INNER JOIN #QueryStats qsFirst ON qsNow.[sql_handle] = qsFirst.[sql_handle] AND qsNow.statement_start_offset = qsFirst.statement_start_offset AND qsNow.statement_end_offset = qsFirst.statement_end_offset AND qsNow.plan_generation_num = qsFirst.plan_generation_num AND qsNow.plan_handle = qsFirst.plan_handle AND qsFirst.Pass = 1 + WHERE qsNow.total_elapsed_time > qsFirst.total_elapsed_time + AND qsNow.Pass = 2 + AND qsNow.total_elapsed_time - qsFirst.total_elapsed_time > 1000000 /* Only queries with over 1 second of runtime */ + ORDER BY (qsNow.total_elapsed_time - COALESCE(qsFirst.total_elapsed_time, 0)) DESC) + UPDATE #QueryStats + SET Points = Points + 1 + FROM #QueryStats qs + INNER JOIN qsTop ON qs.ID = qsTop.ID; -IF (@MinElapsedSeconds + @MinCPUTime + @MinLogicalReads + @MinPhysicalReads + @MinWrites + @MinTempdbMB + @MinRequestedMemoryKB + @MinBlockingSeconds) > 0 - BEGIN - /* They're filtering for something, so set up a where clause that will let any (not all combined) of the min triggers work: */ - SET @StringToExecute += N' AND (1 = 0 '; - IF @MinElapsedSeconds > 0 - SET @StringToExecute += N' OR ABS(COALESCE(r.total_elapsed_time,0)) / 1000 >= ' + CAST(@MinElapsedSeconds AS NVARCHAR(20)); - IF @MinCPUTime > 0 - SET @StringToExecute += N' OR COALESCE(r.cpu_time, s.cpu_time,0) / 1000 >= ' + CAST(@MinCPUTime AS NVARCHAR(20)); - IF @MinLogicalReads > 0 - SET @StringToExecute += N' OR COALESCE(r.logical_reads, s.logical_reads,0) >= ' + CAST(@MinLogicalReads AS NVARCHAR(20)); - IF @MinPhysicalReads > 0 - SET @StringToExecute += N' OR COALESCE(s.reads,0) >= ' + CAST(@MinPhysicalReads AS NVARCHAR(20)); - IF @MinWrites > 0 - SET @StringToExecute += N' OR COALESCE(r.writes, s.writes,0) >= ' + CAST(@MinWrites AS NVARCHAR(20)); - IF @MinTempdbMB > 0 - SET @StringToExecute += N' OR COALESCE(tempdb_allocations.tempdb_allocations_mb,0) >= ' + CAST(@MinTempdbMB AS NVARCHAR(20)); - IF @MinRequestedMemoryKB > 0 - SET @StringToExecute += N' OR COALESCE(qmg.requested_memory_kb,0) >= ' + CAST(@MinRequestedMemoryKB AS NVARCHAR(20)); - /* Blocking is a little different - we're going to return ALL of the queries if we meet the blocking threshold. */ - IF @MinBlockingSeconds > 0 - SET @StringToExecute += N' OR (SELECT SUM(waittime / 1000) FROM @blocked) >= ' + CAST(@MinBlockingSeconds AS NVARCHAR(20)); - SET @StringToExecute += N' ) '; - END + WITH qsTop AS ( + SELECT TOP 10 qsNow.ID + FROM #QueryStats qsNow + INNER JOIN #QueryStats qsFirst ON qsNow.[sql_handle] = qsFirst.[sql_handle] AND qsNow.statement_start_offset = qsFirst.statement_start_offset AND qsNow.statement_end_offset = qsFirst.statement_end_offset AND qsNow.plan_generation_num = qsFirst.plan_generation_num AND qsNow.plan_handle = qsFirst.plan_handle AND qsFirst.Pass = 1 + WHERE qsNow.total_logical_reads > qsFirst.total_logical_reads + AND qsNow.Pass = 2 + AND qsNow.total_logical_reads - qsFirst.total_logical_reads > 1000 /* Only queries with over 1000 reads */ + ORDER BY (qsNow.total_logical_reads - COALESCE(qsFirst.total_logical_reads, 0)) DESC) + UPDATE #QueryStats + SET Points = Points + 1 + FROM #QueryStats qs + INNER JOIN qsTop ON qs.ID = qsTop.ID; -SET @StringToExecute += - N' ORDER BY ' + CASE WHEN @SortOrder = 'session_id' THEN '[session_id] DESC' - WHEN @SortOrder = 'query_cost' THEN '[query_cost] DESC' - WHEN @SortOrder = 'database_name' THEN '[database_name] ASC' - WHEN @SortOrder = 'open_transaction_count' THEN '[open_transaction_count] DESC' - WHEN @SortOrder = 'is_implicit_transaction' THEN '[is_implicit_transaction] DESC' - WHEN @SortOrder = 'login_name' THEN '[login_name] ASC' - WHEN @SortOrder = 'program_name' THEN '[program_name] ASC' - WHEN @SortOrder = 'client_interface_name' THEN '[client_interface_name] ASC' - WHEN @SortOrder = 'request_cpu_time' THEN 'COALESCE(r.cpu_time, s.cpu_time) DESC' - WHEN @SortOrder = 'request_logical_reads' THEN 'COALESCE(r.logical_reads, s.logical_reads) DESC' - WHEN @SortOrder = 'request_writes' THEN 'COALESCE(r.writes, s.writes) DESC' - WHEN @SortOrder = 'request_physical_reads' THEN 'COALESCE(r.reads, s.reads) DESC ' - WHEN @SortOrder = 'session_cpu' THEN 's.cpu_time DESC' - WHEN @SortOrder = 'session_logical_reads' THEN 's.logical_reads DESC' - WHEN @SortOrder = 'session_physical_reads' THEN 's.reads DESC' - WHEN @SortOrder = 'session_writes' THEN 's.writes DESC' - WHEN @SortOrder = 'tempdb_allocations_mb' THEN '[tempdb_allocations_mb] DESC' - WHEN @SortOrder = 'memory_usage' THEN '[memory_usage] DESC' - WHEN @SortOrder = 'deadlock_priority' THEN 'r.deadlock_priority DESC' - WHEN @SortOrder = 'transaction_isolation_level' THEN 'r.[transaction_isolation_level] DESC' - WHEN @SortOrder = 'requested_memory_kb' THEN '[requested_memory_kb] DESC' - WHEN @SortOrder = 'grant_memory_kb' THEN 'qmg.granted_memory_kb DESC' - WHEN @SortOrder = 'grant' THEN 'qmg.granted_memory_kb DESC' - WHEN @SortOrder = 'query_memory_grant_used_memory_kb' THEN 'qmg.used_memory_kb DESC' - WHEN @SortOrder = 'ideal_memory_kb' THEN '[ideal_memory_kb] DESC' - WHEN @SortOrder = 'workload_group_name' THEN 'wg.name ASC' - WHEN @SortOrder = 'resource_pool_name' THEN 'rp.name ASC' - ELSE '[elapsed_time] DESC' - END + ' - '; + WITH qsTop AS ( + SELECT TOP 10 qsNow.ID + FROM #QueryStats qsNow + INNER JOIN #QueryStats qsFirst ON qsNow.[sql_handle] = qsFirst.[sql_handle] AND qsNow.statement_start_offset = qsFirst.statement_start_offset AND qsNow.statement_end_offset = qsFirst.statement_end_offset AND qsNow.plan_generation_num = qsFirst.plan_generation_num AND qsNow.plan_handle = qsFirst.plan_handle AND qsFirst.Pass = 1 + WHERE qsNow.total_worker_time > qsFirst.total_worker_time + AND qsNow.Pass = 2 + AND qsNow.total_worker_time - qsFirst.total_worker_time > 1000000 /* Only queries with over 1 second of worker time */ + ORDER BY (qsNow.total_worker_time - COALESCE(qsFirst.total_worker_time, 0)) DESC) + UPDATE #QueryStats + SET Points = Points + 1 + FROM #QueryStats qs + INNER JOIN qsTop ON qs.ID = qsTop.ID; + WITH qsTop AS ( + SELECT TOP 10 qsNow.ID + FROM #QueryStats qsNow + INNER JOIN #QueryStats qsFirst ON qsNow.[sql_handle] = qsFirst.[sql_handle] AND qsNow.statement_start_offset = qsFirst.statement_start_offset AND qsNow.statement_end_offset = qsFirst.statement_end_offset AND qsNow.plan_generation_num = qsFirst.plan_generation_num AND qsNow.plan_handle = qsFirst.plan_handle AND qsFirst.Pass = 1 + WHERE qsNow.execution_count > qsFirst.execution_count + AND qsNow.Pass = 2 + AND (qsNow.total_elapsed_time - qsFirst.total_elapsed_time > 1000000 /* Only queries with over 1 second of runtime */ + OR qsNow.total_logical_reads - qsFirst.total_logical_reads > 1000 /* Only queries with over 1000 reads */ + OR qsNow.total_worker_time - qsFirst.total_worker_time > 1000000 /* Only queries with over 1 second of worker time */) + ORDER BY (qsNow.execution_count - COALESCE(qsFirst.execution_count, 0)) DESC) + UPDATE #QueryStats + SET Points = Points + 1 + FROM #QueryStats qs + INNER JOIN qsTop ON qs.ID = qsTop.ID; -IF @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @OutputTableName IS NOT NULL - AND EXISTS ( SELECT * - FROM sys.databases - WHERE QUOTENAME([name]) = @OutputDatabaseName) - BEGIN - SET @StringToExecute = N'USE ' - + @OutputDatabaseName + N'; ' - + @BlockingCheck + - + ' INSERT INTO ' - + @OutputSchemaName + N'.' - + @OutputTableName - + N'(ServerName - ,CheckDate - ,[elapsed_time] - ,[session_id] - ,[database_name] - ,[query_text] - ,[query_plan]' - + CASE WHEN @ProductVersionMajor >= 11 THEN N',[live_query_plan]' ELSE N'' END + N' - ,[query_cost] - ,[status] - ,[wait_info]' - + CASE WHEN @ProductVersionMajor >= 11 THEN N',[top_session_waits]' ELSE N'' END + N' - ,[blocking_session_id] - ,[open_transaction_count] - ,[is_implicit_transaction] - ,[nt_domain] - ,[host_name] - ,[login_name] - ,[nt_user_name] - ,[program_name] - ,[fix_parameter_sniffing] - ,[client_interface_name] - ,[login_time] - ,[start_time] - ,[request_time] - ,[request_cpu_time] - ,[request_logical_reads] - ,[request_writes] - ,[request_physical_reads] - ,[session_cpu] - ,[session_logical_reads] - ,[session_physical_reads] - ,[session_writes] - ,[tempdb_allocations_mb] - ,[memory_usage] - ,[estimated_completion_time] - ,[percent_complete] - ,[deadlock_priority] - ,[transaction_isolation_level] - ,[degree_of_parallelism]' - + CASE WHEN @ProductVersionMajor >= 11 THEN N' - ,[last_dop] - ,[min_dop] - ,[max_dop] - ,[last_grant_kb] - ,[min_grant_kb] - ,[max_grant_kb] - ,[last_used_grant_kb] - ,[min_used_grant_kb] - ,[max_used_grant_kb] - ,[last_ideal_grant_kb] - ,[min_ideal_grant_kb] - ,[max_ideal_grant_kb] - ,[last_reserved_threads] - ,[min_reserved_threads] - ,[max_reserved_threads] - ,[last_used_threads] - ,[min_used_threads] - ,[max_used_threads]' ELSE N'' END + N' - ,[grant_time] - ,[requested_memory_kb] - ,[grant_memory_kb] - ,[is_request_granted] - ,[required_memory_kb] - ,[query_memory_grant_used_memory_kb] - ,[ideal_memory_kb] - ,[is_small] - ,[timeout_sec] - ,[resource_semaphore_id] - ,[wait_order] - ,[wait_time_ms] - ,[next_candidate_for_memory_grant] - ,[target_memory_kb] - ,[max_target_memory_kb] - ,[total_memory_kb] - ,[available_memory_kb] - ,[granted_memory_kb] - ,[query_resource_semaphore_used_memory_kb] - ,[grantee_count] - ,[waiter_count] - ,[timeout_error_count] - ,[forced_grant_count] - ,[workload_group_name] - ,[resource_pool_name] - ,[context_info]' - + CASE WHEN @ProductVersionMajor >= 11 THEN N' - ,[query_hash] - ,[query_plan_hash] - ,[sql_handle] - ,[plan_handle] - ,[statement_start_offset] - ,[statement_end_offset]' ELSE N'' END + N' -) - SELECT @@SERVERNAME, COALESCE(@CheckDateOverride, SYSDATETIMEOFFSET()) AS CheckDate , ' - + @StringToExecute; - END -ELSE - SET @StringToExecute = @BlockingCheck + N' SELECT GETDATE() AS run_date , ' + @StringToExecute; + /* Query Stats - Most Resource-Intensive Queries - CheckID 17 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 17',10,1) WITH NOWAIT; + END -/* If the server has > 50GB of memory, add a max grant hint to avoid getting a giant grant */ -IF (@ProductVersionMajor = 11 AND @ProductVersionMinor >= 6020) - OR (@ProductVersionMajor = 12 AND @ProductVersionMinor >= 5000 ) - OR (@ProductVersionMajor >= 13 ) - AND 50000000 < (SELECT cntr_value - FROM sys.dm_os_performance_counters - WHERE object_name LIKE '%:Memory Manager%' - AND counter_name LIKE 'Target Server Memory (KB)%') - BEGIN - SET @StringToExecute = @StringToExecute + N' OPTION (MAX_GRANT_PERCENT = 1, RECOMPILE) '; - END -ELSE - BEGIN - SET @StringToExecute = @StringToExecute + N' OPTION (RECOMPILE) '; - END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, QueryText, QueryStatsNowID, QueryStatsFirstID, PlanHandle, QueryHash) + SELECT 17, 210, 'Query Stats', 'Most Resource-Intensive Queries', 'http://www.BrentOzar.com/go/topqueries', + 'Query stats during the sample:' + @LineFeed + + 'Executions: ' + CAST(qsNow.execution_count - (COALESCE(qsFirst.execution_count, 0)) AS NVARCHAR(100)) + @LineFeed + + 'Elapsed Time: ' + CAST(qsNow.total_elapsed_time - (COALESCE(qsFirst.total_elapsed_time, 0)) AS NVARCHAR(100)) + @LineFeed + + 'CPU Time: ' + CAST(qsNow.total_worker_time - (COALESCE(qsFirst.total_worker_time, 0)) AS NVARCHAR(100)) + @LineFeed + + 'Logical Reads: ' + CAST(qsNow.total_logical_reads - (COALESCE(qsFirst.total_logical_reads, 0)) AS NVARCHAR(100)) + @LineFeed + + 'Logical Writes: ' + CAST(qsNow.total_logical_writes - (COALESCE(qsFirst.total_logical_writes, 0)) AS NVARCHAR(100)) + @LineFeed + + 'CLR Time: ' + CAST(qsNow.total_clr_time - (COALESCE(qsFirst.total_clr_time, 0)) AS NVARCHAR(100)) + @LineFeed + + @LineFeed + @LineFeed + 'Query stats since ' + CONVERT(NVARCHAR(100), qsNow.creation_time ,121) + @LineFeed + + 'Executions: ' + CAST(qsNow.execution_count AS NVARCHAR(100)) + + CASE qsTotal.execution_count WHEN 0 THEN '' ELSE (' - Percent of Server Total: ' + CAST(CAST(100.0 * qsNow.execution_count / qsTotal.execution_count AS DECIMAL(6,2)) AS NVARCHAR(100)) + '%') END + @LineFeed + + 'Elapsed Time: ' + CAST(qsNow.total_elapsed_time AS NVARCHAR(100)) + + CASE qsTotal.total_elapsed_time WHEN 0 THEN '' ELSE (' - Percent of Server Total: ' + CAST(CAST(100.0 * qsNow.total_elapsed_time / qsTotal.total_elapsed_time AS DECIMAL(6,2)) AS NVARCHAR(100)) + '%') END + @LineFeed + + 'CPU Time: ' + CAST(qsNow.total_worker_time AS NVARCHAR(100)) + + CASE qsTotal.total_worker_time WHEN 0 THEN '' ELSE (' - Percent of Server Total: ' + CAST(CAST(100.0 * qsNow.total_worker_time / qsTotal.total_worker_time AS DECIMAL(6,2)) AS NVARCHAR(100)) + '%') END + @LineFeed + + 'Logical Reads: ' + CAST(qsNow.total_logical_reads AS NVARCHAR(100)) + + CASE qsTotal.total_logical_reads WHEN 0 THEN '' ELSE (' - Percent of Server Total: ' + CAST(CAST(100.0 * qsNow.total_logical_reads / qsTotal.total_logical_reads AS DECIMAL(6,2)) AS NVARCHAR(100)) + '%') END + @LineFeed + + 'Logical Writes: ' + CAST(qsNow.total_logical_writes AS NVARCHAR(100)) + + CASE qsTotal.total_logical_writes WHEN 0 THEN '' ELSE (' - Percent of Server Total: ' + CAST(CAST(100.0 * qsNow.total_logical_writes / qsTotal.total_logical_writes AS DECIMAL(6,2)) AS NVARCHAR(100)) + '%') END + @LineFeed + + 'CLR Time: ' + CAST(qsNow.total_clr_time AS NVARCHAR(100)) + + CASE qsTotal.total_clr_time WHEN 0 THEN '' ELSE (' - Percent of Server Total: ' + CAST(CAST(100.0 * qsNow.total_clr_time / qsTotal.total_clr_time AS DECIMAL(6,2)) AS NVARCHAR(100)) + '%') END + @LineFeed + + --@LineFeed + @LineFeed + 'Query hash: ' + CAST(qsNow.query_hash AS NVARCHAR(100)) + @LineFeed + + --@LineFeed + @LineFeed + 'Query plan hash: ' + CAST(qsNow.query_plan_hash AS NVARCHAR(100)) + + @LineFeed AS Details, + 'See the URL for tuning tips on why this query may be consuming resources.' AS HowToStopIt, + qp.query_plan, + QueryText = SUBSTRING(st.text, + (qsNow.statement_start_offset / 2) + 1, + ((CASE qsNow.statement_end_offset + WHEN -1 THEN DATALENGTH(st.text) + ELSE qsNow.statement_end_offset + END - qsNow.statement_start_offset) / 2) + 1), + qsNow.ID AS QueryStatsNowID, + qsFirst.ID AS QueryStatsFirstID, + qsNow.plan_handle AS PlanHandle, + qsNow.query_hash + FROM #QueryStats qsNow + INNER JOIN #QueryStats qsTotal ON qsTotal.Pass = 0 + LEFT OUTER JOIN #QueryStats qsFirst ON qsNow.[sql_handle] = qsFirst.[sql_handle] AND qsNow.statement_start_offset = qsFirst.statement_start_offset AND qsNow.statement_end_offset = qsFirst.statement_end_offset AND qsNow.plan_generation_num = qsFirst.plan_generation_num AND qsNow.plan_handle = qsFirst.plan_handle AND qsFirst.Pass = 1 + CROSS APPLY sys.dm_exec_sql_text(qsNow.sql_handle) AS st + CROSS APPLY sys.dm_exec_query_plan(qsNow.plan_handle) AS qp + WHERE qsNow.Points > 0 AND st.text IS NOT NULL AND qp.query_plan IS NOT NULL; -/* Be good: */ -SET @StringToExecute = @StringToExecute + N' ; '; + UPDATE #BlitzFirstResults + SET DatabaseID = CAST(attr.value AS INT), + DatabaseName = DB_NAME(CAST(attr.value AS INT)) + FROM #BlitzFirstResults + CROSS APPLY sys.dm_exec_plan_attributes(#BlitzFirstResults.PlanHandle) AS attr + WHERE attr.attribute = 'dbid'; -IF @Debug = 1 + END; /* IF DATEDIFF(ss, @FinishSampleTime, SYSDATETIMEOFFSET()) > 10 AND @CheckProcedureCache = 1 */ + + + RAISERROR('Analyzing changes between first and second passes of DMVs',10,1) WITH NOWAIT; + + /* Wait Stats - CheckID 6 */ + /* Compare the current wait stats to the sample we took at the start, and insert the top 10 waits. */ + IF (@Debug = 1) BEGIN - PRINT CONVERT(VARCHAR(8000), SUBSTRING(@StringToExecute, 0, 8000)) - PRINT CONVERT(VARCHAR(8000), SUBSTRING(@StringToExecute, 8000, 16000)) + RAISERROR('Running CheckID 6',10,1) WITH NOWAIT; END -EXEC sp_executesql @StringToExecute, - N'@CheckDateOverride DATETIMEOFFSET', - @CheckDateOverride; + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, DetailsInt) + SELECT TOP 10 6 AS CheckID, + 200 AS Priority, + 'Wait Stats' AS FindingGroup, + wNow.wait_type AS Finding, /* IF YOU CHANGE THIS, STUFF WILL BREAK. Other checks look for wait type names in the Finding field. See checks 11, 12 as example. */ + N'https://www.sqlskills.com/help/waits/' + LOWER(wNow.wait_type) + '/' AS URL, + 'For ' + CAST(((wNow.wait_time_ms - COALESCE(wBase.wait_time_ms,0)) / 1000) AS NVARCHAR(100)) + ' seconds over the last ' + CASE @Seconds WHEN 0 THEN (CAST(DATEDIFF(dd,@StartSampleTime,@FinishSampleTime) AS NVARCHAR(10)) + ' days') ELSE (CAST(@Seconds AS NVARCHAR(10)) + ' seconds') END + ', SQL Server was waiting on this particular bottleneck.' + @LineFeed + @LineFeed AS Details, + 'See the URL for more details on how to mitigate this wait type.' AS HowToStopIt, + ((wNow.wait_time_ms - COALESCE(wBase.wait_time_ms,0)) / 1000) AS DetailsInt + FROM #WaitStats wNow + LEFT OUTER JOIN #WaitStats wBase ON wNow.wait_type = wBase.wait_type AND wNow.SampleTime > wBase.SampleTime + WHERE wNow.wait_time_ms > (wBase.wait_time_ms + (.5 * (DATEDIFF(ss,@StartSampleTime,@FinishSampleTime)) * 1000)) /* Only look for things we've actually waited on for half of the time or more */ + ORDER BY (wNow.wait_time_ms - COALESCE(wBase.wait_time_ms,0)) DESC; -END -GO -IF OBJECT_ID('dbo.sp_DatabaseRestore') IS NULL - EXEC ('CREATE PROCEDURE dbo.sp_DatabaseRestore AS RETURN 0;'); -GO -ALTER PROCEDURE [dbo].[sp_DatabaseRestore] - @Database NVARCHAR(128) = NULL, - @RestoreDatabaseName NVARCHAR(128) = NULL, - @BackupPathFull NVARCHAR(260) = NULL, - @BackupPathDiff NVARCHAR(260) = NULL, - @BackupPathLog NVARCHAR(260) = NULL, - @MoveFiles BIT = 1, - @MoveDataDrive NVARCHAR(260) = NULL, - @MoveLogDrive NVARCHAR(260) = NULL, - @MoveFilestreamDrive NVARCHAR(260) = NULL, - @BufferCount INT = NULL, - @MaxTransferSize INT = NULL, - @BlockSize INT = NULL, - @TestRestore BIT = 0, - @RunCheckDB BIT = 0, - @RestoreDiff BIT = 0, - @ContinueLogs BIT = 0, - @StandbyMode BIT = 0, - @StandbyUndoPath NVARCHAR(MAX) = NULL, - @RunRecovery BIT = 0, - @ForceSimpleRecovery BIT = 0, - @ExistingDBAction tinyint = 0, - @StopAt NVARCHAR(14) = NULL, - @OnlyLogsAfter NVARCHAR(14) = NULL, - @SimpleFolderEnumeration BIT = 0, - @SkipBackupsAlreadyInMsdb BIT = 0, - @DatabaseOwner sysname = NULL, - @Execute CHAR(1) = Y, - @Debug INT = 0, - @Help BIT = 0, - @Version VARCHAR(30) = NULL OUTPUT, - @VersionDate DATETIME = NULL OUTPUT, - @VersionCheckMode BIT = 0 -AS -SET NOCOUNT ON; + /* Server Performance - Poison Wait Detected - CheckID 30 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 30',10,1) WITH NOWAIT; + END -/*Versioning details*/ + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, DetailsInt) + SELECT 30 AS CheckID, + 10 AS Priority, + 'Server Performance' AS FindingGroup, + 'Poison Wait Detected: ' + wNow.wait_type AS Finding, + N'http://www.brentozar.com/go/poison/#' + wNow.wait_type AS URL, + 'For ' + CAST(((wNow.wait_time_ms - COALESCE(wBase.wait_time_ms,0)) / 1000) AS NVARCHAR(100)) + ' seconds over the last ' + CASE @Seconds WHEN 0 THEN (CAST(DATEDIFF(dd,@StartSampleTime,@FinishSampleTime) AS NVARCHAR(10)) + ' days') ELSE (CAST(@Seconds AS NVARCHAR(10)) + ' seconds') END + ', SQL Server was waiting on this particular bottleneck.' + @LineFeed + @LineFeed AS Details, + 'See the URL for more details on how to mitigate this wait type.' AS HowToStopIt, + ((wNow.wait_time_ms - COALESCE(wBase.wait_time_ms,0)) / 1000) AS DetailsInt + FROM #WaitStats wNow + LEFT OUTER JOIN #WaitStats wBase ON wNow.wait_type = wBase.wait_type AND wNow.SampleTime > wBase.SampleTime + WHERE wNow.wait_type IN ('IO_QUEUE_LIMIT', 'IO_RETRY', 'LOG_RATE_GOVERNOR', 'POOL_LOG_RATE_GOVERNOR', 'PREEMPTIVE_DEBUG', 'RESMGR_THROTTLED', 'RESOURCE_SEMAPHORE', 'RESOURCE_SEMAPHORE_QUERY_COMPILE','SE_REPL_CATCHUP_THROTTLE','SE_REPL_COMMIT_ACK','SE_REPL_COMMIT_TURN','SE_REPL_ROLLBACK_ACK','SE_REPL_SLOW_SECONDARY_THROTTLE','THREADPOOL') + AND wNow.wait_time_ms > (wBase.wait_time_ms + 1000); -SELECT @Version = '8.0', @VersionDate = '20210117'; -IF(@VersionCheckMode = 1) -BEGIN - RETURN; -END; + /* Server Performance - Slow Data File Reads - CheckID 11 */ + IF EXISTS (SELECT * FROM #BlitzFirstResults WHERE Finding LIKE 'PAGEIOLATCH%') + BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 11',10,1) WITH NOWAIT; + END - -IF @Help = 1 -BEGIN - PRINT ' - /* - sp_DatabaseRestore from http://FirstResponderKit.org - - This script will restore a database from a given file path. - - To learn more, visit http://FirstResponderKit.org where you can download new - versions for free, watch training videos on how it works, get more info on - the findings, contribute your own code, and more. - - Known limitations of this version: - - Only Microsoft-supported versions of SQL Server. Sorry, 2005 and 2000. - - Tastes awful with marmite. - - Unknown limitations of this version: - - None. (If we knew them, they would be known. Duh.) - - Changes - for the full list of improvements and fixes in this version, see: - https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/ - - MIT License - - Copyright (c) 2021 Brent Ozar Unlimited - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE. - - */ - '; - - PRINT ' - /* - EXEC dbo.sp_DatabaseRestore - @Database = ''LogShipMe'', - @BackupPathFull = ''D:\Backup\SQL2016PROD1A\LogShipMe\FULL\'', - @BackupPathLog = ''D:\Backup\SQL2016PROD1A\LogShipMe\LOG\'', - @ContinueLogs = 0, - @RunRecovery = 0; - - EXEC dbo.sp_DatabaseRestore - @Database = ''LogShipMe'', - @BackupPathFull = ''D:\Backup\SQL2016PROD1A\LogShipMe\FULL\'', - @BackupPathLog = ''D:\Backup\SQL2016PROD1A\LogShipMe\LOG\'', - @ContinueLogs = 1, - @RunRecovery = 0; - - EXEC dbo.sp_DatabaseRestore - @Database = ''LogShipMe'', - @BackupPathFull = ''D:\Backup\SQL2016PROD1A\LogShipMe\FULL\'', - @BackupPathLog = ''D:\Backup\SQL2016PROD1A\LogShipMe\LOG\'', - @ContinueLogs = 1, - @RunRecovery = 1; - - EXEC dbo.sp_DatabaseRestore - @Database = ''LogShipMe'', - @BackupPathFull = ''D:\Backup\SQL2016PROD1A\LogShipMe\FULL\'', - @BackupPathLog = ''D:\Backup\SQL2016PROD1A\LogShipMe\LOG\'', - @ContinueLogs = 0, - @RunRecovery = 1; - - EXEC dbo.sp_DatabaseRestore - @Database = ''LogShipMe'', - @BackupPathFull = ''D:\Backup\SQL2016PROD1A\LogShipMe\FULL\'', - @BackupPathDiff = ''D:\Backup\SQL2016PROD1A\LogShipMe\DIFF\'', - @BackupPathLog = ''D:\Backup\SQL2016PROD1A\LogShipMe\LOG\'', - @RestoreDiff = 1, - @ContinueLogs = 0, - @RunRecovery = 1; - - EXEC dbo.sp_DatabaseRestore - @Database = ''LogShipMe'', - @BackupPathFull = ''\\StorageServer\LogShipMe\FULL\'', - @BackupPathDiff = ''\\StorageServer\LogShipMe\DIFF\'', - @BackupPathLog = ''\\StorageServer\LogShipMe\LOG\'', - @RestoreDiff = 1, - @ContinueLogs = 0, - @RunRecovery = 1, - @TestRestore = 1, - @RunCheckDB = 1, - @Debug = 0; + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, DatabaseID, DatabaseName) + SELECT TOP 10 11 AS CheckID, + 50 AS Priority, + 'Server Performance' AS FindingGroup, + 'Slow Data File Reads' AS Finding, + 'http://www.BrentOzar.com/go/slow/' AS URL, + 'Your server is experiencing PAGEIOLATCH% waits due to slow data file reads. This file is one of the reasons why.' + @LineFeed + + 'File: ' + fNow.PhysicalName + @LineFeed + + 'Number of reads during the sample: ' + CAST((fNow.num_of_reads - fBase.num_of_reads) AS NVARCHAR(20)) + @LineFeed + + 'Seconds spent waiting on storage for these reads: ' + CAST(((fNow.io_stall_read_ms - fBase.io_stall_read_ms) / 1000.0) AS NVARCHAR(20)) + @LineFeed + + 'Average read latency during the sample: ' + CAST(((fNow.io_stall_read_ms - fBase.io_stall_read_ms) / (fNow.num_of_reads - fBase.num_of_reads) ) AS NVARCHAR(20)) + ' milliseconds' + @LineFeed + + 'Microsoft guidance for data file read speed: 20ms or less.' + @LineFeed + @LineFeed AS Details, + 'See the URL for more details on how to mitigate this wait type.' AS HowToStopIt, + fNow.DatabaseID, + fNow.DatabaseName + FROM #FileStats fNow + INNER JOIN #FileStats fBase ON fNow.DatabaseID = fBase.DatabaseID AND fNow.FileID = fBase.FileID AND fNow.SampleTime > fBase.SampleTime AND fNow.num_of_reads > fBase.num_of_reads AND fNow.io_stall_read_ms > (fBase.io_stall_read_ms + 1000) + WHERE (fNow.io_stall_read_ms - fBase.io_stall_read_ms) / (fNow.num_of_reads - fBase.num_of_reads) >= @FileLatencyThresholdMS + AND fNow.TypeDesc = 'ROWS' + ORDER BY (fNow.io_stall_read_ms - fBase.io_stall_read_ms) / (fNow.num_of_reads - fBase.num_of_reads) DESC; + END; + + /* Server Performance - Slow Log File Writes - CheckID 12 */ + IF EXISTS (SELECT * FROM #BlitzFirstResults WHERE Finding LIKE 'WRITELOG%') + BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 12',10,1) WITH NOWAIT; + END - EXEC dbo.sp_DatabaseRestore - @Database = ''LogShipMe'', - @BackupPathFull = ''\\StorageServer\LogShipMe\FULL\'', - @BackupPathLog = ''\\StorageServer\LogShipMe\LOG\'', - @StandbyMode = 1, - @StandbyUndoPath = ''D:\Data\'', - @ContinueLogs = 1, - @RunRecovery = 0, - @Debug = 0; + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, DatabaseID, DatabaseName) + SELECT TOP 10 12 AS CheckID, + 50 AS Priority, + 'Server Performance' AS FindingGroup, + 'Slow Log File Writes' AS Finding, + 'http://www.BrentOzar.com/go/slow/' AS URL, + 'Your server is experiencing WRITELOG waits due to slow log file writes. This file is one of the reasons why.' + @LineFeed + + 'File: ' + fNow.PhysicalName + @LineFeed + + 'Number of writes during the sample: ' + CAST((fNow.num_of_writes - fBase.num_of_writes) AS NVARCHAR(20)) + @LineFeed + + 'Seconds spent waiting on storage for these writes: ' + CAST(((fNow.io_stall_write_ms - fBase.io_stall_write_ms) / 1000.0) AS NVARCHAR(20)) + @LineFeed + + 'Average write latency during the sample: ' + CAST(((fNow.io_stall_write_ms - fBase.io_stall_write_ms) / (fNow.num_of_writes - fBase.num_of_writes) ) AS NVARCHAR(20)) + ' milliseconds' + @LineFeed + + 'Microsoft guidance for log file write speed: 3ms or less.' + @LineFeed + @LineFeed AS Details, + 'See the URL for more details on how to mitigate this wait type.' AS HowToStopIt, + fNow.DatabaseID, + fNow.DatabaseName + FROM #FileStats fNow + INNER JOIN #FileStats fBase ON fNow.DatabaseID = fBase.DatabaseID AND fNow.FileID = fBase.FileID AND fNow.SampleTime > fBase.SampleTime AND fNow.num_of_writes > fBase.num_of_writes AND fNow.io_stall_write_ms > (fBase.io_stall_write_ms + 1000) + WHERE (fNow.io_stall_write_ms - fBase.io_stall_write_ms) / (fNow.num_of_writes - fBase.num_of_writes) >= @FileLatencyThresholdMS + AND fNow.TypeDesc = 'LOG' + ORDER BY (fNow.io_stall_write_ms - fBase.io_stall_write_ms) / (fNow.num_of_writes - fBase.num_of_writes) DESC; + END; - -- Restore from stripped backup set when multiple paths are used. This example will restore stripped full backup set along with stripped transactional logs set from multiple backup paths - EXEC dbo.sp_DatabaseRestore - @Database = ''DBA'', - @BackupPathFull = ''D:\Backup1\DBA\FULL,D:\Backup2\DBA\FULL'', - @BackupPathLog = ''D:\Backup1\DBA\LOG,D:\Backup2\DBA\LOG'', - @StandbyMode = 0, - @ContinueLogs = 1, - @RunRecovery = 0, - @Debug = 0; - - --This example will restore the latest differential backup, and stop transaction logs at the specified date time. It will execute and print debug information. - EXEC dbo.sp_DatabaseRestore - @Database = ''DBA'', - @BackupPathFull = ''\\StorageServer\LogShipMe\FULL\'', - @BackupPathDiff = ''\\StorageServer\LogShipMe\DIFF\'', - @BackupPathLog = ''\\StorageServer\LogShipMe\LOG\'', - @RestoreDiff = 1, - @ContinueLogs = 0, - @RunRecovery = 1, - @StopAt = ''20170508201501'', - @Debug = 1; - --This example NOT execute the restore. Commands will be printed in a copy/paste ready format only - EXEC dbo.sp_DatabaseRestore - @Database = ''DBA'', - @BackupPathFull = ''\\StorageServer\LogShipMe\FULL\'', - @BackupPathDiff = ''\\StorageServer\LogShipMe\DIFF\'', - @BackupPathLog = ''\\StorageServer\LogShipMe\LOG\'', - @RestoreDiff = 1, - @ContinueLogs = 0, - @RunRecovery = 1, - @TestRestore = 1, - @RunCheckDB = 1, - @Debug = 0, - @Execute = ''N''; - '; - - RETURN; -END; + /* SQL Server Internal Maintenance - Log File Growing - CheckID 13 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 13',10,1) WITH NOWAIT; + END --- Get the SQL Server version number because the columns returned by RESTORE commands vary by version --- Based on: https://www.brentozar.com/archive/2015/05/sql-server-version-detection/ --- Need to capture BuildVersion because RESTORE HEADERONLY changed with 2014 CU1, not RTM -DECLARE @ProductVersion AS NVARCHAR(20) = CAST(SERVERPROPERTY ('productversion') AS NVARCHAR(20)); -DECLARE @MajorVersion AS SMALLINT = CAST(PARSENAME(@ProductVersion, 4) AS SMALLINT); -DECLARE @MinorVersion AS SMALLINT = CAST(PARSENAME(@ProductVersion, 3) AS SMALLINT); -DECLARE @BuildVersion AS SMALLINT = CAST(PARSENAME(@ProductVersion, 2) AS SMALLINT); + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) + SELECT 13 AS CheckID, + 1 AS Priority, + 'SQL Server Internal Maintenance' AS FindingGroup, + 'Log File Growing' AS Finding, + 'http://www.BrentOzar.com/askbrent/file-growing/' AS URL, + 'Number of growths during the sample: ' + CAST(ps.value_delta AS NVARCHAR(20)) + @LineFeed + + 'Determined by sampling Perfmon counter ' + ps.object_name + ' - ' + ps.counter_name + @LineFeed AS Details, + 'Pre-grow data and log files during maintenance windows so that they do not grow during production loads. See the URL for more details.' AS HowToStopIt + FROM #PerfmonStats ps + WHERE ps.Pass = 2 + AND object_name = @ServiceName + ':Databases' + AND counter_name = 'Log Growths' + AND value_delta > 0; -IF @MajorVersion < 10 -BEGIN - RAISERROR('Sorry, DatabaseRestore doesn''t work on versions of SQL prior to 2008.', 15, 1); - RETURN; -END; + /* SQL Server Internal Maintenance - Log File Shrinking - CheckID 14 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 14',10,1) WITH NOWAIT; + END -DECLARE @cmd NVARCHAR(4000) = N'', --Holds xp_cmdshell command - @sql NVARCHAR(MAX) = N'', --Holds executable SQL commands - @LastFullBackup NVARCHAR(500) = N'', --Last full backup name - @LastDiffBackup NVARCHAR(500) = N'', --Last diff backup name - @LastDiffBackupDateTime NVARCHAR(500) = N'', --Last diff backup date - @BackupFile NVARCHAR(500) = N'', --Name of backup file - @BackupDateTime AS CHAR(15) = N'', --Used for comparisons to generate ordered backup files/create a stopat point - @FullLastLSN NUMERIC(25, 0), --LSN for full - @DiffLastLSN NUMERIC(25, 0), --LSN for diff - @HeadersSQL AS NVARCHAR(4000) = N'', --Dynamic insert into #Headers table (deals with varying results from RESTORE FILELISTONLY across different versions) - @MoveOption AS NVARCHAR(MAX) = N'', --If you need to move restored files to a different directory - @LogRecoveryOption AS NVARCHAR(MAX) = N'', --Holds the option to cause logs to be restored in standby mode or with no recovery - @DatabaseLastLSN NUMERIC(25, 0), --redo_start_lsn of the current database - @i TINYINT = 1, --Maintains loop to continue logs - @LogRestoreRanking SMALLINT = 1, --Holds Log iteration # when multiple paths & backup files are being stripped - @LogFirstLSN NUMERIC(25, 0), --Holds first LSN in log backup headers - @LogLastLSN NUMERIC(25, 0), --Holds last LSN in log backup headers - @LogLastNameInMsdbAS NVARCHAR(MAX) = N'', -- Holds last TRN file name already restored - @FileListParamSQL NVARCHAR(4000) = N'', --Holds INSERT list for #FileListParameters - @BackupParameters NVARCHAR(500) = N'', --Used to save BlockSize, MaxTransferSize and BufferCount - @RestoreDatabaseID SMALLINT, --Holds DB_ID of @RestoreDatabaseName - @UnquotedRestoreDatabaseName nvarchar(128); --Holds the unquoted @RestoreDatabaseName + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) + SELECT 14 AS CheckID, + 1 AS Priority, + 'SQL Server Internal Maintenance' AS FindingGroup, + 'Log File Shrinking' AS Finding, + 'http://www.BrentOzar.com/askbrent/file-shrinking/' AS URL, + 'Number of shrinks during the sample: ' + CAST(ps.value_delta AS NVARCHAR(20)) + @LineFeed + + 'Determined by sampling Perfmon counter ' + ps.object_name + ' - ' + ps.counter_name + @LineFeed AS Details, + 'Pre-grow data and log files during maintenance windows so that they do not grow during production loads. See the URL for more details.' AS HowToStopIt + FROM #PerfmonStats ps + WHERE ps.Pass = 2 + AND object_name = @ServiceName + ':Databases' + AND counter_name = 'Log Shrinks' + AND value_delta > 0; -DECLARE @FileListSimple TABLE ( - BackupFile NVARCHAR(255) NOT NULL, - depth int NOT NULL, - [file] int NOT NULL -); + /* Query Problems - Compilations/Sec High - CheckID 15 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 15',10,1) WITH NOWAIT; + END -DECLARE @FileList TABLE ( - BackupPath NVARCHAR(255) NULL, - BackupFile NVARCHAR(255) NULL -); + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) + SELECT 15 AS CheckID, + 50 AS Priority, + 'Query Problems' AS FindingGroup, + 'Compilations/Sec High' AS Finding, + 'http://www.BrentOzar.com/askbrent/compilations/' AS URL, + 'Number of batch requests during the sample: ' + CAST(ps.value_delta AS NVARCHAR(20)) + @LineFeed + + 'Number of compilations during the sample: ' + CAST(psComp.value_delta AS NVARCHAR(20)) + @LineFeed + + 'For OLTP environments, Microsoft recommends that 90% of batch requests should hit the plan cache, and not be compiled from scratch. We are exceeding that threshold.' + @LineFeed AS Details, + 'To find the queries that are compiling, start with:' + @LineFeed + + 'sp_BlitzCache @SortOrder = ''recent compilations''' + @LineFeed + + 'If dynamic SQL or non-parameterized strings are involved, consider enabling Forced Parameterization. See the URL for more details.' AS HowToStopIt + FROM #PerfmonStats ps + INNER JOIN #PerfmonStats psComp ON psComp.Pass = 2 AND psComp.object_name = @ServiceName + ':SQL Statistics' AND psComp.counter_name = 'SQL Compilations/sec' AND psComp.value_delta > 0 + WHERE ps.Pass = 2 + AND ps.object_name = @ServiceName + ':SQL Statistics' + AND ps.counter_name = 'Batch Requests/sec' + AND psComp.value_delta > 75 /* Because sp_BlitzFirst does around 50 compilations and re-compilations */ + AND (psComp.value_delta > (10 * @Seconds) OR psComp.value_delta > ps.value_delta) /* Either doing 10 compilations per second, or more compilations than queries */ + AND (psComp.value_delta * 10) > ps.value_delta; /* Compilations are more than 10% of batch requests per second */ -DECLARE @PathItem TABLE ( - PathItem NVARCHAR(512) -); + /* Query Problems - Re-Compilations/Sec High - CheckID 16 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 16',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) + SELECT 16 AS CheckID, + 50 AS Priority, + 'Query Problems' AS FindingGroup, + 'Re-Compilations/Sec High' AS Finding, + 'http://www.BrentOzar.com/askbrent/recompilations/' AS URL, + 'Number of batch requests during the sample: ' + CAST(ps.value_delta AS NVARCHAR(20)) + @LineFeed + + 'Number of recompilations during the sample: ' + CAST(psComp.value_delta AS NVARCHAR(20)) + @LineFeed + + 'More than 10% of our queries are being recompiled. This is typically due to statistics changing on objects.' + @LineFeed AS Details, + 'To find the queries that are being forced to recompile, start with:' + @LineFeed + + 'sp_BlitzCache @SortOrder = ''recent compilations''' + @LineFeed + + 'Examine those plans to find out which objects are changing so quickly that they hit the stats update threshold. See the URL for more details.' AS HowToStopIt + FROM #PerfmonStats ps + INNER JOIN #PerfmonStats psComp ON psComp.Pass = 2 AND psComp.object_name = @ServiceName + ':SQL Statistics' AND psComp.counter_name = 'SQL Re-Compilations/sec' AND psComp.value_delta > 0 + WHERE ps.Pass = 2 + AND ps.object_name = @ServiceName + ':SQL Statistics' + AND ps.counter_name = 'Batch Requests/sec' + AND psComp.value_delta > 75 /* Because sp_BlitzFirst does around 50 compilations and re-compilations */ + AND (psComp.value_delta > (10 * @Seconds) OR psComp.value_delta > ps.value_delta) /* Either doing 10 recompilations per second, or more recompilations than queries */ + AND (psComp.value_delta * 10) > ps.value_delta; /* Recompilations are more than 10% of batch requests per second */ -IF OBJECT_ID(N'tempdb..#FileListParameters') IS NOT NULL DROP TABLE #FileListParameters; -CREATE TABLE #FileListParameters -( - LogicalName NVARCHAR(128) NOT NULL, - PhysicalName NVARCHAR(260) NOT NULL, - [Type] CHAR(1) NOT NULL, - FileGroupName NVARCHAR(120) NULL, - Size NUMERIC(20, 0) NOT NULL, - MaxSize NUMERIC(20, 0) NOT NULL, - FileID BIGINT NULL, - CreateLSN NUMERIC(25, 0) NULL, - DropLSN NUMERIC(25, 0) NULL, - UniqueID UNIQUEIDENTIFIER NULL, - ReadOnlyLSN NUMERIC(25, 0) NULL, - ReadWriteLSN NUMERIC(25, 0) NULL, - BackupSizeInBytes BIGINT NULL, - SourceBlockSize INT NULL, - FileGroupID INT NULL, - LogGroupGUID UNIQUEIDENTIFIER NULL, - DifferentialBaseLSN NUMERIC(25, 0) NULL, - DifferentialBaseGUID UNIQUEIDENTIFIER NULL, - IsReadOnly BIT NULL, - IsPresent BIT NULL, - TDEThumbprint VARBINARY(32) NULL, - SnapshotUrl NVARCHAR(360) NULL -); + /* Table Problems - Forwarded Fetches/Sec High - CheckID 29 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 29',10,1) WITH NOWAIT; + END -IF OBJECT_ID(N'tempdb..#Headers') IS NOT NULL DROP TABLE #Headers; -CREATE TABLE #Headers -( - BackupName NVARCHAR(256), - BackupDescription NVARCHAR(256), - BackupType NVARCHAR(256), - ExpirationDate NVARCHAR(256), - Compressed NVARCHAR(256), - Position NVARCHAR(256), - DeviceType NVARCHAR(256), - UserName NVARCHAR(256), - ServerName NVARCHAR(256), - DatabaseName NVARCHAR(256), - DatabaseVersion NVARCHAR(256), - DatabaseCreationDate NVARCHAR(256), - BackupSize NVARCHAR(256), - FirstLSN NVARCHAR(256), - LastLSN NVARCHAR(256), - CheckpointLSN NVARCHAR(256), - DatabaseBackupLSN NVARCHAR(256), - BackupStartDate NVARCHAR(256), - BackupFinishDate NVARCHAR(256), - SortOrder NVARCHAR(256), - [CodePage] NVARCHAR(256), - UnicodeLocaleId NVARCHAR(256), - UnicodeComparisonStyle NVARCHAR(256), - CompatibilityLevel NVARCHAR(256), - SoftwareVendorId NVARCHAR(256), - SoftwareVersionMajor NVARCHAR(256), - SoftwareVersionMinor NVARCHAR(256), - SoftwareVersionBuild NVARCHAR(256), - MachineName NVARCHAR(256), - Flags NVARCHAR(256), - BindingID NVARCHAR(256), - RecoveryForkID NVARCHAR(256), - Collation NVARCHAR(256), - FamilyGUID NVARCHAR(256), - HasBulkLoggedData NVARCHAR(256), - IsSnapshot NVARCHAR(256), - IsReadOnly NVARCHAR(256), - IsSingleUser NVARCHAR(256), - HasBackupChecksums NVARCHAR(256), - IsDamaged NVARCHAR(256), - BeginsLogChain NVARCHAR(256), - HasIncompleteMetaData NVARCHAR(256), - IsForceOffline NVARCHAR(256), - IsCopyOnly NVARCHAR(256), - FirstRecoveryForkID NVARCHAR(256), - ForkPointLSN NVARCHAR(256), - RecoveryModel NVARCHAR(256), - DifferentialBaseLSN NVARCHAR(256), - DifferentialBaseGUID NVARCHAR(256), - BackupTypeDescription NVARCHAR(256), - BackupSetGUID NVARCHAR(256), - CompressedBackupSize NVARCHAR(256), - Containment NVARCHAR(256), - KeyAlgorithm NVARCHAR(32), - EncryptorThumbprint VARBINARY(20), - EncryptorType NVARCHAR(32), - -- - -- Seq added to retain order by - -- - Seq INT NOT NULL IDENTITY(1, 1) -); + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) + SELECT 29 AS CheckID, + 40 AS Priority, + 'Table Problems' AS FindingGroup, + 'Forwarded Fetches/Sec High' AS Finding, + 'https://BrentOzar.com/go/fetch/' AS URL, + CAST(ps.value_delta AS NVARCHAR(20)) + ' forwarded fetches (from SQLServer:Access Methods counter)' + @LineFeed + + 'Check your heaps: they need to be rebuilt, or they need a clustered index applied.' + @LineFeed AS Details, + 'Rebuild your heaps. If you use Ola Hallengren maintenance scripts, those do not rebuild heaps by default: https://www.brentozar.com/archive/2016/07/fix-forwarded-records/' AS HowToStopIt + FROM #PerfmonStats ps + INNER JOIN #PerfmonStats psComp ON psComp.Pass = 2 AND psComp.object_name = @ServiceName + ':Access Methods' AND psComp.counter_name = 'Forwarded Records/sec' AND psComp.value_delta > 100 + WHERE ps.Pass = 2 + AND ps.object_name = @ServiceName + ':Access Methods' + AND ps.counter_name = 'Forwarded Records/sec' + AND ps.value_delta > (100 * @Seconds); /* Ignore servers sitting idle */ -/* -Correct paths in case people forget a final "\" -*/ -/*Full*/ -IF (SELECT RIGHT(@BackupPathFull, 1)) <> '\' AND CHARINDEX('\', @BackupPathFull) > 0 --Has to end in a '\' -BEGIN - IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Fixing @BackupPathFull to add a "\"', 0, 1) WITH NOWAIT; - SET @BackupPathFull += N'\'; -END; -ELSE IF (SELECT RIGHT(@BackupPathFull, 1)) <> '/' AND CHARINDEX('/', @BackupPathFull) > 0 --Has to end in a '/' -BEGIN - IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Fixing @BackupPathFull to add a "/"', 0, 1) WITH NOWAIT; - SET @BackupPathFull += N'/'; -END; -/*Diff*/ -IF (SELECT RIGHT(@BackupPathDiff, 1)) <> '\' AND CHARINDEX('\', @BackupPathDiff) > 0 --Has to end in a '\' -BEGIN - IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Fixing @BackupPathDiff to add a "\"', 0, 1) WITH NOWAIT; - SET @BackupPathDiff += N'\'; -END; -ELSE IF (SELECT RIGHT(@BackupPathDiff, 1)) <> '/' AND CHARINDEX('/', @BackupPathDiff) > 0 --Has to end in a '/' -BEGIN - IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Fixing @BackupPathDiff to add a "/"', 0, 1) WITH NOWAIT; - SET @BackupPathDiff += N'/'; -END; -/*Log*/ -IF (SELECT RIGHT(@BackupPathLog, 1)) <> '\' AND CHARINDEX('\', @BackupPathLog) > 0 --Has to end in a '\' -BEGIN - IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Fixing @BackupPathLog to add a "\"', 0, 1) WITH NOWAIT; - SET @BackupPathLog += N'\'; -END; -ELSE IF (SELECT RIGHT(@BackupPathLog, 1)) <> '/' AND CHARINDEX('/', @BackupPathLog) > 0 --Has to end in a '/' -BEGIN - IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Fixing @BackupPathLog to add a "/"', 0, 1) WITH NOWAIT; - SET @BackupPathLog += N'/'; -END; -/*Move Data File*/ -IF NULLIF(@MoveDataDrive, '') IS NULL -BEGIN - IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Getting default data drive for @MoveDataDrive', 0, 1) WITH NOWAIT; - SET @MoveDataDrive = CAST(SERVERPROPERTY('InstanceDefaultDataPath') AS nvarchar(260)); -END; -IF (SELECT RIGHT(@MoveDataDrive, 1)) <> '\' AND CHARINDEX('\', @MoveDataDrive) > 0 --Has to end in a '\' -BEGIN - IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Fixing @MoveDataDrive to add a "\"', 0, 1) WITH NOWAIT; - SET @MoveDataDrive += N'\'; -END; -ELSE IF (SELECT RIGHT(@MoveDataDrive, 1)) <> '/' AND CHARINDEX('/', @MoveDataDrive) > 0 --Has to end in a '/' -BEGIN - IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Fixing @MoveDataDrive to add a "/"', 0, 1) WITH NOWAIT; - SET @MoveDataDrive += N'/'; -END; -/*Move Log File*/ -IF NULLIF(@MoveLogDrive, '') IS NULL -BEGIN - IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Getting default log drive for @MoveLogDrive', 0, 1) WITH NOWAIT; - SET @MoveLogDrive = CAST(SERVERPROPERTY('InstanceDefaultLogPath') AS nvarchar(260)); -END; -IF (SELECT RIGHT(@MoveLogDrive, 1)) <> '\' AND CHARINDEX('\', @MoveLogDrive) > 0 --Has to end in a '\' -BEGIN - IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Fixing @MoveLogDrive to add a "\"', 0, 1) WITH NOWAIT; - SET @MoveLogDrive += N'\'; -END; -ELSE IF (SELECT RIGHT(@MoveLogDrive, 1)) <> '/' AND CHARINDEX('/', @MoveLogDrive) > 0 --Has to end in a '/' -BEGIN - IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Fixing@MoveLogDrive to add a "/"', 0, 1) WITH NOWAIT; - SET @MoveLogDrive += N'/'; -END; -/*Move Filestream File*/ -IF NULLIF(@MoveFilestreamDrive, '') IS NULL -BEGIN - IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Setting default data drive for @MoveFilestreamDrive', 0, 1) WITH NOWAIT; - SET @MoveFilestreamDrive = CAST(SERVERPROPERTY('InstanceDefaultDataPath') AS nvarchar(260)); -END; -IF (SELECT RIGHT(@MoveFilestreamDrive, 1)) <> '\' AND CHARINDEX('\', @MoveFilestreamDrive) > 0 --Has to end in a '\' -BEGIN - IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Fixing @MoveFilestreamDrive to add a "\"', 0, 1) WITH NOWAIT; - SET @MoveFilestreamDrive += N'\'; -END; -ELSE IF (SELECT RIGHT(@MoveFilestreamDrive, 1)) <> '/' AND CHARINDEX('/', @MoveFilestreamDrive) > 0 --Has to end in a '/' -BEGIN - IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Fixing @MoveFilestreamDrive to add a "/"', 0, 1) WITH NOWAIT; - SET @MoveFilestreamDrive += N'/'; -END; -/*Standby Undo File*/ -IF (SELECT RIGHT(@StandbyUndoPath, 1)) <> '\' AND CHARINDEX('\', @StandbyUndoPath) > 0 --Has to end in a '\' -BEGIN - IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Fixing @StandbyUndoPath to add a "\"', 0, 1) WITH NOWAIT; - SET @StandbyUndoPath += N'\'; -END; -ELSE IF (SELECT RIGHT(@StandbyUndoPath, 1)) <> '/' AND CHARINDEX('/', @StandbyUndoPath) > 0 --Has to end in a '/' -BEGIN - IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Fixing @StandbyUndoPath to add a "/"', 0, 1) WITH NOWAIT; - SET @StandbyUndoPath += N'/'; -END; + /* Check for temp objects with high forwarded fetches. + This has to be done as dynamic SQL because we have to execute OBJECT_NAME inside TempDB. */ + IF @@ROWCOUNT > 0 + BEGIN + SET @StringToExecute = N'USE tempdb; + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) + SELECT TOP 10 29 AS CheckID, + 40 AS Priority, + ''Table Problems'' AS FindingGroup, + ''Forwarded Fetches/Sec High: TempDB Object'' AS Finding, + ''https://BrentOzar.com/go/fetch/'' AS URL, + CAST(COALESCE(os.forwarded_fetch_count,0) - COALESCE(os_prior.forwarded_fetch_count,0) AS NVARCHAR(20)) + '' forwarded fetches on '' + + CASE WHEN OBJECT_NAME(os.object_id) IS NULL THEN ''an unknown table '' + WHEN LEN(OBJECT_NAME(os.object_id)) < 50 THEN ''a table variable, internal identifier '' + OBJECT_NAME(os.object_id) + ELSE ''a temp table '' + OBJECT_NAME(os.object_id) + END AS Details, + ''Look through your source code to find the object creating these objects, and tune the creation and population to reduce fetches. See the URL for details.'' AS HowToStopIt + FROM tempdb.sys.dm_db_index_operational_stats(DB_ID(''tempdb''), NULL, NULL, NULL) os + LEFT OUTER JOIN #TempdbOperationalStats os_prior ON os.object_id = os_prior.object_id + AND os.forwarded_fetch_count > os_prior.forwarded_fetch_count + WHERE os.database_id = DB_ID(''tempdb'') + AND os.forwarded_fetch_count - COALESCE(os_prior.forwarded_fetch_count,0) > 100 + ORDER BY os.forwarded_fetch_count DESC;' -IF @RestoreDatabaseName IS NULL OR @RestoreDatabaseName LIKE N'' /*use LIKE instead of =, otherwise N'' = N' '. See: https://www.brentozar.com/archive/2017/04/surprising-behavior-trailing-spaces/ */ -BEGIN - SET @RestoreDatabaseName = @Database; -END; + EXECUTE sp_executesql @StringToExecute; + END -/*check input parameters*/ -IF NOT @MaxTransferSize IS NULL -BEGIN - IF @MaxTransferSize > 4194304 + /* In-Memory OLTP - Garbage Collection in Progress - CheckID 31 */ + IF (@Debug = 1) BEGIN - RAISERROR('@MaxTransferSize can not be greater then 4194304', 0, 1) WITH NOWAIT; + RAISERROR('Running CheckID 31',10,1) WITH NOWAIT; END - IF @MaxTransferSize % 64 <> 0 + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) + SELECT 31 AS CheckID, + 50 AS Priority, + 'In-Memory OLTP' AS FindingGroup, + 'Garbage Collection in Progress' AS Finding, + 'https://BrentOzar.com/go/garbage/' AS URL, + CAST(ps.value_delta AS NVARCHAR(50)) + ' rows processed (from SQL Server YYYY XTP Garbage Collection:Rows processed/sec counter)' + @LineFeed + + 'This can happen due to memory pressure (causing In-Memory OLTP to shrink its footprint) or' + @LineFeed + + 'due to transactional workloads that constantly insert/delete data.' AS Details, + 'Sadly, you cannot choose when garbage collection occurs. This is one of the many gotchas of Hekaton. Learn more: http://nedotter.com/archive/2016/04/row-version-lifecycle-for-in-memory-oltp/' AS HowToStopIt + FROM #PerfmonStats ps + INNER JOIN #PerfmonStats psComp ON psComp.Pass = 2 AND psComp.object_name LIKE '%XTP Garbage Collection' AND psComp.counter_name = 'Rows processed/sec' AND psComp.value_delta > 100 + WHERE ps.Pass = 2 + AND ps.object_name LIKE '%XTP Garbage Collection' + AND ps.counter_name = 'Rows processed/sec' + AND ps.value_delta > (100 * @Seconds); /* Ignore servers sitting idle */ + + /* In-Memory OLTP - Transactions Aborted - CheckID 32 */ + IF (@Debug = 1) BEGIN - RAISERROR('@MaxTransferSize has to be a multiple of 65536', 0, 1) WITH NOWAIT; + RAISERROR('Running CheckID 32',10,1) WITH NOWAIT; + END + + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) + SELECT 32 AS CheckID, + 100 AS Priority, + 'In-Memory OLTP' AS FindingGroup, + 'Transactions Aborted' AS Finding, + 'https://BrentOzar.com/go/aborted/' AS URL, + CAST(ps.value_delta AS NVARCHAR(50)) + ' transactions aborted (from SQL Server YYYY XTP Transactions:Transactions aborted/sec counter)' + @LineFeed + + 'This may indicate that data is changing, or causing folks to retry their transactions, thereby increasing load.' AS Details, + 'Dig into your In-Memory OLTP transactions to figure out which ones are failing and being retried.' AS HowToStopIt + FROM #PerfmonStats ps + INNER JOIN #PerfmonStats psComp ON psComp.Pass = 2 AND psComp.object_name LIKE '%XTP Transactions' AND psComp.counter_name = 'Transactions aborted/sec' AND psComp.value_delta > 100 + WHERE ps.Pass = 2 + AND ps.object_name LIKE '%XTP Transactions' + AND ps.counter_name = 'Transactions aborted/sec' + AND ps.value_delta > (10 * @Seconds); /* Ignore servers sitting idle */ + + /* Query Problems - Suboptimal Plans/Sec High - CheckID 33 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 33',10,1) WITH NOWAIT; + END + + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) + SELECT 32 AS CheckID, + 100 AS Priority, + 'Query Problems' AS FindingGroup, + 'Suboptimal Plans/Sec High' AS Finding, + 'https://BrentOzar.com/go/suboptimal/' AS URL, + CAST(ps.value_delta AS NVARCHAR(50)) + ' plans reported in the ' + CAST(ps.instance_name AS NVARCHAR(100)) + ' workload group (from Workload GroupStats:Suboptimal plans/sec counter)' + @LineFeed + + 'Even if you are not using Resource Governor, it still tracks information about user queries, memory grants, etc.' AS Details, + 'Check out sp_BlitzCache to get more information about recent queries, or try sp_BlitzWho to see currently running queries.' AS HowToStopIt + FROM #PerfmonStats ps + INNER JOIN #PerfmonStats psComp ON psComp.Pass = 2 AND psComp.object_name = @ServiceName + ':Workload GroupStats' AND psComp.counter_name = 'Suboptimal plans/sec' AND psComp.value_delta > 100 + WHERE ps.Pass = 2 + AND ps.object_name = @ServiceName + ':Workload GroupStats' + AND ps.counter_name = 'Suboptimal plans/sec' + AND ps.value_delta > (10 * @Seconds); /* Ignore servers sitting idle */ + + /* Azure Performance - Database is Maxed Out - CheckID 41 */ + IF SERVERPROPERTY('Edition') = 'SQL Azure' + BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 41',10,1) WITH NOWAIT; + END + + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) + SELECT 41 AS CheckID, + 10 AS Priority, + 'Azure Performance' AS FindingGroup, + 'Database is Maxed Out' AS Finding, + 'https://BrentOzar.com/go/maxedout' AS URL, + N'At ' + CONVERT(NVARCHAR(100), s.end_time ,121) + N', your database approached (or hit) your DTU limits:' + @LineFeed + + N'Average CPU percent: ' + CAST(avg_cpu_percent AS NVARCHAR(50)) + @LineFeed + + N'Average data IO percent: ' + CAST(avg_data_io_percent AS NVARCHAR(50)) + @LineFeed + + N'Average log write percent: ' + CAST(avg_log_write_percent AS NVARCHAR(50)) + @LineFeed + + N'Max worker percent: ' + CAST(max_worker_percent AS NVARCHAR(50)) + @LineFeed + + N'Max session percent: ' + CAST(max_session_percent AS NVARCHAR(50)) AS Details, + 'Tune your queries or indexes with sp_BlitzCache or sp_BlitzIndex, or consider upgrading to a higher DTU level.' AS HowToStopIt + FROM sys.dm_db_resource_stats s + WHERE s.end_time >= DATEADD(MI, -5, GETDATE()) + AND (avg_cpu_percent > 90 + OR avg_data_io_percent >= 90 + OR avg_log_write_percent >=90 + OR max_worker_percent >= 90 + OR max_session_percent >= 90); END -END; -IF NOT @BlockSize IS NULL -BEGIN - IF @BlockSize NOT IN (512, 1024, 2048, 4096, 8192, 16384, 32768, 65536) + /* Server Info - Batch Requests per Sec - CheckID 19 */ + IF (@Debug = 1) BEGIN - RAISERROR('Supported values for @BlockSize are 512, 1024, 2048, 4096, 8192, 16384, 32768, and 65536', 0, 1) WITH NOWAIT; + RAISERROR('Running CheckID 19',10,1) WITH NOWAIT; END -END - -SET @RestoreDatabaseID = DB_ID(@RestoreDatabaseName); -SET @RestoreDatabaseName = QUOTENAME(@RestoreDatabaseName); -SET @UnquotedRestoreDatabaseName = PARSENAME(@RestoreDatabaseName,1); - ---If xp_cmdshell is disabled, force use of xp_dirtree -IF NOT EXISTS (SELECT * FROM sys.configurations WHERE name = 'xp_cmdshell' AND value_in_use = 1) - SET @SimpleFolderEnumeration = 1; - -SET @HeadersSQL = -N'INSERT INTO #Headers WITH (TABLOCK) - (BackupName, BackupDescription, BackupType, ExpirationDate, Compressed, Position, DeviceType, UserName, ServerName - ,DatabaseName, DatabaseVersion, DatabaseCreationDate, BackupSize, FirstLSN, LastLSN, CheckpointLSN, DatabaseBackupLSN - ,BackupStartDate, BackupFinishDate, SortOrder, CodePage, UnicodeLocaleId, UnicodeComparisonStyle, CompatibilityLevel - ,SoftwareVendorId, SoftwareVersionMajor, SoftwareVersionMinor, SoftwareVersionBuild, MachineName, Flags, BindingID - ,RecoveryForkID, Collation, FamilyGUID, HasBulkLoggedData, IsSnapshot, IsReadOnly, IsSingleUser, HasBackupChecksums - ,IsDamaged, BeginsLogChain, HasIncompleteMetaData, IsForceOffline, IsCopyOnly, FirstRecoveryForkID, ForkPointLSN - ,RecoveryModel, DifferentialBaseLSN, DifferentialBaseGUID, BackupTypeDescription, BackupSetGUID, CompressedBackupSize'; - -IF @MajorVersion >= 11 - SET @HeadersSQL += NCHAR(13) + NCHAR(10) + N', Containment'; -IF @MajorVersion >= 13 OR (@MajorVersion = 12 AND @BuildVersion >= 2342) - SET @HeadersSQL += N', KeyAlgorithm, EncryptorThumbprint, EncryptorType'; - -SET @HeadersSQL += N')' + NCHAR(13) + NCHAR(10); -SET @HeadersSQL += N'EXEC (''RESTORE HEADERONLY FROM DISK=''''{Path}'''''')'; + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, DetailsInt) + SELECT 19 AS CheckID, + 250 AS Priority, + 'Server Info' AS FindingGroup, + 'Batch Requests per Sec' AS Finding, + 'http://www.BrentOzar.com/go/measure' AS URL, + CAST(CAST(ps.value_delta AS MONEY) / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS NVARCHAR(20)) AS Details, + ps.value_delta / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS DetailsInt + FROM #PerfmonStats ps + INNER JOIN #PerfmonStats ps1 ON ps.object_name = ps1.object_name AND ps.counter_name = ps1.counter_name AND ps1.Pass = 1 + WHERE ps.Pass = 2 + AND ps.object_name = @ServiceName + ':SQL Statistics' + AND ps.counter_name = 'Batch Requests/sec'; -IF @BackupPathFull IS NOT NULL -BEGIN - DECLARE @CurrentBackupPathFull NVARCHAR(255); - -- Split CSV string logic has taken from Ola Hallengren's :) - WITH BackupPaths ( - StartPosition, EndPosition, PathItem - ) - AS ( - SELECT 1 AS StartPosition, - ISNULL( NULLIF( CHARINDEX( ',', @BackupPathFull, 1 ), 0 ), LEN( @BackupPathFull ) + 1 ) AS EndPosition, - SUBSTRING( @BackupPathFull, 1, ISNULL( NULLIF( CHARINDEX( ',', @BackupPathFull, 1 ), 0 ), LEN( @BackupPathFull ) + 1 ) - 1 ) AS PathItem - WHERE @BackupPathFull IS NOT NULL - UNION ALL - SELECT CAST( EndPosition AS INT ) + 1 AS StartPosition, - ISNULL( NULLIF( CHARINDEX( ',', @BackupPathFull, EndPosition + 1 ), 0 ), LEN( @BackupPathFull ) + 1 ) AS EndPosition, - SUBSTRING( @BackupPathFull, EndPosition + 1, ISNULL( NULLIF( CHARINDEX( ',', @BackupPathFull, EndPosition + 1 ), 0 ), LEN( @BackupPathFull ) + 1 ) - EndPosition - 1 ) AS PathItem - FROM BackupPaths - WHERE EndPosition < LEN( @BackupPathFull ) + 1 - ) - INSERT INTO @PathItem - SELECT CASE RIGHT( PathItem, 1 ) WHEN '\' THEN PathItem ELSE PathItem + '\' END FROM BackupPaths; + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','SQL Compilations/sec', NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','SQL Re-Compilations/sec', NULL); - WHILE 1 = 1 + /* Server Info - SQL Compilations/sec - CheckID 25 */ + IF @ExpertMode = 1 BEGIN - - SELECT TOP 1 @CurrentBackupPathFull = PathItem FROM @PathItem - WHERE PathItem > COALESCE( @CurrentBackupPathFull, '' ) ORDER BY PathItem; - IF @@rowcount = 0 BREAK; - - IF @SimpleFolderEnumeration = 1 - BEGIN -- Get list of files - INSERT INTO @FileListSimple (BackupFile, depth, [file]) EXEC master.sys.xp_dirtree @CurrentBackupPathFull, 1, 1; - INSERT @FileList (BackupPath,BackupFile) SELECT @CurrentBackupPathFull, BackupFile FROM @FileListSimple; - DELETE FROM @FileListSimple; - END - ELSE - BEGIN - SET @cmd = N'DIR /b "' + @CurrentBackupPathFull + N'"'; - IF @Debug = 1 - BEGIN - IF @cmd IS NULL PRINT '@cmd is NULL for @CurrentBackupPathFull'; - PRINT @cmd; - END; - INSERT INTO @FileList (BackupFile) EXEC master.sys.xp_cmdshell @cmd; - UPDATE @FileList SET BackupPath = @CurrentBackupPathFull - WHERE BackupPath IS NULL; - END; - - IF @Debug = 1 - BEGIN - SELECT BackupPath, BackupFile FROM @FileList; - END; - IF @SimpleFolderEnumeration = 1 + IF (@Debug = 1) BEGIN - /*Check what we can*/ - IF NOT EXISTS (SELECT * FROM @FileList) - BEGIN - RAISERROR('(FULL) No rows were returned for that database in path %s', 16, 1, @CurrentBackupPathFull) WITH NOWAIT; - RETURN; - END; + RAISERROR('Running CheckID 25',10,1) WITH NOWAIT; END - ELSE - BEGIN - /*Full Sanity check folders*/ - IF ( - SELECT COUNT(*) - FROM @FileList AS fl - WHERE fl.BackupFile = 'The system cannot find the path specified.' - OR fl.BackupFile = 'File Not Found' - ) = 1 - BEGIN - RAISERROR('(FULL) No rows or bad value for path %s', 16, 1, @CurrentBackupPathFull) WITH NOWAIT; - RETURN; - END; - IF ( - SELECT COUNT(*) - FROM @FileList AS fl - WHERE fl.BackupFile = 'Access is denied.' - ) = 1 - BEGIN - RAISERROR('(FULL) Access is denied to %s', 16, 1, @CurrentBackupPathFull) WITH NOWAIT; - RETURN; - END; - IF ( - SELECT COUNT(*) - FROM @FileList AS fl - ) = 1 - AND - ( - SELECT COUNT(*) - FROM @FileList AS fl - WHERE fl.BackupFile IS NULL - ) = 1 - BEGIN - RAISERROR('(FULL) Empty directory %s', 16, 1, @CurrentBackupPathFull) WITH NOWAIT; - RETURN; - END - IF ( - SELECT COUNT(*) - FROM @FileList AS fl - WHERE fl.BackupFile = 'The user name or password is incorrect.' - ) = 1 - BEGIN - RAISERROR('(FULL) Incorrect user name or password for %s', 16, 1, @CurrentBackupPathFull) WITH NOWAIT; - RETURN; - END; - END; + + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, DetailsInt) + SELECT 25 AS CheckID, + 250 AS Priority, + 'Server Info' AS FindingGroup, + 'SQL Compilations per Sec' AS Finding, + 'http://www.BrentOzar.com/go/measure' AS URL, + CAST(ps.value_delta / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS NVARCHAR(20)) AS Details, + ps.value_delta / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS DetailsInt + FROM #PerfmonStats ps + INNER JOIN #PerfmonStats ps1 ON ps.object_name = ps1.object_name AND ps.counter_name = ps1.counter_name AND ps1.Pass = 1 + WHERE ps.Pass = 2 + AND ps.object_name = @ServiceName + ':SQL Statistics' + AND ps.counter_name = 'SQL Compilations/sec'; END - /*End folder sanity check*/ - IF @StopAt IS NOT NULL + /* Server Info - SQL Re-Compilations/sec - CheckID 26 */ + IF @ExpertMode = 1 BEGIN - DELETE - FROM @FileList - WHERE BackupFile LIKE N'%.bak' - AND - BackupFile LIKE N'%' + @Database + N'%' - AND - (REPLACE( RIGHT( REPLACE( BackupFile, RIGHT( BackupFile, PATINDEX( '%_[0-9][0-9]%', REVERSE( BackupFile ) ) ), '' ), 16 ), '_', '' ) > @StopAt); + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 26',10,1) WITH NOWAIT; + END + + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, DetailsInt) + SELECT 26 AS CheckID, + 250 AS Priority, + 'Server Info' AS FindingGroup, + 'SQL Re-Compilations per Sec' AS Finding, + 'http://www.BrentOzar.com/go/measure' AS URL, + CAST(ps.value_delta / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS NVARCHAR(20)) AS Details, + ps.value_delta / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS DetailsInt + FROM #PerfmonStats ps + INNER JOIN #PerfmonStats ps1 ON ps.object_name = ps1.object_name AND ps.counter_name = ps1.counter_name AND ps1.Pass = 1 + WHERE ps.Pass = 2 + AND ps.object_name = @ServiceName + ':SQL Statistics' + AND ps.counter_name = 'SQL Re-Compilations/sec'; END - - -- Find latest full backup - SELECT @LastFullBackup = MAX(BackupFile) - FROM @FileList - WHERE BackupFile LIKE N'%.bak' - AND - BackupFile LIKE N'%' + @Database + N'%' - AND - (@StopAt IS NULL OR REPLACE( RIGHT( REPLACE( @LastFullBackup, RIGHT( @LastFullBackup, PATINDEX( '%_[0-9][0-9]%', REVERSE( @LastFullBackup ) ) ), '' ), 16 ), '_', '' ) <= @StopAt); - /* To get all backups that belong to the same set we can do two things: - 1. RESTORE HEADERONLY of ALL backup files in the folder and look for BackupSetGUID. - Backups that belong to the same split will have the same BackupSetGUID. - 2. Olla Hallengren's solution appends file index at the end of the name: - SQLSERVER1_TEST_DB_FULL_20180703_213211_1.bak - SQLSERVER1_TEST_DB_FULL_20180703_213211_2.bak - SQLSERVER1_TEST_DB_FULL_20180703_213211_N.bak - We can and find all related files with the same timestamp but different index. - This option is simpler and requires less changes to this procedure */ + /* Server Info - Wait Time per Core per Sec - CheckID 20 */ + IF @Seconds > 0 + BEGIN; + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 20',10,1) WITH NOWAIT; + END; - IF @LastFullBackup IS NULL - BEGIN - RAISERROR('No backups for "%s" found in "%s"', 16, 1, @Database, @BackupPathFull) WITH NOWAIT; - RETURN; + WITH waits1(SampleTime, waits_ms) AS (SELECT SampleTime, SUM(ws1.wait_time_ms) FROM #WaitStats ws1 WHERE ws1.Pass = 1 GROUP BY SampleTime), + waits2(SampleTime, waits_ms) AS (SELECT SampleTime, SUM(ws2.wait_time_ms) FROM #WaitStats ws2 WHERE ws2.Pass = 2 GROUP BY SampleTime), + cores(cpu_count) AS (SELECT SUM(1) FROM sys.dm_os_schedulers WHERE status = 'VISIBLE ONLINE' AND is_online = 1) + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, DetailsInt) + SELECT 20 AS CheckID, + 250 AS Priority, + 'Server Info' AS FindingGroup, + 'Wait Time per Core per Sec' AS Finding, + 'http://www.BrentOzar.com/go/measure' AS URL, + CAST((CAST(waits2.waits_ms - waits1.waits_ms AS MONEY)) / 1000 / i.cpu_count / DATEDIFF(ss, waits1.SampleTime, waits2.SampleTime) AS NVARCHAR(20)) AS Details, + (waits2.waits_ms - waits1.waits_ms) / 1000 / i.cpu_count / DATEDIFF(ss, waits1.SampleTime, waits2.SampleTime) AS DetailsInt + FROM cores i + CROSS JOIN waits1 + CROSS JOIN waits2; END; - SELECT BackupPath, BackupFile INTO #SplitFullBackups - FROM @FileList - WHERE LEFT( BackupFile, LEN( BackupFile ) - PATINDEX( '%[_]%', REVERSE( BackupFile ) ) ) = LEFT( @LastFullBackup, LEN( @LastFullBackup ) - PATINDEX( '%[_]%', REVERSE( @LastFullBackup ) ) ) - AND PATINDEX( '%[_]%', REVERSE( @LastFullBackup ) ) <= 7 -- there is a 1 or 2 digit index at the end of the string which indicates split backups. Ola only supports up to 64 file split. - ORDER BY REPLACE( RIGHT( REPLACE( BackupFile, RIGHT( BackupFile, PATINDEX( '%_[0-9][0-9]%', REVERSE( BackupFile ) ) ), '' ), 16 ), '_', '' ) DESC; - - -- File list can be obtained by running RESTORE FILELISTONLY of any file from the given BackupSet therefore we do not have to cater for split backups when building @FileListParamSQL - - SET @FileListParamSQL = - N'INSERT INTO #FileListParameters WITH (TABLOCK) - (LogicalName, PhysicalName, Type, FileGroupName, Size, MaxSize, FileID, CreateLSN, DropLSN - ,UniqueID, ReadOnlyLSN, ReadWriteLSN, BackupSizeInBytes, SourceBlockSize, FileGroupID, LogGroupGUID - ,DifferentialBaseLSN, DifferentialBaseGUID, IsReadOnly, IsPresent, TDEThumbprint'; + IF @Seconds > 0 + BEGIN + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) + SELECT + 47 AS CheckId, + 50 AS Priority, + 'Query Problems' AS FindingsGroup, + 'High Percentage Of Runnable Queries' AS Finding, + 'https://erikdarlingdata.com/go/RunnableQueue/' AS URL, + 'On the ' + + CASE WHEN y.pass = 1 + THEN '1st' + ELSE '2nd' + END + + ' pass, ' + + RTRIM(y.runnable_pct) + + '% of your queries were waiting to get on a CPU to run. ' + + ' This can indicate CPU pressure.' + FROM + ( + SELECT + 2 AS pass, + x.total, + x.runnable, + CONVERT(decimal(5,2), + ( + x.runnable / + (1. * NULLIF(x.total, 0)) + ) + ) * 100. AS runnable_pct + FROM + ( + SELECT + COUNT_BIG(*) AS total, + SUM(CASE WHEN status = 'runnable' + THEN 1 + ELSE 0 + END) AS runnable + FROM sys.dm_exec_requests + WHERE session_id > 50 + ) AS x + ) AS y + WHERE y.runnable_pct > 20.; + END - IF @MajorVersion >= 13 + /* If we're waiting 30+ seconds, run these checks at the end. + We get this data from the ring buffers, and it's only updated once per minute, so might + as well get it now - whereas if we're checking 30+ seconds, it might get updated by the + end of our sp_BlitzFirst session. */ + IF @Seconds >= 30 BEGIN - SET @FileListParamSQL += N', SnapshotUrl'; - END; - - SET @FileListParamSQL += N')' + NCHAR(13) + NCHAR(10); - SET @FileListParamSQL += N'EXEC (''RESTORE FILELISTONLY FROM DISK=''''{Path}'''''')'; + /* Server Performance - High CPU Utilization CheckID 24 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 24',10,1) WITH NOWAIT; + END - -- get the TOP record to use in "Restore HeaderOnly/FileListOnly" statement as well as Non-Split Backups Restore Command - SELECT TOP 1 @CurrentBackupPathFull = BackupPath, @LastFullBackup = BackupFile - FROM @FileList - ORDER BY REPLACE( RIGHT( REPLACE( BackupFile, RIGHT( BackupFile, PATINDEX( '%_[0-9][0-9]%', REVERSE( BackupFile ) ) ), '' ), 16 ), '_', '' ) DESC; + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) + SELECT 24, 50, 'Server Performance', 'High CPU Utilization', CAST(100 - SystemIdle AS NVARCHAR(20)) + N'%. Ring buffer details: ' + CAST(record AS NVARCHAR(4000)), 100 - SystemIdle, 'http://www.BrentOzar.com/go/cpu' + FROM ( + SELECT record, + record.value('(./Record/SchedulerMonitorEvent/SystemHealth/SystemIdle)[1]', 'int') AS SystemIdle + FROM ( + SELECT TOP 1 CONVERT(XML, record) AS record + FROM sys.dm_os_ring_buffers + WHERE ring_buffer_type = N'RING_BUFFER_SCHEDULER_MONITOR' + AND record LIKE '%%' + ORDER BY timestamp DESC) AS rb + ) AS y + WHERE 100 - SystemIdle >= 50; - SET @sql = REPLACE(@FileListParamSQL, N'{Path}', @CurrentBackupPathFull + @LastFullBackup); + /* Server Performance - CPU Utilization CheckID 23 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 23',10,1) WITH NOWAIT; + END - IF @Debug = 1 - BEGIN - IF @sql IS NULL PRINT '@sql is NULL for INSERT to #FileListParameters: @BackupPathFull + @LastFullBackup'; - PRINT @sql; - END; + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) + SELECT 23, 250, 'Server Info', 'CPU Utilization', CAST(100 - SystemIdle AS NVARCHAR(20)) + N'%. Ring buffer details: ' + CAST(record AS NVARCHAR(4000)), 100 - SystemIdle, 'http://www.BrentOzar.com/go/cpu' + FROM ( + SELECT record, + record.value('(./Record/SchedulerMonitorEvent/SystemHealth/SystemIdle)[1]', 'int') AS SystemIdle + FROM ( + SELECT TOP 1 CONVERT(XML, record) AS record + FROM sys.dm_os_ring_buffers + WHERE ring_buffer_type = N'RING_BUFFER_SCHEDULER_MONITOR' + AND record LIKE '%%' + ORDER BY timestamp DESC) AS rb + ) AS y; - EXEC (@sql); - IF @Debug = 1 - BEGIN - SELECT '#FileListParameters' AS table_name, * FROM #FileListParameters; - SELECT '@FileList' AS table_name, BackupPath, BackupFile FROM @FileList WHERE BackupFile IS NOT NULL; - END + END; /* IF @Seconds >= 30 */ - --get the backup completed data so we can apply tlogs from that point forwards - SET @sql = REPLACE(@HeadersSQL, N'{Path}', @CurrentBackupPathFull + @LastFullBackup); - IF @Debug = 1 - BEGIN - IF @sql IS NULL PRINT '@sql is NULL for get backup completed data: @BackupPathFull, @LastFullBackup'; - PRINT @sql; - END; - EXECUTE (@sql); - IF @Debug = 1 + /* If we didn't find anything, apologize. */ + IF NOT EXISTS (SELECT * FROM #BlitzFirstResults WHERE Priority < 250) BEGIN - SELECT '#Headers' AS table_name, @LastFullBackup AS FullBackupFile, * FROM #Headers - END; - --Ensure we are looking at the expected backup, but only if we expect to restore a FULL backups - IF NOT EXISTS (SELECT * FROM #Headers h WHERE h.DatabaseName = @Database) - BEGIN - RAISERROR('Backupfile "%s" does not match @Database parameter "%s"', 16, 1, @LastFullBackup, @Database) WITH NOWAIT; - RETURN; - END; + INSERT INTO #BlitzFirstResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + VALUES ( -1 , + 1 , + 'No Problems Found' , + 'From Your Community Volunteers' , + 'http://FirstResponderKit.org/' , + 'Try running our more in-depth checks with sp_Blitz, or there may not be an unusual SQL Server performance problem. ' + ); - IF NOT @BufferCount IS NULL - BEGIN - SET @BackupParameters += N', BufferCount=' + cast(@BufferCount as NVARCHAR(10)) - END + END; /*IF NOT EXISTS (SELECT * FROM #BlitzFirstResults) */ - IF NOT @MaxTransferSize IS NULL - BEGIN - SET @BackupParameters += N', MaxTransferSize=' + cast(@MaxTransferSize as NVARCHAR(7)) - END + /* Add credits for the nice folks who put so much time into building and maintaining this for free: */ + INSERT INTO #BlitzFirstResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + VALUES ( -1 , + 255 , + 'Thanks!' , + 'From Your Community Volunteers' , + 'http://FirstResponderKit.org/' , + 'To get help or add your own contributions, join us at http://FirstResponderKit.org.' + ); - IF NOT @BlockSize IS NULL - BEGIN - SET @BackupParameters += N', BlockSize=' + cast(@BlockSize as NVARCHAR(5)) - END + INSERT INTO #BlitzFirstResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details - IF @MoveFiles = 1 - BEGIN - IF @Execute = 'Y' RAISERROR('@MoveFiles = 1, adjusting paths', 0, 1) WITH NOWAIT; + ) + VALUES ( -1 , + 0 , + 'sp_BlitzFirst ' + CAST(CONVERT(DATETIMEOFFSET, @VersionDate, 102) AS VARCHAR(100)), + 'From Your Community Volunteers' , + 'http://FirstResponderKit.org/' , + 'We hope you found this tool useful.' + ); - WITH Files - AS ( - SELECT - CASE - WHEN Type = 'D' THEN @MoveDataDrive - WHEN Type = 'L' THEN @MoveLogDrive - WHEN Type = 'S' THEN @MoveFilestreamDrive - END + CASE - WHEN @Database = @RestoreDatabaseName THEN REVERSE(LEFT(REVERSE(PhysicalName), CHARINDEX('\', REVERSE(PhysicalName), 1) -1)) - ELSE REPLACE(REVERSE(LEFT(REVERSE(PhysicalName), CHARINDEX('\', REVERSE(PhysicalName), 1) -1)), @Database, SUBSTRING(@RestoreDatabaseName, 2, LEN(@RestoreDatabaseName) -2)) - END AS TargetPhysicalName, - PhysicalName, - LogicalName - FROM #FileListParameters) - SELECT @MoveOption = @MoveOption + N', MOVE ''' + Files.LogicalName + N''' TO ''' + Files.TargetPhysicalName + '''' - FROM Files - WHERE Files.TargetPhysicalName <> Files.PhysicalName; - - IF @Debug = 1 PRINT @MoveOption - END; + /* Outdated sp_BlitzFirst - sp_BlitzFirst is Over 6 Months Old - CheckID 27 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 27',10,1) WITH NOWAIT; + END - /*Process @ExistingDBAction flag */ - IF @ExistingDBAction BETWEEN 1 AND 4 - BEGIN - IF @RestoreDatabaseID IS NOT NULL - BEGIN - IF @ExistingDBAction = 1 - BEGIN - RAISERROR('Setting single user', 0, 1) WITH NOWAIT; - SET @sql = N'ALTER DATABASE ' + @RestoreDatabaseName + ' SET SINGLE_USER WITH ROLLBACK IMMEDIATE; ' + NCHAR(13); - IF @Debug = 1 OR @Execute = 'N' - BEGIN - IF @sql IS NULL PRINT '@sql is NULL for SINGLE_USER'; - PRINT @sql; - END; - IF @Debug IN (0, 1) AND @Execute = 'Y' AND DATABASEPROPERTYEX(@RestoreDatabaseName,'STATUS') != 'RESTORING' - EXECUTE @sql = [dbo].[CommandExecute] @DatabaseContext=N'master', @Command = @sql, @CommandType = 'ALTER DATABASE SINGLE_USER', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y'; - END - IF @ExistingDBAction IN (2, 3) - BEGIN - RAISERROR('Killing connections', 0, 1) WITH NOWAIT; - SET @sql = N'/* Kill connections */' + NCHAR(13); - SELECT - @sql = @sql + N'KILL ' + CAST(spid as nvarchar(5)) + N';' + NCHAR(13) - FROM - --database_ID was only added to sys.dm_exec_sessions in SQL Server 2012 but we need to support older - sys.sysprocesses - WHERE - dbid = @RestoreDatabaseID; - IF @Debug = 1 OR @Execute = 'N' - BEGIN - IF @sql IS NULL PRINT '@sql is NULL for Kill connections'; - PRINT @sql; - END; - IF @Debug IN (0, 1) AND @Execute = 'Y' - EXECUTE @sql = [dbo].[CommandExecute] @DatabaseContext=N'master', @Command = @sql, @CommandType = 'KILL CONNECTIONS', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y'; - END - IF @ExistingDBAction = 3 - BEGIN - RAISERROR('Dropping database', 0, 1) WITH NOWAIT; - - SET @sql = N'DROP DATABASE ' + @RestoreDatabaseName + NCHAR(13); - IF @Debug = 1 OR @Execute = 'N' - BEGIN - IF @sql IS NULL PRINT '@sql is NULL for DROP DATABASE'; - PRINT @sql; - END; - IF @Debug IN (0, 1) AND @Execute = 'Y' - EXECUTE @sql = [dbo].[CommandExecute] @DatabaseContext=N'master', @Command = @sql, @CommandType = 'DROP DATABASE', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y'; - END - IF @ExistingDBAction = 4 - BEGIN - RAISERROR ('Offlining database', 0, 1) WITH NOWAIT; + IF DATEDIFF(MM, @VersionDate, SYSDATETIMEOFFSET()) > 6 + BEGIN + INSERT INTO #BlitzFirstResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT 27 AS CheckID , + 0 AS Priority , + 'Outdated sp_BlitzFirst' AS FindingsGroup , + 'sp_BlitzFirst is Over 6 Months Old' AS Finding , + 'http://FirstResponderKit.org/' AS URL , + 'Some things get better with age, like fine wine and your T-SQL. However, sp_BlitzFirst is not one of those things - time to go download the current one.' AS Details; + END; - SET @sql = N'ALTER DATABASE ' + @RestoreDatabaseName + SPACE( 1 ) + 'SET OFFLINE WITH ROLLBACK IMMEDIATE'; - IF @Debug = 1 OR @Execute = 'N' - BEGIN - IF @sql IS NULL PRINT '@sql is NULL for Offline database'; - PRINT @sql; - END; - IF @Debug IN (0, 1) AND @Execute = 'Y' AND DATABASEPROPERTYEX(@RestoreDatabaseName,'STATUS') != 'RESTORING' - EXECUTE @sql = [dbo].[CommandExecute] @DatabaseContext=N'master', @Command = @sql, @CommandType = 'OFFLINE DATABASE', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y'; - END; + IF @CheckServerInfo = 0 /* Github #1680 */ + BEGIN + DELETE #BlitzFirstResults + WHERE FindingsGroup = 'Server Info'; END - ELSE - RAISERROR('@ExistingDBAction > 0, but no existing @RestoreDatabaseName', 0, 1) WITH NOWAIT; - END - ELSE - IF @Execute = 'Y' OR @Debug = 1 RAISERROR('@ExistingDBAction %u so do nothing', 0, 1, @ExistingDBAction) WITH NOWAIT; - IF @ContinueLogs = 0 + RAISERROR('Analysis finished, outputting results',10,1) WITH NOWAIT; + + + /* If they want to run sp_BlitzCache and export to table, go for it. */ + IF @OutputTableNameBlitzCache IS NOT NULL + AND @OutputDatabaseName IS NOT NULL + AND @OutputSchemaName IS NOT NULL + AND EXISTS ( SELECT * + FROM sys.databases + WHERE QUOTENAME([name]) = @OutputDatabaseName) BEGIN - IF @Execute = 'Y' RAISERROR('@ContinueLogs set to 0', 0, 1) WITH NOWAIT; - - /* now take split backups into account */ - IF (SELECT COUNT(*) FROM #SplitFullBackups) > 0 - BEGIN - RAISERROR('Split backups found', 0, 1) WITH NOWAIT; - SET @sql = N'RESTORE DATABASE ' + @RestoreDatabaseName + N' FROM ' - + STUFF( - (SELECT CHAR( 10 ) + ',DISK=''' + BackupPath + BackupFile + '''' - FROM #SplitFullBackups - ORDER BY BackupFile - FOR XML PATH ('')), - 1, - 2, - '') + N' WITH NORECOVERY, REPLACE' + @BackupParameters + @MoveOption + NCHAR(13) + NCHAR(10); - END; - ELSE - BEGIN - SET @sql = N'RESTORE DATABASE ' + @RestoreDatabaseName + N' FROM DISK = ''' + @CurrentBackupPathFull + @LastFullBackup + N''' WITH NORECOVERY, REPLACE' + @BackupParameters + @MoveOption + NCHAR(13) + NCHAR(10); - END - IF (@StandbyMode = 1) - BEGIN - IF (@StandbyUndoPath IS NULL) - BEGIN - IF @Execute = 'Y' OR @Debug = 1 RAISERROR('The file path of the undo file for standby mode was not specified. The database will not be restored in standby mode.', 0, 1) WITH NOWAIT; - END - ELSE IF (SELECT COUNT(*) FROM #SplitFullBackups) > 0 - BEGIN - SET @sql = @sql + ', STANDBY = ''' + @StandbyUndoPath + @Database + 'Undo.ldf''' + NCHAR(13) + NCHAR(10); - END - ELSE - BEGIN - SET @sql = N'RESTORE DATABASE ' + @RestoreDatabaseName + N' FROM DISK = ''' + @CurrentBackupPathFull + @LastFullBackup + N''' WITH REPLACE' + @BackupParameters + @MoveOption + N' , STANDBY = ''' + @StandbyUndoPath + @Database + 'Undo.ldf''' + NCHAR(13) + NCHAR(10); - END - END; - IF @Debug = 1 OR @Execute = 'N' - BEGIN - IF @sql IS NULL PRINT '@sql is NULL for RESTORE DATABASE: @BackupPathFull, @LastFullBackup, @MoveOption'; - PRINT @sql; - END; - - IF @Debug IN (0, 1) AND @Execute = 'Y' - EXECUTE @sql = [dbo].[CommandExecute] @DatabaseContext=N'master', @Command = @sql, @CommandType = 'RESTORE DATABASE', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y'; - -- We already loaded #Headers above + RAISERROR('Calling sp_BlitzCache',10,1) WITH NOWAIT; - --setting the @BackupDateTime to a numeric string so that it can be used in comparisons - SET @BackupDateTime = REPLACE( RIGHT( REPLACE( @LastFullBackup, RIGHT( @LastFullBackup, PATINDEX( '%_[0-9][0-9]%', REVERSE( @LastFullBackup ) ) ), '' ), 16 ), '_', '' ); - - SELECT @FullLastLSN = CAST(LastLSN AS NUMERIC(25, 0)) FROM #Headers WHERE BackupType = 1; - IF @Debug = 1 - BEGIN - IF @BackupDateTime IS NULL PRINT '@BackupDateTime is NULL for REPLACE: @LastFullBackup'; - PRINT @BackupDateTime; - END; - - END; - ELSE - BEGIN - - SELECT @DatabaseLastLSN = CAST(f.redo_start_lsn AS NUMERIC(25, 0)) - FROM master.sys.databases d - JOIN master.sys.master_files f ON d.database_id = f.database_id - WHERE d.name = SUBSTRING(@RestoreDatabaseName, 2, LEN(@RestoreDatabaseName) - 2) AND f.file_id = 1; - - END; -END; -IF @BackupPathFull IS NULL AND @ContinueLogs = 1 -BEGIN - - SELECT @DatabaseLastLSN = CAST(f.redo_start_lsn AS NUMERIC(25, 0)) - FROM master.sys.databases d - JOIN master.sys.master_files f ON d.database_id = f.database_id - WHERE d.name = SUBSTRING(@RestoreDatabaseName, 2, LEN(@RestoreDatabaseName) - 2) AND f.file_id = 1; - -END; + /* If they have an newer version of sp_BlitzCache that supports @MinutesBack and @CheckDateOverride */ + IF EXISTS (SELECT * FROM sys.objects o + INNER JOIN sys.parameters pMB ON o.object_id = pMB.object_id AND pMB.name = '@MinutesBack' + INNER JOIN sys.parameters pCDO ON o.object_id = pCDO.object_id AND pCDO.name = '@CheckDateOverride' + WHERE o.name = 'sp_BlitzCache') + BEGIN + /* Get the most recent sp_BlitzCache execution before this one - don't use sp_BlitzFirst because user logs are added in there at any time */ + SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' + + @OutputDatabaseName + + '.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''' + + @OutputSchemaName + ''' AND QUOTENAME(TABLE_NAME) = ''' + + QUOTENAME(@OutputTableNameBlitzCache) + ''') SELECT TOP 1 @BlitzCacheMinutesBack = DATEDIFF(MI,CheckDate,SYSDATETIMEOFFSET()) FROM ' + + @OutputDatabaseName + '.' + + @OutputSchemaName + '.' + + QUOTENAME(@OutputTableNameBlitzCache) + + ' WHERE ServerName = ''' + CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128)) + ''' ORDER BY CheckDate DESC;'; + EXEC sp_executesql @StringToExecute, N'@BlitzCacheMinutesBack INT OUTPUT', @BlitzCacheMinutesBack OUTPUT; -IF @BackupPathDiff IS NOT NULL -BEGIN - DELETE FROM @FileList; - DELETE FROM @FileListSimple; - DELETE FROM @PathItem; + /* If there's no data, let's just analyze the last 15 minutes of the plan cache */ + IF @BlitzCacheMinutesBack IS NULL OR @BlitzCacheMinutesBack < 1 OR @BlitzCacheMinutesBack > 60 + SET @BlitzCacheMinutesBack = 15; - DECLARE @CurrentBackupPathDiff NVARCHAR(512); - -- Split CSV string logic has taken from Ola Hallengren's :) - WITH BackupPaths ( - StartPosition, EndPosition, PathItem - ) - AS ( - SELECT 1 AS StartPosition, - ISNULL( NULLIF( CHARINDEX( ',', @BackupPathDiff, 1 ), 0 ), LEN( @BackupPathDiff ) + 1 ) AS EndPosition, - SUBSTRING( @BackupPathDiff, 1, ISNULL( NULLIF( CHARINDEX( ',', @BackupPathDiff, 1 ), 0 ), LEN( @BackupPathDiff ) + 1 ) - 1 ) AS PathItem - WHERE @BackupPathDiff IS NOT NULL - UNION ALL - SELECT CAST( EndPosition AS INT ) + 1 AS StartPosition, - ISNULL( NULLIF( CHARINDEX( ',', @BackupPathDiff, EndPosition + 1 ), 0 ), LEN( @BackupPathDiff ) + 1 ) AS EndPosition, - SUBSTRING( @BackupPathDiff, EndPosition + 1, ISNULL( NULLIF( CHARINDEX( ',', @BackupPathDiff, EndPosition + 1 ), 0 ), LEN( @BackupPathDiff ) + 1 ) - EndPosition - 1 ) AS PathItem - FROM BackupPaths - WHERE EndPosition < LEN( @BackupPathDiff ) + 1 - ) - INSERT INTO @PathItem - SELECT CASE RIGHT( PathItem, 1 ) WHEN '\' THEN PathItem ELSE PathItem + '\' END FROM BackupPaths; + EXEC sp_BlitzCache + @OutputDatabaseName = @UnquotedOutputDatabaseName, + @OutputSchemaName = @UnquotedOutputSchemaName, + @OutputTableName = @OutputTableNameBlitzCache, + @CheckDateOverride = @StartSampleTime, + @SortOrder = 'all', + @SkipAnalysis = @BlitzCacheSkipAnalysis, + @MinutesBack = @BlitzCacheMinutesBack, + @Debug = @Debug; - WHILE 1 = 1 - BEGIN + /* Delete history older than @OutputTableRetentionDays */ + SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' + + @OutputDatabaseName + + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' + + @OutputSchemaName + ''') DELETE ' + + @OutputDatabaseName + '.' + + @OutputSchemaName + '.' + + QUOTENAME(@OutputTableNameBlitzCache) + + ' WHERE ServerName = @SrvName AND CheckDate < @CheckDate;'; + EXEC sp_executesql @StringToExecute, + N'@SrvName NVARCHAR(128), @CheckDate date', + @LocalServerName, @OutputTableCleanupDate; - SELECT TOP 1 @CurrentBackupPathDiff = PathItem FROM @PathItem - WHERE PathItem > COALESCE( @CurrentBackupPathDiff, '' ) ORDER BY PathItem; - IF @@rowcount = 0 BREAK; - IF @SimpleFolderEnumeration = 1 - BEGIN -- Get list of files - INSERT INTO @FileListSimple (BackupFile, depth, [file]) EXEC master.sys.xp_dirtree @CurrentBackupPathDiff, 1, 1; - INSERT @FileList (BackupPath,BackupFile) SELECT @CurrentBackupPathDiff, BackupFile FROM @FileListSimple; - DELETE FROM @FileListSimple; - END - ELSE - BEGIN - SET @cmd = N'DIR /b "' + @CurrentBackupPathDiff + N'"'; - IF @Debug = 1 - BEGIN - IF @cmd IS NULL PRINT '@cmd is NULL for @CurrentBackupPathDiff'; - PRINT @cmd; - END; - INSERT INTO @FileList (BackupFile) EXEC master.sys.xp_cmdshell @cmd; - UPDATE @FileList SET BackupPath = @CurrentBackupPathDiff WHERE BackupPath IS NULL; - END; - - IF @Debug = 1 - BEGIN - SELECT BackupPath,BackupFile FROM @FileList WHERE BackupFile IS NOT NULL; - END; - IF @SimpleFolderEnumeration = 0 - BEGIN - /*Full Sanity check folders*/ - IF ( - SELECT COUNT(*) - FROM @FileList AS fl - WHERE fl.BackupFile = 'The system cannot find the path specified.' - ) = 1 - BEGIN - RAISERROR('(DIFF) Bad value for path %s', 16, 1, @CurrentBackupPathDiff) WITH NOWAIT; - RETURN; - END; - IF ( - SELECT COUNT(*) - FROM @FileList AS fl - WHERE fl.BackupFile = 'Access is denied.' - ) = 1 - BEGIN - RAISERROR('(DIFF) Access is denied to %s', 16, 1, @CurrentBackupPathDiff) WITH NOWAIT; - RETURN; - END; - IF ( - SELECT COUNT(*) - FROM @FileList AS fl - WHERE fl.BackupFile = 'The user name or password is incorrect.' - ) = 1 - BEGIN - RAISERROR('(DIFF) Incorrect user name or password for %s', 16, 1, @CurrentBackupPathDiff) WITH NOWAIT; - RETURN; - END; - END; - END - /*End folder sanity check*/ - -- Find latest diff backup - SELECT @LastDiffBackup = MAX(BackupFile) - FROM @FileList - WHERE BackupFile LIKE N'%.bak' - AND - BackupFile LIKE N'%' + @Database + '%' - AND - (@StopAt IS NULL OR REPLACE( RIGHT( REPLACE( @LastDiffBackup, RIGHT( @LastDiffBackup, PATINDEX( '%_[0-9][0-9]%', REVERSE( @LastDiffBackup ) ) ), '' ), 16 ), '_', '' ) <= @StopAt); + END; - -- Load FileList data into Temp Table sorted by DateTime Stamp desc - SELECT BackupPath, BackupFile INTO #SplitDiffBackups - FROM @FileList - WHERE LEFT( BackupFile, LEN( BackupFile ) - PATINDEX( '%[_]%', REVERSE( BackupFile ) ) ) = LEFT( @LastDiffBackup, LEN( @LastDiffBackup ) - PATINDEX( '%[_]%', REVERSE( @LastDiffBackup ) ) ) - AND PATINDEX( '%[_]%', REVERSE( @LastDiffBackup ) ) <= 7 -- there is a 1 or 2 digit index at the end of the string which indicates split backups. Olla only supports up to 64 file split. - ORDER BY REPLACE( RIGHT( REPLACE( BackupFile, RIGHT( BackupFile, PATINDEX( '%_[0-9][0-9]%', REVERSE( BackupFile ) ) ), '' ), 16 ), '_', '' ) DESC; + ELSE + BEGIN + /* No sp_BlitzCache found, or it's outdated - CheckID 36 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 36',10,1) WITH NOWAIT; + END - --No file = no backup to restore - SET @LastDiffBackupDateTime = REPLACE( RIGHT( REPLACE( @LastDiffBackup, RIGHT( @LastDiffBackup, PATINDEX( '%_[0-9][0-9]%', REVERSE( @LastDiffBackup ) ) ), '' ), 16 ), '_', '' ); + INSERT INTO #BlitzFirstResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT 36 AS CheckID , + 0 AS Priority , + 'Outdated or Missing sp_BlitzCache' AS FindingsGroup , + 'Update Your sp_BlitzCache' AS Finding , + 'http://FirstResponderKit.org/' AS URL , + 'You passed in @OutputTableNameBlitzCache, but we need a newer version of sp_BlitzCache in master or the current database.' AS Details; + END; - -- Get the TOP record to use in "Restore HeaderOnly/FileListOnly" statement as well as non-split backups - SELECT TOP 1 @CurrentBackupPathDiff = BackupPath, @LastDiffBackup = BackupFile FROM @FileList - ORDER BY REPLACE( RIGHT( REPLACE( BackupFile, RIGHT( BackupFile, PATINDEX( '%_[0-9][0-9]%', REVERSE( BackupFile ) ) ), '' ), 16 ), '_', '' ) DESC; + RAISERROR('sp_BlitzCache Finished',10,1) WITH NOWAIT; - IF @RestoreDiff = 1 AND @BackupDateTime < @LastDiffBackupDateTime - BEGIN + END; /* End running sp_BlitzCache */ - IF (SELECT COUNT(*) FROM #SplitDiffBackups) > 0 - BEGIN - RAISERROR ('Split backups found', 0, 1) WITH NOWAIT; - SET @sql = N'RESTORE DATABASE ' + @RestoreDatabaseName + N' FROM ' - + STUFF( - (SELECT CHAR( 10 ) + ',DISK=''' + BackupPath + BackupFile + '''' - FROM #SplitDiffBackups - ORDER BY BackupFile - FOR XML PATH ('')), - 1, - 2, - '' ) + N' WITH NORECOVERY, REPLACE' + @BackupParameters + @MoveOption + NCHAR(13) + NCHAR(10); - END; - ELSE - SET @sql = N'RESTORE DATABASE ' + @RestoreDatabaseName + N' FROM DISK = ''' + @CurrentBackupPathDiff + @LastDiffBackup + N''' WITH NORECOVERY' + @BackupParameters + @MoveOption + NCHAR(13) + NCHAR(10); + /* @OutputTableName lets us export the results to a permanent table */ + IF @OutputDatabaseName IS NOT NULL + AND @OutputSchemaName IS NOT NULL + AND @OutputTableName IS NOT NULL + AND @OutputTableName NOT LIKE '#%' + AND EXISTS ( SELECT * + FROM sys.databases + WHERE QUOTENAME([name]) = @OutputDatabaseName) + BEGIN + SET @StringToExecute = 'USE ' + + @OutputDatabaseName + + '; IF EXISTS(SELECT * FROM ' + + @OutputDatabaseName + + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' + + @OutputSchemaName + + ''') AND NOT EXISTS (SELECT * FROM ' + + @OutputDatabaseName + + '.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''' + + @OutputSchemaName + ''' AND QUOTENAME(TABLE_NAME) = ''' + + @OutputTableName + ''') CREATE TABLE ' + + @OutputSchemaName + '.' + + @OutputTableName + + ' (ID INT IDENTITY(1,1) NOT NULL, + ServerName NVARCHAR(128), + CheckDate DATETIMEOFFSET, + CheckID INT NOT NULL, + Priority TINYINT NOT NULL, + FindingsGroup VARCHAR(50) NOT NULL, + Finding VARCHAR(200) NOT NULL, + URL VARCHAR(200) NOT NULL, + Details NVARCHAR(4000) NULL, + HowToStopIt [XML] NULL, + QueryPlan [XML] NULL, + QueryText NVARCHAR(MAX) NULL, + StartTime DATETIMEOFFSET NULL, + LoginName NVARCHAR(128) NULL, + NTUserName NVARCHAR(128) NULL, + OriginalLoginName NVARCHAR(128) NULL, + ProgramName NVARCHAR(128) NULL, + HostName NVARCHAR(128) NULL, + DatabaseID INT NULL, + DatabaseName NVARCHAR(128) NULL, + OpenTransactionCount INT NULL, + DetailsInt INT NULL, + QueryHash BINARY(8) NULL, + JoinKey AS ServerName + CAST(CheckDate AS NVARCHAR(50)), + PRIMARY KEY CLUSTERED (ID ASC));'; - IF (@StandbyMode = 1) - BEGIN - IF (@StandbyUndoPath IS NULL) - BEGIN - IF @Execute = 'Y' OR @Debug = 1 RAISERROR('The file path of the undo file for standby mode was not specified. The database will not be restored in standby mode.', 0, 1) WITH NOWAIT; - END - ELSE IF (SELECT COUNT(*) FROM #SplitDiffBackups) > 0 - SET @sql = @sql + ', STANDBY = ''' + @StandbyUndoPath + @Database + 'Undo.ldf''' + NCHAR(13) + NCHAR(10); - ELSE - SET @sql = N'RESTORE DATABASE ' + @RestoreDatabaseName + N' FROM DISK = ''' + @BackupPathDiff + @LastDiffBackup + N''' WITH STANDBY = ''' + @StandbyUndoPath + @Database + 'Undo.ldf''' + @BackupParameters + @MoveOption + NCHAR(13) + NCHAR(10); - END; - IF @Debug = 1 OR @Execute = 'N' - BEGIN - IF @sql IS NULL PRINT '@sql is NULL for RESTORE DATABASE: @BackupPathDiff, @LastDiffBackup'; - PRINT @sql; - END; - IF @Debug IN (0, 1) AND @Execute = 'Y' - EXECUTE @sql = [dbo].[CommandExecute] @DatabaseContext=N'master', @Command = @sql, @CommandType = 'RESTORE DATABASE', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y'; - - --get the backup completed data so we can apply tlogs from that point forwards - SET @sql = REPLACE(@HeadersSQL, N'{Path}', @CurrentBackupPathDiff + @LastDiffBackup); - - IF @Debug = 1 - BEGIN - IF @sql IS NULL PRINT '@sql is NULL for REPLACE: @CurrentBackupPathDiff, @LastDiffBackup'; - PRINT @sql; - END; - - EXECUTE (@sql); - IF @Debug = 1 - BEGIN - SELECT '#Headers' AS table_name, @LastDiffBackup AS DiffbackupFile, * FROM #Headers AS h WHERE h.BackupType = 5; - END - - --set the @BackupDateTime to the date time on the most recent differential - SET @BackupDateTime = ISNULL( @LastDiffBackupDateTime, @BackupDateTime ); - IF @Debug = 1 - BEGIN - IF @BackupDateTime IS NULL PRINT '@BackupDateTime is NULL for REPLACE: @LastDiffBackupDateTime'; - PRINT @BackupDateTime; - END; - SELECT @DiffLastLSN = CAST(LastLSN AS NUMERIC(25, 0)) - FROM #Headers - WHERE BackupType = 5; - END; + EXEC(@StringToExecute); - IF @DiffLastLSN IS NULL - BEGIN - SET @DiffLastLSN=@FullLastLSN - END -END + /* If the table doesn't have the new QueryHash column, add it. See Github #2162. */ + SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableName; + SET @StringToExecute = N'IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns + WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''QueryHash'') + ALTER TABLE ' + @ObjectFullName + N' ADD QueryHash BINARY(8) NULL;'; + EXEC(@StringToExecute); + /* If the table doesn't have the new JoinKey computed column, add it. See Github #2164. */ + SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableName; + SET @StringToExecute = N'IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns + WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''JoinKey'') + ALTER TABLE ' + @ObjectFullName + N' ADD JoinKey AS ServerName + CAST(CheckDate AS NVARCHAR(50));'; + EXEC(@StringToExecute); -IF @BackupPathLog IS NOT NULL -BEGIN - DELETE FROM @FileList; - DELETE FROM @FileListSimple; - DELETE FROM @PathItem; + SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' + + @OutputDatabaseName + + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' + + @OutputSchemaName + ''') INSERT ' + + @OutputDatabaseName + '.' + + @OutputSchemaName + '.' + + @OutputTableName + + ' (ServerName, CheckDate, CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, QueryText, StartTime, LoginName, NTUserName, OriginalLoginName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, DetailsInt, QueryHash) SELECT ' + + ' @SrvName, @CheckDate, CheckID, Priority, FindingsGroup, Finding, URL, LEFT(Details,4000), HowToStopIt, QueryPlan, QueryText, StartTime, LoginName, NTUserName, OriginalLoginName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, DetailsInt, QueryHash FROM #BlitzFirstResults ORDER BY Priority , FindingsGroup , Finding , Details'; + + EXEC sp_executesql @StringToExecute, + N'@SrvName NVARCHAR(128), @CheckDate datetimeoffset', + @LocalServerName, @StartSampleTime; - DECLARE @CurrentBackupPathLog NVARCHAR(512); - -- Split CSV string logic has taken from Ola Hallengren's :) - WITH BackupPaths ( - StartPosition, EndPosition, PathItem - ) - AS ( - SELECT 1 AS StartPosition, - ISNULL( NULLIF( CHARINDEX( ',', @BackupPathLog, 1 ), 0 ), LEN( @BackupPathLog ) + 1 ) AS EndPosition, - SUBSTRING( @BackupPathLog, 1, ISNULL( NULLIF( CHARINDEX( ',', @BackupPathLog, 1 ), 0 ), LEN( @BackupPathLog ) + 1 ) - 1 ) AS PathItem - WHERE @BackupPathLog IS NOT NULL - UNION ALL - SELECT CAST( EndPosition AS INT ) + 1 AS StartPosition, - ISNULL( NULLIF( CHARINDEX( ',', @BackupPathLog, EndPosition + 1 ), 0 ), LEN( @BackupPathLog ) + 1 ) AS EndPosition, - SUBSTRING( @BackupPathLog, EndPosition + 1, ISNULL( NULLIF( CHARINDEX( ',', @BackupPathLog, EndPosition + 1 ), 0 ), LEN( @BackupPathLog ) + 1 ) - EndPosition - 1 ) AS PathItem - FROM BackupPaths - WHERE EndPosition < LEN( @BackupPathLog ) + 1 - ) - INSERT INTO @PathItem - SELECT CASE RIGHT( PathItem, 1 ) WHEN '\' THEN PathItem ELSE PathItem + '\' END FROM BackupPaths; + /* Delete history older than @OutputTableRetentionDays */ + SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' + + @OutputDatabaseName + + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' + + @OutputSchemaName + ''') DELETE ' + + @OutputDatabaseName + '.' + + @OutputSchemaName + '.' + + @OutputTableName + + ' WHERE ServerName = @SrvName AND CheckDate < @CheckDate ;'; + + EXEC sp_executesql @StringToExecute, + N'@SrvName NVARCHAR(128), @CheckDate date', + @LocalServerName, @OutputTableCleanupDate; + + END; + ELSE IF (SUBSTRING(@OutputTableName, 2, 2) = '##') + BEGIN + SET @StringToExecute = N' IF (OBJECT_ID(''tempdb..' + + @OutputTableName + + ''') IS NULL) CREATE TABLE ' + + @OutputTableName + + ' (ID INT IDENTITY(1,1) NOT NULL, + ServerName NVARCHAR(128), + CheckDate DATETIMEOFFSET, + CheckID INT NOT NULL, + Priority TINYINT NOT NULL, + FindingsGroup VARCHAR(50) NOT NULL, + Finding VARCHAR(200) NOT NULL, + URL VARCHAR(200) NOT NULL, + Details NVARCHAR(4000) NULL, + HowToStopIt [XML] NULL, + QueryPlan [XML] NULL, + QueryText NVARCHAR(MAX) NULL, + StartTime DATETIMEOFFSET NULL, + LoginName NVARCHAR(128) NULL, + NTUserName NVARCHAR(128) NULL, + OriginalLoginName NVARCHAR(128) NULL, + ProgramName NVARCHAR(128) NULL, + HostName NVARCHAR(128) NULL, + DatabaseID INT NULL, + DatabaseName NVARCHAR(128) NULL, + OpenTransactionCount INT NULL, + DetailsInt INT NULL, + QueryHash BINARY(8) NULL, + JoinKey AS ServerName + CAST(CheckDate AS NVARCHAR(50)), + PRIMARY KEY CLUSTERED (ID ASC));' + + ' INSERT ' + + @OutputTableName + + ' (ServerName, CheckDate, CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, QueryText, StartTime, LoginName, NTUserName, OriginalLoginName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, DetailsInt) SELECT ' + + ' @SrvName, @CheckDate, CheckID, Priority, FindingsGroup, Finding, URL, LEFT(Details,4000), HowToStopIt, QueryPlan, QueryText, StartTime, LoginName, NTUserName, OriginalLoginName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, DetailsInt FROM #BlitzFirstResults ORDER BY Priority , FindingsGroup , Finding , Details'; + + EXEC sp_executesql @StringToExecute, + N'@SrvName NVARCHAR(128), @CheckDate datetimeoffset', + @LocalServerName, @StartSampleTime; + END; + ELSE IF (SUBSTRING(@OutputTableName, 2, 1) = '#') + BEGIN + RAISERROR('Due to the nature of Dymamic SQL, only global (i.e. double pound (##)) temp tables are supported for @OutputTableName', 16, 0); + END; + + /* @OutputTableNameFileStats lets us export the results to a permanent table */ + IF @OutputDatabaseName IS NOT NULL + AND @OutputSchemaName IS NOT NULL + AND @OutputTableNameFileStats IS NOT NULL + AND @OutputTableNameFileStats NOT LIKE '#%' + AND EXISTS ( SELECT * + FROM sys.databases + WHERE QUOTENAME([name]) = @OutputDatabaseName) + BEGIN + /* Create the table */ + SET @StringToExecute = 'USE ' + + @OutputDatabaseName + + '; IF EXISTS(SELECT * FROM ' + + @OutputDatabaseName + + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' + + @OutputSchemaName + + ''') AND NOT EXISTS (SELECT * FROM ' + + @OutputDatabaseName + + '.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''' + + @OutputSchemaName + ''' AND QUOTENAME(TABLE_NAME) = ''' + + @OutputTableNameFileStats + ''') CREATE TABLE ' + + @OutputSchemaName + '.' + + @OutputTableNameFileStats + + ' (ID INT IDENTITY(1,1) NOT NULL, + ServerName NVARCHAR(128), + CheckDate DATETIMEOFFSET, + DatabaseID INT NOT NULL, + FileID INT NOT NULL, + DatabaseName NVARCHAR(256) , + FileLogicalName NVARCHAR(256) , + TypeDesc NVARCHAR(60) , + SizeOnDiskMB BIGINT , + io_stall_read_ms BIGINT , + num_of_reads BIGINT , + bytes_read BIGINT , + io_stall_write_ms BIGINT , + num_of_writes BIGINT , + bytes_written BIGINT, + PhysicalName NVARCHAR(520) , + PRIMARY KEY CLUSTERED (ID ASC));'; - WHILE 1 = 1 - BEGIN - SELECT TOP 1 @CurrentBackupPathLog = PathItem FROM @PathItem - WHERE PathItem > COALESCE( @CurrentBackupPathLog, '' ) ORDER BY PathItem; - IF @@rowcount = 0 BREAK; + EXEC(@StringToExecute); - IF @SimpleFolderEnumeration = 1 - BEGIN -- Get list of files - INSERT INTO @FileListSimple (BackupFile, depth, [file]) EXEC master.sys.xp_dirtree @BackupPathLog, 1, 1; - INSERT @FileList (BackupPath, BackupFile) SELECT @CurrentBackupPathLog, BackupFile FROM @FileListSimple; - DELETE FROM @FileListSimple; - END - ELSE - BEGIN - SET @cmd = N'DIR /b "' + @CurrentBackupPathLog + N'"'; - IF @Debug = 1 - BEGIN - IF @cmd IS NULL PRINT '@cmd is NULL for @CurrentBackupPathLog'; - PRINT @cmd; - END; - INSERT INTO @FileList (BackupFile) EXEC master.sys.xp_cmdshell @cmd; - UPDATE @FileList SET BackupPath = @CurrentBackupPathLog - WHERE BackupPath IS NULL; - END; - - IF @SimpleFolderEnumeration = 1 - BEGIN - /*Check what we can*/ - IF NOT EXISTS (SELECT * FROM @FileList) - BEGIN - RAISERROR('(LOG) No rows were returned for that database %s in path %s', 16, 1, @Database, @CurrentBackupPathLog) WITH NOWAIT; - RETURN; - END; - END - ELSE - BEGIN - /*Full Sanity check folders*/ - IF ( - SELECT COUNT(*) - FROM @FileList AS fl - WHERE fl.BackupFile = 'The system cannot find the path specified.' - OR fl.BackupFile = 'File Not Found' - ) = 1 - BEGIN - RAISERROR('(LOG) No rows or bad value for path %s', 16, 1, @CurrentBackupPathLog) WITH NOWAIT; - RETURN; - END; + SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableNameFileStats_View; - IF ( - SELECT COUNT(*) - FROM @FileList AS fl - WHERE fl.BackupFile = 'Access is denied.' - ) = 1 - BEGIN - RAISERROR('(LOG) Access is denied to %s', 16, 1, @CurrentBackupPathLog) WITH NOWAIT; - RETURN; - END; + /* If the view exists without the most recently added columns, drop it. See Github #2162. */ + IF OBJECT_ID(@ObjectFullName) IS NOT NULL + BEGIN + SET @StringToExecute = N'USE ' + @OutputDatabaseName + N'; IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns + WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''JoinKey'') + DROP VIEW ' + @OutputSchemaName + N'.' + @OutputTableNameFileStats_View + N';'; - IF ( - SELECT COUNT(*) - FROM @FileList AS fl - ) = 1 - AND - ( - SELECT COUNT(*) - FROM @FileList AS fl - WHERE fl.BackupFile IS NULL - ) = 1 - BEGIN - RAISERROR('(LOG) Empty directory %s', 16, 1, @CurrentBackupPathLog) WITH NOWAIT; - RETURN; - END + EXEC(@StringToExecute); + END - IF ( - SELECT COUNT(*) - FROM @FileList AS fl - WHERE fl.BackupFile = 'The user name or password is incorrect.' - ) = 1 - BEGIN - RAISERROR('(LOG) Incorrect user name or password for %s', 16, 1, @CurrentBackupPathLog) WITH NOWAIT; - RETURN; - END; - END; - END - /*End folder sanity check*/ + /* Create the view */ + IF OBJECT_ID(@ObjectFullName) IS NULL + BEGIN + SET @StringToExecute = 'USE ' + + @OutputDatabaseName + + '; EXEC (''CREATE VIEW ' + + @OutputSchemaName + '.' + + @OutputTableNameFileStats_View + ' AS ' + @LineFeed + + 'WITH RowDates as' + @LineFeed + + '(' + @LineFeed + + ' SELECT ' + @LineFeed + + ' ROW_NUMBER() OVER (ORDER BY [ServerName], [CheckDate]) ID,' + @LineFeed + + ' [CheckDate]' + @LineFeed + + ' FROM ' + @OutputSchemaName + '.' + @OutputTableNameFileStats + '' + @LineFeed + + ' GROUP BY [ServerName], [CheckDate]' + @LineFeed + + '),' + @LineFeed + + 'CheckDates as' + @LineFeed + + '(' + @LineFeed + + ' SELECT ThisDate.CheckDate,' + @LineFeed + + ' LastDate.CheckDate as PreviousCheckDate' + @LineFeed + + ' FROM RowDates ThisDate' + @LineFeed + + ' JOIN RowDates LastDate' + @LineFeed + + ' ON ThisDate.ID = LastDate.ID + 1' + @LineFeed + + ')' + @LineFeed + + ' SELECT f.ServerName,' + @LineFeed + + ' f.CheckDate,' + @LineFeed + + ' f.DatabaseID,' + @LineFeed + + ' f.DatabaseName,' + @LineFeed + + ' f.FileID,' + @LineFeed + + ' f.FileLogicalName,' + @LineFeed + + ' f.TypeDesc,' + @LineFeed + + ' f.PhysicalName,' + @LineFeed + + ' f.SizeOnDiskMB,' + @LineFeed + + ' DATEDIFF(ss, fPrior.CheckDate, f.CheckDate) AS ElapsedSeconds,' + @LineFeed + + ' (f.SizeOnDiskMB - fPrior.SizeOnDiskMB) AS SizeOnDiskMBgrowth,' + @LineFeed + + ' (f.io_stall_read_ms - fPrior.io_stall_read_ms) AS io_stall_read_ms,' + @LineFeed + + ' io_stall_read_ms_average = CASE' + @LineFeed + + ' WHEN(f.num_of_reads - fPrior.num_of_reads) = 0' + @LineFeed + + ' THEN 0' + @LineFeed + + ' ELSE(f.io_stall_read_ms - fPrior.io_stall_read_ms) / (f.num_of_reads - fPrior.num_of_reads)' + @LineFeed + + ' END,' + @LineFeed + + ' (f.num_of_reads - fPrior.num_of_reads) AS num_of_reads,' + @LineFeed + + ' (f.bytes_read - fPrior.bytes_read) / 1024.0 / 1024.0 AS megabytes_read,' + @LineFeed + + ' (f.io_stall_write_ms - fPrior.io_stall_write_ms) AS io_stall_write_ms,' + @LineFeed + + ' io_stall_write_ms_average = CASE' + @LineFeed + + ' WHEN(f.num_of_writes - fPrior.num_of_writes) = 0' + @LineFeed + + ' THEN 0' + @LineFeed + + ' ELSE(f.io_stall_write_ms - fPrior.io_stall_write_ms) / (f.num_of_writes - fPrior.num_of_writes)' + @LineFeed + + ' END,' + @LineFeed + + ' (f.num_of_writes - fPrior.num_of_writes) AS num_of_writes,' + @LineFeed + + ' (f.bytes_written - fPrior.bytes_written) / 1024.0 / 1024.0 AS megabytes_written, ' + @LineFeed + + ' f.ServerName + CAST(f.CheckDate AS NVARCHAR(50)) AS JoinKey' + @LineFeed + + ' FROM ' + @OutputSchemaName + '.' + @OutputTableNameFileStats + ' f' + @LineFeed + + ' INNER HASH JOIN CheckDates DATES ON f.CheckDate = DATES.CheckDate' + @LineFeed + + ' INNER JOIN ' + @OutputSchemaName + '.' + @OutputTableNameFileStats + ' fPrior ON f.ServerName = fPrior.ServerName' + @LineFeed + + ' AND f.DatabaseID = fPrior.DatabaseID' + @LineFeed + + ' AND f.FileID = fPrior.FileID' + @LineFeed + + ' AND fPrior.CheckDate = DATES.PreviousCheckDate' + @LineFeed + + '' + @LineFeed + + ' WHERE f.num_of_reads >= fPrior.num_of_reads' + @LineFeed + + ' AND f.num_of_writes >= fPrior.num_of_writes' + @LineFeed + + ' AND DATEDIFF(MI, fPrior.CheckDate, f.CheckDate) BETWEEN 1 AND 60;'')' -IF @Debug = 1 -BEGIN - SELECT * FROM @FileList WHERE BackupFile IS NOT NULL; -END + EXEC(@StringToExecute); + END; -IF @SkipBackupsAlreadyInMsdb = 1 -BEGIN - SELECT TOP 1 @LogLastNameInMsdbAS = bf.physical_device_name - FROM msdb.dbo.backupmediafamily bf - INNER JOIN msdb.dbo.backupset bs ON bs.media_set_id = bf.media_set_id - INNER JOIN msdb.dbo.restorehistory rh ON rh.backup_set_id = bs.backup_set_id - WHERE physical_device_name like @BackupPathLog + '%' - AND rh.destination_database_name = @UnquotedRestoreDatabaseName - ORDER BY physical_device_name DESC - - IF @Debug = 1 - BEGIN - SELECT 'Keeping LOG backups with name > : ' + @LogLastNameInMsdbAS - END - - DELETE fl - FROM @FileList AS fl - WHERE fl.BackupPath + fl.BackupFile <= @LogLastNameInMsdbAS -END + SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' + + @OutputDatabaseName + + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' + + @OutputSchemaName + ''') INSERT ' + + @OutputDatabaseName + '.' + + @OutputSchemaName + '.' + + @OutputTableNameFileStats + + ' (ServerName, CheckDate, DatabaseID, FileID, DatabaseName, FileLogicalName, TypeDesc, SizeOnDiskMB, io_stall_read_ms, num_of_reads, bytes_read, io_stall_write_ms, num_of_writes, bytes_written, PhysicalName) SELECT ' + + ' @SrvName, @CheckDate, DatabaseID, FileID, DatabaseName, FileLogicalName, TypeDesc, SizeOnDiskMB, io_stall_read_ms, num_of_reads, bytes_read, io_stall_write_ms, num_of_writes, bytes_written, PhysicalName FROM #FileStats WHERE Pass = 2'; + EXEC sp_executesql @StringToExecute, + N'@SrvName NVARCHAR(128), @CheckDate datetimeoffset', + @LocalServerName, @StartSampleTime; + /* Delete history older than @OutputTableRetentionDays */ + SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' + + @OutputDatabaseName + + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' + + @OutputSchemaName + ''') DELETE ' + + @OutputDatabaseName + '.' + + @OutputSchemaName + '.' + + @OutputTableNameFileStats + + ' WHERE ServerName = @SrvName AND CheckDate < @CheckDate ;'; -IF (@OnlyLogsAfter IS NOT NULL) -BEGIN - - IF @Execute = 'Y' OR @Debug = 1 RAISERROR('@OnlyLogsAfter is NOT NULL, deleting from @FileList', 0, 1) WITH NOWAIT; - - DELETE fl - FROM @FileList AS fl - WHERE BackupFile LIKE N'%.trn' - AND BackupFile LIKE N'%' + @Database + N'%' - AND REPLACE( RIGHT( REPLACE( fl.BackupFile, RIGHT( fl.BackupFile, PATINDEX( '%_[0-9][0-9]%', REVERSE( fl.BackupFile ) ) ), '' ), 16 ), '_', '' ) < @OnlyLogsAfter; - -END + EXEC sp_executesql @StringToExecute, + N'@SrvName NVARCHAR(128), @CheckDate date', + @LocalServerName, @OutputTableCleanupDate; + END; + ELSE IF (SUBSTRING(@OutputTableNameFileStats, 2, 2) = '##') + BEGIN + SET @StringToExecute = N' IF (OBJECT_ID(''tempdb..' + + @OutputTableNameFileStats + + ''') IS NULL) CREATE TABLE ' + + @OutputTableNameFileStats + + ' (ID INT IDENTITY(1,1) NOT NULL, + ServerName NVARCHAR(128), + CheckDate DATETIMEOFFSET, + DatabaseID INT NOT NULL, + FileID INT NOT NULL, + DatabaseName NVARCHAR(256) , + FileLogicalName NVARCHAR(256) , + TypeDesc NVARCHAR(60) , + SizeOnDiskMB BIGINT , + io_stall_read_ms BIGINT , + num_of_reads BIGINT , + bytes_read BIGINT , + io_stall_write_ms BIGINT , + num_of_writes BIGINT , + bytes_written BIGINT, + PhysicalName NVARCHAR(520) , + DetailsInt INT NULL, + PRIMARY KEY CLUSTERED (ID ASC));' + + ' INSERT ' + + @OutputTableNameFileStats + + ' (ServerName, CheckDate, DatabaseID, FileID, DatabaseName, FileLogicalName, TypeDesc, SizeOnDiskMB, io_stall_read_ms, num_of_reads, bytes_read, io_stall_write_ms, num_of_writes, bytes_written, PhysicalName) SELECT ' + + ' @SrvName, @CheckDate, DatabaseID, FileID, DatabaseName, FileLogicalName, TypeDesc, SizeOnDiskMB, io_stall_read_ms, num_of_reads, bytes_read, io_stall_write_ms, num_of_writes, bytes_written, PhysicalName FROM #FileStats WHERE Pass = 2'; + EXEC sp_executesql @StringToExecute, + N'@SrvName NVARCHAR(128), @CheckDate datetimeoffset', + @LocalServerName, @StartSampleTime; + END; + ELSE IF (SUBSTRING(@OutputTableNameFileStats, 2, 1) = '#') + BEGIN + RAISERROR('Due to the nature of Dymamic SQL, only global (i.e. double pound (##)) temp tables are supported for @OutputTableName', 16, 0); + END; --- Check for log backups -IF(@StopAt IS NULL AND @OnlyLogsAfter IS NULL) - BEGIN - DELETE FROM @FileList - WHERE BackupFile LIKE N'%.trn' - AND BackupFile LIKE N'%' + @Database + N'%' - AND NOT (@ContinueLogs = 1 OR (@ContinueLogs = 0 AND REPLACE( RIGHT( REPLACE( BackupFile, RIGHT( BackupFile, PATINDEX( '%_[0-9][0-9]%', REVERSE( BackupFile ) ) ), '' ), 16 ), '_', '' ) >= @BackupDateTime)); - END; + /* @OutputTableNamePerfmonStats lets us export the results to a permanent table */ + IF @OutputDatabaseName IS NOT NULL + AND @OutputSchemaName IS NOT NULL + AND @OutputTableNamePerfmonStats IS NOT NULL + AND @OutputTableNamePerfmonStats NOT LIKE '#%' + AND EXISTS ( SELECT * + FROM sys.databases + WHERE QUOTENAME([name]) = @OutputDatabaseName) + BEGIN + /* Create the table */ + SET @StringToExecute = 'USE ' + + @OutputDatabaseName + + '; IF EXISTS(SELECT * FROM ' + + @OutputDatabaseName + + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' + + @OutputSchemaName + + ''') AND NOT EXISTS (SELECT * FROM ' + + @OutputDatabaseName + + '.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''' + + @OutputSchemaName + ''' AND QUOTENAME(TABLE_NAME) = ''' + + @OutputTableNamePerfmonStats + ''') CREATE TABLE ' + + @OutputSchemaName + '.' + + @OutputTableNamePerfmonStats + + ' (ID INT IDENTITY(1,1) NOT NULL, + ServerName NVARCHAR(128), + CheckDate DATETIMEOFFSET, + [object_name] NVARCHAR(128) NOT NULL, + [counter_name] NVARCHAR(128) NOT NULL, + [instance_name] NVARCHAR(128) NULL, + [cntr_value] BIGINT NULL, + [cntr_type] INT NOT NULL, + [value_delta] BIGINT NULL, + [value_per_second] DECIMAL(18,2) NULL, + PRIMARY KEY CLUSTERED (ID ASC));'; -IF (@StopAt IS NULL AND @OnlyLogsAfter IS NOT NULL) - BEGIN - DELETE FROM @FileList - WHERE BackupFile LIKE N'%.trn' - AND BackupFile LIKE N'%' + @Database + N'%' - AND NOT (@ContinueLogs = 1 OR (@ContinueLogs = 0 AND REPLACE( RIGHT( REPLACE( BackupFile, RIGHT( BackupFile, PATINDEX( '%_[0-9][0-9]%', REVERSE( BackupFile ) ) ), '' ), 16 ), '_', '' ) >= @OnlyLogsAfter)); - END; + EXEC(@StringToExecute); + SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableNamePerfmonStats_View; -IF (@StopAt IS NOT NULL AND @OnlyLogsAfter IS NULL) - BEGIN - DELETE FROM @FileList - WHERE BackupFile LIKE N'%.trn' - AND BackupFile LIKE N'%' + @Database + N'%' - AND NOT (@ContinueLogs = 1 OR (@ContinueLogs = 0 AND REPLACE( RIGHT( REPLACE( BackupFile, RIGHT( BackupFile, PATINDEX( '%_[0-9][0-9]%', REVERSE( BackupFile ) ) ), '' ), 16 ), '_', '' ) >= @BackupDateTime) AND REPLACE( RIGHT( REPLACE( BackupFile, RIGHT( BackupFile, PATINDEX( '%_[0-9][0-9]%', REVERSE( BackupFile ) ) ), '' ), 16 ), '_', '' ) <= @StopAt) - AND NOT ((@ContinueLogs = 1 AND REPLACE( RIGHT( REPLACE( BackupFile, RIGHT( BackupFile, PATINDEX( '%_[0-9][0-9]%', REVERSE( BackupFile ) ) ), '' ), 16 ), '_', '' ) <= @StopAt) OR (@ContinueLogs = 0 AND REPLACE( RIGHT( REPLACE( BackupFile, RIGHT( BackupFile, PATINDEX( '%_[0-9][0-9]%', REVERSE( BackupFile ) ) ), '' ), 16 ), '_', '' ) >= @BackupDateTime) AND REPLACE( RIGHT( REPLACE( BackupFile, RIGHT( BackupFile, PATINDEX( '%_[0-9][0-9]%', REVERSE( BackupFile ) ) ), '' ), 16 ), '_', '' ) <= @StopAt) + /* If the view exists without the most recently added columns, drop it. See Github #2162. */ + IF OBJECT_ID(@ObjectFullName) IS NOT NULL + BEGIN + SET @StringToExecute = N'USE ' + @OutputDatabaseName + N'; IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns + WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''JoinKey'') + DROP VIEW ' + @OutputSchemaName + N'.' + @OutputTableNamePerfmonStats_View + N';'; - END; + EXEC(@StringToExecute); + END -IF (@StopAt IS NOT NULL AND @OnlyLogsAfter IS NOT NULL) - BEGIN - DECLARE BackupFiles CURSOR FOR - SELECT BackupFile - FROM @FileList - WHERE BackupFile LIKE N'%.trn' - AND BackupFile LIKE N'%' + @Database + N'%' - AND (@ContinueLogs = 1 OR (@ContinueLogs = 0 AND REPLACE(LEFT(RIGHT(BackupFile, 19), 15),'_','') >= @BackupDateTime) AND REPLACE(LEFT(RIGHT(BackupFile, 19), 15),'_','') <= @StopAt) - AND ((@ContinueLogs = 1 AND REPLACE(LEFT(RIGHT(BackupFile, 19), 15),'_','') <= @StopAt) OR (@ContinueLogs = 0 AND REPLACE(LEFT(RIGHT(BackupFile, 19), 15),'_','') >= @BackupDateTime) AND REPLACE(LEFT(RIGHT(BackupFile, 19), 15),'_','') <= @StopAt) - AND (@ContinueLogs = 1 OR (@ContinueLogs = 0 AND REPLACE(LEFT(RIGHT(BackupFile, 19), 15),'_','') >= @OnlyLogsAfter)) - ORDER BY BackupFile; - - OPEN BackupFiles; - END; + /* Create the view */ + IF OBJECT_ID(@ObjectFullName) IS NULL + BEGIN + SET @StringToExecute = 'USE ' + + @OutputDatabaseName + + '; EXEC (''CREATE VIEW ' + + @OutputSchemaName + '.' + + @OutputTableNamePerfmonStats_View + ' AS ' + @LineFeed + + 'WITH RowDates as' + @LineFeed + + '(' + @LineFeed + + ' SELECT ' + @LineFeed + + ' ROW_NUMBER() OVER (ORDER BY [ServerName], [CheckDate]) ID,' + @LineFeed + + ' [CheckDate]' + @LineFeed + + ' FROM ' + @OutputSchemaName + '.' +@OutputTableNamePerfmonStats + '' + @LineFeed + + ' GROUP BY [ServerName], [CheckDate]' + @LineFeed + + '),' + @LineFeed + + 'CheckDates as' + @LineFeed + + '(' + @LineFeed + + ' SELECT ThisDate.CheckDate,' + @LineFeed + + ' LastDate.CheckDate as PreviousCheckDate' + @LineFeed + + ' FROM RowDates ThisDate' + @LineFeed + + ' JOIN RowDates LastDate' + @LineFeed + + ' ON ThisDate.ID = LastDate.ID + 1' + @LineFeed + + ')' + @LineFeed + + 'SELECT' + @LineFeed + + ' pMon.[ServerName]' + @LineFeed + + ' ,pMon.[CheckDate]' + @LineFeed + + ' ,pMon.[object_name]' + @LineFeed + + ' ,pMon.[counter_name]' + @LineFeed + + ' ,pMon.[instance_name]' + @LineFeed + + ' ,DATEDIFF(SECOND,pMonPrior.[CheckDate],pMon.[CheckDate]) AS ElapsedSeconds' + @LineFeed + + ' ,pMon.[cntr_value]' + @LineFeed + + ' ,pMon.[cntr_type]' + @LineFeed + + ' ,(pMon.[cntr_value] - pMonPrior.[cntr_value]) AS cntr_delta' + @LineFeed + + ' ,(pMon.cntr_value - pMonPrior.cntr_value) * 1.0 / DATEDIFF(ss, pMonPrior.CheckDate, pMon.CheckDate) AS cntr_delta_per_second' + @LineFeed + + ' ,pMon.ServerName + CAST(pMon.CheckDate AS NVARCHAR(50)) AS JoinKey' + @LineFeed + + ' FROM ' + @OutputSchemaName + '.' +@OutputTableNamePerfmonStats + ' pMon' + @LineFeed + + ' INNER HASH JOIN CheckDates Dates' + @LineFeed + + ' ON Dates.CheckDate = pMon.CheckDate' + @LineFeed + + ' JOIN ' + @OutputSchemaName + '.' +@OutputTableNamePerfmonStats + ' pMonPrior' + @LineFeed + + ' ON Dates.PreviousCheckDate = pMonPrior.CheckDate' + @LineFeed + + ' AND pMon.[ServerName] = pMonPrior.[ServerName] ' + @LineFeed + + ' AND pMon.[object_name] = pMonPrior.[object_name] ' + @LineFeed + + ' AND pMon.[counter_name] = pMonPrior.[counter_name] ' + @LineFeed + + ' AND pMon.[instance_name] = pMonPrior.[instance_name]' + @LineFeed + + ' WHERE DATEDIFF(MI, pMonPrior.CheckDate, pMon.CheckDate) BETWEEN 1 AND 60;'')' + EXEC(@StringToExecute); + END -IF (@StandbyMode = 1) - BEGIN - IF (@StandbyUndoPath IS NULL) - BEGIN - IF @Execute = 'Y' OR @Debug = 1 RAISERROR('The file path of the undo file for standby mode was not specified. Logs will not be restored in standby mode.', 0, 1) WITH NOWAIT; - END; - ELSE - SET @LogRecoveryOption = N'STANDBY = ''' + @StandbyUndoPath + @Database + 'Undo.ldf'''; - END; + SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableNamePerfmonStatsActuals_View; -IF (@LogRecoveryOption = N'') - BEGIN - SET @LogRecoveryOption = N'NORECOVERY'; - END; + /* If the view exists without the most recently added columns, drop it. See Github #2162. */ + IF OBJECT_ID(@ObjectFullName) IS NOT NULL + BEGIN + SET @StringToExecute = N'USE ' + @OutputDatabaseName + N'; IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns + WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''JoinKey'') + DROP VIEW ' + @OutputSchemaName + N'.' + @OutputTableNamePerfmonStatsActuals_View + N';'; - -- Group Ordering based on Backup File Name excluding Index {#} to construct coma separated string in "Restore Log" Command -SELECT BackupPath,BackupFile,DENSE_RANK() OVER (ORDER BY REPLACE( RIGHT( REPLACE( BackupFile, RIGHT( BackupFile, PATINDEX( '%_[0-9][0-9]%', REVERSE( BackupFile ) ) ), '' ), 16 ), '_', '' )) AS DenseRank INTO #SplitLogBackups -FROM @FileList -WHERE BackupFile IS NOT NULL; + EXEC(@StringToExecute); + END --- Loop through all the files for the database - WHILE 1 = 1 - BEGIN + /* Create the second view */ + IF OBJECT_ID(@ObjectFullName) IS NULL + BEGIN + SET @StringToExecute = 'USE ' + + @OutputDatabaseName + + '; EXEC (''CREATE VIEW ' + + @OutputSchemaName + '.' + + @OutputTableNamePerfmonStatsActuals_View + ' AS ' + @LineFeed + + 'WITH PERF_AVERAGE_BULK AS' + @LineFeed + + '(' + @LineFeed + + ' SELECT ServerName,' + @LineFeed + + ' object_name,' + @LineFeed + + ' instance_name,' + @LineFeed + + ' counter_name,' + @LineFeed + + ' CASE WHEN CHARINDEX(''''('''', counter_name) = 0 THEN counter_name ELSE LEFT (counter_name, CHARINDEX(''''('''',counter_name)-1) END AS counter_join,' + @LineFeed + + ' CheckDate,' + @LineFeed + + ' cntr_delta' + @LineFeed + + ' FROM ' + @OutputSchemaName + '.' + @OutputTableNamePerfmonStats_View + @LineFeed + + ' WHERE cntr_type IN(1073874176)' + @LineFeed + + ' AND cntr_delta <> 0' + @LineFeed + + '),' + @LineFeed + + 'PERF_LARGE_RAW_BASE AS' + @LineFeed + + '(' + @LineFeed + + ' SELECT ServerName,' + @LineFeed + + ' object_name,' + @LineFeed + + ' instance_name,' + @LineFeed + + ' LEFT(counter_name, CHARINDEX(''''BASE'''', UPPER(counter_name))-1) AS counter_join,' + @LineFeed + + ' CheckDate,' + @LineFeed + + ' cntr_delta' + @LineFeed + + ' FROM ' + @OutputSchemaName + '.' + @OutputTableNamePerfmonStats_View + '' + @LineFeed + + ' WHERE cntr_type IN(1073939712)' + @LineFeed + + ' AND cntr_delta <> 0' + @LineFeed + + '),' + @LineFeed + + 'PERF_AVERAGE_FRACTION AS' + @LineFeed + + '(' + @LineFeed + + ' SELECT ServerName,' + @LineFeed + + ' object_name,' + @LineFeed + + ' instance_name,' + @LineFeed + + ' counter_name,' + @LineFeed + + ' counter_name AS counter_join,' + @LineFeed + + ' CheckDate,' + @LineFeed + + ' cntr_delta' + @LineFeed + + ' FROM ' + @OutputSchemaName + '.' + @OutputTableNamePerfmonStats_View + '' + @LineFeed + + ' WHERE cntr_type IN(537003264)' + @LineFeed + + ' AND cntr_delta <> 0' + @LineFeed + + '),' + @LineFeed + + 'PERF_COUNTER_BULK_COUNT AS' + @LineFeed + + '(' + @LineFeed + + ' SELECT ServerName,' + @LineFeed + + ' object_name,' + @LineFeed + + ' instance_name,' + @LineFeed + + ' counter_name,' + @LineFeed + + ' CheckDate,' + @LineFeed + + ' cntr_delta / ElapsedSeconds AS cntr_value' + @LineFeed + + ' FROM ' + @OutputSchemaName + '.' + @OutputTableNamePerfmonStats_View + '' + @LineFeed + + ' WHERE cntr_type IN(272696576, 272696320)' + @LineFeed + + ' AND cntr_delta <> 0' + @LineFeed + + '),' + @LineFeed + + 'PERF_COUNTER_RAWCOUNT AS' + @LineFeed + + '(' + @LineFeed + + ' SELECT ServerName,' + @LineFeed + + ' object_name,' + @LineFeed + + ' instance_name,' + @LineFeed + + ' counter_name,' + @LineFeed + + ' CheckDate,' + @LineFeed + + ' cntr_value' + @LineFeed + + ' FROM ' + @OutputSchemaName + '.' + @OutputTableNamePerfmonStats_View + '' + @LineFeed + + ' WHERE cntr_type IN(65792, 65536)' + @LineFeed + + ')' + @LineFeed + + '' + @LineFeed + + 'SELECT NUM.ServerName,' + @LineFeed + + ' NUM.object_name,' + @LineFeed + + ' NUM.counter_name,' + @LineFeed + + ' NUM.instance_name,' + @LineFeed + + ' NUM.CheckDate,' + @LineFeed + + ' NUM.cntr_delta / DEN.cntr_delta AS cntr_value,' + @LineFeed + + ' NUM.ServerName + CAST(NUM.CheckDate AS NVARCHAR(50)) AS JoinKey' + @LineFeed + + ' ' + @LineFeed + + 'FROM PERF_AVERAGE_BULK AS NUM' + @LineFeed + + ' JOIN PERF_LARGE_RAW_BASE AS DEN ON NUM.counter_join = DEN.counter_join' + @LineFeed + + ' AND NUM.CheckDate = DEN.CheckDate' + @LineFeed + + ' AND NUM.ServerName = DEN.ServerName' + @LineFeed + + ' AND NUM.object_name = DEN.object_name' + @LineFeed + + ' AND NUM.instance_name = DEN.instance_name' + @LineFeed + + ' AND DEN.cntr_delta <> 0' + @LineFeed + + '' + @LineFeed + + 'UNION ALL' + @LineFeed + + '' + @LineFeed + + 'SELECT NUM.ServerName,' + @LineFeed + + ' NUM.object_name,' + @LineFeed + + ' NUM.counter_name,' + @LineFeed + + ' NUM.instance_name,' + @LineFeed + + ' NUM.CheckDate,' + @LineFeed + + ' CAST((CAST(NUM.cntr_delta as DECIMAL(19)) / DEN.cntr_delta) as decimal(23,3)) AS cntr_value,' + @LineFeed + + ' NUM.ServerName + CAST(NUM.CheckDate AS NVARCHAR(50)) AS JoinKey' + @LineFeed + + 'FROM PERF_AVERAGE_FRACTION AS NUM' + @LineFeed + + ' JOIN PERF_LARGE_RAW_BASE AS DEN ON NUM.counter_join = DEN.counter_join' + @LineFeed + + ' AND NUM.CheckDate = DEN.CheckDate' + @LineFeed + + ' AND NUM.ServerName = DEN.ServerName' + @LineFeed + + ' AND NUM.object_name = DEN.object_name' + @LineFeed + + ' AND NUM.instance_name = DEN.instance_name' + @LineFeed + + ' AND DEN.cntr_delta <> 0' + @LineFeed + + 'UNION ALL' + @LineFeed + + '' + @LineFeed + + 'SELECT ServerName,' + @LineFeed + + ' object_name,' + @LineFeed + + ' counter_name,' + @LineFeed + + ' instance_name,' + @LineFeed + + ' CheckDate,' + @LineFeed + + ' cntr_value,' + @LineFeed + + ' ServerName + CAST(CheckDate AS NVARCHAR(50)) AS JoinKey' + @LineFeed + + 'FROM PERF_COUNTER_BULK_COUNT' + @LineFeed + + '' + @LineFeed + + 'UNION ALL' + @LineFeed + + '' + @LineFeed + + 'SELECT ServerName,' + @LineFeed + + ' object_name,' + @LineFeed + + ' counter_name,' + @LineFeed + + ' instance_name,' + @LineFeed + + ' CheckDate,' + @LineFeed + + ' cntr_value,' + @LineFeed + + ' ServerName + CAST(CheckDate AS NVARCHAR(50)) AS JoinKey' + @LineFeed + + 'FROM PERF_COUNTER_RAWCOUNT;'')'; - -- Get the TOP record to use in "Restore HeaderOnly/FileListOnly" statement - SELECT TOP 1 @CurrentBackupPathLog = BackupPath, @BackupFile = BackupFile FROM #SplitLogBackups WHERE DenseRank = @LogRestoreRanking; - IF @@rowcount = 0 BREAK; + EXEC(@StringToExecute); + END; - IF @i = 1 - - BEGIN - SET @sql = REPLACE(@HeadersSQL, N'{Path}', @CurrentBackupPathLog + @BackupFile); - - IF @Debug = 1 - BEGIN - IF @sql IS NULL PRINT '@sql is NULL for REPLACE: @HeadersSQL, @CurrentBackupPathLog, @BackupFile'; - PRINT @sql; - END; - - EXECUTE (@sql); - - SELECT TOP 1 @LogFirstLSN = CAST(FirstLSN AS NUMERIC(25, 0)), - @LogLastLSN = CAST(LastLSN AS NUMERIC(25, 0)) - FROM #Headers - WHERE BackupType = 2; - - IF (@ContinueLogs = 0 AND @LogFirstLSN <= @FullLastLSN AND @FullLastLSN <= @LogLastLSN AND @RestoreDiff = 0) OR (@ContinueLogs = 1 AND @LogFirstLSN <= @DatabaseLastLSN AND @DatabaseLastLSN < @LogLastLSN AND @RestoreDiff = 0) - SET @i = 2; - - IF (@ContinueLogs = 0 AND @LogFirstLSN <= @DiffLastLSN AND @DiffLastLSN <= @LogLastLSN AND @RestoreDiff = 1) OR (@ContinueLogs = 1 AND @LogFirstLSN <= @DatabaseLastLSN AND @DatabaseLastLSN < @LogLastLSN AND @RestoreDiff = 1) - SET @i = 2; - - DELETE FROM #Headers WHERE BackupType = 2; + SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' + + @OutputDatabaseName + + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' + + @OutputSchemaName + ''') INSERT ' + + @OutputDatabaseName + '.' + + @OutputSchemaName + '.' + + @OutputTableNamePerfmonStats + + ' (ServerName, CheckDate, object_name, counter_name, instance_name, cntr_value, cntr_type, value_delta, value_per_second) SELECT ' + + ' @SrvName, @CheckDate, object_name, counter_name, instance_name, cntr_value, cntr_type, value_delta, value_per_second FROM #PerfmonStats WHERE Pass = 2'; - END; + EXEC sp_executesql @StringToExecute, + N'@SrvName NVARCHAR(128), @CheckDate datetimeoffset', + @LocalServerName, @StartSampleTime; - IF @i = 1 - BEGIN - IF @Debug = 1 RAISERROR('No Log to Restore', 0, 1) WITH NOWAIT; - END + /* Delete history older than @OutputTableRetentionDays */ + SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' + + @OutputDatabaseName + + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' + + @OutputSchemaName + ''') DELETE ' + + @OutputDatabaseName + '.' + + @OutputSchemaName + '.' + + @OutputTableNamePerfmonStats + + ' WHERE ServerName = @SrvName AND CheckDate < @CheckDate ;'; - IF @i = 2 - BEGIN - IF @Execute = 'Y' RAISERROR('@i set to 2, restoring logs', 0, 1) WITH NOWAIT; + EXEC sp_executesql @StringToExecute, + N'@SrvName NVARCHAR(128), @CheckDate date', + @LocalServerName, @OutputTableCleanupDate; - IF (SELECT COUNT( * ) FROM #SplitLogBackups WHERE DenseRank = @LogRestoreRanking) > 1 - BEGIN - RAISERROR ('Split backups found', 0, 1) WITH NOWAIT; - SET @sql = N'RESTORE LOG ' + @RestoreDatabaseName + N' FROM ' - + STUFF( - (SELECT CHAR( 10 ) + ',DISK=''' + BackupPath + BackupFile + '''' - FROM #SplitLogBackups - WHERE DenseRank = @LogRestoreRanking - ORDER BY BackupFile - FOR XML PATH ('')), - 1, - 2, - '' ) + N' WITH ' + @LogRecoveryOption + NCHAR(13) + NCHAR(10); - END; - ELSE - SET @sql = N'RESTORE LOG ' + @RestoreDatabaseName + N' FROM DISK = ''' + @CurrentBackupPathLog + @BackupFile + N''' WITH ' + @LogRecoveryOption + NCHAR(13) + NCHAR(10); - - IF @Debug = 1 OR @Execute = 'N' - BEGIN - IF @sql IS NULL PRINT '@sql is NULL for RESTORE LOG: @RestoreDatabaseName, @CurrentBackupPathLog, @BackupFile'; - PRINT @sql; - END; - - IF @Debug IN (0, 1) AND @Execute = 'Y' - EXECUTE @sql = [dbo].[CommandExecute] @DatabaseContext=N'master', @Command = @sql, @CommandType = 'RESTORE LOG', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y'; - END; - - SET @LogRestoreRanking += 1; - END; - IF @Debug = 1 - BEGIN - SELECT '#SplitLogBackups' AS table_name, BackupPath, BackupFile FROM #SplitLogBackups; - END -END + END; + ELSE IF (SUBSTRING(@OutputTableNamePerfmonStats, 2, 2) = '##') + BEGIN + SET @StringToExecute = N' IF (OBJECT_ID(''tempdb..' + + @OutputTableNamePerfmonStats + + ''') IS NULL) CREATE TABLE ' + + @OutputTableNamePerfmonStats + + ' (ID INT IDENTITY(1,1) NOT NULL, + ServerName NVARCHAR(128), + CheckDate DATETIMEOFFSET, + [object_name] NVARCHAR(128) NOT NULL, + [counter_name] NVARCHAR(128) NOT NULL, + [instance_name] NVARCHAR(128) NULL, + [cntr_value] BIGINT NULL, + [cntr_type] INT NOT NULL, + [value_delta] BIGINT NULL, + [value_per_second] DECIMAL(18,2) NULL, + PRIMARY KEY CLUSTERED (ID ASC));' + + ' INSERT ' + + @OutputTableNamePerfmonStats + + ' (ServerName, CheckDate, object_name, counter_name, instance_name, cntr_value, cntr_type, value_delta, value_per_second) SELECT ' + + CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128)) + + ' @SrvName, @CheckDate, object_name, counter_name, instance_name, cntr_value, cntr_type, value_delta, value_per_second FROM #PerfmonStats WHERE Pass = 2'; --- Put database in a useable state -IF @RunRecovery = 1 - BEGIN - SET @sql = N'RESTORE DATABASE ' + @RestoreDatabaseName + N' WITH RECOVERY' + NCHAR(13); + EXEC sp_executesql @StringToExecute, + N'@SrvName NVARCHAR(128), @CheckDate datetimeoffset', + @LocalServerName, @StartSampleTime; + END; + ELSE IF (SUBSTRING(@OutputTableNamePerfmonStats, 2, 1) = '#') + BEGIN + RAISERROR('Due to the nature of Dymamic SQL, only global (i.e. double pound (##)) temp tables are supported for @OutputTableName', 16, 0); + END; - IF @Debug = 1 OR @Execute = 'N' - BEGIN - IF @sql IS NULL PRINT '@sql is NULL for RESTORE DATABASE: @RestoreDatabaseName'; - PRINT @sql; - END; - IF @Debug IN (0, 1) AND @Execute = 'Y' - EXECUTE @sql = [dbo].[CommandExecute] @DatabaseContext=N'master', @Command = @sql, @CommandType = 'RECOVER DATABASE', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y'; - END; + /* @OutputTableNameWaitStats lets us export the results to a permanent table */ + IF @OutputDatabaseName IS NOT NULL + AND @OutputSchemaName IS NOT NULL + AND @OutputTableNameWaitStats IS NOT NULL + AND @OutputTableNameWaitStats NOT LIKE '#%' + AND EXISTS ( SELECT * + FROM sys.databases + WHERE QUOTENAME([name]) = @OutputDatabaseName) + BEGIN + /* Create the table */ + SET @StringToExecute = 'USE ' + + @OutputDatabaseName + + '; IF EXISTS(SELECT * FROM ' + + @OutputDatabaseName + + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' + + @OutputSchemaName + + ''') AND NOT EXISTS (SELECT * FROM ' + + @OutputDatabaseName + + '.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''' + + @OutputSchemaName + ''' AND QUOTENAME(TABLE_NAME) = ''' + + @OutputTableNameWaitStats + ''') ' + @LineFeed + + 'BEGIN' + @LineFeed + + 'CREATE TABLE ' + + @OutputSchemaName + '.' + + @OutputTableNameWaitStats + + ' (ID INT IDENTITY(1,1) NOT NULL, + ServerName NVARCHAR(128), + CheckDate DATETIMEOFFSET, + wait_type NVARCHAR(60), + wait_time_ms BIGINT, + signal_wait_time_ms BIGINT, + waiting_tasks_count BIGINT , + PRIMARY KEY CLUSTERED (ID));' + @LineFeed + + 'CREATE NONCLUSTERED INDEX IX_ServerName_wait_type_CheckDate_Includes ON ' + @OutputSchemaName + '.' + @OutputTableNameWaitStats + @LineFeed + + '(ServerName, wait_type, CheckDate) INCLUDE (wait_time_ms, signal_wait_time_ms, waiting_tasks_count);' + @LineFeed + + 'END'; --- Ensure simple recovery model -IF @ForceSimpleRecovery = 1 - BEGIN - SET @sql = N'ALTER DATABASE ' + @RestoreDatabaseName + N' SET RECOVERY SIMPLE' + NCHAR(13); + EXEC(@StringToExecute); - IF @Debug = 1 OR @Execute = 'N' - BEGIN - IF @sql IS NULL PRINT '@sql is NULL for SET RECOVERY SIMPLE: @RestoreDatabaseName'; - PRINT @sql; - END; + /* Create the wait stats category table */ + SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableNameWaitStats_Categories; + IF OBJECT_ID(@ObjectFullName) IS NULL + BEGIN + SET @StringToExecute = 'USE ' + + @OutputDatabaseName + + '; EXEC (''CREATE TABLE ' + + @OutputSchemaName + '.' + + @OutputTableNameWaitStats_Categories + ' (WaitType NVARCHAR(60) PRIMARY KEY CLUSTERED, WaitCategory NVARCHAR(128) NOT NULL, Ignorable BIT DEFAULT 0);'')'; - IF @Debug IN (0, 1) AND @Execute = 'Y' - EXECUTE @sql = [dbo].[CommandExecute] @DatabaseContext=N'master', @Command = @sql, @CommandType = 'SIMPLE LOGGING', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y'; - END; + EXEC(@StringToExecute); + END; - -- Run checkdb against this database -IF @RunCheckDB = 1 - BEGIN - SET @sql = N'DBCC CHECKDB (' + @RestoreDatabaseName + N') WITH NO_INFOMSGS, ALL_ERRORMSGS, DATA_PURITY;'; - - IF @Debug = 1 OR @Execute = 'N' - BEGIN - IF @sql IS NULL PRINT '@sql is NULL for Run Integrity Check: @RestoreDatabaseName'; - PRINT @sql; - END; - - IF @Debug IN (0, 1) AND @Execute = 'Y' - EXECUTE @sql = [dbo].[CommandExecute] @DatabaseContext=N'master', @Command = @sql, @CommandType = 'DBCC CHECKDB', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y'; - END; + /* Make sure the wait stats category table has the current number of rows */ + SET @StringToExecute = 'USE ' + + @OutputDatabaseName + + '; EXEC (''IF (SELECT COALESCE(SUM(1),0) FROM ' + @OutputSchemaName + '.' + @OutputTableNameWaitStats_Categories + ') <> (SELECT COALESCE(SUM(1),0) FROM ##WaitCategories)' + @LineFeed + + 'BEGIN ' + @LineFeed + + 'TRUNCATE TABLE ' + @OutputSchemaName + '.' + @OutputTableNameWaitStats_Categories + @LineFeed + + 'INSERT INTO ' + @OutputSchemaName + '.' + @OutputTableNameWaitStats_Categories + ' (WaitType, WaitCategory, Ignorable) SELECT WaitType, WaitCategory, Ignorable FROM ##WaitCategories;' + @LineFeed + + 'END'')'; + EXEC(@StringToExecute); -IF @DatabaseOwner IS NOT NULL - BEGIN - IF @RunRecovery = 1 - BEGIN - IF EXISTS (SELECT * FROM master.dbo.syslogins WHERE syslogins.loginname = @DatabaseOwner) - BEGIN - SET @sql = N'ALTER AUTHORIZATION ON DATABASE::' + @RestoreDatabaseName + ' TO [' + @DatabaseOwner + ']'; - IF @Debug = 1 OR @Execute = 'N' - BEGIN - IF @sql IS NULL PRINT '@sql is NULL for Set Database Owner'; - PRINT @sql; - END; + SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableNameWaitStats_View; - IF @Debug IN (0, 1) AND @Execute = 'Y' - EXECUTE @sql = [dbo].[CommandExecute] @DatabaseContext=N'master',@Command = @sql, @CommandType = 'ALTER AUTHORIZATION', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y'; - END - ELSE - BEGIN - PRINT @DatabaseOwner + ' is not a valid Login. Database Owner not set.'; - END - END - ELSE - BEGIN - PRINT @RestoreDatabaseName + ' is still in Recovery, so we are unable to change the database owner to [' + @DatabaseOwner + '].'; - END - END; + /* If the view exists without the most recently added columns, drop it. See Github #2162. */ + IF OBJECT_ID(@ObjectFullName) IS NOT NULL + BEGIN + SET @StringToExecute = N'USE ' + @OutputDatabaseName + N'; IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns + WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''JoinKey'') + DROP VIEW ' + @OutputSchemaName + N'.' + @OutputTableNameWaitStats_View + N';'; - -- If test restore then blow the database away (be careful) -IF @TestRestore = 1 - BEGIN - SET @sql = N'DROP DATABASE ' + @RestoreDatabaseName + NCHAR(13); - - IF @Debug = 1 OR @Execute = 'N' - BEGIN - IF @sql IS NULL PRINT '@sql is NULL for DROP DATABASE: @RestoreDatabaseName'; - PRINT @sql; - END; - - IF @Debug IN (0, 1) AND @Execute = 'Y' - EXECUTE @sql = [dbo].[CommandExecute] @DatabaseContext=N'master',@Command = @sql, @CommandType = 'DROP DATABASE', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y'; + EXEC(@StringToExecute); + END - END; --- Clean-Up Tempdb Objects -IF OBJECT_ID( 'tempdb..#SplitFullBackups' ) IS NOT NULL DROP TABLE #SplitFullBackups; -IF OBJECT_ID( 'tempdb..#SplitDiffBackups' ) IS NOT NULL DROP TABLE #SplitDiffBackups; -IF OBJECT_ID( 'tempdb..#SplitLogBackups' ) IS NOT NULL DROP TABLE #SplitLogBackups; -GO -IF OBJECT_ID('dbo.sp_ineachdb') IS NULL - EXEC ('CREATE PROCEDURE dbo.sp_ineachdb AS RETURN 0') -GO + /* Create the wait stats view */ + IF OBJECT_ID(@ObjectFullName) IS NULL + BEGIN + SET @StringToExecute = 'USE ' + + @OutputDatabaseName + + '; EXEC (''CREATE VIEW ' + + @OutputSchemaName + '.' + + @OutputTableNameWaitStats_View + ' AS ' + @LineFeed + + 'WITH RowDates as' + @LineFeed + + '(' + @LineFeed + + ' SELECT ' + @LineFeed + + ' ROW_NUMBER() OVER (ORDER BY [ServerName], [CheckDate]) ID,' + @LineFeed + + ' [CheckDate]' + @LineFeed + + ' FROM ' + @OutputSchemaName + '.' + @OutputTableNameWaitStats + @LineFeed + + ' GROUP BY [ServerName], [CheckDate]' + @LineFeed + + '),' + @LineFeed + + 'CheckDates as' + @LineFeed + + '(' + @LineFeed + + ' SELECT ThisDate.CheckDate,' + @LineFeed + + ' LastDate.CheckDate as PreviousCheckDate' + @LineFeed + + ' FROM RowDates ThisDate' + @LineFeed + + ' JOIN RowDates LastDate' + @LineFeed + + ' ON ThisDate.ID = LastDate.ID + 1' + @LineFeed + + ')' + @LineFeed + + 'SELECT w.ServerName, w.CheckDate, w.wait_type, COALESCE(wc.WaitCategory, ''''Other'''') AS WaitCategory, COALESCE(wc.Ignorable,0) AS Ignorable' + @LineFeed + + ', DATEDIFF(ss, wPrior.CheckDate, w.CheckDate) AS ElapsedSeconds' + @LineFeed + + ', (w.wait_time_ms - wPrior.wait_time_ms) AS wait_time_ms_delta' + @LineFeed + + ', (w.wait_time_ms - wPrior.wait_time_ms) / 60000.0 AS wait_time_minutes_delta' + @LineFeed + + ', (w.wait_time_ms - wPrior.wait_time_ms) / 1000.0 / DATEDIFF(ss, wPrior.CheckDate, w.CheckDate) AS wait_time_minutes_per_minute' + @LineFeed + + ', (w.signal_wait_time_ms - wPrior.signal_wait_time_ms) AS signal_wait_time_ms_delta' + @LineFeed + + ', (w.waiting_tasks_count - wPrior.waiting_tasks_count) AS waiting_tasks_count_delta' + @LineFeed + + ', w.ServerName + CAST(w.CheckDate AS NVARCHAR(50)) AS JoinKey' + @LineFeed + + 'FROM ' + @OutputSchemaName + '.' + @OutputTableNameWaitStats + ' w' + @LineFeed + + 'INNER HASH JOIN CheckDates Dates' + @LineFeed + + 'ON Dates.CheckDate = w.CheckDate' + @LineFeed + + 'INNER JOIN ' + @OutputSchemaName + '.' + @OutputTableNameWaitStats + ' wPrior ON w.ServerName = wPrior.ServerName AND w.wait_type = wPrior.wait_type AND Dates.PreviousCheckDate = wPrior.CheckDate' + @LineFeed + + 'LEFT OUTER JOIN ' + @OutputSchemaName + '.' + @OutputTableNameWaitStats_Categories + ' wc ON w.wait_type = wc.WaitType' + @LineFeed + + 'WHERE DATEDIFF(MI, wPrior.CheckDate, w.CheckDate) BETWEEN 1 AND 60' + @LineFeed + + 'AND [w].[wait_time_ms] >= [wPrior].[wait_time_ms];'')' -ALTER PROCEDURE [dbo].[sp_ineachdb] - -- mssqltips.com/sqlservertip/5694/execute-a-command-in-the-context-of-each-database-in-sql-server--part-2/ - @command nvarchar(max) = NULL, - @replace_character nchar(1) = N'?', - @print_dbname bit = 0, - @select_dbname bit = 0, - @print_command bit = 0, - @print_command_only bit = 0, - @suppress_quotename bit = 0, -- use with caution - @system_only bit = 0, - @user_only bit = 0, - @name_pattern nvarchar(300) = N'%', - @database_list nvarchar(max) = NULL, - @exclude_pattern nvarchar(300) = NULL, - @exclude_list nvarchar(max) = NULL, - @recovery_model_desc nvarchar(120) = NULL, - @compatibility_level tinyint = NULL, - @state_desc nvarchar(120) = N'ONLINE', - @is_read_only bit = 0, - @is_auto_close_on bit = NULL, - @is_auto_shrink_on bit = NULL, - @is_broker_enabled bit = NULL, - @user_access nvarchar(128) = NULL, - @Help BIT = 0, - @Version VARCHAR(30) = NULL OUTPUT, - @VersionDate DATETIME = NULL OUTPUT, - @VersionCheckMode BIT = 0 --- WITH EXECUTE AS OWNER – maybe not a great idea, depending on the security of your system -AS -BEGIN - SET NOCOUNT ON; + EXEC(@StringToExecute); + END; - SELECT @Version = '8.0', @VersionDate = '20210117'; - - IF(@VersionCheckMode = 1) - BEGIN - RETURN; - END; - - IF @Help = 1 - BEGIN - - PRINT ' - /* - sp_ineachdb from http://FirstResponderKit.org - - This script will execute a command against multiple databases. - - To learn more, visit http://FirstResponderKit.org where you can download new - versions for free, watch training videos on how it works, get more info on - the findings, contribute your own code, and more. - - Known limitations of this version: - - Only Microsoft-supported versions of SQL Server. Sorry, 2005 and 2000. - - Tastes awful with marmite. - - Unknown limitations of this version: - - None. (If we knew them, they would be known. Duh.) - - Changes - for the full list of improvements and fixes in this version, see: - https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/ - - MIT License - - Copyright (c) 2021 Brent Ozar Unlimited - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE. - - */ - '; - - RETURN -1; - END + SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' + + @OutputDatabaseName + + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' + + @OutputSchemaName + ''') INSERT ' + + @OutputDatabaseName + '.' + + @OutputSchemaName + '.' + + @OutputTableNameWaitStats + + ' (ServerName, CheckDate, wait_type, wait_time_ms, signal_wait_time_ms, waiting_tasks_count) SELECT ' + + ' @SrvName, @CheckDate, wait_type, wait_time_ms, signal_wait_time_ms, waiting_tasks_count FROM #WaitStats WHERE Pass = 2 AND wait_time_ms > 0 AND waiting_tasks_count > 0'; - DECLARE @exec nvarchar(150), - @sx nvarchar(18) = N'.sys.sp_executesql', - @db sysname, - @dbq sysname, - @cmd nvarchar(max), - @thisdb sysname, - @cr char(2) = CHAR(13) + CHAR(10), - @SQLVersion AS tinyint = (@@microsoftversion / 0x1000000) & 0xff, -- Stores the SQL Server Version Number(8(2000),9(2005),10(2008 & 2008R2),11(2012),12(2014),13(2016),14(2017),15(2019) - @ServerName AS sysname = CONVERT(sysname, SERVERPROPERTY('ServerName')), -- Stores the SQL Server Instance name. - @NoSpaces nvarchar(20) = N'%[^' + CHAR(9) + CHAR(32) + CHAR(10) + CHAR(13) + N']%'; --Pattern for PATINDEX + EXEC sp_executesql @StringToExecute, + N'@SrvName NVARCHAR(128), @CheckDate datetimeoffset', + @LocalServerName, @StartSampleTime; + /* Delete history older than @OutputTableRetentionDays */ + SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' + + @OutputDatabaseName + + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' + + @OutputSchemaName + ''') DELETE ' + + @OutputDatabaseName + '.' + + @OutputSchemaName + '.' + + @OutputTableNameWaitStats + + ' WHERE ServerName = @SrvName AND CheckDate < @CheckDate ;'; - CREATE TABLE #ineachdb(id int, name nvarchar(512), is_distributor bit); + EXEC sp_executesql @StringToExecute, + N'@SrvName NVARCHAR(128), @CheckDate date', + @LocalServerName, @OutputTableCleanupDate; + END; + ELSE IF (SUBSTRING(@OutputTableNameWaitStats, 2, 2) = '##') + BEGIN + SET @StringToExecute = N' IF (OBJECT_ID(''tempdb..' + + @OutputTableNameWaitStats + + ''') IS NULL) CREATE TABLE ' + + @OutputTableNameWaitStats + + ' (ID INT IDENTITY(1,1) NOT NULL, + ServerName NVARCHAR(128), + CheckDate DATETIMEOFFSET, + wait_type NVARCHAR(60), + wait_time_ms BIGINT, + signal_wait_time_ms BIGINT, + waiting_tasks_count BIGINT , + PRIMARY KEY CLUSTERED (ID ASC));' + + ' INSERT ' + + @OutputTableNameWaitStats + + ' (ServerName, CheckDate, wait_type, wait_time_ms, signal_wait_time_ms, waiting_tasks_count) SELECT ' + + ' @SrvName, @CheckDate, wait_type, wait_time_ms, signal_wait_time_ms, waiting_tasks_count FROM #WaitStats WHERE Pass = 2 AND wait_time_ms > 0 AND waiting_tasks_count > 0'; -/* - -- first, let's limit to only DBs the caller is interested in - IF @database_list > N'' - -- comma-separated list of potentially valid/invalid/quoted/unquoted names - BEGIN - ;WITH n(n) AS (SELECT 1 UNION ALL SELECT n+1 FROM n WHERE n <= LEN(@database_list)), - names AS - ( - SELECT name = LTRIM(RTRIM(PARSENAME(SUBSTRING(@database_list, n, - CHARINDEX(N',', @database_list + N',', n) - n), 1))) - FROM n - WHERE SUBSTRING(N',' + @database_list, n, 1) = N',' - ) - INSERT #ineachdb(id,name,is_distributor) - SELECT d.database_id, d.name, d.is_distributor - FROM sys.databases AS d - WHERE EXISTS (SELECT 1 FROM names WHERE name = d.name) - OPTION (MAXRECURSION 0); - END - ELSE - BEGIN - INSERT #ineachdb(id,name,is_distributor) SELECT database_id, name, is_distributor FROM sys.databases; - END + EXEC sp_executesql @StringToExecute, + N'@SrvName NVARCHAR(128), @CheckDate datetimeoffset', + @LocalServerName, @StartSampleTime; + END; + ELSE IF (SUBSTRING(@OutputTableNameWaitStats, 2, 1) = '#') + BEGIN + RAISERROR('Due to the nature of Dymamic SQL, only global (i.e. double pound (##)) temp tables are supported for @OutputTableName', 16, 0); + END; - -- now delete any that have been explicitly excluded - exclude trumps include - IF @exclude_list > N'' - -- comma-separated list of potentially valid/invalid/quoted/unquoted names - BEGIN - ;WITH n(n) AS (SELECT 1 UNION ALL SELECT n+1 FROM n WHERE n <= LEN(@exclude_list)), - names AS - ( - SELECT name = LTRIM(RTRIM(PARSENAME(SUBSTRING(@exclude_list, n, - CHARINDEX(N',', @exclude_list + N',', n) - n), 1))) - FROM n - WHERE SUBSTRING(N',' + @exclude_list, n, 1) = N',' - ) - DELETE d - FROM #ineachdb AS d - INNER JOIN names - ON names.name = d.name - OPTION (MAXRECURSION 0); - END -*/ -/* -@database_list and @exclude_list are are processed at the same time -1)Read the list searching for a comma or [ -2)If we find a comma, save the name -3)If we find a [, we begin to accumulate the result until we reach closing ], (jumping over escaped ]]). -4)Finally, tabs, line breaks and spaces are removed from unquoted names -*/ -WITH C -AS (SELECT V.SrcList - , CAST('' AS nvarchar(MAX)) AS Name - , V.DBList - , 0 AS InBracket - , 0 AS Quoted - FROM (VALUES ('In', @database_list + ','), ('Out', @exclude_list + ',')) AS V (SrcList, DBList) - UNION ALL - SELECT C.SrcList --- , IIF(V.Found = '[', '', SUBSTRING(C.DBList, 1, V.Place - 1))/*remove initial [*/ - , CASE WHEN V.Found = '[' THEN '' ELSE SUBSTRING(C.DBList, 1, V.Place - 1) END /*remove initial [*/ - , STUFF(C.DBList, 1, V.Place, '') --- , IIF(V.Found = '[', 1, 0) - ,Case WHEN V.Found = '[' THEN 1 ELSE 0 END - , 0 - FROM C - CROSS APPLY - ( VALUES (PATINDEX('%[,[]%', C.DBList), SUBSTRING(C.DBList, PATINDEX('%[,[]%', C.DBList), 1))) AS V (Place, Found) - WHERE C.DBList > '' - AND C.InBracket = 0 - UNION ALL - SELECT C.SrcList --- , CONCAT(C.Name, SUBSTRING(C.DBList, 1, V.Place + W.DoubleBracket - 1)) /*Accumulates only one ] if escaped]] or none if end]*/ - , ISNULL(C.Name,'') + ISNULL(SUBSTRING(C.DBList, 1, V.Place + W.DoubleBracket - 1),'') /*Accumulates only one ] if escaped]] or none if end]*/ - , STUFF(C.DBList, 1, V.Place + W.DoubleBracket, '') - , W.DoubleBracket - , 1 - FROM C - CROSS APPLY (VALUES (CHARINDEX(']', C.DBList))) AS V (Place) - -- CROSS APPLY (VALUES (IIF(SUBSTRING(C.DBList, V.Place + 1, 1) = ']', 1, 0))) AS W (DoubleBracket) - CROSS APPLY (VALUES (CASE WHEN SUBSTRING(C.DBList, V.Place + 1, 1) = ']' THEN 1 ELSE 0 END)) AS W (DoubleBracket) - WHERE C.DBList > '' - AND C.InBracket = 1) - , F -AS (SELECT C.SrcList - , CASE WHEN C.Quoted = 0 THEN - SUBSTRING(C.Name, PATINDEX(@NoSpaces, Name), DATALENGTH (Name)/2 - PATINDEX(@NoSpaces, Name) - PATINDEX(@NoSpaces, REVERSE(Name))+2) - ELSE C.Name END - AS name - FROM C - WHERE C.InBracket = 0 - AND C.Name > '') -INSERT #ineachdb(id,name,is_distributor) -SELECT d.database_id - , d.name - , d.is_distributor -FROM sys.databases AS d -WHERE ( EXISTS (SELECT NULL FROM F WHERE F.name = d.name AND F.SrcList = 'In') - OR @database_list IS NULL) - AND NOT EXISTS (SELECT NULL FROM F WHERE F.name = d.name AND F.SrcList = 'Out') -OPTION (MAXRECURSION 0); -; - -- next, let's delete any that *don't* match various criteria passed in - DELETE dbs FROM #ineachdb AS dbs - WHERE (@system_only = 1 AND (id NOT IN (1,2,3,4) AND is_distributor <> 1)) - OR (@user_only = 1 AND (id IN (1,2,3,4) OR is_distributor = 1)) - OR name NOT LIKE @name_pattern - OR name LIKE @exclude_pattern - OR EXISTS - ( - SELECT 1 - FROM sys.databases AS d - WHERE d.database_id = dbs.id - AND NOT - ( - recovery_model_desc = COALESCE(@recovery_model_desc, recovery_model_desc) - AND compatibility_level = COALESCE(@compatibility_level, compatibility_level) - AND is_read_only = COALESCE(@is_read_only, is_read_only) - AND is_auto_close_on = COALESCE(@is_auto_close_on, is_auto_close_on) - AND is_auto_shrink_on = COALESCE(@is_auto_shrink_on, is_auto_shrink_on) - AND is_broker_enabled = COALESCE(@is_broker_enabled, is_broker_enabled) - ) - ); - -- if a user access is specified, remove any that are NOT in that state - IF @user_access IN (N'SINGLE_USER', N'MULTI_USER', N'RESTRICTED_USER') - BEGIN - DELETE #ineachdb WHERE - CONVERT(nvarchar(128), DATABASEPROPERTYEX(name, 'UserAccess')) <> @user_access; - END - -- finally, remove any that are not *fully* online or we can't access - DELETE dbs FROM #ineachdb AS dbs - WHERE EXISTS - ( - SELECT 1 FROM sys.databases - WHERE database_id = dbs.id - AND - ( - @state_desc = N'ONLINE' AND - ( - [state] & 992 <> 0 -- inaccessible - OR state_desc <> N'ONLINE' -- not online - OR HAS_DBACCESS(name) = 0 -- don't have access - OR DATABASEPROPERTYEX(name, 'Collation') IS NULL -- not fully online. See "status" here: - -- https://docs.microsoft.com/en-us/sql/t-sql/functions/databasepropertyex-transact-sql - ) - OR (@state_desc <> N'ONLINE' AND state_desc <> @state_desc) - ) - ); + DECLARE @separator AS VARCHAR(1); + IF @OutputType = 'RSV' + SET @separator = CHAR(31); + ELSE + SET @separator = ','; - -- from Andy Mallon / First Responders Kit. Make sure that if we're an - -- AG secondary, we skip any database where allow connections is off - IF @SQLVersion >= 11 - BEGIN - DELETE dbs FROM #ineachdb AS dbs - WHERE EXISTS - ( - SELECT 1 FROM sys.dm_hadr_database_replica_states AS drs - INNER JOIN sys.availability_replicas AS ar - ON ar.replica_id = drs.replica_id - INNER JOIN sys.dm_hadr_availability_group_states ags - ON ags.group_id = ar.group_id - WHERE drs.database_id = dbs.id - AND ar.secondary_role_allow_connections = 0 - AND ags.primary_replica <> @ServerName - ); - END + IF @OutputType = 'COUNT' AND @SinceStartup = 0 + BEGIN + SELECT COUNT(*) AS Warnings + FROM #BlitzFirstResults; + END; + ELSE + IF @OutputType = 'Opserver1' AND @SinceStartup = 0 + BEGIN - -- Well, if we deleted them all... - IF NOT EXISTS (SELECT 1 FROM #ineachdb) - BEGIN - RAISERROR(N'No databases to process.', 1, 0); - RETURN; - END + SELECT r.[Priority] , + r.[FindingsGroup] , + r.[Finding] , + r.[URL] , + r.[Details], + r.[HowToStopIt] , + r.[CheckID] , + r.[StartTime], + r.[LoginName], + r.[NTUserName], + r.[OriginalLoginName], + r.[ProgramName], + r.[HostName], + r.[DatabaseID], + r.[DatabaseName], + r.[OpenTransactionCount], + r.[QueryPlan], + r.[QueryText], + qsNow.plan_handle AS PlanHandle, + qsNow.sql_handle AS SqlHandle, + qsNow.statement_start_offset AS StatementStartOffset, + qsNow.statement_end_offset AS StatementEndOffset, + [Executions] = qsNow.execution_count - (COALESCE(qsFirst.execution_count, 0)), + [ExecutionsPercent] = CAST(100.0 * (qsNow.execution_count - (COALESCE(qsFirst.execution_count, 0))) / (qsTotal.execution_count - qsTotalFirst.execution_count) AS DECIMAL(6,2)), + [Duration] = qsNow.total_elapsed_time - (COALESCE(qsFirst.total_elapsed_time, 0)), + [DurationPercent] = CAST(100.0 * (qsNow.total_elapsed_time - (COALESCE(qsFirst.total_elapsed_time, 0))) / (qsTotal.total_elapsed_time - qsTotalFirst.total_elapsed_time) AS DECIMAL(6,2)), + [CPU] = qsNow.total_worker_time - (COALESCE(qsFirst.total_worker_time, 0)), + [CPUPercent] = CAST(100.0 * (qsNow.total_worker_time - (COALESCE(qsFirst.total_worker_time, 0))) / (qsTotal.total_worker_time - qsTotalFirst.total_worker_time) AS DECIMAL(6,2)), + [Reads] = qsNow.total_logical_reads - (COALESCE(qsFirst.total_logical_reads, 0)), + [ReadsPercent] = CAST(100.0 * (qsNow.total_logical_reads - (COALESCE(qsFirst.total_logical_reads, 0))) / (qsTotal.total_logical_reads - qsTotalFirst.total_logical_reads) AS DECIMAL(6,2)), + [PlanCreationTime] = CONVERT(NVARCHAR(100), qsNow.creation_time ,121), + [TotalExecutions] = qsNow.execution_count, + [TotalExecutionsPercent] = CAST(100.0 * qsNow.execution_count / qsTotal.execution_count AS DECIMAL(6,2)), + [TotalDuration] = qsNow.total_elapsed_time, + [TotalDurationPercent] = CAST(100.0 * qsNow.total_elapsed_time / qsTotal.total_elapsed_time AS DECIMAL(6,2)), + [TotalCPU] = qsNow.total_worker_time, + [TotalCPUPercent] = CAST(100.0 * qsNow.total_worker_time / qsTotal.total_worker_time AS DECIMAL(6,2)), + [TotalReads] = qsNow.total_logical_reads, + [TotalReadsPercent] = CAST(100.0 * qsNow.total_logical_reads / qsTotal.total_logical_reads AS DECIMAL(6,2)), + r.[DetailsInt] + FROM #BlitzFirstResults r + LEFT OUTER JOIN #QueryStats qsTotal ON qsTotal.Pass = 0 + LEFT OUTER JOIN #QueryStats qsTotalFirst ON qsTotalFirst.Pass = -1 + LEFT OUTER JOIN #QueryStats qsNow ON r.QueryStatsNowID = qsNow.ID + LEFT OUTER JOIN #QueryStats qsFirst ON r.QueryStatsFirstID = qsFirst.ID + ORDER BY r.Priority , + r.FindingsGroup , + CASE + WHEN r.CheckID = 6 THEN DetailsInt + ELSE 0 + END DESC, + r.Finding, + r.ID; + END; + ELSE IF @OutputType IN ( 'CSV', 'RSV' ) AND @SinceStartup = 0 + BEGIN - -- ok, now, let's go through what we have left - DECLARE dbs CURSOR LOCAL FAST_FORWARD - FOR SELECT DB_NAME(id), QUOTENAME(DB_NAME(id)) - FROM #ineachdb; + SELECT Result = CAST([Priority] AS NVARCHAR(100)) + + @separator + CAST(CheckID AS NVARCHAR(100)) + + @separator + COALESCE([FindingsGroup], + '(N/A)') + @separator + + COALESCE([Finding], '(N/A)') + @separator + + COALESCE(DatabaseName, '(N/A)') + @separator + + COALESCE([URL], '(N/A)') + @separator + + COALESCE([Details], '(N/A)') + FROM #BlitzFirstResults + ORDER BY Priority , + FindingsGroup , + CASE + WHEN CheckID = 6 THEN DetailsInt + ELSE 0 + END DESC, + Finding, + Details; + END; + ELSE IF @OutputType = 'Top10' + BEGIN + /* Measure waits in hours */ + ;WITH max_batch AS ( + SELECT MAX(SampleTime) AS SampleTime + FROM #WaitStats + ) + SELECT TOP 10 + CAST(DATEDIFF(mi,wd1.SampleTime, wd2.SampleTime) / 60.0 AS DECIMAL(18,1)) AS [Hours Sample], + wd1.wait_type, + COALESCE(wcat.WaitCategory, 'Other') AS wait_category, + CAST(c.[Wait Time (Seconds)] / 60.0 / 60 AS DECIMAL(18,1)) AS [Wait Time (Hours)], + CAST((wd2.wait_time_ms - wd1.wait_time_ms) / 1000.0 / cores.cpu_count / DATEDIFF(ss, wd1.SampleTime, wd2.SampleTime) AS DECIMAL(18,1)) AS [Per Core Per Hour], + (wd2.waiting_tasks_count - wd1.waiting_tasks_count) AS [Number of Waits], + CASE WHEN (wd2.waiting_tasks_count - wd1.waiting_tasks_count) > 0 + THEN + CAST((wd2.wait_time_ms-wd1.wait_time_ms)/ + (1.0*(wd2.waiting_tasks_count - wd1.waiting_tasks_count)) AS NUMERIC(12,1)) + ELSE 0 END AS [Avg ms Per Wait] + FROM max_batch b + JOIN #WaitStats wd2 ON + wd2.SampleTime =b.SampleTime + JOIN #WaitStats wd1 ON + wd1.wait_type=wd2.wait_type AND + wd2.SampleTime > wd1.SampleTime + CROSS APPLY (SELECT SUM(1) AS cpu_count FROM sys.dm_os_schedulers WHERE status = 'VISIBLE ONLINE' AND is_online = 1) AS cores + CROSS APPLY (SELECT + CAST((wd2.wait_time_ms-wd1.wait_time_ms)/1000. AS NUMERIC(12,1)) AS [Wait Time (Seconds)], + CAST((wd2.signal_wait_time_ms - wd1.signal_wait_time_ms)/1000. AS NUMERIC(12,1)) AS [Signal Wait Time (Seconds)]) AS c + LEFT OUTER JOIN ##WaitCategories wcat ON wd1.wait_type = wcat.WaitType + WHERE (wd2.waiting_tasks_count - wd1.waiting_tasks_count) > 0 + AND wd2.wait_time_ms-wd1.wait_time_ms > 0 + ORDER BY [Wait Time (Seconds)] DESC; + END; + ELSE IF @ExpertMode = 0 AND @OutputType <> 'NONE' AND @OutputXMLasNVARCHAR = 0 AND @SinceStartup = 0 + BEGIN + SELECT [Priority] , + [FindingsGroup] , + [Finding] , + [URL] , + CAST(@StockDetailsHeader + [Details] + @StockDetailsFooter AS XML) AS Details, + CAST(@StockWarningHeader + HowToStopIt + @StockWarningFooter AS XML) AS HowToStopIt, + [QueryText], + [QueryPlan] + FROM #BlitzFirstResults + WHERE (@Seconds > 0 OR (Priority IN (0, 250, 251, 255))) /* For @Seconds = 0, filter out broken checks for now */ + ORDER BY Priority , + FindingsGroup , + CASE + WHEN CheckID = 6 THEN DetailsInt + ELSE 0 + END DESC, + Finding, + ID, + CAST(Details AS NVARCHAR(4000)); + END; + ELSE IF @OutputType <> 'NONE' AND @OutputXMLasNVARCHAR = 1 AND @SinceStartup = 0 + BEGIN + SELECT [Priority] , + [FindingsGroup] , + [Finding] , + [URL] , + CAST(@StockDetailsHeader + [Details] + @StockDetailsFooter AS NVARCHAR(MAX)) AS Details, + CAST([HowToStopIt] AS NVARCHAR(MAX)) AS HowToStopIt, + CAST([QueryText] AS NVARCHAR(MAX)) AS QueryText, + CAST([QueryPlan] AS NVARCHAR(MAX)) AS QueryPlan + FROM #BlitzFirstResults + WHERE (@Seconds > 0 OR (Priority IN (0, 250, 251, 255))) /* For @Seconds = 0, filter out broken checks for now */ + ORDER BY Priority , + FindingsGroup , + CASE + WHEN CheckID = 6 THEN DetailsInt + ELSE 0 + END DESC, + Finding, + ID, + CAST(Details AS NVARCHAR(4000)); + END; + ELSE IF @ExpertMode = 1 AND @OutputType <> 'NONE' + BEGIN + IF @SinceStartup = 0 + SELECT r.[Priority] , + r.[FindingsGroup] , + r.[Finding] , + r.[URL] , + CAST(@StockDetailsHeader + r.[Details] + @StockDetailsFooter AS XML) AS Details, + CAST(@StockWarningHeader + r.HowToStopIt + @StockWarningFooter AS XML) AS HowToStopIt, + r.[CheckID] , + r.[StartTime], + r.[LoginName], + r.[NTUserName], + r.[OriginalLoginName], + r.[ProgramName], + r.[HostName], + r.[DatabaseID], + r.[DatabaseName], + r.[OpenTransactionCount], + r.[QueryPlan], + r.[QueryText], + qsNow.plan_handle AS PlanHandle, + qsNow.sql_handle AS SqlHandle, + qsNow.statement_start_offset AS StatementStartOffset, + qsNow.statement_end_offset AS StatementEndOffset, + [Executions] = qsNow.execution_count - (COALESCE(qsFirst.execution_count, 0)), + [ExecutionsPercent] = CAST(100.0 * (qsNow.execution_count - (COALESCE(qsFirst.execution_count, 0))) / (qsTotal.execution_count - qsTotalFirst.execution_count) AS DECIMAL(6,2)), + [Duration] = qsNow.total_elapsed_time - (COALESCE(qsFirst.total_elapsed_time, 0)), + [DurationPercent] = CAST(100.0 * (qsNow.total_elapsed_time - (COALESCE(qsFirst.total_elapsed_time, 0))) / (qsTotal.total_elapsed_time - qsTotalFirst.total_elapsed_time) AS DECIMAL(6,2)), + [CPU] = qsNow.total_worker_time - (COALESCE(qsFirst.total_worker_time, 0)), + [CPUPercent] = CAST(100.0 * (qsNow.total_worker_time - (COALESCE(qsFirst.total_worker_time, 0))) / (qsTotal.total_worker_time - qsTotalFirst.total_worker_time) AS DECIMAL(6,2)), + [Reads] = qsNow.total_logical_reads - (COALESCE(qsFirst.total_logical_reads, 0)), + [ReadsPercent] = CAST(100.0 * (qsNow.total_logical_reads - (COALESCE(qsFirst.total_logical_reads, 0))) / (qsTotal.total_logical_reads - qsTotalFirst.total_logical_reads) AS DECIMAL(6,2)), + [PlanCreationTime] = CONVERT(NVARCHAR(100), qsNow.creation_time ,121), + [TotalExecutions] = qsNow.execution_count, + [TotalExecutionsPercent] = CAST(100.0 * qsNow.execution_count / qsTotal.execution_count AS DECIMAL(6,2)), + [TotalDuration] = qsNow.total_elapsed_time, + [TotalDurationPercent] = CAST(100.0 * qsNow.total_elapsed_time / qsTotal.total_elapsed_time AS DECIMAL(6,2)), + [TotalCPU] = qsNow.total_worker_time, + [TotalCPUPercent] = CAST(100.0 * qsNow.total_worker_time / qsTotal.total_worker_time AS DECIMAL(6,2)), + [TotalReads] = qsNow.total_logical_reads, + [TotalReadsPercent] = CAST(100.0 * qsNow.total_logical_reads / qsTotal.total_logical_reads AS DECIMAL(6,2)), + r.[DetailsInt] + FROM #BlitzFirstResults r + LEFT OUTER JOIN #QueryStats qsTotal ON qsTotal.Pass = 0 + LEFT OUTER JOIN #QueryStats qsTotalFirst ON qsTotalFirst.Pass = -1 + LEFT OUTER JOIN #QueryStats qsNow ON r.QueryStatsNowID = qsNow.ID + LEFT OUTER JOIN #QueryStats qsFirst ON r.QueryStatsFirstID = qsFirst.ID + WHERE (@Seconds > 0 OR (Priority IN (0, 250, 251, 255))) /* For @Seconds = 0, filter out broken checks for now */ + ORDER BY r.Priority , + r.FindingsGroup , + CASE + WHEN r.CheckID = 6 THEN DetailsInt + ELSE 0 + END DESC, + r.Finding, + r.ID, + CAST(r.Details AS NVARCHAR(4000)); - OPEN dbs; + ------------------------- + --What happened: #WaitStats + ------------------------- + IF @Seconds = 0 + BEGIN + /* Measure waits in hours */ + ;WITH max_batch AS ( + SELECT MAX(SampleTime) AS SampleTime + FROM #WaitStats + ) + SELECT + 'WAIT STATS' AS Pattern, + b.SampleTime AS [Sample Ended], + CAST(DATEDIFF(mi,wd1.SampleTime, wd2.SampleTime) / 60.0 AS DECIMAL(18,1)) AS [Hours Sample], + wd1.wait_type, + COALESCE(wcat.WaitCategory, 'Other') AS wait_category, + CAST(c.[Wait Time (Seconds)] / 60.0 / 60 AS DECIMAL(18,1)) AS [Wait Time (Hours)], + CAST((wd2.wait_time_ms - wd1.wait_time_ms) / 1000.0 / cores.cpu_count / DATEDIFF(ss, wd1.SampleTime, wd2.SampleTime) AS DECIMAL(18,1)) AS [Per Core Per Hour], + CAST(c.[Signal Wait Time (Seconds)] / 60.0 / 60 AS DECIMAL(18,1)) AS [Signal Wait Time (Hours)], + CASE WHEN c.[Wait Time (Seconds)] > 0 + THEN CAST(100.*(c.[Signal Wait Time (Seconds)]/c.[Wait Time (Seconds)]) AS NUMERIC(4,1)) + ELSE 0 END AS [Percent Signal Waits], + (wd2.waiting_tasks_count - wd1.waiting_tasks_count) AS [Number of Waits], + CASE WHEN (wd2.waiting_tasks_count - wd1.waiting_tasks_count) > 0 + THEN + CAST((wd2.wait_time_ms-wd1.wait_time_ms)/ + (1.0*(wd2.waiting_tasks_count - wd1.waiting_tasks_count)) AS NUMERIC(12,1)) + ELSE 0 END AS [Avg ms Per Wait], + N'https://www.sqlskills.com/help/waits/' + LOWER(wd1.wait_type) + '/' AS URL + FROM max_batch b + JOIN #WaitStats wd2 ON + wd2.SampleTime =b.SampleTime + JOIN #WaitStats wd1 ON + wd1.wait_type=wd2.wait_type AND + wd2.SampleTime > wd1.SampleTime + CROSS APPLY (SELECT SUM(1) AS cpu_count FROM sys.dm_os_schedulers WHERE status = 'VISIBLE ONLINE' AND is_online = 1) AS cores + CROSS APPLY (SELECT + CAST((wd2.wait_time_ms-wd1.wait_time_ms)/1000. AS NUMERIC(12,1)) AS [Wait Time (Seconds)], + CAST((wd2.signal_wait_time_ms - wd1.signal_wait_time_ms)/1000. AS NUMERIC(12,1)) AS [Signal Wait Time (Seconds)]) AS c + LEFT OUTER JOIN ##WaitCategories wcat ON wd1.wait_type = wcat.WaitType + WHERE (wd2.waiting_tasks_count - wd1.waiting_tasks_count) > 0 + AND wd2.wait_time_ms-wd1.wait_time_ms > 0 + ORDER BY [Wait Time (Seconds)] DESC; + END; + ELSE + BEGIN + /* Measure waits in seconds */ + ;WITH max_batch AS ( + SELECT MAX(SampleTime) AS SampleTime + FROM #WaitStats + ) + SELECT + 'WAIT STATS' AS Pattern, + b.SampleTime AS [Sample Ended], + DATEDIFF(ss,wd1.SampleTime, wd2.SampleTime) AS [Seconds Sample], + wd1.wait_type, + COALESCE(wcat.WaitCategory, 'Other') AS wait_category, + c.[Wait Time (Seconds)], + CAST((CAST(wd2.wait_time_ms - wd1.wait_time_ms AS MONEY)) / 1000.0 / cores.cpu_count / DATEDIFF(ss, wd1.SampleTime, wd2.SampleTime) AS DECIMAL(18,1)) AS [Per Core Per Second], + c.[Signal Wait Time (Seconds)], + CASE WHEN c.[Wait Time (Seconds)] > 0 + THEN CAST(100.*(c.[Signal Wait Time (Seconds)]/c.[Wait Time (Seconds)]) AS NUMERIC(4,1)) + ELSE 0 END AS [Percent Signal Waits], + (wd2.waiting_tasks_count - wd1.waiting_tasks_count) AS [Number of Waits], + CASE WHEN (wd2.waiting_tasks_count - wd1.waiting_tasks_count) > 0 + THEN + CAST((wd2.wait_time_ms-wd1.wait_time_ms)/ + (1.0*(wd2.waiting_tasks_count - wd1.waiting_tasks_count)) AS NUMERIC(12,1)) + ELSE 0 END AS [Avg ms Per Wait], + N'https://www.sqlskills.com/help/waits/' + LOWER(wd1.wait_type) + '/' AS URL + FROM max_batch b + JOIN #WaitStats wd2 ON + wd2.SampleTime =b.SampleTime + JOIN #WaitStats wd1 ON + wd1.wait_type=wd2.wait_type AND + wd2.SampleTime > wd1.SampleTime + CROSS APPLY (SELECT SUM(1) AS cpu_count FROM sys.dm_os_schedulers WHERE status = 'VISIBLE ONLINE' AND is_online = 1) AS cores + CROSS APPLY (SELECT + CAST((wd2.wait_time_ms-wd1.wait_time_ms)/1000. AS NUMERIC(12,1)) AS [Wait Time (Seconds)], + CAST((wd2.signal_wait_time_ms - wd1.signal_wait_time_ms)/1000. AS NUMERIC(12,1)) AS [Signal Wait Time (Seconds)]) AS c + LEFT OUTER JOIN ##WaitCategories wcat ON wd1.wait_type = wcat.WaitType + WHERE (wd2.waiting_tasks_count - wd1.waiting_tasks_count) > 0 + AND wd2.wait_time_ms-wd1.wait_time_ms > 0 + ORDER BY [Wait Time (Seconds)] DESC; + END; - FETCH NEXT FROM dbs INTO @db, @dbq; + ------------------------- + --What happened: #FileStats + ------------------------- + WITH readstats AS ( + SELECT 'PHYSICAL READS' AS Pattern, + ROW_NUMBER() OVER (ORDER BY wd2.avg_stall_read_ms DESC) AS StallRank, + wd2.SampleTime AS [Sample Time], + DATEDIFF(ss,wd1.SampleTime, wd2.SampleTime) AS [Sample (seconds)], + wd1.DatabaseName , + wd1.FileLogicalName AS [File Name], + UPPER(SUBSTRING(wd1.PhysicalName, 1, 2)) AS [Drive] , + wd1.SizeOnDiskMB , + ( wd2.num_of_reads - wd1.num_of_reads ) AS [# Reads/Writes], + CASE WHEN wd2.num_of_reads - wd1.num_of_reads > 0 + THEN CAST(( wd2.bytes_read - wd1.bytes_read)/1024./1024. AS NUMERIC(21,1)) + ELSE 0 + END AS [MB Read/Written], + wd2.avg_stall_read_ms AS [Avg Stall (ms)], + wd1.PhysicalName AS [file physical name] + FROM #FileStats wd2 + JOIN #FileStats wd1 ON wd2.SampleTime > wd1.SampleTime + AND wd1.DatabaseID = wd2.DatabaseID + AND wd1.FileID = wd2.FileID + ), + writestats AS ( + SELECT + 'PHYSICAL WRITES' AS Pattern, + ROW_NUMBER() OVER (ORDER BY wd2.avg_stall_write_ms DESC) AS StallRank, + wd2.SampleTime AS [Sample Time], + DATEDIFF(ss,wd1.SampleTime, wd2.SampleTime) AS [Sample (seconds)], + wd1.DatabaseName , + wd1.FileLogicalName AS [File Name], + UPPER(SUBSTRING(wd1.PhysicalName, 1, 2)) AS [Drive] , + wd1.SizeOnDiskMB , + ( wd2.num_of_writes - wd1.num_of_writes ) AS [# Reads/Writes], + CASE WHEN wd2.num_of_writes - wd1.num_of_writes > 0 + THEN CAST(( wd2.bytes_written - wd1.bytes_written)/1024./1024. AS NUMERIC(21,1)) + ELSE 0 + END AS [MB Read/Written], + wd2.avg_stall_write_ms AS [Avg Stall (ms)], + wd1.PhysicalName AS [file physical name] + FROM #FileStats wd2 + JOIN #FileStats wd1 ON wd2.SampleTime > wd1.SampleTime + AND wd1.DatabaseID = wd2.DatabaseID + AND wd1.FileID = wd2.FileID + ) + SELECT + Pattern, [Sample Time], [Sample (seconds)], [File Name], [Drive], [# Reads/Writes],[MB Read/Written],[Avg Stall (ms)], [file physical name], [StallRank] + FROM readstats + WHERE StallRank <=20 AND [MB Read/Written] > 0 + UNION ALL + SELECT Pattern, [Sample Time], [Sample (seconds)], [File Name], [Drive], [# Reads/Writes],[MB Read/Written],[Avg Stall (ms)], [file physical name], [StallRank] + FROM writestats + WHERE StallRank <=20 AND [MB Read/Written] > 0 + ORDER BY Pattern, StallRank; - DECLARE @msg1 nvarchar(512) = N'Could not run against %s : %s.', - @msg2 nvarchar(max); - WHILE @@FETCH_STATUS <> -1 - BEGIN - SET @thisdb = CASE WHEN @suppress_quotename = 1 THEN @db ELSE @dbq END; - SET @cmd = REPLACE(@command, @replace_character, REPLACE(@thisdb,'''','''''')); + ------------------------- + --What happened: #PerfmonStats + ------------------------- - BEGIN TRY - IF @print_dbname = 1 - BEGIN - PRINT N'/* ' + @thisdb + N' */'; - END + SELECT 'PERFMON' AS Pattern, pLast.[object_name], pLast.counter_name, pLast.instance_name, + pFirst.SampleTime AS FirstSampleTime, pFirst.cntr_value AS FirstSampleValue, + pLast.SampleTime AS LastSampleTime, pLast.cntr_value AS LastSampleValue, + pLast.cntr_value - pFirst.cntr_value AS ValueDelta, + ((1.0 * pLast.cntr_value - pFirst.cntr_value) / DATEDIFF(ss, pFirst.SampleTime, pLast.SampleTime)) AS ValuePerSecond + FROM #PerfmonStats pLast + INNER JOIN #PerfmonStats pFirst ON pFirst.[object_name] = pLast.[object_name] AND pFirst.counter_name = pLast.counter_name AND (pFirst.instance_name = pLast.instance_name OR (pFirst.instance_name IS NULL AND pLast.instance_name IS NULL)) + AND pLast.ID > pFirst.ID + WHERE pLast.cntr_value <> pFirst.cntr_value + ORDER BY Pattern, pLast.[object_name], pLast.counter_name, pLast.instance_name; - IF @select_dbname = 1 - BEGIN - SELECT [ineachdb current database] = @thisdb; - END - IF 1 IN (@print_command, @print_command_only) - BEGIN - PRINT N'/* For ' + @thisdb + ': */' + @cr + @cr + @cmd + @cr + @cr; - END + ------------------------- + --What happened: #QueryStats + ------------------------- + IF @CheckProcedureCache = 1 + BEGIN + + SELECT qsNow.*, qsFirst.* + FROM #QueryStats qsNow + INNER JOIN #QueryStats qsFirst ON qsNow.[sql_handle] = qsFirst.[sql_handle] AND qsNow.statement_start_offset = qsFirst.statement_start_offset AND qsNow.statement_end_offset = qsFirst.statement_end_offset AND qsNow.plan_generation_num = qsFirst.plan_generation_num AND qsNow.plan_handle = qsFirst.plan_handle AND qsFirst.Pass = 1 + WHERE qsNow.Pass = 2; + END; + ELSE + BEGIN + SELECT 'Plan Cache' AS [Pattern], 'Plan cache not analyzed' AS [Finding], 'Use @CheckProcedureCache = 1 or run sp_BlitzCache for more analysis' AS [More Info], CONVERT(XML, @StockDetailsHeader + 'firstresponderkit.org' + @StockDetailsFooter) AS [Details]; + END; + END; - IF COALESCE(@print_command_only,0) = 0 - BEGIN - SET @exec = @dbq + @sx; - EXEC @exec @cmd; - END - END TRY + DROP TABLE #BlitzFirstResults; - BEGIN CATCH - SET @msg2 = ERROR_MESSAGE(); - RAISERROR(@msg1, 1, 0, @db, @msg2); - END CATCH + /* What's running right now? This is the first and last result set. */ + IF @SinceStartup = 0 AND @Seconds > 0 AND @ExpertMode = 1 AND @OutputType <> 'NONE' + BEGIN + IF OBJECT_ID('master.dbo.sp_BlitzWho') IS NULL AND OBJECT_ID('dbo.sp_BlitzWho') IS NULL + BEGIN + PRINT N'sp_BlitzWho is not installed in the current database_files. You can get a copy from http://FirstResponderKit.org'; + END; + ELSE + BEGIN + EXEC (@BlitzWho); + END; + END; /* IF @SinceStartup = 0 AND @Seconds > 0 AND @ExpertMode = 1 AND @OutputType <> 'NONE' - What's running right now? This is the first and last result set. */ - FETCH NEXT FROM dbs INTO @db, @dbq; - END +END; /* IF @LogMessage IS NULL */ +END; /* ELSE IF @OutputType = 'SCHEMA' */ - CLOSE dbs; - DEALLOCATE dbs; -END +SET NOCOUNT OFF; GO -IF (OBJECT_ID('dbo.SqlServerVersions') IS NULL) -BEGIN - - CREATE TABLE dbo.SqlServerVersions - ( - MajorVersionNumber tinyint not null, - MinorVersionNumber smallint not null, - Branch varchar(34) not null, - [Url] varchar(99) not null, - ReleaseDate date not null, - MainstreamSupportEndDate date not null, - ExtendedSupportEndDate date not null, - MajorVersionName varchar(19) not null, - MinorVersionName varchar(67) not null, - CONSTRAINT PK_SqlServerVersions PRIMARY KEY CLUSTERED - ( - MajorVersionNumber ASC, - MinorVersionNumber ASC, - ReleaseDate ASC - ) - ); - - EXEC sys.sp_addextendedproperty @name=N'Description', @value=N'The major version number.' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'SqlServerVersions', @level2type=N'COLUMN',@level2name=N'MajorVersionNumber' - EXEC sys.sp_addextendedproperty @name=N'Description', @value=N'The minor version number.' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'SqlServerVersions', @level2type=N'COLUMN',@level2name=N'MinorVersionNumber' - EXEC sys.sp_addextendedproperty @name=N'Description', @value=N'The update level of the build. CU indicates a cumulative update. SP indicates a service pack. RTM indicates Release To Manufacturer. GDR indicates a General Distribution Release. QFE indicates Quick Fix Engineering (aka hotfix).' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'SqlServerVersions', @level2type=N'COLUMN',@level2name=N'Branch' - EXEC sys.sp_addextendedproperty @name=N'Description', @value=N'A link to the KB article for a version.' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'SqlServerVersions', @level2type=N'COLUMN',@level2name=N'Url' - EXEC sys.sp_addextendedproperty @name=N'Description', @value=N'The date the version was publicly released.' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'SqlServerVersions', @level2type=N'COLUMN',@level2name=N'ReleaseDate' - EXEC sys.sp_addextendedproperty @name=N'Description', @value=N'The date main stream Microsoft support ends for the version.' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'SqlServerVersions', @level2type=N'COLUMN',@level2name=N'MainstreamSupportEndDate' - EXEC sys.sp_addextendedproperty @name=N'Description', @value=N'The date extended Microsoft support ends for the version.' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'SqlServerVersions', @level2type=N'COLUMN',@level2name=N'ExtendedSupportEndDate' - EXEC sys.sp_addextendedproperty @name=N'Description', @value=N'The major version name.' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'SqlServerVersions', @level2type=N'COLUMN',@level2name=N'MajorVersionName' - EXEC sys.sp_addextendedproperty @name=N'Description', @value=N'The minor version name.' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'SqlServerVersions', @level2type=N'COLUMN',@level2name=N'MinorVersionName' - EXEC sys.sp_addextendedproperty @name=N'Description', @value=N'A reference for SQL Server major and minor versions.' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'SqlServerVersions' -END; -GO +/* How to run it: +EXEC dbo.sp_BlitzFirst -DELETE FROM dbo.SqlServerVersions; +With extra diagnostic info: +EXEC dbo.sp_BlitzFirst @ExpertMode = 1; -INSERT INTO dbo.SqlServerVersions - (MajorVersionNumber, MinorVersionNumber, Branch, [Url], ReleaseDate, MainstreamSupportEndDate, ExtendedSupportEndDate, MajorVersionName, MinorVersionName) -VALUES - (15, 4073, 'CU8 GDR', 'https://support.microsoft.com/en-us/help/4583459', '2021-01-12', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 8 GDR '), - (15, 4073, 'CU8', 'https://support.microsoft.com/en-us/help/4577194', '2020-10-01', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 8 '), - (15, 4063, 'CU7', 'https://support.microsoft.com/en-us/help/4570012', '2020-09-02', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 7 '), - (15, 4053, 'CU6', 'https://support.microsoft.com/en-us/help/4563110', '2020-08-04', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 6 '), - (15, 4043, 'CU5', 'https://support.microsoft.com/en-us/help/4548597', '2020-06-22', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 5 '), - (15, 4033, 'CU4', 'https://support.microsoft.com/en-us/help/4548597', '2020-03-31', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 4 '), - (15, 4023, 'CU3', 'https://support.microsoft.com/en-us/help/4538853', '2020-03-12', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 3 '), - (15, 4013, 'CU2', 'https://support.microsoft.com/en-us/help/4536075', '2020-02-13', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 2 '), - (15, 4003, 'CU1', 'https://support.microsoft.com/en-us/help/4527376', '2020-01-07', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 1 '), - (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 '), - (14, 3370, 'RTM CU22 GDR', 'https://support.microsoft.com/en-us/help/4583457', '2021-01-12', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 22 GDR'), - (14, 3356, 'RTM CU22', 'https://support.microsoft.com/en-us/help/4577467', '2020-09-10', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 22'), - (14, 3335, 'RTM CU21', 'https://support.microsoft.com/en-us/help/4557397', '2020-07-01', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 21'), - (14, 3294, 'RTM CU20', 'https://support.microsoft.com/en-us/help/4541283', '2020-04-07', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 20'), - (14, 3257, 'RTM CU19', 'https://support.microsoft.com/en-us/help/4535007', '2020-02-05', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 19'), - (14, 3257, 'RTM CU18', 'https://support.microsoft.com/en-us/help/4527377', '2019-12-09', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 18'), - (14, 3238, 'RTM CU17', 'https://support.microsoft.com/en-us/help/4515579', '2019-10-08', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 17'), - (14, 3223, 'RTM CU16', 'https://support.microsoft.com/en-us/help/4508218', '2019-08-01', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 16'), - (14, 3162, 'RTM CU15', 'https://support.microsoft.com/en-us/help/4498951', '2019-05-24', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 15'), - (14, 3076, 'RTM CU14', 'https://support.microsoft.com/en-us/help/4484710', '2019-03-25', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 14'), - (14, 3048, 'RTM CU13', 'https://support.microsoft.com/en-us/help/4466404', '2018-12-18', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 13'), - (14, 3045, 'RTM CU12', 'https://support.microsoft.com/en-us/help/4464082', '2018-10-24', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 12'), - (14, 3038, 'RTM CU11', 'https://support.microsoft.com/en-us/help/4462262', '2018-09-20', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 11'), - (14, 3037, 'RTM CU10', 'https://support.microsoft.com/en-us/help/4524334', '2018-08-27', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 10'), - (14, 3030, 'RTM CU9', 'https://support.microsoft.com/en-us/help/4515435', '2018-07-18', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 9'), - (14, 3029, 'RTM CU8', 'https://support.microsoft.com/en-us/help/4338363', '2018-06-21', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 8'), - (14, 3026, 'RTM CU7', 'https://support.microsoft.com/en-us/help/4229789', '2018-05-23', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 7'), - (14, 3025, 'RTM CU6', 'https://support.microsoft.com/en-us/help/4101464', '2018-04-17', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 6'), - (14, 3023, 'RTM CU5', 'https://support.microsoft.com/en-us/help/4092643', '2018-03-20', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 5'), - (14, 3022, 'RTM CU4', 'https://support.microsoft.com/en-us/help/4056498', '2018-02-20', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 4'), - (14, 3015, 'RTM CU3', 'https://support.microsoft.com/en-us/help/4052987', '2018-01-04', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 3'), - (14, 3008, 'RTM CU2', 'https://support.microsoft.com/en-us/help/4052574', '2017-11-28', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 2'), - (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 '), - (13, 5865, 'SP2 CU15 GDR', 'https://support.microsoft.com/en-us/help/4583461', '2021-01-12', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 15 GDR'), - (13, 5850, 'SP2 CU15', 'https://support.microsoft.com/en-us/help/4577775', '2020-09-28', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 15'), - (13, 5830, 'SP2 CU14', 'https://support.microsoft.com/en-us/help/4564903', '2020-08-06', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 14'), - (13, 5820, 'SP2 CU13', 'https://support.microsoft.com/en-us/help/4549825', '2020-05-28', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 13'), - (13, 5698, 'SP2 CU12', 'https://support.microsoft.com/en-us/help/4536648', '2020-02-25', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 12'), - (13, 5598, 'SP2 CU11', 'https://support.microsoft.com/en-us/help/4527378', '2019-12-09', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 11'), - (13, 5492, 'SP2 CU10', 'https://support.microsoft.com/en-us/help/4505830', '2019-10-08', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 10'), - (13, 5479, 'SP2 CU9', 'https://support.microsoft.com/en-us/help/4505830', '2019-09-30', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 9'), - (13, 5426, 'SP2 CU8', 'https://support.microsoft.com/en-us/help/4505830', '2019-07-31', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 8'), - (13, 5337, 'SP2 CU7', 'https://support.microsoft.com/en-us/help/4495256', '2019-05-23', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 7'), - (13, 5292, 'SP2 CU6', 'https://support.microsoft.com/en-us/help/4488536', '2019-03-19', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 6'), - (13, 5264, 'SP2 CU5', 'https://support.microsoft.com/en-us/help/4475776', '2019-01-23', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 5'), - (13, 5233, 'SP2 CU4', 'https://support.microsoft.com/en-us/help/4464106', '2018-11-13', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 4'), - (13, 5216, 'SP2 CU3', 'https://support.microsoft.com/en-us/help/4458871', '2018-09-20', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 3'), - (13, 5201, 'SP2 CU2 + Security Update', 'https://support.microsoft.com/en-us/help/4458621', '2018-08-21', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 2 + Security Update'), - (13, 5153, 'SP2 CU2', 'https://support.microsoft.com/en-us/help/4340355', '2018-07-16', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 2'), - (13, 5149, 'SP2 CU1', 'https://support.microsoft.com/en-us/help/4135048', '2018-05-30', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 1'), - (13, 5103, 'SP2 GDR', 'https://support.microsoft.com/en-us/help/4583460', '2021-01-12', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 GDR'), - (13, 5026, 'SP2 ', 'https://support.microsoft.com/en-us/help/4052908', '2018-04-24', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 '), - (13, 4574, 'SP1 CU15', 'https://support.microsoft.com/en-us/help/4495257', '2019-05-16', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 15'), - (13, 4560, 'SP1 CU14', 'https://support.microsoft.com/en-us/help/4488535', '2019-03-19', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 14'), - (13, 4550, 'SP1 CU13', 'https://support.microsoft.com/en-us/help/4475775', '2019-01-23', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 13'), - (13, 4541, 'SP1 CU12', 'https://support.microsoft.com/en-us/help/4464343', '2018-11-13', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 12'), - (13, 4528, 'SP1 CU11', 'https://support.microsoft.com/en-us/help/4459676', '2018-09-17', '2019-07-09', '2019-07-09', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 11'), - (13, 4514, 'SP1 CU10', 'https://support.microsoft.com/en-us/help/4341569', '2018-07-16', '2019-07-09', '2019-07-09', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 10'), - (13, 4502, 'SP1 CU9', 'https://support.microsoft.com/en-us/help/4100997', '2018-05-30', '2019-07-09', '2019-07-09', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 9'), - (13, 4474, 'SP1 CU8', 'https://support.microsoft.com/en-us/help/4077064', '2018-03-19', '2019-07-09', '2019-07-09', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 8'), - (13, 4466, 'SP1 CU7', 'https://support.microsoft.com/en-us/help/4057119', '2018-01-04', '2019-07-09', '2019-07-09', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 7'), - (13, 4457, 'SP1 CU6', 'https://support.microsoft.com/en-us/help/4037354', '2017-11-20', '2019-07-09', '2019-07-09', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 6'), - (13, 4451, 'SP1 CU5', 'https://support.microsoft.com/en-us/help/4024305', '2017-09-18', '2019-07-09', '2019-07-09', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 5'), - (13, 4446, 'SP1 CU4', 'https://support.microsoft.com/en-us/help/4024305', '2017-08-08', '2019-07-09', '2019-07-09', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 4'), - (13, 4435, 'SP1 CU3', 'https://support.microsoft.com/en-us/help/4019916', '2017-05-15', '2019-07-09', '2019-07-09', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 3'), - (13, 4422, 'SP1 CU2', 'https://support.microsoft.com/en-us/help/4013106', '2017-03-20', '2019-07-09', '2019-07-09', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 2'), - (13, 4411, 'SP1 CU1', 'https://support.microsoft.com/en-us/help/3208177', '2017-01-17', '2019-07-09', '2019-07-09', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 1'), - (13, 4224, 'SP1 CU10 + Security Update', 'https://support.microsoft.com/en-us/help/4458842', '2018-08-22', '2019-07-09', '2019-07-09', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 10 + Security Update'), - (13, 4001, 'SP1 ', 'https://support.microsoft.com/en-us/help/3182545 ', '2016-11-16', '2019-07-09', '2019-07-09', 'SQL Server 2016', 'Service Pack 1 '), - (13, 2216, 'RTM CU9', 'https://support.microsoft.com/en-us/help/4037357', '2017-11-20', '2018-01-09', '2018-01-09', 'SQL Server 2016', 'RTM Cumulative Update 9'), - (13, 2213, 'RTM CU8', 'https://support.microsoft.com/en-us/help/4024304', '2017-09-18', '2018-01-09', '2018-01-09', 'SQL Server 2016', 'RTM Cumulative Update 8'), - (13, 2210, 'RTM CU7', 'https://support.microsoft.com/en-us/help/4024304', '2017-08-08', '2018-01-09', '2018-01-09', 'SQL Server 2016', 'RTM Cumulative Update 7'), - (13, 2204, 'RTM CU6', 'https://support.microsoft.com/en-us/help/4019914', '2017-05-15', '2018-01-09', '2018-01-09', 'SQL Server 2016', 'RTM Cumulative Update 6'), - (13, 2197, 'RTM CU5', 'https://support.microsoft.com/en-us/help/4013105', '2017-03-20', '2018-01-09', '2018-01-09', 'SQL Server 2016', 'RTM Cumulative Update 5'), - (13, 2193, 'RTM CU4', 'https://support.microsoft.com/en-us/help/3205052 ', '2017-01-17', '2018-01-09', '2018-01-09', 'SQL Server 2016', 'RTM Cumulative Update 4'), - (13, 2186, 'RTM CU3', 'https://support.microsoft.com/en-us/help/3205413 ', '2016-11-16', '2018-01-09', '2018-01-09', 'SQL Server 2016', 'RTM Cumulative Update 3'), - (13, 2164, 'RTM CU2', 'https://support.microsoft.com/en-us/help/3182270 ', '2016-09-22', '2018-01-09', '2018-01-09', 'SQL Server 2016', 'RTM Cumulative Update 2'), - (13, 2149, 'RTM CU1', 'https://support.microsoft.com/en-us/help/3164674 ', '2016-07-25', '2018-01-09', '2018-01-09', 'SQL Server 2016', 'RTM Cumulative Update 1'), - (13, 1601, 'RTM ', '', '2016-06-01', '2019-01-09', '2019-01-09', 'SQL Server 2016', 'RTM '), - (12, 6433, 'SP3 CU4 GDR', 'https://support.microsoft.com/en-us/help/4583462', '2021-01-12', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 4 GDR'), - (12, 6372, 'SP3 CU4 GDR', 'https://support.microsoft.com/en-us/help/4535288', '2020-02-11', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 4 GDR'), - (12, 6329, 'SP3 CU4', 'https://support.microsoft.com/en-us/help/4500181', '2019-07-29', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 4'), - (12, 6259, 'SP3 CU3', 'https://support.microsoft.com/en-us/help/4491539', '2019-04-16', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 3'), - (12, 6214, 'SP3 CU2', 'https://support.microsoft.com/en-us/help/4482960', '2019-02-19', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 2'), - (12, 6205, 'SP3 CU1', 'https://support.microsoft.com/en-us/help/4470220', '2018-12-12', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 1'), - (12, 6164, 'SP3 GDR', 'https://support.microsoft.com/en-us/help/4583463', '2021-01-12', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 GDR'), - (12, 6024, 'SP3 ', 'https://support.microsoft.com/en-us/help/4022619', '2018-10-30', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 '), - (12, 5687, 'SP2 CU18', 'https://support.microsoft.com/en-us/help/4500180', '2019-07-29', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 18'), - (12, 5632, 'SP2 CU17', 'https://support.microsoft.com/en-us/help/4491540', '2019-04-16', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 17'), - (12, 5626, 'SP2 CU16', 'https://support.microsoft.com/en-us/help/4482967', '2019-02-19', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 16'), - (12, 5605, 'SP2 CU15', 'https://support.microsoft.com/en-us/help/4469137', '2018-12-12', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 15'), - (12, 5600, 'SP2 CU14', 'https://support.microsoft.com/en-us/help/4459860', '2018-10-15', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 14'), - (12, 5590, 'SP2 CU13', 'https://support.microsoft.com/en-us/help/4456287', '2018-08-27', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 13'), - (12, 5589, 'SP2 CU12', 'https://support.microsoft.com/en-us/help/4130489', '2018-06-18', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 12'), - (12, 5579, 'SP2 CU11', 'https://support.microsoft.com/en-us/help/4077063', '2018-03-19', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 11'), - (12, 5571, 'SP2 CU10', 'https://support.microsoft.com/en-us/help/4052725', '2018-01-16', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 10'), - (12, 5563, 'SP2 CU9', 'https://support.microsoft.com/en-us/help/4055557', '2017-12-18', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 9'), - (12, 5557, 'SP2 CU8', 'https://support.microsoft.com/en-us/help/4037356', '2017-10-16', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 8'), - (12, 5556, 'SP2 CU7', 'https://support.microsoft.com/en-us/help/4032541', '2017-08-28', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 7'), - (12, 5553, 'SP2 CU6', 'https://support.microsoft.com/en-us/help/4019094', '2017-08-08', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 6'), - (12, 5546, 'SP2 CU5', 'https://support.microsoft.com/en-us/help/4013098', '2017-04-17', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 5'), - (12, 5540, 'SP2 CU4', 'https://support.microsoft.com/en-us/help/4010394', '2017-02-21', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 4'), - (12, 5538, 'SP2 CU3', 'https://support.microsoft.com/en-us/help/3204388 ', '2016-12-19', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 3'), - (12, 5522, 'SP2 CU2', 'https://support.microsoft.com/en-us/help/3188778 ', '2016-10-17', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 2'), - (12, 5511, 'SP2 CU1', 'https://support.microsoft.com/en-us/help/3178925 ', '2016-08-25', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 1'), - (12, 5000, 'SP2 ', 'https://support.microsoft.com/en-us/help/3171021 ', '2016-07-11', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 '), - (12, 4522, 'SP1 CU13', 'https://support.microsoft.com/en-us/help/4019099', '2017-08-08', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 Cumulative Update 13'), - (12, 4511, 'SP1 CU12', 'https://support.microsoft.com/en-us/help/4017793', '2017-04-17', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 Cumulative Update 12'), - (12, 4502, 'SP1 CU11', 'https://support.microsoft.com/en-us/help/4010392', '2017-02-21', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 Cumulative Update 11'), - (12, 4491, 'SP1 CU10', 'https://support.microsoft.com/en-us/help/3204399 ', '2016-12-19', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 Cumulative Update 10'), - (12, 4474, 'SP1 CU9', 'https://support.microsoft.com/en-us/help/3186964 ', '2016-10-17', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 Cumulative Update 9'), - (12, 4468, 'SP1 CU8', 'https://support.microsoft.com/en-us/help/3174038 ', '2016-08-15', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 Cumulative Update 8'), - (12, 4459, 'SP1 CU7', 'https://support.microsoft.com/en-us/help/3162659 ', '2016-06-20', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 Cumulative Update 7'), - (12, 4457, 'SP1 CU6', 'https://support.microsoft.com/en-us/help/3167392 ', '2016-05-30', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 Cumulative Update 6'), - (12, 4449, 'SP1 CU6', 'https://support.microsoft.com/en-us/help/3144524', '2016-04-18', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 Cumulative Update 6'), - (12, 4438, 'SP1 CU5', 'https://support.microsoft.com/en-us/help/3130926', '2016-02-22', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 Cumulative Update 5'), - (12, 4436, 'SP1 CU4', 'https://support.microsoft.com/en-us/help/3106660', '2015-12-21', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 Cumulative Update 4'), - (12, 4427, 'SP1 CU3', 'https://support.microsoft.com/en-us/help/3094221', '2015-10-19', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 Cumulative Update 3'), - (12, 4422, 'SP1 CU2', 'https://support.microsoft.com/en-us/help/3075950', '2015-08-17', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 Cumulative Update 2'), - (12, 4416, 'SP1 CU1', 'https://support.microsoft.com/en-us/help/3067839', '2015-06-19', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 Cumulative Update 1'), - (12, 4213, 'SP1 MS15-058: GDR Security Update', 'https://support.microsoft.com/en-us/help/3070446', '2015-07-14', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 MS15-058: GDR Security Update'), - (12, 4100, 'SP1 ', 'https://support.microsoft.com/en-us/help/3058865', '2015-05-04', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 '), - (12, 2569, 'RTM CU14', 'https://support.microsoft.com/en-us/help/3158271 ', '2016-06-20', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM Cumulative Update 14'), - (12, 2568, 'RTM CU13', 'https://support.microsoft.com/en-us/help/3144517', '2016-04-18', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM Cumulative Update 13'), - (12, 2564, 'RTM CU12', 'https://support.microsoft.com/en-us/help/3130923', '2016-02-22', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM Cumulative Update 12'), - (12, 2560, 'RTM CU11', 'https://support.microsoft.com/en-us/help/3106659', '2015-12-21', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM Cumulative Update 11'), - (12, 2556, 'RTM CU10', 'https://support.microsoft.com/en-us/help/3094220', '2015-10-19', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM Cumulative Update 10'), - (12, 2553, 'RTM CU9', 'https://support.microsoft.com/en-us/help/3075949', '2015-08-17', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM Cumulative Update 9'), - (12, 2548, 'RTM MS15-058: QFE Security Update', 'https://support.microsoft.com/en-us/help/3045323', '2015-07-14', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM MS15-058: QFE Security Update'), - (12, 2546, 'RTM CU8', 'https://support.microsoft.com/en-us/help/3067836', '2015-06-19', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM Cumulative Update 8'), - (12, 2495, 'RTM CU7', 'https://support.microsoft.com/en-us/help/3046038', '2015-04-20', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM Cumulative Update 7'), - (12, 2480, 'RTM CU6', 'https://support.microsoft.com/en-us/help/3031047', '2015-02-16', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM Cumulative Update 6'), - (12, 2456, 'RTM CU5', 'https://support.microsoft.com/en-us/help/3011055', '2014-12-17', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM Cumulative Update 5'), - (12, 2430, 'RTM CU4', 'https://support.microsoft.com/en-us/help/2999197', '2014-10-21', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM Cumulative Update 4'), - (12, 2402, 'RTM CU3', 'https://support.microsoft.com/en-us/help/2984923', '2014-08-18', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM Cumulative Update 3'), - (12, 2381, 'RTM MS14-044: QFE Security Update', 'https://support.microsoft.com/en-us/help/2977316', '2014-08-12', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM MS14-044: QFE Security Update'), - (12, 2370, 'RTM CU2', 'https://support.microsoft.com/en-us/help/2967546', '2014-06-27', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM Cumulative Update 2'), - (12, 2342, 'RTM CU1', 'https://support.microsoft.com/en-us/help/2931693', '2014-04-21', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM Cumulative Update 1'), - (12, 2269, 'RTM MS15-058: GDR Security Update ', 'https://support.microsoft.com/en-us/help/3045324', '2015-07-14', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM MS15-058: GDR Security Update '), - (12, 2254, 'RTM MS14-044: GDR Security Update', 'https://support.microsoft.com/en-us/help/2977315', '2014-08-12', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM MS14-044: GDR Security Update'), - (12, 2000, 'RTM ', '', '2014-04-01', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM '), - (11, 7507, 'SP4 GDR Security Update', 'https://support.microsoft.com/en-us/help/4583465', '2021-01-12', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'Service Pack 4 GDR Security Update for CVE-2021-1636'), - (11, 7493, 'SP4 GDR Security Update', 'https://support.microsoft.com/en-us/help/4532098', '2020-02-11', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'Service Pack 4 GDR Security Update for CVE-2020-0618'), - (11, 7469, 'SP4 On-Demand Hotfix Update', 'https://support.microsoft.com/en-us/help/4091266', '2018-03-28', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'Service Pack 4 SP4 On-Demand Hotfix Update'), - (11, 7462, 'SP4 ADV180002: GDR Security Update', 'https://support.microsoft.com/en-us/help/4057116', '2018-01-12', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'Service Pack 4 ADV180002: GDR Security Update'), - (11, 7001, 'SP4 ', 'https://support.microsoft.com/en-us/help/4018073', '2017-10-02', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'Service Pack 4 '), - (11, 6607, 'SP3 CU10', 'https://support.microsoft.com/en-us/help/4025925', '2017-08-08', '2018-10-09', '2018-10-09', 'SQL Server 2012', 'Service Pack 3 Cumulative Update 10'), - (11, 6598, 'SP3 CU9', 'https://support.microsoft.com/en-us/help/4016762', '2017-05-15', '2018-10-09', '2018-10-09', 'SQL Server 2012', 'Service Pack 3 Cumulative Update 9'), - (11, 6594, 'SP3 CU8', 'https://support.microsoft.com/en-us/help/3205051 ', '2017-03-20', '2018-10-09', '2018-10-09', 'SQL Server 2012', 'Service Pack 3 Cumulative Update 8'), - (11, 6579, 'SP3 CU7', 'https://support.microsoft.com/en-us/help/3205051 ', '2017-01-17', '2018-10-09', '2018-10-09', 'SQL Server 2012', 'Service Pack 3 Cumulative Update 7'), - (11, 6567, 'SP3 CU6', 'https://support.microsoft.com/en-us/help/3194992 ', '2016-11-17', '2018-10-09', '2018-10-09', 'SQL Server 2012', 'Service Pack 3 Cumulative Update 6'), - (11, 6544, 'SP3 CU5', 'https://support.microsoft.com/en-us/help/3180915 ', '2016-09-19', '2018-10-09', '2018-10-09', 'SQL Server 2012', 'Service Pack 3 Cumulative Update 5'), - (11, 6540, 'SP3 CU4', 'https://support.microsoft.com/en-us/help/3165264 ', '2016-07-18', '2018-10-09', '2018-10-09', 'SQL Server 2012', 'Service Pack 3 Cumulative Update 4'), - (11, 6537, 'SP3 CU3', 'https://support.microsoft.com/en-us/help/3152635 ', '2016-05-16', '2018-10-09', '2018-10-09', 'SQL Server 2012', 'Service Pack 3 Cumulative Update 3'), - (11, 6523, 'SP3 CU2', 'https://support.microsoft.com/en-us/help/3137746', '2016-03-21', '2018-10-09', '2018-10-09', 'SQL Server 2012', 'Service Pack 3 Cumulative Update 2'), - (11, 6518, 'SP3 CU1', 'https://support.microsoft.com/en-us/help/3123299', '2016-01-19', '2018-10-09', '2018-10-09', 'SQL Server 2012', 'Service Pack 3 Cumulative Update 1'), - (11, 6020, 'SP3 ', 'https://support.microsoft.com/en-us/help/3072779', '2015-11-20', '2018-10-09', '2018-10-09', 'SQL Server 2012', 'Service Pack 3 '), - (11, 5678, 'SP2 CU16', 'https://support.microsoft.com/en-us/help/3205416 ', '2016-11-17', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 16'), - (11, 5676, 'SP2 CU15', 'https://support.microsoft.com/en-us/help/3205416 ', '2016-11-17', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 15'), - (11, 5657, 'SP2 CU14', 'https://support.microsoft.com/en-us/help/3180914 ', '2016-09-19', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 14'), - (11, 5655, 'SP2 CU13', 'https://support.microsoft.com/en-us/help/3165266 ', '2016-07-18', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 13'), - (11, 5649, 'SP2 CU12', 'https://support.microsoft.com/en-us/help/3152637 ', '2016-05-16', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 12'), - (11, 5646, 'SP2 CU11', 'https://support.microsoft.com/en-us/help/3137745', '2016-03-21', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 11'), - (11, 5644, 'SP2 CU10', 'https://support.microsoft.com/en-us/help/3120313', '2016-01-19', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 10'), - (11, 5641, 'SP2 CU9', 'https://support.microsoft.com/en-us/help/3098512', '2015-11-16', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 9'), - (11, 5634, 'SP2 CU8', 'https://support.microsoft.com/en-us/help/3082561', '2015-09-21', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 8'), - (11, 5623, 'SP2 CU7', 'https://support.microsoft.com/en-us/help/3072100', '2015-07-20', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 7'), - (11, 5613, 'SP2 MS15-058: QFE Security Update', 'https://support.microsoft.com/en-us/help/3045319', '2015-07-14', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 MS15-058: QFE Security Update'), - (11, 5592, 'SP2 CU6', 'https://support.microsoft.com/en-us/help/3052468', '2015-05-18', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 6'), - (11, 5582, 'SP2 CU5', 'https://support.microsoft.com/en-us/help/3037255', '2015-03-16', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 5'), - (11, 5569, 'SP2 CU4', 'https://support.microsoft.com/en-us/help/3007556', '2015-01-19', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 4'), - (11, 5556, 'SP2 CU3', 'https://support.microsoft.com/en-us/help/3002049', '2014-11-17', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 3'), - (11, 5548, 'SP2 CU2', 'https://support.microsoft.com/en-us/help/2983175', '2014-09-15', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 2'), - (11, 5532, 'SP2 CU1', 'https://support.microsoft.com/en-us/help/2976982', '2014-07-23', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 1'), - (11, 5343, 'SP2 MS15-058: GDR Security Update', 'https://support.microsoft.com/en-us/help/3045321', '2015-07-14', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 MS15-058: GDR Security Update'), - (11, 5058, 'SP2 ', 'https://support.microsoft.com/en-us/help/2958429', '2014-06-10', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 '), - (11, 3513, 'SP1 MS15-058: QFE Security Update', 'https://support.microsoft.com/en-us/help/3045317', '2015-07-14', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 MS15-058: QFE Security Update'), - (11, 3482, 'SP1 CU13', 'https://support.microsoft.com/en-us/help/3002044', '2014-11-17', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 Cumulative Update 13'), - (11, 3470, 'SP1 CU12', 'https://support.microsoft.com/en-us/help/2991533', '2014-09-15', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 Cumulative Update 12'), - (11, 3460, 'SP1 MS14-044: QFE Security Update ', 'https://support.microsoft.com/en-us/help/2977325', '2014-08-12', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 MS14-044: QFE Security Update '), - (11, 3449, 'SP1 CU11', 'https://support.microsoft.com/en-us/help/2975396', '2014-07-21', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 Cumulative Update 11'), - (11, 3431, 'SP1 CU10', 'https://support.microsoft.com/en-us/help/2954099', '2014-05-19', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 Cumulative Update 10'), - (11, 3412, 'SP1 CU9', 'https://support.microsoft.com/en-us/help/2931078', '2014-03-17', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 Cumulative Update 9'), - (11, 3401, 'SP1 CU8', 'https://support.microsoft.com/en-us/help/2917531', '2014-01-20', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 Cumulative Update 8'), - (11, 3393, 'SP1 CU7', 'https://support.microsoft.com/en-us/help/2894115', '2013-11-18', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 Cumulative Update 7'), - (11, 3381, 'SP1 CU6', 'https://support.microsoft.com/en-us/help/2874879', '2013-09-16', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 Cumulative Update 6'), - (11, 3373, 'SP1 CU5', 'https://support.microsoft.com/en-us/help/2861107', '2013-07-15', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 Cumulative Update 5'), - (11, 3368, 'SP1 CU4', 'https://support.microsoft.com/en-us/help/2833645', '2013-05-30', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 Cumulative Update 4'), - (11, 3349, 'SP1 CU3', 'https://support.microsoft.com/en-us/help/2812412', '2013-03-18', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 Cumulative Update 3'), - (11, 3339, 'SP1 CU2', 'https://support.microsoft.com/en-us/help/2790947', '2013-01-21', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 Cumulative Update 2'), - (11, 3321, 'SP1 CU1', 'https://support.microsoft.com/en-us/help/2765331', '2012-11-20', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 Cumulative Update 1'), - (11, 3156, 'SP1 MS15-058: GDR Security Update', 'https://support.microsoft.com/en-us/help/3045318', '2015-07-14', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 MS15-058: GDR Security Update'), - (11, 3153, 'SP1 MS14-044: GDR Security Update', 'https://support.microsoft.com/en-us/help/2977326', '2014-08-12', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 MS14-044: GDR Security Update'), - (11, 3000, 'SP1 ', 'https://support.microsoft.com/en-us/help/2674319', '2012-11-07', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 '), - (11, 2424, 'RTM CU11', 'https://support.microsoft.com/en-us/help/2908007', '2013-12-16', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'RTM Cumulative Update 11'), - (11, 2420, 'RTM CU10', 'https://support.microsoft.com/en-us/help/2891666', '2013-10-21', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'RTM Cumulative Update 10'), - (11, 2419, 'RTM CU9', 'https://support.microsoft.com/en-us/help/2867319', '2013-08-20', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'RTM Cumulative Update 9'), - (11, 2410, 'RTM CU8', 'https://support.microsoft.com/en-us/help/2844205', '2013-06-17', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'RTM Cumulative Update 8'), - (11, 2405, 'RTM CU7', 'https://support.microsoft.com/en-us/help/2823247', '2013-04-15', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'RTM Cumulative Update 7'), - (11, 2401, 'RTM CU6', 'https://support.microsoft.com/en-us/help/2728897', '2013-02-18', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'RTM Cumulative Update 6'), - (11, 2395, 'RTM CU5', 'https://support.microsoft.com/en-us/help/2777772', '2012-12-17', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'RTM Cumulative Update 5'), - (11, 2383, 'RTM CU4', 'https://support.microsoft.com/en-us/help/2758687', '2012-10-15', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'RTM Cumulative Update 4'), - (11, 2376, 'RTM MS12-070: QFE Security Update', 'https://support.microsoft.com/en-us/help/2716441', '2012-10-09', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'RTM MS12-070: QFE Security Update'), - (11, 2332, 'RTM CU3', 'https://support.microsoft.com/en-us/help/2723749', '2012-08-31', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'RTM Cumulative Update 3'), - (11, 2325, 'RTM CU2', 'https://support.microsoft.com/en-us/help/2703275', '2012-06-18', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'RTM Cumulative Update 2'), - (11, 2316, 'RTM CU1', 'https://support.microsoft.com/en-us/help/2679368', '2012-04-12', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'RTM Cumulative Update 1'), - (11, 2218, 'RTM MS12-070: GDR Security Update', 'https://support.microsoft.com/en-us/help/2716442', '2012-10-09', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'RTM MS12-070: GDR Security Update'), - (11, 2100, 'RTM ', '', '2012-03-06', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'RTM '), - (10, 6529, 'SP3 MS15-058: QFE Security Update', 'https://support.microsoft.com/en-us/help/3045314', '2015-07-14', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'Service Pack 3 MS15-058: QFE Security Update'), - (10, 6220, 'SP3 MS15-058: QFE Security Update', 'https://support.microsoft.com/en-us/help/3045316', '2015-07-14', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'Service Pack 3 MS15-058: QFE Security Update'), - (10, 6000, 'SP3 ', 'https://support.microsoft.com/en-us/help/2979597', '2014-09-26', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'Service Pack 3 '), - (10, 4339, 'SP2 MS15-058: QFE Security Update', 'https://support.microsoft.com/en-us/help/3045312', '2015-07-14', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 MS15-058: QFE Security Update'), - (10, 4321, 'SP2 MS14-044: QFE Security Update', 'https://support.microsoft.com/en-us/help/2977319', '2014-08-14', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 MS14-044: QFE Security Update'), - (10, 4319, 'SP2 CU13', 'https://support.microsoft.com/en-us/help/2967540', '2014-06-30', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 Cumulative Update 13'), - (10, 4305, 'SP2 CU12', 'https://support.microsoft.com/en-us/help/2938478', '2014-04-21', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 Cumulative Update 12'), - (10, 4302, 'SP2 CU11', 'https://support.microsoft.com/en-us/help/2926028', '2014-02-18', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 Cumulative Update 11'), - (10, 4297, 'SP2 CU10', 'https://support.microsoft.com/en-us/help/2908087', '2013-12-17', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 Cumulative Update 10'), - (10, 4295, 'SP2 CU9', 'https://support.microsoft.com/en-us/help/2887606', '2013-10-28', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 Cumulative Update 9'), - (10, 4290, 'SP2 CU8', 'https://support.microsoft.com/en-us/help/2871401', '2013-08-22', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 Cumulative Update 8'), - (10, 4285, 'SP2 CU7', 'https://support.microsoft.com/en-us/help/2844090', '2013-06-17', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 Cumulative Update 7'), - (10, 4279, 'SP2 CU6', 'https://support.microsoft.com/en-us/help/2830140', '2013-04-15', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 Cumulative Update 6'), - (10, 4276, 'SP2 CU5', 'https://support.microsoft.com/en-us/help/2797460', '2013-02-18', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 Cumulative Update 5'), - (10, 4270, 'SP2 CU4', 'https://support.microsoft.com/en-us/help/2777358', '2012-12-17', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 Cumulative Update 4'), - (10, 4266, 'SP2 CU3', 'https://support.microsoft.com/en-us/help/2754552', '2012-10-15', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 Cumulative Update 3'), - (10, 4263, 'SP2 CU2', 'https://support.microsoft.com/en-us/help/2740411', '2012-08-31', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 Cumulative Update 2'), - (10, 4260, 'SP2 CU1', 'https://support.microsoft.com/en-us/help/2720425', '2012-07-24', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 Cumulative Update 1'), - (10, 4042, 'SP2 MS15-058: GDR Security Update', 'https://support.microsoft.com/en-us/help/3045313', '2015-07-14', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 MS15-058: GDR Security Update'), - (10, 4033, 'SP2 MS14-044: GDR Security Update', 'https://support.microsoft.com/en-us/help/2977320', '2014-08-12', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 MS14-044: GDR Security Update'), - (10, 4000, 'SP2 ', 'https://support.microsoft.com/en-us/help/2630458', '2012-07-26', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 '), - (10, 2881, 'SP1 CU14', 'https://support.microsoft.com/en-us/help/2868244', '2013-08-08', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 Cumulative Update 14'), - (10, 2876, 'SP1 CU13', 'https://support.microsoft.com/en-us/help/2855792', '2013-06-17', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 Cumulative Update 13'), - (10, 2874, 'SP1 CU12', 'https://support.microsoft.com/en-us/help/2828727', '2013-04-15', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 Cumulative Update 12'), - (10, 2869, 'SP1 CU11', 'https://support.microsoft.com/en-us/help/2812683', '2013-02-18', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 Cumulative Update 11'), - (10, 2868, 'SP1 CU10', 'https://support.microsoft.com/en-us/help/2783135', '2012-12-17', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 Cumulative Update 10'), - (10, 2866, 'SP1 CU9', 'https://support.microsoft.com/en-us/help/2756574', '2012-10-15', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 Cumulative Update 9'), - (10, 2861, 'SP1 MS12-070: QFE Security Update', 'https://support.microsoft.com/en-us/help/2716439', '2012-10-09', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 MS12-070: QFE Security Update'), - (10, 2822, 'SP1 CU8', 'https://support.microsoft.com/en-us/help/2723743', '2012-08-31', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 Cumulative Update 8'), - (10, 2817, 'SP1 CU7', 'https://support.microsoft.com/en-us/help/2703282', '2012-06-18', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 Cumulative Update 7'), - (10, 2811, 'SP1 CU6', 'https://support.microsoft.com/en-us/help/2679367', '2012-04-16', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 Cumulative Update 6'), - (10, 2806, 'SP1 CU5', 'https://support.microsoft.com/en-us/help/2659694', '2012-02-22', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 Cumulative Update 5'), - (10, 2796, 'SP1 CU4', 'https://support.microsoft.com/en-us/help/2633146', '2011-12-19', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 Cumulative Update 4'), - (10, 2789, 'SP1 CU3', 'https://support.microsoft.com/en-us/help/2591748', '2011-10-17', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 Cumulative Update 3'), - (10, 2772, 'SP1 CU2', 'https://support.microsoft.com/en-us/help/2567714', '2011-08-15', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 Cumulative Update 2'), - (10, 2769, 'SP1 CU1', 'https://support.microsoft.com/en-us/help/2544793', '2011-07-18', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 Cumulative Update 1'), - (10, 2550, 'SP1 MS12-070: GDR Security Update', 'https://support.microsoft.com/en-us/help/2754849', '2012-10-09', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 MS12-070: GDR Security Update'), - (10, 2500, 'SP1 ', 'https://support.microsoft.com/en-us/help/2528583', '2011-07-12', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 '), - (10, 1815, 'RTM CU13', 'https://support.microsoft.com/en-us/help/2679366', '2012-04-16', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM Cumulative Update 13'), - (10, 1810, 'RTM CU12', 'https://support.microsoft.com/en-us/help/2659692', '2012-02-21', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM Cumulative Update 12'), - (10, 1809, 'RTM CU11', 'https://support.microsoft.com/en-us/help/2633145', '2011-12-19', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM Cumulative Update 11'), - (10, 1807, 'RTM CU10', 'https://support.microsoft.com/en-us/help/2591746', '2011-10-17', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM Cumulative Update 10'), - (10, 1804, 'RTM CU9', 'https://support.microsoft.com/en-us/help/2567713', '2011-08-15', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM Cumulative Update 9'), - (10, 1797, 'RTM CU8', 'https://support.microsoft.com/en-us/help/2534352', '2011-06-20', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM Cumulative Update 8'), - (10, 1790, 'RTM MS11-049: QFE Security Update', 'https://support.microsoft.com/en-us/help/2494086', '2011-06-14', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM MS11-049: QFE Security Update'), - (10, 1777, 'RTM CU7', 'https://support.microsoft.com/en-us/help/2507770', '2011-04-18', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM Cumulative Update 7'), - (10, 1765, 'RTM CU6', 'https://support.microsoft.com/en-us/help/2489376', '2011-02-21', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM Cumulative Update 6'), - (10, 1753, 'RTM CU5', 'https://support.microsoft.com/en-us/help/2438347', '2010-12-20', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM Cumulative Update 5'), - (10, 1746, 'RTM CU4', 'https://support.microsoft.com/en-us/help/2345451', '2010-10-18', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM Cumulative Update 4'), - (10, 1734, 'RTM CU3', 'https://support.microsoft.com/en-us/help/2261464', '2010-08-16', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM Cumulative Update 3'), - (10, 1720, 'RTM CU2', 'https://support.microsoft.com/en-us/help/2072493', '2010-06-21', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM Cumulative Update 2'), - (10, 1702, 'RTM CU1', 'https://support.microsoft.com/en-us/help/981355', '2010-05-18', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM Cumulative Update 1'), - (10, 1617, 'RTM MS11-049: GDR Security Update', 'https://support.microsoft.com/en-us/help/2494088', '2011-06-14', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM MS11-049: GDR Security Update'), - (10, 1600, 'RTM ', '', '2010-05-10', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM '), - (10, 6535, 'SP3 MS15-058: QFE Security Update', 'https://support.microsoft.com/en-us/help/3045308', '2015-07-14', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 MS15-058: QFE Security Update'), - (10, 6241, 'SP3 MS15-058: GDR Security Update', 'https://support.microsoft.com/en-us/help/3045311', '2015-07-14', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 MS15-058: GDR Security Update'), - (10, 5890, 'SP3 MS15-058: QFE Security Update', 'https://support.microsoft.com/en-us/help/3045303', '2015-07-14', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 MS15-058: QFE Security Update'), - (10, 5869, 'SP3 MS14-044: QFE Security Update', 'https://support.microsoft.com/en-us/help/2984340, https://support.microsoft.com/en-us/help/2977322', '2014-08-12', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 MS14-044: QFE Security Update'), - (10, 5861, 'SP3 CU17', 'https://support.microsoft.com/en-us/help/2958696', '2014-05-19', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 17'), - (10, 5852, 'SP3 CU16', 'https://support.microsoft.com/en-us/help/2936421', '2014-03-17', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 16'), - (10, 5850, 'SP3 CU15', 'https://support.microsoft.com/en-us/help/2923520', '2014-01-20', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 15'), - (10, 5848, 'SP3 CU14', 'https://support.microsoft.com/en-us/help/2893410', '2013-11-18', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 14'), - (10, 5846, 'SP3 CU13', 'https://support.microsoft.com/en-us/help/2880350', '2013-09-16', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 13'), - (10, 5844, 'SP3 CU12', 'https://support.microsoft.com/en-us/help/2863205', '2013-07-15', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 12'), - (10, 5840, 'SP3 CU11', 'https://support.microsoft.com/en-us/help/2834048', '2013-05-20', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 11'), - (10, 5835, 'SP3 CU10', 'https://support.microsoft.com/en-us/help/2814783', '2013-03-18', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 10'), - (10, 5829, 'SP3 CU9', 'https://support.microsoft.com/en-us/help/2799883', '2013-01-21', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 9'), - (10, 5828, 'SP3 CU8', 'https://support.microsoft.com/en-us/help/2771833', '2012-11-19', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 8'), - (10, 5826, 'SP3 MS12-070: QFE Security Update', 'https://support.microsoft.com/en-us/help/2716435', '2012-10-09', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 MS12-070: QFE Security Update'), - (10, 5794, 'SP3 CU7', 'https://support.microsoft.com/en-us/help/2738350', '2012-09-17', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 7'), - (10, 5788, 'SP3 CU6', 'https://support.microsoft.com/en-us/help/2715953', '2012-07-16', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 6'), - (10, 5785, 'SP3 CU5', 'https://support.microsoft.com/en-us/help/2696626', '2012-05-21', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 5'), - (10, 5775, 'SP3 CU4', 'https://support.microsoft.com/en-us/help/2673383', '2012-03-19', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 4'), - (10, 5770, 'SP3 CU3', 'https://support.microsoft.com/en-us/help/2648098', '2012-01-16', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 3'), - (10, 5768, 'SP3 CU2', 'https://support.microsoft.com/en-us/help/2633143', '2011-11-21', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 2'), - (10, 5766, 'SP3 CU1', 'https://support.microsoft.com/en-us/help/2617146', '2011-10-17', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 1'), - (10, 5538, 'SP3 MS15-058: GDR Security Update', 'https://support.microsoft.com/en-us/help/3045305', '2015-07-14', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 MS15-058: GDR Security Update'), - (10, 5520, 'SP3 MS14-044: GDR Security Update', 'https://support.microsoft.com/en-us/help/2977321', '2014-08-12', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 MS14-044: GDR Security Update'), - (10, 5512, 'SP3 MS12-070: GDR Security Update', 'https://support.microsoft.com/en-us/help/2716436', '2012-10-09', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 MS12-070: GDR Security Update'), - (10, 5500, 'SP3 ', 'https://support.microsoft.com/en-us/help/2546951', '2011-10-06', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 '), - (10, 4371, 'SP2 MS12-070: QFE Security Update', 'https://support.microsoft.com/en-us/help/2716433', '2012-10-09', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 MS12-070: QFE Security Update'), - (10, 4333, 'SP2 CU11', 'https://support.microsoft.com/en-us/help/2715951', '2012-07-16', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 Cumulative Update 11'), - (10, 4332, 'SP2 CU10', 'https://support.microsoft.com/en-us/help/2696625', '2012-05-21', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 Cumulative Update 10'), - (10, 4330, 'SP2 CU9', 'https://support.microsoft.com/en-us/help/2673382', '2012-03-19', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 Cumulative Update 9'), - (10, 4326, 'SP2 CU8', 'https://support.microsoft.com/en-us/help/2648096', '2012-01-16', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 Cumulative Update 8'), - (10, 4323, 'SP2 CU7', 'https://support.microsoft.com/en-us/help/2617148', '2011-11-21', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 Cumulative Update 7'), - (10, 4321, 'SP2 CU6', 'https://support.microsoft.com/en-us/help/2582285', '2011-09-19', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 Cumulative Update 6'), - (10, 4316, 'SP2 CU5', 'https://support.microsoft.com/en-us/help/2555408', '2011-07-18', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 Cumulative Update 5'), - (10, 4311, 'SP2 MS11-049: QFE Security Update', 'https://support.microsoft.com/en-us/help/2494094', '2011-06-14', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 MS11-049: QFE Security Update'), - (10, 4285, 'SP2 CU4', 'https://support.microsoft.com/en-us/help/2527180', '2011-05-16', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 Cumulative Update 4'), - (10, 4279, 'SP2 CU3', 'https://support.microsoft.com/en-us/help/2498535', '2011-03-17', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 Cumulative Update 3'), - (10, 4272, 'SP2 CU2', 'https://support.microsoft.com/en-us/help/2467239', '2011-01-17', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 Cumulative Update 2'), - (10, 4266, 'SP2 CU1', 'https://support.microsoft.com/en-us/help/2289254', '2010-11-15', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 Cumulative Update 1'), - (10, 4067, 'SP2 MS12-070: GDR Security Update', 'https://support.microsoft.com/en-us/help/2716434', '2012-10-09', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 MS12-070: GDR Security Update'), - (10, 4064, 'SP2 MS11-049: GDR Security Update', 'https://support.microsoft.com/en-us/help/2494089', '2011-06-14', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 MS11-049: GDR Security Update'), - (10, 4000, 'SP2 ', 'https://support.microsoft.com/en-us/help/2285068', '2010-09-29', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 '), - (10, 2850, 'SP1 CU16', 'https://support.microsoft.com/en-us/help/2582282', '2011-09-19', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 16'), - (10, 2847, 'SP1 CU15', 'https://support.microsoft.com/en-us/help/2555406', '2011-07-18', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 15'), - (10, 2841, 'SP1 MS11-049: QFE Security Update', 'https://support.microsoft.com/en-us/help/2494100', '2011-06-14', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 MS11-049: QFE Security Update'), - (10, 2821, 'SP1 CU14', 'https://support.microsoft.com/en-us/help/2527187', '2011-05-16', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 14'), - (10, 2816, 'SP1 CU13', 'https://support.microsoft.com/en-us/help/2497673', '2011-03-17', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 13'), - (10, 2808, 'SP1 CU12', 'https://support.microsoft.com/en-us/help/2467236', '2011-01-17', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 12'), - (10, 2804, 'SP1 CU11', 'https://support.microsoft.com/en-us/help/2413738', '2010-11-15', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 11'), - (10, 2799, 'SP1 CU10', 'https://support.microsoft.com/en-us/help/2279604', '2010-09-20', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 10'), - (10, 2789, 'SP1 CU9', 'https://support.microsoft.com/en-us/help/2083921', '2010-07-19', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 9'), - (10, 2775, 'SP1 CU8', 'https://support.microsoft.com/en-us/help/981702', '2010-05-17', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 8'), - (10, 2766, 'SP1 CU7', 'https://support.microsoft.com/en-us/help/979065', '2010-03-26', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 7'), - (10, 2757, 'SP1 CU6', 'https://support.microsoft.com/en-us/help/977443', '2010-01-18', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 6'), - (10, 2746, 'SP1 CU5', 'https://support.microsoft.com/en-us/help/975977', '2009-11-16', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 5'), - (10, 2734, 'SP1 CU4', 'https://support.microsoft.com/en-us/help/973602', '2009-09-21', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 4'), - (10, 2723, 'SP1 CU3', 'https://support.microsoft.com/en-us/help/971491', '2009-07-20', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 3'), - (10, 2714, 'SP1 CU2', 'https://support.microsoft.com/en-us/help/970315', '2009-05-18', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 2'), - (10, 2710, 'SP1 CU1', 'https://support.microsoft.com/en-us/help/969099', '2009-04-16', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 1'), - (10, 2573, 'SP1 MS11-049: GDR Security update', 'https://support.microsoft.com/en-us/help/2494096', '2011-06-14', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 MS11-049: GDR Security update'), - (10, 2531, 'SP1 ', '', '2009-04-01', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 '), - (10, 1835, 'RTM CU10', 'https://support.microsoft.com/en-us/help/979064', '2010-03-15', '2014-07-08', '2019-07-09', 'SQL Server 2008', 'RTM Cumulative Update 10'), - (10, 1828, 'RTM CU9', 'https://support.microsoft.com/en-us/help/977444', '2010-01-18', '2014-07-08', '2019-07-09', 'SQL Server 2008', 'RTM Cumulative Update 9'), - (10, 1823, 'RTM CU8', 'https://support.microsoft.com/en-us/help/975976', '2009-11-16', '2014-07-08', '2019-07-09', 'SQL Server 2008', 'RTM Cumulative Update 8'), - (10, 1818, 'RTM CU7', 'https://support.microsoft.com/en-us/help/973601', '2009-09-21', '2014-07-08', '2019-07-09', 'SQL Server 2008', 'RTM Cumulative Update 7'), - (10, 1812, 'RTM CU6', 'https://support.microsoft.com/en-us/help/971490', '2009-07-20', '2014-07-08', '2019-07-09', 'SQL Server 2008', 'RTM Cumulative Update 6'), - (10, 1806, 'RTM CU5', 'https://support.microsoft.com/en-us/help/969531', '2009-05-18', '2014-07-08', '2019-07-09', 'SQL Server 2008', 'RTM Cumulative Update 5'), - (10, 1798, 'RTM CU4', 'https://support.microsoft.com/en-us/help/963036', '2009-03-16', '2014-07-08', '2019-07-09', 'SQL Server 2008', 'RTM Cumulative Update 4'), - (10, 1787, 'RTM CU3', 'https://support.microsoft.com/en-us/help/960484', '2009-01-19', '2014-07-08', '2019-07-09', 'SQL Server 2008', 'RTM Cumulative Update 3'), - (10, 1779, 'RTM CU2', 'https://support.microsoft.com/en-us/help/958186', '2008-11-19', '2014-07-08', '2019-07-09', 'SQL Server 2008', 'RTM Cumulative Update 2'), - (10, 1763, 'RTM CU1', 'https://support.microsoft.com/en-us/help/956717', '2008-09-22', '2014-07-08', '2019-07-09', 'SQL Server 2008', 'RTM Cumulative Update 1'), - (10, 1600, 'RTM ', '', '2008-08-06', '2014-07-08', '2019-07-09', 'SQL Server 2008', 'RTM ') -; -GO +Saving output to tables: +EXEC sp_BlitzFirst + @OutputDatabaseName = 'DBAtools' +, @OutputSchemaName = 'dbo' +, @OutputTableName = 'BlitzFirst' +, @OutputTableNameFileStats = 'BlitzFirst_FileStats' +, @OutputTableNamePerfmonStats = 'BlitzFirst_PerfmonStats' +, @OutputTableNameWaitStats = 'BlitzFirst_WaitStats' +, @OutputTableNameBlitzCache = 'BlitzCache' +, @OutputTableNameBlitzWho = 'BlitzWho' +*/ diff --git a/Install-Core-Blitz-No-Query-Store.sql b/Install-Core-Blitz-No-Query-Store.sql index cea7352f5..67a492fc1 100755 --- a/Install-Core-Blitz-No-Query-Store.sql +++ b/Install-Core-Blitz-No-Query-Store.sql @@ -37,7 +37,7 @@ AS SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.0', @VersionDate = '20210117'; + SELECT @Version = '8.01', @VersionDate = '20210222'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -7128,6 +7128,69 @@ IF @ProductVersionMajor >= 10 -- HAVING COUNT(DISTINCT o.object_id) > 0;'; --END; --of Check 220. + /*Check for the last good DBCC CHECKDB date */ + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 68 ) + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 68) WITH NOWAIT; + + EXEC sp_MSforeachdb N'USE [?]; + SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + INSERT #DBCCs + (ParentObject, + Object, + Field, + Value) + EXEC (''DBCC DBInfo() With TableResults, NO_INFOMSGS''); + UPDATE #DBCCs SET DbName = N''?'' WHERE DbName IS NULL OPTION (RECOMPILE);'; + + WITH DB2 + AS ( SELECT DISTINCT + Field , + Value , + DbName + FROM #DBCCs + INNER JOIN sys.databases d ON #DBCCs.DbName = d.name + WHERE Field = 'dbi_dbccLastKnownGood' + AND d.create_date < DATEADD(dd, -14, GETDATE()) + ) + INSERT INTO #BlitzResults + ( CheckID , + DatabaseName , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT 68 AS CheckID , + DB2.DbName AS DatabaseName , + 1 AS PRIORITY , + 'Reliability' AS FindingsGroup , + 'Last good DBCC CHECKDB over 2 weeks old' AS Finding , + 'https://BrentOzar.com/go/checkdb' AS URL , + 'Last successful CHECKDB: ' + + CASE DB2.Value + WHEN '1900-01-01 00:00:00.000' + THEN ' never.' + ELSE DB2.Value + END AS Details + FROM DB2 + WHERE DB2.DbName <> 'tempdb' + AND DB2.DbName NOT IN ( SELECT DISTINCT + DatabaseName + FROM + #SkipChecks + WHERE CheckID IS NULL OR CheckID = 68) + AND DB2.DbName NOT IN ( SELECT name + FROM sys.databases + WHERE is_read_only = 1) + AND CONVERT(DATETIME, DB2.Value, 121) < DATEADD(DD, + -14, + CURRENT_TIMESTAMP); + END; END; /* IF @CheckUserDatabaseObjects = 1 */ @@ -7557,69 +7620,6 @@ IF @ProductVersionMajor >= 10 WHERE s.service_account IS NULL AND ep.principal_id <> 1; END; - /*Check for the last good DBCC CHECKDB date */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 68 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 68) WITH NOWAIT; - - EXEC sp_MSforeachdb N'USE [?]; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - INSERT #DBCCs - (ParentObject, - Object, - Field, - Value) - EXEC (''DBCC DBInfo() With TableResults, NO_INFOMSGS''); - UPDATE #DBCCs SET DbName = N''?'' WHERE DbName IS NULL OPTION (RECOMPILE);'; - - WITH DB2 - AS ( SELECT DISTINCT - Field , - Value , - DbName - FROM #DBCCs - INNER JOIN sys.databases d ON #DBCCs.DbName = d.name - WHERE Field = 'dbi_dbccLastKnownGood' - AND d.create_date < DATEADD(dd, -14, GETDATE()) - ) - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 68 AS CheckID , - DB2.DbName AS DatabaseName , - 1 AS PRIORITY , - 'Reliability' AS FindingsGroup , - 'Last good DBCC CHECKDB over 2 weeks old' AS Finding , - 'https://BrentOzar.com/go/checkdb' AS URL , - 'Last successful CHECKDB: ' - + CASE DB2.Value - WHEN '1900-01-01 00:00:00.000' - THEN ' never.' - ELSE DB2.Value - END AS Details - FROM DB2 - WHERE DB2.DbName <> 'tempdb' - AND DB2.DbName NOT IN ( SELECT DISTINCT - DatabaseName - FROM - #SkipChecks - WHERE CheckID IS NULL OR CheckID = 68) - AND DB2.DbName NOT IN ( SELECT name - FROM sys.databases - WHERE is_read_only = 1) - AND CONVERT(DATETIME, DB2.Value, 121) < DATEADD(DD, - -14, - CURRENT_TIMESTAMP); - END; /*Verify that the servername is set */ IF NOT EXISTS ( SELECT 1 @@ -7741,21 +7741,29 @@ IF @ProductVersionMajor >= 10 SELECT 74 AS CheckID , 200 AS Priority , 'Informational' AS FindingsGroup , - 'TraceFlag On' AS Finding , + 'Trace Flag On' AS Finding , CASE WHEN [T].[TraceFlag] = '834' AND @ColumnStoreIndexesInUse = 1 THEN 'https://support.microsoft.com/en-us/kb/3210239' ELSE'https://www.BrentOzar.com/go/traceflags/' END AS URL , 'Trace flag ' + - CASE WHEN [T].[TraceFlag] = '2330' THEN ' 2330 enabled globally. Using this trace Flag disables missing index requests!' - WHEN [T].[TraceFlag] = '1211' THEN ' 1211 enabled globally. Using this Trace Flag disables lock escalation when you least expect it. No Bueno!' - WHEN [T].[TraceFlag] = '1224' THEN ' 1224 enabled globally. Using this Trace Flag disables lock escalation based on the number of locks being taken. You shouldn''t have done that, Dave.' - WHEN [T].[TraceFlag] = '652' THEN ' 652 enabled globally. Using this Trace Flag disables pre-fetching during index scans. If you hate slow queries, you should turn that off.' - WHEN [T].[TraceFlag] = '661' THEN ' 661 enabled globally. Using this Trace Flag disables ghost record removal. Who you gonna call? No one, turn that thing off.' - WHEN [T].[TraceFlag] = '1806' THEN ' 1806 enabled globally. Using this Trace Flag disables Instant File Initialization. I question your sanity.' - WHEN [T].[TraceFlag] = '3505' THEN ' 3505 enabled globally. Using this Trace Flag disables Checkpoints. Probably not the wisest idea.' - WHEN [T].[TraceFlag] = '8649' THEN ' 8649 enabled globally. Using this Trace Flag drops cost threshold for parallelism down to 0. I hope this is a dev server.' - WHEN [T].[TraceFlag] = '834' AND @ColumnStoreIndexesInUse = 1 THEN ' 834 is enabled globally. Using this Trace Flag with Columnstore Indexes is not a great idea.' - WHEN [T].[TraceFlag] = '8017' AND (CAST(SERVERPROPERTY('Edition') AS NVARCHAR(1000)) LIKE N'%Express%') THEN ' 8017 is enabled globally, which is the default for express edition.' - WHEN [T].[TraceFlag] = '8017' AND (CAST(SERVERPROPERTY('Edition') AS NVARCHAR(1000)) NOT LIKE N'%Express%') THEN ' 8017 is enabled globally. Using this Trace Flag disables creation schedulers for all logical processors. Not good.' + CASE WHEN [T].[TraceFlag] = '652' THEN '652 enabled globally, which disables pre-fetching during index scans. This is usually a very bad idea.' + WHEN [T].[TraceFlag] = '661' THEN '661 enabled globally, which disables ghost record removal, causing the database to grow in size. This is usually a very bad idea.' + WHEN [T].[TraceFlag] = '834' AND @ColumnStoreIndexesInUse = 1 THEN '834 is enabled globally, but you also have columnstore indexes. That combination is not recommended by Microsoft.' + WHEN [T].[TraceFlag] = '1117' THEN '1117 enabled globally, which grows all files in a filegroup at the same time.' + WHEN [T].[TraceFlag] = '1118' THEN '1118 enabled globally, which tries to reduce SGAM waits.' + WHEN [T].[TraceFlag] = '1211' THEN '1211 enabled globally, which disables lock escalation when you least expect it. This is usually a very bad idea.' + WHEN [T].[TraceFlag] = '1222' THEN '1222 enabled globally, which captures deadlock graphs in the error log.' + WHEN [T].[TraceFlag] = '1224' THEN '1224 enabled globally, which disables lock escalation until the server has memory pressure. This is usually a very bad idea.' + WHEN [T].[TraceFlag] = '1806' THEN '1806 enabled globally, which disables Instant File Initialization, causing restores and file growths to take longer. This is usually a very bad idea.' + WHEN [T].[TraceFlag] = '2330' THEN '2330 enabled globally, which disables missing index requests. This is usually a very bad idea.' + WHEN [T].[TraceFlag] = '2371' THEN '2371 enabled globally, which changes the auto update stats threshold.' + WHEN [T].[TraceFlag] = '3023' THEN '3023 enabled globally, which performs checksums by default when doing database backups.' + WHEN [T].[TraceFlag] = '3226' THEN '3226 enabled globally, which keeps the event log clean by not reporting successful backups.' + WHEN [T].[TraceFlag] = '3505' THEN '3505 enabled globally, which disables Checkpoints. This is usually a very bad idea.' + WHEN [T].[TraceFlag] = '4199' THEN '4199 enabled globally, which enables non-default Query Optimizer fixes, changing query plans from the default behaviors.' + WHEN [T].[TraceFlag] = '8048' THEN '8048 enabled globally, which tries to reduce CMEMTHREAD waits on servers with a lot of logical processors.' + WHEN [T].[TraceFlag] = '8017' AND (CAST(SERVERPROPERTY('Edition') AS NVARCHAR(1000)) LIKE N'%Express%') THEN '8017 is enabled globally, but this is the default for Express Edition.' + WHEN [T].[TraceFlag] = '8017' AND (CAST(SERVERPROPERTY('Edition') AS NVARCHAR(1000)) NOT LIKE N'%Express%') THEN '8017 is enabled globally, which disables the creation of schedulers for all logical processors.' + WHEN [T].[TraceFlag] = '8649' THEN '8649 enabled globally, which cost threshold for parallelism down to 0. This is usually a very bad idea.' ELSE [T].[TraceFlag] + ' is enabled globally.' END AS Details FROM #TraceStatus T; @@ -8825,7 +8833,7 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 250 AS Priority , ''Server Info'' AS FindingsGroup , ''Azure Managed Instance'' AS Finding , - ''https://www.BrenOzar.com/go/azurevm'' AS URL , + ''https://www.BrentOzar.com/go/azurevm'' AS URL , ''cpu_rate: '' + CAST(COALESCE(cpu_rate, 0) AS VARCHAR(20)) + '', memory_limit_mb: '' + CAST(COALESCE(memory_limit_mb, 0) AS NVARCHAR(20)) + '', process_memory_limit_mb: '' + CAST(COALESCE(process_memory_limit_mb, 0) AS NVARCHAR(20)) + @@ -8853,7 +8861,7 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 CREATE TABLE #services (cmdshell_output varchar(max)); INSERT INTO #services - EXEC xp_cmdshell 'net start' + EXEC /**/xp_cmdshell/**/ 'net start' /* added comments around command since some firewalls block this string TL 20210221 */ IF EXISTS (SELECT 1 FROM #services @@ -9466,7 +9474,7 @@ AS SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.0', @VersionDate = '20210117'; + SELECT @Version = '8.01', @VersionDate = '20210222'; IF(@VersionCheckMode = 1) BEGIN @@ -11245,7 +11253,7 @@ BEGIN SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.0', @VersionDate = '20210117'; +SELECT @Version = '8.01', @VersionDate = '20210222'; IF(@VersionCheckMode = 1) @@ -13304,7 +13312,7 @@ BEGIN WHEN N'writes' THEN N'AND total_logical_writes > 0' WHEN N'duration' THEN N'AND total_elapsed_time > 0' WHEN N'executions' THEN N'AND execution_count > 0' - WHEN N'compiles' THEN N'AND (age_minutes + age_minutes_lifetime) > 0' + /* WHEN N'compiles' THEN N'AND (age_minutes + age_minutes_lifetime) > 0' BGO 2021-01-24 commenting out for https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2772 */ WHEN N'memory grant' THEN N'AND max_grant_kb > 0' WHEN N'unused grant' THEN N'AND max_grant_kb > 0' WHEN N'spills' THEN N'AND max_spills > 0' @@ -13319,6 +13327,7 @@ BEGIN WHEN COALESCE(age_minutes, age_minutes_lifetime, 0) = 0 THEN 0 ELSE CAST((1.00 * execution_count / COALESCE(age_minutes, age_minutes_lifetime)) AS money) END > 0' + ELSE N' /* No minimum threshold set */ ' END; SET @sql += REPLACE(@body_where, 'cached_time', 'creation_time') ; @@ -15899,7 +15908,8 @@ BEGIN PlanHandle AS [Plan Handle], SqlHandle AS [SQL Handle], COALESCE(SetOptions, '''') AS [SET Options], - QueryHash AS [Query Hash], + QueryHash AS [Query Hash], + PlanGenerationNum, [Remove Plan Handle From Cache]'; END; ELSE @@ -17442,6 +17452,7 @@ IF OBJECT_ID('tempdb.. #bou_allsort') IS NULL SqlHandle VARBINARY(64), SetOptions VARCHAR(MAX), QueryHash BINARY(8), + PlanGenerationNum NVARCHAR(30), RemovePlanHandleFromCache NVARCHAR(200), Pattern NVARCHAR(20) ); @@ -17457,7 +17468,7 @@ SET @AllSortSql += N' INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, RemovePlanHandleFromCache ) + MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''cpu'', @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; @@ -17469,7 +17480,7 @@ SET @AllSortSql += N' INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, RemovePlanHandleFromCache ) + MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''reads'', @IgnoreSqlHandles = @ISH, @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; @@ -17481,7 +17492,7 @@ SET @AllSortSql += N' INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, RemovePlanHandleFromCache ) + MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''writes'', @IgnoreSqlHandles = @ISH, @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; @@ -17493,7 +17504,7 @@ SET @AllSortSql += N' INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, RemovePlanHandleFromCache ) + MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''duration'', @IgnoreSqlHandles = @ISH, @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; @@ -17505,7 +17516,7 @@ SET @AllSortSql += N' INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, RemovePlanHandleFromCache ) + MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''executions'', @IgnoreSqlHandles = @ISH, @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; @@ -17540,7 +17551,7 @@ SET @AllSortSql += N' INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, RemovePlanHandleFromCache ) + MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''memory grant'', @IgnoreSqlHandles = @ISH, @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; @@ -17589,7 +17600,7 @@ SET @AllSortSql += N' INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, RemovePlanHandleFromCache ) + MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''spills'', @IgnoreSqlHandles = @ISH, @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; @@ -17614,7 +17625,7 @@ SET @AllSortSql += N' SET @AllSortSql += N' SELECT DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters,ExecutionCount, ExecutionsPerMinute, ExecutionWeight, TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, RemovePlanHandleFromCache, Pattern + MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache, Pattern FROM #bou_allsort ORDER BY Id OPTION(RECOMPILE); '; @@ -17632,7 +17643,7 @@ SET @AllSortSql += N' INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, RemovePlanHandleFromCache ) + MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''avg cpu'', @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; @@ -17644,7 +17655,7 @@ SET @AllSortSql += N' INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, RemovePlanHandleFromCache ) + MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''avg reads'', @IgnoreSqlHandles = @ISH, @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; @@ -17656,7 +17667,7 @@ SET @AllSortSql += N' INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, RemovePlanHandleFromCache ) + MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''avg writes'', @IgnoreSqlHandles = @ISH, @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; @@ -17668,7 +17679,7 @@ SET @AllSortSql += N' INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, RemovePlanHandleFromCache ) + MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''avg duration'', @IgnoreSqlHandles = @ISH, @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; @@ -17680,7 +17691,7 @@ SET @AllSortSql += N' INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, RemovePlanHandleFromCache ) + MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''avg executions'', @IgnoreSqlHandles = @ISH, @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; @@ -17715,7 +17726,7 @@ SET @AllSortSql += N' INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, RemovePlanHandleFromCache ) + MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''avg memory grant'', @IgnoreSqlHandles = @ISH, @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; @@ -17764,7 +17775,7 @@ SET @AllSortSql += N' INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, RemovePlanHandleFromCache ) + MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''avg spills'', @IgnoreSqlHandles = @ISH, @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; @@ -17790,7 +17801,7 @@ SET @AllSortSql += N' SET @AllSortSql += N' SELECT DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters,ExecutionCount, ExecutionsPerMinute, ExecutionWeight, TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, RemovePlanHandleFromCache, Pattern + MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache, Pattern FROM #bou_allsort ORDER BY Id OPTION(RECOMPILE); '; @@ -17998,7 +18009,7 @@ BEGIN ALTER TABLE '+@OutputDatabaseName+'.'+@OutputSchemaName+'.'+@OutputTableName+' DROP COLUMN [PlanCreationTimeHours]; ALTER TABLE '+@OutputDatabaseName+'.'+@OutputSchemaName+'.'+@OutputTableName+' ADD [PlanCreationTimeHours] AS DATEDIFF(HOUR,CONVERT(DATETIMEOFFSET(7),[PlanCreationTime]),[CheckDate]); END '; - + IF @ValidOutputServer = 1 BEGIN SET @StringToExecute = REPLACE(@StringToExecute,''''+@OutputSchemaName+'''',''''''+@OutputSchemaName+''''''); @@ -18381,54 +18392,55 @@ END; /* End of writing results to table */ END; /*Final End*/ GO -IF OBJECT_ID('dbo.sp_BlitzFirst') IS NULL - EXEC ('CREATE PROCEDURE dbo.sp_BlitzFirst AS RETURN 0;'); +SET ANSI_NULLS ON; +SET ANSI_PADDING ON; +SET ANSI_WARNINGS ON; +SET ARITHABORT ON; +SET CONCAT_NULL_YIELDS_NULL ON; +SET QUOTED_IDENTIFIER ON; +SET STATISTICS IO OFF; +SET STATISTICS TIME OFF; GO +IF OBJECT_ID('dbo.sp_BlitzIndex') IS NULL + EXEC ('CREATE PROCEDURE dbo.sp_BlitzIndex AS RETURN 0;'); +GO -ALTER PROCEDURE [dbo].[sp_BlitzFirst] - @LogMessage NVARCHAR(4000) = NULL , - @Help TINYINT = 0 , - @AsOf DATETIMEOFFSET = NULL , - @ExpertMode TINYINT = 0 , - @Seconds INT = 5 , - @OutputType VARCHAR(20) = 'TABLE' , +ALTER PROCEDURE dbo.sp_BlitzIndex + @DatabaseName NVARCHAR(128) = NULL, /*Defaults to current DB if not specified*/ + @SchemaName NVARCHAR(128) = NULL, /*Requires table_name as well.*/ + @TableName NVARCHAR(128) = NULL, /*Requires schema_name as well.*/ + @Mode TINYINT=0, /*0=Diagnose, 1=Summarize, 2=Index Usage Detail, 3=Missing Index Detail, 4=Diagnose Details*/ + /*Note:@Mode doesn't matter if you're specifying schema_name and @TableName.*/ + @Filter TINYINT = 0, /* 0=no filter (default). 1=No low-usage warnings for objects with 0 reads. 2=Only warn for objects >= 500MB */ + /*Note:@Filter doesn't do anything unless @Mode=0*/ + @SkipPartitions BIT = 0, + @SkipStatistics BIT = 1, + @GetAllDatabases BIT = 0, + @BringThePain BIT = 0, + @IgnoreDatabases NVARCHAR(MAX) = NULL, /* Comma-delimited list of databases you want to skip */ + @ThresholdMB INT = 250 /* Number of megabytes that an object must be before we include it in basic results */, + @OutputType VARCHAR(20) = 'TABLE' , @OutputServerName NVARCHAR(256) = NULL , @OutputDatabaseName NVARCHAR(256) = NULL , @OutputSchemaName NVARCHAR(256) = NULL , @OutputTableName NVARCHAR(256) = NULL , - @OutputTableNameFileStats NVARCHAR(256) = NULL , - @OutputTableNamePerfmonStats NVARCHAR(256) = NULL , - @OutputTableNameWaitStats NVARCHAR(256) = NULL , - @OutputTableNameBlitzCache NVARCHAR(256) = NULL , - @OutputTableNameBlitzWho NVARCHAR(256) = NULL , - @OutputTableRetentionDays TINYINT = 7 , - @OutputXMLasNVARCHAR TINYINT = 0 , - @FilterPlansByDatabase VARCHAR(MAX) = NULL , - @CheckProcedureCache TINYINT = 0 , - @CheckServerInfo TINYINT = 1 , - @FileLatencyThresholdMS INT = 100 , - @SinceStartup TINYINT = 0 , - @ShowSleepingSPIDs TINYINT = 0 , - @BlitzCacheSkipAnalysis BIT = 1 , - @MemoryGrantThresholdPct DECIMAL(5,2) = 15.00, - @LogMessageCheckID INT = 38, - @LogMessagePriority TINYINT = 1, - @LogMessageFindingsGroup VARCHAR(50) = 'Logged Message', - @LogMessageFinding VARCHAR(200) = 'Logged from sp_BlitzFirst', - @LogMessageURL VARCHAR(200) = '', - @LogMessageCheckDate DATETIMEOFFSET = NULL, - @Debug BIT = 0, - @Version VARCHAR(30) = NULL OUTPUT, + @IncludeInactiveIndexes BIT = 0 /* Will skip indexes with no reads or writes */, + @ShowAllMissingIndexRequests BIT = 0 /*Will make all missing index requests show up*/, + @SortOrder NVARCHAR(50) = NULL, /* Only affects @Mode = 2. */ + @SortDirection NVARCHAR(4) = 'DESC', /* Only affects @Mode = 2. */ + @Help TINYINT = 0, + @Debug BIT = 0, + @Version VARCHAR(30) = NULL OUTPUT, @VersionDate DATETIME = NULL OUTPUT, @VersionCheckMode BIT = 0 - WITH EXECUTE AS CALLER, RECOMPILE +WITH RECOMPILE AS -BEGIN SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.0', @VersionDate = '20210117'; +SELECT @Version = '8.01', @VersionDate = '20210222'; +SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) BEGIN @@ -18438,32 +18450,28 @@ END; IF @Help = 1 BEGIN PRINT ' -sp_BlitzFirst from http://FirstResponderKit.org +/* +sp_BlitzIndex from http://FirstResponderKit.org -This script gives you a prioritized list of why your SQL Server is slow right now. - -This is not an overall health check - for that, check out sp_Blitz. +This script analyzes the design and performance of your indexes. To learn more, visit http://FirstResponderKit.org where you can download new versions for free, watch training videos on how it works, get more info on the findings, contribute your own code, and more. Known limitations of this version: - - Only Microsoft-supported versions of SQL Server. Sorry, 2005 and 2000. It - may work just fine on 2005, and if it does, hug your parents. Just don''t - file support issues if it breaks. - - If a temp table called #CustomPerfmonCounters exists for any other session, - but not our session, this stored proc will fail with an error saying the - temp table #CustomPerfmonCounters does not exist. - - @OutputServerName is not functional yet. - - If @OutputDatabaseName, SchemaName, TableName, etc are quoted with brackets, - the write to table may silently fail. Look, I never said I was good at this. + - Only Microsoft-supported versions of SQL Server. Sorry, 2005 and 2000. + - Index create statements are just to give you a rough idea of the syntax. It includes filters and fillfactor. + -- Example 1: index creates use ONLINE=? instead of ONLINE=ON / ONLINE=OFF. This is because it is important + for the user to understand if it is going to be offline and not just run a script. + -- Example 2: they do not include all the options the index may have been created with (padding, compression + filegroup/partition scheme etc.) + -- (The compression and filegroup index create syntax is not trivial because it is set at the partition + level and is not trivial to code.) + - Does not advise you about data modeling for clustered indexes and primary keys (primarily looks for signs of insanity.) Unknown limitations of this version: - - None. Like Zombo.com, the only limit is yourself. - -Changes - for the full list of improvements and fixes in this version, see: -https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/ + - We knew them once, but we forgot. MIT License @@ -18487,5247 +18495,639 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - '; RETURN; END; /* @Help = 1 */ -RAISERROR('Setting up configuration variables',10,1) WITH NOWAIT; -DECLARE @StringToExecute NVARCHAR(MAX), - @ParmDefinitions NVARCHAR(4000), - @Parm1 NVARCHAR(4000), - @OurSessionID INT, - @LineFeed NVARCHAR(10), - @StockWarningHeader NVARCHAR(MAX) = N'', - @StockWarningFooter NVARCHAR(MAX) = N'', - @StockDetailsHeader NVARCHAR(MAX) = N'', - @StockDetailsFooter NVARCHAR(MAX) = N'', - @StartSampleTime DATETIMEOFFSET, - @FinishSampleTime DATETIMEOFFSET, - @FinishSampleTimeWaitFor DATETIME, - @AsOf1 DATETIMEOFFSET, - @AsOf2 DATETIMEOFFSET, - @ServiceName sysname, - @OutputTableNameFileStats_View NVARCHAR(256), - @OutputTableNamePerfmonStats_View NVARCHAR(256), - @OutputTableNamePerfmonStatsActuals_View NVARCHAR(256), - @OutputTableNameWaitStats_View NVARCHAR(256), - @OutputTableNameWaitStats_Categories NVARCHAR(256), - @OutputTableCleanupDate DATE, - @ObjectFullName NVARCHAR(2000), - @BlitzWho NVARCHAR(MAX) = N'EXEC dbo.sp_BlitzWho @ShowSleepingSPIDs = ' + CONVERT(NVARCHAR(1), @ShowSleepingSPIDs) + N';', - @BlitzCacheMinutesBack INT, - @UnquotedOutputServerName NVARCHAR(256) = @OutputServerName , - @UnquotedOutputDatabaseName NVARCHAR(256) = @OutputDatabaseName , - @UnquotedOutputSchemaName NVARCHAR(256) = @OutputSchemaName , - @LocalServerName NVARCHAR(128) = CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128)), - @dm_exec_query_statistics_xml BIT = 0; +DECLARE @ScriptVersionName NVARCHAR(50); +DECLARE @DaysUptime NUMERIC(23,2); +DECLARE @DatabaseID INT; +DECLARE @ObjectID INT; +DECLARE @dsql NVARCHAR(MAX); +DECLARE @params NVARCHAR(MAX); +DECLARE @msg NVARCHAR(4000); +DECLARE @ErrorSeverity INT; +DECLARE @ErrorState INT; +DECLARE @Rowcount BIGINT; +DECLARE @SQLServerProductVersion NVARCHAR(128); +DECLARE @SQLServerEdition INT; +DECLARE @FilterMB INT; +DECLARE @collation NVARCHAR(256); +DECLARE @NumDatabases INT; +DECLARE @LineFeed NVARCHAR(5); +DECLARE @DaysUptimeInsertValue NVARCHAR(256); +DECLARE @DatabaseToIgnore NVARCHAR(MAX); +DECLARE @ColumnList NVARCHAR(MAX); -/* Sanitize our inputs */ -SELECT - @OutputTableNameFileStats_View = QUOTENAME(@OutputTableNameFileStats + '_Deltas'), - @OutputTableNamePerfmonStats_View = QUOTENAME(@OutputTableNamePerfmonStats + '_Deltas'), - @OutputTableNamePerfmonStatsActuals_View = QUOTENAME(@OutputTableNamePerfmonStats + '_Actuals'), - @OutputTableNameWaitStats_View = QUOTENAME(@OutputTableNameWaitStats + '_Deltas'), - @OutputTableNameWaitStats_Categories = QUOTENAME(@OutputTableNameWaitStats + '_Categories'); +/* Let's get @SortOrder set to lower case here for comparisons later */ +SET @SortOrder = REPLACE(LOWER(@SortOrder), N' ', N'_'); +SET @SortDirection = LOWER(@SortDirection); -SELECT - @OutputDatabaseName = QUOTENAME(@OutputDatabaseName), - @OutputSchemaName = QUOTENAME(@OutputSchemaName), - @OutputTableName = QUOTENAME(@OutputTableName), - @OutputTableNameFileStats = QUOTENAME(@OutputTableNameFileStats), - @OutputTableNamePerfmonStats = QUOTENAME(@OutputTableNamePerfmonStats), - @OutputTableNameWaitStats = QUOTENAME(@OutputTableNameWaitStats), - @OutputTableCleanupDate = CAST( (DATEADD(DAY, -1 * @OutputTableRetentionDays, GETDATE() ) ) AS DATE), - /* @OutputTableNameBlitzCache = QUOTENAME(@OutputTableNameBlitzCache), We purposely don't sanitize this because sp_BlitzCache will */ - /* @OutputTableNameBlitzWho = QUOTENAME(@OutputTableNameBlitzWho), We purposely don't sanitize this because sp_BlitzWho will */ - @LineFeed = CHAR(13) + CHAR(10), - @OurSessionID = @@SPID, - @OutputType = UPPER(@OutputType); +SET @LineFeed = CHAR(13) + CHAR(10); +SELECT @SQLServerProductVersion = CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)); +SELECT @SQLServerEdition =CAST(SERVERPROPERTY('EngineEdition') AS INT); /* We default to online index creates where EngineEdition=3*/ +SET @FilterMB=250; +SELECT @ScriptVersionName = 'sp_BlitzIndex(TM) v' + @Version + ' - ' + DATENAME(MM, @VersionDate) + ' ' + RIGHT('0'+DATENAME(DD, @VersionDate),2) + ', ' + DATENAME(YY, @VersionDate); +SET @IgnoreDatabases = REPLACE(REPLACE(LTRIM(RTRIM(@IgnoreDatabases)), CHAR(10), ''), CHAR(13), ''); -IF(@OutputType = 'NONE' AND (@OutputTableName IS NULL OR @OutputSchemaName IS NULL OR @OutputDatabaseName IS NULL)) +RAISERROR(N'Starting run. %s', 0,1, @ScriptVersionName) WITH NOWAIT; + +IF(@OutputType NOT IN ('TABLE','NONE')) BEGIN - RAISERROR('This procedure should be called with a value for all @Output* parameters, as @OutputType is set to NONE',12,1); + RAISERROR('Invalid value for parameter @OutputType. Expected: (TABLE;NONE)',12,1); RETURN; END; + +IF(@OutputType = 'NONE') +BEGIN + IF(@OutputTableName IS NULL OR @OutputSchemaName IS NULL OR @OutputDatabaseName IS NULL) + BEGIN + RAISERROR('This procedure should be called with a value for @Output* parameters, as @OutputType is set to NONE',12,1); + RETURN; + END; + IF(@BringThePain = 1) + BEGIN + RAISERROR('Incompatible Parameters: @BringThePain set to 1 and @OutputType set to NONE',12,1); + RETURN; + END; + /* Eventually limit by mode + IF(@Mode not in (0,4)) + BEGIN + RAISERROR('Incompatible Parameters: @Mode set to %d and @OutputType set to NONE',12,1,@Mode); + RETURN; + END; + */ +END; -IF UPPER(@OutputType) LIKE 'TOP 10%' SET @OutputType = 'Top10'; -IF @OutputType = 'Top10' SET @SinceStartup = 1; +IF OBJECT_ID('tempdb..#IndexSanity') IS NOT NULL + DROP TABLE #IndexSanity; -/* Logged Message - CheckID 38 */ -IF @LogMessage IS NOT NULL - BEGIN +IF OBJECT_ID('tempdb..#IndexPartitionSanity') IS NOT NULL + DROP TABLE #IndexPartitionSanity; - RAISERROR('Saving LogMessage to table',10,1) WITH NOWAIT; +IF OBJECT_ID('tempdb..#IndexSanitySize') IS NOT NULL + DROP TABLE #IndexSanitySize; - /* Try to set the output table parameters if they don't exist */ - IF @OutputSchemaName IS NULL AND @OutputTableName IS NULL AND @OutputDatabaseName IS NULL - BEGIN - SET @OutputSchemaName = N'[dbo]'; - SET @OutputTableName = N'[BlitzFirst]'; - - /* Look for the table in the current database */ - SELECT TOP 1 @OutputDatabaseName = QUOTENAME(TABLE_CATALOG) - FROM INFORMATION_SCHEMA.TABLES - WHERE TABLE_SCHEMA = 'dbo' AND TABLE_NAME = 'BlitzFirst'; +IF OBJECT_ID('tempdb..#IndexColumns') IS NOT NULL + DROP TABLE #IndexColumns; - IF @OutputDatabaseName IS NULL AND EXISTS (SELECT * FROM sys.databases WHERE name = 'DBAtools') - SET @OutputDatabaseName = '[DBAtools]'; +IF OBJECT_ID('tempdb..#MissingIndexes') IS NOT NULL + DROP TABLE #MissingIndexes; - END; +IF OBJECT_ID('tempdb..#ForeignKeys') IS NOT NULL + DROP TABLE #ForeignKeys; - IF @OutputDatabaseName IS NULL OR @OutputSchemaName IS NULL OR @OutputTableName IS NULL - OR NOT EXISTS ( SELECT * - FROM sys.databases - WHERE QUOTENAME([name]) = @OutputDatabaseName) - BEGIN - RAISERROR('We have a hard time logging a message without a valid @OutputDatabaseName, @OutputSchemaName, and @OutputTableName to log it to.', 0, 1) WITH NOWAIT; - RETURN; - END; - IF @LogMessageCheckDate IS NULL - SET @LogMessageCheckDate = SYSDATETIMEOFFSET(); - SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' - + @OutputSchemaName + ''') INSERT ' - + @OutputDatabaseName + '.' - + @OutputSchemaName + '.' - + @OutputTableName - + ' (ServerName, CheckDate, CheckID, Priority, FindingsGroup, Finding, Details, URL) VALUES( ' - + ' @SrvName, @LogMessageCheckDate, @LogMessageCheckID, @LogMessagePriority, @LogMessageFindingsGroup, @LogMessageFinding, @LogMessage, @LogMessageURL)'; +IF OBJECT_ID('tempdb..#BlitzIndexResults') IS NOT NULL + DROP TABLE #BlitzIndexResults; + +IF OBJECT_ID('tempdb..#IndexCreateTsql') IS NOT NULL + DROP TABLE #IndexCreateTsql; - EXECUTE sp_executesql @StringToExecute, - N'@SrvName NVARCHAR(128), @LogMessageCheckID INT, @LogMessagePriority TINYINT, @LogMessageFindingsGroup VARCHAR(50), @LogMessageFinding VARCHAR(200), @LogMessage NVARCHAR(4000), @LogMessageCheckDate DATETIMEOFFSET, @LogMessageURL VARCHAR(200)', - @LocalServerName, @LogMessageCheckID, @LogMessagePriority, @LogMessageFindingsGroup, @LogMessageFinding, @LogMessage, @LogMessageCheckDate, @LogMessageURL; +IF OBJECT_ID('tempdb..#DatabaseList') IS NOT NULL + DROP TABLE #DatabaseList; - RAISERROR('LogMessage saved to table. We have made a note of your activity. Keep up the good work.',10,1) WITH NOWAIT; +IF OBJECT_ID('tempdb..#Statistics') IS NOT NULL + DROP TABLE #Statistics; - RETURN; - END; +IF OBJECT_ID('tempdb..#PartitionCompressionInfo') IS NOT NULL + DROP TABLE #PartitionCompressionInfo; -IF @SinceStartup = 1 - SELECT @Seconds = 0, @ExpertMode = 1; +IF OBJECT_ID('tempdb..#ComputedColumns') IS NOT NULL + DROP TABLE #ComputedColumns; + +IF OBJECT_ID('tempdb..#TraceStatus') IS NOT NULL + DROP TABLE #TraceStatus; +IF OBJECT_ID('tempdb..#TemporalTables') IS NOT NULL + DROP TABLE #TemporalTables; -IF @OutputType = 'SCHEMA' -BEGIN - SELECT FieldList = '[Priority] TINYINT, [FindingsGroup] VARCHAR(50), [Finding] VARCHAR(200), [URL] VARCHAR(200), [Details] NVARCHAR(4000), [HowToStopIt] NVARCHAR(MAX), [QueryPlan] XML, [QueryText] NVARCHAR(MAX)'; +IF OBJECT_ID('tempdb..#CheckConstraints') IS NOT NULL + DROP TABLE #CheckConstraints; -END; -ELSE IF @AsOf IS NOT NULL AND @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @OutputTableName IS NOT NULL -BEGIN - /* They want to look into the past. */ - SET @AsOf1= DATEADD(mi, -15, @AsOf); - SET @AsOf2= DATEADD(mi, +15, @AsOf); +IF OBJECT_ID('tempdb..#FilteredIndexes') IS NOT NULL + DROP TABLE #FilteredIndexes; + +IF OBJECT_ID('tempdb..#Ignore_Databases') IS NOT NULL + DROP TABLE #Ignore_Databases - SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' - + @OutputSchemaName + ''') SELECT CheckDate, [Priority], [FindingsGroup], [Finding], [URL], CAST([Details] AS [XML]) AS Details,' - + '[HowToStopIt], [CheckID], [StartTime], [LoginName], [NTUserName], [OriginalLoginName], [ProgramName], [HostName], [DatabaseID],' - + '[DatabaseName], [OpenTransactionCount], [QueryPlan], [QueryText] FROM ' - + @OutputDatabaseName + '.' - + @OutputSchemaName + '.' - + @OutputTableName - + ' WHERE CheckDate >= @AsOf1' - + ' AND CheckDate <= @AsOf2' - + ' /*ORDER BY CheckDate, Priority , FindingsGroup , Finding , Details*/;'; - EXEC sp_executesql @StringToExecute, - N'@AsOf1 DATETIMEOFFSET, @AsOf2 DATETIMEOFFSET', - @AsOf1, @AsOf2 + RAISERROR (N'Create temp tables.',0,1) WITH NOWAIT; + CREATE TABLE #BlitzIndexResults + ( + blitz_result_id INT IDENTITY PRIMARY KEY, + check_id INT NOT NULL, + index_sanity_id INT NULL, + Priority INT NULL, + findings_group NVARCHAR(4000) NOT NULL, + finding NVARCHAR(200) NOT NULL, + [database_name] NVARCHAR(128) NULL, + URL NVARCHAR(200) NOT NULL, + details NVARCHAR(MAX) NOT NULL, + index_definition NVARCHAR(MAX) NOT NULL, + secret_columns NVARCHAR(MAX) NULL, + index_usage_summary NVARCHAR(MAX) NULL, + index_size_summary NVARCHAR(MAX) NULL, + create_tsql NVARCHAR(MAX) NULL, + more_info NVARCHAR(MAX)NULL + ); + CREATE TABLE #IndexSanity + ( + [index_sanity_id] INT IDENTITY PRIMARY KEY CLUSTERED, + [database_id] SMALLINT NOT NULL , + [object_id] INT NOT NULL , + [index_id] INT NOT NULL , + [index_type] TINYINT NOT NULL, + [database_name] NVARCHAR(128) NOT NULL , + [schema_name] NVARCHAR(128) NOT NULL , + [object_name] NVARCHAR(128) NOT NULL , + index_name NVARCHAR(128) NULL , + key_column_names NVARCHAR(MAX) NULL , + key_column_names_with_sort_order NVARCHAR(MAX) NULL , + key_column_names_with_sort_order_no_types NVARCHAR(MAX) NULL , + count_key_columns INT NULL , + include_column_names NVARCHAR(MAX) NULL , + include_column_names_no_types NVARCHAR(MAX) NULL , + count_included_columns INT NULL , + partition_key_column_name NVARCHAR(MAX) NULL, + filter_definition NVARCHAR(MAX) NOT NULL , + is_indexed_view BIT NOT NULL , + is_unique BIT NOT NULL , + is_primary_key BIT NOT NULL , + is_XML BIT NOT NULL, + is_spatial BIT NOT NULL, + is_NC_columnstore BIT NOT NULL, + is_CX_columnstore BIT NOT NULL, + is_in_memory_oltp BIT NOT NULL , + is_disabled BIT NOT NULL , + is_hypothetical BIT NOT NULL , + is_padded BIT NOT NULL , + fill_factor SMALLINT NOT NULL , + user_seeks BIGINT NOT NULL , + user_scans BIGINT NOT NULL , + user_lookups BIGINT NOT NULL , + user_updates BIGINT NULL , + last_user_seek DATETIME NULL , + last_user_scan DATETIME NULL , + last_user_lookup DATETIME NULL , + last_user_update DATETIME NULL , + is_referenced_by_foreign_key BIT DEFAULT(0), + secret_columns NVARCHAR(MAX) NULL, + count_secret_columns INT NULL, + create_date DATETIME NOT NULL, + modify_date DATETIME NOT NULL, + filter_columns_not_in_index NVARCHAR(MAX), + [db_schema_object_name] AS [schema_name] + N'.' + [object_name] , + [db_schema_object_indexid] AS [schema_name] + N'.' + [object_name] + + CASE WHEN [index_name] IS NOT NULL THEN N'.' + index_name + ELSE N'' + END + N' (' + CAST(index_id AS NVARCHAR(20)) + N')' , + first_key_column_name AS CASE WHEN count_key_columns > 1 + THEN LEFT(key_column_names, CHARINDEX(',', key_column_names, 0) - 1) + ELSE key_column_names + END , + index_definition AS + CASE WHEN partition_key_column_name IS NOT NULL + THEN N'[PARTITIONED BY:' + partition_key_column_name + N']' + ELSE '' + END + + CASE index_id + WHEN 0 THEN N'[HEAP] ' + WHEN 1 THEN N'[CX] ' + ELSE N'' END + CASE WHEN is_indexed_view = 1 THEN N'[VIEW] ' + ELSE N'' END + CASE WHEN is_primary_key = 1 THEN N'[PK] ' + ELSE N'' END + CASE WHEN is_XML = 1 THEN N'[XML] ' + ELSE N'' END + CASE WHEN is_spatial = 1 THEN N'[SPATIAL] ' + ELSE N'' END + CASE WHEN is_NC_columnstore = 1 THEN N'[COLUMNSTORE] ' + ELSE N'' END + CASE WHEN is_in_memory_oltp = 1 THEN N'[IN-MEMORY] ' + ELSE N'' END + CASE WHEN is_disabled = 1 THEN N'[DISABLED] ' + ELSE N'' END + CASE WHEN is_hypothetical = 1 THEN N'[HYPOTHETICAL] ' + ELSE N'' END + CASE WHEN is_unique = 1 AND is_primary_key = 0 THEN N'[UNIQUE] ' + ELSE N'' END + CASE WHEN count_key_columns > 0 THEN + N'[' + CAST(count_key_columns AS NVARCHAR(10)) + N' KEY' + + CASE WHEN count_key_columns > 1 THEN N'S' ELSE N'' END + + N'] ' + LTRIM(key_column_names_with_sort_order) + ELSE N'' END + CASE WHEN count_included_columns > 0 THEN + N' [' + CAST(count_included_columns AS NVARCHAR(10)) + N' INCLUDE' + + + CASE WHEN count_included_columns > 1 THEN N'S' ELSE N'' END + + N'] ' + include_column_names + ELSE N'' END + CASE WHEN filter_definition <> N'' THEN N' [FILTER] ' + filter_definition + ELSE N'' END , + [total_reads] AS user_seeks + user_scans + user_lookups, + [reads_per_write] AS CAST(CASE WHEN user_updates > 0 + THEN ( user_seeks + user_scans + user_lookups ) / (1.0 * user_updates) + ELSE 0 END AS MONEY) , + [index_usage_summary] AS + CASE WHEN is_spatial = 1 THEN N'Not Tracked' + WHEN is_disabled = 1 THEN N'Disabled' + ELSE N'Reads: ' + + REPLACE(CONVERT(NVARCHAR(30),CAST((user_seeks + user_scans + user_lookups) AS MONEY), 1), N'.00', N'') + + CASE WHEN user_seeks + user_scans + user_lookups > 0 THEN + N' (' + + RTRIM( + CASE WHEN user_seeks > 0 THEN REPLACE(CONVERT(NVARCHAR(30),CAST((user_seeks) AS MONEY), 1), N'.00', N'') + N' seek ' ELSE N'' END + + CASE WHEN user_scans > 0 THEN REPLACE(CONVERT(NVARCHAR(30),CAST((user_scans) AS MONEY), 1), N'.00', N'') + N' scan ' ELSE N'' END + + CASE WHEN user_lookups > 0 THEN REPLACE(CONVERT(NVARCHAR(30),CAST((user_lookups) AS MONEY), 1), N'.00', N'') + N' lookup' ELSE N'' END + ) + + N') ' + ELSE N' ' + END + + N'Writes: ' + + REPLACE(CONVERT(NVARCHAR(30),CAST(user_updates AS MONEY), 1), N'.00', N'') + END /* First "end" is about is_spatial */, + [more_info] AS + CASE WHEN is_in_memory_oltp = 1 + THEN N'EXEC dbo.sp_BlitzInMemoryOLTP @dbName=' + QUOTENAME([database_name],N'''') + + N', @tableName=' + QUOTENAME([object_name],N'''') + N';' + ELSE N'EXEC dbo.sp_BlitzIndex @DatabaseName=' + QUOTENAME([database_name],N'''') + + N', @SchemaName=' + QUOTENAME([schema_name],N'''') + N', @TableName=' + QUOTENAME([object_name],N'''') + N';' + END + ); + RAISERROR (N'Adding UQ index on #IndexSanity (database_id, object_id, index_id)',0,1) WITH NOWAIT; + IF NOT EXISTS(SELECT 1 FROM tempdb.sys.indexes WHERE name='uq_database_id_object_id_index_id') + CREATE UNIQUE INDEX uq_database_id_object_id_index_id ON #IndexSanity (database_id, object_id, index_id); -END; /* IF @AsOf IS NOT NULL AND @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @OutputTableName IS NOT NULL */ -ELSE IF @LogMessage IS NULL /* IF @OutputType = 'SCHEMA' */ -BEGIN - /* What's running right now? This is the first and last result set. */ - IF @SinceStartup = 0 AND @Seconds > 0 AND @ExpertMode = 1 AND @OutputType <> 'NONE' - BEGIN - IF OBJECT_ID('master.dbo.sp_BlitzWho') IS NULL AND OBJECT_ID('dbo.sp_BlitzWho') IS NULL - BEGIN - PRINT N'sp_BlitzWho is not installed in the current database_files. You can get a copy from http://FirstResponderKit.org'; - END; - ELSE - BEGIN - EXEC (@BlitzWho); - END; - END; /* IF @SinceStartup = 0 AND @Seconds > 0 AND @ExpertMode = 1 AND @OutputType <> 'NONE' - What's running right now? This is the first and last result set. */ - /* Set start/finish times AFTER sp_BlitzWho runs. For more info: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2244 */ - IF @Seconds = 0 AND SERVERPROPERTY('Edition') = 'SQL Azure' - WITH WaitTimes AS ( - SELECT wait_type, wait_time_ms, - NTILE(3) OVER(ORDER BY wait_time_ms) AS grouper - FROM sys.dm_os_wait_stats w - WHERE wait_type IN ('DIRTY_PAGE_POLL','HADR_FILESTREAM_IOMGR_IOCOMPLETION','LAZYWRITER_SLEEP', - 'LOGMGR_QUEUE','REQUEST_FOR_DEADLOCK_SEARCH','XE_TIMER_EVENT') - ) - SELECT @StartSampleTime = DATEADD(mi, AVG(-wait_time_ms / 1000 / 60), SYSDATETIMEOFFSET()), @FinishSampleTime = SYSDATETIMEOFFSET() - FROM WaitTimes - WHERE grouper = 2; - ELSE IF @Seconds = 0 AND SERVERPROPERTY('Edition') <> 'SQL Azure' - SELECT @StartSampleTime = DATEADD(MINUTE,DATEDIFF(MINUTE, GETDATE(), GETUTCDATE()),create_date) , @FinishSampleTime = SYSDATETIMEOFFSET() - FROM sys.databases - WHERE database_id = 2; - ELSE - SELECT @StartSampleTime = SYSDATETIMEOFFSET(), - @FinishSampleTime = DATEADD(ss, @Seconds, SYSDATETIMEOFFSET()), - @FinishSampleTimeWaitFor = DATEADD(ss, @Seconds, GETDATE()); + CREATE TABLE #IndexPartitionSanity + ( + [index_partition_sanity_id] INT IDENTITY, + [index_sanity_id] INT NULL , + [database_id] INT NOT NULL , + [object_id] INT NOT NULL , + [schema_name] NVARCHAR(128) NOT NULL, + [index_id] INT NOT NULL , + [partition_number] INT NOT NULL , + row_count BIGINT NOT NULL , + reserved_MB NUMERIC(29,2) NOT NULL , + reserved_LOB_MB NUMERIC(29,2) NOT NULL , + reserved_row_overflow_MB NUMERIC(29,2) NOT NULL , + reserved_dictionary_MB NUMERIC(29,2) NOT NULL , + leaf_insert_count BIGINT NULL , + leaf_delete_count BIGINT NULL , + leaf_update_count BIGINT NULL , + range_scan_count BIGINT NULL , + singleton_lookup_count BIGINT NULL , + forwarded_fetch_count BIGINT NULL , + lob_fetch_in_pages BIGINT NULL , + lob_fetch_in_bytes BIGINT NULL , + row_overflow_fetch_in_pages BIGINT NULL , + row_overflow_fetch_in_bytes BIGINT NULL , + row_lock_count BIGINT NULL , + row_lock_wait_count BIGINT NULL , + row_lock_wait_in_ms BIGINT NULL , + page_lock_count BIGINT NULL , + page_lock_wait_count BIGINT NULL , + page_lock_wait_in_ms BIGINT NULL , + index_lock_promotion_attempt_count BIGINT NULL , + index_lock_promotion_count BIGINT NULL, + data_compression_desc NVARCHAR(60) NULL, + page_latch_wait_count BIGINT NULL, + page_latch_wait_in_ms BIGINT NULL, + page_io_latch_wait_count BIGINT NULL, + page_io_latch_wait_in_ms BIGINT NULL, + lock_escalation_desc nvarchar(60) NULL + ); + CREATE TABLE #IndexSanitySize + ( + [index_sanity_size_id] INT IDENTITY NOT NULL , + [index_sanity_id] INT NULL , + [database_id] INT NOT NULL, + [schema_name] NVARCHAR(128) NOT NULL, + partition_count INT NOT NULL , + total_rows BIGINT NOT NULL , + total_reserved_MB NUMERIC(29,2) NOT NULL , + total_reserved_LOB_MB NUMERIC(29,2) NOT NULL , + total_reserved_row_overflow_MB NUMERIC(29,2) NOT NULL , + total_reserved_dictionary_MB NUMERIC(29,2) NOT NULL , + total_leaf_delete_count BIGINT NULL, + total_leaf_update_count BIGINT NULL, + total_range_scan_count BIGINT NULL, + total_singleton_lookup_count BIGINT NULL, + total_forwarded_fetch_count BIGINT NULL, + total_row_lock_count BIGINT NULL , + total_row_lock_wait_count BIGINT NULL , + total_row_lock_wait_in_ms BIGINT NULL , + avg_row_lock_wait_in_ms BIGINT NULL , + total_page_lock_count BIGINT NULL , + total_page_lock_wait_count BIGINT NULL , + total_page_lock_wait_in_ms BIGINT NULL , + avg_page_lock_wait_in_ms BIGINT NULL , + total_index_lock_promotion_attempt_count BIGINT NULL , + total_index_lock_promotion_count BIGINT NULL , + data_compression_desc NVARCHAR(4000) NULL, + page_latch_wait_count BIGINT NULL, + page_latch_wait_in_ms BIGINT NULL, + page_io_latch_wait_count BIGINT NULL, + page_io_latch_wait_in_ms BIGINT NULL, + lock_escalation_desc nvarchar(60) NULL, + index_size_summary AS ISNULL( + CASE WHEN partition_count > 1 + THEN N'[' + CAST(partition_count AS NVARCHAR(10)) + N' PARTITIONS] ' + ELSE N'' + END + REPLACE(CONVERT(NVARCHAR(30),CAST([total_rows] AS MONEY), 1), N'.00', N'') + N' rows; ' + + CASE WHEN total_reserved_MB > 1024 THEN + CAST(CAST(total_reserved_MB/1024. AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'GB' + ELSE + CAST(CAST(total_reserved_MB AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'MB' + END + + CASE WHEN total_reserved_LOB_MB > 1024 THEN + N'; ' + CAST(CAST(total_reserved_LOB_MB/1024. AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'GB ' + CASE WHEN total_reserved_dictionary_MB = 0 THEN N'LOB' ELSE N'Columnstore' END + WHEN total_reserved_LOB_MB > 0 THEN + N'; ' + CAST(CAST(total_reserved_LOB_MB AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'MB ' + CASE WHEN total_reserved_dictionary_MB = 0 THEN N'LOB' ELSE N'Columnstore' END + ELSE '' + END + + CASE WHEN total_reserved_row_overflow_MB > 1024 THEN + N'; ' + CAST(CAST(total_reserved_row_overflow_MB/1024. AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'GB Row Overflow' + WHEN total_reserved_row_overflow_MB > 0 THEN + N'; ' + CAST(CAST(total_reserved_row_overflow_MB AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'MB Row Overflow' + ELSE '' + END + + CASE WHEN total_reserved_dictionary_MB > 1024 THEN + N'; ' + CAST(CAST(total_reserved_dictionary_MB/1024. AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'GB Dictionaries' + WHEN total_reserved_dictionary_MB > 0 THEN + N'; ' + CAST(CAST(total_reserved_dictionary_MB AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'MB Dictionaries' + ELSE '' + END , + N'Error- NULL in computed column'), + index_op_stats AS ISNULL( + ( + REPLACE(CONVERT(NVARCHAR(30),CAST(total_singleton_lookup_count AS MONEY), 1),N'.00',N'') + N' singleton lookups; ' + + REPLACE(CONVERT(NVARCHAR(30),CAST(total_range_scan_count AS MONEY), 1),N'.00',N'') + N' scans/seeks; ' + + REPLACE(CONVERT(NVARCHAR(30),CAST(total_leaf_delete_count AS MONEY), 1),N'.00',N'') + N' deletes; ' + + REPLACE(CONVERT(NVARCHAR(30),CAST(total_leaf_update_count AS MONEY), 1),N'.00',N'') + N' updates; ' + + CASE WHEN ISNULL(total_forwarded_fetch_count,0) >0 THEN + REPLACE(CONVERT(NVARCHAR(30),CAST(total_forwarded_fetch_count AS MONEY), 1),N'.00',N'') + N' forward records fetched; ' + ELSE N'' END - RAISERROR('Now starting diagnostic analysis',10,1) WITH NOWAIT; + /* rows will only be in this dmv when data is in memory for the table */ + ), N'Table metadata not in memory'), + index_lock_wait_summary AS ISNULL( + CASE WHEN total_row_lock_wait_count = 0 AND total_page_lock_wait_count = 0 AND + total_index_lock_promotion_attempt_count = 0 THEN N'0 lock waits; ' + + CASE WHEN lock_escalation_desc = N'DISABLE' THEN N'Lock escalation DISABLE.' + ELSE N'' + END + ELSE + CASE WHEN total_row_lock_wait_count > 0 THEN + N'Row lock waits: ' + REPLACE(CONVERT(NVARCHAR(30),CAST(total_row_lock_wait_count AS MONEY), 1), N'.00', N'') + + N'; total duration: ' + + CASE WHEN total_row_lock_wait_in_ms >= 60000 THEN /*More than 1 min*/ + REPLACE(CONVERT(NVARCHAR(30),CAST((total_row_lock_wait_in_ms/60000) AS MONEY), 1), N'.00', N'') + N' minutes; ' + ELSE + REPLACE(CONVERT(NVARCHAR(30),CAST(ISNULL(total_row_lock_wait_in_ms/1000,0) AS MONEY), 1), N'.00', N'') + N' seconds; ' + END + + N'avg duration: ' + + CASE WHEN avg_row_lock_wait_in_ms >= 60000 THEN /*More than 1 min*/ + REPLACE(CONVERT(NVARCHAR(30),CAST((avg_row_lock_wait_in_ms/60000) AS MONEY), 1), N'.00', N'') + N' minutes; ' + ELSE + REPLACE(CONVERT(NVARCHAR(30),CAST(ISNULL(avg_row_lock_wait_in_ms/1000,0) AS MONEY), 1), N'.00', N'') + N' seconds; ' + END + ELSE N'' + END + + CASE WHEN total_page_lock_wait_count > 0 THEN + N'Page lock waits: ' + REPLACE(CONVERT(NVARCHAR(30),CAST(total_page_lock_wait_count AS MONEY), 1), N'.00', N'') + + N'; total duration: ' + + CASE WHEN total_page_lock_wait_in_ms >= 60000 THEN /*More than 1 min*/ + REPLACE(CONVERT(NVARCHAR(30),CAST((total_page_lock_wait_in_ms/60000) AS MONEY), 1), N'.00', N'') + N' minutes; ' + ELSE + REPLACE(CONVERT(NVARCHAR(30),CAST(ISNULL(total_page_lock_wait_in_ms/1000,0) AS MONEY), 1), N'.00', N'') + N' seconds; ' + END + + N'avg duration: ' + + CASE WHEN avg_page_lock_wait_in_ms >= 60000 THEN /*More than 1 min*/ + REPLACE(CONVERT(NVARCHAR(30),CAST((avg_page_lock_wait_in_ms/60000) AS MONEY), 1), N'.00', N'') + N' minutes; ' + ELSE + REPLACE(CONVERT(NVARCHAR(30),CAST(ISNULL(avg_page_lock_wait_in_ms/1000,0) AS MONEY), 1), N'.00', N'') + N' seconds; ' + END + ELSE N'' + END + + CASE WHEN total_index_lock_promotion_attempt_count > 0 THEN + N'Lock escalation attempts: ' + REPLACE(CONVERT(NVARCHAR(30),CAST(total_index_lock_promotion_attempt_count AS MONEY), 1), N'.00', N'') + + N'; Actual Escalations: ' + REPLACE(CONVERT(NVARCHAR(30),CAST(ISNULL(total_index_lock_promotion_count,0) AS MONEY), 1), N'.00', N'') +N'; ' + ELSE N'' + END + + CASE WHEN lock_escalation_desc = N'DISABLE' THEN + N'Lock escalation is disabled.' + ELSE N'' + END + END + ,'Error- NULL in computed column') + ); - /* - We start by creating #BlitzFirstResults. It's a temp table that will store - the results from our checks. Throughout the rest of this stored procedure, - we're running a series of checks looking for dangerous things inside the SQL - Server. When we find a problem, we insert rows into the temp table. At the - end, we return these results to the end user. + CREATE TABLE #IndexColumns + ( + [database_id] INT NOT NULL, + [schema_name] NVARCHAR(128), + [object_id] INT NOT NULL , + [index_id] INT NOT NULL , + [key_ordinal] INT NULL , + is_included_column BIT NULL , + is_descending_key BIT NULL , + [partition_ordinal] INT NULL , + column_name NVARCHAR(256) NOT NULL , + system_type_name NVARCHAR(256) NOT NULL, + max_length SMALLINT NOT NULL, + [precision] TINYINT NOT NULL, + [scale] TINYINT NOT NULL, + collation_name NVARCHAR(256) NULL, + is_nullable BIT NULL, + is_identity BIT NULL, + is_computed BIT NULL, + is_replicated BIT NULL, + is_sparse BIT NULL, + is_filestream BIT NULL, + seed_value DECIMAL(38,0) NULL, + increment_value DECIMAL(38,0) NULL , + last_value DECIMAL(38,0) NULL, + is_not_for_replication BIT NULL + ); + CREATE CLUSTERED INDEX CLIX_database_id_object_id_index_id ON #IndexColumns + (database_id, object_id, index_id); - #BlitzFirstResults has a CheckID field, but there's no Check table. As we do - checks, we insert data into this table, and we manually put in the CheckID. - We (Brent Ozar Unlimited) maintain a list of the checks by ID#. You can - download that from http://FirstResponderKit.org if you want to build - a tool that relies on the output of sp_BlitzFirst. - */ + CREATE TABLE #MissingIndexes + ([database_id] INT NOT NULL, + [object_id] INT NOT NULL, + [database_name] NVARCHAR(128) NOT NULL , + [schema_name] NVARCHAR(128) NOT NULL , + [table_name] NVARCHAR(128), + [statement] NVARCHAR(512) NOT NULL, + magic_benefit_number AS (( user_seeks + user_scans ) * avg_total_user_cost * avg_user_impact), + avg_total_user_cost NUMERIC(29,4) NOT NULL, + avg_user_impact NUMERIC(29,1) NOT NULL, + user_seeks BIGINT NOT NULL, + user_scans BIGINT NOT NULL, + unique_compiles BIGINT NULL, + equality_columns NVARCHAR(MAX), + equality_columns_with_data_type NVARCHAR(MAX), + inequality_columns NVARCHAR(MAX), + inequality_columns_with_data_type NVARCHAR(MAX), + included_columns NVARCHAR(MAX), + included_columns_with_data_type NVARCHAR(MAX), + is_low BIT, + [index_estimated_impact] AS + REPLACE(CONVERT(NVARCHAR(256),CAST(CAST( + (user_seeks + user_scans) + AS BIGINT) AS MONEY), 1), '.00', '') + N' use' + + CASE WHEN (user_seeks + user_scans) > 1 THEN N's' ELSE N'' END + +N'; Impact: ' + CAST(avg_user_impact AS NVARCHAR(30)) + + N'%; Avg query cost: ' + + CAST(avg_total_user_cost AS NVARCHAR(30)), + [missing_index_details] AS + CASE WHEN COALESCE(equality_columns_with_data_type,equality_columns) IS NOT NULL + THEN N'EQUALITY: ' + COALESCE(CAST(equality_columns_with_data_type AS NVARCHAR(MAX)), CAST(equality_columns AS NVARCHAR(MAX))) + N' ' + ELSE N'' END + + CASE WHEN COALESCE(inequality_columns_with_data_type,inequality_columns) IS NOT NULL + THEN N'INEQUALITY: ' + COALESCE(CAST(inequality_columns_with_data_type AS NVARCHAR(MAX)), CAST(inequality_columns AS NVARCHAR(MAX))) + N' ' + ELSE N'' END + - IF OBJECT_ID('tempdb..#BlitzFirstResults') IS NOT NULL - DROP TABLE #BlitzFirstResults; - CREATE TABLE #BlitzFirstResults - ( - ID INT IDENTITY(1, 1) PRIMARY KEY CLUSTERED, - CheckID INT NOT NULL, - Priority TINYINT NOT NULL, - FindingsGroup VARCHAR(50) NOT NULL, - Finding VARCHAR(200) NOT NULL, - URL VARCHAR(200) NULL, - Details NVARCHAR(MAX) NULL, - HowToStopIt NVARCHAR(MAX) NULL, - QueryPlan [XML] NULL, - QueryText NVARCHAR(MAX) NULL, - StartTime DATETIMEOFFSET NULL, - LoginName NVARCHAR(128) NULL, - NTUserName NVARCHAR(128) NULL, - OriginalLoginName NVARCHAR(128) NULL, - ProgramName NVARCHAR(128) NULL, - HostName NVARCHAR(128) NULL, - DatabaseID INT NULL, - DatabaseName NVARCHAR(128) NULL, - OpenTransactionCount INT NULL, - QueryStatsNowID INT NULL, - QueryStatsFirstID INT NULL, - PlanHandle VARBINARY(64) NULL, - DetailsInt INT NULL, - QueryHash BINARY(8) + CASE WHEN COALESCE(included_columns_with_data_type,included_columns) IS NOT NULL + THEN N'INCLUDE: ' + COALESCE(CAST(included_columns_with_data_type AS NVARCHAR(MAX)), CAST(included_columns AS NVARCHAR(MAX))) + N' ' + ELSE N'' END, + [create_tsql] AS N'CREATE INDEX [' + + LEFT(REPLACE(REPLACE(REPLACE(REPLACE( + ISNULL(equality_columns,N'')+ + CASE WHEN equality_columns IS NOT NULL AND inequality_columns IS NOT NULL THEN N'_' ELSE N'' END + + ISNULL(inequality_columns,''),',','') + ,'[',''),']',''),' ','_') + + CASE WHEN included_columns IS NOT NULL THEN N'_Includes' ELSE N'' END, 128) + N'] ON ' + + [statement] + N' (' + ISNULL(equality_columns,N'') + + CASE WHEN equality_columns IS NOT NULL AND inequality_columns IS NOT NULL THEN N', ' ELSE N'' END + + CASE WHEN inequality_columns IS NOT NULL THEN inequality_columns ELSE N'' END + + ') ' + CASE WHEN included_columns IS NOT NULL THEN N' INCLUDE (' + included_columns + N')' ELSE N'' END + + N' WITH (' + + N'FILLFACTOR=100, ONLINE=?, SORT_IN_TEMPDB=?, DATA_COMPRESSION=?' + + N')' + + N';' + , + [more_info] AS N'EXEC dbo.sp_BlitzIndex @DatabaseName=' + QUOTENAME([database_name],'''') + + N', @SchemaName=' + QUOTENAME([schema_name],'''') + N', @TableName=' + QUOTENAME([table_name],'''') + N';', + [sample_query_plan] XML NULL + ); + + CREATE TABLE #ForeignKeys ( + [database_id] INT NOT NULL, + [database_name] NVARCHAR(128) NOT NULL , + [schema_name] NVARCHAR(128) NOT NULL , + foreign_key_name NVARCHAR(256), + parent_object_id INT, + parent_object_name NVARCHAR(256), + referenced_object_id INT, + referenced_object_name NVARCHAR(256), + is_disabled BIT, + is_not_trusted BIT, + is_not_for_replication BIT, + parent_fk_columns NVARCHAR(MAX), + referenced_fk_columns NVARCHAR(MAX), + update_referential_action_desc NVARCHAR(16), + delete_referential_action_desc NVARCHAR(60) + ); + + CREATE TABLE #IndexCreateTsql ( + index_sanity_id INT NOT NULL, + create_tsql NVARCHAR(MAX) NOT NULL ); - IF OBJECT_ID('tempdb..#WaitStats') IS NOT NULL - DROP TABLE #WaitStats; - CREATE TABLE #WaitStats (Pass TINYINT NOT NULL, wait_type NVARCHAR(60), wait_time_ms BIGINT, signal_wait_time_ms BIGINT, waiting_tasks_count BIGINT, SampleTime DATETIMEOFFSET); + CREATE TABLE #DatabaseList ( + DatabaseName NVARCHAR(256), + secondary_role_allow_connections_desc NVARCHAR(50) - IF OBJECT_ID('tempdb..#FileStats') IS NOT NULL - DROP TABLE #FileStats; - CREATE TABLE #FileStats ( - ID INT IDENTITY(1, 1) PRIMARY KEY CLUSTERED, - Pass TINYINT NOT NULL, - SampleTime DATETIMEOFFSET NOT NULL, - DatabaseID INT NOT NULL, - FileID INT NOT NULL, - DatabaseName NVARCHAR(256) , - FileLogicalName NVARCHAR(256) , - TypeDesc NVARCHAR(60) , - SizeOnDiskMB BIGINT , - io_stall_read_ms BIGINT , - num_of_reads BIGINT , - bytes_read BIGINT , - io_stall_write_ms BIGINT , - num_of_writes BIGINT , - bytes_written BIGINT, - PhysicalName NVARCHAR(520) , - avg_stall_read_ms INT , - avg_stall_write_ms INT - ); + ); - IF OBJECT_ID('tempdb..#QueryStats') IS NOT NULL - DROP TABLE #QueryStats; - CREATE TABLE #QueryStats ( - ID INT IDENTITY(1, 1) PRIMARY KEY CLUSTERED, - Pass INT NOT NULL, - SampleTime DATETIMEOFFSET NOT NULL, - [sql_handle] VARBINARY(64), - statement_start_offset INT, - statement_end_offset INT, - plan_generation_num BIGINT, - plan_handle VARBINARY(64), - execution_count BIGINT, - total_worker_time BIGINT, - total_physical_reads BIGINT, - total_logical_writes BIGINT, - total_logical_reads BIGINT, - total_clr_time BIGINT, - total_elapsed_time BIGINT, - creation_time DATETIMEOFFSET, - query_hash BINARY(8), - query_plan_hash BINARY(8), - Points TINYINT - ); + CREATE TABLE #PartitionCompressionInfo ( + [index_sanity_id] INT NULL, + [partition_compression_detail] NVARCHAR(4000) NULL + ); - IF OBJECT_ID('tempdb..#PerfmonStats') IS NOT NULL - DROP TABLE #PerfmonStats; - CREATE TABLE #PerfmonStats ( - ID INT IDENTITY(1, 1) PRIMARY KEY CLUSTERED, - Pass TINYINT NOT NULL, - SampleTime DATETIMEOFFSET NOT NULL, - [object_name] NVARCHAR(128) NOT NULL, - [counter_name] NVARCHAR(128) NOT NULL, - [instance_name] NVARCHAR(128) NULL, - [cntr_value] BIGINT NULL, - [cntr_type] INT NOT NULL, - [value_delta] BIGINT NULL, - [value_per_second] DECIMAL(18,2) NULL - ); + CREATE TABLE #Statistics ( + database_id INT NOT NULL, + database_name NVARCHAR(256) NOT NULL, + table_name NVARCHAR(128) NULL, + schema_name NVARCHAR(128) NULL, + index_name NVARCHAR(128) NULL, + column_names NVARCHAR(MAX) NULL, + statistics_name NVARCHAR(128) NULL, + last_statistics_update DATETIME NULL, + days_since_last_stats_update INT NULL, + rows BIGINT NULL, + rows_sampled BIGINT NULL, + percent_sampled DECIMAL(18, 1) NULL, + histogram_steps INT NULL, + modification_counter BIGINT NULL, + percent_modifications DECIMAL(18, 1) NULL, + modifications_before_auto_update INT 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 + ); - IF OBJECT_ID('tempdb..#PerfmonCounters') IS NOT NULL - DROP TABLE #PerfmonCounters; - CREATE TABLE #PerfmonCounters ( - ID INT IDENTITY(1, 1) PRIMARY KEY CLUSTERED, - [object_name] NVARCHAR(128) NOT NULL, - [counter_name] NVARCHAR(128) NOT NULL, - [instance_name] NVARCHAR(128) NULL - ); + CREATE TABLE #ComputedColumns + ( + index_sanity_id INT IDENTITY(1, 1) NOT NULL, + database_name NVARCHAR(128) NULL, + database_id INT NOT NULL, + table_name NVARCHAR(128) NOT NULL, + schema_name NVARCHAR(128) NOT NULL, + column_name NVARCHAR(128) NULL, + is_nullable BIT NULL, + definition NVARCHAR(MAX) NULL, + uses_database_collation BIT NOT NULL, + is_persisted BIT NOT NULL, + is_computed BIT NOT NULL, + is_function INT NOT NULL, + column_definition NVARCHAR(MAX) NULL + ); + + CREATE TABLE #TraceStatus + ( + TraceFlag NVARCHAR(10) , + status BIT , + Global BIT , + Session BIT + ); - IF OBJECT_ID('tempdb..#FilterPlansByDatabase') IS NOT NULL - DROP TABLE #FilterPlansByDatabase; - CREATE TABLE #FilterPlansByDatabase (DatabaseID INT PRIMARY KEY CLUSTERED); + CREATE TABLE #TemporalTables + ( + index_sanity_id INT IDENTITY(1, 1) NOT NULL, + database_name NVARCHAR(128) NOT NULL, + database_id INT NOT NULL, + schema_name NVARCHAR(128) NOT NULL, + table_name NVARCHAR(128) NOT NULL, + history_table_name NVARCHAR(128) NOT NULL, + history_schema_name NVARCHAR(128) NOT NULL, + start_column_name NVARCHAR(128) NOT NULL, + end_column_name NVARCHAR(128) NOT NULL, + period_name NVARCHAR(128) NOT NULL + ); - IF OBJECT_ID('tempdb..##WaitCategories') IS NULL - BEGIN - /* We reuse this one by default rather than recreate it every time. */ - CREATE TABLE ##WaitCategories - ( - WaitType NVARCHAR(60) PRIMARY KEY CLUSTERED, - WaitCategory NVARCHAR(128) NOT NULL, - Ignorable BIT DEFAULT 0 - ); - END; /* IF OBJECT_ID('tempdb..##WaitCategories') IS NULL */ + CREATE TABLE #CheckConstraints + ( + index_sanity_id INT IDENTITY(1, 1) NOT NULL, + database_name NVARCHAR(128) NULL, + database_id INT NOT NULL, + table_name NVARCHAR(128) NOT NULL, + schema_name NVARCHAR(128) NOT NULL, + constraint_name NVARCHAR(128) NULL, + is_disabled BIT NULL, + definition NVARCHAR(MAX) NULL, + uses_database_collation BIT NOT NULL, + is_not_trusted BIT NOT NULL, + is_function INT NOT NULL, + column_definition NVARCHAR(MAX) NULL + ); - IF OBJECT_ID ('tempdb..#checkversion') IS NOT NULL - DROP TABLE #checkversion; - CREATE TABLE #checkversion ( - version NVARCHAR(128), - common_version AS SUBSTRING(version, 1, CHARINDEX('.', version) + 1 ), - major AS PARSENAME(CONVERT(VARCHAR(32), version), 4), - minor AS PARSENAME(CONVERT(VARCHAR(32), version), 3), - build AS PARSENAME(CONVERT(VARCHAR(32), version), 2), - revision AS PARSENAME(CONVERT(VARCHAR(32), version), 1) - ); - - IF 527 <> (SELECT COALESCE(SUM(1),0) FROM ##WaitCategories) - BEGIN - TRUNCATE TABLE ##WaitCategories; - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('ASYNC_IO_COMPLETION','Other Disk IO',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('ASYNC_NETWORK_IO','Network IO',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BACKUPIO','Other Disk IO',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_CONNECTION_RECEIVE_TASK','Service Broker',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_DISPATCHER','Service Broker',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_ENDPOINT_STATE_MUTEX','Service Broker',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_EVENTHANDLER','Service Broker',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_FORWARDER','Service Broker',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_INIT','Service Broker',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_MASTERSTART','Service Broker',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_RECEIVE_WAITFOR','User Wait',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_REGISTERALLENDPOINTS','Service Broker',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_SERVICE','Service Broker',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_SHUTDOWN','Service Broker',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_START','Service Broker',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_TASK_SHUTDOWN','Service Broker',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_TASK_STOP','Service Broker',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_TASK_SUBMIT','Service Broker',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_TO_FLUSH','Service Broker',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_TRANSMISSION_OBJECT','Service Broker',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_TRANSMISSION_TABLE','Service Broker',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_TRANSMISSION_WORK','Service Broker',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_TRANSMITTER','Service Broker',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CHECKPOINT_QUEUE','Idle',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CHKPT','Tran Log IO',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CLR_AUTO_EVENT','SQL CLR',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CLR_CRST','SQL CLR',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CLR_JOIN','SQL CLR',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CLR_MANUAL_EVENT','SQL CLR',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CLR_MEMORY_SPY','SQL CLR',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CLR_MONITOR','SQL CLR',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CLR_RWLOCK_READER','SQL CLR',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CLR_RWLOCK_WRITER','SQL CLR',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CLR_SEMAPHORE','SQL CLR',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CLR_TASK_START','SQL CLR',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CLRHOST_STATE_ACCESS','SQL CLR',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CMEMPARTITIONED','Memory',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CMEMTHREAD','Memory',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CXPACKET','Parallelism',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CXCONSUMER','Parallelism',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DBMIRROR_DBM_EVENT','Mirroring',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DBMIRROR_DBM_MUTEX','Mirroring',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DBMIRROR_EVENTS_QUEUE','Mirroring',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DBMIRROR_SEND','Mirroring',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DBMIRROR_WORKER_QUEUE','Mirroring',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DBMIRRORING_CMD','Mirroring',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DIRTY_PAGE_POLL','Other',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DIRTY_PAGE_TABLE_LOCK','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DISPATCHER_QUEUE_SEMAPHORE','Other',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DPT_ENTRY_LOCK','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DTC','Transaction',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DTC_ABORT_REQUEST','Transaction',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DTC_RESOLVE','Transaction',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DTC_STATE','Transaction',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DTC_TMDOWN_REQUEST','Transaction',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DTC_WAITFOR_OUTCOME','Transaction',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DTCNEW_ENLIST','Transaction',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DTCNEW_PREPARE','Transaction',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DTCNEW_RECOVERY','Transaction',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DTCNEW_TM','Transaction',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DTCNEW_TRANSACTION_ENLISTMENT','Transaction',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DTCPNTSYNC','Transaction',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('EE_PMOLOCK','Memory',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('EXCHANGE','Parallelism',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('EXTERNAL_SCRIPT_NETWORK_IOF','Network IO',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FCB_REPLICA_READ','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FCB_REPLICA_WRITE','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_COMPROWSET_RWLOCK','Full Text Search',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_IFTS_RWLOCK','Full Text Search',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_IFTS_SCHEDULER_IDLE_WAIT','Idle',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_IFTSHC_MUTEX','Full Text Search',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_IFTSISM_MUTEX','Full Text Search',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_MASTER_MERGE','Full Text Search',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_MASTER_MERGE_COORDINATOR','Full Text Search',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_METADATA_MUTEX','Full Text Search',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_PROPERTYLIST_CACHE','Full Text Search',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_RESTART_CRAWL','Full Text Search',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FULLTEXT GATHERER','Full Text Search',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_AG_MUTEX','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_AR_CRITICAL_SECTION_ENTRY','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_AR_MANAGER_MUTEX','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_AR_UNLOAD_COMPLETED','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_ARCONTROLLER_NOTIFICATIONS_SUBSCRIBER_LIST','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_BACKUP_BULK_LOCK','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_BACKUP_QUEUE','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_CLUSAPI_CALL','Replication',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_COMPRESSED_CACHE_SYNC','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_CONNECTIVITY_INFO','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_DATABASE_FLOW_CONTROL','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_DATABASE_VERSIONING_STATE','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_DATABASE_WAIT_FOR_RECOVERY','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_DATABASE_WAIT_FOR_RESTART','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_DATABASE_WAIT_FOR_TRANSITION_TO_VERSIONING','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_DB_COMMAND','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_DB_OP_COMPLETION_SYNC','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_DB_OP_START_SYNC','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_DBR_SUBSCRIBER','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_DBR_SUBSCRIBER_FILTER_LIST','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_DBSEEDING','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_DBSEEDING_LIST','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_DBSTATECHANGE_SYNC','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_FABRIC_CALLBACK','Replication',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_FILESTREAM_BLOCK_FLUSH','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_FILESTREAM_FILE_CLOSE','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_FILESTREAM_FILE_REQUEST','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_FILESTREAM_IOMGR','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_FILESTREAM_IOMGR_IOCOMPLETION','Replication',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_FILESTREAM_MANAGER','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_FILESTREAM_PREPROC','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_GROUP_COMMIT','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_LOGCAPTURE_SYNC','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_LOGCAPTURE_WAIT','Replication',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_LOGPROGRESS_SYNC','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_NOTIFICATION_DEQUEUE','Replication',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_NOTIFICATION_WORKER_EXCLUSIVE_ACCESS','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_NOTIFICATION_WORKER_STARTUP_SYNC','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_NOTIFICATION_WORKER_TERMINATION_SYNC','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_PARTNER_SYNC','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_READ_ALL_NETWORKS','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_RECOVERY_WAIT_FOR_CONNECTION','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_RECOVERY_WAIT_FOR_UNDO','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_REPLICAINFO_SYNC','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_SEEDING_CANCELLATION','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_SEEDING_FILE_LIST','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_SEEDING_LIMIT_BACKUPS','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_SEEDING_SYNC_COMPLETION','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_SEEDING_TIMEOUT_TASK','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_SEEDING_WAIT_FOR_COMPLETION','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_SYNC_COMMIT','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_SYNCHRONIZING_THROTTLE','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_TDS_LISTENER_SYNC','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_TDS_LISTENER_SYNC_PROCESSING','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_THROTTLE_LOG_RATE_GOVERNOR','Log Rate Governor',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_TIMER_TASK','Replication',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_TRANSPORT_DBRLIST','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_TRANSPORT_FLOW_CONTROL','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_TRANSPORT_SESSION','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_WORK_POOL','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_WORK_QUEUE','Replication',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_XRF_STACK_ACCESS','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('INSTANCE_LOG_RATE_GOVERNOR','Log Rate Governor',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('IO_COMPLETION','Other Disk IO',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('IO_QUEUE_LIMIT','Other Disk IO',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('IO_RETRY','Other Disk IO',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LATCH_DT','Latch',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LATCH_EX','Latch',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LATCH_KP','Latch',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LATCH_NL','Latch',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LATCH_SH','Latch',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LATCH_UP','Latch',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LAZYWRITER_SLEEP','Idle',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_BU','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_BU_ABORT_BLOCKERS','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_BU_LOW_PRIORITY','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_IS','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_IS_ABORT_BLOCKERS','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_IS_LOW_PRIORITY','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_IU','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_IU_ABORT_BLOCKERS','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_IU_LOW_PRIORITY','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_IX','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_IX_ABORT_BLOCKERS','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_IX_LOW_PRIORITY','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RIn_NL','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RIn_NL_ABORT_BLOCKERS','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RIn_NL_LOW_PRIORITY','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RIn_S','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RIn_S_ABORT_BLOCKERS','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RIn_S_LOW_PRIORITY','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RIn_U','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RIn_U_ABORT_BLOCKERS','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RIn_U_LOW_PRIORITY','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RIn_X','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RIn_X_ABORT_BLOCKERS','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RIn_X_LOW_PRIORITY','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RS_S','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RS_S_ABORT_BLOCKERS','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RS_S_LOW_PRIORITY','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RS_U','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RS_U_ABORT_BLOCKERS','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RS_U_LOW_PRIORITY','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RX_S','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RX_S_ABORT_BLOCKERS','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RX_S_LOW_PRIORITY','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RX_U','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RX_U_ABORT_BLOCKERS','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RX_U_LOW_PRIORITY','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RX_X','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RX_X_ABORT_BLOCKERS','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RX_X_LOW_PRIORITY','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_S','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_S_ABORT_BLOCKERS','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_S_LOW_PRIORITY','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_SCH_M','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_SCH_M_ABORT_BLOCKERS','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_SCH_M_LOW_PRIORITY','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_SCH_S','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_SCH_S_ABORT_BLOCKERS','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_SCH_S_LOW_PRIORITY','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_SIU','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_SIU_ABORT_BLOCKERS','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_SIU_LOW_PRIORITY','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_SIX','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_SIX_ABORT_BLOCKERS','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_SIX_LOW_PRIORITY','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_U','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_U_ABORT_BLOCKERS','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_U_LOW_PRIORITY','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_UIX','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_UIX_ABORT_BLOCKERS','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_UIX_LOW_PRIORITY','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_X','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_X_ABORT_BLOCKERS','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_X_LOW_PRIORITY','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LOG_RATE_GOVERNOR','Tran Log IO',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LOGBUFFER','Tran Log IO',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LOGMGR','Tran Log IO',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LOGMGR_FLUSH','Tran Log IO',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LOGMGR_PMM_LOG','Tran Log IO',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LOGMGR_QUEUE','Idle',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LOGMGR_RESERVE_APPEND','Tran Log IO',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('MEMORY_ALLOCATION_EXT','Memory',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('MEMORY_GRANT_UPDATE','Memory',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('MSQL_XACT_MGR_MUTEX','Transaction',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('MSQL_XACT_MUTEX','Transaction',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('MSSEARCH','Full Text Search',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('NET_WAITFOR_PACKET','Network IO',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('ONDEMAND_TASK_QUEUE','Idle',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PAGEIOLATCH_DT','Buffer IO',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PAGEIOLATCH_EX','Buffer IO',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PAGEIOLATCH_KP','Buffer IO',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PAGEIOLATCH_NL','Buffer IO',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PAGEIOLATCH_SH','Buffer IO',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PAGEIOLATCH_UP','Buffer IO',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PAGELATCH_DT','Buffer Latch',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PAGELATCH_EX','Buffer Latch',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PAGELATCH_KP','Buffer Latch',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PAGELATCH_NL','Buffer Latch',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PAGELATCH_SH','Buffer Latch',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PAGELATCH_UP','Buffer Latch',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PARALLEL_REDO_DRAIN_WORKER','Replication',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PARALLEL_REDO_FLOW_CONTROL','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PARALLEL_REDO_LOG_CACHE','Replication',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PARALLEL_REDO_TRAN_LIST','Replication',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PARALLEL_REDO_TRAN_TURN','Replication',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PARALLEL_REDO_WORKER_SYNC','Replication',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PARALLEL_REDO_WORKER_WAIT_WORK','Replication',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('POOL_LOG_RATE_GOVERNOR','Log Rate Governor',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_ABR','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_CLOSEBACKUPMEDIA','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_CLOSEBACKUPTAPE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_CLOSEBACKUPVDIDEVICE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_CLUSAPI_CLUSTERRESOURCECONTROL','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_COCREATEINSTANCE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_COGETCLASSOBJECT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_CREATEACCESSOR','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_DELETEROWS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_GETCOMMANDTEXT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_GETDATA','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_GETNEXTROWS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_GETRESULT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_GETROWSBYBOOKMARK','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_LBFLUSH','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_LBLOCKREGION','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_LBREADAT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_LBSETSIZE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_LBSTAT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_LBUNLOCKREGION','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_LBWRITEAT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_QUERYINTERFACE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_RELEASE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_RELEASEACCESSOR','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_RELEASEROWS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_RELEASESESSION','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_RESTARTPOSITION','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_SEQSTRMREAD','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_SEQSTRMREADANDWRITE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_SETDATAFAILURE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_SETPARAMETERINFO','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_SETPARAMETERPROPERTIES','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_STRMLOCKREGION','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_STRMSEEKANDREAD','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_STRMSEEKANDWRITE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_STRMSETSIZE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_STRMSTAT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_STRMUNLOCKREGION','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_CONSOLEWRITE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_CREATEPARAM','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DEBUG','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DFSADDLINK','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DFSLINKEXISTCHECK','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DFSLINKHEALTHCHECK','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DFSREMOVELINK','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DFSREMOVEROOT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DFSROOTFOLDERCHECK','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DFSROOTINIT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DFSROOTSHARECHECK','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DTC_ABORT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DTC_ABORTREQUESTDONE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DTC_BEGINTRANSACTION','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DTC_COMMITREQUESTDONE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DTC_ENLIST','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DTC_PREPAREREQUESTDONE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_FILESIZEGET','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_FSAOLEDB_ABORTTRANSACTION','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_FSAOLEDB_COMMITTRANSACTION','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_FSAOLEDB_STARTTRANSACTION','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_FSRECOVER_UNCONDITIONALUNDO','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_GETRMINFO','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_HADR_LEASE_MECHANISM','Preemptive',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_HTTP_EVENT_WAIT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_HTTP_REQUEST','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_LOCKMONITOR','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_MSS_RELEASE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_ODBCOPS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OLE_UNINIT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OLEDB_ABORTORCOMMITTRAN','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OLEDB_ABORTTRAN','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OLEDB_GETDATASOURCE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OLEDB_GETLITERALINFO','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OLEDB_GETPROPERTIES','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OLEDB_GETPROPERTYINFO','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OLEDB_GETSCHEMALOCK','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OLEDB_JOINTRANSACTION','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OLEDB_RELEASE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OLEDB_SETPROPERTIES','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OLEDBOPS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_ACCEPTSECURITYCONTEXT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_ACQUIRECREDENTIALSHANDLE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_AUTHENTICATIONOPS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_AUTHORIZATIONOPS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_AUTHZGETINFORMATIONFROMCONTEXT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_AUTHZINITIALIZECONTEXTFROMSID','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_AUTHZINITIALIZERESOURCEMANAGER','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_BACKUPREAD','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_CLOSEHANDLE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_CLUSTEROPS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_COMOPS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_COMPLETEAUTHTOKEN','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_COPYFILE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_CREATEDIRECTORY','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_CREATEFILE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_CRYPTACQUIRECONTEXT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_CRYPTIMPORTKEY','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_CRYPTOPS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_DECRYPTMESSAGE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_DELETEFILE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_DELETESECURITYCONTEXT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_DEVICEIOCONTROL','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_DEVICEOPS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_DIRSVC_NETWORKOPS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_DISCONNECTNAMEDPIPE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_DOMAINSERVICESOPS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_DSGETDCNAME','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_DTCOPS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_ENCRYPTMESSAGE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_FILEOPS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_FINDFILE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_FLUSHFILEBUFFERS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_FORMATMESSAGE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_FREECREDENTIALSHANDLE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_FREELIBRARY','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_GENERICOPS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_GETADDRINFO','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_GETCOMPRESSEDFILESIZE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_GETDISKFREESPACE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_GETFILEATTRIBUTES','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_GETFILESIZE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_GETFINALFILEPATHBYHANDLE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_GETLONGPATHNAME','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_GETPROCADDRESS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_GETVOLUMENAMEFORVOLUMEMOUNTPOINT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_GETVOLUMEPATHNAME','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_INITIALIZESECURITYCONTEXT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_LIBRARYOPS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_LOADLIBRARY','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_LOGONUSER','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_LOOKUPACCOUNTSID','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_MESSAGEQUEUEOPS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_MOVEFILE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_NETGROUPGETUSERS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_NETLOCALGROUPGETMEMBERS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_NETUSERGETGROUPS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_NETUSERGETLOCALGROUPS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_NETUSERMODALSGET','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_NETVALIDATEPASSWORDPOLICY','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_NETVALIDATEPASSWORDPOLICYFREE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_OPENDIRECTORY','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_PDH_WMI_INIT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_PIPEOPS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_PROCESSOPS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_QUERYCONTEXTATTRIBUTES','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_QUERYREGISTRY','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_QUERYSECURITYCONTEXTTOKEN','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_REMOVEDIRECTORY','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_REPORTEVENT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_REVERTTOSELF','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_RSFXDEVICEOPS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_SECURITYOPS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_SERVICEOPS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_SETENDOFFILE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_SETFILEPOINTER','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_SETFILEVALIDDATA','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_SETNAMEDSECURITYINFO','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_SQLCLROPS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_SQMLAUNCH','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_VERIFYSIGNATURE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_VERIFYTRUST','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_VSSOPS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_WAITFORSINGLEOBJECT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_WINSOCKOPS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_WRITEFILE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_WRITEFILEGATHER','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_WSASETLASTERROR','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_REENLIST','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_RESIZELOG','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_ROLLFORWARDREDO','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_ROLLFORWARDUNDO','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_SB_STOPENDPOINT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_SERVER_STARTUP','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_SETRMINFO','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_SHAREDMEM_GETDATA','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_SNIOPEN','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_SOSHOST','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_SOSTESTING','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_SP_SERVER_DIAGNOSTICS','Preemptive',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_STARTRM','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_STREAMFCB_CHECKPOINT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_STREAMFCB_RECOVER','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_STRESSDRIVER','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_TESTING','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_TRANSIMPORT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_UNMARSHALPROPAGATIONTOKEN','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_VSS_CREATESNAPSHOT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_VSS_CREATEVOLUMESNAPSHOT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_XE_CALLBACKEXECUTE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_XE_CX_FILE_OPEN','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_XE_CX_HTTP_CALL','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_XE_DISPATCHER','Preemptive',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_XE_ENGINEINIT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_XE_GETTARGETSTATE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_XE_SESSIONCOMMIT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_XE_TARGETFINALIZE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_XE_TARGETINIT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_XE_TIMERRUN','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_XETESTING','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PWAIT_HADR_ACTION_COMPLETED','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PWAIT_HADR_CHANGE_NOTIFIER_TERMINATION_SYNC','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PWAIT_HADR_CLUSTER_INTEGRATION','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PWAIT_HADR_FAILOVER_COMPLETED','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PWAIT_HADR_JOIN','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PWAIT_HADR_OFFLINE_COMPLETED','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PWAIT_HADR_ONLINE_COMPLETED','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PWAIT_HADR_POST_ONLINE_COMPLETED','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PWAIT_HADR_SERVER_READY_CONNECTIONS','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PWAIT_HADR_WORKITEM_COMPLETED','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PWAIT_HADRSIM','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PWAIT_RESOURCE_SEMAPHORE_FT_PARALLEL_QUERY_SYNC','Full Text Search',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('QDS_ASYNC_QUEUE','Other',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('QDS_CLEANUP_STALE_QUERIES_TASK_MAIN_LOOP_SLEEP','Other',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('QDS_PERSIST_TASK_MAIN_LOOP_SLEEP','Other',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('QDS_SHUTDOWN_QUEUE','Other',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('QUERY_TRACEOUT','Tracing',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('REDO_THREAD_PENDING_WORK','Other',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('REPL_CACHE_ACCESS','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('REPL_HISTORYCACHE_ACCESS','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('REPL_SCHEMA_ACCESS','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('REPL_TRANFSINFO_ACCESS','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('REPL_TRANHASHTABLE_ACCESS','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('REPL_TRANTEXTINFO_ACCESS','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('REPLICA_WRITES','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('REQUEST_FOR_DEADLOCK_SEARCH','Idle',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('RESERVED_MEMORY_ALLOCATION_EXT','Memory',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('RESOURCE_SEMAPHORE','Memory',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('RESOURCE_SEMAPHORE_QUERY_COMPILE','Compilation',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_BPOOL_FLUSH','Idle',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_BUFFERPOOL_HELPLW','Idle',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_DBSTARTUP','Idle',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_DCOMSTARTUP','Idle',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_MASTERDBREADY','Idle',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_MASTERMDREADY','Idle',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_MASTERUPGRADED','Idle',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_MEMORYPOOL_ALLOCATEPAGES','Idle',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_MSDBSTARTUP','Idle',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_RETRY_VIRTUALALLOC','Idle',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_SYSTEMTASK','Idle',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_TASK','Idle',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_TEMPDBSTARTUP','Idle',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_WORKSPACE_ALLOCATEPAGE','Idle',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SOS_SCHEDULER_YIELD','CPU',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SOS_WORK_DISPATCHER','Idle',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SP_SERVER_DIAGNOSTICS_SLEEP','Other',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SQLCLR_APPDOMAIN','SQL CLR',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SQLCLR_ASSEMBLY','SQL CLR',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SQLCLR_DEADLOCK_DETECTION','SQL CLR',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SQLCLR_QUANTUM_PUNISHMENT','SQL CLR',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SQLTRACE_BUFFER_FLUSH','Idle',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SQLTRACE_FILE_BUFFER','Tracing',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SQLTRACE_FILE_READ_IO_COMPLETION','Tracing',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SQLTRACE_FILE_WRITE_IO_COMPLETION','Tracing',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SQLTRACE_INCREMENTAL_FLUSH_SLEEP','Idle',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SQLTRACE_PENDING_BUFFER_WRITERS','Tracing',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SQLTRACE_SHUTDOWN','Tracing',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SQLTRACE_WAIT_ENTRIES','Idle',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('THREADPOOL','Worker Thread',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('TRACE_EVTNOTIF','Tracing',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('TRACEWRITE','Tracing',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('TRAN_MARKLATCH_DT','Transaction',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('TRAN_MARKLATCH_EX','Transaction',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('TRAN_MARKLATCH_KP','Transaction',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('TRAN_MARKLATCH_NL','Transaction',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('TRAN_MARKLATCH_SH','Transaction',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('TRAN_MARKLATCH_UP','Transaction',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('TRANSACTION_MUTEX','Transaction',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('UCS_SESSION_REGISTRATION','Other',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('WAIT_FOR_RESULTS','User Wait',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('WAIT_XTP_OFFLINE_CKPT_NEW_LOG','Other',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('WAITFOR','User Wait',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('WRITE_COMPLETION','Other Disk IO',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('WRITELOG','Tran Log IO',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('XACT_OWN_TRANSACTION','Transaction',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('XACT_RECLAIM_SESSION','Transaction',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('XACTLOCKINFO','Transaction',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('XACTWORKSPACE_MUTEX','Transaction',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('XE_DISPATCHER_WAIT','Idle',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('XE_LIVE_TARGET_TVF','Other',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('XE_TIMER_EVENT','Idle',1); - END; /* IF SELECT SUM(1) FROM ##WaitCategories <> 527 */ - - - - IF OBJECT_ID('tempdb..#MasterFiles') IS NOT NULL - DROP TABLE #MasterFiles; - CREATE TABLE #MasterFiles (database_id INT, file_id INT, type_desc NVARCHAR(50), name NVARCHAR(255), physical_name NVARCHAR(255), size BIGINT); - /* Azure SQL Database doesn't have sys.master_files, so we have to build our own. */ - IF ((SERVERPROPERTY('Edition')) = 'SQL Azure' - AND (OBJECT_ID('sys.master_files') IS NULL)) - SET @StringToExecute = 'INSERT INTO #MasterFiles (database_id, file_id, type_desc, name, physical_name, size) SELECT DB_ID(), file_id, type_desc, name, physical_name, size FROM sys.database_files;'; - ELSE - SET @StringToExecute = 'INSERT INTO #MasterFiles (database_id, file_id, type_desc, name, physical_name, size) SELECT database_id, file_id, type_desc, name, physical_name, size FROM sys.master_files;'; - EXEC(@StringToExecute); - - IF @FilterPlansByDatabase IS NOT NULL - BEGIN - IF UPPER(LEFT(@FilterPlansByDatabase,4)) = 'USER' - BEGIN - INSERT INTO #FilterPlansByDatabase (DatabaseID) - SELECT database_id - FROM sys.databases - WHERE [name] NOT IN ('master', 'model', 'msdb', 'tempdb'); - END; - ELSE - BEGIN - SET @FilterPlansByDatabase = @FilterPlansByDatabase + ',' - ;WITH a AS - ( - SELECT CAST(1 AS BIGINT) f, CHARINDEX(',', @FilterPlansByDatabase) t, 1 SEQ - UNION ALL - SELECT t + 1, CHARINDEX(',', @FilterPlansByDatabase, t + 1), SEQ + 1 - FROM a - WHERE CHARINDEX(',', @FilterPlansByDatabase, t + 1) > 0 - ) - INSERT #FilterPlansByDatabase (DatabaseID) - SELECT DISTINCT db.database_id - FROM a - INNER JOIN sys.databases db ON LTRIM(RTRIM(SUBSTRING(@FilterPlansByDatabase, a.f, a.t - a.f))) = db.name - WHERE SUBSTRING(@FilterPlansByDatabase, f, t - f) IS NOT NULL - OPTION (MAXRECURSION 0); - END; - END; - - IF OBJECT_ID('tempdb..#ReadableDBs') IS NOT NULL - DROP TABLE #ReadableDBs; - CREATE TABLE #ReadableDBs ( - database_id INT - ); - - IF EXISTS (SELECT * FROM sys.all_objects o WHERE o.name = 'dm_hadr_database_replica_states') - BEGIN - RAISERROR('Checking for Read intent databases to exclude',0,0) WITH NOWAIT; - - SET @StringToExecute = 'INSERT INTO #ReadableDBs (database_id) SELECT DBs.database_id FROM sys.databases DBs INNER JOIN sys.availability_replicas Replicas ON DBs.replica_id = Replicas.replica_id WHERE replica_server_name NOT IN (SELECT DISTINCT primary_replica FROM sys.dm_hadr_availability_group_states States) AND Replicas.secondary_role_allow_connections_desc = ''READ_ONLY'' AND replica_server_name = @@SERVERNAME;'; - EXEC(@StringToExecute); - - END - - DECLARE @v DECIMAL(6,2), - @build INT, - @memGrantSortSupported BIT = 1; - - RAISERROR (N'Determining SQL Server version.',0,1) WITH NOWAIT; - - INSERT INTO #checkversion (version) - SELECT CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)) - OPTION (RECOMPILE); - - - SELECT @v = common_version , - @build = build - FROM #checkversion - OPTION (RECOMPILE); - - IF (@v < 11) - OR (@v = 11 AND @build < 6020) - OR (@v = 12 AND @build < 5000) - OR (@v = 13 AND @build < 1601) - SET @memGrantSortSupported = 0; - - IF EXISTS (SELECT * FROM sys.all_objects WHERE name = 'dm_exec_query_statistics_xml') - AND ((@v = 13 AND @build >= 5337) /* This DMF causes assertion errors: https://support.microsoft.com/en-us/help/4490136/fix-assertion-error-occurs-when-you-use-sys-dm-exec-query-statistics-x */ - OR (@v = 14 AND @build >= 3162) - OR (@v >= 15) - OR (@v <= 12)) /* Azure */ - SET @dm_exec_query_statistics_xml = 1; - - - SET @StockWarningHeader = '', - @StockDetailsHeader = @StockDetailsHeader + ''; - - /* Get the instance name to use as a Perfmon counter prefix. */ - IF CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) = 'SQL Azure' - SELECT TOP 1 @ServiceName = LEFT(object_name, (CHARINDEX(':', object_name) - 1)) - FROM sys.dm_os_performance_counters; - ELSE - BEGIN - SET @StringToExecute = 'INSERT INTO #PerfmonStats(object_name, Pass, SampleTime, counter_name, cntr_type) SELECT CASE WHEN @@SERVICENAME = ''MSSQLSERVER'' THEN ''SQLServer'' ELSE ''MSSQL$'' + @@SERVICENAME END, 0, SYSDATETIMEOFFSET(), ''stuffing'', 0 ;'; - EXEC(@StringToExecute); - SELECT @ServiceName = object_name FROM #PerfmonStats; - DELETE #PerfmonStats; - END; - - /* Build a list of queries that were run in the last 10 seconds. - We're looking for the death-by-a-thousand-small-cuts scenario - where a query is constantly running, and it doesn't have that - big of an impact individually, but it has a ton of impact - overall. We're going to build this list, and then after we - finish our @Seconds sample, we'll compare our plan cache to - this list to see what ran the most. */ - - /* Populate #QueryStats. SQL 2005 doesn't have query hash or query plan hash. */ - IF @CheckProcedureCache = 1 - BEGIN - RAISERROR('@CheckProcedureCache = 1, capturing first pass of plan cache',10,1) WITH NOWAIT; - IF @@VERSION LIKE 'Microsoft SQL Server 2005%' - BEGIN - IF @FilterPlansByDatabase IS NULL - BEGIN - SET @StringToExecute = N'INSERT INTO #QueryStats ([sql_handle], Pass, SampleTime, statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, Points) - SELECT [sql_handle], 1 AS Pass, SYSDATETIMEOFFSET(), statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, NULL AS query_hash, NULL AS query_plan_hash, 0 - FROM sys.dm_exec_query_stats qs - WHERE qs.last_execution_time >= (DATEADD(ss, -10, SYSDATETIMEOFFSET()));'; - END; - ELSE - BEGIN - SET @StringToExecute = N'INSERT INTO #QueryStats ([sql_handle], Pass, SampleTime, statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, Points) - SELECT [sql_handle], 1 AS Pass, SYSDATETIMEOFFSET(), statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, NULL AS query_hash, NULL AS query_plan_hash, 0 - FROM sys.dm_exec_query_stats qs - CROSS APPLY sys.dm_exec_plan_attributes(qs.plan_handle) AS attr - INNER JOIN #FilterPlansByDatabase dbs ON CAST(attr.value AS INT) = dbs.DatabaseID - WHERE qs.last_execution_time >= (DATEADD(ss, -10, SYSDATETIMEOFFSET())) - AND attr.attribute = ''dbid'';'; - END; - END; - ELSE - BEGIN - IF @FilterPlansByDatabase IS NULL - BEGIN - SET @StringToExecute = N'INSERT INTO #QueryStats ([sql_handle], Pass, SampleTime, statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, Points) - SELECT [sql_handle], 1 AS Pass, SYSDATETIMEOFFSET(), statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, 0 - FROM sys.dm_exec_query_stats qs - WHERE qs.last_execution_time >= (DATEADD(ss, -10, SYSDATETIMEOFFSET()));'; - END; - ELSE - BEGIN - SET @StringToExecute = N'INSERT INTO #QueryStats ([sql_handle], Pass, SampleTime, statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, Points) - SELECT [sql_handle], 1 AS Pass, SYSDATETIMEOFFSET(), statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, 0 - FROM sys.dm_exec_query_stats qs - CROSS APPLY sys.dm_exec_plan_attributes(qs.plan_handle) AS attr - INNER JOIN #FilterPlansByDatabase dbs ON CAST(attr.value AS INT) = dbs.DatabaseID - WHERE qs.last_execution_time >= (DATEADD(ss, -10, SYSDATETIMEOFFSET())) - AND attr.attribute = ''dbid'';'; - END; - END; - EXEC(@StringToExecute); - - /* Get the totals for the entire plan cache */ - INSERT INTO #QueryStats (Pass, SampleTime, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time) - SELECT -1 AS Pass, SYSDATETIMEOFFSET(), SUM(execution_count), SUM(total_worker_time), SUM(total_physical_reads), SUM(total_logical_writes), SUM(total_logical_reads), SUM(total_clr_time), SUM(total_elapsed_time), MIN(creation_time) - FROM sys.dm_exec_query_stats qs; - END; /*IF @CheckProcedureCache = 1 */ - - - IF EXISTS (SELECT * - FROM tempdb.sys.all_objects obj - INNER JOIN tempdb.sys.all_columns col1 ON obj.object_id = col1.object_id AND col1.name = 'object_name' - INNER JOIN tempdb.sys.all_columns col2 ON obj.object_id = col2.object_id AND col2.name = 'counter_name' - INNER JOIN tempdb.sys.all_columns col3 ON obj.object_id = col3.object_id AND col3.name = 'instance_name' - WHERE obj.name LIKE '%CustomPerfmonCounters%') - BEGIN - SET @StringToExecute = 'INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) SELECT [object_name],[counter_name],[instance_name] FROM #CustomPerfmonCounters'; - EXEC(@StringToExecute); - END; - ELSE - BEGIN - /* Add our default Perfmon counters */ - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Forwarded Records/sec', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Page compression attempts/sec', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Page Splits/sec', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Skipped Ghosted Records/sec', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Table Lock Escalations/sec', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Worktables Created/sec', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Availability Group','Active Hadr Threads','_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Availability Replica','Bytes Received from Replica/sec','_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Availability Replica','Bytes Sent to Replica/sec','_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Availability Replica','Bytes Sent to Transport/sec','_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Availability Replica','Flow Control Time (ms/sec)','_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Availability Replica','Flow Control/sec','_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Availability Replica','Resent Messages/sec','_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Availability Replica','Sends to Replica/sec','_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Page life expectancy', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Page reads/sec', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Page writes/sec', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Readahead pages/sec', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Target pages', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Total pages', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Databases','', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Active Transactions','_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Database Flow Control Delay', '_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Database Flow Controls/sec', '_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Group Commit Time', '_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Group Commits/Sec', '_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Log Apply Pending Queue', '_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Log Apply Ready Queue', '_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Log Compression Cache misses/sec', '_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Log remaining for undo', '_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Log Send Queue', '_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Recovery Queue', '_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Redo blocked/sec', '_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Redo Bytes Remaining', '_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Redone Bytes/sec', '_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Databases','Log Bytes Flushed/sec', '_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Databases','Log Growths', '_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Databases','Log Pool LogWriter Pushes/sec', '_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Databases','Log Shrinks', '_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Databases','Transactions/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Databases','Write Transactions/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Databases','XTP Memory Used (KB)',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Exec Statistics','Distributed Query', 'Execs in progress'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Exec Statistics','DTC calls', 'Execs in progress'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Exec Statistics','Extended Procedures', 'Execs in progress'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Exec Statistics','OLEDB calls', 'Execs in progress'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':General Statistics','Active Temp Tables', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':General Statistics','Logins/sec', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':General Statistics','Logouts/sec', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':General Statistics','Mars Deadlocks', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':General Statistics','Processes blocked', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Locks','Number of Deadlocks/sec', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Memory Manager','Memory Grants Pending', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Errors','Errors/sec', '_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','Batch Requests/sec', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','Forced Parameterizations/sec', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','Guided plan executions/sec', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','SQL Attention rate', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','SQL Compilations/sec', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','SQL Re-Compilations/sec', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Workload Group Stats','Query optimizations/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Workload Group Stats','Suboptimal plans/sec',NULL); - /* Below counters added by Jefferson Elias */ - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Worktables From Cache Base',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Worktables From Cache Ratio',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Database pages',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Free pages',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Stolen pages',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Memory Manager','Granted Workspace Memory (KB)',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Memory Manager','Maximum Workspace Memory (KB)',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Memory Manager','Target Server Memory (KB)',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Memory Manager','Total Server Memory (KB)',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Buffer cache hit ratio',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Buffer cache hit ratio base',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Checkpoint pages/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Free list stalls/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Lazy writes/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','Auto-Param Attempts/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','Failed Auto-Params/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','Safe Auto-Params/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','Unsafe Auto-Params/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Workfiles Created/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':General Statistics','User Connections',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Latches','Average Latch Wait Time (ms)',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Latches','Average Latch Wait Time Base',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Latches','Latch Waits/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Latches','Total Latch Wait Time (ms)',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Locks','Average Wait Time (ms)',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Locks','Average Wait Time Base',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Locks','Lock Requests/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Locks','Lock Timeouts/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Locks','Lock Wait Time (ms)',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Locks','Lock Waits/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Transactions','Longest Transaction Running Time',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Full Scans/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Index Searches/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Page lookups/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Cursor Manager by Type','Active cursors',NULL); - /* Below counters are for In-Memory OLTP (Hekaton), which have a different naming convention. - And yes, they actually hard-coded the version numbers into the counters, and SQL 2019 still says 2017, oddly. - For why, see: https://connect.microsoft.com/SQLServer/feedback/details/817216/xtp-perfmon-counters-should-appear-under-sql-server-perfmon-counter-group - */ - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP Cursors','Expired rows removed/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP Cursors','Expired rows touched/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP Garbage Collection','Rows processed/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP IO Governor','Io Issued/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP Phantom Processor','Phantom expired rows touched/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP Phantom Processor','Phantom rows touched/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP Transaction Log','Log bytes written/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP Transaction Log','Log records written/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP Transactions','Transactions aborted by user/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP Transactions','Transactions aborted/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP Transactions','Transactions created/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP Cursors','Expired rows removed/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP Cursors','Expired rows touched/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP Garbage Collection','Rows processed/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP IO Governor','Io Issued/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP Phantom Processor','Phantom expired rows touched/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP Phantom Processor','Phantom rows touched/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP Transaction Log','Log bytes written/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP Transaction Log','Log records written/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP Transactions','Transactions aborted by user/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP Transactions','Transactions aborted/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP Transactions','Transactions created/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP Cursors','Expired rows removed/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP Cursors','Expired rows touched/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP Garbage Collection','Rows processed/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP IO Governor','Io Issued/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP Phantom Processor','Phantom expired rows touched/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP Phantom Processor','Phantom rows touched/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP Transaction Log','Log bytes written/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP Transaction Log','Log records written/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP Transactions','Transactions aborted by user/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP Transactions','Transactions aborted/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP Transactions','Transactions created/sec',NULL); - END; - - /* Populate #FileStats, #PerfmonStats, #WaitStats with DMV data. - After we finish doing our checks, we'll take another sample and compare them. */ - RAISERROR('Capturing first pass of wait stats, perfmon counters, file stats',10,1) WITH NOWAIT; - INSERT #WaitStats(Pass, SampleTime, wait_type, wait_time_ms, signal_wait_time_ms, waiting_tasks_count) - SELECT - x.Pass, - x.SampleTime, - x.wait_type, - SUM(x.sum_wait_time_ms) AS sum_wait_time_ms, - SUM(x.sum_signal_wait_time_ms) AS sum_signal_wait_time_ms, - SUM(x.sum_waiting_tasks) AS sum_waiting_tasks - FROM ( - SELECT - 1 AS Pass, - CASE @Seconds WHEN 0 THEN @StartSampleTime ELSE SYSDATETIMEOFFSET() END AS SampleTime, - owt.wait_type, - CASE @Seconds WHEN 0 THEN 0 ELSE SUM(owt.wait_duration_ms) OVER (PARTITION BY owt.wait_type, owt.session_id) - - CASE WHEN @Seconds = 0 THEN 0 ELSE (@Seconds * 1000) END END AS sum_wait_time_ms, - 0 AS sum_signal_wait_time_ms, - 0 AS sum_waiting_tasks - FROM sys.dm_os_waiting_tasks owt - WHERE owt.session_id > 50 - AND owt.wait_duration_ms >= CASE @Seconds WHEN 0 THEN 0 ELSE @Seconds * 1000 END - UNION ALL - SELECT - 1 AS Pass, - CASE @Seconds WHEN 0 THEN @StartSampleTime ELSE SYSDATETIMEOFFSET() END AS SampleTime, - os.wait_type, - CASE @Seconds WHEN 0 THEN 0 ELSE SUM(os.wait_time_ms) OVER (PARTITION BY os.wait_type) END AS sum_wait_time_ms, - CASE @Seconds WHEN 0 THEN 0 ELSE SUM(os.signal_wait_time_ms) OVER (PARTITION BY os.wait_type ) END AS sum_signal_wait_time_ms, - CASE @Seconds WHEN 0 THEN 0 ELSE SUM(os.waiting_tasks_count) OVER (PARTITION BY os.wait_type) END AS sum_waiting_tasks - FROM sys.dm_os_wait_stats os - ) x - WHERE NOT EXISTS - ( - SELECT * - FROM ##WaitCategories AS wc - WHERE wc.WaitType = x.wait_type - AND wc.Ignorable = 1 - ) - GROUP BY x.Pass, x.SampleTime, x.wait_type - ORDER BY sum_wait_time_ms DESC; - - - INSERT INTO #FileStats (Pass, SampleTime, DatabaseID, FileID, DatabaseName, FileLogicalName, SizeOnDiskMB, io_stall_read_ms , - num_of_reads, [bytes_read] , io_stall_write_ms,num_of_writes, [bytes_written], PhysicalName, TypeDesc) - SELECT - 1 AS Pass, - CASE @Seconds WHEN 0 THEN @StartSampleTime ELSE SYSDATETIMEOFFSET() END AS SampleTime, - mf.[database_id], - mf.[file_id], - DB_NAME(vfs.database_id) AS [db_name], - mf.name + N' [' + mf.type_desc COLLATE SQL_Latin1_General_CP1_CI_AS + N']' AS file_logical_name , - CAST(( ( vfs.size_on_disk_bytes / 1024.0 ) / 1024.0 ) AS INT) AS size_on_disk_mb , - CASE @Seconds WHEN 0 THEN 0 ELSE vfs.io_stall_read_ms END , - CASE @Seconds WHEN 0 THEN 0 ELSE vfs.num_of_reads END , - CASE @Seconds WHEN 0 THEN 0 ELSE vfs.[num_of_bytes_read] END , - CASE @Seconds WHEN 0 THEN 0 ELSE vfs.io_stall_write_ms END , - CASE @Seconds WHEN 0 THEN 0 ELSE vfs.num_of_writes END , - CASE @Seconds WHEN 0 THEN 0 ELSE vfs.[num_of_bytes_written] END , - mf.physical_name, - mf.type_desc - FROM sys.dm_io_virtual_file_stats (NULL, NULL) AS vfs - INNER JOIN #MasterFiles AS mf ON vfs.file_id = mf.file_id - AND vfs.database_id = mf.database_id - WHERE vfs.num_of_reads > 0 - OR vfs.num_of_writes > 0; - - INSERT INTO #PerfmonStats (Pass, SampleTime, [object_name],[counter_name],[instance_name],[cntr_value],[cntr_type]) - SELECT 1 AS Pass, - CASE @Seconds WHEN 0 THEN @StartSampleTime ELSE SYSDATETIMEOFFSET() END AS SampleTime, RTRIM(dmv.object_name), RTRIM(dmv.counter_name), RTRIM(dmv.instance_name), CASE @Seconds WHEN 0 THEN 0 ELSE dmv.cntr_value END, dmv.cntr_type - FROM #PerfmonCounters counters - INNER JOIN sys.dm_os_performance_counters dmv ON counters.counter_name COLLATE SQL_Latin1_General_CP1_CI_AS = RTRIM(dmv.counter_name) COLLATE SQL_Latin1_General_CP1_CI_AS - AND counters.[object_name] COLLATE SQL_Latin1_General_CP1_CI_AS = RTRIM(dmv.[object_name]) COLLATE SQL_Latin1_General_CP1_CI_AS - AND (counters.[instance_name] IS NULL OR counters.[instance_name] COLLATE SQL_Latin1_General_CP1_CI_AS = RTRIM(dmv.[instance_name]) COLLATE SQL_Latin1_General_CP1_CI_AS); - - /* For Github #2743: */ - CREATE TABLE #TempdbOperationalStats (object_id BIGINT PRIMARY KEY CLUSTERED, - forwarded_fetch_count BIGINT); - INSERT INTO #TempdbOperationalStats (object_id, forwarded_fetch_count) - SELECT object_id, forwarded_fetch_count - FROM tempdb.sys.dm_db_index_operational_stats(DB_ID('tempdb'), NULL, NULL, NULL) os - WHERE os.database_id = DB_ID('tempdb') - AND os.forwarded_fetch_count > 100; - - - /* If they want to run sp_BlitzWho and export to table, go for it. */ - IF @OutputTableNameBlitzWho IS NOT NULL - AND @OutputDatabaseName IS NOT NULL - AND @OutputSchemaName IS NOT NULL - AND EXISTS ( SELECT * - FROM sys.databases - WHERE QUOTENAME([name]) = @OutputDatabaseName) - BEGIN - RAISERROR('Logging sp_BlitzWho to table',10,1) WITH NOWAIT; - EXEC sp_BlitzWho @OutputDatabaseName = @UnquotedOutputDatabaseName, @OutputSchemaName = @UnquotedOutputSchemaName, @OutputTableName = @OutputTableNameBlitzWho, @CheckDateOverride = @StartSampleTime; - END - - RAISERROR('Beginning investigatory queries',10,1) WITH NOWAIT; - - - /* Maintenance Tasks Running - Backup Running - CheckID 1 */ - IF @Seconds > 0 - BEGIN - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 1',10,1) WITH NOWAIT; - END - - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, QueryHash) - SELECT 1 AS CheckID, - 1 AS Priority, - 'Maintenance Tasks Running' AS FindingGroup, - 'Backup Running' AS Finding, - 'http://www.BrentOzar.com/askbrent/backups/' AS URL, - 'Backup of ' + DB_NAME(db.resource_database_id) + ' database (' + (SELECT CAST(CAST(SUM(size * 8.0 / 1024 / 1024) AS BIGINT) AS NVARCHAR) FROM #MasterFiles WHERE database_id = db.resource_database_id) + 'GB) ' + @LineFeed - + CAST(r.percent_complete AS NVARCHAR(100)) + '% complete, has been running since ' + CAST(r.start_time AS NVARCHAR(100)) + '. ' + @LineFeed - + CASE WHEN COALESCE(s.nt_user_name, s.login_name) IS NOT NULL THEN (' Login: ' + COALESCE(s.nt_user_name, s.login_name) + ' ') ELSE '' END AS Details, - 'KILL ' + CAST(r.session_id AS NVARCHAR(100)) + ';' AS HowToStopIt, - pl.query_plan AS QueryPlan, - r.start_time AS StartTime, - s.login_name AS LoginName, - s.nt_user_name AS NTUserName, - s.[program_name] AS ProgramName, - s.[host_name] AS HostName, - db.[resource_database_id] AS DatabaseID, - DB_NAME(db.resource_database_id) AS DatabaseName, - 0 AS OpenTransactionCount, - r.query_hash - FROM sys.dm_exec_requests r - INNER JOIN sys.dm_exec_connections c ON r.session_id = c.session_id - INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id - INNER JOIN ( - SELECT DISTINCT request_session_id, resource_database_id - FROM sys.dm_tran_locks - WHERE resource_type = N'DATABASE' - AND request_mode = N'S' - AND request_status = N'GRANT' - AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.session_id = db.request_session_id - CROSS APPLY sys.dm_exec_query_plan(r.plan_handle) pl - WHERE r.command LIKE 'BACKUP%' - AND r.start_time <= DATEADD(minute, -5, GETDATE()) - AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs); - END - - /* If there's a backup running, add details explaining how long full backup has been taking in the last month. */ - IF @Seconds > 0 AND CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) <> 'SQL Azure' - BEGIN - SET @StringToExecute = 'UPDATE #BlitzFirstResults SET Details = Details + '' Over the last 60 days, the full backup usually takes '' + CAST((SELECT AVG(DATEDIFF(mi, bs.backup_start_date, bs.backup_finish_date)) FROM msdb.dbo.backupset bs WHERE abr.DatabaseName = bs.database_name AND bs.type = ''D'' AND bs.backup_start_date > DATEADD(dd, -60, SYSDATETIMEOFFSET()) AND bs.backup_finish_date IS NOT NULL) AS NVARCHAR(100)) + '' minutes.'' FROM #BlitzFirstResults abr WHERE abr.CheckID = 1 AND EXISTS (SELECT * FROM msdb.dbo.backupset bs WHERE bs.type = ''D'' AND bs.backup_start_date > DATEADD(dd, -60, SYSDATETIMEOFFSET()) AND bs.backup_finish_date IS NOT NULL AND abr.DatabaseName = bs.database_name AND DATEDIFF(mi, bs.backup_start_date, bs.backup_finish_date) > 1)'; - EXEC(@StringToExecute); - END; - - - /* Maintenance Tasks Running - DBCC CHECK* Running - CheckID 2 */ - IF @Seconds > 0 AND EXISTS(SELECT * FROM sys.dm_exec_requests WHERE command LIKE 'DBCC%') - BEGIN - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 2',10,1) WITH NOWAIT; - END - - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, QueryHash) - SELECT 2 AS CheckID, - 1 AS Priority, - 'Maintenance Tasks Running' AS FindingGroup, - 'DBCC CHECK* Running' AS Finding, - 'http://www.BrentOzar.com/askbrent/dbcc/' AS URL, - 'Corruption check of ' + DB_NAME(db.resource_database_id) + ' database (' + (SELECT CAST(CAST(SUM(size * 8.0 / 1024 / 1024) AS BIGINT) AS NVARCHAR) FROM #MasterFiles WHERE database_id = db.resource_database_id) + 'GB) 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, - s.login_name AS LoginName, - s.nt_user_name AS NTUserName, - s.[program_name] AS ProgramName, - s.[host_name] AS HostName, - db.[resource_database_id] AS DatabaseID, - DB_NAME(db.resource_database_id) AS DatabaseName, - 0 AS OpenTransactionCount, - r.query_hash - FROM sys.dm_exec_requests r - INNER JOIN sys.dm_exec_connections c ON r.session_id = c.session_id - INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id - INNER JOIN (SELECT DISTINCT l.request_session_id, l.resource_database_id - FROM sys.dm_tran_locks l - INNER JOIN sys.databases d ON l.resource_database_id = d.database_id - WHERE l.resource_type = N'DATABASE' - AND l.request_mode = N'S' - AND l.request_status = N'GRANT' - AND l.request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.session_id = db.request_session_id - OUTER APPLY sys.dm_exec_query_plan(r.plan_handle) pl - OUTER APPLY sys.dm_exec_sql_text(r.sql_handle) AS t - WHERE r.command LIKE 'DBCC%' - AND CAST(t.text AS NVARCHAR(4000)) NOT LIKE '%dm_db_index_physical_stats%' - AND CAST(t.text AS NVARCHAR(4000)) NOT LIKE '%ALTER INDEX%' - AND CAST(t.text AS NVARCHAR(4000)) NOT LIKE '%fileproperty%' - AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs); - END - - /* Maintenance Tasks Running - Restore Running - CheckID 3 */ - IF @Seconds > 0 - BEGIN - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 3',10,1) WITH NOWAIT; - END - - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, QueryHash) - SELECT 3 AS CheckID, - 1 AS Priority, - 'Maintenance Tasks Running' AS FindingGroup, - 'Restore Running' AS Finding, - 'http://www.BrentOzar.com/askbrent/backups/' AS URL, - 'Restore of ' + DB_NAME(db.resource_database_id) + ' database (' + (SELECT CAST(CAST(SUM(size * 8.0 / 1024 / 1024) AS BIGINT) AS NVARCHAR) FROM #MasterFiles WHERE database_id = db.resource_database_id) + '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, - s.login_name AS LoginName, - s.nt_user_name AS NTUserName, - s.[program_name] AS ProgramName, - s.[host_name] AS HostName, - db.[resource_database_id] AS DatabaseID, - DB_NAME(db.resource_database_id) AS DatabaseName, - 0 AS OpenTransactionCount, - r.query_hash - FROM sys.dm_exec_requests r - INNER JOIN sys.dm_exec_connections c ON r.session_id = c.session_id - INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id - INNER JOIN ( - SELECT DISTINCT request_session_id, resource_database_id - FROM sys.dm_tran_locks - WHERE resource_type = N'DATABASE' - AND request_mode = N'S' - AND request_status = N'GRANT') AS db ON s.session_id = db.request_session_id - CROSS APPLY sys.dm_exec_query_plan(r.plan_handle) pl - WHERE r.command LIKE 'RESTORE%' - AND s.program_name <> 'SQL Server Log Shipping' - AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs); - END - - /* SQL Server Internal Maintenance - Database File Growing - CheckID 4 */ - IF @Seconds > 0 - BEGIN - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 4',10,1) WITH NOWAIT; - END - - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount) - SELECT 4 AS CheckID, - 1 AS Priority, - 'SQL Server Internal Maintenance' AS FindingGroup, - 'Database File Growing' AS Finding, - 'http://www.BrentOzar.com/go/instant' AS URL, - 'SQL Server is waiting for Windows to provide storage space for a database restore, a data file growth, or a log file growth. This task has been running since ' + CAST(r.start_time AS NVARCHAR(100)) + '.' + @LineFeed + 'Check the query plan (expert mode) to identify the database involved.' AS Details, - 'Unfortunately, you can''t stop this, but you can prevent it next time. Check out http://www.BrentOzar.com/go/instant for details.' AS HowToStopIt, - pl.query_plan AS QueryPlan, - r.start_time AS StartTime, - s.login_name AS LoginName, - s.nt_user_name AS NTUserName, - s.[program_name] AS ProgramName, - s.[host_name] AS HostName, - NULL AS DatabaseID, - NULL AS DatabaseName, - 0 AS OpenTransactionCount - FROM sys.dm_os_waiting_tasks t - INNER JOIN sys.dm_exec_connections c ON t.session_id = c.session_id - INNER JOIN sys.dm_exec_requests r ON t.session_id = r.session_id - INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id - CROSS APPLY sys.dm_exec_query_plan(r.plan_handle) pl - WHERE t.wait_type = 'PREEMPTIVE_OS_WRITEFILEGATHER' - AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs); - END - - /* Query Problems - Long-Running Query Blocking Others - CheckID 5 */ - IF SERVERPROPERTY('Edition') <> 'SQL Azure' AND @Seconds > 0 AND EXISTS(SELECT * FROM sys.dm_os_waiting_tasks WHERE wait_type LIKE 'LCK%' AND wait_duration_ms > 30000) - BEGIN - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 5',10,1) WITH NOWAIT; - END - - SET @StringToExecute = N'INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, QueryText, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, QueryHash) - SELECT 5 AS CheckID, - 1 AS Priority, - ''Query Problems'' AS FindingGroup, - ''Long-Running Query Blocking Others'' AS Finding, - ''http://www.BrentOzar.com/go/blocking'' AS URL, - ''Query in '' + COALESCE(DB_NAME(COALESCE((SELECT TOP 1 dbid FROM sys.dm_exec_sql_text(r.sql_handle)), - (SELECT TOP 1 t.dbid FROM master..sysprocesses spBlocker CROSS APPLY sys.dm_exec_sql_text(spBlocker.sql_handle) t WHERE spBlocker.spid = tBlocked.blocking_session_id))), ''(Unknown)'') + '' has a last request start time of '' + CAST(s.last_request_start_time AS NVARCHAR(100)) + ''. Query follows: ' - + @LineFeed + @LineFeed + - '''+ CAST(COALESCE((SELECT TOP 1 [text] FROM sys.dm_exec_sql_text(r.sql_handle)), - (SELECT TOP 1 [text] FROM master..sysprocesses spBlocker CROSS APPLY sys.dm_exec_sql_text(spBlocker.sql_handle) WHERE spBlocker.spid = tBlocked.blocking_session_id), '''') AS NVARCHAR(2000)) AS Details, - ''KILL '' + CAST(tBlocked.blocking_session_id AS NVARCHAR(100)) + '';'' AS HowToStopIt, - (SELECT TOP 1 query_plan FROM sys.dm_exec_query_plan(r.plan_handle)) AS QueryPlan, - COALESCE((SELECT TOP 1 [text] FROM sys.dm_exec_sql_text(r.sql_handle)), - (SELECT TOP 1 [text] FROM master..sysprocesses spBlocker CROSS APPLY sys.dm_exec_sql_text(spBlocker.sql_handle) WHERE spBlocker.spid = tBlocked.blocking_session_id)) AS QueryText, - r.start_time AS StartTime, - s.login_name AS LoginName, - s.nt_user_name AS NTUserName, - s.[program_name] AS ProgramName, - s.[host_name] AS HostName, - r.[database_id] AS DatabaseID, - DB_NAME(r.database_id) AS DatabaseName, - 0 AS OpenTransactionCount, - r.query_hash - FROM sys.dm_os_waiting_tasks tBlocked - INNER JOIN sys.dm_exec_sessions s ON tBlocked.blocking_session_id = s.session_id - LEFT OUTER JOIN sys.dm_exec_requests r ON s.session_id = r.session_id - INNER JOIN sys.dm_exec_connections c ON s.session_id = c.session_id - WHERE tBlocked.wait_type LIKE ''LCK%'' AND tBlocked.wait_duration_ms > 30000 - /* And the blocking session ID is not blocked by anyone else: */ - AND NOT EXISTS(SELECT * FROM sys.dm_os_waiting_tasks tBlocking WHERE s.session_id = tBlocking.session_id AND tBlocking.session_id <> tBlocking.blocking_session_id AND tBlocking.blocking_session_id IS NOT NULL) - AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs);'; - EXECUTE sp_executesql @StringToExecute; - END; - - /* Query Problems - Plan Cache Erased Recently - CheckID 7 */ - IF DATEADD(mi, -15, SYSDATETIME()) < (SELECT TOP 1 creation_time FROM sys.dm_exec_query_stats ORDER BY creation_time) - BEGIN - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 7',10,1) WITH NOWAIT; - END - - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) - SELECT TOP 1 7 AS CheckID, - 50 AS Priority, - 'Query Problems' AS FindingGroup, - 'Plan Cache Erased Recently' AS Finding, - 'http://www.BrentOzar.com/askbrent/plan-cache-erased-recently/' AS URL, - 'The oldest query in the plan cache was created at ' + CAST(creation_time AS NVARCHAR(50)) + '. ' + @LineFeed + @LineFeed - + 'This indicates that someone ran DBCC FREEPROCCACHE at that time,' + @LineFeed - + 'Giving SQL Server temporary amnesia. Now, as queries come in,' + @LineFeed - + 'SQL Server has to use a lot of CPU power in order to build execution' + @LineFeed - + 'plans and put them in cache again. This causes high CPU loads.' AS Details, - 'Find who did that, and stop them from doing it again.' AS HowToStopIt - FROM sys.dm_exec_query_stats - ORDER BY creation_time; - END; - - - /* Query Problems - Sleeping Query with Open Transactions - CheckID 8 */ - IF @Seconds > 0 - BEGIN - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 8',10,1) WITH NOWAIT; - END - - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, OpenTransactionCount) - SELECT 8 AS CheckID, - 50 AS Priority, - 'Query Problems' AS FindingGroup, - 'Sleeping Query with Open Transactions' AS Finding, - 'http://www.brentozar.com/askbrent/sleeping-query-with-open-transactions/' AS URL, - 'Database: ' + DB_NAME(db.resource_database_id) + @LineFeed + 'Host: ' + s.[host_name] + @LineFeed + 'Program: ' + s.[program_name] + @LineFeed + 'Asleep with open transactions and locks since ' + CAST(s.last_request_end_time AS NVARCHAR(100)) + '. ' AS Details, - 'KILL ' + CAST(s.session_id AS NVARCHAR(100)) + ';' AS HowToStopIt, - s.last_request_start_time AS StartTime, - s.login_name AS LoginName, - s.nt_user_name AS NTUserName, - s.[program_name] AS ProgramName, - s.[host_name] AS HostName, - db.[resource_database_id] AS DatabaseID, - DB_NAME(db.resource_database_id) AS DatabaseName, - (SELECT TOP 1 [text] FROM sys.dm_exec_sql_text(c.most_recent_sql_handle)) AS QueryText, - sessions_with_transactions.open_transaction_count AS OpenTransactionCount - FROM (SELECT session_id, SUM(open_transaction_count) AS open_transaction_count FROM sys.dm_exec_requests WHERE open_transaction_count > 0 GROUP BY session_id) AS sessions_with_transactions - INNER JOIN sys.dm_exec_sessions s ON sessions_with_transactions.session_id = s.session_id - INNER JOIN sys.dm_exec_connections c ON s.session_id = c.session_id - INNER JOIN ( - SELECT DISTINCT request_session_id, resource_database_id - FROM sys.dm_tran_locks - WHERE resource_type = N'DATABASE' - AND request_mode = N'S' - AND request_status = N'GRANT' - AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.session_id = db.request_session_id - WHERE s.status = 'sleeping' - AND s.last_request_end_time < DATEADD(ss, -10, SYSDATETIME()) - AND EXISTS(SELECT * FROM sys.dm_tran_locks WHERE request_session_id = s.session_id - AND NOT (resource_type = N'DATABASE' AND request_mode = N'S' AND request_status = N'GRANT' AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE')); - END - - /*Query Problems - Clients using implicit transactions - CheckID 37 */ - IF @Seconds > 0 - AND ( @@VERSION NOT LIKE 'Microsoft SQL Server 2005%' - AND @@VERSION NOT LIKE 'Microsoft SQL Server 2008%' - AND @@VERSION NOT LIKE 'Microsoft SQL Server 2008 R2%' ) - BEGIN - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 37',10,1) WITH NOWAIT; - END - - SET @StringToExecute = N'INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, OpenTransactionCount) - SELECT 37 AS CheckId, - 50 AS Priority, - ''Query Problems'' AS FindingsGroup, - ''Implicit Transactions'', - ''https://www.brentozar.com/go/ImplicitTransactions/'' AS URL, - ''Database: '' + DB_NAME(s.database_id) + '' '' + CHAR(13) + CHAR(10) + - ''Host: '' + s.[host_name] + '' '' + CHAR(13) + CHAR(10) + - ''Program: '' + s.[program_name] + '' '' + CHAR(13) + CHAR(10) + - CONVERT(NVARCHAR(10), s.open_transaction_count) + - '' open transactions since: '' + - CONVERT(NVARCHAR(30), tat.transaction_begin_time) + ''. '' - AS Details, - ''Run sp_BlitzWho and check the is_implicit_transaction column to spot the culprits. -If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, - tat.transaction_begin_time, - s.login_name, - s.nt_user_name, - s.program_name, - s.host_name, - s.database_id, - DB_NAME(s.database_id) AS DatabaseName, - NULL AS Querytext, - s.open_transaction_count AS OpenTransactionCount - FROM sys.dm_tran_active_transactions AS tat - LEFT JOIN sys.dm_tran_session_transactions AS tst - ON tst.transaction_id = tat.transaction_id - LEFT JOIN sys.dm_exec_sessions AS s - ON s.session_id = tst.session_id - WHERE tat.name = ''implicit_transaction''; - ' - EXECUTE sp_executesql @StringToExecute; - END; - - /* Query Problems - Query Rolling Back - CheckID 9 */ - IF @Seconds > 0 - BEGIN - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 9',10,1) WITH NOWAIT; - END - - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, QueryHash) - SELECT 9 AS CheckID, - 1 AS Priority, - 'Query Problems' AS FindingGroup, - 'Query Rolling Back' AS Finding, - 'http://www.BrentOzar.com/askbrent/rollback/' AS URL, - 'Rollback started at ' + CAST(r.start_time AS NVARCHAR(100)) + ', is ' + CAST(r.percent_complete AS NVARCHAR(100)) + '% complete.' AS Details, - 'Unfortunately, you can''t stop this. Whatever you do, don''t restart the server in an attempt to fix it - SQL Server will keep rolling back.' AS HowToStopIt, - r.start_time AS StartTime, - s.login_name AS LoginName, - s.nt_user_name AS NTUserName, - s.[program_name] AS ProgramName, - s.[host_name] AS HostName, - db.[resource_database_id] AS DatabaseID, - DB_NAME(db.resource_database_id) AS DatabaseName, - (SELECT TOP 1 [text] FROM sys.dm_exec_sql_text(c.most_recent_sql_handle)) AS QueryText, - r.query_hash - FROM sys.dm_exec_sessions s - INNER JOIN sys.dm_exec_connections c ON s.session_id = c.session_id - INNER JOIN sys.dm_exec_requests r ON s.session_id = r.session_id - LEFT OUTER JOIN ( - SELECT DISTINCT request_session_id, resource_database_id - FROM sys.dm_tran_locks - WHERE resource_type = N'DATABASE' - AND request_mode = N'S' - AND request_status = N'GRANT' - AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.session_id = db.request_session_id - WHERE r.status = 'rollback'; - END - - /* Server Performance - Too Much Free Memory - CheckID 34 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 34',10,1) WITH NOWAIT; - END - - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) - SELECT 34 AS CheckID, - 50 AS Priority, - 'Server Performance' AS FindingGroup, - 'Too Much Free Memory' AS Finding, - 'https://BrentOzar.com/go/freememory' AS URL, - CAST((CAST(cFree.cntr_value AS BIGINT) / 1024 / 1024 ) AS NVARCHAR(100)) + N'GB of free memory inside SQL Server''s buffer pool,' + @LineFeed + ' which is ' + CAST((CAST(cTotal.cntr_value AS BIGINT) / 1024 / 1024) AS NVARCHAR(100)) + N'GB. You would think lots of free memory would be good, but check out the URL for more information.' AS Details, - 'Run sp_BlitzCache @SortOrder = ''memory grant'' to find queries with huge memory grants and tune them.' AS HowToStopIt - FROM sys.dm_os_performance_counters cFree - INNER JOIN sys.dm_os_performance_counters cTotal ON cTotal.object_name LIKE N'%Memory Manager%' - AND cTotal.counter_name = N'Total Server Memory (KB) ' - WHERE cFree.object_name LIKE N'%Memory Manager%' - AND cFree.counter_name = N'Free Memory (KB) ' - AND CAST(cFree.cntr_value AS BIGINT) > 20480000000 - AND CAST(cTotal.cntr_value AS BIGINT) * .3 <= CAST(cFree.cntr_value AS BIGINT) - AND CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) NOT LIKE '%Standard%'; - - /* Server Performance - Target Memory Lower Than Max - CheckID 35 */ - IF SERVERPROPERTY('Edition') <> 'SQL Azure' - BEGIN - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 35',10,1) WITH NOWAIT; - END - - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) - SELECT 35 AS CheckID, - 10 AS Priority, - 'Server Performance' AS FindingGroup, - 'Target Memory Lower Than Max' AS Finding, - 'https://BrentOzar.com/go/target' AS URL, - N'Max server memory is ' + CAST(cMax.value_in_use AS NVARCHAR(50)) + N' MB but target server memory is only ' + CAST((CAST(cTarget.cntr_value AS BIGINT) / 1024) AS NVARCHAR(50)) + N' MB,' + @LineFeed - + N'indicating that SQL Server may be under external memory pressure or max server memory may be set too high.' AS Details, - 'Investigate what OS processes are using memory, and double-check the max server memory setting.' AS HowToStopIt - FROM sys.configurations cMax - INNER JOIN sys.dm_os_performance_counters cTarget ON cTarget.object_name LIKE N'%Memory Manager%' - AND cTarget.counter_name = N'Target Server Memory (KB) ' - WHERE cMax.name = 'max server memory (MB)' - AND CAST(cMax.value_in_use AS BIGINT) >= 1.5 * (CAST(cTarget.cntr_value AS BIGINT) / 1024) - AND CAST(cMax.value_in_use AS BIGINT) < 2147483647 /* Not set to default of unlimited */ - AND CAST(cTarget.cntr_value AS BIGINT) < .8 * (SELECT available_physical_memory_kb FROM sys.dm_os_sys_memory); /* Target memory less than 80% of physical memory (in case they set max too high) */ - END - - /* Server Info - Database Size, Total GB - CheckID 21 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 21',10,1) WITH NOWAIT; - END - - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) - SELECT 21 AS CheckID, - 251 AS Priority, - 'Server Info' AS FindingGroup, - 'Database Size, Total GB' AS Finding, - CAST(SUM (CAST(size AS BIGINT)*8./1024./1024.) AS VARCHAR(100)) AS Details, - SUM (CAST(size AS BIGINT))*8./1024./1024. AS DetailsInt, - 'http://www.BrentOzar.com/askbrent/' AS URL - FROM #MasterFiles - WHERE database_id > 4; - - /* Server Info - Database Count - CheckID 22 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 22',10,1) WITH NOWAIT; - END - - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) - SELECT 22 AS CheckID, - 251 AS Priority, - 'Server Info' AS FindingGroup, - 'Database Count' AS Finding, - CAST(SUM(1) AS VARCHAR(100)) AS Details, - SUM (1) AS DetailsInt, - 'http://www.BrentOzar.com/askbrent/' AS URL - FROM sys.databases - WHERE database_id > 4; - - /* Server Info - Memory Grants pending - CheckID 39 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 39',10,1) WITH NOWAIT; - END - - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) - SELECT 39 AS CheckID, - 50 AS Priority, - 'Server Performance' AS FindingGroup, - 'Memory Grants Pending' AS Finding, - CAST(PendingGrants.Details AS NVARCHAR(50)) AS Details, - PendingGrants.DetailsInt, - 'https://www.brentozar.com/blitz/memory-grants/' AS URL - FROM - ( - SELECT - COUNT(1) AS Details, - COUNT(1) AS DetailsInt - FROM sys.dm_exec_query_memory_grants AS Grants - WHERE queue_id IS NOT NULL - ) AS PendingGrants - WHERE PendingGrants.Details > 0; - - /* Server Info - Memory Grant/Workspace info - CheckID 40 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 40',10,1) WITH NOWAIT; - END - - DECLARE @MaxWorkspace BIGINT - SET @MaxWorkspace = (SELECT CAST(cntr_value AS BIGINT)/1024 FROM #PerfmonStats WHERE counter_name = N'Maximum Workspace Memory (KB)') - - IF (@MaxWorkspace IS NULL - OR @MaxWorkspace = 0) - BEGIN - SET @MaxWorkspace = 1 - END - - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) - SELECT 40 AS CheckID, - 251 AS Priority, - 'Server Info' AS FindingGroup, - 'Memory Grant/Workspace info' AS Finding, - + 'Grants Outstanding: ' + CAST((SELECT COUNT(*) FROM sys.dm_exec_query_memory_grants WHERE queue_id IS NULL) AS NVARCHAR(50)) + @LineFeed - + 'Total Granted(MB): ' + CAST(ISNULL(SUM(Grants.granted_memory_kb) / 1024, 0) AS NVARCHAR(50)) + @LineFeed - + 'Total WorkSpace(MB): ' + CAST(ISNULL(@MaxWorkspace, 0) AS NVARCHAR(50)) + @LineFeed - + 'Granted workspace: ' + CAST(ISNULL((CAST(SUM(Grants.granted_memory_kb) / 1024 AS MONEY) - / CAST(@MaxWorkspace AS MONEY)) * 100, 0) AS NVARCHAR(50)) + '%' + @LineFeed - + 'Oldest Grant in seconds: ' + CAST(ISNULL(DATEDIFF(SECOND, MIN(Grants.request_time), GETDATE()), 0) AS NVARCHAR(50)) AS Details, - (SELECT COUNT(*) FROM sys.dm_exec_query_memory_grants WHERE queue_id IS NULL) AS DetailsInt, - 'http://www.BrentOzar.com/askbrent/' AS URL - FROM sys.dm_exec_query_memory_grants AS Grants; - - /* Query Problems - Queries with high memory grants - CheckID 46 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 46',10,1) WITH NOWAIT; - END - - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, URL, QueryText, QueryPlan) - SELECT 46 AS CheckID, - 100 AS Priority, - 'Query Problems' AS FindingGroup, - 'Query with a memory grant exceeding ' - +CAST(@MemoryGrantThresholdPct AS NVARCHAR(15)) - +'%' AS Finding, - 'Granted size: '+ CAST(CAST(Grants.granted_memory_kb / 1024 AS INT) AS NVARCHAR(50)) - +N'MB ' - + @LineFeed - +N'Granted pct of max workspace: ' - + CAST(ISNULL((CAST(Grants.granted_memory_kb / 1024 AS MONEY) - / CAST(@MaxWorkspace AS MONEY)) * 100, 0) AS NVARCHAR(50)) + '%' - + @LineFeed - +N'SQLHandle: ' - +CONVERT(NVARCHAR(128),Grants.[sql_handle],1), - 'https://www.brentozar.com/memory-grants-sql-servers-public-toilet/' AS URL, - SQLText.[text], - QueryPlan.query_plan - FROM sys.dm_exec_query_memory_grants AS Grants - OUTER APPLY sys.dm_exec_sql_text(Grants.[sql_handle]) AS SQLText - OUTER APPLY sys.dm_exec_query_plan(Grants.[plan_handle]) AS QueryPlan - WHERE Grants.granted_memory_kb > ((@MemoryGrantThresholdPct/100.00)*(@MaxWorkspace*1024)); - - /* Query Problems - Memory Leak in USERSTORE_TOKENPERM Cache - CheckID 45 */ - IF EXISTS (SELECT * FROM sys.all_columns WHERE object_id = OBJECT_ID('sys.dm_os_memory_clerks') AND name = 'pages_kb') - BEGIN - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 45',10,1) WITH NOWAIT; - END - - /* SQL 2012+ version */ - SET @StringToExecute = N' - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, URL) - SELECT 45 AS CheckID, - 50 AS Priority, - ''Query Problems'' AS FindingsGroup, - ''Memory Leak in USERSTORE_TOKENPERM Cache'' AS Finding, - N''UserStore_TokenPerm clerk is using '' + CAST(CAST(SUM(CASE WHEN type = ''USERSTORE_TOKENPERM'' AND name = ''TokenAndPermUserStore'' THEN pages_kb * 1.0 ELSE 0.0 END) / 1024.0 / 1024.0 AS INT) AS NVARCHAR(100)) - + N''GB RAM, total buffer pool is '' + CAST(CAST(SUM(pages_kb) / 1024.0 / 1024.0 AS INT) AS NVARCHAR(100)) + N''GB.'' - AS details, - ''https://www.BrentOzar.com/go/userstore'' AS URL - FROM sys.dm_os_memory_clerks - HAVING SUM(CASE WHEN type = ''USERSTORE_TOKENPERM'' AND name = ''TokenAndPermUserStore'' THEN pages_kb * 1.0 ELSE 0.0 END) / SUM(pages_kb) >= 0.1 - AND SUM(pages_kb) / 1024.0 / 1024.0 >= 1; /* At least 1GB RAM overall */'; - EXEC sp_executesql @StringToExecute; - END - ELSE - BEGIN - /* Antiques Roadshow SQL 2008R2 - version */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 45 (Legacy version)',10,1) WITH NOWAIT; - END - - SET @StringToExecute = N' - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, URL) - SELECT 45 AS CheckID, - 50 AS Priority, - ''Performance'' AS FindingsGroup, - ''Memory Leak in USERSTORE_TOKENPERM Cache'' AS Finding, - N''UserStore_TokenPerm clerk is using '' + CAST(CAST(SUM(CASE WHEN type = ''USERSTORE_TOKENPERM'' AND name = ''TokenAndPermUserStore'' THEN single_pages_kb + multi_pages_kb * 1.0 ELSE 0.0 END) / 1024.0 / 1024.0 AS INT) AS NVARCHAR(100)) - + N''GB RAM, total buffer pool is '' + CAST(CAST(SUM(single_pages_kb + multi_pages_kb) / 1024.0 / 1024.0 AS INT) AS NVARCHAR(100)) + N''GB.'' - AS details, - ''https://www.BrentOzar.com/go/userstore'' AS URL - FROM sys.dm_os_memory_clerks - HAVING SUM(CASE WHEN type = ''USERSTORE_TOKENPERM'' AND name = ''TokenAndPermUserStore'' THEN single_pages_kb + multi_pages_kb * 1.0 ELSE 0.0 END) / SUM(single_pages_kb + multi_pages_kb) >= 0.1 - AND SUM(single_pages_kb + multi_pages_kb) / 1024.0 / 1024.0 >= 1; /* At least 1GB RAM overall */'; - EXEC sp_executesql @StringToExecute; - END - - - - IF @Seconds > 0 - BEGIN - - IF EXISTS ( SELECT 1/0 - FROM sys.all_objects AS ao - WHERE ao.name = 'dm_exec_query_profiles' ) - BEGIN - - IF EXISTS( SELECT 1/0 - FROM sys.dm_exec_requests AS r - JOIN sys.dm_exec_sessions AS s - ON r.session_id = s.session_id - WHERE s.host_name IS NOT NULL - AND r.total_elapsed_time > 5000 ) - BEGIN - - SET @StringToExecute = N' - DECLARE @bad_estimate TABLE - ( - session_id INT, - request_id INT, - estimate_inaccuracy BIT - ); - - INSERT @bad_estimate ( session_id, request_id, estimate_inaccuracy ) - SELECT x.session_id, - x.request_id, - x.estimate_inaccuracy - FROM ( - SELECT deqp.session_id, - deqp.request_id, - CASE WHEN deqp.row_count > ( deqp.estimate_row_count * 10000 ) - THEN 1 - ELSE 0 - END AS estimate_inaccuracy - FROM sys.dm_exec_query_profiles AS deqp - WHERE deqp.session_id <> @@SPID - ) AS x - WHERE x.estimate_inaccuracy = 1 - GROUP BY x.session_id, - x.request_id, - x.estimate_inaccuracy; - - DECLARE @parallelism_skew TABLE - ( - session_id INT, - request_id INT, - parallelism_skew BIT - ); - - INSERT @parallelism_skew ( session_id, request_id, parallelism_skew ) - SELECT y.session_id, - y.request_id, - y.parallelism_skew - FROM ( - SELECT x.session_id, - x.request_id, - x.node_id, - x.thread_id, - x.row_count, - x.sum_node_rows, - x.node_dop, - x.sum_node_rows / x.node_dop AS even_distribution, - x.row_count / (1. * ISNULL(NULLIF(x.sum_node_rows / x.node_dop, 0), 1)) AS skew_percent, - CASE - WHEN x.row_count > 10000 - AND x.row_count / (1. * ISNULL(NULLIF(x.sum_node_rows / x.node_dop, 0), 1)) > 2. - THEN 1 - WHEN x.row_count > 10000 - AND x.row_count / (1. * ISNULL(NULLIF(x.sum_node_rows / x.node_dop, 0), 1)) < 0.5 - THEN 1 - ELSE 0 - END AS parallelism_skew - FROM ( - SELECT deqp.session_id, - deqp.request_id, - deqp.node_id, - deqp.thread_id, - deqp.row_count, - SUM(deqp.row_count) - OVER ( PARTITION BY deqp.session_id, - deqp.request_id, - deqp.node_id - ORDER BY deqp.row_count - ROWS BETWEEN UNBOUNDED PRECEDING - AND UNBOUNDED FOLLOWING ) - AS sum_node_rows, - COUNT(*) - OVER ( PARTITION BY deqp.session_id, - deqp.request_id, - deqp.node_id - ORDER BY deqp.row_count - ROWS BETWEEN UNBOUNDED PRECEDING - AND UNBOUNDED FOLLOWING ) - AS node_dop - FROM sys.dm_exec_query_profiles AS deqp - WHERE deqp.thread_id > 0 - AND deqp.session_id <> @@SPID - AND EXISTS - ( - SELECT 1/0 - FROM sys.dm_exec_query_profiles AS deqp2 - WHERE deqp.session_id = deqp2.session_id - AND deqp.node_id = deqp2.node_id - AND deqp2.thread_id > 0 - GROUP BY deqp2.session_id, deqp2.node_id - HAVING COUNT(deqp2.node_id) > 1 - ) - ) AS x - ) AS y - WHERE y.parallelism_skew = 1 - GROUP BY y.session_id, - y.request_id, - y.parallelism_skew; - - /* Queries in dm_exec_query_profiles showing signs of poor cardinality estimates - CheckID 42 */ - IF (@Debug = 1) - BEGIN - RAISERROR(''Running CheckID 42'',10,1) WITH NOWAIT; - END - - INSERT INTO #BlitzFirstResults - (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, OpenTransactionCount, QueryHash, QueryPlan) - SELECT 42 AS CheckID, - 100 AS Priority, - ''Query Performance'' AS FindingsGroup, - ''Queries with 10000x cardinality misestimations'' AS Findings, - ''https://brentozar.com/go/skewedup'' AS URL, - ''The query on SPID '' - + RTRIM(b.session_id) - + '' has been running for '' - + RTRIM(r.total_elapsed_time / 1000) - + '' seconds, with a large cardinality misestimate'' AS Details, - ''No quick fix here: time to dig into the actual execution plan. '' AS HowToStopIt, - r.start_time, - s.login_name, - s.nt_user_name, - s.program_name, - s.host_name, - r.database_id, - DB_NAME(r.database_id), - dest.text, - s.open_transaction_count, - r.query_hash, '; - - IF @dm_exec_query_statistics_xml = 1 - SET @StringToExecute = @StringToExecute + N' COALESCE(qs_live.query_plan, qp.query_plan) AS query_plan '; - ELSE - SET @StringToExecute = @StringToExecute + N' qp.query_plan '; - - SET @StringToExecute = @StringToExecute + N' - FROM @bad_estimate AS b - JOIN sys.dm_exec_requests AS r - ON r.session_id = b.session_id - AND r.request_id = b.request_id - JOIN sys.dm_exec_sessions AS s - ON s.session_id = b.session_id - CROSS APPLY sys.dm_exec_sql_text(r.sql_handle) AS dest - CROSS APPLY sys.dm_exec_query_plan(r.plan_handle) AS qp '; - - IF EXISTS (SELECT * FROM sys.all_objects WHERE name = 'dm_exec_query_statistics_xml') - SET @StringToExecute = @StringToExecute + N' OUTER APPLY sys.dm_exec_query_statistics_xml(s.session_id) qs_live '; - - - SET @StringToExecute = @StringToExecute + N'; - - /* Queries in dm_exec_query_profiles showing signs of unbalanced parallelism - CheckID 43 */ - IF (@Debug = 1) - BEGIN - RAISERROR(''Running CheckID 43'',10,1) WITH NOWAIT; - END - - INSERT INTO #BlitzFirstResults - (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, OpenTransactionCount, QueryHash, QueryPlan) - SELECT 43 AS CheckID, - 100 AS Priority, - ''Query Performance'' AS FindingsGroup, - ''Queries with 10000x skewed parallelism'' AS Findings, - ''https://brentozar.com/go/skewedup'' AS URL, - ''The query on SPID '' - + RTRIM(p.session_id) - + '' has been running for '' - + RTRIM(r.total_elapsed_time / 1000) - + '' seconds, with a parallel threads doing uneven work.'' AS Details, - ''No quick fix here: time to dig into the actual execution plan. '' AS HowToStopIt, - r.start_time, - s.login_name, - s.nt_user_name, - s.program_name, - s.host_name, - r.database_id, - DB_NAME(r.database_id), - dest.text, - s.open_transaction_count, - r.query_hash, '; - - IF @dm_exec_query_statistics_xml = 1 - SET @StringToExecute = @StringToExecute + N' COALESCE(qs_live.query_plan, qp.query_plan) AS query_plan '; - ELSE - SET @StringToExecute = @StringToExecute + N' qp.query_plan '; - - SET @StringToExecute = @StringToExecute + N' - FROM @parallelism_skew AS p - JOIN sys.dm_exec_requests AS r - ON r.session_id = p.session_id - AND r.request_id = p.request_id - JOIN sys.dm_exec_sessions AS s - ON s.session_id = p.session_id - CROSS APPLY sys.dm_exec_sql_text(r.sql_handle) AS dest - CROSS APPLY sys.dm_exec_query_plan(r.plan_handle) AS qp '; - - IF EXISTS (SELECT * FROM sys.all_objects WHERE name = 'dm_exec_query_statistics_xml') - SET @StringToExecute = @StringToExecute + N' OUTER APPLY sys.dm_exec_query_statistics_xml(s.session_id) qs_live '; - - - SET @StringToExecute = @StringToExecute + N';'; - - EXECUTE sp_executesql @StringToExecute, N'@Debug BIT',@Debug = @Debug; - END - - END - END - - /* Server Performance - High CPU Utilization - CheckID 24 */ - IF @Seconds < 30 - BEGIN - /* If we're waiting less than 30 seconds, run this check now rather than wait til the end. - We get this data from the ring buffers, and it's only updated once per minute, so might - as well get it now - whereas if we're checking 30+ seconds, it might get updated by the - end of our sp_BlitzFirst session. */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 24',10,1) WITH NOWAIT; - END - - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) - SELECT 24, 50, 'Server Performance', 'High CPU Utilization', CAST(100 - SystemIdle AS NVARCHAR(20)) + N'%.', 100 - SystemIdle, 'http://www.BrentOzar.com/go/cpu' - FROM ( - SELECT record, - record.value('(./Record/SchedulerMonitorEvent/SystemHealth/SystemIdle)[1]', 'int') AS SystemIdle - FROM ( - SELECT TOP 1 CONVERT(XML, record) AS record - FROM sys.dm_os_ring_buffers - WHERE ring_buffer_type = N'RING_BUFFER_SCHEDULER_MONITOR' - AND record LIKE '%%' - ORDER BY timestamp DESC) AS rb - ) AS y - WHERE 100 - SystemIdle >= 50; - - /* CPU Utilization - CheckID 23 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 23',10,1) WITH NOWAIT; - END - - IF SERVERPROPERTY('Edition') <> 'SQL Azure' - WITH y - AS - ( - SELECT CONVERT(VARCHAR(5), 100 - ca.c.value('.', 'INT')) AS system_idle, - CONVERT(VARCHAR(30), rb.event_date) AS event_date, - CONVERT(VARCHAR(8000), rb.record) AS record - FROM - ( SELECT CONVERT(XML, dorb.record) AS record, - DATEADD(ms, ( ts.ms_ticks - dorb.timestamp ), GETDATE()) AS event_date - FROM sys.dm_os_ring_buffers AS dorb - CROSS JOIN - ( SELECT dosi.ms_ticks FROM sys.dm_os_sys_info AS dosi ) AS ts - WHERE dorb.ring_buffer_type = N'RING_BUFFER_SCHEDULER_MONITOR' - AND record LIKE '%%' ) AS rb - CROSS APPLY rb.record.nodes('/Record/SchedulerMonitorEvent/SystemHealth/SystemIdle') AS ca(c) - ) - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL, HowToStopIt) - SELECT TOP 1 - 23, - 250, - 'Server Info', - 'CPU Utilization', - y.system_idle + N'%. Ring buffer details: ' + CAST(y.record AS NVARCHAR(4000)), - y.system_idle , - 'http://www.BrentOzar.com/go/cpu', - STUFF(( SELECT TOP 2147483647 - CHAR(10) + CHAR(13) - + y2.system_idle - + '% ON ' - + y2.event_date - + ' Ring buffer details: ' - + y2.record - FROM y AS y2 - ORDER BY y2.event_date DESC - FOR XML PATH(N''), TYPE ).value(N'.[1]', N'VARCHAR(MAX)'), 1, 1, N'') AS query - FROM y - ORDER BY y.event_date DESC; - - - /* Highlight if non SQL processes are using >25% CPU - CheckID 28 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 28',10,1) WITH NOWAIT; - END - - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) - SELECT 28, 50, 'Server Performance', 'High CPU Utilization - Not SQL', CONVERT(NVARCHAR(100),100 - (y.SQLUsage + y.SystemIdle)) + N'% - Other Processes (not SQL Server) are using this much CPU. This may impact on the performance of your SQL Server instance', 100 - (y.SQLUsage + y.SystemIdle), 'http://www.BrentOzar.com/go/cpu' - FROM ( - SELECT record, - record.value('(./Record/SchedulerMonitorEvent/SystemHealth/SystemIdle)[1]', 'int') AS SystemIdle - ,record.value('(./Record/SchedulerMonitorEvent/SystemHealth/ProcessUtilization)[1]', 'int') AS SQLUsage - FROM ( - SELECT TOP 1 CONVERT(XML, record) AS record - FROM sys.dm_os_ring_buffers - WHERE ring_buffer_type = N'RING_BUFFER_SCHEDULER_MONITOR' - AND record LIKE '%%' - ORDER BY timestamp DESC) AS rb - ) AS y - WHERE 100 - (y.SQLUsage + y.SystemIdle) >= 25; - - END; /* IF @Seconds < 30 */ - - /* Query Problems - Statistics Updated Recently - CheckID 44 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 44',10,1) WITH NOWAIT; - END - - IF 20 >= (SELECT COUNT(*) FROM sys.databases WHERE name NOT IN ('master', 'model', 'msdb', 'tempdb')) - BEGIN - CREATE TABLE #UpdatedStats (HowToStopIt NVARCHAR(4000), RowsForSorting BIGINT); - IF EXISTS(SELECT * FROM sys.all_objects WHERE name = 'dm_db_stats_properties') - BEGIN - /* We don't want to hang around to obtain locks */ - SET LOCK_TIMEOUT 0; - - EXEC sp_MSforeachdb N'USE [?]; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; SET LOCK_TIMEOUT 1000; - BEGIN TRY - INSERT INTO #UpdatedStats(HowToStopIt, RowsForSorting) - SELECT HowToStopIt = - QUOTENAME(DB_NAME()) + N''.'' + - QUOTENAME(SCHEMA_NAME(obj.schema_id)) + N''.'' + - QUOTENAME(obj.name) + - N'' statistic '' + QUOTENAME(stat.name) + - N'' was updated on '' + CONVERT(NVARCHAR(50), sp.last_updated, 121) + N'','' + - N'' had '' + CAST(sp.rows AS NVARCHAR(50)) + N'' rows, with '' + - CAST(sp.rows_sampled AS NVARCHAR(50)) + N'' rows sampled,'' + - N'' producing '' + CAST(sp.steps AS NVARCHAR(50)) + N'' steps in the histogram.'', - sp.rows - FROM sys.objects AS obj WITH (NOLOCK) - INNER JOIN sys.stats AS stat WITH (NOLOCK) ON stat.object_id = obj.object_id - CROSS APPLY sys.dm_db_stats_properties(stat.object_id, stat.stats_id) AS sp - WHERE sp.last_updated > DATEADD(MI, -15, GETDATE()) - AND obj.is_ms_shipped = 0 - AND ''[?]'' <> ''[tempdb]''; - END TRY - BEGIN CATCH - IF (ERROR_NUMBER() = 1222) - BEGIN - INSERT INTO #UpdatedStats(HowToStopIt, RowsForSorting) - SELECT HowToStopIt = - QUOTENAME(DB_NAME()) + - N'' No information could be retrieved as the lock timeout was exceeded,''+ - N'' this is likely due to an Index operation in Progress'', - -1 - END - ELSE - BEGIN - INSERT INTO #UpdatedStats(HowToStopIt, RowsForSorting) - SELECT HowToStopIt = - QUOTENAME(DB_NAME()) + - N'' No information could be retrieved as a result of error: ''+ - CAST(ERROR_NUMBER() AS NVARCHAR(10)) + - N'' with message: ''+ - CAST(ERROR_MESSAGE() AS NVARCHAR(128)), - -1 - END - END CATCH'; - - /* Set timeout back to a default value of -1 */ - SET LOCK_TIMEOUT -1; - END; - - /* We mark timeout exceeded with a -1 so only show these IF there is statistics info that succeeded */ - IF EXISTS (SELECT * FROM #UpdatedStats WHERE RowsForSorting > -1) - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) - SELECT 44 AS CheckId, - 50 AS Priority, - 'Query Problems' AS FindingGroup, - 'Statistics Updated Recently' AS Finding, - 'http://www.BrentOzar.com/go/stats' AS URL, - 'In the last 15 minutes, statistics were updated. To see which ones, click the HowToStopIt column.' + @LineFeed + @LineFeed - + 'This effectively clears the plan cache for queries that involve these tables,' + @LineFeed - + 'which thereby causes parameter sniffing: those queries are now getting brand new' + @LineFeed - + 'query plans based on whatever parameters happen to call them next.' + @LineFeed + @LineFeed - + 'Be on the lookout for sudden parameter sniffing issues after this time range.', - HowToStopIt = (SELECT (SELECT HowToStopIt + NCHAR(10)) - FROM #UpdatedStats - ORDER BY RowsForSorting DESC - FOR XML PATH('')); - - END - - RAISERROR('Finished running investigatory queries',10,1) WITH NOWAIT; - - - /* End of checks. If we haven't waited @Seconds seconds, wait. */ - IF DATEADD(SECOND,1,SYSDATETIMEOFFSET()) < @FinishSampleTime - BEGIN - RAISERROR('Waiting to match @Seconds parameter',10,1) WITH NOWAIT; - WAITFOR TIME @FinishSampleTimeWaitFor; - END; - - RAISERROR('Capturing second pass of wait stats, perfmon counters, file stats',10,1) WITH NOWAIT; - /* Populate #FileStats, #PerfmonStats, #WaitStats with DMV data. In a second, we'll compare these. */ - INSERT #WaitStats(Pass, SampleTime, wait_type, wait_time_ms, signal_wait_time_ms, waiting_tasks_count) - SELECT - x.Pass, - x.SampleTime, - x.wait_type, - SUM(x.sum_wait_time_ms) AS sum_wait_time_ms, - SUM(x.sum_signal_wait_time_ms) AS sum_signal_wait_time_ms, - SUM(x.sum_waiting_tasks) AS sum_waiting_tasks - FROM ( - SELECT - 2 AS Pass, - SYSDATETIMEOFFSET() AS SampleTime, - owt.wait_type, - SUM(owt.wait_duration_ms) OVER (PARTITION BY owt.wait_type, owt.session_id) - - CASE WHEN @Seconds = 0 THEN 0 ELSE (@Seconds * 1000) END AS sum_wait_time_ms, - 0 AS sum_signal_wait_time_ms, - CASE @Seconds WHEN 0 THEN 0 ELSE 1 END AS sum_waiting_tasks - FROM sys.dm_os_waiting_tasks owt - WHERE owt.session_id > 50 - AND owt.wait_duration_ms >= CASE @Seconds WHEN 0 THEN 0 ELSE @Seconds * 1000 END - UNION ALL - SELECT - 2 AS Pass, - SYSDATETIMEOFFSET() AS SampleTime, - os.wait_type, - SUM(os.wait_time_ms) OVER (PARTITION BY os.wait_type) AS sum_wait_time_ms, - SUM(os.signal_wait_time_ms) OVER (PARTITION BY os.wait_type ) AS sum_signal_wait_time_ms, - SUM(os.waiting_tasks_count) OVER (PARTITION BY os.wait_type) AS sum_waiting_tasks - FROM sys.dm_os_wait_stats os - ) x - WHERE NOT EXISTS - ( - SELECT * - FROM ##WaitCategories AS wc - WHERE wc.WaitType = x.wait_type - AND wc.Ignorable = 1 - ) - GROUP BY x.Pass, x.SampleTime, x.wait_type - ORDER BY sum_wait_time_ms DESC; - - INSERT INTO #FileStats (Pass, SampleTime, DatabaseID, FileID, DatabaseName, FileLogicalName, SizeOnDiskMB, io_stall_read_ms , - num_of_reads, [bytes_read] , io_stall_write_ms,num_of_writes, [bytes_written], PhysicalName, TypeDesc, avg_stall_read_ms, avg_stall_write_ms) - SELECT 2 AS Pass, - SYSDATETIMEOFFSET() AS SampleTime, - mf.[database_id], - mf.[file_id], - DB_NAME(vfs.database_id) AS [db_name], - mf.name + N' [' + mf.type_desc COLLATE SQL_Latin1_General_CP1_CI_AS + N']' AS file_logical_name , - CAST(( ( vfs.size_on_disk_bytes / 1024.0 ) / 1024.0 ) AS INT) AS size_on_disk_mb , - vfs.io_stall_read_ms , - vfs.num_of_reads , - vfs.[num_of_bytes_read], - vfs.io_stall_write_ms , - vfs.num_of_writes , - vfs.[num_of_bytes_written], - mf.physical_name, - mf.type_desc, - 0, - 0 - FROM sys.dm_io_virtual_file_stats (NULL, NULL) AS vfs - INNER JOIN #MasterFiles AS mf ON vfs.file_id = mf.file_id - AND vfs.database_id = mf.database_id - WHERE vfs.num_of_reads > 0 - OR vfs.num_of_writes > 0; - - INSERT INTO #PerfmonStats (Pass, SampleTime, [object_name],[counter_name],[instance_name],[cntr_value],[cntr_type]) - SELECT 2 AS Pass, - SYSDATETIMEOFFSET() AS SampleTime, - RTRIM(dmv.object_name), RTRIM(dmv.counter_name), RTRIM(dmv.instance_name), dmv.cntr_value, dmv.cntr_type - FROM #PerfmonCounters counters - INNER JOIN sys.dm_os_performance_counters dmv ON counters.counter_name COLLATE SQL_Latin1_General_CP1_CI_AS = RTRIM(dmv.counter_name) COLLATE SQL_Latin1_General_CP1_CI_AS - AND counters.[object_name] COLLATE SQL_Latin1_General_CP1_CI_AS = RTRIM(dmv.[object_name]) COLLATE SQL_Latin1_General_CP1_CI_AS - AND (counters.[instance_name] IS NULL OR counters.[instance_name] COLLATE SQL_Latin1_General_CP1_CI_AS = RTRIM(dmv.[instance_name]) COLLATE SQL_Latin1_General_CP1_CI_AS); - - /* Set the latencies and averages. We could do this with a CTE, but we're not ambitious today. */ - UPDATE fNow - SET avg_stall_read_ms = ((fNow.io_stall_read_ms - fBase.io_stall_read_ms) / (fNow.num_of_reads - fBase.num_of_reads)) - FROM #FileStats fNow - INNER JOIN #FileStats fBase ON fNow.DatabaseID = fBase.DatabaseID AND fNow.FileID = fBase.FileID AND fNow.SampleTime > fBase.SampleTime AND fNow.num_of_reads > fBase.num_of_reads AND fNow.io_stall_read_ms > fBase.io_stall_read_ms - WHERE (fNow.num_of_reads - fBase.num_of_reads) > 0; - - UPDATE fNow - SET avg_stall_write_ms = ((fNow.io_stall_write_ms - fBase.io_stall_write_ms) / (fNow.num_of_writes - fBase.num_of_writes)) - FROM #FileStats fNow - INNER JOIN #FileStats fBase ON fNow.DatabaseID = fBase.DatabaseID AND fNow.FileID = fBase.FileID AND fNow.SampleTime > fBase.SampleTime AND fNow.num_of_writes > fBase.num_of_writes AND fNow.io_stall_write_ms > fBase.io_stall_write_ms - WHERE (fNow.num_of_writes - fBase.num_of_writes) > 0; - - UPDATE pNow - SET [value_delta] = pNow.cntr_value - pFirst.cntr_value, - [value_per_second] = ((1.0 * pNow.cntr_value - pFirst.cntr_value) / DATEDIFF(ss, pFirst.SampleTime, pNow.SampleTime)) - FROM #PerfmonStats pNow - INNER JOIN #PerfmonStats pFirst ON pFirst.[object_name] = pNow.[object_name] AND pFirst.counter_name = pNow.counter_name AND (pFirst.instance_name = pNow.instance_name OR (pFirst.instance_name IS NULL AND pNow.instance_name IS NULL)) - AND pNow.ID > pFirst.ID - WHERE DATEDIFF(ss, pFirst.SampleTime, pNow.SampleTime) > 0; - - - /* Query Stats - If we're within 10 seconds of our projected finish time, do the plan cache analysis. - CheckID 18 */ - IF DATEDIFF(ss, @FinishSampleTime, SYSDATETIMEOFFSET()) > 10 AND @CheckProcedureCache = 1 - BEGIN - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 18',10,1) WITH NOWAIT; - END - - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (18, 210, 'Query Stats', 'Plan Cache Analysis Skipped', 'http://www.BrentOzar.com/go/topqueries', - 'Due to excessive load, the plan cache analysis was skipped. To override this, use @ExpertMode = 1.'); - - END; - ELSE IF @CheckProcedureCache = 1 - BEGIN - - - RAISERROR('@CheckProcedureCache = 1, capturing second pass of plan cache',10,1) WITH NOWAIT; - - /* Populate #QueryStats. SQL 2005 doesn't have query hash or query plan hash. */ - IF @@VERSION LIKE 'Microsoft SQL Server 2005%' - BEGIN - IF @FilterPlansByDatabase IS NULL - BEGIN - SET @StringToExecute = N'INSERT INTO #QueryStats ([sql_handle], Pass, SampleTime, statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, Points) - SELECT [sql_handle], 2 AS Pass, SYSDATETIMEOFFSET(), statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, NULL AS query_hash, NULL AS query_plan_hash, 0 - FROM sys.dm_exec_query_stats qs - WHERE qs.last_execution_time >= @StartSampleTimeText;'; - END; - ELSE - BEGIN - SET @StringToExecute = N'INSERT INTO #QueryStats ([sql_handle], Pass, SampleTime, statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, Points) - SELECT [sql_handle], 2 AS Pass, SYSDATETIMEOFFSET(), statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, NULL AS query_hash, NULL AS query_plan_hash, 0 - FROM sys.dm_exec_query_stats qs - CROSS APPLY sys.dm_exec_plan_attributes(qs.plan_handle) AS attr - INNER JOIN #FilterPlansByDatabase dbs ON CAST(attr.value AS INT) = dbs.DatabaseID - WHERE qs.last_execution_time >= @StartSampleTimeText - AND attr.attribute = ''dbid'';'; - END; - END; - ELSE - BEGIN - IF @FilterPlansByDatabase IS NULL - BEGIN - SET @StringToExecute = N'INSERT INTO #QueryStats ([sql_handle], Pass, SampleTime, statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, Points) - SELECT [sql_handle], 2 AS Pass, SYSDATETIMEOFFSET(), statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, 0 - FROM sys.dm_exec_query_stats qs - WHERE qs.last_execution_time >= @StartSampleTimeText'; - END; - ELSE - BEGIN - SET @StringToExecute = N'INSERT INTO #QueryStats ([sql_handle], Pass, SampleTime, statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, Points) - SELECT [sql_handle], 2 AS Pass, SYSDATETIMEOFFSET(), statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, 0 - FROM sys.dm_exec_query_stats qs - CROSS APPLY sys.dm_exec_plan_attributes(qs.plan_handle) AS attr - INNER JOIN #FilterPlansByDatabase dbs ON CAST(attr.value AS INT) = dbs.DatabaseID - WHERE qs.last_execution_time >= @StartSampleTimeText - AND attr.attribute = ''dbid'';'; - END; - END; - /* Old version pre-2016/06/13: - IF @@VERSION LIKE 'Microsoft SQL Server 2005%' - SET @StringToExecute = N'INSERT INTO #QueryStats ([sql_handle], Pass, SampleTime, statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, Points) - SELECT [sql_handle], 2 AS Pass, SYSDATETIMEOFFSET(), statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, NULL AS query_hash, NULL AS query_plan_hash, 0 - FROM sys.dm_exec_query_stats qs - WHERE qs.last_execution_time >= @StartSampleTimeText;'; - ELSE - SET @StringToExecute = N'INSERT INTO #QueryStats ([sql_handle], Pass, SampleTime, statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, Points) - SELECT [sql_handle], 2 AS Pass, SYSDATETIMEOFFSET(), statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, 0 - FROM sys.dm_exec_query_stats qs - WHERE qs.last_execution_time >= @StartSampleTimeText;'; - */ - SET @ParmDefinitions = N'@StartSampleTimeText NVARCHAR(100)'; - SET @Parm1 = CONVERT(NVARCHAR(100), CAST(@StartSampleTime AS DATETIME), 127); - - EXECUTE sp_executesql @StringToExecute, @ParmDefinitions, @StartSampleTimeText = @Parm1; - - RAISERROR('@CheckProcedureCache = 1, totaling up plan cache metrics',10,1) WITH NOWAIT; - - /* Get the totals for the entire plan cache */ - INSERT INTO #QueryStats (Pass, SampleTime, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time) - SELECT 0 AS Pass, SYSDATETIMEOFFSET(), SUM(execution_count), SUM(total_worker_time), SUM(total_physical_reads), SUM(total_logical_writes), SUM(total_logical_reads), SUM(total_clr_time), SUM(total_elapsed_time), MIN(creation_time) - FROM sys.dm_exec_query_stats qs; - - - RAISERROR('@CheckProcedureCache = 1, so analyzing execution plans',10,1) WITH NOWAIT; - /* - Pick the most resource-intensive queries to review. Update the Points field - in #QueryStats - if a query is in the top 10 for logical reads, CPU time, - duration, or execution, add 1 to its points. - */ - WITH qsTop AS ( - SELECT TOP 10 qsNow.ID - FROM #QueryStats qsNow - INNER JOIN #QueryStats qsFirst ON qsNow.[sql_handle] = qsFirst.[sql_handle] AND qsNow.statement_start_offset = qsFirst.statement_start_offset AND qsNow.statement_end_offset = qsFirst.statement_end_offset AND qsNow.plan_generation_num = qsFirst.plan_generation_num AND qsNow.plan_handle = qsFirst.plan_handle AND qsFirst.Pass = 1 - WHERE qsNow.total_elapsed_time > qsFirst.total_elapsed_time - AND qsNow.Pass = 2 - AND qsNow.total_elapsed_time - qsFirst.total_elapsed_time > 1000000 /* Only queries with over 1 second of runtime */ - ORDER BY (qsNow.total_elapsed_time - COALESCE(qsFirst.total_elapsed_time, 0)) DESC) - UPDATE #QueryStats - SET Points = Points + 1 - FROM #QueryStats qs - INNER JOIN qsTop ON qs.ID = qsTop.ID; - - WITH qsTop AS ( - SELECT TOP 10 qsNow.ID - FROM #QueryStats qsNow - INNER JOIN #QueryStats qsFirst ON qsNow.[sql_handle] = qsFirst.[sql_handle] AND qsNow.statement_start_offset = qsFirst.statement_start_offset AND qsNow.statement_end_offset = qsFirst.statement_end_offset AND qsNow.plan_generation_num = qsFirst.plan_generation_num AND qsNow.plan_handle = qsFirst.plan_handle AND qsFirst.Pass = 1 - WHERE qsNow.total_logical_reads > qsFirst.total_logical_reads - AND qsNow.Pass = 2 - AND qsNow.total_logical_reads - qsFirst.total_logical_reads > 1000 /* Only queries with over 1000 reads */ - ORDER BY (qsNow.total_logical_reads - COALESCE(qsFirst.total_logical_reads, 0)) DESC) - UPDATE #QueryStats - SET Points = Points + 1 - FROM #QueryStats qs - INNER JOIN qsTop ON qs.ID = qsTop.ID; - - WITH qsTop AS ( - SELECT TOP 10 qsNow.ID - FROM #QueryStats qsNow - INNER JOIN #QueryStats qsFirst ON qsNow.[sql_handle] = qsFirst.[sql_handle] AND qsNow.statement_start_offset = qsFirst.statement_start_offset AND qsNow.statement_end_offset = qsFirst.statement_end_offset AND qsNow.plan_generation_num = qsFirst.plan_generation_num AND qsNow.plan_handle = qsFirst.plan_handle AND qsFirst.Pass = 1 - WHERE qsNow.total_worker_time > qsFirst.total_worker_time - AND qsNow.Pass = 2 - AND qsNow.total_worker_time - qsFirst.total_worker_time > 1000000 /* Only queries with over 1 second of worker time */ - ORDER BY (qsNow.total_worker_time - COALESCE(qsFirst.total_worker_time, 0)) DESC) - UPDATE #QueryStats - SET Points = Points + 1 - FROM #QueryStats qs - INNER JOIN qsTop ON qs.ID = qsTop.ID; - - WITH qsTop AS ( - SELECT TOP 10 qsNow.ID - FROM #QueryStats qsNow - INNER JOIN #QueryStats qsFirst ON qsNow.[sql_handle] = qsFirst.[sql_handle] AND qsNow.statement_start_offset = qsFirst.statement_start_offset AND qsNow.statement_end_offset = qsFirst.statement_end_offset AND qsNow.plan_generation_num = qsFirst.plan_generation_num AND qsNow.plan_handle = qsFirst.plan_handle AND qsFirst.Pass = 1 - WHERE qsNow.execution_count > qsFirst.execution_count - AND qsNow.Pass = 2 - AND (qsNow.total_elapsed_time - qsFirst.total_elapsed_time > 1000000 /* Only queries with over 1 second of runtime */ - OR qsNow.total_logical_reads - qsFirst.total_logical_reads > 1000 /* Only queries with over 1000 reads */ - OR qsNow.total_worker_time - qsFirst.total_worker_time > 1000000 /* Only queries with over 1 second of worker time */) - ORDER BY (qsNow.execution_count - COALESCE(qsFirst.execution_count, 0)) DESC) - UPDATE #QueryStats - SET Points = Points + 1 - FROM #QueryStats qs - INNER JOIN qsTop ON qs.ID = qsTop.ID; - - /* Query Stats - Most Resource-Intensive Queries - CheckID 17 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 17',10,1) WITH NOWAIT; - END - - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, QueryText, QueryStatsNowID, QueryStatsFirstID, PlanHandle, QueryHash) - SELECT 17, 210, 'Query Stats', 'Most Resource-Intensive Queries', 'http://www.BrentOzar.com/go/topqueries', - 'Query stats during the sample:' + @LineFeed + - 'Executions: ' + CAST(qsNow.execution_count - (COALESCE(qsFirst.execution_count, 0)) AS NVARCHAR(100)) + @LineFeed + - 'Elapsed Time: ' + CAST(qsNow.total_elapsed_time - (COALESCE(qsFirst.total_elapsed_time, 0)) AS NVARCHAR(100)) + @LineFeed + - 'CPU Time: ' + CAST(qsNow.total_worker_time - (COALESCE(qsFirst.total_worker_time, 0)) AS NVARCHAR(100)) + @LineFeed + - 'Logical Reads: ' + CAST(qsNow.total_logical_reads - (COALESCE(qsFirst.total_logical_reads, 0)) AS NVARCHAR(100)) + @LineFeed + - 'Logical Writes: ' + CAST(qsNow.total_logical_writes - (COALESCE(qsFirst.total_logical_writes, 0)) AS NVARCHAR(100)) + @LineFeed + - 'CLR Time: ' + CAST(qsNow.total_clr_time - (COALESCE(qsFirst.total_clr_time, 0)) AS NVARCHAR(100)) + @LineFeed + - @LineFeed + @LineFeed + 'Query stats since ' + CONVERT(NVARCHAR(100), qsNow.creation_time ,121) + @LineFeed + - 'Executions: ' + CAST(qsNow.execution_count AS NVARCHAR(100)) + - CASE qsTotal.execution_count WHEN 0 THEN '' ELSE (' - Percent of Server Total: ' + CAST(CAST(100.0 * qsNow.execution_count / qsTotal.execution_count AS DECIMAL(6,2)) AS NVARCHAR(100)) + '%') END + @LineFeed + - 'Elapsed Time: ' + CAST(qsNow.total_elapsed_time AS NVARCHAR(100)) + - CASE qsTotal.total_elapsed_time WHEN 0 THEN '' ELSE (' - Percent of Server Total: ' + CAST(CAST(100.0 * qsNow.total_elapsed_time / qsTotal.total_elapsed_time AS DECIMAL(6,2)) AS NVARCHAR(100)) + '%') END + @LineFeed + - 'CPU Time: ' + CAST(qsNow.total_worker_time AS NVARCHAR(100)) + - CASE qsTotal.total_worker_time WHEN 0 THEN '' ELSE (' - Percent of Server Total: ' + CAST(CAST(100.0 * qsNow.total_worker_time / qsTotal.total_worker_time AS DECIMAL(6,2)) AS NVARCHAR(100)) + '%') END + @LineFeed + - 'Logical Reads: ' + CAST(qsNow.total_logical_reads AS NVARCHAR(100)) + - CASE qsTotal.total_logical_reads WHEN 0 THEN '' ELSE (' - Percent of Server Total: ' + CAST(CAST(100.0 * qsNow.total_logical_reads / qsTotal.total_logical_reads AS DECIMAL(6,2)) AS NVARCHAR(100)) + '%') END + @LineFeed + - 'Logical Writes: ' + CAST(qsNow.total_logical_writes AS NVARCHAR(100)) + - CASE qsTotal.total_logical_writes WHEN 0 THEN '' ELSE (' - Percent of Server Total: ' + CAST(CAST(100.0 * qsNow.total_logical_writes / qsTotal.total_logical_writes AS DECIMAL(6,2)) AS NVARCHAR(100)) + '%') END + @LineFeed + - 'CLR Time: ' + CAST(qsNow.total_clr_time AS NVARCHAR(100)) + - CASE qsTotal.total_clr_time WHEN 0 THEN '' ELSE (' - Percent of Server Total: ' + CAST(CAST(100.0 * qsNow.total_clr_time / qsTotal.total_clr_time AS DECIMAL(6,2)) AS NVARCHAR(100)) + '%') END + @LineFeed + - --@LineFeed + @LineFeed + 'Query hash: ' + CAST(qsNow.query_hash AS NVARCHAR(100)) + @LineFeed + - --@LineFeed + @LineFeed + 'Query plan hash: ' + CAST(qsNow.query_plan_hash AS NVARCHAR(100)) + - @LineFeed AS Details, - 'See the URL for tuning tips on why this query may be consuming resources.' AS HowToStopIt, - qp.query_plan, - QueryText = SUBSTRING(st.text, - (qsNow.statement_start_offset / 2) + 1, - ((CASE qsNow.statement_end_offset - WHEN -1 THEN DATALENGTH(st.text) - ELSE qsNow.statement_end_offset - END - qsNow.statement_start_offset) / 2) + 1), - qsNow.ID AS QueryStatsNowID, - qsFirst.ID AS QueryStatsFirstID, - qsNow.plan_handle AS PlanHandle, - qsNow.query_hash - FROM #QueryStats qsNow - INNER JOIN #QueryStats qsTotal ON qsTotal.Pass = 0 - LEFT OUTER JOIN #QueryStats qsFirst ON qsNow.[sql_handle] = qsFirst.[sql_handle] AND qsNow.statement_start_offset = qsFirst.statement_start_offset AND qsNow.statement_end_offset = qsFirst.statement_end_offset AND qsNow.plan_generation_num = qsFirst.plan_generation_num AND qsNow.plan_handle = qsFirst.plan_handle AND qsFirst.Pass = 1 - CROSS APPLY sys.dm_exec_sql_text(qsNow.sql_handle) AS st - CROSS APPLY sys.dm_exec_query_plan(qsNow.plan_handle) AS qp - WHERE qsNow.Points > 0 AND st.text IS NOT NULL AND qp.query_plan IS NOT NULL; - - UPDATE #BlitzFirstResults - SET DatabaseID = CAST(attr.value AS INT), - DatabaseName = DB_NAME(CAST(attr.value AS INT)) - FROM #BlitzFirstResults - CROSS APPLY sys.dm_exec_plan_attributes(#BlitzFirstResults.PlanHandle) AS attr - WHERE attr.attribute = 'dbid'; - - - END; /* IF DATEDIFF(ss, @FinishSampleTime, SYSDATETIMEOFFSET()) > 10 AND @CheckProcedureCache = 1 */ - - - RAISERROR('Analyzing changes between first and second passes of DMVs',10,1) WITH NOWAIT; - - /* Wait Stats - CheckID 6 */ - /* Compare the current wait stats to the sample we took at the start, and insert the top 10 waits. */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 6',10,1) WITH NOWAIT; - END - - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, DetailsInt) - SELECT TOP 10 6 AS CheckID, - 200 AS Priority, - 'Wait Stats' AS FindingGroup, - wNow.wait_type AS Finding, /* IF YOU CHANGE THIS, STUFF WILL BREAK. Other checks look for wait type names in the Finding field. See checks 11, 12 as example. */ - N'https://www.sqlskills.com/help/waits/' + LOWER(wNow.wait_type) + '/' AS URL, - 'For ' + CAST(((wNow.wait_time_ms - COALESCE(wBase.wait_time_ms,0)) / 1000) AS NVARCHAR(100)) + ' seconds over the last ' + CASE @Seconds WHEN 0 THEN (CAST(DATEDIFF(dd,@StartSampleTime,@FinishSampleTime) AS NVARCHAR(10)) + ' days') ELSE (CAST(@Seconds AS NVARCHAR(10)) + ' seconds') END + ', SQL Server was waiting on this particular bottleneck.' + @LineFeed + @LineFeed AS Details, - 'See the URL for more details on how to mitigate this wait type.' AS HowToStopIt, - ((wNow.wait_time_ms - COALESCE(wBase.wait_time_ms,0)) / 1000) AS DetailsInt - FROM #WaitStats wNow - LEFT OUTER JOIN #WaitStats wBase ON wNow.wait_type = wBase.wait_type AND wNow.SampleTime > wBase.SampleTime - WHERE wNow.wait_time_ms > (wBase.wait_time_ms + (.5 * (DATEDIFF(ss,@StartSampleTime,@FinishSampleTime)) * 1000)) /* Only look for things we've actually waited on for half of the time or more */ - ORDER BY (wNow.wait_time_ms - COALESCE(wBase.wait_time_ms,0)) DESC; - - /* Server Performance - Poison Wait Detected - CheckID 30 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 30',10,1) WITH NOWAIT; - END - - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, DetailsInt) - SELECT 30 AS CheckID, - 10 AS Priority, - 'Server Performance' AS FindingGroup, - 'Poison Wait Detected: ' + wNow.wait_type AS Finding, - N'http://www.brentozar.com/go/poison/#' + wNow.wait_type AS URL, - 'For ' + CAST(((wNow.wait_time_ms - COALESCE(wBase.wait_time_ms,0)) / 1000) AS NVARCHAR(100)) + ' seconds over the last ' + CASE @Seconds WHEN 0 THEN (CAST(DATEDIFF(dd,@StartSampleTime,@FinishSampleTime) AS NVARCHAR(10)) + ' days') ELSE (CAST(@Seconds AS NVARCHAR(10)) + ' seconds') END + ', SQL Server was waiting on this particular bottleneck.' + @LineFeed + @LineFeed AS Details, - 'See the URL for more details on how to mitigate this wait type.' AS HowToStopIt, - ((wNow.wait_time_ms - COALESCE(wBase.wait_time_ms,0)) / 1000) AS DetailsInt - FROM #WaitStats wNow - LEFT OUTER JOIN #WaitStats wBase ON wNow.wait_type = wBase.wait_type AND wNow.SampleTime > wBase.SampleTime - WHERE wNow.wait_type IN ('IO_QUEUE_LIMIT', 'IO_RETRY', 'LOG_RATE_GOVERNOR', 'POOL_LOG_RATE_GOVERNOR', 'PREEMPTIVE_DEBUG', 'RESMGR_THROTTLED', 'RESOURCE_SEMAPHORE', 'RESOURCE_SEMAPHORE_QUERY_COMPILE','SE_REPL_CATCHUP_THROTTLE','SE_REPL_COMMIT_ACK','SE_REPL_COMMIT_TURN','SE_REPL_ROLLBACK_ACK','SE_REPL_SLOW_SECONDARY_THROTTLE','THREADPOOL') - AND wNow.wait_time_ms > (wBase.wait_time_ms + 1000); - - - /* Server Performance - Slow Data File Reads - CheckID 11 */ - IF EXISTS (SELECT * FROM #BlitzFirstResults WHERE Finding LIKE 'PAGEIOLATCH%') - BEGIN - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 11',10,1) WITH NOWAIT; - END - - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, DatabaseID, DatabaseName) - SELECT TOP 10 11 AS CheckID, - 50 AS Priority, - 'Server Performance' AS FindingGroup, - 'Slow Data File Reads' AS Finding, - 'http://www.BrentOzar.com/go/slow/' AS URL, - 'Your server is experiencing PAGEIOLATCH% waits due to slow data file reads. This file is one of the reasons why.' + @LineFeed - + 'File: ' + fNow.PhysicalName + @LineFeed - + 'Number of reads during the sample: ' + CAST((fNow.num_of_reads - fBase.num_of_reads) AS NVARCHAR(20)) + @LineFeed - + 'Seconds spent waiting on storage for these reads: ' + CAST(((fNow.io_stall_read_ms - fBase.io_stall_read_ms) / 1000.0) AS NVARCHAR(20)) + @LineFeed - + 'Average read latency during the sample: ' + CAST(((fNow.io_stall_read_ms - fBase.io_stall_read_ms) / (fNow.num_of_reads - fBase.num_of_reads) ) AS NVARCHAR(20)) + ' milliseconds' + @LineFeed - + 'Microsoft guidance for data file read speed: 20ms or less.' + @LineFeed + @LineFeed AS Details, - 'See the URL for more details on how to mitigate this wait type.' AS HowToStopIt, - fNow.DatabaseID, - fNow.DatabaseName - FROM #FileStats fNow - INNER JOIN #FileStats fBase ON fNow.DatabaseID = fBase.DatabaseID AND fNow.FileID = fBase.FileID AND fNow.SampleTime > fBase.SampleTime AND fNow.num_of_reads > fBase.num_of_reads AND fNow.io_stall_read_ms > (fBase.io_stall_read_ms + 1000) - WHERE (fNow.io_stall_read_ms - fBase.io_stall_read_ms) / (fNow.num_of_reads - fBase.num_of_reads) >= @FileLatencyThresholdMS - AND fNow.TypeDesc = 'ROWS' - ORDER BY (fNow.io_stall_read_ms - fBase.io_stall_read_ms) / (fNow.num_of_reads - fBase.num_of_reads) DESC; - END; - - /* Server Performance - Slow Log File Writes - CheckID 12 */ - IF EXISTS (SELECT * FROM #BlitzFirstResults WHERE Finding LIKE 'WRITELOG%') - BEGIN - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 12',10,1) WITH NOWAIT; - END - - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, DatabaseID, DatabaseName) - SELECT TOP 10 12 AS CheckID, - 50 AS Priority, - 'Server Performance' AS FindingGroup, - 'Slow Log File Writes' AS Finding, - 'http://www.BrentOzar.com/go/slow/' AS URL, - 'Your server is experiencing WRITELOG waits due to slow log file writes. This file is one of the reasons why.' + @LineFeed - + 'File: ' + fNow.PhysicalName + @LineFeed - + 'Number of writes during the sample: ' + CAST((fNow.num_of_writes - fBase.num_of_writes) AS NVARCHAR(20)) + @LineFeed - + 'Seconds spent waiting on storage for these writes: ' + CAST(((fNow.io_stall_write_ms - fBase.io_stall_write_ms) / 1000.0) AS NVARCHAR(20)) + @LineFeed - + 'Average write latency during the sample: ' + CAST(((fNow.io_stall_write_ms - fBase.io_stall_write_ms) / (fNow.num_of_writes - fBase.num_of_writes) ) AS NVARCHAR(20)) + ' milliseconds' + @LineFeed - + 'Microsoft guidance for log file write speed: 3ms or less.' + @LineFeed + @LineFeed AS Details, - 'See the URL for more details on how to mitigate this wait type.' AS HowToStopIt, - fNow.DatabaseID, - fNow.DatabaseName - FROM #FileStats fNow - INNER JOIN #FileStats fBase ON fNow.DatabaseID = fBase.DatabaseID AND fNow.FileID = fBase.FileID AND fNow.SampleTime > fBase.SampleTime AND fNow.num_of_writes > fBase.num_of_writes AND fNow.io_stall_write_ms > (fBase.io_stall_write_ms + 1000) - WHERE (fNow.io_stall_write_ms - fBase.io_stall_write_ms) / (fNow.num_of_writes - fBase.num_of_writes) >= @FileLatencyThresholdMS - AND fNow.TypeDesc = 'LOG' - ORDER BY (fNow.io_stall_write_ms - fBase.io_stall_write_ms) / (fNow.num_of_writes - fBase.num_of_writes) DESC; - END; - - - /* SQL Server Internal Maintenance - Log File Growing - CheckID 13 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 13',10,1) WITH NOWAIT; - END - - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) - SELECT 13 AS CheckID, - 1 AS Priority, - 'SQL Server Internal Maintenance' AS FindingGroup, - 'Log File Growing' AS Finding, - 'http://www.BrentOzar.com/askbrent/file-growing/' AS URL, - 'Number of growths during the sample: ' + CAST(ps.value_delta AS NVARCHAR(20)) + @LineFeed - + 'Determined by sampling Perfmon counter ' + ps.object_name + ' - ' + ps.counter_name + @LineFeed AS Details, - 'Pre-grow data and log files during maintenance windows so that they do not grow during production loads. See the URL for more details.' AS HowToStopIt - FROM #PerfmonStats ps - WHERE ps.Pass = 2 - AND object_name = @ServiceName + ':Databases' - AND counter_name = 'Log Growths' - AND value_delta > 0; - - - /* SQL Server Internal Maintenance - Log File Shrinking - CheckID 14 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 14',10,1) WITH NOWAIT; - END - - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) - SELECT 14 AS CheckID, - 1 AS Priority, - 'SQL Server Internal Maintenance' AS FindingGroup, - 'Log File Shrinking' AS Finding, - 'http://www.BrentOzar.com/askbrent/file-shrinking/' AS URL, - 'Number of shrinks during the sample: ' + CAST(ps.value_delta AS NVARCHAR(20)) + @LineFeed - + 'Determined by sampling Perfmon counter ' + ps.object_name + ' - ' + ps.counter_name + @LineFeed AS Details, - 'Pre-grow data and log files during maintenance windows so that they do not grow during production loads. See the URL for more details.' AS HowToStopIt - FROM #PerfmonStats ps - WHERE ps.Pass = 2 - AND object_name = @ServiceName + ':Databases' - AND counter_name = 'Log Shrinks' - AND value_delta > 0; - - /* Query Problems - Compilations/Sec High - CheckID 15 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 15',10,1) WITH NOWAIT; - END - - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) - SELECT 15 AS CheckID, - 50 AS Priority, - 'Query Problems' AS FindingGroup, - 'Compilations/Sec High' AS Finding, - 'http://www.BrentOzar.com/askbrent/compilations/' AS URL, - 'Number of batch requests during the sample: ' + CAST(ps.value_delta AS NVARCHAR(20)) + @LineFeed - + 'Number of compilations during the sample: ' + CAST(psComp.value_delta AS NVARCHAR(20)) + @LineFeed - + 'For OLTP environments, Microsoft recommends that 90% of batch requests should hit the plan cache, and not be compiled from scratch. We are exceeding that threshold.' + @LineFeed AS Details, - 'To find the queries that are compiling, start with:' + @LineFeed - + 'sp_BlitzCache @SortOrder = ''recent compilations''' + @LineFeed - + 'If dynamic SQL or non-parameterized strings are involved, consider enabling Forced Parameterization. See the URL for more details.' AS HowToStopIt - FROM #PerfmonStats ps - INNER JOIN #PerfmonStats psComp ON psComp.Pass = 2 AND psComp.object_name = @ServiceName + ':SQL Statistics' AND psComp.counter_name = 'SQL Compilations/sec' AND psComp.value_delta > 0 - WHERE ps.Pass = 2 - AND ps.object_name = @ServiceName + ':SQL Statistics' - AND ps.counter_name = 'Batch Requests/sec' - AND ps.value_delta > (1000 * @Seconds) /* Ignore servers sitting idle */ - AND (psComp.value_delta * 10) > ps.value_delta; /* Compilations are more than 10% of batch requests per second */ - - /* Query Problems - Re-Compilations/Sec High - CheckID 16 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 16',10,1) WITH NOWAIT; - END - - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) - SELECT 16 AS CheckID, - 50 AS Priority, - 'Query Problems' AS FindingGroup, - 'Re-Compilations/Sec High' AS Finding, - 'http://www.BrentOzar.com/askbrent/recompilations/' AS URL, - 'Number of batch requests during the sample: ' + CAST(ps.value_delta AS NVARCHAR(20)) + @LineFeed - + 'Number of recompilations during the sample: ' + CAST(psComp.value_delta AS NVARCHAR(20)) + @LineFeed - + 'More than 10% of our queries are being recompiled. This is typically due to statistics changing on objects.' + @LineFeed AS Details, - 'To find the queries that are being forced to recompile, start with:' + @LineFeed - + 'sp_BlitzCache @SortOrder = ''recent compilations''' + @LineFeed - + 'Examine those plans to find out which objects are changing so quickly that they hit the stats update threshold. See the URL for more details.' AS HowToStopIt - FROM #PerfmonStats ps - INNER JOIN #PerfmonStats psComp ON psComp.Pass = 2 AND psComp.object_name = @ServiceName + ':SQL Statistics' AND psComp.counter_name = 'SQL Re-Compilations/sec' AND psComp.value_delta > 0 - WHERE ps.Pass = 2 - AND ps.object_name = @ServiceName + ':SQL Statistics' - AND ps.counter_name = 'Batch Requests/sec' - AND ps.value_delta > (1000 * @Seconds) /* Ignore servers sitting idle */ - AND (psComp.value_delta * 10) > ps.value_delta; /* Recompilations are more than 10% of batch requests per second */ - - /* Table Problems - Forwarded Fetches/Sec High - CheckID 29 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 29',10,1) WITH NOWAIT; - END - - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) - SELECT 29 AS CheckID, - 40 AS Priority, - 'Table Problems' AS FindingGroup, - 'Forwarded Fetches/Sec High' AS Finding, - 'https://BrentOzar.com/go/fetch/' AS URL, - CAST(ps.value_delta AS NVARCHAR(20)) + ' forwarded fetches (from SQLServer:Access Methods counter)' + @LineFeed - + 'Check your heaps: they need to be rebuilt, or they need a clustered index applied.' + @LineFeed AS Details, - 'Rebuild your heaps. If you use Ola Hallengren maintenance scripts, those do not rebuild heaps by default: https://www.brentozar.com/archive/2016/07/fix-forwarded-records/' AS HowToStopIt - FROM #PerfmonStats ps - INNER JOIN #PerfmonStats psComp ON psComp.Pass = 2 AND psComp.object_name = @ServiceName + ':Access Methods' AND psComp.counter_name = 'Forwarded Records/sec' AND psComp.value_delta > 100 - WHERE ps.Pass = 2 - AND ps.object_name = @ServiceName + ':Access Methods' - AND ps.counter_name = 'Forwarded Records/sec' - AND ps.value_delta > (100 * @Seconds); /* Ignore servers sitting idle */ - - /* Check for temp objects with high forwarded fetches. - This has to be done as dynamic SQL because we have to execute OBJECT_NAME inside TempDB. */ - IF @@ROWCOUNT > 0 - BEGIN - SET @StringToExecute = N'USE tempdb; - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) - SELECT TOP 10 29 AS CheckID, - 40 AS Priority, - ''Table Problems'' AS FindingGroup, - ''Forwarded Fetches/Sec High: TempDB Object'' AS Finding, - ''https://BrentOzar.com/go/fetch/'' AS URL, - CAST(COALESCE(os.forwarded_fetch_count,0) - COALESCE(os_prior.forwarded_fetch_count,0) AS NVARCHAR(20)) + '' forwarded fetches on '' + - CASE WHEN OBJECT_NAME(os.object_id) IS NULL THEN ''an unknown table '' - WHEN LEN(OBJECT_NAME(os.object_id)) < 50 THEN ''a table variable, internal identifier '' + OBJECT_NAME(os.object_id) - ELSE ''a temp table '' + OBJECT_NAME(os.object_id) - END AS Details, - ''Look through your source code to find the object creating these objects, and tune the creation and population to reduce fetches. See the URL for details.'' AS HowToStopIt - FROM tempdb.sys.dm_db_index_operational_stats(DB_ID(''tempdb''), NULL, NULL, NULL) os - LEFT OUTER JOIN #TempdbOperationalStats os_prior ON os.object_id = os_prior.object_id - AND os.forwarded_fetch_count > os_prior.forwarded_fetch_count - WHERE os.database_id = DB_ID(''tempdb'') - AND os.forwarded_fetch_count - COALESCE(os_prior.forwarded_fetch_count,0) > 100 - ORDER BY os.forwarded_fetch_count DESC;' - - EXECUTE sp_executesql @StringToExecute; - END - - /* In-Memory OLTP - Garbage Collection in Progress - CheckID 31 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 31',10,1) WITH NOWAIT; - END - - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) - SELECT 31 AS CheckID, - 50 AS Priority, - 'In-Memory OLTP' AS FindingGroup, - 'Garbage Collection in Progress' AS Finding, - 'https://BrentOzar.com/go/garbage/' AS URL, - CAST(ps.value_delta AS NVARCHAR(50)) + ' rows processed (from SQL Server YYYY XTP Garbage Collection:Rows processed/sec counter)' + @LineFeed - + 'This can happen due to memory pressure (causing In-Memory OLTP to shrink its footprint) or' + @LineFeed - + 'due to transactional workloads that constantly insert/delete data.' AS Details, - 'Sadly, you cannot choose when garbage collection occurs. This is one of the many gotchas of Hekaton. Learn more: http://nedotter.com/archive/2016/04/row-version-lifecycle-for-in-memory-oltp/' AS HowToStopIt - FROM #PerfmonStats ps - INNER JOIN #PerfmonStats psComp ON psComp.Pass = 2 AND psComp.object_name LIKE '%XTP Garbage Collection' AND psComp.counter_name = 'Rows processed/sec' AND psComp.value_delta > 100 - WHERE ps.Pass = 2 - AND ps.object_name LIKE '%XTP Garbage Collection' - AND ps.counter_name = 'Rows processed/sec' - AND ps.value_delta > (100 * @Seconds); /* Ignore servers sitting idle */ - - /* In-Memory OLTP - Transactions Aborted - CheckID 32 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 32',10,1) WITH NOWAIT; - END - - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) - SELECT 32 AS CheckID, - 100 AS Priority, - 'In-Memory OLTP' AS FindingGroup, - 'Transactions Aborted' AS Finding, - 'https://BrentOzar.com/go/aborted/' AS URL, - CAST(ps.value_delta AS NVARCHAR(50)) + ' transactions aborted (from SQL Server YYYY XTP Transactions:Transactions aborted/sec counter)' + @LineFeed - + 'This may indicate that data is changing, or causing folks to retry their transactions, thereby increasing load.' AS Details, - 'Dig into your In-Memory OLTP transactions to figure out which ones are failing and being retried.' AS HowToStopIt - FROM #PerfmonStats ps - INNER JOIN #PerfmonStats psComp ON psComp.Pass = 2 AND psComp.object_name LIKE '%XTP Transactions' AND psComp.counter_name = 'Transactions aborted/sec' AND psComp.value_delta > 100 - WHERE ps.Pass = 2 - AND ps.object_name LIKE '%XTP Transactions' - AND ps.counter_name = 'Transactions aborted/sec' - AND ps.value_delta > (10 * @Seconds); /* Ignore servers sitting idle */ - - /* Query Problems - Suboptimal Plans/Sec High - CheckID 33 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 33',10,1) WITH NOWAIT; - END - - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) - SELECT 32 AS CheckID, - 100 AS Priority, - 'Query Problems' AS FindingGroup, - 'Suboptimal Plans/Sec High' AS Finding, - 'https://BrentOzar.com/go/suboptimal/' AS URL, - CAST(ps.value_delta AS NVARCHAR(50)) + ' plans reported in the ' + CAST(ps.instance_name AS NVARCHAR(100)) + ' workload group (from Workload GroupStats:Suboptimal plans/sec counter)' + @LineFeed - + 'Even if you are not using Resource Governor, it still tracks information about user queries, memory grants, etc.' AS Details, - 'Check out sp_BlitzCache to get more information about recent queries, or try sp_BlitzWho to see currently running queries.' AS HowToStopIt - FROM #PerfmonStats ps - INNER JOIN #PerfmonStats psComp ON psComp.Pass = 2 AND psComp.object_name = @ServiceName + ':Workload GroupStats' AND psComp.counter_name = 'Suboptimal plans/sec' AND psComp.value_delta > 100 - WHERE ps.Pass = 2 - AND ps.object_name = @ServiceName + ':Workload GroupStats' - AND ps.counter_name = 'Suboptimal plans/sec' - AND ps.value_delta > (10 * @Seconds); /* Ignore servers sitting idle */ - - /* Azure Performance - Database is Maxed Out - CheckID 41 */ - IF SERVERPROPERTY('Edition') = 'SQL Azure' - BEGIN - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 41',10,1) WITH NOWAIT; - END - - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) - SELECT 41 AS CheckID, - 10 AS Priority, - 'Azure Performance' AS FindingGroup, - 'Database is Maxed Out' AS Finding, - 'https://BrentOzar.com/go/maxedout' AS URL, - N'At ' + CONVERT(NVARCHAR(100), s.end_time ,121) + N', your database approached (or hit) your DTU limits:' + @LineFeed - + N'Average CPU percent: ' + CAST(avg_cpu_percent AS NVARCHAR(50)) + @LineFeed - + N'Average data IO percent: ' + CAST(avg_data_io_percent AS NVARCHAR(50)) + @LineFeed - + N'Average log write percent: ' + CAST(avg_log_write_percent AS NVARCHAR(50)) + @LineFeed - + N'Max worker percent: ' + CAST(max_worker_percent AS NVARCHAR(50)) + @LineFeed - + N'Max session percent: ' + CAST(max_session_percent AS NVARCHAR(50)) AS Details, - 'Tune your queries or indexes with sp_BlitzCache or sp_BlitzIndex, or consider upgrading to a higher DTU level.' AS HowToStopIt - FROM sys.dm_db_resource_stats s - WHERE s.end_time >= DATEADD(MI, -5, GETDATE()) - AND (avg_cpu_percent > 90 - OR avg_data_io_percent >= 90 - OR avg_log_write_percent >=90 - OR max_worker_percent >= 90 - OR max_session_percent >= 90); - END - - /* Server Info - Batch Requests per Sec - CheckID 19 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 19',10,1) WITH NOWAIT; - END - - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, DetailsInt) - SELECT 19 AS CheckID, - 250 AS Priority, - 'Server Info' AS FindingGroup, - 'Batch Requests per Sec' AS Finding, - 'http://www.BrentOzar.com/go/measure' AS URL, - CAST(CAST(ps.value_delta AS MONEY) / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS NVARCHAR(20)) AS Details, - ps.value_delta / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS DetailsInt - FROM #PerfmonStats ps - INNER JOIN #PerfmonStats ps1 ON ps.object_name = ps1.object_name AND ps.counter_name = ps1.counter_name AND ps1.Pass = 1 - WHERE ps.Pass = 2 - AND ps.object_name = @ServiceName + ':SQL Statistics' - AND ps.counter_name = 'Batch Requests/sec'; - - - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','SQL Compilations/sec', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','SQL Re-Compilations/sec', NULL); - - /* Server Info - SQL Compilations/sec - CheckID 25 */ - IF @ExpertMode = 1 - BEGIN - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 25',10,1) WITH NOWAIT; - END - - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, DetailsInt) - SELECT 25 AS CheckID, - 250 AS Priority, - 'Server Info' AS FindingGroup, - 'SQL Compilations per Sec' AS Finding, - 'http://www.BrentOzar.com/go/measure' AS URL, - CAST(ps.value_delta / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS NVARCHAR(20)) AS Details, - ps.value_delta / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS DetailsInt - FROM #PerfmonStats ps - INNER JOIN #PerfmonStats ps1 ON ps.object_name = ps1.object_name AND ps.counter_name = ps1.counter_name AND ps1.Pass = 1 - WHERE ps.Pass = 2 - AND ps.object_name = @ServiceName + ':SQL Statistics' - AND ps.counter_name = 'SQL Compilations/sec'; - END - - /* Server Info - SQL Re-Compilations/sec - CheckID 26 */ - IF @ExpertMode = 1 - BEGIN - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 26',10,1) WITH NOWAIT; - END - - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, DetailsInt) - SELECT 26 AS CheckID, - 250 AS Priority, - 'Server Info' AS FindingGroup, - 'SQL Re-Compilations per Sec' AS Finding, - 'http://www.BrentOzar.com/go/measure' AS URL, - CAST(ps.value_delta / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS NVARCHAR(20)) AS Details, - ps.value_delta / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS DetailsInt - FROM #PerfmonStats ps - INNER JOIN #PerfmonStats ps1 ON ps.object_name = ps1.object_name AND ps.counter_name = ps1.counter_name AND ps1.Pass = 1 - WHERE ps.Pass = 2 - AND ps.object_name = @ServiceName + ':SQL Statistics' - AND ps.counter_name = 'SQL Re-Compilations/sec'; - END - - /* Server Info - Wait Time per Core per Sec - CheckID 20 */ - IF @Seconds > 0 - BEGIN; - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 20',10,1) WITH NOWAIT; - END; - - WITH waits1(SampleTime, waits_ms) AS (SELECT SampleTime, SUM(ws1.wait_time_ms) FROM #WaitStats ws1 WHERE ws1.Pass = 1 GROUP BY SampleTime), - waits2(SampleTime, waits_ms) AS (SELECT SampleTime, SUM(ws2.wait_time_ms) FROM #WaitStats ws2 WHERE ws2.Pass = 2 GROUP BY SampleTime), - cores(cpu_count) AS (SELECT SUM(1) FROM sys.dm_os_schedulers WHERE status = 'VISIBLE ONLINE' AND is_online = 1) - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, DetailsInt) - SELECT 20 AS CheckID, - 250 AS Priority, - 'Server Info' AS FindingGroup, - 'Wait Time per Core per Sec' AS Finding, - 'http://www.BrentOzar.com/go/measure' AS URL, - CAST((CAST(waits2.waits_ms - waits1.waits_ms AS MONEY)) / 1000 / i.cpu_count / DATEDIFF(ss, waits1.SampleTime, waits2.SampleTime) AS NVARCHAR(20)) AS Details, - (waits2.waits_ms - waits1.waits_ms) / 1000 / i.cpu_count / DATEDIFF(ss, waits1.SampleTime, waits2.SampleTime) AS DetailsInt - FROM cores i - CROSS JOIN waits1 - CROSS JOIN waits2; - END; - - /* If we're waiting 30+ seconds, run these checks at the end. - We get this data from the ring buffers, and it's only updated once per minute, so might - as well get it now - whereas if we're checking 30+ seconds, it might get updated by the - end of our sp_BlitzFirst session. */ - IF @Seconds >= 30 - BEGIN - /* Server Performance - High CPU Utilization CheckID 24 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 24',10,1) WITH NOWAIT; - END - - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) - SELECT 24, 50, 'Server Performance', 'High CPU Utilization', CAST(100 - SystemIdle AS NVARCHAR(20)) + N'%. Ring buffer details: ' + CAST(record AS NVARCHAR(4000)), 100 - SystemIdle, 'http://www.BrentOzar.com/go/cpu' - FROM ( - SELECT record, - record.value('(./Record/SchedulerMonitorEvent/SystemHealth/SystemIdle)[1]', 'int') AS SystemIdle - FROM ( - SELECT TOP 1 CONVERT(XML, record) AS record - FROM sys.dm_os_ring_buffers - WHERE ring_buffer_type = N'RING_BUFFER_SCHEDULER_MONITOR' - AND record LIKE '%%' - ORDER BY timestamp DESC) AS rb - ) AS y - WHERE 100 - SystemIdle >= 50; - - /* Server Performance - CPU Utilization CheckID 23 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 23',10,1) WITH NOWAIT; - END - - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) - SELECT 23, 250, 'Server Info', 'CPU Utilization', CAST(100 - SystemIdle AS NVARCHAR(20)) + N'%. Ring buffer details: ' + CAST(record AS NVARCHAR(4000)), 100 - SystemIdle, 'http://www.BrentOzar.com/go/cpu' - FROM ( - SELECT record, - record.value('(./Record/SchedulerMonitorEvent/SystemHealth/SystemIdle)[1]', 'int') AS SystemIdle - FROM ( - SELECT TOP 1 CONVERT(XML, record) AS record - FROM sys.dm_os_ring_buffers - WHERE ring_buffer_type = N'RING_BUFFER_SCHEDULER_MONITOR' - AND record LIKE '%%' - ORDER BY timestamp DESC) AS rb - ) AS y; - - END; /* IF @Seconds >= 30 */ - - - /* If we didn't find anything, apologize. */ - IF NOT EXISTS (SELECT * FROM #BlitzFirstResults WHERE Priority < 250) - BEGIN - - INSERT INTO #BlitzFirstResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - VALUES ( -1 , - 1 , - 'No Problems Found' , - 'From Your Community Volunteers' , - 'http://FirstResponderKit.org/' , - 'Try running our more in-depth checks with sp_Blitz, or there may not be an unusual SQL Server performance problem. ' - ); - - END; /*IF NOT EXISTS (SELECT * FROM #BlitzFirstResults) */ - - /* Add credits for the nice folks who put so much time into building and maintaining this for free: */ - INSERT INTO #BlitzFirstResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - VALUES ( -1 , - 255 , - 'Thanks!' , - 'From Your Community Volunteers' , - 'http://FirstResponderKit.org/' , - 'To get help or add your own contributions, join us at http://FirstResponderKit.org.' - ); - - INSERT INTO #BlitzFirstResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - - ) - VALUES ( -1 , - 0 , - 'sp_BlitzFirst ' + CAST(CONVERT(DATETIMEOFFSET, @VersionDate, 102) AS VARCHAR(100)), - 'From Your Community Volunteers' , - 'http://FirstResponderKit.org/' , - 'We hope you found this tool useful.' - ); - - /* Outdated sp_BlitzFirst - sp_BlitzFirst is Over 6 Months Old - CheckID 27 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 27',10,1) WITH NOWAIT; - END - - IF DATEDIFF(MM, @VersionDate, SYSDATETIMEOFFSET()) > 6 - BEGIN - INSERT INTO #BlitzFirstResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 27 AS CheckID , - 0 AS Priority , - 'Outdated sp_BlitzFirst' AS FindingsGroup , - 'sp_BlitzFirst is Over 6 Months Old' AS Finding , - 'http://FirstResponderKit.org/' AS URL , - 'Some things get better with age, like fine wine and your T-SQL. However, sp_BlitzFirst is not one of those things - time to go download the current one.' AS Details; - END; - - IF @CheckServerInfo = 0 /* Github #1680 */ - BEGIN - DELETE #BlitzFirstResults - WHERE FindingsGroup = 'Server Info'; - END - - RAISERROR('Analysis finished, outputting results',10,1) WITH NOWAIT; - - - /* If they want to run sp_BlitzCache and export to table, go for it. */ - IF @OutputTableNameBlitzCache IS NOT NULL - AND @OutputDatabaseName IS NOT NULL - AND @OutputSchemaName IS NOT NULL - AND EXISTS ( SELECT * - FROM sys.databases - WHERE QUOTENAME([name]) = @OutputDatabaseName) - BEGIN - - - RAISERROR('Calling sp_BlitzCache',10,1) WITH NOWAIT; - - - /* If they have an newer version of sp_BlitzCache that supports @MinutesBack and @CheckDateOverride */ - IF EXISTS (SELECT * FROM sys.objects o - INNER JOIN sys.parameters pMB ON o.object_id = pMB.object_id AND pMB.name = '@MinutesBack' - INNER JOIN sys.parameters pCDO ON o.object_id = pCDO.object_id AND pCDO.name = '@CheckDateOverride' - WHERE o.name = 'sp_BlitzCache') - BEGIN - /* Get the most recent sp_BlitzCache execution before this one - don't use sp_BlitzFirst because user logs are added in there at any time */ - SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''' - + @OutputSchemaName + ''' AND QUOTENAME(TABLE_NAME) = ''' - + QUOTENAME(@OutputTableNameBlitzCache) + ''') SELECT TOP 1 @BlitzCacheMinutesBack = DATEDIFF(MI,CheckDate,SYSDATETIMEOFFSET()) FROM ' - + @OutputDatabaseName + '.' - + @OutputSchemaName + '.' - + QUOTENAME(@OutputTableNameBlitzCache) - + ' WHERE ServerName = ''' + CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128)) + ''' ORDER BY CheckDate DESC;'; - EXEC sp_executesql @StringToExecute, N'@BlitzCacheMinutesBack INT OUTPUT', @BlitzCacheMinutesBack OUTPUT; - - /* If there's no data, let's just analyze the last 15 minutes of the plan cache */ - IF @BlitzCacheMinutesBack IS NULL OR @BlitzCacheMinutesBack < 1 OR @BlitzCacheMinutesBack > 60 - SET @BlitzCacheMinutesBack = 15; - - EXEC sp_BlitzCache - @OutputDatabaseName = @UnquotedOutputDatabaseName, - @OutputSchemaName = @UnquotedOutputSchemaName, - @OutputTableName = @OutputTableNameBlitzCache, - @CheckDateOverride = @StartSampleTime, - @SortOrder = 'all', - @SkipAnalysis = @BlitzCacheSkipAnalysis, - @MinutesBack = @BlitzCacheMinutesBack, - @Debug = @Debug; - - /* Delete history older than @OutputTableRetentionDays */ - SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' - + @OutputSchemaName + ''') DELETE ' - + @OutputDatabaseName + '.' - + @OutputSchemaName + '.' - + QUOTENAME(@OutputTableNameBlitzCache) - + ' WHERE ServerName = @SrvName AND CheckDate < @CheckDate;'; - EXEC sp_executesql @StringToExecute, - N'@SrvName NVARCHAR(128), @CheckDate date', - @LocalServerName, @OutputTableCleanupDate; - - - END; - - ELSE - BEGIN - /* No sp_BlitzCache found, or it's outdated - CheckID 36 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 36',10,1) WITH NOWAIT; - END - - INSERT INTO #BlitzFirstResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 36 AS CheckID , - 0 AS Priority , - 'Outdated or Missing sp_BlitzCache' AS FindingsGroup , - 'Update Your sp_BlitzCache' AS Finding , - 'http://FirstResponderKit.org/' AS URL , - 'You passed in @OutputTableNameBlitzCache, but we need a newer version of sp_BlitzCache in master or the current database.' AS Details; - END; - - RAISERROR('sp_BlitzCache Finished',10,1) WITH NOWAIT; - - END; /* End running sp_BlitzCache */ - - /* @OutputTableName lets us export the results to a permanent table */ - IF @OutputDatabaseName IS NOT NULL - AND @OutputSchemaName IS NOT NULL - AND @OutputTableName IS NOT NULL - AND @OutputTableName NOT LIKE '#%' - AND EXISTS ( SELECT * - FROM sys.databases - WHERE QUOTENAME([name]) = @OutputDatabaseName) - BEGIN - SET @StringToExecute = 'USE ' - + @OutputDatabaseName - + '; IF EXISTS(SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' - + @OutputSchemaName - + ''') AND NOT EXISTS (SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''' - + @OutputSchemaName + ''' AND QUOTENAME(TABLE_NAME) = ''' - + @OutputTableName + ''') CREATE TABLE ' - + @OutputSchemaName + '.' - + @OutputTableName - + ' (ID INT IDENTITY(1,1) NOT NULL, - ServerName NVARCHAR(128), - CheckDate DATETIMEOFFSET, - CheckID INT NOT NULL, - Priority TINYINT NOT NULL, - FindingsGroup VARCHAR(50) NOT NULL, - Finding VARCHAR(200) NOT NULL, - URL VARCHAR(200) NOT NULL, - Details NVARCHAR(4000) NULL, - HowToStopIt [XML] NULL, - QueryPlan [XML] NULL, - QueryText NVARCHAR(MAX) NULL, - StartTime DATETIMEOFFSET NULL, - LoginName NVARCHAR(128) NULL, - NTUserName NVARCHAR(128) NULL, - OriginalLoginName NVARCHAR(128) NULL, - ProgramName NVARCHAR(128) NULL, - HostName NVARCHAR(128) NULL, - DatabaseID INT NULL, - DatabaseName NVARCHAR(128) NULL, - OpenTransactionCount INT NULL, - DetailsInt INT NULL, - QueryHash BINARY(8) NULL, - JoinKey AS ServerName + CAST(CheckDate AS NVARCHAR(50)), - PRIMARY KEY CLUSTERED (ID ASC));'; - - EXEC(@StringToExecute); - - /* If the table doesn't have the new QueryHash column, add it. See Github #2162. */ - SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableName; - SET @StringToExecute = N'IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns - WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''QueryHash'') - ALTER TABLE ' + @ObjectFullName + N' ADD QueryHash BINARY(8) NULL;'; - EXEC(@StringToExecute); - - /* If the table doesn't have the new JoinKey computed column, add it. See Github #2164. */ - SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableName; - SET @StringToExecute = N'IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns - WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''JoinKey'') - ALTER TABLE ' + @ObjectFullName + N' ADD JoinKey AS ServerName + CAST(CheckDate AS NVARCHAR(50));'; - EXEC(@StringToExecute); - - SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' - + @OutputSchemaName + ''') INSERT ' - + @OutputDatabaseName + '.' - + @OutputSchemaName + '.' - + @OutputTableName - + ' (ServerName, CheckDate, CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, QueryText, StartTime, LoginName, NTUserName, OriginalLoginName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, DetailsInt, QueryHash) SELECT ' - + ' @SrvName, @CheckDate, CheckID, Priority, FindingsGroup, Finding, URL, LEFT(Details,4000), HowToStopIt, QueryPlan, QueryText, StartTime, LoginName, NTUserName, OriginalLoginName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, DetailsInt, QueryHash FROM #BlitzFirstResults ORDER BY Priority , FindingsGroup , Finding , Details'; - - EXEC sp_executesql @StringToExecute, - N'@SrvName NVARCHAR(128), @CheckDate datetimeoffset', - @LocalServerName, @StartSampleTime; - - /* Delete history older than @OutputTableRetentionDays */ - SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' - + @OutputSchemaName + ''') DELETE ' - + @OutputDatabaseName + '.' - + @OutputSchemaName + '.' - + @OutputTableName - + ' WHERE ServerName = @SrvName AND CheckDate < @CheckDate ;'; - - EXEC sp_executesql @StringToExecute, - N'@SrvName NVARCHAR(128), @CheckDate date', - @LocalServerName, @OutputTableCleanupDate; - - END; - ELSE IF (SUBSTRING(@OutputTableName, 2, 2) = '##') - BEGIN - SET @StringToExecute = N' IF (OBJECT_ID(''tempdb..' - + @OutputTableName - + ''') IS NULL) CREATE TABLE ' - + @OutputTableName - + ' (ID INT IDENTITY(1,1) NOT NULL, - ServerName NVARCHAR(128), - CheckDate DATETIMEOFFSET, - CheckID INT NOT NULL, - Priority TINYINT NOT NULL, - FindingsGroup VARCHAR(50) NOT NULL, - Finding VARCHAR(200) NOT NULL, - URL VARCHAR(200) NOT NULL, - Details NVARCHAR(4000) NULL, - HowToStopIt [XML] NULL, - QueryPlan [XML] NULL, - QueryText NVARCHAR(MAX) NULL, - StartTime DATETIMEOFFSET NULL, - LoginName NVARCHAR(128) NULL, - NTUserName NVARCHAR(128) NULL, - OriginalLoginName NVARCHAR(128) NULL, - ProgramName NVARCHAR(128) NULL, - HostName NVARCHAR(128) NULL, - DatabaseID INT NULL, - DatabaseName NVARCHAR(128) NULL, - OpenTransactionCount INT NULL, - DetailsInt INT NULL, - QueryHash BINARY(8) NULL, - JoinKey AS ServerName + CAST(CheckDate AS NVARCHAR(50)), - PRIMARY KEY CLUSTERED (ID ASC));' - + ' INSERT ' - + @OutputTableName - + ' (ServerName, CheckDate, CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, QueryText, StartTime, LoginName, NTUserName, OriginalLoginName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, DetailsInt) SELECT ' - + ' @SrvName, @CheckDate, CheckID, Priority, FindingsGroup, Finding, URL, LEFT(Details,4000), HowToStopIt, QueryPlan, QueryText, StartTime, LoginName, NTUserName, OriginalLoginName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, DetailsInt FROM #BlitzFirstResults ORDER BY Priority , FindingsGroup , Finding , Details'; - - EXEC sp_executesql @StringToExecute, - N'@SrvName NVARCHAR(128), @CheckDate datetimeoffset', - @LocalServerName, @StartSampleTime; - END; - ELSE IF (SUBSTRING(@OutputTableName, 2, 1) = '#') - BEGIN - RAISERROR('Due to the nature of Dymamic SQL, only global (i.e. double pound (##)) temp tables are supported for @OutputTableName', 16, 0); - END; - - /* @OutputTableNameFileStats lets us export the results to a permanent table */ - IF @OutputDatabaseName IS NOT NULL - AND @OutputSchemaName IS NOT NULL - AND @OutputTableNameFileStats IS NOT NULL - AND @OutputTableNameFileStats NOT LIKE '#%' - AND EXISTS ( SELECT * - FROM sys.databases - WHERE QUOTENAME([name]) = @OutputDatabaseName) - BEGIN - /* Create the table */ - SET @StringToExecute = 'USE ' - + @OutputDatabaseName - + '; IF EXISTS(SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' - + @OutputSchemaName - + ''') AND NOT EXISTS (SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''' - + @OutputSchemaName + ''' AND QUOTENAME(TABLE_NAME) = ''' - + @OutputTableNameFileStats + ''') CREATE TABLE ' - + @OutputSchemaName + '.' - + @OutputTableNameFileStats - + ' (ID INT IDENTITY(1,1) NOT NULL, - ServerName NVARCHAR(128), - CheckDate DATETIMEOFFSET, - DatabaseID INT NOT NULL, - FileID INT NOT NULL, - DatabaseName NVARCHAR(256) , - FileLogicalName NVARCHAR(256) , - TypeDesc NVARCHAR(60) , - SizeOnDiskMB BIGINT , - io_stall_read_ms BIGINT , - num_of_reads BIGINT , - bytes_read BIGINT , - io_stall_write_ms BIGINT , - num_of_writes BIGINT , - bytes_written BIGINT, - PhysicalName NVARCHAR(520) , - PRIMARY KEY CLUSTERED (ID ASC));'; - - EXEC(@StringToExecute); - - SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableNameFileStats_View; - - /* If the view exists without the most recently added columns, drop it. See Github #2162. */ - IF OBJECT_ID(@ObjectFullName) IS NOT NULL - BEGIN - SET @StringToExecute = N'USE ' + @OutputDatabaseName + N'; IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns - WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''JoinKey'') - DROP VIEW ' + @OutputSchemaName + N'.' + @OutputTableNameFileStats_View + N';'; - - EXEC(@StringToExecute); - END - - /* Create the view */ - IF OBJECT_ID(@ObjectFullName) IS NULL - BEGIN - SET @StringToExecute = 'USE ' - + @OutputDatabaseName - + '; EXEC (''CREATE VIEW ' - + @OutputSchemaName + '.' - + @OutputTableNameFileStats_View + ' AS ' + @LineFeed - + 'WITH RowDates as' + @LineFeed - + '(' + @LineFeed - + ' SELECT ' + @LineFeed - + ' ROW_NUMBER() OVER (ORDER BY [ServerName], [CheckDate]) ID,' + @LineFeed - + ' [CheckDate]' + @LineFeed - + ' FROM ' + @OutputSchemaName + '.' + @OutputTableNameFileStats + '' + @LineFeed - + ' GROUP BY [ServerName], [CheckDate]' + @LineFeed - + '),' + @LineFeed - + 'CheckDates as' + @LineFeed - + '(' + @LineFeed - + ' SELECT ThisDate.CheckDate,' + @LineFeed - + ' LastDate.CheckDate as PreviousCheckDate' + @LineFeed - + ' FROM RowDates ThisDate' + @LineFeed - + ' JOIN RowDates LastDate' + @LineFeed - + ' ON ThisDate.ID = LastDate.ID + 1' + @LineFeed - + ')' + @LineFeed - + ' SELECT f.ServerName,' + @LineFeed - + ' f.CheckDate,' + @LineFeed - + ' f.DatabaseID,' + @LineFeed - + ' f.DatabaseName,' + @LineFeed - + ' f.FileID,' + @LineFeed - + ' f.FileLogicalName,' + @LineFeed - + ' f.TypeDesc,' + @LineFeed - + ' f.PhysicalName,' + @LineFeed - + ' f.SizeOnDiskMB,' + @LineFeed - + ' DATEDIFF(ss, fPrior.CheckDate, f.CheckDate) AS ElapsedSeconds,' + @LineFeed - + ' (f.SizeOnDiskMB - fPrior.SizeOnDiskMB) AS SizeOnDiskMBgrowth,' + @LineFeed - + ' (f.io_stall_read_ms - fPrior.io_stall_read_ms) AS io_stall_read_ms,' + @LineFeed - + ' io_stall_read_ms_average = CASE' + @LineFeed - + ' WHEN(f.num_of_reads - fPrior.num_of_reads) = 0' + @LineFeed - + ' THEN 0' + @LineFeed - + ' ELSE(f.io_stall_read_ms - fPrior.io_stall_read_ms) / (f.num_of_reads - fPrior.num_of_reads)' + @LineFeed - + ' END,' + @LineFeed - + ' (f.num_of_reads - fPrior.num_of_reads) AS num_of_reads,' + @LineFeed - + ' (f.bytes_read - fPrior.bytes_read) / 1024.0 / 1024.0 AS megabytes_read,' + @LineFeed - + ' (f.io_stall_write_ms - fPrior.io_stall_write_ms) AS io_stall_write_ms,' + @LineFeed - + ' io_stall_write_ms_average = CASE' + @LineFeed - + ' WHEN(f.num_of_writes - fPrior.num_of_writes) = 0' + @LineFeed - + ' THEN 0' + @LineFeed - + ' ELSE(f.io_stall_write_ms - fPrior.io_stall_write_ms) / (f.num_of_writes - fPrior.num_of_writes)' + @LineFeed - + ' END,' + @LineFeed - + ' (f.num_of_writes - fPrior.num_of_writes) AS num_of_writes,' + @LineFeed - + ' (f.bytes_written - fPrior.bytes_written) / 1024.0 / 1024.0 AS megabytes_written, ' + @LineFeed - + ' f.ServerName + CAST(f.CheckDate AS NVARCHAR(50)) AS JoinKey' + @LineFeed - + ' FROM ' + @OutputSchemaName + '.' + @OutputTableNameFileStats + ' f' + @LineFeed - + ' INNER HASH JOIN CheckDates DATES ON f.CheckDate = DATES.CheckDate' + @LineFeed - + ' INNER JOIN ' + @OutputSchemaName + '.' + @OutputTableNameFileStats + ' fPrior ON f.ServerName = fPrior.ServerName' + @LineFeed - + ' AND f.DatabaseID = fPrior.DatabaseID' + @LineFeed - + ' AND f.FileID = fPrior.FileID' + @LineFeed - + ' AND fPrior.CheckDate = DATES.PreviousCheckDate' + @LineFeed - + '' + @LineFeed - + ' WHERE f.num_of_reads >= fPrior.num_of_reads' + @LineFeed - + ' AND f.num_of_writes >= fPrior.num_of_writes' + @LineFeed - + ' AND DATEDIFF(MI, fPrior.CheckDate, f.CheckDate) BETWEEN 1 AND 60;'')' - - EXEC(@StringToExecute); - END; - - - SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' - + @OutputSchemaName + ''') INSERT ' - + @OutputDatabaseName + '.' - + @OutputSchemaName + '.' - + @OutputTableNameFileStats - + ' (ServerName, CheckDate, DatabaseID, FileID, DatabaseName, FileLogicalName, TypeDesc, SizeOnDiskMB, io_stall_read_ms, num_of_reads, bytes_read, io_stall_write_ms, num_of_writes, bytes_written, PhysicalName) SELECT ' - + ' @SrvName, @CheckDate, DatabaseID, FileID, DatabaseName, FileLogicalName, TypeDesc, SizeOnDiskMB, io_stall_read_ms, num_of_reads, bytes_read, io_stall_write_ms, num_of_writes, bytes_written, PhysicalName FROM #FileStats WHERE Pass = 2'; - - EXEC sp_executesql @StringToExecute, - N'@SrvName NVARCHAR(128), @CheckDate datetimeoffset', - @LocalServerName, @StartSampleTime; - - /* Delete history older than @OutputTableRetentionDays */ - SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' - + @OutputSchemaName + ''') DELETE ' - + @OutputDatabaseName + '.' - + @OutputSchemaName + '.' - + @OutputTableNameFileStats - + ' WHERE ServerName = @SrvName AND CheckDate < @CheckDate ;'; - - EXEC sp_executesql @StringToExecute, - N'@SrvName NVARCHAR(128), @CheckDate date', - @LocalServerName, @OutputTableCleanupDate; - - END; - ELSE IF (SUBSTRING(@OutputTableNameFileStats, 2, 2) = '##') - BEGIN - SET @StringToExecute = N' IF (OBJECT_ID(''tempdb..' - + @OutputTableNameFileStats - + ''') IS NULL) CREATE TABLE ' - + @OutputTableNameFileStats - + ' (ID INT IDENTITY(1,1) NOT NULL, - ServerName NVARCHAR(128), - CheckDate DATETIMEOFFSET, - DatabaseID INT NOT NULL, - FileID INT NOT NULL, - DatabaseName NVARCHAR(256) , - FileLogicalName NVARCHAR(256) , - TypeDesc NVARCHAR(60) , - SizeOnDiskMB BIGINT , - io_stall_read_ms BIGINT , - num_of_reads BIGINT , - bytes_read BIGINT , - io_stall_write_ms BIGINT , - num_of_writes BIGINT , - bytes_written BIGINT, - PhysicalName NVARCHAR(520) , - DetailsInt INT NULL, - PRIMARY KEY CLUSTERED (ID ASC));' - + ' INSERT ' - + @OutputTableNameFileStats - + ' (ServerName, CheckDate, DatabaseID, FileID, DatabaseName, FileLogicalName, TypeDesc, SizeOnDiskMB, io_stall_read_ms, num_of_reads, bytes_read, io_stall_write_ms, num_of_writes, bytes_written, PhysicalName) SELECT ' - + ' @SrvName, @CheckDate, DatabaseID, FileID, DatabaseName, FileLogicalName, TypeDesc, SizeOnDiskMB, io_stall_read_ms, num_of_reads, bytes_read, io_stall_write_ms, num_of_writes, bytes_written, PhysicalName FROM #FileStats WHERE Pass = 2'; - - EXEC sp_executesql @StringToExecute, - N'@SrvName NVARCHAR(128), @CheckDate datetimeoffset', - @LocalServerName, @StartSampleTime; - END; - ELSE IF (SUBSTRING(@OutputTableNameFileStats, 2, 1) = '#') - BEGIN - RAISERROR('Due to the nature of Dymamic SQL, only global (i.e. double pound (##)) temp tables are supported for @OutputTableName', 16, 0); - END; - - - /* @OutputTableNamePerfmonStats lets us export the results to a permanent table */ - IF @OutputDatabaseName IS NOT NULL - AND @OutputSchemaName IS NOT NULL - AND @OutputTableNamePerfmonStats IS NOT NULL - AND @OutputTableNamePerfmonStats NOT LIKE '#%' - AND EXISTS ( SELECT * - FROM sys.databases - WHERE QUOTENAME([name]) = @OutputDatabaseName) - BEGIN - /* Create the table */ - SET @StringToExecute = 'USE ' - + @OutputDatabaseName - + '; IF EXISTS(SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' - + @OutputSchemaName - + ''') AND NOT EXISTS (SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''' - + @OutputSchemaName + ''' AND QUOTENAME(TABLE_NAME) = ''' - + @OutputTableNamePerfmonStats + ''') CREATE TABLE ' - + @OutputSchemaName + '.' - + @OutputTableNamePerfmonStats - + ' (ID INT IDENTITY(1,1) NOT NULL, - ServerName NVARCHAR(128), - CheckDate DATETIMEOFFSET, - [object_name] NVARCHAR(128) NOT NULL, - [counter_name] NVARCHAR(128) NOT NULL, - [instance_name] NVARCHAR(128) NULL, - [cntr_value] BIGINT NULL, - [cntr_type] INT NOT NULL, - [value_delta] BIGINT NULL, - [value_per_second] DECIMAL(18,2) NULL, - PRIMARY KEY CLUSTERED (ID ASC));'; - - EXEC(@StringToExecute); - - SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableNamePerfmonStats_View; - - /* If the view exists without the most recently added columns, drop it. See Github #2162. */ - IF OBJECT_ID(@ObjectFullName) IS NOT NULL - BEGIN - SET @StringToExecute = N'USE ' + @OutputDatabaseName + N'; IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns - WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''JoinKey'') - DROP VIEW ' + @OutputSchemaName + N'.' + @OutputTableNamePerfmonStats_View + N';'; - - EXEC(@StringToExecute); - END - - /* Create the view */ - IF OBJECT_ID(@ObjectFullName) IS NULL - BEGIN - SET @StringToExecute = 'USE ' - + @OutputDatabaseName - + '; EXEC (''CREATE VIEW ' - + @OutputSchemaName + '.' - + @OutputTableNamePerfmonStats_View + ' AS ' + @LineFeed - + 'WITH RowDates as' + @LineFeed - + '(' + @LineFeed - + ' SELECT ' + @LineFeed - + ' ROW_NUMBER() OVER (ORDER BY [ServerName], [CheckDate]) ID,' + @LineFeed - + ' [CheckDate]' + @LineFeed - + ' FROM ' + @OutputSchemaName + '.' +@OutputTableNamePerfmonStats + '' + @LineFeed - + ' GROUP BY [ServerName], [CheckDate]' + @LineFeed - + '),' + @LineFeed - + 'CheckDates as' + @LineFeed - + '(' + @LineFeed - + ' SELECT ThisDate.CheckDate,' + @LineFeed - + ' LastDate.CheckDate as PreviousCheckDate' + @LineFeed - + ' FROM RowDates ThisDate' + @LineFeed - + ' JOIN RowDates LastDate' + @LineFeed - + ' ON ThisDate.ID = LastDate.ID + 1' + @LineFeed - + ')' + @LineFeed - + 'SELECT' + @LineFeed - + ' pMon.[ServerName]' + @LineFeed - + ' ,pMon.[CheckDate]' + @LineFeed - + ' ,pMon.[object_name]' + @LineFeed - + ' ,pMon.[counter_name]' + @LineFeed - + ' ,pMon.[instance_name]' + @LineFeed - + ' ,DATEDIFF(SECOND,pMonPrior.[CheckDate],pMon.[CheckDate]) AS ElapsedSeconds' + @LineFeed - + ' ,pMon.[cntr_value]' + @LineFeed - + ' ,pMon.[cntr_type]' + @LineFeed - + ' ,(pMon.[cntr_value] - pMonPrior.[cntr_value]) AS cntr_delta' + @LineFeed - + ' ,(pMon.cntr_value - pMonPrior.cntr_value) * 1.0 / DATEDIFF(ss, pMonPrior.CheckDate, pMon.CheckDate) AS cntr_delta_per_second' + @LineFeed - + ' ,pMon.ServerName + CAST(pMon.CheckDate AS NVARCHAR(50)) AS JoinKey' + @LineFeed - + ' FROM ' + @OutputSchemaName + '.' +@OutputTableNamePerfmonStats + ' pMon' + @LineFeed - + ' INNER HASH JOIN CheckDates Dates' + @LineFeed - + ' ON Dates.CheckDate = pMon.CheckDate' + @LineFeed - + ' JOIN ' + @OutputSchemaName + '.' +@OutputTableNamePerfmonStats + ' pMonPrior' + @LineFeed - + ' ON Dates.PreviousCheckDate = pMonPrior.CheckDate' + @LineFeed - + ' AND pMon.[ServerName] = pMonPrior.[ServerName] ' + @LineFeed - + ' AND pMon.[object_name] = pMonPrior.[object_name] ' + @LineFeed - + ' AND pMon.[counter_name] = pMonPrior.[counter_name] ' + @LineFeed - + ' AND pMon.[instance_name] = pMonPrior.[instance_name]' + @LineFeed - + ' WHERE DATEDIFF(MI, pMonPrior.CheckDate, pMon.CheckDate) BETWEEN 1 AND 60;'')' - - EXEC(@StringToExecute); - END - - SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableNamePerfmonStatsActuals_View; - - /* If the view exists without the most recently added columns, drop it. See Github #2162. */ - IF OBJECT_ID(@ObjectFullName) IS NOT NULL - BEGIN - SET @StringToExecute = N'USE ' + @OutputDatabaseName + N'; IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns - WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''JoinKey'') - DROP VIEW ' + @OutputSchemaName + N'.' + @OutputTableNamePerfmonStatsActuals_View + N';'; - - EXEC(@StringToExecute); - END - - /* Create the second view */ - IF OBJECT_ID(@ObjectFullName) IS NULL - BEGIN - SET @StringToExecute = 'USE ' - + @OutputDatabaseName - + '; EXEC (''CREATE VIEW ' - + @OutputSchemaName + '.' - + @OutputTableNamePerfmonStatsActuals_View + ' AS ' + @LineFeed - + 'WITH PERF_AVERAGE_BULK AS' + @LineFeed - + '(' + @LineFeed - + ' SELECT ServerName,' + @LineFeed - + ' object_name,' + @LineFeed - + ' instance_name,' + @LineFeed - + ' counter_name,' + @LineFeed - + ' CASE WHEN CHARINDEX(''''('''', counter_name) = 0 THEN counter_name ELSE LEFT (counter_name, CHARINDEX(''''('''',counter_name)-1) END AS counter_join,' + @LineFeed - + ' CheckDate,' + @LineFeed - + ' cntr_delta' + @LineFeed - + ' FROM ' + @OutputSchemaName + '.' + @OutputTableNamePerfmonStats_View + @LineFeed - + ' WHERE cntr_type IN(1073874176)' + @LineFeed - + ' AND cntr_delta <> 0' + @LineFeed - + '),' + @LineFeed - + 'PERF_LARGE_RAW_BASE AS' + @LineFeed - + '(' + @LineFeed - + ' SELECT ServerName,' + @LineFeed - + ' object_name,' + @LineFeed - + ' instance_name,' + @LineFeed - + ' LEFT(counter_name, CHARINDEX(''''BASE'''', UPPER(counter_name))-1) AS counter_join,' + @LineFeed - + ' CheckDate,' + @LineFeed - + ' cntr_delta' + @LineFeed - + ' FROM ' + @OutputSchemaName + '.' + @OutputTableNamePerfmonStats_View + '' + @LineFeed - + ' WHERE cntr_type IN(1073939712)' + @LineFeed - + ' AND cntr_delta <> 0' + @LineFeed - + '),' + @LineFeed - + 'PERF_AVERAGE_FRACTION AS' + @LineFeed - + '(' + @LineFeed - + ' SELECT ServerName,' + @LineFeed - + ' object_name,' + @LineFeed - + ' instance_name,' + @LineFeed - + ' counter_name,' + @LineFeed - + ' counter_name AS counter_join,' + @LineFeed - + ' CheckDate,' + @LineFeed - + ' cntr_delta' + @LineFeed - + ' FROM ' + @OutputSchemaName + '.' + @OutputTableNamePerfmonStats_View + '' + @LineFeed - + ' WHERE cntr_type IN(537003264)' + @LineFeed - + ' AND cntr_delta <> 0' + @LineFeed - + '),' + @LineFeed - + 'PERF_COUNTER_BULK_COUNT AS' + @LineFeed - + '(' + @LineFeed - + ' SELECT ServerName,' + @LineFeed - + ' object_name,' + @LineFeed - + ' instance_name,' + @LineFeed - + ' counter_name,' + @LineFeed - + ' CheckDate,' + @LineFeed - + ' cntr_delta / ElapsedSeconds AS cntr_value' + @LineFeed - + ' FROM ' + @OutputSchemaName + '.' + @OutputTableNamePerfmonStats_View + '' + @LineFeed - + ' WHERE cntr_type IN(272696576, 272696320)' + @LineFeed - + ' AND cntr_delta <> 0' + @LineFeed - + '),' + @LineFeed - + 'PERF_COUNTER_RAWCOUNT AS' + @LineFeed - + '(' + @LineFeed - + ' SELECT ServerName,' + @LineFeed - + ' object_name,' + @LineFeed - + ' instance_name,' + @LineFeed - + ' counter_name,' + @LineFeed - + ' CheckDate,' + @LineFeed - + ' cntr_value' + @LineFeed - + ' FROM ' + @OutputSchemaName + '.' + @OutputTableNamePerfmonStats_View + '' + @LineFeed - + ' WHERE cntr_type IN(65792, 65536)' + @LineFeed - + ')' + @LineFeed - + '' + @LineFeed - + 'SELECT NUM.ServerName,' + @LineFeed - + ' NUM.object_name,' + @LineFeed - + ' NUM.counter_name,' + @LineFeed - + ' NUM.instance_name,' + @LineFeed - + ' NUM.CheckDate,' + @LineFeed - + ' NUM.cntr_delta / DEN.cntr_delta AS cntr_value,' + @LineFeed - + ' NUM.ServerName + CAST(NUM.CheckDate AS NVARCHAR(50)) AS JoinKey' + @LineFeed - + ' ' + @LineFeed - + 'FROM PERF_AVERAGE_BULK AS NUM' + @LineFeed - + ' JOIN PERF_LARGE_RAW_BASE AS DEN ON NUM.counter_join = DEN.counter_join' + @LineFeed - + ' AND NUM.CheckDate = DEN.CheckDate' + @LineFeed - + ' AND NUM.ServerName = DEN.ServerName' + @LineFeed - + ' AND NUM.object_name = DEN.object_name' + @LineFeed - + ' AND NUM.instance_name = DEN.instance_name' + @LineFeed - + ' AND DEN.cntr_delta <> 0' + @LineFeed - + '' + @LineFeed - + 'UNION ALL' + @LineFeed - + '' + @LineFeed - + 'SELECT NUM.ServerName,' + @LineFeed - + ' NUM.object_name,' + @LineFeed - + ' NUM.counter_name,' + @LineFeed - + ' NUM.instance_name,' + @LineFeed - + ' NUM.CheckDate,' + @LineFeed - + ' CAST((CAST(NUM.cntr_delta as DECIMAL(19)) / DEN.cntr_delta) as decimal(23,3)) AS cntr_value,' + @LineFeed - + ' NUM.ServerName + CAST(NUM.CheckDate AS NVARCHAR(50)) AS JoinKey' + @LineFeed - + 'FROM PERF_AVERAGE_FRACTION AS NUM' + @LineFeed - + ' JOIN PERF_LARGE_RAW_BASE AS DEN ON NUM.counter_join = DEN.counter_join' + @LineFeed - + ' AND NUM.CheckDate = DEN.CheckDate' + @LineFeed - + ' AND NUM.ServerName = DEN.ServerName' + @LineFeed - + ' AND NUM.object_name = DEN.object_name' + @LineFeed - + ' AND NUM.instance_name = DEN.instance_name' + @LineFeed - + ' AND DEN.cntr_delta <> 0' + @LineFeed - + 'UNION ALL' + @LineFeed - + '' + @LineFeed - + 'SELECT ServerName,' + @LineFeed - + ' object_name,' + @LineFeed - + ' counter_name,' + @LineFeed - + ' instance_name,' + @LineFeed - + ' CheckDate,' + @LineFeed - + ' cntr_value,' + @LineFeed - + ' ServerName + CAST(CheckDate AS NVARCHAR(50)) AS JoinKey' + @LineFeed - + 'FROM PERF_COUNTER_BULK_COUNT' + @LineFeed - + '' + @LineFeed - + 'UNION ALL' + @LineFeed - + '' + @LineFeed - + 'SELECT ServerName,' + @LineFeed - + ' object_name,' + @LineFeed - + ' counter_name,' + @LineFeed - + ' instance_name,' + @LineFeed - + ' CheckDate,' + @LineFeed - + ' cntr_value,' + @LineFeed - + ' ServerName + CAST(CheckDate AS NVARCHAR(50)) AS JoinKey' + @LineFeed - + 'FROM PERF_COUNTER_RAWCOUNT;'')'; - - EXEC(@StringToExecute); - END; - - - SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' - + @OutputSchemaName + ''') INSERT ' - + @OutputDatabaseName + '.' - + @OutputSchemaName + '.' - + @OutputTableNamePerfmonStats - + ' (ServerName, CheckDate, object_name, counter_name, instance_name, cntr_value, cntr_type, value_delta, value_per_second) SELECT ' - + ' @SrvName, @CheckDate, object_name, counter_name, instance_name, cntr_value, cntr_type, value_delta, value_per_second FROM #PerfmonStats WHERE Pass = 2'; - - EXEC sp_executesql @StringToExecute, - N'@SrvName NVARCHAR(128), @CheckDate datetimeoffset', - @LocalServerName, @StartSampleTime; - - /* Delete history older than @OutputTableRetentionDays */ - SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' - + @OutputSchemaName + ''') DELETE ' - + @OutputDatabaseName + '.' - + @OutputSchemaName + '.' - + @OutputTableNamePerfmonStats - + ' WHERE ServerName = @SrvName AND CheckDate < @CheckDate ;'; - - EXEC sp_executesql @StringToExecute, - N'@SrvName NVARCHAR(128), @CheckDate date', - @LocalServerName, @OutputTableCleanupDate; - - - - END; - ELSE IF (SUBSTRING(@OutputTableNamePerfmonStats, 2, 2) = '##') - BEGIN - SET @StringToExecute = N' IF (OBJECT_ID(''tempdb..' - + @OutputTableNamePerfmonStats - + ''') IS NULL) CREATE TABLE ' - + @OutputTableNamePerfmonStats - + ' (ID INT IDENTITY(1,1) NOT NULL, - ServerName NVARCHAR(128), - CheckDate DATETIMEOFFSET, - [object_name] NVARCHAR(128) NOT NULL, - [counter_name] NVARCHAR(128) NOT NULL, - [instance_name] NVARCHAR(128) NULL, - [cntr_value] BIGINT NULL, - [cntr_type] INT NOT NULL, - [value_delta] BIGINT NULL, - [value_per_second] DECIMAL(18,2) NULL, - PRIMARY KEY CLUSTERED (ID ASC));' - + ' INSERT ' - + @OutputTableNamePerfmonStats - + ' (ServerName, CheckDate, object_name, counter_name, instance_name, cntr_value, cntr_type, value_delta, value_per_second) SELECT ' - + CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128)) - + ' @SrvName, @CheckDate, object_name, counter_name, instance_name, cntr_value, cntr_type, value_delta, value_per_second FROM #PerfmonStats WHERE Pass = 2'; - - EXEC sp_executesql @StringToExecute, - N'@SrvName NVARCHAR(128), @CheckDate datetimeoffset', - @LocalServerName, @StartSampleTime; - END; - ELSE IF (SUBSTRING(@OutputTableNamePerfmonStats, 2, 1) = '#') - BEGIN - RAISERROR('Due to the nature of Dymamic SQL, only global (i.e. double pound (##)) temp tables are supported for @OutputTableName', 16, 0); - END; - - - /* @OutputTableNameWaitStats lets us export the results to a permanent table */ - IF @OutputDatabaseName IS NOT NULL - AND @OutputSchemaName IS NOT NULL - AND @OutputTableNameWaitStats IS NOT NULL - AND @OutputTableNameWaitStats NOT LIKE '#%' - AND EXISTS ( SELECT * - FROM sys.databases - WHERE QUOTENAME([name]) = @OutputDatabaseName) - BEGIN - /* Create the table */ - SET @StringToExecute = 'USE ' - + @OutputDatabaseName - + '; IF EXISTS(SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' - + @OutputSchemaName - + ''') AND NOT EXISTS (SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''' - + @OutputSchemaName + ''' AND QUOTENAME(TABLE_NAME) = ''' - + @OutputTableNameWaitStats + ''') ' + @LineFeed - + 'BEGIN' + @LineFeed - + 'CREATE TABLE ' - + @OutputSchemaName + '.' - + @OutputTableNameWaitStats - + ' (ID INT IDENTITY(1,1) NOT NULL, - ServerName NVARCHAR(128), - CheckDate DATETIMEOFFSET, - wait_type NVARCHAR(60), - wait_time_ms BIGINT, - signal_wait_time_ms BIGINT, - waiting_tasks_count BIGINT , - PRIMARY KEY CLUSTERED (ID));' + @LineFeed - + 'CREATE NONCLUSTERED INDEX IX_ServerName_wait_type_CheckDate_Includes ON ' + @OutputSchemaName + '.' + @OutputTableNameWaitStats + @LineFeed - + '(ServerName, wait_type, CheckDate) INCLUDE (wait_time_ms, signal_wait_time_ms, waiting_tasks_count);' + @LineFeed - + 'END'; - - EXEC(@StringToExecute); - - /* Create the wait stats category table */ - SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableNameWaitStats_Categories; - IF OBJECT_ID(@ObjectFullName) IS NULL - BEGIN - SET @StringToExecute = 'USE ' - + @OutputDatabaseName - + '; EXEC (''CREATE TABLE ' - + @OutputSchemaName + '.' - + @OutputTableNameWaitStats_Categories + ' (WaitType NVARCHAR(60) PRIMARY KEY CLUSTERED, WaitCategory NVARCHAR(128) NOT NULL, Ignorable BIT DEFAULT 0);'')'; - - EXEC(@StringToExecute); - END; - - /* Make sure the wait stats category table has the current number of rows */ - SET @StringToExecute = 'USE ' - + @OutputDatabaseName - + '; EXEC (''IF (SELECT COALESCE(SUM(1),0) FROM ' + @OutputSchemaName + '.' + @OutputTableNameWaitStats_Categories + ') <> (SELECT COALESCE(SUM(1),0) FROM ##WaitCategories)' + @LineFeed - + 'BEGIN ' + @LineFeed - + 'TRUNCATE TABLE ' + @OutputSchemaName + '.' + @OutputTableNameWaitStats_Categories + @LineFeed - + 'INSERT INTO ' + @OutputSchemaName + '.' + @OutputTableNameWaitStats_Categories + ' (WaitType, WaitCategory, Ignorable) SELECT WaitType, WaitCategory, Ignorable FROM ##WaitCategories;' + @LineFeed - + 'END'')'; - - EXEC(@StringToExecute); - - - SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableNameWaitStats_View; - - /* If the view exists without the most recently added columns, drop it. See Github #2162. */ - IF OBJECT_ID(@ObjectFullName) IS NOT NULL - BEGIN - SET @StringToExecute = N'USE ' + @OutputDatabaseName + N'; IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns - WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''JoinKey'') - DROP VIEW ' + @OutputSchemaName + N'.' + @OutputTableNameWaitStats_View + N';'; - - EXEC(@StringToExecute); - END - - - /* Create the wait stats view */ - IF OBJECT_ID(@ObjectFullName) IS NULL - BEGIN - SET @StringToExecute = 'USE ' - + @OutputDatabaseName - + '; EXEC (''CREATE VIEW ' - + @OutputSchemaName + '.' - + @OutputTableNameWaitStats_View + ' AS ' + @LineFeed - + 'WITH RowDates as' + @LineFeed - + '(' + @LineFeed - + ' SELECT ' + @LineFeed - + ' ROW_NUMBER() OVER (ORDER BY [ServerName], [CheckDate]) ID,' + @LineFeed - + ' [CheckDate]' + @LineFeed - + ' FROM ' + @OutputSchemaName + '.' + @OutputTableNameWaitStats + @LineFeed - + ' GROUP BY [ServerName], [CheckDate]' + @LineFeed - + '),' + @LineFeed - + 'CheckDates as' + @LineFeed - + '(' + @LineFeed - + ' SELECT ThisDate.CheckDate,' + @LineFeed - + ' LastDate.CheckDate as PreviousCheckDate' + @LineFeed - + ' FROM RowDates ThisDate' + @LineFeed - + ' JOIN RowDates LastDate' + @LineFeed - + ' ON ThisDate.ID = LastDate.ID + 1' + @LineFeed - + ')' + @LineFeed - + 'SELECT w.ServerName, w.CheckDate, w.wait_type, COALESCE(wc.WaitCategory, ''''Other'''') AS WaitCategory, COALESCE(wc.Ignorable,0) AS Ignorable' + @LineFeed - + ', DATEDIFF(ss, wPrior.CheckDate, w.CheckDate) AS ElapsedSeconds' + @LineFeed - + ', (w.wait_time_ms - wPrior.wait_time_ms) AS wait_time_ms_delta' + @LineFeed - + ', (w.wait_time_ms - wPrior.wait_time_ms) / 60000.0 AS wait_time_minutes_delta' + @LineFeed - + ', (w.wait_time_ms - wPrior.wait_time_ms) / 1000.0 / DATEDIFF(ss, wPrior.CheckDate, w.CheckDate) AS wait_time_minutes_per_minute' + @LineFeed - + ', (w.signal_wait_time_ms - wPrior.signal_wait_time_ms) AS signal_wait_time_ms_delta' + @LineFeed - + ', (w.waiting_tasks_count - wPrior.waiting_tasks_count) AS waiting_tasks_count_delta' + @LineFeed - + ', w.ServerName + CAST(w.CheckDate AS NVARCHAR(50)) AS JoinKey' + @LineFeed - + 'FROM ' + @OutputSchemaName + '.' + @OutputTableNameWaitStats + ' w' + @LineFeed - + 'INNER HASH JOIN CheckDates Dates' + @LineFeed - + 'ON Dates.CheckDate = w.CheckDate' + @LineFeed - + 'INNER JOIN ' + @OutputSchemaName + '.' + @OutputTableNameWaitStats + ' wPrior ON w.ServerName = wPrior.ServerName AND w.wait_type = wPrior.wait_type AND Dates.PreviousCheckDate = wPrior.CheckDate' + @LineFeed - + 'LEFT OUTER JOIN ' + @OutputSchemaName + '.' + @OutputTableNameWaitStats_Categories + ' wc ON w.wait_type = wc.WaitType' + @LineFeed - + 'WHERE DATEDIFF(MI, wPrior.CheckDate, w.CheckDate) BETWEEN 1 AND 60' + @LineFeed - + 'AND [w].[wait_time_ms] >= [wPrior].[wait_time_ms];'')' - - EXEC(@StringToExecute); - END; - - - SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' - + @OutputSchemaName + ''') INSERT ' - + @OutputDatabaseName + '.' - + @OutputSchemaName + '.' - + @OutputTableNameWaitStats - + ' (ServerName, CheckDate, wait_type, wait_time_ms, signal_wait_time_ms, waiting_tasks_count) SELECT ' - + ' @SrvName, @CheckDate, wait_type, wait_time_ms, signal_wait_time_ms, waiting_tasks_count FROM #WaitStats WHERE Pass = 2 AND wait_time_ms > 0 AND waiting_tasks_count > 0'; - - EXEC sp_executesql @StringToExecute, - N'@SrvName NVARCHAR(128), @CheckDate datetimeoffset', - @LocalServerName, @StartSampleTime; - - /* Delete history older than @OutputTableRetentionDays */ - SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' - + @OutputSchemaName + ''') DELETE ' - + @OutputDatabaseName + '.' - + @OutputSchemaName + '.' - + @OutputTableNameWaitStats - + ' WHERE ServerName = @SrvName AND CheckDate < @CheckDate ;'; - - EXEC sp_executesql @StringToExecute, - N'@SrvName NVARCHAR(128), @CheckDate date', - @LocalServerName, @OutputTableCleanupDate; - - END; - ELSE IF (SUBSTRING(@OutputTableNameWaitStats, 2, 2) = '##') - BEGIN - SET @StringToExecute = N' IF (OBJECT_ID(''tempdb..' - + @OutputTableNameWaitStats - + ''') IS NULL) CREATE TABLE ' - + @OutputTableNameWaitStats - + ' (ID INT IDENTITY(1,1) NOT NULL, - ServerName NVARCHAR(128), - CheckDate DATETIMEOFFSET, - wait_type NVARCHAR(60), - wait_time_ms BIGINT, - signal_wait_time_ms BIGINT, - waiting_tasks_count BIGINT , - PRIMARY KEY CLUSTERED (ID ASC));' - + ' INSERT ' - + @OutputTableNameWaitStats - + ' (ServerName, CheckDate, wait_type, wait_time_ms, signal_wait_time_ms, waiting_tasks_count) SELECT ' - + ' @SrvName, @CheckDate, wait_type, wait_time_ms, signal_wait_time_ms, waiting_tasks_count FROM #WaitStats WHERE Pass = 2 AND wait_time_ms > 0 AND waiting_tasks_count > 0'; - - EXEC sp_executesql @StringToExecute, - N'@SrvName NVARCHAR(128), @CheckDate datetimeoffset', - @LocalServerName, @StartSampleTime; - END; - ELSE IF (SUBSTRING(@OutputTableNameWaitStats, 2, 1) = '#') - BEGIN - RAISERROR('Due to the nature of Dymamic SQL, only global (i.e. double pound (##)) temp tables are supported for @OutputTableName', 16, 0); - END; - - - - - DECLARE @separator AS VARCHAR(1); - IF @OutputType = 'RSV' - SET @separator = CHAR(31); - ELSE - SET @separator = ','; - - IF @OutputType = 'COUNT' AND @SinceStartup = 0 - BEGIN - SELECT COUNT(*) AS Warnings - FROM #BlitzFirstResults; - END; - ELSE - IF @OutputType = 'Opserver1' AND @SinceStartup = 0 - BEGIN - - SELECT r.[Priority] , - r.[FindingsGroup] , - r.[Finding] , - r.[URL] , - r.[Details], - r.[HowToStopIt] , - r.[CheckID] , - r.[StartTime], - r.[LoginName], - r.[NTUserName], - r.[OriginalLoginName], - r.[ProgramName], - r.[HostName], - r.[DatabaseID], - r.[DatabaseName], - r.[OpenTransactionCount], - r.[QueryPlan], - r.[QueryText], - qsNow.plan_handle AS PlanHandle, - qsNow.sql_handle AS SqlHandle, - qsNow.statement_start_offset AS StatementStartOffset, - qsNow.statement_end_offset AS StatementEndOffset, - [Executions] = qsNow.execution_count - (COALESCE(qsFirst.execution_count, 0)), - [ExecutionsPercent] = CAST(100.0 * (qsNow.execution_count - (COALESCE(qsFirst.execution_count, 0))) / (qsTotal.execution_count - qsTotalFirst.execution_count) AS DECIMAL(6,2)), - [Duration] = qsNow.total_elapsed_time - (COALESCE(qsFirst.total_elapsed_time, 0)), - [DurationPercent] = CAST(100.0 * (qsNow.total_elapsed_time - (COALESCE(qsFirst.total_elapsed_time, 0))) / (qsTotal.total_elapsed_time - qsTotalFirst.total_elapsed_time) AS DECIMAL(6,2)), - [CPU] = qsNow.total_worker_time - (COALESCE(qsFirst.total_worker_time, 0)), - [CPUPercent] = CAST(100.0 * (qsNow.total_worker_time - (COALESCE(qsFirst.total_worker_time, 0))) / (qsTotal.total_worker_time - qsTotalFirst.total_worker_time) AS DECIMAL(6,2)), - [Reads] = qsNow.total_logical_reads - (COALESCE(qsFirst.total_logical_reads, 0)), - [ReadsPercent] = CAST(100.0 * (qsNow.total_logical_reads - (COALESCE(qsFirst.total_logical_reads, 0))) / (qsTotal.total_logical_reads - qsTotalFirst.total_logical_reads) AS DECIMAL(6,2)), - [PlanCreationTime] = CONVERT(NVARCHAR(100), qsNow.creation_time ,121), - [TotalExecutions] = qsNow.execution_count, - [TotalExecutionsPercent] = CAST(100.0 * qsNow.execution_count / qsTotal.execution_count AS DECIMAL(6,2)), - [TotalDuration] = qsNow.total_elapsed_time, - [TotalDurationPercent] = CAST(100.0 * qsNow.total_elapsed_time / qsTotal.total_elapsed_time AS DECIMAL(6,2)), - [TotalCPU] = qsNow.total_worker_time, - [TotalCPUPercent] = CAST(100.0 * qsNow.total_worker_time / qsTotal.total_worker_time AS DECIMAL(6,2)), - [TotalReads] = qsNow.total_logical_reads, - [TotalReadsPercent] = CAST(100.0 * qsNow.total_logical_reads / qsTotal.total_logical_reads AS DECIMAL(6,2)), - r.[DetailsInt] - FROM #BlitzFirstResults r - LEFT OUTER JOIN #QueryStats qsTotal ON qsTotal.Pass = 0 - LEFT OUTER JOIN #QueryStats qsTotalFirst ON qsTotalFirst.Pass = -1 - LEFT OUTER JOIN #QueryStats qsNow ON r.QueryStatsNowID = qsNow.ID - LEFT OUTER JOIN #QueryStats qsFirst ON r.QueryStatsFirstID = qsFirst.ID - ORDER BY r.Priority , - r.FindingsGroup , - CASE - WHEN r.CheckID = 6 THEN DetailsInt - ELSE 0 - END DESC, - r.Finding, - r.ID; - END; - ELSE IF @OutputType IN ( 'CSV', 'RSV' ) AND @SinceStartup = 0 - BEGIN - - SELECT Result = CAST([Priority] AS NVARCHAR(100)) - + @separator + CAST(CheckID AS NVARCHAR(100)) - + @separator + COALESCE([FindingsGroup], - '(N/A)') + @separator - + COALESCE([Finding], '(N/A)') + @separator - + COALESCE(DatabaseName, '(N/A)') + @separator - + COALESCE([URL], '(N/A)') + @separator - + COALESCE([Details], '(N/A)') - FROM #BlitzFirstResults - ORDER BY Priority , - FindingsGroup , - CASE - WHEN CheckID = 6 THEN DetailsInt - ELSE 0 - END DESC, - Finding, - Details; - END; - ELSE IF @OutputType = 'Top10' - BEGIN - /* Measure waits in hours */ - ;WITH max_batch AS ( - SELECT MAX(SampleTime) AS SampleTime - FROM #WaitStats - ) - SELECT TOP 10 - CAST(DATEDIFF(mi,wd1.SampleTime, wd2.SampleTime) / 60.0 AS DECIMAL(18,1)) AS [Hours Sample], - wd1.wait_type, - COALESCE(wcat.WaitCategory, 'Other') AS wait_category, - CAST(c.[Wait Time (Seconds)] / 60.0 / 60 AS DECIMAL(18,1)) AS [Wait Time (Hours)], - CAST((wd2.wait_time_ms - wd1.wait_time_ms) / 1000.0 / cores.cpu_count / DATEDIFF(ss, wd1.SampleTime, wd2.SampleTime) AS DECIMAL(18,1)) AS [Per Core Per Hour], - (wd2.waiting_tasks_count - wd1.waiting_tasks_count) AS [Number of Waits], - CASE WHEN (wd2.waiting_tasks_count - wd1.waiting_tasks_count) > 0 - THEN - CAST((wd2.wait_time_ms-wd1.wait_time_ms)/ - (1.0*(wd2.waiting_tasks_count - wd1.waiting_tasks_count)) AS NUMERIC(12,1)) - ELSE 0 END AS [Avg ms Per Wait] - FROM max_batch b - JOIN #WaitStats wd2 ON - wd2.SampleTime =b.SampleTime - JOIN #WaitStats wd1 ON - wd1.wait_type=wd2.wait_type AND - wd2.SampleTime > wd1.SampleTime - CROSS APPLY (SELECT SUM(1) AS cpu_count FROM sys.dm_os_schedulers WHERE status = 'VISIBLE ONLINE' AND is_online = 1) AS cores - CROSS APPLY (SELECT - CAST((wd2.wait_time_ms-wd1.wait_time_ms)/1000. AS NUMERIC(12,1)) AS [Wait Time (Seconds)], - CAST((wd2.signal_wait_time_ms - wd1.signal_wait_time_ms)/1000. AS NUMERIC(12,1)) AS [Signal Wait Time (Seconds)]) AS c - LEFT OUTER JOIN ##WaitCategories wcat ON wd1.wait_type = wcat.WaitType - WHERE (wd2.waiting_tasks_count - wd1.waiting_tasks_count) > 0 - AND wd2.wait_time_ms-wd1.wait_time_ms > 0 - ORDER BY [Wait Time (Seconds)] DESC; - END; - ELSE IF @ExpertMode = 0 AND @OutputType <> 'NONE' AND @OutputXMLasNVARCHAR = 0 AND @SinceStartup = 0 - BEGIN - SELECT [Priority] , - [FindingsGroup] , - [Finding] , - [URL] , - CAST(@StockDetailsHeader + [Details] + @StockDetailsFooter AS XML) AS Details, - CAST(@StockWarningHeader + HowToStopIt + @StockWarningFooter AS XML) AS HowToStopIt, - [QueryText], - [QueryPlan] - FROM #BlitzFirstResults - WHERE (@Seconds > 0 OR (Priority IN (0, 250, 251, 255))) /* For @Seconds = 0, filter out broken checks for now */ - ORDER BY Priority , - FindingsGroup , - CASE - WHEN CheckID = 6 THEN DetailsInt - ELSE 0 - END DESC, - Finding, - ID, - CAST(Details AS NVARCHAR(4000)); - END; - ELSE IF @OutputType <> 'NONE' AND @OutputXMLasNVARCHAR = 1 AND @SinceStartup = 0 - BEGIN - SELECT [Priority] , - [FindingsGroup] , - [Finding] , - [URL] , - CAST(@StockDetailsHeader + [Details] + @StockDetailsFooter AS NVARCHAR(MAX)) AS Details, - CAST([HowToStopIt] AS NVARCHAR(MAX)) AS HowToStopIt, - CAST([QueryText] AS NVARCHAR(MAX)) AS QueryText, - CAST([QueryPlan] AS NVARCHAR(MAX)) AS QueryPlan - FROM #BlitzFirstResults - WHERE (@Seconds > 0 OR (Priority IN (0, 250, 251, 255))) /* For @Seconds = 0, filter out broken checks for now */ - ORDER BY Priority , - FindingsGroup , - CASE - WHEN CheckID = 6 THEN DetailsInt - ELSE 0 - END DESC, - Finding, - ID, - CAST(Details AS NVARCHAR(4000)); - END; - ELSE IF @ExpertMode = 1 AND @OutputType <> 'NONE' - BEGIN - IF @SinceStartup = 0 - SELECT r.[Priority] , - r.[FindingsGroup] , - r.[Finding] , - r.[URL] , - CAST(@StockDetailsHeader + r.[Details] + @StockDetailsFooter AS XML) AS Details, - CAST(@StockWarningHeader + r.HowToStopIt + @StockWarningFooter AS XML) AS HowToStopIt, - r.[CheckID] , - r.[StartTime], - r.[LoginName], - r.[NTUserName], - r.[OriginalLoginName], - r.[ProgramName], - r.[HostName], - r.[DatabaseID], - r.[DatabaseName], - r.[OpenTransactionCount], - r.[QueryPlan], - r.[QueryText], - qsNow.plan_handle AS PlanHandle, - qsNow.sql_handle AS SqlHandle, - qsNow.statement_start_offset AS StatementStartOffset, - qsNow.statement_end_offset AS StatementEndOffset, - [Executions] = qsNow.execution_count - (COALESCE(qsFirst.execution_count, 0)), - [ExecutionsPercent] = CAST(100.0 * (qsNow.execution_count - (COALESCE(qsFirst.execution_count, 0))) / (qsTotal.execution_count - qsTotalFirst.execution_count) AS DECIMAL(6,2)), - [Duration] = qsNow.total_elapsed_time - (COALESCE(qsFirst.total_elapsed_time, 0)), - [DurationPercent] = CAST(100.0 * (qsNow.total_elapsed_time - (COALESCE(qsFirst.total_elapsed_time, 0))) / (qsTotal.total_elapsed_time - qsTotalFirst.total_elapsed_time) AS DECIMAL(6,2)), - [CPU] = qsNow.total_worker_time - (COALESCE(qsFirst.total_worker_time, 0)), - [CPUPercent] = CAST(100.0 * (qsNow.total_worker_time - (COALESCE(qsFirst.total_worker_time, 0))) / (qsTotal.total_worker_time - qsTotalFirst.total_worker_time) AS DECIMAL(6,2)), - [Reads] = qsNow.total_logical_reads - (COALESCE(qsFirst.total_logical_reads, 0)), - [ReadsPercent] = CAST(100.0 * (qsNow.total_logical_reads - (COALESCE(qsFirst.total_logical_reads, 0))) / (qsTotal.total_logical_reads - qsTotalFirst.total_logical_reads) AS DECIMAL(6,2)), - [PlanCreationTime] = CONVERT(NVARCHAR(100), qsNow.creation_time ,121), - [TotalExecutions] = qsNow.execution_count, - [TotalExecutionsPercent] = CAST(100.0 * qsNow.execution_count / qsTotal.execution_count AS DECIMAL(6,2)), - [TotalDuration] = qsNow.total_elapsed_time, - [TotalDurationPercent] = CAST(100.0 * qsNow.total_elapsed_time / qsTotal.total_elapsed_time AS DECIMAL(6,2)), - [TotalCPU] = qsNow.total_worker_time, - [TotalCPUPercent] = CAST(100.0 * qsNow.total_worker_time / qsTotal.total_worker_time AS DECIMAL(6,2)), - [TotalReads] = qsNow.total_logical_reads, - [TotalReadsPercent] = CAST(100.0 * qsNow.total_logical_reads / qsTotal.total_logical_reads AS DECIMAL(6,2)), - r.[DetailsInt] - FROM #BlitzFirstResults r - LEFT OUTER JOIN #QueryStats qsTotal ON qsTotal.Pass = 0 - LEFT OUTER JOIN #QueryStats qsTotalFirst ON qsTotalFirst.Pass = -1 - LEFT OUTER JOIN #QueryStats qsNow ON r.QueryStatsNowID = qsNow.ID - LEFT OUTER JOIN #QueryStats qsFirst ON r.QueryStatsFirstID = qsFirst.ID - WHERE (@Seconds > 0 OR (Priority IN (0, 250, 251, 255))) /* For @Seconds = 0, filter out broken checks for now */ - ORDER BY r.Priority , - r.FindingsGroup , - CASE - WHEN r.CheckID = 6 THEN DetailsInt - ELSE 0 - END DESC, - r.Finding, - r.ID, - CAST(r.Details AS NVARCHAR(4000)); - - ------------------------- - --What happened: #WaitStats - ------------------------- - IF @Seconds = 0 - BEGIN - /* Measure waits in hours */ - ;WITH max_batch AS ( - SELECT MAX(SampleTime) AS SampleTime - FROM #WaitStats - ) - SELECT - 'WAIT STATS' AS Pattern, - b.SampleTime AS [Sample Ended], - CAST(DATEDIFF(mi,wd1.SampleTime, wd2.SampleTime) / 60.0 AS DECIMAL(18,1)) AS [Hours Sample], - wd1.wait_type, - COALESCE(wcat.WaitCategory, 'Other') AS wait_category, - CAST(c.[Wait Time (Seconds)] / 60.0 / 60 AS DECIMAL(18,1)) AS [Wait Time (Hours)], - CAST((wd2.wait_time_ms - wd1.wait_time_ms) / 1000.0 / cores.cpu_count / DATEDIFF(ss, wd1.SampleTime, wd2.SampleTime) AS DECIMAL(18,1)) AS [Per Core Per Hour], - CAST(c.[Signal Wait Time (Seconds)] / 60.0 / 60 AS DECIMAL(18,1)) AS [Signal Wait Time (Hours)], - CASE WHEN c.[Wait Time (Seconds)] > 0 - THEN CAST(100.*(c.[Signal Wait Time (Seconds)]/c.[Wait Time (Seconds)]) AS NUMERIC(4,1)) - ELSE 0 END AS [Percent Signal Waits], - (wd2.waiting_tasks_count - wd1.waiting_tasks_count) AS [Number of Waits], - CASE WHEN (wd2.waiting_tasks_count - wd1.waiting_tasks_count) > 0 - THEN - CAST((wd2.wait_time_ms-wd1.wait_time_ms)/ - (1.0*(wd2.waiting_tasks_count - wd1.waiting_tasks_count)) AS NUMERIC(12,1)) - ELSE 0 END AS [Avg ms Per Wait], - N'https://www.sqlskills.com/help/waits/' + LOWER(wd1.wait_type) + '/' AS URL - FROM max_batch b - JOIN #WaitStats wd2 ON - wd2.SampleTime =b.SampleTime - JOIN #WaitStats wd1 ON - wd1.wait_type=wd2.wait_type AND - wd2.SampleTime > wd1.SampleTime - CROSS APPLY (SELECT SUM(1) AS cpu_count FROM sys.dm_os_schedulers WHERE status = 'VISIBLE ONLINE' AND is_online = 1) AS cores - CROSS APPLY (SELECT - CAST((wd2.wait_time_ms-wd1.wait_time_ms)/1000. AS NUMERIC(12,1)) AS [Wait Time (Seconds)], - CAST((wd2.signal_wait_time_ms - wd1.signal_wait_time_ms)/1000. AS NUMERIC(12,1)) AS [Signal Wait Time (Seconds)]) AS c - LEFT OUTER JOIN ##WaitCategories wcat ON wd1.wait_type = wcat.WaitType - WHERE (wd2.waiting_tasks_count - wd1.waiting_tasks_count) > 0 - AND wd2.wait_time_ms-wd1.wait_time_ms > 0 - ORDER BY [Wait Time (Seconds)] DESC; - END; - ELSE - BEGIN - /* Measure waits in seconds */ - ;WITH max_batch AS ( - SELECT MAX(SampleTime) AS SampleTime - FROM #WaitStats - ) - SELECT - 'WAIT STATS' AS Pattern, - b.SampleTime AS [Sample Ended], - DATEDIFF(ss,wd1.SampleTime, wd2.SampleTime) AS [Seconds Sample], - wd1.wait_type, - COALESCE(wcat.WaitCategory, 'Other') AS wait_category, - c.[Wait Time (Seconds)], - CAST((CAST(wd2.wait_time_ms - wd1.wait_time_ms AS MONEY)) / 1000.0 / cores.cpu_count / DATEDIFF(ss, wd1.SampleTime, wd2.SampleTime) AS DECIMAL(18,1)) AS [Per Core Per Second], - c.[Signal Wait Time (Seconds)], - CASE WHEN c.[Wait Time (Seconds)] > 0 - THEN CAST(100.*(c.[Signal Wait Time (Seconds)]/c.[Wait Time (Seconds)]) AS NUMERIC(4,1)) - ELSE 0 END AS [Percent Signal Waits], - (wd2.waiting_tasks_count - wd1.waiting_tasks_count) AS [Number of Waits], - CASE WHEN (wd2.waiting_tasks_count - wd1.waiting_tasks_count) > 0 - THEN - CAST((wd2.wait_time_ms-wd1.wait_time_ms)/ - (1.0*(wd2.waiting_tasks_count - wd1.waiting_tasks_count)) AS NUMERIC(12,1)) - ELSE 0 END AS [Avg ms Per Wait], - N'https://www.sqlskills.com/help/waits/' + LOWER(wd1.wait_type) + '/' AS URL - FROM max_batch b - JOIN #WaitStats wd2 ON - wd2.SampleTime =b.SampleTime - JOIN #WaitStats wd1 ON - wd1.wait_type=wd2.wait_type AND - wd2.SampleTime > wd1.SampleTime - CROSS APPLY (SELECT SUM(1) AS cpu_count FROM sys.dm_os_schedulers WHERE status = 'VISIBLE ONLINE' AND is_online = 1) AS cores - CROSS APPLY (SELECT - CAST((wd2.wait_time_ms-wd1.wait_time_ms)/1000. AS NUMERIC(12,1)) AS [Wait Time (Seconds)], - CAST((wd2.signal_wait_time_ms - wd1.signal_wait_time_ms)/1000. AS NUMERIC(12,1)) AS [Signal Wait Time (Seconds)]) AS c - LEFT OUTER JOIN ##WaitCategories wcat ON wd1.wait_type = wcat.WaitType - WHERE (wd2.waiting_tasks_count - wd1.waiting_tasks_count) > 0 - AND wd2.wait_time_ms-wd1.wait_time_ms > 0 - ORDER BY [Wait Time (Seconds)] DESC; - END; - - ------------------------- - --What happened: #FileStats - ------------------------- - WITH readstats AS ( - SELECT 'PHYSICAL READS' AS Pattern, - ROW_NUMBER() OVER (ORDER BY wd2.avg_stall_read_ms DESC) AS StallRank, - wd2.SampleTime AS [Sample Time], - DATEDIFF(ss,wd1.SampleTime, wd2.SampleTime) AS [Sample (seconds)], - wd1.DatabaseName , - wd1.FileLogicalName AS [File Name], - UPPER(SUBSTRING(wd1.PhysicalName, 1, 2)) AS [Drive] , - wd1.SizeOnDiskMB , - ( wd2.num_of_reads - wd1.num_of_reads ) AS [# Reads/Writes], - CASE WHEN wd2.num_of_reads - wd1.num_of_reads > 0 - THEN CAST(( wd2.bytes_read - wd1.bytes_read)/1024./1024. AS NUMERIC(21,1)) - ELSE 0 - END AS [MB Read/Written], - wd2.avg_stall_read_ms AS [Avg Stall (ms)], - wd1.PhysicalName AS [file physical name] - FROM #FileStats wd2 - JOIN #FileStats wd1 ON wd2.SampleTime > wd1.SampleTime - AND wd1.DatabaseID = wd2.DatabaseID - AND wd1.FileID = wd2.FileID - ), - writestats AS ( - SELECT - 'PHYSICAL WRITES' AS Pattern, - ROW_NUMBER() OVER (ORDER BY wd2.avg_stall_write_ms DESC) AS StallRank, - wd2.SampleTime AS [Sample Time], - DATEDIFF(ss,wd1.SampleTime, wd2.SampleTime) AS [Sample (seconds)], - wd1.DatabaseName , - wd1.FileLogicalName AS [File Name], - UPPER(SUBSTRING(wd1.PhysicalName, 1, 2)) AS [Drive] , - wd1.SizeOnDiskMB , - ( wd2.num_of_writes - wd1.num_of_writes ) AS [# Reads/Writes], - CASE WHEN wd2.num_of_writes - wd1.num_of_writes > 0 - THEN CAST(( wd2.bytes_written - wd1.bytes_written)/1024./1024. AS NUMERIC(21,1)) - ELSE 0 - END AS [MB Read/Written], - wd2.avg_stall_write_ms AS [Avg Stall (ms)], - wd1.PhysicalName AS [file physical name] - FROM #FileStats wd2 - JOIN #FileStats wd1 ON wd2.SampleTime > wd1.SampleTime - AND wd1.DatabaseID = wd2.DatabaseID - AND wd1.FileID = wd2.FileID - ) - SELECT - Pattern, [Sample Time], [Sample (seconds)], [File Name], [Drive], [# Reads/Writes],[MB Read/Written],[Avg Stall (ms)], [file physical name], [StallRank] - FROM readstats - WHERE StallRank <=20 AND [MB Read/Written] > 0 - UNION ALL - SELECT Pattern, [Sample Time], [Sample (seconds)], [File Name], [Drive], [# Reads/Writes],[MB Read/Written],[Avg Stall (ms)], [file physical name], [StallRank] - FROM writestats - WHERE StallRank <=20 AND [MB Read/Written] > 0 - ORDER BY Pattern, StallRank; - - - ------------------------- - --What happened: #PerfmonStats - ------------------------- - - SELECT 'PERFMON' AS Pattern, pLast.[object_name], pLast.counter_name, pLast.instance_name, - pFirst.SampleTime AS FirstSampleTime, pFirst.cntr_value AS FirstSampleValue, - pLast.SampleTime AS LastSampleTime, pLast.cntr_value AS LastSampleValue, - pLast.cntr_value - pFirst.cntr_value AS ValueDelta, - ((1.0 * pLast.cntr_value - pFirst.cntr_value) / DATEDIFF(ss, pFirst.SampleTime, pLast.SampleTime)) AS ValuePerSecond - FROM #PerfmonStats pLast - INNER JOIN #PerfmonStats pFirst ON pFirst.[object_name] = pLast.[object_name] AND pFirst.counter_name = pLast.counter_name AND (pFirst.instance_name = pLast.instance_name OR (pFirst.instance_name IS NULL AND pLast.instance_name IS NULL)) - AND pLast.ID > pFirst.ID - WHERE pLast.cntr_value <> pFirst.cntr_value - ORDER BY Pattern, pLast.[object_name], pLast.counter_name, pLast.instance_name; - - - ------------------------- - --What happened: #QueryStats - ------------------------- - IF @CheckProcedureCache = 1 - BEGIN - - SELECT qsNow.*, qsFirst.* - FROM #QueryStats qsNow - INNER JOIN #QueryStats qsFirst ON qsNow.[sql_handle] = qsFirst.[sql_handle] AND qsNow.statement_start_offset = qsFirst.statement_start_offset AND qsNow.statement_end_offset = qsFirst.statement_end_offset AND qsNow.plan_generation_num = qsFirst.plan_generation_num AND qsNow.plan_handle = qsFirst.plan_handle AND qsFirst.Pass = 1 - WHERE qsNow.Pass = 2; - END; - ELSE - BEGIN - SELECT 'Plan Cache' AS [Pattern], 'Plan cache not analyzed' AS [Finding], 'Use @CheckProcedureCache = 1 or run sp_BlitzCache for more analysis' AS [More Info], CONVERT(XML, @StockDetailsHeader + 'firstresponderkit.org' + @StockDetailsFooter) AS [Details]; - END; - END; - - DROP TABLE #BlitzFirstResults; - - /* What's running right now? This is the first and last result set. */ - IF @SinceStartup = 0 AND @Seconds > 0 AND @ExpertMode = 1 AND @OutputType <> 'NONE' - BEGIN - IF OBJECT_ID('master.dbo.sp_BlitzWho') IS NULL AND OBJECT_ID('dbo.sp_BlitzWho') IS NULL - BEGIN - PRINT N'sp_BlitzWho is not installed in the current database_files. You can get a copy from http://FirstResponderKit.org'; - END; - ELSE - BEGIN - EXEC (@BlitzWho); - END; - END; /* IF @SinceStartup = 0 AND @Seconds > 0 AND @ExpertMode = 1 AND @OutputType <> 'NONE' - What's running right now? This is the first and last result set. */ - -END; /* IF @LogMessage IS NULL */ -END; /* ELSE IF @OutputType = 'SCHEMA' */ - -SET NOCOUNT OFF; -GO - - - -/* How to run it: -EXEC dbo.sp_BlitzFirst - -With extra diagnostic info: -EXEC dbo.sp_BlitzFirst @ExpertMode = 1; - -Saving output to tables: -EXEC sp_BlitzFirst - @OutputDatabaseName = 'DBAtools' -, @OutputSchemaName = 'dbo' -, @OutputTableName = 'BlitzFirst' -, @OutputTableNameFileStats = 'BlitzFirst_FileStats' -, @OutputTableNamePerfmonStats = 'BlitzFirst_PerfmonStats' -, @OutputTableNameWaitStats = 'BlitzFirst_WaitStats' -, @OutputTableNameBlitzCache = 'BlitzCache' -, @OutputTableNameBlitzWho = 'BlitzWho' -*/ -SET ANSI_NULLS ON; -SET ANSI_PADDING ON; -SET ANSI_WARNINGS ON; -SET ARITHABORT ON; -SET CONCAT_NULL_YIELDS_NULL ON; -SET QUOTED_IDENTIFIER ON; -SET STATISTICS IO OFF; -SET STATISTICS TIME OFF; -GO - -IF OBJECT_ID('dbo.sp_BlitzIndex') IS NULL - EXEC ('CREATE PROCEDURE dbo.sp_BlitzIndex AS RETURN 0;'); -GO - -ALTER PROCEDURE dbo.sp_BlitzIndex - @DatabaseName NVARCHAR(128) = NULL, /*Defaults to current DB if not specified*/ - @SchemaName NVARCHAR(128) = NULL, /*Requires table_name as well.*/ - @TableName NVARCHAR(128) = NULL, /*Requires schema_name as well.*/ - @Mode TINYINT=0, /*0=Diagnose, 1=Summarize, 2=Index Usage Detail, 3=Missing Index Detail, 4=Diagnose Details*/ - /*Note:@Mode doesn't matter if you're specifying schema_name and @TableName.*/ - @Filter TINYINT = 0, /* 0=no filter (default). 1=No low-usage warnings for objects with 0 reads. 2=Only warn for objects >= 500MB */ - /*Note:@Filter doesn't do anything unless @Mode=0*/ - @SkipPartitions BIT = 0, - @SkipStatistics BIT = 1, - @GetAllDatabases BIT = 0, - @BringThePain BIT = 0, - @IgnoreDatabases NVARCHAR(MAX) = NULL, /* Comma-delimited list of databases you want to skip */ - @ThresholdMB INT = 250 /* Number of megabytes that an object must be before we include it in basic results */, - @OutputType VARCHAR(20) = 'TABLE' , - @OutputServerName NVARCHAR(256) = NULL , - @OutputDatabaseName NVARCHAR(256) = NULL , - @OutputSchemaName NVARCHAR(256) = NULL , - @OutputTableName NVARCHAR(256) = NULL , - @IncludeInactiveIndexes BIT = 0 /* Will skip indexes with no reads or writes */, - @ShowAllMissingIndexRequests BIT = 0 /*Will make all missing index requests show up*/, - @SortOrder NVARCHAR(50) = NULL, /* Only affects @Mode = 2. */ - @SortDirection NVARCHAR(4) = 'DESC', /* Only affects @Mode = 2. */ - @Help TINYINT = 0, - @Debug BIT = 0, - @Version VARCHAR(30) = NULL OUTPUT, - @VersionDate DATETIME = NULL OUTPUT, - @VersionCheckMode BIT = 0 -WITH RECOMPILE -AS -SET NOCOUNT ON; -SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - -SELECT @Version = '8.0', @VersionDate = '20210117'; -SET @OutputType = UPPER(@OutputType); - -IF(@VersionCheckMode = 1) -BEGIN - RETURN; -END; - -IF @Help = 1 -BEGIN -PRINT ' -/* -sp_BlitzIndex from http://FirstResponderKit.org - -This script analyzes the design and performance of your indexes. - -To learn more, visit http://FirstResponderKit.org where you can download new -versions for free, watch training videos on how it works, get more info on -the findings, contribute your own code, and more. - -Known limitations of this version: - - Only Microsoft-supported versions of SQL Server. Sorry, 2005 and 2000. - - Index create statements are just to give you a rough idea of the syntax. It includes filters and fillfactor. - -- Example 1: index creates use ONLINE=? instead of ONLINE=ON / ONLINE=OFF. This is because it is important - for the user to understand if it is going to be offline and not just run a script. - -- Example 2: they do not include all the options the index may have been created with (padding, compression - filegroup/partition scheme etc.) - -- (The compression and filegroup index create syntax is not trivial because it is set at the partition - level and is not trivial to code.) - - Does not advise you about data modeling for clustered indexes and primary keys (primarily looks for signs of insanity.) - -Unknown limitations of this version: - - We knew them once, but we forgot. - - -MIT License - -Copyright (c) 2021 Brent Ozar Unlimited - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -'; -RETURN; -END; /* @Help = 1 */ - -DECLARE @ScriptVersionName NVARCHAR(50); -DECLARE @DaysUptime NUMERIC(23,2); -DECLARE @DatabaseID INT; -DECLARE @ObjectID INT; -DECLARE @dsql NVARCHAR(MAX); -DECLARE @params NVARCHAR(MAX); -DECLARE @msg NVARCHAR(4000); -DECLARE @ErrorSeverity INT; -DECLARE @ErrorState INT; -DECLARE @Rowcount BIGINT; -DECLARE @SQLServerProductVersion NVARCHAR(128); -DECLARE @SQLServerEdition INT; -DECLARE @FilterMB INT; -DECLARE @collation NVARCHAR(256); -DECLARE @NumDatabases INT; -DECLARE @LineFeed NVARCHAR(5); -DECLARE @DaysUptimeInsertValue NVARCHAR(256); -DECLARE @DatabaseToIgnore NVARCHAR(MAX); -DECLARE @ColumnList NVARCHAR(MAX); - -/* Let's get @SortOrder set to lower case here for comparisons later */ -SET @SortOrder = REPLACE(LOWER(@SortOrder), N' ', N'_'); -SET @SortDirection = LOWER(@SortDirection); - -SET @LineFeed = CHAR(13) + CHAR(10); -SELECT @SQLServerProductVersion = CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)); -SELECT @SQLServerEdition =CAST(SERVERPROPERTY('EngineEdition') AS INT); /* We default to online index creates where EngineEdition=3*/ -SET @FilterMB=250; -SELECT @ScriptVersionName = 'sp_BlitzIndex(TM) v' + @Version + ' - ' + DATENAME(MM, @VersionDate) + ' ' + RIGHT('0'+DATENAME(DD, @VersionDate),2) + ', ' + DATENAME(YY, @VersionDate); -SET @IgnoreDatabases = REPLACE(REPLACE(LTRIM(RTRIM(@IgnoreDatabases)), CHAR(10), ''), CHAR(13), ''); - -RAISERROR(N'Starting run. %s', 0,1, @ScriptVersionName) WITH NOWAIT; - -IF(@OutputType NOT IN ('TABLE','NONE')) -BEGIN - RAISERROR('Invalid value for parameter @OutputType. Expected: (TABLE;NONE)',12,1); - RETURN; -END; - -IF(@OutputType = 'NONE') -BEGIN - IF(@OutputTableName IS NULL OR @OutputSchemaName IS NULL OR @OutputDatabaseName IS NULL) - BEGIN - RAISERROR('This procedure should be called with a value for @Output* parameters, as @OutputType is set to NONE',12,1); - RETURN; - END; - IF(@BringThePain = 1) - BEGIN - RAISERROR('Incompatible Parameters: @BringThePain set to 1 and @OutputType set to NONE',12,1); - RETURN; - END; - /* Eventually limit by mode - IF(@Mode not in (0,4)) - BEGIN - RAISERROR('Incompatible Parameters: @Mode set to %d and @OutputType set to NONE',12,1,@Mode); - RETURN; - END; - */ -END; - -IF OBJECT_ID('tempdb..#IndexSanity') IS NOT NULL - DROP TABLE #IndexSanity; - -IF OBJECT_ID('tempdb..#IndexPartitionSanity') IS NOT NULL - DROP TABLE #IndexPartitionSanity; - -IF OBJECT_ID('tempdb..#IndexSanitySize') IS NOT NULL - DROP TABLE #IndexSanitySize; - -IF OBJECT_ID('tempdb..#IndexColumns') IS NOT NULL - DROP TABLE #IndexColumns; - -IF OBJECT_ID('tempdb..#MissingIndexes') IS NOT NULL - DROP TABLE #MissingIndexes; - -IF OBJECT_ID('tempdb..#ForeignKeys') IS NOT NULL - DROP TABLE #ForeignKeys; - -IF OBJECT_ID('tempdb..#BlitzIndexResults') IS NOT NULL - DROP TABLE #BlitzIndexResults; - -IF OBJECT_ID('tempdb..#IndexCreateTsql') IS NOT NULL - DROP TABLE #IndexCreateTsql; - -IF OBJECT_ID('tempdb..#DatabaseList') IS NOT NULL - DROP TABLE #DatabaseList; - -IF OBJECT_ID('tempdb..#Statistics') IS NOT NULL - DROP TABLE #Statistics; - -IF OBJECT_ID('tempdb..#PartitionCompressionInfo') IS NOT NULL - DROP TABLE #PartitionCompressionInfo; - -IF OBJECT_ID('tempdb..#ComputedColumns') IS NOT NULL - DROP TABLE #ComputedColumns; - -IF OBJECT_ID('tempdb..#TraceStatus') IS NOT NULL - DROP TABLE #TraceStatus; - -IF OBJECT_ID('tempdb..#TemporalTables') IS NOT NULL - DROP TABLE #TemporalTables; - -IF OBJECT_ID('tempdb..#CheckConstraints') IS NOT NULL - DROP TABLE #CheckConstraints; - -IF OBJECT_ID('tempdb..#FilteredIndexes') IS NOT NULL - DROP TABLE #FilteredIndexes; - -IF OBJECT_ID('tempdb..#Ignore_Databases') IS NOT NULL - DROP TABLE #Ignore_Databases - - RAISERROR (N'Create temp tables.',0,1) WITH NOWAIT; - CREATE TABLE #BlitzIndexResults - ( - blitz_result_id INT IDENTITY PRIMARY KEY, - check_id INT NOT NULL, - index_sanity_id INT NULL, - Priority INT NULL, - findings_group NVARCHAR(4000) NOT NULL, - finding NVARCHAR(200) NOT NULL, - [database_name] NVARCHAR(128) NULL, - URL NVARCHAR(200) NOT NULL, - details NVARCHAR(MAX) NOT NULL, - index_definition NVARCHAR(MAX) NOT NULL, - secret_columns NVARCHAR(MAX) NULL, - index_usage_summary NVARCHAR(MAX) NULL, - index_size_summary NVARCHAR(MAX) NULL, - create_tsql NVARCHAR(MAX) NULL, - more_info NVARCHAR(MAX)NULL - ); - - CREATE TABLE #IndexSanity - ( - [index_sanity_id] INT IDENTITY PRIMARY KEY CLUSTERED, - [database_id] SMALLINT NOT NULL , - [object_id] INT NOT NULL , - [index_id] INT NOT NULL , - [index_type] TINYINT NOT NULL, - [database_name] NVARCHAR(128) NOT NULL , - [schema_name] NVARCHAR(128) NOT NULL , - [object_name] NVARCHAR(128) NOT NULL , - index_name NVARCHAR(128) NULL , - key_column_names NVARCHAR(MAX) NULL , - key_column_names_with_sort_order NVARCHAR(MAX) NULL , - key_column_names_with_sort_order_no_types NVARCHAR(MAX) NULL , - count_key_columns INT NULL , - include_column_names NVARCHAR(MAX) NULL , - include_column_names_no_types NVARCHAR(MAX) NULL , - count_included_columns INT NULL , - partition_key_column_name NVARCHAR(MAX) NULL, - filter_definition NVARCHAR(MAX) NOT NULL , - is_indexed_view BIT NOT NULL , - is_unique BIT NOT NULL , - is_primary_key BIT NOT NULL , - is_XML BIT NOT NULL, - is_spatial BIT NOT NULL, - is_NC_columnstore BIT NOT NULL, - is_CX_columnstore BIT NOT NULL, - is_in_memory_oltp BIT NOT NULL , - is_disabled BIT NOT NULL , - is_hypothetical BIT NOT NULL , - is_padded BIT NOT NULL , - fill_factor SMALLINT NOT NULL , - user_seeks BIGINT NOT NULL , - user_scans BIGINT NOT NULL , - user_lookups BIGINT NOT NULL , - user_updates BIGINT NULL , - last_user_seek DATETIME NULL , - last_user_scan DATETIME NULL , - last_user_lookup DATETIME NULL , - last_user_update DATETIME NULL , - is_referenced_by_foreign_key BIT DEFAULT(0), - secret_columns NVARCHAR(MAX) NULL, - count_secret_columns INT NULL, - create_date DATETIME NOT NULL, - modify_date DATETIME NOT NULL, - filter_columns_not_in_index NVARCHAR(MAX), - [db_schema_object_name] AS [schema_name] + N'.' + [object_name] , - [db_schema_object_indexid] AS [schema_name] + N'.' + [object_name] - + CASE WHEN [index_name] IS NOT NULL THEN N'.' + index_name - ELSE N'' - END + N' (' + CAST(index_id AS NVARCHAR(20)) + N')' , - first_key_column_name AS CASE WHEN count_key_columns > 1 - THEN LEFT(key_column_names, CHARINDEX(',', key_column_names, 0) - 1) - ELSE key_column_names - END , - index_definition AS - CASE WHEN partition_key_column_name IS NOT NULL - THEN N'[PARTITIONED BY:' + partition_key_column_name + N']' - ELSE '' - END + - CASE index_id - WHEN 0 THEN N'[HEAP] ' - WHEN 1 THEN N'[CX] ' - ELSE N'' END + CASE WHEN is_indexed_view = 1 THEN N'[VIEW] ' - ELSE N'' END + CASE WHEN is_primary_key = 1 THEN N'[PK] ' - ELSE N'' END + CASE WHEN is_XML = 1 THEN N'[XML] ' - ELSE N'' END + CASE WHEN is_spatial = 1 THEN N'[SPATIAL] ' - ELSE N'' END + CASE WHEN is_NC_columnstore = 1 THEN N'[COLUMNSTORE] ' - ELSE N'' END + CASE WHEN is_in_memory_oltp = 1 THEN N'[IN-MEMORY] ' - ELSE N'' END + CASE WHEN is_disabled = 1 THEN N'[DISABLED] ' - ELSE N'' END + CASE WHEN is_hypothetical = 1 THEN N'[HYPOTHETICAL] ' - ELSE N'' END + CASE WHEN is_unique = 1 AND is_primary_key = 0 THEN N'[UNIQUE] ' - ELSE N'' END + CASE WHEN count_key_columns > 0 THEN - N'[' + CAST(count_key_columns AS NVARCHAR(10)) + N' KEY' - + CASE WHEN count_key_columns > 1 THEN N'S' ELSE N'' END - + N'] ' + LTRIM(key_column_names_with_sort_order) - ELSE N'' END + CASE WHEN count_included_columns > 0 THEN - N' [' + CAST(count_included_columns AS NVARCHAR(10)) + N' INCLUDE' + - + CASE WHEN count_included_columns > 1 THEN N'S' ELSE N'' END - + N'] ' + include_column_names - ELSE N'' END + CASE WHEN filter_definition <> N'' THEN N' [FILTER] ' + filter_definition - ELSE N'' END , - [total_reads] AS user_seeks + user_scans + user_lookups, - [reads_per_write] AS CAST(CASE WHEN user_updates > 0 - THEN ( user_seeks + user_scans + user_lookups ) / (1.0 * user_updates) - ELSE 0 END AS MONEY) , - [index_usage_summary] AS - CASE WHEN is_spatial = 1 THEN N'Not Tracked' - WHEN is_disabled = 1 THEN N'Disabled' - ELSE N'Reads: ' + - REPLACE(CONVERT(NVARCHAR(30),CAST((user_seeks + user_scans + user_lookups) AS MONEY), 1), N'.00', N'') - + CASE WHEN user_seeks + user_scans + user_lookups > 0 THEN - N' (' - + RTRIM( - CASE WHEN user_seeks > 0 THEN REPLACE(CONVERT(NVARCHAR(30),CAST((user_seeks) AS MONEY), 1), N'.00', N'') + N' seek ' ELSE N'' END - + CASE WHEN user_scans > 0 THEN REPLACE(CONVERT(NVARCHAR(30),CAST((user_scans) AS MONEY), 1), N'.00', N'') + N' scan ' ELSE N'' END - + CASE WHEN user_lookups > 0 THEN REPLACE(CONVERT(NVARCHAR(30),CAST((user_lookups) AS MONEY), 1), N'.00', N'') + N' lookup' ELSE N'' END - ) - + N') ' - ELSE N' ' - END - + N'Writes: ' + - REPLACE(CONVERT(NVARCHAR(30),CAST(user_updates AS MONEY), 1), N'.00', N'') - END /* First "end" is about is_spatial */, - [more_info] AS - CASE WHEN is_in_memory_oltp = 1 - THEN N'EXEC dbo.sp_BlitzInMemoryOLTP @dbName=' + QUOTENAME([database_name],N'''') + - N', @tableName=' + QUOTENAME([object_name],N'''') + N';' - ELSE N'EXEC dbo.sp_BlitzIndex @DatabaseName=' + QUOTENAME([database_name],N'''') + - N', @SchemaName=' + QUOTENAME([schema_name],N'''') + N', @TableName=' + QUOTENAME([object_name],N'''') + N';' - END - ); - RAISERROR (N'Adding UQ index on #IndexSanity (database_id, object_id, index_id)',0,1) WITH NOWAIT; - IF NOT EXISTS(SELECT 1 FROM tempdb.sys.indexes WHERE name='uq_database_id_object_id_index_id') - CREATE UNIQUE INDEX uq_database_id_object_id_index_id ON #IndexSanity (database_id, object_id, index_id); - - - CREATE TABLE #IndexPartitionSanity - ( - [index_partition_sanity_id] INT IDENTITY, - [index_sanity_id] INT NULL , - [database_id] INT NOT NULL , - [object_id] INT NOT NULL , - [schema_name] NVARCHAR(128) NOT NULL, - [index_id] INT NOT NULL , - [partition_number] INT NOT NULL , - row_count BIGINT NOT NULL , - reserved_MB NUMERIC(29,2) NOT NULL , - reserved_LOB_MB NUMERIC(29,2) NOT NULL , - reserved_row_overflow_MB NUMERIC(29,2) NOT NULL , - reserved_dictionary_MB NUMERIC(29,2) NOT NULL , - leaf_insert_count BIGINT NULL , - leaf_delete_count BIGINT NULL , - leaf_update_count BIGINT NULL , - range_scan_count BIGINT NULL , - singleton_lookup_count BIGINT NULL , - forwarded_fetch_count BIGINT NULL , - lob_fetch_in_pages BIGINT NULL , - lob_fetch_in_bytes BIGINT NULL , - row_overflow_fetch_in_pages BIGINT NULL , - row_overflow_fetch_in_bytes BIGINT NULL , - row_lock_count BIGINT NULL , - row_lock_wait_count BIGINT NULL , - row_lock_wait_in_ms BIGINT NULL , - page_lock_count BIGINT NULL , - page_lock_wait_count BIGINT NULL , - page_lock_wait_in_ms BIGINT NULL , - index_lock_promotion_attempt_count BIGINT NULL , - index_lock_promotion_count BIGINT NULL, - data_compression_desc NVARCHAR(60) NULL, - page_latch_wait_count BIGINT NULL, - page_latch_wait_in_ms BIGINT NULL, - page_io_latch_wait_count BIGINT NULL, - page_io_latch_wait_in_ms BIGINT NULL, - lock_escalation_desc nvarchar(60) NULL - ); - - CREATE TABLE #IndexSanitySize - ( - [index_sanity_size_id] INT IDENTITY NOT NULL , - [index_sanity_id] INT NULL , - [database_id] INT NOT NULL, - [schema_name] NVARCHAR(128) NOT NULL, - partition_count INT NOT NULL , - total_rows BIGINT NOT NULL , - total_reserved_MB NUMERIC(29,2) NOT NULL , - total_reserved_LOB_MB NUMERIC(29,2) NOT NULL , - total_reserved_row_overflow_MB NUMERIC(29,2) NOT NULL , - total_reserved_dictionary_MB NUMERIC(29,2) NOT NULL , - total_leaf_delete_count BIGINT NULL, - total_leaf_update_count BIGINT NULL, - total_range_scan_count BIGINT NULL, - total_singleton_lookup_count BIGINT NULL, - total_forwarded_fetch_count BIGINT NULL, - total_row_lock_count BIGINT NULL , - total_row_lock_wait_count BIGINT NULL , - total_row_lock_wait_in_ms BIGINT NULL , - avg_row_lock_wait_in_ms BIGINT NULL , - total_page_lock_count BIGINT NULL , - total_page_lock_wait_count BIGINT NULL , - total_page_lock_wait_in_ms BIGINT NULL , - avg_page_lock_wait_in_ms BIGINT NULL , - total_index_lock_promotion_attempt_count BIGINT NULL , - total_index_lock_promotion_count BIGINT NULL , - data_compression_desc NVARCHAR(4000) NULL, - page_latch_wait_count BIGINT NULL, - page_latch_wait_in_ms BIGINT NULL, - page_io_latch_wait_count BIGINT NULL, - page_io_latch_wait_in_ms BIGINT NULL, - lock_escalation_desc nvarchar(60) NULL, - index_size_summary AS ISNULL( - CASE WHEN partition_count > 1 - THEN N'[' + CAST(partition_count AS NVARCHAR(10)) + N' PARTITIONS] ' - ELSE N'' - END + REPLACE(CONVERT(NVARCHAR(30),CAST([total_rows] AS MONEY), 1), N'.00', N'') + N' rows; ' - + CASE WHEN total_reserved_MB > 1024 THEN - CAST(CAST(total_reserved_MB/1024. AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'GB' - ELSE - CAST(CAST(total_reserved_MB AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'MB' - END - + CASE WHEN total_reserved_LOB_MB > 1024 THEN - N'; ' + CAST(CAST(total_reserved_LOB_MB/1024. AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'GB ' + CASE WHEN total_reserved_dictionary_MB = 0 THEN N'LOB' ELSE N'Columnstore' END - WHEN total_reserved_LOB_MB > 0 THEN - N'; ' + CAST(CAST(total_reserved_LOB_MB AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'MB ' + CASE WHEN total_reserved_dictionary_MB = 0 THEN N'LOB' ELSE N'Columnstore' END - ELSE '' - END - + CASE WHEN total_reserved_row_overflow_MB > 1024 THEN - N'; ' + CAST(CAST(total_reserved_row_overflow_MB/1024. AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'GB Row Overflow' - WHEN total_reserved_row_overflow_MB > 0 THEN - N'; ' + CAST(CAST(total_reserved_row_overflow_MB AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'MB Row Overflow' - ELSE '' - END - + CASE WHEN total_reserved_dictionary_MB > 1024 THEN - N'; ' + CAST(CAST(total_reserved_dictionary_MB/1024. AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'GB Dictionaries' - WHEN total_reserved_dictionary_MB > 0 THEN - N'; ' + CAST(CAST(total_reserved_dictionary_MB AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'MB Dictionaries' - ELSE '' - END , - N'Error- NULL in computed column'), - index_op_stats AS ISNULL( - ( - REPLACE(CONVERT(NVARCHAR(30),CAST(total_singleton_lookup_count AS MONEY), 1),N'.00',N'') + N' singleton lookups; ' - + REPLACE(CONVERT(NVARCHAR(30),CAST(total_range_scan_count AS MONEY), 1),N'.00',N'') + N' scans/seeks; ' - + REPLACE(CONVERT(NVARCHAR(30),CAST(total_leaf_delete_count AS MONEY), 1),N'.00',N'') + N' deletes; ' - + REPLACE(CONVERT(NVARCHAR(30),CAST(total_leaf_update_count AS MONEY), 1),N'.00',N'') + N' updates; ' - + CASE WHEN ISNULL(total_forwarded_fetch_count,0) >0 THEN - REPLACE(CONVERT(NVARCHAR(30),CAST(total_forwarded_fetch_count AS MONEY), 1),N'.00',N'') + N' forward records fetched; ' - ELSE N'' END - - /* rows will only be in this dmv when data is in memory for the table */ - ), N'Table metadata not in memory'), - index_lock_wait_summary AS ISNULL( - CASE WHEN total_row_lock_wait_count = 0 AND total_page_lock_wait_count = 0 AND - total_index_lock_promotion_attempt_count = 0 THEN N'0 lock waits; ' - + CASE WHEN lock_escalation_desc = N'DISABLE' THEN N'Lock escalation DISABLE.' - ELSE N'' - END - ELSE - CASE WHEN total_row_lock_wait_count > 0 THEN - N'Row lock waits: ' + REPLACE(CONVERT(NVARCHAR(30),CAST(total_row_lock_wait_count AS MONEY), 1), N'.00', N'') - + N'; total duration: ' + - CASE WHEN total_row_lock_wait_in_ms >= 60000 THEN /*More than 1 min*/ - REPLACE(CONVERT(NVARCHAR(30),CAST((total_row_lock_wait_in_ms/60000) AS MONEY), 1), N'.00', N'') + N' minutes; ' - ELSE - REPLACE(CONVERT(NVARCHAR(30),CAST(ISNULL(total_row_lock_wait_in_ms/1000,0) AS MONEY), 1), N'.00', N'') + N' seconds; ' - END - + N'avg duration: ' + - CASE WHEN avg_row_lock_wait_in_ms >= 60000 THEN /*More than 1 min*/ - REPLACE(CONVERT(NVARCHAR(30),CAST((avg_row_lock_wait_in_ms/60000) AS MONEY), 1), N'.00', N'') + N' minutes; ' - ELSE - REPLACE(CONVERT(NVARCHAR(30),CAST(ISNULL(avg_row_lock_wait_in_ms/1000,0) AS MONEY), 1), N'.00', N'') + N' seconds; ' - END - ELSE N'' - END + - CASE WHEN total_page_lock_wait_count > 0 THEN - N'Page lock waits: ' + REPLACE(CONVERT(NVARCHAR(30),CAST(total_page_lock_wait_count AS MONEY), 1), N'.00', N'') - + N'; total duration: ' + - CASE WHEN total_page_lock_wait_in_ms >= 60000 THEN /*More than 1 min*/ - REPLACE(CONVERT(NVARCHAR(30),CAST((total_page_lock_wait_in_ms/60000) AS MONEY), 1), N'.00', N'') + N' minutes; ' - ELSE - REPLACE(CONVERT(NVARCHAR(30),CAST(ISNULL(total_page_lock_wait_in_ms/1000,0) AS MONEY), 1), N'.00', N'') + N' seconds; ' - END - + N'avg duration: ' + - CASE WHEN avg_page_lock_wait_in_ms >= 60000 THEN /*More than 1 min*/ - REPLACE(CONVERT(NVARCHAR(30),CAST((avg_page_lock_wait_in_ms/60000) AS MONEY), 1), N'.00', N'') + N' minutes; ' - ELSE - REPLACE(CONVERT(NVARCHAR(30),CAST(ISNULL(avg_page_lock_wait_in_ms/1000,0) AS MONEY), 1), N'.00', N'') + N' seconds; ' - END - ELSE N'' - END + - CASE WHEN total_index_lock_promotion_attempt_count > 0 THEN - N'Lock escalation attempts: ' + REPLACE(CONVERT(NVARCHAR(30),CAST(total_index_lock_promotion_attempt_count AS MONEY), 1), N'.00', N'') - + N'; Actual Escalations: ' + REPLACE(CONVERT(NVARCHAR(30),CAST(ISNULL(total_index_lock_promotion_count,0) AS MONEY), 1), N'.00', N'') +N'; ' - ELSE N'' - END + - CASE WHEN lock_escalation_desc = N'DISABLE' THEN - N'Lock escalation is disabled.' - ELSE N'' - END - END - ,'Error- NULL in computed column') - ); - - CREATE TABLE #IndexColumns - ( - [database_id] INT NOT NULL, - [schema_name] NVARCHAR(128), - [object_id] INT NOT NULL , - [index_id] INT NOT NULL , - [key_ordinal] INT NULL , - is_included_column BIT NULL , - is_descending_key BIT NULL , - [partition_ordinal] INT NULL , - column_name NVARCHAR(256) NOT NULL , - system_type_name NVARCHAR(256) NOT NULL, - max_length SMALLINT NOT NULL, - [precision] TINYINT NOT NULL, - [scale] TINYINT NOT NULL, - collation_name NVARCHAR(256) NULL, - is_nullable BIT NULL, - is_identity BIT NULL, - is_computed BIT NULL, - is_replicated BIT NULL, - is_sparse BIT NULL, - is_filestream BIT NULL, - seed_value DECIMAL(38,0) NULL, - increment_value DECIMAL(38,0) NULL , - last_value DECIMAL(38,0) NULL, - is_not_for_replication BIT NULL - ); - CREATE CLUSTERED INDEX CLIX_database_id_object_id_index_id ON #IndexColumns - (database_id, object_id, index_id); - - CREATE TABLE #MissingIndexes - ([database_id] INT NOT NULL, - [object_id] INT NOT NULL, - [database_name] NVARCHAR(128) NOT NULL , - [schema_name] NVARCHAR(128) NOT NULL , - [table_name] NVARCHAR(128), - [statement] NVARCHAR(512) NOT NULL, - magic_benefit_number AS (( user_seeks + user_scans ) * avg_total_user_cost * avg_user_impact), - avg_total_user_cost NUMERIC(29,4) NOT NULL, - avg_user_impact NUMERIC(29,1) NOT NULL, - user_seeks BIGINT NOT NULL, - user_scans BIGINT NOT NULL, - unique_compiles BIGINT NULL, - equality_columns NVARCHAR(MAX), - equality_columns_with_data_type NVARCHAR(MAX), - inequality_columns NVARCHAR(MAX), - inequality_columns_with_data_type NVARCHAR(MAX), - included_columns NVARCHAR(MAX), - included_columns_with_data_type NVARCHAR(MAX), - is_low BIT, - [index_estimated_impact] AS - REPLACE(CONVERT(NVARCHAR(256),CAST(CAST( - (user_seeks + user_scans) - AS BIGINT) AS MONEY), 1), '.00', '') + N' use' - + CASE WHEN (user_seeks + user_scans) > 1 THEN N's' ELSE N'' END - +N'; Impact: ' + CAST(avg_user_impact AS NVARCHAR(30)) - + N'%; Avg query cost: ' - + CAST(avg_total_user_cost AS NVARCHAR(30)), - [missing_index_details] AS - CASE WHEN COALESCE(equality_columns_with_data_type,equality_columns) IS NOT NULL - THEN N'EQUALITY: ' + COALESCE(CAST(equality_columns_with_data_type AS NVARCHAR(MAX)), CAST(equality_columns AS NVARCHAR(MAX))) + N' ' - ELSE N'' END + - - CASE WHEN COALESCE(inequality_columns_with_data_type,inequality_columns) IS NOT NULL - THEN N'INEQUALITY: ' + COALESCE(CAST(inequality_columns_with_data_type AS NVARCHAR(MAX)), CAST(inequality_columns AS NVARCHAR(MAX))) + N' ' - ELSE N'' END + - - CASE WHEN COALESCE(included_columns_with_data_type,included_columns) IS NOT NULL - THEN N'INCLUDE: ' + COALESCE(CAST(included_columns_with_data_type AS NVARCHAR(MAX)), CAST(included_columns AS NVARCHAR(MAX))) + N' ' - ELSE N'' END, - [create_tsql] AS N'CREATE INDEX [' - + LEFT(REPLACE(REPLACE(REPLACE(REPLACE( - ISNULL(equality_columns,N'')+ - CASE WHEN equality_columns IS NOT NULL AND inequality_columns IS NOT NULL THEN N'_' ELSE N'' END - + ISNULL(inequality_columns,''),',','') - ,'[',''),']',''),' ','_') - + CASE WHEN included_columns IS NOT NULL THEN N'_Includes' ELSE N'' END, 128) + N'] ON ' - + [statement] + N' (' + ISNULL(equality_columns,N'') - + CASE WHEN equality_columns IS NOT NULL AND inequality_columns IS NOT NULL THEN N', ' ELSE N'' END - + CASE WHEN inequality_columns IS NOT NULL THEN inequality_columns ELSE N'' END + - ') ' + CASE WHEN included_columns IS NOT NULL THEN N' INCLUDE (' + included_columns + N')' ELSE N'' END - + N' WITH (' - + N'FILLFACTOR=100, ONLINE=?, SORT_IN_TEMPDB=?, DATA_COMPRESSION=?' - + N')' - + N';' - , - [more_info] AS N'EXEC dbo.sp_BlitzIndex @DatabaseName=' + QUOTENAME([database_name],'''') + - N', @SchemaName=' + QUOTENAME([schema_name],'''') + N', @TableName=' + QUOTENAME([table_name],'''') + N';', - [sample_query_plan] XML NULL - ); - - CREATE TABLE #ForeignKeys ( - [database_id] INT NOT NULL, - [database_name] NVARCHAR(128) NOT NULL , - [schema_name] NVARCHAR(128) NOT NULL , - foreign_key_name NVARCHAR(256), - parent_object_id INT, - parent_object_name NVARCHAR(256), - referenced_object_id INT, - referenced_object_name NVARCHAR(256), - is_disabled BIT, - is_not_trusted BIT, - is_not_for_replication BIT, - parent_fk_columns NVARCHAR(MAX), - referenced_fk_columns NVARCHAR(MAX), - update_referential_action_desc NVARCHAR(16), - delete_referential_action_desc NVARCHAR(60) - ); - - CREATE TABLE #IndexCreateTsql ( - index_sanity_id INT NOT NULL, - create_tsql NVARCHAR(MAX) NOT NULL - ); - - CREATE TABLE #DatabaseList ( - DatabaseName NVARCHAR(256), - secondary_role_allow_connections_desc NVARCHAR(50) - - ); - - CREATE TABLE #PartitionCompressionInfo ( - [index_sanity_id] INT NULL, - [partition_compression_detail] NVARCHAR(4000) NULL - ); - - CREATE TABLE #Statistics ( - database_id INT NOT NULL, - database_name NVARCHAR(256) NOT NULL, - table_name NVARCHAR(128) NULL, - schema_name NVARCHAR(128) NULL, - index_name NVARCHAR(128) NULL, - column_names NVARCHAR(MAX) NULL, - statistics_name NVARCHAR(128) NULL, - last_statistics_update DATETIME NULL, - days_since_last_stats_update INT NULL, - rows BIGINT NULL, - rows_sampled BIGINT NULL, - percent_sampled DECIMAL(18, 1) NULL, - histogram_steps INT NULL, - modification_counter BIGINT NULL, - percent_modifications DECIMAL(18, 1) NULL, - modifications_before_auto_update INT 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 - ); - - CREATE TABLE #ComputedColumns - ( - index_sanity_id INT IDENTITY(1, 1) NOT NULL, - database_name NVARCHAR(128) NULL, - database_id INT NOT NULL, - table_name NVARCHAR(128) NOT NULL, - schema_name NVARCHAR(128) NOT NULL, - column_name NVARCHAR(128) NULL, - is_nullable BIT NULL, - definition NVARCHAR(MAX) NULL, - uses_database_collation BIT NOT NULL, - is_persisted BIT NOT NULL, - is_computed BIT NOT NULL, - is_function INT NOT NULL, - column_definition NVARCHAR(MAX) NULL - ); - - CREATE TABLE #TraceStatus - ( - TraceFlag NVARCHAR(10) , - status BIT , - Global BIT , - Session BIT - ); - - CREATE TABLE #TemporalTables - ( - index_sanity_id INT IDENTITY(1, 1) NOT NULL, - database_name NVARCHAR(128) NOT NULL, - database_id INT NOT NULL, - schema_name NVARCHAR(128) NOT NULL, - table_name NVARCHAR(128) NOT NULL, - history_table_name NVARCHAR(128) NOT NULL, - history_schema_name NVARCHAR(128) NOT NULL, - start_column_name NVARCHAR(128) NOT NULL, - end_column_name NVARCHAR(128) NOT NULL, - period_name NVARCHAR(128) NOT NULL - ); - - CREATE TABLE #CheckConstraints - ( - index_sanity_id INT IDENTITY(1, 1) NOT NULL, - database_name NVARCHAR(128) NULL, - database_id INT NOT NULL, - table_name NVARCHAR(128) NOT NULL, - schema_name NVARCHAR(128) NOT NULL, - constraint_name NVARCHAR(128) NULL, - is_disabled BIT NULL, - definition NVARCHAR(MAX) NULL, - uses_database_collation BIT NOT NULL, - is_not_trusted BIT NOT NULL, - is_function INT NOT NULL, - column_definition NVARCHAR(MAX) NULL - ); - - CREATE TABLE #FilteredIndexes - ( - index_sanity_id INT IDENTITY(1, 1) NOT NULL, - database_name NVARCHAR(128) NULL, - database_id INT NOT NULL, - schema_name NVARCHAR(128) NOT NULL, - table_name NVARCHAR(128) NOT NULL, - index_name NVARCHAR(128) NULL, - column_name NVARCHAR(128) NULL - ); + CREATE TABLE #FilteredIndexes + ( + index_sanity_id INT IDENTITY(1, 1) NOT NULL, + database_name NVARCHAR(128) NULL, + database_id INT NOT NULL, + schema_name NVARCHAR(128) NOT NULL, + table_name NVARCHAR(128) NOT NULL, + index_name NVARCHAR(128) NULL, + column_name NVARCHAR(128) NULL + ); CREATE TABLE #Ignore_Databases ( @@ -24635,9 +20035,12 @@ BEGIN TRY AND ColumnNamesWithDataTypes.IndexColumnType = ''Included'' ) AS included_columns_with_data_type ' - IF NOT EXISTS (SELECT * FROM sys.all_objects WHERE name = 'dm_db_missing_index_group_stats_query') + /* Github #2780 BGO Removing SQL Server 2019's new missing index sample plan because it doesn't work yet: + IF NOT EXISTS (SELECT * FROM sys.all_objects WHERE name = 'dm_db_missing_index_group_stats_query') + */ SET @dsql = @dsql + N' , NULL AS sample_query_plan ' - ELSE + /* Github #2780 BGO Removing SQL Server 2019's new missing index sample plan because it doesn't work yet: + ELSE BEGIN SET @dsql = @dsql + N' , sample_query_plan = (SELECT TOP 1 p.query_plan FROM sys.dm_db_missing_index_group_stats gs @@ -24649,6 +20052,8 @@ BEGIN TRY CROSS APPLY sys.dm_exec_query_plan(q2.plan_handle) p WHERE gs.group_handle = gs.group_handle) ' END + */ + SET @dsql = @dsql + N'FROM ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_groups ig JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_details id ON ig.index_handle = id.index_handle @@ -25871,7 +21276,7 @@ BEGIN; /* WHERE clause limits to only @ThresholdMB or larger duplicate indexes when getting all databases or using PainRelief mode */ WHERE ips.total_reserved_MB >= CASE WHEN (@GetAllDatabases = 1 OR @Mode = 0) THEN @ThresholdMB ELSE ips.total_reserved_MB END AND ip.is_primary_key = 0 - ORDER BY ip.object_id, ip.key_column_names_with_sort_order + ORDER BY ips.total_rows DESC, ip.[schema_name], ip.[object_name], ip.key_column_names_with_sort_order OPTION ( RECOMPILE ); RAISERROR('check_id 2: Keys w/ identical leading columns.', 0,1) WITH NOWAIT; @@ -25909,14 +21314,14 @@ BEGIN; di.number_dupes > 1 ) AND ip.is_primary_key = 0 - ORDER BY ip.[schema_name], ip.[object_name], ip.key_column_names, ip.include_column_names + ORDER BY ips.total_rows DESC, ip.[schema_name], ip.[object_name], ip.key_column_names, ip.include_column_names OPTION ( RECOMPILE ); ---------------------------------------- --Aggressive Indexes: Check_id 10-19 ---------------------------------------- - RAISERROR(N'check_id 11: Total lock wait time > 5 minutes (row + page) with long average waits', 0,1) WITH NOWAIT; + RAISERROR(N'check_id 11: Total lock wait time > 5 minutes (row + page)', 0,1) WITH NOWAIT; INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, secret_columns, index_usage_summary, index_size_summary ) SELECT 11 AS check_id, @@ -25945,7 +21350,7 @@ BEGIN; WHEN 9 THEN N'Indexes' ELSE N'Over-Indexing' END AS findings_group, - N'Total lock wait time > 5 minutes (row + page) with long average waits' AS finding, + N'Total lock wait time > 5 minutes (row + page)' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/AggressiveIndexes' AS URL, (i.db_schema_object_indexid + N': ' + @@ -25968,67 +21373,10 @@ BEGIN; FROM #IndexSanity AS i JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id WHERE (total_row_lock_wait_in_ms + total_page_lock_wait_in_ms) > 300000 - AND (sz.avg_page_lock_wait_in_ms + sz.avg_row_lock_wait_in_ms) > 5000 GROUP BY i.index_sanity_id, [database_name], i.db_schema_object_indexid, sz.index_lock_wait_summary, i.index_definition, i.secret_columns, i.index_usage_summary, sz.index_size_summary, sz.index_sanity_id - ORDER BY 4, [database_name], 8 + ORDER BY SUM(total_row_lock_wait_in_ms + total_page_lock_wait_in_ms) DESC, 4, [database_name], 8 OPTION ( RECOMPILE ); - RAISERROR(N'check_id 12: Total lock wait time > 5 minutes (row + page) with short average waits', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 12 AS check_id, - i.index_sanity_id, - 70 AS Priority, - N'Aggressive ' - + CASE COALESCE((SELECT SUM(1) - FROM #IndexSanity iMe - INNER JOIN #IndexSanity iOthers - ON iMe.database_id = iOthers.database_id - AND iMe.object_id = iOthers.object_id - AND iOthers.index_id > 1 - WHERE i.index_sanity_id = iMe.index_sanity_id - AND iOthers.is_hypothetical = 0 - AND iOthers.is_disabled = 0 - ), 0) - WHEN 0 THEN N'Under-Indexing' - WHEN 1 THEN N'Under-Indexing' - WHEN 2 THEN N'Under-Indexing' - WHEN 3 THEN N'Under-Indexing' - WHEN 4 THEN N'Indexes' - WHEN 5 THEN N'Indexes' - WHEN 6 THEN N'Indexes' - WHEN 7 THEN N'Indexes' - WHEN 8 THEN N'Indexes' - WHEN 9 THEN N'Indexes' - ELSE N'Over-Indexing' - END AS findings_group, - N'Total lock wait time > 5 minutes (row + page) with short average waits' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/AggressiveIndexes' AS URL, - (i.db_schema_object_indexid + N': ' + - sz.index_lock_wait_summary + N' NC indexes on table: ') COLLATE DATABASE_DEFAULT + - CAST(COALESCE((SELECT SUM(1) - FROM #IndexSanity iMe - INNER JOIN #IndexSanity iOthers - ON iMe.database_id = iOthers.database_id - AND iMe.object_id = iOthers.object_id - AND iOthers.index_id > 1 - WHERE i.index_sanity_id = iMe.index_sanity_id - AND iOthers.is_hypothetical = 0 - AND iOthers.is_disabled = 0 - ),0) - AS NVARCHAR(30)) AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - sz.index_size_summary - FROM #IndexSanity AS i - JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id - WHERE (total_row_lock_wait_in_ms + total_page_lock_wait_in_ms) > 300000 - AND (sz.avg_page_lock_wait_in_ms + sz.avg_row_lock_wait_in_ms) < 5000 - GROUP BY i.index_sanity_id, [database_name], i.db_schema_object_indexid, sz.index_lock_wait_summary, i.index_definition, i.secret_columns, i.index_usage_summary, sz.index_size_summary, sz.index_sanity_id - ORDER BY 4, [database_name], 8 - OPTION ( RECOMPILE ); ---------------------------------------- @@ -28565,7 +23913,7 @@ BEGIN SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.0', @VersionDate = '20210117'; +SELECT @Version = '8.01', @VersionDate = '20210222'; IF(@VersionCheckMode = 1) @@ -30285,7 +25633,7 @@ BEGIN SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.0', @VersionDate = '20210117'; + SELECT @Version = '8.01', @VersionDate = '20210222'; IF(@VersionCheckMode = 1) BEGIN @@ -31526,6 +26874,7 @@ DELETE FROM dbo.SqlServerVersions; INSERT INTO dbo.SqlServerVersions (MajorVersionNumber, MinorVersionNumber, Branch, [Url], ReleaseDate, MainstreamSupportEndDate, ExtendedSupportEndDate, MajorVersionName, MinorVersionName) VALUES + (15, 4102, 'CU9', 'https://support.microsoft.com/en-us/help/5000642', '2021-02-11', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 9 '), (15, 4073, 'CU8 GDR', 'https://support.microsoft.com/en-us/help/4583459', '2021-01-12', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 8 GDR '), (15, 4073, 'CU8', 'https://support.microsoft.com/en-us/help/4577194', '2020-10-01', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 8 '), (15, 4063, 'CU7', 'https://support.microsoft.com/en-us/help/4570012', '2020-09-02', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 7 '), @@ -31561,6 +26910,7 @@ VALUES (14, 3008, 'RTM CU2', 'https://support.microsoft.com/en-us/help/4052574', '2017-11-28', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 2'), (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 '), + (13, 5882, 'SP2 CU16', 'https://support.microsoft.com/en-us/help/5000645', '2021-02-11', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 16'), (13, 5865, 'SP2 CU15 GDR', 'https://support.microsoft.com/en-us/help/4583461', '2021-01-12', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 15 GDR'), (13, 5850, 'SP2 CU15', 'https://support.microsoft.com/en-us/help/4577775', '2020-09-28', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 15'), (13, 5830, 'SP2 CU14', 'https://support.microsoft.com/en-us/help/4564903', '2020-08-06', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 14'), @@ -31864,3 +27214,4706 @@ VALUES (10, 1600, 'RTM ', '', '2008-08-06', '2014-07-08', '2019-07-09', 'SQL Server 2008', 'RTM ') ; GO +IF OBJECT_ID('dbo.sp_BlitzFirst') IS NULL + EXEC ('CREATE PROCEDURE dbo.sp_BlitzFirst AS RETURN 0;'); +GO + + +ALTER PROCEDURE [dbo].[sp_BlitzFirst] + @LogMessage NVARCHAR(4000) = NULL , + @Help TINYINT = 0 , + @AsOf DATETIMEOFFSET = NULL , + @ExpertMode TINYINT = 0 , + @Seconds INT = 5 , + @OutputType VARCHAR(20) = 'TABLE' , + @OutputServerName NVARCHAR(256) = NULL , + @OutputDatabaseName NVARCHAR(256) = NULL , + @OutputSchemaName NVARCHAR(256) = NULL , + @OutputTableName NVARCHAR(256) = NULL , + @OutputTableNameFileStats NVARCHAR(256) = NULL , + @OutputTableNamePerfmonStats NVARCHAR(256) = NULL , + @OutputTableNameWaitStats NVARCHAR(256) = NULL , + @OutputTableNameBlitzCache NVARCHAR(256) = NULL , + @OutputTableNameBlitzWho NVARCHAR(256) = NULL , + @OutputTableRetentionDays TINYINT = 7 , + @OutputXMLasNVARCHAR TINYINT = 0 , + @FilterPlansByDatabase VARCHAR(MAX) = NULL , + @CheckProcedureCache TINYINT = 0 , + @CheckServerInfo TINYINT = 1 , + @FileLatencyThresholdMS INT = 100 , + @SinceStartup TINYINT = 0 , + @ShowSleepingSPIDs TINYINT = 0 , + @BlitzCacheSkipAnalysis BIT = 1 , + @MemoryGrantThresholdPct DECIMAL(5,2) = 15.00, + @LogMessageCheckID INT = 38, + @LogMessagePriority TINYINT = 1, + @LogMessageFindingsGroup VARCHAR(50) = 'Logged Message', + @LogMessageFinding VARCHAR(200) = 'Logged from sp_BlitzFirst', + @LogMessageURL VARCHAR(200) = '', + @LogMessageCheckDate DATETIMEOFFSET = NULL, + @Debug BIT = 0, + @Version VARCHAR(30) = NULL OUTPUT, + @VersionDate DATETIME = NULL OUTPUT, + @VersionCheckMode BIT = 0 + WITH EXECUTE AS CALLER, RECOMPILE +AS +BEGIN +SET NOCOUNT ON; +SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + +SELECT @Version = '8.01', @VersionDate = '20210222'; + +IF(@VersionCheckMode = 1) +BEGIN + RETURN; +END; + +IF @Help = 1 +BEGIN +PRINT ' +sp_BlitzFirst from http://FirstResponderKit.org + +This script gives you a prioritized list of why your SQL Server is slow right now. + +This is not an overall health check - for that, check out sp_Blitz. + +To learn more, visit http://FirstResponderKit.org where you can download new +versions for free, watch training videos on how it works, get more info on +the findings, contribute your own code, and more. + +Known limitations of this version: + - Only Microsoft-supported versions of SQL Server. Sorry, 2005 and 2000. It + may work just fine on 2005, and if it does, hug your parents. Just don''t + file support issues if it breaks. + - If a temp table called #CustomPerfmonCounters exists for any other session, + but not our session, this stored proc will fail with an error saying the + temp table #CustomPerfmonCounters does not exist. + - @OutputServerName is not functional yet. + - If @OutputDatabaseName, SchemaName, TableName, etc are quoted with brackets, + the write to table may silently fail. Look, I never said I was good at this. + +Unknown limitations of this version: + - None. Like Zombo.com, the only limit is yourself. + +Changes - for the full list of improvements and fixes in this version, see: +https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/ + + +MIT License + +Copyright (c) 2021 Brent Ozar Unlimited + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +'; +RETURN; +END; /* @Help = 1 */ + +RAISERROR('Setting up configuration variables',10,1) WITH NOWAIT; +DECLARE @StringToExecute NVARCHAR(MAX), + @ParmDefinitions NVARCHAR(4000), + @Parm1 NVARCHAR(4000), + @OurSessionID INT, + @LineFeed NVARCHAR(10), + @StockWarningHeader NVARCHAR(MAX) = N'', + @StockWarningFooter NVARCHAR(MAX) = N'', + @StockDetailsHeader NVARCHAR(MAX) = N'', + @StockDetailsFooter NVARCHAR(MAX) = N'', + @StartSampleTime DATETIMEOFFSET, + @FinishSampleTime DATETIMEOFFSET, + @FinishSampleTimeWaitFor DATETIME, + @AsOf1 DATETIMEOFFSET, + @AsOf2 DATETIMEOFFSET, + @ServiceName sysname, + @OutputTableNameFileStats_View NVARCHAR(256), + @OutputTableNamePerfmonStats_View NVARCHAR(256), + @OutputTableNamePerfmonStatsActuals_View NVARCHAR(256), + @OutputTableNameWaitStats_View NVARCHAR(256), + @OutputTableNameWaitStats_Categories NVARCHAR(256), + @OutputTableCleanupDate DATE, + @ObjectFullName NVARCHAR(2000), + @BlitzWho NVARCHAR(MAX) = N'EXEC dbo.sp_BlitzWho @ShowSleepingSPIDs = ' + CONVERT(NVARCHAR(1), @ShowSleepingSPIDs) + N';', + @BlitzCacheMinutesBack INT, + @UnquotedOutputServerName NVARCHAR(256) = @OutputServerName , + @UnquotedOutputDatabaseName NVARCHAR(256) = @OutputDatabaseName , + @UnquotedOutputSchemaName NVARCHAR(256) = @OutputSchemaName , + @LocalServerName NVARCHAR(128) = CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128)), + @dm_exec_query_statistics_xml BIT = 0; + +/* Sanitize our inputs */ +SELECT + @OutputTableNameFileStats_View = QUOTENAME(@OutputTableNameFileStats + '_Deltas'), + @OutputTableNamePerfmonStats_View = QUOTENAME(@OutputTableNamePerfmonStats + '_Deltas'), + @OutputTableNamePerfmonStatsActuals_View = QUOTENAME(@OutputTableNamePerfmonStats + '_Actuals'), + @OutputTableNameWaitStats_View = QUOTENAME(@OutputTableNameWaitStats + '_Deltas'), + @OutputTableNameWaitStats_Categories = QUOTENAME(@OutputTableNameWaitStats + '_Categories'); + +SELECT + @OutputDatabaseName = QUOTENAME(@OutputDatabaseName), + @OutputSchemaName = QUOTENAME(@OutputSchemaName), + @OutputTableName = QUOTENAME(@OutputTableName), + @OutputTableNameFileStats = QUOTENAME(@OutputTableNameFileStats), + @OutputTableNamePerfmonStats = QUOTENAME(@OutputTableNamePerfmonStats), + @OutputTableNameWaitStats = QUOTENAME(@OutputTableNameWaitStats), + @OutputTableCleanupDate = CAST( (DATEADD(DAY, -1 * @OutputTableRetentionDays, GETDATE() ) ) AS DATE), + /* @OutputTableNameBlitzCache = QUOTENAME(@OutputTableNameBlitzCache), We purposely don't sanitize this because sp_BlitzCache will */ + /* @OutputTableNameBlitzWho = QUOTENAME(@OutputTableNameBlitzWho), We purposely don't sanitize this because sp_BlitzWho will */ + @LineFeed = CHAR(13) + CHAR(10), + @OurSessionID = @@SPID, + @OutputType = UPPER(@OutputType); + +IF(@OutputType = 'NONE' AND (@OutputTableName IS NULL OR @OutputSchemaName IS NULL OR @OutputDatabaseName IS NULL)) +BEGIN + RAISERROR('This procedure should be called with a value for all @Output* parameters, as @OutputType is set to NONE',12,1); + RETURN; +END; + +IF UPPER(@OutputType) LIKE 'TOP 10%' SET @OutputType = 'Top10'; +IF @OutputType = 'Top10' SET @SinceStartup = 1; + +/* Logged Message - CheckID 38 */ +IF @LogMessage IS NOT NULL + BEGIN + + RAISERROR('Saving LogMessage to table',10,1) WITH NOWAIT; + + /* Try to set the output table parameters if they don't exist */ + IF @OutputSchemaName IS NULL AND @OutputTableName IS NULL AND @OutputDatabaseName IS NULL + BEGIN + SET @OutputSchemaName = N'[dbo]'; + SET @OutputTableName = N'[BlitzFirst]'; + + /* Look for the table in the current database */ + SELECT TOP 1 @OutputDatabaseName = QUOTENAME(TABLE_CATALOG) + FROM INFORMATION_SCHEMA.TABLES + WHERE TABLE_SCHEMA = 'dbo' AND TABLE_NAME = 'BlitzFirst'; + + IF @OutputDatabaseName IS NULL AND EXISTS (SELECT * FROM sys.databases WHERE name = 'DBAtools') + SET @OutputDatabaseName = '[DBAtools]'; + + END; + + IF @OutputDatabaseName IS NULL OR @OutputSchemaName IS NULL OR @OutputTableName IS NULL + OR NOT EXISTS ( SELECT * + FROM sys.databases + WHERE QUOTENAME([name]) = @OutputDatabaseName) + BEGIN + RAISERROR('We have a hard time logging a message without a valid @OutputDatabaseName, @OutputSchemaName, and @OutputTableName to log it to.', 0, 1) WITH NOWAIT; + RETURN; + END; + IF @LogMessageCheckDate IS NULL + SET @LogMessageCheckDate = SYSDATETIMEOFFSET(); + SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' + + @OutputDatabaseName + + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' + + @OutputSchemaName + ''') INSERT ' + + @OutputDatabaseName + '.' + + @OutputSchemaName + '.' + + @OutputTableName + + ' (ServerName, CheckDate, CheckID, Priority, FindingsGroup, Finding, Details, URL) VALUES( ' + + ' @SrvName, @LogMessageCheckDate, @LogMessageCheckID, @LogMessagePriority, @LogMessageFindingsGroup, @LogMessageFinding, @LogMessage, @LogMessageURL)'; + + EXECUTE sp_executesql @StringToExecute, + N'@SrvName NVARCHAR(128), @LogMessageCheckID INT, @LogMessagePriority TINYINT, @LogMessageFindingsGroup VARCHAR(50), @LogMessageFinding VARCHAR(200), @LogMessage NVARCHAR(4000), @LogMessageCheckDate DATETIMEOFFSET, @LogMessageURL VARCHAR(200)', + @LocalServerName, @LogMessageCheckID, @LogMessagePriority, @LogMessageFindingsGroup, @LogMessageFinding, @LogMessage, @LogMessageCheckDate, @LogMessageURL; + + RAISERROR('LogMessage saved to table. We have made a note of your activity. Keep up the good work.',10,1) WITH NOWAIT; + + RETURN; + END; + +IF @SinceStartup = 1 + SELECT @Seconds = 0, @ExpertMode = 1; + + +IF @OutputType = 'SCHEMA' +BEGIN + SELECT FieldList = '[Priority] TINYINT, [FindingsGroup] VARCHAR(50), [Finding] VARCHAR(200), [URL] VARCHAR(200), [Details] NVARCHAR(4000), [HowToStopIt] NVARCHAR(MAX), [QueryPlan] XML, [QueryText] NVARCHAR(MAX)'; + +END; +ELSE IF @AsOf IS NOT NULL AND @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @OutputTableName IS NOT NULL +BEGIN + /* They want to look into the past. */ + SET @AsOf1= DATEADD(mi, -15, @AsOf); + SET @AsOf2= DATEADD(mi, +15, @AsOf); + + SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' + + @OutputDatabaseName + + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' + + @OutputSchemaName + ''') SELECT CheckDate, [Priority], [FindingsGroup], [Finding], [URL], CAST([Details] AS [XML]) AS Details,' + + '[HowToStopIt], [CheckID], [StartTime], [LoginName], [NTUserName], [OriginalLoginName], [ProgramName], [HostName], [DatabaseID],' + + '[DatabaseName], [OpenTransactionCount], [QueryPlan], [QueryText] FROM ' + + @OutputDatabaseName + '.' + + @OutputSchemaName + '.' + + @OutputTableName + + ' WHERE CheckDate >= @AsOf1' + + ' AND CheckDate <= @AsOf2' + + ' /*ORDER BY CheckDate, Priority , FindingsGroup , Finding , Details*/;'; + EXEC sp_executesql @StringToExecute, + N'@AsOf1 DATETIMEOFFSET, @AsOf2 DATETIMEOFFSET', + @AsOf1, @AsOf2 + + +END; /* IF @AsOf IS NOT NULL AND @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @OutputTableName IS NOT NULL */ +ELSE IF @LogMessage IS NULL /* IF @OutputType = 'SCHEMA' */ +BEGIN + /* What's running right now? This is the first and last result set. */ + IF @SinceStartup = 0 AND @Seconds > 0 AND @ExpertMode = 1 AND @OutputType <> 'NONE' + BEGIN + IF OBJECT_ID('master.dbo.sp_BlitzWho') IS NULL AND OBJECT_ID('dbo.sp_BlitzWho') IS NULL + BEGIN + PRINT N'sp_BlitzWho is not installed in the current database_files. You can get a copy from http://FirstResponderKit.org'; + END; + ELSE + BEGIN + EXEC (@BlitzWho); + END; + END; /* IF @SinceStartup = 0 AND @Seconds > 0 AND @ExpertMode = 1 AND @OutputType <> 'NONE' - What's running right now? This is the first and last result set. */ + + /* Set start/finish times AFTER sp_BlitzWho runs. For more info: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2244 */ + IF @Seconds = 0 AND SERVERPROPERTY('Edition') = 'SQL Azure' + WITH WaitTimes AS ( + SELECT wait_type, wait_time_ms, + NTILE(3) OVER(ORDER BY wait_time_ms) AS grouper + FROM sys.dm_os_wait_stats w + WHERE wait_type IN ('DIRTY_PAGE_POLL','HADR_FILESTREAM_IOMGR_IOCOMPLETION','LAZYWRITER_SLEEP', + 'LOGMGR_QUEUE','REQUEST_FOR_DEADLOCK_SEARCH','XE_TIMER_EVENT') + ) + SELECT @StartSampleTime = DATEADD(mi, AVG(-wait_time_ms / 1000 / 60), SYSDATETIMEOFFSET()), @FinishSampleTime = SYSDATETIMEOFFSET() + FROM WaitTimes + WHERE grouper = 2; + ELSE IF @Seconds = 0 AND SERVERPROPERTY('Edition') <> 'SQL Azure' + SELECT @StartSampleTime = DATEADD(MINUTE,DATEDIFF(MINUTE, GETDATE(), GETUTCDATE()),create_date) , @FinishSampleTime = SYSDATETIMEOFFSET() + FROM sys.databases + WHERE database_id = 2; + ELSE + SELECT @StartSampleTime = SYSDATETIMEOFFSET(), + @FinishSampleTime = DATEADD(ss, @Seconds, SYSDATETIMEOFFSET()), + @FinishSampleTimeWaitFor = DATEADD(ss, @Seconds, GETDATE()); + + + RAISERROR('Now starting diagnostic analysis',10,1) WITH NOWAIT; + + /* + We start by creating #BlitzFirstResults. It's a temp table that will store + the results from our checks. Throughout the rest of this stored procedure, + we're running a series of checks looking for dangerous things inside the SQL + Server. When we find a problem, we insert rows into the temp table. At the + end, we return these results to the end user. + + #BlitzFirstResults has a CheckID field, but there's no Check table. As we do + checks, we insert data into this table, and we manually put in the CheckID. + We (Brent Ozar Unlimited) maintain a list of the checks by ID#. You can + download that from http://FirstResponderKit.org if you want to build + a tool that relies on the output of sp_BlitzFirst. + */ + + + IF OBJECT_ID('tempdb..#BlitzFirstResults') IS NOT NULL + DROP TABLE #BlitzFirstResults; + CREATE TABLE #BlitzFirstResults + ( + ID INT IDENTITY(1, 1) PRIMARY KEY CLUSTERED, + CheckID INT NOT NULL, + Priority TINYINT NOT NULL, + FindingsGroup VARCHAR(50) NOT NULL, + Finding VARCHAR(200) NOT NULL, + URL VARCHAR(200) NULL, + Details NVARCHAR(MAX) NULL, + HowToStopIt NVARCHAR(MAX) NULL, + QueryPlan [XML] NULL, + QueryText NVARCHAR(MAX) NULL, + StartTime DATETIMEOFFSET NULL, + LoginName NVARCHAR(128) NULL, + NTUserName NVARCHAR(128) NULL, + OriginalLoginName NVARCHAR(128) NULL, + ProgramName NVARCHAR(128) NULL, + HostName NVARCHAR(128) NULL, + DatabaseID INT NULL, + DatabaseName NVARCHAR(128) NULL, + OpenTransactionCount INT NULL, + QueryStatsNowID INT NULL, + QueryStatsFirstID INT NULL, + PlanHandle VARBINARY(64) NULL, + DetailsInt INT NULL, + QueryHash BINARY(8) + ); + + IF OBJECT_ID('tempdb..#WaitStats') IS NOT NULL + DROP TABLE #WaitStats; + CREATE TABLE #WaitStats (Pass TINYINT NOT NULL, wait_type NVARCHAR(60), wait_time_ms BIGINT, signal_wait_time_ms BIGINT, waiting_tasks_count BIGINT, SampleTime DATETIMEOFFSET); + + IF OBJECT_ID('tempdb..#FileStats') IS NOT NULL + DROP TABLE #FileStats; + CREATE TABLE #FileStats ( + ID INT IDENTITY(1, 1) PRIMARY KEY CLUSTERED, + Pass TINYINT NOT NULL, + SampleTime DATETIMEOFFSET NOT NULL, + DatabaseID INT NOT NULL, + FileID INT NOT NULL, + DatabaseName NVARCHAR(256) , + FileLogicalName NVARCHAR(256) , + TypeDesc NVARCHAR(60) , + SizeOnDiskMB BIGINT , + io_stall_read_ms BIGINT , + num_of_reads BIGINT , + bytes_read BIGINT , + io_stall_write_ms BIGINT , + num_of_writes BIGINT , + bytes_written BIGINT, + PhysicalName NVARCHAR(520) , + avg_stall_read_ms INT , + avg_stall_write_ms INT + ); + + IF OBJECT_ID('tempdb..#QueryStats') IS NOT NULL + DROP TABLE #QueryStats; + CREATE TABLE #QueryStats ( + ID INT IDENTITY(1, 1) PRIMARY KEY CLUSTERED, + Pass INT NOT NULL, + SampleTime DATETIMEOFFSET NOT NULL, + [sql_handle] VARBINARY(64), + statement_start_offset INT, + statement_end_offset INT, + plan_generation_num BIGINT, + plan_handle VARBINARY(64), + execution_count BIGINT, + total_worker_time BIGINT, + total_physical_reads BIGINT, + total_logical_writes BIGINT, + total_logical_reads BIGINT, + total_clr_time BIGINT, + total_elapsed_time BIGINT, + creation_time DATETIMEOFFSET, + query_hash BINARY(8), + query_plan_hash BINARY(8), + Points TINYINT + ); + + IF OBJECT_ID('tempdb..#PerfmonStats') IS NOT NULL + DROP TABLE #PerfmonStats; + CREATE TABLE #PerfmonStats ( + ID INT IDENTITY(1, 1) PRIMARY KEY CLUSTERED, + Pass TINYINT NOT NULL, + SampleTime DATETIMEOFFSET NOT NULL, + [object_name] NVARCHAR(128) NOT NULL, + [counter_name] NVARCHAR(128) NOT NULL, + [instance_name] NVARCHAR(128) NULL, + [cntr_value] BIGINT NULL, + [cntr_type] INT NOT NULL, + [value_delta] BIGINT NULL, + [value_per_second] DECIMAL(18,2) NULL + ); + + IF OBJECT_ID('tempdb..#PerfmonCounters') IS NOT NULL + DROP TABLE #PerfmonCounters; + CREATE TABLE #PerfmonCounters ( + ID INT IDENTITY(1, 1) PRIMARY KEY CLUSTERED, + [object_name] NVARCHAR(128) NOT NULL, + [counter_name] NVARCHAR(128) NOT NULL, + [instance_name] NVARCHAR(128) NULL + ); + + IF OBJECT_ID('tempdb..#FilterPlansByDatabase') IS NOT NULL + DROP TABLE #FilterPlansByDatabase; + CREATE TABLE #FilterPlansByDatabase (DatabaseID INT PRIMARY KEY CLUSTERED); + + IF OBJECT_ID('tempdb..##WaitCategories') IS NULL + BEGIN + /* We reuse this one by default rather than recreate it every time. */ + CREATE TABLE ##WaitCategories + ( + WaitType NVARCHAR(60) PRIMARY KEY CLUSTERED, + WaitCategory NVARCHAR(128) NOT NULL, + Ignorable BIT DEFAULT 0 + ); + END; /* IF OBJECT_ID('tempdb..##WaitCategories') IS NULL */ + + IF OBJECT_ID ('tempdb..#checkversion') IS NOT NULL + DROP TABLE #checkversion; + CREATE TABLE #checkversion ( + version NVARCHAR(128), + common_version AS SUBSTRING(version, 1, CHARINDEX('.', version) + 1 ), + major AS PARSENAME(CONVERT(VARCHAR(32), version), 4), + minor AS PARSENAME(CONVERT(VARCHAR(32), version), 3), + build AS PARSENAME(CONVERT(VARCHAR(32), version), 2), + revision AS PARSENAME(CONVERT(VARCHAR(32), version), 1) + ); + + IF 527 <> (SELECT COALESCE(SUM(1),0) FROM ##WaitCategories) + BEGIN + TRUNCATE TABLE ##WaitCategories; + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('ASYNC_IO_COMPLETION','Other Disk IO',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('ASYNC_NETWORK_IO','Network IO',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BACKUPIO','Other Disk IO',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_CONNECTION_RECEIVE_TASK','Service Broker',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_DISPATCHER','Service Broker',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_ENDPOINT_STATE_MUTEX','Service Broker',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_EVENTHANDLER','Service Broker',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_FORWARDER','Service Broker',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_INIT','Service Broker',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_MASTERSTART','Service Broker',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_RECEIVE_WAITFOR','User Wait',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_REGISTERALLENDPOINTS','Service Broker',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_SERVICE','Service Broker',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_SHUTDOWN','Service Broker',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_START','Service Broker',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_TASK_SHUTDOWN','Service Broker',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_TASK_STOP','Service Broker',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_TASK_SUBMIT','Service Broker',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_TO_FLUSH','Service Broker',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_TRANSMISSION_OBJECT','Service Broker',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_TRANSMISSION_TABLE','Service Broker',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_TRANSMISSION_WORK','Service Broker',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_TRANSMITTER','Service Broker',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CHECKPOINT_QUEUE','Idle',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CHKPT','Tran Log IO',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CLR_AUTO_EVENT','SQL CLR',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CLR_CRST','SQL CLR',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CLR_JOIN','SQL CLR',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CLR_MANUAL_EVENT','SQL CLR',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CLR_MEMORY_SPY','SQL CLR',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CLR_MONITOR','SQL CLR',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CLR_RWLOCK_READER','SQL CLR',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CLR_RWLOCK_WRITER','SQL CLR',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CLR_SEMAPHORE','SQL CLR',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CLR_TASK_START','SQL CLR',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CLRHOST_STATE_ACCESS','SQL CLR',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CMEMPARTITIONED','Memory',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CMEMTHREAD','Memory',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CXPACKET','Parallelism',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CXCONSUMER','Parallelism',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DBMIRROR_DBM_EVENT','Mirroring',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DBMIRROR_DBM_MUTEX','Mirroring',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DBMIRROR_EVENTS_QUEUE','Mirroring',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DBMIRROR_SEND','Mirroring',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DBMIRROR_WORKER_QUEUE','Mirroring',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DBMIRRORING_CMD','Mirroring',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DIRTY_PAGE_POLL','Other',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DIRTY_PAGE_TABLE_LOCK','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DISPATCHER_QUEUE_SEMAPHORE','Other',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DPT_ENTRY_LOCK','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DTC','Transaction',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DTC_ABORT_REQUEST','Transaction',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DTC_RESOLVE','Transaction',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DTC_STATE','Transaction',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DTC_TMDOWN_REQUEST','Transaction',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DTC_WAITFOR_OUTCOME','Transaction',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DTCNEW_ENLIST','Transaction',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DTCNEW_PREPARE','Transaction',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DTCNEW_RECOVERY','Transaction',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DTCNEW_TM','Transaction',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DTCNEW_TRANSACTION_ENLISTMENT','Transaction',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DTCPNTSYNC','Transaction',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('EE_PMOLOCK','Memory',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('EXCHANGE','Parallelism',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('EXTERNAL_SCRIPT_NETWORK_IOF','Network IO',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FCB_REPLICA_READ','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FCB_REPLICA_WRITE','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_COMPROWSET_RWLOCK','Full Text Search',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_IFTS_RWLOCK','Full Text Search',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_IFTS_SCHEDULER_IDLE_WAIT','Idle',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_IFTSHC_MUTEX','Full Text Search',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_IFTSISM_MUTEX','Full Text Search',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_MASTER_MERGE','Full Text Search',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_MASTER_MERGE_COORDINATOR','Full Text Search',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_METADATA_MUTEX','Full Text Search',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_PROPERTYLIST_CACHE','Full Text Search',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_RESTART_CRAWL','Full Text Search',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FULLTEXT GATHERER','Full Text Search',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_AG_MUTEX','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_AR_CRITICAL_SECTION_ENTRY','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_AR_MANAGER_MUTEX','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_AR_UNLOAD_COMPLETED','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_ARCONTROLLER_NOTIFICATIONS_SUBSCRIBER_LIST','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_BACKUP_BULK_LOCK','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_BACKUP_QUEUE','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_CLUSAPI_CALL','Replication',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_COMPRESSED_CACHE_SYNC','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_CONNECTIVITY_INFO','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_DATABASE_FLOW_CONTROL','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_DATABASE_VERSIONING_STATE','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_DATABASE_WAIT_FOR_RECOVERY','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_DATABASE_WAIT_FOR_RESTART','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_DATABASE_WAIT_FOR_TRANSITION_TO_VERSIONING','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_DB_COMMAND','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_DB_OP_COMPLETION_SYNC','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_DB_OP_START_SYNC','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_DBR_SUBSCRIBER','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_DBR_SUBSCRIBER_FILTER_LIST','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_DBSEEDING','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_DBSEEDING_LIST','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_DBSTATECHANGE_SYNC','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_FABRIC_CALLBACK','Replication',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_FILESTREAM_BLOCK_FLUSH','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_FILESTREAM_FILE_CLOSE','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_FILESTREAM_FILE_REQUEST','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_FILESTREAM_IOMGR','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_FILESTREAM_IOMGR_IOCOMPLETION','Replication',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_FILESTREAM_MANAGER','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_FILESTREAM_PREPROC','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_GROUP_COMMIT','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_LOGCAPTURE_SYNC','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_LOGCAPTURE_WAIT','Replication',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_LOGPROGRESS_SYNC','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_NOTIFICATION_DEQUEUE','Replication',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_NOTIFICATION_WORKER_EXCLUSIVE_ACCESS','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_NOTIFICATION_WORKER_STARTUP_SYNC','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_NOTIFICATION_WORKER_TERMINATION_SYNC','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_PARTNER_SYNC','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_READ_ALL_NETWORKS','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_RECOVERY_WAIT_FOR_CONNECTION','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_RECOVERY_WAIT_FOR_UNDO','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_REPLICAINFO_SYNC','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_SEEDING_CANCELLATION','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_SEEDING_FILE_LIST','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_SEEDING_LIMIT_BACKUPS','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_SEEDING_SYNC_COMPLETION','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_SEEDING_TIMEOUT_TASK','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_SEEDING_WAIT_FOR_COMPLETION','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_SYNC_COMMIT','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_SYNCHRONIZING_THROTTLE','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_TDS_LISTENER_SYNC','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_TDS_LISTENER_SYNC_PROCESSING','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_THROTTLE_LOG_RATE_GOVERNOR','Log Rate Governor',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_TIMER_TASK','Replication',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_TRANSPORT_DBRLIST','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_TRANSPORT_FLOW_CONTROL','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_TRANSPORT_SESSION','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_WORK_POOL','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_WORK_QUEUE','Replication',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_XRF_STACK_ACCESS','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('INSTANCE_LOG_RATE_GOVERNOR','Log Rate Governor',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('IO_COMPLETION','Other Disk IO',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('IO_QUEUE_LIMIT','Other Disk IO',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('IO_RETRY','Other Disk IO',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LATCH_DT','Latch',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LATCH_EX','Latch',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LATCH_KP','Latch',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LATCH_NL','Latch',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LATCH_SH','Latch',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LATCH_UP','Latch',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LAZYWRITER_SLEEP','Idle',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_BU','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_BU_ABORT_BLOCKERS','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_BU_LOW_PRIORITY','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_IS','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_IS_ABORT_BLOCKERS','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_IS_LOW_PRIORITY','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_IU','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_IU_ABORT_BLOCKERS','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_IU_LOW_PRIORITY','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_IX','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_IX_ABORT_BLOCKERS','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_IX_LOW_PRIORITY','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RIn_NL','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RIn_NL_ABORT_BLOCKERS','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RIn_NL_LOW_PRIORITY','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RIn_S','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RIn_S_ABORT_BLOCKERS','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RIn_S_LOW_PRIORITY','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RIn_U','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RIn_U_ABORT_BLOCKERS','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RIn_U_LOW_PRIORITY','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RIn_X','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RIn_X_ABORT_BLOCKERS','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RIn_X_LOW_PRIORITY','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RS_S','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RS_S_ABORT_BLOCKERS','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RS_S_LOW_PRIORITY','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RS_U','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RS_U_ABORT_BLOCKERS','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RS_U_LOW_PRIORITY','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RX_S','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RX_S_ABORT_BLOCKERS','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RX_S_LOW_PRIORITY','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RX_U','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RX_U_ABORT_BLOCKERS','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RX_U_LOW_PRIORITY','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RX_X','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RX_X_ABORT_BLOCKERS','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RX_X_LOW_PRIORITY','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_S','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_S_ABORT_BLOCKERS','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_S_LOW_PRIORITY','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_SCH_M','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_SCH_M_ABORT_BLOCKERS','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_SCH_M_LOW_PRIORITY','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_SCH_S','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_SCH_S_ABORT_BLOCKERS','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_SCH_S_LOW_PRIORITY','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_SIU','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_SIU_ABORT_BLOCKERS','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_SIU_LOW_PRIORITY','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_SIX','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_SIX_ABORT_BLOCKERS','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_SIX_LOW_PRIORITY','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_U','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_U_ABORT_BLOCKERS','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_U_LOW_PRIORITY','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_UIX','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_UIX_ABORT_BLOCKERS','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_UIX_LOW_PRIORITY','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_X','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_X_ABORT_BLOCKERS','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_X_LOW_PRIORITY','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LOG_RATE_GOVERNOR','Tran Log IO',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LOGBUFFER','Tran Log IO',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LOGMGR','Tran Log IO',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LOGMGR_FLUSH','Tran Log IO',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LOGMGR_PMM_LOG','Tran Log IO',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LOGMGR_QUEUE','Idle',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LOGMGR_RESERVE_APPEND','Tran Log IO',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('MEMORY_ALLOCATION_EXT','Memory',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('MEMORY_GRANT_UPDATE','Memory',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('MSQL_XACT_MGR_MUTEX','Transaction',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('MSQL_XACT_MUTEX','Transaction',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('MSSEARCH','Full Text Search',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('NET_WAITFOR_PACKET','Network IO',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('ONDEMAND_TASK_QUEUE','Idle',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PAGEIOLATCH_DT','Buffer IO',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PAGEIOLATCH_EX','Buffer IO',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PAGEIOLATCH_KP','Buffer IO',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PAGEIOLATCH_NL','Buffer IO',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PAGEIOLATCH_SH','Buffer IO',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PAGEIOLATCH_UP','Buffer IO',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PAGELATCH_DT','Buffer Latch',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PAGELATCH_EX','Buffer Latch',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PAGELATCH_KP','Buffer Latch',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PAGELATCH_NL','Buffer Latch',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PAGELATCH_SH','Buffer Latch',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PAGELATCH_UP','Buffer Latch',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PARALLEL_REDO_DRAIN_WORKER','Replication',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PARALLEL_REDO_FLOW_CONTROL','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PARALLEL_REDO_LOG_CACHE','Replication',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PARALLEL_REDO_TRAN_LIST','Replication',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PARALLEL_REDO_TRAN_TURN','Replication',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PARALLEL_REDO_WORKER_SYNC','Replication',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PARALLEL_REDO_WORKER_WAIT_WORK','Replication',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('POOL_LOG_RATE_GOVERNOR','Log Rate Governor',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_ABR','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_CLOSEBACKUPMEDIA','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_CLOSEBACKUPTAPE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_CLOSEBACKUPVDIDEVICE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_CLUSAPI_CLUSTERRESOURCECONTROL','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_COCREATEINSTANCE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_COGETCLASSOBJECT','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_CREATEACCESSOR','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_DELETEROWS','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_GETCOMMANDTEXT','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_GETDATA','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_GETNEXTROWS','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_GETRESULT','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_GETROWSBYBOOKMARK','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_LBFLUSH','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_LBLOCKREGION','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_LBREADAT','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_LBSETSIZE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_LBSTAT','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_LBUNLOCKREGION','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_LBWRITEAT','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_QUERYINTERFACE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_RELEASE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_RELEASEACCESSOR','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_RELEASEROWS','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_RELEASESESSION','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_RESTARTPOSITION','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_SEQSTRMREAD','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_SEQSTRMREADANDWRITE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_SETDATAFAILURE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_SETPARAMETERINFO','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_SETPARAMETERPROPERTIES','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_STRMLOCKREGION','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_STRMSEEKANDREAD','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_STRMSEEKANDWRITE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_STRMSETSIZE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_STRMSTAT','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_STRMUNLOCKREGION','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_CONSOLEWRITE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_CREATEPARAM','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DEBUG','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DFSADDLINK','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DFSLINKEXISTCHECK','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DFSLINKHEALTHCHECK','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DFSREMOVELINK','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DFSREMOVEROOT','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DFSROOTFOLDERCHECK','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DFSROOTINIT','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DFSROOTSHARECHECK','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DTC_ABORT','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DTC_ABORTREQUESTDONE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DTC_BEGINTRANSACTION','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DTC_COMMITREQUESTDONE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DTC_ENLIST','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DTC_PREPAREREQUESTDONE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_FILESIZEGET','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_FSAOLEDB_ABORTTRANSACTION','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_FSAOLEDB_COMMITTRANSACTION','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_FSAOLEDB_STARTTRANSACTION','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_FSRECOVER_UNCONDITIONALUNDO','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_GETRMINFO','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_HADR_LEASE_MECHANISM','Preemptive',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_HTTP_EVENT_WAIT','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_HTTP_REQUEST','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_LOCKMONITOR','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_MSS_RELEASE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_ODBCOPS','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OLE_UNINIT','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OLEDB_ABORTORCOMMITTRAN','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OLEDB_ABORTTRAN','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OLEDB_GETDATASOURCE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OLEDB_GETLITERALINFO','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OLEDB_GETPROPERTIES','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OLEDB_GETPROPERTYINFO','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OLEDB_GETSCHEMALOCK','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OLEDB_JOINTRANSACTION','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OLEDB_RELEASE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OLEDB_SETPROPERTIES','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OLEDBOPS','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_ACCEPTSECURITYCONTEXT','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_ACQUIRECREDENTIALSHANDLE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_AUTHENTICATIONOPS','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_AUTHORIZATIONOPS','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_AUTHZGETINFORMATIONFROMCONTEXT','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_AUTHZINITIALIZECONTEXTFROMSID','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_AUTHZINITIALIZERESOURCEMANAGER','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_BACKUPREAD','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_CLOSEHANDLE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_CLUSTEROPS','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_COMOPS','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_COMPLETEAUTHTOKEN','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_COPYFILE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_CREATEDIRECTORY','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_CREATEFILE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_CRYPTACQUIRECONTEXT','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_CRYPTIMPORTKEY','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_CRYPTOPS','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_DECRYPTMESSAGE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_DELETEFILE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_DELETESECURITYCONTEXT','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_DEVICEIOCONTROL','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_DEVICEOPS','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_DIRSVC_NETWORKOPS','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_DISCONNECTNAMEDPIPE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_DOMAINSERVICESOPS','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_DSGETDCNAME','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_DTCOPS','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_ENCRYPTMESSAGE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_FILEOPS','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_FINDFILE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_FLUSHFILEBUFFERS','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_FORMATMESSAGE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_FREECREDENTIALSHANDLE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_FREELIBRARY','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_GENERICOPS','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_GETADDRINFO','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_GETCOMPRESSEDFILESIZE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_GETDISKFREESPACE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_GETFILEATTRIBUTES','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_GETFILESIZE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_GETFINALFILEPATHBYHANDLE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_GETLONGPATHNAME','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_GETPROCADDRESS','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_GETVOLUMENAMEFORVOLUMEMOUNTPOINT','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_GETVOLUMEPATHNAME','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_INITIALIZESECURITYCONTEXT','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_LIBRARYOPS','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_LOADLIBRARY','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_LOGONUSER','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_LOOKUPACCOUNTSID','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_MESSAGEQUEUEOPS','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_MOVEFILE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_NETGROUPGETUSERS','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_NETLOCALGROUPGETMEMBERS','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_NETUSERGETGROUPS','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_NETUSERGETLOCALGROUPS','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_NETUSERMODALSGET','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_NETVALIDATEPASSWORDPOLICY','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_NETVALIDATEPASSWORDPOLICYFREE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_OPENDIRECTORY','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_PDH_WMI_INIT','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_PIPEOPS','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_PROCESSOPS','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_QUERYCONTEXTATTRIBUTES','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_QUERYREGISTRY','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_QUERYSECURITYCONTEXTTOKEN','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_REMOVEDIRECTORY','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_REPORTEVENT','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_REVERTTOSELF','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_RSFXDEVICEOPS','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_SECURITYOPS','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_SERVICEOPS','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_SETENDOFFILE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_SETFILEPOINTER','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_SETFILEVALIDDATA','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_SETNAMEDSECURITYINFO','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_SQLCLROPS','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_SQMLAUNCH','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_VERIFYSIGNATURE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_VERIFYTRUST','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_VSSOPS','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_WAITFORSINGLEOBJECT','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_WINSOCKOPS','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_WRITEFILE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_WRITEFILEGATHER','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_WSASETLASTERROR','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_REENLIST','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_RESIZELOG','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_ROLLFORWARDREDO','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_ROLLFORWARDUNDO','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_SB_STOPENDPOINT','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_SERVER_STARTUP','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_SETRMINFO','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_SHAREDMEM_GETDATA','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_SNIOPEN','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_SOSHOST','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_SOSTESTING','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_SP_SERVER_DIAGNOSTICS','Preemptive',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_STARTRM','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_STREAMFCB_CHECKPOINT','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_STREAMFCB_RECOVER','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_STRESSDRIVER','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_TESTING','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_TRANSIMPORT','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_UNMARSHALPROPAGATIONTOKEN','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_VSS_CREATESNAPSHOT','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_VSS_CREATEVOLUMESNAPSHOT','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_XE_CALLBACKEXECUTE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_XE_CX_FILE_OPEN','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_XE_CX_HTTP_CALL','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_XE_DISPATCHER','Preemptive',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_XE_ENGINEINIT','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_XE_GETTARGETSTATE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_XE_SESSIONCOMMIT','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_XE_TARGETFINALIZE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_XE_TARGETINIT','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_XE_TIMERRUN','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_XETESTING','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PWAIT_HADR_ACTION_COMPLETED','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PWAIT_HADR_CHANGE_NOTIFIER_TERMINATION_SYNC','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PWAIT_HADR_CLUSTER_INTEGRATION','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PWAIT_HADR_FAILOVER_COMPLETED','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PWAIT_HADR_JOIN','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PWAIT_HADR_OFFLINE_COMPLETED','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PWAIT_HADR_ONLINE_COMPLETED','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PWAIT_HADR_POST_ONLINE_COMPLETED','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PWAIT_HADR_SERVER_READY_CONNECTIONS','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PWAIT_HADR_WORKITEM_COMPLETED','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PWAIT_HADRSIM','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PWAIT_RESOURCE_SEMAPHORE_FT_PARALLEL_QUERY_SYNC','Full Text Search',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('QDS_ASYNC_QUEUE','Other',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('QDS_CLEANUP_STALE_QUERIES_TASK_MAIN_LOOP_SLEEP','Other',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('QDS_PERSIST_TASK_MAIN_LOOP_SLEEP','Other',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('QDS_SHUTDOWN_QUEUE','Other',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('QUERY_TRACEOUT','Tracing',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('REDO_THREAD_PENDING_WORK','Other',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('REPL_CACHE_ACCESS','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('REPL_HISTORYCACHE_ACCESS','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('REPL_SCHEMA_ACCESS','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('REPL_TRANFSINFO_ACCESS','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('REPL_TRANHASHTABLE_ACCESS','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('REPL_TRANTEXTINFO_ACCESS','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('REPLICA_WRITES','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('REQUEST_FOR_DEADLOCK_SEARCH','Idle',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('RESERVED_MEMORY_ALLOCATION_EXT','Memory',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('RESOURCE_SEMAPHORE','Memory',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('RESOURCE_SEMAPHORE_QUERY_COMPILE','Compilation',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_BPOOL_FLUSH','Idle',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_BUFFERPOOL_HELPLW','Idle',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_DBSTARTUP','Idle',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_DCOMSTARTUP','Idle',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_MASTERDBREADY','Idle',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_MASTERMDREADY','Idle',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_MASTERUPGRADED','Idle',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_MEMORYPOOL_ALLOCATEPAGES','Idle',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_MSDBSTARTUP','Idle',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_RETRY_VIRTUALALLOC','Idle',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_SYSTEMTASK','Idle',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_TASK','Idle',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_TEMPDBSTARTUP','Idle',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_WORKSPACE_ALLOCATEPAGE','Idle',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SOS_SCHEDULER_YIELD','CPU',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SOS_WORK_DISPATCHER','Idle',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SP_SERVER_DIAGNOSTICS_SLEEP','Other',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SQLCLR_APPDOMAIN','SQL CLR',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SQLCLR_ASSEMBLY','SQL CLR',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SQLCLR_DEADLOCK_DETECTION','SQL CLR',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SQLCLR_QUANTUM_PUNISHMENT','SQL CLR',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SQLTRACE_BUFFER_FLUSH','Idle',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SQLTRACE_FILE_BUFFER','Tracing',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SQLTRACE_FILE_READ_IO_COMPLETION','Tracing',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SQLTRACE_FILE_WRITE_IO_COMPLETION','Tracing',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SQLTRACE_INCREMENTAL_FLUSH_SLEEP','Idle',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SQLTRACE_PENDING_BUFFER_WRITERS','Tracing',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SQLTRACE_SHUTDOWN','Tracing',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SQLTRACE_WAIT_ENTRIES','Idle',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('THREADPOOL','Worker Thread',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('TRACE_EVTNOTIF','Tracing',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('TRACEWRITE','Tracing',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('TRAN_MARKLATCH_DT','Transaction',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('TRAN_MARKLATCH_EX','Transaction',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('TRAN_MARKLATCH_KP','Transaction',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('TRAN_MARKLATCH_NL','Transaction',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('TRAN_MARKLATCH_SH','Transaction',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('TRAN_MARKLATCH_UP','Transaction',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('TRANSACTION_MUTEX','Transaction',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('UCS_SESSION_REGISTRATION','Other',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('WAIT_FOR_RESULTS','User Wait',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('WAIT_XTP_OFFLINE_CKPT_NEW_LOG','Other',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('WAITFOR','User Wait',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('WRITE_COMPLETION','Other Disk IO',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('WRITELOG','Tran Log IO',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('XACT_OWN_TRANSACTION','Transaction',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('XACT_RECLAIM_SESSION','Transaction',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('XACTLOCKINFO','Transaction',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('XACTWORKSPACE_MUTEX','Transaction',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('XE_DISPATCHER_WAIT','Idle',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('XE_LIVE_TARGET_TVF','Other',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('XE_TIMER_EVENT','Idle',1); + END; /* IF SELECT SUM(1) FROM ##WaitCategories <> 527 */ + + + + IF OBJECT_ID('tempdb..#MasterFiles') IS NOT NULL + DROP TABLE #MasterFiles; + CREATE TABLE #MasterFiles (database_id INT, file_id INT, type_desc NVARCHAR(50), name NVARCHAR(255), physical_name NVARCHAR(255), size BIGINT); + /* Azure SQL Database doesn't have sys.master_files, so we have to build our own. */ + IF ((SERVERPROPERTY('Edition')) = 'SQL Azure' + AND (OBJECT_ID('sys.master_files') IS NULL)) + SET @StringToExecute = 'INSERT INTO #MasterFiles (database_id, file_id, type_desc, name, physical_name, size) SELECT DB_ID(), file_id, type_desc, name, physical_name, size FROM sys.database_files;'; + ELSE + SET @StringToExecute = 'INSERT INTO #MasterFiles (database_id, file_id, type_desc, name, physical_name, size) SELECT database_id, file_id, type_desc, name, physical_name, size FROM sys.master_files;'; + EXEC(@StringToExecute); + + IF @FilterPlansByDatabase IS NOT NULL + BEGIN + IF UPPER(LEFT(@FilterPlansByDatabase,4)) = 'USER' + BEGIN + INSERT INTO #FilterPlansByDatabase (DatabaseID) + SELECT database_id + FROM sys.databases + WHERE [name] NOT IN ('master', 'model', 'msdb', 'tempdb'); + END; + ELSE + BEGIN + SET @FilterPlansByDatabase = @FilterPlansByDatabase + ',' + ;WITH a AS + ( + SELECT CAST(1 AS BIGINT) f, CHARINDEX(',', @FilterPlansByDatabase) t, 1 SEQ + UNION ALL + SELECT t + 1, CHARINDEX(',', @FilterPlansByDatabase, t + 1), SEQ + 1 + FROM a + WHERE CHARINDEX(',', @FilterPlansByDatabase, t + 1) > 0 + ) + INSERT #FilterPlansByDatabase (DatabaseID) + SELECT DISTINCT db.database_id + FROM a + INNER JOIN sys.databases db ON LTRIM(RTRIM(SUBSTRING(@FilterPlansByDatabase, a.f, a.t - a.f))) = db.name + WHERE SUBSTRING(@FilterPlansByDatabase, f, t - f) IS NOT NULL + OPTION (MAXRECURSION 0); + END; + END; + + IF OBJECT_ID('tempdb..#ReadableDBs') IS NOT NULL + DROP TABLE #ReadableDBs; + CREATE TABLE #ReadableDBs ( + database_id INT + ); + + IF EXISTS (SELECT * FROM sys.all_objects o WHERE o.name = 'dm_hadr_database_replica_states') + BEGIN + RAISERROR('Checking for Read intent databases to exclude',0,0) WITH NOWAIT; + + SET @StringToExecute = 'INSERT INTO #ReadableDBs (database_id) SELECT DBs.database_id FROM sys.databases DBs INNER JOIN sys.availability_replicas Replicas ON DBs.replica_id = Replicas.replica_id WHERE replica_server_name NOT IN (SELECT DISTINCT primary_replica FROM sys.dm_hadr_availability_group_states States) AND Replicas.secondary_role_allow_connections_desc = ''READ_ONLY'' AND replica_server_name = @@SERVERNAME;'; + EXEC(@StringToExecute); + + END + + DECLARE @v DECIMAL(6,2), + @build INT, + @memGrantSortSupported BIT = 1; + + RAISERROR (N'Determining SQL Server version.',0,1) WITH NOWAIT; + + INSERT INTO #checkversion (version) + SELECT CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)) + OPTION (RECOMPILE); + + + SELECT @v = common_version , + @build = build + FROM #checkversion + OPTION (RECOMPILE); + + IF (@v < 11) + OR (@v = 11 AND @build < 6020) + OR (@v = 12 AND @build < 5000) + OR (@v = 13 AND @build < 1601) + SET @memGrantSortSupported = 0; + + IF EXISTS (SELECT * FROM sys.all_objects WHERE name = 'dm_exec_query_statistics_xml') + AND ((@v = 13 AND @build >= 5337) /* This DMF causes assertion errors: https://support.microsoft.com/en-us/help/4490136/fix-assertion-error-occurs-when-you-use-sys-dm-exec-query-statistics-x */ + OR (@v = 14 AND @build >= 3162) + OR (@v >= 15) + OR (@v <= 12)) /* Azure */ + SET @dm_exec_query_statistics_xml = 1; + + + SET @StockWarningHeader = '', + @StockDetailsHeader = @StockDetailsHeader + ''; + + /* Get the instance name to use as a Perfmon counter prefix. */ + IF CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) = 'SQL Azure' + SELECT TOP 1 @ServiceName = LEFT(object_name, (CHARINDEX(':', object_name) - 1)) + FROM sys.dm_os_performance_counters; + ELSE + BEGIN + SET @StringToExecute = 'INSERT INTO #PerfmonStats(object_name, Pass, SampleTime, counter_name, cntr_type) SELECT CASE WHEN @@SERVICENAME = ''MSSQLSERVER'' THEN ''SQLServer'' ELSE ''MSSQL$'' + @@SERVICENAME END, 0, SYSDATETIMEOFFSET(), ''stuffing'', 0 ;'; + EXEC(@StringToExecute); + SELECT @ServiceName = object_name FROM #PerfmonStats; + DELETE #PerfmonStats; + END; + + /* Build a list of queries that were run in the last 10 seconds. + We're looking for the death-by-a-thousand-small-cuts scenario + where a query is constantly running, and it doesn't have that + big of an impact individually, but it has a ton of impact + overall. We're going to build this list, and then after we + finish our @Seconds sample, we'll compare our plan cache to + this list to see what ran the most. */ + + /* Populate #QueryStats. SQL 2005 doesn't have query hash or query plan hash. */ + IF @CheckProcedureCache = 1 + BEGIN + RAISERROR('@CheckProcedureCache = 1, capturing first pass of plan cache',10,1) WITH NOWAIT; + IF @@VERSION LIKE 'Microsoft SQL Server 2005%' + BEGIN + IF @FilterPlansByDatabase IS NULL + BEGIN + SET @StringToExecute = N'INSERT INTO #QueryStats ([sql_handle], Pass, SampleTime, statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, Points) + SELECT [sql_handle], 1 AS Pass, SYSDATETIMEOFFSET(), statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, NULL AS query_hash, NULL AS query_plan_hash, 0 + FROM sys.dm_exec_query_stats qs + WHERE qs.last_execution_time >= (DATEADD(ss, -10, SYSDATETIMEOFFSET()));'; + END; + ELSE + BEGIN + SET @StringToExecute = N'INSERT INTO #QueryStats ([sql_handle], Pass, SampleTime, statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, Points) + SELECT [sql_handle], 1 AS Pass, SYSDATETIMEOFFSET(), statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, NULL AS query_hash, NULL AS query_plan_hash, 0 + FROM sys.dm_exec_query_stats qs + CROSS APPLY sys.dm_exec_plan_attributes(qs.plan_handle) AS attr + INNER JOIN #FilterPlansByDatabase dbs ON CAST(attr.value AS INT) = dbs.DatabaseID + WHERE qs.last_execution_time >= (DATEADD(ss, -10, SYSDATETIMEOFFSET())) + AND attr.attribute = ''dbid'';'; + END; + END; + ELSE + BEGIN + IF @FilterPlansByDatabase IS NULL + BEGIN + SET @StringToExecute = N'INSERT INTO #QueryStats ([sql_handle], Pass, SampleTime, statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, Points) + SELECT [sql_handle], 1 AS Pass, SYSDATETIMEOFFSET(), statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, 0 + FROM sys.dm_exec_query_stats qs + WHERE qs.last_execution_time >= (DATEADD(ss, -10, SYSDATETIMEOFFSET()));'; + END; + ELSE + BEGIN + SET @StringToExecute = N'INSERT INTO #QueryStats ([sql_handle], Pass, SampleTime, statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, Points) + SELECT [sql_handle], 1 AS Pass, SYSDATETIMEOFFSET(), statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, 0 + FROM sys.dm_exec_query_stats qs + CROSS APPLY sys.dm_exec_plan_attributes(qs.plan_handle) AS attr + INNER JOIN #FilterPlansByDatabase dbs ON CAST(attr.value AS INT) = dbs.DatabaseID + WHERE qs.last_execution_time >= (DATEADD(ss, -10, SYSDATETIMEOFFSET())) + AND attr.attribute = ''dbid'';'; + END; + END; + EXEC(@StringToExecute); + + /* Get the totals for the entire plan cache */ + INSERT INTO #QueryStats (Pass, SampleTime, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time) + SELECT -1 AS Pass, SYSDATETIMEOFFSET(), SUM(execution_count), SUM(total_worker_time), SUM(total_physical_reads), SUM(total_logical_writes), SUM(total_logical_reads), SUM(total_clr_time), SUM(total_elapsed_time), MIN(creation_time) + FROM sys.dm_exec_query_stats qs; + END; /*IF @CheckProcedureCache = 1 */ + + + IF EXISTS (SELECT * + FROM tempdb.sys.all_objects obj + INNER JOIN tempdb.sys.all_columns col1 ON obj.object_id = col1.object_id AND col1.name = 'object_name' + INNER JOIN tempdb.sys.all_columns col2 ON obj.object_id = col2.object_id AND col2.name = 'counter_name' + INNER JOIN tempdb.sys.all_columns col3 ON obj.object_id = col3.object_id AND col3.name = 'instance_name' + WHERE obj.name LIKE '%CustomPerfmonCounters%') + BEGIN + SET @StringToExecute = 'INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) SELECT [object_name],[counter_name],[instance_name] FROM #CustomPerfmonCounters'; + EXEC(@StringToExecute); + END; + ELSE + BEGIN + /* Add our default Perfmon counters */ + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Forwarded Records/sec', NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Page compression attempts/sec', NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Page Splits/sec', NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Skipped Ghosted Records/sec', NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Table Lock Escalations/sec', NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Worktables Created/sec', NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Availability Group','Active Hadr Threads','_Total'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Availability Replica','Bytes Received from Replica/sec','_Total'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Availability Replica','Bytes Sent to Replica/sec','_Total'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Availability Replica','Bytes Sent to Transport/sec','_Total'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Availability Replica','Flow Control Time (ms/sec)','_Total'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Availability Replica','Flow Control/sec','_Total'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Availability Replica','Resent Messages/sec','_Total'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Availability Replica','Sends to Replica/sec','_Total'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Page life expectancy', NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Page reads/sec', NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Page writes/sec', NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Readahead pages/sec', NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Target pages', NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Total pages', NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Databases','', NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Active Transactions','_Total'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Database Flow Control Delay', '_Total'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Database Flow Controls/sec', '_Total'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Group Commit Time', '_Total'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Group Commits/Sec', '_Total'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Log Apply Pending Queue', '_Total'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Log Apply Ready Queue', '_Total'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Log Compression Cache misses/sec', '_Total'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Log remaining for undo', '_Total'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Log Send Queue', '_Total'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Recovery Queue', '_Total'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Redo blocked/sec', '_Total'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Redo Bytes Remaining', '_Total'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Redone Bytes/sec', '_Total'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Databases','Log Bytes Flushed/sec', '_Total'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Databases','Log Growths', '_Total'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Databases','Log Pool LogWriter Pushes/sec', '_Total'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Databases','Log Shrinks', '_Total'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Databases','Transactions/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Databases','Write Transactions/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Databases','XTP Memory Used (KB)',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Exec Statistics','Distributed Query', 'Execs in progress'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Exec Statistics','DTC calls', 'Execs in progress'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Exec Statistics','Extended Procedures', 'Execs in progress'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Exec Statistics','OLEDB calls', 'Execs in progress'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':General Statistics','Active Temp Tables', NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':General Statistics','Logins/sec', NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':General Statistics','Logouts/sec', NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':General Statistics','Mars Deadlocks', NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':General Statistics','Processes blocked', NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Locks','Number of Deadlocks/sec', NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Memory Manager','Memory Grants Pending', NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Errors','Errors/sec', '_Total'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','Batch Requests/sec', NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','Forced Parameterizations/sec', NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','Guided plan executions/sec', NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','SQL Attention rate', NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','SQL Compilations/sec', NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','SQL Re-Compilations/sec', NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Workload Group Stats','Query optimizations/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Workload Group Stats','Suboptimal plans/sec',NULL); + /* Below counters added by Jefferson Elias */ + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Worktables From Cache Base',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Worktables From Cache Ratio',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Database pages',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Free pages',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Stolen pages',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Memory Manager','Granted Workspace Memory (KB)',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Memory Manager','Maximum Workspace Memory (KB)',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Memory Manager','Target Server Memory (KB)',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Memory Manager','Total Server Memory (KB)',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Buffer cache hit ratio',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Buffer cache hit ratio base',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Checkpoint pages/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Free list stalls/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Lazy writes/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','Auto-Param Attempts/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','Failed Auto-Params/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','Safe Auto-Params/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','Unsafe Auto-Params/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Workfiles Created/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':General Statistics','User Connections',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Latches','Average Latch Wait Time (ms)',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Latches','Average Latch Wait Time Base',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Latches','Latch Waits/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Latches','Total Latch Wait Time (ms)',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Locks','Average Wait Time (ms)',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Locks','Average Wait Time Base',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Locks','Lock Requests/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Locks','Lock Timeouts/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Locks','Lock Wait Time (ms)',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Locks','Lock Waits/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Transactions','Longest Transaction Running Time',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Full Scans/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Index Searches/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Page lookups/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Cursor Manager by Type','Active cursors',NULL); + /* Below counters are for In-Memory OLTP (Hekaton), which have a different naming convention. + And yes, they actually hard-coded the version numbers into the counters, and SQL 2019 still says 2017, oddly. + For why, see: https://connect.microsoft.com/SQLServer/feedback/details/817216/xtp-perfmon-counters-should-appear-under-sql-server-perfmon-counter-group + */ + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP Cursors','Expired rows removed/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP Cursors','Expired rows touched/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP Garbage Collection','Rows processed/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP IO Governor','Io Issued/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP Phantom Processor','Phantom expired rows touched/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP Phantom Processor','Phantom rows touched/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP Transaction Log','Log bytes written/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP Transaction Log','Log records written/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP Transactions','Transactions aborted by user/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP Transactions','Transactions aborted/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP Transactions','Transactions created/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP Cursors','Expired rows removed/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP Cursors','Expired rows touched/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP Garbage Collection','Rows processed/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP IO Governor','Io Issued/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP Phantom Processor','Phantom expired rows touched/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP Phantom Processor','Phantom rows touched/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP Transaction Log','Log bytes written/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP Transaction Log','Log records written/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP Transactions','Transactions aborted by user/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP Transactions','Transactions aborted/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP Transactions','Transactions created/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP Cursors','Expired rows removed/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP Cursors','Expired rows touched/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP Garbage Collection','Rows processed/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP IO Governor','Io Issued/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP Phantom Processor','Phantom expired rows touched/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP Phantom Processor','Phantom rows touched/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP Transaction Log','Log bytes written/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP Transaction Log','Log records written/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP Transactions','Transactions aborted by user/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP Transactions','Transactions aborted/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP Transactions','Transactions created/sec',NULL); + END; + + /* Populate #FileStats, #PerfmonStats, #WaitStats with DMV data. + After we finish doing our checks, we'll take another sample and compare them. */ + RAISERROR('Capturing first pass of wait stats, perfmon counters, file stats',10,1) WITH NOWAIT; + INSERT #WaitStats(Pass, SampleTime, wait_type, wait_time_ms, signal_wait_time_ms, waiting_tasks_count) + SELECT + x.Pass, + x.SampleTime, + x.wait_type, + SUM(x.sum_wait_time_ms) AS sum_wait_time_ms, + SUM(x.sum_signal_wait_time_ms) AS sum_signal_wait_time_ms, + SUM(x.sum_waiting_tasks) AS sum_waiting_tasks + FROM ( + SELECT + 1 AS Pass, + CASE @Seconds WHEN 0 THEN @StartSampleTime ELSE SYSDATETIMEOFFSET() END AS SampleTime, + owt.wait_type, + CASE @Seconds WHEN 0 THEN 0 ELSE SUM(owt.wait_duration_ms) OVER (PARTITION BY owt.wait_type, owt.session_id) + - CASE WHEN @Seconds = 0 THEN 0 ELSE (@Seconds * 1000) END END AS sum_wait_time_ms, + 0 AS sum_signal_wait_time_ms, + 0 AS sum_waiting_tasks + FROM sys.dm_os_waiting_tasks owt + WHERE owt.session_id > 50 + AND owt.wait_duration_ms >= CASE @Seconds WHEN 0 THEN 0 ELSE @Seconds * 1000 END + UNION ALL + SELECT + 1 AS Pass, + CASE @Seconds WHEN 0 THEN @StartSampleTime ELSE SYSDATETIMEOFFSET() END AS SampleTime, + os.wait_type, + CASE @Seconds WHEN 0 THEN 0 ELSE SUM(os.wait_time_ms) OVER (PARTITION BY os.wait_type) END AS sum_wait_time_ms, + CASE @Seconds WHEN 0 THEN 0 ELSE SUM(os.signal_wait_time_ms) OVER (PARTITION BY os.wait_type ) END AS sum_signal_wait_time_ms, + CASE @Seconds WHEN 0 THEN 0 ELSE SUM(os.waiting_tasks_count) OVER (PARTITION BY os.wait_type) END AS sum_waiting_tasks + FROM sys.dm_os_wait_stats os + ) x + WHERE NOT EXISTS + ( + SELECT * + FROM ##WaitCategories AS wc + WHERE wc.WaitType = x.wait_type + AND wc.Ignorable = 1 + ) + GROUP BY x.Pass, x.SampleTime, x.wait_type + ORDER BY sum_wait_time_ms DESC; + + + INSERT INTO #FileStats (Pass, SampleTime, DatabaseID, FileID, DatabaseName, FileLogicalName, SizeOnDiskMB, io_stall_read_ms , + num_of_reads, [bytes_read] , io_stall_write_ms,num_of_writes, [bytes_written], PhysicalName, TypeDesc) + SELECT + 1 AS Pass, + CASE @Seconds WHEN 0 THEN @StartSampleTime ELSE SYSDATETIMEOFFSET() END AS SampleTime, + mf.[database_id], + mf.[file_id], + DB_NAME(vfs.database_id) AS [db_name], + mf.name + N' [' + mf.type_desc COLLATE SQL_Latin1_General_CP1_CI_AS + N']' AS file_logical_name , + CAST(( ( vfs.size_on_disk_bytes / 1024.0 ) / 1024.0 ) AS INT) AS size_on_disk_mb , + CASE @Seconds WHEN 0 THEN 0 ELSE vfs.io_stall_read_ms END , + CASE @Seconds WHEN 0 THEN 0 ELSE vfs.num_of_reads END , + CASE @Seconds WHEN 0 THEN 0 ELSE vfs.[num_of_bytes_read] END , + CASE @Seconds WHEN 0 THEN 0 ELSE vfs.io_stall_write_ms END , + CASE @Seconds WHEN 0 THEN 0 ELSE vfs.num_of_writes END , + CASE @Seconds WHEN 0 THEN 0 ELSE vfs.[num_of_bytes_written] END , + mf.physical_name, + mf.type_desc + FROM sys.dm_io_virtual_file_stats (NULL, NULL) AS vfs + INNER JOIN #MasterFiles AS mf ON vfs.file_id = mf.file_id + AND vfs.database_id = mf.database_id + WHERE vfs.num_of_reads > 0 + OR vfs.num_of_writes > 0; + + INSERT INTO #PerfmonStats (Pass, SampleTime, [object_name],[counter_name],[instance_name],[cntr_value],[cntr_type]) + SELECT 1 AS Pass, + CASE @Seconds WHEN 0 THEN @StartSampleTime ELSE SYSDATETIMEOFFSET() END AS SampleTime, RTRIM(dmv.object_name), RTRIM(dmv.counter_name), RTRIM(dmv.instance_name), CASE @Seconds WHEN 0 THEN 0 ELSE dmv.cntr_value END, dmv.cntr_type + FROM #PerfmonCounters counters + INNER JOIN sys.dm_os_performance_counters dmv ON counters.counter_name COLLATE SQL_Latin1_General_CP1_CI_AS = RTRIM(dmv.counter_name) COLLATE SQL_Latin1_General_CP1_CI_AS + AND counters.[object_name] COLLATE SQL_Latin1_General_CP1_CI_AS = RTRIM(dmv.[object_name]) COLLATE SQL_Latin1_General_CP1_CI_AS + AND (counters.[instance_name] IS NULL OR counters.[instance_name] COLLATE SQL_Latin1_General_CP1_CI_AS = RTRIM(dmv.[instance_name]) COLLATE SQL_Latin1_General_CP1_CI_AS); + + /* For Github #2743: */ + CREATE TABLE #TempdbOperationalStats (object_id BIGINT PRIMARY KEY CLUSTERED, + forwarded_fetch_count BIGINT); + INSERT INTO #TempdbOperationalStats (object_id, forwarded_fetch_count) + SELECT object_id, forwarded_fetch_count + FROM tempdb.sys.dm_db_index_operational_stats(DB_ID('tempdb'), NULL, NULL, NULL) os + WHERE os.database_id = DB_ID('tempdb') + AND os.forwarded_fetch_count > 100; + + + /* If they want to run sp_BlitzWho and export to table, go for it. */ + IF @OutputTableNameBlitzWho IS NOT NULL + AND @OutputDatabaseName IS NOT NULL + AND @OutputSchemaName IS NOT NULL + AND EXISTS ( SELECT * + FROM sys.databases + WHERE QUOTENAME([name]) = @OutputDatabaseName) + BEGIN + RAISERROR('Logging sp_BlitzWho to table',10,1) WITH NOWAIT; + EXEC sp_BlitzWho @OutputDatabaseName = @UnquotedOutputDatabaseName, @OutputSchemaName = @UnquotedOutputSchemaName, @OutputTableName = @OutputTableNameBlitzWho, @CheckDateOverride = @StartSampleTime, @OutputTableRetentionDays = @OutputTableRetentionDays; + END + + RAISERROR('Beginning investigatory queries',10,1) WITH NOWAIT; + + + /* Maintenance Tasks Running - Backup Running - CheckID 1 */ + IF @Seconds > 0 + BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 1',10,1) WITH NOWAIT; + END + + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, QueryHash) + SELECT 1 AS CheckID, + 1 AS Priority, + 'Maintenance Tasks Running' AS FindingGroup, + 'Backup Running' AS Finding, + 'http://www.BrentOzar.com/askbrent/backups/' AS URL, + 'Backup of ' + DB_NAME(db.resource_database_id) + ' database (' + (SELECT CAST(CAST(SUM(size * 8.0 / 1024 / 1024) AS BIGINT) AS NVARCHAR) FROM #MasterFiles WHERE database_id = db.resource_database_id) + 'GB) ' + @LineFeed + + CAST(r.percent_complete AS NVARCHAR(100)) + '% complete, has been running since ' + CAST(r.start_time AS NVARCHAR(100)) + '. ' + @LineFeed + + CASE WHEN COALESCE(s.nt_user_name, s.login_name) IS NOT NULL THEN (' Login: ' + COALESCE(s.nt_user_name, s.login_name) + ' ') ELSE '' END AS Details, + 'KILL ' + CAST(r.session_id AS NVARCHAR(100)) + ';' AS HowToStopIt, + pl.query_plan AS QueryPlan, + r.start_time AS StartTime, + s.login_name AS LoginName, + s.nt_user_name AS NTUserName, + s.[program_name] AS ProgramName, + s.[host_name] AS HostName, + db.[resource_database_id] AS DatabaseID, + DB_NAME(db.resource_database_id) AS DatabaseName, + 0 AS OpenTransactionCount, + r.query_hash + FROM sys.dm_exec_requests r + INNER JOIN sys.dm_exec_connections c ON r.session_id = c.session_id + INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id + INNER JOIN ( + SELECT DISTINCT request_session_id, resource_database_id + FROM sys.dm_tran_locks + WHERE resource_type = N'DATABASE' + AND request_mode = N'S' + AND request_status = N'GRANT' + AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.session_id = db.request_session_id + CROSS APPLY sys.dm_exec_query_plan(r.plan_handle) pl + WHERE r.command LIKE 'BACKUP%' + AND r.start_time <= DATEADD(minute, -5, GETDATE()) + AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs); + END + + /* If there's a backup running, add details explaining how long full backup has been taking in the last month. */ + IF @Seconds > 0 AND CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) <> 'SQL Azure' + BEGIN + SET @StringToExecute = 'UPDATE #BlitzFirstResults SET Details = Details + '' Over the last 60 days, the full backup usually takes '' + CAST((SELECT AVG(DATEDIFF(mi, bs.backup_start_date, bs.backup_finish_date)) FROM msdb.dbo.backupset bs WHERE abr.DatabaseName = bs.database_name AND bs.type = ''D'' AND bs.backup_start_date > DATEADD(dd, -60, SYSDATETIMEOFFSET()) AND bs.backup_finish_date IS NOT NULL) AS NVARCHAR(100)) + '' minutes.'' FROM #BlitzFirstResults abr WHERE abr.CheckID = 1 AND EXISTS (SELECT * FROM msdb.dbo.backupset bs WHERE bs.type = ''D'' AND bs.backup_start_date > DATEADD(dd, -60, SYSDATETIMEOFFSET()) AND bs.backup_finish_date IS NOT NULL AND abr.DatabaseName = bs.database_name AND DATEDIFF(mi, bs.backup_start_date, bs.backup_finish_date) > 1)'; + EXEC(@StringToExecute); + END; + + + /* Maintenance Tasks Running - DBCC CHECK* Running - CheckID 2 */ + IF @Seconds > 0 AND EXISTS(SELECT * FROM sys.dm_exec_requests WHERE command LIKE 'DBCC%') + BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 2',10,1) WITH NOWAIT; + END + + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, QueryHash) + SELECT 2 AS CheckID, + 1 AS Priority, + 'Maintenance Tasks Running' AS FindingGroup, + 'DBCC CHECK* Running' AS Finding, + 'http://www.BrentOzar.com/askbrent/dbcc/' AS URL, + 'Corruption check of ' + DB_NAME(db.resource_database_id) + ' database (' + (SELECT CAST(CAST(SUM(size * 8.0 / 1024 / 1024) AS BIGINT) AS NVARCHAR) FROM #MasterFiles WHERE database_id = db.resource_database_id) + 'GB) 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, + s.login_name AS LoginName, + s.nt_user_name AS NTUserName, + s.[program_name] AS ProgramName, + s.[host_name] AS HostName, + db.[resource_database_id] AS DatabaseID, + DB_NAME(db.resource_database_id) AS DatabaseName, + 0 AS OpenTransactionCount, + r.query_hash + FROM sys.dm_exec_requests r + INNER JOIN sys.dm_exec_connections c ON r.session_id = c.session_id + INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id + INNER JOIN (SELECT DISTINCT l.request_session_id, l.resource_database_id + FROM sys.dm_tran_locks l + INNER JOIN sys.databases d ON l.resource_database_id = d.database_id + WHERE l.resource_type = N'DATABASE' + AND l.request_mode = N'S' + AND l.request_status = N'GRANT' + AND l.request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.session_id = db.request_session_id + OUTER APPLY sys.dm_exec_query_plan(r.plan_handle) pl + OUTER APPLY sys.dm_exec_sql_text(r.sql_handle) AS t + WHERE r.command LIKE 'DBCC%' + AND CAST(t.text AS NVARCHAR(4000)) NOT LIKE '%dm_db_index_physical_stats%' + AND CAST(t.text AS NVARCHAR(4000)) NOT LIKE '%ALTER INDEX%' + AND CAST(t.text AS NVARCHAR(4000)) NOT LIKE '%fileproperty%' + AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs); + END + + /* Maintenance Tasks Running - Restore Running - CheckID 3 */ + IF @Seconds > 0 + BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 3',10,1) WITH NOWAIT; + END + + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, QueryHash) + SELECT 3 AS CheckID, + 1 AS Priority, + 'Maintenance Tasks Running' AS FindingGroup, + 'Restore Running' AS Finding, + 'http://www.BrentOzar.com/askbrent/backups/' AS URL, + 'Restore of ' + DB_NAME(db.resource_database_id) + ' database (' + (SELECT CAST(CAST(SUM(size * 8.0 / 1024 / 1024) AS BIGINT) AS NVARCHAR) FROM #MasterFiles WHERE database_id = db.resource_database_id) + '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, + s.login_name AS LoginName, + s.nt_user_name AS NTUserName, + s.[program_name] AS ProgramName, + s.[host_name] AS HostName, + db.[resource_database_id] AS DatabaseID, + DB_NAME(db.resource_database_id) AS DatabaseName, + 0 AS OpenTransactionCount, + r.query_hash + FROM sys.dm_exec_requests r + INNER JOIN sys.dm_exec_connections c ON r.session_id = c.session_id + INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id + INNER JOIN ( + SELECT DISTINCT request_session_id, resource_database_id + FROM sys.dm_tran_locks + WHERE resource_type = N'DATABASE' + AND request_mode = N'S' + AND request_status = N'GRANT') AS db ON s.session_id = db.request_session_id + CROSS APPLY sys.dm_exec_query_plan(r.plan_handle) pl + WHERE r.command LIKE 'RESTORE%' + AND s.program_name <> 'SQL Server Log Shipping' + AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs); + END + + /* SQL Server Internal Maintenance - Database File Growing - CheckID 4 */ + IF @Seconds > 0 + BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 4',10,1) WITH NOWAIT; + END + + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount) + SELECT 4 AS CheckID, + 1 AS Priority, + 'SQL Server Internal Maintenance' AS FindingGroup, + 'Database File Growing' AS Finding, + 'http://www.BrentOzar.com/go/instant' AS URL, + 'SQL Server is waiting for Windows to provide storage space for a database restore, a data file growth, or a log file growth. This task has been running since ' + CAST(r.start_time AS NVARCHAR(100)) + '.' + @LineFeed + 'Check the query plan (expert mode) to identify the database involved.' AS Details, + 'Unfortunately, you can''t stop this, but you can prevent it next time. Check out http://www.BrentOzar.com/go/instant for details.' AS HowToStopIt, + pl.query_plan AS QueryPlan, + r.start_time AS StartTime, + s.login_name AS LoginName, + s.nt_user_name AS NTUserName, + s.[program_name] AS ProgramName, + s.[host_name] AS HostName, + NULL AS DatabaseID, + NULL AS DatabaseName, + 0 AS OpenTransactionCount + FROM sys.dm_os_waiting_tasks t + INNER JOIN sys.dm_exec_connections c ON t.session_id = c.session_id + INNER JOIN sys.dm_exec_requests r ON t.session_id = r.session_id + INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id + CROSS APPLY sys.dm_exec_query_plan(r.plan_handle) pl + WHERE t.wait_type = 'PREEMPTIVE_OS_WRITEFILEGATHER' + AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs); + END + + /* Query Problems - Long-Running Query Blocking Others - CheckID 5 */ + IF SERVERPROPERTY('Edition') <> 'SQL Azure' AND @Seconds > 0 AND EXISTS(SELECT * FROM sys.dm_os_waiting_tasks WHERE wait_type LIKE 'LCK%' AND wait_duration_ms > 30000) + BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 5',10,1) WITH NOWAIT; + END + + SET @StringToExecute = N'INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, QueryText, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, QueryHash) + SELECT 5 AS CheckID, + 1 AS Priority, + ''Query Problems'' AS FindingGroup, + ''Long-Running Query Blocking Others'' AS Finding, + ''http://www.BrentOzar.com/go/blocking'' AS URL, + ''Query in '' + COALESCE(DB_NAME(COALESCE((SELECT TOP 1 dbid FROM sys.dm_exec_sql_text(r.sql_handle)), + (SELECT TOP 1 t.dbid FROM master..sysprocesses spBlocker CROSS APPLY sys.dm_exec_sql_text(spBlocker.sql_handle) t WHERE spBlocker.spid = tBlocked.blocking_session_id))), ''(Unknown)'') + '' has a last request start time of '' + CAST(s.last_request_start_time AS NVARCHAR(100)) + ''. Query follows: ' + + @LineFeed + @LineFeed + + '''+ CAST(COALESCE((SELECT TOP 1 [text] FROM sys.dm_exec_sql_text(r.sql_handle)), + (SELECT TOP 1 [text] FROM master..sysprocesses spBlocker CROSS APPLY sys.dm_exec_sql_text(spBlocker.sql_handle) WHERE spBlocker.spid = tBlocked.blocking_session_id), '''') AS NVARCHAR(2000)) AS Details, + ''KILL '' + CAST(tBlocked.blocking_session_id AS NVARCHAR(100)) + '';'' AS HowToStopIt, + (SELECT TOP 1 query_plan FROM sys.dm_exec_query_plan(r.plan_handle)) AS QueryPlan, + COALESCE((SELECT TOP 1 [text] FROM sys.dm_exec_sql_text(r.sql_handle)), + (SELECT TOP 1 [text] FROM master..sysprocesses spBlocker CROSS APPLY sys.dm_exec_sql_text(spBlocker.sql_handle) WHERE spBlocker.spid = tBlocked.blocking_session_id)) AS QueryText, + r.start_time AS StartTime, + s.login_name AS LoginName, + s.nt_user_name AS NTUserName, + s.[program_name] AS ProgramName, + s.[host_name] AS HostName, + r.[database_id] AS DatabaseID, + DB_NAME(r.database_id) AS DatabaseName, + 0 AS OpenTransactionCount, + r.query_hash + FROM sys.dm_os_waiting_tasks tBlocked + INNER JOIN sys.dm_exec_sessions s ON tBlocked.blocking_session_id = s.session_id + LEFT OUTER JOIN sys.dm_exec_requests r ON s.session_id = r.session_id + INNER JOIN sys.dm_exec_connections c ON s.session_id = c.session_id + WHERE tBlocked.wait_type LIKE ''LCK%'' AND tBlocked.wait_duration_ms > 30000 + /* And the blocking session ID is not blocked by anyone else: */ + AND NOT EXISTS(SELECT * FROM sys.dm_os_waiting_tasks tBlocking WHERE s.session_id = tBlocking.session_id AND tBlocking.session_id <> tBlocking.blocking_session_id AND tBlocking.blocking_session_id IS NOT NULL) + AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs);'; + EXECUTE sp_executesql @StringToExecute; + END; + + /* Query Problems - Plan Cache Erased Recently - CheckID 7 */ + IF DATEADD(mi, -15, SYSDATETIME()) < (SELECT TOP 1 creation_time FROM sys.dm_exec_query_stats ORDER BY creation_time) + BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 7',10,1) WITH NOWAIT; + END + + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) + SELECT TOP 1 7 AS CheckID, + 50 AS Priority, + 'Query Problems' AS FindingGroup, + 'Plan Cache Erased Recently' AS Finding, + 'http://www.BrentOzar.com/askbrent/plan-cache-erased-recently/' AS URL, + 'The oldest query in the plan cache was created at ' + CAST(creation_time AS NVARCHAR(50)) + '. ' + @LineFeed + @LineFeed + + 'This indicates that someone ran DBCC FREEPROCCACHE at that time,' + @LineFeed + + 'Giving SQL Server temporary amnesia. Now, as queries come in,' + @LineFeed + + 'SQL Server has to use a lot of CPU power in order to build execution' + @LineFeed + + 'plans and put them in cache again. This causes high CPU loads.' AS Details, + 'Find who did that, and stop them from doing it again.' AS HowToStopIt + FROM sys.dm_exec_query_stats + ORDER BY creation_time; + END; + + + /* Query Problems - Sleeping Query with Open Transactions - CheckID 8 */ + IF @Seconds > 0 + BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 8',10,1) WITH NOWAIT; + END + + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, OpenTransactionCount) + SELECT 8 AS CheckID, + 50 AS Priority, + 'Query Problems' AS FindingGroup, + 'Sleeping Query with Open Transactions' AS Finding, + 'http://www.brentozar.com/askbrent/sleeping-query-with-open-transactions/' AS URL, + 'Database: ' + DB_NAME(db.resource_database_id) + @LineFeed + 'Host: ' + s.[host_name] + @LineFeed + 'Program: ' + s.[program_name] + @LineFeed + 'Asleep with open transactions and locks since ' + CAST(s.last_request_end_time AS NVARCHAR(100)) + '. ' AS Details, + 'KILL ' + CAST(s.session_id AS NVARCHAR(100)) + ';' AS HowToStopIt, + s.last_request_start_time AS StartTime, + s.login_name AS LoginName, + s.nt_user_name AS NTUserName, + s.[program_name] AS ProgramName, + s.[host_name] AS HostName, + db.[resource_database_id] AS DatabaseID, + DB_NAME(db.resource_database_id) AS DatabaseName, + (SELECT TOP 1 [text] FROM sys.dm_exec_sql_text(c.most_recent_sql_handle)) AS QueryText, + sessions_with_transactions.open_transaction_count AS OpenTransactionCount + FROM (SELECT session_id, SUM(open_transaction_count) AS open_transaction_count FROM sys.dm_exec_requests WHERE open_transaction_count > 0 GROUP BY session_id) AS sessions_with_transactions + INNER JOIN sys.dm_exec_sessions s ON sessions_with_transactions.session_id = s.session_id + INNER JOIN sys.dm_exec_connections c ON s.session_id = c.session_id + INNER JOIN ( + SELECT DISTINCT request_session_id, resource_database_id + FROM sys.dm_tran_locks + WHERE resource_type = N'DATABASE' + AND request_mode = N'S' + AND request_status = N'GRANT' + AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.session_id = db.request_session_id + WHERE s.status = 'sleeping' + AND s.last_request_end_time < DATEADD(ss, -10, SYSDATETIME()) + AND EXISTS(SELECT * FROM sys.dm_tran_locks WHERE request_session_id = s.session_id + AND NOT (resource_type = N'DATABASE' AND request_mode = N'S' AND request_status = N'GRANT' AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE')); + END + + /*Query Problems - Clients using implicit transactions - CheckID 37 */ + IF @Seconds > 0 + AND ( @@VERSION NOT LIKE 'Microsoft SQL Server 2005%' + AND @@VERSION NOT LIKE 'Microsoft SQL Server 2008%' + AND @@VERSION NOT LIKE 'Microsoft SQL Server 2008 R2%' ) + BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 37',10,1) WITH NOWAIT; + END + + SET @StringToExecute = N'INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, OpenTransactionCount) + SELECT 37 AS CheckId, + 50 AS Priority, + ''Query Problems'' AS FindingsGroup, + ''Implicit Transactions'', + ''https://www.brentozar.com/go/ImplicitTransactions/'' AS URL, + ''Database: '' + DB_NAME(s.database_id) + '' '' + CHAR(13) + CHAR(10) + + ''Host: '' + s.[host_name] + '' '' + CHAR(13) + CHAR(10) + + ''Program: '' + s.[program_name] + '' '' + CHAR(13) + CHAR(10) + + CONVERT(NVARCHAR(10), s.open_transaction_count) + + '' open transactions since: '' + + CONVERT(NVARCHAR(30), tat.transaction_begin_time) + ''. '' + AS Details, + ''Run sp_BlitzWho and check the is_implicit_transaction column to spot the culprits. +If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, + tat.transaction_begin_time, + s.login_name, + s.nt_user_name, + s.program_name, + s.host_name, + s.database_id, + DB_NAME(s.database_id) AS DatabaseName, + NULL AS Querytext, + s.open_transaction_count AS OpenTransactionCount + FROM sys.dm_tran_active_transactions AS tat + LEFT JOIN sys.dm_tran_session_transactions AS tst + ON tst.transaction_id = tat.transaction_id + LEFT JOIN sys.dm_exec_sessions AS s + ON s.session_id = tst.session_id + WHERE tat.name = ''implicit_transaction''; + ' + EXECUTE sp_executesql @StringToExecute; + END; + + /* Query Problems - Query Rolling Back - CheckID 9 */ + IF @Seconds > 0 + BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 9',10,1) WITH NOWAIT; + END + + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, QueryHash) + SELECT 9 AS CheckID, + 1 AS Priority, + 'Query Problems' AS FindingGroup, + 'Query Rolling Back' AS Finding, + 'http://www.BrentOzar.com/askbrent/rollback/' AS URL, + 'Rollback started at ' + CAST(r.start_time AS NVARCHAR(100)) + ', is ' + CAST(r.percent_complete AS NVARCHAR(100)) + '% complete.' AS Details, + 'Unfortunately, you can''t stop this. Whatever you do, don''t restart the server in an attempt to fix it - SQL Server will keep rolling back.' AS HowToStopIt, + r.start_time AS StartTime, + s.login_name AS LoginName, + s.nt_user_name AS NTUserName, + s.[program_name] AS ProgramName, + s.[host_name] AS HostName, + db.[resource_database_id] AS DatabaseID, + DB_NAME(db.resource_database_id) AS DatabaseName, + (SELECT TOP 1 [text] FROM sys.dm_exec_sql_text(c.most_recent_sql_handle)) AS QueryText, + r.query_hash + FROM sys.dm_exec_sessions s + INNER JOIN sys.dm_exec_connections c ON s.session_id = c.session_id + INNER JOIN sys.dm_exec_requests r ON s.session_id = r.session_id + LEFT OUTER JOIN ( + SELECT DISTINCT request_session_id, resource_database_id + FROM sys.dm_tran_locks + WHERE resource_type = N'DATABASE' + AND request_mode = N'S' + AND request_status = N'GRANT' + AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.session_id = db.request_session_id + WHERE r.status = 'rollback'; + END + + IF @Seconds > 0 + BEGIN + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) + SELECT + 47 AS CheckId, + 50 AS Priority, + 'Query Problems' AS FindingsGroup, + 'High Percentage Of Runnable Queries' AS Finding, + 'https://erikdarlingdata.com/go/RunnableQueue/' AS URL, + 'On the ' + + CASE WHEN y.pass = 1 + THEN '1st' + ELSE '2nd' + END + + ' pass, ' + + RTRIM(y.runnable_pct) + + '% of your queries were waiting to get on a CPU to run. ' + + ' This can indicate CPU pressure.' + FROM + ( + SELECT + 1 AS pass, + x.total, + x.runnable, + CONVERT(decimal(5,2), + ( + x.runnable / + (1. * NULLIF(x.total, 0)) + ) + ) * 100. AS runnable_pct + FROM + ( + SELECT + COUNT_BIG(*) AS total, + SUM(CASE WHEN status = 'runnable' + THEN 1 + ELSE 0 + END) AS runnable + FROM sys.dm_exec_requests + WHERE session_id > 50 + ) AS x + ) AS y + WHERE y.runnable_pct > 20.; + END + + /* Server Performance - Too Much Free Memory - CheckID 34 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 34',10,1) WITH NOWAIT; + END + + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) + SELECT 34 AS CheckID, + 50 AS Priority, + 'Server Performance' AS FindingGroup, + 'Too Much Free Memory' AS Finding, + 'https://BrentOzar.com/go/freememory' AS URL, + CAST((CAST(cFree.cntr_value AS BIGINT) / 1024 / 1024 ) AS NVARCHAR(100)) + N'GB of free memory inside SQL Server''s buffer pool,' + @LineFeed + ' which is ' + CAST((CAST(cTotal.cntr_value AS BIGINT) / 1024 / 1024) AS NVARCHAR(100)) + N'GB. You would think lots of free memory would be good, but check out the URL for more information.' AS Details, + 'Run sp_BlitzCache @SortOrder = ''memory grant'' to find queries with huge memory grants and tune them.' AS HowToStopIt + FROM sys.dm_os_performance_counters cFree + INNER JOIN sys.dm_os_performance_counters cTotal ON cTotal.object_name LIKE N'%Memory Manager%' + AND cTotal.counter_name = N'Total Server Memory (KB) ' + WHERE cFree.object_name LIKE N'%Memory Manager%' + AND cFree.counter_name = N'Free Memory (KB) ' + AND CAST(cFree.cntr_value AS BIGINT) > 20480000000 + AND CAST(cTotal.cntr_value AS BIGINT) * .3 <= CAST(cFree.cntr_value AS BIGINT) + AND CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) NOT LIKE '%Standard%'; + + /* Server Performance - Target Memory Lower Than Max - CheckID 35 */ + IF SERVERPROPERTY('Edition') <> 'SQL Azure' + BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 35',10,1) WITH NOWAIT; + END + + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) + SELECT 35 AS CheckID, + 10 AS Priority, + 'Server Performance' AS FindingGroup, + 'Target Memory Lower Than Max' AS Finding, + 'https://BrentOzar.com/go/target' AS URL, + N'Max server memory is ' + CAST(cMax.value_in_use AS NVARCHAR(50)) + N' MB but target server memory is only ' + CAST((CAST(cTarget.cntr_value AS BIGINT) / 1024) AS NVARCHAR(50)) + N' MB,' + @LineFeed + + N'indicating that SQL Server may be under external memory pressure or max server memory may be set too high.' AS Details, + 'Investigate what OS processes are using memory, and double-check the max server memory setting.' AS HowToStopIt + FROM sys.configurations cMax + INNER JOIN sys.dm_os_performance_counters cTarget ON cTarget.object_name LIKE N'%Memory Manager%' + AND cTarget.counter_name = N'Target Server Memory (KB) ' + WHERE cMax.name = 'max server memory (MB)' + AND CAST(cMax.value_in_use AS BIGINT) >= 1.5 * (CAST(cTarget.cntr_value AS BIGINT) / 1024) + AND CAST(cMax.value_in_use AS BIGINT) < 2147483647 /* Not set to default of unlimited */ + AND CAST(cTarget.cntr_value AS BIGINT) < .8 * (SELECT available_physical_memory_kb FROM sys.dm_os_sys_memory); /* Target memory less than 80% of physical memory (in case they set max too high) */ + END + + /* Server Info - Database Size, Total GB - CheckID 21 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 21',10,1) WITH NOWAIT; + END + + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) + SELECT 21 AS CheckID, + 251 AS Priority, + 'Server Info' AS FindingGroup, + 'Database Size, Total GB' AS Finding, + CAST(SUM (CAST(size AS BIGINT)*8./1024./1024.) AS VARCHAR(100)) AS Details, + SUM (CAST(size AS BIGINT))*8./1024./1024. AS DetailsInt, + 'http://www.BrentOzar.com/askbrent/' AS URL + FROM #MasterFiles + WHERE database_id > 4; + + /* Server Info - Database Count - CheckID 22 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 22',10,1) WITH NOWAIT; + END + + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) + SELECT 22 AS CheckID, + 251 AS Priority, + 'Server Info' AS FindingGroup, + 'Database Count' AS Finding, + CAST(SUM(1) AS VARCHAR(100)) AS Details, + SUM (1) AS DetailsInt, + 'http://www.BrentOzar.com/askbrent/' AS URL + FROM sys.databases + WHERE database_id > 4; + + /* Server Info - Memory Grants pending - CheckID 39 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 39',10,1) WITH NOWAIT; + END + + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) + SELECT 39 AS CheckID, + 50 AS Priority, + 'Server Performance' AS FindingGroup, + 'Memory Grants Pending' AS Finding, + CAST(PendingGrants.Details AS NVARCHAR(50)) AS Details, + PendingGrants.DetailsInt, + 'https://www.brentozar.com/blitz/memory-grants/' AS URL + FROM + ( + SELECT + COUNT(1) AS Details, + COUNT(1) AS DetailsInt + FROM sys.dm_exec_query_memory_grants AS Grants + WHERE queue_id IS NOT NULL + ) AS PendingGrants + WHERE PendingGrants.Details > 0; + + /* Server Info - Memory Grant/Workspace info - CheckID 40 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 40',10,1) WITH NOWAIT; + END + + DECLARE @MaxWorkspace BIGINT + SET @MaxWorkspace = (SELECT CAST(cntr_value AS BIGINT)/1024 FROM #PerfmonStats WHERE counter_name = N'Maximum Workspace Memory (KB)') + + IF (@MaxWorkspace IS NULL + OR @MaxWorkspace = 0) + BEGIN + SET @MaxWorkspace = 1 + END + + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) + SELECT 40 AS CheckID, + 251 AS Priority, + 'Server Info' AS FindingGroup, + 'Memory Grant/Workspace info' AS Finding, + + 'Grants Outstanding: ' + CAST((SELECT COUNT(*) FROM sys.dm_exec_query_memory_grants WHERE queue_id IS NULL) AS NVARCHAR(50)) + @LineFeed + + 'Total Granted(MB): ' + CAST(ISNULL(SUM(Grants.granted_memory_kb) / 1024, 0) AS NVARCHAR(50)) + @LineFeed + + 'Total WorkSpace(MB): ' + CAST(ISNULL(@MaxWorkspace, 0) AS NVARCHAR(50)) + @LineFeed + + 'Granted workspace: ' + CAST(ISNULL((CAST(SUM(Grants.granted_memory_kb) / 1024 AS MONEY) + / CAST(@MaxWorkspace AS MONEY)) * 100, 0) AS NVARCHAR(50)) + '%' + @LineFeed + + 'Oldest Grant in seconds: ' + CAST(ISNULL(DATEDIFF(SECOND, MIN(Grants.request_time), GETDATE()), 0) AS NVARCHAR(50)) AS Details, + (SELECT COUNT(*) FROM sys.dm_exec_query_memory_grants WHERE queue_id IS NULL) AS DetailsInt, + 'http://www.BrentOzar.com/askbrent/' AS URL + FROM sys.dm_exec_query_memory_grants AS Grants; + + /* Query Problems - Queries with high memory grants - CheckID 46 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 46',10,1) WITH NOWAIT; + END + + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, URL, QueryText, QueryPlan) + SELECT 46 AS CheckID, + 100 AS Priority, + 'Query Problems' AS FindingGroup, + 'Query with a memory grant exceeding ' + +CAST(@MemoryGrantThresholdPct AS NVARCHAR(15)) + +'%' AS Finding, + 'Granted size: '+ CAST(CAST(Grants.granted_memory_kb / 1024 AS INT) AS NVARCHAR(50)) + +N'MB ' + + @LineFeed + +N'Granted pct of max workspace: ' + + CAST(ISNULL((CAST(Grants.granted_memory_kb / 1024 AS MONEY) + / CAST(@MaxWorkspace AS MONEY)) * 100, 0) AS NVARCHAR(50)) + '%' + + @LineFeed + +N'SQLHandle: ' + +CONVERT(NVARCHAR(128),Grants.[sql_handle],1), + 'https://www.brentozar.com/memory-grants-sql-servers-public-toilet/' AS URL, + SQLText.[text], + QueryPlan.query_plan + FROM sys.dm_exec_query_memory_grants AS Grants + OUTER APPLY sys.dm_exec_sql_text(Grants.[sql_handle]) AS SQLText + OUTER APPLY sys.dm_exec_query_plan(Grants.[plan_handle]) AS QueryPlan + WHERE Grants.granted_memory_kb > ((@MemoryGrantThresholdPct/100.00)*(@MaxWorkspace*1024)); + + /* Query Problems - Memory Leak in USERSTORE_TOKENPERM Cache - CheckID 45 */ + IF EXISTS (SELECT * FROM sys.all_columns WHERE object_id = OBJECT_ID('sys.dm_os_memory_clerks') AND name = 'pages_kb') + BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 45',10,1) WITH NOWAIT; + END + + /* SQL 2012+ version */ + SET @StringToExecute = N' + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, URL) + SELECT 45 AS CheckID, + 50 AS Priority, + ''Query Problems'' AS FindingsGroup, + ''Memory Leak in USERSTORE_TOKENPERM Cache'' AS Finding, + N''UserStore_TokenPerm clerk is using '' + CAST(CAST(SUM(CASE WHEN type = ''USERSTORE_TOKENPERM'' AND name = ''TokenAndPermUserStore'' THEN pages_kb * 1.0 ELSE 0.0 END) / 1024.0 / 1024.0 AS INT) AS NVARCHAR(100)) + + N''GB RAM, total buffer pool is '' + CAST(CAST(SUM(pages_kb) / 1024.0 / 1024.0 AS INT) AS NVARCHAR(100)) + N''GB.'' + AS details, + ''https://www.BrentOzar.com/go/userstore'' AS URL + FROM sys.dm_os_memory_clerks + HAVING SUM(CASE WHEN type = ''USERSTORE_TOKENPERM'' AND name = ''TokenAndPermUserStore'' THEN pages_kb * 1.0 ELSE 0.0 END) / SUM(pages_kb) >= 0.1 + AND SUM(pages_kb) / 1024.0 / 1024.0 >= 1; /* At least 1GB RAM overall */'; + EXEC sp_executesql @StringToExecute; + END + ELSE + BEGIN + /* Antiques Roadshow SQL 2008R2 - version */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 45 (Legacy version)',10,1) WITH NOWAIT; + END + + SET @StringToExecute = N' + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, URL) + SELECT 45 AS CheckID, + 50 AS Priority, + ''Performance'' AS FindingsGroup, + ''Memory Leak in USERSTORE_TOKENPERM Cache'' AS Finding, + N''UserStore_TokenPerm clerk is using '' + CAST(CAST(SUM(CASE WHEN type = ''USERSTORE_TOKENPERM'' AND name = ''TokenAndPermUserStore'' THEN single_pages_kb + multi_pages_kb * 1.0 ELSE 0.0 END) / 1024.0 / 1024.0 AS INT) AS NVARCHAR(100)) + + N''GB RAM, total buffer pool is '' + CAST(CAST(SUM(single_pages_kb + multi_pages_kb) / 1024.0 / 1024.0 AS INT) AS NVARCHAR(100)) + N''GB.'' + AS details, + ''https://www.BrentOzar.com/go/userstore'' AS URL + FROM sys.dm_os_memory_clerks + HAVING SUM(CASE WHEN type = ''USERSTORE_TOKENPERM'' AND name = ''TokenAndPermUserStore'' THEN single_pages_kb + multi_pages_kb * 1.0 ELSE 0.0 END) / SUM(single_pages_kb + multi_pages_kb) >= 0.1 + AND SUM(single_pages_kb + multi_pages_kb) / 1024.0 / 1024.0 >= 1; /* At least 1GB RAM overall */'; + EXEC sp_executesql @StringToExecute; + END + + + + IF @Seconds > 0 + BEGIN + + IF EXISTS ( SELECT 1/0 + FROM sys.all_objects AS ao + WHERE ao.name = 'dm_exec_query_profiles' ) + BEGIN + + IF EXISTS( SELECT 1/0 + FROM sys.dm_exec_requests AS r + JOIN sys.dm_exec_sessions AS s + ON r.session_id = s.session_id + WHERE s.host_name IS NOT NULL + AND r.total_elapsed_time > 5000 ) + BEGIN + + SET @StringToExecute = N' + DECLARE @bad_estimate TABLE + ( + session_id INT, + request_id INT, + estimate_inaccuracy BIT + ); + + INSERT @bad_estimate ( session_id, request_id, estimate_inaccuracy ) + SELECT x.session_id, + x.request_id, + x.estimate_inaccuracy + FROM ( + SELECT deqp.session_id, + deqp.request_id, + CASE WHEN deqp.row_count > ( deqp.estimate_row_count * 10000 ) + THEN 1 + ELSE 0 + END AS estimate_inaccuracy + FROM sys.dm_exec_query_profiles AS deqp + WHERE deqp.session_id <> @@SPID + ) AS x + WHERE x.estimate_inaccuracy = 1 + GROUP BY x.session_id, + x.request_id, + x.estimate_inaccuracy; + + DECLARE @parallelism_skew TABLE + ( + session_id INT, + request_id INT, + parallelism_skew BIT + ); + + INSERT @parallelism_skew ( session_id, request_id, parallelism_skew ) + SELECT y.session_id, + y.request_id, + y.parallelism_skew + FROM ( + SELECT x.session_id, + x.request_id, + x.node_id, + x.thread_id, + x.row_count, + x.sum_node_rows, + x.node_dop, + x.sum_node_rows / x.node_dop AS even_distribution, + x.row_count / (1. * ISNULL(NULLIF(x.sum_node_rows / x.node_dop, 0), 1)) AS skew_percent, + CASE + WHEN x.row_count > 10000 + AND x.row_count / (1. * ISNULL(NULLIF(x.sum_node_rows / x.node_dop, 0), 1)) > 2. + THEN 1 + WHEN x.row_count > 10000 + AND x.row_count / (1. * ISNULL(NULLIF(x.sum_node_rows / x.node_dop, 0), 1)) < 0.5 + THEN 1 + ELSE 0 + END AS parallelism_skew + FROM ( + SELECT deqp.session_id, + deqp.request_id, + deqp.node_id, + deqp.thread_id, + deqp.row_count, + SUM(deqp.row_count) + OVER ( PARTITION BY deqp.session_id, + deqp.request_id, + deqp.node_id + ORDER BY deqp.row_count + ROWS BETWEEN UNBOUNDED PRECEDING + AND UNBOUNDED FOLLOWING ) + AS sum_node_rows, + COUNT(*) + OVER ( PARTITION BY deqp.session_id, + deqp.request_id, + deqp.node_id + ORDER BY deqp.row_count + ROWS BETWEEN UNBOUNDED PRECEDING + AND UNBOUNDED FOLLOWING ) + AS node_dop + FROM sys.dm_exec_query_profiles AS deqp + WHERE deqp.thread_id > 0 + AND deqp.session_id <> @@SPID + AND EXISTS + ( + SELECT 1/0 + FROM sys.dm_exec_query_profiles AS deqp2 + WHERE deqp.session_id = deqp2.session_id + AND deqp.node_id = deqp2.node_id + AND deqp2.thread_id > 0 + GROUP BY deqp2.session_id, deqp2.node_id + HAVING COUNT(deqp2.node_id) > 1 + ) + ) AS x + ) AS y + WHERE y.parallelism_skew = 1 + GROUP BY y.session_id, + y.request_id, + y.parallelism_skew; + + /* Queries in dm_exec_query_profiles showing signs of poor cardinality estimates - CheckID 42 */ + IF (@Debug = 1) + BEGIN + RAISERROR(''Running CheckID 42'',10,1) WITH NOWAIT; + END + + INSERT INTO #BlitzFirstResults + (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, OpenTransactionCount, QueryHash, QueryPlan) + SELECT 42 AS CheckID, + 100 AS Priority, + ''Query Performance'' AS FindingsGroup, + ''Queries with 10000x cardinality misestimations'' AS Findings, + ''https://brentozar.com/go/skewedup'' AS URL, + ''The query on SPID '' + + RTRIM(b.session_id) + + '' has been running for '' + + RTRIM(r.total_elapsed_time / 1000) + + '' seconds, with a large cardinality misestimate'' AS Details, + ''No quick fix here: time to dig into the actual execution plan. '' AS HowToStopIt, + r.start_time, + s.login_name, + s.nt_user_name, + s.program_name, + s.host_name, + r.database_id, + DB_NAME(r.database_id), + dest.text, + s.open_transaction_count, + r.query_hash, '; + + IF @dm_exec_query_statistics_xml = 1 + SET @StringToExecute = @StringToExecute + N' COALESCE(qs_live.query_plan, qp.query_plan) AS query_plan '; + ELSE + SET @StringToExecute = @StringToExecute + N' qp.query_plan '; + + SET @StringToExecute = @StringToExecute + N' + FROM @bad_estimate AS b + JOIN sys.dm_exec_requests AS r + ON r.session_id = b.session_id + AND r.request_id = b.request_id + JOIN sys.dm_exec_sessions AS s + ON s.session_id = b.session_id + CROSS APPLY sys.dm_exec_sql_text(r.sql_handle) AS dest + CROSS APPLY sys.dm_exec_query_plan(r.plan_handle) AS qp '; + + IF EXISTS (SELECT * FROM sys.all_objects WHERE name = 'dm_exec_query_statistics_xml') + SET @StringToExecute = @StringToExecute + N' OUTER APPLY sys.dm_exec_query_statistics_xml(s.session_id) qs_live '; + + + SET @StringToExecute = @StringToExecute + N'; + + /* Queries in dm_exec_query_profiles showing signs of unbalanced parallelism - CheckID 43 */ + IF (@Debug = 1) + BEGIN + RAISERROR(''Running CheckID 43'',10,1) WITH NOWAIT; + END + + INSERT INTO #BlitzFirstResults + (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, OpenTransactionCount, QueryHash, QueryPlan) + SELECT 43 AS CheckID, + 100 AS Priority, + ''Query Performance'' AS FindingsGroup, + ''Queries with 10000x skewed parallelism'' AS Findings, + ''https://brentozar.com/go/skewedup'' AS URL, + ''The query on SPID '' + + RTRIM(p.session_id) + + '' has been running for '' + + RTRIM(r.total_elapsed_time / 1000) + + '' seconds, with a parallel threads doing uneven work.'' AS Details, + ''No quick fix here: time to dig into the actual execution plan. '' AS HowToStopIt, + r.start_time, + s.login_name, + s.nt_user_name, + s.program_name, + s.host_name, + r.database_id, + DB_NAME(r.database_id), + dest.text, + s.open_transaction_count, + r.query_hash, '; + + IF @dm_exec_query_statistics_xml = 1 + SET @StringToExecute = @StringToExecute + N' COALESCE(qs_live.query_plan, qp.query_plan) AS query_plan '; + ELSE + SET @StringToExecute = @StringToExecute + N' qp.query_plan '; + + SET @StringToExecute = @StringToExecute + N' + FROM @parallelism_skew AS p + JOIN sys.dm_exec_requests AS r + ON r.session_id = p.session_id + AND r.request_id = p.request_id + JOIN sys.dm_exec_sessions AS s + ON s.session_id = p.session_id + CROSS APPLY sys.dm_exec_sql_text(r.sql_handle) AS dest + CROSS APPLY sys.dm_exec_query_plan(r.plan_handle) AS qp '; + + IF EXISTS (SELECT * FROM sys.all_objects WHERE name = 'dm_exec_query_statistics_xml') + SET @StringToExecute = @StringToExecute + N' OUTER APPLY sys.dm_exec_query_statistics_xml(s.session_id) qs_live '; + + + SET @StringToExecute = @StringToExecute + N';'; + + EXECUTE sp_executesql @StringToExecute, N'@Debug BIT',@Debug = @Debug; + END + + END + END + + /* Server Performance - High CPU Utilization - CheckID 24 */ + IF @Seconds < 30 + BEGIN + /* If we're waiting less than 30 seconds, run this check now rather than wait til the end. + We get this data from the ring buffers, and it's only updated once per minute, so might + as well get it now - whereas if we're checking 30+ seconds, it might get updated by the + end of our sp_BlitzFirst session. */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 24',10,1) WITH NOWAIT; + END + + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) + SELECT 24, 50, 'Server Performance', 'High CPU Utilization', CAST(100 - SystemIdle AS NVARCHAR(20)) + N'%.', 100 - SystemIdle, 'http://www.BrentOzar.com/go/cpu' + FROM ( + SELECT record, + record.value('(./Record/SchedulerMonitorEvent/SystemHealth/SystemIdle)[1]', 'int') AS SystemIdle + FROM ( + SELECT TOP 1 CONVERT(XML, record) AS record + FROM sys.dm_os_ring_buffers + WHERE ring_buffer_type = N'RING_BUFFER_SCHEDULER_MONITOR' + AND record LIKE '%%' + ORDER BY timestamp DESC) AS rb + ) AS y + WHERE 100 - SystemIdle >= 50; + + /* CPU Utilization - CheckID 23 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 23',10,1) WITH NOWAIT; + END + + IF SERVERPROPERTY('Edition') <> 'SQL Azure' + WITH y + AS + ( + SELECT CONVERT(VARCHAR(5), 100 - ca.c.value('.', 'INT')) AS system_idle, + CONVERT(VARCHAR(30), rb.event_date) AS event_date, + CONVERT(VARCHAR(8000), rb.record) AS record + FROM + ( SELECT CONVERT(XML, dorb.record) AS record, + DATEADD(ms, ( ts.ms_ticks - dorb.timestamp ), GETDATE()) AS event_date + FROM sys.dm_os_ring_buffers AS dorb + CROSS JOIN + ( SELECT dosi.ms_ticks FROM sys.dm_os_sys_info AS dosi ) AS ts + WHERE dorb.ring_buffer_type = N'RING_BUFFER_SCHEDULER_MONITOR' + AND record LIKE '%%' ) AS rb + CROSS APPLY rb.record.nodes('/Record/SchedulerMonitorEvent/SystemHealth/SystemIdle') AS ca(c) + ) + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL, HowToStopIt) + SELECT TOP 1 + 23, + 250, + 'Server Info', + 'CPU Utilization', + y.system_idle + N'%. Ring buffer details: ' + CAST(y.record AS NVARCHAR(4000)), + y.system_idle , + 'http://www.BrentOzar.com/go/cpu', + STUFF(( SELECT TOP 2147483647 + CHAR(10) + CHAR(13) + + y2.system_idle + + '% ON ' + + y2.event_date + + ' Ring buffer details: ' + + y2.record + FROM y AS y2 + ORDER BY y2.event_date DESC + FOR XML PATH(N''), TYPE ).value(N'.[1]', N'VARCHAR(MAX)'), 1, 1, N'') AS query + FROM y + ORDER BY y.event_date DESC; + + + /* Highlight if non SQL processes are using >25% CPU - CheckID 28 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 28',10,1) WITH NOWAIT; + END + + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) + SELECT 28, 50, 'Server Performance', 'High CPU Utilization - Not SQL', CONVERT(NVARCHAR(100),100 - (y.SQLUsage + y.SystemIdle)) + N'% - Other Processes (not SQL Server) are using this much CPU. This may impact on the performance of your SQL Server instance', 100 - (y.SQLUsage + y.SystemIdle), 'http://www.BrentOzar.com/go/cpu' + FROM ( + SELECT record, + record.value('(./Record/SchedulerMonitorEvent/SystemHealth/SystemIdle)[1]', 'int') AS SystemIdle + ,record.value('(./Record/SchedulerMonitorEvent/SystemHealth/ProcessUtilization)[1]', 'int') AS SQLUsage + FROM ( + SELECT TOP 1 CONVERT(XML, record) AS record + FROM sys.dm_os_ring_buffers + WHERE ring_buffer_type = N'RING_BUFFER_SCHEDULER_MONITOR' + AND record LIKE '%%' + ORDER BY timestamp DESC) AS rb + ) AS y + WHERE 100 - (y.SQLUsage + y.SystemIdle) >= 25; + + END; /* IF @Seconds < 30 */ + + /* Query Problems - Statistics Updated Recently - CheckID 44 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 44',10,1) WITH NOWAIT; + END + + IF 20 >= (SELECT COUNT(*) FROM sys.databases WHERE name NOT IN ('master', 'model', 'msdb', 'tempdb')) + BEGIN + CREATE TABLE #UpdatedStats (HowToStopIt NVARCHAR(4000), RowsForSorting BIGINT); + IF EXISTS(SELECT * FROM sys.all_objects WHERE name = 'dm_db_stats_properties') + BEGIN + /* We don't want to hang around to obtain locks */ + SET LOCK_TIMEOUT 0; + + EXEC sp_MSforeachdb N'USE [?]; + SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; SET LOCK_TIMEOUT 1000; + BEGIN TRY + INSERT INTO #UpdatedStats(HowToStopIt, RowsForSorting) + SELECT HowToStopIt = + QUOTENAME(DB_NAME()) + N''.'' + + QUOTENAME(SCHEMA_NAME(obj.schema_id)) + N''.'' + + QUOTENAME(obj.name) + + N'' statistic '' + QUOTENAME(stat.name) + + N'' was updated on '' + CONVERT(NVARCHAR(50), sp.last_updated, 121) + N'','' + + N'' had '' + CAST(sp.rows AS NVARCHAR(50)) + N'' rows, with '' + + CAST(sp.rows_sampled AS NVARCHAR(50)) + N'' rows sampled,'' + + N'' producing '' + CAST(sp.steps AS NVARCHAR(50)) + N'' steps in the histogram.'', + sp.rows + FROM sys.objects AS obj WITH (NOLOCK) + INNER JOIN sys.stats AS stat WITH (NOLOCK) ON stat.object_id = obj.object_id + CROSS APPLY sys.dm_db_stats_properties(stat.object_id, stat.stats_id) AS sp + WHERE sp.last_updated > DATEADD(MI, -15, GETDATE()) + AND obj.is_ms_shipped = 0 + AND ''[?]'' <> ''[tempdb]''; + END TRY + BEGIN CATCH + IF (ERROR_NUMBER() = 1222) + BEGIN + INSERT INTO #UpdatedStats(HowToStopIt, RowsForSorting) + SELECT HowToStopIt = + QUOTENAME(DB_NAME()) + + N'' No information could be retrieved as the lock timeout was exceeded,''+ + N'' this is likely due to an Index operation in Progress'', + -1 + END + ELSE + BEGIN + INSERT INTO #UpdatedStats(HowToStopIt, RowsForSorting) + SELECT HowToStopIt = + QUOTENAME(DB_NAME()) + + N'' No information could be retrieved as a result of error: ''+ + CAST(ERROR_NUMBER() AS NVARCHAR(10)) + + N'' with message: ''+ + CAST(ERROR_MESSAGE() AS NVARCHAR(128)), + -1 + END + END CATCH'; + + /* Set timeout back to a default value of -1 */ + SET LOCK_TIMEOUT -1; + END; + + /* We mark timeout exceeded with a -1 so only show these IF there is statistics info that succeeded */ + IF EXISTS (SELECT * FROM #UpdatedStats WHERE RowsForSorting > -1) + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) + SELECT 44 AS CheckId, + 50 AS Priority, + 'Query Problems' AS FindingGroup, + 'Statistics Updated Recently' AS Finding, + 'http://www.BrentOzar.com/go/stats' AS URL, + 'In the last 15 minutes, statistics were updated. To see which ones, click the HowToStopIt column.' + @LineFeed + @LineFeed + + 'This effectively clears the plan cache for queries that involve these tables,' + @LineFeed + + 'which thereby causes parameter sniffing: those queries are now getting brand new' + @LineFeed + + 'query plans based on whatever parameters happen to call them next.' + @LineFeed + @LineFeed + + 'Be on the lookout for sudden parameter sniffing issues after this time range.', + HowToStopIt = (SELECT (SELECT HowToStopIt + NCHAR(10)) + FROM #UpdatedStats + ORDER BY RowsForSorting DESC + FOR XML PATH('')); + + END + + RAISERROR('Finished running investigatory queries',10,1) WITH NOWAIT; + + + /* End of checks. If we haven't waited @Seconds seconds, wait. */ + IF DATEADD(SECOND,1,SYSDATETIMEOFFSET()) < @FinishSampleTime + BEGIN + RAISERROR('Waiting to match @Seconds parameter',10,1) WITH NOWAIT; + WAITFOR TIME @FinishSampleTimeWaitFor; + END; + + RAISERROR('Capturing second pass of wait stats, perfmon counters, file stats',10,1) WITH NOWAIT; + /* Populate #FileStats, #PerfmonStats, #WaitStats with DMV data. In a second, we'll compare these. */ + INSERT #WaitStats(Pass, SampleTime, wait_type, wait_time_ms, signal_wait_time_ms, waiting_tasks_count) + SELECT + x.Pass, + x.SampleTime, + x.wait_type, + SUM(x.sum_wait_time_ms) AS sum_wait_time_ms, + SUM(x.sum_signal_wait_time_ms) AS sum_signal_wait_time_ms, + SUM(x.sum_waiting_tasks) AS sum_waiting_tasks + FROM ( + SELECT + 2 AS Pass, + SYSDATETIMEOFFSET() AS SampleTime, + owt.wait_type, + SUM(owt.wait_duration_ms) OVER (PARTITION BY owt.wait_type, owt.session_id) + - CASE WHEN @Seconds = 0 THEN 0 ELSE (@Seconds * 1000) END AS sum_wait_time_ms, + 0 AS sum_signal_wait_time_ms, + CASE @Seconds WHEN 0 THEN 0 ELSE 1 END AS sum_waiting_tasks + FROM sys.dm_os_waiting_tasks owt + WHERE owt.session_id > 50 + AND owt.wait_duration_ms >= CASE @Seconds WHEN 0 THEN 0 ELSE @Seconds * 1000 END + UNION ALL + SELECT + 2 AS Pass, + SYSDATETIMEOFFSET() AS SampleTime, + os.wait_type, + SUM(os.wait_time_ms) OVER (PARTITION BY os.wait_type) AS sum_wait_time_ms, + SUM(os.signal_wait_time_ms) OVER (PARTITION BY os.wait_type ) AS sum_signal_wait_time_ms, + SUM(os.waiting_tasks_count) OVER (PARTITION BY os.wait_type) AS sum_waiting_tasks + FROM sys.dm_os_wait_stats os + ) x + WHERE NOT EXISTS + ( + SELECT * + FROM ##WaitCategories AS wc + WHERE wc.WaitType = x.wait_type + AND wc.Ignorable = 1 + ) + GROUP BY x.Pass, x.SampleTime, x.wait_type + ORDER BY sum_wait_time_ms DESC; + + INSERT INTO #FileStats (Pass, SampleTime, DatabaseID, FileID, DatabaseName, FileLogicalName, SizeOnDiskMB, io_stall_read_ms , + num_of_reads, [bytes_read] , io_stall_write_ms,num_of_writes, [bytes_written], PhysicalName, TypeDesc, avg_stall_read_ms, avg_stall_write_ms) + SELECT 2 AS Pass, + SYSDATETIMEOFFSET() AS SampleTime, + mf.[database_id], + mf.[file_id], + DB_NAME(vfs.database_id) AS [db_name], + mf.name + N' [' + mf.type_desc COLLATE SQL_Latin1_General_CP1_CI_AS + N']' AS file_logical_name , + CAST(( ( vfs.size_on_disk_bytes / 1024.0 ) / 1024.0 ) AS INT) AS size_on_disk_mb , + vfs.io_stall_read_ms , + vfs.num_of_reads , + vfs.[num_of_bytes_read], + vfs.io_stall_write_ms , + vfs.num_of_writes , + vfs.[num_of_bytes_written], + mf.physical_name, + mf.type_desc, + 0, + 0 + FROM sys.dm_io_virtual_file_stats (NULL, NULL) AS vfs + INNER JOIN #MasterFiles AS mf ON vfs.file_id = mf.file_id + AND vfs.database_id = mf.database_id + WHERE vfs.num_of_reads > 0 + OR vfs.num_of_writes > 0; + + INSERT INTO #PerfmonStats (Pass, SampleTime, [object_name],[counter_name],[instance_name],[cntr_value],[cntr_type]) + SELECT 2 AS Pass, + SYSDATETIMEOFFSET() AS SampleTime, + RTRIM(dmv.object_name), RTRIM(dmv.counter_name), RTRIM(dmv.instance_name), dmv.cntr_value, dmv.cntr_type + FROM #PerfmonCounters counters + INNER JOIN sys.dm_os_performance_counters dmv ON counters.counter_name COLLATE SQL_Latin1_General_CP1_CI_AS = RTRIM(dmv.counter_name) COLLATE SQL_Latin1_General_CP1_CI_AS + AND counters.[object_name] COLLATE SQL_Latin1_General_CP1_CI_AS = RTRIM(dmv.[object_name]) COLLATE SQL_Latin1_General_CP1_CI_AS + AND (counters.[instance_name] IS NULL OR counters.[instance_name] COLLATE SQL_Latin1_General_CP1_CI_AS = RTRIM(dmv.[instance_name]) COLLATE SQL_Latin1_General_CP1_CI_AS); + + /* Set the latencies and averages. We could do this with a CTE, but we're not ambitious today. */ + UPDATE fNow + SET avg_stall_read_ms = ((fNow.io_stall_read_ms - fBase.io_stall_read_ms) / (fNow.num_of_reads - fBase.num_of_reads)) + FROM #FileStats fNow + INNER JOIN #FileStats fBase ON fNow.DatabaseID = fBase.DatabaseID AND fNow.FileID = fBase.FileID AND fNow.SampleTime > fBase.SampleTime AND fNow.num_of_reads > fBase.num_of_reads AND fNow.io_stall_read_ms > fBase.io_stall_read_ms + WHERE (fNow.num_of_reads - fBase.num_of_reads) > 0; + + UPDATE fNow + SET avg_stall_write_ms = ((fNow.io_stall_write_ms - fBase.io_stall_write_ms) / (fNow.num_of_writes - fBase.num_of_writes)) + FROM #FileStats fNow + INNER JOIN #FileStats fBase ON fNow.DatabaseID = fBase.DatabaseID AND fNow.FileID = fBase.FileID AND fNow.SampleTime > fBase.SampleTime AND fNow.num_of_writes > fBase.num_of_writes AND fNow.io_stall_write_ms > fBase.io_stall_write_ms + WHERE (fNow.num_of_writes - fBase.num_of_writes) > 0; + + UPDATE pNow + SET [value_delta] = pNow.cntr_value - pFirst.cntr_value, + [value_per_second] = ((1.0 * pNow.cntr_value - pFirst.cntr_value) / DATEDIFF(ss, pFirst.SampleTime, pNow.SampleTime)) + FROM #PerfmonStats pNow + INNER JOIN #PerfmonStats pFirst ON pFirst.[object_name] = pNow.[object_name] AND pFirst.counter_name = pNow.counter_name AND (pFirst.instance_name = pNow.instance_name OR (pFirst.instance_name IS NULL AND pNow.instance_name IS NULL)) + AND pNow.ID > pFirst.ID + WHERE DATEDIFF(ss, pFirst.SampleTime, pNow.SampleTime) > 0; + + + /* Query Stats - If we're within 10 seconds of our projected finish time, do the plan cache analysis. - CheckID 18 */ + IF DATEDIFF(ss, @FinishSampleTime, SYSDATETIMEOFFSET()) > 10 AND @CheckProcedureCache = 1 + BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 18',10,1) WITH NOWAIT; + END + + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (18, 210, 'Query Stats', 'Plan Cache Analysis Skipped', 'http://www.BrentOzar.com/go/topqueries', + 'Due to excessive load, the plan cache analysis was skipped. To override this, use @ExpertMode = 1.'); + + END; + ELSE IF @CheckProcedureCache = 1 + BEGIN + + + RAISERROR('@CheckProcedureCache = 1, capturing second pass of plan cache',10,1) WITH NOWAIT; + + /* Populate #QueryStats. SQL 2005 doesn't have query hash or query plan hash. */ + IF @@VERSION LIKE 'Microsoft SQL Server 2005%' + BEGIN + IF @FilterPlansByDatabase IS NULL + BEGIN + SET @StringToExecute = N'INSERT INTO #QueryStats ([sql_handle], Pass, SampleTime, statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, Points) + SELECT [sql_handle], 2 AS Pass, SYSDATETIMEOFFSET(), statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, NULL AS query_hash, NULL AS query_plan_hash, 0 + FROM sys.dm_exec_query_stats qs + WHERE qs.last_execution_time >= @StartSampleTimeText;'; + END; + ELSE + BEGIN + SET @StringToExecute = N'INSERT INTO #QueryStats ([sql_handle], Pass, SampleTime, statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, Points) + SELECT [sql_handle], 2 AS Pass, SYSDATETIMEOFFSET(), statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, NULL AS query_hash, NULL AS query_plan_hash, 0 + FROM sys.dm_exec_query_stats qs + CROSS APPLY sys.dm_exec_plan_attributes(qs.plan_handle) AS attr + INNER JOIN #FilterPlansByDatabase dbs ON CAST(attr.value AS INT) = dbs.DatabaseID + WHERE qs.last_execution_time >= @StartSampleTimeText + AND attr.attribute = ''dbid'';'; + END; + END; + ELSE + BEGIN + IF @FilterPlansByDatabase IS NULL + BEGIN + SET @StringToExecute = N'INSERT INTO #QueryStats ([sql_handle], Pass, SampleTime, statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, Points) + SELECT [sql_handle], 2 AS Pass, SYSDATETIMEOFFSET(), statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, 0 + FROM sys.dm_exec_query_stats qs + WHERE qs.last_execution_time >= @StartSampleTimeText'; + END; + ELSE + BEGIN + SET @StringToExecute = N'INSERT INTO #QueryStats ([sql_handle], Pass, SampleTime, statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, Points) + SELECT [sql_handle], 2 AS Pass, SYSDATETIMEOFFSET(), statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, 0 + FROM sys.dm_exec_query_stats qs + CROSS APPLY sys.dm_exec_plan_attributes(qs.plan_handle) AS attr + INNER JOIN #FilterPlansByDatabase dbs ON CAST(attr.value AS INT) = dbs.DatabaseID + WHERE qs.last_execution_time >= @StartSampleTimeText + AND attr.attribute = ''dbid'';'; + END; + END; + /* Old version pre-2016/06/13: + IF @@VERSION LIKE 'Microsoft SQL Server 2005%' + SET @StringToExecute = N'INSERT INTO #QueryStats ([sql_handle], Pass, SampleTime, statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, Points) + SELECT [sql_handle], 2 AS Pass, SYSDATETIMEOFFSET(), statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, NULL AS query_hash, NULL AS query_plan_hash, 0 + FROM sys.dm_exec_query_stats qs + WHERE qs.last_execution_time >= @StartSampleTimeText;'; + ELSE + SET @StringToExecute = N'INSERT INTO #QueryStats ([sql_handle], Pass, SampleTime, statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, Points) + SELECT [sql_handle], 2 AS Pass, SYSDATETIMEOFFSET(), statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, 0 + FROM sys.dm_exec_query_stats qs + WHERE qs.last_execution_time >= @StartSampleTimeText;'; + */ + SET @ParmDefinitions = N'@StartSampleTimeText NVARCHAR(100)'; + SET @Parm1 = CONVERT(NVARCHAR(100), CAST(@StartSampleTime AS DATETIME), 127); + + EXECUTE sp_executesql @StringToExecute, @ParmDefinitions, @StartSampleTimeText = @Parm1; + + RAISERROR('@CheckProcedureCache = 1, totaling up plan cache metrics',10,1) WITH NOWAIT; + + /* Get the totals for the entire plan cache */ + INSERT INTO #QueryStats (Pass, SampleTime, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time) + SELECT 0 AS Pass, SYSDATETIMEOFFSET(), SUM(execution_count), SUM(total_worker_time), SUM(total_physical_reads), SUM(total_logical_writes), SUM(total_logical_reads), SUM(total_clr_time), SUM(total_elapsed_time), MIN(creation_time) + FROM sys.dm_exec_query_stats qs; + + + RAISERROR('@CheckProcedureCache = 1, so analyzing execution plans',10,1) WITH NOWAIT; + /* + Pick the most resource-intensive queries to review. Update the Points field + in #QueryStats - if a query is in the top 10 for logical reads, CPU time, + duration, or execution, add 1 to its points. + */ + WITH qsTop AS ( + SELECT TOP 10 qsNow.ID + FROM #QueryStats qsNow + INNER JOIN #QueryStats qsFirst ON qsNow.[sql_handle] = qsFirst.[sql_handle] AND qsNow.statement_start_offset = qsFirst.statement_start_offset AND qsNow.statement_end_offset = qsFirst.statement_end_offset AND qsNow.plan_generation_num = qsFirst.plan_generation_num AND qsNow.plan_handle = qsFirst.plan_handle AND qsFirst.Pass = 1 + WHERE qsNow.total_elapsed_time > qsFirst.total_elapsed_time + AND qsNow.Pass = 2 + AND qsNow.total_elapsed_time - qsFirst.total_elapsed_time > 1000000 /* Only queries with over 1 second of runtime */ + ORDER BY (qsNow.total_elapsed_time - COALESCE(qsFirst.total_elapsed_time, 0)) DESC) + UPDATE #QueryStats + SET Points = Points + 1 + FROM #QueryStats qs + INNER JOIN qsTop ON qs.ID = qsTop.ID; + + WITH qsTop AS ( + SELECT TOP 10 qsNow.ID + FROM #QueryStats qsNow + INNER JOIN #QueryStats qsFirst ON qsNow.[sql_handle] = qsFirst.[sql_handle] AND qsNow.statement_start_offset = qsFirst.statement_start_offset AND qsNow.statement_end_offset = qsFirst.statement_end_offset AND qsNow.plan_generation_num = qsFirst.plan_generation_num AND qsNow.plan_handle = qsFirst.plan_handle AND qsFirst.Pass = 1 + WHERE qsNow.total_logical_reads > qsFirst.total_logical_reads + AND qsNow.Pass = 2 + AND qsNow.total_logical_reads - qsFirst.total_logical_reads > 1000 /* Only queries with over 1000 reads */ + ORDER BY (qsNow.total_logical_reads - COALESCE(qsFirst.total_logical_reads, 0)) DESC) + UPDATE #QueryStats + SET Points = Points + 1 + FROM #QueryStats qs + INNER JOIN qsTop ON qs.ID = qsTop.ID; + + WITH qsTop AS ( + SELECT TOP 10 qsNow.ID + FROM #QueryStats qsNow + INNER JOIN #QueryStats qsFirst ON qsNow.[sql_handle] = qsFirst.[sql_handle] AND qsNow.statement_start_offset = qsFirst.statement_start_offset AND qsNow.statement_end_offset = qsFirst.statement_end_offset AND qsNow.plan_generation_num = qsFirst.plan_generation_num AND qsNow.plan_handle = qsFirst.plan_handle AND qsFirst.Pass = 1 + WHERE qsNow.total_worker_time > qsFirst.total_worker_time + AND qsNow.Pass = 2 + AND qsNow.total_worker_time - qsFirst.total_worker_time > 1000000 /* Only queries with over 1 second of worker time */ + ORDER BY (qsNow.total_worker_time - COALESCE(qsFirst.total_worker_time, 0)) DESC) + UPDATE #QueryStats + SET Points = Points + 1 + FROM #QueryStats qs + INNER JOIN qsTop ON qs.ID = qsTop.ID; + + WITH qsTop AS ( + SELECT TOP 10 qsNow.ID + FROM #QueryStats qsNow + INNER JOIN #QueryStats qsFirst ON qsNow.[sql_handle] = qsFirst.[sql_handle] AND qsNow.statement_start_offset = qsFirst.statement_start_offset AND qsNow.statement_end_offset = qsFirst.statement_end_offset AND qsNow.plan_generation_num = qsFirst.plan_generation_num AND qsNow.plan_handle = qsFirst.plan_handle AND qsFirst.Pass = 1 + WHERE qsNow.execution_count > qsFirst.execution_count + AND qsNow.Pass = 2 + AND (qsNow.total_elapsed_time - qsFirst.total_elapsed_time > 1000000 /* Only queries with over 1 second of runtime */ + OR qsNow.total_logical_reads - qsFirst.total_logical_reads > 1000 /* Only queries with over 1000 reads */ + OR qsNow.total_worker_time - qsFirst.total_worker_time > 1000000 /* Only queries with over 1 second of worker time */) + ORDER BY (qsNow.execution_count - COALESCE(qsFirst.execution_count, 0)) DESC) + UPDATE #QueryStats + SET Points = Points + 1 + FROM #QueryStats qs + INNER JOIN qsTop ON qs.ID = qsTop.ID; + + /* Query Stats - Most Resource-Intensive Queries - CheckID 17 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 17',10,1) WITH NOWAIT; + END + + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, QueryText, QueryStatsNowID, QueryStatsFirstID, PlanHandle, QueryHash) + SELECT 17, 210, 'Query Stats', 'Most Resource-Intensive Queries', 'http://www.BrentOzar.com/go/topqueries', + 'Query stats during the sample:' + @LineFeed + + 'Executions: ' + CAST(qsNow.execution_count - (COALESCE(qsFirst.execution_count, 0)) AS NVARCHAR(100)) + @LineFeed + + 'Elapsed Time: ' + CAST(qsNow.total_elapsed_time - (COALESCE(qsFirst.total_elapsed_time, 0)) AS NVARCHAR(100)) + @LineFeed + + 'CPU Time: ' + CAST(qsNow.total_worker_time - (COALESCE(qsFirst.total_worker_time, 0)) AS NVARCHAR(100)) + @LineFeed + + 'Logical Reads: ' + CAST(qsNow.total_logical_reads - (COALESCE(qsFirst.total_logical_reads, 0)) AS NVARCHAR(100)) + @LineFeed + + 'Logical Writes: ' + CAST(qsNow.total_logical_writes - (COALESCE(qsFirst.total_logical_writes, 0)) AS NVARCHAR(100)) + @LineFeed + + 'CLR Time: ' + CAST(qsNow.total_clr_time - (COALESCE(qsFirst.total_clr_time, 0)) AS NVARCHAR(100)) + @LineFeed + + @LineFeed + @LineFeed + 'Query stats since ' + CONVERT(NVARCHAR(100), qsNow.creation_time ,121) + @LineFeed + + 'Executions: ' + CAST(qsNow.execution_count AS NVARCHAR(100)) + + CASE qsTotal.execution_count WHEN 0 THEN '' ELSE (' - Percent of Server Total: ' + CAST(CAST(100.0 * qsNow.execution_count / qsTotal.execution_count AS DECIMAL(6,2)) AS NVARCHAR(100)) + '%') END + @LineFeed + + 'Elapsed Time: ' + CAST(qsNow.total_elapsed_time AS NVARCHAR(100)) + + CASE qsTotal.total_elapsed_time WHEN 0 THEN '' ELSE (' - Percent of Server Total: ' + CAST(CAST(100.0 * qsNow.total_elapsed_time / qsTotal.total_elapsed_time AS DECIMAL(6,2)) AS NVARCHAR(100)) + '%') END + @LineFeed + + 'CPU Time: ' + CAST(qsNow.total_worker_time AS NVARCHAR(100)) + + CASE qsTotal.total_worker_time WHEN 0 THEN '' ELSE (' - Percent of Server Total: ' + CAST(CAST(100.0 * qsNow.total_worker_time / qsTotal.total_worker_time AS DECIMAL(6,2)) AS NVARCHAR(100)) + '%') END + @LineFeed + + 'Logical Reads: ' + CAST(qsNow.total_logical_reads AS NVARCHAR(100)) + + CASE qsTotal.total_logical_reads WHEN 0 THEN '' ELSE (' - Percent of Server Total: ' + CAST(CAST(100.0 * qsNow.total_logical_reads / qsTotal.total_logical_reads AS DECIMAL(6,2)) AS NVARCHAR(100)) + '%') END + @LineFeed + + 'Logical Writes: ' + CAST(qsNow.total_logical_writes AS NVARCHAR(100)) + + CASE qsTotal.total_logical_writes WHEN 0 THEN '' ELSE (' - Percent of Server Total: ' + CAST(CAST(100.0 * qsNow.total_logical_writes / qsTotal.total_logical_writes AS DECIMAL(6,2)) AS NVARCHAR(100)) + '%') END + @LineFeed + + 'CLR Time: ' + CAST(qsNow.total_clr_time AS NVARCHAR(100)) + + CASE qsTotal.total_clr_time WHEN 0 THEN '' ELSE (' - Percent of Server Total: ' + CAST(CAST(100.0 * qsNow.total_clr_time / qsTotal.total_clr_time AS DECIMAL(6,2)) AS NVARCHAR(100)) + '%') END + @LineFeed + + --@LineFeed + @LineFeed + 'Query hash: ' + CAST(qsNow.query_hash AS NVARCHAR(100)) + @LineFeed + + --@LineFeed + @LineFeed + 'Query plan hash: ' + CAST(qsNow.query_plan_hash AS NVARCHAR(100)) + + @LineFeed AS Details, + 'See the URL for tuning tips on why this query may be consuming resources.' AS HowToStopIt, + qp.query_plan, + QueryText = SUBSTRING(st.text, + (qsNow.statement_start_offset / 2) + 1, + ((CASE qsNow.statement_end_offset + WHEN -1 THEN DATALENGTH(st.text) + ELSE qsNow.statement_end_offset + END - qsNow.statement_start_offset) / 2) + 1), + qsNow.ID AS QueryStatsNowID, + qsFirst.ID AS QueryStatsFirstID, + qsNow.plan_handle AS PlanHandle, + qsNow.query_hash + FROM #QueryStats qsNow + INNER JOIN #QueryStats qsTotal ON qsTotal.Pass = 0 + LEFT OUTER JOIN #QueryStats qsFirst ON qsNow.[sql_handle] = qsFirst.[sql_handle] AND qsNow.statement_start_offset = qsFirst.statement_start_offset AND qsNow.statement_end_offset = qsFirst.statement_end_offset AND qsNow.plan_generation_num = qsFirst.plan_generation_num AND qsNow.plan_handle = qsFirst.plan_handle AND qsFirst.Pass = 1 + CROSS APPLY sys.dm_exec_sql_text(qsNow.sql_handle) AS st + CROSS APPLY sys.dm_exec_query_plan(qsNow.plan_handle) AS qp + WHERE qsNow.Points > 0 AND st.text IS NOT NULL AND qp.query_plan IS NOT NULL; + + UPDATE #BlitzFirstResults + SET DatabaseID = CAST(attr.value AS INT), + DatabaseName = DB_NAME(CAST(attr.value AS INT)) + FROM #BlitzFirstResults + CROSS APPLY sys.dm_exec_plan_attributes(#BlitzFirstResults.PlanHandle) AS attr + WHERE attr.attribute = 'dbid'; + + + END; /* IF DATEDIFF(ss, @FinishSampleTime, SYSDATETIMEOFFSET()) > 10 AND @CheckProcedureCache = 1 */ + + + RAISERROR('Analyzing changes between first and second passes of DMVs',10,1) WITH NOWAIT; + + /* Wait Stats - CheckID 6 */ + /* Compare the current wait stats to the sample we took at the start, and insert the top 10 waits. */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 6',10,1) WITH NOWAIT; + END + + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, DetailsInt) + SELECT TOP 10 6 AS CheckID, + 200 AS Priority, + 'Wait Stats' AS FindingGroup, + wNow.wait_type AS Finding, /* IF YOU CHANGE THIS, STUFF WILL BREAK. Other checks look for wait type names in the Finding field. See checks 11, 12 as example. */ + N'https://www.sqlskills.com/help/waits/' + LOWER(wNow.wait_type) + '/' AS URL, + 'For ' + CAST(((wNow.wait_time_ms - COALESCE(wBase.wait_time_ms,0)) / 1000) AS NVARCHAR(100)) + ' seconds over the last ' + CASE @Seconds WHEN 0 THEN (CAST(DATEDIFF(dd,@StartSampleTime,@FinishSampleTime) AS NVARCHAR(10)) + ' days') ELSE (CAST(@Seconds AS NVARCHAR(10)) + ' seconds') END + ', SQL Server was waiting on this particular bottleneck.' + @LineFeed + @LineFeed AS Details, + 'See the URL for more details on how to mitigate this wait type.' AS HowToStopIt, + ((wNow.wait_time_ms - COALESCE(wBase.wait_time_ms,0)) / 1000) AS DetailsInt + FROM #WaitStats wNow + LEFT OUTER JOIN #WaitStats wBase ON wNow.wait_type = wBase.wait_type AND wNow.SampleTime > wBase.SampleTime + WHERE wNow.wait_time_ms > (wBase.wait_time_ms + (.5 * (DATEDIFF(ss,@StartSampleTime,@FinishSampleTime)) * 1000)) /* Only look for things we've actually waited on for half of the time or more */ + ORDER BY (wNow.wait_time_ms - COALESCE(wBase.wait_time_ms,0)) DESC; + + /* Server Performance - Poison Wait Detected - CheckID 30 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 30',10,1) WITH NOWAIT; + END + + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, DetailsInt) + SELECT 30 AS CheckID, + 10 AS Priority, + 'Server Performance' AS FindingGroup, + 'Poison Wait Detected: ' + wNow.wait_type AS Finding, + N'http://www.brentozar.com/go/poison/#' + wNow.wait_type AS URL, + 'For ' + CAST(((wNow.wait_time_ms - COALESCE(wBase.wait_time_ms,0)) / 1000) AS NVARCHAR(100)) + ' seconds over the last ' + CASE @Seconds WHEN 0 THEN (CAST(DATEDIFF(dd,@StartSampleTime,@FinishSampleTime) AS NVARCHAR(10)) + ' days') ELSE (CAST(@Seconds AS NVARCHAR(10)) + ' seconds') END + ', SQL Server was waiting on this particular bottleneck.' + @LineFeed + @LineFeed AS Details, + 'See the URL for more details on how to mitigate this wait type.' AS HowToStopIt, + ((wNow.wait_time_ms - COALESCE(wBase.wait_time_ms,0)) / 1000) AS DetailsInt + FROM #WaitStats wNow + LEFT OUTER JOIN #WaitStats wBase ON wNow.wait_type = wBase.wait_type AND wNow.SampleTime > wBase.SampleTime + WHERE wNow.wait_type IN ('IO_QUEUE_LIMIT', 'IO_RETRY', 'LOG_RATE_GOVERNOR', 'POOL_LOG_RATE_GOVERNOR', 'PREEMPTIVE_DEBUG', 'RESMGR_THROTTLED', 'RESOURCE_SEMAPHORE', 'RESOURCE_SEMAPHORE_QUERY_COMPILE','SE_REPL_CATCHUP_THROTTLE','SE_REPL_COMMIT_ACK','SE_REPL_COMMIT_TURN','SE_REPL_ROLLBACK_ACK','SE_REPL_SLOW_SECONDARY_THROTTLE','THREADPOOL') + AND wNow.wait_time_ms > (wBase.wait_time_ms + 1000); + + + /* Server Performance - Slow Data File Reads - CheckID 11 */ + IF EXISTS (SELECT * FROM #BlitzFirstResults WHERE Finding LIKE 'PAGEIOLATCH%') + BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 11',10,1) WITH NOWAIT; + END + + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, DatabaseID, DatabaseName) + SELECT TOP 10 11 AS CheckID, + 50 AS Priority, + 'Server Performance' AS FindingGroup, + 'Slow Data File Reads' AS Finding, + 'http://www.BrentOzar.com/go/slow/' AS URL, + 'Your server is experiencing PAGEIOLATCH% waits due to slow data file reads. This file is one of the reasons why.' + @LineFeed + + 'File: ' + fNow.PhysicalName + @LineFeed + + 'Number of reads during the sample: ' + CAST((fNow.num_of_reads - fBase.num_of_reads) AS NVARCHAR(20)) + @LineFeed + + 'Seconds spent waiting on storage for these reads: ' + CAST(((fNow.io_stall_read_ms - fBase.io_stall_read_ms) / 1000.0) AS NVARCHAR(20)) + @LineFeed + + 'Average read latency during the sample: ' + CAST(((fNow.io_stall_read_ms - fBase.io_stall_read_ms) / (fNow.num_of_reads - fBase.num_of_reads) ) AS NVARCHAR(20)) + ' milliseconds' + @LineFeed + + 'Microsoft guidance for data file read speed: 20ms or less.' + @LineFeed + @LineFeed AS Details, + 'See the URL for more details on how to mitigate this wait type.' AS HowToStopIt, + fNow.DatabaseID, + fNow.DatabaseName + FROM #FileStats fNow + INNER JOIN #FileStats fBase ON fNow.DatabaseID = fBase.DatabaseID AND fNow.FileID = fBase.FileID AND fNow.SampleTime > fBase.SampleTime AND fNow.num_of_reads > fBase.num_of_reads AND fNow.io_stall_read_ms > (fBase.io_stall_read_ms + 1000) + WHERE (fNow.io_stall_read_ms - fBase.io_stall_read_ms) / (fNow.num_of_reads - fBase.num_of_reads) >= @FileLatencyThresholdMS + AND fNow.TypeDesc = 'ROWS' + ORDER BY (fNow.io_stall_read_ms - fBase.io_stall_read_ms) / (fNow.num_of_reads - fBase.num_of_reads) DESC; + END; + + /* Server Performance - Slow Log File Writes - CheckID 12 */ + IF EXISTS (SELECT * FROM #BlitzFirstResults WHERE Finding LIKE 'WRITELOG%') + BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 12',10,1) WITH NOWAIT; + END + + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, DatabaseID, DatabaseName) + SELECT TOP 10 12 AS CheckID, + 50 AS Priority, + 'Server Performance' AS FindingGroup, + 'Slow Log File Writes' AS Finding, + 'http://www.BrentOzar.com/go/slow/' AS URL, + 'Your server is experiencing WRITELOG waits due to slow log file writes. This file is one of the reasons why.' + @LineFeed + + 'File: ' + fNow.PhysicalName + @LineFeed + + 'Number of writes during the sample: ' + CAST((fNow.num_of_writes - fBase.num_of_writes) AS NVARCHAR(20)) + @LineFeed + + 'Seconds spent waiting on storage for these writes: ' + CAST(((fNow.io_stall_write_ms - fBase.io_stall_write_ms) / 1000.0) AS NVARCHAR(20)) + @LineFeed + + 'Average write latency during the sample: ' + CAST(((fNow.io_stall_write_ms - fBase.io_stall_write_ms) / (fNow.num_of_writes - fBase.num_of_writes) ) AS NVARCHAR(20)) + ' milliseconds' + @LineFeed + + 'Microsoft guidance for log file write speed: 3ms or less.' + @LineFeed + @LineFeed AS Details, + 'See the URL for more details on how to mitigate this wait type.' AS HowToStopIt, + fNow.DatabaseID, + fNow.DatabaseName + FROM #FileStats fNow + INNER JOIN #FileStats fBase ON fNow.DatabaseID = fBase.DatabaseID AND fNow.FileID = fBase.FileID AND fNow.SampleTime > fBase.SampleTime AND fNow.num_of_writes > fBase.num_of_writes AND fNow.io_stall_write_ms > (fBase.io_stall_write_ms + 1000) + WHERE (fNow.io_stall_write_ms - fBase.io_stall_write_ms) / (fNow.num_of_writes - fBase.num_of_writes) >= @FileLatencyThresholdMS + AND fNow.TypeDesc = 'LOG' + ORDER BY (fNow.io_stall_write_ms - fBase.io_stall_write_ms) / (fNow.num_of_writes - fBase.num_of_writes) DESC; + END; + + + /* SQL Server Internal Maintenance - Log File Growing - CheckID 13 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 13',10,1) WITH NOWAIT; + END + + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) + SELECT 13 AS CheckID, + 1 AS Priority, + 'SQL Server Internal Maintenance' AS FindingGroup, + 'Log File Growing' AS Finding, + 'http://www.BrentOzar.com/askbrent/file-growing/' AS URL, + 'Number of growths during the sample: ' + CAST(ps.value_delta AS NVARCHAR(20)) + @LineFeed + + 'Determined by sampling Perfmon counter ' + ps.object_name + ' - ' + ps.counter_name + @LineFeed AS Details, + 'Pre-grow data and log files during maintenance windows so that they do not grow during production loads. See the URL for more details.' AS HowToStopIt + FROM #PerfmonStats ps + WHERE ps.Pass = 2 + AND object_name = @ServiceName + ':Databases' + AND counter_name = 'Log Growths' + AND value_delta > 0; + + + /* SQL Server Internal Maintenance - Log File Shrinking - CheckID 14 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 14',10,1) WITH NOWAIT; + END + + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) + SELECT 14 AS CheckID, + 1 AS Priority, + 'SQL Server Internal Maintenance' AS FindingGroup, + 'Log File Shrinking' AS Finding, + 'http://www.BrentOzar.com/askbrent/file-shrinking/' AS URL, + 'Number of shrinks during the sample: ' + CAST(ps.value_delta AS NVARCHAR(20)) + @LineFeed + + 'Determined by sampling Perfmon counter ' + ps.object_name + ' - ' + ps.counter_name + @LineFeed AS Details, + 'Pre-grow data and log files during maintenance windows so that they do not grow during production loads. See the URL for more details.' AS HowToStopIt + FROM #PerfmonStats ps + WHERE ps.Pass = 2 + AND object_name = @ServiceName + ':Databases' + AND counter_name = 'Log Shrinks' + AND value_delta > 0; + + /* Query Problems - Compilations/Sec High - CheckID 15 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 15',10,1) WITH NOWAIT; + END + + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) + SELECT 15 AS CheckID, + 50 AS Priority, + 'Query Problems' AS FindingGroup, + 'Compilations/Sec High' AS Finding, + 'http://www.BrentOzar.com/askbrent/compilations/' AS URL, + 'Number of batch requests during the sample: ' + CAST(ps.value_delta AS NVARCHAR(20)) + @LineFeed + + 'Number of compilations during the sample: ' + CAST(psComp.value_delta AS NVARCHAR(20)) + @LineFeed + + 'For OLTP environments, Microsoft recommends that 90% of batch requests should hit the plan cache, and not be compiled from scratch. We are exceeding that threshold.' + @LineFeed AS Details, + 'To find the queries that are compiling, start with:' + @LineFeed + + 'sp_BlitzCache @SortOrder = ''recent compilations''' + @LineFeed + + 'If dynamic SQL or non-parameterized strings are involved, consider enabling Forced Parameterization. See the URL for more details.' AS HowToStopIt + FROM #PerfmonStats ps + INNER JOIN #PerfmonStats psComp ON psComp.Pass = 2 AND psComp.object_name = @ServiceName + ':SQL Statistics' AND psComp.counter_name = 'SQL Compilations/sec' AND psComp.value_delta > 0 + WHERE ps.Pass = 2 + AND ps.object_name = @ServiceName + ':SQL Statistics' + AND ps.counter_name = 'Batch Requests/sec' + AND psComp.value_delta > 75 /* Because sp_BlitzFirst does around 50 compilations and re-compilations */ + AND (psComp.value_delta > (10 * @Seconds) OR psComp.value_delta > ps.value_delta) /* Either doing 10 compilations per second, or more compilations than queries */ + AND (psComp.value_delta * 10) > ps.value_delta; /* Compilations are more than 10% of batch requests per second */ + + /* Query Problems - Re-Compilations/Sec High - CheckID 16 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 16',10,1) WITH NOWAIT; + END + + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) + SELECT 16 AS CheckID, + 50 AS Priority, + 'Query Problems' AS FindingGroup, + 'Re-Compilations/Sec High' AS Finding, + 'http://www.BrentOzar.com/askbrent/recompilations/' AS URL, + 'Number of batch requests during the sample: ' + CAST(ps.value_delta AS NVARCHAR(20)) + @LineFeed + + 'Number of recompilations during the sample: ' + CAST(psComp.value_delta AS NVARCHAR(20)) + @LineFeed + + 'More than 10% of our queries are being recompiled. This is typically due to statistics changing on objects.' + @LineFeed AS Details, + 'To find the queries that are being forced to recompile, start with:' + @LineFeed + + 'sp_BlitzCache @SortOrder = ''recent compilations''' + @LineFeed + + 'Examine those plans to find out which objects are changing so quickly that they hit the stats update threshold. See the URL for more details.' AS HowToStopIt + FROM #PerfmonStats ps + INNER JOIN #PerfmonStats psComp ON psComp.Pass = 2 AND psComp.object_name = @ServiceName + ':SQL Statistics' AND psComp.counter_name = 'SQL Re-Compilations/sec' AND psComp.value_delta > 0 + WHERE ps.Pass = 2 + AND ps.object_name = @ServiceName + ':SQL Statistics' + AND ps.counter_name = 'Batch Requests/sec' + AND psComp.value_delta > 75 /* Because sp_BlitzFirst does around 50 compilations and re-compilations */ + AND (psComp.value_delta > (10 * @Seconds) OR psComp.value_delta > ps.value_delta) /* Either doing 10 recompilations per second, or more recompilations than queries */ + AND (psComp.value_delta * 10) > ps.value_delta; /* Recompilations are more than 10% of batch requests per second */ + + /* Table Problems - Forwarded Fetches/Sec High - CheckID 29 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 29',10,1) WITH NOWAIT; + END + + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) + SELECT 29 AS CheckID, + 40 AS Priority, + 'Table Problems' AS FindingGroup, + 'Forwarded Fetches/Sec High' AS Finding, + 'https://BrentOzar.com/go/fetch/' AS URL, + CAST(ps.value_delta AS NVARCHAR(20)) + ' forwarded fetches (from SQLServer:Access Methods counter)' + @LineFeed + + 'Check your heaps: they need to be rebuilt, or they need a clustered index applied.' + @LineFeed AS Details, + 'Rebuild your heaps. If you use Ola Hallengren maintenance scripts, those do not rebuild heaps by default: https://www.brentozar.com/archive/2016/07/fix-forwarded-records/' AS HowToStopIt + FROM #PerfmonStats ps + INNER JOIN #PerfmonStats psComp ON psComp.Pass = 2 AND psComp.object_name = @ServiceName + ':Access Methods' AND psComp.counter_name = 'Forwarded Records/sec' AND psComp.value_delta > 100 + WHERE ps.Pass = 2 + AND ps.object_name = @ServiceName + ':Access Methods' + AND ps.counter_name = 'Forwarded Records/sec' + AND ps.value_delta > (100 * @Seconds); /* Ignore servers sitting idle */ + + /* Check for temp objects with high forwarded fetches. + This has to be done as dynamic SQL because we have to execute OBJECT_NAME inside TempDB. */ + IF @@ROWCOUNT > 0 + BEGIN + SET @StringToExecute = N'USE tempdb; + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) + SELECT TOP 10 29 AS CheckID, + 40 AS Priority, + ''Table Problems'' AS FindingGroup, + ''Forwarded Fetches/Sec High: TempDB Object'' AS Finding, + ''https://BrentOzar.com/go/fetch/'' AS URL, + CAST(COALESCE(os.forwarded_fetch_count,0) - COALESCE(os_prior.forwarded_fetch_count,0) AS NVARCHAR(20)) + '' forwarded fetches on '' + + CASE WHEN OBJECT_NAME(os.object_id) IS NULL THEN ''an unknown table '' + WHEN LEN(OBJECT_NAME(os.object_id)) < 50 THEN ''a table variable, internal identifier '' + OBJECT_NAME(os.object_id) + ELSE ''a temp table '' + OBJECT_NAME(os.object_id) + END AS Details, + ''Look through your source code to find the object creating these objects, and tune the creation and population to reduce fetches. See the URL for details.'' AS HowToStopIt + FROM tempdb.sys.dm_db_index_operational_stats(DB_ID(''tempdb''), NULL, NULL, NULL) os + LEFT OUTER JOIN #TempdbOperationalStats os_prior ON os.object_id = os_prior.object_id + AND os.forwarded_fetch_count > os_prior.forwarded_fetch_count + WHERE os.database_id = DB_ID(''tempdb'') + AND os.forwarded_fetch_count - COALESCE(os_prior.forwarded_fetch_count,0) > 100 + ORDER BY os.forwarded_fetch_count DESC;' + + EXECUTE sp_executesql @StringToExecute; + END + + /* In-Memory OLTP - Garbage Collection in Progress - CheckID 31 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 31',10,1) WITH NOWAIT; + END + + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) + SELECT 31 AS CheckID, + 50 AS Priority, + 'In-Memory OLTP' AS FindingGroup, + 'Garbage Collection in Progress' AS Finding, + 'https://BrentOzar.com/go/garbage/' AS URL, + CAST(ps.value_delta AS NVARCHAR(50)) + ' rows processed (from SQL Server YYYY XTP Garbage Collection:Rows processed/sec counter)' + @LineFeed + + 'This can happen due to memory pressure (causing In-Memory OLTP to shrink its footprint) or' + @LineFeed + + 'due to transactional workloads that constantly insert/delete data.' AS Details, + 'Sadly, you cannot choose when garbage collection occurs. This is one of the many gotchas of Hekaton. Learn more: http://nedotter.com/archive/2016/04/row-version-lifecycle-for-in-memory-oltp/' AS HowToStopIt + FROM #PerfmonStats ps + INNER JOIN #PerfmonStats psComp ON psComp.Pass = 2 AND psComp.object_name LIKE '%XTP Garbage Collection' AND psComp.counter_name = 'Rows processed/sec' AND psComp.value_delta > 100 + WHERE ps.Pass = 2 + AND ps.object_name LIKE '%XTP Garbage Collection' + AND ps.counter_name = 'Rows processed/sec' + AND ps.value_delta > (100 * @Seconds); /* Ignore servers sitting idle */ + + /* In-Memory OLTP - Transactions Aborted - CheckID 32 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 32',10,1) WITH NOWAIT; + END + + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) + SELECT 32 AS CheckID, + 100 AS Priority, + 'In-Memory OLTP' AS FindingGroup, + 'Transactions Aborted' AS Finding, + 'https://BrentOzar.com/go/aborted/' AS URL, + CAST(ps.value_delta AS NVARCHAR(50)) + ' transactions aborted (from SQL Server YYYY XTP Transactions:Transactions aborted/sec counter)' + @LineFeed + + 'This may indicate that data is changing, or causing folks to retry their transactions, thereby increasing load.' AS Details, + 'Dig into your In-Memory OLTP transactions to figure out which ones are failing and being retried.' AS HowToStopIt + FROM #PerfmonStats ps + INNER JOIN #PerfmonStats psComp ON psComp.Pass = 2 AND psComp.object_name LIKE '%XTP Transactions' AND psComp.counter_name = 'Transactions aborted/sec' AND psComp.value_delta > 100 + WHERE ps.Pass = 2 + AND ps.object_name LIKE '%XTP Transactions' + AND ps.counter_name = 'Transactions aborted/sec' + AND ps.value_delta > (10 * @Seconds); /* Ignore servers sitting idle */ + + /* Query Problems - Suboptimal Plans/Sec High - CheckID 33 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 33',10,1) WITH NOWAIT; + END + + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) + SELECT 32 AS CheckID, + 100 AS Priority, + 'Query Problems' AS FindingGroup, + 'Suboptimal Plans/Sec High' AS Finding, + 'https://BrentOzar.com/go/suboptimal/' AS URL, + CAST(ps.value_delta AS NVARCHAR(50)) + ' plans reported in the ' + CAST(ps.instance_name AS NVARCHAR(100)) + ' workload group (from Workload GroupStats:Suboptimal plans/sec counter)' + @LineFeed + + 'Even if you are not using Resource Governor, it still tracks information about user queries, memory grants, etc.' AS Details, + 'Check out sp_BlitzCache to get more information about recent queries, or try sp_BlitzWho to see currently running queries.' AS HowToStopIt + FROM #PerfmonStats ps + INNER JOIN #PerfmonStats psComp ON psComp.Pass = 2 AND psComp.object_name = @ServiceName + ':Workload GroupStats' AND psComp.counter_name = 'Suboptimal plans/sec' AND psComp.value_delta > 100 + WHERE ps.Pass = 2 + AND ps.object_name = @ServiceName + ':Workload GroupStats' + AND ps.counter_name = 'Suboptimal plans/sec' + AND ps.value_delta > (10 * @Seconds); /* Ignore servers sitting idle */ + + /* Azure Performance - Database is Maxed Out - CheckID 41 */ + IF SERVERPROPERTY('Edition') = 'SQL Azure' + BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 41',10,1) WITH NOWAIT; + END + + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) + SELECT 41 AS CheckID, + 10 AS Priority, + 'Azure Performance' AS FindingGroup, + 'Database is Maxed Out' AS Finding, + 'https://BrentOzar.com/go/maxedout' AS URL, + N'At ' + CONVERT(NVARCHAR(100), s.end_time ,121) + N', your database approached (or hit) your DTU limits:' + @LineFeed + + N'Average CPU percent: ' + CAST(avg_cpu_percent AS NVARCHAR(50)) + @LineFeed + + N'Average data IO percent: ' + CAST(avg_data_io_percent AS NVARCHAR(50)) + @LineFeed + + N'Average log write percent: ' + CAST(avg_log_write_percent AS NVARCHAR(50)) + @LineFeed + + N'Max worker percent: ' + CAST(max_worker_percent AS NVARCHAR(50)) + @LineFeed + + N'Max session percent: ' + CAST(max_session_percent AS NVARCHAR(50)) AS Details, + 'Tune your queries or indexes with sp_BlitzCache or sp_BlitzIndex, or consider upgrading to a higher DTU level.' AS HowToStopIt + FROM sys.dm_db_resource_stats s + WHERE s.end_time >= DATEADD(MI, -5, GETDATE()) + AND (avg_cpu_percent > 90 + OR avg_data_io_percent >= 90 + OR avg_log_write_percent >=90 + OR max_worker_percent >= 90 + OR max_session_percent >= 90); + END + + /* Server Info - Batch Requests per Sec - CheckID 19 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 19',10,1) WITH NOWAIT; + END + + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, DetailsInt) + SELECT 19 AS CheckID, + 250 AS Priority, + 'Server Info' AS FindingGroup, + 'Batch Requests per Sec' AS Finding, + 'http://www.BrentOzar.com/go/measure' AS URL, + CAST(CAST(ps.value_delta AS MONEY) / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS NVARCHAR(20)) AS Details, + ps.value_delta / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS DetailsInt + FROM #PerfmonStats ps + INNER JOIN #PerfmonStats ps1 ON ps.object_name = ps1.object_name AND ps.counter_name = ps1.counter_name AND ps1.Pass = 1 + WHERE ps.Pass = 2 + AND ps.object_name = @ServiceName + ':SQL Statistics' + AND ps.counter_name = 'Batch Requests/sec'; + + + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','SQL Compilations/sec', NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','SQL Re-Compilations/sec', NULL); + + /* Server Info - SQL Compilations/sec - CheckID 25 */ + IF @ExpertMode = 1 + BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 25',10,1) WITH NOWAIT; + END + + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, DetailsInt) + SELECT 25 AS CheckID, + 250 AS Priority, + 'Server Info' AS FindingGroup, + 'SQL Compilations per Sec' AS Finding, + 'http://www.BrentOzar.com/go/measure' AS URL, + CAST(ps.value_delta / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS NVARCHAR(20)) AS Details, + ps.value_delta / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS DetailsInt + FROM #PerfmonStats ps + INNER JOIN #PerfmonStats ps1 ON ps.object_name = ps1.object_name AND ps.counter_name = ps1.counter_name AND ps1.Pass = 1 + WHERE ps.Pass = 2 + AND ps.object_name = @ServiceName + ':SQL Statistics' + AND ps.counter_name = 'SQL Compilations/sec'; + END + + /* Server Info - SQL Re-Compilations/sec - CheckID 26 */ + IF @ExpertMode = 1 + BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 26',10,1) WITH NOWAIT; + END + + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, DetailsInt) + SELECT 26 AS CheckID, + 250 AS Priority, + 'Server Info' AS FindingGroup, + 'SQL Re-Compilations per Sec' AS Finding, + 'http://www.BrentOzar.com/go/measure' AS URL, + CAST(ps.value_delta / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS NVARCHAR(20)) AS Details, + ps.value_delta / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS DetailsInt + FROM #PerfmonStats ps + INNER JOIN #PerfmonStats ps1 ON ps.object_name = ps1.object_name AND ps.counter_name = ps1.counter_name AND ps1.Pass = 1 + WHERE ps.Pass = 2 + AND ps.object_name = @ServiceName + ':SQL Statistics' + AND ps.counter_name = 'SQL Re-Compilations/sec'; + END + + /* Server Info - Wait Time per Core per Sec - CheckID 20 */ + IF @Seconds > 0 + BEGIN; + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 20',10,1) WITH NOWAIT; + END; + + WITH waits1(SampleTime, waits_ms) AS (SELECT SampleTime, SUM(ws1.wait_time_ms) FROM #WaitStats ws1 WHERE ws1.Pass = 1 GROUP BY SampleTime), + waits2(SampleTime, waits_ms) AS (SELECT SampleTime, SUM(ws2.wait_time_ms) FROM #WaitStats ws2 WHERE ws2.Pass = 2 GROUP BY SampleTime), + cores(cpu_count) AS (SELECT SUM(1) FROM sys.dm_os_schedulers WHERE status = 'VISIBLE ONLINE' AND is_online = 1) + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, DetailsInt) + SELECT 20 AS CheckID, + 250 AS Priority, + 'Server Info' AS FindingGroup, + 'Wait Time per Core per Sec' AS Finding, + 'http://www.BrentOzar.com/go/measure' AS URL, + CAST((CAST(waits2.waits_ms - waits1.waits_ms AS MONEY)) / 1000 / i.cpu_count / DATEDIFF(ss, waits1.SampleTime, waits2.SampleTime) AS NVARCHAR(20)) AS Details, + (waits2.waits_ms - waits1.waits_ms) / 1000 / i.cpu_count / DATEDIFF(ss, waits1.SampleTime, waits2.SampleTime) AS DetailsInt + FROM cores i + CROSS JOIN waits1 + CROSS JOIN waits2; + END; + + IF @Seconds > 0 + BEGIN + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) + SELECT + 47 AS CheckId, + 50 AS Priority, + 'Query Problems' AS FindingsGroup, + 'High Percentage Of Runnable Queries' AS Finding, + 'https://erikdarlingdata.com/go/RunnableQueue/' AS URL, + 'On the ' + + CASE WHEN y.pass = 1 + THEN '1st' + ELSE '2nd' + END + + ' pass, ' + + RTRIM(y.runnable_pct) + + '% of your queries were waiting to get on a CPU to run. ' + + ' This can indicate CPU pressure.' + FROM + ( + SELECT + 2 AS pass, + x.total, + x.runnable, + CONVERT(decimal(5,2), + ( + x.runnable / + (1. * NULLIF(x.total, 0)) + ) + ) * 100. AS runnable_pct + FROM + ( + SELECT + COUNT_BIG(*) AS total, + SUM(CASE WHEN status = 'runnable' + THEN 1 + ELSE 0 + END) AS runnable + FROM sys.dm_exec_requests + WHERE session_id > 50 + ) AS x + ) AS y + WHERE y.runnable_pct > 20.; + END + + /* If we're waiting 30+ seconds, run these checks at the end. + We get this data from the ring buffers, and it's only updated once per minute, so might + as well get it now - whereas if we're checking 30+ seconds, it might get updated by the + end of our sp_BlitzFirst session. */ + IF @Seconds >= 30 + BEGIN + /* Server Performance - High CPU Utilization CheckID 24 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 24',10,1) WITH NOWAIT; + END + + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) + SELECT 24, 50, 'Server Performance', 'High CPU Utilization', CAST(100 - SystemIdle AS NVARCHAR(20)) + N'%. Ring buffer details: ' + CAST(record AS NVARCHAR(4000)), 100 - SystemIdle, 'http://www.BrentOzar.com/go/cpu' + FROM ( + SELECT record, + record.value('(./Record/SchedulerMonitorEvent/SystemHealth/SystemIdle)[1]', 'int') AS SystemIdle + FROM ( + SELECT TOP 1 CONVERT(XML, record) AS record + FROM sys.dm_os_ring_buffers + WHERE ring_buffer_type = N'RING_BUFFER_SCHEDULER_MONITOR' + AND record LIKE '%%' + ORDER BY timestamp DESC) AS rb + ) AS y + WHERE 100 - SystemIdle >= 50; + + /* Server Performance - CPU Utilization CheckID 23 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 23',10,1) WITH NOWAIT; + END + + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) + SELECT 23, 250, 'Server Info', 'CPU Utilization', CAST(100 - SystemIdle AS NVARCHAR(20)) + N'%. Ring buffer details: ' + CAST(record AS NVARCHAR(4000)), 100 - SystemIdle, 'http://www.BrentOzar.com/go/cpu' + FROM ( + SELECT record, + record.value('(./Record/SchedulerMonitorEvent/SystemHealth/SystemIdle)[1]', 'int') AS SystemIdle + FROM ( + SELECT TOP 1 CONVERT(XML, record) AS record + FROM sys.dm_os_ring_buffers + WHERE ring_buffer_type = N'RING_BUFFER_SCHEDULER_MONITOR' + AND record LIKE '%%' + ORDER BY timestamp DESC) AS rb + ) AS y; + + END; /* IF @Seconds >= 30 */ + + + /* If we didn't find anything, apologize. */ + IF NOT EXISTS (SELECT * FROM #BlitzFirstResults WHERE Priority < 250) + BEGIN + + INSERT INTO #BlitzFirstResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + VALUES ( -1 , + 1 , + 'No Problems Found' , + 'From Your Community Volunteers' , + 'http://FirstResponderKit.org/' , + 'Try running our more in-depth checks with sp_Blitz, or there may not be an unusual SQL Server performance problem. ' + ); + + END; /*IF NOT EXISTS (SELECT * FROM #BlitzFirstResults) */ + + /* Add credits for the nice folks who put so much time into building and maintaining this for free: */ + INSERT INTO #BlitzFirstResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + VALUES ( -1 , + 255 , + 'Thanks!' , + 'From Your Community Volunteers' , + 'http://FirstResponderKit.org/' , + 'To get help or add your own contributions, join us at http://FirstResponderKit.org.' + ); + + INSERT INTO #BlitzFirstResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + + ) + VALUES ( -1 , + 0 , + 'sp_BlitzFirst ' + CAST(CONVERT(DATETIMEOFFSET, @VersionDate, 102) AS VARCHAR(100)), + 'From Your Community Volunteers' , + 'http://FirstResponderKit.org/' , + 'We hope you found this tool useful.' + ); + + /* Outdated sp_BlitzFirst - sp_BlitzFirst is Over 6 Months Old - CheckID 27 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 27',10,1) WITH NOWAIT; + END + + IF DATEDIFF(MM, @VersionDate, SYSDATETIMEOFFSET()) > 6 + BEGIN + INSERT INTO #BlitzFirstResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT 27 AS CheckID , + 0 AS Priority , + 'Outdated sp_BlitzFirst' AS FindingsGroup , + 'sp_BlitzFirst is Over 6 Months Old' AS Finding , + 'http://FirstResponderKit.org/' AS URL , + 'Some things get better with age, like fine wine and your T-SQL. However, sp_BlitzFirst is not one of those things - time to go download the current one.' AS Details; + END; + + IF @CheckServerInfo = 0 /* Github #1680 */ + BEGIN + DELETE #BlitzFirstResults + WHERE FindingsGroup = 'Server Info'; + END + + RAISERROR('Analysis finished, outputting results',10,1) WITH NOWAIT; + + + /* If they want to run sp_BlitzCache and export to table, go for it. */ + IF @OutputTableNameBlitzCache IS NOT NULL + AND @OutputDatabaseName IS NOT NULL + AND @OutputSchemaName IS NOT NULL + AND EXISTS ( SELECT * + FROM sys.databases + WHERE QUOTENAME([name]) = @OutputDatabaseName) + BEGIN + + + RAISERROR('Calling sp_BlitzCache',10,1) WITH NOWAIT; + + + /* If they have an newer version of sp_BlitzCache that supports @MinutesBack and @CheckDateOverride */ + IF EXISTS (SELECT * FROM sys.objects o + INNER JOIN sys.parameters pMB ON o.object_id = pMB.object_id AND pMB.name = '@MinutesBack' + INNER JOIN sys.parameters pCDO ON o.object_id = pCDO.object_id AND pCDO.name = '@CheckDateOverride' + WHERE o.name = 'sp_BlitzCache') + BEGIN + /* Get the most recent sp_BlitzCache execution before this one - don't use sp_BlitzFirst because user logs are added in there at any time */ + SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' + + @OutputDatabaseName + + '.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''' + + @OutputSchemaName + ''' AND QUOTENAME(TABLE_NAME) = ''' + + QUOTENAME(@OutputTableNameBlitzCache) + ''') SELECT TOP 1 @BlitzCacheMinutesBack = DATEDIFF(MI,CheckDate,SYSDATETIMEOFFSET()) FROM ' + + @OutputDatabaseName + '.' + + @OutputSchemaName + '.' + + QUOTENAME(@OutputTableNameBlitzCache) + + ' WHERE ServerName = ''' + CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128)) + ''' ORDER BY CheckDate DESC;'; + EXEC sp_executesql @StringToExecute, N'@BlitzCacheMinutesBack INT OUTPUT', @BlitzCacheMinutesBack OUTPUT; + + /* If there's no data, let's just analyze the last 15 minutes of the plan cache */ + IF @BlitzCacheMinutesBack IS NULL OR @BlitzCacheMinutesBack < 1 OR @BlitzCacheMinutesBack > 60 + SET @BlitzCacheMinutesBack = 15; + + EXEC sp_BlitzCache + @OutputDatabaseName = @UnquotedOutputDatabaseName, + @OutputSchemaName = @UnquotedOutputSchemaName, + @OutputTableName = @OutputTableNameBlitzCache, + @CheckDateOverride = @StartSampleTime, + @SortOrder = 'all', + @SkipAnalysis = @BlitzCacheSkipAnalysis, + @MinutesBack = @BlitzCacheMinutesBack, + @Debug = @Debug; + + /* Delete history older than @OutputTableRetentionDays */ + SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' + + @OutputDatabaseName + + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' + + @OutputSchemaName + ''') DELETE ' + + @OutputDatabaseName + '.' + + @OutputSchemaName + '.' + + QUOTENAME(@OutputTableNameBlitzCache) + + ' WHERE ServerName = @SrvName AND CheckDate < @CheckDate;'; + EXEC sp_executesql @StringToExecute, + N'@SrvName NVARCHAR(128), @CheckDate date', + @LocalServerName, @OutputTableCleanupDate; + + + END; + + ELSE + BEGIN + /* No sp_BlitzCache found, or it's outdated - CheckID 36 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 36',10,1) WITH NOWAIT; + END + + INSERT INTO #BlitzFirstResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT 36 AS CheckID , + 0 AS Priority , + 'Outdated or Missing sp_BlitzCache' AS FindingsGroup , + 'Update Your sp_BlitzCache' AS Finding , + 'http://FirstResponderKit.org/' AS URL , + 'You passed in @OutputTableNameBlitzCache, but we need a newer version of sp_BlitzCache in master or the current database.' AS Details; + END; + + RAISERROR('sp_BlitzCache Finished',10,1) WITH NOWAIT; + + END; /* End running sp_BlitzCache */ + + /* @OutputTableName lets us export the results to a permanent table */ + IF @OutputDatabaseName IS NOT NULL + AND @OutputSchemaName IS NOT NULL + AND @OutputTableName IS NOT NULL + AND @OutputTableName NOT LIKE '#%' + AND EXISTS ( SELECT * + FROM sys.databases + WHERE QUOTENAME([name]) = @OutputDatabaseName) + BEGIN + SET @StringToExecute = 'USE ' + + @OutputDatabaseName + + '; IF EXISTS(SELECT * FROM ' + + @OutputDatabaseName + + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' + + @OutputSchemaName + + ''') AND NOT EXISTS (SELECT * FROM ' + + @OutputDatabaseName + + '.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''' + + @OutputSchemaName + ''' AND QUOTENAME(TABLE_NAME) = ''' + + @OutputTableName + ''') CREATE TABLE ' + + @OutputSchemaName + '.' + + @OutputTableName + + ' (ID INT IDENTITY(1,1) NOT NULL, + ServerName NVARCHAR(128), + CheckDate DATETIMEOFFSET, + CheckID INT NOT NULL, + Priority TINYINT NOT NULL, + FindingsGroup VARCHAR(50) NOT NULL, + Finding VARCHAR(200) NOT NULL, + URL VARCHAR(200) NOT NULL, + Details NVARCHAR(4000) NULL, + HowToStopIt [XML] NULL, + QueryPlan [XML] NULL, + QueryText NVARCHAR(MAX) NULL, + StartTime DATETIMEOFFSET NULL, + LoginName NVARCHAR(128) NULL, + NTUserName NVARCHAR(128) NULL, + OriginalLoginName NVARCHAR(128) NULL, + ProgramName NVARCHAR(128) NULL, + HostName NVARCHAR(128) NULL, + DatabaseID INT NULL, + DatabaseName NVARCHAR(128) NULL, + OpenTransactionCount INT NULL, + DetailsInt INT NULL, + QueryHash BINARY(8) NULL, + JoinKey AS ServerName + CAST(CheckDate AS NVARCHAR(50)), + PRIMARY KEY CLUSTERED (ID ASC));'; + + EXEC(@StringToExecute); + + /* If the table doesn't have the new QueryHash column, add it. See Github #2162. */ + SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableName; + SET @StringToExecute = N'IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns + WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''QueryHash'') + ALTER TABLE ' + @ObjectFullName + N' ADD QueryHash BINARY(8) NULL;'; + EXEC(@StringToExecute); + + /* If the table doesn't have the new JoinKey computed column, add it. See Github #2164. */ + SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableName; + SET @StringToExecute = N'IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns + WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''JoinKey'') + ALTER TABLE ' + @ObjectFullName + N' ADD JoinKey AS ServerName + CAST(CheckDate AS NVARCHAR(50));'; + EXEC(@StringToExecute); + + SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' + + @OutputDatabaseName + + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' + + @OutputSchemaName + ''') INSERT ' + + @OutputDatabaseName + '.' + + @OutputSchemaName + '.' + + @OutputTableName + + ' (ServerName, CheckDate, CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, QueryText, StartTime, LoginName, NTUserName, OriginalLoginName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, DetailsInt, QueryHash) SELECT ' + + ' @SrvName, @CheckDate, CheckID, Priority, FindingsGroup, Finding, URL, LEFT(Details,4000), HowToStopIt, QueryPlan, QueryText, StartTime, LoginName, NTUserName, OriginalLoginName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, DetailsInt, QueryHash FROM #BlitzFirstResults ORDER BY Priority , FindingsGroup , Finding , Details'; + + EXEC sp_executesql @StringToExecute, + N'@SrvName NVARCHAR(128), @CheckDate datetimeoffset', + @LocalServerName, @StartSampleTime; + + /* Delete history older than @OutputTableRetentionDays */ + SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' + + @OutputDatabaseName + + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' + + @OutputSchemaName + ''') DELETE ' + + @OutputDatabaseName + '.' + + @OutputSchemaName + '.' + + @OutputTableName + + ' WHERE ServerName = @SrvName AND CheckDate < @CheckDate ;'; + + EXEC sp_executesql @StringToExecute, + N'@SrvName NVARCHAR(128), @CheckDate date', + @LocalServerName, @OutputTableCleanupDate; + + END; + ELSE IF (SUBSTRING(@OutputTableName, 2, 2) = '##') + BEGIN + SET @StringToExecute = N' IF (OBJECT_ID(''tempdb..' + + @OutputTableName + + ''') IS NULL) CREATE TABLE ' + + @OutputTableName + + ' (ID INT IDENTITY(1,1) NOT NULL, + ServerName NVARCHAR(128), + CheckDate DATETIMEOFFSET, + CheckID INT NOT NULL, + Priority TINYINT NOT NULL, + FindingsGroup VARCHAR(50) NOT NULL, + Finding VARCHAR(200) NOT NULL, + URL VARCHAR(200) NOT NULL, + Details NVARCHAR(4000) NULL, + HowToStopIt [XML] NULL, + QueryPlan [XML] NULL, + QueryText NVARCHAR(MAX) NULL, + StartTime DATETIMEOFFSET NULL, + LoginName NVARCHAR(128) NULL, + NTUserName NVARCHAR(128) NULL, + OriginalLoginName NVARCHAR(128) NULL, + ProgramName NVARCHAR(128) NULL, + HostName NVARCHAR(128) NULL, + DatabaseID INT NULL, + DatabaseName NVARCHAR(128) NULL, + OpenTransactionCount INT NULL, + DetailsInt INT NULL, + QueryHash BINARY(8) NULL, + JoinKey AS ServerName + CAST(CheckDate AS NVARCHAR(50)), + PRIMARY KEY CLUSTERED (ID ASC));' + + ' INSERT ' + + @OutputTableName + + ' (ServerName, CheckDate, CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, QueryText, StartTime, LoginName, NTUserName, OriginalLoginName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, DetailsInt) SELECT ' + + ' @SrvName, @CheckDate, CheckID, Priority, FindingsGroup, Finding, URL, LEFT(Details,4000), HowToStopIt, QueryPlan, QueryText, StartTime, LoginName, NTUserName, OriginalLoginName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, DetailsInt FROM #BlitzFirstResults ORDER BY Priority , FindingsGroup , Finding , Details'; + + EXEC sp_executesql @StringToExecute, + N'@SrvName NVARCHAR(128), @CheckDate datetimeoffset', + @LocalServerName, @StartSampleTime; + END; + ELSE IF (SUBSTRING(@OutputTableName, 2, 1) = '#') + BEGIN + RAISERROR('Due to the nature of Dymamic SQL, only global (i.e. double pound (##)) temp tables are supported for @OutputTableName', 16, 0); + END; + + /* @OutputTableNameFileStats lets us export the results to a permanent table */ + IF @OutputDatabaseName IS NOT NULL + AND @OutputSchemaName IS NOT NULL + AND @OutputTableNameFileStats IS NOT NULL + AND @OutputTableNameFileStats NOT LIKE '#%' + AND EXISTS ( SELECT * + FROM sys.databases + WHERE QUOTENAME([name]) = @OutputDatabaseName) + BEGIN + /* Create the table */ + SET @StringToExecute = 'USE ' + + @OutputDatabaseName + + '; IF EXISTS(SELECT * FROM ' + + @OutputDatabaseName + + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' + + @OutputSchemaName + + ''') AND NOT EXISTS (SELECT * FROM ' + + @OutputDatabaseName + + '.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''' + + @OutputSchemaName + ''' AND QUOTENAME(TABLE_NAME) = ''' + + @OutputTableNameFileStats + ''') CREATE TABLE ' + + @OutputSchemaName + '.' + + @OutputTableNameFileStats + + ' (ID INT IDENTITY(1,1) NOT NULL, + ServerName NVARCHAR(128), + CheckDate DATETIMEOFFSET, + DatabaseID INT NOT NULL, + FileID INT NOT NULL, + DatabaseName NVARCHAR(256) , + FileLogicalName NVARCHAR(256) , + TypeDesc NVARCHAR(60) , + SizeOnDiskMB BIGINT , + io_stall_read_ms BIGINT , + num_of_reads BIGINT , + bytes_read BIGINT , + io_stall_write_ms BIGINT , + num_of_writes BIGINT , + bytes_written BIGINT, + PhysicalName NVARCHAR(520) , + PRIMARY KEY CLUSTERED (ID ASC));'; + + EXEC(@StringToExecute); + + SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableNameFileStats_View; + + /* If the view exists without the most recently added columns, drop it. See Github #2162. */ + IF OBJECT_ID(@ObjectFullName) IS NOT NULL + BEGIN + SET @StringToExecute = N'USE ' + @OutputDatabaseName + N'; IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns + WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''JoinKey'') + DROP VIEW ' + @OutputSchemaName + N'.' + @OutputTableNameFileStats_View + N';'; + + EXEC(@StringToExecute); + END + + /* Create the view */ + IF OBJECT_ID(@ObjectFullName) IS NULL + BEGIN + SET @StringToExecute = 'USE ' + + @OutputDatabaseName + + '; EXEC (''CREATE VIEW ' + + @OutputSchemaName + '.' + + @OutputTableNameFileStats_View + ' AS ' + @LineFeed + + 'WITH RowDates as' + @LineFeed + + '(' + @LineFeed + + ' SELECT ' + @LineFeed + + ' ROW_NUMBER() OVER (ORDER BY [ServerName], [CheckDate]) ID,' + @LineFeed + + ' [CheckDate]' + @LineFeed + + ' FROM ' + @OutputSchemaName + '.' + @OutputTableNameFileStats + '' + @LineFeed + + ' GROUP BY [ServerName], [CheckDate]' + @LineFeed + + '),' + @LineFeed + + 'CheckDates as' + @LineFeed + + '(' + @LineFeed + + ' SELECT ThisDate.CheckDate,' + @LineFeed + + ' LastDate.CheckDate as PreviousCheckDate' + @LineFeed + + ' FROM RowDates ThisDate' + @LineFeed + + ' JOIN RowDates LastDate' + @LineFeed + + ' ON ThisDate.ID = LastDate.ID + 1' + @LineFeed + + ')' + @LineFeed + + ' SELECT f.ServerName,' + @LineFeed + + ' f.CheckDate,' + @LineFeed + + ' f.DatabaseID,' + @LineFeed + + ' f.DatabaseName,' + @LineFeed + + ' f.FileID,' + @LineFeed + + ' f.FileLogicalName,' + @LineFeed + + ' f.TypeDesc,' + @LineFeed + + ' f.PhysicalName,' + @LineFeed + + ' f.SizeOnDiskMB,' + @LineFeed + + ' DATEDIFF(ss, fPrior.CheckDate, f.CheckDate) AS ElapsedSeconds,' + @LineFeed + + ' (f.SizeOnDiskMB - fPrior.SizeOnDiskMB) AS SizeOnDiskMBgrowth,' + @LineFeed + + ' (f.io_stall_read_ms - fPrior.io_stall_read_ms) AS io_stall_read_ms,' + @LineFeed + + ' io_stall_read_ms_average = CASE' + @LineFeed + + ' WHEN(f.num_of_reads - fPrior.num_of_reads) = 0' + @LineFeed + + ' THEN 0' + @LineFeed + + ' ELSE(f.io_stall_read_ms - fPrior.io_stall_read_ms) / (f.num_of_reads - fPrior.num_of_reads)' + @LineFeed + + ' END,' + @LineFeed + + ' (f.num_of_reads - fPrior.num_of_reads) AS num_of_reads,' + @LineFeed + + ' (f.bytes_read - fPrior.bytes_read) / 1024.0 / 1024.0 AS megabytes_read,' + @LineFeed + + ' (f.io_stall_write_ms - fPrior.io_stall_write_ms) AS io_stall_write_ms,' + @LineFeed + + ' io_stall_write_ms_average = CASE' + @LineFeed + + ' WHEN(f.num_of_writes - fPrior.num_of_writes) = 0' + @LineFeed + + ' THEN 0' + @LineFeed + + ' ELSE(f.io_stall_write_ms - fPrior.io_stall_write_ms) / (f.num_of_writes - fPrior.num_of_writes)' + @LineFeed + + ' END,' + @LineFeed + + ' (f.num_of_writes - fPrior.num_of_writes) AS num_of_writes,' + @LineFeed + + ' (f.bytes_written - fPrior.bytes_written) / 1024.0 / 1024.0 AS megabytes_written, ' + @LineFeed + + ' f.ServerName + CAST(f.CheckDate AS NVARCHAR(50)) AS JoinKey' + @LineFeed + + ' FROM ' + @OutputSchemaName + '.' + @OutputTableNameFileStats + ' f' + @LineFeed + + ' INNER HASH JOIN CheckDates DATES ON f.CheckDate = DATES.CheckDate' + @LineFeed + + ' INNER JOIN ' + @OutputSchemaName + '.' + @OutputTableNameFileStats + ' fPrior ON f.ServerName = fPrior.ServerName' + @LineFeed + + ' AND f.DatabaseID = fPrior.DatabaseID' + @LineFeed + + ' AND f.FileID = fPrior.FileID' + @LineFeed + + ' AND fPrior.CheckDate = DATES.PreviousCheckDate' + @LineFeed + + '' + @LineFeed + + ' WHERE f.num_of_reads >= fPrior.num_of_reads' + @LineFeed + + ' AND f.num_of_writes >= fPrior.num_of_writes' + @LineFeed + + ' AND DATEDIFF(MI, fPrior.CheckDate, f.CheckDate) BETWEEN 1 AND 60;'')' + + EXEC(@StringToExecute); + END; + + + SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' + + @OutputDatabaseName + + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' + + @OutputSchemaName + ''') INSERT ' + + @OutputDatabaseName + '.' + + @OutputSchemaName + '.' + + @OutputTableNameFileStats + + ' (ServerName, CheckDate, DatabaseID, FileID, DatabaseName, FileLogicalName, TypeDesc, SizeOnDiskMB, io_stall_read_ms, num_of_reads, bytes_read, io_stall_write_ms, num_of_writes, bytes_written, PhysicalName) SELECT ' + + ' @SrvName, @CheckDate, DatabaseID, FileID, DatabaseName, FileLogicalName, TypeDesc, SizeOnDiskMB, io_stall_read_ms, num_of_reads, bytes_read, io_stall_write_ms, num_of_writes, bytes_written, PhysicalName FROM #FileStats WHERE Pass = 2'; + + EXEC sp_executesql @StringToExecute, + N'@SrvName NVARCHAR(128), @CheckDate datetimeoffset', + @LocalServerName, @StartSampleTime; + + /* Delete history older than @OutputTableRetentionDays */ + SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' + + @OutputDatabaseName + + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' + + @OutputSchemaName + ''') DELETE ' + + @OutputDatabaseName + '.' + + @OutputSchemaName + '.' + + @OutputTableNameFileStats + + ' WHERE ServerName = @SrvName AND CheckDate < @CheckDate ;'; + + EXEC sp_executesql @StringToExecute, + N'@SrvName NVARCHAR(128), @CheckDate date', + @LocalServerName, @OutputTableCleanupDate; + + END; + ELSE IF (SUBSTRING(@OutputTableNameFileStats, 2, 2) = '##') + BEGIN + SET @StringToExecute = N' IF (OBJECT_ID(''tempdb..' + + @OutputTableNameFileStats + + ''') IS NULL) CREATE TABLE ' + + @OutputTableNameFileStats + + ' (ID INT IDENTITY(1,1) NOT NULL, + ServerName NVARCHAR(128), + CheckDate DATETIMEOFFSET, + DatabaseID INT NOT NULL, + FileID INT NOT NULL, + DatabaseName NVARCHAR(256) , + FileLogicalName NVARCHAR(256) , + TypeDesc NVARCHAR(60) , + SizeOnDiskMB BIGINT , + io_stall_read_ms BIGINT , + num_of_reads BIGINT , + bytes_read BIGINT , + io_stall_write_ms BIGINT , + num_of_writes BIGINT , + bytes_written BIGINT, + PhysicalName NVARCHAR(520) , + DetailsInt INT NULL, + PRIMARY KEY CLUSTERED (ID ASC));' + + ' INSERT ' + + @OutputTableNameFileStats + + ' (ServerName, CheckDate, DatabaseID, FileID, DatabaseName, FileLogicalName, TypeDesc, SizeOnDiskMB, io_stall_read_ms, num_of_reads, bytes_read, io_stall_write_ms, num_of_writes, bytes_written, PhysicalName) SELECT ' + + ' @SrvName, @CheckDate, DatabaseID, FileID, DatabaseName, FileLogicalName, TypeDesc, SizeOnDiskMB, io_stall_read_ms, num_of_reads, bytes_read, io_stall_write_ms, num_of_writes, bytes_written, PhysicalName FROM #FileStats WHERE Pass = 2'; + + EXEC sp_executesql @StringToExecute, + N'@SrvName NVARCHAR(128), @CheckDate datetimeoffset', + @LocalServerName, @StartSampleTime; + END; + ELSE IF (SUBSTRING(@OutputTableNameFileStats, 2, 1) = '#') + BEGIN + RAISERROR('Due to the nature of Dymamic SQL, only global (i.e. double pound (##)) temp tables are supported for @OutputTableName', 16, 0); + END; + + + /* @OutputTableNamePerfmonStats lets us export the results to a permanent table */ + IF @OutputDatabaseName IS NOT NULL + AND @OutputSchemaName IS NOT NULL + AND @OutputTableNamePerfmonStats IS NOT NULL + AND @OutputTableNamePerfmonStats NOT LIKE '#%' + AND EXISTS ( SELECT * + FROM sys.databases + WHERE QUOTENAME([name]) = @OutputDatabaseName) + BEGIN + /* Create the table */ + SET @StringToExecute = 'USE ' + + @OutputDatabaseName + + '; IF EXISTS(SELECT * FROM ' + + @OutputDatabaseName + + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' + + @OutputSchemaName + + ''') AND NOT EXISTS (SELECT * FROM ' + + @OutputDatabaseName + + '.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''' + + @OutputSchemaName + ''' AND QUOTENAME(TABLE_NAME) = ''' + + @OutputTableNamePerfmonStats + ''') CREATE TABLE ' + + @OutputSchemaName + '.' + + @OutputTableNamePerfmonStats + + ' (ID INT IDENTITY(1,1) NOT NULL, + ServerName NVARCHAR(128), + CheckDate DATETIMEOFFSET, + [object_name] NVARCHAR(128) NOT NULL, + [counter_name] NVARCHAR(128) NOT NULL, + [instance_name] NVARCHAR(128) NULL, + [cntr_value] BIGINT NULL, + [cntr_type] INT NOT NULL, + [value_delta] BIGINT NULL, + [value_per_second] DECIMAL(18,2) NULL, + PRIMARY KEY CLUSTERED (ID ASC));'; + + EXEC(@StringToExecute); + + SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableNamePerfmonStats_View; + + /* If the view exists without the most recently added columns, drop it. See Github #2162. */ + IF OBJECT_ID(@ObjectFullName) IS NOT NULL + BEGIN + SET @StringToExecute = N'USE ' + @OutputDatabaseName + N'; IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns + WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''JoinKey'') + DROP VIEW ' + @OutputSchemaName + N'.' + @OutputTableNamePerfmonStats_View + N';'; + + EXEC(@StringToExecute); + END + + /* Create the view */ + IF OBJECT_ID(@ObjectFullName) IS NULL + BEGIN + SET @StringToExecute = 'USE ' + + @OutputDatabaseName + + '; EXEC (''CREATE VIEW ' + + @OutputSchemaName + '.' + + @OutputTableNamePerfmonStats_View + ' AS ' + @LineFeed + + 'WITH RowDates as' + @LineFeed + + '(' + @LineFeed + + ' SELECT ' + @LineFeed + + ' ROW_NUMBER() OVER (ORDER BY [ServerName], [CheckDate]) ID,' + @LineFeed + + ' [CheckDate]' + @LineFeed + + ' FROM ' + @OutputSchemaName + '.' +@OutputTableNamePerfmonStats + '' + @LineFeed + + ' GROUP BY [ServerName], [CheckDate]' + @LineFeed + + '),' + @LineFeed + + 'CheckDates as' + @LineFeed + + '(' + @LineFeed + + ' SELECT ThisDate.CheckDate,' + @LineFeed + + ' LastDate.CheckDate as PreviousCheckDate' + @LineFeed + + ' FROM RowDates ThisDate' + @LineFeed + + ' JOIN RowDates LastDate' + @LineFeed + + ' ON ThisDate.ID = LastDate.ID + 1' + @LineFeed + + ')' + @LineFeed + + 'SELECT' + @LineFeed + + ' pMon.[ServerName]' + @LineFeed + + ' ,pMon.[CheckDate]' + @LineFeed + + ' ,pMon.[object_name]' + @LineFeed + + ' ,pMon.[counter_name]' + @LineFeed + + ' ,pMon.[instance_name]' + @LineFeed + + ' ,DATEDIFF(SECOND,pMonPrior.[CheckDate],pMon.[CheckDate]) AS ElapsedSeconds' + @LineFeed + + ' ,pMon.[cntr_value]' + @LineFeed + + ' ,pMon.[cntr_type]' + @LineFeed + + ' ,(pMon.[cntr_value] - pMonPrior.[cntr_value]) AS cntr_delta' + @LineFeed + + ' ,(pMon.cntr_value - pMonPrior.cntr_value) * 1.0 / DATEDIFF(ss, pMonPrior.CheckDate, pMon.CheckDate) AS cntr_delta_per_second' + @LineFeed + + ' ,pMon.ServerName + CAST(pMon.CheckDate AS NVARCHAR(50)) AS JoinKey' + @LineFeed + + ' FROM ' + @OutputSchemaName + '.' +@OutputTableNamePerfmonStats + ' pMon' + @LineFeed + + ' INNER HASH JOIN CheckDates Dates' + @LineFeed + + ' ON Dates.CheckDate = pMon.CheckDate' + @LineFeed + + ' JOIN ' + @OutputSchemaName + '.' +@OutputTableNamePerfmonStats + ' pMonPrior' + @LineFeed + + ' ON Dates.PreviousCheckDate = pMonPrior.CheckDate' + @LineFeed + + ' AND pMon.[ServerName] = pMonPrior.[ServerName] ' + @LineFeed + + ' AND pMon.[object_name] = pMonPrior.[object_name] ' + @LineFeed + + ' AND pMon.[counter_name] = pMonPrior.[counter_name] ' + @LineFeed + + ' AND pMon.[instance_name] = pMonPrior.[instance_name]' + @LineFeed + + ' WHERE DATEDIFF(MI, pMonPrior.CheckDate, pMon.CheckDate) BETWEEN 1 AND 60;'')' + + EXEC(@StringToExecute); + END + + SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableNamePerfmonStatsActuals_View; + + /* If the view exists without the most recently added columns, drop it. See Github #2162. */ + IF OBJECT_ID(@ObjectFullName) IS NOT NULL + BEGIN + SET @StringToExecute = N'USE ' + @OutputDatabaseName + N'; IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns + WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''JoinKey'') + DROP VIEW ' + @OutputSchemaName + N'.' + @OutputTableNamePerfmonStatsActuals_View + N';'; + + EXEC(@StringToExecute); + END + + /* Create the second view */ + IF OBJECT_ID(@ObjectFullName) IS NULL + BEGIN + SET @StringToExecute = 'USE ' + + @OutputDatabaseName + + '; EXEC (''CREATE VIEW ' + + @OutputSchemaName + '.' + + @OutputTableNamePerfmonStatsActuals_View + ' AS ' + @LineFeed + + 'WITH PERF_AVERAGE_BULK AS' + @LineFeed + + '(' + @LineFeed + + ' SELECT ServerName,' + @LineFeed + + ' object_name,' + @LineFeed + + ' instance_name,' + @LineFeed + + ' counter_name,' + @LineFeed + + ' CASE WHEN CHARINDEX(''''('''', counter_name) = 0 THEN counter_name ELSE LEFT (counter_name, CHARINDEX(''''('''',counter_name)-1) END AS counter_join,' + @LineFeed + + ' CheckDate,' + @LineFeed + + ' cntr_delta' + @LineFeed + + ' FROM ' + @OutputSchemaName + '.' + @OutputTableNamePerfmonStats_View + @LineFeed + + ' WHERE cntr_type IN(1073874176)' + @LineFeed + + ' AND cntr_delta <> 0' + @LineFeed + + '),' + @LineFeed + + 'PERF_LARGE_RAW_BASE AS' + @LineFeed + + '(' + @LineFeed + + ' SELECT ServerName,' + @LineFeed + + ' object_name,' + @LineFeed + + ' instance_name,' + @LineFeed + + ' LEFT(counter_name, CHARINDEX(''''BASE'''', UPPER(counter_name))-1) AS counter_join,' + @LineFeed + + ' CheckDate,' + @LineFeed + + ' cntr_delta' + @LineFeed + + ' FROM ' + @OutputSchemaName + '.' + @OutputTableNamePerfmonStats_View + '' + @LineFeed + + ' WHERE cntr_type IN(1073939712)' + @LineFeed + + ' AND cntr_delta <> 0' + @LineFeed + + '),' + @LineFeed + + 'PERF_AVERAGE_FRACTION AS' + @LineFeed + + '(' + @LineFeed + + ' SELECT ServerName,' + @LineFeed + + ' object_name,' + @LineFeed + + ' instance_name,' + @LineFeed + + ' counter_name,' + @LineFeed + + ' counter_name AS counter_join,' + @LineFeed + + ' CheckDate,' + @LineFeed + + ' cntr_delta' + @LineFeed + + ' FROM ' + @OutputSchemaName + '.' + @OutputTableNamePerfmonStats_View + '' + @LineFeed + + ' WHERE cntr_type IN(537003264)' + @LineFeed + + ' AND cntr_delta <> 0' + @LineFeed + + '),' + @LineFeed + + 'PERF_COUNTER_BULK_COUNT AS' + @LineFeed + + '(' + @LineFeed + + ' SELECT ServerName,' + @LineFeed + + ' object_name,' + @LineFeed + + ' instance_name,' + @LineFeed + + ' counter_name,' + @LineFeed + + ' CheckDate,' + @LineFeed + + ' cntr_delta / ElapsedSeconds AS cntr_value' + @LineFeed + + ' FROM ' + @OutputSchemaName + '.' + @OutputTableNamePerfmonStats_View + '' + @LineFeed + + ' WHERE cntr_type IN(272696576, 272696320)' + @LineFeed + + ' AND cntr_delta <> 0' + @LineFeed + + '),' + @LineFeed + + 'PERF_COUNTER_RAWCOUNT AS' + @LineFeed + + '(' + @LineFeed + + ' SELECT ServerName,' + @LineFeed + + ' object_name,' + @LineFeed + + ' instance_name,' + @LineFeed + + ' counter_name,' + @LineFeed + + ' CheckDate,' + @LineFeed + + ' cntr_value' + @LineFeed + + ' FROM ' + @OutputSchemaName + '.' + @OutputTableNamePerfmonStats_View + '' + @LineFeed + + ' WHERE cntr_type IN(65792, 65536)' + @LineFeed + + ')' + @LineFeed + + '' + @LineFeed + + 'SELECT NUM.ServerName,' + @LineFeed + + ' NUM.object_name,' + @LineFeed + + ' NUM.counter_name,' + @LineFeed + + ' NUM.instance_name,' + @LineFeed + + ' NUM.CheckDate,' + @LineFeed + + ' NUM.cntr_delta / DEN.cntr_delta AS cntr_value,' + @LineFeed + + ' NUM.ServerName + CAST(NUM.CheckDate AS NVARCHAR(50)) AS JoinKey' + @LineFeed + + ' ' + @LineFeed + + 'FROM PERF_AVERAGE_BULK AS NUM' + @LineFeed + + ' JOIN PERF_LARGE_RAW_BASE AS DEN ON NUM.counter_join = DEN.counter_join' + @LineFeed + + ' AND NUM.CheckDate = DEN.CheckDate' + @LineFeed + + ' AND NUM.ServerName = DEN.ServerName' + @LineFeed + + ' AND NUM.object_name = DEN.object_name' + @LineFeed + + ' AND NUM.instance_name = DEN.instance_name' + @LineFeed + + ' AND DEN.cntr_delta <> 0' + @LineFeed + + '' + @LineFeed + + 'UNION ALL' + @LineFeed + + '' + @LineFeed + + 'SELECT NUM.ServerName,' + @LineFeed + + ' NUM.object_name,' + @LineFeed + + ' NUM.counter_name,' + @LineFeed + + ' NUM.instance_name,' + @LineFeed + + ' NUM.CheckDate,' + @LineFeed + + ' CAST((CAST(NUM.cntr_delta as DECIMAL(19)) / DEN.cntr_delta) as decimal(23,3)) AS cntr_value,' + @LineFeed + + ' NUM.ServerName + CAST(NUM.CheckDate AS NVARCHAR(50)) AS JoinKey' + @LineFeed + + 'FROM PERF_AVERAGE_FRACTION AS NUM' + @LineFeed + + ' JOIN PERF_LARGE_RAW_BASE AS DEN ON NUM.counter_join = DEN.counter_join' + @LineFeed + + ' AND NUM.CheckDate = DEN.CheckDate' + @LineFeed + + ' AND NUM.ServerName = DEN.ServerName' + @LineFeed + + ' AND NUM.object_name = DEN.object_name' + @LineFeed + + ' AND NUM.instance_name = DEN.instance_name' + @LineFeed + + ' AND DEN.cntr_delta <> 0' + @LineFeed + + 'UNION ALL' + @LineFeed + + '' + @LineFeed + + 'SELECT ServerName,' + @LineFeed + + ' object_name,' + @LineFeed + + ' counter_name,' + @LineFeed + + ' instance_name,' + @LineFeed + + ' CheckDate,' + @LineFeed + + ' cntr_value,' + @LineFeed + + ' ServerName + CAST(CheckDate AS NVARCHAR(50)) AS JoinKey' + @LineFeed + + 'FROM PERF_COUNTER_BULK_COUNT' + @LineFeed + + '' + @LineFeed + + 'UNION ALL' + @LineFeed + + '' + @LineFeed + + 'SELECT ServerName,' + @LineFeed + + ' object_name,' + @LineFeed + + ' counter_name,' + @LineFeed + + ' instance_name,' + @LineFeed + + ' CheckDate,' + @LineFeed + + ' cntr_value,' + @LineFeed + + ' ServerName + CAST(CheckDate AS NVARCHAR(50)) AS JoinKey' + @LineFeed + + 'FROM PERF_COUNTER_RAWCOUNT;'')'; + + EXEC(@StringToExecute); + END; + + + SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' + + @OutputDatabaseName + + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' + + @OutputSchemaName + ''') INSERT ' + + @OutputDatabaseName + '.' + + @OutputSchemaName + '.' + + @OutputTableNamePerfmonStats + + ' (ServerName, CheckDate, object_name, counter_name, instance_name, cntr_value, cntr_type, value_delta, value_per_second) SELECT ' + + ' @SrvName, @CheckDate, object_name, counter_name, instance_name, cntr_value, cntr_type, value_delta, value_per_second FROM #PerfmonStats WHERE Pass = 2'; + + EXEC sp_executesql @StringToExecute, + N'@SrvName NVARCHAR(128), @CheckDate datetimeoffset', + @LocalServerName, @StartSampleTime; + + /* Delete history older than @OutputTableRetentionDays */ + SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' + + @OutputDatabaseName + + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' + + @OutputSchemaName + ''') DELETE ' + + @OutputDatabaseName + '.' + + @OutputSchemaName + '.' + + @OutputTableNamePerfmonStats + + ' WHERE ServerName = @SrvName AND CheckDate < @CheckDate ;'; + + EXEC sp_executesql @StringToExecute, + N'@SrvName NVARCHAR(128), @CheckDate date', + @LocalServerName, @OutputTableCleanupDate; + + + + END; + ELSE IF (SUBSTRING(@OutputTableNamePerfmonStats, 2, 2) = '##') + BEGIN + SET @StringToExecute = N' IF (OBJECT_ID(''tempdb..' + + @OutputTableNamePerfmonStats + + ''') IS NULL) CREATE TABLE ' + + @OutputTableNamePerfmonStats + + ' (ID INT IDENTITY(1,1) NOT NULL, + ServerName NVARCHAR(128), + CheckDate DATETIMEOFFSET, + [object_name] NVARCHAR(128) NOT NULL, + [counter_name] NVARCHAR(128) NOT NULL, + [instance_name] NVARCHAR(128) NULL, + [cntr_value] BIGINT NULL, + [cntr_type] INT NOT NULL, + [value_delta] BIGINT NULL, + [value_per_second] DECIMAL(18,2) NULL, + PRIMARY KEY CLUSTERED (ID ASC));' + + ' INSERT ' + + @OutputTableNamePerfmonStats + + ' (ServerName, CheckDate, object_name, counter_name, instance_name, cntr_value, cntr_type, value_delta, value_per_second) SELECT ' + + CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128)) + + ' @SrvName, @CheckDate, object_name, counter_name, instance_name, cntr_value, cntr_type, value_delta, value_per_second FROM #PerfmonStats WHERE Pass = 2'; + + EXEC sp_executesql @StringToExecute, + N'@SrvName NVARCHAR(128), @CheckDate datetimeoffset', + @LocalServerName, @StartSampleTime; + END; + ELSE IF (SUBSTRING(@OutputTableNamePerfmonStats, 2, 1) = '#') + BEGIN + RAISERROR('Due to the nature of Dymamic SQL, only global (i.e. double pound (##)) temp tables are supported for @OutputTableName', 16, 0); + END; + + + /* @OutputTableNameWaitStats lets us export the results to a permanent table */ + IF @OutputDatabaseName IS NOT NULL + AND @OutputSchemaName IS NOT NULL + AND @OutputTableNameWaitStats IS NOT NULL + AND @OutputTableNameWaitStats NOT LIKE '#%' + AND EXISTS ( SELECT * + FROM sys.databases + WHERE QUOTENAME([name]) = @OutputDatabaseName) + BEGIN + /* Create the table */ + SET @StringToExecute = 'USE ' + + @OutputDatabaseName + + '; IF EXISTS(SELECT * FROM ' + + @OutputDatabaseName + + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' + + @OutputSchemaName + + ''') AND NOT EXISTS (SELECT * FROM ' + + @OutputDatabaseName + + '.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''' + + @OutputSchemaName + ''' AND QUOTENAME(TABLE_NAME) = ''' + + @OutputTableNameWaitStats + ''') ' + @LineFeed + + 'BEGIN' + @LineFeed + + 'CREATE TABLE ' + + @OutputSchemaName + '.' + + @OutputTableNameWaitStats + + ' (ID INT IDENTITY(1,1) NOT NULL, + ServerName NVARCHAR(128), + CheckDate DATETIMEOFFSET, + wait_type NVARCHAR(60), + wait_time_ms BIGINT, + signal_wait_time_ms BIGINT, + waiting_tasks_count BIGINT , + PRIMARY KEY CLUSTERED (ID));' + @LineFeed + + 'CREATE NONCLUSTERED INDEX IX_ServerName_wait_type_CheckDate_Includes ON ' + @OutputSchemaName + '.' + @OutputTableNameWaitStats + @LineFeed + + '(ServerName, wait_type, CheckDate) INCLUDE (wait_time_ms, signal_wait_time_ms, waiting_tasks_count);' + @LineFeed + + 'END'; + + EXEC(@StringToExecute); + + /* Create the wait stats category table */ + SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableNameWaitStats_Categories; + IF OBJECT_ID(@ObjectFullName) IS NULL + BEGIN + SET @StringToExecute = 'USE ' + + @OutputDatabaseName + + '; EXEC (''CREATE TABLE ' + + @OutputSchemaName + '.' + + @OutputTableNameWaitStats_Categories + ' (WaitType NVARCHAR(60) PRIMARY KEY CLUSTERED, WaitCategory NVARCHAR(128) NOT NULL, Ignorable BIT DEFAULT 0);'')'; + + EXEC(@StringToExecute); + END; + + /* Make sure the wait stats category table has the current number of rows */ + SET @StringToExecute = 'USE ' + + @OutputDatabaseName + + '; EXEC (''IF (SELECT COALESCE(SUM(1),0) FROM ' + @OutputSchemaName + '.' + @OutputTableNameWaitStats_Categories + ') <> (SELECT COALESCE(SUM(1),0) FROM ##WaitCategories)' + @LineFeed + + 'BEGIN ' + @LineFeed + + 'TRUNCATE TABLE ' + @OutputSchemaName + '.' + @OutputTableNameWaitStats_Categories + @LineFeed + + 'INSERT INTO ' + @OutputSchemaName + '.' + @OutputTableNameWaitStats_Categories + ' (WaitType, WaitCategory, Ignorable) SELECT WaitType, WaitCategory, Ignorable FROM ##WaitCategories;' + @LineFeed + + 'END'')'; + + EXEC(@StringToExecute); + + + SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableNameWaitStats_View; + + /* If the view exists without the most recently added columns, drop it. See Github #2162. */ + IF OBJECT_ID(@ObjectFullName) IS NOT NULL + BEGIN + SET @StringToExecute = N'USE ' + @OutputDatabaseName + N'; IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns + WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''JoinKey'') + DROP VIEW ' + @OutputSchemaName + N'.' + @OutputTableNameWaitStats_View + N';'; + + EXEC(@StringToExecute); + END + + + /* Create the wait stats view */ + IF OBJECT_ID(@ObjectFullName) IS NULL + BEGIN + SET @StringToExecute = 'USE ' + + @OutputDatabaseName + + '; EXEC (''CREATE VIEW ' + + @OutputSchemaName + '.' + + @OutputTableNameWaitStats_View + ' AS ' + @LineFeed + + 'WITH RowDates as' + @LineFeed + + '(' + @LineFeed + + ' SELECT ' + @LineFeed + + ' ROW_NUMBER() OVER (ORDER BY [ServerName], [CheckDate]) ID,' + @LineFeed + + ' [CheckDate]' + @LineFeed + + ' FROM ' + @OutputSchemaName + '.' + @OutputTableNameWaitStats + @LineFeed + + ' GROUP BY [ServerName], [CheckDate]' + @LineFeed + + '),' + @LineFeed + + 'CheckDates as' + @LineFeed + + '(' + @LineFeed + + ' SELECT ThisDate.CheckDate,' + @LineFeed + + ' LastDate.CheckDate as PreviousCheckDate' + @LineFeed + + ' FROM RowDates ThisDate' + @LineFeed + + ' JOIN RowDates LastDate' + @LineFeed + + ' ON ThisDate.ID = LastDate.ID + 1' + @LineFeed + + ')' + @LineFeed + + 'SELECT w.ServerName, w.CheckDate, w.wait_type, COALESCE(wc.WaitCategory, ''''Other'''') AS WaitCategory, COALESCE(wc.Ignorable,0) AS Ignorable' + @LineFeed + + ', DATEDIFF(ss, wPrior.CheckDate, w.CheckDate) AS ElapsedSeconds' + @LineFeed + + ', (w.wait_time_ms - wPrior.wait_time_ms) AS wait_time_ms_delta' + @LineFeed + + ', (w.wait_time_ms - wPrior.wait_time_ms) / 60000.0 AS wait_time_minutes_delta' + @LineFeed + + ', (w.wait_time_ms - wPrior.wait_time_ms) / 1000.0 / DATEDIFF(ss, wPrior.CheckDate, w.CheckDate) AS wait_time_minutes_per_minute' + @LineFeed + + ', (w.signal_wait_time_ms - wPrior.signal_wait_time_ms) AS signal_wait_time_ms_delta' + @LineFeed + + ', (w.waiting_tasks_count - wPrior.waiting_tasks_count) AS waiting_tasks_count_delta' + @LineFeed + + ', w.ServerName + CAST(w.CheckDate AS NVARCHAR(50)) AS JoinKey' + @LineFeed + + 'FROM ' + @OutputSchemaName + '.' + @OutputTableNameWaitStats + ' w' + @LineFeed + + 'INNER HASH JOIN CheckDates Dates' + @LineFeed + + 'ON Dates.CheckDate = w.CheckDate' + @LineFeed + + 'INNER JOIN ' + @OutputSchemaName + '.' + @OutputTableNameWaitStats + ' wPrior ON w.ServerName = wPrior.ServerName AND w.wait_type = wPrior.wait_type AND Dates.PreviousCheckDate = wPrior.CheckDate' + @LineFeed + + 'LEFT OUTER JOIN ' + @OutputSchemaName + '.' + @OutputTableNameWaitStats_Categories + ' wc ON w.wait_type = wc.WaitType' + @LineFeed + + 'WHERE DATEDIFF(MI, wPrior.CheckDate, w.CheckDate) BETWEEN 1 AND 60' + @LineFeed + + 'AND [w].[wait_time_ms] >= [wPrior].[wait_time_ms];'')' + + EXEC(@StringToExecute); + END; + + + SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' + + @OutputDatabaseName + + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' + + @OutputSchemaName + ''') INSERT ' + + @OutputDatabaseName + '.' + + @OutputSchemaName + '.' + + @OutputTableNameWaitStats + + ' (ServerName, CheckDate, wait_type, wait_time_ms, signal_wait_time_ms, waiting_tasks_count) SELECT ' + + ' @SrvName, @CheckDate, wait_type, wait_time_ms, signal_wait_time_ms, waiting_tasks_count FROM #WaitStats WHERE Pass = 2 AND wait_time_ms > 0 AND waiting_tasks_count > 0'; + + EXEC sp_executesql @StringToExecute, + N'@SrvName NVARCHAR(128), @CheckDate datetimeoffset', + @LocalServerName, @StartSampleTime; + + /* Delete history older than @OutputTableRetentionDays */ + SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' + + @OutputDatabaseName + + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' + + @OutputSchemaName + ''') DELETE ' + + @OutputDatabaseName + '.' + + @OutputSchemaName + '.' + + @OutputTableNameWaitStats + + ' WHERE ServerName = @SrvName AND CheckDate < @CheckDate ;'; + + EXEC sp_executesql @StringToExecute, + N'@SrvName NVARCHAR(128), @CheckDate date', + @LocalServerName, @OutputTableCleanupDate; + + END; + ELSE IF (SUBSTRING(@OutputTableNameWaitStats, 2, 2) = '##') + BEGIN + SET @StringToExecute = N' IF (OBJECT_ID(''tempdb..' + + @OutputTableNameWaitStats + + ''') IS NULL) CREATE TABLE ' + + @OutputTableNameWaitStats + + ' (ID INT IDENTITY(1,1) NOT NULL, + ServerName NVARCHAR(128), + CheckDate DATETIMEOFFSET, + wait_type NVARCHAR(60), + wait_time_ms BIGINT, + signal_wait_time_ms BIGINT, + waiting_tasks_count BIGINT , + PRIMARY KEY CLUSTERED (ID ASC));' + + ' INSERT ' + + @OutputTableNameWaitStats + + ' (ServerName, CheckDate, wait_type, wait_time_ms, signal_wait_time_ms, waiting_tasks_count) SELECT ' + + ' @SrvName, @CheckDate, wait_type, wait_time_ms, signal_wait_time_ms, waiting_tasks_count FROM #WaitStats WHERE Pass = 2 AND wait_time_ms > 0 AND waiting_tasks_count > 0'; + + EXEC sp_executesql @StringToExecute, + N'@SrvName NVARCHAR(128), @CheckDate datetimeoffset', + @LocalServerName, @StartSampleTime; + END; + ELSE IF (SUBSTRING(@OutputTableNameWaitStats, 2, 1) = '#') + BEGIN + RAISERROR('Due to the nature of Dymamic SQL, only global (i.e. double pound (##)) temp tables are supported for @OutputTableName', 16, 0); + END; + + + + + DECLARE @separator AS VARCHAR(1); + IF @OutputType = 'RSV' + SET @separator = CHAR(31); + ELSE + SET @separator = ','; + + IF @OutputType = 'COUNT' AND @SinceStartup = 0 + BEGIN + SELECT COUNT(*) AS Warnings + FROM #BlitzFirstResults; + END; + ELSE + IF @OutputType = 'Opserver1' AND @SinceStartup = 0 + BEGIN + + SELECT r.[Priority] , + r.[FindingsGroup] , + r.[Finding] , + r.[URL] , + r.[Details], + r.[HowToStopIt] , + r.[CheckID] , + r.[StartTime], + r.[LoginName], + r.[NTUserName], + r.[OriginalLoginName], + r.[ProgramName], + r.[HostName], + r.[DatabaseID], + r.[DatabaseName], + r.[OpenTransactionCount], + r.[QueryPlan], + r.[QueryText], + qsNow.plan_handle AS PlanHandle, + qsNow.sql_handle AS SqlHandle, + qsNow.statement_start_offset AS StatementStartOffset, + qsNow.statement_end_offset AS StatementEndOffset, + [Executions] = qsNow.execution_count - (COALESCE(qsFirst.execution_count, 0)), + [ExecutionsPercent] = CAST(100.0 * (qsNow.execution_count - (COALESCE(qsFirst.execution_count, 0))) / (qsTotal.execution_count - qsTotalFirst.execution_count) AS DECIMAL(6,2)), + [Duration] = qsNow.total_elapsed_time - (COALESCE(qsFirst.total_elapsed_time, 0)), + [DurationPercent] = CAST(100.0 * (qsNow.total_elapsed_time - (COALESCE(qsFirst.total_elapsed_time, 0))) / (qsTotal.total_elapsed_time - qsTotalFirst.total_elapsed_time) AS DECIMAL(6,2)), + [CPU] = qsNow.total_worker_time - (COALESCE(qsFirst.total_worker_time, 0)), + [CPUPercent] = CAST(100.0 * (qsNow.total_worker_time - (COALESCE(qsFirst.total_worker_time, 0))) / (qsTotal.total_worker_time - qsTotalFirst.total_worker_time) AS DECIMAL(6,2)), + [Reads] = qsNow.total_logical_reads - (COALESCE(qsFirst.total_logical_reads, 0)), + [ReadsPercent] = CAST(100.0 * (qsNow.total_logical_reads - (COALESCE(qsFirst.total_logical_reads, 0))) / (qsTotal.total_logical_reads - qsTotalFirst.total_logical_reads) AS DECIMAL(6,2)), + [PlanCreationTime] = CONVERT(NVARCHAR(100), qsNow.creation_time ,121), + [TotalExecutions] = qsNow.execution_count, + [TotalExecutionsPercent] = CAST(100.0 * qsNow.execution_count / qsTotal.execution_count AS DECIMAL(6,2)), + [TotalDuration] = qsNow.total_elapsed_time, + [TotalDurationPercent] = CAST(100.0 * qsNow.total_elapsed_time / qsTotal.total_elapsed_time AS DECIMAL(6,2)), + [TotalCPU] = qsNow.total_worker_time, + [TotalCPUPercent] = CAST(100.0 * qsNow.total_worker_time / qsTotal.total_worker_time AS DECIMAL(6,2)), + [TotalReads] = qsNow.total_logical_reads, + [TotalReadsPercent] = CAST(100.0 * qsNow.total_logical_reads / qsTotal.total_logical_reads AS DECIMAL(6,2)), + r.[DetailsInt] + FROM #BlitzFirstResults r + LEFT OUTER JOIN #QueryStats qsTotal ON qsTotal.Pass = 0 + LEFT OUTER JOIN #QueryStats qsTotalFirst ON qsTotalFirst.Pass = -1 + LEFT OUTER JOIN #QueryStats qsNow ON r.QueryStatsNowID = qsNow.ID + LEFT OUTER JOIN #QueryStats qsFirst ON r.QueryStatsFirstID = qsFirst.ID + ORDER BY r.Priority , + r.FindingsGroup , + CASE + WHEN r.CheckID = 6 THEN DetailsInt + ELSE 0 + END DESC, + r.Finding, + r.ID; + END; + ELSE IF @OutputType IN ( 'CSV', 'RSV' ) AND @SinceStartup = 0 + BEGIN + + SELECT Result = CAST([Priority] AS NVARCHAR(100)) + + @separator + CAST(CheckID AS NVARCHAR(100)) + + @separator + COALESCE([FindingsGroup], + '(N/A)') + @separator + + COALESCE([Finding], '(N/A)') + @separator + + COALESCE(DatabaseName, '(N/A)') + @separator + + COALESCE([URL], '(N/A)') + @separator + + COALESCE([Details], '(N/A)') + FROM #BlitzFirstResults + ORDER BY Priority , + FindingsGroup , + CASE + WHEN CheckID = 6 THEN DetailsInt + ELSE 0 + END DESC, + Finding, + Details; + END; + ELSE IF @OutputType = 'Top10' + BEGIN + /* Measure waits in hours */ + ;WITH max_batch AS ( + SELECT MAX(SampleTime) AS SampleTime + FROM #WaitStats + ) + SELECT TOP 10 + CAST(DATEDIFF(mi,wd1.SampleTime, wd2.SampleTime) / 60.0 AS DECIMAL(18,1)) AS [Hours Sample], + wd1.wait_type, + COALESCE(wcat.WaitCategory, 'Other') AS wait_category, + CAST(c.[Wait Time (Seconds)] / 60.0 / 60 AS DECIMAL(18,1)) AS [Wait Time (Hours)], + CAST((wd2.wait_time_ms - wd1.wait_time_ms) / 1000.0 / cores.cpu_count / DATEDIFF(ss, wd1.SampleTime, wd2.SampleTime) AS DECIMAL(18,1)) AS [Per Core Per Hour], + (wd2.waiting_tasks_count - wd1.waiting_tasks_count) AS [Number of Waits], + CASE WHEN (wd2.waiting_tasks_count - wd1.waiting_tasks_count) > 0 + THEN + CAST((wd2.wait_time_ms-wd1.wait_time_ms)/ + (1.0*(wd2.waiting_tasks_count - wd1.waiting_tasks_count)) AS NUMERIC(12,1)) + ELSE 0 END AS [Avg ms Per Wait] + FROM max_batch b + JOIN #WaitStats wd2 ON + wd2.SampleTime =b.SampleTime + JOIN #WaitStats wd1 ON + wd1.wait_type=wd2.wait_type AND + wd2.SampleTime > wd1.SampleTime + CROSS APPLY (SELECT SUM(1) AS cpu_count FROM sys.dm_os_schedulers WHERE status = 'VISIBLE ONLINE' AND is_online = 1) AS cores + CROSS APPLY (SELECT + CAST((wd2.wait_time_ms-wd1.wait_time_ms)/1000. AS NUMERIC(12,1)) AS [Wait Time (Seconds)], + CAST((wd2.signal_wait_time_ms - wd1.signal_wait_time_ms)/1000. AS NUMERIC(12,1)) AS [Signal Wait Time (Seconds)]) AS c + LEFT OUTER JOIN ##WaitCategories wcat ON wd1.wait_type = wcat.WaitType + WHERE (wd2.waiting_tasks_count - wd1.waiting_tasks_count) > 0 + AND wd2.wait_time_ms-wd1.wait_time_ms > 0 + ORDER BY [Wait Time (Seconds)] DESC; + END; + ELSE IF @ExpertMode = 0 AND @OutputType <> 'NONE' AND @OutputXMLasNVARCHAR = 0 AND @SinceStartup = 0 + BEGIN + SELECT [Priority] , + [FindingsGroup] , + [Finding] , + [URL] , + CAST(@StockDetailsHeader + [Details] + @StockDetailsFooter AS XML) AS Details, + CAST(@StockWarningHeader + HowToStopIt + @StockWarningFooter AS XML) AS HowToStopIt, + [QueryText], + [QueryPlan] + FROM #BlitzFirstResults + WHERE (@Seconds > 0 OR (Priority IN (0, 250, 251, 255))) /* For @Seconds = 0, filter out broken checks for now */ + ORDER BY Priority , + FindingsGroup , + CASE + WHEN CheckID = 6 THEN DetailsInt + ELSE 0 + END DESC, + Finding, + ID, + CAST(Details AS NVARCHAR(4000)); + END; + ELSE IF @OutputType <> 'NONE' AND @OutputXMLasNVARCHAR = 1 AND @SinceStartup = 0 + BEGIN + SELECT [Priority] , + [FindingsGroup] , + [Finding] , + [URL] , + CAST(@StockDetailsHeader + [Details] + @StockDetailsFooter AS NVARCHAR(MAX)) AS Details, + CAST([HowToStopIt] AS NVARCHAR(MAX)) AS HowToStopIt, + CAST([QueryText] AS NVARCHAR(MAX)) AS QueryText, + CAST([QueryPlan] AS NVARCHAR(MAX)) AS QueryPlan + FROM #BlitzFirstResults + WHERE (@Seconds > 0 OR (Priority IN (0, 250, 251, 255))) /* For @Seconds = 0, filter out broken checks for now */ + ORDER BY Priority , + FindingsGroup , + CASE + WHEN CheckID = 6 THEN DetailsInt + ELSE 0 + END DESC, + Finding, + ID, + CAST(Details AS NVARCHAR(4000)); + END; + ELSE IF @ExpertMode = 1 AND @OutputType <> 'NONE' + BEGIN + IF @SinceStartup = 0 + SELECT r.[Priority] , + r.[FindingsGroup] , + r.[Finding] , + r.[URL] , + CAST(@StockDetailsHeader + r.[Details] + @StockDetailsFooter AS XML) AS Details, + CAST(@StockWarningHeader + r.HowToStopIt + @StockWarningFooter AS XML) AS HowToStopIt, + r.[CheckID] , + r.[StartTime], + r.[LoginName], + r.[NTUserName], + r.[OriginalLoginName], + r.[ProgramName], + r.[HostName], + r.[DatabaseID], + r.[DatabaseName], + r.[OpenTransactionCount], + r.[QueryPlan], + r.[QueryText], + qsNow.plan_handle AS PlanHandle, + qsNow.sql_handle AS SqlHandle, + qsNow.statement_start_offset AS StatementStartOffset, + qsNow.statement_end_offset AS StatementEndOffset, + [Executions] = qsNow.execution_count - (COALESCE(qsFirst.execution_count, 0)), + [ExecutionsPercent] = CAST(100.0 * (qsNow.execution_count - (COALESCE(qsFirst.execution_count, 0))) / (qsTotal.execution_count - qsTotalFirst.execution_count) AS DECIMAL(6,2)), + [Duration] = qsNow.total_elapsed_time - (COALESCE(qsFirst.total_elapsed_time, 0)), + [DurationPercent] = CAST(100.0 * (qsNow.total_elapsed_time - (COALESCE(qsFirst.total_elapsed_time, 0))) / (qsTotal.total_elapsed_time - qsTotalFirst.total_elapsed_time) AS DECIMAL(6,2)), + [CPU] = qsNow.total_worker_time - (COALESCE(qsFirst.total_worker_time, 0)), + [CPUPercent] = CAST(100.0 * (qsNow.total_worker_time - (COALESCE(qsFirst.total_worker_time, 0))) / (qsTotal.total_worker_time - qsTotalFirst.total_worker_time) AS DECIMAL(6,2)), + [Reads] = qsNow.total_logical_reads - (COALESCE(qsFirst.total_logical_reads, 0)), + [ReadsPercent] = CAST(100.0 * (qsNow.total_logical_reads - (COALESCE(qsFirst.total_logical_reads, 0))) / (qsTotal.total_logical_reads - qsTotalFirst.total_logical_reads) AS DECIMAL(6,2)), + [PlanCreationTime] = CONVERT(NVARCHAR(100), qsNow.creation_time ,121), + [TotalExecutions] = qsNow.execution_count, + [TotalExecutionsPercent] = CAST(100.0 * qsNow.execution_count / qsTotal.execution_count AS DECIMAL(6,2)), + [TotalDuration] = qsNow.total_elapsed_time, + [TotalDurationPercent] = CAST(100.0 * qsNow.total_elapsed_time / qsTotal.total_elapsed_time AS DECIMAL(6,2)), + [TotalCPU] = qsNow.total_worker_time, + [TotalCPUPercent] = CAST(100.0 * qsNow.total_worker_time / qsTotal.total_worker_time AS DECIMAL(6,2)), + [TotalReads] = qsNow.total_logical_reads, + [TotalReadsPercent] = CAST(100.0 * qsNow.total_logical_reads / qsTotal.total_logical_reads AS DECIMAL(6,2)), + r.[DetailsInt] + FROM #BlitzFirstResults r + LEFT OUTER JOIN #QueryStats qsTotal ON qsTotal.Pass = 0 + LEFT OUTER JOIN #QueryStats qsTotalFirst ON qsTotalFirst.Pass = -1 + LEFT OUTER JOIN #QueryStats qsNow ON r.QueryStatsNowID = qsNow.ID + LEFT OUTER JOIN #QueryStats qsFirst ON r.QueryStatsFirstID = qsFirst.ID + WHERE (@Seconds > 0 OR (Priority IN (0, 250, 251, 255))) /* For @Seconds = 0, filter out broken checks for now */ + ORDER BY r.Priority , + r.FindingsGroup , + CASE + WHEN r.CheckID = 6 THEN DetailsInt + ELSE 0 + END DESC, + r.Finding, + r.ID, + CAST(r.Details AS NVARCHAR(4000)); + + ------------------------- + --What happened: #WaitStats + ------------------------- + IF @Seconds = 0 + BEGIN + /* Measure waits in hours */ + ;WITH max_batch AS ( + SELECT MAX(SampleTime) AS SampleTime + FROM #WaitStats + ) + SELECT + 'WAIT STATS' AS Pattern, + b.SampleTime AS [Sample Ended], + CAST(DATEDIFF(mi,wd1.SampleTime, wd2.SampleTime) / 60.0 AS DECIMAL(18,1)) AS [Hours Sample], + wd1.wait_type, + COALESCE(wcat.WaitCategory, 'Other') AS wait_category, + CAST(c.[Wait Time (Seconds)] / 60.0 / 60 AS DECIMAL(18,1)) AS [Wait Time (Hours)], + CAST((wd2.wait_time_ms - wd1.wait_time_ms) / 1000.0 / cores.cpu_count / DATEDIFF(ss, wd1.SampleTime, wd2.SampleTime) AS DECIMAL(18,1)) AS [Per Core Per Hour], + CAST(c.[Signal Wait Time (Seconds)] / 60.0 / 60 AS DECIMAL(18,1)) AS [Signal Wait Time (Hours)], + CASE WHEN c.[Wait Time (Seconds)] > 0 + THEN CAST(100.*(c.[Signal Wait Time (Seconds)]/c.[Wait Time (Seconds)]) AS NUMERIC(4,1)) + ELSE 0 END AS [Percent Signal Waits], + (wd2.waiting_tasks_count - wd1.waiting_tasks_count) AS [Number of Waits], + CASE WHEN (wd2.waiting_tasks_count - wd1.waiting_tasks_count) > 0 + THEN + CAST((wd2.wait_time_ms-wd1.wait_time_ms)/ + (1.0*(wd2.waiting_tasks_count - wd1.waiting_tasks_count)) AS NUMERIC(12,1)) + ELSE 0 END AS [Avg ms Per Wait], + N'https://www.sqlskills.com/help/waits/' + LOWER(wd1.wait_type) + '/' AS URL + FROM max_batch b + JOIN #WaitStats wd2 ON + wd2.SampleTime =b.SampleTime + JOIN #WaitStats wd1 ON + wd1.wait_type=wd2.wait_type AND + wd2.SampleTime > wd1.SampleTime + CROSS APPLY (SELECT SUM(1) AS cpu_count FROM sys.dm_os_schedulers WHERE status = 'VISIBLE ONLINE' AND is_online = 1) AS cores + CROSS APPLY (SELECT + CAST((wd2.wait_time_ms-wd1.wait_time_ms)/1000. AS NUMERIC(12,1)) AS [Wait Time (Seconds)], + CAST((wd2.signal_wait_time_ms - wd1.signal_wait_time_ms)/1000. AS NUMERIC(12,1)) AS [Signal Wait Time (Seconds)]) AS c + LEFT OUTER JOIN ##WaitCategories wcat ON wd1.wait_type = wcat.WaitType + WHERE (wd2.waiting_tasks_count - wd1.waiting_tasks_count) > 0 + AND wd2.wait_time_ms-wd1.wait_time_ms > 0 + ORDER BY [Wait Time (Seconds)] DESC; + END; + ELSE + BEGIN + /* Measure waits in seconds */ + ;WITH max_batch AS ( + SELECT MAX(SampleTime) AS SampleTime + FROM #WaitStats + ) + SELECT + 'WAIT STATS' AS Pattern, + b.SampleTime AS [Sample Ended], + DATEDIFF(ss,wd1.SampleTime, wd2.SampleTime) AS [Seconds Sample], + wd1.wait_type, + COALESCE(wcat.WaitCategory, 'Other') AS wait_category, + c.[Wait Time (Seconds)], + CAST((CAST(wd2.wait_time_ms - wd1.wait_time_ms AS MONEY)) / 1000.0 / cores.cpu_count / DATEDIFF(ss, wd1.SampleTime, wd2.SampleTime) AS DECIMAL(18,1)) AS [Per Core Per Second], + c.[Signal Wait Time (Seconds)], + CASE WHEN c.[Wait Time (Seconds)] > 0 + THEN CAST(100.*(c.[Signal Wait Time (Seconds)]/c.[Wait Time (Seconds)]) AS NUMERIC(4,1)) + ELSE 0 END AS [Percent Signal Waits], + (wd2.waiting_tasks_count - wd1.waiting_tasks_count) AS [Number of Waits], + CASE WHEN (wd2.waiting_tasks_count - wd1.waiting_tasks_count) > 0 + THEN + CAST((wd2.wait_time_ms-wd1.wait_time_ms)/ + (1.0*(wd2.waiting_tasks_count - wd1.waiting_tasks_count)) AS NUMERIC(12,1)) + ELSE 0 END AS [Avg ms Per Wait], + N'https://www.sqlskills.com/help/waits/' + LOWER(wd1.wait_type) + '/' AS URL + FROM max_batch b + JOIN #WaitStats wd2 ON + wd2.SampleTime =b.SampleTime + JOIN #WaitStats wd1 ON + wd1.wait_type=wd2.wait_type AND + wd2.SampleTime > wd1.SampleTime + CROSS APPLY (SELECT SUM(1) AS cpu_count FROM sys.dm_os_schedulers WHERE status = 'VISIBLE ONLINE' AND is_online = 1) AS cores + CROSS APPLY (SELECT + CAST((wd2.wait_time_ms-wd1.wait_time_ms)/1000. AS NUMERIC(12,1)) AS [Wait Time (Seconds)], + CAST((wd2.signal_wait_time_ms - wd1.signal_wait_time_ms)/1000. AS NUMERIC(12,1)) AS [Signal Wait Time (Seconds)]) AS c + LEFT OUTER JOIN ##WaitCategories wcat ON wd1.wait_type = wcat.WaitType + WHERE (wd2.waiting_tasks_count - wd1.waiting_tasks_count) > 0 + AND wd2.wait_time_ms-wd1.wait_time_ms > 0 + ORDER BY [Wait Time (Seconds)] DESC; + END; + + ------------------------- + --What happened: #FileStats + ------------------------- + WITH readstats AS ( + SELECT 'PHYSICAL READS' AS Pattern, + ROW_NUMBER() OVER (ORDER BY wd2.avg_stall_read_ms DESC) AS StallRank, + wd2.SampleTime AS [Sample Time], + DATEDIFF(ss,wd1.SampleTime, wd2.SampleTime) AS [Sample (seconds)], + wd1.DatabaseName , + wd1.FileLogicalName AS [File Name], + UPPER(SUBSTRING(wd1.PhysicalName, 1, 2)) AS [Drive] , + wd1.SizeOnDiskMB , + ( wd2.num_of_reads - wd1.num_of_reads ) AS [# Reads/Writes], + CASE WHEN wd2.num_of_reads - wd1.num_of_reads > 0 + THEN CAST(( wd2.bytes_read - wd1.bytes_read)/1024./1024. AS NUMERIC(21,1)) + ELSE 0 + END AS [MB Read/Written], + wd2.avg_stall_read_ms AS [Avg Stall (ms)], + wd1.PhysicalName AS [file physical name] + FROM #FileStats wd2 + JOIN #FileStats wd1 ON wd2.SampleTime > wd1.SampleTime + AND wd1.DatabaseID = wd2.DatabaseID + AND wd1.FileID = wd2.FileID + ), + writestats AS ( + SELECT + 'PHYSICAL WRITES' AS Pattern, + ROW_NUMBER() OVER (ORDER BY wd2.avg_stall_write_ms DESC) AS StallRank, + wd2.SampleTime AS [Sample Time], + DATEDIFF(ss,wd1.SampleTime, wd2.SampleTime) AS [Sample (seconds)], + wd1.DatabaseName , + wd1.FileLogicalName AS [File Name], + UPPER(SUBSTRING(wd1.PhysicalName, 1, 2)) AS [Drive] , + wd1.SizeOnDiskMB , + ( wd2.num_of_writes - wd1.num_of_writes ) AS [# Reads/Writes], + CASE WHEN wd2.num_of_writes - wd1.num_of_writes > 0 + THEN CAST(( wd2.bytes_written - wd1.bytes_written)/1024./1024. AS NUMERIC(21,1)) + ELSE 0 + END AS [MB Read/Written], + wd2.avg_stall_write_ms AS [Avg Stall (ms)], + wd1.PhysicalName AS [file physical name] + FROM #FileStats wd2 + JOIN #FileStats wd1 ON wd2.SampleTime > wd1.SampleTime + AND wd1.DatabaseID = wd2.DatabaseID + AND wd1.FileID = wd2.FileID + ) + SELECT + Pattern, [Sample Time], [Sample (seconds)], [File Name], [Drive], [# Reads/Writes],[MB Read/Written],[Avg Stall (ms)], [file physical name], [StallRank] + FROM readstats + WHERE StallRank <=20 AND [MB Read/Written] > 0 + UNION ALL + SELECT Pattern, [Sample Time], [Sample (seconds)], [File Name], [Drive], [# Reads/Writes],[MB Read/Written],[Avg Stall (ms)], [file physical name], [StallRank] + FROM writestats + WHERE StallRank <=20 AND [MB Read/Written] > 0 + ORDER BY Pattern, StallRank; + + + ------------------------- + --What happened: #PerfmonStats + ------------------------- + + SELECT 'PERFMON' AS Pattern, pLast.[object_name], pLast.counter_name, pLast.instance_name, + pFirst.SampleTime AS FirstSampleTime, pFirst.cntr_value AS FirstSampleValue, + pLast.SampleTime AS LastSampleTime, pLast.cntr_value AS LastSampleValue, + pLast.cntr_value - pFirst.cntr_value AS ValueDelta, + ((1.0 * pLast.cntr_value - pFirst.cntr_value) / DATEDIFF(ss, pFirst.SampleTime, pLast.SampleTime)) AS ValuePerSecond + FROM #PerfmonStats pLast + INNER JOIN #PerfmonStats pFirst ON pFirst.[object_name] = pLast.[object_name] AND pFirst.counter_name = pLast.counter_name AND (pFirst.instance_name = pLast.instance_name OR (pFirst.instance_name IS NULL AND pLast.instance_name IS NULL)) + AND pLast.ID > pFirst.ID + WHERE pLast.cntr_value <> pFirst.cntr_value + ORDER BY Pattern, pLast.[object_name], pLast.counter_name, pLast.instance_name; + + + ------------------------- + --What happened: #QueryStats + ------------------------- + IF @CheckProcedureCache = 1 + BEGIN + + SELECT qsNow.*, qsFirst.* + FROM #QueryStats qsNow + INNER JOIN #QueryStats qsFirst ON qsNow.[sql_handle] = qsFirst.[sql_handle] AND qsNow.statement_start_offset = qsFirst.statement_start_offset AND qsNow.statement_end_offset = qsFirst.statement_end_offset AND qsNow.plan_generation_num = qsFirst.plan_generation_num AND qsNow.plan_handle = qsFirst.plan_handle AND qsFirst.Pass = 1 + WHERE qsNow.Pass = 2; + END; + ELSE + BEGIN + SELECT 'Plan Cache' AS [Pattern], 'Plan cache not analyzed' AS [Finding], 'Use @CheckProcedureCache = 1 or run sp_BlitzCache for more analysis' AS [More Info], CONVERT(XML, @StockDetailsHeader + 'firstresponderkit.org' + @StockDetailsFooter) AS [Details]; + END; + END; + + DROP TABLE #BlitzFirstResults; + + /* What's running right now? This is the first and last result set. */ + IF @SinceStartup = 0 AND @Seconds > 0 AND @ExpertMode = 1 AND @OutputType <> 'NONE' + BEGIN + IF OBJECT_ID('master.dbo.sp_BlitzWho') IS NULL AND OBJECT_ID('dbo.sp_BlitzWho') IS NULL + BEGIN + PRINT N'sp_BlitzWho is not installed in the current database_files. You can get a copy from http://FirstResponderKit.org'; + END; + ELSE + BEGIN + EXEC (@BlitzWho); + END; + END; /* IF @SinceStartup = 0 AND @Seconds > 0 AND @ExpertMode = 1 AND @OutputType <> 'NONE' - What's running right now? This is the first and last result set. */ + +END; /* IF @LogMessage IS NULL */ +END; /* ELSE IF @OutputType = 'SCHEMA' */ + +SET NOCOUNT OFF; +GO + + + +/* How to run it: +EXEC dbo.sp_BlitzFirst + +With extra diagnostic info: +EXEC dbo.sp_BlitzFirst @ExpertMode = 1; + +Saving output to tables: +EXEC sp_BlitzFirst + @OutputDatabaseName = 'DBAtools' +, @OutputSchemaName = 'dbo' +, @OutputTableName = 'BlitzFirst' +, @OutputTableNameFileStats = 'BlitzFirst_FileStats' +, @OutputTableNamePerfmonStats = 'BlitzFirst_PerfmonStats' +, @OutputTableNameWaitStats = 'BlitzFirst_WaitStats' +, @OutputTableNameBlitzCache = 'BlitzCache' +, @OutputTableNameBlitzWho = 'BlitzWho' +*/ diff --git a/Install-Core-Blitz-With-Query-Store.sql b/Install-Core-Blitz-With-Query-Store.sql index 50929ea72..0f27b66ce 100755 --- a/Install-Core-Blitz-With-Query-Store.sql +++ b/Install-Core-Blitz-With-Query-Store.sql @@ -37,7 +37,7 @@ AS SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.0', @VersionDate = '20210117'; + SELECT @Version = '8.01', @VersionDate = '20210222'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -7128,6 +7128,69 @@ IF @ProductVersionMajor >= 10 -- HAVING COUNT(DISTINCT o.object_id) > 0;'; --END; --of Check 220. + /*Check for the last good DBCC CHECKDB date */ + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 68 ) + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 68) WITH NOWAIT; + + EXEC sp_MSforeachdb N'USE [?]; + SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + INSERT #DBCCs + (ParentObject, + Object, + Field, + Value) + EXEC (''DBCC DBInfo() With TableResults, NO_INFOMSGS''); + UPDATE #DBCCs SET DbName = N''?'' WHERE DbName IS NULL OPTION (RECOMPILE);'; + + WITH DB2 + AS ( SELECT DISTINCT + Field , + Value , + DbName + FROM #DBCCs + INNER JOIN sys.databases d ON #DBCCs.DbName = d.name + WHERE Field = 'dbi_dbccLastKnownGood' + AND d.create_date < DATEADD(dd, -14, GETDATE()) + ) + INSERT INTO #BlitzResults + ( CheckID , + DatabaseName , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT 68 AS CheckID , + DB2.DbName AS DatabaseName , + 1 AS PRIORITY , + 'Reliability' AS FindingsGroup , + 'Last good DBCC CHECKDB over 2 weeks old' AS Finding , + 'https://BrentOzar.com/go/checkdb' AS URL , + 'Last successful CHECKDB: ' + + CASE DB2.Value + WHEN '1900-01-01 00:00:00.000' + THEN ' never.' + ELSE DB2.Value + END AS Details + FROM DB2 + WHERE DB2.DbName <> 'tempdb' + AND DB2.DbName NOT IN ( SELECT DISTINCT + DatabaseName + FROM + #SkipChecks + WHERE CheckID IS NULL OR CheckID = 68) + AND DB2.DbName NOT IN ( SELECT name + FROM sys.databases + WHERE is_read_only = 1) + AND CONVERT(DATETIME, DB2.Value, 121) < DATEADD(DD, + -14, + CURRENT_TIMESTAMP); + END; END; /* IF @CheckUserDatabaseObjects = 1 */ @@ -7557,69 +7620,6 @@ IF @ProductVersionMajor >= 10 WHERE s.service_account IS NULL AND ep.principal_id <> 1; END; - /*Check for the last good DBCC CHECKDB date */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 68 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 68) WITH NOWAIT; - - EXEC sp_MSforeachdb N'USE [?]; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - INSERT #DBCCs - (ParentObject, - Object, - Field, - Value) - EXEC (''DBCC DBInfo() With TableResults, NO_INFOMSGS''); - UPDATE #DBCCs SET DbName = N''?'' WHERE DbName IS NULL OPTION (RECOMPILE);'; - - WITH DB2 - AS ( SELECT DISTINCT - Field , - Value , - DbName - FROM #DBCCs - INNER JOIN sys.databases d ON #DBCCs.DbName = d.name - WHERE Field = 'dbi_dbccLastKnownGood' - AND d.create_date < DATEADD(dd, -14, GETDATE()) - ) - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 68 AS CheckID , - DB2.DbName AS DatabaseName , - 1 AS PRIORITY , - 'Reliability' AS FindingsGroup , - 'Last good DBCC CHECKDB over 2 weeks old' AS Finding , - 'https://BrentOzar.com/go/checkdb' AS URL , - 'Last successful CHECKDB: ' - + CASE DB2.Value - WHEN '1900-01-01 00:00:00.000' - THEN ' never.' - ELSE DB2.Value - END AS Details - FROM DB2 - WHERE DB2.DbName <> 'tempdb' - AND DB2.DbName NOT IN ( SELECT DISTINCT - DatabaseName - FROM - #SkipChecks - WHERE CheckID IS NULL OR CheckID = 68) - AND DB2.DbName NOT IN ( SELECT name - FROM sys.databases - WHERE is_read_only = 1) - AND CONVERT(DATETIME, DB2.Value, 121) < DATEADD(DD, - -14, - CURRENT_TIMESTAMP); - END; /*Verify that the servername is set */ IF NOT EXISTS ( SELECT 1 @@ -7741,21 +7741,29 @@ IF @ProductVersionMajor >= 10 SELECT 74 AS CheckID , 200 AS Priority , 'Informational' AS FindingsGroup , - 'TraceFlag On' AS Finding , + 'Trace Flag On' AS Finding , CASE WHEN [T].[TraceFlag] = '834' AND @ColumnStoreIndexesInUse = 1 THEN 'https://support.microsoft.com/en-us/kb/3210239' ELSE'https://www.BrentOzar.com/go/traceflags/' END AS URL , 'Trace flag ' + - CASE WHEN [T].[TraceFlag] = '2330' THEN ' 2330 enabled globally. Using this trace Flag disables missing index requests!' - WHEN [T].[TraceFlag] = '1211' THEN ' 1211 enabled globally. Using this Trace Flag disables lock escalation when you least expect it. No Bueno!' - WHEN [T].[TraceFlag] = '1224' THEN ' 1224 enabled globally. Using this Trace Flag disables lock escalation based on the number of locks being taken. You shouldn''t have done that, Dave.' - WHEN [T].[TraceFlag] = '652' THEN ' 652 enabled globally. Using this Trace Flag disables pre-fetching during index scans. If you hate slow queries, you should turn that off.' - WHEN [T].[TraceFlag] = '661' THEN ' 661 enabled globally. Using this Trace Flag disables ghost record removal. Who you gonna call? No one, turn that thing off.' - WHEN [T].[TraceFlag] = '1806' THEN ' 1806 enabled globally. Using this Trace Flag disables Instant File Initialization. I question your sanity.' - WHEN [T].[TraceFlag] = '3505' THEN ' 3505 enabled globally. Using this Trace Flag disables Checkpoints. Probably not the wisest idea.' - WHEN [T].[TraceFlag] = '8649' THEN ' 8649 enabled globally. Using this Trace Flag drops cost threshold for parallelism down to 0. I hope this is a dev server.' - WHEN [T].[TraceFlag] = '834' AND @ColumnStoreIndexesInUse = 1 THEN ' 834 is enabled globally. Using this Trace Flag with Columnstore Indexes is not a great idea.' - WHEN [T].[TraceFlag] = '8017' AND (CAST(SERVERPROPERTY('Edition') AS NVARCHAR(1000)) LIKE N'%Express%') THEN ' 8017 is enabled globally, which is the default for express edition.' - WHEN [T].[TraceFlag] = '8017' AND (CAST(SERVERPROPERTY('Edition') AS NVARCHAR(1000)) NOT LIKE N'%Express%') THEN ' 8017 is enabled globally. Using this Trace Flag disables creation schedulers for all logical processors. Not good.' + CASE WHEN [T].[TraceFlag] = '652' THEN '652 enabled globally, which disables pre-fetching during index scans. This is usually a very bad idea.' + WHEN [T].[TraceFlag] = '661' THEN '661 enabled globally, which disables ghost record removal, causing the database to grow in size. This is usually a very bad idea.' + WHEN [T].[TraceFlag] = '834' AND @ColumnStoreIndexesInUse = 1 THEN '834 is enabled globally, but you also have columnstore indexes. That combination is not recommended by Microsoft.' + WHEN [T].[TraceFlag] = '1117' THEN '1117 enabled globally, which grows all files in a filegroup at the same time.' + WHEN [T].[TraceFlag] = '1118' THEN '1118 enabled globally, which tries to reduce SGAM waits.' + WHEN [T].[TraceFlag] = '1211' THEN '1211 enabled globally, which disables lock escalation when you least expect it. This is usually a very bad idea.' + WHEN [T].[TraceFlag] = '1222' THEN '1222 enabled globally, which captures deadlock graphs in the error log.' + WHEN [T].[TraceFlag] = '1224' THEN '1224 enabled globally, which disables lock escalation until the server has memory pressure. This is usually a very bad idea.' + WHEN [T].[TraceFlag] = '1806' THEN '1806 enabled globally, which disables Instant File Initialization, causing restores and file growths to take longer. This is usually a very bad idea.' + WHEN [T].[TraceFlag] = '2330' THEN '2330 enabled globally, which disables missing index requests. This is usually a very bad idea.' + WHEN [T].[TraceFlag] = '2371' THEN '2371 enabled globally, which changes the auto update stats threshold.' + WHEN [T].[TraceFlag] = '3023' THEN '3023 enabled globally, which performs checksums by default when doing database backups.' + WHEN [T].[TraceFlag] = '3226' THEN '3226 enabled globally, which keeps the event log clean by not reporting successful backups.' + WHEN [T].[TraceFlag] = '3505' THEN '3505 enabled globally, which disables Checkpoints. This is usually a very bad idea.' + WHEN [T].[TraceFlag] = '4199' THEN '4199 enabled globally, which enables non-default Query Optimizer fixes, changing query plans from the default behaviors.' + WHEN [T].[TraceFlag] = '8048' THEN '8048 enabled globally, which tries to reduce CMEMTHREAD waits on servers with a lot of logical processors.' + WHEN [T].[TraceFlag] = '8017' AND (CAST(SERVERPROPERTY('Edition') AS NVARCHAR(1000)) LIKE N'%Express%') THEN '8017 is enabled globally, but this is the default for Express Edition.' + WHEN [T].[TraceFlag] = '8017' AND (CAST(SERVERPROPERTY('Edition') AS NVARCHAR(1000)) NOT LIKE N'%Express%') THEN '8017 is enabled globally, which disables the creation of schedulers for all logical processors.' + WHEN [T].[TraceFlag] = '8649' THEN '8649 enabled globally, which cost threshold for parallelism down to 0. This is usually a very bad idea.' ELSE [T].[TraceFlag] + ' is enabled globally.' END AS Details FROM #TraceStatus T; @@ -8825,7 +8833,7 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 250 AS Priority , ''Server Info'' AS FindingsGroup , ''Azure Managed Instance'' AS Finding , - ''https://www.BrenOzar.com/go/azurevm'' AS URL , + ''https://www.BrentOzar.com/go/azurevm'' AS URL , ''cpu_rate: '' + CAST(COALESCE(cpu_rate, 0) AS VARCHAR(20)) + '', memory_limit_mb: '' + CAST(COALESCE(memory_limit_mb, 0) AS NVARCHAR(20)) + '', process_memory_limit_mb: '' + CAST(COALESCE(process_memory_limit_mb, 0) AS NVARCHAR(20)) + @@ -8853,7 +8861,7 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 CREATE TABLE #services (cmdshell_output varchar(max)); INSERT INTO #services - EXEC xp_cmdshell 'net start' + EXEC /**/xp_cmdshell/**/ 'net start' /* added comments around command since some firewalls block this string TL 20210221 */ IF EXISTS (SELECT 1 FROM #services @@ -9466,7 +9474,7 @@ AS SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.0', @VersionDate = '20210117'; + SELECT @Version = '8.01', @VersionDate = '20210222'; IF(@VersionCheckMode = 1) BEGIN @@ -11245,7 +11253,7 @@ BEGIN SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.0', @VersionDate = '20210117'; +SELECT @Version = '8.01', @VersionDate = '20210222'; IF(@VersionCheckMode = 1) @@ -13304,7 +13312,7 @@ BEGIN WHEN N'writes' THEN N'AND total_logical_writes > 0' WHEN N'duration' THEN N'AND total_elapsed_time > 0' WHEN N'executions' THEN N'AND execution_count > 0' - WHEN N'compiles' THEN N'AND (age_minutes + age_minutes_lifetime) > 0' + /* WHEN N'compiles' THEN N'AND (age_minutes + age_minutes_lifetime) > 0' BGO 2021-01-24 commenting out for https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2772 */ WHEN N'memory grant' THEN N'AND max_grant_kb > 0' WHEN N'unused grant' THEN N'AND max_grant_kb > 0' WHEN N'spills' THEN N'AND max_spills > 0' @@ -13319,6 +13327,7 @@ BEGIN WHEN COALESCE(age_minutes, age_minutes_lifetime, 0) = 0 THEN 0 ELSE CAST((1.00 * execution_count / COALESCE(age_minutes, age_minutes_lifetime)) AS money) END > 0' + ELSE N' /* No minimum threshold set */ ' END; SET @sql += REPLACE(@body_where, 'cached_time', 'creation_time') ; @@ -15899,7 +15908,8 @@ BEGIN PlanHandle AS [Plan Handle], SqlHandle AS [SQL Handle], COALESCE(SetOptions, '''') AS [SET Options], - QueryHash AS [Query Hash], + QueryHash AS [Query Hash], + PlanGenerationNum, [Remove Plan Handle From Cache]'; END; ELSE @@ -17442,6 +17452,7 @@ IF OBJECT_ID('tempdb.. #bou_allsort') IS NULL SqlHandle VARBINARY(64), SetOptions VARCHAR(MAX), QueryHash BINARY(8), + PlanGenerationNum NVARCHAR(30), RemovePlanHandleFromCache NVARCHAR(200), Pattern NVARCHAR(20) ); @@ -17457,7 +17468,7 @@ SET @AllSortSql += N' INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, RemovePlanHandleFromCache ) + MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''cpu'', @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; @@ -17469,7 +17480,7 @@ SET @AllSortSql += N' INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, RemovePlanHandleFromCache ) + MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''reads'', @IgnoreSqlHandles = @ISH, @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; @@ -17481,7 +17492,7 @@ SET @AllSortSql += N' INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, RemovePlanHandleFromCache ) + MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''writes'', @IgnoreSqlHandles = @ISH, @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; @@ -17493,7 +17504,7 @@ SET @AllSortSql += N' INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, RemovePlanHandleFromCache ) + MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''duration'', @IgnoreSqlHandles = @ISH, @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; @@ -17505,7 +17516,7 @@ SET @AllSortSql += N' INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, RemovePlanHandleFromCache ) + MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''executions'', @IgnoreSqlHandles = @ISH, @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; @@ -17540,7 +17551,7 @@ SET @AllSortSql += N' INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, RemovePlanHandleFromCache ) + MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''memory grant'', @IgnoreSqlHandles = @ISH, @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; @@ -17589,7 +17600,7 @@ SET @AllSortSql += N' INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, RemovePlanHandleFromCache ) + MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''spills'', @IgnoreSqlHandles = @ISH, @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; @@ -17614,7 +17625,7 @@ SET @AllSortSql += N' SET @AllSortSql += N' SELECT DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters,ExecutionCount, ExecutionsPerMinute, ExecutionWeight, TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, RemovePlanHandleFromCache, Pattern + MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache, Pattern FROM #bou_allsort ORDER BY Id OPTION(RECOMPILE); '; @@ -17632,7 +17643,7 @@ SET @AllSortSql += N' INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, RemovePlanHandleFromCache ) + MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''avg cpu'', @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; @@ -17644,7 +17655,7 @@ SET @AllSortSql += N' INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, RemovePlanHandleFromCache ) + MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''avg reads'', @IgnoreSqlHandles = @ISH, @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; @@ -17656,7 +17667,7 @@ SET @AllSortSql += N' INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, RemovePlanHandleFromCache ) + MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''avg writes'', @IgnoreSqlHandles = @ISH, @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; @@ -17668,7 +17679,7 @@ SET @AllSortSql += N' INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, RemovePlanHandleFromCache ) + MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''avg duration'', @IgnoreSqlHandles = @ISH, @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; @@ -17680,7 +17691,7 @@ SET @AllSortSql += N' INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, RemovePlanHandleFromCache ) + MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''avg executions'', @IgnoreSqlHandles = @ISH, @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; @@ -17715,7 +17726,7 @@ SET @AllSortSql += N' INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, RemovePlanHandleFromCache ) + MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''avg memory grant'', @IgnoreSqlHandles = @ISH, @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; @@ -17764,7 +17775,7 @@ SET @AllSortSql += N' INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, RemovePlanHandleFromCache ) + MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''avg spills'', @IgnoreSqlHandles = @ISH, @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; @@ -17790,7 +17801,7 @@ SET @AllSortSql += N' SET @AllSortSql += N' SELECT DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters,ExecutionCount, ExecutionsPerMinute, ExecutionWeight, TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, RemovePlanHandleFromCache, Pattern + MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache, Pattern FROM #bou_allsort ORDER BY Id OPTION(RECOMPILE); '; @@ -17998,7 +18009,7 @@ BEGIN ALTER TABLE '+@OutputDatabaseName+'.'+@OutputSchemaName+'.'+@OutputTableName+' DROP COLUMN [PlanCreationTimeHours]; ALTER TABLE '+@OutputDatabaseName+'.'+@OutputSchemaName+'.'+@OutputTableName+' ADD [PlanCreationTimeHours] AS DATEDIFF(HOUR,CONVERT(DATETIMEOFFSET(7),[PlanCreationTime]),[CheckDate]); END '; - + IF @ValidOutputServer = 1 BEGIN SET @StringToExecute = REPLACE(@StringToExecute,''''+@OutputSchemaName+'''',''''''+@OutputSchemaName+''''''); @@ -18381,54 +18392,55 @@ END; /* End of writing results to table */ END; /*Final End*/ GO -IF OBJECT_ID('dbo.sp_BlitzFirst') IS NULL - EXEC ('CREATE PROCEDURE dbo.sp_BlitzFirst AS RETURN 0;'); +SET ANSI_NULLS ON; +SET ANSI_PADDING ON; +SET ANSI_WARNINGS ON; +SET ARITHABORT ON; +SET CONCAT_NULL_YIELDS_NULL ON; +SET QUOTED_IDENTIFIER ON; +SET STATISTICS IO OFF; +SET STATISTICS TIME OFF; GO +IF OBJECT_ID('dbo.sp_BlitzIndex') IS NULL + EXEC ('CREATE PROCEDURE dbo.sp_BlitzIndex AS RETURN 0;'); +GO -ALTER PROCEDURE [dbo].[sp_BlitzFirst] - @LogMessage NVARCHAR(4000) = NULL , - @Help TINYINT = 0 , - @AsOf DATETIMEOFFSET = NULL , - @ExpertMode TINYINT = 0 , - @Seconds INT = 5 , - @OutputType VARCHAR(20) = 'TABLE' , +ALTER PROCEDURE dbo.sp_BlitzIndex + @DatabaseName NVARCHAR(128) = NULL, /*Defaults to current DB if not specified*/ + @SchemaName NVARCHAR(128) = NULL, /*Requires table_name as well.*/ + @TableName NVARCHAR(128) = NULL, /*Requires schema_name as well.*/ + @Mode TINYINT=0, /*0=Diagnose, 1=Summarize, 2=Index Usage Detail, 3=Missing Index Detail, 4=Diagnose Details*/ + /*Note:@Mode doesn't matter if you're specifying schema_name and @TableName.*/ + @Filter TINYINT = 0, /* 0=no filter (default). 1=No low-usage warnings for objects with 0 reads. 2=Only warn for objects >= 500MB */ + /*Note:@Filter doesn't do anything unless @Mode=0*/ + @SkipPartitions BIT = 0, + @SkipStatistics BIT = 1, + @GetAllDatabases BIT = 0, + @BringThePain BIT = 0, + @IgnoreDatabases NVARCHAR(MAX) = NULL, /* Comma-delimited list of databases you want to skip */ + @ThresholdMB INT = 250 /* Number of megabytes that an object must be before we include it in basic results */, + @OutputType VARCHAR(20) = 'TABLE' , @OutputServerName NVARCHAR(256) = NULL , @OutputDatabaseName NVARCHAR(256) = NULL , @OutputSchemaName NVARCHAR(256) = NULL , @OutputTableName NVARCHAR(256) = NULL , - @OutputTableNameFileStats NVARCHAR(256) = NULL , - @OutputTableNamePerfmonStats NVARCHAR(256) = NULL , - @OutputTableNameWaitStats NVARCHAR(256) = NULL , - @OutputTableNameBlitzCache NVARCHAR(256) = NULL , - @OutputTableNameBlitzWho NVARCHAR(256) = NULL , - @OutputTableRetentionDays TINYINT = 7 , - @OutputXMLasNVARCHAR TINYINT = 0 , - @FilterPlansByDatabase VARCHAR(MAX) = NULL , - @CheckProcedureCache TINYINT = 0 , - @CheckServerInfo TINYINT = 1 , - @FileLatencyThresholdMS INT = 100 , - @SinceStartup TINYINT = 0 , - @ShowSleepingSPIDs TINYINT = 0 , - @BlitzCacheSkipAnalysis BIT = 1 , - @MemoryGrantThresholdPct DECIMAL(5,2) = 15.00, - @LogMessageCheckID INT = 38, - @LogMessagePriority TINYINT = 1, - @LogMessageFindingsGroup VARCHAR(50) = 'Logged Message', - @LogMessageFinding VARCHAR(200) = 'Logged from sp_BlitzFirst', - @LogMessageURL VARCHAR(200) = '', - @LogMessageCheckDate DATETIMEOFFSET = NULL, - @Debug BIT = 0, - @Version VARCHAR(30) = NULL OUTPUT, + @IncludeInactiveIndexes BIT = 0 /* Will skip indexes with no reads or writes */, + @ShowAllMissingIndexRequests BIT = 0 /*Will make all missing index requests show up*/, + @SortOrder NVARCHAR(50) = NULL, /* Only affects @Mode = 2. */ + @SortDirection NVARCHAR(4) = 'DESC', /* Only affects @Mode = 2. */ + @Help TINYINT = 0, + @Debug BIT = 0, + @Version VARCHAR(30) = NULL OUTPUT, @VersionDate DATETIME = NULL OUTPUT, @VersionCheckMode BIT = 0 - WITH EXECUTE AS CALLER, RECOMPILE +WITH RECOMPILE AS -BEGIN SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.0', @VersionDate = '20210117'; +SELECT @Version = '8.01', @VersionDate = '20210222'; +SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) BEGIN @@ -18438,32 +18450,28 @@ END; IF @Help = 1 BEGIN PRINT ' -sp_BlitzFirst from http://FirstResponderKit.org +/* +sp_BlitzIndex from http://FirstResponderKit.org -This script gives you a prioritized list of why your SQL Server is slow right now. - -This is not an overall health check - for that, check out sp_Blitz. +This script analyzes the design and performance of your indexes. To learn more, visit http://FirstResponderKit.org where you can download new versions for free, watch training videos on how it works, get more info on the findings, contribute your own code, and more. Known limitations of this version: - - Only Microsoft-supported versions of SQL Server. Sorry, 2005 and 2000. It - may work just fine on 2005, and if it does, hug your parents. Just don''t - file support issues if it breaks. - - If a temp table called #CustomPerfmonCounters exists for any other session, - but not our session, this stored proc will fail with an error saying the - temp table #CustomPerfmonCounters does not exist. - - @OutputServerName is not functional yet. - - If @OutputDatabaseName, SchemaName, TableName, etc are quoted with brackets, - the write to table may silently fail. Look, I never said I was good at this. + - Only Microsoft-supported versions of SQL Server. Sorry, 2005 and 2000. + - Index create statements are just to give you a rough idea of the syntax. It includes filters and fillfactor. + -- Example 1: index creates use ONLINE=? instead of ONLINE=ON / ONLINE=OFF. This is because it is important + for the user to understand if it is going to be offline and not just run a script. + -- Example 2: they do not include all the options the index may have been created with (padding, compression + filegroup/partition scheme etc.) + -- (The compression and filegroup index create syntax is not trivial because it is set at the partition + level and is not trivial to code.) + - Does not advise you about data modeling for clustered indexes and primary keys (primarily looks for signs of insanity.) Unknown limitations of this version: - - None. Like Zombo.com, the only limit is yourself. - -Changes - for the full list of improvements and fixes in this version, see: -https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/ + - We knew them once, but we forgot. MIT License @@ -18487,12985 +18495,9353 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - '; RETURN; END; /* @Help = 1 */ -RAISERROR('Setting up configuration variables',10,1) WITH NOWAIT; -DECLARE @StringToExecute NVARCHAR(MAX), - @ParmDefinitions NVARCHAR(4000), - @Parm1 NVARCHAR(4000), - @OurSessionID INT, - @LineFeed NVARCHAR(10), - @StockWarningHeader NVARCHAR(MAX) = N'', - @StockWarningFooter NVARCHAR(MAX) = N'', - @StockDetailsHeader NVARCHAR(MAX) = N'', - @StockDetailsFooter NVARCHAR(MAX) = N'', - @StartSampleTime DATETIMEOFFSET, - @FinishSampleTime DATETIMEOFFSET, - @FinishSampleTimeWaitFor DATETIME, - @AsOf1 DATETIMEOFFSET, - @AsOf2 DATETIMEOFFSET, - @ServiceName sysname, - @OutputTableNameFileStats_View NVARCHAR(256), - @OutputTableNamePerfmonStats_View NVARCHAR(256), - @OutputTableNamePerfmonStatsActuals_View NVARCHAR(256), - @OutputTableNameWaitStats_View NVARCHAR(256), - @OutputTableNameWaitStats_Categories NVARCHAR(256), - @OutputTableCleanupDate DATE, - @ObjectFullName NVARCHAR(2000), - @BlitzWho NVARCHAR(MAX) = N'EXEC dbo.sp_BlitzWho @ShowSleepingSPIDs = ' + CONVERT(NVARCHAR(1), @ShowSleepingSPIDs) + N';', - @BlitzCacheMinutesBack INT, - @UnquotedOutputServerName NVARCHAR(256) = @OutputServerName , - @UnquotedOutputDatabaseName NVARCHAR(256) = @OutputDatabaseName , - @UnquotedOutputSchemaName NVARCHAR(256) = @OutputSchemaName , - @LocalServerName NVARCHAR(128) = CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128)), - @dm_exec_query_statistics_xml BIT = 0; +DECLARE @ScriptVersionName NVARCHAR(50); +DECLARE @DaysUptime NUMERIC(23,2); +DECLARE @DatabaseID INT; +DECLARE @ObjectID INT; +DECLARE @dsql NVARCHAR(MAX); +DECLARE @params NVARCHAR(MAX); +DECLARE @msg NVARCHAR(4000); +DECLARE @ErrorSeverity INT; +DECLARE @ErrorState INT; +DECLARE @Rowcount BIGINT; +DECLARE @SQLServerProductVersion NVARCHAR(128); +DECLARE @SQLServerEdition INT; +DECLARE @FilterMB INT; +DECLARE @collation NVARCHAR(256); +DECLARE @NumDatabases INT; +DECLARE @LineFeed NVARCHAR(5); +DECLARE @DaysUptimeInsertValue NVARCHAR(256); +DECLARE @DatabaseToIgnore NVARCHAR(MAX); +DECLARE @ColumnList NVARCHAR(MAX); -/* Sanitize our inputs */ -SELECT - @OutputTableNameFileStats_View = QUOTENAME(@OutputTableNameFileStats + '_Deltas'), - @OutputTableNamePerfmonStats_View = QUOTENAME(@OutputTableNamePerfmonStats + '_Deltas'), - @OutputTableNamePerfmonStatsActuals_View = QUOTENAME(@OutputTableNamePerfmonStats + '_Actuals'), - @OutputTableNameWaitStats_View = QUOTENAME(@OutputTableNameWaitStats + '_Deltas'), - @OutputTableNameWaitStats_Categories = QUOTENAME(@OutputTableNameWaitStats + '_Categories'); +/* Let's get @SortOrder set to lower case here for comparisons later */ +SET @SortOrder = REPLACE(LOWER(@SortOrder), N' ', N'_'); +SET @SortDirection = LOWER(@SortDirection); -SELECT - @OutputDatabaseName = QUOTENAME(@OutputDatabaseName), - @OutputSchemaName = QUOTENAME(@OutputSchemaName), - @OutputTableName = QUOTENAME(@OutputTableName), - @OutputTableNameFileStats = QUOTENAME(@OutputTableNameFileStats), - @OutputTableNamePerfmonStats = QUOTENAME(@OutputTableNamePerfmonStats), - @OutputTableNameWaitStats = QUOTENAME(@OutputTableNameWaitStats), - @OutputTableCleanupDate = CAST( (DATEADD(DAY, -1 * @OutputTableRetentionDays, GETDATE() ) ) AS DATE), - /* @OutputTableNameBlitzCache = QUOTENAME(@OutputTableNameBlitzCache), We purposely don't sanitize this because sp_BlitzCache will */ - /* @OutputTableNameBlitzWho = QUOTENAME(@OutputTableNameBlitzWho), We purposely don't sanitize this because sp_BlitzWho will */ - @LineFeed = CHAR(13) + CHAR(10), - @OurSessionID = @@SPID, - @OutputType = UPPER(@OutputType); +SET @LineFeed = CHAR(13) + CHAR(10); +SELECT @SQLServerProductVersion = CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)); +SELECT @SQLServerEdition =CAST(SERVERPROPERTY('EngineEdition') AS INT); /* We default to online index creates where EngineEdition=3*/ +SET @FilterMB=250; +SELECT @ScriptVersionName = 'sp_BlitzIndex(TM) v' + @Version + ' - ' + DATENAME(MM, @VersionDate) + ' ' + RIGHT('0'+DATENAME(DD, @VersionDate),2) + ', ' + DATENAME(YY, @VersionDate); +SET @IgnoreDatabases = REPLACE(REPLACE(LTRIM(RTRIM(@IgnoreDatabases)), CHAR(10), ''), CHAR(13), ''); -IF(@OutputType = 'NONE' AND (@OutputTableName IS NULL OR @OutputSchemaName IS NULL OR @OutputDatabaseName IS NULL)) +RAISERROR(N'Starting run. %s', 0,1, @ScriptVersionName) WITH NOWAIT; + +IF(@OutputType NOT IN ('TABLE','NONE')) BEGIN - RAISERROR('This procedure should be called with a value for all @Output* parameters, as @OutputType is set to NONE',12,1); + RAISERROR('Invalid value for parameter @OutputType. Expected: (TABLE;NONE)',12,1); RETURN; END; - -IF UPPER(@OutputType) LIKE 'TOP 10%' SET @OutputType = 'Top10'; -IF @OutputType = 'Top10' SET @SinceStartup = 1; - -/* Logged Message - CheckID 38 */ -IF @LogMessage IS NOT NULL + +IF(@OutputType = 'NONE') +BEGIN + IF(@OutputTableName IS NULL OR @OutputSchemaName IS NULL OR @OutputDatabaseName IS NULL) BEGIN - - RAISERROR('Saving LogMessage to table',10,1) WITH NOWAIT; - - /* Try to set the output table parameters if they don't exist */ - IF @OutputSchemaName IS NULL AND @OutputTableName IS NULL AND @OutputDatabaseName IS NULL - BEGIN - SET @OutputSchemaName = N'[dbo]'; - SET @OutputTableName = N'[BlitzFirst]'; - - /* Look for the table in the current database */ - SELECT TOP 1 @OutputDatabaseName = QUOTENAME(TABLE_CATALOG) - FROM INFORMATION_SCHEMA.TABLES - WHERE TABLE_SCHEMA = 'dbo' AND TABLE_NAME = 'BlitzFirst'; - - IF @OutputDatabaseName IS NULL AND EXISTS (SELECT * FROM sys.databases WHERE name = 'DBAtools') - SET @OutputDatabaseName = '[DBAtools]'; - - END; - - IF @OutputDatabaseName IS NULL OR @OutputSchemaName IS NULL OR @OutputTableName IS NULL - OR NOT EXISTS ( SELECT * - FROM sys.databases - WHERE QUOTENAME([name]) = @OutputDatabaseName) - BEGIN - RAISERROR('We have a hard time logging a message without a valid @OutputDatabaseName, @OutputSchemaName, and @OutputTableName to log it to.', 0, 1) WITH NOWAIT; - RETURN; - END; - IF @LogMessageCheckDate IS NULL - SET @LogMessageCheckDate = SYSDATETIMEOFFSET(); - SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' - + @OutputSchemaName + ''') INSERT ' - + @OutputDatabaseName + '.' - + @OutputSchemaName + '.' - + @OutputTableName - + ' (ServerName, CheckDate, CheckID, Priority, FindingsGroup, Finding, Details, URL) VALUES( ' - + ' @SrvName, @LogMessageCheckDate, @LogMessageCheckID, @LogMessagePriority, @LogMessageFindingsGroup, @LogMessageFinding, @LogMessage, @LogMessageURL)'; - - EXECUTE sp_executesql @StringToExecute, - N'@SrvName NVARCHAR(128), @LogMessageCheckID INT, @LogMessagePriority TINYINT, @LogMessageFindingsGroup VARCHAR(50), @LogMessageFinding VARCHAR(200), @LogMessage NVARCHAR(4000), @LogMessageCheckDate DATETIMEOFFSET, @LogMessageURL VARCHAR(200)', - @LocalServerName, @LogMessageCheckID, @LogMessagePriority, @LogMessageFindingsGroup, @LogMessageFinding, @LogMessage, @LogMessageCheckDate, @LogMessageURL; - - RAISERROR('LogMessage saved to table. We have made a note of your activity. Keep up the good work.',10,1) WITH NOWAIT; - - RETURN; + RAISERROR('This procedure should be called with a value for @Output* parameters, as @OutputType is set to NONE',12,1); + RETURN; END; - -IF @SinceStartup = 1 - SELECT @Seconds = 0, @ExpertMode = 1; - - -IF @OutputType = 'SCHEMA' -BEGIN - SELECT FieldList = '[Priority] TINYINT, [FindingsGroup] VARCHAR(50), [Finding] VARCHAR(200), [URL] VARCHAR(200), [Details] NVARCHAR(4000), [HowToStopIt] NVARCHAR(MAX), [QueryPlan] XML, [QueryText] NVARCHAR(MAX)'; - + IF(@BringThePain = 1) + BEGIN + RAISERROR('Incompatible Parameters: @BringThePain set to 1 and @OutputType set to NONE',12,1); + RETURN; + END; + /* Eventually limit by mode + IF(@Mode not in (0,4)) + BEGIN + RAISERROR('Incompatible Parameters: @Mode set to %d and @OutputType set to NONE',12,1,@Mode); + RETURN; + END; + */ END; -ELSE IF @AsOf IS NOT NULL AND @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @OutputTableName IS NOT NULL -BEGIN - /* They want to look into the past. */ - SET @AsOf1= DATEADD(mi, -15, @AsOf); - SET @AsOf2= DATEADD(mi, +15, @AsOf); - - SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' - + @OutputSchemaName + ''') SELECT CheckDate, [Priority], [FindingsGroup], [Finding], [URL], CAST([Details] AS [XML]) AS Details,' - + '[HowToStopIt], [CheckID], [StartTime], [LoginName], [NTUserName], [OriginalLoginName], [ProgramName], [HostName], [DatabaseID],' - + '[DatabaseName], [OpenTransactionCount], [QueryPlan], [QueryText] FROM ' - + @OutputDatabaseName + '.' - + @OutputSchemaName + '.' - + @OutputTableName - + ' WHERE CheckDate >= @AsOf1' - + ' AND CheckDate <= @AsOf2' - + ' /*ORDER BY CheckDate, Priority , FindingsGroup , Finding , Details*/;'; - EXEC sp_executesql @StringToExecute, - N'@AsOf1 DATETIMEOFFSET, @AsOf2 DATETIMEOFFSET', - @AsOf1, @AsOf2 - -END; /* IF @AsOf IS NOT NULL AND @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @OutputTableName IS NOT NULL */ -ELSE IF @LogMessage IS NULL /* IF @OutputType = 'SCHEMA' */ -BEGIN - /* What's running right now? This is the first and last result set. */ - IF @SinceStartup = 0 AND @Seconds > 0 AND @ExpertMode = 1 AND @OutputType <> 'NONE' - BEGIN - IF OBJECT_ID('master.dbo.sp_BlitzWho') IS NULL AND OBJECT_ID('dbo.sp_BlitzWho') IS NULL - BEGIN - PRINT N'sp_BlitzWho is not installed in the current database_files. You can get a copy from http://FirstResponderKit.org'; - END; - ELSE - BEGIN - EXEC (@BlitzWho); - END; - END; /* IF @SinceStartup = 0 AND @Seconds > 0 AND @ExpertMode = 1 AND @OutputType <> 'NONE' - What's running right now? This is the first and last result set. */ +IF OBJECT_ID('tempdb..#IndexSanity') IS NOT NULL + DROP TABLE #IndexSanity; - /* Set start/finish times AFTER sp_BlitzWho runs. For more info: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2244 */ - IF @Seconds = 0 AND SERVERPROPERTY('Edition') = 'SQL Azure' - WITH WaitTimes AS ( - SELECT wait_type, wait_time_ms, - NTILE(3) OVER(ORDER BY wait_time_ms) AS grouper - FROM sys.dm_os_wait_stats w - WHERE wait_type IN ('DIRTY_PAGE_POLL','HADR_FILESTREAM_IOMGR_IOCOMPLETION','LAZYWRITER_SLEEP', - 'LOGMGR_QUEUE','REQUEST_FOR_DEADLOCK_SEARCH','XE_TIMER_EVENT') - ) - SELECT @StartSampleTime = DATEADD(mi, AVG(-wait_time_ms / 1000 / 60), SYSDATETIMEOFFSET()), @FinishSampleTime = SYSDATETIMEOFFSET() - FROM WaitTimes - WHERE grouper = 2; - ELSE IF @Seconds = 0 AND SERVERPROPERTY('Edition') <> 'SQL Azure' - SELECT @StartSampleTime = DATEADD(MINUTE,DATEDIFF(MINUTE, GETDATE(), GETUTCDATE()),create_date) , @FinishSampleTime = SYSDATETIMEOFFSET() - FROM sys.databases - WHERE database_id = 2; - ELSE - SELECT @StartSampleTime = SYSDATETIMEOFFSET(), - @FinishSampleTime = DATEADD(ss, @Seconds, SYSDATETIMEOFFSET()), - @FinishSampleTimeWaitFor = DATEADD(ss, @Seconds, GETDATE()); +IF OBJECT_ID('tempdb..#IndexPartitionSanity') IS NOT NULL + DROP TABLE #IndexPartitionSanity; +IF OBJECT_ID('tempdb..#IndexSanitySize') IS NOT NULL + DROP TABLE #IndexSanitySize; - RAISERROR('Now starting diagnostic analysis',10,1) WITH NOWAIT; +IF OBJECT_ID('tempdb..#IndexColumns') IS NOT NULL + DROP TABLE #IndexColumns; - /* - We start by creating #BlitzFirstResults. It's a temp table that will store - the results from our checks. Throughout the rest of this stored procedure, - we're running a series of checks looking for dangerous things inside the SQL - Server. When we find a problem, we insert rows into the temp table. At the - end, we return these results to the end user. +IF OBJECT_ID('tempdb..#MissingIndexes') IS NOT NULL + DROP TABLE #MissingIndexes; - #BlitzFirstResults has a CheckID field, but there's no Check table. As we do - checks, we insert data into this table, and we manually put in the CheckID. - We (Brent Ozar Unlimited) maintain a list of the checks by ID#. You can - download that from http://FirstResponderKit.org if you want to build - a tool that relies on the output of sp_BlitzFirst. - */ +IF OBJECT_ID('tempdb..#ForeignKeys') IS NOT NULL + DROP TABLE #ForeignKeys; +IF OBJECT_ID('tempdb..#BlitzIndexResults') IS NOT NULL + DROP TABLE #BlitzIndexResults; + +IF OBJECT_ID('tempdb..#IndexCreateTsql') IS NOT NULL + DROP TABLE #IndexCreateTsql; - IF OBJECT_ID('tempdb..#BlitzFirstResults') IS NOT NULL - DROP TABLE #BlitzFirstResults; - CREATE TABLE #BlitzFirstResults - ( - ID INT IDENTITY(1, 1) PRIMARY KEY CLUSTERED, - CheckID INT NOT NULL, - Priority TINYINT NOT NULL, - FindingsGroup VARCHAR(50) NOT NULL, - Finding VARCHAR(200) NOT NULL, - URL VARCHAR(200) NULL, - Details NVARCHAR(MAX) NULL, - HowToStopIt NVARCHAR(MAX) NULL, - QueryPlan [XML] NULL, - QueryText NVARCHAR(MAX) NULL, - StartTime DATETIMEOFFSET NULL, - LoginName NVARCHAR(128) NULL, - NTUserName NVARCHAR(128) NULL, - OriginalLoginName NVARCHAR(128) NULL, - ProgramName NVARCHAR(128) NULL, - HostName NVARCHAR(128) NULL, - DatabaseID INT NULL, - DatabaseName NVARCHAR(128) NULL, - OpenTransactionCount INT NULL, - QueryStatsNowID INT NULL, - QueryStatsFirstID INT NULL, - PlanHandle VARBINARY(64) NULL, - DetailsInt INT NULL, - QueryHash BINARY(8) - ); +IF OBJECT_ID('tempdb..#DatabaseList') IS NOT NULL + DROP TABLE #DatabaseList; - IF OBJECT_ID('tempdb..#WaitStats') IS NOT NULL - DROP TABLE #WaitStats; - CREATE TABLE #WaitStats (Pass TINYINT NOT NULL, wait_type NVARCHAR(60), wait_time_ms BIGINT, signal_wait_time_ms BIGINT, waiting_tasks_count BIGINT, SampleTime DATETIMEOFFSET); +IF OBJECT_ID('tempdb..#Statistics') IS NOT NULL + DROP TABLE #Statistics; - IF OBJECT_ID('tempdb..#FileStats') IS NOT NULL - DROP TABLE #FileStats; - CREATE TABLE #FileStats ( - ID INT IDENTITY(1, 1) PRIMARY KEY CLUSTERED, - Pass TINYINT NOT NULL, - SampleTime DATETIMEOFFSET NOT NULL, - DatabaseID INT NOT NULL, - FileID INT NOT NULL, - DatabaseName NVARCHAR(256) , - FileLogicalName NVARCHAR(256) , - TypeDesc NVARCHAR(60) , - SizeOnDiskMB BIGINT , - io_stall_read_ms BIGINT , - num_of_reads BIGINT , - bytes_read BIGINT , - io_stall_write_ms BIGINT , - num_of_writes BIGINT , - bytes_written BIGINT, - PhysicalName NVARCHAR(520) , - avg_stall_read_ms INT , - avg_stall_write_ms INT - ); +IF OBJECT_ID('tempdb..#PartitionCompressionInfo') IS NOT NULL + DROP TABLE #PartitionCompressionInfo; - IF OBJECT_ID('tempdb..#QueryStats') IS NOT NULL - DROP TABLE #QueryStats; - CREATE TABLE #QueryStats ( - ID INT IDENTITY(1, 1) PRIMARY KEY CLUSTERED, - Pass INT NOT NULL, - SampleTime DATETIMEOFFSET NOT NULL, - [sql_handle] VARBINARY(64), - statement_start_offset INT, - statement_end_offset INT, - plan_generation_num BIGINT, - plan_handle VARBINARY(64), - execution_count BIGINT, - total_worker_time BIGINT, - total_physical_reads BIGINT, - total_logical_writes BIGINT, - total_logical_reads BIGINT, - total_clr_time BIGINT, - total_elapsed_time BIGINT, - creation_time DATETIMEOFFSET, - query_hash BINARY(8), - query_plan_hash BINARY(8), - Points TINYINT - ); +IF OBJECT_ID('tempdb..#ComputedColumns') IS NOT NULL + DROP TABLE #ComputedColumns; + +IF OBJECT_ID('tempdb..#TraceStatus') IS NOT NULL + DROP TABLE #TraceStatus; - IF OBJECT_ID('tempdb..#PerfmonStats') IS NOT NULL - DROP TABLE #PerfmonStats; - CREATE TABLE #PerfmonStats ( - ID INT IDENTITY(1, 1) PRIMARY KEY CLUSTERED, - Pass TINYINT NOT NULL, - SampleTime DATETIMEOFFSET NOT NULL, - [object_name] NVARCHAR(128) NOT NULL, - [counter_name] NVARCHAR(128) NOT NULL, - [instance_name] NVARCHAR(128) NULL, - [cntr_value] BIGINT NULL, - [cntr_type] INT NOT NULL, - [value_delta] BIGINT NULL, - [value_per_second] DECIMAL(18,2) NULL - ); +IF OBJECT_ID('tempdb..#TemporalTables') IS NOT NULL + DROP TABLE #TemporalTables; - IF OBJECT_ID('tempdb..#PerfmonCounters') IS NOT NULL - DROP TABLE #PerfmonCounters; - CREATE TABLE #PerfmonCounters ( - ID INT IDENTITY(1, 1) PRIMARY KEY CLUSTERED, - [object_name] NVARCHAR(128) NOT NULL, - [counter_name] NVARCHAR(128) NOT NULL, - [instance_name] NVARCHAR(128) NULL - ); +IF OBJECT_ID('tempdb..#CheckConstraints') IS NOT NULL + DROP TABLE #CheckConstraints; - IF OBJECT_ID('tempdb..#FilterPlansByDatabase') IS NOT NULL - DROP TABLE #FilterPlansByDatabase; - CREATE TABLE #FilterPlansByDatabase (DatabaseID INT PRIMARY KEY CLUSTERED); +IF OBJECT_ID('tempdb..#FilteredIndexes') IS NOT NULL + DROP TABLE #FilteredIndexes; + +IF OBJECT_ID('tempdb..#Ignore_Databases') IS NOT NULL + DROP TABLE #Ignore_Databases - IF OBJECT_ID('tempdb..##WaitCategories') IS NULL - BEGIN - /* We reuse this one by default rather than recreate it every time. */ - CREATE TABLE ##WaitCategories - ( - WaitType NVARCHAR(60) PRIMARY KEY CLUSTERED, - WaitCategory NVARCHAR(128) NOT NULL, - Ignorable BIT DEFAULT 0 - ); - END; /* IF OBJECT_ID('tempdb..##WaitCategories') IS NULL */ + RAISERROR (N'Create temp tables.',0,1) WITH NOWAIT; + CREATE TABLE #BlitzIndexResults + ( + blitz_result_id INT IDENTITY PRIMARY KEY, + check_id INT NOT NULL, + index_sanity_id INT NULL, + Priority INT NULL, + findings_group NVARCHAR(4000) NOT NULL, + finding NVARCHAR(200) NOT NULL, + [database_name] NVARCHAR(128) NULL, + URL NVARCHAR(200) NOT NULL, + details NVARCHAR(MAX) NOT NULL, + index_definition NVARCHAR(MAX) NOT NULL, + secret_columns NVARCHAR(MAX) NULL, + index_usage_summary NVARCHAR(MAX) NULL, + index_size_summary NVARCHAR(MAX) NULL, + create_tsql NVARCHAR(MAX) NULL, + more_info NVARCHAR(MAX)NULL + ); - IF OBJECT_ID ('tempdb..#checkversion') IS NOT NULL - DROP TABLE #checkversion; - CREATE TABLE #checkversion ( - version NVARCHAR(128), - common_version AS SUBSTRING(version, 1, CHARINDEX('.', version) + 1 ), - major AS PARSENAME(CONVERT(VARCHAR(32), version), 4), - minor AS PARSENAME(CONVERT(VARCHAR(32), version), 3), - build AS PARSENAME(CONVERT(VARCHAR(32), version), 2), - revision AS PARSENAME(CONVERT(VARCHAR(32), version), 1) - ); + CREATE TABLE #IndexSanity + ( + [index_sanity_id] INT IDENTITY PRIMARY KEY CLUSTERED, + [database_id] SMALLINT NOT NULL , + [object_id] INT NOT NULL , + [index_id] INT NOT NULL , + [index_type] TINYINT NOT NULL, + [database_name] NVARCHAR(128) NOT NULL , + [schema_name] NVARCHAR(128) NOT NULL , + [object_name] NVARCHAR(128) NOT NULL , + index_name NVARCHAR(128) NULL , + key_column_names NVARCHAR(MAX) NULL , + key_column_names_with_sort_order NVARCHAR(MAX) NULL , + key_column_names_with_sort_order_no_types NVARCHAR(MAX) NULL , + count_key_columns INT NULL , + include_column_names NVARCHAR(MAX) NULL , + include_column_names_no_types NVARCHAR(MAX) NULL , + count_included_columns INT NULL , + partition_key_column_name NVARCHAR(MAX) NULL, + filter_definition NVARCHAR(MAX) NOT NULL , + is_indexed_view BIT NOT NULL , + is_unique BIT NOT NULL , + is_primary_key BIT NOT NULL , + is_XML BIT NOT NULL, + is_spatial BIT NOT NULL, + is_NC_columnstore BIT NOT NULL, + is_CX_columnstore BIT NOT NULL, + is_in_memory_oltp BIT NOT NULL , + is_disabled BIT NOT NULL , + is_hypothetical BIT NOT NULL , + is_padded BIT NOT NULL , + fill_factor SMALLINT NOT NULL , + user_seeks BIGINT NOT NULL , + user_scans BIGINT NOT NULL , + user_lookups BIGINT NOT NULL , + user_updates BIGINT NULL , + last_user_seek DATETIME NULL , + last_user_scan DATETIME NULL , + last_user_lookup DATETIME NULL , + last_user_update DATETIME NULL , + is_referenced_by_foreign_key BIT DEFAULT(0), + secret_columns NVARCHAR(MAX) NULL, + count_secret_columns INT NULL, + create_date DATETIME NOT NULL, + modify_date DATETIME NOT NULL, + filter_columns_not_in_index NVARCHAR(MAX), + [db_schema_object_name] AS [schema_name] + N'.' + [object_name] , + [db_schema_object_indexid] AS [schema_name] + N'.' + [object_name] + + CASE WHEN [index_name] IS NOT NULL THEN N'.' + index_name + ELSE N'' + END + N' (' + CAST(index_id AS NVARCHAR(20)) + N')' , + first_key_column_name AS CASE WHEN count_key_columns > 1 + THEN LEFT(key_column_names, CHARINDEX(',', key_column_names, 0) - 1) + ELSE key_column_names + END , + index_definition AS + CASE WHEN partition_key_column_name IS NOT NULL + THEN N'[PARTITIONED BY:' + partition_key_column_name + N']' + ELSE '' + END + + CASE index_id + WHEN 0 THEN N'[HEAP] ' + WHEN 1 THEN N'[CX] ' + ELSE N'' END + CASE WHEN is_indexed_view = 1 THEN N'[VIEW] ' + ELSE N'' END + CASE WHEN is_primary_key = 1 THEN N'[PK] ' + ELSE N'' END + CASE WHEN is_XML = 1 THEN N'[XML] ' + ELSE N'' END + CASE WHEN is_spatial = 1 THEN N'[SPATIAL] ' + ELSE N'' END + CASE WHEN is_NC_columnstore = 1 THEN N'[COLUMNSTORE] ' + ELSE N'' END + CASE WHEN is_in_memory_oltp = 1 THEN N'[IN-MEMORY] ' + ELSE N'' END + CASE WHEN is_disabled = 1 THEN N'[DISABLED] ' + ELSE N'' END + CASE WHEN is_hypothetical = 1 THEN N'[HYPOTHETICAL] ' + ELSE N'' END + CASE WHEN is_unique = 1 AND is_primary_key = 0 THEN N'[UNIQUE] ' + ELSE N'' END + CASE WHEN count_key_columns > 0 THEN + N'[' + CAST(count_key_columns AS NVARCHAR(10)) + N' KEY' + + CASE WHEN count_key_columns > 1 THEN N'S' ELSE N'' END + + N'] ' + LTRIM(key_column_names_with_sort_order) + ELSE N'' END + CASE WHEN count_included_columns > 0 THEN + N' [' + CAST(count_included_columns AS NVARCHAR(10)) + N' INCLUDE' + + + CASE WHEN count_included_columns > 1 THEN N'S' ELSE N'' END + + N'] ' + include_column_names + ELSE N'' END + CASE WHEN filter_definition <> N'' THEN N' [FILTER] ' + filter_definition + ELSE N'' END , + [total_reads] AS user_seeks + user_scans + user_lookups, + [reads_per_write] AS CAST(CASE WHEN user_updates > 0 + THEN ( user_seeks + user_scans + user_lookups ) / (1.0 * user_updates) + ELSE 0 END AS MONEY) , + [index_usage_summary] AS + CASE WHEN is_spatial = 1 THEN N'Not Tracked' + WHEN is_disabled = 1 THEN N'Disabled' + ELSE N'Reads: ' + + REPLACE(CONVERT(NVARCHAR(30),CAST((user_seeks + user_scans + user_lookups) AS MONEY), 1), N'.00', N'') + + CASE WHEN user_seeks + user_scans + user_lookups > 0 THEN + N' (' + + RTRIM( + CASE WHEN user_seeks > 0 THEN REPLACE(CONVERT(NVARCHAR(30),CAST((user_seeks) AS MONEY), 1), N'.00', N'') + N' seek ' ELSE N'' END + + CASE WHEN user_scans > 0 THEN REPLACE(CONVERT(NVARCHAR(30),CAST((user_scans) AS MONEY), 1), N'.00', N'') + N' scan ' ELSE N'' END + + CASE WHEN user_lookups > 0 THEN REPLACE(CONVERT(NVARCHAR(30),CAST((user_lookups) AS MONEY), 1), N'.00', N'') + N' lookup' ELSE N'' END + ) + + N') ' + ELSE N' ' + END + + N'Writes: ' + + REPLACE(CONVERT(NVARCHAR(30),CAST(user_updates AS MONEY), 1), N'.00', N'') + END /* First "end" is about is_spatial */, + [more_info] AS + CASE WHEN is_in_memory_oltp = 1 + THEN N'EXEC dbo.sp_BlitzInMemoryOLTP @dbName=' + QUOTENAME([database_name],N'''') + + N', @tableName=' + QUOTENAME([object_name],N'''') + N';' + ELSE N'EXEC dbo.sp_BlitzIndex @DatabaseName=' + QUOTENAME([database_name],N'''') + + N', @SchemaName=' + QUOTENAME([schema_name],N'''') + N', @TableName=' + QUOTENAME([object_name],N'''') + N';' + END + ); + RAISERROR (N'Adding UQ index on #IndexSanity (database_id, object_id, index_id)',0,1) WITH NOWAIT; + IF NOT EXISTS(SELECT 1 FROM tempdb.sys.indexes WHERE name='uq_database_id_object_id_index_id') + CREATE UNIQUE INDEX uq_database_id_object_id_index_id ON #IndexSanity (database_id, object_id, index_id); - IF 527 <> (SELECT COALESCE(SUM(1),0) FROM ##WaitCategories) - BEGIN - TRUNCATE TABLE ##WaitCategories; - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('ASYNC_IO_COMPLETION','Other Disk IO',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('ASYNC_NETWORK_IO','Network IO',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BACKUPIO','Other Disk IO',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_CONNECTION_RECEIVE_TASK','Service Broker',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_DISPATCHER','Service Broker',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_ENDPOINT_STATE_MUTEX','Service Broker',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_EVENTHANDLER','Service Broker',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_FORWARDER','Service Broker',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_INIT','Service Broker',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_MASTERSTART','Service Broker',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_RECEIVE_WAITFOR','User Wait',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_REGISTERALLENDPOINTS','Service Broker',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_SERVICE','Service Broker',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_SHUTDOWN','Service Broker',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_START','Service Broker',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_TASK_SHUTDOWN','Service Broker',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_TASK_STOP','Service Broker',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_TASK_SUBMIT','Service Broker',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_TO_FLUSH','Service Broker',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_TRANSMISSION_OBJECT','Service Broker',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_TRANSMISSION_TABLE','Service Broker',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_TRANSMISSION_WORK','Service Broker',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_TRANSMITTER','Service Broker',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CHECKPOINT_QUEUE','Idle',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CHKPT','Tran Log IO',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CLR_AUTO_EVENT','SQL CLR',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CLR_CRST','SQL CLR',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CLR_JOIN','SQL CLR',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CLR_MANUAL_EVENT','SQL CLR',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CLR_MEMORY_SPY','SQL CLR',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CLR_MONITOR','SQL CLR',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CLR_RWLOCK_READER','SQL CLR',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CLR_RWLOCK_WRITER','SQL CLR',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CLR_SEMAPHORE','SQL CLR',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CLR_TASK_START','SQL CLR',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CLRHOST_STATE_ACCESS','SQL CLR',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CMEMPARTITIONED','Memory',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CMEMTHREAD','Memory',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CXPACKET','Parallelism',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CXCONSUMER','Parallelism',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DBMIRROR_DBM_EVENT','Mirroring',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DBMIRROR_DBM_MUTEX','Mirroring',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DBMIRROR_EVENTS_QUEUE','Mirroring',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DBMIRROR_SEND','Mirroring',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DBMIRROR_WORKER_QUEUE','Mirroring',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DBMIRRORING_CMD','Mirroring',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DIRTY_PAGE_POLL','Other',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DIRTY_PAGE_TABLE_LOCK','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DISPATCHER_QUEUE_SEMAPHORE','Other',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DPT_ENTRY_LOCK','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DTC','Transaction',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DTC_ABORT_REQUEST','Transaction',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DTC_RESOLVE','Transaction',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DTC_STATE','Transaction',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DTC_TMDOWN_REQUEST','Transaction',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DTC_WAITFOR_OUTCOME','Transaction',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DTCNEW_ENLIST','Transaction',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DTCNEW_PREPARE','Transaction',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DTCNEW_RECOVERY','Transaction',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DTCNEW_TM','Transaction',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DTCNEW_TRANSACTION_ENLISTMENT','Transaction',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DTCPNTSYNC','Transaction',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('EE_PMOLOCK','Memory',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('EXCHANGE','Parallelism',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('EXTERNAL_SCRIPT_NETWORK_IOF','Network IO',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FCB_REPLICA_READ','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FCB_REPLICA_WRITE','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_COMPROWSET_RWLOCK','Full Text Search',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_IFTS_RWLOCK','Full Text Search',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_IFTS_SCHEDULER_IDLE_WAIT','Idle',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_IFTSHC_MUTEX','Full Text Search',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_IFTSISM_MUTEX','Full Text Search',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_MASTER_MERGE','Full Text Search',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_MASTER_MERGE_COORDINATOR','Full Text Search',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_METADATA_MUTEX','Full Text Search',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_PROPERTYLIST_CACHE','Full Text Search',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_RESTART_CRAWL','Full Text Search',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FULLTEXT GATHERER','Full Text Search',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_AG_MUTEX','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_AR_CRITICAL_SECTION_ENTRY','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_AR_MANAGER_MUTEX','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_AR_UNLOAD_COMPLETED','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_ARCONTROLLER_NOTIFICATIONS_SUBSCRIBER_LIST','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_BACKUP_BULK_LOCK','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_BACKUP_QUEUE','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_CLUSAPI_CALL','Replication',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_COMPRESSED_CACHE_SYNC','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_CONNECTIVITY_INFO','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_DATABASE_FLOW_CONTROL','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_DATABASE_VERSIONING_STATE','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_DATABASE_WAIT_FOR_RECOVERY','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_DATABASE_WAIT_FOR_RESTART','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_DATABASE_WAIT_FOR_TRANSITION_TO_VERSIONING','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_DB_COMMAND','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_DB_OP_COMPLETION_SYNC','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_DB_OP_START_SYNC','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_DBR_SUBSCRIBER','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_DBR_SUBSCRIBER_FILTER_LIST','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_DBSEEDING','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_DBSEEDING_LIST','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_DBSTATECHANGE_SYNC','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_FABRIC_CALLBACK','Replication',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_FILESTREAM_BLOCK_FLUSH','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_FILESTREAM_FILE_CLOSE','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_FILESTREAM_FILE_REQUEST','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_FILESTREAM_IOMGR','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_FILESTREAM_IOMGR_IOCOMPLETION','Replication',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_FILESTREAM_MANAGER','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_FILESTREAM_PREPROC','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_GROUP_COMMIT','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_LOGCAPTURE_SYNC','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_LOGCAPTURE_WAIT','Replication',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_LOGPROGRESS_SYNC','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_NOTIFICATION_DEQUEUE','Replication',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_NOTIFICATION_WORKER_EXCLUSIVE_ACCESS','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_NOTIFICATION_WORKER_STARTUP_SYNC','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_NOTIFICATION_WORKER_TERMINATION_SYNC','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_PARTNER_SYNC','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_READ_ALL_NETWORKS','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_RECOVERY_WAIT_FOR_CONNECTION','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_RECOVERY_WAIT_FOR_UNDO','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_REPLICAINFO_SYNC','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_SEEDING_CANCELLATION','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_SEEDING_FILE_LIST','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_SEEDING_LIMIT_BACKUPS','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_SEEDING_SYNC_COMPLETION','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_SEEDING_TIMEOUT_TASK','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_SEEDING_WAIT_FOR_COMPLETION','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_SYNC_COMMIT','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_SYNCHRONIZING_THROTTLE','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_TDS_LISTENER_SYNC','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_TDS_LISTENER_SYNC_PROCESSING','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_THROTTLE_LOG_RATE_GOVERNOR','Log Rate Governor',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_TIMER_TASK','Replication',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_TRANSPORT_DBRLIST','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_TRANSPORT_FLOW_CONTROL','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_TRANSPORT_SESSION','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_WORK_POOL','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_WORK_QUEUE','Replication',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_XRF_STACK_ACCESS','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('INSTANCE_LOG_RATE_GOVERNOR','Log Rate Governor',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('IO_COMPLETION','Other Disk IO',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('IO_QUEUE_LIMIT','Other Disk IO',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('IO_RETRY','Other Disk IO',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LATCH_DT','Latch',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LATCH_EX','Latch',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LATCH_KP','Latch',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LATCH_NL','Latch',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LATCH_SH','Latch',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LATCH_UP','Latch',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LAZYWRITER_SLEEP','Idle',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_BU','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_BU_ABORT_BLOCKERS','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_BU_LOW_PRIORITY','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_IS','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_IS_ABORT_BLOCKERS','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_IS_LOW_PRIORITY','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_IU','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_IU_ABORT_BLOCKERS','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_IU_LOW_PRIORITY','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_IX','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_IX_ABORT_BLOCKERS','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_IX_LOW_PRIORITY','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RIn_NL','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RIn_NL_ABORT_BLOCKERS','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RIn_NL_LOW_PRIORITY','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RIn_S','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RIn_S_ABORT_BLOCKERS','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RIn_S_LOW_PRIORITY','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RIn_U','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RIn_U_ABORT_BLOCKERS','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RIn_U_LOW_PRIORITY','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RIn_X','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RIn_X_ABORT_BLOCKERS','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RIn_X_LOW_PRIORITY','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RS_S','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RS_S_ABORT_BLOCKERS','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RS_S_LOW_PRIORITY','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RS_U','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RS_U_ABORT_BLOCKERS','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RS_U_LOW_PRIORITY','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RX_S','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RX_S_ABORT_BLOCKERS','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RX_S_LOW_PRIORITY','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RX_U','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RX_U_ABORT_BLOCKERS','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RX_U_LOW_PRIORITY','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RX_X','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RX_X_ABORT_BLOCKERS','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RX_X_LOW_PRIORITY','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_S','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_S_ABORT_BLOCKERS','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_S_LOW_PRIORITY','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_SCH_M','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_SCH_M_ABORT_BLOCKERS','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_SCH_M_LOW_PRIORITY','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_SCH_S','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_SCH_S_ABORT_BLOCKERS','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_SCH_S_LOW_PRIORITY','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_SIU','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_SIU_ABORT_BLOCKERS','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_SIU_LOW_PRIORITY','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_SIX','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_SIX_ABORT_BLOCKERS','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_SIX_LOW_PRIORITY','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_U','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_U_ABORT_BLOCKERS','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_U_LOW_PRIORITY','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_UIX','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_UIX_ABORT_BLOCKERS','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_UIX_LOW_PRIORITY','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_X','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_X_ABORT_BLOCKERS','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_X_LOW_PRIORITY','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LOG_RATE_GOVERNOR','Tran Log IO',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LOGBUFFER','Tran Log IO',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LOGMGR','Tran Log IO',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LOGMGR_FLUSH','Tran Log IO',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LOGMGR_PMM_LOG','Tran Log IO',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LOGMGR_QUEUE','Idle',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LOGMGR_RESERVE_APPEND','Tran Log IO',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('MEMORY_ALLOCATION_EXT','Memory',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('MEMORY_GRANT_UPDATE','Memory',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('MSQL_XACT_MGR_MUTEX','Transaction',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('MSQL_XACT_MUTEX','Transaction',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('MSSEARCH','Full Text Search',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('NET_WAITFOR_PACKET','Network IO',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('ONDEMAND_TASK_QUEUE','Idle',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PAGEIOLATCH_DT','Buffer IO',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PAGEIOLATCH_EX','Buffer IO',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PAGEIOLATCH_KP','Buffer IO',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PAGEIOLATCH_NL','Buffer IO',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PAGEIOLATCH_SH','Buffer IO',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PAGEIOLATCH_UP','Buffer IO',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PAGELATCH_DT','Buffer Latch',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PAGELATCH_EX','Buffer Latch',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PAGELATCH_KP','Buffer Latch',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PAGELATCH_NL','Buffer Latch',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PAGELATCH_SH','Buffer Latch',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PAGELATCH_UP','Buffer Latch',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PARALLEL_REDO_DRAIN_WORKER','Replication',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PARALLEL_REDO_FLOW_CONTROL','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PARALLEL_REDO_LOG_CACHE','Replication',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PARALLEL_REDO_TRAN_LIST','Replication',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PARALLEL_REDO_TRAN_TURN','Replication',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PARALLEL_REDO_WORKER_SYNC','Replication',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PARALLEL_REDO_WORKER_WAIT_WORK','Replication',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('POOL_LOG_RATE_GOVERNOR','Log Rate Governor',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_ABR','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_CLOSEBACKUPMEDIA','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_CLOSEBACKUPTAPE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_CLOSEBACKUPVDIDEVICE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_CLUSAPI_CLUSTERRESOURCECONTROL','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_COCREATEINSTANCE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_COGETCLASSOBJECT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_CREATEACCESSOR','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_DELETEROWS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_GETCOMMANDTEXT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_GETDATA','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_GETNEXTROWS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_GETRESULT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_GETROWSBYBOOKMARK','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_LBFLUSH','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_LBLOCKREGION','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_LBREADAT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_LBSETSIZE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_LBSTAT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_LBUNLOCKREGION','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_LBWRITEAT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_QUERYINTERFACE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_RELEASE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_RELEASEACCESSOR','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_RELEASEROWS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_RELEASESESSION','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_RESTARTPOSITION','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_SEQSTRMREAD','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_SEQSTRMREADANDWRITE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_SETDATAFAILURE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_SETPARAMETERINFO','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_SETPARAMETERPROPERTIES','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_STRMLOCKREGION','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_STRMSEEKANDREAD','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_STRMSEEKANDWRITE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_STRMSETSIZE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_STRMSTAT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_STRMUNLOCKREGION','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_CONSOLEWRITE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_CREATEPARAM','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DEBUG','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DFSADDLINK','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DFSLINKEXISTCHECK','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DFSLINKHEALTHCHECK','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DFSREMOVELINK','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DFSREMOVEROOT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DFSROOTFOLDERCHECK','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DFSROOTINIT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DFSROOTSHARECHECK','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DTC_ABORT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DTC_ABORTREQUESTDONE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DTC_BEGINTRANSACTION','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DTC_COMMITREQUESTDONE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DTC_ENLIST','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DTC_PREPAREREQUESTDONE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_FILESIZEGET','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_FSAOLEDB_ABORTTRANSACTION','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_FSAOLEDB_COMMITTRANSACTION','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_FSAOLEDB_STARTTRANSACTION','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_FSRECOVER_UNCONDITIONALUNDO','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_GETRMINFO','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_HADR_LEASE_MECHANISM','Preemptive',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_HTTP_EVENT_WAIT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_HTTP_REQUEST','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_LOCKMONITOR','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_MSS_RELEASE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_ODBCOPS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OLE_UNINIT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OLEDB_ABORTORCOMMITTRAN','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OLEDB_ABORTTRAN','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OLEDB_GETDATASOURCE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OLEDB_GETLITERALINFO','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OLEDB_GETPROPERTIES','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OLEDB_GETPROPERTYINFO','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OLEDB_GETSCHEMALOCK','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OLEDB_JOINTRANSACTION','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OLEDB_RELEASE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OLEDB_SETPROPERTIES','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OLEDBOPS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_ACCEPTSECURITYCONTEXT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_ACQUIRECREDENTIALSHANDLE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_AUTHENTICATIONOPS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_AUTHORIZATIONOPS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_AUTHZGETINFORMATIONFROMCONTEXT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_AUTHZINITIALIZECONTEXTFROMSID','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_AUTHZINITIALIZERESOURCEMANAGER','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_BACKUPREAD','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_CLOSEHANDLE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_CLUSTEROPS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_COMOPS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_COMPLETEAUTHTOKEN','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_COPYFILE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_CREATEDIRECTORY','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_CREATEFILE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_CRYPTACQUIRECONTEXT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_CRYPTIMPORTKEY','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_CRYPTOPS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_DECRYPTMESSAGE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_DELETEFILE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_DELETESECURITYCONTEXT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_DEVICEIOCONTROL','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_DEVICEOPS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_DIRSVC_NETWORKOPS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_DISCONNECTNAMEDPIPE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_DOMAINSERVICESOPS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_DSGETDCNAME','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_DTCOPS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_ENCRYPTMESSAGE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_FILEOPS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_FINDFILE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_FLUSHFILEBUFFERS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_FORMATMESSAGE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_FREECREDENTIALSHANDLE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_FREELIBRARY','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_GENERICOPS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_GETADDRINFO','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_GETCOMPRESSEDFILESIZE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_GETDISKFREESPACE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_GETFILEATTRIBUTES','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_GETFILESIZE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_GETFINALFILEPATHBYHANDLE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_GETLONGPATHNAME','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_GETPROCADDRESS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_GETVOLUMENAMEFORVOLUMEMOUNTPOINT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_GETVOLUMEPATHNAME','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_INITIALIZESECURITYCONTEXT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_LIBRARYOPS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_LOADLIBRARY','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_LOGONUSER','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_LOOKUPACCOUNTSID','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_MESSAGEQUEUEOPS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_MOVEFILE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_NETGROUPGETUSERS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_NETLOCALGROUPGETMEMBERS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_NETUSERGETGROUPS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_NETUSERGETLOCALGROUPS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_NETUSERMODALSGET','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_NETVALIDATEPASSWORDPOLICY','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_NETVALIDATEPASSWORDPOLICYFREE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_OPENDIRECTORY','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_PDH_WMI_INIT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_PIPEOPS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_PROCESSOPS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_QUERYCONTEXTATTRIBUTES','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_QUERYREGISTRY','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_QUERYSECURITYCONTEXTTOKEN','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_REMOVEDIRECTORY','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_REPORTEVENT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_REVERTTOSELF','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_RSFXDEVICEOPS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_SECURITYOPS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_SERVICEOPS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_SETENDOFFILE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_SETFILEPOINTER','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_SETFILEVALIDDATA','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_SETNAMEDSECURITYINFO','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_SQLCLROPS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_SQMLAUNCH','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_VERIFYSIGNATURE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_VERIFYTRUST','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_VSSOPS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_WAITFORSINGLEOBJECT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_WINSOCKOPS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_WRITEFILE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_WRITEFILEGATHER','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_WSASETLASTERROR','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_REENLIST','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_RESIZELOG','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_ROLLFORWARDREDO','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_ROLLFORWARDUNDO','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_SB_STOPENDPOINT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_SERVER_STARTUP','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_SETRMINFO','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_SHAREDMEM_GETDATA','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_SNIOPEN','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_SOSHOST','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_SOSTESTING','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_SP_SERVER_DIAGNOSTICS','Preemptive',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_STARTRM','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_STREAMFCB_CHECKPOINT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_STREAMFCB_RECOVER','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_STRESSDRIVER','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_TESTING','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_TRANSIMPORT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_UNMARSHALPROPAGATIONTOKEN','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_VSS_CREATESNAPSHOT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_VSS_CREATEVOLUMESNAPSHOT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_XE_CALLBACKEXECUTE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_XE_CX_FILE_OPEN','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_XE_CX_HTTP_CALL','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_XE_DISPATCHER','Preemptive',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_XE_ENGINEINIT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_XE_GETTARGETSTATE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_XE_SESSIONCOMMIT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_XE_TARGETFINALIZE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_XE_TARGETINIT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_XE_TIMERRUN','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_XETESTING','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PWAIT_HADR_ACTION_COMPLETED','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PWAIT_HADR_CHANGE_NOTIFIER_TERMINATION_SYNC','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PWAIT_HADR_CLUSTER_INTEGRATION','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PWAIT_HADR_FAILOVER_COMPLETED','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PWAIT_HADR_JOIN','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PWAIT_HADR_OFFLINE_COMPLETED','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PWAIT_HADR_ONLINE_COMPLETED','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PWAIT_HADR_POST_ONLINE_COMPLETED','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PWAIT_HADR_SERVER_READY_CONNECTIONS','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PWAIT_HADR_WORKITEM_COMPLETED','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PWAIT_HADRSIM','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PWAIT_RESOURCE_SEMAPHORE_FT_PARALLEL_QUERY_SYNC','Full Text Search',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('QDS_ASYNC_QUEUE','Other',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('QDS_CLEANUP_STALE_QUERIES_TASK_MAIN_LOOP_SLEEP','Other',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('QDS_PERSIST_TASK_MAIN_LOOP_SLEEP','Other',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('QDS_SHUTDOWN_QUEUE','Other',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('QUERY_TRACEOUT','Tracing',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('REDO_THREAD_PENDING_WORK','Other',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('REPL_CACHE_ACCESS','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('REPL_HISTORYCACHE_ACCESS','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('REPL_SCHEMA_ACCESS','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('REPL_TRANFSINFO_ACCESS','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('REPL_TRANHASHTABLE_ACCESS','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('REPL_TRANTEXTINFO_ACCESS','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('REPLICA_WRITES','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('REQUEST_FOR_DEADLOCK_SEARCH','Idle',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('RESERVED_MEMORY_ALLOCATION_EXT','Memory',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('RESOURCE_SEMAPHORE','Memory',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('RESOURCE_SEMAPHORE_QUERY_COMPILE','Compilation',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_BPOOL_FLUSH','Idle',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_BUFFERPOOL_HELPLW','Idle',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_DBSTARTUP','Idle',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_DCOMSTARTUP','Idle',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_MASTERDBREADY','Idle',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_MASTERMDREADY','Idle',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_MASTERUPGRADED','Idle',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_MEMORYPOOL_ALLOCATEPAGES','Idle',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_MSDBSTARTUP','Idle',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_RETRY_VIRTUALALLOC','Idle',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_SYSTEMTASK','Idle',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_TASK','Idle',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_TEMPDBSTARTUP','Idle',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_WORKSPACE_ALLOCATEPAGE','Idle',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SOS_SCHEDULER_YIELD','CPU',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SOS_WORK_DISPATCHER','Idle',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SP_SERVER_DIAGNOSTICS_SLEEP','Other',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SQLCLR_APPDOMAIN','SQL CLR',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SQLCLR_ASSEMBLY','SQL CLR',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SQLCLR_DEADLOCK_DETECTION','SQL CLR',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SQLCLR_QUANTUM_PUNISHMENT','SQL CLR',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SQLTRACE_BUFFER_FLUSH','Idle',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SQLTRACE_FILE_BUFFER','Tracing',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SQLTRACE_FILE_READ_IO_COMPLETION','Tracing',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SQLTRACE_FILE_WRITE_IO_COMPLETION','Tracing',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SQLTRACE_INCREMENTAL_FLUSH_SLEEP','Idle',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SQLTRACE_PENDING_BUFFER_WRITERS','Tracing',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SQLTRACE_SHUTDOWN','Tracing',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SQLTRACE_WAIT_ENTRIES','Idle',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('THREADPOOL','Worker Thread',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('TRACE_EVTNOTIF','Tracing',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('TRACEWRITE','Tracing',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('TRAN_MARKLATCH_DT','Transaction',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('TRAN_MARKLATCH_EX','Transaction',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('TRAN_MARKLATCH_KP','Transaction',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('TRAN_MARKLATCH_NL','Transaction',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('TRAN_MARKLATCH_SH','Transaction',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('TRAN_MARKLATCH_UP','Transaction',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('TRANSACTION_MUTEX','Transaction',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('UCS_SESSION_REGISTRATION','Other',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('WAIT_FOR_RESULTS','User Wait',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('WAIT_XTP_OFFLINE_CKPT_NEW_LOG','Other',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('WAITFOR','User Wait',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('WRITE_COMPLETION','Other Disk IO',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('WRITELOG','Tran Log IO',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('XACT_OWN_TRANSACTION','Transaction',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('XACT_RECLAIM_SESSION','Transaction',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('XACTLOCKINFO','Transaction',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('XACTWORKSPACE_MUTEX','Transaction',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('XE_DISPATCHER_WAIT','Idle',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('XE_LIVE_TARGET_TVF','Other',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('XE_TIMER_EVENT','Idle',1); - END; /* IF SELECT SUM(1) FROM ##WaitCategories <> 527 */ + CREATE TABLE #IndexPartitionSanity + ( + [index_partition_sanity_id] INT IDENTITY, + [index_sanity_id] INT NULL , + [database_id] INT NOT NULL , + [object_id] INT NOT NULL , + [schema_name] NVARCHAR(128) NOT NULL, + [index_id] INT NOT NULL , + [partition_number] INT NOT NULL , + row_count BIGINT NOT NULL , + reserved_MB NUMERIC(29,2) NOT NULL , + reserved_LOB_MB NUMERIC(29,2) NOT NULL , + reserved_row_overflow_MB NUMERIC(29,2) NOT NULL , + reserved_dictionary_MB NUMERIC(29,2) NOT NULL , + leaf_insert_count BIGINT NULL , + leaf_delete_count BIGINT NULL , + leaf_update_count BIGINT NULL , + range_scan_count BIGINT NULL , + singleton_lookup_count BIGINT NULL , + forwarded_fetch_count BIGINT NULL , + lob_fetch_in_pages BIGINT NULL , + lob_fetch_in_bytes BIGINT NULL , + row_overflow_fetch_in_pages BIGINT NULL , + row_overflow_fetch_in_bytes BIGINT NULL , + row_lock_count BIGINT NULL , + row_lock_wait_count BIGINT NULL , + row_lock_wait_in_ms BIGINT NULL , + page_lock_count BIGINT NULL , + page_lock_wait_count BIGINT NULL , + page_lock_wait_in_ms BIGINT NULL , + index_lock_promotion_attempt_count BIGINT NULL , + index_lock_promotion_count BIGINT NULL, + data_compression_desc NVARCHAR(60) NULL, + page_latch_wait_count BIGINT NULL, + page_latch_wait_in_ms BIGINT NULL, + page_io_latch_wait_count BIGINT NULL, + page_io_latch_wait_in_ms BIGINT NULL, + lock_escalation_desc nvarchar(60) NULL + ); - - IF OBJECT_ID('tempdb..#MasterFiles') IS NOT NULL - DROP TABLE #MasterFiles; - CREATE TABLE #MasterFiles (database_id INT, file_id INT, type_desc NVARCHAR(50), name NVARCHAR(255), physical_name NVARCHAR(255), size BIGINT); - /* Azure SQL Database doesn't have sys.master_files, so we have to build our own. */ - IF ((SERVERPROPERTY('Edition')) = 'SQL Azure' - AND (OBJECT_ID('sys.master_files') IS NULL)) - SET @StringToExecute = 'INSERT INTO #MasterFiles (database_id, file_id, type_desc, name, physical_name, size) SELECT DB_ID(), file_id, type_desc, name, physical_name, size FROM sys.database_files;'; - ELSE - SET @StringToExecute = 'INSERT INTO #MasterFiles (database_id, file_id, type_desc, name, physical_name, size) SELECT database_id, file_id, type_desc, name, physical_name, size FROM sys.master_files;'; - EXEC(@StringToExecute); - - IF @FilterPlansByDatabase IS NOT NULL - BEGIN - IF UPPER(LEFT(@FilterPlansByDatabase,4)) = 'USER' - BEGIN - INSERT INTO #FilterPlansByDatabase (DatabaseID) - SELECT database_id - FROM sys.databases - WHERE [name] NOT IN ('master', 'model', 'msdb', 'tempdb'); - END; - ELSE - BEGIN - SET @FilterPlansByDatabase = @FilterPlansByDatabase + ',' - ;WITH a AS + CREATE TABLE #IndexSanitySize + ( + [index_sanity_size_id] INT IDENTITY NOT NULL , + [index_sanity_id] INT NULL , + [database_id] INT NOT NULL, + [schema_name] NVARCHAR(128) NOT NULL, + partition_count INT NOT NULL , + total_rows BIGINT NOT NULL , + total_reserved_MB NUMERIC(29,2) NOT NULL , + total_reserved_LOB_MB NUMERIC(29,2) NOT NULL , + total_reserved_row_overflow_MB NUMERIC(29,2) NOT NULL , + total_reserved_dictionary_MB NUMERIC(29,2) NOT NULL , + total_leaf_delete_count BIGINT NULL, + total_leaf_update_count BIGINT NULL, + total_range_scan_count BIGINT NULL, + total_singleton_lookup_count BIGINT NULL, + total_forwarded_fetch_count BIGINT NULL, + total_row_lock_count BIGINT NULL , + total_row_lock_wait_count BIGINT NULL , + total_row_lock_wait_in_ms BIGINT NULL , + avg_row_lock_wait_in_ms BIGINT NULL , + total_page_lock_count BIGINT NULL , + total_page_lock_wait_count BIGINT NULL , + total_page_lock_wait_in_ms BIGINT NULL , + avg_page_lock_wait_in_ms BIGINT NULL , + total_index_lock_promotion_attempt_count BIGINT NULL , + total_index_lock_promotion_count BIGINT NULL , + data_compression_desc NVARCHAR(4000) NULL, + page_latch_wait_count BIGINT NULL, + page_latch_wait_in_ms BIGINT NULL, + page_io_latch_wait_count BIGINT NULL, + page_io_latch_wait_in_ms BIGINT NULL, + lock_escalation_desc nvarchar(60) NULL, + index_size_summary AS ISNULL( + CASE WHEN partition_count > 1 + THEN N'[' + CAST(partition_count AS NVARCHAR(10)) + N' PARTITIONS] ' + ELSE N'' + END + REPLACE(CONVERT(NVARCHAR(30),CAST([total_rows] AS MONEY), 1), N'.00', N'') + N' rows; ' + + CASE WHEN total_reserved_MB > 1024 THEN + CAST(CAST(total_reserved_MB/1024. AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'GB' + ELSE + CAST(CAST(total_reserved_MB AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'MB' + END + + CASE WHEN total_reserved_LOB_MB > 1024 THEN + N'; ' + CAST(CAST(total_reserved_LOB_MB/1024. AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'GB ' + CASE WHEN total_reserved_dictionary_MB = 0 THEN N'LOB' ELSE N'Columnstore' END + WHEN total_reserved_LOB_MB > 0 THEN + N'; ' + CAST(CAST(total_reserved_LOB_MB AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'MB ' + CASE WHEN total_reserved_dictionary_MB = 0 THEN N'LOB' ELSE N'Columnstore' END + ELSE '' + END + + CASE WHEN total_reserved_row_overflow_MB > 1024 THEN + N'; ' + CAST(CAST(total_reserved_row_overflow_MB/1024. AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'GB Row Overflow' + WHEN total_reserved_row_overflow_MB > 0 THEN + N'; ' + CAST(CAST(total_reserved_row_overflow_MB AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'MB Row Overflow' + ELSE '' + END + + CASE WHEN total_reserved_dictionary_MB > 1024 THEN + N'; ' + CAST(CAST(total_reserved_dictionary_MB/1024. AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'GB Dictionaries' + WHEN total_reserved_dictionary_MB > 0 THEN + N'; ' + CAST(CAST(total_reserved_dictionary_MB AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'MB Dictionaries' + ELSE '' + END , + N'Error- NULL in computed column'), + index_op_stats AS ISNULL( ( - SELECT CAST(1 AS BIGINT) f, CHARINDEX(',', @FilterPlansByDatabase) t, 1 SEQ - UNION ALL - SELECT t + 1, CHARINDEX(',', @FilterPlansByDatabase, t + 1), SEQ + 1 - FROM a - WHERE CHARINDEX(',', @FilterPlansByDatabase, t + 1) > 0 - ) - INSERT #FilterPlansByDatabase (DatabaseID) - SELECT DISTINCT db.database_id - FROM a - INNER JOIN sys.databases db ON LTRIM(RTRIM(SUBSTRING(@FilterPlansByDatabase, a.f, a.t - a.f))) = db.name - WHERE SUBSTRING(@FilterPlansByDatabase, f, t - f) IS NOT NULL - OPTION (MAXRECURSION 0); - END; - END; + REPLACE(CONVERT(NVARCHAR(30),CAST(total_singleton_lookup_count AS MONEY), 1),N'.00',N'') + N' singleton lookups; ' + + REPLACE(CONVERT(NVARCHAR(30),CAST(total_range_scan_count AS MONEY), 1),N'.00',N'') + N' scans/seeks; ' + + REPLACE(CONVERT(NVARCHAR(30),CAST(total_leaf_delete_count AS MONEY), 1),N'.00',N'') + N' deletes; ' + + REPLACE(CONVERT(NVARCHAR(30),CAST(total_leaf_update_count AS MONEY), 1),N'.00',N'') + N' updates; ' + + CASE WHEN ISNULL(total_forwarded_fetch_count,0) >0 THEN + REPLACE(CONVERT(NVARCHAR(30),CAST(total_forwarded_fetch_count AS MONEY), 1),N'.00',N'') + N' forward records fetched; ' + ELSE N'' END - IF OBJECT_ID('tempdb..#ReadableDBs') IS NOT NULL - DROP TABLE #ReadableDBs; - CREATE TABLE #ReadableDBs ( - database_id INT - ); + /* rows will only be in this dmv when data is in memory for the table */ + ), N'Table metadata not in memory'), + index_lock_wait_summary AS ISNULL( + CASE WHEN total_row_lock_wait_count = 0 AND total_page_lock_wait_count = 0 AND + total_index_lock_promotion_attempt_count = 0 THEN N'0 lock waits; ' + + CASE WHEN lock_escalation_desc = N'DISABLE' THEN N'Lock escalation DISABLE.' + ELSE N'' + END + ELSE + CASE WHEN total_row_lock_wait_count > 0 THEN + N'Row lock waits: ' + REPLACE(CONVERT(NVARCHAR(30),CAST(total_row_lock_wait_count AS MONEY), 1), N'.00', N'') + + N'; total duration: ' + + CASE WHEN total_row_lock_wait_in_ms >= 60000 THEN /*More than 1 min*/ + REPLACE(CONVERT(NVARCHAR(30),CAST((total_row_lock_wait_in_ms/60000) AS MONEY), 1), N'.00', N'') + N' minutes; ' + ELSE + REPLACE(CONVERT(NVARCHAR(30),CAST(ISNULL(total_row_lock_wait_in_ms/1000,0) AS MONEY), 1), N'.00', N'') + N' seconds; ' + END + + N'avg duration: ' + + CASE WHEN avg_row_lock_wait_in_ms >= 60000 THEN /*More than 1 min*/ + REPLACE(CONVERT(NVARCHAR(30),CAST((avg_row_lock_wait_in_ms/60000) AS MONEY), 1), N'.00', N'') + N' minutes; ' + ELSE + REPLACE(CONVERT(NVARCHAR(30),CAST(ISNULL(avg_row_lock_wait_in_ms/1000,0) AS MONEY), 1), N'.00', N'') + N' seconds; ' + END + ELSE N'' + END + + CASE WHEN total_page_lock_wait_count > 0 THEN + N'Page lock waits: ' + REPLACE(CONVERT(NVARCHAR(30),CAST(total_page_lock_wait_count AS MONEY), 1), N'.00', N'') + + N'; total duration: ' + + CASE WHEN total_page_lock_wait_in_ms >= 60000 THEN /*More than 1 min*/ + REPLACE(CONVERT(NVARCHAR(30),CAST((total_page_lock_wait_in_ms/60000) AS MONEY), 1), N'.00', N'') + N' minutes; ' + ELSE + REPLACE(CONVERT(NVARCHAR(30),CAST(ISNULL(total_page_lock_wait_in_ms/1000,0) AS MONEY), 1), N'.00', N'') + N' seconds; ' + END + + N'avg duration: ' + + CASE WHEN avg_page_lock_wait_in_ms >= 60000 THEN /*More than 1 min*/ + REPLACE(CONVERT(NVARCHAR(30),CAST((avg_page_lock_wait_in_ms/60000) AS MONEY), 1), N'.00', N'') + N' minutes; ' + ELSE + REPLACE(CONVERT(NVARCHAR(30),CAST(ISNULL(avg_page_lock_wait_in_ms/1000,0) AS MONEY), 1), N'.00', N'') + N' seconds; ' + END + ELSE N'' + END + + CASE WHEN total_index_lock_promotion_attempt_count > 0 THEN + N'Lock escalation attempts: ' + REPLACE(CONVERT(NVARCHAR(30),CAST(total_index_lock_promotion_attempt_count AS MONEY), 1), N'.00', N'') + + N'; Actual Escalations: ' + REPLACE(CONVERT(NVARCHAR(30),CAST(ISNULL(total_index_lock_promotion_count,0) AS MONEY), 1), N'.00', N'') +N'; ' + ELSE N'' + END + + CASE WHEN lock_escalation_desc = N'DISABLE' THEN + N'Lock escalation is disabled.' + ELSE N'' + END + END + ,'Error- NULL in computed column') + ); - IF EXISTS (SELECT * FROM sys.all_objects o WHERE o.name = 'dm_hadr_database_replica_states') - BEGIN - RAISERROR('Checking for Read intent databases to exclude',0,0) WITH NOWAIT; + CREATE TABLE #IndexColumns + ( + [database_id] INT NOT NULL, + [schema_name] NVARCHAR(128), + [object_id] INT NOT NULL , + [index_id] INT NOT NULL , + [key_ordinal] INT NULL , + is_included_column BIT NULL , + is_descending_key BIT NULL , + [partition_ordinal] INT NULL , + column_name NVARCHAR(256) NOT NULL , + system_type_name NVARCHAR(256) NOT NULL, + max_length SMALLINT NOT NULL, + [precision] TINYINT NOT NULL, + [scale] TINYINT NOT NULL, + collation_name NVARCHAR(256) NULL, + is_nullable BIT NULL, + is_identity BIT NULL, + is_computed BIT NULL, + is_replicated BIT NULL, + is_sparse BIT NULL, + is_filestream BIT NULL, + seed_value DECIMAL(38,0) NULL, + increment_value DECIMAL(38,0) NULL , + last_value DECIMAL(38,0) NULL, + is_not_for_replication BIT NULL + ); + CREATE CLUSTERED INDEX CLIX_database_id_object_id_index_id ON #IndexColumns + (database_id, object_id, index_id); - SET @StringToExecute = 'INSERT INTO #ReadableDBs (database_id) SELECT DBs.database_id FROM sys.databases DBs INNER JOIN sys.availability_replicas Replicas ON DBs.replica_id = Replicas.replica_id WHERE replica_server_name NOT IN (SELECT DISTINCT primary_replica FROM sys.dm_hadr_availability_group_states States) AND Replicas.secondary_role_allow_connections_desc = ''READ_ONLY'' AND replica_server_name = @@SERVERNAME;'; - EXEC(@StringToExecute); - - END + CREATE TABLE #MissingIndexes + ([database_id] INT NOT NULL, + [object_id] INT NOT NULL, + [database_name] NVARCHAR(128) NOT NULL , + [schema_name] NVARCHAR(128) NOT NULL , + [table_name] NVARCHAR(128), + [statement] NVARCHAR(512) NOT NULL, + magic_benefit_number AS (( user_seeks + user_scans ) * avg_total_user_cost * avg_user_impact), + avg_total_user_cost NUMERIC(29,4) NOT NULL, + avg_user_impact NUMERIC(29,1) NOT NULL, + user_seeks BIGINT NOT NULL, + user_scans BIGINT NOT NULL, + unique_compiles BIGINT NULL, + equality_columns NVARCHAR(MAX), + equality_columns_with_data_type NVARCHAR(MAX), + inequality_columns NVARCHAR(MAX), + inequality_columns_with_data_type NVARCHAR(MAX), + included_columns NVARCHAR(MAX), + included_columns_with_data_type NVARCHAR(MAX), + is_low BIT, + [index_estimated_impact] AS + REPLACE(CONVERT(NVARCHAR(256),CAST(CAST( + (user_seeks + user_scans) + AS BIGINT) AS MONEY), 1), '.00', '') + N' use' + + CASE WHEN (user_seeks + user_scans) > 1 THEN N's' ELSE N'' END + +N'; Impact: ' + CAST(avg_user_impact AS NVARCHAR(30)) + + N'%; Avg query cost: ' + + CAST(avg_total_user_cost AS NVARCHAR(30)), + [missing_index_details] AS + CASE WHEN COALESCE(equality_columns_with_data_type,equality_columns) IS NOT NULL + THEN N'EQUALITY: ' + COALESCE(CAST(equality_columns_with_data_type AS NVARCHAR(MAX)), CAST(equality_columns AS NVARCHAR(MAX))) + N' ' + ELSE N'' END + - DECLARE @v DECIMAL(6,2), - @build INT, - @memGrantSortSupported BIT = 1; + CASE WHEN COALESCE(inequality_columns_with_data_type,inequality_columns) IS NOT NULL + THEN N'INEQUALITY: ' + COALESCE(CAST(inequality_columns_with_data_type AS NVARCHAR(MAX)), CAST(inequality_columns AS NVARCHAR(MAX))) + N' ' + ELSE N'' END + - RAISERROR (N'Determining SQL Server version.',0,1) WITH NOWAIT; + CASE WHEN COALESCE(included_columns_with_data_type,included_columns) IS NOT NULL + THEN N'INCLUDE: ' + COALESCE(CAST(included_columns_with_data_type AS NVARCHAR(MAX)), CAST(included_columns AS NVARCHAR(MAX))) + N' ' + ELSE N'' END, + [create_tsql] AS N'CREATE INDEX [' + + LEFT(REPLACE(REPLACE(REPLACE(REPLACE( + ISNULL(equality_columns,N'')+ + CASE WHEN equality_columns IS NOT NULL AND inequality_columns IS NOT NULL THEN N'_' ELSE N'' END + + ISNULL(inequality_columns,''),',','') + ,'[',''),']',''),' ','_') + + CASE WHEN included_columns IS NOT NULL THEN N'_Includes' ELSE N'' END, 128) + N'] ON ' + + [statement] + N' (' + ISNULL(equality_columns,N'') + + CASE WHEN equality_columns IS NOT NULL AND inequality_columns IS NOT NULL THEN N', ' ELSE N'' END + + CASE WHEN inequality_columns IS NOT NULL THEN inequality_columns ELSE N'' END + + ') ' + CASE WHEN included_columns IS NOT NULL THEN N' INCLUDE (' + included_columns + N')' ELSE N'' END + + N' WITH (' + + N'FILLFACTOR=100, ONLINE=?, SORT_IN_TEMPDB=?, DATA_COMPRESSION=?' + + N')' + + N';' + , + [more_info] AS N'EXEC dbo.sp_BlitzIndex @DatabaseName=' + QUOTENAME([database_name],'''') + + N', @SchemaName=' + QUOTENAME([schema_name],'''') + N', @TableName=' + QUOTENAME([table_name],'''') + N';', + [sample_query_plan] XML NULL + ); - INSERT INTO #checkversion (version) - SELECT CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)) - OPTION (RECOMPILE); + CREATE TABLE #ForeignKeys ( + [database_id] INT NOT NULL, + [database_name] NVARCHAR(128) NOT NULL , + [schema_name] NVARCHAR(128) NOT NULL , + foreign_key_name NVARCHAR(256), + parent_object_id INT, + parent_object_name NVARCHAR(256), + referenced_object_id INT, + referenced_object_name NVARCHAR(256), + is_disabled BIT, + is_not_trusted BIT, + is_not_for_replication BIT, + parent_fk_columns NVARCHAR(MAX), + referenced_fk_columns NVARCHAR(MAX), + update_referential_action_desc NVARCHAR(16), + delete_referential_action_desc NVARCHAR(60) + ); + + CREATE TABLE #IndexCreateTsql ( + index_sanity_id INT NOT NULL, + create_tsql NVARCHAR(MAX) NOT NULL + ); + CREATE TABLE #DatabaseList ( + DatabaseName NVARCHAR(256), + secondary_role_allow_connections_desc NVARCHAR(50) - SELECT @v = common_version , - @build = build - FROM #checkversion - OPTION (RECOMPILE); + ); - IF (@v < 11) - OR (@v = 11 AND @build < 6020) - OR (@v = 12 AND @build < 5000) - OR (@v = 13 AND @build < 1601) - SET @memGrantSortSupported = 0; + CREATE TABLE #PartitionCompressionInfo ( + [index_sanity_id] INT NULL, + [partition_compression_detail] NVARCHAR(4000) NULL + ); - IF EXISTS (SELECT * FROM sys.all_objects WHERE name = 'dm_exec_query_statistics_xml') - AND ((@v = 13 AND @build >= 5337) /* This DMF causes assertion errors: https://support.microsoft.com/en-us/help/4490136/fix-assertion-error-occurs-when-you-use-sys-dm-exec-query-statistics-x */ - OR (@v = 14 AND @build >= 3162) - OR (@v >= 15) - OR (@v <= 12)) /* Azure */ - SET @dm_exec_query_statistics_xml = 1; + CREATE TABLE #Statistics ( + database_id INT NOT NULL, + database_name NVARCHAR(256) NOT NULL, + table_name NVARCHAR(128) NULL, + schema_name NVARCHAR(128) NULL, + index_name NVARCHAR(128) NULL, + column_names NVARCHAR(MAX) NULL, + statistics_name NVARCHAR(128) NULL, + last_statistics_update DATETIME NULL, + days_since_last_stats_update INT NULL, + rows BIGINT NULL, + rows_sampled BIGINT NULL, + percent_sampled DECIMAL(18, 1) NULL, + histogram_steps INT NULL, + modification_counter BIGINT NULL, + percent_modifications DECIMAL(18, 1) NULL, + modifications_before_auto_update INT 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 + ); + CREATE TABLE #ComputedColumns + ( + index_sanity_id INT IDENTITY(1, 1) NOT NULL, + database_name NVARCHAR(128) NULL, + database_id INT NOT NULL, + table_name NVARCHAR(128) NOT NULL, + schema_name NVARCHAR(128) NOT NULL, + column_name NVARCHAR(128) NULL, + is_nullable BIT NULL, + definition NVARCHAR(MAX) NULL, + uses_database_collation BIT NOT NULL, + is_persisted BIT NOT NULL, + is_computed BIT NOT NULL, + is_function INT NOT NULL, + column_definition NVARCHAR(MAX) NULL + ); + + CREATE TABLE #TraceStatus + ( + TraceFlag NVARCHAR(10) , + status BIT , + Global BIT , + Session BIT + ); - SET @StockWarningHeader = '', - @StockDetailsHeader = @StockDetailsHeader + ''; + CREATE TABLE #CheckConstraints + ( + index_sanity_id INT IDENTITY(1, 1) NOT NULL, + database_name NVARCHAR(128) NULL, + database_id INT NOT NULL, + table_name NVARCHAR(128) NOT NULL, + schema_name NVARCHAR(128) NOT NULL, + constraint_name NVARCHAR(128) NULL, + is_disabled BIT NULL, + definition NVARCHAR(MAX) NULL, + uses_database_collation BIT NOT NULL, + is_not_trusted BIT NOT NULL, + is_function INT NOT NULL, + column_definition NVARCHAR(MAX) NULL + ); - /* Get the instance name to use as a Perfmon counter prefix. */ - IF CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) = 'SQL Azure' - SELECT TOP 1 @ServiceName = LEFT(object_name, (CHARINDEX(':', object_name) - 1)) - FROM sys.dm_os_performance_counters; - ELSE - BEGIN - SET @StringToExecute = 'INSERT INTO #PerfmonStats(object_name, Pass, SampleTime, counter_name, cntr_type) SELECT CASE WHEN @@SERVICENAME = ''MSSQLSERVER'' THEN ''SQLServer'' ELSE ''MSSQL$'' + @@SERVICENAME END, 0, SYSDATETIMEOFFSET(), ''stuffing'', 0 ;'; - EXEC(@StringToExecute); - SELECT @ServiceName = object_name FROM #PerfmonStats; - DELETE #PerfmonStats; - END; + CREATE TABLE #FilteredIndexes + ( + index_sanity_id INT IDENTITY(1, 1) NOT NULL, + database_name NVARCHAR(128) NULL, + database_id INT NOT NULL, + schema_name NVARCHAR(128) NOT NULL, + table_name NVARCHAR(128) NOT NULL, + index_name NVARCHAR(128) NULL, + column_name NVARCHAR(128) NULL + ); - /* Build a list of queries that were run in the last 10 seconds. - We're looking for the death-by-a-thousand-small-cuts scenario - where a query is constantly running, and it doesn't have that - big of an impact individually, but it has a ton of impact - overall. We're going to build this list, and then after we - finish our @Seconds sample, we'll compare our plan cache to - this list to see what ran the most. */ + CREATE TABLE #Ignore_Databases + ( + DatabaseName NVARCHAR(128), + Reason NVARCHAR(100) + ); - /* Populate #QueryStats. SQL 2005 doesn't have query hash or query plan hash. */ - IF @CheckProcedureCache = 1 - BEGIN - RAISERROR('@CheckProcedureCache = 1, capturing first pass of plan cache',10,1) WITH NOWAIT; - IF @@VERSION LIKE 'Microsoft SQL Server 2005%' - BEGIN - IF @FilterPlansByDatabase IS NULL - BEGIN - SET @StringToExecute = N'INSERT INTO #QueryStats ([sql_handle], Pass, SampleTime, statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, Points) - SELECT [sql_handle], 1 AS Pass, SYSDATETIMEOFFSET(), statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, NULL AS query_hash, NULL AS query_plan_hash, 0 - FROM sys.dm_exec_query_stats qs - WHERE qs.last_execution_time >= (DATEADD(ss, -10, SYSDATETIMEOFFSET()));'; - END; - ELSE - BEGIN - SET @StringToExecute = N'INSERT INTO #QueryStats ([sql_handle], Pass, SampleTime, statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, Points) - SELECT [sql_handle], 1 AS Pass, SYSDATETIMEOFFSET(), statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, NULL AS query_hash, NULL AS query_plan_hash, 0 - FROM sys.dm_exec_query_stats qs - CROSS APPLY sys.dm_exec_plan_attributes(qs.plan_handle) AS attr - INNER JOIN #FilterPlansByDatabase dbs ON CAST(attr.value AS INT) = dbs.DatabaseID - WHERE qs.last_execution_time >= (DATEADD(ss, -10, SYSDATETIMEOFFSET())) - AND attr.attribute = ''dbid'';'; - END; - END; - ELSE - BEGIN - IF @FilterPlansByDatabase IS NULL - BEGIN - SET @StringToExecute = N'INSERT INTO #QueryStats ([sql_handle], Pass, SampleTime, statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, Points) - SELECT [sql_handle], 1 AS Pass, SYSDATETIMEOFFSET(), statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, 0 - FROM sys.dm_exec_query_stats qs - WHERE qs.last_execution_time >= (DATEADD(ss, -10, SYSDATETIMEOFFSET()));'; - END; - ELSE - BEGIN - SET @StringToExecute = N'INSERT INTO #QueryStats ([sql_handle], Pass, SampleTime, statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, Points) - SELECT [sql_handle], 1 AS Pass, SYSDATETIMEOFFSET(), statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, 0 - FROM sys.dm_exec_query_stats qs - CROSS APPLY sys.dm_exec_plan_attributes(qs.plan_handle) AS attr - INNER JOIN #FilterPlansByDatabase dbs ON CAST(attr.value AS INT) = dbs.DatabaseID - WHERE qs.last_execution_time >= (DATEADD(ss, -10, SYSDATETIMEOFFSET())) - AND attr.attribute = ''dbid'';'; - END; - END; - EXEC(@StringToExecute); +/* Sanitize our inputs */ +SELECT + @OutputServerName = QUOTENAME(@OutputServerName), + @OutputDatabaseName = QUOTENAME(@OutputDatabaseName), + @OutputSchemaName = QUOTENAME(@OutputSchemaName), + @OutputTableName = QUOTENAME(@OutputTableName); + + +IF @GetAllDatabases = 1 + BEGIN + INSERT INTO #DatabaseList (DatabaseName) + SELECT DB_NAME(database_id) + FROM sys.databases + WHERE user_access_desc = 'MULTI_USER' + AND state_desc = 'ONLINE' + AND database_id > 4 + AND DB_NAME(database_id) NOT LIKE 'ReportServer%' + AND DB_NAME(database_id) NOT LIKE 'rdsadmin%' + AND is_distributor = 0 + OPTION ( RECOMPILE ); - /* Get the totals for the entire plan cache */ - INSERT INTO #QueryStats (Pass, SampleTime, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time) - SELECT -1 AS Pass, SYSDATETIMEOFFSET(), SUM(execution_count), SUM(total_worker_time), SUM(total_physical_reads), SUM(total_logical_writes), SUM(total_logical_reads), SUM(total_clr_time), SUM(total_elapsed_time), MIN(creation_time) - FROM sys.dm_exec_query_stats qs; - END; /*IF @CheckProcedureCache = 1 */ + /* Skip non-readable databases in an AG - see Github issue #1160 */ + IF EXISTS (SELECT * FROM sys.all_objects o INNER JOIN sys.all_columns c ON o.object_id = c.object_id AND o.name = 'dm_hadr_availability_replica_states' AND c.name = 'role_desc') + BEGIN + SET @dsql = N'UPDATE #DatabaseList SET secondary_role_allow_connections_desc = ''NO'' WHERE DatabaseName IN ( + SELECT d.name + FROM sys.dm_hadr_availability_replica_states rs + INNER JOIN sys.databases d ON rs.replica_id = d.replica_id + INNER JOIN sys.availability_replicas r ON rs.replica_id = r.replica_id + WHERE rs.role_desc = ''SECONDARY'' + AND r.secondary_role_allow_connections_desc = ''NO'') + OPTION ( RECOMPILE );'; + EXEC sp_executesql @dsql; + IF EXISTS (SELECT * FROM #DatabaseList WHERE secondary_role_allow_connections_desc = 'NO') + BEGIN + INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, database_name, URL, details, index_definition, + index_usage_summary, index_size_summary ) + VALUES ( 1, + 0, + N'Skipped non-readable AG secondary databases.', + N'You are running this on an AG secondary, and some of your databases are configured as non-readable when this is a secondary node.', + N'To analyze those databases, run sp_BlitzIndex on the primary, or on a readable secondary.', + 'http://FirstResponderKit.org', '', '', '', '' + ); + END; + END; - IF EXISTS (SELECT * - FROM tempdb.sys.all_objects obj - INNER JOIN tempdb.sys.all_columns col1 ON obj.object_id = col1.object_id AND col1.name = 'object_name' - INNER JOIN tempdb.sys.all_columns col2 ON obj.object_id = col2.object_id AND col2.name = 'counter_name' - INNER JOIN tempdb.sys.all_columns col3 ON obj.object_id = col3.object_id AND col3.name = 'instance_name' - WHERE obj.name LIKE '%CustomPerfmonCounters%') - BEGIN - SET @StringToExecute = 'INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) SELECT [object_name],[counter_name],[instance_name] FROM #CustomPerfmonCounters'; - EXEC(@StringToExecute); - END; - ELSE - BEGIN - /* Add our default Perfmon counters */ - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Forwarded Records/sec', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Page compression attempts/sec', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Page Splits/sec', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Skipped Ghosted Records/sec', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Table Lock Escalations/sec', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Worktables Created/sec', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Availability Group','Active Hadr Threads','_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Availability Replica','Bytes Received from Replica/sec','_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Availability Replica','Bytes Sent to Replica/sec','_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Availability Replica','Bytes Sent to Transport/sec','_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Availability Replica','Flow Control Time (ms/sec)','_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Availability Replica','Flow Control/sec','_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Availability Replica','Resent Messages/sec','_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Availability Replica','Sends to Replica/sec','_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Page life expectancy', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Page reads/sec', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Page writes/sec', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Readahead pages/sec', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Target pages', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Total pages', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Databases','', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Active Transactions','_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Database Flow Control Delay', '_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Database Flow Controls/sec', '_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Group Commit Time', '_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Group Commits/Sec', '_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Log Apply Pending Queue', '_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Log Apply Ready Queue', '_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Log Compression Cache misses/sec', '_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Log remaining for undo', '_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Log Send Queue', '_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Recovery Queue', '_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Redo blocked/sec', '_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Redo Bytes Remaining', '_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Redone Bytes/sec', '_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Databases','Log Bytes Flushed/sec', '_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Databases','Log Growths', '_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Databases','Log Pool LogWriter Pushes/sec', '_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Databases','Log Shrinks', '_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Databases','Transactions/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Databases','Write Transactions/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Databases','XTP Memory Used (KB)',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Exec Statistics','Distributed Query', 'Execs in progress'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Exec Statistics','DTC calls', 'Execs in progress'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Exec Statistics','Extended Procedures', 'Execs in progress'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Exec Statistics','OLEDB calls', 'Execs in progress'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':General Statistics','Active Temp Tables', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':General Statistics','Logins/sec', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':General Statistics','Logouts/sec', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':General Statistics','Mars Deadlocks', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':General Statistics','Processes blocked', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Locks','Number of Deadlocks/sec', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Memory Manager','Memory Grants Pending', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Errors','Errors/sec', '_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','Batch Requests/sec', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','Forced Parameterizations/sec', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','Guided plan executions/sec', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','SQL Attention rate', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','SQL Compilations/sec', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','SQL Re-Compilations/sec', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Workload Group Stats','Query optimizations/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Workload Group Stats','Suboptimal plans/sec',NULL); - /* Below counters added by Jefferson Elias */ - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Worktables From Cache Base',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Worktables From Cache Ratio',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Database pages',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Free pages',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Stolen pages',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Memory Manager','Granted Workspace Memory (KB)',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Memory Manager','Maximum Workspace Memory (KB)',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Memory Manager','Target Server Memory (KB)',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Memory Manager','Total Server Memory (KB)',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Buffer cache hit ratio',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Buffer cache hit ratio base',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Checkpoint pages/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Free list stalls/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Lazy writes/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','Auto-Param Attempts/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','Failed Auto-Params/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','Safe Auto-Params/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','Unsafe Auto-Params/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Workfiles Created/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':General Statistics','User Connections',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Latches','Average Latch Wait Time (ms)',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Latches','Average Latch Wait Time Base',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Latches','Latch Waits/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Latches','Total Latch Wait Time (ms)',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Locks','Average Wait Time (ms)',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Locks','Average Wait Time Base',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Locks','Lock Requests/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Locks','Lock Timeouts/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Locks','Lock Wait Time (ms)',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Locks','Lock Waits/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Transactions','Longest Transaction Running Time',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Full Scans/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Index Searches/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Page lookups/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Cursor Manager by Type','Active cursors',NULL); - /* Below counters are for In-Memory OLTP (Hekaton), which have a different naming convention. - And yes, they actually hard-coded the version numbers into the counters, and SQL 2019 still says 2017, oddly. - For why, see: https://connect.microsoft.com/SQLServer/feedback/details/817216/xtp-perfmon-counters-should-appear-under-sql-server-perfmon-counter-group - */ - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP Cursors','Expired rows removed/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP Cursors','Expired rows touched/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP Garbage Collection','Rows processed/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP IO Governor','Io Issued/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP Phantom Processor','Phantom expired rows touched/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP Phantom Processor','Phantom rows touched/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP Transaction Log','Log bytes written/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP Transaction Log','Log records written/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP Transactions','Transactions aborted by user/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP Transactions','Transactions aborted/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP Transactions','Transactions created/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP Cursors','Expired rows removed/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP Cursors','Expired rows touched/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP Garbage Collection','Rows processed/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP IO Governor','Io Issued/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP Phantom Processor','Phantom expired rows touched/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP Phantom Processor','Phantom rows touched/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP Transaction Log','Log bytes written/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP Transaction Log','Log records written/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP Transactions','Transactions aborted by user/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP Transactions','Transactions aborted/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP Transactions','Transactions created/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP Cursors','Expired rows removed/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP Cursors','Expired rows touched/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP Garbage Collection','Rows processed/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP IO Governor','Io Issued/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP Phantom Processor','Phantom expired rows touched/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP Phantom Processor','Phantom rows touched/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP Transaction Log','Log bytes written/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP Transaction Log','Log records written/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP Transactions','Transactions aborted by user/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP Transactions','Transactions aborted/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP Transactions','Transactions created/sec',NULL); - END; + IF @IgnoreDatabases IS NOT NULL + AND LEN(@IgnoreDatabases) > 0 + BEGIN + RAISERROR(N'Setting up filter to ignore databases', 0, 1) WITH NOWAIT; + SET @DatabaseToIgnore = ''; - /* Populate #FileStats, #PerfmonStats, #WaitStats with DMV data. - After we finish doing our checks, we'll take another sample and compare them. */ - RAISERROR('Capturing first pass of wait stats, perfmon counters, file stats',10,1) WITH NOWAIT; - INSERT #WaitStats(Pass, SampleTime, wait_type, wait_time_ms, signal_wait_time_ms, waiting_tasks_count) - SELECT - x.Pass, - x.SampleTime, - x.wait_type, - SUM(x.sum_wait_time_ms) AS sum_wait_time_ms, - SUM(x.sum_signal_wait_time_ms) AS sum_signal_wait_time_ms, - SUM(x.sum_waiting_tasks) AS sum_waiting_tasks - FROM ( - SELECT - 1 AS Pass, - CASE @Seconds WHEN 0 THEN @StartSampleTime ELSE SYSDATETIMEOFFSET() END AS SampleTime, - owt.wait_type, - CASE @Seconds WHEN 0 THEN 0 ELSE SUM(owt.wait_duration_ms) OVER (PARTITION BY owt.wait_type, owt.session_id) - - CASE WHEN @Seconds = 0 THEN 0 ELSE (@Seconds * 1000) END END AS sum_wait_time_ms, - 0 AS sum_signal_wait_time_ms, - 0 AS sum_waiting_tasks - FROM sys.dm_os_waiting_tasks owt - WHERE owt.session_id > 50 - AND owt.wait_duration_ms >= CASE @Seconds WHEN 0 THEN 0 ELSE @Seconds * 1000 END - UNION ALL - SELECT - 1 AS Pass, - CASE @Seconds WHEN 0 THEN @StartSampleTime ELSE SYSDATETIMEOFFSET() END AS SampleTime, - os.wait_type, - CASE @Seconds WHEN 0 THEN 0 ELSE SUM(os.wait_time_ms) OVER (PARTITION BY os.wait_type) END AS sum_wait_time_ms, - CASE @Seconds WHEN 0 THEN 0 ELSE SUM(os.signal_wait_time_ms) OVER (PARTITION BY os.wait_type ) END AS sum_signal_wait_time_ms, - CASE @Seconds WHEN 0 THEN 0 ELSE SUM(os.waiting_tasks_count) OVER (PARTITION BY os.wait_type) END AS sum_waiting_tasks - FROM sys.dm_os_wait_stats os - ) x - WHERE NOT EXISTS - ( - SELECT * - FROM ##WaitCategories AS wc - WHERE wc.WaitType = x.wait_type - AND wc.Ignorable = 1 - ) - GROUP BY x.Pass, x.SampleTime, x.wait_type - ORDER BY sum_wait_time_ms DESC; + WHILE LEN(@IgnoreDatabases) > 0 + BEGIN + IF PATINDEX('%,%', @IgnoreDatabases) > 0 + BEGIN + SET @DatabaseToIgnore = SUBSTRING(@IgnoreDatabases, 0, PATINDEX('%,%',@IgnoreDatabases)) ; + + INSERT INTO #Ignore_Databases (DatabaseName, Reason) + SELECT LTRIM(RTRIM(@DatabaseToIgnore)), 'Specified in the @IgnoreDatabases parameter' + OPTION (RECOMPILE) ; + + SET @IgnoreDatabases = SUBSTRING(@IgnoreDatabases, LEN(@DatabaseToIgnore + ',') + 1, LEN(@IgnoreDatabases)) ; + END; + ELSE + BEGIN + SET @DatabaseToIgnore = @IgnoreDatabases ; + SET @IgnoreDatabases = NULL ; + INSERT INTO #Ignore_Databases (DatabaseName, Reason) + SELECT LTRIM(RTRIM(@DatabaseToIgnore)), 'Specified in the @IgnoreDatabases parameter' + OPTION (RECOMPILE) ; + END; + END; + + END - INSERT INTO #FileStats (Pass, SampleTime, DatabaseID, FileID, DatabaseName, FileLogicalName, SizeOnDiskMB, io_stall_read_ms , - num_of_reads, [bytes_read] , io_stall_write_ms,num_of_writes, [bytes_written], PhysicalName, TypeDesc) - SELECT - 1 AS Pass, - CASE @Seconds WHEN 0 THEN @StartSampleTime ELSE SYSDATETIMEOFFSET() END AS SampleTime, - mf.[database_id], - mf.[file_id], - DB_NAME(vfs.database_id) AS [db_name], - mf.name + N' [' + mf.type_desc COLLATE SQL_Latin1_General_CP1_CI_AS + N']' AS file_logical_name , - CAST(( ( vfs.size_on_disk_bytes / 1024.0 ) / 1024.0 ) AS INT) AS size_on_disk_mb , - CASE @Seconds WHEN 0 THEN 0 ELSE vfs.io_stall_read_ms END , - CASE @Seconds WHEN 0 THEN 0 ELSE vfs.num_of_reads END , - CASE @Seconds WHEN 0 THEN 0 ELSE vfs.[num_of_bytes_read] END , - CASE @Seconds WHEN 0 THEN 0 ELSE vfs.io_stall_write_ms END , - CASE @Seconds WHEN 0 THEN 0 ELSE vfs.num_of_writes END , - CASE @Seconds WHEN 0 THEN 0 ELSE vfs.[num_of_bytes_written] END , - mf.physical_name, - mf.type_desc - FROM sys.dm_io_virtual_file_stats (NULL, NULL) AS vfs - INNER JOIN #MasterFiles AS mf ON vfs.file_id = mf.file_id - AND vfs.database_id = mf.database_id - WHERE vfs.num_of_reads > 0 - OR vfs.num_of_writes > 0; + END; +ELSE + BEGIN + INSERT INTO #DatabaseList + ( DatabaseName ) + SELECT CASE + WHEN @DatabaseName IS NULL OR @DatabaseName = N'' + THEN DB_NAME() + ELSE @DatabaseName END; + END; - INSERT INTO #PerfmonStats (Pass, SampleTime, [object_name],[counter_name],[instance_name],[cntr_value],[cntr_type]) - SELECT 1 AS Pass, - CASE @Seconds WHEN 0 THEN @StartSampleTime ELSE SYSDATETIMEOFFSET() END AS SampleTime, RTRIM(dmv.object_name), RTRIM(dmv.counter_name), RTRIM(dmv.instance_name), CASE @Seconds WHEN 0 THEN 0 ELSE dmv.cntr_value END, dmv.cntr_type - FROM #PerfmonCounters counters - INNER JOIN sys.dm_os_performance_counters dmv ON counters.counter_name COLLATE SQL_Latin1_General_CP1_CI_AS = RTRIM(dmv.counter_name) COLLATE SQL_Latin1_General_CP1_CI_AS - AND counters.[object_name] COLLATE SQL_Latin1_General_CP1_CI_AS = RTRIM(dmv.[object_name]) COLLATE SQL_Latin1_General_CP1_CI_AS - AND (counters.[instance_name] IS NULL OR counters.[instance_name] COLLATE SQL_Latin1_General_CP1_CI_AS = RTRIM(dmv.[instance_name]) COLLATE SQL_Latin1_General_CP1_CI_AS); +SET @NumDatabases = (SELECT COUNT(*) FROM #DatabaseList AS D LEFT OUTER JOIN #Ignore_Databases AS I ON D.DatabaseName = I.DatabaseName WHERE I.DatabaseName IS NULL); +SET @msg = N'Number of databases to examine: ' + CAST(@NumDatabases AS NVARCHAR(50)); +RAISERROR (@msg,0,1) WITH NOWAIT; - /* For Github #2743: */ - CREATE TABLE #TempdbOperationalStats (object_id BIGINT PRIMARY KEY CLUSTERED, - forwarded_fetch_count BIGINT); - INSERT INTO #TempdbOperationalStats (object_id, forwarded_fetch_count) - SELECT object_id, forwarded_fetch_count - FROM tempdb.sys.dm_db_index_operational_stats(DB_ID('tempdb'), NULL, NULL, NULL) os - WHERE os.database_id = DB_ID('tempdb') - AND os.forwarded_fetch_count > 100; - /* If they want to run sp_BlitzWho and export to table, go for it. */ - IF @OutputTableNameBlitzWho IS NOT NULL - AND @OutputDatabaseName IS NOT NULL - AND @OutputSchemaName IS NOT NULL - AND EXISTS ( SELECT * - FROM sys.databases - WHERE QUOTENAME([name]) = @OutputDatabaseName) - BEGIN - RAISERROR('Logging sp_BlitzWho to table',10,1) WITH NOWAIT; - EXEC sp_BlitzWho @OutputDatabaseName = @UnquotedOutputDatabaseName, @OutputSchemaName = @UnquotedOutputSchemaName, @OutputTableName = @OutputTableNameBlitzWho, @CheckDateOverride = @StartSampleTime; - END +/* Running on 50+ databases can take a reaaallly long time, so we want explicit permission to do so (and only after warning about it) */ - RAISERROR('Beginning investigatory queries',10,1) WITH NOWAIT; +BEGIN TRY + IF @NumDatabases >= 50 AND @BringThePain != 1 AND @TableName IS NULL + BEGIN - /* Maintenance Tasks Running - Backup Running - CheckID 1 */ - IF @Seconds > 0 - BEGIN - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 1',10,1) WITH NOWAIT; - END + INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, URL, details, index_definition, + index_usage_summary, index_size_summary ) + VALUES ( -1, + 0 , + @ScriptVersionName, + CASE WHEN @GetAllDatabases = 1 THEN N'All Databases' ELSE N'Database ' + QUOTENAME(@DatabaseName) + N' as of ' + CONVERT(NVARCHAR(16), GETDATE(), 121) END, + N'From Your Community Volunteers', + N'http://FirstResponderKit.org', + N'', + N'', + N'' + ); + INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, database_name, URL, details, index_definition, + index_usage_summary, index_size_summary ) + VALUES ( 1, + 0, + N'You''re trying to run sp_BlitzIndex on a server with ' + CAST(@NumDatabases AS NVARCHAR(8)) + N' databases. ', + N'Running sp_BlitzIndex on a server with 50+ databases may cause temporary insanity for the server and/or user.', + N'If you''re sure you want to do this, run again with the parameter @BringThePain = 1.', + 'http://FirstResponderKit.org', + '', + '', + '', + '' + ); + + if(@OutputType <> 'NONE') + BEGIN + SELECT bir.blitz_result_id, + bir.check_id, + bir.index_sanity_id, + bir.Priority, + bir.findings_group, + bir.finding, + bir.database_name, + bir.URL, + bir.details, + bir.index_definition, + bir.secret_columns, + bir.index_usage_summary, + bir.index_size_summary, + bir.create_tsql, + bir.more_info + FROM #BlitzIndexResults AS bir; + RAISERROR('Running sp_BlitzIndex on a server with 50+ databases may cause temporary insanity for the server', 12, 1); + END; - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, QueryHash) - SELECT 1 AS CheckID, - 1 AS Priority, - 'Maintenance Tasks Running' AS FindingGroup, - 'Backup Running' AS Finding, - 'http://www.BrentOzar.com/askbrent/backups/' AS URL, - 'Backup of ' + DB_NAME(db.resource_database_id) + ' database (' + (SELECT CAST(CAST(SUM(size * 8.0 / 1024 / 1024) AS BIGINT) AS NVARCHAR) FROM #MasterFiles WHERE database_id = db.resource_database_id) + 'GB) ' + @LineFeed - + CAST(r.percent_complete AS NVARCHAR(100)) + '% complete, has been running since ' + CAST(r.start_time AS NVARCHAR(100)) + '. ' + @LineFeed - + CASE WHEN COALESCE(s.nt_user_name, s.login_name) IS NOT NULL THEN (' Login: ' + COALESCE(s.nt_user_name, s.login_name) + ' ') ELSE '' END AS Details, - 'KILL ' + CAST(r.session_id AS NVARCHAR(100)) + ';' AS HowToStopIt, - pl.query_plan AS QueryPlan, - r.start_time AS StartTime, - s.login_name AS LoginName, - s.nt_user_name AS NTUserName, - s.[program_name] AS ProgramName, - s.[host_name] AS HostName, - db.[resource_database_id] AS DatabaseID, - DB_NAME(db.resource_database_id) AS DatabaseName, - 0 AS OpenTransactionCount, - r.query_hash - FROM sys.dm_exec_requests r - INNER JOIN sys.dm_exec_connections c ON r.session_id = c.session_id - INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id - INNER JOIN ( - SELECT DISTINCT request_session_id, resource_database_id - FROM sys.dm_tran_locks - WHERE resource_type = N'DATABASE' - AND request_mode = N'S' - AND request_status = N'GRANT' - AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.session_id = db.request_session_id - CROSS APPLY sys.dm_exec_query_plan(r.plan_handle) pl - WHERE r.command LIKE 'BACKUP%' - AND r.start_time <= DATEADD(minute, -5, GETDATE()) - AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs); - END + RETURN; - /* If there's a backup running, add details explaining how long full backup has been taking in the last month. */ - IF @Seconds > 0 AND CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) <> 'SQL Azure' - BEGIN - SET @StringToExecute = 'UPDATE #BlitzFirstResults SET Details = Details + '' Over the last 60 days, the full backup usually takes '' + CAST((SELECT AVG(DATEDIFF(mi, bs.backup_start_date, bs.backup_finish_date)) FROM msdb.dbo.backupset bs WHERE abr.DatabaseName = bs.database_name AND bs.type = ''D'' AND bs.backup_start_date > DATEADD(dd, -60, SYSDATETIMEOFFSET()) AND bs.backup_finish_date IS NOT NULL) AS NVARCHAR(100)) + '' minutes.'' FROM #BlitzFirstResults abr WHERE abr.CheckID = 1 AND EXISTS (SELECT * FROM msdb.dbo.backupset bs WHERE bs.type = ''D'' AND bs.backup_start_date > DATEADD(dd, -60, SYSDATETIMEOFFSET()) AND bs.backup_finish_date IS NOT NULL AND abr.DatabaseName = bs.database_name AND DATEDIFF(mi, bs.backup_start_date, bs.backup_finish_date) > 1)'; - EXEC(@StringToExecute); - END; + END; +END TRY +BEGIN CATCH + RAISERROR (N'Failure to execute due to number of databases.', 0,1) WITH NOWAIT; + SELECT @msg = ERROR_MESSAGE(), + @ErrorSeverity = ERROR_SEVERITY(), + @ErrorState = ERROR_STATE(); - /* Maintenance Tasks Running - DBCC CHECK* Running - CheckID 2 */ - IF @Seconds > 0 AND EXISTS(SELECT * FROM sys.dm_exec_requests WHERE command LIKE 'DBCC%') - BEGIN - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 2',10,1) WITH NOWAIT; - END + RAISERROR (@msg, @ErrorSeverity, @ErrorState); + + WHILE @@trancount > 0 + ROLLBACK; - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, QueryHash) - SELECT 2 AS CheckID, - 1 AS Priority, - 'Maintenance Tasks Running' AS FindingGroup, - 'DBCC CHECK* Running' AS Finding, - 'http://www.BrentOzar.com/askbrent/dbcc/' AS URL, - 'Corruption check of ' + DB_NAME(db.resource_database_id) + ' database (' + (SELECT CAST(CAST(SUM(size * 8.0 / 1024 / 1024) AS BIGINT) AS NVARCHAR) FROM #MasterFiles WHERE database_id = db.resource_database_id) + 'GB) 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, - s.login_name AS LoginName, - s.nt_user_name AS NTUserName, - s.[program_name] AS ProgramName, - s.[host_name] AS HostName, - db.[resource_database_id] AS DatabaseID, - DB_NAME(db.resource_database_id) AS DatabaseName, - 0 AS OpenTransactionCount, - r.query_hash - FROM sys.dm_exec_requests r - INNER JOIN sys.dm_exec_connections c ON r.session_id = c.session_id - INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id - INNER JOIN (SELECT DISTINCT l.request_session_id, l.resource_database_id - FROM sys.dm_tran_locks l - INNER JOIN sys.databases d ON l.resource_database_id = d.database_id - WHERE l.resource_type = N'DATABASE' - AND l.request_mode = N'S' - AND l.request_status = N'GRANT' - AND l.request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.session_id = db.request_session_id - OUTER APPLY sys.dm_exec_query_plan(r.plan_handle) pl - OUTER APPLY sys.dm_exec_sql_text(r.sql_handle) AS t - WHERE r.command LIKE 'DBCC%' - AND CAST(t.text AS NVARCHAR(4000)) NOT LIKE '%dm_db_index_physical_stats%' - AND CAST(t.text AS NVARCHAR(4000)) NOT LIKE '%ALTER INDEX%' - AND CAST(t.text AS NVARCHAR(4000)) NOT LIKE '%fileproperty%' - AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs); - END + RETURN; + END CATCH; - /* Maintenance Tasks Running - Restore Running - CheckID 3 */ - IF @Seconds > 0 - BEGIN - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 3',10,1) WITH NOWAIT; - END - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, QueryHash) - SELECT 3 AS CheckID, - 1 AS Priority, - 'Maintenance Tasks Running' AS FindingGroup, - 'Restore Running' AS Finding, - 'http://www.BrentOzar.com/askbrent/backups/' AS URL, - 'Restore of ' + DB_NAME(db.resource_database_id) + ' database (' + (SELECT CAST(CAST(SUM(size * 8.0 / 1024 / 1024) AS BIGINT) AS NVARCHAR) FROM #MasterFiles WHERE database_id = db.resource_database_id) + '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, - s.login_name AS LoginName, - s.nt_user_name AS NTUserName, - s.[program_name] AS ProgramName, - s.[host_name] AS HostName, - db.[resource_database_id] AS DatabaseID, - DB_NAME(db.resource_database_id) AS DatabaseName, - 0 AS OpenTransactionCount, - r.query_hash - FROM sys.dm_exec_requests r - INNER JOIN sys.dm_exec_connections c ON r.session_id = c.session_id - INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id - INNER JOIN ( - SELECT DISTINCT request_session_id, resource_database_id - FROM sys.dm_tran_locks - WHERE resource_type = N'DATABASE' - AND request_mode = N'S' - AND request_status = N'GRANT') AS db ON s.session_id = db.request_session_id - CROSS APPLY sys.dm_exec_query_plan(r.plan_handle) pl - WHERE r.command LIKE 'RESTORE%' - AND s.program_name <> 'SQL Server Log Shipping' - AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs); - END +RAISERROR (N'Checking partition counts to exclude databases with over 100 partitions',0,1) WITH NOWAIT; +IF @BringThePain = 0 AND @SkipPartitions = 0 AND @TableName IS NULL + BEGIN + DECLARE partition_cursor CURSOR FOR + SELECT dl.DatabaseName + FROM #DatabaseList dl + LEFT OUTER JOIN #Ignore_Databases i ON dl.DatabaseName = i.DatabaseName + WHERE COALESCE(dl.secondary_role_allow_connections_desc, 'OK') <> 'NO' + AND i.DatabaseName IS NULL - /* SQL Server Internal Maintenance - Database File Growing - CheckID 4 */ - IF @Seconds > 0 - BEGIN - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 4',10,1) WITH NOWAIT; - END + OPEN partition_cursor + FETCH NEXT FROM partition_cursor INTO @DatabaseName + + WHILE @@FETCH_STATUS = 0 + BEGIN + /* Count the total number of partitions */ + SET @dsql = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + SELECT @RowcountOUT = SUM(1) FROM ' + QUOTENAME(@DatabaseName) + '.sys.partitions WHERE partition_number > 1 OPTION ( RECOMPILE );'; + EXEC sp_executesql @dsql, N'@RowcountOUT BIGINT OUTPUT', @RowcountOUT = @Rowcount OUTPUT; + IF @Rowcount > 100 + BEGIN + RAISERROR (N'Skipping database %s because > 100 partitions were found. To check this database, you must set @BringThePain = 1.',0,1,@DatabaseName) WITH NOWAIT; + INSERT INTO #Ignore_Databases (DatabaseName, Reason) + SELECT @DatabaseName, 'Over 100 partitions found - use @BringThePain = 1 to analyze' + END; + FETCH NEXT FROM partition_cursor INTO @DatabaseName + END; + CLOSE partition_cursor + DEALLOCATE partition_cursor - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount) - SELECT 4 AS CheckID, - 1 AS Priority, - 'SQL Server Internal Maintenance' AS FindingGroup, - 'Database File Growing' AS Finding, - 'http://www.BrentOzar.com/go/instant' AS URL, - 'SQL Server is waiting for Windows to provide storage space for a database restore, a data file growth, or a log file growth. This task has been running since ' + CAST(r.start_time AS NVARCHAR(100)) + '.' + @LineFeed + 'Check the query plan (expert mode) to identify the database involved.' AS Details, - 'Unfortunately, you can''t stop this, but you can prevent it next time. Check out http://www.BrentOzar.com/go/instant for details.' AS HowToStopIt, - pl.query_plan AS QueryPlan, - r.start_time AS StartTime, - s.login_name AS LoginName, - s.nt_user_name AS NTUserName, - s.[program_name] AS ProgramName, - s.[host_name] AS HostName, - NULL AS DatabaseID, - NULL AS DatabaseName, - 0 AS OpenTransactionCount - FROM sys.dm_os_waiting_tasks t - INNER JOIN sys.dm_exec_connections c ON t.session_id = c.session_id - INNER JOIN sys.dm_exec_requests r ON t.session_id = r.session_id - INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id - CROSS APPLY sys.dm_exec_query_plan(r.plan_handle) pl - WHERE t.wait_type = 'PREEMPTIVE_OS_WRITEFILEGATHER' - AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs); - END + END; - /* Query Problems - Long-Running Query Blocking Others - CheckID 5 */ - IF SERVERPROPERTY('Edition') <> 'SQL Azure' AND @Seconds > 0 AND EXISTS(SELECT * FROM sys.dm_os_waiting_tasks WHERE wait_type LIKE 'LCK%' AND wait_duration_ms > 30000) - BEGIN - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 5',10,1) WITH NOWAIT; - END +INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, URL, details, index_definition, + index_usage_summary, index_size_summary ) +SELECT 1, 0 , + 'Database Skipped', + i.DatabaseName, + 'http://FirstResponderKit.org', + i.Reason, '', '', '' +FROM #Ignore_Databases i; - SET @StringToExecute = N'INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, QueryText, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, QueryHash) - SELECT 5 AS CheckID, - 1 AS Priority, - ''Query Problems'' AS FindingGroup, - ''Long-Running Query Blocking Others'' AS Finding, - ''http://www.BrentOzar.com/go/blocking'' AS URL, - ''Query in '' + COALESCE(DB_NAME(COALESCE((SELECT TOP 1 dbid FROM sys.dm_exec_sql_text(r.sql_handle)), - (SELECT TOP 1 t.dbid FROM master..sysprocesses spBlocker CROSS APPLY sys.dm_exec_sql_text(spBlocker.sql_handle) t WHERE spBlocker.spid = tBlocked.blocking_session_id))), ''(Unknown)'') + '' has a last request start time of '' + CAST(s.last_request_start_time AS NVARCHAR(100)) + ''. Query follows: ' - + @LineFeed + @LineFeed + - '''+ CAST(COALESCE((SELECT TOP 1 [text] FROM sys.dm_exec_sql_text(r.sql_handle)), - (SELECT TOP 1 [text] FROM master..sysprocesses spBlocker CROSS APPLY sys.dm_exec_sql_text(spBlocker.sql_handle) WHERE spBlocker.spid = tBlocked.blocking_session_id), '''') AS NVARCHAR(2000)) AS Details, - ''KILL '' + CAST(tBlocked.blocking_session_id AS NVARCHAR(100)) + '';'' AS HowToStopIt, - (SELECT TOP 1 query_plan FROM sys.dm_exec_query_plan(r.plan_handle)) AS QueryPlan, - COALESCE((SELECT TOP 1 [text] FROM sys.dm_exec_sql_text(r.sql_handle)), - (SELECT TOP 1 [text] FROM master..sysprocesses spBlocker CROSS APPLY sys.dm_exec_sql_text(spBlocker.sql_handle) WHERE spBlocker.spid = tBlocked.blocking_session_id)) AS QueryText, - r.start_time AS StartTime, - s.login_name AS LoginName, - s.nt_user_name AS NTUserName, - s.[program_name] AS ProgramName, - s.[host_name] AS HostName, - r.[database_id] AS DatabaseID, - DB_NAME(r.database_id) AS DatabaseName, - 0 AS OpenTransactionCount, - r.query_hash - FROM sys.dm_os_waiting_tasks tBlocked - INNER JOIN sys.dm_exec_sessions s ON tBlocked.blocking_session_id = s.session_id - LEFT OUTER JOIN sys.dm_exec_requests r ON s.session_id = r.session_id - INNER JOIN sys.dm_exec_connections c ON s.session_id = c.session_id - WHERE tBlocked.wait_type LIKE ''LCK%'' AND tBlocked.wait_duration_ms > 30000 - /* And the blocking session ID is not blocked by anyone else: */ - AND NOT EXISTS(SELECT * FROM sys.dm_os_waiting_tasks tBlocking WHERE s.session_id = tBlocking.session_id AND tBlocking.session_id <> tBlocking.blocking_session_id AND tBlocking.blocking_session_id IS NOT NULL) - AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs);'; - EXECUTE sp_executesql @StringToExecute; - END; - - /* Query Problems - Plan Cache Erased Recently - CheckID 7 */ - IF DATEADD(mi, -15, SYSDATETIME()) < (SELECT TOP 1 creation_time FROM sys.dm_exec_query_stats ORDER BY creation_time) - BEGIN - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 7',10,1) WITH NOWAIT; - END - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) - SELECT TOP 1 7 AS CheckID, - 50 AS Priority, - 'Query Problems' AS FindingGroup, - 'Plan Cache Erased Recently' AS Finding, - 'http://www.BrentOzar.com/askbrent/plan-cache-erased-recently/' AS URL, - 'The oldest query in the plan cache was created at ' + CAST(creation_time AS NVARCHAR(50)) + '. ' + @LineFeed + @LineFeed - + 'This indicates that someone ran DBCC FREEPROCCACHE at that time,' + @LineFeed - + 'Giving SQL Server temporary amnesia. Now, as queries come in,' + @LineFeed - + 'SQL Server has to use a lot of CPU power in order to build execution' + @LineFeed - + 'plans and put them in cache again. This causes high CPU loads.' AS Details, - 'Find who did that, and stop them from doing it again.' AS HowToStopIt - FROM sys.dm_exec_query_stats - ORDER BY creation_time; - END; +/* Last startup */ +SELECT @DaysUptime = CAST(DATEDIFF(HOUR, create_date, GETDATE()) / 24. AS NUMERIC (23,2)) +FROM sys.databases +WHERE database_id = 2; +IF @DaysUptime = 0 OR @DaysUptime IS NULL + SET @DaysUptime = .01; - /* Query Problems - Sleeping Query with Open Transactions - CheckID 8 */ - IF @Seconds > 0 - BEGIN - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 8',10,1) WITH NOWAIT; - END +SELECT @DaysUptimeInsertValue = 'Server: ' + (CONVERT(VARCHAR(256), (SERVERPROPERTY('ServerName')))) + ' Days Uptime: ' + RTRIM(@DaysUptime); - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, OpenTransactionCount) - SELECT 8 AS CheckID, - 50 AS Priority, - 'Query Problems' AS FindingGroup, - 'Sleeping Query with Open Transactions' AS Finding, - 'http://www.brentozar.com/askbrent/sleeping-query-with-open-transactions/' AS URL, - 'Database: ' + DB_NAME(db.resource_database_id) + @LineFeed + 'Host: ' + s.[host_name] + @LineFeed + 'Program: ' + s.[program_name] + @LineFeed + 'Asleep with open transactions and locks since ' + CAST(s.last_request_end_time AS NVARCHAR(100)) + '. ' AS Details, - 'KILL ' + CAST(s.session_id AS NVARCHAR(100)) + ';' AS HowToStopIt, - s.last_request_start_time AS StartTime, - s.login_name AS LoginName, - s.nt_user_name AS NTUserName, - s.[program_name] AS ProgramName, - s.[host_name] AS HostName, - db.[resource_database_id] AS DatabaseID, - DB_NAME(db.resource_database_id) AS DatabaseName, - (SELECT TOP 1 [text] FROM sys.dm_exec_sql_text(c.most_recent_sql_handle)) AS QueryText, - sessions_with_transactions.open_transaction_count AS OpenTransactionCount - FROM (SELECT session_id, SUM(open_transaction_count) AS open_transaction_count FROM sys.dm_exec_requests WHERE open_transaction_count > 0 GROUP BY session_id) AS sessions_with_transactions - INNER JOIN sys.dm_exec_sessions s ON sessions_with_transactions.session_id = s.session_id - INNER JOIN sys.dm_exec_connections c ON s.session_id = c.session_id - INNER JOIN ( - SELECT DISTINCT request_session_id, resource_database_id - FROM sys.dm_tran_locks - WHERE resource_type = N'DATABASE' - AND request_mode = N'S' - AND request_status = N'GRANT' - AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.session_id = db.request_session_id - WHERE s.status = 'sleeping' - AND s.last_request_end_time < DATEADD(ss, -10, SYSDATETIME()) - AND EXISTS(SELECT * FROM sys.dm_tran_locks WHERE request_session_id = s.session_id - AND NOT (resource_type = N'DATABASE' AND request_mode = N'S' AND request_status = N'GRANT' AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE')); - END - /*Query Problems - Clients using implicit transactions - CheckID 37 */ - IF @Seconds > 0 - AND ( @@VERSION NOT LIKE 'Microsoft SQL Server 2005%' - AND @@VERSION NOT LIKE 'Microsoft SQL Server 2008%' - AND @@VERSION NOT LIKE 'Microsoft SQL Server 2008 R2%' ) - BEGIN - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 37',10,1) WITH NOWAIT; - END +/* Permission granted or unnecessary? Ok, let's go! */ - SET @StringToExecute = N'INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, OpenTransactionCount) - SELECT 37 AS CheckId, - 50 AS Priority, - ''Query Problems'' AS FindingsGroup, - ''Implicit Transactions'', - ''https://www.brentozar.com/go/ImplicitTransactions/'' AS URL, - ''Database: '' + DB_NAME(s.database_id) + '' '' + CHAR(13) + CHAR(10) + - ''Host: '' + s.[host_name] + '' '' + CHAR(13) + CHAR(10) + - ''Program: '' + s.[program_name] + '' '' + CHAR(13) + CHAR(10) + - CONVERT(NVARCHAR(10), s.open_transaction_count) + - '' open transactions since: '' + - CONVERT(NVARCHAR(30), tat.transaction_begin_time) + ''. '' - AS Details, - ''Run sp_BlitzWho and check the is_implicit_transaction column to spot the culprits. -If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, - tat.transaction_begin_time, - s.login_name, - s.nt_user_name, - s.program_name, - s.host_name, - s.database_id, - DB_NAME(s.database_id) AS DatabaseName, - NULL AS Querytext, - s.open_transaction_count AS OpenTransactionCount - FROM sys.dm_tran_active_transactions AS tat - LEFT JOIN sys.dm_tran_session_transactions AS tst - ON tst.transaction_id = tat.transaction_id - LEFT JOIN sys.dm_exec_sessions AS s - ON s.session_id = tst.session_id - WHERE tat.name = ''implicit_transaction''; - ' - EXECUTE sp_executesql @StringToExecute; - END; +RAISERROR (N'Starting loop through databases',0,1) WITH NOWAIT; +DECLARE c1 CURSOR +LOCAL FAST_FORWARD +FOR +SELECT dl.DatabaseName +FROM #DatabaseList dl +LEFT OUTER JOIN #Ignore_Databases i ON dl.DatabaseName = i.DatabaseName +WHERE COALESCE(dl.secondary_role_allow_connections_desc, 'OK') <> 'NO' + AND i.DatabaseName IS NULL +ORDER BY dl.DatabaseName; - /* Query Problems - Query Rolling Back - CheckID 9 */ - IF @Seconds > 0 - BEGIN - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 9',10,1) WITH NOWAIT; - END +OPEN c1; +FETCH NEXT FROM c1 INTO @DatabaseName; + WHILE @@FETCH_STATUS = 0 - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, QueryHash) - SELECT 9 AS CheckID, - 1 AS Priority, - 'Query Problems' AS FindingGroup, - 'Query Rolling Back' AS Finding, - 'http://www.BrentOzar.com/askbrent/rollback/' AS URL, - 'Rollback started at ' + CAST(r.start_time AS NVARCHAR(100)) + ', is ' + CAST(r.percent_complete AS NVARCHAR(100)) + '% complete.' AS Details, - 'Unfortunately, you can''t stop this. Whatever you do, don''t restart the server in an attempt to fix it - SQL Server will keep rolling back.' AS HowToStopIt, - r.start_time AS StartTime, - s.login_name AS LoginName, - s.nt_user_name AS NTUserName, - s.[program_name] AS ProgramName, - s.[host_name] AS HostName, - db.[resource_database_id] AS DatabaseID, - DB_NAME(db.resource_database_id) AS DatabaseName, - (SELECT TOP 1 [text] FROM sys.dm_exec_sql_text(c.most_recent_sql_handle)) AS QueryText, - r.query_hash - FROM sys.dm_exec_sessions s - INNER JOIN sys.dm_exec_connections c ON s.session_id = c.session_id - INNER JOIN sys.dm_exec_requests r ON s.session_id = r.session_id - LEFT OUTER JOIN ( - SELECT DISTINCT request_session_id, resource_database_id - FROM sys.dm_tran_locks - WHERE resource_type = N'DATABASE' - AND request_mode = N'S' - AND request_status = N'GRANT' - AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.session_id = db.request_session_id - WHERE r.status = 'rollback'; - END +BEGIN + + RAISERROR (@LineFeed, 0, 1) WITH NOWAIT; + RAISERROR (@LineFeed, 0, 1) WITH NOWAIT; + RAISERROR (@DatabaseName, 0, 1) WITH NOWAIT; - /* Server Performance - Too Much Free Memory - CheckID 34 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 34',10,1) WITH NOWAIT; - END +SELECT @DatabaseID = [database_id] +FROM sys.databases + WHERE [name] = @DatabaseName + AND user_access_desc='MULTI_USER' + AND state_desc = 'ONLINE'; - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) - SELECT 34 AS CheckID, - 50 AS Priority, - 'Server Performance' AS FindingGroup, - 'Too Much Free Memory' AS Finding, - 'https://BrentOzar.com/go/freememory' AS URL, - CAST((CAST(cFree.cntr_value AS BIGINT) / 1024 / 1024 ) AS NVARCHAR(100)) + N'GB of free memory inside SQL Server''s buffer pool,' + @LineFeed + ' which is ' + CAST((CAST(cTotal.cntr_value AS BIGINT) / 1024 / 1024) AS NVARCHAR(100)) + N'GB. You would think lots of free memory would be good, but check out the URL for more information.' AS Details, - 'Run sp_BlitzCache @SortOrder = ''memory grant'' to find queries with huge memory grants and tune them.' AS HowToStopIt - FROM sys.dm_os_performance_counters cFree - INNER JOIN sys.dm_os_performance_counters cTotal ON cTotal.object_name LIKE N'%Memory Manager%' - AND cTotal.counter_name = N'Total Server Memory (KB) ' - WHERE cFree.object_name LIKE N'%Memory Manager%' - AND cFree.counter_name = N'Free Memory (KB) ' - AND CAST(cFree.cntr_value AS BIGINT) > 20480000000 - AND CAST(cTotal.cntr_value AS BIGINT) * .3 <= CAST(cFree.cntr_value AS BIGINT) - AND CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) NOT LIKE '%Standard%'; +---------------------------------------- +--STEP 1: OBSERVE THE PATIENT +--This step puts index information into temp tables. +---------------------------------------- +BEGIN TRY + BEGIN - /* Server Performance - Target Memory Lower Than Max - CheckID 35 */ - IF SERVERPROPERTY('Edition') <> 'SQL Azure' - BEGIN - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 35',10,1) WITH NOWAIT; - END + --Validate SQL Server Version - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) - SELECT 35 AS CheckID, - 10 AS Priority, - 'Server Performance' AS FindingGroup, - 'Target Memory Lower Than Max' AS Finding, - 'https://BrentOzar.com/go/target' AS URL, - N'Max server memory is ' + CAST(cMax.value_in_use AS NVARCHAR(50)) + N' MB but target server memory is only ' + CAST((CAST(cTarget.cntr_value AS BIGINT) / 1024) AS NVARCHAR(50)) + N' MB,' + @LineFeed - + N'indicating that SQL Server may be under external memory pressure or max server memory may be set too high.' AS Details, - 'Investigate what OS processes are using memory, and double-check the max server memory setting.' AS HowToStopIt - FROM sys.configurations cMax - INNER JOIN sys.dm_os_performance_counters cTarget ON cTarget.object_name LIKE N'%Memory Manager%' - AND cTarget.counter_name = N'Target Server Memory (KB) ' - WHERE cMax.name = 'max server memory (MB)' - AND CAST(cMax.value_in_use AS BIGINT) >= 1.5 * (CAST(cTarget.cntr_value AS BIGINT) / 1024) - AND CAST(cMax.value_in_use AS BIGINT) < 2147483647 /* Not set to default of unlimited */ - AND CAST(cTarget.cntr_value AS BIGINT) < .8 * (SELECT available_physical_memory_kb FROM sys.dm_os_sys_memory); /* Target memory less than 80% of physical memory (in case they set max too high) */ - END - - /* Server Info - Database Size, Total GB - CheckID 21 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 21',10,1) WITH NOWAIT; - END - - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) - SELECT 21 AS CheckID, - 251 AS Priority, - 'Server Info' AS FindingGroup, - 'Database Size, Total GB' AS Finding, - CAST(SUM (CAST(size AS BIGINT)*8./1024./1024.) AS VARCHAR(100)) AS Details, - SUM (CAST(size AS BIGINT))*8./1024./1024. AS DetailsInt, - 'http://www.BrentOzar.com/askbrent/' AS URL - FROM #MasterFiles - WHERE database_id > 4; - - /* Server Info - Database Count - CheckID 22 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 22',10,1) WITH NOWAIT; - END - - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) - SELECT 22 AS CheckID, - 251 AS Priority, - 'Server Info' AS FindingGroup, - 'Database Count' AS Finding, - CAST(SUM(1) AS VARCHAR(100)) AS Details, - SUM (1) AS DetailsInt, - 'http://www.BrentOzar.com/askbrent/' AS URL - FROM sys.databases - WHERE database_id > 4; - - /* Server Info - Memory Grants pending - CheckID 39 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 39',10,1) WITH NOWAIT; - END + IF (SELECT LEFT(@SQLServerProductVersion, + CHARINDEX('.',@SQLServerProductVersion,0)-1 + )) <= 9 + BEGIN + SET @msg=N'sp_BlitzIndex is only supported on SQL Server 2008 and higher. The version of this instance is: ' + @SQLServerProductVersion; + RAISERROR(@msg,16,1); + END; - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) - SELECT 39 AS CheckID, - 50 AS Priority, - 'Server Performance' AS FindingGroup, - 'Memory Grants Pending' AS Finding, - CAST(PendingGrants.Details AS NVARCHAR(50)) AS Details, - PendingGrants.DetailsInt, - 'https://www.brentozar.com/blitz/memory-grants/' AS URL - FROM - ( - SELECT - COUNT(1) AS Details, - COUNT(1) AS DetailsInt - FROM sys.dm_exec_query_memory_grants AS Grants - WHERE queue_id IS NOT NULL - ) AS PendingGrants - WHERE PendingGrants.Details > 0; + --Short circuit here if database name does not exist. + IF @DatabaseName IS NULL OR @DatabaseID IS NULL + BEGIN + SET @msg='Database does not exist or is not online/multi-user: cannot proceed.'; + RAISERROR(@msg,16,1); + END; - /* Server Info - Memory Grant/Workspace info - CheckID 40 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 40',10,1) WITH NOWAIT; - END + --Validate parameters. + IF (@Mode NOT IN (0,1,2,3,4)) + BEGIN + SET @msg=N'Invalid @Mode parameter. 0=diagnose, 1=summarize, 2=index detail, 3=missing index detail, 4=diagnose detail'; + RAISERROR(@msg,16,1); + END; - DECLARE @MaxWorkspace BIGINT - SET @MaxWorkspace = (SELECT CAST(cntr_value AS BIGINT)/1024 FROM #PerfmonStats WHERE counter_name = N'Maximum Workspace Memory (KB)') - - IF (@MaxWorkspace IS NULL - OR @MaxWorkspace = 0) - BEGIN - SET @MaxWorkspace = 1 - END + IF (@Mode <> 0 AND @TableName IS NOT NULL) + BEGIN + SET @msg=N'Setting the @Mode doesn''t change behavior if you supply @TableName. Use default @Mode=0 to see table detail.'; + RAISERROR(@msg,16,1); + END; - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) - SELECT 40 AS CheckID, - 251 AS Priority, - 'Server Info' AS FindingGroup, - 'Memory Grant/Workspace info' AS Finding, - + 'Grants Outstanding: ' + CAST((SELECT COUNT(*) FROM sys.dm_exec_query_memory_grants WHERE queue_id IS NULL) AS NVARCHAR(50)) + @LineFeed - + 'Total Granted(MB): ' + CAST(ISNULL(SUM(Grants.granted_memory_kb) / 1024, 0) AS NVARCHAR(50)) + @LineFeed - + 'Total WorkSpace(MB): ' + CAST(ISNULL(@MaxWorkspace, 0) AS NVARCHAR(50)) + @LineFeed - + 'Granted workspace: ' + CAST(ISNULL((CAST(SUM(Grants.granted_memory_kb) / 1024 AS MONEY) - / CAST(@MaxWorkspace AS MONEY)) * 100, 0) AS NVARCHAR(50)) + '%' + @LineFeed - + 'Oldest Grant in seconds: ' + CAST(ISNULL(DATEDIFF(SECOND, MIN(Grants.request_time), GETDATE()), 0) AS NVARCHAR(50)) AS Details, - (SELECT COUNT(*) FROM sys.dm_exec_query_memory_grants WHERE queue_id IS NULL) AS DetailsInt, - 'http://www.BrentOzar.com/askbrent/' AS URL - FROM sys.dm_exec_query_memory_grants AS Grants; + IF ((@Mode <> 0 OR @TableName IS NOT NULL) AND @Filter <> 0) + BEGIN + SET @msg=N'@Filter only applies when @Mode=0 and @TableName is not specified. Please try again.'; + RAISERROR(@msg,16,1); + END; - /* Query Problems - Queries with high memory grants - CheckID 46 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 46',10,1) WITH NOWAIT; - END + IF (@SchemaName IS NOT NULL AND @TableName IS NULL) + BEGIN + SET @msg='We can''t run against a whole schema! Specify a @TableName, or leave both NULL for diagnosis.'; + RAISERROR(@msg,16,1); + END; - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, URL, QueryText, QueryPlan) - SELECT 46 AS CheckID, - 100 AS Priority, - 'Query Problems' AS FindingGroup, - 'Query with a memory grant exceeding ' - +CAST(@MemoryGrantThresholdPct AS NVARCHAR(15)) - +'%' AS Finding, - 'Granted size: '+ CAST(CAST(Grants.granted_memory_kb / 1024 AS INT) AS NVARCHAR(50)) - +N'MB ' - + @LineFeed - +N'Granted pct of max workspace: ' - + CAST(ISNULL((CAST(Grants.granted_memory_kb / 1024 AS MONEY) - / CAST(@MaxWorkspace AS MONEY)) * 100, 0) AS NVARCHAR(50)) + '%' - + @LineFeed - +N'SQLHandle: ' - +CONVERT(NVARCHAR(128),Grants.[sql_handle],1), - 'https://www.brentozar.com/memory-grants-sql-servers-public-toilet/' AS URL, - SQLText.[text], - QueryPlan.query_plan - FROM sys.dm_exec_query_memory_grants AS Grants - OUTER APPLY sys.dm_exec_sql_text(Grants.[sql_handle]) AS SQLText - OUTER APPLY sys.dm_exec_query_plan(Grants.[plan_handle]) AS QueryPlan - WHERE Grants.granted_memory_kb > ((@MemoryGrantThresholdPct/100.00)*(@MaxWorkspace*1024)); - /* Query Problems - Memory Leak in USERSTORE_TOKENPERM Cache - CheckID 45 */ - IF EXISTS (SELECT * FROM sys.all_columns WHERE object_id = OBJECT_ID('sys.dm_os_memory_clerks') AND name = 'pages_kb') + IF (@TableName IS NOT NULL AND @SchemaName IS NULL) BEGIN - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 45',10,1) WITH NOWAIT; - END + SET @SchemaName=N'dbo'; + SET @msg='@SchemaName wasn''t specified-- assuming schema=dbo.'; + RAISERROR(@msg,1,1) WITH NOWAIT; + END; - /* SQL 2012+ version */ - SET @StringToExecute = N' - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, URL) - SELECT 45 AS CheckID, - 50 AS Priority, - ''Query Problems'' AS FindingsGroup, - ''Memory Leak in USERSTORE_TOKENPERM Cache'' AS Finding, - N''UserStore_TokenPerm clerk is using '' + CAST(CAST(SUM(CASE WHEN type = ''USERSTORE_TOKENPERM'' AND name = ''TokenAndPermUserStore'' THEN pages_kb * 1.0 ELSE 0.0 END) / 1024.0 / 1024.0 AS INT) AS NVARCHAR(100)) - + N''GB RAM, total buffer pool is '' + CAST(CAST(SUM(pages_kb) / 1024.0 / 1024.0 AS INT) AS NVARCHAR(100)) + N''GB.'' - AS details, - ''https://www.BrentOzar.com/go/userstore'' AS URL - FROM sys.dm_os_memory_clerks - HAVING SUM(CASE WHEN type = ''USERSTORE_TOKENPERM'' AND name = ''TokenAndPermUserStore'' THEN pages_kb * 1.0 ELSE 0.0 END) / SUM(pages_kb) >= 0.1 - AND SUM(pages_kb) / 1024.0 / 1024.0 >= 1; /* At least 1GB RAM overall */'; - EXEC sp_executesql @StringToExecute; - END - ELSE + --If a table is specified, grab the object id. + --Short circuit if it doesn't exist. + IF @TableName IS NOT NULL BEGIN - /* Antiques Roadshow SQL 2008R2 - version */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 45 (Legacy version)',10,1) WITH NOWAIT; - END - - SET @StringToExecute = N' - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, URL) - SELECT 45 AS CheckID, - 50 AS Priority, - ''Performance'' AS FindingsGroup, - ''Memory Leak in USERSTORE_TOKENPERM Cache'' AS Finding, - N''UserStore_TokenPerm clerk is using '' + CAST(CAST(SUM(CASE WHEN type = ''USERSTORE_TOKENPERM'' AND name = ''TokenAndPermUserStore'' THEN single_pages_kb + multi_pages_kb * 1.0 ELSE 0.0 END) / 1024.0 / 1024.0 AS INT) AS NVARCHAR(100)) - + N''GB RAM, total buffer pool is '' + CAST(CAST(SUM(single_pages_kb + multi_pages_kb) / 1024.0 / 1024.0 AS INT) AS NVARCHAR(100)) + N''GB.'' - AS details, - ''https://www.BrentOzar.com/go/userstore'' AS URL - FROM sys.dm_os_memory_clerks - HAVING SUM(CASE WHEN type = ''USERSTORE_TOKENPERM'' AND name = ''TokenAndPermUserStore'' THEN single_pages_kb + multi_pages_kb * 1.0 ELSE 0.0 END) / SUM(single_pages_kb + multi_pages_kb) >= 0.1 - AND SUM(single_pages_kb + multi_pages_kb) / 1024.0 / 1024.0 >= 1; /* At least 1GB RAM overall */'; - EXEC sp_executesql @StringToExecute; - END + SET @dsql = N' + SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + SELECT @ObjectID= OBJECT_ID + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.objects AS so + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS sc on + so.schema_id=sc.schema_id + where so.type in (''U'', ''V'') + and so.name=' + QUOTENAME(@TableName,'''')+ N' + and sc.name=' + QUOTENAME(@SchemaName,'''')+ N' + /*Has a row in sys.indexes. This lets us get indexed views.*/ + and exists ( + SELECT si.name + FROM ' + QUOTENAME(@DatabaseName) + '.sys.indexes AS si + WHERE so.object_id=si.object_id) + OPTION (RECOMPILE);'; + SET @params='@ObjectID INT OUTPUT'; + IF @dsql IS NULL + RAISERROR('@dsql is null',16,1); - IF @Seconds > 0 - BEGIN + EXEC sp_executesql @dsql, @params, @ObjectID=@ObjectID OUTPUT; + + IF @ObjectID IS NULL + BEGIN + SET @msg=N'Oh, this is awkward. I can''t find the table or indexed view you''re looking for in that database.' + CHAR(10) + + N'Please check your parameters.'; + RAISERROR(@msg,1,1); + RETURN; + END; + END; - IF EXISTS ( SELECT 1/0 - FROM sys.all_objects AS ao - WHERE ao.name = 'dm_exec_query_profiles' ) - BEGIN + --set @collation + SELECT @collation=collation_name + FROM sys.databases + WHERE database_id=@DatabaseID; - IF EXISTS( SELECT 1/0 - FROM sys.dm_exec_requests AS r - JOIN sys.dm_exec_sessions AS s - ON r.session_id = s.session_id - WHERE s.host_name IS NOT NULL - AND r.total_elapsed_time > 5000 ) - BEGIN + --insert columns for clustered indexes and heaps + --collect info on identity columns for this one + SET @dsql = N'/* sp_BlitzIndex */ + SET LOCK_TIMEOUT 1000; /* To fix locking bug in sys.identity_columns. See Github issue #2176. */ + SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + SELECT ' + CAST(@DatabaseID AS NVARCHAR(16)) + ', + s.name, + si.object_id, + si.index_id, + sc.key_ordinal, + sc.is_included_column, + sc.is_descending_key, + sc.partition_ordinal, + c.name as column_name, + st.name as system_type_name, + c.max_length, + c.[precision], + c.[scale], + c.collation_name, + c.is_nullable, + c.is_identity, + c.is_computed, + c.is_replicated, + ' + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N'c.is_sparse' ELSE N'NULL as is_sparse' END + N', + ' + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N'c.is_filestream' ELSE N'NULL as is_filestream' END + N', + CAST(ic.seed_value AS DECIMAL(38,0)), + CAST(ic.increment_value AS DECIMAL(38,0)), + CAST(ic.last_value AS DECIMAL(38,0)), + ic.is_not_for_replication + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.indexes si + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns c ON + si.object_id=c.object_id + LEFT JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.index_columns sc ON + sc.object_id = si.object_id + and sc.index_id=si.index_id + AND sc.column_id=c.column_id + LEFT JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.identity_columns ic ON + c.object_id=ic.object_id and + c.column_id=ic.column_id + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.types st ON + c.system_type_id=st.system_type_id + AND c.user_type_id=st.user_type_id + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects AS so ON si.object_id = so.object_id + AND so.is_ms_shipped = 0 + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s ON s.schema_id = so.schema_id + WHERE si.index_id in (0,1) ' + + CASE WHEN @ObjectID IS NOT NULL + THEN N' AND si.object_id=' + CAST(@ObjectID AS NVARCHAR(30)) + ELSE N'' END + + N'OPTION (RECOMPILE);'; - SET @StringToExecute = N' - DECLARE @bad_estimate TABLE - ( - session_id INT, - request_id INT, - estimate_inaccuracy BIT - ); - - INSERT @bad_estimate ( session_id, request_id, estimate_inaccuracy ) - SELECT x.session_id, - x.request_id, - x.estimate_inaccuracy - FROM ( - SELECT deqp.session_id, - deqp.request_id, - CASE WHEN deqp.row_count > ( deqp.estimate_row_count * 10000 ) - THEN 1 - ELSE 0 - END AS estimate_inaccuracy - FROM sys.dm_exec_query_profiles AS deqp - WHERE deqp.session_id <> @@SPID - ) AS x - WHERE x.estimate_inaccuracy = 1 - GROUP BY x.session_id, - x.request_id, - x.estimate_inaccuracy; - - DECLARE @parallelism_skew TABLE - ( - session_id INT, - request_id INT, - parallelism_skew BIT - ); - - INSERT @parallelism_skew ( session_id, request_id, parallelism_skew ) - SELECT y.session_id, - y.request_id, - y.parallelism_skew - FROM ( - SELECT x.session_id, - x.request_id, - x.node_id, - x.thread_id, - x.row_count, - x.sum_node_rows, - x.node_dop, - x.sum_node_rows / x.node_dop AS even_distribution, - x.row_count / (1. * ISNULL(NULLIF(x.sum_node_rows / x.node_dop, 0), 1)) AS skew_percent, - CASE - WHEN x.row_count > 10000 - AND x.row_count / (1. * ISNULL(NULLIF(x.sum_node_rows / x.node_dop, 0), 1)) > 2. - THEN 1 - WHEN x.row_count > 10000 - AND x.row_count / (1. * ISNULL(NULLIF(x.sum_node_rows / x.node_dop, 0), 1)) < 0.5 - THEN 1 - ELSE 0 - END AS parallelism_skew - FROM ( - SELECT deqp.session_id, - deqp.request_id, - deqp.node_id, - deqp.thread_id, - deqp.row_count, - SUM(deqp.row_count) - OVER ( PARTITION BY deqp.session_id, - deqp.request_id, - deqp.node_id - ORDER BY deqp.row_count - ROWS BETWEEN UNBOUNDED PRECEDING - AND UNBOUNDED FOLLOWING ) - AS sum_node_rows, - COUNT(*) - OVER ( PARTITION BY deqp.session_id, - deqp.request_id, - deqp.node_id - ORDER BY deqp.row_count - ROWS BETWEEN UNBOUNDED PRECEDING - AND UNBOUNDED FOLLOWING ) - AS node_dop - FROM sys.dm_exec_query_profiles AS deqp - WHERE deqp.thread_id > 0 - AND deqp.session_id <> @@SPID - AND EXISTS - ( - SELECT 1/0 - FROM sys.dm_exec_query_profiles AS deqp2 - WHERE deqp.session_id = deqp2.session_id - AND deqp.node_id = deqp2.node_id - AND deqp2.thread_id > 0 - GROUP BY deqp2.session_id, deqp2.node_id - HAVING COUNT(deqp2.node_id) > 1 - ) - ) AS x - ) AS y - WHERE y.parallelism_skew = 1 - GROUP BY y.session_id, - y.request_id, - y.parallelism_skew; - - /* Queries in dm_exec_query_profiles showing signs of poor cardinality estimates - CheckID 42 */ - IF (@Debug = 1) - BEGIN - RAISERROR(''Running CheckID 42'',10,1) WITH NOWAIT; - END + IF @dsql IS NULL + RAISERROR('@dsql is null',16,1); - INSERT INTO #BlitzFirstResults - (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, OpenTransactionCount, QueryHash, QueryPlan) - SELECT 42 AS CheckID, - 100 AS Priority, - ''Query Performance'' AS FindingsGroup, - ''Queries with 10000x cardinality misestimations'' AS Findings, - ''https://brentozar.com/go/skewedup'' AS URL, - ''The query on SPID '' - + RTRIM(b.session_id) - + '' has been running for '' - + RTRIM(r.total_elapsed_time / 1000) - + '' seconds, with a large cardinality misestimate'' AS Details, - ''No quick fix here: time to dig into the actual execution plan. '' AS HowToStopIt, - r.start_time, - s.login_name, - s.nt_user_name, - s.program_name, - s.host_name, - r.database_id, - DB_NAME(r.database_id), - dest.text, - s.open_transaction_count, - r.query_hash, '; + RAISERROR (N'Inserting data into #IndexColumns for clustered indexes and heaps',0,1) WITH NOWAIT; + IF @Debug = 1 + BEGIN + PRINT SUBSTRING(@dsql, 0, 4000); + PRINT SUBSTRING(@dsql, 4000, 8000); + PRINT SUBSTRING(@dsql, 8000, 12000); + PRINT SUBSTRING(@dsql, 12000, 16000); + PRINT SUBSTRING(@dsql, 16000, 20000); + PRINT SUBSTRING(@dsql, 20000, 24000); + PRINT SUBSTRING(@dsql, 24000, 28000); + PRINT SUBSTRING(@dsql, 28000, 32000); + PRINT SUBSTRING(@dsql, 32000, 36000); + PRINT SUBSTRING(@dsql, 36000, 40000); + END; + BEGIN TRY + INSERT #IndexColumns ( database_id, [schema_name], [object_id], index_id, key_ordinal, is_included_column, is_descending_key, partition_ordinal, + column_name, system_type_name, max_length, precision, scale, collation_name, is_nullable, is_identity, is_computed, + is_replicated, is_sparse, is_filestream, seed_value, increment_value, last_value, is_not_for_replication ) + EXEC sp_executesql @dsql; + END TRY + BEGIN CATCH + RAISERROR (N'Failure inserting data into #IndexColumns for clustered indexes and heaps.', 0,1) WITH NOWAIT; - IF @dm_exec_query_statistics_xml = 1 - SET @StringToExecute = @StringToExecute + N' COALESCE(qs_live.query_plan, qp.query_plan) AS query_plan '; - ELSE - SET @StringToExecute = @StringToExecute + N' qp.query_plan '; + IF @dsql IS NOT NULL + BEGIN + SET @msg= 'Last @dsql: ' + @dsql; + RAISERROR(@msg, 0, 1) WITH NOWAIT; + END; - SET @StringToExecute = @StringToExecute + N' - FROM @bad_estimate AS b - JOIN sys.dm_exec_requests AS r - ON r.session_id = b.session_id - AND r.request_id = b.request_id - JOIN sys.dm_exec_sessions AS s - ON s.session_id = b.session_id - CROSS APPLY sys.dm_exec_sql_text(r.sql_handle) AS dest - CROSS APPLY sys.dm_exec_query_plan(r.plan_handle) AS qp '; + SELECT @msg = @DatabaseName + N' database failed to process. ' + ERROR_MESSAGE(), + @ErrorSeverity = 0, @ErrorState = ERROR_STATE(); + RAISERROR (@msg,@ErrorSeverity, @ErrorState )WITH NOWAIT; - IF EXISTS (SELECT * FROM sys.all_objects WHERE name = 'dm_exec_query_statistics_xml') - SET @StringToExecute = @StringToExecute + N' OUTER APPLY sys.dm_exec_query_statistics_xml(s.session_id) qs_live '; - - - SET @StringToExecute = @StringToExecute + N'; + WHILE @@trancount > 0 + ROLLBACK; - /* Queries in dm_exec_query_profiles showing signs of unbalanced parallelism - CheckID 43 */ - IF (@Debug = 1) - BEGIN - RAISERROR(''Running CheckID 43'',10,1) WITH NOWAIT; - END + RETURN; + END CATCH; - INSERT INTO #BlitzFirstResults - (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, OpenTransactionCount, QueryHash, QueryPlan) - SELECT 43 AS CheckID, - 100 AS Priority, - ''Query Performance'' AS FindingsGroup, - ''Queries with 10000x skewed parallelism'' AS Findings, - ''https://brentozar.com/go/skewedup'' AS URL, - ''The query on SPID '' - + RTRIM(p.session_id) - + '' has been running for '' - + RTRIM(r.total_elapsed_time / 1000) - + '' seconds, with a parallel threads doing uneven work.'' AS Details, - ''No quick fix here: time to dig into the actual execution plan. '' AS HowToStopIt, - r.start_time, - s.login_name, - s.nt_user_name, - s.program_name, - s.host_name, - r.database_id, - DB_NAME(r.database_id), - dest.text, - s.open_transaction_count, - r.query_hash, '; - IF @dm_exec_query_statistics_xml = 1 - SET @StringToExecute = @StringToExecute + N' COALESCE(qs_live.query_plan, qp.query_plan) AS query_plan '; - ELSE - SET @StringToExecute = @StringToExecute + N' qp.query_plan '; - - SET @StringToExecute = @StringToExecute + N' - FROM @parallelism_skew AS p - JOIN sys.dm_exec_requests AS r - ON r.session_id = p.session_id - AND r.request_id = p.request_id - JOIN sys.dm_exec_sessions AS s - ON s.session_id = p.session_id - CROSS APPLY sys.dm_exec_sql_text(r.sql_handle) AS dest - CROSS APPLY sys.dm_exec_query_plan(r.plan_handle) AS qp '; + --insert columns for nonclustered indexes + --this uses a full join to sys.index_columns + --We don't collect info on identity columns here. They may be in NC indexes, but we just analyze identities in the base table. + SET @dsql = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + SELECT ' + CAST(@DatabaseID AS NVARCHAR(16)) + ', + s.name, + si.object_id, + si.index_id, + sc.key_ordinal, + sc.is_included_column, + sc.is_descending_key, + sc.partition_ordinal, + c.name as column_name, + st.name as system_type_name, + c.max_length, + c.[precision], + c.[scale], + c.collation_name, + c.is_nullable, + c.is_identity, + c.is_computed, + c.is_replicated, + ' + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N'c.is_sparse' ELSE N'NULL AS is_sparse' END + N', + ' + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N'c.is_filestream' ELSE N'NULL AS is_filestream' END + N' + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.indexes AS si + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns AS c ON + si.object_id=c.object_id + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.index_columns AS sc ON + sc.object_id = si.object_id + and sc.index_id=si.index_id + AND sc.column_id=c.column_id + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.types AS st ON + c.system_type_id=st.system_type_id + AND c.user_type_id=st.user_type_id + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects AS so ON si.object_id = so.object_id + AND so.is_ms_shipped = 0 + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s ON s.schema_id = so.schema_id + WHERE si.index_id not in (0,1) ' + + CASE WHEN @ObjectID IS NOT NULL + THEN N' AND si.object_id=' + CAST(@ObjectID AS NVARCHAR(30)) + ELSE N'' END + + N'OPTION (RECOMPILE);'; - IF EXISTS (SELECT * FROM sys.all_objects WHERE name = 'dm_exec_query_statistics_xml') - SET @StringToExecute = @StringToExecute + N' OUTER APPLY sys.dm_exec_query_statistics_xml(s.session_id) qs_live '; - - - SET @StringToExecute = @StringToExecute + N';'; + IF @dsql IS NULL + RAISERROR('@dsql is null',16,1); - EXECUTE sp_executesql @StringToExecute, N'@Debug BIT',@Debug = @Debug; - END - - END - END + RAISERROR (N'Inserting data into #IndexColumns for nonclustered indexes',0,1) WITH NOWAIT; + IF @Debug = 1 + BEGIN + PRINT SUBSTRING(@dsql, 0, 4000); + PRINT SUBSTRING(@dsql, 4000, 8000); + PRINT SUBSTRING(@dsql, 8000, 12000); + PRINT SUBSTRING(@dsql, 12000, 16000); + PRINT SUBSTRING(@dsql, 16000, 20000); + PRINT SUBSTRING(@dsql, 20000, 24000); + PRINT SUBSTRING(@dsql, 24000, 28000); + PRINT SUBSTRING(@dsql, 28000, 32000); + PRINT SUBSTRING(@dsql, 32000, 36000); + PRINT SUBSTRING(@dsql, 36000, 40000); + END; + INSERT #IndexColumns ( database_id, [schema_name], [object_id], index_id, key_ordinal, is_included_column, is_descending_key, partition_ordinal, + column_name, system_type_name, max_length, precision, scale, collation_name, is_nullable, is_identity, is_computed, + is_replicated, is_sparse, is_filestream ) + EXEC sp_executesql @dsql; + + SET @dsql = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + SELECT ' + CAST(@DatabaseID AS NVARCHAR(10)) + N' AS database_id, + so.object_id, + si.index_id, + si.type, + @i_DatabaseName AS database_name, + COALESCE(sc.NAME, ''Unknown'') AS [schema_name], + COALESCE(so.name, ''Unknown'') AS [object_name], + COALESCE(si.name, ''Unknown'') AS [index_name], + CASE WHEN so.[type] = CAST(''V'' AS CHAR(2)) THEN 1 ELSE 0 END, + si.is_unique, + si.is_primary_key, + CASE when si.type = 3 THEN 1 ELSE 0 END AS is_XML, + CASE when si.type = 4 THEN 1 ELSE 0 END AS is_spatial, + CASE when si.type = 6 THEN 1 ELSE 0 END AS is_NC_columnstore, + CASE when si.type = 5 then 1 else 0 end as is_CX_columnstore, + CASE when si.data_space_id = 0 then 1 else 0 end as is_in_memory_oltp, + si.is_disabled, + si.is_hypothetical, + si.is_padded, + si.fill_factor,' + + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N' + CASE WHEN si.filter_definition IS NOT NULL THEN si.filter_definition + ELSE N'''' + END AS filter_definition' ELSE N''''' AS filter_definition' END + N' + , ISNULL(us.user_seeks, 0), + ISNULL(us.user_scans, 0), + ISNULL(us.user_lookups, 0), + ISNULL(us.user_updates, 0), + us.last_user_seek, + us.last_user_scan, + us.last_user_lookup, + us.last_user_update, + so.create_date, + so.modify_date + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.indexes AS si WITH (NOLOCK) + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects AS so WITH (NOLOCK) ON si.object_id = so.object_id + AND so.is_ms_shipped = 0 /*Exclude objects shipped by Microsoft*/ + AND so.type <> ''TF'' /*Exclude table valued functions*/ + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas sc ON so.schema_id = sc.schema_id + LEFT JOIN sys.dm_db_index_usage_stats AS us WITH (NOLOCK) ON si.[object_id] = us.[object_id] + AND si.index_id = us.index_id + AND us.database_id = ' + CAST(@DatabaseID AS NVARCHAR(10)) + N' + WHERE si.[type] IN ( 0, 1, 2, 3, 4, 5, 6 ) + /* Heaps, clustered, nonclustered, XML, spatial, Cluster Columnstore, NC Columnstore */ ' + + CASE WHEN @TableName IS NOT NULL THEN N' and so.name=' + QUOTENAME(@TableName,N'''') + N' ' ELSE N'' END + + CASE WHEN ( @IncludeInactiveIndexes = 0 + AND @Mode IN (0, 4) + AND @TableName IS NULL ) + THEN N'AND ( us.user_seeks + us.user_scans + us.user_lookups + us.user_updates ) > 0' + ELSE N'' + END + + N'OPTION ( RECOMPILE ); + '; + IF @dsql IS NULL + RAISERROR('@dsql is null',16,1); - /* Server Performance - High CPU Utilization - CheckID 24 */ - IF @Seconds < 30 - BEGIN - /* If we're waiting less than 30 seconds, run this check now rather than wait til the end. - We get this data from the ring buffers, and it's only updated once per minute, so might - as well get it now - whereas if we're checking 30+ seconds, it might get updated by the - end of our sp_BlitzFirst session. */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 24',10,1) WITH NOWAIT; - END + RAISERROR (N'Inserting data into #IndexSanity',0,1) WITH NOWAIT; + IF @Debug = 1 + BEGIN + PRINT SUBSTRING(@dsql, 0, 4000); + PRINT SUBSTRING(@dsql, 4000, 8000); + PRINT SUBSTRING(@dsql, 8000, 12000); + PRINT SUBSTRING(@dsql, 12000, 16000); + PRINT SUBSTRING(@dsql, 16000, 20000); + PRINT SUBSTRING(@dsql, 20000, 24000); + PRINT SUBSTRING(@dsql, 24000, 28000); + PRINT SUBSTRING(@dsql, 28000, 32000); + PRINT SUBSTRING(@dsql, 32000, 36000); + PRINT SUBSTRING(@dsql, 36000, 40000); + END; + INSERT #IndexSanity ( [database_id], [object_id], [index_id], [index_type], [database_name], [schema_name], [object_name], + index_name, is_indexed_view, is_unique, is_primary_key, is_XML, is_spatial, is_NC_columnstore, is_CX_columnstore, is_in_memory_oltp, + is_disabled, is_hypothetical, is_padded, fill_factor, filter_definition, user_seeks, user_scans, + user_lookups, user_updates, last_user_seek, last_user_scan, last_user_lookup, last_user_update, + create_date, modify_date ) + EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) - SELECT 24, 50, 'Server Performance', 'High CPU Utilization', CAST(100 - SystemIdle AS NVARCHAR(20)) + N'%.', 100 - SystemIdle, 'http://www.BrentOzar.com/go/cpu' - FROM ( - SELECT record, - record.value('(./Record/SchedulerMonitorEvent/SystemHealth/SystemIdle)[1]', 'int') AS SystemIdle - FROM ( - SELECT TOP 1 CONVERT(XML, record) AS record - FROM sys.dm_os_ring_buffers - WHERE ring_buffer_type = N'RING_BUFFER_SCHEDULER_MONITOR' - AND record LIKE '%%' - ORDER BY timestamp DESC) AS rb - ) AS y - WHERE 100 - SystemIdle >= 50; - /* CPU Utilization - CheckID 23 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 23',10,1) WITH NOWAIT; - END + RAISERROR (N'Checking partition count',0,1) WITH NOWAIT; + IF @BringThePain = 0 AND @SkipPartitions = 0 AND @TableName IS NULL + BEGIN + /* Count the total number of partitions */ + SET @dsql = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + SELECT @RowcountOUT = SUM(1) FROM ' + QUOTENAME(@DatabaseName) + '.sys.partitions WHERE partition_number > 1 OPTION ( RECOMPILE );'; + EXEC sp_executesql @dsql, N'@RowcountOUT BIGINT OUTPUT', @RowcountOUT = @Rowcount OUTPUT; + IF @Rowcount > 100 + BEGIN + RAISERROR (N'Setting @SkipPartitions = 1 because > 100 partitions were found. To check them, you must set @BringThePain = 1.',0,1) WITH NOWAIT; + SET @SkipPartitions = 1; + INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, URL, details, index_definition, + index_usage_summary, index_size_summary ) + VALUES ( 1, 0 , + 'Some Checks Were Skipped', + '@SkipPartitions Forced to 1', + 'http://FirstResponderKit.org', CAST(@Rowcount AS NVARCHAR(50)) + ' partitions found. To analyze them, use @BringThePain = 1.', 'We try to keep things quick - and warning, running @BringThePain = 1 can take tens of minutes.', '', '' + ); + END; + END; - IF SERVERPROPERTY('Edition') <> 'SQL Azure' - WITH y - AS - ( - SELECT CONVERT(VARCHAR(5), 100 - ca.c.value('.', 'INT')) AS system_idle, - CONVERT(VARCHAR(30), rb.event_date) AS event_date, - CONVERT(VARCHAR(8000), rb.record) AS record - FROM - ( SELECT CONVERT(XML, dorb.record) AS record, - DATEADD(ms, ( ts.ms_ticks - dorb.timestamp ), GETDATE()) AS event_date - FROM sys.dm_os_ring_buffers AS dorb - CROSS JOIN - ( SELECT dosi.ms_ticks FROM sys.dm_os_sys_info AS dosi ) AS ts - WHERE dorb.ring_buffer_type = N'RING_BUFFER_SCHEDULER_MONITOR' - AND record LIKE '%%' ) AS rb - CROSS APPLY rb.record.nodes('/Record/SchedulerMonitorEvent/SystemHealth/SystemIdle') AS ca(c) - ) - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL, HowToStopIt) - SELECT TOP 1 - 23, - 250, - 'Server Info', - 'CPU Utilization', - y.system_idle + N'%. Ring buffer details: ' + CAST(y.record AS NVARCHAR(4000)), - y.system_idle , - 'http://www.BrentOzar.com/go/cpu', - STUFF(( SELECT TOP 2147483647 - CHAR(10) + CHAR(13) - + y2.system_idle - + '% ON ' - + y2.event_date - + ' Ring buffer details: ' - + y2.record - FROM y AS y2 - ORDER BY y2.event_date DESC - FOR XML PATH(N''), TYPE ).value(N'.[1]', N'VARCHAR(MAX)'), 1, 1, N'') AS query - FROM y - ORDER BY y.event_date DESC; - - /* Highlight if non SQL processes are using >25% CPU - CheckID 28 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 28',10,1) WITH NOWAIT; - END - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) - SELECT 28, 50, 'Server Performance', 'High CPU Utilization - Not SQL', CONVERT(NVARCHAR(100),100 - (y.SQLUsage + y.SystemIdle)) + N'% - Other Processes (not SQL Server) are using this much CPU. This may impact on the performance of your SQL Server instance', 100 - (y.SQLUsage + y.SystemIdle), 'http://www.BrentOzar.com/go/cpu' - FROM ( - SELECT record, - record.value('(./Record/SchedulerMonitorEvent/SystemHealth/SystemIdle)[1]', 'int') AS SystemIdle - ,record.value('(./Record/SchedulerMonitorEvent/SystemHealth/ProcessUtilization)[1]', 'int') AS SQLUsage - FROM ( - SELECT TOP 1 CONVERT(XML, record) AS record - FROM sys.dm_os_ring_buffers - WHERE ring_buffer_type = N'RING_BUFFER_SCHEDULER_MONITOR' - AND record LIKE '%%' - ORDER BY timestamp DESC) AS rb - ) AS y - WHERE 100 - (y.SQLUsage + y.SystemIdle) >= 25; - - END; /* IF @Seconds < 30 */ + IF (@SkipPartitions = 0) + BEGIN + IF (SELECT LEFT(@SQLServerProductVersion, + CHARINDEX('.',@SQLServerProductVersion,0)-1 )) <= 2147483647 --Make change here + BEGIN + + RAISERROR (N'Preferring non-2012 syntax with LEFT JOIN to sys.dm_db_index_operational_stats',0,1) WITH NOWAIT; - /* Query Problems - Statistics Updated Recently - CheckID 44 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 44',10,1) WITH NOWAIT; - END + --NOTE: If you want to use the newer syntax for 2012+, you'll have to change 2147483647 to 11 on line ~819 + --This change was made because on a table with lots of paritions, the OUTER APPLY was crazy slow. + SET @dsql = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + SELECT ' + CAST(@DatabaseID AS NVARCHAR(10)) + N' AS database_id, + ps.object_id, + s.name, + ps.index_id, + ps.partition_number, + ps.row_count, + ps.reserved_page_count * 8. / 1024. AS reserved_MB, + ps.lob_reserved_page_count * 8. / 1024. AS reserved_LOB_MB, + ps.row_overflow_reserved_page_count * 8. / 1024. AS reserved_row_overflow_MB, + le.lock_escalation_desc, + ' + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N'par.data_compression_desc ' ELSE N'null as data_compression_desc ' END + N', + SUM(os.leaf_insert_count), + SUM(os.leaf_delete_count), + SUM(os.leaf_update_count), + SUM(os.range_scan_count), + SUM(os.singleton_lookup_count), + SUM(os.forwarded_fetch_count), + SUM(os.lob_fetch_in_pages), + SUM(os.lob_fetch_in_bytes), + SUM(os.row_overflow_fetch_in_pages), + SUM(os.row_overflow_fetch_in_bytes), + SUM(os.row_lock_count), + SUM(os.row_lock_wait_count), + SUM(os.row_lock_wait_in_ms), + SUM(os.page_lock_count), + SUM(os.page_lock_wait_count), + SUM(os.page_lock_wait_in_ms), + SUM(os.index_lock_promotion_attempt_count), + SUM(os.index_lock_promotion_count), + SUM(os.page_latch_wait_count), + SUM(os.page_latch_wait_in_ms), + SUM(os.page_io_latch_wait_count), + SUM(os.page_io_latch_wait_in_ms), '; - IF 20 >= (SELECT COUNT(*) FROM sys.databases WHERE name NOT IN ('master', 'model', 'msdb', 'tempdb')) - BEGIN - CREATE TABLE #UpdatedStats (HowToStopIt NVARCHAR(4000), RowsForSorting BIGINT); - IF EXISTS(SELECT * FROM sys.all_objects WHERE name = 'dm_db_stats_properties') - BEGIN - /* We don't want to hang around to obtain locks */ - SET LOCK_TIMEOUT 0; + /* Get columnstore dictionary size - more info: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2585 */ + IF EXISTS (SELECT * FROM sys.all_objects WHERE name = 'column_store_dictionaries') + SET @dsql = @dsql + N' COALESCE((SELECT SUM (on_disk_size / 1024.0 / 1024) FROM ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_dictionaries dict WHERE dict.partition_id = ps.partition_id),0) AS reserved_dictionary_MB '; + ELSE + SET @dsql = @dsql + N' 0 AS reserved_dictionary_MB '; - EXEC sp_MSforeachdb N'USE [?]; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; SET LOCK_TIMEOUT 1000; - BEGIN TRY - INSERT INTO #UpdatedStats(HowToStopIt, RowsForSorting) - SELECT HowToStopIt = - QUOTENAME(DB_NAME()) + N''.'' + - QUOTENAME(SCHEMA_NAME(obj.schema_id)) + N''.'' + - QUOTENAME(obj.name) + - N'' statistic '' + QUOTENAME(stat.name) + - N'' was updated on '' + CONVERT(NVARCHAR(50), sp.last_updated, 121) + N'','' + - N'' had '' + CAST(sp.rows AS NVARCHAR(50)) + N'' rows, with '' + - CAST(sp.rows_sampled AS NVARCHAR(50)) + N'' rows sampled,'' + - N'' producing '' + CAST(sp.steps AS NVARCHAR(50)) + N'' steps in the histogram.'', - sp.rows - FROM sys.objects AS obj WITH (NOLOCK) - INNER JOIN sys.stats AS stat WITH (NOLOCK) ON stat.object_id = obj.object_id - CROSS APPLY sys.dm_db_stats_properties(stat.object_id, stat.stats_id) AS sp - WHERE sp.last_updated > DATEADD(MI, -15, GETDATE()) - AND obj.is_ms_shipped = 0 - AND ''[?]'' <> ''[tempdb]''; - END TRY - BEGIN CATCH - IF (ERROR_NUMBER() = 1222) - BEGIN - INSERT INTO #UpdatedStats(HowToStopIt, RowsForSorting) - SELECT HowToStopIt = - QUOTENAME(DB_NAME()) + - N'' No information could be retrieved as the lock timeout was exceeded,''+ - N'' this is likely due to an Index operation in Progress'', - -1 - END - ELSE - BEGIN - INSERT INTO #UpdatedStats(HowToStopIt, RowsForSorting) - SELECT HowToStopIt = - QUOTENAME(DB_NAME()) + - N'' No information could be retrieved as a result of error: ''+ - CAST(ERROR_NUMBER() AS NVARCHAR(10)) + - N'' with message: ''+ - CAST(ERROR_MESSAGE() AS NVARCHAR(128)), - -1 - END - END CATCH'; - /* Set timeout back to a default value of -1 */ - SET LOCK_TIMEOUT -1; - END; - - /* We mark timeout exceeded with a -1 so only show these IF there is statistics info that succeeded */ - IF EXISTS (SELECT * FROM #UpdatedStats WHERE RowsForSorting > -1) - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) - SELECT 44 AS CheckId, - 50 AS Priority, - 'Query Problems' AS FindingGroup, - 'Statistics Updated Recently' AS Finding, - 'http://www.BrentOzar.com/go/stats' AS URL, - 'In the last 15 minutes, statistics were updated. To see which ones, click the HowToStopIt column.' + @LineFeed + @LineFeed - + 'This effectively clears the plan cache for queries that involve these tables,' + @LineFeed - + 'which thereby causes parameter sniffing: those queries are now getting brand new' + @LineFeed - + 'query plans based on whatever parameters happen to call them next.' + @LineFeed + @LineFeed - + 'Be on the lookout for sudden parameter sniffing issues after this time range.', - HowToStopIt = (SELECT (SELECT HowToStopIt + NCHAR(10)) - FROM #UpdatedStats - ORDER BY RowsForSorting DESC - FOR XML PATH('')); + SET @dsql = @dsql + N' + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_partition_stats AS ps + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partitions AS par on ps.partition_id=par.partition_id + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects AS so ON ps.object_id = so.object_id + AND so.is_ms_shipped = 0 /*Exclude objects shipped by Microsoft*/ + AND so.type <> ''TF'' /*Exclude table valued functions*/ + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s ON s.schema_id = so.schema_id + LEFT JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_index_operational_stats(' + + CAST(@DatabaseID AS NVARCHAR(10)) + N', NULL, NULL,NULL) AS os ON + ps.object_id=os.object_id and ps.index_id=os.index_id and ps.partition_number=os.partition_number + OUTER APPLY (SELECT st.lock_escalation_desc + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.tables st + WHERE st.object_id = ps.object_id + AND ps.index_id < 2 ) le + WHERE 1=1 + ' + CASE WHEN @ObjectID IS NOT NULL THEN N'AND so.object_id=' + CAST(@ObjectID AS NVARCHAR(30)) + N' ' ELSE N' ' END + N' + ' + CASE WHEN @Filter = 2 THEN N'AND ps.reserved_page_count * 8./1024. > ' + CAST(@FilterMB AS NVARCHAR(5)) + N' ' ELSE N' ' END + N' + GROUP BY ps.object_id, + s.name, + ps.index_id, + ps.partition_number, + ps.partition_id, + ps.row_count, + ps.reserved_page_count, + ps.lob_reserved_page_count, + ps.row_overflow_reserved_page_count, + le.lock_escalation_desc, + ' + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N'par.data_compression_desc ' ELSE N'null as data_compression_desc ' END + N' + ORDER BY ps.object_id, ps.index_id, ps.partition_number + OPTION ( RECOMPILE ); + '; + END; + ELSE + BEGIN + RAISERROR (N'Using 2012 syntax to query sys.dm_db_index_operational_stats',0,1) WITH NOWAIT; + --This is the syntax that will be used if you change 2147483647 to 11 on line ~819. + --If you have a lot of paritions and this suddenly starts running for a long time, change it back. + SET @dsql = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + SELECT ' + CAST(@DatabaseID AS NVARCHAR(10)) + N' AS database_id, + ps.object_id, + s.name, + ps.index_id, + ps.partition_number, + ps.row_count, + ps.reserved_page_count * 8. / 1024. AS reserved_MB, + ps.lob_reserved_page_count * 8. / 1024. AS reserved_LOB_MB, + ps.row_overflow_reserved_page_count * 8. / 1024. AS reserved_row_overflow_MB, + le.lock_escalation_desc, + ' + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N'par.data_compression_desc ' ELSE N'null as data_compression_desc' END + N', + SUM(os.leaf_insert_count), + SUM(os.leaf_delete_count), + SUM(os.leaf_update_count), + SUM(os.range_scan_count), + SUM(os.singleton_lookup_count), + SUM(os.forwarded_fetch_count), + SUM(os.lob_fetch_in_pages), + SUM(os.lob_fetch_in_bytes), + SUM(os.row_overflow_fetch_in_pages), + SUM(os.row_overflow_fetch_in_bytes), + SUM(os.row_lock_count), + SUM(os.row_lock_wait_count), + SUM(os.row_lock_wait_in_ms), + SUM(os.page_lock_count), + SUM(os.page_lock_wait_count), + SUM(os.page_lock_wait_in_ms), + SUM(os.index_lock_promotion_attempt_count), + SUM(os.index_lock_promotion_count), + SUM(os.page_latch_wait_count), + SUM(os.page_latch_wait_in_ms), + SUM(os.page_io_latch_wait_count), + SUM(os.page_io_latch_wait_in_ms)'; - END + /* Get columnstore dictionary size - more info: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2585 */ + IF EXISTS (SELECT * FROM sys.all_objects WHERE name = 'column_store_dictionaries') + SET @dsql = @dsql + N' COALESCE((SELECT SUM (on_disk_size / 1024.0 / 1024) FROM ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_dictionaries dict WHERE dict.partition_id = ps.partition_id),0) AS reserved_dictionary_MB '; + ELSE + SET @dsql = @dsql + N' 0 AS reserved_dictionary_MB '; - RAISERROR('Finished running investigatory queries',10,1) WITH NOWAIT; + SET @dsql = @dsql + N' + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_partition_stats AS ps + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partitions AS par on ps.partition_id=par.partition_id + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects AS so ON ps.object_id = so.object_id + AND so.is_ms_shipped = 0 /*Exclude objects shipped by Microsoft*/ + AND so.type <> ''TF'' /*Exclude table valued functions*/ + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s ON s.schema_id = so.schema_id + OUTER APPLY ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_index_operational_stats(' + + CAST(@DatabaseID AS NVARCHAR(10)) + N', ps.object_id, ps.index_id,ps.partition_number) AS os + OUTER APPLY (SELECT st.lock_escalation_desc + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.tables st + WHERE st.object_id = ps.object_id + AND ps.index_id < 2 ) le + WHERE 1=1 + ' + CASE WHEN @ObjectID IS NOT NULL THEN N'AND so.object_id=' + CAST(@ObjectID AS NVARCHAR(30)) + N' ' ELSE N' ' END + N' + ' + CASE WHEN @Filter = 2 THEN N'AND ps.reserved_page_count * 8./1024. > ' + CAST(@FilterMB AS NVARCHAR(5)) + N' ' ELSE N' ' END + ' + GROUP BY ps.object_id, + s.name, + ps.index_id, + ps.partition_number, + ps.partition_id, + ps.row_count, + ps.reserved_page_count, + ps.lob_reserved_page_count, + ps.row_overflow_reserved_page_count, + le.lock_escalation_desc, + ' + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N'par.data_compression_desc ' ELSE N'null as data_compression_desc ' END + N' + ORDER BY ps.object_id, ps.index_id, ps.partition_number + OPTION ( RECOMPILE ); + '; + END; - /* End of checks. If we haven't waited @Seconds seconds, wait. */ - IF DATEADD(SECOND,1,SYSDATETIMEOFFSET()) < @FinishSampleTime - BEGIN - RAISERROR('Waiting to match @Seconds parameter',10,1) WITH NOWAIT; - WAITFOR TIME @FinishSampleTimeWaitFor; - END; + IF @dsql IS NULL + RAISERROR('@dsql is null',16,1); - RAISERROR('Capturing second pass of wait stats, perfmon counters, file stats',10,1) WITH NOWAIT; - /* Populate #FileStats, #PerfmonStats, #WaitStats with DMV data. In a second, we'll compare these. */ - INSERT #WaitStats(Pass, SampleTime, wait_type, wait_time_ms, signal_wait_time_ms, waiting_tasks_count) - SELECT - x.Pass, - x.SampleTime, - x.wait_type, - SUM(x.sum_wait_time_ms) AS sum_wait_time_ms, - SUM(x.sum_signal_wait_time_ms) AS sum_signal_wait_time_ms, - SUM(x.sum_waiting_tasks) AS sum_waiting_tasks - FROM ( - SELECT - 2 AS Pass, - SYSDATETIMEOFFSET() AS SampleTime, - owt.wait_type, - SUM(owt.wait_duration_ms) OVER (PARTITION BY owt.wait_type, owt.session_id) - - CASE WHEN @Seconds = 0 THEN 0 ELSE (@Seconds * 1000) END AS sum_wait_time_ms, - 0 AS sum_signal_wait_time_ms, - CASE @Seconds WHEN 0 THEN 0 ELSE 1 END AS sum_waiting_tasks - FROM sys.dm_os_waiting_tasks owt - WHERE owt.session_id > 50 - AND owt.wait_duration_ms >= CASE @Seconds WHEN 0 THEN 0 ELSE @Seconds * 1000 END - UNION ALL - SELECT - 2 AS Pass, - SYSDATETIMEOFFSET() AS SampleTime, - os.wait_type, - SUM(os.wait_time_ms) OVER (PARTITION BY os.wait_type) AS sum_wait_time_ms, - SUM(os.signal_wait_time_ms) OVER (PARTITION BY os.wait_type ) AS sum_signal_wait_time_ms, - SUM(os.waiting_tasks_count) OVER (PARTITION BY os.wait_type) AS sum_waiting_tasks - FROM sys.dm_os_wait_stats os - ) x - WHERE NOT EXISTS - ( - SELECT * - FROM ##WaitCategories AS wc - WHERE wc.WaitType = x.wait_type - AND wc.Ignorable = 1 - ) - GROUP BY x.Pass, x.SampleTime, x.wait_type - ORDER BY sum_wait_time_ms DESC; + RAISERROR (N'Inserting data into #IndexPartitionSanity',0,1) WITH NOWAIT; + IF @Debug = 1 + BEGIN + PRINT SUBSTRING(@dsql, 0, 4000); + PRINT SUBSTRING(@dsql, 4000, 8000); + PRINT SUBSTRING(@dsql, 8000, 12000); + PRINT SUBSTRING(@dsql, 12000, 16000); + PRINT SUBSTRING(@dsql, 16000, 20000); + PRINT SUBSTRING(@dsql, 20000, 24000); + PRINT SUBSTRING(@dsql, 24000, 28000); + PRINT SUBSTRING(@dsql, 28000, 32000); + PRINT SUBSTRING(@dsql, 32000, 36000); + PRINT SUBSTRING(@dsql, 36000, 40000); + END; + INSERT #IndexPartitionSanity ( [database_id], + [object_id], + [schema_name], + index_id, + partition_number, + row_count, + reserved_MB, + reserved_LOB_MB, + reserved_row_overflow_MB, + lock_escalation_desc, + data_compression_desc, + leaf_insert_count, + leaf_delete_count, + leaf_update_count, + range_scan_count, + singleton_lookup_count, + forwarded_fetch_count, + lob_fetch_in_pages, + lob_fetch_in_bytes, + row_overflow_fetch_in_pages, + row_overflow_fetch_in_bytes, + row_lock_count, + row_lock_wait_count, + row_lock_wait_in_ms, + page_lock_count, + page_lock_wait_count, + page_lock_wait_in_ms, + index_lock_promotion_attempt_count, + index_lock_promotion_count, + page_latch_wait_count, + page_latch_wait_in_ms, + page_io_latch_wait_count, + page_io_latch_wait_in_ms, + reserved_dictionary_MB) + EXEC sp_executesql @dsql; + + END; --End Check For @SkipPartitions = 0 - INSERT INTO #FileStats (Pass, SampleTime, DatabaseID, FileID, DatabaseName, FileLogicalName, SizeOnDiskMB, io_stall_read_ms , - num_of_reads, [bytes_read] , io_stall_write_ms,num_of_writes, [bytes_written], PhysicalName, TypeDesc, avg_stall_read_ms, avg_stall_write_ms) - SELECT 2 AS Pass, - SYSDATETIMEOFFSET() AS SampleTime, - mf.[database_id], - mf.[file_id], - DB_NAME(vfs.database_id) AS [db_name], - mf.name + N' [' + mf.type_desc COLLATE SQL_Latin1_General_CP1_CI_AS + N']' AS file_logical_name , - CAST(( ( vfs.size_on_disk_bytes / 1024.0 ) / 1024.0 ) AS INT) AS size_on_disk_mb , - vfs.io_stall_read_ms , - vfs.num_of_reads , - vfs.[num_of_bytes_read], - vfs.io_stall_write_ms , - vfs.num_of_writes , - vfs.[num_of_bytes_written], - mf.physical_name, - mf.type_desc, - 0, - 0 - FROM sys.dm_io_virtual_file_stats (NULL, NULL) AS vfs - INNER JOIN #MasterFiles AS mf ON vfs.file_id = mf.file_id - AND vfs.database_id = mf.database_id - WHERE vfs.num_of_reads > 0 - OR vfs.num_of_writes > 0; - INSERT INTO #PerfmonStats (Pass, SampleTime, [object_name],[counter_name],[instance_name],[cntr_value],[cntr_type]) - SELECT 2 AS Pass, - SYSDATETIMEOFFSET() AS SampleTime, - RTRIM(dmv.object_name), RTRIM(dmv.counter_name), RTRIM(dmv.instance_name), dmv.cntr_value, dmv.cntr_type - FROM #PerfmonCounters counters - INNER JOIN sys.dm_os_performance_counters dmv ON counters.counter_name COLLATE SQL_Latin1_General_CP1_CI_AS = RTRIM(dmv.counter_name) COLLATE SQL_Latin1_General_CP1_CI_AS - AND counters.[object_name] COLLATE SQL_Latin1_General_CP1_CI_AS = RTRIM(dmv.[object_name]) COLLATE SQL_Latin1_General_CP1_CI_AS - AND (counters.[instance_name] IS NULL OR counters.[instance_name] COLLATE SQL_Latin1_General_CP1_CI_AS = RTRIM(dmv.[instance_name]) COLLATE SQL_Latin1_General_CP1_CI_AS); - /* Set the latencies and averages. We could do this with a CTE, but we're not ambitious today. */ - UPDATE fNow - SET avg_stall_read_ms = ((fNow.io_stall_read_ms - fBase.io_stall_read_ms) / (fNow.num_of_reads - fBase.num_of_reads)) - FROM #FileStats fNow - INNER JOIN #FileStats fBase ON fNow.DatabaseID = fBase.DatabaseID AND fNow.FileID = fBase.FileID AND fNow.SampleTime > fBase.SampleTime AND fNow.num_of_reads > fBase.num_of_reads AND fNow.io_stall_read_ms > fBase.io_stall_read_ms - WHERE (fNow.num_of_reads - fBase.num_of_reads) > 0; + RAISERROR (N'Inserting data into #MissingIndexes',0,1) WITH NOWAIT; + SET @dsql=N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' - UPDATE fNow - SET avg_stall_write_ms = ((fNow.io_stall_write_ms - fBase.io_stall_write_ms) / (fNow.num_of_writes - fBase.num_of_writes)) - FROM #FileStats fNow - INNER JOIN #FileStats fBase ON fNow.DatabaseID = fBase.DatabaseID AND fNow.FileID = fBase.FileID AND fNow.SampleTime > fBase.SampleTime AND fNow.num_of_writes > fBase.num_of_writes AND fNow.io_stall_write_ms > fBase.io_stall_write_ms - WHERE (fNow.num_of_writes - fBase.num_of_writes) > 0; - UPDATE pNow - SET [value_delta] = pNow.cntr_value - pFirst.cntr_value, - [value_per_second] = ((1.0 * pNow.cntr_value - pFirst.cntr_value) / DATEDIFF(ss, pFirst.SampleTime, pNow.SampleTime)) - FROM #PerfmonStats pNow - INNER JOIN #PerfmonStats pFirst ON pFirst.[object_name] = pNow.[object_name] AND pFirst.counter_name = pNow.counter_name AND (pFirst.instance_name = pNow.instance_name OR (pFirst.instance_name IS NULL AND pNow.instance_name IS NULL)) - AND pNow.ID > pFirst.ID - WHERE DATEDIFF(ss, pFirst.SampleTime, pNow.SampleTime) > 0; + SET @dsql = @dsql + 'WITH ColumnNamesWithDataTypes AS(SELECT id.index_handle,id.object_id,cn.IndexColumnType,STUFF((SELECT '', '' + cn_inner.ColumnName + '' '' + + N'' {'' + CASE WHEN ty.name IN ( ''varchar'', ''char'' ) THEN ty.name + ''('' + CASE WHEN co.max_length = -1 THEN ''max'' ELSE CAST(co.max_length AS VARCHAR(25)) END + '')'' + WHEN ty.name IN ( ''nvarchar'', ''nchar'' ) THEN ty.name + ''('' + CASE WHEN co.max_length = -1 THEN ''max'' ELSE CAST(co.max_length / 2 AS VARCHAR(25)) END + '')'' + WHEN ty.name IN ( ''decimal'', ''numeric'' ) THEN ty.name + ''('' + CAST(co.precision AS VARCHAR(25)) + '', '' + CAST(co.scale AS VARCHAR(25)) + '')'' + WHEN ty.name IN ( ''datetime2'' ) THEN ty.name + ''('' + CAST(co.scale AS VARCHAR(25)) + '')'' + ELSE ty.name END + ''}'' + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_details AS id_inner + CROSS APPLY( + SELECT LTRIM(RTRIM(v.value(''(./text())[1]'', ''varchar(max)''))) AS ColumnName, ''Equality'' AS IndexColumnType + FROM (VALUES (CONVERT(XML, N'''' + REPLACE((SELECT CAST(id_inner.equality_columns AS nvarchar(max)) FOR XML PATH('''')), N'','', N'''') + N''''))) x(n) + CROSS APPLY n.nodes(''x'') node(v) + UNION ALL + SELECT LTRIM(RTRIM(v.value(N''(./text())[1]'', ''varchar(max)''))) AS ColumnName, ''Inequality'' AS IndexColumnType + FROM (VALUES (CONVERT(XML, N'''' + REPLACE((SELECT CAST(id_inner.inequality_columns AS nvarchar(max)) FOR XML PATH('''')), N'','', N'''') + N''''))) x(n) + CROSS APPLY n.nodes(''x'') node(v) + UNION ALL + SELECT LTRIM(RTRIM(v.value(''(./text())[1]'', ''varchar(max)''))) AS ColumnName, ''Included'' AS IndexColumnType + FROM (VALUES (CONVERT(XML, N'''' + REPLACE((SELECT CAST(id_inner.included_columns AS nvarchar(max)) FOR XML PATH('''')), N'','', N'''') + N''''))) x(n) + CROSS APPLY n.nodes(''x'') node(v) + )AS cn_inner' + + /*split the string otherwise dsql cuts some of it out*/ + ' JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns AS co ON co.object_id = id_inner.object_id AND ''['' + co.name + '']'' = cn_inner.ColumnName + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.types AS ty ON ty.user_type_id = co.user_type_id + WHERE id_inner.index_handle = id.index_handle + AND id_inner.object_id = id.object_id + AND cn_inner.IndexColumnType = cn.IndexColumnType + FOR XML PATH('''') + ),1,1,'''') AS ReplaceColumnNames + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_details AS id + CROSS APPLY( + SELECT LTRIM(RTRIM(v.value(''(./text())[1]'', ''varchar(max)''))) AS ColumnName, ''Equality'' AS IndexColumnType + FROM (VALUES (CONVERT(XML, N'''' + REPLACE((SELECT CAST(id.equality_columns AS nvarchar(max)) FOR XML PATH('''')), N'','', N'''') + N''''))) x(n) + CROSS APPLY n.nodes(''x'') node(v) + UNION ALL + SELECT LTRIM(RTRIM(v.value(''(./text())[1]'', ''varchar(max)''))) AS ColumnName, ''Inequality'' AS IndexColumnType + FROM (VALUES (CONVERT(XML, N'''' + REPLACE((SELECT CAST(id.inequality_columns AS nvarchar(max)) FOR XML PATH('''')), N'','', N'''') + N''''))) x(n) + CROSS APPLY n.nodes(''x'') node(v) + UNION ALL + SELECT LTRIM(RTRIM(v.value(''(./text())[1]'', ''varchar(max)''))) AS ColumnName, ''Included'' AS IndexColumnType + FROM (VALUES (CONVERT(XML, N'''' + REPLACE((SELECT CAST(id.included_columns AS nvarchar(max)) FOR XML PATH('''')), N'','', N'''') + N''''))) x(n) + CROSS APPLY n.nodes(''x'') node(v) + )AS cn + GROUP BY id.index_handle,id.object_id,cn.IndexColumnType + ) + SELECT id.database_id, id.object_id, @i_DatabaseName, sc.[name], so.[name], id.statement , gs.avg_total_user_cost, + gs.avg_user_impact, gs.user_seeks, gs.user_scans, gs.unique_compiles, id.equality_columns, id.inequality_columns, id.included_columns, + ( + SELECT ColumnNamesWithDataTypes.ReplaceColumnNames + FROM ColumnNamesWithDataTypes WHERE ColumnNamesWithDataTypes.index_handle = id.index_handle + AND ColumnNamesWithDataTypes.object_id = id.object_id + AND ColumnNamesWithDataTypes.IndexColumnType = ''Equality'' + ) AS equality_columns_with_data_type + ,( + SELECT ColumnNamesWithDataTypes.ReplaceColumnNames + FROM ColumnNamesWithDataTypes WHERE ColumnNamesWithDataTypes.index_handle = id.index_handle + AND ColumnNamesWithDataTypes.object_id = id.object_id + AND ColumnNamesWithDataTypes.IndexColumnType = ''Inequality'' + ) AS inequality_columns_with_data_type + ,( + SELECT ColumnNamesWithDataTypes.ReplaceColumnNames + FROM ColumnNamesWithDataTypes WHERE ColumnNamesWithDataTypes.index_handle = id.index_handle + AND ColumnNamesWithDataTypes.object_id = id.object_id + AND ColumnNamesWithDataTypes.IndexColumnType = ''Included'' + ) AS included_columns_with_data_type ' + /* Github #2780 BGO Removing SQL Server 2019's new missing index sample plan because it doesn't work yet: + IF NOT EXISTS (SELECT * FROM sys.all_objects WHERE name = 'dm_db_missing_index_group_stats_query') + */ + SET @dsql = @dsql + N' , NULL AS sample_query_plan ' + /* Github #2780 BGO Removing SQL Server 2019's new missing index sample plan because it doesn't work yet: + ELSE + BEGIN + SET @dsql = @dsql + N' , sample_query_plan = (SELECT TOP 1 p.query_plan + FROM sys.dm_db_missing_index_group_stats gs + CROSS APPLY (SELECT TOP 1 s.plan_handle + FROM sys.dm_db_missing_index_group_stats_query q + INNER JOIN sys.dm_exec_query_stats s ON q.query_plan_hash = s.query_plan_hash + WHERE gs.group_handle = q.group_handle + ORDER BY (q.user_seeks + q.user_scans) DESC, s.total_logical_reads DESC) q2 + CROSS APPLY sys.dm_exec_query_plan(q2.plan_handle) p + WHERE gs.group_handle = gs.group_handle) ' + END + */ + - /* Query Stats - If we're within 10 seconds of our projected finish time, do the plan cache analysis. - CheckID 18 */ - IF DATEDIFF(ss, @FinishSampleTime, SYSDATETIMEOFFSET()) > 10 AND @CheckProcedureCache = 1 - BEGIN - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 18',10,1) WITH NOWAIT; - END + SET @dsql = @dsql + N'FROM ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_groups ig + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_details id ON ig.index_handle = id.index_handle + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_group_stats gs ON ig.index_group_handle = gs.group_handle + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects so on + id.object_id=so.object_id + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas sc on + so.schema_id=sc.schema_id + WHERE id.database_id = ' + CAST(@DatabaseID AS NVARCHAR(30)) + ' + ' + CASE WHEN @ObjectID IS NULL THEN N'' + ELSE N'and id.object_id=' + CAST(@ObjectID AS NVARCHAR(30)) + END + + N'OPTION (RECOMPILE);'; - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (18, 210, 'Query Stats', 'Plan Cache Analysis Skipped', 'http://www.BrentOzar.com/go/topqueries', - 'Due to excessive load, the plan cache analysis was skipped. To override this, use @ExpertMode = 1.'); + IF @dsql IS NULL + RAISERROR('@dsql is null',16,1); + IF @Debug = 1 + BEGIN + PRINT SUBSTRING(@dsql, 0, 4000); + PRINT SUBSTRING(@dsql, 4000, 8000); + PRINT SUBSTRING(@dsql, 8000, 12000); + PRINT SUBSTRING(@dsql, 12000, 16000); + PRINT SUBSTRING(@dsql, 16000, 20000); + PRINT SUBSTRING(@dsql, 20000, 24000); + PRINT SUBSTRING(@dsql, 24000, 28000); + PRINT SUBSTRING(@dsql, 28000, 32000); + PRINT SUBSTRING(@dsql, 32000, 36000); + PRINT SUBSTRING(@dsql, 36000, 40000); + END; + INSERT #MissingIndexes ( [database_id], [object_id], [database_name], [schema_name], [table_name], [statement], avg_total_user_cost, + avg_user_impact, user_seeks, user_scans, unique_compiles, equality_columns, + inequality_columns, included_columns, equality_columns_with_data_type, inequality_columns_with_data_type, + included_columns_with_data_type, sample_query_plan) + EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; - END; - ELSE IF @CheckProcedureCache = 1 - BEGIN + SET @dsql = N' + SELECT DB_ID(N' + QUOTENAME(@DatabaseName,'''') + N') AS [database_id], + @i_DatabaseName AS database_name, + s.name, + fk_object.name AS foreign_key_name, + parent_object.[object_id] AS parent_object_id, + parent_object.name AS parent_object_name, + referenced_object.[object_id] AS referenced_object_id, + referenced_object.name AS referenced_object_name, + fk.is_disabled, + fk.is_not_trusted, + fk.is_not_for_replication, + parent.fk_columns, + referenced.fk_columns, + [update_referential_action_desc], + [delete_referential_action_desc] + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.foreign_keys fk + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects fk_object ON fk.object_id=fk_object.object_id + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects parent_object ON fk.parent_object_id=parent_object.object_id + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects referenced_object ON fk.referenced_object_id=referenced_object.object_id + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s ON fk.schema_id=s.schema_id + CROSS APPLY ( SELECT STUFF( (SELECT N'', '' + c_parent.name AS fk_columns + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.foreign_key_columns fkc + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns c_parent ON fkc.parent_object_id=c_parent.[object_id] + AND fkc.parent_column_id=c_parent.column_id + WHERE fk.parent_object_id=fkc.parent_object_id + AND fk.[object_id]=fkc.constraint_object_id + ORDER BY fkc.constraint_column_id + FOR XML PATH('''') , + TYPE).value(''.'', ''nvarchar(max)''), 1, 1, '''')/*This is how we remove the first comma*/ ) parent ( fk_columns ) + CROSS APPLY ( SELECT STUFF( (SELECT N'', '' + c_referenced.name AS fk_columns + FROM ' + QUOTENAME(@DatabaseName) + N'.sys. foreign_key_columns fkc + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns c_referenced ON fkc.referenced_object_id=c_referenced.[object_id] + AND fkc.referenced_column_id=c_referenced.column_id + WHERE fk.referenced_object_id=fkc.referenced_object_id + and fk.[object_id]=fkc.constraint_object_id + ORDER BY fkc.constraint_column_id /*order by col name, we don''t have anything better*/ + FOR XML PATH('''') , + TYPE).value(''.'', ''nvarchar(max)''), 1, 1, '''') ) referenced ( fk_columns ) + ' + CASE WHEN @ObjectID IS NOT NULL THEN + 'WHERE fk.parent_object_id=' + CAST(@ObjectID AS NVARCHAR(30)) + N' OR fk.referenced_object_id=' + CAST(@ObjectID AS NVARCHAR(30)) + N' ' + ELSE N' ' END + ' + ORDER BY parent_object_name, foreign_key_name + OPTION (RECOMPILE);'; + IF @dsql IS NULL + RAISERROR('@dsql is null',16,1); + RAISERROR (N'Inserting data into #ForeignKeys',0,1) WITH NOWAIT; + IF @Debug = 1 + BEGIN + PRINT SUBSTRING(@dsql, 0, 4000); + PRINT SUBSTRING(@dsql, 4000, 8000); + PRINT SUBSTRING(@dsql, 8000, 12000); + PRINT SUBSTRING(@dsql, 12000, 16000); + PRINT SUBSTRING(@dsql, 16000, 20000); + PRINT SUBSTRING(@dsql, 20000, 24000); + PRINT SUBSTRING(@dsql, 24000, 28000); + PRINT SUBSTRING(@dsql, 28000, 32000); + PRINT SUBSTRING(@dsql, 32000, 36000); + PRINT SUBSTRING(@dsql, 36000, 40000); + END; + INSERT #ForeignKeys ( [database_id], [database_name], [schema_name], foreign_key_name, parent_object_id,parent_object_name, referenced_object_id, referenced_object_name, + is_disabled, is_not_trusted, is_not_for_replication, parent_fk_columns, referenced_fk_columns, + [update_referential_action_desc], [delete_referential_action_desc] ) + EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; - RAISERROR('@CheckProcedureCache = 1, capturing second pass of plan cache',10,1) WITH NOWAIT; - /* Populate #QueryStats. SQL 2005 doesn't have query hash or query plan hash. */ - IF @@VERSION LIKE 'Microsoft SQL Server 2005%' + IF @SkipStatistics = 0 /* AND DB_NAME() = @DatabaseName /* Can only get stats in the current database - see https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/1947 */ */ BEGIN - IF @FilterPlansByDatabase IS NULL - BEGIN - SET @StringToExecute = N'INSERT INTO #QueryStats ([sql_handle], Pass, SampleTime, statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, Points) - SELECT [sql_handle], 2 AS Pass, SYSDATETIMEOFFSET(), statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, NULL AS query_hash, NULL AS query_plan_hash, 0 - FROM sys.dm_exec_query_stats qs - WHERE qs.last_execution_time >= @StartSampleTimeText;'; - END; - ELSE - BEGIN - SET @StringToExecute = N'INSERT INTO #QueryStats ([sql_handle], Pass, SampleTime, statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, Points) - SELECT [sql_handle], 2 AS Pass, SYSDATETIMEOFFSET(), statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, NULL AS query_hash, NULL AS query_plan_hash, 0 - FROM sys.dm_exec_query_stats qs - CROSS APPLY sys.dm_exec_plan_attributes(qs.plan_handle) AS attr - INNER JOIN #FilterPlansByDatabase dbs ON CAST(attr.value AS INT) = dbs.DatabaseID - WHERE qs.last_execution_time >= @StartSampleTimeText - AND attr.attribute = ''dbid'';'; - END; + IF ((PARSENAME(@SQLServerProductVersion, 4) >= 12) + OR (PARSENAME(@SQLServerProductVersion, 4) = 11 AND PARSENAME(@SQLServerProductVersion, 2) >= 3000) + OR (PARSENAME(@SQLServerProductVersion, 4) = 10 AND PARSENAME(@SQLServerProductVersion, 3) = 50 AND PARSENAME(@SQLServerProductVersion, 2) >= 2500)) + BEGIN + RAISERROR (N'Gathering Statistics Info With Newer Syntax.',0,1) WITH NOWAIT; + SET @dsql=N'USE ' + @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, + 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) + 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, + ISNULL(i.name, ''System Or User Statistic'') AS index_name, + ca.column_names AS column_names, + s.name AS statistics_name, + CONVERT(DATETIME, ddsp.last_updated) AS last_statistics_update, + DATEDIFF(DAY, ddsp.last_updated, GETDATE()) AS days_since_last_stats_update, + ddsp.rows, + ddsp.rows_sampled, + CAST(ddsp.rows_sampled / ( 1. * NULLIF(ddsp.rows, 0) ) * 100 AS DECIMAL(18, 1)) AS percent_sampled, + ddsp.steps AS histogram_steps, + ddsp.modification_counter, + CASE WHEN ddsp.modification_counter > 0 + THEN CAST(ddsp.modification_counter / ( 1. * NULLIF(ddsp.rows, 0) ) * 100 AS DECIMAL(18, 1)) + ELSE ddsp.modification_counter + END AS percent_modifications, + CASE WHEN ddsp.rows < 500 THEN 500 + ELSE CAST(( ddsp.rows * .20 ) + 500 AS INT) + 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 + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.stats AS s + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects obj + ON s.object_id = obj.object_id + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas sch + ON sch.schema_id = obj.schema_id + LEFT JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.indexes AS i + ON i.object_id = s.object_id + AND i.index_id = s.stats_id + OUTER APPLY ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_stats_properties(s.object_id, s.stats_id) AS ddsp + CROSS APPLY ( SELECT STUFF((SELECT '', '' + c.name + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.stats_columns AS sc + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns AS c + ON sc.column_id = c.column_id AND sc.object_id = c.object_id + WHERE sc.stats_id = s.stats_id AND sc.object_id = s.object_id + ORDER BY sc.stats_column_id + FOR XML PATH(''''), TYPE).value(''.'', ''nvarchar(max)''), 1, 2, '''') + ) ca (column_names) + WHERE obj.is_ms_shipped = 0 + OPTION (RECOMPILE);'; + + IF @dsql IS NULL + RAISERROR('@dsql is null',16,1); + + RAISERROR (N'Inserting data into #Statistics',0,1) WITH NOWAIT; + IF @Debug = 1 + BEGIN + PRINT SUBSTRING(@dsql, 0, 4000); + PRINT SUBSTRING(@dsql, 4000, 8000); + PRINT SUBSTRING(@dsql, 8000, 12000); + PRINT SUBSTRING(@dsql, 12000, 16000); + PRINT SUBSTRING(@dsql, 16000, 20000); + PRINT SUBSTRING(@dsql, 20000, 24000); + PRINT SUBSTRING(@dsql, 24000, 28000); + PRINT SUBSTRING(@dsql, 28000, 32000); + PRINT SUBSTRING(@dsql, 32000, 36000); + PRINT SUBSTRING(@dsql, 36000, 40000); + END; + + EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; END; - ELSE + ELSE BEGIN - IF @FilterPlansByDatabase IS NULL - BEGIN - SET @StringToExecute = N'INSERT INTO #QueryStats ([sql_handle], Pass, SampleTime, statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, Points) - SELECT [sql_handle], 2 AS Pass, SYSDATETIMEOFFSET(), statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, 0 - FROM sys.dm_exec_query_stats qs - WHERE qs.last_execution_time >= @StartSampleTimeText'; - END; - ELSE - BEGIN - SET @StringToExecute = N'INSERT INTO #QueryStats ([sql_handle], Pass, SampleTime, statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, Points) - SELECT [sql_handle], 2 AS Pass, SYSDATETIMEOFFSET(), statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, 0 - FROM sys.dm_exec_query_stats qs - CROSS APPLY sys.dm_exec_plan_attributes(qs.plan_handle) AS attr - INNER JOIN #FilterPlansByDatabase dbs ON CAST(attr.value AS INT) = dbs.DatabaseID - WHERE qs.last_execution_time >= @StartSampleTimeText - AND attr.attribute = ''dbid'';'; - END; - END; - /* Old version pre-2016/06/13: - IF @@VERSION LIKE 'Microsoft SQL Server 2005%' - SET @StringToExecute = N'INSERT INTO #QueryStats ([sql_handle], Pass, SampleTime, statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, Points) - SELECT [sql_handle], 2 AS Pass, SYSDATETIMEOFFSET(), statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, NULL AS query_hash, NULL AS query_plan_hash, 0 - FROM sys.dm_exec_query_stats qs - WHERE qs.last_execution_time >= @StartSampleTimeText;'; - ELSE - SET @StringToExecute = N'INSERT INTO #QueryStats ([sql_handle], Pass, SampleTime, statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, Points) - SELECT [sql_handle], 2 AS Pass, SYSDATETIMEOFFSET(), statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, 0 - FROM sys.dm_exec_query_stats qs - WHERE qs.last_execution_time >= @StartSampleTimeText;'; - */ - SET @ParmDefinitions = N'@StartSampleTimeText NVARCHAR(100)'; - SET @Parm1 = CONVERT(NVARCHAR(100), CAST(@StartSampleTime AS DATETIME), 127); - - EXECUTE sp_executesql @StringToExecute, @ParmDefinitions, @StartSampleTimeText = @Parm1; - - RAISERROR('@CheckProcedureCache = 1, totaling up plan cache metrics',10,1) WITH NOWAIT; - - /* Get the totals for the entire plan cache */ - INSERT INTO #QueryStats (Pass, SampleTime, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time) - SELECT 0 AS Pass, SYSDATETIMEOFFSET(), SUM(execution_count), SUM(total_worker_time), SUM(total_physical_reads), SUM(total_logical_writes), SUM(total_logical_reads), SUM(total_clr_time), SUM(total_elapsed_time), MIN(creation_time) - FROM sys.dm_exec_query_stats qs; - - - RAISERROR('@CheckProcedureCache = 1, so analyzing execution plans',10,1) WITH NOWAIT; - /* - Pick the most resource-intensive queries to review. Update the Points field - in #QueryStats - if a query is in the top 10 for logical reads, CPU time, - duration, or execution, add 1 to its points. - */ - WITH qsTop AS ( - SELECT TOP 10 qsNow.ID - FROM #QueryStats qsNow - INNER JOIN #QueryStats qsFirst ON qsNow.[sql_handle] = qsFirst.[sql_handle] AND qsNow.statement_start_offset = qsFirst.statement_start_offset AND qsNow.statement_end_offset = qsFirst.statement_end_offset AND qsNow.plan_generation_num = qsFirst.plan_generation_num AND qsNow.plan_handle = qsFirst.plan_handle AND qsFirst.Pass = 1 - WHERE qsNow.total_elapsed_time > qsFirst.total_elapsed_time - AND qsNow.Pass = 2 - AND qsNow.total_elapsed_time - qsFirst.total_elapsed_time > 1000000 /* Only queries with over 1 second of runtime */ - ORDER BY (qsNow.total_elapsed_time - COALESCE(qsFirst.total_elapsed_time, 0)) DESC) - UPDATE #QueryStats - SET Points = Points + 1 - FROM #QueryStats qs - INNER JOIN qsTop ON qs.ID = qsTop.ID; - - WITH qsTop AS ( - SELECT TOP 10 qsNow.ID - FROM #QueryStats qsNow - INNER JOIN #QueryStats qsFirst ON qsNow.[sql_handle] = qsFirst.[sql_handle] AND qsNow.statement_start_offset = qsFirst.statement_start_offset AND qsNow.statement_end_offset = qsFirst.statement_end_offset AND qsNow.plan_generation_num = qsFirst.plan_generation_num AND qsNow.plan_handle = qsFirst.plan_handle AND qsFirst.Pass = 1 - WHERE qsNow.total_logical_reads > qsFirst.total_logical_reads - AND qsNow.Pass = 2 - AND qsNow.total_logical_reads - qsFirst.total_logical_reads > 1000 /* Only queries with over 1000 reads */ - ORDER BY (qsNow.total_logical_reads - COALESCE(qsFirst.total_logical_reads, 0)) DESC) - UPDATE #QueryStats - SET Points = Points + 1 - FROM #QueryStats qs - INNER JOIN qsTop ON qs.ID = qsTop.ID; - - WITH qsTop AS ( - SELECT TOP 10 qsNow.ID - FROM #QueryStats qsNow - INNER JOIN #QueryStats qsFirst ON qsNow.[sql_handle] = qsFirst.[sql_handle] AND qsNow.statement_start_offset = qsFirst.statement_start_offset AND qsNow.statement_end_offset = qsFirst.statement_end_offset AND qsNow.plan_generation_num = qsFirst.plan_generation_num AND qsNow.plan_handle = qsFirst.plan_handle AND qsFirst.Pass = 1 - WHERE qsNow.total_worker_time > qsFirst.total_worker_time - AND qsNow.Pass = 2 - AND qsNow.total_worker_time - qsFirst.total_worker_time > 1000000 /* Only queries with over 1 second of worker time */ - ORDER BY (qsNow.total_worker_time - COALESCE(qsFirst.total_worker_time, 0)) DESC) - UPDATE #QueryStats - SET Points = Points + 1 - FROM #QueryStats qs - INNER JOIN qsTop ON qs.ID = qsTop.ID; - - WITH qsTop AS ( - SELECT TOP 10 qsNow.ID - FROM #QueryStats qsNow - INNER JOIN #QueryStats qsFirst ON qsNow.[sql_handle] = qsFirst.[sql_handle] AND qsNow.statement_start_offset = qsFirst.statement_start_offset AND qsNow.statement_end_offset = qsFirst.statement_end_offset AND qsNow.plan_generation_num = qsFirst.plan_generation_num AND qsNow.plan_handle = qsFirst.plan_handle AND qsFirst.Pass = 1 - WHERE qsNow.execution_count > qsFirst.execution_count - AND qsNow.Pass = 2 - AND (qsNow.total_elapsed_time - qsFirst.total_elapsed_time > 1000000 /* Only queries with over 1 second of runtime */ - OR qsNow.total_logical_reads - qsFirst.total_logical_reads > 1000 /* Only queries with over 1000 reads */ - OR qsNow.total_worker_time - qsFirst.total_worker_time > 1000000 /* Only queries with over 1 second of worker time */) - ORDER BY (qsNow.execution_count - COALESCE(qsFirst.execution_count, 0)) DESC) - UPDATE #QueryStats - SET Points = Points + 1 - FROM #QueryStats qs - INNER JOIN qsTop ON qs.ID = qsTop.ID; + RAISERROR (N'Gathering Statistics Info With Older Syntax.',0,1) WITH NOWAIT; + SET @dsql=N'USE ' + @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, 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) + 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, + ISNULL(i.name, ''System Or User Statistic'') AS index_name, + ca.column_names AS column_names, + s.name AS statistics_name, + CONVERT(DATETIME, STATS_DATE(s.object_id, s.stats_id)) AS last_statistics_update, + DATEDIFF(DAY, STATS_DATE(s.object_id, s.stats_id), GETDATE()) AS days_since_last_stats_update, + si.rowcnt, + si.rowmodctr, + CASE WHEN si.rowmodctr > 0 THEN CAST(si.rowmodctr / ( 1. * NULLIF(si.rowcnt, 0) ) * 100 AS DECIMAL(18, 1)) + ELSE si.rowmodctr + END AS percent_modifications, + CASE WHEN si.rowcnt < 500 THEN 500 + ELSE CAST(( si.rowcnt * .20 ) + 500 AS INT) + 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, + ' + + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' + THEN N's.has_filter, + s.filter_definition' + ELSE N'NULL AS has_filter, + NULL AS filter_definition' END + + N' + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.stats AS s + INNER HASH JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.sysindexes si + ON si.name = s.name AND s.object_id = si.id + INNER HASH JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects obj + ON s.object_id = obj.object_id + INNER HASH JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas sch + ON sch.schema_id = obj.schema_id + LEFT HASH JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.indexes AS i + ON i.object_id = s.object_id + AND i.index_id = s.stats_id + CROSS APPLY ( SELECT STUFF((SELECT '', '' + c.name + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.stats_columns AS sc + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns AS c + ON sc.column_id = c.column_id AND sc.object_id = c.object_id + WHERE sc.stats_id = s.stats_id AND sc.object_id = s.object_id + ORDER BY sc.stats_column_id + FOR XML PATH(''''), TYPE).value(''.'', ''nvarchar(max)''), 1, 2, '''') + ) ca (column_names) + WHERE obj.is_ms_shipped = 0 + AND si.rowcnt > 0 + OPTION (RECOMPILE);'; - /* Query Stats - Most Resource-Intensive Queries - CheckID 17 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 17',10,1) WITH NOWAIT; - END + IF @dsql IS NULL + RAISERROR('@dsql is null',16,1); - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, QueryText, QueryStatsNowID, QueryStatsFirstID, PlanHandle, QueryHash) - SELECT 17, 210, 'Query Stats', 'Most Resource-Intensive Queries', 'http://www.BrentOzar.com/go/topqueries', - 'Query stats during the sample:' + @LineFeed + - 'Executions: ' + CAST(qsNow.execution_count - (COALESCE(qsFirst.execution_count, 0)) AS NVARCHAR(100)) + @LineFeed + - 'Elapsed Time: ' + CAST(qsNow.total_elapsed_time - (COALESCE(qsFirst.total_elapsed_time, 0)) AS NVARCHAR(100)) + @LineFeed + - 'CPU Time: ' + CAST(qsNow.total_worker_time - (COALESCE(qsFirst.total_worker_time, 0)) AS NVARCHAR(100)) + @LineFeed + - 'Logical Reads: ' + CAST(qsNow.total_logical_reads - (COALESCE(qsFirst.total_logical_reads, 0)) AS NVARCHAR(100)) + @LineFeed + - 'Logical Writes: ' + CAST(qsNow.total_logical_writes - (COALESCE(qsFirst.total_logical_writes, 0)) AS NVARCHAR(100)) + @LineFeed + - 'CLR Time: ' + CAST(qsNow.total_clr_time - (COALESCE(qsFirst.total_clr_time, 0)) AS NVARCHAR(100)) + @LineFeed + - @LineFeed + @LineFeed + 'Query stats since ' + CONVERT(NVARCHAR(100), qsNow.creation_time ,121) + @LineFeed + - 'Executions: ' + CAST(qsNow.execution_count AS NVARCHAR(100)) + - CASE qsTotal.execution_count WHEN 0 THEN '' ELSE (' - Percent of Server Total: ' + CAST(CAST(100.0 * qsNow.execution_count / qsTotal.execution_count AS DECIMAL(6,2)) AS NVARCHAR(100)) + '%') END + @LineFeed + - 'Elapsed Time: ' + CAST(qsNow.total_elapsed_time AS NVARCHAR(100)) + - CASE qsTotal.total_elapsed_time WHEN 0 THEN '' ELSE (' - Percent of Server Total: ' + CAST(CAST(100.0 * qsNow.total_elapsed_time / qsTotal.total_elapsed_time AS DECIMAL(6,2)) AS NVARCHAR(100)) + '%') END + @LineFeed + - 'CPU Time: ' + CAST(qsNow.total_worker_time AS NVARCHAR(100)) + - CASE qsTotal.total_worker_time WHEN 0 THEN '' ELSE (' - Percent of Server Total: ' + CAST(CAST(100.0 * qsNow.total_worker_time / qsTotal.total_worker_time AS DECIMAL(6,2)) AS NVARCHAR(100)) + '%') END + @LineFeed + - 'Logical Reads: ' + CAST(qsNow.total_logical_reads AS NVARCHAR(100)) + - CASE qsTotal.total_logical_reads WHEN 0 THEN '' ELSE (' - Percent of Server Total: ' + CAST(CAST(100.0 * qsNow.total_logical_reads / qsTotal.total_logical_reads AS DECIMAL(6,2)) AS NVARCHAR(100)) + '%') END + @LineFeed + - 'Logical Writes: ' + CAST(qsNow.total_logical_writes AS NVARCHAR(100)) + - CASE qsTotal.total_logical_writes WHEN 0 THEN '' ELSE (' - Percent of Server Total: ' + CAST(CAST(100.0 * qsNow.total_logical_writes / qsTotal.total_logical_writes AS DECIMAL(6,2)) AS NVARCHAR(100)) + '%') END + @LineFeed + - 'CLR Time: ' + CAST(qsNow.total_clr_time AS NVARCHAR(100)) + - CASE qsTotal.total_clr_time WHEN 0 THEN '' ELSE (' - Percent of Server Total: ' + CAST(CAST(100.0 * qsNow.total_clr_time / qsTotal.total_clr_time AS DECIMAL(6,2)) AS NVARCHAR(100)) + '%') END + @LineFeed + - --@LineFeed + @LineFeed + 'Query hash: ' + CAST(qsNow.query_hash AS NVARCHAR(100)) + @LineFeed + - --@LineFeed + @LineFeed + 'Query plan hash: ' + CAST(qsNow.query_plan_hash AS NVARCHAR(100)) + - @LineFeed AS Details, - 'See the URL for tuning tips on why this query may be consuming resources.' AS HowToStopIt, - qp.query_plan, - QueryText = SUBSTRING(st.text, - (qsNow.statement_start_offset / 2) + 1, - ((CASE qsNow.statement_end_offset - WHEN -1 THEN DATALENGTH(st.text) - ELSE qsNow.statement_end_offset - END - qsNow.statement_start_offset) / 2) + 1), - qsNow.ID AS QueryStatsNowID, - qsFirst.ID AS QueryStatsFirstID, - qsNow.plan_handle AS PlanHandle, - qsNow.query_hash - FROM #QueryStats qsNow - INNER JOIN #QueryStats qsTotal ON qsTotal.Pass = 0 - LEFT OUTER JOIN #QueryStats qsFirst ON qsNow.[sql_handle] = qsFirst.[sql_handle] AND qsNow.statement_start_offset = qsFirst.statement_start_offset AND qsNow.statement_end_offset = qsFirst.statement_end_offset AND qsNow.plan_generation_num = qsFirst.plan_generation_num AND qsNow.plan_handle = qsFirst.plan_handle AND qsFirst.Pass = 1 - CROSS APPLY sys.dm_exec_sql_text(qsNow.sql_handle) AS st - CROSS APPLY sys.dm_exec_query_plan(qsNow.plan_handle) AS qp - WHERE qsNow.Points > 0 AND st.text IS NOT NULL AND qp.query_plan IS NOT NULL; + RAISERROR (N'Inserting data into #Statistics',0,1) WITH NOWAIT; + IF @Debug = 1 + BEGIN + PRINT SUBSTRING(@dsql, 0, 4000); + PRINT SUBSTRING(@dsql, 4000, 8000); + PRINT SUBSTRING(@dsql, 8000, 12000); + PRINT SUBSTRING(@dsql, 12000, 16000); + PRINT SUBSTRING(@dsql, 16000, 20000); + PRINT SUBSTRING(@dsql, 20000, 24000); + PRINT SUBSTRING(@dsql, 24000, 28000); + PRINT SUBSTRING(@dsql, 28000, 32000); + PRINT SUBSTRING(@dsql, 32000, 36000); + PRINT SUBSTRING(@dsql, 36000, 40000); + END; + + EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; + END; - UPDATE #BlitzFirstResults - SET DatabaseID = CAST(attr.value AS INT), - DatabaseName = DB_NAME(CAST(attr.value AS INT)) - FROM #BlitzFirstResults - CROSS APPLY sys.dm_exec_plan_attributes(#BlitzFirstResults.PlanHandle) AS attr - WHERE attr.attribute = 'dbid'; + END; + IF (PARSENAME(@SQLServerProductVersion, 4) >= 10) + BEGIN + RAISERROR (N'Gathering Computed Column Info.',0,1) WITH NOWAIT; + SET @dsql=N'SELECT DB_ID(@i_DatabaseName) AS [database_id], + @i_DatabaseName AS database_name, + t.name AS table_name, + s.name AS schema_name, + c.name AS column_name, + cc.is_nullable, + cc.definition, + cc.uses_database_collation, + cc.is_persisted, + cc.is_computed, + CASE WHEN cc.definition LIKE ''%|].|[%'' ESCAPE ''|'' THEN 1 ELSE 0 END AS is_function, + ''ALTER TABLE '' + QUOTENAME(s.name) + ''.'' + QUOTENAME(t.name) + + '' ADD '' + QUOTENAME(c.name) + '' AS '' + cc.definition + + CASE WHEN is_persisted = 1 THEN '' PERSISTED'' ELSE '''' END + '';'' COLLATE DATABASE_DEFAULT AS [column_definition] + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.computed_columns AS cc + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns AS c + ON cc.object_id = c.object_id + AND cc.column_id = c.column_id + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.tables AS t + ON t.object_id = cc.object_id + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s + ON s.schema_id = t.schema_id + OPTION (RECOMPILE);'; - END; /* IF DATEDIFF(ss, @FinishSampleTime, SYSDATETIMEOFFSET()) > 10 AND @CheckProcedureCache = 1 */ + IF @dsql IS NULL RAISERROR('@dsql is null',16,1); + INSERT #ComputedColumns + ( database_id, [database_name], table_name, schema_name, column_name, is_nullable, definition, + uses_database_collation, is_persisted, is_computed, is_function, column_definition ) + EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; - RAISERROR('Analyzing changes between first and second passes of DMVs',10,1) WITH NOWAIT; + END; + + RAISERROR (N'Gathering Trace Flag Information',0,1) WITH NOWAIT; + INSERT #TraceStatus + EXEC ('DBCC TRACESTATUS(-1) WITH NO_INFOMSGS'); - /* Wait Stats - CheckID 6 */ - /* Compare the current wait stats to the sample we took at the start, and insert the top 10 waits. */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 6',10,1) WITH NOWAIT; - END + IF (PARSENAME(@SQLServerProductVersion, 4) >= 13) + BEGIN + RAISERROR (N'Gathering Temporal Table Info',0,1) WITH NOWAIT; + SET @dsql=N'SELECT ' + QUOTENAME(@DatabaseName,'''') + N' AS database_name, + DB_ID(N' + QUOTENAME(@DatabaseName,'''') + N') AS [database_id], + s.name AS schema_name, + t.name AS table_name, + oa.hsn as history_schema_name, + oa.htn AS history_table_name, + c1.name AS start_column_name, + c2.name AS end_column_name, + p.name AS period_name + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.periods AS p + INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.tables AS t + ON p.object_id = t.object_id + INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns AS c1 + ON t.object_id = c1.object_id + AND p.start_column_id = c1.column_id + INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns AS c2 + ON t.object_id = c2.object_id + AND p.end_column_id = c2.column_id + INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s + ON t.schema_id = s.schema_id + CROSS APPLY ( SELECT s2.name as hsn, t2.name htn + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.tables AS t2 + INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s2 + ON t2.schema_id = s2.schema_id + WHERE t2.object_id = t.history_table_id + AND t2.temporal_type = 1 /*History table*/ ) AS oa + WHERE t.temporal_type IN ( 2, 4 ) /*BOL currently points to these types, but has no definition for 4*/ + OPTION (RECOMPILE); + '; + + IF @dsql IS NULL + RAISERROR('@dsql is null',16,1); + + INSERT #TemporalTables ( database_name, database_id, schema_name, table_name, history_schema_name, + history_table_name, start_column_name, end_column_name, period_name ) + + EXEC sp_executesql @dsql; - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, DetailsInt) - SELECT TOP 10 6 AS CheckID, - 200 AS Priority, - 'Wait Stats' AS FindingGroup, - wNow.wait_type AS Finding, /* IF YOU CHANGE THIS, STUFF WILL BREAK. Other checks look for wait type names in the Finding field. See checks 11, 12 as example. */ - N'https://www.sqlskills.com/help/waits/' + LOWER(wNow.wait_type) + '/' AS URL, - 'For ' + CAST(((wNow.wait_time_ms - COALESCE(wBase.wait_time_ms,0)) / 1000) AS NVARCHAR(100)) + ' seconds over the last ' + CASE @Seconds WHEN 0 THEN (CAST(DATEDIFF(dd,@StartSampleTime,@FinishSampleTime) AS NVARCHAR(10)) + ' days') ELSE (CAST(@Seconds AS NVARCHAR(10)) + ' seconds') END + ', SQL Server was waiting on this particular bottleneck.' + @LineFeed + @LineFeed AS Details, - 'See the URL for more details on how to mitigate this wait type.' AS HowToStopIt, - ((wNow.wait_time_ms - COALESCE(wBase.wait_time_ms,0)) / 1000) AS DetailsInt - FROM #WaitStats wNow - LEFT OUTER JOIN #WaitStats wBase ON wNow.wait_type = wBase.wait_type AND wNow.SampleTime > wBase.SampleTime - WHERE wNow.wait_time_ms > (wBase.wait_time_ms + (.5 * (DATEDIFF(ss,@StartSampleTime,@FinishSampleTime)) * 1000)) /* Only look for things we've actually waited on for half of the time or more */ - ORDER BY (wNow.wait_time_ms - COALESCE(wBase.wait_time_ms,0)) DESC; + SET @dsql=N'SELECT DB_ID(@i_DatabaseName) AS [database_id], + @i_DatabaseName AS database_name, + t.name AS table_name, + s.name AS schema_name, + cc.name AS constraint_name, + cc.is_disabled, + cc.definition, + cc.uses_database_collation, + cc.is_not_trusted, + CASE WHEN cc.definition LIKE ''%|].|[%'' ESCAPE ''|'' THEN 1 ELSE 0 END AS is_function, + ''ALTER TABLE '' + QUOTENAME(s.name) + ''.'' + QUOTENAME(t.name) + + '' ADD CONSTRAINT '' + QUOTENAME(cc.name) + '' CHECK '' + cc.definition + '';'' COLLATE DATABASE_DEFAULT AS [column_definition] + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.check_constraints AS cc + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.tables AS t + ON t.object_id = cc.parent_object_id + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s + ON s.schema_id = t.schema_id + OPTION (RECOMPILE);'; + + INSERT #CheckConstraints + ( database_id, [database_name], table_name, schema_name, constraint_name, is_disabled, definition, + uses_database_collation, is_not_trusted, is_function, column_definition ) + EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; - /* Server Performance - Poison Wait Detected - CheckID 30 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 30',10,1) WITH NOWAIT; - END - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, DetailsInt) - SELECT 30 AS CheckID, - 10 AS Priority, - 'Server Performance' AS FindingGroup, - 'Poison Wait Detected: ' + wNow.wait_type AS Finding, - N'http://www.brentozar.com/go/poison/#' + wNow.wait_type AS URL, - 'For ' + CAST(((wNow.wait_time_ms - COALESCE(wBase.wait_time_ms,0)) / 1000) AS NVARCHAR(100)) + ' seconds over the last ' + CASE @Seconds WHEN 0 THEN (CAST(DATEDIFF(dd,@StartSampleTime,@FinishSampleTime) AS NVARCHAR(10)) + ' days') ELSE (CAST(@Seconds AS NVARCHAR(10)) + ' seconds') END + ', SQL Server was waiting on this particular bottleneck.' + @LineFeed + @LineFeed AS Details, - 'See the URL for more details on how to mitigate this wait type.' AS HowToStopIt, - ((wNow.wait_time_ms - COALESCE(wBase.wait_time_ms,0)) / 1000) AS DetailsInt - FROM #WaitStats wNow - LEFT OUTER JOIN #WaitStats wBase ON wNow.wait_type = wBase.wait_type AND wNow.SampleTime > wBase.SampleTime - WHERE wNow.wait_type IN ('IO_QUEUE_LIMIT', 'IO_RETRY', 'LOG_RATE_GOVERNOR', 'POOL_LOG_RATE_GOVERNOR', 'PREEMPTIVE_DEBUG', 'RESMGR_THROTTLED', 'RESOURCE_SEMAPHORE', 'RESOURCE_SEMAPHORE_QUERY_COMPILE','SE_REPL_CATCHUP_THROTTLE','SE_REPL_COMMIT_ACK','SE_REPL_COMMIT_TURN','SE_REPL_ROLLBACK_ACK','SE_REPL_SLOW_SECONDARY_THROTTLE','THREADPOOL') - AND wNow.wait_time_ms > (wBase.wait_time_ms + 1000); + SET @dsql=N'SELECT DB_ID(@i_DatabaseName) AS [database_id], + @i_DatabaseName AS database_name, + s.name AS missing_schema_name, + t.name AS missing_table_name, + i.name AS missing_index_name, + c.name AS missing_column_name + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.sql_expression_dependencies AS sed + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.tables AS t + ON t.object_id = sed.referenced_id + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s + ON t.schema_id = s.schema_id + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.indexes AS i + ON i.object_id = sed.referenced_id + AND i.index_id = sed.referencing_minor_id + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns AS c + ON c.object_id = sed.referenced_id + AND c.column_id = sed.referenced_minor_id + WHERE sed.referencing_class = 7 + AND sed.referenced_class = 1 + AND i.has_filter = 1 + AND NOT EXISTS ( SELECT 1/0 + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.index_columns AS ic + WHERE ic.index_id = sed.referencing_minor_id + AND ic.column_id = sed.referenced_minor_id + AND ic.object_id = sed.referenced_id ) + OPTION(RECOMPILE);' + INSERT #FilteredIndexes ( database_id, database_name, schema_name, table_name, index_name, column_name ) + EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; - /* Server Performance - Slow Data File Reads - CheckID 11 */ - IF EXISTS (SELECT * FROM #BlitzFirstResults WHERE Finding LIKE 'PAGEIOLATCH%') - BEGIN - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 11',10,1) WITH NOWAIT; - END - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, DatabaseID, DatabaseName) - SELECT TOP 10 11 AS CheckID, - 50 AS Priority, - 'Server Performance' AS FindingGroup, - 'Slow Data File Reads' AS Finding, - 'http://www.BrentOzar.com/go/slow/' AS URL, - 'Your server is experiencing PAGEIOLATCH% waits due to slow data file reads. This file is one of the reasons why.' + @LineFeed - + 'File: ' + fNow.PhysicalName + @LineFeed - + 'Number of reads during the sample: ' + CAST((fNow.num_of_reads - fBase.num_of_reads) AS NVARCHAR(20)) + @LineFeed - + 'Seconds spent waiting on storage for these reads: ' + CAST(((fNow.io_stall_read_ms - fBase.io_stall_read_ms) / 1000.0) AS NVARCHAR(20)) + @LineFeed - + 'Average read latency during the sample: ' + CAST(((fNow.io_stall_read_ms - fBase.io_stall_read_ms) / (fNow.num_of_reads - fBase.num_of_reads) ) AS NVARCHAR(20)) + ' milliseconds' + @LineFeed - + 'Microsoft guidance for data file read speed: 20ms or less.' + @LineFeed + @LineFeed AS Details, - 'See the URL for more details on how to mitigate this wait type.' AS HowToStopIt, - fNow.DatabaseID, - fNow.DatabaseName - FROM #FileStats fNow - INNER JOIN #FileStats fBase ON fNow.DatabaseID = fBase.DatabaseID AND fNow.FileID = fBase.FileID AND fNow.SampleTime > fBase.SampleTime AND fNow.num_of_reads > fBase.num_of_reads AND fNow.io_stall_read_ms > (fBase.io_stall_read_ms + 1000) - WHERE (fNow.io_stall_read_ms - fBase.io_stall_read_ms) / (fNow.num_of_reads - fBase.num_of_reads) >= @FileLatencyThresholdMS - AND fNow.TypeDesc = 'ROWS' - ORDER BY (fNow.io_stall_read_ms - fBase.io_stall_read_ms) / (fNow.num_of_reads - fBase.num_of_reads) DESC; - END; + END; + +END; +END TRY +BEGIN CATCH + RAISERROR (N'Failure populating temp tables.', 0,1) WITH NOWAIT; - /* Server Performance - Slow Log File Writes - CheckID 12 */ - IF EXISTS (SELECT * FROM #BlitzFirstResults WHERE Finding LIKE 'WRITELOG%') - BEGIN - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 12',10,1) WITH NOWAIT; - END + IF @dsql IS NOT NULL + BEGIN + SET @msg= 'Last @dsql: ' + @dsql; + RAISERROR(@msg, 0, 1) WITH NOWAIT; + END; - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, DatabaseID, DatabaseName) - SELECT TOP 10 12 AS CheckID, - 50 AS Priority, - 'Server Performance' AS FindingGroup, - 'Slow Log File Writes' AS Finding, - 'http://www.BrentOzar.com/go/slow/' AS URL, - 'Your server is experiencing WRITELOG waits due to slow log file writes. This file is one of the reasons why.' + @LineFeed - + 'File: ' + fNow.PhysicalName + @LineFeed - + 'Number of writes during the sample: ' + CAST((fNow.num_of_writes - fBase.num_of_writes) AS NVARCHAR(20)) + @LineFeed - + 'Seconds spent waiting on storage for these writes: ' + CAST(((fNow.io_stall_write_ms - fBase.io_stall_write_ms) / 1000.0) AS NVARCHAR(20)) + @LineFeed - + 'Average write latency during the sample: ' + CAST(((fNow.io_stall_write_ms - fBase.io_stall_write_ms) / (fNow.num_of_writes - fBase.num_of_writes) ) AS NVARCHAR(20)) + ' milliseconds' + @LineFeed - + 'Microsoft guidance for log file write speed: 3ms or less.' + @LineFeed + @LineFeed AS Details, - 'See the URL for more details on how to mitigate this wait type.' AS HowToStopIt, - fNow.DatabaseID, - fNow.DatabaseName - FROM #FileStats fNow - INNER JOIN #FileStats fBase ON fNow.DatabaseID = fBase.DatabaseID AND fNow.FileID = fBase.FileID AND fNow.SampleTime > fBase.SampleTime AND fNow.num_of_writes > fBase.num_of_writes AND fNow.io_stall_write_ms > (fBase.io_stall_write_ms + 1000) - WHERE (fNow.io_stall_write_ms - fBase.io_stall_write_ms) / (fNow.num_of_writes - fBase.num_of_writes) >= @FileLatencyThresholdMS - AND fNow.TypeDesc = 'LOG' - ORDER BY (fNow.io_stall_write_ms - fBase.io_stall_write_ms) / (fNow.num_of_writes - fBase.num_of_writes) DESC; - END; + SELECT @msg = @DatabaseName + N' database failed to process. ' + ERROR_MESSAGE(), @ErrorSeverity = ERROR_SEVERITY(), @ErrorState = ERROR_STATE(); + RAISERROR (@msg,@ErrorSeverity, @ErrorState )WITH NOWAIT; + + + WHILE @@trancount > 0 + ROLLBACK; + RETURN; +END CATCH; + FETCH NEXT FROM c1 INTO @DatabaseName; +END; +DEALLOCATE c1; - /* SQL Server Internal Maintenance - Log File Growing - CheckID 13 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 13',10,1) WITH NOWAIT; - END - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) - SELECT 13 AS CheckID, - 1 AS Priority, - 'SQL Server Internal Maintenance' AS FindingGroup, - 'Log File Growing' AS Finding, - 'http://www.BrentOzar.com/askbrent/file-growing/' AS URL, - 'Number of growths during the sample: ' + CAST(ps.value_delta AS NVARCHAR(20)) + @LineFeed - + 'Determined by sampling Perfmon counter ' + ps.object_name + ' - ' + ps.counter_name + @LineFeed AS Details, - 'Pre-grow data and log files during maintenance windows so that they do not grow during production loads. See the URL for more details.' AS HowToStopIt - FROM #PerfmonStats ps - WHERE ps.Pass = 2 - AND object_name = @ServiceName + ':Databases' - AND counter_name = 'Log Growths' - AND value_delta > 0; - /* SQL Server Internal Maintenance - Log File Shrinking - CheckID 14 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 14',10,1) WITH NOWAIT; - END - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) - SELECT 14 AS CheckID, - 1 AS Priority, - 'SQL Server Internal Maintenance' AS FindingGroup, - 'Log File Shrinking' AS Finding, - 'http://www.BrentOzar.com/askbrent/file-shrinking/' AS URL, - 'Number of shrinks during the sample: ' + CAST(ps.value_delta AS NVARCHAR(20)) + @LineFeed - + 'Determined by sampling Perfmon counter ' + ps.object_name + ' - ' + ps.counter_name + @LineFeed AS Details, - 'Pre-grow data and log files during maintenance windows so that they do not grow during production loads. See the URL for more details.' AS HowToStopIt - FROM #PerfmonStats ps - WHERE ps.Pass = 2 - AND object_name = @ServiceName + ':Databases' - AND counter_name = 'Log Shrinks' - AND value_delta > 0; - /* Query Problems - Compilations/Sec High - CheckID 15 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 15',10,1) WITH NOWAIT; - END +---------------------------------------- +--STEP 2: PREP THE TEMP TABLES +--EVERY QUERY AFTER THIS GOES AGAINST TEMP TABLES ONLY. +---------------------------------------- - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) - SELECT 15 AS CheckID, - 50 AS Priority, - 'Query Problems' AS FindingGroup, - 'Compilations/Sec High' AS Finding, - 'http://www.BrentOzar.com/askbrent/compilations/' AS URL, - 'Number of batch requests during the sample: ' + CAST(ps.value_delta AS NVARCHAR(20)) + @LineFeed - + 'Number of compilations during the sample: ' + CAST(psComp.value_delta AS NVARCHAR(20)) + @LineFeed - + 'For OLTP environments, Microsoft recommends that 90% of batch requests should hit the plan cache, and not be compiled from scratch. We are exceeding that threshold.' + @LineFeed AS Details, - 'To find the queries that are compiling, start with:' + @LineFeed - + 'sp_BlitzCache @SortOrder = ''recent compilations''' + @LineFeed - + 'If dynamic SQL or non-parameterized strings are involved, consider enabling Forced Parameterization. See the URL for more details.' AS HowToStopIt - FROM #PerfmonStats ps - INNER JOIN #PerfmonStats psComp ON psComp.Pass = 2 AND psComp.object_name = @ServiceName + ':SQL Statistics' AND psComp.counter_name = 'SQL Compilations/sec' AND psComp.value_delta > 0 - WHERE ps.Pass = 2 - AND ps.object_name = @ServiceName + ':SQL Statistics' - AND ps.counter_name = 'Batch Requests/sec' - AND ps.value_delta > (1000 * @Seconds) /* Ignore servers sitting idle */ - AND (psComp.value_delta * 10) > ps.value_delta; /* Compilations are more than 10% of batch requests per second */ +RAISERROR (N'Updating #IndexSanity.key_column_names',0,1) WITH NOWAIT; +UPDATE #IndexSanity +SET key_column_names = D1.key_column_names +FROM #IndexSanity si + CROSS APPLY ( SELECT RTRIM(STUFF( (SELECT N', ' + c.column_name + + N' {' + system_type_name + N' ' + + CASE max_length WHEN -1 THEN N'(max)' ELSE + CASE + WHEN system_type_name IN (N'char',N'varchar',N'binary',N'varbinary') THEN N'(' + CAST(max_length AS NVARCHAR(20)) + N')' + WHEN system_type_name IN (N'nchar',N'nvarchar') THEN N'(' + CAST(max_length/2 AS NVARCHAR(20)) + N')' + ELSE '' + END + END + + N'}' + AS col_definition + FROM #IndexColumns c + WHERE c.database_id= si.database_id + AND c.schema_name = si.schema_name + AND c.object_id = si.object_id + AND c.index_id = si.index_id + AND c.is_included_column = 0 /*Just Keys*/ + AND c.key_ordinal > 0 /*Ignore non-key columns, such as partitioning keys*/ + ORDER BY c.object_id, c.index_id, c.key_ordinal + FOR XML PATH('') ,TYPE).value('.', 'nvarchar(max)'), 1, 1, '')) + ) D1 ( key_column_names ); - /* Query Problems - Re-Compilations/Sec High - CheckID 16 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 16',10,1) WITH NOWAIT; - END +RAISERROR (N'Updating #IndexSanity.partition_key_column_name',0,1) WITH NOWAIT; +UPDATE #IndexSanity +SET partition_key_column_name = D1.partition_key_column_name +FROM #IndexSanity si + CROSS APPLY ( SELECT RTRIM(STUFF( (SELECT N', ' + c.column_name AS col_definition + FROM #IndexColumns c + WHERE c.database_id= si.database_id + AND c.schema_name = si.schema_name + AND c.object_id = si.object_id + AND c.index_id = si.index_id + AND c.partition_ordinal <> 0 /*Just Partitioned Keys*/ + ORDER BY c.object_id, c.index_id, c.key_ordinal + FOR XML PATH('') , TYPE).value('.', 'nvarchar(max)'), 1, 1,''))) D1 + ( partition_key_column_name ); - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) - SELECT 16 AS CheckID, - 50 AS Priority, - 'Query Problems' AS FindingGroup, - 'Re-Compilations/Sec High' AS Finding, - 'http://www.BrentOzar.com/askbrent/recompilations/' AS URL, - 'Number of batch requests during the sample: ' + CAST(ps.value_delta AS NVARCHAR(20)) + @LineFeed - + 'Number of recompilations during the sample: ' + CAST(psComp.value_delta AS NVARCHAR(20)) + @LineFeed - + 'More than 10% of our queries are being recompiled. This is typically due to statistics changing on objects.' + @LineFeed AS Details, - 'To find the queries that are being forced to recompile, start with:' + @LineFeed - + 'sp_BlitzCache @SortOrder = ''recent compilations''' + @LineFeed - + 'Examine those plans to find out which objects are changing so quickly that they hit the stats update threshold. See the URL for more details.' AS HowToStopIt - FROM #PerfmonStats ps - INNER JOIN #PerfmonStats psComp ON psComp.Pass = 2 AND psComp.object_name = @ServiceName + ':SQL Statistics' AND psComp.counter_name = 'SQL Re-Compilations/sec' AND psComp.value_delta > 0 - WHERE ps.Pass = 2 - AND ps.object_name = @ServiceName + ':SQL Statistics' - AND ps.counter_name = 'Batch Requests/sec' - AND ps.value_delta > (1000 * @Seconds) /* Ignore servers sitting idle */ - AND (psComp.value_delta * 10) > ps.value_delta; /* Recompilations are more than 10% of batch requests per second */ - - /* Table Problems - Forwarded Fetches/Sec High - CheckID 29 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 29',10,1) WITH NOWAIT; - END - - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) - SELECT 29 AS CheckID, - 40 AS Priority, - 'Table Problems' AS FindingGroup, - 'Forwarded Fetches/Sec High' AS Finding, - 'https://BrentOzar.com/go/fetch/' AS URL, - CAST(ps.value_delta AS NVARCHAR(20)) + ' forwarded fetches (from SQLServer:Access Methods counter)' + @LineFeed - + 'Check your heaps: they need to be rebuilt, or they need a clustered index applied.' + @LineFeed AS Details, - 'Rebuild your heaps. If you use Ola Hallengren maintenance scripts, those do not rebuild heaps by default: https://www.brentozar.com/archive/2016/07/fix-forwarded-records/' AS HowToStopIt - FROM #PerfmonStats ps - INNER JOIN #PerfmonStats psComp ON psComp.Pass = 2 AND psComp.object_name = @ServiceName + ':Access Methods' AND psComp.counter_name = 'Forwarded Records/sec' AND psComp.value_delta > 100 - WHERE ps.Pass = 2 - AND ps.object_name = @ServiceName + ':Access Methods' - AND ps.counter_name = 'Forwarded Records/sec' - AND ps.value_delta > (100 * @Seconds); /* Ignore servers sitting idle */ +RAISERROR (N'Updating #IndexSanity.key_column_names_with_sort_order',0,1) WITH NOWAIT; +UPDATE #IndexSanity +SET key_column_names_with_sort_order = D2.key_column_names_with_sort_order +FROM #IndexSanity si + CROSS APPLY ( SELECT RTRIM(STUFF( (SELECT N', ' + c.column_name + CASE c.is_descending_key + WHEN 1 THEN N' DESC' + ELSE N'' + END + + N' {' + system_type_name + N' ' + + CASE max_length WHEN -1 THEN N'(max)' ELSE + CASE + WHEN system_type_name IN (N'char',N'varchar',N'binary',N'varbinary') THEN N'(' + CAST(max_length AS NVARCHAR(20)) + N')' + WHEN system_type_name IN (N'nchar',N'nvarchar') THEN N'(' + CAST(max_length/2 AS NVARCHAR(20)) + N')' + ELSE '' + END + END + + N'}' + AS col_definition + FROM #IndexColumns c + WHERE c.database_id= si.database_id + AND c.schema_name = si.schema_name + AND c.object_id = si.object_id + AND c.index_id = si.index_id + AND c.is_included_column = 0 /*Just Keys*/ + AND c.key_ordinal > 0 /*Ignore non-key columns, such as partitioning keys*/ + ORDER BY c.object_id, c.index_id, c.key_ordinal + FOR XML PATH('') , TYPE).value('.', 'nvarchar(max)'), 1, 1, '')) + ) D2 ( key_column_names_with_sort_order ); - /* Check for temp objects with high forwarded fetches. - This has to be done as dynamic SQL because we have to execute OBJECT_NAME inside TempDB. */ - IF @@ROWCOUNT > 0 - BEGIN - SET @StringToExecute = N'USE tempdb; - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) - SELECT TOP 10 29 AS CheckID, - 40 AS Priority, - ''Table Problems'' AS FindingGroup, - ''Forwarded Fetches/Sec High: TempDB Object'' AS Finding, - ''https://BrentOzar.com/go/fetch/'' AS URL, - CAST(COALESCE(os.forwarded_fetch_count,0) - COALESCE(os_prior.forwarded_fetch_count,0) AS NVARCHAR(20)) + '' forwarded fetches on '' + - CASE WHEN OBJECT_NAME(os.object_id) IS NULL THEN ''an unknown table '' - WHEN LEN(OBJECT_NAME(os.object_id)) < 50 THEN ''a table variable, internal identifier '' + OBJECT_NAME(os.object_id) - ELSE ''a temp table '' + OBJECT_NAME(os.object_id) - END AS Details, - ''Look through your source code to find the object creating these objects, and tune the creation and population to reduce fetches. See the URL for details.'' AS HowToStopIt - FROM tempdb.sys.dm_db_index_operational_stats(DB_ID(''tempdb''), NULL, NULL, NULL) os - LEFT OUTER JOIN #TempdbOperationalStats os_prior ON os.object_id = os_prior.object_id - AND os.forwarded_fetch_count > os_prior.forwarded_fetch_count - WHERE os.database_id = DB_ID(''tempdb'') - AND os.forwarded_fetch_count - COALESCE(os_prior.forwarded_fetch_count,0) > 100 - ORDER BY os.forwarded_fetch_count DESC;' +RAISERROR (N'Updating #IndexSanity.key_column_names_with_sort_order_no_types (for create tsql)',0,1) WITH NOWAIT; +UPDATE #IndexSanity +SET key_column_names_with_sort_order_no_types = D2.key_column_names_with_sort_order_no_types +FROM #IndexSanity si + CROSS APPLY ( SELECT RTRIM(STUFF( (SELECT N', ' + QUOTENAME(c.column_name) + CASE c.is_descending_key + WHEN 1 THEN N' DESC' + ELSE N'' + END AS col_definition + FROM #IndexColumns c + WHERE c.database_id= si.database_id + AND c.schema_name = si.schema_name + AND c.object_id = si.object_id + AND c.index_id = si.index_id + AND c.is_included_column = 0 /*Just Keys*/ + AND c.key_ordinal > 0 /*Ignore non-key columns, such as partitioning keys*/ + ORDER BY c.object_id, c.index_id, c.key_ordinal + FOR XML PATH('') , TYPE).value('.', 'nvarchar(max)'), 1, 1, '')) + ) D2 ( key_column_names_with_sort_order_no_types ); - EXECUTE sp_executesql @StringToExecute; - END +RAISERROR (N'Updating #IndexSanity.include_column_names',0,1) WITH NOWAIT; +UPDATE #IndexSanity +SET include_column_names = D3.include_column_names +FROM #IndexSanity si + CROSS APPLY ( SELECT RTRIM(STUFF( (SELECT N', ' + c.column_name + + N' {' + system_type_name + N' ' + CAST(max_length AS NVARCHAR(50)) + N'}' + FROM #IndexColumns c + WHERE c.database_id= si.database_id + AND c.schema_name = si.schema_name + AND c.object_id = si.object_id + AND c.index_id = si.index_id + AND c.is_included_column = 1 /*Just includes*/ + ORDER BY c.column_name /*Order doesn't matter in includes, + this is here to make rows easy to compare.*/ + FOR XML PATH('') , TYPE).value('.', 'nvarchar(max)'), 1, 1, '')) + ) D3 ( include_column_names ); - /* In-Memory OLTP - Garbage Collection in Progress - CheckID 31 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 31',10,1) WITH NOWAIT; - END +RAISERROR (N'Updating #IndexSanity.include_column_names_no_types (for create tsql)',0,1) WITH NOWAIT; +UPDATE #IndexSanity +SET include_column_names_no_types = D3.include_column_names_no_types +FROM #IndexSanity si + CROSS APPLY ( SELECT RTRIM(STUFF( (SELECT N', ' + QUOTENAME(c.column_name) + FROM #IndexColumns c + WHERE c.database_id= si.database_id + AND c.schema_name = si.schema_name + AND c.object_id = si.object_id + AND c.index_id = si.index_id + AND c.is_included_column = 1 /*Just includes*/ + ORDER BY c.column_name /*Order doesn't matter in includes, + this is here to make rows easy to compare.*/ + FOR XML PATH('') , TYPE).value('.', 'nvarchar(max)'), 1, 1, '')) + ) D3 ( include_column_names_no_types ); - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) - SELECT 31 AS CheckID, - 50 AS Priority, - 'In-Memory OLTP' AS FindingGroup, - 'Garbage Collection in Progress' AS Finding, - 'https://BrentOzar.com/go/garbage/' AS URL, - CAST(ps.value_delta AS NVARCHAR(50)) + ' rows processed (from SQL Server YYYY XTP Garbage Collection:Rows processed/sec counter)' + @LineFeed - + 'This can happen due to memory pressure (causing In-Memory OLTP to shrink its footprint) or' + @LineFeed - + 'due to transactional workloads that constantly insert/delete data.' AS Details, - 'Sadly, you cannot choose when garbage collection occurs. This is one of the many gotchas of Hekaton. Learn more: http://nedotter.com/archive/2016/04/row-version-lifecycle-for-in-memory-oltp/' AS HowToStopIt - FROM #PerfmonStats ps - INNER JOIN #PerfmonStats psComp ON psComp.Pass = 2 AND psComp.object_name LIKE '%XTP Garbage Collection' AND psComp.counter_name = 'Rows processed/sec' AND psComp.value_delta > 100 - WHERE ps.Pass = 2 - AND ps.object_name LIKE '%XTP Garbage Collection' - AND ps.counter_name = 'Rows processed/sec' - AND ps.value_delta > (100 * @Seconds); /* Ignore servers sitting idle */ +RAISERROR (N'Updating #IndexSanity.count_key_columns and count_include_columns',0,1) WITH NOWAIT; +UPDATE #IndexSanity +SET count_included_columns = D4.count_included_columns, + count_key_columns = D4.count_key_columns +FROM #IndexSanity si + CROSS APPLY ( SELECT SUM(CASE WHEN is_included_column = 'true' THEN 1 + ELSE 0 + END) AS count_included_columns, + SUM(CASE WHEN is_included_column = 'false' AND c.key_ordinal > 0 THEN 1 + ELSE 0 + END) AS count_key_columns + FROM #IndexColumns c + WHERE c.database_id= si.database_id + AND c.schema_name = si.schema_name + AND c.object_id = si.object_id + AND c.index_id = si.index_id + ) AS D4 ( count_included_columns, count_key_columns ); - /* In-Memory OLTP - Transactions Aborted - CheckID 32 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 32',10,1) WITH NOWAIT; - END +RAISERROR (N'Updating index_sanity_id on #IndexPartitionSanity',0,1) WITH NOWAIT; +UPDATE #IndexPartitionSanity +SET index_sanity_id = i.index_sanity_id +FROM #IndexPartitionSanity ps + JOIN #IndexSanity i ON ps.[object_id] = i.[object_id] + AND ps.index_id = i.index_id + AND i.database_id = ps.database_id + AND i.schema_name = ps.schema_name; - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) - SELECT 32 AS CheckID, - 100 AS Priority, - 'In-Memory OLTP' AS FindingGroup, - 'Transactions Aborted' AS Finding, - 'https://BrentOzar.com/go/aborted/' AS URL, - CAST(ps.value_delta AS NVARCHAR(50)) + ' transactions aborted (from SQL Server YYYY XTP Transactions:Transactions aborted/sec counter)' + @LineFeed - + 'This may indicate that data is changing, or causing folks to retry their transactions, thereby increasing load.' AS Details, - 'Dig into your In-Memory OLTP transactions to figure out which ones are failing and being retried.' AS HowToStopIt - FROM #PerfmonStats ps - INNER JOIN #PerfmonStats psComp ON psComp.Pass = 2 AND psComp.object_name LIKE '%XTP Transactions' AND psComp.counter_name = 'Transactions aborted/sec' AND psComp.value_delta > 100 - WHERE ps.Pass = 2 - AND ps.object_name LIKE '%XTP Transactions' - AND ps.counter_name = 'Transactions aborted/sec' - AND ps.value_delta > (10 * @Seconds); /* Ignore servers sitting idle */ - /* Query Problems - Suboptimal Plans/Sec High - CheckID 33 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 33',10,1) WITH NOWAIT; - END +RAISERROR (N'Inserting data into #IndexSanitySize',0,1) WITH NOWAIT; +INSERT #IndexSanitySize ( [index_sanity_id], [database_id], [schema_name], [lock_escalation_desc], partition_count, total_rows, total_reserved_MB, + total_reserved_LOB_MB, total_reserved_row_overflow_MB, total_reserved_dictionary_MB, total_range_scan_count, + total_singleton_lookup_count, total_leaf_delete_count, total_leaf_update_count, + total_forwarded_fetch_count,total_row_lock_count, + total_row_lock_wait_count, total_row_lock_wait_in_ms, avg_row_lock_wait_in_ms, + total_page_lock_count, total_page_lock_wait_count, total_page_lock_wait_in_ms, + avg_page_lock_wait_in_ms, total_index_lock_promotion_attempt_count, + total_index_lock_promotion_count, data_compression_desc, + page_latch_wait_count, page_latch_wait_in_ms, page_io_latch_wait_count, page_io_latch_wait_in_ms) + SELECT index_sanity_id, ipp.database_id, ipp.schema_name, ipp.lock_escalation_desc, + COUNT(*), SUM(row_count), SUM(reserved_MB), + SUM(reserved_LOB_MB) - SUM(reserved_dictionary_MB), /* Subtract columnstore dictionaries from LOB data */ + SUM(reserved_row_overflow_MB), + SUM(reserved_dictionary_MB), + SUM(range_scan_count), + SUM(singleton_lookup_count), + SUM(leaf_delete_count), + SUM(leaf_update_count), + SUM(forwarded_fetch_count), + SUM(row_lock_count), + SUM(row_lock_wait_count), + SUM(row_lock_wait_in_ms), + CASE WHEN SUM(row_lock_wait_in_ms) > 0 THEN + SUM(row_lock_wait_in_ms)/(1.*SUM(row_lock_wait_count)) + ELSE 0 END AS avg_row_lock_wait_in_ms, + SUM(page_lock_count), + SUM(page_lock_wait_count), + SUM(page_lock_wait_in_ms), + CASE WHEN SUM(page_lock_wait_in_ms) > 0 THEN + SUM(page_lock_wait_in_ms)/(1.*SUM(page_lock_wait_count)) + ELSE 0 END AS avg_page_lock_wait_in_ms, + SUM(index_lock_promotion_attempt_count), + SUM(index_lock_promotion_count), + LEFT(MAX(data_compression_info.data_compression_rollup),4000), + SUM(page_latch_wait_count), + SUM(page_latch_wait_in_ms), + SUM(page_io_latch_wait_count), + SUM(page_io_latch_wait_in_ms) + FROM #IndexPartitionSanity ipp + /* individual partitions can have distinct compression settings, just roll them into a list here*/ + OUTER APPLY (SELECT STUFF(( + SELECT N', ' + data_compression_desc + FROM #IndexPartitionSanity ipp2 + WHERE ipp.[object_id]=ipp2.[object_id] + AND ipp.[index_id]=ipp2.[index_id] + AND ipp.database_id = ipp2.database_id + AND ipp.schema_name = ipp2.schema_name + ORDER BY ipp2.partition_number + FOR XML PATH(''),TYPE).value('.', 'nvarchar(max)'), 1, 1, '')) + data_compression_info(data_compression_rollup) + GROUP BY index_sanity_id, ipp.database_id, ipp.schema_name, ipp.lock_escalation_desc + ORDER BY index_sanity_id +OPTION ( RECOMPILE ); - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) - SELECT 32 AS CheckID, - 100 AS Priority, - 'Query Problems' AS FindingGroup, - 'Suboptimal Plans/Sec High' AS Finding, - 'https://BrentOzar.com/go/suboptimal/' AS URL, - CAST(ps.value_delta AS NVARCHAR(50)) + ' plans reported in the ' + CAST(ps.instance_name AS NVARCHAR(100)) + ' workload group (from Workload GroupStats:Suboptimal plans/sec counter)' + @LineFeed - + 'Even if you are not using Resource Governor, it still tracks information about user queries, memory grants, etc.' AS Details, - 'Check out sp_BlitzCache to get more information about recent queries, or try sp_BlitzWho to see currently running queries.' AS HowToStopIt - FROM #PerfmonStats ps - INNER JOIN #PerfmonStats psComp ON psComp.Pass = 2 AND psComp.object_name = @ServiceName + ':Workload GroupStats' AND psComp.counter_name = 'Suboptimal plans/sec' AND psComp.value_delta > 100 - WHERE ps.Pass = 2 - AND ps.object_name = @ServiceName + ':Workload GroupStats' - AND ps.counter_name = 'Suboptimal plans/sec' - AND ps.value_delta > (10 * @Seconds); /* Ignore servers sitting idle */ +RAISERROR (N'Determining index usefulness',0,1) WITH NOWAIT; +UPDATE #MissingIndexes +SET is_low = CASE WHEN (user_seeks + user_scans) < 5000 + OR unique_compiles = 1 + THEN 1 + ELSE 0 + END; - /* Azure Performance - Database is Maxed Out - CheckID 41 */ - IF SERVERPROPERTY('Edition') = 'SQL Azure' - BEGIN - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 41',10,1) WITH NOWAIT; - END +RAISERROR (N'Updating #IndexSanity.referenced_by_foreign_key',0,1) WITH NOWAIT; +UPDATE #IndexSanity + SET is_referenced_by_foreign_key=1 +FROM #IndexSanity s +JOIN #ForeignKeys fk ON + s.object_id=fk.referenced_object_id + AND s.database_id=fk.database_id + AND LEFT(s.key_column_names,LEN(fk.referenced_fk_columns)) = fk.referenced_fk_columns; - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) - SELECT 41 AS CheckID, - 10 AS Priority, - 'Azure Performance' AS FindingGroup, - 'Database is Maxed Out' AS Finding, - 'https://BrentOzar.com/go/maxedout' AS URL, - N'At ' + CONVERT(NVARCHAR(100), s.end_time ,121) + N', your database approached (or hit) your DTU limits:' + @LineFeed - + N'Average CPU percent: ' + CAST(avg_cpu_percent AS NVARCHAR(50)) + @LineFeed - + N'Average data IO percent: ' + CAST(avg_data_io_percent AS NVARCHAR(50)) + @LineFeed - + N'Average log write percent: ' + CAST(avg_log_write_percent AS NVARCHAR(50)) + @LineFeed - + N'Max worker percent: ' + CAST(max_worker_percent AS NVARCHAR(50)) + @LineFeed - + N'Max session percent: ' + CAST(max_session_percent AS NVARCHAR(50)) AS Details, - 'Tune your queries or indexes with sp_BlitzCache or sp_BlitzIndex, or consider upgrading to a higher DTU level.' AS HowToStopIt - FROM sys.dm_db_resource_stats s - WHERE s.end_time >= DATEADD(MI, -5, GETDATE()) - AND (avg_cpu_percent > 90 - OR avg_data_io_percent >= 90 - OR avg_log_write_percent >=90 - OR max_worker_percent >= 90 - OR max_session_percent >= 90); - END +RAISERROR (N'Update index_secret on #IndexSanity for NC indexes.',0,1) WITH NOWAIT; +UPDATE nc +SET secret_columns= + N'[' + + CASE tb.count_key_columns WHEN 0 THEN '1' ELSE CAST(tb.count_key_columns AS NVARCHAR(10)) END + + CASE nc.is_unique WHEN 1 THEN N' INCLUDE' ELSE N' KEY' END + + CASE WHEN tb.count_key_columns > 1 THEN N'S] ' ELSE N'] ' END + + CASE tb.index_id WHEN 0 THEN '[RID]' ELSE LTRIM(tb.key_column_names) + + /* Uniquifiers only needed on non-unique clustereds-- not heaps */ + CASE tb.is_unique WHEN 0 THEN ' [UNIQUIFIER]' ELSE N'' END + END + , count_secret_columns= + CASE tb.index_id WHEN 0 THEN 1 ELSE + tb.count_key_columns + + CASE tb.is_unique WHEN 0 THEN 1 ELSE 0 END + END +FROM #IndexSanity AS nc +JOIN #IndexSanity AS tb ON nc.object_id=tb.object_id + AND nc.database_id = tb.database_id + AND nc.schema_name = tb.schema_name + AND tb.index_id IN (0,1) +WHERE nc.index_id > 1; - /* Server Info - Batch Requests per Sec - CheckID 19 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 19',10,1) WITH NOWAIT; - END +RAISERROR (N'Update index_secret on #IndexSanity for heaps and non-unique clustered.',0,1) WITH NOWAIT; +UPDATE tb +SET secret_columns= CASE tb.index_id WHEN 0 THEN '[RID]' ELSE '[UNIQUIFIER]' END + , count_secret_columns = 1 +FROM #IndexSanity AS tb +WHERE tb.index_id = 0 /*Heaps-- these have the RID */ + OR (tb.index_id=1 AND tb.is_unique=0); /* Non-unique CX: has uniquifer (when needed) */ - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, DetailsInt) - SELECT 19 AS CheckID, - 250 AS Priority, - 'Server Info' AS FindingGroup, - 'Batch Requests per Sec' AS Finding, - 'http://www.BrentOzar.com/go/measure' AS URL, - CAST(CAST(ps.value_delta AS MONEY) / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS NVARCHAR(20)) AS Details, - ps.value_delta / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS DetailsInt - FROM #PerfmonStats ps - INNER JOIN #PerfmonStats ps1 ON ps.object_name = ps1.object_name AND ps.counter_name = ps1.counter_name AND ps1.Pass = 1 - WHERE ps.Pass = 2 - AND ps.object_name = @ServiceName + ':SQL Statistics' - AND ps.counter_name = 'Batch Requests/sec'; +RAISERROR (N'Populate #IndexCreateTsql.',0,1) WITH NOWAIT; +INSERT #IndexCreateTsql (index_sanity_id, create_tsql) +SELECT + index_sanity_id, + ISNULL ( + CASE index_id WHEN 0 THEN N'ALTER TABLE ' + QUOTENAME([database_name]) + N'.' + QUOTENAME([schema_name]) + N'.' + QUOTENAME([object_name]) + ' REBUILD;' + ELSE + CASE WHEN is_XML = 1 OR is_spatial = 1 OR is_in_memory_oltp = 1 THEN N'' /* Not even trying for these just yet...*/ + ELSE + CASE WHEN is_primary_key=1 THEN + N'ALTER TABLE ' + QUOTENAME([database_name]) + N'.' + QUOTENAME([schema_name]) + + N'.' + QUOTENAME([object_name]) + + N' ADD CONSTRAINT [' + + index_name + + N'] PRIMARY KEY ' + + CASE WHEN index_id=1 THEN N'CLUSTERED (' ELSE N'(' END + + key_column_names_with_sort_order_no_types + N' )' + WHEN is_CX_columnstore= 1 THEN + N'CREATE CLUSTERED COLUMNSTORE INDEX ' + QUOTENAME(index_name) + N' on ' + QUOTENAME([database_name]) + N'.' + QUOTENAME([schema_name]) + N'.' + QUOTENAME([object_name]) + ELSE /*Else not a PK or cx columnstore */ + N'CREATE ' + + CASE WHEN is_unique=1 THEN N'UNIQUE ' ELSE N'' END + + CASE WHEN index_id=1 THEN N'CLUSTERED ' ELSE N'' END + + CASE WHEN is_NC_columnstore=1 THEN N'NONCLUSTERED COLUMNSTORE ' + ELSE N'' END + + N'INDEX [' + + index_name + N'] ON ' + + QUOTENAME([database_name]) + N'.' + + QUOTENAME([schema_name]) + N'.' + QUOTENAME([object_name]) + + CASE WHEN is_NC_columnstore=1 THEN + N' (' + ISNULL(include_column_names_no_types,'') + N' )' + ELSE /*Else not columnstore */ + N' (' + ISNULL(key_column_names_with_sort_order_no_types,'') + N' )' + + CASE WHEN include_column_names_no_types IS NOT NULL THEN + N' INCLUDE (' + include_column_names_no_types + N')' + ELSE N'' + END + END /*End non-columnstore case */ + + CASE WHEN filter_definition <> N'' THEN N' WHERE ' + filter_definition ELSE N'' END + END /*End Non-PK index CASE */ + + CASE WHEN is_NC_columnstore=0 AND is_CX_columnstore=0 THEN + N' WITH (' + + N'FILLFACTOR=' + CASE fill_factor WHEN 0 THEN N'100' ELSE CAST(fill_factor AS NVARCHAR(5)) END + ', ' + + N'ONLINE=?, SORT_IN_TEMPDB=?, DATA_COMPRESSION=?' + + N')' + ELSE N'' END + + N';' + END /*End non-spatial and non-xml CASE */ + END, '[Unknown Error]') + AS create_tsql +FROM #IndexSanity; + +RAISERROR (N'Populate #PartitionCompressionInfo.',0,1) WITH NOWAIT; +WITH maps + AS + ( + SELECT ips.index_sanity_id, + ips.partition_number, + ips.data_compression_desc, + ips.partition_number - ROW_NUMBER() OVER ( PARTITION BY ips.index_sanity_id, ips.data_compression_desc + ORDER BY ips.partition_number ) AS rn + FROM #IndexPartitionSanity AS ips + ) +SELECT * +INTO #maps +FROM maps; - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','SQL Compilations/sec', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','SQL Re-Compilations/sec', NULL); +WITH grps + AS + ( + SELECT MIN(maps.partition_number) AS MinKey, + MAX(maps.partition_number) AS MaxKey, + maps.index_sanity_id, + maps.data_compression_desc + FROM #maps AS maps + GROUP BY maps.rn, maps.index_sanity_id, maps.data_compression_desc + ) +SELECT * +INTO #grps +FROM grps; - /* Server Info - SQL Compilations/sec - CheckID 25 */ - IF @ExpertMode = 1 - BEGIN - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 25',10,1) WITH NOWAIT; - END +INSERT #PartitionCompressionInfo ( index_sanity_id, partition_compression_detail ) +SELECT DISTINCT + grps.index_sanity_id, + SUBSTRING( + ( STUFF( + ( SELECT N', ' + N' Partition' + + CASE + WHEN grps2.MinKey < grps2.MaxKey + THEN + + N's ' + CAST(grps2.MinKey AS NVARCHAR(10)) + N' - ' + + CAST(grps2.MaxKey AS NVARCHAR(10)) + N' use ' + grps2.data_compression_desc + ELSE + N' ' + CAST(grps2.MinKey AS NVARCHAR(10)) + N' uses ' + grps2.data_compression_desc + END AS Partitions + FROM #grps AS grps2 + WHERE grps2.index_sanity_id = grps.index_sanity_id + ORDER BY grps2.MinKey, grps2.MaxKey + FOR XML PATH(''), TYPE ).value('.', 'NVARCHAR(MAX)'), 1, 1, '')), 0, 8000) AS partition_compression_detail +FROM #grps AS grps; + +RAISERROR (N'Update #PartitionCompressionInfo.',0,1) WITH NOWAIT; +UPDATE sz +SET sz.data_compression_desc = pci.partition_compression_detail +FROM #IndexSanitySize sz +JOIN #PartitionCompressionInfo AS pci +ON pci.index_sanity_id = sz.index_sanity_id; - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, DetailsInt) - SELECT 25 AS CheckID, - 250 AS Priority, - 'Server Info' AS FindingGroup, - 'SQL Compilations per Sec' AS Finding, - 'http://www.BrentOzar.com/go/measure' AS URL, - CAST(ps.value_delta / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS NVARCHAR(20)) AS Details, - ps.value_delta / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS DetailsInt - FROM #PerfmonStats ps - INNER JOIN #PerfmonStats ps1 ON ps.object_name = ps1.object_name AND ps.counter_name = ps1.counter_name AND ps1.Pass = 1 - WHERE ps.Pass = 2 - AND ps.object_name = @ServiceName + ':SQL Statistics' - AND ps.counter_name = 'SQL Compilations/sec'; - END +RAISERROR (N'Update #IndexSanity for filtered indexes with columns not in the index definition.',0,1) WITH NOWAIT; +UPDATE #IndexSanity +SET filter_columns_not_in_index = D1.filter_columns_not_in_index +FROM #IndexSanity si + CROSS APPLY ( SELECT RTRIM(STUFF( (SELECT N', ' + c.column_name AS col_definition + FROM #FilteredIndexes AS c + WHERE c.database_id= si.database_id + AND c.schema_name = si.schema_name + AND c.table_name = si.object_name + AND c.index_name = si.index_name + ORDER BY c.index_sanity_id + FOR XML PATH('') , TYPE).value('.', 'nvarchar(max)'), 1, 1,''))) D1 + ( filter_columns_not_in_index ); - /* Server Info - SQL Re-Compilations/sec - CheckID 26 */ - IF @ExpertMode = 1 - BEGIN - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 26',10,1) WITH NOWAIT; - END - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, DetailsInt) - SELECT 26 AS CheckID, - 250 AS Priority, - 'Server Info' AS FindingGroup, - 'SQL Re-Compilations per Sec' AS Finding, - 'http://www.BrentOzar.com/go/measure' AS URL, - CAST(ps.value_delta / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS NVARCHAR(20)) AS Details, - ps.value_delta / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS DetailsInt - FROM #PerfmonStats ps - INNER JOIN #PerfmonStats ps1 ON ps.object_name = ps1.object_name AND ps.counter_name = ps1.counter_name AND ps1.Pass = 1 - WHERE ps.Pass = 2 - AND ps.object_name = @ServiceName + ':SQL Statistics' - AND ps.counter_name = 'SQL Re-Compilations/sec'; - END +IF @Debug = 1 +BEGIN + SELECT '#IndexSanity' AS table_name, * FROM #IndexSanity; + SELECT '#IndexPartitionSanity' AS table_name, * FROM #IndexPartitionSanity; + SELECT '#IndexSanitySize' AS table_name, * FROM #IndexSanitySize; + SELECT '#IndexColumns' AS table_name, * FROM #IndexColumns; + SELECT '#MissingIndexes' AS table_name, * FROM #MissingIndexes; + SELECT '#ForeignKeys' AS table_name, * FROM #ForeignKeys; + SELECT '#BlitzIndexResults' AS table_name, * FROM #BlitzIndexResults; + SELECT '#IndexCreateTsql' AS table_name, * FROM #IndexCreateTsql; + SELECT '#DatabaseList' AS table_name, * FROM #DatabaseList; + SELECT '#Statistics' AS table_name, * FROM #Statistics; + SELECT '#PartitionCompressionInfo' AS table_name, * FROM #PartitionCompressionInfo; + SELECT '#ComputedColumns' AS table_name, * FROM #ComputedColumns; + SELECT '#TraceStatus' AS table_name, * FROM #TraceStatus; + SELECT '#CheckConstraints' AS table_name, * FROM #CheckConstraints; + SELECT '#FilteredIndexes' AS table_name, * FROM #FilteredIndexes; +END - /* Server Info - Wait Time per Core per Sec - CheckID 20 */ - IF @Seconds > 0 + +---------------------------------------- +--STEP 3: DIAGNOSE THE PATIENT +---------------------------------------- + + +BEGIN TRY +---------------------------------------- +--If @TableName is specified, just return information for that table. +--The @Mode parameter doesn't matter if you're looking at a specific table. +---------------------------------------- +IF @TableName IS NOT NULL +BEGIN + RAISERROR(N'@TableName specified, giving detail only on that table.', 0,1) WITH NOWAIT; + + --We do a left join here in case this is a disabled NC. + --In that case, it won't have any size info/pages allocated. + + + WITH table_mode_cte AS ( + SELECT + s.db_schema_object_indexid, + s.key_column_names, + s.index_definition, + ISNULL(s.secret_columns,N'') AS secret_columns, + s.fill_factor, + s.index_usage_summary, + sz.index_op_stats, + ISNULL(sz.index_size_summary,'') /*disabled NCs will be null*/ AS index_size_summary, + partition_compression_detail , + ISNULL(sz.index_lock_wait_summary,'') AS index_lock_wait_summary, + s.is_referenced_by_foreign_key, + (SELECT COUNT(*) + FROM #ForeignKeys fk WHERE fk.parent_object_id=s.object_id + AND PATINDEX (fk.parent_fk_columns, s.key_column_names)=1) AS FKs_covered_by_index, + s.last_user_seek, + s.last_user_scan, + s.last_user_lookup, + s.last_user_update, + s.create_date, + s.modify_date, + sz.page_latch_wait_count, + CONVERT(VARCHAR(10), (sz.page_latch_wait_in_ms / 1000) / 86400) + ':' + CONVERT(VARCHAR(20), DATEADD(s, (sz.page_latch_wait_in_ms / 1000), 0), 108) AS page_latch_wait_time, + sz.page_io_latch_wait_count, + CONVERT(VARCHAR(10), (sz.page_io_latch_wait_in_ms / 1000) / 86400) + ':' + CONVERT(VARCHAR(20), DATEADD(s, (sz.page_io_latch_wait_in_ms / 1000), 0), 108) AS page_io_latch_wait_time, + ct.create_tsql, + CASE + WHEN s.is_primary_key = 1 AND s.index_definition <> '[HEAP]' + THEN N'--ALTER TABLE ' + QUOTENAME(s.[database_name]) + N'.' + QUOTENAME(s.[schema_name]) + N'.' + QUOTENAME(s.[object_name]) + + N' DROP CONSTRAINT ' + QUOTENAME(s.index_name) + N';' + WHEN s.is_primary_key = 0 AND s.index_definition <> '[HEAP]' + THEN N'--DROP INDEX '+ QUOTENAME(s.index_name) + N' ON ' + QUOTENAME(s.[database_name]) + N'.' + + QUOTENAME(s.[schema_name]) + N'.' + QUOTENAME(s.[object_name]) + N';' + ELSE N'' + END AS drop_tsql, + 1 AS display_order + FROM #IndexSanity s + LEFT JOIN #IndexSanitySize sz ON + s.index_sanity_id=sz.index_sanity_id + LEFT JOIN #IndexCreateTsql ct ON + s.index_sanity_id=ct.index_sanity_id + LEFT JOIN #PartitionCompressionInfo pci ON + pci.index_sanity_id = s.index_sanity_id + WHERE s.[object_id]=@ObjectID + UNION ALL + SELECT N'Database ' + QUOTENAME(@DatabaseName) + N' as of ' + CONVERT(NVARCHAR(16),GETDATE(),121) + + N' (' + @ScriptVersionName + ')' , + N'SQL Server First Responder Kit' , + N'http://FirstResponderKit.org' , + N'From Your Community Volunteers', + NULL,@DaysUptimeInsertValue,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL, + 0 AS display_order + ) + SELECT + db_schema_object_indexid AS [Details: db_schema.table.index(indexid)], + index_definition AS [Definition: [Property]] ColumnName {datatype maxbytes}], + secret_columns AS [Secret Columns], + fill_factor AS [Fillfactor], + index_usage_summary AS [Usage Stats], + index_op_stats AS [Op Stats], + index_size_summary AS [Size], + partition_compression_detail AS [Compression Type], + index_lock_wait_summary AS [Lock Waits], + is_referenced_by_foreign_key AS [Referenced by FK?], + FKs_covered_by_index AS [FK Covered by Index?], + last_user_seek AS [Last User Seek], + last_user_scan AS [Last User Scan], + last_user_lookup AS [Last User Lookup], + last_user_update AS [Last User Write], + create_date AS [Created], + modify_date AS [Last Modified], + page_latch_wait_count AS [Page Latch Wait Count], + page_latch_wait_time as [Page Latch Wait Time (D:H:M:S)], + page_io_latch_wait_count AS [Page IO Latch Wait Count], + page_io_latch_wait_time as [Page IO Latch Wait Time (D:H:M:S)], + create_tsql AS [Create TSQL], + drop_tsql AS [Drop TSQL] + FROM table_mode_cte + ORDER BY display_order ASC, key_column_names ASC + OPTION ( RECOMPILE ); + + IF (SELECT TOP 1 [object_id] FROM #MissingIndexes mi) IS NOT NULL BEGIN; - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 20',10,1) WITH NOWAIT; - END; - WITH waits1(SampleTime, waits_ms) AS (SELECT SampleTime, SUM(ws1.wait_time_ms) FROM #WaitStats ws1 WHERE ws1.Pass = 1 GROUP BY SampleTime), - waits2(SampleTime, waits_ms) AS (SELECT SampleTime, SUM(ws2.wait_time_ms) FROM #WaitStats ws2 WHERE ws2.Pass = 2 GROUP BY SampleTime), - cores(cpu_count) AS (SELECT SUM(1) FROM sys.dm_os_schedulers WHERE status = 'VISIBLE ONLINE' AND is_online = 1) - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, DetailsInt) - SELECT 20 AS CheckID, - 250 AS Priority, - 'Server Info' AS FindingGroup, - 'Wait Time per Core per Sec' AS Finding, - 'http://www.BrentOzar.com/go/measure' AS URL, - CAST((CAST(waits2.waits_ms - waits1.waits_ms AS MONEY)) / 1000 / i.cpu_count / DATEDIFF(ss, waits1.SampleTime, waits2.SampleTime) AS NVARCHAR(20)) AS Details, - (waits2.waits_ms - waits1.waits_ms) / 1000 / i.cpu_count / DATEDIFF(ss, waits1.SampleTime, waits2.SampleTime) AS DetailsInt - FROM cores i - CROSS JOIN waits1 - CROSS JOIN waits2; + WITH create_date AS ( + SELECT i.database_id, + i.schema_name, + i.[object_id], + ISNULL(NULLIF(MAX(DATEDIFF(DAY, i.create_date, SYSDATETIME())), 0), 1) AS create_days + FROM #IndexSanity AS i + GROUP BY i.database_id, i.schema_name, i.object_id + ) + SELECT N'Missing index.' AS Finding , + N'https://www.brentozar.com/go/Indexaphobia' AS URL , + mi.[statement] + + ' Est. Benefit: ' + + CASE WHEN magic_benefit_number >= 922337203685477 THEN '>= 922,337,203,685,477' + ELSE REPLACE(CONVERT(NVARCHAR(256),CAST(CAST( + (magic_benefit_number / CASE WHEN cd.create_days < @DaysUptime THEN cd.create_days ELSE @DaysUptime END) + AS BIGINT) AS MONEY), 1), '.00', '') + END AS [Estimated Benefit], + missing_index_details AS [Missing Index Request] , + index_estimated_impact AS [Estimated Impact], + create_tsql AS [Create TSQL], + sample_query_plan AS [Sample Query Plan] + FROM #MissingIndexes mi + LEFT JOIN create_date AS cd + ON mi.[object_id] = cd.object_id + AND mi.database_id = cd.database_id + AND mi.schema_name = cd.schema_name + WHERE mi.[object_id] = @ObjectID + AND (@ShowAllMissingIndexRequests=1 + /* Minimum benefit threshold = 100k/day of uptime OR since table creation date, whichever is lower*/ + OR (magic_benefit_number / CASE WHEN cd.create_days < @DaysUptime THEN cd.create_days ELSE @DaysUptime END) >= 100000) + ORDER BY magic_benefit_number DESC + OPTION ( RECOMPILE ); + END; + ELSE + SELECT 'No missing indexes.' AS finding; + + SELECT + column_name AS [Column Name], + (SELECT COUNT(*) + FROM #IndexColumns c2 + WHERE c2.column_name=c.column_name + AND c2.key_ordinal IS NOT NULL) + + CASE WHEN c.index_id = 1 AND c.key_ordinal IS NOT NULL THEN + -1+ (SELECT COUNT(DISTINCT index_id) + FROM #IndexColumns c3 + WHERE c3.index_id NOT IN (0,1)) + ELSE 0 END + AS [Found In], + system_type_name + + CASE max_length WHEN -1 THEN N' (max)' ELSE + CASE + WHEN system_type_name IN (N'char',N'varchar',N'binary',N'varbinary') THEN N' (' + CAST(max_length AS NVARCHAR(20)) + N')' + WHEN system_type_name IN (N'nchar',N'nvarchar') THEN N' (' + CAST(max_length/2 AS NVARCHAR(20)) + N')' + ELSE '' + END + END + AS [Type], + CASE is_computed WHEN 1 THEN 'yes' ELSE '' END AS [Computed?], + max_length AS [Length (max bytes)], + [precision] AS [Prec], + [scale] AS [Scale], + CASE is_nullable WHEN 1 THEN 'yes' ELSE '' END AS [Nullable?], + CASE is_identity WHEN 1 THEN 'yes' ELSE '' END AS [Identity?], + CASE is_replicated WHEN 1 THEN 'yes' ELSE '' END AS [Replicated?], + CASE is_sparse WHEN 1 THEN 'yes' ELSE '' END AS [Sparse?], + CASE is_filestream WHEN 1 THEN 'yes' ELSE '' END AS [Filestream?], + collation_name AS [Collation] + FROM #IndexColumns AS c + WHERE index_id IN (0,1); + + IF (SELECT TOP 1 parent_object_id FROM #ForeignKeys) IS NOT NULL + BEGIN + SELECT [database_name] + N':' + parent_object_name + N': ' + foreign_key_name AS [Foreign Key], + parent_fk_columns AS [Foreign Key Columns], + referenced_object_name AS [Referenced Table], + referenced_fk_columns AS [Referenced Table Columns], + is_disabled AS [Is Disabled?], + is_not_trusted AS [Not Trusted?], + is_not_for_replication [Not for Replication?], + [update_referential_action_desc] AS [Cascading Updates?], + [delete_referential_action_desc] AS [Cascading Deletes?] + FROM #ForeignKeys + ORDER BY [Foreign Key] + OPTION ( RECOMPILE ); END; + ELSE + SELECT 'No foreign keys.' AS finding; - /* If we're waiting 30+ seconds, run these checks at the end. - We get this data from the ring buffers, and it's only updated once per minute, so might - as well get it now - whereas if we're checking 30+ seconds, it might get updated by the - end of our sp_BlitzFirst session. */ - IF @Seconds >= 30 + /* Show histograms for all stats on this table. More info: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/1900 */ + IF EXISTS (SELECT * FROM sys.all_objects WHERE name = 'dm_db_stats_histogram') BEGIN - /* Server Performance - High CPU Utilization CheckID 24 */ - IF (@Debug = 1) + SET @dsql=N'SELECT s.name AS [Stat Name], c.name AS [Leading Column Name], hist.step_number AS [Step Number], + hist.range_high_key AS [Range High Key], hist.range_rows AS [Range Rows], + hist.equal_rows AS [Equal Rows], hist.distinct_range_rows AS [Distinct Range Rows], hist.average_range_rows AS [Average Range Rows], + s.auto_created AS [Auto-Created], s.user_created AS [User-Created], + props.last_updated AS [Last Updated], props.modification_counter AS [Modification Counter], props.rows AS [Table Rows], + props.rows_sampled AS [Rows Sampled], s.stats_id AS [StatsID] + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.stats AS s + INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.stats_columns sc ON s.object_id = sc.object_id AND s.stats_id = sc.stats_id AND sc.stats_column_id = 1 + INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns c ON sc.object_id = c.object_id AND sc.column_id = c.column_id + CROSS APPLY ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_stats_properties(s.object_id, s.stats_id) AS props + CROSS APPLY ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_stats_histogram(s.[object_id], s.stats_id) AS hist + WHERE s.object_id = @ObjectID + ORDER BY s.auto_created, s.user_created, s.name, hist.step_number;'; + EXEC sp_executesql @dsql, N'@ObjectID INT', @ObjectID; + END + + /* Visualize columnstore index contents. More info: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2584 */ + IF 2 = (SELECT SUM(1) FROM sys.all_objects WHERE name IN ('column_store_row_groups','column_store_segments')) + BEGIN + RAISERROR(N'Visualizing columnstore index contents.', 0,1) WITH NOWAIT; + + SET @dsql = N'USE ' + QUOTENAME(@DatabaseName) + N'; + IF EXISTS(SELECT * FROM ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_row_groups WHERE object_id = @ObjectID) + BEGIN + SET @ColumnList = N''''; + WITH DistinctColumns AS ( + SELECT DISTINCT QUOTENAME(c.name) AS column_name, c.column_id + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.partitions p + INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns c ON p.object_id = c.object_id + INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.index_columns ic on ic.column_id = c.column_id and ic.object_id = c.object_id AND ic.index_id = p.index_id + INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.types t ON c.system_type_id = t.system_type_id AND c.user_type_id = t.user_type_id + WHERE p.object_id = @ObjectID + AND EXISTS (SELECT * FROM ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_segments seg WHERE p.partition_id = seg.partition_id AND seg.column_id = ic.index_column_id) + AND p.data_compression IN (3,4) + ) + SELECT @ColumnList = @ColumnList + column_name + N'', '' + FROM DistinctColumns + ORDER BY column_id; + END'; + + IF @Debug = 1 + BEGIN + PRINT SUBSTRING(@dsql, 0, 4000); + PRINT SUBSTRING(@dsql, 4000, 8000); + PRINT SUBSTRING(@dsql, 8000, 12000); + PRINT SUBSTRING(@dsql, 12000, 16000); + PRINT SUBSTRING(@dsql, 16000, 20000); + PRINT SUBSTRING(@dsql, 20000, 24000); + PRINT SUBSTRING(@dsql, 24000, 28000); + PRINT SUBSTRING(@dsql, 28000, 32000); + PRINT SUBSTRING(@dsql, 32000, 36000); + PRINT SUBSTRING(@dsql, 36000, 40000); + END; + + EXEC sp_executesql @dsql, N'@ObjectID INT, @ColumnList NVARCHAR(MAX) OUTPUT', @ObjectID, @ColumnList OUTPUT; + + IF @Debug = 1 + SELECT @ColumnList AS ColumnstoreColumnList; + + IF @ColumnList <> '' BEGIN - RAISERROR('Running CheckID 24',10,1) WITH NOWAIT; - END + /* Remove the trailing comma */ + SET @ColumnList = LEFT(@ColumnList, LEN(@ColumnList) - 1); - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) - SELECT 24, 50, 'Server Performance', 'High CPU Utilization', CAST(100 - SystemIdle AS NVARCHAR(20)) + N'%. Ring buffer details: ' + CAST(record AS NVARCHAR(4000)), 100 - SystemIdle, 'http://www.BrentOzar.com/go/cpu' - FROM ( - SELECT record, - record.value('(./Record/SchedulerMonitorEvent/SystemHealth/SystemIdle)[1]', 'int') AS SystemIdle - FROM ( - SELECT TOP 1 CONVERT(XML, record) AS record - FROM sys.dm_os_ring_buffers - WHERE ring_buffer_type = N'RING_BUFFER_SCHEDULER_MONITOR' - AND record LIKE '%%' - ORDER BY timestamp DESC) AS rb - ) AS y - WHERE 100 - SystemIdle >= 50; + SET @dsql = N'USE ' + QUOTENAME(@DatabaseName) + N'; SELECT partition_number, row_group_id, total_rows, deleted_rows, ' + @ColumnList + N' + FROM ( + SELECT c.name AS column_name, p.partition_number, + rg.row_group_id, rg.total_rows, rg.deleted_rows, + details = CAST(seg.min_data_id AS VARCHAR(20)) + '' to '' + CAST(seg.max_data_id AS VARCHAR(20)) + '', '' + CAST(CAST((seg.on_disk_size / 1024.0 / 1024) AS DECIMAL(18,0)) AS VARCHAR(20)) + '' MB'' + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_row_groups rg + INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns c ON rg.object_id = c.object_id + INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partitions p ON rg.object_id = p.object_id AND rg.partition_number = p.partition_number + INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.index_columns ic on ic.column_id = c.column_id AND ic.object_id = c.object_id AND ic.index_id = p.index_id + LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_segments seg ON p.partition_id = seg.partition_id AND ic.index_column_id = seg.column_id AND rg.row_group_id = seg.segment_id + WHERE rg.object_id = @ObjectID + ) AS x + PIVOT (MAX(details) FOR column_name IN ( ' + @ColumnList + N')) AS pivot1 + ORDER BY partition_number, row_group_id;'; + + IF @Debug = 1 + BEGIN + PRINT SUBSTRING(@dsql, 0, 4000); + PRINT SUBSTRING(@dsql, 4000, 8000); + PRINT SUBSTRING(@dsql, 8000, 12000); + PRINT SUBSTRING(@dsql, 12000, 16000); + PRINT SUBSTRING(@dsql, 16000, 20000); + PRINT SUBSTRING(@dsql, 20000, 24000); + PRINT SUBSTRING(@dsql, 24000, 28000); + PRINT SUBSTRING(@dsql, 28000, 32000); + PRINT SUBSTRING(@dsql, 32000, 36000); + PRINT SUBSTRING(@dsql, 36000, 40000); + END; - /* Server Performance - CPU Utilization CheckID 23 */ - IF (@Debug = 1) + IF @dsql IS NULL + RAISERROR('@dsql is null',16,1); + ELSE + EXEC sp_executesql @dsql, N'@ObjectID INT', @ObjectID; + END + ELSE /* No columns were found for this object */ BEGIN - RAISERROR('Running CheckID 23',10,1) WITH NOWAIT; + SELECT N'No compressed columnstore rowgroups were found for this object.' AS Columnstore_Visualization + UNION ALL + SELECT N'SELECT * FROM ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_row_groups WHERE object_id = ' + CAST(@ObjectID AS NVARCHAR(100)); END + RAISERROR(N'Done visualizing columnstore index contents.', 0,1) WITH NOWAIT; + END - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) - SELECT 23, 250, 'Server Info', 'CPU Utilization', CAST(100 - SystemIdle AS NVARCHAR(20)) + N'%. Ring buffer details: ' + CAST(record AS NVARCHAR(4000)), 100 - SystemIdle, 'http://www.BrentOzar.com/go/cpu' - FROM ( - SELECT record, - record.value('(./Record/SchedulerMonitorEvent/SystemHealth/SystemIdle)[1]', 'int') AS SystemIdle - FROM ( - SELECT TOP 1 CONVERT(XML, record) AS record - FROM sys.dm_os_ring_buffers - WHERE ring_buffer_type = N'RING_BUFFER_SCHEDULER_MONITOR' - AND record LIKE '%%' - ORDER BY timestamp DESC) AS rb - ) AS y; +END; /* IF @TableName IS NOT NULL */ - END; /* IF @Seconds >= 30 */ - /* If we didn't find anything, apologize. */ - IF NOT EXISTS (SELECT * FROM #BlitzFirstResults WHERE Priority < 250) - BEGIN - INSERT INTO #BlitzFirstResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - VALUES ( -1 , - 1 , - 'No Problems Found' , - 'From Your Community Volunteers' , - 'http://FirstResponderKit.org/' , - 'Try running our more in-depth checks with sp_Blitz, or there may not be an unusual SQL Server performance problem. ' - ); - END; /*IF NOT EXISTS (SELECT * FROM #BlitzFirstResults) */ - /* Add credits for the nice folks who put so much time into building and maintaining this for free: */ - INSERT INTO #BlitzFirstResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - VALUES ( -1 , - 255 , - 'Thanks!' , - 'From Your Community Volunteers' , - 'http://FirstResponderKit.org/' , - 'To get help or add your own contributions, join us at http://FirstResponderKit.org.' - ); - INSERT INTO #BlitzFirstResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - VALUES ( -1 , - 0 , - 'sp_BlitzFirst ' + CAST(CONVERT(DATETIMEOFFSET, @VersionDate, 102) AS VARCHAR(100)), - 'From Your Community Volunteers' , - 'http://FirstResponderKit.org/' , - 'We hope you found this tool useful.' - ); - /* Outdated sp_BlitzFirst - sp_BlitzFirst is Over 6 Months Old - CheckID 27 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 27',10,1) WITH NOWAIT; - END - IF DATEDIFF(MM, @VersionDate, SYSDATETIMEOFFSET()) > 6 - BEGIN - INSERT INTO #BlitzFirstResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 27 AS CheckID , - 0 AS Priority , - 'Outdated sp_BlitzFirst' AS FindingsGroup , - 'sp_BlitzFirst is Over 6 Months Old' AS Finding , - 'http://FirstResponderKit.org/' AS URL , - 'Some things get better with age, like fine wine and your T-SQL. However, sp_BlitzFirst is not one of those things - time to go download the current one.' AS Details; - END; - IF @CheckServerInfo = 0 /* Github #1680 */ - BEGIN - DELETE #BlitzFirstResults - WHERE FindingsGroup = 'Server Info'; - END - RAISERROR('Analysis finished, outputting results',10,1) WITH NOWAIT; - /* If they want to run sp_BlitzCache and export to table, go for it. */ - IF @OutputTableNameBlitzCache IS NOT NULL - AND @OutputDatabaseName IS NOT NULL - AND @OutputSchemaName IS NOT NULL - AND EXISTS ( SELECT * - FROM sys.databases - WHERE QUOTENAME([name]) = @OutputDatabaseName) - BEGIN - RAISERROR('Calling sp_BlitzCache',10,1) WITH NOWAIT; - /* If they have an newer version of sp_BlitzCache that supports @MinutesBack and @CheckDateOverride */ - IF EXISTS (SELECT * FROM sys.objects o - INNER JOIN sys.parameters pMB ON o.object_id = pMB.object_id AND pMB.name = '@MinutesBack' - INNER JOIN sys.parameters pCDO ON o.object_id = pCDO.object_id AND pCDO.name = '@CheckDateOverride' - WHERE o.name = 'sp_BlitzCache') - BEGIN - /* Get the most recent sp_BlitzCache execution before this one - don't use sp_BlitzFirst because user logs are added in there at any time */ - SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''' - + @OutputSchemaName + ''' AND QUOTENAME(TABLE_NAME) = ''' - + QUOTENAME(@OutputTableNameBlitzCache) + ''') SELECT TOP 1 @BlitzCacheMinutesBack = DATEDIFF(MI,CheckDate,SYSDATETIMEOFFSET()) FROM ' - + @OutputDatabaseName + '.' - + @OutputSchemaName + '.' - + QUOTENAME(@OutputTableNameBlitzCache) - + ' WHERE ServerName = ''' + CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128)) + ''' ORDER BY CheckDate DESC;'; - EXEC sp_executesql @StringToExecute, N'@BlitzCacheMinutesBack INT OUTPUT', @BlitzCacheMinutesBack OUTPUT; - /* If there's no data, let's just analyze the last 15 minutes of the plan cache */ - IF @BlitzCacheMinutesBack IS NULL OR @BlitzCacheMinutesBack < 1 OR @BlitzCacheMinutesBack > 60 - SET @BlitzCacheMinutesBack = 15; - EXEC sp_BlitzCache - @OutputDatabaseName = @UnquotedOutputDatabaseName, - @OutputSchemaName = @UnquotedOutputSchemaName, - @OutputTableName = @OutputTableNameBlitzCache, - @CheckDateOverride = @StartSampleTime, - @SortOrder = 'all', - @SkipAnalysis = @BlitzCacheSkipAnalysis, - @MinutesBack = @BlitzCacheMinutesBack, - @Debug = @Debug; - /* Delete history older than @OutputTableRetentionDays */ - SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' - + @OutputSchemaName + ''') DELETE ' - + @OutputDatabaseName + '.' - + @OutputSchemaName + '.' - + QUOTENAME(@OutputTableNameBlitzCache) - + ' WHERE ServerName = @SrvName AND CheckDate < @CheckDate;'; - EXEC sp_executesql @StringToExecute, - N'@SrvName NVARCHAR(128), @CheckDate date', - @LocalServerName, @OutputTableCleanupDate; - END; - ELSE - BEGIN - /* No sp_BlitzCache found, or it's outdated - CheckID 36 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 36',10,1) WITH NOWAIT; - END - - INSERT INTO #BlitzFirstResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 36 AS CheckID , - 0 AS Priority , - 'Outdated or Missing sp_BlitzCache' AS FindingsGroup , - 'Update Your sp_BlitzCache' AS Finding , - 'http://FirstResponderKit.org/' AS URL , - 'You passed in @OutputTableNameBlitzCache, but we need a newer version of sp_BlitzCache in master or the current database.' AS Details; - END; - - RAISERROR('sp_BlitzCache Finished',10,1) WITH NOWAIT; - - END; /* End running sp_BlitzCache */ - - /* @OutputTableName lets us export the results to a permanent table */ - IF @OutputDatabaseName IS NOT NULL - AND @OutputSchemaName IS NOT NULL - AND @OutputTableName IS NOT NULL - AND @OutputTableName NOT LIKE '#%' - AND EXISTS ( SELECT * - FROM sys.databases - WHERE QUOTENAME([name]) = @OutputDatabaseName) - BEGIN - SET @StringToExecute = 'USE ' - + @OutputDatabaseName - + '; IF EXISTS(SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' - + @OutputSchemaName - + ''') AND NOT EXISTS (SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''' - + @OutputSchemaName + ''' AND QUOTENAME(TABLE_NAME) = ''' - + @OutputTableName + ''') CREATE TABLE ' - + @OutputSchemaName + '.' - + @OutputTableName - + ' (ID INT IDENTITY(1,1) NOT NULL, - ServerName NVARCHAR(128), - CheckDate DATETIMEOFFSET, - CheckID INT NOT NULL, - Priority TINYINT NOT NULL, - FindingsGroup VARCHAR(50) NOT NULL, - Finding VARCHAR(200) NOT NULL, - URL VARCHAR(200) NOT NULL, - Details NVARCHAR(4000) NULL, - HowToStopIt [XML] NULL, - QueryPlan [XML] NULL, - QueryText NVARCHAR(MAX) NULL, - StartTime DATETIMEOFFSET NULL, - LoginName NVARCHAR(128) NULL, - NTUserName NVARCHAR(128) NULL, - OriginalLoginName NVARCHAR(128) NULL, - ProgramName NVARCHAR(128) NULL, - HostName NVARCHAR(128) NULL, - DatabaseID INT NULL, - DatabaseName NVARCHAR(128) NULL, - OpenTransactionCount INT NULL, - DetailsInt INT NULL, - QueryHash BINARY(8) NULL, - JoinKey AS ServerName + CAST(CheckDate AS NVARCHAR(50)), - PRIMARY KEY CLUSTERED (ID ASC));'; - - EXEC(@StringToExecute); - /* If the table doesn't have the new QueryHash column, add it. See Github #2162. */ - SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableName; - SET @StringToExecute = N'IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns - WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''QueryHash'') - ALTER TABLE ' + @ObjectFullName + N' ADD QueryHash BINARY(8) NULL;'; - EXEC(@StringToExecute); - /* If the table doesn't have the new JoinKey computed column, add it. See Github #2164. */ - SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableName; - SET @StringToExecute = N'IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns - WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''JoinKey'') - ALTER TABLE ' + @ObjectFullName + N' ADD JoinKey AS ServerName + CAST(CheckDate AS NVARCHAR(50));'; - EXEC(@StringToExecute); - SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' - + @OutputSchemaName + ''') INSERT ' - + @OutputDatabaseName + '.' - + @OutputSchemaName + '.' - + @OutputTableName - + ' (ServerName, CheckDate, CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, QueryText, StartTime, LoginName, NTUserName, OriginalLoginName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, DetailsInt, QueryHash) SELECT ' - + ' @SrvName, @CheckDate, CheckID, Priority, FindingsGroup, Finding, URL, LEFT(Details,4000), HowToStopIt, QueryPlan, QueryText, StartTime, LoginName, NTUserName, OriginalLoginName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, DetailsInt, QueryHash FROM #BlitzFirstResults ORDER BY Priority , FindingsGroup , Finding , Details'; - - EXEC sp_executesql @StringToExecute, - N'@SrvName NVARCHAR(128), @CheckDate datetimeoffset', - @LocalServerName, @StartSampleTime; - /* Delete history older than @OutputTableRetentionDays */ - SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' - + @OutputSchemaName + ''') DELETE ' - + @OutputDatabaseName + '.' - + @OutputSchemaName + '.' - + @OutputTableName - + ' WHERE ServerName = @SrvName AND CheckDate < @CheckDate ;'; - - EXEC sp_executesql @StringToExecute, - N'@SrvName NVARCHAR(128), @CheckDate date', - @LocalServerName, @OutputTableCleanupDate; - END; - ELSE IF (SUBSTRING(@OutputTableName, 2, 2) = '##') - BEGIN - SET @StringToExecute = N' IF (OBJECT_ID(''tempdb..' - + @OutputTableName - + ''') IS NULL) CREATE TABLE ' - + @OutputTableName - + ' (ID INT IDENTITY(1,1) NOT NULL, - ServerName NVARCHAR(128), - CheckDate DATETIMEOFFSET, - CheckID INT NOT NULL, - Priority TINYINT NOT NULL, - FindingsGroup VARCHAR(50) NOT NULL, - Finding VARCHAR(200) NOT NULL, - URL VARCHAR(200) NOT NULL, - Details NVARCHAR(4000) NULL, - HowToStopIt [XML] NULL, - QueryPlan [XML] NULL, - QueryText NVARCHAR(MAX) NULL, - StartTime DATETIMEOFFSET NULL, - LoginName NVARCHAR(128) NULL, - NTUserName NVARCHAR(128) NULL, - OriginalLoginName NVARCHAR(128) NULL, - ProgramName NVARCHAR(128) NULL, - HostName NVARCHAR(128) NULL, - DatabaseID INT NULL, - DatabaseName NVARCHAR(128) NULL, - OpenTransactionCount INT NULL, - DetailsInt INT NULL, - QueryHash BINARY(8) NULL, - JoinKey AS ServerName + CAST(CheckDate AS NVARCHAR(50)), - PRIMARY KEY CLUSTERED (ID ASC));' - + ' INSERT ' - + @OutputTableName - + ' (ServerName, CheckDate, CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, QueryText, StartTime, LoginName, NTUserName, OriginalLoginName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, DetailsInt) SELECT ' - + ' @SrvName, @CheckDate, CheckID, Priority, FindingsGroup, Finding, URL, LEFT(Details,4000), HowToStopIt, QueryPlan, QueryText, StartTime, LoginName, NTUserName, OriginalLoginName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, DetailsInt FROM #BlitzFirstResults ORDER BY Priority , FindingsGroup , Finding , Details'; - - EXEC sp_executesql @StringToExecute, - N'@SrvName NVARCHAR(128), @CheckDate datetimeoffset', - @LocalServerName, @StartSampleTime; - END; - ELSE IF (SUBSTRING(@OutputTableName, 2, 1) = '#') - BEGIN - RAISERROR('Due to the nature of Dymamic SQL, only global (i.e. double pound (##)) temp tables are supported for @OutputTableName', 16, 0); - END; - /* @OutputTableNameFileStats lets us export the results to a permanent table */ - IF @OutputDatabaseName IS NOT NULL - AND @OutputSchemaName IS NOT NULL - AND @OutputTableNameFileStats IS NOT NULL - AND @OutputTableNameFileStats NOT LIKE '#%' - AND EXISTS ( SELECT * - FROM sys.databases - WHERE QUOTENAME([name]) = @OutputDatabaseName) - BEGIN - /* Create the table */ - SET @StringToExecute = 'USE ' - + @OutputDatabaseName - + '; IF EXISTS(SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' - + @OutputSchemaName - + ''') AND NOT EXISTS (SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''' - + @OutputSchemaName + ''' AND QUOTENAME(TABLE_NAME) = ''' - + @OutputTableNameFileStats + ''') CREATE TABLE ' - + @OutputSchemaName + '.' - + @OutputTableNameFileStats - + ' (ID INT IDENTITY(1,1) NOT NULL, - ServerName NVARCHAR(128), - CheckDate DATETIMEOFFSET, - DatabaseID INT NOT NULL, - FileID INT NOT NULL, - DatabaseName NVARCHAR(256) , - FileLogicalName NVARCHAR(256) , - TypeDesc NVARCHAR(60) , - SizeOnDiskMB BIGINT , - io_stall_read_ms BIGINT , - num_of_reads BIGINT , - bytes_read BIGINT , - io_stall_write_ms BIGINT , - num_of_writes BIGINT , - bytes_written BIGINT, - PhysicalName NVARCHAR(520) , - PRIMARY KEY CLUSTERED (ID ASC));'; - EXEC(@StringToExecute); - SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableNameFileStats_View; +ELSE IF @Mode IN (0, 4) /* DIAGNOSE*//* IF @TableName IS NOT NULL, so @TableName must not be null */ +BEGIN; + IF @Mode IN (0, 4) /* DIAGNOSE priorities 1-100 */ + BEGIN; + RAISERROR(N'@Mode=0 or 4, running rules for priorities 1-100.', 0,1) WITH NOWAIT; - /* If the view exists without the most recently added columns, drop it. See Github #2162. */ - IF OBJECT_ID(@ObjectFullName) IS NOT NULL - BEGIN - SET @StringToExecute = N'USE ' + @OutputDatabaseName + N'; IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns - WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''JoinKey'') - DROP VIEW ' + @OutputSchemaName + N'.' + @OutputTableNameFileStats_View + N';'; + ---------------------------------------- + --Multiple Index Personalities: Check_id 0-10 + ---------------------------------------- + RAISERROR('check_id 1: Duplicate keys', 0,1) WITH NOWAIT; + WITH duplicate_indexes + AS ( SELECT [object_id], key_column_names, database_id, [schema_name] + FROM #IndexSanity AS ip + WHERE index_type IN (1,2) /* Clustered, NC only*/ + AND is_hypothetical = 0 + AND is_disabled = 0 + AND is_primary_key = 0 + AND EXISTS ( + SELECT 1/0 + FROM #IndexSanitySize ips + WHERE ip.index_sanity_id = ips.index_sanity_id + AND ip.database_id = ips.database_id + AND ip.schema_name = ips.schema_name + AND ips.total_reserved_MB >= CASE + WHEN (@GetAllDatabases = 1 OR @Mode = 0) + THEN @ThresholdMB + ELSE ips.total_reserved_MB + END + ) + GROUP BY [object_id], key_column_names, database_id, [schema_name] + HAVING COUNT(*) > 1) + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 1 AS check_id, + ip.index_sanity_id, + 20 AS Priority, + 'Multiple Index Personalities' AS findings_group, + 'Duplicate keys' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/duplicateindex' AS URL, + N'Index Name: ' + ip.index_name + N' Table Name: ' + ip.db_schema_object_name AS details, + ip.index_definition, + ip.secret_columns, + ip.index_usage_summary, + ips.index_size_summary + FROM duplicate_indexes di + JOIN #IndexSanity ip ON di.[object_id] = ip.[object_id] + AND ip.database_id = di.database_id + AND ip.[schema_name] = di.[schema_name] + AND di.key_column_names = ip.key_column_names + JOIN #IndexSanitySize ips ON ip.index_sanity_id = ips.index_sanity_id + AND ip.database_id = ips.database_id + AND ip.schema_name = ips.schema_name + /* WHERE clause limits to only @ThresholdMB or larger duplicate indexes when getting all databases or using PainRelief mode */ + WHERE ips.total_reserved_MB >= CASE WHEN (@GetAllDatabases = 1 OR @Mode = 0) THEN @ThresholdMB ELSE ips.total_reserved_MB END + AND ip.is_primary_key = 0 + ORDER BY ips.total_rows DESC, ip.[schema_name], ip.[object_name], ip.key_column_names_with_sort_order + OPTION ( RECOMPILE ); - EXEC(@StringToExecute); - END + RAISERROR('check_id 2: Keys w/ identical leading columns.', 0,1) WITH NOWAIT; + WITH borderline_duplicate_indexes + AS ( SELECT DISTINCT database_id, [object_id], first_key_column_name, key_column_names, + COUNT([object_id]) OVER ( PARTITION BY database_id, [object_id], first_key_column_name ) AS number_dupes + FROM #IndexSanity + WHERE index_type IN (1,2) /* Clustered, NC only*/ + AND is_hypothetical=0 + AND is_disabled=0 + AND is_primary_key = 0) + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 2 AS check_id, + ip.index_sanity_id, + 30 AS Priority, + 'Multiple Index Personalities' AS findings_group, + 'Borderline duplicate keys' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/duplicateindex' AS URL, + ip.db_schema_object_indexid AS details, + ip.index_definition, + ip.secret_columns, + ip.index_usage_summary, + ips.index_size_summary + FROM #IndexSanity AS ip + JOIN #IndexSanitySize ips ON ip.index_sanity_id = ips.index_sanity_id + WHERE EXISTS ( + SELECT di.[object_id] + FROM borderline_duplicate_indexes AS di + WHERE di.[object_id] = ip.[object_id] AND + di.database_id = ip.database_id AND + di.first_key_column_name = ip.first_key_column_name AND + di.key_column_names <> ip.key_column_names AND + di.number_dupes > 1 + ) + AND ip.is_primary_key = 0 + ORDER BY ips.total_rows DESC, ip.[schema_name], ip.[object_name], ip.key_column_names, ip.include_column_names + OPTION ( RECOMPILE ); - /* Create the view */ - IF OBJECT_ID(@ObjectFullName) IS NULL - BEGIN - SET @StringToExecute = 'USE ' - + @OutputDatabaseName - + '; EXEC (''CREATE VIEW ' - + @OutputSchemaName + '.' - + @OutputTableNameFileStats_View + ' AS ' + @LineFeed - + 'WITH RowDates as' + @LineFeed - + '(' + @LineFeed - + ' SELECT ' + @LineFeed - + ' ROW_NUMBER() OVER (ORDER BY [ServerName], [CheckDate]) ID,' + @LineFeed - + ' [CheckDate]' + @LineFeed - + ' FROM ' + @OutputSchemaName + '.' + @OutputTableNameFileStats + '' + @LineFeed - + ' GROUP BY [ServerName], [CheckDate]' + @LineFeed - + '),' + @LineFeed - + 'CheckDates as' + @LineFeed - + '(' + @LineFeed - + ' SELECT ThisDate.CheckDate,' + @LineFeed - + ' LastDate.CheckDate as PreviousCheckDate' + @LineFeed - + ' FROM RowDates ThisDate' + @LineFeed - + ' JOIN RowDates LastDate' + @LineFeed - + ' ON ThisDate.ID = LastDate.ID + 1' + @LineFeed - + ')' + @LineFeed - + ' SELECT f.ServerName,' + @LineFeed - + ' f.CheckDate,' + @LineFeed - + ' f.DatabaseID,' + @LineFeed - + ' f.DatabaseName,' + @LineFeed - + ' f.FileID,' + @LineFeed - + ' f.FileLogicalName,' + @LineFeed - + ' f.TypeDesc,' + @LineFeed - + ' f.PhysicalName,' + @LineFeed - + ' f.SizeOnDiskMB,' + @LineFeed - + ' DATEDIFF(ss, fPrior.CheckDate, f.CheckDate) AS ElapsedSeconds,' + @LineFeed - + ' (f.SizeOnDiskMB - fPrior.SizeOnDiskMB) AS SizeOnDiskMBgrowth,' + @LineFeed - + ' (f.io_stall_read_ms - fPrior.io_stall_read_ms) AS io_stall_read_ms,' + @LineFeed - + ' io_stall_read_ms_average = CASE' + @LineFeed - + ' WHEN(f.num_of_reads - fPrior.num_of_reads) = 0' + @LineFeed - + ' THEN 0' + @LineFeed - + ' ELSE(f.io_stall_read_ms - fPrior.io_stall_read_ms) / (f.num_of_reads - fPrior.num_of_reads)' + @LineFeed - + ' END,' + @LineFeed - + ' (f.num_of_reads - fPrior.num_of_reads) AS num_of_reads,' + @LineFeed - + ' (f.bytes_read - fPrior.bytes_read) / 1024.0 / 1024.0 AS megabytes_read,' + @LineFeed - + ' (f.io_stall_write_ms - fPrior.io_stall_write_ms) AS io_stall_write_ms,' + @LineFeed - + ' io_stall_write_ms_average = CASE' + @LineFeed - + ' WHEN(f.num_of_writes - fPrior.num_of_writes) = 0' + @LineFeed - + ' THEN 0' + @LineFeed - + ' ELSE(f.io_stall_write_ms - fPrior.io_stall_write_ms) / (f.num_of_writes - fPrior.num_of_writes)' + @LineFeed - + ' END,' + @LineFeed - + ' (f.num_of_writes - fPrior.num_of_writes) AS num_of_writes,' + @LineFeed - + ' (f.bytes_written - fPrior.bytes_written) / 1024.0 / 1024.0 AS megabytes_written, ' + @LineFeed - + ' f.ServerName + CAST(f.CheckDate AS NVARCHAR(50)) AS JoinKey' + @LineFeed - + ' FROM ' + @OutputSchemaName + '.' + @OutputTableNameFileStats + ' f' + @LineFeed - + ' INNER HASH JOIN CheckDates DATES ON f.CheckDate = DATES.CheckDate' + @LineFeed - + ' INNER JOIN ' + @OutputSchemaName + '.' + @OutputTableNameFileStats + ' fPrior ON f.ServerName = fPrior.ServerName' + @LineFeed - + ' AND f.DatabaseID = fPrior.DatabaseID' + @LineFeed - + ' AND f.FileID = fPrior.FileID' + @LineFeed - + ' AND fPrior.CheckDate = DATES.PreviousCheckDate' + @LineFeed - + '' + @LineFeed - + ' WHERE f.num_of_reads >= fPrior.num_of_reads' + @LineFeed - + ' AND f.num_of_writes >= fPrior.num_of_writes' + @LineFeed - + ' AND DATEDIFF(MI, fPrior.CheckDate, f.CheckDate) BETWEEN 1 AND 60;'')' + ---------------------------------------- + --Aggressive Indexes: Check_id 10-19 + ---------------------------------------- - EXEC(@StringToExecute); - END; + RAISERROR(N'check_id 11: Total lock wait time > 5 minutes (row + page)', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 11 AS check_id, + i.index_sanity_id, + 70 AS Priority, + N'Aggressive ' + + CASE COALESCE((SELECT SUM(1) + FROM #IndexSanity iMe + INNER JOIN #IndexSanity iOthers + ON iMe.database_id = iOthers.database_id + AND iMe.object_id = iOthers.object_id + AND iOthers.index_id > 1 + WHERE i.index_sanity_id = iMe.index_sanity_id + AND iOthers.is_hypothetical = 0 + AND iOthers.is_disabled = 0 + ), 0) + WHEN 0 THEN N'Under-Indexing' + WHEN 1 THEN N'Under-Indexing' + WHEN 2 THEN N'Under-Indexing' + WHEN 3 THEN N'Under-Indexing' + WHEN 4 THEN N'Indexes' + WHEN 5 THEN N'Indexes' + WHEN 6 THEN N'Indexes' + WHEN 7 THEN N'Indexes' + WHEN 8 THEN N'Indexes' + WHEN 9 THEN N'Indexes' + ELSE N'Over-Indexing' + END AS findings_group, + N'Total lock wait time > 5 minutes (row + page)' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/AggressiveIndexes' AS URL, + (i.db_schema_object_indexid + N': ' + + sz.index_lock_wait_summary + N' NC indexes on table: ') COLLATE DATABASE_DEFAULT + + CAST(COALESCE((SELECT SUM(1) + FROM #IndexSanity iMe + INNER JOIN #IndexSanity iOthers + ON iMe.database_id = iOthers.database_id + AND iMe.object_id = iOthers.object_id + AND iOthers.index_id > 1 + WHERE i.index_sanity_id = iMe.index_sanity_id + AND iOthers.is_hypothetical = 0 + AND iOthers.is_disabled = 0 + ), 0) + AS NVARCHAR(30)) AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + sz.index_size_summary + FROM #IndexSanity AS i + JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id + WHERE (total_row_lock_wait_in_ms + total_page_lock_wait_in_ms) > 300000 + GROUP BY i.index_sanity_id, [database_name], i.db_schema_object_indexid, sz.index_lock_wait_summary, i.index_definition, i.secret_columns, i.index_usage_summary, sz.index_size_summary, sz.index_sanity_id + ORDER BY SUM(total_row_lock_wait_in_ms + total_page_lock_wait_in_ms) DESC, 4, [database_name], 8 + OPTION ( RECOMPILE ); - SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' - + @OutputSchemaName + ''') INSERT ' - + @OutputDatabaseName + '.' - + @OutputSchemaName + '.' - + @OutputTableNameFileStats - + ' (ServerName, CheckDate, DatabaseID, FileID, DatabaseName, FileLogicalName, TypeDesc, SizeOnDiskMB, io_stall_read_ms, num_of_reads, bytes_read, io_stall_write_ms, num_of_writes, bytes_written, PhysicalName) SELECT ' - + ' @SrvName, @CheckDate, DatabaseID, FileID, DatabaseName, FileLogicalName, TypeDesc, SizeOnDiskMB, io_stall_read_ms, num_of_reads, bytes_read, io_stall_write_ms, num_of_writes, bytes_written, PhysicalName FROM #FileStats WHERE Pass = 2'; - EXEC sp_executesql @StringToExecute, - N'@SrvName NVARCHAR(128), @CheckDate datetimeoffset', - @LocalServerName, @StartSampleTime; + ---------------------------------------- + --Index Hoarder: Check_id 20-29 + ---------------------------------------- + RAISERROR(N'check_id 20: >= 10 NC indexes on any given table. Yes, 10 is an arbitrary number.', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 20 AS check_id, + MAX(i.index_sanity_id) AS index_sanity_id, + 10 AS Priority, + 'Index Hoarder' AS findings_group, + 'Many NC Indexes on a Single Table' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/IndexHoarder' AS URL, + CAST (COUNT(*) AS NVARCHAR(30)) + ' NC indexes on ' + i.db_schema_object_name AS details, + i.db_schema_object_name + ' (' + CAST (COUNT(*) AS NVARCHAR(30)) + ' indexes)' AS index_definition, + '' AS secret_columns, + REPLACE(CONVERT(NVARCHAR(30),CAST(SUM(total_reads) AS MONEY), 1), N'.00', N'') + N' reads (ALL); ' + + REPLACE(CONVERT(NVARCHAR(30),CAST(SUM(user_updates) AS MONEY), 1), N'.00', N'') + N' writes (ALL); ', + REPLACE(CONVERT(NVARCHAR(30),CAST(MAX(total_rows) AS MONEY), 1), N'.00', N'') + N' rows (MAX)' + + CASE WHEN SUM(total_reserved_MB) > 1024 THEN + N'; ' + CAST(CAST(SUM(total_reserved_MB)/1024. AS NUMERIC(29,1)) AS NVARCHAR(30)) + 'GB (ALL)' + WHEN SUM(total_reserved_MB) > 0 THEN + N'; ' + CAST(CAST(SUM(total_reserved_MB) AS NUMERIC(29,1)) AS NVARCHAR(30)) + 'MB (ALL)' + ELSE '' + END AS index_size_summary + FROM #IndexSanity i + JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id + WHERE index_id NOT IN ( 0, 1 ) + GROUP BY db_schema_object_name, [i].[database_name] + HAVING COUNT(*) >= 10 + ORDER BY i.db_schema_object_name DESC + OPTION ( RECOMPILE ); - /* Delete history older than @OutputTableRetentionDays */ - SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' - + @OutputSchemaName + ''') DELETE ' - + @OutputDatabaseName + '.' - + @OutputSchemaName + '.' - + @OutputTableNameFileStats - + ' WHERE ServerName = @SrvName AND CheckDate < @CheckDate ;'; + RAISERROR(N'check_id 22: NC indexes with 0 reads. (Borderline) and >= 10,000 writes', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 22 AS check_id, + i.index_sanity_id, + 10 AS Priority, + N'Index Hoarder' AS findings_group, + N'Unused NC Index with High Writes' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/IndexHoarder' AS URL, + N'Reads: 0,' + + N' Writes: ' + + REPLACE(CONVERT(NVARCHAR(30), CAST((i.user_updates) AS MONEY), 1), N'.00', N'') + + N' on: ' + + i.db_schema_object_indexid + AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + sz.index_size_summary + FROM #IndexSanity AS i + JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id + WHERE i.total_reads=0 + AND i.user_updates >= 10000 + 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 + AND @Filter <> 1 /* 1 = "ignore unused */ + ORDER BY i.db_schema_object_indexid + OPTION ( RECOMPILE ); - EXEC sp_executesql @StringToExecute, - N'@SrvName NVARCHAR(128), @CheckDate date', - @LocalServerName, @OutputTableCleanupDate; - END; - ELSE IF (SUBSTRING(@OutputTableNameFileStats, 2, 2) = '##') - BEGIN - SET @StringToExecute = N' IF (OBJECT_ID(''tempdb..' - + @OutputTableNameFileStats - + ''') IS NULL) CREATE TABLE ' - + @OutputTableNameFileStats - + ' (ID INT IDENTITY(1,1) NOT NULL, - ServerName NVARCHAR(128), - CheckDate DATETIMEOFFSET, - DatabaseID INT NOT NULL, - FileID INT NOT NULL, - DatabaseName NVARCHAR(256) , - FileLogicalName NVARCHAR(256) , - TypeDesc NVARCHAR(60) , - SizeOnDiskMB BIGINT , - io_stall_read_ms BIGINT , - num_of_reads BIGINT , - bytes_read BIGINT , - io_stall_write_ms BIGINT , - num_of_writes BIGINT , - bytes_written BIGINT, - PhysicalName NVARCHAR(520) , - DetailsInt INT NULL, - PRIMARY KEY CLUSTERED (ID ASC));' - + ' INSERT ' - + @OutputTableNameFileStats - + ' (ServerName, CheckDate, DatabaseID, FileID, DatabaseName, FileLogicalName, TypeDesc, SizeOnDiskMB, io_stall_read_ms, num_of_reads, bytes_read, io_stall_write_ms, num_of_writes, bytes_written, PhysicalName) SELECT ' - + ' @SrvName, @CheckDate, DatabaseID, FileID, DatabaseName, FileLogicalName, TypeDesc, SizeOnDiskMB, io_stall_read_ms, num_of_reads, bytes_read, io_stall_write_ms, num_of_writes, bytes_written, PhysicalName FROM #FileStats WHERE Pass = 2'; + RAISERROR(N'check_id 34: Filtered index definition columns not in index definition', 0,1) WITH NOWAIT; + + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 34 AS check_id, + i.index_sanity_id, + 80 AS Priority, + N'Abnormal Psychology' AS findings_group, + N'Filter Columns Not In Index Definition' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/IndexFeatures' AS URL, + N'The index ' + + QUOTENAME(i.index_name) + + N' on [' + + i.db_schema_object_name + + N'] has a filter on [' + + i.filter_definition + + N'] but is missing [' + + LTRIM(i.filter_columns_not_in_index) + + N'] from the index definition.' + AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + sz.index_size_summary + FROM #IndexSanity i + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + WHERE i.filter_columns_not_in_index IS NOT NULL + ORDER BY i.db_schema_object_indexid + OPTION ( RECOMPILE ); + + ---------------------------------------- + --Self Loathing Indexes : Check_id 40-49 + ---------------------------------------- + + RAISERROR(N'check_id 40: Fillfactor in nonclustered 80 percent or less', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 40 AS check_id, + i.index_sanity_id, + 100 AS Priority, + N'Self Loathing Indexes' AS findings_group, + N'Low Fill Factor on Nonclustered Index' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/SelfLoathing' AS URL, + CAST(fill_factor AS NVARCHAR(10)) + N'% fill factor on ' + db_schema_object_indexid + N'. '+ + CASE WHEN (last_user_update IS NULL OR user_updates < 1) + THEN N'No writes have been made.' + ELSE + N'Last write was ' + CONVERT(NVARCHAR(16),last_user_update,121) + N' and ' + + CAST(user_updates AS NVARCHAR(25)) + N' updates have been made.' + END + AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + sz.index_size_summary + FROM #IndexSanity AS i + JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id + WHERE index_id > 1 + AND fill_factor BETWEEN 1 AND 80 OPTION ( RECOMPILE ); - EXEC sp_executesql @StringToExecute, - N'@SrvName NVARCHAR(128), @CheckDate datetimeoffset', - @LocalServerName, @StartSampleTime; - END; - ELSE IF (SUBSTRING(@OutputTableNameFileStats, 2, 1) = '#') - BEGIN - RAISERROR('Due to the nature of Dymamic SQL, only global (i.e. double pound (##)) temp tables are supported for @OutputTableName', 16, 0); - END; + RAISERROR(N'check_id 40: Fillfactor in clustered 80 percent or less', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 40 AS check_id, + i.index_sanity_id, + 100 AS Priority, + N'Self Loathing Indexes' AS findings_group, + N'Low Fill Factor on Clustered Index' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/SelfLoathing' AS URL, + N'Fill factor on ' + db_schema_object_indexid + N' is ' + CAST(fill_factor AS NVARCHAR(10)) + N'%. '+ + CASE WHEN (last_user_update IS NULL OR user_updates < 1) + THEN N'No writes have been made.' + ELSE + N'Last write was ' + CONVERT(NVARCHAR(16),last_user_update,121) + N' and ' + + CAST(user_updates AS NVARCHAR(25)) + N' updates have been made.' + END + AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + sz.index_size_summary + FROM #IndexSanity AS i + JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id + WHERE index_id = 1 + AND fill_factor BETWEEN 1 AND 80 OPTION ( RECOMPILE ); - /* @OutputTableNamePerfmonStats lets us export the results to a permanent table */ - IF @OutputDatabaseName IS NOT NULL - AND @OutputSchemaName IS NOT NULL - AND @OutputTableNamePerfmonStats IS NOT NULL - AND @OutputTableNamePerfmonStats NOT LIKE '#%' - AND EXISTS ( SELECT * - FROM sys.databases - WHERE QUOTENAME([name]) = @OutputDatabaseName) - BEGIN - /* Create the table */ - SET @StringToExecute = 'USE ' - + @OutputDatabaseName - + '; IF EXISTS(SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' - + @OutputSchemaName - + ''') AND NOT EXISTS (SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''' - + @OutputSchemaName + ''' AND QUOTENAME(TABLE_NAME) = ''' - + @OutputTableNamePerfmonStats + ''') CREATE TABLE ' - + @OutputSchemaName + '.' - + @OutputTableNamePerfmonStats - + ' (ID INT IDENTITY(1,1) NOT NULL, - ServerName NVARCHAR(128), - CheckDate DATETIMEOFFSET, - [object_name] NVARCHAR(128) NOT NULL, - [counter_name] NVARCHAR(128) NOT NULL, - [instance_name] NVARCHAR(128) NULL, - [cntr_value] BIGINT NULL, - [cntr_type] INT NOT NULL, - [value_delta] BIGINT NULL, - [value_per_second] DECIMAL(18,2) NULL, - PRIMARY KEY CLUSTERED (ID ASC));'; + RAISERROR(N'check_id 43: Heaps with forwarded records', 0,1) WITH NOWAIT; + WITH heaps_cte + AS ( SELECT [object_id], + [database_id], + [schema_name], + SUM(forwarded_fetch_count) AS forwarded_fetch_count, + SUM(leaf_delete_count) AS leaf_delete_count + FROM #IndexPartitionSanity + GROUP BY [object_id], + [database_id], + [schema_name] + HAVING SUM(forwarded_fetch_count) > 0) + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 43 AS check_id, + i.index_sanity_id, + 100 AS Priority, + N'Self Loathing Indexes' AS findings_group, + N'Heaps with Forwarded Fetches' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/SelfLoathing' AS URL, + CASE WHEN h.forwarded_fetch_count >= 922337203685477 THEN '>= 922,337,203,685,477' + WHEN @DaysUptime < 1 THEN CAST(h.forwarded_fetch_count AS NVARCHAR(256)) + N' forwarded fetches against heap: ' + db_schema_object_indexid + ELSE REPLACE(CONVERT(NVARCHAR(256),CAST(CAST( + (h.forwarded_fetch_count /*/@DaysUptime */) + AS BIGINT) AS MONEY), 1), '.00', '') + END + N' forwarded fetches per day against heap: ' + + db_schema_object_indexid AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + sz.index_size_summary + FROM #IndexSanity i + JOIN heaps_cte h ON i.[object_id] = h.[object_id] + AND i.[database_id] = h.[database_id] + AND i.[schema_name] = h.[schema_name] + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + WHERE i.index_id = 0 + AND h.forwarded_fetch_count / @DaysUptime > 1000 + AND sz.total_reserved_MB >= CASE WHEN NOT (@GetAllDatabases = 1 OR @Mode = 4) THEN @ThresholdMB ELSE sz.total_reserved_MB END + OPTION ( RECOMPILE ); - EXEC(@StringToExecute); + RAISERROR(N'check_id 44: Large Heaps with reads or writes.', 0,1) WITH NOWAIT; + WITH heaps_cte + AS ( SELECT [object_id], + [database_id], + [schema_name], + SUM(forwarded_fetch_count) AS forwarded_fetch_count, + SUM(leaf_delete_count) AS leaf_delete_count + FROM #IndexPartitionSanity + GROUP BY [object_id], + [database_id], + [schema_name] + HAVING SUM(forwarded_fetch_count) > 0 + OR SUM(leaf_delete_count) > 0) + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 44 AS check_id, + i.index_sanity_id, + 100 AS Priority, + N'Self Loathing Indexes' AS findings_group, + N'Large Active Heap' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/SelfLoathing' AS URL, + N'Should this table be a heap? ' + db_schema_object_indexid AS details, + i.index_definition, + 'N/A' AS secret_columns, + i.index_usage_summary, + sz.index_size_summary + FROM #IndexSanity i + LEFT JOIN heaps_cte h ON i.[object_id] = h.[object_id] + AND i.[database_id] = h.[database_id] + AND i.[schema_name] = h.[schema_name] + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + WHERE i.index_id = 0 + AND (i.total_reads > 0 OR i.user_updates > 0) + AND sz.total_rows >= 100000 + AND h.[object_id] IS NULL /*don't duplicate the prior check.*/ + OPTION ( RECOMPILE ); - SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableNamePerfmonStats_View; + RAISERROR(N'check_id 45: Medium Heaps with reads or writes.', 0,1) WITH NOWAIT; + WITH heaps_cte + AS ( SELECT [object_id], + [database_id], + [schema_name], + SUM(forwarded_fetch_count) AS forwarded_fetch_count, + SUM(leaf_delete_count) AS leaf_delete_count + FROM #IndexPartitionSanity + GROUP BY [object_id], + [database_id], + [schema_name] + HAVING SUM(forwarded_fetch_count) > 0 + OR SUM(leaf_delete_count) > 0) + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 45 AS check_id, + i.index_sanity_id, + 100 AS Priority, + N'Self Loathing Indexes' AS findings_group, + N'Medium Active heap' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/SelfLoathing' AS URL, + N'Should this table be a heap? ' + db_schema_object_indexid AS details, + i.index_definition, + 'N/A' AS secret_columns, + i.index_usage_summary, + sz.index_size_summary + FROM #IndexSanity i + LEFT JOIN heaps_cte h ON i.[object_id] = h.[object_id] + AND i.[database_id] = h.[database_id] + AND i.[schema_name] = h.[schema_name] + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + WHERE i.index_id = 0 + AND + (i.total_reads > 0 OR i.user_updates > 0) + AND sz.total_rows >= 10000 AND sz.total_rows < 100000 + AND h.[object_id] IS NULL /*don't duplicate the prior check.*/ + OPTION ( RECOMPILE ); - /* If the view exists without the most recently added columns, drop it. See Github #2162. */ - IF OBJECT_ID(@ObjectFullName) IS NOT NULL - BEGIN - SET @StringToExecute = N'USE ' + @OutputDatabaseName + N'; IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns - WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''JoinKey'') - DROP VIEW ' + @OutputSchemaName + N'.' + @OutputTableNamePerfmonStats_View + N';'; + RAISERROR(N'check_id 46: Small Heaps with reads or writes.', 0,1) WITH NOWAIT; + WITH heaps_cte + AS ( SELECT [object_id], + [database_id], + [schema_name], + SUM(forwarded_fetch_count) AS forwarded_fetch_count, + SUM(leaf_delete_count) AS leaf_delete_count + FROM #IndexPartitionSanity + GROUP BY [object_id], + [database_id], + [schema_name] + HAVING SUM(forwarded_fetch_count) > 0 + OR SUM(leaf_delete_count) > 0) + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 46 AS check_id, + i.index_sanity_id, + 100 AS Priority, + N'Self Loathing Indexes' AS findings_group, + N'Small Active heap' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/SelfLoathing' AS URL, + N'Should this table be a heap? ' + db_schema_object_indexid AS details, + i.index_definition, + 'N/A' AS secret_columns, + i.index_usage_summary, + sz.index_size_summary + FROM #IndexSanity i + LEFT JOIN heaps_cte h ON i.[object_id] = h.[object_id] + AND i.[database_id] = h.[database_id] + AND i.[schema_name] = h.[schema_name] + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + WHERE i.index_id = 0 + AND + (i.total_reads > 0 OR i.user_updates > 0) + AND sz.total_rows < 10000 + AND h.[object_id] IS NULL /*don't duplicate the prior check.*/ + OPTION ( RECOMPILE ); - EXEC(@StringToExecute); - END + RAISERROR(N'check_id 47: Heap with a Nonclustered Primary Key', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 47 AS check_id, + i.index_sanity_id, + 100 AS Priority, + N'Self Loathing Indexes' AS findings_group, + N'Heap with a Nonclustered Primary Key' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/SelfLoathing' AS URL, + db_schema_object_indexid + N' is a HEAP with a Nonclustered Primary Key' AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + sz.index_size_summary + FROM #IndexSanity i + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + WHERE i.index_type = 2 AND i.is_primary_key = 1 + AND EXISTS + ( + SELECT 1/0 + FROM #IndexSanity AS isa + WHERE i.database_id = isa.database_id + AND i.object_id = isa.object_id + AND isa.index_id = 0 + ) + OPTION ( RECOMPILE ); - /* Create the view */ - IF OBJECT_ID(@ObjectFullName) IS NULL - BEGIN - SET @StringToExecute = 'USE ' - + @OutputDatabaseName - + '; EXEC (''CREATE VIEW ' - + @OutputSchemaName + '.' - + @OutputTableNamePerfmonStats_View + ' AS ' + @LineFeed - + 'WITH RowDates as' + @LineFeed - + '(' + @LineFeed - + ' SELECT ' + @LineFeed - + ' ROW_NUMBER() OVER (ORDER BY [ServerName], [CheckDate]) ID,' + @LineFeed - + ' [CheckDate]' + @LineFeed - + ' FROM ' + @OutputSchemaName + '.' +@OutputTableNamePerfmonStats + '' + @LineFeed - + ' GROUP BY [ServerName], [CheckDate]' + @LineFeed - + '),' + @LineFeed - + 'CheckDates as' + @LineFeed - + '(' + @LineFeed - + ' SELECT ThisDate.CheckDate,' + @LineFeed - + ' LastDate.CheckDate as PreviousCheckDate' + @LineFeed - + ' FROM RowDates ThisDate' + @LineFeed - + ' JOIN RowDates LastDate' + @LineFeed - + ' ON ThisDate.ID = LastDate.ID + 1' + @LineFeed - + ')' + @LineFeed - + 'SELECT' + @LineFeed - + ' pMon.[ServerName]' + @LineFeed - + ' ,pMon.[CheckDate]' + @LineFeed - + ' ,pMon.[object_name]' + @LineFeed - + ' ,pMon.[counter_name]' + @LineFeed - + ' ,pMon.[instance_name]' + @LineFeed - + ' ,DATEDIFF(SECOND,pMonPrior.[CheckDate],pMon.[CheckDate]) AS ElapsedSeconds' + @LineFeed - + ' ,pMon.[cntr_value]' + @LineFeed - + ' ,pMon.[cntr_type]' + @LineFeed - + ' ,(pMon.[cntr_value] - pMonPrior.[cntr_value]) AS cntr_delta' + @LineFeed - + ' ,(pMon.cntr_value - pMonPrior.cntr_value) * 1.0 / DATEDIFF(ss, pMonPrior.CheckDate, pMon.CheckDate) AS cntr_delta_per_second' + @LineFeed - + ' ,pMon.ServerName + CAST(pMon.CheckDate AS NVARCHAR(50)) AS JoinKey' + @LineFeed - + ' FROM ' + @OutputSchemaName + '.' +@OutputTableNamePerfmonStats + ' pMon' + @LineFeed - + ' INNER HASH JOIN CheckDates Dates' + @LineFeed - + ' ON Dates.CheckDate = pMon.CheckDate' + @LineFeed - + ' JOIN ' + @OutputSchemaName + '.' +@OutputTableNamePerfmonStats + ' pMonPrior' + @LineFeed - + ' ON Dates.PreviousCheckDate = pMonPrior.CheckDate' + @LineFeed - + ' AND pMon.[ServerName] = pMonPrior.[ServerName] ' + @LineFeed - + ' AND pMon.[object_name] = pMonPrior.[object_name] ' + @LineFeed - + ' AND pMon.[counter_name] = pMonPrior.[counter_name] ' + @LineFeed - + ' AND pMon.[instance_name] = pMonPrior.[instance_name]' + @LineFeed - + ' WHERE DATEDIFF(MI, pMonPrior.CheckDate, pMon.CheckDate) BETWEEN 1 AND 60;'')' + RAISERROR(N'check_id 48: Nonclustered indexes with a bad read to write ratio', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 48 AS check_id, + i.index_sanity_id, + 100 AS Priority, + N'Index Hoarder' AS findings_group, + N'NC index with High Writes:Reads' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/IndexHoarder' AS URL, + N'Reads: ' + + REPLACE(CONVERT(NVARCHAR(30), CAST((i.total_reads) AS MONEY), 1), N'.00', N'') + + N' Writes: ' + + REPLACE(CONVERT(NVARCHAR(30), CAST((i.user_updates) AS MONEY), 1), N'.00', N'') + + N' on: ' + + i.db_schema_object_indexid AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + sz.index_size_summary + FROM #IndexSanity i + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + WHERE i.total_reads > 0 /*Not totally unused*/ + AND i.user_updates >= 10000 /*Decent write activity*/ + AND i.total_reads < 10000 + AND ((i.total_reads * 10) < i.user_updates) /*10x more writes than reads*/ + 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 + ORDER BY i.db_schema_object_indexid + OPTION ( RECOMPILE ); - EXEC(@StringToExecute); - END + ---------------------------------------- + --Indexaphobia + --Missing indexes with value >= 5 million: : Check_id 50-59 + ---------------------------------------- + RAISERROR(N'check_id 50: Indexaphobia.', 0,1) WITH NOWAIT; + WITH index_size_cte + AS ( SELECT i.database_id, + i.schema_name, + i.[object_id], + MAX(i.index_sanity_id) AS index_sanity_id, + ISNULL(NULLIF(MAX(DATEDIFF(DAY, i.create_date, SYSDATETIME())), 0), 1) AS create_days, + ISNULL ( + CAST(SUM(CASE WHEN index_id NOT IN (0,1) THEN 1 ELSE 0 END) + AS NVARCHAR(30))+ N' NC indexes exist (' + + CASE WHEN SUM(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) > 1024 + THEN CAST(CAST(SUM(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END )/1024. - SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableNamePerfmonStatsActuals_View; + AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'GB); ' + ELSE CAST(SUM(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) + AS NVARCHAR(30)) + N'MB); ' + END + + CASE WHEN MAX(sz.[total_rows]) >= 922337203685477 THEN '>= 922,337,203,685,477' + ELSE REPLACE(CONVERT(NVARCHAR(30),CAST(MAX(sz.[total_rows]) AS MONEY), 1), '.00', '') + END + + + N' Estimated Rows;' + ,N'') AS index_size_summary + FROM #IndexSanity AS i + LEFT JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id AND i.database_id = sz.database_id + WHERE i.is_hypothetical = 0 + AND i.is_disabled = 0 + GROUP BY i.database_id, i.schema_name, i.[object_id]) + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + index_usage_summary, index_size_summary, create_tsql, more_info ) + + SELECT check_id, t.index_sanity_id, t.check_id, t.findings_group, t.finding, t.[Database Name], t.URL, t.details, t.[definition], + index_estimated_impact, t.index_size_summary, create_tsql, more_info + FROM + ( + SELECT ROW_NUMBER() OVER (ORDER BY magic_benefit_number DESC) AS rownum, + 50 AS check_id, + sz.index_sanity_id, + 40 AS Priority, + N'Indexaphobia' AS findings_group, + N'High Value Missing Index' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/Indexaphobia' AS URL, + mi.[statement] + + N' Est. benefit per day: ' + + CASE WHEN magic_benefit_number >= 922337203685477 THEN '>= 922,337,203,685,477' + ELSE REPLACE(CONVERT(NVARCHAR(256),CAST(CAST( + (magic_benefit_number/@DaysUptime) + AS BIGINT) AS MONEY), 1), '.00', '') + END AS details, + missing_index_details AS [definition], + index_estimated_impact, + sz.index_size_summary, + mi.create_tsql, + mi.more_info, + magic_benefit_number, + mi.is_low + FROM #MissingIndexes mi + LEFT JOIN index_size_cte sz ON mi.[object_id] = sz.object_id + AND mi.database_id = sz.database_id + AND mi.schema_name = sz.schema_name + /* Minimum benefit threshold = 100k/day of uptime OR since table creation date, whichever is lower*/ + WHERE @ShowAllMissingIndexRequests=1 + OR ( @Mode = 4 AND (magic_benefit_number / CASE WHEN sz.create_days < @DaysUptime THEN sz.create_days ELSE @DaysUptime END) >= 100000 ) + OR (magic_benefit_number / CASE WHEN sz.create_days < @DaysUptime THEN sz.create_days ELSE @DaysUptime END) >= 100000 + ) AS t + WHERE t.rownum <= CASE WHEN (@Mode <> 4) THEN 20 ELSE t.rownum END + ORDER BY magic_benefit_number DESC + OPTION ( RECOMPILE ); - /* If the view exists without the most recently added columns, drop it. See Github #2162. */ - IF OBJECT_ID(@ObjectFullName) IS NOT NULL - BEGIN - SET @StringToExecute = N'USE ' + @OutputDatabaseName + N'; IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns - WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''JoinKey'') - DROP VIEW ' + @OutputSchemaName + N'.' + @OutputTableNamePerfmonStatsActuals_View + N';'; - EXEC(@StringToExecute); - END - /* Create the second view */ - IF OBJECT_ID(@ObjectFullName) IS NULL - BEGIN - SET @StringToExecute = 'USE ' - + @OutputDatabaseName - + '; EXEC (''CREATE VIEW ' - + @OutputSchemaName + '.' - + @OutputTableNamePerfmonStatsActuals_View + ' AS ' + @LineFeed - + 'WITH PERF_AVERAGE_BULK AS' + @LineFeed - + '(' + @LineFeed - + ' SELECT ServerName,' + @LineFeed - + ' object_name,' + @LineFeed - + ' instance_name,' + @LineFeed - + ' counter_name,' + @LineFeed - + ' CASE WHEN CHARINDEX(''''('''', counter_name) = 0 THEN counter_name ELSE LEFT (counter_name, CHARINDEX(''''('''',counter_name)-1) END AS counter_join,' + @LineFeed - + ' CheckDate,' + @LineFeed - + ' cntr_delta' + @LineFeed - + ' FROM ' + @OutputSchemaName + '.' + @OutputTableNamePerfmonStats_View + @LineFeed - + ' WHERE cntr_type IN(1073874176)' + @LineFeed - + ' AND cntr_delta <> 0' + @LineFeed - + '),' + @LineFeed - + 'PERF_LARGE_RAW_BASE AS' + @LineFeed - + '(' + @LineFeed - + ' SELECT ServerName,' + @LineFeed - + ' object_name,' + @LineFeed - + ' instance_name,' + @LineFeed - + ' LEFT(counter_name, CHARINDEX(''''BASE'''', UPPER(counter_name))-1) AS counter_join,' + @LineFeed - + ' CheckDate,' + @LineFeed - + ' cntr_delta' + @LineFeed - + ' FROM ' + @OutputSchemaName + '.' + @OutputTableNamePerfmonStats_View + '' + @LineFeed - + ' WHERE cntr_type IN(1073939712)' + @LineFeed - + ' AND cntr_delta <> 0' + @LineFeed - + '),' + @LineFeed - + 'PERF_AVERAGE_FRACTION AS' + @LineFeed - + '(' + @LineFeed - + ' SELECT ServerName,' + @LineFeed - + ' object_name,' + @LineFeed - + ' instance_name,' + @LineFeed - + ' counter_name,' + @LineFeed - + ' counter_name AS counter_join,' + @LineFeed - + ' CheckDate,' + @LineFeed - + ' cntr_delta' + @LineFeed - + ' FROM ' + @OutputSchemaName + '.' + @OutputTableNamePerfmonStats_View + '' + @LineFeed - + ' WHERE cntr_type IN(537003264)' + @LineFeed - + ' AND cntr_delta <> 0' + @LineFeed - + '),' + @LineFeed - + 'PERF_COUNTER_BULK_COUNT AS' + @LineFeed - + '(' + @LineFeed - + ' SELECT ServerName,' + @LineFeed - + ' object_name,' + @LineFeed - + ' instance_name,' + @LineFeed - + ' counter_name,' + @LineFeed - + ' CheckDate,' + @LineFeed - + ' cntr_delta / ElapsedSeconds AS cntr_value' + @LineFeed - + ' FROM ' + @OutputSchemaName + '.' + @OutputTableNamePerfmonStats_View + '' + @LineFeed - + ' WHERE cntr_type IN(272696576, 272696320)' + @LineFeed - + ' AND cntr_delta <> 0' + @LineFeed - + '),' + @LineFeed - + 'PERF_COUNTER_RAWCOUNT AS' + @LineFeed - + '(' + @LineFeed - + ' SELECT ServerName,' + @LineFeed - + ' object_name,' + @LineFeed - + ' instance_name,' + @LineFeed - + ' counter_name,' + @LineFeed - + ' CheckDate,' + @LineFeed - + ' cntr_value' + @LineFeed - + ' FROM ' + @OutputSchemaName + '.' + @OutputTableNamePerfmonStats_View + '' + @LineFeed - + ' WHERE cntr_type IN(65792, 65536)' + @LineFeed - + ')' + @LineFeed - + '' + @LineFeed - + 'SELECT NUM.ServerName,' + @LineFeed - + ' NUM.object_name,' + @LineFeed - + ' NUM.counter_name,' + @LineFeed - + ' NUM.instance_name,' + @LineFeed - + ' NUM.CheckDate,' + @LineFeed - + ' NUM.cntr_delta / DEN.cntr_delta AS cntr_value,' + @LineFeed - + ' NUM.ServerName + CAST(NUM.CheckDate AS NVARCHAR(50)) AS JoinKey' + @LineFeed - + ' ' + @LineFeed - + 'FROM PERF_AVERAGE_BULK AS NUM' + @LineFeed - + ' JOIN PERF_LARGE_RAW_BASE AS DEN ON NUM.counter_join = DEN.counter_join' + @LineFeed - + ' AND NUM.CheckDate = DEN.CheckDate' + @LineFeed - + ' AND NUM.ServerName = DEN.ServerName' + @LineFeed - + ' AND NUM.object_name = DEN.object_name' + @LineFeed - + ' AND NUM.instance_name = DEN.instance_name' + @LineFeed - + ' AND DEN.cntr_delta <> 0' + @LineFeed - + '' + @LineFeed - + 'UNION ALL' + @LineFeed - + '' + @LineFeed - + 'SELECT NUM.ServerName,' + @LineFeed - + ' NUM.object_name,' + @LineFeed - + ' NUM.counter_name,' + @LineFeed - + ' NUM.instance_name,' + @LineFeed - + ' NUM.CheckDate,' + @LineFeed - + ' CAST((CAST(NUM.cntr_delta as DECIMAL(19)) / DEN.cntr_delta) as decimal(23,3)) AS cntr_value,' + @LineFeed - + ' NUM.ServerName + CAST(NUM.CheckDate AS NVARCHAR(50)) AS JoinKey' + @LineFeed - + 'FROM PERF_AVERAGE_FRACTION AS NUM' + @LineFeed - + ' JOIN PERF_LARGE_RAW_BASE AS DEN ON NUM.counter_join = DEN.counter_join' + @LineFeed - + ' AND NUM.CheckDate = DEN.CheckDate' + @LineFeed - + ' AND NUM.ServerName = DEN.ServerName' + @LineFeed - + ' AND NUM.object_name = DEN.object_name' + @LineFeed - + ' AND NUM.instance_name = DEN.instance_name' + @LineFeed - + ' AND DEN.cntr_delta <> 0' + @LineFeed - + 'UNION ALL' + @LineFeed - + '' + @LineFeed - + 'SELECT ServerName,' + @LineFeed - + ' object_name,' + @LineFeed - + ' counter_name,' + @LineFeed - + ' instance_name,' + @LineFeed - + ' CheckDate,' + @LineFeed - + ' cntr_value,' + @LineFeed - + ' ServerName + CAST(CheckDate AS NVARCHAR(50)) AS JoinKey' + @LineFeed - + 'FROM PERF_COUNTER_BULK_COUNT' + @LineFeed - + '' + @LineFeed - + 'UNION ALL' + @LineFeed - + '' + @LineFeed - + 'SELECT ServerName,' + @LineFeed - + ' object_name,' + @LineFeed - + ' counter_name,' + @LineFeed - + ' instance_name,' + @LineFeed - + ' CheckDate,' + @LineFeed - + ' cntr_value,' + @LineFeed - + ' ServerName + CAST(CheckDate AS NVARCHAR(50)) AS JoinKey' + @LineFeed - + 'FROM PERF_COUNTER_RAWCOUNT;'')'; + RAISERROR(N'check_id 68: Identity columns within 30 percent of the end of range', 0,1) WITH NOWAIT; + -- Allowed Ranges: + --int -2,147,483,648 to 2,147,483,647 + --smallint -32,768 to 32,768 + --tinyint 0 to 255 - EXEC(@StringToExecute); - END; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 68 AS check_id, + i.index_sanity_id, + 80 AS Priority, + N'Abnormal Psychology' AS findings_group, + N'Identity Column Within ' + + CAST (calc1.percent_remaining AS NVARCHAR(256)) + + N' Percent End of Range' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, + i.db_schema_object_name + N'.' + QUOTENAME(ic.column_name) + + N' is an identity with type ' + ic.system_type_name + + N', last value of ' + + ISNULL((CONVERT(NVARCHAR(256),CAST(ic.last_value AS DECIMAL(38,0)), 1)),N'NULL') + + N', seed of ' + + ISNULL((CONVERT(NVARCHAR(256),CAST(ic.seed_value AS DECIMAL(38,0)), 1)),N'NULL') + + N', increment of ' + CAST(ic.increment_value AS NVARCHAR(256)) + + N', and range of ' + + CASE ic.system_type_name WHEN 'int' THEN N'+/- 2,147,483,647' + WHEN 'smallint' THEN N'+/- 32,768' + WHEN 'tinyint' THEN N'0 to 255' + ELSE 'unknown' + END + AS details, + i.index_definition, + secret_columns, + ISNULL(i.index_usage_summary,''), + ISNULL(ip.index_size_summary,'') + FROM #IndexSanity i + JOIN #IndexColumns ic ON + i.object_id=ic.object_id + AND i.database_id = ic.database_id + AND i.schema_name = ic.schema_name + AND i.index_id IN (0,1) /* heaps and cx only */ + AND ic.is_identity=1 + AND ic.system_type_name IN ('tinyint', 'smallint', 'int') + JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id + CROSS APPLY ( + SELECT CAST(CASE WHEN ic.increment_value >= 0 + THEN + CASE ic.system_type_name + WHEN 'int' THEN (2147483647 - (ISNULL(ic.last_value,ic.seed_value) + ic.increment_value)) / 2147483647.*100 + WHEN 'smallint' THEN (32768 - (ISNULL(ic.last_value,ic.seed_value) + ic.increment_value)) / 32768.*100 + WHEN 'tinyint' THEN ( 255 - (ISNULL(ic.last_value,ic.seed_value) + ic.increment_value)) / 255.*100 + ELSE 999 + END + ELSE --ic.increment_value is negative + CASE ic.system_type_name + WHEN 'int' THEN ABS(-2147483647 - (ISNULL(ic.last_value,ic.seed_value) + ic.increment_value)) / 2147483647.*100 + WHEN 'smallint' THEN ABS(-32768 - (ISNULL(ic.last_value,ic.seed_value) + ic.increment_value)) / 32768.*100 + WHEN 'tinyint' THEN ABS( 0 - (ISNULL(ic.last_value,ic.seed_value) + ic.increment_value)) / 255.*100 + ELSE -1 + END + END AS NUMERIC(5,1)) AS percent_remaining + ) AS calc1 + WHERE i.index_id IN (1,0) + AND calc1.percent_remaining <= 30 + OPTION (RECOMPILE); - SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' - + @OutputSchemaName + ''') INSERT ' - + @OutputDatabaseName + '.' - + @OutputSchemaName + '.' - + @OutputTableNamePerfmonStats - + ' (ServerName, CheckDate, object_name, counter_name, instance_name, cntr_value, cntr_type, value_delta, value_per_second) SELECT ' - + ' @SrvName, @CheckDate, object_name, counter_name, instance_name, cntr_value, cntr_type, value_delta, value_per_second FROM #PerfmonStats WHERE Pass = 2'; + RAISERROR(N'check_id 72: Columnstore indexes with Trace Flag 834', 0,1) WITH NOWAIT; + IF EXISTS (SELECT * FROM #IndexSanity WHERE index_type IN (5,6)) + AND EXISTS (SELECT * FROM #TraceStatus WHERE TraceFlag = 834 AND status = 1) + BEGIN + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 72 AS check_id, + i.index_sanity_id, + 80 AS Priority, + N'Abnormal Psychology' AS findings_group, + 'Columnstore Indexes with Trace Flag 834' AS finding, + [database_name] AS [Database Name], + N'https://support.microsoft.com/en-us/kb/3210239' AS URL, + i.db_schema_object_indexid AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + ISNULL(sz.index_size_summary,'') AS index_size_summary + FROM #IndexSanity AS i + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + WHERE i.index_type IN (5,6) + OPTION ( RECOMPILE ); + END; - EXEC sp_executesql @StringToExecute, - N'@SrvName NVARCHAR(128), @CheckDate datetimeoffset', - @LocalServerName, @StartSampleTime; + ---------------------------------------- + --Statistics Info: Check_id 90-99 + ---------------------------------------- - /* Delete history older than @OutputTableRetentionDays */ - SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' - + @OutputSchemaName + ''') DELETE ' - + @OutputDatabaseName + '.' - + @OutputSchemaName + '.' - + @OutputTableNamePerfmonStats - + ' WHERE ServerName = @SrvName AND CheckDate < @CheckDate ;'; + RAISERROR(N'check_id 90: Outdated 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 ) + SELECT 90 AS check_id, + 90 AS Priority, + 'Functioning Statistaholics' AS findings_group, + 'Statistics Not Updated Recently', + s.database_name, + 'https://www.brentozar.com/go/stats' AS URL, + 'Statistics on this table were last updated ' + + CASE WHEN s.last_statistics_update IS NULL THEN N' NEVER ' + ELSE CONVERT(NVARCHAR(20), s.last_statistics_update) + + ' have had ' + CONVERT(NVARCHAR(100), s.modification_counter) + + ' modifications in that time, which is ' + + CONVERT(NVARCHAR(100), s.percent_modifications) + + '% of the table.' + END 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.last_statistics_update <= CONVERT(DATETIME, GETDATE() - 30) + AND s.percent_modifications >= 10. + AND s.rows >= 10000 + OPTION ( RECOMPILE ); - EXEC sp_executesql @StringToExecute, - N'@SrvName NVARCHAR(128), @CheckDate date', - @LocalServerName, @OutputTableCleanupDate; + RAISERROR(N'check_id 91: Statistics with a low 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 91 AS check_id, + 90 AS Priority, + 'Functioning Statistaholics' AS findings_group, + 'Low Sampling Rates', + s.database_name, + 'https://www.brentozar.com/go/stats' AS URL, + 'Only ' + CONVERT(NVARCHAR(100), s.percent_sampled) + '% of the rows were sampled during the last statistics update. This may lead to poor cardinality estimates.' 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.rows BETWEEN 10000 AND 1000000 AND s.percent_sampled < 10) + OR (s.rows > 1000000 AND s.percent_sampled < 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 ) + SELECT 92 AS check_id, + 90 AS Priority, + 'Functioning Statistaholics' AS findings_group, + 'Statistics With NO RECOMPUTE', + s.database_name, + 'https://www.brentozar.com/go/stats' AS URL, + 'The statistic ' + QUOTENAME(s.statistics_name) + ' is set to not recompute. This can be helpful if data is really skewed, but harmful if you expect automatic statistics updates.' 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.no_recompute = 1 + OPTION ( RECOMPILE ); - END; - ELSE IF (SUBSTRING(@OutputTableNamePerfmonStats, 2, 2) = '##') - BEGIN - SET @StringToExecute = N' IF (OBJECT_ID(''tempdb..' - + @OutputTableNamePerfmonStats - + ''') IS NULL) CREATE TABLE ' - + @OutputTableNamePerfmonStats - + ' (ID INT IDENTITY(1,1) NOT NULL, - ServerName NVARCHAR(128), - CheckDate DATETIMEOFFSET, - [object_name] NVARCHAR(128) NOT NULL, - [counter_name] NVARCHAR(128) NOT NULL, - [instance_name] NVARCHAR(128) NULL, - [cntr_value] BIGINT NULL, - [cntr_type] INT NOT NULL, - [value_delta] BIGINT NULL, - [value_per_second] DECIMAL(18,2) NULL, - PRIMARY KEY CLUSTERED (ID ASC));' - + ' INSERT ' - + @OutputTableNamePerfmonStats - + ' (ServerName, CheckDate, object_name, counter_name, instance_name, cntr_value, cntr_type, value_delta, value_per_second) SELECT ' - + CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128)) - + ' @SrvName, @CheckDate, object_name, counter_name, instance_name, cntr_value, cntr_type, value_delta, value_per_second FROM #PerfmonStats WHERE Pass = 2'; + 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 ) + SELECT 94 AS check_id, + 100 AS Priority, + 'Serial Forcer' AS findings_group, + 'Check Constraint with Scalar UDF' AS finding, + cc.database_name, + 'https://www.brentozar.com/go/computedscalar' AS URL, + 'The check constraint ' + QUOTENAME(cc.constraint_name) + ' on ' + QUOTENAME(cc.schema_name) + '.' + QUOTENAME(cc.table_name) + ' is based on ' + cc.definition + + '. That indicates it may reference a scalar function, or a CLR function with data access, which can cause all queries and maintenance to run serially.' AS details, + cc.column_definition, + 'N/A' AS secret_columns, + 'N/A' AS index_usage_summary, + 'N/A' AS index_size_summary + FROM #CheckConstraints AS cc + WHERE cc.is_function = 1 + OPTION ( RECOMPILE ); - EXEC sp_executesql @StringToExecute, - N'@SrvName NVARCHAR(128), @CheckDate datetimeoffset', - @LocalServerName, @StartSampleTime; - END; - ELSE IF (SUBSTRING(@OutputTableNamePerfmonStats, 2, 1) = '#') - BEGIN - RAISERROR('Due to the nature of Dymamic SQL, only global (i.e. double pound (##)) temp tables are supported for @OutputTableName', 16, 0); - END; + RAISERROR(N'check_id 99: Computed Columns 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 ) + SELECT 99 AS check_id, + 100 AS Priority, + 'Serial Forcer' AS findings_group, + 'Computed Column with Scalar UDF' AS finding, + cc.database_name, + 'https://www.brentozar.com/go/serialudf' AS URL, + 'The computed column ' + QUOTENAME(cc.column_name) + ' on ' + QUOTENAME(cc.schema_name) + '.' + QUOTENAME(cc.table_name) + ' is based on ' + cc.definition + + '. That indicates it may reference a scalar function, or a CLR function with data access, which can cause all queries and maintenance to run serially.' AS details, + cc.column_definition, + 'N/A' AS secret_columns, + 'N/A' AS index_usage_summary, + 'N/A' AS index_size_summary + FROM #ComputedColumns AS cc + WHERE cc.is_function = 1 + OPTION ( RECOMPILE ); - /* @OutputTableNameWaitStats lets us export the results to a permanent table */ - IF @OutputDatabaseName IS NOT NULL - AND @OutputSchemaName IS NOT NULL - AND @OutputTableNameWaitStats IS NOT NULL - AND @OutputTableNameWaitStats NOT LIKE '#%' - AND EXISTS ( SELECT * - FROM sys.databases - WHERE QUOTENAME([name]) = @OutputDatabaseName) - BEGIN - /* Create the table */ - SET @StringToExecute = 'USE ' - + @OutputDatabaseName - + '; IF EXISTS(SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' - + @OutputSchemaName - + ''') AND NOT EXISTS (SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''' - + @OutputSchemaName + ''' AND QUOTENAME(TABLE_NAME) = ''' - + @OutputTableNameWaitStats + ''') ' + @LineFeed - + 'BEGIN' + @LineFeed - + 'CREATE TABLE ' - + @OutputSchemaName + '.' - + @OutputTableNameWaitStats - + ' (ID INT IDENTITY(1,1) NOT NULL, - ServerName NVARCHAR(128), - CheckDate DATETIMEOFFSET, - wait_type NVARCHAR(60), - wait_time_ms BIGINT, - signal_wait_time_ms BIGINT, - waiting_tasks_count BIGINT , - PRIMARY KEY CLUSTERED (ID));' + @LineFeed - + 'CREATE NONCLUSTERED INDEX IX_ServerName_wait_type_CheckDate_Includes ON ' + @OutputSchemaName + '.' + @OutputTableNameWaitStats + @LineFeed - + '(ServerName, wait_type, CheckDate) INCLUDE (wait_time_ms, signal_wait_time_ms, waiting_tasks_count);' + @LineFeed - + 'END'; - EXEC(@StringToExecute); + END /* IF @Mode IN (0, 4) DIAGNOSE priorities 1-100 */ - /* Create the wait stats category table */ - SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableNameWaitStats_Categories; - IF OBJECT_ID(@ObjectFullName) IS NULL - BEGIN - SET @StringToExecute = 'USE ' - + @OutputDatabaseName - + '; EXEC (''CREATE TABLE ' - + @OutputSchemaName + '.' - + @OutputTableNameWaitStats_Categories + ' (WaitType NVARCHAR(60) PRIMARY KEY CLUSTERED, WaitCategory NVARCHAR(128) NOT NULL, Ignorable BIT DEFAULT 0);'')'; - EXEC(@StringToExecute); - END; - /* Make sure the wait stats category table has the current number of rows */ - SET @StringToExecute = 'USE ' - + @OutputDatabaseName - + '; EXEC (''IF (SELECT COALESCE(SUM(1),0) FROM ' + @OutputSchemaName + '.' + @OutputTableNameWaitStats_Categories + ') <> (SELECT COALESCE(SUM(1),0) FROM ##WaitCategories)' + @LineFeed - + 'BEGIN ' + @LineFeed - + 'TRUNCATE TABLE ' + @OutputSchemaName + '.' + @OutputTableNameWaitStats_Categories + @LineFeed - + 'INSERT INTO ' + @OutputSchemaName + '.' + @OutputTableNameWaitStats_Categories + ' (WaitType, WaitCategory, Ignorable) SELECT WaitType, WaitCategory, Ignorable FROM ##WaitCategories;' + @LineFeed - + 'END'')'; - EXEC(@StringToExecute); - SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableNameWaitStats_View; - /* If the view exists without the most recently added columns, drop it. See Github #2162. */ - IF OBJECT_ID(@ObjectFullName) IS NOT NULL - BEGIN - SET @StringToExecute = N'USE ' + @OutputDatabaseName + N'; IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns - WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''JoinKey'') - DROP VIEW ' + @OutputSchemaName + N'.' + @OutputTableNameWaitStats_View + N';'; - EXEC(@StringToExecute); - END - /* Create the wait stats view */ - IF OBJECT_ID(@ObjectFullName) IS NULL - BEGIN - SET @StringToExecute = 'USE ' - + @OutputDatabaseName - + '; EXEC (''CREATE VIEW ' - + @OutputSchemaName + '.' - + @OutputTableNameWaitStats_View + ' AS ' + @LineFeed - + 'WITH RowDates as' + @LineFeed - + '(' + @LineFeed - + ' SELECT ' + @LineFeed - + ' ROW_NUMBER() OVER (ORDER BY [ServerName], [CheckDate]) ID,' + @LineFeed - + ' [CheckDate]' + @LineFeed - + ' FROM ' + @OutputSchemaName + '.' + @OutputTableNameWaitStats + @LineFeed - + ' GROUP BY [ServerName], [CheckDate]' + @LineFeed - + '),' + @LineFeed - + 'CheckDates as' + @LineFeed - + '(' + @LineFeed - + ' SELECT ThisDate.CheckDate,' + @LineFeed - + ' LastDate.CheckDate as PreviousCheckDate' + @LineFeed - + ' FROM RowDates ThisDate' + @LineFeed - + ' JOIN RowDates LastDate' + @LineFeed - + ' ON ThisDate.ID = LastDate.ID + 1' + @LineFeed - + ')' + @LineFeed - + 'SELECT w.ServerName, w.CheckDate, w.wait_type, COALESCE(wc.WaitCategory, ''''Other'''') AS WaitCategory, COALESCE(wc.Ignorable,0) AS Ignorable' + @LineFeed - + ', DATEDIFF(ss, wPrior.CheckDate, w.CheckDate) AS ElapsedSeconds' + @LineFeed - + ', (w.wait_time_ms - wPrior.wait_time_ms) AS wait_time_ms_delta' + @LineFeed - + ', (w.wait_time_ms - wPrior.wait_time_ms) / 60000.0 AS wait_time_minutes_delta' + @LineFeed - + ', (w.wait_time_ms - wPrior.wait_time_ms) / 1000.0 / DATEDIFF(ss, wPrior.CheckDate, w.CheckDate) AS wait_time_minutes_per_minute' + @LineFeed - + ', (w.signal_wait_time_ms - wPrior.signal_wait_time_ms) AS signal_wait_time_ms_delta' + @LineFeed - + ', (w.waiting_tasks_count - wPrior.waiting_tasks_count) AS waiting_tasks_count_delta' + @LineFeed - + ', w.ServerName + CAST(w.CheckDate AS NVARCHAR(50)) AS JoinKey' + @LineFeed - + 'FROM ' + @OutputSchemaName + '.' + @OutputTableNameWaitStats + ' w' + @LineFeed - + 'INNER HASH JOIN CheckDates Dates' + @LineFeed - + 'ON Dates.CheckDate = w.CheckDate' + @LineFeed - + 'INNER JOIN ' + @OutputSchemaName + '.' + @OutputTableNameWaitStats + ' wPrior ON w.ServerName = wPrior.ServerName AND w.wait_type = wPrior.wait_type AND Dates.PreviousCheckDate = wPrior.CheckDate' + @LineFeed - + 'LEFT OUTER JOIN ' + @OutputSchemaName + '.' + @OutputTableNameWaitStats_Categories + ' wc ON w.wait_type = wc.WaitType' + @LineFeed - + 'WHERE DATEDIFF(MI, wPrior.CheckDate, w.CheckDate) BETWEEN 1 AND 60' + @LineFeed - + 'AND [w].[wait_time_ms] >= [wPrior].[wait_time_ms];'')' - EXEC(@StringToExecute); - END; - SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' - + @OutputSchemaName + ''') INSERT ' - + @OutputDatabaseName + '.' - + @OutputSchemaName + '.' - + @OutputTableNameWaitStats - + ' (ServerName, CheckDate, wait_type, wait_time_ms, signal_wait_time_ms, waiting_tasks_count) SELECT ' - + ' @SrvName, @CheckDate, wait_type, wait_time_ms, signal_wait_time_ms, waiting_tasks_count FROM #WaitStats WHERE Pass = 2 AND wait_time_ms > 0 AND waiting_tasks_count > 0'; - EXEC sp_executesql @StringToExecute, - N'@SrvName NVARCHAR(128), @CheckDate datetimeoffset', - @LocalServerName, @StartSampleTime; - /* Delete history older than @OutputTableRetentionDays */ - SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' - + @OutputSchemaName + ''') DELETE ' - + @OutputDatabaseName + '.' - + @OutputSchemaName + '.' - + @OutputTableNameWaitStats - + ' WHERE ServerName = @SrvName AND CheckDate < @CheckDate ;'; - EXEC sp_executesql @StringToExecute, - N'@SrvName NVARCHAR(128), @CheckDate date', - @LocalServerName, @OutputTableCleanupDate; - END; - ELSE IF (SUBSTRING(@OutputTableNameWaitStats, 2, 2) = '##') - BEGIN - SET @StringToExecute = N' IF (OBJECT_ID(''tempdb..' - + @OutputTableNameWaitStats - + ''') IS NULL) CREATE TABLE ' - + @OutputTableNameWaitStats - + ' (ID INT IDENTITY(1,1) NOT NULL, - ServerName NVARCHAR(128), - CheckDate DATETIMEOFFSET, - wait_type NVARCHAR(60), - wait_time_ms BIGINT, - signal_wait_time_ms BIGINT, - waiting_tasks_count BIGINT , - PRIMARY KEY CLUSTERED (ID ASC));' - + ' INSERT ' - + @OutputTableNameWaitStats - + ' (ServerName, CheckDate, wait_type, wait_time_ms, signal_wait_time_ms, waiting_tasks_count) SELECT ' - + ' @SrvName, @CheckDate, wait_type, wait_time_ms, signal_wait_time_ms, waiting_tasks_count FROM #WaitStats WHERE Pass = 2 AND wait_time_ms > 0 AND waiting_tasks_count > 0'; - EXEC sp_executesql @StringToExecute, - N'@SrvName NVARCHAR(128), @CheckDate datetimeoffset', - @LocalServerName, @StartSampleTime; - END; - ELSE IF (SUBSTRING(@OutputTableNameWaitStats, 2, 1) = '#') - BEGIN - RAISERROR('Due to the nature of Dymamic SQL, only global (i.e. double pound (##)) temp tables are supported for @OutputTableName', 16, 0); - END; - DECLARE @separator AS VARCHAR(1); - IF @OutputType = 'RSV' - SET @separator = CHAR(31); - ELSE - SET @separator = ','; - - IF @OutputType = 'COUNT' AND @SinceStartup = 0 - BEGIN - SELECT COUNT(*) AS Warnings - FROM #BlitzFirstResults; - END; - ELSE - IF @OutputType = 'Opserver1' AND @SinceStartup = 0 - BEGIN - - SELECT r.[Priority] , - r.[FindingsGroup] , - r.[Finding] , - r.[URL] , - r.[Details], - r.[HowToStopIt] , - r.[CheckID] , - r.[StartTime], - r.[LoginName], - r.[NTUserName], - r.[OriginalLoginName], - r.[ProgramName], - r.[HostName], - r.[DatabaseID], - r.[DatabaseName], - r.[OpenTransactionCount], - r.[QueryPlan], - r.[QueryText], - qsNow.plan_handle AS PlanHandle, - qsNow.sql_handle AS SqlHandle, - qsNow.statement_start_offset AS StatementStartOffset, - qsNow.statement_end_offset AS StatementEndOffset, - [Executions] = qsNow.execution_count - (COALESCE(qsFirst.execution_count, 0)), - [ExecutionsPercent] = CAST(100.0 * (qsNow.execution_count - (COALESCE(qsFirst.execution_count, 0))) / (qsTotal.execution_count - qsTotalFirst.execution_count) AS DECIMAL(6,2)), - [Duration] = qsNow.total_elapsed_time - (COALESCE(qsFirst.total_elapsed_time, 0)), - [DurationPercent] = CAST(100.0 * (qsNow.total_elapsed_time - (COALESCE(qsFirst.total_elapsed_time, 0))) / (qsTotal.total_elapsed_time - qsTotalFirst.total_elapsed_time) AS DECIMAL(6,2)), - [CPU] = qsNow.total_worker_time - (COALESCE(qsFirst.total_worker_time, 0)), - [CPUPercent] = CAST(100.0 * (qsNow.total_worker_time - (COALESCE(qsFirst.total_worker_time, 0))) / (qsTotal.total_worker_time - qsTotalFirst.total_worker_time) AS DECIMAL(6,2)), - [Reads] = qsNow.total_logical_reads - (COALESCE(qsFirst.total_logical_reads, 0)), - [ReadsPercent] = CAST(100.0 * (qsNow.total_logical_reads - (COALESCE(qsFirst.total_logical_reads, 0))) / (qsTotal.total_logical_reads - qsTotalFirst.total_logical_reads) AS DECIMAL(6,2)), - [PlanCreationTime] = CONVERT(NVARCHAR(100), qsNow.creation_time ,121), - [TotalExecutions] = qsNow.execution_count, - [TotalExecutionsPercent] = CAST(100.0 * qsNow.execution_count / qsTotal.execution_count AS DECIMAL(6,2)), - [TotalDuration] = qsNow.total_elapsed_time, - [TotalDurationPercent] = CAST(100.0 * qsNow.total_elapsed_time / qsTotal.total_elapsed_time AS DECIMAL(6,2)), - [TotalCPU] = qsNow.total_worker_time, - [TotalCPUPercent] = CAST(100.0 * qsNow.total_worker_time / qsTotal.total_worker_time AS DECIMAL(6,2)), - [TotalReads] = qsNow.total_logical_reads, - [TotalReadsPercent] = CAST(100.0 * qsNow.total_logical_reads / qsTotal.total_logical_reads AS DECIMAL(6,2)), - r.[DetailsInt] - FROM #BlitzFirstResults r - LEFT OUTER JOIN #QueryStats qsTotal ON qsTotal.Pass = 0 - LEFT OUTER JOIN #QueryStats qsTotalFirst ON qsTotalFirst.Pass = -1 - LEFT OUTER JOIN #QueryStats qsNow ON r.QueryStatsNowID = qsNow.ID - LEFT OUTER JOIN #QueryStats qsFirst ON r.QueryStatsFirstID = qsFirst.ID - ORDER BY r.Priority , - r.FindingsGroup , - CASE - WHEN r.CheckID = 6 THEN DetailsInt - ELSE 0 - END DESC, - r.Finding, - r.ID; - END; - ELSE IF @OutputType IN ( 'CSV', 'RSV' ) AND @SinceStartup = 0 - BEGIN - SELECT Result = CAST([Priority] AS NVARCHAR(100)) - + @separator + CAST(CheckID AS NVARCHAR(100)) - + @separator + COALESCE([FindingsGroup], - '(N/A)') + @separator - + COALESCE([Finding], '(N/A)') + @separator - + COALESCE(DatabaseName, '(N/A)') + @separator - + COALESCE([URL], '(N/A)') + @separator - + COALESCE([Details], '(N/A)') - FROM #BlitzFirstResults - ORDER BY Priority , - FindingsGroup , - CASE - WHEN CheckID = 6 THEN DetailsInt - ELSE 0 - END DESC, - Finding, - Details; - END; - ELSE IF @OutputType = 'Top10' - BEGIN - /* Measure waits in hours */ - ;WITH max_batch AS ( - SELECT MAX(SampleTime) AS SampleTime - FROM #WaitStats - ) - SELECT TOP 10 - CAST(DATEDIFF(mi,wd1.SampleTime, wd2.SampleTime) / 60.0 AS DECIMAL(18,1)) AS [Hours Sample], - wd1.wait_type, - COALESCE(wcat.WaitCategory, 'Other') AS wait_category, - CAST(c.[Wait Time (Seconds)] / 60.0 / 60 AS DECIMAL(18,1)) AS [Wait Time (Hours)], - CAST((wd2.wait_time_ms - wd1.wait_time_ms) / 1000.0 / cores.cpu_count / DATEDIFF(ss, wd1.SampleTime, wd2.SampleTime) AS DECIMAL(18,1)) AS [Per Core Per Hour], - (wd2.waiting_tasks_count - wd1.waiting_tasks_count) AS [Number of Waits], - CASE WHEN (wd2.waiting_tasks_count - wd1.waiting_tasks_count) > 0 - THEN - CAST((wd2.wait_time_ms-wd1.wait_time_ms)/ - (1.0*(wd2.waiting_tasks_count - wd1.waiting_tasks_count)) AS NUMERIC(12,1)) - ELSE 0 END AS [Avg ms Per Wait] - FROM max_batch b - JOIN #WaitStats wd2 ON - wd2.SampleTime =b.SampleTime - JOIN #WaitStats wd1 ON - wd1.wait_type=wd2.wait_type AND - wd2.SampleTime > wd1.SampleTime - CROSS APPLY (SELECT SUM(1) AS cpu_count FROM sys.dm_os_schedulers WHERE status = 'VISIBLE ONLINE' AND is_online = 1) AS cores - CROSS APPLY (SELECT - CAST((wd2.wait_time_ms-wd1.wait_time_ms)/1000. AS NUMERIC(12,1)) AS [Wait Time (Seconds)], - CAST((wd2.signal_wait_time_ms - wd1.signal_wait_time_ms)/1000. AS NUMERIC(12,1)) AS [Signal Wait Time (Seconds)]) AS c - LEFT OUTER JOIN ##WaitCategories wcat ON wd1.wait_type = wcat.WaitType - WHERE (wd2.waiting_tasks_count - wd1.waiting_tasks_count) > 0 - AND wd2.wait_time_ms-wd1.wait_time_ms > 0 - ORDER BY [Wait Time (Seconds)] DESC; - END; - ELSE IF @ExpertMode = 0 AND @OutputType <> 'NONE' AND @OutputXMLasNVARCHAR = 0 AND @SinceStartup = 0 - BEGIN - SELECT [Priority] , - [FindingsGroup] , - [Finding] , - [URL] , - CAST(@StockDetailsHeader + [Details] + @StockDetailsFooter AS XML) AS Details, - CAST(@StockWarningHeader + HowToStopIt + @StockWarningFooter AS XML) AS HowToStopIt, - [QueryText], - [QueryPlan] - FROM #BlitzFirstResults - WHERE (@Seconds > 0 OR (Priority IN (0, 250, 251, 255))) /* For @Seconds = 0, filter out broken checks for now */ - ORDER BY Priority , - FindingsGroup , - CASE - WHEN CheckID = 6 THEN DetailsInt - ELSE 0 - END DESC, - Finding, - ID, - CAST(Details AS NVARCHAR(4000)); - END; - ELSE IF @OutputType <> 'NONE' AND @OutputXMLasNVARCHAR = 1 AND @SinceStartup = 0 - BEGIN - SELECT [Priority] , - [FindingsGroup] , - [Finding] , - [URL] , - CAST(@StockDetailsHeader + [Details] + @StockDetailsFooter AS NVARCHAR(MAX)) AS Details, - CAST([HowToStopIt] AS NVARCHAR(MAX)) AS HowToStopIt, - CAST([QueryText] AS NVARCHAR(MAX)) AS QueryText, - CAST([QueryPlan] AS NVARCHAR(MAX)) AS QueryPlan - FROM #BlitzFirstResults - WHERE (@Seconds > 0 OR (Priority IN (0, 250, 251, 255))) /* For @Seconds = 0, filter out broken checks for now */ - ORDER BY Priority , - FindingsGroup , - CASE - WHEN CheckID = 6 THEN DetailsInt - ELSE 0 - END DESC, - Finding, - ID, - CAST(Details AS NVARCHAR(4000)); - END; - ELSE IF @ExpertMode = 1 AND @OutputType <> 'NONE' - BEGIN - IF @SinceStartup = 0 - SELECT r.[Priority] , - r.[FindingsGroup] , - r.[Finding] , - r.[URL] , - CAST(@StockDetailsHeader + r.[Details] + @StockDetailsFooter AS XML) AS Details, - CAST(@StockWarningHeader + r.HowToStopIt + @StockWarningFooter AS XML) AS HowToStopIt, - r.[CheckID] , - r.[StartTime], - r.[LoginName], - r.[NTUserName], - r.[OriginalLoginName], - r.[ProgramName], - r.[HostName], - r.[DatabaseID], - r.[DatabaseName], - r.[OpenTransactionCount], - r.[QueryPlan], - r.[QueryText], - qsNow.plan_handle AS PlanHandle, - qsNow.sql_handle AS SqlHandle, - qsNow.statement_start_offset AS StatementStartOffset, - qsNow.statement_end_offset AS StatementEndOffset, - [Executions] = qsNow.execution_count - (COALESCE(qsFirst.execution_count, 0)), - [ExecutionsPercent] = CAST(100.0 * (qsNow.execution_count - (COALESCE(qsFirst.execution_count, 0))) / (qsTotal.execution_count - qsTotalFirst.execution_count) AS DECIMAL(6,2)), - [Duration] = qsNow.total_elapsed_time - (COALESCE(qsFirst.total_elapsed_time, 0)), - [DurationPercent] = CAST(100.0 * (qsNow.total_elapsed_time - (COALESCE(qsFirst.total_elapsed_time, 0))) / (qsTotal.total_elapsed_time - qsTotalFirst.total_elapsed_time) AS DECIMAL(6,2)), - [CPU] = qsNow.total_worker_time - (COALESCE(qsFirst.total_worker_time, 0)), - [CPUPercent] = CAST(100.0 * (qsNow.total_worker_time - (COALESCE(qsFirst.total_worker_time, 0))) / (qsTotal.total_worker_time - qsTotalFirst.total_worker_time) AS DECIMAL(6,2)), - [Reads] = qsNow.total_logical_reads - (COALESCE(qsFirst.total_logical_reads, 0)), - [ReadsPercent] = CAST(100.0 * (qsNow.total_logical_reads - (COALESCE(qsFirst.total_logical_reads, 0))) / (qsTotal.total_logical_reads - qsTotalFirst.total_logical_reads) AS DECIMAL(6,2)), - [PlanCreationTime] = CONVERT(NVARCHAR(100), qsNow.creation_time ,121), - [TotalExecutions] = qsNow.execution_count, - [TotalExecutionsPercent] = CAST(100.0 * qsNow.execution_count / qsTotal.execution_count AS DECIMAL(6,2)), - [TotalDuration] = qsNow.total_elapsed_time, - [TotalDurationPercent] = CAST(100.0 * qsNow.total_elapsed_time / qsTotal.total_elapsed_time AS DECIMAL(6,2)), - [TotalCPU] = qsNow.total_worker_time, - [TotalCPUPercent] = CAST(100.0 * qsNow.total_worker_time / qsTotal.total_worker_time AS DECIMAL(6,2)), - [TotalReads] = qsNow.total_logical_reads, - [TotalReadsPercent] = CAST(100.0 * qsNow.total_logical_reads / qsTotal.total_logical_reads AS DECIMAL(6,2)), - r.[DetailsInt] - FROM #BlitzFirstResults r - LEFT OUTER JOIN #QueryStats qsTotal ON qsTotal.Pass = 0 - LEFT OUTER JOIN #QueryStats qsTotalFirst ON qsTotalFirst.Pass = -1 - LEFT OUTER JOIN #QueryStats qsNow ON r.QueryStatsNowID = qsNow.ID - LEFT OUTER JOIN #QueryStats qsFirst ON r.QueryStatsFirstID = qsFirst.ID - WHERE (@Seconds > 0 OR (Priority IN (0, 250, 251, 255))) /* For @Seconds = 0, filter out broken checks for now */ - ORDER BY r.Priority , - r.FindingsGroup , - CASE - WHEN r.CheckID = 6 THEN DetailsInt - ELSE 0 - END DESC, - r.Finding, - r.ID, - CAST(r.Details AS NVARCHAR(4000)); - ------------------------- - --What happened: #WaitStats - ------------------------- - IF @Seconds = 0 - BEGIN - /* Measure waits in hours */ - ;WITH max_batch AS ( - SELECT MAX(SampleTime) AS SampleTime - FROM #WaitStats - ) - SELECT - 'WAIT STATS' AS Pattern, - b.SampleTime AS [Sample Ended], - CAST(DATEDIFF(mi,wd1.SampleTime, wd2.SampleTime) / 60.0 AS DECIMAL(18,1)) AS [Hours Sample], - wd1.wait_type, - COALESCE(wcat.WaitCategory, 'Other') AS wait_category, - CAST(c.[Wait Time (Seconds)] / 60.0 / 60 AS DECIMAL(18,1)) AS [Wait Time (Hours)], - CAST((wd2.wait_time_ms - wd1.wait_time_ms) / 1000.0 / cores.cpu_count / DATEDIFF(ss, wd1.SampleTime, wd2.SampleTime) AS DECIMAL(18,1)) AS [Per Core Per Hour], - CAST(c.[Signal Wait Time (Seconds)] / 60.0 / 60 AS DECIMAL(18,1)) AS [Signal Wait Time (Hours)], - CASE WHEN c.[Wait Time (Seconds)] > 0 - THEN CAST(100.*(c.[Signal Wait Time (Seconds)]/c.[Wait Time (Seconds)]) AS NUMERIC(4,1)) - ELSE 0 END AS [Percent Signal Waits], - (wd2.waiting_tasks_count - wd1.waiting_tasks_count) AS [Number of Waits], - CASE WHEN (wd2.waiting_tasks_count - wd1.waiting_tasks_count) > 0 - THEN - CAST((wd2.wait_time_ms-wd1.wait_time_ms)/ - (1.0*(wd2.waiting_tasks_count - wd1.waiting_tasks_count)) AS NUMERIC(12,1)) - ELSE 0 END AS [Avg ms Per Wait], - N'https://www.sqlskills.com/help/waits/' + LOWER(wd1.wait_type) + '/' AS URL - FROM max_batch b - JOIN #WaitStats wd2 ON - wd2.SampleTime =b.SampleTime - JOIN #WaitStats wd1 ON - wd1.wait_type=wd2.wait_type AND - wd2.SampleTime > wd1.SampleTime - CROSS APPLY (SELECT SUM(1) AS cpu_count FROM sys.dm_os_schedulers WHERE status = 'VISIBLE ONLINE' AND is_online = 1) AS cores - CROSS APPLY (SELECT - CAST((wd2.wait_time_ms-wd1.wait_time_ms)/1000. AS NUMERIC(12,1)) AS [Wait Time (Seconds)], - CAST((wd2.signal_wait_time_ms - wd1.signal_wait_time_ms)/1000. AS NUMERIC(12,1)) AS [Signal Wait Time (Seconds)]) AS c - LEFT OUTER JOIN ##WaitCategories wcat ON wd1.wait_type = wcat.WaitType - WHERE (wd2.waiting_tasks_count - wd1.waiting_tasks_count) > 0 - AND wd2.wait_time_ms-wd1.wait_time_ms > 0 - ORDER BY [Wait Time (Seconds)] DESC; - END; - ELSE - BEGIN - /* Measure waits in seconds */ - ;WITH max_batch AS ( - SELECT MAX(SampleTime) AS SampleTime - FROM #WaitStats - ) - SELECT - 'WAIT STATS' AS Pattern, - b.SampleTime AS [Sample Ended], - DATEDIFF(ss,wd1.SampleTime, wd2.SampleTime) AS [Seconds Sample], - wd1.wait_type, - COALESCE(wcat.WaitCategory, 'Other') AS wait_category, - c.[Wait Time (Seconds)], - CAST((CAST(wd2.wait_time_ms - wd1.wait_time_ms AS MONEY)) / 1000.0 / cores.cpu_count / DATEDIFF(ss, wd1.SampleTime, wd2.SampleTime) AS DECIMAL(18,1)) AS [Per Core Per Second], - c.[Signal Wait Time (Seconds)], - CASE WHEN c.[Wait Time (Seconds)] > 0 - THEN CAST(100.*(c.[Signal Wait Time (Seconds)]/c.[Wait Time (Seconds)]) AS NUMERIC(4,1)) - ELSE 0 END AS [Percent Signal Waits], - (wd2.waiting_tasks_count - wd1.waiting_tasks_count) AS [Number of Waits], - CASE WHEN (wd2.waiting_tasks_count - wd1.waiting_tasks_count) > 0 - THEN - CAST((wd2.wait_time_ms-wd1.wait_time_ms)/ - (1.0*(wd2.waiting_tasks_count - wd1.waiting_tasks_count)) AS NUMERIC(12,1)) - ELSE 0 END AS [Avg ms Per Wait], - N'https://www.sqlskills.com/help/waits/' + LOWER(wd1.wait_type) + '/' AS URL - FROM max_batch b - JOIN #WaitStats wd2 ON - wd2.SampleTime =b.SampleTime - JOIN #WaitStats wd1 ON - wd1.wait_type=wd2.wait_type AND - wd2.SampleTime > wd1.SampleTime - CROSS APPLY (SELECT SUM(1) AS cpu_count FROM sys.dm_os_schedulers WHERE status = 'VISIBLE ONLINE' AND is_online = 1) AS cores - CROSS APPLY (SELECT - CAST((wd2.wait_time_ms-wd1.wait_time_ms)/1000. AS NUMERIC(12,1)) AS [Wait Time (Seconds)], - CAST((wd2.signal_wait_time_ms - wd1.signal_wait_time_ms)/1000. AS NUMERIC(12,1)) AS [Signal Wait Time (Seconds)]) AS c - LEFT OUTER JOIN ##WaitCategories wcat ON wd1.wait_type = wcat.WaitType - WHERE (wd2.waiting_tasks_count - wd1.waiting_tasks_count) > 0 - AND wd2.wait_time_ms-wd1.wait_time_ms > 0 - ORDER BY [Wait Time (Seconds)] DESC; - END; - ------------------------- - --What happened: #FileStats - ------------------------- - WITH readstats AS ( - SELECT 'PHYSICAL READS' AS Pattern, - ROW_NUMBER() OVER (ORDER BY wd2.avg_stall_read_ms DESC) AS StallRank, - wd2.SampleTime AS [Sample Time], - DATEDIFF(ss,wd1.SampleTime, wd2.SampleTime) AS [Sample (seconds)], - wd1.DatabaseName , - wd1.FileLogicalName AS [File Name], - UPPER(SUBSTRING(wd1.PhysicalName, 1, 2)) AS [Drive] , - wd1.SizeOnDiskMB , - ( wd2.num_of_reads - wd1.num_of_reads ) AS [# Reads/Writes], - CASE WHEN wd2.num_of_reads - wd1.num_of_reads > 0 - THEN CAST(( wd2.bytes_read - wd1.bytes_read)/1024./1024. AS NUMERIC(21,1)) - ELSE 0 - END AS [MB Read/Written], - wd2.avg_stall_read_ms AS [Avg Stall (ms)], - wd1.PhysicalName AS [file physical name] - FROM #FileStats wd2 - JOIN #FileStats wd1 ON wd2.SampleTime > wd1.SampleTime - AND wd1.DatabaseID = wd2.DatabaseID - AND wd1.FileID = wd2.FileID - ), - writestats AS ( - SELECT - 'PHYSICAL WRITES' AS Pattern, - ROW_NUMBER() OVER (ORDER BY wd2.avg_stall_write_ms DESC) AS StallRank, - wd2.SampleTime AS [Sample Time], - DATEDIFF(ss,wd1.SampleTime, wd2.SampleTime) AS [Sample (seconds)], - wd1.DatabaseName , - wd1.FileLogicalName AS [File Name], - UPPER(SUBSTRING(wd1.PhysicalName, 1, 2)) AS [Drive] , - wd1.SizeOnDiskMB , - ( wd2.num_of_writes - wd1.num_of_writes ) AS [# Reads/Writes], - CASE WHEN wd2.num_of_writes - wd1.num_of_writes > 0 - THEN CAST(( wd2.bytes_written - wd1.bytes_written)/1024./1024. AS NUMERIC(21,1)) - ELSE 0 - END AS [MB Read/Written], - wd2.avg_stall_write_ms AS [Avg Stall (ms)], - wd1.PhysicalName AS [file physical name] - FROM #FileStats wd2 - JOIN #FileStats wd1 ON wd2.SampleTime > wd1.SampleTime - AND wd1.DatabaseID = wd2.DatabaseID - AND wd1.FileID = wd2.FileID - ) - SELECT - Pattern, [Sample Time], [Sample (seconds)], [File Name], [Drive], [# Reads/Writes],[MB Read/Written],[Avg Stall (ms)], [file physical name], [StallRank] - FROM readstats - WHERE StallRank <=20 AND [MB Read/Written] > 0 - UNION ALL - SELECT Pattern, [Sample Time], [Sample (seconds)], [File Name], [Drive], [# Reads/Writes],[MB Read/Written],[Avg Stall (ms)], [file physical name], [StallRank] - FROM writestats - WHERE StallRank <=20 AND [MB Read/Written] > 0 - ORDER BY Pattern, StallRank; + IF @Mode = 4 /* DIAGNOSE*/ + BEGIN; + RAISERROR(N'@Mode=4, running rules for priorities 101+.', 0,1) WITH NOWAIT; + RAISERROR(N'check_id 21: More Than 5 Percent NC Indexes Are Unused', 0,1) WITH NOWAIT; + DECLARE @percent_NC_indexes_unused NUMERIC(29,1); + DECLARE @NC_indexes_unused_reserved_MB NUMERIC(29,1); - ------------------------- - --What happened: #PerfmonStats - ------------------------- + SELECT @percent_NC_indexes_unused = ( 100.00 * SUM(CASE + WHEN total_reads = 0 + THEN 1 + ELSE 0 + END) ) / COUNT(*), + @NC_indexes_unused_reserved_MB = SUM(CASE + WHEN total_reads = 0 + THEN sz.total_reserved_MB + ELSE 0 + END) + FROM #IndexSanity i + 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()) + 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, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 21 AS check_id, + MAX(i.index_sanity_id) AS index_sanity_id, + 150 AS Priority, + N'Index Hoarder' AS findings_group, + N'More Than 5 Percent NC Indexes Are Unused' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/IndexHoarder' AS URL, + CAST (@percent_NC_indexes_unused AS NVARCHAR(30)) + N' percent NC indexes (' + CAST(COUNT(*) AS NVARCHAR(10)) + N') unused. ' + + N'These take up ' + CAST (@NC_indexes_unused_reserved_MB AS NVARCHAR(30)) + N'MB of space.' AS details, + i.database_name + ' (' + CAST (COUNT(*) AS NVARCHAR(30)) + N' indexes)' AS index_definition, + '' AS secret_columns, + CAST(SUM(total_reads) AS NVARCHAR(256)) + N' reads (ALL); ' + + CAST(SUM([user_updates]) AS NVARCHAR(256)) + N' writes (ALL)' AS index_usage_summary, + + REPLACE(CONVERT(NVARCHAR(30),CAST(MAX([total_rows]) AS MONEY), 1), '.00', '') + N' rows (MAX)' + + CASE WHEN SUM(total_reserved_MB) > 1024 THEN + N'; ' + CAST(CAST(SUM(total_reserved_MB)/1024. AS NUMERIC(29,1)) AS NVARCHAR(30)) + 'GB (ALL)' + WHEN SUM(total_reserved_MB) > 0 THEN + N'; ' + CAST(CAST(SUM(total_reserved_MB) AS NUMERIC(29,1)) AS NVARCHAR(30)) + 'MB (ALL)' + ELSE '' + END AS index_size_summary + FROM #IndexSanity i + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + 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()) + GROUP BY i.database_name + OPTION ( RECOMPILE ); - SELECT 'PERFMON' AS Pattern, pLast.[object_name], pLast.counter_name, pLast.instance_name, - pFirst.SampleTime AS FirstSampleTime, pFirst.cntr_value AS FirstSampleValue, - pLast.SampleTime AS LastSampleTime, pLast.cntr_value AS LastSampleValue, - pLast.cntr_value - pFirst.cntr_value AS ValueDelta, - ((1.0 * pLast.cntr_value - pFirst.cntr_value) / DATEDIFF(ss, pFirst.SampleTime, pLast.SampleTime)) AS ValuePerSecond - FROM #PerfmonStats pLast - INNER JOIN #PerfmonStats pFirst ON pFirst.[object_name] = pLast.[object_name] AND pFirst.counter_name = pLast.counter_name AND (pFirst.instance_name = pLast.instance_name OR (pFirst.instance_name IS NULL AND pLast.instance_name IS NULL)) - AND pLast.ID > pFirst.ID - WHERE pLast.cntr_value <> pFirst.cntr_value - ORDER BY Pattern, pLast.[object_name], pLast.counter_name, pLast.instance_name; + RAISERROR(N'check_id 23: Indexes with 7 or more columns. (Borderline)', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 23 AS check_id, + i.index_sanity_id, + 150 AS Priority, + N'Index Hoarder' AS findings_group, + N'Borderline: Wide Indexes (7 or More Columns)' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/IndexHoarder' AS URL, + CAST(count_key_columns + count_included_columns AS NVARCHAR(10)) + ' columns on ' + + i.db_schema_object_indexid AS details, i.index_definition, + i.secret_columns, + i.index_usage_summary, + sz.index_size_summary + FROM #IndexSanity AS i + JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id + WHERE ( count_key_columns + count_included_columns ) >= 7 + OPTION ( RECOMPILE ); + RAISERROR(N'check_id 24: Wide clustered indexes (> 3 columns or > 16 bytes).', 0,1) WITH NOWAIT; + WITH count_columns AS ( + SELECT database_id, [object_id], + SUM(CASE max_length WHEN -1 THEN 0 ELSE max_length END) AS sum_max_length + FROM #IndexColumns ic + WHERE index_id IN (1,0) /*Heap or clustered only*/ + AND key_ordinal > 0 + GROUP BY database_id, object_id + ) + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 24 AS check_id, + i.index_sanity_id, + 150 AS Priority, + N'Index Hoarder' AS findings_group, + N'Wide Clustered Index (> 3 columns OR > 16 bytes)' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/IndexHoarder' AS URL, + CAST (i.count_key_columns AS NVARCHAR(10)) + N' columns with potential size of ' + + CAST(cc.sum_max_length AS NVARCHAR(10)) + + N' bytes in clustered index:' + i.db_schema_object_name + + N'. ' + + (SELECT CAST(COUNT(*) AS NVARCHAR(23)) + FROM #IndexSanity i2 + WHERE i2.[object_id]=i.[object_id] + AND i2.database_id = i.database_id + AND i2.index_id <> 1 + AND i2.is_disabled=0 + AND i2.is_hypothetical=0) + + N' NC indexes on the table.' + AS details, + i.index_definition, + secret_columns, + i.index_usage_summary, + ip.index_size_summary + FROM #IndexSanity i + JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id + JOIN count_columns AS cc ON i.[object_id]=cc.[object_id] + AND i.database_id = cc.database_id + WHERE index_id = 1 /* clustered only */ + AND + (count_key_columns > 3 /*More than three key columns.*/ + OR cc.sum_max_length > 16 /*More than 16 bytes in key */) + AND i.is_CX_columnstore = 0 + ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); - ------------------------- - --What happened: #QueryStats - ------------------------- - IF @CheckProcedureCache = 1 - BEGIN - - SELECT qsNow.*, qsFirst.* - FROM #QueryStats qsNow - INNER JOIN #QueryStats qsFirst ON qsNow.[sql_handle] = qsFirst.[sql_handle] AND qsNow.statement_start_offset = qsFirst.statement_start_offset AND qsNow.statement_end_offset = qsFirst.statement_end_offset AND qsNow.plan_generation_num = qsFirst.plan_generation_num AND qsNow.plan_handle = qsFirst.plan_handle AND qsFirst.Pass = 1 - WHERE qsNow.Pass = 2; - END; - ELSE - BEGIN - SELECT 'Plan Cache' AS [Pattern], 'Plan cache not analyzed' AS [Finding], 'Use @CheckProcedureCache = 1 or run sp_BlitzCache for more analysis' AS [More Info], CONVERT(XML, @StockDetailsHeader + 'firstresponderkit.org' + @StockDetailsFooter) AS [Details]; - END; - END; - - DROP TABLE #BlitzFirstResults; - - /* What's running right now? This is the first and last result set. */ - IF @SinceStartup = 0 AND @Seconds > 0 AND @ExpertMode = 1 AND @OutputType <> 'NONE' - BEGIN - IF OBJECT_ID('master.dbo.sp_BlitzWho') IS NULL AND OBJECT_ID('dbo.sp_BlitzWho') IS NULL - BEGIN - PRINT N'sp_BlitzWho is not installed in the current database_files. You can get a copy from http://FirstResponderKit.org'; - END; - ELSE - BEGIN - EXEC (@BlitzWho); - END; - END; /* IF @SinceStartup = 0 AND @Seconds > 0 AND @ExpertMode = 1 AND @OutputType <> 'NONE' - What's running right now? This is the first and last result set. */ - -END; /* IF @LogMessage IS NULL */ -END; /* ELSE IF @OutputType = 'SCHEMA' */ - -SET NOCOUNT OFF; -GO - + RAISERROR(N'check_id 25: Addicted to nullable columns.', 0,1) WITH NOWAIT; + WITH count_columns AS ( + SELECT [object_id], + [database_id], + [schema_name], + SUM(CASE is_nullable WHEN 1 THEN 0 ELSE 1 END) AS non_nullable_columns, + COUNT(*) AS total_columns + FROM #IndexColumns ic + WHERE index_id IN (1,0) /*Heap or clustered only*/ + GROUP BY [object_id], + [database_id], + [schema_name] + ) + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 25 AS check_id, + i.index_sanity_id, + 200 AS Priority, + N'Index Hoarder' AS findings_group, + N'Addicted to Nulls' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/IndexHoarder' AS URL, + i.db_schema_object_name + + N' allows null in ' + CAST((total_columns-non_nullable_columns) AS NVARCHAR(10)) + + N' of ' + CAST(total_columns AS NVARCHAR(10)) + + N' columns.' AS details, + i.index_definition, + secret_columns, + ISNULL(i.index_usage_summary,''), + ISNULL(ip.index_size_summary,'') + FROM #IndexSanity i + JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id + JOIN count_columns AS cc ON i.[object_id]=cc.[object_id] + AND cc.database_id = ip.database_id + AND cc.[schema_name] = ip.[schema_name] + WHERE i.index_id IN (1,0) + AND cc.non_nullable_columns < 2 + AND cc.total_columns > 3 + ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); + RAISERROR(N'check_id 26: Wide tables (35+ cols or > 2000 non-LOB bytes).', 0,1) WITH NOWAIT; + WITH count_columns AS ( + SELECT [object_id], + [database_id], + [schema_name], + SUM(CASE max_length WHEN -1 THEN 1 ELSE 0 END) AS count_lob_columns, + SUM(CASE max_length WHEN -1 THEN 0 ELSE max_length END) AS sum_max_length, + COUNT(*) AS total_columns + FROM #IndexColumns ic + WHERE index_id IN (1,0) /*Heap or clustered only*/ + GROUP BY [object_id], + [database_id], + [schema_name] + ) + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 26 AS check_id, + i.index_sanity_id, + 150 AS Priority, + N'Index Hoarder' AS findings_group, + N'Wide Tables: 35+ cols or > 2000 non-LOB bytes' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/IndexHoarder' AS URL, + i.db_schema_object_name + + N' has ' + CAST((total_columns) AS NVARCHAR(10)) + + N' total columns with a max possible width of ' + CAST(sum_max_length AS NVARCHAR(10)) + + N' bytes.' + + CASE WHEN count_lob_columns > 0 THEN CAST((count_lob_columns) AS NVARCHAR(10)) + + ' columns are LOB types.' ELSE '' + END + AS details, + i.index_definition, + secret_columns, + ISNULL(i.index_usage_summary,''), + ISNULL(ip.index_size_summary,'') + FROM #IndexSanity i + JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id + JOIN count_columns AS cc ON i.[object_id]=cc.[object_id] + AND cc.database_id = i.database_id + AND cc.[schema_name] = i.[schema_name] + WHERE i.index_id IN (1,0) + AND + (cc.total_columns >= 35 OR + cc.sum_max_length >= 2000) + ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); + + RAISERROR(N'check_id 27: Addicted to strings.', 0,1) WITH NOWAIT; + WITH count_columns AS ( + SELECT [object_id], + [database_id], + [schema_name], + SUM(CASE WHEN system_type_name IN ('varchar','nvarchar','char') OR max_length=-1 THEN 1 ELSE 0 END) AS string_or_LOB_columns, + COUNT(*) AS total_columns + FROM #IndexColumns ic + WHERE index_id IN (1,0) /*Heap or clustered only*/ + GROUP BY [object_id], + [database_id], + [schema_name] + ) + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 27 AS check_id, + i.index_sanity_id, + 200 AS Priority, + N'Index Hoarder' AS findings_group, + N'Addicted to strings' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/IndexHoarder' AS URL, + i.db_schema_object_name + + N' uses string or LOB types for ' + CAST((string_or_LOB_columns) AS NVARCHAR(10)) + + N' of ' + CAST(total_columns AS NVARCHAR(10)) + + N' columns. Check if data types are valid.' AS details, + i.index_definition, + secret_columns, + ISNULL(i.index_usage_summary,''), + ISNULL(ip.index_size_summary,'') + FROM #IndexSanity i + JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id + JOIN count_columns AS cc ON i.[object_id]=cc.[object_id] + AND cc.database_id = i.database_id + AND cc.[schema_name] = i.[schema_name] + CROSS APPLY (SELECT cc.total_columns - string_or_LOB_columns AS non_string_or_lob_columns) AS calc1 + WHERE i.index_id IN (1,0) + AND calc1.non_string_or_lob_columns <= 1 + AND cc.total_columns > 3 + ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); -/* How to run it: -EXEC dbo.sp_BlitzFirst + RAISERROR(N'check_id 28: Non-unique clustered index.', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 28 AS check_id, + i.index_sanity_id, + 150 AS Priority, + N'Index Hoarder' AS findings_group, + N'Non-Unique Clustered JIndex' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/IndexHoarder' AS URL, + N'Uniquifiers will be required! Clustered index: ' + i.db_schema_object_name + + N' and all NC indexes. ' + + (SELECT CAST(COUNT(*) AS NVARCHAR(23)) + FROM #IndexSanity i2 + WHERE i2.[object_id]=i.[object_id] + AND i2.database_id = i.database_id + AND i2.index_id <> 1 + AND i2.is_disabled=0 + AND i2.is_hypothetical=0) + + N' NC indexes on the table.' + AS details, + i.index_definition, + secret_columns, + i.index_usage_summary, + ip.index_size_summary + FROM #IndexSanity i + JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id + WHERE index_id = 1 /* clustered only */ + AND is_unique=0 /* not unique */ + AND is_CX_columnstore=0 /* not a clustered columnstore-- no unique option on those */ + ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); -With extra diagnostic info: -EXEC dbo.sp_BlitzFirst @ExpertMode = 1; + RAISERROR(N'check_id 29: NC indexes with 0 reads. (Borderline) and < 10,000 writes', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 29 AS check_id, + i.index_sanity_id, + 150 AS Priority, + N'Index Hoarder' AS findings_group, + N'Unused NC index with Low Writes' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/IndexHoarder' AS URL, + N'0 reads: ' + i.db_schema_object_indexid AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + sz.index_size_summary + FROM #IndexSanity AS i + JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id + WHERE i.total_reads=0 + AND i.user_updates < 10000 + 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()) + ORDER BY i.db_schema_object_indexid + OPTION ( RECOMPILE ); -Saving output to tables: -EXEC sp_BlitzFirst - @OutputDatabaseName = 'DBAtools' -, @OutputSchemaName = 'dbo' -, @OutputTableName = 'BlitzFirst' -, @OutputTableNameFileStats = 'BlitzFirst_FileStats' -, @OutputTableNamePerfmonStats = 'BlitzFirst_PerfmonStats' -, @OutputTableNameWaitStats = 'BlitzFirst_WaitStats' -, @OutputTableNameBlitzCache = 'BlitzCache' -, @OutputTableNameBlitzWho = 'BlitzWho' -*/ -SET ANSI_NULLS ON; -SET ANSI_PADDING ON; -SET ANSI_WARNINGS ON; -SET ARITHABORT ON; -SET CONCAT_NULL_YIELDS_NULL ON; -SET QUOTED_IDENTIFIER ON; -SET STATISTICS IO OFF; -SET STATISTICS TIME OFF; -GO -IF OBJECT_ID('dbo.sp_BlitzIndex') IS NULL - EXEC ('CREATE PROCEDURE dbo.sp_BlitzIndex AS RETURN 0;'); -GO + ---------------------------------------- + --Feature-Phobic Indexes: Check_id 30-39 + ---------------------------------------- + RAISERROR(N'check_id 30: No indexes with includes', 0,1) WITH NOWAIT; + /* This does not work the way you'd expect with @GetAllDatabases = 1. For details: + https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/825 + */ -ALTER PROCEDURE dbo.sp_BlitzIndex - @DatabaseName NVARCHAR(128) = NULL, /*Defaults to current DB if not specified*/ - @SchemaName NVARCHAR(128) = NULL, /*Requires table_name as well.*/ - @TableName NVARCHAR(128) = NULL, /*Requires schema_name as well.*/ - @Mode TINYINT=0, /*0=Diagnose, 1=Summarize, 2=Index Usage Detail, 3=Missing Index Detail, 4=Diagnose Details*/ - /*Note:@Mode doesn't matter if you're specifying schema_name and @TableName.*/ - @Filter TINYINT = 0, /* 0=no filter (default). 1=No low-usage warnings for objects with 0 reads. 2=Only warn for objects >= 500MB */ - /*Note:@Filter doesn't do anything unless @Mode=0*/ - @SkipPartitions BIT = 0, - @SkipStatistics BIT = 1, - @GetAllDatabases BIT = 0, - @BringThePain BIT = 0, - @IgnoreDatabases NVARCHAR(MAX) = NULL, /* Comma-delimited list of databases you want to skip */ - @ThresholdMB INT = 250 /* Number of megabytes that an object must be before we include it in basic results */, - @OutputType VARCHAR(20) = 'TABLE' , - @OutputServerName NVARCHAR(256) = NULL , - @OutputDatabaseName NVARCHAR(256) = NULL , - @OutputSchemaName NVARCHAR(256) = NULL , - @OutputTableName NVARCHAR(256) = NULL , - @IncludeInactiveIndexes BIT = 0 /* Will skip indexes with no reads or writes */, - @ShowAllMissingIndexRequests BIT = 0 /*Will make all missing index requests show up*/, - @SortOrder NVARCHAR(50) = NULL, /* Only affects @Mode = 2. */ - @SortDirection NVARCHAR(4) = 'DESC', /* Only affects @Mode = 2. */ - @Help TINYINT = 0, - @Debug BIT = 0, - @Version VARCHAR(30) = NULL OUTPUT, - @VersionDate DATETIME = NULL OUTPUT, - @VersionCheckMode BIT = 0 -WITH RECOMPILE -AS -SET NOCOUNT ON; -SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + SELECT database_name, + SUM(CASE WHEN count_included_columns > 0 THEN 1 ELSE 0 END) AS number_indexes_with_includes, + 100.* SUM(CASE WHEN count_included_columns > 0 THEN 1 ELSE 0 END) / ( 1.0 * COUNT(*) ) AS percent_indexes_with_includes + INTO #index_includes + FROM #IndexSanity + WHERE is_hypothetical = 0 + AND is_disabled = 0 + AND NOT (@GetAllDatabases = 1 OR @Mode = 0) + GROUP BY database_name; -SELECT @Version = '8.0', @VersionDate = '20210117'; -SET @OutputType = UPPER(@OutputType); + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 30 AS check_id, + NULL AS index_sanity_id, + 250 AS Priority, + N'Feature-Phobic Indexes' AS findings_group, + database_name AS [Database Name], + N'No Indexes Use Includes' AS finding, 'https://www.brentozar.com/go/IndexFeatures' AS URL, + N'No Indexes Use Includes' AS details, + database_name + N' (Entire database)' AS index_definition, + N'' AS secret_columns, + N'N/A' AS index_usage_summary, + N'N/A' AS index_size_summary + FROM #index_includes + WHERE number_indexes_with_includes = 0 + OPTION ( RECOMPILE ); -IF(@VersionCheckMode = 1) -BEGIN - RETURN; -END; + RAISERROR(N'check_id 31: < 3 percent of indexes have includes', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 31 AS check_id, + NULL AS index_sanity_id, + 250 AS Priority, + N'Feature-Phobic Indexes' AS findings_group, + N'Few Indexes Use Includes' AS findings, + database_name AS [Database Name], + N'https://www.brentozar.com/go/IndexFeatures' AS URL, + N'Only ' + CAST(percent_indexes_with_includes AS NVARCHAR(20)) + '% of indexes have includes' AS details, + N'Entire database' AS index_definition, + N'' AS secret_columns, + N'N/A' AS index_usage_summary, + N'N/A' AS index_size_summary + FROM #index_includes + WHERE number_indexes_with_includes > 0 AND percent_indexes_with_includes <= 3 + OPTION ( RECOMPILE ); -IF @Help = 1 -BEGIN -PRINT ' -/* -sp_BlitzIndex from http://FirstResponderKit.org - -This script analyzes the design and performance of your indexes. + RAISERROR(N'check_id 32: filtered indexes and indexed views', 0,1) WITH NOWAIT; -To learn more, visit http://FirstResponderKit.org where you can download new -versions for free, watch training videos on how it works, get more info on -the findings, contribute your own code, and more. + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT DISTINCT + 32 AS check_id, + NULL AS index_sanity_id, + 250 AS Priority, + N'Feature-Phobic Indexes' AS findings_group, + N'No Filtered Indexes or Indexed Views' AS finding, + i.database_name AS [Database Name], + N'https://www.brentozar.com/go/IndexFeatures' AS URL, + N'These are NOT always needed-- but do you know when you would use them?' AS details, + i.database_name + N' (Entire database)' AS index_definition, + N'' AS secret_columns, + N'N/A' AS index_usage_summary, + N'N/A' AS index_size_summary + FROM #IndexSanity i + WHERE i.database_name NOT IN ( + SELECT database_name + FROM #IndexSanity + WHERE filter_definition <> '' ) + AND i.database_name NOT IN ( + SELECT database_name + FROM #IndexSanity + WHERE is_indexed_view = 1 ) + OPTION ( RECOMPILE ); -Known limitations of this version: - - Only Microsoft-supported versions of SQL Server. Sorry, 2005 and 2000. - - Index create statements are just to give you a rough idea of the syntax. It includes filters and fillfactor. - -- Example 1: index creates use ONLINE=? instead of ONLINE=ON / ONLINE=OFF. This is because it is important - for the user to understand if it is going to be offline and not just run a script. - -- Example 2: they do not include all the options the index may have been created with (padding, compression - filegroup/partition scheme etc.) - -- (The compression and filegroup index create syntax is not trivial because it is set at the partition - level and is not trivial to code.) - - Does not advise you about data modeling for clustered indexes and primary keys (primarily looks for signs of insanity.) + RAISERROR(N'check_id 33: Potential filtered indexes based on column names.', 0,1) WITH NOWAIT; -Unknown limitations of this version: - - We knew them once, but we forgot. + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 33 AS check_id, + i.index_sanity_id AS index_sanity_id, + 250 AS Priority, + N'Feature-Phobic Indexes' AS findings_group, + N'Potential Filtered Index (Based on Column Name)' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/IndexFeatures' AS URL, + N'A column name in this index suggests it might be a candidate for filtering (is%, %archive%, %active%, %flag%)' AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + sz.index_size_summary + FROM #IndexColumns ic + JOIN #IndexSanity i ON ic.[object_id]=i.[object_id] + AND ic.database_id =i.database_id + AND ic.schema_name = i.schema_name + AND ic.[index_id]=i.[index_id] + AND i.[index_id] > 1 /* non-clustered index */ + JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id + WHERE (column_name LIKE 'is%' + OR column_name LIKE '%archive%' + OR column_name LIKE '%active%' + OR column_name LIKE '%flag%') + OPTION ( RECOMPILE ); + RAISERROR(N'check_id 41: Hypothetical indexes ', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 41 AS check_id, + i.index_sanity_id, + 150 AS Priority, + N'Self Loathing Indexes' AS findings_group, + N'Hypothetical Index' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/SelfLoathing' AS URL, + N'Hypothetical Index: ' + db_schema_object_indexid AS details, + i.index_definition, + i.secret_columns, + N'' AS index_usage_summary, + N'' AS index_size_summary + FROM #IndexSanity AS i + WHERE is_hypothetical = 1 + OPTION ( RECOMPILE ); -MIT License -Copyright (c) 2021 Brent Ozar Unlimited + RAISERROR(N'check_id 42: Disabled indexes', 0,1) WITH NOWAIT; + --Note: disabled NC indexes will have O rows in #IndexSanitySize! + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 42 AS check_id, + index_sanity_id, + 150 AS Priority, + N'Self Loathing Indexes' AS findings_group, + N'Disabled Index' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/SelfLoathing' AS URL, + N'Disabled Index:' + db_schema_object_indexid AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + 'DISABLED' AS index_size_summary + FROM #IndexSanity AS i + WHERE is_disabled = 1 + OPTION ( RECOMPILE ); -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: + RAISERROR(N'check_id 49: Heaps with deletes', 0,1) WITH NOWAIT; + WITH heaps_cte + AS ( SELECT [object_id], + [database_id], + [schema_name], + SUM(leaf_delete_count) AS leaf_delete_count + FROM #IndexPartitionSanity + GROUP BY [object_id], + [database_id], + [schema_name] + HAVING SUM(forwarded_fetch_count) < 1000 * @DaysUptime /* Only alert about indexes with no forwarded fetches - we already alerted about those in check_id 43 */ + AND SUM(leaf_delete_count) > 0) + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 49 AS check_id, + i.index_sanity_id, + 200 AS Priority, + N'Self Loathing Indexes' AS findings_group, + N'Heaps with Deletes' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/SelfLoathing' AS URL, + CAST(h.leaf_delete_count AS NVARCHAR(256)) + N' deletes against heap:' + + db_schema_object_indexid AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + sz.index_size_summary + FROM #IndexSanity i + JOIN heaps_cte h ON i.[object_id] = h.[object_id] + AND i.[database_id] = h.[database_id] + AND i.[schema_name] = h.[schema_name] + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + WHERE i.index_id = 0 + AND sz.total_reserved_MB >= CASE WHEN NOT (@GetAllDatabases = 1 OR @Mode = 4) THEN @ThresholdMB ELSE sz.total_reserved_MB END + OPTION ( RECOMPILE ); -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. + ---------------------------------------- + --Abnormal Psychology : Check_id 60-79 + ---------------------------------------- + RAISERROR(N'check_id 60: XML indexes', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 60 AS check_id, + i.index_sanity_id, + 150 AS Priority, + N'Abnormal Psychology' AS findings_group, + N'XML Index' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, + i.db_schema_object_indexid AS details, + i.index_definition, + i.secret_columns, + N'' AS index_usage_summary, + ISNULL(sz.index_size_summary,'') AS index_size_summary + FROM #IndexSanity AS i + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + WHERE i.is_XML = 1 + OPTION ( RECOMPILE ); -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -'; -RETURN; -END; /* @Help = 1 */ + RAISERROR(N'check_id 61: Columnstore indexes', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 61 AS check_id, + i.index_sanity_id, + 150 AS Priority, + N'Abnormal Psychology' AS findings_group, + CASE WHEN i.is_NC_columnstore=1 + THEN N'NC Columnstore Index' + ELSE N'Clustered Columnstore Index' + END AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, + i.db_schema_object_indexid AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + ISNULL(sz.index_size_summary,'') AS index_size_summary + FROM #IndexSanity AS i + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + WHERE i.is_NC_columnstore = 1 OR i.is_CX_columnstore=1 + OPTION ( RECOMPILE ); -DECLARE @ScriptVersionName NVARCHAR(50); -DECLARE @DaysUptime NUMERIC(23,2); -DECLARE @DatabaseID INT; -DECLARE @ObjectID INT; -DECLARE @dsql NVARCHAR(MAX); -DECLARE @params NVARCHAR(MAX); -DECLARE @msg NVARCHAR(4000); -DECLARE @ErrorSeverity INT; -DECLARE @ErrorState INT; -DECLARE @Rowcount BIGINT; -DECLARE @SQLServerProductVersion NVARCHAR(128); -DECLARE @SQLServerEdition INT; -DECLARE @FilterMB INT; -DECLARE @collation NVARCHAR(256); -DECLARE @NumDatabases INT; -DECLARE @LineFeed NVARCHAR(5); -DECLARE @DaysUptimeInsertValue NVARCHAR(256); -DECLARE @DatabaseToIgnore NVARCHAR(MAX); -DECLARE @ColumnList NVARCHAR(MAX); -/* Let's get @SortOrder set to lower case here for comparisons later */ -SET @SortOrder = REPLACE(LOWER(@SortOrder), N' ', N'_'); -SET @SortDirection = LOWER(@SortDirection); + RAISERROR(N'check_id 62: Spatial indexes', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 62 AS check_id, + i.index_sanity_id, + 150 AS Priority, + N'Abnormal Psychology' AS findings_group, + N'Spatial Index' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, + i.db_schema_object_indexid AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + ISNULL(sz.index_size_summary,'') AS index_size_summary + FROM #IndexSanity AS i + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + WHERE i.is_spatial = 1 + OPTION ( RECOMPILE ); -SET @LineFeed = CHAR(13) + CHAR(10); -SELECT @SQLServerProductVersion = CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)); -SELECT @SQLServerEdition =CAST(SERVERPROPERTY('EngineEdition') AS INT); /* We default to online index creates where EngineEdition=3*/ -SET @FilterMB=250; -SELECT @ScriptVersionName = 'sp_BlitzIndex(TM) v' + @Version + ' - ' + DATENAME(MM, @VersionDate) + ' ' + RIGHT('0'+DATENAME(DD, @VersionDate),2) + ', ' + DATENAME(YY, @VersionDate); -SET @IgnoreDatabases = REPLACE(REPLACE(LTRIM(RTRIM(@IgnoreDatabases)), CHAR(10), ''), CHAR(13), ''); + RAISERROR(N'check_id 63: Compressed indexes', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 63 AS check_id, + i.index_sanity_id, + 150 AS Priority, + N'Abnormal Psychology' AS findings_group, + N'Compressed Index' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, + i.db_schema_object_indexid + N'. COMPRESSION: ' + sz.data_compression_desc AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + ISNULL(sz.index_size_summary,'') AS index_size_summary + FROM #IndexSanity AS i + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + WHERE sz.data_compression_desc LIKE '%PAGE%' OR sz.data_compression_desc LIKE '%ROW%' + OPTION ( RECOMPILE ); -RAISERROR(N'Starting run. %s', 0,1, @ScriptVersionName) WITH NOWAIT; - -IF(@OutputType NOT IN ('TABLE','NONE')) -BEGIN - RAISERROR('Invalid value for parameter @OutputType. Expected: (TABLE;NONE)',12,1); - RETURN; -END; - -IF(@OutputType = 'NONE') -BEGIN - IF(@OutputTableName IS NULL OR @OutputSchemaName IS NULL OR @OutputDatabaseName IS NULL) - BEGIN - RAISERROR('This procedure should be called with a value for @Output* parameters, as @OutputType is set to NONE',12,1); - RETURN; - END; - IF(@BringThePain = 1) - BEGIN - RAISERROR('Incompatible Parameters: @BringThePain set to 1 and @OutputType set to NONE',12,1); - RETURN; - END; - /* Eventually limit by mode - IF(@Mode not in (0,4)) - BEGIN - RAISERROR('Incompatible Parameters: @Mode set to %d and @OutputType set to NONE',12,1,@Mode); - RETURN; - END; - */ -END; + RAISERROR(N'check_id 64: Partitioned', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 64 AS check_id, + i.index_sanity_id, + 150 AS Priority, + N'Abnormal Psychology' AS findings_group, + N'Partitioned Index' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, + i.db_schema_object_indexid AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + ISNULL(sz.index_size_summary,'') AS index_size_summary + FROM #IndexSanity AS i + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + WHERE i.partition_key_column_name IS NOT NULL + OPTION ( RECOMPILE ); -IF OBJECT_ID('tempdb..#IndexSanity') IS NOT NULL - DROP TABLE #IndexSanity; + RAISERROR(N'check_id 65: Non-Aligned Partitioned', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 65 AS check_id, + i.index_sanity_id, + 150 AS Priority, + N'Abnormal Psychology' AS findings_group, + N'Non-Aligned Index on a Partitioned Table' AS finding, + i.[database_name] AS [Database Name], + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, + i.db_schema_object_indexid AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + ISNULL(sz.index_size_summary,'') AS index_size_summary + FROM #IndexSanity AS i + JOIN #IndexSanity AS iParent ON + i.[object_id]=iParent.[object_id] + AND i.database_id = iParent.database_id + AND i.schema_name = iParent.schema_name + AND iParent.index_id IN (0,1) /* could be a partitioned heap or clustered table */ + AND iParent.partition_key_column_name IS NOT NULL /* parent is partitioned*/ + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + WHERE i.partition_key_column_name IS NULL + OPTION ( RECOMPILE ); -IF OBJECT_ID('tempdb..#IndexPartitionSanity') IS NOT NULL - DROP TABLE #IndexPartitionSanity; + RAISERROR(N'check_id 66: Recently created tables/indexes (1 week)', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 66 AS check_id, + i.index_sanity_id, + 200 AS Priority, + N'Abnormal Psychology' AS findings_group, + N'Recently Created Tables/Indexes (1 week)' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, + i.db_schema_object_indexid + N' was created on ' + + CONVERT(NVARCHAR(16),i.create_date,121) + + N'. Tables/indexes which are dropped/created regularly require special methods for index tuning.' + AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + ISNULL(sz.index_size_summary,'') AS index_size_summary + FROM #IndexSanity AS i + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + WHERE i.create_date >= DATEADD(dd,-7,GETDATE()) + OPTION ( RECOMPILE ); -IF OBJECT_ID('tempdb..#IndexSanitySize') IS NOT NULL - DROP TABLE #IndexSanitySize; + RAISERROR(N'check_id 67: Recently modified tables/indexes (2 days)', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 67 AS check_id, + i.index_sanity_id, + 200 AS Priority, + N'Abnormal Psychology' AS findings_group, + N'Recently Modified Tables/Indexes (2 days)' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, + i.db_schema_object_indexid + N' was modified on ' + + CONVERT(NVARCHAR(16),i.modify_date,121) + + N'. A large amount of recently modified indexes may mean a lot of rebuilds are occurring each night.' + AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + ISNULL(sz.index_size_summary,'') AS index_size_summary + FROM #IndexSanity AS i + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + WHERE i.modify_date > DATEADD(dd,-2,GETDATE()) + AND /*Exclude recently created tables.*/ + i.create_date < DATEADD(dd,-7,GETDATE()) + OPTION ( RECOMPILE ); -IF OBJECT_ID('tempdb..#IndexColumns') IS NOT NULL - DROP TABLE #IndexColumns; + RAISERROR(N'check_id 69: Column collation does not match database collation', 0,1) WITH NOWAIT; + WITH count_columns AS ( + SELECT [object_id], + database_id, + schema_name, + COUNT(*) AS column_count + FROM #IndexColumns ic + WHERE index_id IN (1,0) /*Heap or clustered only*/ + AND collation_name <> @collation + GROUP BY [object_id], + database_id, + schema_name + ) + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 69 AS check_id, + i.index_sanity_id, + 150 AS Priority, + N'Abnormal Psychology' AS findings_group, + N'Column Collation Does Not Match Database Collation' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, + i.db_schema_object_name + + N' has ' + CAST(column_count AS NVARCHAR(20)) + + N' column' + CASE WHEN column_count > 1 THEN 's' ELSE '' END + + N' with a different collation than the db collation of ' + + @collation AS details, + i.index_definition, + secret_columns, + ISNULL(i.index_usage_summary,''), + ISNULL(ip.index_size_summary,'') + FROM #IndexSanity i + JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id + JOIN count_columns AS cc ON i.[object_id]=cc.[object_id] + AND cc.database_id = i.database_id + AND cc.schema_name = i.schema_name + WHERE i.index_id IN (1,0) + ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); -IF OBJECT_ID('tempdb..#MissingIndexes') IS NOT NULL - DROP TABLE #MissingIndexes; + RAISERROR(N'check_id 70: Replicated columns', 0,1) WITH NOWAIT; + WITH count_columns AS ( + SELECT [object_id], + database_id, + schema_name, + COUNT(*) AS column_count, + SUM(CASE is_replicated WHEN 1 THEN 1 ELSE 0 END) AS replicated_column_count + FROM #IndexColumns ic + WHERE index_id IN (1,0) /*Heap or clustered only*/ + GROUP BY object_id, + database_id, + schema_name + ) + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 70 AS check_id, + i.index_sanity_id, + 200 AS Priority, + N'Abnormal Psychology' AS findings_group, + N'Replicated Columns' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, + i.db_schema_object_name + + N' has ' + CAST(replicated_column_count AS NVARCHAR(20)) + + N' out of ' + CAST(column_count AS NVARCHAR(20)) + + N' column' + CASE WHEN column_count > 1 THEN 's' ELSE '' END + + N' in one or more publications.' + AS details, + i.index_definition, + secret_columns, + ISNULL(i.index_usage_summary,''), + ISNULL(ip.index_size_summary,'') + FROM #IndexSanity i + JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id + JOIN count_columns AS cc ON i.[object_id]=cc.[object_id] + AND i.database_id = cc.database_id + AND i.schema_name = cc.schema_name + WHERE i.index_id IN (1,0) + AND replicated_column_count > 0 + ORDER BY i.db_schema_object_name DESC + OPTION ( RECOMPILE ); -IF OBJECT_ID('tempdb..#ForeignKeys') IS NOT NULL - DROP TABLE #ForeignKeys; + RAISERROR(N'check_id 71: Cascading updates or cascading deletes.', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary, more_info ) + SELECT 71 AS check_id, + NULL AS index_sanity_id, + 150 AS Priority, + N'Abnormal Psychology' AS findings_group, + N'Cascading Updates or Deletes' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, + N'Foreign Key ' + foreign_key_name + + N' on ' + QUOTENAME(parent_object_name) + N'(' + LTRIM(parent_fk_columns) + N')' + + N' referencing ' + QUOTENAME(referenced_object_name) + N'(' + LTRIM(referenced_fk_columns) + N')' + + N' has settings:' + + CASE [delete_referential_action_desc] WHEN N'NO_ACTION' THEN N'' ELSE N' ON DELETE ' +[delete_referential_action_desc] END + + CASE [update_referential_action_desc] WHEN N'NO_ACTION' THEN N'' ELSE N' ON UPDATE ' + [update_referential_action_desc] END + AS details, + [fk].[database_name] + AS index_definition, + N'N/A' AS secret_columns, + N'N/A' AS index_usage_summary, + N'N/A' AS index_size_summary, + (SELECT TOP 1 more_info FROM #IndexSanity i WHERE i.object_id=fk.parent_object_id AND i.database_id = fk.database_id AND i.schema_name = fk.schema_name) + AS more_info + FROM #ForeignKeys fk + WHERE ([delete_referential_action_desc] <> N'NO_ACTION' + OR [update_referential_action_desc] <> N'NO_ACTION') + OPTION ( RECOMPILE ); -IF OBJECT_ID('tempdb..#BlitzIndexResults') IS NOT NULL - DROP TABLE #BlitzIndexResults; - -IF OBJECT_ID('tempdb..#IndexCreateTsql') IS NOT NULL - DROP TABLE #IndexCreateTsql; -IF OBJECT_ID('tempdb..#DatabaseList') IS NOT NULL - DROP TABLE #DatabaseList; + RAISERROR(N'check_id 73: In-Memory OLTP', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 73 AS check_id, + i.index_sanity_id, + 150 AS Priority, + N'Abnormal Psychology' AS findings_group, + N'In-Memory OLTP' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, + i.db_schema_object_indexid AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + ISNULL(sz.index_size_summary,'') AS index_size_summary + FROM #IndexSanity AS i + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + WHERE i.is_in_memory_oltp = 1 + OPTION ( RECOMPILE ); -IF OBJECT_ID('tempdb..#Statistics') IS NOT NULL - DROP TABLE #Statistics; + RAISERROR(N'check_id 74: Identity column with unusual seed', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 74 AS check_id, + i.index_sanity_id, + 200 AS Priority, + N'Abnormal Psychology' AS findings_group, + N'Identity Column Using a Negative Seed or Increment Other Than 1' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, + i.db_schema_object_name + N'.' + QUOTENAME(ic.column_name) + + N' is an identity with type ' + ic.system_type_name + + N', last value of ' + + ISNULL((CONVERT(NVARCHAR(256),CAST(ic.last_value AS DECIMAL(38,0)), 1)),N'NULL') + + N', seed of ' + + ISNULL((CONVERT(NVARCHAR(256),CAST(ic.seed_value AS DECIMAL(38,0)), 1)),N'NULL') + + N', increment of ' + CAST(ic.increment_value AS NVARCHAR(256)) + + N', and range of ' + + CASE ic.system_type_name WHEN 'int' THEN N'+/- 2,147,483,647' + WHEN 'smallint' THEN N'+/- 32,768' + WHEN 'tinyint' THEN N'0 to 255' + ELSE 'unknown' + END + AS details, + i.index_definition, + secret_columns, + ISNULL(i.index_usage_summary,''), + ISNULL(ip.index_size_summary,'') + FROM #IndexSanity i + JOIN #IndexColumns ic ON + i.object_id=ic.object_id + AND i.database_id = ic.database_id + AND i.schema_name = ic.schema_name + AND i.index_id IN (0,1) /* heaps and cx only */ + AND ic.is_identity=1 + AND ic.system_type_name IN ('tinyint', 'smallint', 'int') + JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id + WHERE i.index_id IN (1,0) + AND (ic.seed_value < 0 OR ic.increment_value <> 1) + ORDER BY finding, details DESC + OPTION ( RECOMPILE ); -IF OBJECT_ID('tempdb..#PartitionCompressionInfo') IS NOT NULL - DROP TABLE #PartitionCompressionInfo; + ---------------------------------------- + --Workaholics: Check_id 80-89 + ---------------------------------------- -IF OBJECT_ID('tempdb..#ComputedColumns') IS NOT NULL - DROP TABLE #ComputedColumns; - -IF OBJECT_ID('tempdb..#TraceStatus') IS NOT NULL - DROP TABLE #TraceStatus; + RAISERROR(N'check_id 80: Most scanned indexes (index_usage_stats)', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) -IF OBJECT_ID('tempdb..#TemporalTables') IS NOT NULL - DROP TABLE #TemporalTables; + --Workaholics according to index_usage_stats + --This isn't perfect: it mentions the number of scans present in a plan + --A "scan" isn't necessarily a full scan, but hey, we gotta do the best with what we've got. + --in the case of things like indexed views, the operator might be in the plan but never executed + SELECT TOP 5 + 80 AS check_id, + i.index_sanity_id AS index_sanity_id, + 200 AS Priority, + N'Workaholics' AS findings_group, + N'Scan-a-lots (index-usage-stats)' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/Workaholics' AS URL, + REPLACE(CONVERT( NVARCHAR(50),CAST(i.user_scans AS MONEY),1),'.00','') + + N' scans against ' + i.db_schema_object_indexid + + N'. Latest scan: ' + ISNULL(CAST(i.last_user_scan AS NVARCHAR(128)),'?') + N'. ' + + N'ScanFactor=' + CAST(((i.user_scans * iss.total_reserved_MB)/1000000.) AS NVARCHAR(256)) AS details, + ISNULL(i.key_column_names_with_sort_order,'N/A') AS index_definition, + ISNULL(i.secret_columns,'') AS secret_columns, + i.index_usage_summary AS index_usage_summary, + iss.index_size_summary AS index_size_summary + FROM #IndexSanity i + JOIN #IndexSanitySize iss ON i.index_sanity_id=iss.index_sanity_id + WHERE ISNULL(i.user_scans,0) > 0 + ORDER BY i.user_scans * iss.total_reserved_MB DESC + OPTION ( RECOMPILE ); -IF OBJECT_ID('tempdb..#CheckConstraints') IS NOT NULL - DROP TABLE #CheckConstraints; + RAISERROR(N'check_id 81: Top recent accesses (op stats)', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + --Workaholics according to index_operational_stats + --This isn't perfect either: range_scan_count contains full scans, partial scans, even seeks in nested loop ops + --But this can help bubble up some most-accessed tables + SELECT TOP 5 + 81 AS check_id, + i.index_sanity_id AS index_sanity_id, + 200 AS Priority, + N'Workaholics' AS findings_group, + N'Top Recent Accesses (index-op-stats)' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/Workaholics' AS URL, + ISNULL(REPLACE( + CONVERT(NVARCHAR(50),CAST((iss.total_range_scan_count + iss.total_singleton_lookup_count) AS MONEY),1), + N'.00',N'') + + N' uses of ' + i.db_schema_object_indexid + N'. ' + + REPLACE(CONVERT(NVARCHAR(50), CAST(iss.total_range_scan_count AS MONEY),1),N'.00',N'') + N' scans or seeks. ' + + REPLACE(CONVERT(NVARCHAR(50), CAST(iss.total_singleton_lookup_count AS MONEY), 1),N'.00',N'') + N' singleton lookups. ' + + N'OpStatsFactor=' + CAST(((((iss.total_range_scan_count + iss.total_singleton_lookup_count) * iss.total_reserved_MB))/1000000.) AS VARCHAR(256)),'') AS details, + ISNULL(i.key_column_names_with_sort_order,'N/A') AS index_definition, + ISNULL(i.secret_columns,'') AS secret_columns, + i.index_usage_summary AS index_usage_summary, + iss.index_size_summary AS index_size_summary + FROM #IndexSanity i + JOIN #IndexSanitySize iss ON i.index_sanity_id=iss.index_sanity_id + WHERE (ISNULL(iss.total_range_scan_count,0) > 0 OR ISNULL(iss.total_singleton_lookup_count,0) > 0) + ORDER BY ((iss.total_range_scan_count + iss.total_singleton_lookup_count) * iss.total_reserved_MB) DESC + OPTION ( RECOMPILE ); -IF OBJECT_ID('tempdb..#FilteredIndexes') IS NOT NULL - DROP TABLE #FilteredIndexes; - -IF OBJECT_ID('tempdb..#Ignore_Databases') IS NOT NULL - DROP TABLE #Ignore_Databases - RAISERROR (N'Create temp tables.',0,1) WITH NOWAIT; - CREATE TABLE #BlitzIndexResults - ( - blitz_result_id INT IDENTITY PRIMARY KEY, - check_id INT NOT NULL, - index_sanity_id INT NULL, - Priority INT NULL, - findings_group NVARCHAR(4000) NOT NULL, - finding NVARCHAR(200) NOT NULL, - [database_name] NVARCHAR(128) NULL, - URL NVARCHAR(200) NOT NULL, - details NVARCHAR(MAX) NOT NULL, - index_definition NVARCHAR(MAX) NOT NULL, - secret_columns NVARCHAR(MAX) NULL, - index_usage_summary NVARCHAR(MAX) NULL, - index_size_summary NVARCHAR(MAX) NULL, - create_tsql NVARCHAR(MAX) NULL, - more_info NVARCHAR(MAX)NULL - ); - CREATE TABLE #IndexSanity - ( - [index_sanity_id] INT IDENTITY PRIMARY KEY CLUSTERED, - [database_id] SMALLINT NOT NULL , - [object_id] INT NOT NULL , - [index_id] INT NOT NULL , - [index_type] TINYINT NOT NULL, - [database_name] NVARCHAR(128) NOT NULL , - [schema_name] NVARCHAR(128) NOT NULL , - [object_name] NVARCHAR(128) NOT NULL , - index_name NVARCHAR(128) NULL , - key_column_names NVARCHAR(MAX) NULL , - key_column_names_with_sort_order NVARCHAR(MAX) NULL , - key_column_names_with_sort_order_no_types NVARCHAR(MAX) NULL , - count_key_columns INT NULL , - include_column_names NVARCHAR(MAX) NULL , - include_column_names_no_types NVARCHAR(MAX) NULL , - count_included_columns INT NULL , - partition_key_column_name NVARCHAR(MAX) NULL, - filter_definition NVARCHAR(MAX) NOT NULL , - is_indexed_view BIT NOT NULL , - is_unique BIT NOT NULL , - is_primary_key BIT NOT NULL , - is_XML BIT NOT NULL, - is_spatial BIT NOT NULL, - is_NC_columnstore BIT NOT NULL, - is_CX_columnstore BIT NOT NULL, - is_in_memory_oltp BIT NOT NULL , - is_disabled BIT NOT NULL , - is_hypothetical BIT NOT NULL , - is_padded BIT NOT NULL , - fill_factor SMALLINT NOT NULL , - user_seeks BIGINT NOT NULL , - user_scans BIGINT NOT NULL , - user_lookups BIGINT NOT NULL , - user_updates BIGINT NULL , - last_user_seek DATETIME NULL , - last_user_scan DATETIME NULL , - last_user_lookup DATETIME NULL , - last_user_update DATETIME NULL , - is_referenced_by_foreign_key BIT DEFAULT(0), - secret_columns NVARCHAR(MAX) NULL, - count_secret_columns INT NULL, - create_date DATETIME NOT NULL, - modify_date DATETIME NOT NULL, - filter_columns_not_in_index NVARCHAR(MAX), - [db_schema_object_name] AS [schema_name] + N'.' + [object_name] , - [db_schema_object_indexid] AS [schema_name] + N'.' + [object_name] - + CASE WHEN [index_name] IS NOT NULL THEN N'.' + index_name - ELSE N'' - END + N' (' + CAST(index_id AS NVARCHAR(20)) + N')' , - first_key_column_name AS CASE WHEN count_key_columns > 1 - THEN LEFT(key_column_names, CHARINDEX(',', key_column_names, 0) - 1) - ELSE key_column_names - END , - index_definition AS - CASE WHEN partition_key_column_name IS NOT NULL - THEN N'[PARTITIONED BY:' + partition_key_column_name + N']' - ELSE '' - END + - CASE index_id - WHEN 0 THEN N'[HEAP] ' - WHEN 1 THEN N'[CX] ' - ELSE N'' END + CASE WHEN is_indexed_view = 1 THEN N'[VIEW] ' - ELSE N'' END + CASE WHEN is_primary_key = 1 THEN N'[PK] ' - ELSE N'' END + CASE WHEN is_XML = 1 THEN N'[XML] ' - ELSE N'' END + CASE WHEN is_spatial = 1 THEN N'[SPATIAL] ' - ELSE N'' END + CASE WHEN is_NC_columnstore = 1 THEN N'[COLUMNSTORE] ' - ELSE N'' END + CASE WHEN is_in_memory_oltp = 1 THEN N'[IN-MEMORY] ' - ELSE N'' END + CASE WHEN is_disabled = 1 THEN N'[DISABLED] ' - ELSE N'' END + CASE WHEN is_hypothetical = 1 THEN N'[HYPOTHETICAL] ' - ELSE N'' END + CASE WHEN is_unique = 1 AND is_primary_key = 0 THEN N'[UNIQUE] ' - ELSE N'' END + CASE WHEN count_key_columns > 0 THEN - N'[' + CAST(count_key_columns AS NVARCHAR(10)) + N' KEY' - + CASE WHEN count_key_columns > 1 THEN N'S' ELSE N'' END - + N'] ' + LTRIM(key_column_names_with_sort_order) - ELSE N'' END + CASE WHEN count_included_columns > 0 THEN - N' [' + CAST(count_included_columns AS NVARCHAR(10)) + N' INCLUDE' + - + CASE WHEN count_included_columns > 1 THEN N'S' ELSE N'' END - + N'] ' + include_column_names - ELSE N'' END + CASE WHEN filter_definition <> N'' THEN N' [FILTER] ' + filter_definition - ELSE N'' END , - [total_reads] AS user_seeks + user_scans + user_lookups, - [reads_per_write] AS CAST(CASE WHEN user_updates > 0 - THEN ( user_seeks + user_scans + user_lookups ) / (1.0 * user_updates) - ELSE 0 END AS MONEY) , - [index_usage_summary] AS - CASE WHEN is_spatial = 1 THEN N'Not Tracked' - WHEN is_disabled = 1 THEN N'Disabled' - ELSE N'Reads: ' + - REPLACE(CONVERT(NVARCHAR(30),CAST((user_seeks + user_scans + user_lookups) AS MONEY), 1), N'.00', N'') - + CASE WHEN user_seeks + user_scans + user_lookups > 0 THEN - N' (' - + RTRIM( - CASE WHEN user_seeks > 0 THEN REPLACE(CONVERT(NVARCHAR(30),CAST((user_seeks) AS MONEY), 1), N'.00', N'') + N' seek ' ELSE N'' END - + CASE WHEN user_scans > 0 THEN REPLACE(CONVERT(NVARCHAR(30),CAST((user_scans) AS MONEY), 1), N'.00', N'') + N' scan ' ELSE N'' END - + CASE WHEN user_lookups > 0 THEN REPLACE(CONVERT(NVARCHAR(30),CAST((user_lookups) AS MONEY), 1), N'.00', N'') + N' lookup' ELSE N'' END - ) - + N') ' - ELSE N' ' - END - + N'Writes: ' + - REPLACE(CONVERT(NVARCHAR(30),CAST(user_updates AS MONEY), 1), N'.00', N'') - END /* First "end" is about is_spatial */, - [more_info] AS - CASE WHEN is_in_memory_oltp = 1 - THEN N'EXEC dbo.sp_BlitzInMemoryOLTP @dbName=' + QUOTENAME([database_name],N'''') + - N', @tableName=' + QUOTENAME([object_name],N'''') + N';' - ELSE N'EXEC dbo.sp_BlitzIndex @DatabaseName=' + QUOTENAME([database_name],N'''') + - N', @SchemaName=' + QUOTENAME([schema_name],N'''') + N', @TableName=' + QUOTENAME([object_name],N'''') + N';' - END - ); - RAISERROR (N'Adding UQ index on #IndexSanity (database_id, object_id, index_id)',0,1) WITH NOWAIT; - IF NOT EXISTS(SELECT 1 FROM tempdb.sys.indexes WHERE name='uq_database_id_object_id_index_id') - CREATE UNIQUE INDEX uq_database_id_object_id_index_id ON #IndexSanity (database_id, object_id, index_id); + RAISERROR(N'check_id 93: Statistics with filters', 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 93 AS check_id, + 200 AS Priority, + 'Functioning Statistaholics' AS findings_group, + 'Filter Fixation', + s.database_name, + 'https://www.brentozar.com/go/stats' AS URL, + 'The statistic ' + QUOTENAME(s.statistics_name) + ' is filtered on [' + s.filter_definition + ']. It could be part of a filtered index, or just a filtered statistic. This is purely informational.' 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_filter = 1 + OPTION ( RECOMPILE ); - CREATE TABLE #IndexPartitionSanity - ( - [index_partition_sanity_id] INT IDENTITY, - [index_sanity_id] INT NULL , - [database_id] INT NOT NULL , - [object_id] INT NOT NULL , - [schema_name] NVARCHAR(128) NOT NULL, - [index_id] INT NOT NULL , - [partition_number] INT NOT NULL , - row_count BIGINT NOT NULL , - reserved_MB NUMERIC(29,2) NOT NULL , - reserved_LOB_MB NUMERIC(29,2) NOT NULL , - reserved_row_overflow_MB NUMERIC(29,2) NOT NULL , - reserved_dictionary_MB NUMERIC(29,2) NOT NULL , - leaf_insert_count BIGINT NULL , - leaf_delete_count BIGINT NULL , - leaf_update_count BIGINT NULL , - range_scan_count BIGINT NULL , - singleton_lookup_count BIGINT NULL , - forwarded_fetch_count BIGINT NULL , - lob_fetch_in_pages BIGINT NULL , - lob_fetch_in_bytes BIGINT NULL , - row_overflow_fetch_in_pages BIGINT NULL , - row_overflow_fetch_in_bytes BIGINT NULL , - row_lock_count BIGINT NULL , - row_lock_wait_count BIGINT NULL , - row_lock_wait_in_ms BIGINT NULL , - page_lock_count BIGINT NULL , - page_lock_wait_count BIGINT NULL , - page_lock_wait_in_ms BIGINT NULL , - index_lock_promotion_attempt_count BIGINT NULL , - index_lock_promotion_count BIGINT NULL, - data_compression_desc NVARCHAR(60) NULL, - page_latch_wait_count BIGINT NULL, - page_latch_wait_in_ms BIGINT NULL, - page_io_latch_wait_count BIGINT NULL, - page_io_latch_wait_in_ms BIGINT NULL, - lock_escalation_desc nvarchar(60) NULL - ); + RAISERROR(N'check_id 100: Computed Columns that are not Persisted.', 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 100 AS check_id, + 200 AS Priority, + 'Cold Calculators' AS findings_group, + 'Definition Defeatists' AS finding, + cc.database_name, + '' AS URL, + 'The computed column ' + QUOTENAME(cc.column_name) + ' on ' + QUOTENAME(cc.schema_name) + '.' + QUOTENAME(cc.table_name) + ' is not persisted, which means it will be calculated when a query runs.' + + 'You can change this with the following command, if the definition is deterministic: ALTER TABLE ' + QUOTENAME(cc.schema_name) + '.' + QUOTENAME(cc.table_name) + ' ALTER COLUMN ' + cc.column_name + + ' ADD PERSISTED' AS details, + cc.column_definition, + 'N/A' AS secret_columns, + 'N/A' AS index_usage_summary, + 'N/A' AS index_size_summary + FROM #ComputedColumns AS cc + WHERE cc.is_persisted = 0 + OPTION ( RECOMPILE ); - CREATE TABLE #IndexSanitySize - ( - [index_sanity_size_id] INT IDENTITY NOT NULL , - [index_sanity_id] INT NULL , - [database_id] INT NOT NULL, - [schema_name] NVARCHAR(128) NOT NULL, - partition_count INT NOT NULL , - total_rows BIGINT NOT NULL , - total_reserved_MB NUMERIC(29,2) NOT NULL , - total_reserved_LOB_MB NUMERIC(29,2) NOT NULL , - total_reserved_row_overflow_MB NUMERIC(29,2) NOT NULL , - total_reserved_dictionary_MB NUMERIC(29,2) NOT NULL , - total_leaf_delete_count BIGINT NULL, - total_leaf_update_count BIGINT NULL, - total_range_scan_count BIGINT NULL, - total_singleton_lookup_count BIGINT NULL, - total_forwarded_fetch_count BIGINT NULL, - total_row_lock_count BIGINT NULL , - total_row_lock_wait_count BIGINT NULL , - total_row_lock_wait_in_ms BIGINT NULL , - avg_row_lock_wait_in_ms BIGINT NULL , - total_page_lock_count BIGINT NULL , - total_page_lock_wait_count BIGINT NULL , - total_page_lock_wait_in_ms BIGINT NULL , - avg_page_lock_wait_in_ms BIGINT NULL , - total_index_lock_promotion_attempt_count BIGINT NULL , - total_index_lock_promotion_count BIGINT NULL , - data_compression_desc NVARCHAR(4000) NULL, - page_latch_wait_count BIGINT NULL, - page_latch_wait_in_ms BIGINT NULL, - page_io_latch_wait_count BIGINT NULL, - page_io_latch_wait_in_ms BIGINT NULL, - lock_escalation_desc nvarchar(60) NULL, - index_size_summary AS ISNULL( - CASE WHEN partition_count > 1 - THEN N'[' + CAST(partition_count AS NVARCHAR(10)) + N' PARTITIONS] ' - ELSE N'' - END + REPLACE(CONVERT(NVARCHAR(30),CAST([total_rows] AS MONEY), 1), N'.00', N'') + N' rows; ' - + CASE WHEN total_reserved_MB > 1024 THEN - CAST(CAST(total_reserved_MB/1024. AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'GB' - ELSE - CAST(CAST(total_reserved_MB AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'MB' - END - + CASE WHEN total_reserved_LOB_MB > 1024 THEN - N'; ' + CAST(CAST(total_reserved_LOB_MB/1024. AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'GB ' + CASE WHEN total_reserved_dictionary_MB = 0 THEN N'LOB' ELSE N'Columnstore' END - WHEN total_reserved_LOB_MB > 0 THEN - N'; ' + CAST(CAST(total_reserved_LOB_MB AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'MB ' + CASE WHEN total_reserved_dictionary_MB = 0 THEN N'LOB' ELSE N'Columnstore' END - ELSE '' - END - + CASE WHEN total_reserved_row_overflow_MB > 1024 THEN - N'; ' + CAST(CAST(total_reserved_row_overflow_MB/1024. AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'GB Row Overflow' - WHEN total_reserved_row_overflow_MB > 0 THEN - N'; ' + CAST(CAST(total_reserved_row_overflow_MB AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'MB Row Overflow' - ELSE '' - END - + CASE WHEN total_reserved_dictionary_MB > 1024 THEN - N'; ' + CAST(CAST(total_reserved_dictionary_MB/1024. AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'GB Dictionaries' - WHEN total_reserved_dictionary_MB > 0 THEN - N'; ' + CAST(CAST(total_reserved_dictionary_MB AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'MB Dictionaries' - ELSE '' - END , - N'Error- NULL in computed column'), - index_op_stats AS ISNULL( - ( - REPLACE(CONVERT(NVARCHAR(30),CAST(total_singleton_lookup_count AS MONEY), 1),N'.00',N'') + N' singleton lookups; ' - + REPLACE(CONVERT(NVARCHAR(30),CAST(total_range_scan_count AS MONEY), 1),N'.00',N'') + N' scans/seeks; ' - + REPLACE(CONVERT(NVARCHAR(30),CAST(total_leaf_delete_count AS MONEY), 1),N'.00',N'') + N' deletes; ' - + REPLACE(CONVERT(NVARCHAR(30),CAST(total_leaf_update_count AS MONEY), 1),N'.00',N'') + N' updates; ' - + CASE WHEN ISNULL(total_forwarded_fetch_count,0) >0 THEN - REPLACE(CONVERT(NVARCHAR(30),CAST(total_forwarded_fetch_count AS MONEY), 1),N'.00',N'') + N' forward records fetched; ' - ELSE N'' END + RAISERROR(N'check_id 110: Temporal Tables.', 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 ) - /* rows will only be in this dmv when data is in memory for the table */ - ), N'Table metadata not in memory'), - index_lock_wait_summary AS ISNULL( - CASE WHEN total_row_lock_wait_count = 0 AND total_page_lock_wait_count = 0 AND - total_index_lock_promotion_attempt_count = 0 THEN N'0 lock waits; ' - + CASE WHEN lock_escalation_desc = N'DISABLE' THEN N'Lock escalation DISABLE.' - ELSE N'' - END - ELSE - CASE WHEN total_row_lock_wait_count > 0 THEN - N'Row lock waits: ' + REPLACE(CONVERT(NVARCHAR(30),CAST(total_row_lock_wait_count AS MONEY), 1), N'.00', N'') - + N'; total duration: ' + - CASE WHEN total_row_lock_wait_in_ms >= 60000 THEN /*More than 1 min*/ - REPLACE(CONVERT(NVARCHAR(30),CAST((total_row_lock_wait_in_ms/60000) AS MONEY), 1), N'.00', N'') + N' minutes; ' - ELSE - REPLACE(CONVERT(NVARCHAR(30),CAST(ISNULL(total_row_lock_wait_in_ms/1000,0) AS MONEY), 1), N'.00', N'') + N' seconds; ' - END - + N'avg duration: ' + - CASE WHEN avg_row_lock_wait_in_ms >= 60000 THEN /*More than 1 min*/ - REPLACE(CONVERT(NVARCHAR(30),CAST((avg_row_lock_wait_in_ms/60000) AS MONEY), 1), N'.00', N'') + N' minutes; ' - ELSE - REPLACE(CONVERT(NVARCHAR(30),CAST(ISNULL(avg_row_lock_wait_in_ms/1000,0) AS MONEY), 1), N'.00', N'') + N' seconds; ' - END - ELSE N'' - END + - CASE WHEN total_page_lock_wait_count > 0 THEN - N'Page lock waits: ' + REPLACE(CONVERT(NVARCHAR(30),CAST(total_page_lock_wait_count AS MONEY), 1), N'.00', N'') - + N'; total duration: ' + - CASE WHEN total_page_lock_wait_in_ms >= 60000 THEN /*More than 1 min*/ - REPLACE(CONVERT(NVARCHAR(30),CAST((total_page_lock_wait_in_ms/60000) AS MONEY), 1), N'.00', N'') + N' minutes; ' - ELSE - REPLACE(CONVERT(NVARCHAR(30),CAST(ISNULL(total_page_lock_wait_in_ms/1000,0) AS MONEY), 1), N'.00', N'') + N' seconds; ' - END - + N'avg duration: ' + - CASE WHEN avg_page_lock_wait_in_ms >= 60000 THEN /*More than 1 min*/ - REPLACE(CONVERT(NVARCHAR(30),CAST((avg_page_lock_wait_in_ms/60000) AS MONEY), 1), N'.00', N'') + N' minutes; ' - ELSE - REPLACE(CONVERT(NVARCHAR(30),CAST(ISNULL(avg_page_lock_wait_in_ms/1000,0) AS MONEY), 1), N'.00', N'') + N' seconds; ' - END - ELSE N'' - END + - CASE WHEN total_index_lock_promotion_attempt_count > 0 THEN - N'Lock escalation attempts: ' + REPLACE(CONVERT(NVARCHAR(30),CAST(total_index_lock_promotion_attempt_count AS MONEY), 1), N'.00', N'') - + N'; Actual Escalations: ' + REPLACE(CONVERT(NVARCHAR(30),CAST(ISNULL(total_index_lock_promotion_count,0) AS MONEY), 1), N'.00', N'') +N'; ' - ELSE N'' - END + - CASE WHEN lock_escalation_desc = N'DISABLE' THEN - N'Lock escalation is disabled.' - ELSE N'' - END - END - ,'Error- NULL in computed column') - ); + SELECT 110 AS check_id, + 200 AS Priority, + 'Abnormal Psychology' AS findings_group, + 'Temporal Tables', + t.database_name, + '' AS URL, + 'The table ' + QUOTENAME(t.schema_name) + '.' + QUOTENAME(t.table_name) + ' is a temporal table, with rows versioned in ' + + QUOTENAME(t.history_schema_name) + '.' + QUOTENAME(t.history_table_name) + ' on History columns ' + QUOTENAME(t.start_column_name) + ' and ' + QUOTENAME(t.end_column_name) + '.' + AS details, + '' AS index_definition, + 'N/A' AS secret_columns, + 'N/A' AS index_usage_summary, + 'N/A' AS index_size_summary + FROM #TemporalTables AS t + OPTION ( RECOMPILE ); - CREATE TABLE #IndexColumns - ( - [database_id] INT NOT NULL, - [schema_name] NVARCHAR(128), - [object_id] INT NOT NULL , - [index_id] INT NOT NULL , - [key_ordinal] INT NULL , - is_included_column BIT NULL , - is_descending_key BIT NULL , - [partition_ordinal] INT NULL , - column_name NVARCHAR(256) NOT NULL , - system_type_name NVARCHAR(256) NOT NULL, - max_length SMALLINT NOT NULL, - [precision] TINYINT NOT NULL, - [scale] TINYINT NOT NULL, - collation_name NVARCHAR(256) NULL, - is_nullable BIT NULL, - is_identity BIT NULL, - is_computed BIT NULL, - is_replicated BIT NULL, - is_sparse BIT NULL, - is_filestream BIT NULL, - seed_value DECIMAL(38,0) NULL, - increment_value DECIMAL(38,0) NULL , - last_value DECIMAL(38,0) NULL, - is_not_for_replication BIT NULL - ); - CREATE CLUSTERED INDEX CLIX_database_id_object_id_index_id ON #IndexColumns - (database_id, object_id, index_id); - CREATE TABLE #MissingIndexes - ([database_id] INT NOT NULL, - [object_id] INT NOT NULL, - [database_name] NVARCHAR(128) NOT NULL , - [schema_name] NVARCHAR(128) NOT NULL , - [table_name] NVARCHAR(128), - [statement] NVARCHAR(512) NOT NULL, - magic_benefit_number AS (( user_seeks + user_scans ) * avg_total_user_cost * avg_user_impact), - avg_total_user_cost NUMERIC(29,4) NOT NULL, - avg_user_impact NUMERIC(29,1) NOT NULL, - user_seeks BIGINT NOT NULL, - user_scans BIGINT NOT NULL, - unique_compiles BIGINT NULL, - equality_columns NVARCHAR(MAX), - equality_columns_with_data_type NVARCHAR(MAX), - inequality_columns NVARCHAR(MAX), - inequality_columns_with_data_type NVARCHAR(MAX), - included_columns NVARCHAR(MAX), - included_columns_with_data_type NVARCHAR(MAX), - is_low BIT, - [index_estimated_impact] AS - REPLACE(CONVERT(NVARCHAR(256),CAST(CAST( - (user_seeks + user_scans) - AS BIGINT) AS MONEY), 1), '.00', '') + N' use' - + CASE WHEN (user_seeks + user_scans) > 1 THEN N's' ELSE N'' END - +N'; Impact: ' + CAST(avg_user_impact AS NVARCHAR(30)) - + N'%; Avg query cost: ' - + CAST(avg_total_user_cost AS NVARCHAR(30)), - [missing_index_details] AS - CASE WHEN COALESCE(equality_columns_with_data_type,equality_columns) IS NOT NULL - THEN N'EQUALITY: ' + COALESCE(CAST(equality_columns_with_data_type AS NVARCHAR(MAX)), CAST(equality_columns AS NVARCHAR(MAX))) + N' ' - ELSE N'' END + - CASE WHEN COALESCE(inequality_columns_with_data_type,inequality_columns) IS NOT NULL - THEN N'INEQUALITY: ' + COALESCE(CAST(inequality_columns_with_data_type AS NVARCHAR(MAX)), CAST(inequality_columns AS NVARCHAR(MAX))) + N' ' - ELSE N'' END + - CASE WHEN COALESCE(included_columns_with_data_type,included_columns) IS NOT NULL - THEN N'INCLUDE: ' + COALESCE(CAST(included_columns_with_data_type AS NVARCHAR(MAX)), CAST(included_columns AS NVARCHAR(MAX))) + N' ' - ELSE N'' END, - [create_tsql] AS N'CREATE INDEX [' - + LEFT(REPLACE(REPLACE(REPLACE(REPLACE( - ISNULL(equality_columns,N'')+ - CASE WHEN equality_columns IS NOT NULL AND inequality_columns IS NOT NULL THEN N'_' ELSE N'' END - + ISNULL(inequality_columns,''),',','') - ,'[',''),']',''),' ','_') - + CASE WHEN included_columns IS NOT NULL THEN N'_Includes' ELSE N'' END, 128) + N'] ON ' - + [statement] + N' (' + ISNULL(equality_columns,N'') - + CASE WHEN equality_columns IS NOT NULL AND inequality_columns IS NOT NULL THEN N', ' ELSE N'' END - + CASE WHEN inequality_columns IS NOT NULL THEN inequality_columns ELSE N'' END + - ') ' + CASE WHEN included_columns IS NOT NULL THEN N' INCLUDE (' + included_columns + N')' ELSE N'' END - + N' WITH (' - + N'FILLFACTOR=100, ONLINE=?, SORT_IN_TEMPDB=?, DATA_COMPRESSION=?' - + N')' - + N';' - , - [more_info] AS N'EXEC dbo.sp_BlitzIndex @DatabaseName=' + QUOTENAME([database_name],'''') + - N', @SchemaName=' + QUOTENAME([schema_name],'''') + N', @TableName=' + QUOTENAME([table_name],'''') + N';', - [sample_query_plan] XML NULL - ); + END /* IF @Mode = 4 */ - CREATE TABLE #ForeignKeys ( - [database_id] INT NOT NULL, - [database_name] NVARCHAR(128) NOT NULL , - [schema_name] NVARCHAR(128) NOT NULL , - foreign_key_name NVARCHAR(256), - parent_object_id INT, - parent_object_name NVARCHAR(256), - referenced_object_id INT, - referenced_object_name NVARCHAR(256), - is_disabled BIT, - is_not_trusted BIT, - is_not_for_replication BIT, - parent_fk_columns NVARCHAR(MAX), - referenced_fk_columns NVARCHAR(MAX), - update_referential_action_desc NVARCHAR(16), - delete_referential_action_desc NVARCHAR(60) - ); - - CREATE TABLE #IndexCreateTsql ( - index_sanity_id INT NOT NULL, - create_tsql NVARCHAR(MAX) NOT NULL - ); - CREATE TABLE #DatabaseList ( - DatabaseName NVARCHAR(256), - secondary_role_allow_connections_desc NVARCHAR(50) - ); - CREATE TABLE #PartitionCompressionInfo ( - [index_sanity_id] INT NULL, - [partition_compression_detail] NVARCHAR(4000) NULL - ); - CREATE TABLE #Statistics ( - database_id INT NOT NULL, - database_name NVARCHAR(256) NOT NULL, - table_name NVARCHAR(128) NULL, - schema_name NVARCHAR(128) NULL, - index_name NVARCHAR(128) NULL, - column_names NVARCHAR(MAX) NULL, - statistics_name NVARCHAR(128) NULL, - last_statistics_update DATETIME NULL, - days_since_last_stats_update INT NULL, - rows BIGINT NULL, - rows_sampled BIGINT NULL, - percent_sampled DECIMAL(18, 1) NULL, - histogram_steps INT NULL, - modification_counter BIGINT NULL, - percent_modifications DECIMAL(18, 1) NULL, - modifications_before_auto_update INT 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 - ); - CREATE TABLE #ComputedColumns - ( - index_sanity_id INT IDENTITY(1, 1) NOT NULL, - database_name NVARCHAR(128) NULL, - database_id INT NOT NULL, - table_name NVARCHAR(128) NOT NULL, - schema_name NVARCHAR(128) NOT NULL, - column_name NVARCHAR(128) NULL, - is_nullable BIT NULL, - definition NVARCHAR(MAX) NULL, - uses_database_collation BIT NOT NULL, - is_persisted BIT NOT NULL, - is_computed BIT NOT NULL, - is_function INT NOT NULL, - column_definition NVARCHAR(MAX) NULL - ); - - CREATE TABLE #TraceStatus - ( - TraceFlag NVARCHAR(10) , - status BIT , - Global BIT , - Session BIT - ); - CREATE TABLE #TemporalTables - ( - index_sanity_id INT IDENTITY(1, 1) NOT NULL, - database_name NVARCHAR(128) NOT NULL, - database_id INT NOT NULL, - schema_name NVARCHAR(128) NOT NULL, - table_name NVARCHAR(128) NOT NULL, - history_table_name NVARCHAR(128) NOT NULL, - history_schema_name NVARCHAR(128) NOT NULL, - start_column_name NVARCHAR(128) NOT NULL, - end_column_name NVARCHAR(128) NOT NULL, - period_name NVARCHAR(128) NOT NULL - ); - CREATE TABLE #CheckConstraints - ( - index_sanity_id INT IDENTITY(1, 1) NOT NULL, - database_name NVARCHAR(128) NULL, - database_id INT NOT NULL, - table_name NVARCHAR(128) NOT NULL, - schema_name NVARCHAR(128) NOT NULL, - constraint_name NVARCHAR(128) NULL, - is_disabled BIT NULL, - definition NVARCHAR(MAX) NULL, - uses_database_collation BIT NOT NULL, - is_not_trusted BIT NOT NULL, - is_function INT NOT NULL, - column_definition NVARCHAR(MAX) NULL - ); - CREATE TABLE #FilteredIndexes - ( - index_sanity_id INT IDENTITY(1, 1) NOT NULL, - database_name NVARCHAR(128) NULL, - database_id INT NOT NULL, - schema_name NVARCHAR(128) NOT NULL, - table_name NVARCHAR(128) NOT NULL, - index_name NVARCHAR(128) NULL, - column_name NVARCHAR(128) NULL - ); - - CREATE TABLE #Ignore_Databases - ( - DatabaseName NVARCHAR(128), - Reason NVARCHAR(100) - ); - -/* Sanitize our inputs */ -SELECT - @OutputServerName = QUOTENAME(@OutputServerName), - @OutputDatabaseName = QUOTENAME(@OutputDatabaseName), - @OutputSchemaName = QUOTENAME(@OutputSchemaName), - @OutputTableName = QUOTENAME(@OutputTableName); - - -IF @GetAllDatabases = 1 - BEGIN - INSERT INTO #DatabaseList (DatabaseName) - SELECT DB_NAME(database_id) - FROM sys.databases - WHERE user_access_desc = 'MULTI_USER' - AND state_desc = 'ONLINE' - AND database_id > 4 - AND DB_NAME(database_id) NOT LIKE 'ReportServer%' - AND DB_NAME(database_id) NOT LIKE 'rdsadmin%' - AND is_distributor = 0 - OPTION ( RECOMPILE ); - - /* Skip non-readable databases in an AG - see Github issue #1160 */ - IF EXISTS (SELECT * FROM sys.all_objects o INNER JOIN sys.all_columns c ON o.object_id = c.object_id AND o.name = 'dm_hadr_availability_replica_states' AND c.name = 'role_desc') - BEGIN - SET @dsql = N'UPDATE #DatabaseList SET secondary_role_allow_connections_desc = ''NO'' WHERE DatabaseName IN ( - SELECT d.name - FROM sys.dm_hadr_availability_replica_states rs - INNER JOIN sys.databases d ON rs.replica_id = d.replica_id - INNER JOIN sys.availability_replicas r ON rs.replica_id = r.replica_id - WHERE rs.role_desc = ''SECONDARY'' - AND r.secondary_role_allow_connections_desc = ''NO'') - OPTION ( RECOMPILE );'; - EXEC sp_executesql @dsql; - - IF EXISTS (SELECT * FROM #DatabaseList WHERE secondary_role_allow_connections_desc = 'NO') - BEGIN - INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, database_name, URL, details, index_definition, - index_usage_summary, index_size_summary ) - VALUES ( 1, - 0, - N'Skipped non-readable AG secondary databases.', - N'You are running this on an AG secondary, and some of your databases are configured as non-readable when this is a secondary node.', - N'To analyze those databases, run sp_BlitzIndex on the primary, or on a readable secondary.', - 'http://FirstResponderKit.org', '', '', '', '' - ); - END; - END; - - IF @IgnoreDatabases IS NOT NULL - AND LEN(@IgnoreDatabases) > 0 - BEGIN - RAISERROR(N'Setting up filter to ignore databases', 0, 1) WITH NOWAIT; - SET @DatabaseToIgnore = ''; - WHILE LEN(@IgnoreDatabases) > 0 - BEGIN - IF PATINDEX('%,%', @IgnoreDatabases) > 0 - BEGIN - SET @DatabaseToIgnore = SUBSTRING(@IgnoreDatabases, 0, PATINDEX('%,%',@IgnoreDatabases)) ; - - INSERT INTO #Ignore_Databases (DatabaseName, Reason) - SELECT LTRIM(RTRIM(@DatabaseToIgnore)), 'Specified in the @IgnoreDatabases parameter' - OPTION (RECOMPILE) ; - - SET @IgnoreDatabases = SUBSTRING(@IgnoreDatabases, LEN(@DatabaseToIgnore + ',') + 1, LEN(@IgnoreDatabases)) ; - END; - ELSE - BEGIN - SET @DatabaseToIgnore = @IgnoreDatabases ; - SET @IgnoreDatabases = NULL ; - INSERT INTO #Ignore_Databases (DatabaseName, Reason) - SELECT LTRIM(RTRIM(@DatabaseToIgnore)), 'Specified in the @IgnoreDatabases parameter' - OPTION (RECOMPILE) ; - END; - END; - - END - END; -ELSE - BEGIN - INSERT INTO #DatabaseList - ( DatabaseName ) - SELECT CASE - WHEN @DatabaseName IS NULL OR @DatabaseName = N'' - THEN DB_NAME() - ELSE @DatabaseName END; - END; -SET @NumDatabases = (SELECT COUNT(*) FROM #DatabaseList AS D LEFT OUTER JOIN #Ignore_Databases AS I ON D.DatabaseName = I.DatabaseName WHERE I.DatabaseName IS NULL); -SET @msg = N'Number of databases to examine: ' + CAST(@NumDatabases AS NVARCHAR(50)); -RAISERROR (@msg,0,1) WITH NOWAIT; -/* Running on 50+ databases can take a reaaallly long time, so we want explicit permission to do so (and only after warning about it) */ -BEGIN TRY - IF @NumDatabases >= 50 AND @BringThePain != 1 AND @TableName IS NULL - BEGIN - INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, URL, details, index_definition, - index_usage_summary, index_size_summary ) - VALUES ( -1, - 0 , - @ScriptVersionName, - CASE WHEN @GetAllDatabases = 1 THEN N'All Databases' ELSE N'Database ' + QUOTENAME(@DatabaseName) + N' as of ' + CONVERT(NVARCHAR(16), GETDATE(), 121) END, - N'From Your Community Volunteers', - N'http://FirstResponderKit.org', - N'', - N'', - N'' - ); - INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, database_name, URL, details, index_definition, - index_usage_summary, index_size_summary ) - VALUES ( 1, - 0, - N'You''re trying to run sp_BlitzIndex on a server with ' + CAST(@NumDatabases AS NVARCHAR(8)) + N' databases. ', - N'Running sp_BlitzIndex on a server with 50+ databases may cause temporary insanity for the server and/or user.', - N'If you''re sure you want to do this, run again with the parameter @BringThePain = 1.', - 'http://FirstResponderKit.org', - '', - '', - '', - '' - ); - - if(@OutputType <> 'NONE') - BEGIN - SELECT bir.blitz_result_id, - bir.check_id, - bir.index_sanity_id, - bir.Priority, - bir.findings_group, - bir.finding, - bir.database_name, - bir.URL, - bir.details, - bir.index_definition, - bir.secret_columns, - bir.index_usage_summary, - bir.index_size_summary, - bir.create_tsql, - bir.more_info - FROM #BlitzIndexResults AS bir; - RAISERROR('Running sp_BlitzIndex on a server with 50+ databases may cause temporary insanity for the server', 12, 1); - END; - RETURN; - END; -END TRY -BEGIN CATCH - RAISERROR (N'Failure to execute due to number of databases.', 0,1) WITH NOWAIT; - SELECT @msg = ERROR_MESSAGE(), - @ErrorSeverity = ERROR_SEVERITY(), - @ErrorState = ERROR_STATE(); - RAISERROR (@msg, @ErrorSeverity, @ErrorState); - - WHILE @@trancount > 0 - ROLLBACK; - RETURN; - END CATCH; -RAISERROR (N'Checking partition counts to exclude databases with over 100 partitions',0,1) WITH NOWAIT; -IF @BringThePain = 0 AND @SkipPartitions = 0 AND @TableName IS NULL - BEGIN - DECLARE partition_cursor CURSOR FOR - SELECT dl.DatabaseName - FROM #DatabaseList dl - LEFT OUTER JOIN #Ignore_Databases i ON dl.DatabaseName = i.DatabaseName - WHERE COALESCE(dl.secondary_role_allow_connections_desc, 'OK') <> 'NO' - AND i.DatabaseName IS NULL - OPEN partition_cursor - FETCH NEXT FROM partition_cursor INTO @DatabaseName - - WHILE @@FETCH_STATUS = 0 - BEGIN - /* Count the total number of partitions */ - SET @dsql = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @RowcountOUT = SUM(1) FROM ' + QUOTENAME(@DatabaseName) + '.sys.partitions WHERE partition_number > 1 OPTION ( RECOMPILE );'; - EXEC sp_executesql @dsql, N'@RowcountOUT BIGINT OUTPUT', @RowcountOUT = @Rowcount OUTPUT; - IF @Rowcount > 100 - BEGIN - RAISERROR (N'Skipping database %s because > 100 partitions were found. To check this database, you must set @BringThePain = 1.',0,1,@DatabaseName) WITH NOWAIT; - INSERT INTO #Ignore_Databases (DatabaseName, Reason) - SELECT @DatabaseName, 'Over 100 partitions found - use @BringThePain = 1 to analyze' - END; - FETCH NEXT FROM partition_cursor INTO @DatabaseName + + RAISERROR(N'Insert a row to help people find help', 0,1) WITH NOWAIT; + IF DATEDIFF(MM, @VersionDate, GETDATE()) > 6 + BEGIN + INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, URL, details, index_definition, + index_usage_summary, index_size_summary ) + VALUES ( -1, 0 , + 'Outdated sp_BlitzIndex', 'sp_BlitzIndex is Over 6 Months Old', 'http://FirstResponderKit.org/', + 'Fine wine gets better with age, but this ' + @ScriptVersionName + ' is more like bad cheese. Time to get a new one.', + @DaysUptimeInsertValue,N'',N'' + ); END; - CLOSE partition_cursor - DEALLOCATE partition_cursor - - END; - -INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, URL, details, index_definition, - index_usage_summary, index_size_summary ) -SELECT 1, 0 , - 'Database Skipped', - i.DatabaseName, - 'http://FirstResponderKit.org', - i.Reason, '', '', '' -FROM #Ignore_Databases i; - - -/* Last startup */ -SELECT @DaysUptime = CAST(DATEDIFF(HOUR, create_date, GETDATE()) / 24. AS NUMERIC (23,2)) -FROM sys.databases -WHERE database_id = 2; - -IF @DaysUptime = 0 OR @DaysUptime IS NULL - SET @DaysUptime = .01; - -SELECT @DaysUptimeInsertValue = 'Server: ' + (CONVERT(VARCHAR(256), (SERVERPROPERTY('ServerName')))) + ' Days Uptime: ' + RTRIM(@DaysUptime); - - -/* Permission granted or unnecessary? Ok, let's go! */ - -RAISERROR (N'Starting loop through databases',0,1) WITH NOWAIT; -DECLARE c1 CURSOR -LOCAL FAST_FORWARD -FOR -SELECT dl.DatabaseName -FROM #DatabaseList dl -LEFT OUTER JOIN #Ignore_Databases i ON dl.DatabaseName = i.DatabaseName -WHERE COALESCE(dl.secondary_role_allow_connections_desc, 'OK') <> 'NO' - AND i.DatabaseName IS NULL -ORDER BY dl.DatabaseName; - -OPEN c1; -FETCH NEXT FROM c1 INTO @DatabaseName; - WHILE @@FETCH_STATUS = 0 - -BEGIN - - RAISERROR (@LineFeed, 0, 1) WITH NOWAIT; - RAISERROR (@LineFeed, 0, 1) WITH NOWAIT; - RAISERROR (@DatabaseName, 0, 1) WITH NOWAIT; - -SELECT @DatabaseID = [database_id] -FROM sys.databases - WHERE [name] = @DatabaseName - AND user_access_desc='MULTI_USER' - AND state_desc = 'ONLINE'; ----------------------------------------- ---STEP 1: OBSERVE THE PATIENT ---This step puts index information into temp tables. ----------------------------------------- -BEGIN TRY - BEGIN - - --Validate SQL Server Version - - IF (SELECT LEFT(@SQLServerProductVersion, - CHARINDEX('.',@SQLServerProductVersion,0)-1 - )) <= 9 - BEGIN - SET @msg=N'sp_BlitzIndex is only supported on SQL Server 2008 and higher. The version of this instance is: ' + @SQLServerProductVersion; - RAISERROR(@msg,16,1); + IF EXISTS(SELECT * FROM #BlitzIndexResults) + BEGIN + INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, URL, details, index_definition, + index_usage_summary, index_size_summary ) + VALUES ( -1, 0 , + @ScriptVersionName, + CASE WHEN @GetAllDatabases = 1 THEN N'All Databases' ELSE N'Database ' + QUOTENAME(@DatabaseName) + N' as of ' + CONVERT(NVARCHAR(16),GETDATE(),121) END, + N'From Your Community Volunteers' , N'http://FirstResponderKit.org' , + @DaysUptimeInsertValue,N'',N'' + ); END; - - --Short circuit here if database name does not exist. - IF @DatabaseName IS NULL OR @DatabaseID IS NULL + ELSE IF @Mode = 0 OR (@GetAllDatabases = 1 AND @Mode <> 4) BEGIN - SET @msg='Database does not exist or is not online/multi-user: cannot proceed.'; - RAISERROR(@msg,16,1); - END; + INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, URL, details, index_definition, + index_usage_summary, index_size_summary ) + VALUES ( -1, 0 , + @ScriptVersionName, + CASE WHEN @GetAllDatabases = 1 THEN N'All Databases' ELSE N'Database ' + QUOTENAME(@DatabaseName) + N' as of ' + CONVERT(NVARCHAR(16),GETDATE(),121) END, + N'From Your Community Volunteers' , N'http://FirstResponderKit.org' , + @DaysUptimeInsertValue, N'',N'' + ); + INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, URL, details, index_definition, + index_usage_summary, index_size_summary ) + VALUES ( 1, 0 , + N'No Major Problems Found', + N'Nice Work!', + N'http://FirstResponderKit.org', + N'Consider running with @Mode = 4 in individual databases (not all) for more detailed diagnostics.', + N'The default Mode 0 only looks for very serious index issues.', + @DaysUptimeInsertValue, N'' + ); - --Validate parameters. - IF (@Mode NOT IN (0,1,2,3,4)) - BEGIN - SET @msg=N'Invalid @Mode parameter. 0=diagnose, 1=summarize, 2=index detail, 3=missing index detail, 4=diagnose detail'; - RAISERROR(@msg,16,1); END; - - IF (@Mode <> 0 AND @TableName IS NOT NULL) + ELSE BEGIN - SET @msg=N'Setting the @Mode doesn''t change behavior if you supply @TableName. Use default @Mode=0 to see table detail.'; - RAISERROR(@msg,16,1); - END; + INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, URL, details, index_definition, + index_usage_summary, index_size_summary ) + VALUES ( -1, 0 , + @ScriptVersionName, + CASE WHEN @GetAllDatabases = 1 THEN N'All Databases' ELSE N'Database ' + QUOTENAME(@DatabaseName) + N' as of ' + CONVERT(NVARCHAR(16),GETDATE(),121) END, + N'From Your Community Volunteers' , N'http://FirstResponderKit.org' , + @DaysUptimeInsertValue, N'',N'' + ); + INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, URL, details, index_definition, + index_usage_summary, index_size_summary ) + VALUES ( 1, 0 , + N'No Problems Found', + N'Nice job! Or more likely, you have a nearly empty database.', + N'http://FirstResponderKit.org', 'Time to go read some blog posts.', + @DaysUptimeInsertValue, N'', N'' + ); - IF ((@Mode <> 0 OR @TableName IS NOT NULL) AND @Filter <> 0) - BEGIN - SET @msg=N'@Filter only applies when @Mode=0 and @TableName is not specified. Please try again.'; - RAISERROR(@msg,16,1); END; - IF (@SchemaName IS NOT NULL AND @TableName IS NULL) + RAISERROR(N'Returning results.', 0,1) WITH NOWAIT; + + /*Return results.*/ + IF (@Mode = 0) BEGIN - SET @msg='We can''t run against a whole schema! Specify a @TableName, or leave both NULL for diagnosis.'; - RAISERROR(@msg,16,1); - END; - + IF(@OutputType <> 'NONE') + BEGIN + SELECT Priority, ISNULL(br.findings_group,N'') + + CASE WHEN ISNULL(br.finding,N'') <> N'' THEN N': ' ELSE N'' END + + br.finding AS [Finding], + br.[database_name] AS [Database Name], + br.details AS [Details: schema.table.index(indexid)], + br.index_definition AS [Definition: [Property]] ColumnName {datatype maxbytes}], + ISNULL(br.secret_columns,'') AS [Secret Columns], + br.index_usage_summary AS [Usage], + br.index_size_summary AS [Size], + COALESCE(br.more_info,sn.more_info,'') AS [More Info], + br.URL, + COALESCE(br.create_tsql,ts.create_tsql,'') AS [Create TSQL] + FROM #BlitzIndexResults br + LEFT JOIN #IndexSanity sn ON + br.index_sanity_id=sn.index_sanity_id + LEFT JOIN #IndexCreateTsql ts ON + br.index_sanity_id=ts.index_sanity_id + ORDER BY br.Priority ASC, br.check_id ASC, br.blitz_result_id ASC, br.findings_group ASC + OPTION (RECOMPILE); + END; - IF (@TableName IS NOT NULL AND @SchemaName IS NULL) - BEGIN - SET @SchemaName=N'dbo'; - SET @msg='@SchemaName wasn''t specified-- assuming schema=dbo.'; - RAISERROR(@msg,1,1) WITH NOWAIT; END; + ELSE IF (@Mode = 4) + IF(@OutputType <> 'NONE') + BEGIN + SELECT Priority, ISNULL(br.findings_group,N'') + + CASE WHEN ISNULL(br.finding,N'') <> N'' THEN N': ' ELSE N'' END + + br.finding AS [Finding], + br.[database_name] AS [Database Name], + br.details AS [Details: schema.table.index(indexid)], + br.index_definition AS [Definition: [Property]] ColumnName {datatype maxbytes}], + ISNULL(br.secret_columns,'') AS [Secret Columns], + br.index_usage_summary AS [Usage], + br.index_size_summary AS [Size], + COALESCE(br.more_info,sn.more_info,'') AS [More Info], + br.URL, + COALESCE(br.create_tsql,ts.create_tsql,'') AS [Create TSQL] + FROM #BlitzIndexResults br + LEFT JOIN #IndexSanity sn ON + br.index_sanity_id=sn.index_sanity_id + LEFT JOIN #IndexCreateTsql ts ON + br.index_sanity_id=ts.index_sanity_id + ORDER BY br.Priority ASC, br.check_id ASC, br.blitz_result_id ASC, br.findings_group ASC + OPTION (RECOMPILE); + END; - --If a table is specified, grab the object id. - --Short circuit if it doesn't exist. - IF @TableName IS NOT NULL - BEGIN - SET @dsql = N' - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @ObjectID= OBJECT_ID - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.objects AS so - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS sc on - so.schema_id=sc.schema_id - where so.type in (''U'', ''V'') - and so.name=' + QUOTENAME(@TableName,'''')+ N' - and sc.name=' + QUOTENAME(@SchemaName,'''')+ N' - /*Has a row in sys.indexes. This lets us get indexed views.*/ - and exists ( - SELECT si.name - FROM ' + QUOTENAME(@DatabaseName) + '.sys.indexes AS si - WHERE so.object_id=si.object_id) - OPTION (RECOMPILE);'; - - SET @params='@ObjectID INT OUTPUT'; - - IF @dsql IS NULL - RAISERROR('@dsql is null',16,1); +END /* End @Mode=0 or 4 (diagnose)*/ - EXEC sp_executesql @dsql, @params, @ObjectID=@ObjectID OUTPUT; - - IF @ObjectID IS NULL - BEGIN - SET @msg=N'Oh, this is awkward. I can''t find the table or indexed view you''re looking for in that database.' + CHAR(10) + - N'Please check your parameters.'; - RAISERROR(@msg,1,1); - RETURN; - END; - END; +ELSE IF (@Mode=1) /*Summarize*/ + BEGIN + --This mode is to give some overall stats on the database. + IF(@OutputType <> 'NONE') + BEGIN + RAISERROR(N'@Mode=1, we are summarizing.', 0,1) WITH NOWAIT; - --set @collation - SELECT @collation=collation_name - FROM sys.databases - WHERE database_id=@DatabaseID; + SELECT DB_NAME(i.database_id) AS [Database Name], + CAST((COUNT(*)) AS NVARCHAR(256)) AS [Number Objects], + CAST(CAST(SUM(sz.total_reserved_MB)/ + 1024. AS NUMERIC(29,1)) AS NVARCHAR(500)) AS [All GB], + CAST(CAST(SUM(sz.total_reserved_LOB_MB)/ + 1024. AS NUMERIC(29,1)) AS NVARCHAR(500)) AS [LOB GB], + CAST(CAST(SUM(sz.total_reserved_row_overflow_MB)/ + 1024. AS NUMERIC(29,1)) AS NVARCHAR(500)) AS [Row Overflow GB], + CAST(SUM(CASE WHEN index_id=1 THEN 1 ELSE 0 END)AS NVARCHAR(50)) AS [Clustered Tables], + CAST(SUM(CASE WHEN index_id=1 THEN sz.total_reserved_MB ELSE 0 END) + /1024. AS NUMERIC(29,1)) AS [Clustered Tables GB], + SUM(CASE WHEN index_id NOT IN (0,1) THEN 1 ELSE 0 END) AS [NC Indexes], + CAST(SUM(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) + /1024. AS NUMERIC(29,1)) AS [NC Indexes GB], + CASE WHEN SUM(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) > 0 THEN + CAST(SUM(CASE WHEN index_id IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) + / SUM(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) AS NUMERIC(29,1)) + ELSE 0 END AS [ratio table: NC Indexes], + SUM(CASE WHEN index_id=0 THEN 1 ELSE 0 END) AS [Heaps], + CAST(SUM(CASE WHEN index_id=0 THEN sz.total_reserved_MB ELSE 0 END) + /1024. AS NUMERIC(29,1)) AS [Heaps GB], + SUM(CASE WHEN index_id IN (0,1) AND partition_key_column_name IS NOT NULL THEN 1 ELSE 0 END) AS [Partitioned Tables], + SUM(CASE WHEN index_id NOT IN (0,1) AND partition_key_column_name IS NOT NULL THEN 1 ELSE 0 END) AS [Partitioned NCs], + CAST(SUM(CASE WHEN partition_key_column_name IS NOT NULL THEN sz.total_reserved_MB ELSE 0 END)/1024. AS NUMERIC(29,1)) AS [Partitioned GB], + SUM(CASE WHEN filter_definition <> '' THEN 1 ELSE 0 END) AS [Filtered Indexes], + SUM(CASE WHEN is_indexed_view=1 THEN 1 ELSE 0 END) AS [Indexed Views], + MAX(total_rows) AS [Max Row Count], + CAST(MAX(CASE WHEN index_id IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) + /1024. AS NUMERIC(29,1)) AS [Max Table GB], + CAST(MAX(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) + /1024. AS NUMERIC(29,1)) AS [Max NC Index GB], + SUM(CASE WHEN index_id IN (0,1) AND sz.total_reserved_MB > 1024 THEN 1 ELSE 0 END) AS [Count Tables > 1GB], + SUM(CASE WHEN index_id IN (0,1) AND sz.total_reserved_MB > 10240 THEN 1 ELSE 0 END) AS [Count Tables > 10GB], + SUM(CASE WHEN index_id IN (0,1) AND sz.total_reserved_MB > 102400 THEN 1 ELSE 0 END) AS [Count Tables > 100GB], + SUM(CASE WHEN index_id NOT IN (0,1) AND sz.total_reserved_MB > 1024 THEN 1 ELSE 0 END) AS [Count NCs > 1GB], + SUM(CASE WHEN index_id NOT IN (0,1) AND sz.total_reserved_MB > 10240 THEN 1 ELSE 0 END) AS [Count NCs > 10GB], + SUM(CASE WHEN index_id NOT IN (0,1) AND sz.total_reserved_MB > 102400 THEN 1 ELSE 0 END) AS [Count NCs > 100GB], + MIN(create_date) AS [Oldest Create Date], + MAX(create_date) AS [Most Recent Create Date], + MAX(modify_date) AS [Most Recent Modify Date], + 1 AS [Display Order] + FROM #IndexSanity AS i + --left join here so we don't lose disabled nc indexes + LEFT JOIN #IndexSanitySize AS sz + ON i.index_sanity_id=sz.index_sanity_id + GROUP BY DB_NAME(i.database_id) + UNION ALL + SELECT CASE WHEN @GetAllDatabases = 1 THEN N'All Databases' ELSE N'Database ' + N' as of ' + CONVERT(NVARCHAR(16),GETDATE(),121) END, + @ScriptVersionName, + N'From Your Community Volunteers' , + N'http://FirstResponderKit.org' , + @DaysUptimeInsertValue, + NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL, + NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL, + NULL,NULL,0 AS display_order + ORDER BY [Display Order] ASC + OPTION (RECOMPILE); + END; + + END; /* End @Mode=1 (summarize)*/ + ELSE IF (@Mode=2) /*Index Detail*/ + BEGIN + --This mode just spits out all the detail without filters. + --This supports slicing AND dicing in Excel + RAISERROR(N'@Mode=2, here''s the details on existing indexes.', 0,1) WITH NOWAIT; - --insert columns for clustered indexes and heaps - --collect info on identity columns for this one - SET @dsql = N'/* sp_BlitzIndex */ - SET LOCK_TIMEOUT 1000; /* To fix locking bug in sys.identity_columns. See Github issue #2176. */ - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT ' + CAST(@DatabaseID AS NVARCHAR(16)) + ', - s.name, - si.object_id, - si.index_id, - sc.key_ordinal, - sc.is_included_column, - sc.is_descending_key, - sc.partition_ordinal, - c.name as column_name, - st.name as system_type_name, - c.max_length, - c.[precision], - c.[scale], - c.collation_name, - c.is_nullable, - c.is_identity, - c.is_computed, - c.is_replicated, - ' + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N'c.is_sparse' ELSE N'NULL as is_sparse' END + N', - ' + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N'c.is_filestream' ELSE N'NULL as is_filestream' END + N', - CAST(ic.seed_value AS DECIMAL(38,0)), - CAST(ic.increment_value AS DECIMAL(38,0)), - CAST(ic.last_value AS DECIMAL(38,0)), - ic.is_not_for_replication - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.indexes si - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns c ON - si.object_id=c.object_id - LEFT JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.index_columns sc ON - sc.object_id = si.object_id - and sc.index_id=si.index_id - AND sc.column_id=c.column_id - LEFT JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.identity_columns ic ON - c.object_id=ic.object_id and - c.column_id=ic.column_id - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.types st ON - c.system_type_id=st.system_type_id - AND c.user_type_id=st.user_type_id - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects AS so ON si.object_id = so.object_id - AND so.is_ms_shipped = 0 - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s ON s.schema_id = so.schema_id - WHERE si.index_id in (0,1) ' - + CASE WHEN @ObjectID IS NOT NULL - THEN N' AND si.object_id=' + CAST(@ObjectID AS NVARCHAR(30)) - ELSE N'' END - + N'OPTION (RECOMPILE);'; - - IF @dsql IS NULL - RAISERROR('@dsql is null',16,1); - - RAISERROR (N'Inserting data into #IndexColumns for clustered indexes and heaps',0,1) WITH NOWAIT; - IF @Debug = 1 - BEGIN - PRINT SUBSTRING(@dsql, 0, 4000); - PRINT SUBSTRING(@dsql, 4000, 8000); - PRINT SUBSTRING(@dsql, 8000, 12000); - PRINT SUBSTRING(@dsql, 12000, 16000); - PRINT SUBSTRING(@dsql, 16000, 20000); - PRINT SUBSTRING(@dsql, 20000, 24000); - PRINT SUBSTRING(@dsql, 24000, 28000); - PRINT SUBSTRING(@dsql, 28000, 32000); - PRINT SUBSTRING(@dsql, 32000, 36000); - PRINT SUBSTRING(@dsql, 36000, 40000); - END; - BEGIN TRY - INSERT #IndexColumns ( database_id, [schema_name], [object_id], index_id, key_ordinal, is_included_column, is_descending_key, partition_ordinal, - column_name, system_type_name, max_length, precision, scale, collation_name, is_nullable, is_identity, is_computed, - is_replicated, is_sparse, is_filestream, seed_value, increment_value, last_value, is_not_for_replication ) - EXEC sp_executesql @dsql; - END TRY - BEGIN CATCH - RAISERROR (N'Failure inserting data into #IndexColumns for clustered indexes and heaps.', 0,1) WITH NOWAIT; - - IF @dsql IS NOT NULL + + /* Checks if @OutputServerName is populated with a valid linked server, and that the database name specified is valid */ + DECLARE @ValidOutputServer BIT; + DECLARE @ValidOutputLocation BIT; + DECLARE @LinkedServerDBCheck NVARCHAR(2000); + DECLARE @ValidLinkedServerDB INT; + DECLARE @tmpdbchk TABLE (cnt INT); + DECLARE @StringToExecute NVARCHAR(MAX); + + IF @OutputServerName IS NOT NULL BEGIN - SET @msg= 'Last @dsql: ' + @dsql; - RAISERROR(@msg, 0, 1) WITH NOWAIT; + IF (SUBSTRING(@OutputTableName, 2, 1) = '#') + BEGIN + RAISERROR('Due to the nature of temporary tables, outputting to a linked server requires a permanent table.', 16, 0); + END; + ELSE IF EXISTS (SELECT server_id FROM sys.servers WHERE QUOTENAME([name]) = @OutputServerName) + BEGIN + SET @LinkedServerDBCheck = 'SELECT 1 WHERE EXISTS (SELECT * FROM '+@OutputServerName+'.master.sys.databases WHERE QUOTENAME([name]) = '''+@OutputDatabaseName+''')'; + INSERT INTO @tmpdbchk EXEC sys.sp_executesql @LinkedServerDBCheck; + SET @ValidLinkedServerDB = (SELECT COUNT(*) FROM @tmpdbchk); + IF (@ValidLinkedServerDB > 0) + BEGIN + SET @ValidOutputServer = 1; + SET @ValidOutputLocation = 1; + END; + ELSE + RAISERROR('The specified database was not found on the output server', 16, 0); + END; + ELSE + BEGIN + RAISERROR('The specified output server was not found', 16, 0); + END; END; - - SELECT @msg = @DatabaseName + N' database failed to process. ' + ERROR_MESSAGE(), - @ErrorSeverity = 0, @ErrorState = ERROR_STATE(); - RAISERROR (@msg,@ErrorSeverity, @ErrorState )WITH NOWAIT; - - WHILE @@trancount > 0 - ROLLBACK; - - RETURN; - END CATCH; - - - --insert columns for nonclustered indexes - --this uses a full join to sys.index_columns - --We don't collect info on identity columns here. They may be in NC indexes, but we just analyze identities in the base table. - SET @dsql = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT ' + CAST(@DatabaseID AS NVARCHAR(16)) + ', - s.name, - si.object_id, - si.index_id, - sc.key_ordinal, - sc.is_included_column, - sc.is_descending_key, - sc.partition_ordinal, - c.name as column_name, - st.name as system_type_name, - c.max_length, - c.[precision], - c.[scale], - c.collation_name, - c.is_nullable, - c.is_identity, - c.is_computed, - c.is_replicated, - ' + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N'c.is_sparse' ELSE N'NULL AS is_sparse' END + N', - ' + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N'c.is_filestream' ELSE N'NULL AS is_filestream' END + N' - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.indexes AS si - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns AS c ON - si.object_id=c.object_id - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.index_columns AS sc ON - sc.object_id = si.object_id - and sc.index_id=si.index_id - AND sc.column_id=c.column_id - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.types AS st ON - c.system_type_id=st.system_type_id - AND c.user_type_id=st.user_type_id - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects AS so ON si.object_id = so.object_id - AND so.is_ms_shipped = 0 - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s ON s.schema_id = so.schema_id - WHERE si.index_id not in (0,1) ' - + CASE WHEN @ObjectID IS NOT NULL - THEN N' AND si.object_id=' + CAST(@ObjectID AS NVARCHAR(30)) - ELSE N'' END - + N'OPTION (RECOMPILE);'; - - IF @dsql IS NULL - RAISERROR('@dsql is null',16,1); - - RAISERROR (N'Inserting data into #IndexColumns for nonclustered indexes',0,1) WITH NOWAIT; - IF @Debug = 1 - BEGIN - PRINT SUBSTRING(@dsql, 0, 4000); - PRINT SUBSTRING(@dsql, 4000, 8000); - PRINT SUBSTRING(@dsql, 8000, 12000); - PRINT SUBSTRING(@dsql, 12000, 16000); - PRINT SUBSTRING(@dsql, 16000, 20000); - PRINT SUBSTRING(@dsql, 20000, 24000); - PRINT SUBSTRING(@dsql, 24000, 28000); - PRINT SUBSTRING(@dsql, 28000, 32000); - PRINT SUBSTRING(@dsql, 32000, 36000); - PRINT SUBSTRING(@dsql, 36000, 40000); - END; - INSERT #IndexColumns ( database_id, [schema_name], [object_id], index_id, key_ordinal, is_included_column, is_descending_key, partition_ordinal, - column_name, system_type_name, max_length, precision, scale, collation_name, is_nullable, is_identity, is_computed, - is_replicated, is_sparse, is_filestream ) - EXEC sp_executesql @dsql; - - SET @dsql = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT ' + CAST(@DatabaseID AS NVARCHAR(10)) + N' AS database_id, - so.object_id, - si.index_id, - si.type, - @i_DatabaseName AS database_name, - COALESCE(sc.NAME, ''Unknown'') AS [schema_name], - COALESCE(so.name, ''Unknown'') AS [object_name], - COALESCE(si.name, ''Unknown'') AS [index_name], - CASE WHEN so.[type] = CAST(''V'' AS CHAR(2)) THEN 1 ELSE 0 END, - si.is_unique, - si.is_primary_key, - CASE when si.type = 3 THEN 1 ELSE 0 END AS is_XML, - CASE when si.type = 4 THEN 1 ELSE 0 END AS is_spatial, - CASE when si.type = 6 THEN 1 ELSE 0 END AS is_NC_columnstore, - CASE when si.type = 5 then 1 else 0 end as is_CX_columnstore, - CASE when si.data_space_id = 0 then 1 else 0 end as is_in_memory_oltp, - si.is_disabled, - si.is_hypothetical, - si.is_padded, - si.fill_factor,' - + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N' - CASE WHEN si.filter_definition IS NOT NULL THEN si.filter_definition - ELSE N'''' - END AS filter_definition' ELSE N''''' AS filter_definition' END + N' - , ISNULL(us.user_seeks, 0), - ISNULL(us.user_scans, 0), - ISNULL(us.user_lookups, 0), - ISNULL(us.user_updates, 0), - us.last_user_seek, - us.last_user_scan, - us.last_user_lookup, - us.last_user_update, - so.create_date, - so.modify_date - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.indexes AS si WITH (NOLOCK) - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects AS so WITH (NOLOCK) ON si.object_id = so.object_id - AND so.is_ms_shipped = 0 /*Exclude objects shipped by Microsoft*/ - AND so.type <> ''TF'' /*Exclude table valued functions*/ - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas sc ON so.schema_id = sc.schema_id - LEFT JOIN sys.dm_db_index_usage_stats AS us WITH (NOLOCK) ON si.[object_id] = us.[object_id] - AND si.index_id = us.index_id - AND us.database_id = ' + CAST(@DatabaseID AS NVARCHAR(10)) + N' - WHERE si.[type] IN ( 0, 1, 2, 3, 4, 5, 6 ) - /* Heaps, clustered, nonclustered, XML, spatial, Cluster Columnstore, NC Columnstore */ ' + - CASE WHEN @TableName IS NOT NULL THEN N' and so.name=' + QUOTENAME(@TableName,N'''') + N' ' ELSE N'' END + - CASE WHEN ( @IncludeInactiveIndexes = 0 - AND @Mode IN (0, 4) - AND @TableName IS NULL ) - THEN N'AND ( us.user_seeks + us.user_scans + us.user_lookups + us.user_updates ) > 0' - ELSE N'' - END - + N'OPTION ( RECOMPILE ); - '; - IF @dsql IS NULL - RAISERROR('@dsql is null',16,1); - - RAISERROR (N'Inserting data into #IndexSanity',0,1) WITH NOWAIT; - IF @Debug = 1 - BEGIN - PRINT SUBSTRING(@dsql, 0, 4000); - PRINT SUBSTRING(@dsql, 4000, 8000); - PRINT SUBSTRING(@dsql, 8000, 12000); - PRINT SUBSTRING(@dsql, 12000, 16000); - PRINT SUBSTRING(@dsql, 16000, 20000); - PRINT SUBSTRING(@dsql, 20000, 24000); - PRINT SUBSTRING(@dsql, 24000, 28000); - PRINT SUBSTRING(@dsql, 28000, 32000); - PRINT SUBSTRING(@dsql, 32000, 36000); - PRINT SUBSTRING(@dsql, 36000, 40000); - END; - INSERT #IndexSanity ( [database_id], [object_id], [index_id], [index_type], [database_name], [schema_name], [object_name], - index_name, is_indexed_view, is_unique, is_primary_key, is_XML, is_spatial, is_NC_columnstore, is_CX_columnstore, is_in_memory_oltp, - is_disabled, is_hypothetical, is_padded, fill_factor, filter_definition, user_seeks, user_scans, - user_lookups, user_updates, last_user_seek, last_user_scan, last_user_lookup, last_user_update, - create_date, modify_date ) - EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; - - - RAISERROR (N'Checking partition count',0,1) WITH NOWAIT; - IF @BringThePain = 0 AND @SkipPartitions = 0 AND @TableName IS NULL - BEGIN - /* Count the total number of partitions */ - SET @dsql = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @RowcountOUT = SUM(1) FROM ' + QUOTENAME(@DatabaseName) + '.sys.partitions WHERE partition_number > 1 OPTION ( RECOMPILE );'; - EXEC sp_executesql @dsql, N'@RowcountOUT BIGINT OUTPUT', @RowcountOUT = @Rowcount OUTPUT; - IF @Rowcount > 100 - BEGIN - RAISERROR (N'Setting @SkipPartitions = 1 because > 100 partitions were found. To check them, you must set @BringThePain = 1.',0,1) WITH NOWAIT; - SET @SkipPartitions = 1; - INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, URL, details, index_definition, - index_usage_summary, index_size_summary ) - VALUES ( 1, 0 , - 'Some Checks Were Skipped', - '@SkipPartitions Forced to 1', - 'http://FirstResponderKit.org', CAST(@Rowcount AS NVARCHAR(50)) + ' partitions found. To analyze them, use @BringThePain = 1.', 'We try to keep things quick - and warning, running @BringThePain = 1 can take tens of minutes.', '', '' - ); - END; - END; - - - - IF (@SkipPartitions = 0) - BEGIN - IF (SELECT LEFT(@SQLServerProductVersion, - CHARINDEX('.',@SQLServerProductVersion,0)-1 )) <= 2147483647 --Make change here + ELSE BEGIN - - RAISERROR (N'Preferring non-2012 syntax with LEFT JOIN to sys.dm_db_index_operational_stats',0,1) WITH NOWAIT; - - --NOTE: If you want to use the newer syntax for 2012+, you'll have to change 2147483647 to 11 on line ~819 - --This change was made because on a table with lots of paritions, the OUTER APPLY was crazy slow. - SET @dsql = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT ' + CAST(@DatabaseID AS NVARCHAR(10)) + N' AS database_id, - ps.object_id, - s.name, - ps.index_id, - ps.partition_number, - ps.row_count, - ps.reserved_page_count * 8. / 1024. AS reserved_MB, - ps.lob_reserved_page_count * 8. / 1024. AS reserved_LOB_MB, - ps.row_overflow_reserved_page_count * 8. / 1024. AS reserved_row_overflow_MB, - le.lock_escalation_desc, - ' + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N'par.data_compression_desc ' ELSE N'null as data_compression_desc ' END + N', - SUM(os.leaf_insert_count), - SUM(os.leaf_delete_count), - SUM(os.leaf_update_count), - SUM(os.range_scan_count), - SUM(os.singleton_lookup_count), - SUM(os.forwarded_fetch_count), - SUM(os.lob_fetch_in_pages), - SUM(os.lob_fetch_in_bytes), - SUM(os.row_overflow_fetch_in_pages), - SUM(os.row_overflow_fetch_in_bytes), - SUM(os.row_lock_count), - SUM(os.row_lock_wait_count), - SUM(os.row_lock_wait_in_ms), - SUM(os.page_lock_count), - SUM(os.page_lock_wait_count), - SUM(os.page_lock_wait_in_ms), - SUM(os.index_lock_promotion_attempt_count), - SUM(os.index_lock_promotion_count), - SUM(os.page_latch_wait_count), - SUM(os.page_latch_wait_in_ms), - SUM(os.page_io_latch_wait_count), - SUM(os.page_io_latch_wait_in_ms), '; - - /* Get columnstore dictionary size - more info: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2585 */ - IF EXISTS (SELECT * FROM sys.all_objects WHERE name = 'column_store_dictionaries') - SET @dsql = @dsql + N' COALESCE((SELECT SUM (on_disk_size / 1024.0 / 1024) FROM ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_dictionaries dict WHERE dict.partition_id = ps.partition_id),0) AS reserved_dictionary_MB '; - ELSE - SET @dsql = @dsql + N' 0 AS reserved_dictionary_MB '; - - - SET @dsql = @dsql + N' - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_partition_stats AS ps - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partitions AS par on ps.partition_id=par.partition_id - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects AS so ON ps.object_id = so.object_id - AND so.is_ms_shipped = 0 /*Exclude objects shipped by Microsoft*/ - AND so.type <> ''TF'' /*Exclude table valued functions*/ - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s ON s.schema_id = so.schema_id - LEFT JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_index_operational_stats(' - + CAST(@DatabaseID AS NVARCHAR(10)) + N', NULL, NULL,NULL) AS os ON - ps.object_id=os.object_id and ps.index_id=os.index_id and ps.partition_number=os.partition_number - OUTER APPLY (SELECT st.lock_escalation_desc - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.tables st - WHERE st.object_id = ps.object_id - AND ps.index_id < 2 ) le - WHERE 1=1 - ' + CASE WHEN @ObjectID IS NOT NULL THEN N'AND so.object_id=' + CAST(@ObjectID AS NVARCHAR(30)) + N' ' ELSE N' ' END + N' - ' + CASE WHEN @Filter = 2 THEN N'AND ps.reserved_page_count * 8./1024. > ' + CAST(@FilterMB AS NVARCHAR(5)) + N' ' ELSE N' ' END + N' - GROUP BY ps.object_id, - s.name, - ps.index_id, - ps.partition_number, - ps.partition_id, - ps.row_count, - ps.reserved_page_count, - ps.lob_reserved_page_count, - ps.row_overflow_reserved_page_count, - le.lock_escalation_desc, - ' + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N'par.data_compression_desc ' ELSE N'null as data_compression_desc ' END + N' - ORDER BY ps.object_id, ps.index_id, ps.partition_number - OPTION ( RECOMPILE ); - '; - END; - ELSE + IF (SUBSTRING(@OutputTableName, 2, 2) = '##') + BEGIN + SET @StringToExecute = N' IF (OBJECT_ID(''[tempdb].[dbo].@@@OutputTableName@@@'') IS NOT NULL) DROP TABLE @@@OutputTableName@@@'; + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); + EXEC(@StringToExecute); + + SET @OutputServerName = QUOTENAME(CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128))); + SET @OutputDatabaseName = '[tempdb]'; + SET @OutputSchemaName = '[dbo]'; + SET @ValidOutputLocation = 1; + END; + ELSE IF (SUBSTRING(@OutputTableName, 2, 1) = '#') + BEGIN + RAISERROR('Due to the nature of Dymamic SQL, only global (i.e. double pound (##)) temp tables are supported for @OutputTableName', 16, 0); + END; + ELSE IF @OutputDatabaseName IS NOT NULL + AND @OutputSchemaName IS NOT NULL + AND @OutputTableName IS NOT NULL + AND EXISTS ( SELECT * + FROM sys.databases + WHERE QUOTENAME([name]) = @OutputDatabaseName) + BEGIN + SET @ValidOutputLocation = 1; + SET @OutputServerName = QUOTENAME(CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128))); + END; + ELSE IF @OutputDatabaseName IS NOT NULL + AND @OutputSchemaName IS NOT NULL + AND @OutputTableName IS NOT NULL + AND NOT EXISTS ( SELECT * + FROM sys.databases + WHERE QUOTENAME([name]) = @OutputDatabaseName) + BEGIN + RAISERROR('The specified output database was not found on this server', 16, 0); + END; + ELSE + BEGIN + SET @ValidOutputLocation = 0; + END; + END; + + IF (@ValidOutputLocation = 0 AND @OutputType = 'NONE') BEGIN - RAISERROR (N'Using 2012 syntax to query sys.dm_db_index_operational_stats',0,1) WITH NOWAIT; - --This is the syntax that will be used if you change 2147483647 to 11 on line ~819. - --If you have a lot of paritions and this suddenly starts running for a long time, change it back. - SET @dsql = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT ' + CAST(@DatabaseID AS NVARCHAR(10)) + N' AS database_id, - ps.object_id, - s.name, - ps.index_id, - ps.partition_number, - ps.row_count, - ps.reserved_page_count * 8. / 1024. AS reserved_MB, - ps.lob_reserved_page_count * 8. / 1024. AS reserved_LOB_MB, - ps.row_overflow_reserved_page_count * 8. / 1024. AS reserved_row_overflow_MB, - le.lock_escalation_desc, - ' + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N'par.data_compression_desc ' ELSE N'null as data_compression_desc' END + N', - SUM(os.leaf_insert_count), - SUM(os.leaf_delete_count), - SUM(os.leaf_update_count), - SUM(os.range_scan_count), - SUM(os.singleton_lookup_count), - SUM(os.forwarded_fetch_count), - SUM(os.lob_fetch_in_pages), - SUM(os.lob_fetch_in_bytes), - SUM(os.row_overflow_fetch_in_pages), - SUM(os.row_overflow_fetch_in_bytes), - SUM(os.row_lock_count), - SUM(os.row_lock_wait_count), - SUM(os.row_lock_wait_in_ms), - SUM(os.page_lock_count), - SUM(os.page_lock_wait_count), - SUM(os.page_lock_wait_in_ms), - SUM(os.index_lock_promotion_attempt_count), - SUM(os.index_lock_promotion_count), - SUM(os.page_latch_wait_count), - SUM(os.page_latch_wait_in_ms), - SUM(os.page_io_latch_wait_count), - SUM(os.page_io_latch_wait_in_ms)'; - - /* Get columnstore dictionary size - more info: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2585 */ - IF EXISTS (SELECT * FROM sys.all_objects WHERE name = 'column_store_dictionaries') - SET @dsql = @dsql + N' COALESCE((SELECT SUM (on_disk_size / 1024.0 / 1024) FROM ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_dictionaries dict WHERE dict.partition_id = ps.partition_id),0) AS reserved_dictionary_MB '; - ELSE - SET @dsql = @dsql + N' 0 AS reserved_dictionary_MB '; - - - SET @dsql = @dsql + N' - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_partition_stats AS ps - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partitions AS par on ps.partition_id=par.partition_id - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects AS so ON ps.object_id = so.object_id - AND so.is_ms_shipped = 0 /*Exclude objects shipped by Microsoft*/ - AND so.type <> ''TF'' /*Exclude table valued functions*/ - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s ON s.schema_id = so.schema_id - OUTER APPLY ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_index_operational_stats(' - + CAST(@DatabaseID AS NVARCHAR(10)) + N', ps.object_id, ps.index_id,ps.partition_number) AS os - OUTER APPLY (SELECT st.lock_escalation_desc - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.tables st - WHERE st.object_id = ps.object_id - AND ps.index_id < 2 ) le - WHERE 1=1 - ' + CASE WHEN @ObjectID IS NOT NULL THEN N'AND so.object_id=' + CAST(@ObjectID AS NVARCHAR(30)) + N' ' ELSE N' ' END + N' - ' + CASE WHEN @Filter = 2 THEN N'AND ps.reserved_page_count * 8./1024. > ' + CAST(@FilterMB AS NVARCHAR(5)) + N' ' ELSE N' ' END + ' - GROUP BY ps.object_id, - s.name, - ps.index_id, - ps.partition_number, - ps.partition_id, - ps.row_count, - ps.reserved_page_count, - ps.lob_reserved_page_count, - ps.row_overflow_reserved_page_count, - le.lock_escalation_desc, - ' + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N'par.data_compression_desc ' ELSE N'null as data_compression_desc ' END + N' - ORDER BY ps.object_id, ps.index_id, ps.partition_number - OPTION ( RECOMPILE ); - '; - END; - - IF @dsql IS NULL - RAISERROR('@dsql is null',16,1); - - RAISERROR (N'Inserting data into #IndexPartitionSanity',0,1) WITH NOWAIT; - IF @Debug = 1 - BEGIN - PRINT SUBSTRING(@dsql, 0, 4000); - PRINT SUBSTRING(@dsql, 4000, 8000); - PRINT SUBSTRING(@dsql, 8000, 12000); - PRINT SUBSTRING(@dsql, 12000, 16000); - PRINT SUBSTRING(@dsql, 16000, 20000); - PRINT SUBSTRING(@dsql, 20000, 24000); - PRINT SUBSTRING(@dsql, 24000, 28000); - PRINT SUBSTRING(@dsql, 28000, 32000); - PRINT SUBSTRING(@dsql, 32000, 36000); - PRINT SUBSTRING(@dsql, 36000, 40000); - END; - INSERT #IndexPartitionSanity ( [database_id], - [object_id], - [schema_name], - index_id, - partition_number, - row_count, - reserved_MB, - reserved_LOB_MB, - reserved_row_overflow_MB, - lock_escalation_desc, - data_compression_desc, - leaf_insert_count, - leaf_delete_count, - leaf_update_count, - range_scan_count, - singleton_lookup_count, - forwarded_fetch_count, - lob_fetch_in_pages, - lob_fetch_in_bytes, - row_overflow_fetch_in_pages, - row_overflow_fetch_in_bytes, - row_lock_count, - row_lock_wait_count, - row_lock_wait_in_ms, - page_lock_count, - page_lock_wait_count, - page_lock_wait_in_ms, - index_lock_promotion_attempt_count, - index_lock_promotion_count, - page_latch_wait_count, - page_latch_wait_in_ms, - page_io_latch_wait_count, - page_io_latch_wait_in_ms, - reserved_dictionary_MB) - EXEC sp_executesql @dsql; - - END; --End Check For @SkipPartitions = 0 - - - - RAISERROR (N'Inserting data into #MissingIndexes',0,1) WITH NOWAIT; - SET @dsql=N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' - - - SET @dsql = @dsql + 'WITH ColumnNamesWithDataTypes AS(SELECT id.index_handle,id.object_id,cn.IndexColumnType,STUFF((SELECT '', '' + cn_inner.ColumnName + '' '' + - N'' {'' + CASE WHEN ty.name IN ( ''varchar'', ''char'' ) THEN ty.name + ''('' + CASE WHEN co.max_length = -1 THEN ''max'' ELSE CAST(co.max_length AS VARCHAR(25)) END + '')'' - WHEN ty.name IN ( ''nvarchar'', ''nchar'' ) THEN ty.name + ''('' + CASE WHEN co.max_length = -1 THEN ''max'' ELSE CAST(co.max_length / 2 AS VARCHAR(25)) END + '')'' - WHEN ty.name IN ( ''decimal'', ''numeric'' ) THEN ty.name + ''('' + CAST(co.precision AS VARCHAR(25)) + '', '' + CAST(co.scale AS VARCHAR(25)) + '')'' - WHEN ty.name IN ( ''datetime2'' ) THEN ty.name + ''('' + CAST(co.scale AS VARCHAR(25)) + '')'' - ELSE ty.name END + ''}'' - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_details AS id_inner - CROSS APPLY( - SELECT LTRIM(RTRIM(v.value(''(./text())[1]'', ''varchar(max)''))) AS ColumnName, ''Equality'' AS IndexColumnType - FROM (VALUES (CONVERT(XML, N'''' + REPLACE((SELECT CAST(id_inner.equality_columns AS nvarchar(max)) FOR XML PATH('''')), N'','', N'''') + N''''))) x(n) - CROSS APPLY n.nodes(''x'') node(v) - UNION ALL - SELECT LTRIM(RTRIM(v.value(N''(./text())[1]'', ''varchar(max)''))) AS ColumnName, ''Inequality'' AS IndexColumnType - FROM (VALUES (CONVERT(XML, N'''' + REPLACE((SELECT CAST(id_inner.inequality_columns AS nvarchar(max)) FOR XML PATH('''')), N'','', N'''') + N''''))) x(n) - CROSS APPLY n.nodes(''x'') node(v) - UNION ALL - SELECT LTRIM(RTRIM(v.value(''(./text())[1]'', ''varchar(max)''))) AS ColumnName, ''Included'' AS IndexColumnType - FROM (VALUES (CONVERT(XML, N'''' + REPLACE((SELECT CAST(id_inner.included_columns AS nvarchar(max)) FOR XML PATH('''')), N'','', N'''') + N''''))) x(n) - CROSS APPLY n.nodes(''x'') node(v) - )AS cn_inner' - + /*split the string otherwise dsql cuts some of it out*/ - ' JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns AS co ON co.object_id = id_inner.object_id AND ''['' + co.name + '']'' = cn_inner.ColumnName - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.types AS ty ON ty.user_type_id = co.user_type_id - WHERE id_inner.index_handle = id.index_handle - AND id_inner.object_id = id.object_id - AND cn_inner.IndexColumnType = cn.IndexColumnType - FOR XML PATH('''') - ),1,1,'''') AS ReplaceColumnNames - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_details AS id - CROSS APPLY( - SELECT LTRIM(RTRIM(v.value(''(./text())[1]'', ''varchar(max)''))) AS ColumnName, ''Equality'' AS IndexColumnType - FROM (VALUES (CONVERT(XML, N'''' + REPLACE((SELECT CAST(id.equality_columns AS nvarchar(max)) FOR XML PATH('''')), N'','', N'''') + N''''))) x(n) - CROSS APPLY n.nodes(''x'') node(v) - UNION ALL - SELECT LTRIM(RTRIM(v.value(''(./text())[1]'', ''varchar(max)''))) AS ColumnName, ''Inequality'' AS IndexColumnType - FROM (VALUES (CONVERT(XML, N'''' + REPLACE((SELECT CAST(id.inequality_columns AS nvarchar(max)) FOR XML PATH('''')), N'','', N'''') + N''''))) x(n) - CROSS APPLY n.nodes(''x'') node(v) - UNION ALL - SELECT LTRIM(RTRIM(v.value(''(./text())[1]'', ''varchar(max)''))) AS ColumnName, ''Included'' AS IndexColumnType - FROM (VALUES (CONVERT(XML, N'''' + REPLACE((SELECT CAST(id.included_columns AS nvarchar(max)) FOR XML PATH('''')), N'','', N'''') + N''''))) x(n) - CROSS APPLY n.nodes(''x'') node(v) - )AS cn - GROUP BY id.index_handle,id.object_id,cn.IndexColumnType - ) - SELECT id.database_id, id.object_id, @i_DatabaseName, sc.[name], so.[name], id.statement , gs.avg_total_user_cost, - gs.avg_user_impact, gs.user_seeks, gs.user_scans, gs.unique_compiles, id.equality_columns, id.inequality_columns, id.included_columns, - ( - SELECT ColumnNamesWithDataTypes.ReplaceColumnNames - FROM ColumnNamesWithDataTypes WHERE ColumnNamesWithDataTypes.index_handle = id.index_handle - AND ColumnNamesWithDataTypes.object_id = id.object_id - AND ColumnNamesWithDataTypes.IndexColumnType = ''Equality'' - ) AS equality_columns_with_data_type - ,( - SELECT ColumnNamesWithDataTypes.ReplaceColumnNames - FROM ColumnNamesWithDataTypes WHERE ColumnNamesWithDataTypes.index_handle = id.index_handle - AND ColumnNamesWithDataTypes.object_id = id.object_id - AND ColumnNamesWithDataTypes.IndexColumnType = ''Inequality'' - ) AS inequality_columns_with_data_type - ,( - SELECT ColumnNamesWithDataTypes.ReplaceColumnNames - FROM ColumnNamesWithDataTypes WHERE ColumnNamesWithDataTypes.index_handle = id.index_handle - AND ColumnNamesWithDataTypes.object_id = id.object_id - AND ColumnNamesWithDataTypes.IndexColumnType = ''Included'' - ) AS included_columns_with_data_type ' - - IF NOT EXISTS (SELECT * FROM sys.all_objects WHERE name = 'dm_db_missing_index_group_stats_query') - SET @dsql = @dsql + N' , NULL AS sample_query_plan ' - ELSE - BEGIN - SET @dsql = @dsql + N' , sample_query_plan = (SELECT TOP 1 p.query_plan - FROM sys.dm_db_missing_index_group_stats gs - CROSS APPLY (SELECT TOP 1 s.plan_handle - FROM sys.dm_db_missing_index_group_stats_query q - INNER JOIN sys.dm_exec_query_stats s ON q.query_plan_hash = s.query_plan_hash - WHERE gs.group_handle = q.group_handle - ORDER BY (q.user_seeks + q.user_scans) DESC, s.total_logical_reads DESC) q2 - CROSS APPLY sys.dm_exec_query_plan(q2.plan_handle) p - WHERE gs.group_handle = gs.group_handle) ' - END - - SET @dsql = @dsql + N'FROM ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_groups ig - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_details id ON ig.index_handle = id.index_handle - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_group_stats gs ON ig.index_group_handle = gs.group_handle - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects so on - id.object_id=so.object_id - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas sc on - so.schema_id=sc.schema_id - WHERE id.database_id = ' + CAST(@DatabaseID AS NVARCHAR(30)) + ' - ' + CASE WHEN @ObjectID IS NULL THEN N'' - ELSE N'and id.object_id=' + CAST(@ObjectID AS NVARCHAR(30)) - END + - N'OPTION (RECOMPILE);'; - - IF @dsql IS NULL - RAISERROR('@dsql is null',16,1); - IF @Debug = 1 - BEGIN - PRINT SUBSTRING(@dsql, 0, 4000); - PRINT SUBSTRING(@dsql, 4000, 8000); - PRINT SUBSTRING(@dsql, 8000, 12000); - PRINT SUBSTRING(@dsql, 12000, 16000); - PRINT SUBSTRING(@dsql, 16000, 20000); - PRINT SUBSTRING(@dsql, 20000, 24000); - PRINT SUBSTRING(@dsql, 24000, 28000); - PRINT SUBSTRING(@dsql, 28000, 32000); - PRINT SUBSTRING(@dsql, 32000, 36000); - PRINT SUBSTRING(@dsql, 36000, 40000); - END; - INSERT #MissingIndexes ( [database_id], [object_id], [database_name], [schema_name], [table_name], [statement], avg_total_user_cost, - avg_user_impact, user_seeks, user_scans, unique_compiles, equality_columns, - inequality_columns, included_columns, equality_columns_with_data_type, inequality_columns_with_data_type, - included_columns_with_data_type, sample_query_plan) - EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; - - SET @dsql = N' - SELECT DB_ID(N' + QUOTENAME(@DatabaseName,'''') + N') AS [database_id], - @i_DatabaseName AS database_name, - s.name, - fk_object.name AS foreign_key_name, - parent_object.[object_id] AS parent_object_id, - parent_object.name AS parent_object_name, - referenced_object.[object_id] AS referenced_object_id, - referenced_object.name AS referenced_object_name, - fk.is_disabled, - fk.is_not_trusted, - fk.is_not_for_replication, - parent.fk_columns, - referenced.fk_columns, - [update_referential_action_desc], - [delete_referential_action_desc] - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.foreign_keys fk - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects fk_object ON fk.object_id=fk_object.object_id - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects parent_object ON fk.parent_object_id=parent_object.object_id - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects referenced_object ON fk.referenced_object_id=referenced_object.object_id - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s ON fk.schema_id=s.schema_id - CROSS APPLY ( SELECT STUFF( (SELECT N'', '' + c_parent.name AS fk_columns - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.foreign_key_columns fkc - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns c_parent ON fkc.parent_object_id=c_parent.[object_id] - AND fkc.parent_column_id=c_parent.column_id - WHERE fk.parent_object_id=fkc.parent_object_id - AND fk.[object_id]=fkc.constraint_object_id - ORDER BY fkc.constraint_column_id - FOR XML PATH('''') , - TYPE).value(''.'', ''nvarchar(max)''), 1, 1, '''')/*This is how we remove the first comma*/ ) parent ( fk_columns ) - CROSS APPLY ( SELECT STUFF( (SELECT N'', '' + c_referenced.name AS fk_columns - FROM ' + QUOTENAME(@DatabaseName) + N'.sys. foreign_key_columns fkc - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns c_referenced ON fkc.referenced_object_id=c_referenced.[object_id] - AND fkc.referenced_column_id=c_referenced.column_id - WHERE fk.referenced_object_id=fkc.referenced_object_id - and fk.[object_id]=fkc.constraint_object_id - ORDER BY fkc.constraint_column_id /*order by col name, we don''t have anything better*/ - FOR XML PATH('''') , - TYPE).value(''.'', ''nvarchar(max)''), 1, 1, '''') ) referenced ( fk_columns ) - ' + CASE WHEN @ObjectID IS NOT NULL THEN - 'WHERE fk.parent_object_id=' + CAST(@ObjectID AS NVARCHAR(30)) + N' OR fk.referenced_object_id=' + CAST(@ObjectID AS NVARCHAR(30)) + N' ' - ELSE N' ' END + ' - ORDER BY parent_object_name, foreign_key_name - OPTION (RECOMPILE);'; - IF @dsql IS NULL - RAISERROR('@dsql is null',16,1); - - RAISERROR (N'Inserting data into #ForeignKeys',0,1) WITH NOWAIT; - IF @Debug = 1 - BEGIN - PRINT SUBSTRING(@dsql, 0, 4000); - PRINT SUBSTRING(@dsql, 4000, 8000); - PRINT SUBSTRING(@dsql, 8000, 12000); - PRINT SUBSTRING(@dsql, 12000, 16000); - PRINT SUBSTRING(@dsql, 16000, 20000); - PRINT SUBSTRING(@dsql, 20000, 24000); - PRINT SUBSTRING(@dsql, 24000, 28000); - PRINT SUBSTRING(@dsql, 28000, 32000); - PRINT SUBSTRING(@dsql, 32000, 36000); - PRINT SUBSTRING(@dsql, 36000, 40000); - END; - INSERT #ForeignKeys ( [database_id], [database_name], [schema_name], foreign_key_name, parent_object_id,parent_object_name, referenced_object_id, referenced_object_name, - is_disabled, is_not_trusted, is_not_for_replication, parent_fk_columns, referenced_fk_columns, - [update_referential_action_desc], [delete_referential_action_desc] ) - EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; - - - IF @SkipStatistics = 0 /* AND DB_NAME() = @DatabaseName /* Can only get stats in the current database - see https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/1947 */ */ - BEGIN - IF ((PARSENAME(@SQLServerProductVersion, 4) >= 12) - OR (PARSENAME(@SQLServerProductVersion, 4) = 11 AND PARSENAME(@SQLServerProductVersion, 2) >= 3000) - OR (PARSENAME(@SQLServerProductVersion, 4) = 10 AND PARSENAME(@SQLServerProductVersion, 3) = 50 AND PARSENAME(@SQLServerProductVersion, 2) >= 2500)) - BEGIN - RAISERROR (N'Gathering Statistics Info With Newer Syntax.',0,1) WITH NOWAIT; - SET @dsql=N'USE ' + @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, - 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) - 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, - ISNULL(i.name, ''System Or User Statistic'') AS index_name, - ca.column_names AS column_names, - s.name AS statistics_name, - CONVERT(DATETIME, ddsp.last_updated) AS last_statistics_update, - DATEDIFF(DAY, ddsp.last_updated, GETDATE()) AS days_since_last_stats_update, - ddsp.rows, - ddsp.rows_sampled, - CAST(ddsp.rows_sampled / ( 1. * NULLIF(ddsp.rows, 0) ) * 100 AS DECIMAL(18, 1)) AS percent_sampled, - ddsp.steps AS histogram_steps, - ddsp.modification_counter, - CASE WHEN ddsp.modification_counter > 0 - THEN CAST(ddsp.modification_counter / ( 1. * NULLIF(ddsp.rows, 0) ) * 100 AS DECIMAL(18, 1)) - ELSE ddsp.modification_counter - END AS percent_modifications, - CASE WHEN ddsp.rows < 500 THEN 500 - ELSE CAST(( ddsp.rows * .20 ) + 500 AS INT) - 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 - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.stats AS s - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects obj - ON s.object_id = obj.object_id - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas sch - ON sch.schema_id = obj.schema_id - LEFT JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.indexes AS i - ON i.object_id = s.object_id - AND i.index_id = s.stats_id - OUTER APPLY ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_stats_properties(s.object_id, s.stats_id) AS ddsp - CROSS APPLY ( SELECT STUFF((SELECT '', '' + c.name - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.stats_columns AS sc - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns AS c - ON sc.column_id = c.column_id AND sc.object_id = c.object_id - WHERE sc.stats_id = s.stats_id AND sc.object_id = s.object_id - ORDER BY sc.stats_column_id - FOR XML PATH(''''), TYPE).value(''.'', ''nvarchar(max)''), 1, 2, '''') - ) ca (column_names) - WHERE obj.is_ms_shipped = 0 - OPTION (RECOMPILE);'; - - IF @dsql IS NULL - RAISERROR('@dsql is null',16,1); - - RAISERROR (N'Inserting data into #Statistics',0,1) WITH NOWAIT; - IF @Debug = 1 - BEGIN - PRINT SUBSTRING(@dsql, 0, 4000); - PRINT SUBSTRING(@dsql, 4000, 8000); - PRINT SUBSTRING(@dsql, 8000, 12000); - PRINT SUBSTRING(@dsql, 12000, 16000); - PRINT SUBSTRING(@dsql, 16000, 20000); - PRINT SUBSTRING(@dsql, 20000, 24000); - PRINT SUBSTRING(@dsql, 24000, 28000); - PRINT SUBSTRING(@dsql, 28000, 32000); - PRINT SUBSTRING(@dsql, 32000, 36000); - PRINT SUBSTRING(@dsql, 36000, 40000); - END; - - EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; - END; - ELSE + RAISERROR('Invalid output location and no output asked',12,1); + RETURN; + END; + + /* @OutputTableName lets us export the results to a permanent table */ + DECLARE @RunID UNIQUEIDENTIFIER; + SET @RunID = NEWID(); + + IF (@ValidOutputLocation = 1 AND COALESCE(@OutputServerName, @OutputDatabaseName, @OutputSchemaName, @OutputTableName) IS NOT NULL) BEGIN - RAISERROR (N'Gathering Statistics Info With Older Syntax.',0,1) WITH NOWAIT; - SET @dsql=N'USE ' + @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, 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) - 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, - ISNULL(i.name, ''System Or User Statistic'') AS index_name, - ca.column_names AS column_names, - s.name AS statistics_name, - CONVERT(DATETIME, STATS_DATE(s.object_id, s.stats_id)) AS last_statistics_update, - DATEDIFF(DAY, STATS_DATE(s.object_id, s.stats_id), GETDATE()) AS days_since_last_stats_update, - si.rowcnt, - si.rowmodctr, - CASE WHEN si.rowmodctr > 0 THEN CAST(si.rowmodctr / ( 1. * NULLIF(si.rowcnt, 0) ) * 100 AS DECIMAL(18, 1)) - ELSE si.rowmodctr - END AS percent_modifications, - CASE WHEN si.rowcnt < 500 THEN 500 - ELSE CAST(( si.rowcnt * .20 ) + 500 AS INT) - 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, - ' - + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' - THEN N's.has_filter, - s.filter_definition' - ELSE N'NULL AS has_filter, - NULL AS filter_definition' END - + N' - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.stats AS s - INNER HASH JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.sysindexes si - ON si.name = s.name AND s.object_id = si.id - INNER HASH JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects obj - ON s.object_id = obj.object_id - INNER HASH JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas sch - ON sch.schema_id = obj.schema_id - LEFT HASH JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.indexes AS i - ON i.object_id = s.object_id - AND i.index_id = s.stats_id - CROSS APPLY ( SELECT STUFF((SELECT '', '' + c.name - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.stats_columns AS sc - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns AS c - ON sc.column_id = c.column_id AND sc.object_id = c.object_id - WHERE sc.stats_id = s.stats_id AND sc.object_id = s.object_id - ORDER BY sc.stats_column_id - FOR XML PATH(''''), TYPE).value(''.'', ''nvarchar(max)''), 1, 2, '''') - ) ca (column_names) - WHERE obj.is_ms_shipped = 0 - AND si.rowcnt > 0 - OPTION (RECOMPILE);'; - - IF @dsql IS NULL - RAISERROR('@dsql is null',16,1); - - RAISERROR (N'Inserting data into #Statistics',0,1) WITH NOWAIT; - IF @Debug = 1 - BEGIN - PRINT SUBSTRING(@dsql, 0, 4000); - PRINT SUBSTRING(@dsql, 4000, 8000); - PRINT SUBSTRING(@dsql, 8000, 12000); - PRINT SUBSTRING(@dsql, 12000, 16000); - PRINT SUBSTRING(@dsql, 16000, 20000); - PRINT SUBSTRING(@dsql, 20000, 24000); - PRINT SUBSTRING(@dsql, 24000, 28000); - PRINT SUBSTRING(@dsql, 28000, 32000); - PRINT SUBSTRING(@dsql, 32000, 36000); - PRINT SUBSTRING(@dsql, 36000, 40000); - END; + DECLARE @TableExists BIT; + DECLARE @SchemaExists BIT; + SET @StringToExecute = + N'SET @SchemaExists = 0; + SET @TableExists = 0; + IF EXISTS(SELECT * FROM @@@OutputServerName@@@.@@@OutputDatabaseName@@@.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''@@@OutputSchemaName@@@'') + SET @SchemaExists = 1 + IF EXISTS (SELECT * FROM @@@OutputServerName@@@.@@@OutputDatabaseName@@@.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''@@@OutputSchemaName@@@'' AND QUOTENAME(TABLE_NAME) = ''@@@OutputTableName@@@'') + SET @TableExists = 1'; + + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputServerName@@@', @OutputServerName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputSchemaName@@@', @OutputSchemaName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); + + EXEC sp_executesql @StringToExecute, N'@TableExists BIT OUTPUT, @SchemaExists BIT OUTPUT', @TableExists OUTPUT, @SchemaExists OUTPUT; + + IF @SchemaExists = 1 + BEGIN + IF @TableExists = 0 + BEGIN + SET @StringToExecute = + N'CREATE TABLE @@@OutputDatabaseName@@@.@@@OutputSchemaName@@@.@@@OutputTableName@@@ + ( + [id] INT IDENTITY(1,1) NOT NULL, + [run_id] UNIQUEIDENTIFIER, + [run_datetime] DATETIME, + [server_name] NVARCHAR(128), + [database_name] NVARCHAR(128), + [schema_name] NVARCHAR(128), + [table_name] NVARCHAR(128), + [index_name] NVARCHAR(128), + [Drop_Tsql] NVARCHAR(MAX), + [Create_Tsql] NVARCHAR(MAX), + [index_id] INT, + [db_schema_object_indexid] NVARCHAR(500), + [object_type] NVARCHAR(15), + [index_definition] NVARCHAR(MAX), + [key_column_names_with_sort_order] NVARCHAR(MAX), + [count_key_columns] INT, + [include_column_names] NVARCHAR(MAX), + [count_included_columns] INT, + [secret_columns] NVARCHAR(MAX), + [count_secret_columns] INT, + [partition_key_column_name] NVARCHAR(MAX), + [filter_definition] NVARCHAR(MAX), + [is_indexed_view] BIT, + [is_primary_key] BIT, + [is_XML] BIT, + [is_spatial] BIT, + [is_NC_columnstore] BIT, + [is_CX_columnstore] BIT, + [is_in_memory_oltp] BIT, + [is_disabled] BIT, + [is_hypothetical] BIT, + [is_padded] BIT, + [fill_factor] INT, + [is_referenced_by_foreign_key] BIT, + [last_user_seek] DATETIME, + [last_user_scan] DATETIME, + [last_user_lookup] DATETIME, + [last_user_update] DATETIME, + [total_reads] BIGINT, + [user_updates] BIGINT, + [reads_per_write] MONEY, + [index_usage_summary] NVARCHAR(200), + [total_singleton_lookup_count] BIGINT, + [total_range_scan_count] BIGINT, + [total_leaf_delete_count] BIGINT, + [total_leaf_update_count] BIGINT, + [index_op_stats] NVARCHAR(200), + [partition_count] INT, + [total_rows] BIGINT, + [total_reserved_MB] NUMERIC(29,2), + [total_reserved_LOB_MB] NUMERIC(29,2), + [total_reserved_row_overflow_MB] NUMERIC(29,2), + [index_size_summary] NVARCHAR(300), + [total_row_lock_count] BIGINT, + [total_row_lock_wait_count] BIGINT, + [total_row_lock_wait_in_ms] BIGINT, + [avg_row_lock_wait_in_ms] BIGINT, + [total_page_lock_count] BIGINT, + [total_page_lock_wait_count] BIGINT, + [total_page_lock_wait_in_ms] BIGINT, + [avg_page_lock_wait_in_ms] BIGINT, + [total_index_lock_promotion_attempt_count] BIGINT, + [total_index_lock_promotion_count] BIGINT, + [data_compression_desc] NVARCHAR(4000), + [page_latch_wait_count] BIGINT, + [page_latch_wait_in_ms] BIGINT, + [page_io_latch_wait_count] BIGINT, + [page_io_latch_wait_in_ms] BIGINT, + [create_date] DATETIME, + [modify_date] DATETIME, + [more_info] NVARCHAR(500), + [display_order] INT, + CONSTRAINT [PK_ID_@@@RunID@@@] PRIMARY KEY CLUSTERED ([id] ASC) + );'; + + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputSchemaName@@@', @OutputSchemaName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@RunID@@@', @RunID); + + IF @ValidOutputServer = 1 + BEGIN + SET @StringToExecute = REPLACE(@StringToExecute,'''',''''''); + EXEC('EXEC('''+@StringToExecute+''') AT ' + @OutputServerName); + END; + ELSE + BEGIN + EXEC(@StringToExecute); + END; + END; /* @TableExists = 0 */ + + SET @StringToExecute = + N'IF EXISTS(SELECT * FROM @@@OutputServerName@@@.@@@OutputDatabaseName@@@.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''@@@OutputSchemaName@@@'') + AND NOT EXISTS (SELECT * FROM @@@OutputServerName@@@.@@@OutputDatabaseName@@@.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''@@@OutputSchemaName@@@'' AND QUOTENAME(TABLE_NAME) = ''@@@OutputTableName@@@'') + SET @TableExists = 0 + ELSE + SET @TableExists = 1'; + + SET @TableExists = NULL; + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputServerName@@@', @OutputServerName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputSchemaName@@@', @OutputSchemaName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); - EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; - END; - - END; - - IF (PARSENAME(@SQLServerProductVersion, 4) >= 10) - BEGIN - RAISERROR (N'Gathering Computed Column Info.',0,1) WITH NOWAIT; - SET @dsql=N'SELECT DB_ID(@i_DatabaseName) AS [database_id], - @i_DatabaseName AS database_name, - t.name AS table_name, - s.name AS schema_name, - c.name AS column_name, - cc.is_nullable, - cc.definition, - cc.uses_database_collation, - cc.is_persisted, - cc.is_computed, - CASE WHEN cc.definition LIKE ''%|].|[%'' ESCAPE ''|'' THEN 1 ELSE 0 END AS is_function, - ''ALTER TABLE '' + QUOTENAME(s.name) + ''.'' + QUOTENAME(t.name) + - '' ADD '' + QUOTENAME(c.name) + '' AS '' + cc.definition + - CASE WHEN is_persisted = 1 THEN '' PERSISTED'' ELSE '''' END + '';'' COLLATE DATABASE_DEFAULT AS [column_definition] - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.computed_columns AS cc - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns AS c - ON cc.object_id = c.object_id - AND cc.column_id = c.column_id - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.tables AS t - ON t.object_id = cc.object_id - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s - ON s.schema_id = t.schema_id - OPTION (RECOMPILE);'; - - IF @dsql IS NULL RAISERROR('@dsql is null',16,1); - - INSERT #ComputedColumns - ( database_id, [database_name], table_name, schema_name, column_name, is_nullable, definition, - uses_database_collation, is_persisted, is_computed, is_function, column_definition ) - EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; - - END; - - RAISERROR (N'Gathering Trace Flag Information',0,1) WITH NOWAIT; - INSERT #TraceStatus - EXEC ('DBCC TRACESTATUS(-1) WITH NO_INFOMSGS'); + EXEC sp_executesql @StringToExecute, N'@TableExists BIT OUTPUT', @TableExists OUTPUT; + + IF @TableExists = 1 + BEGIN + SET @StringToExecute = + N'INSERT @@@OutputServerName@@@.@@@OutputDatabaseName@@@.@@@OutputSchemaName@@@.@@@OutputTableName@@@ + ( + [run_id], + [run_datetime], + [server_name], + [database_name], + [schema_name], + [table_name], + [index_name], + [Drop_Tsql], + [Create_Tsql], + [index_id], + [db_schema_object_indexid], + [object_type], + [index_definition], + [key_column_names_with_sort_order], + [count_key_columns], + [include_column_names], + [count_included_columns], + [secret_columns], + [count_secret_columns], + [partition_key_column_name], + [filter_definition], + [is_indexed_view], + [is_primary_key], + [is_XML], + [is_spatial], + [is_NC_columnstore], + [is_CX_columnstore], + [is_in_memory_oltp], + [is_disabled], + [is_hypothetical], + [is_padded], + [fill_factor], + [is_referenced_by_foreign_key], + [last_user_seek], + [last_user_scan], + [last_user_lookup], + [last_user_update], + [total_reads], + [user_updates], + [reads_per_write], + [index_usage_summary], + [total_singleton_lookup_count], + [total_range_scan_count], + [total_leaf_delete_count], + [total_leaf_update_count], + [index_op_stats], + [partition_count], + [total_rows], + [total_reserved_MB], + [total_reserved_LOB_MB], + [total_reserved_row_overflow_MB], + [index_size_summary], + [total_row_lock_count], + [total_row_lock_wait_count], + [total_row_lock_wait_in_ms], + [avg_row_lock_wait_in_ms], + [total_page_lock_count], + [total_page_lock_wait_count], + [total_page_lock_wait_in_ms], + [avg_page_lock_wait_in_ms], + [total_index_lock_promotion_attempt_count], + [total_index_lock_promotion_count], + [data_compression_desc], + [page_latch_wait_count], + [page_latch_wait_in_ms], + [page_io_latch_wait_count], + [page_io_latch_wait_in_ms], + [create_date], + [modify_date], + [more_info], + [display_order] + ) + SELECT ''@@@RunID@@@'', + ''@@@GETDATE@@@'', + ''@@@LocalServerName@@@'', + -- Below should be a copy/paste of the real query + -- Make sure all quotes are escaped + i.[database_name] AS [Database Name], + i.[schema_name] AS [Schema Name], + i.[object_name] AS [Object Name], + ISNULL(i.index_name, '''') AS [Index Name], + CASE + WHEN i.is_primary_key = 1 AND i.index_definition <> ''[HEAP]'' + THEN N''-ALTER TABLE '' + QUOTENAME(i.[database_name]) + N''.'' + QUOTENAME(i.[schema_name]) + N''.'' + QUOTENAME(i.[object_name]) + + N'' DROP CONSTRAINT '' + QUOTENAME(i.index_name) + N'';'' + WHEN i.is_primary_key = 0 AND i.index_definition <> ''[HEAP]'' + THEN N''--DROP INDEX ''+ QUOTENAME(i.index_name) + N'' ON '' + QUOTENAME(i.[database_name]) + N''.'' + + QUOTENAME(i.[schema_name]) + N''.'' + QUOTENAME(i.[object_name]) + N'';'' + ELSE N'''' + END AS [Drop TSQL], + CASE + WHEN i.index_definition = ''[HEAP]'' THEN N'''' + ELSE N''--'' + ict.create_tsql END AS [Create TSQL], + CAST(i.index_id AS NVARCHAR(10))AS [Index ID], + db_schema_object_indexid AS [Details: schema.table.index(indexid)], + CASE WHEN index_id IN ( 1, 0 ) THEN ''TABLE'' + ELSE ''NonClustered'' + END AS [Object Type], + LEFT(index_definition,4000) AS [Definition: [Property]] ColumnName {datatype maxbytes}], + ISNULL(LTRIM(key_column_names_with_sort_order), '''') AS [Key Column Names With Sort], + ISNULL(count_key_columns, 0) AS [Count Key Columns], + ISNULL(include_column_names, '''') AS [Include Column Names], + ISNULL(count_included_columns,0) AS [Count Included Columns], + ISNULL(secret_columns,'''') AS [Secret Column Names], + ISNULL(count_secret_columns,0) AS [Count Secret Columns], + ISNULL(partition_key_column_name, '''') AS [Partition Key Column Name], + ISNULL(filter_definition, '''') AS [Filter Definition], + is_indexed_view AS [Is Indexed View], + is_primary_key AS [Is Primary Key], + is_XML AS [Is XML], + is_spatial AS [Is Spatial], + is_NC_columnstore AS [Is NC Columnstore], + is_CX_columnstore AS [Is CX Columnstore], + is_in_memory_oltp AS [Is In-Memory OLTP], + is_disabled AS [Is Disabled], + is_hypothetical AS [Is Hypothetical], + is_padded AS [Is Padded], + fill_factor AS [Fill Factor], + is_referenced_by_foreign_key AS [Is Reference by Foreign Key], + last_user_seek AS [Last User Seek], + last_user_scan AS [Last User Scan], + last_user_lookup AS [Last User Lookup], + last_user_update AS [Last User Update], + total_reads AS [Total Reads], + user_updates AS [User Updates], + reads_per_write AS [Reads Per Write], + index_usage_summary AS [Index Usage], + sz.total_singleton_lookup_count AS [Singleton Lookups], + sz.total_range_scan_count AS [Range Scans], + sz.total_leaf_delete_count AS [Leaf Deletes], + sz.total_leaf_update_count AS [Leaf Updates], + sz.index_op_stats AS [Index Op Stats], + sz.partition_count AS [Partition Count], + sz.total_rows AS [Rows], + sz.total_reserved_MB AS [Reserved MB], + sz.total_reserved_LOB_MB AS [Reserved LOB MB], + sz.total_reserved_row_overflow_MB AS [Reserved Row Overflow MB], + sz.index_size_summary AS [Index Size], + sz.total_row_lock_count AS [Row Lock Count], + sz.total_row_lock_wait_count AS [Row Lock Wait Count], + sz.total_row_lock_wait_in_ms AS [Row Lock Wait ms], + sz.avg_row_lock_wait_in_ms AS [Avg Row Lock Wait ms], + sz.total_page_lock_count AS [Page Lock Count], + sz.total_page_lock_wait_count AS [Page Lock Wait Count], + sz.total_page_lock_wait_in_ms AS [Page Lock Wait ms], + sz.avg_page_lock_wait_in_ms AS [Avg Page Lock Wait ms], + sz.total_index_lock_promotion_attempt_count AS [Lock Escalation Attempts], + sz.total_index_lock_promotion_count AS [Lock Escalations], + sz.data_compression_desc AS [Data Compression], + sz.page_latch_wait_count, + sz.page_latch_wait_in_ms, + sz.page_io_latch_wait_count, + sz.page_io_latch_wait_in_ms, + i.create_date AS [Create Date], + i.modify_date AS [Modify Date], + more_info AS [More Info], + 1 AS [Display Order] + FROM #IndexSanity AS i + LEFT JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id + LEFT JOIN #IndexCreateTsql AS ict ON i.index_sanity_id = ict.index_sanity_id + ORDER BY [Database Name], [Schema Name], [Object Name], [Index ID] + OPTION (RECOMPILE);'; + + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputServerName@@@', @OutputServerName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputSchemaName@@@', @OutputSchemaName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@RunID@@@', @RunID); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@GETDATE@@@', GETDATE()); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@LocalServerName@@@', CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128))); + EXEC(@StringToExecute); + END; /* @TableExists = 1 */ + ELSE + RAISERROR('Creation of the output table failed.', 16, 0); + END; /* @TableExists = 0 */ + ELSE + RAISERROR (N'Invalid schema name, data could not be saved.', 16, 0); + END; /* @ValidOutputLocation = 1 */ + ELSE + + IF(@OutputType <> 'NONE') + BEGIN + SELECT i.[database_name] AS [Database Name], + i.[schema_name] AS [Schema Name], + i.[object_name] AS [Object Name], + ISNULL(i.index_name, '') AS [Index Name], + CAST(i.index_id AS NVARCHAR(10))AS [Index ID], + db_schema_object_indexid AS [Details: schema.table.index(indexid)], + CASE WHEN index_id IN ( 1, 0 ) THEN 'TABLE' + ELSE 'NonClustered' + END AS [Object Type], + index_definition AS [Definition: [Property]] ColumnName {datatype maxbytes}], + ISNULL(LTRIM(key_column_names_with_sort_order), '') AS [Key Column Names With Sort], + ISNULL(count_key_columns, 0) AS [Count Key Columns], + ISNULL(include_column_names, '') AS [Include Column Names], + ISNULL(count_included_columns,0) AS [Count Included Columns], + ISNULL(secret_columns,'') AS [Secret Column Names], + ISNULL(count_secret_columns,0) AS [Count Secret Columns], + ISNULL(partition_key_column_name, '') AS [Partition Key Column Name], + ISNULL(filter_definition, '') AS [Filter Definition], + is_indexed_view AS [Is Indexed View], + is_primary_key AS [Is Primary Key], + is_XML AS [Is XML], + is_spatial AS [Is Spatial], + is_NC_columnstore AS [Is NC Columnstore], + is_CX_columnstore AS [Is CX Columnstore], + is_in_memory_oltp AS [Is In-Memory OLTP], + is_disabled AS [Is Disabled], + is_hypothetical AS [Is Hypothetical], + is_padded AS [Is Padded], + fill_factor AS [Fill Factor], + is_referenced_by_foreign_key AS [Is Reference by Foreign Key], + last_user_seek AS [Last User Seek], + last_user_scan AS [Last User Scan], + last_user_lookup AS [Last User Lookup], + last_user_update AS [Last User Update], + total_reads AS [Total Reads], + user_updates AS [User Updates], + reads_per_write AS [Reads Per Write], + index_usage_summary AS [Index Usage], + sz.total_singleton_lookup_count AS [Singleton Lookups], + sz.total_range_scan_count AS [Range Scans], + sz.total_leaf_delete_count AS [Leaf Deletes], + sz.total_leaf_update_count AS [Leaf Updates], + sz.index_op_stats AS [Index Op Stats], + sz.partition_count AS [Partition Count], + sz.total_rows AS [Rows], + sz.total_reserved_MB AS [Reserved MB], + sz.total_reserved_LOB_MB AS [Reserved LOB MB], + sz.total_reserved_row_overflow_MB AS [Reserved Row Overflow MB], + sz.index_size_summary AS [Index Size], + sz.total_row_lock_count AS [Row Lock Count], + sz.total_row_lock_wait_count AS [Row Lock Wait Count], + sz.total_row_lock_wait_in_ms AS [Row Lock Wait ms], + sz.avg_row_lock_wait_in_ms AS [Avg Row Lock Wait ms], + sz.total_page_lock_count AS [Page Lock Count], + sz.total_page_lock_wait_count AS [Page Lock Wait Count], + sz.total_page_lock_wait_in_ms AS [Page Lock Wait ms], + sz.avg_page_lock_wait_in_ms AS [Avg Page Lock Wait ms], + sz.total_index_lock_promotion_attempt_count AS [Lock Escalation Attempts], + sz.total_index_lock_promotion_count AS [Lock Escalations], + sz.page_latch_wait_count AS [Page Latch Wait Count], + sz.page_latch_wait_in_ms AS [Page Latch Wait ms], + sz.page_io_latch_wait_count AS [Page IO Latch Wait Count], + sz.page_io_latch_wait_in_ms as [Page IO Latch Wait ms], + sz.total_forwarded_fetch_count AS [Forwarded Fetches], + sz.data_compression_desc AS [Data Compression], + i.create_date AS [Create Date], + i.modify_date AS [Modify Date], + more_info AS [More Info], + CASE + WHEN i.is_primary_key = 1 AND i.index_definition <> '[HEAP]' + THEN N'--ALTER TABLE ' + QUOTENAME(i.[database_name]) + N'.' + QUOTENAME(i.[schema_name]) + N'.' + QUOTENAME(i.[object_name]) + + N' DROP CONSTRAINT ' + QUOTENAME(i.index_name) + N';' + WHEN i.is_primary_key = 0 AND i.index_definition <> '[HEAP]' + THEN N'--DROP INDEX '+ QUOTENAME(i.index_name) + N' ON ' + QUOTENAME(i.[database_name]) + N'.' + + QUOTENAME(i.[schema_name]) + N'.' + QUOTENAME(i.[object_name]) + N';' + ELSE N'' + END AS [Drop TSQL], + CASE + WHEN i.index_definition = '[HEAP]' THEN N'' + ELSE N'--' + ict.create_tsql END AS [Create TSQL], + 1 AS [Display Order] + FROM #IndexSanity AS i --left join here so we don't lose disabled nc indexes + LEFT JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id + LEFT JOIN #IndexCreateTsql AS ict ON i.index_sanity_id = ict.index_sanity_id + ORDER BY CASE WHEN @SortDirection = 'desc' THEN + CASE WHEN @SortOrder = N'rows' THEN sz.total_rows + WHEN @SortOrder = N'reserved_mb' THEN sz.total_reserved_MB + WHEN @SortOrder = N'size' THEN sz.total_reserved_MB + WHEN @SortOrder = N'reserved_lob_mb' THEN sz.total_reserved_LOB_MB + WHEN @SortOrder = N'lob' THEN sz.total_reserved_LOB_MB + WHEN @SortOrder = N'total_row_lock_wait_in_ms' THEN COALESCE(sz.total_row_lock_wait_in_ms,0) + WHEN @SortOrder = N'total_page_lock_wait_in_ms' THEN COALESCE(sz.total_page_lock_wait_in_ms,0) + WHEN @SortOrder = N'lock_time' THEN (COALESCE(sz.total_row_lock_wait_in_ms,0) + COALESCE(sz.total_page_lock_wait_in_ms,0)) + WHEN @SortOrder = N'total_reads' THEN total_reads + WHEN @SortOrder = N'reads' THEN total_reads + WHEN @SortOrder = N'user_updates' THEN user_updates + WHEN @SortOrder = N'writes' THEN user_updates + WHEN @SortOrder = N'reads_per_write' THEN reads_per_write + WHEN @SortOrder = N'ratio' THEN reads_per_write + WHEN @SortOrder = N'forward_fetches' THEN sz.total_forwarded_fetch_count + WHEN @SortOrder = N'fetches' THEN sz.total_forwarded_fetch_count + ELSE NULL END + ELSE 1 END + DESC, /* Shout out to DHutmacher */ + CASE WHEN @SortDirection = 'asc' THEN + CASE WHEN @SortOrder = N'rows' THEN sz.total_rows + WHEN @SortOrder = N'reserved_mb' THEN sz.total_reserved_MB + WHEN @SortOrder = N'size' THEN sz.total_reserved_MB + WHEN @SortOrder = N'reserved_lob_mb' THEN sz.total_reserved_LOB_MB + WHEN @SortOrder = N'lob' THEN sz.total_reserved_LOB_MB + WHEN @SortOrder = N'total_row_lock_wait_in_ms' THEN COALESCE(sz.total_row_lock_wait_in_ms,0) + WHEN @SortOrder = N'total_page_lock_wait_in_ms' THEN COALESCE(sz.total_page_lock_wait_in_ms,0) + WHEN @SortOrder = N'lock_time' THEN (COALESCE(sz.total_row_lock_wait_in_ms,0) + COALESCE(sz.total_page_lock_wait_in_ms,0)) + WHEN @SortOrder = N'total_reads' THEN total_reads + WHEN @SortOrder = N'reads' THEN total_reads + WHEN @SortOrder = N'user_updates' THEN user_updates + WHEN @SortOrder = N'writes' THEN user_updates + WHEN @SortOrder = N'reads_per_write' THEN reads_per_write + WHEN @SortOrder = N'ratio' THEN reads_per_write + WHEN @SortOrder = N'forward_fetches' THEN sz.total_forwarded_fetch_count + WHEN @SortOrder = N'fetches' THEN sz.total_forwarded_fetch_count + ELSE NULL END + ELSE 1 END + ASC, + i.[database_name], [Schema Name], [Object Name], [Index ID] + OPTION (RECOMPILE); + END; - IF (PARSENAME(@SQLServerProductVersion, 4) >= 13) - BEGIN - RAISERROR (N'Gathering Temporal Table Info',0,1) WITH NOWAIT; - SET @dsql=N'SELECT ' + QUOTENAME(@DatabaseName,'''') + N' AS database_name, - DB_ID(N' + QUOTENAME(@DatabaseName,'''') + N') AS [database_id], - s.name AS schema_name, - t.name AS table_name, - oa.hsn as history_schema_name, - oa.htn AS history_table_name, - c1.name AS start_column_name, - c2.name AS end_column_name, - p.name AS period_name - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.periods AS p - INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.tables AS t - ON p.object_id = t.object_id - INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns AS c1 - ON t.object_id = c1.object_id - AND p.start_column_id = c1.column_id - INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns AS c2 - ON t.object_id = c2.object_id - AND p.end_column_id = c2.column_id - INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s - ON t.schema_id = s.schema_id - CROSS APPLY ( SELECT s2.name as hsn, t2.name htn - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.tables AS t2 - INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s2 - ON t2.schema_id = s2.schema_id - WHERE t2.object_id = t.history_table_id - AND t2.temporal_type = 1 /*History table*/ ) AS oa - WHERE t.temporal_type IN ( 2, 4 ) /*BOL currently points to these types, but has no definition for 4*/ - OPTION (RECOMPILE); - '; - - IF @dsql IS NULL - RAISERROR('@dsql is null',16,1); - - INSERT #TemporalTables ( database_name, database_id, schema_name, table_name, history_schema_name, - history_table_name, start_column_name, end_column_name, period_name ) - - EXEC sp_executesql @dsql; + END; /* End @Mode=2 (index detail)*/ + ELSE IF (@Mode=3) /*Missing index Detail*/ + BEGIN + IF(@OutputType <> 'NONE') + BEGIN; + WITH create_date AS ( + SELECT i.database_id, + i.schema_name, + i.[object_id], + ISNULL(NULLIF(MAX(DATEDIFF(DAY, i.create_date, SYSDATETIME())), 0), 1) AS create_days + FROM #IndexSanity AS i + GROUP BY i.database_id, i.schema_name, i.object_id + ) + SELECT + mi.database_name AS [Database Name], + mi.[schema_name] AS [Schema], + mi.table_name AS [Table], + CAST((mi.magic_benefit_number / CASE WHEN cd.create_days < @DaysUptime THEN cd.create_days ELSE @DaysUptime END) AS BIGINT) + AS [Magic Benefit Number], + mi.missing_index_details AS [Missing Index Details], + mi.avg_total_user_cost AS [Avg Query Cost], + mi.avg_user_impact AS [Est Index Improvement], + mi.user_seeks AS [Seeks], + mi.user_scans AS [Scans], + mi.unique_compiles AS [Compiles], + mi.equality_columns_with_data_type AS [Equality Columns], + mi.inequality_columns_with_data_type AS [Inequality Columns], + mi.included_columns_with_data_type AS [Included Columns], + mi.index_estimated_impact AS [Estimated Impact], + mi.create_tsql AS [Create TSQL], + mi.more_info AS [More Info], + 1 AS [Display Order], + mi.is_low, + mi.sample_query_plan AS [Sample Query Plan] + FROM #MissingIndexes AS mi + LEFT JOIN create_date AS cd + ON mi.[object_id] = cd.object_id + AND mi.database_id = cd.database_id + AND mi.schema_name = cd.schema_name + /* Minimum benefit threshold = 100k/day of uptime OR since table creation date, whichever is lower*/ + WHERE @ShowAllMissingIndexRequests=1 + OR (mi.magic_benefit_number / CASE WHEN cd.create_days < @DaysUptime THEN cd.create_days ELSE @DaysUptime END) >= 100000 + UNION ALL + SELECT + @ScriptVersionName, + N'From Your Community Volunteers' , + N'http://FirstResponderKit.org' , + 100000000000, + @DaysUptimeInsertValue, + NULL,NULL,NULL,NULL,NULL,NULL,NULL, + NULL, NULL, NULL, NULL, 0 AS [Display Order], NULL AS is_low, NULL + ORDER BY [Display Order] ASC, [Magic Benefit Number] DESC + OPTION (RECOMPILE); + END; - SET @dsql=N'SELECT DB_ID(@i_DatabaseName) AS [database_id], - @i_DatabaseName AS database_name, - t.name AS table_name, - s.name AS schema_name, - cc.name AS constraint_name, - cc.is_disabled, - cc.definition, - cc.uses_database_collation, - cc.is_not_trusted, - CASE WHEN cc.definition LIKE ''%|].|[%'' ESCAPE ''|'' THEN 1 ELSE 0 END AS is_function, - ''ALTER TABLE '' + QUOTENAME(s.name) + ''.'' + QUOTENAME(t.name) + - '' ADD CONSTRAINT '' + QUOTENAME(cc.name) + '' CHECK '' + cc.definition + '';'' COLLATE DATABASE_DEFAULT AS [column_definition] - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.check_constraints AS cc - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.tables AS t - ON t.object_id = cc.parent_object_id - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s - ON s.schema_id = t.schema_id - OPTION (RECOMPILE);'; - - INSERT #CheckConstraints - ( database_id, [database_name], table_name, schema_name, constraint_name, is_disabled, definition, - uses_database_collation, is_not_trusted, is_function, column_definition ) - EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; + IF (@BringThePain = 1 + AND @DatabaseName IS NOT NULL + AND @GetAllDatabases = 0) + BEGIN - SET @dsql=N'SELECT DB_ID(@i_DatabaseName) AS [database_id], - @i_DatabaseName AS database_name, - s.name AS missing_schema_name, - t.name AS missing_table_name, - i.name AS missing_index_name, - c.name AS missing_column_name - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.sql_expression_dependencies AS sed - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.tables AS t - ON t.object_id = sed.referenced_id - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s - ON t.schema_id = s.schema_id - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.indexes AS i - ON i.object_id = sed.referenced_id - AND i.index_id = sed.referencing_minor_id - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns AS c - ON c.object_id = sed.referenced_id - AND c.column_id = sed.referenced_minor_id - WHERE sed.referencing_class = 7 - AND sed.referenced_class = 1 - AND i.has_filter = 1 - AND NOT EXISTS ( SELECT 1/0 - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.index_columns AS ic - WHERE ic.index_id = sed.referencing_minor_id - AND ic.column_id = sed.referenced_minor_id - AND ic.object_id = sed.referenced_id ) - OPTION(RECOMPILE);' + EXEC sp_BlitzCache @SortOrder = 'sp_BlitzIndex', @DatabaseName = @DatabaseName, @BringThePain = 1, @QueryFilter = 'statement', @HideSummary = 1; + + END; - INSERT #FilteredIndexes ( database_id, database_name, schema_name, table_name, index_name, column_name ) - EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; + END; /* End @Mode=3 (index detail)*/ - END; - -END; END TRY + BEGIN CATCH - RAISERROR (N'Failure populating temp tables.', 0,1) WITH NOWAIT; + RAISERROR (N'Failure analyzing temp tables.', 0,1) WITH NOWAIT; - IF @dsql IS NOT NULL - BEGIN - SET @msg= 'Last @dsql: ' + @dsql; - RAISERROR(@msg, 0, 1) WITH NOWAIT; - END; + SELECT @msg = ERROR_MESSAGE(), @ErrorSeverity = ERROR_SEVERITY(), @ErrorState = ERROR_STATE(); - SELECT @msg = @DatabaseName + N' database failed to process. ' + ERROR_MESSAGE(), @ErrorSeverity = ERROR_SEVERITY(), @ErrorState = ERROR_STATE(); - RAISERROR (@msg,@ErrorSeverity, @ErrorState )WITH NOWAIT; - + RAISERROR (@msg, + @ErrorSeverity, + @ErrorState + ); WHILE @@trancount > 0 ROLLBACK; RETURN; -END CATCH; - FETCH NEXT FROM c1 INTO @DatabaseName; -END; -DEALLOCATE c1; - - - - + END CATCH; +GO +IF OBJECT_ID('dbo.sp_BlitzLock') IS NULL + EXEC ('CREATE PROCEDURE dbo.sp_BlitzLock AS RETURN 0;'); +GO +ALTER PROCEDURE dbo.sp_BlitzLock +( + @Top INT = 2147483647, + @DatabaseName NVARCHAR(256) = NULL, + @StartDate DATETIME = '19000101', + @EndDate DATETIME = '99991231', + @ObjectName NVARCHAR(1000) = NULL, + @StoredProcName NVARCHAR(1000) = NULL, + @AppName NVARCHAR(256) = NULL, + @HostName NVARCHAR(256) = NULL, + @LoginName NVARCHAR(256) = NULL, + @EventSessionPath VARCHAR(256) = 'system_health*.xel', + @VictimsOnly BIT = 0, + @Debug BIT = 0, + @Help BIT = 0, + @Version VARCHAR(30) = NULL OUTPUT, + @VersionDate DATETIME = NULL OUTPUT, + @VersionCheckMode BIT = 0, + @OutputDatabaseName NVARCHAR(256) = NULL , + @OutputSchemaName NVARCHAR(256) = 'dbo' , --ditto as below + @OutputTableName NVARCHAR(256) = 'BlitzLock', --put a standard here no need to check later in the script + @ExportToExcel BIT = 0 +) +WITH RECOMPILE +AS +BEGIN ----------------------------------------- ---STEP 2: PREP THE TEMP TABLES ---EVERY QUERY AFTER THIS GOES AGAINST TEMP TABLES ONLY. ----------------------------------------- +SET NOCOUNT ON; +SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -RAISERROR (N'Updating #IndexSanity.key_column_names',0,1) WITH NOWAIT; -UPDATE #IndexSanity -SET key_column_names = D1.key_column_names -FROM #IndexSanity si - CROSS APPLY ( SELECT RTRIM(STUFF( (SELECT N', ' + c.column_name - + N' {' + system_type_name + N' ' + - CASE max_length WHEN -1 THEN N'(max)' ELSE - CASE - WHEN system_type_name IN (N'char',N'varchar',N'binary',N'varbinary') THEN N'(' + CAST(max_length AS NVARCHAR(20)) + N')' - WHEN system_type_name IN (N'nchar',N'nvarchar') THEN N'(' + CAST(max_length/2 AS NVARCHAR(20)) + N')' - ELSE '' - END - END - + N'}' - AS col_definition - FROM #IndexColumns c - WHERE c.database_id= si.database_id - AND c.schema_name = si.schema_name - AND c.object_id = si.object_id - AND c.index_id = si.index_id - AND c.is_included_column = 0 /*Just Keys*/ - AND c.key_ordinal > 0 /*Ignore non-key columns, such as partitioning keys*/ - ORDER BY c.object_id, c.index_id, c.key_ordinal - FOR XML PATH('') ,TYPE).value('.', 'nvarchar(max)'), 1, 1, '')) - ) D1 ( key_column_names ); +SELECT @Version = '8.01', @VersionDate = '20210222'; -RAISERROR (N'Updating #IndexSanity.partition_key_column_name',0,1) WITH NOWAIT; -UPDATE #IndexSanity -SET partition_key_column_name = D1.partition_key_column_name -FROM #IndexSanity si - CROSS APPLY ( SELECT RTRIM(STUFF( (SELECT N', ' + c.column_name AS col_definition - FROM #IndexColumns c - WHERE c.database_id= si.database_id - AND c.schema_name = si.schema_name - AND c.object_id = si.object_id - AND c.index_id = si.index_id - AND c.partition_ordinal <> 0 /*Just Partitioned Keys*/ - ORDER BY c.object_id, c.index_id, c.key_ordinal - FOR XML PATH('') , TYPE).value('.', 'nvarchar(max)'), 1, 1,''))) D1 - ( partition_key_column_name ); -RAISERROR (N'Updating #IndexSanity.key_column_names_with_sort_order',0,1) WITH NOWAIT; -UPDATE #IndexSanity -SET key_column_names_with_sort_order = D2.key_column_names_with_sort_order -FROM #IndexSanity si - CROSS APPLY ( SELECT RTRIM(STUFF( (SELECT N', ' + c.column_name + CASE c.is_descending_key - WHEN 1 THEN N' DESC' - ELSE N'' - END - + N' {' + system_type_name + N' ' + - CASE max_length WHEN -1 THEN N'(max)' ELSE - CASE - WHEN system_type_name IN (N'char',N'varchar',N'binary',N'varbinary') THEN N'(' + CAST(max_length AS NVARCHAR(20)) + N')' - WHEN system_type_name IN (N'nchar',N'nvarchar') THEN N'(' + CAST(max_length/2 AS NVARCHAR(20)) + N')' - ELSE '' - END - END - + N'}' - AS col_definition - FROM #IndexColumns c - WHERE c.database_id= si.database_id - AND c.schema_name = si.schema_name - AND c.object_id = si.object_id - AND c.index_id = si.index_id - AND c.is_included_column = 0 /*Just Keys*/ - AND c.key_ordinal > 0 /*Ignore non-key columns, such as partitioning keys*/ - ORDER BY c.object_id, c.index_id, c.key_ordinal - FOR XML PATH('') , TYPE).value('.', 'nvarchar(max)'), 1, 1, '')) - ) D2 ( key_column_names_with_sort_order ); +IF(@VersionCheckMode = 1) +BEGIN + RETURN; +END; + IF @Help = 1 + BEGIN + PRINT ' + /* + sp_BlitzLock from http://FirstResponderKit.org + + This script checks for and analyzes deadlocks from the system health session or a custom extended event path -RAISERROR (N'Updating #IndexSanity.key_column_names_with_sort_order_no_types (for create tsql)',0,1) WITH NOWAIT; -UPDATE #IndexSanity -SET key_column_names_with_sort_order_no_types = D2.key_column_names_with_sort_order_no_types -FROM #IndexSanity si - CROSS APPLY ( SELECT RTRIM(STUFF( (SELECT N', ' + QUOTENAME(c.column_name) + CASE c.is_descending_key - WHEN 1 THEN N' DESC' - ELSE N'' - END AS col_definition - FROM #IndexColumns c - WHERE c.database_id= si.database_id - AND c.schema_name = si.schema_name - AND c.object_id = si.object_id - AND c.index_id = si.index_id - AND c.is_included_column = 0 /*Just Keys*/ - AND c.key_ordinal > 0 /*Ignore non-key columns, such as partitioning keys*/ - ORDER BY c.object_id, c.index_id, c.key_ordinal - FOR XML PATH('') , TYPE).value('.', 'nvarchar(max)'), 1, 1, '')) - ) D2 ( key_column_names_with_sort_order_no_types ); + Variables you can use: + @Top: Use if you want to limit the number of deadlocks to return. + This is ordered by event date ascending -RAISERROR (N'Updating #IndexSanity.include_column_names',0,1) WITH NOWAIT; -UPDATE #IndexSanity -SET include_column_names = D3.include_column_names -FROM #IndexSanity si - CROSS APPLY ( SELECT RTRIM(STUFF( (SELECT N', ' + c.column_name - + N' {' + system_type_name + N' ' + CAST(max_length AS NVARCHAR(50)) + N'}' - FROM #IndexColumns c - WHERE c.database_id= si.database_id - AND c.schema_name = si.schema_name - AND c.object_id = si.object_id - AND c.index_id = si.index_id - AND c.is_included_column = 1 /*Just includes*/ - ORDER BY c.column_name /*Order doesn't matter in includes, - this is here to make rows easy to compare.*/ - FOR XML PATH('') , TYPE).value('.', 'nvarchar(max)'), 1, 1, '')) - ) D3 ( include_column_names ); + @DatabaseName: If you want to filter to a specific database -RAISERROR (N'Updating #IndexSanity.include_column_names_no_types (for create tsql)',0,1) WITH NOWAIT; -UPDATE #IndexSanity -SET include_column_names_no_types = D3.include_column_names_no_types -FROM #IndexSanity si - CROSS APPLY ( SELECT RTRIM(STUFF( (SELECT N', ' + QUOTENAME(c.column_name) - FROM #IndexColumns c - WHERE c.database_id= si.database_id - AND c.schema_name = si.schema_name - AND c.object_id = si.object_id - AND c.index_id = si.index_id - AND c.is_included_column = 1 /*Just includes*/ - ORDER BY c.column_name /*Order doesn't matter in includes, - this is here to make rows easy to compare.*/ - FOR XML PATH('') , TYPE).value('.', 'nvarchar(max)'), 1, 1, '')) - ) D3 ( include_column_names_no_types ); + @StartDate: The date you want to start searching on. -RAISERROR (N'Updating #IndexSanity.count_key_columns and count_include_columns',0,1) WITH NOWAIT; -UPDATE #IndexSanity -SET count_included_columns = D4.count_included_columns, - count_key_columns = D4.count_key_columns -FROM #IndexSanity si - CROSS APPLY ( SELECT SUM(CASE WHEN is_included_column = 'true' THEN 1 - ELSE 0 - END) AS count_included_columns, - SUM(CASE WHEN is_included_column = 'false' AND c.key_ordinal > 0 THEN 1 - ELSE 0 - END) AS count_key_columns - FROM #IndexColumns c - WHERE c.database_id= si.database_id - AND c.schema_name = si.schema_name - AND c.object_id = si.object_id - AND c.index_id = si.index_id - ) AS D4 ( count_included_columns, count_key_columns ); + @EndDate: The date you want to stop searching on. -RAISERROR (N'Updating index_sanity_id on #IndexPartitionSanity',0,1) WITH NOWAIT; -UPDATE #IndexPartitionSanity -SET index_sanity_id = i.index_sanity_id -FROM #IndexPartitionSanity ps - JOIN #IndexSanity i ON ps.[object_id] = i.[object_id] - AND ps.index_id = i.index_id - AND i.database_id = ps.database_id - AND i.schema_name = ps.schema_name; + @ObjectName: If you want to filter to a specific able. + The object name has to be fully qualified ''Database.Schema.Table'' + @StoredProcName: If you want to search for a single stored proc + The proc name has to be fully qualified ''Database.Schema.Sproc'' + + @AppName: If you want to filter to a specific application + + @HostName: If you want to filter to a specific host + + @LoginName: If you want to filter to a specific login -RAISERROR (N'Inserting data into #IndexSanitySize',0,1) WITH NOWAIT; -INSERT #IndexSanitySize ( [index_sanity_id], [database_id], [schema_name], [lock_escalation_desc], partition_count, total_rows, total_reserved_MB, - total_reserved_LOB_MB, total_reserved_row_overflow_MB, total_reserved_dictionary_MB, total_range_scan_count, - total_singleton_lookup_count, total_leaf_delete_count, total_leaf_update_count, - total_forwarded_fetch_count,total_row_lock_count, - total_row_lock_wait_count, total_row_lock_wait_in_ms, avg_row_lock_wait_in_ms, - total_page_lock_count, total_page_lock_wait_count, total_page_lock_wait_in_ms, - avg_page_lock_wait_in_ms, total_index_lock_promotion_attempt_count, - total_index_lock_promotion_count, data_compression_desc, - page_latch_wait_count, page_latch_wait_in_ms, page_io_latch_wait_count, page_io_latch_wait_in_ms) - SELECT index_sanity_id, ipp.database_id, ipp.schema_name, ipp.lock_escalation_desc, - COUNT(*), SUM(row_count), SUM(reserved_MB), - SUM(reserved_LOB_MB) - SUM(reserved_dictionary_MB), /* Subtract columnstore dictionaries from LOB data */ - SUM(reserved_row_overflow_MB), - SUM(reserved_dictionary_MB), - SUM(range_scan_count), - SUM(singleton_lookup_count), - SUM(leaf_delete_count), - SUM(leaf_update_count), - SUM(forwarded_fetch_count), - SUM(row_lock_count), - SUM(row_lock_wait_count), - SUM(row_lock_wait_in_ms), - CASE WHEN SUM(row_lock_wait_in_ms) > 0 THEN - SUM(row_lock_wait_in_ms)/(1.*SUM(row_lock_wait_count)) - ELSE 0 END AS avg_row_lock_wait_in_ms, - SUM(page_lock_count), - SUM(page_lock_wait_count), - SUM(page_lock_wait_in_ms), - CASE WHEN SUM(page_lock_wait_in_ms) > 0 THEN - SUM(page_lock_wait_in_ms)/(1.*SUM(page_lock_wait_count)) - ELSE 0 END AS avg_page_lock_wait_in_ms, - SUM(index_lock_promotion_attempt_count), - SUM(index_lock_promotion_count), - LEFT(MAX(data_compression_info.data_compression_rollup),4000), - SUM(page_latch_wait_count), - SUM(page_latch_wait_in_ms), - SUM(page_io_latch_wait_count), - SUM(page_io_latch_wait_in_ms) - FROM #IndexPartitionSanity ipp - /* individual partitions can have distinct compression settings, just roll them into a list here*/ - OUTER APPLY (SELECT STUFF(( - SELECT N', ' + data_compression_desc - FROM #IndexPartitionSanity ipp2 - WHERE ipp.[object_id]=ipp2.[object_id] - AND ipp.[index_id]=ipp2.[index_id] - AND ipp.database_id = ipp2.database_id - AND ipp.schema_name = ipp2.schema_name - ORDER BY ipp2.partition_number - FOR XML PATH(''),TYPE).value('.', 'nvarchar(max)'), 1, 1, '')) - data_compression_info(data_compression_rollup) - GROUP BY index_sanity_id, ipp.database_id, ipp.schema_name, ipp.lock_escalation_desc - ORDER BY index_sanity_id -OPTION ( RECOMPILE ); + @EventSessionPath: If you want to point this at an XE session rather than the system health session. + + @OutputDatabaseName: If you want to output information to a specific database + @OutputSchemaName: Specify a schema name to output information to a specific Schema + @OutputTableName: Specify table name to to output information to a specific table + + To learn more, visit http://FirstResponderKit.org where you can download new + versions for free, watch training videos on how it works, get more info on + the findings, contribute your own code, and more. -RAISERROR (N'Determining index usefulness',0,1) WITH NOWAIT; -UPDATE #MissingIndexes -SET is_low = CASE WHEN (user_seeks + user_scans) < 5000 - OR unique_compiles = 1 - THEN 1 - ELSE 0 - END; + Known limitations of this version: + - Only SQL Server 2012 and newer is supported + - If your tables have weird characters in them (https://en.wikipedia.org/wiki/List_of_XML_and_HTML_character_entity_references) you may get errors trying to parse the XML. + I took a long look at this one, and: + 1) Trying to account for all the weird places these could crop up is a losing effort. + 2) Replace is slow af on lots of XML. + - Your mom. -RAISERROR (N'Updating #IndexSanity.referenced_by_foreign_key',0,1) WITH NOWAIT; -UPDATE #IndexSanity - SET is_referenced_by_foreign_key=1 -FROM #IndexSanity s -JOIN #ForeignKeys fk ON - s.object_id=fk.referenced_object_id - AND s.database_id=fk.database_id - AND LEFT(s.key_column_names,LEN(fk.referenced_fk_columns)) = fk.referenced_fk_columns; -RAISERROR (N'Update index_secret on #IndexSanity for NC indexes.',0,1) WITH NOWAIT; -UPDATE nc -SET secret_columns= - N'[' + - CASE tb.count_key_columns WHEN 0 THEN '1' ELSE CAST(tb.count_key_columns AS NVARCHAR(10)) END + - CASE nc.is_unique WHEN 1 THEN N' INCLUDE' ELSE N' KEY' END + - CASE WHEN tb.count_key_columns > 1 THEN N'S] ' ELSE N'] ' END + - CASE tb.index_id WHEN 0 THEN '[RID]' ELSE LTRIM(tb.key_column_names) + - /* Uniquifiers only needed on non-unique clustereds-- not heaps */ - CASE tb.is_unique WHEN 0 THEN ' [UNIQUIFIER]' ELSE N'' END - END - , count_secret_columns= - CASE tb.index_id WHEN 0 THEN 1 ELSE - tb.count_key_columns + - CASE tb.is_unique WHEN 0 THEN 1 ELSE 0 END - END -FROM #IndexSanity AS nc -JOIN #IndexSanity AS tb ON nc.object_id=tb.object_id - AND nc.database_id = tb.database_id - AND nc.schema_name = tb.schema_name - AND tb.index_id IN (0,1) -WHERE nc.index_id > 1; -RAISERROR (N'Update index_secret on #IndexSanity for heaps and non-unique clustered.',0,1) WITH NOWAIT; -UPDATE tb -SET secret_columns= CASE tb.index_id WHEN 0 THEN '[RID]' ELSE '[UNIQUIFIER]' END - , count_secret_columns = 1 -FROM #IndexSanity AS tb -WHERE tb.index_id = 0 /*Heaps-- these have the RID */ - OR (tb.index_id=1 AND tb.is_unique=0); /* Non-unique CX: has uniquifer (when needed) */ + Unknown limitations of this version: + - None. (If we knew them, they would be known. Duh.) -RAISERROR (N'Populate #IndexCreateTsql.',0,1) WITH NOWAIT; -INSERT #IndexCreateTsql (index_sanity_id, create_tsql) -SELECT - index_sanity_id, - ISNULL ( - CASE index_id WHEN 0 THEN N'ALTER TABLE ' + QUOTENAME([database_name]) + N'.' + QUOTENAME([schema_name]) + N'.' + QUOTENAME([object_name]) + ' REBUILD;' - ELSE - CASE WHEN is_XML = 1 OR is_spatial = 1 OR is_in_memory_oltp = 1 THEN N'' /* Not even trying for these just yet...*/ - ELSE - CASE WHEN is_primary_key=1 THEN - N'ALTER TABLE ' + QUOTENAME([database_name]) + N'.' + QUOTENAME([schema_name]) + - N'.' + QUOTENAME([object_name]) + - N' ADD CONSTRAINT [' + - index_name + - N'] PRIMARY KEY ' + - CASE WHEN index_id=1 THEN N'CLUSTERED (' ELSE N'(' END + - key_column_names_with_sort_order_no_types + N' )' - WHEN is_CX_columnstore= 1 THEN - N'CREATE CLUSTERED COLUMNSTORE INDEX ' + QUOTENAME(index_name) + N' on ' + QUOTENAME([database_name]) + N'.' + QUOTENAME([schema_name]) + N'.' + QUOTENAME([object_name]) - ELSE /*Else not a PK or cx columnstore */ - N'CREATE ' + - CASE WHEN is_unique=1 THEN N'UNIQUE ' ELSE N'' END + - CASE WHEN index_id=1 THEN N'CLUSTERED ' ELSE N'' END + - CASE WHEN is_NC_columnstore=1 THEN N'NONCLUSTERED COLUMNSTORE ' - ELSE N'' END + - N'INDEX [' - + index_name + N'] ON ' + - QUOTENAME([database_name]) + N'.' + - QUOTENAME([schema_name]) + N'.' + QUOTENAME([object_name]) + - CASE WHEN is_NC_columnstore=1 THEN - N' (' + ISNULL(include_column_names_no_types,'') + N' )' - ELSE /*Else not columnstore */ - N' (' + ISNULL(key_column_names_with_sort_order_no_types,'') + N' )' - + CASE WHEN include_column_names_no_types IS NOT NULL THEN - N' INCLUDE (' + include_column_names_no_types + N')' - ELSE N'' - END - END /*End non-columnstore case */ - + CASE WHEN filter_definition <> N'' THEN N' WHERE ' + filter_definition ELSE N'' END - END /*End Non-PK index CASE */ - + CASE WHEN is_NC_columnstore=0 AND is_CX_columnstore=0 THEN - N' WITH (' - + N'FILLFACTOR=' + CASE fill_factor WHEN 0 THEN N'100' ELSE CAST(fill_factor AS NVARCHAR(5)) END + ', ' - + N'ONLINE=?, SORT_IN_TEMPDB=?, DATA_COMPRESSION=?' - + N')' - ELSE N'' END - + N';' - END /*End non-spatial and non-xml CASE */ - END, '[Unknown Error]') - AS create_tsql -FROM #IndexSanity; - -RAISERROR (N'Populate #PartitionCompressionInfo.',0,1) WITH NOWAIT; -WITH maps - AS - ( - SELECT ips.index_sanity_id, - ips.partition_number, - ips.data_compression_desc, - ips.partition_number - ROW_NUMBER() OVER ( PARTITION BY ips.index_sanity_id, ips.data_compression_desc - ORDER BY ips.partition_number ) AS rn - FROM #IndexPartitionSanity AS ips - ) -SELECT * -INTO #maps -FROM maps; -WITH grps - AS - ( - SELECT MIN(maps.partition_number) AS MinKey, - MAX(maps.partition_number) AS MaxKey, - maps.index_sanity_id, - maps.data_compression_desc - FROM #maps AS maps - GROUP BY maps.rn, maps.index_sanity_id, maps.data_compression_desc - ) -SELECT * -INTO #grps -FROM grps; + MIT License + + Copyright (c) 2021 Brent Ozar Unlimited -INSERT #PartitionCompressionInfo ( index_sanity_id, partition_compression_detail ) -SELECT DISTINCT - grps.index_sanity_id, - SUBSTRING( - ( STUFF( - ( SELECT N', ' + N' Partition' - + CASE - WHEN grps2.MinKey < grps2.MaxKey - THEN - + N's ' + CAST(grps2.MinKey AS NVARCHAR(10)) + N' - ' - + CAST(grps2.MaxKey AS NVARCHAR(10)) + N' use ' + grps2.data_compression_desc - ELSE - N' ' + CAST(grps2.MinKey AS NVARCHAR(10)) + N' uses ' + grps2.data_compression_desc - END AS Partitions - FROM #grps AS grps2 - WHERE grps2.index_sanity_id = grps.index_sanity_id - ORDER BY grps2.MinKey, grps2.MaxKey - FOR XML PATH(''), TYPE ).value('.', 'NVARCHAR(MAX)'), 1, 1, '')), 0, 8000) AS partition_compression_detail -FROM #grps AS grps; - -RAISERROR (N'Update #PartitionCompressionInfo.',0,1) WITH NOWAIT; -UPDATE sz -SET sz.data_compression_desc = pci.partition_compression_detail -FROM #IndexSanitySize sz -JOIN #PartitionCompressionInfo AS pci -ON pci.index_sanity_id = sz.index_sanity_id; + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: -RAISERROR (N'Update #IndexSanity for filtered indexes with columns not in the index definition.',0,1) WITH NOWAIT; -UPDATE #IndexSanity -SET filter_columns_not_in_index = D1.filter_columns_not_in_index -FROM #IndexSanity si - CROSS APPLY ( SELECT RTRIM(STUFF( (SELECT N', ' + c.column_name AS col_definition - FROM #FilteredIndexes AS c - WHERE c.database_id= si.database_id - AND c.schema_name = si.schema_name - AND c.table_name = si.object_name - AND c.index_name = si.index_name - ORDER BY c.index_sanity_id - FOR XML PATH('') , TYPE).value('.', 'nvarchar(max)'), 1, 1,''))) D1 - ( filter_columns_not_in_index ); + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. -IF @Debug = 1 -BEGIN - SELECT '#IndexSanity' AS table_name, * FROM #IndexSanity; - SELECT '#IndexPartitionSanity' AS table_name, * FROM #IndexPartitionSanity; - SELECT '#IndexSanitySize' AS table_name, * FROM #IndexSanitySize; - SELECT '#IndexColumns' AS table_name, * FROM #IndexColumns; - SELECT '#MissingIndexes' AS table_name, * FROM #MissingIndexes; - SELECT '#ForeignKeys' AS table_name, * FROM #ForeignKeys; - SELECT '#BlitzIndexResults' AS table_name, * FROM #BlitzIndexResults; - SELECT '#IndexCreateTsql' AS table_name, * FROM #IndexCreateTsql; - SELECT '#DatabaseList' AS table_name, * FROM #DatabaseList; - SELECT '#Statistics' AS table_name, * FROM #Statistics; - SELECT '#PartitionCompressionInfo' AS table_name, * FROM #PartitionCompressionInfo; - SELECT '#ComputedColumns' AS table_name, * FROM #ComputedColumns; - SELECT '#TraceStatus' AS table_name, * FROM #TraceStatus; - SELECT '#CheckConstraints' AS table_name, * FROM #CheckConstraints; - SELECT '#FilteredIndexes' AS table_name, * FROM #FilteredIndexes; -END + */'; + RETURN; + END; /* @Help = 1 */ + + DECLARE @ProductVersion NVARCHAR(128); + DECLARE @ProductVersionMajor FLOAT; + DECLARE @ProductVersionMinor INT; + SET @ProductVersion = CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)); ----------------------------------------- ---STEP 3: DIAGNOSE THE PATIENT ----------------------------------------- + SELECT @ProductVersionMajor = SUBSTRING(@ProductVersion, 1, CHARINDEX('.', @ProductVersion) + 1), + @ProductVersionMinor = PARSENAME(CONVERT(VARCHAR(32), @ProductVersion), 2); -BEGIN TRY ----------------------------------------- ---If @TableName is specified, just return information for that table. ---The @Mode parameter doesn't matter if you're looking at a specific table. ----------------------------------------- -IF @TableName IS NOT NULL -BEGIN - RAISERROR(N'@TableName specified, giving detail only on that table.', 0,1) WITH NOWAIT; - - --We do a left join here in case this is a disabled NC. - --In that case, it won't have any size info/pages allocated. - - - WITH table_mode_cte AS ( - SELECT - s.db_schema_object_indexid, - s.key_column_names, - s.index_definition, - ISNULL(s.secret_columns,N'') AS secret_columns, - s.fill_factor, - s.index_usage_summary, - sz.index_op_stats, - ISNULL(sz.index_size_summary,'') /*disabled NCs will be null*/ AS index_size_summary, - partition_compression_detail , - ISNULL(sz.index_lock_wait_summary,'') AS index_lock_wait_summary, - s.is_referenced_by_foreign_key, - (SELECT COUNT(*) - FROM #ForeignKeys fk WHERE fk.parent_object_id=s.object_id - AND PATINDEX (fk.parent_fk_columns, s.key_column_names)=1) AS FKs_covered_by_index, - s.last_user_seek, - s.last_user_scan, - s.last_user_lookup, - s.last_user_update, - s.create_date, - s.modify_date, - sz.page_latch_wait_count, - CONVERT(VARCHAR(10), (sz.page_latch_wait_in_ms / 1000) / 86400) + ':' + CONVERT(VARCHAR(20), DATEADD(s, (sz.page_latch_wait_in_ms / 1000), 0), 108) AS page_latch_wait_time, - sz.page_io_latch_wait_count, - CONVERT(VARCHAR(10), (sz.page_io_latch_wait_in_ms / 1000) / 86400) + ':' + CONVERT(VARCHAR(20), DATEADD(s, (sz.page_io_latch_wait_in_ms / 1000), 0), 108) AS page_io_latch_wait_time, - ct.create_tsql, - CASE - WHEN s.is_primary_key = 1 AND s.index_definition <> '[HEAP]' - THEN N'--ALTER TABLE ' + QUOTENAME(s.[database_name]) + N'.' + QUOTENAME(s.[schema_name]) + N'.' + QUOTENAME(s.[object_name]) - + N' DROP CONSTRAINT ' + QUOTENAME(s.index_name) + N';' - WHEN s.is_primary_key = 0 AND s.index_definition <> '[HEAP]' - THEN N'--DROP INDEX '+ QUOTENAME(s.index_name) + N' ON ' + QUOTENAME(s.[database_name]) + N'.' + - QUOTENAME(s.[schema_name]) + N'.' + QUOTENAME(s.[object_name]) + N';' - ELSE N'' - END AS drop_tsql, - 1 AS display_order - FROM #IndexSanity s - LEFT JOIN #IndexSanitySize sz ON - s.index_sanity_id=sz.index_sanity_id - LEFT JOIN #IndexCreateTsql ct ON - s.index_sanity_id=ct.index_sanity_id - LEFT JOIN #PartitionCompressionInfo pci ON - pci.index_sanity_id = s.index_sanity_id - WHERE s.[object_id]=@ObjectID - UNION ALL - SELECT N'Database ' + QUOTENAME(@DatabaseName) + N' as of ' + CONVERT(NVARCHAR(16),GETDATE(),121) + - N' (' + @ScriptVersionName + ')' , - N'SQL Server First Responder Kit' , - N'http://FirstResponderKit.org' , - N'From Your Community Volunteers', - NULL,@DaysUptimeInsertValue,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL, - 0 AS display_order - ) - SELECT - db_schema_object_indexid AS [Details: db_schema.table.index(indexid)], - index_definition AS [Definition: [Property]] ColumnName {datatype maxbytes}], - secret_columns AS [Secret Columns], - fill_factor AS [Fillfactor], - index_usage_summary AS [Usage Stats], - index_op_stats AS [Op Stats], - index_size_summary AS [Size], - partition_compression_detail AS [Compression Type], - index_lock_wait_summary AS [Lock Waits], - is_referenced_by_foreign_key AS [Referenced by FK?], - FKs_covered_by_index AS [FK Covered by Index?], - last_user_seek AS [Last User Seek], - last_user_scan AS [Last User Scan], - last_user_lookup AS [Last User Lookup], - last_user_update AS [Last User Write], - create_date AS [Created], - modify_date AS [Last Modified], - page_latch_wait_count AS [Page Latch Wait Count], - page_latch_wait_time as [Page Latch Wait Time (D:H:M:S)], - page_io_latch_wait_count AS [Page IO Latch Wait Count], - page_io_latch_wait_time as [Page IO Latch Wait Time (D:H:M:S)], - create_tsql AS [Create TSQL], - drop_tsql AS [Drop TSQL] - FROM table_mode_cte - ORDER BY display_order ASC, key_column_names ASC - OPTION ( RECOMPILE ); + IF @ProductVersionMajor < 11.0 + BEGIN + RAISERROR( + 'sp_BlitzLock will throw a bunch of angry errors on versions of SQL Server earlier than 2012.', + 0, + 1) WITH NOWAIT; + RETURN; + END; + + IF ((SELECT SERVERPROPERTY ('EDITION')) = 'SQL Azure' + AND + LOWER(@EventSessionPath) NOT LIKE 'http%') + BEGIN + RAISERROR( + 'The default storage path doesn''t work in Azure SQLDB/Managed instances. +You need to use an Azure storage account, and the path has to look like this: https://StorageAccount.blob.core.windows.net/Container/FileName.xel', + 0, + 1) WITH NOWAIT; + RETURN; + END; - IF (SELECT TOP 1 [object_id] FROM #MissingIndexes mi) IS NOT NULL - BEGIN; - WITH create_date AS ( - SELECT i.database_id, - i.schema_name, - i.[object_id], - ISNULL(NULLIF(MAX(DATEDIFF(DAY, i.create_date, SYSDATETIME())), 0), 1) AS create_days - FROM #IndexSanity AS i - GROUP BY i.database_id, i.schema_name, i.object_id - ) - SELECT N'Missing index.' AS Finding , - N'https://www.brentozar.com/go/Indexaphobia' AS URL , - mi.[statement] + - ' Est. Benefit: ' - + CASE WHEN magic_benefit_number >= 922337203685477 THEN '>= 922,337,203,685,477' - ELSE REPLACE(CONVERT(NVARCHAR(256),CAST(CAST( - (magic_benefit_number / CASE WHEN cd.create_days < @DaysUptime THEN cd.create_days ELSE @DaysUptime END) - AS BIGINT) AS MONEY), 1), '.00', '') - END AS [Estimated Benefit], - missing_index_details AS [Missing Index Request] , - index_estimated_impact AS [Estimated Impact], - create_tsql AS [Create TSQL], - sample_query_plan AS [Sample Query Plan] - FROM #MissingIndexes mi - LEFT JOIN create_date AS cd - ON mi.[object_id] = cd.object_id - AND mi.database_id = cd.database_id - AND mi.schema_name = cd.schema_name - WHERE mi.[object_id] = @ObjectID - AND (@ShowAllMissingIndexRequests=1 - /* Minimum benefit threshold = 100k/day of uptime OR since table creation date, whichever is lower*/ - OR (magic_benefit_number / CASE WHEN cd.create_days < @DaysUptime THEN cd.create_days ELSE @DaysUptime END) >= 100000) - ORDER BY magic_benefit_number DESC - OPTION ( RECOMPILE ); - END; - ELSE - SELECT 'No missing indexes.' AS finding; + IF @Top IS NULL + SET @Top = 2147483647; - SELECT - column_name AS [Column Name], - (SELECT COUNT(*) - FROM #IndexColumns c2 - WHERE c2.column_name=c.column_name - AND c2.key_ordinal IS NOT NULL) - + CASE WHEN c.index_id = 1 AND c.key_ordinal IS NOT NULL THEN - -1+ (SELECT COUNT(DISTINCT index_id) - FROM #IndexColumns c3 - WHERE c3.index_id NOT IN (0,1)) - ELSE 0 END - AS [Found In], - system_type_name + - CASE max_length WHEN -1 THEN N' (max)' ELSE - CASE - WHEN system_type_name IN (N'char',N'varchar',N'binary',N'varbinary') THEN N' (' + CAST(max_length AS NVARCHAR(20)) + N')' - WHEN system_type_name IN (N'nchar',N'nvarchar') THEN N' (' + CAST(max_length/2 AS NVARCHAR(20)) + N')' - ELSE '' - END - END - AS [Type], - CASE is_computed WHEN 1 THEN 'yes' ELSE '' END AS [Computed?], - max_length AS [Length (max bytes)], - [precision] AS [Prec], - [scale] AS [Scale], - CASE is_nullable WHEN 1 THEN 'yes' ELSE '' END AS [Nullable?], - CASE is_identity WHEN 1 THEN 'yes' ELSE '' END AS [Identity?], - CASE is_replicated WHEN 1 THEN 'yes' ELSE '' END AS [Replicated?], - CASE is_sparse WHEN 1 THEN 'yes' ELSE '' END AS [Sparse?], - CASE is_filestream WHEN 1 THEN 'yes' ELSE '' END AS [Filestream?], - collation_name AS [Collation] - FROM #IndexColumns AS c - WHERE index_id IN (0,1); + IF @StartDate IS NULL + SET @StartDate = '19000101'; - IF (SELECT TOP 1 parent_object_id FROM #ForeignKeys) IS NOT NULL - BEGIN - SELECT [database_name] + N':' + parent_object_name + N': ' + foreign_key_name AS [Foreign Key], - parent_fk_columns AS [Foreign Key Columns], - referenced_object_name AS [Referenced Table], - referenced_fk_columns AS [Referenced Table Columns], - is_disabled AS [Is Disabled?], - is_not_trusted AS [Not Trusted?], - is_not_for_replication [Not for Replication?], - [update_referential_action_desc] AS [Cascading Updates?], - [delete_referential_action_desc] AS [Cascading Deletes?] - FROM #ForeignKeys - ORDER BY [Foreign Key] - OPTION ( RECOMPILE ); - END; - ELSE - SELECT 'No foreign keys.' AS finding; + IF @EndDate IS NULL + SET @EndDate = '99991231'; + - /* Show histograms for all stats on this table. More info: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/1900 */ - IF EXISTS (SELECT * FROM sys.all_objects WHERE name = 'dm_db_stats_histogram') - BEGIN - SET @dsql=N'SELECT s.name AS [Stat Name], c.name AS [Leading Column Name], hist.step_number AS [Step Number], - hist.range_high_key AS [Range High Key], hist.range_rows AS [Range Rows], - hist.equal_rows AS [Equal Rows], hist.distinct_range_rows AS [Distinct Range Rows], hist.average_range_rows AS [Average Range Rows], - s.auto_created AS [Auto-Created], s.user_created AS [User-Created], - props.last_updated AS [Last Updated], props.modification_counter AS [Modification Counter], props.rows AS [Table Rows], - props.rows_sampled AS [Rows Sampled], s.stats_id AS [StatsID] - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.stats AS s - INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.stats_columns sc ON s.object_id = sc.object_id AND s.stats_id = sc.stats_id AND sc.stats_column_id = 1 - INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns c ON sc.object_id = c.object_id AND sc.column_id = c.column_id - CROSS APPLY ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_stats_properties(s.object_id, s.stats_id) AS props - CROSS APPLY ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_stats_histogram(s.[object_id], s.stats_id) AS hist - WHERE s.object_id = @ObjectID - ORDER BY s.auto_created, s.user_created, s.name, hist.step_number;'; - EXEC sp_executesql @dsql, N'@ObjectID INT', @ObjectID; - END + IF OBJECT_ID('tempdb..#deadlock_data') IS NOT NULL + DROP TABLE #deadlock_data; - /* Visualize columnstore index contents. More info: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2584 */ - IF 2 = (SELECT SUM(1) FROM sys.all_objects WHERE name IN ('column_store_row_groups','column_store_segments')) - BEGIN - RAISERROR(N'Visualizing columnstore index contents.', 0,1) WITH NOWAIT; + IF OBJECT_ID('tempdb..#deadlock_process') IS NOT NULL + DROP TABLE #deadlock_process; - SET @dsql = N'USE ' + QUOTENAME(@DatabaseName) + N'; - IF EXISTS(SELECT * FROM ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_row_groups WHERE object_id = @ObjectID) - BEGIN - SET @ColumnList = N''''; - WITH DistinctColumns AS ( - SELECT DISTINCT QUOTENAME(c.name) AS column_name, c.column_id - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.partitions p - INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns c ON p.object_id = c.object_id - INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.index_columns ic on ic.column_id = c.column_id and ic.object_id = c.object_id AND ic.index_id = p.index_id - INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.types t ON c.system_type_id = t.system_type_id AND c.user_type_id = t.user_type_id - WHERE p.object_id = @ObjectID - AND EXISTS (SELECT * FROM ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_segments seg WHERE p.partition_id = seg.partition_id AND seg.column_id = ic.index_column_id) - AND p.data_compression IN (3,4) - ) - SELECT @ColumnList = @ColumnList + column_name + N'', '' - FROM DistinctColumns - ORDER BY column_id; - END'; + IF OBJECT_ID('tempdb..#deadlock_stack') IS NOT NULL + DROP TABLE #deadlock_stack; - IF @Debug = 1 - BEGIN - PRINT SUBSTRING(@dsql, 0, 4000); - PRINT SUBSTRING(@dsql, 4000, 8000); - PRINT SUBSTRING(@dsql, 8000, 12000); - PRINT SUBSTRING(@dsql, 12000, 16000); - PRINT SUBSTRING(@dsql, 16000, 20000); - PRINT SUBSTRING(@dsql, 20000, 24000); - PRINT SUBSTRING(@dsql, 24000, 28000); - PRINT SUBSTRING(@dsql, 28000, 32000); - PRINT SUBSTRING(@dsql, 32000, 36000); - PRINT SUBSTRING(@dsql, 36000, 40000); - END; + IF OBJECT_ID('tempdb..#deadlock_resource') IS NOT NULL + DROP TABLE #deadlock_resource; - EXEC sp_executesql @dsql, N'@ObjectID INT, @ColumnList NVARCHAR(MAX) OUTPUT', @ObjectID, @ColumnList OUTPUT; + IF OBJECT_ID('tempdb..#deadlock_owner_waiter') IS NOT NULL + DROP TABLE #deadlock_owner_waiter; - IF @Debug = 1 - SELECT @ColumnList AS ColumnstoreColumnList; + IF OBJECT_ID('tempdb..#deadlock_findings') IS NOT NULL + DROP TABLE #deadlock_findings; - IF @ColumnList <> '' - BEGIN - /* Remove the trailing comma */ - SET @ColumnList = LEFT(@ColumnList, LEN(@ColumnList) - 1); + CREATE TABLE #deadlock_findings + ( + id INT IDENTITY(1, 1) PRIMARY KEY CLUSTERED, + check_id INT NOT NULL, + database_name NVARCHAR(256), + object_name NVARCHAR(1000), + finding_group NVARCHAR(100), + finding NVARCHAR(4000) + ); - SET @dsql = N'USE ' + QUOTENAME(@DatabaseName) + N'; SELECT partition_number, row_group_id, total_rows, deleted_rows, ' + @ColumnList + N' - FROM ( - SELECT c.name AS column_name, p.partition_number, - rg.row_group_id, rg.total_rows, rg.deleted_rows, - details = CAST(seg.min_data_id AS VARCHAR(20)) + '' to '' + CAST(seg.max_data_id AS VARCHAR(20)) + '', '' + CAST(CAST((seg.on_disk_size / 1024.0 / 1024) AS DECIMAL(18,0)) AS VARCHAR(20)) + '' MB'' - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_row_groups rg - INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns c ON rg.object_id = c.object_id - INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partitions p ON rg.object_id = p.object_id AND rg.partition_number = p.partition_number - INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.index_columns ic on ic.column_id = c.column_id AND ic.object_id = c.object_id AND ic.index_id = p.index_id - LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_segments seg ON p.partition_id = seg.partition_id AND ic.index_column_id = seg.column_id AND rg.row_group_id = seg.segment_id - WHERE rg.object_id = @ObjectID - ) AS x - PIVOT (MAX(details) FOR column_name IN ( ' + @ColumnList + N')) AS pivot1 - ORDER BY partition_number, row_group_id;'; - - IF @Debug = 1 + DECLARE @d VARCHAR(40), @StringToExecute NVARCHAR(4000),@StringToExecuteParams NVARCHAR(500),@r NVARCHAR(200),@OutputTableFindings NVARCHAR(100),@DeadlockCount INT; + DECLARE @ServerName NVARCHAR(256) + DECLARE @OutputDatabaseCheck BIT; + SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + SET @OutputTableFindings = '[BlitzLockFindings]' + SET @ServerName = (select @@ServerName) + if(@OutputDatabaseName is not null) + BEGIN --if databaseName is set do some sanity checks and put [] around def. + if( (select name from sys.databases where name=@OutputDatabaseName) is null ) --if database is invalid raiserror and set bitcheck BEGIN - PRINT SUBSTRING(@dsql, 0, 4000); - PRINT SUBSTRING(@dsql, 4000, 8000); - PRINT SUBSTRING(@dsql, 8000, 12000); - PRINT SUBSTRING(@dsql, 12000, 16000); - PRINT SUBSTRING(@dsql, 16000, 20000); - PRINT SUBSTRING(@dsql, 20000, 24000); - PRINT SUBSTRING(@dsql, 24000, 28000); - PRINT SUBSTRING(@dsql, 28000, 32000); - PRINT SUBSTRING(@dsql, 32000, 36000); - PRINT SUBSTRING(@dsql, 36000, 40000); - END; - - IF @dsql IS NULL - RAISERROR('@dsql is null',16,1); + RAISERROR('Database Name for output of table is invalid please correct, Output to Table will not be preformed', 0, 1, @d) WITH NOWAIT; + set @OutputDatabaseCheck = -1 -- -1 invalid/false, 0 = good/true + END ELSE - EXEC sp_executesql @dsql, N'@ObjectID INT', @ObjectID; - END - ELSE /* No columns were found for this object */ - BEGIN - SELECT N'No compressed columnstore rowgroups were found for this object.' AS Columnstore_Visualization - UNION ALL - SELECT N'SELECT * FROM ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_row_groups WHERE object_id = ' + CAST(@ObjectID AS NVARCHAR(100)); - END - RAISERROR(N'Done visualizing columnstore index contents.', 0,1) WITH NOWAIT; - END - -END; /* IF @TableName IS NOT NULL */ - - - - - - - - - - - - - - - - - - - - - - - - - - + BEGIN + set @OutputDatabaseCheck = 0 + select @StringToExecute = N'select @r = name from ' + '' + @OutputDatabaseName + + '' + '.sys.objects where type_desc=''USER_TABLE'' and name=' + '''' + @OutputTableName + '''', + @StringToExecuteParams = N'@OutputDatabaseName NVARCHAR(200),@OutputTableName NVARCHAR(200),@r NVARCHAR(200) OUTPUT' + exec sp_executesql @StringToExecute,@StringToExecuteParams,@OutputDatabaseName,@OutputTableName,@r OUTPUT + --put covers around all before. + SELECT @OutputDatabaseName = QUOTENAME(@OutputDatabaseName), + @OutputTableName = QUOTENAME(@OutputTableName), + @OutputSchemaName = QUOTENAME(@OutputSchemaName) + if(@r is null) --if it is null there is no table, create it from above execution + BEGIN + select @StringToExecute = N'use ' + @OutputDatabaseName + ';create table ' + @OutputSchemaName + '.' + @OutputTableName + ' ( + ServerName NVARCHAR(256), + deadlock_type NVARCHAR(256), + event_date datetime, + database_name NVARCHAR(256), + deadlock_group NVARCHAR(256), + query XML, + object_names XML, + isolation_level NVARCHAR(256), + owner_mode NVARCHAR(256), + waiter_mode NVARCHAR(256), + transaction_count bigint, + login_name NVARCHAR(256), + host_name NVARCHAR(256), + client_app NVARCHAR(256), + wait_time BIGINT, + priority smallint, + log_used BIGINT, + last_tran_started datetime, + last_batch_started datetime, + last_batch_completed datetime, + transaction_name NVARCHAR(256), + owner_waiter_type NVARCHAR(256), + owner_activity NVARCHAR(256), + owner_waiter_activity NVARCHAR(256), + owner_merging NVARCHAR(256), + owner_spilling NVARCHAR(256), + owner_waiting_to_close NVARCHAR(256), + waiter_waiter_type NVARCHAR(256), + waiter_owner_activity NVARCHAR(256), + waiter_waiter_activity NVARCHAR(256), + waiter_merging NVARCHAR(256), + waiter_spilling NVARCHAR(256), + waiter_waiting_to_close NVARCHAR(256), + deadlock_graph XML)', + @StringToExecuteParams = N'@OutputDatabaseName NVARCHAR(200),@OutputSchemaName NVARCHAR(100),@OutputTableName NVARCHAR(200)' + exec sp_executesql @StringToExecute, @StringToExecuteParams,@OutputDatabaseName,@OutputSchemaName,@OutputTableName + --table created. + select @StringToExecute = N'select @r = name from ' + '' + @OutputDatabaseName + + '' + '.sys.objects where type_desc=''USER_TABLE'' and name=''BlitzLockFindings''', + @StringToExecuteParams = N'@OutputDatabaseName NVARCHAR(200),@r NVARCHAR(200) OUTPUT' + exec sp_executesql @StringToExecute,@StringToExecuteParams,@OutputDatabaseName,@r OUTPUT + if(@r is null) --if table does not excist + BEGIN + select @OutputTableFindings=N'[BlitzLockFindings]', + @StringToExecute = N'use ' + @OutputDatabaseName + ';create table ' + @OutputSchemaName + '.' + @OutputTableFindings + ' ( + ServerName NVARCHAR(256), + check_id INT, + database_name NVARCHAR(256), + object_name NVARCHAR(1000), + finding_group NVARCHAR(100), + finding NVARCHAR(4000))', + @StringToExecuteParams = N'@OutputDatabaseName NVARCHAR(200),@OutputSchemaName NVARCHAR(100),@OutputTableFindings NVARCHAR(200)' + exec sp_executesql @StringToExecute, @StringToExecuteParams, @OutputDatabaseName,@OutputSchemaName,@OutputTableFindings + + END + END + --create synonym for deadlockfindings. + if((select name from sys.objects where name='DeadlockFindings' and type_desc='SYNONYM')IS NOT NULL) + BEGIN + RAISERROR('found synonym', 0, 1) WITH NOWAIT; + drop synonym DeadlockFindings; + END + set @StringToExecute = 'CREATE SYNONYM DeadlockFindings FOR ' + @OutputDatabaseName + '.' + @OutputSchemaName + '.' + @OutputTableFindings; + exec sp_executesql @StringToExecute + + --create synonym for deadlock table. + if((select name from sys.objects where name='DeadLockTbl' and type_desc='SYNONYM') IS NOT NULL) + BEGIN + drop SYNONYM DeadLockTbl; + END + set @StringToExecute = 'CREATE SYNONYM DeadLockTbl FOR ' + @OutputDatabaseName + '.' + @OutputSchemaName + '.' + @OutputTableName; + exec sp_executesql @StringToExecute + + END + END + + CREATE TABLE #t (id INT NOT NULL); + /* WITH ROWCOUNT doesn't work on Amazon RDS - see: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2037 */ + IF LEFT(CAST(SERVERPROPERTY('ComputerNamePhysicalNetBIOS') AS VARCHAR(8000)), 8) <> 'EC2AMAZ-' + AND LEFT(CAST(SERVERPROPERTY('MachineName') AS VARCHAR(8000)), 8) <> 'EC2AMAZ-' + AND LEFT(CAST(SERVERPROPERTY('ServerName') AS VARCHAR(8000)), 8) <> 'EC2AMAZ-' + AND db_id('rdsadmin') IS NULL + BEGIN; + BEGIN TRY; + UPDATE STATISTICS #t WITH ROWCOUNT = 100000000, PAGECOUNT = 100000000; + END TRY + BEGIN CATCH; + /* Misleading error returned, if run without permissions to update statistics the error returned is "Cannot find object". + Catching specific error, and returning message with better info. If any other error is returned, then throw as normal */ + IF (ERROR_NUMBER() = 1088) + BEGIN; + SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + RAISERROR('Cannot run UPDATE STATISTICS on temp table without db_owner or sysadmin permissions %s', 0, 1, @d) WITH NOWAIT; + END; + ELSE + BEGIN; + THROW; + END; + END CATCH; + END; + /*Grab the initial set of XML to parse*/ + SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + RAISERROR('Grab the initial set of XML to parse at %s', 0, 1, @d) WITH NOWAIT; + WITH xml + AS ( SELECT CONVERT(XML, event_data) AS deadlock_xml + FROM sys.fn_xe_file_target_read_file(@EventSessionPath, NULL, NULL, NULL) ) + SELECT ISNULL(xml.deadlock_xml, '') AS deadlock_xml + INTO #deadlock_data + FROM xml + LEFT JOIN #t AS t + ON 1 = 1 + CROSS APPLY xml.deadlock_xml.nodes('/event/@name') AS x(c) + WHERE 1 = 1 + AND x.c.exist('/event/@name[ .= "xml_deadlock_report"]') = 1 + AND CONVERT(datetime, SWITCHOFFSET(CONVERT(datetimeoffset, xml.deadlock_xml.value('(/event/@timestamp)[1]', 'datetime') ), DATENAME(TzOffset, SYSDATETIMEOFFSET()))) > @StartDate + AND CONVERT(datetime, SWITCHOFFSET(CONVERT(datetimeoffset, xml.deadlock_xml.value('(/event/@timestamp)[1]', 'datetime') ), DATENAME(TzOffset, SYSDATETIMEOFFSET()))) < @EndDate + ORDER BY xml.deadlock_xml.value('(/event/@timestamp)[1]', 'datetime') DESC + OPTION ( RECOMPILE ); + /*Optimization: if we got back more rows than @Top, remove them. This seems to be better than using @Top in the query above as that results in excessive memory grant*/ + SET @DeadlockCount = @@ROWCOUNT + IF( @Top < @DeadlockCount ) BEGIN + WITH T + AS ( + SELECT TOP ( @DeadlockCount - @Top) * + FROM #deadlock_data + ORDER BY #deadlock_data.deadlock_xml.value('(/event/@timestamp)[1]', 'datetime') ASC) + DELETE FROM T + END -ELSE IF @Mode IN (0, 4) /* DIAGNOSE*//* IF @TableName IS NOT NULL, so @TableName must not be null */ -BEGIN; - IF @Mode IN (0, 4) /* DIAGNOSE priorities 1-100 */ - BEGIN; - RAISERROR(N'@Mode=0 or 4, running rules for priorities 1-100.', 0,1) WITH NOWAIT; - - ---------------------------------------- - --Multiple Index Personalities: Check_id 0-10 - ---------------------------------------- - RAISERROR('check_id 1: Duplicate keys', 0,1) WITH NOWAIT; - WITH duplicate_indexes - AS ( SELECT [object_id], key_column_names, database_id, [schema_name] - FROM #IndexSanity AS ip - WHERE index_type IN (1,2) /* Clustered, NC only*/ - AND is_hypothetical = 0 - AND is_disabled = 0 - AND is_primary_key = 0 - AND EXISTS ( - SELECT 1/0 - FROM #IndexSanitySize ips - WHERE ip.index_sanity_id = ips.index_sanity_id - AND ip.database_id = ips.database_id - AND ip.schema_name = ips.schema_name - AND ips.total_reserved_MB >= CASE - WHEN (@GetAllDatabases = 1 OR @Mode = 0) - THEN @ThresholdMB - ELSE ips.total_reserved_MB - END - ) - GROUP BY [object_id], key_column_names, database_id, [schema_name] - HAVING COUNT(*) > 1) - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 1 AS check_id, - ip.index_sanity_id, - 20 AS Priority, - 'Multiple Index Personalities' AS findings_group, - 'Duplicate keys' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/duplicateindex' AS URL, - N'Index Name: ' + ip.index_name + N' Table Name: ' + ip.db_schema_object_name AS details, - ip.index_definition, - ip.secret_columns, - ip.index_usage_summary, - ips.index_size_summary - FROM duplicate_indexes di - JOIN #IndexSanity ip ON di.[object_id] = ip.[object_id] - AND ip.database_id = di.database_id - AND ip.[schema_name] = di.[schema_name] - AND di.key_column_names = ip.key_column_names - JOIN #IndexSanitySize ips ON ip.index_sanity_id = ips.index_sanity_id - AND ip.database_id = ips.database_id - AND ip.schema_name = ips.schema_name - /* WHERE clause limits to only @ThresholdMB or larger duplicate indexes when getting all databases or using PainRelief mode */ - WHERE ips.total_reserved_MB >= CASE WHEN (@GetAllDatabases = 1 OR @Mode = 0) THEN @ThresholdMB ELSE ips.total_reserved_MB END - AND ip.is_primary_key = 0 - ORDER BY ip.object_id, ip.key_column_names_with_sort_order - OPTION ( RECOMPILE ); + /*Parse process and input buffer XML*/ + SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + RAISERROR('Parse process and input buffer XML %s', 0, 1, @d) WITH NOWAIT; + SELECT q.event_date, + q.victim_id, + CONVERT(BIT, q.is_parallel) AS is_parallel, + q.deadlock_graph, + q.id, + q.database_id, + q.priority, + q.log_used, + q.wait_resource, + q.wait_time, + q.transaction_name, + q.last_tran_started, + q.last_batch_started, + q.last_batch_completed, + q.lock_mode, + q.transaction_count, + q.client_app, + q.host_name, + q.login_name, + q.isolation_level, + q.process_xml, + ISNULL(ca2.ib.query('.'), '') AS input_buffer + INTO #deadlock_process + FROM ( SELECT dd.deadlock_xml, + CONVERT(DATETIME2(7), SWITCHOFFSET(CONVERT(datetimeoffset, dd.event_date ), DATENAME(TzOffset, SYSDATETIMEOFFSET()))) AS event_date, + dd.victim_id, + CONVERT(tinyint, dd.is_parallel) + CONVERT(tinyint, dd.is_parallel_batch) AS is_parallel, + dd.deadlock_graph, + ca.dp.value('@id', 'NVARCHAR(256)') AS id, + ca.dp.value('@currentdb', 'BIGINT') AS database_id, + ca.dp.value('@priority', 'SMALLINT') AS priority, + ca.dp.value('@logused', 'BIGINT') AS log_used, + ca.dp.value('@waitresource', 'NVARCHAR(256)') AS wait_resource, + ca.dp.value('@waittime', 'BIGINT') AS wait_time, + ca.dp.value('@transactionname', 'NVARCHAR(256)') AS transaction_name, + ca.dp.value('@lasttranstarted', 'DATETIME2(7)') AS last_tran_started, + ca.dp.value('@lastbatchstarted', 'DATETIME2(7)') AS last_batch_started, + ca.dp.value('@lastbatchcompleted', 'DATETIME2(7)') AS last_batch_completed, + ca.dp.value('@lockMode', 'NVARCHAR(256)') AS lock_mode, + ca.dp.value('@trancount', 'BIGINT') AS transaction_count, + ca.dp.value('@clientapp', 'NVARCHAR(256)') AS client_app, + ca.dp.value('@hostname', 'NVARCHAR(256)') AS host_name, + ca.dp.value('@loginname', 'NVARCHAR(256)') AS login_name, + ca.dp.value('@isolationlevel', 'NVARCHAR(256)') AS isolation_level, + ISNULL(ca.dp.query('.'), '') AS process_xml + FROM ( SELECT d1.deadlock_xml, + d1.deadlock_xml.value('(event/@timestamp)[1]', 'DATETIME2') AS event_date, + d1.deadlock_xml.value('(//deadlock/victim-list/victimProcess/@id)[1]', 'NVARCHAR(256)') AS victim_id, + d1.deadlock_xml.exist('//deadlock/resource-list/exchangeEvent') AS is_parallel, + d1.deadlock_xml.exist('//deadlock/resource-list/SyncPoint') AS is_parallel_batch, + d1.deadlock_xml.query('/event/data/value/deadlock') AS deadlock_graph + FROM #deadlock_data AS d1 ) AS dd + CROSS APPLY dd.deadlock_xml.nodes('//deadlock/process-list/process') AS ca(dp) + WHERE (ca.dp.value('@currentdb', 'BIGINT') = DB_ID(@DatabaseName) OR @DatabaseName IS NULL) + AND (ca.dp.value('@clientapp', 'NVARCHAR(256)') = @AppName OR @AppName IS NULL) + AND (ca.dp.value('@hostname', 'NVARCHAR(256)') = @HostName OR @HostName IS NULL) + AND (ca.dp.value('@loginname', 'NVARCHAR(256)') = @LoginName OR @LoginName IS NULL) + ) AS q + CROSS APPLY q.deadlock_xml.nodes('//deadlock/process-list/process/inputbuf') AS ca2(ib) + OPTION ( RECOMPILE ); - RAISERROR('check_id 2: Keys w/ identical leading columns.', 0,1) WITH NOWAIT; - WITH borderline_duplicate_indexes - AS ( SELECT DISTINCT database_id, [object_id], first_key_column_name, key_column_names, - COUNT([object_id]) OVER ( PARTITION BY database_id, [object_id], first_key_column_name ) AS number_dupes - FROM #IndexSanity - WHERE index_type IN (1,2) /* Clustered, NC only*/ - AND is_hypothetical=0 - AND is_disabled=0 - AND is_primary_key = 0) - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 2 AS check_id, - ip.index_sanity_id, - 30 AS Priority, - 'Multiple Index Personalities' AS findings_group, - 'Borderline duplicate keys' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/duplicateindex' AS URL, - ip.db_schema_object_indexid AS details, - ip.index_definition, - ip.secret_columns, - ip.index_usage_summary, - ips.index_size_summary - FROM #IndexSanity AS ip - JOIN #IndexSanitySize ips ON ip.index_sanity_id = ips.index_sanity_id - WHERE EXISTS ( - SELECT di.[object_id] - FROM borderline_duplicate_indexes AS di - WHERE di.[object_id] = ip.[object_id] AND - di.database_id = ip.database_id AND - di.first_key_column_name = ip.first_key_column_name AND - di.key_column_names <> ip.key_column_names AND - di.number_dupes > 1 - ) - AND ip.is_primary_key = 0 - ORDER BY ip.[schema_name], ip.[object_name], ip.key_column_names, ip.include_column_names - OPTION ( RECOMPILE ); - ---------------------------------------- - --Aggressive Indexes: Check_id 10-19 - ---------------------------------------- + /*Parse execution stack XML*/ + SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + RAISERROR('Parse execution stack XML %s', 0, 1, @d) WITH NOWAIT; + SELECT DISTINCT + dp.id, + dp.event_date, + ca.dp.value('@procname', 'NVARCHAR(1000)') AS proc_name, + ca.dp.value('@sqlhandle', 'NVARCHAR(128)') AS sql_handle + INTO #deadlock_stack + FROM #deadlock_process AS dp + CROSS APPLY dp.process_xml.nodes('//executionStack/frame') AS ca(dp) + WHERE (ca.dp.value('@procname', 'NVARCHAR(256)') = @StoredProcName OR @StoredProcName IS NULL) + OPTION ( RECOMPILE ); - RAISERROR(N'check_id 11: Total lock wait time > 5 minutes (row + page) with long average waits', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 11 AS check_id, - i.index_sanity_id, - 70 AS Priority, - N'Aggressive ' - + CASE COALESCE((SELECT SUM(1) - FROM #IndexSanity iMe - INNER JOIN #IndexSanity iOthers - ON iMe.database_id = iOthers.database_id - AND iMe.object_id = iOthers.object_id - AND iOthers.index_id > 1 - WHERE i.index_sanity_id = iMe.index_sanity_id - AND iOthers.is_hypothetical = 0 - AND iOthers.is_disabled = 0 - ), 0) - WHEN 0 THEN N'Under-Indexing' - WHEN 1 THEN N'Under-Indexing' - WHEN 2 THEN N'Under-Indexing' - WHEN 3 THEN N'Under-Indexing' - WHEN 4 THEN N'Indexes' - WHEN 5 THEN N'Indexes' - WHEN 6 THEN N'Indexes' - WHEN 7 THEN N'Indexes' - WHEN 8 THEN N'Indexes' - WHEN 9 THEN N'Indexes' - ELSE N'Over-Indexing' - END AS findings_group, - N'Total lock wait time > 5 minutes (row + page) with long average waits' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/AggressiveIndexes' AS URL, - (i.db_schema_object_indexid + N': ' + - sz.index_lock_wait_summary + N' NC indexes on table: ') COLLATE DATABASE_DEFAULT + - CAST(COALESCE((SELECT SUM(1) - FROM #IndexSanity iMe - INNER JOIN #IndexSanity iOthers - ON iMe.database_id = iOthers.database_id - AND iMe.object_id = iOthers.object_id - AND iOthers.index_id > 1 - WHERE i.index_sanity_id = iMe.index_sanity_id - AND iOthers.is_hypothetical = 0 - AND iOthers.is_disabled = 0 - ), 0) - AS NVARCHAR(30)) AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - sz.index_size_summary - FROM #IndexSanity AS i - JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id - WHERE (total_row_lock_wait_in_ms + total_page_lock_wait_in_ms) > 300000 - AND (sz.avg_page_lock_wait_in_ms + sz.avg_row_lock_wait_in_ms) > 5000 - GROUP BY i.index_sanity_id, [database_name], i.db_schema_object_indexid, sz.index_lock_wait_summary, i.index_definition, i.secret_columns, i.index_usage_summary, sz.index_size_summary, sz.index_sanity_id - ORDER BY 4, [database_name], 8 - OPTION ( RECOMPILE ); - RAISERROR(N'check_id 12: Total lock wait time > 5 minutes (row + page) with short average waits', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 12 AS check_id, - i.index_sanity_id, - 70 AS Priority, - N'Aggressive ' - + CASE COALESCE((SELECT SUM(1) - FROM #IndexSanity iMe - INNER JOIN #IndexSanity iOthers - ON iMe.database_id = iOthers.database_id - AND iMe.object_id = iOthers.object_id - AND iOthers.index_id > 1 - WHERE i.index_sanity_id = iMe.index_sanity_id - AND iOthers.is_hypothetical = 0 - AND iOthers.is_disabled = 0 - ), 0) - WHEN 0 THEN N'Under-Indexing' - WHEN 1 THEN N'Under-Indexing' - WHEN 2 THEN N'Under-Indexing' - WHEN 3 THEN N'Under-Indexing' - WHEN 4 THEN N'Indexes' - WHEN 5 THEN N'Indexes' - WHEN 6 THEN N'Indexes' - WHEN 7 THEN N'Indexes' - WHEN 8 THEN N'Indexes' - WHEN 9 THEN N'Indexes' - ELSE N'Over-Indexing' - END AS findings_group, - N'Total lock wait time > 5 minutes (row + page) with short average waits' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/AggressiveIndexes' AS URL, - (i.db_schema_object_indexid + N': ' + - sz.index_lock_wait_summary + N' NC indexes on table: ') COLLATE DATABASE_DEFAULT + - CAST(COALESCE((SELECT SUM(1) - FROM #IndexSanity iMe - INNER JOIN #IndexSanity iOthers - ON iMe.database_id = iOthers.database_id - AND iMe.object_id = iOthers.object_id - AND iOthers.index_id > 1 - WHERE i.index_sanity_id = iMe.index_sanity_id - AND iOthers.is_hypothetical = 0 - AND iOthers.is_disabled = 0 - ),0) - AS NVARCHAR(30)) AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - sz.index_size_summary - FROM #IndexSanity AS i - JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id - WHERE (total_row_lock_wait_in_ms + total_page_lock_wait_in_ms) > 300000 - AND (sz.avg_page_lock_wait_in_ms + sz.avg_row_lock_wait_in_ms) < 5000 - GROUP BY i.index_sanity_id, [database_name], i.db_schema_object_indexid, sz.index_lock_wait_summary, i.index_definition, i.secret_columns, i.index_usage_summary, sz.index_size_summary, sz.index_sanity_id - ORDER BY 4, [database_name], 8 - OPTION ( RECOMPILE ); + /*Grab the full resource list*/ + SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + RAISERROR('Grab the full resource list %s', 0, 1, @d) WITH NOWAIT; + SELECT + CONVERT(DATETIME2(7), SWITCHOFFSET(CONVERT(datetimeoffset, dr.event_date), DATENAME(TzOffset, SYSDATETIMEOFFSET()))) AS event_date, + dr.victim_id, + dr.resource_xml + INTO #deadlock_resource + FROM + ( + SELECT dd.deadlock_xml.value('(event/@timestamp)[1]', 'DATETIME2') AS event_date, + dd.deadlock_xml.value('(//deadlock/victim-list/victimProcess/@id)[1]', 'NVARCHAR(256)') AS victim_id, + ISNULL(ca.dp.query('.'), '') AS resource_xml + FROM #deadlock_data AS dd + CROSS APPLY dd.deadlock_xml.nodes('//deadlock/resource-list') AS ca(dp) + ) AS dr + OPTION ( RECOMPILE ); - ---------------------------------------- - --Index Hoarder: Check_id 20-29 - ---------------------------------------- - RAISERROR(N'check_id 20: >= 10 NC indexes on any given table. Yes, 10 is an arbitrary number.', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 20 AS check_id, - MAX(i.index_sanity_id) AS index_sanity_id, - 10 AS Priority, - 'Index Hoarder' AS findings_group, - 'Many NC Indexes on a Single Table' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/IndexHoarder' AS URL, - CAST (COUNT(*) AS NVARCHAR(30)) + ' NC indexes on ' + i.db_schema_object_name AS details, - i.db_schema_object_name + ' (' + CAST (COUNT(*) AS NVARCHAR(30)) + ' indexes)' AS index_definition, - '' AS secret_columns, - REPLACE(CONVERT(NVARCHAR(30),CAST(SUM(total_reads) AS MONEY), 1), N'.00', N'') + N' reads (ALL); ' - + REPLACE(CONVERT(NVARCHAR(30),CAST(SUM(user_updates) AS MONEY), 1), N'.00', N'') + N' writes (ALL); ', - REPLACE(CONVERT(NVARCHAR(30),CAST(MAX(total_rows) AS MONEY), 1), N'.00', N'') + N' rows (MAX)' - + CASE WHEN SUM(total_reserved_MB) > 1024 THEN - N'; ' + CAST(CAST(SUM(total_reserved_MB)/1024. AS NUMERIC(29,1)) AS NVARCHAR(30)) + 'GB (ALL)' - WHEN SUM(total_reserved_MB) > 0 THEN - N'; ' + CAST(CAST(SUM(total_reserved_MB) AS NUMERIC(29,1)) AS NVARCHAR(30)) + 'MB (ALL)' - ELSE '' - END AS index_size_summary - FROM #IndexSanity i - JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id - WHERE index_id NOT IN ( 0, 1 ) - GROUP BY db_schema_object_name, [i].[database_name] - HAVING COUNT(*) >= 10 - ORDER BY i.db_schema_object_name DESC - OPTION ( RECOMPILE ); - RAISERROR(N'check_id 22: NC indexes with 0 reads. (Borderline) and >= 10,000 writes', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 22 AS check_id, - i.index_sanity_id, - 10 AS Priority, - N'Index Hoarder' AS findings_group, - N'Unused NC Index with High Writes' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/IndexHoarder' AS URL, - N'Reads: 0,' - + N' Writes: ' - + REPLACE(CONVERT(NVARCHAR(30), CAST((i.user_updates) AS MONEY), 1), N'.00', N'') - + N' on: ' - + i.db_schema_object_indexid - AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - sz.index_size_summary - FROM #IndexSanity AS i - JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.total_reads=0 - AND i.user_updates >= 10000 - 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 - AND @Filter <> 1 /* 1 = "ignore unused */ - ORDER BY i.db_schema_object_indexid - OPTION ( RECOMPILE ); + /*Parse object locks*/ + SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + RAISERROR('Parse object locks %s', 0, 1, @d) WITH NOWAIT; + SELECT DISTINCT + ca.event_date, + ca.database_id, + ca.object_name, + ca.lock_mode, + ca.index_name, + ca.associatedObjectId, + w.l.value('@id', 'NVARCHAR(256)') AS waiter_id, + w.l.value('@mode', 'NVARCHAR(256)') AS waiter_mode, + o.l.value('@id', 'NVARCHAR(256)') AS owner_id, + o.l.value('@mode', 'NVARCHAR(256)') AS owner_mode, + N'OBJECT' AS lock_type + INTO #deadlock_owner_waiter + FROM ( + SELECT dr.event_date, + ca.dr.value('@dbid', 'BIGINT') AS database_id, + ca.dr.value('@objectname', 'NVARCHAR(256)') AS object_name, + ca.dr.value('@mode', 'NVARCHAR(256)') AS lock_mode, + ca.dr.value('@indexname', 'NVARCHAR(256)') AS index_name, + ca.dr.value('@associatedObjectId', 'BIGINT') AS associatedObjectId, + ca.dr.query('.') AS dr + FROM #deadlock_resource AS dr + CROSS APPLY dr.resource_xml.nodes('//resource-list/objectlock') AS ca(dr) + ) AS ca + CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l) + CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l) + WHERE (ca.object_name = @ObjectName OR @ObjectName IS NULL) + OPTION ( RECOMPILE ); - RAISERROR(N'check_id 34: Filtered index definition columns not in index definition', 0,1) WITH NOWAIT; - - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 34 AS check_id, - i.index_sanity_id, - 80 AS Priority, - N'Abnormal Psychology' AS findings_group, - N'Filter Columns Not In Index Definition' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/IndexFeatures' AS URL, - N'The index ' - + QUOTENAME(i.index_name) - + N' on [' - + i.db_schema_object_name - + N'] has a filter on [' - + i.filter_definition - + N'] but is missing [' - + LTRIM(i.filter_columns_not_in_index) - + N'] from the index definition.' - AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - sz.index_size_summary - FROM #IndexSanity i - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.filter_columns_not_in_index IS NOT NULL - ORDER BY i.db_schema_object_indexid - OPTION ( RECOMPILE ); - - ---------------------------------------- - --Self Loathing Indexes : Check_id 40-49 - ---------------------------------------- - - RAISERROR(N'check_id 40: Fillfactor in nonclustered 80 percent or less', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 40 AS check_id, - i.index_sanity_id, - 100 AS Priority, - N'Self Loathing Indexes' AS findings_group, - N'Low Fill Factor on Nonclustered Index' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/SelfLoathing' AS URL, - CAST(fill_factor AS NVARCHAR(10)) + N'% fill factor on ' + db_schema_object_indexid + N'. '+ - CASE WHEN (last_user_update IS NULL OR user_updates < 1) - THEN N'No writes have been made.' - ELSE - N'Last write was ' + CONVERT(NVARCHAR(16),last_user_update,121) + N' and ' + - CAST(user_updates AS NVARCHAR(25)) + N' updates have been made.' - END - AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - sz.index_size_summary - FROM #IndexSanity AS i - JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id - WHERE index_id > 1 - AND fill_factor BETWEEN 1 AND 80 OPTION ( RECOMPILE ); - RAISERROR(N'check_id 40: Fillfactor in clustered 80 percent or less', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 40 AS check_id, - i.index_sanity_id, - 100 AS Priority, - N'Self Loathing Indexes' AS findings_group, - N'Low Fill Factor on Clustered Index' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/SelfLoathing' AS URL, - N'Fill factor on ' + db_schema_object_indexid + N' is ' + CAST(fill_factor AS NVARCHAR(10)) + N'%. '+ - CASE WHEN (last_user_update IS NULL OR user_updates < 1) - THEN N'No writes have been made.' - ELSE - N'Last write was ' + CONVERT(NVARCHAR(16),last_user_update,121) + N' and ' + - CAST(user_updates AS NVARCHAR(25)) + N' updates have been made.' - END - AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - sz.index_size_summary - FROM #IndexSanity AS i - JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id - WHERE index_id = 1 - AND fill_factor BETWEEN 1 AND 80 OPTION ( RECOMPILE ); + /*Parse page locks*/ + SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + RAISERROR('Parse page locks %s', 0, 1, @d) WITH NOWAIT; + INSERT #deadlock_owner_waiter WITH(TABLOCKX) + SELECT DISTINCT + ca.event_date, + ca.database_id, + ca.object_name, + ca.lock_mode, + ca.index_name, + ca.associatedObjectId, + w.l.value('@id', 'NVARCHAR(256)') AS waiter_id, + w.l.value('@mode', 'NVARCHAR(256)') AS waiter_mode, + o.l.value('@id', 'NVARCHAR(256)') AS owner_id, + o.l.value('@mode', 'NVARCHAR(256)') AS owner_mode, + N'PAGE' AS lock_type + FROM ( + SELECT dr.event_date, + ca.dr.value('@dbid', 'BIGINT') AS database_id, + ca.dr.value('@objectname', 'NVARCHAR(256)') AS object_name, + ca.dr.value('@mode', 'NVARCHAR(256)') AS lock_mode, + ca.dr.value('@indexname', 'NVARCHAR(256)') AS index_name, + ca.dr.value('@associatedObjectId', 'BIGINT') AS associatedObjectId, + ca.dr.query('.') AS dr + FROM #deadlock_resource AS dr + CROSS APPLY dr.resource_xml.nodes('//resource-list/pagelock') AS ca(dr) + ) AS ca + CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l) + CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l) + OPTION ( RECOMPILE ); - RAISERROR(N'check_id 43: Heaps with forwarded records', 0,1) WITH NOWAIT; - WITH heaps_cte - AS ( SELECT [object_id], - [database_id], - [schema_name], - SUM(forwarded_fetch_count) AS forwarded_fetch_count, - SUM(leaf_delete_count) AS leaf_delete_count - FROM #IndexPartitionSanity - GROUP BY [object_id], - [database_id], - [schema_name] - HAVING SUM(forwarded_fetch_count) > 0) - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 43 AS check_id, - i.index_sanity_id, - 100 AS Priority, - N'Self Loathing Indexes' AS findings_group, - N'Heaps with Forwarded Fetches' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/SelfLoathing' AS URL, - CASE WHEN h.forwarded_fetch_count >= 922337203685477 THEN '>= 922,337,203,685,477' - WHEN @DaysUptime < 1 THEN CAST(h.forwarded_fetch_count AS NVARCHAR(256)) + N' forwarded fetches against heap: ' + db_schema_object_indexid - ELSE REPLACE(CONVERT(NVARCHAR(256),CAST(CAST( - (h.forwarded_fetch_count /*/@DaysUptime */) - AS BIGINT) AS MONEY), 1), '.00', '') - END + N' forwarded fetches per day against heap: ' - + db_schema_object_indexid AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - sz.index_size_summary - FROM #IndexSanity i - JOIN heaps_cte h ON i.[object_id] = h.[object_id] - AND i.[database_id] = h.[database_id] - AND i.[schema_name] = h.[schema_name] - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.index_id = 0 - AND h.forwarded_fetch_count / @DaysUptime > 1000 - AND sz.total_reserved_MB >= CASE WHEN NOT (@GetAllDatabases = 1 OR @Mode = 4) THEN @ThresholdMB ELSE sz.total_reserved_MB END - OPTION ( RECOMPILE ); + /*Parse key locks*/ + SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + RAISERROR('Parse key locks %s', 0, 1, @d) WITH NOWAIT; + INSERT #deadlock_owner_waiter WITH(TABLOCKX) + SELECT DISTINCT + ca.event_date, + ca.database_id, + ca.object_name, + ca.lock_mode, + ca.index_name, + ca.associatedObjectId, + w.l.value('@id', 'NVARCHAR(256)') AS waiter_id, + w.l.value('@mode', 'NVARCHAR(256)') AS waiter_mode, + o.l.value('@id', 'NVARCHAR(256)') AS owner_id, + o.l.value('@mode', 'NVARCHAR(256)') AS owner_mode, + N'KEY' AS lock_type + FROM ( + SELECT dr.event_date, + ca.dr.value('@dbid', 'BIGINT') AS database_id, + ca.dr.value('@objectname', 'NVARCHAR(256)') AS object_name, + ca.dr.value('@mode', 'NVARCHAR(256)') AS lock_mode, + ca.dr.value('@indexname', 'NVARCHAR(256)') AS index_name, + ca.dr.value('@associatedObjectId', 'BIGINT') AS associatedObjectId, + ca.dr.query('.') AS dr + FROM #deadlock_resource AS dr + CROSS APPLY dr.resource_xml.nodes('//resource-list/keylock') AS ca(dr) + ) AS ca + CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l) + CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l) + OPTION ( RECOMPILE ); - RAISERROR(N'check_id 44: Large Heaps with reads or writes.', 0,1) WITH NOWAIT; - WITH heaps_cte - AS ( SELECT [object_id], - [database_id], - [schema_name], - SUM(forwarded_fetch_count) AS forwarded_fetch_count, - SUM(leaf_delete_count) AS leaf_delete_count - FROM #IndexPartitionSanity - GROUP BY [object_id], - [database_id], - [schema_name] - HAVING SUM(forwarded_fetch_count) > 0 - OR SUM(leaf_delete_count) > 0) - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 44 AS check_id, - i.index_sanity_id, - 100 AS Priority, - N'Self Loathing Indexes' AS findings_group, - N'Large Active Heap' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/SelfLoathing' AS URL, - N'Should this table be a heap? ' + db_schema_object_indexid AS details, - i.index_definition, - 'N/A' AS secret_columns, - i.index_usage_summary, - sz.index_size_summary - FROM #IndexSanity i - LEFT JOIN heaps_cte h ON i.[object_id] = h.[object_id] - AND i.[database_id] = h.[database_id] - AND i.[schema_name] = h.[schema_name] - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.index_id = 0 - AND (i.total_reads > 0 OR i.user_updates > 0) - AND sz.total_rows >= 100000 - AND h.[object_id] IS NULL /*don't duplicate the prior check.*/ - OPTION ( RECOMPILE ); - RAISERROR(N'check_id 45: Medium Heaps with reads or writes.', 0,1) WITH NOWAIT; - WITH heaps_cte - AS ( SELECT [object_id], - [database_id], - [schema_name], - SUM(forwarded_fetch_count) AS forwarded_fetch_count, - SUM(leaf_delete_count) AS leaf_delete_count - FROM #IndexPartitionSanity - GROUP BY [object_id], - [database_id], - [schema_name] - HAVING SUM(forwarded_fetch_count) > 0 - OR SUM(leaf_delete_count) > 0) - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 45 AS check_id, - i.index_sanity_id, - 100 AS Priority, - N'Self Loathing Indexes' AS findings_group, - N'Medium Active heap' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/SelfLoathing' AS URL, - N'Should this table be a heap? ' + db_schema_object_indexid AS details, - i.index_definition, - 'N/A' AS secret_columns, - i.index_usage_summary, - sz.index_size_summary - FROM #IndexSanity i - LEFT JOIN heaps_cte h ON i.[object_id] = h.[object_id] - AND i.[database_id] = h.[database_id] - AND i.[schema_name] = h.[schema_name] - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.index_id = 0 - AND - (i.total_reads > 0 OR i.user_updates > 0) - AND sz.total_rows >= 10000 AND sz.total_rows < 100000 - AND h.[object_id] IS NULL /*don't duplicate the prior check.*/ - OPTION ( RECOMPILE ); + /*Parse RID locks*/ + SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + RAISERROR('Parse RID locks %s', 0, 1, @d) WITH NOWAIT; + INSERT #deadlock_owner_waiter WITH(TABLOCKX) + SELECT DISTINCT + ca.event_date, + ca.database_id, + ca.object_name, + ca.lock_mode, + ca.index_name, + ca.associatedObjectId, + w.l.value('@id', 'NVARCHAR(256)') AS waiter_id, + w.l.value('@mode', 'NVARCHAR(256)') AS waiter_mode, + o.l.value('@id', 'NVARCHAR(256)') AS owner_id, + o.l.value('@mode', 'NVARCHAR(256)') AS owner_mode, + N'RID' AS lock_type + FROM ( + SELECT dr.event_date, + ca.dr.value('@dbid', 'BIGINT') AS database_id, + ca.dr.value('@objectname', 'NVARCHAR(256)') AS object_name, + ca.dr.value('@mode', 'NVARCHAR(256)') AS lock_mode, + ca.dr.value('@indexname', 'NVARCHAR(256)') AS index_name, + ca.dr.value('@associatedObjectId', 'BIGINT') AS associatedObjectId, + ca.dr.query('.') AS dr + FROM #deadlock_resource AS dr + CROSS APPLY dr.resource_xml.nodes('//resource-list/ridlock') AS ca(dr) + ) AS ca + CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l) + CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l) + OPTION ( RECOMPILE ); - RAISERROR(N'check_id 46: Small Heaps with reads or writes.', 0,1) WITH NOWAIT; - WITH heaps_cte - AS ( SELECT [object_id], - [database_id], - [schema_name], - SUM(forwarded_fetch_count) AS forwarded_fetch_count, - SUM(leaf_delete_count) AS leaf_delete_count - FROM #IndexPartitionSanity - GROUP BY [object_id], - [database_id], - [schema_name] - HAVING SUM(forwarded_fetch_count) > 0 - OR SUM(leaf_delete_count) > 0) - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 46 AS check_id, - i.index_sanity_id, - 100 AS Priority, - N'Self Loathing Indexes' AS findings_group, - N'Small Active heap' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/SelfLoathing' AS URL, - N'Should this table be a heap? ' + db_schema_object_indexid AS details, - i.index_definition, - 'N/A' AS secret_columns, - i.index_usage_summary, - sz.index_size_summary - FROM #IndexSanity i - LEFT JOIN heaps_cte h ON i.[object_id] = h.[object_id] - AND i.[database_id] = h.[database_id] - AND i.[schema_name] = h.[schema_name] - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.index_id = 0 - AND - (i.total_reads > 0 OR i.user_updates > 0) - AND sz.total_rows < 10000 - AND h.[object_id] IS NULL /*don't duplicate the prior check.*/ - OPTION ( RECOMPILE ); - RAISERROR(N'check_id 47: Heap with a Nonclustered Primary Key', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 47 AS check_id, - i.index_sanity_id, - 100 AS Priority, - N'Self Loathing Indexes' AS findings_group, - N'Heap with a Nonclustered Primary Key' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/SelfLoathing' AS URL, - db_schema_object_indexid + N' is a HEAP with a Nonclustered Primary Key' AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - sz.index_size_summary - FROM #IndexSanity i - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.index_type = 2 AND i.is_primary_key = 1 - AND EXISTS - ( - SELECT 1/0 - FROM #IndexSanity AS isa - WHERE i.database_id = isa.database_id - AND i.object_id = isa.object_id - AND isa.index_id = 0 - ) - OPTION ( RECOMPILE ); + /*Parse row group locks*/ + SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + RAISERROR('Parse row group locks %s', 0, 1, @d) WITH NOWAIT; + INSERT #deadlock_owner_waiter WITH(TABLOCKX) + SELECT DISTINCT + ca.event_date, + ca.database_id, + ca.object_name, + ca.lock_mode, + ca.index_name, + ca.associatedObjectId, + w.l.value('@id', 'NVARCHAR(256)') AS waiter_id, + w.l.value('@mode', 'NVARCHAR(256)') AS waiter_mode, + o.l.value('@id', 'NVARCHAR(256)') AS owner_id, + o.l.value('@mode', 'NVARCHAR(256)') AS owner_mode, + N'ROWGROUP' AS lock_type + FROM ( + SELECT dr.event_date, + ca.dr.value('@dbid', 'BIGINT') AS database_id, + ca.dr.value('@objectname', 'NVARCHAR(256)') AS object_name, + ca.dr.value('@mode', 'NVARCHAR(256)') AS lock_mode, + ca.dr.value('@indexname', 'NVARCHAR(256)') AS index_name, + ca.dr.value('@associatedObjectId', 'BIGINT') AS associatedObjectId, + ca.dr.query('.') AS dr + FROM #deadlock_resource AS dr + CROSS APPLY dr.resource_xml.nodes('//resource-list/rowgrouplock') AS ca(dr) + ) AS ca + CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l) + CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l) + OPTION ( RECOMPILE ); - RAISERROR(N'check_id 48: Nonclustered indexes with a bad read to write ratio', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 48 AS check_id, - i.index_sanity_id, - 100 AS Priority, - N'Index Hoarder' AS findings_group, - N'NC index with High Writes:Reads' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/IndexHoarder' AS URL, - N'Reads: ' - + REPLACE(CONVERT(NVARCHAR(30), CAST((i.total_reads) AS MONEY), 1), N'.00', N'') - + N' Writes: ' - + REPLACE(CONVERT(NVARCHAR(30), CAST((i.user_updates) AS MONEY), 1), N'.00', N'') - + N' on: ' - + i.db_schema_object_indexid AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - sz.index_size_summary - FROM #IndexSanity i - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.total_reads > 0 /*Not totally unused*/ - AND i.user_updates >= 10000 /*Decent write activity*/ - AND i.total_reads < 10000 - AND ((i.total_reads * 10) < i.user_updates) /*10x more writes than reads*/ - 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 - ORDER BY i.db_schema_object_indexid - OPTION ( RECOMPILE ); + UPDATE d + SET d.index_name = d.object_name + + '.HEAP' + FROM #deadlock_owner_waiter AS d + WHERE lock_type IN (N'HEAP', N'RID') + OPTION(RECOMPILE); - ---------------------------------------- - --Indexaphobia - --Missing indexes with value >= 5 million: : Check_id 50-59 - ---------------------------------------- - RAISERROR(N'check_id 50: Indexaphobia.', 0,1) WITH NOWAIT; - WITH index_size_cte - AS ( SELECT i.database_id, - i.schema_name, - i.[object_id], - MAX(i.index_sanity_id) AS index_sanity_id, - ISNULL(NULLIF(MAX(DATEDIFF(DAY, i.create_date, SYSDATETIME())), 0), 1) AS create_days, - ISNULL ( - CAST(SUM(CASE WHEN index_id NOT IN (0,1) THEN 1 ELSE 0 END) - AS NVARCHAR(30))+ N' NC indexes exist (' + - CASE WHEN SUM(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) > 1024 - THEN CAST(CAST(SUM(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END )/1024. + /*Parse parallel deadlocks*/ + SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + RAISERROR('Parse parallel deadlocks %s', 0, 1, @d) WITH NOWAIT; + SELECT DISTINCT + ca.id, + ca.event_date, + ca.wait_type, + ca.node_id, + ca.waiter_type, + ca.owner_activity, + ca.waiter_activity, + ca.merging, + ca.spilling, + ca.waiting_to_close, + w.l.value('@id', 'NVARCHAR(256)') AS waiter_id, + o.l.value('@id', 'NVARCHAR(256)') AS owner_id + INTO #deadlock_resource_parallel + FROM ( + SELECT dr.event_date, + ca.dr.value('@id', 'NVARCHAR(256)') AS id, + ca.dr.value('@WaitType', 'NVARCHAR(256)') AS wait_type, + ca.dr.value('@nodeId', 'BIGINT') AS node_id, + /* These columns are in 2017 CU5 ONLY */ + ca.dr.value('@waiterType', 'NVARCHAR(256)') AS waiter_type, + ca.dr.value('@ownerActivity', 'NVARCHAR(256)') AS owner_activity, + ca.dr.value('@waiterActivity', 'NVARCHAR(256)') AS waiter_activity, + ca.dr.value('@merging', 'NVARCHAR(256)') AS merging, + ca.dr.value('@spilling', 'NVARCHAR(256)') AS spilling, + ca.dr.value('@waitingToClose', 'NVARCHAR(256)') AS waiting_to_close, + /* */ + ca.dr.query('.') AS dr + FROM #deadlock_resource AS dr + CROSS APPLY dr.resource_xml.nodes('//resource-list/exchangeEvent') AS ca(dr) + ) AS ca + CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l) + CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l) + OPTION ( RECOMPILE ); - AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'GB); ' - ELSE CAST(SUM(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) - AS NVARCHAR(30)) + N'MB); ' - END + - CASE WHEN MAX(sz.[total_rows]) >= 922337203685477 THEN '>= 922,337,203,685,477' - ELSE REPLACE(CONVERT(NVARCHAR(30),CAST(MAX(sz.[total_rows]) AS MONEY), 1), '.00', '') - END + - + N' Estimated Rows;' - ,N'') AS index_size_summary - FROM #IndexSanity AS i - LEFT JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id AND i.database_id = sz.database_id - WHERE i.is_hypothetical = 0 - AND i.is_disabled = 0 - GROUP BY i.database_id, i.schema_name, i.[object_id]) - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - index_usage_summary, index_size_summary, create_tsql, more_info ) - - SELECT check_id, t.index_sanity_id, t.check_id, t.findings_group, t.finding, t.[Database Name], t.URL, t.details, t.[definition], - index_estimated_impact, t.index_size_summary, create_tsql, more_info - FROM - ( - SELECT ROW_NUMBER() OVER (ORDER BY magic_benefit_number DESC) AS rownum, - 50 AS check_id, - sz.index_sanity_id, - 40 AS Priority, - N'Indexaphobia' AS findings_group, - N'High Value Missing Index' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/Indexaphobia' AS URL, - mi.[statement] + - N' Est. benefit per day: ' + - CASE WHEN magic_benefit_number >= 922337203685477 THEN '>= 922,337,203,685,477' - ELSE REPLACE(CONVERT(NVARCHAR(256),CAST(CAST( - (magic_benefit_number/@DaysUptime) - AS BIGINT) AS MONEY), 1), '.00', '') - END AS details, - missing_index_details AS [definition], - index_estimated_impact, - sz.index_size_summary, - mi.create_tsql, - mi.more_info, - magic_benefit_number, - mi.is_low - FROM #MissingIndexes mi - LEFT JOIN index_size_cte sz ON mi.[object_id] = sz.object_id - AND mi.database_id = sz.database_id - AND mi.schema_name = sz.schema_name - /* Minimum benefit threshold = 100k/day of uptime OR since table creation date, whichever is lower*/ - WHERE @ShowAllMissingIndexRequests=1 - OR ( @Mode = 4 AND (magic_benefit_number / CASE WHEN sz.create_days < @DaysUptime THEN sz.create_days ELSE @DaysUptime END) >= 100000 ) - OR (magic_benefit_number / CASE WHEN sz.create_days < @DaysUptime THEN sz.create_days ELSE @DaysUptime END) >= 100000 - ) AS t - WHERE t.rownum <= CASE WHEN (@Mode <> 4) THEN 20 ELSE t.rownum END - ORDER BY magic_benefit_number DESC - OPTION ( RECOMPILE ); + /*Get rid of parallel noise*/ + SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + RAISERROR('Get rid of parallel noise %s', 0, 1, @d) WITH NOWAIT; + WITH c + AS + ( + SELECT *, ROW_NUMBER() OVER ( PARTITION BY drp.owner_id, drp.waiter_id ORDER BY drp.event_date ) AS rn + FROM #deadlock_resource_parallel AS drp + ) + DELETE FROM c + WHERE c.rn > 1 + OPTION ( RECOMPILE ); - RAISERROR(N'check_id 68: Identity columns within 30 percent of the end of range', 0,1) WITH NOWAIT; - -- Allowed Ranges: - --int -2,147,483,648 to 2,147,483,647 - --smallint -32,768 to 32,768 - --tinyint 0 to 255 + /*Get rid of nonsense*/ + SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + RAISERROR('Get rid of nonsense %s', 0, 1, @d) WITH NOWAIT; + DELETE dow + FROM #deadlock_owner_waiter AS dow + WHERE dow.owner_id = dow.waiter_id + OPTION ( RECOMPILE ); - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 68 AS check_id, - i.index_sanity_id, - 80 AS Priority, - N'Abnormal Psychology' AS findings_group, - N'Identity Column Within ' + - CAST (calc1.percent_remaining AS NVARCHAR(256)) - + N' Percent End of Range' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, - i.db_schema_object_name + N'.' + QUOTENAME(ic.column_name) - + N' is an identity with type ' + ic.system_type_name - + N', last value of ' - + ISNULL((CONVERT(NVARCHAR(256),CAST(ic.last_value AS DECIMAL(38,0)), 1)),N'NULL') - + N', seed of ' - + ISNULL((CONVERT(NVARCHAR(256),CAST(ic.seed_value AS DECIMAL(38,0)), 1)),N'NULL') - + N', increment of ' + CAST(ic.increment_value AS NVARCHAR(256)) - + N', and range of ' + - CASE ic.system_type_name WHEN 'int' THEN N'+/- 2,147,483,647' - WHEN 'smallint' THEN N'+/- 32,768' - WHEN 'tinyint' THEN N'0 to 255' - ELSE 'unknown' - END - AS details, - i.index_definition, - secret_columns, - ISNULL(i.index_usage_summary,''), - ISNULL(ip.index_size_summary,'') - FROM #IndexSanity i - JOIN #IndexColumns ic ON - i.object_id=ic.object_id - AND i.database_id = ic.database_id - AND i.schema_name = ic.schema_name - AND i.index_id IN (0,1) /* heaps and cx only */ - AND ic.is_identity=1 - AND ic.system_type_name IN ('tinyint', 'smallint', 'int') - JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id - CROSS APPLY ( - SELECT CAST(CASE WHEN ic.increment_value >= 0 - THEN - CASE ic.system_type_name - WHEN 'int' THEN (2147483647 - (ISNULL(ic.last_value,ic.seed_value) + ic.increment_value)) / 2147483647.*100 - WHEN 'smallint' THEN (32768 - (ISNULL(ic.last_value,ic.seed_value) + ic.increment_value)) / 32768.*100 - WHEN 'tinyint' THEN ( 255 - (ISNULL(ic.last_value,ic.seed_value) + ic.increment_value)) / 255.*100 - ELSE 999 - END - ELSE --ic.increment_value is negative - CASE ic.system_type_name - WHEN 'int' THEN ABS(-2147483647 - (ISNULL(ic.last_value,ic.seed_value) + ic.increment_value)) / 2147483647.*100 - WHEN 'smallint' THEN ABS(-32768 - (ISNULL(ic.last_value,ic.seed_value) + ic.increment_value)) / 32768.*100 - WHEN 'tinyint' THEN ABS( 0 - (ISNULL(ic.last_value,ic.seed_value) + ic.increment_value)) / 255.*100 - ELSE -1 - END - END AS NUMERIC(5,1)) AS percent_remaining - ) AS calc1 - WHERE i.index_id IN (1,0) - AND calc1.percent_remaining <= 30 - OPTION (RECOMPILE); + /*Add some nonsense*/ + ALTER TABLE #deadlock_process + ADD waiter_mode NVARCHAR(256), + owner_mode NVARCHAR(256), + is_victim AS CONVERT(BIT, CASE WHEN id = victim_id THEN 1 ELSE 0 END); + /*Update some nonsense*/ + SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + RAISERROR('Update some nonsense part 1 %s', 0, 1, @d) WITH NOWAIT; + UPDATE dp + SET dp.owner_mode = dow.owner_mode + FROM #deadlock_process AS dp + JOIN #deadlock_owner_waiter AS dow + ON dp.id = dow.owner_id + AND dp.event_date = dow.event_date + WHERE dp.is_victim = 0 + OPTION ( RECOMPILE ); - RAISERROR(N'check_id 72: Columnstore indexes with Trace Flag 834', 0,1) WITH NOWAIT; - IF EXISTS (SELECT * FROM #IndexSanity WHERE index_type IN (5,6)) - AND EXISTS (SELECT * FROM #TraceStatus WHERE TraceFlag = 834 AND status = 1) - BEGIN - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 72 AS check_id, - i.index_sanity_id, - 80 AS Priority, - N'Abnormal Psychology' AS findings_group, - 'Columnstore Indexes with Trace Flag 834' AS finding, - [database_name] AS [Database Name], - N'https://support.microsoft.com/en-us/kb/3210239' AS URL, - i.db_schema_object_indexid AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - ISNULL(sz.index_size_summary,'') AS index_size_summary - FROM #IndexSanity AS i - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.index_type IN (5,6) - OPTION ( RECOMPILE ); - END; - - ---------------------------------------- - --Statistics Info: Check_id 90-99 - ---------------------------------------- - - RAISERROR(N'check_id 90: Outdated 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 ) - SELECT 90 AS check_id, - 90 AS Priority, - 'Functioning Statistaholics' AS findings_group, - 'Statistics Not Updated Recently', - s.database_name, - 'https://www.brentozar.com/go/stats' AS URL, - 'Statistics on this table were last updated ' + - CASE WHEN s.last_statistics_update IS NULL THEN N' NEVER ' - ELSE CONVERT(NVARCHAR(20), s.last_statistics_update) + - ' have had ' + CONVERT(NVARCHAR(100), s.modification_counter) + - ' modifications in that time, which is ' + - CONVERT(NVARCHAR(100), s.percent_modifications) + - '% of the table.' - END 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.last_statistics_update <= CONVERT(DATETIME, GETDATE() - 30) - AND s.percent_modifications >= 10. - AND s.rows >= 10000 - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 91: Statistics with a low 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 91 AS check_id, - 90 AS Priority, - 'Functioning Statistaholics' AS findings_group, - 'Low Sampling Rates', - s.database_name, - 'https://www.brentozar.com/go/stats' AS URL, - 'Only ' + CONVERT(NVARCHAR(100), s.percent_sampled) + '% of the rows were sampled during the last statistics update. This may lead to poor cardinality estimates.' 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.rows BETWEEN 10000 AND 1000000 AND s.percent_sampled < 10) - OR (s.rows > 1000000 AND s.percent_sampled < 1) - OPTION ( RECOMPILE ); + SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + RAISERROR('Update some nonsense part 2 %s', 0, 1, @d) WITH NOWAIT; + UPDATE dp + SET dp.waiter_mode = dow.waiter_mode + FROM #deadlock_process AS dp + JOIN #deadlock_owner_waiter AS dow + ON dp.victim_id = dow.waiter_id + AND dp.event_date = dow.event_date + WHERE dp.is_victim = 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 ) - SELECT 92 AS check_id, - 90 AS Priority, - 'Functioning Statistaholics' AS findings_group, - 'Statistics With NO RECOMPUTE', - s.database_name, - 'https://www.brentozar.com/go/stats' AS URL, - 'The statistic ' + QUOTENAME(s.statistics_name) + ' is set to not recompute. This can be helpful if data is really skewed, but harmful if you expect automatic statistics updates.' 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.no_recompute = 1 - OPTION ( RECOMPILE ); + /*Get Agent Job and Step names*/ + SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + RAISERROR('Get Agent Job and Step names %s', 0, 1, @d) WITH NOWAIT; + SELECT *, + CONVERT(UNIQUEIDENTIFIER, + CONVERT(XML, '').value('xs:hexBinary(substring(sql:column("x.job_id"), 0) )', 'BINARY(16)') + ) AS job_id_guid + INTO #agent_job + FROM ( + SELECT dp.event_date, + dp.victim_id, + dp.id, + dp.database_id, + dp.client_app, + SUBSTRING(dp.client_app, + CHARINDEX('0x', dp.client_app) + LEN('0x'), + 32 + ) AS job_id, + SUBSTRING(dp.client_app, + CHARINDEX(': Step ', dp.client_app) + LEN(': Step '), + CHARINDEX(')', dp.client_app, CHARINDEX(': Step ', dp.client_app)) + - (CHARINDEX(': Step ', dp.client_app) + + LEN(': Step ')) + ) AS step_id + FROM #deadlock_process AS dp + WHERE dp.client_app LIKE 'SQLAgent - %' + ) AS x + 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 ) - SELECT 94 AS check_id, - 100 AS Priority, - 'Serial Forcer' AS findings_group, - 'Check Constraint with Scalar UDF' AS finding, - cc.database_name, - 'https://www.brentozar.com/go/computedscalar' AS URL, - 'The check constraint ' + QUOTENAME(cc.constraint_name) + ' on ' + QUOTENAME(cc.schema_name) + '.' + QUOTENAME(cc.table_name) + ' is based on ' + cc.definition - + '. That indicates it may reference a scalar function, or a CLR function with data access, which can cause all queries and maintenance to run serially.' AS details, - cc.column_definition, - 'N/A' AS secret_columns, - 'N/A' AS index_usage_summary, - 'N/A' AS index_size_summary - FROM #CheckConstraints AS cc - WHERE cc.is_function = 1 - OPTION ( RECOMPILE ); + ALTER TABLE #agent_job ADD job_name NVARCHAR(256), + step_name NVARCHAR(256); - RAISERROR(N'check_id 99: Computed Columns 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 ) - SELECT 99 AS check_id, - 100 AS Priority, - 'Serial Forcer' AS findings_group, - 'Computed Column with Scalar UDF' AS finding, - cc.database_name, - 'https://www.brentozar.com/go/serialudf' AS URL, - 'The computed column ' + QUOTENAME(cc.column_name) + ' on ' + QUOTENAME(cc.schema_name) + '.' + QUOTENAME(cc.table_name) + ' is based on ' + cc.definition - + '. That indicates it may reference a scalar function, or a CLR function with data access, which can cause all queries and maintenance to run serially.' AS details, - cc.column_definition, - 'N/A' AS secret_columns, - 'N/A' AS index_usage_summary, - 'N/A' AS index_size_summary - FROM #ComputedColumns AS cc - WHERE cc.is_function = 1 - OPTION ( RECOMPILE ); + IF SERVERPROPERTY('EngineEdition') NOT IN (5, 6) /* Azure SQL DB doesn't support querying jobs */ + AND NOT (LEFT(CAST(SERVERPROPERTY('ComputerNamePhysicalNetBIOS') AS VARCHAR(8000)), 8) = 'EC2AMAZ-' /* Neither does Amazon RDS Express Edition */ + AND LEFT(CAST(SERVERPROPERTY('MachineName') AS VARCHAR(8000)), 8) = 'EC2AMAZ-' + AND LEFT(CAST(SERVERPROPERTY('ServerName') AS VARCHAR(8000)), 8) = 'EC2AMAZ-' + AND db_id('rdsadmin') IS NOT NULL + AND EXISTS(SELECT * FROM master.sys.all_objects WHERE name IN ('rds_startup_tasks', 'rds_help_revlogin', 'rds_hexadecimal', 'rds_failover_tracking', 'rds_database_tracking', 'rds_track_change')) + ) + BEGIN + SET @StringToExecute = N'UPDATE aj + SET aj.job_name = j.name, + aj.step_name = s.step_name + FROM msdb.dbo.sysjobs AS j + JOIN msdb.dbo.sysjobsteps AS s + ON j.job_id = s.job_id + JOIN #agent_job AS aj + ON aj.job_id_guid = j.job_id + AND aj.step_id = s.step_id + OPTION ( RECOMPILE );'; + EXEC(@StringToExecute); + END + UPDATE dp + SET dp.client_app = + CASE WHEN dp.client_app LIKE N'SQLAgent - %' + THEN N'SQLAgent - Job: ' + + aj.job_name + + N' Step: ' + + aj.step_name + ELSE dp.client_app + END + FROM #deadlock_process AS dp + JOIN #agent_job AS aj + ON dp.event_date = aj.event_date + AND dp.victim_id = aj.victim_id + AND dp.id = aj.id + OPTION ( RECOMPILE ); + /*Get each and every table of all databases*/ + DECLARE @sysAssObjId AS TABLE (database_id bigint, partition_id bigint, schema_name varchar(255), table_name varchar(255)); + INSERT into @sysAssObjId EXECUTE sp_MSforeachdb + N'USE [?]; + SELECT DB_ID() as database_id, p.partition_id, s.name as schema_name, t.name as table_name + FROM sys.partitions p + LEFT JOIN sys.tables t ON t.object_id = p.object_id + LEFT JOIN sys.schemas s ON s.schema_id = t.schema_id + WHERE s.name is not NULL AND t.name is not NULL'; - END /* IF @Mode IN (0, 4) DIAGNOSE priorities 1-100 */ + /*Begin checks based on parsed values*/ + /*Check 1 is deadlocks by database*/ + SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + RAISERROR('Check 1 %s', 0, 1, @d) WITH NOWAIT; + INSERT #deadlock_findings WITH (TABLOCKX) + ( check_id, database_name, object_name, finding_group, finding ) + SELECT 1 AS check_id, + DB_NAME(dp.database_id) AS database_name, + '-' AS object_name, + 'Total database locks' AS finding_group, + 'This database had ' + + CONVERT(NVARCHAR(20), COUNT_BIG(DISTINCT dp.event_date)) + + ' deadlocks.' + FROM #deadlock_process AS dp + WHERE 1 = 1 + AND (DB_NAME(dp.database_id) = @DatabaseName OR @DatabaseName IS NULL) + AND (dp.event_date >= @StartDate OR @StartDate IS NULL) + AND (dp.event_date < @EndDate OR @EndDate IS NULL) + AND (dp.client_app = @AppName OR @AppName IS NULL) + AND (dp.host_name = @HostName OR @HostName IS NULL) + AND (dp.login_name = @LoginName OR @LoginName IS NULL) + GROUP BY DB_NAME(dp.database_id) + OPTION ( RECOMPILE ); + /*Check 2 is deadlocks by object*/ + SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + RAISERROR('Check 2 objects %s', 0, 1, @d) WITH NOWAIT; + INSERT #deadlock_findings WITH (TABLOCKX) + ( check_id, database_name, object_name, finding_group, finding ) + SELECT 2 AS check_id, + ISNULL(DB_NAME(dow.database_id), 'UNKNOWN') AS database_name, + ISNULL(dow.object_name, 'UNKNOWN') AS object_name, + 'Total object deadlocks' AS finding_group, + 'This object was involved in ' + + CONVERT(NVARCHAR(20), COUNT_BIG(DISTINCT dow.event_date)) + + ' deadlock(s).' + FROM #deadlock_owner_waiter AS dow + WHERE 1 = 1 + AND (DB_NAME(dow.database_id) = @DatabaseName OR @DatabaseName IS NULL) + AND (dow.event_date >= @StartDate OR @StartDate IS NULL) + AND (dow.event_date < @EndDate OR @EndDate IS NULL) + AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) + GROUP BY DB_NAME(dow.database_id), dow.object_name + OPTION ( RECOMPILE ); + /*Check 2 continuation, number of locks per index*/ + SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + RAISERROR('Check 2 indexes %s', 0, 1, @d) WITH NOWAIT; + INSERT #deadlock_findings WITH (TABLOCKX) + ( check_id, database_name, object_name, finding_group, finding ) + SELECT 2 AS check_id, + ISNULL(DB_NAME(dow.database_id), 'UNKNOWN') AS database_name, + dow.index_name AS index_name, + 'Total index deadlocks' AS finding_group, + 'This index was involved in ' + + CONVERT(NVARCHAR(20), COUNT_BIG(DISTINCT dow.event_date)) + + ' deadlock(s).' + FROM #deadlock_owner_waiter AS dow + WHERE 1 = 1 + AND (DB_NAME(dow.database_id) = @DatabaseName OR @DatabaseName IS NULL) + AND (dow.event_date >= @StartDate OR @StartDate IS NULL) + AND (dow.event_date < @EndDate OR @EndDate IS NULL) + AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) + AND dow.lock_type NOT IN (N'HEAP', N'RID') + AND dow.index_name is not null + GROUP BY DB_NAME(dow.database_id), dow.index_name + OPTION ( RECOMPILE ); + /*Check 2 continuation, number of locks per heap*/ + SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + RAISERROR('Check 2 heaps %s', 0, 1, @d) WITH NOWAIT; + INSERT #deadlock_findings WITH (TABLOCKX) + ( check_id, database_name, object_name, finding_group, finding ) + SELECT 2 AS check_id, + ISNULL(DB_NAME(dow.database_id), 'UNKNOWN') AS database_name, + dow.index_name AS index_name, + 'Total heap deadlocks' AS finding_group, + 'This heap was involved in ' + + CONVERT(NVARCHAR(20), COUNT_BIG(DISTINCT dow.event_date)) + + ' deadlock(s).' + FROM #deadlock_owner_waiter AS dow + WHERE 1 = 1 + AND (DB_NAME(dow.database_id) = @DatabaseName OR @DatabaseName IS NULL) + AND (dow.event_date >= @StartDate OR @StartDate IS NULL) + AND (dow.event_date < @EndDate OR @EndDate IS NULL) + AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) + AND dow.lock_type IN (N'HEAP', N'RID') + GROUP BY DB_NAME(dow.database_id), dow.index_name + OPTION ( RECOMPILE ); + + /*Check 3 looks for Serializable locking*/ + SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + RAISERROR('Check 3 %s', 0, 1, @d) WITH NOWAIT; + INSERT #deadlock_findings WITH (TABLOCKX) + ( check_id, database_name, object_name, finding_group, finding ) + SELECT 3 AS check_id, + DB_NAME(dp.database_id) AS database_name, + '-' AS object_name, + 'Serializable locking' AS finding_group, + 'This database has had ' + + CONVERT(NVARCHAR(20), COUNT_BIG(*)) + + ' instances of serializable deadlocks.' + AS finding + FROM #deadlock_process AS dp + WHERE dp.isolation_level LIKE 'serializable%' + AND (DB_NAME(dp.database_id) = @DatabaseName OR @DatabaseName IS NULL) + AND (dp.event_date >= @StartDate OR @StartDate IS NULL) + AND (dp.event_date < @EndDate OR @EndDate IS NULL) + AND (dp.client_app = @AppName OR @AppName IS NULL) + AND (dp.host_name = @HostName OR @HostName IS NULL) + AND (dp.login_name = @LoginName OR @LoginName IS NULL) + GROUP BY DB_NAME(dp.database_id) + OPTION ( RECOMPILE ); + /*Check 4 looks for Repeatable Read locking*/ + SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + RAISERROR('Check 4 %s', 0, 1, @d) WITH NOWAIT; + INSERT #deadlock_findings WITH (TABLOCKX) + ( check_id, database_name, object_name, finding_group, finding ) + SELECT 4 AS check_id, + DB_NAME(dp.database_id) AS database_name, + '-' AS object_name, + 'Repeatable Read locking' AS finding_group, + 'This database has had ' + + CONVERT(NVARCHAR(20), COUNT_BIG(*)) + + ' instances of repeatable read deadlocks.' + AS finding + FROM #deadlock_process AS dp + WHERE dp.isolation_level LIKE 'repeatable read%' + AND (DB_NAME(dp.database_id) = @DatabaseName OR @DatabaseName IS NULL) + AND (dp.event_date >= @StartDate OR @StartDate IS NULL) + AND (dp.event_date < @EndDate OR @EndDate IS NULL) + AND (dp.client_app = @AppName OR @AppName IS NULL) + AND (dp.host_name = @HostName OR @HostName IS NULL) + AND (dp.login_name = @LoginName OR @LoginName IS NULL) + GROUP BY DB_NAME(dp.database_id) + OPTION ( RECOMPILE ); + /*Check 5 breaks down app, host, and login information*/ + SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + RAISERROR('Check 5 %s', 0, 1, @d) WITH NOWAIT; + INSERT #deadlock_findings WITH (TABLOCKX) + ( check_id, database_name, object_name, finding_group, finding ) + SELECT 5 AS check_id, + DB_NAME(dp.database_id) AS database_name, + '-' AS object_name, + 'Login, App, and Host locking' AS finding_group, + 'This database has had ' + + CONVERT(NVARCHAR(20), COUNT_BIG(DISTINCT dp.event_date)) + + ' instances of deadlocks involving the login ' + + ISNULL(dp.login_name, 'UNKNOWN') + + ' from the application ' + + ISNULL(dp.client_app, 'UNKNOWN') + + ' on host ' + + ISNULL(dp.host_name, 'UNKNOWN') + AS finding + FROM #deadlock_process AS dp + WHERE 1 = 1 + AND (DB_NAME(dp.database_id) = @DatabaseName OR @DatabaseName IS NULL) + AND (dp.event_date >= @StartDate OR @StartDate IS NULL) + AND (dp.event_date < @EndDate OR @EndDate IS NULL) + AND (dp.client_app = @AppName OR @AppName IS NULL) + AND (dp.host_name = @HostName OR @HostName IS NULL) + AND (dp.login_name = @LoginName OR @LoginName IS NULL) + GROUP BY DB_NAME(dp.database_id), dp.login_name, dp.client_app, dp.host_name + OPTION ( RECOMPILE ); + /*Check 6 breaks down the types of locks (object, page, key, etc.)*/ + SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + RAISERROR('Check 6 %s', 0, 1, @d) WITH NOWAIT; + WITH lock_types AS ( + SELECT DB_NAME(dp.database_id) AS database_name, + dow.object_name, + SUBSTRING(dp.wait_resource, 1, CHARINDEX(':', dp.wait_resource) -1) AS lock, + CONVERT(NVARCHAR(20), COUNT_BIG(DISTINCT dp.id)) AS lock_count + FROM #deadlock_process AS dp + JOIN #deadlock_owner_waiter AS dow + ON dp.id = dow.owner_id + AND dp.event_date = dow.event_date + WHERE 1 = 1 + AND (DB_NAME(dp.database_id) = @DatabaseName OR @DatabaseName IS NULL) + AND (dp.event_date >= @StartDate OR @StartDate IS NULL) + AND (dp.event_date < @EndDate OR @EndDate IS NULL) + AND (dp.client_app = @AppName OR @AppName IS NULL) + AND (dp.host_name = @HostName OR @HostName IS NULL) + AND (dp.login_name = @LoginName OR @LoginName IS NULL) + AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) + AND dow.object_name IS NOT NULL + GROUP BY DB_NAME(dp.database_id), SUBSTRING(dp.wait_resource, 1, CHARINDEX(':', dp.wait_resource) - 1), dow.object_name + ) + INSERT #deadlock_findings WITH (TABLOCKX) + ( check_id, database_name, object_name, finding_group, finding ) + SELECT DISTINCT 6 AS check_id, + lt.database_name, + lt.object_name, + 'Types of locks by object' AS finding_group, + 'This object has had ' + + STUFF((SELECT DISTINCT N', ' + lt2.lock_count + ' ' + lt2.lock + FROM lock_types AS lt2 + WHERE lt2.database_name = lt.database_name + AND lt2.object_name = lt.object_name + FOR XML PATH(N''), TYPE).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 1, N'') + + ' locks' + FROM lock_types AS lt + OPTION ( RECOMPILE ); + /*Check 7 gives you more info queries for sp_BlitzCache & BlitzQueryStore*/ + SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + RAISERROR('Check 7 part 1 %s', 0, 1, @d) WITH NOWAIT; + WITH deadlock_stack AS ( + SELECT DISTINCT + ds.id, + ds.proc_name, + ds.event_date, + PARSENAME(ds.proc_name, 3) AS database_name, + PARSENAME(ds.proc_name, 2) AS schema_name, + PARSENAME(ds.proc_name, 1) AS proc_only_name, + '''' + STUFF((SELECT DISTINCT N',' + ds2.sql_handle + FROM #deadlock_stack AS ds2 + WHERE ds2.id = ds.id + AND ds2.event_date = ds.event_date + FOR XML PATH(N''), TYPE).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 1, N'') + '''' AS sql_handle_csv + FROM #deadlock_stack AS ds + GROUP BY PARSENAME(ds.proc_name, 3), + PARSENAME(ds.proc_name, 2), + PARSENAME(ds.proc_name, 1), + ds.id, + ds.proc_name, + ds.event_date + ) + INSERT #deadlock_findings WITH (TABLOCKX) + ( check_id, database_name, object_name, finding_group, finding ) + SELECT DISTINCT 7 AS check_id, + ISNULL(DB_NAME(dow.database_id), 'UNKNOWN') AS database_name, + ds.proc_name AS object_name, + 'More Info - Query' AS finding_group, + 'EXEC sp_BlitzCache ' + + CASE WHEN ds.proc_name = 'adhoc' + THEN ' @OnlySqlHandles = ' + ds.sql_handle_csv + ELSE '@StoredProcName = ' + + QUOTENAME(ds.proc_only_name, '''') + END + + ';' AS finding + FROM deadlock_stack AS ds + JOIN #deadlock_owner_waiter AS dow + ON dow.owner_id = ds.id + AND dow.event_date = ds.event_date + WHERE 1 = 1 + AND (DB_NAME(dow.database_id) = @DatabaseName OR @DatabaseName IS NULL) + AND (dow.event_date >= @StartDate OR @StartDate IS NULL) + AND (dow.event_date < @EndDate OR @EndDate IS NULL) + AND (dow.object_name = @StoredProcName OR @StoredProcName IS NULL) + OPTION ( RECOMPILE ); + IF @ProductVersionMajor >= 13 + BEGIN + SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + RAISERROR('Check 7 part 2 %s', 0, 1, @d) WITH NOWAIT; + WITH deadlock_stack AS ( + SELECT DISTINCT + ds.id, + ds.sql_handle, + ds.proc_name, + ds.event_date, + PARSENAME(ds.proc_name, 3) AS database_name, + PARSENAME(ds.proc_name, 2) AS schema_name, + PARSENAME(ds.proc_name, 1) AS proc_only_name + FROM #deadlock_stack AS ds + ) + INSERT #deadlock_findings WITH (TABLOCKX) + ( check_id, database_name, object_name, finding_group, finding ) + SELECT DISTINCT 7 AS check_id, + DB_NAME(dow.database_id) AS database_name, + ds.proc_name AS object_name, + 'More Info - Query' AS finding_group, + 'EXEC sp_BlitzQueryStore ' + + '@DatabaseName = ' + + QUOTENAME(ds.database_name, '''') + + ', ' + + '@StoredProcName = ' + + QUOTENAME(ds.proc_only_name, '''') + + ';' AS finding + FROM deadlock_stack AS ds + JOIN #deadlock_owner_waiter AS dow + ON dow.owner_id = ds.id + AND dow.event_date = ds.event_date + WHERE ds.proc_name <> 'adhoc' + AND (DB_NAME(dow.database_id) = @DatabaseName OR @DatabaseName IS NULL) + AND (dow.event_date >= @StartDate OR @StartDate IS NULL) + AND (dow.event_date < @EndDate OR @EndDate IS NULL) + AND (dow.object_name = @StoredProcName OR @StoredProcName IS NULL) + OPTION ( RECOMPILE ); + END; + + /*Check 8 gives you stored proc deadlock counts*/ + SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + RAISERROR('Check 8 %s', 0, 1, @d) WITH NOWAIT; + INSERT #deadlock_findings WITH (TABLOCKX) + ( check_id, database_name, object_name, finding_group, finding ) + SELECT 8 AS check_id, + DB_NAME(dp.database_id) AS database_name, + ds.proc_name, + 'Stored Procedure Deadlocks', + 'The stored procedure ' + + PARSENAME(ds.proc_name, 2) + + '.' + + PARSENAME(ds.proc_name, 1) + + ' has been involved in ' + + CONVERT(NVARCHAR(10), COUNT_BIG(DISTINCT ds.id)) + + ' deadlocks.' + FROM #deadlock_stack AS ds + JOIN #deadlock_process AS dp + ON dp.id = ds.id + AND ds.event_date = dp.event_date + WHERE ds.proc_name <> 'adhoc' + AND (ds.proc_name = @StoredProcName OR @StoredProcName IS NULL) + AND (DB_NAME(dp.database_id) = @DatabaseName OR @DatabaseName IS NULL) + AND (dp.event_date >= @StartDate OR @StartDate IS NULL) + AND (dp.event_date < @EndDate OR @EndDate IS NULL) + AND (dp.client_app = @AppName OR @AppName IS NULL) + AND (dp.host_name = @HostName OR @HostName IS NULL) + AND (dp.login_name = @LoginName OR @LoginName IS NULL) + GROUP BY DB_NAME(dp.database_id), ds.proc_name + OPTION(RECOMPILE); + /*Check 9 gives you more info queries for sp_BlitzIndex */ + SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + RAISERROR('Check 9 %s', 0, 1, @d) WITH NOWAIT; + WITH bi AS ( + SELECT DISTINCT + dow.object_name, + DB_NAME(dow.database_id) as database_name, + a.schema_name AS schema_name, + a.table_name AS table_name + FROM #deadlock_owner_waiter AS dow + LEFT JOIN @sysAssObjId a ON a.database_id=dow.database_id AND a.partition_id = dow.associatedObjectId + WHERE 1 = 1 + AND (DB_NAME(dow.database_id) = @DatabaseName OR @DatabaseName IS NULL) + AND (dow.event_date >= @StartDate OR @StartDate IS NULL) + AND (dow.event_date < @EndDate OR @EndDate IS NULL) + AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) + AND dow.object_name IS NOT NULL + ) + INSERT #deadlock_findings WITH (TABLOCKX) + ( check_id, database_name, object_name, finding_group, finding ) + SELECT 9 AS check_id, + bi.database_name, + bi.object_name, + 'More Info - Table' AS finding_group, + 'EXEC sp_BlitzIndex ' + + '@DatabaseName = ' + QUOTENAME(bi.database_name, '''') + + ', @SchemaName = ' + QUOTENAME(bi.schema_name, '''') + + ', @TableName = ' + QUOTENAME(bi.table_name, '''') + + ';' AS finding + FROM bi + OPTION ( RECOMPILE ); + /*Check 10 gets total deadlock wait time per object*/ + SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + RAISERROR('Check 10 %s', 0, 1, @d) WITH NOWAIT; + WITH chopsuey AS ( + SELECT DISTINCT + PARSENAME(dow.object_name, 3) AS database_name, + dow.object_name, + CONVERT(VARCHAR(10), (SUM(DISTINCT dp.wait_time) / 1000) / 86400) AS wait_days, + CONVERT(VARCHAR(20), DATEADD(SECOND, (SUM(DISTINCT dp.wait_time) / 1000), 0), 108) AS wait_time_hms + FROM #deadlock_owner_waiter AS dow + JOIN #deadlock_process AS dp + ON (dp.id = dow.owner_id OR dp.victim_id = dow.waiter_id) + AND dp.event_date = dow.event_date + WHERE 1 = 1 + AND (DB_NAME(dow.database_id) = @DatabaseName OR @DatabaseName IS NULL) + AND (dow.event_date >= @StartDate OR @StartDate IS NULL) + AND (dow.event_date < @EndDate OR @EndDate IS NULL) + AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) + AND (dp.client_app = @AppName OR @AppName IS NULL) + AND (dp.host_name = @HostName OR @HostName IS NULL) + AND (dp.login_name = @LoginName OR @LoginName IS NULL) + GROUP BY PARSENAME(dow.object_name, 3), dow.object_name + ) + INSERT #deadlock_findings WITH (TABLOCKX) + ( check_id, database_name, object_name, finding_group, finding ) + SELECT 10 AS check_id, + cs.database_name, + cs.object_name, + 'Total object deadlock wait time' AS finding_group, + 'This object has had ' + + CONVERT(VARCHAR(10), cs.wait_days) + + ':' + CONVERT(VARCHAR(20), cs.wait_time_hms, 108) + + ' [d/h/m/s] of deadlock wait time.' AS finding + FROM chopsuey AS cs + WHERE cs.object_name IS NOT NULL + OPTION ( RECOMPILE ); + /*Check 11 gets total deadlock wait time per database*/ + SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + RAISERROR('Check 11 %s', 0, 1, @d) WITH NOWAIT; + WITH wait_time AS ( + SELECT DB_NAME(dp.database_id) AS database_name, + SUM(CONVERT(BIGINT, dp.wait_time)) AS total_wait_time_ms + FROM #deadlock_process AS dp + WHERE 1 = 1 + AND (DB_NAME(dp.database_id) = @DatabaseName OR @DatabaseName IS NULL) + AND (dp.event_date >= @StartDate OR @StartDate IS NULL) + AND (dp.event_date < @EndDate OR @EndDate IS NULL) + AND (dp.client_app = @AppName OR @AppName IS NULL) + AND (dp.host_name = @HostName OR @HostName IS NULL) + AND (dp.login_name = @LoginName OR @LoginName IS NULL) + GROUP BY DB_NAME(dp.database_id) + ) + INSERT #deadlock_findings WITH (TABLOCKX) + ( check_id, database_name, object_name, finding_group, finding ) + SELECT 11 AS check_id, + wt.database_name, + '-' AS object_name, + 'Total database deadlock wait time' AS finding_group, + 'This database has had ' + + CONVERT(VARCHAR(10), (SUM(DISTINCT wt.total_wait_time_ms) / 1000) / 86400) + + ':' + CONVERT(VARCHAR(20), DATEADD(SECOND, (SUM(DISTINCT wt.total_wait_time_ms) / 1000), 0), 108) + + ' [d/h/m/s] of deadlock wait time.' + FROM wait_time AS wt + GROUP BY wt.database_name + OPTION ( RECOMPILE ); + /*Check 12 gets total deadlock wait time for SQL Agent*/ + SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + RAISERROR('Check 12 %s', 0, 1, @d) WITH NOWAIT; + INSERT #deadlock_findings WITH (TABLOCKX) + ( check_id, database_name, object_name, finding_group, finding ) + SELECT 12, + DB_NAME(aj.database_id), + 'SQLAgent - Job: ' + + aj.job_name + + ' Step: ' + + aj.step_name, + 'Agent Job Deadlocks', + RTRIM(COUNT(*)) + ' deadlocks from this Agent Job and Step' + FROM #agent_job AS aj + GROUP BY DB_NAME(aj.database_id), aj.job_name, aj.step_name + OPTION ( RECOMPILE ); + /*Check 13 is total parallel deadlocks*/ + SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + RAISERROR('Check 13 %s', 0, 1, @d) WITH NOWAIT; + INSERT #deadlock_findings WITH (TABLOCKX) + ( check_id, database_name, object_name, finding_group, finding ) + SELECT 13 AS check_id, + N'-' AS database_name, + '-' AS object_name, + 'Total parallel deadlocks' AS finding_group, + 'There have been ' + + CONVERT(NVARCHAR(20), COUNT_BIG(DISTINCT drp.event_date)) + + ' parallel deadlocks.' + FROM #deadlock_resource_parallel AS drp + WHERE 1 = 1 + HAVING COUNT_BIG(DISTINCT drp.event_date) > 0 + OPTION ( RECOMPILE ); + /*Thank you goodnight*/ + INSERT #deadlock_findings WITH (TABLOCKX) + ( check_id, database_name, object_name, finding_group, finding ) + VALUES ( -1, + N'sp_BlitzLock ' + CAST(CONVERT(DATETIME, @VersionDate, 102) AS VARCHAR(100)), + N'SQL Server First Responder Kit', + N'http://FirstResponderKit.org/', + N'To get help or add your own contributions, join us at http://FirstResponderKit.org.'); - IF @Mode = 4 /* DIAGNOSE*/ - BEGIN; - RAISERROR(N'@Mode=4, running rules for priorities 101+.', 0,1) WITH NOWAIT; + - RAISERROR(N'check_id 21: More Than 5 Percent NC Indexes Are Unused', 0,1) WITH NOWAIT; - DECLARE @percent_NC_indexes_unused NUMERIC(29,1); - DECLARE @NC_indexes_unused_reserved_MB NUMERIC(29,1); - SELECT @percent_NC_indexes_unused = ( 100.00 * SUM(CASE - WHEN total_reads = 0 - THEN 1 - ELSE 0 - END) ) / COUNT(*), - @NC_indexes_unused_reserved_MB = SUM(CASE - WHEN total_reads = 0 - THEN sz.total_reserved_MB - ELSE 0 - END) - FROM #IndexSanity i - 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()) - 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, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 21 AS check_id, - MAX(i.index_sanity_id) AS index_sanity_id, - 150 AS Priority, - N'Index Hoarder' AS findings_group, - N'More Than 5 Percent NC Indexes Are Unused' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/IndexHoarder' AS URL, - CAST (@percent_NC_indexes_unused AS NVARCHAR(30)) + N' percent NC indexes (' + CAST(COUNT(*) AS NVARCHAR(10)) + N') unused. ' + - N'These take up ' + CAST (@NC_indexes_unused_reserved_MB AS NVARCHAR(30)) + N'MB of space.' AS details, - i.database_name + ' (' + CAST (COUNT(*) AS NVARCHAR(30)) + N' indexes)' AS index_definition, - '' AS secret_columns, - CAST(SUM(total_reads) AS NVARCHAR(256)) + N' reads (ALL); ' - + CAST(SUM([user_updates]) AS NVARCHAR(256)) + N' writes (ALL)' AS index_usage_summary, - - REPLACE(CONVERT(NVARCHAR(30),CAST(MAX([total_rows]) AS MONEY), 1), '.00', '') + N' rows (MAX)' - + CASE WHEN SUM(total_reserved_MB) > 1024 THEN - N'; ' + CAST(CAST(SUM(total_reserved_MB)/1024. AS NUMERIC(29,1)) AS NVARCHAR(30)) + 'GB (ALL)' - WHEN SUM(total_reserved_MB) > 0 THEN - N'; ' + CAST(CAST(SUM(total_reserved_MB) AS NUMERIC(29,1)) AS NVARCHAR(30)) + 'MB (ALL)' - ELSE '' - END AS index_size_summary - FROM #IndexSanity i - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - 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()) - GROUP BY i.database_name - OPTION ( RECOMPILE ); + /*Results*/ + /*Break in case of emergency*/ + --CREATE CLUSTERED INDEX cx_whatever ON #deadlock_process (event_date, id); + --CREATE CLUSTERED INDEX cx_whatever ON #deadlock_resource_parallel (event_date, owner_id); + --CREATE CLUSTERED INDEX cx_whatever ON #deadlock_owner_waiter (event_date, owner_id, waiter_id); + IF(@OutputDatabaseCheck = 0) + BEGIN + + SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + RAISERROR('Results 1 %s', 0, 1, @d) WITH NOWAIT; + WITH deadlocks + AS ( SELECT N'Regular Deadlock' AS deadlock_type, + dp.event_date, + dp.id, + dp.victim_id, + dp.database_id, + dp.priority, + dp.log_used, + dp.wait_resource COLLATE DATABASE_DEFAULT AS wait_resource, + CONVERT( + XML, + STUFF(( SELECT DISTINCT NCHAR(10) + + N' ' + + ISNULL(c.object_name, N'') + + N' ' COLLATE DATABASE_DEFAULT AS object_name + FROM #deadlock_owner_waiter AS c + WHERE ( dp.id = c.owner_id + OR dp.victim_id = c.waiter_id ) + AND CONVERT(DATE, dp.event_date) = CONVERT(DATE, c.event_date) + FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(4000)'), + 1, 1, N'')) AS object_names, + dp.wait_time, + dp.transaction_name, + dp.last_tran_started, + dp.last_batch_started, + dp.last_batch_completed, + dp.lock_mode, + dp.transaction_count, + dp.client_app, + dp.host_name, + dp.login_name, + dp.isolation_level, + dp.process_xml.value('(//process/inputbuf/text())[1]', 'NVARCHAR(MAX)') AS inputbuf, + ROW_NUMBER() OVER ( PARTITION BY dp.event_date, dp.id ORDER BY dp.event_date ) AS dn, + DENSE_RANK() OVER ( ORDER BY dp.event_date ) AS en, + ROW_NUMBER() OVER ( PARTITION BY dp.event_date ORDER BY dp.event_date ) -1 AS qn, + dp.is_victim, + ISNULL(dp.owner_mode, '-') AS owner_mode, + NULL AS owner_waiter_type, + NULL AS owner_activity, + NULL AS owner_waiter_activity, + NULL AS owner_merging, + NULL AS owner_spilling, + NULL AS owner_waiting_to_close, + ISNULL(dp.waiter_mode, '-') AS waiter_mode, + NULL AS waiter_waiter_type, + NULL AS waiter_owner_activity, + NULL AS waiter_waiter_activity, + NULL AS waiter_merging, + NULL AS waiter_spilling, + NULL AS waiter_waiting_to_close, + dp.deadlock_graph + FROM #deadlock_process AS dp + WHERE dp.victim_id IS NOT NULL + AND dp.is_parallel = 0 + + UNION ALL + + SELECT N'Parallel Deadlock' AS deadlock_type, + dp.event_date, + dp.id, + dp.victim_id, + dp.database_id, + dp.priority, + dp.log_used, + dp.wait_resource COLLATE DATABASE_DEFAULT, + CASE WHEN @ExportToExcel = 0 THEN + CONVERT( + XML, + STUFF(( SELECT DISTINCT NCHAR(10) + + N' ' + + ISNULL(c.object_name, N'') + + N' ' COLLATE DATABASE_DEFAULT AS object_name + FROM #deadlock_owner_waiter AS c + WHERE ( dp.id = c.owner_id + OR dp.victim_id = c.waiter_id ) + AND CONVERT(DATE, dp.event_date) = CONVERT(DATE, c.event_date) + FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(4000)'), + 1, 1, N'')) + ELSE NULL END AS object_names, + dp.wait_time, + dp.transaction_name, + dp.last_tran_started, + dp.last_batch_started, + dp.last_batch_completed, + dp.lock_mode, + dp.transaction_count, + dp.client_app, + dp.host_name, + dp.login_name, + dp.isolation_level, + dp.process_xml.value('(//process/inputbuf/text())[1]', 'NVARCHAR(MAX)') AS inputbuf, + ROW_NUMBER() OVER ( PARTITION BY dp.event_date, dp.id ORDER BY dp.event_date ) AS dn, + DENSE_RANK() OVER ( ORDER BY dp.event_date ) AS en, + ROW_NUMBER() OVER ( PARTITION BY dp.event_date ORDER BY dp.event_date ) -1 AS qn, + 1 AS is_victim, + cao.wait_type COLLATE DATABASE_DEFAULT AS owner_mode, + cao.waiter_type AS owner_waiter_type, + cao.owner_activity AS owner_activity, + cao.waiter_activity AS owner_waiter_activity, + cao.merging AS owner_merging, + cao.spilling AS owner_spilling, + cao.waiting_to_close AS owner_waiting_to_close, + caw.wait_type COLLATE DATABASE_DEFAULT AS waiter_mode, + caw.waiter_type AS waiter_waiter_type, + caw.owner_activity AS waiter_owner_activity, + caw.waiter_activity AS waiter_waiter_activity, + caw.merging AS waiter_merging, + caw.spilling AS waiter_spilling, + caw.waiting_to_close AS waiter_waiting_to_close, + dp.deadlock_graph + FROM #deadlock_process AS dp + CROSS APPLY (SELECT TOP 1 * FROM #deadlock_resource_parallel AS drp WHERE drp.owner_id = dp.id AND drp.wait_type = 'e_waitPipeNewRow' ORDER BY drp.event_date) AS cao + CROSS APPLY (SELECT TOP 1 * FROM #deadlock_resource_parallel AS drp WHERE drp.owner_id = dp.id AND drp.wait_type = 'e_waitPipeGetRow' ORDER BY drp.event_date) AS caw + WHERE dp.is_parallel = 1 ) + insert into DeadLockTbl ( + ServerName, + deadlock_type, + event_date, + database_name, + deadlock_group, + query, + object_names, + isolation_level, + owner_mode, + waiter_mode, + transaction_count, + login_name, + host_name, + client_app, + wait_time, + priority, + log_used, + last_tran_started, + last_batch_started, + last_batch_completed, + transaction_name, + owner_waiter_type, + owner_activity, + owner_waiter_activity, + owner_merging, + owner_spilling, + owner_waiting_to_close, + waiter_waiter_type, + waiter_owner_activity, + waiter_waiter_activity, + waiter_merging, + waiter_spilling, + waiter_waiting_to_close, + deadlock_graph + ) + SELECT @ServerName, + d.deadlock_type, + d.event_date, + DB_NAME(d.database_id) AS database_name, + 'Deadlock #' + + CONVERT(NVARCHAR(10), d.en) + + ', Query #' + + CASE WHEN d.qn = 0 THEN N'1' ELSE CONVERT(NVARCHAR(10), d.qn) END + + CASE WHEN d.is_victim = 1 THEN ' - VICTIM' ELSE '' END + AS deadlock_group, + CONVERT(XML, N'') AS query, + d.object_names, + d.isolation_level, + d.owner_mode, + d.waiter_mode, + d.transaction_count, + d.login_name, + d.host_name, + d.client_app, + d.wait_time, + d.priority, + d.log_used, + d.last_tran_started, + d.last_batch_started, + d.last_batch_completed, + d.transaction_name, + /*These columns will be NULL for regular (non-parallel) deadlocks*/ + d.owner_waiter_type, + d.owner_activity, + d.owner_waiter_activity, + d.owner_merging, + d.owner_spilling, + d.owner_waiting_to_close, + d.waiter_waiter_type, + d.waiter_owner_activity, + d.waiter_waiter_activity, + d.waiter_merging, + d.waiter_spilling, + d.waiter_waiting_to_close, + d.deadlock_graph + FROM deadlocks AS d + WHERE d.dn = 1 + AND (is_victim = @VictimsOnly OR @VictimsOnly = 0) + AND d.en < CASE WHEN d.deadlock_type = N'Parallel Deadlock' THEN 2 ELSE 2147483647 END + AND (DB_NAME(d.database_id) = @DatabaseName OR @DatabaseName IS NULL) + AND (d.event_date >= @StartDate OR @StartDate IS NULL) + AND (d.event_date < @EndDate OR @EndDate IS NULL) + AND (CONVERT(NVARCHAR(MAX), d.object_names) LIKE '%' + @ObjectName + '%' OR @ObjectName IS NULL) + AND (d.client_app = @AppName OR @AppName IS NULL) + AND (d.host_name = @HostName OR @HostName IS NULL) + AND (d.login_name = @LoginName OR @LoginName IS NULL) + ORDER BY d.event_date, is_victim DESC + OPTION ( RECOMPILE ); + + drop SYNONYM DeadLockTbl; --done insert into blitzlock table going to insert into findings table first create synonym. - RAISERROR(N'check_id 23: Indexes with 7 or more columns. (Borderline)', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 23 AS check_id, - i.index_sanity_id, - 150 AS Priority, - N'Index Hoarder' AS findings_group, - N'Borderline: Wide Indexes (7 or More Columns)' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/IndexHoarder' AS URL, - CAST(count_key_columns + count_included_columns AS NVARCHAR(10)) + ' columns on ' - + i.db_schema_object_indexid AS details, i.index_definition, - i.secret_columns, - i.index_usage_summary, - sz.index_size_summary - FROM #IndexSanity AS i - JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id - WHERE ( count_key_columns + count_included_columns ) >= 7 - OPTION ( RECOMPILE ); + -- RAISERROR('att deadlock findings', 0, 1) WITH NOWAIT; + - RAISERROR(N'check_id 24: Wide clustered indexes (> 3 columns or > 16 bytes).', 0,1) WITH NOWAIT; - WITH count_columns AS ( - SELECT database_id, [object_id], - SUM(CASE max_length WHEN -1 THEN 0 ELSE max_length END) AS sum_max_length - FROM #IndexColumns ic - WHERE index_id IN (1,0) /*Heap or clustered only*/ - AND key_ordinal > 0 - GROUP BY database_id, object_id - ) - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 24 AS check_id, - i.index_sanity_id, - 150 AS Priority, - N'Index Hoarder' AS findings_group, - N'Wide Clustered Index (> 3 columns OR > 16 bytes)' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/IndexHoarder' AS URL, - CAST (i.count_key_columns AS NVARCHAR(10)) + N' columns with potential size of ' - + CAST(cc.sum_max_length AS NVARCHAR(10)) - + N' bytes in clustered index:' + i.db_schema_object_name - + N'. ' + - (SELECT CAST(COUNT(*) AS NVARCHAR(23)) - FROM #IndexSanity i2 - WHERE i2.[object_id]=i.[object_id] - AND i2.database_id = i.database_id - AND i2.index_id <> 1 - AND i2.is_disabled=0 - AND i2.is_hypothetical=0) - + N' NC indexes on the table.' - AS details, - i.index_definition, - secret_columns, - i.index_usage_summary, - ip.index_size_summary - FROM #IndexSanity i - JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id - JOIN count_columns AS cc ON i.[object_id]=cc.[object_id] - AND i.database_id = cc.database_id - WHERE index_id = 1 /* clustered only */ - AND - (count_key_columns > 3 /*More than three key columns.*/ - OR cc.sum_max_length > 16 /*More than 16 bytes in key */) - AND i.is_CX_columnstore = 0 - ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); + SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + RAISERROR('Findings %s', 0, 1, @d) WITH NOWAIT; - RAISERROR(N'check_id 25: Addicted to nullable columns.', 0,1) WITH NOWAIT; - WITH count_columns AS ( - SELECT [object_id], - [database_id], - [schema_name], - SUM(CASE is_nullable WHEN 1 THEN 0 ELSE 1 END) AS non_nullable_columns, - COUNT(*) AS total_columns - FROM #IndexColumns ic - WHERE index_id IN (1,0) /*Heap or clustered only*/ - GROUP BY [object_id], - [database_id], - [schema_name] - ) - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 25 AS check_id, - i.index_sanity_id, - 200 AS Priority, - N'Index Hoarder' AS findings_group, - N'Addicted to Nulls' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/IndexHoarder' AS URL, - i.db_schema_object_name - + N' allows null in ' + CAST((total_columns-non_nullable_columns) AS NVARCHAR(10)) - + N' of ' + CAST(total_columns AS NVARCHAR(10)) - + N' columns.' AS details, - i.index_definition, - secret_columns, - ISNULL(i.index_usage_summary,''), - ISNULL(ip.index_size_summary,'') - FROM #IndexSanity i - JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id - JOIN count_columns AS cc ON i.[object_id]=cc.[object_id] - AND cc.database_id = ip.database_id - AND cc.[schema_name] = ip.[schema_name] - WHERE i.index_id IN (1,0) - AND cc.non_nullable_columns < 2 - AND cc.total_columns > 3 - ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); + Insert into DeadlockFindings (ServerName,check_id,database_name,object_name,finding_group,finding) + SELECT @ServerName,df.check_id, df.database_name, df.object_name, df.finding_group, df.finding + FROM #deadlock_findings AS df + ORDER BY df.check_id + OPTION ( RECOMPILE ); - RAISERROR(N'check_id 26: Wide tables (35+ cols or > 2000 non-LOB bytes).', 0,1) WITH NOWAIT; - WITH count_columns AS ( - SELECT [object_id], - [database_id], - [schema_name], - SUM(CASE max_length WHEN -1 THEN 1 ELSE 0 END) AS count_lob_columns, - SUM(CASE max_length WHEN -1 THEN 0 ELSE max_length END) AS sum_max_length, - COUNT(*) AS total_columns - FROM #IndexColumns ic - WHERE index_id IN (1,0) /*Heap or clustered only*/ - GROUP BY [object_id], - [database_id], - [schema_name] - ) - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 26 AS check_id, - i.index_sanity_id, - 150 AS Priority, - N'Index Hoarder' AS findings_group, - N'Wide Tables: 35+ cols or > 2000 non-LOB bytes' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/IndexHoarder' AS URL, - i.db_schema_object_name - + N' has ' + CAST((total_columns) AS NVARCHAR(10)) - + N' total columns with a max possible width of ' + CAST(sum_max_length AS NVARCHAR(10)) - + N' bytes.' + - CASE WHEN count_lob_columns > 0 THEN CAST((count_lob_columns) AS NVARCHAR(10)) - + ' columns are LOB types.' ELSE '' - END - AS details, - i.index_definition, - secret_columns, - ISNULL(i.index_usage_summary,''), - ISNULL(ip.index_size_summary,'') - FROM #IndexSanity i - JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id - JOIN count_columns AS cc ON i.[object_id]=cc.[object_id] - AND cc.database_id = i.database_id - AND cc.[schema_name] = i.[schema_name] - WHERE i.index_id IN (1,0) - AND - (cc.total_columns >= 35 OR - cc.sum_max_length >= 2000) - ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 27: Addicted to strings.', 0,1) WITH NOWAIT; - WITH count_columns AS ( - SELECT [object_id], - [database_id], - [schema_name], - SUM(CASE WHEN system_type_name IN ('varchar','nvarchar','char') OR max_length=-1 THEN 1 ELSE 0 END) AS string_or_LOB_columns, - COUNT(*) AS total_columns - FROM #IndexColumns ic - WHERE index_id IN (1,0) /*Heap or clustered only*/ - GROUP BY [object_id], - [database_id], - [schema_name] - ) - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 27 AS check_id, - i.index_sanity_id, - 200 AS Priority, - N'Index Hoarder' AS findings_group, - N'Addicted to strings' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/IndexHoarder' AS URL, - i.db_schema_object_name - + N' uses string or LOB types for ' + CAST((string_or_LOB_columns) AS NVARCHAR(10)) - + N' of ' + CAST(total_columns AS NVARCHAR(10)) - + N' columns. Check if data types are valid.' AS details, - i.index_definition, - secret_columns, - ISNULL(i.index_usage_summary,''), - ISNULL(ip.index_size_summary,'') - FROM #IndexSanity i - JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id - JOIN count_columns AS cc ON i.[object_id]=cc.[object_id] - AND cc.database_id = i.database_id - AND cc.[schema_name] = i.[schema_name] - CROSS APPLY (SELECT cc.total_columns - string_or_LOB_columns AS non_string_or_lob_columns) AS calc1 - WHERE i.index_id IN (1,0) - AND calc1.non_string_or_lob_columns <= 1 - AND cc.total_columns > 3 - ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); + drop SYNONYM DeadlockFindings; --done with inserting. +END +ELSE --Output to database is not set output to client app + BEGIN + SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + RAISERROR('Results 1 %s', 0, 1, @d) WITH NOWAIT; + WITH deadlocks + AS ( SELECT N'Regular Deadlock' AS deadlock_type, + dp.event_date, + dp.id, + dp.victim_id, + dp.database_id, + dp.priority, + dp.log_used, + dp.wait_resource COLLATE DATABASE_DEFAULT AS wait_resource, + CASE WHEN @ExportToExcel = 0 THEN + CONVERT( + XML, + STUFF(( SELECT DISTINCT NCHAR(10) + + N' ' + + ISNULL(c.object_name, N'') + + N' ' COLLATE DATABASE_DEFAULT AS object_name + FROM #deadlock_owner_waiter AS c + WHERE ( dp.id = c.owner_id + OR dp.victim_id = c.waiter_id ) + AND CONVERT(DATE, dp.event_date) = CONVERT(DATE, c.event_date) + FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(4000)'), + 1, 1, N'')) + ELSE NULL END AS object_names, + dp.wait_time, + dp.transaction_name, + dp.last_tran_started, + dp.last_batch_started, + dp.last_batch_completed, + dp.lock_mode, + dp.transaction_count, + dp.client_app, + dp.host_name, + dp.login_name, + dp.isolation_level, + dp.process_xml.value('(//process/inputbuf/text())[1]', 'NVARCHAR(MAX)') AS inputbuf, + ROW_NUMBER() OVER ( PARTITION BY dp.event_date, dp.id ORDER BY dp.event_date ) AS dn, + DENSE_RANK() OVER ( ORDER BY dp.event_date ) AS en, + ROW_NUMBER() OVER ( PARTITION BY dp.event_date ORDER BY dp.event_date ) -1 AS qn, + dp.is_victim, + ISNULL(dp.owner_mode, '-') AS owner_mode, + NULL AS owner_waiter_type, + NULL AS owner_activity, + NULL AS owner_waiter_activity, + NULL AS owner_merging, + NULL AS owner_spilling, + NULL AS owner_waiting_to_close, + ISNULL(dp.waiter_mode, '-') AS waiter_mode, + NULL AS waiter_waiter_type, + NULL AS waiter_owner_activity, + NULL AS waiter_waiter_activity, + NULL AS waiter_merging, + NULL AS waiter_spilling, + NULL AS waiter_waiting_to_close, + dp.deadlock_graph + FROM #deadlock_process AS dp + WHERE dp.victim_id IS NOT NULL + AND dp.is_parallel = 0 - RAISERROR(N'check_id 28: Non-unique clustered index.', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 28 AS check_id, - i.index_sanity_id, - 150 AS Priority, - N'Index Hoarder' AS findings_group, - N'Non-Unique Clustered JIndex' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/IndexHoarder' AS URL, - N'Uniquifiers will be required! Clustered index: ' + i.db_schema_object_name - + N' and all NC indexes. ' + - (SELECT CAST(COUNT(*) AS NVARCHAR(23)) - FROM #IndexSanity i2 - WHERE i2.[object_id]=i.[object_id] - AND i2.database_id = i.database_id - AND i2.index_id <> 1 - AND i2.is_disabled=0 - AND i2.is_hypothetical=0) - + N' NC indexes on the table.' - AS details, - i.index_definition, - secret_columns, - i.index_usage_summary, - ip.index_size_summary - FROM #IndexSanity i - JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id - WHERE index_id = 1 /* clustered only */ - AND is_unique=0 /* not unique */ - AND is_CX_columnstore=0 /* not a clustered columnstore-- no unique option on those */ - ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); + UNION ALL + + SELECT N'Parallel Deadlock' AS deadlock_type, + dp.event_date, + dp.id, + dp.victim_id, + dp.database_id, + dp.priority, + dp.log_used, + dp.wait_resource COLLATE DATABASE_DEFAULT, + CASE WHEN @ExportToExcel = 0 THEN + CONVERT( + XML, + STUFF(( SELECT DISTINCT NCHAR(10) + + N' ' + + ISNULL(c.object_name, N'') + + N' ' COLLATE DATABASE_DEFAULT AS object_name + FROM #deadlock_owner_waiter AS c + WHERE ( dp.id = c.owner_id + OR dp.victim_id = c.waiter_id ) + AND CONVERT(DATE, dp.event_date) = CONVERT(DATE, c.event_date) + FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(4000)'), + 1, 1, N'')) + ELSE NULL END AS object_names, + dp.wait_time, + dp.transaction_name, + dp.last_tran_started, + dp.last_batch_started, + dp.last_batch_completed, + dp.lock_mode, + dp.transaction_count, + dp.client_app, + dp.host_name, + dp.login_name, + dp.isolation_level, + dp.process_xml.value('(//process/inputbuf/text())[1]', 'NVARCHAR(MAX)') AS inputbuf, + ROW_NUMBER() OVER ( PARTITION BY dp.event_date, dp.id ORDER BY dp.event_date ) AS dn, + DENSE_RANK() OVER ( ORDER BY dp.event_date ) AS en, + ROW_NUMBER() OVER ( PARTITION BY dp.event_date ORDER BY dp.event_date ) -1 AS qn, + 1 AS is_victim, + cao.wait_type COLLATE DATABASE_DEFAULT AS owner_mode, + cao.waiter_type AS owner_waiter_type, + cao.owner_activity AS owner_activity, + cao.waiter_activity AS owner_waiter_activity, + cao.merging AS owner_merging, + cao.spilling AS owner_spilling, + cao.waiting_to_close AS owner_waiting_to_close, + caw.wait_type COLLATE DATABASE_DEFAULT AS waiter_mode, + caw.waiter_type AS waiter_waiter_type, + caw.owner_activity AS waiter_owner_activity, + caw.waiter_activity AS waiter_waiter_activity, + caw.merging AS waiter_merging, + caw.spilling AS waiter_spilling, + caw.waiting_to_close AS waiter_waiting_to_close, + dp.deadlock_graph + FROM #deadlock_process AS dp + OUTER APPLY (SELECT TOP 1 * FROM #deadlock_resource_parallel AS drp WHERE drp.owner_id = dp.id AND drp.wait_type IN ('e_waitPortOpen', 'e_waitPipeNewRow') ORDER BY drp.event_date) AS cao + OUTER APPLY (SELECT TOP 1 * FROM #deadlock_resource_parallel AS drp WHERE drp.owner_id = dp.id AND drp.wait_type IN ('e_waitPortOpen', 'e_waitPipeGetRow') ORDER BY drp.event_date) AS caw + WHERE dp.is_parallel = 1 + ) + SELECT d.deadlock_type, + d.event_date, + DB_NAME(d.database_id) AS database_name, + 'Deadlock #' + + CONVERT(NVARCHAR(10), d.en) + + ', Query #' + + CASE WHEN d.qn = 0 THEN N'1' ELSE CONVERT(NVARCHAR(10), d.qn) END + + CASE WHEN d.is_victim = 1 THEN ' - VICTIM' ELSE '' END + AS deadlock_group, + CASE WHEN @ExportToExcel = 0 THEN CONVERT(XML, N'') + ELSE SUBSTRING(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(LTRIM(RTRIM(d.inputbuf)),' ','<>'),'><',''),NCHAR(10), ' '),NCHAR(13), ' '),'<>',' '), 1, 32000) END AS query, + d.object_names, + d.isolation_level, + d.owner_mode, + d.waiter_mode, + d.transaction_count, + d.login_name, + d.host_name, + d.client_app, + d.wait_time, + d.priority, + d.log_used, + d.last_tran_started, + d.last_batch_started, + d.last_batch_completed, + d.transaction_name, + /*These columns will be NULL for regular (non-parallel) deadlocks*/ + d.owner_waiter_type, + d.owner_activity, + d.owner_waiter_activity, + d.owner_merging, + d.owner_spilling, + d.owner_waiting_to_close, + d.waiter_waiter_type, + d.waiter_owner_activity, + d.waiter_waiter_activity, + d.waiter_merging, + d.waiter_spilling, + d.waiter_waiting_to_close, + CASE WHEN @ExportToExcel = 0 THEN d.deadlock_graph ELSE NULL END AS deadlock_graph + FROM deadlocks AS d + WHERE d.dn = 1 + AND (is_victim = @VictimsOnly OR @VictimsOnly = 0) + AND d.en < CASE WHEN d.deadlock_type = N'Parallel Deadlock' THEN 2 ELSE 2147483647 END + AND (DB_NAME(d.database_id) = @DatabaseName OR @DatabaseName IS NULL) + AND (d.event_date >= @StartDate OR @StartDate IS NULL) + AND (d.event_date < @EndDate OR @EndDate IS NULL) + AND (CONVERT(NVARCHAR(MAX), d.object_names) LIKE '%' + @ObjectName + '%' OR @ObjectName IS NULL) + AND (d.client_app = @AppName OR @AppName IS NULL) + AND (d.host_name = @HostName OR @HostName IS NULL) + AND (d.login_name = @LoginName OR @LoginName IS NULL) + ORDER BY d.event_date, is_victim DESC + OPTION ( RECOMPILE ); + + SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + RAISERROR('Findings %s', 0, 1, @d) WITH NOWAIT; + SELECT df.check_id, df.database_name, df.object_name, df.finding_group, df.finding + FROM #deadlock_findings AS df + ORDER BY df.check_id + OPTION ( RECOMPILE ); - RAISERROR(N'check_id 29: NC indexes with 0 reads. (Borderline) and < 10,000 writes', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 29 AS check_id, - i.index_sanity_id, - 150 AS Priority, - N'Index Hoarder' AS findings_group, - N'Unused NC index with Low Writes' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/IndexHoarder' AS URL, - N'0 reads: ' + i.db_schema_object_indexid AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - sz.index_size_summary - FROM #IndexSanity AS i - JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.total_reads=0 - AND i.user_updates < 10000 - 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()) - ORDER BY i.db_schema_object_indexid - OPTION ( RECOMPILE ); + SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + RAISERROR('Done %s', 0, 1, @d) WITH NOWAIT; + END --done with output to client app. - ---------------------------------------- - --Feature-Phobic Indexes: Check_id 30-39 - ---------------------------------------- - RAISERROR(N'check_id 30: No indexes with includes', 0,1) WITH NOWAIT; - /* This does not work the way you'd expect with @GetAllDatabases = 1. For details: - https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/825 - */ - SELECT database_name, - SUM(CASE WHEN count_included_columns > 0 THEN 1 ELSE 0 END) AS number_indexes_with_includes, - 100.* SUM(CASE WHEN count_included_columns > 0 THEN 1 ELSE 0 END) / ( 1.0 * COUNT(*) ) AS percent_indexes_with_includes - INTO #index_includes - FROM #IndexSanity - WHERE is_hypothetical = 0 - AND is_disabled = 0 - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) - GROUP BY database_name; + IF @Debug = 1 + BEGIN - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 30 AS check_id, - NULL AS index_sanity_id, - 250 AS Priority, - N'Feature-Phobic Indexes' AS findings_group, - database_name AS [Database Name], - N'No Indexes Use Includes' AS finding, 'https://www.brentozar.com/go/IndexFeatures' AS URL, - N'No Indexes Use Includes' AS details, - database_name + N' (Entire database)' AS index_definition, - N'' AS secret_columns, - N'N/A' AS index_usage_summary, - N'N/A' AS index_size_summary - FROM #index_includes - WHERE number_indexes_with_includes = 0 - OPTION ( RECOMPILE ); + SELECT '#deadlock_data' AS table_name, * + FROM #deadlock_data AS dd + OPTION ( RECOMPILE ); - RAISERROR(N'check_id 31: < 3 percent of indexes have includes', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 31 AS check_id, - NULL AS index_sanity_id, - 250 AS Priority, - N'Feature-Phobic Indexes' AS findings_group, - N'Few Indexes Use Includes' AS findings, - database_name AS [Database Name], - N'https://www.brentozar.com/go/IndexFeatures' AS URL, - N'Only ' + CAST(percent_indexes_with_includes AS NVARCHAR(20)) + '% of indexes have includes' AS details, - N'Entire database' AS index_definition, - N'' AS secret_columns, - N'N/A' AS index_usage_summary, - N'N/A' AS index_size_summary - FROM #index_includes - WHERE number_indexes_with_includes > 0 AND percent_indexes_with_includes <= 3 - OPTION ( RECOMPILE ); + SELECT '#deadlock_resource' AS table_name, * + FROM #deadlock_resource AS dr + OPTION ( RECOMPILE ); - RAISERROR(N'check_id 32: filtered indexes and indexed views', 0,1) WITH NOWAIT; + SELECT '#deadlock_resource_parallel' AS table_name, * + FROM #deadlock_resource_parallel AS drp + OPTION ( RECOMPILE ); - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT DISTINCT - 32 AS check_id, - NULL AS index_sanity_id, - 250 AS Priority, - N'Feature-Phobic Indexes' AS findings_group, - N'No Filtered Indexes or Indexed Views' AS finding, - i.database_name AS [Database Name], - N'https://www.brentozar.com/go/IndexFeatures' AS URL, - N'These are NOT always needed-- but do you know when you would use them?' AS details, - i.database_name + N' (Entire database)' AS index_definition, - N'' AS secret_columns, - N'N/A' AS index_usage_summary, - N'N/A' AS index_size_summary - FROM #IndexSanity i - WHERE i.database_name NOT IN ( - SELECT database_name - FROM #IndexSanity - WHERE filter_definition <> '' ) - AND i.database_name NOT IN ( - SELECT database_name - FROM #IndexSanity - WHERE is_indexed_view = 1 ) - OPTION ( RECOMPILE ); + SELECT '#deadlock_owner_waiter' AS table_name, * + FROM #deadlock_owner_waiter AS dow + OPTION ( RECOMPILE ); - RAISERROR(N'check_id 33: Potential filtered indexes based on column names.', 0,1) WITH NOWAIT; + SELECT '#deadlock_process' AS table_name, * + FROM #deadlock_process AS dp + OPTION ( RECOMPILE ); - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 33 AS check_id, - i.index_sanity_id AS index_sanity_id, - 250 AS Priority, - N'Feature-Phobic Indexes' AS findings_group, - N'Potential Filtered Index (Based on Column Name)' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/IndexFeatures' AS URL, - N'A column name in this index suggests it might be a candidate for filtering (is%, %archive%, %active%, %flag%)' AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - sz.index_size_summary - FROM #IndexColumns ic - JOIN #IndexSanity i ON ic.[object_id]=i.[object_id] - AND ic.database_id =i.database_id - AND ic.schema_name = i.schema_name - AND ic.[index_id]=i.[index_id] - AND i.[index_id] > 1 /* non-clustered index */ - JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id - WHERE (column_name LIKE 'is%' - OR column_name LIKE '%archive%' - OR column_name LIKE '%active%' - OR column_name LIKE '%flag%') - OPTION ( RECOMPILE ); + SELECT '#deadlock_stack' AS table_name, * + FROM #deadlock_stack AS ds + OPTION ( RECOMPILE ); + + END; -- End debug - RAISERROR(N'check_id 41: Hypothetical indexes ', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 41 AS check_id, - i.index_sanity_id, - 150 AS Priority, - N'Self Loathing Indexes' AS findings_group, - N'Hypothetical Index' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/SelfLoathing' AS URL, - N'Hypothetical Index: ' + db_schema_object_indexid AS details, - i.index_definition, - i.secret_columns, - N'' AS index_usage_summary, - N'' AS index_size_summary - FROM #IndexSanity AS i - WHERE is_hypothetical = 1 - OPTION ( RECOMPILE ); + END; --Final End +GO +SET ANSI_NULLS ON; +SET ANSI_PADDING ON; +SET ANSI_WARNINGS ON; +SET ARITHABORT ON; +SET CONCAT_NULL_YIELDS_NULL ON; +SET QUOTED_IDENTIFIER ON; +SET STATISTICS IO OFF; +SET STATISTICS TIME OFF; +GO - RAISERROR(N'check_id 42: Disabled indexes', 0,1) WITH NOWAIT; - --Note: disabled NC indexes will have O rows in #IndexSanitySize! - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 42 AS check_id, - index_sanity_id, - 150 AS Priority, - N'Self Loathing Indexes' AS findings_group, - N'Disabled Index' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/SelfLoathing' AS URL, - N'Disabled Index:' + db_schema_object_indexid AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - 'DISABLED' AS index_size_summary - FROM #IndexSanity AS i - WHERE is_disabled = 1 - OPTION ( RECOMPILE ); +DECLARE @msg NVARCHAR(MAX) = N''; - RAISERROR(N'check_id 49: Heaps with deletes', 0,1) WITH NOWAIT; - WITH heaps_cte - AS ( SELECT [object_id], - [database_id], - [schema_name], - SUM(leaf_delete_count) AS leaf_delete_count - FROM #IndexPartitionSanity - GROUP BY [object_id], - [database_id], - [schema_name] - HAVING SUM(forwarded_fetch_count) < 1000 * @DaysUptime /* Only alert about indexes with no forwarded fetches - we already alerted about those in check_id 43 */ - AND SUM(leaf_delete_count) > 0) - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 49 AS check_id, - i.index_sanity_id, - 200 AS Priority, - N'Self Loathing Indexes' AS findings_group, - N'Heaps with Deletes' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/SelfLoathing' AS URL, - CAST(h.leaf_delete_count AS NVARCHAR(256)) + N' deletes against heap:' - + db_schema_object_indexid AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - sz.index_size_summary - FROM #IndexSanity i - JOIN heaps_cte h ON i.[object_id] = h.[object_id] - AND i.[database_id] = h.[database_id] - AND i.[schema_name] = h.[schema_name] - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.index_id = 0 - AND sz.total_reserved_MB >= CASE WHEN NOT (@GetAllDatabases = 1 OR @Mode = 4) THEN @ThresholdMB ELSE sz.total_reserved_MB END - OPTION ( RECOMPILE ); + -- Must be a compatible, on-prem version of SQL (2016+) +IF ( (SELECT CONVERT(NVARCHAR(128), SERVERPROPERTY ('EDITION'))) <> 'SQL Azure' + AND (SELECT PARSENAME(CONVERT(NVARCHAR(128), SERVERPROPERTY ('PRODUCTVERSION')), 4)) < 13 + ) + -- or Azure Database (not Azure Data Warehouse), running at database compat level 130+ +OR ( (SELECT CONVERT(NVARCHAR(128), SERVERPROPERTY ('EDITION'))) = 'SQL Azure' + AND (SELECT SERVERPROPERTY ('ENGINEEDITION')) NOT IN (5,8) + AND (SELECT [compatibility_level] FROM sys.databases WHERE [name] = DB_NAME()) < 130 + ) +BEGIN + SELECT @msg = N'Sorry, sp_BlitzQueryStore doesn''t work on versions of SQL prior to 2016, or Azure Database compatibility < 130.' + REPLICATE(CHAR(13), 7933); + PRINT @msg; + RETURN; +END; - ---------------------------------------- - --Abnormal Psychology : Check_id 60-79 - ---------------------------------------- - RAISERROR(N'check_id 60: XML indexes', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 60 AS check_id, - i.index_sanity_id, - 150 AS Priority, - N'Abnormal Psychology' AS findings_group, - N'XML Index' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, - i.db_schema_object_indexid AS details, - i.index_definition, - i.secret_columns, - N'' AS index_usage_summary, - ISNULL(sz.index_size_summary,'') AS index_size_summary - FROM #IndexSanity AS i - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.is_XML = 1 - OPTION ( RECOMPILE ); +IF OBJECT_ID('dbo.sp_BlitzQueryStore') IS NULL + EXEC ('CREATE PROCEDURE dbo.sp_BlitzQueryStore AS RETURN 0;'); +GO - RAISERROR(N'check_id 61: Columnstore indexes', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 61 AS check_id, - i.index_sanity_id, - 150 AS Priority, - N'Abnormal Psychology' AS findings_group, - CASE WHEN i.is_NC_columnstore=1 - THEN N'NC Columnstore Index' - ELSE N'Clustered Columnstore Index' - END AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, - i.db_schema_object_indexid AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - ISNULL(sz.index_size_summary,'') AS index_size_summary - FROM #IndexSanity AS i - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.is_NC_columnstore = 1 OR i.is_CX_columnstore=1 - OPTION ( RECOMPILE ); +ALTER PROCEDURE dbo.sp_BlitzQueryStore + @Help BIT = 0, + @DatabaseName NVARCHAR(128) = NULL , + @Top INT = 3, + @StartDate DATETIME2 = NULL, + @EndDate DATETIME2 = NULL, + @MinimumExecutionCount INT = NULL, + @DurationFilter DECIMAL(38,4) = NULL , + @StoredProcName NVARCHAR(128) = NULL, + @Failed BIT = 0, + @PlanIdFilter INT = NULL, + @QueryIdFilter INT = NULL, + @ExportToExcel BIT = 0, + @HideSummary BIT = 0 , + @SkipXML BIT = 0, + @Debug BIT = 0, + @ExpertMode BIT = 0, + @Version VARCHAR(30) = NULL OUTPUT, + @VersionDate DATETIME = NULL OUTPUT, + @VersionCheckMode BIT = 0 +WITH RECOMPILE +AS +BEGIN /*First BEGIN*/ +SET NOCOUNT ON; +SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - RAISERROR(N'check_id 62: Spatial indexes', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 62 AS check_id, - i.index_sanity_id, - 150 AS Priority, - N'Abnormal Psychology' AS findings_group, - N'Spatial Index' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, - i.db_schema_object_indexid AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - ISNULL(sz.index_size_summary,'') AS index_size_summary - FROM #IndexSanity AS i - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.is_spatial = 1 - OPTION ( RECOMPILE ); +SELECT @Version = '8.01', @VersionDate = '20210222'; +IF(@VersionCheckMode = 1) +BEGIN + RETURN; +END; - RAISERROR(N'check_id 63: Compressed indexes', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 63 AS check_id, - i.index_sanity_id, - 150 AS Priority, - N'Abnormal Psychology' AS findings_group, - N'Compressed Index' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, - i.db_schema_object_indexid + N'. COMPRESSION: ' + sz.data_compression_desc AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - ISNULL(sz.index_size_summary,'') AS index_size_summary - FROM #IndexSanity AS i - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE sz.data_compression_desc LIKE '%PAGE%' OR sz.data_compression_desc LIKE '%ROW%' - OPTION ( RECOMPILE ); - RAISERROR(N'check_id 64: Partitioned', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 64 AS check_id, - i.index_sanity_id, - 150 AS Priority, - N'Abnormal Psychology' AS findings_group, - N'Partitioned Index' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, - i.db_schema_object_indexid AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - ISNULL(sz.index_size_summary,'') AS index_size_summary - FROM #IndexSanity AS i - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.partition_key_column_name IS NOT NULL - OPTION ( RECOMPILE ); +DECLARE /*Variables for the variable Gods*/ + @msg NVARCHAR(MAX) = N'', --Used to format RAISERROR messages in some places + @sql_select NVARCHAR(MAX) = N'', --Used to hold SELECT statements for dynamic SQL + @sql_where NVARCHAR(MAX) = N'', -- Used to hold WHERE clause for dynamic SQL + @duration_filter_ms DECIMAL(38,4) = (@DurationFilter * 1000.), --We accept Duration in seconds, but we filter in milliseconds (this is grandfathered from sp_BlitzCache) + @execution_threshold INT = 1000, --Threshold at which we consider a query to be frequently executed + @ctp_threshold_pct TINYINT = 10, --Percentage of CTFP at which we consider a query to be near parallel + @long_running_query_warning_seconds BIGINT = 300 * 1000 ,--Number of seconds (converted to milliseconds) at which a query is considered long running + @memory_grant_warning_percent INT = 10,--Percent of memory grant used compared to what's granted; used to trigger unused memory grant warning + @ctp INT,--Holds the CTFP value for the server + @min_memory_per_query INT,--Holds the server configuration value for min memory per query + @cr NVARCHAR(1) = NCHAR(13),--Special character + @lf NVARCHAR(1) = NCHAR(10),--Special character + @tab NVARCHAR(1) = NCHAR(9),--Special character + @error_severity INT,--Holds error info for try/catch blocks + @error_state INT,--Holds error info for try/catch blocks + @sp_params NVARCHAR(MAX) = N'@sp_Top INT, @sp_StartDate DATETIME2, @sp_EndDate DATETIME2, @sp_MinimumExecutionCount INT, @sp_MinDuration INT, @sp_StoredProcName NVARCHAR(128), @sp_PlanIdFilter INT, @sp_QueryIdFilter INT',--Holds parameters used in dynamic SQL + @is_azure_db BIT = 0, --Are we using Azure? I'm not. You might be. That's cool. + @compatibility_level TINYINT = 0, --Some functionality (T-SQL) isn't available in lower compat levels. We can use this to weed out those issues as we go. + @log_size_mb DECIMAL(38,2) = 0, + @avg_tempdb_data_file DECIMAL(38,2) = 0; - RAISERROR(N'check_id 65: Non-Aligned Partitioned', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 65 AS check_id, - i.index_sanity_id, - 150 AS Priority, - N'Abnormal Psychology' AS findings_group, - N'Non-Aligned Index on a Partitioned Table' AS finding, - i.[database_name] AS [Database Name], - N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, - i.db_schema_object_indexid AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - ISNULL(sz.index_size_summary,'') AS index_size_summary - FROM #IndexSanity AS i - JOIN #IndexSanity AS iParent ON - i.[object_id]=iParent.[object_id] - AND i.database_id = iParent.database_id - AND i.schema_name = iParent.schema_name - AND iParent.index_id IN (0,1) /* could be a partitioned heap or clustered table */ - AND iParent.partition_key_column_name IS NOT NULL /* parent is partitioned*/ - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.partition_key_column_name IS NULL - OPTION ( RECOMPILE ); +/*Grabs CTFP setting*/ +SELECT @ctp = NULLIF(CAST(value AS INT), 0) +FROM sys.configurations +WHERE name = N'cost threshold for parallelism' +OPTION (RECOMPILE); - RAISERROR(N'check_id 66: Recently created tables/indexes (1 week)', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 66 AS check_id, - i.index_sanity_id, - 200 AS Priority, - N'Abnormal Psychology' AS findings_group, - N'Recently Created Tables/Indexes (1 week)' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, - i.db_schema_object_indexid + N' was created on ' + - CONVERT(NVARCHAR(16),i.create_date,121) + - N'. Tables/indexes which are dropped/created regularly require special methods for index tuning.' - AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - ISNULL(sz.index_size_summary,'') AS index_size_summary - FROM #IndexSanity AS i - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.create_date >= DATEADD(dd,-7,GETDATE()) - OPTION ( RECOMPILE ); +/*Grabs min query memory setting*/ +SELECT @min_memory_per_query = CONVERT(INT, c.value) +FROM sys.configurations AS c +WHERE c.name = N'min memory per query (KB)' +OPTION (RECOMPILE); - RAISERROR(N'check_id 67: Recently modified tables/indexes (2 days)', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 67 AS check_id, - i.index_sanity_id, - 200 AS Priority, - N'Abnormal Psychology' AS findings_group, - N'Recently Modified Tables/Indexes (2 days)' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, - i.db_schema_object_indexid + N' was modified on ' + - CONVERT(NVARCHAR(16),i.modify_date,121) + - N'. A large amount of recently modified indexes may mean a lot of rebuilds are occurring each night.' - AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - ISNULL(sz.index_size_summary,'') AS index_size_summary - FROM #IndexSanity AS i - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.modify_date > DATEADD(dd,-2,GETDATE()) - AND /*Exclude recently created tables.*/ - i.create_date < DATEADD(dd,-7,GETDATE()) - OPTION ( RECOMPILE ); +/*Check if this is Azure first*/ +IF (SELECT CONVERT(NVARCHAR(128), SERVERPROPERTY ('EDITION'))) <> 'SQL Azure' + BEGIN + /*Grabs log size for datbase*/ + SELECT @log_size_mb = AVG(((mf.size * 8) / 1024.)) + FROM sys.master_files AS mf + WHERE mf.database_id = DB_ID(@DatabaseName) + AND mf.type_desc = 'LOG'; + + /*Grab avg tempdb file size*/ + SELECT @avg_tempdb_data_file = AVG(((mf.size * 8) / 1024.)) + FROM sys.master_files AS mf + WHERE mf.database_id = DB_ID('tempdb') + AND mf.type_desc = 'ROWS'; + END; - RAISERROR(N'check_id 69: Column collation does not match database collation', 0,1) WITH NOWAIT; - WITH count_columns AS ( - SELECT [object_id], - database_id, - schema_name, - COUNT(*) AS column_count - FROM #IndexColumns ic - WHERE index_id IN (1,0) /*Heap or clustered only*/ - AND collation_name <> @collation - GROUP BY [object_id], - database_id, - schema_name - ) - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 69 AS check_id, - i.index_sanity_id, - 150 AS Priority, - N'Abnormal Psychology' AS findings_group, - N'Column Collation Does Not Match Database Collation' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, - i.db_schema_object_name - + N' has ' + CAST(column_count AS NVARCHAR(20)) - + N' column' + CASE WHEN column_count > 1 THEN 's' ELSE '' END - + N' with a different collation than the db collation of ' - + @collation AS details, - i.index_definition, - secret_columns, - ISNULL(i.index_usage_summary,''), - ISNULL(ip.index_size_summary,'') - FROM #IndexSanity i - JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id - JOIN count_columns AS cc ON i.[object_id]=cc.[object_id] - AND cc.database_id = i.database_id - AND cc.schema_name = i.schema_name - WHERE i.index_id IN (1,0) - ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); +/*Help section*/ - RAISERROR(N'check_id 70: Replicated columns', 0,1) WITH NOWAIT; - WITH count_columns AS ( - SELECT [object_id], - database_id, - schema_name, - COUNT(*) AS column_count, - SUM(CASE is_replicated WHEN 1 THEN 1 ELSE 0 END) AS replicated_column_count - FROM #IndexColumns ic - WHERE index_id IN (1,0) /*Heap or clustered only*/ - GROUP BY object_id, - database_id, - schema_name - ) - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 70 AS check_id, - i.index_sanity_id, - 200 AS Priority, - N'Abnormal Psychology' AS findings_group, - N'Replicated Columns' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, - i.db_schema_object_name - + N' has ' + CAST(replicated_column_count AS NVARCHAR(20)) - + N' out of ' + CAST(column_count AS NVARCHAR(20)) - + N' column' + CASE WHEN column_count > 1 THEN 's' ELSE '' END - + N' in one or more publications.' - AS details, - i.index_definition, - secret_columns, - ISNULL(i.index_usage_summary,''), - ISNULL(ip.index_size_summary,'') - FROM #IndexSanity i - JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id - JOIN count_columns AS cc ON i.[object_id]=cc.[object_id] - AND i.database_id = cc.database_id - AND i.schema_name = cc.schema_name - WHERE i.index_id IN (1,0) - AND replicated_column_count > 0 - ORDER BY i.db_schema_object_name DESC - OPTION ( RECOMPILE ); +IF @Help = 1 + BEGIN + + SELECT N'You have requested assistance. It will arrive as soon as humanly possible.' AS [Take four red capsules, help is on the way]; - RAISERROR(N'check_id 71: Cascading updates or cascading deletes.', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary, more_info ) - SELECT 71 AS check_id, - NULL AS index_sanity_id, - 150 AS Priority, - N'Abnormal Psychology' AS findings_group, - N'Cascading Updates or Deletes' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, - N'Foreign Key ' + foreign_key_name + - N' on ' + QUOTENAME(parent_object_name) + N'(' + LTRIM(parent_fk_columns) + N')' - + N' referencing ' + QUOTENAME(referenced_object_name) + N'(' + LTRIM(referenced_fk_columns) + N')' - + N' has settings:' - + CASE [delete_referential_action_desc] WHEN N'NO_ACTION' THEN N'' ELSE N' ON DELETE ' +[delete_referential_action_desc] END - + CASE [update_referential_action_desc] WHEN N'NO_ACTION' THEN N'' ELSE N' ON UPDATE ' + [update_referential_action_desc] END - AS details, - [fk].[database_name] - AS index_definition, - N'N/A' AS secret_columns, - N'N/A' AS index_usage_summary, - N'N/A' AS index_size_summary, - (SELECT TOP 1 more_info FROM #IndexSanity i WHERE i.object_id=fk.parent_object_id AND i.database_id = fk.database_id AND i.schema_name = fk.schema_name) - AS more_info - FROM #ForeignKeys fk - WHERE ([delete_referential_action_desc] <> N'NO_ACTION' - OR [update_referential_action_desc] <> N'NO_ACTION') - OPTION ( RECOMPILE ); + PRINT N' + sp_BlitzQueryStore from http://FirstResponderKit.org + + This script displays your most resource-intensive queries from the Query Store, + and points to ways you can tune these queries to make them faster. + + + To learn more, visit http://FirstResponderKit.org where you can download new + versions for free, watch training videos on how it works, get more info on + the findings, contribute your own code, and more. + + Known limitations of this version: + - This query will not run on SQL Server versions less than 2016. + - This query will not run on Azure Databases with compatibility less than 130. + - This query will not run on Azure Data Warehouse. + Unknown limitations of this version: + - Could be tickling + + + MIT License + + Copyright (c) 2021 Brent Ozar Unlimited + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + '; + RETURN; - RAISERROR(N'check_id 73: In-Memory OLTP', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 73 AS check_id, - i.index_sanity_id, - 150 AS Priority, - N'Abnormal Psychology' AS findings_group, - N'In-Memory OLTP' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, - i.db_schema_object_indexid AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - ISNULL(sz.index_size_summary,'') AS index_size_summary - FROM #IndexSanity AS i - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.is_in_memory_oltp = 1 - OPTION ( RECOMPILE ); +END; - RAISERROR(N'check_id 74: Identity column with unusual seed', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 74 AS check_id, - i.index_sanity_id, - 200 AS Priority, - N'Abnormal Psychology' AS findings_group, - N'Identity Column Using a Negative Seed or Increment Other Than 1' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, - i.db_schema_object_name + N'.' + QUOTENAME(ic.column_name) - + N' is an identity with type ' + ic.system_type_name - + N', last value of ' - + ISNULL((CONVERT(NVARCHAR(256),CAST(ic.last_value AS DECIMAL(38,0)), 1)),N'NULL') - + N', seed of ' - + ISNULL((CONVERT(NVARCHAR(256),CAST(ic.seed_value AS DECIMAL(38,0)), 1)),N'NULL') - + N', increment of ' + CAST(ic.increment_value AS NVARCHAR(256)) - + N', and range of ' + - CASE ic.system_type_name WHEN 'int' THEN N'+/- 2,147,483,647' - WHEN 'smallint' THEN N'+/- 32,768' - WHEN 'tinyint' THEN N'0 to 255' - ELSE 'unknown' - END - AS details, - i.index_definition, - secret_columns, - ISNULL(i.index_usage_summary,''), - ISNULL(ip.index_size_summary,'') - FROM #IndexSanity i - JOIN #IndexColumns ic ON - i.object_id=ic.object_id - AND i.database_id = ic.database_id - AND i.schema_name = ic.schema_name - AND i.index_id IN (0,1) /* heaps and cx only */ - AND ic.is_identity=1 - AND ic.system_type_name IN ('tinyint', 'smallint', 'int') - JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id - WHERE i.index_id IN (1,0) - AND (ic.seed_value < 0 OR ic.increment_value <> 1) - ORDER BY finding, details DESC - OPTION ( RECOMPILE ); +/*Making sure your version is copasetic*/ +IF ( (SELECT CONVERT(NVARCHAR(128), SERVERPROPERTY ('EDITION'))) = 'SQL Azure' ) + BEGIN + SET @is_azure_db = 1; - ---------------------------------------- - --Workaholics: Check_id 80-89 - ---------------------------------------- + IF ( (SELECT SERVERPROPERTY ('ENGINEEDITION')) NOT IN (5,8) + OR (SELECT [compatibility_level] FROM sys.databases WHERE [name] = DB_NAME()) < 130 + ) + BEGIN + SELECT @msg = N'Sorry, sp_BlitzQueryStore doesn''t work on Azure Data Warehouse, or Azure Databases with DB compatibility < 130.' + REPLICATE(CHAR(13), 7933); + PRINT @msg; + RETURN; + END; + END; +ELSE IF ( (SELECT PARSENAME(CONVERT(NVARCHAR(128), SERVERPROPERTY ('PRODUCTVERSION')), 4) ) < 13 ) + BEGIN + SELECT @msg = N'Sorry, sp_BlitzQueryStore doesn''t work on versions of SQL prior to 2016.' + REPLICATE(CHAR(13), 7933); + PRINT @msg; + RETURN; + END; - RAISERROR(N'check_id 80: Most scanned indexes (index_usage_stats)', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) +/*Making sure at least one database uses QS*/ +IF ( SELECT COUNT(*) + FROM sys.databases AS d + WHERE d.is_query_store_on = 1 + AND d.user_access_desc='MULTI_USER' + AND d.state_desc = 'ONLINE' + AND d.name NOT IN ('master', 'model', 'msdb', 'tempdb', '32767') + AND d.is_distributor = 0 ) = 0 + BEGIN + SELECT @msg = N'You don''t currently have any databases with Query Store enabled.' + REPLICATE(CHAR(13), 7933); + PRINT @msg; + RETURN; + END; + +/*Making sure your databases are using QDS.*/ +RAISERROR('Checking database validity', 0, 1) WITH NOWAIT; - --Workaholics according to index_usage_stats - --This isn't perfect: it mentions the number of scans present in a plan - --A "scan" isn't necessarily a full scan, but hey, we gotta do the best with what we've got. - --in the case of things like indexed views, the operator might be in the plan but never executed - SELECT TOP 5 - 80 AS check_id, - i.index_sanity_id AS index_sanity_id, - 200 AS Priority, - N'Workaholics' AS findings_group, - N'Scan-a-lots (index-usage-stats)' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/Workaholics' AS URL, - REPLACE(CONVERT( NVARCHAR(50),CAST(i.user_scans AS MONEY),1),'.00','') - + N' scans against ' + i.db_schema_object_indexid - + N'. Latest scan: ' + ISNULL(CAST(i.last_user_scan AS NVARCHAR(128)),'?') + N'. ' - + N'ScanFactor=' + CAST(((i.user_scans * iss.total_reserved_MB)/1000000.) AS NVARCHAR(256)) AS details, - ISNULL(i.key_column_names_with_sort_order,'N/A') AS index_definition, - ISNULL(i.secret_columns,'') AS secret_columns, - i.index_usage_summary AS index_usage_summary, - iss.index_size_summary AS index_size_summary - FROM #IndexSanity i - JOIN #IndexSanitySize iss ON i.index_sanity_id=iss.index_sanity_id - WHERE ISNULL(i.user_scans,0) > 0 - ORDER BY i.user_scans * iss.total_reserved_MB DESC - OPTION ( RECOMPILE ); +IF (@is_azure_db = 1) + SET @DatabaseName = DB_NAME(); +ELSE +BEGIN - RAISERROR(N'check_id 81: Top recent accesses (op stats)', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - --Workaholics according to index_operational_stats - --This isn't perfect either: range_scan_count contains full scans, partial scans, even seeks in nested loop ops - --But this can help bubble up some most-accessed tables - SELECT TOP 5 - 81 AS check_id, - i.index_sanity_id AS index_sanity_id, - 200 AS Priority, - N'Workaholics' AS findings_group, - N'Top Recent Accesses (index-op-stats)' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/Workaholics' AS URL, - ISNULL(REPLACE( - CONVERT(NVARCHAR(50),CAST((iss.total_range_scan_count + iss.total_singleton_lookup_count) AS MONEY),1), - N'.00',N'') - + N' uses of ' + i.db_schema_object_indexid + N'. ' - + REPLACE(CONVERT(NVARCHAR(50), CAST(iss.total_range_scan_count AS MONEY),1),N'.00',N'') + N' scans or seeks. ' - + REPLACE(CONVERT(NVARCHAR(50), CAST(iss.total_singleton_lookup_count AS MONEY), 1),N'.00',N'') + N' singleton lookups. ' - + N'OpStatsFactor=' + CAST(((((iss.total_range_scan_count + iss.total_singleton_lookup_count) * iss.total_reserved_MB))/1000000.) AS VARCHAR(256)),'') AS details, - ISNULL(i.key_column_names_with_sort_order,'N/A') AS index_definition, - ISNULL(i.secret_columns,'') AS secret_columns, - i.index_usage_summary AS index_usage_summary, - iss.index_size_summary AS index_size_summary - FROM #IndexSanity i - JOIN #IndexSanitySize iss ON i.index_sanity_id=iss.index_sanity_id - WHERE (ISNULL(iss.total_range_scan_count,0) > 0 OR ISNULL(iss.total_singleton_lookup_count,0) > 0) - ORDER BY ((iss.total_range_scan_count + iss.total_singleton_lookup_count) * iss.total_reserved_MB) DESC - OPTION ( RECOMPILE ); + /*If we're on Azure we don't need to check all this @DatabaseName stuff...*/ + SET @DatabaseName = LTRIM(RTRIM(@DatabaseName)); + /*Did you set @DatabaseName?*/ + RAISERROR('Making sure [%s] isn''t NULL', 0, 1, @DatabaseName) WITH NOWAIT; + IF (@DatabaseName IS NULL) + BEGIN + RAISERROR('@DatabaseName cannot be NULL', 0, 1) WITH NOWAIT; + RETURN; + END; - RAISERROR(N'check_id 93: Statistics with filters', 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 93 AS check_id, - 200 AS Priority, - 'Functioning Statistaholics' AS findings_group, - 'Filter Fixation', - s.database_name, - 'https://www.brentozar.com/go/stats' AS URL, - 'The statistic ' + QUOTENAME(s.statistics_name) + ' is filtered on [' + s.filter_definition + ']. It could be part of a filtered index, or just a filtered statistic. This is purely informational.' 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_filter = 1 - OPTION ( RECOMPILE ); + /*Does the database exist?*/ + RAISERROR('Making sure [%s] exists', 0, 1, @DatabaseName) WITH NOWAIT; + IF ((DB_ID(@DatabaseName)) IS NULL) + BEGIN + RAISERROR('The @DatabaseName you specified ([%s]) does not exist. Please check the name and try again.', 0, 1, @DatabaseName) WITH NOWAIT; + RETURN; + END; + /*Is it online?*/ + RAISERROR('Making sure [%s] is online', 0, 1, @DatabaseName) WITH NOWAIT; + IF (DATABASEPROPERTYEX(@DatabaseName, 'Collation')) IS NULL + BEGIN + RAISERROR('The @DatabaseName you specified ([%s]) is not readable. Please check the name and try again. Better yet, check your server.', 0, 1, @DatabaseName); + RETURN; + END; +END; - RAISERROR(N'check_id 100: Computed Columns that are not Persisted.', 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 100 AS check_id, - 200 AS Priority, - 'Cold Calculators' AS findings_group, - 'Definition Defeatists' AS finding, - cc.database_name, - '' AS URL, - 'The computed column ' + QUOTENAME(cc.column_name) + ' on ' + QUOTENAME(cc.schema_name) + '.' + QUOTENAME(cc.table_name) + ' is not persisted, which means it will be calculated when a query runs.' + - 'You can change this with the following command, if the definition is deterministic: ALTER TABLE ' + QUOTENAME(cc.schema_name) + '.' + QUOTENAME(cc.table_name) + ' ALTER COLUMN ' + cc.column_name + - ' ADD PERSISTED' AS details, - cc.column_definition, - 'N/A' AS secret_columns, - 'N/A' AS index_usage_summary, - 'N/A' AS index_size_summary - FROM #ComputedColumns AS cc - WHERE cc.is_persisted = 0 - OPTION ( RECOMPILE ); +/*Does it have Query Store enabled?*/ +RAISERROR('Making sure [%s] has Query Store enabled', 0, 1, @DatabaseName) WITH NOWAIT; +IF - RAISERROR(N'check_id 110: Temporal Tables.', 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 [d].[name] + FROM [sys].[databases] AS d + WHERE [d].[is_query_store_on] = 1 + AND [d].[user_access_desc]='MULTI_USER' + AND [d].[state_desc] = 'ONLINE' + AND [d].[database_id] = (SELECT database_id FROM sys.databases WHERE name = @DatabaseName) + ) IS NULL +BEGIN + RAISERROR('The @DatabaseName you specified ([%s]) does not have the Query Store enabled. Please check the name or settings, and try again.', 0, 1, @DatabaseName) WITH NOWAIT; + RETURN; +END; - SELECT 110 AS check_id, - 200 AS Priority, - 'Abnormal Psychology' AS findings_group, - 'Temporal Tables', - t.database_name, - '' AS URL, - 'The table ' + QUOTENAME(t.schema_name) + '.' + QUOTENAME(t.table_name) + ' is a temporal table, with rows versioned in ' - + QUOTENAME(t.history_schema_name) + '.' + QUOTENAME(t.history_table_name) + ' on History columns ' + QUOTENAME(t.start_column_name) + ' and ' + QUOTENAME(t.end_column_name) + '.' - AS details, - '' AS index_definition, - 'N/A' AS secret_columns, - 'N/A' AS index_usage_summary, - 'N/A' AS index_size_summary - FROM #TemporalTables AS t - OPTION ( RECOMPILE ); +/*Check database compat level*/ +RAISERROR('Checking database compatibility level', 0, 1) WITH NOWAIT; +SELECT @compatibility_level = d.compatibility_level +FROM sys.databases AS d +WHERE d.name = @DatabaseName; +RAISERROR('The @DatabaseName you specified ([%s])is running in compatibility level ([%d]).', 0, 1, @DatabaseName, @compatibility_level) WITH NOWAIT; - END /* IF @Mode = 4 */ +/*Making sure top is set to something if NULL*/ +IF ( @Top IS NULL ) + BEGIN + SET @Top = 3; + END; +/* +This section determines if you have the Query Store wait stats DMV +*/ +RAISERROR('Checking for query_store_wait_stats', 0, 1) WITH NOWAIT; +DECLARE @ws_out INT, + @waitstats BIT, + @ws_sql NVARCHAR(MAX) = N'SELECT @i_out = COUNT(*) FROM ' + QUOTENAME(@DatabaseName) + N'.sys.all_objects WHERE name = ''query_store_wait_stats'' OPTION (RECOMPILE);', + @ws_params NVARCHAR(MAX) = N'@i_out INT OUTPUT'; +EXEC sys.sp_executesql @ws_sql, @ws_params, @i_out = @ws_out OUTPUT; +SELECT @waitstats = CASE @ws_out WHEN 0 THEN 0 ELSE 1 END; +SET @msg = N'Wait stats DMV ' + CASE @waitstats + WHEN 0 THEN N' does not exist, skipping.' + WHEN 1 THEN N' exists, will analyze.' + END; +RAISERROR(@msg, 0, 1) WITH NOWAIT; +/* +This section determines if you have some additional columns present in 2017, in case they get back ported. +*/ +RAISERROR('Checking for new columns in query_store_runtime_stats', 0, 1) WITH NOWAIT; +DECLARE @nc_out INT, + @new_columns BIT, + @nc_sql NVARCHAR(MAX) = N'SELECT @i_out = COUNT(*) + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.all_columns AS ac + WHERE OBJECT_NAME(object_id) = ''query_store_runtime_stats'' + AND ac.name IN ( + ''avg_num_physical_io_reads'', + ''last_num_physical_io_reads'', + ''min_num_physical_io_reads'', + ''max_num_physical_io_reads'', + ''avg_log_bytes_used'', + ''last_log_bytes_used'', + ''min_log_bytes_used'', + ''max_log_bytes_used'', + ''avg_tempdb_space_used'', + ''last_tempdb_space_used'', + ''min_tempdb_space_used'', + ''max_tempdb_space_used'' + ) OPTION (RECOMPILE);', + @nc_params NVARCHAR(MAX) = N'@i_out INT OUTPUT'; +EXEC sys.sp_executesql @nc_sql, @ws_params, @i_out = @nc_out OUTPUT; +SELECT @new_columns = CASE @nc_out WHEN 12 THEN 1 ELSE 0 END; +SET @msg = N'New query_store_runtime_stats columns ' + CASE @new_columns + WHEN 0 THEN N' do not exist, skipping.' + WHEN 1 THEN N' exist, will analyze.' + END; +RAISERROR(@msg, 0, 1) WITH NOWAIT; + +/* +These are the temp tables we use +*/ +/* +This one holds the grouped data that helps use figure out which periods to examine +*/ +RAISERROR(N'Creating temp tables', 0, 1) WITH NOWAIT; +DROP TABLE IF EXISTS #grouped_interval; +CREATE TABLE #grouped_interval +( + flat_date DATE NULL, + start_range DATETIME NULL, + end_range DATETIME NULL, + total_avg_duration_ms DECIMAL(38, 2) NULL, + total_avg_cpu_time_ms DECIMAL(38, 2) NULL, + total_avg_logical_io_reads_mb DECIMAL(38, 2) NULL, + total_avg_physical_io_reads_mb DECIMAL(38, 2) NULL, + total_avg_logical_io_writes_mb DECIMAL(38, 2) NULL, + total_avg_query_max_used_memory_mb DECIMAL(38, 2) NULL, + total_rowcount DECIMAL(38, 2) NULL, + total_count_executions BIGINT NULL, + total_avg_log_bytes_mb DECIMAL(38, 2) NULL, + total_avg_tempdb_space DECIMAL(38, 2) NULL, + total_max_duration_ms DECIMAL(38, 2) NULL, + total_max_cpu_time_ms DECIMAL(38, 2) NULL, + total_max_logical_io_reads_mb DECIMAL(38, 2) NULL, + total_max_physical_io_reads_mb DECIMAL(38, 2) NULL, + total_max_logical_io_writes_mb DECIMAL(38, 2) NULL, + total_max_query_max_used_memory_mb DECIMAL(38, 2) NULL, + total_max_log_bytes_mb DECIMAL(38, 2) NULL, + total_max_tempdb_space DECIMAL(38, 2) NULL, + INDEX gi_ix_dates CLUSTERED (start_range, end_range) +); +/* +These are the plans we focus on based on what we find in the grouped intervals +*/ +DROP TABLE IF EXISTS #working_plans; +CREATE TABLE #working_plans +( + plan_id BIGINT, + query_id BIGINT, + pattern NVARCHAR(258), + INDEX wp_ix_ids CLUSTERED (plan_id, query_id) +); +/* +These are the gathered metrics we get from query store to generate some warnings and help you find your worst offenders +*/ +DROP TABLE IF EXISTS #working_metrics; +CREATE TABLE #working_metrics +( + database_name NVARCHAR(258), + plan_id BIGINT, + query_id BIGINT, + query_id_all_plan_ids VARCHAR(8000), + /*these columns are from query_store_query*/ + proc_or_function_name NVARCHAR(258), + batch_sql_handle VARBINARY(64), + query_hash BINARY(8), + query_parameterization_type_desc NVARCHAR(258), + parameter_sniffing_symptoms NVARCHAR(4000), + count_compiles BIGINT, + avg_compile_duration DECIMAL(38,2), + last_compile_duration DECIMAL(38,2), + avg_bind_duration DECIMAL(38,2), + last_bind_duration DECIMAL(38,2), + avg_bind_cpu_time DECIMAL(38,2), + last_bind_cpu_time DECIMAL(38,2), + avg_optimize_duration DECIMAL(38,2), + last_optimize_duration DECIMAL(38,2), + avg_optimize_cpu_time DECIMAL(38,2), + last_optimize_cpu_time DECIMAL(38,2), + avg_compile_memory_kb DECIMAL(38,2), + last_compile_memory_kb DECIMAL(38,2), + /*These come from query_store_runtime_stats*/ + execution_type_desc NVARCHAR(128), + first_execution_time DATETIME2, + last_execution_time DATETIME2, + count_executions BIGINT, + avg_duration DECIMAL(38,2) , + last_duration DECIMAL(38,2), + min_duration DECIMAL(38,2), + max_duration DECIMAL(38,2), + avg_cpu_time DECIMAL(38,2), + last_cpu_time DECIMAL(38,2), + min_cpu_time DECIMAL(38,2), + max_cpu_time DECIMAL(38,2), + avg_logical_io_reads DECIMAL(38,2), + last_logical_io_reads DECIMAL(38,2), + min_logical_io_reads DECIMAL(38,2), + max_logical_io_reads DECIMAL(38,2), + avg_logical_io_writes DECIMAL(38,2), + last_logical_io_writes DECIMAL(38,2), + min_logical_io_writes DECIMAL(38,2), + max_logical_io_writes DECIMAL(38,2), + avg_physical_io_reads DECIMAL(38,2), + last_physical_io_reads DECIMAL(38,2), + min_physical_io_reads DECIMAL(38,2), + max_physical_io_reads DECIMAL(38,2), + avg_clr_time DECIMAL(38,2), + last_clr_time DECIMAL(38,2), + min_clr_time DECIMAL(38,2), + max_clr_time DECIMAL(38,2), + avg_dop BIGINT, + last_dop BIGINT, + min_dop BIGINT, + max_dop BIGINT, + avg_query_max_used_memory DECIMAL(38,2), + last_query_max_used_memory DECIMAL(38,2), + min_query_max_used_memory DECIMAL(38,2), + max_query_max_used_memory DECIMAL(38,2), + avg_rowcount DECIMAL(38,2), + last_rowcount DECIMAL(38,2), + min_rowcount DECIMAL(38,2), + max_rowcount DECIMAL(38,2), + /*These are 2017 only, AFAIK*/ + avg_num_physical_io_reads DECIMAL(38,2), + last_num_physical_io_reads DECIMAL(38,2), + min_num_physical_io_reads DECIMAL(38,2), + max_num_physical_io_reads DECIMAL(38,2), + avg_log_bytes_used DECIMAL(38,2), + last_log_bytes_used DECIMAL(38,2), + min_log_bytes_used DECIMAL(38,2), + max_log_bytes_used DECIMAL(38,2), + avg_tempdb_space_used DECIMAL(38,2), + last_tempdb_space_used DECIMAL(38,2), + min_tempdb_space_used DECIMAL(38,2), + max_tempdb_space_used DECIMAL(38,2), + /*These are computed columns to make some stuff easier down the line*/ + total_compile_duration AS avg_compile_duration * count_compiles, + total_bind_duration AS avg_bind_duration * count_compiles, + total_bind_cpu_time AS avg_bind_cpu_time * count_compiles, + total_optimize_duration AS avg_optimize_duration * count_compiles, + total_optimize_cpu_time AS avg_optimize_cpu_time * count_compiles, + total_compile_memory_kb AS avg_compile_memory_kb * count_compiles, + total_duration AS avg_duration * count_executions, + total_cpu_time AS avg_cpu_time * count_executions, + total_logical_io_reads AS avg_logical_io_reads * count_executions, + total_logical_io_writes AS avg_logical_io_writes * count_executions, + total_physical_io_reads AS avg_physical_io_reads * count_executions, + total_clr_time AS avg_clr_time * count_executions, + total_query_max_used_memory AS avg_query_max_used_memory * count_executions, + total_rowcount AS avg_rowcount * count_executions, + total_num_physical_io_reads AS avg_num_physical_io_reads * count_executions, + total_log_bytes_used AS avg_log_bytes_used * count_executions, + total_tempdb_space_used AS avg_tempdb_space_used * count_executions, + xpm AS NULLIF(count_executions, 0) / NULLIF(DATEDIFF(MINUTE, first_execution_time, last_execution_time), 0), + percent_memory_grant_used AS CONVERT(MONEY, ISNULL(NULLIF(( max_query_max_used_memory * 1.00 ), 0) / NULLIF(min_query_max_used_memory, 0), 0) * 100.), + INDEX wm_ix_ids CLUSTERED (plan_id, query_id, query_hash) +); - - RAISERROR(N'Insert a row to help people find help', 0,1) WITH NOWAIT; - IF DATEDIFF(MM, @VersionDate, GETDATE()) > 6 - BEGIN - INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, URL, details, index_definition, - index_usage_summary, index_size_summary ) - VALUES ( -1, 0 , - 'Outdated sp_BlitzIndex', 'sp_BlitzIndex is Over 6 Months Old', 'http://FirstResponderKit.org/', - 'Fine wine gets better with age, but this ' + @ScriptVersionName + ' is more like bad cheese. Time to get a new one.', - @DaysUptimeInsertValue,N'',N'' - ); - END; +/* +This is where we store some additional metrics, along with the query plan and text +*/ +DROP TABLE IF EXISTS #working_plan_text; - IF EXISTS(SELECT * FROM #BlitzIndexResults) - BEGIN - INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, URL, details, index_definition, - index_usage_summary, index_size_summary ) - VALUES ( -1, 0 , - @ScriptVersionName, - CASE WHEN @GetAllDatabases = 1 THEN N'All Databases' ELSE N'Database ' + QUOTENAME(@DatabaseName) + N' as of ' + CONVERT(NVARCHAR(16),GETDATE(),121) END, - N'From Your Community Volunteers' , N'http://FirstResponderKit.org' , - @DaysUptimeInsertValue,N'',N'' - ); - END; - ELSE IF @Mode = 0 OR (@GetAllDatabases = 1 AND @Mode <> 4) - BEGIN - INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, URL, details, index_definition, - index_usage_summary, index_size_summary ) - VALUES ( -1, 0 , - @ScriptVersionName, - CASE WHEN @GetAllDatabases = 1 THEN N'All Databases' ELSE N'Database ' + QUOTENAME(@DatabaseName) + N' as of ' + CONVERT(NVARCHAR(16),GETDATE(),121) END, - N'From Your Community Volunteers' , N'http://FirstResponderKit.org' , - @DaysUptimeInsertValue, N'',N'' - ); - INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, URL, details, index_definition, - index_usage_summary, index_size_summary ) - VALUES ( 1, 0 , - N'No Major Problems Found', - N'Nice Work!', - N'http://FirstResponderKit.org', - N'Consider running with @Mode = 4 in individual databases (not all) for more detailed diagnostics.', - N'The default Mode 0 only looks for very serious index issues.', - @DaysUptimeInsertValue, N'' - ); +CREATE TABLE #working_plan_text +( + database_name NVARCHAR(258), + plan_id BIGINT, + query_id BIGINT, + /*These are from query_store_plan*/ + plan_group_id BIGINT, + engine_version NVARCHAR(64), + compatibility_level INT, + query_plan_hash BINARY(8), + query_plan_xml XML, + is_online_index_plan BIT, + is_trivial_plan BIT, + is_parallel_plan BIT, + is_forced_plan BIT, + is_natively_compiled BIT, + force_failure_count BIGINT, + last_force_failure_reason_desc NVARCHAR(258), + count_compiles BIGINT, + initial_compile_start_time DATETIME2, + last_compile_start_time DATETIME2, + last_execution_time DATETIME2, + avg_compile_duration DECIMAL(38,2), + last_compile_duration BIGINT, + /*These are from query_store_query*/ + query_sql_text NVARCHAR(MAX), + statement_sql_handle VARBINARY(64), + is_part_of_encrypted_module BIT, + has_restricted_text BIT, + /*This is from query_context_settings*/ + context_settings NVARCHAR(512), + /*This is from #working_plans*/ + pattern NVARCHAR(512), + top_three_waits NVARCHAR(MAX), + INDEX wpt_ix_ids CLUSTERED (plan_id, query_id, query_plan_hash) +); - END; - ELSE - BEGIN - INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, URL, details, index_definition, - index_usage_summary, index_size_summary ) - VALUES ( -1, 0 , - @ScriptVersionName, - CASE WHEN @GetAllDatabases = 1 THEN N'All Databases' ELSE N'Database ' + QUOTENAME(@DatabaseName) + N' as of ' + CONVERT(NVARCHAR(16),GETDATE(),121) END, - N'From Your Community Volunteers' , N'http://FirstResponderKit.org' , - @DaysUptimeInsertValue, N'',N'' - ); - INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, URL, details, index_definition, - index_usage_summary, index_size_summary ) - VALUES ( 1, 0 , - N'No Problems Found', - N'Nice job! Or more likely, you have a nearly empty database.', - N'http://FirstResponderKit.org', 'Time to go read some blog posts.', - @DaysUptimeInsertValue, N'', N'' - ); - END; +/* +This is where we store warnings that we generate from the XML and metrics +*/ +DROP TABLE IF EXISTS #working_warnings; - RAISERROR(N'Returning results.', 0,1) WITH NOWAIT; - - /*Return results.*/ - IF (@Mode = 0) - BEGIN - IF(@OutputType <> 'NONE') - BEGIN - SELECT Priority, ISNULL(br.findings_group,N'') + - CASE WHEN ISNULL(br.finding,N'') <> N'' THEN N': ' ELSE N'' END - + br.finding AS [Finding], - br.[database_name] AS [Database Name], - br.details AS [Details: schema.table.index(indexid)], - br.index_definition AS [Definition: [Property]] ColumnName {datatype maxbytes}], - ISNULL(br.secret_columns,'') AS [Secret Columns], - br.index_usage_summary AS [Usage], - br.index_size_summary AS [Size], - COALESCE(br.more_info,sn.more_info,'') AS [More Info], - br.URL, - COALESCE(br.create_tsql,ts.create_tsql,'') AS [Create TSQL] - FROM #BlitzIndexResults br - LEFT JOIN #IndexSanity sn ON - br.index_sanity_id=sn.index_sanity_id - LEFT JOIN #IndexCreateTsql ts ON - br.index_sanity_id=ts.index_sanity_id - ORDER BY br.Priority ASC, br.check_id ASC, br.blitz_result_id ASC, br.findings_group ASC - OPTION (RECOMPILE); - END; +CREATE TABLE #working_warnings +( + plan_id BIGINT, + query_id BIGINT, + query_hash BINARY(8), + sql_handle VARBINARY(64), + proc_or_function_name NVARCHAR(258), + plan_multiple_plans BIT, + is_forced_plan BIT, + is_forced_parameterized BIT, + is_cursor BIT, + is_optimistic_cursor BIT, + is_forward_only_cursor BIT, + is_fast_forward_cursor BIT, + is_cursor_dynamic BIT, + is_parallel BIT, + is_forced_serial BIT, + is_key_lookup_expensive BIT, + key_lookup_cost FLOAT, + is_remote_query_expensive BIT, + remote_query_cost FLOAT, + frequent_execution BIT, + parameter_sniffing BIT, + unparameterized_query BIT, + near_parallel BIT, + plan_warnings BIT, + long_running BIT, + downlevel_estimator BIT, + implicit_conversions BIT, + tvf_estimate BIT, + compile_timeout BIT, + compile_memory_limit_exceeded BIT, + warning_no_join_predicate BIT, + query_cost FLOAT, + missing_index_count INT, + unmatched_index_count INT, + is_trivial BIT, + trace_flags_session NVARCHAR(1000), + is_unused_grant BIT, + function_count INT, + clr_function_count INT, + is_table_variable BIT, + no_stats_warning BIT, + relop_warnings BIT, + is_table_scan BIT, + backwards_scan BIT, + forced_index BIT, + forced_seek BIT, + forced_scan BIT, + columnstore_row_mode BIT, + is_computed_scalar BIT , + is_sort_expensive BIT, + sort_cost FLOAT, + is_computed_filter BIT, + op_name NVARCHAR(100) NULL, + index_insert_count INT NULL, + index_update_count INT NULL, + index_delete_count INT NULL, + cx_insert_count INT NULL, + cx_update_count INT NULL, + cx_delete_count INT NULL, + table_insert_count INT NULL, + table_update_count INT NULL, + table_delete_count INT NULL, + index_ops AS (index_insert_count + index_update_count + index_delete_count + + cx_insert_count + cx_update_count + cx_delete_count + + table_insert_count + table_update_count + table_delete_count), + is_row_level BIT, + is_spatial BIT, + index_dml BIT, + table_dml BIT, + long_running_low_cpu BIT, + low_cost_high_cpu BIT, + stale_stats BIT, + is_adaptive BIT, + is_slow_plan BIT, + is_compile_more BIT, + index_spool_cost FLOAT, + index_spool_rows FLOAT, + is_spool_expensive BIT, + is_spool_more_rows BIT, + estimated_rows FLOAT, + is_bad_estimate BIT, + is_big_log BIT, + is_big_tempdb BIT, + is_paul_white_electric BIT, + is_row_goal BIT, + is_mstvf BIT, + is_mm_join BIT, + is_nonsargable BIT, + busy_loops BIT, + tvf_join BIT, + implicit_conversion_info XML, + cached_execution_parameters XML, + missing_indexes XML, + warnings NVARCHAR(4000) + INDEX ww_ix_ids CLUSTERED (plan_id, query_id, query_hash, sql_handle) +); - END; - ELSE IF (@Mode = 4) - IF(@OutputType <> 'NONE') - BEGIN - SELECT Priority, ISNULL(br.findings_group,N'') + - CASE WHEN ISNULL(br.finding,N'') <> N'' THEN N': ' ELSE N'' END - + br.finding AS [Finding], - br.[database_name] AS [Database Name], - br.details AS [Details: schema.table.index(indexid)], - br.index_definition AS [Definition: [Property]] ColumnName {datatype maxbytes}], - ISNULL(br.secret_columns,'') AS [Secret Columns], - br.index_usage_summary AS [Usage], - br.index_size_summary AS [Size], - COALESCE(br.more_info,sn.more_info,'') AS [More Info], - br.URL, - COALESCE(br.create_tsql,ts.create_tsql,'') AS [Create TSQL] - FROM #BlitzIndexResults br - LEFT JOIN #IndexSanity sn ON - br.index_sanity_id=sn.index_sanity_id - LEFT JOIN #IndexCreateTsql ts ON - br.index_sanity_id=ts.index_sanity_id - ORDER BY br.Priority ASC, br.check_id ASC, br.blitz_result_id ASC, br.findings_group ASC - OPTION (RECOMPILE); - END; -END /* End @Mode=0 or 4 (diagnose)*/ +DROP TABLE IF EXISTS #working_wait_stats; -ELSE IF (@Mode=1) /*Summarize*/ - BEGIN - --This mode is to give some overall stats on the database. - IF(@OutputType <> 'NONE') - BEGIN - RAISERROR(N'@Mode=1, we are summarizing.', 0,1) WITH NOWAIT; +CREATE TABLE #working_wait_stats +( + plan_id BIGINT, + wait_category TINYINT, + wait_category_desc NVARCHAR(258), + total_query_wait_time_ms BIGINT, + avg_query_wait_time_ms DECIMAL(38, 2), + last_query_wait_time_ms BIGINT, + min_query_wait_time_ms BIGINT, + max_query_wait_time_ms BIGINT, + wait_category_mapped AS CASE wait_category + WHEN 0 THEN N'UNKNOWN' + WHEN 1 THEN N'SOS_SCHEDULER_YIELD' + WHEN 2 THEN N'THREADPOOL' + WHEN 3 THEN N'LCK_M_%' + WHEN 4 THEN N'LATCH_%' + WHEN 5 THEN N'PAGELATCH_%' + WHEN 6 THEN N'PAGEIOLATCH_%' + WHEN 7 THEN N'RESOURCE_SEMAPHORE_QUERY_COMPILE' + WHEN 8 THEN N'CLR%, SQLCLR%' + WHEN 9 THEN N'DBMIRROR%' + WHEN 10 THEN N'XACT%, DTC%, TRAN_MARKLATCH_%, MSQL_XACT_%, TRANSACTION_MUTEX' + WHEN 11 THEN N'SLEEP_%, LAZYWRITER_SLEEP, SQLTRACE_BUFFER_FLUSH, SQLTRACE_INCREMENTAL_FLUSH_SLEEP, SQLTRACE_WAIT_ENTRIES, FT_IFTS_SCHEDULER_IDLE_WAIT, XE_DISPATCHER_WAIT, REQUEST_FOR_DEADLOCK_SEARCH, LOGMGR_QUEUE, ONDEMAND_TASK_QUEUE, CHECKPOINT_QUEUE, XE_TIMER_EVENT' + WHEN 12 THEN N'PREEMPTIVE_%' + WHEN 13 THEN N'BROKER_% (but not BROKER_RECEIVE_WAITFOR)' + WHEN 14 THEN N'LOGMGR, LOGBUFFER, LOGMGR_RESERVE_APPEND, LOGMGR_FLUSH, LOGMGR_PMM_LOG, CHKPT, WRITELOG' + WHEN 15 THEN N'ASYNC_NETWORK_IO, NET_WAITFOR_PACKET, PROXY_NETWORK_IO, EXTERNAL_SCRIPT_NETWORK_IOF' + WHEN 16 THEN N'CXPACKET, EXCHANGE, CXCONSUMER' + WHEN 17 THEN N'RESOURCE_SEMAPHORE, CMEMTHREAD, CMEMPARTITIONED, EE_PMOLOCK, MEMORY_ALLOCATION_EXT, RESERVED_MEMORY_ALLOCATION_EXT, MEMORY_GRANT_UPDATE' + WHEN 18 THEN N'WAITFOR, WAIT_FOR_RESULTS, BROKER_RECEIVE_WAITFOR' + WHEN 19 THEN N'TRACEWRITE, SQLTRACE_LOCK, SQLTRACE_FILE_BUFFER, SQLTRACE_FILE_WRITE_IO_COMPLETION, SQLTRACE_FILE_READ_IO_COMPLETION, SQLTRACE_PENDING_BUFFER_WRITERS, SQLTRACE_SHUTDOWN, QUERY_TRACEOUT, TRACE_EVTNOTIFF' + WHEN 20 THEN N'FT_RESTART_CRAWL, FULLTEXT GATHERER, MSSEARCH, FT_METADATA_MUTEX, FT_IFTSHC_MUTEX, FT_IFTSISM_MUTEX, FT_IFTS_RWLOCK, FT_COMPROWSET_RWLOCK, FT_MASTER_MERGE, FT_PROPERTYLIST_CACHE, FT_MASTER_MERGE_COORDINATOR, PWAIT_RESOURCE_SEMAPHORE_FT_PARALLEL_QUERY_SYNC' + WHEN 21 THEN N'ASYNC_IO_COMPLETION, IO_COMPLETION, BACKUPIO, WRITE_COMPLETION, IO_QUEUE_LIMIT, IO_RETRY' + WHEN 22 THEN N'SE_REPL_%, REPL_%, HADR_% (but not HADR_THROTTLE_LOG_RATE_GOVERNOR), PWAIT_HADR_%, REPLICA_WRITES, FCB_REPLICA_WRITE, FCB_REPLICA_READ, PWAIT_HADRSIM' + WHEN 23 THEN N'LOG_RATE_GOVERNOR, POOL_LOG_RATE_GOVERNOR, HADR_THROTTLE_LOG_RATE_GOVERNOR, INSTANCE_LOG_RATE_GOVERNOR' + END, + INDEX wws_ix_ids CLUSTERED ( plan_id) +); - SELECT DB_NAME(i.database_id) AS [Database Name], - CAST((COUNT(*)) AS NVARCHAR(256)) AS [Number Objects], - CAST(CAST(SUM(sz.total_reserved_MB)/ - 1024. AS NUMERIC(29,1)) AS NVARCHAR(500)) AS [All GB], - CAST(CAST(SUM(sz.total_reserved_LOB_MB)/ - 1024. AS NUMERIC(29,1)) AS NVARCHAR(500)) AS [LOB GB], - CAST(CAST(SUM(sz.total_reserved_row_overflow_MB)/ - 1024. AS NUMERIC(29,1)) AS NVARCHAR(500)) AS [Row Overflow GB], - CAST(SUM(CASE WHEN index_id=1 THEN 1 ELSE 0 END)AS NVARCHAR(50)) AS [Clustered Tables], - CAST(SUM(CASE WHEN index_id=1 THEN sz.total_reserved_MB ELSE 0 END) - /1024. AS NUMERIC(29,1)) AS [Clustered Tables GB], - SUM(CASE WHEN index_id NOT IN (0,1) THEN 1 ELSE 0 END) AS [NC Indexes], - CAST(SUM(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) - /1024. AS NUMERIC(29,1)) AS [NC Indexes GB], - CASE WHEN SUM(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) > 0 THEN - CAST(SUM(CASE WHEN index_id IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) - / SUM(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) AS NUMERIC(29,1)) - ELSE 0 END AS [ratio table: NC Indexes], - SUM(CASE WHEN index_id=0 THEN 1 ELSE 0 END) AS [Heaps], - CAST(SUM(CASE WHEN index_id=0 THEN sz.total_reserved_MB ELSE 0 END) - /1024. AS NUMERIC(29,1)) AS [Heaps GB], - SUM(CASE WHEN index_id IN (0,1) AND partition_key_column_name IS NOT NULL THEN 1 ELSE 0 END) AS [Partitioned Tables], - SUM(CASE WHEN index_id NOT IN (0,1) AND partition_key_column_name IS NOT NULL THEN 1 ELSE 0 END) AS [Partitioned NCs], - CAST(SUM(CASE WHEN partition_key_column_name IS NOT NULL THEN sz.total_reserved_MB ELSE 0 END)/1024. AS NUMERIC(29,1)) AS [Partitioned GB], - SUM(CASE WHEN filter_definition <> '' THEN 1 ELSE 0 END) AS [Filtered Indexes], - SUM(CASE WHEN is_indexed_view=1 THEN 1 ELSE 0 END) AS [Indexed Views], - MAX(total_rows) AS [Max Row Count], - CAST(MAX(CASE WHEN index_id IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) - /1024. AS NUMERIC(29,1)) AS [Max Table GB], - CAST(MAX(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) - /1024. AS NUMERIC(29,1)) AS [Max NC Index GB], - SUM(CASE WHEN index_id IN (0,1) AND sz.total_reserved_MB > 1024 THEN 1 ELSE 0 END) AS [Count Tables > 1GB], - SUM(CASE WHEN index_id IN (0,1) AND sz.total_reserved_MB > 10240 THEN 1 ELSE 0 END) AS [Count Tables > 10GB], - SUM(CASE WHEN index_id IN (0,1) AND sz.total_reserved_MB > 102400 THEN 1 ELSE 0 END) AS [Count Tables > 100GB], - SUM(CASE WHEN index_id NOT IN (0,1) AND sz.total_reserved_MB > 1024 THEN 1 ELSE 0 END) AS [Count NCs > 1GB], - SUM(CASE WHEN index_id NOT IN (0,1) AND sz.total_reserved_MB > 10240 THEN 1 ELSE 0 END) AS [Count NCs > 10GB], - SUM(CASE WHEN index_id NOT IN (0,1) AND sz.total_reserved_MB > 102400 THEN 1 ELSE 0 END) AS [Count NCs > 100GB], - MIN(create_date) AS [Oldest Create Date], - MAX(create_date) AS [Most Recent Create Date], - MAX(modify_date) AS [Most Recent Modify Date], - 1 AS [Display Order] - FROM #IndexSanity AS i - --left join here so we don't lose disabled nc indexes - LEFT JOIN #IndexSanitySize AS sz - ON i.index_sanity_id=sz.index_sanity_id - GROUP BY DB_NAME(i.database_id) - UNION ALL - SELECT CASE WHEN @GetAllDatabases = 1 THEN N'All Databases' ELSE N'Database ' + N' as of ' + CONVERT(NVARCHAR(16),GETDATE(),121) END, - @ScriptVersionName, - N'From Your Community Volunteers' , - N'http://FirstResponderKit.org' , - @DaysUptimeInsertValue, - NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL, - NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL, - NULL,NULL,0 AS display_order - ORDER BY [Display Order] ASC - OPTION (RECOMPILE); - END; - - END; /* End @Mode=1 (summarize)*/ - ELSE IF (@Mode=2) /*Index Detail*/ - BEGIN - --This mode just spits out all the detail without filters. - --This supports slicing AND dicing in Excel - RAISERROR(N'@Mode=2, here''s the details on existing indexes.', 0,1) WITH NOWAIT; - - /* Checks if @OutputServerName is populated with a valid linked server, and that the database name specified is valid */ - DECLARE @ValidOutputServer BIT; - DECLARE @ValidOutputLocation BIT; - DECLARE @LinkedServerDBCheck NVARCHAR(2000); - DECLARE @ValidLinkedServerDB INT; - DECLARE @tmpdbchk TABLE (cnt INT); - DECLARE @StringToExecute NVARCHAR(MAX); - - IF @OutputServerName IS NOT NULL - BEGIN - IF (SUBSTRING(@OutputTableName, 2, 1) = '#') - BEGIN - RAISERROR('Due to the nature of temporary tables, outputting to a linked server requires a permanent table.', 16, 0); - END; - ELSE IF EXISTS (SELECT server_id FROM sys.servers WHERE QUOTENAME([name]) = @OutputServerName) - BEGIN - SET @LinkedServerDBCheck = 'SELECT 1 WHERE EXISTS (SELECT * FROM '+@OutputServerName+'.master.sys.databases WHERE QUOTENAME([name]) = '''+@OutputDatabaseName+''')'; - INSERT INTO @tmpdbchk EXEC sys.sp_executesql @LinkedServerDBCheck; - SET @ValidLinkedServerDB = (SELECT COUNT(*) FROM @tmpdbchk); - IF (@ValidLinkedServerDB > 0) - BEGIN - SET @ValidOutputServer = 1; - SET @ValidOutputLocation = 1; - END; - ELSE - RAISERROR('The specified database was not found on the output server', 16, 0); - END; - ELSE - BEGIN - RAISERROR('The specified output server was not found', 16, 0); - END; - END; - ELSE - BEGIN - IF (SUBSTRING(@OutputTableName, 2, 2) = '##') - BEGIN - SET @StringToExecute = N' IF (OBJECT_ID(''[tempdb].[dbo].@@@OutputTableName@@@'') IS NOT NULL) DROP TABLE @@@OutputTableName@@@'; - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); - EXEC(@StringToExecute); - - SET @OutputServerName = QUOTENAME(CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128))); - SET @OutputDatabaseName = '[tempdb]'; - SET @OutputSchemaName = '[dbo]'; - SET @ValidOutputLocation = 1; - END; - ELSE IF (SUBSTRING(@OutputTableName, 2, 1) = '#') - BEGIN - RAISERROR('Due to the nature of Dymamic SQL, only global (i.e. double pound (##)) temp tables are supported for @OutputTableName', 16, 0); - END; - ELSE IF @OutputDatabaseName IS NOT NULL - AND @OutputSchemaName IS NOT NULL - AND @OutputTableName IS NOT NULL - AND EXISTS ( SELECT * - FROM sys.databases - WHERE QUOTENAME([name]) = @OutputDatabaseName) - BEGIN - SET @ValidOutputLocation = 1; - SET @OutputServerName = QUOTENAME(CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128))); - END; - ELSE IF @OutputDatabaseName IS NOT NULL - AND @OutputSchemaName IS NOT NULL - AND @OutputTableName IS NOT NULL - AND NOT EXISTS ( SELECT * - FROM sys.databases - WHERE QUOTENAME([name]) = @OutputDatabaseName) - BEGIN - RAISERROR('The specified output database was not found on this server', 16, 0); - END; - ELSE - BEGIN - SET @ValidOutputLocation = 0; - END; - END; - - IF (@ValidOutputLocation = 0 AND @OutputType = 'NONE') - BEGIN - RAISERROR('Invalid output location and no output asked',12,1); - RETURN; - END; - - /* @OutputTableName lets us export the results to a permanent table */ - DECLARE @RunID UNIQUEIDENTIFIER; - SET @RunID = NEWID(); - - IF (@ValidOutputLocation = 1 AND COALESCE(@OutputServerName, @OutputDatabaseName, @OutputSchemaName, @OutputTableName) IS NOT NULL) - BEGIN - DECLARE @TableExists BIT; - DECLARE @SchemaExists BIT; - SET @StringToExecute = - N'SET @SchemaExists = 0; - SET @TableExists = 0; - IF EXISTS(SELECT * FROM @@@OutputServerName@@@.@@@OutputDatabaseName@@@.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''@@@OutputSchemaName@@@'') - SET @SchemaExists = 1 - IF EXISTS (SELECT * FROM @@@OutputServerName@@@.@@@OutputDatabaseName@@@.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''@@@OutputSchemaName@@@'' AND QUOTENAME(TABLE_NAME) = ''@@@OutputTableName@@@'') - SET @TableExists = 1'; - - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputServerName@@@', @OutputServerName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputSchemaName@@@', @OutputSchemaName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); - - EXEC sp_executesql @StringToExecute, N'@TableExists BIT OUTPUT, @SchemaExists BIT OUTPUT', @TableExists OUTPUT, @SchemaExists OUTPUT; - - IF @SchemaExists = 1 - BEGIN - IF @TableExists = 0 - BEGIN - SET @StringToExecute = - N'CREATE TABLE @@@OutputDatabaseName@@@.@@@OutputSchemaName@@@.@@@OutputTableName@@@ - ( - [id] INT IDENTITY(1,1) NOT NULL, - [run_id] UNIQUEIDENTIFIER, - [run_datetime] DATETIME, - [server_name] NVARCHAR(128), - [database_name] NVARCHAR(128), - [schema_name] NVARCHAR(128), - [table_name] NVARCHAR(128), - [index_name] NVARCHAR(128), - [Drop_Tsql] NVARCHAR(MAX), - [Create_Tsql] NVARCHAR(MAX), - [index_id] INT, - [db_schema_object_indexid] NVARCHAR(500), - [object_type] NVARCHAR(15), - [index_definition] NVARCHAR(MAX), - [key_column_names_with_sort_order] NVARCHAR(MAX), - [count_key_columns] INT, - [include_column_names] NVARCHAR(MAX), - [count_included_columns] INT, - [secret_columns] NVARCHAR(MAX), - [count_secret_columns] INT, - [partition_key_column_name] NVARCHAR(MAX), - [filter_definition] NVARCHAR(MAX), - [is_indexed_view] BIT, - [is_primary_key] BIT, - [is_XML] BIT, - [is_spatial] BIT, - [is_NC_columnstore] BIT, - [is_CX_columnstore] BIT, - [is_in_memory_oltp] BIT, - [is_disabled] BIT, - [is_hypothetical] BIT, - [is_padded] BIT, - [fill_factor] INT, - [is_referenced_by_foreign_key] BIT, - [last_user_seek] DATETIME, - [last_user_scan] DATETIME, - [last_user_lookup] DATETIME, - [last_user_update] DATETIME, - [total_reads] BIGINT, - [user_updates] BIGINT, - [reads_per_write] MONEY, - [index_usage_summary] NVARCHAR(200), - [total_singleton_lookup_count] BIGINT, - [total_range_scan_count] BIGINT, - [total_leaf_delete_count] BIGINT, - [total_leaf_update_count] BIGINT, - [index_op_stats] NVARCHAR(200), - [partition_count] INT, - [total_rows] BIGINT, - [total_reserved_MB] NUMERIC(29,2), - [total_reserved_LOB_MB] NUMERIC(29,2), - [total_reserved_row_overflow_MB] NUMERIC(29,2), - [index_size_summary] NVARCHAR(300), - [total_row_lock_count] BIGINT, - [total_row_lock_wait_count] BIGINT, - [total_row_lock_wait_in_ms] BIGINT, - [avg_row_lock_wait_in_ms] BIGINT, - [total_page_lock_count] BIGINT, - [total_page_lock_wait_count] BIGINT, - [total_page_lock_wait_in_ms] BIGINT, - [avg_page_lock_wait_in_ms] BIGINT, - [total_index_lock_promotion_attempt_count] BIGINT, - [total_index_lock_promotion_count] BIGINT, - [data_compression_desc] NVARCHAR(4000), - [page_latch_wait_count] BIGINT, - [page_latch_wait_in_ms] BIGINT, - [page_io_latch_wait_count] BIGINT, - [page_io_latch_wait_in_ms] BIGINT, - [create_date] DATETIME, - [modify_date] DATETIME, - [more_info] NVARCHAR(500), - [display_order] INT, - CONSTRAINT [PK_ID_@@@RunID@@@] PRIMARY KEY CLUSTERED ([id] ASC) - );'; - - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputSchemaName@@@', @OutputSchemaName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@RunID@@@', @RunID); - - IF @ValidOutputServer = 1 - BEGIN - SET @StringToExecute = REPLACE(@StringToExecute,'''',''''''); - EXEC('EXEC('''+@StringToExecute+''') AT ' + @OutputServerName); - END; - ELSE - BEGIN - EXEC(@StringToExecute); - END; - END; /* @TableExists = 0 */ - - SET @StringToExecute = - N'IF EXISTS(SELECT * FROM @@@OutputServerName@@@.@@@OutputDatabaseName@@@.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''@@@OutputSchemaName@@@'') - AND NOT EXISTS (SELECT * FROM @@@OutputServerName@@@.@@@OutputDatabaseName@@@.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''@@@OutputSchemaName@@@'' AND QUOTENAME(TABLE_NAME) = ''@@@OutputTableName@@@'') - SET @TableExists = 0 - ELSE - SET @TableExists = 1'; - - SET @TableExists = NULL; - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputServerName@@@', @OutputServerName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputSchemaName@@@', @OutputSchemaName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); - - EXEC sp_executesql @StringToExecute, N'@TableExists BIT OUTPUT', @TableExists OUTPUT; - - IF @TableExists = 1 - BEGIN - SET @StringToExecute = - N'INSERT @@@OutputServerName@@@.@@@OutputDatabaseName@@@.@@@OutputSchemaName@@@.@@@OutputTableName@@@ - ( - [run_id], - [run_datetime], - [server_name], - [database_name], - [schema_name], - [table_name], - [index_name], - [Drop_Tsql], - [Create_Tsql], - [index_id], - [db_schema_object_indexid], - [object_type], - [index_definition], - [key_column_names_with_sort_order], - [count_key_columns], - [include_column_names], - [count_included_columns], - [secret_columns], - [count_secret_columns], - [partition_key_column_name], - [filter_definition], - [is_indexed_view], - [is_primary_key], - [is_XML], - [is_spatial], - [is_NC_columnstore], - [is_CX_columnstore], - [is_in_memory_oltp], - [is_disabled], - [is_hypothetical], - [is_padded], - [fill_factor], - [is_referenced_by_foreign_key], - [last_user_seek], - [last_user_scan], - [last_user_lookup], - [last_user_update], - [total_reads], - [user_updates], - [reads_per_write], - [index_usage_summary], - [total_singleton_lookup_count], - [total_range_scan_count], - [total_leaf_delete_count], - [total_leaf_update_count], - [index_op_stats], - [partition_count], - [total_rows], - [total_reserved_MB], - [total_reserved_LOB_MB], - [total_reserved_row_overflow_MB], - [index_size_summary], - [total_row_lock_count], - [total_row_lock_wait_count], - [total_row_lock_wait_in_ms], - [avg_row_lock_wait_in_ms], - [total_page_lock_count], - [total_page_lock_wait_count], - [total_page_lock_wait_in_ms], - [avg_page_lock_wait_in_ms], - [total_index_lock_promotion_attempt_count], - [total_index_lock_promotion_count], - [data_compression_desc], - [page_latch_wait_count], - [page_latch_wait_in_ms], - [page_io_latch_wait_count], - [page_io_latch_wait_in_ms], - [create_date], - [modify_date], - [more_info], - [display_order] - ) - SELECT ''@@@RunID@@@'', - ''@@@GETDATE@@@'', - ''@@@LocalServerName@@@'', - -- Below should be a copy/paste of the real query - -- Make sure all quotes are escaped - i.[database_name] AS [Database Name], - i.[schema_name] AS [Schema Name], - i.[object_name] AS [Object Name], - ISNULL(i.index_name, '''') AS [Index Name], - CASE - WHEN i.is_primary_key = 1 AND i.index_definition <> ''[HEAP]'' - THEN N''-ALTER TABLE '' + QUOTENAME(i.[database_name]) + N''.'' + QUOTENAME(i.[schema_name]) + N''.'' + QUOTENAME(i.[object_name]) + - N'' DROP CONSTRAINT '' + QUOTENAME(i.index_name) + N'';'' - WHEN i.is_primary_key = 0 AND i.index_definition <> ''[HEAP]'' - THEN N''--DROP INDEX ''+ QUOTENAME(i.index_name) + N'' ON '' + QUOTENAME(i.[database_name]) + N''.'' + - QUOTENAME(i.[schema_name]) + N''.'' + QUOTENAME(i.[object_name]) + N'';'' - ELSE N'''' - END AS [Drop TSQL], - CASE - WHEN i.index_definition = ''[HEAP]'' THEN N'''' - ELSE N''--'' + ict.create_tsql END AS [Create TSQL], - CAST(i.index_id AS NVARCHAR(10))AS [Index ID], - db_schema_object_indexid AS [Details: schema.table.index(indexid)], - CASE WHEN index_id IN ( 1, 0 ) THEN ''TABLE'' - ELSE ''NonClustered'' - END AS [Object Type], - LEFT(index_definition,4000) AS [Definition: [Property]] ColumnName {datatype maxbytes}], - ISNULL(LTRIM(key_column_names_with_sort_order), '''') AS [Key Column Names With Sort], - ISNULL(count_key_columns, 0) AS [Count Key Columns], - ISNULL(include_column_names, '''') AS [Include Column Names], - ISNULL(count_included_columns,0) AS [Count Included Columns], - ISNULL(secret_columns,'''') AS [Secret Column Names], - ISNULL(count_secret_columns,0) AS [Count Secret Columns], - ISNULL(partition_key_column_name, '''') AS [Partition Key Column Name], - ISNULL(filter_definition, '''') AS [Filter Definition], - is_indexed_view AS [Is Indexed View], - is_primary_key AS [Is Primary Key], - is_XML AS [Is XML], - is_spatial AS [Is Spatial], - is_NC_columnstore AS [Is NC Columnstore], - is_CX_columnstore AS [Is CX Columnstore], - is_in_memory_oltp AS [Is In-Memory OLTP], - is_disabled AS [Is Disabled], - is_hypothetical AS [Is Hypothetical], - is_padded AS [Is Padded], - fill_factor AS [Fill Factor], - is_referenced_by_foreign_key AS [Is Reference by Foreign Key], - last_user_seek AS [Last User Seek], - last_user_scan AS [Last User Scan], - last_user_lookup AS [Last User Lookup], - last_user_update AS [Last User Update], - total_reads AS [Total Reads], - user_updates AS [User Updates], - reads_per_write AS [Reads Per Write], - index_usage_summary AS [Index Usage], - sz.total_singleton_lookup_count AS [Singleton Lookups], - sz.total_range_scan_count AS [Range Scans], - sz.total_leaf_delete_count AS [Leaf Deletes], - sz.total_leaf_update_count AS [Leaf Updates], - sz.index_op_stats AS [Index Op Stats], - sz.partition_count AS [Partition Count], - sz.total_rows AS [Rows], - sz.total_reserved_MB AS [Reserved MB], - sz.total_reserved_LOB_MB AS [Reserved LOB MB], - sz.total_reserved_row_overflow_MB AS [Reserved Row Overflow MB], - sz.index_size_summary AS [Index Size], - sz.total_row_lock_count AS [Row Lock Count], - sz.total_row_lock_wait_count AS [Row Lock Wait Count], - sz.total_row_lock_wait_in_ms AS [Row Lock Wait ms], - sz.avg_row_lock_wait_in_ms AS [Avg Row Lock Wait ms], - sz.total_page_lock_count AS [Page Lock Count], - sz.total_page_lock_wait_count AS [Page Lock Wait Count], - sz.total_page_lock_wait_in_ms AS [Page Lock Wait ms], - sz.avg_page_lock_wait_in_ms AS [Avg Page Lock Wait ms], - sz.total_index_lock_promotion_attempt_count AS [Lock Escalation Attempts], - sz.total_index_lock_promotion_count AS [Lock Escalations], - sz.data_compression_desc AS [Data Compression], - sz.page_latch_wait_count, - sz.page_latch_wait_in_ms, - sz.page_io_latch_wait_count, - sz.page_io_latch_wait_in_ms, - i.create_date AS [Create Date], - i.modify_date AS [Modify Date], - more_info AS [More Info], - 1 AS [Display Order] - FROM #IndexSanity AS i - LEFT JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id - LEFT JOIN #IndexCreateTsql AS ict ON i.index_sanity_id = ict.index_sanity_id - ORDER BY [Database Name], [Schema Name], [Object Name], [Index ID] - OPTION (RECOMPILE);'; - - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputServerName@@@', @OutputServerName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputSchemaName@@@', @OutputSchemaName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@RunID@@@', @RunID); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@GETDATE@@@', GETDATE()); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@LocalServerName@@@', CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128))); - EXEC(@StringToExecute); - END; /* @TableExists = 1 */ - ELSE - RAISERROR('Creation of the output table failed.', 16, 0); - END; /* @TableExists = 0 */ - ELSE - RAISERROR (N'Invalid schema name, data could not be saved.', 16, 0); - END; /* @ValidOutputLocation = 1 */ - ELSE - - IF(@OutputType <> 'NONE') - BEGIN - SELECT i.[database_name] AS [Database Name], - i.[schema_name] AS [Schema Name], - i.[object_name] AS [Object Name], - ISNULL(i.index_name, '') AS [Index Name], - CAST(i.index_id AS NVARCHAR(10))AS [Index ID], - db_schema_object_indexid AS [Details: schema.table.index(indexid)], - CASE WHEN index_id IN ( 1, 0 ) THEN 'TABLE' - ELSE 'NonClustered' - END AS [Object Type], - index_definition AS [Definition: [Property]] ColumnName {datatype maxbytes}], - ISNULL(LTRIM(key_column_names_with_sort_order), '') AS [Key Column Names With Sort], - ISNULL(count_key_columns, 0) AS [Count Key Columns], - ISNULL(include_column_names, '') AS [Include Column Names], - ISNULL(count_included_columns,0) AS [Count Included Columns], - ISNULL(secret_columns,'') AS [Secret Column Names], - ISNULL(count_secret_columns,0) AS [Count Secret Columns], - ISNULL(partition_key_column_name, '') AS [Partition Key Column Name], - ISNULL(filter_definition, '') AS [Filter Definition], - is_indexed_view AS [Is Indexed View], - is_primary_key AS [Is Primary Key], - is_XML AS [Is XML], - is_spatial AS [Is Spatial], - is_NC_columnstore AS [Is NC Columnstore], - is_CX_columnstore AS [Is CX Columnstore], - is_in_memory_oltp AS [Is In-Memory OLTP], - is_disabled AS [Is Disabled], - is_hypothetical AS [Is Hypothetical], - is_padded AS [Is Padded], - fill_factor AS [Fill Factor], - is_referenced_by_foreign_key AS [Is Reference by Foreign Key], - last_user_seek AS [Last User Seek], - last_user_scan AS [Last User Scan], - last_user_lookup AS [Last User Lookup], - last_user_update AS [Last User Update], - total_reads AS [Total Reads], - user_updates AS [User Updates], - reads_per_write AS [Reads Per Write], - index_usage_summary AS [Index Usage], - sz.total_singleton_lookup_count AS [Singleton Lookups], - sz.total_range_scan_count AS [Range Scans], - sz.total_leaf_delete_count AS [Leaf Deletes], - sz.total_leaf_update_count AS [Leaf Updates], - sz.index_op_stats AS [Index Op Stats], - sz.partition_count AS [Partition Count], - sz.total_rows AS [Rows], - sz.total_reserved_MB AS [Reserved MB], - sz.total_reserved_LOB_MB AS [Reserved LOB MB], - sz.total_reserved_row_overflow_MB AS [Reserved Row Overflow MB], - sz.index_size_summary AS [Index Size], - sz.total_row_lock_count AS [Row Lock Count], - sz.total_row_lock_wait_count AS [Row Lock Wait Count], - sz.total_row_lock_wait_in_ms AS [Row Lock Wait ms], - sz.avg_row_lock_wait_in_ms AS [Avg Row Lock Wait ms], - sz.total_page_lock_count AS [Page Lock Count], - sz.total_page_lock_wait_count AS [Page Lock Wait Count], - sz.total_page_lock_wait_in_ms AS [Page Lock Wait ms], - sz.avg_page_lock_wait_in_ms AS [Avg Page Lock Wait ms], - sz.total_index_lock_promotion_attempt_count AS [Lock Escalation Attempts], - sz.total_index_lock_promotion_count AS [Lock Escalations], - sz.page_latch_wait_count AS [Page Latch Wait Count], - sz.page_latch_wait_in_ms AS [Page Latch Wait ms], - sz.page_io_latch_wait_count AS [Page IO Latch Wait Count], - sz.page_io_latch_wait_in_ms as [Page IO Latch Wait ms], - sz.total_forwarded_fetch_count AS [Forwarded Fetches], - sz.data_compression_desc AS [Data Compression], - i.create_date AS [Create Date], - i.modify_date AS [Modify Date], - more_info AS [More Info], - CASE - WHEN i.is_primary_key = 1 AND i.index_definition <> '[HEAP]' - THEN N'--ALTER TABLE ' + QUOTENAME(i.[database_name]) + N'.' + QUOTENAME(i.[schema_name]) + N'.' + QUOTENAME(i.[object_name]) - + N' DROP CONSTRAINT ' + QUOTENAME(i.index_name) + N';' - WHEN i.is_primary_key = 0 AND i.index_definition <> '[HEAP]' - THEN N'--DROP INDEX '+ QUOTENAME(i.index_name) + N' ON ' + QUOTENAME(i.[database_name]) + N'.' + - QUOTENAME(i.[schema_name]) + N'.' + QUOTENAME(i.[object_name]) + N';' - ELSE N'' - END AS [Drop TSQL], - CASE - WHEN i.index_definition = '[HEAP]' THEN N'' - ELSE N'--' + ict.create_tsql END AS [Create TSQL], - 1 AS [Display Order] - FROM #IndexSanity AS i --left join here so we don't lose disabled nc indexes - LEFT JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id - LEFT JOIN #IndexCreateTsql AS ict ON i.index_sanity_id = ict.index_sanity_id - ORDER BY CASE WHEN @SortDirection = 'desc' THEN - CASE WHEN @SortOrder = N'rows' THEN sz.total_rows - WHEN @SortOrder = N'reserved_mb' THEN sz.total_reserved_MB - WHEN @SortOrder = N'size' THEN sz.total_reserved_MB - WHEN @SortOrder = N'reserved_lob_mb' THEN sz.total_reserved_LOB_MB - WHEN @SortOrder = N'lob' THEN sz.total_reserved_LOB_MB - WHEN @SortOrder = N'total_row_lock_wait_in_ms' THEN COALESCE(sz.total_row_lock_wait_in_ms,0) - WHEN @SortOrder = N'total_page_lock_wait_in_ms' THEN COALESCE(sz.total_page_lock_wait_in_ms,0) - WHEN @SortOrder = N'lock_time' THEN (COALESCE(sz.total_row_lock_wait_in_ms,0) + COALESCE(sz.total_page_lock_wait_in_ms,0)) - WHEN @SortOrder = N'total_reads' THEN total_reads - WHEN @SortOrder = N'reads' THEN total_reads - WHEN @SortOrder = N'user_updates' THEN user_updates - WHEN @SortOrder = N'writes' THEN user_updates - WHEN @SortOrder = N'reads_per_write' THEN reads_per_write - WHEN @SortOrder = N'ratio' THEN reads_per_write - WHEN @SortOrder = N'forward_fetches' THEN sz.total_forwarded_fetch_count - WHEN @SortOrder = N'fetches' THEN sz.total_forwarded_fetch_count - ELSE NULL END - ELSE 1 END - DESC, /* Shout out to DHutmacher */ - CASE WHEN @SortDirection = 'asc' THEN - CASE WHEN @SortOrder = N'rows' THEN sz.total_rows - WHEN @SortOrder = N'reserved_mb' THEN sz.total_reserved_MB - WHEN @SortOrder = N'size' THEN sz.total_reserved_MB - WHEN @SortOrder = N'reserved_lob_mb' THEN sz.total_reserved_LOB_MB - WHEN @SortOrder = N'lob' THEN sz.total_reserved_LOB_MB - WHEN @SortOrder = N'total_row_lock_wait_in_ms' THEN COALESCE(sz.total_row_lock_wait_in_ms,0) - WHEN @SortOrder = N'total_page_lock_wait_in_ms' THEN COALESCE(sz.total_page_lock_wait_in_ms,0) - WHEN @SortOrder = N'lock_time' THEN (COALESCE(sz.total_row_lock_wait_in_ms,0) + COALESCE(sz.total_page_lock_wait_in_ms,0)) - WHEN @SortOrder = N'total_reads' THEN total_reads - WHEN @SortOrder = N'reads' THEN total_reads - WHEN @SortOrder = N'user_updates' THEN user_updates - WHEN @SortOrder = N'writes' THEN user_updates - WHEN @SortOrder = N'reads_per_write' THEN reads_per_write - WHEN @SortOrder = N'ratio' THEN reads_per_write - WHEN @SortOrder = N'forward_fetches' THEN sz.total_forwarded_fetch_count - WHEN @SortOrder = N'fetches' THEN sz.total_forwarded_fetch_count - ELSE NULL END - ELSE 1 END - ASC, - i.[database_name], [Schema Name], [Object Name], [Index ID] - OPTION (RECOMPILE); - END; - - END; /* End @Mode=2 (index detail)*/ - ELSE IF (@Mode=3) /*Missing index Detail*/ - BEGIN - IF(@OutputType <> 'NONE') - BEGIN; - WITH create_date AS ( - SELECT i.database_id, - i.schema_name, - i.[object_id], - ISNULL(NULLIF(MAX(DATEDIFF(DAY, i.create_date, SYSDATETIME())), 0), 1) AS create_days - FROM #IndexSanity AS i - GROUP BY i.database_id, i.schema_name, i.object_id - ) - SELECT - mi.database_name AS [Database Name], - mi.[schema_name] AS [Schema], - mi.table_name AS [Table], - CAST((mi.magic_benefit_number / CASE WHEN cd.create_days < @DaysUptime THEN cd.create_days ELSE @DaysUptime END) AS BIGINT) - AS [Magic Benefit Number], - mi.missing_index_details AS [Missing Index Details], - mi.avg_total_user_cost AS [Avg Query Cost], - mi.avg_user_impact AS [Est Index Improvement], - mi.user_seeks AS [Seeks], - mi.user_scans AS [Scans], - mi.unique_compiles AS [Compiles], - mi.equality_columns_with_data_type AS [Equality Columns], - mi.inequality_columns_with_data_type AS [Inequality Columns], - mi.included_columns_with_data_type AS [Included Columns], - mi.index_estimated_impact AS [Estimated Impact], - mi.create_tsql AS [Create TSQL], - mi.more_info AS [More Info], - 1 AS [Display Order], - mi.is_low, - mi.sample_query_plan AS [Sample Query Plan] - FROM #MissingIndexes AS mi - LEFT JOIN create_date AS cd - ON mi.[object_id] = cd.object_id - AND mi.database_id = cd.database_id - AND mi.schema_name = cd.schema_name - /* Minimum benefit threshold = 100k/day of uptime OR since table creation date, whichever is lower*/ - WHERE @ShowAllMissingIndexRequests=1 - OR (mi.magic_benefit_number / CASE WHEN cd.create_days < @DaysUptime THEN cd.create_days ELSE @DaysUptime END) >= 100000 - UNION ALL - SELECT - @ScriptVersionName, - N'From Your Community Volunteers' , - N'http://FirstResponderKit.org' , - 100000000000, - @DaysUptimeInsertValue, - NULL,NULL,NULL,NULL,NULL,NULL,NULL, - NULL, NULL, NULL, NULL, 0 AS [Display Order], NULL AS is_low, NULL - ORDER BY [Display Order] ASC, [Magic Benefit Number] DESC - OPTION (RECOMPILE); - END; - - IF (@BringThePain = 1 - AND @DatabaseName IS NOT NULL - AND @GetAllDatabases = 0) +/* +The next three tables hold plan XML parsed out to different degrees +*/ +DROP TABLE IF EXISTS #statements; - BEGIN +CREATE TABLE #statements +( + plan_id BIGINT, + query_id BIGINT, + query_hash BINARY(8), + sql_handle VARBINARY(64), + statement XML, + is_cursor BIT + INDEX s_ix_ids CLUSTERED (plan_id, query_id, query_hash, sql_handle) +); - EXEC sp_BlitzCache @SortOrder = 'sp_BlitzIndex', @DatabaseName = @DatabaseName, @BringThePain = 1, @QueryFilter = 'statement', @HideSummary = 1; - - END; +DROP TABLE IF EXISTS #query_plan; - END; /* End @Mode=3 (index detail)*/ +CREATE TABLE #query_plan +( + plan_id BIGINT, + query_id BIGINT, + query_hash BINARY(8), + sql_handle VARBINARY(64), + query_plan XML, + INDEX qp_ix_ids CLUSTERED (plan_id, query_id, query_hash, sql_handle) +); -END TRY -BEGIN CATCH - RAISERROR (N'Failure analyzing temp tables.', 0,1) WITH NOWAIT; +DROP TABLE IF EXISTS #relop; - SELECT @msg = ERROR_MESSAGE(), @ErrorSeverity = ERROR_SEVERITY(), @ErrorState = ERROR_STATE(); +CREATE TABLE #relop +( + plan_id BIGINT, + query_id BIGINT, + query_hash BINARY(8), + sql_handle VARBINARY(64), + relop XML, + INDEX ix_ids CLUSTERED (plan_id, query_id, query_hash, sql_handle) +); - RAISERROR (@msg, - @ErrorSeverity, - @ErrorState - ); - - WHILE @@trancount > 0 - ROLLBACK; - RETURN; - END CATCH; -GO -IF OBJECT_ID('dbo.sp_BlitzLock') IS NULL - EXEC ('CREATE PROCEDURE dbo.sp_BlitzLock AS RETURN 0;'); -GO +DROP TABLE IF EXISTS #plan_cost; -ALTER PROCEDURE dbo.sp_BlitzLock +CREATE TABLE #plan_cost ( - @Top INT = 2147483647, - @DatabaseName NVARCHAR(256) = NULL, - @StartDate DATETIME = '19000101', - @EndDate DATETIME = '99991231', - @ObjectName NVARCHAR(1000) = NULL, - @StoredProcName NVARCHAR(1000) = NULL, - @AppName NVARCHAR(256) = NULL, - @HostName NVARCHAR(256) = NULL, - @LoginName NVARCHAR(256) = NULL, - @EventSessionPath VARCHAR(256) = 'system_health*.xel', - @VictimsOnly BIT = 0, - @Debug BIT = 0, - @Help BIT = 0, - @Version VARCHAR(30) = NULL OUTPUT, - @VersionDate DATETIME = NULL OUTPUT, - @VersionCheckMode BIT = 0, - @OutputDatabaseName NVARCHAR(256) = NULL , - @OutputSchemaName NVARCHAR(256) = 'dbo' , --ditto as below - @OutputTableName NVARCHAR(256) = 'BlitzLock', --put a standard here no need to check later in the script - @ExportToExcel BIT = 0 -) -WITH RECOMPILE -AS -BEGIN + query_plan_cost DECIMAL(38,2), + sql_handle VARBINARY(64), + plan_id INT, + INDEX px_ix_ids CLUSTERED (sql_handle, plan_id) +); -SET NOCOUNT ON; -SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.0', @VersionDate = '20210117'; +DROP TABLE IF EXISTS #est_rows; +CREATE TABLE #est_rows +( + estimated_rows DECIMAL(38,2), + query_hash BINARY(8), + INDEX px_ix_ids CLUSTERED (query_hash) +); -IF(@VersionCheckMode = 1) -BEGIN - RETURN; -END; - IF @Help = 1 - BEGIN - PRINT ' - /* - sp_BlitzLock from http://FirstResponderKit.org - - This script checks for and analyzes deadlocks from the system health session or a custom extended event path - Variables you can use: - @Top: Use if you want to limit the number of deadlocks to return. - This is ordered by event date ascending +DROP TABLE IF EXISTS #stats_agg; - @DatabaseName: If you want to filter to a specific database +CREATE TABLE #stats_agg +( + sql_handle VARBINARY(64), + last_update DATETIME2, + modification_count BIGINT, + sampling_percent DECIMAL(38, 2), + [statistics] NVARCHAR(258), + [table] NVARCHAR(258), + [schema] NVARCHAR(258), + [database] NVARCHAR(258), + INDEX sa_ix_ids CLUSTERED (sql_handle) +); - @StartDate: The date you want to start searching on. - @EndDate: The date you want to stop searching on. +DROP TABLE IF EXISTS #trace_flags; - @ObjectName: If you want to filter to a specific able. - The object name has to be fully qualified ''Database.Schema.Table'' +CREATE TABLE #trace_flags +( + sql_handle VARBINARY(54), + global_trace_flags NVARCHAR(4000), + session_trace_flags NVARCHAR(4000), + INDEX tf_ix_ids CLUSTERED (sql_handle) +); - @StoredProcName: If you want to search for a single stored proc - The proc name has to be fully qualified ''Database.Schema.Sproc'' - - @AppName: If you want to filter to a specific application - - @HostName: If you want to filter to a specific host - - @LoginName: If you want to filter to a specific login - @EventSessionPath: If you want to point this at an XE session rather than the system health session. - - @OutputDatabaseName: If you want to output information to a specific database - @OutputSchemaName: Specify a schema name to output information to a specific Schema - @OutputTableName: Specify table name to to output information to a specific table - - To learn more, visit http://FirstResponderKit.org where you can download new - versions for free, watch training videos on how it works, get more info on - the findings, contribute your own code, and more. +DROP TABLE IF EXISTS #warning_results; - Known limitations of this version: - - Only SQL Server 2012 and newer is supported - - If your tables have weird characters in them (https://en.wikipedia.org/wiki/List_of_XML_and_HTML_character_entity_references) you may get errors trying to parse the XML. - I took a long look at this one, and: - 1) Trying to account for all the weird places these could crop up is a losing effort. - 2) Replace is slow af on lots of XML. - - Your mom. +CREATE TABLE #warning_results +( + ID INT IDENTITY(1,1) PRIMARY KEY CLUSTERED, + CheckID INT, + Priority TINYINT, + FindingsGroup NVARCHAR(50), + Finding NVARCHAR(200), + URL NVARCHAR(200), + Details NVARCHAR(4000) +); +/*These next three tables hold information about implicit conversion and cached parameters */ +DROP TABLE IF EXISTS #stored_proc_info; +CREATE TABLE #stored_proc_info +( + sql_handle VARBINARY(64), + query_hash BINARY(8), + variable_name NVARCHAR(258), + variable_datatype NVARCHAR(258), + converted_column_name NVARCHAR(258), + compile_time_value NVARCHAR(258), + proc_name NVARCHAR(1000), + column_name NVARCHAR(4000), + converted_to NVARCHAR(258), + set_options NVARCHAR(1000) + INDEX tf_ix_ids CLUSTERED (sql_handle, query_hash) +); +DROP TABLE IF EXISTS #variable_info; - Unknown limitations of this version: - - None. (If we knew them, they would be known. Duh.) +CREATE TABLE #variable_info +( + query_hash BINARY(8), + sql_handle VARBINARY(64), + proc_name NVARCHAR(1000), + variable_name NVARCHAR(258), + variable_datatype NVARCHAR(258), + compile_time_value NVARCHAR(258), + INDEX vif_ix_ids CLUSTERED (sql_handle, query_hash) +); +DROP TABLE IF EXISTS #conversion_info; - MIT License - - Copyright (c) 2021 Brent Ozar Unlimited +CREATE TABLE #conversion_info +( + query_hash BINARY(8), + sql_handle VARBINARY(64), + proc_name NVARCHAR(128), + expression NVARCHAR(4000), + at_charindex AS CHARINDEX('@', expression), + bracket_charindex AS CHARINDEX(']', expression, CHARINDEX('@', expression)) - CHARINDEX('@', expression), + comma_charindex AS CHARINDEX(',', expression) + 1, + second_comma_charindex AS + CHARINDEX(',', expression, CHARINDEX(',', expression) + 1) - CHARINDEX(',', expression) - 1, + equal_charindex AS CHARINDEX('=', expression) + 1, + paren_charindex AS CHARINDEX('(', expression) + 1, + comma_paren_charindex AS + CHARINDEX(',', expression, CHARINDEX('(', expression) + 1) - CHARINDEX('(', expression) - 1, + convert_implicit_charindex AS CHARINDEX('=CONVERT_IMPLICIT', expression), + INDEX cif_ix_ids CLUSTERED (sql_handle, query_hash) +); - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: +/* These tables support the Missing Index details clickable*/ - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE. +DROP TABLE IF EXISTS #missing_index_xml; - */'; - RETURN; - END; /* @Help = 1 */ - - DECLARE @ProductVersion NVARCHAR(128); - DECLARE @ProductVersionMajor FLOAT; - DECLARE @ProductVersionMinor INT; +CREATE TABLE #missing_index_xml +( + query_hash BINARY(8), + sql_handle VARBINARY(64), + impact FLOAT, + index_xml XML, + INDEX mix_ix_ids CLUSTERED (sql_handle, query_hash) +); - SET @ProductVersion = CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)); +DROP TABLE IF EXISTS #missing_index_schema; - SELECT @ProductVersionMajor = SUBSTRING(@ProductVersion, 1, CHARINDEX('.', @ProductVersion) + 1), - @ProductVersionMinor = PARSENAME(CONVERT(VARCHAR(32), @ProductVersion), 2); +CREATE TABLE #missing_index_schema +( + query_hash BINARY(8), + sql_handle VARBINARY(64), + impact FLOAT, + database_name NVARCHAR(128), + schema_name NVARCHAR(128), + table_name NVARCHAR(128), + index_xml XML, + INDEX mis_ix_ids CLUSTERED (sql_handle, query_hash) +); - IF @ProductVersionMajor < 11.0 - BEGIN - RAISERROR( - 'sp_BlitzLock will throw a bunch of angry errors on versions of SQL Server earlier than 2012.', - 0, - 1) WITH NOWAIT; - RETURN; - END; - - IF ((SELECT SERVERPROPERTY ('EDITION')) = 'SQL Azure' - AND - LOWER(@EventSessionPath) NOT LIKE 'http%') - BEGIN - RAISERROR( - 'The default storage path doesn''t work in Azure SQLDB/Managed instances. -You need to use an Azure storage account, and the path has to look like this: https://StorageAccount.blob.core.windows.net/Container/FileName.xel', - 0, - 1) WITH NOWAIT; - RETURN; - END; +DROP TABLE IF EXISTS #missing_index_usage; +CREATE TABLE #missing_index_usage +( + query_hash BINARY(8), + sql_handle VARBINARY(64), + impact FLOAT, + database_name NVARCHAR(128), + schema_name NVARCHAR(128), + table_name NVARCHAR(128), + usage NVARCHAR(128), + index_xml XML, + INDEX miu_ix_ids CLUSTERED (sql_handle, query_hash) +); - IF @Top IS NULL - SET @Top = 2147483647; +DROP TABLE IF EXISTS #missing_index_detail; - IF @StartDate IS NULL - SET @StartDate = '19000101'; +CREATE TABLE #missing_index_detail +( + query_hash BINARY(8), + sql_handle VARBINARY(64), + impact FLOAT, + database_name NVARCHAR(128), + schema_name NVARCHAR(128), + table_name NVARCHAR(128), + usage NVARCHAR(128), + column_name NVARCHAR(128), + INDEX mid_ix_ids CLUSTERED (sql_handle, query_hash) +); - IF @EndDate IS NULL - SET @EndDate = '99991231'; - - IF OBJECT_ID('tempdb..#deadlock_data') IS NOT NULL - DROP TABLE #deadlock_data; +DROP TABLE IF EXISTS #missing_index_pretty; - IF OBJECT_ID('tempdb..#deadlock_process') IS NOT NULL - DROP TABLE #deadlock_process; +CREATE TABLE #missing_index_pretty +( + query_hash BINARY(8), + sql_handle VARBINARY(64), + impact FLOAT, + database_name NVARCHAR(128), + schema_name NVARCHAR(128), + table_name NVARCHAR(128), + equality NVARCHAR(MAX), + inequality NVARCHAR(MAX), + [include] NVARCHAR(MAX), + is_spool BIT, + details AS N'/* ' + + CHAR(10) + + CASE is_spool + WHEN 0 + THEN N'The Query Processor estimates that implementing the ' + ELSE N'We estimate that implementing the ' + END + + CONVERT(NVARCHAR(30), impact) + + '%.' + + CHAR(10) + + N'*/' + + CHAR(10) + CHAR(13) + + N'/* ' + + CHAR(10) + + N'USE ' + + database_name + + CHAR(10) + + N'GO' + + CHAR(10) + CHAR(13) + + N'CREATE NONCLUSTERED INDEX ix_' + + ISNULL(REPLACE(REPLACE(REPLACE(equality,'[', ''), ']', ''), ', ', '_'), '') + + ISNULL(REPLACE(REPLACE(REPLACE(inequality,'[', ''), ']', ''), ', ', '_'), '') + + CASE WHEN [include] IS NOT NULL THEN + N'_Includes' ELSE N'' END + + CHAR(10) + + N' ON ' + + schema_name + + N'.' + + table_name + + N' (' + + + CASE WHEN equality IS NOT NULL + THEN equality + + CASE WHEN inequality IS NOT NULL + THEN N', ' + inequality + ELSE N'' + END + ELSE inequality + END + + N')' + + CHAR(10) + + CASE WHEN include IS NOT NULL + THEN N'INCLUDE (' + include + N') WITH (FILLFACTOR=100, ONLINE=?, SORT_IN_TEMPDB=?, DATA_COMPRESSION=?);' + ELSE N' WITH (FILLFACTOR=100, ONLINE=?, SORT_IN_TEMPDB=?, DATA_COMPRESSION=?);' + END + + CHAR(10) + + N'GO' + + CHAR(10) + + N'*/', + INDEX mip_ix_ids CLUSTERED (sql_handle, query_hash) +); - IF OBJECT_ID('tempdb..#deadlock_stack') IS NOT NULL - DROP TABLE #deadlock_stack; +DROP TABLE IF EXISTS #index_spool_ugly; - IF OBJECT_ID('tempdb..#deadlock_resource') IS NOT NULL - DROP TABLE #deadlock_resource; +CREATE TABLE #index_spool_ugly +( + query_hash BINARY(8), + sql_handle VARBINARY(64), + impact FLOAT, + database_name NVARCHAR(128), + schema_name NVARCHAR(128), + table_name NVARCHAR(128), + equality NVARCHAR(MAX), + inequality NVARCHAR(MAX), + [include] NVARCHAR(MAX), + INDEX isu_ix_ids CLUSTERED (sql_handle, query_hash) +); - IF OBJECT_ID('tempdb..#deadlock_owner_waiter') IS NOT NULL - DROP TABLE #deadlock_owner_waiter; - IF OBJECT_ID('tempdb..#deadlock_findings') IS NOT NULL - DROP TABLE #deadlock_findings; +/*Sets up WHERE clause that gets used quite a bit*/ - CREATE TABLE #deadlock_findings - ( - id INT IDENTITY(1, 1) PRIMARY KEY CLUSTERED, - check_id INT NOT NULL, - database_name NVARCHAR(256), - object_name NVARCHAR(1000), - finding_group NVARCHAR(100), - finding NVARCHAR(4000) - ); +--Date stuff +--If they're both NULL, we'll just look at the last 7 days +IF (@StartDate IS NULL AND @EndDate IS NULL) + BEGIN + RAISERROR(N'@StartDate and @EndDate are NULL, checking last 7 days', 0, 1) WITH NOWAIT; + SET @sql_where += N' AND qsrs.last_execution_time >= DATEADD(DAY, -7, DATEDIFF(DAY, 0, SYSDATETIME() )) + '; + END; - DECLARE @d VARCHAR(40), @StringToExecute NVARCHAR(4000),@StringToExecuteParams NVARCHAR(500),@r NVARCHAR(200),@OutputTableFindings NVARCHAR(100),@DeadlockCount INT; - DECLARE @ServerName NVARCHAR(256) - DECLARE @OutputDatabaseCheck BIT; - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - SET @OutputTableFindings = '[BlitzLockFindings]' - SET @ServerName = (select @@ServerName) - if(@OutputDatabaseName is not null) - BEGIN --if databaseName is set do some sanity checks and put [] around def. - if( (select name from sys.databases where name=@OutputDatabaseName) is null ) --if database is invalid raiserror and set bitcheck - BEGIN - RAISERROR('Database Name for output of table is invalid please correct, Output to Table will not be preformed', 0, 1, @d) WITH NOWAIT; - set @OutputDatabaseCheck = -1 -- -1 invalid/false, 0 = good/true - END - ELSE - BEGIN - set @OutputDatabaseCheck = 0 - select @StringToExecute = N'select @r = name from ' + '' + @OutputDatabaseName + - '' + '.sys.objects where type_desc=''USER_TABLE'' and name=' + '''' + @OutputTableName + '''', - @StringToExecuteParams = N'@OutputDatabaseName NVARCHAR(200),@OutputTableName NVARCHAR(200),@r NVARCHAR(200) OUTPUT' - exec sp_executesql @StringToExecute,@StringToExecuteParams,@OutputDatabaseName,@OutputTableName,@r OUTPUT - --put covers around all before. - SELECT @OutputDatabaseName = QUOTENAME(@OutputDatabaseName), - @OutputTableName = QUOTENAME(@OutputTableName), - @OutputSchemaName = QUOTENAME(@OutputSchemaName) - if(@r is null) --if it is null there is no table, create it from above execution - BEGIN - select @StringToExecute = N'use ' + @OutputDatabaseName + ';create table ' + @OutputSchemaName + '.' + @OutputTableName + ' ( - ServerName NVARCHAR(256), - deadlock_type NVARCHAR(256), - event_date datetime, - database_name NVARCHAR(256), - deadlock_group NVARCHAR(256), - query XML, - object_names XML, - isolation_level NVARCHAR(256), - owner_mode NVARCHAR(256), - waiter_mode NVARCHAR(256), - transaction_count bigint, - login_name NVARCHAR(256), - host_name NVARCHAR(256), - client_app NVARCHAR(256), - wait_time BIGINT, - priority smallint, - log_used BIGINT, - last_tran_started datetime, - last_batch_started datetime, - last_batch_completed datetime, - transaction_name NVARCHAR(256), - owner_waiter_type NVARCHAR(256), - owner_activity NVARCHAR(256), - owner_waiter_activity NVARCHAR(256), - owner_merging NVARCHAR(256), - owner_spilling NVARCHAR(256), - owner_waiting_to_close NVARCHAR(256), - waiter_waiter_type NVARCHAR(256), - waiter_owner_activity NVARCHAR(256), - waiter_waiter_activity NVARCHAR(256), - waiter_merging NVARCHAR(256), - waiter_spilling NVARCHAR(256), - waiter_waiting_to_close NVARCHAR(256), - deadlock_graph XML)', - @StringToExecuteParams = N'@OutputDatabaseName NVARCHAR(200),@OutputSchemaName NVARCHAR(100),@OutputTableName NVARCHAR(200)' - exec sp_executesql @StringToExecute, @StringToExecuteParams,@OutputDatabaseName,@OutputSchemaName,@OutputTableName - --table created. - select @StringToExecute = N'select @r = name from ' + '' + @OutputDatabaseName + - '' + '.sys.objects where type_desc=''USER_TABLE'' and name=''BlitzLockFindings''', - @StringToExecuteParams = N'@OutputDatabaseName NVARCHAR(200),@r NVARCHAR(200) OUTPUT' - exec sp_executesql @StringToExecute,@StringToExecuteParams,@OutputDatabaseName,@r OUTPUT - if(@r is null) --if table does not excist - BEGIN - select @OutputTableFindings=N'[BlitzLockFindings]', - @StringToExecute = N'use ' + @OutputDatabaseName + ';create table ' + @OutputSchemaName + '.' + @OutputTableFindings + ' ( - ServerName NVARCHAR(256), - check_id INT, - database_name NVARCHAR(256), - object_name NVARCHAR(1000), - finding_group NVARCHAR(100), - finding NVARCHAR(4000))', - @StringToExecuteParams = N'@OutputDatabaseName NVARCHAR(200),@OutputSchemaName NVARCHAR(100),@OutputTableFindings NVARCHAR(200)' - exec sp_executesql @StringToExecute, @StringToExecuteParams, @OutputDatabaseName,@OutputSchemaName,@OutputTableFindings - - END +--Hey, that's nice of me +IF @StartDate IS NOT NULL + BEGIN + RAISERROR(N'Setting start date filter', 0, 1) WITH NOWAIT; + SET @sql_where += N' AND qsrs.last_execution_time >= @sp_StartDate + '; + END; - END - --create synonym for deadlockfindings. - if((select name from sys.objects where name='DeadlockFindings' and type_desc='SYNONYM')IS NOT NULL) - BEGIN - RAISERROR('found synonym', 0, 1) WITH NOWAIT; - drop synonym DeadlockFindings; - END - set @StringToExecute = 'CREATE SYNONYM DeadlockFindings FOR ' + @OutputDatabaseName + '.' + @OutputSchemaName + '.' + @OutputTableFindings; - exec sp_executesql @StringToExecute - - --create synonym for deadlock table. - if((select name from sys.objects where name='DeadLockTbl' and type_desc='SYNONYM') IS NOT NULL) - BEGIN - drop SYNONYM DeadLockTbl; - END - set @StringToExecute = 'CREATE SYNONYM DeadLockTbl FOR ' + @OutputDatabaseName + '.' + @OutputSchemaName + '.' + @OutputTableName; - exec sp_executesql @StringToExecute - - END - END - +--Alright, sensible +IF @EndDate IS NOT NULL + BEGIN + RAISERROR(N'Setting end date filter', 0, 1) WITH NOWAIT; + SET @sql_where += N' AND qsrs.last_execution_time < @sp_EndDate + '; + END; - CREATE TABLE #t (id INT NOT NULL); +--C'mon, why would you do that? +IF (@StartDate IS NULL AND @EndDate IS NOT NULL) + BEGIN + RAISERROR(N'Setting reasonable start date filter', 0, 1) WITH NOWAIT; + SET @sql_where += N' AND qsrs.last_execution_time >= DATEADD(DAY, -7, @sp_EndDate) + '; + END; - /* WITH ROWCOUNT doesn't work on Amazon RDS - see: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2037 */ - IF LEFT(CAST(SERVERPROPERTY('ComputerNamePhysicalNetBIOS') AS VARCHAR(8000)), 8) <> 'EC2AMAZ-' - AND LEFT(CAST(SERVERPROPERTY('MachineName') AS VARCHAR(8000)), 8) <> 'EC2AMAZ-' - AND LEFT(CAST(SERVERPROPERTY('ServerName') AS VARCHAR(8000)), 8) <> 'EC2AMAZ-' - AND db_id('rdsadmin') IS NULL - BEGIN; - BEGIN TRY; - UPDATE STATISTICS #t WITH ROWCOUNT = 100000000, PAGECOUNT = 100000000; - END TRY - BEGIN CATCH; - /* Misleading error returned, if run without permissions to update statistics the error returned is "Cannot find object". - Catching specific error, and returning message with better info. If any other error is returned, then throw as normal */ - IF (ERROR_NUMBER() = 1088) - BEGIN; - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Cannot run UPDATE STATISTICS on temp table without db_owner or sysadmin permissions %s', 0, 1, @d) WITH NOWAIT; - END; - ELSE - BEGIN; - THROW; - END; - END CATCH; - END; +--Jeez, abusive +IF (@StartDate IS NOT NULL AND @EndDate IS NULL) + BEGIN + RAISERROR(N'Setting reasonable end date filter', 0, 1) WITH NOWAIT; + SET @sql_where += N' AND qsrs.last_execution_time < DATEADD(DAY, 7, @sp_StartDate) + '; + END; - /*Grab the initial set of XML to parse*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Grab the initial set of XML to parse at %s', 0, 1, @d) WITH NOWAIT; - WITH xml - AS ( SELECT CONVERT(XML, event_data) AS deadlock_xml - FROM sys.fn_xe_file_target_read_file(@EventSessionPath, NULL, NULL, NULL) ) - SELECT ISNULL(xml.deadlock_xml, '') AS deadlock_xml - INTO #deadlock_data - FROM xml - LEFT JOIN #t AS t - ON 1 = 1 - CROSS APPLY xml.deadlock_xml.nodes('/event/@name') AS x(c) - WHERE 1 = 1 - AND x.c.exist('/event/@name[ .= "xml_deadlock_report"]') = 1 - AND CONVERT(datetime, SWITCHOFFSET(CONVERT(datetimeoffset, xml.deadlock_xml.value('(/event/@timestamp)[1]', 'datetime') ), DATENAME(TzOffset, SYSDATETIMEOFFSET()))) > @StartDate - AND CONVERT(datetime, SWITCHOFFSET(CONVERT(datetimeoffset, xml.deadlock_xml.value('(/event/@timestamp)[1]', 'datetime') ), DATENAME(TzOffset, SYSDATETIMEOFFSET()))) < @EndDate - ORDER BY xml.deadlock_xml.value('(/event/@timestamp)[1]', 'datetime') DESC - OPTION ( RECOMPILE ); +--I care about minimum execution counts +IF @MinimumExecutionCount IS NOT NULL + BEGIN + RAISERROR(N'Setting execution filter', 0, 1) WITH NOWAIT; + SET @sql_where += N' AND qsrs.count_executions >= @sp_MinimumExecutionCount + '; + END; - /*Optimization: if we got back more rows than @Top, remove them. This seems to be better than using @Top in the query above as that results in excessive memory grant*/ - SET @DeadlockCount = @@ROWCOUNT - IF( @Top < @DeadlockCount ) BEGIN - WITH T - AS ( - SELECT TOP ( @DeadlockCount - @Top) * - FROM #deadlock_data - ORDER BY #deadlock_data.deadlock_xml.value('(/event/@timestamp)[1]', 'datetime') ASC) - DELETE FROM T - END +--You care about stored proc names +IF @StoredProcName IS NOT NULL + BEGIN + RAISERROR(N'Setting stored proc filter', 0, 1) WITH NOWAIT; + SET @sql_where += N' AND object_name(qsq.object_id, DB_ID(' + QUOTENAME(@DatabaseName, '''') + N')) = @sp_StoredProcName + '; + END; - /*Parse process and input buffer XML*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Parse process and input buffer XML %s', 0, 1, @d) WITH NOWAIT; - SELECT q.event_date, - q.victim_id, - CONVERT(BIT, q.is_parallel) AS is_parallel, - q.deadlock_graph, - q.id, - q.database_id, - q.priority, - q.log_used, - q.wait_resource, - q.wait_time, - q.transaction_name, - q.last_tran_started, - q.last_batch_started, - q.last_batch_completed, - q.lock_mode, - q.transaction_count, - q.client_app, - q.host_name, - q.login_name, - q.isolation_level, - q.process_xml, - ISNULL(ca2.ib.query('.'), '') AS input_buffer - INTO #deadlock_process - FROM ( SELECT dd.deadlock_xml, - CONVERT(DATETIME2(7), SWITCHOFFSET(CONVERT(datetimeoffset, dd.event_date ), DATENAME(TzOffset, SYSDATETIMEOFFSET()))) AS event_date, - dd.victim_id, - CONVERT(tinyint, dd.is_parallel) + CONVERT(tinyint, dd.is_parallel_batch) AS is_parallel, - dd.deadlock_graph, - ca.dp.value('@id', 'NVARCHAR(256)') AS id, - ca.dp.value('@currentdb', 'BIGINT') AS database_id, - ca.dp.value('@priority', 'SMALLINT') AS priority, - ca.dp.value('@logused', 'BIGINT') AS log_used, - ca.dp.value('@waitresource', 'NVARCHAR(256)') AS wait_resource, - ca.dp.value('@waittime', 'BIGINT') AS wait_time, - ca.dp.value('@transactionname', 'NVARCHAR(256)') AS transaction_name, - ca.dp.value('@lasttranstarted', 'DATETIME2(7)') AS last_tran_started, - ca.dp.value('@lastbatchstarted', 'DATETIME2(7)') AS last_batch_started, - ca.dp.value('@lastbatchcompleted', 'DATETIME2(7)') AS last_batch_completed, - ca.dp.value('@lockMode', 'NVARCHAR(256)') AS lock_mode, - ca.dp.value('@trancount', 'BIGINT') AS transaction_count, - ca.dp.value('@clientapp', 'NVARCHAR(256)') AS client_app, - ca.dp.value('@hostname', 'NVARCHAR(256)') AS host_name, - ca.dp.value('@loginname', 'NVARCHAR(256)') AS login_name, - ca.dp.value('@isolationlevel', 'NVARCHAR(256)') AS isolation_level, - ISNULL(ca.dp.query('.'), '') AS process_xml - FROM ( SELECT d1.deadlock_xml, - d1.deadlock_xml.value('(event/@timestamp)[1]', 'DATETIME2') AS event_date, - d1.deadlock_xml.value('(//deadlock/victim-list/victimProcess/@id)[1]', 'NVARCHAR(256)') AS victim_id, - d1.deadlock_xml.exist('//deadlock/resource-list/exchangeEvent') AS is_parallel, - d1.deadlock_xml.exist('//deadlock/resource-list/SyncPoint') AS is_parallel_batch, - d1.deadlock_xml.query('/event/data/value/deadlock') AS deadlock_graph - FROM #deadlock_data AS d1 ) AS dd - CROSS APPLY dd.deadlock_xml.nodes('//deadlock/process-list/process') AS ca(dp) - WHERE (ca.dp.value('@currentdb', 'BIGINT') = DB_ID(@DatabaseName) OR @DatabaseName IS NULL) - AND (ca.dp.value('@clientapp', 'NVARCHAR(256)') = @AppName OR @AppName IS NULL) - AND (ca.dp.value('@hostname', 'NVARCHAR(256)') = @HostName OR @HostName IS NULL) - AND (ca.dp.value('@loginname', 'NVARCHAR(256)') = @LoginName OR @LoginName IS NULL) - ) AS q - CROSS APPLY q.deadlock_xml.nodes('//deadlock/process-list/process/inputbuf') AS ca2(ib) - OPTION ( RECOMPILE ); +--I will always love you, but hopefully this query will eventually end +IF @DurationFilter IS NOT NULL + BEGIN + RAISERROR(N'Setting duration filter', 0, 1) WITH NOWAIT; + SET @sql_where += N' AND (qsrs.avg_duration / 1000.) >= @sp_MinDuration + '; + END; +--I don't know why you'd go looking for failed queries, but hey +IF (@Failed = 0 OR @Failed IS NULL) + BEGIN + RAISERROR(N'Setting failed query filter to 0', 0, 1) WITH NOWAIT; + SET @sql_where += N' AND qsrs.execution_type = 0 + '; + END; +IF (@Failed = 1) + BEGIN + RAISERROR(N'Setting failed query filter to 3, 4', 0, 1) WITH NOWAIT; + SET @sql_where += N' AND qsrs.execution_type IN (3, 4) + '; + END; - /*Parse execution stack XML*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Parse execution stack XML %s', 0, 1, @d) WITH NOWAIT; - SELECT DISTINCT - dp.id, - dp.event_date, - ca.dp.value('@procname', 'NVARCHAR(1000)') AS proc_name, - ca.dp.value('@sqlhandle', 'NVARCHAR(128)') AS sql_handle - INTO #deadlock_stack - FROM #deadlock_process AS dp - CROSS APPLY dp.process_xml.nodes('//executionStack/frame') AS ca(dp) - WHERE (ca.dp.value('@procname', 'NVARCHAR(256)') = @StoredProcName OR @StoredProcName IS NULL) - OPTION ( RECOMPILE ); +/*Filtering for plan_id or query_id*/ +IF (@PlanIdFilter IS NOT NULL) + BEGIN + RAISERROR(N'Setting plan_id filter', 0, 1) WITH NOWAIT; + SET @sql_where += N' AND qsp.plan_id = @sp_PlanIdFilter + '; + END; +IF (@QueryIdFilter IS NOT NULL) + BEGIN + RAISERROR(N'Setting query_id filter', 0, 1) WITH NOWAIT; + SET @sql_where += N' AND qsq.query_id = @sp_QueryIdFilter + '; + END; +IF @Debug = 1 + RAISERROR(N'Starting WHERE clause:', 0, 1) WITH NOWAIT; + PRINT @sql_where; - /*Grab the full resource list*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Grab the full resource list %s', 0, 1, @d) WITH NOWAIT; - SELECT - CONVERT(DATETIME2(7), SWITCHOFFSET(CONVERT(datetimeoffset, dr.event_date), DATENAME(TzOffset, SYSDATETIMEOFFSET()))) AS event_date, - dr.victim_id, - dr.resource_xml - INTO #deadlock_resource - FROM - ( - SELECT dd.deadlock_xml.value('(event/@timestamp)[1]', 'DATETIME2') AS event_date, - dd.deadlock_xml.value('(//deadlock/victim-list/victimProcess/@id)[1]', 'NVARCHAR(256)') AS victim_id, - ISNULL(ca.dp.query('.'), '') AS resource_xml - FROM #deadlock_data AS dd - CROSS APPLY dd.deadlock_xml.nodes('//deadlock/resource-list') AS ca(dp) - ) AS dr - OPTION ( RECOMPILE ); +IF @sql_where IS NULL + BEGIN + RAISERROR(N'@sql_where is NULL', 0, 1) WITH NOWAIT; + RETURN; + END; +IF (@ExportToExcel = 1 OR @SkipXML = 1) + BEGIN + RAISERROR(N'Exporting to Excel or skipping XML, hiding summary', 0, 1) WITH NOWAIT; + SET @HideSummary = 1; + END; - /*Parse object locks*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Parse object locks %s', 0, 1, @d) WITH NOWAIT; - SELECT DISTINCT - ca.event_date, - ca.database_id, - ca.object_name, - ca.lock_mode, - ca.index_name, - ca.associatedObjectId, - w.l.value('@id', 'NVARCHAR(256)') AS waiter_id, - w.l.value('@mode', 'NVARCHAR(256)') AS waiter_mode, - o.l.value('@id', 'NVARCHAR(256)') AS owner_id, - o.l.value('@mode', 'NVARCHAR(256)') AS owner_mode, - N'OBJECT' AS lock_type - INTO #deadlock_owner_waiter - FROM ( - SELECT dr.event_date, - ca.dr.value('@dbid', 'BIGINT') AS database_id, - ca.dr.value('@objectname', 'NVARCHAR(256)') AS object_name, - ca.dr.value('@mode', 'NVARCHAR(256)') AS lock_mode, - ca.dr.value('@indexname', 'NVARCHAR(256)') AS index_name, - ca.dr.value('@associatedObjectId', 'BIGINT') AS associatedObjectId, - ca.dr.query('.') AS dr - FROM #deadlock_resource AS dr - CROSS APPLY dr.resource_xml.nodes('//resource-list/objectlock') AS ca(dr) - ) AS ca - CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l) - CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l) - WHERE (ca.object_name = @ObjectName OR @ObjectName IS NULL) - OPTION ( RECOMPILE ); +IF @StoredProcName IS NOT NULL + BEGIN + + DECLARE @sql NVARCHAR(MAX); + DECLARE @out INT; + DECLARE @proc_params NVARCHAR(MAX) = N'@sp_StartDate DATETIME2, @sp_EndDate DATETIME2, @sp_MinimumExecutionCount INT, @sp_MinDuration INT, @sp_StoredProcName NVARCHAR(128), @sp_PlanIdFilter INT, @sp_QueryIdFilter INT, @i_out INT OUTPUT'; + + + SET @sql = N'SELECT @i_out = COUNT(*) + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp + ON qsp.plan_id = qsrs.plan_id + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq + ON qsq.query_id = qsp.query_id + WHERE 1 = 1 + AND qsq.is_internal_query = 0 + AND qsp.query_plan IS NOT NULL + '; + + SET @sql += @sql_where; + EXEC sys.sp_executesql @sql, + @proc_params, + @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter, @i_out = @out OUTPUT; + + IF @out = 0 + BEGIN + SET @msg = N'We couldn''t find the Stored Procedure ' + QUOTENAME(@StoredProcName) + N' in the Query Store views for ' + QUOTENAME(@DatabaseName) + N' between ' + CONVERT(NVARCHAR(30), ISNULL(@StartDate, DATEADD(DAY, -7, DATEDIFF(DAY, 0, SYSDATETIME() ))) ) + N' and ' + CONVERT(NVARCHAR(30), ISNULL(@EndDate, SYSDATETIME())) + + '. Try removing schema prefixes or adjusting dates. If it was executed from a different database context, try searching there instead.'; + RAISERROR(@msg, 0, 1) WITH NOWAIT; - /*Parse page locks*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Parse page locks %s', 0, 1, @d) WITH NOWAIT; - INSERT #deadlock_owner_waiter WITH(TABLOCKX) - SELECT DISTINCT - ca.event_date, - ca.database_id, - ca.object_name, - ca.lock_mode, - ca.index_name, - ca.associatedObjectId, - w.l.value('@id', 'NVARCHAR(256)') AS waiter_id, - w.l.value('@mode', 'NVARCHAR(256)') AS waiter_mode, - o.l.value('@id', 'NVARCHAR(256)') AS owner_id, - o.l.value('@mode', 'NVARCHAR(256)') AS owner_mode, - N'PAGE' AS lock_type - FROM ( - SELECT dr.event_date, - ca.dr.value('@dbid', 'BIGINT') AS database_id, - ca.dr.value('@objectname', 'NVARCHAR(256)') AS object_name, - ca.dr.value('@mode', 'NVARCHAR(256)') AS lock_mode, - ca.dr.value('@indexname', 'NVARCHAR(256)') AS index_name, - ca.dr.value('@associatedObjectId', 'BIGINT') AS associatedObjectId, - ca.dr.query('.') AS dr - FROM #deadlock_resource AS dr - CROSS APPLY dr.resource_xml.nodes('//resource-list/pagelock') AS ca(dr) - ) AS ca - CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l) - CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l) - OPTION ( RECOMPILE ); + SELECT @msg AS [Blue Flowers, Blue Flowers, Blue Flowers]; + + RETURN; + + END; + + END; - /*Parse key locks*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Parse key locks %s', 0, 1, @d) WITH NOWAIT; - INSERT #deadlock_owner_waiter WITH(TABLOCKX) - SELECT DISTINCT - ca.event_date, - ca.database_id, - ca.object_name, - ca.lock_mode, - ca.index_name, - ca.associatedObjectId, - w.l.value('@id', 'NVARCHAR(256)') AS waiter_id, - w.l.value('@mode', 'NVARCHAR(256)') AS waiter_mode, - o.l.value('@id', 'NVARCHAR(256)') AS owner_id, - o.l.value('@mode', 'NVARCHAR(256)') AS owner_mode, - N'KEY' AS lock_type - FROM ( - SELECT dr.event_date, - ca.dr.value('@dbid', 'BIGINT') AS database_id, - ca.dr.value('@objectname', 'NVARCHAR(256)') AS object_name, - ca.dr.value('@mode', 'NVARCHAR(256)') AS lock_mode, - ca.dr.value('@indexname', 'NVARCHAR(256)') AS index_name, - ca.dr.value('@associatedObjectId', 'BIGINT') AS associatedObjectId, - ca.dr.query('.') AS dr - FROM #deadlock_resource AS dr - CROSS APPLY dr.resource_xml.nodes('//resource-list/keylock') AS ca(dr) - ) AS ca - CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l) - CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l) - OPTION ( RECOMPILE ); - /*Parse RID locks*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Parse RID locks %s', 0, 1, @d) WITH NOWAIT; - INSERT #deadlock_owner_waiter WITH(TABLOCKX) - SELECT DISTINCT - ca.event_date, - ca.database_id, - ca.object_name, - ca.lock_mode, - ca.index_name, - ca.associatedObjectId, - w.l.value('@id', 'NVARCHAR(256)') AS waiter_id, - w.l.value('@mode', 'NVARCHAR(256)') AS waiter_mode, - o.l.value('@id', 'NVARCHAR(256)') AS owner_id, - o.l.value('@mode', 'NVARCHAR(256)') AS owner_mode, - N'RID' AS lock_type - FROM ( - SELECT dr.event_date, - ca.dr.value('@dbid', 'BIGINT') AS database_id, - ca.dr.value('@objectname', 'NVARCHAR(256)') AS object_name, - ca.dr.value('@mode', 'NVARCHAR(256)') AS lock_mode, - ca.dr.value('@indexname', 'NVARCHAR(256)') AS index_name, - ca.dr.value('@associatedObjectId', 'BIGINT') AS associatedObjectId, - ca.dr.query('.') AS dr - FROM #deadlock_resource AS dr - CROSS APPLY dr.resource_xml.nodes('//resource-list/ridlock') AS ca(dr) - ) AS ca - CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l) - CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l) - OPTION ( RECOMPILE ); +/* +This is our grouped interval query. +By default, it looks at queries: + In the last 7 days + That aren't system queries + That have a query plan (some won't, if nested level is > 128, along with other reasons) + And haven't failed + This stuff, along with some other options, will be configurable in the stored proc - /*Parse row group locks*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Parse row group locks %s', 0, 1, @d) WITH NOWAIT; - INSERT #deadlock_owner_waiter WITH(TABLOCKX) - SELECT DISTINCT - ca.event_date, - ca.database_id, - ca.object_name, - ca.lock_mode, - ca.index_name, - ca.associatedObjectId, - w.l.value('@id', 'NVARCHAR(256)') AS waiter_id, - w.l.value('@mode', 'NVARCHAR(256)') AS waiter_mode, - o.l.value('@id', 'NVARCHAR(256)') AS owner_id, - o.l.value('@mode', 'NVARCHAR(256)') AS owner_mode, - N'ROWGROUP' AS lock_type - FROM ( - SELECT dr.event_date, - ca.dr.value('@dbid', 'BIGINT') AS database_id, - ca.dr.value('@objectname', 'NVARCHAR(256)') AS object_name, - ca.dr.value('@mode', 'NVARCHAR(256)') AS lock_mode, - ca.dr.value('@indexname', 'NVARCHAR(256)') AS index_name, - ca.dr.value('@associatedObjectId', 'BIGINT') AS associatedObjectId, - ca.dr.query('.') AS dr - FROM #deadlock_resource AS dr - CROSS APPLY dr.resource_xml.nodes('//resource-list/rowgrouplock') AS ca(dr) - ) AS ca - CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l) - CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l) - OPTION ( RECOMPILE ); +*/ - UPDATE d - SET d.index_name = d.object_name - + '.HEAP' - FROM #deadlock_owner_waiter AS d - WHERE lock_type IN (N'HEAP', N'RID') - OPTION(RECOMPILE); +IF @sql_where IS NOT NULL +BEGIN TRY + BEGIN - /*Parse parallel deadlocks*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Parse parallel deadlocks %s', 0, 1, @d) WITH NOWAIT; - SELECT DISTINCT - ca.id, - ca.event_date, - ca.wait_type, - ca.node_id, - ca.waiter_type, - ca.owner_activity, - ca.waiter_activity, - ca.merging, - ca.spilling, - ca.waiting_to_close, - w.l.value('@id', 'NVARCHAR(256)') AS waiter_id, - o.l.value('@id', 'NVARCHAR(256)') AS owner_id - INTO #deadlock_resource_parallel - FROM ( - SELECT dr.event_date, - ca.dr.value('@id', 'NVARCHAR(256)') AS id, - ca.dr.value('@WaitType', 'NVARCHAR(256)') AS wait_type, - ca.dr.value('@nodeId', 'BIGINT') AS node_id, - /* These columns are in 2017 CU5 ONLY */ - ca.dr.value('@waiterType', 'NVARCHAR(256)') AS waiter_type, - ca.dr.value('@ownerActivity', 'NVARCHAR(256)') AS owner_activity, - ca.dr.value('@waiterActivity', 'NVARCHAR(256)') AS waiter_activity, - ca.dr.value('@merging', 'NVARCHAR(256)') AS merging, - ca.dr.value('@spilling', 'NVARCHAR(256)') AS spilling, - ca.dr.value('@waitingToClose', 'NVARCHAR(256)') AS waiting_to_close, - /* */ - ca.dr.query('.') AS dr - FROM #deadlock_resource AS dr - CROSS APPLY dr.resource_xml.nodes('//resource-list/exchangeEvent') AS ca(dr) - ) AS ca - CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l) - CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l) - OPTION ( RECOMPILE ); + RAISERROR(N'Populating temp tables', 0, 1) WITH NOWAIT; +RAISERROR(N'Gathering intervals', 0, 1) WITH NOWAIT; - /*Get rid of parallel noise*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Get rid of parallel noise %s', 0, 1, @d) WITH NOWAIT; - WITH c - AS - ( - SELECT *, ROW_NUMBER() OVER ( PARTITION BY drp.owner_id, drp.waiter_id ORDER BY drp.event_date ) AS rn - FROM #deadlock_resource_parallel AS drp - ) - DELETE FROM c - WHERE c.rn > 1 - OPTION ( RECOMPILE ); +SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; +SET @sql_select += N' +SELECT CONVERT(DATE, qsrs.last_execution_time) AS flat_date, + MIN(DATEADD(HOUR, DATEDIFF(HOUR, 0, qsrs.last_execution_time), 0)) AS start_range, + MAX(DATEADD(HOUR, DATEDIFF(HOUR, 0, qsrs.last_execution_time) + 1, 0)) AS end_range, + SUM(qsrs.avg_duration / 1000.) / SUM(qsrs.count_executions) AS total_avg_duration_ms, + SUM(qsrs.avg_cpu_time / 1000.) / SUM(qsrs.count_executions) AS total_avg_cpu_time_ms, + SUM((qsrs.avg_logical_io_reads * 8 ) / 1024.) / SUM(qsrs.count_executions) AS total_avg_logical_io_reads_mb, + SUM((qsrs.avg_physical_io_reads* 8 ) / 1024.) / SUM(qsrs.count_executions) AS total_avg_physical_io_reads_mb, + SUM((qsrs.avg_logical_io_writes* 8 ) / 1024.) / SUM(qsrs.count_executions) AS total_avg_logical_io_writes_mb, + SUM((qsrs.avg_query_max_used_memory * 8 ) / 1024.) / SUM(qsrs.count_executions) AS total_avg_query_max_used_memory_mb, + SUM(qsrs.avg_rowcount) AS total_rowcount, + SUM(qsrs.count_executions) AS total_count_executions, + SUM(qsrs.max_duration / 1000.) AS total_max_duration_ms, + SUM(qsrs.max_cpu_time / 1000.) AS total_max_cpu_time_ms, + SUM((qsrs.max_logical_io_reads * 8 ) / 1024.) AS total_max_logical_io_reads_mb, + SUM((qsrs.max_physical_io_reads* 8 ) / 1024.) AS total_max_physical_io_reads_mb, + SUM((qsrs.max_logical_io_writes* 8 ) / 1024.) AS total_max_logical_io_writes_mb, + SUM((qsrs.max_query_max_used_memory * 8 ) / 1024.) AS total_max_query_max_used_memory_mb '; + IF @new_columns = 1 + BEGIN + SET @sql_select += N', + SUM((qsrs.avg_log_bytes_used) / 1048576.) / SUM(qsrs.count_executions) AS total_avg_log_bytes_mb, + SUM(qsrs.avg_tempdb_space_used) / SUM(qsrs.count_executions) AS total_avg_tempdb_space, + SUM((qsrs.max_log_bytes_used) / 1048576.) AS total_max_log_bytes_mb, + SUM(qsrs.max_tempdb_space_used) AS total_max_tempdb_space + '; + END; + IF @new_columns = 0 + BEGIN + SET @sql_select += N', + NULL AS total_avg_log_bytes_mb, + NULL AS total_avg_tempdb_space, + NULL AS total_max_log_bytes_mb, + NULL AS total_max_tempdb_space + '; + END; - /*Get rid of nonsense*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Get rid of nonsense %s', 0, 1, @d) WITH NOWAIT; - DELETE dow - FROM #deadlock_owner_waiter AS dow - WHERE dow.owner_id = dow.waiter_id - OPTION ( RECOMPILE ); +SET @sql_select += N'FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp + ON qsp.plan_id = qsrs.plan_id + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq + ON qsq.query_id = qsp.query_id + WHERE 1 = 1 + AND qsq.is_internal_query = 0 + AND qsp.query_plan IS NOT NULL + '; - /*Add some nonsense*/ - ALTER TABLE #deadlock_process - ADD waiter_mode NVARCHAR(256), - owner_mode NVARCHAR(256), - is_victim AS CONVERT(BIT, CASE WHEN id = victim_id THEN 1 ELSE 0 END); - /*Update some nonsense*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Update some nonsense part 1 %s', 0, 1, @d) WITH NOWAIT; - UPDATE dp - SET dp.owner_mode = dow.owner_mode - FROM #deadlock_process AS dp - JOIN #deadlock_owner_waiter AS dow - ON dp.id = dow.owner_id - AND dp.event_date = dow.event_date - WHERE dp.is_victim = 0 - OPTION ( RECOMPILE ); +SET @sql_select += @sql_where; - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Update some nonsense part 2 %s', 0, 1, @d) WITH NOWAIT; - UPDATE dp - SET dp.waiter_mode = dow.waiter_mode - FROM #deadlock_process AS dp - JOIN #deadlock_owner_waiter AS dow - ON dp.victim_id = dow.waiter_id - AND dp.event_date = dow.event_date - WHERE dp.is_victim = 1 - OPTION ( RECOMPILE ); +SET @sql_select += + N'GROUP BY CONVERT(DATE, qsrs.last_execution_time) + OPTION (RECOMPILE); + '; - /*Get Agent Job and Step names*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Get Agent Job and Step names %s', 0, 1, @d) WITH NOWAIT; - SELECT *, - CONVERT(UNIQUEIDENTIFIER, - CONVERT(XML, '').value('xs:hexBinary(substring(sql:column("x.job_id"), 0) )', 'BINARY(16)') - ) AS job_id_guid - INTO #agent_job - FROM ( - SELECT dp.event_date, - dp.victim_id, - dp.id, - dp.database_id, - dp.client_app, - SUBSTRING(dp.client_app, - CHARINDEX('0x', dp.client_app) + LEN('0x'), - 32 - ) AS job_id, - SUBSTRING(dp.client_app, - CHARINDEX(': Step ', dp.client_app) + LEN(': Step '), - CHARINDEX(')', dp.client_app, CHARINDEX(': Step ', dp.client_app)) - - (CHARINDEX(': Step ', dp.client_app) - + LEN(': Step ')) - ) AS step_id - FROM #deadlock_process AS dp - WHERE dp.client_app LIKE 'SQLAgent - %' - ) AS x - OPTION ( RECOMPILE ); +IF @Debug = 1 + PRINT @sql_select; +IF @sql_select IS NULL + BEGIN + RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; + RETURN; + END; - ALTER TABLE #agent_job ADD job_name NVARCHAR(256), - step_name NVARCHAR(256); +INSERT #grouped_interval WITH (TABLOCK) + ( flat_date, start_range, end_range, total_avg_duration_ms, + total_avg_cpu_time_ms, total_avg_logical_io_reads_mb, total_avg_physical_io_reads_mb, + total_avg_logical_io_writes_mb, total_avg_query_max_used_memory_mb, total_rowcount, + total_count_executions, total_max_duration_ms, total_max_cpu_time_ms, total_max_logical_io_reads_mb, + total_max_physical_io_reads_mb, total_max_logical_io_writes_mb, total_max_query_max_used_memory_mb, + total_avg_log_bytes_mb, total_avg_tempdb_space, total_max_log_bytes_mb, total_max_tempdb_space ) - IF SERVERPROPERTY('EngineEdition') NOT IN (5, 6) /* Azure SQL DB doesn't support querying jobs */ - AND NOT (LEFT(CAST(SERVERPROPERTY('ComputerNamePhysicalNetBIOS') AS VARCHAR(8000)), 8) = 'EC2AMAZ-' /* Neither does Amazon RDS Express Edition */ - AND LEFT(CAST(SERVERPROPERTY('MachineName') AS VARCHAR(8000)), 8) = 'EC2AMAZ-' - AND LEFT(CAST(SERVERPROPERTY('ServerName') AS VARCHAR(8000)), 8) = 'EC2AMAZ-' - AND db_id('rdsadmin') IS NOT NULL - AND EXISTS(SELECT * FROM master.sys.all_objects WHERE name IN ('rds_startup_tasks', 'rds_help_revlogin', 'rds_hexadecimal', 'rds_failover_tracking', 'rds_database_tracking', 'rds_track_change')) - ) - BEGIN - SET @StringToExecute = N'UPDATE aj - SET aj.job_name = j.name, - aj.step_name = s.step_name - FROM msdb.dbo.sysjobs AS j - JOIN msdb.dbo.sysjobsteps AS s - ON j.job_id = s.job_id - JOIN #agent_job AS aj - ON aj.job_id_guid = j.job_id - AND aj.step_id = s.step_id - OPTION ( RECOMPILE );'; - EXEC(@StringToExecute); - END +EXEC sys.sp_executesql @stmt = @sql_select, + @params = @sp_params, + @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; - UPDATE dp - SET dp.client_app = - CASE WHEN dp.client_app LIKE N'SQLAgent - %' - THEN N'SQLAgent - Job: ' - + aj.job_name - + N' Step: ' - + aj.step_name - ELSE dp.client_app - END - FROM #deadlock_process AS dp - JOIN #agent_job AS aj - ON dp.event_date = aj.event_date - AND dp.victim_id = aj.victim_id - AND dp.id = aj.id - OPTION ( RECOMPILE ); - /*Get each and every table of all databases*/ - DECLARE @sysAssObjId AS TABLE (database_id bigint, partition_id bigint, schema_name varchar(255), table_name varchar(255)); - INSERT into @sysAssObjId EXECUTE sp_MSforeachdb - N'USE [?]; - SELECT DB_ID() as database_id, p.partition_id, s.name as schema_name, t.name as table_name - FROM sys.partitions p - LEFT JOIN sys.tables t ON t.object_id = p.object_id - LEFT JOIN sys.schemas s ON s.schema_id = t.schema_id - WHERE s.name is not NULL AND t.name is not NULL'; +/* +The next group of queries looks at plans in the ranges we found in the grouped interval query +We take the highest value from each metric (duration, cpu, etc) and find the top plans by that metric in the range - /*Begin checks based on parsed values*/ +They insert into the #working_plans table +*/ - /*Check 1 is deadlocks by database*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Check 1 %s', 0, 1, @d) WITH NOWAIT; - INSERT #deadlock_findings WITH (TABLOCKX) - ( check_id, database_name, object_name, finding_group, finding ) - SELECT 1 AS check_id, - DB_NAME(dp.database_id) AS database_name, - '-' AS object_name, - 'Total database locks' AS finding_group, - 'This database had ' - + CONVERT(NVARCHAR(20), COUNT_BIG(DISTINCT dp.event_date)) - + ' deadlocks.' - FROM #deadlock_process AS dp - WHERE 1 = 1 - AND (DB_NAME(dp.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (dp.event_date >= @StartDate OR @StartDate IS NULL) - AND (dp.event_date < @EndDate OR @EndDate IS NULL) - AND (dp.client_app = @AppName OR @AppName IS NULL) - AND (dp.host_name = @HostName OR @HostName IS NULL) - AND (dp.login_name = @LoginName OR @LoginName IS NULL) - GROUP BY DB_NAME(dp.database_id) - OPTION ( RECOMPILE ); - /*Check 2 is deadlocks by object*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Check 2 objects %s', 0, 1, @d) WITH NOWAIT; - INSERT #deadlock_findings WITH (TABLOCKX) - ( check_id, database_name, object_name, finding_group, finding ) - SELECT 2 AS check_id, - ISNULL(DB_NAME(dow.database_id), 'UNKNOWN') AS database_name, - ISNULL(dow.object_name, 'UNKNOWN') AS object_name, - 'Total object deadlocks' AS finding_group, - 'This object was involved in ' - + CONVERT(NVARCHAR(20), COUNT_BIG(DISTINCT dow.event_date)) - + ' deadlock(s).' - FROM #deadlock_owner_waiter AS dow - WHERE 1 = 1 - AND (DB_NAME(dow.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (dow.event_date >= @StartDate OR @StartDate IS NULL) - AND (dow.event_date < @EndDate OR @EndDate IS NULL) - AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) - GROUP BY DB_NAME(dow.database_id), dow.object_name - OPTION ( RECOMPILE ); - /*Check 2 continuation, number of locks per index*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Check 2 indexes %s', 0, 1, @d) WITH NOWAIT; - INSERT #deadlock_findings WITH (TABLOCKX) - ( check_id, database_name, object_name, finding_group, finding ) - SELECT 2 AS check_id, - ISNULL(DB_NAME(dow.database_id), 'UNKNOWN') AS database_name, - dow.index_name AS index_name, - 'Total index deadlocks' AS finding_group, - 'This index was involved in ' - + CONVERT(NVARCHAR(20), COUNT_BIG(DISTINCT dow.event_date)) - + ' deadlock(s).' - FROM #deadlock_owner_waiter AS dow - WHERE 1 = 1 - AND (DB_NAME(dow.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (dow.event_date >= @StartDate OR @StartDate IS NULL) - AND (dow.event_date < @EndDate OR @EndDate IS NULL) - AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) - AND dow.lock_type NOT IN (N'HEAP', N'RID') - AND dow.index_name is not null - GROUP BY DB_NAME(dow.database_id), dow.index_name - OPTION ( RECOMPILE ); +/*Get longest duration plans*/ +RAISERROR(N'Gathering longest duration plans', 0, 1) WITH NOWAIT; - /*Check 2 continuation, number of locks per heap*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Check 2 heaps %s', 0, 1, @d) WITH NOWAIT; - INSERT #deadlock_findings WITH (TABLOCKX) - ( check_id, database_name, object_name, finding_group, finding ) - SELECT 2 AS check_id, - ISNULL(DB_NAME(dow.database_id), 'UNKNOWN') AS database_name, - dow.index_name AS index_name, - 'Total heap deadlocks' AS finding_group, - 'This heap was involved in ' - + CONVERT(NVARCHAR(20), COUNT_BIG(DISTINCT dow.event_date)) - + ' deadlock(s).' - FROM #deadlock_owner_waiter AS dow - WHERE 1 = 1 - AND (DB_NAME(dow.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (dow.event_date >= @StartDate OR @StartDate IS NULL) - AND (dow.event_date < @EndDate OR @EndDate IS NULL) - AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) - AND dow.lock_type IN (N'HEAP', N'RID') - GROUP BY DB_NAME(dow.database_id), dow.index_name - OPTION ( RECOMPILE ); - +SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; +SET @sql_select += N' +WITH duration_max +AS ( SELECT TOP 1 + gi.start_range, + gi.end_range + FROM #grouped_interval AS gi + ORDER BY gi.total_avg_duration_ms DESC ) +INSERT #working_plans WITH (TABLOCK) + ( plan_id, query_id, pattern ) +SELECT TOP ( @sp_Top ) + qsp.plan_id, qsp.query_id, ''avg duration'' +FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp +JOIN duration_max AS dm +ON qsp.last_execution_time >= dm.start_range + AND qsp.last_execution_time < dm.end_range +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs +ON qsrs.plan_id = qsp.plan_id +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq +ON qsq.query_id = qsp.query_id +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt +ON qsqt.query_text_id = qsq.query_text_id +WHERE 1 = 1 + AND qsq.is_internal_query = 0 + AND qsp.query_plan IS NOT NULL + '; - /*Check 3 looks for Serializable locking*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Check 3 %s', 0, 1, @d) WITH NOWAIT; - INSERT #deadlock_findings WITH (TABLOCKX) - ( check_id, database_name, object_name, finding_group, finding ) - SELECT 3 AS check_id, - DB_NAME(dp.database_id) AS database_name, - '-' AS object_name, - 'Serializable locking' AS finding_group, - 'This database has had ' + - CONVERT(NVARCHAR(20), COUNT_BIG(*)) + - ' instances of serializable deadlocks.' - AS finding - FROM #deadlock_process AS dp - WHERE dp.isolation_level LIKE 'serializable%' - AND (DB_NAME(dp.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (dp.event_date >= @StartDate OR @StartDate IS NULL) - AND (dp.event_date < @EndDate OR @EndDate IS NULL) - AND (dp.client_app = @AppName OR @AppName IS NULL) - AND (dp.host_name = @HostName OR @HostName IS NULL) - AND (dp.login_name = @LoginName OR @LoginName IS NULL) - GROUP BY DB_NAME(dp.database_id) - OPTION ( RECOMPILE ); +SET @sql_select += @sql_where; +SET @sql_select += N'ORDER BY qsrs.avg_duration DESC + OPTION (RECOMPILE); + '; - /*Check 4 looks for Repeatable Read locking*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Check 4 %s', 0, 1, @d) WITH NOWAIT; - INSERT #deadlock_findings WITH (TABLOCKX) - ( check_id, database_name, object_name, finding_group, finding ) - SELECT 4 AS check_id, - DB_NAME(dp.database_id) AS database_name, - '-' AS object_name, - 'Repeatable Read locking' AS finding_group, - 'This database has had ' + - CONVERT(NVARCHAR(20), COUNT_BIG(*)) + - ' instances of repeatable read deadlocks.' - AS finding - FROM #deadlock_process AS dp - WHERE dp.isolation_level LIKE 'repeatable read%' - AND (DB_NAME(dp.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (dp.event_date >= @StartDate OR @StartDate IS NULL) - AND (dp.event_date < @EndDate OR @EndDate IS NULL) - AND (dp.client_app = @AppName OR @AppName IS NULL) - AND (dp.host_name = @HostName OR @HostName IS NULL) - AND (dp.login_name = @LoginName OR @LoginName IS NULL) - GROUP BY DB_NAME(dp.database_id) - OPTION ( RECOMPILE ); +IF @Debug = 1 + PRINT @sql_select; +IF @sql_select IS NULL + BEGIN + RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; + RETURN; + END; - /*Check 5 breaks down app, host, and login information*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Check 5 %s', 0, 1, @d) WITH NOWAIT; - INSERT #deadlock_findings WITH (TABLOCKX) - ( check_id, database_name, object_name, finding_group, finding ) - SELECT 5 AS check_id, - DB_NAME(dp.database_id) AS database_name, - '-' AS object_name, - 'Login, App, and Host locking' AS finding_group, - 'This database has had ' + - CONVERT(NVARCHAR(20), COUNT_BIG(DISTINCT dp.event_date)) + - ' instances of deadlocks involving the login ' + - ISNULL(dp.login_name, 'UNKNOWN') + - ' from the application ' + - ISNULL(dp.client_app, 'UNKNOWN') + - ' on host ' + - ISNULL(dp.host_name, 'UNKNOWN') - AS finding - FROM #deadlock_process AS dp - WHERE 1 = 1 - AND (DB_NAME(dp.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (dp.event_date >= @StartDate OR @StartDate IS NULL) - AND (dp.event_date < @EndDate OR @EndDate IS NULL) - AND (dp.client_app = @AppName OR @AppName IS NULL) - AND (dp.host_name = @HostName OR @HostName IS NULL) - AND (dp.login_name = @LoginName OR @LoginName IS NULL) - GROUP BY DB_NAME(dp.database_id), dp.login_name, dp.client_app, dp.host_name - OPTION ( RECOMPILE ); +EXEC sys.sp_executesql @stmt = @sql_select, + @params = @sp_params, + @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; - /*Check 6 breaks down the types of locks (object, page, key, etc.)*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Check 6 %s', 0, 1, @d) WITH NOWAIT; - WITH lock_types AS ( - SELECT DB_NAME(dp.database_id) AS database_name, - dow.object_name, - SUBSTRING(dp.wait_resource, 1, CHARINDEX(':', dp.wait_resource) -1) AS lock, - CONVERT(NVARCHAR(20), COUNT_BIG(DISTINCT dp.id)) AS lock_count - FROM #deadlock_process AS dp - JOIN #deadlock_owner_waiter AS dow - ON dp.id = dow.owner_id - AND dp.event_date = dow.event_date - WHERE 1 = 1 - AND (DB_NAME(dp.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (dp.event_date >= @StartDate OR @StartDate IS NULL) - AND (dp.event_date < @EndDate OR @EndDate IS NULL) - AND (dp.client_app = @AppName OR @AppName IS NULL) - AND (dp.host_name = @HostName OR @HostName IS NULL) - AND (dp.login_name = @LoginName OR @LoginName IS NULL) - AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) - AND dow.object_name IS NOT NULL - GROUP BY DB_NAME(dp.database_id), SUBSTRING(dp.wait_resource, 1, CHARINDEX(':', dp.wait_resource) - 1), dow.object_name - ) - INSERT #deadlock_findings WITH (TABLOCKX) - ( check_id, database_name, object_name, finding_group, finding ) - SELECT DISTINCT 6 AS check_id, - lt.database_name, - lt.object_name, - 'Types of locks by object' AS finding_group, - 'This object has had ' + - STUFF((SELECT DISTINCT N', ' + lt2.lock_count + ' ' + lt2.lock - FROM lock_types AS lt2 - WHERE lt2.database_name = lt.database_name - AND lt2.object_name = lt.object_name - FOR XML PATH(N''), TYPE).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 1, N'') - + ' locks' - FROM lock_types AS lt - OPTION ( RECOMPILE ); +SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; +SET @sql_select += N' +WITH duration_max +AS ( SELECT TOP 1 + gi.start_range, + gi.end_range + FROM #grouped_interval AS gi + ORDER BY gi.total_max_duration_ms DESC ) +INSERT #working_plans WITH (TABLOCK) + ( plan_id, query_id, pattern ) +SELECT TOP ( @sp_Top ) + qsp.plan_id, qsp.query_id, ''max duration'' +FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp +JOIN duration_max AS dm +ON qsp.last_execution_time >= dm.start_range + AND qsp.last_execution_time < dm.end_range +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs +ON qsrs.plan_id = qsp.plan_id +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq +ON qsq.query_id = qsp.query_id +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt +ON qsqt.query_text_id = qsq.query_text_id +WHERE 1 = 1 + AND qsq.is_internal_query = 0 + AND qsp.query_plan IS NOT NULL + '; +SET @sql_select += @sql_where; - /*Check 7 gives you more info queries for sp_BlitzCache & BlitzQueryStore*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Check 7 part 1 %s', 0, 1, @d) WITH NOWAIT; - WITH deadlock_stack AS ( - SELECT DISTINCT - ds.id, - ds.proc_name, - ds.event_date, - PARSENAME(ds.proc_name, 3) AS database_name, - PARSENAME(ds.proc_name, 2) AS schema_name, - PARSENAME(ds.proc_name, 1) AS proc_only_name, - '''' + STUFF((SELECT DISTINCT N',' + ds2.sql_handle - FROM #deadlock_stack AS ds2 - WHERE ds2.id = ds.id - AND ds2.event_date = ds.event_date - FOR XML PATH(N''), TYPE).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 1, N'') + '''' AS sql_handle_csv - FROM #deadlock_stack AS ds - GROUP BY PARSENAME(ds.proc_name, 3), - PARSENAME(ds.proc_name, 2), - PARSENAME(ds.proc_name, 1), - ds.id, - ds.proc_name, - ds.event_date - ) - INSERT #deadlock_findings WITH (TABLOCKX) - ( check_id, database_name, object_name, finding_group, finding ) - SELECT DISTINCT 7 AS check_id, - ISNULL(DB_NAME(dow.database_id), 'UNKNOWN') AS database_name, - ds.proc_name AS object_name, - 'More Info - Query' AS finding_group, - 'EXEC sp_BlitzCache ' + - CASE WHEN ds.proc_name = 'adhoc' - THEN ' @OnlySqlHandles = ' + ds.sql_handle_csv - ELSE '@StoredProcName = ' + - QUOTENAME(ds.proc_only_name, '''') - END + - ';' AS finding - FROM deadlock_stack AS ds - JOIN #deadlock_owner_waiter AS dow - ON dow.owner_id = ds.id - AND dow.event_date = ds.event_date - WHERE 1 = 1 - AND (DB_NAME(dow.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (dow.event_date >= @StartDate OR @StartDate IS NULL) - AND (dow.event_date < @EndDate OR @EndDate IS NULL) - AND (dow.object_name = @StoredProcName OR @StoredProcName IS NULL) - OPTION ( RECOMPILE ); +SET @sql_select += N'ORDER BY qsrs.max_duration DESC + OPTION (RECOMPILE); + '; - IF @ProductVersionMajor >= 13 - BEGIN - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Check 7 part 2 %s', 0, 1, @d) WITH NOWAIT; - WITH deadlock_stack AS ( - SELECT DISTINCT - ds.id, - ds.sql_handle, - ds.proc_name, - ds.event_date, - PARSENAME(ds.proc_name, 3) AS database_name, - PARSENAME(ds.proc_name, 2) AS schema_name, - PARSENAME(ds.proc_name, 1) AS proc_only_name - FROM #deadlock_stack AS ds - ) - INSERT #deadlock_findings WITH (TABLOCKX) - ( check_id, database_name, object_name, finding_group, finding ) - SELECT DISTINCT 7 AS check_id, - DB_NAME(dow.database_id) AS database_name, - ds.proc_name AS object_name, - 'More Info - Query' AS finding_group, - 'EXEC sp_BlitzQueryStore ' - + '@DatabaseName = ' - + QUOTENAME(ds.database_name, '''') - + ', ' - + '@StoredProcName = ' - + QUOTENAME(ds.proc_only_name, '''') - + ';' AS finding - FROM deadlock_stack AS ds - JOIN #deadlock_owner_waiter AS dow - ON dow.owner_id = ds.id - AND dow.event_date = ds.event_date - WHERE ds.proc_name <> 'adhoc' - AND (DB_NAME(dow.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (dow.event_date >= @StartDate OR @StartDate IS NULL) - AND (dow.event_date < @EndDate OR @EndDate IS NULL) - AND (dow.object_name = @StoredProcName OR @StoredProcName IS NULL) - OPTION ( RECOMPILE ); - END; - +IF @Debug = 1 + PRINT @sql_select; - /*Check 8 gives you stored proc deadlock counts*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Check 8 %s', 0, 1, @d) WITH NOWAIT; - INSERT #deadlock_findings WITH (TABLOCKX) - ( check_id, database_name, object_name, finding_group, finding ) - SELECT 8 AS check_id, - DB_NAME(dp.database_id) AS database_name, - ds.proc_name, - 'Stored Procedure Deadlocks', - 'The stored procedure ' - + PARSENAME(ds.proc_name, 2) - + '.' - + PARSENAME(ds.proc_name, 1) - + ' has been involved in ' - + CONVERT(NVARCHAR(10), COUNT_BIG(DISTINCT ds.id)) - + ' deadlocks.' - FROM #deadlock_stack AS ds - JOIN #deadlock_process AS dp - ON dp.id = ds.id - AND ds.event_date = dp.event_date - WHERE ds.proc_name <> 'adhoc' - AND (ds.proc_name = @StoredProcName OR @StoredProcName IS NULL) - AND (DB_NAME(dp.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (dp.event_date >= @StartDate OR @StartDate IS NULL) - AND (dp.event_date < @EndDate OR @EndDate IS NULL) - AND (dp.client_app = @AppName OR @AppName IS NULL) - AND (dp.host_name = @HostName OR @HostName IS NULL) - AND (dp.login_name = @LoginName OR @LoginName IS NULL) - GROUP BY DB_NAME(dp.database_id), ds.proc_name - OPTION(RECOMPILE); +IF @sql_select IS NULL + BEGIN + RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; + RETURN; + END; +EXEC sys.sp_executesql @stmt = @sql_select, + @params = @sp_params, + @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; - /*Check 9 gives you more info queries for sp_BlitzIndex */ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Check 9 %s', 0, 1, @d) WITH NOWAIT; - WITH bi AS ( - SELECT DISTINCT - dow.object_name, - DB_NAME(dow.database_id) as database_name, - a.schema_name AS schema_name, - a.table_name AS table_name - FROM #deadlock_owner_waiter AS dow - LEFT JOIN @sysAssObjId a ON a.database_id=dow.database_id AND a.partition_id = dow.associatedObjectId - WHERE 1 = 1 - AND (DB_NAME(dow.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (dow.event_date >= @StartDate OR @StartDate IS NULL) - AND (dow.event_date < @EndDate OR @EndDate IS NULL) - AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) - AND dow.object_name IS NOT NULL - ) - INSERT #deadlock_findings WITH (TABLOCKX) - ( check_id, database_name, object_name, finding_group, finding ) - SELECT 9 AS check_id, - bi.database_name, - bi.object_name, - 'More Info - Table' AS finding_group, - 'EXEC sp_BlitzIndex ' + - '@DatabaseName = ' + QUOTENAME(bi.database_name, '''') + - ', @SchemaName = ' + QUOTENAME(bi.schema_name, '''') + - ', @TableName = ' + QUOTENAME(bi.table_name, '''') + - ';' AS finding - FROM bi - OPTION ( RECOMPILE ); - /*Check 10 gets total deadlock wait time per object*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Check 10 %s', 0, 1, @d) WITH NOWAIT; - WITH chopsuey AS ( - SELECT DISTINCT - PARSENAME(dow.object_name, 3) AS database_name, - dow.object_name, - CONVERT(VARCHAR(10), (SUM(DISTINCT dp.wait_time) / 1000) / 86400) AS wait_days, - CONVERT(VARCHAR(20), DATEADD(SECOND, (SUM(DISTINCT dp.wait_time) / 1000), 0), 108) AS wait_time_hms - FROM #deadlock_owner_waiter AS dow - JOIN #deadlock_process AS dp - ON (dp.id = dow.owner_id OR dp.victim_id = dow.waiter_id) - AND dp.event_date = dow.event_date - WHERE 1 = 1 - AND (DB_NAME(dow.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (dow.event_date >= @StartDate OR @StartDate IS NULL) - AND (dow.event_date < @EndDate OR @EndDate IS NULL) - AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) - AND (dp.client_app = @AppName OR @AppName IS NULL) - AND (dp.host_name = @HostName OR @HostName IS NULL) - AND (dp.login_name = @LoginName OR @LoginName IS NULL) - GROUP BY PARSENAME(dow.object_name, 3), dow.object_name - ) - INSERT #deadlock_findings WITH (TABLOCKX) - ( check_id, database_name, object_name, finding_group, finding ) - SELECT 10 AS check_id, - cs.database_name, - cs.object_name, - 'Total object deadlock wait time' AS finding_group, - 'This object has had ' - + CONVERT(VARCHAR(10), cs.wait_days) - + ':' + CONVERT(VARCHAR(20), cs.wait_time_hms, 108) - + ' [d/h/m/s] of deadlock wait time.' AS finding - FROM chopsuey AS cs - WHERE cs.object_name IS NOT NULL - OPTION ( RECOMPILE ); +/*Get longest cpu plans*/ - /*Check 11 gets total deadlock wait time per database*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Check 11 %s', 0, 1, @d) WITH NOWAIT; - WITH wait_time AS ( - SELECT DB_NAME(dp.database_id) AS database_name, - SUM(CONVERT(BIGINT, dp.wait_time)) AS total_wait_time_ms - FROM #deadlock_process AS dp - WHERE 1 = 1 - AND (DB_NAME(dp.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (dp.event_date >= @StartDate OR @StartDate IS NULL) - AND (dp.event_date < @EndDate OR @EndDate IS NULL) - AND (dp.client_app = @AppName OR @AppName IS NULL) - AND (dp.host_name = @HostName OR @HostName IS NULL) - AND (dp.login_name = @LoginName OR @LoginName IS NULL) - GROUP BY DB_NAME(dp.database_id) - ) - INSERT #deadlock_findings WITH (TABLOCKX) - ( check_id, database_name, object_name, finding_group, finding ) - SELECT 11 AS check_id, - wt.database_name, - '-' AS object_name, - 'Total database deadlock wait time' AS finding_group, - 'This database has had ' - + CONVERT(VARCHAR(10), (SUM(DISTINCT wt.total_wait_time_ms) / 1000) / 86400) - + ':' + CONVERT(VARCHAR(20), DATEADD(SECOND, (SUM(DISTINCT wt.total_wait_time_ms) / 1000), 0), 108) - + ' [d/h/m/s] of deadlock wait time.' - FROM wait_time AS wt - GROUP BY wt.database_name - OPTION ( RECOMPILE ); +RAISERROR(N'Gathering highest cpu plans', 0, 1) WITH NOWAIT; - /*Check 12 gets total deadlock wait time for SQL Agent*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Check 12 %s', 0, 1, @d) WITH NOWAIT; - INSERT #deadlock_findings WITH (TABLOCKX) - ( check_id, database_name, object_name, finding_group, finding ) - SELECT 12, - DB_NAME(aj.database_id), - 'SQLAgent - Job: ' - + aj.job_name - + ' Step: ' - + aj.step_name, - 'Agent Job Deadlocks', - RTRIM(COUNT(*)) + ' deadlocks from this Agent Job and Step' - FROM #agent_job AS aj - GROUP BY DB_NAME(aj.database_id), aj.job_name, aj.step_name - OPTION ( RECOMPILE ); - - /*Check 13 is total parallel deadlocks*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Check 13 %s', 0, 1, @d) WITH NOWAIT; - INSERT #deadlock_findings WITH (TABLOCKX) - ( check_id, database_name, object_name, finding_group, finding ) - SELECT 13 AS check_id, - N'-' AS database_name, - '-' AS object_name, - 'Total parallel deadlocks' AS finding_group, - 'There have been ' - + CONVERT(NVARCHAR(20), COUNT_BIG(DISTINCT drp.event_date)) - + ' parallel deadlocks.' - FROM #deadlock_resource_parallel AS drp - WHERE 1 = 1 - HAVING COUNT_BIG(DISTINCT drp.event_date) > 0 - OPTION ( RECOMPILE ); +SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; +SET @sql_select += N' +WITH cpu_max +AS ( SELECT TOP 1 + gi.start_range, + gi.end_range + FROM #grouped_interval AS gi + ORDER BY gi.total_avg_cpu_time_ms DESC ) +INSERT #working_plans WITH (TABLOCK) + ( plan_id, query_id, pattern ) +SELECT TOP ( @sp_Top ) + qsp.plan_id, qsp.query_id, ''avg cpu'' +FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp +JOIN cpu_max AS dm +ON qsp.last_execution_time >= dm.start_range + AND qsp.last_execution_time < dm.end_range +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs +ON qsrs.plan_id = qsp.plan_id +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq +ON qsq.query_id = qsp.query_id +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt +ON qsqt.query_text_id = qsq.query_text_id +WHERE 1 = 1 + AND qsq.is_internal_query = 0 + AND qsp.query_plan IS NOT NULL + '; - /*Thank you goodnight*/ - INSERT #deadlock_findings WITH (TABLOCKX) - ( check_id, database_name, object_name, finding_group, finding ) - VALUES ( -1, - N'sp_BlitzLock ' + CAST(CONVERT(DATETIME, @VersionDate, 102) AS VARCHAR(100)), - N'SQL Server First Responder Kit', - N'http://FirstResponderKit.org/', - N'To get help or add your own contributions, join us at http://FirstResponderKit.org.'); +SET @sql_select += @sql_where; - +SET @sql_select += N'ORDER BY qsrs.avg_cpu_time DESC + OPTION (RECOMPILE); + '; +IF @Debug = 1 + PRINT @sql_select; - /*Results*/ - /*Break in case of emergency*/ - --CREATE CLUSTERED INDEX cx_whatever ON #deadlock_process (event_date, id); - --CREATE CLUSTERED INDEX cx_whatever ON #deadlock_resource_parallel (event_date, owner_id); - --CREATE CLUSTERED INDEX cx_whatever ON #deadlock_owner_waiter (event_date, owner_id, waiter_id); - IF(@OutputDatabaseCheck = 0) - BEGIN - - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Results 1 %s', 0, 1, @d) WITH NOWAIT; - WITH deadlocks - AS ( SELECT N'Regular Deadlock' AS deadlock_type, - dp.event_date, - dp.id, - dp.victim_id, - dp.database_id, - dp.priority, - dp.log_used, - dp.wait_resource COLLATE DATABASE_DEFAULT AS wait_resource, - CONVERT( - XML, - STUFF(( SELECT DISTINCT NCHAR(10) - + N' ' - + ISNULL(c.object_name, N'') - + N' ' COLLATE DATABASE_DEFAULT AS object_name - FROM #deadlock_owner_waiter AS c - WHERE ( dp.id = c.owner_id - OR dp.victim_id = c.waiter_id ) - AND CONVERT(DATE, dp.event_date) = CONVERT(DATE, c.event_date) - FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(4000)'), - 1, 1, N'')) AS object_names, - dp.wait_time, - dp.transaction_name, - dp.last_tran_started, - dp.last_batch_started, - dp.last_batch_completed, - dp.lock_mode, - dp.transaction_count, - dp.client_app, - dp.host_name, - dp.login_name, - dp.isolation_level, - dp.process_xml.value('(//process/inputbuf/text())[1]', 'NVARCHAR(MAX)') AS inputbuf, - ROW_NUMBER() OVER ( PARTITION BY dp.event_date, dp.id ORDER BY dp.event_date ) AS dn, - DENSE_RANK() OVER ( ORDER BY dp.event_date ) AS en, - ROW_NUMBER() OVER ( PARTITION BY dp.event_date ORDER BY dp.event_date ) -1 AS qn, - dp.is_victim, - ISNULL(dp.owner_mode, '-') AS owner_mode, - NULL AS owner_waiter_type, - NULL AS owner_activity, - NULL AS owner_waiter_activity, - NULL AS owner_merging, - NULL AS owner_spilling, - NULL AS owner_waiting_to_close, - ISNULL(dp.waiter_mode, '-') AS waiter_mode, - NULL AS waiter_waiter_type, - NULL AS waiter_owner_activity, - NULL AS waiter_waiter_activity, - NULL AS waiter_merging, - NULL AS waiter_spilling, - NULL AS waiter_waiting_to_close, - dp.deadlock_graph - FROM #deadlock_process AS dp - WHERE dp.victim_id IS NOT NULL - AND dp.is_parallel = 0 - - UNION ALL - - SELECT N'Parallel Deadlock' AS deadlock_type, - dp.event_date, - dp.id, - dp.victim_id, - dp.database_id, - dp.priority, - dp.log_used, - dp.wait_resource COLLATE DATABASE_DEFAULT, - CASE WHEN @ExportToExcel = 0 THEN - CONVERT( - XML, - STUFF(( SELECT DISTINCT NCHAR(10) - + N' ' - + ISNULL(c.object_name, N'') - + N' ' COLLATE DATABASE_DEFAULT AS object_name - FROM #deadlock_owner_waiter AS c - WHERE ( dp.id = c.owner_id - OR dp.victim_id = c.waiter_id ) - AND CONVERT(DATE, dp.event_date) = CONVERT(DATE, c.event_date) - FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(4000)'), - 1, 1, N'')) - ELSE NULL END AS object_names, - dp.wait_time, - dp.transaction_name, - dp.last_tran_started, - dp.last_batch_started, - dp.last_batch_completed, - dp.lock_mode, - dp.transaction_count, - dp.client_app, - dp.host_name, - dp.login_name, - dp.isolation_level, - dp.process_xml.value('(//process/inputbuf/text())[1]', 'NVARCHAR(MAX)') AS inputbuf, - ROW_NUMBER() OVER ( PARTITION BY dp.event_date, dp.id ORDER BY dp.event_date ) AS dn, - DENSE_RANK() OVER ( ORDER BY dp.event_date ) AS en, - ROW_NUMBER() OVER ( PARTITION BY dp.event_date ORDER BY dp.event_date ) -1 AS qn, - 1 AS is_victim, - cao.wait_type COLLATE DATABASE_DEFAULT AS owner_mode, - cao.waiter_type AS owner_waiter_type, - cao.owner_activity AS owner_activity, - cao.waiter_activity AS owner_waiter_activity, - cao.merging AS owner_merging, - cao.spilling AS owner_spilling, - cao.waiting_to_close AS owner_waiting_to_close, - caw.wait_type COLLATE DATABASE_DEFAULT AS waiter_mode, - caw.waiter_type AS waiter_waiter_type, - caw.owner_activity AS waiter_owner_activity, - caw.waiter_activity AS waiter_waiter_activity, - caw.merging AS waiter_merging, - caw.spilling AS waiter_spilling, - caw.waiting_to_close AS waiter_waiting_to_close, - dp.deadlock_graph - FROM #deadlock_process AS dp - CROSS APPLY (SELECT TOP 1 * FROM #deadlock_resource_parallel AS drp WHERE drp.owner_id = dp.id AND drp.wait_type = 'e_waitPipeNewRow' ORDER BY drp.event_date) AS cao - CROSS APPLY (SELECT TOP 1 * FROM #deadlock_resource_parallel AS drp WHERE drp.owner_id = dp.id AND drp.wait_type = 'e_waitPipeGetRow' ORDER BY drp.event_date) AS caw - WHERE dp.is_parallel = 1 ) - insert into DeadLockTbl ( - ServerName, - deadlock_type, - event_date, - database_name, - deadlock_group, - query, - object_names, - isolation_level, - owner_mode, - waiter_mode, - transaction_count, - login_name, - host_name, - client_app, - wait_time, - priority, - log_used, - last_tran_started, - last_batch_started, - last_batch_completed, - transaction_name, - owner_waiter_type, - owner_activity, - owner_waiter_activity, - owner_merging, - owner_spilling, - owner_waiting_to_close, - waiter_waiter_type, - waiter_owner_activity, - waiter_waiter_activity, - waiter_merging, - waiter_spilling, - waiter_waiting_to_close, - deadlock_graph - ) - SELECT @ServerName, - d.deadlock_type, - d.event_date, - DB_NAME(d.database_id) AS database_name, - 'Deadlock #' - + CONVERT(NVARCHAR(10), d.en) - + ', Query #' - + CASE WHEN d.qn = 0 THEN N'1' ELSE CONVERT(NVARCHAR(10), d.qn) END - + CASE WHEN d.is_victim = 1 THEN ' - VICTIM' ELSE '' END - AS deadlock_group, - CONVERT(XML, N'') AS query, - d.object_names, - d.isolation_level, - d.owner_mode, - d.waiter_mode, - d.transaction_count, - d.login_name, - d.host_name, - d.client_app, - d.wait_time, - d.priority, - d.log_used, - d.last_tran_started, - d.last_batch_started, - d.last_batch_completed, - d.transaction_name, - /*These columns will be NULL for regular (non-parallel) deadlocks*/ - d.owner_waiter_type, - d.owner_activity, - d.owner_waiter_activity, - d.owner_merging, - d.owner_spilling, - d.owner_waiting_to_close, - d.waiter_waiter_type, - d.waiter_owner_activity, - d.waiter_waiter_activity, - d.waiter_merging, - d.waiter_spilling, - d.waiter_waiting_to_close, - d.deadlock_graph - FROM deadlocks AS d - WHERE d.dn = 1 - AND (is_victim = @VictimsOnly OR @VictimsOnly = 0) - AND d.en < CASE WHEN d.deadlock_type = N'Parallel Deadlock' THEN 2 ELSE 2147483647 END - AND (DB_NAME(d.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (d.event_date >= @StartDate OR @StartDate IS NULL) - AND (d.event_date < @EndDate OR @EndDate IS NULL) - AND (CONVERT(NVARCHAR(MAX), d.object_names) LIKE '%' + @ObjectName + '%' OR @ObjectName IS NULL) - AND (d.client_app = @AppName OR @AppName IS NULL) - AND (d.host_name = @HostName OR @HostName IS NULL) - AND (d.login_name = @LoginName OR @LoginName IS NULL) - ORDER BY d.event_date, is_victim DESC - OPTION ( RECOMPILE ); - - drop SYNONYM DeadLockTbl; --done insert into blitzlock table going to insert into findings table first create synonym. +IF @sql_select IS NULL + BEGIN + RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; + RETURN; + END; - -- RAISERROR('att deadlock findings', 0, 1) WITH NOWAIT; - +EXEC sys.sp_executesql @stmt = @sql_select, + @params = @sp_params, + @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Findings %s', 0, 1, @d) WITH NOWAIT; - Insert into DeadlockFindings (ServerName,check_id,database_name,object_name,finding_group,finding) - SELECT @ServerName,df.check_id, df.database_name, df.object_name, df.finding_group, df.finding - FROM #deadlock_findings AS df - ORDER BY df.check_id - OPTION ( RECOMPILE ); +SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; +SET @sql_select += N' +WITH cpu_max +AS ( SELECT TOP 1 + gi.start_range, + gi.end_range + FROM #grouped_interval AS gi + ORDER BY gi.total_max_cpu_time_ms DESC ) +INSERT #working_plans WITH (TABLOCK) + ( plan_id, query_id, pattern ) +SELECT TOP ( @sp_Top ) + qsp.plan_id, qsp.query_id, ''max cpu'' +FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp +JOIN cpu_max AS dm +ON qsp.last_execution_time >= dm.start_range + AND qsp.last_execution_time < dm.end_range +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs +ON qsrs.plan_id = qsp.plan_id +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq +ON qsq.query_id = qsp.query_id +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt +ON qsqt.query_text_id = qsq.query_text_id +WHERE 1 = 1 + AND qsq.is_internal_query = 0 + AND qsp.query_plan IS NOT NULL + '; - drop SYNONYM DeadlockFindings; --done with inserting. -END -ELSE --Output to database is not set output to client app - BEGIN - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Results 1 %s', 0, 1, @d) WITH NOWAIT; - WITH deadlocks - AS ( SELECT N'Regular Deadlock' AS deadlock_type, - dp.event_date, - dp.id, - dp.victim_id, - dp.database_id, - dp.priority, - dp.log_used, - dp.wait_resource COLLATE DATABASE_DEFAULT AS wait_resource, - CASE WHEN @ExportToExcel = 0 THEN - CONVERT( - XML, - STUFF(( SELECT DISTINCT NCHAR(10) - + N' ' - + ISNULL(c.object_name, N'') - + N' ' COLLATE DATABASE_DEFAULT AS object_name - FROM #deadlock_owner_waiter AS c - WHERE ( dp.id = c.owner_id - OR dp.victim_id = c.waiter_id ) - AND CONVERT(DATE, dp.event_date) = CONVERT(DATE, c.event_date) - FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(4000)'), - 1, 1, N'')) - ELSE NULL END AS object_names, - dp.wait_time, - dp.transaction_name, - dp.last_tran_started, - dp.last_batch_started, - dp.last_batch_completed, - dp.lock_mode, - dp.transaction_count, - dp.client_app, - dp.host_name, - dp.login_name, - dp.isolation_level, - dp.process_xml.value('(//process/inputbuf/text())[1]', 'NVARCHAR(MAX)') AS inputbuf, - ROW_NUMBER() OVER ( PARTITION BY dp.event_date, dp.id ORDER BY dp.event_date ) AS dn, - DENSE_RANK() OVER ( ORDER BY dp.event_date ) AS en, - ROW_NUMBER() OVER ( PARTITION BY dp.event_date ORDER BY dp.event_date ) -1 AS qn, - dp.is_victim, - ISNULL(dp.owner_mode, '-') AS owner_mode, - NULL AS owner_waiter_type, - NULL AS owner_activity, - NULL AS owner_waiter_activity, - NULL AS owner_merging, - NULL AS owner_spilling, - NULL AS owner_waiting_to_close, - ISNULL(dp.waiter_mode, '-') AS waiter_mode, - NULL AS waiter_waiter_type, - NULL AS waiter_owner_activity, - NULL AS waiter_waiter_activity, - NULL AS waiter_merging, - NULL AS waiter_spilling, - NULL AS waiter_waiting_to_close, - dp.deadlock_graph - FROM #deadlock_process AS dp - WHERE dp.victim_id IS NOT NULL - AND dp.is_parallel = 0 +SET @sql_select += @sql_where; - UNION ALL - - SELECT N'Parallel Deadlock' AS deadlock_type, - dp.event_date, - dp.id, - dp.victim_id, - dp.database_id, - dp.priority, - dp.log_used, - dp.wait_resource COLLATE DATABASE_DEFAULT, - CASE WHEN @ExportToExcel = 0 THEN - CONVERT( - XML, - STUFF(( SELECT DISTINCT NCHAR(10) - + N' ' - + ISNULL(c.object_name, N'') - + N' ' COLLATE DATABASE_DEFAULT AS object_name - FROM #deadlock_owner_waiter AS c - WHERE ( dp.id = c.owner_id - OR dp.victim_id = c.waiter_id ) - AND CONVERT(DATE, dp.event_date) = CONVERT(DATE, c.event_date) - FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(4000)'), - 1, 1, N'')) - ELSE NULL END AS object_names, - dp.wait_time, - dp.transaction_name, - dp.last_tran_started, - dp.last_batch_started, - dp.last_batch_completed, - dp.lock_mode, - dp.transaction_count, - dp.client_app, - dp.host_name, - dp.login_name, - dp.isolation_level, - dp.process_xml.value('(//process/inputbuf/text())[1]', 'NVARCHAR(MAX)') AS inputbuf, - ROW_NUMBER() OVER ( PARTITION BY dp.event_date, dp.id ORDER BY dp.event_date ) AS dn, - DENSE_RANK() OVER ( ORDER BY dp.event_date ) AS en, - ROW_NUMBER() OVER ( PARTITION BY dp.event_date ORDER BY dp.event_date ) -1 AS qn, - 1 AS is_victim, - cao.wait_type COLLATE DATABASE_DEFAULT AS owner_mode, - cao.waiter_type AS owner_waiter_type, - cao.owner_activity AS owner_activity, - cao.waiter_activity AS owner_waiter_activity, - cao.merging AS owner_merging, - cao.spilling AS owner_spilling, - cao.waiting_to_close AS owner_waiting_to_close, - caw.wait_type COLLATE DATABASE_DEFAULT AS waiter_mode, - caw.waiter_type AS waiter_waiter_type, - caw.owner_activity AS waiter_owner_activity, - caw.waiter_activity AS waiter_waiter_activity, - caw.merging AS waiter_merging, - caw.spilling AS waiter_spilling, - caw.waiting_to_close AS waiter_waiting_to_close, - dp.deadlock_graph - FROM #deadlock_process AS dp - OUTER APPLY (SELECT TOP 1 * FROM #deadlock_resource_parallel AS drp WHERE drp.owner_id = dp.id AND drp.wait_type IN ('e_waitPortOpen', 'e_waitPipeNewRow') ORDER BY drp.event_date) AS cao - OUTER APPLY (SELECT TOP 1 * FROM #deadlock_resource_parallel AS drp WHERE drp.owner_id = dp.id AND drp.wait_type IN ('e_waitPortOpen', 'e_waitPipeGetRow') ORDER BY drp.event_date) AS caw - WHERE dp.is_parallel = 1 - ) - SELECT d.deadlock_type, - d.event_date, - DB_NAME(d.database_id) AS database_name, - 'Deadlock #' - + CONVERT(NVARCHAR(10), d.en) - + ', Query #' - + CASE WHEN d.qn = 0 THEN N'1' ELSE CONVERT(NVARCHAR(10), d.qn) END - + CASE WHEN d.is_victim = 1 THEN ' - VICTIM' ELSE '' END - AS deadlock_group, - CASE WHEN @ExportToExcel = 0 THEN CONVERT(XML, N'') - ELSE SUBSTRING(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(LTRIM(RTRIM(d.inputbuf)),' ','<>'),'><',''),NCHAR(10), ' '),NCHAR(13), ' '),'<>',' '), 1, 32000) END AS query, - d.object_names, - d.isolation_level, - d.owner_mode, - d.waiter_mode, - d.transaction_count, - d.login_name, - d.host_name, - d.client_app, - d.wait_time, - d.priority, - d.log_used, - d.last_tran_started, - d.last_batch_started, - d.last_batch_completed, - d.transaction_name, - /*These columns will be NULL for regular (non-parallel) deadlocks*/ - d.owner_waiter_type, - d.owner_activity, - d.owner_waiter_activity, - d.owner_merging, - d.owner_spilling, - d.owner_waiting_to_close, - d.waiter_waiter_type, - d.waiter_owner_activity, - d.waiter_waiter_activity, - d.waiter_merging, - d.waiter_spilling, - d.waiter_waiting_to_close, - CASE WHEN @ExportToExcel = 0 THEN d.deadlock_graph ELSE NULL END AS deadlock_graph - FROM deadlocks AS d - WHERE d.dn = 1 - AND (is_victim = @VictimsOnly OR @VictimsOnly = 0) - AND d.en < CASE WHEN d.deadlock_type = N'Parallel Deadlock' THEN 2 ELSE 2147483647 END - AND (DB_NAME(d.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (d.event_date >= @StartDate OR @StartDate IS NULL) - AND (d.event_date < @EndDate OR @EndDate IS NULL) - AND (CONVERT(NVARCHAR(MAX), d.object_names) LIKE '%' + @ObjectName + '%' OR @ObjectName IS NULL) - AND (d.client_app = @AppName OR @AppName IS NULL) - AND (d.host_name = @HostName OR @HostName IS NULL) - AND (d.login_name = @LoginName OR @LoginName IS NULL) - ORDER BY d.event_date, is_victim DESC - OPTION ( RECOMPILE ); - - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Findings %s', 0, 1, @d) WITH NOWAIT; - SELECT df.check_id, df.database_name, df.object_name, df.finding_group, df.finding - FROM #deadlock_findings AS df - ORDER BY df.check_id - OPTION ( RECOMPILE ); - - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Done %s', 0, 1, @d) WITH NOWAIT; - END --done with output to client app. +SET @sql_select += N'ORDER BY qsrs.max_cpu_time DESC + OPTION (RECOMPILE); + '; +IF @Debug = 1 + PRINT @sql_select; +IF @sql_select IS NULL + BEGIN + RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; + RETURN; + END; - IF @Debug = 1 - BEGIN +EXEC sys.sp_executesql @stmt = @sql_select, + @params = @sp_params, + @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; - SELECT '#deadlock_data' AS table_name, * - FROM #deadlock_data AS dd - OPTION ( RECOMPILE ); - SELECT '#deadlock_resource' AS table_name, * - FROM #deadlock_resource AS dr - OPTION ( RECOMPILE ); +/*Get highest logical read plans*/ - SELECT '#deadlock_resource_parallel' AS table_name, * - FROM #deadlock_resource_parallel AS drp - OPTION ( RECOMPILE ); +RAISERROR(N'Gathering highest logical read plans', 0, 1) WITH NOWAIT; - SELECT '#deadlock_owner_waiter' AS table_name, * - FROM #deadlock_owner_waiter AS dow - OPTION ( RECOMPILE ); +SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; +SET @sql_select += N' +WITH logical_reads_max +AS ( SELECT TOP 1 + gi.start_range, + gi.end_range + FROM #grouped_interval AS gi + ORDER BY gi.total_avg_logical_io_reads_mb DESC ) +INSERT #working_plans WITH (TABLOCK) + ( plan_id, query_id, pattern ) +SELECT TOP ( @sp_Top ) + qsp.plan_id, qsp.query_id, ''avg logical reads'' +FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp +JOIN logical_reads_max AS dm +ON qsp.last_execution_time >= dm.start_range + AND qsp.last_execution_time < dm.end_range +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs +ON qsrs.plan_id = qsp.plan_id +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq +ON qsq.query_id = qsp.query_id +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt +ON qsqt.query_text_id = qsq.query_text_id +WHERE 1 = 1 + AND qsq.is_internal_query = 0 + AND qsp.query_plan IS NOT NULL + '; - SELECT '#deadlock_process' AS table_name, * - FROM #deadlock_process AS dp - OPTION ( RECOMPILE ); +SET @sql_select += @sql_where; - SELECT '#deadlock_stack' AS table_name, * - FROM #deadlock_stack AS ds - OPTION ( RECOMPILE ); - - END; -- End debug +SET @sql_select += N'ORDER BY qsrs.avg_logical_io_reads DESC + OPTION (RECOMPILE); + '; - END; --Final End +IF @Debug = 1 + PRINT @sql_select; -GO -SET ANSI_NULLS ON; -SET ANSI_PADDING ON; -SET ANSI_WARNINGS ON; -SET ARITHABORT ON; -SET CONCAT_NULL_YIELDS_NULL ON; -SET QUOTED_IDENTIFIER ON; -SET STATISTICS IO OFF; -SET STATISTICS TIME OFF; -GO +IF @sql_select IS NULL + BEGIN + RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; + RETURN; + END; -DECLARE @msg NVARCHAR(MAX) = N''; +EXEC sys.sp_executesql @stmt = @sql_select, + @params = @sp_params, + @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; - -- Must be a compatible, on-prem version of SQL (2016+) -IF ( (SELECT CONVERT(NVARCHAR(128), SERVERPROPERTY ('EDITION'))) <> 'SQL Azure' - AND (SELECT PARSENAME(CONVERT(NVARCHAR(128), SERVERPROPERTY ('PRODUCTVERSION')), 4)) < 13 - ) - -- or Azure Database (not Azure Data Warehouse), running at database compat level 130+ -OR ( (SELECT CONVERT(NVARCHAR(128), SERVERPROPERTY ('EDITION'))) = 'SQL Azure' - AND (SELECT SERVERPROPERTY ('ENGINEEDITION')) NOT IN (5,8) - AND (SELECT [compatibility_level] FROM sys.databases WHERE [name] = DB_NAME()) < 130 - ) -BEGIN - SELECT @msg = N'Sorry, sp_BlitzQueryStore doesn''t work on versions of SQL prior to 2016, or Azure Database compatibility < 130.' + REPLICATE(CHAR(13), 7933); - PRINT @msg; - RETURN; -END; -IF OBJECT_ID('dbo.sp_BlitzQueryStore') IS NULL - EXEC ('CREATE PROCEDURE dbo.sp_BlitzQueryStore AS RETURN 0;'); -GO +SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; +SET @sql_select += N' +WITH logical_reads_max +AS ( SELECT TOP 1 + gi.start_range, + gi.end_range + FROM #grouped_interval AS gi + ORDER BY gi.total_max_logical_io_reads_mb DESC ) +INSERT #working_plans WITH (TABLOCK) + ( plan_id, query_id, pattern ) +SELECT TOP ( @sp_Top ) + qsp.plan_id, qsp.query_id, ''max logical reads'' +FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp +JOIN logical_reads_max AS dm +ON qsp.last_execution_time >= dm.start_range + AND qsp.last_execution_time < dm.end_range +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs +ON qsrs.plan_id = qsp.plan_id +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq +ON qsq.query_id = qsp.query_id +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt +ON qsqt.query_text_id = qsq.query_text_id +WHERE 1 = 1 + AND qsq.is_internal_query = 0 + AND qsp.query_plan IS NOT NULL + '; -ALTER PROCEDURE dbo.sp_BlitzQueryStore - @Help BIT = 0, - @DatabaseName NVARCHAR(128) = NULL , - @Top INT = 3, - @StartDate DATETIME2 = NULL, - @EndDate DATETIME2 = NULL, - @MinimumExecutionCount INT = NULL, - @DurationFilter DECIMAL(38,4) = NULL , - @StoredProcName NVARCHAR(128) = NULL, - @Failed BIT = 0, - @PlanIdFilter INT = NULL, - @QueryIdFilter INT = NULL, - @ExportToExcel BIT = 0, - @HideSummary BIT = 0 , - @SkipXML BIT = 0, - @Debug BIT = 0, - @ExpertMode BIT = 0, - @Version VARCHAR(30) = NULL OUTPUT, - @VersionDate DATETIME = NULL OUTPUT, - @VersionCheckMode BIT = 0 -WITH RECOMPILE -AS -BEGIN /*First BEGIN*/ +SET @sql_select += @sql_where; -SET NOCOUNT ON; -SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; +SET @sql_select += N'ORDER BY qsrs.max_logical_io_reads DESC + OPTION (RECOMPILE); + '; -SELECT @Version = '8.0', @VersionDate = '20210117'; -IF(@VersionCheckMode = 1) -BEGIN - RETURN; -END; +IF @Debug = 1 + PRINT @sql_select; +IF @sql_select IS NULL + BEGIN + RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; + RETURN; + END; -DECLARE /*Variables for the variable Gods*/ - @msg NVARCHAR(MAX) = N'', --Used to format RAISERROR messages in some places - @sql_select NVARCHAR(MAX) = N'', --Used to hold SELECT statements for dynamic SQL - @sql_where NVARCHAR(MAX) = N'', -- Used to hold WHERE clause for dynamic SQL - @duration_filter_ms DECIMAL(38,4) = (@DurationFilter * 1000.), --We accept Duration in seconds, but we filter in milliseconds (this is grandfathered from sp_BlitzCache) - @execution_threshold INT = 1000, --Threshold at which we consider a query to be frequently executed - @ctp_threshold_pct TINYINT = 10, --Percentage of CTFP at which we consider a query to be near parallel - @long_running_query_warning_seconds BIGINT = 300 * 1000 ,--Number of seconds (converted to milliseconds) at which a query is considered long running - @memory_grant_warning_percent INT = 10,--Percent of memory grant used compared to what's granted; used to trigger unused memory grant warning - @ctp INT,--Holds the CTFP value for the server - @min_memory_per_query INT,--Holds the server configuration value for min memory per query - @cr NVARCHAR(1) = NCHAR(13),--Special character - @lf NVARCHAR(1) = NCHAR(10),--Special character - @tab NVARCHAR(1) = NCHAR(9),--Special character - @error_severity INT,--Holds error info for try/catch blocks - @error_state INT,--Holds error info for try/catch blocks - @sp_params NVARCHAR(MAX) = N'@sp_Top INT, @sp_StartDate DATETIME2, @sp_EndDate DATETIME2, @sp_MinimumExecutionCount INT, @sp_MinDuration INT, @sp_StoredProcName NVARCHAR(128), @sp_PlanIdFilter INT, @sp_QueryIdFilter INT',--Holds parameters used in dynamic SQL - @is_azure_db BIT = 0, --Are we using Azure? I'm not. You might be. That's cool. - @compatibility_level TINYINT = 0, --Some functionality (T-SQL) isn't available in lower compat levels. We can use this to weed out those issues as we go. - @log_size_mb DECIMAL(38,2) = 0, - @avg_tempdb_data_file DECIMAL(38,2) = 0; +EXEC sys.sp_executesql @stmt = @sql_select, + @params = @sp_params, + @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; -/*Grabs CTFP setting*/ -SELECT @ctp = NULLIF(CAST(value AS INT), 0) -FROM sys.configurations -WHERE name = N'cost threshold for parallelism' -OPTION (RECOMPILE); -/*Grabs min query memory setting*/ -SELECT @min_memory_per_query = CONVERT(INT, c.value) -FROM sys.configurations AS c -WHERE c.name = N'min memory per query (KB)' -OPTION (RECOMPILE); +/*Get highest physical read plans*/ -/*Check if this is Azure first*/ -IF (SELECT CONVERT(NVARCHAR(128), SERVERPROPERTY ('EDITION'))) <> 'SQL Azure' - BEGIN - /*Grabs log size for datbase*/ - SELECT @log_size_mb = AVG(((mf.size * 8) / 1024.)) - FROM sys.master_files AS mf - WHERE mf.database_id = DB_ID(@DatabaseName) - AND mf.type_desc = 'LOG'; - - /*Grab avg tempdb file size*/ - SELECT @avg_tempdb_data_file = AVG(((mf.size * 8) / 1024.)) - FROM sys.master_files AS mf - WHERE mf.database_id = DB_ID('tempdb') - AND mf.type_desc = 'ROWS'; - END; +RAISERROR(N'Gathering highest physical read plans', 0, 1) WITH NOWAIT; -/*Help section*/ +SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; +SET @sql_select += N' +WITH physical_read_max +AS ( SELECT TOP 1 + gi.start_range, + gi.end_range + FROM #grouped_interval AS gi + ORDER BY gi.total_avg_physical_io_reads_mb DESC ) +INSERT #working_plans WITH (TABLOCK) + ( plan_id, query_id, pattern ) +SELECT TOP ( @sp_Top ) + qsp.plan_id, qsp.query_id, ''avg physical reads'' +FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp +JOIN physical_read_max AS dm +ON qsp.last_execution_time >= dm.start_range + AND qsp.last_execution_time < dm.end_range +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs +ON qsrs.plan_id = qsp.plan_id +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq +ON qsq.query_id = qsp.query_id +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt +ON qsqt.query_text_id = qsq.query_text_id +WHERE 1 = 1 + AND qsq.is_internal_query = 0 + AND qsp.query_plan IS NOT NULL + '; -IF @Help = 1 - BEGIN - - SELECT N'You have requested assistance. It will arrive as soon as humanly possible.' AS [Take four red capsules, help is on the way]; +SET @sql_select += @sql_where; - PRINT N' - sp_BlitzQueryStore from http://FirstResponderKit.org - - This script displays your most resource-intensive queries from the Query Store, - and points to ways you can tune these queries to make them faster. - - - To learn more, visit http://FirstResponderKit.org where you can download new - versions for free, watch training videos on how it works, get more info on - the findings, contribute your own code, and more. - - Known limitations of this version: - - This query will not run on SQL Server versions less than 2016. - - This query will not run on Azure Databases with compatibility less than 130. - - This query will not run on Azure Data Warehouse. +SET @sql_select += N'ORDER BY qsrs.avg_physical_io_reads DESC + OPTION (RECOMPILE); + '; - Unknown limitations of this version: - - Could be tickling - - - MIT License - - Copyright (c) 2021 Brent Ozar Unlimited - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE. - '; - RETURN; +IF @Debug = 1 + PRINT @sql_select; -END; +IF @sql_select IS NULL + BEGIN + RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; + RETURN; + END; -/*Making sure your version is copasetic*/ -IF ( (SELECT CONVERT(NVARCHAR(128), SERVERPROPERTY ('EDITION'))) = 'SQL Azure' ) - BEGIN - SET @is_azure_db = 1; +EXEC sys.sp_executesql @stmt = @sql_select, + @params = @sp_params, + @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; - IF ( (SELECT SERVERPROPERTY ('ENGINEEDITION')) NOT IN (5,8) - OR (SELECT [compatibility_level] FROM sys.databases WHERE [name] = DB_NAME()) < 130 - ) - BEGIN - SELECT @msg = N'Sorry, sp_BlitzQueryStore doesn''t work on Azure Data Warehouse, or Azure Databases with DB compatibility < 130.' + REPLICATE(CHAR(13), 7933); - PRINT @msg; - RETURN; - END; - END; -ELSE IF ( (SELECT PARSENAME(CONVERT(NVARCHAR(128), SERVERPROPERTY ('PRODUCTVERSION')), 4) ) < 13 ) - BEGIN - SELECT @msg = N'Sorry, sp_BlitzQueryStore doesn''t work on versions of SQL prior to 2016.' + REPLICATE(CHAR(13), 7933); - PRINT @msg; - RETURN; - END; -/*Making sure at least one database uses QS*/ -IF ( SELECT COUNT(*) - FROM sys.databases AS d - WHERE d.is_query_store_on = 1 - AND d.user_access_desc='MULTI_USER' - AND d.state_desc = 'ONLINE' - AND d.name NOT IN ('master', 'model', 'msdb', 'tempdb', '32767') - AND d.is_distributor = 0 ) = 0 - BEGIN - SELECT @msg = N'You don''t currently have any databases with Query Store enabled.' + REPLICATE(CHAR(13), 7933); - PRINT @msg; - RETURN; - END; - -/*Making sure your databases are using QDS.*/ -RAISERROR('Checking database validity', 0, 1) WITH NOWAIT; +SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; +SET @sql_select += N' +WITH physical_read_max +AS ( SELECT TOP 1 + gi.start_range, + gi.end_range + FROM #grouped_interval AS gi + ORDER BY gi.total_max_physical_io_reads_mb DESC ) +INSERT #working_plans WITH (TABLOCK) + ( plan_id, query_id, pattern ) +SELECT TOP ( @sp_Top ) + qsp.plan_id, qsp.query_id, ''max physical reads'' +FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp +JOIN physical_read_max AS dm +ON qsp.last_execution_time >= dm.start_range + AND qsp.last_execution_time < dm.end_range +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs +ON qsrs.plan_id = qsp.plan_id +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq +ON qsq.query_id = qsp.query_id +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt +ON qsqt.query_text_id = qsq.query_text_id +WHERE 1 = 1 + AND qsq.is_internal_query = 0 + AND qsp.query_plan IS NOT NULL + '; -IF (@is_azure_db = 1) - SET @DatabaseName = DB_NAME(); -ELSE -BEGIN +SET @sql_select += @sql_where; - /*If we're on Azure we don't need to check all this @DatabaseName stuff...*/ +SET @sql_select += N'ORDER BY qsrs.max_physical_io_reads DESC + OPTION (RECOMPILE); + '; - SET @DatabaseName = LTRIM(RTRIM(@DatabaseName)); +IF @Debug = 1 + PRINT @sql_select; - /*Did you set @DatabaseName?*/ - RAISERROR('Making sure [%s] isn''t NULL', 0, 1, @DatabaseName) WITH NOWAIT; - IF (@DatabaseName IS NULL) - BEGIN - RAISERROR('@DatabaseName cannot be NULL', 0, 1) WITH NOWAIT; - RETURN; - END; +IF @sql_select IS NULL + BEGIN + RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; + RETURN; + END; - /*Does the database exist?*/ - RAISERROR('Making sure [%s] exists', 0, 1, @DatabaseName) WITH NOWAIT; - IF ((DB_ID(@DatabaseName)) IS NULL) - BEGIN - RAISERROR('The @DatabaseName you specified ([%s]) does not exist. Please check the name and try again.', 0, 1, @DatabaseName) WITH NOWAIT; - RETURN; - END; +EXEC sys.sp_executesql @stmt = @sql_select, + @params = @sp_params, + @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; - /*Is it online?*/ - RAISERROR('Making sure [%s] is online', 0, 1, @DatabaseName) WITH NOWAIT; - IF (DATABASEPROPERTYEX(@DatabaseName, 'Collation')) IS NULL - BEGIN - RAISERROR('The @DatabaseName you specified ([%s]) is not readable. Please check the name and try again. Better yet, check your server.', 0, 1, @DatabaseName); - RETURN; - END; -END; -/*Does it have Query Store enabled?*/ -RAISERROR('Making sure [%s] has Query Store enabled', 0, 1, @DatabaseName) WITH NOWAIT; -IF +/*Get highest logical write plans*/ - ( SELECT [d].[name] - FROM [sys].[databases] AS d - WHERE [d].[is_query_store_on] = 1 - AND [d].[user_access_desc]='MULTI_USER' - AND [d].[state_desc] = 'ONLINE' - AND [d].[database_id] = (SELECT database_id FROM sys.databases WHERE name = @DatabaseName) - ) IS NULL -BEGIN - RAISERROR('The @DatabaseName you specified ([%s]) does not have the Query Store enabled. Please check the name or settings, and try again.', 0, 1, @DatabaseName) WITH NOWAIT; - RETURN; -END; +RAISERROR(N'Gathering highest write plans', 0, 1) WITH NOWAIT; -/*Check database compat level*/ +SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; +SET @sql_select += N' +WITH logical_writes_max +AS ( SELECT TOP 1 + gi.start_range, + gi.end_range + FROM #grouped_interval AS gi + ORDER BY gi.total_avg_logical_io_writes_mb DESC ) +INSERT #working_plans WITH (TABLOCK) + ( plan_id, query_id, pattern ) +SELECT TOP ( @sp_Top ) + qsp.plan_id, qsp.query_id, ''avg writes'' +FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp +JOIN logical_writes_max AS dm +ON qsp.last_execution_time >= dm.start_range + AND qsp.last_execution_time < dm.end_range +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs +ON qsrs.plan_id = qsp.plan_id +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq +ON qsq.query_id = qsp.query_id +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt +ON qsqt.query_text_id = qsq.query_text_id +WHERE 1 = 1 + AND qsq.is_internal_query = 0 + AND qsp.query_plan IS NOT NULL + '; -RAISERROR('Checking database compatibility level', 0, 1) WITH NOWAIT; +SET @sql_select += @sql_where; -SELECT @compatibility_level = d.compatibility_level -FROM sys.databases AS d -WHERE d.name = @DatabaseName; +SET @sql_select += N'ORDER BY qsrs.avg_logical_io_writes DESC + OPTION (RECOMPILE); + '; -RAISERROR('The @DatabaseName you specified ([%s])is running in compatibility level ([%d]).', 0, 1, @DatabaseName, @compatibility_level) WITH NOWAIT; +IF @Debug = 1 + PRINT @sql_select; +IF @sql_select IS NULL + BEGIN + RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; + RETURN; + END; -/*Making sure top is set to something if NULL*/ -IF ( @Top IS NULL ) - BEGIN - SET @Top = 3; - END; +EXEC sys.sp_executesql @stmt = @sql_select, + @params = @sp_params, + @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; -/* -This section determines if you have the Query Store wait stats DMV -*/ -RAISERROR('Checking for query_store_wait_stats', 0, 1) WITH NOWAIT; +SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; +SET @sql_select += N' +WITH logical_writes_max +AS ( SELECT TOP 1 + gi.start_range, + gi.end_range + FROM #grouped_interval AS gi + ORDER BY gi.total_max_logical_io_writes_mb DESC ) +INSERT #working_plans WITH (TABLOCK) + ( plan_id, query_id, pattern ) +SELECT TOP ( @sp_Top ) + qsp.plan_id, qsp.query_id, ''max writes'' +FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp +JOIN logical_writes_max AS dm +ON qsp.last_execution_time >= dm.start_range + AND qsp.last_execution_time < dm.end_range +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs +ON qsrs.plan_id = qsp.plan_id +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq +ON qsq.query_id = qsp.query_id +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt +ON qsqt.query_text_id = qsq.query_text_id +WHERE 1 = 1 + AND qsq.is_internal_query = 0 + AND qsp.query_plan IS NOT NULL + '; -DECLARE @ws_out INT, - @waitstats BIT, - @ws_sql NVARCHAR(MAX) = N'SELECT @i_out = COUNT(*) FROM ' + QUOTENAME(@DatabaseName) + N'.sys.all_objects WHERE name = ''query_store_wait_stats'' OPTION (RECOMPILE);', - @ws_params NVARCHAR(MAX) = N'@i_out INT OUTPUT'; +SET @sql_select += @sql_where; -EXEC sys.sp_executesql @ws_sql, @ws_params, @i_out = @ws_out OUTPUT; +SET @sql_select += N'ORDER BY qsrs.max_logical_io_writes DESC + OPTION (RECOMPILE); + '; -SELECT @waitstats = CASE @ws_out WHEN 0 THEN 0 ELSE 1 END; +IF @Debug = 1 + PRINT @sql_select; -SET @msg = N'Wait stats DMV ' + CASE @waitstats - WHEN 0 THEN N' does not exist, skipping.' - WHEN 1 THEN N' exists, will analyze.' - END; -RAISERROR(@msg, 0, 1) WITH NOWAIT; +IF @sql_select IS NULL + BEGIN + RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; + RETURN; + END; -/* -This section determines if you have some additional columns present in 2017, in case they get back ported. -*/ +EXEC sys.sp_executesql @stmt = @sql_select, + @params = @sp_params, + @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; -RAISERROR('Checking for new columns in query_store_runtime_stats', 0, 1) WITH NOWAIT; -DECLARE @nc_out INT, - @new_columns BIT, - @nc_sql NVARCHAR(MAX) = N'SELECT @i_out = COUNT(*) - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.all_columns AS ac - WHERE OBJECT_NAME(object_id) = ''query_store_runtime_stats'' - AND ac.name IN ( - ''avg_num_physical_io_reads'', - ''last_num_physical_io_reads'', - ''min_num_physical_io_reads'', - ''max_num_physical_io_reads'', - ''avg_log_bytes_used'', - ''last_log_bytes_used'', - ''min_log_bytes_used'', - ''max_log_bytes_used'', - ''avg_tempdb_space_used'', - ''last_tempdb_space_used'', - ''min_tempdb_space_used'', - ''max_tempdb_space_used'' - ) OPTION (RECOMPILE);', - @nc_params NVARCHAR(MAX) = N'@i_out INT OUTPUT'; +/*Get highest memory use plans*/ -EXEC sys.sp_executesql @nc_sql, @ws_params, @i_out = @nc_out OUTPUT; +RAISERROR(N'Gathering highest memory use plans', 0, 1) WITH NOWAIT; -SELECT @new_columns = CASE @nc_out WHEN 12 THEN 1 ELSE 0 END; +SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; +SET @sql_select += N' +WITH memory_max +AS ( SELECT TOP 1 + gi.start_range, + gi.end_range + FROM #grouped_interval AS gi + ORDER BY gi.total_avg_query_max_used_memory_mb DESC ) +INSERT #working_plans WITH (TABLOCK) + ( plan_id, query_id, pattern ) +SELECT TOP ( @sp_Top ) + qsp.plan_id, qsp.query_id, ''avg memory'' +FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp +JOIN memory_max AS dm +ON qsp.last_execution_time >= dm.start_range + AND qsp.last_execution_time < dm.end_range +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs +ON qsrs.plan_id = qsp.plan_id +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq +ON qsq.query_id = qsp.query_id +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt +ON qsqt.query_text_id = qsq.query_text_id +WHERE 1 = 1 + AND qsq.is_internal_query = 0 + AND qsp.query_plan IS NOT NULL + '; -SET @msg = N'New query_store_runtime_stats columns ' + CASE @new_columns - WHEN 0 THEN N' do not exist, skipping.' - WHEN 1 THEN N' exist, will analyze.' - END; -RAISERROR(@msg, 0, 1) WITH NOWAIT; +SET @sql_select += @sql_where; - -/* -These are the temp tables we use -*/ +SET @sql_select += N'ORDER BY qsrs.avg_query_max_used_memory DESC + OPTION (RECOMPILE); + '; +IF @Debug = 1 + PRINT @sql_select; -/* -This one holds the grouped data that helps use figure out which periods to examine -*/ +IF @sql_select IS NULL + BEGIN + RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; + RETURN; + END; -RAISERROR(N'Creating temp tables', 0, 1) WITH NOWAIT; +EXEC sys.sp_executesql @stmt = @sql_select, + @params = @sp_params, + @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; -DROP TABLE IF EXISTS #grouped_interval; +SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; +SET @sql_select += N' +WITH memory_max +AS ( SELECT TOP 1 + gi.start_range, + gi.end_range + FROM #grouped_interval AS gi + ORDER BY gi.total_max_query_max_used_memory_mb DESC ) +INSERT #working_plans WITH (TABLOCK) + ( plan_id, query_id, pattern ) +SELECT TOP ( @sp_Top ) + qsp.plan_id, qsp.query_id, ''max memory'' +FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp +JOIN memory_max AS dm +ON qsp.last_execution_time >= dm.start_range + AND qsp.last_execution_time < dm.end_range +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs +ON qsrs.plan_id = qsp.plan_id +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq +ON qsq.query_id = qsp.query_id +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt +ON qsqt.query_text_id = qsq.query_text_id +WHERE 1 = 1 + AND qsq.is_internal_query = 0 + AND qsp.query_plan IS NOT NULL + '; -CREATE TABLE #grouped_interval -( - flat_date DATE NULL, - start_range DATETIME NULL, - end_range DATETIME NULL, - total_avg_duration_ms DECIMAL(38, 2) NULL, - total_avg_cpu_time_ms DECIMAL(38, 2) NULL, - total_avg_logical_io_reads_mb DECIMAL(38, 2) NULL, - total_avg_physical_io_reads_mb DECIMAL(38, 2) NULL, - total_avg_logical_io_writes_mb DECIMAL(38, 2) NULL, - total_avg_query_max_used_memory_mb DECIMAL(38, 2) NULL, - total_rowcount DECIMAL(38, 2) NULL, - total_count_executions BIGINT NULL, - total_avg_log_bytes_mb DECIMAL(38, 2) NULL, - total_avg_tempdb_space DECIMAL(38, 2) NULL, - total_max_duration_ms DECIMAL(38, 2) NULL, - total_max_cpu_time_ms DECIMAL(38, 2) NULL, - total_max_logical_io_reads_mb DECIMAL(38, 2) NULL, - total_max_physical_io_reads_mb DECIMAL(38, 2) NULL, - total_max_logical_io_writes_mb DECIMAL(38, 2) NULL, - total_max_query_max_used_memory_mb DECIMAL(38, 2) NULL, - total_max_log_bytes_mb DECIMAL(38, 2) NULL, - total_max_tempdb_space DECIMAL(38, 2) NULL, - INDEX gi_ix_dates CLUSTERED (start_range, end_range) -); +SET @sql_select += @sql_where; +SET @sql_select += N'ORDER BY qsrs.max_query_max_used_memory DESC + OPTION (RECOMPILE); + '; -/* -These are the plans we focus on based on what we find in the grouped intervals -*/ -DROP TABLE IF EXISTS #working_plans; +IF @Debug = 1 + PRINT @sql_select; -CREATE TABLE #working_plans -( - plan_id BIGINT, - query_id BIGINT, - pattern NVARCHAR(258), - INDEX wp_ix_ids CLUSTERED (plan_id, query_id) -); +IF @sql_select IS NULL + BEGIN + RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; + RETURN; + END; +EXEC sys.sp_executesql @stmt = @sql_select, + @params = @sp_params, + @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; -/* -These are the gathered metrics we get from query store to generate some warnings and help you find your worst offenders -*/ -DROP TABLE IF EXISTS #working_metrics; -CREATE TABLE #working_metrics -( - database_name NVARCHAR(258), - plan_id BIGINT, - query_id BIGINT, - query_id_all_plan_ids VARCHAR(8000), - /*these columns are from query_store_query*/ - proc_or_function_name NVARCHAR(258), - batch_sql_handle VARBINARY(64), - query_hash BINARY(8), - query_parameterization_type_desc NVARCHAR(258), - parameter_sniffing_symptoms NVARCHAR(4000), - count_compiles BIGINT, - avg_compile_duration DECIMAL(38,2), - last_compile_duration DECIMAL(38,2), - avg_bind_duration DECIMAL(38,2), - last_bind_duration DECIMAL(38,2), - avg_bind_cpu_time DECIMAL(38,2), - last_bind_cpu_time DECIMAL(38,2), - avg_optimize_duration DECIMAL(38,2), - last_optimize_duration DECIMAL(38,2), - avg_optimize_cpu_time DECIMAL(38,2), - last_optimize_cpu_time DECIMAL(38,2), - avg_compile_memory_kb DECIMAL(38,2), - last_compile_memory_kb DECIMAL(38,2), - /*These come from query_store_runtime_stats*/ - execution_type_desc NVARCHAR(128), - first_execution_time DATETIME2, - last_execution_time DATETIME2, - count_executions BIGINT, - avg_duration DECIMAL(38,2) , - last_duration DECIMAL(38,2), - min_duration DECIMAL(38,2), - max_duration DECIMAL(38,2), - avg_cpu_time DECIMAL(38,2), - last_cpu_time DECIMAL(38,2), - min_cpu_time DECIMAL(38,2), - max_cpu_time DECIMAL(38,2), - avg_logical_io_reads DECIMAL(38,2), - last_logical_io_reads DECIMAL(38,2), - min_logical_io_reads DECIMAL(38,2), - max_logical_io_reads DECIMAL(38,2), - avg_logical_io_writes DECIMAL(38,2), - last_logical_io_writes DECIMAL(38,2), - min_logical_io_writes DECIMAL(38,2), - max_logical_io_writes DECIMAL(38,2), - avg_physical_io_reads DECIMAL(38,2), - last_physical_io_reads DECIMAL(38,2), - min_physical_io_reads DECIMAL(38,2), - max_physical_io_reads DECIMAL(38,2), - avg_clr_time DECIMAL(38,2), - last_clr_time DECIMAL(38,2), - min_clr_time DECIMAL(38,2), - max_clr_time DECIMAL(38,2), - avg_dop BIGINT, - last_dop BIGINT, - min_dop BIGINT, - max_dop BIGINT, - avg_query_max_used_memory DECIMAL(38,2), - last_query_max_used_memory DECIMAL(38,2), - min_query_max_used_memory DECIMAL(38,2), - max_query_max_used_memory DECIMAL(38,2), - avg_rowcount DECIMAL(38,2), - last_rowcount DECIMAL(38,2), - min_rowcount DECIMAL(38,2), - max_rowcount DECIMAL(38,2), - /*These are 2017 only, AFAIK*/ - avg_num_physical_io_reads DECIMAL(38,2), - last_num_physical_io_reads DECIMAL(38,2), - min_num_physical_io_reads DECIMAL(38,2), - max_num_physical_io_reads DECIMAL(38,2), - avg_log_bytes_used DECIMAL(38,2), - last_log_bytes_used DECIMAL(38,2), - min_log_bytes_used DECIMAL(38,2), - max_log_bytes_used DECIMAL(38,2), - avg_tempdb_space_used DECIMAL(38,2), - last_tempdb_space_used DECIMAL(38,2), - min_tempdb_space_used DECIMAL(38,2), - max_tempdb_space_used DECIMAL(38,2), - /*These are computed columns to make some stuff easier down the line*/ - total_compile_duration AS avg_compile_duration * count_compiles, - total_bind_duration AS avg_bind_duration * count_compiles, - total_bind_cpu_time AS avg_bind_cpu_time * count_compiles, - total_optimize_duration AS avg_optimize_duration * count_compiles, - total_optimize_cpu_time AS avg_optimize_cpu_time * count_compiles, - total_compile_memory_kb AS avg_compile_memory_kb * count_compiles, - total_duration AS avg_duration * count_executions, - total_cpu_time AS avg_cpu_time * count_executions, - total_logical_io_reads AS avg_logical_io_reads * count_executions, - total_logical_io_writes AS avg_logical_io_writes * count_executions, - total_physical_io_reads AS avg_physical_io_reads * count_executions, - total_clr_time AS avg_clr_time * count_executions, - total_query_max_used_memory AS avg_query_max_used_memory * count_executions, - total_rowcount AS avg_rowcount * count_executions, - total_num_physical_io_reads AS avg_num_physical_io_reads * count_executions, - total_log_bytes_used AS avg_log_bytes_used * count_executions, - total_tempdb_space_used AS avg_tempdb_space_used * count_executions, - xpm AS NULLIF(count_executions, 0) / NULLIF(DATEDIFF(MINUTE, first_execution_time, last_execution_time), 0), - percent_memory_grant_used AS CONVERT(MONEY, ISNULL(NULLIF(( max_query_max_used_memory * 1.00 ), 0) / NULLIF(min_query_max_used_memory, 0), 0) * 100.), - INDEX wm_ix_ids CLUSTERED (plan_id, query_id, query_hash) -); +/*Get highest row count plans*/ +RAISERROR(N'Gathering highest row count plans', 0, 1) WITH NOWAIT; -/* -This is where we store some additional metrics, along with the query plan and text -*/ -DROP TABLE IF EXISTS #working_plan_text; +SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; +SET @sql_select += N' +WITH rowcount_max +AS ( SELECT TOP 1 + gi.start_range, + gi.end_range + FROM #grouped_interval AS gi + ORDER BY gi.total_rowcount DESC ) +INSERT #working_plans WITH (TABLOCK) + ( plan_id, query_id, pattern ) +SELECT TOP ( @sp_Top ) + qsp.plan_id, qsp.query_id, ''avg rows'' +FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp +JOIN rowcount_max AS dm +ON qsp.last_execution_time >= dm.start_range + AND qsp.last_execution_time < dm.end_range +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs +ON qsrs.plan_id = qsp.plan_id +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq +ON qsq.query_id = qsp.query_id +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt +ON qsqt.query_text_id = qsq.query_text_id +WHERE 1 = 1 + AND qsq.is_internal_query = 0 + AND qsp.query_plan IS NOT NULL + '; -CREATE TABLE #working_plan_text -( - database_name NVARCHAR(258), - plan_id BIGINT, - query_id BIGINT, - /*These are from query_store_plan*/ - plan_group_id BIGINT, - engine_version NVARCHAR(64), - compatibility_level INT, - query_plan_hash BINARY(8), - query_plan_xml XML, - is_online_index_plan BIT, - is_trivial_plan BIT, - is_parallel_plan BIT, - is_forced_plan BIT, - is_natively_compiled BIT, - force_failure_count BIGINT, - last_force_failure_reason_desc NVARCHAR(258), - count_compiles BIGINT, - initial_compile_start_time DATETIME2, - last_compile_start_time DATETIME2, - last_execution_time DATETIME2, - avg_compile_duration DECIMAL(38,2), - last_compile_duration BIGINT, - /*These are from query_store_query*/ - query_sql_text NVARCHAR(MAX), - statement_sql_handle VARBINARY(64), - is_part_of_encrypted_module BIT, - has_restricted_text BIT, - /*This is from query_context_settings*/ - context_settings NVARCHAR(512), - /*This is from #working_plans*/ - pattern NVARCHAR(512), - top_three_waits NVARCHAR(MAX), - INDEX wpt_ix_ids CLUSTERED (plan_id, query_id, query_plan_hash) -); +SET @sql_select += @sql_where; +SET @sql_select += N'ORDER BY qsrs.avg_rowcount DESC + OPTION (RECOMPILE); + '; -/* -This is where we store warnings that we generate from the XML and metrics -*/ -DROP TABLE IF EXISTS #working_warnings; +IF @Debug = 1 + PRINT @sql_select; -CREATE TABLE #working_warnings -( - plan_id BIGINT, - query_id BIGINT, - query_hash BINARY(8), - sql_handle VARBINARY(64), - proc_or_function_name NVARCHAR(258), - plan_multiple_plans BIT, - is_forced_plan BIT, - is_forced_parameterized BIT, - is_cursor BIT, - is_optimistic_cursor BIT, - is_forward_only_cursor BIT, - is_fast_forward_cursor BIT, - is_cursor_dynamic BIT, - is_parallel BIT, - is_forced_serial BIT, - is_key_lookup_expensive BIT, - key_lookup_cost FLOAT, - is_remote_query_expensive BIT, - remote_query_cost FLOAT, - frequent_execution BIT, - parameter_sniffing BIT, - unparameterized_query BIT, - near_parallel BIT, - plan_warnings BIT, - long_running BIT, - downlevel_estimator BIT, - implicit_conversions BIT, - tvf_estimate BIT, - compile_timeout BIT, - compile_memory_limit_exceeded BIT, - warning_no_join_predicate BIT, - query_cost FLOAT, - missing_index_count INT, - unmatched_index_count INT, - is_trivial BIT, - trace_flags_session NVARCHAR(1000), - is_unused_grant BIT, - function_count INT, - clr_function_count INT, - is_table_variable BIT, - no_stats_warning BIT, - relop_warnings BIT, - is_table_scan BIT, - backwards_scan BIT, - forced_index BIT, - forced_seek BIT, - forced_scan BIT, - columnstore_row_mode BIT, - is_computed_scalar BIT , - is_sort_expensive BIT, - sort_cost FLOAT, - is_computed_filter BIT, - op_name NVARCHAR(100) NULL, - index_insert_count INT NULL, - index_update_count INT NULL, - index_delete_count INT NULL, - cx_insert_count INT NULL, - cx_update_count INT NULL, - cx_delete_count INT NULL, - table_insert_count INT NULL, - table_update_count INT NULL, - table_delete_count INT NULL, - index_ops AS (index_insert_count + index_update_count + index_delete_count + - cx_insert_count + cx_update_count + cx_delete_count + - table_insert_count + table_update_count + table_delete_count), - is_row_level BIT, - is_spatial BIT, - index_dml BIT, - table_dml BIT, - long_running_low_cpu BIT, - low_cost_high_cpu BIT, - stale_stats BIT, - is_adaptive BIT, - is_slow_plan BIT, - is_compile_more BIT, - index_spool_cost FLOAT, - index_spool_rows FLOAT, - is_spool_expensive BIT, - is_spool_more_rows BIT, - estimated_rows FLOAT, - is_bad_estimate BIT, - is_big_log BIT, - is_big_tempdb BIT, - is_paul_white_electric BIT, - is_row_goal BIT, - is_mstvf BIT, - is_mm_join BIT, - is_nonsargable BIT, - busy_loops BIT, - tvf_join BIT, - implicit_conversion_info XML, - cached_execution_parameters XML, - missing_indexes XML, - warnings NVARCHAR(4000) - INDEX ww_ix_ids CLUSTERED (plan_id, query_id, query_hash, sql_handle) -); +IF @sql_select IS NULL + BEGIN + RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; + RETURN; + END; +EXEC sys.sp_executesql @stmt = @sql_select, + @params = @sp_params, + @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; -DROP TABLE IF EXISTS #working_wait_stats; -CREATE TABLE #working_wait_stats -( - plan_id BIGINT, - wait_category TINYINT, - wait_category_desc NVARCHAR(258), - total_query_wait_time_ms BIGINT, - avg_query_wait_time_ms DECIMAL(38, 2), - last_query_wait_time_ms BIGINT, - min_query_wait_time_ms BIGINT, - max_query_wait_time_ms BIGINT, - wait_category_mapped AS CASE wait_category - WHEN 0 THEN N'UNKNOWN' - WHEN 1 THEN N'SOS_SCHEDULER_YIELD' - WHEN 2 THEN N'THREADPOOL' - WHEN 3 THEN N'LCK_M_%' - WHEN 4 THEN N'LATCH_%' - WHEN 5 THEN N'PAGELATCH_%' - WHEN 6 THEN N'PAGEIOLATCH_%' - WHEN 7 THEN N'RESOURCE_SEMAPHORE_QUERY_COMPILE' - WHEN 8 THEN N'CLR%, SQLCLR%' - WHEN 9 THEN N'DBMIRROR%' - WHEN 10 THEN N'XACT%, DTC%, TRAN_MARKLATCH_%, MSQL_XACT_%, TRANSACTION_MUTEX' - WHEN 11 THEN N'SLEEP_%, LAZYWRITER_SLEEP, SQLTRACE_BUFFER_FLUSH, SQLTRACE_INCREMENTAL_FLUSH_SLEEP, SQLTRACE_WAIT_ENTRIES, FT_IFTS_SCHEDULER_IDLE_WAIT, XE_DISPATCHER_WAIT, REQUEST_FOR_DEADLOCK_SEARCH, LOGMGR_QUEUE, ONDEMAND_TASK_QUEUE, CHECKPOINT_QUEUE, XE_TIMER_EVENT' - WHEN 12 THEN N'PREEMPTIVE_%' - WHEN 13 THEN N'BROKER_% (but not BROKER_RECEIVE_WAITFOR)' - WHEN 14 THEN N'LOGMGR, LOGBUFFER, LOGMGR_RESERVE_APPEND, LOGMGR_FLUSH, LOGMGR_PMM_LOG, CHKPT, WRITELOG' - WHEN 15 THEN N'ASYNC_NETWORK_IO, NET_WAITFOR_PACKET, PROXY_NETWORK_IO, EXTERNAL_SCRIPT_NETWORK_IOF' - WHEN 16 THEN N'CXPACKET, EXCHANGE, CXCONSUMER' - WHEN 17 THEN N'RESOURCE_SEMAPHORE, CMEMTHREAD, CMEMPARTITIONED, EE_PMOLOCK, MEMORY_ALLOCATION_EXT, RESERVED_MEMORY_ALLOCATION_EXT, MEMORY_GRANT_UPDATE' - WHEN 18 THEN N'WAITFOR, WAIT_FOR_RESULTS, BROKER_RECEIVE_WAITFOR' - WHEN 19 THEN N'TRACEWRITE, SQLTRACE_LOCK, SQLTRACE_FILE_BUFFER, SQLTRACE_FILE_WRITE_IO_COMPLETION, SQLTRACE_FILE_READ_IO_COMPLETION, SQLTRACE_PENDING_BUFFER_WRITERS, SQLTRACE_SHUTDOWN, QUERY_TRACEOUT, TRACE_EVTNOTIFF' - WHEN 20 THEN N'FT_RESTART_CRAWL, FULLTEXT GATHERER, MSSEARCH, FT_METADATA_MUTEX, FT_IFTSHC_MUTEX, FT_IFTSISM_MUTEX, FT_IFTS_RWLOCK, FT_COMPROWSET_RWLOCK, FT_MASTER_MERGE, FT_PROPERTYLIST_CACHE, FT_MASTER_MERGE_COORDINATOR, PWAIT_RESOURCE_SEMAPHORE_FT_PARALLEL_QUERY_SYNC' - WHEN 21 THEN N'ASYNC_IO_COMPLETION, IO_COMPLETION, BACKUPIO, WRITE_COMPLETION, IO_QUEUE_LIMIT, IO_RETRY' - WHEN 22 THEN N'SE_REPL_%, REPL_%, HADR_% (but not HADR_THROTTLE_LOG_RATE_GOVERNOR), PWAIT_HADR_%, REPLICA_WRITES, FCB_REPLICA_WRITE, FCB_REPLICA_READ, PWAIT_HADRSIM' - WHEN 23 THEN N'LOG_RATE_GOVERNOR, POOL_LOG_RATE_GOVERNOR, HADR_THROTTLE_LOG_RATE_GOVERNOR, INSTANCE_LOG_RATE_GOVERNOR' - END, - INDEX wws_ix_ids CLUSTERED ( plan_id) -); +IF @new_columns = 1 +BEGIN +RAISERROR(N'Gathering new 2017 new column info...', 0, 1) WITH NOWAIT; -/* -The next three tables hold plan XML parsed out to different degrees -*/ -DROP TABLE IF EXISTS #statements; - -CREATE TABLE #statements -( - plan_id BIGINT, - query_id BIGINT, - query_hash BINARY(8), - sql_handle VARBINARY(64), - statement XML, - is_cursor BIT - INDEX s_ix_ids CLUSTERED (plan_id, query_id, query_hash, sql_handle) -); - - -DROP TABLE IF EXISTS #query_plan; - -CREATE TABLE #query_plan -( - plan_id BIGINT, - query_id BIGINT, - query_hash BINARY(8), - sql_handle VARBINARY(64), - query_plan XML, - INDEX qp_ix_ids CLUSTERED (plan_id, query_id, query_hash, sql_handle) -); - - -DROP TABLE IF EXISTS #relop; - -CREATE TABLE #relop -( - plan_id BIGINT, - query_id BIGINT, - query_hash BINARY(8), - sql_handle VARBINARY(64), - relop XML, - INDEX ix_ids CLUSTERED (plan_id, query_id, query_hash, sql_handle) -); +/*Get highest log byte count plans*/ +RAISERROR(N'Gathering highest log byte use plans', 0, 1) WITH NOWAIT; -DROP TABLE IF EXISTS #plan_cost; +SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; +SET @sql_select += N' +WITH rowcount_max +AS ( SELECT TOP 1 + gi.start_range, + gi.end_range + FROM #grouped_interval AS gi + ORDER BY gi.total_avg_log_bytes_mb DESC ) +INSERT #working_plans WITH (TABLOCK) + ( plan_id, query_id, pattern ) +SELECT TOP ( @sp_Top ) + qsp.plan_id, qsp.query_id, ''avg log bytes'' +FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp +JOIN rowcount_max AS dm +ON qsp.last_execution_time >= dm.start_range + AND qsp.last_execution_time < dm.end_range +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs +ON qsrs.plan_id = qsp.plan_id +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq +ON qsq.query_id = qsp.query_id +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt +ON qsqt.query_text_id = qsq.query_text_id +WHERE 1 = 1 + AND qsq.is_internal_query = 0 + AND qsp.query_plan IS NOT NULL + '; -CREATE TABLE #plan_cost -( - query_plan_cost DECIMAL(38,2), - sql_handle VARBINARY(64), - plan_id INT, - INDEX px_ix_ids CLUSTERED (sql_handle, plan_id) -); +SET @sql_select += @sql_where; +SET @sql_select += N'ORDER BY qsrs.avg_log_bytes_used DESC + OPTION (RECOMPILE); + '; -DROP TABLE IF EXISTS #est_rows; +IF @Debug = 1 + PRINT @sql_select; -CREATE TABLE #est_rows -( - estimated_rows DECIMAL(38,2), - query_hash BINARY(8), - INDEX px_ix_ids CLUSTERED (query_hash) -); +IF @sql_select IS NULL + BEGIN + RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; + RETURN; + END; +EXEC sys.sp_executesql @stmt = @sql_select, + @params = @sp_params, + @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; -DROP TABLE IF EXISTS #stats_agg; +RAISERROR(N'Gathering highest log byte use plans', 0, 1) WITH NOWAIT; -CREATE TABLE #stats_agg -( - sql_handle VARBINARY(64), - last_update DATETIME2, - modification_count BIGINT, - sampling_percent DECIMAL(38, 2), - [statistics] NVARCHAR(258), - [table] NVARCHAR(258), - [schema] NVARCHAR(258), - [database] NVARCHAR(258), - INDEX sa_ix_ids CLUSTERED (sql_handle) -); +SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; +SET @sql_select += N' +WITH rowcount_max +AS ( SELECT TOP 1 + gi.start_range, + gi.end_range + FROM #grouped_interval AS gi + ORDER BY gi.total_max_log_bytes_mb DESC ) +INSERT #working_plans WITH (TABLOCK) + ( plan_id, query_id, pattern ) +SELECT TOP ( @sp_Top ) + qsp.plan_id, qsp.query_id, ''max log bytes'' +FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp +JOIN rowcount_max AS dm +ON qsp.last_execution_time >= dm.start_range + AND qsp.last_execution_time < dm.end_range +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs +ON qsrs.plan_id = qsp.plan_id +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq +ON qsq.query_id = qsp.query_id +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt +ON qsqt.query_text_id = qsq.query_text_id +WHERE 1 = 1 + AND qsq.is_internal_query = 0 + AND qsp.query_plan IS NOT NULL + '; +SET @sql_select += @sql_where; -DROP TABLE IF EXISTS #trace_flags; +SET @sql_select += N'ORDER BY qsrs.max_log_bytes_used DESC + OPTION (RECOMPILE); + '; -CREATE TABLE #trace_flags -( - sql_handle VARBINARY(54), - global_trace_flags NVARCHAR(4000), - session_trace_flags NVARCHAR(4000), - INDEX tf_ix_ids CLUSTERED (sql_handle) -); +IF @Debug = 1 + PRINT @sql_select; +IF @sql_select IS NULL + BEGIN + RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; + RETURN; + END; -DROP TABLE IF EXISTS #warning_results; +EXEC sys.sp_executesql @stmt = @sql_select, + @params = @sp_params, + @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; -CREATE TABLE #warning_results -( - ID INT IDENTITY(1,1) PRIMARY KEY CLUSTERED, - CheckID INT, - Priority TINYINT, - FindingsGroup NVARCHAR(50), - Finding NVARCHAR(200), - URL NVARCHAR(200), - Details NVARCHAR(4000) -); -/*These next three tables hold information about implicit conversion and cached parameters */ -DROP TABLE IF EXISTS #stored_proc_info; +/*Get highest tempdb use plans*/ -CREATE TABLE #stored_proc_info -( - sql_handle VARBINARY(64), - query_hash BINARY(8), - variable_name NVARCHAR(258), - variable_datatype NVARCHAR(258), - converted_column_name NVARCHAR(258), - compile_time_value NVARCHAR(258), - proc_name NVARCHAR(1000), - column_name NVARCHAR(4000), - converted_to NVARCHAR(258), - set_options NVARCHAR(1000) - INDEX tf_ix_ids CLUSTERED (sql_handle, query_hash) -); +RAISERROR(N'Gathering highest tempdb use plans', 0, 1) WITH NOWAIT; -DROP TABLE IF EXISTS #variable_info; +SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; +SET @sql_select += N' +WITH rowcount_max +AS ( SELECT TOP 1 + gi.start_range, + gi.end_range + FROM #grouped_interval AS gi + ORDER BY gi.total_avg_tempdb_space DESC ) +INSERT #working_plans WITH (TABLOCK) + ( plan_id, query_id, pattern ) +SELECT TOP ( @sp_Top ) + qsp.plan_id, qsp.query_id, ''avg tempdb space'' +FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp +JOIN rowcount_max AS dm +ON qsp.last_execution_time >= dm.start_range + AND qsp.last_execution_time < dm.end_range +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs +ON qsrs.plan_id = qsp.plan_id +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq +ON qsq.query_id = qsp.query_id +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt +ON qsqt.query_text_id = qsq.query_text_id +WHERE 1 = 1 + AND qsq.is_internal_query = 0 + AND qsp.query_plan IS NOT NULL + '; -CREATE TABLE #variable_info -( - query_hash BINARY(8), - sql_handle VARBINARY(64), - proc_name NVARCHAR(1000), - variable_name NVARCHAR(258), - variable_datatype NVARCHAR(258), - compile_time_value NVARCHAR(258), - INDEX vif_ix_ids CLUSTERED (sql_handle, query_hash) -); +SET @sql_select += @sql_where; -DROP TABLE IF EXISTS #conversion_info; +SET @sql_select += N'ORDER BY qsrs.avg_tempdb_space_used DESC + OPTION (RECOMPILE); + '; -CREATE TABLE #conversion_info -( - query_hash BINARY(8), - sql_handle VARBINARY(64), - proc_name NVARCHAR(128), - expression NVARCHAR(4000), - at_charindex AS CHARINDEX('@', expression), - bracket_charindex AS CHARINDEX(']', expression, CHARINDEX('@', expression)) - CHARINDEX('@', expression), - comma_charindex AS CHARINDEX(',', expression) + 1, - second_comma_charindex AS - CHARINDEX(',', expression, CHARINDEX(',', expression) + 1) - CHARINDEX(',', expression) - 1, - equal_charindex AS CHARINDEX('=', expression) + 1, - paren_charindex AS CHARINDEX('(', expression) + 1, - comma_paren_charindex AS - CHARINDEX(',', expression, CHARINDEX('(', expression) + 1) - CHARINDEX('(', expression) - 1, - convert_implicit_charindex AS CHARINDEX('=CONVERT_IMPLICIT', expression), - INDEX cif_ix_ids CLUSTERED (sql_handle, query_hash) -); +IF @Debug = 1 + PRINT @sql_select; -/* These tables support the Missing Index details clickable*/ +IF @sql_select IS NULL + BEGIN + RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; + RETURN; + END; +EXEC sys.sp_executesql @stmt = @sql_select, + @params = @sp_params, + @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; -DROP TABLE IF EXISTS #missing_index_xml; +SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; +SET @sql_select += N' +WITH rowcount_max +AS ( SELECT TOP 1 + gi.start_range, + gi.end_range + FROM #grouped_interval AS gi + ORDER BY gi.total_max_tempdb_space DESC ) +INSERT #working_plans WITH (TABLOCK) + ( plan_id, query_id, pattern ) +SELECT TOP ( @sp_Top ) + qsp.plan_id, qsp.query_id, ''max tempdb space'' +FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp +JOIN rowcount_max AS dm +ON qsp.last_execution_time >= dm.start_range + AND qsp.last_execution_time < dm.end_range +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs +ON qsrs.plan_id = qsp.plan_id +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq +ON qsq.query_id = qsp.query_id +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt +ON qsqt.query_text_id = qsq.query_text_id +WHERE 1 = 1 + AND qsq.is_internal_query = 0 + AND qsp.query_plan IS NOT NULL + '; -CREATE TABLE #missing_index_xml -( - query_hash BINARY(8), - sql_handle VARBINARY(64), - impact FLOAT, - index_xml XML, - INDEX mix_ix_ids CLUSTERED (sql_handle, query_hash) -); +SET @sql_select += @sql_where; -DROP TABLE IF EXISTS #missing_index_schema; +SET @sql_select += N'ORDER BY qsrs.max_tempdb_space_used DESC + OPTION (RECOMPILE); + '; -CREATE TABLE #missing_index_schema -( - query_hash BINARY(8), - sql_handle VARBINARY(64), - impact FLOAT, - database_name NVARCHAR(128), - schema_name NVARCHAR(128), - table_name NVARCHAR(128), - index_xml XML, - INDEX mis_ix_ids CLUSTERED (sql_handle, query_hash) -); +IF @Debug = 1 + PRINT @sql_select; +IF @sql_select IS NULL + BEGIN + RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; + RETURN; + END; -DROP TABLE IF EXISTS #missing_index_usage; +EXEC sys.sp_executesql @stmt = @sql_select, + @params = @sp_params, + @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; -CREATE TABLE #missing_index_usage -( - query_hash BINARY(8), - sql_handle VARBINARY(64), - impact FLOAT, - database_name NVARCHAR(128), - schema_name NVARCHAR(128), - table_name NVARCHAR(128), - usage NVARCHAR(128), - index_xml XML, - INDEX miu_ix_ids CLUSTERED (sql_handle, query_hash) -); -DROP TABLE IF EXISTS #missing_index_detail; +END; -CREATE TABLE #missing_index_detail -( - query_hash BINARY(8), - sql_handle VARBINARY(64), - impact FLOAT, - database_name NVARCHAR(128), - schema_name NVARCHAR(128), - table_name NVARCHAR(128), - usage NVARCHAR(128), - column_name NVARCHAR(128), - INDEX mid_ix_ids CLUSTERED (sql_handle, query_hash) -); +/* +This rolls up the different patterns we find before deduplicating. -DROP TABLE IF EXISTS #missing_index_pretty; +The point of this is so we know if a query was gathered by one or more of the search queries -CREATE TABLE #missing_index_pretty -( - query_hash BINARY(8), - sql_handle VARBINARY(64), - impact FLOAT, - database_name NVARCHAR(128), - schema_name NVARCHAR(128), - table_name NVARCHAR(128), - equality NVARCHAR(MAX), - inequality NVARCHAR(MAX), - [include] NVARCHAR(MAX), - is_spool BIT, - details AS N'/* ' - + CHAR(10) - + CASE is_spool - WHEN 0 - THEN N'The Query Processor estimates that implementing the ' - ELSE N'We estimate that implementing the ' - END - + CONVERT(NVARCHAR(30), impact) - + '%.' - + CHAR(10) - + N'*/' - + CHAR(10) + CHAR(13) - + N'/* ' - + CHAR(10) - + N'USE ' - + database_name - + CHAR(10) - + N'GO' - + CHAR(10) + CHAR(13) - + N'CREATE NONCLUSTERED INDEX ix_' - + ISNULL(REPLACE(REPLACE(REPLACE(equality,'[', ''), ']', ''), ', ', '_'), '') - + ISNULL(REPLACE(REPLACE(REPLACE(inequality,'[', ''), ']', ''), ', ', '_'), '') - + CASE WHEN [include] IS NOT NULL THEN + N'_Includes' ELSE N'' END - + CHAR(10) - + N' ON ' - + schema_name - + N'.' - + table_name - + N' (' + - + CASE WHEN equality IS NOT NULL - THEN equality - + CASE WHEN inequality IS NOT NULL - THEN N', ' + inequality - ELSE N'' - END - ELSE inequality - END - + N')' - + CHAR(10) - + CASE WHEN include IS NOT NULL - THEN N'INCLUDE (' + include + N') WITH (FILLFACTOR=100, ONLINE=?, SORT_IN_TEMPDB=?, DATA_COMPRESSION=?);' - ELSE N' WITH (FILLFACTOR=100, ONLINE=?, SORT_IN_TEMPDB=?, DATA_COMPRESSION=?);' - END - + CHAR(10) - + N'GO' - + CHAR(10) - + N'*/', - INDEX mip_ix_ids CLUSTERED (sql_handle, query_hash) -); +*/ -DROP TABLE IF EXISTS #index_spool_ugly; +RAISERROR(N'Updating patterns', 0, 1) WITH NOWAIT; -CREATE TABLE #index_spool_ugly -( - query_hash BINARY(8), - sql_handle VARBINARY(64), - impact FLOAT, - database_name NVARCHAR(128), - schema_name NVARCHAR(128), - table_name NVARCHAR(128), - equality NVARCHAR(MAX), - inequality NVARCHAR(MAX), - [include] NVARCHAR(MAX), - INDEX isu_ix_ids CLUSTERED (sql_handle, query_hash) -); +WITH patterns AS ( +SELECT wp.plan_id, wp.query_id, + pattern_path = STUFF((SELECT DISTINCT N', ' + wp2.pattern + FROM #working_plans AS wp2 + WHERE wp.plan_id = wp2.plan_id + AND wp.query_id = wp2.query_id + FOR XML PATH(N''), TYPE).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 2, N'') +FROM #working_plans AS wp +) +UPDATE wp +SET wp.pattern = patterns.pattern_path +FROM #working_plans AS wp +JOIN patterns +ON wp.plan_id = patterns.plan_id +AND wp.query_id = patterns.query_id +OPTION (RECOMPILE); -/*Sets up WHERE clause that gets used quite a bit*/ +/* +This dedupes our results so we hopefully don't double-work the same plan +*/ ---Date stuff ---If they're both NULL, we'll just look at the last 7 days -IF (@StartDate IS NULL AND @EndDate IS NULL) - BEGIN - RAISERROR(N'@StartDate and @EndDate are NULL, checking last 7 days', 0, 1) WITH NOWAIT; - SET @sql_where += N' AND qsrs.last_execution_time >= DATEADD(DAY, -7, DATEDIFF(DAY, 0, SYSDATETIME() )) - '; - END; +RAISERROR(N'Deduplicating gathered plans', 0, 1) WITH NOWAIT; ---Hey, that's nice of me -IF @StartDate IS NOT NULL - BEGIN - RAISERROR(N'Setting start date filter', 0, 1) WITH NOWAIT; - SET @sql_where += N' AND qsrs.last_execution_time >= @sp_StartDate - '; - END; +WITH dedupe AS ( +SELECT * , ROW_NUMBER() OVER (PARTITION BY wp.plan_id ORDER BY wp.plan_id) AS dupes +FROM #working_plans AS wp +) +DELETE dedupe +WHERE dedupe.dupes > 1 +OPTION (RECOMPILE); ---Alright, sensible -IF @EndDate IS NOT NULL - BEGIN - RAISERROR(N'Setting end date filter', 0, 1) WITH NOWAIT; - SET @sql_where += N' AND qsrs.last_execution_time < @sp_EndDate - '; - END; +SET @msg = N'Removed ' + CONVERT(NVARCHAR(10), @@ROWCOUNT) + N' duplicate plan_ids.'; +RAISERROR(@msg, 0, 1) WITH NOWAIT; ---C'mon, why would you do that? -IF (@StartDate IS NULL AND @EndDate IS NOT NULL) - BEGIN - RAISERROR(N'Setting reasonable start date filter', 0, 1) WITH NOWAIT; - SET @sql_where += N' AND qsrs.last_execution_time >= DATEADD(DAY, -7, @sp_EndDate) - '; - END; ---Jeez, abusive -IF (@StartDate IS NOT NULL AND @EndDate IS NULL) - BEGIN - RAISERROR(N'Setting reasonable end date filter', 0, 1) WITH NOWAIT; - SET @sql_where += N' AND qsrs.last_execution_time < DATEADD(DAY, 7, @sp_StartDate) - '; - END; +/* +This gathers data for the #working_metrics table +*/ ---I care about minimum execution counts -IF @MinimumExecutionCount IS NOT NULL - BEGIN - RAISERROR(N'Setting execution filter', 0, 1) WITH NOWAIT; - SET @sql_where += N' AND qsrs.count_executions >= @sp_MinimumExecutionCount - '; - END; ---You care about stored proc names -IF @StoredProcName IS NOT NULL - BEGIN - RAISERROR(N'Setting stored proc filter', 0, 1) WITH NOWAIT; - SET @sql_where += N' AND object_name(qsq.object_id, DB_ID(' + QUOTENAME(@DatabaseName, '''') + N')) = @sp_StoredProcName - '; - END; - ---I will always love you, but hopefully this query will eventually end -IF @DurationFilter IS NOT NULL - BEGIN - RAISERROR(N'Setting duration filter', 0, 1) WITH NOWAIT; - SET @sql_where += N' AND (qsrs.avg_duration / 1000.) >= @sp_MinDuration - '; - END; - ---I don't know why you'd go looking for failed queries, but hey -IF (@Failed = 0 OR @Failed IS NULL) - BEGIN - RAISERROR(N'Setting failed query filter to 0', 0, 1) WITH NOWAIT; - SET @sql_where += N' AND qsrs.execution_type = 0 - '; - END; -IF (@Failed = 1) - BEGIN - RAISERROR(N'Setting failed query filter to 3, 4', 0, 1) WITH NOWAIT; - SET @sql_where += N' AND qsrs.execution_type IN (3, 4) - '; - END; - -/*Filtering for plan_id or query_id*/ -IF (@PlanIdFilter IS NOT NULL) - BEGIN - RAISERROR(N'Setting plan_id filter', 0, 1) WITH NOWAIT; - SET @sql_where += N' AND qsp.plan_id = @sp_PlanIdFilter - '; - END; - -IF (@QueryIdFilter IS NOT NULL) - BEGIN - RAISERROR(N'Setting query_id filter', 0, 1) WITH NOWAIT; - SET @sql_where += N' AND qsq.query_id = @sp_QueryIdFilter - '; - END; - -IF @Debug = 1 - RAISERROR(N'Starting WHERE clause:', 0, 1) WITH NOWAIT; - PRINT @sql_where; - -IF @sql_where IS NULL - BEGIN - RAISERROR(N'@sql_where is NULL', 0, 1) WITH NOWAIT; - RETURN; - END; - -IF (@ExportToExcel = 1 OR @SkipXML = 1) - BEGIN - RAISERROR(N'Exporting to Excel or skipping XML, hiding summary', 0, 1) WITH NOWAIT; - SET @HideSummary = 1; - END; - -IF @StoredProcName IS NOT NULL - BEGIN - - DECLARE @sql NVARCHAR(MAX); - DECLARE @out INT; - DECLARE @proc_params NVARCHAR(MAX) = N'@sp_StartDate DATETIME2, @sp_EndDate DATETIME2, @sp_MinimumExecutionCount INT, @sp_MinDuration INT, @sp_StoredProcName NVARCHAR(128), @sp_PlanIdFilter INT, @sp_QueryIdFilter INT, @i_out INT OUTPUT'; - - - SET @sql = N'SELECT @i_out = COUNT(*) - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp - ON qsp.plan_id = qsrs.plan_id - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq - ON qsq.query_id = qsp.query_id - WHERE 1 = 1 - AND qsq.is_internal_query = 0 - AND qsp.query_plan IS NOT NULL - '; - - SET @sql += @sql_where; - - EXEC sys.sp_executesql @sql, - @proc_params, - @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter, @i_out = @out OUTPUT; - - IF @out = 0 - BEGIN - - SET @msg = N'We couldn''t find the Stored Procedure ' + QUOTENAME(@StoredProcName) + N' in the Query Store views for ' + QUOTENAME(@DatabaseName) + N' between ' + CONVERT(NVARCHAR(30), ISNULL(@StartDate, DATEADD(DAY, -7, DATEDIFF(DAY, 0, SYSDATETIME() ))) ) + N' and ' + CONVERT(NVARCHAR(30), ISNULL(@EndDate, SYSDATETIME())) + - '. Try removing schema prefixes or adjusting dates. If it was executed from a different database context, try searching there instead.'; - RAISERROR(@msg, 0, 1) WITH NOWAIT; - - SELECT @msg AS [Blue Flowers, Blue Flowers, Blue Flowers]; - - RETURN; - - END; - - END; - - - - -/* -This is our grouped interval query. - -By default, it looks at queries: - In the last 7 days - That aren't system queries - That have a query plan (some won't, if nested level is > 128, along with other reasons) - And haven't failed - This stuff, along with some other options, will be configurable in the stored proc - -*/ - -IF @sql_where IS NOT NULL -BEGIN TRY - BEGIN - - RAISERROR(N'Populating temp tables', 0, 1) WITH NOWAIT; - -RAISERROR(N'Gathering intervals', 0, 1) WITH NOWAIT; +RAISERROR(N'Collecting worker metrics', 0, 1) WITH NOWAIT; SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; SET @sql_select += N' -SELECT CONVERT(DATE, qsrs.last_execution_time) AS flat_date, - MIN(DATEADD(HOUR, DATEDIFF(HOUR, 0, qsrs.last_execution_time), 0)) AS start_range, - MAX(DATEADD(HOUR, DATEDIFF(HOUR, 0, qsrs.last_execution_time) + 1, 0)) AS end_range, - SUM(qsrs.avg_duration / 1000.) / SUM(qsrs.count_executions) AS total_avg_duration_ms, - SUM(qsrs.avg_cpu_time / 1000.) / SUM(qsrs.count_executions) AS total_avg_cpu_time_ms, - SUM((qsrs.avg_logical_io_reads * 8 ) / 1024.) / SUM(qsrs.count_executions) AS total_avg_logical_io_reads_mb, - SUM((qsrs.avg_physical_io_reads* 8 ) / 1024.) / SUM(qsrs.count_executions) AS total_avg_physical_io_reads_mb, - SUM((qsrs.avg_logical_io_writes* 8 ) / 1024.) / SUM(qsrs.count_executions) AS total_avg_logical_io_writes_mb, - SUM((qsrs.avg_query_max_used_memory * 8 ) / 1024.) / SUM(qsrs.count_executions) AS total_avg_query_max_used_memory_mb, - SUM(qsrs.avg_rowcount) AS total_rowcount, - SUM(qsrs.count_executions) AS total_count_executions, - SUM(qsrs.max_duration / 1000.) AS total_max_duration_ms, - SUM(qsrs.max_cpu_time / 1000.) AS total_max_cpu_time_ms, - SUM((qsrs.max_logical_io_reads * 8 ) / 1024.) AS total_max_logical_io_reads_mb, - SUM((qsrs.max_physical_io_reads* 8 ) / 1024.) AS total_max_physical_io_reads_mb, - SUM((qsrs.max_logical_io_writes* 8 ) / 1024.) AS total_max_logical_io_writes_mb, - SUM((qsrs.max_query_max_used_memory * 8 ) / 1024.) AS total_max_query_max_used_memory_mb '; - IF @new_columns = 1 +SELECT ' + QUOTENAME(@DatabaseName, '''') + N' AS database_name, wp.plan_id, wp.query_id, + QUOTENAME(object_schema_name(qsq.object_id, DB_ID(' + QUOTENAME(@DatabaseName, '''') + N'))) + ''.'' + + QUOTENAME(object_name(qsq.object_id, DB_ID(' + QUOTENAME(@DatabaseName, '''') + N'))) AS proc_or_function_name, + qsq.batch_sql_handle, qsq.query_hash, qsq.query_parameterization_type_desc, qsq.count_compiles, + (qsq.avg_compile_duration / 1000.), + (qsq.last_compile_duration / 1000.), + (qsq.avg_bind_duration / 1000.), + (qsq.last_bind_duration / 1000.), + (qsq.avg_bind_cpu_time / 1000.), + (qsq.last_bind_cpu_time / 1000.), + (qsq.avg_optimize_duration / 1000.), + (qsq.last_optimize_duration / 1000.), + (qsq.avg_optimize_cpu_time / 1000.), + (qsq.last_optimize_cpu_time / 1000.), + (qsq.avg_compile_memory_kb / 1024.), + (qsq.last_compile_memory_kb / 1024.), + qsrs.execution_type_desc, qsrs.first_execution_time, qsrs.last_execution_time, qsrs.count_executions, + (qsrs.avg_duration / 1000.), + (qsrs.last_duration / 1000.), + (qsrs.min_duration / 1000.), + (qsrs.max_duration / 1000.), + (qsrs.avg_cpu_time / 1000.), + (qsrs.last_cpu_time / 1000.), + (qsrs.min_cpu_time / 1000.), + (qsrs.max_cpu_time / 1000.), + ((qsrs.avg_logical_io_reads * 8 ) / 1024.), + ((qsrs.last_logical_io_reads * 8 ) / 1024.), + ((qsrs.min_logical_io_reads * 8 ) / 1024.), + ((qsrs.max_logical_io_reads * 8 ) / 1024.), + ((qsrs.avg_logical_io_writes * 8 ) / 1024.), + ((qsrs.last_logical_io_writes * 8 ) / 1024.), + ((qsrs.min_logical_io_writes * 8 ) / 1024.), + ((qsrs.max_logical_io_writes * 8 ) / 1024.), + ((qsrs.avg_physical_io_reads * 8 ) / 1024.), + ((qsrs.last_physical_io_reads * 8 ) / 1024.), + ((qsrs.min_physical_io_reads * 8 ) / 1024.), + ((qsrs.max_physical_io_reads * 8 ) / 1024.), + (qsrs.avg_clr_time / 1000.), + (qsrs.last_clr_time / 1000.), + (qsrs.min_clr_time / 1000.), + (qsrs.max_clr_time / 1000.), + qsrs.avg_dop, qsrs.last_dop, qsrs.min_dop, qsrs.max_dop, + ((qsrs.avg_query_max_used_memory * 8 ) / 1024.), + ((qsrs.last_query_max_used_memory * 8 ) / 1024.), + ((qsrs.min_query_max_used_memory * 8 ) / 1024.), + ((qsrs.max_query_max_used_memory * 8 ) / 1024.), + qsrs.avg_rowcount, qsrs.last_rowcount, qsrs.min_rowcount, qsrs.max_rowcount,'; + + IF @new_columns = 1 BEGIN - SET @sql_select += N', - SUM((qsrs.avg_log_bytes_used) / 1048576.) / SUM(qsrs.count_executions) AS total_avg_log_bytes_mb, - SUM(qsrs.avg_tempdb_space_used) / SUM(qsrs.count_executions) AS total_avg_tempdb_space, - SUM((qsrs.max_log_bytes_used) / 1048576.) AS total_max_log_bytes_mb, - SUM(qsrs.max_tempdb_space_used) AS total_max_tempdb_space - '; - END; + SET @sql_select += N' + qsrs.avg_num_physical_io_reads, qsrs.last_num_physical_io_reads, qsrs.min_num_physical_io_reads, qsrs.max_num_physical_io_reads, + (qsrs.avg_log_bytes_used / 100000000), + (qsrs.last_log_bytes_used / 100000000), + (qsrs.min_log_bytes_used / 100000000), + (qsrs.max_log_bytes_used / 100000000), + ((qsrs.avg_tempdb_space_used * 8 ) / 1024.), + ((qsrs.last_tempdb_space_used * 8 ) / 1024.), + ((qsrs.min_tempdb_space_used * 8 ) / 1024.), + ((qsrs.max_tempdb_space_used * 8 ) / 1024.) + '; + END; IF @new_columns = 0 BEGIN - SET @sql_select += N', - NULL AS total_avg_log_bytes_mb, - NULL AS total_avg_tempdb_space, - NULL AS total_max_log_bytes_mb, - NULL AS total_max_tempdb_space - '; + SET @sql_select += N' + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL + '; END; - - -SET @sql_select += N'FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp - ON qsp.plan_id = qsrs.plan_id - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq - ON qsq.query_id = qsp.query_id - WHERE 1 = 1 - AND qsq.is_internal_query = 0 - AND qsp.query_plan IS NOT NULL - '; - +SET @sql_select += +N'FROM #working_plans AS wp +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq +ON wp.query_id = qsq.query_id +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs +ON qsrs.plan_id = wp.plan_id +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp +ON qsp.plan_id = wp.plan_id +AND qsp.query_id = wp.query_id +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt +ON qsqt.query_text_id = qsq.query_text_id +WHERE 1 = 1 + AND qsq.is_internal_query = 0 + AND qsp.query_plan IS NOT NULL + '; SET @sql_select += @sql_where; -SET @sql_select += - N'GROUP BY CONVERT(DATE, qsrs.last_execution_time) - OPTION (RECOMPILE); - '; +SET @sql_select += N'OPTION (RECOMPILE); + '; IF @Debug = 1 PRINT @sql_select; @@ -31476,111 +27852,87 @@ IF @sql_select IS NULL RETURN; END; -INSERT #grouped_interval WITH (TABLOCK) - ( flat_date, start_range, end_range, total_avg_duration_ms, - total_avg_cpu_time_ms, total_avg_logical_io_reads_mb, total_avg_physical_io_reads_mb, - total_avg_logical_io_writes_mb, total_avg_query_max_used_memory_mb, total_rowcount, - total_count_executions, total_max_duration_ms, total_max_cpu_time_ms, total_max_logical_io_reads_mb, - total_max_physical_io_reads_mb, total_max_logical_io_writes_mb, total_max_query_max_used_memory_mb, - total_avg_log_bytes_mb, total_avg_tempdb_space, total_max_log_bytes_mb, total_max_tempdb_space ) +INSERT #working_metrics WITH (TABLOCK) + ( database_name, plan_id, query_id, + proc_or_function_name, + batch_sql_handle, query_hash, query_parameterization_type_desc, count_compiles, + avg_compile_duration, last_compile_duration, avg_bind_duration, last_bind_duration, avg_bind_cpu_time, last_bind_cpu_time, avg_optimize_duration, + last_optimize_duration, avg_optimize_cpu_time, last_optimize_cpu_time, avg_compile_memory_kb, last_compile_memory_kb, execution_type_desc, + first_execution_time, last_execution_time, count_executions, avg_duration, last_duration, min_duration, max_duration, avg_cpu_time, last_cpu_time, + min_cpu_time, max_cpu_time, avg_logical_io_reads, last_logical_io_reads, min_logical_io_reads, max_logical_io_reads, avg_logical_io_writes, + last_logical_io_writes, min_logical_io_writes, max_logical_io_writes, avg_physical_io_reads, last_physical_io_reads, min_physical_io_reads, + max_physical_io_reads, avg_clr_time, last_clr_time, min_clr_time, max_clr_time, avg_dop, last_dop, min_dop, max_dop, avg_query_max_used_memory, + last_query_max_used_memory, min_query_max_used_memory, max_query_max_used_memory, avg_rowcount, last_rowcount, min_rowcount, max_rowcount, + /* 2017 only columns */ + avg_num_physical_io_reads, last_num_physical_io_reads, min_num_physical_io_reads, max_num_physical_io_reads, + avg_log_bytes_used, last_log_bytes_used, min_log_bytes_used, max_log_bytes_used, + avg_tempdb_space_used, last_tempdb_space_used, min_tempdb_space_used, max_tempdb_space_used ) EXEC sys.sp_executesql @stmt = @sql_select, @params = @sp_params, @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; -/* -The next group of queries looks at plans in the ranges we found in the grouped interval query - -We take the highest value from each metric (duration, cpu, etc) and find the top plans by that metric in the range - -They insert into the #working_plans table -*/ - - -/*Get longest duration plans*/ - -RAISERROR(N'Gathering longest duration plans', 0, 1) WITH NOWAIT; +/*This just helps us classify our queries*/ +UPDATE #working_metrics +SET proc_or_function_name = N'Statement' +WHERE proc_or_function_name IS NULL +OPTION(RECOMPILE); SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; SET @sql_select += N' -WITH duration_max -AS ( SELECT TOP 1 - gi.start_range, - gi.end_range - FROM #grouped_interval AS gi - ORDER BY gi.total_avg_duration_ms DESC ) -INSERT #working_plans WITH (TABLOCK) - ( plan_id, query_id, pattern ) -SELECT TOP ( @sp_Top ) - qsp.plan_id, qsp.query_id, ''avg duration'' -FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp -JOIN duration_max AS dm -ON qsp.last_execution_time >= dm.start_range - AND qsp.last_execution_time < dm.end_range -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs -ON qsrs.plan_id = qsp.plan_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq -ON qsq.query_id = qsp.query_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt -ON qsqt.query_text_id = qsq.query_text_id -WHERE 1 = 1 - AND qsq.is_internal_query = 0 - AND qsp.query_plan IS NOT NULL - '; - -SET @sql_select += @sql_where; - -SET @sql_select += N'ORDER BY qsrs.avg_duration DESC - OPTION (RECOMPILE); - '; + WITH patterns AS ( + SELECT query_id, planid_path = STUFF((SELECT DISTINCT N'', '' + RTRIM(qsp2.plan_id) + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp2 + WHERE qsp.query_id = qsp2.query_id + FOR XML PATH(N''''), TYPE).value(N''.[1]'', N''NVARCHAR(MAX)''), 1, 2, N'''') + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp + ) + UPDATE wm + SET wm.query_id_all_plan_ids = patterns.planid_path + FROM #working_metrics AS wm + JOIN patterns + ON wm.query_id = patterns.query_id + OPTION (RECOMPILE); +' -IF @Debug = 1 - PRINT @sql_select; +EXEC sys.sp_executesql @stmt = @sql_select; -IF @sql_select IS NULL - BEGIN - RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; - RETURN; - END; +/* +This gathers data for the #working_plan_text table +*/ -EXEC sys.sp_executesql @stmt = @sql_select, - @params = @sp_params, - @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; +RAISERROR(N'Gathering working plans', 0, 1) WITH NOWAIT; SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; SET @sql_select += N' -WITH duration_max -AS ( SELECT TOP 1 - gi.start_range, - gi.end_range - FROM #grouped_interval AS gi - ORDER BY gi.total_max_duration_ms DESC ) -INSERT #working_plans WITH (TABLOCK) - ( plan_id, query_id, pattern ) -SELECT TOP ( @sp_Top ) - qsp.plan_id, qsp.query_id, ''max duration'' -FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp -JOIN duration_max AS dm -ON qsp.last_execution_time >= dm.start_range - AND qsp.last_execution_time < dm.end_range -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs -ON qsrs.plan_id = qsp.plan_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq -ON qsq.query_id = qsp.query_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt +SELECT ' + QUOTENAME(@DatabaseName, '''') + N' AS database_name, wp.plan_id, wp.query_id, + qsp.plan_group_id, qsp.engine_version, qsp.compatibility_level, qsp.query_plan_hash, TRY_CONVERT(XML, qsp.query_plan), qsp.is_online_index_plan, qsp.is_trivial_plan, + qsp.is_parallel_plan, qsp.is_forced_plan, qsp.is_natively_compiled, qsp.force_failure_count, qsp.last_force_failure_reason_desc, qsp.count_compiles, + qsp.initial_compile_start_time, qsp.last_compile_start_time, qsp.last_execution_time, + (qsp.avg_compile_duration / 1000.), + (qsp.last_compile_duration / 1000.), + qsqt.query_sql_text, qsqt.statement_sql_handle, qsqt.is_part_of_encrypted_module, qsqt.has_restricted_text +FROM #working_plans AS wp +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp +ON qsp.plan_id = wp.plan_id + AND qsp.query_id = wp.query_id +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq +ON wp.query_id = qsq.query_id +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt ON qsqt.query_text_id = qsq.query_text_id +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs +ON qsrs.plan_id = wp.plan_id WHERE 1 = 1 - AND qsq.is_internal_query = 0 + AND qsq.is_internal_query = 0 AND qsp.query_plan IS NOT NULL '; SET @sql_select += @sql_where; -SET @sql_select += N'ORDER BY qsrs.max_duration DESC - OPTION (RECOMPILE); +SET @sql_select += N'OPTION (RECOMPILE); '; IF @Debug = 1 @@ -31592,47 +27944,44 @@ IF @sql_select IS NULL RETURN; END; +INSERT #working_plan_text WITH (TABLOCK) + ( database_name, plan_id, query_id, + plan_group_id, engine_version, compatibility_level, query_plan_hash, query_plan_xml, is_online_index_plan, is_trivial_plan, + is_parallel_plan, is_forced_plan, is_natively_compiled, force_failure_count, last_force_failure_reason_desc, count_compiles, + initial_compile_start_time, last_compile_start_time, last_execution_time, avg_compile_duration, last_compile_duration, + query_sql_text, statement_sql_handle, is_part_of_encrypted_module, has_restricted_text ) + EXEC sys.sp_executesql @stmt = @sql_select, @params = @sp_params, @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; -/*Get longest cpu plans*/ -RAISERROR(N'Gathering highest cpu plans', 0, 1) WITH NOWAIT; +/* +This gets us context settings for our queries and adds it to the #working_plan_text table +*/ + +RAISERROR(N'Gathering context settings', 0, 1) WITH NOWAIT; SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; SET @sql_select += N' -WITH cpu_max -AS ( SELECT TOP 1 - gi.start_range, - gi.end_range - FROM #grouped_interval AS gi - ORDER BY gi.total_avg_cpu_time_ms DESC ) -INSERT #working_plans WITH (TABLOCK) - ( plan_id, query_id, pattern ) -SELECT TOP ( @sp_Top ) - qsp.plan_id, qsp.query_id, ''avg cpu'' -FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp -JOIN cpu_max AS dm -ON qsp.last_execution_time >= dm.start_range - AND qsp.last_execution_time < dm.end_range -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs -ON qsrs.plan_id = qsp.plan_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq -ON qsq.query_id = qsp.query_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt -ON qsqt.query_text_id = qsq.query_text_id -WHERE 1 = 1 - AND qsq.is_internal_query = 0 - AND qsp.query_plan IS NOT NULL - '; - -SET @sql_select += @sql_where; - -SET @sql_select += N'ORDER BY qsrs.avg_cpu_time DESC - OPTION (RECOMPILE); - '; +UPDATE wp +SET wp.context_settings = SUBSTRING( + CASE WHEN (CAST(qcs.set_options AS INT) & 1 = 1) THEN '', ANSI_PADDING'' ELSE '''' END + + CASE WHEN (CAST(qcs.set_options AS INT) & 8 = 8) THEN '', CONCAT_NULL_YIELDS_NULL'' ELSE '''' END + + CASE WHEN (CAST(qcs.set_options AS INT) & 16 = 16) THEN '', ANSI_WARNINGS'' ELSE '''' END + + CASE WHEN (CAST(qcs.set_options AS INT) & 32 = 32) THEN '', ANSI_NULLS'' ELSE '''' END + + CASE WHEN (CAST(qcs.set_options AS INT) & 64 = 64) THEN '', QUOTED_IDENTIFIER'' ELSE '''' END + + CASE WHEN (CAST(qcs.set_options AS INT) & 4096 = 4096) THEN '', ARITH_ABORT'' ELSE '''' END + + CASE WHEN (CAST(qcs.set_options AS INT) & 8192 = 8192) THEN '', NUMERIC_ROUNDABORT'' ELSE '''' END + , 2, 200000) +FROM #working_plan_text wp +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq +ON wp.query_id = qsq.query_id +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_context_settings AS qcs +ON qcs.context_settings_id = qsq.context_settings_id +OPTION (RECOMPILE); +'; IF @Debug = 1 PRINT @sql_select; @@ -31643,131 +27992,153 @@ IF @sql_select IS NULL RETURN; END; -EXEC sys.sp_executesql @stmt = @sql_select, - @params = @sp_params, - @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; +EXEC sys.sp_executesql @stmt = @sql_select; -SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; -SET @sql_select += N' -WITH cpu_max -AS ( SELECT TOP 1 - gi.start_range, - gi.end_range - FROM #grouped_interval AS gi - ORDER BY gi.total_max_cpu_time_ms DESC ) -INSERT #working_plans WITH (TABLOCK) - ( plan_id, query_id, pattern ) -SELECT TOP ( @sp_Top ) - qsp.plan_id, qsp.query_id, ''max cpu'' -FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp -JOIN cpu_max AS dm -ON qsp.last_execution_time >= dm.start_range - AND qsp.last_execution_time < dm.end_range -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs -ON qsrs.plan_id = qsp.plan_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq -ON qsq.query_id = qsp.query_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt -ON qsqt.query_text_id = qsq.query_text_id -WHERE 1 = 1 - AND qsq.is_internal_query = 0 - AND qsp.query_plan IS NOT NULL - '; +/*This adds the patterns we found from each interval to the #working_plan_text table*/ -SET @sql_select += @sql_where; +RAISERROR(N'Add patterns to working plans', 0, 1) WITH NOWAIT; -SET @sql_select += N'ORDER BY qsrs.max_cpu_time DESC - OPTION (RECOMPILE); - '; +UPDATE wpt +SET wpt.pattern = wp.pattern +FROM #working_plans AS wp +JOIN #working_plan_text AS wpt +ON wpt.plan_id = wp.plan_id +AND wpt.query_id = wp.query_id +OPTION (RECOMPILE); -IF @Debug = 1 - PRINT @sql_select; +/*This cleans up query text a bit*/ -IF @sql_select IS NULL - BEGIN - RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; - RETURN; - END; +RAISERROR(N'Clean awkward characters from query text', 0, 1) WITH NOWAIT; -EXEC sys.sp_executesql @stmt = @sql_select, - @params = @sp_params, - @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; +UPDATE b +SET b.query_sql_text = REPLACE(REPLACE(REPLACE(b.query_sql_text, @cr, ' '), @lf, ' '), @tab, ' ') +FROM #working_plan_text AS b +OPTION (RECOMPILE); -/*Get highest logical read plans*/ +/*This populates #working_wait_stats when available*/ -RAISERROR(N'Gathering highest logical read plans', 0, 1) WITH NOWAIT; +IF @waitstats = 1 -SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; -SET @sql_select += N' -WITH logical_reads_max -AS ( SELECT TOP 1 - gi.start_range, - gi.end_range - FROM #grouped_interval AS gi - ORDER BY gi.total_avg_logical_io_reads_mb DESC ) -INSERT #working_plans WITH (TABLOCK) - ( plan_id, query_id, pattern ) -SELECT TOP ( @sp_Top ) - qsp.plan_id, qsp.query_id, ''avg logical reads'' -FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp -JOIN logical_reads_max AS dm -ON qsp.last_execution_time >= dm.start_range - AND qsp.last_execution_time < dm.end_range -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs -ON qsrs.plan_id = qsp.plan_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq -ON qsq.query_id = qsp.query_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt -ON qsqt.query_text_id = qsq.query_text_id -WHERE 1 = 1 - AND qsq.is_internal_query = 0 - AND qsp.query_plan IS NOT NULL - '; + BEGIN + + RAISERROR(N'Collecting wait stats info', 0, 1) WITH NOWAIT; + + + SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; + SET @sql_select += N' + SELECT qws.plan_id, + qws.wait_category, + qws.wait_category_desc, + SUM(qws.total_query_wait_time_ms) AS total_query_wait_time_ms, + SUM(qws.avg_query_wait_time_ms) AS avg_query_wait_time_ms, + SUM(qws.last_query_wait_time_ms) AS last_query_wait_time_ms, + SUM(qws.min_query_wait_time_ms) AS min_query_wait_time_ms, + SUM(qws.max_query_wait_time_ms) AS max_query_wait_time_ms + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_wait_stats qws + JOIN #working_plans AS wp + ON qws.plan_id = wp.plan_id + GROUP BY qws.plan_id, qws.wait_category, qws.wait_category_desc + HAVING SUM(qws.min_query_wait_time_ms) >= 5 + OPTION (RECOMPILE); + '; + + IF @Debug = 1 + PRINT @sql_select; + + IF @sql_select IS NULL + BEGIN + RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; + RETURN; + END; + + INSERT #working_wait_stats WITH (TABLOCK) + ( plan_id, wait_category, wait_category_desc, total_query_wait_time_ms, avg_query_wait_time_ms, last_query_wait_time_ms, min_query_wait_time_ms, max_query_wait_time_ms ) + + EXEC sys.sp_executesql @stmt = @sql_select; + + + /*This updates #working_plan_text with the top three waits from the wait stats DMV*/ + + RAISERROR(N'Update working_plan_text with top three waits', 0, 1) WITH NOWAIT; + + + UPDATE wpt + SET wpt.top_three_waits = x.top_three_waits + FROM #working_plan_text AS wpt + JOIN ( + SELECT wws.plan_id, + top_three_waits = STUFF((SELECT TOP 3 N', ' + wws2.wait_category_desc + N' (' + CONVERT(NVARCHAR(20), SUM(CONVERT(BIGINT, wws2.avg_query_wait_time_ms))) + N' ms) ' + FROM #working_wait_stats AS wws2 + WHERE wws.plan_id = wws2.plan_id + GROUP BY wws2.wait_category_desc + ORDER BY SUM(wws2.avg_query_wait_time_ms) DESC + FOR XML PATH(N''), TYPE).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 2, N'') + FROM #working_wait_stats AS wws + GROUP BY wws.plan_id + ) AS x + ON x.plan_id = wpt.plan_id + OPTION (RECOMPILE); -SET @sql_select += @sql_where; +END; -SET @sql_select += N'ORDER BY qsrs.avg_logical_io_reads DESC - OPTION (RECOMPILE); - '; +/*End wait stats population*/ -IF @Debug = 1 - PRINT @sql_select; +UPDATE #working_plan_text +SET top_three_waits = CASE + WHEN @waitstats = 0 + THEN N'The query store waits stats DMV is not available' + ELSE N'No Significant waits detected!' + END +WHERE top_three_waits IS NULL +OPTION(RECOMPILE); + +END; +END TRY +BEGIN CATCH + RAISERROR (N'Failure populating temp tables.', 0,1) WITH NOWAIT; + + IF @sql_select IS NOT NULL + BEGIN + SET @msg = N'Last @sql_select: ' + @sql_select; + RAISERROR(@msg, 0, 1) WITH NOWAIT; + END; + + SELECT @msg = @DatabaseName + N' database failed to process. ' + ERROR_MESSAGE(), @error_severity = ERROR_SEVERITY(), @error_state = ERROR_STATE(); + RAISERROR (@msg, @error_severity, @error_state) WITH NOWAIT; + + + WHILE @@TRANCOUNT > 0 + ROLLBACK; -IF @sql_select IS NULL - BEGIN - RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; RETURN; - END; +END CATCH; -EXEC sys.sp_executesql @stmt = @sql_select, - @params = @sp_params, - @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; +IF (@SkipXML = 0) +BEGIN TRY +BEGIN + +/* +This sets up the #working_warnings table with the IDs we're interested in so we can tie warnings back to them +*/ + +RAISERROR(N'Populate working warnings table with gathered plans', 0, 1) WITH NOWAIT; SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; SET @sql_select += N' -WITH logical_reads_max -AS ( SELECT TOP 1 - gi.start_range, - gi.end_range - FROM #grouped_interval AS gi - ORDER BY gi.total_max_logical_io_reads_mb DESC ) -INSERT #working_plans WITH (TABLOCK) - ( plan_id, query_id, pattern ) -SELECT TOP ( @sp_Top ) - qsp.plan_id, qsp.query_id, ''max logical reads'' -FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp -JOIN logical_reads_max AS dm -ON qsp.last_execution_time >= dm.start_range - AND qsp.last_execution_time < dm.end_range -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs -ON qsrs.plan_id = qsp.plan_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq -ON qsq.query_id = qsp.query_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt +SELECT DISTINCT wp.plan_id, wp.query_id, qsq.query_hash, qsqt.statement_sql_handle +FROM #working_plans AS wp +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp +ON qsp.plan_id = wp.plan_id + AND qsp.query_id = wp.query_id +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq +ON wp.query_id = qsq.query_id +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt ON qsqt.query_text_id = qsq.query_text_id +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs +ON qsrs.plan_id = wp.plan_id WHERE 1 = 1 AND qsq.is_internal_query = 0 AND qsp.query_plan IS NOT NULL @@ -31775,8 +28146,7 @@ WHERE 1 = 1 SET @sql_select += @sql_where; -SET @sql_select += N'ORDER BY qsrs.max_logical_io_reads DESC - OPTION (RECOMPILE); +SET @sql_select += N'OPTION (RECOMPILE); '; IF @Debug = 1 @@ -31788,37 +28158,50 @@ IF @sql_select IS NULL RETURN; END; +INSERT #working_warnings WITH (TABLOCK) + ( plan_id, query_id, query_hash, sql_handle ) EXEC sys.sp_executesql @stmt = @sql_select, @params = @sp_params, @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; +/* +This looks for queries in the query stores that we picked up from an internal that have multiple plans in cache -/*Get highest physical read plans*/ +This and several of the following queries all replaced XML parsing to find plan attributes. Sweet. -RAISERROR(N'Gathering highest physical read plans', 0, 1) WITH NOWAIT; +Thanks, Query Store +*/ + +RAISERROR(N'Populating object name in #working_warnings', 0, 1) WITH NOWAIT; +UPDATE w +SET w.proc_or_function_name = ISNULL(wm.proc_or_function_name, N'Statement') +FROM #working_warnings AS w +JOIN #working_metrics AS wm +ON w.plan_id = wm.plan_id + AND w.query_id = wm.query_id +OPTION (RECOMPILE); + + +RAISERROR(N'Checking for multiple plans', 0, 1) WITH NOWAIT; SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; SET @sql_select += N' -WITH physical_read_max -AS ( SELECT TOP 1 - gi.start_range, - gi.end_range - FROM #grouped_interval AS gi - ORDER BY gi.total_avg_physical_io_reads_mb DESC ) -INSERT #working_plans WITH (TABLOCK) - ( plan_id, query_id, pattern ) -SELECT TOP ( @sp_Top ) - qsp.plan_id, qsp.query_id, ''avg physical reads'' -FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp -JOIN physical_read_max AS dm -ON qsp.last_execution_time >= dm.start_range - AND qsp.last_execution_time < dm.end_range -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs -ON qsrs.plan_id = qsp.plan_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq -ON qsq.query_id = qsp.query_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt +UPDATE ww +SET ww.plan_multiple_plans = 1 +FROM #working_warnings AS ww +JOIN +( +SELECT wp.query_id, COUNT(qsp.plan_id) AS plans +FROM #working_plans AS wp +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp +ON qsp.plan_id = wp.plan_id + AND qsp.query_id = wp.query_id +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq +ON wp.query_id = qsq.query_id +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt ON qsqt.query_text_id = qsq.query_text_id +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs +ON qsrs.plan_id = wp.plan_id WHERE 1 = 1 AND qsq.is_internal_query = 0 AND qsp.query_plan IS NOT NULL @@ -31826,9 +28209,13 @@ WHERE 1 = 1 SET @sql_select += @sql_where; -SET @sql_select += N'ORDER BY qsrs.avg_physical_io_reads DESC - OPTION (RECOMPILE); - '; +SET @sql_select += +N'GROUP BY wp.query_id + HAVING COUNT(qsp.plan_id) > 1 +) AS x + ON ww.query_id = x.query_id +OPTION (RECOMPILE); +'; IF @Debug = 1 PRINT @sql_select; @@ -31843,5778 +28230,9444 @@ EXEC sys.sp_executesql @stmt = @sql_select, @params = @sp_params, @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; +/* +This looks for forced plans +*/ -SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; -SET @sql_select += N' -WITH physical_read_max -AS ( SELECT TOP 1 - gi.start_range, - gi.end_range - FROM #grouped_interval AS gi - ORDER BY gi.total_max_physical_io_reads_mb DESC ) -INSERT #working_plans WITH (TABLOCK) - ( plan_id, query_id, pattern ) -SELECT TOP ( @sp_Top ) - qsp.plan_id, qsp.query_id, ''max physical reads'' -FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp -JOIN physical_read_max AS dm -ON qsp.last_execution_time >= dm.start_range - AND qsp.last_execution_time < dm.end_range -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs -ON qsrs.plan_id = qsp.plan_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq -ON qsq.query_id = qsp.query_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt -ON qsqt.query_text_id = qsq.query_text_id -WHERE 1 = 1 - AND qsq.is_internal_query = 0 - AND qsp.query_plan IS NOT NULL - '; - -SET @sql_select += @sql_where; +RAISERROR(N'Checking for forced plans', 0, 1) WITH NOWAIT; -SET @sql_select += N'ORDER BY qsrs.max_physical_io_reads DESC - OPTION (RECOMPILE); - '; +UPDATE ww +SET ww.is_forced_plan = 1 +FROM #working_warnings AS ww +JOIN #working_plan_text AS wp +ON ww.plan_id = wp.plan_id + AND ww.query_id = wp.query_id + AND wp.is_forced_plan = 1 +OPTION (RECOMPILE); -IF @Debug = 1 - PRINT @sql_select; -IF @sql_select IS NULL - BEGIN - RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; - RETURN; - END; +/* +This looks for forced parameterization +*/ -EXEC sys.sp_executesql @stmt = @sql_select, - @params = @sp_params, - @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; +RAISERROR(N'Checking for forced parameterization', 0, 1) WITH NOWAIT; +UPDATE ww +SET ww.is_forced_parameterized = 1 +FROM #working_warnings AS ww +JOIN #working_metrics AS wm +ON ww.plan_id = wm.plan_id + AND ww.query_id = wm.query_id + AND wm.query_parameterization_type_desc = 'Forced' +OPTION (RECOMPILE); -/*Get highest logical write plans*/ -RAISERROR(N'Gathering highest write plans', 0, 1) WITH NOWAIT; +/* +This looks for unparameterized queries +*/ -SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; -SET @sql_select += N' -WITH logical_writes_max -AS ( SELECT TOP 1 - gi.start_range, - gi.end_range - FROM #grouped_interval AS gi - ORDER BY gi.total_avg_logical_io_writes_mb DESC ) -INSERT #working_plans WITH (TABLOCK) - ( plan_id, query_id, pattern ) -SELECT TOP ( @sp_Top ) - qsp.plan_id, qsp.query_id, ''avg writes'' -FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp -JOIN logical_writes_max AS dm -ON qsp.last_execution_time >= dm.start_range - AND qsp.last_execution_time < dm.end_range -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs -ON qsrs.plan_id = qsp.plan_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq -ON qsq.query_id = qsp.query_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt -ON qsqt.query_text_id = qsq.query_text_id -WHERE 1 = 1 - AND qsq.is_internal_query = 0 - AND qsp.query_plan IS NOT NULL - '; +RAISERROR(N'Checking for unparameterized plans', 0, 1) WITH NOWAIT; -SET @sql_select += @sql_where; +UPDATE ww +SET ww.unparameterized_query = 1 +FROM #working_warnings AS ww +JOIN #working_metrics AS wm +ON ww.plan_id = wm.plan_id + AND ww.query_id = wm.query_id + AND wm.query_parameterization_type_desc = 'None' + AND ww.proc_or_function_name = 'Statement' +OPTION (RECOMPILE); -SET @sql_select += N'ORDER BY qsrs.avg_logical_io_writes DESC - OPTION (RECOMPILE); - '; -IF @Debug = 1 - PRINT @sql_select; +/* +This looks for cursors +*/ -IF @sql_select IS NULL - BEGIN - RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; - RETURN; - END; +RAISERROR(N'Checking for cursors', 0, 1) WITH NOWAIT; +UPDATE ww +SET ww.is_cursor = 1 +FROM #working_warnings AS ww +JOIN #working_plan_text AS wp +ON ww.plan_id = wp.plan_id + AND ww.query_id = wp.query_id + AND wp.plan_group_id > 0 +OPTION (RECOMPILE); -EXEC sys.sp_executesql @stmt = @sql_select, - @params = @sp_params, - @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; +UPDATE ww +SET ww.is_cursor = 1 +FROM #working_warnings AS ww +JOIN #working_plan_text AS wp +ON ww.plan_id = wp.plan_id + AND ww.query_id = wp.query_id +WHERE ww.query_hash = 0x0000000000000000 +OR wp.query_plan_hash = 0x0000000000000000 +OPTION (RECOMPILE); -SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; -SET @sql_select += N' -WITH logical_writes_max -AS ( SELECT TOP 1 - gi.start_range, - gi.end_range - FROM #grouped_interval AS gi - ORDER BY gi.total_max_logical_io_writes_mb DESC ) -INSERT #working_plans WITH (TABLOCK) - ( plan_id, query_id, pattern ) -SELECT TOP ( @sp_Top ) - qsp.plan_id, qsp.query_id, ''max writes'' -FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp -JOIN logical_writes_max AS dm -ON qsp.last_execution_time >= dm.start_range - AND qsp.last_execution_time < dm.end_range -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs -ON qsrs.plan_id = qsp.plan_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq -ON qsq.query_id = qsp.query_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt -ON qsqt.query_text_id = qsq.query_text_id -WHERE 1 = 1 - AND qsq.is_internal_query = 0 - AND qsp.query_plan IS NOT NULL - '; +/* +This looks for parallel plans +*/ +UPDATE ww +SET ww.is_parallel = 1 +FROM #working_warnings AS ww +JOIN #working_plan_text AS wp +ON ww.plan_id = wp.plan_id + AND ww.query_id = wp.query_id + AND wp.is_parallel_plan = 1 +OPTION (RECOMPILE); -SET @sql_select += @sql_where; +/*This looks for old CE*/ -SET @sql_select += N'ORDER BY qsrs.max_logical_io_writes DESC - OPTION (RECOMPILE); - '; +RAISERROR(N'Checking for legacy CE', 0, 1) WITH NOWAIT; -IF @Debug = 1 - PRINT @sql_select; +UPDATE w +SET w.downlevel_estimator = 1 +FROM #working_warnings AS w +JOIN #working_plan_text AS wpt +ON w.plan_id = wpt.plan_id +AND w.query_id = wpt.query_id +/*PLEASE DON'T TELL ANYONE I DID THIS*/ +WHERE PARSENAME(wpt.engine_version, 4) < PARSENAME(CONVERT(VARCHAR(128), SERVERPROPERTY ('PRODUCTVERSION')), 4) +OPTION (RECOMPILE); +/*NO SERIOUSLY THIS IS A HORRIBLE IDEA*/ -IF @sql_select IS NULL - BEGIN - RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; - RETURN; - END; -EXEC sys.sp_executesql @stmt = @sql_select, - @params = @sp_params, - @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; +/*Plans that compile 2x more than they execute*/ +RAISERROR(N'Checking for plans that compile 2x more than they execute', 0, 1) WITH NOWAIT; -/*Get highest memory use plans*/ +UPDATE ww +SET ww.is_compile_more = 1 +FROM #working_warnings AS ww +JOIN #working_metrics AS wm +ON ww.plan_id = wm.plan_id + AND ww.query_id = wm.query_id + AND wm.count_compiles > (wm.count_executions * 2) +OPTION (RECOMPILE); -RAISERROR(N'Gathering highest memory use plans', 0, 1) WITH NOWAIT; +/*Plans that compile 2x more than they execute*/ -SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; -SET @sql_select += N' -WITH memory_max -AS ( SELECT TOP 1 - gi.start_range, - gi.end_range - FROM #grouped_interval AS gi - ORDER BY gi.total_avg_query_max_used_memory_mb DESC ) -INSERT #working_plans WITH (TABLOCK) - ( plan_id, query_id, pattern ) -SELECT TOP ( @sp_Top ) - qsp.plan_id, qsp.query_id, ''avg memory'' -FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp -JOIN memory_max AS dm -ON qsp.last_execution_time >= dm.start_range - AND qsp.last_execution_time < dm.end_range -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs -ON qsrs.plan_id = qsp.plan_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq -ON qsq.query_id = qsp.query_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt -ON qsqt.query_text_id = qsq.query_text_id -WHERE 1 = 1 - AND qsq.is_internal_query = 0 - AND qsp.query_plan IS NOT NULL - '; +RAISERROR(N'Checking for plans that take more than 5 seconds to bind, compile, or optimize', 0, 1) WITH NOWAIT; -SET @sql_select += @sql_where; +UPDATE ww +SET ww.is_slow_plan = 1 +FROM #working_warnings AS ww +JOIN #working_metrics AS wm +ON ww.plan_id = wm.plan_id + AND ww.query_id = wm.query_id + AND (wm.avg_bind_duration > 5000 + OR + wm.avg_compile_duration > 5000 + OR + wm.avg_optimize_duration > 5000 + OR + wm.avg_optimize_cpu_time > 5000) +OPTION (RECOMPILE); -SET @sql_select += N'ORDER BY qsrs.avg_query_max_used_memory DESC - OPTION (RECOMPILE); - '; -IF @Debug = 1 - PRINT @sql_select; -IF @sql_select IS NULL - BEGIN - RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; - RETURN; - END; +/* +This parses the XML from our top plans into smaller chunks for easier consumption +*/ -EXEC sys.sp_executesql @stmt = @sql_select, - @params = @sp_params, - @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; +RAISERROR(N'Begin XML nodes parsing', 0, 1) WITH NOWAIT; -SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; -SET @sql_select += N' -WITH memory_max -AS ( SELECT TOP 1 - gi.start_range, - gi.end_range - FROM #grouped_interval AS gi - ORDER BY gi.total_max_query_max_used_memory_mb DESC ) -INSERT #working_plans WITH (TABLOCK) - ( plan_id, query_id, pattern ) -SELECT TOP ( @sp_Top ) - qsp.plan_id, qsp.query_id, ''max memory'' -FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp -JOIN memory_max AS dm -ON qsp.last_execution_time >= dm.start_range - AND qsp.last_execution_time < dm.end_range -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs -ON qsrs.plan_id = qsp.plan_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq -ON qsq.query_id = qsp.query_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt -ON qsqt.query_text_id = qsq.query_text_id -WHERE 1 = 1 - AND qsq.is_internal_query = 0 - AND qsp.query_plan IS NOT NULL - '; +RAISERROR(N'Inserting #statements', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +INSERT #statements WITH (TABLOCK) ( plan_id, query_id, query_hash, sql_handle, statement, is_cursor ) + SELECT ww.plan_id, ww.query_id, ww.query_hash, ww.sql_handle, q.n.query('.') AS statement, 0 AS is_cursor + FROM #working_warnings AS ww + JOIN #working_plan_text AS wp + ON ww.plan_id = wp.plan_id + AND ww.query_id = wp.query_id + CROSS APPLY wp.query_plan_xml.nodes('//p:StmtSimple') AS q(n) +OPTION (RECOMPILE); -SET @sql_select += @sql_where; +RAISERROR(N'Inserting parsed cursor XML to #statements', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +INSERT #statements WITH (TABLOCK) ( plan_id, query_id, query_hash, sql_handle, statement, is_cursor ) + SELECT ww.plan_id, ww.query_id, ww.query_hash, ww.sql_handle, q.n.query('.') AS statement, 1 AS is_cursor + FROM #working_warnings AS ww + JOIN #working_plan_text AS wp + ON ww.plan_id = wp.plan_id + AND ww.query_id = wp.query_id + CROSS APPLY wp.query_plan_xml.nodes('//p:StmtCursor') AS q(n) +OPTION (RECOMPILE); -SET @sql_select += N'ORDER BY qsrs.max_query_max_used_memory DESC - OPTION (RECOMPILE); - '; +RAISERROR(N'Inserting to #query_plan', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +INSERT #query_plan WITH (TABLOCK) ( plan_id, query_id, query_hash, sql_handle, query_plan ) +SELECT s.plan_id, s.query_id, s.query_hash, s.sql_handle, q.n.query('.') AS query_plan +FROM #statements AS s + CROSS APPLY s.statement.nodes('//p:QueryPlan') AS q(n) +OPTION (RECOMPILE); -IF @Debug = 1 - PRINT @sql_select; +RAISERROR(N'Inserting to #relop', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +INSERT #relop WITH (TABLOCK) ( plan_id, query_id, query_hash, sql_handle, relop) +SELECT qp.plan_id, qp.query_id, qp.query_hash, qp.sql_handle, q.n.query('.') AS relop +FROM #query_plan qp + CROSS APPLY qp.query_plan.nodes('//p:RelOp') AS q(n) +OPTION (RECOMPILE); -IF @sql_select IS NULL - BEGIN - RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; - RETURN; - END; -EXEC sys.sp_executesql @stmt = @sql_select, - @params = @sp_params, - @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; +-- statement level checks +RAISERROR(N'Performing compile timeout checks', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +UPDATE b +SET b.compile_timeout = 1 +FROM #statements s +JOIN #working_warnings AS b +ON s.query_hash = b.query_hash +WHERE s.statement.exist('/p:StmtSimple/@StatementOptmEarlyAbortReason[.="TimeOut"]') = 1 +OPTION (RECOMPILE); -/*Get highest row count plans*/ -RAISERROR(N'Gathering highest row count plans', 0, 1) WITH NOWAIT; +RAISERROR(N'Performing compile memory limit exceeded checks', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +UPDATE b +SET b.compile_memory_limit_exceeded = 1 +FROM #statements s +JOIN #working_warnings AS b +ON s.query_hash = b.query_hash +WHERE s.statement.exist('/p:StmtSimple/@StatementOptmEarlyAbortReason[.="MemoryLimitExceeded"]') = 1 +OPTION (RECOMPILE); -SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; -SET @sql_select += N' -WITH rowcount_max -AS ( SELECT TOP 1 - gi.start_range, - gi.end_range - FROM #grouped_interval AS gi - ORDER BY gi.total_rowcount DESC ) -INSERT #working_plans WITH (TABLOCK) - ( plan_id, query_id, pattern ) -SELECT TOP ( @sp_Top ) - qsp.plan_id, qsp.query_id, ''avg rows'' -FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp -JOIN rowcount_max AS dm -ON qsp.last_execution_time >= dm.start_range - AND qsp.last_execution_time < dm.end_range -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs -ON qsrs.plan_id = qsp.plan_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq -ON qsq.query_id = qsp.query_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt -ON qsqt.query_text_id = qsq.query_text_id -WHERE 1 = 1 - AND qsq.is_internal_query = 0 - AND qsp.query_plan IS NOT NULL - '; +IF @ExpertMode > 0 +BEGIN +RAISERROR(N'Performing index DML checks', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p), +index_dml AS ( + SELECT s.query_hash, + index_dml = CASE WHEN s.statement.exist('//p:StmtSimple/@StatementType[.="CREATE INDEX"]') = 1 THEN 1 + WHEN s.statement.exist('//p:StmtSimple/@StatementType[.="DROP INDEX"]') = 1 THEN 1 + END + FROM #statements s + ) + UPDATE b + SET b.index_dml = i.index_dml + FROM #working_warnings AS b + JOIN index_dml i + ON i.query_hash = b.query_hash + WHERE i.index_dml = 1 +OPTION (RECOMPILE); -SET @sql_select += @sql_where; -SET @sql_select += N'ORDER BY qsrs.avg_rowcount DESC - OPTION (RECOMPILE); - '; +RAISERROR(N'Performing table DML checks', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p), +table_dml AS ( + SELECT s.query_hash, + table_dml = CASE WHEN s.statement.exist('//p:StmtSimple/@StatementType[.="CREATE TABLE"]') = 1 THEN 1 + WHEN s.statement.exist('//p:StmtSimple/@StatementType[.="DROP OBJECT"]') = 1 THEN 1 + END + FROM #statements AS s + ) + UPDATE b + SET b.table_dml = t.table_dml + FROM #working_warnings AS b + JOIN table_dml t + ON t.query_hash = b.query_hash + WHERE t.table_dml = 1 +OPTION (RECOMPILE); +END; -IF @Debug = 1 - PRINT @sql_select; -IF @sql_select IS NULL - BEGIN - RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; - RETURN; - END; +RAISERROR(N'Gathering trivial plans', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES ( 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) +UPDATE b +SET b.is_trivial = 1 +FROM #working_warnings AS b +JOIN ( +SELECT s.sql_handle +FROM #statements AS s +JOIN ( SELECT r.sql_handle + FROM #relop AS r + WHERE r.relop.exist('//p:RelOp[contains(@LogicalOp, "Scan")]') = 1 ) AS r + ON r.sql_handle = s.sql_handle +WHERE s.statement.exist('//p:StmtSimple[@StatementOptmLevel[.="TRIVIAL"]]/p:QueryPlan/p:ParameterList') = 1 +) AS s +ON b.sql_handle = s.sql_handle +OPTION (RECOMPILE); -EXEC sys.sp_executesql @stmt = @sql_select, - @params = @sp_params, - @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; +IF @ExpertMode > 0 +BEGIN +RAISERROR(N'Gathering row estimates', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES ('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) +INSERT #est_rows (query_hash, estimated_rows) +SELECT DISTINCT + CONVERT(BINARY(8), RIGHT('0000000000000000' + SUBSTRING(c.n.value('@QueryHash', 'VARCHAR(18)'), 3, 18), 16), 2) AS query_hash, + c.n.value('(/p:StmtSimple/@StatementEstRows)[1]', 'FLOAT') AS estimated_rows +FROM #statements AS s +CROSS APPLY s.statement.nodes('/p:StmtSimple') AS c(n) +WHERE c.n.exist('/p:StmtSimple[@StatementEstRows > 0]') = 1; + UPDATE b + SET b.estimated_rows = er.estimated_rows + FROM #working_warnings AS b + JOIN #est_rows er + ON er.query_hash = b.query_hash + OPTION (RECOMPILE); +END; -IF @new_columns = 1 -BEGIN -RAISERROR(N'Gathering new 2017 new column info...', 0, 1) WITH NOWAIT; +/*Begin plan cost calculations*/ +RAISERROR(N'Gathering statement costs', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +INSERT #plan_cost WITH (TABLOCK) + ( query_plan_cost, sql_handle, plan_id ) +SELECT DISTINCT + s.statement.value('sum(/p:StmtSimple/@StatementSubTreeCost)', 'float') query_plan_cost, + s.sql_handle, + s.plan_id +FROM #statements s +OUTER APPLY s.statement.nodes('/p:StmtSimple') AS q(n) +WHERE s.statement.value('sum(/p:StmtSimple/@StatementSubTreeCost)', 'float') > 0 +OPTION (RECOMPILE); -/*Get highest log byte count plans*/ -RAISERROR(N'Gathering highest log byte use plans', 0, 1) WITH NOWAIT; +RAISERROR(N'Updating statement costs', 0, 1) WITH NOWAIT; +WITH pc AS ( + SELECT SUM(DISTINCT pc.query_plan_cost) AS queryplancostsum, pc.sql_handle, pc.plan_id + FROM #plan_cost AS pc + GROUP BY pc.sql_handle, pc.plan_id + ) + UPDATE b + SET b.query_cost = ISNULL(pc.queryplancostsum, 0) + FROM #working_warnings AS b + JOIN pc + ON pc.sql_handle = b.sql_handle + AND pc.plan_id = b.plan_id +OPTION (RECOMPILE); -SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; -SET @sql_select += N' -WITH rowcount_max -AS ( SELECT TOP 1 - gi.start_range, - gi.end_range - FROM #grouped_interval AS gi - ORDER BY gi.total_avg_log_bytes_mb DESC ) -INSERT #working_plans WITH (TABLOCK) - ( plan_id, query_id, pattern ) -SELECT TOP ( @sp_Top ) - qsp.plan_id, qsp.query_id, ''avg log bytes'' -FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp -JOIN rowcount_max AS dm -ON qsp.last_execution_time >= dm.start_range - AND qsp.last_execution_time < dm.end_range -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs -ON qsrs.plan_id = qsp.plan_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq -ON qsq.query_id = qsp.query_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt -ON qsqt.query_text_id = qsq.query_text_id -WHERE 1 = 1 - AND qsq.is_internal_query = 0 - AND qsp.query_plan IS NOT NULL - '; -SET @sql_select += @sql_where; +/*End plan cost calculations*/ -SET @sql_select += N'ORDER BY qsrs.avg_log_bytes_used DESC - OPTION (RECOMPILE); - '; -IF @Debug = 1 - PRINT @sql_select; +RAISERROR(N'Checking for plan warnings', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +UPDATE b +SET b.plan_warnings = 1 +FROM #query_plan qp +JOIN #working_warnings b +ON qp.sql_handle = b.sql_handle +AND qp.query_plan.exist('/p:QueryPlan/p:Warnings') = 1 +OPTION (RECOMPILE); -IF @sql_select IS NULL - BEGIN - RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; - RETURN; - END; -EXEC sys.sp_executesql @stmt = @sql_select, - @params = @sp_params, - @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; +RAISERROR(N'Checking for implicit conversion', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +UPDATE b +SET b.implicit_conversions = 1 +FROM #query_plan qp +JOIN #working_warnings b +ON qp.sql_handle = b.sql_handle +AND qp.query_plan.exist('/p:QueryPlan/p:Warnings/p:PlanAffectingConvert/@Expression[contains(., "CONVERT_IMPLICIT")]') = 1 +OPTION (RECOMPILE); -RAISERROR(N'Gathering highest log byte use plans', 0, 1) WITH NOWAIT; +IF @ExpertMode > 0 +BEGIN +RAISERROR(N'Performing busy loops checks', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +UPDATE p +SET busy_loops = CASE WHEN (x.estimated_executions / 100.0) > x.estimated_rows THEN 1 END +FROM #working_warnings p + JOIN ( + SELECT qs.sql_handle, + relop.value('sum(/p:RelOp/@EstimateRows)', 'float') AS estimated_rows , + relop.value('sum(/p:RelOp/@EstimateRewinds)', 'float') + relop.value('sum(/p:RelOp/@EstimateRebinds)', 'float') + 1.0 AS estimated_executions + FROM #relop qs + ) AS x ON p.sql_handle = x.sql_handle +OPTION (RECOMPILE); +END; -SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; -SET @sql_select += N' -WITH rowcount_max -AS ( SELECT TOP 1 - gi.start_range, - gi.end_range - FROM #grouped_interval AS gi - ORDER BY gi.total_max_log_bytes_mb DESC ) -INSERT #working_plans WITH (TABLOCK) - ( plan_id, query_id, pattern ) -SELECT TOP ( @sp_Top ) - qsp.plan_id, qsp.query_id, ''max log bytes'' -FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp -JOIN rowcount_max AS dm -ON qsp.last_execution_time >= dm.start_range - AND qsp.last_execution_time < dm.end_range -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs -ON qsrs.plan_id = qsp.plan_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq -ON qsq.query_id = qsp.query_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt -ON qsqt.query_text_id = qsq.query_text_id -WHERE 1 = 1 - AND qsq.is_internal_query = 0 - AND qsp.query_plan IS NOT NULL - '; -SET @sql_select += @sql_where; +RAISERROR(N'Performing TVF join check', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +UPDATE p +SET p.tvf_join = CASE WHEN x.tvf_join = 1 THEN 1 END +FROM #working_warnings p + JOIN ( + SELECT r.sql_handle, + 1 AS tvf_join + FROM #relop AS r + WHERE r.relop.exist('//p:RelOp[(@LogicalOp[.="Table-valued function"])]') = 1 + AND r.relop.exist('//p:RelOp[contains(@LogicalOp, "Join")]') = 1 + ) AS x ON p.sql_handle = x.sql_handle +OPTION (RECOMPILE); -SET @sql_select += N'ORDER BY qsrs.max_log_bytes_used DESC - OPTION (RECOMPILE); - '; +IF @ExpertMode > 0 +BEGIN +RAISERROR(N'Checking for operator warnings', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +, x AS ( +SELECT r.sql_handle, + c.n.exist('//p:Warnings[(@NoJoinPredicate[.="1"])]') AS warning_no_join_predicate, + c.n.exist('//p:ColumnsWithNoStatistics') AS no_stats_warning , + c.n.exist('//p:Warnings') AS relop_warnings +FROM #relop AS r +CROSS APPLY r.relop.nodes('/p:RelOp/p:Warnings') AS c(n) +) +UPDATE b +SET b.warning_no_join_predicate = x.warning_no_join_predicate, + b.no_stats_warning = x.no_stats_warning, + b.relop_warnings = x.relop_warnings +FROM #working_warnings b +JOIN x ON x.sql_handle = b.sql_handle +OPTION (RECOMPILE); +END; -IF @Debug = 1 - PRINT @sql_select; -IF @sql_select IS NULL - BEGIN - RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; - RETURN; - END; +RAISERROR(N'Checking for table variables', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +, x AS ( +SELECT r.sql_handle, + c.n.value('substring(@Table, 2, 1)','VARCHAR(100)') AS first_char +FROM #relop r +CROSS APPLY r.relop.nodes('//p:Object') AS c(n) +) +UPDATE b +SET b.is_table_variable = 1 +FROM #working_warnings b +JOIN x ON x.sql_handle = b.sql_handle +JOIN #working_metrics AS wm +ON b.plan_id = wm.plan_id +AND b.query_id = wm.query_id +AND wm.batch_sql_handle IS NOT NULL +WHERE x.first_char = '@' +OPTION (RECOMPILE); -EXEC sys.sp_executesql @stmt = @sql_select, - @params = @sp_params, - @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; +IF @ExpertMode > 0 +BEGIN +RAISERROR(N'Checking for functions', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +, x AS ( +SELECT r.sql_handle, + n.fn.value('count(distinct-values(//p:UserDefinedFunction[not(@IsClrFunction)]))', 'INT') AS function_count, + n.fn.value('count(distinct-values(//p:UserDefinedFunction[@IsClrFunction = "1"]))', 'INT') AS clr_function_count +FROM #relop r +CROSS APPLY r.relop.nodes('/p:RelOp/p:ComputeScalar/p:DefinedValues/p:DefinedValue/p:ScalarOperator') n(fn) +) +UPDATE b +SET b.function_count = x.function_count, + b.clr_function_count = x.clr_function_count +FROM #working_warnings b +JOIN x ON x.sql_handle = b.sql_handle +OPTION (RECOMPILE); +END; -/*Get highest tempdb use plans*/ -RAISERROR(N'Gathering highest tempdb use plans', 0, 1) WITH NOWAIT; +RAISERROR(N'Checking for expensive key lookups', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +UPDATE b +SET b.key_lookup_cost = x.key_lookup_cost +FROM #working_warnings b +JOIN ( +SELECT + r.sql_handle, + MAX(r.relop.value('sum(/p:RelOp/@EstimatedTotalSubtreeCost)', 'float')) AS key_lookup_cost +FROM #relop r +WHERE r.relop.exist('/p:RelOp/p:IndexScan[(@Lookup[.="1"])]') = 1 +GROUP BY r.sql_handle +) AS x ON x.sql_handle = b.sql_handle +OPTION (RECOMPILE); -SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; -SET @sql_select += N' -WITH rowcount_max -AS ( SELECT TOP 1 - gi.start_range, - gi.end_range - FROM #grouped_interval AS gi - ORDER BY gi.total_avg_tempdb_space DESC ) -INSERT #working_plans WITH (TABLOCK) - ( plan_id, query_id, pattern ) -SELECT TOP ( @sp_Top ) - qsp.plan_id, qsp.query_id, ''avg tempdb space'' -FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp -JOIN rowcount_max AS dm -ON qsp.last_execution_time >= dm.start_range - AND qsp.last_execution_time < dm.end_range -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs -ON qsrs.plan_id = qsp.plan_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq -ON qsq.query_id = qsp.query_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt -ON qsqt.query_text_id = qsq.query_text_id -WHERE 1 = 1 - AND qsq.is_internal_query = 0 - AND qsp.query_plan IS NOT NULL - '; -SET @sql_select += @sql_where; +RAISERROR(N'Checking for expensive remote queries', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +UPDATE b +SET b.remote_query_cost = x.remote_query_cost +FROM #working_warnings b +JOIN ( +SELECT + r.sql_handle, + MAX(r.relop.value('sum(/p:RelOp/@EstimatedTotalSubtreeCost)', 'float')) AS remote_query_cost +FROM #relop r +WHERE r.relop.exist('/p:RelOp[(@PhysicalOp[contains(., "Remote")])]') = 1 +GROUP BY r.sql_handle +) AS x ON x.sql_handle = b.sql_handle +OPTION (RECOMPILE); -SET @sql_select += N'ORDER BY qsrs.avg_tempdb_space_used DESC - OPTION (RECOMPILE); - '; -IF @Debug = 1 - PRINT @sql_select; +RAISERROR(N'Checking for expensive sorts', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +UPDATE b +SET sort_cost = y.max_sort_cost +FROM #working_warnings b +JOIN ( + SELECT x.sql_handle, MAX((x.sort_io + x.sort_cpu)) AS max_sort_cost + FROM ( + SELECT + qs.sql_handle, + relop.value('sum(/p:RelOp/@EstimateIO)', 'float') AS sort_io, + relop.value('sum(/p:RelOp/@EstimateCPU)', 'float') AS sort_cpu + FROM #relop qs + WHERE [relop].exist('/p:RelOp[(@PhysicalOp[.="Sort"])]') = 1 + ) AS x + GROUP BY x.sql_handle + ) AS y +ON b.sql_handle = y.sql_handle +OPTION (RECOMPILE); -IF @sql_select IS NULL - BEGIN - RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; - RETURN; - END; +IF NOT EXISTS(SELECT 1/0 FROM #statements AS s WHERE s.is_cursor = 1) +BEGIN -EXEC sys.sp_executesql @stmt = @sql_select, - @params = @sp_params, - @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; +RAISERROR(N'No cursor plans found, skipping', 0, 1) WITH NOWAIT; -SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; -SET @sql_select += N' -WITH rowcount_max -AS ( SELECT TOP 1 - gi.start_range, - gi.end_range - FROM #grouped_interval AS gi - ORDER BY gi.total_max_tempdb_space DESC ) -INSERT #working_plans WITH (TABLOCK) - ( plan_id, query_id, pattern ) -SELECT TOP ( @sp_Top ) - qsp.plan_id, qsp.query_id, ''max tempdb space'' -FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp -JOIN rowcount_max AS dm -ON qsp.last_execution_time >= dm.start_range - AND qsp.last_execution_time < dm.end_range -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs -ON qsrs.plan_id = qsp.plan_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq -ON qsq.query_id = qsp.query_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt -ON qsqt.query_text_id = qsq.query_text_id -WHERE 1 = 1 - AND qsq.is_internal_query = 0 - AND qsp.query_plan IS NOT NULL - '; +END -SET @sql_select += @sql_where; +IF EXISTS(SELECT 1/0 FROM #statements AS s WHERE s.is_cursor = 1) +BEGIN -SET @sql_select += N'ORDER BY qsrs.max_tempdb_space_used DESC - OPTION (RECOMPILE); - '; +RAISERROR(N'Cursor plans found, investigating', 0, 1) WITH NOWAIT; -IF @Debug = 1 - PRINT @sql_select; +RAISERROR(N'Checking for Optimistic cursors', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +UPDATE b +SET b.is_optimistic_cursor = 1 +FROM #working_warnings b +JOIN #statements AS s +ON b.sql_handle = s.sql_handle +CROSS APPLY s.statement.nodes('/p:StmtCursor') AS n1(fn) +WHERE n1.fn.exist('//p:CursorPlan/@CursorConcurrency[.="Optimistic"]') = 1 +AND s.is_cursor = 1 +OPTION (RECOMPILE); -IF @sql_select IS NULL - BEGIN - RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; - RETURN; - END; -EXEC sys.sp_executesql @stmt = @sql_select, - @params = @sp_params, - @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; +RAISERROR(N'Checking if cursor is Forward Only', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +UPDATE b +SET b.is_forward_only_cursor = 1 +FROM #working_warnings b +JOIN #statements AS s +ON b.sql_handle = s.sql_handle +CROSS APPLY s.statement.nodes('/p:StmtCursor') AS n1(fn) +WHERE n1.fn.exist('//p:CursorPlan/@ForwardOnly[.="true"]') = 1 +AND s.is_cursor = 1 +OPTION (RECOMPILE); -END; +RAISERROR(N'Checking if cursor is Fast Forward', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +UPDATE b +SET b.is_fast_forward_cursor = 1 +FROM #working_warnings b +JOIN #statements AS s +ON b.sql_handle = s.sql_handle +CROSS APPLY s.statement.nodes('/p:StmtCursor') AS n1(fn) +WHERE n1.fn.exist('//p:CursorPlan/@CursorActualType[.="FastForward"]') = 1 +AND s.is_cursor = 1 +OPTION (RECOMPILE); -/* -This rolls up the different patterns we find before deduplicating. +RAISERROR(N'Checking for Dynamic cursors', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +UPDATE b +SET b.is_cursor_dynamic = 1 +FROM #working_warnings b +JOIN #statements AS s +ON b.sql_handle = s.sql_handle +CROSS APPLY s.statement.nodes('/p:StmtCursor') AS n1(fn) +WHERE n1.fn.exist('//p:CursorPlan/@CursorActualType[.="Dynamic"]') = 1 +AND s.is_cursor = 1 +OPTION (RECOMPILE); -The point of this is so we know if a query was gathered by one or more of the search queries +END -*/ +IF @ExpertMode > 0 +BEGIN +RAISERROR(N'Checking for bad scans and plan forcing', 0, 1) WITH NOWAIT; +;WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +UPDATE b +SET +b.is_table_scan = x.is_table_scan, +b.backwards_scan = x.backwards_scan, +b.forced_index = x.forced_index, +b.forced_seek = x.forced_seek, +b.forced_scan = x.forced_scan +FROM #working_warnings b +JOIN ( +SELECT + r.sql_handle, + 0 AS is_table_scan, + q.n.exist('@ScanDirection[.="BACKWARD"]') AS backwards_scan, + q.n.value('@ForcedIndex', 'bit') AS forced_index, + q.n.value('@ForceSeek', 'bit') AS forced_seek, + q.n.value('@ForceScan', 'bit') AS forced_scan +FROM #relop r +CROSS APPLY r.relop.nodes('//p:IndexScan') AS q(n) +UNION ALL +SELECT + r.sql_handle, + 1 AS is_table_scan, + q.n.exist('@ScanDirection[.="BACKWARD"]') AS backwards_scan, + q.n.value('@ForcedIndex', 'bit') AS forced_index, + q.n.value('@ForceSeek', 'bit') AS forced_seek, + q.n.value('@ForceScan', 'bit') AS forced_scan +FROM #relop r +CROSS APPLY r.relop.nodes('//p:TableScan') AS q(n) +) AS x ON b.sql_handle = x.sql_handle +OPTION (RECOMPILE); +END; -RAISERROR(N'Updating patterns', 0, 1) WITH NOWAIT; -WITH patterns AS ( -SELECT wp.plan_id, wp.query_id, - pattern_path = STUFF((SELECT DISTINCT N', ' + wp2.pattern - FROM #working_plans AS wp2 - WHERE wp.plan_id = wp2.plan_id - AND wp.query_id = wp2.query_id - FOR XML PATH(N''), TYPE).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 2, N'') -FROM #working_plans AS wp -) -UPDATE wp -SET wp.pattern = patterns.pattern_path -FROM #working_plans AS wp -JOIN patterns -ON wp.plan_id = patterns.plan_id -AND wp.query_id = patterns.query_id +IF @ExpertMode > 0 +BEGIN +RAISERROR(N'Checking for computed columns that reference scalar UDFs', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +UPDATE b +SET b.is_computed_scalar = x.computed_column_function +FROM #working_warnings b +JOIN ( +SELECT r.sql_handle, + n.fn.value('count(distinct-values(//p:UserDefinedFunction[not(@IsClrFunction)]))', 'INT') AS computed_column_function +FROM #relop r +CROSS APPLY r.relop.nodes('/p:RelOp/p:ComputeScalar/p:DefinedValues/p:DefinedValue/p:ScalarOperator') n(fn) +WHERE n.fn.exist('/p:RelOp/p:ComputeScalar/p:DefinedValues/p:DefinedValue/p:ColumnReference[(@ComputedColumn[.="1"])]') = 1 +) AS x ON x.sql_handle = b.sql_handle OPTION (RECOMPILE); +END; -/* -This dedupes our results so we hopefully don't double-work the same plan -*/ +RAISERROR(N'Checking for filters that reference scalar UDFs', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +UPDATE b +SET b.is_computed_filter = x.filter_function +FROM #working_warnings b +JOIN ( +SELECT +r.sql_handle, +c.n.value('count(distinct-values(//p:UserDefinedFunction[not(@IsClrFunction)]))', 'INT') AS filter_function +FROM #relop AS r +CROSS APPLY r.relop.nodes('/p:RelOp/p:Filter/p:Predicate/p:ScalarOperator/p:Compare/p:ScalarOperator/p:UserDefinedFunction') c(n) +) x ON x.sql_handle = b.sql_handle +OPTION (RECOMPILE); -RAISERROR(N'Deduplicating gathered plans', 0, 1) WITH NOWAIT; -WITH dedupe AS ( -SELECT * , ROW_NUMBER() OVER (PARTITION BY wp.plan_id ORDER BY wp.plan_id) AS dupes -FROM #working_plans AS wp -) -DELETE dedupe -WHERE dedupe.dupes > 1 +IF @ExpertMode > 0 +BEGIN +RAISERROR(N'Checking modification queries that hit lots of indexes', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p), +IndexOps AS +( + SELECT + r.query_hash, + c.n.value('@PhysicalOp', 'VARCHAR(100)') AS op_name, + c.n.exist('@PhysicalOp[.="Index Insert"]') AS ii, + c.n.exist('@PhysicalOp[.="Index Update"]') AS iu, + c.n.exist('@PhysicalOp[.="Index Delete"]') AS id, + c.n.exist('@PhysicalOp[.="Clustered Index Insert"]') AS cii, + c.n.exist('@PhysicalOp[.="Clustered Index Update"]') AS ciu, + c.n.exist('@PhysicalOp[.="Clustered Index Delete"]') AS cid, + c.n.exist('@PhysicalOp[.="Table Insert"]') AS ti, + c.n.exist('@PhysicalOp[.="Table Update"]') AS tu, + c.n.exist('@PhysicalOp[.="Table Delete"]') AS td + FROM #relop AS r + CROSS APPLY r.relop.nodes('/p:RelOp') c(n) + OUTER APPLY r.relop.nodes('/p:RelOp/p:ScalarInsert/p:Object') q(n) + OUTER APPLY r.relop.nodes('/p:RelOp/p:Update/p:Object') o2(n) + OUTER APPLY r.relop.nodes('/p:RelOp/p:SimpleUpdate/p:Object') o3(n) +), iops AS +( + SELECT ios.query_hash, + SUM(CONVERT(TINYINT, ios.ii)) AS index_insert_count, + SUM(CONVERT(TINYINT, ios.iu)) AS index_update_count, + SUM(CONVERT(TINYINT, ios.id)) AS index_delete_count, + SUM(CONVERT(TINYINT, ios.cii)) AS cx_insert_count, + SUM(CONVERT(TINYINT, ios.ciu)) AS cx_update_count, + SUM(CONVERT(TINYINT, ios.cid)) AS cx_delete_count, + SUM(CONVERT(TINYINT, ios.ti)) AS table_insert_count, + SUM(CONVERT(TINYINT, ios.tu)) AS table_update_count, + SUM(CONVERT(TINYINT, ios.td)) AS table_delete_count + FROM IndexOps AS ios + WHERE ios.op_name IN ('Index Insert', 'Index Delete', 'Index Update', + 'Clustered Index Insert', 'Clustered Index Delete', 'Clustered Index Update', + 'Table Insert', 'Table Delete', 'Table Update') + GROUP BY ios.query_hash) +UPDATE b +SET b.index_insert_count = iops.index_insert_count, + b.index_update_count = iops.index_update_count, + b.index_delete_count = iops.index_delete_count, + b.cx_insert_count = iops.cx_insert_count, + b.cx_update_count = iops.cx_update_count, + b.cx_delete_count = iops.cx_delete_count, + b.table_insert_count = iops.table_insert_count, + b.table_update_count = iops.table_update_count, + b.table_delete_count = iops.table_delete_count +FROM #working_warnings AS b +JOIN iops ON iops.query_hash = b.query_hash OPTION (RECOMPILE); +END; -SET @msg = N'Removed ' + CONVERT(NVARCHAR(10), @@ROWCOUNT) + N' duplicate plan_ids.'; -RAISERROR(@msg, 0, 1) WITH NOWAIT; +IF @ExpertMode > 0 +BEGIN +RAISERROR(N'Checking for Spatial index use', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +UPDATE b +SET b.is_spatial = x.is_spatial +FROM #working_warnings AS b +JOIN ( +SELECT r.sql_handle, + 1 AS is_spatial +FROM #relop r +CROSS APPLY r.relop.nodes('/p:RelOp//p:Object') n(fn) +WHERE n.fn.exist('(@IndexKind[.="Spatial"])') = 1 +) AS x ON x.sql_handle = b.sql_handle +OPTION (RECOMPILE); +END; -/* -This gathers data for the #working_metrics table -*/ +RAISERROR(N'Checking for forced serialization', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +UPDATE b +SET b.is_forced_serial = 1 +FROM #query_plan qp +JOIN #working_warnings AS b +ON qp.sql_handle = b.sql_handle +AND b.is_parallel IS NULL +AND qp.query_plan.exist('/p:QueryPlan/@NonParallelPlanReason') = 1 +OPTION (RECOMPILE); -RAISERROR(N'Collecting worker metrics', 0, 1) WITH NOWAIT; +IF @ExpertMode > 0 +BEGIN +RAISERROR(N'Checking for ColumnStore queries operating in Row Mode instead of Batch Mode', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +UPDATE b +SET b.columnstore_row_mode = x.is_row_mode +FROM #working_warnings AS b +JOIN ( +SELECT + r.sql_handle, + r.relop.exist('/p:RelOp[(@EstimatedExecutionMode[.="Row"])]') AS is_row_mode +FROM #relop r +WHERE r.relop.exist('/p:RelOp/p:IndexScan[(@Storage[.="ColumnStore"])]') = 1 +) AS x ON x.sql_handle = b.sql_handle +OPTION (RECOMPILE); +END; -SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; -SET @sql_select += N' -SELECT ' + QUOTENAME(@DatabaseName, '''') + N' AS database_name, wp.plan_id, wp.query_id, - QUOTENAME(object_schema_name(qsq.object_id, DB_ID(' + QUOTENAME(@DatabaseName, '''') + N'))) + ''.'' + - QUOTENAME(object_name(qsq.object_id, DB_ID(' + QUOTENAME(@DatabaseName, '''') + N'))) AS proc_or_function_name, - qsq.batch_sql_handle, qsq.query_hash, qsq.query_parameterization_type_desc, qsq.count_compiles, - (qsq.avg_compile_duration / 1000.), - (qsq.last_compile_duration / 1000.), - (qsq.avg_bind_duration / 1000.), - (qsq.last_bind_duration / 1000.), - (qsq.avg_bind_cpu_time / 1000.), - (qsq.last_bind_cpu_time / 1000.), - (qsq.avg_optimize_duration / 1000.), - (qsq.last_optimize_duration / 1000.), - (qsq.avg_optimize_cpu_time / 1000.), - (qsq.last_optimize_cpu_time / 1000.), - (qsq.avg_compile_memory_kb / 1024.), - (qsq.last_compile_memory_kb / 1024.), - qsrs.execution_type_desc, qsrs.first_execution_time, qsrs.last_execution_time, qsrs.count_executions, - (qsrs.avg_duration / 1000.), - (qsrs.last_duration / 1000.), - (qsrs.min_duration / 1000.), - (qsrs.max_duration / 1000.), - (qsrs.avg_cpu_time / 1000.), - (qsrs.last_cpu_time / 1000.), - (qsrs.min_cpu_time / 1000.), - (qsrs.max_cpu_time / 1000.), - ((qsrs.avg_logical_io_reads * 8 ) / 1024.), - ((qsrs.last_logical_io_reads * 8 ) / 1024.), - ((qsrs.min_logical_io_reads * 8 ) / 1024.), - ((qsrs.max_logical_io_reads * 8 ) / 1024.), - ((qsrs.avg_logical_io_writes * 8 ) / 1024.), - ((qsrs.last_logical_io_writes * 8 ) / 1024.), - ((qsrs.min_logical_io_writes * 8 ) / 1024.), - ((qsrs.max_logical_io_writes * 8 ) / 1024.), - ((qsrs.avg_physical_io_reads * 8 ) / 1024.), - ((qsrs.last_physical_io_reads * 8 ) / 1024.), - ((qsrs.min_physical_io_reads * 8 ) / 1024.), - ((qsrs.max_physical_io_reads * 8 ) / 1024.), - (qsrs.avg_clr_time / 1000.), - (qsrs.last_clr_time / 1000.), - (qsrs.min_clr_time / 1000.), - (qsrs.max_clr_time / 1000.), - qsrs.avg_dop, qsrs.last_dop, qsrs.min_dop, qsrs.max_dop, - ((qsrs.avg_query_max_used_memory * 8 ) / 1024.), - ((qsrs.last_query_max_used_memory * 8 ) / 1024.), - ((qsrs.min_query_max_used_memory * 8 ) / 1024.), - ((qsrs.max_query_max_used_memory * 8 ) / 1024.), - qsrs.avg_rowcount, qsrs.last_rowcount, qsrs.min_rowcount, qsrs.max_rowcount,'; - - IF @new_columns = 1 - BEGIN - SET @sql_select += N' - qsrs.avg_num_physical_io_reads, qsrs.last_num_physical_io_reads, qsrs.min_num_physical_io_reads, qsrs.max_num_physical_io_reads, - (qsrs.avg_log_bytes_used / 100000000), - (qsrs.last_log_bytes_used / 100000000), - (qsrs.min_log_bytes_used / 100000000), - (qsrs.max_log_bytes_used / 100000000), - ((qsrs.avg_tempdb_space_used * 8 ) / 1024.), - ((qsrs.last_tempdb_space_used * 8 ) / 1024.), - ((qsrs.min_tempdb_space_used * 8 ) / 1024.), - ((qsrs.max_tempdb_space_used * 8 ) / 1024.) - '; - END; - IF @new_columns = 0 - BEGIN - SET @sql_select += N' - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL - '; - END; -SET @sql_select += -N'FROM #working_plans AS wp -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq -ON wp.query_id = qsq.query_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs -ON qsrs.plan_id = wp.plan_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp -ON qsp.plan_id = wp.plan_id -AND qsp.query_id = wp.query_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt -ON qsqt.query_text_id = qsq.query_text_id -WHERE 1 = 1 - AND qsq.is_internal_query = 0 - AND qsp.query_plan IS NOT NULL - '; -SET @sql_select += @sql_where; +IF @ExpertMode > 0 +BEGIN +RAISERROR('Checking for row level security only', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +UPDATE b +SET b.is_row_level = 1 +FROM #working_warnings b +JOIN #statements s +ON s.query_hash = b.query_hash +WHERE s.statement.exist('/p:StmtSimple/@SecurityPolicyApplied[.="true"]') = 1 +OPTION (RECOMPILE); +END; -SET @sql_select += N'OPTION (RECOMPILE); - '; -IF @Debug = 1 - PRINT @sql_select; +IF @ExpertMode > 0 +BEGIN +RAISERROR('Checking for wonky Index Spools', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES ( + 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) +, selects +AS ( SELECT s.plan_id, s.query_id + FROM #statements AS s + WHERE s.statement.exist('/p:StmtSimple/@StatementType[.="SELECT"]') = 1 ) +, spools +AS ( SELECT DISTINCT r.plan_id, + r.query_id, + c.n.value('@EstimateRows', 'FLOAT') AS estimated_rows, + c.n.value('@EstimateIO', 'FLOAT') AS estimated_io, + c.n.value('@EstimateCPU', 'FLOAT') AS estimated_cpu, + c.n.value('@EstimateRebinds', 'FLOAT') AS estimated_rebinds +FROM #relop AS r +JOIN selects AS s +ON s.plan_id = r.plan_id + AND s.query_id = r.query_id +CROSS APPLY r.relop.nodes('/p:RelOp') AS c(n) +WHERE r.relop.exist('/p:RelOp[@PhysicalOp="Index Spool" and @LogicalOp="Eager Spool"]') = 1 +) +UPDATE ww + SET ww.index_spool_rows = sp.estimated_rows, + ww.index_spool_cost = ((sp.estimated_io * sp.estimated_cpu) * CASE WHEN sp.estimated_rebinds < 1 THEN 1 ELSE sp.estimated_rebinds END) -IF @sql_select IS NULL - BEGIN - RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; - RETURN; - END; +FROM #working_warnings ww +JOIN spools sp +ON ww.plan_id = sp.plan_id +AND ww.query_id = sp.query_id +OPTION (RECOMPILE); +END; -INSERT #working_metrics WITH (TABLOCK) - ( database_name, plan_id, query_id, - proc_or_function_name, - batch_sql_handle, query_hash, query_parameterization_type_desc, count_compiles, - avg_compile_duration, last_compile_duration, avg_bind_duration, last_bind_duration, avg_bind_cpu_time, last_bind_cpu_time, avg_optimize_duration, - last_optimize_duration, avg_optimize_cpu_time, last_optimize_cpu_time, avg_compile_memory_kb, last_compile_memory_kb, execution_type_desc, - first_execution_time, last_execution_time, count_executions, avg_duration, last_duration, min_duration, max_duration, avg_cpu_time, last_cpu_time, - min_cpu_time, max_cpu_time, avg_logical_io_reads, last_logical_io_reads, min_logical_io_reads, max_logical_io_reads, avg_logical_io_writes, - last_logical_io_writes, min_logical_io_writes, max_logical_io_writes, avg_physical_io_reads, last_physical_io_reads, min_physical_io_reads, - max_physical_io_reads, avg_clr_time, last_clr_time, min_clr_time, max_clr_time, avg_dop, last_dop, min_dop, max_dop, avg_query_max_used_memory, - last_query_max_used_memory, min_query_max_used_memory, max_query_max_used_memory, avg_rowcount, last_rowcount, min_rowcount, max_rowcount, - /* 2017 only columns */ - avg_num_physical_io_reads, last_num_physical_io_reads, min_num_physical_io_reads, max_num_physical_io_reads, - avg_log_bytes_used, last_log_bytes_used, min_log_bytes_used, max_log_bytes_used, - avg_tempdb_space_used, last_tempdb_space_used, min_tempdb_space_used, max_tempdb_space_used ) -EXEC sys.sp_executesql @stmt = @sql_select, - @params = @sp_params, - @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; +IF (PARSENAME(CONVERT(VARCHAR(128), SERVERPROPERTY ('PRODUCTVERSION')), 4)) >= 14 +OR ((PARSENAME(CONVERT(VARCHAR(128), SERVERPROPERTY ('PRODUCTVERSION')), 4)) = 13 + AND PARSENAME(CONVERT(VARCHAR(128), SERVERPROPERTY ('PRODUCTVERSION')), 2) >= 5026) +BEGIN +RAISERROR(N'Beginning 2017 and 2016 SP2 specfic checks', 0, 1) WITH NOWAIT; -/*This just helps us classify our queries*/ -UPDATE #working_metrics -SET proc_or_function_name = N'Statement' -WHERE proc_or_function_name IS NULL -OPTION(RECOMPILE); +IF @ExpertMode > 0 +BEGIN +RAISERROR('Gathering stats information', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +INSERT #stats_agg WITH (TABLOCK) + (sql_handle, last_update, modification_count, sampling_percent, [statistics], [table], [schema], [database]) +SELECT qp.sql_handle, + x.c.value('@LastUpdate', 'DATETIME2(7)') AS LastUpdate, + x.c.value('@ModificationCount', 'BIGINT') AS ModificationCount, + x.c.value('@SamplingPercent', 'FLOAT') AS SamplingPercent, + x.c.value('@Statistics', 'NVARCHAR(258)') AS [Statistics], + x.c.value('@Table', 'NVARCHAR(258)') AS [Table], + x.c.value('@Schema', 'NVARCHAR(258)') AS [Schema], + x.c.value('@Database', 'NVARCHAR(258)') AS [Database] +FROM #query_plan AS qp +CROSS APPLY qp.query_plan.nodes('//p:OptimizerStatsUsage/p:StatisticsInfo') x (c) +OPTION (RECOMPILE); -SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; -SET @sql_select += N' - WITH patterns AS ( - SELECT query_id, planid_path = STUFF((SELECT DISTINCT N'', '' + RTRIM(qsp2.plan_id) - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp2 - WHERE qsp.query_id = qsp2.query_id - FOR XML PATH(N''''), TYPE).value(N''.[1]'', N''NVARCHAR(MAX)''), 1, 2, N'''') - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp - ) - UPDATE wm - SET wm.query_id_all_plan_ids = patterns.planid_path - FROM #working_metrics AS wm - JOIN patterns - ON wm.query_id = patterns.query_id - OPTION (RECOMPILE); -' -EXEC sys.sp_executesql @stmt = @sql_select; +RAISERROR('Checking for stale stats', 0, 1) WITH NOWAIT; +WITH stale_stats AS ( + SELECT sa.sql_handle + FROM #stats_agg AS sa + GROUP BY sa.sql_handle + HAVING MAX(sa.last_update) <= DATEADD(DAY, -7, SYSDATETIME()) + AND AVG(sa.modification_count) >= 100000 +) +UPDATE b +SET b.stale_stats = 1 +FROM #working_warnings AS b +JOIN stale_stats os +ON b.sql_handle = os.sql_handle +OPTION (RECOMPILE); +END; -/* -This gathers data for the #working_plan_text table -*/ - - -RAISERROR(N'Gathering working plans', 0, 1) WITH NOWAIT; - -SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; -SET @sql_select += N' -SELECT ' + QUOTENAME(@DatabaseName, '''') + N' AS database_name, wp.plan_id, wp.query_id, - qsp.plan_group_id, qsp.engine_version, qsp.compatibility_level, qsp.query_plan_hash, TRY_CONVERT(XML, qsp.query_plan), qsp.is_online_index_plan, qsp.is_trivial_plan, - qsp.is_parallel_plan, qsp.is_forced_plan, qsp.is_natively_compiled, qsp.force_failure_count, qsp.last_force_failure_reason_desc, qsp.count_compiles, - qsp.initial_compile_start_time, qsp.last_compile_start_time, qsp.last_execution_time, - (qsp.avg_compile_duration / 1000.), - (qsp.last_compile_duration / 1000.), - qsqt.query_sql_text, qsqt.statement_sql_handle, qsqt.is_part_of_encrypted_module, qsqt.has_restricted_text -FROM #working_plans AS wp -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp -ON qsp.plan_id = wp.plan_id - AND qsp.query_id = wp.query_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq -ON wp.query_id = qsq.query_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt -ON qsqt.query_text_id = qsq.query_text_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs -ON qsrs.plan_id = wp.plan_id -WHERE 1 = 1 - AND qsq.is_internal_query = 0 - AND qsp.query_plan IS NOT NULL - '; - -SET @sql_select += @sql_where; - -SET @sql_select += N'OPTION (RECOMPILE); - '; -IF @Debug = 1 - PRINT @sql_select; +IF (PARSENAME(CONVERT(VARCHAR(128), SERVERPROPERTY ('PRODUCTVERSION')), 4)) >= 14 + AND @ExpertMode > 0 +BEGIN +RAISERROR(N'Checking for Adaptive Joins', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p), +aj AS ( + SELECT r.sql_handle + FROM #relop AS r + CROSS APPLY r.relop.nodes('//p:RelOp') x(c) + WHERE x.c.exist('@IsAdaptive[.=1]') = 1 +) +UPDATE b +SET b.is_adaptive = 1 +FROM #working_warnings AS b +JOIN aj +ON b.sql_handle = aj.sql_handle +OPTION (RECOMPILE); +END; -IF @sql_select IS NULL - BEGIN - RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; - RETURN; - END; -INSERT #working_plan_text WITH (TABLOCK) - ( database_name, plan_id, query_id, - plan_group_id, engine_version, compatibility_level, query_plan_hash, query_plan_xml, is_online_index_plan, is_trivial_plan, - is_parallel_plan, is_forced_plan, is_natively_compiled, force_failure_count, last_force_failure_reason_desc, count_compiles, - initial_compile_start_time, last_compile_start_time, last_execution_time, avg_compile_duration, last_compile_duration, - query_sql_text, statement_sql_handle, is_part_of_encrypted_module, has_restricted_text ) +IF @ExpertMode > 0 +BEGIN; +RAISERROR(N'Checking for Row Goals', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p), +row_goals AS( +SELECT qs.query_hash +FROM #relop qs +WHERE relop.value('sum(/p:RelOp/@EstimateRowsWithoutRowGoal)', 'float') > 0 +) +UPDATE b +SET b.is_row_goal = 1 +FROM #working_warnings b +JOIN row_goals +ON b.query_hash = row_goals.query_hash +OPTION (RECOMPILE); +END; -EXEC sys.sp_executesql @stmt = @sql_select, - @params = @sp_params, - @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; +END; +RAISERROR(N'Performing query level checks', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +UPDATE b +SET b.missing_index_count = query_plan.value('count(//p:QueryPlan/p:MissingIndexes/p:MissingIndexGroup)', 'int') , + b.unmatched_index_count = CASE WHEN is_trivial <> 1 THEN query_plan.value('count(//p:QueryPlan/p:UnmatchedIndexes/p:Parameterization/p:Object)', 'int') END +FROM #query_plan qp +JOIN #working_warnings AS b +ON b.query_hash = qp.query_hash +OPTION (RECOMPILE); -/* -This gets us context settings for our queries and adds it to the #working_plan_text table -*/ -RAISERROR(N'Gathering context settings', 0, 1) WITH NOWAIT; +RAISERROR(N'Trace flag checks', 0, 1) WITH NOWAIT; +;WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +, tf_pretty AS ( +SELECT qp.sql_handle, + q.n.value('@Value', 'INT') AS trace_flag, + q.n.value('@Scope', 'VARCHAR(10)') AS scope +FROM #query_plan qp +CROSS APPLY qp.query_plan.nodes('/p:QueryPlan/p:TraceFlags/p:TraceFlag') AS q(n) +) +INSERT #trace_flags WITH (TABLOCK) + (sql_handle, global_trace_flags, session_trace_flags ) +SELECT DISTINCT tf1.sql_handle , + STUFF(( + SELECT DISTINCT N', ' + CONVERT(NVARCHAR(5), tf2.trace_flag) + FROM tf_pretty AS tf2 + WHERE tf1.sql_handle = tf2.sql_handle + AND tf2.scope = 'Global' + FOR XML PATH(N'')), 1, 2, N'' + ) AS global_trace_flags, + STUFF(( + SELECT DISTINCT N', ' + CONVERT(NVARCHAR(5), tf2.trace_flag) + FROM tf_pretty AS tf2 + WHERE tf1.sql_handle = tf2.sql_handle + AND tf2.scope = 'Session' + FOR XML PATH(N'')), 1, 2, N'' + ) AS session_trace_flags +FROM tf_pretty AS tf1 +OPTION (RECOMPILE); -SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; -SET @sql_select += N' -UPDATE wp -SET wp.context_settings = SUBSTRING( - CASE WHEN (CAST(qcs.set_options AS INT) & 1 = 1) THEN '', ANSI_PADDING'' ELSE '''' END + - CASE WHEN (CAST(qcs.set_options AS INT) & 8 = 8) THEN '', CONCAT_NULL_YIELDS_NULL'' ELSE '''' END + - CASE WHEN (CAST(qcs.set_options AS INT) & 16 = 16) THEN '', ANSI_WARNINGS'' ELSE '''' END + - CASE WHEN (CAST(qcs.set_options AS INT) & 32 = 32) THEN '', ANSI_NULLS'' ELSE '''' END + - CASE WHEN (CAST(qcs.set_options AS INT) & 64 = 64) THEN '', QUOTED_IDENTIFIER'' ELSE '''' END + - CASE WHEN (CAST(qcs.set_options AS INT) & 4096 = 4096) THEN '', ARITH_ABORT'' ELSE '''' END + - CASE WHEN (CAST(qcs.set_options AS INT) & 8192 = 8192) THEN '', NUMERIC_ROUNDABORT'' ELSE '''' END - , 2, 200000) -FROM #working_plan_text wp -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq -ON wp.query_id = qsq.query_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_context_settings AS qcs -ON qcs.context_settings_id = qsq.context_settings_id +UPDATE b +SET b.trace_flags_session = tf.session_trace_flags +FROM #working_warnings AS b +JOIN #trace_flags tf +ON tf.sql_handle = b.sql_handle OPTION (RECOMPILE); -'; -IF @Debug = 1 - PRINT @sql_select; -IF @sql_select IS NULL - BEGIN - RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; - RETURN; - END; +RAISERROR(N'Checking for MSTVFs', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +UPDATE b +SET b.is_mstvf = 1 +FROM #relop AS r +JOIN #working_warnings AS b +ON b.sql_handle = r.sql_handle +WHERE r.relop.exist('/p:RelOp[(@EstimateRows="100" or @EstimateRows="1") and @LogicalOp="Table-valued function"]') = 1 +OPTION (RECOMPILE); -EXEC sys.sp_executesql @stmt = @sql_select; +IF @ExpertMode > 0 +BEGIN +RAISERROR(N'Checking for many to many merge joins', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +UPDATE b +SET b.is_mm_join = 1 +FROM #relop AS r +JOIN #working_warnings AS b +ON b.sql_handle = r.sql_handle +WHERE r.relop.exist('/p:RelOp/p:Merge/@ManyToMany[.="1"]') = 1 +OPTION (RECOMPILE); +END; -/*This adds the patterns we found from each interval to the #working_plan_text table*/ +IF @ExpertMode > 0 +BEGIN +RAISERROR(N'Is Paul White Electric?', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p), +is_paul_white_electric AS ( +SELECT 1 AS [is_paul_white_electric], +r.sql_handle +FROM #relop AS r +CROSS APPLY r.relop.nodes('//p:RelOp') c(n) +WHERE c.n.exist('@PhysicalOp[.="Switch"]') = 1 +) +UPDATE b +SET b.is_paul_white_electric = ipwe.is_paul_white_electric +FROM #working_warnings AS b +JOIN is_paul_white_electric ipwe +ON ipwe.sql_handle = b.sql_handle +OPTION (RECOMPILE); +END; -RAISERROR(N'Add patterns to working plans', 0, 1) WITH NOWAIT; -UPDATE wpt -SET wpt.pattern = wp.pattern -FROM #working_plans AS wp -JOIN #working_plan_text AS wpt -ON wpt.plan_id = wp.plan_id -AND wpt.query_id = wp.query_id -OPTION (RECOMPILE); -/*This cleans up query text a bit*/ +RAISERROR(N'Checking for non-sargable predicates', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES ( 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) +, nsarg + AS ( SELECT r.query_hash, 1 AS fn, 0 AS jo, 0 AS lk + FROM #relop AS r + CROSS APPLY r.relop.nodes('/p:RelOp/p:IndexScan/p:Predicate/p:ScalarOperator/p:Compare/p:ScalarOperator') AS ca(x) + WHERE ( ca.x.exist('//p:ScalarOperator/p:Intrinsic/@FunctionName') = 1 + OR ca.x.exist('//p:ScalarOperator/p:IF') = 1 ) + UNION ALL + SELECT r.query_hash, 0 AS fn, 1 AS jo, 0 AS lk + FROM #relop AS r + CROSS APPLY r.relop.nodes('/p:RelOp//p:ScalarOperator') AS ca(x) + WHERE r.relop.exist('/p:RelOp[contains(@LogicalOp, "Join")]') = 1 + AND ca.x.exist('//p:ScalarOperator[contains(@ScalarString, "Expr")]') = 1 + UNION ALL + SELECT r.query_hash, 0 AS fn, 0 AS jo, 1 AS lk + FROM #relop AS r + CROSS APPLY r.relop.nodes('/p:RelOp/p:IndexScan/p:Predicate/p:ScalarOperator') AS ca(x) + CROSS APPLY ca.x.nodes('//p:Const') AS co(x) + WHERE ca.x.exist('//p:ScalarOperator/p:Intrinsic/@FunctionName[.="like"]') = 1 + AND ( ( co.x.value('substring(@ConstValue, 1, 1)', 'VARCHAR(100)') <> 'N' + AND co.x.value('substring(@ConstValue, 2, 1)', 'VARCHAR(100)') = '%' ) + OR ( co.x.value('substring(@ConstValue, 1, 1)', 'VARCHAR(100)') = 'N' + AND co.x.value('substring(@ConstValue, 3, 1)', 'VARCHAR(100)') = '%' ))), + d_nsarg + AS ( SELECT DISTINCT + nsarg.query_hash + FROM nsarg + WHERE nsarg.fn = 1 + OR nsarg.jo = 1 + OR nsarg.lk = 1 ) +UPDATE b +SET b.is_nonsargable = 1 +FROM d_nsarg AS d +JOIN #working_warnings AS b + ON b.query_hash = d.query_hash +OPTION ( RECOMPILE ); -RAISERROR(N'Clean awkward characters from query text', 0, 1) WITH NOWAIT; -UPDATE b -SET b.query_sql_text = REPLACE(REPLACE(REPLACE(b.query_sql_text, @cr, ' '), @lf, ' '), @tab, ' ') -FROM #working_plan_text AS b -OPTION (RECOMPILE); + RAISERROR(N'Getting information about implicit conversions and stored proc parameters', 0, 1) WITH NOWAIT; + RAISERROR(N'Getting variable info', 0, 1) WITH NOWAIT; + WITH XMLNAMESPACES ( 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) + INSERT #variable_info ( query_hash, sql_handle, proc_name, variable_name, variable_datatype, compile_time_value ) + SELECT DISTINCT + qp.query_hash, + qp.sql_handle, + b.proc_or_function_name AS proc_name, + q.n.value('@Column', 'NVARCHAR(258)') AS variable_name, + q.n.value('@ParameterDataType', 'NVARCHAR(258)') AS variable_datatype, + q.n.value('@ParameterCompiledValue', 'NVARCHAR(258)') AS compile_time_value + FROM #query_plan AS qp + JOIN #working_warnings AS b + ON (b.query_hash = qp.query_hash AND b.proc_or_function_name = 'adhoc') + OR (b.sql_handle = qp.sql_handle AND b.proc_or_function_name <> 'adhoc') + CROSS APPLY qp.query_plan.nodes('//p:QueryPlan/p:ParameterList/p:ColumnReference') AS q(n) + OPTION (RECOMPILE); -/*This populates #working_wait_stats when available*/ + RAISERROR(N'Getting conversion info', 0, 1) WITH NOWAIT; + WITH XMLNAMESPACES ( 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) + INSERT #conversion_info ( query_hash, sql_handle, proc_name, expression ) + SELECT DISTINCT + qp.query_hash, + qp.sql_handle, + b.proc_or_function_name AS proc_name, + qq.c.value('@Expression', 'NVARCHAR(4000)') AS expression + FROM #query_plan AS qp + JOIN #working_warnings AS b + ON (b.query_hash = qp.query_hash AND b.proc_or_function_name = 'adhoc') + OR (b.sql_handle = qp.sql_handle AND b.proc_or_function_name <> 'adhoc') + CROSS APPLY qp.query_plan.nodes('//p:QueryPlan/p:Warnings/p:PlanAffectingConvert') AS qq(c) + WHERE qq.c.exist('@ConvertIssue[.="Seek Plan"]') = 1 + AND b.implicit_conversions = 1 + OPTION (RECOMPILE); -IF @waitstats = 1 + RAISERROR(N'Parsing conversion info', 0, 1) WITH NOWAIT; + INSERT #stored_proc_info ( sql_handle, query_hash, proc_name, variable_name, variable_datatype, converted_column_name, column_name, converted_to, compile_time_value ) + SELECT ci.sql_handle, + ci.query_hash, + ci.proc_name, + CASE WHEN ci.at_charindex > 0 + AND ci.bracket_charindex > 0 + THEN SUBSTRING(ci.expression, ci.at_charindex, ci.bracket_charindex) + ELSE N'**no_variable**' + END AS variable_name, + N'**no_variable**' AS variable_datatype, + CASE WHEN ci.at_charindex = 0 + AND ci.comma_charindex > 0 + AND ci.second_comma_charindex > 0 + THEN SUBSTRING(ci.expression, ci.comma_charindex, ci.second_comma_charindex) + ELSE N'**no_column**' + END AS converted_column_name, + CASE WHEN ci.at_charindex = 0 + AND ci.equal_charindex > 0 + AND ci.convert_implicit_charindex = 0 + THEN SUBSTRING(ci.expression, ci.equal_charindex, 4000) + WHEN ci.at_charindex = 0 + AND (ci.equal_charindex -1) > 0 + AND ci.convert_implicit_charindex > 0 + THEN SUBSTRING(ci.expression, 0, ci.equal_charindex -1) + WHEN ci.at_charindex > 0 + AND ci.comma_charindex > 0 + AND ci.second_comma_charindex > 0 + THEN SUBSTRING(ci.expression, ci.comma_charindex, ci.second_comma_charindex) + ELSE N'**no_column **' + END AS column_name, + CASE WHEN ci.paren_charindex > 0 + 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 + 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 + FROM #conversion_info AS ci + OPTION (RECOMPILE); - BEGIN - - RAISERROR(N'Collecting wait stats info', 0, 1) WITH NOWAIT; - - - SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; - SET @sql_select += N' - SELECT qws.plan_id, - qws.wait_category, - qws.wait_category_desc, - SUM(qws.total_query_wait_time_ms) AS total_query_wait_time_ms, - SUM(qws.avg_query_wait_time_ms) AS avg_query_wait_time_ms, - SUM(qws.last_query_wait_time_ms) AS last_query_wait_time_ms, - SUM(qws.min_query_wait_time_ms) AS min_query_wait_time_ms, - SUM(qws.max_query_wait_time_ms) AS max_query_wait_time_ms - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_wait_stats qws - JOIN #working_plans AS wp - ON qws.plan_id = wp.plan_id - GROUP BY qws.plan_id, qws.wait_category, qws.wait_category_desc - HAVING SUM(qws.min_query_wait_time_ms) >= 5 + RAISERROR(N'Updating variables inserted procs', 0, 1) WITH NOWAIT; + UPDATE sp + SET sp.variable_datatype = vi.variable_datatype, + sp.compile_time_value = vi.compile_time_value + FROM #stored_proc_info AS sp + JOIN #variable_info AS vi + ON (sp.proc_name = 'adhoc' AND sp.query_hash = vi.query_hash) + OR (sp.proc_name <> 'adhoc' AND sp.sql_handle = vi.sql_handle) + AND sp.variable_name = vi.variable_name OPTION (RECOMPILE); - '; - - IF @Debug = 1 - PRINT @sql_select; - IF @sql_select IS NULL - BEGIN - RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; - RETURN; - END; - INSERT #working_wait_stats WITH (TABLOCK) - ( plan_id, wait_category, wait_category_desc, total_query_wait_time_ms, avg_query_wait_time_ms, last_query_wait_time_ms, min_query_wait_time_ms, max_query_wait_time_ms ) + RAISERROR(N'Inserting variables for other procs', 0, 1) WITH NOWAIT; + INSERT #stored_proc_info + ( sql_handle, query_hash, variable_name, variable_datatype, compile_time_value, proc_name ) + SELECT vi.sql_handle, vi.query_hash, vi.variable_name, vi.variable_datatype, vi.compile_time_value, vi.proc_name + FROM #variable_info AS vi + WHERE NOT EXISTS + ( + SELECT * + FROM #stored_proc_info AS sp + WHERE (sp.proc_name = 'adhoc' AND sp.query_hash = vi.query_hash) + OR (sp.proc_name <> 'adhoc' AND sp.sql_handle = vi.sql_handle) + ) + OPTION (RECOMPILE); - EXEC sys.sp_executesql @stmt = @sql_select; - - - /*This updates #working_plan_text with the top three waits from the wait stats DMV*/ - - RAISERROR(N'Update working_plan_text with top three waits', 0, 1) WITH NOWAIT; - - - UPDATE wpt - SET wpt.top_three_waits = x.top_three_waits - FROM #working_plan_text AS wpt - JOIN ( - SELECT wws.plan_id, - top_three_waits = STUFF((SELECT TOP 3 N', ' + wws2.wait_category_desc + N' (' + CONVERT(NVARCHAR(20), SUM(CONVERT(BIGINT, wws2.avg_query_wait_time_ms))) + N' ms) ' - FROM #working_wait_stats AS wws2 - WHERE wws.plan_id = wws2.plan_id - GROUP BY wws2.wait_category_desc - ORDER BY SUM(wws2.avg_query_wait_time_ms) DESC - FOR XML PATH(N''), TYPE).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 2, N'') - FROM #working_wait_stats AS wws - GROUP BY wws.plan_id - ) AS x - ON x.plan_id = wpt.plan_id + RAISERROR(N'Updating procs', 0, 1) WITH NOWAIT; + UPDATE s + SET s.variable_datatype = CASE WHEN s.variable_datatype LIKE '%(%)%' + THEN LEFT(s.variable_datatype, CHARINDEX('(', s.variable_datatype) - 1) + ELSE s.variable_datatype + END, + s.converted_to = CASE WHEN s.converted_to LIKE '%(%)%' + THEN LEFT(s.converted_to, CHARINDEX('(', s.converted_to) - 1) + ELSE s.converted_to + END, + s.compile_time_value = CASE WHEN s.compile_time_value LIKE '%(%)%' + THEN SUBSTRING(s.compile_time_value, + CHARINDEX('(', s.compile_time_value) + 1, + CHARINDEX(')', s.compile_time_value) - 1 - CHARINDEX('(', s.compile_time_value) + ) + WHEN variable_datatype NOT IN ('bit', 'tinyint', 'smallint', 'int', 'bigint') + AND s.variable_datatype NOT LIKE '%binary%' + AND s.compile_time_value NOT LIKE 'N''%''' + AND s.compile_time_value NOT LIKE '''%''' + AND s.compile_time_value <> s.column_name + AND s.compile_time_value <> '**idk_man**' + THEN QUOTENAME(compile_time_value, '''') + ELSE s.compile_time_value + END + FROM #stored_proc_info AS s OPTION (RECOMPILE); -END; - -/*End wait stats population*/ - -UPDATE #working_plan_text -SET top_three_waits = CASE - WHEN @waitstats = 0 - THEN N'The query store waits stats DMV is not available' - ELSE N'No Significant waits detected!' - END -WHERE top_three_waits IS NULL -OPTION(RECOMPILE); - -END; -END TRY -BEGIN CATCH - RAISERROR (N'Failure populating temp tables.', 0,1) WITH NOWAIT; - - IF @sql_select IS NOT NULL - BEGIN - SET @msg = N'Last @sql_select: ' + @sql_select; - RAISERROR(@msg, 0, 1) WITH NOWAIT; - END; - - SELECT @msg = @DatabaseName + N' database failed to process. ' + ERROR_MESSAGE(), @error_severity = ERROR_SEVERITY(), @error_state = ERROR_STATE(); - RAISERROR (@msg, @error_severity, @error_state) WITH NOWAIT; - - - WHILE @@TRANCOUNT > 0 - ROLLBACK; - - RETURN; -END CATCH; - -IF (@SkipXML = 0) -BEGIN TRY -BEGIN - -/* -This sets up the #working_warnings table with the IDs we're interested in so we can tie warnings back to them -*/ - -RAISERROR(N'Populate working warnings table with gathered plans', 0, 1) WITH NOWAIT; + + RAISERROR(N'Updating SET options', 0, 1) WITH NOWAIT; + WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) + UPDATE s + SET set_options = set_options.ansi_set_options + FROM #stored_proc_info AS s + JOIN ( + SELECT x.sql_handle, + N'SET ANSI_NULLS ' + CASE WHEN [ANSI_NULLS] = 'true' THEN N'ON ' ELSE N'OFF ' END + NCHAR(10) + + N'SET ANSI_PADDING ' + CASE WHEN [ANSI_PADDING] = 'true' THEN N'ON ' ELSE N'OFF ' END + NCHAR(10) + + N'SET ANSI_WARNINGS ' + CASE WHEN [ANSI_WARNINGS] = 'true' THEN N'ON ' ELSE N'OFF ' END + NCHAR(10) + + N'SET ARITHABORT ' + CASE WHEN [ARITHABORT] = 'true' THEN N'ON ' ELSE N' OFF ' END + NCHAR(10) + + N'SET CONCAT_NULL_YIELDS_NULL ' + CASE WHEN [CONCAT_NULL_YIELDS_NULL] = 'true' THEN N'ON ' ELSE N'OFF ' END + NCHAR(10) + + N'SET NUMERIC_ROUNDABORT ' + CASE WHEN [NUMERIC_ROUNDABORT] = 'true' THEN N'ON ' ELSE N'OFF ' END + NCHAR(10) + + N'SET QUOTED_IDENTIFIER ' + CASE WHEN [QUOTED_IDENTIFIER] = 'true' THEN N'ON ' ELSE N'OFF ' + NCHAR(10) END AS [ansi_set_options] + FROM ( + SELECT + s.sql_handle, + so.o.value('@ANSI_NULLS', 'NVARCHAR(20)') AS [ANSI_NULLS], + so.o.value('@ANSI_PADDING', 'NVARCHAR(20)') AS [ANSI_PADDING], + so.o.value('@ANSI_WARNINGS', 'NVARCHAR(20)') AS [ANSI_WARNINGS], + so.o.value('@ARITHABORT', 'NVARCHAR(20)') AS [ARITHABORT], + so.o.value('@CONCAT_NULL_YIELDS_NULL', 'NVARCHAR(20)') AS [CONCAT_NULL_YIELDS_NULL], + so.o.value('@NUMERIC_ROUNDABORT', 'NVARCHAR(20)') AS [NUMERIC_ROUNDABORT], + so.o.value('@QUOTED_IDENTIFIER', 'NVARCHAR(20)') AS [QUOTED_IDENTIFIER] + FROM #statements AS s + CROSS APPLY s.statement.nodes('//p:StatementSetOptions') AS so(o) + ) AS x + ) AS set_options ON set_options.sql_handle = s.sql_handle + OPTION(RECOMPILE); -SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; -SET @sql_select += N' -SELECT DISTINCT wp.plan_id, wp.query_id, qsq.query_hash, qsqt.statement_sql_handle -FROM #working_plans AS wp -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp -ON qsp.plan_id = wp.plan_id - AND qsp.query_id = wp.query_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq -ON wp.query_id = qsq.query_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt -ON qsqt.query_text_id = qsq.query_text_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs -ON qsrs.plan_id = wp.plan_id -WHERE 1 = 1 - AND qsq.is_internal_query = 0 - AND qsp.query_plan IS NOT NULL - '; - -SET @sql_select += @sql_where; - -SET @sql_select += N'OPTION (RECOMPILE); - '; - -IF @Debug = 1 - PRINT @sql_select; - -IF @sql_select IS NULL - BEGIN - RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; - RETURN; - END; - -INSERT #working_warnings WITH (TABLOCK) - ( plan_id, query_id, query_hash, sql_handle ) -EXEC sys.sp_executesql @stmt = @sql_select, - @params = @sp_params, - @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; - -/* -This looks for queries in the query stores that we picked up from an internal that have multiple plans in cache - -This and several of the following queries all replaced XML parsing to find plan attributes. Sweet. - -Thanks, Query Store -*/ - -RAISERROR(N'Populating object name in #working_warnings', 0, 1) WITH NOWAIT; -UPDATE w -SET w.proc_or_function_name = ISNULL(wm.proc_or_function_name, N'Statement') -FROM #working_warnings AS w -JOIN #working_metrics AS wm -ON w.plan_id = wm.plan_id - AND w.query_id = wm.query_id -OPTION (RECOMPILE); + RAISERROR(N'Updating conversion XML', 0, 1) WITH NOWAIT; + WITH precheck AS ( + SELECT spi.sql_handle, + spi.proc_name, + (SELECT CASE WHEN spi.proc_name <> 'Statement' + THEN N'The stored procedure ' + spi.proc_name + ELSE N'This ad hoc statement' + END + + N' had the following implicit conversions: ' + + CHAR(10) + + STUFF(( + SELECT DISTINCT + @cr + @lf + + CASE WHEN spi2.variable_name <> N'**no_variable**' + THEN N'The variable ' + WHEN spi2.variable_name = N'**no_variable**' AND (spi2.column_name = spi2.converted_column_name OR spi2.column_name LIKE '%CONVERT_IMPLICIT%') + THEN N'The compiled value ' + WHEN spi2.column_name LIKE '%Expr%' + THEN 'The expression ' + ELSE N'The column ' + END + + CASE WHEN spi2.variable_name <> N'**no_variable**' + THEN spi2.variable_name + WHEN spi2.variable_name = N'**no_variable**' AND (spi2.column_name = spi2.converted_column_name OR spi2.column_name LIKE '%CONVERT_IMPLICIT%') + THEN spi2.compile_time_value + + ELSE spi2.column_name + END + + N' has a data type of ' + + CASE WHEN spi2.variable_datatype = N'**no_variable**' THEN spi2.converted_to + ELSE spi2.variable_datatype + END + + N' which caused implicit conversion on the column ' + + CASE WHEN spi2.column_name LIKE N'%CONVERT_IMPLICIT%' + THEN spi2.converted_column_name + WHEN spi2.column_name = N'**no_column**' + THEN spi2.converted_column_name + WHEN spi2.converted_column_name = N'**no_column**' + THEN spi2.column_name + WHEN spi2.column_name <> spi2.converted_column_name + THEN spi2.converted_column_name + ELSE spi2.column_name + END + + CASE WHEN spi2.variable_name = N'**no_variable**' AND (spi2.column_name = spi2.converted_column_name OR spi2.column_name LIKE '%CONVERT_IMPLICIT%') + THEN N'' + WHEN spi2.column_name LIKE '%Expr%' + THEN N'' + WHEN spi2.compile_time_value NOT IN ('**declared in proc**', '**idk_man**') + AND spi2.compile_time_value <> spi2.column_name + THEN ' with the value ' + RTRIM(spi2.compile_time_value) + ELSE N'' + END + + '.' + FROM #stored_proc_info AS spi2 + WHERE spi.sql_handle = spi2.sql_handle + FOR XML PATH(N''), TYPE).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 1, N'') + AS [processing-instruction(ClickMe)] FOR XML PATH(''), TYPE ) + AS implicit_conversion_info + FROM #stored_proc_info AS spi + GROUP BY spi.sql_handle, spi.proc_name + ) + UPDATE b + SET b.implicit_conversion_info = pk.implicit_conversion_info + FROM #working_warnings AS b + JOIN precheck AS pk + ON pk.sql_handle = b.sql_handle + OPTION (RECOMPILE); + RAISERROR(N'Updating cached parameter XML for procs', 0, 1) WITH NOWAIT; + WITH precheck AS ( + SELECT spi.sql_handle, + spi.proc_name, + (SELECT set_options + + @cr + @lf + + @cr + @lf + + N'EXEC ' + + spi.proc_name + + N' ' + + STUFF(( + SELECT DISTINCT N', ' + + CASE WHEN spi2.variable_name <> N'**no_variable**' AND spi2.compile_time_value <> N'**idk_man**' + THEN spi2.variable_name + N' = ' + ELSE @cr + @lf + N' We could not find any cached parameter values for this stored proc. ' + END + + CASE WHEN spi2.variable_name = N'**no_variable**' OR spi2.compile_time_value = N'**idk_man**' + THEN @cr + @lf + N' Possible reasons include declared variables inside the procedure, recompile hints, etc. ' + WHEN spi2.compile_time_value = N'NULL' + THEN spi2.compile_time_value + ELSE RTRIM(spi2.compile_time_value) + END + FROM #stored_proc_info AS spi2 + WHERE spi.sql_handle = spi2.sql_handle + AND spi2.proc_name <> N'Statement' + FOR XML PATH(N''), TYPE).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 1, N'') + AS [processing-instruction(ClickMe)] FOR XML PATH(''), TYPE ) + AS cached_execution_parameters + FROM #stored_proc_info AS spi + GROUP BY spi.sql_handle, spi.proc_name, set_options + ) + UPDATE b + SET b.cached_execution_parameters = pk.cached_execution_parameters + FROM #working_warnings AS b + JOIN precheck AS pk + ON pk.sql_handle = b.sql_handle + WHERE b.proc_or_function_name <> N'Statement' + OPTION (RECOMPILE); -RAISERROR(N'Checking for multiple plans', 0, 1) WITH NOWAIT; -SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; -SET @sql_select += N' -UPDATE ww -SET ww.plan_multiple_plans = 1 -FROM #working_warnings AS ww -JOIN -( -SELECT wp.query_id, COUNT(qsp.plan_id) AS plans -FROM #working_plans AS wp -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp -ON qsp.plan_id = wp.plan_id - AND qsp.query_id = wp.query_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq -ON wp.query_id = qsq.query_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt -ON qsqt.query_text_id = qsq.query_text_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs -ON qsrs.plan_id = wp.plan_id -WHERE 1 = 1 - AND qsq.is_internal_query = 0 - AND qsp.query_plan IS NOT NULL - '; + RAISERROR(N'Updating cached parameter XML for statements', 0, 1) WITH NOWAIT; + WITH precheck AS ( + SELECT spi.sql_handle, + spi.proc_name, + (SELECT + set_options + + @cr + @lf + + @cr + @lf + + N' See QueryText column for full query text' + + @cr + @lf + + @cr + @lf + + STUFF(( + SELECT DISTINCT N', ' + + CASE WHEN spi2.variable_name <> N'**no_variable**' AND spi2.compile_time_value <> N'**idk_man**' + THEN spi2.variable_name + N' = ' + ELSE + @cr + @lf + N' We could not find any cached parameter values for this stored proc. ' + END + + CASE WHEN spi2.variable_name = N'**no_variable**' OR spi2.compile_time_value = N'**idk_man**' + THEN + @cr + @lf + N' Possible reasons include declared variables inside the procedure, recompile hints, etc. ' + WHEN spi2.compile_time_value = N'NULL' + THEN spi2.compile_time_value + ELSE RTRIM(spi2.compile_time_value) + END + FROM #stored_proc_info AS spi2 + WHERE spi.sql_handle = spi2.sql_handle + AND spi2.proc_name = N'Statement' + AND spi2.variable_name NOT LIKE N'%msparam%' + FOR XML PATH(N''), TYPE).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 1, N'') + AS [processing-instruction(ClickMe)] FOR XML PATH(''), TYPE ) + AS cached_execution_parameters + FROM #stored_proc_info AS spi + GROUP BY spi.sql_handle, spi.proc_name, spi.set_options + ) + UPDATE b + SET b.cached_execution_parameters = pk.cached_execution_parameters + FROM #working_warnings AS b + JOIN precheck AS pk + ON pk.sql_handle = b.sql_handle + WHERE b.proc_or_function_name = N'Statement' + OPTION (RECOMPILE); -SET @sql_select += @sql_where; -SET @sql_select += -N'GROUP BY wp.query_id - HAVING COUNT(qsp.plan_id) > 1 -) AS x - ON ww.query_id = x.query_id +RAISERROR(N'Filling in implicit conversion info', 0, 1) WITH NOWAIT; +UPDATE b +SET b.implicit_conversion_info = CASE WHEN b.implicit_conversion_info IS NULL + OR CONVERT(NVARCHAR(MAX), b.implicit_conversion_info) = N'' + THEN N'' + ELSE b.implicit_conversion_info + END, + b.cached_execution_parameters = CASE WHEN b.cached_execution_parameters IS NULL + OR CONVERT(NVARCHAR(MAX), b.cached_execution_parameters) = N'' + THEN N'' + ELSE b.cached_execution_parameters + END +FROM #working_warnings AS b OPTION (RECOMPILE); -'; - -IF @Debug = 1 - PRINT @sql_select; - -IF @sql_select IS NULL - BEGIN - RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; - RETURN; - END; - -EXEC sys.sp_executesql @stmt = @sql_select, - @params = @sp_params, - @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; - -/* -This looks for forced plans -*/ -RAISERROR(N'Checking for forced plans', 0, 1) WITH NOWAIT; +/*End implicit conversion and parameter info*/ -UPDATE ww -SET ww.is_forced_plan = 1 -FROM #working_warnings AS ww -JOIN #working_plan_text AS wp -ON ww.plan_id = wp.plan_id - AND ww.query_id = wp.query_id - AND wp.is_forced_plan = 1 -OPTION (RECOMPILE); +/*Begin Missing Index*/ +IF EXISTS ( SELECT 1/0 + FROM #working_warnings AS ww + WHERE ww.missing_index_count > 0 + OR ww.index_spool_cost > 0 + OR ww.index_spool_rows > 0 ) + + BEGIN + + RAISERROR(N'Inserting to #missing_index_xml', 0, 1) WITH NOWAIT; + WITH XMLNAMESPACES ( 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) + INSERT #missing_index_xml + SELECT qp.query_hash, + qp.sql_handle, + c.mg.value('@Impact', 'FLOAT') AS Impact, + c.mg.query('.') AS cmg + FROM #query_plan AS qp + CROSS APPLY qp.query_plan.nodes('//p:MissingIndexes/p:MissingIndexGroup') AS c(mg) + WHERE qp.query_hash IS NOT NULL + AND c.mg.value('@Impact', 'FLOAT') > 70.0 + OPTION (RECOMPILE); + RAISERROR(N'Inserting to #missing_index_schema', 0, 1) WITH NOWAIT; + WITH XMLNAMESPACES ( 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) + INSERT #missing_index_schema + SELECT mix.query_hash, mix.sql_handle, mix.impact, + c.mi.value('@Database', 'NVARCHAR(128)'), + c.mi.value('@Schema', 'NVARCHAR(128)'), + c.mi.value('@Table', 'NVARCHAR(128)'), + c.mi.query('.') + FROM #missing_index_xml AS mix + CROSS APPLY mix.index_xml.nodes('//p:MissingIndex') AS c(mi) + OPTION (RECOMPILE); + + RAISERROR(N'Inserting to #missing_index_usage', 0, 1) WITH NOWAIT; + WITH XMLNAMESPACES ( 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) + INSERT #missing_index_usage + SELECT ms.query_hash, ms.sql_handle, ms.impact, ms.database_name, ms.schema_name, ms.table_name, + c.cg.value('@Usage', 'NVARCHAR(128)'), + c.cg.query('.') + FROM #missing_index_schema ms + CROSS APPLY ms.index_xml.nodes('//p:ColumnGroup') AS c(cg) + OPTION (RECOMPILE); + + RAISERROR(N'Inserting to #missing_index_detail', 0, 1) WITH NOWAIT; + WITH XMLNAMESPACES ( 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) + INSERT #missing_index_detail + SELECT miu.query_hash, + miu.sql_handle, + miu.impact, + miu.database_name, + miu.schema_name, + miu.table_name, + miu.usage, + c.c.value('@Name', 'NVARCHAR(128)') + FROM #missing_index_usage AS miu + CROSS APPLY miu.index_xml.nodes('//p:Column') AS c(c) + OPTION (RECOMPILE); + + RAISERROR(N'Inserting to #missing_index_pretty', 0, 1) WITH NOWAIT; + INSERT #missing_index_pretty + SELECT DISTINCT m.query_hash, m.sql_handle, m.impact, m.database_name, m.schema_name, m.table_name + , STUFF(( SELECT DISTINCT N', ' + ISNULL(m2.column_name, '') AS column_name + FROM #missing_index_detail AS m2 + WHERE m2.usage = 'EQUALITY' + AND m.query_hash = m2.query_hash + AND m.sql_handle = m2.sql_handle + AND m.impact = m2.impact + AND m.database_name = m2.database_name + AND m.schema_name = m2.schema_name + AND m.table_name = m2.table_name + FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 2, N'') AS equality + , STUFF(( SELECT DISTINCT N', ' + ISNULL(m2.column_name, '') AS column_name + FROM #missing_index_detail AS m2 + WHERE m2.usage = 'INEQUALITY' + AND m.query_hash = m2.query_hash + AND m.sql_handle = m2.sql_handle + AND m.impact = m2.impact + AND m.database_name = m2.database_name + AND m.schema_name = m2.schema_name + AND m.table_name = m2.table_name + FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 2, N'') AS inequality + , STUFF(( SELECT DISTINCT N', ' + ISNULL(m2.column_name, '') AS column_name + FROM #missing_index_detail AS m2 + WHERE m2.usage = 'INCLUDE' + AND m.query_hash = m2.query_hash + AND m.sql_handle = m2.sql_handle + AND m.impact = m2.impact + AND m.database_name = m2.database_name + AND m.schema_name = m2.schema_name + AND m.table_name = m2.table_name + FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 2, N'') AS [include], + 0 AS is_spool + FROM #missing_index_detail AS m + GROUP BY m.query_hash, m.sql_handle, m.impact, m.database_name, m.schema_name, m.table_name + OPTION (RECOMPILE); -/* -This looks for forced parameterization -*/ + RAISERROR(N'Inserting to #index_spool_ugly', 0, 1) WITH NOWAIT; + WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) + INSERT #index_spool_ugly + (query_hash, sql_handle, impact, database_name, schema_name, table_name, equality, inequality, include) + SELECT r.query_hash, + r.sql_handle, + (c.n.value('@EstimateIO', 'FLOAT') + (c.n.value('@EstimateCPU', 'FLOAT'))) + / ( 1 * NULLIF(ww.query_cost, 0)) * 100 AS impact, + o.n.value('@Database', 'NVARCHAR(128)') AS output_database, + o.n.value('@Schema', 'NVARCHAR(128)') AS output_schema, + o.n.value('@Table', 'NVARCHAR(128)') AS output_table, + k.n.value('@Column', 'NVARCHAR(128)') AS range_column, + e.n.value('@Column', 'NVARCHAR(128)') AS expression_column, + o.n.value('@Column', 'NVARCHAR(128)') AS output_column + FROM #relop AS r + JOIN #working_warnings AS ww + ON ww.query_hash = r.query_hash + CROSS APPLY r.relop.nodes('/p:RelOp') AS c(n) + CROSS APPLY r.relop.nodes('/p:RelOp/p:OutputList/p:ColumnReference') AS o(n) + OUTER APPLY r.relop.nodes('/p:RelOp/p:Spool/p:SeekPredicateNew/p:SeekKeys/p:Prefix/p:RangeColumns/p:ColumnReference') AS k(n) + OUTER APPLY r.relop.nodes('/p:RelOp/p:Spool/p:SeekPredicateNew/p:SeekKeys/p:Prefix/p:RangeExpressions/p:ColumnReference') AS e(n) + WHERE r.relop.exist('/p:RelOp[@PhysicalOp="Index Spool" and @LogicalOp="Eager Spool"]') = 1 + + RAISERROR(N'Inserting to spools to #missing_index_pretty', 0, 1) WITH NOWAIT; + INSERT #missing_index_pretty + (query_hash, sql_handle, impact, database_name, schema_name, table_name, equality, inequality, include, is_spool) + SELECT DISTINCT + isu.query_hash, + isu.sql_handle, + isu.impact, + isu.database_name, + isu.schema_name, + isu.table_name + , STUFF(( SELECT DISTINCT N', ' + ISNULL(isu2.equality, '') AS column_name + FROM #index_spool_ugly AS isu2 + WHERE isu2.equality IS NOT NULL + AND isu.query_hash = isu2.query_hash + AND isu.sql_handle = isu2.sql_handle + AND isu.impact = isu2.impact + AND isu.database_name = isu2.database_name + AND isu.schema_name = isu2.schema_name + AND isu.table_name = isu2.table_name + FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 2, N'') AS equality + , STUFF(( SELECT DISTINCT N', ' + ISNULL(isu2.inequality, '') AS column_name + FROM #index_spool_ugly AS isu2 + WHERE isu2.inequality IS NOT NULL + AND isu.query_hash = isu2.query_hash + AND isu.sql_handle = isu2.sql_handle + AND isu.impact = isu2.impact + AND isu.database_name = isu2.database_name + AND isu.schema_name = isu2.schema_name + AND isu.table_name = isu2.table_name + FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 2, N'') AS inequality + , STUFF(( SELECT DISTINCT N', ' + ISNULL(isu2.include, '') AS column_name + FROM #index_spool_ugly AS isu2 + WHERE isu2.include IS NOT NULL + AND isu.query_hash = isu2.query_hash + AND isu.sql_handle = isu2.sql_handle + AND isu.impact = isu2.impact + AND isu.database_name = isu2.database_name + AND isu.schema_name = isu2.schema_name + AND isu.table_name = isu2.table_name + FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 2, N'') AS include, + 1 AS is_spool + FROM #index_spool_ugly AS isu -RAISERROR(N'Checking for forced parameterization', 0, 1) WITH NOWAIT; -UPDATE ww -SET ww.is_forced_parameterized = 1 -FROM #working_warnings AS ww -JOIN #working_metrics AS wm -ON ww.plan_id = wm.plan_id - AND ww.query_id = wm.query_id - AND wm.query_parameterization_type_desc = 'Forced' -OPTION (RECOMPILE); + RAISERROR(N'Updating missing index information', 0, 1) WITH NOWAIT; + WITH missing AS ( + SELECT DISTINCT + mip.query_hash, + mip.sql_handle, + N'' + AS full_details + FROM #missing_index_pretty AS mip + GROUP BY mip.query_hash, mip.sql_handle, mip.impact + ) + UPDATE ww + SET ww.missing_indexes = m.full_details + FROM #working_warnings AS ww + JOIN missing AS m + ON m.sql_handle = ww.sql_handle + OPTION (RECOMPILE); + RAISERROR(N'Filling in missing index blanks', 0, 1) WITH NOWAIT; + UPDATE ww + SET ww.missing_indexes = + CASE WHEN ww.missing_indexes IS NULL + THEN '' + ELSE ww.missing_indexes + END + FROM #working_warnings AS ww + OPTION (RECOMPILE); -/* -This looks for unparameterized queries -*/ +END +/*End Missing Index*/ -RAISERROR(N'Checking for unparameterized plans', 0, 1) WITH NOWAIT; +RAISERROR(N'General query dispositions: frequent executions, long running, etc.', 0, 1) WITH NOWAIT; -UPDATE ww -SET ww.unparameterized_query = 1 -FROM #working_warnings AS ww -JOIN #working_metrics AS wm -ON ww.plan_id = wm.plan_id - AND ww.query_id = wm.query_id - AND wm.query_parameterization_type_desc = 'None' - AND ww.proc_or_function_name = 'Statement' +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +UPDATE b +SET b.frequent_execution = CASE WHEN wm.xpm > @execution_threshold THEN 1 END , + b.near_parallel = CASE WHEN b.query_cost BETWEEN @ctp * (1 - (@ctp_threshold_pct / 100.0)) AND @ctp THEN 1 END, + b.long_running = CASE WHEN wm.avg_duration > @long_running_query_warning_seconds THEN 1 + WHEN wm.max_duration > @long_running_query_warning_seconds THEN 1 + WHEN wm.avg_cpu_time > @long_running_query_warning_seconds THEN 1 + WHEN wm.max_cpu_time > @long_running_query_warning_seconds THEN 1 END, + b.is_key_lookup_expensive = CASE WHEN b.query_cost >= (@ctp / 2) AND b.key_lookup_cost >= b.query_cost * .5 THEN 1 END, + b.is_sort_expensive = CASE WHEN b.query_cost >= (@ctp / 2) AND b.sort_cost >= b.query_cost * .5 THEN 1 END, + b.is_remote_query_expensive = CASE WHEN b.remote_query_cost >= b.query_cost * .05 THEN 1 END, + b.is_unused_grant = CASE WHEN percent_memory_grant_used <= @memory_grant_warning_percent AND min_query_max_used_memory > @min_memory_per_query THEN 1 END, + b.long_running_low_cpu = CASE WHEN wm.avg_duration > wm.avg_cpu_time * 4 AND avg_cpu_time < 500. THEN 1 END, + b.low_cost_high_cpu = CASE WHEN b.query_cost < 10 AND wm.avg_cpu_time > 5000. THEN 1 END, + b.is_spool_expensive = CASE WHEN b.query_cost > (@ctp / 2) AND b.index_spool_cost >= b.query_cost * .1 THEN 1 END, + b.is_spool_more_rows = CASE WHEN b.index_spool_rows >= wm.min_rowcount THEN 1 END, + b.is_bad_estimate = CASE WHEN wm.avg_rowcount > 0 AND (b.estimated_rows * 1000 < wm.avg_rowcount OR b.estimated_rows > wm.avg_rowcount * 1000) THEN 1 END, + b.is_big_log = CASE WHEN wm.avg_log_bytes_used >= (@log_size_mb / 2.) THEN 1 END, + b.is_big_tempdb = CASE WHEN wm.avg_tempdb_space_used >= (@avg_tempdb_data_file / 2.) THEN 1 END +FROM #working_warnings AS b +JOIN #working_metrics AS wm +ON b.plan_id = wm.plan_id +AND b.query_id = wm.query_id +JOIN #working_plan_text AS wpt +ON b.plan_id = wpt.plan_id +AND b.query_id = wpt.query_id OPTION (RECOMPILE); -/* -This looks for cursors -*/ - -RAISERROR(N'Checking for cursors', 0, 1) WITH NOWAIT; -UPDATE ww -SET ww.is_cursor = 1 -FROM #working_warnings AS ww -JOIN #working_plan_text AS wp -ON ww.plan_id = wp.plan_id - AND ww.query_id = wp.query_id - AND wp.plan_group_id > 0 +RAISERROR('Populating Warnings column', 0, 1) WITH NOWAIT; +/* Populate warnings */ +UPDATE b +SET b.warnings = SUBSTRING( + CASE WHEN b.warning_no_join_predicate = 1 THEN ', No Join Predicate' ELSE '' END + + CASE WHEN b.compile_timeout = 1 THEN ', Compilation Timeout' ELSE '' END + + CASE WHEN b.compile_memory_limit_exceeded = 1 THEN ', Compile Memory Limit Exceeded' ELSE '' END + + CASE WHEN b.is_forced_plan = 1 THEN ', Forced Plan' ELSE '' END + + CASE WHEN b.is_forced_parameterized = 1 THEN ', Forced Parameterization' ELSE '' END + + CASE WHEN b.unparameterized_query = 1 THEN ', Unparameterized Query' ELSE '' END + + CASE WHEN b.missing_index_count > 0 THEN ', Missing Indexes (' + CAST(b.missing_index_count AS NVARCHAR(3)) + ')' ELSE '' END + + CASE WHEN b.unmatched_index_count > 0 THEN ', Unmatched Indexes (' + CAST(b.unmatched_index_count AS NVARCHAR(3)) + ')' ELSE '' END + + CASE WHEN b.is_cursor = 1 THEN ', Cursor' + + CASE WHEN b.is_optimistic_cursor = 1 THEN '; optimistic' ELSE '' END + + CASE WHEN b.is_forward_only_cursor = 0 THEN '; not forward only' ELSE '' END + + CASE WHEN b.is_cursor_dynamic = 1 THEN '; dynamic' ELSE '' END + + CASE WHEN b.is_fast_forward_cursor = 1 THEN '; fast forward' ELSE '' END + ELSE '' END + + CASE WHEN b.is_parallel = 1 THEN ', Parallel' ELSE '' END + + CASE WHEN b.near_parallel = 1 THEN ', Nearly Parallel' ELSE '' END + + CASE WHEN b.frequent_execution = 1 THEN ', Frequent Execution' ELSE '' END + + CASE WHEN b.plan_warnings = 1 THEN ', Plan Warnings' ELSE '' END + + CASE WHEN b.parameter_sniffing = 1 THEN ', Parameter Sniffing' ELSE '' END + + CASE WHEN b.long_running = 1 THEN ', Long Running Query' ELSE '' END + + CASE WHEN b.downlevel_estimator = 1 THEN ', Downlevel CE' ELSE '' END + + CASE WHEN b.implicit_conversions = 1 THEN ', Implicit Conversions' ELSE '' END + + CASE WHEN b.plan_multiple_plans = 1 THEN ', Multiple Plans' ELSE '' END + + CASE WHEN b.is_trivial = 1 THEN ', Trivial Plans' ELSE '' END + + CASE WHEN b.is_forced_serial = 1 THEN ', Forced Serialization' ELSE '' END + + CASE WHEN b.is_key_lookup_expensive = 1 THEN ', Expensive Key Lookup' ELSE '' END + + CASE WHEN b.is_remote_query_expensive = 1 THEN ', Expensive Remote Query' ELSE '' END + + CASE WHEN b.trace_flags_session IS NOT NULL THEN ', Session Level Trace Flag(s) Enabled: ' + b.trace_flags_session ELSE '' END + + CASE WHEN b.is_unused_grant = 1 THEN ', Unused Memory Grant' ELSE '' END + + CASE WHEN b.function_count > 0 THEN ', Calls ' + CONVERT(VARCHAR(10), b.function_count) + ' function(s)' ELSE '' END + + CASE WHEN b.clr_function_count > 0 THEN ', Calls ' + CONVERT(VARCHAR(10), b.clr_function_count) + ' CLR function(s)' ELSE '' END + + CASE WHEN b.is_table_variable = 1 THEN ', Table Variables' ELSE '' END + + CASE WHEN b.no_stats_warning = 1 THEN ', Columns With No Statistics' ELSE '' END + + CASE WHEN b.relop_warnings = 1 THEN ', Operator Warnings' ELSE '' END + + CASE WHEN b.is_table_scan = 1 THEN ', Table Scans' ELSE '' END + + CASE WHEN b.backwards_scan = 1 THEN ', Backwards Scans' ELSE '' END + + CASE WHEN b.forced_index = 1 THEN ', Forced Indexes' ELSE '' END + + CASE WHEN b.forced_seek = 1 THEN ', Forced Seeks' ELSE '' END + + CASE WHEN b.forced_scan = 1 THEN ', Forced Scans' ELSE '' END + + CASE WHEN b.columnstore_row_mode = 1 THEN ', ColumnStore Row Mode ' ELSE '' END + + CASE WHEN b.is_computed_scalar = 1 THEN ', Computed Column UDF ' ELSE '' END + + CASE WHEN b.is_sort_expensive = 1 THEN ', Expensive Sort' ELSE '' END + + CASE WHEN b.is_computed_filter = 1 THEN ', Filter UDF' ELSE '' END + + CASE WHEN b.index_ops >= 5 THEN ', >= 5 Indexes Modified' ELSE '' END + + CASE WHEN b.is_row_level = 1 THEN ', Row Level Security' ELSE '' END + + CASE WHEN b.is_spatial = 1 THEN ', Spatial Index' ELSE '' END + + CASE WHEN b.index_dml = 1 THEN ', Index DML' ELSE '' END + + CASE WHEN b.table_dml = 1 THEN ', Table DML' ELSE '' END + + CASE WHEN b.low_cost_high_cpu = 1 THEN ', Low Cost High CPU' ELSE '' END + + CASE WHEN b.long_running_low_cpu = 1 THEN + ', Long Running With Low CPU' ELSE '' END + + CASE WHEN b.stale_stats = 1 THEN + ', Statistics used have > 100k modifications in the last 7 days' ELSE '' END + + CASE WHEN b.is_adaptive = 1 THEN + ', Adaptive Joins' ELSE '' END + + CASE WHEN b.is_spool_expensive = 1 THEN + ', Expensive Index Spool' ELSE '' END + + CASE WHEN b.is_spool_more_rows = 1 THEN + ', Large Index Row Spool' ELSE '' END + + CASE WHEN b.is_bad_estimate = 1 THEN + ', Row estimate mismatch' ELSE '' END + + CASE WHEN b.is_big_log = 1 THEN + ', High log use' ELSE '' END + + CASE WHEN b.is_big_tempdb = 1 THEN ', High tempdb use' ELSE '' END + + CASE WHEN b.is_paul_white_electric = 1 THEN ', SWITCH!' ELSE '' END + + CASE WHEN b.is_row_goal = 1 THEN ', Row Goals' ELSE '' END + + CASE WHEN b.is_mstvf = 1 THEN ', MSTVFs' ELSE '' END + + CASE WHEN b.is_mm_join = 1 THEN ', Many to Many Merge' ELSE '' END + + CASE WHEN b.is_nonsargable = 1 THEN ', non-SARGables' ELSE '' END + , 2, 200000) +FROM #working_warnings b OPTION (RECOMPILE); -UPDATE ww -SET ww.is_cursor = 1 -FROM #working_warnings AS ww -JOIN #working_plan_text AS wp -ON ww.plan_id = wp.plan_id - AND ww.query_id = wp.query_id -WHERE ww.query_hash = 0x0000000000000000 -OR wp.query_plan_hash = 0x0000000000000000 -OPTION (RECOMPILE); - -/* -This looks for parallel plans -*/ -UPDATE ww -SET ww.is_parallel = 1 -FROM #working_warnings AS ww -JOIN #working_plan_text AS wp -ON ww.plan_id = wp.plan_id - AND ww.query_id = wp.query_id - AND wp.is_parallel_plan = 1 -OPTION (RECOMPILE); +END; +END TRY +BEGIN CATCH + RAISERROR (N'Failure generating warnings.', 0,1) WITH NOWAIT; -/*This looks for old CE*/ + IF @sql_select IS NOT NULL + BEGIN + SET @msg = N'Last @sql_select: ' + @sql_select; + RAISERROR(@msg, 0, 1) WITH NOWAIT; + END; -RAISERROR(N'Checking for legacy CE', 0, 1) WITH NOWAIT; + SELECT @msg = @DatabaseName + N' database failed to process. ' + ERROR_MESSAGE(), @error_severity = ERROR_SEVERITY(), @error_state = ERROR_STATE(); + RAISERROR (@msg, @error_severity, @error_state) WITH NOWAIT; + + + WHILE @@TRANCOUNT > 0 + ROLLBACK; -UPDATE w -SET w.downlevel_estimator = 1 -FROM #working_warnings AS w -JOIN #working_plan_text AS wpt -ON w.plan_id = wpt.plan_id -AND w.query_id = wpt.query_id -/*PLEASE DON'T TELL ANYONE I DID THIS*/ -WHERE PARSENAME(wpt.engine_version, 4) < PARSENAME(CONVERT(VARCHAR(128), SERVERPROPERTY ('PRODUCTVERSION')), 4) -OPTION (RECOMPILE); -/*NO SERIOUSLY THIS IS A HORRIBLE IDEA*/ + RETURN; +END CATCH; -/*Plans that compile 2x more than they execute*/ +BEGIN TRY +BEGIN -RAISERROR(N'Checking for plans that compile 2x more than they execute', 0, 1) WITH NOWAIT; +RAISERROR(N'Checking for parameter sniffing symptoms', 0, 1) WITH NOWAIT; -UPDATE ww -SET ww.is_compile_more = 1 -FROM #working_warnings AS ww -JOIN #working_metrics AS wm -ON ww.plan_id = wm.plan_id - AND ww.query_id = wm.query_id - AND wm.count_compiles > (wm.count_executions * 2) +UPDATE b +SET b.parameter_sniffing_symptoms = +CASE WHEN b.count_executions < 2 THEN 'Too few executions to compare (< 2).' + ELSE + SUBSTRING( + /*Duration*/ + CASE WHEN (b.min_duration * 100) < (b.avg_duration) THEN ', Fast sometimes' ELSE '' END + + CASE WHEN (b.max_duration) > (b.avg_duration * 100) THEN ', Slow sometimes' ELSE '' END + + CASE WHEN (b.last_duration * 100) < (b.avg_duration) THEN ', Fast last run' ELSE '' END + + CASE WHEN (b.last_duration) > (b.avg_duration * 100) THEN ', Slow last run' ELSE '' END + + /*CPU*/ + CASE WHEN (b.min_cpu_time / b.avg_dop) * 100 < (b.avg_cpu_time / b.avg_dop) THEN ', Low CPU sometimes' ELSE '' END + + CASE WHEN (b.max_cpu_time / b.max_dop) > (b.avg_cpu_time / b.avg_dop) * 100 THEN ', High CPU sometimes' ELSE '' END + + CASE WHEN (b.last_cpu_time / b.last_dop) * 100 < (b.avg_cpu_time / b.avg_dop) THEN ', Low CPU last run' ELSE '' END + + CASE WHEN (b.last_cpu_time / b.last_dop) > (b.avg_cpu_time / b.avg_dop) * 100 THEN ', High CPU last run' ELSE '' END + + /*Logical Reads*/ + CASE WHEN (b.min_logical_io_reads * 100) < (b.avg_logical_io_reads) THEN ', Low reads sometimes' ELSE '' END + + CASE WHEN (b.max_logical_io_reads) > (b.avg_logical_io_reads * 100) THEN ', High reads sometimes' ELSE '' END + + CASE WHEN (b.last_logical_io_reads * 100) < (b.avg_logical_io_reads) THEN ', Low reads last run' ELSE '' END + + CASE WHEN (b.last_logical_io_reads) > (b.avg_logical_io_reads * 100) THEN ', High reads last run' ELSE '' END + + /*Logical Writes*/ + CASE WHEN (b.min_logical_io_writes * 100) < (b.avg_logical_io_writes) THEN ', Low writes sometimes' ELSE '' END + + CASE WHEN (b.max_logical_io_writes) > (b.avg_logical_io_writes * 100) THEN ', High writes sometimes' ELSE '' END + + CASE WHEN (b.last_logical_io_writes * 100) < (b.avg_logical_io_writes) THEN ', Low writes last run' ELSE '' END + + CASE WHEN (b.last_logical_io_writes) > (b.avg_logical_io_writes * 100) THEN ', High writes last run' ELSE '' END + + /*Physical Reads*/ + CASE WHEN (b.min_physical_io_reads * 100) < (b.avg_physical_io_reads) THEN ', Low physical reads sometimes' ELSE '' END + + CASE WHEN (b.max_physical_io_reads) > (b.avg_physical_io_reads * 100) THEN ', High physical reads sometimes' ELSE '' END + + CASE WHEN (b.last_physical_io_reads * 100) < (b.avg_physical_io_reads) THEN ', Low physical reads last run' ELSE '' END + + CASE WHEN (b.last_physical_io_reads) > (b.avg_physical_io_reads * 100) THEN ', High physical reads last run' ELSE '' END + + /*Memory*/ + CASE WHEN (b.min_query_max_used_memory * 100) < (b.avg_query_max_used_memory) THEN ', Low memory sometimes' ELSE '' END + + CASE WHEN (b.max_query_max_used_memory) > (b.avg_query_max_used_memory * 100) THEN ', High memory sometimes' ELSE '' END + + CASE WHEN (b.last_query_max_used_memory * 100) < (b.avg_query_max_used_memory) THEN ', Low memory last run' ELSE '' END + + CASE WHEN (b.last_query_max_used_memory) > (b.avg_query_max_used_memory * 100) THEN ', High memory last run' ELSE '' END + + /*Duration*/ + CASE WHEN b.min_rowcount * 100 < b.avg_rowcount THEN ', Low row count sometimes' ELSE '' END + + CASE WHEN b.max_rowcount > b.avg_rowcount * 100 THEN ', High row count sometimes' ELSE '' END + + CASE WHEN b.last_rowcount * 100 < b.avg_rowcount THEN ', Low row count run' ELSE '' END + + CASE WHEN b.last_rowcount > b.avg_rowcount * 100 THEN ', High row count last run' ELSE '' END + + /*DOP*/ + CASE WHEN b.min_dop <> b.max_dop THEN ', Serial sometimes' ELSE '' END + + CASE WHEN b.min_dop <> b.max_dop AND b.last_dop = 1 THEN ', Serial last run' ELSE '' END + + CASE WHEN b.min_dop <> b.max_dop AND b.last_dop > 1 THEN ', Parallel last run' ELSE '' END + + /*tempdb*/ + CASE WHEN b.min_tempdb_space_used * 100 < b.avg_tempdb_space_used THEN ', Low tempdb sometimes' ELSE '' END + + CASE WHEN b.max_tempdb_space_used > b.avg_tempdb_space_used * 100 THEN ', High tempdb sometimes' ELSE '' END + + CASE WHEN b.last_tempdb_space_used * 100 < b.avg_tempdb_space_used THEN ', Low tempdb run' ELSE '' END + + CASE WHEN b.last_tempdb_space_used > b.avg_tempdb_space_used * 100 THEN ', High tempdb last run' ELSE '' END + + /*tlog*/ + CASE WHEN b.min_log_bytes_used * 100 < b.avg_log_bytes_used THEN ', Low log use sometimes' ELSE '' END + + CASE WHEN b.max_log_bytes_used > b.avg_log_bytes_used * 100 THEN ', High log use sometimes' ELSE '' END + + CASE WHEN b.last_log_bytes_used * 100 < b.avg_log_bytes_used THEN ', Low log use run' ELSE '' END + + CASE WHEN b.last_log_bytes_used > b.avg_log_bytes_used * 100 THEN ', High log use last run' ELSE '' END + , 2, 200000) + END +FROM #working_metrics AS b OPTION (RECOMPILE); -/*Plans that compile 2x more than they execute*/ - -RAISERROR(N'Checking for plans that take more than 5 seconds to bind, compile, or optimize', 0, 1) WITH NOWAIT; +END; +END TRY +BEGIN CATCH + RAISERROR (N'Failure analyzing parameter sniffing', 0,1) WITH NOWAIT; -UPDATE ww -SET ww.is_slow_plan = 1 -FROM #working_warnings AS ww -JOIN #working_metrics AS wm -ON ww.plan_id = wm.plan_id - AND ww.query_id = wm.query_id - AND (wm.avg_bind_duration > 5000 - OR - wm.avg_compile_duration > 5000 - OR - wm.avg_optimize_duration > 5000 - OR - wm.avg_optimize_cpu_time > 5000) -OPTION (RECOMPILE); + IF @sql_select IS NOT NULL + BEGIN + SET @msg = N'Last @sql_select: ' + @sql_select; + RAISERROR(@msg, 0, 1) WITH NOWAIT; + END; + SELECT @msg = @DatabaseName + N' database failed to process. ' + ERROR_MESSAGE(), @error_severity = ERROR_SEVERITY(), @error_state = ERROR_STATE(); + RAISERROR (@msg, @error_severity, @error_state) WITH NOWAIT; + + + WHILE @@TRANCOUNT > 0 + ROLLBACK; + RETURN; +END CATCH; -/* -This parses the XML from our top plans into smaller chunks for easier consumption -*/ +BEGIN TRY -RAISERROR(N'Begin XML nodes parsing', 0, 1) WITH NOWAIT; +BEGIN -RAISERROR(N'Inserting #statements', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -INSERT #statements WITH (TABLOCK) ( plan_id, query_id, query_hash, sql_handle, statement, is_cursor ) - SELECT ww.plan_id, ww.query_id, ww.query_hash, ww.sql_handle, q.n.query('.') AS statement, 0 AS is_cursor - FROM #working_warnings AS ww - JOIN #working_plan_text AS wp - ON ww.plan_id = wp.plan_id - AND ww.query_id = wp.query_id - CROSS APPLY wp.query_plan_xml.nodes('//p:StmtSimple') AS q(n) -OPTION (RECOMPILE); +IF (@Failed = 0 AND @ExportToExcel = 0 AND @SkipXML = 0) +BEGIN -RAISERROR(N'Inserting parsed cursor XML to #statements', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -INSERT #statements WITH (TABLOCK) ( plan_id, query_id, query_hash, sql_handle, statement, is_cursor ) - SELECT ww.plan_id, ww.query_id, ww.query_hash, ww.sql_handle, q.n.query('.') AS statement, 1 AS is_cursor - FROM #working_warnings AS ww - JOIN #working_plan_text AS wp - ON ww.plan_id = wp.plan_id - AND ww.query_id = wp.query_id - CROSS APPLY wp.query_plan_xml.nodes('//p:StmtCursor') AS q(n) -OPTION (RECOMPILE); +RAISERROR(N'Returning regular results', 0, 1) WITH NOWAIT; -RAISERROR(N'Inserting to #query_plan', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -INSERT #query_plan WITH (TABLOCK) ( plan_id, query_id, query_hash, sql_handle, query_plan ) -SELECT s.plan_id, s.query_id, s.query_hash, s.sql_handle, q.n.query('.') AS query_plan -FROM #statements AS s - CROSS APPLY s.statement.nodes('//p:QueryPlan') AS q(n) +WITH x AS ( +SELECT wpt.database_name, ww.query_cost, wm.plan_id, wm.query_id, wm.query_id_all_plan_ids, wpt.query_sql_text, wm.proc_or_function_name, wpt.query_plan_xml, ww.warnings, wpt.pattern, + wm.parameter_sniffing_symptoms, wpt.top_three_waits, ww.missing_indexes, ww.implicit_conversion_info, ww.cached_execution_parameters, wm.count_executions, wm.count_compiles, wm.total_cpu_time, wm.avg_cpu_time, + wm.total_duration, wm.avg_duration, wm.total_logical_io_reads, wm.avg_logical_io_reads, + wm.total_physical_io_reads, wm.avg_physical_io_reads, wm.total_logical_io_writes, wm.avg_logical_io_writes, wm.total_rowcount, wm.avg_rowcount, + wm.total_query_max_used_memory, wm.avg_query_max_used_memory, wm.total_tempdb_space_used, wm.avg_tempdb_space_used, + wm.total_log_bytes_used, wm.avg_log_bytes_used, wm.total_num_physical_io_reads, wm.avg_num_physical_io_reads, + wm.first_execution_time, wm.last_execution_time, wpt.last_force_failure_reason_desc, wpt.context_settings, ROW_NUMBER() OVER (PARTITION BY wm.plan_id, wm.query_id, wm.last_execution_time ORDER BY wm.plan_id) AS rn +FROM #working_plan_text AS wpt +JOIN #working_warnings AS ww + ON wpt.plan_id = ww.plan_id + AND wpt.query_id = ww.query_id +JOIN #working_metrics AS wm + ON wpt.plan_id = wm.plan_id + AND wpt.query_id = wm.query_id +) +SELECT * +FROM x +WHERE x.rn = 1 +ORDER BY x.last_execution_time OPTION (RECOMPILE); -RAISERROR(N'Inserting to #relop', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -INSERT #relop WITH (TABLOCK) ( plan_id, query_id, query_hash, sql_handle, relop) -SELECT qp.plan_id, qp.query_id, qp.query_hash, qp.sql_handle, q.n.query('.') AS relop -FROM #query_plan qp - CROSS APPLY qp.query_plan.nodes('//p:RelOp') AS q(n) -OPTION (RECOMPILE); +END; +IF (@Failed = 1 AND @ExportToExcel = 0 AND @SkipXML = 0) +BEGIN --- statement level checks +RAISERROR(N'Returning results for failed queries', 0, 1) WITH NOWAIT; -RAISERROR(N'Performing compile timeout checks', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET b.compile_timeout = 1 -FROM #statements s -JOIN #working_warnings AS b -ON s.query_hash = b.query_hash -WHERE s.statement.exist('/p:StmtSimple/@StatementOptmEarlyAbortReason[.="TimeOut"]') = 1 +WITH x AS ( +SELECT wpt.database_name, ww.query_cost, wm.plan_id, wm.query_id, wm.query_id_all_plan_ids, wpt.query_sql_text, wm.proc_or_function_name, wpt.query_plan_xml, ww.warnings, wpt.pattern, + wm.parameter_sniffing_symptoms, wpt.last_force_failure_reason_desc, wpt.top_three_waits, ww.missing_indexes, ww.implicit_conversion_info, ww.cached_execution_parameters, + wm.count_executions, wm.count_compiles, wm.total_cpu_time, wm.avg_cpu_time, + wm.total_duration, wm.avg_duration, wm.total_logical_io_reads, wm.avg_logical_io_reads, + wm.total_physical_io_reads, wm.avg_physical_io_reads, wm.total_logical_io_writes, wm.avg_logical_io_writes, wm.total_rowcount, wm.avg_rowcount, + wm.total_query_max_used_memory, wm.avg_query_max_used_memory, wm.total_tempdb_space_used, wm.avg_tempdb_space_used, + wm.total_log_bytes_used, wm.avg_log_bytes_used, wm.total_num_physical_io_reads, wm.avg_num_physical_io_reads, + wm.first_execution_time, wm.last_execution_time, wpt.context_settings, ROW_NUMBER() OVER (PARTITION BY wm.plan_id, wm.query_id, wm.last_execution_time ORDER BY wm.plan_id) AS rn +FROM #working_plan_text AS wpt +JOIN #working_warnings AS ww + ON wpt.plan_id = ww.plan_id + AND wpt.query_id = ww.query_id +JOIN #working_metrics AS wm + ON wpt.plan_id = wm.plan_id + AND wpt.query_id = wm.query_id +) +SELECT * +FROM x +WHERE x.rn = 1 +ORDER BY x.last_execution_time OPTION (RECOMPILE); +END; -RAISERROR(N'Performing compile memory limit exceeded checks', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET b.compile_memory_limit_exceeded = 1 -FROM #statements s -JOIN #working_warnings AS b -ON s.query_hash = b.query_hash -WHERE s.statement.exist('/p:StmtSimple/@StatementOptmEarlyAbortReason[.="MemoryLimitExceeded"]') = 1 -OPTION (RECOMPILE); - -IF @ExpertMode > 0 +IF (@ExportToExcel = 1 AND @SkipXML = 0) BEGIN -RAISERROR(N'Performing index DML checks', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p), -index_dml AS ( - SELECT s.query_hash, - index_dml = CASE WHEN s.statement.exist('//p:StmtSimple/@StatementType[.="CREATE INDEX"]') = 1 THEN 1 - WHEN s.statement.exist('//p:StmtSimple/@StatementType[.="DROP INDEX"]') = 1 THEN 1 - END - FROM #statements s - ) - UPDATE b - SET b.index_dml = i.index_dml - FROM #working_warnings AS b - JOIN index_dml i - ON i.query_hash = b.query_hash - WHERE i.index_dml = 1 -OPTION (RECOMPILE); +RAISERROR(N'Returning results for Excel export', 0, 1) WITH NOWAIT; -RAISERROR(N'Performing table DML checks', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p), -table_dml AS ( - SELECT s.query_hash, - table_dml = CASE WHEN s.statement.exist('//p:StmtSimple/@StatementType[.="CREATE TABLE"]') = 1 THEN 1 - WHEN s.statement.exist('//p:StmtSimple/@StatementType[.="DROP OBJECT"]') = 1 THEN 1 - END - FROM #statements AS s - ) - UPDATE b - SET b.table_dml = t.table_dml - FROM #working_warnings AS b - JOIN table_dml t - ON t.query_hash = b.query_hash - WHERE t.table_dml = 1 +UPDATE #working_plan_text +SET query_sql_text = SUBSTRING(REPLACE(REPLACE(REPLACE(LTRIM(RTRIM(query_sql_text)),' ','<>'),'><',''),'<>',' '), 1, 31000) OPTION (RECOMPILE); -END; - -RAISERROR(N'Gathering trivial plans', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES ( 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) -UPDATE b -SET b.is_trivial = 1 -FROM #working_warnings AS b -JOIN ( -SELECT s.sql_handle -FROM #statements AS s -JOIN ( SELECT r.sql_handle - FROM #relop AS r - WHERE r.relop.exist('//p:RelOp[contains(@LogicalOp, "Scan")]') = 1 ) AS r - ON r.sql_handle = s.sql_handle -WHERE s.statement.exist('//p:StmtSimple[@StatementOptmLevel[.="TRIVIAL"]]/p:QueryPlan/p:ParameterList') = 1 -) AS s -ON b.sql_handle = s.sql_handle +WITH x AS ( +SELECT wpt.database_name, ww.query_cost, wm.plan_id, wm.query_id, wm.query_id_all_plan_ids, wpt.query_sql_text, wm.proc_or_function_name, ww.warnings, wpt.pattern, + wm.parameter_sniffing_symptoms, wpt.last_force_failure_reason_desc, wpt.top_three_waits, wm.count_executions, wm.count_compiles, wm.total_cpu_time, wm.avg_cpu_time, + wm.total_duration, wm.avg_duration, wm.total_logical_io_reads, wm.avg_logical_io_reads, + wm.total_physical_io_reads, wm.avg_physical_io_reads, wm.total_logical_io_writes, wm.avg_logical_io_writes, wm.total_rowcount, wm.avg_rowcount, + wm.total_query_max_used_memory, wm.avg_query_max_used_memory, wm.total_tempdb_space_used, wm.avg_tempdb_space_used, + wm.total_log_bytes_used, wm.avg_log_bytes_used, wm.total_num_physical_io_reads, wm.avg_num_physical_io_reads, + wm.first_execution_time, wm.last_execution_time, wpt.context_settings, ROW_NUMBER() OVER (PARTITION BY wm.plan_id, wm.query_id, wm.last_execution_time ORDER BY wm.plan_id) AS rn +FROM #working_plan_text AS wpt +JOIN #working_warnings AS ww + ON wpt.plan_id = ww.plan_id + AND wpt.query_id = ww.query_id +JOIN #working_metrics AS wm + ON wpt.plan_id = wm.plan_id + AND wpt.query_id = wm.query_id +) +SELECT * +FROM x +WHERE x.rn = 1 +ORDER BY x.last_execution_time OPTION (RECOMPILE); -IF @ExpertMode > 0 -BEGIN -RAISERROR(N'Gathering row estimates', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES ('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) -INSERT #est_rows (query_hash, estimated_rows) -SELECT DISTINCT - CONVERT(BINARY(8), RIGHT('0000000000000000' + SUBSTRING(c.n.value('@QueryHash', 'VARCHAR(18)'), 3, 18), 16), 2) AS query_hash, - c.n.value('(/p:StmtSimple/@StatementEstRows)[1]', 'FLOAT') AS estimated_rows -FROM #statements AS s -CROSS APPLY s.statement.nodes('/p:StmtSimple') AS c(n) -WHERE c.n.exist('/p:StmtSimple[@StatementEstRows > 0]') = 1; - - UPDATE b - SET b.estimated_rows = er.estimated_rows - FROM #working_warnings AS b - JOIN #est_rows er - ON er.query_hash = b.query_hash - OPTION (RECOMPILE); END; +IF (@ExportToExcel = 0 AND @SkipXML = 1) +BEGIN -/*Begin plan cost calculations*/ -RAISERROR(N'Gathering statement costs', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -INSERT #plan_cost WITH (TABLOCK) - ( query_plan_cost, sql_handle, plan_id ) -SELECT DISTINCT - s.statement.value('sum(/p:StmtSimple/@StatementSubTreeCost)', 'float') query_plan_cost, - s.sql_handle, - s.plan_id -FROM #statements s -OUTER APPLY s.statement.nodes('/p:StmtSimple') AS q(n) -WHERE s.statement.value('sum(/p:StmtSimple/@StatementSubTreeCost)', 'float') > 0 -OPTION (RECOMPILE); - +RAISERROR(N'Returning results for skipped XML', 0, 1) WITH NOWAIT; -RAISERROR(N'Updating statement costs', 0, 1) WITH NOWAIT; -WITH pc AS ( - SELECT SUM(DISTINCT pc.query_plan_cost) AS queryplancostsum, pc.sql_handle, pc.plan_id - FROM #plan_cost AS pc - GROUP BY pc.sql_handle, pc.plan_id - ) - UPDATE b - SET b.query_cost = ISNULL(pc.queryplancostsum, 0) - FROM #working_warnings AS b - JOIN pc - ON pc.sql_handle = b.sql_handle - AND pc.plan_id = b.plan_id +WITH x AS ( +SELECT wpt.database_name, wm.plan_id, wm.query_id, wm.query_id_all_plan_ids, wpt.query_sql_text, wpt.query_plan_xml, wpt.pattern, + wm.parameter_sniffing_symptoms, wpt.top_three_waits, wm.count_executions, wm.count_compiles, wm.total_cpu_time, wm.avg_cpu_time, + wm.total_duration, wm.avg_duration, wm.total_logical_io_reads, wm.avg_logical_io_reads, + wm.total_physical_io_reads, wm.avg_physical_io_reads, wm.total_logical_io_writes, wm.avg_logical_io_writes, wm.total_rowcount, wm.avg_rowcount, + wm.total_query_max_used_memory, wm.avg_query_max_used_memory, wm.total_tempdb_space_used, wm.avg_tempdb_space_used, + wm.total_log_bytes_used, wm.avg_log_bytes_used, wm.total_num_physical_io_reads, wm.avg_num_physical_io_reads, + wm.first_execution_time, wm.last_execution_time, wpt.last_force_failure_reason_desc, wpt.context_settings, ROW_NUMBER() OVER (PARTITION BY wm.plan_id, wm.query_id, wm.last_execution_time ORDER BY wm.plan_id) AS rn +FROM #working_plan_text AS wpt +JOIN #working_metrics AS wm + ON wpt.plan_id = wm.plan_id + AND wpt.query_id = wm.query_id +) +SELECT * +FROM x +WHERE x.rn = 1 +ORDER BY x.last_execution_time OPTION (RECOMPILE); +END; -/*End plan cost calculations*/ +END; +END TRY +BEGIN CATCH + RAISERROR (N'Failure returning results', 0,1) WITH NOWAIT; + IF @sql_select IS NOT NULL + BEGIN + SET @msg = N'Last @sql_select: ' + @sql_select; + RAISERROR(@msg, 0, 1) WITH NOWAIT; + END; -RAISERROR(N'Checking for plan warnings', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET b.plan_warnings = 1 -FROM #query_plan qp -JOIN #working_warnings b -ON qp.sql_handle = b.sql_handle -AND qp.query_plan.exist('/p:QueryPlan/p:Warnings') = 1 -OPTION (RECOMPILE); + SELECT @msg = @DatabaseName + N' database failed to process. ' + ERROR_MESSAGE(), @error_severity = ERROR_SEVERITY(), @error_state = ERROR_STATE(); + RAISERROR (@msg, @error_severity, @error_state) WITH NOWAIT; + + + WHILE @@TRANCOUNT > 0 + ROLLBACK; + RETURN; +END CATCH; -RAISERROR(N'Checking for implicit conversion', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET b.implicit_conversions = 1 -FROM #query_plan qp -JOIN #working_warnings b -ON qp.sql_handle = b.sql_handle -AND qp.query_plan.exist('/p:QueryPlan/p:Warnings/p:PlanAffectingConvert/@Expression[contains(., "CONVERT_IMPLICIT")]') = 1 -OPTION (RECOMPILE); +BEGIN TRY +BEGIN -IF @ExpertMode > 0 +IF (@ExportToExcel = 0 AND @HideSummary = 0 AND @SkipXML = 0) BEGIN -RAISERROR(N'Performing busy loops checks', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE p -SET busy_loops = CASE WHEN (x.estimated_executions / 100.0) > x.estimated_rows THEN 1 END -FROM #working_warnings p - JOIN ( - SELECT qs.sql_handle, - relop.value('sum(/p:RelOp/@EstimateRows)', 'float') AS estimated_rows , - relop.value('sum(/p:RelOp/@EstimateRewinds)', 'float') + relop.value('sum(/p:RelOp/@EstimateRebinds)', 'float') + 1.0 AS estimated_executions - FROM #relop qs - ) AS x ON p.sql_handle = x.sql_handle -OPTION (RECOMPILE); -END; - - -RAISERROR(N'Performing TVF join check', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE p -SET p.tvf_join = CASE WHEN x.tvf_join = 1 THEN 1 END -FROM #working_warnings p - JOIN ( - SELECT r.sql_handle, - 1 AS tvf_join - FROM #relop AS r - WHERE r.relop.exist('//p:RelOp[(@LogicalOp[.="Table-valued function"])]') = 1 - AND r.relop.exist('//p:RelOp[contains(@LogicalOp, "Join")]') = 1 - ) AS x ON p.sql_handle = x.sql_handle -OPTION (RECOMPILE); + RAISERROR('Building query plan summary data.', 0, 1) WITH NOWAIT; -IF @ExpertMode > 0 -BEGIN -RAISERROR(N'Checking for operator warnings', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -, x AS ( -SELECT r.sql_handle, - c.n.exist('//p:Warnings[(@NoJoinPredicate[.="1"])]') AS warning_no_join_predicate, - c.n.exist('//p:ColumnsWithNoStatistics') AS no_stats_warning , - c.n.exist('//p:Warnings') AS relop_warnings -FROM #relop AS r -CROSS APPLY r.relop.nodes('/p:RelOp/p:Warnings') AS c(n) -) -UPDATE b -SET b.warning_no_join_predicate = x.warning_no_join_predicate, - b.no_stats_warning = x.no_stats_warning, - b.relop_warnings = x.relop_warnings -FROM #working_warnings b -JOIN x ON x.sql_handle = b.sql_handle -OPTION (RECOMPILE); -END; + /* Build summary data */ + IF EXISTS (SELECT 1/0 + FROM #working_warnings + WHERE frequent_execution = 1 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 1, + 100, + 'Execution Pattern', + 'Frequently Executed Queries', + 'http://brentozar.com/blitzcache/frequently-executed-queries/', + 'Queries are being executed more than ' + + CAST (@execution_threshold AS VARCHAR(5)) + + ' times per minute. This can put additional load on the server, even when queries are lightweight.') ; + IF EXISTS (SELECT 1/0 + FROM #working_warnings + WHERE parameter_sniffing = 1 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 2, + 50, + 'Parameterization', + 'Parameter Sniffing', + 'http://brentozar.com/blitzcache/parameter-sniffing/', + 'There are signs of parameter sniffing (wide variance in rows return or time to execute). Investigate query patterns and tune code appropriately.') ; -RAISERROR(N'Checking for table variables', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -, x AS ( -SELECT r.sql_handle, - c.n.value('substring(@Table, 2, 1)','VARCHAR(100)') AS first_char -FROM #relop r -CROSS APPLY r.relop.nodes('//p:Object') AS c(n) -) -UPDATE b -SET b.is_table_variable = 1 -FROM #working_warnings b -JOIN x ON x.sql_handle = b.sql_handle -JOIN #working_metrics AS wm -ON b.plan_id = wm.plan_id -AND b.query_id = wm.query_id -AND wm.batch_sql_handle IS NOT NULL -WHERE x.first_char = '@' -OPTION (RECOMPILE); + /* Forced execution plans */ + IF EXISTS (SELECT 1/0 + FROM #working_warnings + WHERE is_forced_plan = 1 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 3, + 5, + 'Parameterization', + 'Forced Plans', + 'http://brentozar.com/blitzcache/forced-plans/', + 'Execution plans have been compiled with forced plans, either through FORCEPLAN, plan guides, or forced parameterization. This will make general tuning efforts less effective.'); + IF EXISTS (SELECT 1/0 + FROM #working_warnings + WHERE is_cursor = 1 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 4, + 200, + 'Cursors', + 'Cursors', + 'http://brentozar.com/blitzcache/cursors-found-slow-queries/', + 'There are cursors in the plan cache. This is neither good nor bad, but it is a thing. Cursors are weird in SQL Server.'); -IF @ExpertMode > 0 -BEGIN -RAISERROR(N'Checking for functions', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -, x AS ( -SELECT r.sql_handle, - n.fn.value('count(distinct-values(//p:UserDefinedFunction[not(@IsClrFunction)]))', 'INT') AS function_count, - n.fn.value('count(distinct-values(//p:UserDefinedFunction[@IsClrFunction = "1"]))', 'INT') AS clr_function_count -FROM #relop r -CROSS APPLY r.relop.nodes('/p:RelOp/p:ComputeScalar/p:DefinedValues/p:DefinedValue/p:ScalarOperator') n(fn) -) -UPDATE b -SET b.function_count = x.function_count, - b.clr_function_count = x.clr_function_count -FROM #working_warnings b -JOIN x ON x.sql_handle = b.sql_handle -OPTION (RECOMPILE); -END; + IF EXISTS (SELECT 1/0 + FROM #working_warnings + WHERE is_cursor = 1 + AND is_optimistic_cursor = 1 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 4, + 200, + 'Cursors', + 'Optimistic Cursors', + 'http://brentozar.com/blitzcache/cursors-found-slow-queries/', + 'There are optimistic cursors in the plan cache, which can harm performance.'); + IF EXISTS (SELECT 1/0 + FROM #working_warnings + WHERE is_cursor = 1 + AND is_forward_only_cursor = 0 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 4, + 200, + 'Cursors', + 'Non-forward Only Cursors', + 'http://brentozar.com/blitzcache/cursors-found-slow-queries/', + 'There are non-forward only cursors in the plan cache, which can harm performance.'); -RAISERROR(N'Checking for expensive key lookups', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET b.key_lookup_cost = x.key_lookup_cost -FROM #working_warnings b -JOIN ( -SELECT - r.sql_handle, - MAX(r.relop.value('sum(/p:RelOp/@EstimatedTotalSubtreeCost)', 'float')) AS key_lookup_cost -FROM #relop r -WHERE r.relop.exist('/p:RelOp/p:IndexScan[(@Lookup[.="1"])]') = 1 -GROUP BY r.sql_handle -) AS x ON x.sql_handle = b.sql_handle -OPTION (RECOMPILE); + IF EXISTS (SELECT 1/0 + FROM #working_warnings + WHERE is_cursor = 1 + AND is_cursor_dynamic = 1 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (4, + 200, + 'Cursors', + 'Dynamic Cursors', + 'http://brentozar.com/blitzcache/cursors-found-slow-queries/', + 'Dynamic Cursors inhibit parallelism!.'); + IF EXISTS (SELECT 1/0 + FROM #working_warnings + WHERE is_cursor = 1 + AND is_fast_forward_cursor = 1 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (4, + 200, + 'Cursors', + 'Fast Forward Cursors', + 'http://brentozar.com/blitzcache/cursors-found-slow-queries/', + 'Fast forward cursors inhibit parallelism!.'); + + IF EXISTS (SELECT 1/0 + FROM #working_warnings + WHERE is_forced_parameterized = 1 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 5, + 50, + 'Parameterization', + 'Forced Parameterization', + 'http://brentozar.com/blitzcache/forced-parameterization/', + 'Execution plans have been compiled with forced parameterization.') ; -RAISERROR(N'Checking for expensive remote queries', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET b.remote_query_cost = x.remote_query_cost -FROM #working_warnings b -JOIN ( -SELECT - r.sql_handle, - MAX(r.relop.value('sum(/p:RelOp/@EstimatedTotalSubtreeCost)', 'float')) AS remote_query_cost -FROM #relop r -WHERE r.relop.exist('/p:RelOp[(@PhysicalOp[contains(., "Remote")])]') = 1 -GROUP BY r.sql_handle -) AS x ON x.sql_handle = b.sql_handle -OPTION (RECOMPILE); + IF EXISTS (SELECT 1/0 + FROM #working_warnings p + WHERE p.is_parallel = 1 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 6, + 200, + 'Execution Plans', + 'Parallelism', + 'http://brentozar.com/blitzcache/parallel-plans-detected/', + 'Parallel plans detected. These warrant investigation, but are neither good nor bad.') ; + IF EXISTS (SELECT 1/0 + FROM #working_warnings p + WHERE p.near_parallel = 1 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 7, + 200, + 'Execution Plans', + 'Nearly Parallel', + 'http://brentozar.com/blitzcache/query-cost-near-cost-threshold-parallelism/', + 'Queries near the cost threshold for parallelism. These may go parallel when you least expect it.') ; -RAISERROR(N'Checking for expensive sorts', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET sort_cost = y.max_sort_cost -FROM #working_warnings b -JOIN ( - SELECT x.sql_handle, MAX((x.sort_io + x.sort_cpu)) AS max_sort_cost - FROM ( - SELECT - qs.sql_handle, - relop.value('sum(/p:RelOp/@EstimateIO)', 'float') AS sort_io, - relop.value('sum(/p:RelOp/@EstimateCPU)', 'float') AS sort_cpu - FROM #relop qs - WHERE [relop].exist('/p:RelOp[(@PhysicalOp[.="Sort"])]') = 1 - ) AS x - GROUP BY x.sql_handle - ) AS y -ON b.sql_handle = y.sql_handle -OPTION (RECOMPILE); + IF EXISTS (SELECT 1/0 + FROM #working_warnings p + WHERE p.plan_warnings = 1 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 8, + 50, + 'Execution Plans', + 'Query Plan Warnings', + 'http://brentozar.com/blitzcache/query-plan-warnings/', + 'Warnings detected in execution plans. SQL Server is telling you that something bad is going on that requires your attention.') ; -IF NOT EXISTS(SELECT 1/0 FROM #statements AS s WHERE s.is_cursor = 1) -BEGIN + IF EXISTS (SELECT 1/0 + FROM #working_warnings p + WHERE p.long_running = 1 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 9, + 50, + 'Performance', + 'Long Running Queries', + 'http://brentozar.com/blitzcache/long-running-queries/', + 'Long running queries have been found. These are queries with an average duration longer than ' + + CAST(@long_running_query_warning_seconds / 1000 / 1000 AS VARCHAR(5)) + + ' second(s). These queries should be investigated for additional tuning options.') ; -RAISERROR(N'No cursor plans found, skipping', 0, 1) WITH NOWAIT; + IF EXISTS (SELECT 1/0 + FROM #working_warnings p + WHERE p.missing_index_count > 0 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 10, + 50, + 'Performance', + 'Missing Index Request', + 'http://brentozar.com/blitzcache/missing-index-request/', + 'Queries found with missing indexes.'); -END + IF EXISTS (SELECT 1/0 + FROM #working_warnings p + WHERE p.downlevel_estimator = 1 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 13, + 200, + 'Cardinality', + 'Legacy Cardinality Estimator in Use', + 'http://brentozar.com/blitzcache/legacy-cardinality-estimator/', + 'A legacy cardinality estimator is being used by one or more queries. Investigate whether you need to be using this cardinality estimator. This may be caused by compatibility levels, global trace flags, or query level trace flags.'); -IF EXISTS(SELECT 1/0 FROM #statements AS s WHERE s.is_cursor = 1) -BEGIN + IF EXISTS (SELECT 1/0 + FROM #working_warnings p + WHERE p.implicit_conversions = 1 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 14, + 50, + 'Performance', + 'Implicit Conversions', + 'http://brentozar.com/go/implicit', + 'One or more queries are comparing two fields that are not of the same data type.') ; -RAISERROR(N'Cursor plans found, investigating', 0, 1) WITH NOWAIT; + IF EXISTS (SELECT 1/0 + FROM #working_warnings p + WHERE busy_loops = 1 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 16, + 100, + 'Performance', + 'Busy Loops', + 'http://brentozar.com/blitzcache/busy-loops/', + 'Operations have been found that are executed 100 times more often than the number of rows returned by each iteration. This is an indicator that something is off in query execution.'); -RAISERROR(N'Checking for Optimistic cursors', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET b.is_optimistic_cursor = 1 -FROM #working_warnings b -JOIN #statements AS s -ON b.sql_handle = s.sql_handle -CROSS APPLY s.statement.nodes('/p:StmtCursor') AS n1(fn) -WHERE n1.fn.exist('//p:CursorPlan/@CursorConcurrency[.="Optimistic"]') = 1 -AND s.is_cursor = 1 -OPTION (RECOMPILE); + IF EXISTS (SELECT 1/0 + FROM #working_warnings p + WHERE tvf_join = 1 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 17, + 50, + 'Performance', + 'Joining to table valued functions', + 'http://brentozar.com/blitzcache/tvf-join/', + 'Execution plans have been found that join to table valued functions (TVFs). TVFs produce inaccurate estimates of the number of rows returned and can lead to any number of query plan problems.'); + IF EXISTS (SELECT 1/0 + FROM #working_warnings + WHERE compile_timeout = 1 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 18, + 50, + 'Execution Plans', + 'Compilation timeout', + 'http://brentozar.com/blitzcache/compilation-timeout/', + 'Query compilation timed out for one or more queries. SQL Server did not find a plan that meets acceptable performance criteria in the time allotted so the best guess was returned. There is a very good chance that this plan isn''t even below average - it''s probably terrible.'); -RAISERROR(N'Checking if cursor is Forward Only', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET b.is_forward_only_cursor = 1 -FROM #working_warnings b -JOIN #statements AS s -ON b.sql_handle = s.sql_handle -CROSS APPLY s.statement.nodes('/p:StmtCursor') AS n1(fn) -WHERE n1.fn.exist('//p:CursorPlan/@ForwardOnly[.="true"]') = 1 -AND s.is_cursor = 1 -OPTION (RECOMPILE); + IF EXISTS (SELECT 1/0 + FROM #working_warnings + WHERE compile_memory_limit_exceeded = 1 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 19, + 50, + 'Execution Plans', + 'Compilation memory limit exceeded', + 'http://brentozar.com/blitzcache/compile-memory-limit-exceeded/', + 'The optimizer has a limited amount of memory available. One or more queries are complex enough that SQL Server was unable to allocate enough memory to fully optimize the query. A best fit plan was found, and it''s probably terrible.'); + IF EXISTS (SELECT 1/0 + FROM #working_warnings + WHERE warning_no_join_predicate = 1 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 20, + 10, + 'Execution Plans', + 'No join predicate', + 'http://brentozar.com/blitzcache/no-join-predicate/', + 'Operators in a query have no join predicate. This means that all rows from one table will be matched with all rows from anther table producing a Cartesian product. That''s a whole lot of rows. This may be your goal, but it''s important to investigate why this is happening.'); -RAISERROR(N'Checking if cursor is Fast Forward', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET b.is_fast_forward_cursor = 1 -FROM #working_warnings b -JOIN #statements AS s -ON b.sql_handle = s.sql_handle -CROSS APPLY s.statement.nodes('/p:StmtCursor') AS n1(fn) -WHERE n1.fn.exist('//p:CursorPlan/@CursorActualType[.="FastForward"]') = 1 -AND s.is_cursor = 1 -OPTION (RECOMPILE); + IF EXISTS (SELECT 1/0 + FROM #working_warnings + WHERE plan_multiple_plans = 1 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 21, + 200, + 'Execution Plans', + 'Multiple execution plans', + 'http://brentozar.com/blitzcache/multiple-plans/', + 'Queries exist with multiple execution plans (as determined by query_plan_hash). Investigate possible ways to parameterize these queries or otherwise reduce the plan count.'); + IF EXISTS (SELECT 1/0 + FROM #working_warnings + WHERE unmatched_index_count > 0 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 22, + 100, + 'Performance', + 'Unmatched indexes', + 'http://brentozar.com/blitzcache/unmatched-indexes', + 'An index could have been used, but SQL Server chose not to use it - likely due to parameterization and filtered indexes.'); -RAISERROR(N'Checking for Dynamic cursors', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET b.is_cursor_dynamic = 1 -FROM #working_warnings b -JOIN #statements AS s -ON b.sql_handle = s.sql_handle -CROSS APPLY s.statement.nodes('/p:StmtCursor') AS n1(fn) -WHERE n1.fn.exist('//p:CursorPlan/@CursorActualType[.="Dynamic"]') = 1 -AND s.is_cursor = 1 -OPTION (RECOMPILE); + IF EXISTS (SELECT 1/0 + FROM #working_warnings + WHERE unparameterized_query = 1 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 23, + 100, + 'Parameterization', + 'Unparameterized queries', + 'http://brentozar.com/blitzcache/unparameterized-queries', + 'Unparameterized queries found. These could be ad hoc queries, data exploration, or queries using "OPTIMIZE FOR UNKNOWN".'); -END - -IF @ExpertMode > 0 -BEGIN -RAISERROR(N'Checking for bad scans and plan forcing', 0, 1) WITH NOWAIT; -;WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET -b.is_table_scan = x.is_table_scan, -b.backwards_scan = x.backwards_scan, -b.forced_index = x.forced_index, -b.forced_seek = x.forced_seek, -b.forced_scan = x.forced_scan -FROM #working_warnings b -JOIN ( -SELECT - r.sql_handle, - 0 AS is_table_scan, - q.n.exist('@ScanDirection[.="BACKWARD"]') AS backwards_scan, - q.n.value('@ForcedIndex', 'bit') AS forced_index, - q.n.value('@ForceSeek', 'bit') AS forced_seek, - q.n.value('@ForceScan', 'bit') AS forced_scan -FROM #relop r -CROSS APPLY r.relop.nodes('//p:IndexScan') AS q(n) -UNION ALL -SELECT - r.sql_handle, - 1 AS is_table_scan, - q.n.exist('@ScanDirection[.="BACKWARD"]') AS backwards_scan, - q.n.value('@ForcedIndex', 'bit') AS forced_index, - q.n.value('@ForceSeek', 'bit') AS forced_seek, - q.n.value('@ForceScan', 'bit') AS forced_scan -FROM #relop r -CROSS APPLY r.relop.nodes('//p:TableScan') AS q(n) -) AS x ON b.sql_handle = x.sql_handle -OPTION (RECOMPILE); -END; + IF EXISTS (SELECT 1/0 + FROM #working_warnings + WHERE is_trivial = 1 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 24, + 100, + 'Execution Plans', + 'Trivial Plans', + 'http://brentozar.com/blitzcache/trivial-plans', + 'Trivial plans get almost no optimization. If you''re finding these in the top worst queries, something may be going wrong.'); + + IF EXISTS (SELECT 1/0 + FROM #working_warnings p + WHERE p.is_forced_serial= 1 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 25, + 10, + 'Execution Plans', + 'Forced Serialization', + 'http://www.brentozar.com/blitzcache/forced-serialization/', + 'Something in your plan is forcing a serial query. Further investigation is needed if this is not by design.') ; + IF EXISTS (SELECT 1/0 + FROM #working_warnings p + WHERE p.is_key_lookup_expensive= 1 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 26, + 100, + 'Execution Plans', + 'Expensive Key Lookups', + 'http://www.brentozar.com/blitzcache/expensive-key-lookups/', + 'There''s a key lookup in your plan that costs >=50% of the total plan cost.') ; -IF @ExpertMode > 0 -BEGIN -RAISERROR(N'Checking for computed columns that reference scalar UDFs', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET b.is_computed_scalar = x.computed_column_function -FROM #working_warnings b -JOIN ( -SELECT r.sql_handle, - n.fn.value('count(distinct-values(//p:UserDefinedFunction[not(@IsClrFunction)]))', 'INT') AS computed_column_function -FROM #relop r -CROSS APPLY r.relop.nodes('/p:RelOp/p:ComputeScalar/p:DefinedValues/p:DefinedValue/p:ScalarOperator') n(fn) -WHERE n.fn.exist('/p:RelOp/p:ComputeScalar/p:DefinedValues/p:DefinedValue/p:ColumnReference[(@ComputedColumn[.="1"])]') = 1 -) AS x ON x.sql_handle = b.sql_handle -OPTION (RECOMPILE); -END; + IF EXISTS (SELECT 1/0 + FROM #working_warnings p + WHERE p.is_remote_query_expensive= 1 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 28, + 100, + 'Execution Plans', + 'Expensive Remote Query', + 'http://www.brentozar.com/blitzcache/expensive-remote-query/', + 'There''s a remote query in your plan that costs >=50% of the total plan cost.') ; + IF EXISTS (SELECT 1/0 + FROM #working_warnings p + WHERE p.trace_flags_session IS NOT NULL + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 29, + 100, + 'Trace Flags', + 'Session Level Trace Flags Enabled', + 'https://www.brentozar.com/blitz/trace-flags-enabled-globally/', + 'Someone is enabling session level Trace Flags in a query.') ; -RAISERROR(N'Checking for filters that reference scalar UDFs', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET b.is_computed_filter = x.filter_function -FROM #working_warnings b -JOIN ( -SELECT -r.sql_handle, -c.n.value('count(distinct-values(//p:UserDefinedFunction[not(@IsClrFunction)]))', 'INT') AS filter_function -FROM #relop AS r -CROSS APPLY r.relop.nodes('/p:RelOp/p:Filter/p:Predicate/p:ScalarOperator/p:Compare/p:ScalarOperator/p:UserDefinedFunction') c(n) -) x ON x.sql_handle = b.sql_handle -OPTION (RECOMPILE); + IF EXISTS (SELECT 1/0 + FROM #working_warnings p + WHERE p.is_unused_grant IS NOT NULL + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 30, + 100, + 'Unused memory grants', + 'Queries are asking for more memory than they''re using', + 'https://www.brentozar.com/blitzcache/unused-memory-grants/', + 'Queries have large unused memory grants. This can cause concurrency issues, if queries are waiting a long time to get memory to run.') ; + IF EXISTS (SELECT 1/0 + FROM #working_warnings p + WHERE p.function_count > 0 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 31, + 100, + 'Compute Scalar That References A Function', + 'This could be trouble if you''re using Scalar Functions or MSTVFs', + 'https://www.brentozar.com/blitzcache/compute-scalar-functions/', + 'Both of these will force queries to run serially, run at least once per row, and may result in poor cardinality estimates.') ; -IF @ExpertMode > 0 -BEGIN -RAISERROR(N'Checking modification queries that hit lots of indexes', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p), -IndexOps AS -( - SELECT - r.query_hash, - c.n.value('@PhysicalOp', 'VARCHAR(100)') AS op_name, - c.n.exist('@PhysicalOp[.="Index Insert"]') AS ii, - c.n.exist('@PhysicalOp[.="Index Update"]') AS iu, - c.n.exist('@PhysicalOp[.="Index Delete"]') AS id, - c.n.exist('@PhysicalOp[.="Clustered Index Insert"]') AS cii, - c.n.exist('@PhysicalOp[.="Clustered Index Update"]') AS ciu, - c.n.exist('@PhysicalOp[.="Clustered Index Delete"]') AS cid, - c.n.exist('@PhysicalOp[.="Table Insert"]') AS ti, - c.n.exist('@PhysicalOp[.="Table Update"]') AS tu, - c.n.exist('@PhysicalOp[.="Table Delete"]') AS td - FROM #relop AS r - CROSS APPLY r.relop.nodes('/p:RelOp') c(n) - OUTER APPLY r.relop.nodes('/p:RelOp/p:ScalarInsert/p:Object') q(n) - OUTER APPLY r.relop.nodes('/p:RelOp/p:Update/p:Object') o2(n) - OUTER APPLY r.relop.nodes('/p:RelOp/p:SimpleUpdate/p:Object') o3(n) -), iops AS -( - SELECT ios.query_hash, - SUM(CONVERT(TINYINT, ios.ii)) AS index_insert_count, - SUM(CONVERT(TINYINT, ios.iu)) AS index_update_count, - SUM(CONVERT(TINYINT, ios.id)) AS index_delete_count, - SUM(CONVERT(TINYINT, ios.cii)) AS cx_insert_count, - SUM(CONVERT(TINYINT, ios.ciu)) AS cx_update_count, - SUM(CONVERT(TINYINT, ios.cid)) AS cx_delete_count, - SUM(CONVERT(TINYINT, ios.ti)) AS table_insert_count, - SUM(CONVERT(TINYINT, ios.tu)) AS table_update_count, - SUM(CONVERT(TINYINT, ios.td)) AS table_delete_count - FROM IndexOps AS ios - WHERE ios.op_name IN ('Index Insert', 'Index Delete', 'Index Update', - 'Clustered Index Insert', 'Clustered Index Delete', 'Clustered Index Update', - 'Table Insert', 'Table Delete', 'Table Update') - GROUP BY ios.query_hash) -UPDATE b -SET b.index_insert_count = iops.index_insert_count, - b.index_update_count = iops.index_update_count, - b.index_delete_count = iops.index_delete_count, - b.cx_insert_count = iops.cx_insert_count, - b.cx_update_count = iops.cx_update_count, - b.cx_delete_count = iops.cx_delete_count, - b.table_insert_count = iops.table_insert_count, - b.table_update_count = iops.table_update_count, - b.table_delete_count = iops.table_delete_count -FROM #working_warnings AS b -JOIN iops ON iops.query_hash = b.query_hash -OPTION (RECOMPILE); -END; + IF EXISTS (SELECT 1/0 + FROM #working_warnings p + WHERE p.clr_function_count > 0 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 32, + 100, + 'Compute Scalar That References A CLR Function', + 'This could be trouble if your CLR functions perform data access', + 'https://www.brentozar.com/blitzcache/compute-scalar-functions/', + 'May force queries to run serially, run at least once per row, and may result in poor cardinlity estimates.') ; -IF @ExpertMode > 0 -BEGIN -RAISERROR(N'Checking for Spatial index use', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET b.is_spatial = x.is_spatial -FROM #working_warnings AS b -JOIN ( -SELECT r.sql_handle, - 1 AS is_spatial -FROM #relop r -CROSS APPLY r.relop.nodes('/p:RelOp//p:Object') n(fn) -WHERE n.fn.exist('(@IndexKind[.="Spatial"])') = 1 -) AS x ON x.sql_handle = b.sql_handle -OPTION (RECOMPILE); -END; + IF EXISTS (SELECT 1/0 + FROM #working_warnings p + WHERE p.is_table_variable = 1 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 33, + 100, + 'Table Variables detected', + 'Beware nasty side effects', + 'https://www.brentozar.com/blitzcache/table-variables/', + 'All modifications are single threaded, and selects have really low row estimates.') ; -RAISERROR(N'Checking for forced serialization', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET b.is_forced_serial = 1 -FROM #query_plan qp -JOIN #working_warnings AS b -ON qp.sql_handle = b.sql_handle -AND b.is_parallel IS NULL -AND qp.query_plan.exist('/p:QueryPlan/@NonParallelPlanReason') = 1 -OPTION (RECOMPILE); + IF EXISTS (SELECT 1/0 + FROM #working_warnings p + WHERE p.no_stats_warning = 1 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 35, + 100, + 'Columns with no statistics', + 'Poor cardinality estimates may ensue', + 'https://www.brentozar.com/blitzcache/columns-no-statistics/', + 'Sometimes this happens with indexed views, other times because auto create stats is turned off.') ; + IF EXISTS (SELECT 1/0 + FROM #working_warnings p + WHERE p.relop_warnings = 1 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 36, + 100, + 'Operator Warnings', + 'SQL is throwing operator level plan warnings', + 'http://brentozar.com/blitzcache/query-plan-warnings/', + 'Check the plan for more details.') ; -IF @ExpertMode > 0 -BEGIN -RAISERROR(N'Checking for ColumnStore queries operating in Row Mode instead of Batch Mode', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET b.columnstore_row_mode = x.is_row_mode -FROM #working_warnings AS b -JOIN ( -SELECT - r.sql_handle, - r.relop.exist('/p:RelOp[(@EstimatedExecutionMode[.="Row"])]') AS is_row_mode -FROM #relop r -WHERE r.relop.exist('/p:RelOp/p:IndexScan[(@Storage[.="ColumnStore"])]') = 1 -) AS x ON x.sql_handle = b.sql_handle -OPTION (RECOMPILE); -END; + IF EXISTS (SELECT 1/0 + FROM #working_warnings p + WHERE p.is_table_scan = 1 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 37, + 100, + 'Table Scans', + 'Your database has HEAPs', + 'https://www.brentozar.com/archive/2012/05/video-heaps/', + 'This may not be a problem. Run sp_BlitzIndex for more information.') ; + + IF EXISTS (SELECT 1/0 + FROM #working_warnings p + WHERE p.backwards_scan = 1 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 38, + 100, + 'Backwards Scans', + 'Indexes are being read backwards', + 'https://www.brentozar.com/blitzcache/backwards-scans/', + 'This isn''t always a problem. They can cause serial zones in plans, and may need an index to match sort order.') ; + IF EXISTS (SELECT 1/0 + FROM #working_warnings p + WHERE p.forced_index = 1 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 39, + 100, + 'Index forcing', + 'Someone is using hints to force index usage', + 'https://www.brentozar.com/blitzcache/optimizer-forcing/', + 'This can cause inefficient plans, and will prevent missing index requests.') ; -IF @ExpertMode > 0 -BEGIN -RAISERROR('Checking for row level security only', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET b.is_row_level = 1 -FROM #working_warnings b -JOIN #statements s -ON s.query_hash = b.query_hash -WHERE s.statement.exist('/p:StmtSimple/@SecurityPolicyApplied[.="true"]') = 1 -OPTION (RECOMPILE); -END; + IF EXISTS (SELECT 1/0 + FROM #working_warnings p + WHERE p.forced_seek = 1 + OR p.forced_scan = 1 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 40, + 100, + 'Seek/Scan forcing', + 'Someone is using hints to force index seeks/scans', + 'https://www.brentozar.com/blitzcache/optimizer-forcing/', + 'This can cause inefficient plans by taking seek vs scan choice away from the optimizer.') ; + IF EXISTS (SELECT 1/0 + FROM #working_warnings p + WHERE p.columnstore_row_mode = 1 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 41, + 100, + 'ColumnStore indexes operating in Row Mode', + 'Batch Mode is optimal for ColumnStore indexes', + 'https://www.brentozar.com/blitzcache/columnstore-indexes-operating-row-mode/', + 'ColumnStore indexes operating in Row Mode indicate really poor query choices.') ; -IF @ExpertMode > 0 -BEGIN -RAISERROR('Checking for wonky Index Spools', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES ( - 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) -, selects -AS ( SELECT s.plan_id, s.query_id - FROM #statements AS s - WHERE s.statement.exist('/p:StmtSimple/@StatementType[.="SELECT"]') = 1 ) -, spools -AS ( SELECT DISTINCT r.plan_id, - r.query_id, - c.n.value('@EstimateRows', 'FLOAT') AS estimated_rows, - c.n.value('@EstimateIO', 'FLOAT') AS estimated_io, - c.n.value('@EstimateCPU', 'FLOAT') AS estimated_cpu, - c.n.value('@EstimateRebinds', 'FLOAT') AS estimated_rebinds -FROM #relop AS r -JOIN selects AS s -ON s.plan_id = r.plan_id - AND s.query_id = r.query_id -CROSS APPLY r.relop.nodes('/p:RelOp') AS c(n) -WHERE r.relop.exist('/p:RelOp[@PhysicalOp="Index Spool" and @LogicalOp="Eager Spool"]') = 1 -) -UPDATE ww - SET ww.index_spool_rows = sp.estimated_rows, - ww.index_spool_cost = ((sp.estimated_io * sp.estimated_cpu) * CASE WHEN sp.estimated_rebinds < 1 THEN 1 ELSE sp.estimated_rebinds END) + IF EXISTS (SELECT 1/0 + FROM #working_warnings p + WHERE p.is_computed_scalar = 1 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 42, + 50, + 'Computed Columns Referencing Scalar UDFs', + 'This makes a whole lot of stuff run serially', + 'https://www.brentozar.com/blitzcache/computed-columns-referencing-functions/', + 'This can cause a whole mess of bad serializartion problems.') ; -FROM #working_warnings ww -JOIN spools sp -ON ww.plan_id = sp.plan_id -AND ww.query_id = sp.query_id -OPTION (RECOMPILE); -END; + IF EXISTS (SELECT 1/0 + FROM #working_warnings p + WHERE p.is_sort_expensive = 1 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 43, + 100, + 'Execution Plans', + 'Expensive Sort', + 'http://www.brentozar.com/blitzcache/expensive-sorts/', + 'There''s a sort in your plan that costs >=50% of the total plan cost.') ; + IF EXISTS (SELECT 1/0 + FROM #working_warnings p + WHERE p.is_computed_filter = 1 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 44, + 50, + 'Filters Referencing Scalar UDFs', + 'This forces serialization', + 'https://www.brentozar.com/blitzcache/compute-scalar-functions/', + 'Someone put a Scalar UDF in the WHERE clause!') ; -IF (PARSENAME(CONVERT(VARCHAR(128), SERVERPROPERTY ('PRODUCTVERSION')), 4)) >= 14 -OR ((PARSENAME(CONVERT(VARCHAR(128), SERVERPROPERTY ('PRODUCTVERSION')), 4)) = 13 - AND PARSENAME(CONVERT(VARCHAR(128), SERVERPROPERTY ('PRODUCTVERSION')), 2) >= 5026) + IF EXISTS (SELECT 1/0 + FROM #working_warnings p + WHERE p.index_ops >= 5 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 45, + 100, + 'Many Indexes Modified', + 'Write Queries Are Hitting >= 5 Indexes', + 'https://www.brentozar.com/blitzcache/many-indexes-modified/', + 'This can cause lots of hidden I/O -- Run sp_BlitzIndex for more information.') ; -BEGIN + IF EXISTS (SELECT 1/0 + FROM #working_warnings p + WHERE p.is_row_level = 1 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 46, + 100, + 'Plan Confusion', + 'Row Level Security is in use', + 'https://www.brentozar.com/blitzcache/row-level-security/', + 'You may see a lot of confusing junk in your query plan.') ; -RAISERROR(N'Beginning 2017 and 2016 SP2 specfic checks', 0, 1) WITH NOWAIT; + IF EXISTS (SELECT 1/0 + FROM #working_warnings p + WHERE p.is_spatial = 1 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 47, + 200, + 'Spatial Abuse', + 'You hit a Spatial Index', + 'https://www.brentozar.com/blitzcache/spatial-indexes/', + 'Purely informational.') ; -IF @ExpertMode > 0 -BEGIN -RAISERROR('Gathering stats information', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -INSERT #stats_agg WITH (TABLOCK) - (sql_handle, last_update, modification_count, sampling_percent, [statistics], [table], [schema], [database]) -SELECT qp.sql_handle, - x.c.value('@LastUpdate', 'DATETIME2(7)') AS LastUpdate, - x.c.value('@ModificationCount', 'BIGINT') AS ModificationCount, - x.c.value('@SamplingPercent', 'FLOAT') AS SamplingPercent, - x.c.value('@Statistics', 'NVARCHAR(258)') AS [Statistics], - x.c.value('@Table', 'NVARCHAR(258)') AS [Table], - x.c.value('@Schema', 'NVARCHAR(258)') AS [Schema], - x.c.value('@Database', 'NVARCHAR(258)') AS [Database] -FROM #query_plan AS qp -CROSS APPLY qp.query_plan.nodes('//p:OptimizerStatsUsage/p:StatisticsInfo') x (c) -OPTION (RECOMPILE); + IF EXISTS (SELECT 1/0 + FROM #working_warnings p + WHERE p.index_dml = 1 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 48, + 150, + 'Index DML', + 'Indexes were created or dropped', + 'https://www.brentozar.com/blitzcache/index-dml/', + 'This can cause recompiles and stuff.') ; + IF EXISTS (SELECT 1/0 + FROM #working_warnings p + WHERE p.table_dml = 1 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 49, + 150, + 'Table DML', + 'Tables were created or dropped', + 'https://www.brentozar.com/blitzcache/table-dml/', + 'This can cause recompiles and stuff.') ; -RAISERROR('Checking for stale stats', 0, 1) WITH NOWAIT; -WITH stale_stats AS ( - SELECT sa.sql_handle - FROM #stats_agg AS sa - GROUP BY sa.sql_handle - HAVING MAX(sa.last_update) <= DATEADD(DAY, -7, SYSDATETIME()) - AND AVG(sa.modification_count) >= 100000 -) -UPDATE b -SET b.stale_stats = 1 -FROM #working_warnings AS b -JOIN stale_stats os -ON b.sql_handle = os.sql_handle -OPTION (RECOMPILE); -END; + IF EXISTS (SELECT 1/0 + FROM #working_warnings p + WHERE p.long_running_low_cpu = 1 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 50, + 150, + 'Long Running Low CPU', + 'You have a query that runs for much longer than it uses CPU', + 'https://www.brentozar.com/blitzcache/long-running-low-cpu/', + 'This can be a sign of blocking, linked servers, or poor client application code (ASYNC_NETWORK_IO).') ; + IF EXISTS (SELECT 1/0 + FROM #working_warnings p + WHERE p.low_cost_high_cpu = 1 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 51, + 150, + 'Low Cost Query With High CPU', + 'You have a low cost query that uses a lot of CPU', + 'https://www.brentozar.com/blitzcache/low-cost-high-cpu/', + 'This can be a sign of functions or Dynamic SQL that calls black-box code.') ; -IF (PARSENAME(CONVERT(VARCHAR(128), SERVERPROPERTY ('PRODUCTVERSION')), 4)) >= 14 - AND @ExpertMode > 0 -BEGIN -RAISERROR(N'Checking for Adaptive Joins', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p), -aj AS ( - SELECT r.sql_handle - FROM #relop AS r - CROSS APPLY r.relop.nodes('//p:RelOp') x(c) - WHERE x.c.exist('@IsAdaptive[.=1]') = 1 -) -UPDATE b -SET b.is_adaptive = 1 -FROM #working_warnings AS b -JOIN aj -ON b.sql_handle = aj.sql_handle -OPTION (RECOMPILE); -END; + IF EXISTS (SELECT 1/0 + FROM #working_warnings p + WHERE p.stale_stats = 1 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 52, + 150, + 'Biblical Statistics', + 'Statistics used in queries are >7 days old with >100k modifications', + 'https://www.brentozar.com/blitzcache/stale-statistics/', + 'Ever heard of updating statistics?') ; + IF EXISTS (SELECT 1/0 + FROM #working_warnings p + WHERE p.is_adaptive = 1 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 53, + 150, + 'Adaptive joins', + 'This is pretty cool -- you''re living in the future.', + 'https://www.brentozar.com/blitzcache/adaptive-joins/', + 'Joe Sack rules.') ; -IF @ExpertMode > 0 -BEGIN; -RAISERROR(N'Checking for Row Goals', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p), -row_goals AS( -SELECT qs.query_hash -FROM #relop qs -WHERE relop.value('sum(/p:RelOp/@EstimateRowsWithoutRowGoal)', 'float') > 0 -) -UPDATE b -SET b.is_row_goal = 1 -FROM #working_warnings b -JOIN row_goals -ON b.query_hash = row_goals.query_hash -OPTION (RECOMPILE); -END; + IF EXISTS (SELECT 1/0 + FROM #working_warnings p + WHERE p.is_spool_expensive = 1 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 54, + 150, + 'Expensive Index Spool', + 'You have an index spool, this is usually a sign that there''s an index missing somewhere.', + 'https://www.brentozar.com/blitzcache/eager-index-spools/', + 'Check operator predicates and output for index definition guidance') ; -END; + IF EXISTS (SELECT 1/0 + FROM #working_warnings p + WHERE p.is_spool_more_rows = 1 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 55, + 150, + 'Index Spools Many Rows', + 'You have an index spool that spools more rows than the query returns', + 'https://www.brentozar.com/blitzcache/eager-index-spools/', + 'Check operator predicates and output for index definition guidance') ; + IF EXISTS (SELECT 1/0 + FROM #working_warnings p + WHERE p.is_bad_estimate = 1 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 56, + 100, + 'Potentially bad cardinality estimates', + 'Estimated rows are different from average rows by a factor of 10000', + 'https://www.brentozar.com/blitzcache/bad-estimates/', + 'This may indicate a performance problem if mismatches occur regularly') ; -RAISERROR(N'Performing query level checks', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET b.missing_index_count = query_plan.value('count(//p:QueryPlan/p:MissingIndexes/p:MissingIndexGroup)', 'int') , - b.unmatched_index_count = CASE WHEN is_trivial <> 1 THEN query_plan.value('count(//p:QueryPlan/p:UnmatchedIndexes/p:Parameterization/p:Object)', 'int') END -FROM #query_plan qp -JOIN #working_warnings AS b -ON b.query_hash = qp.query_hash -OPTION (RECOMPILE); + IF EXISTS (SELECT 1/0 + FROM #working_warnings p + WHERE p.is_big_log = 1 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 57, + 100, + 'High transaction log use', + 'This query on average uses more than half of the transaction log', + 'http://michaeljswart.com/2014/09/take-care-when-scripting-batches/', + 'This is probably a sign that you need to start batching queries') ; + + IF EXISTS (SELECT 1/0 + FROM #working_warnings p + WHERE p.is_big_tempdb = 1 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 58, + 100, + 'High tempdb use', + 'This query uses more than half of a data file on average', + 'No URL yet', + 'You should take a look at tempdb waits to see if you''re having problems') ; + + IF EXISTS (SELECT 1/0 + FROM #working_warnings p + WHERE p.is_row_goal = 1 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (59, + 200, + 'Row Goals', + 'This query had row goals introduced', + 'https://www.brentozar.com/archive/2018/01/sql-server-2017-cu3-adds-optimizer-row-goal-information-query-plans/', + 'This can be good or bad, and should be investigated for high read queries') ; + IF EXISTS (SELECT 1/0 + FROM #working_warnings p + WHERE p.is_mstvf = 1 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 60, + 100, + 'MSTVFs', + 'These have many of the same problems scalar UDFs have', + 'http://brentozar.com/blitzcache/tvf-join/', + 'Execution plans have been found that join to table valued functions (TVFs). TVFs produce inaccurate estimates of the number of rows returned and can lead to any number of query plan problems.'); -RAISERROR(N'Trace flag checks', 0, 1) WITH NOWAIT; -;WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -, tf_pretty AS ( -SELECT qp.sql_handle, - q.n.value('@Value', 'INT') AS trace_flag, - q.n.value('@Scope', 'VARCHAR(10)') AS scope -FROM #query_plan qp -CROSS APPLY qp.query_plan.nodes('/p:QueryPlan/p:TraceFlags/p:TraceFlag') AS q(n) -) -INSERT #trace_flags WITH (TABLOCK) - (sql_handle, global_trace_flags, session_trace_flags ) -SELECT DISTINCT tf1.sql_handle , - STUFF(( - SELECT DISTINCT N', ' + CONVERT(NVARCHAR(5), tf2.trace_flag) - FROM tf_pretty AS tf2 - WHERE tf1.sql_handle = tf2.sql_handle - AND tf2.scope = 'Global' - FOR XML PATH(N'')), 1, 2, N'' - ) AS global_trace_flags, - STUFF(( - SELECT DISTINCT N', ' + CONVERT(NVARCHAR(5), tf2.trace_flag) - FROM tf_pretty AS tf2 - WHERE tf1.sql_handle = tf2.sql_handle - AND tf2.scope = 'Session' - FOR XML PATH(N'')), 1, 2, N'' - ) AS session_trace_flags -FROM tf_pretty AS tf1 -OPTION (RECOMPILE); + IF EXISTS (SELECT 1/0 + FROM #working_warnings p + WHERE p.is_mstvf = 1 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (61, + 100, + 'Many to Many Merge', + 'These use secret worktables that could be doing lots of reads', + 'https://www.brentozar.com/archive/2018/04/many-mysteries-merge-joins/', + 'Occurs when join inputs aren''t known to be unique. Can be really bad when parallel.'); -UPDATE b -SET b.trace_flags_session = tf.session_trace_flags -FROM #working_warnings AS b -JOIN #trace_flags tf -ON tf.sql_handle = b.sql_handle -OPTION (RECOMPILE); + IF EXISTS (SELECT 1/0 + FROM #working_warnings p + WHERE p.is_nonsargable = 1 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (62, + 50, + 'Non-SARGable queries', + 'Queries may be using', + 'https://www.brentozar.com/blitzcache/non-sargable-predicates/', + 'Occurs when join inputs aren''t known to be unique. Can be really bad when parallel.'); + + IF EXISTS (SELECT 1/0 + FROM #working_warnings p + WHERE p.is_paul_white_electric = 1 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 998, + 200, + 'Is Paul White Electric?', + 'This query has a Switch operator in it!', + 'https://www.sql.kiwi/2013/06/hello-operator-my-switch-is-bored.html', + 'You should email this query plan to Paul: SQLkiwi at gmail dot com') ; + + + + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + SELECT + 999, + 200, + 'Database Level Statistics', + 'The database ' + sa.[database] + ' last had a stats update on ' + CONVERT(NVARCHAR(10), CONVERT(DATE, MAX(sa.last_update))) + ' and has ' + CONVERT(NVARCHAR(10), AVG(sa.modification_count)) + ' modifications on average.' AS Finding, + 'https://www.brentozar.com/blitzcache/stale-statistics/' AS URL, + 'Consider updating statistics more frequently,' AS Details + FROM #stats_agg AS sa + GROUP BY sa.[database] + HAVING MAX(sa.last_update) <= DATEADD(DAY, -7, SYSDATETIME()) + AND AVG(sa.modification_count) >= 100000; + + IF EXISTS (SELECT 1/0 + FROM #trace_flags AS tf + WHERE tf.global_trace_flags IS NOT NULL + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 1000, + 255, + 'Global Trace Flags Enabled', + 'You have Global Trace Flags enabled on your server', + 'https://www.brentozar.com/blitz/trace-flags-enabled-globally/', + 'You have the following Global Trace Flags enabled: ' + (SELECT TOP 1 tf.global_trace_flags FROM #trace_flags AS tf WHERE tf.global_trace_flags IS NOT NULL)) ; -RAISERROR(N'Checking for MSTVFs', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET b.is_mstvf = 1 -FROM #relop AS r -JOIN #working_warnings AS b -ON b.sql_handle = r.sql_handle -WHERE r.relop.exist('/p:RelOp[(@EstimateRows="100" or @EstimateRows="1") and @LogicalOp="Table-valued function"]') = 1 -OPTION (RECOMPILE); -IF @ExpertMode > 0 -BEGIN -RAISERROR(N'Checking for many to many merge joins', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET b.is_mm_join = 1 -FROM #relop AS r -JOIN #working_warnings AS b -ON b.sql_handle = r.sql_handle -WHERE r.relop.exist('/p:RelOp/p:Merge/@ManyToMany[.="1"]') = 1 -OPTION (RECOMPILE); -END; + /* + Return worsts + */ + WITH worsts AS ( + SELECT gi.flat_date, + gi.start_range, + gi.end_range, + gi.total_avg_duration_ms, + gi.total_avg_cpu_time_ms, + gi.total_avg_logical_io_reads_mb, + gi.total_avg_physical_io_reads_mb, + gi.total_avg_logical_io_writes_mb, + gi.total_avg_query_max_used_memory_mb, + gi.total_rowcount, + gi.total_avg_log_bytes_mb, + gi.total_avg_tempdb_space, + gi.total_max_duration_ms, + gi.total_max_cpu_time_ms, + gi.total_max_logical_io_reads_mb, + gi.total_max_physical_io_reads_mb, + gi.total_max_logical_io_writes_mb, + gi.total_max_query_max_used_memory_mb, + gi.total_max_log_bytes_mb, + gi.total_max_tempdb_space, + CONVERT(NVARCHAR(20), gi.flat_date) AS worst_date, + CASE WHEN DATEPART(HOUR, gi.start_range) = 0 THEN ' midnight ' + WHEN DATEPART(HOUR, gi.start_range) <= 12 THEN CONVERT(NVARCHAR(3), DATEPART(HOUR, gi.start_range)) + 'am ' + WHEN DATEPART(HOUR, gi.start_range) > 12 THEN CONVERT(NVARCHAR(3), DATEPART(HOUR, gi.start_range) -12) + 'pm ' + END AS worst_start_time, + CASE WHEN DATEPART(HOUR, gi.end_range) = 0 THEN ' midnight ' + WHEN DATEPART(HOUR, gi.end_range) <= 12 THEN CONVERT(NVARCHAR(3), DATEPART(HOUR, gi.end_range)) + 'am ' + WHEN DATEPART(HOUR, gi.end_range) > 12 THEN CONVERT(NVARCHAR(3), DATEPART(HOUR, gi.end_range) -12) + 'pm ' + END AS worst_end_time + FROM #grouped_interval AS gi + ), /*averages*/ + duration_worst AS ( + SELECT TOP 1 'Your worst avg duration range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg + FROM worsts + ORDER BY worsts.total_avg_duration_ms DESC + ), + cpu_worst AS ( + SELECT TOP 1 'Your worst avg cpu range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg + FROM worsts + ORDER BY worsts.total_avg_cpu_time_ms DESC + ), + logical_reads_worst AS ( + SELECT TOP 1 'Your worst avg logical read range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg + FROM worsts + ORDER BY worsts.total_avg_logical_io_reads_mb DESC + ), + physical_reads_worst AS ( + SELECT TOP 1 'Your worst avg physical read range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg + FROM worsts + ORDER BY worsts.total_avg_physical_io_reads_mb DESC + ), + logical_writes_worst AS ( + SELECT TOP 1 'Your worst avg logical write range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg + FROM worsts + ORDER BY worsts.total_avg_logical_io_writes_mb DESC + ), + memory_worst AS ( + SELECT TOP 1 'Your worst avg memory range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg + FROM worsts + ORDER BY worsts.total_avg_query_max_used_memory_mb DESC + ), + rowcount_worst AS ( + SELECT TOP 1 'Your worst avg row count range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg + FROM worsts + ORDER BY worsts.total_rowcount DESC + ), + logbytes_worst AS ( + SELECT TOP 1 'Your worst avg log bytes range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg + FROM worsts + ORDER BY worsts.total_avg_log_bytes_mb DESC + ), + tempdb_worst AS ( + SELECT TOP 1 'Your worst avg tempdb range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg + FROM worsts + ORDER BY worsts.total_avg_tempdb_space DESC + )/*maxes*/, + max_duration_worst AS ( + SELECT TOP 1 'Your worst max duration range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg + FROM worsts + ORDER BY worsts.total_max_duration_ms DESC + ), + max_cpu_worst AS ( + SELECT TOP 1 'Your worst max cpu range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg + FROM worsts + ORDER BY worsts.total_max_cpu_time_ms DESC + ), + max_logical_reads_worst AS ( + SELECT TOP 1 'Your worst max logical read range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg + FROM worsts + ORDER BY worsts.total_max_logical_io_reads_mb DESC + ), + max_physical_reads_worst AS ( + SELECT TOP 1 'Your worst max physical read range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg + FROM worsts + ORDER BY worsts.total_max_physical_io_reads_mb DESC + ), + max_logical_writes_worst AS ( + SELECT TOP 1 'Your worst max logical write range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg + FROM worsts + ORDER BY worsts.total_max_logical_io_writes_mb DESC + ), + max_memory_worst AS ( + SELECT TOP 1 'Your worst max memory range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg + FROM worsts + ORDER BY worsts.total_max_query_max_used_memory_mb DESC + ), + max_logbytes_worst AS ( + SELECT TOP 1 'Your worst max log bytes range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg + FROM worsts + ORDER BY worsts.total_max_log_bytes_mb DESC + ), + max_tempdb_worst AS ( + SELECT TOP 1 'Your worst max tempdb range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg + FROM worsts + ORDER BY worsts.total_max_tempdb_space DESC + ) + INSERT #warning_results ( CheckID, Priority, FindingsGroup, Finding, URL, Details ) + /*averages*/ + SELECT 1002, 255, 'Worsts', 'Worst Avg Duration', 'N/A', duration_worst.msg + FROM duration_worst + UNION ALL + SELECT 1002, 255, 'Worsts', 'Worst Avg CPU', 'N/A', cpu_worst.msg + FROM cpu_worst + UNION ALL + SELECT 1002, 255, 'Worsts', 'Worst Avg Logical Reads', 'N/A', logical_reads_worst.msg + FROM logical_reads_worst + UNION ALL + SELECT 1002, 255, 'Worsts', 'Worst Avg Physical Reads', 'N/A', physical_reads_worst.msg + FROM physical_reads_worst + UNION ALL + SELECT 1002, 255, 'Worsts', 'Worst Avg Logical Writes', 'N/A', logical_writes_worst.msg + FROM logical_writes_worst + UNION ALL + SELECT 1002, 255, 'Worsts', 'Worst Avg Memory', 'N/A', memory_worst.msg + FROM memory_worst + UNION ALL + SELECT 1002, 255, 'Worsts', 'Worst Row Counts', 'N/A', rowcount_worst.msg + FROM rowcount_worst + UNION ALL + SELECT 1002, 255, 'Worsts', 'Worst Avg Log Bytes', 'N/A', logbytes_worst.msg + FROM logbytes_worst + UNION ALL + SELECT 1002, 255, 'Worsts', 'Worst Avg tempdb', 'N/A', tempdb_worst.msg + FROM tempdb_worst + UNION ALL + /*maxes*/ + SELECT 1002, 255, 'Worsts', 'Worst Max Duration', 'N/A', max_duration_worst.msg + FROM max_duration_worst + UNION ALL + SELECT 1002, 255, 'Worsts', 'Worst Max CPU', 'N/A', max_cpu_worst.msg + FROM max_cpu_worst + UNION ALL + SELECT 1002, 255, 'Worsts', 'Worst Max Logical Reads', 'N/A', max_logical_reads_worst.msg + FROM max_logical_reads_worst + UNION ALL + SELECT 1002, 255, 'Worsts', 'Worst Max Physical Reads', 'N/A', max_physical_reads_worst.msg + FROM max_physical_reads_worst + UNION ALL + SELECT 1002, 255, 'Worsts', 'Worst Max Logical Writes', 'N/A', max_logical_writes_worst.msg + FROM max_logical_writes_worst + UNION ALL + SELECT 1002, 255, 'Worsts', 'Worst Max Memory', 'N/A', max_memory_worst.msg + FROM max_memory_worst + UNION ALL + SELECT 1002, 255, 'Worsts', 'Worst Max Log Bytes', 'N/A', max_logbytes_worst.msg + FROM max_logbytes_worst + UNION ALL + SELECT 1002, 255, 'Worsts', 'Worst Max tempdb', 'N/A', max_tempdb_worst.msg + FROM max_tempdb_worst + OPTION (RECOMPILE); -IF @ExpertMode > 0 -BEGIN -RAISERROR(N'Is Paul White Electric?', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p), -is_paul_white_electric AS ( -SELECT 1 AS [is_paul_white_electric], -r.sql_handle -FROM #relop AS r -CROSS APPLY r.relop.nodes('//p:RelOp') c(n) -WHERE c.n.exist('@PhysicalOp[.="Switch"]') = 1 -) -UPDATE b -SET b.is_paul_white_electric = ipwe.is_paul_white_electric -FROM #working_warnings AS b -JOIN is_paul_white_electric ipwe -ON ipwe.sql_handle = b.sql_handle -OPTION (RECOMPILE); -END; + IF NOT EXISTS (SELECT 1/0 + FROM #warning_results AS bcr + WHERE bcr.Priority = 2147483646 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (2147483646, + 255, + 'Need more help?' , + 'Paste your plan on the internet!', + 'http://pastetheplan.com', + 'This makes it easy to share plans and post them to Q&A sites like https://dba.stackexchange.com/!') ; + IF NOT EXISTS (SELECT 1/0 + FROM #warning_results AS bcr + WHERE bcr.Priority = 2147483647 + ) + INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES ( + 2147483647, + 255, + 'Thanks for using sp_BlitzQueryStore!' , + 'From Your Community Volunteers', + 'http://FirstResponderKit.org', + 'We hope you found this tool useful. Current version: ' + @Version + ' released on ' + CONVERT(NVARCHAR(30), @VersionDate) + '.') ; + -RAISERROR(N'Checking for non-sargable predicates', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES ( 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) -, nsarg - AS ( SELECT r.query_hash, 1 AS fn, 0 AS jo, 0 AS lk - FROM #relop AS r - CROSS APPLY r.relop.nodes('/p:RelOp/p:IndexScan/p:Predicate/p:ScalarOperator/p:Compare/p:ScalarOperator') AS ca(x) - WHERE ( ca.x.exist('//p:ScalarOperator/p:Intrinsic/@FunctionName') = 1 - OR ca.x.exist('//p:ScalarOperator/p:IF') = 1 ) - UNION ALL - SELECT r.query_hash, 0 AS fn, 1 AS jo, 0 AS lk - FROM #relop AS r - CROSS APPLY r.relop.nodes('/p:RelOp//p:ScalarOperator') AS ca(x) - WHERE r.relop.exist('/p:RelOp[contains(@LogicalOp, "Join")]') = 1 - AND ca.x.exist('//p:ScalarOperator[contains(@ScalarString, "Expr")]') = 1 - UNION ALL - SELECT r.query_hash, 0 AS fn, 0 AS jo, 1 AS lk - FROM #relop AS r - CROSS APPLY r.relop.nodes('/p:RelOp/p:IndexScan/p:Predicate/p:ScalarOperator') AS ca(x) - CROSS APPLY ca.x.nodes('//p:Const') AS co(x) - WHERE ca.x.exist('//p:ScalarOperator/p:Intrinsic/@FunctionName[.="like"]') = 1 - AND ( ( co.x.value('substring(@ConstValue, 1, 1)', 'VARCHAR(100)') <> 'N' - AND co.x.value('substring(@ConstValue, 2, 1)', 'VARCHAR(100)') = '%' ) - OR ( co.x.value('substring(@ConstValue, 1, 1)', 'VARCHAR(100)') = 'N' - AND co.x.value('substring(@ConstValue, 3, 1)', 'VARCHAR(100)') = '%' ))), - d_nsarg - AS ( SELECT DISTINCT - nsarg.query_hash - FROM nsarg - WHERE nsarg.fn = 1 - OR nsarg.jo = 1 - OR nsarg.lk = 1 ) -UPDATE b -SET b.is_nonsargable = 1 -FROM d_nsarg AS d -JOIN #working_warnings AS b - ON b.query_hash = d.query_hash + + SELECT Priority, + FindingsGroup, + Finding, + URL, + Details, + CheckID + FROM #warning_results + GROUP BY Priority, + FindingsGroup, + Finding, + URL, + Details, + CheckID + ORDER BY Priority ASC, FindingsGroup, Finding, CheckID ASC + OPTION (RECOMPILE); + + + +END; + +END; +END TRY +BEGIN CATCH + RAISERROR (N'Failure returning warnings', 0,1) WITH NOWAIT; + + IF @sql_select IS NOT NULL + BEGIN + SET @msg = N'Last @sql_select: ' + @sql_select; + RAISERROR(@msg, 0, 1) WITH NOWAIT; + END; + + SELECT @msg = @DatabaseName + N' database failed to process. ' + ERROR_MESSAGE(), @error_severity = ERROR_SEVERITY(), @error_state = ERROR_STATE(); + RAISERROR (@msg, @error_severity, @error_state) WITH NOWAIT; + + + WHILE @@TRANCOUNT > 0 + ROLLBACK; + + RETURN; +END CATCH; + +IF @Debug = 1 + +BEGIN TRY + +BEGIN + +RAISERROR(N'Returning debugging data from temp tables', 0, 1) WITH NOWAIT; + +--Table content debugging + +SELECT '#working_metrics' AS table_name, * +FROM #working_metrics AS wm +OPTION (RECOMPILE); + +SELECT '#working_plan_text' AS table_name, * +FROM #working_plan_text AS wpt +OPTION (RECOMPILE); + +SELECT '#working_warnings' AS table_name, * +FROM #working_warnings AS ww +OPTION (RECOMPILE); + +SELECT '#working_wait_stats' AS table_name, * +FROM #working_wait_stats wws +OPTION (RECOMPILE); + +SELECT '#grouped_interval' AS table_name, * +FROM #grouped_interval +OPTION (RECOMPILE); + +SELECT '#working_plans' AS table_name, * +FROM #working_plans +OPTION (RECOMPILE); + +SELECT '#stats_agg' AS table_name, * +FROM #stats_agg +OPTION (RECOMPILE); + +SELECT '#trace_flags' AS table_name, * +FROM #trace_flags +OPTION (RECOMPILE); + +SELECT '#statements' AS table_name, * +FROM #statements AS s +OPTION (RECOMPILE); + +SELECT '#query_plan' AS table_name, * +FROM #query_plan AS qp +OPTION (RECOMPILE); + +SELECT '#relop' AS table_name, * +FROM #relop AS r +OPTION (RECOMPILE); + +SELECT '#plan_cost' AS table_name, * +FROM #plan_cost AS pc +OPTION (RECOMPILE); + +SELECT '#est_rows' AS table_name, * +FROM #est_rows AS er +OPTION (RECOMPILE); + +SELECT '#stored_proc_info' AS table_name, * +FROM #stored_proc_info AS spi +OPTION(RECOMPILE); + +SELECT '#conversion_info' AS table_name, * +FROM #conversion_info AS ci OPTION ( RECOMPILE ); +SELECT '#variable_info' AS table_name, * +FROM #variable_info AS vi +OPTION ( RECOMPILE ); - RAISERROR(N'Getting information about implicit conversions and stored proc parameters', 0, 1) WITH NOWAIT; +SELECT '#missing_index_xml' AS table_name, * +FROM #missing_index_xml +OPTION ( RECOMPILE ); - RAISERROR(N'Getting variable info', 0, 1) WITH NOWAIT; - WITH XMLNAMESPACES ( 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) - INSERT #variable_info ( query_hash, sql_handle, proc_name, variable_name, variable_datatype, compile_time_value ) - SELECT DISTINCT - qp.query_hash, - qp.sql_handle, - b.proc_or_function_name AS proc_name, - q.n.value('@Column', 'NVARCHAR(258)') AS variable_name, - q.n.value('@ParameterDataType', 'NVARCHAR(258)') AS variable_datatype, - q.n.value('@ParameterCompiledValue', 'NVARCHAR(258)') AS compile_time_value - FROM #query_plan AS qp - JOIN #working_warnings AS b - ON (b.query_hash = qp.query_hash AND b.proc_or_function_name = 'adhoc') - OR (b.sql_handle = qp.sql_handle AND b.proc_or_function_name <> 'adhoc') - CROSS APPLY qp.query_plan.nodes('//p:QueryPlan/p:ParameterList/p:ColumnReference') AS q(n) - OPTION (RECOMPILE); +SELECT '#missing_index_schema' AS table_name, * +FROM #missing_index_schema +OPTION ( RECOMPILE ); - RAISERROR(N'Getting conversion info', 0, 1) WITH NOWAIT; - WITH XMLNAMESPACES ( 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) - INSERT #conversion_info ( query_hash, sql_handle, proc_name, expression ) - SELECT DISTINCT - qp.query_hash, - qp.sql_handle, - b.proc_or_function_name AS proc_name, - qq.c.value('@Expression', 'NVARCHAR(4000)') AS expression - FROM #query_plan AS qp - JOIN #working_warnings AS b - ON (b.query_hash = qp.query_hash AND b.proc_or_function_name = 'adhoc') - OR (b.sql_handle = qp.sql_handle AND b.proc_or_function_name <> 'adhoc') - CROSS APPLY qp.query_plan.nodes('//p:QueryPlan/p:Warnings/p:PlanAffectingConvert') AS qq(c) - WHERE qq.c.exist('@ConvertIssue[.="Seek Plan"]') = 1 - AND b.implicit_conversions = 1 - OPTION (RECOMPILE); +SELECT '#missing_index_usage' AS table_name, * +FROM #missing_index_usage +OPTION ( RECOMPILE ); - RAISERROR(N'Parsing conversion info', 0, 1) WITH NOWAIT; - INSERT #stored_proc_info ( sql_handle, query_hash, proc_name, variable_name, variable_datatype, converted_column_name, column_name, converted_to, compile_time_value ) - SELECT ci.sql_handle, - ci.query_hash, - ci.proc_name, - CASE WHEN ci.at_charindex > 0 - AND ci.bracket_charindex > 0 - THEN SUBSTRING(ci.expression, ci.at_charindex, ci.bracket_charindex) - ELSE N'**no_variable**' - END AS variable_name, - N'**no_variable**' AS variable_datatype, - CASE WHEN ci.at_charindex = 0 - AND ci.comma_charindex > 0 - AND ci.second_comma_charindex > 0 - THEN SUBSTRING(ci.expression, ci.comma_charindex, ci.second_comma_charindex) - ELSE N'**no_column**' - END AS converted_column_name, - CASE WHEN ci.at_charindex = 0 - AND ci.equal_charindex > 0 - AND ci.convert_implicit_charindex = 0 - THEN SUBSTRING(ci.expression, ci.equal_charindex, 4000) - WHEN ci.at_charindex = 0 - AND (ci.equal_charindex -1) > 0 - AND ci.convert_implicit_charindex > 0 - THEN SUBSTRING(ci.expression, 0, ci.equal_charindex -1) - WHEN ci.at_charindex > 0 - AND ci.comma_charindex > 0 - AND ci.second_comma_charindex > 0 - THEN SUBSTRING(ci.expression, ci.comma_charindex, ci.second_comma_charindex) - ELSE N'**no_column **' - END AS column_name, - CASE WHEN ci.paren_charindex > 0 - 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 - 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 - FROM #conversion_info AS ci - OPTION (RECOMPILE); +SELECT '#missing_index_detail' AS table_name, * +FROM #missing_index_detail +OPTION ( RECOMPILE ); - RAISERROR(N'Updating variables inserted procs', 0, 1) WITH NOWAIT; - UPDATE sp - SET sp.variable_datatype = vi.variable_datatype, - sp.compile_time_value = vi.compile_time_value - FROM #stored_proc_info AS sp - JOIN #variable_info AS vi - ON (sp.proc_name = 'adhoc' AND sp.query_hash = vi.query_hash) - OR (sp.proc_name <> 'adhoc' AND sp.sql_handle = vi.sql_handle) - AND sp.variable_name = vi.variable_name - OPTION (RECOMPILE); - - - RAISERROR(N'Inserting variables for other procs', 0, 1) WITH NOWAIT; - INSERT #stored_proc_info - ( sql_handle, query_hash, variable_name, variable_datatype, compile_time_value, proc_name ) - SELECT vi.sql_handle, vi.query_hash, vi.variable_name, vi.variable_datatype, vi.compile_time_value, vi.proc_name - FROM #variable_info AS vi - WHERE NOT EXISTS - ( - SELECT * - FROM #stored_proc_info AS sp - WHERE (sp.proc_name = 'adhoc' AND sp.query_hash = vi.query_hash) - OR (sp.proc_name <> 'adhoc' AND sp.sql_handle = vi.sql_handle) - ) - OPTION (RECOMPILE); - - RAISERROR(N'Updating procs', 0, 1) WITH NOWAIT; - UPDATE s - SET s.variable_datatype = CASE WHEN s.variable_datatype LIKE '%(%)%' - THEN LEFT(s.variable_datatype, CHARINDEX('(', s.variable_datatype) - 1) - ELSE s.variable_datatype - END, - s.converted_to = CASE WHEN s.converted_to LIKE '%(%)%' - THEN LEFT(s.converted_to, CHARINDEX('(', s.converted_to) - 1) - ELSE s.converted_to - END, - s.compile_time_value = CASE WHEN s.compile_time_value LIKE '%(%)%' - THEN SUBSTRING(s.compile_time_value, - CHARINDEX('(', s.compile_time_value) + 1, - CHARINDEX(')', s.compile_time_value) - 1 - CHARINDEX('(', s.compile_time_value) - ) - WHEN variable_datatype NOT IN ('bit', 'tinyint', 'smallint', 'int', 'bigint') - AND s.variable_datatype NOT LIKE '%binary%' - AND s.compile_time_value NOT LIKE 'N''%''' - AND s.compile_time_value NOT LIKE '''%''' - AND s.compile_time_value <> s.column_name - AND s.compile_time_value <> '**idk_man**' - THEN QUOTENAME(compile_time_value, '''') - ELSE s.compile_time_value - END - FROM #stored_proc_info AS s - OPTION (RECOMPILE); +SELECT '#missing_index_pretty' AS table_name, * +FROM #missing_index_pretty +OPTION ( RECOMPILE ); - - RAISERROR(N'Updating SET options', 0, 1) WITH NOWAIT; - WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) - UPDATE s - SET set_options = set_options.ansi_set_options - FROM #stored_proc_info AS s - JOIN ( - SELECT x.sql_handle, - N'SET ANSI_NULLS ' + CASE WHEN [ANSI_NULLS] = 'true' THEN N'ON ' ELSE N'OFF ' END + NCHAR(10) + - N'SET ANSI_PADDING ' + CASE WHEN [ANSI_PADDING] = 'true' THEN N'ON ' ELSE N'OFF ' END + NCHAR(10) + - N'SET ANSI_WARNINGS ' + CASE WHEN [ANSI_WARNINGS] = 'true' THEN N'ON ' ELSE N'OFF ' END + NCHAR(10) + - N'SET ARITHABORT ' + CASE WHEN [ARITHABORT] = 'true' THEN N'ON ' ELSE N' OFF ' END + NCHAR(10) + - N'SET CONCAT_NULL_YIELDS_NULL ' + CASE WHEN [CONCAT_NULL_YIELDS_NULL] = 'true' THEN N'ON ' ELSE N'OFF ' END + NCHAR(10) + - N'SET NUMERIC_ROUNDABORT ' + CASE WHEN [NUMERIC_ROUNDABORT] = 'true' THEN N'ON ' ELSE N'OFF ' END + NCHAR(10) + - N'SET QUOTED_IDENTIFIER ' + CASE WHEN [QUOTED_IDENTIFIER] = 'true' THEN N'ON ' ELSE N'OFF ' + NCHAR(10) END AS [ansi_set_options] - FROM ( - SELECT - s.sql_handle, - so.o.value('@ANSI_NULLS', 'NVARCHAR(20)') AS [ANSI_NULLS], - so.o.value('@ANSI_PADDING', 'NVARCHAR(20)') AS [ANSI_PADDING], - so.o.value('@ANSI_WARNINGS', 'NVARCHAR(20)') AS [ANSI_WARNINGS], - so.o.value('@ARITHABORT', 'NVARCHAR(20)') AS [ARITHABORT], - so.o.value('@CONCAT_NULL_YIELDS_NULL', 'NVARCHAR(20)') AS [CONCAT_NULL_YIELDS_NULL], - so.o.value('@NUMERIC_ROUNDABORT', 'NVARCHAR(20)') AS [NUMERIC_ROUNDABORT], - so.o.value('@QUOTED_IDENTIFIER', 'NVARCHAR(20)') AS [QUOTED_IDENTIFIER] - FROM #statements AS s - CROSS APPLY s.statement.nodes('//p:StatementSetOptions') AS so(o) - ) AS x - ) AS set_options ON set_options.sql_handle = s.sql_handle - OPTION(RECOMPILE); +END; +END TRY +BEGIN CATCH + RAISERROR (N'Failure returning debug temp tables', 0,1) WITH NOWAIT; - RAISERROR(N'Updating conversion XML', 0, 1) WITH NOWAIT; - WITH precheck AS ( - SELECT spi.sql_handle, - spi.proc_name, - (SELECT CASE WHEN spi.proc_name <> 'Statement' - THEN N'The stored procedure ' + spi.proc_name - ELSE N'This ad hoc statement' - END - + N' had the following implicit conversions: ' - + CHAR(10) - + STUFF(( - SELECT DISTINCT - @cr + @lf - + CASE WHEN spi2.variable_name <> N'**no_variable**' - THEN N'The variable ' - WHEN spi2.variable_name = N'**no_variable**' AND (spi2.column_name = spi2.converted_column_name OR spi2.column_name LIKE '%CONVERT_IMPLICIT%') - THEN N'The compiled value ' - WHEN spi2.column_name LIKE '%Expr%' - THEN 'The expression ' - ELSE N'The column ' - END - + CASE WHEN spi2.variable_name <> N'**no_variable**' - THEN spi2.variable_name - WHEN spi2.variable_name = N'**no_variable**' AND (spi2.column_name = spi2.converted_column_name OR spi2.column_name LIKE '%CONVERT_IMPLICIT%') - THEN spi2.compile_time_value - - ELSE spi2.column_name - END - + N' has a data type of ' - + CASE WHEN spi2.variable_datatype = N'**no_variable**' THEN spi2.converted_to - ELSE spi2.variable_datatype - END - + N' which caused implicit conversion on the column ' - + CASE WHEN spi2.column_name LIKE N'%CONVERT_IMPLICIT%' - THEN spi2.converted_column_name - WHEN spi2.column_name = N'**no_column**' - THEN spi2.converted_column_name - WHEN spi2.converted_column_name = N'**no_column**' - THEN spi2.column_name - WHEN spi2.column_name <> spi2.converted_column_name - THEN spi2.converted_column_name - ELSE spi2.column_name - END - + CASE WHEN spi2.variable_name = N'**no_variable**' AND (spi2.column_name = spi2.converted_column_name OR spi2.column_name LIKE '%CONVERT_IMPLICIT%') - THEN N'' - WHEN spi2.column_name LIKE '%Expr%' - THEN N'' - WHEN spi2.compile_time_value NOT IN ('**declared in proc**', '**idk_man**') - AND spi2.compile_time_value <> spi2.column_name - THEN ' with the value ' + RTRIM(spi2.compile_time_value) - ELSE N'' - END - + '.' - FROM #stored_proc_info AS spi2 - WHERE spi.sql_handle = spi2.sql_handle - FOR XML PATH(N''), TYPE).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 1, N'') - AS [processing-instruction(ClickMe)] FOR XML PATH(''), TYPE ) - AS implicit_conversion_info - FROM #stored_proc_info AS spi - GROUP BY spi.sql_handle, spi.proc_name - ) - UPDATE b - SET b.implicit_conversion_info = pk.implicit_conversion_info - FROM #working_warnings AS b - JOIN precheck AS pk - ON pk.sql_handle = b.sql_handle - OPTION (RECOMPILE); + IF @sql_select IS NOT NULL + BEGIN + SET @msg = N'Last @sql_select: ' + @sql_select; + RAISERROR(@msg, 0, 1) WITH NOWAIT; + END; - RAISERROR(N'Updating cached parameter XML for procs', 0, 1) WITH NOWAIT; - WITH precheck AS ( - SELECT spi.sql_handle, - spi.proc_name, - (SELECT set_options - + @cr + @lf - + @cr + @lf - + N'EXEC ' - + spi.proc_name - + N' ' - + STUFF(( - SELECT DISTINCT N', ' - + CASE WHEN spi2.variable_name <> N'**no_variable**' AND spi2.compile_time_value <> N'**idk_man**' - THEN spi2.variable_name + N' = ' - ELSE @cr + @lf + N' We could not find any cached parameter values for this stored proc. ' - END - + CASE WHEN spi2.variable_name = N'**no_variable**' OR spi2.compile_time_value = N'**idk_man**' - THEN @cr + @lf + N' Possible reasons include declared variables inside the procedure, recompile hints, etc. ' - WHEN spi2.compile_time_value = N'NULL' - THEN spi2.compile_time_value - ELSE RTRIM(spi2.compile_time_value) - END - FROM #stored_proc_info AS spi2 - WHERE spi.sql_handle = spi2.sql_handle - AND spi2.proc_name <> N'Statement' - FOR XML PATH(N''), TYPE).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 1, N'') - AS [processing-instruction(ClickMe)] FOR XML PATH(''), TYPE ) - AS cached_execution_parameters - FROM #stored_proc_info AS spi - GROUP BY spi.sql_handle, spi.proc_name, set_options - ) - UPDATE b - SET b.cached_execution_parameters = pk.cached_execution_parameters - FROM #working_warnings AS b - JOIN precheck AS pk - ON pk.sql_handle = b.sql_handle - WHERE b.proc_or_function_name <> N'Statement' - OPTION (RECOMPILE); + SELECT @msg = @DatabaseName + N' database failed to process. ' + ERROR_MESSAGE(), @error_severity = ERROR_SEVERITY(), @error_state = ERROR_STATE(); + RAISERROR (@msg, @error_severity, @error_state) WITH NOWAIT; + + + WHILE @@TRANCOUNT > 0 + ROLLBACK; + RETURN; +END CATCH; - RAISERROR(N'Updating cached parameter XML for statements', 0, 1) WITH NOWAIT; - WITH precheck AS ( - SELECT spi.sql_handle, - spi.proc_name, - (SELECT - set_options - + @cr + @lf - + @cr + @lf - + N' See QueryText column for full query text' - + @cr + @lf - + @cr + @lf - + STUFF(( - SELECT DISTINCT N', ' - + CASE WHEN spi2.variable_name <> N'**no_variable**' AND spi2.compile_time_value <> N'**idk_man**' - THEN spi2.variable_name + N' = ' - ELSE + @cr + @lf + N' We could not find any cached parameter values for this stored proc. ' - END - + CASE WHEN spi2.variable_name = N'**no_variable**' OR spi2.compile_time_value = N'**idk_man**' - THEN + @cr + @lf + N' Possible reasons include declared variables inside the procedure, recompile hints, etc. ' - WHEN spi2.compile_time_value = N'NULL' - THEN spi2.compile_time_value - ELSE RTRIM(spi2.compile_time_value) - END - FROM #stored_proc_info AS spi2 - WHERE spi.sql_handle = spi2.sql_handle - AND spi2.proc_name = N'Statement' - AND spi2.variable_name NOT LIKE N'%msparam%' - FOR XML PATH(N''), TYPE).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 1, N'') - AS [processing-instruction(ClickMe)] FOR XML PATH(''), TYPE ) - AS cached_execution_parameters - FROM #stored_proc_info AS spi - GROUP BY spi.sql_handle, spi.proc_name, spi.set_options - ) - UPDATE b - SET b.cached_execution_parameters = pk.cached_execution_parameters - FROM #working_warnings AS b - JOIN precheck AS pk - ON pk.sql_handle = b.sql_handle - WHERE b.proc_or_function_name = N'Statement' - OPTION (RECOMPILE); +/* +Ways to run this thing + +--Debug +EXEC sp_BlitzQueryStore @DatabaseName = 'StackOverflow', @Debug = 1 + +--Get the top 1 +EXEC sp_BlitzQueryStore @DatabaseName = 'StackOverflow', @Top = 1, @Debug = 1 + +--Use a StartDate +EXEC sp_BlitzQueryStore @DatabaseName = 'StackOverflow', @Top = 1, @StartDate = '20170527' + +--Use an EndDate +EXEC sp_BlitzQueryStore @DatabaseName = 'StackOverflow', @Top = 1, @EndDate = '20170527' + +--Use Both +EXEC sp_BlitzQueryStore @DatabaseName = 'StackOverflow', @Top = 1, @StartDate = '20170526', @EndDate = '20170527' + +--Set a minimum execution count +EXEC sp_BlitzQueryStore @DatabaseName = 'StackOverflow', @Top = 1, @MinimumExecutionCount = 10 + +--Set a duration minimum +EXEC sp_BlitzQueryStore @DatabaseName = 'StackOverflow', @Top = 1, @DurationFilter = 5 + +--Look for a stored procedure name (that doesn't exist!) +EXEC sp_BlitzQueryStore @DatabaseName = 'StackOverflow', @Top = 1, @StoredProcName = 'blah' + +--Look for a stored procedure name that does (at least On My Computer®) +EXEC sp_BlitzQueryStore @DatabaseName = 'StackOverflow', @Top = 1, @StoredProcName = 'UserReportExtended' + +--Look for failed queries +EXEC sp_BlitzQueryStore @DatabaseName = 'StackOverflow', @Top = 1, @Failed = 1 + +--Filter by plan_id +EXEC sp_BlitzQueryStore @DatabaseName = 'StackOverflow', @PlanIdFilter = 3356 + +--Filter by query_id +EXEC sp_BlitzQueryStore @DatabaseName = 'StackOverflow', @QueryIdFilter = 2958 + +*/ + +END; + +GO +IF OBJECT_ID('dbo.sp_BlitzWho') IS NULL + EXEC ('CREATE PROCEDURE dbo.sp_BlitzWho AS RETURN 0;') +GO + +ALTER PROCEDURE dbo.sp_BlitzWho + @Help TINYINT = 0 , + @ShowSleepingSPIDs TINYINT = 0, + @ExpertMode BIT = 0, + @Debug BIT = 0, + @OutputDatabaseName NVARCHAR(256) = NULL , + @OutputSchemaName NVARCHAR(256) = NULL , + @OutputTableName NVARCHAR(256) = NULL , + @OutputTableRetentionDays TINYINT = 3 , + @MinElapsedSeconds INT = 0 , + @MinCPUTime INT = 0 , + @MinLogicalReads INT = 0 , + @MinPhysicalReads INT = 0 , + @MinWrites INT = 0 , + @MinTempdbMB INT = 0 , + @MinRequestedMemoryKB INT = 0 , + @MinBlockingSeconds INT = 0 , + @CheckDateOverride DATETIMEOFFSET = NULL, + @Version VARCHAR(30) = NULL OUTPUT, + @VersionDate DATETIME = NULL OUTPUT, + @VersionCheckMode BIT = 0, + @SortOrder NVARCHAR(256) = N'elapsed time' +AS +BEGIN + SET NOCOUNT ON; + SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + + SELECT @Version = '8.01', @VersionDate = '20210222'; + + IF(@VersionCheckMode = 1) + BEGIN + RETURN; + END; + + + + IF @Help = 1 + BEGIN + PRINT ' +sp_BlitzWho from http://FirstResponderKit.org + +This script gives you a snapshot of everything currently executing on your SQL Server. + +To learn more, visit http://FirstResponderKit.org where you can download new +versions for free, watch training videos on how it works, get more info on +the findings, contribute your own code, and more. + +Known limitations of this version: + - Only Microsoft-supported versions of SQL Server. Sorry, 2005 and 2000. + - Outputting to table is only supported with SQL Server 2012 and higher. + - If @OutputDatabaseName and @OutputSchemaName are populated, the database and + schema must already exist. We will not create them, only the table. + +MIT License + +Copyright (c) 2021 Brent Ozar Unlimited + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +'; +RETURN; +END; /* @Help = 1 */ + +/* Get the major and minor build numbers */ +DECLARE @ProductVersion NVARCHAR(128) + ,@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]) + ,@EnhanceFlag BIT = 0 + ,@BlockingCheck NVARCHAR(MAX) + ,@StringToSelect NVARCHAR(MAX) + ,@StringToExecute NVARCHAR(MAX) + ,@OutputTableCleanupDate DATE + ,@SessionWaits BIT = 0 + ,@SessionWaitsSQL NVARCHAR(MAX) = + N'LEFT JOIN ( SELECT DISTINCT + wait.session_id , + ( SELECT TOP 5 waitwait.wait_type + N'' ('' + + CAST(MAX(waitwait.wait_time_ms) AS NVARCHAR(128)) + + N'' ms), '' + FROM sys.dm_exec_session_wait_stats AS waitwait + WHERE waitwait.session_id = wait.session_id + GROUP BY waitwait.wait_type + HAVING SUM(waitwait.wait_time_ms) > 5 + ORDER BY 1 + FOR + XML PATH('''') ) AS session_wait_info + FROM sys.dm_exec_session_wait_stats AS wait ) AS wt2 + ON s.session_id = wt2.session_id + LEFT JOIN sys.dm_exec_query_stats AS session_stats + ON r.sql_handle = session_stats.sql_handle + AND r.plan_handle = session_stats.plan_handle + AND r.statement_start_offset = session_stats.statement_start_offset + AND r.statement_end_offset = session_stats.statement_end_offset' + ,@QueryStatsXMLselect NVARCHAR(MAX) = N' CAST(COALESCE(qs_live.query_plan, '''') AS XML) AS live_query_plan , ' + ,@QueryStatsXMLSQL NVARCHAR(MAX) = N'OUTER APPLY sys.dm_exec_query_statistics_xml(s.session_id) qs_live' + ,@ObjectFullName NVARCHAR(2000) + ,@OutputTableNameQueryStats_View NVARCHAR(256) + ,@LineFeed NVARCHAR(MAX) /* Had to set as MAX up from 10 as it was truncating the view creation*/; + +/* 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) +IF EXISTS (SELECT * FROM sys.all_columns WHERE object_id = OBJECT_ID('sys.dm_exec_query_statistics_xml') AND name = 'query_plan') + BEGIN + SET @QueryStatsXMLselect = N' CAST(COALESCE(qs_live.query_plan, '''') AS XML) AS live_query_plan , '; + SET @QueryStatsXMLSQL = N'OUTER APPLY sys.dm_exec_query_statistics_xml(s.session_id) qs_live'; + END + ELSE + BEGIN + SET @QueryStatsXMLselect = N' NULL AS live_query_plan , '; + SET @QueryStatsXMLSQL = N' '; + END + +SELECT + @OutputTableNameQueryStats_View = QUOTENAME(@OutputTableName + '_Deltas'), + @OutputDatabaseName = QUOTENAME(@OutputDatabaseName), + @OutputSchemaName = QUOTENAME(@OutputSchemaName), + @OutputTableName = QUOTENAME(@OutputTableName), + @LineFeed = CHAR(13) + CHAR(10); + +IF @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @OutputTableName IS NOT NULL + AND EXISTS ( SELECT * + FROM sys.databases + WHERE QUOTENAME([name]) = @OutputDatabaseName) + BEGIN + SET @ExpertMode = 1; /* Force ExpertMode when we're logging to table */ + + /* Create the table if it doesn't exist */ + SET @StringToExecute = N'USE ' + + @OutputDatabaseName + + N'; IF EXISTS(SELECT * FROM ' + + @OutputDatabaseName + + N'.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' + + @OutputSchemaName + + N''') AND NOT EXISTS (SELECT * FROM ' + + @OutputDatabaseName + + N'.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''' + + @OutputSchemaName + N''' AND QUOTENAME(TABLE_NAME) = ''' + + @OutputTableName + N''') CREATE TABLE ' + + @OutputSchemaName + N'.' + + @OutputTableName + + N'('; + SET @StringToExecute = @StringToExecute + N' + ID INT IDENTITY(1,1) NOT NULL, + ServerName NVARCHAR(128) NOT NULL, + CheckDate DATETIMEOFFSET NOT NULL, + [elapsed_time] [varchar](41) NULL, + [session_id] [smallint] NOT NULL, + [database_name] [nvarchar](128) NULL, + [query_text] [nvarchar](max) NULL, + [query_plan] [xml] NULL, + [live_query_plan] [xml] NULL, + [query_cost] [float] NULL, + [status] [nvarchar](30) NOT NULL, + [wait_info] [nvarchar](max) NULL, + [top_session_waits] [nvarchar](max) NULL, + [blocking_session_id] [smallint] NULL, + [open_transaction_count] [int] NULL, + [is_implicit_transaction] [int] NOT NULL, + [nt_domain] [nvarchar](128) NULL, + [host_name] [nvarchar](128) NULL, + [login_name] [nvarchar](128) NOT NULL, + [nt_user_name] [nvarchar](128) NULL, + [program_name] [nvarchar](128) NULL, + [fix_parameter_sniffing] [nvarchar](150) NULL, + [client_interface_name] [nvarchar](32) NULL, + [login_time] [datetime] NOT NULL, + [start_time] [datetime] NULL, + [request_time] [datetime] NULL, + [request_cpu_time] [int] NULL, + [request_logical_reads] [bigint] NULL, + [request_writes] [bigint] NULL, + [request_physical_reads] [bigint] NULL, + [session_cpu] [int] NOT NULL, + [session_logical_reads] [bigint] NOT NULL, + [session_physical_reads] [bigint] NOT NULL, + [session_writes] [bigint] NOT NULL, + [tempdb_allocations_mb] [decimal](38, 2) NULL, + [memory_usage] [int] NOT NULL, + [estimated_completion_time] [bigint] NULL, + [percent_complete] [real] NULL, + [deadlock_priority] [int] NULL, + [transaction_isolation_level] [varchar](33) NOT NULL, + [degree_of_parallelism] [smallint] NULL, + [last_dop] [bigint] NULL, + [min_dop] [bigint] NULL, + [max_dop] [bigint] NULL, + [last_grant_kb] [bigint] NULL, + [min_grant_kb] [bigint] NULL, + [max_grant_kb] [bigint] NULL, + [last_used_grant_kb] [bigint] NULL, + [min_used_grant_kb] [bigint] NULL, + [max_used_grant_kb] [bigint] NULL, + [last_ideal_grant_kb] [bigint] NULL, + [min_ideal_grant_kb] [bigint] NULL, + [max_ideal_grant_kb] [bigint] NULL, + [last_reserved_threads] [bigint] NULL, + [min_reserved_threads] [bigint] NULL, + [max_reserved_threads] [bigint] NULL, + [last_used_threads] [bigint] NULL, + [min_used_threads] [bigint] NULL, + [max_used_threads] [bigint] NULL, + [grant_time] [varchar](20) NULL, + [requested_memory_kb] [bigint] NULL, + [grant_memory_kb] [bigint] NULL, + [is_request_granted] [varchar](39) NOT NULL, + [required_memory_kb] [bigint] NULL, + [query_memory_grant_used_memory_kb] [bigint] NULL, + [ideal_memory_kb] [bigint] NULL, + [is_small] [bit] NULL, + [timeout_sec] [int] NULL, + [resource_semaphore_id] [smallint] NULL, + [wait_order] [varchar](20) NULL, + [wait_time_ms] [varchar](20) NULL, + [next_candidate_for_memory_grant] [varchar](3) NOT NULL, + [target_memory_kb] [bigint] NULL, + [max_target_memory_kb] [varchar](30) NULL, + [total_memory_kb] [bigint] NULL, + [available_memory_kb] [bigint] NULL, + [granted_memory_kb] [bigint] NULL, + [query_resource_semaphore_used_memory_kb] [bigint] NULL, + [grantee_count] [int] NULL, + [waiter_count] [int] NULL, + [timeout_error_count] [bigint] NULL, + [forced_grant_count] [varchar](30) NULL, + [workload_group_name] [sysname] NULL, + [resource_pool_name] [sysname] NULL, + [context_info] [varchar](128) NULL, + [query_hash] [binary](8) NULL, + [query_plan_hash] [binary](8) NULL, + [sql_handle] [varbinary] (64) NULL, + [plan_handle] [varbinary] (64) NULL, + [statement_start_offset] INT NULL, + [statement_end_offset] INT NULL, + JoinKey AS ServerName + CAST(CheckDate AS NVARCHAR(50)), + PRIMARY KEY CLUSTERED (ID ASC));'; + IF @Debug = 1 + BEGIN + PRINT CONVERT(VARCHAR(8000), SUBSTRING(@StringToExecute, 0, 8000)) + PRINT CONVERT(VARCHAR(8000), SUBSTRING(@StringToExecute, 8000, 16000)) + END + EXEC(@StringToExecute); + + /* If the table doesn't have the new JoinKey computed column, add it. See Github #2162. */ + SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableName; + SET @StringToExecute = N'IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns + WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''JoinKey'') + ALTER TABLE ' + @ObjectFullName + N' ADD JoinKey AS ServerName + CAST(CheckDate AS NVARCHAR(50));'; + EXEC(@StringToExecute); + + /* Delete history older than @OutputTableRetentionDays */ + SET @OutputTableCleanupDate = CAST( (DATEADD(DAY, -1 * @OutputTableRetentionDays, GETDATE() ) ) AS DATE); + SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' + + @OutputDatabaseName + + N'.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' + + @OutputSchemaName + N''') DELETE ' + + @OutputDatabaseName + '.' + + @OutputSchemaName + '.' + + @OutputTableName + + N' WHERE ServerName = @SrvName AND CheckDate < @CheckDate;'; + IF @Debug = 1 + BEGIN + PRINT CONVERT(VARCHAR(8000), SUBSTRING(@StringToExecute, 0, 8000)) + PRINT CONVERT(VARCHAR(8000), SUBSTRING(@StringToExecute, 8000, 16000)) + END + EXEC sp_executesql @StringToExecute, + N'@SrvName NVARCHAR(128), @CheckDate date', + @@SERVERNAME, @OutputTableCleanupDate; + + SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableNameQueryStats_View; + + /* Create the view */ + IF OBJECT_ID(@ObjectFullName) IS NULL + BEGIN + SET @StringToExecute = N'USE ' + + @OutputDatabaseName + + N'; EXEC (''CREATE VIEW ' + + @OutputSchemaName + '.' + + @OutputTableNameQueryStats_View + N' AS ' + @LineFeed + + N'WITH MaxQueryDuration AS ' + @LineFeed + + N'( ' + @LineFeed + + N' SELECT ' + @LineFeed + + N' MIN([ID]) AS [MinID], ' + @LineFeed + + N' MAX([ID]) AS [MaxID] ' + @LineFeed + + N' FROM ' + @OutputSchemaName + '.' + @OutputTableName + '' + @LineFeed + + N' GROUP BY [ServerName], ' + @LineFeed + + N' [session_id], ' + @LineFeed + + N' [database_name], ' + @LineFeed + + N' [request_time], ' + @LineFeed + + N' [start_time], ' + @LineFeed + + N' [sql_handle] ' + @LineFeed + + N') ' + @LineFeed + + N'SELECT ' + @LineFeed + + N' [ID], ' + @LineFeed + + N' [ServerName], ' + @LineFeed + + N' [CheckDate], ' + @LineFeed + + N' [elapsed_time], ' + @LineFeed + + N' [session_id], ' + @LineFeed + + N' [database_name], ' + @LineFeed + + N' [query_text_snippet], ' + @LineFeed + + N' [query_plan], ' + @LineFeed + + N' [live_query_plan], ' + @LineFeed + + N' [query_cost], ' + @LineFeed + + N' [status], ' + @LineFeed + + N' [wait_info], ' + @LineFeed + + N' [top_session_waits], ' + @LineFeed + + N' [blocking_session_id], ' + @LineFeed + + N' [open_transaction_count], ' + @LineFeed + + N' [is_implicit_transaction], ' + @LineFeed + + N' [nt_domain], ' + @LineFeed + + N' [host_name], ' + @LineFeed + + N' [login_name], ' + @LineFeed + + N' [nt_user_name], ' + @LineFeed + + N' [program_name], ' + @LineFeed + + N' [fix_parameter_sniffing], ' + @LineFeed + + N' [client_interface_name], ' + @LineFeed + + N' [login_time], ' + @LineFeed + + N' [start_time], ' + @LineFeed + + N' [request_time], ' + @LineFeed + + N' [request_cpu_time], ' + @LineFeed + + N' [degree_of_parallelism], ' + @LineFeed + + N' [request_logical_reads], ' + @LineFeed + + N' [Logical_Reads_MB], ' + @LineFeed + + N' [request_writes], ' + @LineFeed + + N' [Logical_Writes_MB], ' + @LineFeed + + N' [request_physical_reads], ' + @LineFeed + + N' [Physical_reads_MB], ' + @LineFeed + + N' [session_cpu], ' + @LineFeed + + N' [session_logical_reads], ' + @LineFeed + + N' [session_logical_reads_MB], ' + @LineFeed + + N' [session_physical_reads], ' + @LineFeed + + N' [session_physical_reads_MB], ' + @LineFeed + + N' [session_writes], ' + @LineFeed + + N' [session_writes_MB], ' + @LineFeed + + N' [tempdb_allocations_mb], ' + @LineFeed + + N' [memory_usage], ' + @LineFeed + + N' [estimated_completion_time], ' + @LineFeed + + N' [percent_complete], ' + @LineFeed + + N' [deadlock_priority], ' + @LineFeed + + N' [transaction_isolation_level], ' + @LineFeed + + N' [last_dop], ' + @LineFeed + + N' [min_dop], ' + @LineFeed + + N' [max_dop], ' + @LineFeed + + N' [last_grant_kb], ' + @LineFeed + + N' [min_grant_kb], ' + @LineFeed + + N' [max_grant_kb], ' + @LineFeed + + N' [last_used_grant_kb], ' + @LineFeed + + N' [min_used_grant_kb], ' + @LineFeed + + N' [max_used_grant_kb], ' + @LineFeed + + N' [last_ideal_grant_kb], ' + @LineFeed + + N' [min_ideal_grant_kb], ' + @LineFeed + + N' [max_ideal_grant_kb], ' + @LineFeed + + N' [last_reserved_threads], ' + @LineFeed + + N' [min_reserved_threads], ' + @LineFeed + + N' [max_reserved_threads], ' + @LineFeed + + N' [last_used_threads], ' + @LineFeed + + N' [min_used_threads], ' + @LineFeed + + N' [max_used_threads], ' + @LineFeed + + N' [grant_time], ' + @LineFeed + + N' [requested_memory_kb], ' + @LineFeed + + N' [grant_memory_kb], ' + @LineFeed + + N' [is_request_granted], ' + @LineFeed + + N' [required_memory_kb], ' + @LineFeed + + N' [query_memory_grant_used_memory_kb], ' + @LineFeed + + N' [ideal_memory_kb], ' + @LineFeed + + N' [is_small], ' + @LineFeed + + N' [timeout_sec], ' + @LineFeed + + N' [resource_semaphore_id], ' + @LineFeed + + N' [wait_order], ' + @LineFeed + + N' [wait_time_ms], ' + @LineFeed + + N' [next_candidate_for_memory_grant], ' + @LineFeed + + N' [target_memory_kb], ' + @LineFeed + + N' [max_target_memory_kb], ' + @LineFeed + + N' [total_memory_kb], ' + @LineFeed + + N' [available_memory_kb], ' + @LineFeed + + N' [granted_memory_kb], ' + @LineFeed + + N' [query_resource_semaphore_used_memory_kb], ' + @LineFeed + + N' [grantee_count], ' + @LineFeed + + N' [waiter_count], ' + @LineFeed + + N' [timeout_error_count], ' + @LineFeed + + N' [forced_grant_count], ' + @LineFeed + + N' [workload_group_name], ' + @LineFeed + + N' [resource_pool_name], ' + @LineFeed + + N' [context_info], ' + @LineFeed + + N' [query_hash], ' + @LineFeed + + N' [query_plan_hash], ' + @LineFeed + + N' [sql_handle], ' + @LineFeed + + N' [plan_handle], ' + @LineFeed + + N' [statement_start_offset], ' + @LineFeed + + N' [statement_end_offset] ' + @LineFeed + + N' FROM ' + @LineFeed + + N' ( ' + @LineFeed + + N' SELECT ' + @LineFeed + + N' [ID], ' + @LineFeed + + N' [ServerName], ' + @LineFeed + + N' [CheckDate], ' + @LineFeed + + N' [elapsed_time], ' + @LineFeed + + N' [session_id], ' + @LineFeed + + N' [database_name], ' + @LineFeed + + N' /* Truncate the query text to aid performance of painting the rows in SSMS */ ' + @LineFeed + + N' CAST([query_text] AS NVARCHAR(1000)) AS [query_text_snippet], ' + @LineFeed + + N' [query_plan], ' + @LineFeed + + N' [live_query_plan], ' + @LineFeed + + N' [query_cost], ' + @LineFeed + + N' [status], ' + @LineFeed + + N' [wait_info], ' + @LineFeed + + N' [top_session_waits], ' + @LineFeed + + N' [blocking_session_id], ' + @LineFeed + + N' [open_transaction_count], ' + @LineFeed + + N' [is_implicit_transaction], ' + @LineFeed + + N' [nt_domain], ' + @LineFeed + + N' [host_name], ' + @LineFeed + + N' [login_name], ' + @LineFeed + + N' [nt_user_name], ' + @LineFeed + + N' [program_name], ' + @LineFeed + + N' [fix_parameter_sniffing], ' + @LineFeed + + N' [client_interface_name], ' + @LineFeed + + N' [login_time], ' + @LineFeed + + N' [start_time], ' + @LineFeed + + N' [request_time], ' + @LineFeed + + N' [request_cpu_time], ' + @LineFeed + + N' [degree_of_parallelism], ' + @LineFeed + + N' [request_logical_reads], ' + @LineFeed + + N' ((CAST([request_logical_reads] AS MONEY)* 8)/ 1024) [Logical_Reads_MB], ' + @LineFeed + + N' [request_writes], ' + @LineFeed + + N' ((CAST([request_writes] AS MONEY)* 8)/ 1024) [Logical_Writes_MB], ' + @LineFeed + + N' [request_physical_reads], ' + @LineFeed + + N' ((CAST([request_physical_reads] AS MONEY)* 8)/ 1024) [Physical_reads_MB], ' + @LineFeed + + N' [session_cpu], ' + @LineFeed + + N' [session_logical_reads], ' + @LineFeed + + N' ((CAST([session_logical_reads] AS MONEY)* 8)/ 1024) [session_logical_reads_MB], ' + @LineFeed + + N' [session_physical_reads], ' + @LineFeed + + N' ((CAST([session_physical_reads] AS MONEY)* 8)/ 1024) [session_physical_reads_MB], ' + @LineFeed + + N' [session_writes], ' + @LineFeed + + N' ((CAST([session_writes] AS MONEY)* 8)/ 1024) [session_writes_MB], ' + @LineFeed + + N' [tempdb_allocations_mb], ' + @LineFeed + + N' [memory_usage], ' + @LineFeed + + N' [estimated_completion_time], ' + @LineFeed + + N' [percent_complete], ' + @LineFeed + + N' [deadlock_priority], ' + @LineFeed + + N' [transaction_isolation_level], ' + @LineFeed + + N' [last_dop], ' + @LineFeed + + N' [min_dop], ' + @LineFeed + + N' [max_dop], ' + @LineFeed + + N' [last_grant_kb], ' + @LineFeed + + N' [min_grant_kb], ' + @LineFeed + + N' [max_grant_kb], ' + @LineFeed + + N' [last_used_grant_kb], ' + @LineFeed + + N' [min_used_grant_kb], ' + @LineFeed + + N' [max_used_grant_kb], ' + @LineFeed + + N' [last_ideal_grant_kb], ' + @LineFeed + + N' [min_ideal_grant_kb], ' + @LineFeed + + N' [max_ideal_grant_kb], ' + @LineFeed + + N' [last_reserved_threads], ' + @LineFeed + + N' [min_reserved_threads], ' + @LineFeed + + N' [max_reserved_threads], ' + @LineFeed + + N' [last_used_threads], ' + @LineFeed + + N' [min_used_threads], ' + @LineFeed + + N' [max_used_threads], ' + @LineFeed + + N' [grant_time], ' + @LineFeed + + N' [requested_memory_kb], ' + @LineFeed + + N' [grant_memory_kb], ' + @LineFeed + + N' [is_request_granted], ' + @LineFeed + + N' [required_memory_kb], ' + @LineFeed + + N' [query_memory_grant_used_memory_kb], ' + @LineFeed + + N' [ideal_memory_kb], ' + @LineFeed + + N' [is_small], ' + @LineFeed + + N' [timeout_sec], ' + @LineFeed + + N' [resource_semaphore_id], ' + @LineFeed + + N' [wait_order], ' + @LineFeed + + N' [wait_time_ms], ' + @LineFeed + + N' [next_candidate_for_memory_grant], ' + @LineFeed + + N' [target_memory_kb], ' + @LineFeed + + N' [max_target_memory_kb], ' + @LineFeed + + N' [total_memory_kb], ' + @LineFeed + + N' [available_memory_kb], ' + @LineFeed + + N' [granted_memory_kb], ' + @LineFeed + + N' [query_resource_semaphore_used_memory_kb], ' + @LineFeed + + N' [grantee_count], ' + @LineFeed + + N' [waiter_count], ' + @LineFeed + + N' [timeout_error_count], ' + @LineFeed + + N' [forced_grant_count], ' + @LineFeed + + N' [workload_group_name], ' + @LineFeed + + N' [resource_pool_name], ' + @LineFeed + + N' [context_info], ' + @LineFeed + + N' [query_hash], ' + @LineFeed + + N' [query_plan_hash], ' + @LineFeed + + N' [sql_handle], ' + @LineFeed + + N' [plan_handle], ' + @LineFeed + + N' [statement_start_offset], ' + @LineFeed + + N' [statement_end_offset] ' + @LineFeed + + N' FROM ' + @OutputSchemaName + '.' + @OutputTableName + '' + @LineFeed + + N' ) AS [BlitzWho] ' + @LineFeed + + N'INNER JOIN [MaxQueryDuration] ON [BlitzWho].[ID] = [MaxQueryDuration].[MaxID]; ' + @LineFeed + + N''');' + + IF @Debug = 1 + BEGIN + PRINT CONVERT(VARCHAR(8000), SUBSTRING(@StringToExecute, 0, 8000)) + PRINT CONVERT(VARCHAR(8000), SUBSTRING(@StringToExecute, 8000, 16000)) + END + + EXEC(@StringToExecute); + END; + + END + + IF OBJECT_ID('tempdb..#WhoReadableDBs') IS NOT NULL + DROP TABLE #WhoReadableDBs; + +CREATE TABLE #WhoReadableDBs +( +database_id INT +); + +IF EXISTS (SELECT * FROM sys.all_objects o WHERE o.name = 'dm_hadr_database_replica_states') +BEGIN + RAISERROR('Checking for Read intent databases to exclude',0,0) WITH NOWAIT; + + EXEC('INSERT INTO #WhoReadableDBs (database_id) SELECT DBs.database_id FROM sys.databases DBs INNER JOIN sys.availability_replicas Replicas ON DBs.replica_id = Replicas.replica_id WHERE replica_server_name NOT IN (SELECT DISTINCT primary_replica FROM sys.dm_hadr_availability_group_states States) AND Replicas.secondary_role_allow_connections_desc = ''READ_ONLY'' AND replica_server_name = @@SERVERNAME;'); +END + +SELECT @BlockingCheck = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + + DECLARE @blocked TABLE + ( + dbid SMALLINT NOT NULL, + last_batch DATETIME NOT NULL, + open_tran SMALLINT NOT NULL, + sql_handle BINARY(20) NOT NULL, + session_id SMALLINT NOT NULL, + blocking_session_id SMALLINT NOT NULL, + lastwaittype NCHAR(32) NOT NULL, + waittime BIGINT NOT NULL, + cpu INT NOT NULL, + physical_io BIGINT NOT NULL, + memusage INT NOT NULL + ); + + INSERT @blocked ( dbid, last_batch, open_tran, sql_handle, session_id, blocking_session_id, lastwaittype, waittime, cpu, physical_io, memusage ) + SELECT + sys1.dbid, sys1.last_batch, sys1.open_tran, sys1.sql_handle, + sys2.spid AS session_id, sys2.blocked AS blocking_session_id, sys2.lastwaittype, sys2.waittime, sys2.cpu, sys2.physical_io, sys2.memusage + FROM sys.sysprocesses AS sys1 + JOIN sys.sysprocesses AS sys2 + ON sys1.spid = sys2.blocked;'; + +IF @ProductVersionMajor > 9 and @ProductVersionMajor < 11 +BEGIN + /* Think of the StringToExecute as starting with this, but we'll set this up later depending on whether we're doing an insert or a select: + SELECT @StringToExecute = N'SELECT GETDATE() AS run_date , + */ + SET @StringToExecute = N'COALESCE( RIGHT(''00'' + CONVERT(VARCHAR(20), (ABS(r.total_elapsed_time) / 1000) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), (DATEADD(SECOND, (r.total_elapsed_time / 1000), 0) + DATEADD(MILLISECOND, (r.total_elapsed_time % 1000), 0)), 114), RIGHT(''00'' + CONVERT(VARCHAR(20), DATEDIFF(SECOND, s.last_request_start_time, GETDATE()) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), DATEADD(SECOND, DATEDIFF(SECOND, s.last_request_start_time, GETDATE()), 0), 114) ) AS [elapsed_time] , + s.session_id , + COALESCE(DB_NAME(r.database_id), DB_NAME(blocked.dbid), ''N/A'') AS database_name, + ISNULL(SUBSTRING(dest.text, + ( query_stats.statement_start_offset / 2 ) + 1, + ( ( CASE query_stats.statement_end_offset + WHEN -1 THEN DATALENGTH(dest.text) + ELSE query_stats.statement_end_offset + END - query_stats.statement_start_offset ) + / 2 ) + 1), dest.text) AS query_text , + derp.query_plan , + qmg.query_cost , + s.status , + CASE + WHEN s.status <> ''sleeping'' THEN COALESCE(wt.wait_info, RTRIM(blocked.lastwaittype) + '' ('' + CONVERT(VARCHAR(10), blocked.waittime) + '')'' ) + ELSE NULL + END AS wait_info , + CASE WHEN r.blocking_session_id <> 0 AND blocked.session_id IS NULL + THEN r.blocking_session_id + WHEN r.blocking_session_id <> 0 AND s.session_id <> blocked.blocking_session_id + THEN blocked.blocking_session_id + WHEN r.blocking_session_id = 0 AND s.session_id = blocked.session_id + THEN blocked.blocking_session_id + WHEN r.blocking_session_id <> 0 AND s.session_id = blocked.blocking_session_id + THEN r.blocking_session_id + ELSE NULL + END AS blocking_session_id, + COALESCE(r.open_transaction_count, blocked.open_tran) AS open_transaction_count , + CASE WHEN EXISTS ( SELECT 1 + FROM sys.dm_tran_active_transactions AS tat + JOIN sys.dm_tran_session_transactions AS tst + ON tst.transaction_id = tat.transaction_id + WHERE tat.name = ''implicit_transaction'' + AND s.session_id = tst.session_id + ) THEN 1 + ELSE 0 + END AS is_implicit_transaction , + s.nt_domain , + s.host_name , + s.login_name , + s.nt_user_name ,' + IF @Platform = 'NonAzure' + BEGIN + SET @StringToExecute += + N'program_name = COALESCE(( + SELECT REPLACE(program_name,Substring(program_name,30,34),''"''+j.name+''"'') + FROM msdb.dbo.sysjobs j WHERE Substring(program_name,32,32) = CONVERT(char(32),CAST(j.job_id AS binary(16)),2) + ),s.program_name)' + END + ELSE + BEGIN + SET @StringToExecute += N's.program_name' + END + + IF @ExpertMode = 1 + BEGIN + SET @StringToExecute += + N', + ''DBCC FREEPROCCACHE ('' + CONVERT(NVARCHAR(128), r.plan_handle, 1) + '');'' AS fix_parameter_sniffing, + s.client_interface_name , + s.login_time , + r.start_time , + qmg.request_time , + COALESCE(r.cpu_time, s.cpu_time) AS request_cpu_time, + COALESCE(r.logical_reads, s.logical_reads) AS request_logical_reads, + COALESCE(r.writes, s.writes) AS request_writes, + COALESCE(r.reads, s.reads) AS request_physical_reads , + s.cpu_time AS session_cpu, + s.logical_reads AS session_logical_reads, + s.reads AS session_physical_reads , + s.writes AS session_writes, + tempdb_allocations.tempdb_allocations_mb, + s.memory_usage , + r.estimated_completion_time , + r.percent_complete , + r.deadlock_priority , + CASE + WHEN s.transaction_isolation_level = 0 THEN ''Unspecified'' + WHEN s.transaction_isolation_level = 1 THEN ''Read Uncommitted'' + WHEN s.transaction_isolation_level = 2 AND EXISTS (SELECT 1 FROM sys.databases WHERE name = DB_NAME(r.database_id) AND is_read_committed_snapshot_on = 1) THEN ''Read Committed Snapshot Isolation'' + WHEN s.transaction_isolation_level = 2 THEN ''Read Committed'' + WHEN s.transaction_isolation_level = 3 THEN ''Repeatable Read'' + WHEN s.transaction_isolation_level = 4 THEN ''Serializable'' + WHEN s.transaction_isolation_level = 5 THEN ''Snapshot'' + ELSE ''WHAT HAVE YOU DONE?'' + END AS transaction_isolation_level , + qmg.dop AS degree_of_parallelism , + COALESCE(CAST(qmg.grant_time AS VARCHAR(20)), ''N/A'') AS grant_time , + qmg.requested_memory_kb , + qmg.granted_memory_kb AS grant_memory_kb, + CASE WHEN qmg.grant_time IS NULL THEN ''N/A'' + WHEN qmg.requested_memory_kb < qmg.granted_memory_kb + THEN ''Query Granted Less Than Query Requested'' + ELSE ''Memory Request Granted'' + END AS is_request_granted , + qmg.required_memory_kb , + qmg.used_memory_kb AS query_memory_grant_used_memory_kb, + qmg.ideal_memory_kb , + qmg.is_small , + qmg.timeout_sec , + qmg.resource_semaphore_id , + COALESCE(CAST(qmg.wait_order AS VARCHAR(20)), ''N/A'') AS wait_order , + COALESCE(CAST(qmg.wait_time_ms AS VARCHAR(20)), + ''N/A'') AS wait_time_ms , + CASE qmg.is_next_candidate + WHEN 0 THEN ''No'' + WHEN 1 THEN ''Yes'' + ELSE ''N/A'' + END AS next_candidate_for_memory_grant , + qrs.target_memory_kb , + COALESCE(CAST(qrs.max_target_memory_kb AS VARCHAR(20)), + ''Small Query Resource Semaphore'') AS max_target_memory_kb , + qrs.total_memory_kb , + qrs.available_memory_kb , + qrs.granted_memory_kb , + qrs.used_memory_kb AS query_resource_semaphore_used_memory_kb, + qrs.grantee_count , + qrs.waiter_count , + qrs.timeout_error_count , + COALESCE(CAST(qrs.forced_grant_count AS VARCHAR(20)), + ''Small Query Resource Semaphore'') AS forced_grant_count, + wg.name AS workload_group_name , + rp.name AS resource_pool_name, + CONVERT(VARCHAR(128), r.context_info) AS context_info + ' + END /* IF @ExpertMode = 1 */ + + SET @StringToExecute += + N'FROM sys.dm_exec_sessions AS s + LEFT JOIN sys.dm_exec_requests AS r + ON r.session_id = s.session_id + LEFT JOIN ( SELECT DISTINCT + wait.session_id , + ( SELECT waitwait.wait_type + N'' ('' + + CAST(MAX(waitwait.wait_duration_ms) AS NVARCHAR(128)) + + N'' ms) '' + FROM sys.dm_os_waiting_tasks AS waitwait + WHERE waitwait.session_id = wait.session_id + GROUP BY waitwait.wait_type + ORDER BY SUM(waitwait.wait_duration_ms) DESC + FOR + XML PATH('''') ) AS wait_info + FROM sys.dm_os_waiting_tasks AS wait ) AS wt + ON s.session_id = wt.session_id + LEFT JOIN sys.dm_exec_query_stats AS query_stats + ON r.sql_handle = query_stats.sql_handle + AND r.plan_handle = query_stats.plan_handle + AND r.statement_start_offset = query_stats.statement_start_offset + AND r.statement_end_offset = query_stats.statement_end_offset + LEFT JOIN sys.dm_exec_query_memory_grants qmg + ON r.session_id = qmg.session_id + AND r.request_id = qmg.request_id + LEFT JOIN sys.dm_exec_query_resource_semaphores qrs + ON qmg.resource_semaphore_id = qrs.resource_semaphore_id + AND qmg.pool_id = qrs.pool_id + LEFT JOIN sys.resource_governor_workload_groups wg + ON s.group_id = wg.group_id + LEFT JOIN sys.resource_governor_resource_pools rp + ON wg.pool_id = rp.pool_id + OUTER APPLY ( + SELECT TOP 1 + b.dbid, b.last_batch, b.open_tran, b.sql_handle, + b.session_id, b.blocking_session_id, b.lastwaittype, b.waittime + FROM @blocked b + WHERE (s.session_id = b.session_id + OR s.session_id = b.blocking_session_id) + ) AS blocked + OUTER APPLY sys.dm_exec_sql_text(COALESCE(r.sql_handle, blocked.sql_handle)) AS dest + OUTER APPLY sys.dm_exec_query_plan(r.plan_handle) AS derp + OUTER APPLY ( + SELECT CONVERT(DECIMAL(38,2), SUM( (((tsu.user_objects_alloc_page_count - user_objects_dealloc_page_count) * 8) / 1024.)) ) AS tempdb_allocations_mb + FROM sys.dm_db_task_space_usage tsu + WHERE tsu.request_id = r.request_id + AND tsu.session_id = r.session_id + AND tsu.session_id = s.session_id + ) as tempdb_allocations + WHERE s.session_id <> @@SPID + AND s.host_name IS NOT NULL + ' + + CASE WHEN @ShowSleepingSPIDs = 0 THEN + N' AND COALESCE(DB_NAME(r.database_id), DB_NAME(blocked.dbid)) IS NOT NULL' + WHEN @ShowSleepingSPIDs = 1 THEN + N' OR COALESCE(r.open_transaction_count, blocked.open_tran) >= 1' + ELSE N'' END; +END /* IF @ProductVersionMajor > 9 and @ProductVersionMajor < 11 */ + +IF @ProductVersionMajor >= 11 + BEGIN + SELECT @EnhanceFlag = + CASE WHEN @ProductVersionMajor = 11 AND @ProductVersionMinor >= 6020 THEN 1 + WHEN @ProductVersionMajor = 12 AND @ProductVersionMinor >= 5000 THEN 1 + WHEN @ProductVersionMajor = 13 AND @ProductVersionMinor >= 1601 THEN 1 + WHEN @ProductVersionMajor > 13 THEN 1 + ELSE 0 + END + + + IF OBJECT_ID('sys.dm_exec_session_wait_stats') IS NOT NULL + BEGIN + SET @SessionWaits = 1 + END + + /* Think of the StringToExecute as starting with this, but we'll set this up later depending on whether we're doing an insert or a select: + SELECT @StringToExecute = N'SELECT GETDATE() AS run_date , + */ + SELECT @StringToExecute = N'COALESCE( RIGHT(''00'' + CONVERT(VARCHAR(20), (ABS(r.total_elapsed_time) / 1000) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), (DATEADD(SECOND, (r.total_elapsed_time / 1000), 0) + DATEADD(MILLISECOND, (r.total_elapsed_time % 1000), 0)), 114), RIGHT(''00'' + CONVERT(VARCHAR(20), DATEDIFF(SECOND, s.last_request_start_time, GETDATE()) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), DATEADD(SECOND, DATEDIFF(SECOND, s.last_request_start_time, GETDATE()), 0), 114) ) AS [elapsed_time] , + s.session_id , + COALESCE(DB_NAME(r.database_id), DB_NAME(blocked.dbid), ''N/A'') AS database_name, + ISNULL(SUBSTRING(dest.text, + ( query_stats.statement_start_offset / 2 ) + 1, + ( ( CASE query_stats.statement_end_offset + WHEN -1 THEN DATALENGTH(dest.text) + ELSE query_stats.statement_end_offset + END - query_stats.statement_start_offset ) + / 2 ) + 1), dest.text) AS query_text , + derp.query_plan ,' + + @QueryStatsXMLselect + +' + qmg.query_cost , + s.status , + CASE + WHEN s.status <> ''sleeping'' THEN COALESCE(wt.wait_info, RTRIM(blocked.lastwaittype) + '' ('' + CONVERT(VARCHAR(10), blocked.waittime) + '')'' ) + ELSE NULL + END AS wait_info ,' + + + CASE @SessionWaits + WHEN 1 THEN + N'SUBSTRING(wt2.session_wait_info, 0, LEN(wt2.session_wait_info) ) AS top_session_waits ,' + ELSE N' NULL AS top_session_waits ,' + END + + + N'CASE WHEN r.blocking_session_id <> 0 AND blocked.session_id IS NULL + THEN r.blocking_session_id + WHEN r.blocking_session_id <> 0 AND s.session_id <> blocked.blocking_session_id + THEN blocked.blocking_session_id + WHEN r.blocking_session_id = 0 AND s.session_id = blocked.session_id + THEN blocked.blocking_session_id + WHEN r.blocking_session_id <> 0 AND s.session_id = blocked.blocking_session_id + THEN r.blocking_session_id + ELSE NULL + END AS blocking_session_id, + COALESCE(r.open_transaction_count, blocked.open_tran) AS open_transaction_count , + CASE WHEN EXISTS ( SELECT 1 + FROM sys.dm_tran_active_transactions AS tat + JOIN sys.dm_tran_session_transactions AS tst + ON tst.transaction_id = tat.transaction_id + WHERE tat.name = ''implicit_transaction'' + AND s.session_id = tst.session_id + ) THEN 1 + ELSE 0 + END AS is_implicit_transaction , + s.nt_domain , + s.host_name , + s.login_name , + s.nt_user_name ,' + IF @Platform = 'NonAzure' + BEGIN + SET @StringToExecute += + N'program_name = COALESCE(( + SELECT REPLACE(program_name,Substring(program_name,30,34),''"''+j.name+''"'') + FROM msdb.dbo.sysjobs j WHERE Substring(program_name,32,32) = CONVERT(char(32),CAST(j.job_id AS binary(16)),2) + ),s.program_name)' + END + ELSE + BEGIN + SET @StringToExecute += N's.program_name' + END + + IF @ExpertMode = 1 /* We show more columns in expert mode, so the SELECT gets longer */ + BEGIN + SET @StringToExecute += + N', ''DBCC FREEPROCCACHE ('' + CONVERT(NVARCHAR(128), r.plan_handle, 1) + '');'' AS fix_parameter_sniffing, + s.client_interface_name , + s.login_time , + r.start_time , + qmg.request_time , + COALESCE(r.cpu_time, s.cpu_time) AS request_cpu_time, + COALESCE(r.logical_reads, s.logical_reads) AS request_logical_reads, + COALESCE(r.writes, s.writes) AS request_writes, + COALESCE(r.reads, s.reads) AS request_physical_reads , + s.cpu_time AS session_cpu, + s.logical_reads AS session_logical_reads, + s.reads AS session_physical_reads , + s.writes AS session_writes, + tempdb_allocations.tempdb_allocations_mb, + s.memory_usage , + r.estimated_completion_time , + r.percent_complete , + r.deadlock_priority , + CASE + WHEN s.transaction_isolation_level = 0 THEN ''Unspecified'' + WHEN s.transaction_isolation_level = 1 THEN ''Read Uncommitted'' + WHEN s.transaction_isolation_level = 2 AND EXISTS (SELECT 1 FROM sys.databases WHERE name = DB_NAME(r.database_id) AND is_read_committed_snapshot_on = 1) THEN ''Read Committed Snapshot Isolation'' + WHEN s.transaction_isolation_level = 2 THEN ''Read Committed'' + WHEN s.transaction_isolation_level = 3 THEN ''Repeatable Read'' + WHEN s.transaction_isolation_level = 4 THEN ''Serializable'' + WHEN s.transaction_isolation_level = 5 THEN ''Snapshot'' + ELSE ''WHAT HAVE YOU DONE?'' + END AS transaction_isolation_level , + qmg.dop AS degree_of_parallelism , ' + + + CASE @EnhanceFlag + WHEN 1 THEN N'query_stats.last_dop, + query_stats.min_dop, + query_stats.max_dop, + query_stats.last_grant_kb, + query_stats.min_grant_kb, + query_stats.max_grant_kb, + query_stats.last_used_grant_kb, + query_stats.min_used_grant_kb, + query_stats.max_used_grant_kb, + query_stats.last_ideal_grant_kb, + query_stats.min_ideal_grant_kb, + query_stats.max_ideal_grant_kb, + query_stats.last_reserved_threads, + query_stats.min_reserved_threads, + query_stats.max_reserved_threads, + query_stats.last_used_threads, + query_stats.min_used_threads, + query_stats.max_used_threads,' + ELSE N' NULL AS last_dop, + NULL AS min_dop, + NULL AS max_dop, + NULL AS last_grant_kb, + NULL AS min_grant_kb, + NULL AS max_grant_kb, + NULL AS last_used_grant_kb, + NULL AS min_used_grant_kb, + NULL AS max_used_grant_kb, + NULL AS last_ideal_grant_kb, + NULL AS min_ideal_grant_kb, + NULL AS max_ideal_grant_kb, + NULL AS last_reserved_threads, + NULL AS min_reserved_threads, + NULL AS max_reserved_threads, + NULL AS last_used_threads, + NULL AS min_used_threads, + NULL AS max_used_threads,' + END + + SET @StringToExecute += + N' + COALESCE(CAST(qmg.grant_time AS VARCHAR(20)), ''Memory Not Granted'') AS grant_time , + qmg.requested_memory_kb , + qmg.granted_memory_kb AS grant_memory_kb, + CASE WHEN qmg.grant_time IS NULL THEN ''N/A'' + WHEN qmg.requested_memory_kb < qmg.granted_memory_kb + THEN ''Query Granted Less Than Query Requested'' + ELSE ''Memory Request Granted'' + END AS is_request_granted , + qmg.required_memory_kb , + qmg.used_memory_kb AS query_memory_grant_used_memory_kb, + qmg.ideal_memory_kb , + qmg.is_small , + qmg.timeout_sec , + qmg.resource_semaphore_id , + COALESCE(CAST(qmg.wait_order AS VARCHAR(20)), ''N/A'') AS wait_order , + COALESCE(CAST(qmg.wait_time_ms AS VARCHAR(20)), + ''N/A'') AS wait_time_ms , + CASE qmg.is_next_candidate + WHEN 0 THEN ''No'' + WHEN 1 THEN ''Yes'' + ELSE ''N/A'' + END AS next_candidate_for_memory_grant , + qrs.target_memory_kb , + COALESCE(CAST(qrs.max_target_memory_kb AS VARCHAR(20)), + ''Small Query Resource Semaphore'') AS max_target_memory_kb , + qrs.total_memory_kb , + qrs.available_memory_kb , + qrs.granted_memory_kb , + qrs.used_memory_kb AS query_resource_semaphore_used_memory_kb, + qrs.grantee_count , + qrs.waiter_count , + qrs.timeout_error_count , + COALESCE(CAST(qrs.forced_grant_count AS VARCHAR(20)), + ''Small Query Resource Semaphore'') AS forced_grant_count, + wg.name AS workload_group_name, + rp.name AS resource_pool_name, + CONVERT(VARCHAR(128), r.context_info) AS context_info, + r.query_hash, r.query_plan_hash, r.sql_handle, r.plan_handle, r.statement_start_offset, r.statement_end_offset ' + END /* IF @ExpertMode = 1 */ + + SET @StringToExecute += + N' FROM sys.dm_exec_sessions AS s + LEFT JOIN sys.dm_exec_requests AS r + ON r.session_id = s.session_id + LEFT JOIN ( SELECT DISTINCT + wait.session_id , + ( SELECT waitwait.wait_type + N'' ('' + + CAST(MAX(waitwait.wait_duration_ms) AS NVARCHAR(128)) + + N'' ms) '' + FROM sys.dm_os_waiting_tasks AS waitwait + WHERE waitwait.session_id = wait.session_id + GROUP BY waitwait.wait_type + ORDER BY SUM(waitwait.wait_duration_ms) DESC + FOR + XML PATH('''') ) AS wait_info + FROM sys.dm_os_waiting_tasks AS wait ) AS wt + ON s.session_id = wt.session_id + LEFT JOIN sys.dm_exec_query_stats AS query_stats + ON r.sql_handle = query_stats.sql_handle + AND r.plan_handle = query_stats.plan_handle + AND r.statement_start_offset = query_stats.statement_start_offset + AND r.statement_end_offset = query_stats.statement_end_offset + ' + + + CASE @SessionWaits + WHEN 1 THEN @SessionWaitsSQL + ELSE N'' + END + + + N' + LEFT JOIN sys.dm_exec_query_memory_grants qmg + ON r.session_id = qmg.session_id + AND r.request_id = qmg.request_id + LEFT JOIN sys.dm_exec_query_resource_semaphores qrs + ON qmg.resource_semaphore_id = qrs.resource_semaphore_id + AND qmg.pool_id = qrs.pool_id + LEFT JOIN sys.resource_governor_workload_groups wg + ON s.group_id = wg.group_id + LEFT JOIN sys.resource_governor_resource_pools rp + ON wg.pool_id = rp.pool_id + OUTER APPLY ( + SELECT TOP 1 + b.dbid, b.last_batch, b.open_tran, b.sql_handle, + b.session_id, b.blocking_session_id, b.lastwaittype, b.waittime + FROM @blocked b + WHERE (s.session_id = b.session_id + OR s.session_id = b.blocking_session_id) + ) AS blocked + OUTER APPLY sys.dm_exec_sql_text(COALESCE(r.sql_handle, blocked.sql_handle)) AS dest + OUTER APPLY sys.dm_exec_query_plan(r.plan_handle) AS derp + OUTER APPLY ( + SELECT CONVERT(DECIMAL(38,2), SUM( (((tsu.user_objects_alloc_page_count - user_objects_dealloc_page_count) * 8) / 1024.)) ) AS tempdb_allocations_mb + FROM sys.dm_db_task_space_usage tsu + WHERE tsu.request_id = r.request_id + AND tsu.session_id = r.session_id + AND tsu.session_id = s.session_id + ) as tempdb_allocations + ' + + @QueryStatsXMLSQL + + + N' + WHERE s.session_id <> @@SPID + AND s.host_name IS NOT NULL + AND r.database_id NOT IN (SELECT database_id FROM #WhoReadableDBs) + ' + + CASE WHEN @ShowSleepingSPIDs = 0 THEN + N' AND COALESCE(DB_NAME(r.database_id), DB_NAME(blocked.dbid)) IS NOT NULL' + WHEN @ShowSleepingSPIDs = 1 THEN + N' OR COALESCE(r.open_transaction_count, blocked.open_tran) >= 1' + ELSE N'' END; + + +END /* IF @ProductVersionMajor >= 11 */ + +IF (@MinElapsedSeconds + @MinCPUTime + @MinLogicalReads + @MinPhysicalReads + @MinWrites + @MinTempdbMB + @MinRequestedMemoryKB + @MinBlockingSeconds) > 0 + BEGIN + /* They're filtering for something, so set up a where clause that will let any (not all combined) of the min triggers work: */ + SET @StringToExecute += N' AND (1 = 0 '; + IF @MinElapsedSeconds > 0 + SET @StringToExecute += N' OR ABS(COALESCE(r.total_elapsed_time,0)) / 1000 >= ' + CAST(@MinElapsedSeconds AS NVARCHAR(20)); + IF @MinCPUTime > 0 + SET @StringToExecute += N' OR COALESCE(r.cpu_time, s.cpu_time,0) / 1000 >= ' + CAST(@MinCPUTime AS NVARCHAR(20)); + IF @MinLogicalReads > 0 + SET @StringToExecute += N' OR COALESCE(r.logical_reads, s.logical_reads,0) >= ' + CAST(@MinLogicalReads AS NVARCHAR(20)); + IF @MinPhysicalReads > 0 + SET @StringToExecute += N' OR COALESCE(s.reads,0) >= ' + CAST(@MinPhysicalReads AS NVARCHAR(20)); + IF @MinWrites > 0 + SET @StringToExecute += N' OR COALESCE(r.writes, s.writes,0) >= ' + CAST(@MinWrites AS NVARCHAR(20)); + IF @MinTempdbMB > 0 + SET @StringToExecute += N' OR COALESCE(tempdb_allocations.tempdb_allocations_mb,0) >= ' + CAST(@MinTempdbMB AS NVARCHAR(20)); + IF @MinRequestedMemoryKB > 0 + SET @StringToExecute += N' OR COALESCE(qmg.requested_memory_kb,0) >= ' + CAST(@MinRequestedMemoryKB AS NVARCHAR(20)); + /* Blocking is a little different - we're going to return ALL of the queries if we meet the blocking threshold. */ + IF @MinBlockingSeconds > 0 + SET @StringToExecute += N' OR (SELECT SUM(waittime / 1000) FROM @blocked) >= ' + CAST(@MinBlockingSeconds AS NVARCHAR(20)); + SET @StringToExecute += N' ) '; + END + +SET @StringToExecute += + N' ORDER BY ' + CASE WHEN @SortOrder = 'session_id' THEN '[session_id] DESC' + WHEN @SortOrder = 'query_cost' THEN '[query_cost] DESC' + WHEN @SortOrder = 'database_name' THEN '[database_name] ASC' + WHEN @SortOrder = 'open_transaction_count' THEN '[open_transaction_count] DESC' + WHEN @SortOrder = 'is_implicit_transaction' THEN '[is_implicit_transaction] DESC' + WHEN @SortOrder = 'login_name' THEN '[login_name] ASC' + WHEN @SortOrder = 'program_name' THEN '[program_name] ASC' + WHEN @SortOrder = 'client_interface_name' THEN '[client_interface_name] ASC' + WHEN @SortOrder = 'request_cpu_time' THEN 'COALESCE(r.cpu_time, s.cpu_time) DESC' + WHEN @SortOrder = 'request_logical_reads' THEN 'COALESCE(r.logical_reads, s.logical_reads) DESC' + WHEN @SortOrder = 'request_writes' THEN 'COALESCE(r.writes, s.writes) DESC' + WHEN @SortOrder = 'request_physical_reads' THEN 'COALESCE(r.reads, s.reads) DESC ' + WHEN @SortOrder = 'session_cpu' THEN 's.cpu_time DESC' + WHEN @SortOrder = 'session_logical_reads' THEN 's.logical_reads DESC' + WHEN @SortOrder = 'session_physical_reads' THEN 's.reads DESC' + WHEN @SortOrder = 'session_writes' THEN 's.writes DESC' + WHEN @SortOrder = 'tempdb_allocations_mb' THEN '[tempdb_allocations_mb] DESC' + WHEN @SortOrder = 'memory_usage' THEN '[memory_usage] DESC' + WHEN @SortOrder = 'deadlock_priority' THEN 'r.deadlock_priority DESC' + WHEN @SortOrder = 'transaction_isolation_level' THEN 'r.[transaction_isolation_level] DESC' + WHEN @SortOrder = 'requested_memory_kb' THEN '[requested_memory_kb] DESC' + WHEN @SortOrder = 'grant_memory_kb' THEN 'qmg.granted_memory_kb DESC' + WHEN @SortOrder = 'grant' THEN 'qmg.granted_memory_kb DESC' + WHEN @SortOrder = 'query_memory_grant_used_memory_kb' THEN 'qmg.used_memory_kb DESC' + WHEN @SortOrder = 'ideal_memory_kb' THEN '[ideal_memory_kb] DESC' + WHEN @SortOrder = 'workload_group_name' THEN 'wg.name ASC' + WHEN @SortOrder = 'resource_pool_name' THEN 'rp.name ASC' + ELSE '[elapsed_time] DESC' + END + ' + '; + + +IF @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @OutputTableName IS NOT NULL + AND EXISTS ( SELECT * + FROM sys.databases + WHERE QUOTENAME([name]) = @OutputDatabaseName) + BEGIN + SET @StringToExecute = N'USE ' + + @OutputDatabaseName + N'; ' + + @BlockingCheck + + + ' INSERT INTO ' + + @OutputSchemaName + N'.' + + @OutputTableName + + N'(ServerName + ,CheckDate + ,[elapsed_time] + ,[session_id] + ,[database_name] + ,[query_text] + ,[query_plan]' + + CASE WHEN @ProductVersionMajor >= 11 THEN N',[live_query_plan]' ELSE N'' END + N' + ,[query_cost] + ,[status] + ,[wait_info]' + + CASE WHEN @ProductVersionMajor >= 11 THEN N',[top_session_waits]' ELSE N'' END + N' + ,[blocking_session_id] + ,[open_transaction_count] + ,[is_implicit_transaction] + ,[nt_domain] + ,[host_name] + ,[login_name] + ,[nt_user_name] + ,[program_name] + ,[fix_parameter_sniffing] + ,[client_interface_name] + ,[login_time] + ,[start_time] + ,[request_time] + ,[request_cpu_time] + ,[request_logical_reads] + ,[request_writes] + ,[request_physical_reads] + ,[session_cpu] + ,[session_logical_reads] + ,[session_physical_reads] + ,[session_writes] + ,[tempdb_allocations_mb] + ,[memory_usage] + ,[estimated_completion_time] + ,[percent_complete] + ,[deadlock_priority] + ,[transaction_isolation_level] + ,[degree_of_parallelism]' + + CASE WHEN @ProductVersionMajor >= 11 THEN N' + ,[last_dop] + ,[min_dop] + ,[max_dop] + ,[last_grant_kb] + ,[min_grant_kb] + ,[max_grant_kb] + ,[last_used_grant_kb] + ,[min_used_grant_kb] + ,[max_used_grant_kb] + ,[last_ideal_grant_kb] + ,[min_ideal_grant_kb] + ,[max_ideal_grant_kb] + ,[last_reserved_threads] + ,[min_reserved_threads] + ,[max_reserved_threads] + ,[last_used_threads] + ,[min_used_threads] + ,[max_used_threads]' ELSE N'' END + N' + ,[grant_time] + ,[requested_memory_kb] + ,[grant_memory_kb] + ,[is_request_granted] + ,[required_memory_kb] + ,[query_memory_grant_used_memory_kb] + ,[ideal_memory_kb] + ,[is_small] + ,[timeout_sec] + ,[resource_semaphore_id] + ,[wait_order] + ,[wait_time_ms] + ,[next_candidate_for_memory_grant] + ,[target_memory_kb] + ,[max_target_memory_kb] + ,[total_memory_kb] + ,[available_memory_kb] + ,[granted_memory_kb] + ,[query_resource_semaphore_used_memory_kb] + ,[grantee_count] + ,[waiter_count] + ,[timeout_error_count] + ,[forced_grant_count] + ,[workload_group_name] + ,[resource_pool_name] + ,[context_info]' + + CASE WHEN @ProductVersionMajor >= 11 THEN N' + ,[query_hash] + ,[query_plan_hash] + ,[sql_handle] + ,[plan_handle] + ,[statement_start_offset] + ,[statement_end_offset]' ELSE N'' END + N' +) + SELECT @@SERVERNAME, COALESCE(@CheckDateOverride, SYSDATETIMEOFFSET()) AS CheckDate , ' + + @StringToExecute; + END +ELSE + SET @StringToExecute = @BlockingCheck + N' SELECT GETDATE() AS run_date , ' + @StringToExecute; + +/* If the server has > 50GB of memory, add a max grant hint to avoid getting a giant grant */ +IF (@ProductVersionMajor = 11 AND @ProductVersionMinor >= 6020) + OR (@ProductVersionMajor = 12 AND @ProductVersionMinor >= 5000 ) + OR (@ProductVersionMajor >= 13 ) + AND 50000000 < (SELECT cntr_value + FROM sys.dm_os_performance_counters + WHERE object_name LIKE '%:Memory Manager%' + AND counter_name LIKE 'Target Server Memory (KB)%') + BEGIN + SET @StringToExecute = @StringToExecute + N' OPTION (MAX_GRANT_PERCENT = 1, RECOMPILE) '; + END +ELSE + BEGIN + SET @StringToExecute = @StringToExecute + N' OPTION (RECOMPILE) '; + END + +/* Be good: */ +SET @StringToExecute = @StringToExecute + N' ; '; + + +IF @Debug = 1 + BEGIN + PRINT CONVERT(VARCHAR(8000), SUBSTRING(@StringToExecute, 0, 8000)) + PRINT CONVERT(VARCHAR(8000), SUBSTRING(@StringToExecute, 8000, 16000)) + END + +EXEC sp_executesql @StringToExecute, + N'@CheckDateOverride DATETIMEOFFSET', + @CheckDateOverride; + +END +GO + +IF (OBJECT_ID('dbo.SqlServerVersions') IS NULL) +BEGIN + + CREATE TABLE dbo.SqlServerVersions + ( + MajorVersionNumber tinyint not null, + MinorVersionNumber smallint not null, + Branch varchar(34) not null, + [Url] varchar(99) not null, + ReleaseDate date not null, + MainstreamSupportEndDate date not null, + ExtendedSupportEndDate date not null, + MajorVersionName varchar(19) not null, + MinorVersionName varchar(67) not null, + + CONSTRAINT PK_SqlServerVersions PRIMARY KEY CLUSTERED + ( + MajorVersionNumber ASC, + MinorVersionNumber ASC, + ReleaseDate ASC + ) + ); + + EXEC sys.sp_addextendedproperty @name=N'Description', @value=N'The major version number.' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'SqlServerVersions', @level2type=N'COLUMN',@level2name=N'MajorVersionNumber' + EXEC sys.sp_addextendedproperty @name=N'Description', @value=N'The minor version number.' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'SqlServerVersions', @level2type=N'COLUMN',@level2name=N'MinorVersionNumber' + EXEC sys.sp_addextendedproperty @name=N'Description', @value=N'The update level of the build. CU indicates a cumulative update. SP indicates a service pack. RTM indicates Release To Manufacturer. GDR indicates a General Distribution Release. QFE indicates Quick Fix Engineering (aka hotfix).' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'SqlServerVersions', @level2type=N'COLUMN',@level2name=N'Branch' + EXEC sys.sp_addextendedproperty @name=N'Description', @value=N'A link to the KB article for a version.' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'SqlServerVersions', @level2type=N'COLUMN',@level2name=N'Url' + EXEC sys.sp_addextendedproperty @name=N'Description', @value=N'The date the version was publicly released.' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'SqlServerVersions', @level2type=N'COLUMN',@level2name=N'ReleaseDate' + EXEC sys.sp_addextendedproperty @name=N'Description', @value=N'The date main stream Microsoft support ends for the version.' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'SqlServerVersions', @level2type=N'COLUMN',@level2name=N'MainstreamSupportEndDate' + EXEC sys.sp_addextendedproperty @name=N'Description', @value=N'The date extended Microsoft support ends for the version.' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'SqlServerVersions', @level2type=N'COLUMN',@level2name=N'ExtendedSupportEndDate' + EXEC sys.sp_addextendedproperty @name=N'Description', @value=N'The major version name.' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'SqlServerVersions', @level2type=N'COLUMN',@level2name=N'MajorVersionName' + EXEC sys.sp_addextendedproperty @name=N'Description', @value=N'The minor version name.' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'SqlServerVersions', @level2type=N'COLUMN',@level2name=N'MinorVersionName' + EXEC sys.sp_addextendedproperty @name=N'Description', @value=N'A reference for SQL Server major and minor versions.' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'SqlServerVersions' + +END; +GO + +DELETE FROM dbo.SqlServerVersions; + +INSERT INTO dbo.SqlServerVersions + (MajorVersionNumber, MinorVersionNumber, Branch, [Url], ReleaseDate, MainstreamSupportEndDate, ExtendedSupportEndDate, MajorVersionName, MinorVersionName) +VALUES + (15, 4102, 'CU9', 'https://support.microsoft.com/en-us/help/5000642', '2021-02-11', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 9 '), + (15, 4073, 'CU8 GDR', 'https://support.microsoft.com/en-us/help/4583459', '2021-01-12', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 8 GDR '), + (15, 4073, 'CU8', 'https://support.microsoft.com/en-us/help/4577194', '2020-10-01', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 8 '), + (15, 4063, 'CU7', 'https://support.microsoft.com/en-us/help/4570012', '2020-09-02', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 7 '), + (15, 4053, 'CU6', 'https://support.microsoft.com/en-us/help/4563110', '2020-08-04', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 6 '), + (15, 4043, 'CU5', 'https://support.microsoft.com/en-us/help/4548597', '2020-06-22', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 5 '), + (15, 4033, 'CU4', 'https://support.microsoft.com/en-us/help/4548597', '2020-03-31', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 4 '), + (15, 4023, 'CU3', 'https://support.microsoft.com/en-us/help/4538853', '2020-03-12', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 3 '), + (15, 4013, 'CU2', 'https://support.microsoft.com/en-us/help/4536075', '2020-02-13', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 2 '), + (15, 4003, 'CU1', 'https://support.microsoft.com/en-us/help/4527376', '2020-01-07', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 1 '), + (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 '), + (14, 3370, 'RTM CU22 GDR', 'https://support.microsoft.com/en-us/help/4583457', '2021-01-12', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 22 GDR'), + (14, 3356, 'RTM CU22', 'https://support.microsoft.com/en-us/help/4577467', '2020-09-10', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 22'), + (14, 3335, 'RTM CU21', 'https://support.microsoft.com/en-us/help/4557397', '2020-07-01', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 21'), + (14, 3294, 'RTM CU20', 'https://support.microsoft.com/en-us/help/4541283', '2020-04-07', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 20'), + (14, 3257, 'RTM CU19', 'https://support.microsoft.com/en-us/help/4535007', '2020-02-05', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 19'), + (14, 3257, 'RTM CU18', 'https://support.microsoft.com/en-us/help/4527377', '2019-12-09', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 18'), + (14, 3238, 'RTM CU17', 'https://support.microsoft.com/en-us/help/4515579', '2019-10-08', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 17'), + (14, 3223, 'RTM CU16', 'https://support.microsoft.com/en-us/help/4508218', '2019-08-01', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 16'), + (14, 3162, 'RTM CU15', 'https://support.microsoft.com/en-us/help/4498951', '2019-05-24', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 15'), + (14, 3076, 'RTM CU14', 'https://support.microsoft.com/en-us/help/4484710', '2019-03-25', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 14'), + (14, 3048, 'RTM CU13', 'https://support.microsoft.com/en-us/help/4466404', '2018-12-18', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 13'), + (14, 3045, 'RTM CU12', 'https://support.microsoft.com/en-us/help/4464082', '2018-10-24', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 12'), + (14, 3038, 'RTM CU11', 'https://support.microsoft.com/en-us/help/4462262', '2018-09-20', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 11'), + (14, 3037, 'RTM CU10', 'https://support.microsoft.com/en-us/help/4524334', '2018-08-27', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 10'), + (14, 3030, 'RTM CU9', 'https://support.microsoft.com/en-us/help/4515435', '2018-07-18', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 9'), + (14, 3029, 'RTM CU8', 'https://support.microsoft.com/en-us/help/4338363', '2018-06-21', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 8'), + (14, 3026, 'RTM CU7', 'https://support.microsoft.com/en-us/help/4229789', '2018-05-23', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 7'), + (14, 3025, 'RTM CU6', 'https://support.microsoft.com/en-us/help/4101464', '2018-04-17', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 6'), + (14, 3023, 'RTM CU5', 'https://support.microsoft.com/en-us/help/4092643', '2018-03-20', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 5'), + (14, 3022, 'RTM CU4', 'https://support.microsoft.com/en-us/help/4056498', '2018-02-20', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 4'), + (14, 3015, 'RTM CU3', 'https://support.microsoft.com/en-us/help/4052987', '2018-01-04', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 3'), + (14, 3008, 'RTM CU2', 'https://support.microsoft.com/en-us/help/4052574', '2017-11-28', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 2'), + (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 '), + (13, 5882, 'SP2 CU16', 'https://support.microsoft.com/en-us/help/5000645', '2021-02-11', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 16'), + (13, 5865, 'SP2 CU15 GDR', 'https://support.microsoft.com/en-us/help/4583461', '2021-01-12', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 15 GDR'), + (13, 5850, 'SP2 CU15', 'https://support.microsoft.com/en-us/help/4577775', '2020-09-28', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 15'), + (13, 5830, 'SP2 CU14', 'https://support.microsoft.com/en-us/help/4564903', '2020-08-06', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 14'), + (13, 5820, 'SP2 CU13', 'https://support.microsoft.com/en-us/help/4549825', '2020-05-28', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 13'), + (13, 5698, 'SP2 CU12', 'https://support.microsoft.com/en-us/help/4536648', '2020-02-25', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 12'), + (13, 5598, 'SP2 CU11', 'https://support.microsoft.com/en-us/help/4527378', '2019-12-09', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 11'), + (13, 5492, 'SP2 CU10', 'https://support.microsoft.com/en-us/help/4505830', '2019-10-08', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 10'), + (13, 5479, 'SP2 CU9', 'https://support.microsoft.com/en-us/help/4505830', '2019-09-30', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 9'), + (13, 5426, 'SP2 CU8', 'https://support.microsoft.com/en-us/help/4505830', '2019-07-31', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 8'), + (13, 5337, 'SP2 CU7', 'https://support.microsoft.com/en-us/help/4495256', '2019-05-23', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 7'), + (13, 5292, 'SP2 CU6', 'https://support.microsoft.com/en-us/help/4488536', '2019-03-19', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 6'), + (13, 5264, 'SP2 CU5', 'https://support.microsoft.com/en-us/help/4475776', '2019-01-23', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 5'), + (13, 5233, 'SP2 CU4', 'https://support.microsoft.com/en-us/help/4464106', '2018-11-13', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 4'), + (13, 5216, 'SP2 CU3', 'https://support.microsoft.com/en-us/help/4458871', '2018-09-20', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 3'), + (13, 5201, 'SP2 CU2 + Security Update', 'https://support.microsoft.com/en-us/help/4458621', '2018-08-21', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 2 + Security Update'), + (13, 5153, 'SP2 CU2', 'https://support.microsoft.com/en-us/help/4340355', '2018-07-16', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 2'), + (13, 5149, 'SP2 CU1', 'https://support.microsoft.com/en-us/help/4135048', '2018-05-30', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 1'), + (13, 5103, 'SP2 GDR', 'https://support.microsoft.com/en-us/help/4583460', '2021-01-12', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 GDR'), + (13, 5026, 'SP2 ', 'https://support.microsoft.com/en-us/help/4052908', '2018-04-24', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 '), + (13, 4574, 'SP1 CU15', 'https://support.microsoft.com/en-us/help/4495257', '2019-05-16', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 15'), + (13, 4560, 'SP1 CU14', 'https://support.microsoft.com/en-us/help/4488535', '2019-03-19', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 14'), + (13, 4550, 'SP1 CU13', 'https://support.microsoft.com/en-us/help/4475775', '2019-01-23', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 13'), + (13, 4541, 'SP1 CU12', 'https://support.microsoft.com/en-us/help/4464343', '2018-11-13', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 12'), + (13, 4528, 'SP1 CU11', 'https://support.microsoft.com/en-us/help/4459676', '2018-09-17', '2019-07-09', '2019-07-09', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 11'), + (13, 4514, 'SP1 CU10', 'https://support.microsoft.com/en-us/help/4341569', '2018-07-16', '2019-07-09', '2019-07-09', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 10'), + (13, 4502, 'SP1 CU9', 'https://support.microsoft.com/en-us/help/4100997', '2018-05-30', '2019-07-09', '2019-07-09', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 9'), + (13, 4474, 'SP1 CU8', 'https://support.microsoft.com/en-us/help/4077064', '2018-03-19', '2019-07-09', '2019-07-09', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 8'), + (13, 4466, 'SP1 CU7', 'https://support.microsoft.com/en-us/help/4057119', '2018-01-04', '2019-07-09', '2019-07-09', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 7'), + (13, 4457, 'SP1 CU6', 'https://support.microsoft.com/en-us/help/4037354', '2017-11-20', '2019-07-09', '2019-07-09', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 6'), + (13, 4451, 'SP1 CU5', 'https://support.microsoft.com/en-us/help/4024305', '2017-09-18', '2019-07-09', '2019-07-09', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 5'), + (13, 4446, 'SP1 CU4', 'https://support.microsoft.com/en-us/help/4024305', '2017-08-08', '2019-07-09', '2019-07-09', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 4'), + (13, 4435, 'SP1 CU3', 'https://support.microsoft.com/en-us/help/4019916', '2017-05-15', '2019-07-09', '2019-07-09', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 3'), + (13, 4422, 'SP1 CU2', 'https://support.microsoft.com/en-us/help/4013106', '2017-03-20', '2019-07-09', '2019-07-09', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 2'), + (13, 4411, 'SP1 CU1', 'https://support.microsoft.com/en-us/help/3208177', '2017-01-17', '2019-07-09', '2019-07-09', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 1'), + (13, 4224, 'SP1 CU10 + Security Update', 'https://support.microsoft.com/en-us/help/4458842', '2018-08-22', '2019-07-09', '2019-07-09', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 10 + Security Update'), + (13, 4001, 'SP1 ', 'https://support.microsoft.com/en-us/help/3182545 ', '2016-11-16', '2019-07-09', '2019-07-09', 'SQL Server 2016', 'Service Pack 1 '), + (13, 2216, 'RTM CU9', 'https://support.microsoft.com/en-us/help/4037357', '2017-11-20', '2018-01-09', '2018-01-09', 'SQL Server 2016', 'RTM Cumulative Update 9'), + (13, 2213, 'RTM CU8', 'https://support.microsoft.com/en-us/help/4024304', '2017-09-18', '2018-01-09', '2018-01-09', 'SQL Server 2016', 'RTM Cumulative Update 8'), + (13, 2210, 'RTM CU7', 'https://support.microsoft.com/en-us/help/4024304', '2017-08-08', '2018-01-09', '2018-01-09', 'SQL Server 2016', 'RTM Cumulative Update 7'), + (13, 2204, 'RTM CU6', 'https://support.microsoft.com/en-us/help/4019914', '2017-05-15', '2018-01-09', '2018-01-09', 'SQL Server 2016', 'RTM Cumulative Update 6'), + (13, 2197, 'RTM CU5', 'https://support.microsoft.com/en-us/help/4013105', '2017-03-20', '2018-01-09', '2018-01-09', 'SQL Server 2016', 'RTM Cumulative Update 5'), + (13, 2193, 'RTM CU4', 'https://support.microsoft.com/en-us/help/3205052 ', '2017-01-17', '2018-01-09', '2018-01-09', 'SQL Server 2016', 'RTM Cumulative Update 4'), + (13, 2186, 'RTM CU3', 'https://support.microsoft.com/en-us/help/3205413 ', '2016-11-16', '2018-01-09', '2018-01-09', 'SQL Server 2016', 'RTM Cumulative Update 3'), + (13, 2164, 'RTM CU2', 'https://support.microsoft.com/en-us/help/3182270 ', '2016-09-22', '2018-01-09', '2018-01-09', 'SQL Server 2016', 'RTM Cumulative Update 2'), + (13, 2149, 'RTM CU1', 'https://support.microsoft.com/en-us/help/3164674 ', '2016-07-25', '2018-01-09', '2018-01-09', 'SQL Server 2016', 'RTM Cumulative Update 1'), + (13, 1601, 'RTM ', '', '2016-06-01', '2019-01-09', '2019-01-09', 'SQL Server 2016', 'RTM '), + (12, 6433, 'SP3 CU4 GDR', 'https://support.microsoft.com/en-us/help/4583462', '2021-01-12', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 4 GDR'), + (12, 6372, 'SP3 CU4 GDR', 'https://support.microsoft.com/en-us/help/4535288', '2020-02-11', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 4 GDR'), + (12, 6329, 'SP3 CU4', 'https://support.microsoft.com/en-us/help/4500181', '2019-07-29', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 4'), + (12, 6259, 'SP3 CU3', 'https://support.microsoft.com/en-us/help/4491539', '2019-04-16', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 3'), + (12, 6214, 'SP3 CU2', 'https://support.microsoft.com/en-us/help/4482960', '2019-02-19', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 2'), + (12, 6205, 'SP3 CU1', 'https://support.microsoft.com/en-us/help/4470220', '2018-12-12', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 1'), + (12, 6164, 'SP3 GDR', 'https://support.microsoft.com/en-us/help/4583463', '2021-01-12', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 GDR'), + (12, 6024, 'SP3 ', 'https://support.microsoft.com/en-us/help/4022619', '2018-10-30', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 '), + (12, 5687, 'SP2 CU18', 'https://support.microsoft.com/en-us/help/4500180', '2019-07-29', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 18'), + (12, 5632, 'SP2 CU17', 'https://support.microsoft.com/en-us/help/4491540', '2019-04-16', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 17'), + (12, 5626, 'SP2 CU16', 'https://support.microsoft.com/en-us/help/4482967', '2019-02-19', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 16'), + (12, 5605, 'SP2 CU15', 'https://support.microsoft.com/en-us/help/4469137', '2018-12-12', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 15'), + (12, 5600, 'SP2 CU14', 'https://support.microsoft.com/en-us/help/4459860', '2018-10-15', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 14'), + (12, 5590, 'SP2 CU13', 'https://support.microsoft.com/en-us/help/4456287', '2018-08-27', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 13'), + (12, 5589, 'SP2 CU12', 'https://support.microsoft.com/en-us/help/4130489', '2018-06-18', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 12'), + (12, 5579, 'SP2 CU11', 'https://support.microsoft.com/en-us/help/4077063', '2018-03-19', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 11'), + (12, 5571, 'SP2 CU10', 'https://support.microsoft.com/en-us/help/4052725', '2018-01-16', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 10'), + (12, 5563, 'SP2 CU9', 'https://support.microsoft.com/en-us/help/4055557', '2017-12-18', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 9'), + (12, 5557, 'SP2 CU8', 'https://support.microsoft.com/en-us/help/4037356', '2017-10-16', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 8'), + (12, 5556, 'SP2 CU7', 'https://support.microsoft.com/en-us/help/4032541', '2017-08-28', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 7'), + (12, 5553, 'SP2 CU6', 'https://support.microsoft.com/en-us/help/4019094', '2017-08-08', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 6'), + (12, 5546, 'SP2 CU5', 'https://support.microsoft.com/en-us/help/4013098', '2017-04-17', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 5'), + (12, 5540, 'SP2 CU4', 'https://support.microsoft.com/en-us/help/4010394', '2017-02-21', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 4'), + (12, 5538, 'SP2 CU3', 'https://support.microsoft.com/en-us/help/3204388 ', '2016-12-19', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 3'), + (12, 5522, 'SP2 CU2', 'https://support.microsoft.com/en-us/help/3188778 ', '2016-10-17', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 2'), + (12, 5511, 'SP2 CU1', 'https://support.microsoft.com/en-us/help/3178925 ', '2016-08-25', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 1'), + (12, 5000, 'SP2 ', 'https://support.microsoft.com/en-us/help/3171021 ', '2016-07-11', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 '), + (12, 4522, 'SP1 CU13', 'https://support.microsoft.com/en-us/help/4019099', '2017-08-08', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 Cumulative Update 13'), + (12, 4511, 'SP1 CU12', 'https://support.microsoft.com/en-us/help/4017793', '2017-04-17', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 Cumulative Update 12'), + (12, 4502, 'SP1 CU11', 'https://support.microsoft.com/en-us/help/4010392', '2017-02-21', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 Cumulative Update 11'), + (12, 4491, 'SP1 CU10', 'https://support.microsoft.com/en-us/help/3204399 ', '2016-12-19', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 Cumulative Update 10'), + (12, 4474, 'SP1 CU9', 'https://support.microsoft.com/en-us/help/3186964 ', '2016-10-17', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 Cumulative Update 9'), + (12, 4468, 'SP1 CU8', 'https://support.microsoft.com/en-us/help/3174038 ', '2016-08-15', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 Cumulative Update 8'), + (12, 4459, 'SP1 CU7', 'https://support.microsoft.com/en-us/help/3162659 ', '2016-06-20', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 Cumulative Update 7'), + (12, 4457, 'SP1 CU6', 'https://support.microsoft.com/en-us/help/3167392 ', '2016-05-30', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 Cumulative Update 6'), + (12, 4449, 'SP1 CU6', 'https://support.microsoft.com/en-us/help/3144524', '2016-04-18', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 Cumulative Update 6'), + (12, 4438, 'SP1 CU5', 'https://support.microsoft.com/en-us/help/3130926', '2016-02-22', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 Cumulative Update 5'), + (12, 4436, 'SP1 CU4', 'https://support.microsoft.com/en-us/help/3106660', '2015-12-21', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 Cumulative Update 4'), + (12, 4427, 'SP1 CU3', 'https://support.microsoft.com/en-us/help/3094221', '2015-10-19', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 Cumulative Update 3'), + (12, 4422, 'SP1 CU2', 'https://support.microsoft.com/en-us/help/3075950', '2015-08-17', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 Cumulative Update 2'), + (12, 4416, 'SP1 CU1', 'https://support.microsoft.com/en-us/help/3067839', '2015-06-19', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 Cumulative Update 1'), + (12, 4213, 'SP1 MS15-058: GDR Security Update', 'https://support.microsoft.com/en-us/help/3070446', '2015-07-14', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 MS15-058: GDR Security Update'), + (12, 4100, 'SP1 ', 'https://support.microsoft.com/en-us/help/3058865', '2015-05-04', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 '), + (12, 2569, 'RTM CU14', 'https://support.microsoft.com/en-us/help/3158271 ', '2016-06-20', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM Cumulative Update 14'), + (12, 2568, 'RTM CU13', 'https://support.microsoft.com/en-us/help/3144517', '2016-04-18', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM Cumulative Update 13'), + (12, 2564, 'RTM CU12', 'https://support.microsoft.com/en-us/help/3130923', '2016-02-22', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM Cumulative Update 12'), + (12, 2560, 'RTM CU11', 'https://support.microsoft.com/en-us/help/3106659', '2015-12-21', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM Cumulative Update 11'), + (12, 2556, 'RTM CU10', 'https://support.microsoft.com/en-us/help/3094220', '2015-10-19', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM Cumulative Update 10'), + (12, 2553, 'RTM CU9', 'https://support.microsoft.com/en-us/help/3075949', '2015-08-17', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM Cumulative Update 9'), + (12, 2548, 'RTM MS15-058: QFE Security Update', 'https://support.microsoft.com/en-us/help/3045323', '2015-07-14', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM MS15-058: QFE Security Update'), + (12, 2546, 'RTM CU8', 'https://support.microsoft.com/en-us/help/3067836', '2015-06-19', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM Cumulative Update 8'), + (12, 2495, 'RTM CU7', 'https://support.microsoft.com/en-us/help/3046038', '2015-04-20', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM Cumulative Update 7'), + (12, 2480, 'RTM CU6', 'https://support.microsoft.com/en-us/help/3031047', '2015-02-16', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM Cumulative Update 6'), + (12, 2456, 'RTM CU5', 'https://support.microsoft.com/en-us/help/3011055', '2014-12-17', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM Cumulative Update 5'), + (12, 2430, 'RTM CU4', 'https://support.microsoft.com/en-us/help/2999197', '2014-10-21', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM Cumulative Update 4'), + (12, 2402, 'RTM CU3', 'https://support.microsoft.com/en-us/help/2984923', '2014-08-18', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM Cumulative Update 3'), + (12, 2381, 'RTM MS14-044: QFE Security Update', 'https://support.microsoft.com/en-us/help/2977316', '2014-08-12', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM MS14-044: QFE Security Update'), + (12, 2370, 'RTM CU2', 'https://support.microsoft.com/en-us/help/2967546', '2014-06-27', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM Cumulative Update 2'), + (12, 2342, 'RTM CU1', 'https://support.microsoft.com/en-us/help/2931693', '2014-04-21', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM Cumulative Update 1'), + (12, 2269, 'RTM MS15-058: GDR Security Update ', 'https://support.microsoft.com/en-us/help/3045324', '2015-07-14', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM MS15-058: GDR Security Update '), + (12, 2254, 'RTM MS14-044: GDR Security Update', 'https://support.microsoft.com/en-us/help/2977315', '2014-08-12', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM MS14-044: GDR Security Update'), + (12, 2000, 'RTM ', '', '2014-04-01', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM '), + (11, 7507, 'SP4 GDR Security Update', 'https://support.microsoft.com/en-us/help/4583465', '2021-01-12', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'Service Pack 4 GDR Security Update for CVE-2021-1636'), + (11, 7493, 'SP4 GDR Security Update', 'https://support.microsoft.com/en-us/help/4532098', '2020-02-11', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'Service Pack 4 GDR Security Update for CVE-2020-0618'), + (11, 7469, 'SP4 On-Demand Hotfix Update', 'https://support.microsoft.com/en-us/help/4091266', '2018-03-28', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'Service Pack 4 SP4 On-Demand Hotfix Update'), + (11, 7462, 'SP4 ADV180002: GDR Security Update', 'https://support.microsoft.com/en-us/help/4057116', '2018-01-12', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'Service Pack 4 ADV180002: GDR Security Update'), + (11, 7001, 'SP4 ', 'https://support.microsoft.com/en-us/help/4018073', '2017-10-02', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'Service Pack 4 '), + (11, 6607, 'SP3 CU10', 'https://support.microsoft.com/en-us/help/4025925', '2017-08-08', '2018-10-09', '2018-10-09', 'SQL Server 2012', 'Service Pack 3 Cumulative Update 10'), + (11, 6598, 'SP3 CU9', 'https://support.microsoft.com/en-us/help/4016762', '2017-05-15', '2018-10-09', '2018-10-09', 'SQL Server 2012', 'Service Pack 3 Cumulative Update 9'), + (11, 6594, 'SP3 CU8', 'https://support.microsoft.com/en-us/help/3205051 ', '2017-03-20', '2018-10-09', '2018-10-09', 'SQL Server 2012', 'Service Pack 3 Cumulative Update 8'), + (11, 6579, 'SP3 CU7', 'https://support.microsoft.com/en-us/help/3205051 ', '2017-01-17', '2018-10-09', '2018-10-09', 'SQL Server 2012', 'Service Pack 3 Cumulative Update 7'), + (11, 6567, 'SP3 CU6', 'https://support.microsoft.com/en-us/help/3194992 ', '2016-11-17', '2018-10-09', '2018-10-09', 'SQL Server 2012', 'Service Pack 3 Cumulative Update 6'), + (11, 6544, 'SP3 CU5', 'https://support.microsoft.com/en-us/help/3180915 ', '2016-09-19', '2018-10-09', '2018-10-09', 'SQL Server 2012', 'Service Pack 3 Cumulative Update 5'), + (11, 6540, 'SP3 CU4', 'https://support.microsoft.com/en-us/help/3165264 ', '2016-07-18', '2018-10-09', '2018-10-09', 'SQL Server 2012', 'Service Pack 3 Cumulative Update 4'), + (11, 6537, 'SP3 CU3', 'https://support.microsoft.com/en-us/help/3152635 ', '2016-05-16', '2018-10-09', '2018-10-09', 'SQL Server 2012', 'Service Pack 3 Cumulative Update 3'), + (11, 6523, 'SP3 CU2', 'https://support.microsoft.com/en-us/help/3137746', '2016-03-21', '2018-10-09', '2018-10-09', 'SQL Server 2012', 'Service Pack 3 Cumulative Update 2'), + (11, 6518, 'SP3 CU1', 'https://support.microsoft.com/en-us/help/3123299', '2016-01-19', '2018-10-09', '2018-10-09', 'SQL Server 2012', 'Service Pack 3 Cumulative Update 1'), + (11, 6020, 'SP3 ', 'https://support.microsoft.com/en-us/help/3072779', '2015-11-20', '2018-10-09', '2018-10-09', 'SQL Server 2012', 'Service Pack 3 '), + (11, 5678, 'SP2 CU16', 'https://support.microsoft.com/en-us/help/3205416 ', '2016-11-17', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 16'), + (11, 5676, 'SP2 CU15', 'https://support.microsoft.com/en-us/help/3205416 ', '2016-11-17', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 15'), + (11, 5657, 'SP2 CU14', 'https://support.microsoft.com/en-us/help/3180914 ', '2016-09-19', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 14'), + (11, 5655, 'SP2 CU13', 'https://support.microsoft.com/en-us/help/3165266 ', '2016-07-18', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 13'), + (11, 5649, 'SP2 CU12', 'https://support.microsoft.com/en-us/help/3152637 ', '2016-05-16', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 12'), + (11, 5646, 'SP2 CU11', 'https://support.microsoft.com/en-us/help/3137745', '2016-03-21', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 11'), + (11, 5644, 'SP2 CU10', 'https://support.microsoft.com/en-us/help/3120313', '2016-01-19', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 10'), + (11, 5641, 'SP2 CU9', 'https://support.microsoft.com/en-us/help/3098512', '2015-11-16', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 9'), + (11, 5634, 'SP2 CU8', 'https://support.microsoft.com/en-us/help/3082561', '2015-09-21', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 8'), + (11, 5623, 'SP2 CU7', 'https://support.microsoft.com/en-us/help/3072100', '2015-07-20', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 7'), + (11, 5613, 'SP2 MS15-058: QFE Security Update', 'https://support.microsoft.com/en-us/help/3045319', '2015-07-14', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 MS15-058: QFE Security Update'), + (11, 5592, 'SP2 CU6', 'https://support.microsoft.com/en-us/help/3052468', '2015-05-18', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 6'), + (11, 5582, 'SP2 CU5', 'https://support.microsoft.com/en-us/help/3037255', '2015-03-16', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 5'), + (11, 5569, 'SP2 CU4', 'https://support.microsoft.com/en-us/help/3007556', '2015-01-19', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 4'), + (11, 5556, 'SP2 CU3', 'https://support.microsoft.com/en-us/help/3002049', '2014-11-17', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 3'), + (11, 5548, 'SP2 CU2', 'https://support.microsoft.com/en-us/help/2983175', '2014-09-15', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 2'), + (11, 5532, 'SP2 CU1', 'https://support.microsoft.com/en-us/help/2976982', '2014-07-23', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 1'), + (11, 5343, 'SP2 MS15-058: GDR Security Update', 'https://support.microsoft.com/en-us/help/3045321', '2015-07-14', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 MS15-058: GDR Security Update'), + (11, 5058, 'SP2 ', 'https://support.microsoft.com/en-us/help/2958429', '2014-06-10', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 '), + (11, 3513, 'SP1 MS15-058: QFE Security Update', 'https://support.microsoft.com/en-us/help/3045317', '2015-07-14', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 MS15-058: QFE Security Update'), + (11, 3482, 'SP1 CU13', 'https://support.microsoft.com/en-us/help/3002044', '2014-11-17', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 Cumulative Update 13'), + (11, 3470, 'SP1 CU12', 'https://support.microsoft.com/en-us/help/2991533', '2014-09-15', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 Cumulative Update 12'), + (11, 3460, 'SP1 MS14-044: QFE Security Update ', 'https://support.microsoft.com/en-us/help/2977325', '2014-08-12', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 MS14-044: QFE Security Update '), + (11, 3449, 'SP1 CU11', 'https://support.microsoft.com/en-us/help/2975396', '2014-07-21', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 Cumulative Update 11'), + (11, 3431, 'SP1 CU10', 'https://support.microsoft.com/en-us/help/2954099', '2014-05-19', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 Cumulative Update 10'), + (11, 3412, 'SP1 CU9', 'https://support.microsoft.com/en-us/help/2931078', '2014-03-17', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 Cumulative Update 9'), + (11, 3401, 'SP1 CU8', 'https://support.microsoft.com/en-us/help/2917531', '2014-01-20', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 Cumulative Update 8'), + (11, 3393, 'SP1 CU7', 'https://support.microsoft.com/en-us/help/2894115', '2013-11-18', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 Cumulative Update 7'), + (11, 3381, 'SP1 CU6', 'https://support.microsoft.com/en-us/help/2874879', '2013-09-16', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 Cumulative Update 6'), + (11, 3373, 'SP1 CU5', 'https://support.microsoft.com/en-us/help/2861107', '2013-07-15', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 Cumulative Update 5'), + (11, 3368, 'SP1 CU4', 'https://support.microsoft.com/en-us/help/2833645', '2013-05-30', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 Cumulative Update 4'), + (11, 3349, 'SP1 CU3', 'https://support.microsoft.com/en-us/help/2812412', '2013-03-18', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 Cumulative Update 3'), + (11, 3339, 'SP1 CU2', 'https://support.microsoft.com/en-us/help/2790947', '2013-01-21', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 Cumulative Update 2'), + (11, 3321, 'SP1 CU1', 'https://support.microsoft.com/en-us/help/2765331', '2012-11-20', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 Cumulative Update 1'), + (11, 3156, 'SP1 MS15-058: GDR Security Update', 'https://support.microsoft.com/en-us/help/3045318', '2015-07-14', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 MS15-058: GDR Security Update'), + (11, 3153, 'SP1 MS14-044: GDR Security Update', 'https://support.microsoft.com/en-us/help/2977326', '2014-08-12', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 MS14-044: GDR Security Update'), + (11, 3000, 'SP1 ', 'https://support.microsoft.com/en-us/help/2674319', '2012-11-07', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 '), + (11, 2424, 'RTM CU11', 'https://support.microsoft.com/en-us/help/2908007', '2013-12-16', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'RTM Cumulative Update 11'), + (11, 2420, 'RTM CU10', 'https://support.microsoft.com/en-us/help/2891666', '2013-10-21', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'RTM Cumulative Update 10'), + (11, 2419, 'RTM CU9', 'https://support.microsoft.com/en-us/help/2867319', '2013-08-20', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'RTM Cumulative Update 9'), + (11, 2410, 'RTM CU8', 'https://support.microsoft.com/en-us/help/2844205', '2013-06-17', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'RTM Cumulative Update 8'), + (11, 2405, 'RTM CU7', 'https://support.microsoft.com/en-us/help/2823247', '2013-04-15', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'RTM Cumulative Update 7'), + (11, 2401, 'RTM CU6', 'https://support.microsoft.com/en-us/help/2728897', '2013-02-18', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'RTM Cumulative Update 6'), + (11, 2395, 'RTM CU5', 'https://support.microsoft.com/en-us/help/2777772', '2012-12-17', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'RTM Cumulative Update 5'), + (11, 2383, 'RTM CU4', 'https://support.microsoft.com/en-us/help/2758687', '2012-10-15', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'RTM Cumulative Update 4'), + (11, 2376, 'RTM MS12-070: QFE Security Update', 'https://support.microsoft.com/en-us/help/2716441', '2012-10-09', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'RTM MS12-070: QFE Security Update'), + (11, 2332, 'RTM CU3', 'https://support.microsoft.com/en-us/help/2723749', '2012-08-31', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'RTM Cumulative Update 3'), + (11, 2325, 'RTM CU2', 'https://support.microsoft.com/en-us/help/2703275', '2012-06-18', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'RTM Cumulative Update 2'), + (11, 2316, 'RTM CU1', 'https://support.microsoft.com/en-us/help/2679368', '2012-04-12', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'RTM Cumulative Update 1'), + (11, 2218, 'RTM MS12-070: GDR Security Update', 'https://support.microsoft.com/en-us/help/2716442', '2012-10-09', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'RTM MS12-070: GDR Security Update'), + (11, 2100, 'RTM ', '', '2012-03-06', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'RTM '), + (10, 6529, 'SP3 MS15-058: QFE Security Update', 'https://support.microsoft.com/en-us/help/3045314', '2015-07-14', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'Service Pack 3 MS15-058: QFE Security Update'), + (10, 6220, 'SP3 MS15-058: QFE Security Update', 'https://support.microsoft.com/en-us/help/3045316', '2015-07-14', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'Service Pack 3 MS15-058: QFE Security Update'), + (10, 6000, 'SP3 ', 'https://support.microsoft.com/en-us/help/2979597', '2014-09-26', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'Service Pack 3 '), + (10, 4339, 'SP2 MS15-058: QFE Security Update', 'https://support.microsoft.com/en-us/help/3045312', '2015-07-14', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 MS15-058: QFE Security Update'), + (10, 4321, 'SP2 MS14-044: QFE Security Update', 'https://support.microsoft.com/en-us/help/2977319', '2014-08-14', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 MS14-044: QFE Security Update'), + (10, 4319, 'SP2 CU13', 'https://support.microsoft.com/en-us/help/2967540', '2014-06-30', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 Cumulative Update 13'), + (10, 4305, 'SP2 CU12', 'https://support.microsoft.com/en-us/help/2938478', '2014-04-21', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 Cumulative Update 12'), + (10, 4302, 'SP2 CU11', 'https://support.microsoft.com/en-us/help/2926028', '2014-02-18', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 Cumulative Update 11'), + (10, 4297, 'SP2 CU10', 'https://support.microsoft.com/en-us/help/2908087', '2013-12-17', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 Cumulative Update 10'), + (10, 4295, 'SP2 CU9', 'https://support.microsoft.com/en-us/help/2887606', '2013-10-28', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 Cumulative Update 9'), + (10, 4290, 'SP2 CU8', 'https://support.microsoft.com/en-us/help/2871401', '2013-08-22', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 Cumulative Update 8'), + (10, 4285, 'SP2 CU7', 'https://support.microsoft.com/en-us/help/2844090', '2013-06-17', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 Cumulative Update 7'), + (10, 4279, 'SP2 CU6', 'https://support.microsoft.com/en-us/help/2830140', '2013-04-15', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 Cumulative Update 6'), + (10, 4276, 'SP2 CU5', 'https://support.microsoft.com/en-us/help/2797460', '2013-02-18', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 Cumulative Update 5'), + (10, 4270, 'SP2 CU4', 'https://support.microsoft.com/en-us/help/2777358', '2012-12-17', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 Cumulative Update 4'), + (10, 4266, 'SP2 CU3', 'https://support.microsoft.com/en-us/help/2754552', '2012-10-15', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 Cumulative Update 3'), + (10, 4263, 'SP2 CU2', 'https://support.microsoft.com/en-us/help/2740411', '2012-08-31', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 Cumulative Update 2'), + (10, 4260, 'SP2 CU1', 'https://support.microsoft.com/en-us/help/2720425', '2012-07-24', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 Cumulative Update 1'), + (10, 4042, 'SP2 MS15-058: GDR Security Update', 'https://support.microsoft.com/en-us/help/3045313', '2015-07-14', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 MS15-058: GDR Security Update'), + (10, 4033, 'SP2 MS14-044: GDR Security Update', 'https://support.microsoft.com/en-us/help/2977320', '2014-08-12', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 MS14-044: GDR Security Update'), + (10, 4000, 'SP2 ', 'https://support.microsoft.com/en-us/help/2630458', '2012-07-26', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 '), + (10, 2881, 'SP1 CU14', 'https://support.microsoft.com/en-us/help/2868244', '2013-08-08', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 Cumulative Update 14'), + (10, 2876, 'SP1 CU13', 'https://support.microsoft.com/en-us/help/2855792', '2013-06-17', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 Cumulative Update 13'), + (10, 2874, 'SP1 CU12', 'https://support.microsoft.com/en-us/help/2828727', '2013-04-15', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 Cumulative Update 12'), + (10, 2869, 'SP1 CU11', 'https://support.microsoft.com/en-us/help/2812683', '2013-02-18', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 Cumulative Update 11'), + (10, 2868, 'SP1 CU10', 'https://support.microsoft.com/en-us/help/2783135', '2012-12-17', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 Cumulative Update 10'), + (10, 2866, 'SP1 CU9', 'https://support.microsoft.com/en-us/help/2756574', '2012-10-15', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 Cumulative Update 9'), + (10, 2861, 'SP1 MS12-070: QFE Security Update', 'https://support.microsoft.com/en-us/help/2716439', '2012-10-09', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 MS12-070: QFE Security Update'), + (10, 2822, 'SP1 CU8', 'https://support.microsoft.com/en-us/help/2723743', '2012-08-31', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 Cumulative Update 8'), + (10, 2817, 'SP1 CU7', 'https://support.microsoft.com/en-us/help/2703282', '2012-06-18', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 Cumulative Update 7'), + (10, 2811, 'SP1 CU6', 'https://support.microsoft.com/en-us/help/2679367', '2012-04-16', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 Cumulative Update 6'), + (10, 2806, 'SP1 CU5', 'https://support.microsoft.com/en-us/help/2659694', '2012-02-22', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 Cumulative Update 5'), + (10, 2796, 'SP1 CU4', 'https://support.microsoft.com/en-us/help/2633146', '2011-12-19', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 Cumulative Update 4'), + (10, 2789, 'SP1 CU3', 'https://support.microsoft.com/en-us/help/2591748', '2011-10-17', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 Cumulative Update 3'), + (10, 2772, 'SP1 CU2', 'https://support.microsoft.com/en-us/help/2567714', '2011-08-15', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 Cumulative Update 2'), + (10, 2769, 'SP1 CU1', 'https://support.microsoft.com/en-us/help/2544793', '2011-07-18', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 Cumulative Update 1'), + (10, 2550, 'SP1 MS12-070: GDR Security Update', 'https://support.microsoft.com/en-us/help/2754849', '2012-10-09', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 MS12-070: GDR Security Update'), + (10, 2500, 'SP1 ', 'https://support.microsoft.com/en-us/help/2528583', '2011-07-12', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 '), + (10, 1815, 'RTM CU13', 'https://support.microsoft.com/en-us/help/2679366', '2012-04-16', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM Cumulative Update 13'), + (10, 1810, 'RTM CU12', 'https://support.microsoft.com/en-us/help/2659692', '2012-02-21', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM Cumulative Update 12'), + (10, 1809, 'RTM CU11', 'https://support.microsoft.com/en-us/help/2633145', '2011-12-19', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM Cumulative Update 11'), + (10, 1807, 'RTM CU10', 'https://support.microsoft.com/en-us/help/2591746', '2011-10-17', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM Cumulative Update 10'), + (10, 1804, 'RTM CU9', 'https://support.microsoft.com/en-us/help/2567713', '2011-08-15', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM Cumulative Update 9'), + (10, 1797, 'RTM CU8', 'https://support.microsoft.com/en-us/help/2534352', '2011-06-20', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM Cumulative Update 8'), + (10, 1790, 'RTM MS11-049: QFE Security Update', 'https://support.microsoft.com/en-us/help/2494086', '2011-06-14', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM MS11-049: QFE Security Update'), + (10, 1777, 'RTM CU7', 'https://support.microsoft.com/en-us/help/2507770', '2011-04-18', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM Cumulative Update 7'), + (10, 1765, 'RTM CU6', 'https://support.microsoft.com/en-us/help/2489376', '2011-02-21', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM Cumulative Update 6'), + (10, 1753, 'RTM CU5', 'https://support.microsoft.com/en-us/help/2438347', '2010-12-20', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM Cumulative Update 5'), + (10, 1746, 'RTM CU4', 'https://support.microsoft.com/en-us/help/2345451', '2010-10-18', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM Cumulative Update 4'), + (10, 1734, 'RTM CU3', 'https://support.microsoft.com/en-us/help/2261464', '2010-08-16', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM Cumulative Update 3'), + (10, 1720, 'RTM CU2', 'https://support.microsoft.com/en-us/help/2072493', '2010-06-21', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM Cumulative Update 2'), + (10, 1702, 'RTM CU1', 'https://support.microsoft.com/en-us/help/981355', '2010-05-18', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM Cumulative Update 1'), + (10, 1617, 'RTM MS11-049: GDR Security Update', 'https://support.microsoft.com/en-us/help/2494088', '2011-06-14', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM MS11-049: GDR Security Update'), + (10, 1600, 'RTM ', '', '2010-05-10', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM '), + (10, 6535, 'SP3 MS15-058: QFE Security Update', 'https://support.microsoft.com/en-us/help/3045308', '2015-07-14', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 MS15-058: QFE Security Update'), + (10, 6241, 'SP3 MS15-058: GDR Security Update', 'https://support.microsoft.com/en-us/help/3045311', '2015-07-14', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 MS15-058: GDR Security Update'), + (10, 5890, 'SP3 MS15-058: QFE Security Update', 'https://support.microsoft.com/en-us/help/3045303', '2015-07-14', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 MS15-058: QFE Security Update'), + (10, 5869, 'SP3 MS14-044: QFE Security Update', 'https://support.microsoft.com/en-us/help/2984340, https://support.microsoft.com/en-us/help/2977322', '2014-08-12', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 MS14-044: QFE Security Update'), + (10, 5861, 'SP3 CU17', 'https://support.microsoft.com/en-us/help/2958696', '2014-05-19', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 17'), + (10, 5852, 'SP3 CU16', 'https://support.microsoft.com/en-us/help/2936421', '2014-03-17', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 16'), + (10, 5850, 'SP3 CU15', 'https://support.microsoft.com/en-us/help/2923520', '2014-01-20', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 15'), + (10, 5848, 'SP3 CU14', 'https://support.microsoft.com/en-us/help/2893410', '2013-11-18', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 14'), + (10, 5846, 'SP3 CU13', 'https://support.microsoft.com/en-us/help/2880350', '2013-09-16', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 13'), + (10, 5844, 'SP3 CU12', 'https://support.microsoft.com/en-us/help/2863205', '2013-07-15', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 12'), + (10, 5840, 'SP3 CU11', 'https://support.microsoft.com/en-us/help/2834048', '2013-05-20', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 11'), + (10, 5835, 'SP3 CU10', 'https://support.microsoft.com/en-us/help/2814783', '2013-03-18', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 10'), + (10, 5829, 'SP3 CU9', 'https://support.microsoft.com/en-us/help/2799883', '2013-01-21', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 9'), + (10, 5828, 'SP3 CU8', 'https://support.microsoft.com/en-us/help/2771833', '2012-11-19', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 8'), + (10, 5826, 'SP3 MS12-070: QFE Security Update', 'https://support.microsoft.com/en-us/help/2716435', '2012-10-09', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 MS12-070: QFE Security Update'), + (10, 5794, 'SP3 CU7', 'https://support.microsoft.com/en-us/help/2738350', '2012-09-17', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 7'), + (10, 5788, 'SP3 CU6', 'https://support.microsoft.com/en-us/help/2715953', '2012-07-16', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 6'), + (10, 5785, 'SP3 CU5', 'https://support.microsoft.com/en-us/help/2696626', '2012-05-21', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 5'), + (10, 5775, 'SP3 CU4', 'https://support.microsoft.com/en-us/help/2673383', '2012-03-19', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 4'), + (10, 5770, 'SP3 CU3', 'https://support.microsoft.com/en-us/help/2648098', '2012-01-16', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 3'), + (10, 5768, 'SP3 CU2', 'https://support.microsoft.com/en-us/help/2633143', '2011-11-21', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 2'), + (10, 5766, 'SP3 CU1', 'https://support.microsoft.com/en-us/help/2617146', '2011-10-17', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 1'), + (10, 5538, 'SP3 MS15-058: GDR Security Update', 'https://support.microsoft.com/en-us/help/3045305', '2015-07-14', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 MS15-058: GDR Security Update'), + (10, 5520, 'SP3 MS14-044: GDR Security Update', 'https://support.microsoft.com/en-us/help/2977321', '2014-08-12', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 MS14-044: GDR Security Update'), + (10, 5512, 'SP3 MS12-070: GDR Security Update', 'https://support.microsoft.com/en-us/help/2716436', '2012-10-09', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 MS12-070: GDR Security Update'), + (10, 5500, 'SP3 ', 'https://support.microsoft.com/en-us/help/2546951', '2011-10-06', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 '), + (10, 4371, 'SP2 MS12-070: QFE Security Update', 'https://support.microsoft.com/en-us/help/2716433', '2012-10-09', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 MS12-070: QFE Security Update'), + (10, 4333, 'SP2 CU11', 'https://support.microsoft.com/en-us/help/2715951', '2012-07-16', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 Cumulative Update 11'), + (10, 4332, 'SP2 CU10', 'https://support.microsoft.com/en-us/help/2696625', '2012-05-21', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 Cumulative Update 10'), + (10, 4330, 'SP2 CU9', 'https://support.microsoft.com/en-us/help/2673382', '2012-03-19', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 Cumulative Update 9'), + (10, 4326, 'SP2 CU8', 'https://support.microsoft.com/en-us/help/2648096', '2012-01-16', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 Cumulative Update 8'), + (10, 4323, 'SP2 CU7', 'https://support.microsoft.com/en-us/help/2617148', '2011-11-21', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 Cumulative Update 7'), + (10, 4321, 'SP2 CU6', 'https://support.microsoft.com/en-us/help/2582285', '2011-09-19', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 Cumulative Update 6'), + (10, 4316, 'SP2 CU5', 'https://support.microsoft.com/en-us/help/2555408', '2011-07-18', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 Cumulative Update 5'), + (10, 4311, 'SP2 MS11-049: QFE Security Update', 'https://support.microsoft.com/en-us/help/2494094', '2011-06-14', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 MS11-049: QFE Security Update'), + (10, 4285, 'SP2 CU4', 'https://support.microsoft.com/en-us/help/2527180', '2011-05-16', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 Cumulative Update 4'), + (10, 4279, 'SP2 CU3', 'https://support.microsoft.com/en-us/help/2498535', '2011-03-17', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 Cumulative Update 3'), + (10, 4272, 'SP2 CU2', 'https://support.microsoft.com/en-us/help/2467239', '2011-01-17', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 Cumulative Update 2'), + (10, 4266, 'SP2 CU1', 'https://support.microsoft.com/en-us/help/2289254', '2010-11-15', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 Cumulative Update 1'), + (10, 4067, 'SP2 MS12-070: GDR Security Update', 'https://support.microsoft.com/en-us/help/2716434', '2012-10-09', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 MS12-070: GDR Security Update'), + (10, 4064, 'SP2 MS11-049: GDR Security Update', 'https://support.microsoft.com/en-us/help/2494089', '2011-06-14', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 MS11-049: GDR Security Update'), + (10, 4000, 'SP2 ', 'https://support.microsoft.com/en-us/help/2285068', '2010-09-29', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 '), + (10, 2850, 'SP1 CU16', 'https://support.microsoft.com/en-us/help/2582282', '2011-09-19', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 16'), + (10, 2847, 'SP1 CU15', 'https://support.microsoft.com/en-us/help/2555406', '2011-07-18', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 15'), + (10, 2841, 'SP1 MS11-049: QFE Security Update', 'https://support.microsoft.com/en-us/help/2494100', '2011-06-14', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 MS11-049: QFE Security Update'), + (10, 2821, 'SP1 CU14', 'https://support.microsoft.com/en-us/help/2527187', '2011-05-16', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 14'), + (10, 2816, 'SP1 CU13', 'https://support.microsoft.com/en-us/help/2497673', '2011-03-17', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 13'), + (10, 2808, 'SP1 CU12', 'https://support.microsoft.com/en-us/help/2467236', '2011-01-17', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 12'), + (10, 2804, 'SP1 CU11', 'https://support.microsoft.com/en-us/help/2413738', '2010-11-15', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 11'), + (10, 2799, 'SP1 CU10', 'https://support.microsoft.com/en-us/help/2279604', '2010-09-20', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 10'), + (10, 2789, 'SP1 CU9', 'https://support.microsoft.com/en-us/help/2083921', '2010-07-19', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 9'), + (10, 2775, 'SP1 CU8', 'https://support.microsoft.com/en-us/help/981702', '2010-05-17', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 8'), + (10, 2766, 'SP1 CU7', 'https://support.microsoft.com/en-us/help/979065', '2010-03-26', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 7'), + (10, 2757, 'SP1 CU6', 'https://support.microsoft.com/en-us/help/977443', '2010-01-18', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 6'), + (10, 2746, 'SP1 CU5', 'https://support.microsoft.com/en-us/help/975977', '2009-11-16', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 5'), + (10, 2734, 'SP1 CU4', 'https://support.microsoft.com/en-us/help/973602', '2009-09-21', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 4'), + (10, 2723, 'SP1 CU3', 'https://support.microsoft.com/en-us/help/971491', '2009-07-20', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 3'), + (10, 2714, 'SP1 CU2', 'https://support.microsoft.com/en-us/help/970315', '2009-05-18', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 2'), + (10, 2710, 'SP1 CU1', 'https://support.microsoft.com/en-us/help/969099', '2009-04-16', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 1'), + (10, 2573, 'SP1 MS11-049: GDR Security update', 'https://support.microsoft.com/en-us/help/2494096', '2011-06-14', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 MS11-049: GDR Security update'), + (10, 2531, 'SP1 ', '', '2009-04-01', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 '), + (10, 1835, 'RTM CU10', 'https://support.microsoft.com/en-us/help/979064', '2010-03-15', '2014-07-08', '2019-07-09', 'SQL Server 2008', 'RTM Cumulative Update 10'), + (10, 1828, 'RTM CU9', 'https://support.microsoft.com/en-us/help/977444', '2010-01-18', '2014-07-08', '2019-07-09', 'SQL Server 2008', 'RTM Cumulative Update 9'), + (10, 1823, 'RTM CU8', 'https://support.microsoft.com/en-us/help/975976', '2009-11-16', '2014-07-08', '2019-07-09', 'SQL Server 2008', 'RTM Cumulative Update 8'), + (10, 1818, 'RTM CU7', 'https://support.microsoft.com/en-us/help/973601', '2009-09-21', '2014-07-08', '2019-07-09', 'SQL Server 2008', 'RTM Cumulative Update 7'), + (10, 1812, 'RTM CU6', 'https://support.microsoft.com/en-us/help/971490', '2009-07-20', '2014-07-08', '2019-07-09', 'SQL Server 2008', 'RTM Cumulative Update 6'), + (10, 1806, 'RTM CU5', 'https://support.microsoft.com/en-us/help/969531', '2009-05-18', '2014-07-08', '2019-07-09', 'SQL Server 2008', 'RTM Cumulative Update 5'), + (10, 1798, 'RTM CU4', 'https://support.microsoft.com/en-us/help/963036', '2009-03-16', '2014-07-08', '2019-07-09', 'SQL Server 2008', 'RTM Cumulative Update 4'), + (10, 1787, 'RTM CU3', 'https://support.microsoft.com/en-us/help/960484', '2009-01-19', '2014-07-08', '2019-07-09', 'SQL Server 2008', 'RTM Cumulative Update 3'), + (10, 1779, 'RTM CU2', 'https://support.microsoft.com/en-us/help/958186', '2008-11-19', '2014-07-08', '2019-07-09', 'SQL Server 2008', 'RTM Cumulative Update 2'), + (10, 1763, 'RTM CU1', 'https://support.microsoft.com/en-us/help/956717', '2008-09-22', '2014-07-08', '2019-07-09', 'SQL Server 2008', 'RTM Cumulative Update 1'), + (10, 1600, 'RTM ', '', '2008-08-06', '2014-07-08', '2019-07-09', 'SQL Server 2008', 'RTM ') +; +GO +IF OBJECT_ID('dbo.sp_BlitzFirst') IS NULL + EXEC ('CREATE PROCEDURE dbo.sp_BlitzFirst AS RETURN 0;'); +GO + + +ALTER PROCEDURE [dbo].[sp_BlitzFirst] + @LogMessage NVARCHAR(4000) = NULL , + @Help TINYINT = 0 , + @AsOf DATETIMEOFFSET = NULL , + @ExpertMode TINYINT = 0 , + @Seconds INT = 5 , + @OutputType VARCHAR(20) = 'TABLE' , + @OutputServerName NVARCHAR(256) = NULL , + @OutputDatabaseName NVARCHAR(256) = NULL , + @OutputSchemaName NVARCHAR(256) = NULL , + @OutputTableName NVARCHAR(256) = NULL , + @OutputTableNameFileStats NVARCHAR(256) = NULL , + @OutputTableNamePerfmonStats NVARCHAR(256) = NULL , + @OutputTableNameWaitStats NVARCHAR(256) = NULL , + @OutputTableNameBlitzCache NVARCHAR(256) = NULL , + @OutputTableNameBlitzWho NVARCHAR(256) = NULL , + @OutputTableRetentionDays TINYINT = 7 , + @OutputXMLasNVARCHAR TINYINT = 0 , + @FilterPlansByDatabase VARCHAR(MAX) = NULL , + @CheckProcedureCache TINYINT = 0 , + @CheckServerInfo TINYINT = 1 , + @FileLatencyThresholdMS INT = 100 , + @SinceStartup TINYINT = 0 , + @ShowSleepingSPIDs TINYINT = 0 , + @BlitzCacheSkipAnalysis BIT = 1 , + @MemoryGrantThresholdPct DECIMAL(5,2) = 15.00, + @LogMessageCheckID INT = 38, + @LogMessagePriority TINYINT = 1, + @LogMessageFindingsGroup VARCHAR(50) = 'Logged Message', + @LogMessageFinding VARCHAR(200) = 'Logged from sp_BlitzFirst', + @LogMessageURL VARCHAR(200) = '', + @LogMessageCheckDate DATETIMEOFFSET = NULL, + @Debug BIT = 0, + @Version VARCHAR(30) = NULL OUTPUT, + @VersionDate DATETIME = NULL OUTPUT, + @VersionCheckMode BIT = 0 + WITH EXECUTE AS CALLER, RECOMPILE +AS +BEGIN +SET NOCOUNT ON; +SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + +SELECT @Version = '8.01', @VersionDate = '20210222'; + +IF(@VersionCheckMode = 1) +BEGIN + RETURN; +END; + +IF @Help = 1 +BEGIN +PRINT ' +sp_BlitzFirst from http://FirstResponderKit.org + +This script gives you a prioritized list of why your SQL Server is slow right now. + +This is not an overall health check - for that, check out sp_Blitz. + +To learn more, visit http://FirstResponderKit.org where you can download new +versions for free, watch training videos on how it works, get more info on +the findings, contribute your own code, and more. + +Known limitations of this version: + - Only Microsoft-supported versions of SQL Server. Sorry, 2005 and 2000. It + may work just fine on 2005, and if it does, hug your parents. Just don''t + file support issues if it breaks. + - If a temp table called #CustomPerfmonCounters exists for any other session, + but not our session, this stored proc will fail with an error saying the + temp table #CustomPerfmonCounters does not exist. + - @OutputServerName is not functional yet. + - If @OutputDatabaseName, SchemaName, TableName, etc are quoted with brackets, + the write to table may silently fail. Look, I never said I was good at this. + +Unknown limitations of this version: + - None. Like Zombo.com, the only limit is yourself. + +Changes - for the full list of improvements and fixes in this version, see: +https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/ + + +MIT License + +Copyright (c) 2021 Brent Ozar Unlimited + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +'; +RETURN; +END; /* @Help = 1 */ + +RAISERROR('Setting up configuration variables',10,1) WITH NOWAIT; +DECLARE @StringToExecute NVARCHAR(MAX), + @ParmDefinitions NVARCHAR(4000), + @Parm1 NVARCHAR(4000), + @OurSessionID INT, + @LineFeed NVARCHAR(10), + @StockWarningHeader NVARCHAR(MAX) = N'', + @StockWarningFooter NVARCHAR(MAX) = N'', + @StockDetailsHeader NVARCHAR(MAX) = N'', + @StockDetailsFooter NVARCHAR(MAX) = N'', + @StartSampleTime DATETIMEOFFSET, + @FinishSampleTime DATETIMEOFFSET, + @FinishSampleTimeWaitFor DATETIME, + @AsOf1 DATETIMEOFFSET, + @AsOf2 DATETIMEOFFSET, + @ServiceName sysname, + @OutputTableNameFileStats_View NVARCHAR(256), + @OutputTableNamePerfmonStats_View NVARCHAR(256), + @OutputTableNamePerfmonStatsActuals_View NVARCHAR(256), + @OutputTableNameWaitStats_View NVARCHAR(256), + @OutputTableNameWaitStats_Categories NVARCHAR(256), + @OutputTableCleanupDate DATE, + @ObjectFullName NVARCHAR(2000), + @BlitzWho NVARCHAR(MAX) = N'EXEC dbo.sp_BlitzWho @ShowSleepingSPIDs = ' + CONVERT(NVARCHAR(1), @ShowSleepingSPIDs) + N';', + @BlitzCacheMinutesBack INT, + @UnquotedOutputServerName NVARCHAR(256) = @OutputServerName , + @UnquotedOutputDatabaseName NVARCHAR(256) = @OutputDatabaseName , + @UnquotedOutputSchemaName NVARCHAR(256) = @OutputSchemaName , + @LocalServerName NVARCHAR(128) = CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128)), + @dm_exec_query_statistics_xml BIT = 0; + +/* Sanitize our inputs */ +SELECT + @OutputTableNameFileStats_View = QUOTENAME(@OutputTableNameFileStats + '_Deltas'), + @OutputTableNamePerfmonStats_View = QUOTENAME(@OutputTableNamePerfmonStats + '_Deltas'), + @OutputTableNamePerfmonStatsActuals_View = QUOTENAME(@OutputTableNamePerfmonStats + '_Actuals'), + @OutputTableNameWaitStats_View = QUOTENAME(@OutputTableNameWaitStats + '_Deltas'), + @OutputTableNameWaitStats_Categories = QUOTENAME(@OutputTableNameWaitStats + '_Categories'); + +SELECT + @OutputDatabaseName = QUOTENAME(@OutputDatabaseName), + @OutputSchemaName = QUOTENAME(@OutputSchemaName), + @OutputTableName = QUOTENAME(@OutputTableName), + @OutputTableNameFileStats = QUOTENAME(@OutputTableNameFileStats), + @OutputTableNamePerfmonStats = QUOTENAME(@OutputTableNamePerfmonStats), + @OutputTableNameWaitStats = QUOTENAME(@OutputTableNameWaitStats), + @OutputTableCleanupDate = CAST( (DATEADD(DAY, -1 * @OutputTableRetentionDays, GETDATE() ) ) AS DATE), + /* @OutputTableNameBlitzCache = QUOTENAME(@OutputTableNameBlitzCache), We purposely don't sanitize this because sp_BlitzCache will */ + /* @OutputTableNameBlitzWho = QUOTENAME(@OutputTableNameBlitzWho), We purposely don't sanitize this because sp_BlitzWho will */ + @LineFeed = CHAR(13) + CHAR(10), + @OurSessionID = @@SPID, + @OutputType = UPPER(@OutputType); + +IF(@OutputType = 'NONE' AND (@OutputTableName IS NULL OR @OutputSchemaName IS NULL OR @OutputDatabaseName IS NULL)) +BEGIN + RAISERROR('This procedure should be called with a value for all @Output* parameters, as @OutputType is set to NONE',12,1); + RETURN; +END; + +IF UPPER(@OutputType) LIKE 'TOP 10%' SET @OutputType = 'Top10'; +IF @OutputType = 'Top10' SET @SinceStartup = 1; + +/* Logged Message - CheckID 38 */ +IF @LogMessage IS NOT NULL + BEGIN + + RAISERROR('Saving LogMessage to table',10,1) WITH NOWAIT; + + /* Try to set the output table parameters if they don't exist */ + IF @OutputSchemaName IS NULL AND @OutputTableName IS NULL AND @OutputDatabaseName IS NULL + BEGIN + SET @OutputSchemaName = N'[dbo]'; + SET @OutputTableName = N'[BlitzFirst]'; + + /* Look for the table in the current database */ + SELECT TOP 1 @OutputDatabaseName = QUOTENAME(TABLE_CATALOG) + FROM INFORMATION_SCHEMA.TABLES + WHERE TABLE_SCHEMA = 'dbo' AND TABLE_NAME = 'BlitzFirst'; + + IF @OutputDatabaseName IS NULL AND EXISTS (SELECT * FROM sys.databases WHERE name = 'DBAtools') + SET @OutputDatabaseName = '[DBAtools]'; + + END; + + IF @OutputDatabaseName IS NULL OR @OutputSchemaName IS NULL OR @OutputTableName IS NULL + OR NOT EXISTS ( SELECT * + FROM sys.databases + WHERE QUOTENAME([name]) = @OutputDatabaseName) + BEGIN + RAISERROR('We have a hard time logging a message without a valid @OutputDatabaseName, @OutputSchemaName, and @OutputTableName to log it to.', 0, 1) WITH NOWAIT; + RETURN; + END; + IF @LogMessageCheckDate IS NULL + SET @LogMessageCheckDate = SYSDATETIMEOFFSET(); + SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' + + @OutputDatabaseName + + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' + + @OutputSchemaName + ''') INSERT ' + + @OutputDatabaseName + '.' + + @OutputSchemaName + '.' + + @OutputTableName + + ' (ServerName, CheckDate, CheckID, Priority, FindingsGroup, Finding, Details, URL) VALUES( ' + + ' @SrvName, @LogMessageCheckDate, @LogMessageCheckID, @LogMessagePriority, @LogMessageFindingsGroup, @LogMessageFinding, @LogMessage, @LogMessageURL)'; + + EXECUTE sp_executesql @StringToExecute, + N'@SrvName NVARCHAR(128), @LogMessageCheckID INT, @LogMessagePriority TINYINT, @LogMessageFindingsGroup VARCHAR(50), @LogMessageFinding VARCHAR(200), @LogMessage NVARCHAR(4000), @LogMessageCheckDate DATETIMEOFFSET, @LogMessageURL VARCHAR(200)', + @LocalServerName, @LogMessageCheckID, @LogMessagePriority, @LogMessageFindingsGroup, @LogMessageFinding, @LogMessage, @LogMessageCheckDate, @LogMessageURL; + + RAISERROR('LogMessage saved to table. We have made a note of your activity. Keep up the good work.',10,1) WITH NOWAIT; + + RETURN; + END; + +IF @SinceStartup = 1 + SELECT @Seconds = 0, @ExpertMode = 1; + + +IF @OutputType = 'SCHEMA' +BEGIN + SELECT FieldList = '[Priority] TINYINT, [FindingsGroup] VARCHAR(50), [Finding] VARCHAR(200), [URL] VARCHAR(200), [Details] NVARCHAR(4000), [HowToStopIt] NVARCHAR(MAX), [QueryPlan] XML, [QueryText] NVARCHAR(MAX)'; + +END; +ELSE IF @AsOf IS NOT NULL AND @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @OutputTableName IS NOT NULL +BEGIN + /* They want to look into the past. */ + SET @AsOf1= DATEADD(mi, -15, @AsOf); + SET @AsOf2= DATEADD(mi, +15, @AsOf); + + SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' + + @OutputDatabaseName + + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' + + @OutputSchemaName + ''') SELECT CheckDate, [Priority], [FindingsGroup], [Finding], [URL], CAST([Details] AS [XML]) AS Details,' + + '[HowToStopIt], [CheckID], [StartTime], [LoginName], [NTUserName], [OriginalLoginName], [ProgramName], [HostName], [DatabaseID],' + + '[DatabaseName], [OpenTransactionCount], [QueryPlan], [QueryText] FROM ' + + @OutputDatabaseName + '.' + + @OutputSchemaName + '.' + + @OutputTableName + + ' WHERE CheckDate >= @AsOf1' + + ' AND CheckDate <= @AsOf2' + + ' /*ORDER BY CheckDate, Priority , FindingsGroup , Finding , Details*/;'; + EXEC sp_executesql @StringToExecute, + N'@AsOf1 DATETIMEOFFSET, @AsOf2 DATETIMEOFFSET', + @AsOf1, @AsOf2 + + +END; /* IF @AsOf IS NOT NULL AND @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @OutputTableName IS NOT NULL */ +ELSE IF @LogMessage IS NULL /* IF @OutputType = 'SCHEMA' */ +BEGIN + /* What's running right now? This is the first and last result set. */ + IF @SinceStartup = 0 AND @Seconds > 0 AND @ExpertMode = 1 AND @OutputType <> 'NONE' + BEGIN + IF OBJECT_ID('master.dbo.sp_BlitzWho') IS NULL AND OBJECT_ID('dbo.sp_BlitzWho') IS NULL + BEGIN + PRINT N'sp_BlitzWho is not installed in the current database_files. You can get a copy from http://FirstResponderKit.org'; + END; + ELSE + BEGIN + EXEC (@BlitzWho); + END; + END; /* IF @SinceStartup = 0 AND @Seconds > 0 AND @ExpertMode = 1 AND @OutputType <> 'NONE' - What's running right now? This is the first and last result set. */ + + /* Set start/finish times AFTER sp_BlitzWho runs. For more info: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2244 */ + IF @Seconds = 0 AND SERVERPROPERTY('Edition') = 'SQL Azure' + WITH WaitTimes AS ( + SELECT wait_type, wait_time_ms, + NTILE(3) OVER(ORDER BY wait_time_ms) AS grouper + FROM sys.dm_os_wait_stats w + WHERE wait_type IN ('DIRTY_PAGE_POLL','HADR_FILESTREAM_IOMGR_IOCOMPLETION','LAZYWRITER_SLEEP', + 'LOGMGR_QUEUE','REQUEST_FOR_DEADLOCK_SEARCH','XE_TIMER_EVENT') + ) + SELECT @StartSampleTime = DATEADD(mi, AVG(-wait_time_ms / 1000 / 60), SYSDATETIMEOFFSET()), @FinishSampleTime = SYSDATETIMEOFFSET() + FROM WaitTimes + WHERE grouper = 2; + ELSE IF @Seconds = 0 AND SERVERPROPERTY('Edition') <> 'SQL Azure' + SELECT @StartSampleTime = DATEADD(MINUTE,DATEDIFF(MINUTE, GETDATE(), GETUTCDATE()),create_date) , @FinishSampleTime = SYSDATETIMEOFFSET() + FROM sys.databases + WHERE database_id = 2; + ELSE + SELECT @StartSampleTime = SYSDATETIMEOFFSET(), + @FinishSampleTime = DATEADD(ss, @Seconds, SYSDATETIMEOFFSET()), + @FinishSampleTimeWaitFor = DATEADD(ss, @Seconds, GETDATE()); + + + RAISERROR('Now starting diagnostic analysis',10,1) WITH NOWAIT; + + /* + We start by creating #BlitzFirstResults. It's a temp table that will store + the results from our checks. Throughout the rest of this stored procedure, + we're running a series of checks looking for dangerous things inside the SQL + Server. When we find a problem, we insert rows into the temp table. At the + end, we return these results to the end user. + + #BlitzFirstResults has a CheckID field, but there's no Check table. As we do + checks, we insert data into this table, and we manually put in the CheckID. + We (Brent Ozar Unlimited) maintain a list of the checks by ID#. You can + download that from http://FirstResponderKit.org if you want to build + a tool that relies on the output of sp_BlitzFirst. + */ + + + IF OBJECT_ID('tempdb..#BlitzFirstResults') IS NOT NULL + DROP TABLE #BlitzFirstResults; + CREATE TABLE #BlitzFirstResults + ( + ID INT IDENTITY(1, 1) PRIMARY KEY CLUSTERED, + CheckID INT NOT NULL, + Priority TINYINT NOT NULL, + FindingsGroup VARCHAR(50) NOT NULL, + Finding VARCHAR(200) NOT NULL, + URL VARCHAR(200) NULL, + Details NVARCHAR(MAX) NULL, + HowToStopIt NVARCHAR(MAX) NULL, + QueryPlan [XML] NULL, + QueryText NVARCHAR(MAX) NULL, + StartTime DATETIMEOFFSET NULL, + LoginName NVARCHAR(128) NULL, + NTUserName NVARCHAR(128) NULL, + OriginalLoginName NVARCHAR(128) NULL, + ProgramName NVARCHAR(128) NULL, + HostName NVARCHAR(128) NULL, + DatabaseID INT NULL, + DatabaseName NVARCHAR(128) NULL, + OpenTransactionCount INT NULL, + QueryStatsNowID INT NULL, + QueryStatsFirstID INT NULL, + PlanHandle VARBINARY(64) NULL, + DetailsInt INT NULL, + QueryHash BINARY(8) + ); + + IF OBJECT_ID('tempdb..#WaitStats') IS NOT NULL + DROP TABLE #WaitStats; + CREATE TABLE #WaitStats (Pass TINYINT NOT NULL, wait_type NVARCHAR(60), wait_time_ms BIGINT, signal_wait_time_ms BIGINT, waiting_tasks_count BIGINT, SampleTime DATETIMEOFFSET); + + IF OBJECT_ID('tempdb..#FileStats') IS NOT NULL + DROP TABLE #FileStats; + CREATE TABLE #FileStats ( + ID INT IDENTITY(1, 1) PRIMARY KEY CLUSTERED, + Pass TINYINT NOT NULL, + SampleTime DATETIMEOFFSET NOT NULL, + DatabaseID INT NOT NULL, + FileID INT NOT NULL, + DatabaseName NVARCHAR(256) , + FileLogicalName NVARCHAR(256) , + TypeDesc NVARCHAR(60) , + SizeOnDiskMB BIGINT , + io_stall_read_ms BIGINT , + num_of_reads BIGINT , + bytes_read BIGINT , + io_stall_write_ms BIGINT , + num_of_writes BIGINT , + bytes_written BIGINT, + PhysicalName NVARCHAR(520) , + avg_stall_read_ms INT , + avg_stall_write_ms INT + ); + + IF OBJECT_ID('tempdb..#QueryStats') IS NOT NULL + DROP TABLE #QueryStats; + CREATE TABLE #QueryStats ( + ID INT IDENTITY(1, 1) PRIMARY KEY CLUSTERED, + Pass INT NOT NULL, + SampleTime DATETIMEOFFSET NOT NULL, + [sql_handle] VARBINARY(64), + statement_start_offset INT, + statement_end_offset INT, + plan_generation_num BIGINT, + plan_handle VARBINARY(64), + execution_count BIGINT, + total_worker_time BIGINT, + total_physical_reads BIGINT, + total_logical_writes BIGINT, + total_logical_reads BIGINT, + total_clr_time BIGINT, + total_elapsed_time BIGINT, + creation_time DATETIMEOFFSET, + query_hash BINARY(8), + query_plan_hash BINARY(8), + Points TINYINT + ); + + IF OBJECT_ID('tempdb..#PerfmonStats') IS NOT NULL + DROP TABLE #PerfmonStats; + CREATE TABLE #PerfmonStats ( + ID INT IDENTITY(1, 1) PRIMARY KEY CLUSTERED, + Pass TINYINT NOT NULL, + SampleTime DATETIMEOFFSET NOT NULL, + [object_name] NVARCHAR(128) NOT NULL, + [counter_name] NVARCHAR(128) NOT NULL, + [instance_name] NVARCHAR(128) NULL, + [cntr_value] BIGINT NULL, + [cntr_type] INT NOT NULL, + [value_delta] BIGINT NULL, + [value_per_second] DECIMAL(18,2) NULL + ); + + IF OBJECT_ID('tempdb..#PerfmonCounters') IS NOT NULL + DROP TABLE #PerfmonCounters; + CREATE TABLE #PerfmonCounters ( + ID INT IDENTITY(1, 1) PRIMARY KEY CLUSTERED, + [object_name] NVARCHAR(128) NOT NULL, + [counter_name] NVARCHAR(128) NOT NULL, + [instance_name] NVARCHAR(128) NULL + ); + + IF OBJECT_ID('tempdb..#FilterPlansByDatabase') IS NOT NULL + DROP TABLE #FilterPlansByDatabase; + CREATE TABLE #FilterPlansByDatabase (DatabaseID INT PRIMARY KEY CLUSTERED); + + IF OBJECT_ID('tempdb..##WaitCategories') IS NULL + BEGIN + /* We reuse this one by default rather than recreate it every time. */ + CREATE TABLE ##WaitCategories + ( + WaitType NVARCHAR(60) PRIMARY KEY CLUSTERED, + WaitCategory NVARCHAR(128) NOT NULL, + Ignorable BIT DEFAULT 0 + ); + END; /* IF OBJECT_ID('tempdb..##WaitCategories') IS NULL */ + + IF OBJECT_ID ('tempdb..#checkversion') IS NOT NULL + DROP TABLE #checkversion; + CREATE TABLE #checkversion ( + version NVARCHAR(128), + common_version AS SUBSTRING(version, 1, CHARINDEX('.', version) + 1 ), + major AS PARSENAME(CONVERT(VARCHAR(32), version), 4), + minor AS PARSENAME(CONVERT(VARCHAR(32), version), 3), + build AS PARSENAME(CONVERT(VARCHAR(32), version), 2), + revision AS PARSENAME(CONVERT(VARCHAR(32), version), 1) + ); + + IF 527 <> (SELECT COALESCE(SUM(1),0) FROM ##WaitCategories) + BEGIN + TRUNCATE TABLE ##WaitCategories; + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('ASYNC_IO_COMPLETION','Other Disk IO',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('ASYNC_NETWORK_IO','Network IO',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BACKUPIO','Other Disk IO',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_CONNECTION_RECEIVE_TASK','Service Broker',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_DISPATCHER','Service Broker',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_ENDPOINT_STATE_MUTEX','Service Broker',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_EVENTHANDLER','Service Broker',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_FORWARDER','Service Broker',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_INIT','Service Broker',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_MASTERSTART','Service Broker',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_RECEIVE_WAITFOR','User Wait',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_REGISTERALLENDPOINTS','Service Broker',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_SERVICE','Service Broker',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_SHUTDOWN','Service Broker',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_START','Service Broker',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_TASK_SHUTDOWN','Service Broker',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_TASK_STOP','Service Broker',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_TASK_SUBMIT','Service Broker',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_TO_FLUSH','Service Broker',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_TRANSMISSION_OBJECT','Service Broker',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_TRANSMISSION_TABLE','Service Broker',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_TRANSMISSION_WORK','Service Broker',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_TRANSMITTER','Service Broker',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CHECKPOINT_QUEUE','Idle',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CHKPT','Tran Log IO',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CLR_AUTO_EVENT','SQL CLR',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CLR_CRST','SQL CLR',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CLR_JOIN','SQL CLR',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CLR_MANUAL_EVENT','SQL CLR',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CLR_MEMORY_SPY','SQL CLR',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CLR_MONITOR','SQL CLR',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CLR_RWLOCK_READER','SQL CLR',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CLR_RWLOCK_WRITER','SQL CLR',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CLR_SEMAPHORE','SQL CLR',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CLR_TASK_START','SQL CLR',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CLRHOST_STATE_ACCESS','SQL CLR',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CMEMPARTITIONED','Memory',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CMEMTHREAD','Memory',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CXPACKET','Parallelism',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CXCONSUMER','Parallelism',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DBMIRROR_DBM_EVENT','Mirroring',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DBMIRROR_DBM_MUTEX','Mirroring',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DBMIRROR_EVENTS_QUEUE','Mirroring',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DBMIRROR_SEND','Mirroring',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DBMIRROR_WORKER_QUEUE','Mirroring',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DBMIRRORING_CMD','Mirroring',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DIRTY_PAGE_POLL','Other',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DIRTY_PAGE_TABLE_LOCK','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DISPATCHER_QUEUE_SEMAPHORE','Other',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DPT_ENTRY_LOCK','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DTC','Transaction',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DTC_ABORT_REQUEST','Transaction',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DTC_RESOLVE','Transaction',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DTC_STATE','Transaction',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DTC_TMDOWN_REQUEST','Transaction',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DTC_WAITFOR_OUTCOME','Transaction',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DTCNEW_ENLIST','Transaction',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DTCNEW_PREPARE','Transaction',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DTCNEW_RECOVERY','Transaction',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DTCNEW_TM','Transaction',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DTCNEW_TRANSACTION_ENLISTMENT','Transaction',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DTCPNTSYNC','Transaction',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('EE_PMOLOCK','Memory',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('EXCHANGE','Parallelism',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('EXTERNAL_SCRIPT_NETWORK_IOF','Network IO',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FCB_REPLICA_READ','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FCB_REPLICA_WRITE','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_COMPROWSET_RWLOCK','Full Text Search',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_IFTS_RWLOCK','Full Text Search',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_IFTS_SCHEDULER_IDLE_WAIT','Idle',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_IFTSHC_MUTEX','Full Text Search',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_IFTSISM_MUTEX','Full Text Search',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_MASTER_MERGE','Full Text Search',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_MASTER_MERGE_COORDINATOR','Full Text Search',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_METADATA_MUTEX','Full Text Search',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_PROPERTYLIST_CACHE','Full Text Search',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_RESTART_CRAWL','Full Text Search',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FULLTEXT GATHERER','Full Text Search',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_AG_MUTEX','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_AR_CRITICAL_SECTION_ENTRY','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_AR_MANAGER_MUTEX','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_AR_UNLOAD_COMPLETED','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_ARCONTROLLER_NOTIFICATIONS_SUBSCRIBER_LIST','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_BACKUP_BULK_LOCK','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_BACKUP_QUEUE','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_CLUSAPI_CALL','Replication',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_COMPRESSED_CACHE_SYNC','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_CONNECTIVITY_INFO','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_DATABASE_FLOW_CONTROL','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_DATABASE_VERSIONING_STATE','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_DATABASE_WAIT_FOR_RECOVERY','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_DATABASE_WAIT_FOR_RESTART','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_DATABASE_WAIT_FOR_TRANSITION_TO_VERSIONING','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_DB_COMMAND','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_DB_OP_COMPLETION_SYNC','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_DB_OP_START_SYNC','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_DBR_SUBSCRIBER','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_DBR_SUBSCRIBER_FILTER_LIST','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_DBSEEDING','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_DBSEEDING_LIST','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_DBSTATECHANGE_SYNC','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_FABRIC_CALLBACK','Replication',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_FILESTREAM_BLOCK_FLUSH','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_FILESTREAM_FILE_CLOSE','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_FILESTREAM_FILE_REQUEST','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_FILESTREAM_IOMGR','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_FILESTREAM_IOMGR_IOCOMPLETION','Replication',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_FILESTREAM_MANAGER','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_FILESTREAM_PREPROC','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_GROUP_COMMIT','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_LOGCAPTURE_SYNC','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_LOGCAPTURE_WAIT','Replication',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_LOGPROGRESS_SYNC','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_NOTIFICATION_DEQUEUE','Replication',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_NOTIFICATION_WORKER_EXCLUSIVE_ACCESS','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_NOTIFICATION_WORKER_STARTUP_SYNC','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_NOTIFICATION_WORKER_TERMINATION_SYNC','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_PARTNER_SYNC','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_READ_ALL_NETWORKS','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_RECOVERY_WAIT_FOR_CONNECTION','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_RECOVERY_WAIT_FOR_UNDO','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_REPLICAINFO_SYNC','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_SEEDING_CANCELLATION','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_SEEDING_FILE_LIST','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_SEEDING_LIMIT_BACKUPS','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_SEEDING_SYNC_COMPLETION','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_SEEDING_TIMEOUT_TASK','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_SEEDING_WAIT_FOR_COMPLETION','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_SYNC_COMMIT','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_SYNCHRONIZING_THROTTLE','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_TDS_LISTENER_SYNC','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_TDS_LISTENER_SYNC_PROCESSING','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_THROTTLE_LOG_RATE_GOVERNOR','Log Rate Governor',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_TIMER_TASK','Replication',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_TRANSPORT_DBRLIST','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_TRANSPORT_FLOW_CONTROL','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_TRANSPORT_SESSION','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_WORK_POOL','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_WORK_QUEUE','Replication',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_XRF_STACK_ACCESS','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('INSTANCE_LOG_RATE_GOVERNOR','Log Rate Governor',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('IO_COMPLETION','Other Disk IO',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('IO_QUEUE_LIMIT','Other Disk IO',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('IO_RETRY','Other Disk IO',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LATCH_DT','Latch',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LATCH_EX','Latch',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LATCH_KP','Latch',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LATCH_NL','Latch',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LATCH_SH','Latch',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LATCH_UP','Latch',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LAZYWRITER_SLEEP','Idle',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_BU','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_BU_ABORT_BLOCKERS','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_BU_LOW_PRIORITY','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_IS','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_IS_ABORT_BLOCKERS','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_IS_LOW_PRIORITY','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_IU','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_IU_ABORT_BLOCKERS','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_IU_LOW_PRIORITY','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_IX','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_IX_ABORT_BLOCKERS','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_IX_LOW_PRIORITY','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RIn_NL','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RIn_NL_ABORT_BLOCKERS','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RIn_NL_LOW_PRIORITY','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RIn_S','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RIn_S_ABORT_BLOCKERS','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RIn_S_LOW_PRIORITY','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RIn_U','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RIn_U_ABORT_BLOCKERS','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RIn_U_LOW_PRIORITY','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RIn_X','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RIn_X_ABORT_BLOCKERS','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RIn_X_LOW_PRIORITY','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RS_S','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RS_S_ABORT_BLOCKERS','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RS_S_LOW_PRIORITY','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RS_U','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RS_U_ABORT_BLOCKERS','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RS_U_LOW_PRIORITY','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RX_S','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RX_S_ABORT_BLOCKERS','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RX_S_LOW_PRIORITY','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RX_U','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RX_U_ABORT_BLOCKERS','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RX_U_LOW_PRIORITY','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RX_X','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RX_X_ABORT_BLOCKERS','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RX_X_LOW_PRIORITY','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_S','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_S_ABORT_BLOCKERS','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_S_LOW_PRIORITY','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_SCH_M','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_SCH_M_ABORT_BLOCKERS','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_SCH_M_LOW_PRIORITY','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_SCH_S','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_SCH_S_ABORT_BLOCKERS','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_SCH_S_LOW_PRIORITY','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_SIU','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_SIU_ABORT_BLOCKERS','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_SIU_LOW_PRIORITY','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_SIX','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_SIX_ABORT_BLOCKERS','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_SIX_LOW_PRIORITY','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_U','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_U_ABORT_BLOCKERS','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_U_LOW_PRIORITY','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_UIX','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_UIX_ABORT_BLOCKERS','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_UIX_LOW_PRIORITY','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_X','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_X_ABORT_BLOCKERS','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_X_LOW_PRIORITY','Lock',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LOG_RATE_GOVERNOR','Tran Log IO',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LOGBUFFER','Tran Log IO',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LOGMGR','Tran Log IO',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LOGMGR_FLUSH','Tran Log IO',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LOGMGR_PMM_LOG','Tran Log IO',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LOGMGR_QUEUE','Idle',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LOGMGR_RESERVE_APPEND','Tran Log IO',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('MEMORY_ALLOCATION_EXT','Memory',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('MEMORY_GRANT_UPDATE','Memory',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('MSQL_XACT_MGR_MUTEX','Transaction',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('MSQL_XACT_MUTEX','Transaction',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('MSSEARCH','Full Text Search',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('NET_WAITFOR_PACKET','Network IO',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('ONDEMAND_TASK_QUEUE','Idle',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PAGEIOLATCH_DT','Buffer IO',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PAGEIOLATCH_EX','Buffer IO',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PAGEIOLATCH_KP','Buffer IO',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PAGEIOLATCH_NL','Buffer IO',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PAGEIOLATCH_SH','Buffer IO',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PAGEIOLATCH_UP','Buffer IO',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PAGELATCH_DT','Buffer Latch',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PAGELATCH_EX','Buffer Latch',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PAGELATCH_KP','Buffer Latch',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PAGELATCH_NL','Buffer Latch',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PAGELATCH_SH','Buffer Latch',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PAGELATCH_UP','Buffer Latch',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PARALLEL_REDO_DRAIN_WORKER','Replication',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PARALLEL_REDO_FLOW_CONTROL','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PARALLEL_REDO_LOG_CACHE','Replication',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PARALLEL_REDO_TRAN_LIST','Replication',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PARALLEL_REDO_TRAN_TURN','Replication',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PARALLEL_REDO_WORKER_SYNC','Replication',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PARALLEL_REDO_WORKER_WAIT_WORK','Replication',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('POOL_LOG_RATE_GOVERNOR','Log Rate Governor',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_ABR','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_CLOSEBACKUPMEDIA','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_CLOSEBACKUPTAPE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_CLOSEBACKUPVDIDEVICE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_CLUSAPI_CLUSTERRESOURCECONTROL','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_COCREATEINSTANCE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_COGETCLASSOBJECT','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_CREATEACCESSOR','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_DELETEROWS','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_GETCOMMANDTEXT','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_GETDATA','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_GETNEXTROWS','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_GETRESULT','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_GETROWSBYBOOKMARK','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_LBFLUSH','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_LBLOCKREGION','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_LBREADAT','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_LBSETSIZE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_LBSTAT','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_LBUNLOCKREGION','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_LBWRITEAT','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_QUERYINTERFACE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_RELEASE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_RELEASEACCESSOR','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_RELEASEROWS','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_RELEASESESSION','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_RESTARTPOSITION','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_SEQSTRMREAD','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_SEQSTRMREADANDWRITE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_SETDATAFAILURE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_SETPARAMETERINFO','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_SETPARAMETERPROPERTIES','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_STRMLOCKREGION','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_STRMSEEKANDREAD','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_STRMSEEKANDWRITE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_STRMSETSIZE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_STRMSTAT','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_STRMUNLOCKREGION','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_CONSOLEWRITE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_CREATEPARAM','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DEBUG','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DFSADDLINK','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DFSLINKEXISTCHECK','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DFSLINKHEALTHCHECK','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DFSREMOVELINK','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DFSREMOVEROOT','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DFSROOTFOLDERCHECK','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DFSROOTINIT','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DFSROOTSHARECHECK','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DTC_ABORT','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DTC_ABORTREQUESTDONE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DTC_BEGINTRANSACTION','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DTC_COMMITREQUESTDONE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DTC_ENLIST','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DTC_PREPAREREQUESTDONE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_FILESIZEGET','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_FSAOLEDB_ABORTTRANSACTION','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_FSAOLEDB_COMMITTRANSACTION','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_FSAOLEDB_STARTTRANSACTION','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_FSRECOVER_UNCONDITIONALUNDO','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_GETRMINFO','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_HADR_LEASE_MECHANISM','Preemptive',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_HTTP_EVENT_WAIT','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_HTTP_REQUEST','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_LOCKMONITOR','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_MSS_RELEASE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_ODBCOPS','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OLE_UNINIT','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OLEDB_ABORTORCOMMITTRAN','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OLEDB_ABORTTRAN','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OLEDB_GETDATASOURCE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OLEDB_GETLITERALINFO','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OLEDB_GETPROPERTIES','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OLEDB_GETPROPERTYINFO','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OLEDB_GETSCHEMALOCK','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OLEDB_JOINTRANSACTION','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OLEDB_RELEASE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OLEDB_SETPROPERTIES','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OLEDBOPS','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_ACCEPTSECURITYCONTEXT','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_ACQUIRECREDENTIALSHANDLE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_AUTHENTICATIONOPS','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_AUTHORIZATIONOPS','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_AUTHZGETINFORMATIONFROMCONTEXT','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_AUTHZINITIALIZECONTEXTFROMSID','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_AUTHZINITIALIZERESOURCEMANAGER','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_BACKUPREAD','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_CLOSEHANDLE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_CLUSTEROPS','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_COMOPS','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_COMPLETEAUTHTOKEN','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_COPYFILE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_CREATEDIRECTORY','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_CREATEFILE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_CRYPTACQUIRECONTEXT','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_CRYPTIMPORTKEY','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_CRYPTOPS','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_DECRYPTMESSAGE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_DELETEFILE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_DELETESECURITYCONTEXT','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_DEVICEIOCONTROL','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_DEVICEOPS','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_DIRSVC_NETWORKOPS','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_DISCONNECTNAMEDPIPE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_DOMAINSERVICESOPS','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_DSGETDCNAME','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_DTCOPS','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_ENCRYPTMESSAGE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_FILEOPS','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_FINDFILE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_FLUSHFILEBUFFERS','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_FORMATMESSAGE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_FREECREDENTIALSHANDLE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_FREELIBRARY','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_GENERICOPS','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_GETADDRINFO','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_GETCOMPRESSEDFILESIZE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_GETDISKFREESPACE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_GETFILEATTRIBUTES','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_GETFILESIZE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_GETFINALFILEPATHBYHANDLE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_GETLONGPATHNAME','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_GETPROCADDRESS','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_GETVOLUMENAMEFORVOLUMEMOUNTPOINT','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_GETVOLUMEPATHNAME','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_INITIALIZESECURITYCONTEXT','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_LIBRARYOPS','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_LOADLIBRARY','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_LOGONUSER','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_LOOKUPACCOUNTSID','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_MESSAGEQUEUEOPS','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_MOVEFILE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_NETGROUPGETUSERS','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_NETLOCALGROUPGETMEMBERS','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_NETUSERGETGROUPS','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_NETUSERGETLOCALGROUPS','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_NETUSERMODALSGET','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_NETVALIDATEPASSWORDPOLICY','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_NETVALIDATEPASSWORDPOLICYFREE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_OPENDIRECTORY','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_PDH_WMI_INIT','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_PIPEOPS','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_PROCESSOPS','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_QUERYCONTEXTATTRIBUTES','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_QUERYREGISTRY','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_QUERYSECURITYCONTEXTTOKEN','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_REMOVEDIRECTORY','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_REPORTEVENT','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_REVERTTOSELF','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_RSFXDEVICEOPS','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_SECURITYOPS','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_SERVICEOPS','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_SETENDOFFILE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_SETFILEPOINTER','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_SETFILEVALIDDATA','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_SETNAMEDSECURITYINFO','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_SQLCLROPS','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_SQMLAUNCH','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_VERIFYSIGNATURE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_VERIFYTRUST','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_VSSOPS','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_WAITFORSINGLEOBJECT','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_WINSOCKOPS','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_WRITEFILE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_WRITEFILEGATHER','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_WSASETLASTERROR','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_REENLIST','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_RESIZELOG','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_ROLLFORWARDREDO','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_ROLLFORWARDUNDO','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_SB_STOPENDPOINT','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_SERVER_STARTUP','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_SETRMINFO','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_SHAREDMEM_GETDATA','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_SNIOPEN','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_SOSHOST','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_SOSTESTING','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_SP_SERVER_DIAGNOSTICS','Preemptive',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_STARTRM','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_STREAMFCB_CHECKPOINT','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_STREAMFCB_RECOVER','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_STRESSDRIVER','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_TESTING','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_TRANSIMPORT','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_UNMARSHALPROPAGATIONTOKEN','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_VSS_CREATESNAPSHOT','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_VSS_CREATEVOLUMESNAPSHOT','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_XE_CALLBACKEXECUTE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_XE_CX_FILE_OPEN','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_XE_CX_HTTP_CALL','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_XE_DISPATCHER','Preemptive',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_XE_ENGINEINIT','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_XE_GETTARGETSTATE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_XE_SESSIONCOMMIT','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_XE_TARGETFINALIZE','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_XE_TARGETINIT','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_XE_TIMERRUN','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_XETESTING','Preemptive',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PWAIT_HADR_ACTION_COMPLETED','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PWAIT_HADR_CHANGE_NOTIFIER_TERMINATION_SYNC','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PWAIT_HADR_CLUSTER_INTEGRATION','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PWAIT_HADR_FAILOVER_COMPLETED','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PWAIT_HADR_JOIN','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PWAIT_HADR_OFFLINE_COMPLETED','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PWAIT_HADR_ONLINE_COMPLETED','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PWAIT_HADR_POST_ONLINE_COMPLETED','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PWAIT_HADR_SERVER_READY_CONNECTIONS','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PWAIT_HADR_WORKITEM_COMPLETED','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PWAIT_HADRSIM','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PWAIT_RESOURCE_SEMAPHORE_FT_PARALLEL_QUERY_SYNC','Full Text Search',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('QDS_ASYNC_QUEUE','Other',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('QDS_CLEANUP_STALE_QUERIES_TASK_MAIN_LOOP_SLEEP','Other',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('QDS_PERSIST_TASK_MAIN_LOOP_SLEEP','Other',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('QDS_SHUTDOWN_QUEUE','Other',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('QUERY_TRACEOUT','Tracing',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('REDO_THREAD_PENDING_WORK','Other',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('REPL_CACHE_ACCESS','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('REPL_HISTORYCACHE_ACCESS','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('REPL_SCHEMA_ACCESS','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('REPL_TRANFSINFO_ACCESS','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('REPL_TRANHASHTABLE_ACCESS','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('REPL_TRANTEXTINFO_ACCESS','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('REPLICA_WRITES','Replication',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('REQUEST_FOR_DEADLOCK_SEARCH','Idle',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('RESERVED_MEMORY_ALLOCATION_EXT','Memory',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('RESOURCE_SEMAPHORE','Memory',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('RESOURCE_SEMAPHORE_QUERY_COMPILE','Compilation',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_BPOOL_FLUSH','Idle',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_BUFFERPOOL_HELPLW','Idle',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_DBSTARTUP','Idle',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_DCOMSTARTUP','Idle',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_MASTERDBREADY','Idle',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_MASTERMDREADY','Idle',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_MASTERUPGRADED','Idle',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_MEMORYPOOL_ALLOCATEPAGES','Idle',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_MSDBSTARTUP','Idle',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_RETRY_VIRTUALALLOC','Idle',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_SYSTEMTASK','Idle',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_TASK','Idle',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_TEMPDBSTARTUP','Idle',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_WORKSPACE_ALLOCATEPAGE','Idle',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SOS_SCHEDULER_YIELD','CPU',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SOS_WORK_DISPATCHER','Idle',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SP_SERVER_DIAGNOSTICS_SLEEP','Other',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SQLCLR_APPDOMAIN','SQL CLR',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SQLCLR_ASSEMBLY','SQL CLR',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SQLCLR_DEADLOCK_DETECTION','SQL CLR',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SQLCLR_QUANTUM_PUNISHMENT','SQL CLR',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SQLTRACE_BUFFER_FLUSH','Idle',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SQLTRACE_FILE_BUFFER','Tracing',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SQLTRACE_FILE_READ_IO_COMPLETION','Tracing',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SQLTRACE_FILE_WRITE_IO_COMPLETION','Tracing',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SQLTRACE_INCREMENTAL_FLUSH_SLEEP','Idle',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SQLTRACE_PENDING_BUFFER_WRITERS','Tracing',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SQLTRACE_SHUTDOWN','Tracing',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SQLTRACE_WAIT_ENTRIES','Idle',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('THREADPOOL','Worker Thread',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('TRACE_EVTNOTIF','Tracing',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('TRACEWRITE','Tracing',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('TRAN_MARKLATCH_DT','Transaction',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('TRAN_MARKLATCH_EX','Transaction',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('TRAN_MARKLATCH_KP','Transaction',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('TRAN_MARKLATCH_NL','Transaction',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('TRAN_MARKLATCH_SH','Transaction',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('TRAN_MARKLATCH_UP','Transaction',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('TRANSACTION_MUTEX','Transaction',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('UCS_SESSION_REGISTRATION','Other',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('WAIT_FOR_RESULTS','User Wait',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('WAIT_XTP_OFFLINE_CKPT_NEW_LOG','Other',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('WAITFOR','User Wait',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('WRITE_COMPLETION','Other Disk IO',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('WRITELOG','Tran Log IO',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('XACT_OWN_TRANSACTION','Transaction',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('XACT_RECLAIM_SESSION','Transaction',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('XACTLOCKINFO','Transaction',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('XACTWORKSPACE_MUTEX','Transaction',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('XE_DISPATCHER_WAIT','Idle',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('XE_LIVE_TARGET_TVF','Other',1); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('XE_TIMER_EVENT','Idle',1); + END; /* IF SELECT SUM(1) FROM ##WaitCategories <> 527 */ -RAISERROR(N'Filling in implicit conversion info', 0, 1) WITH NOWAIT; -UPDATE b -SET b.implicit_conversion_info = CASE WHEN b.implicit_conversion_info IS NULL - OR CONVERT(NVARCHAR(MAX), b.implicit_conversion_info) = N'' - THEN N'' - ELSE b.implicit_conversion_info - END, - b.cached_execution_parameters = CASE WHEN b.cached_execution_parameters IS NULL - OR CONVERT(NVARCHAR(MAX), b.cached_execution_parameters) = N'' - THEN N'' - ELSE b.cached_execution_parameters - END -FROM #working_warnings AS b -OPTION (RECOMPILE); -/*End implicit conversion and parameter info*/ + IF OBJECT_ID('tempdb..#MasterFiles') IS NOT NULL + DROP TABLE #MasterFiles; + CREATE TABLE #MasterFiles (database_id INT, file_id INT, type_desc NVARCHAR(50), name NVARCHAR(255), physical_name NVARCHAR(255), size BIGINT); + /* Azure SQL Database doesn't have sys.master_files, so we have to build our own. */ + IF ((SERVERPROPERTY('Edition')) = 'SQL Azure' + AND (OBJECT_ID('sys.master_files') IS NULL)) + SET @StringToExecute = 'INSERT INTO #MasterFiles (database_id, file_id, type_desc, name, physical_name, size) SELECT DB_ID(), file_id, type_desc, name, physical_name, size FROM sys.database_files;'; + ELSE + SET @StringToExecute = 'INSERT INTO #MasterFiles (database_id, file_id, type_desc, name, physical_name, size) SELECT database_id, file_id, type_desc, name, physical_name, size FROM sys.master_files;'; + EXEC(@StringToExecute); -/*Begin Missing Index*/ -IF EXISTS ( SELECT 1/0 - FROM #working_warnings AS ww - WHERE ww.missing_index_count > 0 - OR ww.index_spool_cost > 0 - OR ww.index_spool_rows > 0 ) - - BEGIN - - RAISERROR(N'Inserting to #missing_index_xml', 0, 1) WITH NOWAIT; - WITH XMLNAMESPACES ( 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) - INSERT #missing_index_xml - SELECT qp.query_hash, - qp.sql_handle, - c.mg.value('@Impact', 'FLOAT') AS Impact, - c.mg.query('.') AS cmg - FROM #query_plan AS qp - CROSS APPLY qp.query_plan.nodes('//p:MissingIndexes/p:MissingIndexGroup') AS c(mg) - WHERE qp.query_hash IS NOT NULL - AND c.mg.value('@Impact', 'FLOAT') > 70.0 - OPTION (RECOMPILE); + IF @FilterPlansByDatabase IS NOT NULL + BEGIN + IF UPPER(LEFT(@FilterPlansByDatabase,4)) = 'USER' + BEGIN + INSERT INTO #FilterPlansByDatabase (DatabaseID) + SELECT database_id + FROM sys.databases + WHERE [name] NOT IN ('master', 'model', 'msdb', 'tempdb'); + END; + ELSE + BEGIN + SET @FilterPlansByDatabase = @FilterPlansByDatabase + ',' + ;WITH a AS + ( + SELECT CAST(1 AS BIGINT) f, CHARINDEX(',', @FilterPlansByDatabase) t, 1 SEQ + UNION ALL + SELECT t + 1, CHARINDEX(',', @FilterPlansByDatabase, t + 1), SEQ + 1 + FROM a + WHERE CHARINDEX(',', @FilterPlansByDatabase, t + 1) > 0 + ) + INSERT #FilterPlansByDatabase (DatabaseID) + SELECT DISTINCT db.database_id + FROM a + INNER JOIN sys.databases db ON LTRIM(RTRIM(SUBSTRING(@FilterPlansByDatabase, a.f, a.t - a.f))) = db.name + WHERE SUBSTRING(@FilterPlansByDatabase, f, t - f) IS NOT NULL + OPTION (MAXRECURSION 0); + END; + END; - RAISERROR(N'Inserting to #missing_index_schema', 0, 1) WITH NOWAIT; - WITH XMLNAMESPACES ( 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) - INSERT #missing_index_schema - SELECT mix.query_hash, mix.sql_handle, mix.impact, - c.mi.value('@Database', 'NVARCHAR(128)'), - c.mi.value('@Schema', 'NVARCHAR(128)'), - c.mi.value('@Table', 'NVARCHAR(128)'), - c.mi.query('.') - FROM #missing_index_xml AS mix - CROSS APPLY mix.index_xml.nodes('//p:MissingIndex') AS c(mi) - OPTION (RECOMPILE); - - RAISERROR(N'Inserting to #missing_index_usage', 0, 1) WITH NOWAIT; - WITH XMLNAMESPACES ( 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) - INSERT #missing_index_usage - SELECT ms.query_hash, ms.sql_handle, ms.impact, ms.database_name, ms.schema_name, ms.table_name, - c.cg.value('@Usage', 'NVARCHAR(128)'), - c.cg.query('.') - FROM #missing_index_schema ms - CROSS APPLY ms.index_xml.nodes('//p:ColumnGroup') AS c(cg) - OPTION (RECOMPILE); - - RAISERROR(N'Inserting to #missing_index_detail', 0, 1) WITH NOWAIT; - WITH XMLNAMESPACES ( 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) - INSERT #missing_index_detail - SELECT miu.query_hash, - miu.sql_handle, - miu.impact, - miu.database_name, - miu.schema_name, - miu.table_name, - miu.usage, - c.c.value('@Name', 'NVARCHAR(128)') - FROM #missing_index_usage AS miu - CROSS APPLY miu.index_xml.nodes('//p:Column') AS c(c) - OPTION (RECOMPILE); - - RAISERROR(N'Inserting to #missing_index_pretty', 0, 1) WITH NOWAIT; - INSERT #missing_index_pretty - SELECT DISTINCT m.query_hash, m.sql_handle, m.impact, m.database_name, m.schema_name, m.table_name - , STUFF(( SELECT DISTINCT N', ' + ISNULL(m2.column_name, '') AS column_name - FROM #missing_index_detail AS m2 - WHERE m2.usage = 'EQUALITY' - AND m.query_hash = m2.query_hash - AND m.sql_handle = m2.sql_handle - AND m.impact = m2.impact - AND m.database_name = m2.database_name - AND m.schema_name = m2.schema_name - AND m.table_name = m2.table_name - FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 2, N'') AS equality - , STUFF(( SELECT DISTINCT N', ' + ISNULL(m2.column_name, '') AS column_name - FROM #missing_index_detail AS m2 - WHERE m2.usage = 'INEQUALITY' - AND m.query_hash = m2.query_hash - AND m.sql_handle = m2.sql_handle - AND m.impact = m2.impact - AND m.database_name = m2.database_name - AND m.schema_name = m2.schema_name - AND m.table_name = m2.table_name - FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 2, N'') AS inequality - , STUFF(( SELECT DISTINCT N', ' + ISNULL(m2.column_name, '') AS column_name - FROM #missing_index_detail AS m2 - WHERE m2.usage = 'INCLUDE' - AND m.query_hash = m2.query_hash - AND m.sql_handle = m2.sql_handle - AND m.impact = m2.impact - AND m.database_name = m2.database_name - AND m.schema_name = m2.schema_name - AND m.table_name = m2.table_name - FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 2, N'') AS [include], - 0 AS is_spool - FROM #missing_index_detail AS m - GROUP BY m.query_hash, m.sql_handle, m.impact, m.database_name, m.schema_name, m.table_name - OPTION (RECOMPILE); + IF OBJECT_ID('tempdb..#ReadableDBs') IS NOT NULL + DROP TABLE #ReadableDBs; + CREATE TABLE #ReadableDBs ( + database_id INT + ); - RAISERROR(N'Inserting to #index_spool_ugly', 0, 1) WITH NOWAIT; - WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) - INSERT #index_spool_ugly - (query_hash, sql_handle, impact, database_name, schema_name, table_name, equality, inequality, include) - SELECT r.query_hash, - r.sql_handle, - (c.n.value('@EstimateIO', 'FLOAT') + (c.n.value('@EstimateCPU', 'FLOAT'))) - / ( 1 * NULLIF(ww.query_cost, 0)) * 100 AS impact, - o.n.value('@Database', 'NVARCHAR(128)') AS output_database, - o.n.value('@Schema', 'NVARCHAR(128)') AS output_schema, - o.n.value('@Table', 'NVARCHAR(128)') AS output_table, - k.n.value('@Column', 'NVARCHAR(128)') AS range_column, - e.n.value('@Column', 'NVARCHAR(128)') AS expression_column, - o.n.value('@Column', 'NVARCHAR(128)') AS output_column - FROM #relop AS r - JOIN #working_warnings AS ww - ON ww.query_hash = r.query_hash - CROSS APPLY r.relop.nodes('/p:RelOp') AS c(n) - CROSS APPLY r.relop.nodes('/p:RelOp/p:OutputList/p:ColumnReference') AS o(n) - OUTER APPLY r.relop.nodes('/p:RelOp/p:Spool/p:SeekPredicateNew/p:SeekKeys/p:Prefix/p:RangeColumns/p:ColumnReference') AS k(n) - OUTER APPLY r.relop.nodes('/p:RelOp/p:Spool/p:SeekPredicateNew/p:SeekKeys/p:Prefix/p:RangeExpressions/p:ColumnReference') AS e(n) - WHERE r.relop.exist('/p:RelOp[@PhysicalOp="Index Spool" and @LogicalOp="Eager Spool"]') = 1 + IF EXISTS (SELECT * FROM sys.all_objects o WHERE o.name = 'dm_hadr_database_replica_states') + BEGIN + RAISERROR('Checking for Read intent databases to exclude',0,0) WITH NOWAIT; + + SET @StringToExecute = 'INSERT INTO #ReadableDBs (database_id) SELECT DBs.database_id FROM sys.databases DBs INNER JOIN sys.availability_replicas Replicas ON DBs.replica_id = Replicas.replica_id WHERE replica_server_name NOT IN (SELECT DISTINCT primary_replica FROM sys.dm_hadr_availability_group_states States) AND Replicas.secondary_role_allow_connections_desc = ''READ_ONLY'' AND replica_server_name = @@SERVERNAME;'; + EXEC(@StringToExecute); - RAISERROR(N'Inserting to spools to #missing_index_pretty', 0, 1) WITH NOWAIT; - INSERT #missing_index_pretty - (query_hash, sql_handle, impact, database_name, schema_name, table_name, equality, inequality, include, is_spool) - SELECT DISTINCT - isu.query_hash, - isu.sql_handle, - isu.impact, - isu.database_name, - isu.schema_name, - isu.table_name - , STUFF(( SELECT DISTINCT N', ' + ISNULL(isu2.equality, '') AS column_name - FROM #index_spool_ugly AS isu2 - WHERE isu2.equality IS NOT NULL - AND isu.query_hash = isu2.query_hash - AND isu.sql_handle = isu2.sql_handle - AND isu.impact = isu2.impact - AND isu.database_name = isu2.database_name - AND isu.schema_name = isu2.schema_name - AND isu.table_name = isu2.table_name - FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 2, N'') AS equality - , STUFF(( SELECT DISTINCT N', ' + ISNULL(isu2.inequality, '') AS column_name - FROM #index_spool_ugly AS isu2 - WHERE isu2.inequality IS NOT NULL - AND isu.query_hash = isu2.query_hash - AND isu.sql_handle = isu2.sql_handle - AND isu.impact = isu2.impact - AND isu.database_name = isu2.database_name - AND isu.schema_name = isu2.schema_name - AND isu.table_name = isu2.table_name - FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 2, N'') AS inequality - , STUFF(( SELECT DISTINCT N', ' + ISNULL(isu2.include, '') AS column_name - FROM #index_spool_ugly AS isu2 - WHERE isu2.include IS NOT NULL - AND isu.query_hash = isu2.query_hash - AND isu.sql_handle = isu2.sql_handle - AND isu.impact = isu2.impact - AND isu.database_name = isu2.database_name - AND isu.schema_name = isu2.schema_name - AND isu.table_name = isu2.table_name - FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 2, N'') AS include, - 1 AS is_spool - FROM #index_spool_ugly AS isu + END + + DECLARE @v DECIMAL(6,2), + @build INT, + @memGrantSortSupported BIT = 1; + RAISERROR (N'Determining SQL Server version.',0,1) WITH NOWAIT; - RAISERROR(N'Updating missing index information', 0, 1) WITH NOWAIT; - WITH missing AS ( - SELECT DISTINCT - mip.query_hash, - mip.sql_handle, - N'' - AS full_details - FROM #missing_index_pretty AS mip - GROUP BY mip.query_hash, mip.sql_handle, mip.impact - ) - UPDATE ww - SET ww.missing_indexes = m.full_details - FROM #working_warnings AS ww - JOIN missing AS m - ON m.sql_handle = ww.sql_handle - OPTION (RECOMPILE); + INSERT INTO #checkversion (version) + SELECT CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)) + OPTION (RECOMPILE); - RAISERROR(N'Filling in missing index blanks', 0, 1) WITH NOWAIT; - UPDATE ww - SET ww.missing_indexes = - CASE WHEN ww.missing_indexes IS NULL - THEN '' - ELSE ww.missing_indexes - END - FROM #working_warnings AS ww + + SELECT @v = common_version , + @build = build + FROM #checkversion OPTION (RECOMPILE); -END -/*End Missing Index*/ + IF (@v < 11) + OR (@v = 11 AND @build < 6020) + OR (@v = 12 AND @build < 5000) + OR (@v = 13 AND @build < 1601) + SET @memGrantSortSupported = 0; -RAISERROR(N'General query dispositions: frequent executions, long running, etc.', 0, 1) WITH NOWAIT; + IF EXISTS (SELECT * FROM sys.all_objects WHERE name = 'dm_exec_query_statistics_xml') + AND ((@v = 13 AND @build >= 5337) /* This DMF causes assertion errors: https://support.microsoft.com/en-us/help/4490136/fix-assertion-error-occurs-when-you-use-sys-dm-exec-query-statistics-x */ + OR (@v = 14 AND @build >= 3162) + OR (@v >= 15) + OR (@v <= 12)) /* Azure */ + SET @dm_exec_query_statistics_xml = 1; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET b.frequent_execution = CASE WHEN wm.xpm > @execution_threshold THEN 1 END , - b.near_parallel = CASE WHEN b.query_cost BETWEEN @ctp * (1 - (@ctp_threshold_pct / 100.0)) AND @ctp THEN 1 END, - b.long_running = CASE WHEN wm.avg_duration > @long_running_query_warning_seconds THEN 1 - WHEN wm.max_duration > @long_running_query_warning_seconds THEN 1 - WHEN wm.avg_cpu_time > @long_running_query_warning_seconds THEN 1 - WHEN wm.max_cpu_time > @long_running_query_warning_seconds THEN 1 END, - b.is_key_lookup_expensive = CASE WHEN b.query_cost >= (@ctp / 2) AND b.key_lookup_cost >= b.query_cost * .5 THEN 1 END, - b.is_sort_expensive = CASE WHEN b.query_cost >= (@ctp / 2) AND b.sort_cost >= b.query_cost * .5 THEN 1 END, - b.is_remote_query_expensive = CASE WHEN b.remote_query_cost >= b.query_cost * .05 THEN 1 END, - b.is_unused_grant = CASE WHEN percent_memory_grant_used <= @memory_grant_warning_percent AND min_query_max_used_memory > @min_memory_per_query THEN 1 END, - b.long_running_low_cpu = CASE WHEN wm.avg_duration > wm.avg_cpu_time * 4 AND avg_cpu_time < 500. THEN 1 END, - b.low_cost_high_cpu = CASE WHEN b.query_cost < 10 AND wm.avg_cpu_time > 5000. THEN 1 END, - b.is_spool_expensive = CASE WHEN b.query_cost > (@ctp / 2) AND b.index_spool_cost >= b.query_cost * .1 THEN 1 END, - b.is_spool_more_rows = CASE WHEN b.index_spool_rows >= wm.min_rowcount THEN 1 END, - b.is_bad_estimate = CASE WHEN wm.avg_rowcount > 0 AND (b.estimated_rows * 1000 < wm.avg_rowcount OR b.estimated_rows > wm.avg_rowcount * 1000) THEN 1 END, - b.is_big_log = CASE WHEN wm.avg_log_bytes_used >= (@log_size_mb / 2.) THEN 1 END, - b.is_big_tempdb = CASE WHEN wm.avg_tempdb_space_used >= (@avg_tempdb_data_file / 2.) THEN 1 END -FROM #working_warnings AS b -JOIN #working_metrics AS wm -ON b.plan_id = wm.plan_id -AND b.query_id = wm.query_id -JOIN #working_plan_text AS wpt -ON b.plan_id = wpt.plan_id -AND b.query_id = wpt.query_id -OPTION (RECOMPILE); + SET @StockWarningHeader = ' 0 THEN ', Missing Indexes (' + CAST(b.missing_index_count AS NVARCHAR(3)) + ')' ELSE '' END + - CASE WHEN b.unmatched_index_count > 0 THEN ', Unmatched Indexes (' + CAST(b.unmatched_index_count AS NVARCHAR(3)) + ')' ELSE '' END + - CASE WHEN b.is_cursor = 1 THEN ', Cursor' - + CASE WHEN b.is_optimistic_cursor = 1 THEN '; optimistic' ELSE '' END - + CASE WHEN b.is_forward_only_cursor = 0 THEN '; not forward only' ELSE '' END - + CASE WHEN b.is_cursor_dynamic = 1 THEN '; dynamic' ELSE '' END - + CASE WHEN b.is_fast_forward_cursor = 1 THEN '; fast forward' ELSE '' END - ELSE '' END + - CASE WHEN b.is_parallel = 1 THEN ', Parallel' ELSE '' END + - CASE WHEN b.near_parallel = 1 THEN ', Nearly Parallel' ELSE '' END + - CASE WHEN b.frequent_execution = 1 THEN ', Frequent Execution' ELSE '' END + - CASE WHEN b.plan_warnings = 1 THEN ', Plan Warnings' ELSE '' END + - CASE WHEN b.parameter_sniffing = 1 THEN ', Parameter Sniffing' ELSE '' END + - CASE WHEN b.long_running = 1 THEN ', Long Running Query' ELSE '' END + - CASE WHEN b.downlevel_estimator = 1 THEN ', Downlevel CE' ELSE '' END + - CASE WHEN b.implicit_conversions = 1 THEN ', Implicit Conversions' ELSE '' END + - CASE WHEN b.plan_multiple_plans = 1 THEN ', Multiple Plans' ELSE '' END + - CASE WHEN b.is_trivial = 1 THEN ', Trivial Plans' ELSE '' END + - CASE WHEN b.is_forced_serial = 1 THEN ', Forced Serialization' ELSE '' END + - CASE WHEN b.is_key_lookup_expensive = 1 THEN ', Expensive Key Lookup' ELSE '' END + - CASE WHEN b.is_remote_query_expensive = 1 THEN ', Expensive Remote Query' ELSE '' END + - CASE WHEN b.trace_flags_session IS NOT NULL THEN ', Session Level Trace Flag(s) Enabled: ' + b.trace_flags_session ELSE '' END + - CASE WHEN b.is_unused_grant = 1 THEN ', Unused Memory Grant' ELSE '' END + - CASE WHEN b.function_count > 0 THEN ', Calls ' + CONVERT(VARCHAR(10), b.function_count) + ' function(s)' ELSE '' END + - CASE WHEN b.clr_function_count > 0 THEN ', Calls ' + CONVERT(VARCHAR(10), b.clr_function_count) + ' CLR function(s)' ELSE '' END + - CASE WHEN b.is_table_variable = 1 THEN ', Table Variables' ELSE '' END + - CASE WHEN b.no_stats_warning = 1 THEN ', Columns With No Statistics' ELSE '' END + - CASE WHEN b.relop_warnings = 1 THEN ', Operator Warnings' ELSE '' END + - CASE WHEN b.is_table_scan = 1 THEN ', Table Scans' ELSE '' END + - CASE WHEN b.backwards_scan = 1 THEN ', Backwards Scans' ELSE '' END + - CASE WHEN b.forced_index = 1 THEN ', Forced Indexes' ELSE '' END + - CASE WHEN b.forced_seek = 1 THEN ', Forced Seeks' ELSE '' END + - CASE WHEN b.forced_scan = 1 THEN ', Forced Scans' ELSE '' END + - CASE WHEN b.columnstore_row_mode = 1 THEN ', ColumnStore Row Mode ' ELSE '' END + - CASE WHEN b.is_computed_scalar = 1 THEN ', Computed Column UDF ' ELSE '' END + - CASE WHEN b.is_sort_expensive = 1 THEN ', Expensive Sort' ELSE '' END + - CASE WHEN b.is_computed_filter = 1 THEN ', Filter UDF' ELSE '' END + - CASE WHEN b.index_ops >= 5 THEN ', >= 5 Indexes Modified' ELSE '' END + - CASE WHEN b.is_row_level = 1 THEN ', Row Level Security' ELSE '' END + - CASE WHEN b.is_spatial = 1 THEN ', Spatial Index' ELSE '' END + - CASE WHEN b.index_dml = 1 THEN ', Index DML' ELSE '' END + - CASE WHEN b.table_dml = 1 THEN ', Table DML' ELSE '' END + - CASE WHEN b.low_cost_high_cpu = 1 THEN ', Low Cost High CPU' ELSE '' END + - CASE WHEN b.long_running_low_cpu = 1 THEN + ', Long Running With Low CPU' ELSE '' END + - CASE WHEN b.stale_stats = 1 THEN + ', Statistics used have > 100k modifications in the last 7 days' ELSE '' END + - CASE WHEN b.is_adaptive = 1 THEN + ', Adaptive Joins' ELSE '' END + - CASE WHEN b.is_spool_expensive = 1 THEN + ', Expensive Index Spool' ELSE '' END + - CASE WHEN b.is_spool_more_rows = 1 THEN + ', Large Index Row Spool' ELSE '' END + - CASE WHEN b.is_bad_estimate = 1 THEN + ', Row estimate mismatch' ELSE '' END + - CASE WHEN b.is_big_log = 1 THEN + ', High log use' ELSE '' END + - CASE WHEN b.is_big_tempdb = 1 THEN ', High tempdb use' ELSE '' END + - CASE WHEN b.is_paul_white_electric = 1 THEN ', SWITCH!' ELSE '' END + - CASE WHEN b.is_row_goal = 1 THEN ', Row Goals' ELSE '' END + - CASE WHEN b.is_mstvf = 1 THEN ', MSTVFs' ELSE '' END + - CASE WHEN b.is_mm_join = 1 THEN ', Many to Many Merge' ELSE '' END + - CASE WHEN b.is_nonsargable = 1 THEN ', non-SARGables' ELSE '' END - , 2, 200000) -FROM #working_warnings b -OPTION (RECOMPILE); + SELECT @StockWarningFooter = @StockWarningFooter + @LineFeed + @LineFeed + '-- ?>', + @StockDetailsHeader = @StockDetailsHeader + ''; + /* Get the instance name to use as a Perfmon counter prefix. */ + IF CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) = 'SQL Azure' + SELECT TOP 1 @ServiceName = LEFT(object_name, (CHARINDEX(':', object_name) - 1)) + FROM sys.dm_os_performance_counters; + ELSE + BEGIN + SET @StringToExecute = 'INSERT INTO #PerfmonStats(object_name, Pass, SampleTime, counter_name, cntr_type) SELECT CASE WHEN @@SERVICENAME = ''MSSQLSERVER'' THEN ''SQLServer'' ELSE ''MSSQL$'' + @@SERVICENAME END, 0, SYSDATETIMEOFFSET(), ''stuffing'', 0 ;'; + EXEC(@StringToExecute); + SELECT @ServiceName = object_name FROM #PerfmonStats; + DELETE #PerfmonStats; + END; -END; -END TRY -BEGIN CATCH - RAISERROR (N'Failure generating warnings.', 0,1) WITH NOWAIT; + /* Build a list of queries that were run in the last 10 seconds. + We're looking for the death-by-a-thousand-small-cuts scenario + where a query is constantly running, and it doesn't have that + big of an impact individually, but it has a ton of impact + overall. We're going to build this list, and then after we + finish our @Seconds sample, we'll compare our plan cache to + this list to see what ran the most. */ - IF @sql_select IS NOT NULL + /* Populate #QueryStats. SQL 2005 doesn't have query hash or query plan hash. */ + IF @CheckProcedureCache = 1 + BEGIN + RAISERROR('@CheckProcedureCache = 1, capturing first pass of plan cache',10,1) WITH NOWAIT; + IF @@VERSION LIKE 'Microsoft SQL Server 2005%' + BEGIN + IF @FilterPlansByDatabase IS NULL + BEGIN + SET @StringToExecute = N'INSERT INTO #QueryStats ([sql_handle], Pass, SampleTime, statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, Points) + SELECT [sql_handle], 1 AS Pass, SYSDATETIMEOFFSET(), statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, NULL AS query_hash, NULL AS query_plan_hash, 0 + FROM sys.dm_exec_query_stats qs + WHERE qs.last_execution_time >= (DATEADD(ss, -10, SYSDATETIMEOFFSET()));'; + END; + ELSE + BEGIN + SET @StringToExecute = N'INSERT INTO #QueryStats ([sql_handle], Pass, SampleTime, statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, Points) + SELECT [sql_handle], 1 AS Pass, SYSDATETIMEOFFSET(), statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, NULL AS query_hash, NULL AS query_plan_hash, 0 + FROM sys.dm_exec_query_stats qs + CROSS APPLY sys.dm_exec_plan_attributes(qs.plan_handle) AS attr + INNER JOIN #FilterPlansByDatabase dbs ON CAST(attr.value AS INT) = dbs.DatabaseID + WHERE qs.last_execution_time >= (DATEADD(ss, -10, SYSDATETIMEOFFSET())) + AND attr.attribute = ''dbid'';'; + END; + END; + ELSE + BEGIN + IF @FilterPlansByDatabase IS NULL + BEGIN + SET @StringToExecute = N'INSERT INTO #QueryStats ([sql_handle], Pass, SampleTime, statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, Points) + SELECT [sql_handle], 1 AS Pass, SYSDATETIMEOFFSET(), statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, 0 + FROM sys.dm_exec_query_stats qs + WHERE qs.last_execution_time >= (DATEADD(ss, -10, SYSDATETIMEOFFSET()));'; + END; + ELSE + BEGIN + SET @StringToExecute = N'INSERT INTO #QueryStats ([sql_handle], Pass, SampleTime, statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, Points) + SELECT [sql_handle], 1 AS Pass, SYSDATETIMEOFFSET(), statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, 0 + FROM sys.dm_exec_query_stats qs + CROSS APPLY sys.dm_exec_plan_attributes(qs.plan_handle) AS attr + INNER JOIN #FilterPlansByDatabase dbs ON CAST(attr.value AS INT) = dbs.DatabaseID + WHERE qs.last_execution_time >= (DATEADD(ss, -10, SYSDATETIMEOFFSET())) + AND attr.attribute = ''dbid'';'; + END; + END; + EXEC(@StringToExecute); + + /* Get the totals for the entire plan cache */ + INSERT INTO #QueryStats (Pass, SampleTime, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time) + SELECT -1 AS Pass, SYSDATETIMEOFFSET(), SUM(execution_count), SUM(total_worker_time), SUM(total_physical_reads), SUM(total_logical_writes), SUM(total_logical_reads), SUM(total_clr_time), SUM(total_elapsed_time), MIN(creation_time) + FROM sys.dm_exec_query_stats qs; + END; /*IF @CheckProcedureCache = 1 */ + + + IF EXISTS (SELECT * + FROM tempdb.sys.all_objects obj + INNER JOIN tempdb.sys.all_columns col1 ON obj.object_id = col1.object_id AND col1.name = 'object_name' + INNER JOIN tempdb.sys.all_columns col2 ON obj.object_id = col2.object_id AND col2.name = 'counter_name' + INNER JOIN tempdb.sys.all_columns col3 ON obj.object_id = col3.object_id AND col3.name = 'instance_name' + WHERE obj.name LIKE '%CustomPerfmonCounters%') BEGIN - SET @msg = N'Last @sql_select: ' + @sql_select; - RAISERROR(@msg, 0, 1) WITH NOWAIT; + SET @StringToExecute = 'INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) SELECT [object_name],[counter_name],[instance_name] FROM #CustomPerfmonCounters'; + EXEC(@StringToExecute); + END; + ELSE + BEGIN + /* Add our default Perfmon counters */ + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Forwarded Records/sec', NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Page compression attempts/sec', NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Page Splits/sec', NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Skipped Ghosted Records/sec', NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Table Lock Escalations/sec', NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Worktables Created/sec', NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Availability Group','Active Hadr Threads','_Total'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Availability Replica','Bytes Received from Replica/sec','_Total'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Availability Replica','Bytes Sent to Replica/sec','_Total'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Availability Replica','Bytes Sent to Transport/sec','_Total'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Availability Replica','Flow Control Time (ms/sec)','_Total'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Availability Replica','Flow Control/sec','_Total'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Availability Replica','Resent Messages/sec','_Total'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Availability Replica','Sends to Replica/sec','_Total'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Page life expectancy', NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Page reads/sec', NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Page writes/sec', NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Readahead pages/sec', NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Target pages', NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Total pages', NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Databases','', NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Active Transactions','_Total'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Database Flow Control Delay', '_Total'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Database Flow Controls/sec', '_Total'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Group Commit Time', '_Total'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Group Commits/Sec', '_Total'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Log Apply Pending Queue', '_Total'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Log Apply Ready Queue', '_Total'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Log Compression Cache misses/sec', '_Total'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Log remaining for undo', '_Total'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Log Send Queue', '_Total'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Recovery Queue', '_Total'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Redo blocked/sec', '_Total'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Redo Bytes Remaining', '_Total'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Redone Bytes/sec', '_Total'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Databases','Log Bytes Flushed/sec', '_Total'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Databases','Log Growths', '_Total'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Databases','Log Pool LogWriter Pushes/sec', '_Total'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Databases','Log Shrinks', '_Total'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Databases','Transactions/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Databases','Write Transactions/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Databases','XTP Memory Used (KB)',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Exec Statistics','Distributed Query', 'Execs in progress'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Exec Statistics','DTC calls', 'Execs in progress'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Exec Statistics','Extended Procedures', 'Execs in progress'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Exec Statistics','OLEDB calls', 'Execs in progress'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':General Statistics','Active Temp Tables', NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':General Statistics','Logins/sec', NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':General Statistics','Logouts/sec', NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':General Statistics','Mars Deadlocks', NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':General Statistics','Processes blocked', NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Locks','Number of Deadlocks/sec', NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Memory Manager','Memory Grants Pending', NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Errors','Errors/sec', '_Total'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','Batch Requests/sec', NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','Forced Parameterizations/sec', NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','Guided plan executions/sec', NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','SQL Attention rate', NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','SQL Compilations/sec', NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','SQL Re-Compilations/sec', NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Workload Group Stats','Query optimizations/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Workload Group Stats','Suboptimal plans/sec',NULL); + /* Below counters added by Jefferson Elias */ + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Worktables From Cache Base',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Worktables From Cache Ratio',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Database pages',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Free pages',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Stolen pages',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Memory Manager','Granted Workspace Memory (KB)',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Memory Manager','Maximum Workspace Memory (KB)',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Memory Manager','Target Server Memory (KB)',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Memory Manager','Total Server Memory (KB)',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Buffer cache hit ratio',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Buffer cache hit ratio base',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Checkpoint pages/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Free list stalls/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Lazy writes/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','Auto-Param Attempts/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','Failed Auto-Params/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','Safe Auto-Params/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','Unsafe Auto-Params/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Workfiles Created/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':General Statistics','User Connections',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Latches','Average Latch Wait Time (ms)',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Latches','Average Latch Wait Time Base',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Latches','Latch Waits/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Latches','Total Latch Wait Time (ms)',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Locks','Average Wait Time (ms)',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Locks','Average Wait Time Base',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Locks','Lock Requests/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Locks','Lock Timeouts/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Locks','Lock Wait Time (ms)',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Locks','Lock Waits/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Transactions','Longest Transaction Running Time',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Full Scans/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Index Searches/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Page lookups/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Cursor Manager by Type','Active cursors',NULL); + /* Below counters are for In-Memory OLTP (Hekaton), which have a different naming convention. + And yes, they actually hard-coded the version numbers into the counters, and SQL 2019 still says 2017, oddly. + For why, see: https://connect.microsoft.com/SQLServer/feedback/details/817216/xtp-perfmon-counters-should-appear-under-sql-server-perfmon-counter-group + */ + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP Cursors','Expired rows removed/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP Cursors','Expired rows touched/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP Garbage Collection','Rows processed/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP IO Governor','Io Issued/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP Phantom Processor','Phantom expired rows touched/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP Phantom Processor','Phantom rows touched/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP Transaction Log','Log bytes written/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP Transaction Log','Log records written/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP Transactions','Transactions aborted by user/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP Transactions','Transactions aborted/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP Transactions','Transactions created/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP Cursors','Expired rows removed/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP Cursors','Expired rows touched/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP Garbage Collection','Rows processed/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP IO Governor','Io Issued/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP Phantom Processor','Phantom expired rows touched/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP Phantom Processor','Phantom rows touched/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP Transaction Log','Log bytes written/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP Transaction Log','Log records written/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP Transactions','Transactions aborted by user/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP Transactions','Transactions aborted/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP Transactions','Transactions created/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP Cursors','Expired rows removed/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP Cursors','Expired rows touched/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP Garbage Collection','Rows processed/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP IO Governor','Io Issued/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP Phantom Processor','Phantom expired rows touched/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP Phantom Processor','Phantom rows touched/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP Transaction Log','Log bytes written/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP Transaction Log','Log records written/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP Transactions','Transactions aborted by user/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP Transactions','Transactions aborted/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP Transactions','Transactions created/sec',NULL); END; - SELECT @msg = @DatabaseName + N' database failed to process. ' + ERROR_MESSAGE(), @error_severity = ERROR_SEVERITY(), @error_state = ERROR_STATE(); - RAISERROR (@msg, @error_severity, @error_state) WITH NOWAIT; - - - WHILE @@TRANCOUNT > 0 - ROLLBACK; + /* Populate #FileStats, #PerfmonStats, #WaitStats with DMV data. + After we finish doing our checks, we'll take another sample and compare them. */ + RAISERROR('Capturing first pass of wait stats, perfmon counters, file stats',10,1) WITH NOWAIT; + INSERT #WaitStats(Pass, SampleTime, wait_type, wait_time_ms, signal_wait_time_ms, waiting_tasks_count) + SELECT + x.Pass, + x.SampleTime, + x.wait_type, + SUM(x.sum_wait_time_ms) AS sum_wait_time_ms, + SUM(x.sum_signal_wait_time_ms) AS sum_signal_wait_time_ms, + SUM(x.sum_waiting_tasks) AS sum_waiting_tasks + FROM ( + SELECT + 1 AS Pass, + CASE @Seconds WHEN 0 THEN @StartSampleTime ELSE SYSDATETIMEOFFSET() END AS SampleTime, + owt.wait_type, + CASE @Seconds WHEN 0 THEN 0 ELSE SUM(owt.wait_duration_ms) OVER (PARTITION BY owt.wait_type, owt.session_id) + - CASE WHEN @Seconds = 0 THEN 0 ELSE (@Seconds * 1000) END END AS sum_wait_time_ms, + 0 AS sum_signal_wait_time_ms, + 0 AS sum_waiting_tasks + FROM sys.dm_os_waiting_tasks owt + WHERE owt.session_id > 50 + AND owt.wait_duration_ms >= CASE @Seconds WHEN 0 THEN 0 ELSE @Seconds * 1000 END + UNION ALL + SELECT + 1 AS Pass, + CASE @Seconds WHEN 0 THEN @StartSampleTime ELSE SYSDATETIMEOFFSET() END AS SampleTime, + os.wait_type, + CASE @Seconds WHEN 0 THEN 0 ELSE SUM(os.wait_time_ms) OVER (PARTITION BY os.wait_type) END AS sum_wait_time_ms, + CASE @Seconds WHEN 0 THEN 0 ELSE SUM(os.signal_wait_time_ms) OVER (PARTITION BY os.wait_type ) END AS sum_signal_wait_time_ms, + CASE @Seconds WHEN 0 THEN 0 ELSE SUM(os.waiting_tasks_count) OVER (PARTITION BY os.wait_type) END AS sum_waiting_tasks + FROM sys.dm_os_wait_stats os + ) x + WHERE NOT EXISTS + ( + SELECT * + FROM ##WaitCategories AS wc + WHERE wc.WaitType = x.wait_type + AND wc.Ignorable = 1 + ) + GROUP BY x.Pass, x.SampleTime, x.wait_type + ORDER BY sum_wait_time_ms DESC; - RETURN; -END CATCH; + INSERT INTO #FileStats (Pass, SampleTime, DatabaseID, FileID, DatabaseName, FileLogicalName, SizeOnDiskMB, io_stall_read_ms , + num_of_reads, [bytes_read] , io_stall_write_ms,num_of_writes, [bytes_written], PhysicalName, TypeDesc) + SELECT + 1 AS Pass, + CASE @Seconds WHEN 0 THEN @StartSampleTime ELSE SYSDATETIMEOFFSET() END AS SampleTime, + mf.[database_id], + mf.[file_id], + DB_NAME(vfs.database_id) AS [db_name], + mf.name + N' [' + mf.type_desc COLLATE SQL_Latin1_General_CP1_CI_AS + N']' AS file_logical_name , + CAST(( ( vfs.size_on_disk_bytes / 1024.0 ) / 1024.0 ) AS INT) AS size_on_disk_mb , + CASE @Seconds WHEN 0 THEN 0 ELSE vfs.io_stall_read_ms END , + CASE @Seconds WHEN 0 THEN 0 ELSE vfs.num_of_reads END , + CASE @Seconds WHEN 0 THEN 0 ELSE vfs.[num_of_bytes_read] END , + CASE @Seconds WHEN 0 THEN 0 ELSE vfs.io_stall_write_ms END , + CASE @Seconds WHEN 0 THEN 0 ELSE vfs.num_of_writes END , + CASE @Seconds WHEN 0 THEN 0 ELSE vfs.[num_of_bytes_written] END , + mf.physical_name, + mf.type_desc + FROM sys.dm_io_virtual_file_stats (NULL, NULL) AS vfs + INNER JOIN #MasterFiles AS mf ON vfs.file_id = mf.file_id + AND vfs.database_id = mf.database_id + WHERE vfs.num_of_reads > 0 + OR vfs.num_of_writes > 0; -BEGIN TRY -BEGIN + INSERT INTO #PerfmonStats (Pass, SampleTime, [object_name],[counter_name],[instance_name],[cntr_value],[cntr_type]) + SELECT 1 AS Pass, + CASE @Seconds WHEN 0 THEN @StartSampleTime ELSE SYSDATETIMEOFFSET() END AS SampleTime, RTRIM(dmv.object_name), RTRIM(dmv.counter_name), RTRIM(dmv.instance_name), CASE @Seconds WHEN 0 THEN 0 ELSE dmv.cntr_value END, dmv.cntr_type + FROM #PerfmonCounters counters + INNER JOIN sys.dm_os_performance_counters dmv ON counters.counter_name COLLATE SQL_Latin1_General_CP1_CI_AS = RTRIM(dmv.counter_name) COLLATE SQL_Latin1_General_CP1_CI_AS + AND counters.[object_name] COLLATE SQL_Latin1_General_CP1_CI_AS = RTRIM(dmv.[object_name]) COLLATE SQL_Latin1_General_CP1_CI_AS + AND (counters.[instance_name] IS NULL OR counters.[instance_name] COLLATE SQL_Latin1_General_CP1_CI_AS = RTRIM(dmv.[instance_name]) COLLATE SQL_Latin1_General_CP1_CI_AS); -RAISERROR(N'Checking for parameter sniffing symptoms', 0, 1) WITH NOWAIT; + /* For Github #2743: */ + CREATE TABLE #TempdbOperationalStats (object_id BIGINT PRIMARY KEY CLUSTERED, + forwarded_fetch_count BIGINT); + INSERT INTO #TempdbOperationalStats (object_id, forwarded_fetch_count) + SELECT object_id, forwarded_fetch_count + FROM tempdb.sys.dm_db_index_operational_stats(DB_ID('tempdb'), NULL, NULL, NULL) os + WHERE os.database_id = DB_ID('tempdb') + AND os.forwarded_fetch_count > 100; -UPDATE b -SET b.parameter_sniffing_symptoms = -CASE WHEN b.count_executions < 2 THEN 'Too few executions to compare (< 2).' - ELSE - SUBSTRING( - /*Duration*/ - CASE WHEN (b.min_duration * 100) < (b.avg_duration) THEN ', Fast sometimes' ELSE '' END + - CASE WHEN (b.max_duration) > (b.avg_duration * 100) THEN ', Slow sometimes' ELSE '' END + - CASE WHEN (b.last_duration * 100) < (b.avg_duration) THEN ', Fast last run' ELSE '' END + - CASE WHEN (b.last_duration) > (b.avg_duration * 100) THEN ', Slow last run' ELSE '' END + - /*CPU*/ - CASE WHEN (b.min_cpu_time / b.avg_dop) * 100 < (b.avg_cpu_time / b.avg_dop) THEN ', Low CPU sometimes' ELSE '' END + - CASE WHEN (b.max_cpu_time / b.max_dop) > (b.avg_cpu_time / b.avg_dop) * 100 THEN ', High CPU sometimes' ELSE '' END + - CASE WHEN (b.last_cpu_time / b.last_dop) * 100 < (b.avg_cpu_time / b.avg_dop) THEN ', Low CPU last run' ELSE '' END + - CASE WHEN (b.last_cpu_time / b.last_dop) > (b.avg_cpu_time / b.avg_dop) * 100 THEN ', High CPU last run' ELSE '' END + - /*Logical Reads*/ - CASE WHEN (b.min_logical_io_reads * 100) < (b.avg_logical_io_reads) THEN ', Low reads sometimes' ELSE '' END + - CASE WHEN (b.max_logical_io_reads) > (b.avg_logical_io_reads * 100) THEN ', High reads sometimes' ELSE '' END + - CASE WHEN (b.last_logical_io_reads * 100) < (b.avg_logical_io_reads) THEN ', Low reads last run' ELSE '' END + - CASE WHEN (b.last_logical_io_reads) > (b.avg_logical_io_reads * 100) THEN ', High reads last run' ELSE '' END + - /*Logical Writes*/ - CASE WHEN (b.min_logical_io_writes * 100) < (b.avg_logical_io_writes) THEN ', Low writes sometimes' ELSE '' END + - CASE WHEN (b.max_logical_io_writes) > (b.avg_logical_io_writes * 100) THEN ', High writes sometimes' ELSE '' END + - CASE WHEN (b.last_logical_io_writes * 100) < (b.avg_logical_io_writes) THEN ', Low writes last run' ELSE '' END + - CASE WHEN (b.last_logical_io_writes) > (b.avg_logical_io_writes * 100) THEN ', High writes last run' ELSE '' END + - /*Physical Reads*/ - CASE WHEN (b.min_physical_io_reads * 100) < (b.avg_physical_io_reads) THEN ', Low physical reads sometimes' ELSE '' END + - CASE WHEN (b.max_physical_io_reads) > (b.avg_physical_io_reads * 100) THEN ', High physical reads sometimes' ELSE '' END + - CASE WHEN (b.last_physical_io_reads * 100) < (b.avg_physical_io_reads) THEN ', Low physical reads last run' ELSE '' END + - CASE WHEN (b.last_physical_io_reads) > (b.avg_physical_io_reads * 100) THEN ', High physical reads last run' ELSE '' END + - /*Memory*/ - CASE WHEN (b.min_query_max_used_memory * 100) < (b.avg_query_max_used_memory) THEN ', Low memory sometimes' ELSE '' END + - CASE WHEN (b.max_query_max_used_memory) > (b.avg_query_max_used_memory * 100) THEN ', High memory sometimes' ELSE '' END + - CASE WHEN (b.last_query_max_used_memory * 100) < (b.avg_query_max_used_memory) THEN ', Low memory last run' ELSE '' END + - CASE WHEN (b.last_query_max_used_memory) > (b.avg_query_max_used_memory * 100) THEN ', High memory last run' ELSE '' END + - /*Duration*/ - CASE WHEN b.min_rowcount * 100 < b.avg_rowcount THEN ', Low row count sometimes' ELSE '' END + - CASE WHEN b.max_rowcount > b.avg_rowcount * 100 THEN ', High row count sometimes' ELSE '' END + - CASE WHEN b.last_rowcount * 100 < b.avg_rowcount THEN ', Low row count run' ELSE '' END + - CASE WHEN b.last_rowcount > b.avg_rowcount * 100 THEN ', High row count last run' ELSE '' END + - /*DOP*/ - CASE WHEN b.min_dop <> b.max_dop THEN ', Serial sometimes' ELSE '' END + - CASE WHEN b.min_dop <> b.max_dop AND b.last_dop = 1 THEN ', Serial last run' ELSE '' END + - CASE WHEN b.min_dop <> b.max_dop AND b.last_dop > 1 THEN ', Parallel last run' ELSE '' END + - /*tempdb*/ - CASE WHEN b.min_tempdb_space_used * 100 < b.avg_tempdb_space_used THEN ', Low tempdb sometimes' ELSE '' END + - CASE WHEN b.max_tempdb_space_used > b.avg_tempdb_space_used * 100 THEN ', High tempdb sometimes' ELSE '' END + - CASE WHEN b.last_tempdb_space_used * 100 < b.avg_tempdb_space_used THEN ', Low tempdb run' ELSE '' END + - CASE WHEN b.last_tempdb_space_used > b.avg_tempdb_space_used * 100 THEN ', High tempdb last run' ELSE '' END + - /*tlog*/ - CASE WHEN b.min_log_bytes_used * 100 < b.avg_log_bytes_used THEN ', Low log use sometimes' ELSE '' END + - CASE WHEN b.max_log_bytes_used > b.avg_log_bytes_used * 100 THEN ', High log use sometimes' ELSE '' END + - CASE WHEN b.last_log_bytes_used * 100 < b.avg_log_bytes_used THEN ', Low log use run' ELSE '' END + - CASE WHEN b.last_log_bytes_used > b.avg_log_bytes_used * 100 THEN ', High log use last run' ELSE '' END - , 2, 200000) - END -FROM #working_metrics AS b -OPTION (RECOMPILE); -END; -END TRY -BEGIN CATCH - RAISERROR (N'Failure analyzing parameter sniffing', 0,1) WITH NOWAIT; + /* If they want to run sp_BlitzWho and export to table, go for it. */ + IF @OutputTableNameBlitzWho IS NOT NULL + AND @OutputDatabaseName IS NOT NULL + AND @OutputSchemaName IS NOT NULL + AND EXISTS ( SELECT * + FROM sys.databases + WHERE QUOTENAME([name]) = @OutputDatabaseName) + BEGIN + RAISERROR('Logging sp_BlitzWho to table',10,1) WITH NOWAIT; + EXEC sp_BlitzWho @OutputDatabaseName = @UnquotedOutputDatabaseName, @OutputSchemaName = @UnquotedOutputSchemaName, @OutputTableName = @OutputTableNameBlitzWho, @CheckDateOverride = @StartSampleTime, @OutputTableRetentionDays = @OutputTableRetentionDays; + END - IF @sql_select IS NOT NULL - BEGIN - SET @msg = N'Last @sql_select: ' + @sql_select; - RAISERROR(@msg, 0, 1) WITH NOWAIT; - END; + RAISERROR('Beginning investigatory queries',10,1) WITH NOWAIT; - SELECT @msg = @DatabaseName + N' database failed to process. ' + ERROR_MESSAGE(), @error_severity = ERROR_SEVERITY(), @error_state = ERROR_STATE(); - RAISERROR (@msg, @error_severity, @error_state) WITH NOWAIT; - - - WHILE @@TRANCOUNT > 0 - ROLLBACK; - RETURN; -END CATCH; + /* Maintenance Tasks Running - Backup Running - CheckID 1 */ + IF @Seconds > 0 + BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 1',10,1) WITH NOWAIT; + END -BEGIN TRY + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, QueryHash) + SELECT 1 AS CheckID, + 1 AS Priority, + 'Maintenance Tasks Running' AS FindingGroup, + 'Backup Running' AS Finding, + 'http://www.BrentOzar.com/askbrent/backups/' AS URL, + 'Backup of ' + DB_NAME(db.resource_database_id) + ' database (' + (SELECT CAST(CAST(SUM(size * 8.0 / 1024 / 1024) AS BIGINT) AS NVARCHAR) FROM #MasterFiles WHERE database_id = db.resource_database_id) + 'GB) ' + @LineFeed + + CAST(r.percent_complete AS NVARCHAR(100)) + '% complete, has been running since ' + CAST(r.start_time AS NVARCHAR(100)) + '. ' + @LineFeed + + CASE WHEN COALESCE(s.nt_user_name, s.login_name) IS NOT NULL THEN (' Login: ' + COALESCE(s.nt_user_name, s.login_name) + ' ') ELSE '' END AS Details, + 'KILL ' + CAST(r.session_id AS NVARCHAR(100)) + ';' AS HowToStopIt, + pl.query_plan AS QueryPlan, + r.start_time AS StartTime, + s.login_name AS LoginName, + s.nt_user_name AS NTUserName, + s.[program_name] AS ProgramName, + s.[host_name] AS HostName, + db.[resource_database_id] AS DatabaseID, + DB_NAME(db.resource_database_id) AS DatabaseName, + 0 AS OpenTransactionCount, + r.query_hash + FROM sys.dm_exec_requests r + INNER JOIN sys.dm_exec_connections c ON r.session_id = c.session_id + INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id + INNER JOIN ( + SELECT DISTINCT request_session_id, resource_database_id + FROM sys.dm_tran_locks + WHERE resource_type = N'DATABASE' + AND request_mode = N'S' + AND request_status = N'GRANT' + AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.session_id = db.request_session_id + CROSS APPLY sys.dm_exec_query_plan(r.plan_handle) pl + WHERE r.command LIKE 'BACKUP%' + AND r.start_time <= DATEADD(minute, -5, GETDATE()) + AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs); + END -BEGIN + /* If there's a backup running, add details explaining how long full backup has been taking in the last month. */ + IF @Seconds > 0 AND CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) <> 'SQL Azure' + BEGIN + SET @StringToExecute = 'UPDATE #BlitzFirstResults SET Details = Details + '' Over the last 60 days, the full backup usually takes '' + CAST((SELECT AVG(DATEDIFF(mi, bs.backup_start_date, bs.backup_finish_date)) FROM msdb.dbo.backupset bs WHERE abr.DatabaseName = bs.database_name AND bs.type = ''D'' AND bs.backup_start_date > DATEADD(dd, -60, SYSDATETIMEOFFSET()) AND bs.backup_finish_date IS NOT NULL) AS NVARCHAR(100)) + '' minutes.'' FROM #BlitzFirstResults abr WHERE abr.CheckID = 1 AND EXISTS (SELECT * FROM msdb.dbo.backupset bs WHERE bs.type = ''D'' AND bs.backup_start_date > DATEADD(dd, -60, SYSDATETIMEOFFSET()) AND bs.backup_finish_date IS NOT NULL AND abr.DatabaseName = bs.database_name AND DATEDIFF(mi, bs.backup_start_date, bs.backup_finish_date) > 1)'; + EXEC(@StringToExecute); + END; -IF (@Failed = 0 AND @ExportToExcel = 0 AND @SkipXML = 0) -BEGIN -RAISERROR(N'Returning regular results', 0, 1) WITH NOWAIT; + /* Maintenance Tasks Running - DBCC CHECK* Running - CheckID 2 */ + IF @Seconds > 0 AND EXISTS(SELECT * FROM sys.dm_exec_requests WHERE command LIKE 'DBCC%') + BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 2',10,1) WITH NOWAIT; + END -WITH x AS ( -SELECT wpt.database_name, ww.query_cost, wm.plan_id, wm.query_id, wm.query_id_all_plan_ids, wpt.query_sql_text, wm.proc_or_function_name, wpt.query_plan_xml, ww.warnings, wpt.pattern, - wm.parameter_sniffing_symptoms, wpt.top_three_waits, ww.missing_indexes, ww.implicit_conversion_info, ww.cached_execution_parameters, wm.count_executions, wm.count_compiles, wm.total_cpu_time, wm.avg_cpu_time, - wm.total_duration, wm.avg_duration, wm.total_logical_io_reads, wm.avg_logical_io_reads, - wm.total_physical_io_reads, wm.avg_physical_io_reads, wm.total_logical_io_writes, wm.avg_logical_io_writes, wm.total_rowcount, wm.avg_rowcount, - wm.total_query_max_used_memory, wm.avg_query_max_used_memory, wm.total_tempdb_space_used, wm.avg_tempdb_space_used, - wm.total_log_bytes_used, wm.avg_log_bytes_used, wm.total_num_physical_io_reads, wm.avg_num_physical_io_reads, - wm.first_execution_time, wm.last_execution_time, wpt.last_force_failure_reason_desc, wpt.context_settings, ROW_NUMBER() OVER (PARTITION BY wm.plan_id, wm.query_id, wm.last_execution_time ORDER BY wm.plan_id) AS rn -FROM #working_plan_text AS wpt -JOIN #working_warnings AS ww - ON wpt.plan_id = ww.plan_id - AND wpt.query_id = ww.query_id -JOIN #working_metrics AS wm - ON wpt.plan_id = wm.plan_id - AND wpt.query_id = wm.query_id -) -SELECT * -FROM x -WHERE x.rn = 1 -ORDER BY x.last_execution_time -OPTION (RECOMPILE); + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, QueryHash) + SELECT 2 AS CheckID, + 1 AS Priority, + 'Maintenance Tasks Running' AS FindingGroup, + 'DBCC CHECK* Running' AS Finding, + 'http://www.BrentOzar.com/askbrent/dbcc/' AS URL, + 'Corruption check of ' + DB_NAME(db.resource_database_id) + ' database (' + (SELECT CAST(CAST(SUM(size * 8.0 / 1024 / 1024) AS BIGINT) AS NVARCHAR) FROM #MasterFiles WHERE database_id = db.resource_database_id) + 'GB) 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, + s.login_name AS LoginName, + s.nt_user_name AS NTUserName, + s.[program_name] AS ProgramName, + s.[host_name] AS HostName, + db.[resource_database_id] AS DatabaseID, + DB_NAME(db.resource_database_id) AS DatabaseName, + 0 AS OpenTransactionCount, + r.query_hash + FROM sys.dm_exec_requests r + INNER JOIN sys.dm_exec_connections c ON r.session_id = c.session_id + INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id + INNER JOIN (SELECT DISTINCT l.request_session_id, l.resource_database_id + FROM sys.dm_tran_locks l + INNER JOIN sys.databases d ON l.resource_database_id = d.database_id + WHERE l.resource_type = N'DATABASE' + AND l.request_mode = N'S' + AND l.request_status = N'GRANT' + AND l.request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.session_id = db.request_session_id + OUTER APPLY sys.dm_exec_query_plan(r.plan_handle) pl + OUTER APPLY sys.dm_exec_sql_text(r.sql_handle) AS t + WHERE r.command LIKE 'DBCC%' + AND CAST(t.text AS NVARCHAR(4000)) NOT LIKE '%dm_db_index_physical_stats%' + AND CAST(t.text AS NVARCHAR(4000)) NOT LIKE '%ALTER INDEX%' + AND CAST(t.text AS NVARCHAR(4000)) NOT LIKE '%fileproperty%' + AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs); + END -END; + /* Maintenance Tasks Running - Restore Running - CheckID 3 */ + IF @Seconds > 0 + BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 3',10,1) WITH NOWAIT; + END -IF (@Failed = 1 AND @ExportToExcel = 0 AND @SkipXML = 0) -BEGIN + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, QueryHash) + SELECT 3 AS CheckID, + 1 AS Priority, + 'Maintenance Tasks Running' AS FindingGroup, + 'Restore Running' AS Finding, + 'http://www.BrentOzar.com/askbrent/backups/' AS URL, + 'Restore of ' + DB_NAME(db.resource_database_id) + ' database (' + (SELECT CAST(CAST(SUM(size * 8.0 / 1024 / 1024) AS BIGINT) AS NVARCHAR) FROM #MasterFiles WHERE database_id = db.resource_database_id) + '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, + s.login_name AS LoginName, + s.nt_user_name AS NTUserName, + s.[program_name] AS ProgramName, + s.[host_name] AS HostName, + db.[resource_database_id] AS DatabaseID, + DB_NAME(db.resource_database_id) AS DatabaseName, + 0 AS OpenTransactionCount, + r.query_hash + FROM sys.dm_exec_requests r + INNER JOIN sys.dm_exec_connections c ON r.session_id = c.session_id + INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id + INNER JOIN ( + SELECT DISTINCT request_session_id, resource_database_id + FROM sys.dm_tran_locks + WHERE resource_type = N'DATABASE' + AND request_mode = N'S' + AND request_status = N'GRANT') AS db ON s.session_id = db.request_session_id + CROSS APPLY sys.dm_exec_query_plan(r.plan_handle) pl + WHERE r.command LIKE 'RESTORE%' + AND s.program_name <> 'SQL Server Log Shipping' + AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs); + END -RAISERROR(N'Returning results for failed queries', 0, 1) WITH NOWAIT; + /* SQL Server Internal Maintenance - Database File Growing - CheckID 4 */ + IF @Seconds > 0 + BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 4',10,1) WITH NOWAIT; + END -WITH x AS ( -SELECT wpt.database_name, ww.query_cost, wm.plan_id, wm.query_id, wm.query_id_all_plan_ids, wpt.query_sql_text, wm.proc_or_function_name, wpt.query_plan_xml, ww.warnings, wpt.pattern, - wm.parameter_sniffing_symptoms, wpt.last_force_failure_reason_desc, wpt.top_three_waits, ww.missing_indexes, ww.implicit_conversion_info, ww.cached_execution_parameters, - wm.count_executions, wm.count_compiles, wm.total_cpu_time, wm.avg_cpu_time, - wm.total_duration, wm.avg_duration, wm.total_logical_io_reads, wm.avg_logical_io_reads, - wm.total_physical_io_reads, wm.avg_physical_io_reads, wm.total_logical_io_writes, wm.avg_logical_io_writes, wm.total_rowcount, wm.avg_rowcount, - wm.total_query_max_used_memory, wm.avg_query_max_used_memory, wm.total_tempdb_space_used, wm.avg_tempdb_space_used, - wm.total_log_bytes_used, wm.avg_log_bytes_used, wm.total_num_physical_io_reads, wm.avg_num_physical_io_reads, - wm.first_execution_time, wm.last_execution_time, wpt.context_settings, ROW_NUMBER() OVER (PARTITION BY wm.plan_id, wm.query_id, wm.last_execution_time ORDER BY wm.plan_id) AS rn -FROM #working_plan_text AS wpt -JOIN #working_warnings AS ww - ON wpt.plan_id = ww.plan_id - AND wpt.query_id = ww.query_id -JOIN #working_metrics AS wm - ON wpt.plan_id = wm.plan_id - AND wpt.query_id = wm.query_id -) -SELECT * -FROM x -WHERE x.rn = 1 -ORDER BY x.last_execution_time -OPTION (RECOMPILE); + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount) + SELECT 4 AS CheckID, + 1 AS Priority, + 'SQL Server Internal Maintenance' AS FindingGroup, + 'Database File Growing' AS Finding, + 'http://www.BrentOzar.com/go/instant' AS URL, + 'SQL Server is waiting for Windows to provide storage space for a database restore, a data file growth, or a log file growth. This task has been running since ' + CAST(r.start_time AS NVARCHAR(100)) + '.' + @LineFeed + 'Check the query plan (expert mode) to identify the database involved.' AS Details, + 'Unfortunately, you can''t stop this, but you can prevent it next time. Check out http://www.BrentOzar.com/go/instant for details.' AS HowToStopIt, + pl.query_plan AS QueryPlan, + r.start_time AS StartTime, + s.login_name AS LoginName, + s.nt_user_name AS NTUserName, + s.[program_name] AS ProgramName, + s.[host_name] AS HostName, + NULL AS DatabaseID, + NULL AS DatabaseName, + 0 AS OpenTransactionCount + FROM sys.dm_os_waiting_tasks t + INNER JOIN sys.dm_exec_connections c ON t.session_id = c.session_id + INNER JOIN sys.dm_exec_requests r ON t.session_id = r.session_id + INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id + CROSS APPLY sys.dm_exec_query_plan(r.plan_handle) pl + WHERE t.wait_type = 'PREEMPTIVE_OS_WRITEFILEGATHER' + AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs); + END -END; + /* Query Problems - Long-Running Query Blocking Others - CheckID 5 */ + IF SERVERPROPERTY('Edition') <> 'SQL Azure' AND @Seconds > 0 AND EXISTS(SELECT * FROM sys.dm_os_waiting_tasks WHERE wait_type LIKE 'LCK%' AND wait_duration_ms > 30000) + BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 5',10,1) WITH NOWAIT; + END -IF (@ExportToExcel = 1 AND @SkipXML = 0) -BEGIN + SET @StringToExecute = N'INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, QueryText, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, QueryHash) + SELECT 5 AS CheckID, + 1 AS Priority, + ''Query Problems'' AS FindingGroup, + ''Long-Running Query Blocking Others'' AS Finding, + ''http://www.BrentOzar.com/go/blocking'' AS URL, + ''Query in '' + COALESCE(DB_NAME(COALESCE((SELECT TOP 1 dbid FROM sys.dm_exec_sql_text(r.sql_handle)), + (SELECT TOP 1 t.dbid FROM master..sysprocesses spBlocker CROSS APPLY sys.dm_exec_sql_text(spBlocker.sql_handle) t WHERE spBlocker.spid = tBlocked.blocking_session_id))), ''(Unknown)'') + '' has a last request start time of '' + CAST(s.last_request_start_time AS NVARCHAR(100)) + ''. Query follows: ' + + @LineFeed + @LineFeed + + '''+ CAST(COALESCE((SELECT TOP 1 [text] FROM sys.dm_exec_sql_text(r.sql_handle)), + (SELECT TOP 1 [text] FROM master..sysprocesses spBlocker CROSS APPLY sys.dm_exec_sql_text(spBlocker.sql_handle) WHERE spBlocker.spid = tBlocked.blocking_session_id), '''') AS NVARCHAR(2000)) AS Details, + ''KILL '' + CAST(tBlocked.blocking_session_id AS NVARCHAR(100)) + '';'' AS HowToStopIt, + (SELECT TOP 1 query_plan FROM sys.dm_exec_query_plan(r.plan_handle)) AS QueryPlan, + COALESCE((SELECT TOP 1 [text] FROM sys.dm_exec_sql_text(r.sql_handle)), + (SELECT TOP 1 [text] FROM master..sysprocesses spBlocker CROSS APPLY sys.dm_exec_sql_text(spBlocker.sql_handle) WHERE spBlocker.spid = tBlocked.blocking_session_id)) AS QueryText, + r.start_time AS StartTime, + s.login_name AS LoginName, + s.nt_user_name AS NTUserName, + s.[program_name] AS ProgramName, + s.[host_name] AS HostName, + r.[database_id] AS DatabaseID, + DB_NAME(r.database_id) AS DatabaseName, + 0 AS OpenTransactionCount, + r.query_hash + FROM sys.dm_os_waiting_tasks tBlocked + INNER JOIN sys.dm_exec_sessions s ON tBlocked.blocking_session_id = s.session_id + LEFT OUTER JOIN sys.dm_exec_requests r ON s.session_id = r.session_id + INNER JOIN sys.dm_exec_connections c ON s.session_id = c.session_id + WHERE tBlocked.wait_type LIKE ''LCK%'' AND tBlocked.wait_duration_ms > 30000 + /* And the blocking session ID is not blocked by anyone else: */ + AND NOT EXISTS(SELECT * FROM sys.dm_os_waiting_tasks tBlocking WHERE s.session_id = tBlocking.session_id AND tBlocking.session_id <> tBlocking.blocking_session_id AND tBlocking.blocking_session_id IS NOT NULL) + AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs);'; + EXECUTE sp_executesql @StringToExecute; + END; + + /* Query Problems - Plan Cache Erased Recently - CheckID 7 */ + IF DATEADD(mi, -15, SYSDATETIME()) < (SELECT TOP 1 creation_time FROM sys.dm_exec_query_stats ORDER BY creation_time) + BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 7',10,1) WITH NOWAIT; + END -RAISERROR(N'Returning results for Excel export', 0, 1) WITH NOWAIT; + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) + SELECT TOP 1 7 AS CheckID, + 50 AS Priority, + 'Query Problems' AS FindingGroup, + 'Plan Cache Erased Recently' AS Finding, + 'http://www.BrentOzar.com/askbrent/plan-cache-erased-recently/' AS URL, + 'The oldest query in the plan cache was created at ' + CAST(creation_time AS NVARCHAR(50)) + '. ' + @LineFeed + @LineFeed + + 'This indicates that someone ran DBCC FREEPROCCACHE at that time,' + @LineFeed + + 'Giving SQL Server temporary amnesia. Now, as queries come in,' + @LineFeed + + 'SQL Server has to use a lot of CPU power in order to build execution' + @LineFeed + + 'plans and put them in cache again. This causes high CPU loads.' AS Details, + 'Find who did that, and stop them from doing it again.' AS HowToStopIt + FROM sys.dm_exec_query_stats + ORDER BY creation_time; + END; -UPDATE #working_plan_text -SET query_sql_text = SUBSTRING(REPLACE(REPLACE(REPLACE(LTRIM(RTRIM(query_sql_text)),' ','<>'),'><',''),'<>',' '), 1, 31000) -OPTION (RECOMPILE); -WITH x AS ( -SELECT wpt.database_name, ww.query_cost, wm.plan_id, wm.query_id, wm.query_id_all_plan_ids, wpt.query_sql_text, wm.proc_or_function_name, ww.warnings, wpt.pattern, - wm.parameter_sniffing_symptoms, wpt.last_force_failure_reason_desc, wpt.top_three_waits, wm.count_executions, wm.count_compiles, wm.total_cpu_time, wm.avg_cpu_time, - wm.total_duration, wm.avg_duration, wm.total_logical_io_reads, wm.avg_logical_io_reads, - wm.total_physical_io_reads, wm.avg_physical_io_reads, wm.total_logical_io_writes, wm.avg_logical_io_writes, wm.total_rowcount, wm.avg_rowcount, - wm.total_query_max_used_memory, wm.avg_query_max_used_memory, wm.total_tempdb_space_used, wm.avg_tempdb_space_used, - wm.total_log_bytes_used, wm.avg_log_bytes_used, wm.total_num_physical_io_reads, wm.avg_num_physical_io_reads, - wm.first_execution_time, wm.last_execution_time, wpt.context_settings, ROW_NUMBER() OVER (PARTITION BY wm.plan_id, wm.query_id, wm.last_execution_time ORDER BY wm.plan_id) AS rn -FROM #working_plan_text AS wpt -JOIN #working_warnings AS ww - ON wpt.plan_id = ww.plan_id - AND wpt.query_id = ww.query_id -JOIN #working_metrics AS wm - ON wpt.plan_id = wm.plan_id - AND wpt.query_id = wm.query_id -) -SELECT * -FROM x -WHERE x.rn = 1 -ORDER BY x.last_execution_time -OPTION (RECOMPILE); + /* Query Problems - Sleeping Query with Open Transactions - CheckID 8 */ + IF @Seconds > 0 + BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 8',10,1) WITH NOWAIT; + END -END; + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, OpenTransactionCount) + SELECT 8 AS CheckID, + 50 AS Priority, + 'Query Problems' AS FindingGroup, + 'Sleeping Query with Open Transactions' AS Finding, + 'http://www.brentozar.com/askbrent/sleeping-query-with-open-transactions/' AS URL, + 'Database: ' + DB_NAME(db.resource_database_id) + @LineFeed + 'Host: ' + s.[host_name] + @LineFeed + 'Program: ' + s.[program_name] + @LineFeed + 'Asleep with open transactions and locks since ' + CAST(s.last_request_end_time AS NVARCHAR(100)) + '. ' AS Details, + 'KILL ' + CAST(s.session_id AS NVARCHAR(100)) + ';' AS HowToStopIt, + s.last_request_start_time AS StartTime, + s.login_name AS LoginName, + s.nt_user_name AS NTUserName, + s.[program_name] AS ProgramName, + s.[host_name] AS HostName, + db.[resource_database_id] AS DatabaseID, + DB_NAME(db.resource_database_id) AS DatabaseName, + (SELECT TOP 1 [text] FROM sys.dm_exec_sql_text(c.most_recent_sql_handle)) AS QueryText, + sessions_with_transactions.open_transaction_count AS OpenTransactionCount + FROM (SELECT session_id, SUM(open_transaction_count) AS open_transaction_count FROM sys.dm_exec_requests WHERE open_transaction_count > 0 GROUP BY session_id) AS sessions_with_transactions + INNER JOIN sys.dm_exec_sessions s ON sessions_with_transactions.session_id = s.session_id + INNER JOIN sys.dm_exec_connections c ON s.session_id = c.session_id + INNER JOIN ( + SELECT DISTINCT request_session_id, resource_database_id + FROM sys.dm_tran_locks + WHERE resource_type = N'DATABASE' + AND request_mode = N'S' + AND request_status = N'GRANT' + AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.session_id = db.request_session_id + WHERE s.status = 'sleeping' + AND s.last_request_end_time < DATEADD(ss, -10, SYSDATETIME()) + AND EXISTS(SELECT * FROM sys.dm_tran_locks WHERE request_session_id = s.session_id + AND NOT (resource_type = N'DATABASE' AND request_mode = N'S' AND request_status = N'GRANT' AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE')); + END -IF (@ExportToExcel = 0 AND @SkipXML = 1) -BEGIN + /*Query Problems - Clients using implicit transactions - CheckID 37 */ + IF @Seconds > 0 + AND ( @@VERSION NOT LIKE 'Microsoft SQL Server 2005%' + AND @@VERSION NOT LIKE 'Microsoft SQL Server 2008%' + AND @@VERSION NOT LIKE 'Microsoft SQL Server 2008 R2%' ) + BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 37',10,1) WITH NOWAIT; + END -RAISERROR(N'Returning results for skipped XML', 0, 1) WITH NOWAIT; + SET @StringToExecute = N'INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, OpenTransactionCount) + SELECT 37 AS CheckId, + 50 AS Priority, + ''Query Problems'' AS FindingsGroup, + ''Implicit Transactions'', + ''https://www.brentozar.com/go/ImplicitTransactions/'' AS URL, + ''Database: '' + DB_NAME(s.database_id) + '' '' + CHAR(13) + CHAR(10) + + ''Host: '' + s.[host_name] + '' '' + CHAR(13) + CHAR(10) + + ''Program: '' + s.[program_name] + '' '' + CHAR(13) + CHAR(10) + + CONVERT(NVARCHAR(10), s.open_transaction_count) + + '' open transactions since: '' + + CONVERT(NVARCHAR(30), tat.transaction_begin_time) + ''. '' + AS Details, + ''Run sp_BlitzWho and check the is_implicit_transaction column to spot the culprits. +If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, + tat.transaction_begin_time, + s.login_name, + s.nt_user_name, + s.program_name, + s.host_name, + s.database_id, + DB_NAME(s.database_id) AS DatabaseName, + NULL AS Querytext, + s.open_transaction_count AS OpenTransactionCount + FROM sys.dm_tran_active_transactions AS tat + LEFT JOIN sys.dm_tran_session_transactions AS tst + ON tst.transaction_id = tat.transaction_id + LEFT JOIN sys.dm_exec_sessions AS s + ON s.session_id = tst.session_id + WHERE tat.name = ''implicit_transaction''; + ' + EXECUTE sp_executesql @StringToExecute; + END; -WITH x AS ( -SELECT wpt.database_name, wm.plan_id, wm.query_id, wm.query_id_all_plan_ids, wpt.query_sql_text, wpt.query_plan_xml, wpt.pattern, - wm.parameter_sniffing_symptoms, wpt.top_three_waits, wm.count_executions, wm.count_compiles, wm.total_cpu_time, wm.avg_cpu_time, - wm.total_duration, wm.avg_duration, wm.total_logical_io_reads, wm.avg_logical_io_reads, - wm.total_physical_io_reads, wm.avg_physical_io_reads, wm.total_logical_io_writes, wm.avg_logical_io_writes, wm.total_rowcount, wm.avg_rowcount, - wm.total_query_max_used_memory, wm.avg_query_max_used_memory, wm.total_tempdb_space_used, wm.avg_tempdb_space_used, - wm.total_log_bytes_used, wm.avg_log_bytes_used, wm.total_num_physical_io_reads, wm.avg_num_physical_io_reads, - wm.first_execution_time, wm.last_execution_time, wpt.last_force_failure_reason_desc, wpt.context_settings, ROW_NUMBER() OVER (PARTITION BY wm.plan_id, wm.query_id, wm.last_execution_time ORDER BY wm.plan_id) AS rn -FROM #working_plan_text AS wpt -JOIN #working_metrics AS wm - ON wpt.plan_id = wm.plan_id - AND wpt.query_id = wm.query_id -) -SELECT * -FROM x -WHERE x.rn = 1 -ORDER BY x.last_execution_time -OPTION (RECOMPILE); + /* Query Problems - Query Rolling Back - CheckID 9 */ + IF @Seconds > 0 + BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 9',10,1) WITH NOWAIT; + END -END; + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, QueryHash) + SELECT 9 AS CheckID, + 1 AS Priority, + 'Query Problems' AS FindingGroup, + 'Query Rolling Back' AS Finding, + 'http://www.BrentOzar.com/askbrent/rollback/' AS URL, + 'Rollback started at ' + CAST(r.start_time AS NVARCHAR(100)) + ', is ' + CAST(r.percent_complete AS NVARCHAR(100)) + '% complete.' AS Details, + 'Unfortunately, you can''t stop this. Whatever you do, don''t restart the server in an attempt to fix it - SQL Server will keep rolling back.' AS HowToStopIt, + r.start_time AS StartTime, + s.login_name AS LoginName, + s.nt_user_name AS NTUserName, + s.[program_name] AS ProgramName, + s.[host_name] AS HostName, + db.[resource_database_id] AS DatabaseID, + DB_NAME(db.resource_database_id) AS DatabaseName, + (SELECT TOP 1 [text] FROM sys.dm_exec_sql_text(c.most_recent_sql_handle)) AS QueryText, + r.query_hash + FROM sys.dm_exec_sessions s + INNER JOIN sys.dm_exec_connections c ON s.session_id = c.session_id + INNER JOIN sys.dm_exec_requests r ON s.session_id = r.session_id + LEFT OUTER JOIN ( + SELECT DISTINCT request_session_id, resource_database_id + FROM sys.dm_tran_locks + WHERE resource_type = N'DATABASE' + AND request_mode = N'S' + AND request_status = N'GRANT' + AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.session_id = db.request_session_id + WHERE r.status = 'rollback'; + END -END; -END TRY -BEGIN CATCH - RAISERROR (N'Failure returning results', 0,1) WITH NOWAIT; + IF @Seconds > 0 + BEGIN + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) + SELECT + 47 AS CheckId, + 50 AS Priority, + 'Query Problems' AS FindingsGroup, + 'High Percentage Of Runnable Queries' AS Finding, + 'https://erikdarlingdata.com/go/RunnableQueue/' AS URL, + 'On the ' + + CASE WHEN y.pass = 1 + THEN '1st' + ELSE '2nd' + END + + ' pass, ' + + RTRIM(y.runnable_pct) + + '% of your queries were waiting to get on a CPU to run. ' + + ' This can indicate CPU pressure.' + FROM + ( + SELECT + 1 AS pass, + x.total, + x.runnable, + CONVERT(decimal(5,2), + ( + x.runnable / + (1. * NULLIF(x.total, 0)) + ) + ) * 100. AS runnable_pct + FROM + ( + SELECT + COUNT_BIG(*) AS total, + SUM(CASE WHEN status = 'runnable' + THEN 1 + ELSE 0 + END) AS runnable + FROM sys.dm_exec_requests + WHERE session_id > 50 + ) AS x + ) AS y + WHERE y.runnable_pct > 20.; + END - IF @sql_select IS NOT NULL - BEGIN - SET @msg = N'Last @sql_select: ' + @sql_select; - RAISERROR(@msg, 0, 1) WITH NOWAIT; - END; + /* Server Performance - Too Much Free Memory - CheckID 34 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 34',10,1) WITH NOWAIT; + END - SELECT @msg = @DatabaseName + N' database failed to process. ' + ERROR_MESSAGE(), @error_severity = ERROR_SEVERITY(), @error_state = ERROR_STATE(); - RAISERROR (@msg, @error_severity, @error_state) WITH NOWAIT; - - - WHILE @@TRANCOUNT > 0 - ROLLBACK; + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) + SELECT 34 AS CheckID, + 50 AS Priority, + 'Server Performance' AS FindingGroup, + 'Too Much Free Memory' AS Finding, + 'https://BrentOzar.com/go/freememory' AS URL, + CAST((CAST(cFree.cntr_value AS BIGINT) / 1024 / 1024 ) AS NVARCHAR(100)) + N'GB of free memory inside SQL Server''s buffer pool,' + @LineFeed + ' which is ' + CAST((CAST(cTotal.cntr_value AS BIGINT) / 1024 / 1024) AS NVARCHAR(100)) + N'GB. You would think lots of free memory would be good, but check out the URL for more information.' AS Details, + 'Run sp_BlitzCache @SortOrder = ''memory grant'' to find queries with huge memory grants and tune them.' AS HowToStopIt + FROM sys.dm_os_performance_counters cFree + INNER JOIN sys.dm_os_performance_counters cTotal ON cTotal.object_name LIKE N'%Memory Manager%' + AND cTotal.counter_name = N'Total Server Memory (KB) ' + WHERE cFree.object_name LIKE N'%Memory Manager%' + AND cFree.counter_name = N'Free Memory (KB) ' + AND CAST(cFree.cntr_value AS BIGINT) > 20480000000 + AND CAST(cTotal.cntr_value AS BIGINT) * .3 <= CAST(cFree.cntr_value AS BIGINT) + AND CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) NOT LIKE '%Standard%'; - RETURN; -END CATCH; + /* Server Performance - Target Memory Lower Than Max - CheckID 35 */ + IF SERVERPROPERTY('Edition') <> 'SQL Azure' + BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 35',10,1) WITH NOWAIT; + END -BEGIN TRY -BEGIN + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) + SELECT 35 AS CheckID, + 10 AS Priority, + 'Server Performance' AS FindingGroup, + 'Target Memory Lower Than Max' AS Finding, + 'https://BrentOzar.com/go/target' AS URL, + N'Max server memory is ' + CAST(cMax.value_in_use AS NVARCHAR(50)) + N' MB but target server memory is only ' + CAST((CAST(cTarget.cntr_value AS BIGINT) / 1024) AS NVARCHAR(50)) + N' MB,' + @LineFeed + + N'indicating that SQL Server may be under external memory pressure or max server memory may be set too high.' AS Details, + 'Investigate what OS processes are using memory, and double-check the max server memory setting.' AS HowToStopIt + FROM sys.configurations cMax + INNER JOIN sys.dm_os_performance_counters cTarget ON cTarget.object_name LIKE N'%Memory Manager%' + AND cTarget.counter_name = N'Target Server Memory (KB) ' + WHERE cMax.name = 'max server memory (MB)' + AND CAST(cMax.value_in_use AS BIGINT) >= 1.5 * (CAST(cTarget.cntr_value AS BIGINT) / 1024) + AND CAST(cMax.value_in_use AS BIGINT) < 2147483647 /* Not set to default of unlimited */ + AND CAST(cTarget.cntr_value AS BIGINT) < .8 * (SELECT available_physical_memory_kb FROM sys.dm_os_sys_memory); /* Target memory less than 80% of physical memory (in case they set max too high) */ + END -IF (@ExportToExcel = 0 AND @HideSummary = 0 AND @SkipXML = 0) -BEGIN - RAISERROR('Building query plan summary data.', 0, 1) WITH NOWAIT; + /* Server Info - Database Size, Total GB - CheckID 21 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 21',10,1) WITH NOWAIT; + END - /* Build summary data */ - IF EXISTS (SELECT 1/0 - FROM #working_warnings - WHERE frequent_execution = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 1, - 100, - 'Execution Pattern', - 'Frequently Executed Queries', - 'http://brentozar.com/blitzcache/frequently-executed-queries/', - 'Queries are being executed more than ' - + CAST (@execution_threshold AS VARCHAR(5)) - + ' times per minute. This can put additional load on the server, even when queries are lightweight.') ; + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) + SELECT 21 AS CheckID, + 251 AS Priority, + 'Server Info' AS FindingGroup, + 'Database Size, Total GB' AS Finding, + CAST(SUM (CAST(size AS BIGINT)*8./1024./1024.) AS VARCHAR(100)) AS Details, + SUM (CAST(size AS BIGINT))*8./1024./1024. AS DetailsInt, + 'http://www.BrentOzar.com/askbrent/' AS URL + FROM #MasterFiles + WHERE database_id > 4; - IF EXISTS (SELECT 1/0 - FROM #working_warnings - WHERE parameter_sniffing = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 2, - 50, - 'Parameterization', - 'Parameter Sniffing', - 'http://brentozar.com/blitzcache/parameter-sniffing/', - 'There are signs of parameter sniffing (wide variance in rows return or time to execute). Investigate query patterns and tune code appropriately.') ; + /* Server Info - Database Count - CheckID 22 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 22',10,1) WITH NOWAIT; + END - /* Forced execution plans */ - IF EXISTS (SELECT 1/0 - FROM #working_warnings - WHERE is_forced_plan = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 3, - 5, - 'Parameterization', - 'Forced Plans', - 'http://brentozar.com/blitzcache/forced-plans/', - 'Execution plans have been compiled with forced plans, either through FORCEPLAN, plan guides, or forced parameterization. This will make general tuning efforts less effective.'); + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) + SELECT 22 AS CheckID, + 251 AS Priority, + 'Server Info' AS FindingGroup, + 'Database Count' AS Finding, + CAST(SUM(1) AS VARCHAR(100)) AS Details, + SUM (1) AS DetailsInt, + 'http://www.BrentOzar.com/askbrent/' AS URL + FROM sys.databases + WHERE database_id > 4; - IF EXISTS (SELECT 1/0 - FROM #working_warnings - WHERE is_cursor = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 4, - 200, - 'Cursors', - 'Cursors', - 'http://brentozar.com/blitzcache/cursors-found-slow-queries/', - 'There are cursors in the plan cache. This is neither good nor bad, but it is a thing. Cursors are weird in SQL Server.'); + /* Server Info - Memory Grants pending - CheckID 39 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 39',10,1) WITH NOWAIT; + END - IF EXISTS (SELECT 1/0 - FROM #working_warnings - WHERE is_cursor = 1 - AND is_optimistic_cursor = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 4, - 200, - 'Cursors', - 'Optimistic Cursors', - 'http://brentozar.com/blitzcache/cursors-found-slow-queries/', - 'There are optimistic cursors in the plan cache, which can harm performance.'); + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) + SELECT 39 AS CheckID, + 50 AS Priority, + 'Server Performance' AS FindingGroup, + 'Memory Grants Pending' AS Finding, + CAST(PendingGrants.Details AS NVARCHAR(50)) AS Details, + PendingGrants.DetailsInt, + 'https://www.brentozar.com/blitz/memory-grants/' AS URL + FROM + ( + SELECT + COUNT(1) AS Details, + COUNT(1) AS DetailsInt + FROM sys.dm_exec_query_memory_grants AS Grants + WHERE queue_id IS NOT NULL + ) AS PendingGrants + WHERE PendingGrants.Details > 0; - IF EXISTS (SELECT 1/0 - FROM #working_warnings - WHERE is_cursor = 1 - AND is_forward_only_cursor = 0 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 4, - 200, - 'Cursors', - 'Non-forward Only Cursors', - 'http://brentozar.com/blitzcache/cursors-found-slow-queries/', - 'There are non-forward only cursors in the plan cache, which can harm performance.'); + /* Server Info - Memory Grant/Workspace info - CheckID 40 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 40',10,1) WITH NOWAIT; + END - IF EXISTS (SELECT 1/0 - FROM #working_warnings - WHERE is_cursor = 1 - AND is_cursor_dynamic = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (4, - 200, - 'Cursors', - 'Dynamic Cursors', - 'http://brentozar.com/blitzcache/cursors-found-slow-queries/', - 'Dynamic Cursors inhibit parallelism!.'); + DECLARE @MaxWorkspace BIGINT + SET @MaxWorkspace = (SELECT CAST(cntr_value AS BIGINT)/1024 FROM #PerfmonStats WHERE counter_name = N'Maximum Workspace Memory (KB)') + + IF (@MaxWorkspace IS NULL + OR @MaxWorkspace = 0) + BEGIN + SET @MaxWorkspace = 1 + END - IF EXISTS (SELECT 1/0 - FROM #working_warnings - WHERE is_cursor = 1 - AND is_fast_forward_cursor = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (4, - 200, - 'Cursors', - 'Fast Forward Cursors', - 'http://brentozar.com/blitzcache/cursors-found-slow-queries/', - 'Fast forward cursors inhibit parallelism!.'); - - IF EXISTS (SELECT 1/0 - FROM #working_warnings - WHERE is_forced_parameterized = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 5, - 50, - 'Parameterization', - 'Forced Parameterization', - 'http://brentozar.com/blitzcache/forced-parameterization/', - 'Execution plans have been compiled with forced parameterization.') ; + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) + SELECT 40 AS CheckID, + 251 AS Priority, + 'Server Info' AS FindingGroup, + 'Memory Grant/Workspace info' AS Finding, + + 'Grants Outstanding: ' + CAST((SELECT COUNT(*) FROM sys.dm_exec_query_memory_grants WHERE queue_id IS NULL) AS NVARCHAR(50)) + @LineFeed + + 'Total Granted(MB): ' + CAST(ISNULL(SUM(Grants.granted_memory_kb) / 1024, 0) AS NVARCHAR(50)) + @LineFeed + + 'Total WorkSpace(MB): ' + CAST(ISNULL(@MaxWorkspace, 0) AS NVARCHAR(50)) + @LineFeed + + 'Granted workspace: ' + CAST(ISNULL((CAST(SUM(Grants.granted_memory_kb) / 1024 AS MONEY) + / CAST(@MaxWorkspace AS MONEY)) * 100, 0) AS NVARCHAR(50)) + '%' + @LineFeed + + 'Oldest Grant in seconds: ' + CAST(ISNULL(DATEDIFF(SECOND, MIN(Grants.request_time), GETDATE()), 0) AS NVARCHAR(50)) AS Details, + (SELECT COUNT(*) FROM sys.dm_exec_query_memory_grants WHERE queue_id IS NULL) AS DetailsInt, + 'http://www.BrentOzar.com/askbrent/' AS URL + FROM sys.dm_exec_query_memory_grants AS Grants; - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.is_parallel = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 6, - 200, - 'Execution Plans', - 'Parallelism', - 'http://brentozar.com/blitzcache/parallel-plans-detected/', - 'Parallel plans detected. These warrant investigation, but are neither good nor bad.') ; + /* Query Problems - Queries with high memory grants - CheckID 46 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 46',10,1) WITH NOWAIT; + END - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.near_parallel = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 7, - 200, - 'Execution Plans', - 'Nearly Parallel', - 'http://brentozar.com/blitzcache/query-cost-near-cost-threshold-parallelism/', - 'Queries near the cost threshold for parallelism. These may go parallel when you least expect it.') ; + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, URL, QueryText, QueryPlan) + SELECT 46 AS CheckID, + 100 AS Priority, + 'Query Problems' AS FindingGroup, + 'Query with a memory grant exceeding ' + +CAST(@MemoryGrantThresholdPct AS NVARCHAR(15)) + +'%' AS Finding, + 'Granted size: '+ CAST(CAST(Grants.granted_memory_kb / 1024 AS INT) AS NVARCHAR(50)) + +N'MB ' + + @LineFeed + +N'Granted pct of max workspace: ' + + CAST(ISNULL((CAST(Grants.granted_memory_kb / 1024 AS MONEY) + / CAST(@MaxWorkspace AS MONEY)) * 100, 0) AS NVARCHAR(50)) + '%' + + @LineFeed + +N'SQLHandle: ' + +CONVERT(NVARCHAR(128),Grants.[sql_handle],1), + 'https://www.brentozar.com/memory-grants-sql-servers-public-toilet/' AS URL, + SQLText.[text], + QueryPlan.query_plan + FROM sys.dm_exec_query_memory_grants AS Grants + OUTER APPLY sys.dm_exec_sql_text(Grants.[sql_handle]) AS SQLText + OUTER APPLY sys.dm_exec_query_plan(Grants.[plan_handle]) AS QueryPlan + WHERE Grants.granted_memory_kb > ((@MemoryGrantThresholdPct/100.00)*(@MaxWorkspace*1024)); - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.plan_warnings = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 8, - 50, - 'Execution Plans', - 'Query Plan Warnings', - 'http://brentozar.com/blitzcache/query-plan-warnings/', - 'Warnings detected in execution plans. SQL Server is telling you that something bad is going on that requires your attention.') ; + /* Query Problems - Memory Leak in USERSTORE_TOKENPERM Cache - CheckID 45 */ + IF EXISTS (SELECT * FROM sys.all_columns WHERE object_id = OBJECT_ID('sys.dm_os_memory_clerks') AND name = 'pages_kb') + BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 45',10,1) WITH NOWAIT; + END - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.long_running = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 9, - 50, - 'Performance', - 'Long Running Queries', - 'http://brentozar.com/blitzcache/long-running-queries/', - 'Long running queries have been found. These are queries with an average duration longer than ' - + CAST(@long_running_query_warning_seconds / 1000 / 1000 AS VARCHAR(5)) - + ' second(s). These queries should be investigated for additional tuning options.') ; + /* SQL 2012+ version */ + SET @StringToExecute = N' + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, URL) + SELECT 45 AS CheckID, + 50 AS Priority, + ''Query Problems'' AS FindingsGroup, + ''Memory Leak in USERSTORE_TOKENPERM Cache'' AS Finding, + N''UserStore_TokenPerm clerk is using '' + CAST(CAST(SUM(CASE WHEN type = ''USERSTORE_TOKENPERM'' AND name = ''TokenAndPermUserStore'' THEN pages_kb * 1.0 ELSE 0.0 END) / 1024.0 / 1024.0 AS INT) AS NVARCHAR(100)) + + N''GB RAM, total buffer pool is '' + CAST(CAST(SUM(pages_kb) / 1024.0 / 1024.0 AS INT) AS NVARCHAR(100)) + N''GB.'' + AS details, + ''https://www.BrentOzar.com/go/userstore'' AS URL + FROM sys.dm_os_memory_clerks + HAVING SUM(CASE WHEN type = ''USERSTORE_TOKENPERM'' AND name = ''TokenAndPermUserStore'' THEN pages_kb * 1.0 ELSE 0.0 END) / SUM(pages_kb) >= 0.1 + AND SUM(pages_kb) / 1024.0 / 1024.0 >= 1; /* At least 1GB RAM overall */'; + EXEC sp_executesql @StringToExecute; + END + ELSE + BEGIN + /* Antiques Roadshow SQL 2008R2 - version */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 45 (Legacy version)',10,1) WITH NOWAIT; + END - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.missing_index_count > 0 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 10, - 50, - 'Performance', - 'Missing Index Request', - 'http://brentozar.com/blitzcache/missing-index-request/', - 'Queries found with missing indexes.'); + SET @StringToExecute = N' + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, URL) + SELECT 45 AS CheckID, + 50 AS Priority, + ''Performance'' AS FindingsGroup, + ''Memory Leak in USERSTORE_TOKENPERM Cache'' AS Finding, + N''UserStore_TokenPerm clerk is using '' + CAST(CAST(SUM(CASE WHEN type = ''USERSTORE_TOKENPERM'' AND name = ''TokenAndPermUserStore'' THEN single_pages_kb + multi_pages_kb * 1.0 ELSE 0.0 END) / 1024.0 / 1024.0 AS INT) AS NVARCHAR(100)) + + N''GB RAM, total buffer pool is '' + CAST(CAST(SUM(single_pages_kb + multi_pages_kb) / 1024.0 / 1024.0 AS INT) AS NVARCHAR(100)) + N''GB.'' + AS details, + ''https://www.BrentOzar.com/go/userstore'' AS URL + FROM sys.dm_os_memory_clerks + HAVING SUM(CASE WHEN type = ''USERSTORE_TOKENPERM'' AND name = ''TokenAndPermUserStore'' THEN single_pages_kb + multi_pages_kb * 1.0 ELSE 0.0 END) / SUM(single_pages_kb + multi_pages_kb) >= 0.1 + AND SUM(single_pages_kb + multi_pages_kb) / 1024.0 / 1024.0 >= 1; /* At least 1GB RAM overall */'; + EXEC sp_executesql @StringToExecute; + END - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.downlevel_estimator = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 13, - 200, - 'Cardinality', - 'Legacy Cardinality Estimator in Use', - 'http://brentozar.com/blitzcache/legacy-cardinality-estimator/', - 'A legacy cardinality estimator is being used by one or more queries. Investigate whether you need to be using this cardinality estimator. This may be caused by compatibility levels, global trace flags, or query level trace flags.'); - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.implicit_conversions = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 14, - 50, - 'Performance', - 'Implicit Conversions', - 'http://brentozar.com/go/implicit', - 'One or more queries are comparing two fields that are not of the same data type.') ; - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE busy_loops = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 16, - 100, - 'Performance', - 'Busy Loops', - 'http://brentozar.com/blitzcache/busy-loops/', - 'Operations have been found that are executed 100 times more often than the number of rows returned by each iteration. This is an indicator that something is off in query execution.'); + IF @Seconds > 0 + BEGIN - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE tvf_join = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 17, - 50, - 'Performance', - 'Joining to table valued functions', - 'http://brentozar.com/blitzcache/tvf-join/', - 'Execution plans have been found that join to table valued functions (TVFs). TVFs produce inaccurate estimates of the number of rows returned and can lead to any number of query plan problems.'); + IF EXISTS ( SELECT 1/0 + FROM sys.all_objects AS ao + WHERE ao.name = 'dm_exec_query_profiles' ) + BEGIN - IF EXISTS (SELECT 1/0 - FROM #working_warnings - WHERE compile_timeout = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 18, - 50, - 'Execution Plans', - 'Compilation timeout', - 'http://brentozar.com/blitzcache/compilation-timeout/', - 'Query compilation timed out for one or more queries. SQL Server did not find a plan that meets acceptable performance criteria in the time allotted so the best guess was returned. There is a very good chance that this plan isn''t even below average - it''s probably terrible.'); + IF EXISTS( SELECT 1/0 + FROM sys.dm_exec_requests AS r + JOIN sys.dm_exec_sessions AS s + ON r.session_id = s.session_id + WHERE s.host_name IS NOT NULL + AND r.total_elapsed_time > 5000 ) + BEGIN - IF EXISTS (SELECT 1/0 - FROM #working_warnings - WHERE compile_memory_limit_exceeded = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 19, - 50, - 'Execution Plans', - 'Compilation memory limit exceeded', - 'http://brentozar.com/blitzcache/compile-memory-limit-exceeded/', - 'The optimizer has a limited amount of memory available. One or more queries are complex enough that SQL Server was unable to allocate enough memory to fully optimize the query. A best fit plan was found, and it''s probably terrible.'); + SET @StringToExecute = N' + DECLARE @bad_estimate TABLE + ( + session_id INT, + request_id INT, + estimate_inaccuracy BIT + ); + + INSERT @bad_estimate ( session_id, request_id, estimate_inaccuracy ) + SELECT x.session_id, + x.request_id, + x.estimate_inaccuracy + FROM ( + SELECT deqp.session_id, + deqp.request_id, + CASE WHEN deqp.row_count > ( deqp.estimate_row_count * 10000 ) + THEN 1 + ELSE 0 + END AS estimate_inaccuracy + FROM sys.dm_exec_query_profiles AS deqp + WHERE deqp.session_id <> @@SPID + ) AS x + WHERE x.estimate_inaccuracy = 1 + GROUP BY x.session_id, + x.request_id, + x.estimate_inaccuracy; + + DECLARE @parallelism_skew TABLE + ( + session_id INT, + request_id INT, + parallelism_skew BIT + ); + + INSERT @parallelism_skew ( session_id, request_id, parallelism_skew ) + SELECT y.session_id, + y.request_id, + y.parallelism_skew + FROM ( + SELECT x.session_id, + x.request_id, + x.node_id, + x.thread_id, + x.row_count, + x.sum_node_rows, + x.node_dop, + x.sum_node_rows / x.node_dop AS even_distribution, + x.row_count / (1. * ISNULL(NULLIF(x.sum_node_rows / x.node_dop, 0), 1)) AS skew_percent, + CASE + WHEN x.row_count > 10000 + AND x.row_count / (1. * ISNULL(NULLIF(x.sum_node_rows / x.node_dop, 0), 1)) > 2. + THEN 1 + WHEN x.row_count > 10000 + AND x.row_count / (1. * ISNULL(NULLIF(x.sum_node_rows / x.node_dop, 0), 1)) < 0.5 + THEN 1 + ELSE 0 + END AS parallelism_skew + FROM ( + SELECT deqp.session_id, + deqp.request_id, + deqp.node_id, + deqp.thread_id, + deqp.row_count, + SUM(deqp.row_count) + OVER ( PARTITION BY deqp.session_id, + deqp.request_id, + deqp.node_id + ORDER BY deqp.row_count + ROWS BETWEEN UNBOUNDED PRECEDING + AND UNBOUNDED FOLLOWING ) + AS sum_node_rows, + COUNT(*) + OVER ( PARTITION BY deqp.session_id, + deqp.request_id, + deqp.node_id + ORDER BY deqp.row_count + ROWS BETWEEN UNBOUNDED PRECEDING + AND UNBOUNDED FOLLOWING ) + AS node_dop + FROM sys.dm_exec_query_profiles AS deqp + WHERE deqp.thread_id > 0 + AND deqp.session_id <> @@SPID + AND EXISTS + ( + SELECT 1/0 + FROM sys.dm_exec_query_profiles AS deqp2 + WHERE deqp.session_id = deqp2.session_id + AND deqp.node_id = deqp2.node_id + AND deqp2.thread_id > 0 + GROUP BY deqp2.session_id, deqp2.node_id + HAVING COUNT(deqp2.node_id) > 1 + ) + ) AS x + ) AS y + WHERE y.parallelism_skew = 1 + GROUP BY y.session_id, + y.request_id, + y.parallelism_skew; + + /* Queries in dm_exec_query_profiles showing signs of poor cardinality estimates - CheckID 42 */ + IF (@Debug = 1) + BEGIN + RAISERROR(''Running CheckID 42'',10,1) WITH NOWAIT; + END - IF EXISTS (SELECT 1/0 - FROM #working_warnings - WHERE warning_no_join_predicate = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 20, - 10, - 'Execution Plans', - 'No join predicate', - 'http://brentozar.com/blitzcache/no-join-predicate/', - 'Operators in a query have no join predicate. This means that all rows from one table will be matched with all rows from anther table producing a Cartesian product. That''s a whole lot of rows. This may be your goal, but it''s important to investigate why this is happening.'); + INSERT INTO #BlitzFirstResults + (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, OpenTransactionCount, QueryHash, QueryPlan) + SELECT 42 AS CheckID, + 100 AS Priority, + ''Query Performance'' AS FindingsGroup, + ''Queries with 10000x cardinality misestimations'' AS Findings, + ''https://brentozar.com/go/skewedup'' AS URL, + ''The query on SPID '' + + RTRIM(b.session_id) + + '' has been running for '' + + RTRIM(r.total_elapsed_time / 1000) + + '' seconds, with a large cardinality misestimate'' AS Details, + ''No quick fix here: time to dig into the actual execution plan. '' AS HowToStopIt, + r.start_time, + s.login_name, + s.nt_user_name, + s.program_name, + s.host_name, + r.database_id, + DB_NAME(r.database_id), + dest.text, + s.open_transaction_count, + r.query_hash, '; - IF EXISTS (SELECT 1/0 - FROM #working_warnings - WHERE plan_multiple_plans = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 21, - 200, - 'Execution Plans', - 'Multiple execution plans', - 'http://brentozar.com/blitzcache/multiple-plans/', - 'Queries exist with multiple execution plans (as determined by query_plan_hash). Investigate possible ways to parameterize these queries or otherwise reduce the plan count.'); + IF @dm_exec_query_statistics_xml = 1 + SET @StringToExecute = @StringToExecute + N' COALESCE(qs_live.query_plan, qp.query_plan) AS query_plan '; + ELSE + SET @StringToExecute = @StringToExecute + N' qp.query_plan '; - IF EXISTS (SELECT 1/0 - FROM #working_warnings - WHERE unmatched_index_count > 0 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 22, - 100, - 'Performance', - 'Unmatched indexes', - 'http://brentozar.com/blitzcache/unmatched-indexes', - 'An index could have been used, but SQL Server chose not to use it - likely due to parameterization and filtered indexes.'); + SET @StringToExecute = @StringToExecute + N' + FROM @bad_estimate AS b + JOIN sys.dm_exec_requests AS r + ON r.session_id = b.session_id + AND r.request_id = b.request_id + JOIN sys.dm_exec_sessions AS s + ON s.session_id = b.session_id + CROSS APPLY sys.dm_exec_sql_text(r.sql_handle) AS dest + CROSS APPLY sys.dm_exec_query_plan(r.plan_handle) AS qp '; - IF EXISTS (SELECT 1/0 - FROM #working_warnings - WHERE unparameterized_query = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 23, - 100, - 'Parameterization', - 'Unparameterized queries', - 'http://brentozar.com/blitzcache/unparameterized-queries', - 'Unparameterized queries found. These could be ad hoc queries, data exploration, or queries using "OPTIMIZE FOR UNKNOWN".'); + IF EXISTS (SELECT * FROM sys.all_objects WHERE name = 'dm_exec_query_statistics_xml') + SET @StringToExecute = @StringToExecute + N' OUTER APPLY sys.dm_exec_query_statistics_xml(s.session_id) qs_live '; + + + SET @StringToExecute = @StringToExecute + N'; - IF EXISTS (SELECT 1/0 - FROM #working_warnings - WHERE is_trivial = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 24, - 100, - 'Execution Plans', - 'Trivial Plans', - 'http://brentozar.com/blitzcache/trivial-plans', - 'Trivial plans get almost no optimization. If you''re finding these in the top worst queries, something may be going wrong.'); - - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.is_forced_serial= 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 25, - 10, - 'Execution Plans', - 'Forced Serialization', - 'http://www.brentozar.com/blitzcache/forced-serialization/', - 'Something in your plan is forcing a serial query. Further investigation is needed if this is not by design.') ; + /* Queries in dm_exec_query_profiles showing signs of unbalanced parallelism - CheckID 43 */ + IF (@Debug = 1) + BEGIN + RAISERROR(''Running CheckID 43'',10,1) WITH NOWAIT; + END - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.is_key_lookup_expensive= 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 26, - 100, - 'Execution Plans', - 'Expensive Key Lookups', - 'http://www.brentozar.com/blitzcache/expensive-key-lookups/', - 'There''s a key lookup in your plan that costs >=50% of the total plan cost.') ; + INSERT INTO #BlitzFirstResults + (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, OpenTransactionCount, QueryHash, QueryPlan) + SELECT 43 AS CheckID, + 100 AS Priority, + ''Query Performance'' AS FindingsGroup, + ''Queries with 10000x skewed parallelism'' AS Findings, + ''https://brentozar.com/go/skewedup'' AS URL, + ''The query on SPID '' + + RTRIM(p.session_id) + + '' has been running for '' + + RTRIM(r.total_elapsed_time / 1000) + + '' seconds, with a parallel threads doing uneven work.'' AS Details, + ''No quick fix here: time to dig into the actual execution plan. '' AS HowToStopIt, + r.start_time, + s.login_name, + s.nt_user_name, + s.program_name, + s.host_name, + r.database_id, + DB_NAME(r.database_id), + dest.text, + s.open_transaction_count, + r.query_hash, '; - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.is_remote_query_expensive= 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 28, - 100, - 'Execution Plans', - 'Expensive Remote Query', - 'http://www.brentozar.com/blitzcache/expensive-remote-query/', - 'There''s a remote query in your plan that costs >=50% of the total plan cost.') ; + IF @dm_exec_query_statistics_xml = 1 + SET @StringToExecute = @StringToExecute + N' COALESCE(qs_live.query_plan, qp.query_plan) AS query_plan '; + ELSE + SET @StringToExecute = @StringToExecute + N' qp.query_plan '; + + SET @StringToExecute = @StringToExecute + N' + FROM @parallelism_skew AS p + JOIN sys.dm_exec_requests AS r + ON r.session_id = p.session_id + AND r.request_id = p.request_id + JOIN sys.dm_exec_sessions AS s + ON s.session_id = p.session_id + CROSS APPLY sys.dm_exec_sql_text(r.sql_handle) AS dest + CROSS APPLY sys.dm_exec_query_plan(r.plan_handle) AS qp '; + + IF EXISTS (SELECT * FROM sys.all_objects WHERE name = 'dm_exec_query_statistics_xml') + SET @StringToExecute = @StringToExecute + N' OUTER APPLY sys.dm_exec_query_statistics_xml(s.session_id) qs_live '; + + + SET @StringToExecute = @StringToExecute + N';'; - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.trace_flags_session IS NOT NULL - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 29, - 100, - 'Trace Flags', - 'Session Level Trace Flags Enabled', - 'https://www.brentozar.com/blitz/trace-flags-enabled-globally/', - 'Someone is enabling session level Trace Flags in a query.') ; + EXECUTE sp_executesql @StringToExecute, N'@Debug BIT',@Debug = @Debug; + END + + END + END - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.is_unused_grant IS NOT NULL - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 30, - 100, - 'Unused memory grants', - 'Queries are asking for more memory than they''re using', - 'https://www.brentozar.com/blitzcache/unused-memory-grants/', - 'Queries have large unused memory grants. This can cause concurrency issues, if queries are waiting a long time to get memory to run.') ; + /* Server Performance - High CPU Utilization - CheckID 24 */ + IF @Seconds < 30 + BEGIN + /* If we're waiting less than 30 seconds, run this check now rather than wait til the end. + We get this data from the ring buffers, and it's only updated once per minute, so might + as well get it now - whereas if we're checking 30+ seconds, it might get updated by the + end of our sp_BlitzFirst session. */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 24',10,1) WITH NOWAIT; + END - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.function_count > 0 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 31, - 100, - 'Compute Scalar That References A Function', - 'This could be trouble if you''re using Scalar Functions or MSTVFs', - 'https://www.brentozar.com/blitzcache/compute-scalar-functions/', - 'Both of these will force queries to run serially, run at least once per row, and may result in poor cardinality estimates.') ; + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) + SELECT 24, 50, 'Server Performance', 'High CPU Utilization', CAST(100 - SystemIdle AS NVARCHAR(20)) + N'%.', 100 - SystemIdle, 'http://www.BrentOzar.com/go/cpu' + FROM ( + SELECT record, + record.value('(./Record/SchedulerMonitorEvent/SystemHealth/SystemIdle)[1]', 'int') AS SystemIdle + FROM ( + SELECT TOP 1 CONVERT(XML, record) AS record + FROM sys.dm_os_ring_buffers + WHERE ring_buffer_type = N'RING_BUFFER_SCHEDULER_MONITOR' + AND record LIKE '%%' + ORDER BY timestamp DESC) AS rb + ) AS y + WHERE 100 - SystemIdle >= 50; - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.clr_function_count > 0 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 32, - 100, - 'Compute Scalar That References A CLR Function', - 'This could be trouble if your CLR functions perform data access', - 'https://www.brentozar.com/blitzcache/compute-scalar-functions/', - 'May force queries to run serially, run at least once per row, and may result in poor cardinlity estimates.') ; + /* CPU Utilization - CheckID 23 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 23',10,1) WITH NOWAIT; + END + IF SERVERPROPERTY('Edition') <> 'SQL Azure' + WITH y + AS + ( + SELECT CONVERT(VARCHAR(5), 100 - ca.c.value('.', 'INT')) AS system_idle, + CONVERT(VARCHAR(30), rb.event_date) AS event_date, + CONVERT(VARCHAR(8000), rb.record) AS record + FROM + ( SELECT CONVERT(XML, dorb.record) AS record, + DATEADD(ms, ( ts.ms_ticks - dorb.timestamp ), GETDATE()) AS event_date + FROM sys.dm_os_ring_buffers AS dorb + CROSS JOIN + ( SELECT dosi.ms_ticks FROM sys.dm_os_sys_info AS dosi ) AS ts + WHERE dorb.ring_buffer_type = N'RING_BUFFER_SCHEDULER_MONITOR' + AND record LIKE '%%' ) AS rb + CROSS APPLY rb.record.nodes('/Record/SchedulerMonitorEvent/SystemHealth/SystemIdle') AS ca(c) + ) + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL, HowToStopIt) + SELECT TOP 1 + 23, + 250, + 'Server Info', + 'CPU Utilization', + y.system_idle + N'%. Ring buffer details: ' + CAST(y.record AS NVARCHAR(4000)), + y.system_idle , + 'http://www.BrentOzar.com/go/cpu', + STUFF(( SELECT TOP 2147483647 + CHAR(10) + CHAR(13) + + y2.system_idle + + '% ON ' + + y2.event_date + + ' Ring buffer details: ' + + y2.record + FROM y AS y2 + ORDER BY y2.event_date DESC + FOR XML PATH(N''), TYPE ).value(N'.[1]', N'VARCHAR(MAX)'), 1, 1, N'') AS query + FROM y + ORDER BY y.event_date DESC; - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.is_table_variable = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 33, - 100, - 'Table Variables detected', - 'Beware nasty side effects', - 'https://www.brentozar.com/blitzcache/table-variables/', - 'All modifications are single threaded, and selects have really low row estimates.') ; + + /* Highlight if non SQL processes are using >25% CPU - CheckID 28 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 28',10,1) WITH NOWAIT; + END - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.no_stats_warning = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 35, - 100, - 'Columns with no statistics', - 'Poor cardinality estimates may ensue', - 'https://www.brentozar.com/blitzcache/columns-no-statistics/', - 'Sometimes this happens with indexed views, other times because auto create stats is turned off.') ; + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) + SELECT 28, 50, 'Server Performance', 'High CPU Utilization - Not SQL', CONVERT(NVARCHAR(100),100 - (y.SQLUsage + y.SystemIdle)) + N'% - Other Processes (not SQL Server) are using this much CPU. This may impact on the performance of your SQL Server instance', 100 - (y.SQLUsage + y.SystemIdle), 'http://www.BrentOzar.com/go/cpu' + FROM ( + SELECT record, + record.value('(./Record/SchedulerMonitorEvent/SystemHealth/SystemIdle)[1]', 'int') AS SystemIdle + ,record.value('(./Record/SchedulerMonitorEvent/SystemHealth/ProcessUtilization)[1]', 'int') AS SQLUsage + FROM ( + SELECT TOP 1 CONVERT(XML, record) AS record + FROM sys.dm_os_ring_buffers + WHERE ring_buffer_type = N'RING_BUFFER_SCHEDULER_MONITOR' + AND record LIKE '%%' + ORDER BY timestamp DESC) AS rb + ) AS y + WHERE 100 - (y.SQLUsage + y.SystemIdle) >= 25; + + END; /* IF @Seconds < 30 */ - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.relop_warnings = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 36, - 100, - 'Operator Warnings', - 'SQL is throwing operator level plan warnings', - 'http://brentozar.com/blitzcache/query-plan-warnings/', - 'Check the plan for more details.') ; + /* Query Problems - Statistics Updated Recently - CheckID 44 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 44',10,1) WITH NOWAIT; + END - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.is_table_scan = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 37, - 100, - 'Table Scans', - 'Your database has HEAPs', - 'https://www.brentozar.com/archive/2012/05/video-heaps/', - 'This may not be a problem. Run sp_BlitzIndex for more information.') ; - - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.backwards_scan = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 38, - 100, - 'Backwards Scans', - 'Indexes are being read backwards', - 'https://www.brentozar.com/blitzcache/backwards-scans/', - 'This isn''t always a problem. They can cause serial zones in plans, and may need an index to match sort order.') ; + IF 20 >= (SELECT COUNT(*) FROM sys.databases WHERE name NOT IN ('master', 'model', 'msdb', 'tempdb')) + BEGIN + CREATE TABLE #UpdatedStats (HowToStopIt NVARCHAR(4000), RowsForSorting BIGINT); + IF EXISTS(SELECT * FROM sys.all_objects WHERE name = 'dm_db_stats_properties') + BEGIN + /* We don't want to hang around to obtain locks */ + SET LOCK_TIMEOUT 0; - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.forced_index = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 39, - 100, - 'Index forcing', - 'Someone is using hints to force index usage', - 'https://www.brentozar.com/blitzcache/optimizer-forcing/', - 'This can cause inefficient plans, and will prevent missing index requests.') ; + EXEC sp_MSforeachdb N'USE [?]; + SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; SET LOCK_TIMEOUT 1000; + BEGIN TRY + INSERT INTO #UpdatedStats(HowToStopIt, RowsForSorting) + SELECT HowToStopIt = + QUOTENAME(DB_NAME()) + N''.'' + + QUOTENAME(SCHEMA_NAME(obj.schema_id)) + N''.'' + + QUOTENAME(obj.name) + + N'' statistic '' + QUOTENAME(stat.name) + + N'' was updated on '' + CONVERT(NVARCHAR(50), sp.last_updated, 121) + N'','' + + N'' had '' + CAST(sp.rows AS NVARCHAR(50)) + N'' rows, with '' + + CAST(sp.rows_sampled AS NVARCHAR(50)) + N'' rows sampled,'' + + N'' producing '' + CAST(sp.steps AS NVARCHAR(50)) + N'' steps in the histogram.'', + sp.rows + FROM sys.objects AS obj WITH (NOLOCK) + INNER JOIN sys.stats AS stat WITH (NOLOCK) ON stat.object_id = obj.object_id + CROSS APPLY sys.dm_db_stats_properties(stat.object_id, stat.stats_id) AS sp + WHERE sp.last_updated > DATEADD(MI, -15, GETDATE()) + AND obj.is_ms_shipped = 0 + AND ''[?]'' <> ''[tempdb]''; + END TRY + BEGIN CATCH + IF (ERROR_NUMBER() = 1222) + BEGIN + INSERT INTO #UpdatedStats(HowToStopIt, RowsForSorting) + SELECT HowToStopIt = + QUOTENAME(DB_NAME()) + + N'' No information could be retrieved as the lock timeout was exceeded,''+ + N'' this is likely due to an Index operation in Progress'', + -1 + END + ELSE + BEGIN + INSERT INTO #UpdatedStats(HowToStopIt, RowsForSorting) + SELECT HowToStopIt = + QUOTENAME(DB_NAME()) + + N'' No information could be retrieved as a result of error: ''+ + CAST(ERROR_NUMBER() AS NVARCHAR(10)) + + N'' with message: ''+ + CAST(ERROR_MESSAGE() AS NVARCHAR(128)), + -1 + END + END CATCH'; - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.forced_seek = 1 - OR p.forced_scan = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 40, - 100, - 'Seek/Scan forcing', - 'Someone is using hints to force index seeks/scans', - 'https://www.brentozar.com/blitzcache/optimizer-forcing/', - 'This can cause inefficient plans by taking seek vs scan choice away from the optimizer.') ; + /* Set timeout back to a default value of -1 */ + SET LOCK_TIMEOUT -1; + END; + + /* We mark timeout exceeded with a -1 so only show these IF there is statistics info that succeeded */ + IF EXISTS (SELECT * FROM #UpdatedStats WHERE RowsForSorting > -1) + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) + SELECT 44 AS CheckId, + 50 AS Priority, + 'Query Problems' AS FindingGroup, + 'Statistics Updated Recently' AS Finding, + 'http://www.BrentOzar.com/go/stats' AS URL, + 'In the last 15 minutes, statistics were updated. To see which ones, click the HowToStopIt column.' + @LineFeed + @LineFeed + + 'This effectively clears the plan cache for queries that involve these tables,' + @LineFeed + + 'which thereby causes parameter sniffing: those queries are now getting brand new' + @LineFeed + + 'query plans based on whatever parameters happen to call them next.' + @LineFeed + @LineFeed + + 'Be on the lookout for sudden parameter sniffing issues after this time range.', + HowToStopIt = (SELECT (SELECT HowToStopIt + NCHAR(10)) + FROM #UpdatedStats + ORDER BY RowsForSorting DESC + FOR XML PATH('')); - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.columnstore_row_mode = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 41, - 100, - 'ColumnStore indexes operating in Row Mode', - 'Batch Mode is optimal for ColumnStore indexes', - 'https://www.brentozar.com/blitzcache/columnstore-indexes-operating-row-mode/', - 'ColumnStore indexes operating in Row Mode indicate really poor query choices.') ; + END - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.is_computed_scalar = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 42, - 50, - 'Computed Columns Referencing Scalar UDFs', - 'This makes a whole lot of stuff run serially', - 'https://www.brentozar.com/blitzcache/computed-columns-referencing-functions/', - 'This can cause a whole mess of bad serializartion problems.') ; + RAISERROR('Finished running investigatory queries',10,1) WITH NOWAIT; - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.is_sort_expensive = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 43, - 100, - 'Execution Plans', - 'Expensive Sort', - 'http://www.brentozar.com/blitzcache/expensive-sorts/', - 'There''s a sort in your plan that costs >=50% of the total plan cost.') ; - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.is_computed_filter = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 44, - 50, - 'Filters Referencing Scalar UDFs', - 'This forces serialization', - 'https://www.brentozar.com/blitzcache/compute-scalar-functions/', - 'Someone put a Scalar UDF in the WHERE clause!') ; + /* End of checks. If we haven't waited @Seconds seconds, wait. */ + IF DATEADD(SECOND,1,SYSDATETIMEOFFSET()) < @FinishSampleTime + BEGIN + RAISERROR('Waiting to match @Seconds parameter',10,1) WITH NOWAIT; + WAITFOR TIME @FinishSampleTimeWaitFor; + END; - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.index_ops >= 5 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 45, - 100, - 'Many Indexes Modified', - 'Write Queries Are Hitting >= 5 Indexes', - 'https://www.brentozar.com/blitzcache/many-indexes-modified/', - 'This can cause lots of hidden I/O -- Run sp_BlitzIndex for more information.') ; + RAISERROR('Capturing second pass of wait stats, perfmon counters, file stats',10,1) WITH NOWAIT; + /* Populate #FileStats, #PerfmonStats, #WaitStats with DMV data. In a second, we'll compare these. */ + INSERT #WaitStats(Pass, SampleTime, wait_type, wait_time_ms, signal_wait_time_ms, waiting_tasks_count) + SELECT + x.Pass, + x.SampleTime, + x.wait_type, + SUM(x.sum_wait_time_ms) AS sum_wait_time_ms, + SUM(x.sum_signal_wait_time_ms) AS sum_signal_wait_time_ms, + SUM(x.sum_waiting_tasks) AS sum_waiting_tasks + FROM ( + SELECT + 2 AS Pass, + SYSDATETIMEOFFSET() AS SampleTime, + owt.wait_type, + SUM(owt.wait_duration_ms) OVER (PARTITION BY owt.wait_type, owt.session_id) + - CASE WHEN @Seconds = 0 THEN 0 ELSE (@Seconds * 1000) END AS sum_wait_time_ms, + 0 AS sum_signal_wait_time_ms, + CASE @Seconds WHEN 0 THEN 0 ELSE 1 END AS sum_waiting_tasks + FROM sys.dm_os_waiting_tasks owt + WHERE owt.session_id > 50 + AND owt.wait_duration_ms >= CASE @Seconds WHEN 0 THEN 0 ELSE @Seconds * 1000 END + UNION ALL + SELECT + 2 AS Pass, + SYSDATETIMEOFFSET() AS SampleTime, + os.wait_type, + SUM(os.wait_time_ms) OVER (PARTITION BY os.wait_type) AS sum_wait_time_ms, + SUM(os.signal_wait_time_ms) OVER (PARTITION BY os.wait_type ) AS sum_signal_wait_time_ms, + SUM(os.waiting_tasks_count) OVER (PARTITION BY os.wait_type) AS sum_waiting_tasks + FROM sys.dm_os_wait_stats os + ) x + WHERE NOT EXISTS + ( + SELECT * + FROM ##WaitCategories AS wc + WHERE wc.WaitType = x.wait_type + AND wc.Ignorable = 1 + ) + GROUP BY x.Pass, x.SampleTime, x.wait_type + ORDER BY sum_wait_time_ms DESC; - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.is_row_level = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 46, - 100, - 'Plan Confusion', - 'Row Level Security is in use', - 'https://www.brentozar.com/blitzcache/row-level-security/', - 'You may see a lot of confusing junk in your query plan.') ; + INSERT INTO #FileStats (Pass, SampleTime, DatabaseID, FileID, DatabaseName, FileLogicalName, SizeOnDiskMB, io_stall_read_ms , + num_of_reads, [bytes_read] , io_stall_write_ms,num_of_writes, [bytes_written], PhysicalName, TypeDesc, avg_stall_read_ms, avg_stall_write_ms) + SELECT 2 AS Pass, + SYSDATETIMEOFFSET() AS SampleTime, + mf.[database_id], + mf.[file_id], + DB_NAME(vfs.database_id) AS [db_name], + mf.name + N' [' + mf.type_desc COLLATE SQL_Latin1_General_CP1_CI_AS + N']' AS file_logical_name , + CAST(( ( vfs.size_on_disk_bytes / 1024.0 ) / 1024.0 ) AS INT) AS size_on_disk_mb , + vfs.io_stall_read_ms , + vfs.num_of_reads , + vfs.[num_of_bytes_read], + vfs.io_stall_write_ms , + vfs.num_of_writes , + vfs.[num_of_bytes_written], + mf.physical_name, + mf.type_desc, + 0, + 0 + FROM sys.dm_io_virtual_file_stats (NULL, NULL) AS vfs + INNER JOIN #MasterFiles AS mf ON vfs.file_id = mf.file_id + AND vfs.database_id = mf.database_id + WHERE vfs.num_of_reads > 0 + OR vfs.num_of_writes > 0; - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.is_spatial = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 47, - 200, - 'Spatial Abuse', - 'You hit a Spatial Index', - 'https://www.brentozar.com/blitzcache/spatial-indexes/', - 'Purely informational.') ; + INSERT INTO #PerfmonStats (Pass, SampleTime, [object_name],[counter_name],[instance_name],[cntr_value],[cntr_type]) + SELECT 2 AS Pass, + SYSDATETIMEOFFSET() AS SampleTime, + RTRIM(dmv.object_name), RTRIM(dmv.counter_name), RTRIM(dmv.instance_name), dmv.cntr_value, dmv.cntr_type + FROM #PerfmonCounters counters + INNER JOIN sys.dm_os_performance_counters dmv ON counters.counter_name COLLATE SQL_Latin1_General_CP1_CI_AS = RTRIM(dmv.counter_name) COLLATE SQL_Latin1_General_CP1_CI_AS + AND counters.[object_name] COLLATE SQL_Latin1_General_CP1_CI_AS = RTRIM(dmv.[object_name]) COLLATE SQL_Latin1_General_CP1_CI_AS + AND (counters.[instance_name] IS NULL OR counters.[instance_name] COLLATE SQL_Latin1_General_CP1_CI_AS = RTRIM(dmv.[instance_name]) COLLATE SQL_Latin1_General_CP1_CI_AS); - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.index_dml = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 48, - 150, - 'Index DML', - 'Indexes were created or dropped', - 'https://www.brentozar.com/blitzcache/index-dml/', - 'This can cause recompiles and stuff.') ; + /* Set the latencies and averages. We could do this with a CTE, but we're not ambitious today. */ + UPDATE fNow + SET avg_stall_read_ms = ((fNow.io_stall_read_ms - fBase.io_stall_read_ms) / (fNow.num_of_reads - fBase.num_of_reads)) + FROM #FileStats fNow + INNER JOIN #FileStats fBase ON fNow.DatabaseID = fBase.DatabaseID AND fNow.FileID = fBase.FileID AND fNow.SampleTime > fBase.SampleTime AND fNow.num_of_reads > fBase.num_of_reads AND fNow.io_stall_read_ms > fBase.io_stall_read_ms + WHERE (fNow.num_of_reads - fBase.num_of_reads) > 0; - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.table_dml = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 49, - 150, - 'Table DML', - 'Tables were created or dropped', - 'https://www.brentozar.com/blitzcache/table-dml/', - 'This can cause recompiles and stuff.') ; + UPDATE fNow + SET avg_stall_write_ms = ((fNow.io_stall_write_ms - fBase.io_stall_write_ms) / (fNow.num_of_writes - fBase.num_of_writes)) + FROM #FileStats fNow + INNER JOIN #FileStats fBase ON fNow.DatabaseID = fBase.DatabaseID AND fNow.FileID = fBase.FileID AND fNow.SampleTime > fBase.SampleTime AND fNow.num_of_writes > fBase.num_of_writes AND fNow.io_stall_write_ms > fBase.io_stall_write_ms + WHERE (fNow.num_of_writes - fBase.num_of_writes) > 0; + + UPDATE pNow + SET [value_delta] = pNow.cntr_value - pFirst.cntr_value, + [value_per_second] = ((1.0 * pNow.cntr_value - pFirst.cntr_value) / DATEDIFF(ss, pFirst.SampleTime, pNow.SampleTime)) + FROM #PerfmonStats pNow + INNER JOIN #PerfmonStats pFirst ON pFirst.[object_name] = pNow.[object_name] AND pFirst.counter_name = pNow.counter_name AND (pFirst.instance_name = pNow.instance_name OR (pFirst.instance_name IS NULL AND pNow.instance_name IS NULL)) + AND pNow.ID > pFirst.ID + WHERE DATEDIFF(ss, pFirst.SampleTime, pNow.SampleTime) > 0; - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.long_running_low_cpu = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 50, - 150, - 'Long Running Low CPU', - 'You have a query that runs for much longer than it uses CPU', - 'https://www.brentozar.com/blitzcache/long-running-low-cpu/', - 'This can be a sign of blocking, linked servers, or poor client application code (ASYNC_NETWORK_IO).') ; - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.low_cost_high_cpu = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 51, - 150, - 'Low Cost Query With High CPU', - 'You have a low cost query that uses a lot of CPU', - 'https://www.brentozar.com/blitzcache/low-cost-high-cpu/', - 'This can be a sign of functions or Dynamic SQL that calls black-box code.') ; + /* Query Stats - If we're within 10 seconds of our projected finish time, do the plan cache analysis. - CheckID 18 */ + IF DATEDIFF(ss, @FinishSampleTime, SYSDATETIMEOFFSET()) > 10 AND @CheckProcedureCache = 1 + BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 18',10,1) WITH NOWAIT; + END - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.stale_stats = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 52, - 150, - 'Biblical Statistics', - 'Statistics used in queries are >7 days old with >100k modifications', - 'https://www.brentozar.com/blitzcache/stale-statistics/', - 'Ever heard of updating statistics?') ; + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (18, 210, 'Query Stats', 'Plan Cache Analysis Skipped', 'http://www.BrentOzar.com/go/topqueries', + 'Due to excessive load, the plan cache analysis was skipped. To override this, use @ExpertMode = 1.'); - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.is_adaptive = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 53, - 150, - 'Adaptive joins', - 'This is pretty cool -- you''re living in the future.', - 'https://www.brentozar.com/blitzcache/adaptive-joins/', - 'Joe Sack rules.') ; + END; + ELSE IF @CheckProcedureCache = 1 + BEGIN - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.is_spool_expensive = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 54, - 150, - 'Expensive Index Spool', - 'You have an index spool, this is usually a sign that there''s an index missing somewhere.', - 'https://www.brentozar.com/blitzcache/eager-index-spools/', - 'Check operator predicates and output for index definition guidance') ; - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.is_spool_more_rows = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 55, - 150, - 'Index Spools Many Rows', - 'You have an index spool that spools more rows than the query returns', - 'https://www.brentozar.com/blitzcache/eager-index-spools/', - 'Check operator predicates and output for index definition guidance') ; + RAISERROR('@CheckProcedureCache = 1, capturing second pass of plan cache',10,1) WITH NOWAIT; - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.is_bad_estimate = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 56, - 100, - 'Potentially bad cardinality estimates', - 'Estimated rows are different from average rows by a factor of 10000', - 'https://www.brentozar.com/blitzcache/bad-estimates/', - 'This may indicate a performance problem if mismatches occur regularly') ; + /* Populate #QueryStats. SQL 2005 doesn't have query hash or query plan hash. */ + IF @@VERSION LIKE 'Microsoft SQL Server 2005%' + BEGIN + IF @FilterPlansByDatabase IS NULL + BEGIN + SET @StringToExecute = N'INSERT INTO #QueryStats ([sql_handle], Pass, SampleTime, statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, Points) + SELECT [sql_handle], 2 AS Pass, SYSDATETIMEOFFSET(), statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, NULL AS query_hash, NULL AS query_plan_hash, 0 + FROM sys.dm_exec_query_stats qs + WHERE qs.last_execution_time >= @StartSampleTimeText;'; + END; + ELSE + BEGIN + SET @StringToExecute = N'INSERT INTO #QueryStats ([sql_handle], Pass, SampleTime, statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, Points) + SELECT [sql_handle], 2 AS Pass, SYSDATETIMEOFFSET(), statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, NULL AS query_hash, NULL AS query_plan_hash, 0 + FROM sys.dm_exec_query_stats qs + CROSS APPLY sys.dm_exec_plan_attributes(qs.plan_handle) AS attr + INNER JOIN #FilterPlansByDatabase dbs ON CAST(attr.value AS INT) = dbs.DatabaseID + WHERE qs.last_execution_time >= @StartSampleTimeText + AND attr.attribute = ''dbid'';'; + END; + END; + ELSE + BEGIN + IF @FilterPlansByDatabase IS NULL + BEGIN + SET @StringToExecute = N'INSERT INTO #QueryStats ([sql_handle], Pass, SampleTime, statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, Points) + SELECT [sql_handle], 2 AS Pass, SYSDATETIMEOFFSET(), statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, 0 + FROM sys.dm_exec_query_stats qs + WHERE qs.last_execution_time >= @StartSampleTimeText'; + END; + ELSE + BEGIN + SET @StringToExecute = N'INSERT INTO #QueryStats ([sql_handle], Pass, SampleTime, statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, Points) + SELECT [sql_handle], 2 AS Pass, SYSDATETIMEOFFSET(), statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, 0 + FROM sys.dm_exec_query_stats qs + CROSS APPLY sys.dm_exec_plan_attributes(qs.plan_handle) AS attr + INNER JOIN #FilterPlansByDatabase dbs ON CAST(attr.value AS INT) = dbs.DatabaseID + WHERE qs.last_execution_time >= @StartSampleTimeText + AND attr.attribute = ''dbid'';'; + END; + END; + /* Old version pre-2016/06/13: + IF @@VERSION LIKE 'Microsoft SQL Server 2005%' + SET @StringToExecute = N'INSERT INTO #QueryStats ([sql_handle], Pass, SampleTime, statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, Points) + SELECT [sql_handle], 2 AS Pass, SYSDATETIMEOFFSET(), statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, NULL AS query_hash, NULL AS query_plan_hash, 0 + FROM sys.dm_exec_query_stats qs + WHERE qs.last_execution_time >= @StartSampleTimeText;'; + ELSE + SET @StringToExecute = N'INSERT INTO #QueryStats ([sql_handle], Pass, SampleTime, statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, Points) + SELECT [sql_handle], 2 AS Pass, SYSDATETIMEOFFSET(), statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, 0 + FROM sys.dm_exec_query_stats qs + WHERE qs.last_execution_time >= @StartSampleTimeText;'; + */ + SET @ParmDefinitions = N'@StartSampleTimeText NVARCHAR(100)'; + SET @Parm1 = CONVERT(NVARCHAR(100), CAST(@StartSampleTime AS DATETIME), 127); - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.is_big_log = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 57, - 100, - 'High transaction log use', - 'This query on average uses more than half of the transaction log', - 'http://michaeljswart.com/2014/09/take-care-when-scripting-batches/', - 'This is probably a sign that you need to start batching queries') ; - - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.is_big_tempdb = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 58, - 100, - 'High tempdb use', - 'This query uses more than half of a data file on average', - 'No URL yet', - 'You should take a look at tempdb waits to see if you''re having problems') ; - - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.is_row_goal = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (59, - 200, - 'Row Goals', - 'This query had row goals introduced', - 'https://www.brentozar.com/archive/2018/01/sql-server-2017-cu3-adds-optimizer-row-goal-information-query-plans/', - 'This can be good or bad, and should be investigated for high read queries') ; + EXECUTE sp_executesql @StringToExecute, @ParmDefinitions, @StartSampleTimeText = @Parm1; - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.is_mstvf = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 60, - 100, - 'MSTVFs', - 'These have many of the same problems scalar UDFs have', - 'http://brentozar.com/blitzcache/tvf-join/', - 'Execution plans have been found that join to table valued functions (TVFs). TVFs produce inaccurate estimates of the number of rows returned and can lead to any number of query plan problems.'); + RAISERROR('@CheckProcedureCache = 1, totaling up plan cache metrics',10,1) WITH NOWAIT; - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.is_mstvf = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (61, - 100, - 'Many to Many Merge', - 'These use secret worktables that could be doing lots of reads', - 'https://www.brentozar.com/archive/2018/04/many-mysteries-merge-joins/', - 'Occurs when join inputs aren''t known to be unique. Can be really bad when parallel.'); + /* Get the totals for the entire plan cache */ + INSERT INTO #QueryStats (Pass, SampleTime, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time) + SELECT 0 AS Pass, SYSDATETIMEOFFSET(), SUM(execution_count), SUM(total_worker_time), SUM(total_physical_reads), SUM(total_logical_writes), SUM(total_logical_reads), SUM(total_clr_time), SUM(total_elapsed_time), MIN(creation_time) + FROM sys.dm_exec_query_stats qs; - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.is_nonsargable = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (62, - 50, - 'Non-SARGable queries', - 'Queries may be using', - 'https://www.brentozar.com/blitzcache/non-sargable-predicates/', - 'Occurs when join inputs aren''t known to be unique. Can be really bad when parallel.'); - - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.is_paul_white_electric = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 998, - 200, - 'Is Paul White Electric?', - 'This query has a Switch operator in it!', - 'https://www.sql.kiwi/2013/06/hello-operator-my-switch-is-bored.html', - 'You should email this query plan to Paul: SQLkiwi at gmail dot com') ; - - - - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - SELECT - 999, - 200, - 'Database Level Statistics', - 'The database ' + sa.[database] + ' last had a stats update on ' + CONVERT(NVARCHAR(10), CONVERT(DATE, MAX(sa.last_update))) + ' and has ' + CONVERT(NVARCHAR(10), AVG(sa.modification_count)) + ' modifications on average.' AS Finding, - 'https://www.brentozar.com/blitzcache/stale-statistics/' AS URL, - 'Consider updating statistics more frequently,' AS Details - FROM #stats_agg AS sa - GROUP BY sa.[database] - HAVING MAX(sa.last_update) <= DATEADD(DAY, -7, SYSDATETIME()) - AND AVG(sa.modification_count) >= 100000; - - IF EXISTS (SELECT 1/0 - FROM #trace_flags AS tf - WHERE tf.global_trace_flags IS NOT NULL - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 1000, - 255, - 'Global Trace Flags Enabled', - 'You have Global Trace Flags enabled on your server', - 'https://www.brentozar.com/blitz/trace-flags-enabled-globally/', - 'You have the following Global Trace Flags enabled: ' + (SELECT TOP 1 tf.global_trace_flags FROM #trace_flags AS tf WHERE tf.global_trace_flags IS NOT NULL)) ; + RAISERROR('@CheckProcedureCache = 1, so analyzing execution plans',10,1) WITH NOWAIT; + /* + Pick the most resource-intensive queries to review. Update the Points field + in #QueryStats - if a query is in the top 10 for logical reads, CPU time, + duration, or execution, add 1 to its points. + */ + WITH qsTop AS ( + SELECT TOP 10 qsNow.ID + FROM #QueryStats qsNow + INNER JOIN #QueryStats qsFirst ON qsNow.[sql_handle] = qsFirst.[sql_handle] AND qsNow.statement_start_offset = qsFirst.statement_start_offset AND qsNow.statement_end_offset = qsFirst.statement_end_offset AND qsNow.plan_generation_num = qsFirst.plan_generation_num AND qsNow.plan_handle = qsFirst.plan_handle AND qsFirst.Pass = 1 + WHERE qsNow.total_elapsed_time > qsFirst.total_elapsed_time + AND qsNow.Pass = 2 + AND qsNow.total_elapsed_time - qsFirst.total_elapsed_time > 1000000 /* Only queries with over 1 second of runtime */ + ORDER BY (qsNow.total_elapsed_time - COALESCE(qsFirst.total_elapsed_time, 0)) DESC) + UPDATE #QueryStats + SET Points = Points + 1 + FROM #QueryStats qs + INNER JOIN qsTop ON qs.ID = qsTop.ID; + WITH qsTop AS ( + SELECT TOP 10 qsNow.ID + FROM #QueryStats qsNow + INNER JOIN #QueryStats qsFirst ON qsNow.[sql_handle] = qsFirst.[sql_handle] AND qsNow.statement_start_offset = qsFirst.statement_start_offset AND qsNow.statement_end_offset = qsFirst.statement_end_offset AND qsNow.plan_generation_num = qsFirst.plan_generation_num AND qsNow.plan_handle = qsFirst.plan_handle AND qsFirst.Pass = 1 + WHERE qsNow.total_logical_reads > qsFirst.total_logical_reads + AND qsNow.Pass = 2 + AND qsNow.total_logical_reads - qsFirst.total_logical_reads > 1000 /* Only queries with over 1000 reads */ + ORDER BY (qsNow.total_logical_reads - COALESCE(qsFirst.total_logical_reads, 0)) DESC) + UPDATE #QueryStats + SET Points = Points + 1 + FROM #QueryStats qs + INNER JOIN qsTop ON qs.ID = qsTop.ID; - /* - Return worsts - */ - WITH worsts AS ( - SELECT gi.flat_date, - gi.start_range, - gi.end_range, - gi.total_avg_duration_ms, - gi.total_avg_cpu_time_ms, - gi.total_avg_logical_io_reads_mb, - gi.total_avg_physical_io_reads_mb, - gi.total_avg_logical_io_writes_mb, - gi.total_avg_query_max_used_memory_mb, - gi.total_rowcount, - gi.total_avg_log_bytes_mb, - gi.total_avg_tempdb_space, - gi.total_max_duration_ms, - gi.total_max_cpu_time_ms, - gi.total_max_logical_io_reads_mb, - gi.total_max_physical_io_reads_mb, - gi.total_max_logical_io_writes_mb, - gi.total_max_query_max_used_memory_mb, - gi.total_max_log_bytes_mb, - gi.total_max_tempdb_space, - CONVERT(NVARCHAR(20), gi.flat_date) AS worst_date, - CASE WHEN DATEPART(HOUR, gi.start_range) = 0 THEN ' midnight ' - WHEN DATEPART(HOUR, gi.start_range) <= 12 THEN CONVERT(NVARCHAR(3), DATEPART(HOUR, gi.start_range)) + 'am ' - WHEN DATEPART(HOUR, gi.start_range) > 12 THEN CONVERT(NVARCHAR(3), DATEPART(HOUR, gi.start_range) -12) + 'pm ' - END AS worst_start_time, - CASE WHEN DATEPART(HOUR, gi.end_range) = 0 THEN ' midnight ' - WHEN DATEPART(HOUR, gi.end_range) <= 12 THEN CONVERT(NVARCHAR(3), DATEPART(HOUR, gi.end_range)) + 'am ' - WHEN DATEPART(HOUR, gi.end_range) > 12 THEN CONVERT(NVARCHAR(3), DATEPART(HOUR, gi.end_range) -12) + 'pm ' - END AS worst_end_time - FROM #grouped_interval AS gi - ), /*averages*/ - duration_worst AS ( - SELECT TOP 1 'Your worst avg duration range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg - FROM worsts - ORDER BY worsts.total_avg_duration_ms DESC - ), - cpu_worst AS ( - SELECT TOP 1 'Your worst avg cpu range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg - FROM worsts - ORDER BY worsts.total_avg_cpu_time_ms DESC - ), - logical_reads_worst AS ( - SELECT TOP 1 'Your worst avg logical read range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg - FROM worsts - ORDER BY worsts.total_avg_logical_io_reads_mb DESC - ), - physical_reads_worst AS ( - SELECT TOP 1 'Your worst avg physical read range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg - FROM worsts - ORDER BY worsts.total_avg_physical_io_reads_mb DESC - ), - logical_writes_worst AS ( - SELECT TOP 1 'Your worst avg logical write range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg - FROM worsts - ORDER BY worsts.total_avg_logical_io_writes_mb DESC - ), - memory_worst AS ( - SELECT TOP 1 'Your worst avg memory range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg - FROM worsts - ORDER BY worsts.total_avg_query_max_used_memory_mb DESC - ), - rowcount_worst AS ( - SELECT TOP 1 'Your worst avg row count range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg - FROM worsts - ORDER BY worsts.total_rowcount DESC - ), - logbytes_worst AS ( - SELECT TOP 1 'Your worst avg log bytes range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg - FROM worsts - ORDER BY worsts.total_avg_log_bytes_mb DESC - ), - tempdb_worst AS ( - SELECT TOP 1 'Your worst avg tempdb range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg - FROM worsts - ORDER BY worsts.total_avg_tempdb_space DESC - )/*maxes*/, - max_duration_worst AS ( - SELECT TOP 1 'Your worst max duration range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg - FROM worsts - ORDER BY worsts.total_max_duration_ms DESC - ), - max_cpu_worst AS ( - SELECT TOP 1 'Your worst max cpu range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg - FROM worsts - ORDER BY worsts.total_max_cpu_time_ms DESC - ), - max_logical_reads_worst AS ( - SELECT TOP 1 'Your worst max logical read range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg - FROM worsts - ORDER BY worsts.total_max_logical_io_reads_mb DESC - ), - max_physical_reads_worst AS ( - SELECT TOP 1 'Your worst max physical read range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg - FROM worsts - ORDER BY worsts.total_max_physical_io_reads_mb DESC - ), - max_logical_writes_worst AS ( - SELECT TOP 1 'Your worst max logical write range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg - FROM worsts - ORDER BY worsts.total_max_logical_io_writes_mb DESC - ), - max_memory_worst AS ( - SELECT TOP 1 'Your worst max memory range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg - FROM worsts - ORDER BY worsts.total_max_query_max_used_memory_mb DESC - ), - max_logbytes_worst AS ( - SELECT TOP 1 'Your worst max log bytes range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg - FROM worsts - ORDER BY worsts.total_max_log_bytes_mb DESC - ), - max_tempdb_worst AS ( - SELECT TOP 1 'Your worst max tempdb range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg - FROM worsts - ORDER BY worsts.total_max_tempdb_space DESC - ) - INSERT #warning_results ( CheckID, Priority, FindingsGroup, Finding, URL, Details ) - /*averages*/ - SELECT 1002, 255, 'Worsts', 'Worst Avg Duration', 'N/A', duration_worst.msg - FROM duration_worst - UNION ALL - SELECT 1002, 255, 'Worsts', 'Worst Avg CPU', 'N/A', cpu_worst.msg - FROM cpu_worst - UNION ALL - SELECT 1002, 255, 'Worsts', 'Worst Avg Logical Reads', 'N/A', logical_reads_worst.msg - FROM logical_reads_worst - UNION ALL - SELECT 1002, 255, 'Worsts', 'Worst Avg Physical Reads', 'N/A', physical_reads_worst.msg - FROM physical_reads_worst - UNION ALL - SELECT 1002, 255, 'Worsts', 'Worst Avg Logical Writes', 'N/A', logical_writes_worst.msg - FROM logical_writes_worst - UNION ALL - SELECT 1002, 255, 'Worsts', 'Worst Avg Memory', 'N/A', memory_worst.msg - FROM memory_worst - UNION ALL - SELECT 1002, 255, 'Worsts', 'Worst Row Counts', 'N/A', rowcount_worst.msg - FROM rowcount_worst - UNION ALL - SELECT 1002, 255, 'Worsts', 'Worst Avg Log Bytes', 'N/A', logbytes_worst.msg - FROM logbytes_worst - UNION ALL - SELECT 1002, 255, 'Worsts', 'Worst Avg tempdb', 'N/A', tempdb_worst.msg - FROM tempdb_worst - UNION ALL - /*maxes*/ - SELECT 1002, 255, 'Worsts', 'Worst Max Duration', 'N/A', max_duration_worst.msg - FROM max_duration_worst - UNION ALL - SELECT 1002, 255, 'Worsts', 'Worst Max CPU', 'N/A', max_cpu_worst.msg - FROM max_cpu_worst - UNION ALL - SELECT 1002, 255, 'Worsts', 'Worst Max Logical Reads', 'N/A', max_logical_reads_worst.msg - FROM max_logical_reads_worst - UNION ALL - SELECT 1002, 255, 'Worsts', 'Worst Max Physical Reads', 'N/A', max_physical_reads_worst.msg - FROM max_physical_reads_worst - UNION ALL - SELECT 1002, 255, 'Worsts', 'Worst Max Logical Writes', 'N/A', max_logical_writes_worst.msg - FROM max_logical_writes_worst - UNION ALL - SELECT 1002, 255, 'Worsts', 'Worst Max Memory', 'N/A', max_memory_worst.msg - FROM max_memory_worst - UNION ALL - SELECT 1002, 255, 'Worsts', 'Worst Max Log Bytes', 'N/A', max_logbytes_worst.msg - FROM max_logbytes_worst - UNION ALL - SELECT 1002, 255, 'Worsts', 'Worst Max tempdb', 'N/A', max_tempdb_worst.msg - FROM max_tempdb_worst - OPTION (RECOMPILE); + WITH qsTop AS ( + SELECT TOP 10 qsNow.ID + FROM #QueryStats qsNow + INNER JOIN #QueryStats qsFirst ON qsNow.[sql_handle] = qsFirst.[sql_handle] AND qsNow.statement_start_offset = qsFirst.statement_start_offset AND qsNow.statement_end_offset = qsFirst.statement_end_offset AND qsNow.plan_generation_num = qsFirst.plan_generation_num AND qsNow.plan_handle = qsFirst.plan_handle AND qsFirst.Pass = 1 + WHERE qsNow.total_worker_time > qsFirst.total_worker_time + AND qsNow.Pass = 2 + AND qsNow.total_worker_time - qsFirst.total_worker_time > 1000000 /* Only queries with over 1 second of worker time */ + ORDER BY (qsNow.total_worker_time - COALESCE(qsFirst.total_worker_time, 0)) DESC) + UPDATE #QueryStats + SET Points = Points + 1 + FROM #QueryStats qs + INNER JOIN qsTop ON qs.ID = qsTop.ID; + + WITH qsTop AS ( + SELECT TOP 10 qsNow.ID + FROM #QueryStats qsNow + INNER JOIN #QueryStats qsFirst ON qsNow.[sql_handle] = qsFirst.[sql_handle] AND qsNow.statement_start_offset = qsFirst.statement_start_offset AND qsNow.statement_end_offset = qsFirst.statement_end_offset AND qsNow.plan_generation_num = qsFirst.plan_generation_num AND qsNow.plan_handle = qsFirst.plan_handle AND qsFirst.Pass = 1 + WHERE qsNow.execution_count > qsFirst.execution_count + AND qsNow.Pass = 2 + AND (qsNow.total_elapsed_time - qsFirst.total_elapsed_time > 1000000 /* Only queries with over 1 second of runtime */ + OR qsNow.total_logical_reads - qsFirst.total_logical_reads > 1000 /* Only queries with over 1000 reads */ + OR qsNow.total_worker_time - qsFirst.total_worker_time > 1000000 /* Only queries with over 1 second of worker time */) + ORDER BY (qsNow.execution_count - COALESCE(qsFirst.execution_count, 0)) DESC) + UPDATE #QueryStats + SET Points = Points + 1 + FROM #QueryStats qs + INNER JOIN qsTop ON qs.ID = qsTop.ID; + + /* Query Stats - Most Resource-Intensive Queries - CheckID 17 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 17',10,1) WITH NOWAIT; + END + + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, QueryText, QueryStatsNowID, QueryStatsFirstID, PlanHandle, QueryHash) + SELECT 17, 210, 'Query Stats', 'Most Resource-Intensive Queries', 'http://www.BrentOzar.com/go/topqueries', + 'Query stats during the sample:' + @LineFeed + + 'Executions: ' + CAST(qsNow.execution_count - (COALESCE(qsFirst.execution_count, 0)) AS NVARCHAR(100)) + @LineFeed + + 'Elapsed Time: ' + CAST(qsNow.total_elapsed_time - (COALESCE(qsFirst.total_elapsed_time, 0)) AS NVARCHAR(100)) + @LineFeed + + 'CPU Time: ' + CAST(qsNow.total_worker_time - (COALESCE(qsFirst.total_worker_time, 0)) AS NVARCHAR(100)) + @LineFeed + + 'Logical Reads: ' + CAST(qsNow.total_logical_reads - (COALESCE(qsFirst.total_logical_reads, 0)) AS NVARCHAR(100)) + @LineFeed + + 'Logical Writes: ' + CAST(qsNow.total_logical_writes - (COALESCE(qsFirst.total_logical_writes, 0)) AS NVARCHAR(100)) + @LineFeed + + 'CLR Time: ' + CAST(qsNow.total_clr_time - (COALESCE(qsFirst.total_clr_time, 0)) AS NVARCHAR(100)) + @LineFeed + + @LineFeed + @LineFeed + 'Query stats since ' + CONVERT(NVARCHAR(100), qsNow.creation_time ,121) + @LineFeed + + 'Executions: ' + CAST(qsNow.execution_count AS NVARCHAR(100)) + + CASE qsTotal.execution_count WHEN 0 THEN '' ELSE (' - Percent of Server Total: ' + CAST(CAST(100.0 * qsNow.execution_count / qsTotal.execution_count AS DECIMAL(6,2)) AS NVARCHAR(100)) + '%') END + @LineFeed + + 'Elapsed Time: ' + CAST(qsNow.total_elapsed_time AS NVARCHAR(100)) + + CASE qsTotal.total_elapsed_time WHEN 0 THEN '' ELSE (' - Percent of Server Total: ' + CAST(CAST(100.0 * qsNow.total_elapsed_time / qsTotal.total_elapsed_time AS DECIMAL(6,2)) AS NVARCHAR(100)) + '%') END + @LineFeed + + 'CPU Time: ' + CAST(qsNow.total_worker_time AS NVARCHAR(100)) + + CASE qsTotal.total_worker_time WHEN 0 THEN '' ELSE (' - Percent of Server Total: ' + CAST(CAST(100.0 * qsNow.total_worker_time / qsTotal.total_worker_time AS DECIMAL(6,2)) AS NVARCHAR(100)) + '%') END + @LineFeed + + 'Logical Reads: ' + CAST(qsNow.total_logical_reads AS NVARCHAR(100)) + + CASE qsTotal.total_logical_reads WHEN 0 THEN '' ELSE (' - Percent of Server Total: ' + CAST(CAST(100.0 * qsNow.total_logical_reads / qsTotal.total_logical_reads AS DECIMAL(6,2)) AS NVARCHAR(100)) + '%') END + @LineFeed + + 'Logical Writes: ' + CAST(qsNow.total_logical_writes AS NVARCHAR(100)) + + CASE qsTotal.total_logical_writes WHEN 0 THEN '' ELSE (' - Percent of Server Total: ' + CAST(CAST(100.0 * qsNow.total_logical_writes / qsTotal.total_logical_writes AS DECIMAL(6,2)) AS NVARCHAR(100)) + '%') END + @LineFeed + + 'CLR Time: ' + CAST(qsNow.total_clr_time AS NVARCHAR(100)) + + CASE qsTotal.total_clr_time WHEN 0 THEN '' ELSE (' - Percent of Server Total: ' + CAST(CAST(100.0 * qsNow.total_clr_time / qsTotal.total_clr_time AS DECIMAL(6,2)) AS NVARCHAR(100)) + '%') END + @LineFeed + + --@LineFeed + @LineFeed + 'Query hash: ' + CAST(qsNow.query_hash AS NVARCHAR(100)) + @LineFeed + + --@LineFeed + @LineFeed + 'Query plan hash: ' + CAST(qsNow.query_plan_hash AS NVARCHAR(100)) + + @LineFeed AS Details, + 'See the URL for tuning tips on why this query may be consuming resources.' AS HowToStopIt, + qp.query_plan, + QueryText = SUBSTRING(st.text, + (qsNow.statement_start_offset / 2) + 1, + ((CASE qsNow.statement_end_offset + WHEN -1 THEN DATALENGTH(st.text) + ELSE qsNow.statement_end_offset + END - qsNow.statement_start_offset) / 2) + 1), + qsNow.ID AS QueryStatsNowID, + qsFirst.ID AS QueryStatsFirstID, + qsNow.plan_handle AS PlanHandle, + qsNow.query_hash + FROM #QueryStats qsNow + INNER JOIN #QueryStats qsTotal ON qsTotal.Pass = 0 + LEFT OUTER JOIN #QueryStats qsFirst ON qsNow.[sql_handle] = qsFirst.[sql_handle] AND qsNow.statement_start_offset = qsFirst.statement_start_offset AND qsNow.statement_end_offset = qsFirst.statement_end_offset AND qsNow.plan_generation_num = qsFirst.plan_generation_num AND qsNow.plan_handle = qsFirst.plan_handle AND qsFirst.Pass = 1 + CROSS APPLY sys.dm_exec_sql_text(qsNow.sql_handle) AS st + CROSS APPLY sys.dm_exec_query_plan(qsNow.plan_handle) AS qp + WHERE qsNow.Points > 0 AND st.text IS NOT NULL AND qp.query_plan IS NOT NULL; + UPDATE #BlitzFirstResults + SET DatabaseID = CAST(attr.value AS INT), + DatabaseName = DB_NAME(CAST(attr.value AS INT)) + FROM #BlitzFirstResults + CROSS APPLY sys.dm_exec_plan_attributes(#BlitzFirstResults.PlanHandle) AS attr + WHERE attr.attribute = 'dbid'; - IF NOT EXISTS (SELECT 1/0 - FROM #warning_results AS bcr - WHERE bcr.Priority = 2147483646 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (2147483646, - 255, - 'Need more help?' , - 'Paste your plan on the internet!', - 'http://pastetheplan.com', - 'This makes it easy to share plans and post them to Q&A sites like https://dba.stackexchange.com/!') ; + END; /* IF DATEDIFF(ss, @FinishSampleTime, SYSDATETIMEOFFSET()) > 10 AND @CheckProcedureCache = 1 */ - IF NOT EXISTS (SELECT 1/0 - FROM #warning_results AS bcr - WHERE bcr.Priority = 2147483647 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 2147483647, - 255, - 'Thanks for using sp_BlitzQueryStore!' , - 'From Your Community Volunteers', - 'http://FirstResponderKit.org', - 'We hope you found this tool useful. Current version: ' + @Version + ' released on ' + CONVERT(NVARCHAR(30), @VersionDate) + '.') ; - - - SELECT Priority, - FindingsGroup, - Finding, - URL, - Details, - CheckID - FROM #warning_results - GROUP BY Priority, - FindingsGroup, - Finding, - URL, - Details, - CheckID - ORDER BY Priority ASC, FindingsGroup, Finding, CheckID ASC - OPTION (RECOMPILE); + RAISERROR('Analyzing changes between first and second passes of DMVs',10,1) WITH NOWAIT; + /* Wait Stats - CheckID 6 */ + /* Compare the current wait stats to the sample we took at the start, and insert the top 10 waits. */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 6',10,1) WITH NOWAIT; + END + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, DetailsInt) + SELECT TOP 10 6 AS CheckID, + 200 AS Priority, + 'Wait Stats' AS FindingGroup, + wNow.wait_type AS Finding, /* IF YOU CHANGE THIS, STUFF WILL BREAK. Other checks look for wait type names in the Finding field. See checks 11, 12 as example. */ + N'https://www.sqlskills.com/help/waits/' + LOWER(wNow.wait_type) + '/' AS URL, + 'For ' + CAST(((wNow.wait_time_ms - COALESCE(wBase.wait_time_ms,0)) / 1000) AS NVARCHAR(100)) + ' seconds over the last ' + CASE @Seconds WHEN 0 THEN (CAST(DATEDIFF(dd,@StartSampleTime,@FinishSampleTime) AS NVARCHAR(10)) + ' days') ELSE (CAST(@Seconds AS NVARCHAR(10)) + ' seconds') END + ', SQL Server was waiting on this particular bottleneck.' + @LineFeed + @LineFeed AS Details, + 'See the URL for more details on how to mitigate this wait type.' AS HowToStopIt, + ((wNow.wait_time_ms - COALESCE(wBase.wait_time_ms,0)) / 1000) AS DetailsInt + FROM #WaitStats wNow + LEFT OUTER JOIN #WaitStats wBase ON wNow.wait_type = wBase.wait_type AND wNow.SampleTime > wBase.SampleTime + WHERE wNow.wait_time_ms > (wBase.wait_time_ms + (.5 * (DATEDIFF(ss,@StartSampleTime,@FinishSampleTime)) * 1000)) /* Only look for things we've actually waited on for half of the time or more */ + ORDER BY (wNow.wait_time_ms - COALESCE(wBase.wait_time_ms,0)) DESC; -END; + /* Server Performance - Poison Wait Detected - CheckID 30 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 30',10,1) WITH NOWAIT; + END -END; -END TRY -BEGIN CATCH - RAISERROR (N'Failure returning warnings', 0,1) WITH NOWAIT; + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, DetailsInt) + SELECT 30 AS CheckID, + 10 AS Priority, + 'Server Performance' AS FindingGroup, + 'Poison Wait Detected: ' + wNow.wait_type AS Finding, + N'http://www.brentozar.com/go/poison/#' + wNow.wait_type AS URL, + 'For ' + CAST(((wNow.wait_time_ms - COALESCE(wBase.wait_time_ms,0)) / 1000) AS NVARCHAR(100)) + ' seconds over the last ' + CASE @Seconds WHEN 0 THEN (CAST(DATEDIFF(dd,@StartSampleTime,@FinishSampleTime) AS NVARCHAR(10)) + ' days') ELSE (CAST(@Seconds AS NVARCHAR(10)) + ' seconds') END + ', SQL Server was waiting on this particular bottleneck.' + @LineFeed + @LineFeed AS Details, + 'See the URL for more details on how to mitigate this wait type.' AS HowToStopIt, + ((wNow.wait_time_ms - COALESCE(wBase.wait_time_ms,0)) / 1000) AS DetailsInt + FROM #WaitStats wNow + LEFT OUTER JOIN #WaitStats wBase ON wNow.wait_type = wBase.wait_type AND wNow.SampleTime > wBase.SampleTime + WHERE wNow.wait_type IN ('IO_QUEUE_LIMIT', 'IO_RETRY', 'LOG_RATE_GOVERNOR', 'POOL_LOG_RATE_GOVERNOR', 'PREEMPTIVE_DEBUG', 'RESMGR_THROTTLED', 'RESOURCE_SEMAPHORE', 'RESOURCE_SEMAPHORE_QUERY_COMPILE','SE_REPL_CATCHUP_THROTTLE','SE_REPL_COMMIT_ACK','SE_REPL_COMMIT_TURN','SE_REPL_ROLLBACK_ACK','SE_REPL_SLOW_SECONDARY_THROTTLE','THREADPOOL') + AND wNow.wait_time_ms > (wBase.wait_time_ms + 1000); - IF @sql_select IS NOT NULL - BEGIN - SET @msg = N'Last @sql_select: ' + @sql_select; - RAISERROR(@msg, 0, 1) WITH NOWAIT; - END; - SELECT @msg = @DatabaseName + N' database failed to process. ' + ERROR_MESSAGE(), @error_severity = ERROR_SEVERITY(), @error_state = ERROR_STATE(); - RAISERROR (@msg, @error_severity, @error_state) WITH NOWAIT; - - - WHILE @@TRANCOUNT > 0 - ROLLBACK; + /* Server Performance - Slow Data File Reads - CheckID 11 */ + IF EXISTS (SELECT * FROM #BlitzFirstResults WHERE Finding LIKE 'PAGEIOLATCH%') + BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 11',10,1) WITH NOWAIT; + END - RETURN; -END CATCH; + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, DatabaseID, DatabaseName) + SELECT TOP 10 11 AS CheckID, + 50 AS Priority, + 'Server Performance' AS FindingGroup, + 'Slow Data File Reads' AS Finding, + 'http://www.BrentOzar.com/go/slow/' AS URL, + 'Your server is experiencing PAGEIOLATCH% waits due to slow data file reads. This file is one of the reasons why.' + @LineFeed + + 'File: ' + fNow.PhysicalName + @LineFeed + + 'Number of reads during the sample: ' + CAST((fNow.num_of_reads - fBase.num_of_reads) AS NVARCHAR(20)) + @LineFeed + + 'Seconds spent waiting on storage for these reads: ' + CAST(((fNow.io_stall_read_ms - fBase.io_stall_read_ms) / 1000.0) AS NVARCHAR(20)) + @LineFeed + + 'Average read latency during the sample: ' + CAST(((fNow.io_stall_read_ms - fBase.io_stall_read_ms) / (fNow.num_of_reads - fBase.num_of_reads) ) AS NVARCHAR(20)) + ' milliseconds' + @LineFeed + + 'Microsoft guidance for data file read speed: 20ms or less.' + @LineFeed + @LineFeed AS Details, + 'See the URL for more details on how to mitigate this wait type.' AS HowToStopIt, + fNow.DatabaseID, + fNow.DatabaseName + FROM #FileStats fNow + INNER JOIN #FileStats fBase ON fNow.DatabaseID = fBase.DatabaseID AND fNow.FileID = fBase.FileID AND fNow.SampleTime > fBase.SampleTime AND fNow.num_of_reads > fBase.num_of_reads AND fNow.io_stall_read_ms > (fBase.io_stall_read_ms + 1000) + WHERE (fNow.io_stall_read_ms - fBase.io_stall_read_ms) / (fNow.num_of_reads - fBase.num_of_reads) >= @FileLatencyThresholdMS + AND fNow.TypeDesc = 'ROWS' + ORDER BY (fNow.io_stall_read_ms - fBase.io_stall_read_ms) / (fNow.num_of_reads - fBase.num_of_reads) DESC; + END; -IF @Debug = 1 + /* Server Performance - Slow Log File Writes - CheckID 12 */ + IF EXISTS (SELECT * FROM #BlitzFirstResults WHERE Finding LIKE 'WRITELOG%') + BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 12',10,1) WITH NOWAIT; + END -BEGIN TRY + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, DatabaseID, DatabaseName) + SELECT TOP 10 12 AS CheckID, + 50 AS Priority, + 'Server Performance' AS FindingGroup, + 'Slow Log File Writes' AS Finding, + 'http://www.BrentOzar.com/go/slow/' AS URL, + 'Your server is experiencing WRITELOG waits due to slow log file writes. This file is one of the reasons why.' + @LineFeed + + 'File: ' + fNow.PhysicalName + @LineFeed + + 'Number of writes during the sample: ' + CAST((fNow.num_of_writes - fBase.num_of_writes) AS NVARCHAR(20)) + @LineFeed + + 'Seconds spent waiting on storage for these writes: ' + CAST(((fNow.io_stall_write_ms - fBase.io_stall_write_ms) / 1000.0) AS NVARCHAR(20)) + @LineFeed + + 'Average write latency during the sample: ' + CAST(((fNow.io_stall_write_ms - fBase.io_stall_write_ms) / (fNow.num_of_writes - fBase.num_of_writes) ) AS NVARCHAR(20)) + ' milliseconds' + @LineFeed + + 'Microsoft guidance for log file write speed: 3ms or less.' + @LineFeed + @LineFeed AS Details, + 'See the URL for more details on how to mitigate this wait type.' AS HowToStopIt, + fNow.DatabaseID, + fNow.DatabaseName + FROM #FileStats fNow + INNER JOIN #FileStats fBase ON fNow.DatabaseID = fBase.DatabaseID AND fNow.FileID = fBase.FileID AND fNow.SampleTime > fBase.SampleTime AND fNow.num_of_writes > fBase.num_of_writes AND fNow.io_stall_write_ms > (fBase.io_stall_write_ms + 1000) + WHERE (fNow.io_stall_write_ms - fBase.io_stall_write_ms) / (fNow.num_of_writes - fBase.num_of_writes) >= @FileLatencyThresholdMS + AND fNow.TypeDesc = 'LOG' + ORDER BY (fNow.io_stall_write_ms - fBase.io_stall_write_ms) / (fNow.num_of_writes - fBase.num_of_writes) DESC; + END; -BEGIN -RAISERROR(N'Returning debugging data from temp tables', 0, 1) WITH NOWAIT; + /* SQL Server Internal Maintenance - Log File Growing - CheckID 13 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 13',10,1) WITH NOWAIT; + END ---Table content debugging + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) + SELECT 13 AS CheckID, + 1 AS Priority, + 'SQL Server Internal Maintenance' AS FindingGroup, + 'Log File Growing' AS Finding, + 'http://www.BrentOzar.com/askbrent/file-growing/' AS URL, + 'Number of growths during the sample: ' + CAST(ps.value_delta AS NVARCHAR(20)) + @LineFeed + + 'Determined by sampling Perfmon counter ' + ps.object_name + ' - ' + ps.counter_name + @LineFeed AS Details, + 'Pre-grow data and log files during maintenance windows so that they do not grow during production loads. See the URL for more details.' AS HowToStopIt + FROM #PerfmonStats ps + WHERE ps.Pass = 2 + AND object_name = @ServiceName + ':Databases' + AND counter_name = 'Log Growths' + AND value_delta > 0; -SELECT '#working_metrics' AS table_name, * -FROM #working_metrics AS wm -OPTION (RECOMPILE); -SELECT '#working_plan_text' AS table_name, * -FROM #working_plan_text AS wpt -OPTION (RECOMPILE); + /* SQL Server Internal Maintenance - Log File Shrinking - CheckID 14 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 14',10,1) WITH NOWAIT; + END -SELECT '#working_warnings' AS table_name, * -FROM #working_warnings AS ww -OPTION (RECOMPILE); + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) + SELECT 14 AS CheckID, + 1 AS Priority, + 'SQL Server Internal Maintenance' AS FindingGroup, + 'Log File Shrinking' AS Finding, + 'http://www.BrentOzar.com/askbrent/file-shrinking/' AS URL, + 'Number of shrinks during the sample: ' + CAST(ps.value_delta AS NVARCHAR(20)) + @LineFeed + + 'Determined by sampling Perfmon counter ' + ps.object_name + ' - ' + ps.counter_name + @LineFeed AS Details, + 'Pre-grow data and log files during maintenance windows so that they do not grow during production loads. See the URL for more details.' AS HowToStopIt + FROM #PerfmonStats ps + WHERE ps.Pass = 2 + AND object_name = @ServiceName + ':Databases' + AND counter_name = 'Log Shrinks' + AND value_delta > 0; -SELECT '#working_wait_stats' AS table_name, * -FROM #working_wait_stats wws -OPTION (RECOMPILE); + /* Query Problems - Compilations/Sec High - CheckID 15 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 15',10,1) WITH NOWAIT; + END -SELECT '#grouped_interval' AS table_name, * -FROM #grouped_interval -OPTION (RECOMPILE); + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) + SELECT 15 AS CheckID, + 50 AS Priority, + 'Query Problems' AS FindingGroup, + 'Compilations/Sec High' AS Finding, + 'http://www.BrentOzar.com/askbrent/compilations/' AS URL, + 'Number of batch requests during the sample: ' + CAST(ps.value_delta AS NVARCHAR(20)) + @LineFeed + + 'Number of compilations during the sample: ' + CAST(psComp.value_delta AS NVARCHAR(20)) + @LineFeed + + 'For OLTP environments, Microsoft recommends that 90% of batch requests should hit the plan cache, and not be compiled from scratch. We are exceeding that threshold.' + @LineFeed AS Details, + 'To find the queries that are compiling, start with:' + @LineFeed + + 'sp_BlitzCache @SortOrder = ''recent compilations''' + @LineFeed + + 'If dynamic SQL or non-parameterized strings are involved, consider enabling Forced Parameterization. See the URL for more details.' AS HowToStopIt + FROM #PerfmonStats ps + INNER JOIN #PerfmonStats psComp ON psComp.Pass = 2 AND psComp.object_name = @ServiceName + ':SQL Statistics' AND psComp.counter_name = 'SQL Compilations/sec' AND psComp.value_delta > 0 + WHERE ps.Pass = 2 + AND ps.object_name = @ServiceName + ':SQL Statistics' + AND ps.counter_name = 'Batch Requests/sec' + AND psComp.value_delta > 75 /* Because sp_BlitzFirst does around 50 compilations and re-compilations */ + AND (psComp.value_delta > (10 * @Seconds) OR psComp.value_delta > ps.value_delta) /* Either doing 10 compilations per second, or more compilations than queries */ + AND (psComp.value_delta * 10) > ps.value_delta; /* Compilations are more than 10% of batch requests per second */ -SELECT '#working_plans' AS table_name, * -FROM #working_plans -OPTION (RECOMPILE); + /* Query Problems - Re-Compilations/Sec High - CheckID 16 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 16',10,1) WITH NOWAIT; + END -SELECT '#stats_agg' AS table_name, * -FROM #stats_agg -OPTION (RECOMPILE); + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) + SELECT 16 AS CheckID, + 50 AS Priority, + 'Query Problems' AS FindingGroup, + 'Re-Compilations/Sec High' AS Finding, + 'http://www.BrentOzar.com/askbrent/recompilations/' AS URL, + 'Number of batch requests during the sample: ' + CAST(ps.value_delta AS NVARCHAR(20)) + @LineFeed + + 'Number of recompilations during the sample: ' + CAST(psComp.value_delta AS NVARCHAR(20)) + @LineFeed + + 'More than 10% of our queries are being recompiled. This is typically due to statistics changing on objects.' + @LineFeed AS Details, + 'To find the queries that are being forced to recompile, start with:' + @LineFeed + + 'sp_BlitzCache @SortOrder = ''recent compilations''' + @LineFeed + + 'Examine those plans to find out which objects are changing so quickly that they hit the stats update threshold. See the URL for more details.' AS HowToStopIt + FROM #PerfmonStats ps + INNER JOIN #PerfmonStats psComp ON psComp.Pass = 2 AND psComp.object_name = @ServiceName + ':SQL Statistics' AND psComp.counter_name = 'SQL Re-Compilations/sec' AND psComp.value_delta > 0 + WHERE ps.Pass = 2 + AND ps.object_name = @ServiceName + ':SQL Statistics' + AND ps.counter_name = 'Batch Requests/sec' + AND psComp.value_delta > 75 /* Because sp_BlitzFirst does around 50 compilations and re-compilations */ + AND (psComp.value_delta > (10 * @Seconds) OR psComp.value_delta > ps.value_delta) /* Either doing 10 recompilations per second, or more recompilations than queries */ + AND (psComp.value_delta * 10) > ps.value_delta; /* Recompilations are more than 10% of batch requests per second */ -SELECT '#trace_flags' AS table_name, * -FROM #trace_flags -OPTION (RECOMPILE); + /* Table Problems - Forwarded Fetches/Sec High - CheckID 29 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 29',10,1) WITH NOWAIT; + END -SELECT '#statements' AS table_name, * -FROM #statements AS s -OPTION (RECOMPILE); + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) + SELECT 29 AS CheckID, + 40 AS Priority, + 'Table Problems' AS FindingGroup, + 'Forwarded Fetches/Sec High' AS Finding, + 'https://BrentOzar.com/go/fetch/' AS URL, + CAST(ps.value_delta AS NVARCHAR(20)) + ' forwarded fetches (from SQLServer:Access Methods counter)' + @LineFeed + + 'Check your heaps: they need to be rebuilt, or they need a clustered index applied.' + @LineFeed AS Details, + 'Rebuild your heaps. If you use Ola Hallengren maintenance scripts, those do not rebuild heaps by default: https://www.brentozar.com/archive/2016/07/fix-forwarded-records/' AS HowToStopIt + FROM #PerfmonStats ps + INNER JOIN #PerfmonStats psComp ON psComp.Pass = 2 AND psComp.object_name = @ServiceName + ':Access Methods' AND psComp.counter_name = 'Forwarded Records/sec' AND psComp.value_delta > 100 + WHERE ps.Pass = 2 + AND ps.object_name = @ServiceName + ':Access Methods' + AND ps.counter_name = 'Forwarded Records/sec' + AND ps.value_delta > (100 * @Seconds); /* Ignore servers sitting idle */ -SELECT '#query_plan' AS table_name, * -FROM #query_plan AS qp -OPTION (RECOMPILE); + /* Check for temp objects with high forwarded fetches. + This has to be done as dynamic SQL because we have to execute OBJECT_NAME inside TempDB. */ + IF @@ROWCOUNT > 0 + BEGIN + SET @StringToExecute = N'USE tempdb; + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) + SELECT TOP 10 29 AS CheckID, + 40 AS Priority, + ''Table Problems'' AS FindingGroup, + ''Forwarded Fetches/Sec High: TempDB Object'' AS Finding, + ''https://BrentOzar.com/go/fetch/'' AS URL, + CAST(COALESCE(os.forwarded_fetch_count,0) - COALESCE(os_prior.forwarded_fetch_count,0) AS NVARCHAR(20)) + '' forwarded fetches on '' + + CASE WHEN OBJECT_NAME(os.object_id) IS NULL THEN ''an unknown table '' + WHEN LEN(OBJECT_NAME(os.object_id)) < 50 THEN ''a table variable, internal identifier '' + OBJECT_NAME(os.object_id) + ELSE ''a temp table '' + OBJECT_NAME(os.object_id) + END AS Details, + ''Look through your source code to find the object creating these objects, and tune the creation and population to reduce fetches. See the URL for details.'' AS HowToStopIt + FROM tempdb.sys.dm_db_index_operational_stats(DB_ID(''tempdb''), NULL, NULL, NULL) os + LEFT OUTER JOIN #TempdbOperationalStats os_prior ON os.object_id = os_prior.object_id + AND os.forwarded_fetch_count > os_prior.forwarded_fetch_count + WHERE os.database_id = DB_ID(''tempdb'') + AND os.forwarded_fetch_count - COALESCE(os_prior.forwarded_fetch_count,0) > 100 + ORDER BY os.forwarded_fetch_count DESC;' -SELECT '#relop' AS table_name, * -FROM #relop AS r -OPTION (RECOMPILE); + EXECUTE sp_executesql @StringToExecute; + END -SELECT '#plan_cost' AS table_name, * -FROM #plan_cost AS pc -OPTION (RECOMPILE); + /* In-Memory OLTP - Garbage Collection in Progress - CheckID 31 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 31',10,1) WITH NOWAIT; + END -SELECT '#est_rows' AS table_name, * -FROM #est_rows AS er -OPTION (RECOMPILE); + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) + SELECT 31 AS CheckID, + 50 AS Priority, + 'In-Memory OLTP' AS FindingGroup, + 'Garbage Collection in Progress' AS Finding, + 'https://BrentOzar.com/go/garbage/' AS URL, + CAST(ps.value_delta AS NVARCHAR(50)) + ' rows processed (from SQL Server YYYY XTP Garbage Collection:Rows processed/sec counter)' + @LineFeed + + 'This can happen due to memory pressure (causing In-Memory OLTP to shrink its footprint) or' + @LineFeed + + 'due to transactional workloads that constantly insert/delete data.' AS Details, + 'Sadly, you cannot choose when garbage collection occurs. This is one of the many gotchas of Hekaton. Learn more: http://nedotter.com/archive/2016/04/row-version-lifecycle-for-in-memory-oltp/' AS HowToStopIt + FROM #PerfmonStats ps + INNER JOIN #PerfmonStats psComp ON psComp.Pass = 2 AND psComp.object_name LIKE '%XTP Garbage Collection' AND psComp.counter_name = 'Rows processed/sec' AND psComp.value_delta > 100 + WHERE ps.Pass = 2 + AND ps.object_name LIKE '%XTP Garbage Collection' + AND ps.counter_name = 'Rows processed/sec' + AND ps.value_delta > (100 * @Seconds); /* Ignore servers sitting idle */ + + /* In-Memory OLTP - Transactions Aborted - CheckID 32 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 32',10,1) WITH NOWAIT; + END + + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) + SELECT 32 AS CheckID, + 100 AS Priority, + 'In-Memory OLTP' AS FindingGroup, + 'Transactions Aborted' AS Finding, + 'https://BrentOzar.com/go/aborted/' AS URL, + CAST(ps.value_delta AS NVARCHAR(50)) + ' transactions aborted (from SQL Server YYYY XTP Transactions:Transactions aborted/sec counter)' + @LineFeed + + 'This may indicate that data is changing, or causing folks to retry their transactions, thereby increasing load.' AS Details, + 'Dig into your In-Memory OLTP transactions to figure out which ones are failing and being retried.' AS HowToStopIt + FROM #PerfmonStats ps + INNER JOIN #PerfmonStats psComp ON psComp.Pass = 2 AND psComp.object_name LIKE '%XTP Transactions' AND psComp.counter_name = 'Transactions aborted/sec' AND psComp.value_delta > 100 + WHERE ps.Pass = 2 + AND ps.object_name LIKE '%XTP Transactions' + AND ps.counter_name = 'Transactions aborted/sec' + AND ps.value_delta > (10 * @Seconds); /* Ignore servers sitting idle */ + + /* Query Problems - Suboptimal Plans/Sec High - CheckID 33 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 33',10,1) WITH NOWAIT; + END + + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) + SELECT 32 AS CheckID, + 100 AS Priority, + 'Query Problems' AS FindingGroup, + 'Suboptimal Plans/Sec High' AS Finding, + 'https://BrentOzar.com/go/suboptimal/' AS URL, + CAST(ps.value_delta AS NVARCHAR(50)) + ' plans reported in the ' + CAST(ps.instance_name AS NVARCHAR(100)) + ' workload group (from Workload GroupStats:Suboptimal plans/sec counter)' + @LineFeed + + 'Even if you are not using Resource Governor, it still tracks information about user queries, memory grants, etc.' AS Details, + 'Check out sp_BlitzCache to get more information about recent queries, or try sp_BlitzWho to see currently running queries.' AS HowToStopIt + FROM #PerfmonStats ps + INNER JOIN #PerfmonStats psComp ON psComp.Pass = 2 AND psComp.object_name = @ServiceName + ':Workload GroupStats' AND psComp.counter_name = 'Suboptimal plans/sec' AND psComp.value_delta > 100 + WHERE ps.Pass = 2 + AND ps.object_name = @ServiceName + ':Workload GroupStats' + AND ps.counter_name = 'Suboptimal plans/sec' + AND ps.value_delta > (10 * @Seconds); /* Ignore servers sitting idle */ + + /* Azure Performance - Database is Maxed Out - CheckID 41 */ + IF SERVERPROPERTY('Edition') = 'SQL Azure' + BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 41',10,1) WITH NOWAIT; + END + + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) + SELECT 41 AS CheckID, + 10 AS Priority, + 'Azure Performance' AS FindingGroup, + 'Database is Maxed Out' AS Finding, + 'https://BrentOzar.com/go/maxedout' AS URL, + N'At ' + CONVERT(NVARCHAR(100), s.end_time ,121) + N', your database approached (or hit) your DTU limits:' + @LineFeed + + N'Average CPU percent: ' + CAST(avg_cpu_percent AS NVARCHAR(50)) + @LineFeed + + N'Average data IO percent: ' + CAST(avg_data_io_percent AS NVARCHAR(50)) + @LineFeed + + N'Average log write percent: ' + CAST(avg_log_write_percent AS NVARCHAR(50)) + @LineFeed + + N'Max worker percent: ' + CAST(max_worker_percent AS NVARCHAR(50)) + @LineFeed + + N'Max session percent: ' + CAST(max_session_percent AS NVARCHAR(50)) AS Details, + 'Tune your queries or indexes with sp_BlitzCache or sp_BlitzIndex, or consider upgrading to a higher DTU level.' AS HowToStopIt + FROM sys.dm_db_resource_stats s + WHERE s.end_time >= DATEADD(MI, -5, GETDATE()) + AND (avg_cpu_percent > 90 + OR avg_data_io_percent >= 90 + OR avg_log_write_percent >=90 + OR max_worker_percent >= 90 + OR max_session_percent >= 90); + END + + /* Server Info - Batch Requests per Sec - CheckID 19 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 19',10,1) WITH NOWAIT; + END + + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, DetailsInt) + SELECT 19 AS CheckID, + 250 AS Priority, + 'Server Info' AS FindingGroup, + 'Batch Requests per Sec' AS Finding, + 'http://www.BrentOzar.com/go/measure' AS URL, + CAST(CAST(ps.value_delta AS MONEY) / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS NVARCHAR(20)) AS Details, + ps.value_delta / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS DetailsInt + FROM #PerfmonStats ps + INNER JOIN #PerfmonStats ps1 ON ps.object_name = ps1.object_name AND ps.counter_name = ps1.counter_name AND ps1.Pass = 1 + WHERE ps.Pass = 2 + AND ps.object_name = @ServiceName + ':SQL Statistics' + AND ps.counter_name = 'Batch Requests/sec'; + + + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','SQL Compilations/sec', NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','SQL Re-Compilations/sec', NULL); + + /* Server Info - SQL Compilations/sec - CheckID 25 */ + IF @ExpertMode = 1 + BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 25',10,1) WITH NOWAIT; + END + + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, DetailsInt) + SELECT 25 AS CheckID, + 250 AS Priority, + 'Server Info' AS FindingGroup, + 'SQL Compilations per Sec' AS Finding, + 'http://www.BrentOzar.com/go/measure' AS URL, + CAST(ps.value_delta / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS NVARCHAR(20)) AS Details, + ps.value_delta / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS DetailsInt + FROM #PerfmonStats ps + INNER JOIN #PerfmonStats ps1 ON ps.object_name = ps1.object_name AND ps.counter_name = ps1.counter_name AND ps1.Pass = 1 + WHERE ps.Pass = 2 + AND ps.object_name = @ServiceName + ':SQL Statistics' + AND ps.counter_name = 'SQL Compilations/sec'; + END -SELECT '#stored_proc_info' AS table_name, * -FROM #stored_proc_info AS spi -OPTION(RECOMPILE); + /* Server Info - SQL Re-Compilations/sec - CheckID 26 */ + IF @ExpertMode = 1 + BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 26',10,1) WITH NOWAIT; + END -SELECT '#conversion_info' AS table_name, * -FROM #conversion_info AS ci -OPTION ( RECOMPILE ); + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, DetailsInt) + SELECT 26 AS CheckID, + 250 AS Priority, + 'Server Info' AS FindingGroup, + 'SQL Re-Compilations per Sec' AS Finding, + 'http://www.BrentOzar.com/go/measure' AS URL, + CAST(ps.value_delta / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS NVARCHAR(20)) AS Details, + ps.value_delta / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS DetailsInt + FROM #PerfmonStats ps + INNER JOIN #PerfmonStats ps1 ON ps.object_name = ps1.object_name AND ps.counter_name = ps1.counter_name AND ps1.Pass = 1 + WHERE ps.Pass = 2 + AND ps.object_name = @ServiceName + ':SQL Statistics' + AND ps.counter_name = 'SQL Re-Compilations/sec'; + END -SELECT '#variable_info' AS table_name, * -FROM #variable_info AS vi -OPTION ( RECOMPILE ); + /* Server Info - Wait Time per Core per Sec - CheckID 20 */ + IF @Seconds > 0 + BEGIN; + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 20',10,1) WITH NOWAIT; + END; -SELECT '#missing_index_xml' AS table_name, * -FROM #missing_index_xml -OPTION ( RECOMPILE ); + WITH waits1(SampleTime, waits_ms) AS (SELECT SampleTime, SUM(ws1.wait_time_ms) FROM #WaitStats ws1 WHERE ws1.Pass = 1 GROUP BY SampleTime), + waits2(SampleTime, waits_ms) AS (SELECT SampleTime, SUM(ws2.wait_time_ms) FROM #WaitStats ws2 WHERE ws2.Pass = 2 GROUP BY SampleTime), + cores(cpu_count) AS (SELECT SUM(1) FROM sys.dm_os_schedulers WHERE status = 'VISIBLE ONLINE' AND is_online = 1) + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, DetailsInt) + SELECT 20 AS CheckID, + 250 AS Priority, + 'Server Info' AS FindingGroup, + 'Wait Time per Core per Sec' AS Finding, + 'http://www.BrentOzar.com/go/measure' AS URL, + CAST((CAST(waits2.waits_ms - waits1.waits_ms AS MONEY)) / 1000 / i.cpu_count / DATEDIFF(ss, waits1.SampleTime, waits2.SampleTime) AS NVARCHAR(20)) AS Details, + (waits2.waits_ms - waits1.waits_ms) / 1000 / i.cpu_count / DATEDIFF(ss, waits1.SampleTime, waits2.SampleTime) AS DetailsInt + FROM cores i + CROSS JOIN waits1 + CROSS JOIN waits2; + END; -SELECT '#missing_index_schema' AS table_name, * -FROM #missing_index_schema -OPTION ( RECOMPILE ); + IF @Seconds > 0 + BEGIN + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) + SELECT + 47 AS CheckId, + 50 AS Priority, + 'Query Problems' AS FindingsGroup, + 'High Percentage Of Runnable Queries' AS Finding, + 'https://erikdarlingdata.com/go/RunnableQueue/' AS URL, + 'On the ' + + CASE WHEN y.pass = 1 + THEN '1st' + ELSE '2nd' + END + + ' pass, ' + + RTRIM(y.runnable_pct) + + '% of your queries were waiting to get on a CPU to run. ' + + ' This can indicate CPU pressure.' + FROM + ( + SELECT + 2 AS pass, + x.total, + x.runnable, + CONVERT(decimal(5,2), + ( + x.runnable / + (1. * NULLIF(x.total, 0)) + ) + ) * 100. AS runnable_pct + FROM + ( + SELECT + COUNT_BIG(*) AS total, + SUM(CASE WHEN status = 'runnable' + THEN 1 + ELSE 0 + END) AS runnable + FROM sys.dm_exec_requests + WHERE session_id > 50 + ) AS x + ) AS y + WHERE y.runnable_pct > 20.; + END -SELECT '#missing_index_usage' AS table_name, * -FROM #missing_index_usage -OPTION ( RECOMPILE ); + /* If we're waiting 30+ seconds, run these checks at the end. + We get this data from the ring buffers, and it's only updated once per minute, so might + as well get it now - whereas if we're checking 30+ seconds, it might get updated by the + end of our sp_BlitzFirst session. */ + IF @Seconds >= 30 + BEGIN + /* Server Performance - High CPU Utilization CheckID 24 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 24',10,1) WITH NOWAIT; + END -SELECT '#missing_index_detail' AS table_name, * -FROM #missing_index_detail -OPTION ( RECOMPILE ); + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) + SELECT 24, 50, 'Server Performance', 'High CPU Utilization', CAST(100 - SystemIdle AS NVARCHAR(20)) + N'%. Ring buffer details: ' + CAST(record AS NVARCHAR(4000)), 100 - SystemIdle, 'http://www.BrentOzar.com/go/cpu' + FROM ( + SELECT record, + record.value('(./Record/SchedulerMonitorEvent/SystemHealth/SystemIdle)[1]', 'int') AS SystemIdle + FROM ( + SELECT TOP 1 CONVERT(XML, record) AS record + FROM sys.dm_os_ring_buffers + WHERE ring_buffer_type = N'RING_BUFFER_SCHEDULER_MONITOR' + AND record LIKE '%%' + ORDER BY timestamp DESC) AS rb + ) AS y + WHERE 100 - SystemIdle >= 50; -SELECT '#missing_index_pretty' AS table_name, * -FROM #missing_index_pretty -OPTION ( RECOMPILE ); + /* Server Performance - CPU Utilization CheckID 23 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 23',10,1) WITH NOWAIT; + END -END; + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) + SELECT 23, 250, 'Server Info', 'CPU Utilization', CAST(100 - SystemIdle AS NVARCHAR(20)) + N'%. Ring buffer details: ' + CAST(record AS NVARCHAR(4000)), 100 - SystemIdle, 'http://www.BrentOzar.com/go/cpu' + FROM ( + SELECT record, + record.value('(./Record/SchedulerMonitorEvent/SystemHealth/SystemIdle)[1]', 'int') AS SystemIdle + FROM ( + SELECT TOP 1 CONVERT(XML, record) AS record + FROM sys.dm_os_ring_buffers + WHERE ring_buffer_type = N'RING_BUFFER_SCHEDULER_MONITOR' + AND record LIKE '%%' + ORDER BY timestamp DESC) AS rb + ) AS y; -END TRY -BEGIN CATCH - RAISERROR (N'Failure returning debug temp tables', 0,1) WITH NOWAIT; + END; /* IF @Seconds >= 30 */ - IF @sql_select IS NOT NULL - BEGIN - SET @msg = N'Last @sql_select: ' + @sql_select; - RAISERROR(@msg, 0, 1) WITH NOWAIT; - END; - SELECT @msg = @DatabaseName + N' database failed to process. ' + ERROR_MESSAGE(), @error_severity = ERROR_SEVERITY(), @error_state = ERROR_STATE(); - RAISERROR (@msg, @error_severity, @error_state) WITH NOWAIT; - - - WHILE @@TRANCOUNT > 0 - ROLLBACK; + /* If we didn't find anything, apologize. */ + IF NOT EXISTS (SELECT * FROM #BlitzFirstResults WHERE Priority < 250) + BEGIN - RETURN; -END CATCH; + INSERT INTO #BlitzFirstResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + VALUES ( -1 , + 1 , + 'No Problems Found' , + 'From Your Community Volunteers' , + 'http://FirstResponderKit.org/' , + 'Try running our more in-depth checks with sp_Blitz, or there may not be an unusual SQL Server performance problem. ' + ); -/* -Ways to run this thing + END; /*IF NOT EXISTS (SELECT * FROM #BlitzFirstResults) */ ---Debug -EXEC sp_BlitzQueryStore @DatabaseName = 'StackOverflow', @Debug = 1 + /* Add credits for the nice folks who put so much time into building and maintaining this for free: */ + INSERT INTO #BlitzFirstResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + VALUES ( -1 , + 255 , + 'Thanks!' , + 'From Your Community Volunteers' , + 'http://FirstResponderKit.org/' , + 'To get help or add your own contributions, join us at http://FirstResponderKit.org.' + ); ---Get the top 1 -EXEC sp_BlitzQueryStore @DatabaseName = 'StackOverflow', @Top = 1, @Debug = 1 + INSERT INTO #BlitzFirstResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details ---Use a StartDate -EXEC sp_BlitzQueryStore @DatabaseName = 'StackOverflow', @Top = 1, @StartDate = '20170527' - ---Use an EndDate -EXEC sp_BlitzQueryStore @DatabaseName = 'StackOverflow', @Top = 1, @EndDate = '20170527' - ---Use Both -EXEC sp_BlitzQueryStore @DatabaseName = 'StackOverflow', @Top = 1, @StartDate = '20170526', @EndDate = '20170527' + ) + VALUES ( -1 , + 0 , + 'sp_BlitzFirst ' + CAST(CONVERT(DATETIMEOFFSET, @VersionDate, 102) AS VARCHAR(100)), + 'From Your Community Volunteers' , + 'http://FirstResponderKit.org/' , + 'We hope you found this tool useful.' + ); ---Set a minimum execution count -EXEC sp_BlitzQueryStore @DatabaseName = 'StackOverflow', @Top = 1, @MinimumExecutionCount = 10 + /* Outdated sp_BlitzFirst - sp_BlitzFirst is Over 6 Months Old - CheckID 27 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 27',10,1) WITH NOWAIT; + END ---Set a duration minimum -EXEC sp_BlitzQueryStore @DatabaseName = 'StackOverflow', @Top = 1, @DurationFilter = 5 + IF DATEDIFF(MM, @VersionDate, SYSDATETIMEOFFSET()) > 6 + BEGIN + INSERT INTO #BlitzFirstResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT 27 AS CheckID , + 0 AS Priority , + 'Outdated sp_BlitzFirst' AS FindingsGroup , + 'sp_BlitzFirst is Over 6 Months Old' AS Finding , + 'http://FirstResponderKit.org/' AS URL , + 'Some things get better with age, like fine wine and your T-SQL. However, sp_BlitzFirst is not one of those things - time to go download the current one.' AS Details; + END; ---Look for a stored procedure name (that doesn't exist!) -EXEC sp_BlitzQueryStore @DatabaseName = 'StackOverflow', @Top = 1, @StoredProcName = 'blah' + IF @CheckServerInfo = 0 /* Github #1680 */ + BEGIN + DELETE #BlitzFirstResults + WHERE FindingsGroup = 'Server Info'; + END ---Look for a stored procedure name that does (at least On My Computer®) -EXEC sp_BlitzQueryStore @DatabaseName = 'StackOverflow', @Top = 1, @StoredProcName = 'UserReportExtended' + RAISERROR('Analysis finished, outputting results',10,1) WITH NOWAIT; ---Look for failed queries -EXEC sp_BlitzQueryStore @DatabaseName = 'StackOverflow', @Top = 1, @Failed = 1 ---Filter by plan_id -EXEC sp_BlitzQueryStore @DatabaseName = 'StackOverflow', @PlanIdFilter = 3356 + /* If they want to run sp_BlitzCache and export to table, go for it. */ + IF @OutputTableNameBlitzCache IS NOT NULL + AND @OutputDatabaseName IS NOT NULL + AND @OutputSchemaName IS NOT NULL + AND EXISTS ( SELECT * + FROM sys.databases + WHERE QUOTENAME([name]) = @OutputDatabaseName) + BEGIN ---Filter by query_id -EXEC sp_BlitzQueryStore @DatabaseName = 'StackOverflow', @QueryIdFilter = 2958 -*/ + RAISERROR('Calling sp_BlitzCache',10,1) WITH NOWAIT; -END; -GO -IF OBJECT_ID('dbo.sp_BlitzWho') IS NULL - EXEC ('CREATE PROCEDURE dbo.sp_BlitzWho AS RETURN 0;') -GO + /* If they have an newer version of sp_BlitzCache that supports @MinutesBack and @CheckDateOverride */ + IF EXISTS (SELECT * FROM sys.objects o + INNER JOIN sys.parameters pMB ON o.object_id = pMB.object_id AND pMB.name = '@MinutesBack' + INNER JOIN sys.parameters pCDO ON o.object_id = pCDO.object_id AND pCDO.name = '@CheckDateOverride' + WHERE o.name = 'sp_BlitzCache') + BEGIN + /* Get the most recent sp_BlitzCache execution before this one - don't use sp_BlitzFirst because user logs are added in there at any time */ + SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' + + @OutputDatabaseName + + '.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''' + + @OutputSchemaName + ''' AND QUOTENAME(TABLE_NAME) = ''' + + QUOTENAME(@OutputTableNameBlitzCache) + ''') SELECT TOP 1 @BlitzCacheMinutesBack = DATEDIFF(MI,CheckDate,SYSDATETIMEOFFSET()) FROM ' + + @OutputDatabaseName + '.' + + @OutputSchemaName + '.' + + QUOTENAME(@OutputTableNameBlitzCache) + + ' WHERE ServerName = ''' + CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128)) + ''' ORDER BY CheckDate DESC;'; + EXEC sp_executesql @StringToExecute, N'@BlitzCacheMinutesBack INT OUTPUT', @BlitzCacheMinutesBack OUTPUT; -ALTER PROCEDURE dbo.sp_BlitzWho - @Help TINYINT = 0 , - @ShowSleepingSPIDs TINYINT = 0, - @ExpertMode BIT = 0, - @Debug BIT = 0, - @OutputDatabaseName NVARCHAR(256) = NULL , - @OutputSchemaName NVARCHAR(256) = NULL , - @OutputTableName NVARCHAR(256) = NULL , - @OutputTableRetentionDays TINYINT = 3 , - @MinElapsedSeconds INT = 0 , - @MinCPUTime INT = 0 , - @MinLogicalReads INT = 0 , - @MinPhysicalReads INT = 0 , - @MinWrites INT = 0 , - @MinTempdbMB INT = 0 , - @MinRequestedMemoryKB INT = 0 , - @MinBlockingSeconds INT = 0 , - @CheckDateOverride DATETIMEOFFSET = NULL, - @Version VARCHAR(30) = NULL OUTPUT, - @VersionDate DATETIME = NULL OUTPUT, - @VersionCheckMode BIT = 0, - @SortOrder NVARCHAR(256) = N'elapsed time' -AS -BEGIN - SET NOCOUNT ON; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - - SELECT @Version = '8.0', @VersionDate = '20210117'; - - IF(@VersionCheckMode = 1) - BEGIN - RETURN; - END; + /* If there's no data, let's just analyze the last 15 minutes of the plan cache */ + IF @BlitzCacheMinutesBack IS NULL OR @BlitzCacheMinutesBack < 1 OR @BlitzCacheMinutesBack > 60 + SET @BlitzCacheMinutesBack = 15; + EXEC sp_BlitzCache + @OutputDatabaseName = @UnquotedOutputDatabaseName, + @OutputSchemaName = @UnquotedOutputSchemaName, + @OutputTableName = @OutputTableNameBlitzCache, + @CheckDateOverride = @StartSampleTime, + @SortOrder = 'all', + @SkipAnalysis = @BlitzCacheSkipAnalysis, + @MinutesBack = @BlitzCacheMinutesBack, + @Debug = @Debug; + /* Delete history older than @OutputTableRetentionDays */ + SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' + + @OutputDatabaseName + + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' + + @OutputSchemaName + ''') DELETE ' + + @OutputDatabaseName + '.' + + @OutputSchemaName + '.' + + QUOTENAME(@OutputTableNameBlitzCache) + + ' WHERE ServerName = @SrvName AND CheckDate < @CheckDate;'; + EXEC sp_executesql @StringToExecute, + N'@SrvName NVARCHAR(128), @CheckDate date', + @LocalServerName, @OutputTableCleanupDate; - IF @Help = 1 - BEGIN - PRINT ' -sp_BlitzWho from http://FirstResponderKit.org -This script gives you a snapshot of everything currently executing on your SQL Server. + END; -To learn more, visit http://FirstResponderKit.org where you can download new -versions for free, watch training videos on how it works, get more info on -the findings, contribute your own code, and more. + ELSE + BEGIN + /* No sp_BlitzCache found, or it's outdated - CheckID 36 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 36',10,1) WITH NOWAIT; + END -Known limitations of this version: - - Only Microsoft-supported versions of SQL Server. Sorry, 2005 and 2000. - - Outputting to table is only supported with SQL Server 2012 and higher. - - If @OutputDatabaseName and @OutputSchemaName are populated, the database and - schema must already exist. We will not create them, only the table. - -MIT License + INSERT INTO #BlitzFirstResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT 36 AS CheckID , + 0 AS Priority , + 'Outdated or Missing sp_BlitzCache' AS FindingsGroup , + 'Update Your sp_BlitzCache' AS Finding , + 'http://FirstResponderKit.org/' AS URL , + 'You passed in @OutputTableNameBlitzCache, but we need a newer version of sp_BlitzCache in master or the current database.' AS Details; + END; -Copyright (c) 2021 Brent Ozar Unlimited + RAISERROR('sp_BlitzCache Finished',10,1) WITH NOWAIT; -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: + END; /* End running sp_BlitzCache */ -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. + /* @OutputTableName lets us export the results to a permanent table */ + IF @OutputDatabaseName IS NOT NULL + AND @OutputSchemaName IS NOT NULL + AND @OutputTableName IS NOT NULL + AND @OutputTableName NOT LIKE '#%' + AND EXISTS ( SELECT * + FROM sys.databases + WHERE QUOTENAME([name]) = @OutputDatabaseName) + BEGIN + SET @StringToExecute = 'USE ' + + @OutputDatabaseName + + '; IF EXISTS(SELECT * FROM ' + + @OutputDatabaseName + + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' + + @OutputSchemaName + + ''') AND NOT EXISTS (SELECT * FROM ' + + @OutputDatabaseName + + '.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''' + + @OutputSchemaName + ''' AND QUOTENAME(TABLE_NAME) = ''' + + @OutputTableName + ''') CREATE TABLE ' + + @OutputSchemaName + '.' + + @OutputTableName + + ' (ID INT IDENTITY(1,1) NOT NULL, + ServerName NVARCHAR(128), + CheckDate DATETIMEOFFSET, + CheckID INT NOT NULL, + Priority TINYINT NOT NULL, + FindingsGroup VARCHAR(50) NOT NULL, + Finding VARCHAR(200) NOT NULL, + URL VARCHAR(200) NOT NULL, + Details NVARCHAR(4000) NULL, + HowToStopIt [XML] NULL, + QueryPlan [XML] NULL, + QueryText NVARCHAR(MAX) NULL, + StartTime DATETIMEOFFSET NULL, + LoginName NVARCHAR(128) NULL, + NTUserName NVARCHAR(128) NULL, + OriginalLoginName NVARCHAR(128) NULL, + ProgramName NVARCHAR(128) NULL, + HostName NVARCHAR(128) NULL, + DatabaseID INT NULL, + DatabaseName NVARCHAR(128) NULL, + OpenTransactionCount INT NULL, + DetailsInt INT NULL, + QueryHash BINARY(8) NULL, + JoinKey AS ServerName + CAST(CheckDate AS NVARCHAR(50)), + PRIMARY KEY CLUSTERED (ID ASC));'; -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -'; -RETURN; -END; /* @Help = 1 */ + EXEC(@StringToExecute); -/* Get the major and minor build numbers */ -DECLARE @ProductVersion NVARCHAR(128) - ,@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]) - ,@EnhanceFlag BIT = 0 - ,@BlockingCheck NVARCHAR(MAX) - ,@StringToSelect NVARCHAR(MAX) - ,@StringToExecute NVARCHAR(MAX) - ,@OutputTableCleanupDate DATE - ,@SessionWaits BIT = 0 - ,@SessionWaitsSQL NVARCHAR(MAX) = - N'LEFT JOIN ( SELECT DISTINCT - wait.session_id , - ( SELECT TOP 5 waitwait.wait_type + N'' ('' - + CAST(MAX(waitwait.wait_time_ms) AS NVARCHAR(128)) - + N'' ms), '' - FROM sys.dm_exec_session_wait_stats AS waitwait - WHERE waitwait.session_id = wait.session_id - GROUP BY waitwait.wait_type - HAVING SUM(waitwait.wait_time_ms) > 5 - ORDER BY 1 - FOR - XML PATH('''') ) AS session_wait_info - FROM sys.dm_exec_session_wait_stats AS wait ) AS wt2 - ON s.session_id = wt2.session_id - LEFT JOIN sys.dm_exec_query_stats AS session_stats - ON r.sql_handle = session_stats.sql_handle - AND r.plan_handle = session_stats.plan_handle - AND r.statement_start_offset = session_stats.statement_start_offset - AND r.statement_end_offset = session_stats.statement_end_offset' - ,@QueryStatsXMLselect NVARCHAR(MAX) = N' CAST(COALESCE(qs_live.query_plan, '''') AS XML) AS live_query_plan , ' - ,@QueryStatsXMLSQL NVARCHAR(MAX) = N'OUTER APPLY sys.dm_exec_query_statistics_xml(s.session_id) qs_live' - ,@ObjectFullName NVARCHAR(2000) - ,@OutputTableNameQueryStats_View NVARCHAR(256) - ,@LineFeed NVARCHAR(MAX) /* Had to set as MAX up from 10 as it was truncating the view creation*/; + /* If the table doesn't have the new QueryHash column, add it. See Github #2162. */ + SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableName; + SET @StringToExecute = N'IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns + WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''QueryHash'') + ALTER TABLE ' + @ObjectFullName + N' ADD QueryHash BINARY(8) NULL;'; + EXEC(@StringToExecute); -/* Let's get @SortOrder set to lower case here for comparisons later */ -SET @SortOrder = REPLACE(LOWER(@SortOrder), N' ', N'_'); + /* If the table doesn't have the new JoinKey computed column, add it. See Github #2164. */ + SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableName; + SET @StringToExecute = N'IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns + WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''JoinKey'') + ALTER TABLE ' + @ObjectFullName + N' ADD JoinKey AS ServerName + CAST(CheckDate AS NVARCHAR(50));'; + EXEC(@StringToExecute); -SET @ProductVersion = CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)); -SELECT @ProductVersionMajor = SUBSTRING(@ProductVersion, 1,CHARINDEX('.', @ProductVersion) + 1 ), - @ProductVersionMinor = PARSENAME(CONVERT(VARCHAR(32), @ProductVersion), 2) -IF EXISTS (SELECT * FROM sys.all_columns WHERE object_id = OBJECT_ID('sys.dm_exec_query_statistics_xml') AND name = 'query_plan') - BEGIN - SET @QueryStatsXMLselect = N' CAST(COALESCE(qs_live.query_plan, '''') AS XML) AS live_query_plan , '; - SET @QueryStatsXMLSQL = N'OUTER APPLY sys.dm_exec_query_statistics_xml(s.session_id) qs_live'; - END - ELSE - BEGIN - SET @QueryStatsXMLselect = N' NULL AS live_query_plan , '; - SET @QueryStatsXMLSQL = N' '; - END + SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' + + @OutputDatabaseName + + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' + + @OutputSchemaName + ''') INSERT ' + + @OutputDatabaseName + '.' + + @OutputSchemaName + '.' + + @OutputTableName + + ' (ServerName, CheckDate, CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, QueryText, StartTime, LoginName, NTUserName, OriginalLoginName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, DetailsInt, QueryHash) SELECT ' + + ' @SrvName, @CheckDate, CheckID, Priority, FindingsGroup, Finding, URL, LEFT(Details,4000), HowToStopIt, QueryPlan, QueryText, StartTime, LoginName, NTUserName, OriginalLoginName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, DetailsInt, QueryHash FROM #BlitzFirstResults ORDER BY Priority , FindingsGroup , Finding , Details'; + + EXEC sp_executesql @StringToExecute, + N'@SrvName NVARCHAR(128), @CheckDate datetimeoffset', + @LocalServerName, @StartSampleTime; -SELECT - @OutputTableNameQueryStats_View = QUOTENAME(@OutputTableName + '_Deltas'), - @OutputDatabaseName = QUOTENAME(@OutputDatabaseName), - @OutputSchemaName = QUOTENAME(@OutputSchemaName), - @OutputTableName = QUOTENAME(@OutputTableName), - @LineFeed = CHAR(13) + CHAR(10); + /* Delete history older than @OutputTableRetentionDays */ + SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' + + @OutputDatabaseName + + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' + + @OutputSchemaName + ''') DELETE ' + + @OutputDatabaseName + '.' + + @OutputSchemaName + '.' + + @OutputTableName + + ' WHERE ServerName = @SrvName AND CheckDate < @CheckDate ;'; + + EXEC sp_executesql @StringToExecute, + N'@SrvName NVARCHAR(128), @CheckDate date', + @LocalServerName, @OutputTableCleanupDate; -IF @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @OutputTableName IS NOT NULL - AND EXISTS ( SELECT * - FROM sys.databases - WHERE QUOTENAME([name]) = @OutputDatabaseName) - BEGIN - SET @ExpertMode = 1; /* Force ExpertMode when we're logging to table */ + END; + ELSE IF (SUBSTRING(@OutputTableName, 2, 2) = '##') + BEGIN + SET @StringToExecute = N' IF (OBJECT_ID(''tempdb..' + + @OutputTableName + + ''') IS NULL) CREATE TABLE ' + + @OutputTableName + + ' (ID INT IDENTITY(1,1) NOT NULL, + ServerName NVARCHAR(128), + CheckDate DATETIMEOFFSET, + CheckID INT NOT NULL, + Priority TINYINT NOT NULL, + FindingsGroup VARCHAR(50) NOT NULL, + Finding VARCHAR(200) NOT NULL, + URL VARCHAR(200) NOT NULL, + Details NVARCHAR(4000) NULL, + HowToStopIt [XML] NULL, + QueryPlan [XML] NULL, + QueryText NVARCHAR(MAX) NULL, + StartTime DATETIMEOFFSET NULL, + LoginName NVARCHAR(128) NULL, + NTUserName NVARCHAR(128) NULL, + OriginalLoginName NVARCHAR(128) NULL, + ProgramName NVARCHAR(128) NULL, + HostName NVARCHAR(128) NULL, + DatabaseID INT NULL, + DatabaseName NVARCHAR(128) NULL, + OpenTransactionCount INT NULL, + DetailsInt INT NULL, + QueryHash BINARY(8) NULL, + JoinKey AS ServerName + CAST(CheckDate AS NVARCHAR(50)), + PRIMARY KEY CLUSTERED (ID ASC));' + + ' INSERT ' + + @OutputTableName + + ' (ServerName, CheckDate, CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, QueryText, StartTime, LoginName, NTUserName, OriginalLoginName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, DetailsInt) SELECT ' + + ' @SrvName, @CheckDate, CheckID, Priority, FindingsGroup, Finding, URL, LEFT(Details,4000), HowToStopIt, QueryPlan, QueryText, StartTime, LoginName, NTUserName, OriginalLoginName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, DetailsInt FROM #BlitzFirstResults ORDER BY Priority , FindingsGroup , Finding , Details'; + + EXEC sp_executesql @StringToExecute, + N'@SrvName NVARCHAR(128), @CheckDate datetimeoffset', + @LocalServerName, @StartSampleTime; + END; + ELSE IF (SUBSTRING(@OutputTableName, 2, 1) = '#') + BEGIN + RAISERROR('Due to the nature of Dymamic SQL, only global (i.e. double pound (##)) temp tables are supported for @OutputTableName', 16, 0); + END; - /* Create the table if it doesn't exist */ - SET @StringToExecute = N'USE ' - + @OutputDatabaseName - + N'; IF EXISTS(SELECT * FROM ' - + @OutputDatabaseName - + N'.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' - + @OutputSchemaName - + N''') AND NOT EXISTS (SELECT * FROM ' - + @OutputDatabaseName - + N'.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''' - + @OutputSchemaName + N''' AND QUOTENAME(TABLE_NAME) = ''' - + @OutputTableName + N''') CREATE TABLE ' - + @OutputSchemaName + N'.' - + @OutputTableName - + N'('; - SET @StringToExecute = @StringToExecute + N' - ID INT IDENTITY(1,1) NOT NULL, - ServerName NVARCHAR(128) NOT NULL, - CheckDate DATETIMEOFFSET NOT NULL, - [elapsed_time] [varchar](41) NULL, - [session_id] [smallint] NOT NULL, - [database_name] [nvarchar](128) NULL, - [query_text] [nvarchar](max) NULL, - [query_plan] [xml] NULL, - [live_query_plan] [xml] NULL, - [query_cost] [float] NULL, - [status] [nvarchar](30) NOT NULL, - [wait_info] [nvarchar](max) NULL, - [top_session_waits] [nvarchar](max) NULL, - [blocking_session_id] [smallint] NULL, - [open_transaction_count] [int] NULL, - [is_implicit_transaction] [int] NOT NULL, - [nt_domain] [nvarchar](128) NULL, - [host_name] [nvarchar](128) NULL, - [login_name] [nvarchar](128) NOT NULL, - [nt_user_name] [nvarchar](128) NULL, - [program_name] [nvarchar](128) NULL, - [fix_parameter_sniffing] [nvarchar](150) NULL, - [client_interface_name] [nvarchar](32) NULL, - [login_time] [datetime] NOT NULL, - [start_time] [datetime] NULL, - [request_time] [datetime] NULL, - [request_cpu_time] [int] NULL, - [request_logical_reads] [bigint] NULL, - [request_writes] [bigint] NULL, - [request_physical_reads] [bigint] NULL, - [session_cpu] [int] NOT NULL, - [session_logical_reads] [bigint] NOT NULL, - [session_physical_reads] [bigint] NOT NULL, - [session_writes] [bigint] NOT NULL, - [tempdb_allocations_mb] [decimal](38, 2) NULL, - [memory_usage] [int] NOT NULL, - [estimated_completion_time] [bigint] NULL, - [percent_complete] [real] NULL, - [deadlock_priority] [int] NULL, - [transaction_isolation_level] [varchar](33) NOT NULL, - [degree_of_parallelism] [smallint] NULL, - [last_dop] [bigint] NULL, - [min_dop] [bigint] NULL, - [max_dop] [bigint] NULL, - [last_grant_kb] [bigint] NULL, - [min_grant_kb] [bigint] NULL, - [max_grant_kb] [bigint] NULL, - [last_used_grant_kb] [bigint] NULL, - [min_used_grant_kb] [bigint] NULL, - [max_used_grant_kb] [bigint] NULL, - [last_ideal_grant_kb] [bigint] NULL, - [min_ideal_grant_kb] [bigint] NULL, - [max_ideal_grant_kb] [bigint] NULL, - [last_reserved_threads] [bigint] NULL, - [min_reserved_threads] [bigint] NULL, - [max_reserved_threads] [bigint] NULL, - [last_used_threads] [bigint] NULL, - [min_used_threads] [bigint] NULL, - [max_used_threads] [bigint] NULL, - [grant_time] [varchar](20) NULL, - [requested_memory_kb] [bigint] NULL, - [grant_memory_kb] [bigint] NULL, - [is_request_granted] [varchar](39) NOT NULL, - [required_memory_kb] [bigint] NULL, - [query_memory_grant_used_memory_kb] [bigint] NULL, - [ideal_memory_kb] [bigint] NULL, - [is_small] [bit] NULL, - [timeout_sec] [int] NULL, - [resource_semaphore_id] [smallint] NULL, - [wait_order] [varchar](20) NULL, - [wait_time_ms] [varchar](20) NULL, - [next_candidate_for_memory_grant] [varchar](3) NOT NULL, - [target_memory_kb] [bigint] NULL, - [max_target_memory_kb] [varchar](30) NULL, - [total_memory_kb] [bigint] NULL, - [available_memory_kb] [bigint] NULL, - [granted_memory_kb] [bigint] NULL, - [query_resource_semaphore_used_memory_kb] [bigint] NULL, - [grantee_count] [int] NULL, - [waiter_count] [int] NULL, - [timeout_error_count] [bigint] NULL, - [forced_grant_count] [varchar](30) NULL, - [workload_group_name] [sysname] NULL, - [resource_pool_name] [sysname] NULL, - [context_info] [varchar](128) NULL, - [query_hash] [binary](8) NULL, - [query_plan_hash] [binary](8) NULL, - [sql_handle] [varbinary] (64) NULL, - [plan_handle] [varbinary] (64) NULL, - [statement_start_offset] INT NULL, - [statement_end_offset] INT NULL, - JoinKey AS ServerName + CAST(CheckDate AS NVARCHAR(50)), - PRIMARY KEY CLUSTERED (ID ASC));'; - IF @Debug = 1 - BEGIN - PRINT CONVERT(VARCHAR(8000), SUBSTRING(@StringToExecute, 0, 8000)) - PRINT CONVERT(VARCHAR(8000), SUBSTRING(@StringToExecute, 8000, 16000)) - END - EXEC(@StringToExecute); + /* @OutputTableNameFileStats lets us export the results to a permanent table */ + IF @OutputDatabaseName IS NOT NULL + AND @OutputSchemaName IS NOT NULL + AND @OutputTableNameFileStats IS NOT NULL + AND @OutputTableNameFileStats NOT LIKE '#%' + AND EXISTS ( SELECT * + FROM sys.databases + WHERE QUOTENAME([name]) = @OutputDatabaseName) + BEGIN + /* Create the table */ + SET @StringToExecute = 'USE ' + + @OutputDatabaseName + + '; IF EXISTS(SELECT * FROM ' + + @OutputDatabaseName + + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' + + @OutputSchemaName + + ''') AND NOT EXISTS (SELECT * FROM ' + + @OutputDatabaseName + + '.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''' + + @OutputSchemaName + ''' AND QUOTENAME(TABLE_NAME) = ''' + + @OutputTableNameFileStats + ''') CREATE TABLE ' + + @OutputSchemaName + '.' + + @OutputTableNameFileStats + + ' (ID INT IDENTITY(1,1) NOT NULL, + ServerName NVARCHAR(128), + CheckDate DATETIMEOFFSET, + DatabaseID INT NOT NULL, + FileID INT NOT NULL, + DatabaseName NVARCHAR(256) , + FileLogicalName NVARCHAR(256) , + TypeDesc NVARCHAR(60) , + SizeOnDiskMB BIGINT , + io_stall_read_ms BIGINT , + num_of_reads BIGINT , + bytes_read BIGINT , + io_stall_write_ms BIGINT , + num_of_writes BIGINT , + bytes_written BIGINT, + PhysicalName NVARCHAR(520) , + PRIMARY KEY CLUSTERED (ID ASC));'; - /* If the table doesn't have the new JoinKey computed column, add it. See Github #2162. */ - SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableName; - SET @StringToExecute = N'IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns - WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''JoinKey'') - ALTER TABLE ' + @ObjectFullName + N' ADD JoinKey AS ServerName + CAST(CheckDate AS NVARCHAR(50));'; - EXEC(@StringToExecute); + EXEC(@StringToExecute); - /* Delete history older than @OutputTableRetentionDays */ - SET @OutputTableCleanupDate = CAST( (DATEADD(DAY, -1 * @OutputTableRetentionDays, GETDATE() ) ) AS DATE); - SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' - + @OutputDatabaseName - + N'.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' - + @OutputSchemaName + N''') DELETE ' - + @OutputDatabaseName + '.' - + @OutputSchemaName + '.' - + @OutputTableName - + N' WHERE ServerName = @SrvName AND CheckDate < @CheckDate;'; - IF @Debug = 1 - BEGIN - PRINT CONVERT(VARCHAR(8000), SUBSTRING(@StringToExecute, 0, 8000)) - PRINT CONVERT(VARCHAR(8000), SUBSTRING(@StringToExecute, 8000, 16000)) - END - EXEC sp_executesql @StringToExecute, - N'@SrvName NVARCHAR(128), @CheckDate date', - @@SERVERNAME, @OutputTableCleanupDate; + SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableNameFileStats_View; - SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableNameQueryStats_View; + /* If the view exists without the most recently added columns, drop it. See Github #2162. */ + IF OBJECT_ID(@ObjectFullName) IS NOT NULL + BEGIN + SET @StringToExecute = N'USE ' + @OutputDatabaseName + N'; IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns + WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''JoinKey'') + DROP VIEW ' + @OutputSchemaName + N'.' + @OutputTableNameFileStats_View + N';'; + + EXEC(@StringToExecute); + END /* Create the view */ IF OBJECT_ID(@ObjectFullName) IS NULL BEGIN - SET @StringToExecute = N'USE ' + SET @StringToExecute = 'USE ' + @OutputDatabaseName - + N'; EXEC (''CREATE VIEW ' + + '; EXEC (''CREATE VIEW ' + @OutputSchemaName + '.' - + @OutputTableNameQueryStats_View + N' AS ' + @LineFeed - + N'WITH MaxQueryDuration AS ' + @LineFeed - + N'( ' + @LineFeed - + N' SELECT ' + @LineFeed - + N' MIN([ID]) AS [MinID], ' + @LineFeed - + N' MAX([ID]) AS [MaxID] ' + @LineFeed - + N' FROM ' + @OutputSchemaName + '.' + @OutputTableName + '' + @LineFeed - + N' GROUP BY [ServerName], ' + @LineFeed - + N' [session_id], ' + @LineFeed - + N' [database_name], ' + @LineFeed - + N' [request_time], ' + @LineFeed - + N' [start_time], ' + @LineFeed - + N' [sql_handle] ' + @LineFeed - + N') ' + @LineFeed - + N'SELECT ' + @LineFeed - + N' [ID], ' + @LineFeed - + N' [ServerName], ' + @LineFeed - + N' [CheckDate], ' + @LineFeed - + N' [elapsed_time], ' + @LineFeed - + N' [session_id], ' + @LineFeed - + N' [database_name], ' + @LineFeed - + N' [query_text_snippet], ' + @LineFeed - + N' [query_plan], ' + @LineFeed - + N' [live_query_plan], ' + @LineFeed - + N' [query_cost], ' + @LineFeed - + N' [status], ' + @LineFeed - + N' [wait_info], ' + @LineFeed - + N' [top_session_waits], ' + @LineFeed - + N' [blocking_session_id], ' + @LineFeed - + N' [open_transaction_count], ' + @LineFeed - + N' [is_implicit_transaction], ' + @LineFeed - + N' [nt_domain], ' + @LineFeed - + N' [host_name], ' + @LineFeed - + N' [login_name], ' + @LineFeed - + N' [nt_user_name], ' + @LineFeed - + N' [program_name], ' + @LineFeed - + N' [fix_parameter_sniffing], ' + @LineFeed - + N' [client_interface_name], ' + @LineFeed - + N' [login_time], ' + @LineFeed - + N' [start_time], ' + @LineFeed - + N' [request_time], ' + @LineFeed - + N' [request_cpu_time], ' + @LineFeed - + N' [degree_of_parallelism], ' + @LineFeed - + N' [request_logical_reads], ' + @LineFeed - + N' [Logical_Reads_MB], ' + @LineFeed - + N' [request_writes], ' + @LineFeed - + N' [Logical_Writes_MB], ' + @LineFeed - + N' [request_physical_reads], ' + @LineFeed - + N' [Physical_reads_MB], ' + @LineFeed - + N' [session_cpu], ' + @LineFeed - + N' [session_logical_reads], ' + @LineFeed - + N' [session_logical_reads_MB], ' + @LineFeed - + N' [session_physical_reads], ' + @LineFeed - + N' [session_physical_reads_MB], ' + @LineFeed - + N' [session_writes], ' + @LineFeed - + N' [session_writes_MB], ' + @LineFeed - + N' [tempdb_allocations_mb], ' + @LineFeed - + N' [memory_usage], ' + @LineFeed - + N' [estimated_completion_time], ' + @LineFeed - + N' [percent_complete], ' + @LineFeed - + N' [deadlock_priority], ' + @LineFeed - + N' [transaction_isolation_level], ' + @LineFeed - + N' [last_dop], ' + @LineFeed - + N' [min_dop], ' + @LineFeed - + N' [max_dop], ' + @LineFeed - + N' [last_grant_kb], ' + @LineFeed - + N' [min_grant_kb], ' + @LineFeed - + N' [max_grant_kb], ' + @LineFeed - + N' [last_used_grant_kb], ' + @LineFeed - + N' [min_used_grant_kb], ' + @LineFeed - + N' [max_used_grant_kb], ' + @LineFeed - + N' [last_ideal_grant_kb], ' + @LineFeed - + N' [min_ideal_grant_kb], ' + @LineFeed - + N' [max_ideal_grant_kb], ' + @LineFeed - + N' [last_reserved_threads], ' + @LineFeed - + N' [min_reserved_threads], ' + @LineFeed - + N' [max_reserved_threads], ' + @LineFeed - + N' [last_used_threads], ' + @LineFeed - + N' [min_used_threads], ' + @LineFeed - + N' [max_used_threads], ' + @LineFeed - + N' [grant_time], ' + @LineFeed - + N' [requested_memory_kb], ' + @LineFeed - + N' [grant_memory_kb], ' + @LineFeed - + N' [is_request_granted], ' + @LineFeed - + N' [required_memory_kb], ' + @LineFeed - + N' [query_memory_grant_used_memory_kb], ' + @LineFeed - + N' [ideal_memory_kb], ' + @LineFeed - + N' [is_small], ' + @LineFeed - + N' [timeout_sec], ' + @LineFeed - + N' [resource_semaphore_id], ' + @LineFeed - + N' [wait_order], ' + @LineFeed - + N' [wait_time_ms], ' + @LineFeed - + N' [next_candidate_for_memory_grant], ' + @LineFeed - + N' [target_memory_kb], ' + @LineFeed - + N' [max_target_memory_kb], ' + @LineFeed - + N' [total_memory_kb], ' + @LineFeed - + N' [available_memory_kb], ' + @LineFeed - + N' [granted_memory_kb], ' + @LineFeed - + N' [query_resource_semaphore_used_memory_kb], ' + @LineFeed - + N' [grantee_count], ' + @LineFeed - + N' [waiter_count], ' + @LineFeed - + N' [timeout_error_count], ' + @LineFeed - + N' [forced_grant_count], ' + @LineFeed - + N' [workload_group_name], ' + @LineFeed - + N' [resource_pool_name], ' + @LineFeed - + N' [context_info], ' + @LineFeed - + N' [query_hash], ' + @LineFeed - + N' [query_plan_hash], ' + @LineFeed - + N' [sql_handle], ' + @LineFeed - + N' [plan_handle], ' + @LineFeed - + N' [statement_start_offset], ' + @LineFeed - + N' [statement_end_offset] ' + @LineFeed - + N' FROM ' + @LineFeed - + N' ( ' + @LineFeed - + N' SELECT ' + @LineFeed - + N' [ID], ' + @LineFeed - + N' [ServerName], ' + @LineFeed - + N' [CheckDate], ' + @LineFeed - + N' [elapsed_time], ' + @LineFeed - + N' [session_id], ' + @LineFeed - + N' [database_name], ' + @LineFeed - + N' /* Truncate the query text to aid performance of painting the rows in SSMS */ ' + @LineFeed - + N' CAST([query_text] AS NVARCHAR(1000)) AS [query_text_snippet], ' + @LineFeed - + N' [query_plan], ' + @LineFeed - + N' [live_query_plan], ' + @LineFeed - + N' [query_cost], ' + @LineFeed - + N' [status], ' + @LineFeed - + N' [wait_info], ' + @LineFeed - + N' [top_session_waits], ' + @LineFeed - + N' [blocking_session_id], ' + @LineFeed - + N' [open_transaction_count], ' + @LineFeed - + N' [is_implicit_transaction], ' + @LineFeed - + N' [nt_domain], ' + @LineFeed - + N' [host_name], ' + @LineFeed - + N' [login_name], ' + @LineFeed - + N' [nt_user_name], ' + @LineFeed - + N' [program_name], ' + @LineFeed - + N' [fix_parameter_sniffing], ' + @LineFeed - + N' [client_interface_name], ' + @LineFeed - + N' [login_time], ' + @LineFeed - + N' [start_time], ' + @LineFeed - + N' [request_time], ' + @LineFeed - + N' [request_cpu_time], ' + @LineFeed - + N' [degree_of_parallelism], ' + @LineFeed - + N' [request_logical_reads], ' + @LineFeed - + N' ((CAST([request_logical_reads] AS MONEY)* 8)/ 1024) [Logical_Reads_MB], ' + @LineFeed - + N' [request_writes], ' + @LineFeed - + N' ((CAST([request_writes] AS MONEY)* 8)/ 1024) [Logical_Writes_MB], ' + @LineFeed - + N' [request_physical_reads], ' + @LineFeed - + N' ((CAST([request_physical_reads] AS MONEY)* 8)/ 1024) [Physical_reads_MB], ' + @LineFeed - + N' [session_cpu], ' + @LineFeed - + N' [session_logical_reads], ' + @LineFeed - + N' ((CAST([session_logical_reads] AS MONEY)* 8)/ 1024) [session_logical_reads_MB], ' + @LineFeed - + N' [session_physical_reads], ' + @LineFeed - + N' ((CAST([session_physical_reads] AS MONEY)* 8)/ 1024) [session_physical_reads_MB], ' + @LineFeed - + N' [session_writes], ' + @LineFeed - + N' ((CAST([session_writes] AS MONEY)* 8)/ 1024) [session_writes_MB], ' + @LineFeed - + N' [tempdb_allocations_mb], ' + @LineFeed - + N' [memory_usage], ' + @LineFeed - + N' [estimated_completion_time], ' + @LineFeed - + N' [percent_complete], ' + @LineFeed - + N' [deadlock_priority], ' + @LineFeed - + N' [transaction_isolation_level], ' + @LineFeed - + N' [last_dop], ' + @LineFeed - + N' [min_dop], ' + @LineFeed - + N' [max_dop], ' + @LineFeed - + N' [last_grant_kb], ' + @LineFeed - + N' [min_grant_kb], ' + @LineFeed - + N' [max_grant_kb], ' + @LineFeed - + N' [last_used_grant_kb], ' + @LineFeed - + N' [min_used_grant_kb], ' + @LineFeed - + N' [max_used_grant_kb], ' + @LineFeed - + N' [last_ideal_grant_kb], ' + @LineFeed - + N' [min_ideal_grant_kb], ' + @LineFeed - + N' [max_ideal_grant_kb], ' + @LineFeed - + N' [last_reserved_threads], ' + @LineFeed - + N' [min_reserved_threads], ' + @LineFeed - + N' [max_reserved_threads], ' + @LineFeed - + N' [last_used_threads], ' + @LineFeed - + N' [min_used_threads], ' + @LineFeed - + N' [max_used_threads], ' + @LineFeed - + N' [grant_time], ' + @LineFeed - + N' [requested_memory_kb], ' + @LineFeed - + N' [grant_memory_kb], ' + @LineFeed - + N' [is_request_granted], ' + @LineFeed - + N' [required_memory_kb], ' + @LineFeed - + N' [query_memory_grant_used_memory_kb], ' + @LineFeed - + N' [ideal_memory_kb], ' + @LineFeed - + N' [is_small], ' + @LineFeed - + N' [timeout_sec], ' + @LineFeed - + N' [resource_semaphore_id], ' + @LineFeed - + N' [wait_order], ' + @LineFeed - + N' [wait_time_ms], ' + @LineFeed - + N' [next_candidate_for_memory_grant], ' + @LineFeed - + N' [target_memory_kb], ' + @LineFeed - + N' [max_target_memory_kb], ' + @LineFeed - + N' [total_memory_kb], ' + @LineFeed - + N' [available_memory_kb], ' + @LineFeed - + N' [granted_memory_kb], ' + @LineFeed - + N' [query_resource_semaphore_used_memory_kb], ' + @LineFeed - + N' [grantee_count], ' + @LineFeed - + N' [waiter_count], ' + @LineFeed - + N' [timeout_error_count], ' + @LineFeed - + N' [forced_grant_count], ' + @LineFeed - + N' [workload_group_name], ' + @LineFeed - + N' [resource_pool_name], ' + @LineFeed - + N' [context_info], ' + @LineFeed - + N' [query_hash], ' + @LineFeed - + N' [query_plan_hash], ' + @LineFeed - + N' [sql_handle], ' + @LineFeed - + N' [plan_handle], ' + @LineFeed - + N' [statement_start_offset], ' + @LineFeed - + N' [statement_end_offset] ' + @LineFeed - + N' FROM ' + @OutputSchemaName + '.' + @OutputTableName + '' + @LineFeed - + N' ) AS [BlitzWho] ' + @LineFeed - + N'INNER JOIN [MaxQueryDuration] ON [BlitzWho].[ID] = [MaxQueryDuration].[MaxID]; ' + @LineFeed - + N''');' - - IF @Debug = 1 - BEGIN - PRINT CONVERT(VARCHAR(8000), SUBSTRING(@StringToExecute, 0, 8000)) - PRINT CONVERT(VARCHAR(8000), SUBSTRING(@StringToExecute, 8000, 16000)) - END + + @OutputTableNameFileStats_View + ' AS ' + @LineFeed + + 'WITH RowDates as' + @LineFeed + + '(' + @LineFeed + + ' SELECT ' + @LineFeed + + ' ROW_NUMBER() OVER (ORDER BY [ServerName], [CheckDate]) ID,' + @LineFeed + + ' [CheckDate]' + @LineFeed + + ' FROM ' + @OutputSchemaName + '.' + @OutputTableNameFileStats + '' + @LineFeed + + ' GROUP BY [ServerName], [CheckDate]' + @LineFeed + + '),' + @LineFeed + + 'CheckDates as' + @LineFeed + + '(' + @LineFeed + + ' SELECT ThisDate.CheckDate,' + @LineFeed + + ' LastDate.CheckDate as PreviousCheckDate' + @LineFeed + + ' FROM RowDates ThisDate' + @LineFeed + + ' JOIN RowDates LastDate' + @LineFeed + + ' ON ThisDate.ID = LastDate.ID + 1' + @LineFeed + + ')' + @LineFeed + + ' SELECT f.ServerName,' + @LineFeed + + ' f.CheckDate,' + @LineFeed + + ' f.DatabaseID,' + @LineFeed + + ' f.DatabaseName,' + @LineFeed + + ' f.FileID,' + @LineFeed + + ' f.FileLogicalName,' + @LineFeed + + ' f.TypeDesc,' + @LineFeed + + ' f.PhysicalName,' + @LineFeed + + ' f.SizeOnDiskMB,' + @LineFeed + + ' DATEDIFF(ss, fPrior.CheckDate, f.CheckDate) AS ElapsedSeconds,' + @LineFeed + + ' (f.SizeOnDiskMB - fPrior.SizeOnDiskMB) AS SizeOnDiskMBgrowth,' + @LineFeed + + ' (f.io_stall_read_ms - fPrior.io_stall_read_ms) AS io_stall_read_ms,' + @LineFeed + + ' io_stall_read_ms_average = CASE' + @LineFeed + + ' WHEN(f.num_of_reads - fPrior.num_of_reads) = 0' + @LineFeed + + ' THEN 0' + @LineFeed + + ' ELSE(f.io_stall_read_ms - fPrior.io_stall_read_ms) / (f.num_of_reads - fPrior.num_of_reads)' + @LineFeed + + ' END,' + @LineFeed + + ' (f.num_of_reads - fPrior.num_of_reads) AS num_of_reads,' + @LineFeed + + ' (f.bytes_read - fPrior.bytes_read) / 1024.0 / 1024.0 AS megabytes_read,' + @LineFeed + + ' (f.io_stall_write_ms - fPrior.io_stall_write_ms) AS io_stall_write_ms,' + @LineFeed + + ' io_stall_write_ms_average = CASE' + @LineFeed + + ' WHEN(f.num_of_writes - fPrior.num_of_writes) = 0' + @LineFeed + + ' THEN 0' + @LineFeed + + ' ELSE(f.io_stall_write_ms - fPrior.io_stall_write_ms) / (f.num_of_writes - fPrior.num_of_writes)' + @LineFeed + + ' END,' + @LineFeed + + ' (f.num_of_writes - fPrior.num_of_writes) AS num_of_writes,' + @LineFeed + + ' (f.bytes_written - fPrior.bytes_written) / 1024.0 / 1024.0 AS megabytes_written, ' + @LineFeed + + ' f.ServerName + CAST(f.CheckDate AS NVARCHAR(50)) AS JoinKey' + @LineFeed + + ' FROM ' + @OutputSchemaName + '.' + @OutputTableNameFileStats + ' f' + @LineFeed + + ' INNER HASH JOIN CheckDates DATES ON f.CheckDate = DATES.CheckDate' + @LineFeed + + ' INNER JOIN ' + @OutputSchemaName + '.' + @OutputTableNameFileStats + ' fPrior ON f.ServerName = fPrior.ServerName' + @LineFeed + + ' AND f.DatabaseID = fPrior.DatabaseID' + @LineFeed + + ' AND f.FileID = fPrior.FileID' + @LineFeed + + ' AND fPrior.CheckDate = DATES.PreviousCheckDate' + @LineFeed + + '' + @LineFeed + + ' WHERE f.num_of_reads >= fPrior.num_of_reads' + @LineFeed + + ' AND f.num_of_writes >= fPrior.num_of_writes' + @LineFeed + + ' AND DATEDIFF(MI, fPrior.CheckDate, f.CheckDate) BETWEEN 1 AND 60;'')' - EXEC(@StringToExecute); + EXEC(@StringToExecute); END; - END - IF OBJECT_ID('tempdb..#WhoReadableDBs') IS NOT NULL - DROP TABLE #WhoReadableDBs; + SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' + + @OutputDatabaseName + + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' + + @OutputSchemaName + ''') INSERT ' + + @OutputDatabaseName + '.' + + @OutputSchemaName + '.' + + @OutputTableNameFileStats + + ' (ServerName, CheckDate, DatabaseID, FileID, DatabaseName, FileLogicalName, TypeDesc, SizeOnDiskMB, io_stall_read_ms, num_of_reads, bytes_read, io_stall_write_ms, num_of_writes, bytes_written, PhysicalName) SELECT ' + + ' @SrvName, @CheckDate, DatabaseID, FileID, DatabaseName, FileLogicalName, TypeDesc, SizeOnDiskMB, io_stall_read_ms, num_of_reads, bytes_read, io_stall_write_ms, num_of_writes, bytes_written, PhysicalName FROM #FileStats WHERE Pass = 2'; -CREATE TABLE #WhoReadableDBs -( -database_id INT -); + EXEC sp_executesql @StringToExecute, + N'@SrvName NVARCHAR(128), @CheckDate datetimeoffset', + @LocalServerName, @StartSampleTime; -IF EXISTS (SELECT * FROM sys.all_objects o WHERE o.name = 'dm_hadr_database_replica_states') -BEGIN - RAISERROR('Checking for Read intent databases to exclude',0,0) WITH NOWAIT; + /* Delete history older than @OutputTableRetentionDays */ + SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' + + @OutputDatabaseName + + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' + + @OutputSchemaName + ''') DELETE ' + + @OutputDatabaseName + '.' + + @OutputSchemaName + '.' + + @OutputTableNameFileStats + + ' WHERE ServerName = @SrvName AND CheckDate < @CheckDate ;'; - EXEC('INSERT INTO #WhoReadableDBs (database_id) SELECT DBs.database_id FROM sys.databases DBs INNER JOIN sys.availability_replicas Replicas ON DBs.replica_id = Replicas.replica_id WHERE replica_server_name NOT IN (SELECT DISTINCT primary_replica FROM sys.dm_hadr_availability_group_states States) AND Replicas.secondary_role_allow_connections_desc = ''READ_ONLY'' AND replica_server_name = @@SERVERNAME;'); -END + EXEC sp_executesql @StringToExecute, + N'@SrvName NVARCHAR(128), @CheckDate date', + @LocalServerName, @OutputTableCleanupDate; -SELECT @BlockingCheck = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - - DECLARE @blocked TABLE - ( - dbid SMALLINT NOT NULL, - last_batch DATETIME NOT NULL, - open_tran SMALLINT NOT NULL, - sql_handle BINARY(20) NOT NULL, - session_id SMALLINT NOT NULL, - blocking_session_id SMALLINT NOT NULL, - lastwaittype NCHAR(32) NOT NULL, - waittime BIGINT NOT NULL, - cpu INT NOT NULL, - physical_io BIGINT NOT NULL, - memusage INT NOT NULL - ); - - INSERT @blocked ( dbid, last_batch, open_tran, sql_handle, session_id, blocking_session_id, lastwaittype, waittime, cpu, physical_io, memusage ) - SELECT - sys1.dbid, sys1.last_batch, sys1.open_tran, sys1.sql_handle, - sys2.spid AS session_id, sys2.blocked AS blocking_session_id, sys2.lastwaittype, sys2.waittime, sys2.cpu, sys2.physical_io, sys2.memusage - FROM sys.sysprocesses AS sys1 - JOIN sys.sysprocesses AS sys2 - ON sys1.spid = sys2.blocked;'; + END; + ELSE IF (SUBSTRING(@OutputTableNameFileStats, 2, 2) = '##') + BEGIN + SET @StringToExecute = N' IF (OBJECT_ID(''tempdb..' + + @OutputTableNameFileStats + + ''') IS NULL) CREATE TABLE ' + + @OutputTableNameFileStats + + ' (ID INT IDENTITY(1,1) NOT NULL, + ServerName NVARCHAR(128), + CheckDate DATETIMEOFFSET, + DatabaseID INT NOT NULL, + FileID INT NOT NULL, + DatabaseName NVARCHAR(256) , + FileLogicalName NVARCHAR(256) , + TypeDesc NVARCHAR(60) , + SizeOnDiskMB BIGINT , + io_stall_read_ms BIGINT , + num_of_reads BIGINT , + bytes_read BIGINT , + io_stall_write_ms BIGINT , + num_of_writes BIGINT , + bytes_written BIGINT, + PhysicalName NVARCHAR(520) , + DetailsInt INT NULL, + PRIMARY KEY CLUSTERED (ID ASC));' + + ' INSERT ' + + @OutputTableNameFileStats + + ' (ServerName, CheckDate, DatabaseID, FileID, DatabaseName, FileLogicalName, TypeDesc, SizeOnDiskMB, io_stall_read_ms, num_of_reads, bytes_read, io_stall_write_ms, num_of_writes, bytes_written, PhysicalName) SELECT ' + + ' @SrvName, @CheckDate, DatabaseID, FileID, DatabaseName, FileLogicalName, TypeDesc, SizeOnDiskMB, io_stall_read_ms, num_of_reads, bytes_read, io_stall_write_ms, num_of_writes, bytes_written, PhysicalName FROM #FileStats WHERE Pass = 2'; -IF @ProductVersionMajor > 9 and @ProductVersionMajor < 11 -BEGIN - /* Think of the StringToExecute as starting with this, but we'll set this up later depending on whether we're doing an insert or a select: - SELECT @StringToExecute = N'SELECT GETDATE() AS run_date , - */ - SET @StringToExecute = N'COALESCE( RIGHT(''00'' + CONVERT(VARCHAR(20), (ABS(r.total_elapsed_time) / 1000) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), (DATEADD(SECOND, (r.total_elapsed_time / 1000), 0) + DATEADD(MILLISECOND, (r.total_elapsed_time % 1000), 0)), 114), RIGHT(''00'' + CONVERT(VARCHAR(20), DATEDIFF(SECOND, s.last_request_start_time, GETDATE()) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), DATEADD(SECOND, DATEDIFF(SECOND, s.last_request_start_time, GETDATE()), 0), 114) ) AS [elapsed_time] , - s.session_id , - COALESCE(DB_NAME(r.database_id), DB_NAME(blocked.dbid), ''N/A'') AS database_name, - ISNULL(SUBSTRING(dest.text, - ( query_stats.statement_start_offset / 2 ) + 1, - ( ( CASE query_stats.statement_end_offset - WHEN -1 THEN DATALENGTH(dest.text) - ELSE query_stats.statement_end_offset - END - query_stats.statement_start_offset ) - / 2 ) + 1), dest.text) AS query_text , - derp.query_plan , - qmg.query_cost , - s.status , - CASE - WHEN s.status <> ''sleeping'' THEN COALESCE(wt.wait_info, RTRIM(blocked.lastwaittype) + '' ('' + CONVERT(VARCHAR(10), blocked.waittime) + '')'' ) - ELSE NULL - END AS wait_info , - CASE WHEN r.blocking_session_id <> 0 AND blocked.session_id IS NULL - THEN r.blocking_session_id - WHEN r.blocking_session_id <> 0 AND s.session_id <> blocked.blocking_session_id - THEN blocked.blocking_session_id - WHEN r.blocking_session_id = 0 AND s.session_id = blocked.session_id - THEN blocked.blocking_session_id - WHEN r.blocking_session_id <> 0 AND s.session_id = blocked.blocking_session_id - THEN r.blocking_session_id - ELSE NULL - END AS blocking_session_id, - COALESCE(r.open_transaction_count, blocked.open_tran) AS open_transaction_count , - CASE WHEN EXISTS ( SELECT 1 - FROM sys.dm_tran_active_transactions AS tat - JOIN sys.dm_tran_session_transactions AS tst - ON tst.transaction_id = tat.transaction_id - WHERE tat.name = ''implicit_transaction'' - AND s.session_id = tst.session_id - ) THEN 1 - ELSE 0 - END AS is_implicit_transaction , - s.nt_domain , - s.host_name , - s.login_name , - s.nt_user_name ,' - IF @Platform = 'NonAzure' - BEGIN - SET @StringToExecute += - N'program_name = COALESCE(( - SELECT REPLACE(program_name,Substring(program_name,30,34),''"''+j.name+''"'') - FROM msdb.dbo.sysjobs j WHERE Substring(program_name,32,32) = CONVERT(char(32),CAST(j.job_id AS binary(16)),2) - ),s.program_name)' - END - ELSE - BEGIN - SET @StringToExecute += N's.program_name' - END - - IF @ExpertMode = 1 + EXEC sp_executesql @StringToExecute, + N'@SrvName NVARCHAR(128), @CheckDate datetimeoffset', + @LocalServerName, @StartSampleTime; + END; + ELSE IF (SUBSTRING(@OutputTableNameFileStats, 2, 1) = '#') BEGIN - SET @StringToExecute += - N', - ''DBCC FREEPROCCACHE ('' + CONVERT(NVARCHAR(128), r.plan_handle, 1) + '');'' AS fix_parameter_sniffing, - s.client_interface_name , - s.login_time , - r.start_time , - qmg.request_time , - COALESCE(r.cpu_time, s.cpu_time) AS request_cpu_time, - COALESCE(r.logical_reads, s.logical_reads) AS request_logical_reads, - COALESCE(r.writes, s.writes) AS request_writes, - COALESCE(r.reads, s.reads) AS request_physical_reads , - s.cpu_time AS session_cpu, - s.logical_reads AS session_logical_reads, - s.reads AS session_physical_reads , - s.writes AS session_writes, - tempdb_allocations.tempdb_allocations_mb, - s.memory_usage , - r.estimated_completion_time , - r.percent_complete , - r.deadlock_priority , - CASE - WHEN s.transaction_isolation_level = 0 THEN ''Unspecified'' - WHEN s.transaction_isolation_level = 1 THEN ''Read Uncommitted'' - WHEN s.transaction_isolation_level = 2 AND EXISTS (SELECT 1 FROM sys.databases WHERE name = DB_NAME(r.database_id) AND is_read_committed_snapshot_on = 1) THEN ''Read Committed Snapshot Isolation'' - WHEN s.transaction_isolation_level = 2 THEN ''Read Committed'' - WHEN s.transaction_isolation_level = 3 THEN ''Repeatable Read'' - WHEN s.transaction_isolation_level = 4 THEN ''Serializable'' - WHEN s.transaction_isolation_level = 5 THEN ''Snapshot'' - ELSE ''WHAT HAVE YOU DONE?'' - END AS transaction_isolation_level , - qmg.dop AS degree_of_parallelism , - COALESCE(CAST(qmg.grant_time AS VARCHAR(20)), ''N/A'') AS grant_time , - qmg.requested_memory_kb , - qmg.granted_memory_kb AS grant_memory_kb, - CASE WHEN qmg.grant_time IS NULL THEN ''N/A'' - WHEN qmg.requested_memory_kb < qmg.granted_memory_kb - THEN ''Query Granted Less Than Query Requested'' - ELSE ''Memory Request Granted'' - END AS is_request_granted , - qmg.required_memory_kb , - qmg.used_memory_kb AS query_memory_grant_used_memory_kb, - qmg.ideal_memory_kb , - qmg.is_small , - qmg.timeout_sec , - qmg.resource_semaphore_id , - COALESCE(CAST(qmg.wait_order AS VARCHAR(20)), ''N/A'') AS wait_order , - COALESCE(CAST(qmg.wait_time_ms AS VARCHAR(20)), - ''N/A'') AS wait_time_ms , - CASE qmg.is_next_candidate - WHEN 0 THEN ''No'' - WHEN 1 THEN ''Yes'' - ELSE ''N/A'' - END AS next_candidate_for_memory_grant , - qrs.target_memory_kb , - COALESCE(CAST(qrs.max_target_memory_kb AS VARCHAR(20)), - ''Small Query Resource Semaphore'') AS max_target_memory_kb , - qrs.total_memory_kb , - qrs.available_memory_kb , - qrs.granted_memory_kb , - qrs.used_memory_kb AS query_resource_semaphore_used_memory_kb, - qrs.grantee_count , - qrs.waiter_count , - qrs.timeout_error_count , - COALESCE(CAST(qrs.forced_grant_count AS VARCHAR(20)), - ''Small Query Resource Semaphore'') AS forced_grant_count, - wg.name AS workload_group_name , - rp.name AS resource_pool_name, - CONVERT(VARCHAR(128), r.context_info) AS context_info - ' - END /* IF @ExpertMode = 1 */ - - SET @StringToExecute += - N'FROM sys.dm_exec_sessions AS s - LEFT JOIN sys.dm_exec_requests AS r - ON r.session_id = s.session_id - LEFT JOIN ( SELECT DISTINCT - wait.session_id , - ( SELECT waitwait.wait_type + N'' ('' - + CAST(MAX(waitwait.wait_duration_ms) AS NVARCHAR(128)) - + N'' ms) '' - FROM sys.dm_os_waiting_tasks AS waitwait - WHERE waitwait.session_id = wait.session_id - GROUP BY waitwait.wait_type - ORDER BY SUM(waitwait.wait_duration_ms) DESC - FOR - XML PATH('''') ) AS wait_info - FROM sys.dm_os_waiting_tasks AS wait ) AS wt - ON s.session_id = wt.session_id - LEFT JOIN sys.dm_exec_query_stats AS query_stats - ON r.sql_handle = query_stats.sql_handle - AND r.plan_handle = query_stats.plan_handle - AND r.statement_start_offset = query_stats.statement_start_offset - AND r.statement_end_offset = query_stats.statement_end_offset - LEFT JOIN sys.dm_exec_query_memory_grants qmg - ON r.session_id = qmg.session_id - AND r.request_id = qmg.request_id - LEFT JOIN sys.dm_exec_query_resource_semaphores qrs - ON qmg.resource_semaphore_id = qrs.resource_semaphore_id - AND qmg.pool_id = qrs.pool_id - LEFT JOIN sys.resource_governor_workload_groups wg - ON s.group_id = wg.group_id - LEFT JOIN sys.resource_governor_resource_pools rp - ON wg.pool_id = rp.pool_id - OUTER APPLY ( - SELECT TOP 1 - b.dbid, b.last_batch, b.open_tran, b.sql_handle, - b.session_id, b.blocking_session_id, b.lastwaittype, b.waittime - FROM @blocked b - WHERE (s.session_id = b.session_id - OR s.session_id = b.blocking_session_id) - ) AS blocked - OUTER APPLY sys.dm_exec_sql_text(COALESCE(r.sql_handle, blocked.sql_handle)) AS dest - OUTER APPLY sys.dm_exec_query_plan(r.plan_handle) AS derp - OUTER APPLY ( - SELECT CONVERT(DECIMAL(38,2), SUM( (((tsu.user_objects_alloc_page_count - user_objects_dealloc_page_count) * 8) / 1024.)) ) AS tempdb_allocations_mb - FROM sys.dm_db_task_space_usage tsu - WHERE tsu.request_id = r.request_id - AND tsu.session_id = r.session_id - AND tsu.session_id = s.session_id - ) as tempdb_allocations - WHERE s.session_id <> @@SPID - AND s.host_name IS NOT NULL - ' - + CASE WHEN @ShowSleepingSPIDs = 0 THEN - N' AND COALESCE(DB_NAME(r.database_id), DB_NAME(blocked.dbid)) IS NOT NULL' - WHEN @ShowSleepingSPIDs = 1 THEN - N' OR COALESCE(r.open_transaction_count, blocked.open_tran) >= 1' - ELSE N'' END; -END /* IF @ProductVersionMajor > 9 and @ProductVersionMajor < 11 */ + RAISERROR('Due to the nature of Dymamic SQL, only global (i.e. double pound (##)) temp tables are supported for @OutputTableName', 16, 0); + END; + + + /* @OutputTableNamePerfmonStats lets us export the results to a permanent table */ + IF @OutputDatabaseName IS NOT NULL + AND @OutputSchemaName IS NOT NULL + AND @OutputTableNamePerfmonStats IS NOT NULL + AND @OutputTableNamePerfmonStats NOT LIKE '#%' + AND EXISTS ( SELECT * + FROM sys.databases + WHERE QUOTENAME([name]) = @OutputDatabaseName) + BEGIN + /* Create the table */ + SET @StringToExecute = 'USE ' + + @OutputDatabaseName + + '; IF EXISTS(SELECT * FROM ' + + @OutputDatabaseName + + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' + + @OutputSchemaName + + ''') AND NOT EXISTS (SELECT * FROM ' + + @OutputDatabaseName + + '.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''' + + @OutputSchemaName + ''' AND QUOTENAME(TABLE_NAME) = ''' + + @OutputTableNamePerfmonStats + ''') CREATE TABLE ' + + @OutputSchemaName + '.' + + @OutputTableNamePerfmonStats + + ' (ID INT IDENTITY(1,1) NOT NULL, + ServerName NVARCHAR(128), + CheckDate DATETIMEOFFSET, + [object_name] NVARCHAR(128) NOT NULL, + [counter_name] NVARCHAR(128) NOT NULL, + [instance_name] NVARCHAR(128) NULL, + [cntr_value] BIGINT NULL, + [cntr_type] INT NOT NULL, + [value_delta] BIGINT NULL, + [value_per_second] DECIMAL(18,2) NULL, + PRIMARY KEY CLUSTERED (ID ASC));'; + + EXEC(@StringToExecute); + + SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableNamePerfmonStats_View; + + /* If the view exists without the most recently added columns, drop it. See Github #2162. */ + IF OBJECT_ID(@ObjectFullName) IS NOT NULL + BEGIN + SET @StringToExecute = N'USE ' + @OutputDatabaseName + N'; IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns + WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''JoinKey'') + DROP VIEW ' + @OutputSchemaName + N'.' + @OutputTableNamePerfmonStats_View + N';'; + + EXEC(@StringToExecute); + END + + /* Create the view */ + IF OBJECT_ID(@ObjectFullName) IS NULL + BEGIN + SET @StringToExecute = 'USE ' + + @OutputDatabaseName + + '; EXEC (''CREATE VIEW ' + + @OutputSchemaName + '.' + + @OutputTableNamePerfmonStats_View + ' AS ' + @LineFeed + + 'WITH RowDates as' + @LineFeed + + '(' + @LineFeed + + ' SELECT ' + @LineFeed + + ' ROW_NUMBER() OVER (ORDER BY [ServerName], [CheckDate]) ID,' + @LineFeed + + ' [CheckDate]' + @LineFeed + + ' FROM ' + @OutputSchemaName + '.' +@OutputTableNamePerfmonStats + '' + @LineFeed + + ' GROUP BY [ServerName], [CheckDate]' + @LineFeed + + '),' + @LineFeed + + 'CheckDates as' + @LineFeed + + '(' + @LineFeed + + ' SELECT ThisDate.CheckDate,' + @LineFeed + + ' LastDate.CheckDate as PreviousCheckDate' + @LineFeed + + ' FROM RowDates ThisDate' + @LineFeed + + ' JOIN RowDates LastDate' + @LineFeed + + ' ON ThisDate.ID = LastDate.ID + 1' + @LineFeed + + ')' + @LineFeed + + 'SELECT' + @LineFeed + + ' pMon.[ServerName]' + @LineFeed + + ' ,pMon.[CheckDate]' + @LineFeed + + ' ,pMon.[object_name]' + @LineFeed + + ' ,pMon.[counter_name]' + @LineFeed + + ' ,pMon.[instance_name]' + @LineFeed + + ' ,DATEDIFF(SECOND,pMonPrior.[CheckDate],pMon.[CheckDate]) AS ElapsedSeconds' + @LineFeed + + ' ,pMon.[cntr_value]' + @LineFeed + + ' ,pMon.[cntr_type]' + @LineFeed + + ' ,(pMon.[cntr_value] - pMonPrior.[cntr_value]) AS cntr_delta' + @LineFeed + + ' ,(pMon.cntr_value - pMonPrior.cntr_value) * 1.0 / DATEDIFF(ss, pMonPrior.CheckDate, pMon.CheckDate) AS cntr_delta_per_second' + @LineFeed + + ' ,pMon.ServerName + CAST(pMon.CheckDate AS NVARCHAR(50)) AS JoinKey' + @LineFeed + + ' FROM ' + @OutputSchemaName + '.' +@OutputTableNamePerfmonStats + ' pMon' + @LineFeed + + ' INNER HASH JOIN CheckDates Dates' + @LineFeed + + ' ON Dates.CheckDate = pMon.CheckDate' + @LineFeed + + ' JOIN ' + @OutputSchemaName + '.' +@OutputTableNamePerfmonStats + ' pMonPrior' + @LineFeed + + ' ON Dates.PreviousCheckDate = pMonPrior.CheckDate' + @LineFeed + + ' AND pMon.[ServerName] = pMonPrior.[ServerName] ' + @LineFeed + + ' AND pMon.[object_name] = pMonPrior.[object_name] ' + @LineFeed + + ' AND pMon.[counter_name] = pMonPrior.[counter_name] ' + @LineFeed + + ' AND pMon.[instance_name] = pMonPrior.[instance_name]' + @LineFeed + + ' WHERE DATEDIFF(MI, pMonPrior.CheckDate, pMon.CheckDate) BETWEEN 1 AND 60;'')' + + EXEC(@StringToExecute); + END + + SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableNamePerfmonStatsActuals_View; + + /* If the view exists without the most recently added columns, drop it. See Github #2162. */ + IF OBJECT_ID(@ObjectFullName) IS NOT NULL + BEGIN + SET @StringToExecute = N'USE ' + @OutputDatabaseName + N'; IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns + WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''JoinKey'') + DROP VIEW ' + @OutputSchemaName + N'.' + @OutputTableNamePerfmonStatsActuals_View + N';'; + + EXEC(@StringToExecute); + END + + /* Create the second view */ + IF OBJECT_ID(@ObjectFullName) IS NULL + BEGIN + SET @StringToExecute = 'USE ' + + @OutputDatabaseName + + '; EXEC (''CREATE VIEW ' + + @OutputSchemaName + '.' + + @OutputTableNamePerfmonStatsActuals_View + ' AS ' + @LineFeed + + 'WITH PERF_AVERAGE_BULK AS' + @LineFeed + + '(' + @LineFeed + + ' SELECT ServerName,' + @LineFeed + + ' object_name,' + @LineFeed + + ' instance_name,' + @LineFeed + + ' counter_name,' + @LineFeed + + ' CASE WHEN CHARINDEX(''''('''', counter_name) = 0 THEN counter_name ELSE LEFT (counter_name, CHARINDEX(''''('''',counter_name)-1) END AS counter_join,' + @LineFeed + + ' CheckDate,' + @LineFeed + + ' cntr_delta' + @LineFeed + + ' FROM ' + @OutputSchemaName + '.' + @OutputTableNamePerfmonStats_View + @LineFeed + + ' WHERE cntr_type IN(1073874176)' + @LineFeed + + ' AND cntr_delta <> 0' + @LineFeed + + '),' + @LineFeed + + 'PERF_LARGE_RAW_BASE AS' + @LineFeed + + '(' + @LineFeed + + ' SELECT ServerName,' + @LineFeed + + ' object_name,' + @LineFeed + + ' instance_name,' + @LineFeed + + ' LEFT(counter_name, CHARINDEX(''''BASE'''', UPPER(counter_name))-1) AS counter_join,' + @LineFeed + + ' CheckDate,' + @LineFeed + + ' cntr_delta' + @LineFeed + + ' FROM ' + @OutputSchemaName + '.' + @OutputTableNamePerfmonStats_View + '' + @LineFeed + + ' WHERE cntr_type IN(1073939712)' + @LineFeed + + ' AND cntr_delta <> 0' + @LineFeed + + '),' + @LineFeed + + 'PERF_AVERAGE_FRACTION AS' + @LineFeed + + '(' + @LineFeed + + ' SELECT ServerName,' + @LineFeed + + ' object_name,' + @LineFeed + + ' instance_name,' + @LineFeed + + ' counter_name,' + @LineFeed + + ' counter_name AS counter_join,' + @LineFeed + + ' CheckDate,' + @LineFeed + + ' cntr_delta' + @LineFeed + + ' FROM ' + @OutputSchemaName + '.' + @OutputTableNamePerfmonStats_View + '' + @LineFeed + + ' WHERE cntr_type IN(537003264)' + @LineFeed + + ' AND cntr_delta <> 0' + @LineFeed + + '),' + @LineFeed + + 'PERF_COUNTER_BULK_COUNT AS' + @LineFeed + + '(' + @LineFeed + + ' SELECT ServerName,' + @LineFeed + + ' object_name,' + @LineFeed + + ' instance_name,' + @LineFeed + + ' counter_name,' + @LineFeed + + ' CheckDate,' + @LineFeed + + ' cntr_delta / ElapsedSeconds AS cntr_value' + @LineFeed + + ' FROM ' + @OutputSchemaName + '.' + @OutputTableNamePerfmonStats_View + '' + @LineFeed + + ' WHERE cntr_type IN(272696576, 272696320)' + @LineFeed + + ' AND cntr_delta <> 0' + @LineFeed + + '),' + @LineFeed + + 'PERF_COUNTER_RAWCOUNT AS' + @LineFeed + + '(' + @LineFeed + + ' SELECT ServerName,' + @LineFeed + + ' object_name,' + @LineFeed + + ' instance_name,' + @LineFeed + + ' counter_name,' + @LineFeed + + ' CheckDate,' + @LineFeed + + ' cntr_value' + @LineFeed + + ' FROM ' + @OutputSchemaName + '.' + @OutputTableNamePerfmonStats_View + '' + @LineFeed + + ' WHERE cntr_type IN(65792, 65536)' + @LineFeed + + ')' + @LineFeed + + '' + @LineFeed + + 'SELECT NUM.ServerName,' + @LineFeed + + ' NUM.object_name,' + @LineFeed + + ' NUM.counter_name,' + @LineFeed + + ' NUM.instance_name,' + @LineFeed + + ' NUM.CheckDate,' + @LineFeed + + ' NUM.cntr_delta / DEN.cntr_delta AS cntr_value,' + @LineFeed + + ' NUM.ServerName + CAST(NUM.CheckDate AS NVARCHAR(50)) AS JoinKey' + @LineFeed + + ' ' + @LineFeed + + 'FROM PERF_AVERAGE_BULK AS NUM' + @LineFeed + + ' JOIN PERF_LARGE_RAW_BASE AS DEN ON NUM.counter_join = DEN.counter_join' + @LineFeed + + ' AND NUM.CheckDate = DEN.CheckDate' + @LineFeed + + ' AND NUM.ServerName = DEN.ServerName' + @LineFeed + + ' AND NUM.object_name = DEN.object_name' + @LineFeed + + ' AND NUM.instance_name = DEN.instance_name' + @LineFeed + + ' AND DEN.cntr_delta <> 0' + @LineFeed + + '' + @LineFeed + + 'UNION ALL' + @LineFeed + + '' + @LineFeed + + 'SELECT NUM.ServerName,' + @LineFeed + + ' NUM.object_name,' + @LineFeed + + ' NUM.counter_name,' + @LineFeed + + ' NUM.instance_name,' + @LineFeed + + ' NUM.CheckDate,' + @LineFeed + + ' CAST((CAST(NUM.cntr_delta as DECIMAL(19)) / DEN.cntr_delta) as decimal(23,3)) AS cntr_value,' + @LineFeed + + ' NUM.ServerName + CAST(NUM.CheckDate AS NVARCHAR(50)) AS JoinKey' + @LineFeed + + 'FROM PERF_AVERAGE_FRACTION AS NUM' + @LineFeed + + ' JOIN PERF_LARGE_RAW_BASE AS DEN ON NUM.counter_join = DEN.counter_join' + @LineFeed + + ' AND NUM.CheckDate = DEN.CheckDate' + @LineFeed + + ' AND NUM.ServerName = DEN.ServerName' + @LineFeed + + ' AND NUM.object_name = DEN.object_name' + @LineFeed + + ' AND NUM.instance_name = DEN.instance_name' + @LineFeed + + ' AND DEN.cntr_delta <> 0' + @LineFeed + + 'UNION ALL' + @LineFeed + + '' + @LineFeed + + 'SELECT ServerName,' + @LineFeed + + ' object_name,' + @LineFeed + + ' counter_name,' + @LineFeed + + ' instance_name,' + @LineFeed + + ' CheckDate,' + @LineFeed + + ' cntr_value,' + @LineFeed + + ' ServerName + CAST(CheckDate AS NVARCHAR(50)) AS JoinKey' + @LineFeed + + 'FROM PERF_COUNTER_BULK_COUNT' + @LineFeed + + '' + @LineFeed + + 'UNION ALL' + @LineFeed + + '' + @LineFeed + + 'SELECT ServerName,' + @LineFeed + + ' object_name,' + @LineFeed + + ' counter_name,' + @LineFeed + + ' instance_name,' + @LineFeed + + ' CheckDate,' + @LineFeed + + ' cntr_value,' + @LineFeed + + ' ServerName + CAST(CheckDate AS NVARCHAR(50)) AS JoinKey' + @LineFeed + + 'FROM PERF_COUNTER_RAWCOUNT;'')'; -IF @ProductVersionMajor >= 11 - BEGIN - SELECT @EnhanceFlag = - CASE WHEN @ProductVersionMajor = 11 AND @ProductVersionMinor >= 6020 THEN 1 - WHEN @ProductVersionMajor = 12 AND @ProductVersionMinor >= 5000 THEN 1 - WHEN @ProductVersionMajor = 13 AND @ProductVersionMinor >= 1601 THEN 1 - WHEN @ProductVersionMajor > 13 THEN 1 - ELSE 0 - END + EXEC(@StringToExecute); + END; - IF OBJECT_ID('sys.dm_exec_session_wait_stats') IS NOT NULL + SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' + + @OutputDatabaseName + + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' + + @OutputSchemaName + ''') INSERT ' + + @OutputDatabaseName + '.' + + @OutputSchemaName + '.' + + @OutputTableNamePerfmonStats + + ' (ServerName, CheckDate, object_name, counter_name, instance_name, cntr_value, cntr_type, value_delta, value_per_second) SELECT ' + + ' @SrvName, @CheckDate, object_name, counter_name, instance_name, cntr_value, cntr_type, value_delta, value_per_second FROM #PerfmonStats WHERE Pass = 2'; + + EXEC sp_executesql @StringToExecute, + N'@SrvName NVARCHAR(128), @CheckDate datetimeoffset', + @LocalServerName, @StartSampleTime; + + /* Delete history older than @OutputTableRetentionDays */ + SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' + + @OutputDatabaseName + + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' + + @OutputSchemaName + ''') DELETE ' + + @OutputDatabaseName + '.' + + @OutputSchemaName + '.' + + @OutputTableNamePerfmonStats + + ' WHERE ServerName = @SrvName AND CheckDate < @CheckDate ;'; + + EXEC sp_executesql @StringToExecute, + N'@SrvName NVARCHAR(128), @CheckDate date', + @LocalServerName, @OutputTableCleanupDate; + + + + END; + ELSE IF (SUBSTRING(@OutputTableNamePerfmonStats, 2, 2) = '##') BEGIN - SET @SessionWaits = 1 - END + SET @StringToExecute = N' IF (OBJECT_ID(''tempdb..' + + @OutputTableNamePerfmonStats + + ''') IS NULL) CREATE TABLE ' + + @OutputTableNamePerfmonStats + + ' (ID INT IDENTITY(1,1) NOT NULL, + ServerName NVARCHAR(128), + CheckDate DATETIMEOFFSET, + [object_name] NVARCHAR(128) NOT NULL, + [counter_name] NVARCHAR(128) NOT NULL, + [instance_name] NVARCHAR(128) NULL, + [cntr_value] BIGINT NULL, + [cntr_type] INT NOT NULL, + [value_delta] BIGINT NULL, + [value_per_second] DECIMAL(18,2) NULL, + PRIMARY KEY CLUSTERED (ID ASC));' + + ' INSERT ' + + @OutputTableNamePerfmonStats + + ' (ServerName, CheckDate, object_name, counter_name, instance_name, cntr_value, cntr_type, value_delta, value_per_second) SELECT ' + + CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128)) + + ' @SrvName, @CheckDate, object_name, counter_name, instance_name, cntr_value, cntr_type, value_delta, value_per_second FROM #PerfmonStats WHERE Pass = 2'; - /* Think of the StringToExecute as starting with this, but we'll set this up later depending on whether we're doing an insert or a select: - SELECT @StringToExecute = N'SELECT GETDATE() AS run_date , - */ - SELECT @StringToExecute = N'COALESCE( RIGHT(''00'' + CONVERT(VARCHAR(20), (ABS(r.total_elapsed_time) / 1000) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), (DATEADD(SECOND, (r.total_elapsed_time / 1000), 0) + DATEADD(MILLISECOND, (r.total_elapsed_time % 1000), 0)), 114), RIGHT(''00'' + CONVERT(VARCHAR(20), DATEDIFF(SECOND, s.last_request_start_time, GETDATE()) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), DATEADD(SECOND, DATEDIFF(SECOND, s.last_request_start_time, GETDATE()), 0), 114) ) AS [elapsed_time] , - s.session_id , - COALESCE(DB_NAME(r.database_id), DB_NAME(blocked.dbid), ''N/A'') AS database_name, - ISNULL(SUBSTRING(dest.text, - ( query_stats.statement_start_offset / 2 ) + 1, - ( ( CASE query_stats.statement_end_offset - WHEN -1 THEN DATALENGTH(dest.text) - ELSE query_stats.statement_end_offset - END - query_stats.statement_start_offset ) - / 2 ) + 1), dest.text) AS query_text , - derp.query_plan ,' - + @QueryStatsXMLselect - +' - qmg.query_cost , - s.status , - CASE - WHEN s.status <> ''sleeping'' THEN COALESCE(wt.wait_info, RTRIM(blocked.lastwaittype) + '' ('' + CONVERT(VARCHAR(10), blocked.waittime) + '')'' ) - ELSE NULL - END AS wait_info ,' - + - CASE @SessionWaits - WHEN 1 THEN + N'SUBSTRING(wt2.session_wait_info, 0, LEN(wt2.session_wait_info) ) AS top_session_waits ,' - ELSE N' NULL AS top_session_waits ,' - END - + - N'CASE WHEN r.blocking_session_id <> 0 AND blocked.session_id IS NULL - THEN r.blocking_session_id - WHEN r.blocking_session_id <> 0 AND s.session_id <> blocked.blocking_session_id - THEN blocked.blocking_session_id - WHEN r.blocking_session_id = 0 AND s.session_id = blocked.session_id - THEN blocked.blocking_session_id - WHEN r.blocking_session_id <> 0 AND s.session_id = blocked.blocking_session_id - THEN r.blocking_session_id - ELSE NULL - END AS blocking_session_id, - COALESCE(r.open_transaction_count, blocked.open_tran) AS open_transaction_count , - CASE WHEN EXISTS ( SELECT 1 - FROM sys.dm_tran_active_transactions AS tat - JOIN sys.dm_tran_session_transactions AS tst - ON tst.transaction_id = tat.transaction_id - WHERE tat.name = ''implicit_transaction'' - AND s.session_id = tst.session_id - ) THEN 1 - ELSE 0 - END AS is_implicit_transaction , - s.nt_domain , - s.host_name , - s.login_name , - s.nt_user_name ,' - IF @Platform = 'NonAzure' - BEGIN - SET @StringToExecute += - N'program_name = COALESCE(( - SELECT REPLACE(program_name,Substring(program_name,30,34),''"''+j.name+''"'') - FROM msdb.dbo.sysjobs j WHERE Substring(program_name,32,32) = CONVERT(char(32),CAST(j.job_id AS binary(16)),2) - ),s.program_name)' - END - ELSE - BEGIN - SET @StringToExecute += N's.program_name' - END + EXEC sp_executesql @StringToExecute, + N'@SrvName NVARCHAR(128), @CheckDate datetimeoffset', + @LocalServerName, @StartSampleTime; + END; + ELSE IF (SUBSTRING(@OutputTableNamePerfmonStats, 2, 1) = '#') + BEGIN + RAISERROR('Due to the nature of Dymamic SQL, only global (i.e. double pound (##)) temp tables are supported for @OutputTableName', 16, 0); + END; - IF @ExpertMode = 1 /* We show more columns in expert mode, so the SELECT gets longer */ + + /* @OutputTableNameWaitStats lets us export the results to a permanent table */ + IF @OutputDatabaseName IS NOT NULL + AND @OutputSchemaName IS NOT NULL + AND @OutputTableNameWaitStats IS NOT NULL + AND @OutputTableNameWaitStats NOT LIKE '#%' + AND EXISTS ( SELECT * + FROM sys.databases + WHERE QUOTENAME([name]) = @OutputDatabaseName) BEGIN - SET @StringToExecute += - N', ''DBCC FREEPROCCACHE ('' + CONVERT(NVARCHAR(128), r.plan_handle, 1) + '');'' AS fix_parameter_sniffing, - s.client_interface_name , - s.login_time , - r.start_time , - qmg.request_time , - COALESCE(r.cpu_time, s.cpu_time) AS request_cpu_time, - COALESCE(r.logical_reads, s.logical_reads) AS request_logical_reads, - COALESCE(r.writes, s.writes) AS request_writes, - COALESCE(r.reads, s.reads) AS request_physical_reads , - s.cpu_time AS session_cpu, - s.logical_reads AS session_logical_reads, - s.reads AS session_physical_reads , - s.writes AS session_writes, - tempdb_allocations.tempdb_allocations_mb, - s.memory_usage , - r.estimated_completion_time , - r.percent_complete , - r.deadlock_priority , - CASE - WHEN s.transaction_isolation_level = 0 THEN ''Unspecified'' - WHEN s.transaction_isolation_level = 1 THEN ''Read Uncommitted'' - WHEN s.transaction_isolation_level = 2 AND EXISTS (SELECT 1 FROM sys.databases WHERE name = DB_NAME(r.database_id) AND is_read_committed_snapshot_on = 1) THEN ''Read Committed Snapshot Isolation'' - WHEN s.transaction_isolation_level = 2 THEN ''Read Committed'' - WHEN s.transaction_isolation_level = 3 THEN ''Repeatable Read'' - WHEN s.transaction_isolation_level = 4 THEN ''Serializable'' - WHEN s.transaction_isolation_level = 5 THEN ''Snapshot'' - ELSE ''WHAT HAVE YOU DONE?'' - END AS transaction_isolation_level , - qmg.dop AS degree_of_parallelism , ' - + - CASE @EnhanceFlag - WHEN 1 THEN N'query_stats.last_dop, - query_stats.min_dop, - query_stats.max_dop, - query_stats.last_grant_kb, - query_stats.min_grant_kb, - query_stats.max_grant_kb, - query_stats.last_used_grant_kb, - query_stats.min_used_grant_kb, - query_stats.max_used_grant_kb, - query_stats.last_ideal_grant_kb, - query_stats.min_ideal_grant_kb, - query_stats.max_ideal_grant_kb, - query_stats.last_reserved_threads, - query_stats.min_reserved_threads, - query_stats.max_reserved_threads, - query_stats.last_used_threads, - query_stats.min_used_threads, - query_stats.max_used_threads,' - ELSE N' NULL AS last_dop, - NULL AS min_dop, - NULL AS max_dop, - NULL AS last_grant_kb, - NULL AS min_grant_kb, - NULL AS max_grant_kb, - NULL AS last_used_grant_kb, - NULL AS min_used_grant_kb, - NULL AS max_used_grant_kb, - NULL AS last_ideal_grant_kb, - NULL AS min_ideal_grant_kb, - NULL AS max_ideal_grant_kb, - NULL AS last_reserved_threads, - NULL AS min_reserved_threads, - NULL AS max_reserved_threads, - NULL AS last_used_threads, - NULL AS min_used_threads, - NULL AS max_used_threads,' - END + /* Create the table */ + SET @StringToExecute = 'USE ' + + @OutputDatabaseName + + '; IF EXISTS(SELECT * FROM ' + + @OutputDatabaseName + + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' + + @OutputSchemaName + + ''') AND NOT EXISTS (SELECT * FROM ' + + @OutputDatabaseName + + '.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''' + + @OutputSchemaName + ''' AND QUOTENAME(TABLE_NAME) = ''' + + @OutputTableNameWaitStats + ''') ' + @LineFeed + + 'BEGIN' + @LineFeed + + 'CREATE TABLE ' + + @OutputSchemaName + '.' + + @OutputTableNameWaitStats + + ' (ID INT IDENTITY(1,1) NOT NULL, + ServerName NVARCHAR(128), + CheckDate DATETIMEOFFSET, + wait_type NVARCHAR(60), + wait_time_ms BIGINT, + signal_wait_time_ms BIGINT, + waiting_tasks_count BIGINT , + PRIMARY KEY CLUSTERED (ID));' + @LineFeed + + 'CREATE NONCLUSTERED INDEX IX_ServerName_wait_type_CheckDate_Includes ON ' + @OutputSchemaName + '.' + @OutputTableNameWaitStats + @LineFeed + + '(ServerName, wait_type, CheckDate) INCLUDE (wait_time_ms, signal_wait_time_ms, waiting_tasks_count);' + @LineFeed + + 'END'; - SET @StringToExecute += - N' - COALESCE(CAST(qmg.grant_time AS VARCHAR(20)), ''Memory Not Granted'') AS grant_time , - qmg.requested_memory_kb , - qmg.granted_memory_kb AS grant_memory_kb, - CASE WHEN qmg.grant_time IS NULL THEN ''N/A'' - WHEN qmg.requested_memory_kb < qmg.granted_memory_kb - THEN ''Query Granted Less Than Query Requested'' - ELSE ''Memory Request Granted'' - END AS is_request_granted , - qmg.required_memory_kb , - qmg.used_memory_kb AS query_memory_grant_used_memory_kb, - qmg.ideal_memory_kb , - qmg.is_small , - qmg.timeout_sec , - qmg.resource_semaphore_id , - COALESCE(CAST(qmg.wait_order AS VARCHAR(20)), ''N/A'') AS wait_order , - COALESCE(CAST(qmg.wait_time_ms AS VARCHAR(20)), - ''N/A'') AS wait_time_ms , - CASE qmg.is_next_candidate - WHEN 0 THEN ''No'' - WHEN 1 THEN ''Yes'' - ELSE ''N/A'' - END AS next_candidate_for_memory_grant , - qrs.target_memory_kb , - COALESCE(CAST(qrs.max_target_memory_kb AS VARCHAR(20)), - ''Small Query Resource Semaphore'') AS max_target_memory_kb , - qrs.total_memory_kb , - qrs.available_memory_kb , - qrs.granted_memory_kb , - qrs.used_memory_kb AS query_resource_semaphore_used_memory_kb, - qrs.grantee_count , - qrs.waiter_count , - qrs.timeout_error_count , - COALESCE(CAST(qrs.forced_grant_count AS VARCHAR(20)), - ''Small Query Resource Semaphore'') AS forced_grant_count, - wg.name AS workload_group_name, - rp.name AS resource_pool_name, - CONVERT(VARCHAR(128), r.context_info) AS context_info, - r.query_hash, r.query_plan_hash, r.sql_handle, r.plan_handle, r.statement_start_offset, r.statement_end_offset ' - END /* IF @ExpertMode = 1 */ - - SET @StringToExecute += - N' FROM sys.dm_exec_sessions AS s - LEFT JOIN sys.dm_exec_requests AS r - ON r.session_id = s.session_id - LEFT JOIN ( SELECT DISTINCT - wait.session_id , - ( SELECT waitwait.wait_type + N'' ('' - + CAST(MAX(waitwait.wait_duration_ms) AS NVARCHAR(128)) - + N'' ms) '' - FROM sys.dm_os_waiting_tasks AS waitwait - WHERE waitwait.session_id = wait.session_id - GROUP BY waitwait.wait_type - ORDER BY SUM(waitwait.wait_duration_ms) DESC - FOR - XML PATH('''') ) AS wait_info - FROM sys.dm_os_waiting_tasks AS wait ) AS wt - ON s.session_id = wt.session_id - LEFT JOIN sys.dm_exec_query_stats AS query_stats - ON r.sql_handle = query_stats.sql_handle - AND r.plan_handle = query_stats.plan_handle - AND r.statement_start_offset = query_stats.statement_start_offset - AND r.statement_end_offset = query_stats.statement_end_offset - ' - + - CASE @SessionWaits - WHEN 1 THEN @SessionWaitsSQL - ELSE N'' - END - + - N' - LEFT JOIN sys.dm_exec_query_memory_grants qmg - ON r.session_id = qmg.session_id - AND r.request_id = qmg.request_id - LEFT JOIN sys.dm_exec_query_resource_semaphores qrs - ON qmg.resource_semaphore_id = qrs.resource_semaphore_id - AND qmg.pool_id = qrs.pool_id - LEFT JOIN sys.resource_governor_workload_groups wg - ON s.group_id = wg.group_id - LEFT JOIN sys.resource_governor_resource_pools rp - ON wg.pool_id = rp.pool_id - OUTER APPLY ( - SELECT TOP 1 - b.dbid, b.last_batch, b.open_tran, b.sql_handle, - b.session_id, b.blocking_session_id, b.lastwaittype, b.waittime - FROM @blocked b - WHERE (s.session_id = b.session_id - OR s.session_id = b.blocking_session_id) - ) AS blocked - OUTER APPLY sys.dm_exec_sql_text(COALESCE(r.sql_handle, blocked.sql_handle)) AS dest - OUTER APPLY sys.dm_exec_query_plan(r.plan_handle) AS derp - OUTER APPLY ( - SELECT CONVERT(DECIMAL(38,2), SUM( (((tsu.user_objects_alloc_page_count - user_objects_dealloc_page_count) * 8) / 1024.)) ) AS tempdb_allocations_mb - FROM sys.dm_db_task_space_usage tsu - WHERE tsu.request_id = r.request_id - AND tsu.session_id = r.session_id - AND tsu.session_id = s.session_id - ) as tempdb_allocations - ' - + @QueryStatsXMLSQL - + - N' - WHERE s.session_id <> @@SPID - AND s.host_name IS NOT NULL - AND r.database_id NOT IN (SELECT database_id FROM #WhoReadableDBs) - ' - + CASE WHEN @ShowSleepingSPIDs = 0 THEN - N' AND COALESCE(DB_NAME(r.database_id), DB_NAME(blocked.dbid)) IS NOT NULL' - WHEN @ShowSleepingSPIDs = 1 THEN - N' OR COALESCE(r.open_transaction_count, blocked.open_tran) >= 1' - ELSE N'' END; + EXEC(@StringToExecute); + + /* Create the wait stats category table */ + SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableNameWaitStats_Categories; + IF OBJECT_ID(@ObjectFullName) IS NULL + BEGIN + SET @StringToExecute = 'USE ' + + @OutputDatabaseName + + '; EXEC (''CREATE TABLE ' + + @OutputSchemaName + '.' + + @OutputTableNameWaitStats_Categories + ' (WaitType NVARCHAR(60) PRIMARY KEY CLUSTERED, WaitCategory NVARCHAR(128) NOT NULL, Ignorable BIT DEFAULT 0);'')'; + + EXEC(@StringToExecute); + END; + + /* Make sure the wait stats category table has the current number of rows */ + SET @StringToExecute = 'USE ' + + @OutputDatabaseName + + '; EXEC (''IF (SELECT COALESCE(SUM(1),0) FROM ' + @OutputSchemaName + '.' + @OutputTableNameWaitStats_Categories + ') <> (SELECT COALESCE(SUM(1),0) FROM ##WaitCategories)' + @LineFeed + + 'BEGIN ' + @LineFeed + + 'TRUNCATE TABLE ' + @OutputSchemaName + '.' + @OutputTableNameWaitStats_Categories + @LineFeed + + 'INSERT INTO ' + @OutputSchemaName + '.' + @OutputTableNameWaitStats_Categories + ' (WaitType, WaitCategory, Ignorable) SELECT WaitType, WaitCategory, Ignorable FROM ##WaitCategories;' + @LineFeed + + 'END'')'; + + EXEC(@StringToExecute); + + + SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableNameWaitStats_View; + + /* If the view exists without the most recently added columns, drop it. See Github #2162. */ + IF OBJECT_ID(@ObjectFullName) IS NOT NULL + BEGIN + SET @StringToExecute = N'USE ' + @OutputDatabaseName + N'; IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns + WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''JoinKey'') + DROP VIEW ' + @OutputSchemaName + N'.' + @OutputTableNameWaitStats_View + N';'; + + EXEC(@StringToExecute); + END + + + /* Create the wait stats view */ + IF OBJECT_ID(@ObjectFullName) IS NULL + BEGIN + SET @StringToExecute = 'USE ' + + @OutputDatabaseName + + '; EXEC (''CREATE VIEW ' + + @OutputSchemaName + '.' + + @OutputTableNameWaitStats_View + ' AS ' + @LineFeed + + 'WITH RowDates as' + @LineFeed + + '(' + @LineFeed + + ' SELECT ' + @LineFeed + + ' ROW_NUMBER() OVER (ORDER BY [ServerName], [CheckDate]) ID,' + @LineFeed + + ' [CheckDate]' + @LineFeed + + ' FROM ' + @OutputSchemaName + '.' + @OutputTableNameWaitStats + @LineFeed + + ' GROUP BY [ServerName], [CheckDate]' + @LineFeed + + '),' + @LineFeed + + 'CheckDates as' + @LineFeed + + '(' + @LineFeed + + ' SELECT ThisDate.CheckDate,' + @LineFeed + + ' LastDate.CheckDate as PreviousCheckDate' + @LineFeed + + ' FROM RowDates ThisDate' + @LineFeed + + ' JOIN RowDates LastDate' + @LineFeed + + ' ON ThisDate.ID = LastDate.ID + 1' + @LineFeed + + ')' + @LineFeed + + 'SELECT w.ServerName, w.CheckDate, w.wait_type, COALESCE(wc.WaitCategory, ''''Other'''') AS WaitCategory, COALESCE(wc.Ignorable,0) AS Ignorable' + @LineFeed + + ', DATEDIFF(ss, wPrior.CheckDate, w.CheckDate) AS ElapsedSeconds' + @LineFeed + + ', (w.wait_time_ms - wPrior.wait_time_ms) AS wait_time_ms_delta' + @LineFeed + + ', (w.wait_time_ms - wPrior.wait_time_ms) / 60000.0 AS wait_time_minutes_delta' + @LineFeed + + ', (w.wait_time_ms - wPrior.wait_time_ms) / 1000.0 / DATEDIFF(ss, wPrior.CheckDate, w.CheckDate) AS wait_time_minutes_per_minute' + @LineFeed + + ', (w.signal_wait_time_ms - wPrior.signal_wait_time_ms) AS signal_wait_time_ms_delta' + @LineFeed + + ', (w.waiting_tasks_count - wPrior.waiting_tasks_count) AS waiting_tasks_count_delta' + @LineFeed + + ', w.ServerName + CAST(w.CheckDate AS NVARCHAR(50)) AS JoinKey' + @LineFeed + + 'FROM ' + @OutputSchemaName + '.' + @OutputTableNameWaitStats + ' w' + @LineFeed + + 'INNER HASH JOIN CheckDates Dates' + @LineFeed + + 'ON Dates.CheckDate = w.CheckDate' + @LineFeed + + 'INNER JOIN ' + @OutputSchemaName + '.' + @OutputTableNameWaitStats + ' wPrior ON w.ServerName = wPrior.ServerName AND w.wait_type = wPrior.wait_type AND Dates.PreviousCheckDate = wPrior.CheckDate' + @LineFeed + + 'LEFT OUTER JOIN ' + @OutputSchemaName + '.' + @OutputTableNameWaitStats_Categories + ' wc ON w.wait_type = wc.WaitType' + @LineFeed + + 'WHERE DATEDIFF(MI, wPrior.CheckDate, w.CheckDate) BETWEEN 1 AND 60' + @LineFeed + + 'AND [w].[wait_time_ms] >= [wPrior].[wait_time_ms];'')' + + EXEC(@StringToExecute); + END; + + + SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' + + @OutputDatabaseName + + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' + + @OutputSchemaName + ''') INSERT ' + + @OutputDatabaseName + '.' + + @OutputSchemaName + '.' + + @OutputTableNameWaitStats + + ' (ServerName, CheckDate, wait_type, wait_time_ms, signal_wait_time_ms, waiting_tasks_count) SELECT ' + + ' @SrvName, @CheckDate, wait_type, wait_time_ms, signal_wait_time_ms, waiting_tasks_count FROM #WaitStats WHERE Pass = 2 AND wait_time_ms > 0 AND waiting_tasks_count > 0'; + + EXEC sp_executesql @StringToExecute, + N'@SrvName NVARCHAR(128), @CheckDate datetimeoffset', + @LocalServerName, @StartSampleTime; + + /* Delete history older than @OutputTableRetentionDays */ + SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' + + @OutputDatabaseName + + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' + + @OutputSchemaName + ''') DELETE ' + + @OutputDatabaseName + '.' + + @OutputSchemaName + '.' + + @OutputTableNameWaitStats + + ' WHERE ServerName = @SrvName AND CheckDate < @CheckDate ;'; + + EXEC sp_executesql @StringToExecute, + N'@SrvName NVARCHAR(128), @CheckDate date', + @LocalServerName, @OutputTableCleanupDate; + END; + ELSE IF (SUBSTRING(@OutputTableNameWaitStats, 2, 2) = '##') + BEGIN + SET @StringToExecute = N' IF (OBJECT_ID(''tempdb..' + + @OutputTableNameWaitStats + + ''') IS NULL) CREATE TABLE ' + + @OutputTableNameWaitStats + + ' (ID INT IDENTITY(1,1) NOT NULL, + ServerName NVARCHAR(128), + CheckDate DATETIMEOFFSET, + wait_type NVARCHAR(60), + wait_time_ms BIGINT, + signal_wait_time_ms BIGINT, + waiting_tasks_count BIGINT , + PRIMARY KEY CLUSTERED (ID ASC));' + + ' INSERT ' + + @OutputTableNameWaitStats + + ' (ServerName, CheckDate, wait_type, wait_time_ms, signal_wait_time_ms, waiting_tasks_count) SELECT ' + + ' @SrvName, @CheckDate, wait_type, wait_time_ms, signal_wait_time_ms, waiting_tasks_count FROM #WaitStats WHERE Pass = 2 AND wait_time_ms > 0 AND waiting_tasks_count > 0'; -END /* IF @ProductVersionMajor >= 11 */ + EXEC sp_executesql @StringToExecute, + N'@SrvName NVARCHAR(128), @CheckDate datetimeoffset', + @LocalServerName, @StartSampleTime; + END; + ELSE IF (SUBSTRING(@OutputTableNameWaitStats, 2, 1) = '#') + BEGIN + RAISERROR('Due to the nature of Dymamic SQL, only global (i.e. double pound (##)) temp tables are supported for @OutputTableName', 16, 0); + END; -IF (@MinElapsedSeconds + @MinCPUTime + @MinLogicalReads + @MinPhysicalReads + @MinWrites + @MinTempdbMB + @MinRequestedMemoryKB + @MinBlockingSeconds) > 0 - BEGIN - /* They're filtering for something, so set up a where clause that will let any (not all combined) of the min triggers work: */ - SET @StringToExecute += N' AND (1 = 0 '; - IF @MinElapsedSeconds > 0 - SET @StringToExecute += N' OR ABS(COALESCE(r.total_elapsed_time,0)) / 1000 >= ' + CAST(@MinElapsedSeconds AS NVARCHAR(20)); - IF @MinCPUTime > 0 - SET @StringToExecute += N' OR COALESCE(r.cpu_time, s.cpu_time,0) / 1000 >= ' + CAST(@MinCPUTime AS NVARCHAR(20)); - IF @MinLogicalReads > 0 - SET @StringToExecute += N' OR COALESCE(r.logical_reads, s.logical_reads,0) >= ' + CAST(@MinLogicalReads AS NVARCHAR(20)); - IF @MinPhysicalReads > 0 - SET @StringToExecute += N' OR COALESCE(s.reads,0) >= ' + CAST(@MinPhysicalReads AS NVARCHAR(20)); - IF @MinWrites > 0 - SET @StringToExecute += N' OR COALESCE(r.writes, s.writes,0) >= ' + CAST(@MinWrites AS NVARCHAR(20)); - IF @MinTempdbMB > 0 - SET @StringToExecute += N' OR COALESCE(tempdb_allocations.tempdb_allocations_mb,0) >= ' + CAST(@MinTempdbMB AS NVARCHAR(20)); - IF @MinRequestedMemoryKB > 0 - SET @StringToExecute += N' OR COALESCE(qmg.requested_memory_kb,0) >= ' + CAST(@MinRequestedMemoryKB AS NVARCHAR(20)); - /* Blocking is a little different - we're going to return ALL of the queries if we meet the blocking threshold. */ - IF @MinBlockingSeconds > 0 - SET @StringToExecute += N' OR (SELECT SUM(waittime / 1000) FROM @blocked) >= ' + CAST(@MinBlockingSeconds AS NVARCHAR(20)); - SET @StringToExecute += N' ) '; - END -SET @StringToExecute += - N' ORDER BY ' + CASE WHEN @SortOrder = 'session_id' THEN '[session_id] DESC' - WHEN @SortOrder = 'query_cost' THEN '[query_cost] DESC' - WHEN @SortOrder = 'database_name' THEN '[database_name] ASC' - WHEN @SortOrder = 'open_transaction_count' THEN '[open_transaction_count] DESC' - WHEN @SortOrder = 'is_implicit_transaction' THEN '[is_implicit_transaction] DESC' - WHEN @SortOrder = 'login_name' THEN '[login_name] ASC' - WHEN @SortOrder = 'program_name' THEN '[program_name] ASC' - WHEN @SortOrder = 'client_interface_name' THEN '[client_interface_name] ASC' - WHEN @SortOrder = 'request_cpu_time' THEN 'COALESCE(r.cpu_time, s.cpu_time) DESC' - WHEN @SortOrder = 'request_logical_reads' THEN 'COALESCE(r.logical_reads, s.logical_reads) DESC' - WHEN @SortOrder = 'request_writes' THEN 'COALESCE(r.writes, s.writes) DESC' - WHEN @SortOrder = 'request_physical_reads' THEN 'COALESCE(r.reads, s.reads) DESC ' - WHEN @SortOrder = 'session_cpu' THEN 's.cpu_time DESC' - WHEN @SortOrder = 'session_logical_reads' THEN 's.logical_reads DESC' - WHEN @SortOrder = 'session_physical_reads' THEN 's.reads DESC' - WHEN @SortOrder = 'session_writes' THEN 's.writes DESC' - WHEN @SortOrder = 'tempdb_allocations_mb' THEN '[tempdb_allocations_mb] DESC' - WHEN @SortOrder = 'memory_usage' THEN '[memory_usage] DESC' - WHEN @SortOrder = 'deadlock_priority' THEN 'r.deadlock_priority DESC' - WHEN @SortOrder = 'transaction_isolation_level' THEN 'r.[transaction_isolation_level] DESC' - WHEN @SortOrder = 'requested_memory_kb' THEN '[requested_memory_kb] DESC' - WHEN @SortOrder = 'grant_memory_kb' THEN 'qmg.granted_memory_kb DESC' - WHEN @SortOrder = 'grant' THEN 'qmg.granted_memory_kb DESC' - WHEN @SortOrder = 'query_memory_grant_used_memory_kb' THEN 'qmg.used_memory_kb DESC' - WHEN @SortOrder = 'ideal_memory_kb' THEN '[ideal_memory_kb] DESC' - WHEN @SortOrder = 'workload_group_name' THEN 'wg.name ASC' - WHEN @SortOrder = 'resource_pool_name' THEN 'rp.name ASC' - ELSE '[elapsed_time] DESC' - END + ' - '; -IF @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @OutputTableName IS NOT NULL - AND EXISTS ( SELECT * - FROM sys.databases - WHERE QUOTENAME([name]) = @OutputDatabaseName) - BEGIN - SET @StringToExecute = N'USE ' - + @OutputDatabaseName + N'; ' - + @BlockingCheck + - + ' INSERT INTO ' - + @OutputSchemaName + N'.' - + @OutputTableName - + N'(ServerName - ,CheckDate - ,[elapsed_time] - ,[session_id] - ,[database_name] - ,[query_text] - ,[query_plan]' - + CASE WHEN @ProductVersionMajor >= 11 THEN N',[live_query_plan]' ELSE N'' END + N' - ,[query_cost] - ,[status] - ,[wait_info]' - + CASE WHEN @ProductVersionMajor >= 11 THEN N',[top_session_waits]' ELSE N'' END + N' - ,[blocking_session_id] - ,[open_transaction_count] - ,[is_implicit_transaction] - ,[nt_domain] - ,[host_name] - ,[login_name] - ,[nt_user_name] - ,[program_name] - ,[fix_parameter_sniffing] - ,[client_interface_name] - ,[login_time] - ,[start_time] - ,[request_time] - ,[request_cpu_time] - ,[request_logical_reads] - ,[request_writes] - ,[request_physical_reads] - ,[session_cpu] - ,[session_logical_reads] - ,[session_physical_reads] - ,[session_writes] - ,[tempdb_allocations_mb] - ,[memory_usage] - ,[estimated_completion_time] - ,[percent_complete] - ,[deadlock_priority] - ,[transaction_isolation_level] - ,[degree_of_parallelism]' - + CASE WHEN @ProductVersionMajor >= 11 THEN N' - ,[last_dop] - ,[min_dop] - ,[max_dop] - ,[last_grant_kb] - ,[min_grant_kb] - ,[max_grant_kb] - ,[last_used_grant_kb] - ,[min_used_grant_kb] - ,[max_used_grant_kb] - ,[last_ideal_grant_kb] - ,[min_ideal_grant_kb] - ,[max_ideal_grant_kb] - ,[last_reserved_threads] - ,[min_reserved_threads] - ,[max_reserved_threads] - ,[last_used_threads] - ,[min_used_threads] - ,[max_used_threads]' ELSE N'' END + N' - ,[grant_time] - ,[requested_memory_kb] - ,[grant_memory_kb] - ,[is_request_granted] - ,[required_memory_kb] - ,[query_memory_grant_used_memory_kb] - ,[ideal_memory_kb] - ,[is_small] - ,[timeout_sec] - ,[resource_semaphore_id] - ,[wait_order] - ,[wait_time_ms] - ,[next_candidate_for_memory_grant] - ,[target_memory_kb] - ,[max_target_memory_kb] - ,[total_memory_kb] - ,[available_memory_kb] - ,[granted_memory_kb] - ,[query_resource_semaphore_used_memory_kb] - ,[grantee_count] - ,[waiter_count] - ,[timeout_error_count] - ,[forced_grant_count] - ,[workload_group_name] - ,[resource_pool_name] - ,[context_info]' - + CASE WHEN @ProductVersionMajor >= 11 THEN N' - ,[query_hash] - ,[query_plan_hash] - ,[sql_handle] - ,[plan_handle] - ,[statement_start_offset] - ,[statement_end_offset]' ELSE N'' END + N' -) - SELECT @@SERVERNAME, COALESCE(@CheckDateOverride, SYSDATETIMEOFFSET()) AS CheckDate , ' - + @StringToExecute; - END -ELSE - SET @StringToExecute = @BlockingCheck + N' SELECT GETDATE() AS run_date , ' + @StringToExecute; + DECLARE @separator AS VARCHAR(1); + IF @OutputType = 'RSV' + SET @separator = CHAR(31); + ELSE + SET @separator = ','; -/* If the server has > 50GB of memory, add a max grant hint to avoid getting a giant grant */ -IF (@ProductVersionMajor = 11 AND @ProductVersionMinor >= 6020) - OR (@ProductVersionMajor = 12 AND @ProductVersionMinor >= 5000 ) - OR (@ProductVersionMajor >= 13 ) - AND 50000000 < (SELECT cntr_value - FROM sys.dm_os_performance_counters - WHERE object_name LIKE '%:Memory Manager%' - AND counter_name LIKE 'Target Server Memory (KB)%') - BEGIN - SET @StringToExecute = @StringToExecute + N' OPTION (MAX_GRANT_PERCENT = 1, RECOMPILE) '; - END -ELSE - BEGIN - SET @StringToExecute = @StringToExecute + N' OPTION (RECOMPILE) '; - END + IF @OutputType = 'COUNT' AND @SinceStartup = 0 + BEGIN + SELECT COUNT(*) AS Warnings + FROM #BlitzFirstResults; + END; + ELSE + IF @OutputType = 'Opserver1' AND @SinceStartup = 0 + BEGIN + + SELECT r.[Priority] , + r.[FindingsGroup] , + r.[Finding] , + r.[URL] , + r.[Details], + r.[HowToStopIt] , + r.[CheckID] , + r.[StartTime], + r.[LoginName], + r.[NTUserName], + r.[OriginalLoginName], + r.[ProgramName], + r.[HostName], + r.[DatabaseID], + r.[DatabaseName], + r.[OpenTransactionCount], + r.[QueryPlan], + r.[QueryText], + qsNow.plan_handle AS PlanHandle, + qsNow.sql_handle AS SqlHandle, + qsNow.statement_start_offset AS StatementStartOffset, + qsNow.statement_end_offset AS StatementEndOffset, + [Executions] = qsNow.execution_count - (COALESCE(qsFirst.execution_count, 0)), + [ExecutionsPercent] = CAST(100.0 * (qsNow.execution_count - (COALESCE(qsFirst.execution_count, 0))) / (qsTotal.execution_count - qsTotalFirst.execution_count) AS DECIMAL(6,2)), + [Duration] = qsNow.total_elapsed_time - (COALESCE(qsFirst.total_elapsed_time, 0)), + [DurationPercent] = CAST(100.0 * (qsNow.total_elapsed_time - (COALESCE(qsFirst.total_elapsed_time, 0))) / (qsTotal.total_elapsed_time - qsTotalFirst.total_elapsed_time) AS DECIMAL(6,2)), + [CPU] = qsNow.total_worker_time - (COALESCE(qsFirst.total_worker_time, 0)), + [CPUPercent] = CAST(100.0 * (qsNow.total_worker_time - (COALESCE(qsFirst.total_worker_time, 0))) / (qsTotal.total_worker_time - qsTotalFirst.total_worker_time) AS DECIMAL(6,2)), + [Reads] = qsNow.total_logical_reads - (COALESCE(qsFirst.total_logical_reads, 0)), + [ReadsPercent] = CAST(100.0 * (qsNow.total_logical_reads - (COALESCE(qsFirst.total_logical_reads, 0))) / (qsTotal.total_logical_reads - qsTotalFirst.total_logical_reads) AS DECIMAL(6,2)), + [PlanCreationTime] = CONVERT(NVARCHAR(100), qsNow.creation_time ,121), + [TotalExecutions] = qsNow.execution_count, + [TotalExecutionsPercent] = CAST(100.0 * qsNow.execution_count / qsTotal.execution_count AS DECIMAL(6,2)), + [TotalDuration] = qsNow.total_elapsed_time, + [TotalDurationPercent] = CAST(100.0 * qsNow.total_elapsed_time / qsTotal.total_elapsed_time AS DECIMAL(6,2)), + [TotalCPU] = qsNow.total_worker_time, + [TotalCPUPercent] = CAST(100.0 * qsNow.total_worker_time / qsTotal.total_worker_time AS DECIMAL(6,2)), + [TotalReads] = qsNow.total_logical_reads, + [TotalReadsPercent] = CAST(100.0 * qsNow.total_logical_reads / qsTotal.total_logical_reads AS DECIMAL(6,2)), + r.[DetailsInt] + FROM #BlitzFirstResults r + LEFT OUTER JOIN #QueryStats qsTotal ON qsTotal.Pass = 0 + LEFT OUTER JOIN #QueryStats qsTotalFirst ON qsTotalFirst.Pass = -1 + LEFT OUTER JOIN #QueryStats qsNow ON r.QueryStatsNowID = qsNow.ID + LEFT OUTER JOIN #QueryStats qsFirst ON r.QueryStatsFirstID = qsFirst.ID + ORDER BY r.Priority , + r.FindingsGroup , + CASE + WHEN r.CheckID = 6 THEN DetailsInt + ELSE 0 + END DESC, + r.Finding, + r.ID; + END; + ELSE IF @OutputType IN ( 'CSV', 'RSV' ) AND @SinceStartup = 0 + BEGIN + + SELECT Result = CAST([Priority] AS NVARCHAR(100)) + + @separator + CAST(CheckID AS NVARCHAR(100)) + + @separator + COALESCE([FindingsGroup], + '(N/A)') + @separator + + COALESCE([Finding], '(N/A)') + @separator + + COALESCE(DatabaseName, '(N/A)') + @separator + + COALESCE([URL], '(N/A)') + @separator + + COALESCE([Details], '(N/A)') + FROM #BlitzFirstResults + ORDER BY Priority , + FindingsGroup , + CASE + WHEN CheckID = 6 THEN DetailsInt + ELSE 0 + END DESC, + Finding, + Details; + END; + ELSE IF @OutputType = 'Top10' + BEGIN + /* Measure waits in hours */ + ;WITH max_batch AS ( + SELECT MAX(SampleTime) AS SampleTime + FROM #WaitStats + ) + SELECT TOP 10 + CAST(DATEDIFF(mi,wd1.SampleTime, wd2.SampleTime) / 60.0 AS DECIMAL(18,1)) AS [Hours Sample], + wd1.wait_type, + COALESCE(wcat.WaitCategory, 'Other') AS wait_category, + CAST(c.[Wait Time (Seconds)] / 60.0 / 60 AS DECIMAL(18,1)) AS [Wait Time (Hours)], + CAST((wd2.wait_time_ms - wd1.wait_time_ms) / 1000.0 / cores.cpu_count / DATEDIFF(ss, wd1.SampleTime, wd2.SampleTime) AS DECIMAL(18,1)) AS [Per Core Per Hour], + (wd2.waiting_tasks_count - wd1.waiting_tasks_count) AS [Number of Waits], + CASE WHEN (wd2.waiting_tasks_count - wd1.waiting_tasks_count) > 0 + THEN + CAST((wd2.wait_time_ms-wd1.wait_time_ms)/ + (1.0*(wd2.waiting_tasks_count - wd1.waiting_tasks_count)) AS NUMERIC(12,1)) + ELSE 0 END AS [Avg ms Per Wait] + FROM max_batch b + JOIN #WaitStats wd2 ON + wd2.SampleTime =b.SampleTime + JOIN #WaitStats wd1 ON + wd1.wait_type=wd2.wait_type AND + wd2.SampleTime > wd1.SampleTime + CROSS APPLY (SELECT SUM(1) AS cpu_count FROM sys.dm_os_schedulers WHERE status = 'VISIBLE ONLINE' AND is_online = 1) AS cores + CROSS APPLY (SELECT + CAST((wd2.wait_time_ms-wd1.wait_time_ms)/1000. AS NUMERIC(12,1)) AS [Wait Time (Seconds)], + CAST((wd2.signal_wait_time_ms - wd1.signal_wait_time_ms)/1000. AS NUMERIC(12,1)) AS [Signal Wait Time (Seconds)]) AS c + LEFT OUTER JOIN ##WaitCategories wcat ON wd1.wait_type = wcat.WaitType + WHERE (wd2.waiting_tasks_count - wd1.waiting_tasks_count) > 0 + AND wd2.wait_time_ms-wd1.wait_time_ms > 0 + ORDER BY [Wait Time (Seconds)] DESC; + END; + ELSE IF @ExpertMode = 0 AND @OutputType <> 'NONE' AND @OutputXMLasNVARCHAR = 0 AND @SinceStartup = 0 + BEGIN + SELECT [Priority] , + [FindingsGroup] , + [Finding] , + [URL] , + CAST(@StockDetailsHeader + [Details] + @StockDetailsFooter AS XML) AS Details, + CAST(@StockWarningHeader + HowToStopIt + @StockWarningFooter AS XML) AS HowToStopIt, + [QueryText], + [QueryPlan] + FROM #BlitzFirstResults + WHERE (@Seconds > 0 OR (Priority IN (0, 250, 251, 255))) /* For @Seconds = 0, filter out broken checks for now */ + ORDER BY Priority , + FindingsGroup , + CASE + WHEN CheckID = 6 THEN DetailsInt + ELSE 0 + END DESC, + Finding, + ID, + CAST(Details AS NVARCHAR(4000)); + END; + ELSE IF @OutputType <> 'NONE' AND @OutputXMLasNVARCHAR = 1 AND @SinceStartup = 0 + BEGIN + SELECT [Priority] , + [FindingsGroup] , + [Finding] , + [URL] , + CAST(@StockDetailsHeader + [Details] + @StockDetailsFooter AS NVARCHAR(MAX)) AS Details, + CAST([HowToStopIt] AS NVARCHAR(MAX)) AS HowToStopIt, + CAST([QueryText] AS NVARCHAR(MAX)) AS QueryText, + CAST([QueryPlan] AS NVARCHAR(MAX)) AS QueryPlan + FROM #BlitzFirstResults + WHERE (@Seconds > 0 OR (Priority IN (0, 250, 251, 255))) /* For @Seconds = 0, filter out broken checks for now */ + ORDER BY Priority , + FindingsGroup , + CASE + WHEN CheckID = 6 THEN DetailsInt + ELSE 0 + END DESC, + Finding, + ID, + CAST(Details AS NVARCHAR(4000)); + END; + ELSE IF @ExpertMode = 1 AND @OutputType <> 'NONE' + BEGIN + IF @SinceStartup = 0 + SELECT r.[Priority] , + r.[FindingsGroup] , + r.[Finding] , + r.[URL] , + CAST(@StockDetailsHeader + r.[Details] + @StockDetailsFooter AS XML) AS Details, + CAST(@StockWarningHeader + r.HowToStopIt + @StockWarningFooter AS XML) AS HowToStopIt, + r.[CheckID] , + r.[StartTime], + r.[LoginName], + r.[NTUserName], + r.[OriginalLoginName], + r.[ProgramName], + r.[HostName], + r.[DatabaseID], + r.[DatabaseName], + r.[OpenTransactionCount], + r.[QueryPlan], + r.[QueryText], + qsNow.plan_handle AS PlanHandle, + qsNow.sql_handle AS SqlHandle, + qsNow.statement_start_offset AS StatementStartOffset, + qsNow.statement_end_offset AS StatementEndOffset, + [Executions] = qsNow.execution_count - (COALESCE(qsFirst.execution_count, 0)), + [ExecutionsPercent] = CAST(100.0 * (qsNow.execution_count - (COALESCE(qsFirst.execution_count, 0))) / (qsTotal.execution_count - qsTotalFirst.execution_count) AS DECIMAL(6,2)), + [Duration] = qsNow.total_elapsed_time - (COALESCE(qsFirst.total_elapsed_time, 0)), + [DurationPercent] = CAST(100.0 * (qsNow.total_elapsed_time - (COALESCE(qsFirst.total_elapsed_time, 0))) / (qsTotal.total_elapsed_time - qsTotalFirst.total_elapsed_time) AS DECIMAL(6,2)), + [CPU] = qsNow.total_worker_time - (COALESCE(qsFirst.total_worker_time, 0)), + [CPUPercent] = CAST(100.0 * (qsNow.total_worker_time - (COALESCE(qsFirst.total_worker_time, 0))) / (qsTotal.total_worker_time - qsTotalFirst.total_worker_time) AS DECIMAL(6,2)), + [Reads] = qsNow.total_logical_reads - (COALESCE(qsFirst.total_logical_reads, 0)), + [ReadsPercent] = CAST(100.0 * (qsNow.total_logical_reads - (COALESCE(qsFirst.total_logical_reads, 0))) / (qsTotal.total_logical_reads - qsTotalFirst.total_logical_reads) AS DECIMAL(6,2)), + [PlanCreationTime] = CONVERT(NVARCHAR(100), qsNow.creation_time ,121), + [TotalExecutions] = qsNow.execution_count, + [TotalExecutionsPercent] = CAST(100.0 * qsNow.execution_count / qsTotal.execution_count AS DECIMAL(6,2)), + [TotalDuration] = qsNow.total_elapsed_time, + [TotalDurationPercent] = CAST(100.0 * qsNow.total_elapsed_time / qsTotal.total_elapsed_time AS DECIMAL(6,2)), + [TotalCPU] = qsNow.total_worker_time, + [TotalCPUPercent] = CAST(100.0 * qsNow.total_worker_time / qsTotal.total_worker_time AS DECIMAL(6,2)), + [TotalReads] = qsNow.total_logical_reads, + [TotalReadsPercent] = CAST(100.0 * qsNow.total_logical_reads / qsTotal.total_logical_reads AS DECIMAL(6,2)), + r.[DetailsInt] + FROM #BlitzFirstResults r + LEFT OUTER JOIN #QueryStats qsTotal ON qsTotal.Pass = 0 + LEFT OUTER JOIN #QueryStats qsTotalFirst ON qsTotalFirst.Pass = -1 + LEFT OUTER JOIN #QueryStats qsNow ON r.QueryStatsNowID = qsNow.ID + LEFT OUTER JOIN #QueryStats qsFirst ON r.QueryStatsFirstID = qsFirst.ID + WHERE (@Seconds > 0 OR (Priority IN (0, 250, 251, 255))) /* For @Seconds = 0, filter out broken checks for now */ + ORDER BY r.Priority , + r.FindingsGroup , + CASE + WHEN r.CheckID = 6 THEN DetailsInt + ELSE 0 + END DESC, + r.Finding, + r.ID, + CAST(r.Details AS NVARCHAR(4000)); + + ------------------------- + --What happened: #WaitStats + ------------------------- + IF @Seconds = 0 + BEGIN + /* Measure waits in hours */ + ;WITH max_batch AS ( + SELECT MAX(SampleTime) AS SampleTime + FROM #WaitStats + ) + SELECT + 'WAIT STATS' AS Pattern, + b.SampleTime AS [Sample Ended], + CAST(DATEDIFF(mi,wd1.SampleTime, wd2.SampleTime) / 60.0 AS DECIMAL(18,1)) AS [Hours Sample], + wd1.wait_type, + COALESCE(wcat.WaitCategory, 'Other') AS wait_category, + CAST(c.[Wait Time (Seconds)] / 60.0 / 60 AS DECIMAL(18,1)) AS [Wait Time (Hours)], + CAST((wd2.wait_time_ms - wd1.wait_time_ms) / 1000.0 / cores.cpu_count / DATEDIFF(ss, wd1.SampleTime, wd2.SampleTime) AS DECIMAL(18,1)) AS [Per Core Per Hour], + CAST(c.[Signal Wait Time (Seconds)] / 60.0 / 60 AS DECIMAL(18,1)) AS [Signal Wait Time (Hours)], + CASE WHEN c.[Wait Time (Seconds)] > 0 + THEN CAST(100.*(c.[Signal Wait Time (Seconds)]/c.[Wait Time (Seconds)]) AS NUMERIC(4,1)) + ELSE 0 END AS [Percent Signal Waits], + (wd2.waiting_tasks_count - wd1.waiting_tasks_count) AS [Number of Waits], + CASE WHEN (wd2.waiting_tasks_count - wd1.waiting_tasks_count) > 0 + THEN + CAST((wd2.wait_time_ms-wd1.wait_time_ms)/ + (1.0*(wd2.waiting_tasks_count - wd1.waiting_tasks_count)) AS NUMERIC(12,1)) + ELSE 0 END AS [Avg ms Per Wait], + N'https://www.sqlskills.com/help/waits/' + LOWER(wd1.wait_type) + '/' AS URL + FROM max_batch b + JOIN #WaitStats wd2 ON + wd2.SampleTime =b.SampleTime + JOIN #WaitStats wd1 ON + wd1.wait_type=wd2.wait_type AND + wd2.SampleTime > wd1.SampleTime + CROSS APPLY (SELECT SUM(1) AS cpu_count FROM sys.dm_os_schedulers WHERE status = 'VISIBLE ONLINE' AND is_online = 1) AS cores + CROSS APPLY (SELECT + CAST((wd2.wait_time_ms-wd1.wait_time_ms)/1000. AS NUMERIC(12,1)) AS [Wait Time (Seconds)], + CAST((wd2.signal_wait_time_ms - wd1.signal_wait_time_ms)/1000. AS NUMERIC(12,1)) AS [Signal Wait Time (Seconds)]) AS c + LEFT OUTER JOIN ##WaitCategories wcat ON wd1.wait_type = wcat.WaitType + WHERE (wd2.waiting_tasks_count - wd1.waiting_tasks_count) > 0 + AND wd2.wait_time_ms-wd1.wait_time_ms > 0 + ORDER BY [Wait Time (Seconds)] DESC; + END; + ELSE + BEGIN + /* Measure waits in seconds */ + ;WITH max_batch AS ( + SELECT MAX(SampleTime) AS SampleTime + FROM #WaitStats + ) + SELECT + 'WAIT STATS' AS Pattern, + b.SampleTime AS [Sample Ended], + DATEDIFF(ss,wd1.SampleTime, wd2.SampleTime) AS [Seconds Sample], + wd1.wait_type, + COALESCE(wcat.WaitCategory, 'Other') AS wait_category, + c.[Wait Time (Seconds)], + CAST((CAST(wd2.wait_time_ms - wd1.wait_time_ms AS MONEY)) / 1000.0 / cores.cpu_count / DATEDIFF(ss, wd1.SampleTime, wd2.SampleTime) AS DECIMAL(18,1)) AS [Per Core Per Second], + c.[Signal Wait Time (Seconds)], + CASE WHEN c.[Wait Time (Seconds)] > 0 + THEN CAST(100.*(c.[Signal Wait Time (Seconds)]/c.[Wait Time (Seconds)]) AS NUMERIC(4,1)) + ELSE 0 END AS [Percent Signal Waits], + (wd2.waiting_tasks_count - wd1.waiting_tasks_count) AS [Number of Waits], + CASE WHEN (wd2.waiting_tasks_count - wd1.waiting_tasks_count) > 0 + THEN + CAST((wd2.wait_time_ms-wd1.wait_time_ms)/ + (1.0*(wd2.waiting_tasks_count - wd1.waiting_tasks_count)) AS NUMERIC(12,1)) + ELSE 0 END AS [Avg ms Per Wait], + N'https://www.sqlskills.com/help/waits/' + LOWER(wd1.wait_type) + '/' AS URL + FROM max_batch b + JOIN #WaitStats wd2 ON + wd2.SampleTime =b.SampleTime + JOIN #WaitStats wd1 ON + wd1.wait_type=wd2.wait_type AND + wd2.SampleTime > wd1.SampleTime + CROSS APPLY (SELECT SUM(1) AS cpu_count FROM sys.dm_os_schedulers WHERE status = 'VISIBLE ONLINE' AND is_online = 1) AS cores + CROSS APPLY (SELECT + CAST((wd2.wait_time_ms-wd1.wait_time_ms)/1000. AS NUMERIC(12,1)) AS [Wait Time (Seconds)], + CAST((wd2.signal_wait_time_ms - wd1.signal_wait_time_ms)/1000. AS NUMERIC(12,1)) AS [Signal Wait Time (Seconds)]) AS c + LEFT OUTER JOIN ##WaitCategories wcat ON wd1.wait_type = wcat.WaitType + WHERE (wd2.waiting_tasks_count - wd1.waiting_tasks_count) > 0 + AND wd2.wait_time_ms-wd1.wait_time_ms > 0 + ORDER BY [Wait Time (Seconds)] DESC; + END; -/* Be good: */ -SET @StringToExecute = @StringToExecute + N' ; '; + ------------------------- + --What happened: #FileStats + ------------------------- + WITH readstats AS ( + SELECT 'PHYSICAL READS' AS Pattern, + ROW_NUMBER() OVER (ORDER BY wd2.avg_stall_read_ms DESC) AS StallRank, + wd2.SampleTime AS [Sample Time], + DATEDIFF(ss,wd1.SampleTime, wd2.SampleTime) AS [Sample (seconds)], + wd1.DatabaseName , + wd1.FileLogicalName AS [File Name], + UPPER(SUBSTRING(wd1.PhysicalName, 1, 2)) AS [Drive] , + wd1.SizeOnDiskMB , + ( wd2.num_of_reads - wd1.num_of_reads ) AS [# Reads/Writes], + CASE WHEN wd2.num_of_reads - wd1.num_of_reads > 0 + THEN CAST(( wd2.bytes_read - wd1.bytes_read)/1024./1024. AS NUMERIC(21,1)) + ELSE 0 + END AS [MB Read/Written], + wd2.avg_stall_read_ms AS [Avg Stall (ms)], + wd1.PhysicalName AS [file physical name] + FROM #FileStats wd2 + JOIN #FileStats wd1 ON wd2.SampleTime > wd1.SampleTime + AND wd1.DatabaseID = wd2.DatabaseID + AND wd1.FileID = wd2.FileID + ), + writestats AS ( + SELECT + 'PHYSICAL WRITES' AS Pattern, + ROW_NUMBER() OVER (ORDER BY wd2.avg_stall_write_ms DESC) AS StallRank, + wd2.SampleTime AS [Sample Time], + DATEDIFF(ss,wd1.SampleTime, wd2.SampleTime) AS [Sample (seconds)], + wd1.DatabaseName , + wd1.FileLogicalName AS [File Name], + UPPER(SUBSTRING(wd1.PhysicalName, 1, 2)) AS [Drive] , + wd1.SizeOnDiskMB , + ( wd2.num_of_writes - wd1.num_of_writes ) AS [# Reads/Writes], + CASE WHEN wd2.num_of_writes - wd1.num_of_writes > 0 + THEN CAST(( wd2.bytes_written - wd1.bytes_written)/1024./1024. AS NUMERIC(21,1)) + ELSE 0 + END AS [MB Read/Written], + wd2.avg_stall_write_ms AS [Avg Stall (ms)], + wd1.PhysicalName AS [file physical name] + FROM #FileStats wd2 + JOIN #FileStats wd1 ON wd2.SampleTime > wd1.SampleTime + AND wd1.DatabaseID = wd2.DatabaseID + AND wd1.FileID = wd2.FileID + ) + SELECT + Pattern, [Sample Time], [Sample (seconds)], [File Name], [Drive], [# Reads/Writes],[MB Read/Written],[Avg Stall (ms)], [file physical name], [StallRank] + FROM readstats + WHERE StallRank <=20 AND [MB Read/Written] > 0 + UNION ALL + SELECT Pattern, [Sample Time], [Sample (seconds)], [File Name], [Drive], [# Reads/Writes],[MB Read/Written],[Avg Stall (ms)], [file physical name], [StallRank] + FROM writestats + WHERE StallRank <=20 AND [MB Read/Written] > 0 + ORDER BY Pattern, StallRank; -IF @Debug = 1 - BEGIN - PRINT CONVERT(VARCHAR(8000), SUBSTRING(@StringToExecute, 0, 8000)) - PRINT CONVERT(VARCHAR(8000), SUBSTRING(@StringToExecute, 8000, 16000)) - END + ------------------------- + --What happened: #PerfmonStats + ------------------------- -EXEC sp_executesql @StringToExecute, - N'@CheckDateOverride DATETIMEOFFSET', - @CheckDateOverride; + SELECT 'PERFMON' AS Pattern, pLast.[object_name], pLast.counter_name, pLast.instance_name, + pFirst.SampleTime AS FirstSampleTime, pFirst.cntr_value AS FirstSampleValue, + pLast.SampleTime AS LastSampleTime, pLast.cntr_value AS LastSampleValue, + pLast.cntr_value - pFirst.cntr_value AS ValueDelta, + ((1.0 * pLast.cntr_value - pFirst.cntr_value) / DATEDIFF(ss, pFirst.SampleTime, pLast.SampleTime)) AS ValuePerSecond + FROM #PerfmonStats pLast + INNER JOIN #PerfmonStats pFirst ON pFirst.[object_name] = pLast.[object_name] AND pFirst.counter_name = pLast.counter_name AND (pFirst.instance_name = pLast.instance_name OR (pFirst.instance_name IS NULL AND pLast.instance_name IS NULL)) + AND pLast.ID > pFirst.ID + WHERE pLast.cntr_value <> pFirst.cntr_value + ORDER BY Pattern, pLast.[object_name], pLast.counter_name, pLast.instance_name; -END -GO -IF (OBJECT_ID('dbo.SqlServerVersions') IS NULL) -BEGIN + ------------------------- + --What happened: #QueryStats + ------------------------- + IF @CheckProcedureCache = 1 + BEGIN + + SELECT qsNow.*, qsFirst.* + FROM #QueryStats qsNow + INNER JOIN #QueryStats qsFirst ON qsNow.[sql_handle] = qsFirst.[sql_handle] AND qsNow.statement_start_offset = qsFirst.statement_start_offset AND qsNow.statement_end_offset = qsFirst.statement_end_offset AND qsNow.plan_generation_num = qsFirst.plan_generation_num AND qsNow.plan_handle = qsFirst.plan_handle AND qsFirst.Pass = 1 + WHERE qsNow.Pass = 2; + END; + ELSE + BEGIN + SELECT 'Plan Cache' AS [Pattern], 'Plan cache not analyzed' AS [Finding], 'Use @CheckProcedureCache = 1 or run sp_BlitzCache for more analysis' AS [More Info], CONVERT(XML, @StockDetailsHeader + 'firstresponderkit.org' + @StockDetailsFooter) AS [Details]; + END; + END; - CREATE TABLE dbo.SqlServerVersions - ( - MajorVersionNumber tinyint not null, - MinorVersionNumber smallint not null, - Branch varchar(34) not null, - [Url] varchar(99) not null, - ReleaseDate date not null, - MainstreamSupportEndDate date not null, - ExtendedSupportEndDate date not null, - MajorVersionName varchar(19) not null, - MinorVersionName varchar(67) not null, + DROP TABLE #BlitzFirstResults; - CONSTRAINT PK_SqlServerVersions PRIMARY KEY CLUSTERED - ( - MajorVersionNumber ASC, - MinorVersionNumber ASC, - ReleaseDate ASC - ) - ); - - EXEC sys.sp_addextendedproperty @name=N'Description', @value=N'The major version number.' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'SqlServerVersions', @level2type=N'COLUMN',@level2name=N'MajorVersionNumber' - EXEC sys.sp_addextendedproperty @name=N'Description', @value=N'The minor version number.' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'SqlServerVersions', @level2type=N'COLUMN',@level2name=N'MinorVersionNumber' - EXEC sys.sp_addextendedproperty @name=N'Description', @value=N'The update level of the build. CU indicates a cumulative update. SP indicates a service pack. RTM indicates Release To Manufacturer. GDR indicates a General Distribution Release. QFE indicates Quick Fix Engineering (aka hotfix).' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'SqlServerVersions', @level2type=N'COLUMN',@level2name=N'Branch' - EXEC sys.sp_addextendedproperty @name=N'Description', @value=N'A link to the KB article for a version.' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'SqlServerVersions', @level2type=N'COLUMN',@level2name=N'Url' - EXEC sys.sp_addextendedproperty @name=N'Description', @value=N'The date the version was publicly released.' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'SqlServerVersions', @level2type=N'COLUMN',@level2name=N'ReleaseDate' - EXEC sys.sp_addextendedproperty @name=N'Description', @value=N'The date main stream Microsoft support ends for the version.' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'SqlServerVersions', @level2type=N'COLUMN',@level2name=N'MainstreamSupportEndDate' - EXEC sys.sp_addextendedproperty @name=N'Description', @value=N'The date extended Microsoft support ends for the version.' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'SqlServerVersions', @level2type=N'COLUMN',@level2name=N'ExtendedSupportEndDate' - EXEC sys.sp_addextendedproperty @name=N'Description', @value=N'The major version name.' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'SqlServerVersions', @level2type=N'COLUMN',@level2name=N'MajorVersionName' - EXEC sys.sp_addextendedproperty @name=N'Description', @value=N'The minor version name.' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'SqlServerVersions', @level2type=N'COLUMN',@level2name=N'MinorVersionName' - EXEC sys.sp_addextendedproperty @name=N'Description', @value=N'A reference for SQL Server major and minor versions.' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'SqlServerVersions' + /* What's running right now? This is the first and last result set. */ + IF @SinceStartup = 0 AND @Seconds > 0 AND @ExpertMode = 1 AND @OutputType <> 'NONE' + BEGIN + IF OBJECT_ID('master.dbo.sp_BlitzWho') IS NULL AND OBJECT_ID('dbo.sp_BlitzWho') IS NULL + BEGIN + PRINT N'sp_BlitzWho is not installed in the current database_files. You can get a copy from http://FirstResponderKit.org'; + END; + ELSE + BEGIN + EXEC (@BlitzWho); + END; + END; /* IF @SinceStartup = 0 AND @Seconds > 0 AND @ExpertMode = 1 AND @OutputType <> 'NONE' - What's running right now? This is the first and last result set. */ -END; +END; /* IF @LogMessage IS NULL */ +END; /* ELSE IF @OutputType = 'SCHEMA' */ + +SET NOCOUNT OFF; GO -DELETE FROM dbo.SqlServerVersions; -INSERT INTO dbo.SqlServerVersions - (MajorVersionNumber, MinorVersionNumber, Branch, [Url], ReleaseDate, MainstreamSupportEndDate, ExtendedSupportEndDate, MajorVersionName, MinorVersionName) -VALUES - (15, 4073, 'CU8 GDR', 'https://support.microsoft.com/en-us/help/4583459', '2021-01-12', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 8 GDR '), - (15, 4073, 'CU8', 'https://support.microsoft.com/en-us/help/4577194', '2020-10-01', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 8 '), - (15, 4063, 'CU7', 'https://support.microsoft.com/en-us/help/4570012', '2020-09-02', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 7 '), - (15, 4053, 'CU6', 'https://support.microsoft.com/en-us/help/4563110', '2020-08-04', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 6 '), - (15, 4043, 'CU5', 'https://support.microsoft.com/en-us/help/4548597', '2020-06-22', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 5 '), - (15, 4033, 'CU4', 'https://support.microsoft.com/en-us/help/4548597', '2020-03-31', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 4 '), - (15, 4023, 'CU3', 'https://support.microsoft.com/en-us/help/4538853', '2020-03-12', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 3 '), - (15, 4013, 'CU2', 'https://support.microsoft.com/en-us/help/4536075', '2020-02-13', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 2 '), - (15, 4003, 'CU1', 'https://support.microsoft.com/en-us/help/4527376', '2020-01-07', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 1 '), - (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 '), - (14, 3370, 'RTM CU22 GDR', 'https://support.microsoft.com/en-us/help/4583457', '2021-01-12', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 22 GDR'), - (14, 3356, 'RTM CU22', 'https://support.microsoft.com/en-us/help/4577467', '2020-09-10', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 22'), - (14, 3335, 'RTM CU21', 'https://support.microsoft.com/en-us/help/4557397', '2020-07-01', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 21'), - (14, 3294, 'RTM CU20', 'https://support.microsoft.com/en-us/help/4541283', '2020-04-07', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 20'), - (14, 3257, 'RTM CU19', 'https://support.microsoft.com/en-us/help/4535007', '2020-02-05', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 19'), - (14, 3257, 'RTM CU18', 'https://support.microsoft.com/en-us/help/4527377', '2019-12-09', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 18'), - (14, 3238, 'RTM CU17', 'https://support.microsoft.com/en-us/help/4515579', '2019-10-08', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 17'), - (14, 3223, 'RTM CU16', 'https://support.microsoft.com/en-us/help/4508218', '2019-08-01', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 16'), - (14, 3162, 'RTM CU15', 'https://support.microsoft.com/en-us/help/4498951', '2019-05-24', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 15'), - (14, 3076, 'RTM CU14', 'https://support.microsoft.com/en-us/help/4484710', '2019-03-25', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 14'), - (14, 3048, 'RTM CU13', 'https://support.microsoft.com/en-us/help/4466404', '2018-12-18', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 13'), - (14, 3045, 'RTM CU12', 'https://support.microsoft.com/en-us/help/4464082', '2018-10-24', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 12'), - (14, 3038, 'RTM CU11', 'https://support.microsoft.com/en-us/help/4462262', '2018-09-20', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 11'), - (14, 3037, 'RTM CU10', 'https://support.microsoft.com/en-us/help/4524334', '2018-08-27', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 10'), - (14, 3030, 'RTM CU9', 'https://support.microsoft.com/en-us/help/4515435', '2018-07-18', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 9'), - (14, 3029, 'RTM CU8', 'https://support.microsoft.com/en-us/help/4338363', '2018-06-21', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 8'), - (14, 3026, 'RTM CU7', 'https://support.microsoft.com/en-us/help/4229789', '2018-05-23', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 7'), - (14, 3025, 'RTM CU6', 'https://support.microsoft.com/en-us/help/4101464', '2018-04-17', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 6'), - (14, 3023, 'RTM CU5', 'https://support.microsoft.com/en-us/help/4092643', '2018-03-20', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 5'), - (14, 3022, 'RTM CU4', 'https://support.microsoft.com/en-us/help/4056498', '2018-02-20', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 4'), - (14, 3015, 'RTM CU3', 'https://support.microsoft.com/en-us/help/4052987', '2018-01-04', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 3'), - (14, 3008, 'RTM CU2', 'https://support.microsoft.com/en-us/help/4052574', '2017-11-28', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 2'), - (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 '), - (13, 5865, 'SP2 CU15 GDR', 'https://support.microsoft.com/en-us/help/4583461', '2021-01-12', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 15 GDR'), - (13, 5850, 'SP2 CU15', 'https://support.microsoft.com/en-us/help/4577775', '2020-09-28', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 15'), - (13, 5830, 'SP2 CU14', 'https://support.microsoft.com/en-us/help/4564903', '2020-08-06', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 14'), - (13, 5820, 'SP2 CU13', 'https://support.microsoft.com/en-us/help/4549825', '2020-05-28', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 13'), - (13, 5698, 'SP2 CU12', 'https://support.microsoft.com/en-us/help/4536648', '2020-02-25', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 12'), - (13, 5598, 'SP2 CU11', 'https://support.microsoft.com/en-us/help/4527378', '2019-12-09', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 11'), - (13, 5492, 'SP2 CU10', 'https://support.microsoft.com/en-us/help/4505830', '2019-10-08', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 10'), - (13, 5479, 'SP2 CU9', 'https://support.microsoft.com/en-us/help/4505830', '2019-09-30', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 9'), - (13, 5426, 'SP2 CU8', 'https://support.microsoft.com/en-us/help/4505830', '2019-07-31', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 8'), - (13, 5337, 'SP2 CU7', 'https://support.microsoft.com/en-us/help/4495256', '2019-05-23', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 7'), - (13, 5292, 'SP2 CU6', 'https://support.microsoft.com/en-us/help/4488536', '2019-03-19', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 6'), - (13, 5264, 'SP2 CU5', 'https://support.microsoft.com/en-us/help/4475776', '2019-01-23', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 5'), - (13, 5233, 'SP2 CU4', 'https://support.microsoft.com/en-us/help/4464106', '2018-11-13', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 4'), - (13, 5216, 'SP2 CU3', 'https://support.microsoft.com/en-us/help/4458871', '2018-09-20', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 3'), - (13, 5201, 'SP2 CU2 + Security Update', 'https://support.microsoft.com/en-us/help/4458621', '2018-08-21', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 2 + Security Update'), - (13, 5153, 'SP2 CU2', 'https://support.microsoft.com/en-us/help/4340355', '2018-07-16', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 2'), - (13, 5149, 'SP2 CU1', 'https://support.microsoft.com/en-us/help/4135048', '2018-05-30', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 1'), - (13, 5103, 'SP2 GDR', 'https://support.microsoft.com/en-us/help/4583460', '2021-01-12', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 GDR'), - (13, 5026, 'SP2 ', 'https://support.microsoft.com/en-us/help/4052908', '2018-04-24', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 '), - (13, 4574, 'SP1 CU15', 'https://support.microsoft.com/en-us/help/4495257', '2019-05-16', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 15'), - (13, 4560, 'SP1 CU14', 'https://support.microsoft.com/en-us/help/4488535', '2019-03-19', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 14'), - (13, 4550, 'SP1 CU13', 'https://support.microsoft.com/en-us/help/4475775', '2019-01-23', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 13'), - (13, 4541, 'SP1 CU12', 'https://support.microsoft.com/en-us/help/4464343', '2018-11-13', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 12'), - (13, 4528, 'SP1 CU11', 'https://support.microsoft.com/en-us/help/4459676', '2018-09-17', '2019-07-09', '2019-07-09', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 11'), - (13, 4514, 'SP1 CU10', 'https://support.microsoft.com/en-us/help/4341569', '2018-07-16', '2019-07-09', '2019-07-09', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 10'), - (13, 4502, 'SP1 CU9', 'https://support.microsoft.com/en-us/help/4100997', '2018-05-30', '2019-07-09', '2019-07-09', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 9'), - (13, 4474, 'SP1 CU8', 'https://support.microsoft.com/en-us/help/4077064', '2018-03-19', '2019-07-09', '2019-07-09', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 8'), - (13, 4466, 'SP1 CU7', 'https://support.microsoft.com/en-us/help/4057119', '2018-01-04', '2019-07-09', '2019-07-09', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 7'), - (13, 4457, 'SP1 CU6', 'https://support.microsoft.com/en-us/help/4037354', '2017-11-20', '2019-07-09', '2019-07-09', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 6'), - (13, 4451, 'SP1 CU5', 'https://support.microsoft.com/en-us/help/4024305', '2017-09-18', '2019-07-09', '2019-07-09', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 5'), - (13, 4446, 'SP1 CU4', 'https://support.microsoft.com/en-us/help/4024305', '2017-08-08', '2019-07-09', '2019-07-09', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 4'), - (13, 4435, 'SP1 CU3', 'https://support.microsoft.com/en-us/help/4019916', '2017-05-15', '2019-07-09', '2019-07-09', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 3'), - (13, 4422, 'SP1 CU2', 'https://support.microsoft.com/en-us/help/4013106', '2017-03-20', '2019-07-09', '2019-07-09', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 2'), - (13, 4411, 'SP1 CU1', 'https://support.microsoft.com/en-us/help/3208177', '2017-01-17', '2019-07-09', '2019-07-09', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 1'), - (13, 4224, 'SP1 CU10 + Security Update', 'https://support.microsoft.com/en-us/help/4458842', '2018-08-22', '2019-07-09', '2019-07-09', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 10 + Security Update'), - (13, 4001, 'SP1 ', 'https://support.microsoft.com/en-us/help/3182545 ', '2016-11-16', '2019-07-09', '2019-07-09', 'SQL Server 2016', 'Service Pack 1 '), - (13, 2216, 'RTM CU9', 'https://support.microsoft.com/en-us/help/4037357', '2017-11-20', '2018-01-09', '2018-01-09', 'SQL Server 2016', 'RTM Cumulative Update 9'), - (13, 2213, 'RTM CU8', 'https://support.microsoft.com/en-us/help/4024304', '2017-09-18', '2018-01-09', '2018-01-09', 'SQL Server 2016', 'RTM Cumulative Update 8'), - (13, 2210, 'RTM CU7', 'https://support.microsoft.com/en-us/help/4024304', '2017-08-08', '2018-01-09', '2018-01-09', 'SQL Server 2016', 'RTM Cumulative Update 7'), - (13, 2204, 'RTM CU6', 'https://support.microsoft.com/en-us/help/4019914', '2017-05-15', '2018-01-09', '2018-01-09', 'SQL Server 2016', 'RTM Cumulative Update 6'), - (13, 2197, 'RTM CU5', 'https://support.microsoft.com/en-us/help/4013105', '2017-03-20', '2018-01-09', '2018-01-09', 'SQL Server 2016', 'RTM Cumulative Update 5'), - (13, 2193, 'RTM CU4', 'https://support.microsoft.com/en-us/help/3205052 ', '2017-01-17', '2018-01-09', '2018-01-09', 'SQL Server 2016', 'RTM Cumulative Update 4'), - (13, 2186, 'RTM CU3', 'https://support.microsoft.com/en-us/help/3205413 ', '2016-11-16', '2018-01-09', '2018-01-09', 'SQL Server 2016', 'RTM Cumulative Update 3'), - (13, 2164, 'RTM CU2', 'https://support.microsoft.com/en-us/help/3182270 ', '2016-09-22', '2018-01-09', '2018-01-09', 'SQL Server 2016', 'RTM Cumulative Update 2'), - (13, 2149, 'RTM CU1', 'https://support.microsoft.com/en-us/help/3164674 ', '2016-07-25', '2018-01-09', '2018-01-09', 'SQL Server 2016', 'RTM Cumulative Update 1'), - (13, 1601, 'RTM ', '', '2016-06-01', '2019-01-09', '2019-01-09', 'SQL Server 2016', 'RTM '), - (12, 6433, 'SP3 CU4 GDR', 'https://support.microsoft.com/en-us/help/4583462', '2021-01-12', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 4 GDR'), - (12, 6372, 'SP3 CU4 GDR', 'https://support.microsoft.com/en-us/help/4535288', '2020-02-11', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 4 GDR'), - (12, 6329, 'SP3 CU4', 'https://support.microsoft.com/en-us/help/4500181', '2019-07-29', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 4'), - (12, 6259, 'SP3 CU3', 'https://support.microsoft.com/en-us/help/4491539', '2019-04-16', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 3'), - (12, 6214, 'SP3 CU2', 'https://support.microsoft.com/en-us/help/4482960', '2019-02-19', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 2'), - (12, 6205, 'SP3 CU1', 'https://support.microsoft.com/en-us/help/4470220', '2018-12-12', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 1'), - (12, 6164, 'SP3 GDR', 'https://support.microsoft.com/en-us/help/4583463', '2021-01-12', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 GDR'), - (12, 6024, 'SP3 ', 'https://support.microsoft.com/en-us/help/4022619', '2018-10-30', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 '), - (12, 5687, 'SP2 CU18', 'https://support.microsoft.com/en-us/help/4500180', '2019-07-29', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 18'), - (12, 5632, 'SP2 CU17', 'https://support.microsoft.com/en-us/help/4491540', '2019-04-16', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 17'), - (12, 5626, 'SP2 CU16', 'https://support.microsoft.com/en-us/help/4482967', '2019-02-19', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 16'), - (12, 5605, 'SP2 CU15', 'https://support.microsoft.com/en-us/help/4469137', '2018-12-12', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 15'), - (12, 5600, 'SP2 CU14', 'https://support.microsoft.com/en-us/help/4459860', '2018-10-15', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 14'), - (12, 5590, 'SP2 CU13', 'https://support.microsoft.com/en-us/help/4456287', '2018-08-27', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 13'), - (12, 5589, 'SP2 CU12', 'https://support.microsoft.com/en-us/help/4130489', '2018-06-18', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 12'), - (12, 5579, 'SP2 CU11', 'https://support.microsoft.com/en-us/help/4077063', '2018-03-19', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 11'), - (12, 5571, 'SP2 CU10', 'https://support.microsoft.com/en-us/help/4052725', '2018-01-16', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 10'), - (12, 5563, 'SP2 CU9', 'https://support.microsoft.com/en-us/help/4055557', '2017-12-18', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 9'), - (12, 5557, 'SP2 CU8', 'https://support.microsoft.com/en-us/help/4037356', '2017-10-16', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 8'), - (12, 5556, 'SP2 CU7', 'https://support.microsoft.com/en-us/help/4032541', '2017-08-28', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 7'), - (12, 5553, 'SP2 CU6', 'https://support.microsoft.com/en-us/help/4019094', '2017-08-08', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 6'), - (12, 5546, 'SP2 CU5', 'https://support.microsoft.com/en-us/help/4013098', '2017-04-17', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 5'), - (12, 5540, 'SP2 CU4', 'https://support.microsoft.com/en-us/help/4010394', '2017-02-21', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 4'), - (12, 5538, 'SP2 CU3', 'https://support.microsoft.com/en-us/help/3204388 ', '2016-12-19', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 3'), - (12, 5522, 'SP2 CU2', 'https://support.microsoft.com/en-us/help/3188778 ', '2016-10-17', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 2'), - (12, 5511, 'SP2 CU1', 'https://support.microsoft.com/en-us/help/3178925 ', '2016-08-25', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 1'), - (12, 5000, 'SP2 ', 'https://support.microsoft.com/en-us/help/3171021 ', '2016-07-11', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 '), - (12, 4522, 'SP1 CU13', 'https://support.microsoft.com/en-us/help/4019099', '2017-08-08', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 Cumulative Update 13'), - (12, 4511, 'SP1 CU12', 'https://support.microsoft.com/en-us/help/4017793', '2017-04-17', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 Cumulative Update 12'), - (12, 4502, 'SP1 CU11', 'https://support.microsoft.com/en-us/help/4010392', '2017-02-21', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 Cumulative Update 11'), - (12, 4491, 'SP1 CU10', 'https://support.microsoft.com/en-us/help/3204399 ', '2016-12-19', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 Cumulative Update 10'), - (12, 4474, 'SP1 CU9', 'https://support.microsoft.com/en-us/help/3186964 ', '2016-10-17', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 Cumulative Update 9'), - (12, 4468, 'SP1 CU8', 'https://support.microsoft.com/en-us/help/3174038 ', '2016-08-15', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 Cumulative Update 8'), - (12, 4459, 'SP1 CU7', 'https://support.microsoft.com/en-us/help/3162659 ', '2016-06-20', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 Cumulative Update 7'), - (12, 4457, 'SP1 CU6', 'https://support.microsoft.com/en-us/help/3167392 ', '2016-05-30', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 Cumulative Update 6'), - (12, 4449, 'SP1 CU6', 'https://support.microsoft.com/en-us/help/3144524', '2016-04-18', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 Cumulative Update 6'), - (12, 4438, 'SP1 CU5', 'https://support.microsoft.com/en-us/help/3130926', '2016-02-22', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 Cumulative Update 5'), - (12, 4436, 'SP1 CU4', 'https://support.microsoft.com/en-us/help/3106660', '2015-12-21', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 Cumulative Update 4'), - (12, 4427, 'SP1 CU3', 'https://support.microsoft.com/en-us/help/3094221', '2015-10-19', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 Cumulative Update 3'), - (12, 4422, 'SP1 CU2', 'https://support.microsoft.com/en-us/help/3075950', '2015-08-17', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 Cumulative Update 2'), - (12, 4416, 'SP1 CU1', 'https://support.microsoft.com/en-us/help/3067839', '2015-06-19', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 Cumulative Update 1'), - (12, 4213, 'SP1 MS15-058: GDR Security Update', 'https://support.microsoft.com/en-us/help/3070446', '2015-07-14', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 MS15-058: GDR Security Update'), - (12, 4100, 'SP1 ', 'https://support.microsoft.com/en-us/help/3058865', '2015-05-04', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 '), - (12, 2569, 'RTM CU14', 'https://support.microsoft.com/en-us/help/3158271 ', '2016-06-20', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM Cumulative Update 14'), - (12, 2568, 'RTM CU13', 'https://support.microsoft.com/en-us/help/3144517', '2016-04-18', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM Cumulative Update 13'), - (12, 2564, 'RTM CU12', 'https://support.microsoft.com/en-us/help/3130923', '2016-02-22', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM Cumulative Update 12'), - (12, 2560, 'RTM CU11', 'https://support.microsoft.com/en-us/help/3106659', '2015-12-21', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM Cumulative Update 11'), - (12, 2556, 'RTM CU10', 'https://support.microsoft.com/en-us/help/3094220', '2015-10-19', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM Cumulative Update 10'), - (12, 2553, 'RTM CU9', 'https://support.microsoft.com/en-us/help/3075949', '2015-08-17', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM Cumulative Update 9'), - (12, 2548, 'RTM MS15-058: QFE Security Update', 'https://support.microsoft.com/en-us/help/3045323', '2015-07-14', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM MS15-058: QFE Security Update'), - (12, 2546, 'RTM CU8', 'https://support.microsoft.com/en-us/help/3067836', '2015-06-19', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM Cumulative Update 8'), - (12, 2495, 'RTM CU7', 'https://support.microsoft.com/en-us/help/3046038', '2015-04-20', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM Cumulative Update 7'), - (12, 2480, 'RTM CU6', 'https://support.microsoft.com/en-us/help/3031047', '2015-02-16', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM Cumulative Update 6'), - (12, 2456, 'RTM CU5', 'https://support.microsoft.com/en-us/help/3011055', '2014-12-17', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM Cumulative Update 5'), - (12, 2430, 'RTM CU4', 'https://support.microsoft.com/en-us/help/2999197', '2014-10-21', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM Cumulative Update 4'), - (12, 2402, 'RTM CU3', 'https://support.microsoft.com/en-us/help/2984923', '2014-08-18', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM Cumulative Update 3'), - (12, 2381, 'RTM MS14-044: QFE Security Update', 'https://support.microsoft.com/en-us/help/2977316', '2014-08-12', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM MS14-044: QFE Security Update'), - (12, 2370, 'RTM CU2', 'https://support.microsoft.com/en-us/help/2967546', '2014-06-27', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM Cumulative Update 2'), - (12, 2342, 'RTM CU1', 'https://support.microsoft.com/en-us/help/2931693', '2014-04-21', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM Cumulative Update 1'), - (12, 2269, 'RTM MS15-058: GDR Security Update ', 'https://support.microsoft.com/en-us/help/3045324', '2015-07-14', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM MS15-058: GDR Security Update '), - (12, 2254, 'RTM MS14-044: GDR Security Update', 'https://support.microsoft.com/en-us/help/2977315', '2014-08-12', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM MS14-044: GDR Security Update'), - (12, 2000, 'RTM ', '', '2014-04-01', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM '), - (11, 7507, 'SP4 GDR Security Update', 'https://support.microsoft.com/en-us/help/4583465', '2021-01-12', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'Service Pack 4 GDR Security Update for CVE-2021-1636'), - (11, 7493, 'SP4 GDR Security Update', 'https://support.microsoft.com/en-us/help/4532098', '2020-02-11', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'Service Pack 4 GDR Security Update for CVE-2020-0618'), - (11, 7469, 'SP4 On-Demand Hotfix Update', 'https://support.microsoft.com/en-us/help/4091266', '2018-03-28', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'Service Pack 4 SP4 On-Demand Hotfix Update'), - (11, 7462, 'SP4 ADV180002: GDR Security Update', 'https://support.microsoft.com/en-us/help/4057116', '2018-01-12', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'Service Pack 4 ADV180002: GDR Security Update'), - (11, 7001, 'SP4 ', 'https://support.microsoft.com/en-us/help/4018073', '2017-10-02', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'Service Pack 4 '), - (11, 6607, 'SP3 CU10', 'https://support.microsoft.com/en-us/help/4025925', '2017-08-08', '2018-10-09', '2018-10-09', 'SQL Server 2012', 'Service Pack 3 Cumulative Update 10'), - (11, 6598, 'SP3 CU9', 'https://support.microsoft.com/en-us/help/4016762', '2017-05-15', '2018-10-09', '2018-10-09', 'SQL Server 2012', 'Service Pack 3 Cumulative Update 9'), - (11, 6594, 'SP3 CU8', 'https://support.microsoft.com/en-us/help/3205051 ', '2017-03-20', '2018-10-09', '2018-10-09', 'SQL Server 2012', 'Service Pack 3 Cumulative Update 8'), - (11, 6579, 'SP3 CU7', 'https://support.microsoft.com/en-us/help/3205051 ', '2017-01-17', '2018-10-09', '2018-10-09', 'SQL Server 2012', 'Service Pack 3 Cumulative Update 7'), - (11, 6567, 'SP3 CU6', 'https://support.microsoft.com/en-us/help/3194992 ', '2016-11-17', '2018-10-09', '2018-10-09', 'SQL Server 2012', 'Service Pack 3 Cumulative Update 6'), - (11, 6544, 'SP3 CU5', 'https://support.microsoft.com/en-us/help/3180915 ', '2016-09-19', '2018-10-09', '2018-10-09', 'SQL Server 2012', 'Service Pack 3 Cumulative Update 5'), - (11, 6540, 'SP3 CU4', 'https://support.microsoft.com/en-us/help/3165264 ', '2016-07-18', '2018-10-09', '2018-10-09', 'SQL Server 2012', 'Service Pack 3 Cumulative Update 4'), - (11, 6537, 'SP3 CU3', 'https://support.microsoft.com/en-us/help/3152635 ', '2016-05-16', '2018-10-09', '2018-10-09', 'SQL Server 2012', 'Service Pack 3 Cumulative Update 3'), - (11, 6523, 'SP3 CU2', 'https://support.microsoft.com/en-us/help/3137746', '2016-03-21', '2018-10-09', '2018-10-09', 'SQL Server 2012', 'Service Pack 3 Cumulative Update 2'), - (11, 6518, 'SP3 CU1', 'https://support.microsoft.com/en-us/help/3123299', '2016-01-19', '2018-10-09', '2018-10-09', 'SQL Server 2012', 'Service Pack 3 Cumulative Update 1'), - (11, 6020, 'SP3 ', 'https://support.microsoft.com/en-us/help/3072779', '2015-11-20', '2018-10-09', '2018-10-09', 'SQL Server 2012', 'Service Pack 3 '), - (11, 5678, 'SP2 CU16', 'https://support.microsoft.com/en-us/help/3205416 ', '2016-11-17', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 16'), - (11, 5676, 'SP2 CU15', 'https://support.microsoft.com/en-us/help/3205416 ', '2016-11-17', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 15'), - (11, 5657, 'SP2 CU14', 'https://support.microsoft.com/en-us/help/3180914 ', '2016-09-19', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 14'), - (11, 5655, 'SP2 CU13', 'https://support.microsoft.com/en-us/help/3165266 ', '2016-07-18', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 13'), - (11, 5649, 'SP2 CU12', 'https://support.microsoft.com/en-us/help/3152637 ', '2016-05-16', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 12'), - (11, 5646, 'SP2 CU11', 'https://support.microsoft.com/en-us/help/3137745', '2016-03-21', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 11'), - (11, 5644, 'SP2 CU10', 'https://support.microsoft.com/en-us/help/3120313', '2016-01-19', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 10'), - (11, 5641, 'SP2 CU9', 'https://support.microsoft.com/en-us/help/3098512', '2015-11-16', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 9'), - (11, 5634, 'SP2 CU8', 'https://support.microsoft.com/en-us/help/3082561', '2015-09-21', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 8'), - (11, 5623, 'SP2 CU7', 'https://support.microsoft.com/en-us/help/3072100', '2015-07-20', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 7'), - (11, 5613, 'SP2 MS15-058: QFE Security Update', 'https://support.microsoft.com/en-us/help/3045319', '2015-07-14', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 MS15-058: QFE Security Update'), - (11, 5592, 'SP2 CU6', 'https://support.microsoft.com/en-us/help/3052468', '2015-05-18', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 6'), - (11, 5582, 'SP2 CU5', 'https://support.microsoft.com/en-us/help/3037255', '2015-03-16', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 5'), - (11, 5569, 'SP2 CU4', 'https://support.microsoft.com/en-us/help/3007556', '2015-01-19', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 4'), - (11, 5556, 'SP2 CU3', 'https://support.microsoft.com/en-us/help/3002049', '2014-11-17', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 3'), - (11, 5548, 'SP2 CU2', 'https://support.microsoft.com/en-us/help/2983175', '2014-09-15', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 2'), - (11, 5532, 'SP2 CU1', 'https://support.microsoft.com/en-us/help/2976982', '2014-07-23', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 1'), - (11, 5343, 'SP2 MS15-058: GDR Security Update', 'https://support.microsoft.com/en-us/help/3045321', '2015-07-14', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 MS15-058: GDR Security Update'), - (11, 5058, 'SP2 ', 'https://support.microsoft.com/en-us/help/2958429', '2014-06-10', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 '), - (11, 3513, 'SP1 MS15-058: QFE Security Update', 'https://support.microsoft.com/en-us/help/3045317', '2015-07-14', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 MS15-058: QFE Security Update'), - (11, 3482, 'SP1 CU13', 'https://support.microsoft.com/en-us/help/3002044', '2014-11-17', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 Cumulative Update 13'), - (11, 3470, 'SP1 CU12', 'https://support.microsoft.com/en-us/help/2991533', '2014-09-15', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 Cumulative Update 12'), - (11, 3460, 'SP1 MS14-044: QFE Security Update ', 'https://support.microsoft.com/en-us/help/2977325', '2014-08-12', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 MS14-044: QFE Security Update '), - (11, 3449, 'SP1 CU11', 'https://support.microsoft.com/en-us/help/2975396', '2014-07-21', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 Cumulative Update 11'), - (11, 3431, 'SP1 CU10', 'https://support.microsoft.com/en-us/help/2954099', '2014-05-19', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 Cumulative Update 10'), - (11, 3412, 'SP1 CU9', 'https://support.microsoft.com/en-us/help/2931078', '2014-03-17', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 Cumulative Update 9'), - (11, 3401, 'SP1 CU8', 'https://support.microsoft.com/en-us/help/2917531', '2014-01-20', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 Cumulative Update 8'), - (11, 3393, 'SP1 CU7', 'https://support.microsoft.com/en-us/help/2894115', '2013-11-18', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 Cumulative Update 7'), - (11, 3381, 'SP1 CU6', 'https://support.microsoft.com/en-us/help/2874879', '2013-09-16', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 Cumulative Update 6'), - (11, 3373, 'SP1 CU5', 'https://support.microsoft.com/en-us/help/2861107', '2013-07-15', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 Cumulative Update 5'), - (11, 3368, 'SP1 CU4', 'https://support.microsoft.com/en-us/help/2833645', '2013-05-30', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 Cumulative Update 4'), - (11, 3349, 'SP1 CU3', 'https://support.microsoft.com/en-us/help/2812412', '2013-03-18', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 Cumulative Update 3'), - (11, 3339, 'SP1 CU2', 'https://support.microsoft.com/en-us/help/2790947', '2013-01-21', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 Cumulative Update 2'), - (11, 3321, 'SP1 CU1', 'https://support.microsoft.com/en-us/help/2765331', '2012-11-20', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 Cumulative Update 1'), - (11, 3156, 'SP1 MS15-058: GDR Security Update', 'https://support.microsoft.com/en-us/help/3045318', '2015-07-14', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 MS15-058: GDR Security Update'), - (11, 3153, 'SP1 MS14-044: GDR Security Update', 'https://support.microsoft.com/en-us/help/2977326', '2014-08-12', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 MS14-044: GDR Security Update'), - (11, 3000, 'SP1 ', 'https://support.microsoft.com/en-us/help/2674319', '2012-11-07', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 '), - (11, 2424, 'RTM CU11', 'https://support.microsoft.com/en-us/help/2908007', '2013-12-16', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'RTM Cumulative Update 11'), - (11, 2420, 'RTM CU10', 'https://support.microsoft.com/en-us/help/2891666', '2013-10-21', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'RTM Cumulative Update 10'), - (11, 2419, 'RTM CU9', 'https://support.microsoft.com/en-us/help/2867319', '2013-08-20', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'RTM Cumulative Update 9'), - (11, 2410, 'RTM CU8', 'https://support.microsoft.com/en-us/help/2844205', '2013-06-17', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'RTM Cumulative Update 8'), - (11, 2405, 'RTM CU7', 'https://support.microsoft.com/en-us/help/2823247', '2013-04-15', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'RTM Cumulative Update 7'), - (11, 2401, 'RTM CU6', 'https://support.microsoft.com/en-us/help/2728897', '2013-02-18', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'RTM Cumulative Update 6'), - (11, 2395, 'RTM CU5', 'https://support.microsoft.com/en-us/help/2777772', '2012-12-17', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'RTM Cumulative Update 5'), - (11, 2383, 'RTM CU4', 'https://support.microsoft.com/en-us/help/2758687', '2012-10-15', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'RTM Cumulative Update 4'), - (11, 2376, 'RTM MS12-070: QFE Security Update', 'https://support.microsoft.com/en-us/help/2716441', '2012-10-09', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'RTM MS12-070: QFE Security Update'), - (11, 2332, 'RTM CU3', 'https://support.microsoft.com/en-us/help/2723749', '2012-08-31', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'RTM Cumulative Update 3'), - (11, 2325, 'RTM CU2', 'https://support.microsoft.com/en-us/help/2703275', '2012-06-18', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'RTM Cumulative Update 2'), - (11, 2316, 'RTM CU1', 'https://support.microsoft.com/en-us/help/2679368', '2012-04-12', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'RTM Cumulative Update 1'), - (11, 2218, 'RTM MS12-070: GDR Security Update', 'https://support.microsoft.com/en-us/help/2716442', '2012-10-09', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'RTM MS12-070: GDR Security Update'), - (11, 2100, 'RTM ', '', '2012-03-06', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'RTM '), - (10, 6529, 'SP3 MS15-058: QFE Security Update', 'https://support.microsoft.com/en-us/help/3045314', '2015-07-14', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'Service Pack 3 MS15-058: QFE Security Update'), - (10, 6220, 'SP3 MS15-058: QFE Security Update', 'https://support.microsoft.com/en-us/help/3045316', '2015-07-14', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'Service Pack 3 MS15-058: QFE Security Update'), - (10, 6000, 'SP3 ', 'https://support.microsoft.com/en-us/help/2979597', '2014-09-26', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'Service Pack 3 '), - (10, 4339, 'SP2 MS15-058: QFE Security Update', 'https://support.microsoft.com/en-us/help/3045312', '2015-07-14', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 MS15-058: QFE Security Update'), - (10, 4321, 'SP2 MS14-044: QFE Security Update', 'https://support.microsoft.com/en-us/help/2977319', '2014-08-14', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 MS14-044: QFE Security Update'), - (10, 4319, 'SP2 CU13', 'https://support.microsoft.com/en-us/help/2967540', '2014-06-30', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 Cumulative Update 13'), - (10, 4305, 'SP2 CU12', 'https://support.microsoft.com/en-us/help/2938478', '2014-04-21', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 Cumulative Update 12'), - (10, 4302, 'SP2 CU11', 'https://support.microsoft.com/en-us/help/2926028', '2014-02-18', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 Cumulative Update 11'), - (10, 4297, 'SP2 CU10', 'https://support.microsoft.com/en-us/help/2908087', '2013-12-17', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 Cumulative Update 10'), - (10, 4295, 'SP2 CU9', 'https://support.microsoft.com/en-us/help/2887606', '2013-10-28', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 Cumulative Update 9'), - (10, 4290, 'SP2 CU8', 'https://support.microsoft.com/en-us/help/2871401', '2013-08-22', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 Cumulative Update 8'), - (10, 4285, 'SP2 CU7', 'https://support.microsoft.com/en-us/help/2844090', '2013-06-17', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 Cumulative Update 7'), - (10, 4279, 'SP2 CU6', 'https://support.microsoft.com/en-us/help/2830140', '2013-04-15', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 Cumulative Update 6'), - (10, 4276, 'SP2 CU5', 'https://support.microsoft.com/en-us/help/2797460', '2013-02-18', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 Cumulative Update 5'), - (10, 4270, 'SP2 CU4', 'https://support.microsoft.com/en-us/help/2777358', '2012-12-17', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 Cumulative Update 4'), - (10, 4266, 'SP2 CU3', 'https://support.microsoft.com/en-us/help/2754552', '2012-10-15', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 Cumulative Update 3'), - (10, 4263, 'SP2 CU2', 'https://support.microsoft.com/en-us/help/2740411', '2012-08-31', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 Cumulative Update 2'), - (10, 4260, 'SP2 CU1', 'https://support.microsoft.com/en-us/help/2720425', '2012-07-24', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 Cumulative Update 1'), - (10, 4042, 'SP2 MS15-058: GDR Security Update', 'https://support.microsoft.com/en-us/help/3045313', '2015-07-14', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 MS15-058: GDR Security Update'), - (10, 4033, 'SP2 MS14-044: GDR Security Update', 'https://support.microsoft.com/en-us/help/2977320', '2014-08-12', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 MS14-044: GDR Security Update'), - (10, 4000, 'SP2 ', 'https://support.microsoft.com/en-us/help/2630458', '2012-07-26', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 '), - (10, 2881, 'SP1 CU14', 'https://support.microsoft.com/en-us/help/2868244', '2013-08-08', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 Cumulative Update 14'), - (10, 2876, 'SP1 CU13', 'https://support.microsoft.com/en-us/help/2855792', '2013-06-17', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 Cumulative Update 13'), - (10, 2874, 'SP1 CU12', 'https://support.microsoft.com/en-us/help/2828727', '2013-04-15', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 Cumulative Update 12'), - (10, 2869, 'SP1 CU11', 'https://support.microsoft.com/en-us/help/2812683', '2013-02-18', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 Cumulative Update 11'), - (10, 2868, 'SP1 CU10', 'https://support.microsoft.com/en-us/help/2783135', '2012-12-17', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 Cumulative Update 10'), - (10, 2866, 'SP1 CU9', 'https://support.microsoft.com/en-us/help/2756574', '2012-10-15', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 Cumulative Update 9'), - (10, 2861, 'SP1 MS12-070: QFE Security Update', 'https://support.microsoft.com/en-us/help/2716439', '2012-10-09', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 MS12-070: QFE Security Update'), - (10, 2822, 'SP1 CU8', 'https://support.microsoft.com/en-us/help/2723743', '2012-08-31', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 Cumulative Update 8'), - (10, 2817, 'SP1 CU7', 'https://support.microsoft.com/en-us/help/2703282', '2012-06-18', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 Cumulative Update 7'), - (10, 2811, 'SP1 CU6', 'https://support.microsoft.com/en-us/help/2679367', '2012-04-16', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 Cumulative Update 6'), - (10, 2806, 'SP1 CU5', 'https://support.microsoft.com/en-us/help/2659694', '2012-02-22', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 Cumulative Update 5'), - (10, 2796, 'SP1 CU4', 'https://support.microsoft.com/en-us/help/2633146', '2011-12-19', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 Cumulative Update 4'), - (10, 2789, 'SP1 CU3', 'https://support.microsoft.com/en-us/help/2591748', '2011-10-17', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 Cumulative Update 3'), - (10, 2772, 'SP1 CU2', 'https://support.microsoft.com/en-us/help/2567714', '2011-08-15', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 Cumulative Update 2'), - (10, 2769, 'SP1 CU1', 'https://support.microsoft.com/en-us/help/2544793', '2011-07-18', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 Cumulative Update 1'), - (10, 2550, 'SP1 MS12-070: GDR Security Update', 'https://support.microsoft.com/en-us/help/2754849', '2012-10-09', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 MS12-070: GDR Security Update'), - (10, 2500, 'SP1 ', 'https://support.microsoft.com/en-us/help/2528583', '2011-07-12', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 '), - (10, 1815, 'RTM CU13', 'https://support.microsoft.com/en-us/help/2679366', '2012-04-16', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM Cumulative Update 13'), - (10, 1810, 'RTM CU12', 'https://support.microsoft.com/en-us/help/2659692', '2012-02-21', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM Cumulative Update 12'), - (10, 1809, 'RTM CU11', 'https://support.microsoft.com/en-us/help/2633145', '2011-12-19', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM Cumulative Update 11'), - (10, 1807, 'RTM CU10', 'https://support.microsoft.com/en-us/help/2591746', '2011-10-17', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM Cumulative Update 10'), - (10, 1804, 'RTM CU9', 'https://support.microsoft.com/en-us/help/2567713', '2011-08-15', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM Cumulative Update 9'), - (10, 1797, 'RTM CU8', 'https://support.microsoft.com/en-us/help/2534352', '2011-06-20', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM Cumulative Update 8'), - (10, 1790, 'RTM MS11-049: QFE Security Update', 'https://support.microsoft.com/en-us/help/2494086', '2011-06-14', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM MS11-049: QFE Security Update'), - (10, 1777, 'RTM CU7', 'https://support.microsoft.com/en-us/help/2507770', '2011-04-18', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM Cumulative Update 7'), - (10, 1765, 'RTM CU6', 'https://support.microsoft.com/en-us/help/2489376', '2011-02-21', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM Cumulative Update 6'), - (10, 1753, 'RTM CU5', 'https://support.microsoft.com/en-us/help/2438347', '2010-12-20', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM Cumulative Update 5'), - (10, 1746, 'RTM CU4', 'https://support.microsoft.com/en-us/help/2345451', '2010-10-18', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM Cumulative Update 4'), - (10, 1734, 'RTM CU3', 'https://support.microsoft.com/en-us/help/2261464', '2010-08-16', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM Cumulative Update 3'), - (10, 1720, 'RTM CU2', 'https://support.microsoft.com/en-us/help/2072493', '2010-06-21', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM Cumulative Update 2'), - (10, 1702, 'RTM CU1', 'https://support.microsoft.com/en-us/help/981355', '2010-05-18', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM Cumulative Update 1'), - (10, 1617, 'RTM MS11-049: GDR Security Update', 'https://support.microsoft.com/en-us/help/2494088', '2011-06-14', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM MS11-049: GDR Security Update'), - (10, 1600, 'RTM ', '', '2010-05-10', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM '), - (10, 6535, 'SP3 MS15-058: QFE Security Update', 'https://support.microsoft.com/en-us/help/3045308', '2015-07-14', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 MS15-058: QFE Security Update'), - (10, 6241, 'SP3 MS15-058: GDR Security Update', 'https://support.microsoft.com/en-us/help/3045311', '2015-07-14', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 MS15-058: GDR Security Update'), - (10, 5890, 'SP3 MS15-058: QFE Security Update', 'https://support.microsoft.com/en-us/help/3045303', '2015-07-14', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 MS15-058: QFE Security Update'), - (10, 5869, 'SP3 MS14-044: QFE Security Update', 'https://support.microsoft.com/en-us/help/2984340, https://support.microsoft.com/en-us/help/2977322', '2014-08-12', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 MS14-044: QFE Security Update'), - (10, 5861, 'SP3 CU17', 'https://support.microsoft.com/en-us/help/2958696', '2014-05-19', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 17'), - (10, 5852, 'SP3 CU16', 'https://support.microsoft.com/en-us/help/2936421', '2014-03-17', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 16'), - (10, 5850, 'SP3 CU15', 'https://support.microsoft.com/en-us/help/2923520', '2014-01-20', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 15'), - (10, 5848, 'SP3 CU14', 'https://support.microsoft.com/en-us/help/2893410', '2013-11-18', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 14'), - (10, 5846, 'SP3 CU13', 'https://support.microsoft.com/en-us/help/2880350', '2013-09-16', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 13'), - (10, 5844, 'SP3 CU12', 'https://support.microsoft.com/en-us/help/2863205', '2013-07-15', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 12'), - (10, 5840, 'SP3 CU11', 'https://support.microsoft.com/en-us/help/2834048', '2013-05-20', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 11'), - (10, 5835, 'SP3 CU10', 'https://support.microsoft.com/en-us/help/2814783', '2013-03-18', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 10'), - (10, 5829, 'SP3 CU9', 'https://support.microsoft.com/en-us/help/2799883', '2013-01-21', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 9'), - (10, 5828, 'SP3 CU8', 'https://support.microsoft.com/en-us/help/2771833', '2012-11-19', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 8'), - (10, 5826, 'SP3 MS12-070: QFE Security Update', 'https://support.microsoft.com/en-us/help/2716435', '2012-10-09', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 MS12-070: QFE Security Update'), - (10, 5794, 'SP3 CU7', 'https://support.microsoft.com/en-us/help/2738350', '2012-09-17', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 7'), - (10, 5788, 'SP3 CU6', 'https://support.microsoft.com/en-us/help/2715953', '2012-07-16', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 6'), - (10, 5785, 'SP3 CU5', 'https://support.microsoft.com/en-us/help/2696626', '2012-05-21', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 5'), - (10, 5775, 'SP3 CU4', 'https://support.microsoft.com/en-us/help/2673383', '2012-03-19', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 4'), - (10, 5770, 'SP3 CU3', 'https://support.microsoft.com/en-us/help/2648098', '2012-01-16', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 3'), - (10, 5768, 'SP3 CU2', 'https://support.microsoft.com/en-us/help/2633143', '2011-11-21', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 2'), - (10, 5766, 'SP3 CU1', 'https://support.microsoft.com/en-us/help/2617146', '2011-10-17', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 1'), - (10, 5538, 'SP3 MS15-058: GDR Security Update', 'https://support.microsoft.com/en-us/help/3045305', '2015-07-14', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 MS15-058: GDR Security Update'), - (10, 5520, 'SP3 MS14-044: GDR Security Update', 'https://support.microsoft.com/en-us/help/2977321', '2014-08-12', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 MS14-044: GDR Security Update'), - (10, 5512, 'SP3 MS12-070: GDR Security Update', 'https://support.microsoft.com/en-us/help/2716436', '2012-10-09', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 MS12-070: GDR Security Update'), - (10, 5500, 'SP3 ', 'https://support.microsoft.com/en-us/help/2546951', '2011-10-06', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 '), - (10, 4371, 'SP2 MS12-070: QFE Security Update', 'https://support.microsoft.com/en-us/help/2716433', '2012-10-09', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 MS12-070: QFE Security Update'), - (10, 4333, 'SP2 CU11', 'https://support.microsoft.com/en-us/help/2715951', '2012-07-16', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 Cumulative Update 11'), - (10, 4332, 'SP2 CU10', 'https://support.microsoft.com/en-us/help/2696625', '2012-05-21', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 Cumulative Update 10'), - (10, 4330, 'SP2 CU9', 'https://support.microsoft.com/en-us/help/2673382', '2012-03-19', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 Cumulative Update 9'), - (10, 4326, 'SP2 CU8', 'https://support.microsoft.com/en-us/help/2648096', '2012-01-16', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 Cumulative Update 8'), - (10, 4323, 'SP2 CU7', 'https://support.microsoft.com/en-us/help/2617148', '2011-11-21', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 Cumulative Update 7'), - (10, 4321, 'SP2 CU6', 'https://support.microsoft.com/en-us/help/2582285', '2011-09-19', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 Cumulative Update 6'), - (10, 4316, 'SP2 CU5', 'https://support.microsoft.com/en-us/help/2555408', '2011-07-18', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 Cumulative Update 5'), - (10, 4311, 'SP2 MS11-049: QFE Security Update', 'https://support.microsoft.com/en-us/help/2494094', '2011-06-14', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 MS11-049: QFE Security Update'), - (10, 4285, 'SP2 CU4', 'https://support.microsoft.com/en-us/help/2527180', '2011-05-16', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 Cumulative Update 4'), - (10, 4279, 'SP2 CU3', 'https://support.microsoft.com/en-us/help/2498535', '2011-03-17', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 Cumulative Update 3'), - (10, 4272, 'SP2 CU2', 'https://support.microsoft.com/en-us/help/2467239', '2011-01-17', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 Cumulative Update 2'), - (10, 4266, 'SP2 CU1', 'https://support.microsoft.com/en-us/help/2289254', '2010-11-15', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 Cumulative Update 1'), - (10, 4067, 'SP2 MS12-070: GDR Security Update', 'https://support.microsoft.com/en-us/help/2716434', '2012-10-09', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 MS12-070: GDR Security Update'), - (10, 4064, 'SP2 MS11-049: GDR Security Update', 'https://support.microsoft.com/en-us/help/2494089', '2011-06-14', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 MS11-049: GDR Security Update'), - (10, 4000, 'SP2 ', 'https://support.microsoft.com/en-us/help/2285068', '2010-09-29', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 '), - (10, 2850, 'SP1 CU16', 'https://support.microsoft.com/en-us/help/2582282', '2011-09-19', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 16'), - (10, 2847, 'SP1 CU15', 'https://support.microsoft.com/en-us/help/2555406', '2011-07-18', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 15'), - (10, 2841, 'SP1 MS11-049: QFE Security Update', 'https://support.microsoft.com/en-us/help/2494100', '2011-06-14', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 MS11-049: QFE Security Update'), - (10, 2821, 'SP1 CU14', 'https://support.microsoft.com/en-us/help/2527187', '2011-05-16', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 14'), - (10, 2816, 'SP1 CU13', 'https://support.microsoft.com/en-us/help/2497673', '2011-03-17', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 13'), - (10, 2808, 'SP1 CU12', 'https://support.microsoft.com/en-us/help/2467236', '2011-01-17', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 12'), - (10, 2804, 'SP1 CU11', 'https://support.microsoft.com/en-us/help/2413738', '2010-11-15', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 11'), - (10, 2799, 'SP1 CU10', 'https://support.microsoft.com/en-us/help/2279604', '2010-09-20', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 10'), - (10, 2789, 'SP1 CU9', 'https://support.microsoft.com/en-us/help/2083921', '2010-07-19', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 9'), - (10, 2775, 'SP1 CU8', 'https://support.microsoft.com/en-us/help/981702', '2010-05-17', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 8'), - (10, 2766, 'SP1 CU7', 'https://support.microsoft.com/en-us/help/979065', '2010-03-26', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 7'), - (10, 2757, 'SP1 CU6', 'https://support.microsoft.com/en-us/help/977443', '2010-01-18', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 6'), - (10, 2746, 'SP1 CU5', 'https://support.microsoft.com/en-us/help/975977', '2009-11-16', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 5'), - (10, 2734, 'SP1 CU4', 'https://support.microsoft.com/en-us/help/973602', '2009-09-21', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 4'), - (10, 2723, 'SP1 CU3', 'https://support.microsoft.com/en-us/help/971491', '2009-07-20', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 3'), - (10, 2714, 'SP1 CU2', 'https://support.microsoft.com/en-us/help/970315', '2009-05-18', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 2'), - (10, 2710, 'SP1 CU1', 'https://support.microsoft.com/en-us/help/969099', '2009-04-16', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 1'), - (10, 2573, 'SP1 MS11-049: GDR Security update', 'https://support.microsoft.com/en-us/help/2494096', '2011-06-14', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 MS11-049: GDR Security update'), - (10, 2531, 'SP1 ', '', '2009-04-01', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 '), - (10, 1835, 'RTM CU10', 'https://support.microsoft.com/en-us/help/979064', '2010-03-15', '2014-07-08', '2019-07-09', 'SQL Server 2008', 'RTM Cumulative Update 10'), - (10, 1828, 'RTM CU9', 'https://support.microsoft.com/en-us/help/977444', '2010-01-18', '2014-07-08', '2019-07-09', 'SQL Server 2008', 'RTM Cumulative Update 9'), - (10, 1823, 'RTM CU8', 'https://support.microsoft.com/en-us/help/975976', '2009-11-16', '2014-07-08', '2019-07-09', 'SQL Server 2008', 'RTM Cumulative Update 8'), - (10, 1818, 'RTM CU7', 'https://support.microsoft.com/en-us/help/973601', '2009-09-21', '2014-07-08', '2019-07-09', 'SQL Server 2008', 'RTM Cumulative Update 7'), - (10, 1812, 'RTM CU6', 'https://support.microsoft.com/en-us/help/971490', '2009-07-20', '2014-07-08', '2019-07-09', 'SQL Server 2008', 'RTM Cumulative Update 6'), - (10, 1806, 'RTM CU5', 'https://support.microsoft.com/en-us/help/969531', '2009-05-18', '2014-07-08', '2019-07-09', 'SQL Server 2008', 'RTM Cumulative Update 5'), - (10, 1798, 'RTM CU4', 'https://support.microsoft.com/en-us/help/963036', '2009-03-16', '2014-07-08', '2019-07-09', 'SQL Server 2008', 'RTM Cumulative Update 4'), - (10, 1787, 'RTM CU3', 'https://support.microsoft.com/en-us/help/960484', '2009-01-19', '2014-07-08', '2019-07-09', 'SQL Server 2008', 'RTM Cumulative Update 3'), - (10, 1779, 'RTM CU2', 'https://support.microsoft.com/en-us/help/958186', '2008-11-19', '2014-07-08', '2019-07-09', 'SQL Server 2008', 'RTM Cumulative Update 2'), - (10, 1763, 'RTM CU1', 'https://support.microsoft.com/en-us/help/956717', '2008-09-22', '2014-07-08', '2019-07-09', 'SQL Server 2008', 'RTM Cumulative Update 1'), - (10, 1600, 'RTM ', '', '2008-08-06', '2014-07-08', '2019-07-09', 'SQL Server 2008', 'RTM ') -; -GO + +/* How to run it: +EXEC dbo.sp_BlitzFirst + +With extra diagnostic info: +EXEC dbo.sp_BlitzFirst @ExpertMode = 1; + +Saving output to tables: +EXEC sp_BlitzFirst + @OutputDatabaseName = 'DBAtools' +, @OutputSchemaName = 'dbo' +, @OutputTableName = 'BlitzFirst' +, @OutputTableNameFileStats = 'BlitzFirst_FileStats' +, @OutputTableNamePerfmonStats = 'BlitzFirst_PerfmonStats' +, @OutputTableNameWaitStats = 'BlitzFirst_WaitStats' +, @OutputTableNameBlitzCache = 'BlitzCache' +, @OutputTableNameBlitzWho = 'BlitzWho' +*/ From 1b267f07225f67c386f494e4cb58d1832d6389fc Mon Sep 17 00:00:00 2001 From: Devendra Singh Date: Tue, 23 Feb 2021 16:03:35 +0400 Subject: [PATCH 114/662] Update README.md Change the parameters to reflect the ones in SP --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index cc1837752..2f7c05882 100644 --- a/README.md +++ b/README.md @@ -398,8 +398,8 @@ Get information for the last hour from all sp_BlitzFirst output tables ```SQL EXEC sp_BlitzAnalysis - @FromDate = NULL, - @ToDate = NULL, + @StartDate = NULL, + @EndDate = NULL, @OutputDatabaseName = 'DBAtools', @OutputSchemaName = 'dbo', @OutputTableNameFileStats = N'BlitzFirst_FileStats', @@ -413,8 +413,8 @@ Exclude specific tables e.g lets exclude PerfmonStats by setting to NULL, no loo ```SQL EXEC sp_BlitzAnalysis - @FromDate = NULL, - @ToDate = NULL, + @StartDate = NULL, + @EndDate = NULL, @OutputDatabaseName = 'DBAtools', @OutputSchemaName = 'Blitz', @OutputTableNameFileStats = N'BlitzFirst_FileStats', From 690c4d9a3925e5a8b3f20550f28b5c5625d7e54f Mon Sep 17 00:00:00 2001 From: Kendall Lister Date: Thu, 25 Feb 2021 12:34:29 +1100 Subject: [PATCH 115/662] Added an omitted word to a UI message in sp_Blitz --- sp_Blitz.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index afaecc1e8..217f5f851 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -5747,7 +5747,7 @@ IF @ProductVersionMajor >= 10 'DBCC SHRINK% Ran Recently' AS Finding , 'https://www.BrentOzar.com/go/dbcc' AS URL , 'The user ' + COALESCE(d.nt_user_name, d.login_name) + ' has run file shrinks ' + CAST(COUNT(*) AS NVARCHAR(100)) + ' times between ' + CONVERT(NVARCHAR(30), MIN(d.min_start_time)) + ' and ' + CONVERT(NVARCHAR(30), MAX(d.max_start_time)) + - '. So, uh, are they trying cause bad performance on purpose?' + '. So, uh, are they trying to cause bad performance on purpose?' AS Details FROM #dbcc_events_from_trace d WHERE d.dbcc_event_trunc_upper LIKE N'DBCC SHRINK%' From 100d3d2ffe6ea109849a9fb15f4c48d5f5e16fc8 Mon Sep 17 00:00:00 2001 From: fvanderhaegen Date: Mon, 1 Mar 2021 12:18:55 +0100 Subject: [PATCH 116/662] #2805 sp_DataBaseRestore FullText Catalog restore Added option to move FullText Catalog files during restore --- sp_DatabaseRestore.sql | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/sp_DatabaseRestore.sql b/sp_DatabaseRestore.sql index 7a9036fca..3c53eb6e3 100755 --- a/sp_DatabaseRestore.sql +++ b/sp_DatabaseRestore.sql @@ -11,6 +11,7 @@ ALTER PROCEDURE [dbo].[sp_DatabaseRestore] @MoveDataDrive NVARCHAR(260) = NULL, @MoveLogDrive NVARCHAR(260) = NULL, @MoveFilestreamDrive NVARCHAR(260) = NULL, + @MoveFullTextCatalogDrive NVARCHAR(260) = NULL, @BufferCount INT = NULL, @MaxTransferSize INT = NULL, @BlockSize INT = NULL, @@ -425,6 +426,22 @@ BEGIN IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Fixing @MoveFilestreamDrive to add a "/"', 0, 1) WITH NOWAIT; SET @MoveFilestreamDrive += N'/'; END; +/*Move FullText Catalog File*/ +IF NULLIF(@MoveFullTextCatalogDrive, '') IS NULL +BEGIN + IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Setting default data drive for @MoveFullTextCatalogDrive', 0, 1) WITH NOWAIT; + SET @MoveFullTextCatalogDrive = CAST(SERVERPROPERTY('InstanceDefaultDataPath') AS nvarchar(260)); +END; +IF (SELECT RIGHT(@MoveFullTextCatalogDrive, 1)) <> '\' AND CHARINDEX('\', @MoveFullTextCatalogDrive) > 0 --Has to end in a '\' +BEGIN + IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Fixing @MoveFullTextCatalogDrive to add a "\"', 0, 1) WITH NOWAIT; + SET @MoveFullTextCatalogDrive += N'\'; +END; +ELSE IF (SELECT RIGHT(@MoveFullTextCatalogDrive, 1)) <> '/' AND CHARINDEX('/', @MoveFullTextCatalogDrive) > 0 --Has to end in a '/' +BEGIN + IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Fixing @MoveFullTextCatalogDrive to add a "/"', 0, 1) WITH NOWAIT; + SET @MoveFullTextCatalogDrive += N'/'; +END; /*Standby Undo File*/ IF (SELECT RIGHT(@StandbyUndoPath, 1)) <> '\' AND CHARINDEX('\', @StandbyUndoPath) > 0 --Has to end in a '\' BEGIN @@ -727,6 +744,7 @@ BEGIN WHEN Type = 'D' THEN @MoveDataDrive WHEN Type = 'L' THEN @MoveLogDrive WHEN Type = 'S' THEN @MoveFilestreamDrive + WHEN Type = 'F' THEN @MoveFullTextCatalogDrive END + CASE WHEN @Database = @RestoreDatabaseName THEN REVERSE(LEFT(REVERSE(PhysicalName), CHARINDEX('\', REVERSE(PhysicalName), 1) -1)) ELSE REPLACE(REVERSE(LEFT(REVERSE(PhysicalName), CHARINDEX('\', REVERSE(PhysicalName), 1) -1)), @Database, SUBSTRING(@RestoreDatabaseName, 2, LEN(@RestoreDatabaseName) -2)) From 1f4f6ae146d69323133ffb9dfa9cac94ca0094e8 Mon Sep 17 00:00:00 2001 From: luke-mckee-ol Date: Tue, 2 Mar 2021 21:05:09 +1100 Subject: [PATCH 117/662] Update sp_BlitzCache.sql Ignore dbmaintenance dbadmin and dbatools --- sp_BlitzCache.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sp_BlitzCache.sql b/sp_BlitzCache.sql index 17d57b426..7b57365f0 100644 --- a/sp_BlitzCache.sql +++ b/sp_BlitzCache.sql @@ -1947,7 +1947,7 @@ SET @body += N' WHERE 1 = 1 ' + @nl ; IF @IgnoreSystemDBs = 1 BEGIN RAISERROR(N'Ignoring system databases by default', 0, 1) WITH NOWAIT; - SET @body += N' AND COALESCE(DB_NAME(CAST(xpa.value AS INT)), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'') AND COALESCE(DB_NAME(CAST(xpa.value AS INT)), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ; + SET @body += N' AND COALESCE(DB_NAME(CAST(xpa.value AS INT)), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'', ''dbmaintenance'', ''dbadmin'', ''dbatools'') AND COALESCE(DB_NAME(CAST(xpa.value AS INT)), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ; END; IF @DatabaseName IS NOT NULL OR @DatabaseName <> N'' From 1934c15cce65e7ec497542b8f5c6cbd076d31eaf Mon Sep 17 00:00:00 2001 From: luke-mckee-ol Date: Tue, 2 Mar 2021 21:15:19 +1100 Subject: [PATCH 118/662] blitzcache update help message --- sp_BlitzCache.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sp_BlitzCache.sql b/sp_BlitzCache.sql index 7b57365f0..1eb66187b 100644 --- a/sp_BlitzCache.sql +++ b/sp_BlitzCache.sql @@ -397,7 +397,7 @@ IF @Help = 1 UNION ALL SELECT N'@IgnoreSystemDBs', N'BIT', - N'Ignores plans found in the system databases (master, model, msdb, tempdb, and resourcedb)' + N'Ignores plans found in the system databases (master, model, msdb, tempdb, dbmaintenance, dbadmin, dbatools and resourcedb)' UNION ALL SELECT N'@OnlyQueryHashes', From 17a2e5fc66916b6d94206c2b8a573ca78536ff74 Mon Sep 17 00:00:00 2001 From: luke-mckee-ol Date: Tue, 2 Mar 2021 21:16:42 +1100 Subject: [PATCH 119/662] BlitzCache - missed spots --- sp_BlitzCache.sql | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sp_BlitzCache.sql b/sp_BlitzCache.sql index 1eb66187b..f119271e4 100644 --- a/sp_BlitzCache.sql +++ b/sp_BlitzCache.sql @@ -2382,7 +2382,7 @@ BEGIN SET @sql += @body_where ; IF @IgnoreSystemDBs = 1 - SET @sql += N' AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'') AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ; + SET @sql += N' AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'', ''dbmaintenance'', ''dbadmin'', ''dbatools'') AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ; SET @sql += @sort_filter + @nl; @@ -2414,7 +2414,7 @@ BEGIN SET @sql += @body_where ; IF @IgnoreSystemDBs = 1 - SET @sql += N' AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'') AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ; + SET @sql += N' AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'', ''dbmaintenance'', ''dbadmin'', ''dbatools'') AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ; SET @sql += @sort_filter + @nl; @@ -2450,7 +2450,7 @@ BEGIN SET @sql += @body_where ; IF @IgnoreSystemDBs = 1 - SET @sql += N' AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'') AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ; + SET @sql += N' AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'', ''dbmaintenance'', ''dbadmin'', ''dbatools'') AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ; SET @sql += @sort_filter + @nl; From 60fb5f17f624850a928a66d201e5db3aa87add1d Mon Sep 17 00:00:00 2001 From: luke-mckee-ol Date: Tue, 2 Mar 2021 21:20:41 +1100 Subject: [PATCH 120/662] Update README.md Include note about ignoring specifically named databases --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2f7c05882..901cfffce 100644 --- a/README.md +++ b/README.md @@ -179,7 +179,7 @@ Other common parameters include: * @Top = 10 - by default, you get 10 plans, but you can ask for more. Just know that the more you get, the slower it goes. * @ExportToExcel = 1 - turn this on, and it doesn't return XML fields that would hinder you from copy/pasting the data into Excel. * @ExpertMode = 1 - turn this on, and you get more columns with more data. Doesn't take longer to run though. -* @IgnoreSystemDBs = 0 - if you want to show queries in master/model/msdb. By default we hide these. +* @IgnoreSystemDBs = 0 - if you want to show queries in master/model/msdb. By default we hide these. Additionally hides queries from databases named `dbadmin`, `dbmaintenance`, and `dbatools`. * @MinimumExecutionCount = 0 - in servers like data warehouses where lots of queries only run a few times, you can set a floor number for examination. [*Back to top*](#header1) From 2095be096c3528e2a9ee0f2e9eaaf6e15614cd3b Mon Sep 17 00:00:00 2001 From: luke-mckee-ol Date: Tue, 2 Mar 2021 21:24:22 +1100 Subject: [PATCH 121/662] Update Install-All-Scripts.sql Update blitzcache in install all scripts with databases to ignore --- Install-All-Scripts.sql | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Install-All-Scripts.sql b/Install-All-Scripts.sql index af87b9405..e8f59f9e3 100755 --- a/Install-All-Scripts.sql +++ b/Install-All-Scripts.sql @@ -14191,7 +14191,7 @@ IF @Help = 1 UNION ALL SELECT N'@IgnoreSystemDBs', N'BIT', - N'Ignores plans found in the system databases (master, model, msdb, tempdb, and resourcedb)' + N'Ignores plans found in the system databases (master, model, msdb, tempdb, dbmaintenance, dbadmin, dbatools, and resourcedb)' UNION ALL SELECT N'@OnlyQueryHashes', @@ -15741,7 +15741,7 @@ SET @body += N' WHERE 1 = 1 ' + @nl ; IF @IgnoreSystemDBs = 1 BEGIN RAISERROR(N'Ignoring system databases by default', 0, 1) WITH NOWAIT; - SET @body += N' AND COALESCE(DB_NAME(CAST(xpa.value AS INT)), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'') AND COALESCE(DB_NAME(CAST(xpa.value AS INT)), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ; + SET @body += N' AND COALESCE(DB_NAME(CAST(xpa.value AS INT)), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'', ''dbmaintenance'', ''dbadmin'', ''dbatools'') AND COALESCE(DB_NAME(CAST(xpa.value AS INT)), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ; END; IF @DatabaseName IS NOT NULL OR @DatabaseName <> N'' @@ -16176,7 +16176,7 @@ BEGIN SET @sql += @body_where ; IF @IgnoreSystemDBs = 1 - SET @sql += N' AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'') AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ; + SET @sql += N' AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'', ''dbmaintenance'', ''dbadmin'', ''dbatools'') AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ; SET @sql += @sort_filter + @nl; @@ -16208,7 +16208,7 @@ BEGIN SET @sql += @body_where ; IF @IgnoreSystemDBs = 1 - SET @sql += N' AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'') AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ; + SET @sql += N' AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'', ''dbmaintenance'', ''dbadmin'', ''dbatools'') AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ; SET @sql += @sort_filter + @nl; @@ -16244,7 +16244,7 @@ BEGIN SET @sql += @body_where ; IF @IgnoreSystemDBs = 1 - SET @sql += N' AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'') AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ; + SET @sql += N' AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'', ''dbmaintenance'', ''dbadmin'', ''dbatools'') AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ; SET @sql += @sort_filter + @nl; From 6ee22740b3dfe8115a856883f954a3385118a4c7 Mon Sep 17 00:00:00 2001 From: luke-mckee-ol Date: Tue, 2 Mar 2021 21:27:03 +1100 Subject: [PATCH 122/662] Update Install-Core-Blitz-With-Query-Store.sql Make changes for ignoring commonly named databases --- Install-Core-Blitz-With-Query-Store.sql | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Install-Core-Blitz-With-Query-Store.sql b/Install-Core-Blitz-With-Query-Store.sql index 0f27b66ce..2e12810cd 100755 --- a/Install-Core-Blitz-With-Query-Store.sql +++ b/Install-Core-Blitz-With-Query-Store.sql @@ -11372,7 +11372,7 @@ IF @Help = 1 UNION ALL SELECT N'@IgnoreSystemDBs', N'BIT', - N'Ignores plans found in the system databases (master, model, msdb, tempdb, and resourcedb)' + N'Ignores plans found in the system databases (master, model, msdb, tempdb, dbmaintenance, dbadmin, dbatools, and resourcedb)' UNION ALL SELECT N'@OnlyQueryHashes', @@ -12922,7 +12922,7 @@ SET @body += N' WHERE 1 = 1 ' + @nl ; IF @IgnoreSystemDBs = 1 BEGIN RAISERROR(N'Ignoring system databases by default', 0, 1) WITH NOWAIT; - SET @body += N' AND COALESCE(DB_NAME(CAST(xpa.value AS INT)), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'') AND COALESCE(DB_NAME(CAST(xpa.value AS INT)), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ; + SET @body += N' AND COALESCE(DB_NAME(CAST(xpa.value AS INT)), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'', ''dbmaintenance'', ''dbadmin'', ''dbatools'') AND COALESCE(DB_NAME(CAST(xpa.value AS INT)), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ; END; IF @DatabaseName IS NOT NULL OR @DatabaseName <> N'' @@ -13357,7 +13357,7 @@ BEGIN SET @sql += @body_where ; IF @IgnoreSystemDBs = 1 - SET @sql += N' AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'') AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ; + SET @sql += N' AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'', ''dbmaintenance'', ''dbadmin'', ''dbatools'') AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ; SET @sql += @sort_filter + @nl; @@ -13389,7 +13389,7 @@ BEGIN SET @sql += @body_where ; IF @IgnoreSystemDBs = 1 - SET @sql += N' AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'') AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ; + SET @sql += N' AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'', ''dbmaintenance'', ''dbadmin'', ''dbatools'') AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ; SET @sql += @sort_filter + @nl; @@ -13425,7 +13425,7 @@ BEGIN SET @sql += @body_where ; IF @IgnoreSystemDBs = 1 - SET @sql += N' AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'') AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ; + SET @sql += N' AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'', ''dbmaintenance'', ''dbadmin'', ''dbatools'') AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ; SET @sql += @sort_filter + @nl; From 494774e3fbf3b9a297a4e30b2cede06f756df3e9 Mon Sep 17 00:00:00 2001 From: luke-mckee-ol Date: Tue, 2 Mar 2021 21:27:10 +1100 Subject: [PATCH 123/662] Update Install-Core-Blitz-No-Query-Store.sql Make changes for ignoring commonly named databases --- Install-Core-Blitz-No-Query-Store.sql | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Install-Core-Blitz-No-Query-Store.sql b/Install-Core-Blitz-No-Query-Store.sql index 67a492fc1..f654a2177 100755 --- a/Install-Core-Blitz-No-Query-Store.sql +++ b/Install-Core-Blitz-No-Query-Store.sql @@ -11372,7 +11372,7 @@ IF @Help = 1 UNION ALL SELECT N'@IgnoreSystemDBs', N'BIT', - N'Ignores plans found in the system databases (master, model, msdb, tempdb, and resourcedb)' + N'Ignores plans found in the system databases (master, model, msdb, tempdb, dbmaintenance, dbadmin, dbatools, and resourcedb)' UNION ALL SELECT N'@OnlyQueryHashes', @@ -12922,7 +12922,7 @@ SET @body += N' WHERE 1 = 1 ' + @nl ; IF @IgnoreSystemDBs = 1 BEGIN RAISERROR(N'Ignoring system databases by default', 0, 1) WITH NOWAIT; - SET @body += N' AND COALESCE(DB_NAME(CAST(xpa.value AS INT)), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'') AND COALESCE(DB_NAME(CAST(xpa.value AS INT)), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ; + SET @body += N' AND COALESCE(DB_NAME(CAST(xpa.value AS INT)), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'', ''dbmaintenance'', ''dbadmin'', ''dbatools'') AND COALESCE(DB_NAME(CAST(xpa.value AS INT)), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ; END; IF @DatabaseName IS NOT NULL OR @DatabaseName <> N'' @@ -13357,7 +13357,7 @@ BEGIN SET @sql += @body_where ; IF @IgnoreSystemDBs = 1 - SET @sql += N' AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'') AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ; + SET @sql += N' AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'', ''dbmaintenance'', ''dbadmin'', ''dbatools'') AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ; SET @sql += @sort_filter + @nl; @@ -13389,7 +13389,7 @@ BEGIN SET @sql += @body_where ; IF @IgnoreSystemDBs = 1 - SET @sql += N' AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'') AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ; + SET @sql += N' AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'', ''dbmaintenance'', ''dbadmin'', ''dbatools'') AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ; SET @sql += @sort_filter + @nl; @@ -13425,7 +13425,7 @@ BEGIN SET @sql += @body_where ; IF @IgnoreSystemDBs = 1 - SET @sql += N' AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'') AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ; + SET @sql += N' AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'', ''dbmaintenance'', ''dbadmin'', ''dbatools'') AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ; SET @sql += @sort_filter + @nl; From 9e0d60c059a9c14418c45e6a3bc6c707b3ea66a4 Mon Sep 17 00:00:00 2001 From: JohnDBallentineIII <39534156+JohnDBallentineIII@users.noreply.github.com> Date: Wed, 3 Mar 2021 06:45:53 -0800 Subject: [PATCH 124/662] Rationalized brentozar.com Fix for issue 'Need to rationalize 'brentozar.com' #2803' --- .../sp_BlitzCache_Checks_by_Priority.md | 66 ++-- .../sp_BlitzFirst_Checks_by_Priority.md | 70 ++-- Documentation/sp_Blitz_Checks_by_Priority.md | 4 +- sp_Blitz.sql | 354 +++++++++--------- sp_BlitzCache.sql | 70 ++-- sp_BlitzFirst.sql | 82 ++-- sp_BlitzQueryStore.sql | 62 +-- 7 files changed, 354 insertions(+), 354 deletions(-) diff --git a/Documentation/sp_BlitzCache_Checks_by_Priority.md b/Documentation/sp_BlitzCache_Checks_by_Priority.md index b0b5fdf63..480bb5c72 100644 --- a/Documentation/sp_BlitzCache_Checks_by_Priority.md +++ b/Documentation/sp_BlitzCache_Checks_by_Priority.md @@ -11,49 +11,49 @@ If you want to add a new check, start at 70 | Priority | FindingsGroup | Finding | URL | CheckID | Expert Mode | |----------|---------------------------------|---------------------------------------|-------------------------------------------------|----------|-------------| -| 10 | Execution Plans | Forced Serialization | http://www.brentozar.com/blitzcache/forced-serialization/ | 25 | No | -| 10 | Large USERSTORE_TOKENPERM cache | Using Over 10% of the Buffer Pool | https://brentozar.com/go/userstore | 69 | No | +| 10 | Execution Plans | Forced Serialization | https://www.brentozar.com/blitzcache/forced-serialization/ | 25 | No | +| 10 | Large USERSTORE_TOKENPERM cache | Using Over 10% of the Buffer Pool | https://www.brentozar.com/go/userstore | 69 | No | | 50 | Complexity | High Compile CPU | https://www.brentozar.com/blitzcache/high-compilers/ | 64 | No | | 50 | Complexity | High Compile Memory | https://www.brentozar.com/blitzcache/high-compilers/ | 65 | No | -| 50 | Execution Plans | Compilation timeout | http://brentozar.com/blitzcache/compilation-timeout/ | 18 | No | -| 50 | Execution Plans | Compile Memory Limit Exceeded | http://brentozar.com/blitzcache/compile-memory-limit-exceeded/ | 19 | No | -| 50 | Execution Plans | No join predicate | http://brentozar.com/blitzcache/no-join-predicate/ | 20 | No | -| 50 | Execution Plans | Plan Warnings | http://brentozar.com/blitzcache/query-plan-warnings/ | 8 | No | +| 50 | Execution Plans | Compilation timeout | https://www.brentozar.com/blitzcache/compilation-timeout/ | 18 | No | +| 50 | Execution Plans | Compile Memory Limit Exceeded | https://www.brentozar.com/blitzcache/compile-memory-limit-exceeded/ | 19 | No | +| 50 | Execution Plans | No join predicate | https://www.brentozar.com/blitzcache/no-join-predicate/ | 20 | No | +| 50 | Execution Plans | Plan Warnings | https://www.brentozar.com/blitzcache/query-plan-warnings/ | 8 | No | | 50 | Functions | Computed Column UDF | https://www.brentozar.com/blitzcache/computed-columns-referencing-functions/ | 42 | Yes | | 50 | Functions | Filter UDF | https://www.brentozar.com/blitzcache/compute-scalar-functions/ | 44 | Yes | -| 50 | Non-SARGable queries | Queries may have non-SARGable predicates |http://brentozar.com/go/sargable| 62 | No | -| 50 | Parameterization | Forced Parameterization | http://brentozar.com/blitzcache/forced-parameterization/ | 5 | No | -| 50 | Parameterization | Forced Plan | http://brentozar.com/blitzcache/forced-plans/ | 3 | No | -| 50 | Parameterization | Parameter Sniffing | http://brentozar.com/blitzcache/parameter-sniffing/ | 2 | No | -| 50 | Performance | Function Join | http://brentozar.com/blitzcache/tvf-join/ | 17 | Yes | -| 50 | Performance | Implicit Conversions | http://brentozar.com/go/implicit | 14 | No | -| 50 | Performance | Long Running Query | http://brentozar.com/blitzcache/long-running-queries/ | 9 | No | -| 50 | Performance | Missing Indexes | http://brentozar.com/blitzcache/missing-index-request/ | 10 | No | +| 50 | Non-SARGable queries | Queries may have non-SARGable predicates |https://www.brentozar.com/go/sargable| 62 | No | +| 50 | Parameterization | Forced Parameterization | https://www.brentozar.com/blitzcache/forced-parameterization/ | 5 | No | +| 50 | Parameterization | Forced Plan | https://www.brentozar.com/blitzcache/forced-plans/ | 3 | No | +| 50 | Parameterization | Parameter Sniffing | https://www.brentozar.com/blitzcache/parameter-sniffing/ | 2 | No | +| 50 | Performance | Function Join | https://www.brentozar.com/blitzcache/tvf-join/ | 17 | Yes | +| 50 | Performance | Implicit Conversions | https://www.brentozar.com/go/implicit | 14 | No | +| 50 | Performance | Long Running Query | https://www.brentozar.com/blitzcache/long-running-queries/ | 9 | No | +| 50 | Performance | Missing Indexes | https://www.brentozar.com/blitzcache/missing-index-request/ | 10 | No | | 50 | Selects w/ Writes | Read queries are causing writes | https://dba.stackexchange.com/questions/191825/ | 66 | No | | 100 | Complexity | Long Compile Time | https://www.brentozar.com/blitzcache/high-compilers/ | No | | 100 | Complexity | Many to Many Merge | Blog not published yet | 61 | Yes | | 100 | Complexity | Row Estimate Mismatch | https://www.brentozar.com/blitzcache/bad-estimates/ | 56 | Yes | | 100 | Compute Scalar That References A CLR Function | Calls CLR Functions | https://www.brentozar.com/blitzcache/compute-scalar-functions/| 31 | Yes | | 100 | Compute Scalar That References A Function | Calls Functions | https://www.brentozar.com/blitzcache/compute-scalar-functions/| 31 | Yes | -| 100 | Execution Pattern | Frequently Execution | http://brentozar.com/blitzcache/frequently-executed-queries/ | 1 | No | -| 100 | Execution Plans | Expensive Key Lookup | http://www.brentozar.com/blitzcache/expensive-key-lookups/ | 26 | No | -| 100 | Execution Plans | Expensive Remote Query | http://www.brentozar.com/blitzcache/expensive-remote-query/ | 28 | | -| 100 | Execution Plans | Expensive Sort | http://www.brentozar.com/blitzcache/expensive-sorts/ | 43 | No | -| 100 | Execution Plans | Trivial Plans | http://brentozar.com/blitzcache/trivial-plans | 24 | No | -| 100 | Functions | MSTVFs | http://brentozar.com/blitzcache/tvf-join/ | 60 | No | +| 100 | Execution Pattern | Frequently Execution | https://www.brentozar.com/blitzcache/frequently-executed-queries/ | 1 | No | +| 100 | Execution Plans | Expensive Key Lookup | https://www.brentozar.com/blitzcache/expensive-key-lookups/ | 26 | No | +| 100 | Execution Plans | Expensive Remote Query | https://www.brentozar.com/blitzcache/expensive-remote-query/ | 28 | | +| 100 | Execution Plans | Expensive Sort | https://www.brentozar.com/blitzcache/expensive-sorts/ | 43 | No | +| 100 | Execution Plans | Trivial Plans | https://www.brentozar.com/blitzcache/trivial-plans | 24 | No | +| 100 | Functions | MSTVFs | https://www.brentozar.com/blitzcache/tvf-join/ | 60 | No | | 100 | Indexes | \>= 5 Indexes Modified | https://www.brentozar.com/blitzcache/many-indexes-modified/ | 45 | Yes | | 100 | Indexes | ColumnStore Row Mode | https://www.brentozar.com/blitzcache/columnstore-indexes-operating-row-mode/ | 41 | Yes | | 100 | Indexes | Forced Indexes | https://www.brentozar.com/blitzcache/optimizer-forcing/ | 39 | Yes | | 100 | Indexes | Forced Seeks/Scans | https://www.brentozar.com/blitzcache/optimizer-forcing/ | 40 | Yes | | 100 | Indexes | Table Scans (Heaps) | https://www.brentozar.com/archive/2012/05/video-heaps/ | 37 | No | | 100 | Memory Grant | Unused Memory Grant | https://www.brentozar.com/blitzcache/unused-memory-grants/ | 30 | No | -| 100 | Parameterization | Unparameterized Query | http://brentozar.com/blitzcache/unparameterized-queries | 23 | Yes | -| 100 | Performance | Frequently executed operators | http://brentozar.com/blitzcache/busy-loops/ | 16 | Yes | -| 100 | Performance | Unmatched Indexes | http://brentozar.com/blitzcache/unmatched-indexes | 22 | No | +| 100 | Parameterization | Unparameterized Query | https://www.brentozar.com/blitzcache/unparameterized-queries | 23 | Yes | +| 100 | Performance | Frequently executed operators | https://www.brentozar.com/blitzcache/busy-loops/ | 16 | Yes | +| 100 | Performance | Unmatched Indexes | https://www.brentozar.com/blitzcache/unmatched-indexes | 22 | No | | 100 | Statistics | Columns With No Statistics | https://www.brentozar.com/blitzcache/columns-no-statistics/ | 35 | No | | 100 | Table Variables detected | Beware nasty side effects | https://www.brentozar.com/blitzcache/table-variables/ | 33 | No | | 100 | TempDB | >500mb Spills | https://www.brentozar.com/blitzcache/tempdb-spills/ | 59 | No | -| 100 | Warnings | Operator Warnings | http://brentozar.com/blitzcache/query-plan-warnings/ | 36 | Yes | +| 100 | Warnings | Operator Warnings | https://www.brentozar.com/blitzcache/query-plan-warnings/ | 36 | Yes | | 150 | Blocking | Long Running Low CPU | https://www.brentozar.com/blitzcache/long-running-low-cpu/ | 50 | No | | 150 | Complexity | Index DML | https://www.brentozar.com/blitzcache/index-dml/ | 48 | Yes | | 150 | Complexity | Low Cost High CPU | https://www.brentozar.com/blitzcache/low-cost-high-cpu/ | 51 | No | @@ -63,20 +63,20 @@ If you want to add a new check, start at 70 | 150 | Indexes | Expensive Index Spool | https://sqlperformance.com/2019/09/sql-performance/nested-loops-joins-performance-spools | 67 | No | | 150 | Indexes | Large Index Row Spool | https://www.brentozar.com/blitzcache/eager-index-spools/ | 55 | No | | 150 | Indexes | Large Index Row Spool | https://sqlperformance.com/2019/09/sql-performance/nested-loops-joins-performance-spools | 68 | No | -| 200 | Cardinality | Downlevel CE | http://brentozar.com/blitzcache/legacy-cardinality-estimator/ | 13 | No | +| 200 | Cardinality | Downlevel CE | https://www.brentozar.com/blitzcache/legacy-cardinality-estimator/ | 13 | No | | 200 | Complexity | Adaptive Joins | https://www.brentozar.com/blitzcache/adaptive-joins/ | 53 | No | | 200 | Complexity | Row Goals | https://www.brentozar.com/go/rowgoals/ | 58 | Yes | | 200 | Complexity | Row Level Security | https://www.brentozar.com/blitzcache/row-level-security/ | 46 | Yes | | 200 | Complexity | Spatial Index | https://www.brentozar.com/blitzcache/spatial-indexes/ | 47 | Yes | -| 200 | Cursors | Cursor | http://brentozar.com/blitzcache/cursors-found-slow-queries/ | 4 | No | -| 200 | Cursors | Dynamic Cursors | http://brentozar.com/blitzcache/cursors-found-slow-queries/ | 4 | No | -| 200 | Cursors | Fast Forward Cursors | http://brentozar.com/blitzcache/cursors-found-slow-queries/ | 4 | No | -| 200 | Cursors | Non-forward Only Cursors | http://brentozar.com/blitzcache/cursors-found-slow-queries/ | 4 | No | -| 200 | Cursors | Optimistic Cursors | http://brentozar.com/blitzcache/cursors-found-slow-queries/ | 4 | No | +| 200 | Cursors | Cursor | https://www.brentozar.com/blitzcache/cursors-found-slow-queries/ | 4 | No | +| 200 | Cursors | Dynamic Cursors | https://www.brentozar.com/blitzcache/cursors-found-slow-queries/ | 4 | No | +| 200 | Cursors | Fast Forward Cursors | https://www.brentozar.com/blitzcache/cursors-found-slow-queries/ | 4 | No | +| 200 | Cursors | Non-forward Only Cursors | https://www.brentozar.com/blitzcache/cursors-found-slow-queries/ | 4 | No | +| 200 | Cursors | Optimistic Cursors | https://www.brentozar.com/blitzcache/cursors-found-slow-queries/ | 4 | No | | 200 | Database Level Statistics | Database has stats updated 7 days ago with more than 100k modifications | https://www.brentozar.com/blitzcache/stale-statistics/ | 997 | No | -| 200 | Execution Plans | Multiple Plans | http://brentozar.com/blitzcache/multiple-plans/ | 21 | No | -| 200 | Execution Plans | Nearly Parallel | http://brentozar.com/blitzcache/query-cost-near-cost-threshold-parallelism/ | 7 | No | -| 200 | Execution Plans | Parallel | http://brentozar.com/blitzcache/parallel-plans-detected/ | 6 | No | +| 200 | Execution Plans | Multiple Plans | https://www.brentozar.com/blitzcache/multiple-plans/ | 21 | No | +| 200 | Execution Plans | Nearly Parallel | https://www.brentozar.com/blitzcache/query-cost-near-cost-threshold-parallelism/ | 7 | No | +| 200 | Execution Plans | Parallel | https://www.brentozar.com/blitzcache/parallel-plans-detected/ | 6 | No | | 200 | Indexes | Backwards Scans | https://www.brentozar.com/blitzcache/backwards-scans/ | 38 | Yes | | 200 | Is Paul White Electric? | This query has a Switch operator in it! | https://www.sql.kiwi/2013/06/hello-operator-my-switch-is-bored.html | 57 | Yes | | 200 | Trace Flags | Session Level Trace Flags Enabled | https://www.brentozar.com/blitz/trace-flags-enabled-globally/ | 29 | No | diff --git a/Documentation/sp_BlitzFirst_Checks_by_Priority.md b/Documentation/sp_BlitzFirst_Checks_by_Priority.md index 292913426..8c4440766 100644 --- a/Documentation/sp_BlitzFirst_Checks_by_Priority.md +++ b/Documentation/sp_BlitzFirst_Checks_by_Priority.md @@ -14,45 +14,45 @@ If you want to add a new check, start at 48 | 0 | Outdated sp_BlitzFirst | sp_BlitzFirst is Over 6 Months Old | http://FirstResponderKit.org/ | 27 | | 0 | Outdated or Missing sp_BlitzCache | Update Your sp_BlitzCache | http://FirstResponderKit.org/ | 36 | | 1 | Logged Message | Logged from sp_BlitzFirst | http://FirstResponderKit.org | 38 | -| 1 | Maintenance Tasks Running | Backup Running | https://BrentOzar.com/askbrent/backups | 1 | -| 1 | Maintenance Tasks Running | DBCC CHECK* Running | https://BrentOzar.com/askbrent/dbcc | 2 | -| 1 | Maintenance Tasks Running | Restore Running | https://BrentOzar.com/askbrent/backups | 3 | -| 1 | Query Problems | Long-Running Query Blocking Others | https://BrentOzar.com/go/blocking | 5 | -| 1 | Query Problems | Query Rolling Back | https://BrentOzar.com/go/rollback | 9 | -| 1 | Query Problems | Sleeping Query with Open Transactions | https://BrentOzar.com/go/sleeping | 8 | -| 1 | SQL Server Internal Maintenance | Data File Growing | https://BrentOzar.com/go/instant | 4 | -| 1 | SQL Server Internal Maintenance | Log File Growing | https://BrentOzar.com/go/logsize | 13 | -| 1 | SQL Server Internal Maintenance | Log File Shrinking | https://BrentOzar.com/go/logsize | 14 | -| 10 | Server Performance | Poison Wait Detected | https://BrentOzar.com/go/poison | 30 | -| 10 | Server Performance | Target Memory Lower Than Max | https://BrentOzar.com/go/target | 35 | -| 10 | Azure Performance | Database is Maxed Out | https://BrentOzar.com/go/maxedout | 41 | -| 40 | Table Problems | Forwarded Fetches/Sec High | https://BrentOzar.com/go/fetch | 29 | -| 50 | In-Memory OLTP | Garbage Collection in Progress | https://BrentOzar.com/go/garbage | 31 | -| 50 | Query Problems | Compilations/Sec High | https://BrentOzar.com/go/compile | 15 | +| 1 | Maintenance Tasks Running | Backup Running | https://www.brentozar.com/askbrent/backups | 1 | +| 1 | Maintenance Tasks Running | DBCC CHECK* Running | https://www.brentozar.com/askbrent/dbcc | 2 | +| 1 | Maintenance Tasks Running | Restore Running | https://www.brentozar.com/askbrent/backups | 3 | +| 1 | Query Problems | Long-Running Query Blocking Others | https://www.brentozar.com/go/blocking | 5 | +| 1 | Query Problems | Query Rolling Back | https://www.brentozar.com/go/rollback | 9 | +| 1 | Query Problems | Sleeping Query with Open Transactions | https://www.brentozar.com/go/sleeping | 8 | +| 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 | 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 | +| 40 | Table Problems | Forwarded Fetches/Sec High | https://www.brentozar.com/go/fetch | 29 | +| 50 | In-Memory OLTP | Garbage Collection in Progress | https://www.brentozar.com/go/garbage | 31 | +| 50 | Query Problems | Compilations/Sec High | https://www.brentozar.com/go/compile | 15 | | 50 | Query Problems | Implicit Transactions | https://www.brentozar.com/go/ImplicitTransactions/ | 37 | -| 50 | Query Problems | Memory Leak in USERSTORE_TOKENPERM Cache | https://BrentOzar.com/go/userstore | 45 | -| 50 | Query Problems | Plan Cache Erased Recently | https://BrentOzar.com/go/freeproccache | 7 | -| 50 | Query Problems | Re-Compilations/Sec High | https://BrentOzar.com/go/recompile | 16 | -| 50 | Query Problems | Statistics Updated Recently | https://BrentOzar.com/go/stats | 44 | +| 50 | Query Problems | Memory Leak in USERSTORE_TOKENPERM Cache | https://www.brentozar.com/go/userstore | 45 | +| 50 | Query Problems | Plan Cache Erased Recently | https://www.brentozar.com/go/freeproccache | 7 | +| 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 | High CPU Utilization | https://BrentOzar.com/go/cpu | 24 | -| 50 | Server Performance | High CPU Utilization - Non SQL Processes | https://BrentOzar.com/go/cpu | 28 | -| 50 | Server Performance | Slow Data File Reads | https://BrentOzar.com/go/slow | 11 | -| 50 | Server Performance | Slow Log File Writes | https://BrentOzar.com/go/slow | 12 | -| 50 | Server Performance | Too Much Free Memory | https://BrentOzar.com/go/freememory | 34 | +| 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 | +| 50 | Server Performance | Slow Log File Writes | https://www.brentozar.com/go/slow | 12 | +| 50 | Server Performance | Too Much Free Memory | https://www.brentozar.com/go/freememory | 34 | | 50 | Server Performance | Memory Grants pending | https://www.brentozar.com/blitz/memory-grants | 39 | -| 100 | In-Memory OLTP | Transactions aborted | https://BrentOzar.com/go/aborted | 32 | -| 100 | Query Problems | Suboptimal Plans/Sec High | https://BrentOzar.com/go/suboptimal | 33 | -| 100 | Query Problems | Bad Estimates | https://brentozar.com/go/skewedup | 42 | -| 100 | Query Problems | Skewed Parallelism | https://brentozar.com/go/skewedup | 43 | +| 100 | In-Memory OLTP | Transactions aborted | https://www.brentozar.com/go/aborted | 32 | +| 100 | Query Problems | Suboptimal Plans/Sec High | https://www.brentozar.com/go/suboptimal | 33 | +| 100 | Query Problems | Bad Estimates | https://www.brentozar.com/go/skewedup | 42 | +| 100 | Query Problems | Skewed Parallelism | https://www.brentozar.com/go/skewedup | 43 | | 100 | Query Problems | Query with a memory grant exceeding @MemoryGrantThresholdPct | https://www.brentozar.com/memory-grants-sql-servers-public-toilet/ | 46 | -| 200 | Wait Stats | (One per wait type) | https://BrentOzar.com/sql/wait-stats/#(waittype) | 6 | -| 210 | Query Stats | Plan Cache Analysis Skipped | https://BrentOzar.com/go/topqueries | 18 | -| 210 | Query Stats | Top Resource-Intensive Queries | https://BrentOzar.com/go/topqueries | 17 | -| 250 | Server Info | Batch Requests per Second | https://BrentOzar.com/go/measure | 19 | -| 250 | Server Info | Re-Compiles per Second | https://BrentOzar.com/go/measure | 26 | -| 250 | Server Info | SQL Compilations/sec | https://BrentOzar.com/go/measure | 25 | -| 250 | Server Info | Wait Time per Core per Second | https://BrentOzar.com/go/measure | 20 | +| 200 | Wait Stats | (One per wait type) | https://www.brentozar.com/sql/wait-stats/#(waittype) | 6 | +| 210 | Query Stats | Plan Cache Analysis Skipped | https://www.brentozar.com/go/topqueries | 18 | +| 210 | Query Stats | Top Resource-Intensive Queries | https://www.brentozar.com/go/topqueries | 17 | +| 250 | Server Info | Batch Requests per Second | https://www.brentozar.com/go/measure | 19 | +| 250 | Server Info | Re-Compiles per Second | https://www.brentozar.com/go/measure | 26 | +| 250 | Server Info | SQL Compilations/sec | https://www.brentozar.com/go/measure | 25 | +| 250 | Server Info | Wait Time per Core per Second | https://www.brentozar.com/go/measure | 20 | | 251 | Server Info | CPU Utilization | | 23 | | 251 | Server Info | Database Count | | 22 | | 251 | Server Info | Database Size, Total GB | | 21 | diff --git a/Documentation/sp_Blitz_Checks_by_Priority.md b/Documentation/sp_Blitz_Checks_by_Priority.md index 375b80e3e..5810c2c9e 100644 --- a/Documentation/sp_Blitz_Checks_by_Priority.md +++ b/Documentation/sp_Blitz_Checks_by_Priority.md @@ -69,7 +69,7 @@ If you want to add a new one, start at 257. | 50 | Reliability | Possibly Broken Log Shipping | https://www.BrentOzar.com/go/shipping | 111 | | 50 | Reliability | TempDB File Error | https://www.BrentOzar.com/go/tempdboops | 191 | | 50 | Reliability | Transaction Log Larger than Data File | https://www.BrentOzar.com/go/biglog | 75 | -| 50 | Reliability | Default Trace File Error | https://BrentOzar.com/go/defaulttrace | 199 | +| 50 | Reliability | Default Trace File Error | https://www.brentozar.com/go/defaulttrace | 199 | | 100 | In-Memory OLTP (Hekaton) | Transaction Errors | https://www.BrentOzar.com/go/hekaton | 147 | | 100 | Features | Missing Features (2016 SP1) | https://www.BrentOzar.com/ | 189 | | 100 | Features | Missing Features (2017 CU3) | https://www.BrentOzar.com/ | 216 | @@ -155,7 +155,7 @@ If you want to add a new one, start at 257. | 200 | Monitoring | Agent Jobs Without Failure Emails | https://www.BrentOzar.com/go/alerts | 94 | | 200 | Monitoring | Alerts Configured without Follow Up | https://www.BrentOzar.com/go/alert | 59 | | 200 | Monitoring | Alerts Disabled | https://www.BrentOzar.com/go/alerts/ | 98 | -| 200 | Monitoring | Alerts Without Event Descriptions | https://BrentOzar.com/go/alert | 219 | +| 200 | Monitoring | Alerts Without Event Descriptions | https://www.brentozar.com/go/alert | 219 | | 200 | Monitoring | Extended Events Hyperextension | https://www.BrentOzar.com/go/xe | 176 | | 200 | Monitoring | No Alerts for Corruption | https://www.BrentOzar.com/go/alert | 96 | | 200 | Monitoring | No Alerts for Sev 19-25 | https://www.BrentOzar.com/go/alert | 61 | diff --git a/sp_Blitz.sql b/sp_Blitz.sql index 217f5f851..5995f334b 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -800,7 +800,7 @@ AS 240, 'Wait Stats', 'Wait Stats Have Been Cleared', - 'https://BrentOzar.com/go/waits', + 'https://www.brentozar.com/go/waits', 'Someone ran DBCC SQLPERF to clear sys.dm_os_wait_stats at approximately: ' + CONVERT(NVARCHAR(100), DATEADD(MINUTE, (-1. * (@MsSinceWaitsCleared) / 1000. / 60.), GETDATE()), 120)); @@ -909,7 +909,7 @@ AS 1 AS Priority , 'Backup' AS FindingsGroup , 'Backups Not Performed Recently' AS Finding , - 'https://BrentOzar.com/go/nobak' AS URL , + 'https://www.brentozar.com/go/nobak' AS URL , 'Last backed up: ' + COALESCE(CAST(MAX(b.backup_finish_date) AS VARCHAR(25)),'never') AS Details FROM master.sys.databases d @@ -949,7 +949,7 @@ AS 1 AS Priority , 'Backup' AS FindingsGroup , 'Backups Not Performed Recently' AS Finding , - 'https://BrentOzar.com/go/nobak' AS URL , + 'https://www.brentozar.com/go/nobak' AS URL , 'Last backed up: ' + COALESCE(CAST(MAX(b.backup_finish_date) AS VARCHAR(25)),'never') AS Details FROM master.sys.databases d @@ -1014,7 +1014,7 @@ AS 1 AS Priority , 'Backup' AS FindingsGroup , 'Full Recovery Model w/o Log Backups' AS Finding , - 'https://BrentOzar.com/go/biglogs' AS URL , + 'https://www.brentozar.com/go/biglogs' AS URL , ( 'The ' + CAST(CAST((SELECT ((SUM([mf].[size]) * 8.) / 1024.) FROM sys.[master_files] AS [mf] WHERE [mf].[database_id] = d.[database_id] AND [mf].[type_desc] = 'LOG') AS DECIMAL(18,2)) AS VARCHAR(30)) + 'MB log file has not been backed up in the last week.' ) AS Details FROM master.sys.databases d WHERE d.recovery_model IN ( 1, 2 ) @@ -1119,7 +1119,7 @@ AS 230 AS Priority, ''Security'' AS FindingsGroup, ''Server Audits Running'' AS Finding, - ''https://BrentOzar.com/go/audits'' AS URL, + ''https://www.brentozar.com/go/audits'' AS URL, (''SQL Server built-in audit functionality is being used by server audit: '' + [name]) AS Details FROM sys.dm_server_audit_status OPTION (RECOMPILE);'; IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; @@ -1164,7 +1164,7 @@ AS 1 AS Priority , 'Backup' AS FindingsGroup , 'Backing Up to Same Drive Where Databases Reside' AS Finding , - 'https://BrentOzar.com/go/backup' AS URL , + 'https://www.brentozar.com/go/backup' AS URL , CAST(COUNT(1) AS VARCHAR(50)) + ' backups done on drive ' + UPPER(LEFT(bmf.physical_device_name, 3)) + ' in the last two weeks, where database files also live. This represents a serious risk if that array fails.' Details @@ -1202,7 +1202,7 @@ AS ''Backup'' AS FindingsGroup, ''TDE Certificate Not Backed Up Recently'' AS Finding, db_name(dek.database_id) AS DatabaseName, - ''https://BrentOzar.com/go/tde'' AS URL, + ''https://www.brentozar.com/go/tde'' AS URL, ''The certificate '' + c.name + '' is used to encrypt database '' + db_name(dek.database_id) + ''. Last backup date: '' + COALESCE(CAST(c.pvt_key_last_backup_date AS VARCHAR(100)), ''Never'') AS Details FROM sys.certificates c INNER JOIN sys.dm_database_encryption_keys dek ON c.thumbprint = dek.encryptor_thumbprint WHERE pvt_key_last_backup_date IS NULL OR pvt_key_last_backup_date <= DATEADD(dd, -30, GETDATE()) OPTION (RECOMPILE);'; @@ -1231,7 +1231,7 @@ AS 1 AS Priority, ''Backup'' AS FindingsGroup, ''Encryption Certificate Not Backed Up Recently'' AS Finding, - ''https://BrentOzar.com/go/tde'' AS URL, + ''https://www.brentozar.com/go/tde'' AS URL, ''The certificate '' + c.name + '' is used to encrypt database backups. Last backup date: '' + COALESCE(CAST(c.pvt_key_last_backup_date AS VARCHAR(100)), ''Never'') AS Details FROM sys.certificates c INNER JOIN msdb.dbo.backupset bs ON c.thumbprint = bs.encryptor_thumbprint @@ -1268,7 +1268,7 @@ AS 200 AS Priority , 'Backup' AS FindingsGroup , 'MSDB Backup History Not Purged' AS Finding , - 'https://BrentOzar.com/go/history' AS URL , + 'https://www.brentozar.com/go/history' AS URL , ( 'Database backup history retained back to ' + CAST(bs.backup_start_date AS VARCHAR(20)) ) AS Details FROM msdb.dbo.backupset bs @@ -1303,7 +1303,7 @@ AS 200 AS Priority , 'Backup' AS FindingsGroup , 'MSDB Backup History Purged Too Frequently' AS Finding , - 'https://BrentOzar.com/go/history' AS URL , + 'https://www.brentozar.com/go/history' AS URL , ( 'Database backup history only retained back to ' + CAST(bs.backup_start_date AS VARCHAR(20)) ) AS Details FROM msdb.dbo.backupset bs @@ -1336,7 +1336,7 @@ AS 200 AS Priority , 'Performance' AS FindingsGroup , 'Snapshot Backups Occurring' AS Finding , - 'https://BrentOzar.com/go/snaps' AS URL , + 'https://www.brentozar.com/go/snaps' AS URL , ( CAST(COUNT(*) AS VARCHAR(20)) + ' snapshot-looking backups have occurred in the last two weeks, indicating that IO may be freezing up.') AS Details FROM msdb.dbo.backupset bs WHERE bs.type = 'D' @@ -1364,7 +1364,7 @@ AS 50 AS Priority , 'Performance' AS FindingsGroup , 'Snapshotting Too Many Databases' AS Finding , - 'https://BrentOzar.com/go/toomanysnaps' AS URL , + 'https://www.brentozar.com/go/toomanysnaps' AS URL , ( CAST(SUM(1) AS VARCHAR(20)) + ' databases snapshotted at once in the last two weeks, indicating that IO may be freezing up. Microsoft does not recommend VSS snaps for 35 or more databases.') AS Details FROM msdb.dbo.backupset bs WHERE bs.type = 'D' @@ -1393,7 +1393,7 @@ AS 230 AS Priority , 'Security' AS FindingsGroup , 'Sysadmins' AS Finding , - 'https://BrentOzar.com/go/sa' AS URL , + 'https://www.brentozar.com/go/sa' AS URL , ( 'Login [' + l.name + '] is a sysadmin - meaning they can do absolutely anything in SQL Server, including dropping databases or hiding their tracks.' ) AS Details FROM master.sys.syslogins l @@ -1451,7 +1451,7 @@ AS 230 AS Priority , 'Security' AS FindingsGroup , 'Security Admins' AS Finding , - 'https://BrentOzar.com/go/sa' AS URL , + 'https://www.brentozar.com/go/sa' AS URL , ( 'Login [' + l.name + '] is a security admin - meaning they can give themselves permission to do absolutely anything in SQL Server, including dropping databases or hiding their tracks.' ) AS Details FROM master.sys.syslogins l @@ -1479,7 +1479,7 @@ AS 230 AS [Priority] , 'Security' AS [FindingsGroup] , 'Login Can Control Server' AS [Finding] , - 'https://BrentOzar.com/go/sa' AS [URL] , + 'https://www.brentozar.com/go/sa' AS [URL] , 'Login [' + pri.[name] + '] has the CONTROL SERVER permission - meaning they can do absolutely anything in SQL Server, including dropping databases or hiding their tracks.' AS [Details] FROM sys.server_principals AS pri @@ -1511,7 +1511,7 @@ AS 230 AS Priority , 'Security' AS FindingsGroup , 'Jobs Owned By Users' AS Finding , - 'https://BrentOzar.com/go/owners' AS URL , + 'https://www.brentozar.com/go/owners' AS URL , ( 'Job [' + j.name + '] is owned by [' + SUSER_SNAME(j.owner_sid) + '] - meaning if their login is disabled or not available due to Active Directory problems, the job will stop working.' ) AS Details @@ -1541,7 +1541,7 @@ AS 230 AS Priority , 'Security' AS FindingsGroup , 'Stored Procedure Runs at Startup' AS Finding , - 'https://BrentOzar.com/go/startup' AS URL , + 'https://www.brentozar.com/go/startup' AS URL , ( 'Stored procedure [master].[' + r.SPECIFIC_SCHEMA + '].[' + r.SPECIFIC_NAME @@ -1572,7 +1572,7 @@ AS 100 AS Priority, ''Performance'' AS FindingsGroup, ''Resource Governor Enabled'' AS Finding, - ''https://BrentOzar.com/go/rg'' AS URL, + ''https://www.brentozar.com/go/rg'' AS URL, (''Resource Governor is enabled. Queries may be throttled. Make sure you understand how the Classifier Function is configured.'') AS Details FROM sys.resource_governor_configuration WHERE is_enabled = 1 OPTION (RECOMPILE);'; IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; @@ -1602,7 +1602,7 @@ AS 100 AS Priority, ''Performance'' AS FindingsGroup, ''Server Triggers Enabled'' AS Finding, - ''https://BrentOzar.com/go/logontriggers/'' AS URL, + ''https://www.brentozar.com/go/logontriggers/'' AS URL, (''Server Trigger ['' + [name] ++ ''] is enabled. Make sure you understand what that trigger is doing - the less work it does, the better.'') AS Details FROM sys.server_triggers WHERE is_disabled = 0 AND is_ms_shipped = 0 OPTION (RECOMPILE);'; IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; @@ -1633,7 +1633,7 @@ AS 10 AS Priority , 'Performance' AS FindingsGroup , 'Auto-Close Enabled' AS Finding , - 'https://BrentOzar.com/go/autoclose' AS URL , + 'https://www.brentozar.com/go/autoclose' AS URL , ( 'Database [' + [name] + '] has auto-close enabled. This setting can dramatically decrease performance.' ) AS Details FROM sys.databases @@ -1665,7 +1665,7 @@ AS 10 AS Priority , 'Performance' AS FindingsGroup , 'Auto-Shrink Enabled' AS Finding , - 'https://BrentOzar.com/go/autoshrink' AS URL , + 'https://www.brentozar.com/go/autoshrink' AS URL , ( 'Database [' + [name] + '] has auto-shrink enabled. This setting can dramatically decrease performance.' ) AS Details FROM sys.databases @@ -1699,7 +1699,7 @@ AS 50 AS Priority, ''Reliability'' AS FindingsGroup, ''Page Verification Not Optimal'' AS Finding, - ''https://BrentOzar.com/go/torn'' AS URL, + ''https://www.brentozar.com/go/torn'' AS URL, (''Database ['' + [name] + ''] has '' + [page_verify_option_desc] + '' for page verification. SQL Server may have a harder time recognizing and recovering from storage corruption. Consider using CHECKSUM instead.'') COLLATE database_default AS Details FROM sys.databases WHERE page_verify_option < 2 @@ -1735,7 +1735,7 @@ AS 110 AS Priority , 'Performance' AS FindingsGroup , 'Auto-Create Stats Disabled' AS Finding , - 'https://BrentOzar.com/go/acs' AS URL , + 'https://www.brentozar.com/go/acs' AS URL , ( 'Database [' + [name] + '] has auto-create-stats disabled. SQL Server uses statistics to build better execution plans, and without the ability to automatically create more, performance may suffer.' ) AS Details FROM sys.databases @@ -1767,7 +1767,7 @@ AS 110 AS Priority , 'Performance' AS FindingsGroup , 'Auto-Update Stats Disabled' AS Finding , - 'https://BrentOzar.com/go/aus' AS URL , + 'https://www.brentozar.com/go/aus' AS URL , ( 'Database [' + [name] + '] has auto-update-stats disabled. SQL Server uses statistics to build better execution plans, and without the ability to automatically update them, performance may suffer.' ) AS Details FROM sys.databases @@ -1799,7 +1799,7 @@ AS 150 AS Priority , 'Performance' AS FindingsGroup , 'Stats Updated Asynchronously' AS Finding , - 'https://BrentOzar.com/go/asyncstats' AS URL , + 'https://www.brentozar.com/go/asyncstats' AS URL , ( 'Database [' + [name] + '] has auto-update-stats-async enabled. When SQL Server gets a query for a table with out-of-date statistics, it will run the query with the stats it has - while updating stats to make later queries better. The initial run of the query may suffer, though.' ) AS Details FROM sys.databases @@ -1831,7 +1831,7 @@ AS 200 AS Priority , 'Informational' AS FindingsGroup , 'Date Correlation On' AS Finding , - 'https://BrentOzar.com/go/corr' AS URL , + 'https://www.brentozar.com/go/corr' AS URL , ( 'Database [' + [name] + '] has date correlation enabled. This is not a default setting, and it has some performance overhead. It tells SQL Server that date fields in two tables are related, and SQL Server maintains statistics showing that relation.' ) AS Details FROM sys.databases @@ -1866,7 +1866,7 @@ AS 200 AS Priority, ''Informational'' AS FindingsGroup, ''Database Encrypted'' AS Finding, - ''https://BrentOzar.com/go/tde'' AS URL, + ''https://www.brentozar.com/go/tde'' AS URL, (''Database ['' + [name] + ''] has Transparent Data Encryption enabled. Make absolutely sure you have backed up the certificate and private key, or else you will not be able to restore this database.'') AS Details FROM sys.databases WHERE is_encrypted = 1 @@ -2077,7 +2077,7 @@ AS 200 AS Priority , 'Non-Default Server Config' AS FindingsGroup , cr.name AS Finding , - 'https://BrentOzar.com/go/conf' AS URL , + 'https://www.brentozar.com/go/conf' AS URL , ( 'This sp_configure option has been changed. Its default value is ' + COALESCE(CAST(cd.[DefaultValue] AS VARCHAR(100)), '(unknown)') @@ -2119,7 +2119,7 @@ AS 200, 'Performance', 'Non-Dynamic Memory', - 'https://BrentOzar.com/go/memory', + 'https://www.brentozar.com/go/memory', 'Minimum Server Memory setting is the same as the Maximum (both set to ' + CAST(@MinServerMemory AS NVARCHAR(50)) + '). This will not allow dynamic memory. Please revise memory settings' ); END; @@ -2159,7 +2159,7 @@ AS 200 AS Priority , 'Performance' AS FindingsGroup , cr.name AS Finding , - 'https://BrentOzar.com/go/cxpacket' AS URL , + 'https://www.brentozar.com/go/cxpacket' AS URL , ( 'Set to ' + CAST(cr.value_in_use AS NVARCHAR(50)) + ', its default value. Changing this sp_configure setting may reduce CXPACKET waits.') FROM sys.configurations cr INNER JOIN #ConfigurationDefaults cd ON cd.name = cr.name @@ -2190,7 +2190,7 @@ AS 170 AS Priority , 'File Configuration' AS FindingsGroup , 'System Database on C Drive' AS Finding , - 'https://BrentOzar.com/go/cdrive' AS URL , + 'https://www.brentozar.com/go/cdrive' AS URL , ( 'The ' + DB_NAME(database_id) + ' database has a file on the C drive. Putting system databases on the C drive runs the risk of crashing the server when it runs out of space.' ) AS Details FROM sys.master_files @@ -2222,7 +2222,7 @@ AS 20 AS Priority , 'File Configuration' AS FindingsGroup , 'TempDB on C Drive' AS Finding , - 'https://BrentOzar.com/go/cdrive' AS URL , + 'https://www.brentozar.com/go/cdrive' AS URL , CASE WHEN growth > 0 THEN ( 'The tempdb database has files on the C drive. TempDB frequently grows unpredictably, putting your server at risk of running out of C drive space and crashing hard. C is also often much slower than other drives, so performance may be suffering.' ) ELSE ( 'The tempdb database has files on the C drive. TempDB is not set to Autogrow, hopefully it is big enough. C is also often much slower than other drives, so performance may be suffering.' ) @@ -2255,7 +2255,7 @@ AS 20 AS Priority , 'Reliability' AS FindingsGroup , 'User Databases on C Drive' AS Finding , - 'https://BrentOzar.com/go/cdrive' AS URL , + 'https://www.brentozar.com/go/cdrive' AS URL , ( 'The ' + DB_NAME(database_id) + ' database has a file on the C drive. Putting databases on the C drive runs the risk of crashing the server when it runs out of space.' ) AS Details FROM sys.master_files @@ -2291,7 +2291,7 @@ AS 200 AS Priority , 'Informational' AS FindingsGroup , 'Tables in the Master Database' AS Finding , - 'https://BrentOzar.com/go/mastuser' AS URL , + 'https://www.brentozar.com/go/mastuser' AS URL , ( 'The ' + name + ' table in the master database was created by end users on ' + CAST(create_date AS VARCHAR(20)) @@ -2323,7 +2323,7 @@ AS 200 AS Priority , 'Informational' AS FindingsGroup , 'Tables in the MSDB Database' AS Finding , - 'https://BrentOzar.com/go/msdbuser' AS URL , + 'https://www.brentozar.com/go/msdbuser' AS URL , ( 'The ' + name + ' table in the msdb database was created by end users on ' + CAST(create_date AS VARCHAR(20)) @@ -2353,7 +2353,7 @@ AS 200 AS Priority , 'Informational' AS FindingsGroup , 'Tables in the Model Database' AS Finding , - 'https://BrentOzar.com/go/model' AS URL , + 'https://www.brentozar.com/go/model' AS URL , ( 'The ' + name + ' table in the model database was created by end users on ' + CAST(create_date AS VARCHAR(20)) @@ -2387,7 +2387,7 @@ AS 200 AS Priority , 'Monitoring' AS FindingsGroup , 'Not All Alerts Configured' AS Finding , - 'https://BrentOzar.com/go/alert' AS URL , + 'https://www.brentozar.com/go/alert' AS URL , ( 'Not all SQL Server Agent alerts have been configured. This is a free, easy way to get notified of corruption, job failures, or major outages even before monitoring systems pick it up.' ) AS Details; END; END; @@ -2418,7 +2418,7 @@ AS 200 AS Priority , 'Monitoring' AS FindingsGroup , 'Alerts Configured without Follow Up' AS Finding , - 'https://BrentOzar.com/go/alert' AS URL , + 'https://www.brentozar.com/go/alert' AS URL , ( 'SQL Server Agent alerts have been configured but they either do not notify anyone or else they do not take any action. This is a free, easy way to get notified of corruption, job failures, or major outages even before monitoring systems pick it up.' ) AS Details; END; @@ -2448,7 +2448,7 @@ AS 200 AS Priority , 'Monitoring' AS FindingsGroup , 'No Alerts for Corruption' AS Finding , - 'https://BrentOzar.com/go/alert' AS URL , + 'https://www.brentozar.com/go/alert' AS URL , ( 'SQL Server Agent alerts do not exist for errors 823, 824, and 825. These three errors can give you notification about early hardware failure. Enabling them can prevent you a lot of heartbreak.' ) AS Details; END; @@ -2478,7 +2478,7 @@ AS 200 AS Priority , 'Monitoring' AS FindingsGroup , 'No Alerts for Sev 19-25' AS Finding , - 'https://BrentOzar.com/go/alert' AS URL , + 'https://www.brentozar.com/go/alert' AS URL , ( 'SQL Server Agent alerts do not exist for severity levels 19 through 25. These are some very severe SQL Server errors. Knowing that these are happening may let you recover from errors faster.' ) AS Details; END; @@ -2510,7 +2510,7 @@ AS 200 AS Priority , 'Monitoring' AS FindingsGroup , 'Alerts Disabled' AS Finding , - 'https://BrentOzar.com/go/alert' AS URL , + 'https://www.brentozar.com/go/alert' AS URL , ( 'The following Alert is disabled, please review and enable if desired: ' + name ) AS Details FROM msdb.dbo.sysalerts @@ -2543,7 +2543,7 @@ AS ,200 AS [Priority] ,'Monitoring' AS FindingsGroup ,'Alerts Without Event Descriptions' AS Finding - ,'https://BrentOzar.com/go/alert' AS [URL] + ,'https://www.brentozar.com/go/alert' AS [URL] ,('The following Alert is not including detailed event descriptions in its output messages: ' + QUOTENAME([name]) + '. You can fix it by ticking the relevant boxes in its Properties --> Options page.') AS Details FROM msdb.dbo.sysalerts @@ -2577,7 +2577,7 @@ AS 200 AS Priority , 'Monitoring' AS FindingsGroup , 'No Operators Configured/Enabled' AS Finding , - 'https://BrentOzar.com/go/op' AS URL , + 'https://www.brentozar.com/go/op' AS URL , ( 'No SQL Server Agent operators (emails) have been configured. This is a free, easy way to get notified of corruption, job failures, or major outages even before monitoring systems pick it up.' ) AS Details; END; @@ -2608,7 +2608,7 @@ AS 1 AS Priority , ''Corruption'' AS FindingsGroup , ''Database Corruption Detected'' AS Finding , - ''https://BrentOzar.com/go/repair'' AS URL , + ''https://www.brentozar.com/go/repair'' AS URL , ( ''Database mirroring has automatically repaired at least one corrupt page in the last 30 days. For more information, query the DMV sys.dm_db_mirroring_auto_page_repair.'' ) AS Details FROM (SELECT rp2.database_id, rp2.modification_time FROM sys.dm_db_mirroring_auto_page_repair rp2 @@ -2652,7 +2652,7 @@ AS 1 AS Priority , ''Corruption'' AS FindingsGroup , ''Database Corruption Detected'' AS Finding , - ''https://BrentOzar.com/go/repair'' AS URL , + ''https://www.brentozar.com/go/repair'' AS URL , ( ''Availability Groups has automatically repaired at least one corrupt page in the last 30 days. For more information, query the DMV sys.dm_hadr_auto_page_repair.'' ) AS Details FROM sys.dm_hadr_auto_page_repair rp INNER JOIN master.sys.databases db ON rp.database_id = db.database_id @@ -2690,7 +2690,7 @@ AS 1 AS Priority , ''Corruption'' AS FindingsGroup , ''Database Corruption Detected'' AS Finding , - ''https://BrentOzar.com/go/repair'' AS URL , + ''https://www.brentozar.com/go/repair'' AS URL , ( ''SQL Server has detected at least one corrupt page in the last 30 days. For more information, query the system table msdb.dbo.suspect_pages.'' ) AS Details FROM msdb.dbo.suspect_pages sp INNER JOIN master.sys.databases db ON sp.database_id = db.database_id @@ -2724,7 +2724,7 @@ AS 'Performance' AS FindingsGroup , 'Slow Storage Reads on Drive ' + UPPER(LEFT(mf.physical_name, 1)) AS Finding , - 'https://BrentOzar.com/go/slow' AS URL , + 'https://www.brentozar.com/go/slow' AS URL , 'Reads are averaging longer than 200ms for at least one database on this drive. For specific database file speeds, run the query from the information link.' AS Details FROM sys.dm_io_virtual_file_stats(NULL, NULL) AS fs @@ -2755,7 +2755,7 @@ AS 'Performance' AS FindingsGroup , 'Slow Storage Writes on Drive ' + UPPER(LEFT(mf.physical_name, 1)) AS Finding , - 'https://BrentOzar.com/go/slow' AS URL , + 'https://www.brentozar.com/go/slow' AS URL , 'Writes are averaging longer than 100ms for at least one database on this drive. For specific database file speeds, run the query from the information link.' AS Details FROM sys.dm_io_virtual_file_stats(NULL, NULL) AS fs @@ -2792,7 +2792,7 @@ AS 170 , 'File Configuration' , 'TempDB Only Has 1 Data File' , - 'https://BrentOzar.com/go/tempdb' , + 'https://www.brentozar.com/go/tempdb' , 'TempDB is only configured with one data file. More data files are usually required to alleviate SGAM contention.' ); END; @@ -2827,7 +2827,7 @@ AS 170 , 'File Configuration' , 'TempDB Unevenly Sized Data Files' , - 'https://BrentOzar.com/go/tempdb' , + 'https://www.brentozar.com/go/tempdb' , 'TempDB data files are not configured with the same size. Unevenly sized tempdb data files will result in unevenly sized workloads.' ); END; @@ -2852,7 +2852,7 @@ AS 150 AS Priority , 'Performance' AS FindingsGroup , 'Queries Forcing Order Hints' AS Finding , - 'https://BrentOzar.com/go/hints' AS URL , + 'https://www.brentozar.com/go/hints' AS URL , CAST(occurrence AS VARCHAR(10)) + ' instances of order hinting have been recorded since restart. This means queries are bossing the SQL Server optimizer around, and if they don''t know what they''re doing, this can cause more harm than good. This can also explain why DBA tuning efforts aren''t working.' AS Details FROM sys.dm_exec_query_optimizer_info @@ -2879,7 +2879,7 @@ AS 150 AS Priority , 'Performance' AS FindingsGroup , 'Queries Forcing Join Hints' AS Finding , - 'https://BrentOzar.com/go/hints' AS URL , + 'https://www.brentozar.com/go/hints' AS URL , CAST(occurrence AS VARCHAR(10)) + ' instances of join hinting have been recorded since restart. This means queries are bossing the SQL Server optimizer around, and if they don''t know what they''re doing, this can cause more harm than good. This can also explain why DBA tuning efforts aren''t working.' AS Details FROM sys.dm_exec_query_optimizer_info @@ -2907,7 +2907,7 @@ AS 200 AS Priority , 'Informational' AS FindingsGroup , 'Linked Server Configured' AS Finding , - 'https://BrentOzar.com/go/link' AS URL , + 'https://www.brentozar.com/go/link' AS URL , +CASE WHEN l.remote_name = 'sa' THEN COALESCE(s.data_source, s.provider) + ' is configured as a linked server. Check its security configuration as it is connecting with sa, because any user who queries it will get admin-level permissions.' @@ -2934,7 +2934,7 @@ AS 100 AS Priority , ''Performance'' AS FindingsGroup , ''Max Memory Set Too High'' AS Finding , - ''https://BrentOzar.com/go/max'' AS URL , + ''https://www.brentozar.com/go/max'' AS URL , ''SQL Server max memory is set to '' + CAST(c.value_in_use AS VARCHAR(20)) + '' megabytes, but the server only has '' @@ -2966,7 +2966,7 @@ AS 1 AS Priority , ''Performance'' AS FindingsGroup , ''Memory Dangerously Low'' AS Finding , - ''https://BrentOzar.com/go/max'' AS URL , + ''https://www.brentozar.com/go/max'' AS URL , ''The server has '' + CAST(( CAST(m.total_physical_memory_kb AS BIGINT) / 1024 ) AS VARCHAR(20)) + '' megabytes of physical memory, but only '' + CAST(( CAST(m.available_physical_memory_kb AS BIGINT) / 1024 ) AS VARCHAR(20)) + '' megabytes are available. As the server runs out of memory, there is danger of swapping to disk, which will kill performance.'' AS Details FROM sys.dm_os_sys_memory m @@ -2994,7 +2994,7 @@ AS 1 AS Priority , ''Performance'' AS FindingsGroup , ''Memory Dangerously Low in NUMA Nodes'' AS Finding , - ''https://BrentOzar.com/go/max'' AS URL , + ''https://www.brentozar.com/go/max'' AS URL , ''At least one NUMA node is reporting THREAD_RESOURCES_LOW in sys.dm_os_nodes and can no longer create threads.'' AS Details FROM sys.dm_os_nodes m WHERE node_state_desc LIKE ''%THREAD_RESOURCES_LOW%'' OPTION (RECOMPILE);'; @@ -3026,7 +3026,7 @@ AS 200 AS Priority , 'Informational' AS FindingsGroup , 'Cluster Node' AS Finding , - 'https://BrentOzar.com/go/node' AS URL , + 'https://www.brentozar.com/go/node' AS URL , 'This is a node in a cluster.' AS Details FROM sys.dm_os_cluster_nodes; END; @@ -3055,7 +3055,7 @@ AS 230 AS Priority , 'Security' AS FindingsGroup , 'Database Owner <> ' + @UsualDBOwner AS Finding , - 'https://BrentOzar.com/go/owndb' AS URL , + 'https://www.brentozar.com/go/owndb' AS URL , ( 'Database name: ' + [name] + ' ' + 'Owner name: ' + SUSER_SNAME(owner_sid) ) AS Details FROM sys.databases @@ -3117,7 +3117,7 @@ AS 230 AS Priority , 'Security' AS FindingsGroup , 'SQL Agent Job Runs at Startup' AS Finding , - 'https://BrentOzar.com/go/startup' AS URL , + 'https://www.brentozar.com/go/startup' AS URL , ( 'Job [' + j.name + '] runs automatically when SQL Server Agent starts up. Make sure you know exactly what this job is doing, because it could pose a security risk.' ) AS Details FROM msdb.dbo.sysschedules sched @@ -3146,7 +3146,7 @@ AS 100 AS Priority , 'Performance' AS FindingsGroup , 'Unusual SQL Server Edition' AS Finding , - 'https://BrentOzar.com/go/workgroup' AS URL , + 'https://www.brentozar.com/go/workgroup' AS URL , ( 'This server is using ' + CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) + ', which is capped at low amounts of CPU and memory.' ) AS Details @@ -3177,7 +3177,7 @@ AS 10 AS Priority , 'Performance' AS FindingsGroup , '32-bit SQL Server Installed' AS Finding , - 'https://BrentOzar.com/go/32bit' AS URL , + 'https://www.brentozar.com/go/32bit' AS URL , ( 'This server uses the 32-bit x86 binaries for SQL Server instead of the 64-bit x64 binaries. The amount of memory available for query workspace and execution plans is heavily limited.' ) AS Details WHERE CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) NOT LIKE '%64%'; END; @@ -3203,7 +3203,7 @@ AS 200 AS Priority , 'Performance' AS FindingsGroup , 'Old Compatibility Level' AS Finding , - 'https://BrentOzar.com/go/compatlevel' AS URL , + 'https://www.brentozar.com/go/compatlevel' AS URL , ( 'Database ' + [name] + ' is compatibility level ' + CAST(compatibility_level AS VARCHAR(20)) @@ -3235,7 +3235,7 @@ AS 200 AS [Priority] , 'Monitoring' AS FindingsGroup , 'Agent Jobs Without Failure Emails' AS Finding , - 'https://BrentOzar.com/go/alerts' AS URL , + 'https://www.brentozar.com/go/alerts' AS URL , 'The job ' + [name] + ' has not been set up to notify an operator if it fails.' AS Details FROM msdb.[dbo].[sysjobs] j @@ -3274,7 +3274,7 @@ AS 170 AS Priority , 'Reliability' AS FindingGroup , 'Remote DAC Disabled' AS Finding , - 'https://BrentOzar.com/go/dac' AS URL , + 'https://www.brentozar.com/go/dac' AS URL , 'Remote access to the Dedicated Admin Connection (DAC) is not enabled. The DAC can make remote troubleshooting much easier when SQL Server is unresponsive.'; END; @@ -3300,7 +3300,7 @@ AS 50 AS Priority , 'Performance' AS FindingGroup , 'CPU Schedulers Offline' AS Finding , - 'https://BrentOzar.com/go/schedulers' AS URL , + 'https://www.brentozar.com/go/schedulers' AS URL , 'Some CPU cores are not accessible to SQL Server due to affinity masking or licensing problems.'; END; @@ -3328,7 +3328,7 @@ AS 50 AS Priority , ''Performance'' AS FindingGroup , ''Memory Nodes Offline'' AS Finding , - ''https://BrentOzar.com/go/schedulers'' AS URL , + ''https://www.brentozar.com/go/schedulers'' AS URL , ''Due to affinity masking or licensing problems, some of the memory may not be available.'' OPTION (RECOMPILE)'; IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; @@ -3361,7 +3361,7 @@ AS 20 AS Priority , 'Reliability' AS FindingGroup , 'Unusual Database State: ' + [state_desc] AS Finding , - 'https://BrentOzar.com/go/repair' AS URL , + 'https://www.brentozar.com/go/repair' AS URL , 'This database may not be online.' FROM sys.databases WHERE state > 1; @@ -3390,7 +3390,7 @@ AS 200 AS Priority , 'Reliability' AS FindingGroup , 'Extended Stored Procedures in Master' AS Finding , - 'https://BrentOzar.com/go/clr' AS URL , + 'https://www.brentozar.com/go/clr' AS URL , 'The [' + name + '] extended stored procedure is in the master database. CLR may be in use, and the master database now needs to be part of your backup/recovery planning.' FROM master.sys.extended_procedures; @@ -3415,7 +3415,7 @@ AS 50 AS Priority , 'Performance' AS FindingGroup , 'Poison Wait Detected: ' + wait_type AS Finding , - 'https://BrentOzar.com/go/poison/#' + wait_type AS URL , + 'https://www.brentozar.com/go/poison/#' + wait_type AS URL , CONVERT(VARCHAR(10), (SUM([wait_time_ms]) / 1000) / 86400) + ':' + CONVERT(VARCHAR(20), DATEADD(s, (SUM([wait_time_ms]) / 1000), 0), 108) + ' of this wait have been recorded. This wait often indicates killer performance problems.' FROM sys.[dm_os_wait_stats] WHERE wait_type IN('IO_QUEUE_LIMIT', 'IO_RETRY', 'LOG_RATE_GOVERNOR', 'POOL_LOG_RATE_GOVERNOR', 'PREEMPTIVE_DEBUG', 'RESMGR_THROTTLED', 'RESOURCE_SEMAPHORE', 'RESOURCE_SEMAPHORE_QUERY_COMPILE','SE_REPL_CATCHUP_THROTTLE','SE_REPL_COMMIT_ACK','SE_REPL_COMMIT_TURN','SE_REPL_ROLLBACK_ACK','SE_REPL_SLOW_SECONDARY_THROTTLE','THREADPOOL') @@ -3443,7 +3443,7 @@ AS 50 AS Priority , 'Performance' AS FindingGroup , 'Poison Wait Detected: Serializable Locking' AS Finding , - 'https://BrentOzar.com/go/serializable' AS URL , + 'https://www.brentozar.com/go/serializable' AS URL , CONVERT(VARCHAR(10), (SUM([wait_time_ms]) / 1000) / 86400) + ':' + CONVERT(VARCHAR(20), DATEADD(s, (SUM([wait_time_ms]) / 1000), 0), 108) + ' of LCK_M_R% waits have been recorded. This wait often indicates killer performance problems.' FROM sys.[dm_os_wait_stats] WHERE wait_type IN ('LCK_M_RS_S', 'LCK_M_RS_U', 'LCK_M_RIn_NL','LCK_M_RIn_S', 'LCK_M_RIn_U','LCK_M_RIn_X', 'LCK_M_RX_S', 'LCK_M_RX_U','LCK_M_RX_X') @@ -3473,7 +3473,7 @@ AS 'Reliability' AS FindingGroup , 'Possibly Broken Log Shipping' AS Finding , d.[name] , - 'https://BrentOzar.com/go/shipping' AS URL , + 'https://www.brentozar.com/go/shipping' AS URL , d.[name] + ' is in a restoring state, but has not had a backup applied in the last two days. This is a possible indication of a broken transaction log shipping setup.' FROM [master].sys.databases d INNER JOIN [master].sys.database_mirroring dm ON d.database_id = dm.database_id @@ -3508,7 +3508,7 @@ AS ''Performance'' AS FindingsGroup, ''Change Tracking Enabled'' AS Finding, d.[name], - ''https://BrentOzar.com/go/tracking'' AS URL, + ''https://www.brentozar.com/go/tracking'' AS URL, ( d.[name] + '' has change tracking enabled. This is not a default setting, and it has some performance overhead. It keeps track of changes to rows in tables that have change tracking turned on.'' ) AS Details FROM sys.change_tracking_databases AS ctd INNER JOIN sys.databases AS d ON ctd.database_id = d.database_id OPTION (RECOMPILE)'; IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; @@ -3537,7 +3537,7 @@ AS 200 AS Priority , ''Informational'' AS FindingGroup , ''Backup Compression Default Off'' AS Finding , - ''https://BrentOzar.com/go/backup'' AS URL , + ''https://www.brentozar.com/go/backup'' AS URL , ''Uncompressed full backups have happened recently, and backup compression is not turned on at the server level. Backup compression is included with SQL Server 2008R2 & newer, even in Standard Edition. We recommend turning backup compression on by default so that ad-hoc backups will get compressed.'' FROM sys.configurations WHERE configuration_id = 1579 AND CAST(value_in_use AS INT) = 0 @@ -3569,7 +3569,7 @@ AS 100 AS Priority, ''Performance'' AS FindingsGroup, ''Memory Pressure Affecting Queries'' AS Finding, - ''https://BrentOzar.com/go/grants'' AS URL, + ''https://www.brentozar.com/go/grants'' AS URL, CAST(SUM(forced_grant_count) AS NVARCHAR(100)) + '' forced grants reported in the DMV sys.dm_exec_query_resource_semaphores, indicating memory pressure has affected query runtimes.'' FROM sys.dm_exec_query_resource_semaphores WHERE [forced_grant_count] IS NOT NULL OPTION (RECOMPILE);'; @@ -3597,7 +3597,7 @@ AS 150, 'Performance', 'Deadlocks Happening Daily', - 'https://BrentOzar.com/go/deadlocks', + 'https://www.brentozar.com/go/deadlocks', CAST(CAST(p.cntr_value / @DaysUptime AS BIGINT) AS NVARCHAR(100)) + ' average deadlocks per day. To find them, run sp_BlitzLock.' AS Details FROM sys.dm_os_performance_counters p INNER JOIN sys.databases d ON d.name = 'tempdb' @@ -3660,7 +3660,7 @@ AS Finding, URL, Details) - SELECT TOP 1 125, 10, 'Performance', 'Plan Cache Erased Recently', 'https://BrentOzar.com/askbrent/plan-cache-erased-recently/', + SELECT TOP 1 125, 10, 'Performance', 'Plan Cache Erased Recently', 'https://www.brentozar.com/askbrent/plan-cache-erased-recently/', 'The oldest query in the plan cache was created at ' + CAST(creation_time AS NVARCHAR(50)) + CASE WHEN @user_perm_gb_out IS NULL THEN '. Someone ran DBCC FREEPROCCACHE, restarted SQL Server, or it is under horrific memory pressure.' @@ -3685,7 +3685,7 @@ AS Finding, URL, Details) - VALUES(126, 5, 'Reliability', 'Priority Boost Enabled', 'https://BrentOzar.com/go/priorityboost/', + VALUES(126, 5, 'Reliability', 'Priority Boost Enabled', 'https://www.brentozar.com/go/priorityboost/', 'Priority Boost sounds awesome, but it can actually cause your SQL Server to crash.'); END; @@ -3708,7 +3708,7 @@ AS IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 128) WITH NOWAIT; INSERT INTO #BlitzResults(CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES(128, 20, 'Reliability', 'Unsupported Build of SQL Server', 'https://BrentOzar.com/go/unsupported', + VALUES(128, 20, 'Reliability', 'Unsupported Build of SQL Server', 'https://www.brentozar.com/go/unsupported', 'Version ' + CAST(@ProductVersionMajor AS VARCHAR(100)) + CASE WHEN @ProductVersionMajor >= 11 THEN '.' + CAST(@ProductVersionMinor AS VARCHAR(100)) + ' is no longer supported by Microsoft. You need to apply a service pack.' @@ -3833,7 +3833,7 @@ AS 10 AS Priority, ''Performance'' AS FindingsGroup, ''High Memory Use for In-Memory OLTP (Hekaton)'' AS Finding, - ''https://BrentOzar.com/go/hekaton'' AS URL, + ''https://www.brentozar.com/go/hekaton'' AS URL, CAST(CAST((SUM(mem.pages_kb / 1024.0) / CAST(value_in_use AS INT) * 100) AS INT) AS NVARCHAR(100)) + ''% of your '' + CAST(CAST((CAST(value_in_use AS DECIMAL(38,1)) / 1024) AS MONEY) AS NVARCHAR(100)) + ''GB of your max server memory is being used for in-memory OLTP tables (Hekaton). Microsoft recommends having 2X your Hekaton table space available in memory just for Hekaton, with a max of 250GB of in-memory data regardless of your server memory capacity.'' AS Details FROM sys.configurations c INNER JOIN sys.dm_os_memory_clerks mem ON mem.type = ''MEMORYCLERK_XTP'' WHERE c.name = ''max server memory (MB)'' @@ -3863,7 +3863,7 @@ AS 200 AS Priority, ''Performance'' AS FindingsGroup, ''In-Memory OLTP (Hekaton) In Use'' AS Finding, - ''https://BrentOzar.com/go/hekaton'' AS URL, + ''https://www.brentozar.com/go/hekaton'' AS URL, CAST(CAST((SUM(mem.pages_kb / 1024.0) / CAST(value_in_use AS INT) * 100) AS INT) AS NVARCHAR(100)) + ''% of your '' + CAST(CAST((CAST(value_in_use AS DECIMAL(38,1)) / 1024) AS MONEY) AS NVARCHAR(100)) + ''GB of your max server memory is being used for in-memory OLTP tables (Hekaton).'' AS Details FROM sys.configurations c INNER JOIN sys.dm_os_memory_clerks mem ON mem.type = ''MEMORYCLERK_XTP'' WHERE c.name = ''max server memory (MB)'' @@ -3892,7 +3892,7 @@ AS 100 AS Priority, ''In-Memory OLTP (Hekaton)'' AS FindingsGroup, ''Transaction Errors'' AS Finding, - ''https://BrentOzar.com/go/hekaton'' AS URL, + ''https://www.brentozar.com/go/hekaton'' AS URL, ''Since restart: '' + CAST(validation_failures AS NVARCHAR(100)) + '' validation failures, '' + CAST(dependencies_failed AS NVARCHAR(100)) + '' dependency failures, '' + CAST(write_conflicts AS NVARCHAR(100)) + '' write conflicts, '' + CAST(unique_constraint_violations AS NVARCHAR(100)) + '' unique constraint violations.'' AS Details FROM sys.dm_xtp_transaction_stats WHERE validation_failures <> 0 @@ -3928,7 +3928,7 @@ AS 170 AS Priority , 'Reliability' AS FindingsGroup , 'Database Files on Network File Shares' AS Finding , - 'https://BrentOzar.com/go/nas' AS URL , + 'https://www.brentozar.com/go/nas' AS URL , ( 'Files for this database are on: ' + LEFT(mf.physical_name, 30)) AS Details FROM sys.databases d INNER JOIN sys.master_files mf ON d.database_id = mf.database_id @@ -3961,7 +3961,7 @@ AS 170 AS Priority , 'Reliability' AS FindingsGroup , 'Database Files Stored in Azure' AS Finding , - 'https://BrentOzar.com/go/azurefiles' AS URL , + 'https://www.brentozar.com/go/azurefiles' AS URL , ( 'Files for this database are on: ' + LEFT(mf.physical_name, 30)) AS Details FROM sys.databases d INNER JOIN sys.master_files mf ON d.database_id = mf.database_id @@ -4054,7 +4054,7 @@ AS 50 AS Priority , 'Reliability' AS FindingsGroup , 'There Is An Error With The Default Trace' AS Finding , - 'https://BrentOzar.com/go/defaulttrace' AS URL , + 'https://www.brentozar.com/go/defaulttrace' AS URL , 'Somebody has been messing with your trace files. Check the files are present at ' + @base_tracefilename AS Details END @@ -4081,7 +4081,7 @@ AS 170 AS Priority , 'Reliability' AS FindingsGroup , 'Errors Logged Recently in the Default Trace' AS Finding , - 'https://BrentOzar.com/go/defaulttrace' AS URL , + 'https://www.brentozar.com/go/defaulttrace' AS URL , CAST(t.TextData AS NVARCHAR(4000)) AS Details FROM #fnTraceGettable t WHERE t.EventClass = 22 @@ -4114,7 +4114,7 @@ AS 50 AS Priority , 'Performance' AS FindingsGroup , 'File Growths Slow' AS Finding , - 'https://BrentOzar.com/go/filegrowth' AS URL , + 'https://www.brentozar.com/go/filegrowth' AS URL , CAST(COUNT(*) AS NVARCHAR(100)) + ' growths took more than 15 seconds each. Consider setting file autogrowth to a smaller increment.' AS Details FROM #fnTraceGettable t WHERE t.EventClass IN (92, 93) @@ -4139,7 +4139,7 @@ AS 100 AS Priority, ''Performance'' AS FindingsGroup, ''Many Plans for One Query'' AS Finding, - ''https://BrentOzar.com/go/parameterization'' AS URL, + ''https://www.brentozar.com/go/parameterization'' AS URL, CAST(COUNT(DISTINCT plan_handle) AS NVARCHAR(50)) + '' plans are present for a single query in the plan cache - meaning we probably have parameterization issues.'' AS Details FROM sys.dm_exec_query_stats qs CROSS APPLY sys.dm_exec_plan_attributes(qs.plan_handle) pa @@ -4173,7 +4173,7 @@ AS 100 AS Priority, ''Performance'' AS FindingsGroup, ''High Number of Cached Plans'' AS Finding, - ''https://BrentOzar.com/go/planlimits'' AS URL, + ''https://www.brentozar.com/go/planlimits'' AS URL, ''Your server configuration is limited to '' + CAST(ht.buckets_count * 4 AS VARCHAR(20)) + '' '' + ht.name + '', and you are currently caching '' + CAST(cc.entries_count AS VARCHAR(20)) + ''.'' AS Details FROM sys.dm_os_memory_cache_hash_tables ht INNER JOIN sys.dm_os_memory_cache_counters cc ON ht.name = cc.name AND ht.type = cc.type @@ -4201,7 +4201,7 @@ AS Finding, URL, Details) - SELECT 165, 50, 'Performance', 'Too Much Free Memory', 'https://BrentOzar.com/go/freememory', + SELECT 165, 50, 'Performance', 'Too Much Free Memory', 'https://www.brentozar.com/go/freememory', CAST((CAST(cFree.cntr_value AS BIGINT) / 1024 / 1024 ) AS NVARCHAR(100)) + N'GB of free memory inside SQL Server''s buffer pool, which is ' + CAST((CAST(cTotal.cntr_value AS BIGINT) / 1024 / 1024) AS NVARCHAR(100)) + N'GB. You would think lots of free memory would be good, but check out the URL for more information.' AS Details FROM sys.dm_os_performance_counters cFree INNER JOIN sys.dm_os_performance_counters cTotal ON cTotal.object_name LIKE N'%Memory Manager%' @@ -4247,71 +4247,71 @@ AS IF @Debug IN (1, 2) RAISERROR('Generating database defaults.', 0, 1) WITH NOWAIT; INSERT INTO #DatabaseDefaults - SELECT 'is_supplemental_logging_enabled', 0, 131, 210, 'Supplemental Logging Enabled', 'https://BrentOzar.com/go/dbdefaults', NULL + SELECT 'is_supplemental_logging_enabled', 0, 131, 210, 'Supplemental Logging Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL FROM sys.all_columns WHERE name = 'is_supplemental_logging_enabled' AND object_id = OBJECT_ID('sys.databases'); INSERT INTO #DatabaseDefaults - SELECT 'snapshot_isolation_state', 0, 132, 210, 'Snapshot Isolation Enabled', 'https://BrentOzar.com/go/dbdefaults', NULL + SELECT 'snapshot_isolation_state', 0, 132, 210, 'Snapshot Isolation Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL FROM sys.all_columns WHERE name = 'snapshot_isolation_state' AND object_id = OBJECT_ID('sys.databases'); INSERT INTO #DatabaseDefaults SELECT 'is_read_committed_snapshot_on', CASE WHEN SERVERPROPERTY('EngineEdition') = 5 THEN 1 ELSE 0 END, /* RCSI is always enabled in Azure SQL DB per https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/1919 */ - 133, 210, CASE WHEN SERVERPROPERTY('EngineEdition') = 5 THEN 'Read Committed Snapshot Isolation Disabled' ELSE 'Read Committed Snapshot Isolation Enabled' END, 'https://BrentOzar.com/go/dbdefaults', NULL + 133, 210, CASE WHEN SERVERPROPERTY('EngineEdition') = 5 THEN 'Read Committed Snapshot Isolation Disabled' ELSE 'Read Committed Snapshot Isolation Enabled' END, 'https://www.brentozar.com/go/dbdefaults', NULL FROM sys.all_columns WHERE name = 'is_read_committed_snapshot_on' AND object_id = OBJECT_ID('sys.databases'); INSERT INTO #DatabaseDefaults - SELECT 'is_auto_create_stats_incremental_on', 0, 134, 210, 'Auto Create Stats Incremental Enabled', 'https://BrentOzar.com/go/dbdefaults', NULL + SELECT 'is_auto_create_stats_incremental_on', 0, 134, 210, 'Auto Create Stats Incremental Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL FROM sys.all_columns WHERE name = 'is_auto_create_stats_incremental_on' AND object_id = OBJECT_ID('sys.databases'); INSERT INTO #DatabaseDefaults - SELECT 'is_ansi_null_default_on', 0, 135, 210, 'ANSI NULL Default Enabled', 'https://BrentOzar.com/go/dbdefaults', NULL + SELECT 'is_ansi_null_default_on', 0, 135, 210, 'ANSI NULL Default Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL FROM sys.all_columns WHERE name = 'is_ansi_null_default_on' AND object_id = OBJECT_ID('sys.databases'); INSERT INTO #DatabaseDefaults - SELECT 'is_recursive_triggers_on', 0, 136, 210, 'Recursive Triggers Enabled', 'https://BrentOzar.com/go/dbdefaults', NULL + SELECT 'is_recursive_triggers_on', 0, 136, 210, 'Recursive Triggers Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL FROM sys.all_columns WHERE name = 'is_recursive_triggers_on' AND object_id = OBJECT_ID('sys.databases'); INSERT INTO #DatabaseDefaults - SELECT 'is_trustworthy_on', 0, 137, 210, 'Trustworthy Enabled', 'https://BrentOzar.com/go/dbdefaults', NULL + SELECT 'is_trustworthy_on', 0, 137, 210, 'Trustworthy Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL FROM sys.all_columns WHERE name = 'is_trustworthy_on' AND object_id = OBJECT_ID('sys.databases'); INSERT INTO #DatabaseDefaults - SELECT 'is_broker_enabled', 0, 230, 210, 'Broker Enabled', 'https://BrentOzar.com/go/dbdefaults', NULL + SELECT 'is_broker_enabled', 0, 230, 210, 'Broker Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL FROM sys.all_columns WHERE name = 'is_broker_enabled' AND object_id = OBJECT_ID('sys.databases'); INSERT INTO #DatabaseDefaults - SELECT 'is_honor_broker_priority_on', 0, 231, 210, 'Honor Broker Priority Enabled', 'https://BrentOzar.com/go/dbdefaults', NULL + SELECT 'is_honor_broker_priority_on', 0, 231, 210, 'Honor Broker Priority Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL FROM sys.all_columns WHERE name = 'is_honor_broker_priority_on' AND object_id = OBJECT_ID('sys.databases'); INSERT INTO #DatabaseDefaults - SELECT 'is_parameterization_forced', 0, 138, 210, 'Forced Parameterization Enabled', 'https://BrentOzar.com/go/dbdefaults', NULL + SELECT 'is_parameterization_forced', 0, 138, 210, 'Forced Parameterization Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL FROM sys.all_columns WHERE name = 'is_parameterization_forced' AND object_id = OBJECT_ID('sys.databases'); /* Not alerting for this since we actually want it and we have a separate check for it: INSERT INTO #DatabaseDefaults - SELECT 'is_query_store_on', 0, 139, 210, 'Query Store Enabled', 'https://BrentOzar.com/go/dbdefaults', NULL + SELECT 'is_query_store_on', 0, 139, 210, 'Query Store Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL FROM sys.all_columns WHERE name = 'is_query_store_on' AND object_id = OBJECT_ID('sys.databases'); */ INSERT INTO #DatabaseDefaults - SELECT 'is_cdc_enabled', 0, 140, 210, 'Change Data Capture Enabled', 'https://BrentOzar.com/go/dbdefaults', NULL + SELECT 'is_cdc_enabled', 0, 140, 210, 'Change Data Capture Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL FROM sys.all_columns WHERE name = 'is_cdc_enabled' AND object_id = OBJECT_ID('sys.databases'); INSERT INTO #DatabaseDefaults - SELECT 'containment', 0, 141, 210, 'Containment Enabled', 'https://BrentOzar.com/go/dbdefaults', NULL + SELECT 'containment', 0, 141, 210, 'Containment Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL FROM sys.all_columns WHERE name = 'containment' AND object_id = OBJECT_ID('sys.databases'); INSERT INTO #DatabaseDefaults - SELECT 'target_recovery_time_in_seconds', 0, 142, 210, 'Target Recovery Time Changed', 'https://BrentOzar.com/go/dbdefaults', NULL + SELECT 'target_recovery_time_in_seconds', 0, 142, 210, 'Target Recovery Time Changed', 'https://www.brentozar.com/go/dbdefaults', NULL FROM sys.all_columns WHERE name = 'target_recovery_time_in_seconds' AND object_id = OBJECT_ID('sys.databases'); INSERT INTO #DatabaseDefaults - SELECT 'delayed_durability', 0, 143, 210, 'Delayed Durability Enabled', 'https://BrentOzar.com/go/dbdefaults', NULL + SELECT 'delayed_durability', 0, 143, 210, 'Delayed Durability Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL FROM sys.all_columns WHERE name = 'delayed_durability' AND object_id = OBJECT_ID('sys.databases'); INSERT INTO #DatabaseDefaults - SELECT 'is_memory_optimized_elevate_to_snapshot_on', 0, 144, 210, 'Memory Optimized Enabled', 'https://BrentOzar.com/go/dbdefaults', NULL + SELECT 'is_memory_optimized_elevate_to_snapshot_on', 0, 144, 210, 'Memory Optimized Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL FROM sys.all_columns WHERE name = 'is_memory_optimized_elevate_to_snapshot_on' AND object_id = OBJECT_ID('sys.databases') AND SERVERPROPERTY('EngineEdition') <> 8; /* Hekaton is always enabled in Managed Instances per https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/1919 */ @@ -4453,7 +4453,7 @@ IF @ProductVersionMajor >= 10 250 AS [Priority] , 'Informational' AS [FindingsGroup] , 'SQL Server is running under an NT Service account' AS [Finding] , - 'https://BrentOzar.com/go/setup' AS [URL] , + 'https://www.brentozar.com/go/setup' AS [URL] , ( 'I''m running as ' + [service_account] + '. I wish I had an Active Directory service account instead.' ) AS [Details] FROM @@ -4493,7 +4493,7 @@ IF @ProductVersionMajor >= 10 250 AS [Priority] , 'Informational' AS [FindingsGroup] , 'SQL Server Agent is running under an NT Service account' AS [Finding] , - 'https://BrentOzar.com/go/setup' AS [URL] , + 'https://www.brentozar.com/go/setup' AS [URL] , ( 'I''m running as ' + [service_account] + '. I wish I had an Active Directory service account instead.' ) AS [Details] FROM @@ -5039,7 +5039,7 @@ IF @ProductVersionMajor >= 10 20 AS [Priority] , 'Reliability' AS [FindingsGroup] , 'Memory Dumps Have Occurred' AS [Finding] , - 'https://BrentOzar.com/go/dump' AS [URL] , + 'https://www.brentozar.com/go/dump' AS [URL] , ( 'That ain''t good. I''ve had ' + CAST(COUNT(*) AS VARCHAR(100)) + ' memory dumps between ' + CAST(CAST(MIN([creation_time]) AS DATETIME) AS VARCHAR(100)) + @@ -5076,7 +5076,7 @@ IF @ProductVersionMajor >= 10 200 AS [Priority] , 'Licensing' AS [FindingsGroup] , 'Non-Production License' AS [Finding] , - 'https://BrentOzar.com/go/licensing' AS [URL] , + 'https://www.brentozar.com/go/licensing' AS [URL] , ( 'We''re not the licensing police, but if this is supposed to be a production server, and you''re running ' + CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) + ' the good folks at Microsoft might get upset with you. Better start counting those cores.' @@ -5108,7 +5108,7 @@ IF @ProductVersionMajor >= 10 200 AS [Priority] , 'Performance' AS [FindingsGroup] , 'Buffer Pool Extensions Enabled' AS [Finding] , - 'https://BrentOzar.com/go/bpe' AS [URL] , + 'https://www.brentozar.com/go/bpe' AS [URL] , ( 'You have Buffer Pool Extensions enabled, and one lives here: ' + [path] + '. It''s currently ' + @@ -5148,7 +5148,7 @@ IF @ProductVersionMajor >= 10 170 AS Priority , 'File Configuration' AS FindingsGroup , 'TempDB Has >16 Data Files' AS Finding , - 'https://BrentOzar.com/go/tempdb' AS URL , + 'https://www.brentozar.com/go/tempdb' AS URL , 'Woah, Nelly! TempDB has ' + CAST(COUNT_BIG(*) AS VARCHAR(30)) + '. Did you forget to terminate a loop somewhere?' AS Details FROM sys.[master_files] AS [mf] WHERE [mf].[database_id] = 2 AND [mf].[type] = 0 @@ -5183,7 +5183,7 @@ IF @ProductVersionMajor >= 10 200 AS Priority , 'Monitoring' AS FindingsGroup , 'Extended Events Hyperextension' AS Finding , - 'https://BrentOzar.com/go/xe' AS URL , + 'https://www.brentozar.com/go/xe' AS URL , 'Hey big spender, you have ' + CAST(COUNT_BIG(*) AS VARCHAR(30)) + ' Extended Events sessions running. You sure you meant to do that?' AS Details FROM sys.dm_xe_sessions WHERE [name] NOT IN @@ -5305,7 +5305,7 @@ IF @ProductVersionMajor >= 10 END AS Priority, 'Performance' AS [FindingsGroup] , 'Shrink Database Step In Maintenance Plan' AS [Finding] , - 'https://BrentOzar.com/go/autoshrink' AS [URL] , + 'https://www.brentozar.com/go/autoshrink' AS [URL] , 'The maintenance plan ' + [mps].[name] + ' has a step to shrink databases in it. Shrinking databases is as outdated as maintenance plans.' + CASE WHEN COALESCE(ssc.name,'0') != '0' THEN + ' (Schedule: [' + ssc.name + '])' ELSE + '' END AS [Details] FROM [maintenance_plan_steps] [mps] @@ -5391,7 +5391,7 @@ IF @ProductVersionMajor >= 10 20 AS Priority , ''Reliability'' AS FindingsGroup , ''No Failover Cluster Nodes Available'' AS Finding , - ''https://BrentOzar.com/go/node'' AS URL , + ''https://www.brentozar.com/go/node'' AS URL , ''There are no failover cluster nodes available if the active node fails'' AS Details FROM ( SELECT SUM(CASE WHEN [status] = 0 AND [is_current_owner] = 0 THEN 1 ELSE 0 END) AS [available_nodes] @@ -5427,7 +5427,7 @@ IF @ProductVersionMajor >= 10 50 AS [Priority] , 'Reliability' AS [FindingsGroup] , 'TempDB File Error' AS [Finding] , - 'https://BrentOzar.com/go/tempdboops' AS [URL] , + 'https://www.brentozar.com/go/tempdboops' AS [URL] , 'Mismatch between the number of TempDB files in sys.master_files versus tempdb.sys.database_files' AS [Details]; END; @@ -5463,7 +5463,7 @@ IF @ProductVersionMajor >= 10 10 AS Priority, 'Performance' AS FindingsGroup, 'CPU w/Odd Number of Cores' AS Finding, - 'https://BrentOzar.com/go/oddity' AS URL, + 'https://www.brentozar.com/go/oddity' AS URL, 'Node ' + CONVERT(VARCHAR(10), parent_node_id) + ' has ' + CONVERT(VARCHAR(10), COUNT(cpu_id)) + CASE WHEN COUNT(cpu_id) = 1 THEN ' core assigned to it. This is a really bad NUMA configuration.' ELSE ' cores assigned to it. This is a really bad NUMA configuration.' @@ -6034,7 +6034,7 @@ IF @ProductVersionMajor >= 10 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 99) WITH NOWAIT; - EXEC dbo.sp_MSforeachdb 'USE [?]; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; IF EXISTS (SELECT * FROM sys.tables WITH (NOLOCK) WHERE name = ''sysmergepublications'' ) IF EXISTS ( SELECT * FROM sysmergepublications WITH (NOLOCK) WHERE retention = 0) INSERT INTO #BlitzResults (CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details) SELECT DISTINCT 99, DB_NAME(), 110, ''Performance'', ''Infinite merge replication metadata retention period'', ''https://BrentOzar.com/go/merge'', (''The ['' + DB_NAME() + ''] database has merge replication metadata retention period set to infinite - this can be the case of significant performance issues.'')'; + EXEC dbo.sp_MSforeachdb 'USE [?]; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; IF EXISTS (SELECT * FROM sys.tables WITH (NOLOCK) WHERE name = ''sysmergepublications'' ) IF EXISTS ( SELECT * FROM sysmergepublications WITH (NOLOCK) WHERE retention = 0) INSERT INTO #BlitzResults (CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details) SELECT DISTINCT 99, DB_NAME(), 110, ''Performance'', ''Infinite merge replication metadata retention period'', ''https://www.brentozar.com/go/merge'', (''The ['' + DB_NAME() + ''] database has merge replication metadata retention period set to infinite - this can be the case of significant performance issues.'')'; END; /* Note that by using sp_MSforeachdb, we're running the query in all @@ -6068,7 +6068,7 @@ IF @ProductVersionMajor >= 10 200, ''Performance'', ''Query Store Disabled'', - ''https://BrentOzar.com/go/querystore'', + ''https://www.brentozar.com/go/querystore'', (''The new SQL Server 2016 Query Store feature has not been enabled on this database.'') FROM [?].sys.database_query_store_options WHERE desired_state = 0 AND N''?'' NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''DWConfiguration'', ''DWDiagnostics'', ''DWQueue'', ''ReportServer'', ''ReportServerTempDB'') OPTION (RECOMPILE)'; @@ -6099,7 +6099,7 @@ IF @ProductVersionMajor >= 10 20, ''Reliability'', ''Query Store Cleanup Disabled'', - ''https://BrentOzar.com/go/cleanup'', + ''https://www.brentozar.com/go/cleanup'', (''SQL 2016 RTM has a bug involving dumps that happen every time Query Store cleanup jobs run. This is fixed in CU1 and later: https://sqlserverupdates.com/sql-server-2016-updates/'') FROM sys.databases AS d WHERE d.is_query_store_on = 1 OPTION (RECOMPILE);'; @@ -6163,7 +6163,7 @@ IF @ProductVersionMajor >= 10 170, ''File Configuration'', ''Multiple Log Files on One Drive'', - ''https://BrentOzar.com/go/manylogs'', + ''https://www.brentozar.com/go/manylogs'', (''The ['' + DB_NAME() + ''] database has multiple log files on the '' + LEFT(physical_name, 1) + '' drive. This is not a performance booster because log file access is sequential, not parallel.'') FROM [?].sys.database_files WHERE type_desc = ''LOG'' AND N''?'' <> ''[tempdb]'' @@ -6193,7 +6193,7 @@ IF @ProductVersionMajor >= 10 170, ''File Configuration'', ''Uneven File Growth Settings in One Filegroup'', - ''https://BrentOzar.com/go/grow'', + ''https://www.brentozar.com/go/grow'', (''The ['' + DB_NAME() + ''] database has multiple data files in one filegroup, but they are not all set up to grow in identical amounts. This can lead to uneven file activity inside the filegroup.'') FROM [?].sys.database_files WHERE type_desc = ''ROWS'' @@ -6222,7 +6222,7 @@ IF @ProductVersionMajor >= 10 170 AS Priority, ''File Configuration'' AS FindingsGroup, ''File growth set to percent'', - ''https://BrentOzar.com/go/percentgrowth'' AS URL, + ''https://www.brentozar.com/go/percentgrowth'' AS URL, ''The ['' + DB_NAME() + ''] database file '' + f.physical_name + '' has grown to '' + CONVERT(NVARCHAR(10), CONVERT(NUMERIC(38, 2), (f.size / 128.) / 1024.)) + '' GB, and is using percent filegrowth settings. This can lead to slow performance during growths if Instant File Initialization is not enabled.'' FROM [?].sys.database_files f WHERE is_percent_growth = 1 and size > 128000 OPTION (RECOMPILE);'; @@ -6250,7 +6250,7 @@ IF @ProductVersionMajor >= 10 170 AS Priority, ''File Configuration'' AS FindingsGroup, ''File growth set to 1MB'', - ''https://BrentOzar.com/go/percentgrowth'' AS URL, + ''https://www.brentozar.com/go/percentgrowth'' AS URL, ''The ['' + DB_NAME() + ''] database file '' + f.physical_name + '' is using 1MB filegrowth settings, but it has grown to '' + CAST((f.size * 8 / 1000000) AS NVARCHAR(10)) + '' GB. Time to up the growth amount.'' FROM [?].sys.database_files f WHERE is_percent_growth = 0 and growth=128 and size > 128000 OPTION (RECOMPILE);'; @@ -6281,7 +6281,7 @@ IF @ProductVersionMajor >= 10 200, ''Licensing'', ''Enterprise Edition Features In Use'', - ''https://BrentOzar.com/go/ee'', + ''https://www.brentozar.com/go/ee'', (''The ['' + DB_NAME() + ''] database is using '' + feature_name + ''. If this database is restored onto a Standard Edition server, the restore will fail on versions prior to 2016 SP1.'') FROM [?].sys.dm_db_persisted_sku_features OPTION (RECOMPILE);'; END; @@ -6310,7 +6310,7 @@ IF @ProductVersionMajor >= 10 200 AS Priority , 'Informational' AS FindingsGroup , 'Replication In Use' AS Finding , - 'https://BrentOzar.com/go/repl' AS URL , + 'https://www.brentozar.com/go/repl' AS URL , ( 'Database [' + [name] + '] is a replication publisher, subscriber, or distributor.' ) AS Details FROM sys.databases @@ -6338,7 +6338,7 @@ IF @ProductVersionMajor >= 10 200, ''Informational'', ''Replication In Use'', - ''https://BrentOzar.com/go/repl'', + ''https://www.brentozar.com/go/repl'', (''['' + DB_NAME() + ''] has MSreplication_objects tables in it, indicating it is a replication subscriber.'') FROM [?].sys.tables WHERE name = ''dbo.MSreplication_objects'' AND ''?'' <> ''master'' OPTION (RECOMPILE)'; @@ -6367,7 +6367,7 @@ IF @ProductVersionMajor >= 10 150, ''Performance'', ''Triggers on Tables'', - ''https://BrentOzar.com/go/trig'', + ''https://www.brentozar.com/go/trig'', (''The ['' + DB_NAME() + ''] database has '' + CAST(SUM(1) AS NVARCHAR(50)) + '' triggers.'') FROM [?].sys.triggers t INNER JOIN [?].sys.objects o ON t.parent_id = o.object_id INNER JOIN [?].sys.schemas s ON o.schema_id = s.schema_id WHERE t.is_ms_shipped = 0 AND DB_NAME() != ''ReportServer'' @@ -6396,7 +6396,7 @@ IF @ProductVersionMajor >= 10 110, ''Performance'', ''Active Tables Without Clustered Indexes'', - ''https://BrentOzar.com/go/heaps'', + ''https://www.brentozar.com/go/heaps'', (''The ['' + DB_NAME() + ''] database has heaps - tables without a clustered index - that are being actively queried.'') FROM [?].sys.indexes i INNER JOIN [?].sys.objects o ON i.object_id = o.object_id INNER JOIN [?].sys.partitions p ON i.object_id = p.object_id AND i.index_id = p.index_id @@ -6429,7 +6429,7 @@ IF @ProductVersionMajor >= 10 100, ''Reliability'', ''Plan Guides Failing'', - ''https://BrentOzar.com/go/misguided'', + ''https://www.brentozar.com/go/misguided'', (''The ['' + DB_NAME() + ''] database has plan guides that are no longer valid, so the queries involved may be failing silently.'') FROM [?].sys.plan_guides g CROSS APPLY fn_validate_plan_guide(g.plan_guide_id) OPTION (RECOMPILE)'; END; @@ -6456,7 +6456,7 @@ IF @ProductVersionMajor >= 10 150, ''Performance'', ''Inactive Tables Without Clustered Indexes'', - ''https://BrentOzar.com/go/heaps'', + ''https://www.brentozar.com/go/heaps'', (''The ['' + DB_NAME() + ''] database has heaps - tables without a clustered index - that have not been queried since the last restart. These may be backup tables carelessly left behind.'') FROM [?].sys.indexes i INNER JOIN [?].sys.objects o ON i.object_id = o.object_id INNER JOIN [?].sys.partitions p ON i.object_id = p.object_id AND i.index_id = p.index_id @@ -6488,7 +6488,7 @@ IF @ProductVersionMajor >= 10 150, ''Performance'', ''Leftover Fake Indexes From Wizards'', - ''https://BrentOzar.com/go/hypo'', + ''https://www.brentozar.com/go/hypo'', (''The index ['' + DB_NAME() + ''].['' + s.name + ''].['' + o.name + ''].['' + i.name + ''] is a leftover hypothetical index from the Index Tuning Wizard or Database Tuning Advisor. This index is not actually helping performance and should be removed.'') from [?].sys.indexes i INNER JOIN [?].sys.objects o ON i.object_id = o.object_id INNER JOIN [?].sys.schemas s ON o.schema_id = s.schema_id WHERE i.is_hypothetical = 1 OPTION (RECOMPILE);'; @@ -6516,7 +6516,7 @@ IF @ProductVersionMajor >= 10 100, ''Performance'', ''Indexes Disabled'', - ''https://BrentOzar.com/go/ixoff'', + ''https://www.brentozar.com/go/ixoff'', (''The index ['' + DB_NAME() + ''].['' + s.name + ''].['' + o.name + ''].['' + i.name + ''] is disabled. This index is not actually helping performance and should either be enabled or removed.'') from [?].sys.indexes i INNER JOIN [?].sys.objects o ON i.object_id = o.object_id INNER JOIN [?].sys.schemas s ON o.schema_id = s.schema_id WHERE i.is_disabled = 1 OPTION (RECOMPILE);'; @@ -6544,7 +6544,7 @@ IF @ProductVersionMajor >= 10 150, ''Performance'', ''Foreign Keys Not Trusted'', - ''https://BrentOzar.com/go/trust'', + ''https://www.brentozar.com/go/trust'', (''The ['' + DB_NAME() + ''] database has foreign keys that were probably disabled, data was changed, and then the key was enabled again. Simply enabling the key is not enough for the optimizer to use this key - we have to alter the table using the WITH CHECK CHECK CONSTRAINT parameter.'') from [?].sys.foreign_keys i INNER JOIN [?].sys.objects o ON i.parent_object_id = o.object_id INNER JOIN [?].sys.schemas s ON o.schema_id = s.schema_id WHERE i.is_not_trusted = 1 AND i.is_not_for_replication = 0 AND i.is_disabled = 0 AND N''?'' NOT IN (''master'', ''model'', ''msdb'', ''ReportServer'', ''ReportServerTempDB'') OPTION (RECOMPILE);'; @@ -6572,7 +6572,7 @@ IF @ProductVersionMajor >= 10 150, ''Performance'', ''Check Constraint Not Trusted'', - ''https://BrentOzar.com/go/trust'', + ''https://www.brentozar.com/go/trust'', (''The check constraint ['' + DB_NAME() + ''].['' + s.name + ''].['' + o.name + ''].['' + i.name + ''] is not trusted - meaning, it was disabled, data was changed, and then the constraint was enabled again. Simply enabling the constraint is not enough for the optimizer to use this constraint - we have to alter the table using the WITH CHECK CHECK CONSTRAINT parameter.'') from [?].sys.check_constraints i INNER JOIN [?].sys.objects o ON i.parent_object_id = o.object_id INNER JOIN [?].sys.schemas s ON o.schema_id = s.schema_id @@ -6604,7 +6604,7 @@ IF @ProductVersionMajor >= 10 110 AS Priority, ''Performance'' AS FindingsGroup, ''Plan Guides Enabled'' AS Finding, - ''https://BrentOzar.com/go/guides'' AS URL, + ''https://www.brentozar.com/go/guides'' AS URL, (''Database ['' + DB_NAME() + ''] has query plan guides so a query will always get a specific execution plan. If you are having trouble getting query performance to improve, it might be due to a frozen plan. Review the DMV sys.plan_guides to learn more about the plan guides in place on this server.'') AS Details FROM [?].sys.plan_guides WHERE is_disabled = 0 OPTION (RECOMPILE);'; END; @@ -6632,7 +6632,7 @@ IF @ProductVersionMajor >= 10 100 AS Priority, ''Performance'' AS FindingsGroup, ''Fill Factor Changed'', - ''https://BrentOzar.com/go/fillfactor'' AS URL, + ''https://www.brentozar.com/go/fillfactor'' AS URL, ''The ['' + DB_NAME() + ''] database has '' + CAST(SUM(1) AS NVARCHAR(50)) + '' objects with fill factor = '' + CAST(fill_factor AS NVARCHAR(5)) + ''%. This can cause memory and storage performance problems, but may also prevent page splits.'' FROM [?].sys.indexes WHERE fill_factor <> 0 AND fill_factor < 80 AND is_disabled = 0 AND is_hypothetical = 0 @@ -6668,7 +6668,7 @@ IF @ProductVersionMajor >= 10 FindingsGroup = 'Performance', Finding = 'Stored Procedure WITH RECOMPILE', DatabaseName = DBName, - URL = 'https://BrentOzar.com/go/recompile', + URL = 'https://www.brentozar.com/go/recompile', Details = '[' + DBName + '].[' + SPSchema + '].[' + ProcName + '] has WITH RECOMPILE in the stored procedure code, which may cause increased CPU usage due to constant recompiles of the code.', CheckID = '78' FROM #Recompile AS TR WHERE ProcName NOT LIKE 'sp_AllNightLog%' AND ProcName NOT LIKE 'sp_AskBrent%' AND ProcName NOT LIKE 'sp_Blitz%'; @@ -6682,7 +6682,7 @@ IF @ProductVersionMajor >= 10 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 86) WITH NOWAIT; - EXEC dbo.sp_MSforeachdb 'USE [?]; INSERT INTO #BlitzResults (CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details) SELECT DISTINCT 86, DB_NAME(), 230, ''Security'', ''Elevated Permissions on a Database'', ''https://BrentOzar.com/go/elevated'', (''In ['' + DB_NAME() + ''], user ['' + u.name + ''] has the role ['' + g.name + '']. This user can perform tasks beyond just reading and writing data.'') FROM (SELECT memberuid = convert(int, member_principal_id), groupuid = convert(int, role_principal_id) FROM [?].sys.database_role_members) m inner join [?].dbo.sysusers u on m.memberuid = u.uid inner join sysusers g on m.groupuid = g.uid where u.name <> ''dbo'' and g.name in (''db_owner'' , ''db_accessadmin'' , ''db_securityadmin'' , ''db_ddladmin'') OPTION (RECOMPILE);'; + EXEC dbo.sp_MSforeachdb 'USE [?]; INSERT INTO #BlitzResults (CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details) SELECT DISTINCT 86, DB_NAME(), 230, ''Security'', ''Elevated Permissions on a Database'', ''https://www.brentozar.com/go/elevated'', (''In ['' + DB_NAME() + ''], user ['' + u.name + ''] has the role ['' + g.name + '']. This user can perform tasks beyond just reading and writing data.'') FROM (SELECT memberuid = convert(int, member_principal_id), groupuid = convert(int, role_principal_id) FROM [?].sys.database_role_members) m inner join [?].dbo.sysusers u on m.memberuid = u.uid inner join sysusers g on m.groupuid = g.uid where u.name <> ''dbo'' and g.name in (''db_owner'' , ''db_accessadmin'' , ''db_securityadmin'' , ''db_ddladmin'') OPTION (RECOMPILE);'; END; /*Check for non-aligned indexes in partioned databases*/ @@ -6726,7 +6726,7 @@ IF @ProductVersionMajor >= 10 'Performance' AS FindingsGroup , 'The partitioned database ' + dbname + ' may have non-aligned indexes' AS Finding , - 'https://BrentOzar.com/go/aligned' AS URL , + 'https://www.brentozar.com/go/aligned' AS URL , 'Having non-aligned indexes on partitioned tables may cause inefficient query plans and CPU pressure' AS Details FROM #partdb WHERE dbname IS NOT NULL @@ -6759,7 +6759,7 @@ IF @ProductVersionMajor >= 10 50, ''Reliability'', ''Full Text Indexes Not Updating'', - ''https://BrentOzar.com/go/fulltext'', + ''https://www.brentozar.com/go/fulltext'', (''At least one full text index in this database has not been crawled in the last week.'') from [?].sys.fulltext_indexes i WHERE change_tracking_state_desc <> ''AUTO'' AND i.is_enabled = 1 AND i.crawl_end_date < DATEADD(dd, -7, GETDATE()) OPTION (RECOMPILE);'; END; @@ -6786,7 +6786,7 @@ IF @ProductVersionMajor >= 10 110, ''Performance'', ''Parallelism Rocket Surgery'', - ''https://BrentOzar.com/go/makeparallel'', + ''https://www.brentozar.com/go/makeparallel'', (''['' + DB_NAME() + ''] has a make_parallel function, indicating that an advanced developer may be manhandling SQL Server into forcing queries to go parallel.'') from [?].INFORMATION_SCHEMA.ROUTINES WHERE ROUTINE_NAME = ''make_parallel'' AND ROUTINE_TYPE = ''FUNCTION'' OPTION (RECOMPILE);'; END; @@ -6819,7 +6819,7 @@ IF @ProductVersionMajor >= 10 200, ''Performance'', ''User-Created Statistics In Place'', - ''https://BrentOzar.com/go/userstats'', + ''https://www.brentozar.com/go/userstats'', (''['' + DB_NAME() + ''] has '' + CAST(SUM(1) AS NVARCHAR(10)) + '' user-created statistics. This indicates that someone is being a rocket scientist with the stats, and might actually be slowing things down, especially during stats updates.'') from [?].sys.stats WHERE user_created = 1 AND is_temporary = 0 HAVING SUM(1) > 0 OPTION (RECOMPILE);'; @@ -6840,7 +6840,7 @@ IF @ProductVersionMajor >= 10 200, ''Performance'', ''User-Created Statistics In Place'', - ''https://BrentOzar.com/go/userstats'', + ''https://www.brentozar.com/go/userstats'', (''['' + DB_NAME() + ''] has '' + CAST(SUM(1) AS NVARCHAR(10)) + '' user-created statistics. This indicates that someone is being a rocket scientist with the stats, and might actually be slowing things down, especially during stats updates.'') from [?].sys.stats WHERE user_created = 1 HAVING SUM(1) > 0 OPTION (RECOMPILE);'; @@ -6878,7 +6878,7 @@ IF @ProductVersionMajor >= 10 ,170 ,''File Configuration'' ,''High VLF Count'' - ,''https://BrentOzar.com/go/vlf'' + ,''https://www.brentozar.com/go/vlf'' ,''The ['' + DB_NAME() + ''] database has '' + CAST(COUNT(*) as VARCHAR(20)) + '' virtual log files (VLFs). This may be slowing down startup, restores, and even inserts/updates/deletes.'' FROM #LogInfo2012 WHERE EXISTS (SELECT name FROM master.sys.databases @@ -6911,7 +6911,7 @@ IF @ProductVersionMajor >= 10 ,170 ,''File Configuration'' ,''High VLF Count'' - ,''https://BrentOzar.com/go/vlf'' + ,''https://www.brentozar.com/go/vlf'' ,''The ['' + DB_NAME() + ''] database has '' + CAST(COUNT(*) as VARCHAR(20)) + '' virtual log files (VLFs). This may be slowing down startup, restores, and even inserts/updates/deletes.'' FROM #LogInfo WHERE EXISTS (SELECT name FROM master.sys.databases @@ -6932,7 +6932,7 @@ IF @ProductVersionMajor >= 10 EXEC dbo.sp_MSforeachdb 'USE [?]; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; INSERT INTO #BlitzResults (CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details) - SELECT DISTINCT 80, DB_NAME(), 170, ''Reliability'', ''Max File Size Set'', ''https://BrentOzar.com/go/maxsize'', + SELECT DISTINCT 80, DB_NAME(), 170, ''Reliability'', ''Max File Size Set'', ''https://www.brentozar.com/go/maxsize'', (''The ['' + DB_NAME() + ''] database file '' + df.name + '' has a max file size set to '' + CAST(CAST(df.max_size AS BIGINT) * 8 / 1024 AS VARCHAR(100)) + ''MB. If it runs out of space, the database will stop working even though there may be drive space available.'') @@ -7016,7 +7016,7 @@ IF @ProductVersionMajor >= 10 UNION ALL SELECT 24, 'LAST_QUERY_PLAN_STATS', '0', NULL, 255; EXEC dbo.sp_MSforeachdb 'USE [?]; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; INSERT INTO #BlitzResults (CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details) - SELECT def1.CheckID, DB_NAME(), 210, ''Non-Default Database Scoped Config'', dsc.[name], ''https://BrentOzar.com/go/dbscope'', (''Set value: '' + COALESCE(CAST(dsc.value AS NVARCHAR(100)),''Empty'') + '' Default: '' + COALESCE(CAST(def1.default_value AS NVARCHAR(100)),''Empty'') + '' Set value for secondary: '' + COALESCE(CAST(dsc.value_for_secondary AS NVARCHAR(100)),''Empty'') + '' Default value for secondary: '' + COALESCE(CAST(def1.default_value_for_secondary AS NVARCHAR(100)),''Empty'')) + SELECT def1.CheckID, DB_NAME(), 210, ''Non-Default Database Scoped Config'', dsc.[name], ''https://www.brentozar.com/go/dbscope'', (''Set value: '' + COALESCE(CAST(dsc.value AS NVARCHAR(100)),''Empty'') + '' Default: '' + COALESCE(CAST(def1.default_value AS NVARCHAR(100)),''Empty'') + '' Set value for secondary: '' + COALESCE(CAST(dsc.value_for_secondary AS NVARCHAR(100)),''Empty'') + '' Default value for secondary: '' + COALESCE(CAST(def1.default_value_for_secondary AS NVARCHAR(100)),''Empty'')) FROM [?].sys.database_scoped_configurations dsc INNER JOIN #DatabaseScopedConfigurationDefaults def1 ON dsc.configuration_id = def1.configuration_id LEFT OUTER JOIN #DatabaseScopedConfigurationDefaults def ON dsc.configuration_id = def.configuration_id AND (cast(dsc.value as nvarchar(100)) = cast(def.default_value as nvarchar(100)) OR dsc.value IS NULL) AND (dsc.value_for_secondary = def.default_value_for_secondary OR dsc.value_for_secondary IS NULL) @@ -7046,7 +7046,7 @@ IF @ProductVersionMajor >= 10 ,150 AS Priority ,''Performance'' AS FindingsGroup ,''Objects created with dangerous SET Options'' AS Finding - ,''https://BrentOzar.com/go/badset'' AS URL + ,''https://www.brentozar.com/go/badset'' AS URL ,''The '' + QUOTENAME(DB_NAME()) + '' database has '' + CONVERT(VARCHAR(20),COUNT(1)) + '' objects that were created with dangerous ANSI_NULL or QUOTED_IDENTIFIER options.'' @@ -7084,7 +7084,7 @@ IF @ProductVersionMajor >= 10 ,200 AS Priority ,''Reliability'' AS FindingsGroup ,''Resumable Index Operation Paused'' AS Finding - ,''https://BrentOzar.com/go/resumable'' AS URL + ,''https://www.brentozar.com/go/resumable'' AS URL ,iro.state_desc + N'' since '' + CONVERT(NVARCHAR(50), last_pause_time, 120) + '', '' + CAST(iro.percent_complete AS NVARCHAR(20)) + ''% complete: '' + CAST(iro.sql_text AS NVARCHAR(1000)) AS Details @@ -7115,7 +7115,7 @@ IF @ProductVersionMajor >= 10 -- ,110 AS Priority -- ,''Performance'' AS FindingsGroup -- ,''Statistics Without Histograms'' AS Finding - -- ,''https://BrentOzar.com/go/brokenstats'' AS URL + -- ,''https://www.brentozar.com/go/brokenstats'' AS URL -- ,CAST(COUNT(DISTINCT o.object_id) AS VARCHAR(100)) + '' tables have statistics that have not been updated since the database was restored or upgraded,'' -- + '' and have no data in their histogram. See the More Info URL for a script to update them. '' AS Details -- FROM sys.all_objects o @@ -7170,7 +7170,7 @@ IF @ProductVersionMajor >= 10 1 AS PRIORITY , 'Reliability' AS FindingsGroup , 'Last good DBCC CHECKDB over 2 weeks old' AS Finding , - 'https://BrentOzar.com/go/checkdb' AS URL , + 'https://www.brentozar.com/go/checkdb' AS URL , 'Last successful CHECKDB: ' + CASE DB2.Value WHEN '1900-01-01 00:00:00.000' @@ -7221,7 +7221,7 @@ IF @ProductVersionMajor >= 10 100 AS Priority , 'Performance' AS FindingsGroup , 'Single-Use Plans in Procedure Cache' AS Finding , - 'https://BrentOzar.com/go/single' AS URL , + 'https://www.brentozar.com/go/single' AS URL , ( CAST(COUNT(*) AS VARCHAR(10)) + ' query plans are taking up memory in the procedure cache. This may be wasted memory if we cache plans for queries that never get called again. This may be a good use case for SQL Server 2008''s Optimize for Ad Hoc or for Forced Parameterization.' ) AS Details FROM sys.dm_exec_cached_plans AS cp @@ -7420,7 +7420,7 @@ IF @ProductVersionMajor >= 10 120 AS Priority , 'Query Plans' AS FindingsGroup , 'Implicit Conversion' AS Finding , - 'https://BrentOzar.com/go/implicit' AS URL , + 'https://www.brentozar.com/go/implicit' AS URL , ( 'One of the top resource-intensive queries is comparing two fields that are not the same datatype.' ) AS Details , qs.query_plan , qs.query_plan_filtered @@ -7452,7 +7452,7 @@ IF @ProductVersionMajor >= 10 120 AS Priority , 'Query Plans' AS FindingsGroup , 'Implicit Conversion Affecting Cardinality' AS Finding , - 'https://BrentOzar.com/go/implicit' AS URL , + 'https://www.brentozar.com/go/implicit' AS URL , ( 'One of the top resource-intensive queries has an implicit conversion that is affecting cardinality estimation.' ) AS Details , qs.query_plan , qs.query_plan_filtered @@ -7483,7 +7483,7 @@ IF @ProductVersionMajor >= 10 120 AS Priority , 'Query Plans' AS FindingsGroup , 'RID or Key Lookups' AS Finding , - 'https://BrentOzar.com/go/lookup' AS URL , + 'https://www.brentozar.com/go/lookup' AS URL , 'One of the top resource-intensive queries contains RID or Key Lookups. Try to avoid them by creating covering indexes.' AS Details , qs.query_plan , qs.query_plan_filtered @@ -7514,7 +7514,7 @@ IF @ProductVersionMajor >= 10 120 AS Priority , 'Query Plans' AS FindingsGroup , 'Missing Index' AS Finding , - 'https://BrentOzar.com/go/missingindex' AS URL , + 'https://www.brentozar.com/go/missingindex' AS URL , ( 'One of the top resource-intensive queries may be dramatically improved by adding an index.' ) AS Details , qs.query_plan , qs.query_plan_filtered @@ -7545,7 +7545,7 @@ IF @ProductVersionMajor >= 10 120 AS Priority , 'Query Plans' AS FindingsGroup , 'Cursor' AS Finding , - 'https://BrentOzar.com/go/cursor' AS URL , + 'https://www.brentozar.com/go/cursor' AS URL , ( 'One of the top resource-intensive queries is using a cursor.' ) AS Details , qs.query_plan , qs.query_plan_filtered @@ -7577,7 +7577,7 @@ IF @ProductVersionMajor >= 10 120 AS Priority , 'Query Plans' AS FindingsGroup , 'Scalar UDFs' AS Finding , - 'https://BrentOzar.com/go/functions' AS URL , + 'https://www.brentozar.com/go/functions' AS URL , ( 'One of the top resource-intensive queries is using a user-defined scalar function that may inhibit parallelism.' ) AS Details , qs.query_plan , qs.query_plan_filtered @@ -7612,7 +7612,7 @@ IF @ProductVersionMajor >= 10 230 AS [Priority] , 'Security' AS [FindingsGroup] , 'Endpoints Owned by Users' AS [Finding] , - 'https://BrentOzar.com/go/owners' AS [URL] , + 'https://www.brentozar.com/go/owners' AS [URL] , ( 'Endpoint ' + ep.[name] + ' is owned by ' + SUSER_NAME(ep.principal_id) + '. If the endpoint owner login is disabled or not available due to Active Directory problems, the high availability will stop working.' ) AS [Details] FROM sys.database_mirroring_endpoints ep @@ -7643,7 +7643,7 @@ IF @ProductVersionMajor >= 10 200 AS Priority , 'Informational' AS FindingsGroup , '@@Servername Not Set' AS Finding , - 'https://BrentOzar.com/go/servername' AS URL , + 'https://www.brentozar.com/go/servername' AS URL , '@@Servername variable is null. You can fix it by executing: "sp_addserver '''', local"' AS Details; END; @@ -7674,7 +7674,7 @@ IF @ProductVersionMajor >= 10 200 AS Priority , 'Configuration' AS FindingsGroup , '@@Servername Not Correct' AS Finding , - 'https://BrentOzar.com/go/servername' AS URL , + 'https://www.brentozar.com/go/servername' AS URL , 'The @@Servername is different than the computer name, which may trigger certificate errors.' AS Details; END; @@ -7713,7 +7713,7 @@ IF @ProductVersionMajor >= 10 200 AS Priority , 'Monitoring' AS FindingsGroup , 'No Failsafe Operator Configured' AS Finding , - 'https://BrentOzar.com/go/failsafe' AS URL , + 'https://www.brentozar.com/go/failsafe' AS URL , ( 'No failsafe operator is configured on this server. This is a good idea just in-case there are issues with the [msdb] database that prevents alerting.' ) AS Details FROM @AlertInfo WHERE FailSafeOperator IS NULL; @@ -7792,7 +7792,7 @@ IF @ProductVersionMajor >= 10 50 AS Priority , 'Performance' AS FindingGroup , 'Poison Wait Detected: CMEMTHREAD & NUMA' AS Finding , - 'https://BrentOzar.com/go/poison' AS URL , + 'https://www.brentozar.com/go/poison' AS URL , CONVERT(VARCHAR(10), (MAX([wait_time_ms]) / 1000) / 86400) + ':' + CONVERT(VARCHAR(20), DATEADD(s, (MAX([wait_time_ms]) / 1000), 0), 108) + ' of this wait have been recorded' + CASE WHEN ts.status = 1 THEN ' despite enabling trace flag 8048 already.' ELSE '. In servers with over 8 cores per NUMA node, when CMEMTHREAD waits are a bottleneck, trace flag 8048 may be needed.' @@ -7830,7 +7830,7 @@ IF @ProductVersionMajor >= 10 50 AS Priority , 'Reliability' AS FindingsGroup , 'Transaction Log Larger than Data File' AS Finding , - 'https://BrentOzar.com/go/biglog' AS URL , + 'https://www.brentozar.com/go/biglog' AS URL , 'The database [' + DB_NAME(a.database_id) + '] has a ' + CAST((CAST(a.size AS BIGINT) * 8 / 1000000) AS NVARCHAR(20)) + ' GB transaction log file, larger than the total data file sizes. This may indicate that transaction log backups are not being performed or not performed often enough.' AS Details FROM sys.master_files a @@ -7874,7 +7874,7 @@ IF @ProductVersionMajor >= 10 200 AS Priority , 'Informational' AS FindingsGroup , 'Collation is ' + collation_name AS Finding , - 'https://BrentOzar.com/go/collate' AS URL , + 'https://www.brentozar.com/go/collate' AS URL , 'Collation differences between user databases and tempdb can cause conflicts especially when comparing string values' AS Details FROM sys.databases WHERE name NOT IN ( 'master', 'model', 'msdb') @@ -7913,7 +7913,7 @@ IF @ProductVersionMajor >= 10 170 AS Priority , 'Reliability' AS FindingsGroup , 'Database Snapshot Online' AS Finding , - 'https://BrentOzar.com/go/snapshot' AS URL , + 'https://www.brentozar.com/go/snapshot' AS URL , 'Database [' + dSnap.[name] + '] is a snapshot of [' + dOriginal.[name] @@ -7951,7 +7951,7 @@ IF @ProductVersionMajor >= 10 END AS Priority, 'Performance' AS FindingsGroup , 'Shrink Database Job' AS Finding , - 'https://BrentOzar.com/go/autoshrink' AS URL , + 'https://www.brentozar.com/go/autoshrink' AS URL , 'In the [' + j.[name] + '] job, step [' + step.[step_name] + '] has SHRINKDATABASE or SHRINKFILE, which may be causing database fragmentation.' @@ -8021,7 +8021,7 @@ IF @ProductVersionMajor >= 10 200 AS Priority , 'Informational' AS FindingsGroup , 'Agent Jobs Starting Simultaneously' AS Finding , - 'https://BrentOzar.com/go/busyagent/' AS URL , + 'https://www.brentozar.com/go/busyagent/' AS URL , ( 'Multiple SQL Server Agent jobs are configured to start simultaneously. For detailed schedule listings, see the query in the URL.' ) AS Details FROM msdb.dbo.sysjobactivity WHERE start_execution_date > DATEADD(dd, -14, GETDATE()) @@ -8137,7 +8137,7 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 250 AS [Priority] , 'Server Info' AS [FindingsGroup] , 'Locked Pages In Memory Enabled' AS [Finding] , - 'https://BrentOzar.com/go/lpim' AS [URL] , + 'https://www.brentozar.com/go/lpim' AS [URL] , ( 'You currently have ' + CASE WHEN [dopm].[locked_page_allocations_kb] / 1024. / 1024. > 0 THEN CAST([dopm].[locked_page_allocations_kb] / 1024 / 1024 AS VARCHAR(100)) @@ -8169,7 +8169,7 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 250 AS Priority , ''Server Info'' AS FindingsGroup , ''Memory Model Unconventional'' AS Finding , - ''https://BrentOzar.com/go/lpim'' AS URL , + ''https://www.brentozar.com/go/lpim'' AS URL , ''Memory Model: '' + CAST(sql_memory_model_desc AS NVARCHAR(100)) FROM sys.dm_os_sys_info WHERE sql_memory_model <> 1 OPTION (RECOMPILE);'; @@ -8208,7 +8208,7 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 250 AS [Priority] , 'Server Info' AS [FindingsGroup] , 'Instant File Initialization Enabled' AS [Finding] , - 'https://BrentOzar.com/go/instant' AS [URL] , + 'https://www.brentozar.com/go/instant' AS [URL] , 'The service account has the Perform Volume Maintenance Tasks permission.'; END; @@ -8230,7 +8230,7 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 50 AS Priority , ''Server Info'' AS FindingsGroup , ''Instant File Initialization Not Enabled'' AS Finding , - ''https://BrentOzar.com/go/instant'' AS URL , + ''https://www.brentozar.com/go/instant'' AS URL , ''Consider enabling IFI for faster restores and data file growths.'' FROM sys.dm_server_services WHERE instant_file_initialization_enabled <> ''Y'' AND filename LIKE ''%sqlservr.exe%'' OPTION (RECOMPILE);'; @@ -8259,7 +8259,7 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 250 AS Priority , 'Server Info' AS FindingsGroup , 'Server Name' AS Finding , - 'https://BrentOzar.com/go/servername' AS URL , + 'https://www.brentozar.com/go/servername' AS URL , @@SERVERNAME AS Details WHERE @@SERVERNAME IS NOT NULL; END; @@ -8530,7 +8530,7 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 250 AS Priority, ''Server Info'' AS FindingsGroup, ''Virtual Server'' AS Finding, - ''https://BrentOzar.com/go/virtual'' AS URL, + ''https://www.brentozar.com/go/virtual'' AS URL, ''Type: ('' + virtual_machine_type_desc + '')'' AS Details FROM sys.dm_os_sys_info WHERE virtual_machine_type <> 0 OPTION (RECOMPILE);'; @@ -8558,7 +8558,7 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 250 AS Priority, ''Server Info'' AS FindingsGroup, ''Container'' AS Finding, - ''https://BrentOzar.com/go/virtual'' AS URL, + ''https://www.brentozar.com/go/virtual'' AS URL, ''Type: ('' + container_type_desc + '')'' AS Details FROM sys.dm_os_sys_info WHERE container_type_desc <> ''NONE'' OPTION (RECOMPILE);'; @@ -8731,7 +8731,7 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 ,250 AS Priority ,'Server Info' AS FindingsGroup ,'Default Trace Contents' AS Finding - ,'https://BrentOzar.com/go/trace' AS URL + ,'https://www.brentozar.com/go/trace' AS URL ,'The default trace holds '+cast(DATEDIFF(hour,MIN(StartTime),GETDATE())as VARCHAR(30))+' hours of data' +' between '+cast(Min(StartTime) as VARCHAR(30))+' and '+cast(GETDATE()as VARCHAR(30)) +('. The default trace files are located in: '+left( @curr_tracefilename,len(@curr_tracefilename) - @indx) @@ -8811,7 +8811,7 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 URL , Details ) - VALUES (153, 240, 'Wait Stats', 'No Significant Waits Detected', 'https://BrentOzar.com/go/waits', 'This server might be just sitting around idle, or someone may have cleared wait stats recently.'); + VALUES (153, 240, 'Wait Stats', 'No Significant Waits Detected', 'https://www.brentozar.com/go/waits', 'This server might be just sitting around idle, or someone may have cleared wait stats recently.'); END; END; /* CheckID 152 */ diff --git a/sp_BlitzCache.sql b/sp_BlitzCache.sql index 17d57b426..b9c98621a 100644 --- a/sp_BlitzCache.sql +++ b/sp_BlitzCache.sql @@ -4101,7 +4101,7 @@ SELECT spi.SPID, ELSE @nl + N' We could not find any cached parameter values for this stored proc. ' END + CASE WHEN spi2.variable_name = N'**no_variable**' OR spi2.compile_time_value = N'**idk_man**' - THEN @nl + N'More info on possible reasons: https://BrentOzar.com/go/noplans ' + THEN @nl + N'More info on possible reasons: https://www.brentozar.com/go/noplans ' WHEN spi2.compile_time_value = N'NULL' THEN spi2.compile_time_value ELSE RTRIM(spi2.compile_time_value) @@ -4144,7 +4144,7 @@ SELECT spi.SPID, ELSE @nl + N' We could not find any cached parameter values for this stored proc. ' END + CASE WHEN spi2.variable_name = N'**no_variable**' OR spi2.compile_time_value = N'**idk_man**' - THEN @nl + N' More info on possible reasons: https://BrentOzar.com/go/noplans ' + THEN @nl + N' More info on possible reasons: https://www.brentozar.com/go/noplans ' WHEN spi2.compile_time_value = N'NULL' THEN spi2.compile_time_value ELSE RTRIM(spi2.compile_time_value) @@ -4748,7 +4748,7 @@ FROM ##BlitzCacheProcs b ) UPDATE b SET Warnings = ISNULL('Your query plan is >128 levels of nested nodes, and can''t be converted to XML. Use SELECT * FROM sys.dm_exec_text_query_plan('+ CONVERT(VARCHAR(128), ph.PlanHandle, 1) + ', 0, -1) to get more information' - , 'We couldn''t find a plan for this query. More info on possible reasons: https://BrentOzar.com/go/noplans') + , 'We couldn''t find a plan for this query. More info on possible reasons: https://www.brentozar.com/go/noplans') FROM ##BlitzCacheProcs b LEFT JOIN plan_handle ph ON b.PlanHandle = ph.PlanHandle @@ -5230,7 +5230,7 @@ BEGIN 100, 'Execution Pattern', 'Frequent Execution', - 'http://brentozar.com/blitzcache/frequently-executed-queries/', + 'https://www.brentozar.com/blitzcache/frequently-executed-queries/', 'Queries are being executed more than ' + CAST (@execution_threshold AS VARCHAR(5)) + ' times per minute. This can put additional load on the server, even when queries are lightweight.') ; @@ -5245,7 +5245,7 @@ BEGIN 50, 'Parameterization', 'Parameter Sniffing', - 'http://brentozar.com/blitzcache/parameter-sniffing/', + 'https://www.brentozar.com/blitzcache/parameter-sniffing/', 'There are signs of parameter sniffing (wide variance in rows return or time to execute). Investigate query patterns and tune code appropriately.') ; /* Forced execution plans */ @@ -5259,7 +5259,7 @@ BEGIN 50, 'Parameterization', 'Forced Plan', - 'http://brentozar.com/blitzcache/forced-plans/', + 'https://www.brentozar.com/blitzcache/forced-plans/', 'Execution plans have been compiled with forced plans, either through FORCEPLAN, plan guides, or forced parameterization. This will make general tuning efforts less effective.'); IF EXISTS (SELECT 1/0 @@ -5272,7 +5272,7 @@ BEGIN 200, 'Cursors', 'Cursor', - 'http://brentozar.com/blitzcache/cursors-found-slow-queries/', + 'https://www.brentozar.com/blitzcache/cursors-found-slow-queries/', 'There are cursors in the plan cache. This is neither good nor bad, but it is a thing. Cursors are weird in SQL Server.'); IF EXISTS (SELECT 1/0 @@ -5286,7 +5286,7 @@ BEGIN 200, 'Cursors', 'Optimistic Cursors', - 'http://brentozar.com/blitzcache/cursors-found-slow-queries/', + 'https://www.brentozar.com/blitzcache/cursors-found-slow-queries/', 'There are optimistic cursors in the plan cache, which can harm performance.'); IF EXISTS (SELECT 1/0 @@ -5300,7 +5300,7 @@ BEGIN 200, 'Cursors', 'Non-forward Only Cursors', - 'http://brentozar.com/blitzcache/cursors-found-slow-queries/', + 'https://www.brentozar.com/blitzcache/cursors-found-slow-queries/', 'There are non-forward only cursors in the plan cache, which can harm performance.'); IF EXISTS (SELECT 1/0 @@ -5314,7 +5314,7 @@ BEGIN 200, 'Cursors', 'Dynamic Cursors', - 'http://brentozar.com/blitzcache/cursors-found-slow-queries/', + 'https://www.brentozar.com/blitzcache/cursors-found-slow-queries/', 'Dynamic Cursors inhibit parallelism!.'); IF EXISTS (SELECT 1/0 @@ -5328,7 +5328,7 @@ BEGIN 200, 'Cursors', 'Fast Forward Cursors', - 'http://brentozar.com/blitzcache/cursors-found-slow-queries/', + 'https://www.brentozar.com/blitzcache/cursors-found-slow-queries/', 'Fast forward cursors inhibit parallelism!.'); IF EXISTS (SELECT 1/0 @@ -5341,7 +5341,7 @@ BEGIN 50, 'Parameterization', 'Forced Parameterization', - 'http://brentozar.com/blitzcache/forced-parameterization/', + 'https://www.brentozar.com/blitzcache/forced-parameterization/', 'Execution plans have been compiled with forced parameterization.') ; IF EXISTS (SELECT 1/0 @@ -5354,7 +5354,7 @@ BEGIN 200, 'Execution Plans', 'Parallel', - 'http://brentozar.com/blitzcache/parallel-plans-detected/', + 'https://www.brentozar.com/blitzcache/parallel-plans-detected/', 'Parallel plans detected. These warrant investigation, but are neither good nor bad.') ; IF EXISTS (SELECT 1/0 @@ -5367,7 +5367,7 @@ BEGIN 200, 'Execution Plans', 'Nearly Parallel', - 'http://brentozar.com/blitzcache/query-cost-near-cost-threshold-parallelism/', + 'https://www.brentozar.com/blitzcache/query-cost-near-cost-threshold-parallelism/', 'Queries near the cost threshold for parallelism. These may go parallel when you least expect it.') ; IF EXISTS (SELECT 1/0 @@ -5380,7 +5380,7 @@ BEGIN 50, 'Execution Plans', 'Plan Warnings', - 'http://brentozar.com/blitzcache/query-plan-warnings/', + 'https://www.brentozar.com/blitzcache/query-plan-warnings/', 'Warnings detected in execution plans. SQL Server is telling you that something bad is going on that requires your attention.') ; IF EXISTS (SELECT 1/0 @@ -5393,7 +5393,7 @@ BEGIN 50, 'Performance', 'Long Running Query', - 'http://brentozar.com/blitzcache/long-running-queries/', + 'https://www.brentozar.com/blitzcache/long-running-queries/', 'Long running queries have been found. These are queries with an average duration longer than ' + CAST(@long_running_query_warning_seconds / 1000 / 1000 AS VARCHAR(5)) + ' second(s). These queries should be investigated for additional tuning options.') ; @@ -5408,7 +5408,7 @@ BEGIN 50, 'Performance', 'Missing Indexes', - 'http://brentozar.com/blitzcache/missing-index-request/', + 'https://www.brentozar.com/blitzcache/missing-index-request/', 'Queries found with missing indexes.'); IF EXISTS (SELECT 1/0 @@ -5421,7 +5421,7 @@ BEGIN 200, 'Cardinality', 'Downlevel CE', - 'http://brentozar.com/blitzcache/legacy-cardinality-estimator/', + 'https://www.brentozar.com/blitzcache/legacy-cardinality-estimator/', 'A legacy cardinality estimator is being used by one or more queries. Investigate whether you need to be using this cardinality estimator. This may be caused by compatibility levels, global trace flags, or query level trace flags.'); IF EXISTS (SELECT 1/0 @@ -5434,7 +5434,7 @@ BEGIN 50, 'Performance', 'Implicit Conversions', - 'http://brentozar.com/go/implicit', + 'https://www.brentozar.com/go/implicit', 'One or more queries are comparing two fields that are not of the same data type.') ; IF EXISTS (SELECT 1/0 @@ -5447,7 +5447,7 @@ BEGIN 100, 'Performance', 'Busy Loops', - 'http://brentozar.com/blitzcache/busy-loops/', + 'https://www.brentozar.com/blitzcache/busy-loops/', 'Operations have been found that are executed 100 times more often than the number of rows returned by each iteration. This is an indicator that something is off in query execution.'); IF EXISTS (SELECT 1/0 @@ -5460,7 +5460,7 @@ BEGIN 50, 'Performance', 'Function Join', - 'http://brentozar.com/blitzcache/tvf-join/', + 'https://www.brentozar.com/blitzcache/tvf-join/', 'Execution plans have been found that join to table valued functions (TVFs). TVFs produce inaccurate estimates of the number of rows returned and can lead to any number of query plan problems.'); IF EXISTS (SELECT 1/0 @@ -5473,7 +5473,7 @@ BEGIN 50, 'Execution Plans', 'Compilation Timeout', - 'http://brentozar.com/blitzcache/compilation-timeout/', + 'https://www.brentozar.com/blitzcache/compilation-timeout/', 'Query compilation timed out for one or more queries. SQL Server did not find a plan that meets acceptable performance criteria in the time allotted so the best guess was returned. There is a very good chance that this plan isn''t even below average - it''s probably terrible.'); IF EXISTS (SELECT 1/0 @@ -5486,7 +5486,7 @@ BEGIN 50, 'Execution Plans', 'Compile Memory Limit Exceeded', - 'http://brentozar.com/blitzcache/compile-memory-limit-exceeded/', + 'https://www.brentozar.com/blitzcache/compile-memory-limit-exceeded/', 'The optimizer has a limited amount of memory available. One or more queries are complex enough that SQL Server was unable to allocate enough memory to fully optimize the query. A best fit plan was found, and it''s probably terrible.'); IF EXISTS (SELECT 1/0 @@ -5499,7 +5499,7 @@ BEGIN 50, 'Execution Plans', 'No Join Predicate', - 'http://brentozar.com/blitzcache/no-join-predicate/', + 'https://www.brentozar.com/blitzcache/no-join-predicate/', 'Operators in a query have no join predicate. This means that all rows from one table will be matched with all rows from anther table producing a Cartesian product. That''s a whole lot of rows. This may be your goal, but it''s important to investigate why this is happening.'); IF EXISTS (SELECT 1/0 @@ -5512,7 +5512,7 @@ BEGIN 200, 'Execution Plans', 'Multiple Plans', - 'http://brentozar.com/blitzcache/multiple-plans/', + 'https://www.brentozar.com/blitzcache/multiple-plans/', 'Queries exist with multiple execution plans (as determined by query_plan_hash). Investigate possible ways to parameterize these queries or otherwise reduce the plan count.'); IF EXISTS (SELECT 1/0 @@ -5525,7 +5525,7 @@ BEGIN 100, 'Performance', 'Unmatched Indexes', - 'http://brentozar.com/blitzcache/unmatched-indexes', + 'https://www.brentozar.com/blitzcache/unmatched-indexes', 'An index could have been used, but SQL Server chose not to use it - likely due to parameterization and filtered indexes.'); IF EXISTS (SELECT 1/0 @@ -5538,7 +5538,7 @@ BEGIN 100, 'Parameterization', 'Unparameterized Query', - 'http://brentozar.com/blitzcache/unparameterized-queries', + 'https://www.brentozar.com/blitzcache/unparameterized-queries', 'Unparameterized queries found. These could be ad hoc queries, data exploration, or queries using "OPTIMIZE FOR UNKNOWN".'); IF EXISTS (SELECT 1/0 @@ -5551,7 +5551,7 @@ BEGIN 100, 'Execution Plans', 'Trivial Plans', - 'http://brentozar.com/blitzcache/trivial-plans', + 'https://www.brentozar.com/blitzcache/trivial-plans', 'Trivial plans get almost no optimization. If you''re finding these in the top worst queries, something may be going wrong.'); IF EXISTS (SELECT 1/0 @@ -5564,7 +5564,7 @@ BEGIN 10, 'Execution Plans', 'Forced Serialization', - 'http://www.brentozar.com/blitzcache/forced-serialization/', + 'https://www.brentozar.com/blitzcache/forced-serialization/', 'Something in your plan is forcing a serial query. Further investigation is needed if this is not by design.') ; IF EXISTS (SELECT 1/0 @@ -5577,7 +5577,7 @@ BEGIN 100, 'Execution Plans', 'Expensive Key Lookup', - 'http://www.brentozar.com/blitzcache/expensive-key-lookups/', + 'https://www.brentozar.com/blitzcache/expensive-key-lookups/', 'There''s a key lookup in your plan that costs >=50% of the total plan cost.') ; IF EXISTS (SELECT 1/0 @@ -5590,7 +5590,7 @@ BEGIN 100, 'Execution Plans', 'Expensive Remote Query', - 'http://www.brentozar.com/blitzcache/expensive-remote-query/', + 'https://www.brentozar.com/blitzcache/expensive-remote-query/', 'There''s a remote query in your plan that costs >=50% of the total plan cost.') ; IF EXISTS (SELECT 1/0 @@ -5682,7 +5682,7 @@ BEGIN 100, 'Warnings', 'Operator Warnings', - 'http://brentozar.com/blitzcache/query-plan-warnings/', + 'https://www.brentozar.com/blitzcache/query-plan-warnings/', 'Check the plan for more details.') ; IF EXISTS (SELECT 1/0 @@ -5786,7 +5786,7 @@ BEGIN 100, 'Execution Plans', 'Expensive Sort', - 'http://www.brentozar.com/blitzcache/expensive-sorts/', + 'https://www.brentozar.com/blitzcache/expensive-sorts/', 'There''s a sort in your plan that costs >=50% of the total plan cost.') ; IF EXISTS (SELECT 1/0 @@ -6027,7 +6027,7 @@ BEGIN 100, 'Functions', 'MSTVFs', - 'http://brentozar.com/blitzcache/tvf-join/', + 'https://www.brentozar.com/blitzcache/tvf-join/', 'Execution plans have been found that join to table valued functions (TVFs). TVFs produce inaccurate estimates of the number of rows returned and can lead to any number of query plan problems.'); IF EXISTS (SELECT 1/0 @@ -6204,7 +6204,7 @@ BEGIN N'Large USERSTORE_TOKENPERM cache: ' + CONVERT(NVARCHAR(11), @user_perm_gb_out) + N'GB', N'The USERSTORE_TOKENPERM is taking up ' + CONVERT(NVARCHAR(11), @user_perm_percent) + N'% of the buffer pool, and your plan cache seems to be unstable', - N'https://brentozar.com/go/userstore', + N'https://www.brentozar.com/go/userstore', N'A growing USERSTORE_TOKENPERM cache can cause the plan cache to clear out' IF @v >= 11 diff --git a/sp_BlitzFirst.sql b/sp_BlitzFirst.sql index 85641df45..f36d135ec 100644 --- a/sp_BlitzFirst.sql +++ b/sp_BlitzFirst.sql @@ -1406,7 +1406,7 @@ BEGIN 1 AS Priority, 'Maintenance Tasks Running' AS FindingGroup, 'Backup Running' AS Finding, - 'http://www.BrentOzar.com/askbrent/backups/' AS URL, + 'https://www.brentozar.com/askbrent/backups/' AS URL, 'Backup of ' + DB_NAME(db.resource_database_id) + ' database (' + (SELECT CAST(CAST(SUM(size * 8.0 / 1024 / 1024) AS BIGINT) AS NVARCHAR) FROM #MasterFiles WHERE database_id = db.resource_database_id) + 'GB) ' + @LineFeed + CAST(r.percent_complete AS NVARCHAR(100)) + '% complete, has been running since ' + CAST(r.start_time AS NVARCHAR(100)) + '. ' + @LineFeed + CASE WHEN COALESCE(s.nt_user_name, s.login_name) IS NOT NULL THEN (' Login: ' + COALESCE(s.nt_user_name, s.login_name) + ' ') ELSE '' END AS Details, @@ -1458,7 +1458,7 @@ BEGIN 1 AS Priority, 'Maintenance Tasks Running' AS FindingGroup, 'DBCC CHECK* Running' AS Finding, - 'http://www.BrentOzar.com/askbrent/dbcc/' AS URL, + 'https://www.brentozar.com/askbrent/dbcc/' AS URL, 'Corruption check of ' + DB_NAME(db.resource_database_id) + ' database (' + (SELECT CAST(CAST(SUM(size * 8.0 / 1024 / 1024) AS BIGINT) AS NVARCHAR) FROM #MasterFiles WHERE database_id = db.resource_database_id) + 'GB) 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, @@ -1503,7 +1503,7 @@ BEGIN 1 AS Priority, 'Maintenance Tasks Running' AS FindingGroup, 'Restore Running' AS Finding, - 'http://www.BrentOzar.com/askbrent/backups/' AS URL, + 'https://www.brentozar.com/askbrent/backups/' AS URL, 'Restore of ' + DB_NAME(db.resource_database_id) + ' database (' + (SELECT CAST(CAST(SUM(size * 8.0 / 1024 / 1024) AS BIGINT) AS NVARCHAR) FROM #MasterFiles WHERE database_id = db.resource_database_id) + '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, @@ -1544,9 +1544,9 @@ BEGIN 1 AS Priority, 'SQL Server Internal Maintenance' AS FindingGroup, 'Database File Growing' AS Finding, - 'http://www.BrentOzar.com/go/instant' AS URL, + 'https://www.brentozar.com/go/instant' AS URL, 'SQL Server is waiting for Windows to provide storage space for a database restore, a data file growth, or a log file growth. This task has been running since ' + CAST(r.start_time AS NVARCHAR(100)) + '.' + @LineFeed + 'Check the query plan (expert mode) to identify the database involved.' AS Details, - 'Unfortunately, you can''t stop this, but you can prevent it next time. Check out http://www.BrentOzar.com/go/instant for details.' AS HowToStopIt, + 'Unfortunately, you can''t stop this, but you can prevent it next time. Check out https://www.brentozar.com/go/instant for details.' AS HowToStopIt, pl.query_plan AS QueryPlan, r.start_time AS StartTime, s.login_name AS LoginName, @@ -1578,7 +1578,7 @@ BEGIN 1 AS Priority, ''Query Problems'' AS FindingGroup, ''Long-Running Query Blocking Others'' AS Finding, - ''http://www.BrentOzar.com/go/blocking'' AS URL, + ''https://www.brentozar.com/go/blocking'' AS URL, ''Query in '' + COALESCE(DB_NAME(COALESCE((SELECT TOP 1 dbid FROM sys.dm_exec_sql_text(r.sql_handle)), (SELECT TOP 1 t.dbid FROM master..sysprocesses spBlocker CROSS APPLY sys.dm_exec_sql_text(spBlocker.sql_handle) t WHERE spBlocker.spid = tBlocked.blocking_session_id))), ''(Unknown)'') + '' has a last request start time of '' + CAST(s.last_request_start_time AS NVARCHAR(100)) + ''. Query follows: ' + @LineFeed + @LineFeed + @@ -1621,7 +1621,7 @@ BEGIN 50 AS Priority, 'Query Problems' AS FindingGroup, 'Plan Cache Erased Recently' AS Finding, - 'http://www.BrentOzar.com/askbrent/plan-cache-erased-recently/' AS URL, + 'https://www.brentozar.com/askbrent/plan-cache-erased-recently/' AS URL, 'The oldest query in the plan cache was created at ' + CAST(creation_time AS NVARCHAR(50)) + '. ' + @LineFeed + @LineFeed + 'This indicates that someone ran DBCC FREEPROCCACHE at that time,' + @LineFeed + 'Giving SQL Server temporary amnesia. Now, as queries come in,' + @LineFeed @@ -1646,7 +1646,7 @@ BEGIN 50 AS Priority, 'Query Problems' AS FindingGroup, 'Sleeping Query with Open Transactions' AS Finding, - 'http://www.brentozar.com/askbrent/sleeping-query-with-open-transactions/' AS URL, + 'https://www.brentozar.com/askbrent/sleeping-query-with-open-transactions/' AS URL, 'Database: ' + DB_NAME(db.resource_database_id) + @LineFeed + 'Host: ' + s.[host_name] + @LineFeed + 'Program: ' + s.[program_name] + @LineFeed + 'Asleep with open transactions and locks since ' + CAST(s.last_request_end_time AS NVARCHAR(100)) + '. ' AS Details, 'KILL ' + CAST(s.session_id AS NVARCHAR(100)) + ';' AS HowToStopIt, s.last_request_start_time AS StartTime, @@ -1732,7 +1732,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 1 AS Priority, 'Query Problems' AS FindingGroup, 'Query Rolling Back' AS Finding, - 'http://www.BrentOzar.com/askbrent/rollback/' AS URL, + 'https://www.brentozar.com/askbrent/rollback/' AS URL, 'Rollback started at ' + CAST(r.start_time AS NVARCHAR(100)) + ', is ' + CAST(r.percent_complete AS NVARCHAR(100)) + '% complete.' AS Details, 'Unfortunately, you can''t stop this. Whatever you do, don''t restart the server in an attempt to fix it - SQL Server will keep rolling back.' AS HowToStopIt, r.start_time AS StartTime, @@ -1813,7 +1813,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 50 AS Priority, 'Server Performance' AS FindingGroup, 'Too Much Free Memory' AS Finding, - 'https://BrentOzar.com/go/freememory' AS URL, + 'https://www.brentozar.com/go/freememory' AS URL, CAST((CAST(cFree.cntr_value AS BIGINT) / 1024 / 1024 ) AS NVARCHAR(100)) + N'GB of free memory inside SQL Server''s buffer pool,' + @LineFeed + ' which is ' + CAST((CAST(cTotal.cntr_value AS BIGINT) / 1024 / 1024) AS NVARCHAR(100)) + N'GB. You would think lots of free memory would be good, but check out the URL for more information.' AS Details, 'Run sp_BlitzCache @SortOrder = ''memory grant'' to find queries with huge memory grants and tune them.' AS HowToStopIt FROM sys.dm_os_performance_counters cFree @@ -1838,7 +1838,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 10 AS Priority, 'Server Performance' AS FindingGroup, 'Target Memory Lower Than Max' AS Finding, - 'https://BrentOzar.com/go/target' AS URL, + 'https://www.brentozar.com/go/target' AS URL, N'Max server memory is ' + CAST(cMax.value_in_use AS NVARCHAR(50)) + N' MB but target server memory is only ' + CAST((CAST(cTarget.cntr_value AS BIGINT) / 1024) AS NVARCHAR(50)) + N' MB,' + @LineFeed + N'indicating that SQL Server may be under external memory pressure or max server memory may be set too high.' AS Details, 'Investigate what OS processes are using memory, and double-check the max server memory setting.' AS HowToStopIt @@ -1864,7 +1864,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 'Database Size, Total GB' AS Finding, CAST(SUM (CAST(size AS BIGINT)*8./1024./1024.) AS VARCHAR(100)) AS Details, SUM (CAST(size AS BIGINT))*8./1024./1024. AS DetailsInt, - 'http://www.BrentOzar.com/askbrent/' AS URL + 'https://www.brentozar.com/askbrent/' AS URL FROM #MasterFiles WHERE database_id > 4; @@ -1881,7 +1881,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 'Database Count' AS Finding, CAST(SUM(1) AS VARCHAR(100)) AS Details, SUM (1) AS DetailsInt, - 'http://www.BrentOzar.com/askbrent/' AS URL + 'https://www.brentozar.com/askbrent/' AS URL FROM sys.databases WHERE database_id > 4; @@ -1936,7 +1936,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, / CAST(@MaxWorkspace AS MONEY)) * 100, 0) AS NVARCHAR(50)) + '%' + @LineFeed + 'Oldest Grant in seconds: ' + CAST(ISNULL(DATEDIFF(SECOND, MIN(Grants.request_time), GETDATE()), 0) AS NVARCHAR(50)) AS Details, (SELECT COUNT(*) FROM sys.dm_exec_query_memory_grants WHERE queue_id IS NULL) AS DetailsInt, - 'http://www.BrentOzar.com/askbrent/' AS URL + 'https://www.brentozar.com/askbrent/' AS URL FROM sys.dm_exec_query_memory_grants AS Grants; /* Query Problems - Queries with high memory grants - CheckID 46 */ @@ -2146,7 +2146,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 100 AS Priority, ''Query Performance'' AS FindingsGroup, ''Queries with 10000x cardinality misestimations'' AS Findings, - ''https://brentozar.com/go/skewedup'' AS URL, + ''https://www.brentozar.com/go/skewedup'' AS URL, ''The query on SPID '' + RTRIM(b.session_id) + '' has been running for '' @@ -2197,7 +2197,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 100 AS Priority, ''Query Performance'' AS FindingsGroup, ''Queries with 10000x skewed parallelism'' AS Findings, - ''https://brentozar.com/go/skewedup'' AS URL, + ''https://www.brentozar.com/go/skewedup'' AS URL, ''The query on SPID '' + RTRIM(p.session_id) + '' has been running for '' @@ -2255,7 +2255,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, END INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) - SELECT 24, 50, 'Server Performance', 'High CPU Utilization', CAST(100 - SystemIdle AS NVARCHAR(20)) + N'%.', 100 - SystemIdle, 'http://www.BrentOzar.com/go/cpu' + SELECT 24, 50, 'Server Performance', 'High CPU Utilization', CAST(100 - SystemIdle AS NVARCHAR(20)) + N'%.', 100 - SystemIdle, 'https://www.brentozar.com/go/cpu' FROM ( SELECT record, record.value('(./Record/SchedulerMonitorEvent/SystemHealth/SystemIdle)[1]', 'int') AS SystemIdle @@ -2299,7 +2299,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 'CPU Utilization', y.system_idle + N'%. Ring buffer details: ' + CAST(y.record AS NVARCHAR(4000)), y.system_idle , - 'http://www.BrentOzar.com/go/cpu', + 'https://www.brentozar.com/go/cpu', STUFF(( SELECT TOP 2147483647 CHAR(10) + CHAR(13) + y2.system_idle @@ -2321,7 +2321,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, END INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) - SELECT 28, 50, 'Server Performance', 'High CPU Utilization - Not SQL', CONVERT(NVARCHAR(100),100 - (y.SQLUsage + y.SystemIdle)) + N'% - Other Processes (not SQL Server) are using this much CPU. This may impact on the performance of your SQL Server instance', 100 - (y.SQLUsage + y.SystemIdle), 'http://www.BrentOzar.com/go/cpu' + SELECT 28, 50, 'Server Performance', 'High CPU Utilization - Not SQL', CONVERT(NVARCHAR(100),100 - (y.SQLUsage + y.SystemIdle)) + N'% - Other Processes (not SQL Server) are using this much CPU. This may impact on the performance of your SQL Server instance', 100 - (y.SQLUsage + y.SystemIdle), 'https://www.brentozar.com/go/cpu' FROM ( SELECT record, record.value('(./Record/SchedulerMonitorEvent/SystemHealth/SystemIdle)[1]', 'int') AS SystemIdle @@ -2406,7 +2406,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 50 AS Priority, 'Query Problems' AS FindingGroup, 'Statistics Updated Recently' AS Finding, - 'http://www.BrentOzar.com/go/stats' AS URL, + 'https://www.brentozar.com/go/stats' AS URL, 'In the last 15 minutes, statistics were updated. To see which ones, click the HowToStopIt column.' + @LineFeed + @LineFeed + 'This effectively clears the plan cache for queries that involve these tables,' + @LineFeed + 'which thereby causes parameter sniffing: those queries are now getting brand new' + @LineFeed @@ -2536,7 +2536,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, END INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (18, 210, 'Query Stats', 'Plan Cache Analysis Skipped', 'http://www.BrentOzar.com/go/topqueries', + VALUES (18, 210, 'Query Stats', 'Plan Cache Analysis Skipped', 'https://www.brentozar.com/go/topqueries', 'Due to excessive load, the plan cache analysis was skipped. To override this, use @ExpertMode = 1.'); END; @@ -2679,7 +2679,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, END INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, QueryText, QueryStatsNowID, QueryStatsFirstID, PlanHandle, QueryHash) - SELECT 17, 210, 'Query Stats', 'Most Resource-Intensive Queries', 'http://www.BrentOzar.com/go/topqueries', + SELECT 17, 210, 'Query Stats', 'Most Resource-Intensive Queries', 'https://www.brentozar.com/go/topqueries', 'Query stats during the sample:' + @LineFeed + 'Executions: ' + CAST(qsNow.execution_count - (COALESCE(qsFirst.execution_count, 0)) AS NVARCHAR(100)) + @LineFeed + 'Elapsed Time: ' + CAST(qsNow.total_elapsed_time - (COALESCE(qsFirst.total_elapsed_time, 0)) AS NVARCHAR(100)) + @LineFeed + @@ -2767,7 +2767,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 10 AS Priority, 'Server Performance' AS FindingGroup, 'Poison Wait Detected: ' + wNow.wait_type AS Finding, - N'http://www.brentozar.com/go/poison/#' + wNow.wait_type AS URL, + N'https://www.brentozar.com/go/poison/#' + wNow.wait_type AS URL, 'For ' + CAST(((wNow.wait_time_ms - COALESCE(wBase.wait_time_ms,0)) / 1000) AS NVARCHAR(100)) + ' seconds over the last ' + CASE @Seconds WHEN 0 THEN (CAST(DATEDIFF(dd,@StartSampleTime,@FinishSampleTime) AS NVARCHAR(10)) + ' days') ELSE (CAST(@Seconds AS NVARCHAR(10)) + ' seconds') END + ', SQL Server was waiting on this particular bottleneck.' + @LineFeed + @LineFeed AS Details, 'See the URL for more details on how to mitigate this wait type.' AS HowToStopIt, ((wNow.wait_time_ms - COALESCE(wBase.wait_time_ms,0)) / 1000) AS DetailsInt @@ -2790,7 +2790,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 50 AS Priority, 'Server Performance' AS FindingGroup, 'Slow Data File Reads' AS Finding, - 'http://www.BrentOzar.com/go/slow/' AS URL, + 'https://www.brentozar.com/go/slow/' AS URL, 'Your server is experiencing PAGEIOLATCH% waits due to slow data file reads. This file is one of the reasons why.' + @LineFeed + 'File: ' + fNow.PhysicalName + @LineFeed + 'Number of reads during the sample: ' + CAST((fNow.num_of_reads - fBase.num_of_reads) AS NVARCHAR(20)) + @LineFeed @@ -2820,7 +2820,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 50 AS Priority, 'Server Performance' AS FindingGroup, 'Slow Log File Writes' AS Finding, - 'http://www.BrentOzar.com/go/slow/' AS URL, + 'https://www.brentozar.com/go/slow/' AS URL, 'Your server is experiencing WRITELOG waits due to slow log file writes. This file is one of the reasons why.' + @LineFeed + 'File: ' + fNow.PhysicalName + @LineFeed + 'Number of writes during the sample: ' + CAST((fNow.num_of_writes - fBase.num_of_writes) AS NVARCHAR(20)) + @LineFeed @@ -2849,7 +2849,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 1 AS Priority, 'SQL Server Internal Maintenance' AS FindingGroup, 'Log File Growing' AS Finding, - 'http://www.BrentOzar.com/askbrent/file-growing/' AS URL, + 'https://www.brentozar.com/askbrent/file-growing/' AS URL, 'Number of growths during the sample: ' + CAST(ps.value_delta AS NVARCHAR(20)) + @LineFeed + 'Determined by sampling Perfmon counter ' + ps.object_name + ' - ' + ps.counter_name + @LineFeed AS Details, 'Pre-grow data and log files during maintenance windows so that they do not grow during production loads. See the URL for more details.' AS HowToStopIt @@ -2871,7 +2871,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 1 AS Priority, 'SQL Server Internal Maintenance' AS FindingGroup, 'Log File Shrinking' AS Finding, - 'http://www.BrentOzar.com/askbrent/file-shrinking/' AS URL, + 'https://www.brentozar.com/askbrent/file-shrinking/' AS URL, 'Number of shrinks during the sample: ' + CAST(ps.value_delta AS NVARCHAR(20)) + @LineFeed + 'Determined by sampling Perfmon counter ' + ps.object_name + ' - ' + ps.counter_name + @LineFeed AS Details, 'Pre-grow data and log files during maintenance windows so that they do not grow during production loads. See the URL for more details.' AS HowToStopIt @@ -2892,7 +2892,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 50 AS Priority, 'Query Problems' AS FindingGroup, 'Compilations/Sec High' AS Finding, - 'http://www.BrentOzar.com/askbrent/compilations/' AS URL, + 'https://www.brentozar.com/askbrent/compilations/' AS URL, 'Number of batch requests during the sample: ' + CAST(ps.value_delta AS NVARCHAR(20)) + @LineFeed + 'Number of compilations during the sample: ' + CAST(psComp.value_delta AS NVARCHAR(20)) + @LineFeed + 'For OLTP environments, Microsoft recommends that 90% of batch requests should hit the plan cache, and not be compiled from scratch. We are exceeding that threshold.' + @LineFeed AS Details, @@ -2919,7 +2919,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 50 AS Priority, 'Query Problems' AS FindingGroup, 'Re-Compilations/Sec High' AS Finding, - 'http://www.BrentOzar.com/askbrent/recompilations/' AS URL, + 'https://www.brentozar.com/askbrent/recompilations/' AS URL, 'Number of batch requests during the sample: ' + CAST(ps.value_delta AS NVARCHAR(20)) + @LineFeed + 'Number of recompilations during the sample: ' + CAST(psComp.value_delta AS NVARCHAR(20)) + @LineFeed + 'More than 10% of our queries are being recompiled. This is typically due to statistics changing on objects.' + @LineFeed AS Details, @@ -2946,7 +2946,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 40 AS Priority, 'Table Problems' AS FindingGroup, 'Forwarded Fetches/Sec High' AS Finding, - 'https://BrentOzar.com/go/fetch/' AS URL, + 'https://www.brentozar.com/go/fetch/' AS URL, CAST(ps.value_delta AS NVARCHAR(20)) + ' forwarded fetches (from SQLServer:Access Methods counter)' + @LineFeed + 'Check your heaps: they need to be rebuilt, or they need a clustered index applied.' + @LineFeed AS Details, 'Rebuild your heaps. If you use Ola Hallengren maintenance scripts, those do not rebuild heaps by default: https://www.brentozar.com/archive/2016/07/fix-forwarded-records/' AS HowToStopIt @@ -2967,7 +2967,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 40 AS Priority, ''Table Problems'' AS FindingGroup, ''Forwarded Fetches/Sec High: TempDB Object'' AS Finding, - ''https://BrentOzar.com/go/fetch/'' AS URL, + ''https://www.brentozar.com/go/fetch/'' AS URL, CAST(COALESCE(os.forwarded_fetch_count,0) - COALESCE(os_prior.forwarded_fetch_count,0) AS NVARCHAR(20)) + '' forwarded fetches on '' + CASE WHEN OBJECT_NAME(os.object_id) IS NULL THEN ''an unknown table '' WHEN LEN(OBJECT_NAME(os.object_id)) < 50 THEN ''a table variable, internal identifier '' + OBJECT_NAME(os.object_id) @@ -2995,7 +2995,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 50 AS Priority, 'In-Memory OLTP' AS FindingGroup, 'Garbage Collection in Progress' AS Finding, - 'https://BrentOzar.com/go/garbage/' AS URL, + 'https://www.brentozar.com/go/garbage/' AS URL, CAST(ps.value_delta AS NVARCHAR(50)) + ' rows processed (from SQL Server YYYY XTP Garbage Collection:Rows processed/sec counter)' + @LineFeed + 'This can happen due to memory pressure (causing In-Memory OLTP to shrink its footprint) or' + @LineFeed + 'due to transactional workloads that constantly insert/delete data.' AS Details, @@ -3018,7 +3018,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 100 AS Priority, 'In-Memory OLTP' AS FindingGroup, 'Transactions Aborted' AS Finding, - 'https://BrentOzar.com/go/aborted/' AS URL, + 'https://www.brentozar.com/go/aborted/' AS URL, CAST(ps.value_delta AS NVARCHAR(50)) + ' transactions aborted (from SQL Server YYYY XTP Transactions:Transactions aborted/sec counter)' + @LineFeed + 'This may indicate that data is changing, or causing folks to retry their transactions, thereby increasing load.' AS Details, 'Dig into your In-Memory OLTP transactions to figure out which ones are failing and being retried.' AS HowToStopIt @@ -3040,7 +3040,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 100 AS Priority, 'Query Problems' AS FindingGroup, 'Suboptimal Plans/Sec High' AS Finding, - 'https://BrentOzar.com/go/suboptimal/' AS URL, + 'https://www.brentozar.com/go/suboptimal/' AS URL, CAST(ps.value_delta AS NVARCHAR(50)) + ' plans reported in the ' + CAST(ps.instance_name AS NVARCHAR(100)) + ' workload group (from Workload GroupStats:Suboptimal plans/sec counter)' + @LineFeed + 'Even if you are not using Resource Governor, it still tracks information about user queries, memory grants, etc.' AS Details, 'Check out sp_BlitzCache to get more information about recent queries, or try sp_BlitzWho to see currently running queries.' AS HowToStopIt @@ -3064,7 +3064,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 10 AS Priority, 'Azure Performance' AS FindingGroup, 'Database is Maxed Out' AS Finding, - 'https://BrentOzar.com/go/maxedout' AS URL, + 'https://www.brentozar.com/go/maxedout' AS URL, N'At ' + CONVERT(NVARCHAR(100), s.end_time ,121) + N', your database approached (or hit) your DTU limits:' + @LineFeed + N'Average CPU percent: ' + CAST(avg_cpu_percent AS NVARCHAR(50)) + @LineFeed + N'Average data IO percent: ' + CAST(avg_data_io_percent AS NVARCHAR(50)) + @LineFeed @@ -3092,7 +3092,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 250 AS Priority, 'Server Info' AS FindingGroup, 'Batch Requests per Sec' AS Finding, - 'http://www.BrentOzar.com/go/measure' AS URL, + 'https://www.brentozar.com/go/measure' AS URL, CAST(CAST(ps.value_delta AS MONEY) / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS NVARCHAR(20)) AS Details, ps.value_delta / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS DetailsInt FROM #PerfmonStats ps @@ -3118,7 +3118,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 250 AS Priority, 'Server Info' AS FindingGroup, 'SQL Compilations per Sec' AS Finding, - 'http://www.BrentOzar.com/go/measure' AS URL, + 'https://www.brentozar.com/go/measure' AS URL, CAST(ps.value_delta / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS NVARCHAR(20)) AS Details, ps.value_delta / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS DetailsInt FROM #PerfmonStats ps @@ -3141,7 +3141,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 250 AS Priority, 'Server Info' AS FindingGroup, 'SQL Re-Compilations per Sec' AS Finding, - 'http://www.BrentOzar.com/go/measure' AS URL, + 'https://www.brentozar.com/go/measure' AS URL, CAST(ps.value_delta / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS NVARCHAR(20)) AS Details, ps.value_delta / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS DetailsInt FROM #PerfmonStats ps @@ -3167,7 +3167,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 250 AS Priority, 'Server Info' AS FindingGroup, 'Wait Time per Core per Sec' AS Finding, - 'http://www.BrentOzar.com/go/measure' AS URL, + 'https://www.brentozar.com/go/measure' AS URL, CAST((CAST(waits2.waits_ms - waits1.waits_ms AS MONEY)) / 1000 / i.cpu_count / DATEDIFF(ss, waits1.SampleTime, waits2.SampleTime) AS NVARCHAR(20)) AS Details, (waits2.waits_ms - waits1.waits_ms) / 1000 / i.cpu_count / DATEDIFF(ss, waits1.SampleTime, waits2.SampleTime) AS DetailsInt FROM cores i @@ -3233,7 +3233,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, END INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) - SELECT 24, 50, 'Server Performance', 'High CPU Utilization', CAST(100 - SystemIdle AS NVARCHAR(20)) + N'%. Ring buffer details: ' + CAST(record AS NVARCHAR(4000)), 100 - SystemIdle, 'http://www.BrentOzar.com/go/cpu' + SELECT 24, 50, 'Server Performance', 'High CPU Utilization', CAST(100 - SystemIdle AS NVARCHAR(20)) + N'%. Ring buffer details: ' + CAST(record AS NVARCHAR(4000)), 100 - SystemIdle, 'https://www.brentozar.com/go/cpu' FROM ( SELECT record, record.value('(./Record/SchedulerMonitorEvent/SystemHealth/SystemIdle)[1]', 'int') AS SystemIdle @@ -3253,7 +3253,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, END INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) - SELECT 23, 250, 'Server Info', 'CPU Utilization', CAST(100 - SystemIdle AS NVARCHAR(20)) + N'%. Ring buffer details: ' + CAST(record AS NVARCHAR(4000)), 100 - SystemIdle, 'http://www.BrentOzar.com/go/cpu' + SELECT 23, 250, 'Server Info', 'CPU Utilization', CAST(100 - SystemIdle AS NVARCHAR(20)) + N'%. Ring buffer details: ' + CAST(record AS NVARCHAR(4000)), 100 - SystemIdle, 'https://www.brentozar.com/go/cpu' FROM ( SELECT record, record.value('(./Record/SchedulerMonitorEvent/SystemHealth/SystemIdle)[1]', 'int') AS SystemIdle diff --git a/sp_BlitzQueryStore.sql b/sp_BlitzQueryStore.sql index 6e84b8bb8..5e840579c 100644 --- a/sp_BlitzQueryStore.sql +++ b/sp_BlitzQueryStore.sql @@ -4515,7 +4515,7 @@ BEGIN 100, 'Execution Pattern', 'Frequently Executed Queries', - 'http://brentozar.com/blitzcache/frequently-executed-queries/', + 'https://www.brentozar.com/blitzcache/frequently-executed-queries/', 'Queries are being executed more than ' + CAST (@execution_threshold AS VARCHAR(5)) + ' times per minute. This can put additional load on the server, even when queries are lightweight.') ; @@ -4530,7 +4530,7 @@ BEGIN 50, 'Parameterization', 'Parameter Sniffing', - 'http://brentozar.com/blitzcache/parameter-sniffing/', + 'https://www.brentozar.com/blitzcache/parameter-sniffing/', 'There are signs of parameter sniffing (wide variance in rows return or time to execute). Investigate query patterns and tune code appropriately.') ; /* Forced execution plans */ @@ -4544,7 +4544,7 @@ BEGIN 5, 'Parameterization', 'Forced Plans', - 'http://brentozar.com/blitzcache/forced-plans/', + 'https://www.brentozar.com/blitzcache/forced-plans/', 'Execution plans have been compiled with forced plans, either through FORCEPLAN, plan guides, or forced parameterization. This will make general tuning efforts less effective.'); IF EXISTS (SELECT 1/0 @@ -4557,7 +4557,7 @@ BEGIN 200, 'Cursors', 'Cursors', - 'http://brentozar.com/blitzcache/cursors-found-slow-queries/', + 'https://www.brentozar.com/blitzcache/cursors-found-slow-queries/', 'There are cursors in the plan cache. This is neither good nor bad, but it is a thing. Cursors are weird in SQL Server.'); IF EXISTS (SELECT 1/0 @@ -4571,7 +4571,7 @@ BEGIN 200, 'Cursors', 'Optimistic Cursors', - 'http://brentozar.com/blitzcache/cursors-found-slow-queries/', + 'https://www.brentozar.com/blitzcache/cursors-found-slow-queries/', 'There are optimistic cursors in the plan cache, which can harm performance.'); IF EXISTS (SELECT 1/0 @@ -4585,7 +4585,7 @@ BEGIN 200, 'Cursors', 'Non-forward Only Cursors', - 'http://brentozar.com/blitzcache/cursors-found-slow-queries/', + 'https://www.brentozar.com/blitzcache/cursors-found-slow-queries/', 'There are non-forward only cursors in the plan cache, which can harm performance.'); IF EXISTS (SELECT 1/0 @@ -4598,7 +4598,7 @@ BEGIN 200, 'Cursors', 'Dynamic Cursors', - 'http://brentozar.com/blitzcache/cursors-found-slow-queries/', + 'https://www.brentozar.com/blitzcache/cursors-found-slow-queries/', 'Dynamic Cursors inhibit parallelism!.'); IF EXISTS (SELECT 1/0 @@ -4611,7 +4611,7 @@ BEGIN 200, 'Cursors', 'Fast Forward Cursors', - 'http://brentozar.com/blitzcache/cursors-found-slow-queries/', + 'https://www.brentozar.com/blitzcache/cursors-found-slow-queries/', 'Fast forward cursors inhibit parallelism!.'); IF EXISTS (SELECT 1/0 @@ -4624,7 +4624,7 @@ BEGIN 50, 'Parameterization', 'Forced Parameterization', - 'http://brentozar.com/blitzcache/forced-parameterization/', + 'https://www.brentozar.com/blitzcache/forced-parameterization/', 'Execution plans have been compiled with forced parameterization.') ; IF EXISTS (SELECT 1/0 @@ -4637,7 +4637,7 @@ BEGIN 200, 'Execution Plans', 'Parallelism', - 'http://brentozar.com/blitzcache/parallel-plans-detected/', + 'https://www.brentozar.com/blitzcache/parallel-plans-detected/', 'Parallel plans detected. These warrant investigation, but are neither good nor bad.') ; IF EXISTS (SELECT 1/0 @@ -4650,7 +4650,7 @@ BEGIN 200, 'Execution Plans', 'Nearly Parallel', - 'http://brentozar.com/blitzcache/query-cost-near-cost-threshold-parallelism/', + 'https://www.brentozar.com/blitzcache/query-cost-near-cost-threshold-parallelism/', 'Queries near the cost threshold for parallelism. These may go parallel when you least expect it.') ; IF EXISTS (SELECT 1/0 @@ -4663,7 +4663,7 @@ BEGIN 50, 'Execution Plans', 'Query Plan Warnings', - 'http://brentozar.com/blitzcache/query-plan-warnings/', + 'https://www.brentozar.com/blitzcache/query-plan-warnings/', 'Warnings detected in execution plans. SQL Server is telling you that something bad is going on that requires your attention.') ; IF EXISTS (SELECT 1/0 @@ -4676,7 +4676,7 @@ BEGIN 50, 'Performance', 'Long Running Queries', - 'http://brentozar.com/blitzcache/long-running-queries/', + 'https://www.brentozar.com/blitzcache/long-running-queries/', 'Long running queries have been found. These are queries with an average duration longer than ' + CAST(@long_running_query_warning_seconds / 1000 / 1000 AS VARCHAR(5)) + ' second(s). These queries should be investigated for additional tuning options.') ; @@ -4691,7 +4691,7 @@ BEGIN 50, 'Performance', 'Missing Index Request', - 'http://brentozar.com/blitzcache/missing-index-request/', + 'https://www.brentozar.com/blitzcache/missing-index-request/', 'Queries found with missing indexes.'); IF EXISTS (SELECT 1/0 @@ -4704,7 +4704,7 @@ BEGIN 200, 'Cardinality', 'Legacy Cardinality Estimator in Use', - 'http://brentozar.com/blitzcache/legacy-cardinality-estimator/', + 'https://www.brentozar.com/blitzcache/legacy-cardinality-estimator/', 'A legacy cardinality estimator is being used by one or more queries. Investigate whether you need to be using this cardinality estimator. This may be caused by compatibility levels, global trace flags, or query level trace flags.'); IF EXISTS (SELECT 1/0 @@ -4717,7 +4717,7 @@ BEGIN 50, 'Performance', 'Implicit Conversions', - 'http://brentozar.com/go/implicit', + 'https://www.brentozar.com/go/implicit', 'One or more queries are comparing two fields that are not of the same data type.') ; IF EXISTS (SELECT 1/0 @@ -4730,7 +4730,7 @@ BEGIN 100, 'Performance', 'Busy Loops', - 'http://brentozar.com/blitzcache/busy-loops/', + 'https://www.brentozar.com/blitzcache/busy-loops/', 'Operations have been found that are executed 100 times more often than the number of rows returned by each iteration. This is an indicator that something is off in query execution.'); IF EXISTS (SELECT 1/0 @@ -4743,7 +4743,7 @@ BEGIN 50, 'Performance', 'Joining to table valued functions', - 'http://brentozar.com/blitzcache/tvf-join/', + 'https://www.brentozar.com/blitzcache/tvf-join/', 'Execution plans have been found that join to table valued functions (TVFs). TVFs produce inaccurate estimates of the number of rows returned and can lead to any number of query plan problems.'); IF EXISTS (SELECT 1/0 @@ -4756,7 +4756,7 @@ BEGIN 50, 'Execution Plans', 'Compilation timeout', - 'http://brentozar.com/blitzcache/compilation-timeout/', + 'https://www.brentozar.com/blitzcache/compilation-timeout/', 'Query compilation timed out for one or more queries. SQL Server did not find a plan that meets acceptable performance criteria in the time allotted so the best guess was returned. There is a very good chance that this plan isn''t even below average - it''s probably terrible.'); IF EXISTS (SELECT 1/0 @@ -4769,7 +4769,7 @@ BEGIN 50, 'Execution Plans', 'Compilation memory limit exceeded', - 'http://brentozar.com/blitzcache/compile-memory-limit-exceeded/', + 'https://www.brentozar.com/blitzcache/compile-memory-limit-exceeded/', 'The optimizer has a limited amount of memory available. One or more queries are complex enough that SQL Server was unable to allocate enough memory to fully optimize the query. A best fit plan was found, and it''s probably terrible.'); IF EXISTS (SELECT 1/0 @@ -4782,7 +4782,7 @@ BEGIN 10, 'Execution Plans', 'No join predicate', - 'http://brentozar.com/blitzcache/no-join-predicate/', + 'https://www.brentozar.com/blitzcache/no-join-predicate/', 'Operators in a query have no join predicate. This means that all rows from one table will be matched with all rows from anther table producing a Cartesian product. That''s a whole lot of rows. This may be your goal, but it''s important to investigate why this is happening.'); IF EXISTS (SELECT 1/0 @@ -4795,7 +4795,7 @@ BEGIN 200, 'Execution Plans', 'Multiple execution plans', - 'http://brentozar.com/blitzcache/multiple-plans/', + 'https://www.brentozar.com/blitzcache/multiple-plans/', 'Queries exist with multiple execution plans (as determined by query_plan_hash). Investigate possible ways to parameterize these queries or otherwise reduce the plan count.'); IF EXISTS (SELECT 1/0 @@ -4808,7 +4808,7 @@ BEGIN 100, 'Performance', 'Unmatched indexes', - 'http://brentozar.com/blitzcache/unmatched-indexes', + 'https://www.brentozar.com/blitzcache/unmatched-indexes', 'An index could have been used, but SQL Server chose not to use it - likely due to parameterization and filtered indexes.'); IF EXISTS (SELECT 1/0 @@ -4821,7 +4821,7 @@ BEGIN 100, 'Parameterization', 'Unparameterized queries', - 'http://brentozar.com/blitzcache/unparameterized-queries', + 'https://www.brentozar.com/blitzcache/unparameterized-queries', 'Unparameterized queries found. These could be ad hoc queries, data exploration, or queries using "OPTIMIZE FOR UNKNOWN".'); IF EXISTS (SELECT 1/0 @@ -4834,7 +4834,7 @@ BEGIN 100, 'Execution Plans', 'Trivial Plans', - 'http://brentozar.com/blitzcache/trivial-plans', + 'https://www.brentozar.com/blitzcache/trivial-plans', 'Trivial plans get almost no optimization. If you''re finding these in the top worst queries, something may be going wrong.'); IF EXISTS (SELECT 1/0 @@ -4847,7 +4847,7 @@ BEGIN 10, 'Execution Plans', 'Forced Serialization', - 'http://www.brentozar.com/blitzcache/forced-serialization/', + 'https://www.brentozar.com/blitzcache/forced-serialization/', 'Something in your plan is forcing a serial query. Further investigation is needed if this is not by design.') ; IF EXISTS (SELECT 1/0 @@ -4860,7 +4860,7 @@ BEGIN 100, 'Execution Plans', 'Expensive Key Lookups', - 'http://www.brentozar.com/blitzcache/expensive-key-lookups/', + 'https://www.brentozar.com/blitzcache/expensive-key-lookups/', 'There''s a key lookup in your plan that costs >=50% of the total plan cost.') ; IF EXISTS (SELECT 1/0 @@ -4873,7 +4873,7 @@ BEGIN 100, 'Execution Plans', 'Expensive Remote Query', - 'http://www.brentozar.com/blitzcache/expensive-remote-query/', + 'https://www.brentozar.com/blitzcache/expensive-remote-query/', 'There''s a remote query in your plan that costs >=50% of the total plan cost.') ; IF EXISTS (SELECT 1/0 @@ -4965,7 +4965,7 @@ BEGIN 100, 'Operator Warnings', 'SQL is throwing operator level plan warnings', - 'http://brentozar.com/blitzcache/query-plan-warnings/', + 'https://www.brentozar.com/blitzcache/query-plan-warnings/', 'Check the plan for more details.') ; IF EXISTS (SELECT 1/0 @@ -5057,7 +5057,7 @@ BEGIN 100, 'Execution Plans', 'Expensive Sort', - 'http://www.brentozar.com/blitzcache/expensive-sorts/', + 'https://www.brentozar.com/blitzcache/expensive-sorts/', 'There''s a sort in your plan that costs >=50% of the total plan cost.') ; IF EXISTS (SELECT 1/0 @@ -5277,7 +5277,7 @@ BEGIN 100, 'MSTVFs', 'These have many of the same problems scalar UDFs have', - 'http://brentozar.com/blitzcache/tvf-join/', + 'https://www.brentozar.com/blitzcache/tvf-join/', 'Execution plans have been found that join to table valued functions (TVFs). TVFs produce inaccurate estimates of the number of rows returned and can lead to any number of query plan problems.'); IF EXISTS (SELECT 1/0 From e25f7233872ffea3cfae1502ee7df4138dda6414 Mon Sep 17 00:00:00 2001 From: ianmanton <49268750+ianmanton@users.noreply.github.com> Date: Fri, 5 Mar 2021 08:50:16 +0000 Subject: [PATCH 125/662] Update sp_BlitzAnalysis.sql Ref https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2807 - money data type was too small and causing Arithmetic overflow for certain hefty values in our system. Code developed in conjunction with Ade. --- sp_BlitzAnalysis.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sp_BlitzAnalysis.sql b/sp_BlitzAnalysis.sql index e4e28b961..9cda62a84 100644 --- a/sp_BlitzAnalysis.sql +++ b/sp_BlitzAnalysis.sql @@ -304,7 +304,7 @@ SET @Sql = N'SELECT [wait_time_minutes_per_minute], [signal_wait_time_ms_delta], [waiting_tasks_count_delta], -CAST(ISNULL((CAST([wait_time_ms_delta] AS MONEY)/NULLIF(CAST([waiting_tasks_count_delta] AS MONEY),0)),0) AS DECIMAL(18,2)) AS [wait_time_ms_per_wait] +ISNULL((CAST([wait_time_ms_delta] AS DECIMAL(38,2))/NULLIF(CAST([waiting_tasks_count_delta] AS DECIMAL(38,2)),0)),0) AS [wait_time_ms_per_wait] FROM ( SELECT From ae982129f1660173a6016e73676f868a473eb0ac Mon Sep 17 00:00:00 2001 From: Jefferson ELIAS Date: Fri, 5 Mar 2021 12:00:58 +0100 Subject: [PATCH 126/662] Updates sp_BlitzFirst --- sp_BlitzFirst.sql | 89 ++++++++++++++++++++++++----------------------- 1 file changed, 46 insertions(+), 43 deletions(-) diff --git a/sp_BlitzFirst.sql b/sp_BlitzFirst.sql index 85641df45..ee29a639d 100644 --- a/sp_BlitzFirst.sql +++ b/sp_BlitzFirst.sql @@ -2351,49 +2351,52 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, /* We don't want to hang around to obtain locks */ SET LOCK_TIMEOUT 0; - EXEC sp_MSforeachdb N'USE [?]; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; SET LOCK_TIMEOUT 1000; - BEGIN TRY - INSERT INTO #UpdatedStats(HowToStopIt, RowsForSorting) - SELECT HowToStopIt = - QUOTENAME(DB_NAME()) + N''.'' + - QUOTENAME(SCHEMA_NAME(obj.schema_id)) + N''.'' + - QUOTENAME(obj.name) + - N'' statistic '' + QUOTENAME(stat.name) + - N'' was updated on '' + CONVERT(NVARCHAR(50), sp.last_updated, 121) + N'','' + - N'' had '' + CAST(sp.rows AS NVARCHAR(50)) + N'' rows, with '' + - CAST(sp.rows_sampled AS NVARCHAR(50)) + N'' rows sampled,'' + - N'' producing '' + CAST(sp.steps AS NVARCHAR(50)) + N'' steps in the histogram.'', - sp.rows - FROM sys.objects AS obj WITH (NOLOCK) - INNER JOIN sys.stats AS stat WITH (NOLOCK) ON stat.object_id = obj.object_id - CROSS APPLY sys.dm_db_stats_properties(stat.object_id, stat.stats_id) AS sp - WHERE sp.last_updated > DATEADD(MI, -15, GETDATE()) - AND obj.is_ms_shipped = 0 - AND ''[?]'' <> ''[tempdb]''; - END TRY - BEGIN CATCH - IF (ERROR_NUMBER() = 1222) - BEGIN - INSERT INTO #UpdatedStats(HowToStopIt, RowsForSorting) - SELECT HowToStopIt = - QUOTENAME(DB_NAME()) + - N'' No information could be retrieved as the lock timeout was exceeded,''+ - N'' this is likely due to an Index operation in Progress'', - -1 - END - ELSE - BEGIN - INSERT INTO #UpdatedStats(HowToStopIt, RowsForSorting) - SELECT HowToStopIt = - QUOTENAME(DB_NAME()) + - N'' No information could be retrieved as a result of error: ''+ - CAST(ERROR_NUMBER() AS NVARCHAR(10)) + - N'' with message: ''+ - CAST(ERROR_MESSAGE() AS NVARCHAR(128)), - -1 - END - END CATCH'; + + SET @StringToExecute = 'USE [?];' + @LineFeed + + 'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; SET LOCK_TIMEOUT 1000;' + @LineFeed + + 'BEGIN TRY' + @LineFeed + + ' INSERT INTO #UpdatedStats(HowToStopIt, RowsForSorting)' + @LineFeed + + ' SELECT HowToStopIt = ' + @LineFeed + + ' QUOTENAME(DB_NAME()) + N''.'' +' + @LineFeed + + ' QUOTENAME(SCHEMA_NAME(obj.schema_id)) + N''.'' +' + @LineFeed + + ' QUOTENAME(obj.name) +' + @LineFeed + + ' N'' statistic '' + QUOTENAME(stat.name) + ' + @LineFeed + + ' N'' was updated on '' + CONVERT(NVARCHAR(50), sp.last_updated, 121) + N'','' + ' + @LineFeed + + ' N'' had '' + CAST(sp.rows AS NVARCHAR(50)) + N'' rows, with '' +' + @LineFeed + + ' CAST(sp.rows_sampled AS NVARCHAR(50)) + N'' rows sampled,'' + ' + @LineFeed + + ' N'' producing '' + CAST(sp.steps AS NVARCHAR(50)) + N'' steps in the histogram.'',' + @LineFeed + + ' sp.rows' + @LineFeed + + ' FROM sys.objects AS obj WITH (NOLOCK)' + @LineFeed + + ' INNER JOIN sys.stats AS stat WITH (NOLOCK) ON stat.object_id = obj.object_id ' + @LineFeed + + ' CROSS APPLY sys.dm_db_stats_properties(stat.object_id, stat.stats_id) AS sp ' + @LineFeed + + ' WHERE sp.last_updated > DATEADD(MI, -15, GETDATE())' + @LineFeed + + ' AND obj.is_ms_shipped = 0' + @LineFeed + + ' AND ''[?]'' <> ''[tempdb]'';' + @LineFeed + + 'END TRY' + @LineFeed + + 'BEGIN CATCH' + @LineFeed + + ' IF (ERROR_NUMBER() = 1222)' + @LineFeed + + ' BEGIN ' + @LineFeed + + ' INSERT INTO #UpdatedStats(HowToStopIt, RowsForSorting)' + @LineFeed + + ' SELECT HowToStopIt = ' + @LineFeed + + ' QUOTENAME(DB_NAME()) +' + @LineFeed + + ' N'' No information could be retrieved as the lock timeout was exceeded,''+' + @LineFeed + + ' N'' this is likely due to an Index operation in Progress'',' + @LineFeed + + ' -1' + @LineFeed + + ' END' + @LineFeed + + ' ELSE' + @LineFeed + + ' BEGIN' + @LineFeed + + ' INSERT INTO #UpdatedStats(HowToStopIt, RowsForSorting)' + @LineFeed + + ' SELECT HowToStopIt = ' + @LineFeed + + ' QUOTENAME(DB_NAME()) +' + @LineFeed + + ' N'' No information could be retrieved as a result of error: ''+' + @LineFeed + + ' CAST(ERROR_NUMBER() AS NVARCHAR(10)) +' + @LineFeed + + ' N'' with message: ''+' + @LineFeed + + ' CAST(ERROR_MESSAGE() AS NVARCHAR(128)),' + @LineFeed + + ' -1' + @LineFeed + + ' END' + @LineFeed + + 'END CATCH' + ; + EXEC sp_MSforeachdb @StringToExecute; /* Set timeout back to a default value of -1 */ SET LOCK_TIMEOUT -1; From 5ceb50efbee3acd4866bfddfcf9f2198bab10d8e Mon Sep 17 00:00:00 2001 From: ianmanton <49268750+ianmanton@users.noreply.github.com> Date: Mon, 8 Mar 2021 09:17:56 +0000 Subject: [PATCH 127/662] Update sp_BlitzWho to amend BlitzWho_deltas view Related to: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2813 Change money to decimal 38,2 to resolve arithmetic overflow error when trying to read from this view when large values are involved. --- sp_BlitzWho.sql | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/sp_BlitzWho.sql b/sp_BlitzWho.sql index 6744bf0a5..c93c6c834 100644 --- a/sp_BlitzWho.sql +++ b/sp_BlitzWho.sql @@ -444,18 +444,18 @@ IF @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @Output + N' [request_cpu_time], ' + @LineFeed + N' [degree_of_parallelism], ' + @LineFeed + N' [request_logical_reads], ' + @LineFeed - + N' ((CAST([request_logical_reads] AS MONEY)* 8)/ 1024) [Logical_Reads_MB], ' + @LineFeed + + N' ((CAST([request_logical_reads] AS DECIMAL(38,2))* 8)/ 1024) [Logical_Reads_MB], ' + @LineFeed + N' [request_writes], ' + @LineFeed - + N' ((CAST([request_writes] AS MONEY)* 8)/ 1024) [Logical_Writes_MB], ' + @LineFeed + + N' ((CAST([request_writes] AS DECIMAL(38,2))* 8)/ 1024) [Logical_Writes_MB], ' + @LineFeed + N' [request_physical_reads], ' + @LineFeed - + N' ((CAST([request_physical_reads] AS MONEY)* 8)/ 1024) [Physical_reads_MB], ' + @LineFeed + + N' ((CAST([request_physical_reads] AS DECIMAL(38,2))* 8)/ 1024) [Physical_reads_MB], ' + @LineFeed + N' [session_cpu], ' + @LineFeed + N' [session_logical_reads], ' + @LineFeed - + N' ((CAST([session_logical_reads] AS MONEY)* 8)/ 1024) [session_logical_reads_MB], ' + @LineFeed + + N' ((CAST([session_logical_reads] AS DECIMAL(38,2))* 8)/ 1024) [session_logical_reads_MB], ' + @LineFeed + N' [session_physical_reads], ' + @LineFeed - + N' ((CAST([session_physical_reads] AS MONEY)* 8)/ 1024) [session_physical_reads_MB], ' + @LineFeed + + N' ((CAST([session_physical_reads] AS DECIMAL(38,2))* 8)/ 1024) [session_physical_reads_MB], ' + @LineFeed + N' [session_writes], ' + @LineFeed - + N' ((CAST([session_writes] AS MONEY)* 8)/ 1024) [session_writes_MB], ' + @LineFeed + + N' ((CAST([session_writes] AS DECIMAL(38,2))* 8)/ 1024) [session_writes_MB], ' + @LineFeed + N' [tempdb_allocations_mb], ' + @LineFeed + N' [memory_usage], ' + @LineFeed + N' [estimated_completion_time], ' + @LineFeed From 89623a702bc25e2ff69f62a522ac18e0ad220461 Mon Sep 17 00:00:00 2001 From: luke-mckee-ol Date: Thu, 11 Mar 2021 22:17:57 +1100 Subject: [PATCH 128/662] Revert "Update Install-Core-Blitz-No-Query-Store.sql" This reverts commit 494774e3fbf3b9a297a4e30b2cede06f756df3e9. --- Install-Core-Blitz-No-Query-Store.sql | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Install-Core-Blitz-No-Query-Store.sql b/Install-Core-Blitz-No-Query-Store.sql index f654a2177..67a492fc1 100755 --- a/Install-Core-Blitz-No-Query-Store.sql +++ b/Install-Core-Blitz-No-Query-Store.sql @@ -11372,7 +11372,7 @@ IF @Help = 1 UNION ALL SELECT N'@IgnoreSystemDBs', N'BIT', - N'Ignores plans found in the system databases (master, model, msdb, tempdb, dbmaintenance, dbadmin, dbatools, and resourcedb)' + N'Ignores plans found in the system databases (master, model, msdb, tempdb, and resourcedb)' UNION ALL SELECT N'@OnlyQueryHashes', @@ -12922,7 +12922,7 @@ SET @body += N' WHERE 1 = 1 ' + @nl ; IF @IgnoreSystemDBs = 1 BEGIN RAISERROR(N'Ignoring system databases by default', 0, 1) WITH NOWAIT; - SET @body += N' AND COALESCE(DB_NAME(CAST(xpa.value AS INT)), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'', ''dbmaintenance'', ''dbadmin'', ''dbatools'') AND COALESCE(DB_NAME(CAST(xpa.value AS INT)), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ; + SET @body += N' AND COALESCE(DB_NAME(CAST(xpa.value AS INT)), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'') AND COALESCE(DB_NAME(CAST(xpa.value AS INT)), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ; END; IF @DatabaseName IS NOT NULL OR @DatabaseName <> N'' @@ -13357,7 +13357,7 @@ BEGIN SET @sql += @body_where ; IF @IgnoreSystemDBs = 1 - SET @sql += N' AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'', ''dbmaintenance'', ''dbadmin'', ''dbatools'') AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ; + SET @sql += N' AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'') AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ; SET @sql += @sort_filter + @nl; @@ -13389,7 +13389,7 @@ BEGIN SET @sql += @body_where ; IF @IgnoreSystemDBs = 1 - SET @sql += N' AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'', ''dbmaintenance'', ''dbadmin'', ''dbatools'') AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ; + SET @sql += N' AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'') AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ; SET @sql += @sort_filter + @nl; @@ -13425,7 +13425,7 @@ BEGIN SET @sql += @body_where ; IF @IgnoreSystemDBs = 1 - SET @sql += N' AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'', ''dbmaintenance'', ''dbadmin'', ''dbatools'') AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ; + SET @sql += N' AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'') AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ; SET @sql += @sort_filter + @nl; From 70fd09153324b28cabb591f521999872e77a4c76 Mon Sep 17 00:00:00 2001 From: luke-mckee-ol Date: Thu, 11 Mar 2021 22:18:06 +1100 Subject: [PATCH 129/662] Revert "Update Install-Core-Blitz-With-Query-Store.sql" This reverts commit 6ee22740b3dfe8115a856883f954a3385118a4c7. --- Install-Core-Blitz-With-Query-Store.sql | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Install-Core-Blitz-With-Query-Store.sql b/Install-Core-Blitz-With-Query-Store.sql index 2e12810cd..0f27b66ce 100755 --- a/Install-Core-Blitz-With-Query-Store.sql +++ b/Install-Core-Blitz-With-Query-Store.sql @@ -11372,7 +11372,7 @@ IF @Help = 1 UNION ALL SELECT N'@IgnoreSystemDBs', N'BIT', - N'Ignores plans found in the system databases (master, model, msdb, tempdb, dbmaintenance, dbadmin, dbatools, and resourcedb)' + N'Ignores plans found in the system databases (master, model, msdb, tempdb, and resourcedb)' UNION ALL SELECT N'@OnlyQueryHashes', @@ -12922,7 +12922,7 @@ SET @body += N' WHERE 1 = 1 ' + @nl ; IF @IgnoreSystemDBs = 1 BEGIN RAISERROR(N'Ignoring system databases by default', 0, 1) WITH NOWAIT; - SET @body += N' AND COALESCE(DB_NAME(CAST(xpa.value AS INT)), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'', ''dbmaintenance'', ''dbadmin'', ''dbatools'') AND COALESCE(DB_NAME(CAST(xpa.value AS INT)), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ; + SET @body += N' AND COALESCE(DB_NAME(CAST(xpa.value AS INT)), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'') AND COALESCE(DB_NAME(CAST(xpa.value AS INT)), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ; END; IF @DatabaseName IS NOT NULL OR @DatabaseName <> N'' @@ -13357,7 +13357,7 @@ BEGIN SET @sql += @body_where ; IF @IgnoreSystemDBs = 1 - SET @sql += N' AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'', ''dbmaintenance'', ''dbadmin'', ''dbatools'') AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ; + SET @sql += N' AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'') AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ; SET @sql += @sort_filter + @nl; @@ -13389,7 +13389,7 @@ BEGIN SET @sql += @body_where ; IF @IgnoreSystemDBs = 1 - SET @sql += N' AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'', ''dbmaintenance'', ''dbadmin'', ''dbatools'') AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ; + SET @sql += N' AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'') AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ; SET @sql += @sort_filter + @nl; @@ -13425,7 +13425,7 @@ BEGIN SET @sql += @body_where ; IF @IgnoreSystemDBs = 1 - SET @sql += N' AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'', ''dbmaintenance'', ''dbadmin'', ''dbatools'') AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ; + SET @sql += N' AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'') AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ; SET @sql += @sort_filter + @nl; From 33e69883dcb54925009eab43ae6aba12e79d7302 Mon Sep 17 00:00:00 2001 From: luke-mckee-ol Date: Thu, 11 Mar 2021 22:18:17 +1100 Subject: [PATCH 130/662] Revert "Update Install-All-Scripts.sql" This reverts commit 2095be096c3528e2a9ee0f2e9eaaf6e15614cd3b. --- Install-All-Scripts.sql | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Install-All-Scripts.sql b/Install-All-Scripts.sql index e8f59f9e3..af87b9405 100755 --- a/Install-All-Scripts.sql +++ b/Install-All-Scripts.sql @@ -14191,7 +14191,7 @@ IF @Help = 1 UNION ALL SELECT N'@IgnoreSystemDBs', N'BIT', - N'Ignores plans found in the system databases (master, model, msdb, tempdb, dbmaintenance, dbadmin, dbatools, and resourcedb)' + N'Ignores plans found in the system databases (master, model, msdb, tempdb, and resourcedb)' UNION ALL SELECT N'@OnlyQueryHashes', @@ -15741,7 +15741,7 @@ SET @body += N' WHERE 1 = 1 ' + @nl ; IF @IgnoreSystemDBs = 1 BEGIN RAISERROR(N'Ignoring system databases by default', 0, 1) WITH NOWAIT; - SET @body += N' AND COALESCE(DB_NAME(CAST(xpa.value AS INT)), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'', ''dbmaintenance'', ''dbadmin'', ''dbatools'') AND COALESCE(DB_NAME(CAST(xpa.value AS INT)), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ; + SET @body += N' AND COALESCE(DB_NAME(CAST(xpa.value AS INT)), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'') AND COALESCE(DB_NAME(CAST(xpa.value AS INT)), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ; END; IF @DatabaseName IS NOT NULL OR @DatabaseName <> N'' @@ -16176,7 +16176,7 @@ BEGIN SET @sql += @body_where ; IF @IgnoreSystemDBs = 1 - SET @sql += N' AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'', ''dbmaintenance'', ''dbadmin'', ''dbatools'') AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ; + SET @sql += N' AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'') AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ; SET @sql += @sort_filter + @nl; @@ -16208,7 +16208,7 @@ BEGIN SET @sql += @body_where ; IF @IgnoreSystemDBs = 1 - SET @sql += N' AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'', ''dbmaintenance'', ''dbadmin'', ''dbatools'') AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ; + SET @sql += N' AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'') AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ; SET @sql += @sort_filter + @nl; @@ -16244,7 +16244,7 @@ BEGIN SET @sql += @body_where ; IF @IgnoreSystemDBs = 1 - SET @sql += N' AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'', ''dbmaintenance'', ''dbadmin'', ''dbatools'') AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ; + SET @sql += N' AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'') AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ; SET @sql += @sort_filter + @nl; From d105e2184ec75a20e3d22bb1c3492ba77769ab0c Mon Sep 17 00:00:00 2001 From: luke-mckee-ol Date: Thu, 11 Mar 2021 22:38:55 +1100 Subject: [PATCH 131/662] Lower DB Name value to account for case sensitive databases --- sp_BlitzCache.sql | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sp_BlitzCache.sql b/sp_BlitzCache.sql index f119271e4..743c7e313 100644 --- a/sp_BlitzCache.sql +++ b/sp_BlitzCache.sql @@ -1947,7 +1947,7 @@ SET @body += N' WHERE 1 = 1 ' + @nl ; IF @IgnoreSystemDBs = 1 BEGIN RAISERROR(N'Ignoring system databases by default', 0, 1) WITH NOWAIT; - SET @body += N' AND COALESCE(DB_NAME(CAST(xpa.value AS INT)), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'', ''dbmaintenance'', ''dbadmin'', ''dbatools'') AND COALESCE(DB_NAME(CAST(xpa.value AS INT)), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ; + SET @body += N' AND COALESCE(LOWER(DB_NAME(CAST(xpa.value AS INT))), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'', ''dbmaintenance'', ''dbadmin'', ''dbatools'') AND COALESCE(DB_NAME(CAST(xpa.value AS INT)), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ; END; IF @DatabaseName IS NOT NULL OR @DatabaseName <> N'' @@ -2382,7 +2382,7 @@ BEGIN SET @sql += @body_where ; IF @IgnoreSystemDBs = 1 - SET @sql += N' AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'', ''dbmaintenance'', ''dbadmin'', ''dbatools'') AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ; + SET @sql += N' AND COALESCE(LOWER(DB_NAME(database_id)), LOWER(CAST(pa.value AS sysname)), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'', ''dbmaintenance'', ''dbadmin'', ''dbatools'') AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ; SET @sql += @sort_filter + @nl; @@ -2414,7 +2414,7 @@ BEGIN SET @sql += @body_where ; IF @IgnoreSystemDBs = 1 - SET @sql += N' AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'', ''dbmaintenance'', ''dbadmin'', ''dbatools'') AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ; + SET @sql += N' AND COALESCE(LOWER(DB_NAME(database_id)), LOWER(CAST(pa.value AS sysname)), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'', ''dbmaintenance'', ''dbadmin'', ''dbatools'') AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ; SET @sql += @sort_filter + @nl; @@ -2450,7 +2450,7 @@ BEGIN SET @sql += @body_where ; IF @IgnoreSystemDBs = 1 - SET @sql += N' AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'', ''dbmaintenance'', ''dbadmin'', ''dbatools'') AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ; + SET @sql += N' AND COALESCE(LOWER(DB_NAME(database_id)), LOWER(CAST(pa.value AS sysname)), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'', ''dbmaintenance'', ''dbadmin'', ''dbatools'') AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ; SET @sql += @sort_filter + @nl; From 6b78084e06e5ea27d720ac95a530a404bc84feb2 Mon Sep 17 00:00:00 2001 From: Jefferson ELIAS Date: Thu, 11 Mar 2021 15:19:16 +0100 Subject: [PATCH 132/662] Add OutputType parameter to sp_BlitzCache and manage value NONE --- sp_BlitzCache.sql | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/sp_BlitzCache.sql b/sp_BlitzCache.sql index 090f9e8fa..ae3794a16 100644 --- a/sp_BlitzCache.sql +++ b/sp_BlitzCache.sql @@ -244,6 +244,7 @@ ALTER PROCEDURE dbo.sp_BlitzCache @UseTriggersAnyway BIT = NULL, @ExportToExcel BIT = 0, @ExpertMode TINYINT = 0, + @OutputType VARCHAR(20) = 'TABLE' , @OutputServerName NVARCHAR(258) = NULL , @OutputDatabaseName NVARCHAR(258) = NULL , @OutputSchemaName NVARCHAR(258) = NULL , @@ -279,7 +280,7 @@ SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; SELECT @Version = '8.01', @VersionDate = '20210222'; - +SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) BEGIN @@ -755,6 +756,18 @@ BEGIN RETURN; END; +IF(@OutputType = 'NONE' AND (@OutputTableName IS NULL OR @OutputSchemaName IS NULL OR @OutputDatabaseName IS NULL)) +BEGIN + RAISERROR('This procedure should be called with a value for all @Output* parameters, as @OutputType is set to NONE',12,1); + RETURN; +END; + +IF(@OutputType = 'NONE') +BEGIN + SET @HideSummary = 1; +END; + + /* Lets get @SortOrder set to lower case here for comparisons later */ SET @SortOrder = LOWER(@SortOrder); @@ -5131,9 +5144,10 @@ IF @Debug = 1 PRINT SUBSTRING(@sql, 32000, 36000); PRINT SUBSTRING(@sql, 36000, 40000); END; - -EXEC sp_executesql @sql, N'@Top INT, @spid INT, @minimumExecutionCount INT, @min_back INT', @Top, @@SPID, @MinimumExecutionCount, @MinutesBack; - +IF(@OutputType <> 'NONE') +BEGIN + EXEC sp_executesql @sql, N'@Top INT, @spid INT, @minimumExecutionCount INT, @min_back INT', @Top, @@SPID, @MinimumExecutionCount, @MinutesBack; +END; /* @@ -6845,11 +6859,12 @@ END; PRINT SUBSTRING(@AllSortSql, 32000, 36000); PRINT SUBSTRING(@AllSortSql, 36000, 40000); END; - +IF(@OutputType <> 'NONE') +BEGIN EXEC sys.sp_executesql @stmt = @AllSortSql, @params = N'@i_DatabaseName NVARCHAR(128), @i_Top INT, @i_SkipAnalysis BIT, @i_OutputDatabaseName NVARCHAR(258), @i_OutputSchemaName NVARCHAR(258), @i_OutputTableName NVARCHAR(258), @i_CheckDateOverride DATETIMEOFFSET, @i_MinutesBack INT ', @i_DatabaseName = @DatabaseName, @i_Top = @Top, @i_SkipAnalysis = @SkipAnalysis, @i_OutputDatabaseName = @OutputDatabaseName, @i_OutputSchemaName = @OutputSchemaName, @i_OutputTableName = @OutputTableName, @i_CheckDateOverride = @CheckDateOverride, @i_MinutesBack = @MinutesBack; - +END; /*End of AllSort section*/ From a5e7df280e1477a9eabafd9f9ee818f6d974b911 Mon Sep 17 00:00:00 2001 From: Jefferson ELIAS Date: Thu, 11 Mar 2021 15:25:01 +0100 Subject: [PATCH 133/662] sp_BlitzFirst - pass OutputType NONE to sp_BlitzCache --- sp_BlitzFirst.sql | 37 ++++++++++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/sp_BlitzFirst.sql b/sp_BlitzFirst.sql index ea5c399e9..8d4525c2a 100644 --- a/sp_BlitzFirst.sql +++ b/sp_BlitzFirst.sql @@ -3395,15 +3395,34 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, IF @BlitzCacheMinutesBack IS NULL OR @BlitzCacheMinutesBack < 1 OR @BlitzCacheMinutesBack > 60 SET @BlitzCacheMinutesBack = 15; - EXEC sp_BlitzCache - @OutputDatabaseName = @UnquotedOutputDatabaseName, - @OutputSchemaName = @UnquotedOutputSchemaName, - @OutputTableName = @OutputTableNameBlitzCache, - @CheckDateOverride = @StartSampleTime, - @SortOrder = 'all', - @SkipAnalysis = @BlitzCacheSkipAnalysis, - @MinutesBack = @BlitzCacheMinutesBack, - @Debug = @Debug; + IF(@OutputType = 'NONE') + BEGIN + EXEC sp_BlitzCache + @OutputDatabaseName = @UnquotedOutputDatabaseName, + @OutputSchemaName = @UnquotedOutputSchemaName, + @OutputTableName = @OutputTableNameBlitzCache, + @CheckDateOverride = @StartSampleTime, + @SortOrder = 'all', + @SkipAnalysis = @BlitzCacheSkipAnalysis, + @MinutesBack = @BlitzCacheMinutesBack, + @Debug = @Debug, + @OutputType = @OutputType + ; + END; + ELSE + BEGIN + + EXEC sp_BlitzCache + @OutputDatabaseName = @UnquotedOutputDatabaseName, + @OutputSchemaName = @UnquotedOutputSchemaName, + @OutputTableName = @OutputTableNameBlitzCache, + @CheckDateOverride = @StartSampleTime, + @SortOrder = 'all', + @SkipAnalysis = @BlitzCacheSkipAnalysis, + @MinutesBack = @BlitzCacheMinutesBack, + @Debug = @Debug + ; + END; /* Delete history older than @OutputTableRetentionDays */ SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' From e9c6cef8d653313df66bcaaea0e2d305f469225f Mon Sep 17 00:00:00 2001 From: Jefferson ELIAS Date: Thu, 11 Mar 2021 15:28:13 +0100 Subject: [PATCH 134/662] Add reference to OutputType in description of parameters @Help = 1 --- sp_BlitzCache.sql | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/sp_BlitzCache.sql b/sp_BlitzCache.sql index ae3794a16..e71a20f48 100644 --- a/sp_BlitzCache.sql +++ b/sp_BlitzCache.sql @@ -369,7 +369,11 @@ IF @Help = 1 SELECT N'@ExpertMode', N'TINYINT', N'Default 0. When set to 1, results include more columns. When 2, mode is optimized for Opserver, the open source dashboard.' - + UNION ALL + SELECT N'@OutputType', + N'NVARCHAR(258)', + N'If set to "NONE", this will tell this procedure not to run any query leading to a results set thrown to caller.' + UNION ALL SELECT N'@OutputDatabaseName', N'NVARCHAR(128)', From 4c04f547e795436a63c32823347296a0aea1b4d4 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Sun, 14 Mar 2021 09:12:19 +0000 Subject: [PATCH 135/662] #2819 sp_BlitzFirst in Azure SQL DB Avoids calling sp_MSforeachdb. Closes #2819. --- sp_BlitzFirst.sql | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/sp_BlitzFirst.sql b/sp_BlitzFirst.sql index ea5c399e9..e706d388f 100644 --- a/sp_BlitzFirst.sql +++ b/sp_BlitzFirst.sql @@ -2344,6 +2344,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, END IF 20 >= (SELECT COUNT(*) FROM sys.databases WHERE name NOT IN ('master', 'model', 'msdb', 'tempdb')) + AND @Seconds > 0 BEGIN CREATE TABLE #UpdatedStats (HowToStopIt NVARCHAR(4000), RowsForSorting BIGINT); IF EXISTS(SELECT * FROM sys.all_objects WHERE name = 'dm_db_stats_properties') @@ -2351,9 +2352,12 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, /* We don't want to hang around to obtain locks */ SET LOCK_TIMEOUT 0; + IF SERVERPROPERTY('Edition') <> 'SQL Azure' + SET @StringToExecute = N'USE [?];' + @LineFeed; + ELSE + SET @StringToExecute = N''; - SET @StringToExecute = 'USE [?];' + @LineFeed + - 'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; SET LOCK_TIMEOUT 1000;' + @LineFeed + + SET @StringToExecute = @StringToExecute + 'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; SET LOCK_TIMEOUT 1000;' + @LineFeed + 'BEGIN TRY' + @LineFeed + ' INSERT INTO #UpdatedStats(HowToStopIt, RowsForSorting)' + @LineFeed + ' SELECT HowToStopIt = ' + @LineFeed + @@ -2396,7 +2400,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, ' END' + @LineFeed + 'END CATCH' ; - EXEC sp_MSforeachdb @StringToExecute; + + IF SERVERPROPERTY('Edition') <> 'SQL Azure' + EXEC sp_MSforeachdb @StringToExecute; + ELSE + EXEC(@StringToExecute); /* Set timeout back to a default value of -1 */ SET LOCK_TIMEOUT -1; From 0cf5aed85d567e82be8df21c42dc269e509770e3 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Sun, 14 Mar 2021 01:29:41 -0800 Subject: [PATCH 136/662] #2821 sp_BlitzFirst Azure waits In Azure SQL DB, use sys.dm_db_wait_stats. Closes #2821. --- sp_BlitzFirst.sql | 136 ++++++++++++++++++++++++++-------------------- 1 file changed, 76 insertions(+), 60 deletions(-) diff --git a/sp_BlitzFirst.sql b/sp_BlitzFirst.sql index e706d388f..3fdba09c2 100644 --- a/sp_BlitzFirst.sql +++ b/sp_BlitzFirst.sql @@ -1295,35 +1295,42 @@ BEGIN /* Populate #FileStats, #PerfmonStats, #WaitStats with DMV data. After we finish doing our checks, we'll take another sample and compare them. */ RAISERROR('Capturing first pass of wait stats, perfmon counters, file stats',10,1) WITH NOWAIT; - INSERT #WaitStats(Pass, SampleTime, wait_type, wait_time_ms, signal_wait_time_ms, waiting_tasks_count) - SELECT - x.Pass, - x.SampleTime, - x.wait_type, - SUM(x.sum_wait_time_ms) AS sum_wait_time_ms, - SUM(x.sum_signal_wait_time_ms) AS sum_signal_wait_time_ms, - SUM(x.sum_waiting_tasks) AS sum_waiting_tasks - FROM ( - SELECT - 1 AS Pass, - CASE @Seconds WHEN 0 THEN @StartSampleTime ELSE SYSDATETIMEOFFSET() END AS SampleTime, - owt.wait_type, - CASE @Seconds WHEN 0 THEN 0 ELSE SUM(owt.wait_duration_ms) OVER (PARTITION BY owt.wait_type, owt.session_id) - - CASE WHEN @Seconds = 0 THEN 0 ELSE (@Seconds * 1000) END END AS sum_wait_time_ms, - 0 AS sum_signal_wait_time_ms, - 0 AS sum_waiting_tasks - FROM sys.dm_os_waiting_tasks owt - WHERE owt.session_id > 50 - AND owt.wait_duration_ms >= CASE @Seconds WHEN 0 THEN 0 ELSE @Seconds * 1000 END - UNION ALL - SELECT - 1 AS Pass, - CASE @Seconds WHEN 0 THEN @StartSampleTime ELSE SYSDATETIMEOFFSET() END AS SampleTime, - os.wait_type, - CASE @Seconds WHEN 0 THEN 0 ELSE SUM(os.wait_time_ms) OVER (PARTITION BY os.wait_type) END AS sum_wait_time_ms, - CASE @Seconds WHEN 0 THEN 0 ELSE SUM(os.signal_wait_time_ms) OVER (PARTITION BY os.wait_type ) END AS sum_signal_wait_time_ms, - CASE @Seconds WHEN 0 THEN 0 ELSE SUM(os.waiting_tasks_count) OVER (PARTITION BY os.wait_type) END AS sum_waiting_tasks - FROM sys.dm_os_wait_stats os + SET @StringToExecute = N' + INSERT #WaitStats(Pass, SampleTime, wait_type, wait_time_ms, signal_wait_time_ms, waiting_tasks_count) + SELECT + x.Pass, + x.SampleTime, + x.wait_type, + SUM(x.sum_wait_time_ms) AS sum_wait_time_ms, + SUM(x.sum_signal_wait_time_ms) AS sum_signal_wait_time_ms, + SUM(x.sum_waiting_tasks) AS sum_waiting_tasks + FROM ( + SELECT + 1 AS Pass, + CASE @Seconds WHEN 0 THEN @StartSampleTime ELSE SYSDATETIMEOFFSET() END AS SampleTime, + owt.wait_type, + CASE @Seconds WHEN 0 THEN 0 ELSE SUM(owt.wait_duration_ms) OVER (PARTITION BY owt.wait_type, owt.session_id) + - CASE WHEN @Seconds = 0 THEN 0 ELSE (@Seconds * 1000) END END AS sum_wait_time_ms, + 0 AS sum_signal_wait_time_ms, + 0 AS sum_waiting_tasks + FROM sys.dm_os_waiting_tasks owt + WHERE owt.session_id > 50 + AND owt.wait_duration_ms >= CASE @Seconds WHEN 0 THEN 0 ELSE @Seconds * 1000 END + UNION ALL + SELECT + 1 AS Pass, + CASE @Seconds WHEN 0 THEN @StartSampleTime ELSE SYSDATETIMEOFFSET() END AS SampleTime, + os.wait_type, + CASE @Seconds WHEN 0 THEN 0 ELSE SUM(os.wait_time_ms) OVER (PARTITION BY os.wait_type) END AS sum_wait_time_ms, + CASE @Seconds WHEN 0 THEN 0 ELSE SUM(os.signal_wait_time_ms) OVER (PARTITION BY os.wait_type ) END AS sum_signal_wait_time_ms, + CASE @Seconds WHEN 0 THEN 0 ELSE SUM(os.waiting_tasks_count) OVER (PARTITION BY os.wait_type) END AS sum_waiting_tasks '; + + IF SERVERPROPERTY('Edition') = 'SQL Azure' + SET @StringToExecute = @StringToExecute + N' FROM sys.dm_db_wait_stats os '; + ELSE + SET @StringToExecute = @StringToExecute + N' FROM sys.dm_os_wait_stats os '; + + SET @StringToExecute = @StringToExecute + N' ) x WHERE NOT EXISTS ( @@ -1333,7 +1340,8 @@ BEGIN AND wc.Ignorable = 1 ) GROUP BY x.Pass, x.SampleTime, x.wait_type - ORDER BY sum_wait_time_ms DESC; + ORDER BY sum_wait_time_ms DESC;' + EXEC sp_executesql @StringToExecute, N'@StartSampleTime DATETIMEOFFSET, @Seconds INT', @StartSampleTime, @Seconds; INSERT INTO #FileStats (Pass, SampleTime, DatabaseID, FileID, DatabaseName, FileLogicalName, SizeOnDiskMB, io_stall_read_ms , @@ -2442,35 +2450,42 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, RAISERROR('Capturing second pass of wait stats, perfmon counters, file stats',10,1) WITH NOWAIT; /* Populate #FileStats, #PerfmonStats, #WaitStats with DMV data. In a second, we'll compare these. */ - INSERT #WaitStats(Pass, SampleTime, wait_type, wait_time_ms, signal_wait_time_ms, waiting_tasks_count) - SELECT - x.Pass, - x.SampleTime, - x.wait_type, - SUM(x.sum_wait_time_ms) AS sum_wait_time_ms, - SUM(x.sum_signal_wait_time_ms) AS sum_signal_wait_time_ms, - SUM(x.sum_waiting_tasks) AS sum_waiting_tasks - FROM ( - SELECT - 2 AS Pass, - SYSDATETIMEOFFSET() AS SampleTime, - owt.wait_type, - SUM(owt.wait_duration_ms) OVER (PARTITION BY owt.wait_type, owt.session_id) - - CASE WHEN @Seconds = 0 THEN 0 ELSE (@Seconds * 1000) END AS sum_wait_time_ms, - 0 AS sum_signal_wait_time_ms, - CASE @Seconds WHEN 0 THEN 0 ELSE 1 END AS sum_waiting_tasks - FROM sys.dm_os_waiting_tasks owt - WHERE owt.session_id > 50 - AND owt.wait_duration_ms >= CASE @Seconds WHEN 0 THEN 0 ELSE @Seconds * 1000 END - UNION ALL - SELECT - 2 AS Pass, - SYSDATETIMEOFFSET() AS SampleTime, - os.wait_type, - SUM(os.wait_time_ms) OVER (PARTITION BY os.wait_type) AS sum_wait_time_ms, - SUM(os.signal_wait_time_ms) OVER (PARTITION BY os.wait_type ) AS sum_signal_wait_time_ms, - SUM(os.waiting_tasks_count) OVER (PARTITION BY os.wait_type) AS sum_waiting_tasks - FROM sys.dm_os_wait_stats os + SET @StringToExecute = N' + INSERT #WaitStats(Pass, SampleTime, wait_type, wait_time_ms, signal_wait_time_ms, waiting_tasks_count) + SELECT + x.Pass, + x.SampleTime, + x.wait_type, + SUM(x.sum_wait_time_ms) AS sum_wait_time_ms, + SUM(x.sum_signal_wait_time_ms) AS sum_signal_wait_time_ms, + SUM(x.sum_waiting_tasks) AS sum_waiting_tasks + FROM ( + SELECT + 2 AS Pass, + SYSDATETIMEOFFSET() AS SampleTime, + owt.wait_type, + SUM(owt.wait_duration_ms) OVER (PARTITION BY owt.wait_type, owt.session_id) + - CASE WHEN @Seconds = 0 THEN 0 ELSE (@Seconds * 1000) END AS sum_wait_time_ms, + 0 AS sum_signal_wait_time_ms, + CASE @Seconds WHEN 0 THEN 0 ELSE 1 END AS sum_waiting_tasks + FROM sys.dm_os_waiting_tasks owt + WHERE owt.session_id > 50 + AND owt.wait_duration_ms >= CASE @Seconds WHEN 0 THEN 0 ELSE @Seconds * 1000 END + UNION ALL + SELECT + 2 AS Pass, + SYSDATETIMEOFFSET() AS SampleTime, + os.wait_type, + SUM(os.wait_time_ms) OVER (PARTITION BY os.wait_type) AS sum_wait_time_ms, + SUM(os.signal_wait_time_ms) OVER (PARTITION BY os.wait_type ) AS sum_signal_wait_time_ms, + SUM(os.waiting_tasks_count) OVER (PARTITION BY os.wait_type) AS sum_waiting_tasks '; + + IF SERVERPROPERTY('Edition') = 'SQL Azure' + SET @StringToExecute = @StringToExecute + N' FROM sys.dm_db_wait_stats os '; + ELSE + SET @StringToExecute = @StringToExecute + N' FROM sys.dm_os_wait_stats os '; + + SET @StringToExecute = @StringToExecute + N' ) x WHERE NOT EXISTS ( @@ -2480,7 +2495,8 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, AND wc.Ignorable = 1 ) GROUP BY x.Pass, x.SampleTime, x.wait_type - ORDER BY sum_wait_time_ms DESC; + ORDER BY sum_wait_time_ms DESC;'; + EXEC sp_executesql @StringToExecute, N'@Seconds INT', @Seconds; INSERT INTO #FileStats (Pass, SampleTime, DatabaseID, FileID, DatabaseName, FileLogicalName, SizeOnDiskMB, io_stall_read_ms , num_of_reads, [bytes_read] , io_stall_write_ms,num_of_writes, [bytes_written], PhysicalName, TypeDesc, avg_stall_read_ms, avg_stall_write_ms) From 8fc2bd1bdc7cadd8d4a7c3840651ef8fa27179cc Mon Sep 17 00:00:00 2001 From: Erik Darling Date: Thu, 18 Mar 2021 19:13:01 -0400 Subject: [PATCH 137/662] Update sp_BlitzLock.sql This update attempts to fix a few issues, which I know is frowned upon, but one of them is minor and the other ones I discovered while testing changes. Closes #2800 - filters out the Initial Boot Probe SQL Agent lines Closes #2824 - fixes the way we output query text when outputting to Excel Bugs I found along the way: * Filtering out parallel deadlocks got weird after I made fixes in #2686 and #2636 * After I fixed #2674 and dates were corrected, getting object names gathering broke * Fixes were applied across the regular output and the cross-server output --- sp_BlitzLock.sql | 181 ++++++++++++++++++++++++++++++----------------- 1 file changed, 118 insertions(+), 63 deletions(-) diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index 392d24986..f5013bd84 100644 --- a/sp_BlitzLock.sql +++ b/sp_BlitzLock.sql @@ -735,6 +735,7 @@ You need to use an Azure storage account, and the path has to look like this: ht ) AS step_id FROM #deadlock_process AS dp WHERE dp.client_app LIKE 'SQLAgent - %' + AND dp.client_app <> 'SQLAgent - Initial Boot Probe' ) AS x OPTION ( RECOMPILE ); @@ -1288,18 +1289,18 @@ You need to use an Azure storage account, and the path has to look like this: ht dp.priority, dp.log_used, dp.wait_resource COLLATE DATABASE_DEFAULT AS wait_resource, - CONVERT( - XML, - STUFF(( SELECT DISTINCT NCHAR(10) + CONVERT( + XML, + STUFF(( SELECT DISTINCT NCHAR(10) + N' ' + ISNULL(c.object_name, N'') + N' ' COLLATE DATABASE_DEFAULT AS object_name - FROM #deadlock_owner_waiter AS c + FROM #deadlock_owner_waiter AS c WHERE ( dp.id = c.owner_id - OR dp.victim_id = c.waiter_id ) - AND CONVERT(DATE, dp.event_date) = CONVERT(DATE, c.event_date) - FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(4000)'), - 1, 1, N'')) AS object_names, + OR dp.victim_id = c.waiter_id ) + AND dp.event_date = c.event_date + FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(4000)'), + 1, 1, N'')) AS object_names, dp.wait_time, dp.transaction_name, dp.last_tran_started, @@ -1312,7 +1313,6 @@ You need to use an Azure storage account, and the path has to look like this: ht dp.login_name, dp.isolation_level, dp.process_xml.value('(//process/inputbuf/text())[1]', 'NVARCHAR(MAX)') AS inputbuf, - ROW_NUMBER() OVER ( PARTITION BY dp.event_date, dp.id ORDER BY dp.event_date ) AS dn, DENSE_RANK() OVER ( ORDER BY dp.event_date ) AS en, ROW_NUMBER() OVER ( PARTITION BY dp.event_date ORDER BY dp.event_date ) -1 AS qn, dp.is_victim, @@ -1345,20 +1345,18 @@ You need to use an Azure storage account, and the path has to look like this: ht dp.priority, dp.log_used, dp.wait_resource COLLATE DATABASE_DEFAULT, - CASE WHEN @ExportToExcel = 0 THEN - CONVERT( - XML, - STUFF(( SELECT DISTINCT NCHAR(10) - + N' ' - + ISNULL(c.object_name, N'') - + N' ' COLLATE DATABASE_DEFAULT AS object_name - FROM #deadlock_owner_waiter AS c - WHERE ( dp.id = c.owner_id - OR dp.victim_id = c.waiter_id ) - AND CONVERT(DATE, dp.event_date) = CONVERT(DATE, c.event_date) - FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(4000)'), - 1, 1, N'')) - ELSE NULL END AS object_names, + CONVERT( + XML, + STUFF(( SELECT DISTINCT NCHAR(10) + + N' ' + + ISNULL(c.object_name, N'') + + N' ' COLLATE DATABASE_DEFAULT AS object_name + FROM #deadlock_owner_waiter AS c + WHERE ( dp.id = c.owner_id + OR dp.victim_id = c.waiter_id ) + AND dp.event_date = c.event_date + FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(4000)'), + 1, 1, N'')) AS object_names, dp.wait_time, dp.transaction_name, dp.last_tran_started, @@ -1371,7 +1369,6 @@ You need to use an Azure storage account, and the path has to look like this: ht dp.login_name, dp.isolation_level, dp.process_xml.value('(//process/inputbuf/text())[1]', 'NVARCHAR(MAX)') AS inputbuf, - ROW_NUMBER() OVER ( PARTITION BY dp.event_date, dp.id ORDER BY dp.event_date ) AS dn, DENSE_RANK() OVER ( ORDER BY dp.event_date ) AS en, ROW_NUMBER() OVER ( PARTITION BY dp.event_date ORDER BY dp.event_date ) -1 AS qn, 1 AS is_victim, @@ -1473,7 +1470,7 @@ You need to use an Azure storage account, and the path has to look like this: ht FROM deadlocks AS d WHERE d.dn = 1 AND (is_victim = @VictimsOnly OR @VictimsOnly = 0) - AND d.en < CASE WHEN d.deadlock_type = N'Parallel Deadlock' THEN 2 ELSE 2147483647 END + AND d.qn < CASE WHEN d.deadlock_type = N'Parallel Deadlock' THEN 2 ELSE 2147483647 END AND (DB_NAME(d.database_id) = @DatabaseName OR @DatabaseName IS NULL) AND (d.event_date >= @StartDate OR @StartDate IS NULL) AND (d.event_date < @EndDate OR @EndDate IS NULL) @@ -1513,20 +1510,18 @@ ELSE --Output to database is not set output to client app dp.priority, dp.log_used, dp.wait_resource COLLATE DATABASE_DEFAULT AS wait_resource, - CASE WHEN @ExportToExcel = 0 THEN - CONVERT( - XML, - STUFF(( SELECT DISTINCT NCHAR(10) - + N' ' - + ISNULL(c.object_name, N'') - + N' ' COLLATE DATABASE_DEFAULT AS object_name - FROM #deadlock_owner_waiter AS c - WHERE ( dp.id = c.owner_id - OR dp.victim_id = c.waiter_id ) - AND CONVERT(DATE, dp.event_date) = CONVERT(DATE, c.event_date) - FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(4000)'), - 1, 1, N'')) - ELSE NULL END AS object_names, + CONVERT( + XML, + STUFF(( SELECT DISTINCT NCHAR(10) + + N' ' + + ISNULL(c.object_name, N'') + + N' ' COLLATE DATABASE_DEFAULT AS object_name + FROM #deadlock_owner_waiter AS c + WHERE ( dp.id = c.owner_id + OR dp.victim_id = c.waiter_id ) + AND dp.event_date = c.event_date + FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(4000)'), + 1, 1, N'')) AS object_names, dp.wait_time, dp.transaction_name, dp.last_tran_started, @@ -1539,7 +1534,6 @@ ELSE --Output to database is not set output to client app dp.login_name, dp.isolation_level, dp.process_xml.value('(//process/inputbuf/text())[1]', 'NVARCHAR(MAX)') AS inputbuf, - ROW_NUMBER() OVER ( PARTITION BY dp.event_date, dp.id ORDER BY dp.event_date ) AS dn, DENSE_RANK() OVER ( ORDER BY dp.event_date ) AS en, ROW_NUMBER() OVER ( PARTITION BY dp.event_date ORDER BY dp.event_date ) -1 AS qn, dp.is_victim, @@ -1572,20 +1566,18 @@ ELSE --Output to database is not set output to client app dp.priority, dp.log_used, dp.wait_resource COLLATE DATABASE_DEFAULT, - CASE WHEN @ExportToExcel = 0 THEN - CONVERT( - XML, - STUFF(( SELECT DISTINCT NCHAR(10) - + N' ' - + ISNULL(c.object_name, N'') - + N' ' COLLATE DATABASE_DEFAULT AS object_name - FROM #deadlock_owner_waiter AS c - WHERE ( dp.id = c.owner_id - OR dp.victim_id = c.waiter_id ) - AND CONVERT(DATE, dp.event_date) = CONVERT(DATE, c.event_date) - FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(4000)'), - 1, 1, N'')) - ELSE NULL END AS object_names, + CONVERT( + XML, + STUFF(( SELECT DISTINCT NCHAR(10) + + N' ' + + ISNULL(c.object_name, N'') + + N' ' COLLATE DATABASE_DEFAULT AS object_name + FROM #deadlock_owner_waiter AS c + WHERE ( dp.id = c.owner_id + OR dp.victim_id = c.waiter_id ) + AND dp.event_date = c.event_date + FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(4000)'), + 1, 1, N'')) AS object_names, dp.wait_time, dp.transaction_name, dp.last_tran_started, @@ -1598,7 +1590,6 @@ ELSE --Output to database is not set output to client app dp.login_name, dp.isolation_level, dp.process_xml.value('(//process/inputbuf/text())[1]', 'NVARCHAR(MAX)') AS inputbuf, - ROW_NUMBER() OVER ( PARTITION BY dp.event_date, dp.id ORDER BY dp.event_date ) AS dn, DENSE_RANK() OVER ( ORDER BY dp.event_date ) AS en, ROW_NUMBER() OVER ( PARTITION BY dp.event_date ORDER BY dp.event_date ) -1 AS qn, 1 AS is_victim, @@ -1631,8 +1622,8 @@ ELSE --Output to database is not set output to client app + CASE WHEN d.qn = 0 THEN N'1' ELSE CONVERT(NVARCHAR(10), d.qn) END + CASE WHEN d.is_victim = 1 THEN ' - VICTIM' ELSE '' END AS deadlock_group, - CASE WHEN @ExportToExcel = 0 THEN CONVERT(XML, N'') - ELSE SUBSTRING(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(LTRIM(RTRIM(d.inputbuf)),' ','<>'),'><',''),NCHAR(10), ' '),NCHAR(13), ' '),'<>',' '), 1, 32000) END AS query, + CONVERT(XML, N'') AS query_xml, + d.inputbuf AS query_string, d.object_names, d.isolation_level, d.owner_mode, @@ -1661,11 +1652,13 @@ ELSE --Output to database is not set output to client app d.waiter_merging, d.waiter_spilling, d.waiter_waiting_to_close, - CASE WHEN @ExportToExcel = 0 THEN d.deadlock_graph ELSE NULL END AS deadlock_graph - FROM deadlocks AS d + d.deadlock_graph, + d.is_victim + INTO #deadlock_results + FROM deadlocks AS d WHERE d.dn = 1 - AND (is_victim = @VictimsOnly OR @VictimsOnly = 0) - AND d.en < CASE WHEN d.deadlock_type = N'Parallel Deadlock' THEN 2 ELSE 2147483647 END + AND (d.is_victim = @VictimsOnly OR @VictimsOnly = 0) + AND d.qn < CASE WHEN d.deadlock_type = N'Parallel Deadlock' THEN 2 ELSE 2147483647 END AND (DB_NAME(d.database_id) = @DatabaseName OR @DatabaseName IS NULL) AND (d.event_date >= @StartDate OR @StartDate IS NULL) AND (d.event_date < @EndDate OR @EndDate IS NULL) @@ -1673,9 +1666,67 @@ ELSE --Output to database is not set output to client app AND (d.client_app = @AppName OR @AppName IS NULL) AND (d.host_name = @HostName OR @HostName IS NULL) AND (d.login_name = @LoginName OR @LoginName IS NULL) - ORDER BY d.event_date, is_victim DESC OPTION ( RECOMPILE ); + + DECLARE @deadlock_result NVARCHAR(MAX) = N'' + + SET @deadlock_result += N' + SELECT + dr.deadlock_type, + dr.event_date, + dr.database_name, + dr.deadlock_group, + ' + + CASE @ExportToExcel + WHEN 1 + THEN N'dr.query_string AS query, + REPLACE(REPLACE(CONVERT(NVARCHAR(MAX), dr.object_names), '''', ''''), '''', '''') AS object_names,' + ELSE N'dr.query_xml AS query, + dr.object_names,' + END + + N' + dr.isolation_level, + dr.owner_mode, + dr.waiter_mode, + dr.transaction_count, + dr.login_name, + dr.host_name, + dr.client_app, + dr.wait_time, + dr.priority, + dr.log_used, + dr.last_tran_started, + dr.last_batch_started, + dr.last_batch_completed, + dr.transaction_name, + dr.owner_waiter_type, + dr.owner_activity, + dr.owner_waiter_activity, + dr.owner_merging, + dr.owner_spilling, + dr.owner_waiting_to_close, + dr.waiter_waiter_type, + dr.waiter_owner_activity, + dr.waiter_waiter_activity, + dr.waiter_merging, + dr.waiter_spilling, + dr.waiter_waiting_to_close' + + CASE @ExportToExcel + WHEN 1 + THEN N'' + ELSE N', + dr.deadlock_graph' + END + + ' + FROM #deadlock_results AS dr + ORDER BY dr.event_date, dr.is_victim DESC + OPTION(RECOMPILE); + ' + + EXEC sys.sp_executesql + @deadlock_result; + SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); RAISERROR('Findings %s', 0, 1, @d) WITH NOWAIT; SELECT df.check_id, df.database_name, df.object_name, df.finding_group, df.finding @@ -1715,7 +1766,11 @@ ELSE --Output to database is not set output to client app SELECT '#deadlock_stack' AS table_name, * FROM #deadlock_stack AS ds OPTION ( RECOMPILE ); - + + SELECT '#deadlock_results' AS table_name, * + FROM #deadlock_results AS dr + OPTION ( RECOMPILE ); + END; -- End debug END; --Final End From a1fb05eab46c9ad2681279dcf4cda70ec6fd174d Mon Sep 17 00:00:00 2001 From: Erik Darling Date: Thu, 18 Mar 2021 19:50:43 -0400 Subject: [PATCH 138/662] Add ignored databases This adds typically non-production databases to ignore lists in Blitz and BlitzIndex #2787 --- sp_Blitz.sql | 9 +++++++++ sp_BlitzIndex.sql | 1 + 2 files changed, 10 insertions(+) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index 5995f334b..424714196 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -321,6 +321,15 @@ AS ); CREATE CLUSTERED INDEX IX_CheckID_DatabaseName ON #SkipChecks(CheckID, DatabaseName); + INSERT INTO #SkipChecks + (DatabaseName) + SELECT + DB_NAME(d.database_id) + FROM sys.databases AS d + WHERE (DB_NAME(d.database_id) LIKE 'rdsadmin%' + OR LOWER(d.name) IN ('dbatools', 'dbadmin', 'dbmaintenance')) + OPTION(RECOMPILE); + IF(OBJECT_ID('tempdb..#InvalidLogins') IS NOT NULL) BEGIN EXEC sp_executesql N'DROP TABLE #InvalidLogins;'; diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index d47eb23ba..1ebb94005 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -759,6 +759,7 @@ IF @GetAllDatabases = 1 AND database_id > 4 AND DB_NAME(database_id) NOT LIKE 'ReportServer%' AND DB_NAME(database_id) NOT LIKE 'rdsadmin%' + AND LOWER(name) NOT IN('dbatools', 'dbadmin', 'dbmaintenance') AND is_distributor = 0 OPTION ( RECOMPILE ); From 41901aa51c816f9dc35e3e336edc94bb253430d0 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Sat, 20 Mar 2021 06:53:07 +0000 Subject: [PATCH 139/662] #2818 sp_Blitz remove checks 38 and 39 For active & inactive heaps in user databases. Low value, already visible in sp_BlitzIndex. Closes #2818. --- Documentation/sp_Blitz_Checks_by_Priority.md | 1 - sp_Blitz.sql | 64 -------------------- 2 files changed, 65 deletions(-) diff --git a/Documentation/sp_Blitz_Checks_by_Priority.md b/Documentation/sp_Blitz_Checks_by_Priority.md index 5810c2c9e..05fd9b64d 100644 --- a/Documentation/sp_Blitz_Checks_by_Priority.md +++ b/Documentation/sp_Blitz_Checks_by_Priority.md @@ -110,7 +110,6 @@ If you want to add a new one, start at 257. | 150 | Performance | Deadlocks Happening Daily | https://www.BrentOzar.com/go/deadlocks | 124 | | 150 | Performance | Forced Parameterization On | https://www.BrentOzar.com/go/forced | 18 | | 150 | Performance | Foreign Keys Not Trusted | https://www.BrentOzar.com/go/trust | 48 | -| 150 | Performance | Inactive Tables Without Clustered Indexes | https://www.BrentOzar.com/go/heaps | 39 | | 150 | Performance | Leftover Fake Indexes From Wizards | https://www.BrentOzar.com/go/hypo | 46 | | 150 | Performance | Objects created with dangerous SET Options | https://www.BrentOzar.com/go/badset | 218 | | 150 | Performance | Queries Forcing Join Hints | https://www.BrentOzar.com/go/hints | 45 | diff --git a/sp_Blitz.sql b/sp_Blitz.sql index 424714196..7be4605be 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -6383,38 +6383,6 @@ IF @ProductVersionMajor >= 10 HAVING SUM(1) > 0 OPTION (RECOMPILE)'; END; - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 38 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 38) WITH NOWAIT; - - EXEC dbo.sp_MSforeachdb 'USE [?]; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - INSERT INTO #BlitzResults - (CheckID, - DatabaseName, - Priority, - FindingsGroup, - Finding, - URL, - Details) - SELECT DISTINCT 38, - N''?'', - 110, - ''Performance'', - ''Active Tables Without Clustered Indexes'', - ''https://www.brentozar.com/go/heaps'', - (''The ['' + DB_NAME() + ''] database has heaps - tables without a clustered index - that are being actively queried.'') - FROM [?].sys.indexes i INNER JOIN [?].sys.objects o ON i.object_id = o.object_id - INNER JOIN [?].sys.partitions p ON i.object_id = p.object_id AND i.index_id = p.index_id - INNER JOIN sys.databases sd ON sd.name = N''?'' - LEFT OUTER JOIN [?].sys.dm_db_index_usage_stats ius ON i.object_id = ius.object_id AND i.index_id = ius.index_id AND ius.database_id = sd.database_id - WHERE i.type_desc = ''HEAP'' AND COALESCE(NULLIF(ius.user_seeks,0), NULLIF(ius.user_scans,0), NULLIF(ius.user_lookups,0), NULLIF(ius.user_updates,0)) IS NOT NULL - AND sd.name <> ''tempdb'' AND sd.name <> ''DWDiagnostics'' AND o.is_ms_shipped = 0 AND o.type <> ''S'' OPTION (RECOMPILE)'; - END; - IF NOT EXISTS ( SELECT 1 FROM #SkipChecks WHERE DatabaseName IS NULL AND CheckID = 164 ) @@ -6443,38 +6411,6 @@ IF @ProductVersionMajor >= 10 FROM [?].sys.plan_guides g CROSS APPLY fn_validate_plan_guide(g.plan_guide_id) OPTION (RECOMPILE)'; END; - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 39 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 39) WITH NOWAIT; - - EXEC dbo.sp_MSforeachdb 'USE [?]; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - INSERT INTO #BlitzResults - (CheckID, - DatabaseName, - Priority, - FindingsGroup, - Finding, - URL, - Details) - SELECT DISTINCT 39, - N''?'', - 150, - ''Performance'', - ''Inactive Tables Without Clustered Indexes'', - ''https://www.brentozar.com/go/heaps'', - (''The ['' + DB_NAME() + ''] database has heaps - tables without a clustered index - that have not been queried since the last restart. These may be backup tables carelessly left behind.'') - FROM [?].sys.indexes i INNER JOIN [?].sys.objects o ON i.object_id = o.object_id - INNER JOIN [?].sys.partitions p ON i.object_id = p.object_id AND i.index_id = p.index_id - INNER JOIN sys.databases sd ON sd.name = N''?'' - LEFT OUTER JOIN [?].sys.dm_db_index_usage_stats ius ON i.object_id = ius.object_id AND i.index_id = ius.index_id AND ius.database_id = sd.database_id - WHERE i.type_desc = ''HEAP'' AND COALESCE(NULLIF(ius.user_seeks,0), NULLIF(ius.user_scans,0), NULLIF(ius.user_lookups,0), NULLIF(ius.user_updates,0)) IS NULL - AND sd.name <> ''tempdb'' AND sd.name <> ''DWDiagnostics'' AND o.is_ms_shipped = 0 AND o.type <> ''S'' OPTION (RECOMPILE)'; - END; - IF NOT EXISTS ( SELECT 1 FROM #SkipChecks WHERE DatabaseName IS NULL AND CheckID = 46 ) From ee97c1c2194d80fc721146f78f6d5526c24b581d Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Sun, 21 Mar 2021 00:03:05 -0700 Subject: [PATCH 140/662] 2021-03 installer scripts Setting up for release. --- Documentation/Development/_TestBed.sql | 16 +- Install-All-Scripts.sql | 1189 ++++++++++++----------- Install-Core-Blitz-No-Query-Store.sql | 1099 +++++++++++---------- Install-Core-Blitz-With-Query-Store.sql | 1163 +++++++++++----------- SqlServerVersions.sql | 1 + sp_AllNightLog.sql | 2 +- sp_AllNightLog_Setup.sql | 2 +- sp_Blitz.sql | 2 +- sp_BlitzAnalysis.sql | 2 +- sp_BlitzBackups.sql | 2 +- sp_BlitzCache.sql | 2 +- sp_BlitzFirst.sql | 2 +- sp_BlitzInMemoryOLTP.sql | 2 +- sp_BlitzIndex.sql | 2 +- sp_BlitzLock.sql | 2 +- sp_BlitzQueryStore.sql | 2 +- sp_BlitzWho.sql | 2 +- sp_DatabaseRestore.sql | 2 +- sp_ineachdb.sql | 2 +- 19 files changed, 1858 insertions(+), 1638 deletions(-) diff --git a/Documentation/Development/_TestBed.sql b/Documentation/Development/_TestBed.sql index 26b2b02aa..a2ad13748 100644 --- a/Documentation/Development/_TestBed.sql +++ b/Documentation/Development/_TestBed.sql @@ -1,4 +1,4 @@ -/*Blitz*/ +/*Blitz*/ EXEC dbo.sp_Blitz @CheckUserDatabaseObjects = 1, @CheckServerInfo = 1; EXEC dbo.sp_Blitz @CheckUserDatabaseObjects = 1, @CheckServerInfo = 1, @OutputDatabaseName = 'DBAtools', @OutputSchemaName = 'dbo', @OutputTableName = 'Blitz'; @@ -45,9 +45,9 @@ SELECT TOP 100 * FROM DBAtools.dbo.BlitzWho ORDER BY 1 DESC; /*BlitzIndex*/ EXEC dbo.sp_BlitzIndex @GetAllDatabases = 1, @Mode = 4; -EXEC dbo.sp_BlitzIndex @DatabaseName = 'StackOverflow2010', @Mode = 4; +EXEC dbo.sp_BlitzIndex @DatabaseName = 'StackOverflow', @Mode = 4; -EXEC dbo.sp_BlitzIndex @DatabaseName = 'StackOverflow2010', @Mode = 4, @SkipPartitions = 0, @SkipStatistics = 0; +EXEC dbo.sp_BlitzIndex @DatabaseName = 'StackOverflow', @Mode = 4, @SkipPartitions = 0, @SkipStatistics = 0; EXEC dbo.sp_BlitzIndex @GetAllDatabases = 1, @Mode = 1; @@ -57,13 +57,13 @@ EXEC dbo.sp_BlitzIndex @GetAllDatabases = 1, @Mode = 3; /*BlitzCache*/ -EXEC dbo.sp_BlitzCache @SortOrder = 'all', @Debug = 1; +EXEC dbo.sp_BlitzCache @SortOrder = 'all'; -EXEC dbo.sp_BlitzCache @SortOrder = 'all avg'; +EXEC dbo.sp_BlitzCache @SortOrder = 'all avg', @Debug = 1; EXEC dbo.sp_BlitzCache @MinimumExecutionCount = 10; -EXEC dbo.sp_BlitzCache @DatabaseName = N'StackOverflow2010'; +EXEC dbo.sp_BlitzCache @DatabaseName = N'StackOverflow'; EXEC dbo.sp_BlitzCache @OutputDatabaseName = 'DBAtools', @OutputSchemaName = 'dbo', @OutputTableName = 'BlitzCache'; @@ -72,7 +72,7 @@ EXEC dbo.sp_BlitzCache @ExpertMode = 1; EXEC dbo.sp_BlitzCache @ExpertMode = 2; /*BlitzQueryStore*/ -EXEC dbo.sp_BlitzQueryStore @DatabaseName = 'StackOverflow2010'; +EXEC dbo.sp_BlitzQueryStore @DatabaseName = 'StackOverflow'; /*BlitzBackups*/ EXEC dbo.sp_BlitzBackups @HoursBack = 1000000; @@ -81,4 +81,4 @@ EXEC dbo.sp_BlitzBackups @HoursBack = 1000000; EXEC dbo.sp_AllNightLog_Setup @RPOSeconds = 30, @RTOSeconds = 30, @BackupPath = 'D:\Backup', @RestorePath = 'D:\Backup', @RunSetup = 1; /*sp_BlitzLock*/ -EXEC dbo.sp_BlitzLock @Debug = 1; \ No newline at end of file +EXEC dbo.sp_BlitzLock @Debug = 1; diff --git a/Install-All-Scripts.sql b/Install-All-Scripts.sql index af87b9405..e1e4a7124 100755 --- a/Install-All-Scripts.sql +++ b/Install-All-Scripts.sql @@ -30,7 +30,7 @@ SET NOCOUNT ON; BEGIN; -SELECT @Version = '8.01', @VersionDate = '20210222'; +SELECT @Version = '8.02', @VersionDate = '20210321'; IF(@VersionCheckMode = 1) BEGIN @@ -1524,7 +1524,7 @@ SET NOCOUNT ON; BEGIN; -SELECT @Version = '8.01', @VersionDate = '20210222'; +SELECT @Version = '8.02', @VersionDate = '20210321'; IF(@VersionCheckMode = 1) BEGIN @@ -2856,7 +2856,7 @@ AS SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.01', @VersionDate = '20210222'; + SELECT @Version = '8.02', @VersionDate = '20210321'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -3140,6 +3140,15 @@ AS ); CREATE CLUSTERED INDEX IX_CheckID_DatabaseName ON #SkipChecks(CheckID, DatabaseName); + INSERT INTO #SkipChecks + (DatabaseName) + SELECT + DB_NAME(d.database_id) + FROM sys.databases AS d + WHERE (DB_NAME(d.database_id) LIKE 'rdsadmin%' + OR LOWER(d.name) IN ('dbatools', 'dbadmin', 'dbmaintenance')) + OPTION(RECOMPILE); + IF(OBJECT_ID('tempdb..#InvalidLogins') IS NOT NULL) BEGIN EXEC sp_executesql N'DROP TABLE #InvalidLogins;'; @@ -3619,7 +3628,7 @@ AS 240, 'Wait Stats', 'Wait Stats Have Been Cleared', - 'https://BrentOzar.com/go/waits', + 'https://www.brentozar.com/go/waits', 'Someone ran DBCC SQLPERF to clear sys.dm_os_wait_stats at approximately: ' + CONVERT(NVARCHAR(100), DATEADD(MINUTE, (-1. * (@MsSinceWaitsCleared) / 1000. / 60.), GETDATE()), 120)); @@ -3728,7 +3737,7 @@ AS 1 AS Priority , 'Backup' AS FindingsGroup , 'Backups Not Performed Recently' AS Finding , - 'https://BrentOzar.com/go/nobak' AS URL , + 'https://www.brentozar.com/go/nobak' AS URL , 'Last backed up: ' + COALESCE(CAST(MAX(b.backup_finish_date) AS VARCHAR(25)),'never') AS Details FROM master.sys.databases d @@ -3768,7 +3777,7 @@ AS 1 AS Priority , 'Backup' AS FindingsGroup , 'Backups Not Performed Recently' AS Finding , - 'https://BrentOzar.com/go/nobak' AS URL , + 'https://www.brentozar.com/go/nobak' AS URL , 'Last backed up: ' + COALESCE(CAST(MAX(b.backup_finish_date) AS VARCHAR(25)),'never') AS Details FROM master.sys.databases d @@ -3833,7 +3842,7 @@ AS 1 AS Priority , 'Backup' AS FindingsGroup , 'Full Recovery Model w/o Log Backups' AS Finding , - 'https://BrentOzar.com/go/biglogs' AS URL , + 'https://www.brentozar.com/go/biglogs' AS URL , ( 'The ' + CAST(CAST((SELECT ((SUM([mf].[size]) * 8.) / 1024.) FROM sys.[master_files] AS [mf] WHERE [mf].[database_id] = d.[database_id] AND [mf].[type_desc] = 'LOG') AS DECIMAL(18,2)) AS VARCHAR(30)) + 'MB log file has not been backed up in the last week.' ) AS Details FROM master.sys.databases d WHERE d.recovery_model IN ( 1, 2 ) @@ -3938,7 +3947,7 @@ AS 230 AS Priority, ''Security'' AS FindingsGroup, ''Server Audits Running'' AS Finding, - ''https://BrentOzar.com/go/audits'' AS URL, + ''https://www.brentozar.com/go/audits'' AS URL, (''SQL Server built-in audit functionality is being used by server audit: '' + [name]) AS Details FROM sys.dm_server_audit_status OPTION (RECOMPILE);'; IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; @@ -3983,7 +3992,7 @@ AS 1 AS Priority , 'Backup' AS FindingsGroup , 'Backing Up to Same Drive Where Databases Reside' AS Finding , - 'https://BrentOzar.com/go/backup' AS URL , + 'https://www.brentozar.com/go/backup' AS URL , CAST(COUNT(1) AS VARCHAR(50)) + ' backups done on drive ' + UPPER(LEFT(bmf.physical_device_name, 3)) + ' in the last two weeks, where database files also live. This represents a serious risk if that array fails.' Details @@ -4021,7 +4030,7 @@ AS ''Backup'' AS FindingsGroup, ''TDE Certificate Not Backed Up Recently'' AS Finding, db_name(dek.database_id) AS DatabaseName, - ''https://BrentOzar.com/go/tde'' AS URL, + ''https://www.brentozar.com/go/tde'' AS URL, ''The certificate '' + c.name + '' is used to encrypt database '' + db_name(dek.database_id) + ''. Last backup date: '' + COALESCE(CAST(c.pvt_key_last_backup_date AS VARCHAR(100)), ''Never'') AS Details FROM sys.certificates c INNER JOIN sys.dm_database_encryption_keys dek ON c.thumbprint = dek.encryptor_thumbprint WHERE pvt_key_last_backup_date IS NULL OR pvt_key_last_backup_date <= DATEADD(dd, -30, GETDATE()) OPTION (RECOMPILE);'; @@ -4050,7 +4059,7 @@ AS 1 AS Priority, ''Backup'' AS FindingsGroup, ''Encryption Certificate Not Backed Up Recently'' AS Finding, - ''https://BrentOzar.com/go/tde'' AS URL, + ''https://www.brentozar.com/go/tde'' AS URL, ''The certificate '' + c.name + '' is used to encrypt database backups. Last backup date: '' + COALESCE(CAST(c.pvt_key_last_backup_date AS VARCHAR(100)), ''Never'') AS Details FROM sys.certificates c INNER JOIN msdb.dbo.backupset bs ON c.thumbprint = bs.encryptor_thumbprint @@ -4087,7 +4096,7 @@ AS 200 AS Priority , 'Backup' AS FindingsGroup , 'MSDB Backup History Not Purged' AS Finding , - 'https://BrentOzar.com/go/history' AS URL , + 'https://www.brentozar.com/go/history' AS URL , ( 'Database backup history retained back to ' + CAST(bs.backup_start_date AS VARCHAR(20)) ) AS Details FROM msdb.dbo.backupset bs @@ -4122,7 +4131,7 @@ AS 200 AS Priority , 'Backup' AS FindingsGroup , 'MSDB Backup History Purged Too Frequently' AS Finding , - 'https://BrentOzar.com/go/history' AS URL , + 'https://www.brentozar.com/go/history' AS URL , ( 'Database backup history only retained back to ' + CAST(bs.backup_start_date AS VARCHAR(20)) ) AS Details FROM msdb.dbo.backupset bs @@ -4155,7 +4164,7 @@ AS 200 AS Priority , 'Performance' AS FindingsGroup , 'Snapshot Backups Occurring' AS Finding , - 'https://BrentOzar.com/go/snaps' AS URL , + 'https://www.brentozar.com/go/snaps' AS URL , ( CAST(COUNT(*) AS VARCHAR(20)) + ' snapshot-looking backups have occurred in the last two weeks, indicating that IO may be freezing up.') AS Details FROM msdb.dbo.backupset bs WHERE bs.type = 'D' @@ -4183,7 +4192,7 @@ AS 50 AS Priority , 'Performance' AS FindingsGroup , 'Snapshotting Too Many Databases' AS Finding , - 'https://BrentOzar.com/go/toomanysnaps' AS URL , + 'https://www.brentozar.com/go/toomanysnaps' AS URL , ( CAST(SUM(1) AS VARCHAR(20)) + ' databases snapshotted at once in the last two weeks, indicating that IO may be freezing up. Microsoft does not recommend VSS snaps for 35 or more databases.') AS Details FROM msdb.dbo.backupset bs WHERE bs.type = 'D' @@ -4212,7 +4221,7 @@ AS 230 AS Priority , 'Security' AS FindingsGroup , 'Sysadmins' AS Finding , - 'https://BrentOzar.com/go/sa' AS URL , + 'https://www.brentozar.com/go/sa' AS URL , ( 'Login [' + l.name + '] is a sysadmin - meaning they can do absolutely anything in SQL Server, including dropping databases or hiding their tracks.' ) AS Details FROM master.sys.syslogins l @@ -4270,7 +4279,7 @@ AS 230 AS Priority , 'Security' AS FindingsGroup , 'Security Admins' AS Finding , - 'https://BrentOzar.com/go/sa' AS URL , + 'https://www.brentozar.com/go/sa' AS URL , ( 'Login [' + l.name + '] is a security admin - meaning they can give themselves permission to do absolutely anything in SQL Server, including dropping databases or hiding their tracks.' ) AS Details FROM master.sys.syslogins l @@ -4298,7 +4307,7 @@ AS 230 AS [Priority] , 'Security' AS [FindingsGroup] , 'Login Can Control Server' AS [Finding] , - 'https://BrentOzar.com/go/sa' AS [URL] , + 'https://www.brentozar.com/go/sa' AS [URL] , 'Login [' + pri.[name] + '] has the CONTROL SERVER permission - meaning they can do absolutely anything in SQL Server, including dropping databases or hiding their tracks.' AS [Details] FROM sys.server_principals AS pri @@ -4330,7 +4339,7 @@ AS 230 AS Priority , 'Security' AS FindingsGroup , 'Jobs Owned By Users' AS Finding , - 'https://BrentOzar.com/go/owners' AS URL , + 'https://www.brentozar.com/go/owners' AS URL , ( 'Job [' + j.name + '] is owned by [' + SUSER_SNAME(j.owner_sid) + '] - meaning if their login is disabled or not available due to Active Directory problems, the job will stop working.' ) AS Details @@ -4360,7 +4369,7 @@ AS 230 AS Priority , 'Security' AS FindingsGroup , 'Stored Procedure Runs at Startup' AS Finding , - 'https://BrentOzar.com/go/startup' AS URL , + 'https://www.brentozar.com/go/startup' AS URL , ( 'Stored procedure [master].[' + r.SPECIFIC_SCHEMA + '].[' + r.SPECIFIC_NAME @@ -4391,7 +4400,7 @@ AS 100 AS Priority, ''Performance'' AS FindingsGroup, ''Resource Governor Enabled'' AS Finding, - ''https://BrentOzar.com/go/rg'' AS URL, + ''https://www.brentozar.com/go/rg'' AS URL, (''Resource Governor is enabled. Queries may be throttled. Make sure you understand how the Classifier Function is configured.'') AS Details FROM sys.resource_governor_configuration WHERE is_enabled = 1 OPTION (RECOMPILE);'; IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; @@ -4421,7 +4430,7 @@ AS 100 AS Priority, ''Performance'' AS FindingsGroup, ''Server Triggers Enabled'' AS Finding, - ''https://BrentOzar.com/go/logontriggers/'' AS URL, + ''https://www.brentozar.com/go/logontriggers/'' AS URL, (''Server Trigger ['' + [name] ++ ''] is enabled. Make sure you understand what that trigger is doing - the less work it does, the better.'') AS Details FROM sys.server_triggers WHERE is_disabled = 0 AND is_ms_shipped = 0 OPTION (RECOMPILE);'; IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; @@ -4452,7 +4461,7 @@ AS 10 AS Priority , 'Performance' AS FindingsGroup , 'Auto-Close Enabled' AS Finding , - 'https://BrentOzar.com/go/autoclose' AS URL , + 'https://www.brentozar.com/go/autoclose' AS URL , ( 'Database [' + [name] + '] has auto-close enabled. This setting can dramatically decrease performance.' ) AS Details FROM sys.databases @@ -4484,7 +4493,7 @@ AS 10 AS Priority , 'Performance' AS FindingsGroup , 'Auto-Shrink Enabled' AS Finding , - 'https://BrentOzar.com/go/autoshrink' AS URL , + 'https://www.brentozar.com/go/autoshrink' AS URL , ( 'Database [' + [name] + '] has auto-shrink enabled. This setting can dramatically decrease performance.' ) AS Details FROM sys.databases @@ -4518,7 +4527,7 @@ AS 50 AS Priority, ''Reliability'' AS FindingsGroup, ''Page Verification Not Optimal'' AS Finding, - ''https://BrentOzar.com/go/torn'' AS URL, + ''https://www.brentozar.com/go/torn'' AS URL, (''Database ['' + [name] + ''] has '' + [page_verify_option_desc] + '' for page verification. SQL Server may have a harder time recognizing and recovering from storage corruption. Consider using CHECKSUM instead.'') COLLATE database_default AS Details FROM sys.databases WHERE page_verify_option < 2 @@ -4554,7 +4563,7 @@ AS 110 AS Priority , 'Performance' AS FindingsGroup , 'Auto-Create Stats Disabled' AS Finding , - 'https://BrentOzar.com/go/acs' AS URL , + 'https://www.brentozar.com/go/acs' AS URL , ( 'Database [' + [name] + '] has auto-create-stats disabled. SQL Server uses statistics to build better execution plans, and without the ability to automatically create more, performance may suffer.' ) AS Details FROM sys.databases @@ -4586,7 +4595,7 @@ AS 110 AS Priority , 'Performance' AS FindingsGroup , 'Auto-Update Stats Disabled' AS Finding , - 'https://BrentOzar.com/go/aus' AS URL , + 'https://www.brentozar.com/go/aus' AS URL , ( 'Database [' + [name] + '] has auto-update-stats disabled. SQL Server uses statistics to build better execution plans, and without the ability to automatically update them, performance may suffer.' ) AS Details FROM sys.databases @@ -4618,7 +4627,7 @@ AS 150 AS Priority , 'Performance' AS FindingsGroup , 'Stats Updated Asynchronously' AS Finding , - 'https://BrentOzar.com/go/asyncstats' AS URL , + 'https://www.brentozar.com/go/asyncstats' AS URL , ( 'Database [' + [name] + '] has auto-update-stats-async enabled. When SQL Server gets a query for a table with out-of-date statistics, it will run the query with the stats it has - while updating stats to make later queries better. The initial run of the query may suffer, though.' ) AS Details FROM sys.databases @@ -4650,7 +4659,7 @@ AS 200 AS Priority , 'Informational' AS FindingsGroup , 'Date Correlation On' AS Finding , - 'https://BrentOzar.com/go/corr' AS URL , + 'https://www.brentozar.com/go/corr' AS URL , ( 'Database [' + [name] + '] has date correlation enabled. This is not a default setting, and it has some performance overhead. It tells SQL Server that date fields in two tables are related, and SQL Server maintains statistics showing that relation.' ) AS Details FROM sys.databases @@ -4685,7 +4694,7 @@ AS 200 AS Priority, ''Informational'' AS FindingsGroup, ''Database Encrypted'' AS Finding, - ''https://BrentOzar.com/go/tde'' AS URL, + ''https://www.brentozar.com/go/tde'' AS URL, (''Database ['' + [name] + ''] has Transparent Data Encryption enabled. Make absolutely sure you have backed up the certificate and private key, or else you will not be able to restore this database.'') AS Details FROM sys.databases WHERE is_encrypted = 1 @@ -4896,7 +4905,7 @@ AS 200 AS Priority , 'Non-Default Server Config' AS FindingsGroup , cr.name AS Finding , - 'https://BrentOzar.com/go/conf' AS URL , + 'https://www.brentozar.com/go/conf' AS URL , ( 'This sp_configure option has been changed. Its default value is ' + COALESCE(CAST(cd.[DefaultValue] AS VARCHAR(100)), '(unknown)') @@ -4938,7 +4947,7 @@ AS 200, 'Performance', 'Non-Dynamic Memory', - 'https://BrentOzar.com/go/memory', + 'https://www.brentozar.com/go/memory', 'Minimum Server Memory setting is the same as the Maximum (both set to ' + CAST(@MinServerMemory AS NVARCHAR(50)) + '). This will not allow dynamic memory. Please revise memory settings' ); END; @@ -4978,7 +4987,7 @@ AS 200 AS Priority , 'Performance' AS FindingsGroup , cr.name AS Finding , - 'https://BrentOzar.com/go/cxpacket' AS URL , + 'https://www.brentozar.com/go/cxpacket' AS URL , ( 'Set to ' + CAST(cr.value_in_use AS NVARCHAR(50)) + ', its default value. Changing this sp_configure setting may reduce CXPACKET waits.') FROM sys.configurations cr INNER JOIN #ConfigurationDefaults cd ON cd.name = cr.name @@ -5009,7 +5018,7 @@ AS 170 AS Priority , 'File Configuration' AS FindingsGroup , 'System Database on C Drive' AS Finding , - 'https://BrentOzar.com/go/cdrive' AS URL , + 'https://www.brentozar.com/go/cdrive' AS URL , ( 'The ' + DB_NAME(database_id) + ' database has a file on the C drive. Putting system databases on the C drive runs the risk of crashing the server when it runs out of space.' ) AS Details FROM sys.master_files @@ -5041,7 +5050,7 @@ AS 20 AS Priority , 'File Configuration' AS FindingsGroup , 'TempDB on C Drive' AS Finding , - 'https://BrentOzar.com/go/cdrive' AS URL , + 'https://www.brentozar.com/go/cdrive' AS URL , CASE WHEN growth > 0 THEN ( 'The tempdb database has files on the C drive. TempDB frequently grows unpredictably, putting your server at risk of running out of C drive space and crashing hard. C is also often much slower than other drives, so performance may be suffering.' ) ELSE ( 'The tempdb database has files on the C drive. TempDB is not set to Autogrow, hopefully it is big enough. C is also often much slower than other drives, so performance may be suffering.' ) @@ -5074,7 +5083,7 @@ AS 20 AS Priority , 'Reliability' AS FindingsGroup , 'User Databases on C Drive' AS Finding , - 'https://BrentOzar.com/go/cdrive' AS URL , + 'https://www.brentozar.com/go/cdrive' AS URL , ( 'The ' + DB_NAME(database_id) + ' database has a file on the C drive. Putting databases on the C drive runs the risk of crashing the server when it runs out of space.' ) AS Details FROM sys.master_files @@ -5110,7 +5119,7 @@ AS 200 AS Priority , 'Informational' AS FindingsGroup , 'Tables in the Master Database' AS Finding , - 'https://BrentOzar.com/go/mastuser' AS URL , + 'https://www.brentozar.com/go/mastuser' AS URL , ( 'The ' + name + ' table in the master database was created by end users on ' + CAST(create_date AS VARCHAR(20)) @@ -5142,7 +5151,7 @@ AS 200 AS Priority , 'Informational' AS FindingsGroup , 'Tables in the MSDB Database' AS Finding , - 'https://BrentOzar.com/go/msdbuser' AS URL , + 'https://www.brentozar.com/go/msdbuser' AS URL , ( 'The ' + name + ' table in the msdb database was created by end users on ' + CAST(create_date AS VARCHAR(20)) @@ -5172,7 +5181,7 @@ AS 200 AS Priority , 'Informational' AS FindingsGroup , 'Tables in the Model Database' AS Finding , - 'https://BrentOzar.com/go/model' AS URL , + 'https://www.brentozar.com/go/model' AS URL , ( 'The ' + name + ' table in the model database was created by end users on ' + CAST(create_date AS VARCHAR(20)) @@ -5206,7 +5215,7 @@ AS 200 AS Priority , 'Monitoring' AS FindingsGroup , 'Not All Alerts Configured' AS Finding , - 'https://BrentOzar.com/go/alert' AS URL , + 'https://www.brentozar.com/go/alert' AS URL , ( 'Not all SQL Server Agent alerts have been configured. This is a free, easy way to get notified of corruption, job failures, or major outages even before monitoring systems pick it up.' ) AS Details; END; END; @@ -5237,7 +5246,7 @@ AS 200 AS Priority , 'Monitoring' AS FindingsGroup , 'Alerts Configured without Follow Up' AS Finding , - 'https://BrentOzar.com/go/alert' AS URL , + 'https://www.brentozar.com/go/alert' AS URL , ( 'SQL Server Agent alerts have been configured but they either do not notify anyone or else they do not take any action. This is a free, easy way to get notified of corruption, job failures, or major outages even before monitoring systems pick it up.' ) AS Details; END; @@ -5267,7 +5276,7 @@ AS 200 AS Priority , 'Monitoring' AS FindingsGroup , 'No Alerts for Corruption' AS Finding , - 'https://BrentOzar.com/go/alert' AS URL , + 'https://www.brentozar.com/go/alert' AS URL , ( 'SQL Server Agent alerts do not exist for errors 823, 824, and 825. These three errors can give you notification about early hardware failure. Enabling them can prevent you a lot of heartbreak.' ) AS Details; END; @@ -5297,7 +5306,7 @@ AS 200 AS Priority , 'Monitoring' AS FindingsGroup , 'No Alerts for Sev 19-25' AS Finding , - 'https://BrentOzar.com/go/alert' AS URL , + 'https://www.brentozar.com/go/alert' AS URL , ( 'SQL Server Agent alerts do not exist for severity levels 19 through 25. These are some very severe SQL Server errors. Knowing that these are happening may let you recover from errors faster.' ) AS Details; END; @@ -5329,7 +5338,7 @@ AS 200 AS Priority , 'Monitoring' AS FindingsGroup , 'Alerts Disabled' AS Finding , - 'https://BrentOzar.com/go/alert' AS URL , + 'https://www.brentozar.com/go/alert' AS URL , ( 'The following Alert is disabled, please review and enable if desired: ' + name ) AS Details FROM msdb.dbo.sysalerts @@ -5362,7 +5371,7 @@ AS ,200 AS [Priority] ,'Monitoring' AS FindingsGroup ,'Alerts Without Event Descriptions' AS Finding - ,'https://BrentOzar.com/go/alert' AS [URL] + ,'https://www.brentozar.com/go/alert' AS [URL] ,('The following Alert is not including detailed event descriptions in its output messages: ' + QUOTENAME([name]) + '. You can fix it by ticking the relevant boxes in its Properties --> Options page.') AS Details FROM msdb.dbo.sysalerts @@ -5396,7 +5405,7 @@ AS 200 AS Priority , 'Monitoring' AS FindingsGroup , 'No Operators Configured/Enabled' AS Finding , - 'https://BrentOzar.com/go/op' AS URL , + 'https://www.brentozar.com/go/op' AS URL , ( 'No SQL Server Agent operators (emails) have been configured. This is a free, easy way to get notified of corruption, job failures, or major outages even before monitoring systems pick it up.' ) AS Details; END; @@ -5427,7 +5436,7 @@ AS 1 AS Priority , ''Corruption'' AS FindingsGroup , ''Database Corruption Detected'' AS Finding , - ''https://BrentOzar.com/go/repair'' AS URL , + ''https://www.brentozar.com/go/repair'' AS URL , ( ''Database mirroring has automatically repaired at least one corrupt page in the last 30 days. For more information, query the DMV sys.dm_db_mirroring_auto_page_repair.'' ) AS Details FROM (SELECT rp2.database_id, rp2.modification_time FROM sys.dm_db_mirroring_auto_page_repair rp2 @@ -5471,7 +5480,7 @@ AS 1 AS Priority , ''Corruption'' AS FindingsGroup , ''Database Corruption Detected'' AS Finding , - ''https://BrentOzar.com/go/repair'' AS URL , + ''https://www.brentozar.com/go/repair'' AS URL , ( ''Availability Groups has automatically repaired at least one corrupt page in the last 30 days. For more information, query the DMV sys.dm_hadr_auto_page_repair.'' ) AS Details FROM sys.dm_hadr_auto_page_repair rp INNER JOIN master.sys.databases db ON rp.database_id = db.database_id @@ -5509,7 +5518,7 @@ AS 1 AS Priority , ''Corruption'' AS FindingsGroup , ''Database Corruption Detected'' AS Finding , - ''https://BrentOzar.com/go/repair'' AS URL , + ''https://www.brentozar.com/go/repair'' AS URL , ( ''SQL Server has detected at least one corrupt page in the last 30 days. For more information, query the system table msdb.dbo.suspect_pages.'' ) AS Details FROM msdb.dbo.suspect_pages sp INNER JOIN master.sys.databases db ON sp.database_id = db.database_id @@ -5543,7 +5552,7 @@ AS 'Performance' AS FindingsGroup , 'Slow Storage Reads on Drive ' + UPPER(LEFT(mf.physical_name, 1)) AS Finding , - 'https://BrentOzar.com/go/slow' AS URL , + 'https://www.brentozar.com/go/slow' AS URL , 'Reads are averaging longer than 200ms for at least one database on this drive. For specific database file speeds, run the query from the information link.' AS Details FROM sys.dm_io_virtual_file_stats(NULL, NULL) AS fs @@ -5574,7 +5583,7 @@ AS 'Performance' AS FindingsGroup , 'Slow Storage Writes on Drive ' + UPPER(LEFT(mf.physical_name, 1)) AS Finding , - 'https://BrentOzar.com/go/slow' AS URL , + 'https://www.brentozar.com/go/slow' AS URL , 'Writes are averaging longer than 100ms for at least one database on this drive. For specific database file speeds, run the query from the information link.' AS Details FROM sys.dm_io_virtual_file_stats(NULL, NULL) AS fs @@ -5611,7 +5620,7 @@ AS 170 , 'File Configuration' , 'TempDB Only Has 1 Data File' , - 'https://BrentOzar.com/go/tempdb' , + 'https://www.brentozar.com/go/tempdb' , 'TempDB is only configured with one data file. More data files are usually required to alleviate SGAM contention.' ); END; @@ -5646,7 +5655,7 @@ AS 170 , 'File Configuration' , 'TempDB Unevenly Sized Data Files' , - 'https://BrentOzar.com/go/tempdb' , + 'https://www.brentozar.com/go/tempdb' , 'TempDB data files are not configured with the same size. Unevenly sized tempdb data files will result in unevenly sized workloads.' ); END; @@ -5671,7 +5680,7 @@ AS 150 AS Priority , 'Performance' AS FindingsGroup , 'Queries Forcing Order Hints' AS Finding , - 'https://BrentOzar.com/go/hints' AS URL , + 'https://www.brentozar.com/go/hints' AS URL , CAST(occurrence AS VARCHAR(10)) + ' instances of order hinting have been recorded since restart. This means queries are bossing the SQL Server optimizer around, and if they don''t know what they''re doing, this can cause more harm than good. This can also explain why DBA tuning efforts aren''t working.' AS Details FROM sys.dm_exec_query_optimizer_info @@ -5698,7 +5707,7 @@ AS 150 AS Priority , 'Performance' AS FindingsGroup , 'Queries Forcing Join Hints' AS Finding , - 'https://BrentOzar.com/go/hints' AS URL , + 'https://www.brentozar.com/go/hints' AS URL , CAST(occurrence AS VARCHAR(10)) + ' instances of join hinting have been recorded since restart. This means queries are bossing the SQL Server optimizer around, and if they don''t know what they''re doing, this can cause more harm than good. This can also explain why DBA tuning efforts aren''t working.' AS Details FROM sys.dm_exec_query_optimizer_info @@ -5726,7 +5735,7 @@ AS 200 AS Priority , 'Informational' AS FindingsGroup , 'Linked Server Configured' AS Finding , - 'https://BrentOzar.com/go/link' AS URL , + 'https://www.brentozar.com/go/link' AS URL , +CASE WHEN l.remote_name = 'sa' THEN COALESCE(s.data_source, s.provider) + ' is configured as a linked server. Check its security configuration as it is connecting with sa, because any user who queries it will get admin-level permissions.' @@ -5753,7 +5762,7 @@ AS 100 AS Priority , ''Performance'' AS FindingsGroup , ''Max Memory Set Too High'' AS Finding , - ''https://BrentOzar.com/go/max'' AS URL , + ''https://www.brentozar.com/go/max'' AS URL , ''SQL Server max memory is set to '' + CAST(c.value_in_use AS VARCHAR(20)) + '' megabytes, but the server only has '' @@ -5785,7 +5794,7 @@ AS 1 AS Priority , ''Performance'' AS FindingsGroup , ''Memory Dangerously Low'' AS Finding , - ''https://BrentOzar.com/go/max'' AS URL , + ''https://www.brentozar.com/go/max'' AS URL , ''The server has '' + CAST(( CAST(m.total_physical_memory_kb AS BIGINT) / 1024 ) AS VARCHAR(20)) + '' megabytes of physical memory, but only '' + CAST(( CAST(m.available_physical_memory_kb AS BIGINT) / 1024 ) AS VARCHAR(20)) + '' megabytes are available. As the server runs out of memory, there is danger of swapping to disk, which will kill performance.'' AS Details FROM sys.dm_os_sys_memory m @@ -5813,7 +5822,7 @@ AS 1 AS Priority , ''Performance'' AS FindingsGroup , ''Memory Dangerously Low in NUMA Nodes'' AS Finding , - ''https://BrentOzar.com/go/max'' AS URL , + ''https://www.brentozar.com/go/max'' AS URL , ''At least one NUMA node is reporting THREAD_RESOURCES_LOW in sys.dm_os_nodes and can no longer create threads.'' AS Details FROM sys.dm_os_nodes m WHERE node_state_desc LIKE ''%THREAD_RESOURCES_LOW%'' OPTION (RECOMPILE);'; @@ -5845,7 +5854,7 @@ AS 200 AS Priority , 'Informational' AS FindingsGroup , 'Cluster Node' AS Finding , - 'https://BrentOzar.com/go/node' AS URL , + 'https://www.brentozar.com/go/node' AS URL , 'This is a node in a cluster.' AS Details FROM sys.dm_os_cluster_nodes; END; @@ -5874,7 +5883,7 @@ AS 230 AS Priority , 'Security' AS FindingsGroup , 'Database Owner <> ' + @UsualDBOwner AS Finding , - 'https://BrentOzar.com/go/owndb' AS URL , + 'https://www.brentozar.com/go/owndb' AS URL , ( 'Database name: ' + [name] + ' ' + 'Owner name: ' + SUSER_SNAME(owner_sid) ) AS Details FROM sys.databases @@ -5936,7 +5945,7 @@ AS 230 AS Priority , 'Security' AS FindingsGroup , 'SQL Agent Job Runs at Startup' AS Finding , - 'https://BrentOzar.com/go/startup' AS URL , + 'https://www.brentozar.com/go/startup' AS URL , ( 'Job [' + j.name + '] runs automatically when SQL Server Agent starts up. Make sure you know exactly what this job is doing, because it could pose a security risk.' ) AS Details FROM msdb.dbo.sysschedules sched @@ -5965,7 +5974,7 @@ AS 100 AS Priority , 'Performance' AS FindingsGroup , 'Unusual SQL Server Edition' AS Finding , - 'https://BrentOzar.com/go/workgroup' AS URL , + 'https://www.brentozar.com/go/workgroup' AS URL , ( 'This server is using ' + CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) + ', which is capped at low amounts of CPU and memory.' ) AS Details @@ -5996,7 +6005,7 @@ AS 10 AS Priority , 'Performance' AS FindingsGroup , '32-bit SQL Server Installed' AS Finding , - 'https://BrentOzar.com/go/32bit' AS URL , + 'https://www.brentozar.com/go/32bit' AS URL , ( 'This server uses the 32-bit x86 binaries for SQL Server instead of the 64-bit x64 binaries. The amount of memory available for query workspace and execution plans is heavily limited.' ) AS Details WHERE CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) NOT LIKE '%64%'; END; @@ -6022,7 +6031,7 @@ AS 200 AS Priority , 'Performance' AS FindingsGroup , 'Old Compatibility Level' AS Finding , - 'https://BrentOzar.com/go/compatlevel' AS URL , + 'https://www.brentozar.com/go/compatlevel' AS URL , ( 'Database ' + [name] + ' is compatibility level ' + CAST(compatibility_level AS VARCHAR(20)) @@ -6054,7 +6063,7 @@ AS 200 AS [Priority] , 'Monitoring' AS FindingsGroup , 'Agent Jobs Without Failure Emails' AS Finding , - 'https://BrentOzar.com/go/alerts' AS URL , + 'https://www.brentozar.com/go/alerts' AS URL , 'The job ' + [name] + ' has not been set up to notify an operator if it fails.' AS Details FROM msdb.[dbo].[sysjobs] j @@ -6093,7 +6102,7 @@ AS 170 AS Priority , 'Reliability' AS FindingGroup , 'Remote DAC Disabled' AS Finding , - 'https://BrentOzar.com/go/dac' AS URL , + 'https://www.brentozar.com/go/dac' AS URL , 'Remote access to the Dedicated Admin Connection (DAC) is not enabled. The DAC can make remote troubleshooting much easier when SQL Server is unresponsive.'; END; @@ -6119,7 +6128,7 @@ AS 50 AS Priority , 'Performance' AS FindingGroup , 'CPU Schedulers Offline' AS Finding , - 'https://BrentOzar.com/go/schedulers' AS URL , + 'https://www.brentozar.com/go/schedulers' AS URL , 'Some CPU cores are not accessible to SQL Server due to affinity masking or licensing problems.'; END; @@ -6147,7 +6156,7 @@ AS 50 AS Priority , ''Performance'' AS FindingGroup , ''Memory Nodes Offline'' AS Finding , - ''https://BrentOzar.com/go/schedulers'' AS URL , + ''https://www.brentozar.com/go/schedulers'' AS URL , ''Due to affinity masking or licensing problems, some of the memory may not be available.'' OPTION (RECOMPILE)'; IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; @@ -6180,7 +6189,7 @@ AS 20 AS Priority , 'Reliability' AS FindingGroup , 'Unusual Database State: ' + [state_desc] AS Finding , - 'https://BrentOzar.com/go/repair' AS URL , + 'https://www.brentozar.com/go/repair' AS URL , 'This database may not be online.' FROM sys.databases WHERE state > 1; @@ -6209,7 +6218,7 @@ AS 200 AS Priority , 'Reliability' AS FindingGroup , 'Extended Stored Procedures in Master' AS Finding , - 'https://BrentOzar.com/go/clr' AS URL , + 'https://www.brentozar.com/go/clr' AS URL , 'The [' + name + '] extended stored procedure is in the master database. CLR may be in use, and the master database now needs to be part of your backup/recovery planning.' FROM master.sys.extended_procedures; @@ -6234,7 +6243,7 @@ AS 50 AS Priority , 'Performance' AS FindingGroup , 'Poison Wait Detected: ' + wait_type AS Finding , - 'https://BrentOzar.com/go/poison/#' + wait_type AS URL , + 'https://www.brentozar.com/go/poison/#' + wait_type AS URL , CONVERT(VARCHAR(10), (SUM([wait_time_ms]) / 1000) / 86400) + ':' + CONVERT(VARCHAR(20), DATEADD(s, (SUM([wait_time_ms]) / 1000), 0), 108) + ' of this wait have been recorded. This wait often indicates killer performance problems.' FROM sys.[dm_os_wait_stats] WHERE wait_type IN('IO_QUEUE_LIMIT', 'IO_RETRY', 'LOG_RATE_GOVERNOR', 'POOL_LOG_RATE_GOVERNOR', 'PREEMPTIVE_DEBUG', 'RESMGR_THROTTLED', 'RESOURCE_SEMAPHORE', 'RESOURCE_SEMAPHORE_QUERY_COMPILE','SE_REPL_CATCHUP_THROTTLE','SE_REPL_COMMIT_ACK','SE_REPL_COMMIT_TURN','SE_REPL_ROLLBACK_ACK','SE_REPL_SLOW_SECONDARY_THROTTLE','THREADPOOL') @@ -6262,7 +6271,7 @@ AS 50 AS Priority , 'Performance' AS FindingGroup , 'Poison Wait Detected: Serializable Locking' AS Finding , - 'https://BrentOzar.com/go/serializable' AS URL , + 'https://www.brentozar.com/go/serializable' AS URL , CONVERT(VARCHAR(10), (SUM([wait_time_ms]) / 1000) / 86400) + ':' + CONVERT(VARCHAR(20), DATEADD(s, (SUM([wait_time_ms]) / 1000), 0), 108) + ' of LCK_M_R% waits have been recorded. This wait often indicates killer performance problems.' FROM sys.[dm_os_wait_stats] WHERE wait_type IN ('LCK_M_RS_S', 'LCK_M_RS_U', 'LCK_M_RIn_NL','LCK_M_RIn_S', 'LCK_M_RIn_U','LCK_M_RIn_X', 'LCK_M_RX_S', 'LCK_M_RX_U','LCK_M_RX_X') @@ -6292,7 +6301,7 @@ AS 'Reliability' AS FindingGroup , 'Possibly Broken Log Shipping' AS Finding , d.[name] , - 'https://BrentOzar.com/go/shipping' AS URL , + 'https://www.brentozar.com/go/shipping' AS URL , d.[name] + ' is in a restoring state, but has not had a backup applied in the last two days. This is a possible indication of a broken transaction log shipping setup.' FROM [master].sys.databases d INNER JOIN [master].sys.database_mirroring dm ON d.database_id = dm.database_id @@ -6327,7 +6336,7 @@ AS ''Performance'' AS FindingsGroup, ''Change Tracking Enabled'' AS Finding, d.[name], - ''https://BrentOzar.com/go/tracking'' AS URL, + ''https://www.brentozar.com/go/tracking'' AS URL, ( d.[name] + '' has change tracking enabled. This is not a default setting, and it has some performance overhead. It keeps track of changes to rows in tables that have change tracking turned on.'' ) AS Details FROM sys.change_tracking_databases AS ctd INNER JOIN sys.databases AS d ON ctd.database_id = d.database_id OPTION (RECOMPILE)'; IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; @@ -6356,7 +6365,7 @@ AS 200 AS Priority , ''Informational'' AS FindingGroup , ''Backup Compression Default Off'' AS Finding , - ''https://BrentOzar.com/go/backup'' AS URL , + ''https://www.brentozar.com/go/backup'' AS URL , ''Uncompressed full backups have happened recently, and backup compression is not turned on at the server level. Backup compression is included with SQL Server 2008R2 & newer, even in Standard Edition. We recommend turning backup compression on by default so that ad-hoc backups will get compressed.'' FROM sys.configurations WHERE configuration_id = 1579 AND CAST(value_in_use AS INT) = 0 @@ -6388,7 +6397,7 @@ AS 100 AS Priority, ''Performance'' AS FindingsGroup, ''Memory Pressure Affecting Queries'' AS Finding, - ''https://BrentOzar.com/go/grants'' AS URL, + ''https://www.brentozar.com/go/grants'' AS URL, CAST(SUM(forced_grant_count) AS NVARCHAR(100)) + '' forced grants reported in the DMV sys.dm_exec_query_resource_semaphores, indicating memory pressure has affected query runtimes.'' FROM sys.dm_exec_query_resource_semaphores WHERE [forced_grant_count] IS NOT NULL OPTION (RECOMPILE);'; @@ -6416,7 +6425,7 @@ AS 150, 'Performance', 'Deadlocks Happening Daily', - 'https://BrentOzar.com/go/deadlocks', + 'https://www.brentozar.com/go/deadlocks', CAST(CAST(p.cntr_value / @DaysUptime AS BIGINT) AS NVARCHAR(100)) + ' average deadlocks per day. To find them, run sp_BlitzLock.' AS Details FROM sys.dm_os_performance_counters p INNER JOIN sys.databases d ON d.name = 'tempdb' @@ -6479,7 +6488,7 @@ AS Finding, URL, Details) - SELECT TOP 1 125, 10, 'Performance', 'Plan Cache Erased Recently', 'https://BrentOzar.com/askbrent/plan-cache-erased-recently/', + SELECT TOP 1 125, 10, 'Performance', 'Plan Cache Erased Recently', 'https://www.brentozar.com/askbrent/plan-cache-erased-recently/', 'The oldest query in the plan cache was created at ' + CAST(creation_time AS NVARCHAR(50)) + CASE WHEN @user_perm_gb_out IS NULL THEN '. Someone ran DBCC FREEPROCCACHE, restarted SQL Server, or it is under horrific memory pressure.' @@ -6504,7 +6513,7 @@ AS Finding, URL, Details) - VALUES(126, 5, 'Reliability', 'Priority Boost Enabled', 'https://BrentOzar.com/go/priorityboost/', + VALUES(126, 5, 'Reliability', 'Priority Boost Enabled', 'https://www.brentozar.com/go/priorityboost/', 'Priority Boost sounds awesome, but it can actually cause your SQL Server to crash.'); END; @@ -6527,7 +6536,7 @@ AS IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 128) WITH NOWAIT; INSERT INTO #BlitzResults(CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES(128, 20, 'Reliability', 'Unsupported Build of SQL Server', 'https://BrentOzar.com/go/unsupported', + VALUES(128, 20, 'Reliability', 'Unsupported Build of SQL Server', 'https://www.brentozar.com/go/unsupported', 'Version ' + CAST(@ProductVersionMajor AS VARCHAR(100)) + CASE WHEN @ProductVersionMajor >= 11 THEN '.' + CAST(@ProductVersionMinor AS VARCHAR(100)) + ' is no longer supported by Microsoft. You need to apply a service pack.' @@ -6652,7 +6661,7 @@ AS 10 AS Priority, ''Performance'' AS FindingsGroup, ''High Memory Use for In-Memory OLTP (Hekaton)'' AS Finding, - ''https://BrentOzar.com/go/hekaton'' AS URL, + ''https://www.brentozar.com/go/hekaton'' AS URL, CAST(CAST((SUM(mem.pages_kb / 1024.0) / CAST(value_in_use AS INT) * 100) AS INT) AS NVARCHAR(100)) + ''% of your '' + CAST(CAST((CAST(value_in_use AS DECIMAL(38,1)) / 1024) AS MONEY) AS NVARCHAR(100)) + ''GB of your max server memory is being used for in-memory OLTP tables (Hekaton). Microsoft recommends having 2X your Hekaton table space available in memory just for Hekaton, with a max of 250GB of in-memory data regardless of your server memory capacity.'' AS Details FROM sys.configurations c INNER JOIN sys.dm_os_memory_clerks mem ON mem.type = ''MEMORYCLERK_XTP'' WHERE c.name = ''max server memory (MB)'' @@ -6682,7 +6691,7 @@ AS 200 AS Priority, ''Performance'' AS FindingsGroup, ''In-Memory OLTP (Hekaton) In Use'' AS Finding, - ''https://BrentOzar.com/go/hekaton'' AS URL, + ''https://www.brentozar.com/go/hekaton'' AS URL, CAST(CAST((SUM(mem.pages_kb / 1024.0) / CAST(value_in_use AS INT) * 100) AS INT) AS NVARCHAR(100)) + ''% of your '' + CAST(CAST((CAST(value_in_use AS DECIMAL(38,1)) / 1024) AS MONEY) AS NVARCHAR(100)) + ''GB of your max server memory is being used for in-memory OLTP tables (Hekaton).'' AS Details FROM sys.configurations c INNER JOIN sys.dm_os_memory_clerks mem ON mem.type = ''MEMORYCLERK_XTP'' WHERE c.name = ''max server memory (MB)'' @@ -6711,7 +6720,7 @@ AS 100 AS Priority, ''In-Memory OLTP (Hekaton)'' AS FindingsGroup, ''Transaction Errors'' AS Finding, - ''https://BrentOzar.com/go/hekaton'' AS URL, + ''https://www.brentozar.com/go/hekaton'' AS URL, ''Since restart: '' + CAST(validation_failures AS NVARCHAR(100)) + '' validation failures, '' + CAST(dependencies_failed AS NVARCHAR(100)) + '' dependency failures, '' + CAST(write_conflicts AS NVARCHAR(100)) + '' write conflicts, '' + CAST(unique_constraint_violations AS NVARCHAR(100)) + '' unique constraint violations.'' AS Details FROM sys.dm_xtp_transaction_stats WHERE validation_failures <> 0 @@ -6747,7 +6756,7 @@ AS 170 AS Priority , 'Reliability' AS FindingsGroup , 'Database Files on Network File Shares' AS Finding , - 'https://BrentOzar.com/go/nas' AS URL , + 'https://www.brentozar.com/go/nas' AS URL , ( 'Files for this database are on: ' + LEFT(mf.physical_name, 30)) AS Details FROM sys.databases d INNER JOIN sys.master_files mf ON d.database_id = mf.database_id @@ -6780,7 +6789,7 @@ AS 170 AS Priority , 'Reliability' AS FindingsGroup , 'Database Files Stored in Azure' AS Finding , - 'https://BrentOzar.com/go/azurefiles' AS URL , + 'https://www.brentozar.com/go/azurefiles' AS URL , ( 'Files for this database are on: ' + LEFT(mf.physical_name, 30)) AS Details FROM sys.databases d INNER JOIN sys.master_files mf ON d.database_id = mf.database_id @@ -6873,7 +6882,7 @@ AS 50 AS Priority , 'Reliability' AS FindingsGroup , 'There Is An Error With The Default Trace' AS Finding , - 'https://BrentOzar.com/go/defaulttrace' AS URL , + 'https://www.brentozar.com/go/defaulttrace' AS URL , 'Somebody has been messing with your trace files. Check the files are present at ' + @base_tracefilename AS Details END @@ -6900,7 +6909,7 @@ AS 170 AS Priority , 'Reliability' AS FindingsGroup , 'Errors Logged Recently in the Default Trace' AS Finding , - 'https://BrentOzar.com/go/defaulttrace' AS URL , + 'https://www.brentozar.com/go/defaulttrace' AS URL , CAST(t.TextData AS NVARCHAR(4000)) AS Details FROM #fnTraceGettable t WHERE t.EventClass = 22 @@ -6933,7 +6942,7 @@ AS 50 AS Priority , 'Performance' AS FindingsGroup , 'File Growths Slow' AS Finding , - 'https://BrentOzar.com/go/filegrowth' AS URL , + 'https://www.brentozar.com/go/filegrowth' AS URL , CAST(COUNT(*) AS NVARCHAR(100)) + ' growths took more than 15 seconds each. Consider setting file autogrowth to a smaller increment.' AS Details FROM #fnTraceGettable t WHERE t.EventClass IN (92, 93) @@ -6958,7 +6967,7 @@ AS 100 AS Priority, ''Performance'' AS FindingsGroup, ''Many Plans for One Query'' AS Finding, - ''https://BrentOzar.com/go/parameterization'' AS URL, + ''https://www.brentozar.com/go/parameterization'' AS URL, CAST(COUNT(DISTINCT plan_handle) AS NVARCHAR(50)) + '' plans are present for a single query in the plan cache - meaning we probably have parameterization issues.'' AS Details FROM sys.dm_exec_query_stats qs CROSS APPLY sys.dm_exec_plan_attributes(qs.plan_handle) pa @@ -6992,7 +7001,7 @@ AS 100 AS Priority, ''Performance'' AS FindingsGroup, ''High Number of Cached Plans'' AS Finding, - ''https://BrentOzar.com/go/planlimits'' AS URL, + ''https://www.brentozar.com/go/planlimits'' AS URL, ''Your server configuration is limited to '' + CAST(ht.buckets_count * 4 AS VARCHAR(20)) + '' '' + ht.name + '', and you are currently caching '' + CAST(cc.entries_count AS VARCHAR(20)) + ''.'' AS Details FROM sys.dm_os_memory_cache_hash_tables ht INNER JOIN sys.dm_os_memory_cache_counters cc ON ht.name = cc.name AND ht.type = cc.type @@ -7020,7 +7029,7 @@ AS Finding, URL, Details) - SELECT 165, 50, 'Performance', 'Too Much Free Memory', 'https://BrentOzar.com/go/freememory', + SELECT 165, 50, 'Performance', 'Too Much Free Memory', 'https://www.brentozar.com/go/freememory', CAST((CAST(cFree.cntr_value AS BIGINT) / 1024 / 1024 ) AS NVARCHAR(100)) + N'GB of free memory inside SQL Server''s buffer pool, which is ' + CAST((CAST(cTotal.cntr_value AS BIGINT) / 1024 / 1024) AS NVARCHAR(100)) + N'GB. You would think lots of free memory would be good, but check out the URL for more information.' AS Details FROM sys.dm_os_performance_counters cFree INNER JOIN sys.dm_os_performance_counters cTotal ON cTotal.object_name LIKE N'%Memory Manager%' @@ -7066,71 +7075,71 @@ AS IF @Debug IN (1, 2) RAISERROR('Generating database defaults.', 0, 1) WITH NOWAIT; INSERT INTO #DatabaseDefaults - SELECT 'is_supplemental_logging_enabled', 0, 131, 210, 'Supplemental Logging Enabled', 'https://BrentOzar.com/go/dbdefaults', NULL + SELECT 'is_supplemental_logging_enabled', 0, 131, 210, 'Supplemental Logging Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL FROM sys.all_columns WHERE name = 'is_supplemental_logging_enabled' AND object_id = OBJECT_ID('sys.databases'); INSERT INTO #DatabaseDefaults - SELECT 'snapshot_isolation_state', 0, 132, 210, 'Snapshot Isolation Enabled', 'https://BrentOzar.com/go/dbdefaults', NULL + SELECT 'snapshot_isolation_state', 0, 132, 210, 'Snapshot Isolation Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL FROM sys.all_columns WHERE name = 'snapshot_isolation_state' AND object_id = OBJECT_ID('sys.databases'); INSERT INTO #DatabaseDefaults SELECT 'is_read_committed_snapshot_on', CASE WHEN SERVERPROPERTY('EngineEdition') = 5 THEN 1 ELSE 0 END, /* RCSI is always enabled in Azure SQL DB per https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/1919 */ - 133, 210, CASE WHEN SERVERPROPERTY('EngineEdition') = 5 THEN 'Read Committed Snapshot Isolation Disabled' ELSE 'Read Committed Snapshot Isolation Enabled' END, 'https://BrentOzar.com/go/dbdefaults', NULL + 133, 210, CASE WHEN SERVERPROPERTY('EngineEdition') = 5 THEN 'Read Committed Snapshot Isolation Disabled' ELSE 'Read Committed Snapshot Isolation Enabled' END, 'https://www.brentozar.com/go/dbdefaults', NULL FROM sys.all_columns WHERE name = 'is_read_committed_snapshot_on' AND object_id = OBJECT_ID('sys.databases'); INSERT INTO #DatabaseDefaults - SELECT 'is_auto_create_stats_incremental_on', 0, 134, 210, 'Auto Create Stats Incremental Enabled', 'https://BrentOzar.com/go/dbdefaults', NULL + SELECT 'is_auto_create_stats_incremental_on', 0, 134, 210, 'Auto Create Stats Incremental Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL FROM sys.all_columns WHERE name = 'is_auto_create_stats_incremental_on' AND object_id = OBJECT_ID('sys.databases'); INSERT INTO #DatabaseDefaults - SELECT 'is_ansi_null_default_on', 0, 135, 210, 'ANSI NULL Default Enabled', 'https://BrentOzar.com/go/dbdefaults', NULL + SELECT 'is_ansi_null_default_on', 0, 135, 210, 'ANSI NULL Default Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL FROM sys.all_columns WHERE name = 'is_ansi_null_default_on' AND object_id = OBJECT_ID('sys.databases'); INSERT INTO #DatabaseDefaults - SELECT 'is_recursive_triggers_on', 0, 136, 210, 'Recursive Triggers Enabled', 'https://BrentOzar.com/go/dbdefaults', NULL + SELECT 'is_recursive_triggers_on', 0, 136, 210, 'Recursive Triggers Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL FROM sys.all_columns WHERE name = 'is_recursive_triggers_on' AND object_id = OBJECT_ID('sys.databases'); INSERT INTO #DatabaseDefaults - SELECT 'is_trustworthy_on', 0, 137, 210, 'Trustworthy Enabled', 'https://BrentOzar.com/go/dbdefaults', NULL + SELECT 'is_trustworthy_on', 0, 137, 210, 'Trustworthy Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL FROM sys.all_columns WHERE name = 'is_trustworthy_on' AND object_id = OBJECT_ID('sys.databases'); INSERT INTO #DatabaseDefaults - SELECT 'is_broker_enabled', 0, 230, 210, 'Broker Enabled', 'https://BrentOzar.com/go/dbdefaults', NULL + SELECT 'is_broker_enabled', 0, 230, 210, 'Broker Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL FROM sys.all_columns WHERE name = 'is_broker_enabled' AND object_id = OBJECT_ID('sys.databases'); INSERT INTO #DatabaseDefaults - SELECT 'is_honor_broker_priority_on', 0, 231, 210, 'Honor Broker Priority Enabled', 'https://BrentOzar.com/go/dbdefaults', NULL + SELECT 'is_honor_broker_priority_on', 0, 231, 210, 'Honor Broker Priority Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL FROM sys.all_columns WHERE name = 'is_honor_broker_priority_on' AND object_id = OBJECT_ID('sys.databases'); INSERT INTO #DatabaseDefaults - SELECT 'is_parameterization_forced', 0, 138, 210, 'Forced Parameterization Enabled', 'https://BrentOzar.com/go/dbdefaults', NULL + SELECT 'is_parameterization_forced', 0, 138, 210, 'Forced Parameterization Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL FROM sys.all_columns WHERE name = 'is_parameterization_forced' AND object_id = OBJECT_ID('sys.databases'); /* Not alerting for this since we actually want it and we have a separate check for it: INSERT INTO #DatabaseDefaults - SELECT 'is_query_store_on', 0, 139, 210, 'Query Store Enabled', 'https://BrentOzar.com/go/dbdefaults', NULL + SELECT 'is_query_store_on', 0, 139, 210, 'Query Store Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL FROM sys.all_columns WHERE name = 'is_query_store_on' AND object_id = OBJECT_ID('sys.databases'); */ INSERT INTO #DatabaseDefaults - SELECT 'is_cdc_enabled', 0, 140, 210, 'Change Data Capture Enabled', 'https://BrentOzar.com/go/dbdefaults', NULL + SELECT 'is_cdc_enabled', 0, 140, 210, 'Change Data Capture Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL FROM sys.all_columns WHERE name = 'is_cdc_enabled' AND object_id = OBJECT_ID('sys.databases'); INSERT INTO #DatabaseDefaults - SELECT 'containment', 0, 141, 210, 'Containment Enabled', 'https://BrentOzar.com/go/dbdefaults', NULL + SELECT 'containment', 0, 141, 210, 'Containment Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL FROM sys.all_columns WHERE name = 'containment' AND object_id = OBJECT_ID('sys.databases'); INSERT INTO #DatabaseDefaults - SELECT 'target_recovery_time_in_seconds', 0, 142, 210, 'Target Recovery Time Changed', 'https://BrentOzar.com/go/dbdefaults', NULL + SELECT 'target_recovery_time_in_seconds', 0, 142, 210, 'Target Recovery Time Changed', 'https://www.brentozar.com/go/dbdefaults', NULL FROM sys.all_columns WHERE name = 'target_recovery_time_in_seconds' AND object_id = OBJECT_ID('sys.databases'); INSERT INTO #DatabaseDefaults - SELECT 'delayed_durability', 0, 143, 210, 'Delayed Durability Enabled', 'https://BrentOzar.com/go/dbdefaults', NULL + SELECT 'delayed_durability', 0, 143, 210, 'Delayed Durability Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL FROM sys.all_columns WHERE name = 'delayed_durability' AND object_id = OBJECT_ID('sys.databases'); INSERT INTO #DatabaseDefaults - SELECT 'is_memory_optimized_elevate_to_snapshot_on', 0, 144, 210, 'Memory Optimized Enabled', 'https://BrentOzar.com/go/dbdefaults', NULL + SELECT 'is_memory_optimized_elevate_to_snapshot_on', 0, 144, 210, 'Memory Optimized Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL FROM sys.all_columns WHERE name = 'is_memory_optimized_elevate_to_snapshot_on' AND object_id = OBJECT_ID('sys.databases') AND SERVERPROPERTY('EngineEdition') <> 8; /* Hekaton is always enabled in Managed Instances per https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/1919 */ @@ -7272,7 +7281,7 @@ IF @ProductVersionMajor >= 10 250 AS [Priority] , 'Informational' AS [FindingsGroup] , 'SQL Server is running under an NT Service account' AS [Finding] , - 'https://BrentOzar.com/go/setup' AS [URL] , + 'https://www.brentozar.com/go/setup' AS [URL] , ( 'I''m running as ' + [service_account] + '. I wish I had an Active Directory service account instead.' ) AS [Details] FROM @@ -7312,7 +7321,7 @@ IF @ProductVersionMajor >= 10 250 AS [Priority] , 'Informational' AS [FindingsGroup] , 'SQL Server Agent is running under an NT Service account' AS [Finding] , - 'https://BrentOzar.com/go/setup' AS [URL] , + 'https://www.brentozar.com/go/setup' AS [URL] , ( 'I''m running as ' + [service_account] + '. I wish I had an Active Directory service account instead.' ) AS [Details] FROM @@ -7858,7 +7867,7 @@ IF @ProductVersionMajor >= 10 20 AS [Priority] , 'Reliability' AS [FindingsGroup] , 'Memory Dumps Have Occurred' AS [Finding] , - 'https://BrentOzar.com/go/dump' AS [URL] , + 'https://www.brentozar.com/go/dump' AS [URL] , ( 'That ain''t good. I''ve had ' + CAST(COUNT(*) AS VARCHAR(100)) + ' memory dumps between ' + CAST(CAST(MIN([creation_time]) AS DATETIME) AS VARCHAR(100)) + @@ -7895,7 +7904,7 @@ IF @ProductVersionMajor >= 10 200 AS [Priority] , 'Licensing' AS [FindingsGroup] , 'Non-Production License' AS [Finding] , - 'https://BrentOzar.com/go/licensing' AS [URL] , + 'https://www.brentozar.com/go/licensing' AS [URL] , ( 'We''re not the licensing police, but if this is supposed to be a production server, and you''re running ' + CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) + ' the good folks at Microsoft might get upset with you. Better start counting those cores.' @@ -7927,7 +7936,7 @@ IF @ProductVersionMajor >= 10 200 AS [Priority] , 'Performance' AS [FindingsGroup] , 'Buffer Pool Extensions Enabled' AS [Finding] , - 'https://BrentOzar.com/go/bpe' AS [URL] , + 'https://www.brentozar.com/go/bpe' AS [URL] , ( 'You have Buffer Pool Extensions enabled, and one lives here: ' + [path] + '. It''s currently ' + @@ -7967,7 +7976,7 @@ IF @ProductVersionMajor >= 10 170 AS Priority , 'File Configuration' AS FindingsGroup , 'TempDB Has >16 Data Files' AS Finding , - 'https://BrentOzar.com/go/tempdb' AS URL , + 'https://www.brentozar.com/go/tempdb' AS URL , 'Woah, Nelly! TempDB has ' + CAST(COUNT_BIG(*) AS VARCHAR(30)) + '. Did you forget to terminate a loop somewhere?' AS Details FROM sys.[master_files] AS [mf] WHERE [mf].[database_id] = 2 AND [mf].[type] = 0 @@ -8002,7 +8011,7 @@ IF @ProductVersionMajor >= 10 200 AS Priority , 'Monitoring' AS FindingsGroup , 'Extended Events Hyperextension' AS Finding , - 'https://BrentOzar.com/go/xe' AS URL , + 'https://www.brentozar.com/go/xe' AS URL , 'Hey big spender, you have ' + CAST(COUNT_BIG(*) AS VARCHAR(30)) + ' Extended Events sessions running. You sure you meant to do that?' AS Details FROM sys.dm_xe_sessions WHERE [name] NOT IN @@ -8124,7 +8133,7 @@ IF @ProductVersionMajor >= 10 END AS Priority, 'Performance' AS [FindingsGroup] , 'Shrink Database Step In Maintenance Plan' AS [Finding] , - 'https://BrentOzar.com/go/autoshrink' AS [URL] , + 'https://www.brentozar.com/go/autoshrink' AS [URL] , 'The maintenance plan ' + [mps].[name] + ' has a step to shrink databases in it. Shrinking databases is as outdated as maintenance plans.' + CASE WHEN COALESCE(ssc.name,'0') != '0' THEN + ' (Schedule: [' + ssc.name + '])' ELSE + '' END AS [Details] FROM [maintenance_plan_steps] [mps] @@ -8210,7 +8219,7 @@ IF @ProductVersionMajor >= 10 20 AS Priority , ''Reliability'' AS FindingsGroup , ''No Failover Cluster Nodes Available'' AS Finding , - ''https://BrentOzar.com/go/node'' AS URL , + ''https://www.brentozar.com/go/node'' AS URL , ''There are no failover cluster nodes available if the active node fails'' AS Details FROM ( SELECT SUM(CASE WHEN [status] = 0 AND [is_current_owner] = 0 THEN 1 ELSE 0 END) AS [available_nodes] @@ -8246,7 +8255,7 @@ IF @ProductVersionMajor >= 10 50 AS [Priority] , 'Reliability' AS [FindingsGroup] , 'TempDB File Error' AS [Finding] , - 'https://BrentOzar.com/go/tempdboops' AS [URL] , + 'https://www.brentozar.com/go/tempdboops' AS [URL] , 'Mismatch between the number of TempDB files in sys.master_files versus tempdb.sys.database_files' AS [Details]; END; @@ -8282,7 +8291,7 @@ IF @ProductVersionMajor >= 10 10 AS Priority, 'Performance' AS FindingsGroup, 'CPU w/Odd Number of Cores' AS Finding, - 'https://BrentOzar.com/go/oddity' AS URL, + 'https://www.brentozar.com/go/oddity' AS URL, 'Node ' + CONVERT(VARCHAR(10), parent_node_id) + ' has ' + CONVERT(VARCHAR(10), COUNT(cpu_id)) + CASE WHEN COUNT(cpu_id) = 1 THEN ' core assigned to it. This is a really bad NUMA configuration.' ELSE ' cores assigned to it. This is a really bad NUMA configuration.' @@ -8566,7 +8575,7 @@ IF @ProductVersionMajor >= 10 'DBCC SHRINK% Ran Recently' AS Finding , 'https://www.BrentOzar.com/go/dbcc' AS URL , 'The user ' + COALESCE(d.nt_user_name, d.login_name) + ' has run file shrinks ' + CAST(COUNT(*) AS NVARCHAR(100)) + ' times between ' + CONVERT(NVARCHAR(30), MIN(d.min_start_time)) + ' and ' + CONVERT(NVARCHAR(30), MAX(d.max_start_time)) + - '. So, uh, are they trying cause bad performance on purpose?' + '. So, uh, are they trying to cause bad performance on purpose?' AS Details FROM #dbcc_events_from_trace d WHERE d.dbcc_event_trunc_upper LIKE N'DBCC SHRINK%' @@ -8853,7 +8862,7 @@ IF @ProductVersionMajor >= 10 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 99) WITH NOWAIT; - EXEC dbo.sp_MSforeachdb 'USE [?]; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; IF EXISTS (SELECT * FROM sys.tables WITH (NOLOCK) WHERE name = ''sysmergepublications'' ) IF EXISTS ( SELECT * FROM sysmergepublications WITH (NOLOCK) WHERE retention = 0) INSERT INTO #BlitzResults (CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details) SELECT DISTINCT 99, DB_NAME(), 110, ''Performance'', ''Infinite merge replication metadata retention period'', ''https://BrentOzar.com/go/merge'', (''The ['' + DB_NAME() + ''] database has merge replication metadata retention period set to infinite - this can be the case of significant performance issues.'')'; + EXEC dbo.sp_MSforeachdb 'USE [?]; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; IF EXISTS (SELECT * FROM sys.tables WITH (NOLOCK) WHERE name = ''sysmergepublications'' ) IF EXISTS ( SELECT * FROM sysmergepublications WITH (NOLOCK) WHERE retention = 0) INSERT INTO #BlitzResults (CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details) SELECT DISTINCT 99, DB_NAME(), 110, ''Performance'', ''Infinite merge replication metadata retention period'', ''https://www.brentozar.com/go/merge'', (''The ['' + DB_NAME() + ''] database has merge replication metadata retention period set to infinite - this can be the case of significant performance issues.'')'; END; /* Note that by using sp_MSforeachdb, we're running the query in all @@ -8887,7 +8896,7 @@ IF @ProductVersionMajor >= 10 200, ''Performance'', ''Query Store Disabled'', - ''https://BrentOzar.com/go/querystore'', + ''https://www.brentozar.com/go/querystore'', (''The new SQL Server 2016 Query Store feature has not been enabled on this database.'') FROM [?].sys.database_query_store_options WHERE desired_state = 0 AND N''?'' NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''DWConfiguration'', ''DWDiagnostics'', ''DWQueue'', ''ReportServer'', ''ReportServerTempDB'') OPTION (RECOMPILE)'; @@ -8918,7 +8927,7 @@ IF @ProductVersionMajor >= 10 20, ''Reliability'', ''Query Store Cleanup Disabled'', - ''https://BrentOzar.com/go/cleanup'', + ''https://www.brentozar.com/go/cleanup'', (''SQL 2016 RTM has a bug involving dumps that happen every time Query Store cleanup jobs run. This is fixed in CU1 and later: https://sqlserverupdates.com/sql-server-2016-updates/'') FROM sys.databases AS d WHERE d.is_query_store_on = 1 OPTION (RECOMPILE);'; @@ -8982,7 +8991,7 @@ IF @ProductVersionMajor >= 10 170, ''File Configuration'', ''Multiple Log Files on One Drive'', - ''https://BrentOzar.com/go/manylogs'', + ''https://www.brentozar.com/go/manylogs'', (''The ['' + DB_NAME() + ''] database has multiple log files on the '' + LEFT(physical_name, 1) + '' drive. This is not a performance booster because log file access is sequential, not parallel.'') FROM [?].sys.database_files WHERE type_desc = ''LOG'' AND N''?'' <> ''[tempdb]'' @@ -9012,7 +9021,7 @@ IF @ProductVersionMajor >= 10 170, ''File Configuration'', ''Uneven File Growth Settings in One Filegroup'', - ''https://BrentOzar.com/go/grow'', + ''https://www.brentozar.com/go/grow'', (''The ['' + DB_NAME() + ''] database has multiple data files in one filegroup, but they are not all set up to grow in identical amounts. This can lead to uneven file activity inside the filegroup.'') FROM [?].sys.database_files WHERE type_desc = ''ROWS'' @@ -9041,7 +9050,7 @@ IF @ProductVersionMajor >= 10 170 AS Priority, ''File Configuration'' AS FindingsGroup, ''File growth set to percent'', - ''https://BrentOzar.com/go/percentgrowth'' AS URL, + ''https://www.brentozar.com/go/percentgrowth'' AS URL, ''The ['' + DB_NAME() + ''] database file '' + f.physical_name + '' has grown to '' + CONVERT(NVARCHAR(10), CONVERT(NUMERIC(38, 2), (f.size / 128.) / 1024.)) + '' GB, and is using percent filegrowth settings. This can lead to slow performance during growths if Instant File Initialization is not enabled.'' FROM [?].sys.database_files f WHERE is_percent_growth = 1 and size > 128000 OPTION (RECOMPILE);'; @@ -9069,7 +9078,7 @@ IF @ProductVersionMajor >= 10 170 AS Priority, ''File Configuration'' AS FindingsGroup, ''File growth set to 1MB'', - ''https://BrentOzar.com/go/percentgrowth'' AS URL, + ''https://www.brentozar.com/go/percentgrowth'' AS URL, ''The ['' + DB_NAME() + ''] database file '' + f.physical_name + '' is using 1MB filegrowth settings, but it has grown to '' + CAST((f.size * 8 / 1000000) AS NVARCHAR(10)) + '' GB. Time to up the growth amount.'' FROM [?].sys.database_files f WHERE is_percent_growth = 0 and growth=128 and size > 128000 OPTION (RECOMPILE);'; @@ -9100,7 +9109,7 @@ IF @ProductVersionMajor >= 10 200, ''Licensing'', ''Enterprise Edition Features In Use'', - ''https://BrentOzar.com/go/ee'', + ''https://www.brentozar.com/go/ee'', (''The ['' + DB_NAME() + ''] database is using '' + feature_name + ''. If this database is restored onto a Standard Edition server, the restore will fail on versions prior to 2016 SP1.'') FROM [?].sys.dm_db_persisted_sku_features OPTION (RECOMPILE);'; END; @@ -9129,7 +9138,7 @@ IF @ProductVersionMajor >= 10 200 AS Priority , 'Informational' AS FindingsGroup , 'Replication In Use' AS Finding , - 'https://BrentOzar.com/go/repl' AS URL , + 'https://www.brentozar.com/go/repl' AS URL , ( 'Database [' + [name] + '] is a replication publisher, subscriber, or distributor.' ) AS Details FROM sys.databases @@ -9157,7 +9166,7 @@ IF @ProductVersionMajor >= 10 200, ''Informational'', ''Replication In Use'', - ''https://BrentOzar.com/go/repl'', + ''https://www.brentozar.com/go/repl'', (''['' + DB_NAME() + ''] has MSreplication_objects tables in it, indicating it is a replication subscriber.'') FROM [?].sys.tables WHERE name = ''dbo.MSreplication_objects'' AND ''?'' <> ''master'' OPTION (RECOMPILE)'; @@ -9186,45 +9195,13 @@ IF @ProductVersionMajor >= 10 150, ''Performance'', ''Triggers on Tables'', - ''https://BrentOzar.com/go/trig'', + ''https://www.brentozar.com/go/trig'', (''The ['' + DB_NAME() + ''] database has '' + CAST(SUM(1) AS NVARCHAR(50)) + '' triggers.'') FROM [?].sys.triggers t INNER JOIN [?].sys.objects o ON t.parent_id = o.object_id INNER JOIN [?].sys.schemas s ON o.schema_id = s.schema_id WHERE t.is_ms_shipped = 0 AND DB_NAME() != ''ReportServer'' HAVING SUM(1) > 0 OPTION (RECOMPILE)'; END; - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 38 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 38) WITH NOWAIT; - - EXEC dbo.sp_MSforeachdb 'USE [?]; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - INSERT INTO #BlitzResults - (CheckID, - DatabaseName, - Priority, - FindingsGroup, - Finding, - URL, - Details) - SELECT DISTINCT 38, - N''?'', - 110, - ''Performance'', - ''Active Tables Without Clustered Indexes'', - ''https://BrentOzar.com/go/heaps'', - (''The ['' + DB_NAME() + ''] database has heaps - tables without a clustered index - that are being actively queried.'') - FROM [?].sys.indexes i INNER JOIN [?].sys.objects o ON i.object_id = o.object_id - INNER JOIN [?].sys.partitions p ON i.object_id = p.object_id AND i.index_id = p.index_id - INNER JOIN sys.databases sd ON sd.name = N''?'' - LEFT OUTER JOIN [?].sys.dm_db_index_usage_stats ius ON i.object_id = ius.object_id AND i.index_id = ius.index_id AND ius.database_id = sd.database_id - WHERE i.type_desc = ''HEAP'' AND COALESCE(NULLIF(ius.user_seeks,0), NULLIF(ius.user_scans,0), NULLIF(ius.user_lookups,0), NULLIF(ius.user_updates,0)) IS NOT NULL - AND sd.name <> ''tempdb'' AND sd.name <> ''DWDiagnostics'' AND o.is_ms_shipped = 0 AND o.type <> ''S'' OPTION (RECOMPILE)'; - END; - IF NOT EXISTS ( SELECT 1 FROM #SkipChecks WHERE DatabaseName IS NULL AND CheckID = 164 ) @@ -9248,43 +9225,11 @@ IF @ProductVersionMajor >= 10 100, ''Reliability'', ''Plan Guides Failing'', - ''https://BrentOzar.com/go/misguided'', + ''https://www.brentozar.com/go/misguided'', (''The ['' + DB_NAME() + ''] database has plan guides that are no longer valid, so the queries involved may be failing silently.'') FROM [?].sys.plan_guides g CROSS APPLY fn_validate_plan_guide(g.plan_guide_id) OPTION (RECOMPILE)'; END; - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 39 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 39) WITH NOWAIT; - - EXEC dbo.sp_MSforeachdb 'USE [?]; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - INSERT INTO #BlitzResults - (CheckID, - DatabaseName, - Priority, - FindingsGroup, - Finding, - URL, - Details) - SELECT DISTINCT 39, - N''?'', - 150, - ''Performance'', - ''Inactive Tables Without Clustered Indexes'', - ''https://BrentOzar.com/go/heaps'', - (''The ['' + DB_NAME() + ''] database has heaps - tables without a clustered index - that have not been queried since the last restart. These may be backup tables carelessly left behind.'') - FROM [?].sys.indexes i INNER JOIN [?].sys.objects o ON i.object_id = o.object_id - INNER JOIN [?].sys.partitions p ON i.object_id = p.object_id AND i.index_id = p.index_id - INNER JOIN sys.databases sd ON sd.name = N''?'' - LEFT OUTER JOIN [?].sys.dm_db_index_usage_stats ius ON i.object_id = ius.object_id AND i.index_id = ius.index_id AND ius.database_id = sd.database_id - WHERE i.type_desc = ''HEAP'' AND COALESCE(NULLIF(ius.user_seeks,0), NULLIF(ius.user_scans,0), NULLIF(ius.user_lookups,0), NULLIF(ius.user_updates,0)) IS NULL - AND sd.name <> ''tempdb'' AND sd.name <> ''DWDiagnostics'' AND o.is_ms_shipped = 0 AND o.type <> ''S'' OPTION (RECOMPILE)'; - END; - IF NOT EXISTS ( SELECT 1 FROM #SkipChecks WHERE DatabaseName IS NULL AND CheckID = 46 ) @@ -9307,7 +9252,7 @@ IF @ProductVersionMajor >= 10 150, ''Performance'', ''Leftover Fake Indexes From Wizards'', - ''https://BrentOzar.com/go/hypo'', + ''https://www.brentozar.com/go/hypo'', (''The index ['' + DB_NAME() + ''].['' + s.name + ''].['' + o.name + ''].['' + i.name + ''] is a leftover hypothetical index from the Index Tuning Wizard or Database Tuning Advisor. This index is not actually helping performance and should be removed.'') from [?].sys.indexes i INNER JOIN [?].sys.objects o ON i.object_id = o.object_id INNER JOIN [?].sys.schemas s ON o.schema_id = s.schema_id WHERE i.is_hypothetical = 1 OPTION (RECOMPILE);'; @@ -9335,7 +9280,7 @@ IF @ProductVersionMajor >= 10 100, ''Performance'', ''Indexes Disabled'', - ''https://BrentOzar.com/go/ixoff'', + ''https://www.brentozar.com/go/ixoff'', (''The index ['' + DB_NAME() + ''].['' + s.name + ''].['' + o.name + ''].['' + i.name + ''] is disabled. This index is not actually helping performance and should either be enabled or removed.'') from [?].sys.indexes i INNER JOIN [?].sys.objects o ON i.object_id = o.object_id INNER JOIN [?].sys.schemas s ON o.schema_id = s.schema_id WHERE i.is_disabled = 1 OPTION (RECOMPILE);'; @@ -9363,7 +9308,7 @@ IF @ProductVersionMajor >= 10 150, ''Performance'', ''Foreign Keys Not Trusted'', - ''https://BrentOzar.com/go/trust'', + ''https://www.brentozar.com/go/trust'', (''The ['' + DB_NAME() + ''] database has foreign keys that were probably disabled, data was changed, and then the key was enabled again. Simply enabling the key is not enough for the optimizer to use this key - we have to alter the table using the WITH CHECK CHECK CONSTRAINT parameter.'') from [?].sys.foreign_keys i INNER JOIN [?].sys.objects o ON i.parent_object_id = o.object_id INNER JOIN [?].sys.schemas s ON o.schema_id = s.schema_id WHERE i.is_not_trusted = 1 AND i.is_not_for_replication = 0 AND i.is_disabled = 0 AND N''?'' NOT IN (''master'', ''model'', ''msdb'', ''ReportServer'', ''ReportServerTempDB'') OPTION (RECOMPILE);'; @@ -9391,7 +9336,7 @@ IF @ProductVersionMajor >= 10 150, ''Performance'', ''Check Constraint Not Trusted'', - ''https://BrentOzar.com/go/trust'', + ''https://www.brentozar.com/go/trust'', (''The check constraint ['' + DB_NAME() + ''].['' + s.name + ''].['' + o.name + ''].['' + i.name + ''] is not trusted - meaning, it was disabled, data was changed, and then the constraint was enabled again. Simply enabling the constraint is not enough for the optimizer to use this constraint - we have to alter the table using the WITH CHECK CHECK CONSTRAINT parameter.'') from [?].sys.check_constraints i INNER JOIN [?].sys.objects o ON i.parent_object_id = o.object_id INNER JOIN [?].sys.schemas s ON o.schema_id = s.schema_id @@ -9423,7 +9368,7 @@ IF @ProductVersionMajor >= 10 110 AS Priority, ''Performance'' AS FindingsGroup, ''Plan Guides Enabled'' AS Finding, - ''https://BrentOzar.com/go/guides'' AS URL, + ''https://www.brentozar.com/go/guides'' AS URL, (''Database ['' + DB_NAME() + ''] has query plan guides so a query will always get a specific execution plan. If you are having trouble getting query performance to improve, it might be due to a frozen plan. Review the DMV sys.plan_guides to learn more about the plan guides in place on this server.'') AS Details FROM [?].sys.plan_guides WHERE is_disabled = 0 OPTION (RECOMPILE);'; END; @@ -9451,7 +9396,7 @@ IF @ProductVersionMajor >= 10 100 AS Priority, ''Performance'' AS FindingsGroup, ''Fill Factor Changed'', - ''https://BrentOzar.com/go/fillfactor'' AS URL, + ''https://www.brentozar.com/go/fillfactor'' AS URL, ''The ['' + DB_NAME() + ''] database has '' + CAST(SUM(1) AS NVARCHAR(50)) + '' objects with fill factor = '' + CAST(fill_factor AS NVARCHAR(5)) + ''%. This can cause memory and storage performance problems, but may also prevent page splits.'' FROM [?].sys.indexes WHERE fill_factor <> 0 AND fill_factor < 80 AND is_disabled = 0 AND is_hypothetical = 0 @@ -9487,7 +9432,7 @@ IF @ProductVersionMajor >= 10 FindingsGroup = 'Performance', Finding = 'Stored Procedure WITH RECOMPILE', DatabaseName = DBName, - URL = 'https://BrentOzar.com/go/recompile', + URL = 'https://www.brentozar.com/go/recompile', Details = '[' + DBName + '].[' + SPSchema + '].[' + ProcName + '] has WITH RECOMPILE in the stored procedure code, which may cause increased CPU usage due to constant recompiles of the code.', CheckID = '78' FROM #Recompile AS TR WHERE ProcName NOT LIKE 'sp_AllNightLog%' AND ProcName NOT LIKE 'sp_AskBrent%' AND ProcName NOT LIKE 'sp_Blitz%'; @@ -9501,7 +9446,7 @@ IF @ProductVersionMajor >= 10 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 86) WITH NOWAIT; - EXEC dbo.sp_MSforeachdb 'USE [?]; INSERT INTO #BlitzResults (CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details) SELECT DISTINCT 86, DB_NAME(), 230, ''Security'', ''Elevated Permissions on a Database'', ''https://BrentOzar.com/go/elevated'', (''In ['' + DB_NAME() + ''], user ['' + u.name + ''] has the role ['' + g.name + '']. This user can perform tasks beyond just reading and writing data.'') FROM (SELECT memberuid = convert(int, member_principal_id), groupuid = convert(int, role_principal_id) FROM [?].sys.database_role_members) m inner join [?].dbo.sysusers u on m.memberuid = u.uid inner join sysusers g on m.groupuid = g.uid where u.name <> ''dbo'' and g.name in (''db_owner'' , ''db_accessadmin'' , ''db_securityadmin'' , ''db_ddladmin'') OPTION (RECOMPILE);'; + EXEC dbo.sp_MSforeachdb 'USE [?]; INSERT INTO #BlitzResults (CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details) SELECT DISTINCT 86, DB_NAME(), 230, ''Security'', ''Elevated Permissions on a Database'', ''https://www.brentozar.com/go/elevated'', (''In ['' + DB_NAME() + ''], user ['' + u.name + ''] has the role ['' + g.name + '']. This user can perform tasks beyond just reading and writing data.'') FROM (SELECT memberuid = convert(int, member_principal_id), groupuid = convert(int, role_principal_id) FROM [?].sys.database_role_members) m inner join [?].dbo.sysusers u on m.memberuid = u.uid inner join sysusers g on m.groupuid = g.uid where u.name <> ''dbo'' and g.name in (''db_owner'' , ''db_accessadmin'' , ''db_securityadmin'' , ''db_ddladmin'') OPTION (RECOMPILE);'; END; /*Check for non-aligned indexes in partioned databases*/ @@ -9545,7 +9490,7 @@ IF @ProductVersionMajor >= 10 'Performance' AS FindingsGroup , 'The partitioned database ' + dbname + ' may have non-aligned indexes' AS Finding , - 'https://BrentOzar.com/go/aligned' AS URL , + 'https://www.brentozar.com/go/aligned' AS URL , 'Having non-aligned indexes on partitioned tables may cause inefficient query plans and CPU pressure' AS Details FROM #partdb WHERE dbname IS NOT NULL @@ -9578,7 +9523,7 @@ IF @ProductVersionMajor >= 10 50, ''Reliability'', ''Full Text Indexes Not Updating'', - ''https://BrentOzar.com/go/fulltext'', + ''https://www.brentozar.com/go/fulltext'', (''At least one full text index in this database has not been crawled in the last week.'') from [?].sys.fulltext_indexes i WHERE change_tracking_state_desc <> ''AUTO'' AND i.is_enabled = 1 AND i.crawl_end_date < DATEADD(dd, -7, GETDATE()) OPTION (RECOMPILE);'; END; @@ -9605,7 +9550,7 @@ IF @ProductVersionMajor >= 10 110, ''Performance'', ''Parallelism Rocket Surgery'', - ''https://BrentOzar.com/go/makeparallel'', + ''https://www.brentozar.com/go/makeparallel'', (''['' + DB_NAME() + ''] has a make_parallel function, indicating that an advanced developer may be manhandling SQL Server into forcing queries to go parallel.'') from [?].INFORMATION_SCHEMA.ROUTINES WHERE ROUTINE_NAME = ''make_parallel'' AND ROUTINE_TYPE = ''FUNCTION'' OPTION (RECOMPILE);'; END; @@ -9638,7 +9583,7 @@ IF @ProductVersionMajor >= 10 200, ''Performance'', ''User-Created Statistics In Place'', - ''https://BrentOzar.com/go/userstats'', + ''https://www.brentozar.com/go/userstats'', (''['' + DB_NAME() + ''] has '' + CAST(SUM(1) AS NVARCHAR(10)) + '' user-created statistics. This indicates that someone is being a rocket scientist with the stats, and might actually be slowing things down, especially during stats updates.'') from [?].sys.stats WHERE user_created = 1 AND is_temporary = 0 HAVING SUM(1) > 0 OPTION (RECOMPILE);'; @@ -9659,7 +9604,7 @@ IF @ProductVersionMajor >= 10 200, ''Performance'', ''User-Created Statistics In Place'', - ''https://BrentOzar.com/go/userstats'', + ''https://www.brentozar.com/go/userstats'', (''['' + DB_NAME() + ''] has '' + CAST(SUM(1) AS NVARCHAR(10)) + '' user-created statistics. This indicates that someone is being a rocket scientist with the stats, and might actually be slowing things down, especially during stats updates.'') from [?].sys.stats WHERE user_created = 1 HAVING SUM(1) > 0 OPTION (RECOMPILE);'; @@ -9697,7 +9642,7 @@ IF @ProductVersionMajor >= 10 ,170 ,''File Configuration'' ,''High VLF Count'' - ,''https://BrentOzar.com/go/vlf'' + ,''https://www.brentozar.com/go/vlf'' ,''The ['' + DB_NAME() + ''] database has '' + CAST(COUNT(*) as VARCHAR(20)) + '' virtual log files (VLFs). This may be slowing down startup, restores, and even inserts/updates/deletes.'' FROM #LogInfo2012 WHERE EXISTS (SELECT name FROM master.sys.databases @@ -9730,7 +9675,7 @@ IF @ProductVersionMajor >= 10 ,170 ,''File Configuration'' ,''High VLF Count'' - ,''https://BrentOzar.com/go/vlf'' + ,''https://www.brentozar.com/go/vlf'' ,''The ['' + DB_NAME() + ''] database has '' + CAST(COUNT(*) as VARCHAR(20)) + '' virtual log files (VLFs). This may be slowing down startup, restores, and even inserts/updates/deletes.'' FROM #LogInfo WHERE EXISTS (SELECT name FROM master.sys.databases @@ -9751,7 +9696,7 @@ IF @ProductVersionMajor >= 10 EXEC dbo.sp_MSforeachdb 'USE [?]; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; INSERT INTO #BlitzResults (CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details) - SELECT DISTINCT 80, DB_NAME(), 170, ''Reliability'', ''Max File Size Set'', ''https://BrentOzar.com/go/maxsize'', + SELECT DISTINCT 80, DB_NAME(), 170, ''Reliability'', ''Max File Size Set'', ''https://www.brentozar.com/go/maxsize'', (''The ['' + DB_NAME() + ''] database file '' + df.name + '' has a max file size set to '' + CAST(CAST(df.max_size AS BIGINT) * 8 / 1024 AS VARCHAR(100)) + ''MB. If it runs out of space, the database will stop working even though there may be drive space available.'') @@ -9835,7 +9780,7 @@ IF @ProductVersionMajor >= 10 UNION ALL SELECT 24, 'LAST_QUERY_PLAN_STATS', '0', NULL, 255; EXEC dbo.sp_MSforeachdb 'USE [?]; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; INSERT INTO #BlitzResults (CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details) - SELECT def1.CheckID, DB_NAME(), 210, ''Non-Default Database Scoped Config'', dsc.[name], ''https://BrentOzar.com/go/dbscope'', (''Set value: '' + COALESCE(CAST(dsc.value AS NVARCHAR(100)),''Empty'') + '' Default: '' + COALESCE(CAST(def1.default_value AS NVARCHAR(100)),''Empty'') + '' Set value for secondary: '' + COALESCE(CAST(dsc.value_for_secondary AS NVARCHAR(100)),''Empty'') + '' Default value for secondary: '' + COALESCE(CAST(def1.default_value_for_secondary AS NVARCHAR(100)),''Empty'')) + SELECT def1.CheckID, DB_NAME(), 210, ''Non-Default Database Scoped Config'', dsc.[name], ''https://www.brentozar.com/go/dbscope'', (''Set value: '' + COALESCE(CAST(dsc.value AS NVARCHAR(100)),''Empty'') + '' Default: '' + COALESCE(CAST(def1.default_value AS NVARCHAR(100)),''Empty'') + '' Set value for secondary: '' + COALESCE(CAST(dsc.value_for_secondary AS NVARCHAR(100)),''Empty'') + '' Default value for secondary: '' + COALESCE(CAST(def1.default_value_for_secondary AS NVARCHAR(100)),''Empty'')) FROM [?].sys.database_scoped_configurations dsc INNER JOIN #DatabaseScopedConfigurationDefaults def1 ON dsc.configuration_id = def1.configuration_id LEFT OUTER JOIN #DatabaseScopedConfigurationDefaults def ON dsc.configuration_id = def.configuration_id AND (cast(dsc.value as nvarchar(100)) = cast(def.default_value as nvarchar(100)) OR dsc.value IS NULL) AND (dsc.value_for_secondary = def.default_value_for_secondary OR dsc.value_for_secondary IS NULL) @@ -9865,7 +9810,7 @@ IF @ProductVersionMajor >= 10 ,150 AS Priority ,''Performance'' AS FindingsGroup ,''Objects created with dangerous SET Options'' AS Finding - ,''https://BrentOzar.com/go/badset'' AS URL + ,''https://www.brentozar.com/go/badset'' AS URL ,''The '' + QUOTENAME(DB_NAME()) + '' database has '' + CONVERT(VARCHAR(20),COUNT(1)) + '' objects that were created with dangerous ANSI_NULL or QUOTED_IDENTIFIER options.'' @@ -9903,7 +9848,7 @@ IF @ProductVersionMajor >= 10 ,200 AS Priority ,''Reliability'' AS FindingsGroup ,''Resumable Index Operation Paused'' AS Finding - ,''https://BrentOzar.com/go/resumable'' AS URL + ,''https://www.brentozar.com/go/resumable'' AS URL ,iro.state_desc + N'' since '' + CONVERT(NVARCHAR(50), last_pause_time, 120) + '', '' + CAST(iro.percent_complete AS NVARCHAR(20)) + ''% complete: '' + CAST(iro.sql_text AS NVARCHAR(1000)) AS Details @@ -9934,7 +9879,7 @@ IF @ProductVersionMajor >= 10 -- ,110 AS Priority -- ,''Performance'' AS FindingsGroup -- ,''Statistics Without Histograms'' AS Finding - -- ,''https://BrentOzar.com/go/brokenstats'' AS URL + -- ,''https://www.brentozar.com/go/brokenstats'' AS URL -- ,CAST(COUNT(DISTINCT o.object_id) AS VARCHAR(100)) + '' tables have statistics that have not been updated since the database was restored or upgraded,'' -- + '' and have no data in their histogram. See the More Info URL for a script to update them. '' AS Details -- FROM sys.all_objects o @@ -9989,7 +9934,7 @@ IF @ProductVersionMajor >= 10 1 AS PRIORITY , 'Reliability' AS FindingsGroup , 'Last good DBCC CHECKDB over 2 weeks old' AS Finding , - 'https://BrentOzar.com/go/checkdb' AS URL , + 'https://www.brentozar.com/go/checkdb' AS URL , 'Last successful CHECKDB: ' + CASE DB2.Value WHEN '1900-01-01 00:00:00.000' @@ -10040,7 +9985,7 @@ IF @ProductVersionMajor >= 10 100 AS Priority , 'Performance' AS FindingsGroup , 'Single-Use Plans in Procedure Cache' AS Finding , - 'https://BrentOzar.com/go/single' AS URL , + 'https://www.brentozar.com/go/single' AS URL , ( CAST(COUNT(*) AS VARCHAR(10)) + ' query plans are taking up memory in the procedure cache. This may be wasted memory if we cache plans for queries that never get called again. This may be a good use case for SQL Server 2008''s Optimize for Ad Hoc or for Forced Parameterization.' ) AS Details FROM sys.dm_exec_cached_plans AS cp @@ -10239,7 +10184,7 @@ IF @ProductVersionMajor >= 10 120 AS Priority , 'Query Plans' AS FindingsGroup , 'Implicit Conversion' AS Finding , - 'https://BrentOzar.com/go/implicit' AS URL , + 'https://www.brentozar.com/go/implicit' AS URL , ( 'One of the top resource-intensive queries is comparing two fields that are not the same datatype.' ) AS Details , qs.query_plan , qs.query_plan_filtered @@ -10271,7 +10216,7 @@ IF @ProductVersionMajor >= 10 120 AS Priority , 'Query Plans' AS FindingsGroup , 'Implicit Conversion Affecting Cardinality' AS Finding , - 'https://BrentOzar.com/go/implicit' AS URL , + 'https://www.brentozar.com/go/implicit' AS URL , ( 'One of the top resource-intensive queries has an implicit conversion that is affecting cardinality estimation.' ) AS Details , qs.query_plan , qs.query_plan_filtered @@ -10302,7 +10247,7 @@ IF @ProductVersionMajor >= 10 120 AS Priority , 'Query Plans' AS FindingsGroup , 'RID or Key Lookups' AS Finding , - 'https://BrentOzar.com/go/lookup' AS URL , + 'https://www.brentozar.com/go/lookup' AS URL , 'One of the top resource-intensive queries contains RID or Key Lookups. Try to avoid them by creating covering indexes.' AS Details , qs.query_plan , qs.query_plan_filtered @@ -10333,7 +10278,7 @@ IF @ProductVersionMajor >= 10 120 AS Priority , 'Query Plans' AS FindingsGroup , 'Missing Index' AS Finding , - 'https://BrentOzar.com/go/missingindex' AS URL , + 'https://www.brentozar.com/go/missingindex' AS URL , ( 'One of the top resource-intensive queries may be dramatically improved by adding an index.' ) AS Details , qs.query_plan , qs.query_plan_filtered @@ -10364,7 +10309,7 @@ IF @ProductVersionMajor >= 10 120 AS Priority , 'Query Plans' AS FindingsGroup , 'Cursor' AS Finding , - 'https://BrentOzar.com/go/cursor' AS URL , + 'https://www.brentozar.com/go/cursor' AS URL , ( 'One of the top resource-intensive queries is using a cursor.' ) AS Details , qs.query_plan , qs.query_plan_filtered @@ -10396,7 +10341,7 @@ IF @ProductVersionMajor >= 10 120 AS Priority , 'Query Plans' AS FindingsGroup , 'Scalar UDFs' AS Finding , - 'https://BrentOzar.com/go/functions' AS URL , + 'https://www.brentozar.com/go/functions' AS URL , ( 'One of the top resource-intensive queries is using a user-defined scalar function that may inhibit parallelism.' ) AS Details , qs.query_plan , qs.query_plan_filtered @@ -10431,7 +10376,7 @@ IF @ProductVersionMajor >= 10 230 AS [Priority] , 'Security' AS [FindingsGroup] , 'Endpoints Owned by Users' AS [Finding] , - 'https://BrentOzar.com/go/owners' AS [URL] , + 'https://www.brentozar.com/go/owners' AS [URL] , ( 'Endpoint ' + ep.[name] + ' is owned by ' + SUSER_NAME(ep.principal_id) + '. If the endpoint owner login is disabled or not available due to Active Directory problems, the high availability will stop working.' ) AS [Details] FROM sys.database_mirroring_endpoints ep @@ -10462,7 +10407,7 @@ IF @ProductVersionMajor >= 10 200 AS Priority , 'Informational' AS FindingsGroup , '@@Servername Not Set' AS Finding , - 'https://BrentOzar.com/go/servername' AS URL , + 'https://www.brentozar.com/go/servername' AS URL , '@@Servername variable is null. You can fix it by executing: "sp_addserver '''', local"' AS Details; END; @@ -10493,7 +10438,7 @@ IF @ProductVersionMajor >= 10 200 AS Priority , 'Configuration' AS FindingsGroup , '@@Servername Not Correct' AS Finding , - 'https://BrentOzar.com/go/servername' AS URL , + 'https://www.brentozar.com/go/servername' AS URL , 'The @@Servername is different than the computer name, which may trigger certificate errors.' AS Details; END; @@ -10532,7 +10477,7 @@ IF @ProductVersionMajor >= 10 200 AS Priority , 'Monitoring' AS FindingsGroup , 'No Failsafe Operator Configured' AS Finding , - 'https://BrentOzar.com/go/failsafe' AS URL , + 'https://www.brentozar.com/go/failsafe' AS URL , ( 'No failsafe operator is configured on this server. This is a good idea just in-case there are issues with the [msdb] database that prevents alerting.' ) AS Details FROM @AlertInfo WHERE FailSafeOperator IS NULL; @@ -10611,7 +10556,7 @@ IF @ProductVersionMajor >= 10 50 AS Priority , 'Performance' AS FindingGroup , 'Poison Wait Detected: CMEMTHREAD & NUMA' AS Finding , - 'https://BrentOzar.com/go/poison' AS URL , + 'https://www.brentozar.com/go/poison' AS URL , CONVERT(VARCHAR(10), (MAX([wait_time_ms]) / 1000) / 86400) + ':' + CONVERT(VARCHAR(20), DATEADD(s, (MAX([wait_time_ms]) / 1000), 0), 108) + ' of this wait have been recorded' + CASE WHEN ts.status = 1 THEN ' despite enabling trace flag 8048 already.' ELSE '. In servers with over 8 cores per NUMA node, when CMEMTHREAD waits are a bottleneck, trace flag 8048 may be needed.' @@ -10649,7 +10594,7 @@ IF @ProductVersionMajor >= 10 50 AS Priority , 'Reliability' AS FindingsGroup , 'Transaction Log Larger than Data File' AS Finding , - 'https://BrentOzar.com/go/biglog' AS URL , + 'https://www.brentozar.com/go/biglog' AS URL , 'The database [' + DB_NAME(a.database_id) + '] has a ' + CAST((CAST(a.size AS BIGINT) * 8 / 1000000) AS NVARCHAR(20)) + ' GB transaction log file, larger than the total data file sizes. This may indicate that transaction log backups are not being performed or not performed often enough.' AS Details FROM sys.master_files a @@ -10693,7 +10638,7 @@ IF @ProductVersionMajor >= 10 200 AS Priority , 'Informational' AS FindingsGroup , 'Collation is ' + collation_name AS Finding , - 'https://BrentOzar.com/go/collate' AS URL , + 'https://www.brentozar.com/go/collate' AS URL , 'Collation differences between user databases and tempdb can cause conflicts especially when comparing string values' AS Details FROM sys.databases WHERE name NOT IN ( 'master', 'model', 'msdb') @@ -10732,7 +10677,7 @@ IF @ProductVersionMajor >= 10 170 AS Priority , 'Reliability' AS FindingsGroup , 'Database Snapshot Online' AS Finding , - 'https://BrentOzar.com/go/snapshot' AS URL , + 'https://www.brentozar.com/go/snapshot' AS URL , 'Database [' + dSnap.[name] + '] is a snapshot of [' + dOriginal.[name] @@ -10770,7 +10715,7 @@ IF @ProductVersionMajor >= 10 END AS Priority, 'Performance' AS FindingsGroup , 'Shrink Database Job' AS Finding , - 'https://BrentOzar.com/go/autoshrink' AS URL , + 'https://www.brentozar.com/go/autoshrink' AS URL , 'In the [' + j.[name] + '] job, step [' + step.[step_name] + '] has SHRINKDATABASE or SHRINKFILE, which may be causing database fragmentation.' @@ -10840,7 +10785,7 @@ IF @ProductVersionMajor >= 10 200 AS Priority , 'Informational' AS FindingsGroup , 'Agent Jobs Starting Simultaneously' AS Finding , - 'https://BrentOzar.com/go/busyagent/' AS URL , + 'https://www.brentozar.com/go/busyagent/' AS URL , ( 'Multiple SQL Server Agent jobs are configured to start simultaneously. For detailed schedule listings, see the query in the URL.' ) AS Details FROM msdb.dbo.sysjobactivity WHERE start_execution_date > DATEADD(dd, -14, GETDATE()) @@ -10956,7 +10901,7 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 250 AS [Priority] , 'Server Info' AS [FindingsGroup] , 'Locked Pages In Memory Enabled' AS [Finding] , - 'https://BrentOzar.com/go/lpim' AS [URL] , + 'https://www.brentozar.com/go/lpim' AS [URL] , ( 'You currently have ' + CASE WHEN [dopm].[locked_page_allocations_kb] / 1024. / 1024. > 0 THEN CAST([dopm].[locked_page_allocations_kb] / 1024 / 1024 AS VARCHAR(100)) @@ -10988,7 +10933,7 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 250 AS Priority , ''Server Info'' AS FindingsGroup , ''Memory Model Unconventional'' AS Finding , - ''https://BrentOzar.com/go/lpim'' AS URL , + ''https://www.brentozar.com/go/lpim'' AS URL , ''Memory Model: '' + CAST(sql_memory_model_desc AS NVARCHAR(100)) FROM sys.dm_os_sys_info WHERE sql_memory_model <> 1 OPTION (RECOMPILE);'; @@ -11027,7 +10972,7 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 250 AS [Priority] , 'Server Info' AS [FindingsGroup] , 'Instant File Initialization Enabled' AS [Finding] , - 'https://BrentOzar.com/go/instant' AS [URL] , + 'https://www.brentozar.com/go/instant' AS [URL] , 'The service account has the Perform Volume Maintenance Tasks permission.'; END; @@ -11049,7 +10994,7 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 50 AS Priority , ''Server Info'' AS FindingsGroup , ''Instant File Initialization Not Enabled'' AS Finding , - ''https://BrentOzar.com/go/instant'' AS URL , + ''https://www.brentozar.com/go/instant'' AS URL , ''Consider enabling IFI for faster restores and data file growths.'' FROM sys.dm_server_services WHERE instant_file_initialization_enabled <> ''Y'' AND filename LIKE ''%sqlservr.exe%'' OPTION (RECOMPILE);'; @@ -11078,7 +11023,7 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 250 AS Priority , 'Server Info' AS FindingsGroup , 'Server Name' AS Finding , - 'https://BrentOzar.com/go/servername' AS URL , + 'https://www.brentozar.com/go/servername' AS URL , @@SERVERNAME AS Details WHERE @@SERVERNAME IS NOT NULL; END; @@ -11349,7 +11294,7 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 250 AS Priority, ''Server Info'' AS FindingsGroup, ''Virtual Server'' AS Finding, - ''https://BrentOzar.com/go/virtual'' AS URL, + ''https://www.brentozar.com/go/virtual'' AS URL, ''Type: ('' + virtual_machine_type_desc + '')'' AS Details FROM sys.dm_os_sys_info WHERE virtual_machine_type <> 0 OPTION (RECOMPILE);'; @@ -11377,7 +11322,7 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 250 AS Priority, ''Server Info'' AS FindingsGroup, ''Container'' AS Finding, - ''https://BrentOzar.com/go/virtual'' AS URL, + ''https://www.brentozar.com/go/virtual'' AS URL, ''Type: ('' + container_type_desc + '')'' AS Details FROM sys.dm_os_sys_info WHERE container_type_desc <> ''NONE'' OPTION (RECOMPILE);'; @@ -11550,7 +11495,7 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 ,250 AS Priority ,'Server Info' AS FindingsGroup ,'Default Trace Contents' AS Finding - ,'https://BrentOzar.com/go/trace' AS URL + ,'https://www.brentozar.com/go/trace' AS URL ,'The default trace holds '+cast(DATEDIFF(hour,MIN(StartTime),GETDATE())as VARCHAR(30))+' hours of data' +' between '+cast(Min(StartTime) as VARCHAR(30))+' and '+cast(GETDATE()as VARCHAR(30)) +('. The default trace files are located in: '+left( @curr_tracefilename,len(@curr_tracefilename) - @indx) @@ -11630,7 +11575,7 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 URL , Details ) - VALUES (153, 240, 'Wait Stats', 'No Significant Waits Detected', 'https://BrentOzar.com/go/waits', 'This server might be just sitting around idle, or someone may have cleared wait stats recently.'); + VALUES (153, 240, 'Wait Stats', 'No Significant Waits Detected', 'https://www.brentozar.com/go/waits', 'This server might be just sitting around idle, or someone may have cleared wait stats recently.'); END; END; /* CheckID 152 */ @@ -12293,7 +12238,7 @@ AS SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.01', @VersionDate = '20210222'; + SELECT @Version = '8.02', @VersionDate = '20210321'; IF(@VersionCheckMode = 1) BEGIN @@ -14038,6 +13983,7 @@ ALTER PROCEDURE dbo.sp_BlitzCache @UseTriggersAnyway BIT = NULL, @ExportToExcel BIT = 0, @ExpertMode TINYINT = 0, + @OutputType VARCHAR(20) = 'TABLE' , @OutputServerName NVARCHAR(258) = NULL , @OutputDatabaseName NVARCHAR(258) = NULL , @OutputSchemaName NVARCHAR(258) = NULL , @@ -14072,8 +14018,8 @@ BEGIN SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.01', @VersionDate = '20210222'; - +SELECT @Version = '8.02', @VersionDate = '20210321'; +SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) BEGIN @@ -14162,7 +14108,11 @@ IF @Help = 1 SELECT N'@ExpertMode', N'TINYINT', N'Default 0. When set to 1, results include more columns. When 2, mode is optimized for Opserver, the open source dashboard.' - + UNION ALL + SELECT N'@OutputType', + N'NVARCHAR(258)', + N'If set to "NONE", this will tell this procedure not to run any query leading to a results set thrown to caller.' + UNION ALL SELECT N'@OutputDatabaseName', N'NVARCHAR(128)', @@ -14191,7 +14141,7 @@ IF @Help = 1 UNION ALL SELECT N'@IgnoreSystemDBs', N'BIT', - N'Ignores plans found in the system databases (master, model, msdb, tempdb, and resourcedb)' + N'Ignores plans found in the system databases (master, model, msdb, tempdb, dbmaintenance, dbadmin, dbatools and resourcedb)' UNION ALL SELECT N'@OnlyQueryHashes', @@ -14549,6 +14499,18 @@ BEGIN RETURN; END; +IF(@OutputType = 'NONE' AND (@OutputTableName IS NULL OR @OutputSchemaName IS NULL OR @OutputDatabaseName IS NULL)) +BEGIN + RAISERROR('This procedure should be called with a value for all @Output* parameters, as @OutputType is set to NONE',12,1); + RETURN; +END; + +IF(@OutputType = 'NONE') +BEGIN + SET @HideSummary = 1; +END; + + /* Lets get @SortOrder set to lower case here for comparisons later */ SET @SortOrder = LOWER(@SortOrder); @@ -15741,7 +15703,7 @@ SET @body += N' WHERE 1 = 1 ' + @nl ; IF @IgnoreSystemDBs = 1 BEGIN RAISERROR(N'Ignoring system databases by default', 0, 1) WITH NOWAIT; - SET @body += N' AND COALESCE(DB_NAME(CAST(xpa.value AS INT)), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'') AND COALESCE(DB_NAME(CAST(xpa.value AS INT)), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ; + SET @body += N' AND COALESCE(LOWER(DB_NAME(CAST(xpa.value AS INT))), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'', ''dbmaintenance'', ''dbadmin'', ''dbatools'') AND COALESCE(DB_NAME(CAST(xpa.value AS INT)), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ; END; IF @DatabaseName IS NOT NULL OR @DatabaseName <> N'' @@ -16176,7 +16138,7 @@ BEGIN SET @sql += @body_where ; IF @IgnoreSystemDBs = 1 - SET @sql += N' AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'') AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ; + SET @sql += N' AND COALESCE(LOWER(DB_NAME(database_id)), LOWER(CAST(pa.value AS sysname)), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'', ''dbmaintenance'', ''dbadmin'', ''dbatools'') AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ; SET @sql += @sort_filter + @nl; @@ -16208,7 +16170,7 @@ BEGIN SET @sql += @body_where ; IF @IgnoreSystemDBs = 1 - SET @sql += N' AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'') AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ; + SET @sql += N' AND COALESCE(LOWER(DB_NAME(database_id)), LOWER(CAST(pa.value AS sysname)), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'', ''dbmaintenance'', ''dbadmin'', ''dbatools'') AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ; SET @sql += @sort_filter + @nl; @@ -16244,7 +16206,7 @@ BEGIN SET @sql += @body_where ; IF @IgnoreSystemDBs = 1 - SET @sql += N' AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'') AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ; + SET @sql += N' AND COALESCE(LOWER(DB_NAME(database_id)), LOWER(CAST(pa.value AS sysname)), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'', ''dbmaintenance'', ''dbadmin'', ''dbatools'') AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ; SET @sql += @sort_filter + @nl; @@ -17895,7 +17857,7 @@ SELECT spi.SPID, ELSE @nl + N' We could not find any cached parameter values for this stored proc. ' END + CASE WHEN spi2.variable_name = N'**no_variable**' OR spi2.compile_time_value = N'**idk_man**' - THEN @nl + N'More info on possible reasons: https://BrentOzar.com/go/noplans ' + THEN @nl + N'More info on possible reasons: https://www.brentozar.com/go/noplans ' WHEN spi2.compile_time_value = N'NULL' THEN spi2.compile_time_value ELSE RTRIM(spi2.compile_time_value) @@ -17938,7 +17900,7 @@ SELECT spi.SPID, ELSE @nl + N' We could not find any cached parameter values for this stored proc. ' END + CASE WHEN spi2.variable_name = N'**no_variable**' OR spi2.compile_time_value = N'**idk_man**' - THEN @nl + N' More info on possible reasons: https://BrentOzar.com/go/noplans ' + THEN @nl + N' More info on possible reasons: https://www.brentozar.com/go/noplans ' WHEN spi2.compile_time_value = N'NULL' THEN spi2.compile_time_value ELSE RTRIM(spi2.compile_time_value) @@ -18542,7 +18504,7 @@ FROM ##BlitzCacheProcs b ) UPDATE b SET Warnings = ISNULL('Your query plan is >128 levels of nested nodes, and can''t be converted to XML. Use SELECT * FROM sys.dm_exec_text_query_plan('+ CONVERT(VARCHAR(128), ph.PlanHandle, 1) + ', 0, -1) to get more information' - , 'We couldn''t find a plan for this query. More info on possible reasons: https://BrentOzar.com/go/noplans') + , 'We couldn''t find a plan for this query. More info on possible reasons: https://www.brentozar.com/go/noplans') FROM ##BlitzCacheProcs b LEFT JOIN plan_handle ph ON b.PlanHandle = ph.PlanHandle @@ -18925,9 +18887,10 @@ IF @Debug = 1 PRINT SUBSTRING(@sql, 32000, 36000); PRINT SUBSTRING(@sql, 36000, 40000); END; - -EXEC sp_executesql @sql, N'@Top INT, @spid INT, @minimumExecutionCount INT, @min_back INT', @Top, @@SPID, @MinimumExecutionCount, @MinutesBack; - +IF(@OutputType <> 'NONE') +BEGIN + EXEC sp_executesql @sql, N'@Top INT, @spid INT, @minimumExecutionCount INT, @min_back INT', @Top, @@SPID, @MinimumExecutionCount, @MinutesBack; +END; /* @@ -19024,7 +18987,7 @@ BEGIN 100, 'Execution Pattern', 'Frequent Execution', - 'http://brentozar.com/blitzcache/frequently-executed-queries/', + 'https://www.brentozar.com/blitzcache/frequently-executed-queries/', 'Queries are being executed more than ' + CAST (@execution_threshold AS VARCHAR(5)) + ' times per minute. This can put additional load on the server, even when queries are lightweight.') ; @@ -19039,7 +19002,7 @@ BEGIN 50, 'Parameterization', 'Parameter Sniffing', - 'http://brentozar.com/blitzcache/parameter-sniffing/', + 'https://www.brentozar.com/blitzcache/parameter-sniffing/', 'There are signs of parameter sniffing (wide variance in rows return or time to execute). Investigate query patterns and tune code appropriately.') ; /* Forced execution plans */ @@ -19053,7 +19016,7 @@ BEGIN 50, 'Parameterization', 'Forced Plan', - 'http://brentozar.com/blitzcache/forced-plans/', + 'https://www.brentozar.com/blitzcache/forced-plans/', 'Execution plans have been compiled with forced plans, either through FORCEPLAN, plan guides, or forced parameterization. This will make general tuning efforts less effective.'); IF EXISTS (SELECT 1/0 @@ -19066,7 +19029,7 @@ BEGIN 200, 'Cursors', 'Cursor', - 'http://brentozar.com/blitzcache/cursors-found-slow-queries/', + 'https://www.brentozar.com/blitzcache/cursors-found-slow-queries/', 'There are cursors in the plan cache. This is neither good nor bad, but it is a thing. Cursors are weird in SQL Server.'); IF EXISTS (SELECT 1/0 @@ -19080,7 +19043,7 @@ BEGIN 200, 'Cursors', 'Optimistic Cursors', - 'http://brentozar.com/blitzcache/cursors-found-slow-queries/', + 'https://www.brentozar.com/blitzcache/cursors-found-slow-queries/', 'There are optimistic cursors in the plan cache, which can harm performance.'); IF EXISTS (SELECT 1/0 @@ -19094,7 +19057,7 @@ BEGIN 200, 'Cursors', 'Non-forward Only Cursors', - 'http://brentozar.com/blitzcache/cursors-found-slow-queries/', + 'https://www.brentozar.com/blitzcache/cursors-found-slow-queries/', 'There are non-forward only cursors in the plan cache, which can harm performance.'); IF EXISTS (SELECT 1/0 @@ -19108,7 +19071,7 @@ BEGIN 200, 'Cursors', 'Dynamic Cursors', - 'http://brentozar.com/blitzcache/cursors-found-slow-queries/', + 'https://www.brentozar.com/blitzcache/cursors-found-slow-queries/', 'Dynamic Cursors inhibit parallelism!.'); IF EXISTS (SELECT 1/0 @@ -19122,7 +19085,7 @@ BEGIN 200, 'Cursors', 'Fast Forward Cursors', - 'http://brentozar.com/blitzcache/cursors-found-slow-queries/', + 'https://www.brentozar.com/blitzcache/cursors-found-slow-queries/', 'Fast forward cursors inhibit parallelism!.'); IF EXISTS (SELECT 1/0 @@ -19135,7 +19098,7 @@ BEGIN 50, 'Parameterization', 'Forced Parameterization', - 'http://brentozar.com/blitzcache/forced-parameterization/', + 'https://www.brentozar.com/blitzcache/forced-parameterization/', 'Execution plans have been compiled with forced parameterization.') ; IF EXISTS (SELECT 1/0 @@ -19148,7 +19111,7 @@ BEGIN 200, 'Execution Plans', 'Parallel', - 'http://brentozar.com/blitzcache/parallel-plans-detected/', + 'https://www.brentozar.com/blitzcache/parallel-plans-detected/', 'Parallel plans detected. These warrant investigation, but are neither good nor bad.') ; IF EXISTS (SELECT 1/0 @@ -19161,7 +19124,7 @@ BEGIN 200, 'Execution Plans', 'Nearly Parallel', - 'http://brentozar.com/blitzcache/query-cost-near-cost-threshold-parallelism/', + 'https://www.brentozar.com/blitzcache/query-cost-near-cost-threshold-parallelism/', 'Queries near the cost threshold for parallelism. These may go parallel when you least expect it.') ; IF EXISTS (SELECT 1/0 @@ -19174,7 +19137,7 @@ BEGIN 50, 'Execution Plans', 'Plan Warnings', - 'http://brentozar.com/blitzcache/query-plan-warnings/', + 'https://www.brentozar.com/blitzcache/query-plan-warnings/', 'Warnings detected in execution plans. SQL Server is telling you that something bad is going on that requires your attention.') ; IF EXISTS (SELECT 1/0 @@ -19187,7 +19150,7 @@ BEGIN 50, 'Performance', 'Long Running Query', - 'http://brentozar.com/blitzcache/long-running-queries/', + 'https://www.brentozar.com/blitzcache/long-running-queries/', 'Long running queries have been found. These are queries with an average duration longer than ' + CAST(@long_running_query_warning_seconds / 1000 / 1000 AS VARCHAR(5)) + ' second(s). These queries should be investigated for additional tuning options.') ; @@ -19202,7 +19165,7 @@ BEGIN 50, 'Performance', 'Missing Indexes', - 'http://brentozar.com/blitzcache/missing-index-request/', + 'https://www.brentozar.com/blitzcache/missing-index-request/', 'Queries found with missing indexes.'); IF EXISTS (SELECT 1/0 @@ -19215,7 +19178,7 @@ BEGIN 200, 'Cardinality', 'Downlevel CE', - 'http://brentozar.com/blitzcache/legacy-cardinality-estimator/', + 'https://www.brentozar.com/blitzcache/legacy-cardinality-estimator/', 'A legacy cardinality estimator is being used by one or more queries. Investigate whether you need to be using this cardinality estimator. This may be caused by compatibility levels, global trace flags, or query level trace flags.'); IF EXISTS (SELECT 1/0 @@ -19228,7 +19191,7 @@ BEGIN 50, 'Performance', 'Implicit Conversions', - 'http://brentozar.com/go/implicit', + 'https://www.brentozar.com/go/implicit', 'One or more queries are comparing two fields that are not of the same data type.') ; IF EXISTS (SELECT 1/0 @@ -19241,7 +19204,7 @@ BEGIN 100, 'Performance', 'Busy Loops', - 'http://brentozar.com/blitzcache/busy-loops/', + 'https://www.brentozar.com/blitzcache/busy-loops/', 'Operations have been found that are executed 100 times more often than the number of rows returned by each iteration. This is an indicator that something is off in query execution.'); IF EXISTS (SELECT 1/0 @@ -19254,7 +19217,7 @@ BEGIN 50, 'Performance', 'Function Join', - 'http://brentozar.com/blitzcache/tvf-join/', + 'https://www.brentozar.com/blitzcache/tvf-join/', 'Execution plans have been found that join to table valued functions (TVFs). TVFs produce inaccurate estimates of the number of rows returned and can lead to any number of query plan problems.'); IF EXISTS (SELECT 1/0 @@ -19267,7 +19230,7 @@ BEGIN 50, 'Execution Plans', 'Compilation Timeout', - 'http://brentozar.com/blitzcache/compilation-timeout/', + 'https://www.brentozar.com/blitzcache/compilation-timeout/', 'Query compilation timed out for one or more queries. SQL Server did not find a plan that meets acceptable performance criteria in the time allotted so the best guess was returned. There is a very good chance that this plan isn''t even below average - it''s probably terrible.'); IF EXISTS (SELECT 1/0 @@ -19280,7 +19243,7 @@ BEGIN 50, 'Execution Plans', 'Compile Memory Limit Exceeded', - 'http://brentozar.com/blitzcache/compile-memory-limit-exceeded/', + 'https://www.brentozar.com/blitzcache/compile-memory-limit-exceeded/', 'The optimizer has a limited amount of memory available. One or more queries are complex enough that SQL Server was unable to allocate enough memory to fully optimize the query. A best fit plan was found, and it''s probably terrible.'); IF EXISTS (SELECT 1/0 @@ -19293,7 +19256,7 @@ BEGIN 50, 'Execution Plans', 'No Join Predicate', - 'http://brentozar.com/blitzcache/no-join-predicate/', + 'https://www.brentozar.com/blitzcache/no-join-predicate/', 'Operators in a query have no join predicate. This means that all rows from one table will be matched with all rows from anther table producing a Cartesian product. That''s a whole lot of rows. This may be your goal, but it''s important to investigate why this is happening.'); IF EXISTS (SELECT 1/0 @@ -19306,7 +19269,7 @@ BEGIN 200, 'Execution Plans', 'Multiple Plans', - 'http://brentozar.com/blitzcache/multiple-plans/', + 'https://www.brentozar.com/blitzcache/multiple-plans/', 'Queries exist with multiple execution plans (as determined by query_plan_hash). Investigate possible ways to parameterize these queries or otherwise reduce the plan count.'); IF EXISTS (SELECT 1/0 @@ -19319,7 +19282,7 @@ BEGIN 100, 'Performance', 'Unmatched Indexes', - 'http://brentozar.com/blitzcache/unmatched-indexes', + 'https://www.brentozar.com/blitzcache/unmatched-indexes', 'An index could have been used, but SQL Server chose not to use it - likely due to parameterization and filtered indexes.'); IF EXISTS (SELECT 1/0 @@ -19332,7 +19295,7 @@ BEGIN 100, 'Parameterization', 'Unparameterized Query', - 'http://brentozar.com/blitzcache/unparameterized-queries', + 'https://www.brentozar.com/blitzcache/unparameterized-queries', 'Unparameterized queries found. These could be ad hoc queries, data exploration, or queries using "OPTIMIZE FOR UNKNOWN".'); IF EXISTS (SELECT 1/0 @@ -19345,7 +19308,7 @@ BEGIN 100, 'Execution Plans', 'Trivial Plans', - 'http://brentozar.com/blitzcache/trivial-plans', + 'https://www.brentozar.com/blitzcache/trivial-plans', 'Trivial plans get almost no optimization. If you''re finding these in the top worst queries, something may be going wrong.'); IF EXISTS (SELECT 1/0 @@ -19358,7 +19321,7 @@ BEGIN 10, 'Execution Plans', 'Forced Serialization', - 'http://www.brentozar.com/blitzcache/forced-serialization/', + 'https://www.brentozar.com/blitzcache/forced-serialization/', 'Something in your plan is forcing a serial query. Further investigation is needed if this is not by design.') ; IF EXISTS (SELECT 1/0 @@ -19371,7 +19334,7 @@ BEGIN 100, 'Execution Plans', 'Expensive Key Lookup', - 'http://www.brentozar.com/blitzcache/expensive-key-lookups/', + 'https://www.brentozar.com/blitzcache/expensive-key-lookups/', 'There''s a key lookup in your plan that costs >=50% of the total plan cost.') ; IF EXISTS (SELECT 1/0 @@ -19384,7 +19347,7 @@ BEGIN 100, 'Execution Plans', 'Expensive Remote Query', - 'http://www.brentozar.com/blitzcache/expensive-remote-query/', + 'https://www.brentozar.com/blitzcache/expensive-remote-query/', 'There''s a remote query in your plan that costs >=50% of the total plan cost.') ; IF EXISTS (SELECT 1/0 @@ -19476,7 +19439,7 @@ BEGIN 100, 'Warnings', 'Operator Warnings', - 'http://brentozar.com/blitzcache/query-plan-warnings/', + 'https://www.brentozar.com/blitzcache/query-plan-warnings/', 'Check the plan for more details.') ; IF EXISTS (SELECT 1/0 @@ -19580,7 +19543,7 @@ BEGIN 100, 'Execution Plans', 'Expensive Sort', - 'http://www.brentozar.com/blitzcache/expensive-sorts/', + 'https://www.brentozar.com/blitzcache/expensive-sorts/', 'There''s a sort in your plan that costs >=50% of the total plan cost.') ; IF EXISTS (SELECT 1/0 @@ -19821,7 +19784,7 @@ BEGIN 100, 'Functions', 'MSTVFs', - 'http://brentozar.com/blitzcache/tvf-join/', + 'https://www.brentozar.com/blitzcache/tvf-join/', 'Execution plans have been found that join to table valued functions (TVFs). TVFs produce inaccurate estimates of the number of rows returned and can lead to any number of query plan problems.'); IF EXISTS (SELECT 1/0 @@ -19998,7 +19961,7 @@ BEGIN N'Large USERSTORE_TOKENPERM cache: ' + CONVERT(NVARCHAR(11), @user_perm_gb_out) + N'GB', N'The USERSTORE_TOKENPERM is taking up ' + CONVERT(NVARCHAR(11), @user_perm_percent) + N'% of the buffer pool, and your plan cache seems to be unstable', - N'https://brentozar.com/go/userstore', + N'https://www.brentozar.com/go/userstore', N'A growing USERSTORE_TOKENPERM cache can cause the plan cache to clear out' IF @v >= 11 @@ -20639,11 +20602,12 @@ END; PRINT SUBSTRING(@AllSortSql, 32000, 36000); PRINT SUBSTRING(@AllSortSql, 36000, 40000); END; - +IF(@OutputType <> 'NONE') +BEGIN EXEC sys.sp_executesql @stmt = @AllSortSql, @params = N'@i_DatabaseName NVARCHAR(128), @i_Top INT, @i_SkipAnalysis BIT, @i_OutputDatabaseName NVARCHAR(258), @i_OutputSchemaName NVARCHAR(258), @i_OutputTableName NVARCHAR(258), @i_CheckDateOverride DATETIMEOFFSET, @i_MinutesBack INT ', @i_DatabaseName = @DatabaseName, @i_Top = @Top, @i_SkipAnalysis = @SkipAnalysis, @i_OutputDatabaseName = @OutputDatabaseName, @i_OutputSchemaName = @OutputSchemaName, @i_OutputTableName = @OutputTableName, @i_CheckDateOverride = @CheckDateOverride, @i_MinutesBack = @MinutesBack; - +END; /*End of AllSort section*/ @@ -21258,7 +21222,7 @@ AS SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.01', @VersionDate = '20210222'; +SELECT @Version = '8.02', @VersionDate = '20210321'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -21972,6 +21936,7 @@ IF @GetAllDatabases = 1 AND database_id > 4 AND DB_NAME(database_id) NOT LIKE 'ReportServer%' AND DB_NAME(database_id) NOT LIKE 'rdsadmin%' + AND LOWER(name) NOT IN('dbatools', 'dbadmin', 'dbmaintenance') AND is_distributor = 0 OPTION ( RECOMPILE ); @@ -26732,7 +26697,7 @@ BEGIN SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.01', @VersionDate = '20210222'; +SELECT @Version = '8.02', @VersionDate = '20210321'; IF(@VersionCheckMode = 1) @@ -27435,6 +27400,7 @@ You need to use an Azure storage account, and the path has to look like this: ht ) AS step_id FROM #deadlock_process AS dp WHERE dp.client_app LIKE 'SQLAgent - %' + AND dp.client_app <> 'SQLAgent - Initial Boot Probe' ) AS x OPTION ( RECOMPILE ); @@ -27988,18 +27954,18 @@ You need to use an Azure storage account, and the path has to look like this: ht dp.priority, dp.log_used, dp.wait_resource COLLATE DATABASE_DEFAULT AS wait_resource, - CONVERT( - XML, - STUFF(( SELECT DISTINCT NCHAR(10) + CONVERT( + XML, + STUFF(( SELECT DISTINCT NCHAR(10) + N' ' + ISNULL(c.object_name, N'') + N' ' COLLATE DATABASE_DEFAULT AS object_name - FROM #deadlock_owner_waiter AS c + FROM #deadlock_owner_waiter AS c WHERE ( dp.id = c.owner_id - OR dp.victim_id = c.waiter_id ) - AND CONVERT(DATE, dp.event_date) = CONVERT(DATE, c.event_date) - FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(4000)'), - 1, 1, N'')) AS object_names, + OR dp.victim_id = c.waiter_id ) + AND dp.event_date = c.event_date + FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(4000)'), + 1, 1, N'')) AS object_names, dp.wait_time, dp.transaction_name, dp.last_tran_started, @@ -28012,7 +27978,6 @@ You need to use an Azure storage account, and the path has to look like this: ht dp.login_name, dp.isolation_level, dp.process_xml.value('(//process/inputbuf/text())[1]', 'NVARCHAR(MAX)') AS inputbuf, - ROW_NUMBER() OVER ( PARTITION BY dp.event_date, dp.id ORDER BY dp.event_date ) AS dn, DENSE_RANK() OVER ( ORDER BY dp.event_date ) AS en, ROW_NUMBER() OVER ( PARTITION BY dp.event_date ORDER BY dp.event_date ) -1 AS qn, dp.is_victim, @@ -28045,20 +28010,18 @@ You need to use an Azure storage account, and the path has to look like this: ht dp.priority, dp.log_used, dp.wait_resource COLLATE DATABASE_DEFAULT, - CASE WHEN @ExportToExcel = 0 THEN - CONVERT( - XML, - STUFF(( SELECT DISTINCT NCHAR(10) - + N' ' - + ISNULL(c.object_name, N'') - + N' ' COLLATE DATABASE_DEFAULT AS object_name - FROM #deadlock_owner_waiter AS c - WHERE ( dp.id = c.owner_id - OR dp.victim_id = c.waiter_id ) - AND CONVERT(DATE, dp.event_date) = CONVERT(DATE, c.event_date) - FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(4000)'), - 1, 1, N'')) - ELSE NULL END AS object_names, + CONVERT( + XML, + STUFF(( SELECT DISTINCT NCHAR(10) + + N' ' + + ISNULL(c.object_name, N'') + + N' ' COLLATE DATABASE_DEFAULT AS object_name + FROM #deadlock_owner_waiter AS c + WHERE ( dp.id = c.owner_id + OR dp.victim_id = c.waiter_id ) + AND dp.event_date = c.event_date + FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(4000)'), + 1, 1, N'')) AS object_names, dp.wait_time, dp.transaction_name, dp.last_tran_started, @@ -28071,7 +28034,6 @@ You need to use an Azure storage account, and the path has to look like this: ht dp.login_name, dp.isolation_level, dp.process_xml.value('(//process/inputbuf/text())[1]', 'NVARCHAR(MAX)') AS inputbuf, - ROW_NUMBER() OVER ( PARTITION BY dp.event_date, dp.id ORDER BY dp.event_date ) AS dn, DENSE_RANK() OVER ( ORDER BY dp.event_date ) AS en, ROW_NUMBER() OVER ( PARTITION BY dp.event_date ORDER BY dp.event_date ) -1 AS qn, 1 AS is_victim, @@ -28173,7 +28135,7 @@ You need to use an Azure storage account, and the path has to look like this: ht FROM deadlocks AS d WHERE d.dn = 1 AND (is_victim = @VictimsOnly OR @VictimsOnly = 0) - AND d.en < CASE WHEN d.deadlock_type = N'Parallel Deadlock' THEN 2 ELSE 2147483647 END + AND d.qn < CASE WHEN d.deadlock_type = N'Parallel Deadlock' THEN 2 ELSE 2147483647 END AND (DB_NAME(d.database_id) = @DatabaseName OR @DatabaseName IS NULL) AND (d.event_date >= @StartDate OR @StartDate IS NULL) AND (d.event_date < @EndDate OR @EndDate IS NULL) @@ -28213,20 +28175,18 @@ ELSE --Output to database is not set output to client app dp.priority, dp.log_used, dp.wait_resource COLLATE DATABASE_DEFAULT AS wait_resource, - CASE WHEN @ExportToExcel = 0 THEN - CONVERT( - XML, - STUFF(( SELECT DISTINCT NCHAR(10) - + N' ' - + ISNULL(c.object_name, N'') - + N' ' COLLATE DATABASE_DEFAULT AS object_name - FROM #deadlock_owner_waiter AS c - WHERE ( dp.id = c.owner_id - OR dp.victim_id = c.waiter_id ) - AND CONVERT(DATE, dp.event_date) = CONVERT(DATE, c.event_date) - FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(4000)'), - 1, 1, N'')) - ELSE NULL END AS object_names, + CONVERT( + XML, + STUFF(( SELECT DISTINCT NCHAR(10) + + N' ' + + ISNULL(c.object_name, N'') + + N' ' COLLATE DATABASE_DEFAULT AS object_name + FROM #deadlock_owner_waiter AS c + WHERE ( dp.id = c.owner_id + OR dp.victim_id = c.waiter_id ) + AND dp.event_date = c.event_date + FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(4000)'), + 1, 1, N'')) AS object_names, dp.wait_time, dp.transaction_name, dp.last_tran_started, @@ -28239,7 +28199,6 @@ ELSE --Output to database is not set output to client app dp.login_name, dp.isolation_level, dp.process_xml.value('(//process/inputbuf/text())[1]', 'NVARCHAR(MAX)') AS inputbuf, - ROW_NUMBER() OVER ( PARTITION BY dp.event_date, dp.id ORDER BY dp.event_date ) AS dn, DENSE_RANK() OVER ( ORDER BY dp.event_date ) AS en, ROW_NUMBER() OVER ( PARTITION BY dp.event_date ORDER BY dp.event_date ) -1 AS qn, dp.is_victim, @@ -28272,20 +28231,18 @@ ELSE --Output to database is not set output to client app dp.priority, dp.log_used, dp.wait_resource COLLATE DATABASE_DEFAULT, - CASE WHEN @ExportToExcel = 0 THEN - CONVERT( - XML, - STUFF(( SELECT DISTINCT NCHAR(10) - + N' ' - + ISNULL(c.object_name, N'') - + N' ' COLLATE DATABASE_DEFAULT AS object_name - FROM #deadlock_owner_waiter AS c - WHERE ( dp.id = c.owner_id - OR dp.victim_id = c.waiter_id ) - AND CONVERT(DATE, dp.event_date) = CONVERT(DATE, c.event_date) - FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(4000)'), - 1, 1, N'')) - ELSE NULL END AS object_names, + CONVERT( + XML, + STUFF(( SELECT DISTINCT NCHAR(10) + + N' ' + + ISNULL(c.object_name, N'') + + N' ' COLLATE DATABASE_DEFAULT AS object_name + FROM #deadlock_owner_waiter AS c + WHERE ( dp.id = c.owner_id + OR dp.victim_id = c.waiter_id ) + AND dp.event_date = c.event_date + FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(4000)'), + 1, 1, N'')) AS object_names, dp.wait_time, dp.transaction_name, dp.last_tran_started, @@ -28298,7 +28255,6 @@ ELSE --Output to database is not set output to client app dp.login_name, dp.isolation_level, dp.process_xml.value('(//process/inputbuf/text())[1]', 'NVARCHAR(MAX)') AS inputbuf, - ROW_NUMBER() OVER ( PARTITION BY dp.event_date, dp.id ORDER BY dp.event_date ) AS dn, DENSE_RANK() OVER ( ORDER BY dp.event_date ) AS en, ROW_NUMBER() OVER ( PARTITION BY dp.event_date ORDER BY dp.event_date ) -1 AS qn, 1 AS is_victim, @@ -28331,8 +28287,8 @@ ELSE --Output to database is not set output to client app + CASE WHEN d.qn = 0 THEN N'1' ELSE CONVERT(NVARCHAR(10), d.qn) END + CASE WHEN d.is_victim = 1 THEN ' - VICTIM' ELSE '' END AS deadlock_group, - CASE WHEN @ExportToExcel = 0 THEN CONVERT(XML, N'') - ELSE SUBSTRING(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(LTRIM(RTRIM(d.inputbuf)),' ','<>'),'><',''),NCHAR(10), ' '),NCHAR(13), ' '),'<>',' '), 1, 32000) END AS query, + CONVERT(XML, N'') AS query_xml, + d.inputbuf AS query_string, d.object_names, d.isolation_level, d.owner_mode, @@ -28361,11 +28317,13 @@ ELSE --Output to database is not set output to client app d.waiter_merging, d.waiter_spilling, d.waiter_waiting_to_close, - CASE WHEN @ExportToExcel = 0 THEN d.deadlock_graph ELSE NULL END AS deadlock_graph - FROM deadlocks AS d + d.deadlock_graph, + d.is_victim + INTO #deadlock_results + FROM deadlocks AS d WHERE d.dn = 1 - AND (is_victim = @VictimsOnly OR @VictimsOnly = 0) - AND d.en < CASE WHEN d.deadlock_type = N'Parallel Deadlock' THEN 2 ELSE 2147483647 END + AND (d.is_victim = @VictimsOnly OR @VictimsOnly = 0) + AND d.qn < CASE WHEN d.deadlock_type = N'Parallel Deadlock' THEN 2 ELSE 2147483647 END AND (DB_NAME(d.database_id) = @DatabaseName OR @DatabaseName IS NULL) AND (d.event_date >= @StartDate OR @StartDate IS NULL) AND (d.event_date < @EndDate OR @EndDate IS NULL) @@ -28373,9 +28331,67 @@ ELSE --Output to database is not set output to client app AND (d.client_app = @AppName OR @AppName IS NULL) AND (d.host_name = @HostName OR @HostName IS NULL) AND (d.login_name = @LoginName OR @LoginName IS NULL) - ORDER BY d.event_date, is_victim DESC OPTION ( RECOMPILE ); + + DECLARE @deadlock_result NVARCHAR(MAX) = N'' + + SET @deadlock_result += N' + SELECT + dr.deadlock_type, + dr.event_date, + dr.database_name, + dr.deadlock_group, + ' + + CASE @ExportToExcel + WHEN 1 + THEN N'dr.query_string AS query, + REPLACE(REPLACE(CONVERT(NVARCHAR(MAX), dr.object_names), '''', ''''), '''', '''') AS object_names,' + ELSE N'dr.query_xml AS query, + dr.object_names,' + END + + N' + dr.isolation_level, + dr.owner_mode, + dr.waiter_mode, + dr.transaction_count, + dr.login_name, + dr.host_name, + dr.client_app, + dr.wait_time, + dr.priority, + dr.log_used, + dr.last_tran_started, + dr.last_batch_started, + dr.last_batch_completed, + dr.transaction_name, + dr.owner_waiter_type, + dr.owner_activity, + dr.owner_waiter_activity, + dr.owner_merging, + dr.owner_spilling, + dr.owner_waiting_to_close, + dr.waiter_waiter_type, + dr.waiter_owner_activity, + dr.waiter_waiter_activity, + dr.waiter_merging, + dr.waiter_spilling, + dr.waiter_waiting_to_close' + + CASE @ExportToExcel + WHEN 1 + THEN N'' + ELSE N', + dr.deadlock_graph' + END + + ' + FROM #deadlock_results AS dr + ORDER BY dr.event_date, dr.is_victim DESC + OPTION(RECOMPILE); + ' + + EXEC sys.sp_executesql + @deadlock_result; + SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); RAISERROR('Findings %s', 0, 1, @d) WITH NOWAIT; SELECT df.check_id, df.database_name, df.object_name, df.finding_group, df.finding @@ -28415,7 +28431,11 @@ ELSE --Output to database is not set output to client app SELECT '#deadlock_stack' AS table_name, * FROM #deadlock_stack AS ds OPTION ( RECOMPILE ); - + + SELECT '#deadlock_results' AS table_name, * + FROM #deadlock_results AS dr + OPTION ( RECOMPILE ); + END; -- End debug END; --Final End @@ -28479,7 +28499,7 @@ BEGIN /*First BEGIN*/ SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.01', @VersionDate = '20210222'; +SELECT @Version = '8.02', @VersionDate = '20210321'; IF(@VersionCheckMode = 1) BEGIN RETURN; @@ -32938,7 +32958,7 @@ BEGIN 100, 'Execution Pattern', 'Frequently Executed Queries', - 'http://brentozar.com/blitzcache/frequently-executed-queries/', + 'https://www.brentozar.com/blitzcache/frequently-executed-queries/', 'Queries are being executed more than ' + CAST (@execution_threshold AS VARCHAR(5)) + ' times per minute. This can put additional load on the server, even when queries are lightweight.') ; @@ -32953,7 +32973,7 @@ BEGIN 50, 'Parameterization', 'Parameter Sniffing', - 'http://brentozar.com/blitzcache/parameter-sniffing/', + 'https://www.brentozar.com/blitzcache/parameter-sniffing/', 'There are signs of parameter sniffing (wide variance in rows return or time to execute). Investigate query patterns and tune code appropriately.') ; /* Forced execution plans */ @@ -32967,7 +32987,7 @@ BEGIN 5, 'Parameterization', 'Forced Plans', - 'http://brentozar.com/blitzcache/forced-plans/', + 'https://www.brentozar.com/blitzcache/forced-plans/', 'Execution plans have been compiled with forced plans, either through FORCEPLAN, plan guides, or forced parameterization. This will make general tuning efforts less effective.'); IF EXISTS (SELECT 1/0 @@ -32980,7 +33000,7 @@ BEGIN 200, 'Cursors', 'Cursors', - 'http://brentozar.com/blitzcache/cursors-found-slow-queries/', + 'https://www.brentozar.com/blitzcache/cursors-found-slow-queries/', 'There are cursors in the plan cache. This is neither good nor bad, but it is a thing. Cursors are weird in SQL Server.'); IF EXISTS (SELECT 1/0 @@ -32994,7 +33014,7 @@ BEGIN 200, 'Cursors', 'Optimistic Cursors', - 'http://brentozar.com/blitzcache/cursors-found-slow-queries/', + 'https://www.brentozar.com/blitzcache/cursors-found-slow-queries/', 'There are optimistic cursors in the plan cache, which can harm performance.'); IF EXISTS (SELECT 1/0 @@ -33008,7 +33028,7 @@ BEGIN 200, 'Cursors', 'Non-forward Only Cursors', - 'http://brentozar.com/blitzcache/cursors-found-slow-queries/', + 'https://www.brentozar.com/blitzcache/cursors-found-slow-queries/', 'There are non-forward only cursors in the plan cache, which can harm performance.'); IF EXISTS (SELECT 1/0 @@ -33021,7 +33041,7 @@ BEGIN 200, 'Cursors', 'Dynamic Cursors', - 'http://brentozar.com/blitzcache/cursors-found-slow-queries/', + 'https://www.brentozar.com/blitzcache/cursors-found-slow-queries/', 'Dynamic Cursors inhibit parallelism!.'); IF EXISTS (SELECT 1/0 @@ -33034,7 +33054,7 @@ BEGIN 200, 'Cursors', 'Fast Forward Cursors', - 'http://brentozar.com/blitzcache/cursors-found-slow-queries/', + 'https://www.brentozar.com/blitzcache/cursors-found-slow-queries/', 'Fast forward cursors inhibit parallelism!.'); IF EXISTS (SELECT 1/0 @@ -33047,7 +33067,7 @@ BEGIN 50, 'Parameterization', 'Forced Parameterization', - 'http://brentozar.com/blitzcache/forced-parameterization/', + 'https://www.brentozar.com/blitzcache/forced-parameterization/', 'Execution plans have been compiled with forced parameterization.') ; IF EXISTS (SELECT 1/0 @@ -33060,7 +33080,7 @@ BEGIN 200, 'Execution Plans', 'Parallelism', - 'http://brentozar.com/blitzcache/parallel-plans-detected/', + 'https://www.brentozar.com/blitzcache/parallel-plans-detected/', 'Parallel plans detected. These warrant investigation, but are neither good nor bad.') ; IF EXISTS (SELECT 1/0 @@ -33073,7 +33093,7 @@ BEGIN 200, 'Execution Plans', 'Nearly Parallel', - 'http://brentozar.com/blitzcache/query-cost-near-cost-threshold-parallelism/', + 'https://www.brentozar.com/blitzcache/query-cost-near-cost-threshold-parallelism/', 'Queries near the cost threshold for parallelism. These may go parallel when you least expect it.') ; IF EXISTS (SELECT 1/0 @@ -33086,7 +33106,7 @@ BEGIN 50, 'Execution Plans', 'Query Plan Warnings', - 'http://brentozar.com/blitzcache/query-plan-warnings/', + 'https://www.brentozar.com/blitzcache/query-plan-warnings/', 'Warnings detected in execution plans. SQL Server is telling you that something bad is going on that requires your attention.') ; IF EXISTS (SELECT 1/0 @@ -33099,7 +33119,7 @@ BEGIN 50, 'Performance', 'Long Running Queries', - 'http://brentozar.com/blitzcache/long-running-queries/', + 'https://www.brentozar.com/blitzcache/long-running-queries/', 'Long running queries have been found. These are queries with an average duration longer than ' + CAST(@long_running_query_warning_seconds / 1000 / 1000 AS VARCHAR(5)) + ' second(s). These queries should be investigated for additional tuning options.') ; @@ -33114,7 +33134,7 @@ BEGIN 50, 'Performance', 'Missing Index Request', - 'http://brentozar.com/blitzcache/missing-index-request/', + 'https://www.brentozar.com/blitzcache/missing-index-request/', 'Queries found with missing indexes.'); IF EXISTS (SELECT 1/0 @@ -33127,7 +33147,7 @@ BEGIN 200, 'Cardinality', 'Legacy Cardinality Estimator in Use', - 'http://brentozar.com/blitzcache/legacy-cardinality-estimator/', + 'https://www.brentozar.com/blitzcache/legacy-cardinality-estimator/', 'A legacy cardinality estimator is being used by one or more queries. Investigate whether you need to be using this cardinality estimator. This may be caused by compatibility levels, global trace flags, or query level trace flags.'); IF EXISTS (SELECT 1/0 @@ -33140,7 +33160,7 @@ BEGIN 50, 'Performance', 'Implicit Conversions', - 'http://brentozar.com/go/implicit', + 'https://www.brentozar.com/go/implicit', 'One or more queries are comparing two fields that are not of the same data type.') ; IF EXISTS (SELECT 1/0 @@ -33153,7 +33173,7 @@ BEGIN 100, 'Performance', 'Busy Loops', - 'http://brentozar.com/blitzcache/busy-loops/', + 'https://www.brentozar.com/blitzcache/busy-loops/', 'Operations have been found that are executed 100 times more often than the number of rows returned by each iteration. This is an indicator that something is off in query execution.'); IF EXISTS (SELECT 1/0 @@ -33166,7 +33186,7 @@ BEGIN 50, 'Performance', 'Joining to table valued functions', - 'http://brentozar.com/blitzcache/tvf-join/', + 'https://www.brentozar.com/blitzcache/tvf-join/', 'Execution plans have been found that join to table valued functions (TVFs). TVFs produce inaccurate estimates of the number of rows returned and can lead to any number of query plan problems.'); IF EXISTS (SELECT 1/0 @@ -33179,7 +33199,7 @@ BEGIN 50, 'Execution Plans', 'Compilation timeout', - 'http://brentozar.com/blitzcache/compilation-timeout/', + 'https://www.brentozar.com/blitzcache/compilation-timeout/', 'Query compilation timed out for one or more queries. SQL Server did not find a plan that meets acceptable performance criteria in the time allotted so the best guess was returned. There is a very good chance that this plan isn''t even below average - it''s probably terrible.'); IF EXISTS (SELECT 1/0 @@ -33192,7 +33212,7 @@ BEGIN 50, 'Execution Plans', 'Compilation memory limit exceeded', - 'http://brentozar.com/blitzcache/compile-memory-limit-exceeded/', + 'https://www.brentozar.com/blitzcache/compile-memory-limit-exceeded/', 'The optimizer has a limited amount of memory available. One or more queries are complex enough that SQL Server was unable to allocate enough memory to fully optimize the query. A best fit plan was found, and it''s probably terrible.'); IF EXISTS (SELECT 1/0 @@ -33205,7 +33225,7 @@ BEGIN 10, 'Execution Plans', 'No join predicate', - 'http://brentozar.com/blitzcache/no-join-predicate/', + 'https://www.brentozar.com/blitzcache/no-join-predicate/', 'Operators in a query have no join predicate. This means that all rows from one table will be matched with all rows from anther table producing a Cartesian product. That''s a whole lot of rows. This may be your goal, but it''s important to investigate why this is happening.'); IF EXISTS (SELECT 1/0 @@ -33218,7 +33238,7 @@ BEGIN 200, 'Execution Plans', 'Multiple execution plans', - 'http://brentozar.com/blitzcache/multiple-plans/', + 'https://www.brentozar.com/blitzcache/multiple-plans/', 'Queries exist with multiple execution plans (as determined by query_plan_hash). Investigate possible ways to parameterize these queries or otherwise reduce the plan count.'); IF EXISTS (SELECT 1/0 @@ -33231,7 +33251,7 @@ BEGIN 100, 'Performance', 'Unmatched indexes', - 'http://brentozar.com/blitzcache/unmatched-indexes', + 'https://www.brentozar.com/blitzcache/unmatched-indexes', 'An index could have been used, but SQL Server chose not to use it - likely due to parameterization and filtered indexes.'); IF EXISTS (SELECT 1/0 @@ -33244,7 +33264,7 @@ BEGIN 100, 'Parameterization', 'Unparameterized queries', - 'http://brentozar.com/blitzcache/unparameterized-queries', + 'https://www.brentozar.com/blitzcache/unparameterized-queries', 'Unparameterized queries found. These could be ad hoc queries, data exploration, or queries using "OPTIMIZE FOR UNKNOWN".'); IF EXISTS (SELECT 1/0 @@ -33257,7 +33277,7 @@ BEGIN 100, 'Execution Plans', 'Trivial Plans', - 'http://brentozar.com/blitzcache/trivial-plans', + 'https://www.brentozar.com/blitzcache/trivial-plans', 'Trivial plans get almost no optimization. If you''re finding these in the top worst queries, something may be going wrong.'); IF EXISTS (SELECT 1/0 @@ -33270,7 +33290,7 @@ BEGIN 10, 'Execution Plans', 'Forced Serialization', - 'http://www.brentozar.com/blitzcache/forced-serialization/', + 'https://www.brentozar.com/blitzcache/forced-serialization/', 'Something in your plan is forcing a serial query. Further investigation is needed if this is not by design.') ; IF EXISTS (SELECT 1/0 @@ -33283,7 +33303,7 @@ BEGIN 100, 'Execution Plans', 'Expensive Key Lookups', - 'http://www.brentozar.com/blitzcache/expensive-key-lookups/', + 'https://www.brentozar.com/blitzcache/expensive-key-lookups/', 'There''s a key lookup in your plan that costs >=50% of the total plan cost.') ; IF EXISTS (SELECT 1/0 @@ -33296,7 +33316,7 @@ BEGIN 100, 'Execution Plans', 'Expensive Remote Query', - 'http://www.brentozar.com/blitzcache/expensive-remote-query/', + 'https://www.brentozar.com/blitzcache/expensive-remote-query/', 'There''s a remote query in your plan that costs >=50% of the total plan cost.') ; IF EXISTS (SELECT 1/0 @@ -33388,7 +33408,7 @@ BEGIN 100, 'Operator Warnings', 'SQL is throwing operator level plan warnings', - 'http://brentozar.com/blitzcache/query-plan-warnings/', + 'https://www.brentozar.com/blitzcache/query-plan-warnings/', 'Check the plan for more details.') ; IF EXISTS (SELECT 1/0 @@ -33480,7 +33500,7 @@ BEGIN 100, 'Execution Plans', 'Expensive Sort', - 'http://www.brentozar.com/blitzcache/expensive-sorts/', + 'https://www.brentozar.com/blitzcache/expensive-sorts/', 'There''s a sort in your plan that costs >=50% of the total plan cost.') ; IF EXISTS (SELECT 1/0 @@ -33700,7 +33720,7 @@ BEGIN 100, 'MSTVFs', 'These have many of the same problems scalar UDFs have', - 'http://brentozar.com/blitzcache/tvf-join/', + 'https://www.brentozar.com/blitzcache/tvf-join/', 'Execution plans have been found that join to table valued functions (TVFs). TVFs produce inaccurate estimates of the number of rows returned and can lead to any number of query plan problems.'); IF EXISTS (SELECT 1/0 @@ -34206,7 +34226,7 @@ BEGIN SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.01', @VersionDate = '20210222'; + SELECT @Version = '8.02', @VersionDate = '20210321'; IF(@VersionCheckMode = 1) BEGIN @@ -34621,18 +34641,18 @@ IF @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @Output + N' [request_cpu_time], ' + @LineFeed + N' [degree_of_parallelism], ' + @LineFeed + N' [request_logical_reads], ' + @LineFeed - + N' ((CAST([request_logical_reads] AS MONEY)* 8)/ 1024) [Logical_Reads_MB], ' + @LineFeed + + N' ((CAST([request_logical_reads] AS DECIMAL(38,2))* 8)/ 1024) [Logical_Reads_MB], ' + @LineFeed + N' [request_writes], ' + @LineFeed - + N' ((CAST([request_writes] AS MONEY)* 8)/ 1024) [Logical_Writes_MB], ' + @LineFeed + + N' ((CAST([request_writes] AS DECIMAL(38,2))* 8)/ 1024) [Logical_Writes_MB], ' + @LineFeed + N' [request_physical_reads], ' + @LineFeed - + N' ((CAST([request_physical_reads] AS MONEY)* 8)/ 1024) [Physical_reads_MB], ' + @LineFeed + + N' ((CAST([request_physical_reads] AS DECIMAL(38,2))* 8)/ 1024) [Physical_reads_MB], ' + @LineFeed + N' [session_cpu], ' + @LineFeed + N' [session_logical_reads], ' + @LineFeed - + N' ((CAST([session_logical_reads] AS MONEY)* 8)/ 1024) [session_logical_reads_MB], ' + @LineFeed + + N' ((CAST([session_logical_reads] AS DECIMAL(38,2))* 8)/ 1024) [session_logical_reads_MB], ' + @LineFeed + N' [session_physical_reads], ' + @LineFeed - + N' ((CAST([session_physical_reads] AS MONEY)* 8)/ 1024) [session_physical_reads_MB], ' + @LineFeed + + N' ((CAST([session_physical_reads] AS DECIMAL(38,2))* 8)/ 1024) [session_physical_reads_MB], ' + @LineFeed + N' [session_writes], ' + @LineFeed - + N' ((CAST([session_writes] AS MONEY)* 8)/ 1024) [session_writes_MB], ' + @LineFeed + + N' ((CAST([session_writes] AS DECIMAL(38,2))* 8)/ 1024) [session_writes_MB], ' + @LineFeed + N' [tempdb_allocations_mb], ' + @LineFeed + N' [memory_usage], ' + @LineFeed + N' [estimated_completion_time], ' + @LineFeed @@ -35417,6 +35437,7 @@ ALTER PROCEDURE [dbo].[sp_DatabaseRestore] @MoveDataDrive NVARCHAR(260) = NULL, @MoveLogDrive NVARCHAR(260) = NULL, @MoveFilestreamDrive NVARCHAR(260) = NULL, + @MoveFullTextCatalogDrive NVARCHAR(260) = NULL, @BufferCount INT = NULL, @MaxTransferSize INT = NULL, @BlockSize INT = NULL, @@ -35445,7 +35466,7 @@ SET NOCOUNT ON; /*Versioning details*/ -SELECT @Version = '8.01', @VersionDate = '20210222'; +SELECT @Version = '8.02', @VersionDate = '20210321'; IF(@VersionCheckMode = 1) BEGIN @@ -35831,6 +35852,22 @@ BEGIN IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Fixing @MoveFilestreamDrive to add a "/"', 0, 1) WITH NOWAIT; SET @MoveFilestreamDrive += N'/'; END; +/*Move FullText Catalog File*/ +IF NULLIF(@MoveFullTextCatalogDrive, '') IS NULL +BEGIN + IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Setting default data drive for @MoveFullTextCatalogDrive', 0, 1) WITH NOWAIT; + SET @MoveFullTextCatalogDrive = CAST(SERVERPROPERTY('InstanceDefaultDataPath') AS nvarchar(260)); +END; +IF (SELECT RIGHT(@MoveFullTextCatalogDrive, 1)) <> '\' AND CHARINDEX('\', @MoveFullTextCatalogDrive) > 0 --Has to end in a '\' +BEGIN + IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Fixing @MoveFullTextCatalogDrive to add a "\"', 0, 1) WITH NOWAIT; + SET @MoveFullTextCatalogDrive += N'\'; +END; +ELSE IF (SELECT RIGHT(@MoveFullTextCatalogDrive, 1)) <> '/' AND CHARINDEX('/', @MoveFullTextCatalogDrive) > 0 --Has to end in a '/' +BEGIN + IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Fixing @MoveFullTextCatalogDrive to add a "/"', 0, 1) WITH NOWAIT; + SET @MoveFullTextCatalogDrive += N'/'; +END; /*Standby Undo File*/ IF (SELECT RIGHT(@StandbyUndoPath, 1)) <> '\' AND CHARINDEX('\', @StandbyUndoPath) > 0 --Has to end in a '\' BEGIN @@ -36133,6 +36170,7 @@ BEGIN WHEN Type = 'D' THEN @MoveDataDrive WHEN Type = 'L' THEN @MoveLogDrive WHEN Type = 'S' THEN @MoveFilestreamDrive + WHEN Type = 'F' THEN @MoveFullTextCatalogDrive END + CASE WHEN @Database = @RestoreDatabaseName THEN REVERSE(LEFT(REVERSE(PhysicalName), CHARINDEX('\', REVERSE(PhysicalName), 1) -1)) ELSE REPLACE(REVERSE(LEFT(REVERSE(PhysicalName), CHARINDEX('\', REVERSE(PhysicalName), 1) -1)), @Database, SUBSTRING(@RestoreDatabaseName, 2, LEN(@RestoreDatabaseName) -2)) @@ -36914,7 +36952,7 @@ AS BEGIN SET NOCOUNT ON; - SELECT @Version = '8.01', @VersionDate = '20210222'; + SELECT @Version = '8.02', @VersionDate = '20210321'; IF(@VersionCheckMode = 1) BEGIN @@ -37272,6 +37310,7 @@ VALUES (15, 4003, 'CU1', 'https://support.microsoft.com/en-us/help/4527376', '2020-01-07', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 1 '), (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 '), + (14, 3356, 'RTM CU23', 'https://support.microsoft.com/en-us/help/5000685', '2021-02-25', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 23'), (14, 3370, 'RTM CU22 GDR', 'https://support.microsoft.com/en-us/help/4583457', '2021-01-12', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 22 GDR'), (14, 3356, 'RTM CU22', 'https://support.microsoft.com/en-us/help/4577467', '2020-09-10', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 22'), (14, 3335, 'RTM CU21', 'https://support.microsoft.com/en-us/help/4557397', '2020-07-01', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 21'), @@ -37647,7 +37686,7 @@ BEGIN SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.01', @VersionDate = '20210222'; +SELECT @Version = '8.02', @VersionDate = '20210321'; IF(@VersionCheckMode = 1) BEGIN @@ -38897,35 +38936,42 @@ BEGIN /* Populate #FileStats, #PerfmonStats, #WaitStats with DMV data. After we finish doing our checks, we'll take another sample and compare them. */ RAISERROR('Capturing first pass of wait stats, perfmon counters, file stats',10,1) WITH NOWAIT; - INSERT #WaitStats(Pass, SampleTime, wait_type, wait_time_ms, signal_wait_time_ms, waiting_tasks_count) - SELECT - x.Pass, - x.SampleTime, - x.wait_type, - SUM(x.sum_wait_time_ms) AS sum_wait_time_ms, - SUM(x.sum_signal_wait_time_ms) AS sum_signal_wait_time_ms, - SUM(x.sum_waiting_tasks) AS sum_waiting_tasks - FROM ( - SELECT - 1 AS Pass, - CASE @Seconds WHEN 0 THEN @StartSampleTime ELSE SYSDATETIMEOFFSET() END AS SampleTime, - owt.wait_type, - CASE @Seconds WHEN 0 THEN 0 ELSE SUM(owt.wait_duration_ms) OVER (PARTITION BY owt.wait_type, owt.session_id) - - CASE WHEN @Seconds = 0 THEN 0 ELSE (@Seconds * 1000) END END AS sum_wait_time_ms, - 0 AS sum_signal_wait_time_ms, - 0 AS sum_waiting_tasks - FROM sys.dm_os_waiting_tasks owt - WHERE owt.session_id > 50 - AND owt.wait_duration_ms >= CASE @Seconds WHEN 0 THEN 0 ELSE @Seconds * 1000 END - UNION ALL - SELECT - 1 AS Pass, - CASE @Seconds WHEN 0 THEN @StartSampleTime ELSE SYSDATETIMEOFFSET() END AS SampleTime, - os.wait_type, - CASE @Seconds WHEN 0 THEN 0 ELSE SUM(os.wait_time_ms) OVER (PARTITION BY os.wait_type) END AS sum_wait_time_ms, - CASE @Seconds WHEN 0 THEN 0 ELSE SUM(os.signal_wait_time_ms) OVER (PARTITION BY os.wait_type ) END AS sum_signal_wait_time_ms, - CASE @Seconds WHEN 0 THEN 0 ELSE SUM(os.waiting_tasks_count) OVER (PARTITION BY os.wait_type) END AS sum_waiting_tasks - FROM sys.dm_os_wait_stats os + SET @StringToExecute = N' + INSERT #WaitStats(Pass, SampleTime, wait_type, wait_time_ms, signal_wait_time_ms, waiting_tasks_count) + SELECT + x.Pass, + x.SampleTime, + x.wait_type, + SUM(x.sum_wait_time_ms) AS sum_wait_time_ms, + SUM(x.sum_signal_wait_time_ms) AS sum_signal_wait_time_ms, + SUM(x.sum_waiting_tasks) AS sum_waiting_tasks + FROM ( + SELECT + 1 AS Pass, + CASE @Seconds WHEN 0 THEN @StartSampleTime ELSE SYSDATETIMEOFFSET() END AS SampleTime, + owt.wait_type, + CASE @Seconds WHEN 0 THEN 0 ELSE SUM(owt.wait_duration_ms) OVER (PARTITION BY owt.wait_type, owt.session_id) + - CASE WHEN @Seconds = 0 THEN 0 ELSE (@Seconds * 1000) END END AS sum_wait_time_ms, + 0 AS sum_signal_wait_time_ms, + 0 AS sum_waiting_tasks + FROM sys.dm_os_waiting_tasks owt + WHERE owt.session_id > 50 + AND owt.wait_duration_ms >= CASE @Seconds WHEN 0 THEN 0 ELSE @Seconds * 1000 END + UNION ALL + SELECT + 1 AS Pass, + CASE @Seconds WHEN 0 THEN @StartSampleTime ELSE SYSDATETIMEOFFSET() END AS SampleTime, + os.wait_type, + CASE @Seconds WHEN 0 THEN 0 ELSE SUM(os.wait_time_ms) OVER (PARTITION BY os.wait_type) END AS sum_wait_time_ms, + CASE @Seconds WHEN 0 THEN 0 ELSE SUM(os.signal_wait_time_ms) OVER (PARTITION BY os.wait_type ) END AS sum_signal_wait_time_ms, + CASE @Seconds WHEN 0 THEN 0 ELSE SUM(os.waiting_tasks_count) OVER (PARTITION BY os.wait_type) END AS sum_waiting_tasks '; + + IF SERVERPROPERTY('Edition') = 'SQL Azure' + SET @StringToExecute = @StringToExecute + N' FROM sys.dm_db_wait_stats os '; + ELSE + SET @StringToExecute = @StringToExecute + N' FROM sys.dm_os_wait_stats os '; + + SET @StringToExecute = @StringToExecute + N' ) x WHERE NOT EXISTS ( @@ -38935,7 +38981,8 @@ BEGIN AND wc.Ignorable = 1 ) GROUP BY x.Pass, x.SampleTime, x.wait_type - ORDER BY sum_wait_time_ms DESC; + ORDER BY sum_wait_time_ms DESC;' + EXEC sp_executesql @StringToExecute, N'@StartSampleTime DATETIMEOFFSET, @Seconds INT', @StartSampleTime, @Seconds; INSERT INTO #FileStats (Pass, SampleTime, DatabaseID, FileID, DatabaseName, FileLogicalName, SizeOnDiskMB, io_stall_read_ms , @@ -39008,7 +39055,7 @@ BEGIN 1 AS Priority, 'Maintenance Tasks Running' AS FindingGroup, 'Backup Running' AS Finding, - 'http://www.BrentOzar.com/askbrent/backups/' AS URL, + 'https://www.brentozar.com/askbrent/backups/' AS URL, 'Backup of ' + DB_NAME(db.resource_database_id) + ' database (' + (SELECT CAST(CAST(SUM(size * 8.0 / 1024 / 1024) AS BIGINT) AS NVARCHAR) FROM #MasterFiles WHERE database_id = db.resource_database_id) + 'GB) ' + @LineFeed + CAST(r.percent_complete AS NVARCHAR(100)) + '% complete, has been running since ' + CAST(r.start_time AS NVARCHAR(100)) + '. ' + @LineFeed + CASE WHEN COALESCE(s.nt_user_name, s.login_name) IS NOT NULL THEN (' Login: ' + COALESCE(s.nt_user_name, s.login_name) + ' ') ELSE '' END AS Details, @@ -39060,7 +39107,7 @@ BEGIN 1 AS Priority, 'Maintenance Tasks Running' AS FindingGroup, 'DBCC CHECK* Running' AS Finding, - 'http://www.BrentOzar.com/askbrent/dbcc/' AS URL, + 'https://www.brentozar.com/askbrent/dbcc/' AS URL, 'Corruption check of ' + DB_NAME(db.resource_database_id) + ' database (' + (SELECT CAST(CAST(SUM(size * 8.0 / 1024 / 1024) AS BIGINT) AS NVARCHAR) FROM #MasterFiles WHERE database_id = db.resource_database_id) + 'GB) 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, @@ -39105,7 +39152,7 @@ BEGIN 1 AS Priority, 'Maintenance Tasks Running' AS FindingGroup, 'Restore Running' AS Finding, - 'http://www.BrentOzar.com/askbrent/backups/' AS URL, + 'https://www.brentozar.com/askbrent/backups/' AS URL, 'Restore of ' + DB_NAME(db.resource_database_id) + ' database (' + (SELECT CAST(CAST(SUM(size * 8.0 / 1024 / 1024) AS BIGINT) AS NVARCHAR) FROM #MasterFiles WHERE database_id = db.resource_database_id) + '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, @@ -39146,9 +39193,9 @@ BEGIN 1 AS Priority, 'SQL Server Internal Maintenance' AS FindingGroup, 'Database File Growing' AS Finding, - 'http://www.BrentOzar.com/go/instant' AS URL, + 'https://www.brentozar.com/go/instant' AS URL, 'SQL Server is waiting for Windows to provide storage space for a database restore, a data file growth, or a log file growth. This task has been running since ' + CAST(r.start_time AS NVARCHAR(100)) + '.' + @LineFeed + 'Check the query plan (expert mode) to identify the database involved.' AS Details, - 'Unfortunately, you can''t stop this, but you can prevent it next time. Check out http://www.BrentOzar.com/go/instant for details.' AS HowToStopIt, + 'Unfortunately, you can''t stop this, but you can prevent it next time. Check out https://www.brentozar.com/go/instant for details.' AS HowToStopIt, pl.query_plan AS QueryPlan, r.start_time AS StartTime, s.login_name AS LoginName, @@ -39180,7 +39227,7 @@ BEGIN 1 AS Priority, ''Query Problems'' AS FindingGroup, ''Long-Running Query Blocking Others'' AS Finding, - ''http://www.BrentOzar.com/go/blocking'' AS URL, + ''https://www.brentozar.com/go/blocking'' AS URL, ''Query in '' + COALESCE(DB_NAME(COALESCE((SELECT TOP 1 dbid FROM sys.dm_exec_sql_text(r.sql_handle)), (SELECT TOP 1 t.dbid FROM master..sysprocesses spBlocker CROSS APPLY sys.dm_exec_sql_text(spBlocker.sql_handle) t WHERE spBlocker.spid = tBlocked.blocking_session_id))), ''(Unknown)'') + '' has a last request start time of '' + CAST(s.last_request_start_time AS NVARCHAR(100)) + ''. Query follows: ' + @LineFeed + @LineFeed + @@ -39223,7 +39270,7 @@ BEGIN 50 AS Priority, 'Query Problems' AS FindingGroup, 'Plan Cache Erased Recently' AS Finding, - 'http://www.BrentOzar.com/askbrent/plan-cache-erased-recently/' AS URL, + 'https://www.brentozar.com/askbrent/plan-cache-erased-recently/' AS URL, 'The oldest query in the plan cache was created at ' + CAST(creation_time AS NVARCHAR(50)) + '. ' + @LineFeed + @LineFeed + 'This indicates that someone ran DBCC FREEPROCCACHE at that time,' + @LineFeed + 'Giving SQL Server temporary amnesia. Now, as queries come in,' + @LineFeed @@ -39248,7 +39295,7 @@ BEGIN 50 AS Priority, 'Query Problems' AS FindingGroup, 'Sleeping Query with Open Transactions' AS Finding, - 'http://www.brentozar.com/askbrent/sleeping-query-with-open-transactions/' AS URL, + 'https://www.brentozar.com/askbrent/sleeping-query-with-open-transactions/' AS URL, 'Database: ' + DB_NAME(db.resource_database_id) + @LineFeed + 'Host: ' + s.[host_name] + @LineFeed + 'Program: ' + s.[program_name] + @LineFeed + 'Asleep with open transactions and locks since ' + CAST(s.last_request_end_time AS NVARCHAR(100)) + '. ' AS Details, 'KILL ' + CAST(s.session_id AS NVARCHAR(100)) + ';' AS HowToStopIt, s.last_request_start_time AS StartTime, @@ -39334,7 +39381,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 1 AS Priority, 'Query Problems' AS FindingGroup, 'Query Rolling Back' AS Finding, - 'http://www.BrentOzar.com/askbrent/rollback/' AS URL, + 'https://www.brentozar.com/askbrent/rollback/' AS URL, 'Rollback started at ' + CAST(r.start_time AS NVARCHAR(100)) + ', is ' + CAST(r.percent_complete AS NVARCHAR(100)) + '% complete.' AS Details, 'Unfortunately, you can''t stop this. Whatever you do, don''t restart the server in an attempt to fix it - SQL Server will keep rolling back.' AS HowToStopIt, r.start_time AS StartTime, @@ -39415,7 +39462,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 50 AS Priority, 'Server Performance' AS FindingGroup, 'Too Much Free Memory' AS Finding, - 'https://BrentOzar.com/go/freememory' AS URL, + 'https://www.brentozar.com/go/freememory' AS URL, CAST((CAST(cFree.cntr_value AS BIGINT) / 1024 / 1024 ) AS NVARCHAR(100)) + N'GB of free memory inside SQL Server''s buffer pool,' + @LineFeed + ' which is ' + CAST((CAST(cTotal.cntr_value AS BIGINT) / 1024 / 1024) AS NVARCHAR(100)) + N'GB. You would think lots of free memory would be good, but check out the URL for more information.' AS Details, 'Run sp_BlitzCache @SortOrder = ''memory grant'' to find queries with huge memory grants and tune them.' AS HowToStopIt FROM sys.dm_os_performance_counters cFree @@ -39440,7 +39487,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 10 AS Priority, 'Server Performance' AS FindingGroup, 'Target Memory Lower Than Max' AS Finding, - 'https://BrentOzar.com/go/target' AS URL, + 'https://www.brentozar.com/go/target' AS URL, N'Max server memory is ' + CAST(cMax.value_in_use AS NVARCHAR(50)) + N' MB but target server memory is only ' + CAST((CAST(cTarget.cntr_value AS BIGINT) / 1024) AS NVARCHAR(50)) + N' MB,' + @LineFeed + N'indicating that SQL Server may be under external memory pressure or max server memory may be set too high.' AS Details, 'Investigate what OS processes are using memory, and double-check the max server memory setting.' AS HowToStopIt @@ -39466,7 +39513,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 'Database Size, Total GB' AS Finding, CAST(SUM (CAST(size AS BIGINT)*8./1024./1024.) AS VARCHAR(100)) AS Details, SUM (CAST(size AS BIGINT))*8./1024./1024. AS DetailsInt, - 'http://www.BrentOzar.com/askbrent/' AS URL + 'https://www.brentozar.com/askbrent/' AS URL FROM #MasterFiles WHERE database_id > 4; @@ -39483,7 +39530,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 'Database Count' AS Finding, CAST(SUM(1) AS VARCHAR(100)) AS Details, SUM (1) AS DetailsInt, - 'http://www.BrentOzar.com/askbrent/' AS URL + 'https://www.brentozar.com/askbrent/' AS URL FROM sys.databases WHERE database_id > 4; @@ -39538,7 +39585,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, / CAST(@MaxWorkspace AS MONEY)) * 100, 0) AS NVARCHAR(50)) + '%' + @LineFeed + 'Oldest Grant in seconds: ' + CAST(ISNULL(DATEDIFF(SECOND, MIN(Grants.request_time), GETDATE()), 0) AS NVARCHAR(50)) AS Details, (SELECT COUNT(*) FROM sys.dm_exec_query_memory_grants WHERE queue_id IS NULL) AS DetailsInt, - 'http://www.BrentOzar.com/askbrent/' AS URL + 'https://www.brentozar.com/askbrent/' AS URL FROM sys.dm_exec_query_memory_grants AS Grants; /* Query Problems - Queries with high memory grants - CheckID 46 */ @@ -39748,7 +39795,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 100 AS Priority, ''Query Performance'' AS FindingsGroup, ''Queries with 10000x cardinality misestimations'' AS Findings, - ''https://brentozar.com/go/skewedup'' AS URL, + ''https://www.brentozar.com/go/skewedup'' AS URL, ''The query on SPID '' + RTRIM(b.session_id) + '' has been running for '' @@ -39799,7 +39846,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 100 AS Priority, ''Query Performance'' AS FindingsGroup, ''Queries with 10000x skewed parallelism'' AS Findings, - ''https://brentozar.com/go/skewedup'' AS URL, + ''https://www.brentozar.com/go/skewedup'' AS URL, ''The query on SPID '' + RTRIM(p.session_id) + '' has been running for '' @@ -39857,7 +39904,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, END INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) - SELECT 24, 50, 'Server Performance', 'High CPU Utilization', CAST(100 - SystemIdle AS NVARCHAR(20)) + N'%.', 100 - SystemIdle, 'http://www.BrentOzar.com/go/cpu' + SELECT 24, 50, 'Server Performance', 'High CPU Utilization', CAST(100 - SystemIdle AS NVARCHAR(20)) + N'%.', 100 - SystemIdle, 'https://www.brentozar.com/go/cpu' FROM ( SELECT record, record.value('(./Record/SchedulerMonitorEvent/SystemHealth/SystemIdle)[1]', 'int') AS SystemIdle @@ -39901,7 +39948,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 'CPU Utilization', y.system_idle + N'%. Ring buffer details: ' + CAST(y.record AS NVARCHAR(4000)), y.system_idle , - 'http://www.BrentOzar.com/go/cpu', + 'https://www.brentozar.com/go/cpu', STUFF(( SELECT TOP 2147483647 CHAR(10) + CHAR(13) + y2.system_idle @@ -39923,7 +39970,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, END INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) - SELECT 28, 50, 'Server Performance', 'High CPU Utilization - Not SQL', CONVERT(NVARCHAR(100),100 - (y.SQLUsage + y.SystemIdle)) + N'% - Other Processes (not SQL Server) are using this much CPU. This may impact on the performance of your SQL Server instance', 100 - (y.SQLUsage + y.SystemIdle), 'http://www.BrentOzar.com/go/cpu' + SELECT 28, 50, 'Server Performance', 'High CPU Utilization - Not SQL', CONVERT(NVARCHAR(100),100 - (y.SQLUsage + y.SystemIdle)) + N'% - Other Processes (not SQL Server) are using this much CPU. This may impact on the performance of your SQL Server instance', 100 - (y.SQLUsage + y.SystemIdle), 'https://www.brentozar.com/go/cpu' FROM ( SELECT record, record.value('(./Record/SchedulerMonitorEvent/SystemHealth/SystemIdle)[1]', 'int') AS SystemIdle @@ -39946,6 +39993,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, END IF 20 >= (SELECT COUNT(*) FROM sys.databases WHERE name NOT IN ('master', 'model', 'msdb', 'tempdb')) + AND @Seconds > 0 BEGIN CREATE TABLE #UpdatedStats (HowToStopIt NVARCHAR(4000), RowsForSorting BIGINT); IF EXISTS(SELECT * FROM sys.all_objects WHERE name = 'dm_db_stats_properties') @@ -39953,49 +40001,59 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, /* We don't want to hang around to obtain locks */ SET LOCK_TIMEOUT 0; - EXEC sp_MSforeachdb N'USE [?]; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; SET LOCK_TIMEOUT 1000; - BEGIN TRY - INSERT INTO #UpdatedStats(HowToStopIt, RowsForSorting) - SELECT HowToStopIt = - QUOTENAME(DB_NAME()) + N''.'' + - QUOTENAME(SCHEMA_NAME(obj.schema_id)) + N''.'' + - QUOTENAME(obj.name) + - N'' statistic '' + QUOTENAME(stat.name) + - N'' was updated on '' + CONVERT(NVARCHAR(50), sp.last_updated, 121) + N'','' + - N'' had '' + CAST(sp.rows AS NVARCHAR(50)) + N'' rows, with '' + - CAST(sp.rows_sampled AS NVARCHAR(50)) + N'' rows sampled,'' + - N'' producing '' + CAST(sp.steps AS NVARCHAR(50)) + N'' steps in the histogram.'', - sp.rows - FROM sys.objects AS obj WITH (NOLOCK) - INNER JOIN sys.stats AS stat WITH (NOLOCK) ON stat.object_id = obj.object_id - CROSS APPLY sys.dm_db_stats_properties(stat.object_id, stat.stats_id) AS sp - WHERE sp.last_updated > DATEADD(MI, -15, GETDATE()) - AND obj.is_ms_shipped = 0 - AND ''[?]'' <> ''[tempdb]''; - END TRY - BEGIN CATCH - IF (ERROR_NUMBER() = 1222) - BEGIN - INSERT INTO #UpdatedStats(HowToStopIt, RowsForSorting) - SELECT HowToStopIt = - QUOTENAME(DB_NAME()) + - N'' No information could be retrieved as the lock timeout was exceeded,''+ - N'' this is likely due to an Index operation in Progress'', - -1 - END - ELSE - BEGIN - INSERT INTO #UpdatedStats(HowToStopIt, RowsForSorting) - SELECT HowToStopIt = - QUOTENAME(DB_NAME()) + - N'' No information could be retrieved as a result of error: ''+ - CAST(ERROR_NUMBER() AS NVARCHAR(10)) + - N'' with message: ''+ - CAST(ERROR_MESSAGE() AS NVARCHAR(128)), - -1 - END - END CATCH'; + IF SERVERPROPERTY('Edition') <> 'SQL Azure' + SET @StringToExecute = N'USE [?];' + @LineFeed; + ELSE + SET @StringToExecute = N''; + + SET @StringToExecute = @StringToExecute + 'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; SET LOCK_TIMEOUT 1000;' + @LineFeed + + 'BEGIN TRY' + @LineFeed + + ' INSERT INTO #UpdatedStats(HowToStopIt, RowsForSorting)' + @LineFeed + + ' SELECT HowToStopIt = ' + @LineFeed + + ' QUOTENAME(DB_NAME()) + N''.'' +' + @LineFeed + + ' QUOTENAME(SCHEMA_NAME(obj.schema_id)) + N''.'' +' + @LineFeed + + ' QUOTENAME(obj.name) +' + @LineFeed + + ' N'' statistic '' + QUOTENAME(stat.name) + ' + @LineFeed + + ' N'' was updated on '' + CONVERT(NVARCHAR(50), sp.last_updated, 121) + N'','' + ' + @LineFeed + + ' N'' had '' + CAST(sp.rows AS NVARCHAR(50)) + N'' rows, with '' +' + @LineFeed + + ' CAST(sp.rows_sampled AS NVARCHAR(50)) + N'' rows sampled,'' + ' + @LineFeed + + ' N'' producing '' + CAST(sp.steps AS NVARCHAR(50)) + N'' steps in the histogram.'',' + @LineFeed + + ' sp.rows' + @LineFeed + + ' FROM sys.objects AS obj WITH (NOLOCK)' + @LineFeed + + ' INNER JOIN sys.stats AS stat WITH (NOLOCK) ON stat.object_id = obj.object_id ' + @LineFeed + + ' CROSS APPLY sys.dm_db_stats_properties(stat.object_id, stat.stats_id) AS sp ' + @LineFeed + + ' WHERE sp.last_updated > DATEADD(MI, -15, GETDATE())' + @LineFeed + + ' AND obj.is_ms_shipped = 0' + @LineFeed + + ' AND ''[?]'' <> ''[tempdb]'';' + @LineFeed + + 'END TRY' + @LineFeed + + 'BEGIN CATCH' + @LineFeed + + ' IF (ERROR_NUMBER() = 1222)' + @LineFeed + + ' BEGIN ' + @LineFeed + + ' INSERT INTO #UpdatedStats(HowToStopIt, RowsForSorting)' + @LineFeed + + ' SELECT HowToStopIt = ' + @LineFeed + + ' QUOTENAME(DB_NAME()) +' + @LineFeed + + ' N'' No information could be retrieved as the lock timeout was exceeded,''+' + @LineFeed + + ' N'' this is likely due to an Index operation in Progress'',' + @LineFeed + + ' -1' + @LineFeed + + ' END' + @LineFeed + + ' ELSE' + @LineFeed + + ' BEGIN' + @LineFeed + + ' INSERT INTO #UpdatedStats(HowToStopIt, RowsForSorting)' + @LineFeed + + ' SELECT HowToStopIt = ' + @LineFeed + + ' QUOTENAME(DB_NAME()) +' + @LineFeed + + ' N'' No information could be retrieved as a result of error: ''+' + @LineFeed + + ' CAST(ERROR_NUMBER() AS NVARCHAR(10)) +' + @LineFeed + + ' N'' with message: ''+' + @LineFeed + + ' CAST(ERROR_MESSAGE() AS NVARCHAR(128)),' + @LineFeed + + ' -1' + @LineFeed + + ' END' + @LineFeed + + 'END CATCH' + ; + + IF SERVERPROPERTY('Edition') <> 'SQL Azure' + EXEC sp_MSforeachdb @StringToExecute; + ELSE + EXEC(@StringToExecute); /* Set timeout back to a default value of -1 */ SET LOCK_TIMEOUT -1; @@ -40008,7 +40066,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 50 AS Priority, 'Query Problems' AS FindingGroup, 'Statistics Updated Recently' AS Finding, - 'http://www.BrentOzar.com/go/stats' AS URL, + 'https://www.brentozar.com/go/stats' AS URL, 'In the last 15 minutes, statistics were updated. To see which ones, click the HowToStopIt column.' + @LineFeed + @LineFeed + 'This effectively clears the plan cache for queries that involve these tables,' + @LineFeed + 'which thereby causes parameter sniffing: those queries are now getting brand new' + @LineFeed @@ -40033,35 +40091,42 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, RAISERROR('Capturing second pass of wait stats, perfmon counters, file stats',10,1) WITH NOWAIT; /* Populate #FileStats, #PerfmonStats, #WaitStats with DMV data. In a second, we'll compare these. */ - INSERT #WaitStats(Pass, SampleTime, wait_type, wait_time_ms, signal_wait_time_ms, waiting_tasks_count) - SELECT - x.Pass, - x.SampleTime, - x.wait_type, - SUM(x.sum_wait_time_ms) AS sum_wait_time_ms, - SUM(x.sum_signal_wait_time_ms) AS sum_signal_wait_time_ms, - SUM(x.sum_waiting_tasks) AS sum_waiting_tasks - FROM ( - SELECT - 2 AS Pass, - SYSDATETIMEOFFSET() AS SampleTime, - owt.wait_type, - SUM(owt.wait_duration_ms) OVER (PARTITION BY owt.wait_type, owt.session_id) - - CASE WHEN @Seconds = 0 THEN 0 ELSE (@Seconds * 1000) END AS sum_wait_time_ms, - 0 AS sum_signal_wait_time_ms, - CASE @Seconds WHEN 0 THEN 0 ELSE 1 END AS sum_waiting_tasks - FROM sys.dm_os_waiting_tasks owt - WHERE owt.session_id > 50 - AND owt.wait_duration_ms >= CASE @Seconds WHEN 0 THEN 0 ELSE @Seconds * 1000 END - UNION ALL - SELECT - 2 AS Pass, - SYSDATETIMEOFFSET() AS SampleTime, - os.wait_type, - SUM(os.wait_time_ms) OVER (PARTITION BY os.wait_type) AS sum_wait_time_ms, - SUM(os.signal_wait_time_ms) OVER (PARTITION BY os.wait_type ) AS sum_signal_wait_time_ms, - SUM(os.waiting_tasks_count) OVER (PARTITION BY os.wait_type) AS sum_waiting_tasks - FROM sys.dm_os_wait_stats os + SET @StringToExecute = N' + INSERT #WaitStats(Pass, SampleTime, wait_type, wait_time_ms, signal_wait_time_ms, waiting_tasks_count) + SELECT + x.Pass, + x.SampleTime, + x.wait_type, + SUM(x.sum_wait_time_ms) AS sum_wait_time_ms, + SUM(x.sum_signal_wait_time_ms) AS sum_signal_wait_time_ms, + SUM(x.sum_waiting_tasks) AS sum_waiting_tasks + FROM ( + SELECT + 2 AS Pass, + SYSDATETIMEOFFSET() AS SampleTime, + owt.wait_type, + SUM(owt.wait_duration_ms) OVER (PARTITION BY owt.wait_type, owt.session_id) + - CASE WHEN @Seconds = 0 THEN 0 ELSE (@Seconds * 1000) END AS sum_wait_time_ms, + 0 AS sum_signal_wait_time_ms, + CASE @Seconds WHEN 0 THEN 0 ELSE 1 END AS sum_waiting_tasks + FROM sys.dm_os_waiting_tasks owt + WHERE owt.session_id > 50 + AND owt.wait_duration_ms >= CASE @Seconds WHEN 0 THEN 0 ELSE @Seconds * 1000 END + UNION ALL + SELECT + 2 AS Pass, + SYSDATETIMEOFFSET() AS SampleTime, + os.wait_type, + SUM(os.wait_time_ms) OVER (PARTITION BY os.wait_type) AS sum_wait_time_ms, + SUM(os.signal_wait_time_ms) OVER (PARTITION BY os.wait_type ) AS sum_signal_wait_time_ms, + SUM(os.waiting_tasks_count) OVER (PARTITION BY os.wait_type) AS sum_waiting_tasks '; + + IF SERVERPROPERTY('Edition') = 'SQL Azure' + SET @StringToExecute = @StringToExecute + N' FROM sys.dm_db_wait_stats os '; + ELSE + SET @StringToExecute = @StringToExecute + N' FROM sys.dm_os_wait_stats os '; + + SET @StringToExecute = @StringToExecute + N' ) x WHERE NOT EXISTS ( @@ -40071,7 +40136,8 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, AND wc.Ignorable = 1 ) GROUP BY x.Pass, x.SampleTime, x.wait_type - ORDER BY sum_wait_time_ms DESC; + ORDER BY sum_wait_time_ms DESC;'; + EXEC sp_executesql @StringToExecute, N'@Seconds INT', @Seconds; INSERT INTO #FileStats (Pass, SampleTime, DatabaseID, FileID, DatabaseName, FileLogicalName, SizeOnDiskMB, io_stall_read_ms , num_of_reads, [bytes_read] , io_stall_write_ms,num_of_writes, [bytes_written], PhysicalName, TypeDesc, avg_stall_read_ms, avg_stall_write_ms) @@ -40138,7 +40204,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, END INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (18, 210, 'Query Stats', 'Plan Cache Analysis Skipped', 'http://www.BrentOzar.com/go/topqueries', + VALUES (18, 210, 'Query Stats', 'Plan Cache Analysis Skipped', 'https://www.brentozar.com/go/topqueries', 'Due to excessive load, the plan cache analysis was skipped. To override this, use @ExpertMode = 1.'); END; @@ -40281,7 +40347,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, END INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, QueryText, QueryStatsNowID, QueryStatsFirstID, PlanHandle, QueryHash) - SELECT 17, 210, 'Query Stats', 'Most Resource-Intensive Queries', 'http://www.BrentOzar.com/go/topqueries', + SELECT 17, 210, 'Query Stats', 'Most Resource-Intensive Queries', 'https://www.brentozar.com/go/topqueries', 'Query stats during the sample:' + @LineFeed + 'Executions: ' + CAST(qsNow.execution_count - (COALESCE(qsFirst.execution_count, 0)) AS NVARCHAR(100)) + @LineFeed + 'Elapsed Time: ' + CAST(qsNow.total_elapsed_time - (COALESCE(qsFirst.total_elapsed_time, 0)) AS NVARCHAR(100)) + @LineFeed + @@ -40369,7 +40435,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 10 AS Priority, 'Server Performance' AS FindingGroup, 'Poison Wait Detected: ' + wNow.wait_type AS Finding, - N'http://www.brentozar.com/go/poison/#' + wNow.wait_type AS URL, + N'https://www.brentozar.com/go/poison/#' + wNow.wait_type AS URL, 'For ' + CAST(((wNow.wait_time_ms - COALESCE(wBase.wait_time_ms,0)) / 1000) AS NVARCHAR(100)) + ' seconds over the last ' + CASE @Seconds WHEN 0 THEN (CAST(DATEDIFF(dd,@StartSampleTime,@FinishSampleTime) AS NVARCHAR(10)) + ' days') ELSE (CAST(@Seconds AS NVARCHAR(10)) + ' seconds') END + ', SQL Server was waiting on this particular bottleneck.' + @LineFeed + @LineFeed AS Details, 'See the URL for more details on how to mitigate this wait type.' AS HowToStopIt, ((wNow.wait_time_ms - COALESCE(wBase.wait_time_ms,0)) / 1000) AS DetailsInt @@ -40392,7 +40458,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 50 AS Priority, 'Server Performance' AS FindingGroup, 'Slow Data File Reads' AS Finding, - 'http://www.BrentOzar.com/go/slow/' AS URL, + 'https://www.brentozar.com/go/slow/' AS URL, 'Your server is experiencing PAGEIOLATCH% waits due to slow data file reads. This file is one of the reasons why.' + @LineFeed + 'File: ' + fNow.PhysicalName + @LineFeed + 'Number of reads during the sample: ' + CAST((fNow.num_of_reads - fBase.num_of_reads) AS NVARCHAR(20)) + @LineFeed @@ -40422,7 +40488,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 50 AS Priority, 'Server Performance' AS FindingGroup, 'Slow Log File Writes' AS Finding, - 'http://www.BrentOzar.com/go/slow/' AS URL, + 'https://www.brentozar.com/go/slow/' AS URL, 'Your server is experiencing WRITELOG waits due to slow log file writes. This file is one of the reasons why.' + @LineFeed + 'File: ' + fNow.PhysicalName + @LineFeed + 'Number of writes during the sample: ' + CAST((fNow.num_of_writes - fBase.num_of_writes) AS NVARCHAR(20)) + @LineFeed @@ -40451,7 +40517,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 1 AS Priority, 'SQL Server Internal Maintenance' AS FindingGroup, 'Log File Growing' AS Finding, - 'http://www.BrentOzar.com/askbrent/file-growing/' AS URL, + 'https://www.brentozar.com/askbrent/file-growing/' AS URL, 'Number of growths during the sample: ' + CAST(ps.value_delta AS NVARCHAR(20)) + @LineFeed + 'Determined by sampling Perfmon counter ' + ps.object_name + ' - ' + ps.counter_name + @LineFeed AS Details, 'Pre-grow data and log files during maintenance windows so that they do not grow during production loads. See the URL for more details.' AS HowToStopIt @@ -40473,7 +40539,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 1 AS Priority, 'SQL Server Internal Maintenance' AS FindingGroup, 'Log File Shrinking' AS Finding, - 'http://www.BrentOzar.com/askbrent/file-shrinking/' AS URL, + 'https://www.brentozar.com/askbrent/file-shrinking/' AS URL, 'Number of shrinks during the sample: ' + CAST(ps.value_delta AS NVARCHAR(20)) + @LineFeed + 'Determined by sampling Perfmon counter ' + ps.object_name + ' - ' + ps.counter_name + @LineFeed AS Details, 'Pre-grow data and log files during maintenance windows so that they do not grow during production loads. See the URL for more details.' AS HowToStopIt @@ -40494,7 +40560,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 50 AS Priority, 'Query Problems' AS FindingGroup, 'Compilations/Sec High' AS Finding, - 'http://www.BrentOzar.com/askbrent/compilations/' AS URL, + 'https://www.brentozar.com/askbrent/compilations/' AS URL, 'Number of batch requests during the sample: ' + CAST(ps.value_delta AS NVARCHAR(20)) + @LineFeed + 'Number of compilations during the sample: ' + CAST(psComp.value_delta AS NVARCHAR(20)) + @LineFeed + 'For OLTP environments, Microsoft recommends that 90% of batch requests should hit the plan cache, and not be compiled from scratch. We are exceeding that threshold.' + @LineFeed AS Details, @@ -40521,7 +40587,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 50 AS Priority, 'Query Problems' AS FindingGroup, 'Re-Compilations/Sec High' AS Finding, - 'http://www.BrentOzar.com/askbrent/recompilations/' AS URL, + 'https://www.brentozar.com/askbrent/recompilations/' AS URL, 'Number of batch requests during the sample: ' + CAST(ps.value_delta AS NVARCHAR(20)) + @LineFeed + 'Number of recompilations during the sample: ' + CAST(psComp.value_delta AS NVARCHAR(20)) + @LineFeed + 'More than 10% of our queries are being recompiled. This is typically due to statistics changing on objects.' + @LineFeed AS Details, @@ -40548,7 +40614,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 40 AS Priority, 'Table Problems' AS FindingGroup, 'Forwarded Fetches/Sec High' AS Finding, - 'https://BrentOzar.com/go/fetch/' AS URL, + 'https://www.brentozar.com/go/fetch/' AS URL, CAST(ps.value_delta AS NVARCHAR(20)) + ' forwarded fetches (from SQLServer:Access Methods counter)' + @LineFeed + 'Check your heaps: they need to be rebuilt, or they need a clustered index applied.' + @LineFeed AS Details, 'Rebuild your heaps. If you use Ola Hallengren maintenance scripts, those do not rebuild heaps by default: https://www.brentozar.com/archive/2016/07/fix-forwarded-records/' AS HowToStopIt @@ -40569,7 +40635,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 40 AS Priority, ''Table Problems'' AS FindingGroup, ''Forwarded Fetches/Sec High: TempDB Object'' AS Finding, - ''https://BrentOzar.com/go/fetch/'' AS URL, + ''https://www.brentozar.com/go/fetch/'' AS URL, CAST(COALESCE(os.forwarded_fetch_count,0) - COALESCE(os_prior.forwarded_fetch_count,0) AS NVARCHAR(20)) + '' forwarded fetches on '' + CASE WHEN OBJECT_NAME(os.object_id) IS NULL THEN ''an unknown table '' WHEN LEN(OBJECT_NAME(os.object_id)) < 50 THEN ''a table variable, internal identifier '' + OBJECT_NAME(os.object_id) @@ -40597,7 +40663,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 50 AS Priority, 'In-Memory OLTP' AS FindingGroup, 'Garbage Collection in Progress' AS Finding, - 'https://BrentOzar.com/go/garbage/' AS URL, + 'https://www.brentozar.com/go/garbage/' AS URL, CAST(ps.value_delta AS NVARCHAR(50)) + ' rows processed (from SQL Server YYYY XTP Garbage Collection:Rows processed/sec counter)' + @LineFeed + 'This can happen due to memory pressure (causing In-Memory OLTP to shrink its footprint) or' + @LineFeed + 'due to transactional workloads that constantly insert/delete data.' AS Details, @@ -40620,7 +40686,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 100 AS Priority, 'In-Memory OLTP' AS FindingGroup, 'Transactions Aborted' AS Finding, - 'https://BrentOzar.com/go/aborted/' AS URL, + 'https://www.brentozar.com/go/aborted/' AS URL, CAST(ps.value_delta AS NVARCHAR(50)) + ' transactions aborted (from SQL Server YYYY XTP Transactions:Transactions aborted/sec counter)' + @LineFeed + 'This may indicate that data is changing, or causing folks to retry their transactions, thereby increasing load.' AS Details, 'Dig into your In-Memory OLTP transactions to figure out which ones are failing and being retried.' AS HowToStopIt @@ -40642,7 +40708,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 100 AS Priority, 'Query Problems' AS FindingGroup, 'Suboptimal Plans/Sec High' AS Finding, - 'https://BrentOzar.com/go/suboptimal/' AS URL, + 'https://www.brentozar.com/go/suboptimal/' AS URL, CAST(ps.value_delta AS NVARCHAR(50)) + ' plans reported in the ' + CAST(ps.instance_name AS NVARCHAR(100)) + ' workload group (from Workload GroupStats:Suboptimal plans/sec counter)' + @LineFeed + 'Even if you are not using Resource Governor, it still tracks information about user queries, memory grants, etc.' AS Details, 'Check out sp_BlitzCache to get more information about recent queries, or try sp_BlitzWho to see currently running queries.' AS HowToStopIt @@ -40666,7 +40732,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 10 AS Priority, 'Azure Performance' AS FindingGroup, 'Database is Maxed Out' AS Finding, - 'https://BrentOzar.com/go/maxedout' AS URL, + 'https://www.brentozar.com/go/maxedout' AS URL, N'At ' + CONVERT(NVARCHAR(100), s.end_time ,121) + N', your database approached (or hit) your DTU limits:' + @LineFeed + N'Average CPU percent: ' + CAST(avg_cpu_percent AS NVARCHAR(50)) + @LineFeed + N'Average data IO percent: ' + CAST(avg_data_io_percent AS NVARCHAR(50)) + @LineFeed @@ -40694,7 +40760,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 250 AS Priority, 'Server Info' AS FindingGroup, 'Batch Requests per Sec' AS Finding, - 'http://www.BrentOzar.com/go/measure' AS URL, + 'https://www.brentozar.com/go/measure' AS URL, CAST(CAST(ps.value_delta AS MONEY) / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS NVARCHAR(20)) AS Details, ps.value_delta / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS DetailsInt FROM #PerfmonStats ps @@ -40720,7 +40786,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 250 AS Priority, 'Server Info' AS FindingGroup, 'SQL Compilations per Sec' AS Finding, - 'http://www.BrentOzar.com/go/measure' AS URL, + 'https://www.brentozar.com/go/measure' AS URL, CAST(ps.value_delta / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS NVARCHAR(20)) AS Details, ps.value_delta / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS DetailsInt FROM #PerfmonStats ps @@ -40743,7 +40809,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 250 AS Priority, 'Server Info' AS FindingGroup, 'SQL Re-Compilations per Sec' AS Finding, - 'http://www.BrentOzar.com/go/measure' AS URL, + 'https://www.brentozar.com/go/measure' AS URL, CAST(ps.value_delta / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS NVARCHAR(20)) AS Details, ps.value_delta / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS DetailsInt FROM #PerfmonStats ps @@ -40769,7 +40835,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 250 AS Priority, 'Server Info' AS FindingGroup, 'Wait Time per Core per Sec' AS Finding, - 'http://www.BrentOzar.com/go/measure' AS URL, + 'https://www.brentozar.com/go/measure' AS URL, CAST((CAST(waits2.waits_ms - waits1.waits_ms AS MONEY)) / 1000 / i.cpu_count / DATEDIFF(ss, waits1.SampleTime, waits2.SampleTime) AS NVARCHAR(20)) AS Details, (waits2.waits_ms - waits1.waits_ms) / 1000 / i.cpu_count / DATEDIFF(ss, waits1.SampleTime, waits2.SampleTime) AS DetailsInt FROM cores i @@ -40835,7 +40901,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, END INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) - SELECT 24, 50, 'Server Performance', 'High CPU Utilization', CAST(100 - SystemIdle AS NVARCHAR(20)) + N'%. Ring buffer details: ' + CAST(record AS NVARCHAR(4000)), 100 - SystemIdle, 'http://www.BrentOzar.com/go/cpu' + SELECT 24, 50, 'Server Performance', 'High CPU Utilization', CAST(100 - SystemIdle AS NVARCHAR(20)) + N'%. Ring buffer details: ' + CAST(record AS NVARCHAR(4000)), 100 - SystemIdle, 'https://www.brentozar.com/go/cpu' FROM ( SELECT record, record.value('(./Record/SchedulerMonitorEvent/SystemHealth/SystemIdle)[1]', 'int') AS SystemIdle @@ -40855,7 +40921,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, END INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) - SELECT 23, 250, 'Server Info', 'CPU Utilization', CAST(100 - SystemIdle AS NVARCHAR(20)) + N'%. Ring buffer details: ' + CAST(record AS NVARCHAR(4000)), 100 - SystemIdle, 'http://www.BrentOzar.com/go/cpu' + SELECT 23, 250, 'Server Info', 'CPU Utilization', CAST(100 - SystemIdle AS NVARCHAR(20)) + N'%. Ring buffer details: ' + CAST(record AS NVARCHAR(4000)), 100 - SystemIdle, 'https://www.brentozar.com/go/cpu' FROM ( SELECT record, record.value('(./Record/SchedulerMonitorEvent/SystemHealth/SystemIdle)[1]', 'int') AS SystemIdle @@ -40994,15 +41060,34 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, IF @BlitzCacheMinutesBack IS NULL OR @BlitzCacheMinutesBack < 1 OR @BlitzCacheMinutesBack > 60 SET @BlitzCacheMinutesBack = 15; - EXEC sp_BlitzCache - @OutputDatabaseName = @UnquotedOutputDatabaseName, - @OutputSchemaName = @UnquotedOutputSchemaName, - @OutputTableName = @OutputTableNameBlitzCache, - @CheckDateOverride = @StartSampleTime, - @SortOrder = 'all', - @SkipAnalysis = @BlitzCacheSkipAnalysis, - @MinutesBack = @BlitzCacheMinutesBack, - @Debug = @Debug; + IF(@OutputType = 'NONE') + BEGIN + EXEC sp_BlitzCache + @OutputDatabaseName = @UnquotedOutputDatabaseName, + @OutputSchemaName = @UnquotedOutputSchemaName, + @OutputTableName = @OutputTableNameBlitzCache, + @CheckDateOverride = @StartSampleTime, + @SortOrder = 'all', + @SkipAnalysis = @BlitzCacheSkipAnalysis, + @MinutesBack = @BlitzCacheMinutesBack, + @Debug = @Debug, + @OutputType = @OutputType + ; + END; + ELSE + BEGIN + + EXEC sp_BlitzCache + @OutputDatabaseName = @UnquotedOutputDatabaseName, + @OutputSchemaName = @UnquotedOutputSchemaName, + @OutputTableName = @OutputTableNameBlitzCache, + @CheckDateOverride = @StartSampleTime, + @SortOrder = 'all', + @SkipAnalysis = @BlitzCacheSkipAnalysis, + @MinutesBack = @BlitzCacheMinutesBack, + @Debug = @Debug + ; + END; /* Delete history older than @OutputTableRetentionDays */ SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' diff --git a/Install-Core-Blitz-No-Query-Store.sql b/Install-Core-Blitz-No-Query-Store.sql index 67a492fc1..b07af56a8 100755 --- a/Install-Core-Blitz-No-Query-Store.sql +++ b/Install-Core-Blitz-No-Query-Store.sql @@ -37,7 +37,7 @@ AS SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.01', @VersionDate = '20210222'; + SELECT @Version = '8.02', @VersionDate = '20210321'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -321,6 +321,15 @@ AS ); CREATE CLUSTERED INDEX IX_CheckID_DatabaseName ON #SkipChecks(CheckID, DatabaseName); + INSERT INTO #SkipChecks + (DatabaseName) + SELECT + DB_NAME(d.database_id) + FROM sys.databases AS d + WHERE (DB_NAME(d.database_id) LIKE 'rdsadmin%' + OR LOWER(d.name) IN ('dbatools', 'dbadmin', 'dbmaintenance')) + OPTION(RECOMPILE); + IF(OBJECT_ID('tempdb..#InvalidLogins') IS NOT NULL) BEGIN EXEC sp_executesql N'DROP TABLE #InvalidLogins;'; @@ -800,7 +809,7 @@ AS 240, 'Wait Stats', 'Wait Stats Have Been Cleared', - 'https://BrentOzar.com/go/waits', + 'https://www.brentozar.com/go/waits', 'Someone ran DBCC SQLPERF to clear sys.dm_os_wait_stats at approximately: ' + CONVERT(NVARCHAR(100), DATEADD(MINUTE, (-1. * (@MsSinceWaitsCleared) / 1000. / 60.), GETDATE()), 120)); @@ -909,7 +918,7 @@ AS 1 AS Priority , 'Backup' AS FindingsGroup , 'Backups Not Performed Recently' AS Finding , - 'https://BrentOzar.com/go/nobak' AS URL , + 'https://www.brentozar.com/go/nobak' AS URL , 'Last backed up: ' + COALESCE(CAST(MAX(b.backup_finish_date) AS VARCHAR(25)),'never') AS Details FROM master.sys.databases d @@ -949,7 +958,7 @@ AS 1 AS Priority , 'Backup' AS FindingsGroup , 'Backups Not Performed Recently' AS Finding , - 'https://BrentOzar.com/go/nobak' AS URL , + 'https://www.brentozar.com/go/nobak' AS URL , 'Last backed up: ' + COALESCE(CAST(MAX(b.backup_finish_date) AS VARCHAR(25)),'never') AS Details FROM master.sys.databases d @@ -1014,7 +1023,7 @@ AS 1 AS Priority , 'Backup' AS FindingsGroup , 'Full Recovery Model w/o Log Backups' AS Finding , - 'https://BrentOzar.com/go/biglogs' AS URL , + 'https://www.brentozar.com/go/biglogs' AS URL , ( 'The ' + CAST(CAST((SELECT ((SUM([mf].[size]) * 8.) / 1024.) FROM sys.[master_files] AS [mf] WHERE [mf].[database_id] = d.[database_id] AND [mf].[type_desc] = 'LOG') AS DECIMAL(18,2)) AS VARCHAR(30)) + 'MB log file has not been backed up in the last week.' ) AS Details FROM master.sys.databases d WHERE d.recovery_model IN ( 1, 2 ) @@ -1119,7 +1128,7 @@ AS 230 AS Priority, ''Security'' AS FindingsGroup, ''Server Audits Running'' AS Finding, - ''https://BrentOzar.com/go/audits'' AS URL, + ''https://www.brentozar.com/go/audits'' AS URL, (''SQL Server built-in audit functionality is being used by server audit: '' + [name]) AS Details FROM sys.dm_server_audit_status OPTION (RECOMPILE);'; IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; @@ -1164,7 +1173,7 @@ AS 1 AS Priority , 'Backup' AS FindingsGroup , 'Backing Up to Same Drive Where Databases Reside' AS Finding , - 'https://BrentOzar.com/go/backup' AS URL , + 'https://www.brentozar.com/go/backup' AS URL , CAST(COUNT(1) AS VARCHAR(50)) + ' backups done on drive ' + UPPER(LEFT(bmf.physical_device_name, 3)) + ' in the last two weeks, where database files also live. This represents a serious risk if that array fails.' Details @@ -1202,7 +1211,7 @@ AS ''Backup'' AS FindingsGroup, ''TDE Certificate Not Backed Up Recently'' AS Finding, db_name(dek.database_id) AS DatabaseName, - ''https://BrentOzar.com/go/tde'' AS URL, + ''https://www.brentozar.com/go/tde'' AS URL, ''The certificate '' + c.name + '' is used to encrypt database '' + db_name(dek.database_id) + ''. Last backup date: '' + COALESCE(CAST(c.pvt_key_last_backup_date AS VARCHAR(100)), ''Never'') AS Details FROM sys.certificates c INNER JOIN sys.dm_database_encryption_keys dek ON c.thumbprint = dek.encryptor_thumbprint WHERE pvt_key_last_backup_date IS NULL OR pvt_key_last_backup_date <= DATEADD(dd, -30, GETDATE()) OPTION (RECOMPILE);'; @@ -1231,7 +1240,7 @@ AS 1 AS Priority, ''Backup'' AS FindingsGroup, ''Encryption Certificate Not Backed Up Recently'' AS Finding, - ''https://BrentOzar.com/go/tde'' AS URL, + ''https://www.brentozar.com/go/tde'' AS URL, ''The certificate '' + c.name + '' is used to encrypt database backups. Last backup date: '' + COALESCE(CAST(c.pvt_key_last_backup_date AS VARCHAR(100)), ''Never'') AS Details FROM sys.certificates c INNER JOIN msdb.dbo.backupset bs ON c.thumbprint = bs.encryptor_thumbprint @@ -1268,7 +1277,7 @@ AS 200 AS Priority , 'Backup' AS FindingsGroup , 'MSDB Backup History Not Purged' AS Finding , - 'https://BrentOzar.com/go/history' AS URL , + 'https://www.brentozar.com/go/history' AS URL , ( 'Database backup history retained back to ' + CAST(bs.backup_start_date AS VARCHAR(20)) ) AS Details FROM msdb.dbo.backupset bs @@ -1303,7 +1312,7 @@ AS 200 AS Priority , 'Backup' AS FindingsGroup , 'MSDB Backup History Purged Too Frequently' AS Finding , - 'https://BrentOzar.com/go/history' AS URL , + 'https://www.brentozar.com/go/history' AS URL , ( 'Database backup history only retained back to ' + CAST(bs.backup_start_date AS VARCHAR(20)) ) AS Details FROM msdb.dbo.backupset bs @@ -1336,7 +1345,7 @@ AS 200 AS Priority , 'Performance' AS FindingsGroup , 'Snapshot Backups Occurring' AS Finding , - 'https://BrentOzar.com/go/snaps' AS URL , + 'https://www.brentozar.com/go/snaps' AS URL , ( CAST(COUNT(*) AS VARCHAR(20)) + ' snapshot-looking backups have occurred in the last two weeks, indicating that IO may be freezing up.') AS Details FROM msdb.dbo.backupset bs WHERE bs.type = 'D' @@ -1364,7 +1373,7 @@ AS 50 AS Priority , 'Performance' AS FindingsGroup , 'Snapshotting Too Many Databases' AS Finding , - 'https://BrentOzar.com/go/toomanysnaps' AS URL , + 'https://www.brentozar.com/go/toomanysnaps' AS URL , ( CAST(SUM(1) AS VARCHAR(20)) + ' databases snapshotted at once in the last two weeks, indicating that IO may be freezing up. Microsoft does not recommend VSS snaps for 35 or more databases.') AS Details FROM msdb.dbo.backupset bs WHERE bs.type = 'D' @@ -1393,7 +1402,7 @@ AS 230 AS Priority , 'Security' AS FindingsGroup , 'Sysadmins' AS Finding , - 'https://BrentOzar.com/go/sa' AS URL , + 'https://www.brentozar.com/go/sa' AS URL , ( 'Login [' + l.name + '] is a sysadmin - meaning they can do absolutely anything in SQL Server, including dropping databases or hiding their tracks.' ) AS Details FROM master.sys.syslogins l @@ -1451,7 +1460,7 @@ AS 230 AS Priority , 'Security' AS FindingsGroup , 'Security Admins' AS Finding , - 'https://BrentOzar.com/go/sa' AS URL , + 'https://www.brentozar.com/go/sa' AS URL , ( 'Login [' + l.name + '] is a security admin - meaning they can give themselves permission to do absolutely anything in SQL Server, including dropping databases or hiding their tracks.' ) AS Details FROM master.sys.syslogins l @@ -1479,7 +1488,7 @@ AS 230 AS [Priority] , 'Security' AS [FindingsGroup] , 'Login Can Control Server' AS [Finding] , - 'https://BrentOzar.com/go/sa' AS [URL] , + 'https://www.brentozar.com/go/sa' AS [URL] , 'Login [' + pri.[name] + '] has the CONTROL SERVER permission - meaning they can do absolutely anything in SQL Server, including dropping databases or hiding their tracks.' AS [Details] FROM sys.server_principals AS pri @@ -1511,7 +1520,7 @@ AS 230 AS Priority , 'Security' AS FindingsGroup , 'Jobs Owned By Users' AS Finding , - 'https://BrentOzar.com/go/owners' AS URL , + 'https://www.brentozar.com/go/owners' AS URL , ( 'Job [' + j.name + '] is owned by [' + SUSER_SNAME(j.owner_sid) + '] - meaning if their login is disabled or not available due to Active Directory problems, the job will stop working.' ) AS Details @@ -1541,7 +1550,7 @@ AS 230 AS Priority , 'Security' AS FindingsGroup , 'Stored Procedure Runs at Startup' AS Finding , - 'https://BrentOzar.com/go/startup' AS URL , + 'https://www.brentozar.com/go/startup' AS URL , ( 'Stored procedure [master].[' + r.SPECIFIC_SCHEMA + '].[' + r.SPECIFIC_NAME @@ -1572,7 +1581,7 @@ AS 100 AS Priority, ''Performance'' AS FindingsGroup, ''Resource Governor Enabled'' AS Finding, - ''https://BrentOzar.com/go/rg'' AS URL, + ''https://www.brentozar.com/go/rg'' AS URL, (''Resource Governor is enabled. Queries may be throttled. Make sure you understand how the Classifier Function is configured.'') AS Details FROM sys.resource_governor_configuration WHERE is_enabled = 1 OPTION (RECOMPILE);'; IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; @@ -1602,7 +1611,7 @@ AS 100 AS Priority, ''Performance'' AS FindingsGroup, ''Server Triggers Enabled'' AS Finding, - ''https://BrentOzar.com/go/logontriggers/'' AS URL, + ''https://www.brentozar.com/go/logontriggers/'' AS URL, (''Server Trigger ['' + [name] ++ ''] is enabled. Make sure you understand what that trigger is doing - the less work it does, the better.'') AS Details FROM sys.server_triggers WHERE is_disabled = 0 AND is_ms_shipped = 0 OPTION (RECOMPILE);'; IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; @@ -1633,7 +1642,7 @@ AS 10 AS Priority , 'Performance' AS FindingsGroup , 'Auto-Close Enabled' AS Finding , - 'https://BrentOzar.com/go/autoclose' AS URL , + 'https://www.brentozar.com/go/autoclose' AS URL , ( 'Database [' + [name] + '] has auto-close enabled. This setting can dramatically decrease performance.' ) AS Details FROM sys.databases @@ -1665,7 +1674,7 @@ AS 10 AS Priority , 'Performance' AS FindingsGroup , 'Auto-Shrink Enabled' AS Finding , - 'https://BrentOzar.com/go/autoshrink' AS URL , + 'https://www.brentozar.com/go/autoshrink' AS URL , ( 'Database [' + [name] + '] has auto-shrink enabled. This setting can dramatically decrease performance.' ) AS Details FROM sys.databases @@ -1699,7 +1708,7 @@ AS 50 AS Priority, ''Reliability'' AS FindingsGroup, ''Page Verification Not Optimal'' AS Finding, - ''https://BrentOzar.com/go/torn'' AS URL, + ''https://www.brentozar.com/go/torn'' AS URL, (''Database ['' + [name] + ''] has '' + [page_verify_option_desc] + '' for page verification. SQL Server may have a harder time recognizing and recovering from storage corruption. Consider using CHECKSUM instead.'') COLLATE database_default AS Details FROM sys.databases WHERE page_verify_option < 2 @@ -1735,7 +1744,7 @@ AS 110 AS Priority , 'Performance' AS FindingsGroup , 'Auto-Create Stats Disabled' AS Finding , - 'https://BrentOzar.com/go/acs' AS URL , + 'https://www.brentozar.com/go/acs' AS URL , ( 'Database [' + [name] + '] has auto-create-stats disabled. SQL Server uses statistics to build better execution plans, and without the ability to automatically create more, performance may suffer.' ) AS Details FROM sys.databases @@ -1767,7 +1776,7 @@ AS 110 AS Priority , 'Performance' AS FindingsGroup , 'Auto-Update Stats Disabled' AS Finding , - 'https://BrentOzar.com/go/aus' AS URL , + 'https://www.brentozar.com/go/aus' AS URL , ( 'Database [' + [name] + '] has auto-update-stats disabled. SQL Server uses statistics to build better execution plans, and without the ability to automatically update them, performance may suffer.' ) AS Details FROM sys.databases @@ -1799,7 +1808,7 @@ AS 150 AS Priority , 'Performance' AS FindingsGroup , 'Stats Updated Asynchronously' AS Finding , - 'https://BrentOzar.com/go/asyncstats' AS URL , + 'https://www.brentozar.com/go/asyncstats' AS URL , ( 'Database [' + [name] + '] has auto-update-stats-async enabled. When SQL Server gets a query for a table with out-of-date statistics, it will run the query with the stats it has - while updating stats to make later queries better. The initial run of the query may suffer, though.' ) AS Details FROM sys.databases @@ -1831,7 +1840,7 @@ AS 200 AS Priority , 'Informational' AS FindingsGroup , 'Date Correlation On' AS Finding , - 'https://BrentOzar.com/go/corr' AS URL , + 'https://www.brentozar.com/go/corr' AS URL , ( 'Database [' + [name] + '] has date correlation enabled. This is not a default setting, and it has some performance overhead. It tells SQL Server that date fields in two tables are related, and SQL Server maintains statistics showing that relation.' ) AS Details FROM sys.databases @@ -1866,7 +1875,7 @@ AS 200 AS Priority, ''Informational'' AS FindingsGroup, ''Database Encrypted'' AS Finding, - ''https://BrentOzar.com/go/tde'' AS URL, + ''https://www.brentozar.com/go/tde'' AS URL, (''Database ['' + [name] + ''] has Transparent Data Encryption enabled. Make absolutely sure you have backed up the certificate and private key, or else you will not be able to restore this database.'') AS Details FROM sys.databases WHERE is_encrypted = 1 @@ -2077,7 +2086,7 @@ AS 200 AS Priority , 'Non-Default Server Config' AS FindingsGroup , cr.name AS Finding , - 'https://BrentOzar.com/go/conf' AS URL , + 'https://www.brentozar.com/go/conf' AS URL , ( 'This sp_configure option has been changed. Its default value is ' + COALESCE(CAST(cd.[DefaultValue] AS VARCHAR(100)), '(unknown)') @@ -2119,7 +2128,7 @@ AS 200, 'Performance', 'Non-Dynamic Memory', - 'https://BrentOzar.com/go/memory', + 'https://www.brentozar.com/go/memory', 'Minimum Server Memory setting is the same as the Maximum (both set to ' + CAST(@MinServerMemory AS NVARCHAR(50)) + '). This will not allow dynamic memory. Please revise memory settings' ); END; @@ -2159,7 +2168,7 @@ AS 200 AS Priority , 'Performance' AS FindingsGroup , cr.name AS Finding , - 'https://BrentOzar.com/go/cxpacket' AS URL , + 'https://www.brentozar.com/go/cxpacket' AS URL , ( 'Set to ' + CAST(cr.value_in_use AS NVARCHAR(50)) + ', its default value. Changing this sp_configure setting may reduce CXPACKET waits.') FROM sys.configurations cr INNER JOIN #ConfigurationDefaults cd ON cd.name = cr.name @@ -2190,7 +2199,7 @@ AS 170 AS Priority , 'File Configuration' AS FindingsGroup , 'System Database on C Drive' AS Finding , - 'https://BrentOzar.com/go/cdrive' AS URL , + 'https://www.brentozar.com/go/cdrive' AS URL , ( 'The ' + DB_NAME(database_id) + ' database has a file on the C drive. Putting system databases on the C drive runs the risk of crashing the server when it runs out of space.' ) AS Details FROM sys.master_files @@ -2222,7 +2231,7 @@ AS 20 AS Priority , 'File Configuration' AS FindingsGroup , 'TempDB on C Drive' AS Finding , - 'https://BrentOzar.com/go/cdrive' AS URL , + 'https://www.brentozar.com/go/cdrive' AS URL , CASE WHEN growth > 0 THEN ( 'The tempdb database has files on the C drive. TempDB frequently grows unpredictably, putting your server at risk of running out of C drive space and crashing hard. C is also often much slower than other drives, so performance may be suffering.' ) ELSE ( 'The tempdb database has files on the C drive. TempDB is not set to Autogrow, hopefully it is big enough. C is also often much slower than other drives, so performance may be suffering.' ) @@ -2255,7 +2264,7 @@ AS 20 AS Priority , 'Reliability' AS FindingsGroup , 'User Databases on C Drive' AS Finding , - 'https://BrentOzar.com/go/cdrive' AS URL , + 'https://www.brentozar.com/go/cdrive' AS URL , ( 'The ' + DB_NAME(database_id) + ' database has a file on the C drive. Putting databases on the C drive runs the risk of crashing the server when it runs out of space.' ) AS Details FROM sys.master_files @@ -2291,7 +2300,7 @@ AS 200 AS Priority , 'Informational' AS FindingsGroup , 'Tables in the Master Database' AS Finding , - 'https://BrentOzar.com/go/mastuser' AS URL , + 'https://www.brentozar.com/go/mastuser' AS URL , ( 'The ' + name + ' table in the master database was created by end users on ' + CAST(create_date AS VARCHAR(20)) @@ -2323,7 +2332,7 @@ AS 200 AS Priority , 'Informational' AS FindingsGroup , 'Tables in the MSDB Database' AS Finding , - 'https://BrentOzar.com/go/msdbuser' AS URL , + 'https://www.brentozar.com/go/msdbuser' AS URL , ( 'The ' + name + ' table in the msdb database was created by end users on ' + CAST(create_date AS VARCHAR(20)) @@ -2353,7 +2362,7 @@ AS 200 AS Priority , 'Informational' AS FindingsGroup , 'Tables in the Model Database' AS Finding , - 'https://BrentOzar.com/go/model' AS URL , + 'https://www.brentozar.com/go/model' AS URL , ( 'The ' + name + ' table in the model database was created by end users on ' + CAST(create_date AS VARCHAR(20)) @@ -2387,7 +2396,7 @@ AS 200 AS Priority , 'Monitoring' AS FindingsGroup , 'Not All Alerts Configured' AS Finding , - 'https://BrentOzar.com/go/alert' AS URL , + 'https://www.brentozar.com/go/alert' AS URL , ( 'Not all SQL Server Agent alerts have been configured. This is a free, easy way to get notified of corruption, job failures, or major outages even before monitoring systems pick it up.' ) AS Details; END; END; @@ -2418,7 +2427,7 @@ AS 200 AS Priority , 'Monitoring' AS FindingsGroup , 'Alerts Configured without Follow Up' AS Finding , - 'https://BrentOzar.com/go/alert' AS URL , + 'https://www.brentozar.com/go/alert' AS URL , ( 'SQL Server Agent alerts have been configured but they either do not notify anyone or else they do not take any action. This is a free, easy way to get notified of corruption, job failures, or major outages even before monitoring systems pick it up.' ) AS Details; END; @@ -2448,7 +2457,7 @@ AS 200 AS Priority , 'Monitoring' AS FindingsGroup , 'No Alerts for Corruption' AS Finding , - 'https://BrentOzar.com/go/alert' AS URL , + 'https://www.brentozar.com/go/alert' AS URL , ( 'SQL Server Agent alerts do not exist for errors 823, 824, and 825. These three errors can give you notification about early hardware failure. Enabling them can prevent you a lot of heartbreak.' ) AS Details; END; @@ -2478,7 +2487,7 @@ AS 200 AS Priority , 'Monitoring' AS FindingsGroup , 'No Alerts for Sev 19-25' AS Finding , - 'https://BrentOzar.com/go/alert' AS URL , + 'https://www.brentozar.com/go/alert' AS URL , ( 'SQL Server Agent alerts do not exist for severity levels 19 through 25. These are some very severe SQL Server errors. Knowing that these are happening may let you recover from errors faster.' ) AS Details; END; @@ -2510,7 +2519,7 @@ AS 200 AS Priority , 'Monitoring' AS FindingsGroup , 'Alerts Disabled' AS Finding , - 'https://BrentOzar.com/go/alert' AS URL , + 'https://www.brentozar.com/go/alert' AS URL , ( 'The following Alert is disabled, please review and enable if desired: ' + name ) AS Details FROM msdb.dbo.sysalerts @@ -2543,7 +2552,7 @@ AS ,200 AS [Priority] ,'Monitoring' AS FindingsGroup ,'Alerts Without Event Descriptions' AS Finding - ,'https://BrentOzar.com/go/alert' AS [URL] + ,'https://www.brentozar.com/go/alert' AS [URL] ,('The following Alert is not including detailed event descriptions in its output messages: ' + QUOTENAME([name]) + '. You can fix it by ticking the relevant boxes in its Properties --> Options page.') AS Details FROM msdb.dbo.sysalerts @@ -2577,7 +2586,7 @@ AS 200 AS Priority , 'Monitoring' AS FindingsGroup , 'No Operators Configured/Enabled' AS Finding , - 'https://BrentOzar.com/go/op' AS URL , + 'https://www.brentozar.com/go/op' AS URL , ( 'No SQL Server Agent operators (emails) have been configured. This is a free, easy way to get notified of corruption, job failures, or major outages even before monitoring systems pick it up.' ) AS Details; END; @@ -2608,7 +2617,7 @@ AS 1 AS Priority , ''Corruption'' AS FindingsGroup , ''Database Corruption Detected'' AS Finding , - ''https://BrentOzar.com/go/repair'' AS URL , + ''https://www.brentozar.com/go/repair'' AS URL , ( ''Database mirroring has automatically repaired at least one corrupt page in the last 30 days. For more information, query the DMV sys.dm_db_mirroring_auto_page_repair.'' ) AS Details FROM (SELECT rp2.database_id, rp2.modification_time FROM sys.dm_db_mirroring_auto_page_repair rp2 @@ -2652,7 +2661,7 @@ AS 1 AS Priority , ''Corruption'' AS FindingsGroup , ''Database Corruption Detected'' AS Finding , - ''https://BrentOzar.com/go/repair'' AS URL , + ''https://www.brentozar.com/go/repair'' AS URL , ( ''Availability Groups has automatically repaired at least one corrupt page in the last 30 days. For more information, query the DMV sys.dm_hadr_auto_page_repair.'' ) AS Details FROM sys.dm_hadr_auto_page_repair rp INNER JOIN master.sys.databases db ON rp.database_id = db.database_id @@ -2690,7 +2699,7 @@ AS 1 AS Priority , ''Corruption'' AS FindingsGroup , ''Database Corruption Detected'' AS Finding , - ''https://BrentOzar.com/go/repair'' AS URL , + ''https://www.brentozar.com/go/repair'' AS URL , ( ''SQL Server has detected at least one corrupt page in the last 30 days. For more information, query the system table msdb.dbo.suspect_pages.'' ) AS Details FROM msdb.dbo.suspect_pages sp INNER JOIN master.sys.databases db ON sp.database_id = db.database_id @@ -2724,7 +2733,7 @@ AS 'Performance' AS FindingsGroup , 'Slow Storage Reads on Drive ' + UPPER(LEFT(mf.physical_name, 1)) AS Finding , - 'https://BrentOzar.com/go/slow' AS URL , + 'https://www.brentozar.com/go/slow' AS URL , 'Reads are averaging longer than 200ms for at least one database on this drive. For specific database file speeds, run the query from the information link.' AS Details FROM sys.dm_io_virtual_file_stats(NULL, NULL) AS fs @@ -2755,7 +2764,7 @@ AS 'Performance' AS FindingsGroup , 'Slow Storage Writes on Drive ' + UPPER(LEFT(mf.physical_name, 1)) AS Finding , - 'https://BrentOzar.com/go/slow' AS URL , + 'https://www.brentozar.com/go/slow' AS URL , 'Writes are averaging longer than 100ms for at least one database on this drive. For specific database file speeds, run the query from the information link.' AS Details FROM sys.dm_io_virtual_file_stats(NULL, NULL) AS fs @@ -2792,7 +2801,7 @@ AS 170 , 'File Configuration' , 'TempDB Only Has 1 Data File' , - 'https://BrentOzar.com/go/tempdb' , + 'https://www.brentozar.com/go/tempdb' , 'TempDB is only configured with one data file. More data files are usually required to alleviate SGAM contention.' ); END; @@ -2827,7 +2836,7 @@ AS 170 , 'File Configuration' , 'TempDB Unevenly Sized Data Files' , - 'https://BrentOzar.com/go/tempdb' , + 'https://www.brentozar.com/go/tempdb' , 'TempDB data files are not configured with the same size. Unevenly sized tempdb data files will result in unevenly sized workloads.' ); END; @@ -2852,7 +2861,7 @@ AS 150 AS Priority , 'Performance' AS FindingsGroup , 'Queries Forcing Order Hints' AS Finding , - 'https://BrentOzar.com/go/hints' AS URL , + 'https://www.brentozar.com/go/hints' AS URL , CAST(occurrence AS VARCHAR(10)) + ' instances of order hinting have been recorded since restart. This means queries are bossing the SQL Server optimizer around, and if they don''t know what they''re doing, this can cause more harm than good. This can also explain why DBA tuning efforts aren''t working.' AS Details FROM sys.dm_exec_query_optimizer_info @@ -2879,7 +2888,7 @@ AS 150 AS Priority , 'Performance' AS FindingsGroup , 'Queries Forcing Join Hints' AS Finding , - 'https://BrentOzar.com/go/hints' AS URL , + 'https://www.brentozar.com/go/hints' AS URL , CAST(occurrence AS VARCHAR(10)) + ' instances of join hinting have been recorded since restart. This means queries are bossing the SQL Server optimizer around, and if they don''t know what they''re doing, this can cause more harm than good. This can also explain why DBA tuning efforts aren''t working.' AS Details FROM sys.dm_exec_query_optimizer_info @@ -2907,7 +2916,7 @@ AS 200 AS Priority , 'Informational' AS FindingsGroup , 'Linked Server Configured' AS Finding , - 'https://BrentOzar.com/go/link' AS URL , + 'https://www.brentozar.com/go/link' AS URL , +CASE WHEN l.remote_name = 'sa' THEN COALESCE(s.data_source, s.provider) + ' is configured as a linked server. Check its security configuration as it is connecting with sa, because any user who queries it will get admin-level permissions.' @@ -2934,7 +2943,7 @@ AS 100 AS Priority , ''Performance'' AS FindingsGroup , ''Max Memory Set Too High'' AS Finding , - ''https://BrentOzar.com/go/max'' AS URL , + ''https://www.brentozar.com/go/max'' AS URL , ''SQL Server max memory is set to '' + CAST(c.value_in_use AS VARCHAR(20)) + '' megabytes, but the server only has '' @@ -2966,7 +2975,7 @@ AS 1 AS Priority , ''Performance'' AS FindingsGroup , ''Memory Dangerously Low'' AS Finding , - ''https://BrentOzar.com/go/max'' AS URL , + ''https://www.brentozar.com/go/max'' AS URL , ''The server has '' + CAST(( CAST(m.total_physical_memory_kb AS BIGINT) / 1024 ) AS VARCHAR(20)) + '' megabytes of physical memory, but only '' + CAST(( CAST(m.available_physical_memory_kb AS BIGINT) / 1024 ) AS VARCHAR(20)) + '' megabytes are available. As the server runs out of memory, there is danger of swapping to disk, which will kill performance.'' AS Details FROM sys.dm_os_sys_memory m @@ -2994,7 +3003,7 @@ AS 1 AS Priority , ''Performance'' AS FindingsGroup , ''Memory Dangerously Low in NUMA Nodes'' AS Finding , - ''https://BrentOzar.com/go/max'' AS URL , + ''https://www.brentozar.com/go/max'' AS URL , ''At least one NUMA node is reporting THREAD_RESOURCES_LOW in sys.dm_os_nodes and can no longer create threads.'' AS Details FROM sys.dm_os_nodes m WHERE node_state_desc LIKE ''%THREAD_RESOURCES_LOW%'' OPTION (RECOMPILE);'; @@ -3026,7 +3035,7 @@ AS 200 AS Priority , 'Informational' AS FindingsGroup , 'Cluster Node' AS Finding , - 'https://BrentOzar.com/go/node' AS URL , + 'https://www.brentozar.com/go/node' AS URL , 'This is a node in a cluster.' AS Details FROM sys.dm_os_cluster_nodes; END; @@ -3055,7 +3064,7 @@ AS 230 AS Priority , 'Security' AS FindingsGroup , 'Database Owner <> ' + @UsualDBOwner AS Finding , - 'https://BrentOzar.com/go/owndb' AS URL , + 'https://www.brentozar.com/go/owndb' AS URL , ( 'Database name: ' + [name] + ' ' + 'Owner name: ' + SUSER_SNAME(owner_sid) ) AS Details FROM sys.databases @@ -3117,7 +3126,7 @@ AS 230 AS Priority , 'Security' AS FindingsGroup , 'SQL Agent Job Runs at Startup' AS Finding , - 'https://BrentOzar.com/go/startup' AS URL , + 'https://www.brentozar.com/go/startup' AS URL , ( 'Job [' + j.name + '] runs automatically when SQL Server Agent starts up. Make sure you know exactly what this job is doing, because it could pose a security risk.' ) AS Details FROM msdb.dbo.sysschedules sched @@ -3146,7 +3155,7 @@ AS 100 AS Priority , 'Performance' AS FindingsGroup , 'Unusual SQL Server Edition' AS Finding , - 'https://BrentOzar.com/go/workgroup' AS URL , + 'https://www.brentozar.com/go/workgroup' AS URL , ( 'This server is using ' + CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) + ', which is capped at low amounts of CPU and memory.' ) AS Details @@ -3177,7 +3186,7 @@ AS 10 AS Priority , 'Performance' AS FindingsGroup , '32-bit SQL Server Installed' AS Finding , - 'https://BrentOzar.com/go/32bit' AS URL , + 'https://www.brentozar.com/go/32bit' AS URL , ( 'This server uses the 32-bit x86 binaries for SQL Server instead of the 64-bit x64 binaries. The amount of memory available for query workspace and execution plans is heavily limited.' ) AS Details WHERE CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) NOT LIKE '%64%'; END; @@ -3203,7 +3212,7 @@ AS 200 AS Priority , 'Performance' AS FindingsGroup , 'Old Compatibility Level' AS Finding , - 'https://BrentOzar.com/go/compatlevel' AS URL , + 'https://www.brentozar.com/go/compatlevel' AS URL , ( 'Database ' + [name] + ' is compatibility level ' + CAST(compatibility_level AS VARCHAR(20)) @@ -3235,7 +3244,7 @@ AS 200 AS [Priority] , 'Monitoring' AS FindingsGroup , 'Agent Jobs Without Failure Emails' AS Finding , - 'https://BrentOzar.com/go/alerts' AS URL , + 'https://www.brentozar.com/go/alerts' AS URL , 'The job ' + [name] + ' has not been set up to notify an operator if it fails.' AS Details FROM msdb.[dbo].[sysjobs] j @@ -3274,7 +3283,7 @@ AS 170 AS Priority , 'Reliability' AS FindingGroup , 'Remote DAC Disabled' AS Finding , - 'https://BrentOzar.com/go/dac' AS URL , + 'https://www.brentozar.com/go/dac' AS URL , 'Remote access to the Dedicated Admin Connection (DAC) is not enabled. The DAC can make remote troubleshooting much easier when SQL Server is unresponsive.'; END; @@ -3300,7 +3309,7 @@ AS 50 AS Priority , 'Performance' AS FindingGroup , 'CPU Schedulers Offline' AS Finding , - 'https://BrentOzar.com/go/schedulers' AS URL , + 'https://www.brentozar.com/go/schedulers' AS URL , 'Some CPU cores are not accessible to SQL Server due to affinity masking or licensing problems.'; END; @@ -3328,7 +3337,7 @@ AS 50 AS Priority , ''Performance'' AS FindingGroup , ''Memory Nodes Offline'' AS Finding , - ''https://BrentOzar.com/go/schedulers'' AS URL , + ''https://www.brentozar.com/go/schedulers'' AS URL , ''Due to affinity masking or licensing problems, some of the memory may not be available.'' OPTION (RECOMPILE)'; IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; @@ -3361,7 +3370,7 @@ AS 20 AS Priority , 'Reliability' AS FindingGroup , 'Unusual Database State: ' + [state_desc] AS Finding , - 'https://BrentOzar.com/go/repair' AS URL , + 'https://www.brentozar.com/go/repair' AS URL , 'This database may not be online.' FROM sys.databases WHERE state > 1; @@ -3390,7 +3399,7 @@ AS 200 AS Priority , 'Reliability' AS FindingGroup , 'Extended Stored Procedures in Master' AS Finding , - 'https://BrentOzar.com/go/clr' AS URL , + 'https://www.brentozar.com/go/clr' AS URL , 'The [' + name + '] extended stored procedure is in the master database. CLR may be in use, and the master database now needs to be part of your backup/recovery planning.' FROM master.sys.extended_procedures; @@ -3415,7 +3424,7 @@ AS 50 AS Priority , 'Performance' AS FindingGroup , 'Poison Wait Detected: ' + wait_type AS Finding , - 'https://BrentOzar.com/go/poison/#' + wait_type AS URL , + 'https://www.brentozar.com/go/poison/#' + wait_type AS URL , CONVERT(VARCHAR(10), (SUM([wait_time_ms]) / 1000) / 86400) + ':' + CONVERT(VARCHAR(20), DATEADD(s, (SUM([wait_time_ms]) / 1000), 0), 108) + ' of this wait have been recorded. This wait often indicates killer performance problems.' FROM sys.[dm_os_wait_stats] WHERE wait_type IN('IO_QUEUE_LIMIT', 'IO_RETRY', 'LOG_RATE_GOVERNOR', 'POOL_LOG_RATE_GOVERNOR', 'PREEMPTIVE_DEBUG', 'RESMGR_THROTTLED', 'RESOURCE_SEMAPHORE', 'RESOURCE_SEMAPHORE_QUERY_COMPILE','SE_REPL_CATCHUP_THROTTLE','SE_REPL_COMMIT_ACK','SE_REPL_COMMIT_TURN','SE_REPL_ROLLBACK_ACK','SE_REPL_SLOW_SECONDARY_THROTTLE','THREADPOOL') @@ -3443,7 +3452,7 @@ AS 50 AS Priority , 'Performance' AS FindingGroup , 'Poison Wait Detected: Serializable Locking' AS Finding , - 'https://BrentOzar.com/go/serializable' AS URL , + 'https://www.brentozar.com/go/serializable' AS URL , CONVERT(VARCHAR(10), (SUM([wait_time_ms]) / 1000) / 86400) + ':' + CONVERT(VARCHAR(20), DATEADD(s, (SUM([wait_time_ms]) / 1000), 0), 108) + ' of LCK_M_R% waits have been recorded. This wait often indicates killer performance problems.' FROM sys.[dm_os_wait_stats] WHERE wait_type IN ('LCK_M_RS_S', 'LCK_M_RS_U', 'LCK_M_RIn_NL','LCK_M_RIn_S', 'LCK_M_RIn_U','LCK_M_RIn_X', 'LCK_M_RX_S', 'LCK_M_RX_U','LCK_M_RX_X') @@ -3473,7 +3482,7 @@ AS 'Reliability' AS FindingGroup , 'Possibly Broken Log Shipping' AS Finding , d.[name] , - 'https://BrentOzar.com/go/shipping' AS URL , + 'https://www.brentozar.com/go/shipping' AS URL , d.[name] + ' is in a restoring state, but has not had a backup applied in the last two days. This is a possible indication of a broken transaction log shipping setup.' FROM [master].sys.databases d INNER JOIN [master].sys.database_mirroring dm ON d.database_id = dm.database_id @@ -3508,7 +3517,7 @@ AS ''Performance'' AS FindingsGroup, ''Change Tracking Enabled'' AS Finding, d.[name], - ''https://BrentOzar.com/go/tracking'' AS URL, + ''https://www.brentozar.com/go/tracking'' AS URL, ( d.[name] + '' has change tracking enabled. This is not a default setting, and it has some performance overhead. It keeps track of changes to rows in tables that have change tracking turned on.'' ) AS Details FROM sys.change_tracking_databases AS ctd INNER JOIN sys.databases AS d ON ctd.database_id = d.database_id OPTION (RECOMPILE)'; IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; @@ -3537,7 +3546,7 @@ AS 200 AS Priority , ''Informational'' AS FindingGroup , ''Backup Compression Default Off'' AS Finding , - ''https://BrentOzar.com/go/backup'' AS URL , + ''https://www.brentozar.com/go/backup'' AS URL , ''Uncompressed full backups have happened recently, and backup compression is not turned on at the server level. Backup compression is included with SQL Server 2008R2 & newer, even in Standard Edition. We recommend turning backup compression on by default so that ad-hoc backups will get compressed.'' FROM sys.configurations WHERE configuration_id = 1579 AND CAST(value_in_use AS INT) = 0 @@ -3569,7 +3578,7 @@ AS 100 AS Priority, ''Performance'' AS FindingsGroup, ''Memory Pressure Affecting Queries'' AS Finding, - ''https://BrentOzar.com/go/grants'' AS URL, + ''https://www.brentozar.com/go/grants'' AS URL, CAST(SUM(forced_grant_count) AS NVARCHAR(100)) + '' forced grants reported in the DMV sys.dm_exec_query_resource_semaphores, indicating memory pressure has affected query runtimes.'' FROM sys.dm_exec_query_resource_semaphores WHERE [forced_grant_count] IS NOT NULL OPTION (RECOMPILE);'; @@ -3597,7 +3606,7 @@ AS 150, 'Performance', 'Deadlocks Happening Daily', - 'https://BrentOzar.com/go/deadlocks', + 'https://www.brentozar.com/go/deadlocks', CAST(CAST(p.cntr_value / @DaysUptime AS BIGINT) AS NVARCHAR(100)) + ' average deadlocks per day. To find them, run sp_BlitzLock.' AS Details FROM sys.dm_os_performance_counters p INNER JOIN sys.databases d ON d.name = 'tempdb' @@ -3660,7 +3669,7 @@ AS Finding, URL, Details) - SELECT TOP 1 125, 10, 'Performance', 'Plan Cache Erased Recently', 'https://BrentOzar.com/askbrent/plan-cache-erased-recently/', + SELECT TOP 1 125, 10, 'Performance', 'Plan Cache Erased Recently', 'https://www.brentozar.com/askbrent/plan-cache-erased-recently/', 'The oldest query in the plan cache was created at ' + CAST(creation_time AS NVARCHAR(50)) + CASE WHEN @user_perm_gb_out IS NULL THEN '. Someone ran DBCC FREEPROCCACHE, restarted SQL Server, or it is under horrific memory pressure.' @@ -3685,7 +3694,7 @@ AS Finding, URL, Details) - VALUES(126, 5, 'Reliability', 'Priority Boost Enabled', 'https://BrentOzar.com/go/priorityboost/', + VALUES(126, 5, 'Reliability', 'Priority Boost Enabled', 'https://www.brentozar.com/go/priorityboost/', 'Priority Boost sounds awesome, but it can actually cause your SQL Server to crash.'); END; @@ -3708,7 +3717,7 @@ AS IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 128) WITH NOWAIT; INSERT INTO #BlitzResults(CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES(128, 20, 'Reliability', 'Unsupported Build of SQL Server', 'https://BrentOzar.com/go/unsupported', + VALUES(128, 20, 'Reliability', 'Unsupported Build of SQL Server', 'https://www.brentozar.com/go/unsupported', 'Version ' + CAST(@ProductVersionMajor AS VARCHAR(100)) + CASE WHEN @ProductVersionMajor >= 11 THEN '.' + CAST(@ProductVersionMinor AS VARCHAR(100)) + ' is no longer supported by Microsoft. You need to apply a service pack.' @@ -3833,7 +3842,7 @@ AS 10 AS Priority, ''Performance'' AS FindingsGroup, ''High Memory Use for In-Memory OLTP (Hekaton)'' AS Finding, - ''https://BrentOzar.com/go/hekaton'' AS URL, + ''https://www.brentozar.com/go/hekaton'' AS URL, CAST(CAST((SUM(mem.pages_kb / 1024.0) / CAST(value_in_use AS INT) * 100) AS INT) AS NVARCHAR(100)) + ''% of your '' + CAST(CAST((CAST(value_in_use AS DECIMAL(38,1)) / 1024) AS MONEY) AS NVARCHAR(100)) + ''GB of your max server memory is being used for in-memory OLTP tables (Hekaton). Microsoft recommends having 2X your Hekaton table space available in memory just for Hekaton, with a max of 250GB of in-memory data regardless of your server memory capacity.'' AS Details FROM sys.configurations c INNER JOIN sys.dm_os_memory_clerks mem ON mem.type = ''MEMORYCLERK_XTP'' WHERE c.name = ''max server memory (MB)'' @@ -3863,7 +3872,7 @@ AS 200 AS Priority, ''Performance'' AS FindingsGroup, ''In-Memory OLTP (Hekaton) In Use'' AS Finding, - ''https://BrentOzar.com/go/hekaton'' AS URL, + ''https://www.brentozar.com/go/hekaton'' AS URL, CAST(CAST((SUM(mem.pages_kb / 1024.0) / CAST(value_in_use AS INT) * 100) AS INT) AS NVARCHAR(100)) + ''% of your '' + CAST(CAST((CAST(value_in_use AS DECIMAL(38,1)) / 1024) AS MONEY) AS NVARCHAR(100)) + ''GB of your max server memory is being used for in-memory OLTP tables (Hekaton).'' AS Details FROM sys.configurations c INNER JOIN sys.dm_os_memory_clerks mem ON mem.type = ''MEMORYCLERK_XTP'' WHERE c.name = ''max server memory (MB)'' @@ -3892,7 +3901,7 @@ AS 100 AS Priority, ''In-Memory OLTP (Hekaton)'' AS FindingsGroup, ''Transaction Errors'' AS Finding, - ''https://BrentOzar.com/go/hekaton'' AS URL, + ''https://www.brentozar.com/go/hekaton'' AS URL, ''Since restart: '' + CAST(validation_failures AS NVARCHAR(100)) + '' validation failures, '' + CAST(dependencies_failed AS NVARCHAR(100)) + '' dependency failures, '' + CAST(write_conflicts AS NVARCHAR(100)) + '' write conflicts, '' + CAST(unique_constraint_violations AS NVARCHAR(100)) + '' unique constraint violations.'' AS Details FROM sys.dm_xtp_transaction_stats WHERE validation_failures <> 0 @@ -3928,7 +3937,7 @@ AS 170 AS Priority , 'Reliability' AS FindingsGroup , 'Database Files on Network File Shares' AS Finding , - 'https://BrentOzar.com/go/nas' AS URL , + 'https://www.brentozar.com/go/nas' AS URL , ( 'Files for this database are on: ' + LEFT(mf.physical_name, 30)) AS Details FROM sys.databases d INNER JOIN sys.master_files mf ON d.database_id = mf.database_id @@ -3961,7 +3970,7 @@ AS 170 AS Priority , 'Reliability' AS FindingsGroup , 'Database Files Stored in Azure' AS Finding , - 'https://BrentOzar.com/go/azurefiles' AS URL , + 'https://www.brentozar.com/go/azurefiles' AS URL , ( 'Files for this database are on: ' + LEFT(mf.physical_name, 30)) AS Details FROM sys.databases d INNER JOIN sys.master_files mf ON d.database_id = mf.database_id @@ -4054,7 +4063,7 @@ AS 50 AS Priority , 'Reliability' AS FindingsGroup , 'There Is An Error With The Default Trace' AS Finding , - 'https://BrentOzar.com/go/defaulttrace' AS URL , + 'https://www.brentozar.com/go/defaulttrace' AS URL , 'Somebody has been messing with your trace files. Check the files are present at ' + @base_tracefilename AS Details END @@ -4081,7 +4090,7 @@ AS 170 AS Priority , 'Reliability' AS FindingsGroup , 'Errors Logged Recently in the Default Trace' AS Finding , - 'https://BrentOzar.com/go/defaulttrace' AS URL , + 'https://www.brentozar.com/go/defaulttrace' AS URL , CAST(t.TextData AS NVARCHAR(4000)) AS Details FROM #fnTraceGettable t WHERE t.EventClass = 22 @@ -4114,7 +4123,7 @@ AS 50 AS Priority , 'Performance' AS FindingsGroup , 'File Growths Slow' AS Finding , - 'https://BrentOzar.com/go/filegrowth' AS URL , + 'https://www.brentozar.com/go/filegrowth' AS URL , CAST(COUNT(*) AS NVARCHAR(100)) + ' growths took more than 15 seconds each. Consider setting file autogrowth to a smaller increment.' AS Details FROM #fnTraceGettable t WHERE t.EventClass IN (92, 93) @@ -4139,7 +4148,7 @@ AS 100 AS Priority, ''Performance'' AS FindingsGroup, ''Many Plans for One Query'' AS Finding, - ''https://BrentOzar.com/go/parameterization'' AS URL, + ''https://www.brentozar.com/go/parameterization'' AS URL, CAST(COUNT(DISTINCT plan_handle) AS NVARCHAR(50)) + '' plans are present for a single query in the plan cache - meaning we probably have parameterization issues.'' AS Details FROM sys.dm_exec_query_stats qs CROSS APPLY sys.dm_exec_plan_attributes(qs.plan_handle) pa @@ -4173,7 +4182,7 @@ AS 100 AS Priority, ''Performance'' AS FindingsGroup, ''High Number of Cached Plans'' AS Finding, - ''https://BrentOzar.com/go/planlimits'' AS URL, + ''https://www.brentozar.com/go/planlimits'' AS URL, ''Your server configuration is limited to '' + CAST(ht.buckets_count * 4 AS VARCHAR(20)) + '' '' + ht.name + '', and you are currently caching '' + CAST(cc.entries_count AS VARCHAR(20)) + ''.'' AS Details FROM sys.dm_os_memory_cache_hash_tables ht INNER JOIN sys.dm_os_memory_cache_counters cc ON ht.name = cc.name AND ht.type = cc.type @@ -4201,7 +4210,7 @@ AS Finding, URL, Details) - SELECT 165, 50, 'Performance', 'Too Much Free Memory', 'https://BrentOzar.com/go/freememory', + SELECT 165, 50, 'Performance', 'Too Much Free Memory', 'https://www.brentozar.com/go/freememory', CAST((CAST(cFree.cntr_value AS BIGINT) / 1024 / 1024 ) AS NVARCHAR(100)) + N'GB of free memory inside SQL Server''s buffer pool, which is ' + CAST((CAST(cTotal.cntr_value AS BIGINT) / 1024 / 1024) AS NVARCHAR(100)) + N'GB. You would think lots of free memory would be good, but check out the URL for more information.' AS Details FROM sys.dm_os_performance_counters cFree INNER JOIN sys.dm_os_performance_counters cTotal ON cTotal.object_name LIKE N'%Memory Manager%' @@ -4247,71 +4256,71 @@ AS IF @Debug IN (1, 2) RAISERROR('Generating database defaults.', 0, 1) WITH NOWAIT; INSERT INTO #DatabaseDefaults - SELECT 'is_supplemental_logging_enabled', 0, 131, 210, 'Supplemental Logging Enabled', 'https://BrentOzar.com/go/dbdefaults', NULL + SELECT 'is_supplemental_logging_enabled', 0, 131, 210, 'Supplemental Logging Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL FROM sys.all_columns WHERE name = 'is_supplemental_logging_enabled' AND object_id = OBJECT_ID('sys.databases'); INSERT INTO #DatabaseDefaults - SELECT 'snapshot_isolation_state', 0, 132, 210, 'Snapshot Isolation Enabled', 'https://BrentOzar.com/go/dbdefaults', NULL + SELECT 'snapshot_isolation_state', 0, 132, 210, 'Snapshot Isolation Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL FROM sys.all_columns WHERE name = 'snapshot_isolation_state' AND object_id = OBJECT_ID('sys.databases'); INSERT INTO #DatabaseDefaults SELECT 'is_read_committed_snapshot_on', CASE WHEN SERVERPROPERTY('EngineEdition') = 5 THEN 1 ELSE 0 END, /* RCSI is always enabled in Azure SQL DB per https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/1919 */ - 133, 210, CASE WHEN SERVERPROPERTY('EngineEdition') = 5 THEN 'Read Committed Snapshot Isolation Disabled' ELSE 'Read Committed Snapshot Isolation Enabled' END, 'https://BrentOzar.com/go/dbdefaults', NULL + 133, 210, CASE WHEN SERVERPROPERTY('EngineEdition') = 5 THEN 'Read Committed Snapshot Isolation Disabled' ELSE 'Read Committed Snapshot Isolation Enabled' END, 'https://www.brentozar.com/go/dbdefaults', NULL FROM sys.all_columns WHERE name = 'is_read_committed_snapshot_on' AND object_id = OBJECT_ID('sys.databases'); INSERT INTO #DatabaseDefaults - SELECT 'is_auto_create_stats_incremental_on', 0, 134, 210, 'Auto Create Stats Incremental Enabled', 'https://BrentOzar.com/go/dbdefaults', NULL + SELECT 'is_auto_create_stats_incremental_on', 0, 134, 210, 'Auto Create Stats Incremental Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL FROM sys.all_columns WHERE name = 'is_auto_create_stats_incremental_on' AND object_id = OBJECT_ID('sys.databases'); INSERT INTO #DatabaseDefaults - SELECT 'is_ansi_null_default_on', 0, 135, 210, 'ANSI NULL Default Enabled', 'https://BrentOzar.com/go/dbdefaults', NULL + SELECT 'is_ansi_null_default_on', 0, 135, 210, 'ANSI NULL Default Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL FROM sys.all_columns WHERE name = 'is_ansi_null_default_on' AND object_id = OBJECT_ID('sys.databases'); INSERT INTO #DatabaseDefaults - SELECT 'is_recursive_triggers_on', 0, 136, 210, 'Recursive Triggers Enabled', 'https://BrentOzar.com/go/dbdefaults', NULL + SELECT 'is_recursive_triggers_on', 0, 136, 210, 'Recursive Triggers Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL FROM sys.all_columns WHERE name = 'is_recursive_triggers_on' AND object_id = OBJECT_ID('sys.databases'); INSERT INTO #DatabaseDefaults - SELECT 'is_trustworthy_on', 0, 137, 210, 'Trustworthy Enabled', 'https://BrentOzar.com/go/dbdefaults', NULL + SELECT 'is_trustworthy_on', 0, 137, 210, 'Trustworthy Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL FROM sys.all_columns WHERE name = 'is_trustworthy_on' AND object_id = OBJECT_ID('sys.databases'); INSERT INTO #DatabaseDefaults - SELECT 'is_broker_enabled', 0, 230, 210, 'Broker Enabled', 'https://BrentOzar.com/go/dbdefaults', NULL + SELECT 'is_broker_enabled', 0, 230, 210, 'Broker Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL FROM sys.all_columns WHERE name = 'is_broker_enabled' AND object_id = OBJECT_ID('sys.databases'); INSERT INTO #DatabaseDefaults - SELECT 'is_honor_broker_priority_on', 0, 231, 210, 'Honor Broker Priority Enabled', 'https://BrentOzar.com/go/dbdefaults', NULL + SELECT 'is_honor_broker_priority_on', 0, 231, 210, 'Honor Broker Priority Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL FROM sys.all_columns WHERE name = 'is_honor_broker_priority_on' AND object_id = OBJECT_ID('sys.databases'); INSERT INTO #DatabaseDefaults - SELECT 'is_parameterization_forced', 0, 138, 210, 'Forced Parameterization Enabled', 'https://BrentOzar.com/go/dbdefaults', NULL + SELECT 'is_parameterization_forced', 0, 138, 210, 'Forced Parameterization Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL FROM sys.all_columns WHERE name = 'is_parameterization_forced' AND object_id = OBJECT_ID('sys.databases'); /* Not alerting for this since we actually want it and we have a separate check for it: INSERT INTO #DatabaseDefaults - SELECT 'is_query_store_on', 0, 139, 210, 'Query Store Enabled', 'https://BrentOzar.com/go/dbdefaults', NULL + SELECT 'is_query_store_on', 0, 139, 210, 'Query Store Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL FROM sys.all_columns WHERE name = 'is_query_store_on' AND object_id = OBJECT_ID('sys.databases'); */ INSERT INTO #DatabaseDefaults - SELECT 'is_cdc_enabled', 0, 140, 210, 'Change Data Capture Enabled', 'https://BrentOzar.com/go/dbdefaults', NULL + SELECT 'is_cdc_enabled', 0, 140, 210, 'Change Data Capture Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL FROM sys.all_columns WHERE name = 'is_cdc_enabled' AND object_id = OBJECT_ID('sys.databases'); INSERT INTO #DatabaseDefaults - SELECT 'containment', 0, 141, 210, 'Containment Enabled', 'https://BrentOzar.com/go/dbdefaults', NULL + SELECT 'containment', 0, 141, 210, 'Containment Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL FROM sys.all_columns WHERE name = 'containment' AND object_id = OBJECT_ID('sys.databases'); INSERT INTO #DatabaseDefaults - SELECT 'target_recovery_time_in_seconds', 0, 142, 210, 'Target Recovery Time Changed', 'https://BrentOzar.com/go/dbdefaults', NULL + SELECT 'target_recovery_time_in_seconds', 0, 142, 210, 'Target Recovery Time Changed', 'https://www.brentozar.com/go/dbdefaults', NULL FROM sys.all_columns WHERE name = 'target_recovery_time_in_seconds' AND object_id = OBJECT_ID('sys.databases'); INSERT INTO #DatabaseDefaults - SELECT 'delayed_durability', 0, 143, 210, 'Delayed Durability Enabled', 'https://BrentOzar.com/go/dbdefaults', NULL + SELECT 'delayed_durability', 0, 143, 210, 'Delayed Durability Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL FROM sys.all_columns WHERE name = 'delayed_durability' AND object_id = OBJECT_ID('sys.databases'); INSERT INTO #DatabaseDefaults - SELECT 'is_memory_optimized_elevate_to_snapshot_on', 0, 144, 210, 'Memory Optimized Enabled', 'https://BrentOzar.com/go/dbdefaults', NULL + SELECT 'is_memory_optimized_elevate_to_snapshot_on', 0, 144, 210, 'Memory Optimized Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL FROM sys.all_columns WHERE name = 'is_memory_optimized_elevate_to_snapshot_on' AND object_id = OBJECT_ID('sys.databases') AND SERVERPROPERTY('EngineEdition') <> 8; /* Hekaton is always enabled in Managed Instances per https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/1919 */ @@ -4453,7 +4462,7 @@ IF @ProductVersionMajor >= 10 250 AS [Priority] , 'Informational' AS [FindingsGroup] , 'SQL Server is running under an NT Service account' AS [Finding] , - 'https://BrentOzar.com/go/setup' AS [URL] , + 'https://www.brentozar.com/go/setup' AS [URL] , ( 'I''m running as ' + [service_account] + '. I wish I had an Active Directory service account instead.' ) AS [Details] FROM @@ -4493,7 +4502,7 @@ IF @ProductVersionMajor >= 10 250 AS [Priority] , 'Informational' AS [FindingsGroup] , 'SQL Server Agent is running under an NT Service account' AS [Finding] , - 'https://BrentOzar.com/go/setup' AS [URL] , + 'https://www.brentozar.com/go/setup' AS [URL] , ( 'I''m running as ' + [service_account] + '. I wish I had an Active Directory service account instead.' ) AS [Details] FROM @@ -5039,7 +5048,7 @@ IF @ProductVersionMajor >= 10 20 AS [Priority] , 'Reliability' AS [FindingsGroup] , 'Memory Dumps Have Occurred' AS [Finding] , - 'https://BrentOzar.com/go/dump' AS [URL] , + 'https://www.brentozar.com/go/dump' AS [URL] , ( 'That ain''t good. I''ve had ' + CAST(COUNT(*) AS VARCHAR(100)) + ' memory dumps between ' + CAST(CAST(MIN([creation_time]) AS DATETIME) AS VARCHAR(100)) + @@ -5076,7 +5085,7 @@ IF @ProductVersionMajor >= 10 200 AS [Priority] , 'Licensing' AS [FindingsGroup] , 'Non-Production License' AS [Finding] , - 'https://BrentOzar.com/go/licensing' AS [URL] , + 'https://www.brentozar.com/go/licensing' AS [URL] , ( 'We''re not the licensing police, but if this is supposed to be a production server, and you''re running ' + CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) + ' the good folks at Microsoft might get upset with you. Better start counting those cores.' @@ -5108,7 +5117,7 @@ IF @ProductVersionMajor >= 10 200 AS [Priority] , 'Performance' AS [FindingsGroup] , 'Buffer Pool Extensions Enabled' AS [Finding] , - 'https://BrentOzar.com/go/bpe' AS [URL] , + 'https://www.brentozar.com/go/bpe' AS [URL] , ( 'You have Buffer Pool Extensions enabled, and one lives here: ' + [path] + '. It''s currently ' + @@ -5148,7 +5157,7 @@ IF @ProductVersionMajor >= 10 170 AS Priority , 'File Configuration' AS FindingsGroup , 'TempDB Has >16 Data Files' AS Finding , - 'https://BrentOzar.com/go/tempdb' AS URL , + 'https://www.brentozar.com/go/tempdb' AS URL , 'Woah, Nelly! TempDB has ' + CAST(COUNT_BIG(*) AS VARCHAR(30)) + '. Did you forget to terminate a loop somewhere?' AS Details FROM sys.[master_files] AS [mf] WHERE [mf].[database_id] = 2 AND [mf].[type] = 0 @@ -5183,7 +5192,7 @@ IF @ProductVersionMajor >= 10 200 AS Priority , 'Monitoring' AS FindingsGroup , 'Extended Events Hyperextension' AS Finding , - 'https://BrentOzar.com/go/xe' AS URL , + 'https://www.brentozar.com/go/xe' AS URL , 'Hey big spender, you have ' + CAST(COUNT_BIG(*) AS VARCHAR(30)) + ' Extended Events sessions running. You sure you meant to do that?' AS Details FROM sys.dm_xe_sessions WHERE [name] NOT IN @@ -5305,7 +5314,7 @@ IF @ProductVersionMajor >= 10 END AS Priority, 'Performance' AS [FindingsGroup] , 'Shrink Database Step In Maintenance Plan' AS [Finding] , - 'https://BrentOzar.com/go/autoshrink' AS [URL] , + 'https://www.brentozar.com/go/autoshrink' AS [URL] , 'The maintenance plan ' + [mps].[name] + ' has a step to shrink databases in it. Shrinking databases is as outdated as maintenance plans.' + CASE WHEN COALESCE(ssc.name,'0') != '0' THEN + ' (Schedule: [' + ssc.name + '])' ELSE + '' END AS [Details] FROM [maintenance_plan_steps] [mps] @@ -5391,7 +5400,7 @@ IF @ProductVersionMajor >= 10 20 AS Priority , ''Reliability'' AS FindingsGroup , ''No Failover Cluster Nodes Available'' AS Finding , - ''https://BrentOzar.com/go/node'' AS URL , + ''https://www.brentozar.com/go/node'' AS URL , ''There are no failover cluster nodes available if the active node fails'' AS Details FROM ( SELECT SUM(CASE WHEN [status] = 0 AND [is_current_owner] = 0 THEN 1 ELSE 0 END) AS [available_nodes] @@ -5427,7 +5436,7 @@ IF @ProductVersionMajor >= 10 50 AS [Priority] , 'Reliability' AS [FindingsGroup] , 'TempDB File Error' AS [Finding] , - 'https://BrentOzar.com/go/tempdboops' AS [URL] , + 'https://www.brentozar.com/go/tempdboops' AS [URL] , 'Mismatch between the number of TempDB files in sys.master_files versus tempdb.sys.database_files' AS [Details]; END; @@ -5463,7 +5472,7 @@ IF @ProductVersionMajor >= 10 10 AS Priority, 'Performance' AS FindingsGroup, 'CPU w/Odd Number of Cores' AS Finding, - 'https://BrentOzar.com/go/oddity' AS URL, + 'https://www.brentozar.com/go/oddity' AS URL, 'Node ' + CONVERT(VARCHAR(10), parent_node_id) + ' has ' + CONVERT(VARCHAR(10), COUNT(cpu_id)) + CASE WHEN COUNT(cpu_id) = 1 THEN ' core assigned to it. This is a really bad NUMA configuration.' ELSE ' cores assigned to it. This is a really bad NUMA configuration.' @@ -5747,7 +5756,7 @@ IF @ProductVersionMajor >= 10 'DBCC SHRINK% Ran Recently' AS Finding , 'https://www.BrentOzar.com/go/dbcc' AS URL , 'The user ' + COALESCE(d.nt_user_name, d.login_name) + ' has run file shrinks ' + CAST(COUNT(*) AS NVARCHAR(100)) + ' times between ' + CONVERT(NVARCHAR(30), MIN(d.min_start_time)) + ' and ' + CONVERT(NVARCHAR(30), MAX(d.max_start_time)) + - '. So, uh, are they trying cause bad performance on purpose?' + '. So, uh, are they trying to cause bad performance on purpose?' AS Details FROM #dbcc_events_from_trace d WHERE d.dbcc_event_trunc_upper LIKE N'DBCC SHRINK%' @@ -6034,7 +6043,7 @@ IF @ProductVersionMajor >= 10 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 99) WITH NOWAIT; - EXEC dbo.sp_MSforeachdb 'USE [?]; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; IF EXISTS (SELECT * FROM sys.tables WITH (NOLOCK) WHERE name = ''sysmergepublications'' ) IF EXISTS ( SELECT * FROM sysmergepublications WITH (NOLOCK) WHERE retention = 0) INSERT INTO #BlitzResults (CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details) SELECT DISTINCT 99, DB_NAME(), 110, ''Performance'', ''Infinite merge replication metadata retention period'', ''https://BrentOzar.com/go/merge'', (''The ['' + DB_NAME() + ''] database has merge replication metadata retention period set to infinite - this can be the case of significant performance issues.'')'; + EXEC dbo.sp_MSforeachdb 'USE [?]; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; IF EXISTS (SELECT * FROM sys.tables WITH (NOLOCK) WHERE name = ''sysmergepublications'' ) IF EXISTS ( SELECT * FROM sysmergepublications WITH (NOLOCK) WHERE retention = 0) INSERT INTO #BlitzResults (CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details) SELECT DISTINCT 99, DB_NAME(), 110, ''Performance'', ''Infinite merge replication metadata retention period'', ''https://www.brentozar.com/go/merge'', (''The ['' + DB_NAME() + ''] database has merge replication metadata retention period set to infinite - this can be the case of significant performance issues.'')'; END; /* Note that by using sp_MSforeachdb, we're running the query in all @@ -6068,7 +6077,7 @@ IF @ProductVersionMajor >= 10 200, ''Performance'', ''Query Store Disabled'', - ''https://BrentOzar.com/go/querystore'', + ''https://www.brentozar.com/go/querystore'', (''The new SQL Server 2016 Query Store feature has not been enabled on this database.'') FROM [?].sys.database_query_store_options WHERE desired_state = 0 AND N''?'' NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''DWConfiguration'', ''DWDiagnostics'', ''DWQueue'', ''ReportServer'', ''ReportServerTempDB'') OPTION (RECOMPILE)'; @@ -6099,7 +6108,7 @@ IF @ProductVersionMajor >= 10 20, ''Reliability'', ''Query Store Cleanup Disabled'', - ''https://BrentOzar.com/go/cleanup'', + ''https://www.brentozar.com/go/cleanup'', (''SQL 2016 RTM has a bug involving dumps that happen every time Query Store cleanup jobs run. This is fixed in CU1 and later: https://sqlserverupdates.com/sql-server-2016-updates/'') FROM sys.databases AS d WHERE d.is_query_store_on = 1 OPTION (RECOMPILE);'; @@ -6163,7 +6172,7 @@ IF @ProductVersionMajor >= 10 170, ''File Configuration'', ''Multiple Log Files on One Drive'', - ''https://BrentOzar.com/go/manylogs'', + ''https://www.brentozar.com/go/manylogs'', (''The ['' + DB_NAME() + ''] database has multiple log files on the '' + LEFT(physical_name, 1) + '' drive. This is not a performance booster because log file access is sequential, not parallel.'') FROM [?].sys.database_files WHERE type_desc = ''LOG'' AND N''?'' <> ''[tempdb]'' @@ -6193,7 +6202,7 @@ IF @ProductVersionMajor >= 10 170, ''File Configuration'', ''Uneven File Growth Settings in One Filegroup'', - ''https://BrentOzar.com/go/grow'', + ''https://www.brentozar.com/go/grow'', (''The ['' + DB_NAME() + ''] database has multiple data files in one filegroup, but they are not all set up to grow in identical amounts. This can lead to uneven file activity inside the filegroup.'') FROM [?].sys.database_files WHERE type_desc = ''ROWS'' @@ -6222,7 +6231,7 @@ IF @ProductVersionMajor >= 10 170 AS Priority, ''File Configuration'' AS FindingsGroup, ''File growth set to percent'', - ''https://BrentOzar.com/go/percentgrowth'' AS URL, + ''https://www.brentozar.com/go/percentgrowth'' AS URL, ''The ['' + DB_NAME() + ''] database file '' + f.physical_name + '' has grown to '' + CONVERT(NVARCHAR(10), CONVERT(NUMERIC(38, 2), (f.size / 128.) / 1024.)) + '' GB, and is using percent filegrowth settings. This can lead to slow performance during growths if Instant File Initialization is not enabled.'' FROM [?].sys.database_files f WHERE is_percent_growth = 1 and size > 128000 OPTION (RECOMPILE);'; @@ -6250,7 +6259,7 @@ IF @ProductVersionMajor >= 10 170 AS Priority, ''File Configuration'' AS FindingsGroup, ''File growth set to 1MB'', - ''https://BrentOzar.com/go/percentgrowth'' AS URL, + ''https://www.brentozar.com/go/percentgrowth'' AS URL, ''The ['' + DB_NAME() + ''] database file '' + f.physical_name + '' is using 1MB filegrowth settings, but it has grown to '' + CAST((f.size * 8 / 1000000) AS NVARCHAR(10)) + '' GB. Time to up the growth amount.'' FROM [?].sys.database_files f WHERE is_percent_growth = 0 and growth=128 and size > 128000 OPTION (RECOMPILE);'; @@ -6281,7 +6290,7 @@ IF @ProductVersionMajor >= 10 200, ''Licensing'', ''Enterprise Edition Features In Use'', - ''https://BrentOzar.com/go/ee'', + ''https://www.brentozar.com/go/ee'', (''The ['' + DB_NAME() + ''] database is using '' + feature_name + ''. If this database is restored onto a Standard Edition server, the restore will fail on versions prior to 2016 SP1.'') FROM [?].sys.dm_db_persisted_sku_features OPTION (RECOMPILE);'; END; @@ -6310,7 +6319,7 @@ IF @ProductVersionMajor >= 10 200 AS Priority , 'Informational' AS FindingsGroup , 'Replication In Use' AS Finding , - 'https://BrentOzar.com/go/repl' AS URL , + 'https://www.brentozar.com/go/repl' AS URL , ( 'Database [' + [name] + '] is a replication publisher, subscriber, or distributor.' ) AS Details FROM sys.databases @@ -6338,7 +6347,7 @@ IF @ProductVersionMajor >= 10 200, ''Informational'', ''Replication In Use'', - ''https://BrentOzar.com/go/repl'', + ''https://www.brentozar.com/go/repl'', (''['' + DB_NAME() + ''] has MSreplication_objects tables in it, indicating it is a replication subscriber.'') FROM [?].sys.tables WHERE name = ''dbo.MSreplication_objects'' AND ''?'' <> ''master'' OPTION (RECOMPILE)'; @@ -6367,45 +6376,13 @@ IF @ProductVersionMajor >= 10 150, ''Performance'', ''Triggers on Tables'', - ''https://BrentOzar.com/go/trig'', + ''https://www.brentozar.com/go/trig'', (''The ['' + DB_NAME() + ''] database has '' + CAST(SUM(1) AS NVARCHAR(50)) + '' triggers.'') FROM [?].sys.triggers t INNER JOIN [?].sys.objects o ON t.parent_id = o.object_id INNER JOIN [?].sys.schemas s ON o.schema_id = s.schema_id WHERE t.is_ms_shipped = 0 AND DB_NAME() != ''ReportServer'' HAVING SUM(1) > 0 OPTION (RECOMPILE)'; END; - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 38 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 38) WITH NOWAIT; - - EXEC dbo.sp_MSforeachdb 'USE [?]; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - INSERT INTO #BlitzResults - (CheckID, - DatabaseName, - Priority, - FindingsGroup, - Finding, - URL, - Details) - SELECT DISTINCT 38, - N''?'', - 110, - ''Performance'', - ''Active Tables Without Clustered Indexes'', - ''https://BrentOzar.com/go/heaps'', - (''The ['' + DB_NAME() + ''] database has heaps - tables without a clustered index - that are being actively queried.'') - FROM [?].sys.indexes i INNER JOIN [?].sys.objects o ON i.object_id = o.object_id - INNER JOIN [?].sys.partitions p ON i.object_id = p.object_id AND i.index_id = p.index_id - INNER JOIN sys.databases sd ON sd.name = N''?'' - LEFT OUTER JOIN [?].sys.dm_db_index_usage_stats ius ON i.object_id = ius.object_id AND i.index_id = ius.index_id AND ius.database_id = sd.database_id - WHERE i.type_desc = ''HEAP'' AND COALESCE(NULLIF(ius.user_seeks,0), NULLIF(ius.user_scans,0), NULLIF(ius.user_lookups,0), NULLIF(ius.user_updates,0)) IS NOT NULL - AND sd.name <> ''tempdb'' AND sd.name <> ''DWDiagnostics'' AND o.is_ms_shipped = 0 AND o.type <> ''S'' OPTION (RECOMPILE)'; - END; - IF NOT EXISTS ( SELECT 1 FROM #SkipChecks WHERE DatabaseName IS NULL AND CheckID = 164 ) @@ -6429,43 +6406,11 @@ IF @ProductVersionMajor >= 10 100, ''Reliability'', ''Plan Guides Failing'', - ''https://BrentOzar.com/go/misguided'', + ''https://www.brentozar.com/go/misguided'', (''The ['' + DB_NAME() + ''] database has plan guides that are no longer valid, so the queries involved may be failing silently.'') FROM [?].sys.plan_guides g CROSS APPLY fn_validate_plan_guide(g.plan_guide_id) OPTION (RECOMPILE)'; END; - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 39 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 39) WITH NOWAIT; - - EXEC dbo.sp_MSforeachdb 'USE [?]; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - INSERT INTO #BlitzResults - (CheckID, - DatabaseName, - Priority, - FindingsGroup, - Finding, - URL, - Details) - SELECT DISTINCT 39, - N''?'', - 150, - ''Performance'', - ''Inactive Tables Without Clustered Indexes'', - ''https://BrentOzar.com/go/heaps'', - (''The ['' + DB_NAME() + ''] database has heaps - tables without a clustered index - that have not been queried since the last restart. These may be backup tables carelessly left behind.'') - FROM [?].sys.indexes i INNER JOIN [?].sys.objects o ON i.object_id = o.object_id - INNER JOIN [?].sys.partitions p ON i.object_id = p.object_id AND i.index_id = p.index_id - INNER JOIN sys.databases sd ON sd.name = N''?'' - LEFT OUTER JOIN [?].sys.dm_db_index_usage_stats ius ON i.object_id = ius.object_id AND i.index_id = ius.index_id AND ius.database_id = sd.database_id - WHERE i.type_desc = ''HEAP'' AND COALESCE(NULLIF(ius.user_seeks,0), NULLIF(ius.user_scans,0), NULLIF(ius.user_lookups,0), NULLIF(ius.user_updates,0)) IS NULL - AND sd.name <> ''tempdb'' AND sd.name <> ''DWDiagnostics'' AND o.is_ms_shipped = 0 AND o.type <> ''S'' OPTION (RECOMPILE)'; - END; - IF NOT EXISTS ( SELECT 1 FROM #SkipChecks WHERE DatabaseName IS NULL AND CheckID = 46 ) @@ -6488,7 +6433,7 @@ IF @ProductVersionMajor >= 10 150, ''Performance'', ''Leftover Fake Indexes From Wizards'', - ''https://BrentOzar.com/go/hypo'', + ''https://www.brentozar.com/go/hypo'', (''The index ['' + DB_NAME() + ''].['' + s.name + ''].['' + o.name + ''].['' + i.name + ''] is a leftover hypothetical index from the Index Tuning Wizard or Database Tuning Advisor. This index is not actually helping performance and should be removed.'') from [?].sys.indexes i INNER JOIN [?].sys.objects o ON i.object_id = o.object_id INNER JOIN [?].sys.schemas s ON o.schema_id = s.schema_id WHERE i.is_hypothetical = 1 OPTION (RECOMPILE);'; @@ -6516,7 +6461,7 @@ IF @ProductVersionMajor >= 10 100, ''Performance'', ''Indexes Disabled'', - ''https://BrentOzar.com/go/ixoff'', + ''https://www.brentozar.com/go/ixoff'', (''The index ['' + DB_NAME() + ''].['' + s.name + ''].['' + o.name + ''].['' + i.name + ''] is disabled. This index is not actually helping performance and should either be enabled or removed.'') from [?].sys.indexes i INNER JOIN [?].sys.objects o ON i.object_id = o.object_id INNER JOIN [?].sys.schemas s ON o.schema_id = s.schema_id WHERE i.is_disabled = 1 OPTION (RECOMPILE);'; @@ -6544,7 +6489,7 @@ IF @ProductVersionMajor >= 10 150, ''Performance'', ''Foreign Keys Not Trusted'', - ''https://BrentOzar.com/go/trust'', + ''https://www.brentozar.com/go/trust'', (''The ['' + DB_NAME() + ''] database has foreign keys that were probably disabled, data was changed, and then the key was enabled again. Simply enabling the key is not enough for the optimizer to use this key - we have to alter the table using the WITH CHECK CHECK CONSTRAINT parameter.'') from [?].sys.foreign_keys i INNER JOIN [?].sys.objects o ON i.parent_object_id = o.object_id INNER JOIN [?].sys.schemas s ON o.schema_id = s.schema_id WHERE i.is_not_trusted = 1 AND i.is_not_for_replication = 0 AND i.is_disabled = 0 AND N''?'' NOT IN (''master'', ''model'', ''msdb'', ''ReportServer'', ''ReportServerTempDB'') OPTION (RECOMPILE);'; @@ -6572,7 +6517,7 @@ IF @ProductVersionMajor >= 10 150, ''Performance'', ''Check Constraint Not Trusted'', - ''https://BrentOzar.com/go/trust'', + ''https://www.brentozar.com/go/trust'', (''The check constraint ['' + DB_NAME() + ''].['' + s.name + ''].['' + o.name + ''].['' + i.name + ''] is not trusted - meaning, it was disabled, data was changed, and then the constraint was enabled again. Simply enabling the constraint is not enough for the optimizer to use this constraint - we have to alter the table using the WITH CHECK CHECK CONSTRAINT parameter.'') from [?].sys.check_constraints i INNER JOIN [?].sys.objects o ON i.parent_object_id = o.object_id INNER JOIN [?].sys.schemas s ON o.schema_id = s.schema_id @@ -6604,7 +6549,7 @@ IF @ProductVersionMajor >= 10 110 AS Priority, ''Performance'' AS FindingsGroup, ''Plan Guides Enabled'' AS Finding, - ''https://BrentOzar.com/go/guides'' AS URL, + ''https://www.brentozar.com/go/guides'' AS URL, (''Database ['' + DB_NAME() + ''] has query plan guides so a query will always get a specific execution plan. If you are having trouble getting query performance to improve, it might be due to a frozen plan. Review the DMV sys.plan_guides to learn more about the plan guides in place on this server.'') AS Details FROM [?].sys.plan_guides WHERE is_disabled = 0 OPTION (RECOMPILE);'; END; @@ -6632,7 +6577,7 @@ IF @ProductVersionMajor >= 10 100 AS Priority, ''Performance'' AS FindingsGroup, ''Fill Factor Changed'', - ''https://BrentOzar.com/go/fillfactor'' AS URL, + ''https://www.brentozar.com/go/fillfactor'' AS URL, ''The ['' + DB_NAME() + ''] database has '' + CAST(SUM(1) AS NVARCHAR(50)) + '' objects with fill factor = '' + CAST(fill_factor AS NVARCHAR(5)) + ''%. This can cause memory and storage performance problems, but may also prevent page splits.'' FROM [?].sys.indexes WHERE fill_factor <> 0 AND fill_factor < 80 AND is_disabled = 0 AND is_hypothetical = 0 @@ -6668,7 +6613,7 @@ IF @ProductVersionMajor >= 10 FindingsGroup = 'Performance', Finding = 'Stored Procedure WITH RECOMPILE', DatabaseName = DBName, - URL = 'https://BrentOzar.com/go/recompile', + URL = 'https://www.brentozar.com/go/recompile', Details = '[' + DBName + '].[' + SPSchema + '].[' + ProcName + '] has WITH RECOMPILE in the stored procedure code, which may cause increased CPU usage due to constant recompiles of the code.', CheckID = '78' FROM #Recompile AS TR WHERE ProcName NOT LIKE 'sp_AllNightLog%' AND ProcName NOT LIKE 'sp_AskBrent%' AND ProcName NOT LIKE 'sp_Blitz%'; @@ -6682,7 +6627,7 @@ IF @ProductVersionMajor >= 10 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 86) WITH NOWAIT; - EXEC dbo.sp_MSforeachdb 'USE [?]; INSERT INTO #BlitzResults (CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details) SELECT DISTINCT 86, DB_NAME(), 230, ''Security'', ''Elevated Permissions on a Database'', ''https://BrentOzar.com/go/elevated'', (''In ['' + DB_NAME() + ''], user ['' + u.name + ''] has the role ['' + g.name + '']. This user can perform tasks beyond just reading and writing data.'') FROM (SELECT memberuid = convert(int, member_principal_id), groupuid = convert(int, role_principal_id) FROM [?].sys.database_role_members) m inner join [?].dbo.sysusers u on m.memberuid = u.uid inner join sysusers g on m.groupuid = g.uid where u.name <> ''dbo'' and g.name in (''db_owner'' , ''db_accessadmin'' , ''db_securityadmin'' , ''db_ddladmin'') OPTION (RECOMPILE);'; + EXEC dbo.sp_MSforeachdb 'USE [?]; INSERT INTO #BlitzResults (CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details) SELECT DISTINCT 86, DB_NAME(), 230, ''Security'', ''Elevated Permissions on a Database'', ''https://www.brentozar.com/go/elevated'', (''In ['' + DB_NAME() + ''], user ['' + u.name + ''] has the role ['' + g.name + '']. This user can perform tasks beyond just reading and writing data.'') FROM (SELECT memberuid = convert(int, member_principal_id), groupuid = convert(int, role_principal_id) FROM [?].sys.database_role_members) m inner join [?].dbo.sysusers u on m.memberuid = u.uid inner join sysusers g on m.groupuid = g.uid where u.name <> ''dbo'' and g.name in (''db_owner'' , ''db_accessadmin'' , ''db_securityadmin'' , ''db_ddladmin'') OPTION (RECOMPILE);'; END; /*Check for non-aligned indexes in partioned databases*/ @@ -6726,7 +6671,7 @@ IF @ProductVersionMajor >= 10 'Performance' AS FindingsGroup , 'The partitioned database ' + dbname + ' may have non-aligned indexes' AS Finding , - 'https://BrentOzar.com/go/aligned' AS URL , + 'https://www.brentozar.com/go/aligned' AS URL , 'Having non-aligned indexes on partitioned tables may cause inefficient query plans and CPU pressure' AS Details FROM #partdb WHERE dbname IS NOT NULL @@ -6759,7 +6704,7 @@ IF @ProductVersionMajor >= 10 50, ''Reliability'', ''Full Text Indexes Not Updating'', - ''https://BrentOzar.com/go/fulltext'', + ''https://www.brentozar.com/go/fulltext'', (''At least one full text index in this database has not been crawled in the last week.'') from [?].sys.fulltext_indexes i WHERE change_tracking_state_desc <> ''AUTO'' AND i.is_enabled = 1 AND i.crawl_end_date < DATEADD(dd, -7, GETDATE()) OPTION (RECOMPILE);'; END; @@ -6786,7 +6731,7 @@ IF @ProductVersionMajor >= 10 110, ''Performance'', ''Parallelism Rocket Surgery'', - ''https://BrentOzar.com/go/makeparallel'', + ''https://www.brentozar.com/go/makeparallel'', (''['' + DB_NAME() + ''] has a make_parallel function, indicating that an advanced developer may be manhandling SQL Server into forcing queries to go parallel.'') from [?].INFORMATION_SCHEMA.ROUTINES WHERE ROUTINE_NAME = ''make_parallel'' AND ROUTINE_TYPE = ''FUNCTION'' OPTION (RECOMPILE);'; END; @@ -6819,7 +6764,7 @@ IF @ProductVersionMajor >= 10 200, ''Performance'', ''User-Created Statistics In Place'', - ''https://BrentOzar.com/go/userstats'', + ''https://www.brentozar.com/go/userstats'', (''['' + DB_NAME() + ''] has '' + CAST(SUM(1) AS NVARCHAR(10)) + '' user-created statistics. This indicates that someone is being a rocket scientist with the stats, and might actually be slowing things down, especially during stats updates.'') from [?].sys.stats WHERE user_created = 1 AND is_temporary = 0 HAVING SUM(1) > 0 OPTION (RECOMPILE);'; @@ -6840,7 +6785,7 @@ IF @ProductVersionMajor >= 10 200, ''Performance'', ''User-Created Statistics In Place'', - ''https://BrentOzar.com/go/userstats'', + ''https://www.brentozar.com/go/userstats'', (''['' + DB_NAME() + ''] has '' + CAST(SUM(1) AS NVARCHAR(10)) + '' user-created statistics. This indicates that someone is being a rocket scientist with the stats, and might actually be slowing things down, especially during stats updates.'') from [?].sys.stats WHERE user_created = 1 HAVING SUM(1) > 0 OPTION (RECOMPILE);'; @@ -6878,7 +6823,7 @@ IF @ProductVersionMajor >= 10 ,170 ,''File Configuration'' ,''High VLF Count'' - ,''https://BrentOzar.com/go/vlf'' + ,''https://www.brentozar.com/go/vlf'' ,''The ['' + DB_NAME() + ''] database has '' + CAST(COUNT(*) as VARCHAR(20)) + '' virtual log files (VLFs). This may be slowing down startup, restores, and even inserts/updates/deletes.'' FROM #LogInfo2012 WHERE EXISTS (SELECT name FROM master.sys.databases @@ -6911,7 +6856,7 @@ IF @ProductVersionMajor >= 10 ,170 ,''File Configuration'' ,''High VLF Count'' - ,''https://BrentOzar.com/go/vlf'' + ,''https://www.brentozar.com/go/vlf'' ,''The ['' + DB_NAME() + ''] database has '' + CAST(COUNT(*) as VARCHAR(20)) + '' virtual log files (VLFs). This may be slowing down startup, restores, and even inserts/updates/deletes.'' FROM #LogInfo WHERE EXISTS (SELECT name FROM master.sys.databases @@ -6932,7 +6877,7 @@ IF @ProductVersionMajor >= 10 EXEC dbo.sp_MSforeachdb 'USE [?]; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; INSERT INTO #BlitzResults (CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details) - SELECT DISTINCT 80, DB_NAME(), 170, ''Reliability'', ''Max File Size Set'', ''https://BrentOzar.com/go/maxsize'', + SELECT DISTINCT 80, DB_NAME(), 170, ''Reliability'', ''Max File Size Set'', ''https://www.brentozar.com/go/maxsize'', (''The ['' + DB_NAME() + ''] database file '' + df.name + '' has a max file size set to '' + CAST(CAST(df.max_size AS BIGINT) * 8 / 1024 AS VARCHAR(100)) + ''MB. If it runs out of space, the database will stop working even though there may be drive space available.'') @@ -7016,7 +6961,7 @@ IF @ProductVersionMajor >= 10 UNION ALL SELECT 24, 'LAST_QUERY_PLAN_STATS', '0', NULL, 255; EXEC dbo.sp_MSforeachdb 'USE [?]; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; INSERT INTO #BlitzResults (CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details) - SELECT def1.CheckID, DB_NAME(), 210, ''Non-Default Database Scoped Config'', dsc.[name], ''https://BrentOzar.com/go/dbscope'', (''Set value: '' + COALESCE(CAST(dsc.value AS NVARCHAR(100)),''Empty'') + '' Default: '' + COALESCE(CAST(def1.default_value AS NVARCHAR(100)),''Empty'') + '' Set value for secondary: '' + COALESCE(CAST(dsc.value_for_secondary AS NVARCHAR(100)),''Empty'') + '' Default value for secondary: '' + COALESCE(CAST(def1.default_value_for_secondary AS NVARCHAR(100)),''Empty'')) + SELECT def1.CheckID, DB_NAME(), 210, ''Non-Default Database Scoped Config'', dsc.[name], ''https://www.brentozar.com/go/dbscope'', (''Set value: '' + COALESCE(CAST(dsc.value AS NVARCHAR(100)),''Empty'') + '' Default: '' + COALESCE(CAST(def1.default_value AS NVARCHAR(100)),''Empty'') + '' Set value for secondary: '' + COALESCE(CAST(dsc.value_for_secondary AS NVARCHAR(100)),''Empty'') + '' Default value for secondary: '' + COALESCE(CAST(def1.default_value_for_secondary AS NVARCHAR(100)),''Empty'')) FROM [?].sys.database_scoped_configurations dsc INNER JOIN #DatabaseScopedConfigurationDefaults def1 ON dsc.configuration_id = def1.configuration_id LEFT OUTER JOIN #DatabaseScopedConfigurationDefaults def ON dsc.configuration_id = def.configuration_id AND (cast(dsc.value as nvarchar(100)) = cast(def.default_value as nvarchar(100)) OR dsc.value IS NULL) AND (dsc.value_for_secondary = def.default_value_for_secondary OR dsc.value_for_secondary IS NULL) @@ -7046,7 +6991,7 @@ IF @ProductVersionMajor >= 10 ,150 AS Priority ,''Performance'' AS FindingsGroup ,''Objects created with dangerous SET Options'' AS Finding - ,''https://BrentOzar.com/go/badset'' AS URL + ,''https://www.brentozar.com/go/badset'' AS URL ,''The '' + QUOTENAME(DB_NAME()) + '' database has '' + CONVERT(VARCHAR(20),COUNT(1)) + '' objects that were created with dangerous ANSI_NULL or QUOTED_IDENTIFIER options.'' @@ -7084,7 +7029,7 @@ IF @ProductVersionMajor >= 10 ,200 AS Priority ,''Reliability'' AS FindingsGroup ,''Resumable Index Operation Paused'' AS Finding - ,''https://BrentOzar.com/go/resumable'' AS URL + ,''https://www.brentozar.com/go/resumable'' AS URL ,iro.state_desc + N'' since '' + CONVERT(NVARCHAR(50), last_pause_time, 120) + '', '' + CAST(iro.percent_complete AS NVARCHAR(20)) + ''% complete: '' + CAST(iro.sql_text AS NVARCHAR(1000)) AS Details @@ -7115,7 +7060,7 @@ IF @ProductVersionMajor >= 10 -- ,110 AS Priority -- ,''Performance'' AS FindingsGroup -- ,''Statistics Without Histograms'' AS Finding - -- ,''https://BrentOzar.com/go/brokenstats'' AS URL + -- ,''https://www.brentozar.com/go/brokenstats'' AS URL -- ,CAST(COUNT(DISTINCT o.object_id) AS VARCHAR(100)) + '' tables have statistics that have not been updated since the database was restored or upgraded,'' -- + '' and have no data in their histogram. See the More Info URL for a script to update them. '' AS Details -- FROM sys.all_objects o @@ -7170,7 +7115,7 @@ IF @ProductVersionMajor >= 10 1 AS PRIORITY , 'Reliability' AS FindingsGroup , 'Last good DBCC CHECKDB over 2 weeks old' AS Finding , - 'https://BrentOzar.com/go/checkdb' AS URL , + 'https://www.brentozar.com/go/checkdb' AS URL , 'Last successful CHECKDB: ' + CASE DB2.Value WHEN '1900-01-01 00:00:00.000' @@ -7221,7 +7166,7 @@ IF @ProductVersionMajor >= 10 100 AS Priority , 'Performance' AS FindingsGroup , 'Single-Use Plans in Procedure Cache' AS Finding , - 'https://BrentOzar.com/go/single' AS URL , + 'https://www.brentozar.com/go/single' AS URL , ( CAST(COUNT(*) AS VARCHAR(10)) + ' query plans are taking up memory in the procedure cache. This may be wasted memory if we cache plans for queries that never get called again. This may be a good use case for SQL Server 2008''s Optimize for Ad Hoc or for Forced Parameterization.' ) AS Details FROM sys.dm_exec_cached_plans AS cp @@ -7420,7 +7365,7 @@ IF @ProductVersionMajor >= 10 120 AS Priority , 'Query Plans' AS FindingsGroup , 'Implicit Conversion' AS Finding , - 'https://BrentOzar.com/go/implicit' AS URL , + 'https://www.brentozar.com/go/implicit' AS URL , ( 'One of the top resource-intensive queries is comparing two fields that are not the same datatype.' ) AS Details , qs.query_plan , qs.query_plan_filtered @@ -7452,7 +7397,7 @@ IF @ProductVersionMajor >= 10 120 AS Priority , 'Query Plans' AS FindingsGroup , 'Implicit Conversion Affecting Cardinality' AS Finding , - 'https://BrentOzar.com/go/implicit' AS URL , + 'https://www.brentozar.com/go/implicit' AS URL , ( 'One of the top resource-intensive queries has an implicit conversion that is affecting cardinality estimation.' ) AS Details , qs.query_plan , qs.query_plan_filtered @@ -7483,7 +7428,7 @@ IF @ProductVersionMajor >= 10 120 AS Priority , 'Query Plans' AS FindingsGroup , 'RID or Key Lookups' AS Finding , - 'https://BrentOzar.com/go/lookup' AS URL , + 'https://www.brentozar.com/go/lookup' AS URL , 'One of the top resource-intensive queries contains RID or Key Lookups. Try to avoid them by creating covering indexes.' AS Details , qs.query_plan , qs.query_plan_filtered @@ -7514,7 +7459,7 @@ IF @ProductVersionMajor >= 10 120 AS Priority , 'Query Plans' AS FindingsGroup , 'Missing Index' AS Finding , - 'https://BrentOzar.com/go/missingindex' AS URL , + 'https://www.brentozar.com/go/missingindex' AS URL , ( 'One of the top resource-intensive queries may be dramatically improved by adding an index.' ) AS Details , qs.query_plan , qs.query_plan_filtered @@ -7545,7 +7490,7 @@ IF @ProductVersionMajor >= 10 120 AS Priority , 'Query Plans' AS FindingsGroup , 'Cursor' AS Finding , - 'https://BrentOzar.com/go/cursor' AS URL , + 'https://www.brentozar.com/go/cursor' AS URL , ( 'One of the top resource-intensive queries is using a cursor.' ) AS Details , qs.query_plan , qs.query_plan_filtered @@ -7577,7 +7522,7 @@ IF @ProductVersionMajor >= 10 120 AS Priority , 'Query Plans' AS FindingsGroup , 'Scalar UDFs' AS Finding , - 'https://BrentOzar.com/go/functions' AS URL , + 'https://www.brentozar.com/go/functions' AS URL , ( 'One of the top resource-intensive queries is using a user-defined scalar function that may inhibit parallelism.' ) AS Details , qs.query_plan , qs.query_plan_filtered @@ -7612,7 +7557,7 @@ IF @ProductVersionMajor >= 10 230 AS [Priority] , 'Security' AS [FindingsGroup] , 'Endpoints Owned by Users' AS [Finding] , - 'https://BrentOzar.com/go/owners' AS [URL] , + 'https://www.brentozar.com/go/owners' AS [URL] , ( 'Endpoint ' + ep.[name] + ' is owned by ' + SUSER_NAME(ep.principal_id) + '. If the endpoint owner login is disabled or not available due to Active Directory problems, the high availability will stop working.' ) AS [Details] FROM sys.database_mirroring_endpoints ep @@ -7643,7 +7588,7 @@ IF @ProductVersionMajor >= 10 200 AS Priority , 'Informational' AS FindingsGroup , '@@Servername Not Set' AS Finding , - 'https://BrentOzar.com/go/servername' AS URL , + 'https://www.brentozar.com/go/servername' AS URL , '@@Servername variable is null. You can fix it by executing: "sp_addserver '''', local"' AS Details; END; @@ -7674,7 +7619,7 @@ IF @ProductVersionMajor >= 10 200 AS Priority , 'Configuration' AS FindingsGroup , '@@Servername Not Correct' AS Finding , - 'https://BrentOzar.com/go/servername' AS URL , + 'https://www.brentozar.com/go/servername' AS URL , 'The @@Servername is different than the computer name, which may trigger certificate errors.' AS Details; END; @@ -7713,7 +7658,7 @@ IF @ProductVersionMajor >= 10 200 AS Priority , 'Monitoring' AS FindingsGroup , 'No Failsafe Operator Configured' AS Finding , - 'https://BrentOzar.com/go/failsafe' AS URL , + 'https://www.brentozar.com/go/failsafe' AS URL , ( 'No failsafe operator is configured on this server. This is a good idea just in-case there are issues with the [msdb] database that prevents alerting.' ) AS Details FROM @AlertInfo WHERE FailSafeOperator IS NULL; @@ -7792,7 +7737,7 @@ IF @ProductVersionMajor >= 10 50 AS Priority , 'Performance' AS FindingGroup , 'Poison Wait Detected: CMEMTHREAD & NUMA' AS Finding , - 'https://BrentOzar.com/go/poison' AS URL , + 'https://www.brentozar.com/go/poison' AS URL , CONVERT(VARCHAR(10), (MAX([wait_time_ms]) / 1000) / 86400) + ':' + CONVERT(VARCHAR(20), DATEADD(s, (MAX([wait_time_ms]) / 1000), 0), 108) + ' of this wait have been recorded' + CASE WHEN ts.status = 1 THEN ' despite enabling trace flag 8048 already.' ELSE '. In servers with over 8 cores per NUMA node, when CMEMTHREAD waits are a bottleneck, trace flag 8048 may be needed.' @@ -7830,7 +7775,7 @@ IF @ProductVersionMajor >= 10 50 AS Priority , 'Reliability' AS FindingsGroup , 'Transaction Log Larger than Data File' AS Finding , - 'https://BrentOzar.com/go/biglog' AS URL , + 'https://www.brentozar.com/go/biglog' AS URL , 'The database [' + DB_NAME(a.database_id) + '] has a ' + CAST((CAST(a.size AS BIGINT) * 8 / 1000000) AS NVARCHAR(20)) + ' GB transaction log file, larger than the total data file sizes. This may indicate that transaction log backups are not being performed or not performed often enough.' AS Details FROM sys.master_files a @@ -7874,7 +7819,7 @@ IF @ProductVersionMajor >= 10 200 AS Priority , 'Informational' AS FindingsGroup , 'Collation is ' + collation_name AS Finding , - 'https://BrentOzar.com/go/collate' AS URL , + 'https://www.brentozar.com/go/collate' AS URL , 'Collation differences between user databases and tempdb can cause conflicts especially when comparing string values' AS Details FROM sys.databases WHERE name NOT IN ( 'master', 'model', 'msdb') @@ -7913,7 +7858,7 @@ IF @ProductVersionMajor >= 10 170 AS Priority , 'Reliability' AS FindingsGroup , 'Database Snapshot Online' AS Finding , - 'https://BrentOzar.com/go/snapshot' AS URL , + 'https://www.brentozar.com/go/snapshot' AS URL , 'Database [' + dSnap.[name] + '] is a snapshot of [' + dOriginal.[name] @@ -7951,7 +7896,7 @@ IF @ProductVersionMajor >= 10 END AS Priority, 'Performance' AS FindingsGroup , 'Shrink Database Job' AS Finding , - 'https://BrentOzar.com/go/autoshrink' AS URL , + 'https://www.brentozar.com/go/autoshrink' AS URL , 'In the [' + j.[name] + '] job, step [' + step.[step_name] + '] has SHRINKDATABASE or SHRINKFILE, which may be causing database fragmentation.' @@ -8021,7 +7966,7 @@ IF @ProductVersionMajor >= 10 200 AS Priority , 'Informational' AS FindingsGroup , 'Agent Jobs Starting Simultaneously' AS Finding , - 'https://BrentOzar.com/go/busyagent/' AS URL , + 'https://www.brentozar.com/go/busyagent/' AS URL , ( 'Multiple SQL Server Agent jobs are configured to start simultaneously. For detailed schedule listings, see the query in the URL.' ) AS Details FROM msdb.dbo.sysjobactivity WHERE start_execution_date > DATEADD(dd, -14, GETDATE()) @@ -8137,7 +8082,7 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 250 AS [Priority] , 'Server Info' AS [FindingsGroup] , 'Locked Pages In Memory Enabled' AS [Finding] , - 'https://BrentOzar.com/go/lpim' AS [URL] , + 'https://www.brentozar.com/go/lpim' AS [URL] , ( 'You currently have ' + CASE WHEN [dopm].[locked_page_allocations_kb] / 1024. / 1024. > 0 THEN CAST([dopm].[locked_page_allocations_kb] / 1024 / 1024 AS VARCHAR(100)) @@ -8169,7 +8114,7 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 250 AS Priority , ''Server Info'' AS FindingsGroup , ''Memory Model Unconventional'' AS Finding , - ''https://BrentOzar.com/go/lpim'' AS URL , + ''https://www.brentozar.com/go/lpim'' AS URL , ''Memory Model: '' + CAST(sql_memory_model_desc AS NVARCHAR(100)) FROM sys.dm_os_sys_info WHERE sql_memory_model <> 1 OPTION (RECOMPILE);'; @@ -8208,7 +8153,7 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 250 AS [Priority] , 'Server Info' AS [FindingsGroup] , 'Instant File Initialization Enabled' AS [Finding] , - 'https://BrentOzar.com/go/instant' AS [URL] , + 'https://www.brentozar.com/go/instant' AS [URL] , 'The service account has the Perform Volume Maintenance Tasks permission.'; END; @@ -8230,7 +8175,7 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 50 AS Priority , ''Server Info'' AS FindingsGroup , ''Instant File Initialization Not Enabled'' AS Finding , - ''https://BrentOzar.com/go/instant'' AS URL , + ''https://www.brentozar.com/go/instant'' AS URL , ''Consider enabling IFI for faster restores and data file growths.'' FROM sys.dm_server_services WHERE instant_file_initialization_enabled <> ''Y'' AND filename LIKE ''%sqlservr.exe%'' OPTION (RECOMPILE);'; @@ -8259,7 +8204,7 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 250 AS Priority , 'Server Info' AS FindingsGroup , 'Server Name' AS Finding , - 'https://BrentOzar.com/go/servername' AS URL , + 'https://www.brentozar.com/go/servername' AS URL , @@SERVERNAME AS Details WHERE @@SERVERNAME IS NOT NULL; END; @@ -8530,7 +8475,7 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 250 AS Priority, ''Server Info'' AS FindingsGroup, ''Virtual Server'' AS Finding, - ''https://BrentOzar.com/go/virtual'' AS URL, + ''https://www.brentozar.com/go/virtual'' AS URL, ''Type: ('' + virtual_machine_type_desc + '')'' AS Details FROM sys.dm_os_sys_info WHERE virtual_machine_type <> 0 OPTION (RECOMPILE);'; @@ -8558,7 +8503,7 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 250 AS Priority, ''Server Info'' AS FindingsGroup, ''Container'' AS Finding, - ''https://BrentOzar.com/go/virtual'' AS URL, + ''https://www.brentozar.com/go/virtual'' AS URL, ''Type: ('' + container_type_desc + '')'' AS Details FROM sys.dm_os_sys_info WHERE container_type_desc <> ''NONE'' OPTION (RECOMPILE);'; @@ -8731,7 +8676,7 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 ,250 AS Priority ,'Server Info' AS FindingsGroup ,'Default Trace Contents' AS Finding - ,'https://BrentOzar.com/go/trace' AS URL + ,'https://www.brentozar.com/go/trace' AS URL ,'The default trace holds '+cast(DATEDIFF(hour,MIN(StartTime),GETDATE())as VARCHAR(30))+' hours of data' +' between '+cast(Min(StartTime) as VARCHAR(30))+' and '+cast(GETDATE()as VARCHAR(30)) +('. The default trace files are located in: '+left( @curr_tracefilename,len(@curr_tracefilename) - @indx) @@ -8811,7 +8756,7 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 URL , Details ) - VALUES (153, 240, 'Wait Stats', 'No Significant Waits Detected', 'https://BrentOzar.com/go/waits', 'This server might be just sitting around idle, or someone may have cleared wait stats recently.'); + VALUES (153, 240, 'Wait Stats', 'No Significant Waits Detected', 'https://www.brentozar.com/go/waits', 'This server might be just sitting around idle, or someone may have cleared wait stats recently.'); END; END; /* CheckID 152 */ @@ -9474,7 +9419,7 @@ AS SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.01', @VersionDate = '20210222'; + SELECT @Version = '8.02', @VersionDate = '20210321'; IF(@VersionCheckMode = 1) BEGIN @@ -11219,6 +11164,7 @@ ALTER PROCEDURE dbo.sp_BlitzCache @UseTriggersAnyway BIT = NULL, @ExportToExcel BIT = 0, @ExpertMode TINYINT = 0, + @OutputType VARCHAR(20) = 'TABLE' , @OutputServerName NVARCHAR(258) = NULL , @OutputDatabaseName NVARCHAR(258) = NULL , @OutputSchemaName NVARCHAR(258) = NULL , @@ -11253,8 +11199,8 @@ BEGIN SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.01', @VersionDate = '20210222'; - +SELECT @Version = '8.02', @VersionDate = '20210321'; +SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) BEGIN @@ -11343,7 +11289,11 @@ IF @Help = 1 SELECT N'@ExpertMode', N'TINYINT', N'Default 0. When set to 1, results include more columns. When 2, mode is optimized for Opserver, the open source dashboard.' - + UNION ALL + SELECT N'@OutputType', + N'NVARCHAR(258)', + N'If set to "NONE", this will tell this procedure not to run any query leading to a results set thrown to caller.' + UNION ALL SELECT N'@OutputDatabaseName', N'NVARCHAR(128)', @@ -11372,7 +11322,7 @@ IF @Help = 1 UNION ALL SELECT N'@IgnoreSystemDBs', N'BIT', - N'Ignores plans found in the system databases (master, model, msdb, tempdb, and resourcedb)' + N'Ignores plans found in the system databases (master, model, msdb, tempdb, dbmaintenance, dbadmin, dbatools and resourcedb)' UNION ALL SELECT N'@OnlyQueryHashes', @@ -11730,6 +11680,18 @@ BEGIN RETURN; END; +IF(@OutputType = 'NONE' AND (@OutputTableName IS NULL OR @OutputSchemaName IS NULL OR @OutputDatabaseName IS NULL)) +BEGIN + RAISERROR('This procedure should be called with a value for all @Output* parameters, as @OutputType is set to NONE',12,1); + RETURN; +END; + +IF(@OutputType = 'NONE') +BEGIN + SET @HideSummary = 1; +END; + + /* Lets get @SortOrder set to lower case here for comparisons later */ SET @SortOrder = LOWER(@SortOrder); @@ -12922,7 +12884,7 @@ SET @body += N' WHERE 1 = 1 ' + @nl ; IF @IgnoreSystemDBs = 1 BEGIN RAISERROR(N'Ignoring system databases by default', 0, 1) WITH NOWAIT; - SET @body += N' AND COALESCE(DB_NAME(CAST(xpa.value AS INT)), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'') AND COALESCE(DB_NAME(CAST(xpa.value AS INT)), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ; + SET @body += N' AND COALESCE(LOWER(DB_NAME(CAST(xpa.value AS INT))), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'', ''dbmaintenance'', ''dbadmin'', ''dbatools'') AND COALESCE(DB_NAME(CAST(xpa.value AS INT)), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ; END; IF @DatabaseName IS NOT NULL OR @DatabaseName <> N'' @@ -13357,7 +13319,7 @@ BEGIN SET @sql += @body_where ; IF @IgnoreSystemDBs = 1 - SET @sql += N' AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'') AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ; + SET @sql += N' AND COALESCE(LOWER(DB_NAME(database_id)), LOWER(CAST(pa.value AS sysname)), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'', ''dbmaintenance'', ''dbadmin'', ''dbatools'') AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ; SET @sql += @sort_filter + @nl; @@ -13389,7 +13351,7 @@ BEGIN SET @sql += @body_where ; IF @IgnoreSystemDBs = 1 - SET @sql += N' AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'') AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ; + SET @sql += N' AND COALESCE(LOWER(DB_NAME(database_id)), LOWER(CAST(pa.value AS sysname)), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'', ''dbmaintenance'', ''dbadmin'', ''dbatools'') AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ; SET @sql += @sort_filter + @nl; @@ -13425,7 +13387,7 @@ BEGIN SET @sql += @body_where ; IF @IgnoreSystemDBs = 1 - SET @sql += N' AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'') AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ; + SET @sql += N' AND COALESCE(LOWER(DB_NAME(database_id)), LOWER(CAST(pa.value AS sysname)), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'', ''dbmaintenance'', ''dbadmin'', ''dbatools'') AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ; SET @sql += @sort_filter + @nl; @@ -15076,7 +15038,7 @@ SELECT spi.SPID, ELSE @nl + N' We could not find any cached parameter values for this stored proc. ' END + CASE WHEN spi2.variable_name = N'**no_variable**' OR spi2.compile_time_value = N'**idk_man**' - THEN @nl + N'More info on possible reasons: https://BrentOzar.com/go/noplans ' + THEN @nl + N'More info on possible reasons: https://www.brentozar.com/go/noplans ' WHEN spi2.compile_time_value = N'NULL' THEN spi2.compile_time_value ELSE RTRIM(spi2.compile_time_value) @@ -15119,7 +15081,7 @@ SELECT spi.SPID, ELSE @nl + N' We could not find any cached parameter values for this stored proc. ' END + CASE WHEN spi2.variable_name = N'**no_variable**' OR spi2.compile_time_value = N'**idk_man**' - THEN @nl + N' More info on possible reasons: https://BrentOzar.com/go/noplans ' + THEN @nl + N' More info on possible reasons: https://www.brentozar.com/go/noplans ' WHEN spi2.compile_time_value = N'NULL' THEN spi2.compile_time_value ELSE RTRIM(spi2.compile_time_value) @@ -15723,7 +15685,7 @@ FROM ##BlitzCacheProcs b ) UPDATE b SET Warnings = ISNULL('Your query plan is >128 levels of nested nodes, and can''t be converted to XML. Use SELECT * FROM sys.dm_exec_text_query_plan('+ CONVERT(VARCHAR(128), ph.PlanHandle, 1) + ', 0, -1) to get more information' - , 'We couldn''t find a plan for this query. More info on possible reasons: https://BrentOzar.com/go/noplans') + , 'We couldn''t find a plan for this query. More info on possible reasons: https://www.brentozar.com/go/noplans') FROM ##BlitzCacheProcs b LEFT JOIN plan_handle ph ON b.PlanHandle = ph.PlanHandle @@ -16106,9 +16068,10 @@ IF @Debug = 1 PRINT SUBSTRING(@sql, 32000, 36000); PRINT SUBSTRING(@sql, 36000, 40000); END; - -EXEC sp_executesql @sql, N'@Top INT, @spid INT, @minimumExecutionCount INT, @min_back INT', @Top, @@SPID, @MinimumExecutionCount, @MinutesBack; - +IF(@OutputType <> 'NONE') +BEGIN + EXEC sp_executesql @sql, N'@Top INT, @spid INT, @minimumExecutionCount INT, @min_back INT', @Top, @@SPID, @MinimumExecutionCount, @MinutesBack; +END; /* @@ -16205,7 +16168,7 @@ BEGIN 100, 'Execution Pattern', 'Frequent Execution', - 'http://brentozar.com/blitzcache/frequently-executed-queries/', + 'https://www.brentozar.com/blitzcache/frequently-executed-queries/', 'Queries are being executed more than ' + CAST (@execution_threshold AS VARCHAR(5)) + ' times per minute. This can put additional load on the server, even when queries are lightweight.') ; @@ -16220,7 +16183,7 @@ BEGIN 50, 'Parameterization', 'Parameter Sniffing', - 'http://brentozar.com/blitzcache/parameter-sniffing/', + 'https://www.brentozar.com/blitzcache/parameter-sniffing/', 'There are signs of parameter sniffing (wide variance in rows return or time to execute). Investigate query patterns and tune code appropriately.') ; /* Forced execution plans */ @@ -16234,7 +16197,7 @@ BEGIN 50, 'Parameterization', 'Forced Plan', - 'http://brentozar.com/blitzcache/forced-plans/', + 'https://www.brentozar.com/blitzcache/forced-plans/', 'Execution plans have been compiled with forced plans, either through FORCEPLAN, plan guides, or forced parameterization. This will make general tuning efforts less effective.'); IF EXISTS (SELECT 1/0 @@ -16247,7 +16210,7 @@ BEGIN 200, 'Cursors', 'Cursor', - 'http://brentozar.com/blitzcache/cursors-found-slow-queries/', + 'https://www.brentozar.com/blitzcache/cursors-found-slow-queries/', 'There are cursors in the plan cache. This is neither good nor bad, but it is a thing. Cursors are weird in SQL Server.'); IF EXISTS (SELECT 1/0 @@ -16261,7 +16224,7 @@ BEGIN 200, 'Cursors', 'Optimistic Cursors', - 'http://brentozar.com/blitzcache/cursors-found-slow-queries/', + 'https://www.brentozar.com/blitzcache/cursors-found-slow-queries/', 'There are optimistic cursors in the plan cache, which can harm performance.'); IF EXISTS (SELECT 1/0 @@ -16275,7 +16238,7 @@ BEGIN 200, 'Cursors', 'Non-forward Only Cursors', - 'http://brentozar.com/blitzcache/cursors-found-slow-queries/', + 'https://www.brentozar.com/blitzcache/cursors-found-slow-queries/', 'There are non-forward only cursors in the plan cache, which can harm performance.'); IF EXISTS (SELECT 1/0 @@ -16289,7 +16252,7 @@ BEGIN 200, 'Cursors', 'Dynamic Cursors', - 'http://brentozar.com/blitzcache/cursors-found-slow-queries/', + 'https://www.brentozar.com/blitzcache/cursors-found-slow-queries/', 'Dynamic Cursors inhibit parallelism!.'); IF EXISTS (SELECT 1/0 @@ -16303,7 +16266,7 @@ BEGIN 200, 'Cursors', 'Fast Forward Cursors', - 'http://brentozar.com/blitzcache/cursors-found-slow-queries/', + 'https://www.brentozar.com/blitzcache/cursors-found-slow-queries/', 'Fast forward cursors inhibit parallelism!.'); IF EXISTS (SELECT 1/0 @@ -16316,7 +16279,7 @@ BEGIN 50, 'Parameterization', 'Forced Parameterization', - 'http://brentozar.com/blitzcache/forced-parameterization/', + 'https://www.brentozar.com/blitzcache/forced-parameterization/', 'Execution plans have been compiled with forced parameterization.') ; IF EXISTS (SELECT 1/0 @@ -16329,7 +16292,7 @@ BEGIN 200, 'Execution Plans', 'Parallel', - 'http://brentozar.com/blitzcache/parallel-plans-detected/', + 'https://www.brentozar.com/blitzcache/parallel-plans-detected/', 'Parallel plans detected. These warrant investigation, but are neither good nor bad.') ; IF EXISTS (SELECT 1/0 @@ -16342,7 +16305,7 @@ BEGIN 200, 'Execution Plans', 'Nearly Parallel', - 'http://brentozar.com/blitzcache/query-cost-near-cost-threshold-parallelism/', + 'https://www.brentozar.com/blitzcache/query-cost-near-cost-threshold-parallelism/', 'Queries near the cost threshold for parallelism. These may go parallel when you least expect it.') ; IF EXISTS (SELECT 1/0 @@ -16355,7 +16318,7 @@ BEGIN 50, 'Execution Plans', 'Plan Warnings', - 'http://brentozar.com/blitzcache/query-plan-warnings/', + 'https://www.brentozar.com/blitzcache/query-plan-warnings/', 'Warnings detected in execution plans. SQL Server is telling you that something bad is going on that requires your attention.') ; IF EXISTS (SELECT 1/0 @@ -16368,7 +16331,7 @@ BEGIN 50, 'Performance', 'Long Running Query', - 'http://brentozar.com/blitzcache/long-running-queries/', + 'https://www.brentozar.com/blitzcache/long-running-queries/', 'Long running queries have been found. These are queries with an average duration longer than ' + CAST(@long_running_query_warning_seconds / 1000 / 1000 AS VARCHAR(5)) + ' second(s). These queries should be investigated for additional tuning options.') ; @@ -16383,7 +16346,7 @@ BEGIN 50, 'Performance', 'Missing Indexes', - 'http://brentozar.com/blitzcache/missing-index-request/', + 'https://www.brentozar.com/blitzcache/missing-index-request/', 'Queries found with missing indexes.'); IF EXISTS (SELECT 1/0 @@ -16396,7 +16359,7 @@ BEGIN 200, 'Cardinality', 'Downlevel CE', - 'http://brentozar.com/blitzcache/legacy-cardinality-estimator/', + 'https://www.brentozar.com/blitzcache/legacy-cardinality-estimator/', 'A legacy cardinality estimator is being used by one or more queries. Investigate whether you need to be using this cardinality estimator. This may be caused by compatibility levels, global trace flags, or query level trace flags.'); IF EXISTS (SELECT 1/0 @@ -16409,7 +16372,7 @@ BEGIN 50, 'Performance', 'Implicit Conversions', - 'http://brentozar.com/go/implicit', + 'https://www.brentozar.com/go/implicit', 'One or more queries are comparing two fields that are not of the same data type.') ; IF EXISTS (SELECT 1/0 @@ -16422,7 +16385,7 @@ BEGIN 100, 'Performance', 'Busy Loops', - 'http://brentozar.com/blitzcache/busy-loops/', + 'https://www.brentozar.com/blitzcache/busy-loops/', 'Operations have been found that are executed 100 times more often than the number of rows returned by each iteration. This is an indicator that something is off in query execution.'); IF EXISTS (SELECT 1/0 @@ -16435,7 +16398,7 @@ BEGIN 50, 'Performance', 'Function Join', - 'http://brentozar.com/blitzcache/tvf-join/', + 'https://www.brentozar.com/blitzcache/tvf-join/', 'Execution plans have been found that join to table valued functions (TVFs). TVFs produce inaccurate estimates of the number of rows returned and can lead to any number of query plan problems.'); IF EXISTS (SELECT 1/0 @@ -16448,7 +16411,7 @@ BEGIN 50, 'Execution Plans', 'Compilation Timeout', - 'http://brentozar.com/blitzcache/compilation-timeout/', + 'https://www.brentozar.com/blitzcache/compilation-timeout/', 'Query compilation timed out for one or more queries. SQL Server did not find a plan that meets acceptable performance criteria in the time allotted so the best guess was returned. There is a very good chance that this plan isn''t even below average - it''s probably terrible.'); IF EXISTS (SELECT 1/0 @@ -16461,7 +16424,7 @@ BEGIN 50, 'Execution Plans', 'Compile Memory Limit Exceeded', - 'http://brentozar.com/blitzcache/compile-memory-limit-exceeded/', + 'https://www.brentozar.com/blitzcache/compile-memory-limit-exceeded/', 'The optimizer has a limited amount of memory available. One or more queries are complex enough that SQL Server was unable to allocate enough memory to fully optimize the query. A best fit plan was found, and it''s probably terrible.'); IF EXISTS (SELECT 1/0 @@ -16474,7 +16437,7 @@ BEGIN 50, 'Execution Plans', 'No Join Predicate', - 'http://brentozar.com/blitzcache/no-join-predicate/', + 'https://www.brentozar.com/blitzcache/no-join-predicate/', 'Operators in a query have no join predicate. This means that all rows from one table will be matched with all rows from anther table producing a Cartesian product. That''s a whole lot of rows. This may be your goal, but it''s important to investigate why this is happening.'); IF EXISTS (SELECT 1/0 @@ -16487,7 +16450,7 @@ BEGIN 200, 'Execution Plans', 'Multiple Plans', - 'http://brentozar.com/blitzcache/multiple-plans/', + 'https://www.brentozar.com/blitzcache/multiple-plans/', 'Queries exist with multiple execution plans (as determined by query_plan_hash). Investigate possible ways to parameterize these queries or otherwise reduce the plan count.'); IF EXISTS (SELECT 1/0 @@ -16500,7 +16463,7 @@ BEGIN 100, 'Performance', 'Unmatched Indexes', - 'http://brentozar.com/blitzcache/unmatched-indexes', + 'https://www.brentozar.com/blitzcache/unmatched-indexes', 'An index could have been used, but SQL Server chose not to use it - likely due to parameterization and filtered indexes.'); IF EXISTS (SELECT 1/0 @@ -16513,7 +16476,7 @@ BEGIN 100, 'Parameterization', 'Unparameterized Query', - 'http://brentozar.com/blitzcache/unparameterized-queries', + 'https://www.brentozar.com/blitzcache/unparameterized-queries', 'Unparameterized queries found. These could be ad hoc queries, data exploration, or queries using "OPTIMIZE FOR UNKNOWN".'); IF EXISTS (SELECT 1/0 @@ -16526,7 +16489,7 @@ BEGIN 100, 'Execution Plans', 'Trivial Plans', - 'http://brentozar.com/blitzcache/trivial-plans', + 'https://www.brentozar.com/blitzcache/trivial-plans', 'Trivial plans get almost no optimization. If you''re finding these in the top worst queries, something may be going wrong.'); IF EXISTS (SELECT 1/0 @@ -16539,7 +16502,7 @@ BEGIN 10, 'Execution Plans', 'Forced Serialization', - 'http://www.brentozar.com/blitzcache/forced-serialization/', + 'https://www.brentozar.com/blitzcache/forced-serialization/', 'Something in your plan is forcing a serial query. Further investigation is needed if this is not by design.') ; IF EXISTS (SELECT 1/0 @@ -16552,7 +16515,7 @@ BEGIN 100, 'Execution Plans', 'Expensive Key Lookup', - 'http://www.brentozar.com/blitzcache/expensive-key-lookups/', + 'https://www.brentozar.com/blitzcache/expensive-key-lookups/', 'There''s a key lookup in your plan that costs >=50% of the total plan cost.') ; IF EXISTS (SELECT 1/0 @@ -16565,7 +16528,7 @@ BEGIN 100, 'Execution Plans', 'Expensive Remote Query', - 'http://www.brentozar.com/blitzcache/expensive-remote-query/', + 'https://www.brentozar.com/blitzcache/expensive-remote-query/', 'There''s a remote query in your plan that costs >=50% of the total plan cost.') ; IF EXISTS (SELECT 1/0 @@ -16657,7 +16620,7 @@ BEGIN 100, 'Warnings', 'Operator Warnings', - 'http://brentozar.com/blitzcache/query-plan-warnings/', + 'https://www.brentozar.com/blitzcache/query-plan-warnings/', 'Check the plan for more details.') ; IF EXISTS (SELECT 1/0 @@ -16761,7 +16724,7 @@ BEGIN 100, 'Execution Plans', 'Expensive Sort', - 'http://www.brentozar.com/blitzcache/expensive-sorts/', + 'https://www.brentozar.com/blitzcache/expensive-sorts/', 'There''s a sort in your plan that costs >=50% of the total plan cost.') ; IF EXISTS (SELECT 1/0 @@ -17002,7 +16965,7 @@ BEGIN 100, 'Functions', 'MSTVFs', - 'http://brentozar.com/blitzcache/tvf-join/', + 'https://www.brentozar.com/blitzcache/tvf-join/', 'Execution plans have been found that join to table valued functions (TVFs). TVFs produce inaccurate estimates of the number of rows returned and can lead to any number of query plan problems.'); IF EXISTS (SELECT 1/0 @@ -17179,7 +17142,7 @@ BEGIN N'Large USERSTORE_TOKENPERM cache: ' + CONVERT(NVARCHAR(11), @user_perm_gb_out) + N'GB', N'The USERSTORE_TOKENPERM is taking up ' + CONVERT(NVARCHAR(11), @user_perm_percent) + N'% of the buffer pool, and your plan cache seems to be unstable', - N'https://brentozar.com/go/userstore', + N'https://www.brentozar.com/go/userstore', N'A growing USERSTORE_TOKENPERM cache can cause the plan cache to clear out' IF @v >= 11 @@ -17820,11 +17783,12 @@ END; PRINT SUBSTRING(@AllSortSql, 32000, 36000); PRINT SUBSTRING(@AllSortSql, 36000, 40000); END; - +IF(@OutputType <> 'NONE') +BEGIN EXEC sys.sp_executesql @stmt = @AllSortSql, @params = N'@i_DatabaseName NVARCHAR(128), @i_Top INT, @i_SkipAnalysis BIT, @i_OutputDatabaseName NVARCHAR(258), @i_OutputSchemaName NVARCHAR(258), @i_OutputTableName NVARCHAR(258), @i_CheckDateOverride DATETIMEOFFSET, @i_MinutesBack INT ', @i_DatabaseName = @DatabaseName, @i_Top = @Top, @i_SkipAnalysis = @SkipAnalysis, @i_OutputDatabaseName = @OutputDatabaseName, @i_OutputSchemaName = @OutputSchemaName, @i_OutputTableName = @OutputTableName, @i_CheckDateOverride = @CheckDateOverride, @i_MinutesBack = @MinutesBack; - +END; /*End of AllSort section*/ @@ -18439,7 +18403,7 @@ AS SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.01', @VersionDate = '20210222'; +SELECT @Version = '8.02', @VersionDate = '20210321'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -19153,6 +19117,7 @@ IF @GetAllDatabases = 1 AND database_id > 4 AND DB_NAME(database_id) NOT LIKE 'ReportServer%' AND DB_NAME(database_id) NOT LIKE 'rdsadmin%' + AND LOWER(name) NOT IN('dbatools', 'dbadmin', 'dbmaintenance') AND is_distributor = 0 OPTION ( RECOMPILE ); @@ -23913,7 +23878,7 @@ BEGIN SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.01', @VersionDate = '20210222'; +SELECT @Version = '8.02', @VersionDate = '20210321'; IF(@VersionCheckMode = 1) @@ -24616,6 +24581,7 @@ You need to use an Azure storage account, and the path has to look like this: ht ) AS step_id FROM #deadlock_process AS dp WHERE dp.client_app LIKE 'SQLAgent - %' + AND dp.client_app <> 'SQLAgent - Initial Boot Probe' ) AS x OPTION ( RECOMPILE ); @@ -25169,18 +25135,18 @@ You need to use an Azure storage account, and the path has to look like this: ht dp.priority, dp.log_used, dp.wait_resource COLLATE DATABASE_DEFAULT AS wait_resource, - CONVERT( - XML, - STUFF(( SELECT DISTINCT NCHAR(10) + CONVERT( + XML, + STUFF(( SELECT DISTINCT NCHAR(10) + N' ' + ISNULL(c.object_name, N'') + N' ' COLLATE DATABASE_DEFAULT AS object_name - FROM #deadlock_owner_waiter AS c + FROM #deadlock_owner_waiter AS c WHERE ( dp.id = c.owner_id - OR dp.victim_id = c.waiter_id ) - AND CONVERT(DATE, dp.event_date) = CONVERT(DATE, c.event_date) - FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(4000)'), - 1, 1, N'')) AS object_names, + OR dp.victim_id = c.waiter_id ) + AND dp.event_date = c.event_date + FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(4000)'), + 1, 1, N'')) AS object_names, dp.wait_time, dp.transaction_name, dp.last_tran_started, @@ -25193,7 +25159,6 @@ You need to use an Azure storage account, and the path has to look like this: ht dp.login_name, dp.isolation_level, dp.process_xml.value('(//process/inputbuf/text())[1]', 'NVARCHAR(MAX)') AS inputbuf, - ROW_NUMBER() OVER ( PARTITION BY dp.event_date, dp.id ORDER BY dp.event_date ) AS dn, DENSE_RANK() OVER ( ORDER BY dp.event_date ) AS en, ROW_NUMBER() OVER ( PARTITION BY dp.event_date ORDER BY dp.event_date ) -1 AS qn, dp.is_victim, @@ -25226,20 +25191,18 @@ You need to use an Azure storage account, and the path has to look like this: ht dp.priority, dp.log_used, dp.wait_resource COLLATE DATABASE_DEFAULT, - CASE WHEN @ExportToExcel = 0 THEN - CONVERT( - XML, - STUFF(( SELECT DISTINCT NCHAR(10) - + N' ' - + ISNULL(c.object_name, N'') - + N' ' COLLATE DATABASE_DEFAULT AS object_name - FROM #deadlock_owner_waiter AS c - WHERE ( dp.id = c.owner_id - OR dp.victim_id = c.waiter_id ) - AND CONVERT(DATE, dp.event_date) = CONVERT(DATE, c.event_date) - FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(4000)'), - 1, 1, N'')) - ELSE NULL END AS object_names, + CONVERT( + XML, + STUFF(( SELECT DISTINCT NCHAR(10) + + N' ' + + ISNULL(c.object_name, N'') + + N' ' COLLATE DATABASE_DEFAULT AS object_name + FROM #deadlock_owner_waiter AS c + WHERE ( dp.id = c.owner_id + OR dp.victim_id = c.waiter_id ) + AND dp.event_date = c.event_date + FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(4000)'), + 1, 1, N'')) AS object_names, dp.wait_time, dp.transaction_name, dp.last_tran_started, @@ -25252,7 +25215,6 @@ You need to use an Azure storage account, and the path has to look like this: ht dp.login_name, dp.isolation_level, dp.process_xml.value('(//process/inputbuf/text())[1]', 'NVARCHAR(MAX)') AS inputbuf, - ROW_NUMBER() OVER ( PARTITION BY dp.event_date, dp.id ORDER BY dp.event_date ) AS dn, DENSE_RANK() OVER ( ORDER BY dp.event_date ) AS en, ROW_NUMBER() OVER ( PARTITION BY dp.event_date ORDER BY dp.event_date ) -1 AS qn, 1 AS is_victim, @@ -25354,7 +25316,7 @@ You need to use an Azure storage account, and the path has to look like this: ht FROM deadlocks AS d WHERE d.dn = 1 AND (is_victim = @VictimsOnly OR @VictimsOnly = 0) - AND d.en < CASE WHEN d.deadlock_type = N'Parallel Deadlock' THEN 2 ELSE 2147483647 END + AND d.qn < CASE WHEN d.deadlock_type = N'Parallel Deadlock' THEN 2 ELSE 2147483647 END AND (DB_NAME(d.database_id) = @DatabaseName OR @DatabaseName IS NULL) AND (d.event_date >= @StartDate OR @StartDate IS NULL) AND (d.event_date < @EndDate OR @EndDate IS NULL) @@ -25394,20 +25356,18 @@ ELSE --Output to database is not set output to client app dp.priority, dp.log_used, dp.wait_resource COLLATE DATABASE_DEFAULT AS wait_resource, - CASE WHEN @ExportToExcel = 0 THEN - CONVERT( - XML, - STUFF(( SELECT DISTINCT NCHAR(10) - + N' ' - + ISNULL(c.object_name, N'') - + N' ' COLLATE DATABASE_DEFAULT AS object_name - FROM #deadlock_owner_waiter AS c - WHERE ( dp.id = c.owner_id - OR dp.victim_id = c.waiter_id ) - AND CONVERT(DATE, dp.event_date) = CONVERT(DATE, c.event_date) - FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(4000)'), - 1, 1, N'')) - ELSE NULL END AS object_names, + CONVERT( + XML, + STUFF(( SELECT DISTINCT NCHAR(10) + + N' ' + + ISNULL(c.object_name, N'') + + N' ' COLLATE DATABASE_DEFAULT AS object_name + FROM #deadlock_owner_waiter AS c + WHERE ( dp.id = c.owner_id + OR dp.victim_id = c.waiter_id ) + AND dp.event_date = c.event_date + FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(4000)'), + 1, 1, N'')) AS object_names, dp.wait_time, dp.transaction_name, dp.last_tran_started, @@ -25420,7 +25380,6 @@ ELSE --Output to database is not set output to client app dp.login_name, dp.isolation_level, dp.process_xml.value('(//process/inputbuf/text())[1]', 'NVARCHAR(MAX)') AS inputbuf, - ROW_NUMBER() OVER ( PARTITION BY dp.event_date, dp.id ORDER BY dp.event_date ) AS dn, DENSE_RANK() OVER ( ORDER BY dp.event_date ) AS en, ROW_NUMBER() OVER ( PARTITION BY dp.event_date ORDER BY dp.event_date ) -1 AS qn, dp.is_victim, @@ -25453,20 +25412,18 @@ ELSE --Output to database is not set output to client app dp.priority, dp.log_used, dp.wait_resource COLLATE DATABASE_DEFAULT, - CASE WHEN @ExportToExcel = 0 THEN - CONVERT( - XML, - STUFF(( SELECT DISTINCT NCHAR(10) - + N' ' - + ISNULL(c.object_name, N'') - + N' ' COLLATE DATABASE_DEFAULT AS object_name - FROM #deadlock_owner_waiter AS c - WHERE ( dp.id = c.owner_id - OR dp.victim_id = c.waiter_id ) - AND CONVERT(DATE, dp.event_date) = CONVERT(DATE, c.event_date) - FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(4000)'), - 1, 1, N'')) - ELSE NULL END AS object_names, + CONVERT( + XML, + STUFF(( SELECT DISTINCT NCHAR(10) + + N' ' + + ISNULL(c.object_name, N'') + + N' ' COLLATE DATABASE_DEFAULT AS object_name + FROM #deadlock_owner_waiter AS c + WHERE ( dp.id = c.owner_id + OR dp.victim_id = c.waiter_id ) + AND dp.event_date = c.event_date + FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(4000)'), + 1, 1, N'')) AS object_names, dp.wait_time, dp.transaction_name, dp.last_tran_started, @@ -25479,7 +25436,6 @@ ELSE --Output to database is not set output to client app dp.login_name, dp.isolation_level, dp.process_xml.value('(//process/inputbuf/text())[1]', 'NVARCHAR(MAX)') AS inputbuf, - ROW_NUMBER() OVER ( PARTITION BY dp.event_date, dp.id ORDER BY dp.event_date ) AS dn, DENSE_RANK() OVER ( ORDER BY dp.event_date ) AS en, ROW_NUMBER() OVER ( PARTITION BY dp.event_date ORDER BY dp.event_date ) -1 AS qn, 1 AS is_victim, @@ -25512,8 +25468,8 @@ ELSE --Output to database is not set output to client app + CASE WHEN d.qn = 0 THEN N'1' ELSE CONVERT(NVARCHAR(10), d.qn) END + CASE WHEN d.is_victim = 1 THEN ' - VICTIM' ELSE '' END AS deadlock_group, - CASE WHEN @ExportToExcel = 0 THEN CONVERT(XML, N'') - ELSE SUBSTRING(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(LTRIM(RTRIM(d.inputbuf)),' ','<>'),'><',''),NCHAR(10), ' '),NCHAR(13), ' '),'<>',' '), 1, 32000) END AS query, + CONVERT(XML, N'') AS query_xml, + d.inputbuf AS query_string, d.object_names, d.isolation_level, d.owner_mode, @@ -25542,11 +25498,13 @@ ELSE --Output to database is not set output to client app d.waiter_merging, d.waiter_spilling, d.waiter_waiting_to_close, - CASE WHEN @ExportToExcel = 0 THEN d.deadlock_graph ELSE NULL END AS deadlock_graph - FROM deadlocks AS d + d.deadlock_graph, + d.is_victim + INTO #deadlock_results + FROM deadlocks AS d WHERE d.dn = 1 - AND (is_victim = @VictimsOnly OR @VictimsOnly = 0) - AND d.en < CASE WHEN d.deadlock_type = N'Parallel Deadlock' THEN 2 ELSE 2147483647 END + AND (d.is_victim = @VictimsOnly OR @VictimsOnly = 0) + AND d.qn < CASE WHEN d.deadlock_type = N'Parallel Deadlock' THEN 2 ELSE 2147483647 END AND (DB_NAME(d.database_id) = @DatabaseName OR @DatabaseName IS NULL) AND (d.event_date >= @StartDate OR @StartDate IS NULL) AND (d.event_date < @EndDate OR @EndDate IS NULL) @@ -25554,9 +25512,67 @@ ELSE --Output to database is not set output to client app AND (d.client_app = @AppName OR @AppName IS NULL) AND (d.host_name = @HostName OR @HostName IS NULL) AND (d.login_name = @LoginName OR @LoginName IS NULL) - ORDER BY d.event_date, is_victim DESC OPTION ( RECOMPILE ); + + DECLARE @deadlock_result NVARCHAR(MAX) = N'' + + SET @deadlock_result += N' + SELECT + dr.deadlock_type, + dr.event_date, + dr.database_name, + dr.deadlock_group, + ' + + CASE @ExportToExcel + WHEN 1 + THEN N'dr.query_string AS query, + REPLACE(REPLACE(CONVERT(NVARCHAR(MAX), dr.object_names), '''', ''''), '''', '''') AS object_names,' + ELSE N'dr.query_xml AS query, + dr.object_names,' + END + + N' + dr.isolation_level, + dr.owner_mode, + dr.waiter_mode, + dr.transaction_count, + dr.login_name, + dr.host_name, + dr.client_app, + dr.wait_time, + dr.priority, + dr.log_used, + dr.last_tran_started, + dr.last_batch_started, + dr.last_batch_completed, + dr.transaction_name, + dr.owner_waiter_type, + dr.owner_activity, + dr.owner_waiter_activity, + dr.owner_merging, + dr.owner_spilling, + dr.owner_waiting_to_close, + dr.waiter_waiter_type, + dr.waiter_owner_activity, + dr.waiter_waiter_activity, + dr.waiter_merging, + dr.waiter_spilling, + dr.waiter_waiting_to_close' + + CASE @ExportToExcel + WHEN 1 + THEN N'' + ELSE N', + dr.deadlock_graph' + END + + ' + FROM #deadlock_results AS dr + ORDER BY dr.event_date, dr.is_victim DESC + OPTION(RECOMPILE); + ' + + EXEC sys.sp_executesql + @deadlock_result; + SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); RAISERROR('Findings %s', 0, 1, @d) WITH NOWAIT; SELECT df.check_id, df.database_name, df.object_name, df.finding_group, df.finding @@ -25596,7 +25612,11 @@ ELSE --Output to database is not set output to client app SELECT '#deadlock_stack' AS table_name, * FROM #deadlock_stack AS ds OPTION ( RECOMPILE ); - + + SELECT '#deadlock_results' AS table_name, * + FROM #deadlock_results AS dr + OPTION ( RECOMPILE ); + END; -- End debug END; --Final End @@ -25633,7 +25653,7 @@ BEGIN SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.01', @VersionDate = '20210222'; + SELECT @Version = '8.02', @VersionDate = '20210321'; IF(@VersionCheckMode = 1) BEGIN @@ -26048,18 +26068,18 @@ IF @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @Output + N' [request_cpu_time], ' + @LineFeed + N' [degree_of_parallelism], ' + @LineFeed + N' [request_logical_reads], ' + @LineFeed - + N' ((CAST([request_logical_reads] AS MONEY)* 8)/ 1024) [Logical_Reads_MB], ' + @LineFeed + + N' ((CAST([request_logical_reads] AS DECIMAL(38,2))* 8)/ 1024) [Logical_Reads_MB], ' + @LineFeed + N' [request_writes], ' + @LineFeed - + N' ((CAST([request_writes] AS MONEY)* 8)/ 1024) [Logical_Writes_MB], ' + @LineFeed + + N' ((CAST([request_writes] AS DECIMAL(38,2))* 8)/ 1024) [Logical_Writes_MB], ' + @LineFeed + N' [request_physical_reads], ' + @LineFeed - + N' ((CAST([request_physical_reads] AS MONEY)* 8)/ 1024) [Physical_reads_MB], ' + @LineFeed + + N' ((CAST([request_physical_reads] AS DECIMAL(38,2))* 8)/ 1024) [Physical_reads_MB], ' + @LineFeed + N' [session_cpu], ' + @LineFeed + N' [session_logical_reads], ' + @LineFeed - + N' ((CAST([session_logical_reads] AS MONEY)* 8)/ 1024) [session_logical_reads_MB], ' + @LineFeed + + N' ((CAST([session_logical_reads] AS DECIMAL(38,2))* 8)/ 1024) [session_logical_reads_MB], ' + @LineFeed + N' [session_physical_reads], ' + @LineFeed - + N' ((CAST([session_physical_reads] AS MONEY)* 8)/ 1024) [session_physical_reads_MB], ' + @LineFeed + + N' ((CAST([session_physical_reads] AS DECIMAL(38,2))* 8)/ 1024) [session_physical_reads_MB], ' + @LineFeed + N' [session_writes], ' + @LineFeed - + N' ((CAST([session_writes] AS MONEY)* 8)/ 1024) [session_writes_MB], ' + @LineFeed + + N' ((CAST([session_writes] AS DECIMAL(38,2))* 8)/ 1024) [session_writes_MB], ' + @LineFeed + N' [tempdb_allocations_mb], ' + @LineFeed + N' [memory_usage], ' + @LineFeed + N' [estimated_completion_time], ' + @LineFeed @@ -26886,6 +26906,7 @@ VALUES (15, 4003, 'CU1', 'https://support.microsoft.com/en-us/help/4527376', '2020-01-07', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 1 '), (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 '), + (14, 3356, 'RTM CU23', 'https://support.microsoft.com/en-us/help/5000685', '2021-02-25', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 23'), (14, 3370, 'RTM CU22 GDR', 'https://support.microsoft.com/en-us/help/4583457', '2021-01-12', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 22 GDR'), (14, 3356, 'RTM CU22', 'https://support.microsoft.com/en-us/help/4577467', '2020-09-10', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 22'), (14, 3335, 'RTM CU21', 'https://support.microsoft.com/en-us/help/4557397', '2020-07-01', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 21'), @@ -27261,7 +27282,7 @@ BEGIN SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.01', @VersionDate = '20210222'; +SELECT @Version = '8.02', @VersionDate = '20210321'; IF(@VersionCheckMode = 1) BEGIN @@ -28511,35 +28532,42 @@ BEGIN /* Populate #FileStats, #PerfmonStats, #WaitStats with DMV data. After we finish doing our checks, we'll take another sample and compare them. */ RAISERROR('Capturing first pass of wait stats, perfmon counters, file stats',10,1) WITH NOWAIT; - INSERT #WaitStats(Pass, SampleTime, wait_type, wait_time_ms, signal_wait_time_ms, waiting_tasks_count) - SELECT - x.Pass, - x.SampleTime, - x.wait_type, - SUM(x.sum_wait_time_ms) AS sum_wait_time_ms, - SUM(x.sum_signal_wait_time_ms) AS sum_signal_wait_time_ms, - SUM(x.sum_waiting_tasks) AS sum_waiting_tasks - FROM ( - SELECT - 1 AS Pass, - CASE @Seconds WHEN 0 THEN @StartSampleTime ELSE SYSDATETIMEOFFSET() END AS SampleTime, - owt.wait_type, - CASE @Seconds WHEN 0 THEN 0 ELSE SUM(owt.wait_duration_ms) OVER (PARTITION BY owt.wait_type, owt.session_id) - - CASE WHEN @Seconds = 0 THEN 0 ELSE (@Seconds * 1000) END END AS sum_wait_time_ms, - 0 AS sum_signal_wait_time_ms, - 0 AS sum_waiting_tasks - FROM sys.dm_os_waiting_tasks owt - WHERE owt.session_id > 50 - AND owt.wait_duration_ms >= CASE @Seconds WHEN 0 THEN 0 ELSE @Seconds * 1000 END - UNION ALL - SELECT - 1 AS Pass, - CASE @Seconds WHEN 0 THEN @StartSampleTime ELSE SYSDATETIMEOFFSET() END AS SampleTime, - os.wait_type, - CASE @Seconds WHEN 0 THEN 0 ELSE SUM(os.wait_time_ms) OVER (PARTITION BY os.wait_type) END AS sum_wait_time_ms, - CASE @Seconds WHEN 0 THEN 0 ELSE SUM(os.signal_wait_time_ms) OVER (PARTITION BY os.wait_type ) END AS sum_signal_wait_time_ms, - CASE @Seconds WHEN 0 THEN 0 ELSE SUM(os.waiting_tasks_count) OVER (PARTITION BY os.wait_type) END AS sum_waiting_tasks - FROM sys.dm_os_wait_stats os + SET @StringToExecute = N' + INSERT #WaitStats(Pass, SampleTime, wait_type, wait_time_ms, signal_wait_time_ms, waiting_tasks_count) + SELECT + x.Pass, + x.SampleTime, + x.wait_type, + SUM(x.sum_wait_time_ms) AS sum_wait_time_ms, + SUM(x.sum_signal_wait_time_ms) AS sum_signal_wait_time_ms, + SUM(x.sum_waiting_tasks) AS sum_waiting_tasks + FROM ( + SELECT + 1 AS Pass, + CASE @Seconds WHEN 0 THEN @StartSampleTime ELSE SYSDATETIMEOFFSET() END AS SampleTime, + owt.wait_type, + CASE @Seconds WHEN 0 THEN 0 ELSE SUM(owt.wait_duration_ms) OVER (PARTITION BY owt.wait_type, owt.session_id) + - CASE WHEN @Seconds = 0 THEN 0 ELSE (@Seconds * 1000) END END AS sum_wait_time_ms, + 0 AS sum_signal_wait_time_ms, + 0 AS sum_waiting_tasks + FROM sys.dm_os_waiting_tasks owt + WHERE owt.session_id > 50 + AND owt.wait_duration_ms >= CASE @Seconds WHEN 0 THEN 0 ELSE @Seconds * 1000 END + UNION ALL + SELECT + 1 AS Pass, + CASE @Seconds WHEN 0 THEN @StartSampleTime ELSE SYSDATETIMEOFFSET() END AS SampleTime, + os.wait_type, + CASE @Seconds WHEN 0 THEN 0 ELSE SUM(os.wait_time_ms) OVER (PARTITION BY os.wait_type) END AS sum_wait_time_ms, + CASE @Seconds WHEN 0 THEN 0 ELSE SUM(os.signal_wait_time_ms) OVER (PARTITION BY os.wait_type ) END AS sum_signal_wait_time_ms, + CASE @Seconds WHEN 0 THEN 0 ELSE SUM(os.waiting_tasks_count) OVER (PARTITION BY os.wait_type) END AS sum_waiting_tasks '; + + IF SERVERPROPERTY('Edition') = 'SQL Azure' + SET @StringToExecute = @StringToExecute + N' FROM sys.dm_db_wait_stats os '; + ELSE + SET @StringToExecute = @StringToExecute + N' FROM sys.dm_os_wait_stats os '; + + SET @StringToExecute = @StringToExecute + N' ) x WHERE NOT EXISTS ( @@ -28549,7 +28577,8 @@ BEGIN AND wc.Ignorable = 1 ) GROUP BY x.Pass, x.SampleTime, x.wait_type - ORDER BY sum_wait_time_ms DESC; + ORDER BY sum_wait_time_ms DESC;' + EXEC sp_executesql @StringToExecute, N'@StartSampleTime DATETIMEOFFSET, @Seconds INT', @StartSampleTime, @Seconds; INSERT INTO #FileStats (Pass, SampleTime, DatabaseID, FileID, DatabaseName, FileLogicalName, SizeOnDiskMB, io_stall_read_ms , @@ -28622,7 +28651,7 @@ BEGIN 1 AS Priority, 'Maintenance Tasks Running' AS FindingGroup, 'Backup Running' AS Finding, - 'http://www.BrentOzar.com/askbrent/backups/' AS URL, + 'https://www.brentozar.com/askbrent/backups/' AS URL, 'Backup of ' + DB_NAME(db.resource_database_id) + ' database (' + (SELECT CAST(CAST(SUM(size * 8.0 / 1024 / 1024) AS BIGINT) AS NVARCHAR) FROM #MasterFiles WHERE database_id = db.resource_database_id) + 'GB) ' + @LineFeed + CAST(r.percent_complete AS NVARCHAR(100)) + '% complete, has been running since ' + CAST(r.start_time AS NVARCHAR(100)) + '. ' + @LineFeed + CASE WHEN COALESCE(s.nt_user_name, s.login_name) IS NOT NULL THEN (' Login: ' + COALESCE(s.nt_user_name, s.login_name) + ' ') ELSE '' END AS Details, @@ -28674,7 +28703,7 @@ BEGIN 1 AS Priority, 'Maintenance Tasks Running' AS FindingGroup, 'DBCC CHECK* Running' AS Finding, - 'http://www.BrentOzar.com/askbrent/dbcc/' AS URL, + 'https://www.brentozar.com/askbrent/dbcc/' AS URL, 'Corruption check of ' + DB_NAME(db.resource_database_id) + ' database (' + (SELECT CAST(CAST(SUM(size * 8.0 / 1024 / 1024) AS BIGINT) AS NVARCHAR) FROM #MasterFiles WHERE database_id = db.resource_database_id) + 'GB) 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, @@ -28719,7 +28748,7 @@ BEGIN 1 AS Priority, 'Maintenance Tasks Running' AS FindingGroup, 'Restore Running' AS Finding, - 'http://www.BrentOzar.com/askbrent/backups/' AS URL, + 'https://www.brentozar.com/askbrent/backups/' AS URL, 'Restore of ' + DB_NAME(db.resource_database_id) + ' database (' + (SELECT CAST(CAST(SUM(size * 8.0 / 1024 / 1024) AS BIGINT) AS NVARCHAR) FROM #MasterFiles WHERE database_id = db.resource_database_id) + '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, @@ -28760,9 +28789,9 @@ BEGIN 1 AS Priority, 'SQL Server Internal Maintenance' AS FindingGroup, 'Database File Growing' AS Finding, - 'http://www.BrentOzar.com/go/instant' AS URL, + 'https://www.brentozar.com/go/instant' AS URL, 'SQL Server is waiting for Windows to provide storage space for a database restore, a data file growth, or a log file growth. This task has been running since ' + CAST(r.start_time AS NVARCHAR(100)) + '.' + @LineFeed + 'Check the query plan (expert mode) to identify the database involved.' AS Details, - 'Unfortunately, you can''t stop this, but you can prevent it next time. Check out http://www.BrentOzar.com/go/instant for details.' AS HowToStopIt, + 'Unfortunately, you can''t stop this, but you can prevent it next time. Check out https://www.brentozar.com/go/instant for details.' AS HowToStopIt, pl.query_plan AS QueryPlan, r.start_time AS StartTime, s.login_name AS LoginName, @@ -28794,7 +28823,7 @@ BEGIN 1 AS Priority, ''Query Problems'' AS FindingGroup, ''Long-Running Query Blocking Others'' AS Finding, - ''http://www.BrentOzar.com/go/blocking'' AS URL, + ''https://www.brentozar.com/go/blocking'' AS URL, ''Query in '' + COALESCE(DB_NAME(COALESCE((SELECT TOP 1 dbid FROM sys.dm_exec_sql_text(r.sql_handle)), (SELECT TOP 1 t.dbid FROM master..sysprocesses spBlocker CROSS APPLY sys.dm_exec_sql_text(spBlocker.sql_handle) t WHERE spBlocker.spid = tBlocked.blocking_session_id))), ''(Unknown)'') + '' has a last request start time of '' + CAST(s.last_request_start_time AS NVARCHAR(100)) + ''. Query follows: ' + @LineFeed + @LineFeed + @@ -28837,7 +28866,7 @@ BEGIN 50 AS Priority, 'Query Problems' AS FindingGroup, 'Plan Cache Erased Recently' AS Finding, - 'http://www.BrentOzar.com/askbrent/plan-cache-erased-recently/' AS URL, + 'https://www.brentozar.com/askbrent/plan-cache-erased-recently/' AS URL, 'The oldest query in the plan cache was created at ' + CAST(creation_time AS NVARCHAR(50)) + '. ' + @LineFeed + @LineFeed + 'This indicates that someone ran DBCC FREEPROCCACHE at that time,' + @LineFeed + 'Giving SQL Server temporary amnesia. Now, as queries come in,' + @LineFeed @@ -28862,7 +28891,7 @@ BEGIN 50 AS Priority, 'Query Problems' AS FindingGroup, 'Sleeping Query with Open Transactions' AS Finding, - 'http://www.brentozar.com/askbrent/sleeping-query-with-open-transactions/' AS URL, + 'https://www.brentozar.com/askbrent/sleeping-query-with-open-transactions/' AS URL, 'Database: ' + DB_NAME(db.resource_database_id) + @LineFeed + 'Host: ' + s.[host_name] + @LineFeed + 'Program: ' + s.[program_name] + @LineFeed + 'Asleep with open transactions and locks since ' + CAST(s.last_request_end_time AS NVARCHAR(100)) + '. ' AS Details, 'KILL ' + CAST(s.session_id AS NVARCHAR(100)) + ';' AS HowToStopIt, s.last_request_start_time AS StartTime, @@ -28948,7 +28977,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 1 AS Priority, 'Query Problems' AS FindingGroup, 'Query Rolling Back' AS Finding, - 'http://www.BrentOzar.com/askbrent/rollback/' AS URL, + 'https://www.brentozar.com/askbrent/rollback/' AS URL, 'Rollback started at ' + CAST(r.start_time AS NVARCHAR(100)) + ', is ' + CAST(r.percent_complete AS NVARCHAR(100)) + '% complete.' AS Details, 'Unfortunately, you can''t stop this. Whatever you do, don''t restart the server in an attempt to fix it - SQL Server will keep rolling back.' AS HowToStopIt, r.start_time AS StartTime, @@ -29029,7 +29058,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 50 AS Priority, 'Server Performance' AS FindingGroup, 'Too Much Free Memory' AS Finding, - 'https://BrentOzar.com/go/freememory' AS URL, + 'https://www.brentozar.com/go/freememory' AS URL, CAST((CAST(cFree.cntr_value AS BIGINT) / 1024 / 1024 ) AS NVARCHAR(100)) + N'GB of free memory inside SQL Server''s buffer pool,' + @LineFeed + ' which is ' + CAST((CAST(cTotal.cntr_value AS BIGINT) / 1024 / 1024) AS NVARCHAR(100)) + N'GB. You would think lots of free memory would be good, but check out the URL for more information.' AS Details, 'Run sp_BlitzCache @SortOrder = ''memory grant'' to find queries with huge memory grants and tune them.' AS HowToStopIt FROM sys.dm_os_performance_counters cFree @@ -29054,7 +29083,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 10 AS Priority, 'Server Performance' AS FindingGroup, 'Target Memory Lower Than Max' AS Finding, - 'https://BrentOzar.com/go/target' AS URL, + 'https://www.brentozar.com/go/target' AS URL, N'Max server memory is ' + CAST(cMax.value_in_use AS NVARCHAR(50)) + N' MB but target server memory is only ' + CAST((CAST(cTarget.cntr_value AS BIGINT) / 1024) AS NVARCHAR(50)) + N' MB,' + @LineFeed + N'indicating that SQL Server may be under external memory pressure or max server memory may be set too high.' AS Details, 'Investigate what OS processes are using memory, and double-check the max server memory setting.' AS HowToStopIt @@ -29080,7 +29109,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 'Database Size, Total GB' AS Finding, CAST(SUM (CAST(size AS BIGINT)*8./1024./1024.) AS VARCHAR(100)) AS Details, SUM (CAST(size AS BIGINT))*8./1024./1024. AS DetailsInt, - 'http://www.BrentOzar.com/askbrent/' AS URL + 'https://www.brentozar.com/askbrent/' AS URL FROM #MasterFiles WHERE database_id > 4; @@ -29097,7 +29126,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 'Database Count' AS Finding, CAST(SUM(1) AS VARCHAR(100)) AS Details, SUM (1) AS DetailsInt, - 'http://www.BrentOzar.com/askbrent/' AS URL + 'https://www.brentozar.com/askbrent/' AS URL FROM sys.databases WHERE database_id > 4; @@ -29152,7 +29181,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, / CAST(@MaxWorkspace AS MONEY)) * 100, 0) AS NVARCHAR(50)) + '%' + @LineFeed + 'Oldest Grant in seconds: ' + CAST(ISNULL(DATEDIFF(SECOND, MIN(Grants.request_time), GETDATE()), 0) AS NVARCHAR(50)) AS Details, (SELECT COUNT(*) FROM sys.dm_exec_query_memory_grants WHERE queue_id IS NULL) AS DetailsInt, - 'http://www.BrentOzar.com/askbrent/' AS URL + 'https://www.brentozar.com/askbrent/' AS URL FROM sys.dm_exec_query_memory_grants AS Grants; /* Query Problems - Queries with high memory grants - CheckID 46 */ @@ -29362,7 +29391,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 100 AS Priority, ''Query Performance'' AS FindingsGroup, ''Queries with 10000x cardinality misestimations'' AS Findings, - ''https://brentozar.com/go/skewedup'' AS URL, + ''https://www.brentozar.com/go/skewedup'' AS URL, ''The query on SPID '' + RTRIM(b.session_id) + '' has been running for '' @@ -29413,7 +29442,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 100 AS Priority, ''Query Performance'' AS FindingsGroup, ''Queries with 10000x skewed parallelism'' AS Findings, - ''https://brentozar.com/go/skewedup'' AS URL, + ''https://www.brentozar.com/go/skewedup'' AS URL, ''The query on SPID '' + RTRIM(p.session_id) + '' has been running for '' @@ -29471,7 +29500,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, END INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) - SELECT 24, 50, 'Server Performance', 'High CPU Utilization', CAST(100 - SystemIdle AS NVARCHAR(20)) + N'%.', 100 - SystemIdle, 'http://www.BrentOzar.com/go/cpu' + SELECT 24, 50, 'Server Performance', 'High CPU Utilization', CAST(100 - SystemIdle AS NVARCHAR(20)) + N'%.', 100 - SystemIdle, 'https://www.brentozar.com/go/cpu' FROM ( SELECT record, record.value('(./Record/SchedulerMonitorEvent/SystemHealth/SystemIdle)[1]', 'int') AS SystemIdle @@ -29515,7 +29544,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 'CPU Utilization', y.system_idle + N'%. Ring buffer details: ' + CAST(y.record AS NVARCHAR(4000)), y.system_idle , - 'http://www.BrentOzar.com/go/cpu', + 'https://www.brentozar.com/go/cpu', STUFF(( SELECT TOP 2147483647 CHAR(10) + CHAR(13) + y2.system_idle @@ -29537,7 +29566,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, END INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) - SELECT 28, 50, 'Server Performance', 'High CPU Utilization - Not SQL', CONVERT(NVARCHAR(100),100 - (y.SQLUsage + y.SystemIdle)) + N'% - Other Processes (not SQL Server) are using this much CPU. This may impact on the performance of your SQL Server instance', 100 - (y.SQLUsage + y.SystemIdle), 'http://www.BrentOzar.com/go/cpu' + SELECT 28, 50, 'Server Performance', 'High CPU Utilization - Not SQL', CONVERT(NVARCHAR(100),100 - (y.SQLUsage + y.SystemIdle)) + N'% - Other Processes (not SQL Server) are using this much CPU. This may impact on the performance of your SQL Server instance', 100 - (y.SQLUsage + y.SystemIdle), 'https://www.brentozar.com/go/cpu' FROM ( SELECT record, record.value('(./Record/SchedulerMonitorEvent/SystemHealth/SystemIdle)[1]', 'int') AS SystemIdle @@ -29560,6 +29589,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, END IF 20 >= (SELECT COUNT(*) FROM sys.databases WHERE name NOT IN ('master', 'model', 'msdb', 'tempdb')) + AND @Seconds > 0 BEGIN CREATE TABLE #UpdatedStats (HowToStopIt NVARCHAR(4000), RowsForSorting BIGINT); IF EXISTS(SELECT * FROM sys.all_objects WHERE name = 'dm_db_stats_properties') @@ -29567,49 +29597,59 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, /* We don't want to hang around to obtain locks */ SET LOCK_TIMEOUT 0; - EXEC sp_MSforeachdb N'USE [?]; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; SET LOCK_TIMEOUT 1000; - BEGIN TRY - INSERT INTO #UpdatedStats(HowToStopIt, RowsForSorting) - SELECT HowToStopIt = - QUOTENAME(DB_NAME()) + N''.'' + - QUOTENAME(SCHEMA_NAME(obj.schema_id)) + N''.'' + - QUOTENAME(obj.name) + - N'' statistic '' + QUOTENAME(stat.name) + - N'' was updated on '' + CONVERT(NVARCHAR(50), sp.last_updated, 121) + N'','' + - N'' had '' + CAST(sp.rows AS NVARCHAR(50)) + N'' rows, with '' + - CAST(sp.rows_sampled AS NVARCHAR(50)) + N'' rows sampled,'' + - N'' producing '' + CAST(sp.steps AS NVARCHAR(50)) + N'' steps in the histogram.'', - sp.rows - FROM sys.objects AS obj WITH (NOLOCK) - INNER JOIN sys.stats AS stat WITH (NOLOCK) ON stat.object_id = obj.object_id - CROSS APPLY sys.dm_db_stats_properties(stat.object_id, stat.stats_id) AS sp - WHERE sp.last_updated > DATEADD(MI, -15, GETDATE()) - AND obj.is_ms_shipped = 0 - AND ''[?]'' <> ''[tempdb]''; - END TRY - BEGIN CATCH - IF (ERROR_NUMBER() = 1222) - BEGIN - INSERT INTO #UpdatedStats(HowToStopIt, RowsForSorting) - SELECT HowToStopIt = - QUOTENAME(DB_NAME()) + - N'' No information could be retrieved as the lock timeout was exceeded,''+ - N'' this is likely due to an Index operation in Progress'', - -1 - END - ELSE - BEGIN - INSERT INTO #UpdatedStats(HowToStopIt, RowsForSorting) - SELECT HowToStopIt = - QUOTENAME(DB_NAME()) + - N'' No information could be retrieved as a result of error: ''+ - CAST(ERROR_NUMBER() AS NVARCHAR(10)) + - N'' with message: ''+ - CAST(ERROR_MESSAGE() AS NVARCHAR(128)), - -1 - END - END CATCH'; + IF SERVERPROPERTY('Edition') <> 'SQL Azure' + SET @StringToExecute = N'USE [?];' + @LineFeed; + ELSE + SET @StringToExecute = N''; + + SET @StringToExecute = @StringToExecute + 'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; SET LOCK_TIMEOUT 1000;' + @LineFeed + + 'BEGIN TRY' + @LineFeed + + ' INSERT INTO #UpdatedStats(HowToStopIt, RowsForSorting)' + @LineFeed + + ' SELECT HowToStopIt = ' + @LineFeed + + ' QUOTENAME(DB_NAME()) + N''.'' +' + @LineFeed + + ' QUOTENAME(SCHEMA_NAME(obj.schema_id)) + N''.'' +' + @LineFeed + + ' QUOTENAME(obj.name) +' + @LineFeed + + ' N'' statistic '' + QUOTENAME(stat.name) + ' + @LineFeed + + ' N'' was updated on '' + CONVERT(NVARCHAR(50), sp.last_updated, 121) + N'','' + ' + @LineFeed + + ' N'' had '' + CAST(sp.rows AS NVARCHAR(50)) + N'' rows, with '' +' + @LineFeed + + ' CAST(sp.rows_sampled AS NVARCHAR(50)) + N'' rows sampled,'' + ' + @LineFeed + + ' N'' producing '' + CAST(sp.steps AS NVARCHAR(50)) + N'' steps in the histogram.'',' + @LineFeed + + ' sp.rows' + @LineFeed + + ' FROM sys.objects AS obj WITH (NOLOCK)' + @LineFeed + + ' INNER JOIN sys.stats AS stat WITH (NOLOCK) ON stat.object_id = obj.object_id ' + @LineFeed + + ' CROSS APPLY sys.dm_db_stats_properties(stat.object_id, stat.stats_id) AS sp ' + @LineFeed + + ' WHERE sp.last_updated > DATEADD(MI, -15, GETDATE())' + @LineFeed + + ' AND obj.is_ms_shipped = 0' + @LineFeed + + ' AND ''[?]'' <> ''[tempdb]'';' + @LineFeed + + 'END TRY' + @LineFeed + + 'BEGIN CATCH' + @LineFeed + + ' IF (ERROR_NUMBER() = 1222)' + @LineFeed + + ' BEGIN ' + @LineFeed + + ' INSERT INTO #UpdatedStats(HowToStopIt, RowsForSorting)' + @LineFeed + + ' SELECT HowToStopIt = ' + @LineFeed + + ' QUOTENAME(DB_NAME()) +' + @LineFeed + + ' N'' No information could be retrieved as the lock timeout was exceeded,''+' + @LineFeed + + ' N'' this is likely due to an Index operation in Progress'',' + @LineFeed + + ' -1' + @LineFeed + + ' END' + @LineFeed + + ' ELSE' + @LineFeed + + ' BEGIN' + @LineFeed + + ' INSERT INTO #UpdatedStats(HowToStopIt, RowsForSorting)' + @LineFeed + + ' SELECT HowToStopIt = ' + @LineFeed + + ' QUOTENAME(DB_NAME()) +' + @LineFeed + + ' N'' No information could be retrieved as a result of error: ''+' + @LineFeed + + ' CAST(ERROR_NUMBER() AS NVARCHAR(10)) +' + @LineFeed + + ' N'' with message: ''+' + @LineFeed + + ' CAST(ERROR_MESSAGE() AS NVARCHAR(128)),' + @LineFeed + + ' -1' + @LineFeed + + ' END' + @LineFeed + + 'END CATCH' + ; + + IF SERVERPROPERTY('Edition') <> 'SQL Azure' + EXEC sp_MSforeachdb @StringToExecute; + ELSE + EXEC(@StringToExecute); /* Set timeout back to a default value of -1 */ SET LOCK_TIMEOUT -1; @@ -29622,7 +29662,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 50 AS Priority, 'Query Problems' AS FindingGroup, 'Statistics Updated Recently' AS Finding, - 'http://www.BrentOzar.com/go/stats' AS URL, + 'https://www.brentozar.com/go/stats' AS URL, 'In the last 15 minutes, statistics were updated. To see which ones, click the HowToStopIt column.' + @LineFeed + @LineFeed + 'This effectively clears the plan cache for queries that involve these tables,' + @LineFeed + 'which thereby causes parameter sniffing: those queries are now getting brand new' + @LineFeed @@ -29647,35 +29687,42 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, RAISERROR('Capturing second pass of wait stats, perfmon counters, file stats',10,1) WITH NOWAIT; /* Populate #FileStats, #PerfmonStats, #WaitStats with DMV data. In a second, we'll compare these. */ - INSERT #WaitStats(Pass, SampleTime, wait_type, wait_time_ms, signal_wait_time_ms, waiting_tasks_count) - SELECT - x.Pass, - x.SampleTime, - x.wait_type, - SUM(x.sum_wait_time_ms) AS sum_wait_time_ms, - SUM(x.sum_signal_wait_time_ms) AS sum_signal_wait_time_ms, - SUM(x.sum_waiting_tasks) AS sum_waiting_tasks - FROM ( - SELECT - 2 AS Pass, - SYSDATETIMEOFFSET() AS SampleTime, - owt.wait_type, - SUM(owt.wait_duration_ms) OVER (PARTITION BY owt.wait_type, owt.session_id) - - CASE WHEN @Seconds = 0 THEN 0 ELSE (@Seconds * 1000) END AS sum_wait_time_ms, - 0 AS sum_signal_wait_time_ms, - CASE @Seconds WHEN 0 THEN 0 ELSE 1 END AS sum_waiting_tasks - FROM sys.dm_os_waiting_tasks owt - WHERE owt.session_id > 50 - AND owt.wait_duration_ms >= CASE @Seconds WHEN 0 THEN 0 ELSE @Seconds * 1000 END - UNION ALL - SELECT - 2 AS Pass, - SYSDATETIMEOFFSET() AS SampleTime, - os.wait_type, - SUM(os.wait_time_ms) OVER (PARTITION BY os.wait_type) AS sum_wait_time_ms, - SUM(os.signal_wait_time_ms) OVER (PARTITION BY os.wait_type ) AS sum_signal_wait_time_ms, - SUM(os.waiting_tasks_count) OVER (PARTITION BY os.wait_type) AS sum_waiting_tasks - FROM sys.dm_os_wait_stats os + SET @StringToExecute = N' + INSERT #WaitStats(Pass, SampleTime, wait_type, wait_time_ms, signal_wait_time_ms, waiting_tasks_count) + SELECT + x.Pass, + x.SampleTime, + x.wait_type, + SUM(x.sum_wait_time_ms) AS sum_wait_time_ms, + SUM(x.sum_signal_wait_time_ms) AS sum_signal_wait_time_ms, + SUM(x.sum_waiting_tasks) AS sum_waiting_tasks + FROM ( + SELECT + 2 AS Pass, + SYSDATETIMEOFFSET() AS SampleTime, + owt.wait_type, + SUM(owt.wait_duration_ms) OVER (PARTITION BY owt.wait_type, owt.session_id) + - CASE WHEN @Seconds = 0 THEN 0 ELSE (@Seconds * 1000) END AS sum_wait_time_ms, + 0 AS sum_signal_wait_time_ms, + CASE @Seconds WHEN 0 THEN 0 ELSE 1 END AS sum_waiting_tasks + FROM sys.dm_os_waiting_tasks owt + WHERE owt.session_id > 50 + AND owt.wait_duration_ms >= CASE @Seconds WHEN 0 THEN 0 ELSE @Seconds * 1000 END + UNION ALL + SELECT + 2 AS Pass, + SYSDATETIMEOFFSET() AS SampleTime, + os.wait_type, + SUM(os.wait_time_ms) OVER (PARTITION BY os.wait_type) AS sum_wait_time_ms, + SUM(os.signal_wait_time_ms) OVER (PARTITION BY os.wait_type ) AS sum_signal_wait_time_ms, + SUM(os.waiting_tasks_count) OVER (PARTITION BY os.wait_type) AS sum_waiting_tasks '; + + IF SERVERPROPERTY('Edition') = 'SQL Azure' + SET @StringToExecute = @StringToExecute + N' FROM sys.dm_db_wait_stats os '; + ELSE + SET @StringToExecute = @StringToExecute + N' FROM sys.dm_os_wait_stats os '; + + SET @StringToExecute = @StringToExecute + N' ) x WHERE NOT EXISTS ( @@ -29685,7 +29732,8 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, AND wc.Ignorable = 1 ) GROUP BY x.Pass, x.SampleTime, x.wait_type - ORDER BY sum_wait_time_ms DESC; + ORDER BY sum_wait_time_ms DESC;'; + EXEC sp_executesql @StringToExecute, N'@Seconds INT', @Seconds; INSERT INTO #FileStats (Pass, SampleTime, DatabaseID, FileID, DatabaseName, FileLogicalName, SizeOnDiskMB, io_stall_read_ms , num_of_reads, [bytes_read] , io_stall_write_ms,num_of_writes, [bytes_written], PhysicalName, TypeDesc, avg_stall_read_ms, avg_stall_write_ms) @@ -29752,7 +29800,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, END INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (18, 210, 'Query Stats', 'Plan Cache Analysis Skipped', 'http://www.BrentOzar.com/go/topqueries', + VALUES (18, 210, 'Query Stats', 'Plan Cache Analysis Skipped', 'https://www.brentozar.com/go/topqueries', 'Due to excessive load, the plan cache analysis was skipped. To override this, use @ExpertMode = 1.'); END; @@ -29895,7 +29943,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, END INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, QueryText, QueryStatsNowID, QueryStatsFirstID, PlanHandle, QueryHash) - SELECT 17, 210, 'Query Stats', 'Most Resource-Intensive Queries', 'http://www.BrentOzar.com/go/topqueries', + SELECT 17, 210, 'Query Stats', 'Most Resource-Intensive Queries', 'https://www.brentozar.com/go/topqueries', 'Query stats during the sample:' + @LineFeed + 'Executions: ' + CAST(qsNow.execution_count - (COALESCE(qsFirst.execution_count, 0)) AS NVARCHAR(100)) + @LineFeed + 'Elapsed Time: ' + CAST(qsNow.total_elapsed_time - (COALESCE(qsFirst.total_elapsed_time, 0)) AS NVARCHAR(100)) + @LineFeed + @@ -29983,7 +30031,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 10 AS Priority, 'Server Performance' AS FindingGroup, 'Poison Wait Detected: ' + wNow.wait_type AS Finding, - N'http://www.brentozar.com/go/poison/#' + wNow.wait_type AS URL, + N'https://www.brentozar.com/go/poison/#' + wNow.wait_type AS URL, 'For ' + CAST(((wNow.wait_time_ms - COALESCE(wBase.wait_time_ms,0)) / 1000) AS NVARCHAR(100)) + ' seconds over the last ' + CASE @Seconds WHEN 0 THEN (CAST(DATEDIFF(dd,@StartSampleTime,@FinishSampleTime) AS NVARCHAR(10)) + ' days') ELSE (CAST(@Seconds AS NVARCHAR(10)) + ' seconds') END + ', SQL Server was waiting on this particular bottleneck.' + @LineFeed + @LineFeed AS Details, 'See the URL for more details on how to mitigate this wait type.' AS HowToStopIt, ((wNow.wait_time_ms - COALESCE(wBase.wait_time_ms,0)) / 1000) AS DetailsInt @@ -30006,7 +30054,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 50 AS Priority, 'Server Performance' AS FindingGroup, 'Slow Data File Reads' AS Finding, - 'http://www.BrentOzar.com/go/slow/' AS URL, + 'https://www.brentozar.com/go/slow/' AS URL, 'Your server is experiencing PAGEIOLATCH% waits due to slow data file reads. This file is one of the reasons why.' + @LineFeed + 'File: ' + fNow.PhysicalName + @LineFeed + 'Number of reads during the sample: ' + CAST((fNow.num_of_reads - fBase.num_of_reads) AS NVARCHAR(20)) + @LineFeed @@ -30036,7 +30084,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 50 AS Priority, 'Server Performance' AS FindingGroup, 'Slow Log File Writes' AS Finding, - 'http://www.BrentOzar.com/go/slow/' AS URL, + 'https://www.brentozar.com/go/slow/' AS URL, 'Your server is experiencing WRITELOG waits due to slow log file writes. This file is one of the reasons why.' + @LineFeed + 'File: ' + fNow.PhysicalName + @LineFeed + 'Number of writes during the sample: ' + CAST((fNow.num_of_writes - fBase.num_of_writes) AS NVARCHAR(20)) + @LineFeed @@ -30065,7 +30113,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 1 AS Priority, 'SQL Server Internal Maintenance' AS FindingGroup, 'Log File Growing' AS Finding, - 'http://www.BrentOzar.com/askbrent/file-growing/' AS URL, + 'https://www.brentozar.com/askbrent/file-growing/' AS URL, 'Number of growths during the sample: ' + CAST(ps.value_delta AS NVARCHAR(20)) + @LineFeed + 'Determined by sampling Perfmon counter ' + ps.object_name + ' - ' + ps.counter_name + @LineFeed AS Details, 'Pre-grow data and log files during maintenance windows so that they do not grow during production loads. See the URL for more details.' AS HowToStopIt @@ -30087,7 +30135,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 1 AS Priority, 'SQL Server Internal Maintenance' AS FindingGroup, 'Log File Shrinking' AS Finding, - 'http://www.BrentOzar.com/askbrent/file-shrinking/' AS URL, + 'https://www.brentozar.com/askbrent/file-shrinking/' AS URL, 'Number of shrinks during the sample: ' + CAST(ps.value_delta AS NVARCHAR(20)) + @LineFeed + 'Determined by sampling Perfmon counter ' + ps.object_name + ' - ' + ps.counter_name + @LineFeed AS Details, 'Pre-grow data and log files during maintenance windows so that they do not grow during production loads. See the URL for more details.' AS HowToStopIt @@ -30108,7 +30156,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 50 AS Priority, 'Query Problems' AS FindingGroup, 'Compilations/Sec High' AS Finding, - 'http://www.BrentOzar.com/askbrent/compilations/' AS URL, + 'https://www.brentozar.com/askbrent/compilations/' AS URL, 'Number of batch requests during the sample: ' + CAST(ps.value_delta AS NVARCHAR(20)) + @LineFeed + 'Number of compilations during the sample: ' + CAST(psComp.value_delta AS NVARCHAR(20)) + @LineFeed + 'For OLTP environments, Microsoft recommends that 90% of batch requests should hit the plan cache, and not be compiled from scratch. We are exceeding that threshold.' + @LineFeed AS Details, @@ -30135,7 +30183,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 50 AS Priority, 'Query Problems' AS FindingGroup, 'Re-Compilations/Sec High' AS Finding, - 'http://www.BrentOzar.com/askbrent/recompilations/' AS URL, + 'https://www.brentozar.com/askbrent/recompilations/' AS URL, 'Number of batch requests during the sample: ' + CAST(ps.value_delta AS NVARCHAR(20)) + @LineFeed + 'Number of recompilations during the sample: ' + CAST(psComp.value_delta AS NVARCHAR(20)) + @LineFeed + 'More than 10% of our queries are being recompiled. This is typically due to statistics changing on objects.' + @LineFeed AS Details, @@ -30162,7 +30210,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 40 AS Priority, 'Table Problems' AS FindingGroup, 'Forwarded Fetches/Sec High' AS Finding, - 'https://BrentOzar.com/go/fetch/' AS URL, + 'https://www.brentozar.com/go/fetch/' AS URL, CAST(ps.value_delta AS NVARCHAR(20)) + ' forwarded fetches (from SQLServer:Access Methods counter)' + @LineFeed + 'Check your heaps: they need to be rebuilt, or they need a clustered index applied.' + @LineFeed AS Details, 'Rebuild your heaps. If you use Ola Hallengren maintenance scripts, those do not rebuild heaps by default: https://www.brentozar.com/archive/2016/07/fix-forwarded-records/' AS HowToStopIt @@ -30183,7 +30231,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 40 AS Priority, ''Table Problems'' AS FindingGroup, ''Forwarded Fetches/Sec High: TempDB Object'' AS Finding, - ''https://BrentOzar.com/go/fetch/'' AS URL, + ''https://www.brentozar.com/go/fetch/'' AS URL, CAST(COALESCE(os.forwarded_fetch_count,0) - COALESCE(os_prior.forwarded_fetch_count,0) AS NVARCHAR(20)) + '' forwarded fetches on '' + CASE WHEN OBJECT_NAME(os.object_id) IS NULL THEN ''an unknown table '' WHEN LEN(OBJECT_NAME(os.object_id)) < 50 THEN ''a table variable, internal identifier '' + OBJECT_NAME(os.object_id) @@ -30211,7 +30259,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 50 AS Priority, 'In-Memory OLTP' AS FindingGroup, 'Garbage Collection in Progress' AS Finding, - 'https://BrentOzar.com/go/garbage/' AS URL, + 'https://www.brentozar.com/go/garbage/' AS URL, CAST(ps.value_delta AS NVARCHAR(50)) + ' rows processed (from SQL Server YYYY XTP Garbage Collection:Rows processed/sec counter)' + @LineFeed + 'This can happen due to memory pressure (causing In-Memory OLTP to shrink its footprint) or' + @LineFeed + 'due to transactional workloads that constantly insert/delete data.' AS Details, @@ -30234,7 +30282,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 100 AS Priority, 'In-Memory OLTP' AS FindingGroup, 'Transactions Aborted' AS Finding, - 'https://BrentOzar.com/go/aborted/' AS URL, + 'https://www.brentozar.com/go/aborted/' AS URL, CAST(ps.value_delta AS NVARCHAR(50)) + ' transactions aborted (from SQL Server YYYY XTP Transactions:Transactions aborted/sec counter)' + @LineFeed + 'This may indicate that data is changing, or causing folks to retry their transactions, thereby increasing load.' AS Details, 'Dig into your In-Memory OLTP transactions to figure out which ones are failing and being retried.' AS HowToStopIt @@ -30256,7 +30304,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 100 AS Priority, 'Query Problems' AS FindingGroup, 'Suboptimal Plans/Sec High' AS Finding, - 'https://BrentOzar.com/go/suboptimal/' AS URL, + 'https://www.brentozar.com/go/suboptimal/' AS URL, CAST(ps.value_delta AS NVARCHAR(50)) + ' plans reported in the ' + CAST(ps.instance_name AS NVARCHAR(100)) + ' workload group (from Workload GroupStats:Suboptimal plans/sec counter)' + @LineFeed + 'Even if you are not using Resource Governor, it still tracks information about user queries, memory grants, etc.' AS Details, 'Check out sp_BlitzCache to get more information about recent queries, or try sp_BlitzWho to see currently running queries.' AS HowToStopIt @@ -30280,7 +30328,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 10 AS Priority, 'Azure Performance' AS FindingGroup, 'Database is Maxed Out' AS Finding, - 'https://BrentOzar.com/go/maxedout' AS URL, + 'https://www.brentozar.com/go/maxedout' AS URL, N'At ' + CONVERT(NVARCHAR(100), s.end_time ,121) + N', your database approached (or hit) your DTU limits:' + @LineFeed + N'Average CPU percent: ' + CAST(avg_cpu_percent AS NVARCHAR(50)) + @LineFeed + N'Average data IO percent: ' + CAST(avg_data_io_percent AS NVARCHAR(50)) + @LineFeed @@ -30308,7 +30356,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 250 AS Priority, 'Server Info' AS FindingGroup, 'Batch Requests per Sec' AS Finding, - 'http://www.BrentOzar.com/go/measure' AS URL, + 'https://www.brentozar.com/go/measure' AS URL, CAST(CAST(ps.value_delta AS MONEY) / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS NVARCHAR(20)) AS Details, ps.value_delta / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS DetailsInt FROM #PerfmonStats ps @@ -30334,7 +30382,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 250 AS Priority, 'Server Info' AS FindingGroup, 'SQL Compilations per Sec' AS Finding, - 'http://www.BrentOzar.com/go/measure' AS URL, + 'https://www.brentozar.com/go/measure' AS URL, CAST(ps.value_delta / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS NVARCHAR(20)) AS Details, ps.value_delta / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS DetailsInt FROM #PerfmonStats ps @@ -30357,7 +30405,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 250 AS Priority, 'Server Info' AS FindingGroup, 'SQL Re-Compilations per Sec' AS Finding, - 'http://www.BrentOzar.com/go/measure' AS URL, + 'https://www.brentozar.com/go/measure' AS URL, CAST(ps.value_delta / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS NVARCHAR(20)) AS Details, ps.value_delta / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS DetailsInt FROM #PerfmonStats ps @@ -30383,7 +30431,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 250 AS Priority, 'Server Info' AS FindingGroup, 'Wait Time per Core per Sec' AS Finding, - 'http://www.BrentOzar.com/go/measure' AS URL, + 'https://www.brentozar.com/go/measure' AS URL, CAST((CAST(waits2.waits_ms - waits1.waits_ms AS MONEY)) / 1000 / i.cpu_count / DATEDIFF(ss, waits1.SampleTime, waits2.SampleTime) AS NVARCHAR(20)) AS Details, (waits2.waits_ms - waits1.waits_ms) / 1000 / i.cpu_count / DATEDIFF(ss, waits1.SampleTime, waits2.SampleTime) AS DetailsInt FROM cores i @@ -30449,7 +30497,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, END INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) - SELECT 24, 50, 'Server Performance', 'High CPU Utilization', CAST(100 - SystemIdle AS NVARCHAR(20)) + N'%. Ring buffer details: ' + CAST(record AS NVARCHAR(4000)), 100 - SystemIdle, 'http://www.BrentOzar.com/go/cpu' + SELECT 24, 50, 'Server Performance', 'High CPU Utilization', CAST(100 - SystemIdle AS NVARCHAR(20)) + N'%. Ring buffer details: ' + CAST(record AS NVARCHAR(4000)), 100 - SystemIdle, 'https://www.brentozar.com/go/cpu' FROM ( SELECT record, record.value('(./Record/SchedulerMonitorEvent/SystemHealth/SystemIdle)[1]', 'int') AS SystemIdle @@ -30469,7 +30517,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, END INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) - SELECT 23, 250, 'Server Info', 'CPU Utilization', CAST(100 - SystemIdle AS NVARCHAR(20)) + N'%. Ring buffer details: ' + CAST(record AS NVARCHAR(4000)), 100 - SystemIdle, 'http://www.BrentOzar.com/go/cpu' + SELECT 23, 250, 'Server Info', 'CPU Utilization', CAST(100 - SystemIdle AS NVARCHAR(20)) + N'%. Ring buffer details: ' + CAST(record AS NVARCHAR(4000)), 100 - SystemIdle, 'https://www.brentozar.com/go/cpu' FROM ( SELECT record, record.value('(./Record/SchedulerMonitorEvent/SystemHealth/SystemIdle)[1]', 'int') AS SystemIdle @@ -30608,15 +30656,34 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, IF @BlitzCacheMinutesBack IS NULL OR @BlitzCacheMinutesBack < 1 OR @BlitzCacheMinutesBack > 60 SET @BlitzCacheMinutesBack = 15; - EXEC sp_BlitzCache - @OutputDatabaseName = @UnquotedOutputDatabaseName, - @OutputSchemaName = @UnquotedOutputSchemaName, - @OutputTableName = @OutputTableNameBlitzCache, - @CheckDateOverride = @StartSampleTime, - @SortOrder = 'all', - @SkipAnalysis = @BlitzCacheSkipAnalysis, - @MinutesBack = @BlitzCacheMinutesBack, - @Debug = @Debug; + IF(@OutputType = 'NONE') + BEGIN + EXEC sp_BlitzCache + @OutputDatabaseName = @UnquotedOutputDatabaseName, + @OutputSchemaName = @UnquotedOutputSchemaName, + @OutputTableName = @OutputTableNameBlitzCache, + @CheckDateOverride = @StartSampleTime, + @SortOrder = 'all', + @SkipAnalysis = @BlitzCacheSkipAnalysis, + @MinutesBack = @BlitzCacheMinutesBack, + @Debug = @Debug, + @OutputType = @OutputType + ; + END; + ELSE + BEGIN + + EXEC sp_BlitzCache + @OutputDatabaseName = @UnquotedOutputDatabaseName, + @OutputSchemaName = @UnquotedOutputSchemaName, + @OutputTableName = @OutputTableNameBlitzCache, + @CheckDateOverride = @StartSampleTime, + @SortOrder = 'all', + @SkipAnalysis = @BlitzCacheSkipAnalysis, + @MinutesBack = @BlitzCacheMinutesBack, + @Debug = @Debug + ; + END; /* Delete history older than @OutputTableRetentionDays */ SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' diff --git a/Install-Core-Blitz-With-Query-Store.sql b/Install-Core-Blitz-With-Query-Store.sql index 0f27b66ce..82ce6d501 100755 --- a/Install-Core-Blitz-With-Query-Store.sql +++ b/Install-Core-Blitz-With-Query-Store.sql @@ -37,7 +37,7 @@ AS SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.01', @VersionDate = '20210222'; + SELECT @Version = '8.02', @VersionDate = '20210321'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -321,6 +321,15 @@ AS ); CREATE CLUSTERED INDEX IX_CheckID_DatabaseName ON #SkipChecks(CheckID, DatabaseName); + INSERT INTO #SkipChecks + (DatabaseName) + SELECT + DB_NAME(d.database_id) + FROM sys.databases AS d + WHERE (DB_NAME(d.database_id) LIKE 'rdsadmin%' + OR LOWER(d.name) IN ('dbatools', 'dbadmin', 'dbmaintenance')) + OPTION(RECOMPILE); + IF(OBJECT_ID('tempdb..#InvalidLogins') IS NOT NULL) BEGIN EXEC sp_executesql N'DROP TABLE #InvalidLogins;'; @@ -800,7 +809,7 @@ AS 240, 'Wait Stats', 'Wait Stats Have Been Cleared', - 'https://BrentOzar.com/go/waits', + 'https://www.brentozar.com/go/waits', 'Someone ran DBCC SQLPERF to clear sys.dm_os_wait_stats at approximately: ' + CONVERT(NVARCHAR(100), DATEADD(MINUTE, (-1. * (@MsSinceWaitsCleared) / 1000. / 60.), GETDATE()), 120)); @@ -909,7 +918,7 @@ AS 1 AS Priority , 'Backup' AS FindingsGroup , 'Backups Not Performed Recently' AS Finding , - 'https://BrentOzar.com/go/nobak' AS URL , + 'https://www.brentozar.com/go/nobak' AS URL , 'Last backed up: ' + COALESCE(CAST(MAX(b.backup_finish_date) AS VARCHAR(25)),'never') AS Details FROM master.sys.databases d @@ -949,7 +958,7 @@ AS 1 AS Priority , 'Backup' AS FindingsGroup , 'Backups Not Performed Recently' AS Finding , - 'https://BrentOzar.com/go/nobak' AS URL , + 'https://www.brentozar.com/go/nobak' AS URL , 'Last backed up: ' + COALESCE(CAST(MAX(b.backup_finish_date) AS VARCHAR(25)),'never') AS Details FROM master.sys.databases d @@ -1014,7 +1023,7 @@ AS 1 AS Priority , 'Backup' AS FindingsGroup , 'Full Recovery Model w/o Log Backups' AS Finding , - 'https://BrentOzar.com/go/biglogs' AS URL , + 'https://www.brentozar.com/go/biglogs' AS URL , ( 'The ' + CAST(CAST((SELECT ((SUM([mf].[size]) * 8.) / 1024.) FROM sys.[master_files] AS [mf] WHERE [mf].[database_id] = d.[database_id] AND [mf].[type_desc] = 'LOG') AS DECIMAL(18,2)) AS VARCHAR(30)) + 'MB log file has not been backed up in the last week.' ) AS Details FROM master.sys.databases d WHERE d.recovery_model IN ( 1, 2 ) @@ -1119,7 +1128,7 @@ AS 230 AS Priority, ''Security'' AS FindingsGroup, ''Server Audits Running'' AS Finding, - ''https://BrentOzar.com/go/audits'' AS URL, + ''https://www.brentozar.com/go/audits'' AS URL, (''SQL Server built-in audit functionality is being used by server audit: '' + [name]) AS Details FROM sys.dm_server_audit_status OPTION (RECOMPILE);'; IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; @@ -1164,7 +1173,7 @@ AS 1 AS Priority , 'Backup' AS FindingsGroup , 'Backing Up to Same Drive Where Databases Reside' AS Finding , - 'https://BrentOzar.com/go/backup' AS URL , + 'https://www.brentozar.com/go/backup' AS URL , CAST(COUNT(1) AS VARCHAR(50)) + ' backups done on drive ' + UPPER(LEFT(bmf.physical_device_name, 3)) + ' in the last two weeks, where database files also live. This represents a serious risk if that array fails.' Details @@ -1202,7 +1211,7 @@ AS ''Backup'' AS FindingsGroup, ''TDE Certificate Not Backed Up Recently'' AS Finding, db_name(dek.database_id) AS DatabaseName, - ''https://BrentOzar.com/go/tde'' AS URL, + ''https://www.brentozar.com/go/tde'' AS URL, ''The certificate '' + c.name + '' is used to encrypt database '' + db_name(dek.database_id) + ''. Last backup date: '' + COALESCE(CAST(c.pvt_key_last_backup_date AS VARCHAR(100)), ''Never'') AS Details FROM sys.certificates c INNER JOIN sys.dm_database_encryption_keys dek ON c.thumbprint = dek.encryptor_thumbprint WHERE pvt_key_last_backup_date IS NULL OR pvt_key_last_backup_date <= DATEADD(dd, -30, GETDATE()) OPTION (RECOMPILE);'; @@ -1231,7 +1240,7 @@ AS 1 AS Priority, ''Backup'' AS FindingsGroup, ''Encryption Certificate Not Backed Up Recently'' AS Finding, - ''https://BrentOzar.com/go/tde'' AS URL, + ''https://www.brentozar.com/go/tde'' AS URL, ''The certificate '' + c.name + '' is used to encrypt database backups. Last backup date: '' + COALESCE(CAST(c.pvt_key_last_backup_date AS VARCHAR(100)), ''Never'') AS Details FROM sys.certificates c INNER JOIN msdb.dbo.backupset bs ON c.thumbprint = bs.encryptor_thumbprint @@ -1268,7 +1277,7 @@ AS 200 AS Priority , 'Backup' AS FindingsGroup , 'MSDB Backup History Not Purged' AS Finding , - 'https://BrentOzar.com/go/history' AS URL , + 'https://www.brentozar.com/go/history' AS URL , ( 'Database backup history retained back to ' + CAST(bs.backup_start_date AS VARCHAR(20)) ) AS Details FROM msdb.dbo.backupset bs @@ -1303,7 +1312,7 @@ AS 200 AS Priority , 'Backup' AS FindingsGroup , 'MSDB Backup History Purged Too Frequently' AS Finding , - 'https://BrentOzar.com/go/history' AS URL , + 'https://www.brentozar.com/go/history' AS URL , ( 'Database backup history only retained back to ' + CAST(bs.backup_start_date AS VARCHAR(20)) ) AS Details FROM msdb.dbo.backupset bs @@ -1336,7 +1345,7 @@ AS 200 AS Priority , 'Performance' AS FindingsGroup , 'Snapshot Backups Occurring' AS Finding , - 'https://BrentOzar.com/go/snaps' AS URL , + 'https://www.brentozar.com/go/snaps' AS URL , ( CAST(COUNT(*) AS VARCHAR(20)) + ' snapshot-looking backups have occurred in the last two weeks, indicating that IO may be freezing up.') AS Details FROM msdb.dbo.backupset bs WHERE bs.type = 'D' @@ -1364,7 +1373,7 @@ AS 50 AS Priority , 'Performance' AS FindingsGroup , 'Snapshotting Too Many Databases' AS Finding , - 'https://BrentOzar.com/go/toomanysnaps' AS URL , + 'https://www.brentozar.com/go/toomanysnaps' AS URL , ( CAST(SUM(1) AS VARCHAR(20)) + ' databases snapshotted at once in the last two weeks, indicating that IO may be freezing up. Microsoft does not recommend VSS snaps for 35 or more databases.') AS Details FROM msdb.dbo.backupset bs WHERE bs.type = 'D' @@ -1393,7 +1402,7 @@ AS 230 AS Priority , 'Security' AS FindingsGroup , 'Sysadmins' AS Finding , - 'https://BrentOzar.com/go/sa' AS URL , + 'https://www.brentozar.com/go/sa' AS URL , ( 'Login [' + l.name + '] is a sysadmin - meaning they can do absolutely anything in SQL Server, including dropping databases or hiding their tracks.' ) AS Details FROM master.sys.syslogins l @@ -1451,7 +1460,7 @@ AS 230 AS Priority , 'Security' AS FindingsGroup , 'Security Admins' AS Finding , - 'https://BrentOzar.com/go/sa' AS URL , + 'https://www.brentozar.com/go/sa' AS URL , ( 'Login [' + l.name + '] is a security admin - meaning they can give themselves permission to do absolutely anything in SQL Server, including dropping databases or hiding their tracks.' ) AS Details FROM master.sys.syslogins l @@ -1479,7 +1488,7 @@ AS 230 AS [Priority] , 'Security' AS [FindingsGroup] , 'Login Can Control Server' AS [Finding] , - 'https://BrentOzar.com/go/sa' AS [URL] , + 'https://www.brentozar.com/go/sa' AS [URL] , 'Login [' + pri.[name] + '] has the CONTROL SERVER permission - meaning they can do absolutely anything in SQL Server, including dropping databases or hiding their tracks.' AS [Details] FROM sys.server_principals AS pri @@ -1511,7 +1520,7 @@ AS 230 AS Priority , 'Security' AS FindingsGroup , 'Jobs Owned By Users' AS Finding , - 'https://BrentOzar.com/go/owners' AS URL , + 'https://www.brentozar.com/go/owners' AS URL , ( 'Job [' + j.name + '] is owned by [' + SUSER_SNAME(j.owner_sid) + '] - meaning if their login is disabled or not available due to Active Directory problems, the job will stop working.' ) AS Details @@ -1541,7 +1550,7 @@ AS 230 AS Priority , 'Security' AS FindingsGroup , 'Stored Procedure Runs at Startup' AS Finding , - 'https://BrentOzar.com/go/startup' AS URL , + 'https://www.brentozar.com/go/startup' AS URL , ( 'Stored procedure [master].[' + r.SPECIFIC_SCHEMA + '].[' + r.SPECIFIC_NAME @@ -1572,7 +1581,7 @@ AS 100 AS Priority, ''Performance'' AS FindingsGroup, ''Resource Governor Enabled'' AS Finding, - ''https://BrentOzar.com/go/rg'' AS URL, + ''https://www.brentozar.com/go/rg'' AS URL, (''Resource Governor is enabled. Queries may be throttled. Make sure you understand how the Classifier Function is configured.'') AS Details FROM sys.resource_governor_configuration WHERE is_enabled = 1 OPTION (RECOMPILE);'; IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; @@ -1602,7 +1611,7 @@ AS 100 AS Priority, ''Performance'' AS FindingsGroup, ''Server Triggers Enabled'' AS Finding, - ''https://BrentOzar.com/go/logontriggers/'' AS URL, + ''https://www.brentozar.com/go/logontriggers/'' AS URL, (''Server Trigger ['' + [name] ++ ''] is enabled. Make sure you understand what that trigger is doing - the less work it does, the better.'') AS Details FROM sys.server_triggers WHERE is_disabled = 0 AND is_ms_shipped = 0 OPTION (RECOMPILE);'; IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; @@ -1633,7 +1642,7 @@ AS 10 AS Priority , 'Performance' AS FindingsGroup , 'Auto-Close Enabled' AS Finding , - 'https://BrentOzar.com/go/autoclose' AS URL , + 'https://www.brentozar.com/go/autoclose' AS URL , ( 'Database [' + [name] + '] has auto-close enabled. This setting can dramatically decrease performance.' ) AS Details FROM sys.databases @@ -1665,7 +1674,7 @@ AS 10 AS Priority , 'Performance' AS FindingsGroup , 'Auto-Shrink Enabled' AS Finding , - 'https://BrentOzar.com/go/autoshrink' AS URL , + 'https://www.brentozar.com/go/autoshrink' AS URL , ( 'Database [' + [name] + '] has auto-shrink enabled. This setting can dramatically decrease performance.' ) AS Details FROM sys.databases @@ -1699,7 +1708,7 @@ AS 50 AS Priority, ''Reliability'' AS FindingsGroup, ''Page Verification Not Optimal'' AS Finding, - ''https://BrentOzar.com/go/torn'' AS URL, + ''https://www.brentozar.com/go/torn'' AS URL, (''Database ['' + [name] + ''] has '' + [page_verify_option_desc] + '' for page verification. SQL Server may have a harder time recognizing and recovering from storage corruption. Consider using CHECKSUM instead.'') COLLATE database_default AS Details FROM sys.databases WHERE page_verify_option < 2 @@ -1735,7 +1744,7 @@ AS 110 AS Priority , 'Performance' AS FindingsGroup , 'Auto-Create Stats Disabled' AS Finding , - 'https://BrentOzar.com/go/acs' AS URL , + 'https://www.brentozar.com/go/acs' AS URL , ( 'Database [' + [name] + '] has auto-create-stats disabled. SQL Server uses statistics to build better execution plans, and without the ability to automatically create more, performance may suffer.' ) AS Details FROM sys.databases @@ -1767,7 +1776,7 @@ AS 110 AS Priority , 'Performance' AS FindingsGroup , 'Auto-Update Stats Disabled' AS Finding , - 'https://BrentOzar.com/go/aus' AS URL , + 'https://www.brentozar.com/go/aus' AS URL , ( 'Database [' + [name] + '] has auto-update-stats disabled. SQL Server uses statistics to build better execution plans, and without the ability to automatically update them, performance may suffer.' ) AS Details FROM sys.databases @@ -1799,7 +1808,7 @@ AS 150 AS Priority , 'Performance' AS FindingsGroup , 'Stats Updated Asynchronously' AS Finding , - 'https://BrentOzar.com/go/asyncstats' AS URL , + 'https://www.brentozar.com/go/asyncstats' AS URL , ( 'Database [' + [name] + '] has auto-update-stats-async enabled. When SQL Server gets a query for a table with out-of-date statistics, it will run the query with the stats it has - while updating stats to make later queries better. The initial run of the query may suffer, though.' ) AS Details FROM sys.databases @@ -1831,7 +1840,7 @@ AS 200 AS Priority , 'Informational' AS FindingsGroup , 'Date Correlation On' AS Finding , - 'https://BrentOzar.com/go/corr' AS URL , + 'https://www.brentozar.com/go/corr' AS URL , ( 'Database [' + [name] + '] has date correlation enabled. This is not a default setting, and it has some performance overhead. It tells SQL Server that date fields in two tables are related, and SQL Server maintains statistics showing that relation.' ) AS Details FROM sys.databases @@ -1866,7 +1875,7 @@ AS 200 AS Priority, ''Informational'' AS FindingsGroup, ''Database Encrypted'' AS Finding, - ''https://BrentOzar.com/go/tde'' AS URL, + ''https://www.brentozar.com/go/tde'' AS URL, (''Database ['' + [name] + ''] has Transparent Data Encryption enabled. Make absolutely sure you have backed up the certificate and private key, or else you will not be able to restore this database.'') AS Details FROM sys.databases WHERE is_encrypted = 1 @@ -2077,7 +2086,7 @@ AS 200 AS Priority , 'Non-Default Server Config' AS FindingsGroup , cr.name AS Finding , - 'https://BrentOzar.com/go/conf' AS URL , + 'https://www.brentozar.com/go/conf' AS URL , ( 'This sp_configure option has been changed. Its default value is ' + COALESCE(CAST(cd.[DefaultValue] AS VARCHAR(100)), '(unknown)') @@ -2119,7 +2128,7 @@ AS 200, 'Performance', 'Non-Dynamic Memory', - 'https://BrentOzar.com/go/memory', + 'https://www.brentozar.com/go/memory', 'Minimum Server Memory setting is the same as the Maximum (both set to ' + CAST(@MinServerMemory AS NVARCHAR(50)) + '). This will not allow dynamic memory. Please revise memory settings' ); END; @@ -2159,7 +2168,7 @@ AS 200 AS Priority , 'Performance' AS FindingsGroup , cr.name AS Finding , - 'https://BrentOzar.com/go/cxpacket' AS URL , + 'https://www.brentozar.com/go/cxpacket' AS URL , ( 'Set to ' + CAST(cr.value_in_use AS NVARCHAR(50)) + ', its default value. Changing this sp_configure setting may reduce CXPACKET waits.') FROM sys.configurations cr INNER JOIN #ConfigurationDefaults cd ON cd.name = cr.name @@ -2190,7 +2199,7 @@ AS 170 AS Priority , 'File Configuration' AS FindingsGroup , 'System Database on C Drive' AS Finding , - 'https://BrentOzar.com/go/cdrive' AS URL , + 'https://www.brentozar.com/go/cdrive' AS URL , ( 'The ' + DB_NAME(database_id) + ' database has a file on the C drive. Putting system databases on the C drive runs the risk of crashing the server when it runs out of space.' ) AS Details FROM sys.master_files @@ -2222,7 +2231,7 @@ AS 20 AS Priority , 'File Configuration' AS FindingsGroup , 'TempDB on C Drive' AS Finding , - 'https://BrentOzar.com/go/cdrive' AS URL , + 'https://www.brentozar.com/go/cdrive' AS URL , CASE WHEN growth > 0 THEN ( 'The tempdb database has files on the C drive. TempDB frequently grows unpredictably, putting your server at risk of running out of C drive space and crashing hard. C is also often much slower than other drives, so performance may be suffering.' ) ELSE ( 'The tempdb database has files on the C drive. TempDB is not set to Autogrow, hopefully it is big enough. C is also often much slower than other drives, so performance may be suffering.' ) @@ -2255,7 +2264,7 @@ AS 20 AS Priority , 'Reliability' AS FindingsGroup , 'User Databases on C Drive' AS Finding , - 'https://BrentOzar.com/go/cdrive' AS URL , + 'https://www.brentozar.com/go/cdrive' AS URL , ( 'The ' + DB_NAME(database_id) + ' database has a file on the C drive. Putting databases on the C drive runs the risk of crashing the server when it runs out of space.' ) AS Details FROM sys.master_files @@ -2291,7 +2300,7 @@ AS 200 AS Priority , 'Informational' AS FindingsGroup , 'Tables in the Master Database' AS Finding , - 'https://BrentOzar.com/go/mastuser' AS URL , + 'https://www.brentozar.com/go/mastuser' AS URL , ( 'The ' + name + ' table in the master database was created by end users on ' + CAST(create_date AS VARCHAR(20)) @@ -2323,7 +2332,7 @@ AS 200 AS Priority , 'Informational' AS FindingsGroup , 'Tables in the MSDB Database' AS Finding , - 'https://BrentOzar.com/go/msdbuser' AS URL , + 'https://www.brentozar.com/go/msdbuser' AS URL , ( 'The ' + name + ' table in the msdb database was created by end users on ' + CAST(create_date AS VARCHAR(20)) @@ -2353,7 +2362,7 @@ AS 200 AS Priority , 'Informational' AS FindingsGroup , 'Tables in the Model Database' AS Finding , - 'https://BrentOzar.com/go/model' AS URL , + 'https://www.brentozar.com/go/model' AS URL , ( 'The ' + name + ' table in the model database was created by end users on ' + CAST(create_date AS VARCHAR(20)) @@ -2387,7 +2396,7 @@ AS 200 AS Priority , 'Monitoring' AS FindingsGroup , 'Not All Alerts Configured' AS Finding , - 'https://BrentOzar.com/go/alert' AS URL , + 'https://www.brentozar.com/go/alert' AS URL , ( 'Not all SQL Server Agent alerts have been configured. This is a free, easy way to get notified of corruption, job failures, or major outages even before monitoring systems pick it up.' ) AS Details; END; END; @@ -2418,7 +2427,7 @@ AS 200 AS Priority , 'Monitoring' AS FindingsGroup , 'Alerts Configured without Follow Up' AS Finding , - 'https://BrentOzar.com/go/alert' AS URL , + 'https://www.brentozar.com/go/alert' AS URL , ( 'SQL Server Agent alerts have been configured but they either do not notify anyone or else they do not take any action. This is a free, easy way to get notified of corruption, job failures, or major outages even before monitoring systems pick it up.' ) AS Details; END; @@ -2448,7 +2457,7 @@ AS 200 AS Priority , 'Monitoring' AS FindingsGroup , 'No Alerts for Corruption' AS Finding , - 'https://BrentOzar.com/go/alert' AS URL , + 'https://www.brentozar.com/go/alert' AS URL , ( 'SQL Server Agent alerts do not exist for errors 823, 824, and 825. These three errors can give you notification about early hardware failure. Enabling them can prevent you a lot of heartbreak.' ) AS Details; END; @@ -2478,7 +2487,7 @@ AS 200 AS Priority , 'Monitoring' AS FindingsGroup , 'No Alerts for Sev 19-25' AS Finding , - 'https://BrentOzar.com/go/alert' AS URL , + 'https://www.brentozar.com/go/alert' AS URL , ( 'SQL Server Agent alerts do not exist for severity levels 19 through 25. These are some very severe SQL Server errors. Knowing that these are happening may let you recover from errors faster.' ) AS Details; END; @@ -2510,7 +2519,7 @@ AS 200 AS Priority , 'Monitoring' AS FindingsGroup , 'Alerts Disabled' AS Finding , - 'https://BrentOzar.com/go/alert' AS URL , + 'https://www.brentozar.com/go/alert' AS URL , ( 'The following Alert is disabled, please review and enable if desired: ' + name ) AS Details FROM msdb.dbo.sysalerts @@ -2543,7 +2552,7 @@ AS ,200 AS [Priority] ,'Monitoring' AS FindingsGroup ,'Alerts Without Event Descriptions' AS Finding - ,'https://BrentOzar.com/go/alert' AS [URL] + ,'https://www.brentozar.com/go/alert' AS [URL] ,('The following Alert is not including detailed event descriptions in its output messages: ' + QUOTENAME([name]) + '. You can fix it by ticking the relevant boxes in its Properties --> Options page.') AS Details FROM msdb.dbo.sysalerts @@ -2577,7 +2586,7 @@ AS 200 AS Priority , 'Monitoring' AS FindingsGroup , 'No Operators Configured/Enabled' AS Finding , - 'https://BrentOzar.com/go/op' AS URL , + 'https://www.brentozar.com/go/op' AS URL , ( 'No SQL Server Agent operators (emails) have been configured. This is a free, easy way to get notified of corruption, job failures, or major outages even before monitoring systems pick it up.' ) AS Details; END; @@ -2608,7 +2617,7 @@ AS 1 AS Priority , ''Corruption'' AS FindingsGroup , ''Database Corruption Detected'' AS Finding , - ''https://BrentOzar.com/go/repair'' AS URL , + ''https://www.brentozar.com/go/repair'' AS URL , ( ''Database mirroring has automatically repaired at least one corrupt page in the last 30 days. For more information, query the DMV sys.dm_db_mirroring_auto_page_repair.'' ) AS Details FROM (SELECT rp2.database_id, rp2.modification_time FROM sys.dm_db_mirroring_auto_page_repair rp2 @@ -2652,7 +2661,7 @@ AS 1 AS Priority , ''Corruption'' AS FindingsGroup , ''Database Corruption Detected'' AS Finding , - ''https://BrentOzar.com/go/repair'' AS URL , + ''https://www.brentozar.com/go/repair'' AS URL , ( ''Availability Groups has automatically repaired at least one corrupt page in the last 30 days. For more information, query the DMV sys.dm_hadr_auto_page_repair.'' ) AS Details FROM sys.dm_hadr_auto_page_repair rp INNER JOIN master.sys.databases db ON rp.database_id = db.database_id @@ -2690,7 +2699,7 @@ AS 1 AS Priority , ''Corruption'' AS FindingsGroup , ''Database Corruption Detected'' AS Finding , - ''https://BrentOzar.com/go/repair'' AS URL , + ''https://www.brentozar.com/go/repair'' AS URL , ( ''SQL Server has detected at least one corrupt page in the last 30 days. For more information, query the system table msdb.dbo.suspect_pages.'' ) AS Details FROM msdb.dbo.suspect_pages sp INNER JOIN master.sys.databases db ON sp.database_id = db.database_id @@ -2724,7 +2733,7 @@ AS 'Performance' AS FindingsGroup , 'Slow Storage Reads on Drive ' + UPPER(LEFT(mf.physical_name, 1)) AS Finding , - 'https://BrentOzar.com/go/slow' AS URL , + 'https://www.brentozar.com/go/slow' AS URL , 'Reads are averaging longer than 200ms for at least one database on this drive. For specific database file speeds, run the query from the information link.' AS Details FROM sys.dm_io_virtual_file_stats(NULL, NULL) AS fs @@ -2755,7 +2764,7 @@ AS 'Performance' AS FindingsGroup , 'Slow Storage Writes on Drive ' + UPPER(LEFT(mf.physical_name, 1)) AS Finding , - 'https://BrentOzar.com/go/slow' AS URL , + 'https://www.brentozar.com/go/slow' AS URL , 'Writes are averaging longer than 100ms for at least one database on this drive. For specific database file speeds, run the query from the information link.' AS Details FROM sys.dm_io_virtual_file_stats(NULL, NULL) AS fs @@ -2792,7 +2801,7 @@ AS 170 , 'File Configuration' , 'TempDB Only Has 1 Data File' , - 'https://BrentOzar.com/go/tempdb' , + 'https://www.brentozar.com/go/tempdb' , 'TempDB is only configured with one data file. More data files are usually required to alleviate SGAM contention.' ); END; @@ -2827,7 +2836,7 @@ AS 170 , 'File Configuration' , 'TempDB Unevenly Sized Data Files' , - 'https://BrentOzar.com/go/tempdb' , + 'https://www.brentozar.com/go/tempdb' , 'TempDB data files are not configured with the same size. Unevenly sized tempdb data files will result in unevenly sized workloads.' ); END; @@ -2852,7 +2861,7 @@ AS 150 AS Priority , 'Performance' AS FindingsGroup , 'Queries Forcing Order Hints' AS Finding , - 'https://BrentOzar.com/go/hints' AS URL , + 'https://www.brentozar.com/go/hints' AS URL , CAST(occurrence AS VARCHAR(10)) + ' instances of order hinting have been recorded since restart. This means queries are bossing the SQL Server optimizer around, and if they don''t know what they''re doing, this can cause more harm than good. This can also explain why DBA tuning efforts aren''t working.' AS Details FROM sys.dm_exec_query_optimizer_info @@ -2879,7 +2888,7 @@ AS 150 AS Priority , 'Performance' AS FindingsGroup , 'Queries Forcing Join Hints' AS Finding , - 'https://BrentOzar.com/go/hints' AS URL , + 'https://www.brentozar.com/go/hints' AS URL , CAST(occurrence AS VARCHAR(10)) + ' instances of join hinting have been recorded since restart. This means queries are bossing the SQL Server optimizer around, and if they don''t know what they''re doing, this can cause more harm than good. This can also explain why DBA tuning efforts aren''t working.' AS Details FROM sys.dm_exec_query_optimizer_info @@ -2907,7 +2916,7 @@ AS 200 AS Priority , 'Informational' AS FindingsGroup , 'Linked Server Configured' AS Finding , - 'https://BrentOzar.com/go/link' AS URL , + 'https://www.brentozar.com/go/link' AS URL , +CASE WHEN l.remote_name = 'sa' THEN COALESCE(s.data_source, s.provider) + ' is configured as a linked server. Check its security configuration as it is connecting with sa, because any user who queries it will get admin-level permissions.' @@ -2934,7 +2943,7 @@ AS 100 AS Priority , ''Performance'' AS FindingsGroup , ''Max Memory Set Too High'' AS Finding , - ''https://BrentOzar.com/go/max'' AS URL , + ''https://www.brentozar.com/go/max'' AS URL , ''SQL Server max memory is set to '' + CAST(c.value_in_use AS VARCHAR(20)) + '' megabytes, but the server only has '' @@ -2966,7 +2975,7 @@ AS 1 AS Priority , ''Performance'' AS FindingsGroup , ''Memory Dangerously Low'' AS Finding , - ''https://BrentOzar.com/go/max'' AS URL , + ''https://www.brentozar.com/go/max'' AS URL , ''The server has '' + CAST(( CAST(m.total_physical_memory_kb AS BIGINT) / 1024 ) AS VARCHAR(20)) + '' megabytes of physical memory, but only '' + CAST(( CAST(m.available_physical_memory_kb AS BIGINT) / 1024 ) AS VARCHAR(20)) + '' megabytes are available. As the server runs out of memory, there is danger of swapping to disk, which will kill performance.'' AS Details FROM sys.dm_os_sys_memory m @@ -2994,7 +3003,7 @@ AS 1 AS Priority , ''Performance'' AS FindingsGroup , ''Memory Dangerously Low in NUMA Nodes'' AS Finding , - ''https://BrentOzar.com/go/max'' AS URL , + ''https://www.brentozar.com/go/max'' AS URL , ''At least one NUMA node is reporting THREAD_RESOURCES_LOW in sys.dm_os_nodes and can no longer create threads.'' AS Details FROM sys.dm_os_nodes m WHERE node_state_desc LIKE ''%THREAD_RESOURCES_LOW%'' OPTION (RECOMPILE);'; @@ -3026,7 +3035,7 @@ AS 200 AS Priority , 'Informational' AS FindingsGroup , 'Cluster Node' AS Finding , - 'https://BrentOzar.com/go/node' AS URL , + 'https://www.brentozar.com/go/node' AS URL , 'This is a node in a cluster.' AS Details FROM sys.dm_os_cluster_nodes; END; @@ -3055,7 +3064,7 @@ AS 230 AS Priority , 'Security' AS FindingsGroup , 'Database Owner <> ' + @UsualDBOwner AS Finding , - 'https://BrentOzar.com/go/owndb' AS URL , + 'https://www.brentozar.com/go/owndb' AS URL , ( 'Database name: ' + [name] + ' ' + 'Owner name: ' + SUSER_SNAME(owner_sid) ) AS Details FROM sys.databases @@ -3117,7 +3126,7 @@ AS 230 AS Priority , 'Security' AS FindingsGroup , 'SQL Agent Job Runs at Startup' AS Finding , - 'https://BrentOzar.com/go/startup' AS URL , + 'https://www.brentozar.com/go/startup' AS URL , ( 'Job [' + j.name + '] runs automatically when SQL Server Agent starts up. Make sure you know exactly what this job is doing, because it could pose a security risk.' ) AS Details FROM msdb.dbo.sysschedules sched @@ -3146,7 +3155,7 @@ AS 100 AS Priority , 'Performance' AS FindingsGroup , 'Unusual SQL Server Edition' AS Finding , - 'https://BrentOzar.com/go/workgroup' AS URL , + 'https://www.brentozar.com/go/workgroup' AS URL , ( 'This server is using ' + CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) + ', which is capped at low amounts of CPU and memory.' ) AS Details @@ -3177,7 +3186,7 @@ AS 10 AS Priority , 'Performance' AS FindingsGroup , '32-bit SQL Server Installed' AS Finding , - 'https://BrentOzar.com/go/32bit' AS URL , + 'https://www.brentozar.com/go/32bit' AS URL , ( 'This server uses the 32-bit x86 binaries for SQL Server instead of the 64-bit x64 binaries. The amount of memory available for query workspace and execution plans is heavily limited.' ) AS Details WHERE CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) NOT LIKE '%64%'; END; @@ -3203,7 +3212,7 @@ AS 200 AS Priority , 'Performance' AS FindingsGroup , 'Old Compatibility Level' AS Finding , - 'https://BrentOzar.com/go/compatlevel' AS URL , + 'https://www.brentozar.com/go/compatlevel' AS URL , ( 'Database ' + [name] + ' is compatibility level ' + CAST(compatibility_level AS VARCHAR(20)) @@ -3235,7 +3244,7 @@ AS 200 AS [Priority] , 'Monitoring' AS FindingsGroup , 'Agent Jobs Without Failure Emails' AS Finding , - 'https://BrentOzar.com/go/alerts' AS URL , + 'https://www.brentozar.com/go/alerts' AS URL , 'The job ' + [name] + ' has not been set up to notify an operator if it fails.' AS Details FROM msdb.[dbo].[sysjobs] j @@ -3274,7 +3283,7 @@ AS 170 AS Priority , 'Reliability' AS FindingGroup , 'Remote DAC Disabled' AS Finding , - 'https://BrentOzar.com/go/dac' AS URL , + 'https://www.brentozar.com/go/dac' AS URL , 'Remote access to the Dedicated Admin Connection (DAC) is not enabled. The DAC can make remote troubleshooting much easier when SQL Server is unresponsive.'; END; @@ -3300,7 +3309,7 @@ AS 50 AS Priority , 'Performance' AS FindingGroup , 'CPU Schedulers Offline' AS Finding , - 'https://BrentOzar.com/go/schedulers' AS URL , + 'https://www.brentozar.com/go/schedulers' AS URL , 'Some CPU cores are not accessible to SQL Server due to affinity masking or licensing problems.'; END; @@ -3328,7 +3337,7 @@ AS 50 AS Priority , ''Performance'' AS FindingGroup , ''Memory Nodes Offline'' AS Finding , - ''https://BrentOzar.com/go/schedulers'' AS URL , + ''https://www.brentozar.com/go/schedulers'' AS URL , ''Due to affinity masking or licensing problems, some of the memory may not be available.'' OPTION (RECOMPILE)'; IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; @@ -3361,7 +3370,7 @@ AS 20 AS Priority , 'Reliability' AS FindingGroup , 'Unusual Database State: ' + [state_desc] AS Finding , - 'https://BrentOzar.com/go/repair' AS URL , + 'https://www.brentozar.com/go/repair' AS URL , 'This database may not be online.' FROM sys.databases WHERE state > 1; @@ -3390,7 +3399,7 @@ AS 200 AS Priority , 'Reliability' AS FindingGroup , 'Extended Stored Procedures in Master' AS Finding , - 'https://BrentOzar.com/go/clr' AS URL , + 'https://www.brentozar.com/go/clr' AS URL , 'The [' + name + '] extended stored procedure is in the master database. CLR may be in use, and the master database now needs to be part of your backup/recovery planning.' FROM master.sys.extended_procedures; @@ -3415,7 +3424,7 @@ AS 50 AS Priority , 'Performance' AS FindingGroup , 'Poison Wait Detected: ' + wait_type AS Finding , - 'https://BrentOzar.com/go/poison/#' + wait_type AS URL , + 'https://www.brentozar.com/go/poison/#' + wait_type AS URL , CONVERT(VARCHAR(10), (SUM([wait_time_ms]) / 1000) / 86400) + ':' + CONVERT(VARCHAR(20), DATEADD(s, (SUM([wait_time_ms]) / 1000), 0), 108) + ' of this wait have been recorded. This wait often indicates killer performance problems.' FROM sys.[dm_os_wait_stats] WHERE wait_type IN('IO_QUEUE_LIMIT', 'IO_RETRY', 'LOG_RATE_GOVERNOR', 'POOL_LOG_RATE_GOVERNOR', 'PREEMPTIVE_DEBUG', 'RESMGR_THROTTLED', 'RESOURCE_SEMAPHORE', 'RESOURCE_SEMAPHORE_QUERY_COMPILE','SE_REPL_CATCHUP_THROTTLE','SE_REPL_COMMIT_ACK','SE_REPL_COMMIT_TURN','SE_REPL_ROLLBACK_ACK','SE_REPL_SLOW_SECONDARY_THROTTLE','THREADPOOL') @@ -3443,7 +3452,7 @@ AS 50 AS Priority , 'Performance' AS FindingGroup , 'Poison Wait Detected: Serializable Locking' AS Finding , - 'https://BrentOzar.com/go/serializable' AS URL , + 'https://www.brentozar.com/go/serializable' AS URL , CONVERT(VARCHAR(10), (SUM([wait_time_ms]) / 1000) / 86400) + ':' + CONVERT(VARCHAR(20), DATEADD(s, (SUM([wait_time_ms]) / 1000), 0), 108) + ' of LCK_M_R% waits have been recorded. This wait often indicates killer performance problems.' FROM sys.[dm_os_wait_stats] WHERE wait_type IN ('LCK_M_RS_S', 'LCK_M_RS_U', 'LCK_M_RIn_NL','LCK_M_RIn_S', 'LCK_M_RIn_U','LCK_M_RIn_X', 'LCK_M_RX_S', 'LCK_M_RX_U','LCK_M_RX_X') @@ -3473,7 +3482,7 @@ AS 'Reliability' AS FindingGroup , 'Possibly Broken Log Shipping' AS Finding , d.[name] , - 'https://BrentOzar.com/go/shipping' AS URL , + 'https://www.brentozar.com/go/shipping' AS URL , d.[name] + ' is in a restoring state, but has not had a backup applied in the last two days. This is a possible indication of a broken transaction log shipping setup.' FROM [master].sys.databases d INNER JOIN [master].sys.database_mirroring dm ON d.database_id = dm.database_id @@ -3508,7 +3517,7 @@ AS ''Performance'' AS FindingsGroup, ''Change Tracking Enabled'' AS Finding, d.[name], - ''https://BrentOzar.com/go/tracking'' AS URL, + ''https://www.brentozar.com/go/tracking'' AS URL, ( d.[name] + '' has change tracking enabled. This is not a default setting, and it has some performance overhead. It keeps track of changes to rows in tables that have change tracking turned on.'' ) AS Details FROM sys.change_tracking_databases AS ctd INNER JOIN sys.databases AS d ON ctd.database_id = d.database_id OPTION (RECOMPILE)'; IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; @@ -3537,7 +3546,7 @@ AS 200 AS Priority , ''Informational'' AS FindingGroup , ''Backup Compression Default Off'' AS Finding , - ''https://BrentOzar.com/go/backup'' AS URL , + ''https://www.brentozar.com/go/backup'' AS URL , ''Uncompressed full backups have happened recently, and backup compression is not turned on at the server level. Backup compression is included with SQL Server 2008R2 & newer, even in Standard Edition. We recommend turning backup compression on by default so that ad-hoc backups will get compressed.'' FROM sys.configurations WHERE configuration_id = 1579 AND CAST(value_in_use AS INT) = 0 @@ -3569,7 +3578,7 @@ AS 100 AS Priority, ''Performance'' AS FindingsGroup, ''Memory Pressure Affecting Queries'' AS Finding, - ''https://BrentOzar.com/go/grants'' AS URL, + ''https://www.brentozar.com/go/grants'' AS URL, CAST(SUM(forced_grant_count) AS NVARCHAR(100)) + '' forced grants reported in the DMV sys.dm_exec_query_resource_semaphores, indicating memory pressure has affected query runtimes.'' FROM sys.dm_exec_query_resource_semaphores WHERE [forced_grant_count] IS NOT NULL OPTION (RECOMPILE);'; @@ -3597,7 +3606,7 @@ AS 150, 'Performance', 'Deadlocks Happening Daily', - 'https://BrentOzar.com/go/deadlocks', + 'https://www.brentozar.com/go/deadlocks', CAST(CAST(p.cntr_value / @DaysUptime AS BIGINT) AS NVARCHAR(100)) + ' average deadlocks per day. To find them, run sp_BlitzLock.' AS Details FROM sys.dm_os_performance_counters p INNER JOIN sys.databases d ON d.name = 'tempdb' @@ -3660,7 +3669,7 @@ AS Finding, URL, Details) - SELECT TOP 1 125, 10, 'Performance', 'Plan Cache Erased Recently', 'https://BrentOzar.com/askbrent/plan-cache-erased-recently/', + SELECT TOP 1 125, 10, 'Performance', 'Plan Cache Erased Recently', 'https://www.brentozar.com/askbrent/plan-cache-erased-recently/', 'The oldest query in the plan cache was created at ' + CAST(creation_time AS NVARCHAR(50)) + CASE WHEN @user_perm_gb_out IS NULL THEN '. Someone ran DBCC FREEPROCCACHE, restarted SQL Server, or it is under horrific memory pressure.' @@ -3685,7 +3694,7 @@ AS Finding, URL, Details) - VALUES(126, 5, 'Reliability', 'Priority Boost Enabled', 'https://BrentOzar.com/go/priorityboost/', + VALUES(126, 5, 'Reliability', 'Priority Boost Enabled', 'https://www.brentozar.com/go/priorityboost/', 'Priority Boost sounds awesome, but it can actually cause your SQL Server to crash.'); END; @@ -3708,7 +3717,7 @@ AS IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 128) WITH NOWAIT; INSERT INTO #BlitzResults(CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES(128, 20, 'Reliability', 'Unsupported Build of SQL Server', 'https://BrentOzar.com/go/unsupported', + VALUES(128, 20, 'Reliability', 'Unsupported Build of SQL Server', 'https://www.brentozar.com/go/unsupported', 'Version ' + CAST(@ProductVersionMajor AS VARCHAR(100)) + CASE WHEN @ProductVersionMajor >= 11 THEN '.' + CAST(@ProductVersionMinor AS VARCHAR(100)) + ' is no longer supported by Microsoft. You need to apply a service pack.' @@ -3833,7 +3842,7 @@ AS 10 AS Priority, ''Performance'' AS FindingsGroup, ''High Memory Use for In-Memory OLTP (Hekaton)'' AS Finding, - ''https://BrentOzar.com/go/hekaton'' AS URL, + ''https://www.brentozar.com/go/hekaton'' AS URL, CAST(CAST((SUM(mem.pages_kb / 1024.0) / CAST(value_in_use AS INT) * 100) AS INT) AS NVARCHAR(100)) + ''% of your '' + CAST(CAST((CAST(value_in_use AS DECIMAL(38,1)) / 1024) AS MONEY) AS NVARCHAR(100)) + ''GB of your max server memory is being used for in-memory OLTP tables (Hekaton). Microsoft recommends having 2X your Hekaton table space available in memory just for Hekaton, with a max of 250GB of in-memory data regardless of your server memory capacity.'' AS Details FROM sys.configurations c INNER JOIN sys.dm_os_memory_clerks mem ON mem.type = ''MEMORYCLERK_XTP'' WHERE c.name = ''max server memory (MB)'' @@ -3863,7 +3872,7 @@ AS 200 AS Priority, ''Performance'' AS FindingsGroup, ''In-Memory OLTP (Hekaton) In Use'' AS Finding, - ''https://BrentOzar.com/go/hekaton'' AS URL, + ''https://www.brentozar.com/go/hekaton'' AS URL, CAST(CAST((SUM(mem.pages_kb / 1024.0) / CAST(value_in_use AS INT) * 100) AS INT) AS NVARCHAR(100)) + ''% of your '' + CAST(CAST((CAST(value_in_use AS DECIMAL(38,1)) / 1024) AS MONEY) AS NVARCHAR(100)) + ''GB of your max server memory is being used for in-memory OLTP tables (Hekaton).'' AS Details FROM sys.configurations c INNER JOIN sys.dm_os_memory_clerks mem ON mem.type = ''MEMORYCLERK_XTP'' WHERE c.name = ''max server memory (MB)'' @@ -3892,7 +3901,7 @@ AS 100 AS Priority, ''In-Memory OLTP (Hekaton)'' AS FindingsGroup, ''Transaction Errors'' AS Finding, - ''https://BrentOzar.com/go/hekaton'' AS URL, + ''https://www.brentozar.com/go/hekaton'' AS URL, ''Since restart: '' + CAST(validation_failures AS NVARCHAR(100)) + '' validation failures, '' + CAST(dependencies_failed AS NVARCHAR(100)) + '' dependency failures, '' + CAST(write_conflicts AS NVARCHAR(100)) + '' write conflicts, '' + CAST(unique_constraint_violations AS NVARCHAR(100)) + '' unique constraint violations.'' AS Details FROM sys.dm_xtp_transaction_stats WHERE validation_failures <> 0 @@ -3928,7 +3937,7 @@ AS 170 AS Priority , 'Reliability' AS FindingsGroup , 'Database Files on Network File Shares' AS Finding , - 'https://BrentOzar.com/go/nas' AS URL , + 'https://www.brentozar.com/go/nas' AS URL , ( 'Files for this database are on: ' + LEFT(mf.physical_name, 30)) AS Details FROM sys.databases d INNER JOIN sys.master_files mf ON d.database_id = mf.database_id @@ -3961,7 +3970,7 @@ AS 170 AS Priority , 'Reliability' AS FindingsGroup , 'Database Files Stored in Azure' AS Finding , - 'https://BrentOzar.com/go/azurefiles' AS URL , + 'https://www.brentozar.com/go/azurefiles' AS URL , ( 'Files for this database are on: ' + LEFT(mf.physical_name, 30)) AS Details FROM sys.databases d INNER JOIN sys.master_files mf ON d.database_id = mf.database_id @@ -4054,7 +4063,7 @@ AS 50 AS Priority , 'Reliability' AS FindingsGroup , 'There Is An Error With The Default Trace' AS Finding , - 'https://BrentOzar.com/go/defaulttrace' AS URL , + 'https://www.brentozar.com/go/defaulttrace' AS URL , 'Somebody has been messing with your trace files. Check the files are present at ' + @base_tracefilename AS Details END @@ -4081,7 +4090,7 @@ AS 170 AS Priority , 'Reliability' AS FindingsGroup , 'Errors Logged Recently in the Default Trace' AS Finding , - 'https://BrentOzar.com/go/defaulttrace' AS URL , + 'https://www.brentozar.com/go/defaulttrace' AS URL , CAST(t.TextData AS NVARCHAR(4000)) AS Details FROM #fnTraceGettable t WHERE t.EventClass = 22 @@ -4114,7 +4123,7 @@ AS 50 AS Priority , 'Performance' AS FindingsGroup , 'File Growths Slow' AS Finding , - 'https://BrentOzar.com/go/filegrowth' AS URL , + 'https://www.brentozar.com/go/filegrowth' AS URL , CAST(COUNT(*) AS NVARCHAR(100)) + ' growths took more than 15 seconds each. Consider setting file autogrowth to a smaller increment.' AS Details FROM #fnTraceGettable t WHERE t.EventClass IN (92, 93) @@ -4139,7 +4148,7 @@ AS 100 AS Priority, ''Performance'' AS FindingsGroup, ''Many Plans for One Query'' AS Finding, - ''https://BrentOzar.com/go/parameterization'' AS URL, + ''https://www.brentozar.com/go/parameterization'' AS URL, CAST(COUNT(DISTINCT plan_handle) AS NVARCHAR(50)) + '' plans are present for a single query in the plan cache - meaning we probably have parameterization issues.'' AS Details FROM sys.dm_exec_query_stats qs CROSS APPLY sys.dm_exec_plan_attributes(qs.plan_handle) pa @@ -4173,7 +4182,7 @@ AS 100 AS Priority, ''Performance'' AS FindingsGroup, ''High Number of Cached Plans'' AS Finding, - ''https://BrentOzar.com/go/planlimits'' AS URL, + ''https://www.brentozar.com/go/planlimits'' AS URL, ''Your server configuration is limited to '' + CAST(ht.buckets_count * 4 AS VARCHAR(20)) + '' '' + ht.name + '', and you are currently caching '' + CAST(cc.entries_count AS VARCHAR(20)) + ''.'' AS Details FROM sys.dm_os_memory_cache_hash_tables ht INNER JOIN sys.dm_os_memory_cache_counters cc ON ht.name = cc.name AND ht.type = cc.type @@ -4201,7 +4210,7 @@ AS Finding, URL, Details) - SELECT 165, 50, 'Performance', 'Too Much Free Memory', 'https://BrentOzar.com/go/freememory', + SELECT 165, 50, 'Performance', 'Too Much Free Memory', 'https://www.brentozar.com/go/freememory', CAST((CAST(cFree.cntr_value AS BIGINT) / 1024 / 1024 ) AS NVARCHAR(100)) + N'GB of free memory inside SQL Server''s buffer pool, which is ' + CAST((CAST(cTotal.cntr_value AS BIGINT) / 1024 / 1024) AS NVARCHAR(100)) + N'GB. You would think lots of free memory would be good, but check out the URL for more information.' AS Details FROM sys.dm_os_performance_counters cFree INNER JOIN sys.dm_os_performance_counters cTotal ON cTotal.object_name LIKE N'%Memory Manager%' @@ -4247,71 +4256,71 @@ AS IF @Debug IN (1, 2) RAISERROR('Generating database defaults.', 0, 1) WITH NOWAIT; INSERT INTO #DatabaseDefaults - SELECT 'is_supplemental_logging_enabled', 0, 131, 210, 'Supplemental Logging Enabled', 'https://BrentOzar.com/go/dbdefaults', NULL + SELECT 'is_supplemental_logging_enabled', 0, 131, 210, 'Supplemental Logging Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL FROM sys.all_columns WHERE name = 'is_supplemental_logging_enabled' AND object_id = OBJECT_ID('sys.databases'); INSERT INTO #DatabaseDefaults - SELECT 'snapshot_isolation_state', 0, 132, 210, 'Snapshot Isolation Enabled', 'https://BrentOzar.com/go/dbdefaults', NULL + SELECT 'snapshot_isolation_state', 0, 132, 210, 'Snapshot Isolation Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL FROM sys.all_columns WHERE name = 'snapshot_isolation_state' AND object_id = OBJECT_ID('sys.databases'); INSERT INTO #DatabaseDefaults SELECT 'is_read_committed_snapshot_on', CASE WHEN SERVERPROPERTY('EngineEdition') = 5 THEN 1 ELSE 0 END, /* RCSI is always enabled in Azure SQL DB per https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/1919 */ - 133, 210, CASE WHEN SERVERPROPERTY('EngineEdition') = 5 THEN 'Read Committed Snapshot Isolation Disabled' ELSE 'Read Committed Snapshot Isolation Enabled' END, 'https://BrentOzar.com/go/dbdefaults', NULL + 133, 210, CASE WHEN SERVERPROPERTY('EngineEdition') = 5 THEN 'Read Committed Snapshot Isolation Disabled' ELSE 'Read Committed Snapshot Isolation Enabled' END, 'https://www.brentozar.com/go/dbdefaults', NULL FROM sys.all_columns WHERE name = 'is_read_committed_snapshot_on' AND object_id = OBJECT_ID('sys.databases'); INSERT INTO #DatabaseDefaults - SELECT 'is_auto_create_stats_incremental_on', 0, 134, 210, 'Auto Create Stats Incremental Enabled', 'https://BrentOzar.com/go/dbdefaults', NULL + SELECT 'is_auto_create_stats_incremental_on', 0, 134, 210, 'Auto Create Stats Incremental Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL FROM sys.all_columns WHERE name = 'is_auto_create_stats_incremental_on' AND object_id = OBJECT_ID('sys.databases'); INSERT INTO #DatabaseDefaults - SELECT 'is_ansi_null_default_on', 0, 135, 210, 'ANSI NULL Default Enabled', 'https://BrentOzar.com/go/dbdefaults', NULL + SELECT 'is_ansi_null_default_on', 0, 135, 210, 'ANSI NULL Default Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL FROM sys.all_columns WHERE name = 'is_ansi_null_default_on' AND object_id = OBJECT_ID('sys.databases'); INSERT INTO #DatabaseDefaults - SELECT 'is_recursive_triggers_on', 0, 136, 210, 'Recursive Triggers Enabled', 'https://BrentOzar.com/go/dbdefaults', NULL + SELECT 'is_recursive_triggers_on', 0, 136, 210, 'Recursive Triggers Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL FROM sys.all_columns WHERE name = 'is_recursive_triggers_on' AND object_id = OBJECT_ID('sys.databases'); INSERT INTO #DatabaseDefaults - SELECT 'is_trustworthy_on', 0, 137, 210, 'Trustworthy Enabled', 'https://BrentOzar.com/go/dbdefaults', NULL + SELECT 'is_trustworthy_on', 0, 137, 210, 'Trustworthy Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL FROM sys.all_columns WHERE name = 'is_trustworthy_on' AND object_id = OBJECT_ID('sys.databases'); INSERT INTO #DatabaseDefaults - SELECT 'is_broker_enabled', 0, 230, 210, 'Broker Enabled', 'https://BrentOzar.com/go/dbdefaults', NULL + SELECT 'is_broker_enabled', 0, 230, 210, 'Broker Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL FROM sys.all_columns WHERE name = 'is_broker_enabled' AND object_id = OBJECT_ID('sys.databases'); INSERT INTO #DatabaseDefaults - SELECT 'is_honor_broker_priority_on', 0, 231, 210, 'Honor Broker Priority Enabled', 'https://BrentOzar.com/go/dbdefaults', NULL + SELECT 'is_honor_broker_priority_on', 0, 231, 210, 'Honor Broker Priority Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL FROM sys.all_columns WHERE name = 'is_honor_broker_priority_on' AND object_id = OBJECT_ID('sys.databases'); INSERT INTO #DatabaseDefaults - SELECT 'is_parameterization_forced', 0, 138, 210, 'Forced Parameterization Enabled', 'https://BrentOzar.com/go/dbdefaults', NULL + SELECT 'is_parameterization_forced', 0, 138, 210, 'Forced Parameterization Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL FROM sys.all_columns WHERE name = 'is_parameterization_forced' AND object_id = OBJECT_ID('sys.databases'); /* Not alerting for this since we actually want it and we have a separate check for it: INSERT INTO #DatabaseDefaults - SELECT 'is_query_store_on', 0, 139, 210, 'Query Store Enabled', 'https://BrentOzar.com/go/dbdefaults', NULL + SELECT 'is_query_store_on', 0, 139, 210, 'Query Store Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL FROM sys.all_columns WHERE name = 'is_query_store_on' AND object_id = OBJECT_ID('sys.databases'); */ INSERT INTO #DatabaseDefaults - SELECT 'is_cdc_enabled', 0, 140, 210, 'Change Data Capture Enabled', 'https://BrentOzar.com/go/dbdefaults', NULL + SELECT 'is_cdc_enabled', 0, 140, 210, 'Change Data Capture Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL FROM sys.all_columns WHERE name = 'is_cdc_enabled' AND object_id = OBJECT_ID('sys.databases'); INSERT INTO #DatabaseDefaults - SELECT 'containment', 0, 141, 210, 'Containment Enabled', 'https://BrentOzar.com/go/dbdefaults', NULL + SELECT 'containment', 0, 141, 210, 'Containment Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL FROM sys.all_columns WHERE name = 'containment' AND object_id = OBJECT_ID('sys.databases'); INSERT INTO #DatabaseDefaults - SELECT 'target_recovery_time_in_seconds', 0, 142, 210, 'Target Recovery Time Changed', 'https://BrentOzar.com/go/dbdefaults', NULL + SELECT 'target_recovery_time_in_seconds', 0, 142, 210, 'Target Recovery Time Changed', 'https://www.brentozar.com/go/dbdefaults', NULL FROM sys.all_columns WHERE name = 'target_recovery_time_in_seconds' AND object_id = OBJECT_ID('sys.databases'); INSERT INTO #DatabaseDefaults - SELECT 'delayed_durability', 0, 143, 210, 'Delayed Durability Enabled', 'https://BrentOzar.com/go/dbdefaults', NULL + SELECT 'delayed_durability', 0, 143, 210, 'Delayed Durability Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL FROM sys.all_columns WHERE name = 'delayed_durability' AND object_id = OBJECT_ID('sys.databases'); INSERT INTO #DatabaseDefaults - SELECT 'is_memory_optimized_elevate_to_snapshot_on', 0, 144, 210, 'Memory Optimized Enabled', 'https://BrentOzar.com/go/dbdefaults', NULL + SELECT 'is_memory_optimized_elevate_to_snapshot_on', 0, 144, 210, 'Memory Optimized Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL FROM sys.all_columns WHERE name = 'is_memory_optimized_elevate_to_snapshot_on' AND object_id = OBJECT_ID('sys.databases') AND SERVERPROPERTY('EngineEdition') <> 8; /* Hekaton is always enabled in Managed Instances per https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/1919 */ @@ -4453,7 +4462,7 @@ IF @ProductVersionMajor >= 10 250 AS [Priority] , 'Informational' AS [FindingsGroup] , 'SQL Server is running under an NT Service account' AS [Finding] , - 'https://BrentOzar.com/go/setup' AS [URL] , + 'https://www.brentozar.com/go/setup' AS [URL] , ( 'I''m running as ' + [service_account] + '. I wish I had an Active Directory service account instead.' ) AS [Details] FROM @@ -4493,7 +4502,7 @@ IF @ProductVersionMajor >= 10 250 AS [Priority] , 'Informational' AS [FindingsGroup] , 'SQL Server Agent is running under an NT Service account' AS [Finding] , - 'https://BrentOzar.com/go/setup' AS [URL] , + 'https://www.brentozar.com/go/setup' AS [URL] , ( 'I''m running as ' + [service_account] + '. I wish I had an Active Directory service account instead.' ) AS [Details] FROM @@ -5039,7 +5048,7 @@ IF @ProductVersionMajor >= 10 20 AS [Priority] , 'Reliability' AS [FindingsGroup] , 'Memory Dumps Have Occurred' AS [Finding] , - 'https://BrentOzar.com/go/dump' AS [URL] , + 'https://www.brentozar.com/go/dump' AS [URL] , ( 'That ain''t good. I''ve had ' + CAST(COUNT(*) AS VARCHAR(100)) + ' memory dumps between ' + CAST(CAST(MIN([creation_time]) AS DATETIME) AS VARCHAR(100)) + @@ -5076,7 +5085,7 @@ IF @ProductVersionMajor >= 10 200 AS [Priority] , 'Licensing' AS [FindingsGroup] , 'Non-Production License' AS [Finding] , - 'https://BrentOzar.com/go/licensing' AS [URL] , + 'https://www.brentozar.com/go/licensing' AS [URL] , ( 'We''re not the licensing police, but if this is supposed to be a production server, and you''re running ' + CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) + ' the good folks at Microsoft might get upset with you. Better start counting those cores.' @@ -5108,7 +5117,7 @@ IF @ProductVersionMajor >= 10 200 AS [Priority] , 'Performance' AS [FindingsGroup] , 'Buffer Pool Extensions Enabled' AS [Finding] , - 'https://BrentOzar.com/go/bpe' AS [URL] , + 'https://www.brentozar.com/go/bpe' AS [URL] , ( 'You have Buffer Pool Extensions enabled, and one lives here: ' + [path] + '. It''s currently ' + @@ -5148,7 +5157,7 @@ IF @ProductVersionMajor >= 10 170 AS Priority , 'File Configuration' AS FindingsGroup , 'TempDB Has >16 Data Files' AS Finding , - 'https://BrentOzar.com/go/tempdb' AS URL , + 'https://www.brentozar.com/go/tempdb' AS URL , 'Woah, Nelly! TempDB has ' + CAST(COUNT_BIG(*) AS VARCHAR(30)) + '. Did you forget to terminate a loop somewhere?' AS Details FROM sys.[master_files] AS [mf] WHERE [mf].[database_id] = 2 AND [mf].[type] = 0 @@ -5183,7 +5192,7 @@ IF @ProductVersionMajor >= 10 200 AS Priority , 'Monitoring' AS FindingsGroup , 'Extended Events Hyperextension' AS Finding , - 'https://BrentOzar.com/go/xe' AS URL , + 'https://www.brentozar.com/go/xe' AS URL , 'Hey big spender, you have ' + CAST(COUNT_BIG(*) AS VARCHAR(30)) + ' Extended Events sessions running. You sure you meant to do that?' AS Details FROM sys.dm_xe_sessions WHERE [name] NOT IN @@ -5305,7 +5314,7 @@ IF @ProductVersionMajor >= 10 END AS Priority, 'Performance' AS [FindingsGroup] , 'Shrink Database Step In Maintenance Plan' AS [Finding] , - 'https://BrentOzar.com/go/autoshrink' AS [URL] , + 'https://www.brentozar.com/go/autoshrink' AS [URL] , 'The maintenance plan ' + [mps].[name] + ' has a step to shrink databases in it. Shrinking databases is as outdated as maintenance plans.' + CASE WHEN COALESCE(ssc.name,'0') != '0' THEN + ' (Schedule: [' + ssc.name + '])' ELSE + '' END AS [Details] FROM [maintenance_plan_steps] [mps] @@ -5391,7 +5400,7 @@ IF @ProductVersionMajor >= 10 20 AS Priority , ''Reliability'' AS FindingsGroup , ''No Failover Cluster Nodes Available'' AS Finding , - ''https://BrentOzar.com/go/node'' AS URL , + ''https://www.brentozar.com/go/node'' AS URL , ''There are no failover cluster nodes available if the active node fails'' AS Details FROM ( SELECT SUM(CASE WHEN [status] = 0 AND [is_current_owner] = 0 THEN 1 ELSE 0 END) AS [available_nodes] @@ -5427,7 +5436,7 @@ IF @ProductVersionMajor >= 10 50 AS [Priority] , 'Reliability' AS [FindingsGroup] , 'TempDB File Error' AS [Finding] , - 'https://BrentOzar.com/go/tempdboops' AS [URL] , + 'https://www.brentozar.com/go/tempdboops' AS [URL] , 'Mismatch between the number of TempDB files in sys.master_files versus tempdb.sys.database_files' AS [Details]; END; @@ -5463,7 +5472,7 @@ IF @ProductVersionMajor >= 10 10 AS Priority, 'Performance' AS FindingsGroup, 'CPU w/Odd Number of Cores' AS Finding, - 'https://BrentOzar.com/go/oddity' AS URL, + 'https://www.brentozar.com/go/oddity' AS URL, 'Node ' + CONVERT(VARCHAR(10), parent_node_id) + ' has ' + CONVERT(VARCHAR(10), COUNT(cpu_id)) + CASE WHEN COUNT(cpu_id) = 1 THEN ' core assigned to it. This is a really bad NUMA configuration.' ELSE ' cores assigned to it. This is a really bad NUMA configuration.' @@ -5747,7 +5756,7 @@ IF @ProductVersionMajor >= 10 'DBCC SHRINK% Ran Recently' AS Finding , 'https://www.BrentOzar.com/go/dbcc' AS URL , 'The user ' + COALESCE(d.nt_user_name, d.login_name) + ' has run file shrinks ' + CAST(COUNT(*) AS NVARCHAR(100)) + ' times between ' + CONVERT(NVARCHAR(30), MIN(d.min_start_time)) + ' and ' + CONVERT(NVARCHAR(30), MAX(d.max_start_time)) + - '. So, uh, are they trying cause bad performance on purpose?' + '. So, uh, are they trying to cause bad performance on purpose?' AS Details FROM #dbcc_events_from_trace d WHERE d.dbcc_event_trunc_upper LIKE N'DBCC SHRINK%' @@ -6034,7 +6043,7 @@ IF @ProductVersionMajor >= 10 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 99) WITH NOWAIT; - EXEC dbo.sp_MSforeachdb 'USE [?]; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; IF EXISTS (SELECT * FROM sys.tables WITH (NOLOCK) WHERE name = ''sysmergepublications'' ) IF EXISTS ( SELECT * FROM sysmergepublications WITH (NOLOCK) WHERE retention = 0) INSERT INTO #BlitzResults (CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details) SELECT DISTINCT 99, DB_NAME(), 110, ''Performance'', ''Infinite merge replication metadata retention period'', ''https://BrentOzar.com/go/merge'', (''The ['' + DB_NAME() + ''] database has merge replication metadata retention period set to infinite - this can be the case of significant performance issues.'')'; + EXEC dbo.sp_MSforeachdb 'USE [?]; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; IF EXISTS (SELECT * FROM sys.tables WITH (NOLOCK) WHERE name = ''sysmergepublications'' ) IF EXISTS ( SELECT * FROM sysmergepublications WITH (NOLOCK) WHERE retention = 0) INSERT INTO #BlitzResults (CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details) SELECT DISTINCT 99, DB_NAME(), 110, ''Performance'', ''Infinite merge replication metadata retention period'', ''https://www.brentozar.com/go/merge'', (''The ['' + DB_NAME() + ''] database has merge replication metadata retention period set to infinite - this can be the case of significant performance issues.'')'; END; /* Note that by using sp_MSforeachdb, we're running the query in all @@ -6068,7 +6077,7 @@ IF @ProductVersionMajor >= 10 200, ''Performance'', ''Query Store Disabled'', - ''https://BrentOzar.com/go/querystore'', + ''https://www.brentozar.com/go/querystore'', (''The new SQL Server 2016 Query Store feature has not been enabled on this database.'') FROM [?].sys.database_query_store_options WHERE desired_state = 0 AND N''?'' NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''DWConfiguration'', ''DWDiagnostics'', ''DWQueue'', ''ReportServer'', ''ReportServerTempDB'') OPTION (RECOMPILE)'; @@ -6099,7 +6108,7 @@ IF @ProductVersionMajor >= 10 20, ''Reliability'', ''Query Store Cleanup Disabled'', - ''https://BrentOzar.com/go/cleanup'', + ''https://www.brentozar.com/go/cleanup'', (''SQL 2016 RTM has a bug involving dumps that happen every time Query Store cleanup jobs run. This is fixed in CU1 and later: https://sqlserverupdates.com/sql-server-2016-updates/'') FROM sys.databases AS d WHERE d.is_query_store_on = 1 OPTION (RECOMPILE);'; @@ -6163,7 +6172,7 @@ IF @ProductVersionMajor >= 10 170, ''File Configuration'', ''Multiple Log Files on One Drive'', - ''https://BrentOzar.com/go/manylogs'', + ''https://www.brentozar.com/go/manylogs'', (''The ['' + DB_NAME() + ''] database has multiple log files on the '' + LEFT(physical_name, 1) + '' drive. This is not a performance booster because log file access is sequential, not parallel.'') FROM [?].sys.database_files WHERE type_desc = ''LOG'' AND N''?'' <> ''[tempdb]'' @@ -6193,7 +6202,7 @@ IF @ProductVersionMajor >= 10 170, ''File Configuration'', ''Uneven File Growth Settings in One Filegroup'', - ''https://BrentOzar.com/go/grow'', + ''https://www.brentozar.com/go/grow'', (''The ['' + DB_NAME() + ''] database has multiple data files in one filegroup, but they are not all set up to grow in identical amounts. This can lead to uneven file activity inside the filegroup.'') FROM [?].sys.database_files WHERE type_desc = ''ROWS'' @@ -6222,7 +6231,7 @@ IF @ProductVersionMajor >= 10 170 AS Priority, ''File Configuration'' AS FindingsGroup, ''File growth set to percent'', - ''https://BrentOzar.com/go/percentgrowth'' AS URL, + ''https://www.brentozar.com/go/percentgrowth'' AS URL, ''The ['' + DB_NAME() + ''] database file '' + f.physical_name + '' has grown to '' + CONVERT(NVARCHAR(10), CONVERT(NUMERIC(38, 2), (f.size / 128.) / 1024.)) + '' GB, and is using percent filegrowth settings. This can lead to slow performance during growths if Instant File Initialization is not enabled.'' FROM [?].sys.database_files f WHERE is_percent_growth = 1 and size > 128000 OPTION (RECOMPILE);'; @@ -6250,7 +6259,7 @@ IF @ProductVersionMajor >= 10 170 AS Priority, ''File Configuration'' AS FindingsGroup, ''File growth set to 1MB'', - ''https://BrentOzar.com/go/percentgrowth'' AS URL, + ''https://www.brentozar.com/go/percentgrowth'' AS URL, ''The ['' + DB_NAME() + ''] database file '' + f.physical_name + '' is using 1MB filegrowth settings, but it has grown to '' + CAST((f.size * 8 / 1000000) AS NVARCHAR(10)) + '' GB. Time to up the growth amount.'' FROM [?].sys.database_files f WHERE is_percent_growth = 0 and growth=128 and size > 128000 OPTION (RECOMPILE);'; @@ -6281,7 +6290,7 @@ IF @ProductVersionMajor >= 10 200, ''Licensing'', ''Enterprise Edition Features In Use'', - ''https://BrentOzar.com/go/ee'', + ''https://www.brentozar.com/go/ee'', (''The ['' + DB_NAME() + ''] database is using '' + feature_name + ''. If this database is restored onto a Standard Edition server, the restore will fail on versions prior to 2016 SP1.'') FROM [?].sys.dm_db_persisted_sku_features OPTION (RECOMPILE);'; END; @@ -6310,7 +6319,7 @@ IF @ProductVersionMajor >= 10 200 AS Priority , 'Informational' AS FindingsGroup , 'Replication In Use' AS Finding , - 'https://BrentOzar.com/go/repl' AS URL , + 'https://www.brentozar.com/go/repl' AS URL , ( 'Database [' + [name] + '] is a replication publisher, subscriber, or distributor.' ) AS Details FROM sys.databases @@ -6338,7 +6347,7 @@ IF @ProductVersionMajor >= 10 200, ''Informational'', ''Replication In Use'', - ''https://BrentOzar.com/go/repl'', + ''https://www.brentozar.com/go/repl'', (''['' + DB_NAME() + ''] has MSreplication_objects tables in it, indicating it is a replication subscriber.'') FROM [?].sys.tables WHERE name = ''dbo.MSreplication_objects'' AND ''?'' <> ''master'' OPTION (RECOMPILE)'; @@ -6367,45 +6376,13 @@ IF @ProductVersionMajor >= 10 150, ''Performance'', ''Triggers on Tables'', - ''https://BrentOzar.com/go/trig'', + ''https://www.brentozar.com/go/trig'', (''The ['' + DB_NAME() + ''] database has '' + CAST(SUM(1) AS NVARCHAR(50)) + '' triggers.'') FROM [?].sys.triggers t INNER JOIN [?].sys.objects o ON t.parent_id = o.object_id INNER JOIN [?].sys.schemas s ON o.schema_id = s.schema_id WHERE t.is_ms_shipped = 0 AND DB_NAME() != ''ReportServer'' HAVING SUM(1) > 0 OPTION (RECOMPILE)'; END; - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 38 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 38) WITH NOWAIT; - - EXEC dbo.sp_MSforeachdb 'USE [?]; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - INSERT INTO #BlitzResults - (CheckID, - DatabaseName, - Priority, - FindingsGroup, - Finding, - URL, - Details) - SELECT DISTINCT 38, - N''?'', - 110, - ''Performance'', - ''Active Tables Without Clustered Indexes'', - ''https://BrentOzar.com/go/heaps'', - (''The ['' + DB_NAME() + ''] database has heaps - tables without a clustered index - that are being actively queried.'') - FROM [?].sys.indexes i INNER JOIN [?].sys.objects o ON i.object_id = o.object_id - INNER JOIN [?].sys.partitions p ON i.object_id = p.object_id AND i.index_id = p.index_id - INNER JOIN sys.databases sd ON sd.name = N''?'' - LEFT OUTER JOIN [?].sys.dm_db_index_usage_stats ius ON i.object_id = ius.object_id AND i.index_id = ius.index_id AND ius.database_id = sd.database_id - WHERE i.type_desc = ''HEAP'' AND COALESCE(NULLIF(ius.user_seeks,0), NULLIF(ius.user_scans,0), NULLIF(ius.user_lookups,0), NULLIF(ius.user_updates,0)) IS NOT NULL - AND sd.name <> ''tempdb'' AND sd.name <> ''DWDiagnostics'' AND o.is_ms_shipped = 0 AND o.type <> ''S'' OPTION (RECOMPILE)'; - END; - IF NOT EXISTS ( SELECT 1 FROM #SkipChecks WHERE DatabaseName IS NULL AND CheckID = 164 ) @@ -6429,43 +6406,11 @@ IF @ProductVersionMajor >= 10 100, ''Reliability'', ''Plan Guides Failing'', - ''https://BrentOzar.com/go/misguided'', + ''https://www.brentozar.com/go/misguided'', (''The ['' + DB_NAME() + ''] database has plan guides that are no longer valid, so the queries involved may be failing silently.'') FROM [?].sys.plan_guides g CROSS APPLY fn_validate_plan_guide(g.plan_guide_id) OPTION (RECOMPILE)'; END; - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 39 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 39) WITH NOWAIT; - - EXEC dbo.sp_MSforeachdb 'USE [?]; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - INSERT INTO #BlitzResults - (CheckID, - DatabaseName, - Priority, - FindingsGroup, - Finding, - URL, - Details) - SELECT DISTINCT 39, - N''?'', - 150, - ''Performance'', - ''Inactive Tables Without Clustered Indexes'', - ''https://BrentOzar.com/go/heaps'', - (''The ['' + DB_NAME() + ''] database has heaps - tables without a clustered index - that have not been queried since the last restart. These may be backup tables carelessly left behind.'') - FROM [?].sys.indexes i INNER JOIN [?].sys.objects o ON i.object_id = o.object_id - INNER JOIN [?].sys.partitions p ON i.object_id = p.object_id AND i.index_id = p.index_id - INNER JOIN sys.databases sd ON sd.name = N''?'' - LEFT OUTER JOIN [?].sys.dm_db_index_usage_stats ius ON i.object_id = ius.object_id AND i.index_id = ius.index_id AND ius.database_id = sd.database_id - WHERE i.type_desc = ''HEAP'' AND COALESCE(NULLIF(ius.user_seeks,0), NULLIF(ius.user_scans,0), NULLIF(ius.user_lookups,0), NULLIF(ius.user_updates,0)) IS NULL - AND sd.name <> ''tempdb'' AND sd.name <> ''DWDiagnostics'' AND o.is_ms_shipped = 0 AND o.type <> ''S'' OPTION (RECOMPILE)'; - END; - IF NOT EXISTS ( SELECT 1 FROM #SkipChecks WHERE DatabaseName IS NULL AND CheckID = 46 ) @@ -6488,7 +6433,7 @@ IF @ProductVersionMajor >= 10 150, ''Performance'', ''Leftover Fake Indexes From Wizards'', - ''https://BrentOzar.com/go/hypo'', + ''https://www.brentozar.com/go/hypo'', (''The index ['' + DB_NAME() + ''].['' + s.name + ''].['' + o.name + ''].['' + i.name + ''] is a leftover hypothetical index from the Index Tuning Wizard or Database Tuning Advisor. This index is not actually helping performance and should be removed.'') from [?].sys.indexes i INNER JOIN [?].sys.objects o ON i.object_id = o.object_id INNER JOIN [?].sys.schemas s ON o.schema_id = s.schema_id WHERE i.is_hypothetical = 1 OPTION (RECOMPILE);'; @@ -6516,7 +6461,7 @@ IF @ProductVersionMajor >= 10 100, ''Performance'', ''Indexes Disabled'', - ''https://BrentOzar.com/go/ixoff'', + ''https://www.brentozar.com/go/ixoff'', (''The index ['' + DB_NAME() + ''].['' + s.name + ''].['' + o.name + ''].['' + i.name + ''] is disabled. This index is not actually helping performance and should either be enabled or removed.'') from [?].sys.indexes i INNER JOIN [?].sys.objects o ON i.object_id = o.object_id INNER JOIN [?].sys.schemas s ON o.schema_id = s.schema_id WHERE i.is_disabled = 1 OPTION (RECOMPILE);'; @@ -6544,7 +6489,7 @@ IF @ProductVersionMajor >= 10 150, ''Performance'', ''Foreign Keys Not Trusted'', - ''https://BrentOzar.com/go/trust'', + ''https://www.brentozar.com/go/trust'', (''The ['' + DB_NAME() + ''] database has foreign keys that were probably disabled, data was changed, and then the key was enabled again. Simply enabling the key is not enough for the optimizer to use this key - we have to alter the table using the WITH CHECK CHECK CONSTRAINT parameter.'') from [?].sys.foreign_keys i INNER JOIN [?].sys.objects o ON i.parent_object_id = o.object_id INNER JOIN [?].sys.schemas s ON o.schema_id = s.schema_id WHERE i.is_not_trusted = 1 AND i.is_not_for_replication = 0 AND i.is_disabled = 0 AND N''?'' NOT IN (''master'', ''model'', ''msdb'', ''ReportServer'', ''ReportServerTempDB'') OPTION (RECOMPILE);'; @@ -6572,7 +6517,7 @@ IF @ProductVersionMajor >= 10 150, ''Performance'', ''Check Constraint Not Trusted'', - ''https://BrentOzar.com/go/trust'', + ''https://www.brentozar.com/go/trust'', (''The check constraint ['' + DB_NAME() + ''].['' + s.name + ''].['' + o.name + ''].['' + i.name + ''] is not trusted - meaning, it was disabled, data was changed, and then the constraint was enabled again. Simply enabling the constraint is not enough for the optimizer to use this constraint - we have to alter the table using the WITH CHECK CHECK CONSTRAINT parameter.'') from [?].sys.check_constraints i INNER JOIN [?].sys.objects o ON i.parent_object_id = o.object_id INNER JOIN [?].sys.schemas s ON o.schema_id = s.schema_id @@ -6604,7 +6549,7 @@ IF @ProductVersionMajor >= 10 110 AS Priority, ''Performance'' AS FindingsGroup, ''Plan Guides Enabled'' AS Finding, - ''https://BrentOzar.com/go/guides'' AS URL, + ''https://www.brentozar.com/go/guides'' AS URL, (''Database ['' + DB_NAME() + ''] has query plan guides so a query will always get a specific execution plan. If you are having trouble getting query performance to improve, it might be due to a frozen plan. Review the DMV sys.plan_guides to learn more about the plan guides in place on this server.'') AS Details FROM [?].sys.plan_guides WHERE is_disabled = 0 OPTION (RECOMPILE);'; END; @@ -6632,7 +6577,7 @@ IF @ProductVersionMajor >= 10 100 AS Priority, ''Performance'' AS FindingsGroup, ''Fill Factor Changed'', - ''https://BrentOzar.com/go/fillfactor'' AS URL, + ''https://www.brentozar.com/go/fillfactor'' AS URL, ''The ['' + DB_NAME() + ''] database has '' + CAST(SUM(1) AS NVARCHAR(50)) + '' objects with fill factor = '' + CAST(fill_factor AS NVARCHAR(5)) + ''%. This can cause memory and storage performance problems, but may also prevent page splits.'' FROM [?].sys.indexes WHERE fill_factor <> 0 AND fill_factor < 80 AND is_disabled = 0 AND is_hypothetical = 0 @@ -6668,7 +6613,7 @@ IF @ProductVersionMajor >= 10 FindingsGroup = 'Performance', Finding = 'Stored Procedure WITH RECOMPILE', DatabaseName = DBName, - URL = 'https://BrentOzar.com/go/recompile', + URL = 'https://www.brentozar.com/go/recompile', Details = '[' + DBName + '].[' + SPSchema + '].[' + ProcName + '] has WITH RECOMPILE in the stored procedure code, which may cause increased CPU usage due to constant recompiles of the code.', CheckID = '78' FROM #Recompile AS TR WHERE ProcName NOT LIKE 'sp_AllNightLog%' AND ProcName NOT LIKE 'sp_AskBrent%' AND ProcName NOT LIKE 'sp_Blitz%'; @@ -6682,7 +6627,7 @@ IF @ProductVersionMajor >= 10 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 86) WITH NOWAIT; - EXEC dbo.sp_MSforeachdb 'USE [?]; INSERT INTO #BlitzResults (CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details) SELECT DISTINCT 86, DB_NAME(), 230, ''Security'', ''Elevated Permissions on a Database'', ''https://BrentOzar.com/go/elevated'', (''In ['' + DB_NAME() + ''], user ['' + u.name + ''] has the role ['' + g.name + '']. This user can perform tasks beyond just reading and writing data.'') FROM (SELECT memberuid = convert(int, member_principal_id), groupuid = convert(int, role_principal_id) FROM [?].sys.database_role_members) m inner join [?].dbo.sysusers u on m.memberuid = u.uid inner join sysusers g on m.groupuid = g.uid where u.name <> ''dbo'' and g.name in (''db_owner'' , ''db_accessadmin'' , ''db_securityadmin'' , ''db_ddladmin'') OPTION (RECOMPILE);'; + EXEC dbo.sp_MSforeachdb 'USE [?]; INSERT INTO #BlitzResults (CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details) SELECT DISTINCT 86, DB_NAME(), 230, ''Security'', ''Elevated Permissions on a Database'', ''https://www.brentozar.com/go/elevated'', (''In ['' + DB_NAME() + ''], user ['' + u.name + ''] has the role ['' + g.name + '']. This user can perform tasks beyond just reading and writing data.'') FROM (SELECT memberuid = convert(int, member_principal_id), groupuid = convert(int, role_principal_id) FROM [?].sys.database_role_members) m inner join [?].dbo.sysusers u on m.memberuid = u.uid inner join sysusers g on m.groupuid = g.uid where u.name <> ''dbo'' and g.name in (''db_owner'' , ''db_accessadmin'' , ''db_securityadmin'' , ''db_ddladmin'') OPTION (RECOMPILE);'; END; /*Check for non-aligned indexes in partioned databases*/ @@ -6726,7 +6671,7 @@ IF @ProductVersionMajor >= 10 'Performance' AS FindingsGroup , 'The partitioned database ' + dbname + ' may have non-aligned indexes' AS Finding , - 'https://BrentOzar.com/go/aligned' AS URL , + 'https://www.brentozar.com/go/aligned' AS URL , 'Having non-aligned indexes on partitioned tables may cause inefficient query plans and CPU pressure' AS Details FROM #partdb WHERE dbname IS NOT NULL @@ -6759,7 +6704,7 @@ IF @ProductVersionMajor >= 10 50, ''Reliability'', ''Full Text Indexes Not Updating'', - ''https://BrentOzar.com/go/fulltext'', + ''https://www.brentozar.com/go/fulltext'', (''At least one full text index in this database has not been crawled in the last week.'') from [?].sys.fulltext_indexes i WHERE change_tracking_state_desc <> ''AUTO'' AND i.is_enabled = 1 AND i.crawl_end_date < DATEADD(dd, -7, GETDATE()) OPTION (RECOMPILE);'; END; @@ -6786,7 +6731,7 @@ IF @ProductVersionMajor >= 10 110, ''Performance'', ''Parallelism Rocket Surgery'', - ''https://BrentOzar.com/go/makeparallel'', + ''https://www.brentozar.com/go/makeparallel'', (''['' + DB_NAME() + ''] has a make_parallel function, indicating that an advanced developer may be manhandling SQL Server into forcing queries to go parallel.'') from [?].INFORMATION_SCHEMA.ROUTINES WHERE ROUTINE_NAME = ''make_parallel'' AND ROUTINE_TYPE = ''FUNCTION'' OPTION (RECOMPILE);'; END; @@ -6819,7 +6764,7 @@ IF @ProductVersionMajor >= 10 200, ''Performance'', ''User-Created Statistics In Place'', - ''https://BrentOzar.com/go/userstats'', + ''https://www.brentozar.com/go/userstats'', (''['' + DB_NAME() + ''] has '' + CAST(SUM(1) AS NVARCHAR(10)) + '' user-created statistics. This indicates that someone is being a rocket scientist with the stats, and might actually be slowing things down, especially during stats updates.'') from [?].sys.stats WHERE user_created = 1 AND is_temporary = 0 HAVING SUM(1) > 0 OPTION (RECOMPILE);'; @@ -6840,7 +6785,7 @@ IF @ProductVersionMajor >= 10 200, ''Performance'', ''User-Created Statistics In Place'', - ''https://BrentOzar.com/go/userstats'', + ''https://www.brentozar.com/go/userstats'', (''['' + DB_NAME() + ''] has '' + CAST(SUM(1) AS NVARCHAR(10)) + '' user-created statistics. This indicates that someone is being a rocket scientist with the stats, and might actually be slowing things down, especially during stats updates.'') from [?].sys.stats WHERE user_created = 1 HAVING SUM(1) > 0 OPTION (RECOMPILE);'; @@ -6878,7 +6823,7 @@ IF @ProductVersionMajor >= 10 ,170 ,''File Configuration'' ,''High VLF Count'' - ,''https://BrentOzar.com/go/vlf'' + ,''https://www.brentozar.com/go/vlf'' ,''The ['' + DB_NAME() + ''] database has '' + CAST(COUNT(*) as VARCHAR(20)) + '' virtual log files (VLFs). This may be slowing down startup, restores, and even inserts/updates/deletes.'' FROM #LogInfo2012 WHERE EXISTS (SELECT name FROM master.sys.databases @@ -6911,7 +6856,7 @@ IF @ProductVersionMajor >= 10 ,170 ,''File Configuration'' ,''High VLF Count'' - ,''https://BrentOzar.com/go/vlf'' + ,''https://www.brentozar.com/go/vlf'' ,''The ['' + DB_NAME() + ''] database has '' + CAST(COUNT(*) as VARCHAR(20)) + '' virtual log files (VLFs). This may be slowing down startup, restores, and even inserts/updates/deletes.'' FROM #LogInfo WHERE EXISTS (SELECT name FROM master.sys.databases @@ -6932,7 +6877,7 @@ IF @ProductVersionMajor >= 10 EXEC dbo.sp_MSforeachdb 'USE [?]; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; INSERT INTO #BlitzResults (CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details) - SELECT DISTINCT 80, DB_NAME(), 170, ''Reliability'', ''Max File Size Set'', ''https://BrentOzar.com/go/maxsize'', + SELECT DISTINCT 80, DB_NAME(), 170, ''Reliability'', ''Max File Size Set'', ''https://www.brentozar.com/go/maxsize'', (''The ['' + DB_NAME() + ''] database file '' + df.name + '' has a max file size set to '' + CAST(CAST(df.max_size AS BIGINT) * 8 / 1024 AS VARCHAR(100)) + ''MB. If it runs out of space, the database will stop working even though there may be drive space available.'') @@ -7016,7 +6961,7 @@ IF @ProductVersionMajor >= 10 UNION ALL SELECT 24, 'LAST_QUERY_PLAN_STATS', '0', NULL, 255; EXEC dbo.sp_MSforeachdb 'USE [?]; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; INSERT INTO #BlitzResults (CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details) - SELECT def1.CheckID, DB_NAME(), 210, ''Non-Default Database Scoped Config'', dsc.[name], ''https://BrentOzar.com/go/dbscope'', (''Set value: '' + COALESCE(CAST(dsc.value AS NVARCHAR(100)),''Empty'') + '' Default: '' + COALESCE(CAST(def1.default_value AS NVARCHAR(100)),''Empty'') + '' Set value for secondary: '' + COALESCE(CAST(dsc.value_for_secondary AS NVARCHAR(100)),''Empty'') + '' Default value for secondary: '' + COALESCE(CAST(def1.default_value_for_secondary AS NVARCHAR(100)),''Empty'')) + SELECT def1.CheckID, DB_NAME(), 210, ''Non-Default Database Scoped Config'', dsc.[name], ''https://www.brentozar.com/go/dbscope'', (''Set value: '' + COALESCE(CAST(dsc.value AS NVARCHAR(100)),''Empty'') + '' Default: '' + COALESCE(CAST(def1.default_value AS NVARCHAR(100)),''Empty'') + '' Set value for secondary: '' + COALESCE(CAST(dsc.value_for_secondary AS NVARCHAR(100)),''Empty'') + '' Default value for secondary: '' + COALESCE(CAST(def1.default_value_for_secondary AS NVARCHAR(100)),''Empty'')) FROM [?].sys.database_scoped_configurations dsc INNER JOIN #DatabaseScopedConfigurationDefaults def1 ON dsc.configuration_id = def1.configuration_id LEFT OUTER JOIN #DatabaseScopedConfigurationDefaults def ON dsc.configuration_id = def.configuration_id AND (cast(dsc.value as nvarchar(100)) = cast(def.default_value as nvarchar(100)) OR dsc.value IS NULL) AND (dsc.value_for_secondary = def.default_value_for_secondary OR dsc.value_for_secondary IS NULL) @@ -7046,7 +6991,7 @@ IF @ProductVersionMajor >= 10 ,150 AS Priority ,''Performance'' AS FindingsGroup ,''Objects created with dangerous SET Options'' AS Finding - ,''https://BrentOzar.com/go/badset'' AS URL + ,''https://www.brentozar.com/go/badset'' AS URL ,''The '' + QUOTENAME(DB_NAME()) + '' database has '' + CONVERT(VARCHAR(20),COUNT(1)) + '' objects that were created with dangerous ANSI_NULL or QUOTED_IDENTIFIER options.'' @@ -7084,7 +7029,7 @@ IF @ProductVersionMajor >= 10 ,200 AS Priority ,''Reliability'' AS FindingsGroup ,''Resumable Index Operation Paused'' AS Finding - ,''https://BrentOzar.com/go/resumable'' AS URL + ,''https://www.brentozar.com/go/resumable'' AS URL ,iro.state_desc + N'' since '' + CONVERT(NVARCHAR(50), last_pause_time, 120) + '', '' + CAST(iro.percent_complete AS NVARCHAR(20)) + ''% complete: '' + CAST(iro.sql_text AS NVARCHAR(1000)) AS Details @@ -7115,7 +7060,7 @@ IF @ProductVersionMajor >= 10 -- ,110 AS Priority -- ,''Performance'' AS FindingsGroup -- ,''Statistics Without Histograms'' AS Finding - -- ,''https://BrentOzar.com/go/brokenstats'' AS URL + -- ,''https://www.brentozar.com/go/brokenstats'' AS URL -- ,CAST(COUNT(DISTINCT o.object_id) AS VARCHAR(100)) + '' tables have statistics that have not been updated since the database was restored or upgraded,'' -- + '' and have no data in their histogram. See the More Info URL for a script to update them. '' AS Details -- FROM sys.all_objects o @@ -7170,7 +7115,7 @@ IF @ProductVersionMajor >= 10 1 AS PRIORITY , 'Reliability' AS FindingsGroup , 'Last good DBCC CHECKDB over 2 weeks old' AS Finding , - 'https://BrentOzar.com/go/checkdb' AS URL , + 'https://www.brentozar.com/go/checkdb' AS URL , 'Last successful CHECKDB: ' + CASE DB2.Value WHEN '1900-01-01 00:00:00.000' @@ -7221,7 +7166,7 @@ IF @ProductVersionMajor >= 10 100 AS Priority , 'Performance' AS FindingsGroup , 'Single-Use Plans in Procedure Cache' AS Finding , - 'https://BrentOzar.com/go/single' AS URL , + 'https://www.brentozar.com/go/single' AS URL , ( CAST(COUNT(*) AS VARCHAR(10)) + ' query plans are taking up memory in the procedure cache. This may be wasted memory if we cache plans for queries that never get called again. This may be a good use case for SQL Server 2008''s Optimize for Ad Hoc or for Forced Parameterization.' ) AS Details FROM sys.dm_exec_cached_plans AS cp @@ -7420,7 +7365,7 @@ IF @ProductVersionMajor >= 10 120 AS Priority , 'Query Plans' AS FindingsGroup , 'Implicit Conversion' AS Finding , - 'https://BrentOzar.com/go/implicit' AS URL , + 'https://www.brentozar.com/go/implicit' AS URL , ( 'One of the top resource-intensive queries is comparing two fields that are not the same datatype.' ) AS Details , qs.query_plan , qs.query_plan_filtered @@ -7452,7 +7397,7 @@ IF @ProductVersionMajor >= 10 120 AS Priority , 'Query Plans' AS FindingsGroup , 'Implicit Conversion Affecting Cardinality' AS Finding , - 'https://BrentOzar.com/go/implicit' AS URL , + 'https://www.brentozar.com/go/implicit' AS URL , ( 'One of the top resource-intensive queries has an implicit conversion that is affecting cardinality estimation.' ) AS Details , qs.query_plan , qs.query_plan_filtered @@ -7483,7 +7428,7 @@ IF @ProductVersionMajor >= 10 120 AS Priority , 'Query Plans' AS FindingsGroup , 'RID or Key Lookups' AS Finding , - 'https://BrentOzar.com/go/lookup' AS URL , + 'https://www.brentozar.com/go/lookup' AS URL , 'One of the top resource-intensive queries contains RID or Key Lookups. Try to avoid them by creating covering indexes.' AS Details , qs.query_plan , qs.query_plan_filtered @@ -7514,7 +7459,7 @@ IF @ProductVersionMajor >= 10 120 AS Priority , 'Query Plans' AS FindingsGroup , 'Missing Index' AS Finding , - 'https://BrentOzar.com/go/missingindex' AS URL , + 'https://www.brentozar.com/go/missingindex' AS URL , ( 'One of the top resource-intensive queries may be dramatically improved by adding an index.' ) AS Details , qs.query_plan , qs.query_plan_filtered @@ -7545,7 +7490,7 @@ IF @ProductVersionMajor >= 10 120 AS Priority , 'Query Plans' AS FindingsGroup , 'Cursor' AS Finding , - 'https://BrentOzar.com/go/cursor' AS URL , + 'https://www.brentozar.com/go/cursor' AS URL , ( 'One of the top resource-intensive queries is using a cursor.' ) AS Details , qs.query_plan , qs.query_plan_filtered @@ -7577,7 +7522,7 @@ IF @ProductVersionMajor >= 10 120 AS Priority , 'Query Plans' AS FindingsGroup , 'Scalar UDFs' AS Finding , - 'https://BrentOzar.com/go/functions' AS URL , + 'https://www.brentozar.com/go/functions' AS URL , ( 'One of the top resource-intensive queries is using a user-defined scalar function that may inhibit parallelism.' ) AS Details , qs.query_plan , qs.query_plan_filtered @@ -7612,7 +7557,7 @@ IF @ProductVersionMajor >= 10 230 AS [Priority] , 'Security' AS [FindingsGroup] , 'Endpoints Owned by Users' AS [Finding] , - 'https://BrentOzar.com/go/owners' AS [URL] , + 'https://www.brentozar.com/go/owners' AS [URL] , ( 'Endpoint ' + ep.[name] + ' is owned by ' + SUSER_NAME(ep.principal_id) + '. If the endpoint owner login is disabled or not available due to Active Directory problems, the high availability will stop working.' ) AS [Details] FROM sys.database_mirroring_endpoints ep @@ -7643,7 +7588,7 @@ IF @ProductVersionMajor >= 10 200 AS Priority , 'Informational' AS FindingsGroup , '@@Servername Not Set' AS Finding , - 'https://BrentOzar.com/go/servername' AS URL , + 'https://www.brentozar.com/go/servername' AS URL , '@@Servername variable is null. You can fix it by executing: "sp_addserver '''', local"' AS Details; END; @@ -7674,7 +7619,7 @@ IF @ProductVersionMajor >= 10 200 AS Priority , 'Configuration' AS FindingsGroup , '@@Servername Not Correct' AS Finding , - 'https://BrentOzar.com/go/servername' AS URL , + 'https://www.brentozar.com/go/servername' AS URL , 'The @@Servername is different than the computer name, which may trigger certificate errors.' AS Details; END; @@ -7713,7 +7658,7 @@ IF @ProductVersionMajor >= 10 200 AS Priority , 'Monitoring' AS FindingsGroup , 'No Failsafe Operator Configured' AS Finding , - 'https://BrentOzar.com/go/failsafe' AS URL , + 'https://www.brentozar.com/go/failsafe' AS URL , ( 'No failsafe operator is configured on this server. This is a good idea just in-case there are issues with the [msdb] database that prevents alerting.' ) AS Details FROM @AlertInfo WHERE FailSafeOperator IS NULL; @@ -7792,7 +7737,7 @@ IF @ProductVersionMajor >= 10 50 AS Priority , 'Performance' AS FindingGroup , 'Poison Wait Detected: CMEMTHREAD & NUMA' AS Finding , - 'https://BrentOzar.com/go/poison' AS URL , + 'https://www.brentozar.com/go/poison' AS URL , CONVERT(VARCHAR(10), (MAX([wait_time_ms]) / 1000) / 86400) + ':' + CONVERT(VARCHAR(20), DATEADD(s, (MAX([wait_time_ms]) / 1000), 0), 108) + ' of this wait have been recorded' + CASE WHEN ts.status = 1 THEN ' despite enabling trace flag 8048 already.' ELSE '. In servers with over 8 cores per NUMA node, when CMEMTHREAD waits are a bottleneck, trace flag 8048 may be needed.' @@ -7830,7 +7775,7 @@ IF @ProductVersionMajor >= 10 50 AS Priority , 'Reliability' AS FindingsGroup , 'Transaction Log Larger than Data File' AS Finding , - 'https://BrentOzar.com/go/biglog' AS URL , + 'https://www.brentozar.com/go/biglog' AS URL , 'The database [' + DB_NAME(a.database_id) + '] has a ' + CAST((CAST(a.size AS BIGINT) * 8 / 1000000) AS NVARCHAR(20)) + ' GB transaction log file, larger than the total data file sizes. This may indicate that transaction log backups are not being performed or not performed often enough.' AS Details FROM sys.master_files a @@ -7874,7 +7819,7 @@ IF @ProductVersionMajor >= 10 200 AS Priority , 'Informational' AS FindingsGroup , 'Collation is ' + collation_name AS Finding , - 'https://BrentOzar.com/go/collate' AS URL , + 'https://www.brentozar.com/go/collate' AS URL , 'Collation differences between user databases and tempdb can cause conflicts especially when comparing string values' AS Details FROM sys.databases WHERE name NOT IN ( 'master', 'model', 'msdb') @@ -7913,7 +7858,7 @@ IF @ProductVersionMajor >= 10 170 AS Priority , 'Reliability' AS FindingsGroup , 'Database Snapshot Online' AS Finding , - 'https://BrentOzar.com/go/snapshot' AS URL , + 'https://www.brentozar.com/go/snapshot' AS URL , 'Database [' + dSnap.[name] + '] is a snapshot of [' + dOriginal.[name] @@ -7951,7 +7896,7 @@ IF @ProductVersionMajor >= 10 END AS Priority, 'Performance' AS FindingsGroup , 'Shrink Database Job' AS Finding , - 'https://BrentOzar.com/go/autoshrink' AS URL , + 'https://www.brentozar.com/go/autoshrink' AS URL , 'In the [' + j.[name] + '] job, step [' + step.[step_name] + '] has SHRINKDATABASE or SHRINKFILE, which may be causing database fragmentation.' @@ -8021,7 +7966,7 @@ IF @ProductVersionMajor >= 10 200 AS Priority , 'Informational' AS FindingsGroup , 'Agent Jobs Starting Simultaneously' AS Finding , - 'https://BrentOzar.com/go/busyagent/' AS URL , + 'https://www.brentozar.com/go/busyagent/' AS URL , ( 'Multiple SQL Server Agent jobs are configured to start simultaneously. For detailed schedule listings, see the query in the URL.' ) AS Details FROM msdb.dbo.sysjobactivity WHERE start_execution_date > DATEADD(dd, -14, GETDATE()) @@ -8137,7 +8082,7 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 250 AS [Priority] , 'Server Info' AS [FindingsGroup] , 'Locked Pages In Memory Enabled' AS [Finding] , - 'https://BrentOzar.com/go/lpim' AS [URL] , + 'https://www.brentozar.com/go/lpim' AS [URL] , ( 'You currently have ' + CASE WHEN [dopm].[locked_page_allocations_kb] / 1024. / 1024. > 0 THEN CAST([dopm].[locked_page_allocations_kb] / 1024 / 1024 AS VARCHAR(100)) @@ -8169,7 +8114,7 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 250 AS Priority , ''Server Info'' AS FindingsGroup , ''Memory Model Unconventional'' AS Finding , - ''https://BrentOzar.com/go/lpim'' AS URL , + ''https://www.brentozar.com/go/lpim'' AS URL , ''Memory Model: '' + CAST(sql_memory_model_desc AS NVARCHAR(100)) FROM sys.dm_os_sys_info WHERE sql_memory_model <> 1 OPTION (RECOMPILE);'; @@ -8208,7 +8153,7 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 250 AS [Priority] , 'Server Info' AS [FindingsGroup] , 'Instant File Initialization Enabled' AS [Finding] , - 'https://BrentOzar.com/go/instant' AS [URL] , + 'https://www.brentozar.com/go/instant' AS [URL] , 'The service account has the Perform Volume Maintenance Tasks permission.'; END; @@ -8230,7 +8175,7 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 50 AS Priority , ''Server Info'' AS FindingsGroup , ''Instant File Initialization Not Enabled'' AS Finding , - ''https://BrentOzar.com/go/instant'' AS URL , + ''https://www.brentozar.com/go/instant'' AS URL , ''Consider enabling IFI for faster restores and data file growths.'' FROM sys.dm_server_services WHERE instant_file_initialization_enabled <> ''Y'' AND filename LIKE ''%sqlservr.exe%'' OPTION (RECOMPILE);'; @@ -8259,7 +8204,7 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 250 AS Priority , 'Server Info' AS FindingsGroup , 'Server Name' AS Finding , - 'https://BrentOzar.com/go/servername' AS URL , + 'https://www.brentozar.com/go/servername' AS URL , @@SERVERNAME AS Details WHERE @@SERVERNAME IS NOT NULL; END; @@ -8530,7 +8475,7 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 250 AS Priority, ''Server Info'' AS FindingsGroup, ''Virtual Server'' AS Finding, - ''https://BrentOzar.com/go/virtual'' AS URL, + ''https://www.brentozar.com/go/virtual'' AS URL, ''Type: ('' + virtual_machine_type_desc + '')'' AS Details FROM sys.dm_os_sys_info WHERE virtual_machine_type <> 0 OPTION (RECOMPILE);'; @@ -8558,7 +8503,7 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 250 AS Priority, ''Server Info'' AS FindingsGroup, ''Container'' AS Finding, - ''https://BrentOzar.com/go/virtual'' AS URL, + ''https://www.brentozar.com/go/virtual'' AS URL, ''Type: ('' + container_type_desc + '')'' AS Details FROM sys.dm_os_sys_info WHERE container_type_desc <> ''NONE'' OPTION (RECOMPILE);'; @@ -8731,7 +8676,7 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 ,250 AS Priority ,'Server Info' AS FindingsGroup ,'Default Trace Contents' AS Finding - ,'https://BrentOzar.com/go/trace' AS URL + ,'https://www.brentozar.com/go/trace' AS URL ,'The default trace holds '+cast(DATEDIFF(hour,MIN(StartTime),GETDATE())as VARCHAR(30))+' hours of data' +' between '+cast(Min(StartTime) as VARCHAR(30))+' and '+cast(GETDATE()as VARCHAR(30)) +('. The default trace files are located in: '+left( @curr_tracefilename,len(@curr_tracefilename) - @indx) @@ -8811,7 +8756,7 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 URL , Details ) - VALUES (153, 240, 'Wait Stats', 'No Significant Waits Detected', 'https://BrentOzar.com/go/waits', 'This server might be just sitting around idle, or someone may have cleared wait stats recently.'); + VALUES (153, 240, 'Wait Stats', 'No Significant Waits Detected', 'https://www.brentozar.com/go/waits', 'This server might be just sitting around idle, or someone may have cleared wait stats recently.'); END; END; /* CheckID 152 */ @@ -9474,7 +9419,7 @@ AS SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.01', @VersionDate = '20210222'; + SELECT @Version = '8.02', @VersionDate = '20210321'; IF(@VersionCheckMode = 1) BEGIN @@ -11219,6 +11164,7 @@ ALTER PROCEDURE dbo.sp_BlitzCache @UseTriggersAnyway BIT = NULL, @ExportToExcel BIT = 0, @ExpertMode TINYINT = 0, + @OutputType VARCHAR(20) = 'TABLE' , @OutputServerName NVARCHAR(258) = NULL , @OutputDatabaseName NVARCHAR(258) = NULL , @OutputSchemaName NVARCHAR(258) = NULL , @@ -11253,8 +11199,8 @@ BEGIN SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.01', @VersionDate = '20210222'; - +SELECT @Version = '8.02', @VersionDate = '20210321'; +SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) BEGIN @@ -11343,7 +11289,11 @@ IF @Help = 1 SELECT N'@ExpertMode', N'TINYINT', N'Default 0. When set to 1, results include more columns. When 2, mode is optimized for Opserver, the open source dashboard.' - + UNION ALL + SELECT N'@OutputType', + N'NVARCHAR(258)', + N'If set to "NONE", this will tell this procedure not to run any query leading to a results set thrown to caller.' + UNION ALL SELECT N'@OutputDatabaseName', N'NVARCHAR(128)', @@ -11372,7 +11322,7 @@ IF @Help = 1 UNION ALL SELECT N'@IgnoreSystemDBs', N'BIT', - N'Ignores plans found in the system databases (master, model, msdb, tempdb, and resourcedb)' + N'Ignores plans found in the system databases (master, model, msdb, tempdb, dbmaintenance, dbadmin, dbatools and resourcedb)' UNION ALL SELECT N'@OnlyQueryHashes', @@ -11730,6 +11680,18 @@ BEGIN RETURN; END; +IF(@OutputType = 'NONE' AND (@OutputTableName IS NULL OR @OutputSchemaName IS NULL OR @OutputDatabaseName IS NULL)) +BEGIN + RAISERROR('This procedure should be called with a value for all @Output* parameters, as @OutputType is set to NONE',12,1); + RETURN; +END; + +IF(@OutputType = 'NONE') +BEGIN + SET @HideSummary = 1; +END; + + /* Lets get @SortOrder set to lower case here for comparisons later */ SET @SortOrder = LOWER(@SortOrder); @@ -12922,7 +12884,7 @@ SET @body += N' WHERE 1 = 1 ' + @nl ; IF @IgnoreSystemDBs = 1 BEGIN RAISERROR(N'Ignoring system databases by default', 0, 1) WITH NOWAIT; - SET @body += N' AND COALESCE(DB_NAME(CAST(xpa.value AS INT)), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'') AND COALESCE(DB_NAME(CAST(xpa.value AS INT)), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ; + SET @body += N' AND COALESCE(LOWER(DB_NAME(CAST(xpa.value AS INT))), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'', ''dbmaintenance'', ''dbadmin'', ''dbatools'') AND COALESCE(DB_NAME(CAST(xpa.value AS INT)), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ; END; IF @DatabaseName IS NOT NULL OR @DatabaseName <> N'' @@ -13357,7 +13319,7 @@ BEGIN SET @sql += @body_where ; IF @IgnoreSystemDBs = 1 - SET @sql += N' AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'') AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ; + SET @sql += N' AND COALESCE(LOWER(DB_NAME(database_id)), LOWER(CAST(pa.value AS sysname)), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'', ''dbmaintenance'', ''dbadmin'', ''dbatools'') AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ; SET @sql += @sort_filter + @nl; @@ -13389,7 +13351,7 @@ BEGIN SET @sql += @body_where ; IF @IgnoreSystemDBs = 1 - SET @sql += N' AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'') AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ; + SET @sql += N' AND COALESCE(LOWER(DB_NAME(database_id)), LOWER(CAST(pa.value AS sysname)), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'', ''dbmaintenance'', ''dbadmin'', ''dbatools'') AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ; SET @sql += @sort_filter + @nl; @@ -13425,7 +13387,7 @@ BEGIN SET @sql += @body_where ; IF @IgnoreSystemDBs = 1 - SET @sql += N' AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'') AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ; + SET @sql += N' AND COALESCE(LOWER(DB_NAME(database_id)), LOWER(CAST(pa.value AS sysname)), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'', ''dbmaintenance'', ''dbadmin'', ''dbatools'') AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ; SET @sql += @sort_filter + @nl; @@ -15076,7 +15038,7 @@ SELECT spi.SPID, ELSE @nl + N' We could not find any cached parameter values for this stored proc. ' END + CASE WHEN spi2.variable_name = N'**no_variable**' OR spi2.compile_time_value = N'**idk_man**' - THEN @nl + N'More info on possible reasons: https://BrentOzar.com/go/noplans ' + THEN @nl + N'More info on possible reasons: https://www.brentozar.com/go/noplans ' WHEN spi2.compile_time_value = N'NULL' THEN spi2.compile_time_value ELSE RTRIM(spi2.compile_time_value) @@ -15119,7 +15081,7 @@ SELECT spi.SPID, ELSE @nl + N' We could not find any cached parameter values for this stored proc. ' END + CASE WHEN spi2.variable_name = N'**no_variable**' OR spi2.compile_time_value = N'**idk_man**' - THEN @nl + N' More info on possible reasons: https://BrentOzar.com/go/noplans ' + THEN @nl + N' More info on possible reasons: https://www.brentozar.com/go/noplans ' WHEN spi2.compile_time_value = N'NULL' THEN spi2.compile_time_value ELSE RTRIM(spi2.compile_time_value) @@ -15723,7 +15685,7 @@ FROM ##BlitzCacheProcs b ) UPDATE b SET Warnings = ISNULL('Your query plan is >128 levels of nested nodes, and can''t be converted to XML. Use SELECT * FROM sys.dm_exec_text_query_plan('+ CONVERT(VARCHAR(128), ph.PlanHandle, 1) + ', 0, -1) to get more information' - , 'We couldn''t find a plan for this query. More info on possible reasons: https://BrentOzar.com/go/noplans') + , 'We couldn''t find a plan for this query. More info on possible reasons: https://www.brentozar.com/go/noplans') FROM ##BlitzCacheProcs b LEFT JOIN plan_handle ph ON b.PlanHandle = ph.PlanHandle @@ -16106,9 +16068,10 @@ IF @Debug = 1 PRINT SUBSTRING(@sql, 32000, 36000); PRINT SUBSTRING(@sql, 36000, 40000); END; - -EXEC sp_executesql @sql, N'@Top INT, @spid INT, @minimumExecutionCount INT, @min_back INT', @Top, @@SPID, @MinimumExecutionCount, @MinutesBack; - +IF(@OutputType <> 'NONE') +BEGIN + EXEC sp_executesql @sql, N'@Top INT, @spid INT, @minimumExecutionCount INT, @min_back INT', @Top, @@SPID, @MinimumExecutionCount, @MinutesBack; +END; /* @@ -16205,7 +16168,7 @@ BEGIN 100, 'Execution Pattern', 'Frequent Execution', - 'http://brentozar.com/blitzcache/frequently-executed-queries/', + 'https://www.brentozar.com/blitzcache/frequently-executed-queries/', 'Queries are being executed more than ' + CAST (@execution_threshold AS VARCHAR(5)) + ' times per minute. This can put additional load on the server, even when queries are lightweight.') ; @@ -16220,7 +16183,7 @@ BEGIN 50, 'Parameterization', 'Parameter Sniffing', - 'http://brentozar.com/blitzcache/parameter-sniffing/', + 'https://www.brentozar.com/blitzcache/parameter-sniffing/', 'There are signs of parameter sniffing (wide variance in rows return or time to execute). Investigate query patterns and tune code appropriately.') ; /* Forced execution plans */ @@ -16234,7 +16197,7 @@ BEGIN 50, 'Parameterization', 'Forced Plan', - 'http://brentozar.com/blitzcache/forced-plans/', + 'https://www.brentozar.com/blitzcache/forced-plans/', 'Execution plans have been compiled with forced plans, either through FORCEPLAN, plan guides, or forced parameterization. This will make general tuning efforts less effective.'); IF EXISTS (SELECT 1/0 @@ -16247,7 +16210,7 @@ BEGIN 200, 'Cursors', 'Cursor', - 'http://brentozar.com/blitzcache/cursors-found-slow-queries/', + 'https://www.brentozar.com/blitzcache/cursors-found-slow-queries/', 'There are cursors in the plan cache. This is neither good nor bad, but it is a thing. Cursors are weird in SQL Server.'); IF EXISTS (SELECT 1/0 @@ -16261,7 +16224,7 @@ BEGIN 200, 'Cursors', 'Optimistic Cursors', - 'http://brentozar.com/blitzcache/cursors-found-slow-queries/', + 'https://www.brentozar.com/blitzcache/cursors-found-slow-queries/', 'There are optimistic cursors in the plan cache, which can harm performance.'); IF EXISTS (SELECT 1/0 @@ -16275,7 +16238,7 @@ BEGIN 200, 'Cursors', 'Non-forward Only Cursors', - 'http://brentozar.com/blitzcache/cursors-found-slow-queries/', + 'https://www.brentozar.com/blitzcache/cursors-found-slow-queries/', 'There are non-forward only cursors in the plan cache, which can harm performance.'); IF EXISTS (SELECT 1/0 @@ -16289,7 +16252,7 @@ BEGIN 200, 'Cursors', 'Dynamic Cursors', - 'http://brentozar.com/blitzcache/cursors-found-slow-queries/', + 'https://www.brentozar.com/blitzcache/cursors-found-slow-queries/', 'Dynamic Cursors inhibit parallelism!.'); IF EXISTS (SELECT 1/0 @@ -16303,7 +16266,7 @@ BEGIN 200, 'Cursors', 'Fast Forward Cursors', - 'http://brentozar.com/blitzcache/cursors-found-slow-queries/', + 'https://www.brentozar.com/blitzcache/cursors-found-slow-queries/', 'Fast forward cursors inhibit parallelism!.'); IF EXISTS (SELECT 1/0 @@ -16316,7 +16279,7 @@ BEGIN 50, 'Parameterization', 'Forced Parameterization', - 'http://brentozar.com/blitzcache/forced-parameterization/', + 'https://www.brentozar.com/blitzcache/forced-parameterization/', 'Execution plans have been compiled with forced parameterization.') ; IF EXISTS (SELECT 1/0 @@ -16329,7 +16292,7 @@ BEGIN 200, 'Execution Plans', 'Parallel', - 'http://brentozar.com/blitzcache/parallel-plans-detected/', + 'https://www.brentozar.com/blitzcache/parallel-plans-detected/', 'Parallel plans detected. These warrant investigation, but are neither good nor bad.') ; IF EXISTS (SELECT 1/0 @@ -16342,7 +16305,7 @@ BEGIN 200, 'Execution Plans', 'Nearly Parallel', - 'http://brentozar.com/blitzcache/query-cost-near-cost-threshold-parallelism/', + 'https://www.brentozar.com/blitzcache/query-cost-near-cost-threshold-parallelism/', 'Queries near the cost threshold for parallelism. These may go parallel when you least expect it.') ; IF EXISTS (SELECT 1/0 @@ -16355,7 +16318,7 @@ BEGIN 50, 'Execution Plans', 'Plan Warnings', - 'http://brentozar.com/blitzcache/query-plan-warnings/', + 'https://www.brentozar.com/blitzcache/query-plan-warnings/', 'Warnings detected in execution plans. SQL Server is telling you that something bad is going on that requires your attention.') ; IF EXISTS (SELECT 1/0 @@ -16368,7 +16331,7 @@ BEGIN 50, 'Performance', 'Long Running Query', - 'http://brentozar.com/blitzcache/long-running-queries/', + 'https://www.brentozar.com/blitzcache/long-running-queries/', 'Long running queries have been found. These are queries with an average duration longer than ' + CAST(@long_running_query_warning_seconds / 1000 / 1000 AS VARCHAR(5)) + ' second(s). These queries should be investigated for additional tuning options.') ; @@ -16383,7 +16346,7 @@ BEGIN 50, 'Performance', 'Missing Indexes', - 'http://brentozar.com/blitzcache/missing-index-request/', + 'https://www.brentozar.com/blitzcache/missing-index-request/', 'Queries found with missing indexes.'); IF EXISTS (SELECT 1/0 @@ -16396,7 +16359,7 @@ BEGIN 200, 'Cardinality', 'Downlevel CE', - 'http://brentozar.com/blitzcache/legacy-cardinality-estimator/', + 'https://www.brentozar.com/blitzcache/legacy-cardinality-estimator/', 'A legacy cardinality estimator is being used by one or more queries. Investigate whether you need to be using this cardinality estimator. This may be caused by compatibility levels, global trace flags, or query level trace flags.'); IF EXISTS (SELECT 1/0 @@ -16409,7 +16372,7 @@ BEGIN 50, 'Performance', 'Implicit Conversions', - 'http://brentozar.com/go/implicit', + 'https://www.brentozar.com/go/implicit', 'One or more queries are comparing two fields that are not of the same data type.') ; IF EXISTS (SELECT 1/0 @@ -16422,7 +16385,7 @@ BEGIN 100, 'Performance', 'Busy Loops', - 'http://brentozar.com/blitzcache/busy-loops/', + 'https://www.brentozar.com/blitzcache/busy-loops/', 'Operations have been found that are executed 100 times more often than the number of rows returned by each iteration. This is an indicator that something is off in query execution.'); IF EXISTS (SELECT 1/0 @@ -16435,7 +16398,7 @@ BEGIN 50, 'Performance', 'Function Join', - 'http://brentozar.com/blitzcache/tvf-join/', + 'https://www.brentozar.com/blitzcache/tvf-join/', 'Execution plans have been found that join to table valued functions (TVFs). TVFs produce inaccurate estimates of the number of rows returned and can lead to any number of query plan problems.'); IF EXISTS (SELECT 1/0 @@ -16448,7 +16411,7 @@ BEGIN 50, 'Execution Plans', 'Compilation Timeout', - 'http://brentozar.com/blitzcache/compilation-timeout/', + 'https://www.brentozar.com/blitzcache/compilation-timeout/', 'Query compilation timed out for one or more queries. SQL Server did not find a plan that meets acceptable performance criteria in the time allotted so the best guess was returned. There is a very good chance that this plan isn''t even below average - it''s probably terrible.'); IF EXISTS (SELECT 1/0 @@ -16461,7 +16424,7 @@ BEGIN 50, 'Execution Plans', 'Compile Memory Limit Exceeded', - 'http://brentozar.com/blitzcache/compile-memory-limit-exceeded/', + 'https://www.brentozar.com/blitzcache/compile-memory-limit-exceeded/', 'The optimizer has a limited amount of memory available. One or more queries are complex enough that SQL Server was unable to allocate enough memory to fully optimize the query. A best fit plan was found, and it''s probably terrible.'); IF EXISTS (SELECT 1/0 @@ -16474,7 +16437,7 @@ BEGIN 50, 'Execution Plans', 'No Join Predicate', - 'http://brentozar.com/blitzcache/no-join-predicate/', + 'https://www.brentozar.com/blitzcache/no-join-predicate/', 'Operators in a query have no join predicate. This means that all rows from one table will be matched with all rows from anther table producing a Cartesian product. That''s a whole lot of rows. This may be your goal, but it''s important to investigate why this is happening.'); IF EXISTS (SELECT 1/0 @@ -16487,7 +16450,7 @@ BEGIN 200, 'Execution Plans', 'Multiple Plans', - 'http://brentozar.com/blitzcache/multiple-plans/', + 'https://www.brentozar.com/blitzcache/multiple-plans/', 'Queries exist with multiple execution plans (as determined by query_plan_hash). Investigate possible ways to parameterize these queries or otherwise reduce the plan count.'); IF EXISTS (SELECT 1/0 @@ -16500,7 +16463,7 @@ BEGIN 100, 'Performance', 'Unmatched Indexes', - 'http://brentozar.com/blitzcache/unmatched-indexes', + 'https://www.brentozar.com/blitzcache/unmatched-indexes', 'An index could have been used, but SQL Server chose not to use it - likely due to parameterization and filtered indexes.'); IF EXISTS (SELECT 1/0 @@ -16513,7 +16476,7 @@ BEGIN 100, 'Parameterization', 'Unparameterized Query', - 'http://brentozar.com/blitzcache/unparameterized-queries', + 'https://www.brentozar.com/blitzcache/unparameterized-queries', 'Unparameterized queries found. These could be ad hoc queries, data exploration, or queries using "OPTIMIZE FOR UNKNOWN".'); IF EXISTS (SELECT 1/0 @@ -16526,7 +16489,7 @@ BEGIN 100, 'Execution Plans', 'Trivial Plans', - 'http://brentozar.com/blitzcache/trivial-plans', + 'https://www.brentozar.com/blitzcache/trivial-plans', 'Trivial plans get almost no optimization. If you''re finding these in the top worst queries, something may be going wrong.'); IF EXISTS (SELECT 1/0 @@ -16539,7 +16502,7 @@ BEGIN 10, 'Execution Plans', 'Forced Serialization', - 'http://www.brentozar.com/blitzcache/forced-serialization/', + 'https://www.brentozar.com/blitzcache/forced-serialization/', 'Something in your plan is forcing a serial query. Further investigation is needed if this is not by design.') ; IF EXISTS (SELECT 1/0 @@ -16552,7 +16515,7 @@ BEGIN 100, 'Execution Plans', 'Expensive Key Lookup', - 'http://www.brentozar.com/blitzcache/expensive-key-lookups/', + 'https://www.brentozar.com/blitzcache/expensive-key-lookups/', 'There''s a key lookup in your plan that costs >=50% of the total plan cost.') ; IF EXISTS (SELECT 1/0 @@ -16565,7 +16528,7 @@ BEGIN 100, 'Execution Plans', 'Expensive Remote Query', - 'http://www.brentozar.com/blitzcache/expensive-remote-query/', + 'https://www.brentozar.com/blitzcache/expensive-remote-query/', 'There''s a remote query in your plan that costs >=50% of the total plan cost.') ; IF EXISTS (SELECT 1/0 @@ -16657,7 +16620,7 @@ BEGIN 100, 'Warnings', 'Operator Warnings', - 'http://brentozar.com/blitzcache/query-plan-warnings/', + 'https://www.brentozar.com/blitzcache/query-plan-warnings/', 'Check the plan for more details.') ; IF EXISTS (SELECT 1/0 @@ -16761,7 +16724,7 @@ BEGIN 100, 'Execution Plans', 'Expensive Sort', - 'http://www.brentozar.com/blitzcache/expensive-sorts/', + 'https://www.brentozar.com/blitzcache/expensive-sorts/', 'There''s a sort in your plan that costs >=50% of the total plan cost.') ; IF EXISTS (SELECT 1/0 @@ -17002,7 +16965,7 @@ BEGIN 100, 'Functions', 'MSTVFs', - 'http://brentozar.com/blitzcache/tvf-join/', + 'https://www.brentozar.com/blitzcache/tvf-join/', 'Execution plans have been found that join to table valued functions (TVFs). TVFs produce inaccurate estimates of the number of rows returned and can lead to any number of query plan problems.'); IF EXISTS (SELECT 1/0 @@ -17179,7 +17142,7 @@ BEGIN N'Large USERSTORE_TOKENPERM cache: ' + CONVERT(NVARCHAR(11), @user_perm_gb_out) + N'GB', N'The USERSTORE_TOKENPERM is taking up ' + CONVERT(NVARCHAR(11), @user_perm_percent) + N'% of the buffer pool, and your plan cache seems to be unstable', - N'https://brentozar.com/go/userstore', + N'https://www.brentozar.com/go/userstore', N'A growing USERSTORE_TOKENPERM cache can cause the plan cache to clear out' IF @v >= 11 @@ -17820,11 +17783,12 @@ END; PRINT SUBSTRING(@AllSortSql, 32000, 36000); PRINT SUBSTRING(@AllSortSql, 36000, 40000); END; - +IF(@OutputType <> 'NONE') +BEGIN EXEC sys.sp_executesql @stmt = @AllSortSql, @params = N'@i_DatabaseName NVARCHAR(128), @i_Top INT, @i_SkipAnalysis BIT, @i_OutputDatabaseName NVARCHAR(258), @i_OutputSchemaName NVARCHAR(258), @i_OutputTableName NVARCHAR(258), @i_CheckDateOverride DATETIMEOFFSET, @i_MinutesBack INT ', @i_DatabaseName = @DatabaseName, @i_Top = @Top, @i_SkipAnalysis = @SkipAnalysis, @i_OutputDatabaseName = @OutputDatabaseName, @i_OutputSchemaName = @OutputSchemaName, @i_OutputTableName = @OutputTableName, @i_CheckDateOverride = @CheckDateOverride, @i_MinutesBack = @MinutesBack; - +END; /*End of AllSort section*/ @@ -18439,7 +18403,7 @@ AS SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.01', @VersionDate = '20210222'; +SELECT @Version = '8.02', @VersionDate = '20210321'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -19153,6 +19117,7 @@ IF @GetAllDatabases = 1 AND database_id > 4 AND DB_NAME(database_id) NOT LIKE 'ReportServer%' AND DB_NAME(database_id) NOT LIKE 'rdsadmin%' + AND LOWER(name) NOT IN('dbatools', 'dbadmin', 'dbmaintenance') AND is_distributor = 0 OPTION ( RECOMPILE ); @@ -23913,7 +23878,7 @@ BEGIN SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.01', @VersionDate = '20210222'; +SELECT @Version = '8.02', @VersionDate = '20210321'; IF(@VersionCheckMode = 1) @@ -24616,6 +24581,7 @@ You need to use an Azure storage account, and the path has to look like this: ht ) AS step_id FROM #deadlock_process AS dp WHERE dp.client_app LIKE 'SQLAgent - %' + AND dp.client_app <> 'SQLAgent - Initial Boot Probe' ) AS x OPTION ( RECOMPILE ); @@ -25169,18 +25135,18 @@ You need to use an Azure storage account, and the path has to look like this: ht dp.priority, dp.log_used, dp.wait_resource COLLATE DATABASE_DEFAULT AS wait_resource, - CONVERT( - XML, - STUFF(( SELECT DISTINCT NCHAR(10) + CONVERT( + XML, + STUFF(( SELECT DISTINCT NCHAR(10) + N' ' + ISNULL(c.object_name, N'') + N' ' COLLATE DATABASE_DEFAULT AS object_name - FROM #deadlock_owner_waiter AS c + FROM #deadlock_owner_waiter AS c WHERE ( dp.id = c.owner_id - OR dp.victim_id = c.waiter_id ) - AND CONVERT(DATE, dp.event_date) = CONVERT(DATE, c.event_date) - FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(4000)'), - 1, 1, N'')) AS object_names, + OR dp.victim_id = c.waiter_id ) + AND dp.event_date = c.event_date + FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(4000)'), + 1, 1, N'')) AS object_names, dp.wait_time, dp.transaction_name, dp.last_tran_started, @@ -25193,7 +25159,6 @@ You need to use an Azure storage account, and the path has to look like this: ht dp.login_name, dp.isolation_level, dp.process_xml.value('(//process/inputbuf/text())[1]', 'NVARCHAR(MAX)') AS inputbuf, - ROW_NUMBER() OVER ( PARTITION BY dp.event_date, dp.id ORDER BY dp.event_date ) AS dn, DENSE_RANK() OVER ( ORDER BY dp.event_date ) AS en, ROW_NUMBER() OVER ( PARTITION BY dp.event_date ORDER BY dp.event_date ) -1 AS qn, dp.is_victim, @@ -25226,20 +25191,18 @@ You need to use an Azure storage account, and the path has to look like this: ht dp.priority, dp.log_used, dp.wait_resource COLLATE DATABASE_DEFAULT, - CASE WHEN @ExportToExcel = 0 THEN - CONVERT( - XML, - STUFF(( SELECT DISTINCT NCHAR(10) - + N' ' - + ISNULL(c.object_name, N'') - + N' ' COLLATE DATABASE_DEFAULT AS object_name - FROM #deadlock_owner_waiter AS c - WHERE ( dp.id = c.owner_id - OR dp.victim_id = c.waiter_id ) - AND CONVERT(DATE, dp.event_date) = CONVERT(DATE, c.event_date) - FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(4000)'), - 1, 1, N'')) - ELSE NULL END AS object_names, + CONVERT( + XML, + STUFF(( SELECT DISTINCT NCHAR(10) + + N' ' + + ISNULL(c.object_name, N'') + + N' ' COLLATE DATABASE_DEFAULT AS object_name + FROM #deadlock_owner_waiter AS c + WHERE ( dp.id = c.owner_id + OR dp.victim_id = c.waiter_id ) + AND dp.event_date = c.event_date + FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(4000)'), + 1, 1, N'')) AS object_names, dp.wait_time, dp.transaction_name, dp.last_tran_started, @@ -25252,7 +25215,6 @@ You need to use an Azure storage account, and the path has to look like this: ht dp.login_name, dp.isolation_level, dp.process_xml.value('(//process/inputbuf/text())[1]', 'NVARCHAR(MAX)') AS inputbuf, - ROW_NUMBER() OVER ( PARTITION BY dp.event_date, dp.id ORDER BY dp.event_date ) AS dn, DENSE_RANK() OVER ( ORDER BY dp.event_date ) AS en, ROW_NUMBER() OVER ( PARTITION BY dp.event_date ORDER BY dp.event_date ) -1 AS qn, 1 AS is_victim, @@ -25354,7 +25316,7 @@ You need to use an Azure storage account, and the path has to look like this: ht FROM deadlocks AS d WHERE d.dn = 1 AND (is_victim = @VictimsOnly OR @VictimsOnly = 0) - AND d.en < CASE WHEN d.deadlock_type = N'Parallel Deadlock' THEN 2 ELSE 2147483647 END + AND d.qn < CASE WHEN d.deadlock_type = N'Parallel Deadlock' THEN 2 ELSE 2147483647 END AND (DB_NAME(d.database_id) = @DatabaseName OR @DatabaseName IS NULL) AND (d.event_date >= @StartDate OR @StartDate IS NULL) AND (d.event_date < @EndDate OR @EndDate IS NULL) @@ -25394,20 +25356,18 @@ ELSE --Output to database is not set output to client app dp.priority, dp.log_used, dp.wait_resource COLLATE DATABASE_DEFAULT AS wait_resource, - CASE WHEN @ExportToExcel = 0 THEN - CONVERT( - XML, - STUFF(( SELECT DISTINCT NCHAR(10) - + N' ' - + ISNULL(c.object_name, N'') - + N' ' COLLATE DATABASE_DEFAULT AS object_name - FROM #deadlock_owner_waiter AS c - WHERE ( dp.id = c.owner_id - OR dp.victim_id = c.waiter_id ) - AND CONVERT(DATE, dp.event_date) = CONVERT(DATE, c.event_date) - FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(4000)'), - 1, 1, N'')) - ELSE NULL END AS object_names, + CONVERT( + XML, + STUFF(( SELECT DISTINCT NCHAR(10) + + N' ' + + ISNULL(c.object_name, N'') + + N' ' COLLATE DATABASE_DEFAULT AS object_name + FROM #deadlock_owner_waiter AS c + WHERE ( dp.id = c.owner_id + OR dp.victim_id = c.waiter_id ) + AND dp.event_date = c.event_date + FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(4000)'), + 1, 1, N'')) AS object_names, dp.wait_time, dp.transaction_name, dp.last_tran_started, @@ -25420,7 +25380,6 @@ ELSE --Output to database is not set output to client app dp.login_name, dp.isolation_level, dp.process_xml.value('(//process/inputbuf/text())[1]', 'NVARCHAR(MAX)') AS inputbuf, - ROW_NUMBER() OVER ( PARTITION BY dp.event_date, dp.id ORDER BY dp.event_date ) AS dn, DENSE_RANK() OVER ( ORDER BY dp.event_date ) AS en, ROW_NUMBER() OVER ( PARTITION BY dp.event_date ORDER BY dp.event_date ) -1 AS qn, dp.is_victim, @@ -25453,20 +25412,18 @@ ELSE --Output to database is not set output to client app dp.priority, dp.log_used, dp.wait_resource COLLATE DATABASE_DEFAULT, - CASE WHEN @ExportToExcel = 0 THEN - CONVERT( - XML, - STUFF(( SELECT DISTINCT NCHAR(10) - + N' ' - + ISNULL(c.object_name, N'') - + N' ' COLLATE DATABASE_DEFAULT AS object_name - FROM #deadlock_owner_waiter AS c - WHERE ( dp.id = c.owner_id - OR dp.victim_id = c.waiter_id ) - AND CONVERT(DATE, dp.event_date) = CONVERT(DATE, c.event_date) - FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(4000)'), - 1, 1, N'')) - ELSE NULL END AS object_names, + CONVERT( + XML, + STUFF(( SELECT DISTINCT NCHAR(10) + + N' ' + + ISNULL(c.object_name, N'') + + N' ' COLLATE DATABASE_DEFAULT AS object_name + FROM #deadlock_owner_waiter AS c + WHERE ( dp.id = c.owner_id + OR dp.victim_id = c.waiter_id ) + AND dp.event_date = c.event_date + FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(4000)'), + 1, 1, N'')) AS object_names, dp.wait_time, dp.transaction_name, dp.last_tran_started, @@ -25479,7 +25436,6 @@ ELSE --Output to database is not set output to client app dp.login_name, dp.isolation_level, dp.process_xml.value('(//process/inputbuf/text())[1]', 'NVARCHAR(MAX)') AS inputbuf, - ROW_NUMBER() OVER ( PARTITION BY dp.event_date, dp.id ORDER BY dp.event_date ) AS dn, DENSE_RANK() OVER ( ORDER BY dp.event_date ) AS en, ROW_NUMBER() OVER ( PARTITION BY dp.event_date ORDER BY dp.event_date ) -1 AS qn, 1 AS is_victim, @@ -25512,8 +25468,8 @@ ELSE --Output to database is not set output to client app + CASE WHEN d.qn = 0 THEN N'1' ELSE CONVERT(NVARCHAR(10), d.qn) END + CASE WHEN d.is_victim = 1 THEN ' - VICTIM' ELSE '' END AS deadlock_group, - CASE WHEN @ExportToExcel = 0 THEN CONVERT(XML, N'') - ELSE SUBSTRING(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(LTRIM(RTRIM(d.inputbuf)),' ','<>'),'><',''),NCHAR(10), ' '),NCHAR(13), ' '),'<>',' '), 1, 32000) END AS query, + CONVERT(XML, N'') AS query_xml, + d.inputbuf AS query_string, d.object_names, d.isolation_level, d.owner_mode, @@ -25542,11 +25498,13 @@ ELSE --Output to database is not set output to client app d.waiter_merging, d.waiter_spilling, d.waiter_waiting_to_close, - CASE WHEN @ExportToExcel = 0 THEN d.deadlock_graph ELSE NULL END AS deadlock_graph - FROM deadlocks AS d + d.deadlock_graph, + d.is_victim + INTO #deadlock_results + FROM deadlocks AS d WHERE d.dn = 1 - AND (is_victim = @VictimsOnly OR @VictimsOnly = 0) - AND d.en < CASE WHEN d.deadlock_type = N'Parallel Deadlock' THEN 2 ELSE 2147483647 END + AND (d.is_victim = @VictimsOnly OR @VictimsOnly = 0) + AND d.qn < CASE WHEN d.deadlock_type = N'Parallel Deadlock' THEN 2 ELSE 2147483647 END AND (DB_NAME(d.database_id) = @DatabaseName OR @DatabaseName IS NULL) AND (d.event_date >= @StartDate OR @StartDate IS NULL) AND (d.event_date < @EndDate OR @EndDate IS NULL) @@ -25554,9 +25512,67 @@ ELSE --Output to database is not set output to client app AND (d.client_app = @AppName OR @AppName IS NULL) AND (d.host_name = @HostName OR @HostName IS NULL) AND (d.login_name = @LoginName OR @LoginName IS NULL) - ORDER BY d.event_date, is_victim DESC OPTION ( RECOMPILE ); + + DECLARE @deadlock_result NVARCHAR(MAX) = N'' + + SET @deadlock_result += N' + SELECT + dr.deadlock_type, + dr.event_date, + dr.database_name, + dr.deadlock_group, + ' + + CASE @ExportToExcel + WHEN 1 + THEN N'dr.query_string AS query, + REPLACE(REPLACE(CONVERT(NVARCHAR(MAX), dr.object_names), '''', ''''), '''', '''') AS object_names,' + ELSE N'dr.query_xml AS query, + dr.object_names,' + END + + N' + dr.isolation_level, + dr.owner_mode, + dr.waiter_mode, + dr.transaction_count, + dr.login_name, + dr.host_name, + dr.client_app, + dr.wait_time, + dr.priority, + dr.log_used, + dr.last_tran_started, + dr.last_batch_started, + dr.last_batch_completed, + dr.transaction_name, + dr.owner_waiter_type, + dr.owner_activity, + dr.owner_waiter_activity, + dr.owner_merging, + dr.owner_spilling, + dr.owner_waiting_to_close, + dr.waiter_waiter_type, + dr.waiter_owner_activity, + dr.waiter_waiter_activity, + dr.waiter_merging, + dr.waiter_spilling, + dr.waiter_waiting_to_close' + + CASE @ExportToExcel + WHEN 1 + THEN N'' + ELSE N', + dr.deadlock_graph' + END + + ' + FROM #deadlock_results AS dr + ORDER BY dr.event_date, dr.is_victim DESC + OPTION(RECOMPILE); + ' + + EXEC sys.sp_executesql + @deadlock_result; + SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); RAISERROR('Findings %s', 0, 1, @d) WITH NOWAIT; SELECT df.check_id, df.database_name, df.object_name, df.finding_group, df.finding @@ -25596,7 +25612,11 @@ ELSE --Output to database is not set output to client app SELECT '#deadlock_stack' AS table_name, * FROM #deadlock_stack AS ds OPTION ( RECOMPILE ); - + + SELECT '#deadlock_results' AS table_name, * + FROM #deadlock_results AS dr + OPTION ( RECOMPILE ); + END; -- End debug END; --Final End @@ -25660,7 +25680,7 @@ BEGIN /*First BEGIN*/ SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.01', @VersionDate = '20210222'; +SELECT @Version = '8.02', @VersionDate = '20210321'; IF(@VersionCheckMode = 1) BEGIN RETURN; @@ -30119,7 +30139,7 @@ BEGIN 100, 'Execution Pattern', 'Frequently Executed Queries', - 'http://brentozar.com/blitzcache/frequently-executed-queries/', + 'https://www.brentozar.com/blitzcache/frequently-executed-queries/', 'Queries are being executed more than ' + CAST (@execution_threshold AS VARCHAR(5)) + ' times per minute. This can put additional load on the server, even when queries are lightweight.') ; @@ -30134,7 +30154,7 @@ BEGIN 50, 'Parameterization', 'Parameter Sniffing', - 'http://brentozar.com/blitzcache/parameter-sniffing/', + 'https://www.brentozar.com/blitzcache/parameter-sniffing/', 'There are signs of parameter sniffing (wide variance in rows return or time to execute). Investigate query patterns and tune code appropriately.') ; /* Forced execution plans */ @@ -30148,7 +30168,7 @@ BEGIN 5, 'Parameterization', 'Forced Plans', - 'http://brentozar.com/blitzcache/forced-plans/', + 'https://www.brentozar.com/blitzcache/forced-plans/', 'Execution plans have been compiled with forced plans, either through FORCEPLAN, plan guides, or forced parameterization. This will make general tuning efforts less effective.'); IF EXISTS (SELECT 1/0 @@ -30161,7 +30181,7 @@ BEGIN 200, 'Cursors', 'Cursors', - 'http://brentozar.com/blitzcache/cursors-found-slow-queries/', + 'https://www.brentozar.com/blitzcache/cursors-found-slow-queries/', 'There are cursors in the plan cache. This is neither good nor bad, but it is a thing. Cursors are weird in SQL Server.'); IF EXISTS (SELECT 1/0 @@ -30175,7 +30195,7 @@ BEGIN 200, 'Cursors', 'Optimistic Cursors', - 'http://brentozar.com/blitzcache/cursors-found-slow-queries/', + 'https://www.brentozar.com/blitzcache/cursors-found-slow-queries/', 'There are optimistic cursors in the plan cache, which can harm performance.'); IF EXISTS (SELECT 1/0 @@ -30189,7 +30209,7 @@ BEGIN 200, 'Cursors', 'Non-forward Only Cursors', - 'http://brentozar.com/blitzcache/cursors-found-slow-queries/', + 'https://www.brentozar.com/blitzcache/cursors-found-slow-queries/', 'There are non-forward only cursors in the plan cache, which can harm performance.'); IF EXISTS (SELECT 1/0 @@ -30202,7 +30222,7 @@ BEGIN 200, 'Cursors', 'Dynamic Cursors', - 'http://brentozar.com/blitzcache/cursors-found-slow-queries/', + 'https://www.brentozar.com/blitzcache/cursors-found-slow-queries/', 'Dynamic Cursors inhibit parallelism!.'); IF EXISTS (SELECT 1/0 @@ -30215,7 +30235,7 @@ BEGIN 200, 'Cursors', 'Fast Forward Cursors', - 'http://brentozar.com/blitzcache/cursors-found-slow-queries/', + 'https://www.brentozar.com/blitzcache/cursors-found-slow-queries/', 'Fast forward cursors inhibit parallelism!.'); IF EXISTS (SELECT 1/0 @@ -30228,7 +30248,7 @@ BEGIN 50, 'Parameterization', 'Forced Parameterization', - 'http://brentozar.com/blitzcache/forced-parameterization/', + 'https://www.brentozar.com/blitzcache/forced-parameterization/', 'Execution plans have been compiled with forced parameterization.') ; IF EXISTS (SELECT 1/0 @@ -30241,7 +30261,7 @@ BEGIN 200, 'Execution Plans', 'Parallelism', - 'http://brentozar.com/blitzcache/parallel-plans-detected/', + 'https://www.brentozar.com/blitzcache/parallel-plans-detected/', 'Parallel plans detected. These warrant investigation, but are neither good nor bad.') ; IF EXISTS (SELECT 1/0 @@ -30254,7 +30274,7 @@ BEGIN 200, 'Execution Plans', 'Nearly Parallel', - 'http://brentozar.com/blitzcache/query-cost-near-cost-threshold-parallelism/', + 'https://www.brentozar.com/blitzcache/query-cost-near-cost-threshold-parallelism/', 'Queries near the cost threshold for parallelism. These may go parallel when you least expect it.') ; IF EXISTS (SELECT 1/0 @@ -30267,7 +30287,7 @@ BEGIN 50, 'Execution Plans', 'Query Plan Warnings', - 'http://brentozar.com/blitzcache/query-plan-warnings/', + 'https://www.brentozar.com/blitzcache/query-plan-warnings/', 'Warnings detected in execution plans. SQL Server is telling you that something bad is going on that requires your attention.') ; IF EXISTS (SELECT 1/0 @@ -30280,7 +30300,7 @@ BEGIN 50, 'Performance', 'Long Running Queries', - 'http://brentozar.com/blitzcache/long-running-queries/', + 'https://www.brentozar.com/blitzcache/long-running-queries/', 'Long running queries have been found. These are queries with an average duration longer than ' + CAST(@long_running_query_warning_seconds / 1000 / 1000 AS VARCHAR(5)) + ' second(s). These queries should be investigated for additional tuning options.') ; @@ -30295,7 +30315,7 @@ BEGIN 50, 'Performance', 'Missing Index Request', - 'http://brentozar.com/blitzcache/missing-index-request/', + 'https://www.brentozar.com/blitzcache/missing-index-request/', 'Queries found with missing indexes.'); IF EXISTS (SELECT 1/0 @@ -30308,7 +30328,7 @@ BEGIN 200, 'Cardinality', 'Legacy Cardinality Estimator in Use', - 'http://brentozar.com/blitzcache/legacy-cardinality-estimator/', + 'https://www.brentozar.com/blitzcache/legacy-cardinality-estimator/', 'A legacy cardinality estimator is being used by one or more queries. Investigate whether you need to be using this cardinality estimator. This may be caused by compatibility levels, global trace flags, or query level trace flags.'); IF EXISTS (SELECT 1/0 @@ -30321,7 +30341,7 @@ BEGIN 50, 'Performance', 'Implicit Conversions', - 'http://brentozar.com/go/implicit', + 'https://www.brentozar.com/go/implicit', 'One or more queries are comparing two fields that are not of the same data type.') ; IF EXISTS (SELECT 1/0 @@ -30334,7 +30354,7 @@ BEGIN 100, 'Performance', 'Busy Loops', - 'http://brentozar.com/blitzcache/busy-loops/', + 'https://www.brentozar.com/blitzcache/busy-loops/', 'Operations have been found that are executed 100 times more often than the number of rows returned by each iteration. This is an indicator that something is off in query execution.'); IF EXISTS (SELECT 1/0 @@ -30347,7 +30367,7 @@ BEGIN 50, 'Performance', 'Joining to table valued functions', - 'http://brentozar.com/blitzcache/tvf-join/', + 'https://www.brentozar.com/blitzcache/tvf-join/', 'Execution plans have been found that join to table valued functions (TVFs). TVFs produce inaccurate estimates of the number of rows returned and can lead to any number of query plan problems.'); IF EXISTS (SELECT 1/0 @@ -30360,7 +30380,7 @@ BEGIN 50, 'Execution Plans', 'Compilation timeout', - 'http://brentozar.com/blitzcache/compilation-timeout/', + 'https://www.brentozar.com/blitzcache/compilation-timeout/', 'Query compilation timed out for one or more queries. SQL Server did not find a plan that meets acceptable performance criteria in the time allotted so the best guess was returned. There is a very good chance that this plan isn''t even below average - it''s probably terrible.'); IF EXISTS (SELECT 1/0 @@ -30373,7 +30393,7 @@ BEGIN 50, 'Execution Plans', 'Compilation memory limit exceeded', - 'http://brentozar.com/blitzcache/compile-memory-limit-exceeded/', + 'https://www.brentozar.com/blitzcache/compile-memory-limit-exceeded/', 'The optimizer has a limited amount of memory available. One or more queries are complex enough that SQL Server was unable to allocate enough memory to fully optimize the query. A best fit plan was found, and it''s probably terrible.'); IF EXISTS (SELECT 1/0 @@ -30386,7 +30406,7 @@ BEGIN 10, 'Execution Plans', 'No join predicate', - 'http://brentozar.com/blitzcache/no-join-predicate/', + 'https://www.brentozar.com/blitzcache/no-join-predicate/', 'Operators in a query have no join predicate. This means that all rows from one table will be matched with all rows from anther table producing a Cartesian product. That''s a whole lot of rows. This may be your goal, but it''s important to investigate why this is happening.'); IF EXISTS (SELECT 1/0 @@ -30399,7 +30419,7 @@ BEGIN 200, 'Execution Plans', 'Multiple execution plans', - 'http://brentozar.com/blitzcache/multiple-plans/', + 'https://www.brentozar.com/blitzcache/multiple-plans/', 'Queries exist with multiple execution plans (as determined by query_plan_hash). Investigate possible ways to parameterize these queries or otherwise reduce the plan count.'); IF EXISTS (SELECT 1/0 @@ -30412,7 +30432,7 @@ BEGIN 100, 'Performance', 'Unmatched indexes', - 'http://brentozar.com/blitzcache/unmatched-indexes', + 'https://www.brentozar.com/blitzcache/unmatched-indexes', 'An index could have been used, but SQL Server chose not to use it - likely due to parameterization and filtered indexes.'); IF EXISTS (SELECT 1/0 @@ -30425,7 +30445,7 @@ BEGIN 100, 'Parameterization', 'Unparameterized queries', - 'http://brentozar.com/blitzcache/unparameterized-queries', + 'https://www.brentozar.com/blitzcache/unparameterized-queries', 'Unparameterized queries found. These could be ad hoc queries, data exploration, or queries using "OPTIMIZE FOR UNKNOWN".'); IF EXISTS (SELECT 1/0 @@ -30438,7 +30458,7 @@ BEGIN 100, 'Execution Plans', 'Trivial Plans', - 'http://brentozar.com/blitzcache/trivial-plans', + 'https://www.brentozar.com/blitzcache/trivial-plans', 'Trivial plans get almost no optimization. If you''re finding these in the top worst queries, something may be going wrong.'); IF EXISTS (SELECT 1/0 @@ -30451,7 +30471,7 @@ BEGIN 10, 'Execution Plans', 'Forced Serialization', - 'http://www.brentozar.com/blitzcache/forced-serialization/', + 'https://www.brentozar.com/blitzcache/forced-serialization/', 'Something in your plan is forcing a serial query. Further investigation is needed if this is not by design.') ; IF EXISTS (SELECT 1/0 @@ -30464,7 +30484,7 @@ BEGIN 100, 'Execution Plans', 'Expensive Key Lookups', - 'http://www.brentozar.com/blitzcache/expensive-key-lookups/', + 'https://www.brentozar.com/blitzcache/expensive-key-lookups/', 'There''s a key lookup in your plan that costs >=50% of the total plan cost.') ; IF EXISTS (SELECT 1/0 @@ -30477,7 +30497,7 @@ BEGIN 100, 'Execution Plans', 'Expensive Remote Query', - 'http://www.brentozar.com/blitzcache/expensive-remote-query/', + 'https://www.brentozar.com/blitzcache/expensive-remote-query/', 'There''s a remote query in your plan that costs >=50% of the total plan cost.') ; IF EXISTS (SELECT 1/0 @@ -30569,7 +30589,7 @@ BEGIN 100, 'Operator Warnings', 'SQL is throwing operator level plan warnings', - 'http://brentozar.com/blitzcache/query-plan-warnings/', + 'https://www.brentozar.com/blitzcache/query-plan-warnings/', 'Check the plan for more details.') ; IF EXISTS (SELECT 1/0 @@ -30661,7 +30681,7 @@ BEGIN 100, 'Execution Plans', 'Expensive Sort', - 'http://www.brentozar.com/blitzcache/expensive-sorts/', + 'https://www.brentozar.com/blitzcache/expensive-sorts/', 'There''s a sort in your plan that costs >=50% of the total plan cost.') ; IF EXISTS (SELECT 1/0 @@ -30881,7 +30901,7 @@ BEGIN 100, 'MSTVFs', 'These have many of the same problems scalar UDFs have', - 'http://brentozar.com/blitzcache/tvf-join/', + 'https://www.brentozar.com/blitzcache/tvf-join/', 'Execution plans have been found that join to table valued functions (TVFs). TVFs produce inaccurate estimates of the number of rows returned and can lead to any number of query plan problems.'); IF EXISTS (SELECT 1/0 @@ -31387,7 +31407,7 @@ BEGIN SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.01', @VersionDate = '20210222'; + SELECT @Version = '8.02', @VersionDate = '20210321'; IF(@VersionCheckMode = 1) BEGIN @@ -31802,18 +31822,18 @@ IF @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @Output + N' [request_cpu_time], ' + @LineFeed + N' [degree_of_parallelism], ' + @LineFeed + N' [request_logical_reads], ' + @LineFeed - + N' ((CAST([request_logical_reads] AS MONEY)* 8)/ 1024) [Logical_Reads_MB], ' + @LineFeed + + N' ((CAST([request_logical_reads] AS DECIMAL(38,2))* 8)/ 1024) [Logical_Reads_MB], ' + @LineFeed + N' [request_writes], ' + @LineFeed - + N' ((CAST([request_writes] AS MONEY)* 8)/ 1024) [Logical_Writes_MB], ' + @LineFeed + + N' ((CAST([request_writes] AS DECIMAL(38,2))* 8)/ 1024) [Logical_Writes_MB], ' + @LineFeed + N' [request_physical_reads], ' + @LineFeed - + N' ((CAST([request_physical_reads] AS MONEY)* 8)/ 1024) [Physical_reads_MB], ' + @LineFeed + + N' ((CAST([request_physical_reads] AS DECIMAL(38,2))* 8)/ 1024) [Physical_reads_MB], ' + @LineFeed + N' [session_cpu], ' + @LineFeed + N' [session_logical_reads], ' + @LineFeed - + N' ((CAST([session_logical_reads] AS MONEY)* 8)/ 1024) [session_logical_reads_MB], ' + @LineFeed + + N' ((CAST([session_logical_reads] AS DECIMAL(38,2))* 8)/ 1024) [session_logical_reads_MB], ' + @LineFeed + N' [session_physical_reads], ' + @LineFeed - + N' ((CAST([session_physical_reads] AS MONEY)* 8)/ 1024) [session_physical_reads_MB], ' + @LineFeed + + N' ((CAST([session_physical_reads] AS DECIMAL(38,2))* 8)/ 1024) [session_physical_reads_MB], ' + @LineFeed + N' [session_writes], ' + @LineFeed - + N' ((CAST([session_writes] AS MONEY)* 8)/ 1024) [session_writes_MB], ' + @LineFeed + + N' ((CAST([session_writes] AS DECIMAL(38,2))* 8)/ 1024) [session_writes_MB], ' + @LineFeed + N' [tempdb_allocations_mb], ' + @LineFeed + N' [memory_usage], ' + @LineFeed + N' [estimated_completion_time], ' + @LineFeed @@ -32640,6 +32660,7 @@ VALUES (15, 4003, 'CU1', 'https://support.microsoft.com/en-us/help/4527376', '2020-01-07', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 1 '), (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 '), + (14, 3356, 'RTM CU23', 'https://support.microsoft.com/en-us/help/5000685', '2021-02-25', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 23'), (14, 3370, 'RTM CU22 GDR', 'https://support.microsoft.com/en-us/help/4583457', '2021-01-12', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 22 GDR'), (14, 3356, 'RTM CU22', 'https://support.microsoft.com/en-us/help/4577467', '2020-09-10', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 22'), (14, 3335, 'RTM CU21', 'https://support.microsoft.com/en-us/help/4557397', '2020-07-01', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 21'), @@ -33015,7 +33036,7 @@ BEGIN SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.01', @VersionDate = '20210222'; +SELECT @Version = '8.02', @VersionDate = '20210321'; IF(@VersionCheckMode = 1) BEGIN @@ -34265,35 +34286,42 @@ BEGIN /* Populate #FileStats, #PerfmonStats, #WaitStats with DMV data. After we finish doing our checks, we'll take another sample and compare them. */ RAISERROR('Capturing first pass of wait stats, perfmon counters, file stats',10,1) WITH NOWAIT; - INSERT #WaitStats(Pass, SampleTime, wait_type, wait_time_ms, signal_wait_time_ms, waiting_tasks_count) - SELECT - x.Pass, - x.SampleTime, - x.wait_type, - SUM(x.sum_wait_time_ms) AS sum_wait_time_ms, - SUM(x.sum_signal_wait_time_ms) AS sum_signal_wait_time_ms, - SUM(x.sum_waiting_tasks) AS sum_waiting_tasks - FROM ( - SELECT - 1 AS Pass, - CASE @Seconds WHEN 0 THEN @StartSampleTime ELSE SYSDATETIMEOFFSET() END AS SampleTime, - owt.wait_type, - CASE @Seconds WHEN 0 THEN 0 ELSE SUM(owt.wait_duration_ms) OVER (PARTITION BY owt.wait_type, owt.session_id) - - CASE WHEN @Seconds = 0 THEN 0 ELSE (@Seconds * 1000) END END AS sum_wait_time_ms, - 0 AS sum_signal_wait_time_ms, - 0 AS sum_waiting_tasks - FROM sys.dm_os_waiting_tasks owt - WHERE owt.session_id > 50 - AND owt.wait_duration_ms >= CASE @Seconds WHEN 0 THEN 0 ELSE @Seconds * 1000 END - UNION ALL - SELECT - 1 AS Pass, - CASE @Seconds WHEN 0 THEN @StartSampleTime ELSE SYSDATETIMEOFFSET() END AS SampleTime, - os.wait_type, - CASE @Seconds WHEN 0 THEN 0 ELSE SUM(os.wait_time_ms) OVER (PARTITION BY os.wait_type) END AS sum_wait_time_ms, - CASE @Seconds WHEN 0 THEN 0 ELSE SUM(os.signal_wait_time_ms) OVER (PARTITION BY os.wait_type ) END AS sum_signal_wait_time_ms, - CASE @Seconds WHEN 0 THEN 0 ELSE SUM(os.waiting_tasks_count) OVER (PARTITION BY os.wait_type) END AS sum_waiting_tasks - FROM sys.dm_os_wait_stats os + SET @StringToExecute = N' + INSERT #WaitStats(Pass, SampleTime, wait_type, wait_time_ms, signal_wait_time_ms, waiting_tasks_count) + SELECT + x.Pass, + x.SampleTime, + x.wait_type, + SUM(x.sum_wait_time_ms) AS sum_wait_time_ms, + SUM(x.sum_signal_wait_time_ms) AS sum_signal_wait_time_ms, + SUM(x.sum_waiting_tasks) AS sum_waiting_tasks + FROM ( + SELECT + 1 AS Pass, + CASE @Seconds WHEN 0 THEN @StartSampleTime ELSE SYSDATETIMEOFFSET() END AS SampleTime, + owt.wait_type, + CASE @Seconds WHEN 0 THEN 0 ELSE SUM(owt.wait_duration_ms) OVER (PARTITION BY owt.wait_type, owt.session_id) + - CASE WHEN @Seconds = 0 THEN 0 ELSE (@Seconds * 1000) END END AS sum_wait_time_ms, + 0 AS sum_signal_wait_time_ms, + 0 AS sum_waiting_tasks + FROM sys.dm_os_waiting_tasks owt + WHERE owt.session_id > 50 + AND owt.wait_duration_ms >= CASE @Seconds WHEN 0 THEN 0 ELSE @Seconds * 1000 END + UNION ALL + SELECT + 1 AS Pass, + CASE @Seconds WHEN 0 THEN @StartSampleTime ELSE SYSDATETIMEOFFSET() END AS SampleTime, + os.wait_type, + CASE @Seconds WHEN 0 THEN 0 ELSE SUM(os.wait_time_ms) OVER (PARTITION BY os.wait_type) END AS sum_wait_time_ms, + CASE @Seconds WHEN 0 THEN 0 ELSE SUM(os.signal_wait_time_ms) OVER (PARTITION BY os.wait_type ) END AS sum_signal_wait_time_ms, + CASE @Seconds WHEN 0 THEN 0 ELSE SUM(os.waiting_tasks_count) OVER (PARTITION BY os.wait_type) END AS sum_waiting_tasks '; + + IF SERVERPROPERTY('Edition') = 'SQL Azure' + SET @StringToExecute = @StringToExecute + N' FROM sys.dm_db_wait_stats os '; + ELSE + SET @StringToExecute = @StringToExecute + N' FROM sys.dm_os_wait_stats os '; + + SET @StringToExecute = @StringToExecute + N' ) x WHERE NOT EXISTS ( @@ -34303,7 +34331,8 @@ BEGIN AND wc.Ignorable = 1 ) GROUP BY x.Pass, x.SampleTime, x.wait_type - ORDER BY sum_wait_time_ms DESC; + ORDER BY sum_wait_time_ms DESC;' + EXEC sp_executesql @StringToExecute, N'@StartSampleTime DATETIMEOFFSET, @Seconds INT', @StartSampleTime, @Seconds; INSERT INTO #FileStats (Pass, SampleTime, DatabaseID, FileID, DatabaseName, FileLogicalName, SizeOnDiskMB, io_stall_read_ms , @@ -34376,7 +34405,7 @@ BEGIN 1 AS Priority, 'Maintenance Tasks Running' AS FindingGroup, 'Backup Running' AS Finding, - 'http://www.BrentOzar.com/askbrent/backups/' AS URL, + 'https://www.brentozar.com/askbrent/backups/' AS URL, 'Backup of ' + DB_NAME(db.resource_database_id) + ' database (' + (SELECT CAST(CAST(SUM(size * 8.0 / 1024 / 1024) AS BIGINT) AS NVARCHAR) FROM #MasterFiles WHERE database_id = db.resource_database_id) + 'GB) ' + @LineFeed + CAST(r.percent_complete AS NVARCHAR(100)) + '% complete, has been running since ' + CAST(r.start_time AS NVARCHAR(100)) + '. ' + @LineFeed + CASE WHEN COALESCE(s.nt_user_name, s.login_name) IS NOT NULL THEN (' Login: ' + COALESCE(s.nt_user_name, s.login_name) + ' ') ELSE '' END AS Details, @@ -34428,7 +34457,7 @@ BEGIN 1 AS Priority, 'Maintenance Tasks Running' AS FindingGroup, 'DBCC CHECK* Running' AS Finding, - 'http://www.BrentOzar.com/askbrent/dbcc/' AS URL, + 'https://www.brentozar.com/askbrent/dbcc/' AS URL, 'Corruption check of ' + DB_NAME(db.resource_database_id) + ' database (' + (SELECT CAST(CAST(SUM(size * 8.0 / 1024 / 1024) AS BIGINT) AS NVARCHAR) FROM #MasterFiles WHERE database_id = db.resource_database_id) + 'GB) 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, @@ -34473,7 +34502,7 @@ BEGIN 1 AS Priority, 'Maintenance Tasks Running' AS FindingGroup, 'Restore Running' AS Finding, - 'http://www.BrentOzar.com/askbrent/backups/' AS URL, + 'https://www.brentozar.com/askbrent/backups/' AS URL, 'Restore of ' + DB_NAME(db.resource_database_id) + ' database (' + (SELECT CAST(CAST(SUM(size * 8.0 / 1024 / 1024) AS BIGINT) AS NVARCHAR) FROM #MasterFiles WHERE database_id = db.resource_database_id) + '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, @@ -34514,9 +34543,9 @@ BEGIN 1 AS Priority, 'SQL Server Internal Maintenance' AS FindingGroup, 'Database File Growing' AS Finding, - 'http://www.BrentOzar.com/go/instant' AS URL, + 'https://www.brentozar.com/go/instant' AS URL, 'SQL Server is waiting for Windows to provide storage space for a database restore, a data file growth, or a log file growth. This task has been running since ' + CAST(r.start_time AS NVARCHAR(100)) + '.' + @LineFeed + 'Check the query plan (expert mode) to identify the database involved.' AS Details, - 'Unfortunately, you can''t stop this, but you can prevent it next time. Check out http://www.BrentOzar.com/go/instant for details.' AS HowToStopIt, + 'Unfortunately, you can''t stop this, but you can prevent it next time. Check out https://www.brentozar.com/go/instant for details.' AS HowToStopIt, pl.query_plan AS QueryPlan, r.start_time AS StartTime, s.login_name AS LoginName, @@ -34548,7 +34577,7 @@ BEGIN 1 AS Priority, ''Query Problems'' AS FindingGroup, ''Long-Running Query Blocking Others'' AS Finding, - ''http://www.BrentOzar.com/go/blocking'' AS URL, + ''https://www.brentozar.com/go/blocking'' AS URL, ''Query in '' + COALESCE(DB_NAME(COALESCE((SELECT TOP 1 dbid FROM sys.dm_exec_sql_text(r.sql_handle)), (SELECT TOP 1 t.dbid FROM master..sysprocesses spBlocker CROSS APPLY sys.dm_exec_sql_text(spBlocker.sql_handle) t WHERE spBlocker.spid = tBlocked.blocking_session_id))), ''(Unknown)'') + '' has a last request start time of '' + CAST(s.last_request_start_time AS NVARCHAR(100)) + ''. Query follows: ' + @LineFeed + @LineFeed + @@ -34591,7 +34620,7 @@ BEGIN 50 AS Priority, 'Query Problems' AS FindingGroup, 'Plan Cache Erased Recently' AS Finding, - 'http://www.BrentOzar.com/askbrent/plan-cache-erased-recently/' AS URL, + 'https://www.brentozar.com/askbrent/plan-cache-erased-recently/' AS URL, 'The oldest query in the plan cache was created at ' + CAST(creation_time AS NVARCHAR(50)) + '. ' + @LineFeed + @LineFeed + 'This indicates that someone ran DBCC FREEPROCCACHE at that time,' + @LineFeed + 'Giving SQL Server temporary amnesia. Now, as queries come in,' + @LineFeed @@ -34616,7 +34645,7 @@ BEGIN 50 AS Priority, 'Query Problems' AS FindingGroup, 'Sleeping Query with Open Transactions' AS Finding, - 'http://www.brentozar.com/askbrent/sleeping-query-with-open-transactions/' AS URL, + 'https://www.brentozar.com/askbrent/sleeping-query-with-open-transactions/' AS URL, 'Database: ' + DB_NAME(db.resource_database_id) + @LineFeed + 'Host: ' + s.[host_name] + @LineFeed + 'Program: ' + s.[program_name] + @LineFeed + 'Asleep with open transactions and locks since ' + CAST(s.last_request_end_time AS NVARCHAR(100)) + '. ' AS Details, 'KILL ' + CAST(s.session_id AS NVARCHAR(100)) + ';' AS HowToStopIt, s.last_request_start_time AS StartTime, @@ -34702,7 +34731,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 1 AS Priority, 'Query Problems' AS FindingGroup, 'Query Rolling Back' AS Finding, - 'http://www.BrentOzar.com/askbrent/rollback/' AS URL, + 'https://www.brentozar.com/askbrent/rollback/' AS URL, 'Rollback started at ' + CAST(r.start_time AS NVARCHAR(100)) + ', is ' + CAST(r.percent_complete AS NVARCHAR(100)) + '% complete.' AS Details, 'Unfortunately, you can''t stop this. Whatever you do, don''t restart the server in an attempt to fix it - SQL Server will keep rolling back.' AS HowToStopIt, r.start_time AS StartTime, @@ -34783,7 +34812,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 50 AS Priority, 'Server Performance' AS FindingGroup, 'Too Much Free Memory' AS Finding, - 'https://BrentOzar.com/go/freememory' AS URL, + 'https://www.brentozar.com/go/freememory' AS URL, CAST((CAST(cFree.cntr_value AS BIGINT) / 1024 / 1024 ) AS NVARCHAR(100)) + N'GB of free memory inside SQL Server''s buffer pool,' + @LineFeed + ' which is ' + CAST((CAST(cTotal.cntr_value AS BIGINT) / 1024 / 1024) AS NVARCHAR(100)) + N'GB. You would think lots of free memory would be good, but check out the URL for more information.' AS Details, 'Run sp_BlitzCache @SortOrder = ''memory grant'' to find queries with huge memory grants and tune them.' AS HowToStopIt FROM sys.dm_os_performance_counters cFree @@ -34808,7 +34837,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 10 AS Priority, 'Server Performance' AS FindingGroup, 'Target Memory Lower Than Max' AS Finding, - 'https://BrentOzar.com/go/target' AS URL, + 'https://www.brentozar.com/go/target' AS URL, N'Max server memory is ' + CAST(cMax.value_in_use AS NVARCHAR(50)) + N' MB but target server memory is only ' + CAST((CAST(cTarget.cntr_value AS BIGINT) / 1024) AS NVARCHAR(50)) + N' MB,' + @LineFeed + N'indicating that SQL Server may be under external memory pressure or max server memory may be set too high.' AS Details, 'Investigate what OS processes are using memory, and double-check the max server memory setting.' AS HowToStopIt @@ -34834,7 +34863,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 'Database Size, Total GB' AS Finding, CAST(SUM (CAST(size AS BIGINT)*8./1024./1024.) AS VARCHAR(100)) AS Details, SUM (CAST(size AS BIGINT))*8./1024./1024. AS DetailsInt, - 'http://www.BrentOzar.com/askbrent/' AS URL + 'https://www.brentozar.com/askbrent/' AS URL FROM #MasterFiles WHERE database_id > 4; @@ -34851,7 +34880,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 'Database Count' AS Finding, CAST(SUM(1) AS VARCHAR(100)) AS Details, SUM (1) AS DetailsInt, - 'http://www.BrentOzar.com/askbrent/' AS URL + 'https://www.brentozar.com/askbrent/' AS URL FROM sys.databases WHERE database_id > 4; @@ -34906,7 +34935,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, / CAST(@MaxWorkspace AS MONEY)) * 100, 0) AS NVARCHAR(50)) + '%' + @LineFeed + 'Oldest Grant in seconds: ' + CAST(ISNULL(DATEDIFF(SECOND, MIN(Grants.request_time), GETDATE()), 0) AS NVARCHAR(50)) AS Details, (SELECT COUNT(*) FROM sys.dm_exec_query_memory_grants WHERE queue_id IS NULL) AS DetailsInt, - 'http://www.BrentOzar.com/askbrent/' AS URL + 'https://www.brentozar.com/askbrent/' AS URL FROM sys.dm_exec_query_memory_grants AS Grants; /* Query Problems - Queries with high memory grants - CheckID 46 */ @@ -35116,7 +35145,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 100 AS Priority, ''Query Performance'' AS FindingsGroup, ''Queries with 10000x cardinality misestimations'' AS Findings, - ''https://brentozar.com/go/skewedup'' AS URL, + ''https://www.brentozar.com/go/skewedup'' AS URL, ''The query on SPID '' + RTRIM(b.session_id) + '' has been running for '' @@ -35167,7 +35196,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 100 AS Priority, ''Query Performance'' AS FindingsGroup, ''Queries with 10000x skewed parallelism'' AS Findings, - ''https://brentozar.com/go/skewedup'' AS URL, + ''https://www.brentozar.com/go/skewedup'' AS URL, ''The query on SPID '' + RTRIM(p.session_id) + '' has been running for '' @@ -35225,7 +35254,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, END INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) - SELECT 24, 50, 'Server Performance', 'High CPU Utilization', CAST(100 - SystemIdle AS NVARCHAR(20)) + N'%.', 100 - SystemIdle, 'http://www.BrentOzar.com/go/cpu' + SELECT 24, 50, 'Server Performance', 'High CPU Utilization', CAST(100 - SystemIdle AS NVARCHAR(20)) + N'%.', 100 - SystemIdle, 'https://www.brentozar.com/go/cpu' FROM ( SELECT record, record.value('(./Record/SchedulerMonitorEvent/SystemHealth/SystemIdle)[1]', 'int') AS SystemIdle @@ -35269,7 +35298,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 'CPU Utilization', y.system_idle + N'%. Ring buffer details: ' + CAST(y.record AS NVARCHAR(4000)), y.system_idle , - 'http://www.BrentOzar.com/go/cpu', + 'https://www.brentozar.com/go/cpu', STUFF(( SELECT TOP 2147483647 CHAR(10) + CHAR(13) + y2.system_idle @@ -35291,7 +35320,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, END INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) - SELECT 28, 50, 'Server Performance', 'High CPU Utilization - Not SQL', CONVERT(NVARCHAR(100),100 - (y.SQLUsage + y.SystemIdle)) + N'% - Other Processes (not SQL Server) are using this much CPU. This may impact on the performance of your SQL Server instance', 100 - (y.SQLUsage + y.SystemIdle), 'http://www.BrentOzar.com/go/cpu' + SELECT 28, 50, 'Server Performance', 'High CPU Utilization - Not SQL', CONVERT(NVARCHAR(100),100 - (y.SQLUsage + y.SystemIdle)) + N'% - Other Processes (not SQL Server) are using this much CPU. This may impact on the performance of your SQL Server instance', 100 - (y.SQLUsage + y.SystemIdle), 'https://www.brentozar.com/go/cpu' FROM ( SELECT record, record.value('(./Record/SchedulerMonitorEvent/SystemHealth/SystemIdle)[1]', 'int') AS SystemIdle @@ -35314,6 +35343,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, END IF 20 >= (SELECT COUNT(*) FROM sys.databases WHERE name NOT IN ('master', 'model', 'msdb', 'tempdb')) + AND @Seconds > 0 BEGIN CREATE TABLE #UpdatedStats (HowToStopIt NVARCHAR(4000), RowsForSorting BIGINT); IF EXISTS(SELECT * FROM sys.all_objects WHERE name = 'dm_db_stats_properties') @@ -35321,49 +35351,59 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, /* We don't want to hang around to obtain locks */ SET LOCK_TIMEOUT 0; - EXEC sp_MSforeachdb N'USE [?]; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; SET LOCK_TIMEOUT 1000; - BEGIN TRY - INSERT INTO #UpdatedStats(HowToStopIt, RowsForSorting) - SELECT HowToStopIt = - QUOTENAME(DB_NAME()) + N''.'' + - QUOTENAME(SCHEMA_NAME(obj.schema_id)) + N''.'' + - QUOTENAME(obj.name) + - N'' statistic '' + QUOTENAME(stat.name) + - N'' was updated on '' + CONVERT(NVARCHAR(50), sp.last_updated, 121) + N'','' + - N'' had '' + CAST(sp.rows AS NVARCHAR(50)) + N'' rows, with '' + - CAST(sp.rows_sampled AS NVARCHAR(50)) + N'' rows sampled,'' + - N'' producing '' + CAST(sp.steps AS NVARCHAR(50)) + N'' steps in the histogram.'', - sp.rows - FROM sys.objects AS obj WITH (NOLOCK) - INNER JOIN sys.stats AS stat WITH (NOLOCK) ON stat.object_id = obj.object_id - CROSS APPLY sys.dm_db_stats_properties(stat.object_id, stat.stats_id) AS sp - WHERE sp.last_updated > DATEADD(MI, -15, GETDATE()) - AND obj.is_ms_shipped = 0 - AND ''[?]'' <> ''[tempdb]''; - END TRY - BEGIN CATCH - IF (ERROR_NUMBER() = 1222) - BEGIN - INSERT INTO #UpdatedStats(HowToStopIt, RowsForSorting) - SELECT HowToStopIt = - QUOTENAME(DB_NAME()) + - N'' No information could be retrieved as the lock timeout was exceeded,''+ - N'' this is likely due to an Index operation in Progress'', - -1 - END - ELSE - BEGIN - INSERT INTO #UpdatedStats(HowToStopIt, RowsForSorting) - SELECT HowToStopIt = - QUOTENAME(DB_NAME()) + - N'' No information could be retrieved as a result of error: ''+ - CAST(ERROR_NUMBER() AS NVARCHAR(10)) + - N'' with message: ''+ - CAST(ERROR_MESSAGE() AS NVARCHAR(128)), - -1 - END - END CATCH'; + IF SERVERPROPERTY('Edition') <> 'SQL Azure' + SET @StringToExecute = N'USE [?];' + @LineFeed; + ELSE + SET @StringToExecute = N''; + + SET @StringToExecute = @StringToExecute + 'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; SET LOCK_TIMEOUT 1000;' + @LineFeed + + 'BEGIN TRY' + @LineFeed + + ' INSERT INTO #UpdatedStats(HowToStopIt, RowsForSorting)' + @LineFeed + + ' SELECT HowToStopIt = ' + @LineFeed + + ' QUOTENAME(DB_NAME()) + N''.'' +' + @LineFeed + + ' QUOTENAME(SCHEMA_NAME(obj.schema_id)) + N''.'' +' + @LineFeed + + ' QUOTENAME(obj.name) +' + @LineFeed + + ' N'' statistic '' + QUOTENAME(stat.name) + ' + @LineFeed + + ' N'' was updated on '' + CONVERT(NVARCHAR(50), sp.last_updated, 121) + N'','' + ' + @LineFeed + + ' N'' had '' + CAST(sp.rows AS NVARCHAR(50)) + N'' rows, with '' +' + @LineFeed + + ' CAST(sp.rows_sampled AS NVARCHAR(50)) + N'' rows sampled,'' + ' + @LineFeed + + ' N'' producing '' + CAST(sp.steps AS NVARCHAR(50)) + N'' steps in the histogram.'',' + @LineFeed + + ' sp.rows' + @LineFeed + + ' FROM sys.objects AS obj WITH (NOLOCK)' + @LineFeed + + ' INNER JOIN sys.stats AS stat WITH (NOLOCK) ON stat.object_id = obj.object_id ' + @LineFeed + + ' CROSS APPLY sys.dm_db_stats_properties(stat.object_id, stat.stats_id) AS sp ' + @LineFeed + + ' WHERE sp.last_updated > DATEADD(MI, -15, GETDATE())' + @LineFeed + + ' AND obj.is_ms_shipped = 0' + @LineFeed + + ' AND ''[?]'' <> ''[tempdb]'';' + @LineFeed + + 'END TRY' + @LineFeed + + 'BEGIN CATCH' + @LineFeed + + ' IF (ERROR_NUMBER() = 1222)' + @LineFeed + + ' BEGIN ' + @LineFeed + + ' INSERT INTO #UpdatedStats(HowToStopIt, RowsForSorting)' + @LineFeed + + ' SELECT HowToStopIt = ' + @LineFeed + + ' QUOTENAME(DB_NAME()) +' + @LineFeed + + ' N'' No information could be retrieved as the lock timeout was exceeded,''+' + @LineFeed + + ' N'' this is likely due to an Index operation in Progress'',' + @LineFeed + + ' -1' + @LineFeed + + ' END' + @LineFeed + + ' ELSE' + @LineFeed + + ' BEGIN' + @LineFeed + + ' INSERT INTO #UpdatedStats(HowToStopIt, RowsForSorting)' + @LineFeed + + ' SELECT HowToStopIt = ' + @LineFeed + + ' QUOTENAME(DB_NAME()) +' + @LineFeed + + ' N'' No information could be retrieved as a result of error: ''+' + @LineFeed + + ' CAST(ERROR_NUMBER() AS NVARCHAR(10)) +' + @LineFeed + + ' N'' with message: ''+' + @LineFeed + + ' CAST(ERROR_MESSAGE() AS NVARCHAR(128)),' + @LineFeed + + ' -1' + @LineFeed + + ' END' + @LineFeed + + 'END CATCH' + ; + + IF SERVERPROPERTY('Edition') <> 'SQL Azure' + EXEC sp_MSforeachdb @StringToExecute; + ELSE + EXEC(@StringToExecute); /* Set timeout back to a default value of -1 */ SET LOCK_TIMEOUT -1; @@ -35376,7 +35416,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 50 AS Priority, 'Query Problems' AS FindingGroup, 'Statistics Updated Recently' AS Finding, - 'http://www.BrentOzar.com/go/stats' AS URL, + 'https://www.brentozar.com/go/stats' AS URL, 'In the last 15 minutes, statistics were updated. To see which ones, click the HowToStopIt column.' + @LineFeed + @LineFeed + 'This effectively clears the plan cache for queries that involve these tables,' + @LineFeed + 'which thereby causes parameter sniffing: those queries are now getting brand new' + @LineFeed @@ -35401,35 +35441,42 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, RAISERROR('Capturing second pass of wait stats, perfmon counters, file stats',10,1) WITH NOWAIT; /* Populate #FileStats, #PerfmonStats, #WaitStats with DMV data. In a second, we'll compare these. */ - INSERT #WaitStats(Pass, SampleTime, wait_type, wait_time_ms, signal_wait_time_ms, waiting_tasks_count) - SELECT - x.Pass, - x.SampleTime, - x.wait_type, - SUM(x.sum_wait_time_ms) AS sum_wait_time_ms, - SUM(x.sum_signal_wait_time_ms) AS sum_signal_wait_time_ms, - SUM(x.sum_waiting_tasks) AS sum_waiting_tasks - FROM ( - SELECT - 2 AS Pass, - SYSDATETIMEOFFSET() AS SampleTime, - owt.wait_type, - SUM(owt.wait_duration_ms) OVER (PARTITION BY owt.wait_type, owt.session_id) - - CASE WHEN @Seconds = 0 THEN 0 ELSE (@Seconds * 1000) END AS sum_wait_time_ms, - 0 AS sum_signal_wait_time_ms, - CASE @Seconds WHEN 0 THEN 0 ELSE 1 END AS sum_waiting_tasks - FROM sys.dm_os_waiting_tasks owt - WHERE owt.session_id > 50 - AND owt.wait_duration_ms >= CASE @Seconds WHEN 0 THEN 0 ELSE @Seconds * 1000 END - UNION ALL - SELECT - 2 AS Pass, - SYSDATETIMEOFFSET() AS SampleTime, - os.wait_type, - SUM(os.wait_time_ms) OVER (PARTITION BY os.wait_type) AS sum_wait_time_ms, - SUM(os.signal_wait_time_ms) OVER (PARTITION BY os.wait_type ) AS sum_signal_wait_time_ms, - SUM(os.waiting_tasks_count) OVER (PARTITION BY os.wait_type) AS sum_waiting_tasks - FROM sys.dm_os_wait_stats os + SET @StringToExecute = N' + INSERT #WaitStats(Pass, SampleTime, wait_type, wait_time_ms, signal_wait_time_ms, waiting_tasks_count) + SELECT + x.Pass, + x.SampleTime, + x.wait_type, + SUM(x.sum_wait_time_ms) AS sum_wait_time_ms, + SUM(x.sum_signal_wait_time_ms) AS sum_signal_wait_time_ms, + SUM(x.sum_waiting_tasks) AS sum_waiting_tasks + FROM ( + SELECT + 2 AS Pass, + SYSDATETIMEOFFSET() AS SampleTime, + owt.wait_type, + SUM(owt.wait_duration_ms) OVER (PARTITION BY owt.wait_type, owt.session_id) + - CASE WHEN @Seconds = 0 THEN 0 ELSE (@Seconds * 1000) END AS sum_wait_time_ms, + 0 AS sum_signal_wait_time_ms, + CASE @Seconds WHEN 0 THEN 0 ELSE 1 END AS sum_waiting_tasks + FROM sys.dm_os_waiting_tasks owt + WHERE owt.session_id > 50 + AND owt.wait_duration_ms >= CASE @Seconds WHEN 0 THEN 0 ELSE @Seconds * 1000 END + UNION ALL + SELECT + 2 AS Pass, + SYSDATETIMEOFFSET() AS SampleTime, + os.wait_type, + SUM(os.wait_time_ms) OVER (PARTITION BY os.wait_type) AS sum_wait_time_ms, + SUM(os.signal_wait_time_ms) OVER (PARTITION BY os.wait_type ) AS sum_signal_wait_time_ms, + SUM(os.waiting_tasks_count) OVER (PARTITION BY os.wait_type) AS sum_waiting_tasks '; + + IF SERVERPROPERTY('Edition') = 'SQL Azure' + SET @StringToExecute = @StringToExecute + N' FROM sys.dm_db_wait_stats os '; + ELSE + SET @StringToExecute = @StringToExecute + N' FROM sys.dm_os_wait_stats os '; + + SET @StringToExecute = @StringToExecute + N' ) x WHERE NOT EXISTS ( @@ -35439,7 +35486,8 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, AND wc.Ignorable = 1 ) GROUP BY x.Pass, x.SampleTime, x.wait_type - ORDER BY sum_wait_time_ms DESC; + ORDER BY sum_wait_time_ms DESC;'; + EXEC sp_executesql @StringToExecute, N'@Seconds INT', @Seconds; INSERT INTO #FileStats (Pass, SampleTime, DatabaseID, FileID, DatabaseName, FileLogicalName, SizeOnDiskMB, io_stall_read_ms , num_of_reads, [bytes_read] , io_stall_write_ms,num_of_writes, [bytes_written], PhysicalName, TypeDesc, avg_stall_read_ms, avg_stall_write_ms) @@ -35506,7 +35554,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, END INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (18, 210, 'Query Stats', 'Plan Cache Analysis Skipped', 'http://www.BrentOzar.com/go/topqueries', + VALUES (18, 210, 'Query Stats', 'Plan Cache Analysis Skipped', 'https://www.brentozar.com/go/topqueries', 'Due to excessive load, the plan cache analysis was skipped. To override this, use @ExpertMode = 1.'); END; @@ -35649,7 +35697,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, END INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, QueryText, QueryStatsNowID, QueryStatsFirstID, PlanHandle, QueryHash) - SELECT 17, 210, 'Query Stats', 'Most Resource-Intensive Queries', 'http://www.BrentOzar.com/go/topqueries', + SELECT 17, 210, 'Query Stats', 'Most Resource-Intensive Queries', 'https://www.brentozar.com/go/topqueries', 'Query stats during the sample:' + @LineFeed + 'Executions: ' + CAST(qsNow.execution_count - (COALESCE(qsFirst.execution_count, 0)) AS NVARCHAR(100)) + @LineFeed + 'Elapsed Time: ' + CAST(qsNow.total_elapsed_time - (COALESCE(qsFirst.total_elapsed_time, 0)) AS NVARCHAR(100)) + @LineFeed + @@ -35737,7 +35785,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 10 AS Priority, 'Server Performance' AS FindingGroup, 'Poison Wait Detected: ' + wNow.wait_type AS Finding, - N'http://www.brentozar.com/go/poison/#' + wNow.wait_type AS URL, + N'https://www.brentozar.com/go/poison/#' + wNow.wait_type AS URL, 'For ' + CAST(((wNow.wait_time_ms - COALESCE(wBase.wait_time_ms,0)) / 1000) AS NVARCHAR(100)) + ' seconds over the last ' + CASE @Seconds WHEN 0 THEN (CAST(DATEDIFF(dd,@StartSampleTime,@FinishSampleTime) AS NVARCHAR(10)) + ' days') ELSE (CAST(@Seconds AS NVARCHAR(10)) + ' seconds') END + ', SQL Server was waiting on this particular bottleneck.' + @LineFeed + @LineFeed AS Details, 'See the URL for more details on how to mitigate this wait type.' AS HowToStopIt, ((wNow.wait_time_ms - COALESCE(wBase.wait_time_ms,0)) / 1000) AS DetailsInt @@ -35760,7 +35808,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 50 AS Priority, 'Server Performance' AS FindingGroup, 'Slow Data File Reads' AS Finding, - 'http://www.BrentOzar.com/go/slow/' AS URL, + 'https://www.brentozar.com/go/slow/' AS URL, 'Your server is experiencing PAGEIOLATCH% waits due to slow data file reads. This file is one of the reasons why.' + @LineFeed + 'File: ' + fNow.PhysicalName + @LineFeed + 'Number of reads during the sample: ' + CAST((fNow.num_of_reads - fBase.num_of_reads) AS NVARCHAR(20)) + @LineFeed @@ -35790,7 +35838,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 50 AS Priority, 'Server Performance' AS FindingGroup, 'Slow Log File Writes' AS Finding, - 'http://www.BrentOzar.com/go/slow/' AS URL, + 'https://www.brentozar.com/go/slow/' AS URL, 'Your server is experiencing WRITELOG waits due to slow log file writes. This file is one of the reasons why.' + @LineFeed + 'File: ' + fNow.PhysicalName + @LineFeed + 'Number of writes during the sample: ' + CAST((fNow.num_of_writes - fBase.num_of_writes) AS NVARCHAR(20)) + @LineFeed @@ -35819,7 +35867,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 1 AS Priority, 'SQL Server Internal Maintenance' AS FindingGroup, 'Log File Growing' AS Finding, - 'http://www.BrentOzar.com/askbrent/file-growing/' AS URL, + 'https://www.brentozar.com/askbrent/file-growing/' AS URL, 'Number of growths during the sample: ' + CAST(ps.value_delta AS NVARCHAR(20)) + @LineFeed + 'Determined by sampling Perfmon counter ' + ps.object_name + ' - ' + ps.counter_name + @LineFeed AS Details, 'Pre-grow data and log files during maintenance windows so that they do not grow during production loads. See the URL for more details.' AS HowToStopIt @@ -35841,7 +35889,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 1 AS Priority, 'SQL Server Internal Maintenance' AS FindingGroup, 'Log File Shrinking' AS Finding, - 'http://www.BrentOzar.com/askbrent/file-shrinking/' AS URL, + 'https://www.brentozar.com/askbrent/file-shrinking/' AS URL, 'Number of shrinks during the sample: ' + CAST(ps.value_delta AS NVARCHAR(20)) + @LineFeed + 'Determined by sampling Perfmon counter ' + ps.object_name + ' - ' + ps.counter_name + @LineFeed AS Details, 'Pre-grow data and log files during maintenance windows so that they do not grow during production loads. See the URL for more details.' AS HowToStopIt @@ -35862,7 +35910,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 50 AS Priority, 'Query Problems' AS FindingGroup, 'Compilations/Sec High' AS Finding, - 'http://www.BrentOzar.com/askbrent/compilations/' AS URL, + 'https://www.brentozar.com/askbrent/compilations/' AS URL, 'Number of batch requests during the sample: ' + CAST(ps.value_delta AS NVARCHAR(20)) + @LineFeed + 'Number of compilations during the sample: ' + CAST(psComp.value_delta AS NVARCHAR(20)) + @LineFeed + 'For OLTP environments, Microsoft recommends that 90% of batch requests should hit the plan cache, and not be compiled from scratch. We are exceeding that threshold.' + @LineFeed AS Details, @@ -35889,7 +35937,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 50 AS Priority, 'Query Problems' AS FindingGroup, 'Re-Compilations/Sec High' AS Finding, - 'http://www.BrentOzar.com/askbrent/recompilations/' AS URL, + 'https://www.brentozar.com/askbrent/recompilations/' AS URL, 'Number of batch requests during the sample: ' + CAST(ps.value_delta AS NVARCHAR(20)) + @LineFeed + 'Number of recompilations during the sample: ' + CAST(psComp.value_delta AS NVARCHAR(20)) + @LineFeed + 'More than 10% of our queries are being recompiled. This is typically due to statistics changing on objects.' + @LineFeed AS Details, @@ -35916,7 +35964,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 40 AS Priority, 'Table Problems' AS FindingGroup, 'Forwarded Fetches/Sec High' AS Finding, - 'https://BrentOzar.com/go/fetch/' AS URL, + 'https://www.brentozar.com/go/fetch/' AS URL, CAST(ps.value_delta AS NVARCHAR(20)) + ' forwarded fetches (from SQLServer:Access Methods counter)' + @LineFeed + 'Check your heaps: they need to be rebuilt, or they need a clustered index applied.' + @LineFeed AS Details, 'Rebuild your heaps. If you use Ola Hallengren maintenance scripts, those do not rebuild heaps by default: https://www.brentozar.com/archive/2016/07/fix-forwarded-records/' AS HowToStopIt @@ -35937,7 +35985,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 40 AS Priority, ''Table Problems'' AS FindingGroup, ''Forwarded Fetches/Sec High: TempDB Object'' AS Finding, - ''https://BrentOzar.com/go/fetch/'' AS URL, + ''https://www.brentozar.com/go/fetch/'' AS URL, CAST(COALESCE(os.forwarded_fetch_count,0) - COALESCE(os_prior.forwarded_fetch_count,0) AS NVARCHAR(20)) + '' forwarded fetches on '' + CASE WHEN OBJECT_NAME(os.object_id) IS NULL THEN ''an unknown table '' WHEN LEN(OBJECT_NAME(os.object_id)) < 50 THEN ''a table variable, internal identifier '' + OBJECT_NAME(os.object_id) @@ -35965,7 +36013,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 50 AS Priority, 'In-Memory OLTP' AS FindingGroup, 'Garbage Collection in Progress' AS Finding, - 'https://BrentOzar.com/go/garbage/' AS URL, + 'https://www.brentozar.com/go/garbage/' AS URL, CAST(ps.value_delta AS NVARCHAR(50)) + ' rows processed (from SQL Server YYYY XTP Garbage Collection:Rows processed/sec counter)' + @LineFeed + 'This can happen due to memory pressure (causing In-Memory OLTP to shrink its footprint) or' + @LineFeed + 'due to transactional workloads that constantly insert/delete data.' AS Details, @@ -35988,7 +36036,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 100 AS Priority, 'In-Memory OLTP' AS FindingGroup, 'Transactions Aborted' AS Finding, - 'https://BrentOzar.com/go/aborted/' AS URL, + 'https://www.brentozar.com/go/aborted/' AS URL, CAST(ps.value_delta AS NVARCHAR(50)) + ' transactions aborted (from SQL Server YYYY XTP Transactions:Transactions aborted/sec counter)' + @LineFeed + 'This may indicate that data is changing, or causing folks to retry their transactions, thereby increasing load.' AS Details, 'Dig into your In-Memory OLTP transactions to figure out which ones are failing and being retried.' AS HowToStopIt @@ -36010,7 +36058,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 100 AS Priority, 'Query Problems' AS FindingGroup, 'Suboptimal Plans/Sec High' AS Finding, - 'https://BrentOzar.com/go/suboptimal/' AS URL, + 'https://www.brentozar.com/go/suboptimal/' AS URL, CAST(ps.value_delta AS NVARCHAR(50)) + ' plans reported in the ' + CAST(ps.instance_name AS NVARCHAR(100)) + ' workload group (from Workload GroupStats:Suboptimal plans/sec counter)' + @LineFeed + 'Even if you are not using Resource Governor, it still tracks information about user queries, memory grants, etc.' AS Details, 'Check out sp_BlitzCache to get more information about recent queries, or try sp_BlitzWho to see currently running queries.' AS HowToStopIt @@ -36034,7 +36082,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 10 AS Priority, 'Azure Performance' AS FindingGroup, 'Database is Maxed Out' AS Finding, - 'https://BrentOzar.com/go/maxedout' AS URL, + 'https://www.brentozar.com/go/maxedout' AS URL, N'At ' + CONVERT(NVARCHAR(100), s.end_time ,121) + N', your database approached (or hit) your DTU limits:' + @LineFeed + N'Average CPU percent: ' + CAST(avg_cpu_percent AS NVARCHAR(50)) + @LineFeed + N'Average data IO percent: ' + CAST(avg_data_io_percent AS NVARCHAR(50)) + @LineFeed @@ -36062,7 +36110,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 250 AS Priority, 'Server Info' AS FindingGroup, 'Batch Requests per Sec' AS Finding, - 'http://www.BrentOzar.com/go/measure' AS URL, + 'https://www.brentozar.com/go/measure' AS URL, CAST(CAST(ps.value_delta AS MONEY) / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS NVARCHAR(20)) AS Details, ps.value_delta / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS DetailsInt FROM #PerfmonStats ps @@ -36088,7 +36136,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 250 AS Priority, 'Server Info' AS FindingGroup, 'SQL Compilations per Sec' AS Finding, - 'http://www.BrentOzar.com/go/measure' AS URL, + 'https://www.brentozar.com/go/measure' AS URL, CAST(ps.value_delta / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS NVARCHAR(20)) AS Details, ps.value_delta / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS DetailsInt FROM #PerfmonStats ps @@ -36111,7 +36159,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 250 AS Priority, 'Server Info' AS FindingGroup, 'SQL Re-Compilations per Sec' AS Finding, - 'http://www.BrentOzar.com/go/measure' AS URL, + 'https://www.brentozar.com/go/measure' AS URL, CAST(ps.value_delta / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS NVARCHAR(20)) AS Details, ps.value_delta / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS DetailsInt FROM #PerfmonStats ps @@ -36137,7 +36185,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 250 AS Priority, 'Server Info' AS FindingGroup, 'Wait Time per Core per Sec' AS Finding, - 'http://www.BrentOzar.com/go/measure' AS URL, + 'https://www.brentozar.com/go/measure' AS URL, CAST((CAST(waits2.waits_ms - waits1.waits_ms AS MONEY)) / 1000 / i.cpu_count / DATEDIFF(ss, waits1.SampleTime, waits2.SampleTime) AS NVARCHAR(20)) AS Details, (waits2.waits_ms - waits1.waits_ms) / 1000 / i.cpu_count / DATEDIFF(ss, waits1.SampleTime, waits2.SampleTime) AS DetailsInt FROM cores i @@ -36203,7 +36251,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, END INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) - SELECT 24, 50, 'Server Performance', 'High CPU Utilization', CAST(100 - SystemIdle AS NVARCHAR(20)) + N'%. Ring buffer details: ' + CAST(record AS NVARCHAR(4000)), 100 - SystemIdle, 'http://www.BrentOzar.com/go/cpu' + SELECT 24, 50, 'Server Performance', 'High CPU Utilization', CAST(100 - SystemIdle AS NVARCHAR(20)) + N'%. Ring buffer details: ' + CAST(record AS NVARCHAR(4000)), 100 - SystemIdle, 'https://www.brentozar.com/go/cpu' FROM ( SELECT record, record.value('(./Record/SchedulerMonitorEvent/SystemHealth/SystemIdle)[1]', 'int') AS SystemIdle @@ -36223,7 +36271,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, END INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) - SELECT 23, 250, 'Server Info', 'CPU Utilization', CAST(100 - SystemIdle AS NVARCHAR(20)) + N'%. Ring buffer details: ' + CAST(record AS NVARCHAR(4000)), 100 - SystemIdle, 'http://www.BrentOzar.com/go/cpu' + SELECT 23, 250, 'Server Info', 'CPU Utilization', CAST(100 - SystemIdle AS NVARCHAR(20)) + N'%. Ring buffer details: ' + CAST(record AS NVARCHAR(4000)), 100 - SystemIdle, 'https://www.brentozar.com/go/cpu' FROM ( SELECT record, record.value('(./Record/SchedulerMonitorEvent/SystemHealth/SystemIdle)[1]', 'int') AS SystemIdle @@ -36362,15 +36410,34 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, IF @BlitzCacheMinutesBack IS NULL OR @BlitzCacheMinutesBack < 1 OR @BlitzCacheMinutesBack > 60 SET @BlitzCacheMinutesBack = 15; - EXEC sp_BlitzCache - @OutputDatabaseName = @UnquotedOutputDatabaseName, - @OutputSchemaName = @UnquotedOutputSchemaName, - @OutputTableName = @OutputTableNameBlitzCache, - @CheckDateOverride = @StartSampleTime, - @SortOrder = 'all', - @SkipAnalysis = @BlitzCacheSkipAnalysis, - @MinutesBack = @BlitzCacheMinutesBack, - @Debug = @Debug; + IF(@OutputType = 'NONE') + BEGIN + EXEC sp_BlitzCache + @OutputDatabaseName = @UnquotedOutputDatabaseName, + @OutputSchemaName = @UnquotedOutputSchemaName, + @OutputTableName = @OutputTableNameBlitzCache, + @CheckDateOverride = @StartSampleTime, + @SortOrder = 'all', + @SkipAnalysis = @BlitzCacheSkipAnalysis, + @MinutesBack = @BlitzCacheMinutesBack, + @Debug = @Debug, + @OutputType = @OutputType + ; + END; + ELSE + BEGIN + + EXEC sp_BlitzCache + @OutputDatabaseName = @UnquotedOutputDatabaseName, + @OutputSchemaName = @UnquotedOutputSchemaName, + @OutputTableName = @OutputTableNameBlitzCache, + @CheckDateOverride = @StartSampleTime, + @SortOrder = 'all', + @SkipAnalysis = @BlitzCacheSkipAnalysis, + @MinutesBack = @BlitzCacheMinutesBack, + @Debug = @Debug + ; + END; /* Delete history older than @OutputTableRetentionDays */ SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' diff --git a/SqlServerVersions.sql b/SqlServerVersions.sql index dc20dd5fe..29450ac60 100644 --- a/SqlServerVersions.sql +++ b/SqlServerVersions.sql @@ -53,6 +53,7 @@ VALUES (15, 4003, 'CU1', 'https://support.microsoft.com/en-us/help/4527376', '2020-01-07', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 1 '), (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 '), + (14, 3356, 'RTM CU23', 'https://support.microsoft.com/en-us/help/5000685', '2021-02-25', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 23'), (14, 3370, 'RTM CU22 GDR', 'https://support.microsoft.com/en-us/help/4583457', '2021-01-12', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 22 GDR'), (14, 3356, 'RTM CU22', 'https://support.microsoft.com/en-us/help/4577467', '2020-09-10', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 22'), (14, 3335, 'RTM CU21', 'https://support.microsoft.com/en-us/help/4557397', '2020-07-01', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 21'), diff --git a/sp_AllNightLog.sql b/sp_AllNightLog.sql index 7e142e1f7..fe20fe425 100644 --- a/sp_AllNightLog.sql +++ b/sp_AllNightLog.sql @@ -30,7 +30,7 @@ SET NOCOUNT ON; BEGIN; -SELECT @Version = '8.01', @VersionDate = '20210222'; +SELECT @Version = '8.02', @VersionDate = '20210321'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_AllNightLog_Setup.sql b/sp_AllNightLog_Setup.sql index 1d7faa8b3..d65e49002 100644 --- a/sp_AllNightLog_Setup.sql +++ b/sp_AllNightLog_Setup.sql @@ -36,7 +36,7 @@ SET NOCOUNT ON; BEGIN; -SELECT @Version = '8.01', @VersionDate = '20210222'; +SELECT @Version = '8.02', @VersionDate = '20210321'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_Blitz.sql b/sp_Blitz.sql index 7be4605be..3adef6a30 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -37,7 +37,7 @@ AS SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.01', @VersionDate = '20210222'; + SELECT @Version = '8.02', @VersionDate = '20210321'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) diff --git a/sp_BlitzAnalysis.sql b/sp_BlitzAnalysis.sql index 9cda62a84..1e2577a1a 100644 --- a/sp_BlitzAnalysis.sql +++ b/sp_BlitzAnalysis.sql @@ -36,7 +36,7 @@ ALTER PROCEDURE [dbo].[sp_BlitzAnalysis] ( AS SET NOCOUNT ON; -SELECT @Version = '8.01', @VersionDate = '20210222'; +SELECT @Version = '8.02', @VersionDate = '20210321'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_BlitzBackups.sql b/sp_BlitzBackups.sql index abe5f436a..e3ef6b425 100755 --- a/sp_BlitzBackups.sql +++ b/sp_BlitzBackups.sql @@ -23,7 +23,7 @@ AS SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.01', @VersionDate = '20210222'; + SELECT @Version = '8.02', @VersionDate = '20210321'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_BlitzCache.sql b/sp_BlitzCache.sql index e71a20f48..fd4ef5be3 100644 --- a/sp_BlitzCache.sql +++ b/sp_BlitzCache.sql @@ -279,7 +279,7 @@ BEGIN SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.01', @VersionDate = '20210222'; +SELECT @Version = '8.02', @VersionDate = '20210321'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) diff --git a/sp_BlitzFirst.sql b/sp_BlitzFirst.sql index 5251fdf36..060b9a01c 100644 --- a/sp_BlitzFirst.sql +++ b/sp_BlitzFirst.sql @@ -45,7 +45,7 @@ BEGIN SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.01', @VersionDate = '20210222'; +SELECT @Version = '8.02', @VersionDate = '20210321'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_BlitzInMemoryOLTP.sql b/sp_BlitzInMemoryOLTP.sql index e08427faf..bd83fe4d0 100644 --- a/sp_BlitzInMemoryOLTP.sql +++ b/sp_BlitzInMemoryOLTP.sql @@ -82,7 +82,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI */ AS DECLARE @ScriptVersion VARCHAR(30); -SELECT @ScriptVersion = '1.8', @VersionDate = '20210222'; +SELECT @ScriptVersion = '1.8', @VersionDate = '20210321'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index 1ebb94005..a0f544d62 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -45,7 +45,7 @@ AS SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.01', @VersionDate = '20210222'; +SELECT @Version = '8.02', @VersionDate = '20210321'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index f5013bd84..1a482f346 100644 --- a/sp_BlitzLock.sql +++ b/sp_BlitzLock.sql @@ -32,7 +32,7 @@ BEGIN SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.01', @VersionDate = '20210222'; +SELECT @Version = '8.02', @VersionDate = '20210321'; IF(@VersionCheckMode = 1) diff --git a/sp_BlitzQueryStore.sql b/sp_BlitzQueryStore.sql index 5e840579c..83ad14344 100644 --- a/sp_BlitzQueryStore.sql +++ b/sp_BlitzQueryStore.sql @@ -56,7 +56,7 @@ BEGIN /*First BEGIN*/ SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.01', @VersionDate = '20210222'; +SELECT @Version = '8.02', @VersionDate = '20210321'; IF(@VersionCheckMode = 1) BEGIN RETURN; diff --git a/sp_BlitzWho.sql b/sp_BlitzWho.sql index c93c6c834..c368f4f2b 100644 --- a/sp_BlitzWho.sql +++ b/sp_BlitzWho.sql @@ -29,7 +29,7 @@ BEGIN SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.01', @VersionDate = '20210222'; + SELECT @Version = '8.02', @VersionDate = '20210321'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_DatabaseRestore.sql b/sp_DatabaseRestore.sql index 3c53eb6e3..76dff16ed 100755 --- a/sp_DatabaseRestore.sql +++ b/sp_DatabaseRestore.sql @@ -40,7 +40,7 @@ SET NOCOUNT ON; /*Versioning details*/ -SELECT @Version = '8.01', @VersionDate = '20210222'; +SELECT @Version = '8.02', @VersionDate = '20210321'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_ineachdb.sql b/sp_ineachdb.sql index 555ee9b80..9253e595f 100644 --- a/sp_ineachdb.sql +++ b/sp_ineachdb.sql @@ -34,7 +34,7 @@ AS BEGIN SET NOCOUNT ON; - SELECT @Version = '8.01', @VersionDate = '20210222'; + SELECT @Version = '8.02', @VersionDate = '20210321'; IF(@VersionCheckMode = 1) BEGIN From ebb982917144ee75d4c6b3e05b43d0eb6c6480ab Mon Sep 17 00:00:00 2001 From: Erik Darling Date: Sun, 21 Mar 2021 10:01:03 -0400 Subject: [PATCH 141/662] Closes #2831 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit dn is back! You uh, don't need to list that I fixed the bug I introduced in the liner notes. I'm fine with this one going uncredited 😃 --- sp_BlitzLock.sql | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index f5013bd84..cddf4e015 100644 --- a/sp_BlitzLock.sql +++ b/sp_BlitzLock.sql @@ -1315,6 +1315,7 @@ You need to use an Azure storage account, and the path has to look like this: ht dp.process_xml.value('(//process/inputbuf/text())[1]', 'NVARCHAR(MAX)') AS inputbuf, DENSE_RANK() OVER ( ORDER BY dp.event_date ) AS en, ROW_NUMBER() OVER ( PARTITION BY dp.event_date ORDER BY dp.event_date ) -1 AS qn, + ROW_NUMBER() OVER ( PARTITION BY dp.event_date, dp.id ORDER BY dp.event_date ) AS dn, dp.is_victim, ISNULL(dp.owner_mode, '-') AS owner_mode, NULL AS owner_waiter_type, @@ -1371,6 +1372,7 @@ You need to use an Azure storage account, and the path has to look like this: ht dp.process_xml.value('(//process/inputbuf/text())[1]', 'NVARCHAR(MAX)') AS inputbuf, DENSE_RANK() OVER ( ORDER BY dp.event_date ) AS en, ROW_NUMBER() OVER ( PARTITION BY dp.event_date ORDER BY dp.event_date ) -1 AS qn, + ROW_NUMBER() OVER ( PARTITION BY dp.event_date, dp.id ORDER BY dp.event_date ) AS dn, 1 AS is_victim, cao.wait_type COLLATE DATABASE_DEFAULT AS owner_mode, cao.waiter_type AS owner_waiter_type, @@ -1536,6 +1538,7 @@ ELSE --Output to database is not set output to client app dp.process_xml.value('(//process/inputbuf/text())[1]', 'NVARCHAR(MAX)') AS inputbuf, DENSE_RANK() OVER ( ORDER BY dp.event_date ) AS en, ROW_NUMBER() OVER ( PARTITION BY dp.event_date ORDER BY dp.event_date ) -1 AS qn, + ROW_NUMBER() OVER ( PARTITION BY dp.event_date, dp.id ORDER BY dp.event_date ) AS dn, dp.is_victim, ISNULL(dp.owner_mode, '-') AS owner_mode, NULL AS owner_waiter_type, @@ -1592,6 +1595,7 @@ ELSE --Output to database is not set output to client app dp.process_xml.value('(//process/inputbuf/text())[1]', 'NVARCHAR(MAX)') AS inputbuf, DENSE_RANK() OVER ( ORDER BY dp.event_date ) AS en, ROW_NUMBER() OVER ( PARTITION BY dp.event_date ORDER BY dp.event_date ) -1 AS qn, + ROW_NUMBER() OVER ( PARTITION BY dp.event_date, dp.id ORDER BY dp.event_date ) AS dn, 1 AS is_victim, cao.wait_type COLLATE DATABASE_DEFAULT AS owner_mode, cao.waiter_type AS owner_waiter_type, From ee9c2f116d492cf07948dd538d639954ec5404e7 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Sun, 21 Mar 2021 22:04:47 -0700 Subject: [PATCH 142/662] 2021-03 installer scripts Take 2. --- Install-All-Scripts.sql | 28 ++++++++++++++----------- Install-Core-Blitz-No-Query-Store.sql | 18 +++++++++------- Install-Core-Blitz-With-Query-Store.sql | 20 +++++++++++------- sp_AllNightLog.sql | 2 +- sp_AllNightLog_Setup.sql | 2 +- sp_Blitz.sql | 2 +- sp_BlitzAnalysis.sql | 2 +- sp_BlitzBackups.sql | 2 +- sp_BlitzCache.sql | 2 +- sp_BlitzFirst.sql | 2 +- sp_BlitzInMemoryOLTP.sql | 2 +- sp_BlitzIndex.sql | 2 +- sp_BlitzLock.sql | 2 +- sp_BlitzQueryStore.sql | 2 +- sp_BlitzWho.sql | 2 +- sp_DatabaseRestore.sql | 2 +- sp_ineachdb.sql | 2 +- 17 files changed, 53 insertions(+), 41 deletions(-) diff --git a/Install-All-Scripts.sql b/Install-All-Scripts.sql index e1e4a7124..0dd92683b 100755 --- a/Install-All-Scripts.sql +++ b/Install-All-Scripts.sql @@ -30,7 +30,7 @@ SET NOCOUNT ON; BEGIN; -SELECT @Version = '8.02', @VersionDate = '20210321'; +SELECT @Version = '8.02', @VersionDate = '20210322'; IF(@VersionCheckMode = 1) BEGIN @@ -1524,7 +1524,7 @@ SET NOCOUNT ON; BEGIN; -SELECT @Version = '8.02', @VersionDate = '20210321'; +SELECT @Version = '8.02', @VersionDate = '20210322'; IF(@VersionCheckMode = 1) BEGIN @@ -2856,7 +2856,7 @@ AS SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.02', @VersionDate = '20210321'; + SELECT @Version = '8.02', @VersionDate = '20210322'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -12238,7 +12238,7 @@ AS SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.02', @VersionDate = '20210321'; + SELECT @Version = '8.02', @VersionDate = '20210322'; IF(@VersionCheckMode = 1) BEGIN @@ -14018,7 +14018,7 @@ BEGIN SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.02', @VersionDate = '20210321'; +SELECT @Version = '8.02', @VersionDate = '20210322'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -21222,7 +21222,7 @@ AS SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.02', @VersionDate = '20210321'; +SELECT @Version = '8.02', @VersionDate = '20210322'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -26697,7 +26697,7 @@ BEGIN SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.02', @VersionDate = '20210321'; +SELECT @Version = '8.02', @VersionDate = '20210322'; IF(@VersionCheckMode = 1) @@ -27980,6 +27980,7 @@ You need to use an Azure storage account, and the path has to look like this: ht dp.process_xml.value('(//process/inputbuf/text())[1]', 'NVARCHAR(MAX)') AS inputbuf, DENSE_RANK() OVER ( ORDER BY dp.event_date ) AS en, ROW_NUMBER() OVER ( PARTITION BY dp.event_date ORDER BY dp.event_date ) -1 AS qn, + ROW_NUMBER() OVER ( PARTITION BY dp.event_date, dp.id ORDER BY dp.event_date ) AS dn, dp.is_victim, ISNULL(dp.owner_mode, '-') AS owner_mode, NULL AS owner_waiter_type, @@ -28036,6 +28037,7 @@ You need to use an Azure storage account, and the path has to look like this: ht dp.process_xml.value('(//process/inputbuf/text())[1]', 'NVARCHAR(MAX)') AS inputbuf, DENSE_RANK() OVER ( ORDER BY dp.event_date ) AS en, ROW_NUMBER() OVER ( PARTITION BY dp.event_date ORDER BY dp.event_date ) -1 AS qn, + ROW_NUMBER() OVER ( PARTITION BY dp.event_date, dp.id ORDER BY dp.event_date ) AS dn, 1 AS is_victim, cao.wait_type COLLATE DATABASE_DEFAULT AS owner_mode, cao.waiter_type AS owner_waiter_type, @@ -28201,6 +28203,7 @@ ELSE --Output to database is not set output to client app dp.process_xml.value('(//process/inputbuf/text())[1]', 'NVARCHAR(MAX)') AS inputbuf, DENSE_RANK() OVER ( ORDER BY dp.event_date ) AS en, ROW_NUMBER() OVER ( PARTITION BY dp.event_date ORDER BY dp.event_date ) -1 AS qn, + ROW_NUMBER() OVER ( PARTITION BY dp.event_date, dp.id ORDER BY dp.event_date ) AS dn, dp.is_victim, ISNULL(dp.owner_mode, '-') AS owner_mode, NULL AS owner_waiter_type, @@ -28257,6 +28260,7 @@ ELSE --Output to database is not set output to client app dp.process_xml.value('(//process/inputbuf/text())[1]', 'NVARCHAR(MAX)') AS inputbuf, DENSE_RANK() OVER ( ORDER BY dp.event_date ) AS en, ROW_NUMBER() OVER ( PARTITION BY dp.event_date ORDER BY dp.event_date ) -1 AS qn, + ROW_NUMBER() OVER ( PARTITION BY dp.event_date, dp.id ORDER BY dp.event_date ) AS dn, 1 AS is_victim, cao.wait_type COLLATE DATABASE_DEFAULT AS owner_mode, cao.waiter_type AS owner_waiter_type, @@ -28499,7 +28503,7 @@ BEGIN /*First BEGIN*/ SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.02', @VersionDate = '20210321'; +SELECT @Version = '8.02', @VersionDate = '20210322'; IF(@VersionCheckMode = 1) BEGIN RETURN; @@ -34226,7 +34230,7 @@ BEGIN SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.02', @VersionDate = '20210321'; + SELECT @Version = '8.02', @VersionDate = '20210322'; IF(@VersionCheckMode = 1) BEGIN @@ -35466,7 +35470,7 @@ SET NOCOUNT ON; /*Versioning details*/ -SELECT @Version = '8.02', @VersionDate = '20210321'; +SELECT @Version = '8.02', @VersionDate = '20210322'; IF(@VersionCheckMode = 1) BEGIN @@ -36952,7 +36956,7 @@ AS BEGIN SET NOCOUNT ON; - SELECT @Version = '8.02', @VersionDate = '20210321'; + SELECT @Version = '8.02', @VersionDate = '20210322'; IF(@VersionCheckMode = 1) BEGIN @@ -37686,7 +37690,7 @@ BEGIN SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.02', @VersionDate = '20210321'; +SELECT @Version = '8.02', @VersionDate = '20210322'; IF(@VersionCheckMode = 1) BEGIN diff --git a/Install-Core-Blitz-No-Query-Store.sql b/Install-Core-Blitz-No-Query-Store.sql index b07af56a8..61725a9f0 100755 --- a/Install-Core-Blitz-No-Query-Store.sql +++ b/Install-Core-Blitz-No-Query-Store.sql @@ -37,7 +37,7 @@ AS SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.02', @VersionDate = '20210321'; + SELECT @Version = '8.02', @VersionDate = '20210322'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -9419,7 +9419,7 @@ AS SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.02', @VersionDate = '20210321'; + SELECT @Version = '8.02', @VersionDate = '20210322'; IF(@VersionCheckMode = 1) BEGIN @@ -11199,7 +11199,7 @@ BEGIN SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.02', @VersionDate = '20210321'; +SELECT @Version = '8.02', @VersionDate = '20210322'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -18403,7 +18403,7 @@ AS SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.02', @VersionDate = '20210321'; +SELECT @Version = '8.02', @VersionDate = '20210322'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -23878,7 +23878,7 @@ BEGIN SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.02', @VersionDate = '20210321'; +SELECT @Version = '8.02', @VersionDate = '20210322'; IF(@VersionCheckMode = 1) @@ -25161,6 +25161,7 @@ You need to use an Azure storage account, and the path has to look like this: ht dp.process_xml.value('(//process/inputbuf/text())[1]', 'NVARCHAR(MAX)') AS inputbuf, DENSE_RANK() OVER ( ORDER BY dp.event_date ) AS en, ROW_NUMBER() OVER ( PARTITION BY dp.event_date ORDER BY dp.event_date ) -1 AS qn, + ROW_NUMBER() OVER ( PARTITION BY dp.event_date, dp.id ORDER BY dp.event_date ) AS dn, dp.is_victim, ISNULL(dp.owner_mode, '-') AS owner_mode, NULL AS owner_waiter_type, @@ -25217,6 +25218,7 @@ You need to use an Azure storage account, and the path has to look like this: ht dp.process_xml.value('(//process/inputbuf/text())[1]', 'NVARCHAR(MAX)') AS inputbuf, DENSE_RANK() OVER ( ORDER BY dp.event_date ) AS en, ROW_NUMBER() OVER ( PARTITION BY dp.event_date ORDER BY dp.event_date ) -1 AS qn, + ROW_NUMBER() OVER ( PARTITION BY dp.event_date, dp.id ORDER BY dp.event_date ) AS dn, 1 AS is_victim, cao.wait_type COLLATE DATABASE_DEFAULT AS owner_mode, cao.waiter_type AS owner_waiter_type, @@ -25382,6 +25384,7 @@ ELSE --Output to database is not set output to client app dp.process_xml.value('(//process/inputbuf/text())[1]', 'NVARCHAR(MAX)') AS inputbuf, DENSE_RANK() OVER ( ORDER BY dp.event_date ) AS en, ROW_NUMBER() OVER ( PARTITION BY dp.event_date ORDER BY dp.event_date ) -1 AS qn, + ROW_NUMBER() OVER ( PARTITION BY dp.event_date, dp.id ORDER BY dp.event_date ) AS dn, dp.is_victim, ISNULL(dp.owner_mode, '-') AS owner_mode, NULL AS owner_waiter_type, @@ -25438,6 +25441,7 @@ ELSE --Output to database is not set output to client app dp.process_xml.value('(//process/inputbuf/text())[1]', 'NVARCHAR(MAX)') AS inputbuf, DENSE_RANK() OVER ( ORDER BY dp.event_date ) AS en, ROW_NUMBER() OVER ( PARTITION BY dp.event_date ORDER BY dp.event_date ) -1 AS qn, + ROW_NUMBER() OVER ( PARTITION BY dp.event_date, dp.id ORDER BY dp.event_date ) AS dn, 1 AS is_victim, cao.wait_type COLLATE DATABASE_DEFAULT AS owner_mode, cao.waiter_type AS owner_waiter_type, @@ -25653,7 +25657,7 @@ BEGIN SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.02', @VersionDate = '20210321'; + SELECT @Version = '8.02', @VersionDate = '20210322'; IF(@VersionCheckMode = 1) BEGIN @@ -27282,7 +27286,7 @@ BEGIN SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.02', @VersionDate = '20210321'; +SELECT @Version = '8.02', @VersionDate = '20210322'; IF(@VersionCheckMode = 1) BEGIN diff --git a/Install-Core-Blitz-With-Query-Store.sql b/Install-Core-Blitz-With-Query-Store.sql index 82ce6d501..ee68e740b 100755 --- a/Install-Core-Blitz-With-Query-Store.sql +++ b/Install-Core-Blitz-With-Query-Store.sql @@ -37,7 +37,7 @@ AS SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.02', @VersionDate = '20210321'; + SELECT @Version = '8.02', @VersionDate = '20210322'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -9419,7 +9419,7 @@ AS SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.02', @VersionDate = '20210321'; + SELECT @Version = '8.02', @VersionDate = '20210322'; IF(@VersionCheckMode = 1) BEGIN @@ -11199,7 +11199,7 @@ BEGIN SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.02', @VersionDate = '20210321'; +SELECT @Version = '8.02', @VersionDate = '20210322'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -18403,7 +18403,7 @@ AS SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.02', @VersionDate = '20210321'; +SELECT @Version = '8.02', @VersionDate = '20210322'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -23878,7 +23878,7 @@ BEGIN SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.02', @VersionDate = '20210321'; +SELECT @Version = '8.02', @VersionDate = '20210322'; IF(@VersionCheckMode = 1) @@ -25161,6 +25161,7 @@ You need to use an Azure storage account, and the path has to look like this: ht dp.process_xml.value('(//process/inputbuf/text())[1]', 'NVARCHAR(MAX)') AS inputbuf, DENSE_RANK() OVER ( ORDER BY dp.event_date ) AS en, ROW_NUMBER() OVER ( PARTITION BY dp.event_date ORDER BY dp.event_date ) -1 AS qn, + ROW_NUMBER() OVER ( PARTITION BY dp.event_date, dp.id ORDER BY dp.event_date ) AS dn, dp.is_victim, ISNULL(dp.owner_mode, '-') AS owner_mode, NULL AS owner_waiter_type, @@ -25217,6 +25218,7 @@ You need to use an Azure storage account, and the path has to look like this: ht dp.process_xml.value('(//process/inputbuf/text())[1]', 'NVARCHAR(MAX)') AS inputbuf, DENSE_RANK() OVER ( ORDER BY dp.event_date ) AS en, ROW_NUMBER() OVER ( PARTITION BY dp.event_date ORDER BY dp.event_date ) -1 AS qn, + ROW_NUMBER() OVER ( PARTITION BY dp.event_date, dp.id ORDER BY dp.event_date ) AS dn, 1 AS is_victim, cao.wait_type COLLATE DATABASE_DEFAULT AS owner_mode, cao.waiter_type AS owner_waiter_type, @@ -25382,6 +25384,7 @@ ELSE --Output to database is not set output to client app dp.process_xml.value('(//process/inputbuf/text())[1]', 'NVARCHAR(MAX)') AS inputbuf, DENSE_RANK() OVER ( ORDER BY dp.event_date ) AS en, ROW_NUMBER() OVER ( PARTITION BY dp.event_date ORDER BY dp.event_date ) -1 AS qn, + ROW_NUMBER() OVER ( PARTITION BY dp.event_date, dp.id ORDER BY dp.event_date ) AS dn, dp.is_victim, ISNULL(dp.owner_mode, '-') AS owner_mode, NULL AS owner_waiter_type, @@ -25438,6 +25441,7 @@ ELSE --Output to database is not set output to client app dp.process_xml.value('(//process/inputbuf/text())[1]', 'NVARCHAR(MAX)') AS inputbuf, DENSE_RANK() OVER ( ORDER BY dp.event_date ) AS en, ROW_NUMBER() OVER ( PARTITION BY dp.event_date ORDER BY dp.event_date ) -1 AS qn, + ROW_NUMBER() OVER ( PARTITION BY dp.event_date, dp.id ORDER BY dp.event_date ) AS dn, 1 AS is_victim, cao.wait_type COLLATE DATABASE_DEFAULT AS owner_mode, cao.waiter_type AS owner_waiter_type, @@ -25680,7 +25684,7 @@ BEGIN /*First BEGIN*/ SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.02', @VersionDate = '20210321'; +SELECT @Version = '8.02', @VersionDate = '20210322'; IF(@VersionCheckMode = 1) BEGIN RETURN; @@ -31407,7 +31411,7 @@ BEGIN SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.02', @VersionDate = '20210321'; + SELECT @Version = '8.02', @VersionDate = '20210322'; IF(@VersionCheckMode = 1) BEGIN @@ -33036,7 +33040,7 @@ BEGIN SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.02', @VersionDate = '20210321'; +SELECT @Version = '8.02', @VersionDate = '20210322'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_AllNightLog.sql b/sp_AllNightLog.sql index fe20fe425..22b93309f 100644 --- a/sp_AllNightLog.sql +++ b/sp_AllNightLog.sql @@ -30,7 +30,7 @@ SET NOCOUNT ON; BEGIN; -SELECT @Version = '8.02', @VersionDate = '20210321'; +SELECT @Version = '8.02', @VersionDate = '20210322'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_AllNightLog_Setup.sql b/sp_AllNightLog_Setup.sql index d65e49002..617ec7614 100644 --- a/sp_AllNightLog_Setup.sql +++ b/sp_AllNightLog_Setup.sql @@ -36,7 +36,7 @@ SET NOCOUNT ON; BEGIN; -SELECT @Version = '8.02', @VersionDate = '20210321'; +SELECT @Version = '8.02', @VersionDate = '20210322'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_Blitz.sql b/sp_Blitz.sql index 3adef6a30..69b71e680 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -37,7 +37,7 @@ AS SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.02', @VersionDate = '20210321'; + SELECT @Version = '8.02', @VersionDate = '20210322'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) diff --git a/sp_BlitzAnalysis.sql b/sp_BlitzAnalysis.sql index 1e2577a1a..472ad1646 100644 --- a/sp_BlitzAnalysis.sql +++ b/sp_BlitzAnalysis.sql @@ -36,7 +36,7 @@ ALTER PROCEDURE [dbo].[sp_BlitzAnalysis] ( AS SET NOCOUNT ON; -SELECT @Version = '8.02', @VersionDate = '20210321'; +SELECT @Version = '8.02', @VersionDate = '20210322'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_BlitzBackups.sql b/sp_BlitzBackups.sql index e3ef6b425..a86a0207b 100755 --- a/sp_BlitzBackups.sql +++ b/sp_BlitzBackups.sql @@ -23,7 +23,7 @@ AS SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.02', @VersionDate = '20210321'; + SELECT @Version = '8.02', @VersionDate = '20210322'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_BlitzCache.sql b/sp_BlitzCache.sql index fd4ef5be3..f3711970c 100644 --- a/sp_BlitzCache.sql +++ b/sp_BlitzCache.sql @@ -279,7 +279,7 @@ BEGIN SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.02', @VersionDate = '20210321'; +SELECT @Version = '8.02', @VersionDate = '20210322'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) diff --git a/sp_BlitzFirst.sql b/sp_BlitzFirst.sql index 060b9a01c..63db7e38b 100644 --- a/sp_BlitzFirst.sql +++ b/sp_BlitzFirst.sql @@ -45,7 +45,7 @@ BEGIN SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.02', @VersionDate = '20210321'; +SELECT @Version = '8.02', @VersionDate = '20210322'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_BlitzInMemoryOLTP.sql b/sp_BlitzInMemoryOLTP.sql index bd83fe4d0..59d3658af 100644 --- a/sp_BlitzInMemoryOLTP.sql +++ b/sp_BlitzInMemoryOLTP.sql @@ -82,7 +82,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI */ AS DECLARE @ScriptVersion VARCHAR(30); -SELECT @ScriptVersion = '1.8', @VersionDate = '20210321'; +SELECT @ScriptVersion = '1.8', @VersionDate = '20210322'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index a0f544d62..d06808542 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -45,7 +45,7 @@ AS SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.02', @VersionDate = '20210321'; +SELECT @Version = '8.02', @VersionDate = '20210322'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index 2bbe72986..f5185e0f9 100644 --- a/sp_BlitzLock.sql +++ b/sp_BlitzLock.sql @@ -32,7 +32,7 @@ BEGIN SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.02', @VersionDate = '20210321'; +SELECT @Version = '8.02', @VersionDate = '20210322'; IF(@VersionCheckMode = 1) diff --git a/sp_BlitzQueryStore.sql b/sp_BlitzQueryStore.sql index 83ad14344..90af487a9 100644 --- a/sp_BlitzQueryStore.sql +++ b/sp_BlitzQueryStore.sql @@ -56,7 +56,7 @@ BEGIN /*First BEGIN*/ SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.02', @VersionDate = '20210321'; +SELECT @Version = '8.02', @VersionDate = '20210322'; IF(@VersionCheckMode = 1) BEGIN RETURN; diff --git a/sp_BlitzWho.sql b/sp_BlitzWho.sql index c368f4f2b..9f5b07909 100644 --- a/sp_BlitzWho.sql +++ b/sp_BlitzWho.sql @@ -29,7 +29,7 @@ BEGIN SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.02', @VersionDate = '20210321'; + SELECT @Version = '8.02', @VersionDate = '20210322'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_DatabaseRestore.sql b/sp_DatabaseRestore.sql index 76dff16ed..22fd897a6 100755 --- a/sp_DatabaseRestore.sql +++ b/sp_DatabaseRestore.sql @@ -40,7 +40,7 @@ SET NOCOUNT ON; /*Versioning details*/ -SELECT @Version = '8.02', @VersionDate = '20210321'; +SELECT @Version = '8.02', @VersionDate = '20210322'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_ineachdb.sql b/sp_ineachdb.sql index 9253e595f..8e4b65860 100644 --- a/sp_ineachdb.sql +++ b/sp_ineachdb.sql @@ -34,7 +34,7 @@ AS BEGIN SET NOCOUNT ON; - SELECT @Version = '8.02', @VersionDate = '20210321'; + SELECT @Version = '8.02', @VersionDate = '20210322'; IF(@VersionCheckMode = 1) BEGIN From 4a72994628d1c66025813acb67361f78fb727fc8 Mon Sep 17 00:00:00 2001 From: Greg Dodd Date: Tue, 23 Mar 2021 16:22:24 -0400 Subject: [PATCH 143/662] Update sp_BlitzWho.sql Only show Live Parameter Info when ShowActualParameters = 1 --- sp_BlitzWho.sql | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/sp_BlitzWho.sql b/sp_BlitzWho.sql index 5545d1a1b..4758102d6 100644 --- a/sp_BlitzWho.sql +++ b/sp_BlitzWho.sql @@ -20,6 +20,7 @@ ALTER PROCEDURE dbo.sp_BlitzWho @MinRequestedMemoryKB INT = 0 , @MinBlockingSeconds INT = 0 , @CheckDateOverride DATETIMEOFFSET = NULL, + @ShowActualParameters BIT = 0, @Version VARCHAR(30) = NULL OUTPUT, @VersionDate DATETIME = NULL OUTPUT, @VersionCheckMode BIT = 0, @@ -813,7 +814,13 @@ IF @ProductVersionMajor >= 11 FROM derp.query_plan.nodes(''/*:ShowPlanXML/*:BatchSequence/*:Batch/*:Statements/*:StmtSimple/*:QueryPlan/*:ParameterList/*:ColumnReference'') AS Node(Data) FOR XML PATH('''')), 1,2,'''') AS Cached_Parameter_Info, - qs_live.Live_Parameter_Info as Live_Parameter_Info, + ' + IF @ShowActualParameters = 1 + BEGIN + SELECT @StringToExecute = @StringToExecute + N'qs_live.Live_Parameter_Info as Live_Parameter_Info,' + END + + SELECT @StringToExecute = @StringToExecute + N' qmg.query_cost , s.status , CASE @@ -1135,7 +1142,7 @@ IF @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @Output ,[query_plan]' + CASE WHEN @ProductVersionMajor >= 11 THEN N',[live_query_plan]' ELSE N'' END + N' ,[Cached_Parameter_Info]' - + CASE WHEN @ProductVersionMajor >= 11 THEN N',[live_parameter_info]' ELSE N'' END + N' + + CASE WHEN @ProductVersionMajor >= 11 AND @ShowActualParameters = 1 THEN N',[live_parameter_info]' ELSE N'' END + N' ,[query_cost] ,[status] ,[wait_info]' From 15e7f69946cce71c1e6535a827dfc950f1464026 Mon Sep 17 00:00:00 2001 From: Greg Dodd Date: Tue, 23 Mar 2021 16:49:33 -0400 Subject: [PATCH 144/662] Update sp_BlitzWho.sql Remove variables that are no longer used Add new fields to output table --- sp_BlitzWho.sql | 21 ++------------------- 1 file changed, 2 insertions(+), 19 deletions(-) diff --git a/sp_BlitzWho.sql b/sp_BlitzWho.sql index bdd99afe4..dd80d7839 100644 --- a/sp_BlitzWho.sql +++ b/sp_BlitzWho.sql @@ -112,9 +112,6 @@ DECLARE @ProductVersion NVARCHAR(128) AND r.plan_handle = session_stats.plan_handle AND r.statement_start_offset = session_stats.statement_start_offset AND r.statement_end_offset = session_stats.statement_end_offset' - ,@QueryStatsXMLselect NVARCHAR(MAX) = N' CAST(COALESCE(qs_live.query_plan, '''') AS XML) AS live_query_plan , ' - ,@QueryStatsXMLselect NVARCHAR(MAX) = N' CAST(COALESCE(qs_live.query_plan, '''') AS XML) AS live_query_plan , ' - ,@QueryStatsXMLSQL NVARCHAR(MAX) = N'OUTER APPLY sys.dm_exec_query_statistics_xml(s.session_id) qs_live' ,@ObjectFullName NVARCHAR(2000) ,@OutputTableNameQueryStats_View NVARCHAR(256) ,@LineFeed NVARCHAR(MAX) /* Had to set as MAX up from 10 as it was truncating the view creation*/; @@ -126,18 +123,6 @@ SET @ProductVersion = CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)); SELECT @ProductVersionMajor = SUBSTRING(@ProductVersion, 1,CHARINDEX('.', @ProductVersion) + 1 ), @ProductVersionMinor = PARSENAME(CONVERT(VARCHAR(32), @ProductVersion), 2) -SET @QueryStatsXMLselect = N' NULL AS live_query_plan , '; -IF EXISTS (SELECT * FROM sys.all_columns WHERE object_id = OBJECT_ID('sys.dm_exec_query_statistics_xml') AND name = 'query_plan') - BEGIN - SET @QueryStatsXMLselect = N' CAST(COALESCE(qs_live.query_plan, '''') AS XML) AS live_query_plan , '; - SET @QueryStatsXMLSQL = N'OUTER APPLY sys.dm_exec_query_statistics_xml(s.session_id) qs_live'; - END - ELSE - BEGIN - SET @QueryStatsXMLselect = N' NULL AS live_query_plan , '; - SET @QueryStatsXMLSQL = N' '; - END - SELECT @OutputTableNameQueryStats_View = QUOTENAME(@OutputTableName + '_Deltas'), @OutputDatabaseName = QUOTENAME(@OutputDatabaseName), @@ -177,6 +162,8 @@ IF @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @Output [query_text] [nvarchar](max) NULL, [query_plan] [xml] NULL, [live_query_plan] [xml] NULL, + [cached_parameter_info] [varchar](max) NULL, + [live_parameter_info] [varchar](max) NULL, [query_cost] [float] NULL, [status] [nvarchar](30) NOT NULL, [wait_info] [nvarchar](max) NULL, @@ -825,10 +812,6 @@ IF @ProductVersionMajor >= 11 END SELECT @StringToExecute = @StringToExecute + N' - qs_live.Live_Parameter_Info as Live_Parameter_Info, - derp.query_plan ,' - + @QueryStatsXMLselect - +' qmg.query_cost , s.status , CASE From a349a65d94577b495aefc226173ec649c1c930a9 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Wed, 24 Mar 2021 05:19:20 +0000 Subject: [PATCH 145/662] #2839 sp_DatabaseRestore smallint Change smallint to int to prevent arithmetic overflows. Closes #2839. --- sp_DatabaseRestore.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sp_DatabaseRestore.sql b/sp_DatabaseRestore.sql index 22fd897a6..6caa3a634 100755 --- a/sp_DatabaseRestore.sql +++ b/sp_DatabaseRestore.sql @@ -225,7 +225,7 @@ DECLARE @cmd NVARCHAR(4000) = N'', --Holds xp_cmdshell command @LogRecoveryOption AS NVARCHAR(MAX) = N'', --Holds the option to cause logs to be restored in standby mode or with no recovery @DatabaseLastLSN NUMERIC(25, 0), --redo_start_lsn of the current database @i TINYINT = 1, --Maintains loop to continue logs - @LogRestoreRanking SMALLINT = 1, --Holds Log iteration # when multiple paths & backup files are being stripped + @LogRestoreRanking INT = 1, --Holds Log iteration # when multiple paths & backup files are being stripped @LogFirstLSN NUMERIC(25, 0), --Holds first LSN in log backup headers @LogLastLSN NUMERIC(25, 0), --Holds last LSN in log backup headers @LogLastNameInMsdbAS NVARCHAR(MAX) = N'', -- Holds last TRN file name already restored From 655adfaab6cd480335199971c760dbc736d6fc75 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Wed, 24 Mar 2021 05:27:27 +0000 Subject: [PATCH 146/662] Removing sp_BlitzIndex changes It looked like a valid fix, but we try to keep pull requests focused on just one issue to make troubleshooting easier later when stuff breaks. --- sp_BlitzIndex.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index 35b3e00c5..f14914ccd 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -5224,7 +5224,7 @@ ELSE IF (@Mode=1) /*Summarize*/ ISNULL(i.index_name, '''') AS [Index Name], CASE WHEN i.is_primary_key = 1 AND i.index_definition <> ''[HEAP]'' - THEN N''--ALTER TABLE '' + QUOTENAME(i.[database_name]) + N''.'' + QUOTENAME(i.[schema_name]) + N''.'' + QUOTENAME(i.[object_name]) + + THEN N''-ALTER TABLE '' + QUOTENAME(i.[database_name]) + N''.'' + QUOTENAME(i.[schema_name]) + N''.'' + QUOTENAME(i.[object_name]) + N'' DROP CONSTRAINT '' + QUOTENAME(i.index_name) + N'';'' WHEN i.is_primary_key = 0 AND i.index_definition <> ''[HEAP]'' THEN N''--DROP INDEX ''+ QUOTENAME(i.index_name) + N'' ON '' + QUOTENAME(i.[database_name]) + N''.'' + From e2a4e7fe3e45a9f16f3643c29b9e7373bc44a257 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Wed, 24 Mar 2021 05:32:14 +0000 Subject: [PATCH 147/662] sp_Blitz: handle 8MB free No arithmetic overflow even when your hard drive has just 8MB free. Closes #2837. --- sp_Blitz.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index 69b71e680..1daffb1ac 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -8407,7 +8407,7 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 ,CASE WHEN ISNULL(logical_volume_name,'''') = '''' THEN '''' ELSE '' ('' + logical_volume_name + '')'' END AS logical_volume_name ,total_bytes/1024/1024 AS total_MB ,available_bytes/1024/1024 AS available_MB - ,(CONVERT(DECIMAL(4,2),(total_bytes/1.0 - available_bytes)/total_bytes * 100)) AS used_percent + ,(CONVERT(DECIMAL(5,2),(total_bytes/1.0 - available_bytes)/total_bytes * 100)) AS used_percent FROM (SELECT TOP 1 WITH TIES database_id From 4289de705c1f994d4d763cd7ebb321e5e72baf3e Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Wed, 24 Mar 2021 05:41:30 +0000 Subject: [PATCH 148/662] sp_BlitzWho: fixing capitalization of Live_Parameter_Info Just because it looked odd next to Cached_Parameter_Info in the output. --- sp_BlitzWho.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sp_BlitzWho.sql b/sp_BlitzWho.sql index dd80d7839..478985c2d 100644 --- a/sp_BlitzWho.sql +++ b/sp_BlitzWho.sql @@ -1134,7 +1134,7 @@ IF @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @Output ,[query_plan]' + CASE WHEN @ProductVersionMajor >= 11 THEN N',[live_query_plan]' ELSE N'' END + N' ,[Cached_Parameter_Info]' - + CASE WHEN @ProductVersionMajor >= 11 AND @ShowActualParameters = 1 THEN N',[live_parameter_info]' ELSE N'' END + N' + + CASE WHEN @ProductVersionMajor >= 11 AND @ShowActualParameters = 1 THEN N',[Live_Parameter_Info]' ELSE N'' END + N' ,[query_cost] ,[status] ,[wait_info]' From 17080c45e8985eddf23b27a300ce78c26ee2f7d3 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Wed, 24 Mar 2021 05:47:15 +0000 Subject: [PATCH 149/662] sp_BlitzWho: changing parameter info cols to nvarchar In case someone has nvarchar parameter names or values. --- sp_BlitzWho.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sp_BlitzWho.sql b/sp_BlitzWho.sql index 478985c2d..db531604e 100644 --- a/sp_BlitzWho.sql +++ b/sp_BlitzWho.sql @@ -162,8 +162,8 @@ IF @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @Output [query_text] [nvarchar](max) NULL, [query_plan] [xml] NULL, [live_query_plan] [xml] NULL, - [cached_parameter_info] [varchar](max) NULL, - [live_parameter_info] [varchar](max) NULL, + [cached_parameter_info] [nvarchar](max) NULL, + [live_parameter_info] [nvarchar](max) NULL, [query_cost] [float] NULL, [status] [nvarchar](30) NOT NULL, [wait_info] [nvarchar](max) NULL, From 9c7a454edfe56a2a75c3701fe105a631ef23e46c Mon Sep 17 00:00:00 2001 From: Ties Voskamp Date: Wed, 24 Mar 2021 10:50:08 +0100 Subject: [PATCH 150/662] Github #2823 - Truncate output to 32K when @OutputXMLasNVARCHAR = 1 is used so it parses nicely into Excel. --- sp_BlitzFirst.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sp_BlitzFirst.sql b/sp_BlitzFirst.sql index 63db7e38b..5c47656c8 100644 --- a/sp_BlitzFirst.sql +++ b/sp_BlitzFirst.sql @@ -4456,8 +4456,8 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, [FindingsGroup] , [Finding] , [URL] , - CAST(@StockDetailsHeader + [Details] + @StockDetailsFooter AS NVARCHAR(MAX)) AS Details, - CAST([HowToStopIt] AS NVARCHAR(MAX)) AS HowToStopIt, + CAST(LEFT(@StockDetailsHeader + [Details] + @StockDetailsFooter,32000) AS TEXT) AS Details, + CAST(LEFT([HowToStopIt],32000) AS TEXT) AS HowToStopIt, CAST([QueryText] AS NVARCHAR(MAX)) AS QueryText, CAST([QueryPlan] AS NVARCHAR(MAX)) AS QueryPlan FROM #BlitzFirstResults From bd51a34ac4ac7e56798c31234602f2670aac53bb Mon Sep 17 00:00:00 2001 From: Erik Darling Date: Thu, 25 Mar 2021 15:27:32 -0400 Subject: [PATCH 151/662] Add dates to sort order option Closes #2845 --- sp_BlitzIndex.sql | 79 +++++++++++++++++++++++------------------------ 1 file changed, 39 insertions(+), 40 deletions(-) diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index d06808542..80af615a3 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -5356,46 +5356,45 @@ ELSE IF (@Mode=1) /*Summarize*/ FROM #IndexSanity AS i --left join here so we don't lose disabled nc indexes LEFT JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id LEFT JOIN #IndexCreateTsql AS ict ON i.index_sanity_id = ict.index_sanity_id - ORDER BY CASE WHEN @SortDirection = 'desc' THEN - CASE WHEN @SortOrder = N'rows' THEN sz.total_rows - WHEN @SortOrder = N'reserved_mb' THEN sz.total_reserved_MB - WHEN @SortOrder = N'size' THEN sz.total_reserved_MB - WHEN @SortOrder = N'reserved_lob_mb' THEN sz.total_reserved_LOB_MB - WHEN @SortOrder = N'lob' THEN sz.total_reserved_LOB_MB - WHEN @SortOrder = N'total_row_lock_wait_in_ms' THEN COALESCE(sz.total_row_lock_wait_in_ms,0) - WHEN @SortOrder = N'total_page_lock_wait_in_ms' THEN COALESCE(sz.total_page_lock_wait_in_ms,0) - WHEN @SortOrder = N'lock_time' THEN (COALESCE(sz.total_row_lock_wait_in_ms,0) + COALESCE(sz.total_page_lock_wait_in_ms,0)) - WHEN @SortOrder = N'total_reads' THEN total_reads - WHEN @SortOrder = N'reads' THEN total_reads - WHEN @SortOrder = N'user_updates' THEN user_updates - WHEN @SortOrder = N'writes' THEN user_updates - WHEN @SortOrder = N'reads_per_write' THEN reads_per_write - WHEN @SortOrder = N'ratio' THEN reads_per_write - WHEN @SortOrder = N'forward_fetches' THEN sz.total_forwarded_fetch_count - WHEN @SortOrder = N'fetches' THEN sz.total_forwarded_fetch_count - ELSE NULL END - ELSE 1 END - DESC, /* Shout out to DHutmacher */ - CASE WHEN @SortDirection = 'asc' THEN - CASE WHEN @SortOrder = N'rows' THEN sz.total_rows - WHEN @SortOrder = N'reserved_mb' THEN sz.total_reserved_MB - WHEN @SortOrder = N'size' THEN sz.total_reserved_MB - WHEN @SortOrder = N'reserved_lob_mb' THEN sz.total_reserved_LOB_MB - WHEN @SortOrder = N'lob' THEN sz.total_reserved_LOB_MB - WHEN @SortOrder = N'total_row_lock_wait_in_ms' THEN COALESCE(sz.total_row_lock_wait_in_ms,0) - WHEN @SortOrder = N'total_page_lock_wait_in_ms' THEN COALESCE(sz.total_page_lock_wait_in_ms,0) - WHEN @SortOrder = N'lock_time' THEN (COALESCE(sz.total_row_lock_wait_in_ms,0) + COALESCE(sz.total_page_lock_wait_in_ms,0)) - WHEN @SortOrder = N'total_reads' THEN total_reads - WHEN @SortOrder = N'reads' THEN total_reads - WHEN @SortOrder = N'user_updates' THEN user_updates - WHEN @SortOrder = N'writes' THEN user_updates - WHEN @SortOrder = N'reads_per_write' THEN reads_per_write - WHEN @SortOrder = N'ratio' THEN reads_per_write - WHEN @SortOrder = N'forward_fetches' THEN sz.total_forwarded_fetch_count - WHEN @SortOrder = N'fetches' THEN sz.total_forwarded_fetch_count - ELSE NULL END - ELSE 1 END - ASC, + ORDER BY /* Shout out to DHutmacher */ + /*DESC*/ + CASE WHEN @SortOrder = N'rows' AND @SortDirection = N'desc' THEN sz.total_rows ELSE NULL END DESC, + CASE WHEN @SortOrder = N'reserved_mb' AND @SortDirection = N'desc' THEN sz.total_reserved_MB ELSE NULL END DESC, + CASE WHEN @SortOrder = N'size' AND @SortDirection = N'desc' THEN sz.total_reserved_MB ELSE NULL END DESC, + CASE WHEN @SortOrder = N'reserved_lob_mb' AND @SortDirection = N'desc' THEN sz.total_reserved_LOB_MB ELSE NULL END DESC, + CASE WHEN @SortOrder = N'lob' AND @SortDirection = N'desc' THEN sz.total_reserved_LOB_MB ELSE NULL END DESC, + CASE WHEN @SortOrder = N'total_row_lock_wait_in_ms' AND @SortDirection = N'desc' THEN COALESCE(sz.total_row_lock_wait_in_ms,0) ELSE NULL END DESC, + CASE WHEN @SortOrder = N'total_page_lock_wait_in_ms' AND @SortDirection = N'desc' THEN COALESCE(sz.total_page_lock_wait_in_ms,0) ELSE NULL END DESC, + CASE WHEN @SortOrder = N'lock_time' AND @SortDirection = N'desc' THEN (COALESCE(sz.total_row_lock_wait_in_ms,0) + COALESCE(sz.total_page_lock_wait_in_ms,0)) ELSE NULL END DESC, + CASE WHEN @SortOrder = N'total_reads' AND @SortDirection = N'desc' THEN total_reads ELSE NULL END DESC, + CASE WHEN @SortOrder = N'reads' AND @SortDirection = N'desc' THEN total_reads ELSE NULL END DESC, + CASE WHEN @SortOrder = N'user_updates' AND @SortDirection = N'desc' THEN user_updates ELSE NULL END DESC, + CASE WHEN @SortOrder = N'writes' AND @SortDirection = N'desc' THEN user_updates ELSE NULL END DESC, + CASE WHEN @SortOrder = N'reads_per_write' AND @SortDirection = N'desc' THEN reads_per_write ELSE NULL END DESC, + CASE WHEN @SortOrder = N'ratio' AND @SortDirection = N'desc' THEN reads_per_write ELSE NULL END DESC, + CASE WHEN @SortOrder = N'forward_fetches' AND @SortDirection = N'desc' THEN sz.total_forwarded_fetch_count ELSE NULL END DESC, + CASE WHEN @SortOrder = N'fetches' AND @SortDirection = N'desc' THEN sz.total_forwarded_fetch_count ELSE NULL END DESC, + CASE WHEN @SortOrder = N'create_date' AND @SortDirection = N'desc' THEN CONVERT(DATETIME, i.create_date) ELSE NULL END DESC, + CASE WHEN @SortOrder = N'modify_date' AND @SortDirection = N'desc' THEN CONVERT(DATETIME, i.modify_date) ELSE NULL END DESC, + /*ASC*/ + CASE WHEN @SortOrder = N'rows' AND @SortDirection = N'asc' THEN sz.total_rows ELSE NULL END ASC, + CASE WHEN @SortOrder = N'reserved_mb' AND @SortDirection = N'asc' THEN sz.total_reserved_MB ELSE NULL END ASC, + CASE WHEN @SortOrder = N'size' AND @SortDirection = N'asc' THEN sz.total_reserved_MB ELSE NULL END ASC, + CASE WHEN @SortOrder = N'reserved_lob_mb' AND @SortDirection = N'asc' THEN sz.total_reserved_LOB_MB ELSE NULL END ASC, + CASE WHEN @SortOrder = N'lob' AND @SortDirection = N'asc' THEN sz.total_reserved_LOB_MB ELSE NULL END ASC, + CASE WHEN @SortOrder = N'total_row_lock_wait_in_ms' AND @SortDirection = N'asc' THEN COALESCE(sz.total_row_lock_wait_in_ms,0) ELSE NULL END ASC, + CASE WHEN @SortOrder = N'total_page_lock_wait_in_ms' AND @SortDirection = N'asc' THEN COALESCE(sz.total_page_lock_wait_in_ms,0) ELSE NULL END ASC, + CASE WHEN @SortOrder = N'lock_time' AND @SortDirection = N'asc' THEN (COALESCE(sz.total_row_lock_wait_in_ms,0) + COALESCE(sz.total_page_lock_wait_in_ms,0)) ELSE NULL END ASC, + CASE WHEN @SortOrder = N'total_reads' AND @SortDirection = N'asc' THEN total_reads ELSE NULL END ASC, + CASE WHEN @SortOrder = N'reads' AND @SortDirection = N'asc' THEN total_reads ELSE NULL END ASC, + CASE WHEN @SortOrder = N'user_updates' AND @SortDirection = N'asc' THEN user_updates ELSE NULL END ASC, + CASE WHEN @SortOrder = N'writes' AND @SortDirection = N'asc' THEN user_updates ELSE NULL END ASC, + CASE WHEN @SortOrder = N'reads_per_write' AND @SortDirection = N'asc' THEN reads_per_write ELSE NULL END ASC, + CASE WHEN @SortOrder = N'ratio' AND @SortDirection = N'asc' THEN reads_per_write ELSE NULL END ASC, + CASE WHEN @SortOrder = N'forward_fetches' AND @SortDirection = N'asc' THEN sz.total_forwarded_fetch_count ELSE NULL END ASC, + CASE WHEN @SortOrder = N'fetches' AND @SortDirection = N'asc' THEN sz.total_forwarded_fetch_count ELSE NULL END ASC, + CASE WHEN @SortOrder = N'create_date' AND @SortDirection = N'asc' THEN CONVERT(DATETIME, i.create_date) ELSE NULL END ASC, + CASE WHEN @SortOrder = N'modify_date' AND @SortDirection = N'asc' THEN CONVERT(DATETIME, i.modify_date) ELSE NULL END ASC, i.[database_name], [Schema Name], [Object Name], [Index ID] OPTION (RECOMPILE); END; From 1901057e4e55d36f54e279f9a629961680ce7a28 Mon Sep 17 00:00:00 2001 From: Pete Sral Date: Sat, 27 Mar 2021 14:31:13 -0500 Subject: [PATCH 152/662] #2847 Fix negative CHARINDEX result when ) precedes ( Setting the `starting_position` of the `(` found when retrieving the closing `)` will keep us out of the negative sub-string length. --- sp_BlitzCache.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sp_BlitzCache.sql b/sp_BlitzCache.sql index f3711970c..24457cb6d 100644 --- a/sp_BlitzCache.sql +++ b/sp_BlitzCache.sql @@ -3982,7 +3982,7 @@ SET s.variable_datatype = CASE WHEN s.variable_datatype LIKE '%(%)%' s.compile_time_value = CASE WHEN s.compile_time_value LIKE '%(%)%' THEN SUBSTRING(s.compile_time_value, CHARINDEX('(', s.compile_time_value) + 1, - CHARINDEX(')', s.compile_time_value) - 1 - CHARINDEX('(', s.compile_time_value) + CHARINDEX(')', s.compile_time_value, CHARINDEX('(', s.compile_time_value) + 1) - 1 - CHARINDEX('(', s.compile_time_value) ) WHEN variable_datatype NOT IN ('bit', 'tinyint', 'smallint', 'int', 'bigint') AND s.variable_datatype NOT LIKE '%binary%' From 9970e4aee6651a37b0e9fd0d67d3f06d876ad0df Mon Sep 17 00:00:00 2001 From: AdrianB1 Date: Thu, 1 Apr 2021 13:00:03 +0300 Subject: [PATCH 153/662] Changed sp_BlitzLock help text #2849 Removed the text described in #2849 --- sp_BlitzLock.sql | 1 - 1 file changed, 1 deletion(-) diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index f5185e0f9..0c54077c1 100644 --- a/sp_BlitzLock.sql +++ b/sp_BlitzLock.sql @@ -85,7 +85,6 @@ END; I took a long look at this one, and: 1) Trying to account for all the weird places these could crop up is a losing effort. 2) Replace is slow af on lots of XML. - - Your mom. From 88df88a8eab05d0e3fdf129115725455bf5b9923 Mon Sep 17 00:00:00 2001 From: maddave2000 Date: Thu, 1 Apr 2021 16:26:30 +0100 Subject: [PATCH 154/662] Issue 2851: Added new parameter @ShowColumnstoreOnly to only show the columnstore index visualisation information when assessing a specific table. --- sp_BlitzIndex.sql | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index d06808542..5f1e32ce5 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -23,6 +23,7 @@ ALTER PROCEDURE dbo.sp_BlitzIndex @SkipPartitions BIT = 0, @SkipStatistics BIT = 1, @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, @IgnoreDatabases NVARCHAR(MAX) = NULL, /* Comma-delimited list of databases you want to skip */ @ThresholdMB INT = 250 /* Number of megabytes that an object must be before we include it in basic results */, @@ -2510,7 +2511,8 @@ BEGIN --We do a left join here in case this is a disabled NC. --In that case, it won't have any size info/pages allocated. - + IF (@ShowColumnstoreOnly = 0) + BEGIN WITH table_mode_cte AS ( SELECT s.db_schema_object_indexid, @@ -2701,7 +2703,8 @@ BEGIN WHERE s.object_id = @ObjectID ORDER BY s.auto_created, s.user_created, s.name, hist.step_number;'; EXEC sp_executesql @dsql, N'@ObjectID INT', @ObjectID; - END + END + END /* Visualize columnstore index contents. More info: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2584 */ IF 2 = (SELECT SUM(1) FROM sys.all_objects WHERE name IN ('column_store_row_groups','column_store_segments')) @@ -5173,7 +5176,7 @@ ELSE IF (@Mode=1) /*Summarize*/ ISNULL(i.index_name, '''') AS [Index Name], CASE WHEN i.is_primary_key = 1 AND i.index_definition <> ''[HEAP]'' - THEN N''-ALTER TABLE '' + QUOTENAME(i.[database_name]) + N''.'' + QUOTENAME(i.[schema_name]) + N''.'' + QUOTENAME(i.[object_name]) + + THEN N''--ALTER TABLE '' + QUOTENAME(i.[database_name]) + N''.'' + QUOTENAME(i.[schema_name]) + N''.'' + QUOTENAME(i.[object_name]) + N'' DROP CONSTRAINT '' + QUOTENAME(i.index_name) + N'';'' WHEN i.is_primary_key = 0 AND i.index_definition <> ''[HEAP]'' THEN N''--DROP INDEX ''+ QUOTENAME(i.index_name) + N'' ON '' + QUOTENAME(i.[database_name]) + N''.'' + From 015490386606ad8d75fa0d04322c8fccc4f733fa Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Wed, 7 Apr 2021 05:29:23 +0000 Subject: [PATCH 155/662] #2854 sp_Blitz service account Remove advisory language. Closes #2854. --- sp_Blitz.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index 1daffb1ac..c52861bfd 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -4463,7 +4463,7 @@ IF @ProductVersionMajor >= 10 'Informational' AS [FindingsGroup] , 'SQL Server is running under an NT Service account' AS [Finding] , 'https://www.brentozar.com/go/setup' AS [URL] , - ( 'I''m running as ' + [service_account] + '. I wish I had an Active Directory service account instead.' + ( 'I''m running as ' + [service_account] + '.' ) AS [Details] FROM [sys].[dm_server_services] @@ -4503,7 +4503,7 @@ IF @ProductVersionMajor >= 10 'Informational' AS [FindingsGroup] , 'SQL Server Agent is running under an NT Service account' AS [Finding] , 'https://www.brentozar.com/go/setup' AS [URL] , - ( 'I''m running as ' + [service_account] + '. I wish I had an Active Directory service account instead.' + ( 'I''m running as ' + [service_account] + '.' ) AS [Details] FROM [sys].[dm_server_services] From 09e704e8f9386ff80d1bc12ec778abd1adc6362c Mon Sep 17 00:00:00 2001 From: Ant Green Date: Wed, 7 Apr 2021 08:03:14 +0100 Subject: [PATCH 156/662] Update SqlServerVersions.sql Fixed build number for 2017 CU23 (3381) Added new CU10 for 2019 --- SqlServerVersions.sql | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/SqlServerVersions.sql b/SqlServerVersions.sql index 29450ac60..ba9aa1851 100644 --- a/SqlServerVersions.sql +++ b/SqlServerVersions.sql @@ -41,6 +41,7 @@ DELETE FROM dbo.SqlServerVersions; INSERT INTO dbo.SqlServerVersions (MajorVersionNumber, MinorVersionNumber, Branch, [Url], ReleaseDate, MainstreamSupportEndDate, ExtendedSupportEndDate, MajorVersionName, MinorVersionName) VALUES + (15, 4123, 'CU10', 'https://support.microsoft.com/en-us/help/5001090', '2021-02-11', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 10'), (15, 4102, 'CU9', 'https://support.microsoft.com/en-us/help/5000642', '2021-02-11', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 9 '), (15, 4073, 'CU8 GDR', 'https://support.microsoft.com/en-us/help/4583459', '2021-01-12', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 8 GDR '), (15, 4073, 'CU8', 'https://support.microsoft.com/en-us/help/4577194', '2020-10-01', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 8 '), @@ -53,7 +54,7 @@ VALUES (15, 4003, 'CU1', 'https://support.microsoft.com/en-us/help/4527376', '2020-01-07', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 1 '), (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 '), - (14, 3356, 'RTM CU23', 'https://support.microsoft.com/en-us/help/5000685', '2021-02-25', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 23'), + (14, 3381, 'RTM CU23', 'https://support.microsoft.com/en-us/help/5000685', '2021-02-25', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 23'), (14, 3370, 'RTM CU22 GDR', 'https://support.microsoft.com/en-us/help/4583457', '2021-01-12', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 22 GDR'), (14, 3356, 'RTM CU22', 'https://support.microsoft.com/en-us/help/4577467', '2020-09-10', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 22'), (14, 3335, 'RTM CU21', 'https://support.microsoft.com/en-us/help/4557397', '2020-07-01', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 21'), From 67f27642bfa75734523add055ae6c67e4b261c03 Mon Sep 17 00:00:00 2001 From: Erik Darling Date: Wed, 7 Apr 2021 15:36:53 -0400 Subject: [PATCH 157/662] Fix for 2816 In testing the scenarios mentioned in the open issue, I was able to get a correctly printed statement. The main fix was converting the concatenated block of columns to an nvarchar(max), but I also took the opportunity to make all string assignment Unicode. Closes #2816 (Hopefully) --- sp_BlitzCache.sql | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/sp_BlitzCache.sql b/sp_BlitzCache.sql index f3711970c..240199778 100644 --- a/sp_BlitzCache.sql +++ b/sp_BlitzCache.sql @@ -6942,20 +6942,23 @@ ELSE IF @ValidOutputLocation = 1 BEGIN - SET @StringToExecute = 'USE ' + SET @StringToExecute = N'USE ' + @OutputDatabaseName - + '; IF EXISTS(SELECT * FROM ' + + N'; IF EXISTS(SELECT * FROM ' + @OutputDatabaseName - + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' + + N'.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' + @OutputSchemaName - + ''') AND NOT EXISTS (SELECT * FROM ' + + N''') AND NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName - + '.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''' - + @OutputSchemaName + ''' AND QUOTENAME(TABLE_NAME) = ''' - + @OutputTableName + ''') CREATE TABLE ' - + @OutputSchemaName + '.' + + N'.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''' + + @OutputSchemaName + N''' AND QUOTENAME(TABLE_NAME) = ''' + + @OutputTableName + N''') CREATE TABLE ' + + @OutputSchemaName + N'.' + @OutputTableName - + N'(ID bigint NOT NULL IDENTITY(1,1), + + CONVERT + ( + nvarchar(MAX), + N'(ID bigint NOT NULL IDENTITY(1,1), ServerName NVARCHAR(258), CheckDate DATETIMEOFFSET, Version NVARCHAR(258), @@ -7031,27 +7034,28 @@ ELSE AvgSpills MONEY, QueryPlanCost FLOAT, JoinKey AS ServerName + Cast(CheckDate AS NVARCHAR(50)), - CONSTRAINT [PK_' + REPLACE(REPLACE(@OutputTableName,'[',''),']','') + '] PRIMARY KEY CLUSTERED(ID ASC));'; + CONSTRAINT [PK_' + REPLACE(REPLACE(@OutputTableName,N'[',N''),N']',N'') + N'] PRIMARY KEY CLUSTERED(ID ASC));' + ); SET @StringToExecute += N'IF EXISTS(SELECT * FROM ' +@OutputDatabaseName +N'.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' +@OutputSchemaName - +''') AND EXISTS (SELECT * FROM ' + +N''') AND EXISTS (SELECT * FROM ' +@OutputDatabaseName+ N'.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''' +@OutputSchemaName - +''' AND QUOTENAME(TABLE_NAME) = ''' + +N''' AND QUOTENAME(TABLE_NAME) = ''' +@OutputTableName - +''') AND EXISTS (SELECT * FROM ' + +N''') AND EXISTS (SELECT * FROM ' +@OutputDatabaseName+ N'.sys.computed_columns WHERE [name] = N''PlanCreationTimeHours'' AND QUOTENAME(OBJECT_NAME(object_id)) = N''' +@OutputTableName - +''' AND [definition] = N''(datediff(hour,[PlanCreationTime],sysdatetime()))'') + +N''' AND [definition] = N''(datediff(hour,[PlanCreationTime],sysdatetime()))'') BEGIN RAISERROR(''We noticed that you are running an old computed column definition for PlanCreationTimeHours, fixing that now'',0,0) WITH NOWAIT; - ALTER TABLE '+@OutputDatabaseName+'.'+@OutputSchemaName+'.'+@OutputTableName+' DROP COLUMN [PlanCreationTimeHours]; - ALTER TABLE '+@OutputDatabaseName+'.'+@OutputSchemaName+'.'+@OutputTableName+' ADD [PlanCreationTimeHours] AS DATEDIFF(HOUR,CONVERT(DATETIMEOFFSET(7),[PlanCreationTime]),[CheckDate]); + ALTER TABLE '+@OutputDatabaseName+N'.'+@OutputSchemaName+N'.'+@OutputTableName+N' DROP COLUMN [PlanCreationTimeHours]; + ALTER TABLE '+@OutputDatabaseName+N'.'+@OutputSchemaName+N'.'+@OutputTableName+N' ADD [PlanCreationTimeHours] AS DATEDIFF(HOUR,CONVERT(DATETIMEOFFSET(7),[PlanCreationTime]),[CheckDate]); END '; IF @ValidOutputServer = 1 From 7e87800a4c32c885017f1b4b836fd7a317239fec Mon Sep 17 00:00:00 2001 From: Erik Darling Date: Thu, 8 Apr 2021 13:58:34 -0400 Subject: [PATCH 158/662] Fix USERSTORE_TOKENPERM calculation Closes #2858 --- sp_Blitz.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index c52861bfd..1d2834a9c 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -3632,8 +3632,8 @@ AS BEGIN SET @user_perm_sql += N' - SELECT @user_perm_gb = CASE WHEN (pages_kb / 128.0 / 1024.) >= 2. - THEN CONVERT(DECIMAL(38, 2), (pages_kb / 128.0 / 1024.)) + SELECT @user_perm_gb = CASE WHEN (pages_kb / 1024. / 1024.) >= 2. + THEN CONVERT(DECIMAL(38, 2), (pages_kb / 1024. / 1024.)) ELSE NULL END FROM sys.dm_os_memory_clerks From 10ec5bb0674835011fd64ad36f4e7447c74482df Mon Sep 17 00:00:00 2001 From: Erik Darling Date: Sun, 11 Apr 2021 21:28:16 -0400 Subject: [PATCH 159/662] More accurate missing index plans Fixing the apply thing --- sp_BlitzIndex.sql | 46 +++++++++++++++++++++++++++++++--------------- 1 file changed, 31 insertions(+), 15 deletions(-) diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index c73cc12c4..31a74cf1c 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -1643,24 +1643,40 @@ BEGIN TRY AND ColumnNamesWithDataTypes.IndexColumnType = ''Included'' ) AS included_columns_with_data_type ' - /* Github #2780 BGO Removing SQL Server 2019's new missing index sample plan because it doesn't work yet: - IF NOT EXISTS (SELECT * FROM sys.all_objects WHERE name = 'dm_db_missing_index_group_stats_query') - */ - SET @dsql = @dsql + N' , NULL AS sample_query_plan ' - /* Github #2780 BGO Removing SQL Server 2019's new missing index sample plan because it doesn't work yet: + /* Github #2780 BGO Removing SQL Server 2019's new missing index sample plan because it doesn't work yet:*/ + IF NOT EXISTS + ( + SELECT + 1/0 + FROM sys.all_objects AS o + WHERE o.name = 'dm_db_missing_index_group_stats_query' + ) + SELECT + @dsql += N' , NULL AS sample_query_plan ' + /* Github #2780 BGO Removing SQL Server 2019's new missing index sample plan because it doesn't work yet:*/ ELSE BEGIN - SET @dsql = @dsql + N' , sample_query_plan = (SELECT TOP 1 p.query_plan - FROM sys.dm_db_missing_index_group_stats gs - CROSS APPLY (SELECT TOP 1 s.plan_handle - FROM sys.dm_db_missing_index_group_stats_query q - INNER JOIN sys.dm_exec_query_stats s ON q.query_plan_hash = s.query_plan_hash - WHERE gs.group_handle = q.group_handle - ORDER BY (q.user_seeks + q.user_scans) DESC, s.total_logical_reads DESC) q2 - CROSS APPLY sys.dm_exec_query_plan(q2.plan_handle) p - WHERE gs.group_handle = gs.group_handle) ' + SELECT + @dsql += N' + , sample_query_plan = + ( + SELECT TOP (1) + p.query_plan + FROM sys.dm_db_missing_index_group_stats gs + CROSS APPLY + ( + SELECT TOP (1) + s.plan_handle + FROM sys.dm_db_missing_index_group_stats_query q + INNER JOIN sys.dm_exec_query_stats s + ON q.query_plan_hash = s.query_plan_hash + WHERE gs.group_handle = q.group_handle + ORDER BY (q.user_seeks + q.user_scans) DESC, s.total_logical_reads DESC + ) q2 + CROSS APPLY sys.dm_exec_query_plan(q2.plan_handle) p + ) ' END - */ + SET @dsql = @dsql + N'FROM ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_groups ig From f0969171c288b4a5467c2418cb2120960bc8a2df Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Mon, 12 Apr 2021 05:49:25 +0000 Subject: [PATCH 160/662] #2842 sp_BlitzWho params to table Adds new cached_parameter_info and live_parameter_info to existing tables. Closes #2842. --- sp_BlitzWho.sql | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/sp_BlitzWho.sql b/sp_BlitzWho.sql index 34e6b72c8..c8e3b9748 100644 --- a/sp_BlitzWho.sql +++ b/sp_BlitzWho.sql @@ -262,6 +262,20 @@ IF @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @Output ALTER TABLE ' + @ObjectFullName + N' ADD JoinKey AS ServerName + CAST(CheckDate AS NVARCHAR(50));'; EXEC(@StringToExecute); + /* If the table doesn't have the new cached_parameter_info computed column, add it. See Github #2842. */ + SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableName; + SET @StringToExecute = N'IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns + WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''cached_parameter_info'') + ALTER TABLE ' + @ObjectFullName + N' ADD cached_parameter_info NVARCHAR(MAX) NULL;'; + EXEC(@StringToExecute); + + /* If the table doesn't have the new live_parameter_info computed column, add it. See Github #2842. */ + SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableName; + SET @StringToExecute = N'IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns + WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''live_parameter_info'') + ALTER TABLE ' + @ObjectFullName + N' ADD live_parameter_info NVARCHAR(MAX) NULL;'; + EXEC(@StringToExecute); + /* Delete history older than @OutputTableRetentionDays */ SET @OutputTableCleanupDate = CAST( (DATEADD(DAY, -1 * @OutputTableRetentionDays, GETDATE() ) ) AS DATE); SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' From 9a7d30dc171720814b1950a2f49ef6eeb6946b0d Mon Sep 17 00:00:00 2001 From: NickPapatonis <81128876+NickPapatonis@users.noreply.github.com> Date: Sun, 11 Apr 2021 00:38:49 -0400 Subject: [PATCH 161/662] Added partition ranges to columnstore visualization Added optional column to columnstore visualization output containing starting and ending values for each row group's partition. --- sp_BlitzIndex.sql | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index 31a74cf1c..217eebbfb 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -34,6 +34,7 @@ ALTER PROCEDURE dbo.sp_BlitzIndex @OutputTableName NVARCHAR(256) = NULL , @IncludeInactiveIndexes BIT = 0 /* Will skip indexes with no reads or writes */, @ShowAllMissingIndexRequests BIT = 0 /*Will make all missing index requests show up*/, + @ShowPartitionRanges BIT = 1 /* Will add partition range values column to columnstore visualization */, @SortOrder NVARCHAR(50) = NULL, /* Only affects @Mode = 2. */ @SortDirection NVARCHAR(4) = 'DESC', /* Only affects @Mode = 2. */ @Help TINYINT = 0, @@ -125,6 +126,7 @@ DECLARE @LineFeed NVARCHAR(5); DECLARE @DaysUptimeInsertValue NVARCHAR(256); DECLARE @DatabaseToIgnore NVARCHAR(MAX); DECLARE @ColumnList NVARCHAR(MAX); +DECLARE @PartitionCount INT; /* Let's get @SortOrder set to lower case here for comparisons later */ SET @SortOrder = REPLACE(LOWER(@SortOrder), N' ', N'_'); @@ -2744,6 +2746,7 @@ BEGIN SELECT @ColumnList = @ColumnList + column_name + N'', '' FROM DistinctColumns ORDER BY column_id; + SELECT @PartitionCount = COUNT(1) FROM ' + QUOTENAME(@DatabaseName) + N'.sys.partitions WHERE object_id = @ObjectID AND data_compression IN (3,4); END'; IF @Debug = 1 @@ -2760,25 +2763,41 @@ BEGIN PRINT SUBSTRING(@dsql, 36000, 40000); END; - EXEC sp_executesql @dsql, N'@ObjectID INT, @ColumnList NVARCHAR(MAX) OUTPUT', @ObjectID, @ColumnList OUTPUT; + EXEC sp_executesql @dsql, N'@ObjectID INT, @ColumnList NVARCHAR(MAX) OUTPUT, @PartitionCount INT OUTPUT', @ObjectID, @ColumnList OUTPUT, @PartitionCount OUTPUT; + + IF @PartitionCount < 2 + SET @ShowPartitionRanges = 0; IF @Debug = 1 - SELECT @ColumnList AS ColumnstoreColumnList; + SELECT @ColumnList AS ColumnstoreColumnList, @PartitionCount AS PartitionCount, @ShowPartitionRanges AS ShowPartitionRanges; IF @ColumnList <> '' BEGIN /* Remove the trailing comma */ SET @ColumnList = LEFT(@ColumnList, LEN(@ColumnList) - 1); - SET @dsql = N'USE ' + QUOTENAME(@DatabaseName) + N'; SELECT partition_number, row_group_id, total_rows, deleted_rows, ' + @ColumnList + N' + SET @dsql = N'USE ' + QUOTENAME(@DatabaseName) + N'; + SELECT partition_number, + row_group_id, total_rows, deleted_rows, ' + @ColumnList + IIF(@ShowPartitionRanges = 1, N', + COALESCE(range_start_op + '' '' + range_start + '' '', '''') + COALESCE(range_end_op + '' '' + range_end, '''') AS partition_range', '') + N' FROM ( - SELECT c.name AS column_name, p.partition_number, + SELECT c.name AS column_name, p.partition_number,' + IIF(@ShowPartitionRanges = 1, N' + CASE WHEN pf.boundary_value_on_right = 0 THEN ''>'' ELSE ''>='' END range_start_op, + CASE WHEN pp.system_type_id IN (42, 43, 58, 61) THEN CONVERT(NVARCHAR(4000), prvs.value, 126) ELSE CAST(prvs.value AS NVARCHAR(4000)) END range_start, + CASE WHEN pf.boundary_value_on_right = 0 THEN ''<='' ELSE ''<'' END range_end_op, + CASE WHEN pp.system_type_id IN (42, 43, 58, 61) THEN CONVERT(NVARCHAR(4000), prve.value, 126) ELSE CAST(prve.value AS NVARCHAR(4000)) END range_end,', '') + N' rg.row_group_id, rg.total_rows, rg.deleted_rows, details = CAST(seg.min_data_id AS VARCHAR(20)) + '' to '' + CAST(seg.max_data_id AS VARCHAR(20)) + '', '' + CAST(CAST((seg.on_disk_size / 1024.0 / 1024) AS DECIMAL(18,0)) AS VARCHAR(20)) + '' MB'' FROM ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_row_groups rg INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns c ON rg.object_id = c.object_id INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partitions p ON rg.object_id = p.object_id AND rg.partition_number = p.partition_number - INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.index_columns ic on ic.column_id = c.column_id AND ic.object_id = c.object_id AND ic.index_id = p.index_id + INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.index_columns ic on ic.column_id = c.column_id AND ic.object_id = c.object_id AND ic.index_id = p.index_id' + IIF(@ShowPartitionRanges = 1, N' + LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.indexes i ON i.object_id = rg.object_id AND i.index_id = rg.index_id + LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partition_schemes ps ON ps.data_space_id = i.data_space_id + LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partition_functions pf ON pf.function_id = ps.function_id + LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partition_parameters pp ON pp.function_id = pf.function_id + LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partition_range_values prvs ON prvs.function_id = pf.function_id AND prvs.boundary_id = p.partition_number - 1 + LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partition_range_values prve ON prve.function_id = pf.function_id AND prve.boundary_id = p.partition_number', '') + N' LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_segments seg ON p.partition_id = seg.partition_id AND ic.index_column_id = seg.column_id AND rg.row_group_id = seg.segment_id WHERE rg.object_id = @ObjectID ) AS x From 2492176321a30450a1202f3bc7819ce35c57c54e Mon Sep 17 00:00:00 2001 From: NickPapatonis <81128876+NickPapatonis@users.noreply.github.com> Date: Mon, 12 Apr 2021 22:46:31 -0400 Subject: [PATCH 162/662] Added DATE and TIME types to partition range formatting --- sp_BlitzIndex.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index 217eebbfb..5a0e5b421 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -2783,9 +2783,9 @@ BEGIN FROM ( SELECT c.name AS column_name, p.partition_number,' + IIF(@ShowPartitionRanges = 1, N' CASE WHEN pf.boundary_value_on_right = 0 THEN ''>'' ELSE ''>='' END range_start_op, - CASE WHEN pp.system_type_id IN (42, 43, 58, 61) THEN CONVERT(NVARCHAR(4000), prvs.value, 126) ELSE CAST(prvs.value AS NVARCHAR(4000)) END range_start, + CASE WHEN pp.system_type_id IN (40, 41, 42, 43, 58, 61) THEN CONVERT(NVARCHAR(4000), prvs.value, 126) ELSE CAST(prvs.value AS NVARCHAR(4000)) END range_start, CASE WHEN pf.boundary_value_on_right = 0 THEN ''<='' ELSE ''<'' END range_end_op, - CASE WHEN pp.system_type_id IN (42, 43, 58, 61) THEN CONVERT(NVARCHAR(4000), prve.value, 126) ELSE CAST(prve.value AS NVARCHAR(4000)) END range_end,', '') + N' + CASE WHEN pp.system_type_id IN (40, 41, 42, 43, 58, 61) THEN CONVERT(NVARCHAR(4000), prve.value, 126) ELSE CAST(prve.value AS NVARCHAR(4000)) END range_end,', '') + N' rg.row_group_id, rg.total_rows, rg.deleted_rows, details = CAST(seg.min_data_id AS VARCHAR(20)) + '' to '' + CAST(seg.max_data_id AS VARCHAR(20)) + '', '' + CAST(CAST((seg.on_disk_size / 1024.0 / 1024) AS DECIMAL(18,0)) AS VARCHAR(20)) + '' MB'' FROM ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_row_groups rg From 022fb2fa5a4fb1886ad59a8ad5cf03cb113154d6 Mon Sep 17 00:00:00 2001 From: NickPapatonis <81128876+NickPapatonis@users.noreply.github.com> Date: Tue, 13 Apr 2021 21:06:06 -0400 Subject: [PATCH 163/662] Added FLOAT and REAL types to partition range formatting --- sp_BlitzIndex.sql | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index 5a0e5b421..ee6c41795 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -2783,9 +2783,15 @@ BEGIN FROM ( SELECT c.name AS column_name, p.partition_number,' + IIF(@ShowPartitionRanges = 1, N' CASE WHEN pf.boundary_value_on_right = 0 THEN ''>'' ELSE ''>='' END range_start_op, - CASE WHEN pp.system_type_id IN (40, 41, 42, 43, 58, 61) THEN CONVERT(NVARCHAR(4000), prvs.value, 126) ELSE CAST(prvs.value AS NVARCHAR(4000)) END range_start, + CASE + WHEN pp.system_type_id IN (40, 41, 42, 43, 58, 61) THEN CONVERT(NVARCHAR(4000), prvs.value, 126) + WHEN pp.system_type_id IN (59, 62) THEN CONVERT(NVARCHAR(4000), prvs.value, 3) + ELSE CAST(prvs.value AS NVARCHAR(4000)) END range_start, CASE WHEN pf.boundary_value_on_right = 0 THEN ''<='' ELSE ''<'' END range_end_op, - CASE WHEN pp.system_type_id IN (40, 41, 42, 43, 58, 61) THEN CONVERT(NVARCHAR(4000), prve.value, 126) ELSE CAST(prve.value AS NVARCHAR(4000)) END range_end,', '') + N' + CASE + WHEN pp.system_type_id IN (40, 41, 42, 43, 58, 61) THEN CONVERT(NVARCHAR(4000), prve.value, 126) + WHEN pp.system_type_id IN (59, 62) THEN CONVERT(NVARCHAR(4000), prve.value, 3) + ELSE CAST(prve.value AS NVARCHAR(4000)) END range_end,', '') + N' rg.row_group_id, rg.total_rows, rg.deleted_rows, details = CAST(seg.min_data_id AS VARCHAR(20)) + '' to '' + CAST(seg.max_data_id AS VARCHAR(20)) + '', '' + CAST(CAST((seg.on_disk_size / 1024.0 / 1024) AS DECIMAL(18,0)) AS VARCHAR(20)) + '' MB'' FROM ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_row_groups rg From a17fc7d9453a92c789b7a377e605a3a2d2ba21ce Mon Sep 17 00:00:00 2001 From: NickPapatonis <81128876+NickPapatonis@users.noreply.github.com> Date: Tue, 13 Apr 2021 21:26:02 -0400 Subject: [PATCH 164/662] Refactored partition range formatting --- sp_BlitzIndex.sql | 57 ++++++++++++++++++++++++++--------------------- 1 file changed, 32 insertions(+), 25 deletions(-) diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index ee6c41795..4d00bb941 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -2777,35 +2777,42 @@ BEGIN SET @ColumnList = LEFT(@ColumnList, LEN(@ColumnList) - 1); SET @dsql = N'USE ' + QUOTENAME(@DatabaseName) + N'; - SELECT partition_number, - row_group_id, total_rows, deleted_rows, ' + @ColumnList + IIF(@ShowPartitionRanges = 1, N', + SELECT partition_number, row_group_id, total_rows, deleted_rows, ' + @ColumnList + IIF(@ShowPartitionRanges = 1, N', COALESCE(range_start_op + '' '' + range_start + '' '', '''') + COALESCE(range_end_op + '' '' + range_end, '''') AS partition_range', '') + N' FROM ( - SELECT c.name AS column_name, p.partition_number,' + IIF(@ShowPartitionRanges = 1, N' - CASE WHEN pf.boundary_value_on_right = 0 THEN ''>'' ELSE ''>='' END range_start_op, + SELECT column_name, partition_number, row_group_id, total_rows, deleted_rows, details' + IIF(@ShowPartitionRanges = 1, N', + range_start_op, CASE - WHEN pp.system_type_id IN (40, 41, 42, 43, 58, 61) THEN CONVERT(NVARCHAR(4000), prvs.value, 126) - WHEN pp.system_type_id IN (59, 62) THEN CONVERT(NVARCHAR(4000), prvs.value, 3) - ELSE CAST(prvs.value AS NVARCHAR(4000)) END range_start, - CASE WHEN pf.boundary_value_on_right = 0 THEN ''<='' ELSE ''<'' END range_end_op, + WHEN format_type IS NULL THEN CAST(range_start_value AS NVARCHAR(4000)) + ELSE CONVERT(NVARCHAR(4000), range_start_value, format_type) END range_start, + range_end_op, CASE - WHEN pp.system_type_id IN (40, 41, 42, 43, 58, 61) THEN CONVERT(NVARCHAR(4000), prve.value, 126) - WHEN pp.system_type_id IN (59, 62) THEN CONVERT(NVARCHAR(4000), prve.value, 3) - ELSE CAST(prve.value AS NVARCHAR(4000)) END range_end,', '') + N' - rg.row_group_id, rg.total_rows, rg.deleted_rows, - details = CAST(seg.min_data_id AS VARCHAR(20)) + '' to '' + CAST(seg.max_data_id AS VARCHAR(20)) + '', '' + CAST(CAST((seg.on_disk_size / 1024.0 / 1024) AS DECIMAL(18,0)) AS VARCHAR(20)) + '' MB'' - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_row_groups rg - INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns c ON rg.object_id = c.object_id - INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partitions p ON rg.object_id = p.object_id AND rg.partition_number = p.partition_number - INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.index_columns ic on ic.column_id = c.column_id AND ic.object_id = c.object_id AND ic.index_id = p.index_id' + IIF(@ShowPartitionRanges = 1, N' - LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.indexes i ON i.object_id = rg.object_id AND i.index_id = rg.index_id - LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partition_schemes ps ON ps.data_space_id = i.data_space_id - LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partition_functions pf ON pf.function_id = ps.function_id - LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partition_parameters pp ON pp.function_id = pf.function_id - LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partition_range_values prvs ON prvs.function_id = pf.function_id AND prvs.boundary_id = p.partition_number - 1 - LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partition_range_values prve ON prve.function_id = pf.function_id AND prve.boundary_id = p.partition_number', '') + N' - LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_segments seg ON p.partition_id = seg.partition_id AND ic.index_column_id = seg.column_id AND rg.row_group_id = seg.segment_id - WHERE rg.object_id = @ObjectID + WHEN format_type IS NULL THEN CAST(range_end_value AS NVARCHAR(4000)) + ELSE CONVERT(NVARCHAR(4000), range_end_value, format_type) END range_end', '') + N' + FROM ( + SELECT c.name AS column_name, p.partition_number, rg.row_group_id, rg.total_rows, rg.deleted_rows, + details = CAST(seg.min_data_id AS VARCHAR(20)) + '' to '' + CAST(seg.max_data_id AS VARCHAR(20)) + '', '' + CAST(CAST((seg.on_disk_size / 1024.0 / 1024) AS DECIMAL(18,0)) AS VARCHAR(20)) + '' MB''' + IIF(@ShowPartitionRanges = 1, N', + CASE + WHEN pp.system_type_id IN (40, 41, 42, 43, 58, 61) THEN 126 + WHEN pp.system_type_id IN (59, 62) THEN 3 + ELSE NULL END format_type, + CASE WHEN pf.boundary_value_on_right = 0 THEN ''>'' ELSE ''>='' END range_start_op, + prvs.value range_start_value, + CASE WHEN pf.boundary_value_on_right = 0 THEN ''<='' ELSE ''<'' END range_end_op, + prve.value range_end_value', '') + N' + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_row_groups rg + INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns c ON rg.object_id = c.object_id + INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partitions p ON rg.object_id = p.object_id AND rg.partition_number = p.partition_number + INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.index_columns ic on ic.column_id = c.column_id AND ic.object_id = c.object_id AND ic.index_id = p.index_id' + IIF(@ShowPartitionRanges = 1, N' + LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.indexes i ON i.object_id = rg.object_id AND i.index_id = rg.index_id + LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partition_schemes ps ON ps.data_space_id = i.data_space_id + LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partition_functions pf ON pf.function_id = ps.function_id + LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partition_parameters pp ON pp.function_id = pf.function_id + LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partition_range_values prvs ON prvs.function_id = pf.function_id AND prvs.boundary_id = p.partition_number - 1 + LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partition_range_values prve ON prve.function_id = pf.function_id AND prve.boundary_id = p.partition_number', '') + N' + LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_segments seg ON p.partition_id = seg.partition_id AND ic.index_column_id = seg.column_id AND rg.row_group_id = seg.segment_id + WHERE rg.object_id = @ObjectID + ) AS y ) AS x PIVOT (MAX(details) FOR column_name IN ( ' + @ColumnList + N')) AS pivot1 ORDER BY partition_number, row_group_id;'; From 294b7c662dc47b548b7d311a7e7642b848f67d77 Mon Sep 17 00:00:00 2001 From: NickPapatonis <81128876+NickPapatonis@users.noreply.github.com> Date: Tue, 13 Apr 2021 21:49:35 -0400 Subject: [PATCH 165/662] More refactoring of partition range formatting --- sp_BlitzIndex.sql | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index 4d00bb941..e19dc09c2 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -2778,9 +2778,9 @@ BEGIN SET @dsql = N'USE ' + QUOTENAME(@DatabaseName) + N'; SELECT partition_number, row_group_id, total_rows, deleted_rows, ' + @ColumnList + IIF(@ShowPartitionRanges = 1, N', - COALESCE(range_start_op + '' '' + range_start + '' '', '''') + COALESCE(range_end_op + '' '' + range_end, '''') AS partition_range', '') + N' + COALESCE(range_start_op + '' '' + range_start + '' '', '''') + COALESCE(range_end_op + '' '' + range_end, '''') AS partition_range FROM ( - SELECT column_name, partition_number, row_group_id, total_rows, deleted_rows, details' + IIF(@ShowPartitionRanges = 1, N', + SELECT column_name, partition_number, row_group_id, total_rows, deleted_rows, details, range_start_op, CASE WHEN format_type IS NULL THEN CAST(range_start_value AS NVARCHAR(4000)) @@ -2811,8 +2811,8 @@ BEGIN LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partition_range_values prvs ON prvs.function_id = pf.function_id AND prvs.boundary_id = p.partition_number - 1 LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partition_range_values prve ON prve.function_id = pf.function_id AND prve.boundary_id = p.partition_number', '') + N' LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_segments seg ON p.partition_id = seg.partition_id AND ic.index_column_id = seg.column_id AND rg.row_group_id = seg.segment_id - WHERE rg.object_id = @ObjectID - ) AS y + WHERE rg.object_id = @ObjectID' + IIF(@ShowPartitionRanges = 1, N' + ) AS y', '') + N' ) AS x PIVOT (MAX(details) FOR column_name IN ( ' + @ColumnList + N')) AS pivot1 ORDER BY partition_number, row_group_id;'; From b076ccc17aae628f86e40bd932e30b07b8f1c0b6 Mon Sep 17 00:00:00 2001 From: NickPapatonis <81128876+NickPapatonis@users.noreply.github.com> Date: Wed, 14 Apr 2021 23:04:07 -0400 Subject: [PATCH 166/662] Added MONEY and SMALLMONEY types to partition range formatting --- sp_BlitzIndex.sql | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index e19dc09c2..796be3493 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -34,7 +34,7 @@ ALTER PROCEDURE dbo.sp_BlitzIndex @OutputTableName NVARCHAR(256) = NULL , @IncludeInactiveIndexes BIT = 0 /* Will skip indexes with no reads or writes */, @ShowAllMissingIndexRequests BIT = 0 /*Will make all missing index requests show up*/, - @ShowPartitionRanges BIT = 1 /* Will add partition range values column to columnstore visualization */, + @ShowPartitionRanges BIT = 0 /* Will add partition range values column to columnstore visualization */, @SortOrder NVARCHAR(50) = NULL, /* Only affects @Mode = 2. */ @SortDirection NVARCHAR(4) = 'DESC', /* Only affects @Mode = 2. */ @Help TINYINT = 0, @@ -2795,6 +2795,7 @@ BEGIN CASE WHEN pp.system_type_id IN (40, 41, 42, 43, 58, 61) THEN 126 WHEN pp.system_type_id IN (59, 62) THEN 3 + WHEN pp.system_type_id IN (60, 122) THEN 2 ELSE NULL END format_type, CASE WHEN pf.boundary_value_on_right = 0 THEN ''>'' ELSE ''>='' END range_start_op, prvs.value range_start_value, From fcd0249b4e67567c2b7a8756a1c8b6a2d3a1f846 Mon Sep 17 00:00:00 2001 From: Erik Darling Date: Thu, 15 Apr 2021 15:43:01 -0400 Subject: [PATCH 167/662] Fix #2865 We don't need no string truncation Closes #2685 --- sp_Blitz.sql | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index 1d2834a9c..08ece0248 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -634,8 +634,8 @@ AS DROP TABLE #driveInfo; CREATE TABLE #driveInfo ( - drive NVARCHAR, - logical_volume_name NVARCHAR(32), --Limit is 32 for NTFS, 11 for FAT + drive NVARCHAR(2), + logical_volume_name NVARCHAR(36), --Limit is 32 for NTFS, 11 for FAT available_MB DECIMAL(18, 0), total_MB DECIMAL(18, 0), used_percent DECIMAL(18, 2) @@ -8404,7 +8404,7 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 inner join ( SELECT DISTINCT SUBSTRING(volume_mount_point, 1, 1) AS volume_mount_point - ,CASE WHEN ISNULL(logical_volume_name,'''') = '''' THEN '''' ELSE '' ('' + logical_volume_name + '')'' END AS logical_volume_name + ,CASE WHEN ISNULL(logical_volume_name,'''') = '''' THEN '''' ELSE ''('' + logical_volume_name + '')'' END AS logical_volume_name ,total_bytes/1024/1024 AS total_MB ,available_bytes/1024/1024 AS available_MB ,(CONVERT(DECIMAL(5,2),(total_bytes/1.0 - available_bytes)/total_bytes * 100)) AS used_percent @@ -8441,7 +8441,7 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 + '' drive'' ELSE CAST(CAST(i.available_MB/1024 AS NUMERIC(18,2)) AS VARCHAR(30)) + '' GB free on '' + i.drive - + '' drive'' + i.logical_volume_name + + '' drive '' + i.logical_volume_name + '' out of '' + CAST(CAST(i.total_MB/1024 AS NUMERIC(18,2)) AS VARCHAR(30)) + '' GB total ('' + CAST(i.used_percent AS VARCHAR(30)) + ''%)'' END AS Details From 689271f4d1feb95d49d0e9dd24e2320bb89602ec Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Tue, 20 Apr 2021 01:02:11 -0700 Subject: [PATCH 168/662] 2021-04 installer scripts Bumping dates and version numbers. --- Documentation/Development/Merge Blitz.ps1 | 6 +- Install-All-Scripts.sql | 1198 ++++++++++++++++++--- Install-Core-Blitz-No-Query-Store.sql | 1186 ++++++++++++++++++-- Install-Core-Blitz-With-Query-Store.sql | 1188 ++++++++++++++++++-- sp_AllNightLog.sql | 2 +- sp_AllNightLog_Setup.sql | 2 +- sp_Blitz.sql | 2 +- sp_BlitzAnalysis.sql | 2 +- sp_BlitzBackups.sql | 2 +- sp_BlitzCache.sql | 2 +- sp_BlitzFirst.sql | 2 +- sp_BlitzInMemoryOLTP.sql | 2 +- sp_BlitzIndex.sql | 2 +- sp_BlitzLock.sql | 2 +- sp_BlitzQueryStore.sql | 2 +- sp_BlitzWho.sql | 2 +- sp_DatabaseRestore.sql | 2 +- sp_ineachdb.sql | 2 +- 18 files changed, 3240 insertions(+), 366 deletions(-) diff --git a/Documentation/Development/Merge Blitz.ps1 b/Documentation/Development/Merge Blitz.ps1 index d49045f71..2916f0407 100644 --- a/Documentation/Development/Merge Blitz.ps1 +++ b/Documentation/Development/Merge Blitz.ps1 @@ -5,7 +5,7 @@ $BlitzFirstPath = "$FilePath\sp_BlitzFirst.sql" #All Core Blitz Without sp_BlitzQueryStore Get-ChildItem -Path "$FilePath" -Filter "sp_Blitz*.sql" | -Where-Object { $_.FullName -notlike "*sp_BlitzQueryStore.sql*" -and $_.FullName -notlike "*sp_BlitzInMemoryOLTP*" -and $_.FullName -notlike "*sp_BlitzFirst*" -and $_.FullName -notlike "*sp_BlitzAnalysis*"} | +Where-Object { $_.FullName -notlike "*sp_BlitzQueryStore.sql*" -and $_.FullName -notlike "*sp_BlitzInMemoryOLTP*" -and $_.FullName -notlike "*sp_BlitzFirst*"} | ForEach-Object { Get-Content $_.FullName } | Set-Content -Path "$FilePath\Install-Core-Blitz-No-Query-Store.sql" -Force #append script to (re-)create SqlServerVersions Table (https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2429) @@ -17,7 +17,7 @@ if ( test-path "$BlitzFirstPath") #All Core Blitz With sp_BlitzQueryStore Get-ChildItem -Path "$FilePath" -Filter "sp_Blitz*.sql" | -Where-Object { $_.FullName -notlike "*sp_BlitzInMemoryOLTP*" -and $_.FullName -notlike "*sp_BlitzFirst*" -and $_.FullName -notlike "*sp_BlitzAnalysis*"} | +Where-Object { $_.FullName -notlike "*sp_BlitzInMemoryOLTP*" -and $_.FullName -notlike "*sp_BlitzFirst*"} | ForEach-Object { Get-Content $_.FullName } | Set-Content -Path "$FilePath\Install-Core-Blitz-With-Query-Store.sql" -Force #append script to (re-)create SqlServerVersions Table (https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2429) @@ -29,7 +29,7 @@ if ( test-path "$BlitzFirstPath") #All Scripts Get-ChildItem -Path "$FilePath" -Filter "sp_*.sql" | -Where-Object { $_.FullName -notlike "*sp_BlitzInMemoryOLTP*" -and $_.FullName -notlike "*sp_BlitzFirst*" -and $_.FullName -notlike "*sp_BlitzAnalysis*"} | +Where-Object { $_.FullName -notlike "*sp_BlitzInMemoryOLTP*" -and $_.FullName -notlike "*sp_BlitzFirst*"} | ForEach-Object { Get-Content $_.FullName } | Set-Content -Path "$FilePath\Install-All-Scripts.sql" -Force #append script to (re-)create SqlServerVersions Table (https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2429) diff --git a/Install-All-Scripts.sql b/Install-All-Scripts.sql index 0dd92683b..d24d7f49e 100755 --- a/Install-All-Scripts.sql +++ b/Install-All-Scripts.sql @@ -30,7 +30,7 @@ SET NOCOUNT ON; BEGIN; -SELECT @Version = '8.02', @VersionDate = '20210322'; +SELECT @Version = '8.03', @VersionDate = '20210420'; IF(@VersionCheckMode = 1) BEGIN @@ -1524,7 +1524,7 @@ SET NOCOUNT ON; BEGIN; -SELECT @Version = '8.02', @VersionDate = '20210322'; +SELECT @Version = '8.03', @VersionDate = '20210420'; IF(@VersionCheckMode = 1) BEGIN @@ -2856,7 +2856,7 @@ AS SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.02', @VersionDate = '20210322'; + SELECT @Version = '8.03', @VersionDate = '20210420'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -3453,8 +3453,8 @@ AS DROP TABLE #driveInfo; CREATE TABLE #driveInfo ( - drive NVARCHAR, - logical_volume_name NVARCHAR(32), --Limit is 32 for NTFS, 11 for FAT + drive NVARCHAR(2), + logical_volume_name NVARCHAR(36), --Limit is 32 for NTFS, 11 for FAT available_MB DECIMAL(18, 0), total_MB DECIMAL(18, 0), used_percent DECIMAL(18, 2) @@ -6451,8 +6451,8 @@ AS BEGIN SET @user_perm_sql += N' - SELECT @user_perm_gb = CASE WHEN (pages_kb / 128.0 / 1024.) >= 2. - THEN CONVERT(DECIMAL(38, 2), (pages_kb / 128.0 / 1024.)) + SELECT @user_perm_gb = CASE WHEN (pages_kb / 1024. / 1024.) >= 2. + THEN CONVERT(DECIMAL(38, 2), (pages_kb / 1024. / 1024.)) ELSE NULL END FROM sys.dm_os_memory_clerks @@ -7282,7 +7282,7 @@ IF @ProductVersionMajor >= 10 'Informational' AS [FindingsGroup] , 'SQL Server is running under an NT Service account' AS [Finding] , 'https://www.brentozar.com/go/setup' AS [URL] , - ( 'I''m running as ' + [service_account] + '. I wish I had an Active Directory service account instead.' + ( 'I''m running as ' + [service_account] + '.' ) AS [Details] FROM [sys].[dm_server_services] @@ -7322,7 +7322,7 @@ IF @ProductVersionMajor >= 10 'Informational' AS [FindingsGroup] , 'SQL Server Agent is running under an NT Service account' AS [Finding] , 'https://www.brentozar.com/go/setup' AS [URL] , - ( 'I''m running as ' + [service_account] + '. I wish I had an Active Directory service account instead.' + ( 'I''m running as ' + [service_account] + '.' ) AS [Details] FROM [sys].[dm_server_services] @@ -11223,10 +11223,10 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 inner join ( SELECT DISTINCT SUBSTRING(volume_mount_point, 1, 1) AS volume_mount_point - ,CASE WHEN ISNULL(logical_volume_name,'''') = '''' THEN '''' ELSE '' ('' + logical_volume_name + '')'' END AS logical_volume_name + ,CASE WHEN ISNULL(logical_volume_name,'''') = '''' THEN '''' ELSE ''('' + logical_volume_name + '')'' END AS logical_volume_name ,total_bytes/1024/1024 AS total_MB ,available_bytes/1024/1024 AS available_MB - ,(CONVERT(DECIMAL(4,2),(total_bytes/1.0 - available_bytes)/total_bytes * 100)) AS used_percent + ,(CONVERT(DECIMAL(5,2),(total_bytes/1.0 - available_bytes)/total_bytes * 100)) AS used_percent FROM (SELECT TOP 1 WITH TIES database_id @@ -11260,7 +11260,7 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 + '' drive'' ELSE CAST(CAST(i.available_MB/1024 AS NUMERIC(18,2)) AS VARCHAR(30)) + '' GB free on '' + i.drive - + '' drive'' + i.logical_volume_name + + '' drive '' + i.logical_volume_name + '' out of '' + CAST(CAST(i.total_MB/1024 AS NUMERIC(18,2)) AS VARCHAR(30)) + '' GB total ('' + CAST(i.used_percent AS VARCHAR(30)) + ''%)'' END AS Details @@ -12213,6 +12213,896 @@ EXEC [dbo].[sp_Blitz] @CheckProcedureCacheFilter = NULL, @CheckServerInfo = 1 */ +SET ANSI_NULLS ON; +SET QUOTED_IDENTIFIER ON + +IF NOT EXISTS (SELECT * FROM sys.objects WHERE [object_id] = OBJECT_ID(N'[dbo].[sp_BlitzAnalysis]') AND [type] in (N'P', N'PC')) +BEGIN +EXEC dbo.sp_executesql @statement = N'CREATE PROCEDURE [dbo].[sp_BlitzAnalysis] AS' +END +GO + +ALTER PROCEDURE [dbo].[sp_BlitzAnalysis] ( +@Help TINYINT = 0, +@StartDate DATETIMEOFFSET(7) = NULL, +@EndDate DATETIMEOFFSET(7) = NULL, +@OutputDatabaseName NVARCHAR(256) = 'DBAtools', +@OutputSchemaName NVARCHAR(256) = N'dbo', +@OutputTableNameBlitzFirst NVARCHAR(256) = N'BlitzFirst', +@OutputTableNameFileStats NVARCHAR(256) = N'BlitzFirst_FileStats', +@OutputTableNamePerfmonStats NVARCHAR(256) = N'BlitzFirst_PerfmonStats', +@OutputTableNameWaitStats NVARCHAR(256) = N'BlitzFirst_WaitStats', +@OutputTableNameBlitzCache NVARCHAR(256) = N'BlitzCache', +@OutputTableNameBlitzWho NVARCHAR(256) = N'BlitzWho', +@Servername NVARCHAR(128) = @@SERVERNAME, +@Databasename NVARCHAR(128) = NULL, +@BlitzCacheSortorder NVARCHAR(20) = N'cpu', +@MaxBlitzFirstPriority INT = 249, +@ReadLatencyThreshold INT = 100, +@WriteLatencyThreshold INT = 100, +@WaitStatsTop TINYINT = 10, +@Version VARCHAR(30) = NULL OUTPUT, +@VersionDate DATETIME = NULL OUTPUT, +@VersionCheckMode BIT = 0, +@BringThePain BIT = 0, +@Maxdop INT = 1, +@Debug BIT = 0 +) +AS +SET NOCOUNT ON; + +SELECT @Version = '8.03', @VersionDate = '20210420'; + +IF(@VersionCheckMode = 1) +BEGIN + RETURN; +END; + +IF (@Help = 1) +BEGIN + PRINT 'EXEC sp_BlitzAnalysis +@StartDate = NULL, /* Specify a datetime or NULL will get an hour ago */ +@EndDate = NULL, /* Specify a datetime or NULL will get an hour of data since @StartDate */ +@OutputDatabaseName = N''DBA'', /* Specify the database name where where we can find your logged blitz data */ +@OutputSchemaName = N''dbo'', /* Specify the schema */ +@OutputTableNameBlitzFirst = N''BlitzFirst'', /* Table name where you are storing sp_BlitzFirst output, Set to NULL to ignore */ +@OutputTableNameFileStats = N''BlitzFirst_FileStats'', /* Table name where you are storing sp_BlitzFirst filestats output, Set to NULL to ignore */ +@OutputTableNamePerfmonStats = N''BlitzFirst_PerfmonStats'', /* Table name where you are storing sp_BlitzFirst Perfmon output, Set to NULL to ignore */ +@OutputTableNameWaitStats = N''BlitzFirst_WaitStats'', /* Table name where you are storing sp_BlitzFirst Wait stats output, Set to NULL to ignore */ +@OutputTableNameBlitzCache = N''BlitzCache'', /* Table name where you are storing sp_BlitzCache output, Set to NULL to ignore */ +@OutputTableNameBlitzWho = N''BlitzWho'', /* Table name where you are storing sp_BlitzWho output, Set to NULL to ignore */ +@Databasename = NULL, /* Filters results for BlitzCache, FileStats (will also include tempdb), BlitzWho. Leave as NULL for all databases */ +@MaxBlitzFirstPriority = 249, /* Max priority to include in the results */ +@BlitzCacheSortorder = ''cpu'', /* Accepted values ''all'' ''cpu'' ''reads'' ''writes'' ''duration'' ''executions'' ''memory grant'' ''spills'' */ +@WaitStatsTop = 3, /* Controls the top for wait stats only */ +@Maxdop = 1, /* Control the degree of parallelism that the queries within this proc can use if they want to*/ +@Debug = 0; /* Show sp_BlitzAnalysis SQL commands in the messages tab as they execute */ + +/* +Additional parameters: +@ReadLatencyThreshold INT /* Default: 100 - Sets the threshold in ms to compare against io_stall_read_average_ms in your filestats table */ +@WriteLatencyThreshold INT /* Default: 100 - Sets the threshold in ms to compare against io_stall_write_average_ms in your filestats table */ +@BringThePain BIT /* Default: 0 - If you are getting more than 4 hours of data with blitzcachesortorder set to ''all'' you will need to set BringThePain to 1 */ +*/'; + RETURN; +END + +/* Declare all local variables required */ +DECLARE @FullOutputTableNameBlitzFirst NVARCHAR(1000); +DECLARE @FullOutputTableNameFileStats NVARCHAR(1000); +DECLARE @FullOutputTableNamePerfmonStats NVARCHAR(1000); +DECLARE @FullOutputTableNameWaitStats NVARCHAR(1000); +DECLARE @FullOutputTableNameBlitzCache NVARCHAR(1000); +DECLARE @FullOutputTableNameBlitzWho NVARCHAR(1000); +DECLARE @Sql NVARCHAR(MAX); +DECLARE @NewLine NVARCHAR(2) = CHAR(13); +DECLARE @IncludeMemoryGrants BIT; +DECLARE @IncludeSpills BIT; + +/* Validate the database name */ +IF (DB_ID(@OutputDatabaseName) IS NULL) +BEGIN + RAISERROR('Invalid database name provided for parameter @OutputDatabaseName: %s',11,0,@OutputDatabaseName); + RETURN; +END + +/* Set fully qualified table names */ +SET @FullOutputTableNameBlitzFirst = QUOTENAME(@OutputDatabaseName)+N'.'+QUOTENAME(@OutputSchemaName)+N'.'+QUOTENAME(@OutputTableNameBlitzFirst); +SET @FullOutputTableNameFileStats = QUOTENAME(@OutputDatabaseName)+N'.'+QUOTENAME(@OutputSchemaName)+N'.'+QUOTENAME(@OutputTableNameFileStats+N'_Deltas'); +SET @FullOutputTableNamePerfmonStats = QUOTENAME(@OutputDatabaseName)+N'.'+QUOTENAME(@OutputSchemaName)+N'.'+QUOTENAME(@OutputTableNamePerfmonStats+N'_Actuals'); +SET @FullOutputTableNameWaitStats = QUOTENAME(@OutputDatabaseName)+N'.'+QUOTENAME(@OutputSchemaName)+N'.'+QUOTENAME(@OutputTableNameWaitStats+N'_Deltas'); +SET @FullOutputTableNameBlitzCache = QUOTENAME(@OutputDatabaseName)+N'.'+QUOTENAME(@OutputSchemaName)+N'.'+QUOTENAME(@OutputTableNameBlitzCache); +SET @FullOutputTableNameBlitzWho = QUOTENAME(@OutputDatabaseName)+N'.'+QUOTENAME(@OutputSchemaName)+N'.'+QUOTENAME(@OutputTableNameBlitzWho+N'_Deltas'); + +IF OBJECT_ID('tempdb.dbo.#BlitzFirstCounts') IS NOT NULL +BEGIN + DROP TABLE #BlitzFirstCounts; +END + +CREATE TABLE #BlitzFirstCounts ( + [Priority] TINYINT NOT NULL, + [FindingsGroup] VARCHAR(50) NOT NULL, + [Finding] VARCHAR(200) NOT NULL, + [TotalOccurrences] INT NULL, + [FirstOccurrence] DATETIMEOFFSET(7) NULL, + [LastOccurrence] DATETIMEOFFSET(7) NULL +); + +/* Validate variables and set defaults as required */ +IF (@BlitzCacheSortorder IS NULL) +BEGIN + SET @BlitzCacheSortorder = N'cpu'; +END + +SET @BlitzCacheSortorder = LOWER(@BlitzCacheSortorder); + +IF (@OutputTableNameBlitzCache IS NOT NULL AND @BlitzCacheSortorder NOT IN (N'all',N'cpu',N'reads',N'writes',N'duration',N'executions',N'memory grant',N'spills')) +BEGIN + RAISERROR('Invalid sort option specified for @BlitzCacheSortorder, supported values are ''all'', ''cpu'', ''reads'', ''writes'', ''duration'', ''executions'', ''memory grant'', ''spills''',11,0) WITH NOWAIT; + RETURN; +END + +/* Set @Maxdop to 1 if NULL was passed in */ +IF (@Maxdop IS NULL) +BEGIN + SET @Maxdop = 1; +END + +/* iF @Maxdop is set higher than the core count just set it to 0 */ +IF (@Maxdop > (SELECT CAST(cpu_count AS INT) FROM sys.dm_os_sys_info)) +BEGIN + SET @Maxdop = 0; +END + +/* We need to check if your SQL version has memory grant and spills columns in sys.dm_exec_query_stats */ +SELECT @IncludeMemoryGrants = + CASE + WHEN (EXISTS(SELECT * FROM sys.all_columns WHERE [object_id] = OBJECT_ID('sys.dm_exec_query_stats') AND name = 'max_grant_kb')) THEN 1 + ELSE 0 + END; + +SELECT @IncludeSpills = + CASE + WHEN (EXISTS(SELECT * FROM sys.all_columns WHERE [object_id] = OBJECT_ID('sys.dm_exec_query_stats') AND name = 'max_spills')) THEN 1 + ELSE 0 + END; + + +IF (@StartDate IS NULL) +BEGIN + RAISERROR('Setting @StartDate to: 1 hour ago',0,0) WITH NOWAIT; + /* Set StartDate to be an hour ago */ + SET @StartDate = DATEADD(HOUR,-1,SYSDATETIMEOFFSET()); + + IF (@EndDate IS NULL) + BEGIN + RAISERROR('Setting @EndDate to: Now',0,0) WITH NOWAIT; + /* Get data right up to now */ + SET @EndDate = SYSDATETIMEOFFSET(); + END +END + +IF (@EndDate IS NULL) +BEGIN + /* Default to an hour of data or SYSDATETIMEOFFSET() if now is earlier than the hour added to @StartDate */ + IF(DATEADD(HOUR,1,@StartDate) < SYSDATETIMEOFFSET()) + BEGIN + RAISERROR('@EndDate was NULL - Setting to return 1 hour of information, if you want more then set @EndDate aswell',0,0) WITH NOWAIT; + SET @EndDate = DATEADD(HOUR,1,@StartDate); + END + ELSE + BEGIN + RAISERROR('@EndDate was NULL - Setting to SYSDATETIMEOFFSET()',0,0) WITH NOWAIT; + SET @EndDate = SYSDATETIMEOFFSET(); + END +END + +/* Default to dbo schema if NULL is passed in */ +IF (@OutputSchemaName IS NULL) +BEGIN + SET @OutputSchemaName = 'dbo'; +END + +/* Prompt the user for @BringThePain = 1 if they are searching a timeframe greater than 4 hours and they are using BlitzCacheSortorder = 'all' */ +IF(@BlitzCacheSortorder = 'all' AND DATEDIFF(HOUR,@StartDate,@EndDate) > 4 AND @BringThePain = 0) +BEGIN + RAISERROR('Wow! hold up now, are you sure you wanna do this? Are sure you want to query over 4 hours of data with @BlitzCacheSortorder set to ''all''? IF you do then set @BringThePain = 1 but I gotta warn you this might hurt a bit!',11,1) WITH NOWAIT; + RETURN; +END + +/* Output report window information */ +SELECT + @Servername AS [ServerToReportOn], + CAST(1 AS NVARCHAR(20)) + N' - '+ CAST(@MaxBlitzFirstPriority AS NVARCHAR(20)) AS [PrioritesToInclude], + @StartDate AS [StartDatetime], + @EndDate AS [EndDatetime];; + + +/* BlitzFirst data */ +SET @Sql = N' +INSERT INTO #BlitzFirstCounts ([Priority],[FindingsGroup],[Finding],[TotalOccurrences],[FirstOccurrence],[LastOccurrence]) +SELECT +[Priority], +[FindingsGroup], +[Finding], +COUNT(*) AS [TotalOccurrences], +MIN(CheckDate) AS [FirstOccurrence], +MAX(CheckDate) AS [LastOccurrence] +FROM '+@FullOutputTableNameBlitzFirst+N' +WHERE [ServerName] = @Servername +AND [Priority] BETWEEN 1 AND @MaxBlitzFirstPriority +AND CheckDate BETWEEN @StartDate AND @EndDate +AND [CheckID] > -1 +GROUP BY [Priority],[FindingsGroup],[Finding]; + +IF EXISTS(SELECT 1 FROM #BlitzFirstCounts) +BEGIN + SELECT + [Priority], + [FindingsGroup], + [Finding], + [TotalOccurrences], + [FirstOccurrence], + [LastOccurrence] + FROM #BlitzFirstCounts + ORDER BY [Priority] ASC,[TotalOccurrences] DESC; +END +ELSE +BEGIN + SELECT N''No findings with a priority between 1 and ''+CAST(@MaxBlitzFirstPriority AS NVARCHAR(10))+N'' found for this period''; +END +SELECT + [ServerName] +,[CheckDate] +,[CheckID] +,[Priority] +,[Finding] +,[URL] +,[Details] +,[HowToStopIt] +,[QueryPlan] +,[QueryText] +FROM '+@FullOutputTableNameBlitzFirst+N' Findings +WHERE [ServerName] = @Servername +AND [Priority] BETWEEN 1 AND @MaxBlitzFirstPriority +AND [CheckDate] BETWEEN @StartDate AND @EndDate +AND [CheckID] > -1 +ORDER BY CheckDate ASC,[Priority] ASC +OPTION (RECOMPILE, MAXDOP '+CAST(@Maxdop AS NVARCHAR(2))+N');'; + + +RAISERROR('Getting BlitzFirst info from %s',0,0,@FullOutputTableNameBlitzFirst) WITH NOWAIT; + +IF (@Debug = 1) +BEGIN + PRINT @Sql; +END + +IF (OBJECT_ID(@FullOutputTableNameBlitzFirst) IS NULL) +BEGIN + IF (@OutputTableNameBlitzFirst IS NULL) + BEGIN + RAISERROR('BlitzFirst data skipped',10,0); + SELECT N'Skipped logged BlitzFirst data as NULL was passed to parameter @OutputTableNameBlitzFirst'; + END + ELSE + BEGIN + RAISERROR('Table provided for BlitzFirst data: %s does not exist',10,0,@FullOutputTableNameBlitzFirst); + SELECT N'No BlitzFirst data available as the table cannot be found'; + END + +END +ELSE /* Table exists then run the query */ +BEGIN + EXEC sp_executesql @Sql, + N'@StartDate DATETIMEOFFSET(7), + @EndDate DATETIMEOFFSET(7), + @Servername NVARCHAR(128), + @MaxBlitzFirstPriority INT', + @StartDate=@StartDate, + @EndDate=@EndDate, + @Servername=@Servername, + @MaxBlitzFirstPriority = @MaxBlitzFirstPriority; +END + +/* Blitz WaitStats data */ +SET @Sql = N'SELECT +[ServerName], +[CheckDate], +[wait_type], +[WaitsRank], +[WaitCategory], +[Ignorable], +[ElapsedSeconds], +[wait_time_ms_delta], +[wait_time_minutes_delta], +[wait_time_minutes_per_minute], +[signal_wait_time_ms_delta], +[waiting_tasks_count_delta], +ISNULL((CAST([wait_time_ms_delta] AS DECIMAL(38,2))/NULLIF(CAST([waiting_tasks_count_delta] AS DECIMAL(38,2)),0)),0) AS [wait_time_ms_per_wait] +FROM +( + SELECT + [ServerName], + [CheckDate], + [wait_type], + [WaitCategory], + [Ignorable], + [ElapsedSeconds], + [wait_time_ms_delta], + [wait_time_minutes_delta], + [wait_time_minutes_per_minute], + [signal_wait_time_ms_delta], + [waiting_tasks_count_delta], + ROW_NUMBER() OVER(PARTITION BY [CheckDate] ORDER BY [CheckDate] ASC,[wait_time_ms_delta] DESC) AS [WaitsRank] + FROM '+@FullOutputTableNameWaitStats+N' AS [Waits] + WHERE [ServerName] = @Servername + AND [CheckDate] BETWEEN @StartDate AND @EndDate +) TopWaits +WHERE [WaitsRank] <= @WaitStatsTop +ORDER BY +[CheckDate] ASC, +[wait_time_ms_delta] DESC +OPTION(RECOMPILE, MAXDOP '+CAST(@Maxdop AS NVARCHAR(2))+N');' + +RAISERROR('Getting wait stats info from %s',0,0,@FullOutputTableNameWaitStats) WITH NOWAIT; + +IF (@Debug = 1) +BEGIN + PRINT @Sql; +END + +IF (OBJECT_ID(@FullOutputTableNameWaitStats) IS NULL) +BEGIN + IF (@OutputTableNameWaitStats IS NULL) + BEGIN + RAISERROR('Wait stats data skipped',10,0); + SELECT N'Skipped logged wait stats data as NULL was passed to parameter @OutputTableNameWaitStats'; + END + ELSE + BEGIN + RAISERROR('Table provided for wait stats data: %s does not exist',10,0,@FullOutputTableNameWaitStats); + SELECT N'No wait stats data available as the table cannot be found'; + END +END +ELSE /* Table exists then run the query */ +BEGIN + EXEC sp_executesql @Sql, + N'@StartDate DATETIMEOFFSET(7), + @EndDate DATETIMEOFFSET(7), + @Servername NVARCHAR(128), + @WaitStatsTop TINYINT', + @StartDate=@StartDate, + @EndDate=@EndDate, + @Servername=@Servername, + @WaitStatsTop=@WaitStatsTop; +END + +/* BlitzFileStats info */ +SET @Sql = N' +SELECT +[ServerName], +[CheckDate], +CASE + WHEN MAX([io_stall_read_ms_average]) > @ReadLatencyThreshold THEN ''Yes'' + WHEN MAX([io_stall_write_ms_average]) > @WriteLatencyThreshold THEN ''Yes'' + ELSE ''No'' +END AS [io_stall_ms_breached], +LEFT([PhysicalName],LEN([PhysicalName])-CHARINDEX(''\'',REVERSE([PhysicalName]))+1) AS [PhysicalPath], +SUM([SizeOnDiskMB]) AS [SizeOnDiskMB], +SUM([SizeOnDiskMBgrowth]) AS [SizeOnDiskMBgrowth], +MAX([io_stall_read_ms]) AS [max_io_stall_read_ms], +MAX([io_stall_read_ms_average]) AS [max_io_stall_read_ms_average], +@ReadLatencyThreshold AS [is_stall_read_ms_threshold], +SUM([num_of_reads]) AS [num_of_reads], +SUM([megabytes_read]) AS [megabytes_read], +MAX([io_stall_write_ms]) AS [max_io_stall_write_ms], +MAX([io_stall_write_ms_average]) AS [max_io_stall_write_ms_average], +@WriteLatencyThreshold AS [io_stall_write_ms_average], +SUM([num_of_writes]) AS [num_of_writes], +SUM([megabytes_written]) AS [megabytes_written] +FROM '+@FullOutputTableNameFileStats+N' +WHERE [ServerName] = @Servername +AND [CheckDate] BETWEEN @StartDate AND @EndDate +' ++CASE + WHEN @Databasename IS NOT NULL THEN N'AND [DatabaseName] IN (N''tempdb'',@Databasename) +' + ELSE N'' +END ++N'GROUP BY +[ServerName], +[CheckDate], +LEFT([PhysicalName],LEN([PhysicalName])-CHARINDEX(''\'',REVERSE([PhysicalName]))+1) +ORDER BY +[CheckDate] ASC +OPTION (RECOMPILE, MAXDOP '+CAST(@Maxdop AS NVARCHAR(2))+N');' + +RAISERROR('Getting FileStats info from %s',0,0,@FullOutputTableNameFileStats) WITH NOWAIT; + +IF (@Debug = 1) +BEGIN + PRINT @Sql; +END + +IF (OBJECT_ID(@FullOutputTableNameFileStats) IS NULL) +BEGIN + IF (@OutputTableNameFileStats IS NULL) + BEGIN + RAISERROR('File stats data skipped',10,0); + SELECT N'Skipped logged File stats data as NULL was passed to parameter @OutputTableNameFileStats'; + END + ELSE + BEGIN + RAISERROR('Table provided for FileStats data: %s does not exist',10,0,@FullOutputTableNameFileStats); + SELECT N'No File stats data available as the table cannot be found'; + END +END +ELSE /* Table exists then run the query */ +BEGIN + EXEC sp_executesql @Sql, + N'@StartDate DATETIMEOFFSET(7), + @EndDate DATETIMEOFFSET(7), + @Servername NVARCHAR(128), + @Databasename NVARCHAR(128), + @ReadLatencyThreshold INT, + @WriteLatencyThreshold INT', + @StartDate=@StartDate, + @EndDate=@EndDate, + @Servername=@Servername, + @Databasename = @Databasename, + @ReadLatencyThreshold = @ReadLatencyThreshold, + @WriteLatencyThreshold = @WriteLatencyThreshold; +END + +/* Blitz Perfmon stats*/ +SET @Sql = N' +SELECT + [ServerName] + ,[CheckDate] + ,[counter_name] + ,[object_name] + ,[instance_name] + ,[cntr_value] +FROM '+@FullOutputTableNamePerfmonStats+N' +WHERE [ServerName] = @Servername +AND CheckDate BETWEEN @StartDate AND @EndDate +ORDER BY + [CheckDate] ASC, + [counter_name] ASC +OPTION (RECOMPILE, MAXDOP '+CAST(@Maxdop AS NVARCHAR(2))+N');' + +RAISERROR('Getting Perfmon info from %s',0,0,@FullOutputTableNamePerfmonStats) WITH NOWAIT; + +IF (@Debug = 1) +BEGIN + PRINT @Sql; +END + +IF (OBJECT_ID(@FullOutputTableNamePerfmonStats) IS NULL) +BEGIN + IF (@OutputTableNamePerfmonStats IS NULL) + BEGIN + RAISERROR('Perfmon stats data skipped',10,0); + SELECT N'Skipped logged Perfmon stats data as NULL was passed to parameter @OutputTableNamePerfmonStats'; + END + ELSE + BEGIN + RAISERROR('Table provided for Perfmon stats data: %s does not exist',10,0,@FullOutputTableNamePerfmonStats); + SELECT N'No Perfmon data available as the table cannot be found'; + END +END +ELSE /* Table exists then run the query */ +BEGIN + EXEC sp_executesql @Sql, + N'@StartDate DATETIMEOFFSET(7), + @EndDate DATETIMEOFFSET(7), + @Servername NVARCHAR(128)', + @StartDate=@StartDate, + @EndDate=@EndDate, + @Servername=@Servername; +END + +/* Blitz cache data */ +RAISERROR('Sortorder for BlitzCache data: %s',0,0,@BlitzCacheSortorder) WITH NOWAIT; + +/* Set intial CTE */ +SET @Sql = N'WITH CheckDates AS ( +SELECT DISTINCT CheckDate +FROM ' ++@FullOutputTableNameBlitzCache ++N' +WHERE [ServerName] = @Servername +AND [CheckDate] BETWEEN @StartDate AND @EndDate' ++@NewLine ++CASE + WHEN @Databasename IS NOT NULL THEN N'AND [DatabaseName] = @Databasename'+@NewLine + ELSE N'' +END ++N')' +; + +SET @Sql += @NewLine; + +/* Append additional CTEs based on sortorder */ +SET @Sql += ( +SELECT CAST(N',' AS NVARCHAR(MAX)) ++[SortOptions].[Aliasname]+N' AS ( +SELECT + [ServerName] + ,'+[SortOptions].[Aliasname]+N'.[CheckDate] + ,[Sortorder] + ,[TimeFrameRank] + ,ROW_NUMBER() OVER(ORDER BY ['+[SortOptions].[Columnname]+N'] DESC) AS [OverallRank] + ,'+[SortOptions].[Aliasname]+N'.[QueryType] + ,'+[SortOptions].[Aliasname]+N'.[QueryText] + ,'+[SortOptions].[Aliasname]+N'.[DatabaseName] + ,'+[SortOptions].[Aliasname]+N'.[AverageCPU] + ,'+[SortOptions].[Aliasname]+N'.[TotalCPU] + ,'+[SortOptions].[Aliasname]+N'.[PercentCPUByType] + ,'+[SortOptions].[Aliasname]+N'.[AverageDuration] + ,'+[SortOptions].[Aliasname]+N'.[TotalDuration] + ,'+[SortOptions].[Aliasname]+N'.[PercentDurationByType] + ,'+[SortOptions].[Aliasname]+N'.[AverageReads] + ,'+[SortOptions].[Aliasname]+N'.[TotalReads] + ,'+[SortOptions].[Aliasname]+N'.[PercentReadsByType] + ,'+[SortOptions].[Aliasname]+N'.[AverageWrites] + ,'+[SortOptions].[Aliasname]+N'.[TotalWrites] + ,'+[SortOptions].[Aliasname]+N'.[PercentWritesByType] + ,'+[SortOptions].[Aliasname]+N'.[ExecutionCount] + ,'+[SortOptions].[Aliasname]+N'.[ExecutionWeight] + ,'+[SortOptions].[Aliasname]+N'.[PercentExecutionsByType] + ,'+[SortOptions].[Aliasname]+N'.[ExecutionsPerMinute] + ,'+[SortOptions].[Aliasname]+N'.[PlanCreationTime] + ,'+[SortOptions].[Aliasname]+N'.[PlanCreationTimeHours] + ,'+[SortOptions].[Aliasname]+N'.[LastExecutionTime] + ,'+[SortOptions].[Aliasname]+N'.[PlanHandle] + ,'+[SortOptions].[Aliasname]+N'.[SqlHandle] + ,'+[SortOptions].[Aliasname]+N'.[SQL Handle More Info] + ,'+[SortOptions].[Aliasname]+N'.[QueryHash] + ,'+[SortOptions].[Aliasname]+N'.[Query Hash More Info] + ,'+[SortOptions].[Aliasname]+N'.[QueryPlanHash] + ,'+[SortOptions].[Aliasname]+N'.[StatementStartOffset] + ,'+[SortOptions].[Aliasname]+N'.[StatementEndOffset] + ,'+[SortOptions].[Aliasname]+N'.[MinReturnedRows] + ,'+[SortOptions].[Aliasname]+N'.[MaxReturnedRows] + ,'+[SortOptions].[Aliasname]+N'.[AverageReturnedRows] + ,'+[SortOptions].[Aliasname]+N'.[TotalReturnedRows] + ,'+[SortOptions].[Aliasname]+N'.[QueryPlan] + ,'+[SortOptions].[Aliasname]+N'.[NumberOfPlans] + ,'+[SortOptions].[Aliasname]+N'.[NumberOfDistinctPlans] + ,'+[SortOptions].[Aliasname]+N'.[MinGrantKB] + ,'+[SortOptions].[Aliasname]+N'.[MaxGrantKB] + ,'+[SortOptions].[Aliasname]+N'.[MinUsedGrantKB] + ,'+[SortOptions].[Aliasname]+N'.[MaxUsedGrantKB] + ,'+[SortOptions].[Aliasname]+N'.[PercentMemoryGrantUsed] + ,'+[SortOptions].[Aliasname]+N'.[AvgMaxMemoryGrant] + ,'+[SortOptions].[Aliasname]+N'.[MinSpills] + ,'+[SortOptions].[Aliasname]+N'.[MaxSpills] + ,'+[SortOptions].[Aliasname]+N'.[TotalSpills] + ,'+[SortOptions].[Aliasname]+N'.[AvgSpills] + ,'+[SortOptions].[Aliasname]+N'.[QueryPlanCost] +FROM CheckDates +CROSS APPLY ( + SELECT TOP (5) + [ServerName] + ,'+[SortOptions].[Aliasname]+N'.[CheckDate] + ,'+QUOTENAME(UPPER([SortOptions].[Sortorder]),N'''')+N' AS [Sortorder] + ,ROW_NUMBER() OVER(ORDER BY ['+[SortOptions].[Columnname]+N'] DESC) AS [TimeFrameRank] + ,'+[SortOptions].[Aliasname]+N'.[QueryType] + ,'+[SortOptions].[Aliasname]+N'.[QueryText] + ,'+[SortOptions].[Aliasname]+N'.[DatabaseName] + ,'+[SortOptions].[Aliasname]+N'.[AverageCPU] + ,'+[SortOptions].[Aliasname]+N'.[TotalCPU] + ,'+[SortOptions].[Aliasname]+N'.[PercentCPUByType] + ,'+[SortOptions].[Aliasname]+N'.[AverageDuration] + ,'+[SortOptions].[Aliasname]+N'.[TotalDuration] + ,'+[SortOptions].[Aliasname]+N'.[PercentDurationByType] + ,'+[SortOptions].[Aliasname]+N'.[AverageReads] + ,'+[SortOptions].[Aliasname]+N'.[TotalReads] + ,'+[SortOptions].[Aliasname]+N'.[PercentReadsByType] + ,'+[SortOptions].[Aliasname]+N'.[AverageWrites] + ,'+[SortOptions].[Aliasname]+N'.[TotalWrites] + ,'+[SortOptions].[Aliasname]+N'.[PercentWritesByType] + ,'+[SortOptions].[Aliasname]+N'.[ExecutionCount] + ,'+[SortOptions].[Aliasname]+N'.[ExecutionWeight] + ,'+[SortOptions].[Aliasname]+N'.[PercentExecutionsByType] + ,'+[SortOptions].[Aliasname]+N'.[ExecutionsPerMinute] + ,'+[SortOptions].[Aliasname]+N'.[PlanCreationTime] + ,'+[SortOptions].[Aliasname]+N'.[PlanCreationTimeHours] + ,'+[SortOptions].[Aliasname]+N'.[LastExecutionTime] + ,'+[SortOptions].[Aliasname]+N'.[PlanHandle] + ,'+[SortOptions].[Aliasname]+N'.[SqlHandle] + ,'+[SortOptions].[Aliasname]+N'.[SQL Handle More Info] + ,'+[SortOptions].[Aliasname]+N'.[QueryHash] + ,'+[SortOptions].[Aliasname]+N'.[Query Hash More Info] + ,'+[SortOptions].[Aliasname]+N'.[QueryPlanHash] + ,'+[SortOptions].[Aliasname]+N'.[StatementStartOffset] + ,'+[SortOptions].[Aliasname]+N'.[StatementEndOffset] + ,'+[SortOptions].[Aliasname]+N'.[MinReturnedRows] + ,'+[SortOptions].[Aliasname]+N'.[MaxReturnedRows] + ,'+[SortOptions].[Aliasname]+N'.[AverageReturnedRows] + ,'+[SortOptions].[Aliasname]+N'.[TotalReturnedRows] + ,'+[SortOptions].[Aliasname]+N'.[QueryPlan] + ,'+[SortOptions].[Aliasname]+N'.[NumberOfPlans] + ,'+[SortOptions].[Aliasname]+N'.[NumberOfDistinctPlans] + ,'+[SortOptions].[Aliasname]+N'.[MinGrantKB] + ,'+[SortOptions].[Aliasname]+N'.[MaxGrantKB] + ,'+[SortOptions].[Aliasname]+N'.[MinUsedGrantKB] + ,'+[SortOptions].[Aliasname]+N'.[MaxUsedGrantKB] + ,'+[SortOptions].[Aliasname]+N'.[PercentMemoryGrantUsed] + ,'+[SortOptions].[Aliasname]+N'.[AvgMaxMemoryGrant] + ,'+[SortOptions].[Aliasname]+N'.[MinSpills] + ,'+[SortOptions].[Aliasname]+N'.[MaxSpills] + ,'+[SortOptions].[Aliasname]+N'.[TotalSpills] + ,'+[SortOptions].[Aliasname]+N'.[AvgSpills] + ,'+[SortOptions].[Aliasname]+N'.[QueryPlanCost] + FROM '+@FullOutputTableNameBlitzCache+N' AS '+[SortOptions].[Aliasname]+N' + WHERE [ServerName] = @Servername + AND [CheckDate] BETWEEN @StartDate AND @EndDate + AND ['+[SortOptions].[Aliasname]+N'].[CheckDate] = [CheckDates].[CheckDate]' + +@NewLine + +CASE + WHEN @Databasename IS NOT NULL THEN N'AND ['+[SortOptions].[Aliasname]+N'].[DatabaseName] = @Databasename'+@NewLine + ELSE N'' + END + +CASE + WHEN [Sortorder] = N'cpu' THEN N'AND [TotalCPU] > 0' + WHEN [Sortorder] = N'reads' THEN N'AND [TotalReads] > 0' + WHEN [Sortorder] = N'writes' THEN N'AND [TotalWrites] > 0' + WHEN [Sortorder] = N'duration' THEN N'AND [TotalDuration] > 0' + WHEN [Sortorder] = N'executions' THEN N'AND [ExecutionCount] > 0' + WHEN [Sortorder] = N'memory grant' THEN N'AND [MaxGrantKB] > 0' + WHEN [Sortorder] = N'spills' THEN N'AND [MaxSpills] > 0' + ELSE N'' + END + +N' + ORDER BY ['+[SortOptions].[Columnname]+N'] DESC) '+[SortOptions].[Aliasname]+N' +)' +FROM (VALUES + (N'cpu',N'TopCPU',N'TotalCPU'), + (N'reads',N'TopReads',N'TotalReads'), + (N'writes',N'TopWrites',N'TotalWrites'), + (N'duration',N'TopDuration',N'TotalDuration'), + (N'executions',N'TopExecutions',N'ExecutionCount'), + (N'memory grant',N'TopMemoryGrants',N'MaxGrantKB'), + (N'spills',N'TopSpills',N'MaxSpills') + ) SortOptions(Sortorder,Aliasname,Columnname) +WHERE + CASE /* for spills and memory grant sorts make sure the underlying columns exist in the DMV otherwise do not include them */ + WHEN (@IncludeMemoryGrants = 0 OR @IncludeMemoryGrants IS NULL) AND ([SortOptions].[Sortorder] = N'memory grant' OR [SortOptions].[Sortorder] = N'all') THEN NULL + WHEN (@IncludeSpills = 0 OR @IncludeSpills IS NULL) AND ([SortOptions].[Sortorder] = N'spills' OR [SortOptions].[Sortorder] = N'all') THEN NULL + ELSE [SortOptions].[Sortorder] + END = ISNULL(NULLIF(@BlitzCacheSortorder,N'all'),[SortOptions].[Sortorder]) +FOR XML PATH(N''), TYPE).value(N'.[1]', N'NVARCHAR(MAX)'); + +SET @Sql += @NewLine; + +/* Build the select statements to return the data after CTE declarations */ +SET @Sql += ( +SELECT STUFF(( +SELECT @NewLine ++N'UNION ALL' ++@NewLine ++N'SELECT * +FROM '+[SortOptions].[Aliasname] +FROM (VALUES + (N'cpu',N'TopCPU',N'TotalCPU'), + (N'reads',N'TopReads',N'TotalReads'), + (N'writes',N'TopWrites',N'TotalWrites'), + (N'duration',N'TopDuration',N'TotalDuration'), + (N'executions',N'TopExecutions',N'ExecutionCount'), + (N'memory grant',N'TopMemoryGrants',N'MaxGrantKB'), + (N'spills',N'TopSpills',N'MaxSpills') + ) SortOptions(Sortorder,Aliasname,Columnname) +WHERE + CASE /* for spills and memory grant sorts make sure the underlying columns exist in the DMV otherwise do not include them */ + WHEN (@IncludeMemoryGrants = 0 OR @IncludeMemoryGrants IS NULL) AND ([SortOptions].[Sortorder] = N'memory grant' OR [SortOptions].[Sortorder] = N'all') THEN NULL + WHEN (@IncludeSpills = 0 OR @IncludeSpills IS NULL) AND ([SortOptions].[Sortorder] = N'spills' OR [SortOptions].[Sortorder] = N'all') THEN NULL + ELSE [SortOptions].[Sortorder] + END = ISNULL(NULLIF(@BlitzCacheSortorder,N'all'),[SortOptions].[Sortorder]) +FOR XML PATH(N''), TYPE).value(N'.[1]', N'NVARCHAR(MAX)'),1,11,N'') +); + +/* Append Order By */ +SET @Sql += @NewLine ++N'ORDER BY + [Sortorder] ASC, + [CheckDate] ASC, + [TimeFrameRank] ASC'; + +/* Append OPTION(RECOMPILE, MAXDOP) to complete the statement */ +SET @Sql += @NewLine ++N'OPTION(RECOMPILE, MAXDOP '+CAST(@Maxdop AS NVARCHAR(2))+N');'; + +RAISERROR('Getting BlitzCache info from %s',0,0,@FullOutputTableNameBlitzCache) WITH NOWAIT; + +IF (@Debug = 1) +BEGIN + PRINT SUBSTRING(@Sql, 0, 4000); + PRINT SUBSTRING(@Sql, 4000, 8000); + PRINT SUBSTRING(@Sql, 8000, 12000); + PRINT SUBSTRING(@Sql, 12000, 16000); + PRINT SUBSTRING(@Sql, 16000, 20000); + PRINT SUBSTRING(@Sql, 20000, 24000); + PRINT SUBSTRING(@Sql, 24000, 28000); +END + +IF (OBJECT_ID(@FullOutputTableNameBlitzCache) IS NULL) +BEGIN + IF (@OutputTableNameBlitzCache IS NULL) + BEGIN + RAISERROR('BlitzCache data skipped',10,0); + SELECT N'Skipped logged BlitzCache data as NULL was passed to parameter @OutputTableNameBlitzCache'; + END + ELSE + BEGIN + RAISERROR('Table provided for BlitzCache data: %s does not exist',10,0,@FullOutputTableNameBlitzCache); + SELECT N'No BlitzCache data available as the table cannot be found'; + END +END +ELSE /* Table exists then run the query */ +BEGIN + EXEC sp_executesql @Sql, + N'@Servername NVARCHAR(128), + @Databasename NVARCHAR(128), + @BlitzCacheSortorder NVARCHAR(20), + @StartDate DATETIMEOFFSET(7), + @EndDate DATETIMEOFFSET(7)', + @Servername = @Servername, + @Databasename = @Databasename, + @BlitzCacheSortorder = @BlitzCacheSortorder, + @StartDate = @StartDate, + @EndDate = @EndDate; +END + + + +/* BlitzWho data */ +SET @Sql = N' +SELECT [ServerName] + ,[CheckDate] + ,[elapsed_time] + ,[session_id] + ,[database_name] + ,[query_text_snippet] + ,[query_plan] + ,[live_query_plan] + ,[query_cost] + ,[status] + ,[wait_info] + ,[top_session_waits] + ,[blocking_session_id] + ,[open_transaction_count] + ,[is_implicit_transaction] + ,[nt_domain] + ,[host_name] + ,[login_name] + ,[nt_user_name] + ,[program_name] + ,[fix_parameter_sniffing] + ,[client_interface_name] + ,[login_time] + ,[start_time] + ,[request_time] + ,[request_cpu_time] + ,[degree_of_parallelism] + ,[request_logical_reads] + ,[Logical_Reads_MB] + ,[request_writes] + ,[Logical_Writes_MB] + ,[request_physical_reads] + ,[Physical_reads_MB] + ,[session_cpu] + ,[session_logical_reads] + ,[session_logical_reads_MB] + ,[session_physical_reads] + ,[session_physical_reads_MB] + ,[session_writes] + ,[session_writes_MB] + ,[tempdb_allocations_mb] + ,[memory_usage] + ,[estimated_completion_time] + ,[percent_complete] + ,[deadlock_priority] + ,[transaction_isolation_level] + ,[last_dop] + ,[min_dop] + ,[max_dop] + ,[last_grant_kb] + ,[min_grant_kb] + ,[max_grant_kb] + ,[last_used_grant_kb] + ,[min_used_grant_kb] + ,[max_used_grant_kb] + ,[last_ideal_grant_kb] + ,[min_ideal_grant_kb] + ,[max_ideal_grant_kb] + ,[last_reserved_threads] + ,[min_reserved_threads] + ,[max_reserved_threads] + ,[last_used_threads] + ,[min_used_threads] + ,[max_used_threads] + ,[grant_time] + ,[requested_memory_kb] + ,[grant_memory_kb] + ,[is_request_granted] + ,[required_memory_kb] + ,[query_memory_grant_used_memory_kb] + ,[ideal_memory_kb] + ,[is_small] + ,[timeout_sec] + ,[resource_semaphore_id] + ,[wait_order] + ,[wait_time_ms] + ,[next_candidate_for_memory_grant] + ,[target_memory_kb] + ,[max_target_memory_kb] + ,[total_memory_kb] + ,[available_memory_kb] + ,[granted_memory_kb] + ,[query_resource_semaphore_used_memory_kb] + ,[grantee_count] + ,[waiter_count] + ,[timeout_error_count] + ,[forced_grant_count] + ,[workload_group_name] + ,[resource_pool_name] + ,[context_info] + ,[query_hash] + ,[query_plan_hash] + ,[sql_handle] + ,[plan_handle] + ,[statement_start_offset] + ,[statement_end_offset] + FROM '+@FullOutputTableNameBlitzWho+N' + WHERE [ServerName] = @Servername + AND ([CheckDate] BETWEEN @StartDate AND @EndDate OR [start_time] BETWEEN CAST(@StartDate AS DATETIME) AND CAST(@EndDate AS DATETIME)) + ' + +CASE + WHEN @Databasename IS NOT NULL THEN N'AND [database_name] = @Databasename + ' + ELSE N'' + END ++N'ORDER BY [CheckDate] ASC + OPTION (RECOMPILE, MAXDOP '+CAST(@Maxdop AS NVARCHAR(2))+N');'; + +RAISERROR('Getting BlitzWho info from %s',0,0,@FullOutputTableNameBlitzWho) WITH NOWAIT; + +IF (@Debug = 1) +BEGIN + PRINT @Sql; +END + +IF (OBJECT_ID(@FullOutputTableNameBlitzWho) IS NULL) +BEGIN + IF (@OutputTableNameBlitzWho IS NULL) + BEGIN + RAISERROR('BlitzWho data skipped',10,0); + SELECT N'Skipped logged BlitzWho data as NULL was passed to parameter @OutputTableNameBlitzWho'; + END + ELSE + BEGIN + RAISERROR('Table provided for BlitzWho data: %s does not exist',10,0,@FullOutputTableNameBlitzWho); + SELECT N'No BlitzWho data available as the table cannot be found'; + END +END +ELSE +BEGIN + EXEC sp_executesql @Sql, + N'@StartDate DATETIMEOFFSET(7), + @EndDate DATETIMEOFFSET(7), + @Servername NVARCHAR(128), + @Databasename NVARCHAR(128)', + @StartDate=@StartDate, + @EndDate=@EndDate, + @Servername=@Servername, + @Databasename = @Databasename; +END + + +GO IF OBJECT_ID('dbo.sp_BlitzBackups') IS NULL EXEC ('CREATE PROCEDURE dbo.sp_BlitzBackups AS RETURN 0;'); GO @@ -12238,7 +13128,7 @@ AS SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.02', @VersionDate = '20210322'; + SELECT @Version = '8.03', @VersionDate = '20210420'; IF(@VersionCheckMode = 1) BEGIN @@ -14018,7 +14908,7 @@ BEGIN SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.02', @VersionDate = '20210322'; +SELECT @Version = '8.03', @VersionDate = '20210420'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -17721,7 +18611,7 @@ SET s.variable_datatype = CASE WHEN s.variable_datatype LIKE '%(%)%' s.compile_time_value = CASE WHEN s.compile_time_value LIKE '%(%)%' THEN SUBSTRING(s.compile_time_value, CHARINDEX('(', s.compile_time_value) + 1, - CHARINDEX(')', s.compile_time_value) - 1 - CHARINDEX('(', s.compile_time_value) + CHARINDEX(')', s.compile_time_value, CHARINDEX('(', s.compile_time_value) + 1) - 1 - CHARINDEX('(', s.compile_time_value) ) WHEN variable_datatype NOT IN ('bit', 'tinyint', 'smallint', 'int', 'bigint') AND s.variable_datatype NOT LIKE '%binary%' @@ -20681,20 +21571,23 @@ ELSE IF @ValidOutputLocation = 1 BEGIN - SET @StringToExecute = 'USE ' + SET @StringToExecute = N'USE ' + @OutputDatabaseName - + '; IF EXISTS(SELECT * FROM ' + + N'; IF EXISTS(SELECT * FROM ' + @OutputDatabaseName - + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' + + N'.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' + @OutputSchemaName - + ''') AND NOT EXISTS (SELECT * FROM ' + + N''') AND NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName - + '.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''' - + @OutputSchemaName + ''' AND QUOTENAME(TABLE_NAME) = ''' - + @OutputTableName + ''') CREATE TABLE ' - + @OutputSchemaName + '.' + + N'.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''' + + @OutputSchemaName + N''' AND QUOTENAME(TABLE_NAME) = ''' + + @OutputTableName + N''') CREATE TABLE ' + + @OutputSchemaName + N'.' + @OutputTableName - + N'(ID bigint NOT NULL IDENTITY(1,1), + + CONVERT + ( + nvarchar(MAX), + N'(ID bigint NOT NULL IDENTITY(1,1), ServerName NVARCHAR(258), CheckDate DATETIMEOFFSET, Version NVARCHAR(258), @@ -20770,27 +21663,28 @@ ELSE AvgSpills MONEY, QueryPlanCost FLOAT, JoinKey AS ServerName + Cast(CheckDate AS NVARCHAR(50)), - CONSTRAINT [PK_' + REPLACE(REPLACE(@OutputTableName,'[',''),']','') + '] PRIMARY KEY CLUSTERED(ID ASC));'; + CONSTRAINT [PK_' + REPLACE(REPLACE(@OutputTableName,N'[',N''),N']',N'') + N'] PRIMARY KEY CLUSTERED(ID ASC));' + ); SET @StringToExecute += N'IF EXISTS(SELECT * FROM ' +@OutputDatabaseName +N'.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' +@OutputSchemaName - +''') AND EXISTS (SELECT * FROM ' + +N''') AND EXISTS (SELECT * FROM ' +@OutputDatabaseName+ N'.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''' +@OutputSchemaName - +''' AND QUOTENAME(TABLE_NAME) = ''' + +N''' AND QUOTENAME(TABLE_NAME) = ''' +@OutputTableName - +''') AND EXISTS (SELECT * FROM ' + +N''') AND EXISTS (SELECT * FROM ' +@OutputDatabaseName+ N'.sys.computed_columns WHERE [name] = N''PlanCreationTimeHours'' AND QUOTENAME(OBJECT_NAME(object_id)) = N''' +@OutputTableName - +''' AND [definition] = N''(datediff(hour,[PlanCreationTime],sysdatetime()))'') + +N''' AND [definition] = N''(datediff(hour,[PlanCreationTime],sysdatetime()))'') BEGIN RAISERROR(''We noticed that you are running an old computed column definition for PlanCreationTimeHours, fixing that now'',0,0) WITH NOWAIT; - ALTER TABLE '+@OutputDatabaseName+'.'+@OutputSchemaName+'.'+@OutputTableName+' DROP COLUMN [PlanCreationTimeHours]; - ALTER TABLE '+@OutputDatabaseName+'.'+@OutputSchemaName+'.'+@OutputTableName+' ADD [PlanCreationTimeHours] AS DATEDIFF(HOUR,CONVERT(DATETIMEOFFSET(7),[PlanCreationTime]),[CheckDate]); + ALTER TABLE '+@OutputDatabaseName+N'.'+@OutputSchemaName+N'.'+@OutputTableName+N' DROP COLUMN [PlanCreationTimeHours]; + ALTER TABLE '+@OutputDatabaseName+N'.'+@OutputSchemaName+N'.'+@OutputTableName+N' ADD [PlanCreationTimeHours] AS DATEDIFF(HOUR,CONVERT(DATETIMEOFFSET(7),[PlanCreationTime]),[CheckDate]); END '; IF @ValidOutputServer = 1 @@ -21200,6 +22094,7 @@ ALTER PROCEDURE dbo.sp_BlitzIndex @SkipPartitions BIT = 0, @SkipStatistics BIT = 1, @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, @IgnoreDatabases NVARCHAR(MAX) = NULL, /* Comma-delimited list of databases you want to skip */ @ThresholdMB INT = 250 /* Number of megabytes that an object must be before we include it in basic results */, @@ -21222,7 +22117,7 @@ AS SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.02', @VersionDate = '20210322'; +SELECT @Version = '8.03', @VersionDate = '20210420'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -22819,24 +23714,40 @@ BEGIN TRY AND ColumnNamesWithDataTypes.IndexColumnType = ''Included'' ) AS included_columns_with_data_type ' - /* Github #2780 BGO Removing SQL Server 2019's new missing index sample plan because it doesn't work yet: - IF NOT EXISTS (SELECT * FROM sys.all_objects WHERE name = 'dm_db_missing_index_group_stats_query') - */ - SET @dsql = @dsql + N' , NULL AS sample_query_plan ' - /* Github #2780 BGO Removing SQL Server 2019's new missing index sample plan because it doesn't work yet: + /* Github #2780 BGO Removing SQL Server 2019's new missing index sample plan because it doesn't work yet:*/ + IF NOT EXISTS + ( + SELECT + 1/0 + FROM sys.all_objects AS o + WHERE o.name = 'dm_db_missing_index_group_stats_query' + ) + SELECT + @dsql += N' , NULL AS sample_query_plan ' + /* Github #2780 BGO Removing SQL Server 2019's new missing index sample plan because it doesn't work yet:*/ ELSE BEGIN - SET @dsql = @dsql + N' , sample_query_plan = (SELECT TOP 1 p.query_plan - FROM sys.dm_db_missing_index_group_stats gs - CROSS APPLY (SELECT TOP 1 s.plan_handle - FROM sys.dm_db_missing_index_group_stats_query q - INNER JOIN sys.dm_exec_query_stats s ON q.query_plan_hash = s.query_plan_hash - WHERE gs.group_handle = q.group_handle - ORDER BY (q.user_seeks + q.user_scans) DESC, s.total_logical_reads DESC) q2 - CROSS APPLY sys.dm_exec_query_plan(q2.plan_handle) p - WHERE gs.group_handle = gs.group_handle) ' + SELECT + @dsql += N' + , sample_query_plan = + ( + SELECT TOP (1) + p.query_plan + FROM sys.dm_db_missing_index_group_stats gs + CROSS APPLY + ( + SELECT TOP (1) + s.plan_handle + FROM sys.dm_db_missing_index_group_stats_query q + INNER JOIN sys.dm_exec_query_stats s + ON q.query_plan_hash = s.query_plan_hash + WHERE gs.group_handle = q.group_handle + ORDER BY (q.user_seeks + q.user_scans) DESC, s.total_logical_reads DESC + ) q2 + CROSS APPLY sys.dm_exec_query_plan(q2.plan_handle) p + ) ' END - */ + SET @dsql = @dsql + N'FROM ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_groups ig @@ -23687,7 +24598,8 @@ BEGIN --We do a left join here in case this is a disabled NC. --In that case, it won't have any size info/pages allocated. - + IF (@ShowColumnstoreOnly = 0) + BEGIN WITH table_mode_cte AS ( SELECT s.db_schema_object_indexid, @@ -23878,7 +24790,8 @@ BEGIN WHERE s.object_id = @ObjectID ORDER BY s.auto_created, s.user_created, s.name, hist.step_number;'; EXEC sp_executesql @dsql, N'@ObjectID INT', @ObjectID; - END + END + END /* Visualize columnstore index contents. More info: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2584 */ IF 2 = (SELECT SUM(1) FROM sys.all_objects WHERE name IN ('column_store_row_groups','column_store_segments')) @@ -26350,7 +27263,7 @@ ELSE IF (@Mode=1) /*Summarize*/ ISNULL(i.index_name, '''') AS [Index Name], CASE WHEN i.is_primary_key = 1 AND i.index_definition <> ''[HEAP]'' - THEN N''-ALTER TABLE '' + QUOTENAME(i.[database_name]) + N''.'' + QUOTENAME(i.[schema_name]) + N''.'' + QUOTENAME(i.[object_name]) + + THEN N''--ALTER TABLE '' + QUOTENAME(i.[database_name]) + N''.'' + QUOTENAME(i.[schema_name]) + N''.'' + QUOTENAME(i.[object_name]) + N'' DROP CONSTRAINT '' + QUOTENAME(i.index_name) + N'';'' WHEN i.is_primary_key = 0 AND i.index_definition <> ''[HEAP]'' THEN N''--DROP INDEX ''+ QUOTENAME(i.index_name) + N'' ON '' + QUOTENAME(i.[database_name]) + N''.'' + @@ -26533,46 +27446,45 @@ ELSE IF (@Mode=1) /*Summarize*/ FROM #IndexSanity AS i --left join here so we don't lose disabled nc indexes LEFT JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id LEFT JOIN #IndexCreateTsql AS ict ON i.index_sanity_id = ict.index_sanity_id - ORDER BY CASE WHEN @SortDirection = 'desc' THEN - CASE WHEN @SortOrder = N'rows' THEN sz.total_rows - WHEN @SortOrder = N'reserved_mb' THEN sz.total_reserved_MB - WHEN @SortOrder = N'size' THEN sz.total_reserved_MB - WHEN @SortOrder = N'reserved_lob_mb' THEN sz.total_reserved_LOB_MB - WHEN @SortOrder = N'lob' THEN sz.total_reserved_LOB_MB - WHEN @SortOrder = N'total_row_lock_wait_in_ms' THEN COALESCE(sz.total_row_lock_wait_in_ms,0) - WHEN @SortOrder = N'total_page_lock_wait_in_ms' THEN COALESCE(sz.total_page_lock_wait_in_ms,0) - WHEN @SortOrder = N'lock_time' THEN (COALESCE(sz.total_row_lock_wait_in_ms,0) + COALESCE(sz.total_page_lock_wait_in_ms,0)) - WHEN @SortOrder = N'total_reads' THEN total_reads - WHEN @SortOrder = N'reads' THEN total_reads - WHEN @SortOrder = N'user_updates' THEN user_updates - WHEN @SortOrder = N'writes' THEN user_updates - WHEN @SortOrder = N'reads_per_write' THEN reads_per_write - WHEN @SortOrder = N'ratio' THEN reads_per_write - WHEN @SortOrder = N'forward_fetches' THEN sz.total_forwarded_fetch_count - WHEN @SortOrder = N'fetches' THEN sz.total_forwarded_fetch_count - ELSE NULL END - ELSE 1 END - DESC, /* Shout out to DHutmacher */ - CASE WHEN @SortDirection = 'asc' THEN - CASE WHEN @SortOrder = N'rows' THEN sz.total_rows - WHEN @SortOrder = N'reserved_mb' THEN sz.total_reserved_MB - WHEN @SortOrder = N'size' THEN sz.total_reserved_MB - WHEN @SortOrder = N'reserved_lob_mb' THEN sz.total_reserved_LOB_MB - WHEN @SortOrder = N'lob' THEN sz.total_reserved_LOB_MB - WHEN @SortOrder = N'total_row_lock_wait_in_ms' THEN COALESCE(sz.total_row_lock_wait_in_ms,0) - WHEN @SortOrder = N'total_page_lock_wait_in_ms' THEN COALESCE(sz.total_page_lock_wait_in_ms,0) - WHEN @SortOrder = N'lock_time' THEN (COALESCE(sz.total_row_lock_wait_in_ms,0) + COALESCE(sz.total_page_lock_wait_in_ms,0)) - WHEN @SortOrder = N'total_reads' THEN total_reads - WHEN @SortOrder = N'reads' THEN total_reads - WHEN @SortOrder = N'user_updates' THEN user_updates - WHEN @SortOrder = N'writes' THEN user_updates - WHEN @SortOrder = N'reads_per_write' THEN reads_per_write - WHEN @SortOrder = N'ratio' THEN reads_per_write - WHEN @SortOrder = N'forward_fetches' THEN sz.total_forwarded_fetch_count - WHEN @SortOrder = N'fetches' THEN sz.total_forwarded_fetch_count - ELSE NULL END - ELSE 1 END - ASC, + ORDER BY /* Shout out to DHutmacher */ + /*DESC*/ + CASE WHEN @SortOrder = N'rows' AND @SortDirection = N'desc' THEN sz.total_rows ELSE NULL END DESC, + CASE WHEN @SortOrder = N'reserved_mb' AND @SortDirection = N'desc' THEN sz.total_reserved_MB ELSE NULL END DESC, + CASE WHEN @SortOrder = N'size' AND @SortDirection = N'desc' THEN sz.total_reserved_MB ELSE NULL END DESC, + CASE WHEN @SortOrder = N'reserved_lob_mb' AND @SortDirection = N'desc' THEN sz.total_reserved_LOB_MB ELSE NULL END DESC, + CASE WHEN @SortOrder = N'lob' AND @SortDirection = N'desc' THEN sz.total_reserved_LOB_MB ELSE NULL END DESC, + CASE WHEN @SortOrder = N'total_row_lock_wait_in_ms' AND @SortDirection = N'desc' THEN COALESCE(sz.total_row_lock_wait_in_ms,0) ELSE NULL END DESC, + CASE WHEN @SortOrder = N'total_page_lock_wait_in_ms' AND @SortDirection = N'desc' THEN COALESCE(sz.total_page_lock_wait_in_ms,0) ELSE NULL END DESC, + CASE WHEN @SortOrder = N'lock_time' AND @SortDirection = N'desc' THEN (COALESCE(sz.total_row_lock_wait_in_ms,0) + COALESCE(sz.total_page_lock_wait_in_ms,0)) ELSE NULL END DESC, + CASE WHEN @SortOrder = N'total_reads' AND @SortDirection = N'desc' THEN total_reads ELSE NULL END DESC, + CASE WHEN @SortOrder = N'reads' AND @SortDirection = N'desc' THEN total_reads ELSE NULL END DESC, + CASE WHEN @SortOrder = N'user_updates' AND @SortDirection = N'desc' THEN user_updates ELSE NULL END DESC, + CASE WHEN @SortOrder = N'writes' AND @SortDirection = N'desc' THEN user_updates ELSE NULL END DESC, + CASE WHEN @SortOrder = N'reads_per_write' AND @SortDirection = N'desc' THEN reads_per_write ELSE NULL END DESC, + CASE WHEN @SortOrder = N'ratio' AND @SortDirection = N'desc' THEN reads_per_write ELSE NULL END DESC, + CASE WHEN @SortOrder = N'forward_fetches' AND @SortDirection = N'desc' THEN sz.total_forwarded_fetch_count ELSE NULL END DESC, + CASE WHEN @SortOrder = N'fetches' AND @SortDirection = N'desc' THEN sz.total_forwarded_fetch_count ELSE NULL END DESC, + CASE WHEN @SortOrder = N'create_date' AND @SortDirection = N'desc' THEN CONVERT(DATETIME, i.create_date) ELSE NULL END DESC, + CASE WHEN @SortOrder = N'modify_date' AND @SortDirection = N'desc' THEN CONVERT(DATETIME, i.modify_date) ELSE NULL END DESC, + /*ASC*/ + CASE WHEN @SortOrder = N'rows' AND @SortDirection = N'asc' THEN sz.total_rows ELSE NULL END ASC, + CASE WHEN @SortOrder = N'reserved_mb' AND @SortDirection = N'asc' THEN sz.total_reserved_MB ELSE NULL END ASC, + CASE WHEN @SortOrder = N'size' AND @SortDirection = N'asc' THEN sz.total_reserved_MB ELSE NULL END ASC, + CASE WHEN @SortOrder = N'reserved_lob_mb' AND @SortDirection = N'asc' THEN sz.total_reserved_LOB_MB ELSE NULL END ASC, + CASE WHEN @SortOrder = N'lob' AND @SortDirection = N'asc' THEN sz.total_reserved_LOB_MB ELSE NULL END ASC, + CASE WHEN @SortOrder = N'total_row_lock_wait_in_ms' AND @SortDirection = N'asc' THEN COALESCE(sz.total_row_lock_wait_in_ms,0) ELSE NULL END ASC, + CASE WHEN @SortOrder = N'total_page_lock_wait_in_ms' AND @SortDirection = N'asc' THEN COALESCE(sz.total_page_lock_wait_in_ms,0) ELSE NULL END ASC, + CASE WHEN @SortOrder = N'lock_time' AND @SortDirection = N'asc' THEN (COALESCE(sz.total_row_lock_wait_in_ms,0) + COALESCE(sz.total_page_lock_wait_in_ms,0)) ELSE NULL END ASC, + CASE WHEN @SortOrder = N'total_reads' AND @SortDirection = N'asc' THEN total_reads ELSE NULL END ASC, + CASE WHEN @SortOrder = N'reads' AND @SortDirection = N'asc' THEN total_reads ELSE NULL END ASC, + CASE WHEN @SortOrder = N'user_updates' AND @SortDirection = N'asc' THEN user_updates ELSE NULL END ASC, + CASE WHEN @SortOrder = N'writes' AND @SortDirection = N'asc' THEN user_updates ELSE NULL END ASC, + CASE WHEN @SortOrder = N'reads_per_write' AND @SortDirection = N'asc' THEN reads_per_write ELSE NULL END ASC, + CASE WHEN @SortOrder = N'ratio' AND @SortDirection = N'asc' THEN reads_per_write ELSE NULL END ASC, + CASE WHEN @SortOrder = N'forward_fetches' AND @SortDirection = N'asc' THEN sz.total_forwarded_fetch_count ELSE NULL END ASC, + CASE WHEN @SortOrder = N'fetches' AND @SortDirection = N'asc' THEN sz.total_forwarded_fetch_count ELSE NULL END ASC, + CASE WHEN @SortOrder = N'create_date' AND @SortDirection = N'asc' THEN CONVERT(DATETIME, i.create_date) ELSE NULL END ASC, + CASE WHEN @SortOrder = N'modify_date' AND @SortDirection = N'asc' THEN CONVERT(DATETIME, i.modify_date) ELSE NULL END ASC, i.[database_name], [Schema Name], [Object Name], [Index ID] OPTION (RECOMPILE); END; @@ -26697,7 +27609,7 @@ BEGIN SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.02', @VersionDate = '20210322'; +SELECT @Version = '8.03', @VersionDate = '20210420'; IF(@VersionCheckMode = 1) @@ -28503,7 +29415,7 @@ BEGIN /*First BEGIN*/ SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.02', @VersionDate = '20210322'; +SELECT @Version = '8.03', @VersionDate = '20210420'; IF(@VersionCheckMode = 1) BEGIN RETURN; @@ -34221,6 +35133,7 @@ ALTER PROCEDURE dbo.sp_BlitzWho @MinRequestedMemoryKB INT = 0 , @MinBlockingSeconds INT = 0 , @CheckDateOverride DATETIMEOFFSET = NULL, + @ShowActualParameters BIT = 0, @Version VARCHAR(30) = NULL OUTPUT, @VersionDate DATETIME = NULL OUTPUT, @VersionCheckMode BIT = 0, @@ -34230,7 +35143,7 @@ BEGIN SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.02', @VersionDate = '20210322'; + SELECT @Version = '8.03', @VersionDate = '20210420'; IF(@VersionCheckMode = 1) BEGIN @@ -34312,8 +35225,6 @@ DECLARE @ProductVersion NVARCHAR(128) AND r.plan_handle = session_stats.plan_handle AND r.statement_start_offset = session_stats.statement_start_offset AND r.statement_end_offset = session_stats.statement_end_offset' - ,@QueryStatsXMLselect NVARCHAR(MAX) = N' CAST(COALESCE(qs_live.query_plan, '''') AS XML) AS live_query_plan , ' - ,@QueryStatsXMLSQL NVARCHAR(MAX) = N'OUTER APPLY sys.dm_exec_query_statistics_xml(s.session_id) qs_live' ,@ObjectFullName NVARCHAR(2000) ,@OutputTableNameQueryStats_View NVARCHAR(256) ,@LineFeed NVARCHAR(MAX) /* Had to set as MAX up from 10 as it was truncating the view creation*/; @@ -34324,16 +35235,6 @@ 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) -IF EXISTS (SELECT * FROM sys.all_columns WHERE object_id = OBJECT_ID('sys.dm_exec_query_statistics_xml') AND name = 'query_plan') - BEGIN - SET @QueryStatsXMLselect = N' CAST(COALESCE(qs_live.query_plan, '''') AS XML) AS live_query_plan , '; - SET @QueryStatsXMLSQL = N'OUTER APPLY sys.dm_exec_query_statistics_xml(s.session_id) qs_live'; - END - ELSE - BEGIN - SET @QueryStatsXMLselect = N' NULL AS live_query_plan , '; - SET @QueryStatsXMLSQL = N' '; - END SELECT @OutputTableNameQueryStats_View = QUOTENAME(@OutputTableName + '_Deltas'), @@ -34374,6 +35275,8 @@ IF @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @Output [query_text] [nvarchar](max) NULL, [query_plan] [xml] NULL, [live_query_plan] [xml] NULL, + [cached_parameter_info] [nvarchar](max) NULL, + [live_parameter_info] [nvarchar](max) NULL, [query_cost] [float] NULL, [status] [nvarchar](30) NOT NULL, [wait_info] [nvarchar](max) NULL, @@ -34472,6 +35375,20 @@ IF @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @Output ALTER TABLE ' + @ObjectFullName + N' ADD JoinKey AS ServerName + CAST(CheckDate AS NVARCHAR(50));'; EXEC(@StringToExecute); + /* If the table doesn't have the new cached_parameter_info computed column, add it. See Github #2842. */ + SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableName; + SET @StringToExecute = N'IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns + WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''cached_parameter_info'') + ALTER TABLE ' + @ObjectFullName + N' ADD cached_parameter_info NVARCHAR(MAX) NULL;'; + EXEC(@StringToExecute); + + /* If the table doesn't have the new live_parameter_info computed column, add it. See Github #2842. */ + SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableName; + SET @StringToExecute = N'IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns + WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''live_parameter_info'') + ALTER TABLE ' + @ObjectFullName + N' ADD live_parameter_info NVARCHAR(MAX) NULL;'; + EXEC(@StringToExecute); + /* Delete history older than @OutputTableRetentionDays */ SET @OutputTableCleanupDate = CAST( (DATEADD(DAY, -1 * @OutputTableRetentionDays, GETDATE() ) ) AS DATE); SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' @@ -34767,7 +35684,27 @@ SELECT @BlockingCheck = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; sys2.spid AS session_id, sys2.blocked AS blocking_session_id, sys2.lastwaittype, sys2.waittime, sys2.cpu, sys2.physical_io, sys2.memusage FROM sys.sysprocesses AS sys1 JOIN sys.sysprocesses AS sys2 - ON sys1.spid = sys2.blocked;'; + ON sys1.spid = sys2.blocked; + + + DECLARE @LiveQueryPlans TABLE + ( + Session_Id INT NOT NULL, + Query_Plan XML NOT NULL + ); + + ' + +IF EXISTS (SELECT * FROM sys.all_columns WHERE object_id = OBJECT_ID('sys.dm_exec_query_statistics_xml') AND name = 'query_plan') +BEGIN + SET @BlockingCheck = @BlockingCheck + N' + INSERT INTO @LiveQueryPlans + SELECT s.session_id, query_plan + FROM sys.dm_exec_sessions AS s + CROSS APPLY sys.dm_exec_query_statistics_xml(s.session_id) + WHERE s.session_id <> @@SPID;'; +END + IF @ProductVersionMajor > 9 and @ProductVersionMajor < 11 BEGIN @@ -34989,9 +35926,19 @@ IF @ProductVersionMajor >= 11 ELSE query_stats.statement_end_offset END - query_stats.statement_start_offset ) / 2 ) + 1), dest.text) AS query_text , - derp.query_plan ,' - + @QueryStatsXMLselect - +' + derp.query_plan , + CAST(COALESCE(qs_live.query_plan, '''') AS XML) AS live_query_plan , + STUFF((SELECT DISTINCT N'', '' + Node.Data.value(''(@Column)[1]'', ''NVARCHAR(4000)'') + N'' {'' + Node.Data.value(''(@ParameterDataType)[1]'', ''NVARCHAR(4000)'') + N''}: '' + Node.Data.value(''(@ParameterCompiledValue)[1]'', ''NVARCHAR(4000)'') + FROM derp.query_plan.nodes(''/*:ShowPlanXML/*:BatchSequence/*:Batch/*:Statements/*:StmtSimple/*:QueryPlan/*:ParameterList/*:ColumnReference'') AS Node(Data) + FOR XML PATH('''')), 1,2,'''') + AS Cached_Parameter_Info, + ' + IF @ShowActualParameters = 1 + BEGIN + SELECT @StringToExecute = @StringToExecute + N'qs_live.Live_Parameter_Info as Live_Parameter_Info,' + END + + SELECT @StringToExecute = @StringToExecute + N' qmg.query_cost , s.status , CASE @@ -35212,10 +36159,18 @@ IF @ProductVersionMajor >= 11 AND tsu.session_id = r.session_id AND tsu.session_id = s.session_id ) as tempdb_allocations - ' - + @QueryStatsXMLSQL - + - N' + + OUTER APPLY ( + SELECT TOP 1 query_plan, + STUFF((SELECT DISTINCT N'', '' + Node.Data.value(''(@Column)[1]'', ''NVARCHAR(4000)'') + N'' {'' + Node.Data.value(''(@ParameterDataType)[1]'', ''NVARCHAR(4000)'') + N''}: '' + Node.Data.value(''(@ParameterCompiledValue)[1]'', ''NVARCHAR(4000)'') + N'' (Actual: '' + Node.Data.value(''(@ParameterRuntimeValue)[1]'', ''NVARCHAR(4000)'') + N'')'' + FROM q.query_plan.nodes(''/*:ShowPlanXML/*:BatchSequence/*:Batch/*:Statements/*:StmtSimple/*:QueryPlan/*:ParameterList/*:ColumnReference'') AS Node(Data) + FOR XML PATH('''')), 1,2,'''') + AS Live_Parameter_Info + FROM @LiveQueryPlans q + WHERE (s.session_id = q.session_id) + + ) AS qs_live + WHERE s.session_id <> @@SPID AND s.host_name IS NOT NULL AND r.database_id NOT IN (SELECT database_id FROM #WhoReadableDBs) @@ -35305,6 +36260,8 @@ IF @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @Output ,[query_text] ,[query_plan]' + CASE WHEN @ProductVersionMajor >= 11 THEN N',[live_query_plan]' ELSE N'' END + N' + ,[Cached_Parameter_Info]' + + CASE WHEN @ProductVersionMajor >= 11 AND @ShowActualParameters = 1 THEN N',[Live_Parameter_Info]' ELSE N'' END + N' ,[query_cost] ,[status] ,[wait_info]' @@ -35470,7 +36427,7 @@ SET NOCOUNT ON; /*Versioning details*/ -SELECT @Version = '8.02', @VersionDate = '20210322'; +SELECT @Version = '8.03', @VersionDate = '20210420'; IF(@VersionCheckMode = 1) BEGIN @@ -35655,7 +36612,7 @@ DECLARE @cmd NVARCHAR(4000) = N'', --Holds xp_cmdshell command @LogRecoveryOption AS NVARCHAR(MAX) = N'', --Holds the option to cause logs to be restored in standby mode or with no recovery @DatabaseLastLSN NUMERIC(25, 0), --redo_start_lsn of the current database @i TINYINT = 1, --Maintains loop to continue logs - @LogRestoreRanking SMALLINT = 1, --Holds Log iteration # when multiple paths & backup files are being stripped + @LogRestoreRanking INT = 1, --Holds Log iteration # when multiple paths & backup files are being stripped @LogFirstLSN NUMERIC(25, 0), --Holds first LSN in log backup headers @LogLastLSN NUMERIC(25, 0), --Holds last LSN in log backup headers @LogLastNameInMsdbAS NVARCHAR(MAX) = N'', -- Holds last TRN file name already restored @@ -36956,7 +37913,7 @@ AS BEGIN SET NOCOUNT ON; - SELECT @Version = '8.02', @VersionDate = '20210322'; + SELECT @Version = '8.03', @VersionDate = '20210420'; IF(@VersionCheckMode = 1) BEGIN @@ -37302,6 +38259,7 @@ DELETE FROM dbo.SqlServerVersions; INSERT INTO dbo.SqlServerVersions (MajorVersionNumber, MinorVersionNumber, Branch, [Url], ReleaseDate, MainstreamSupportEndDate, ExtendedSupportEndDate, MajorVersionName, MinorVersionName) VALUES + (15, 4123, 'CU10', 'https://support.microsoft.com/en-us/help/5001090', '2021-02-11', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 10'), (15, 4102, 'CU9', 'https://support.microsoft.com/en-us/help/5000642', '2021-02-11', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 9 '), (15, 4073, 'CU8 GDR', 'https://support.microsoft.com/en-us/help/4583459', '2021-01-12', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 8 GDR '), (15, 4073, 'CU8', 'https://support.microsoft.com/en-us/help/4577194', '2020-10-01', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 8 '), @@ -37314,7 +38272,7 @@ VALUES (15, 4003, 'CU1', 'https://support.microsoft.com/en-us/help/4527376', '2020-01-07', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 1 '), (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 '), - (14, 3356, 'RTM CU23', 'https://support.microsoft.com/en-us/help/5000685', '2021-02-25', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 23'), + (14, 3381, 'RTM CU23', 'https://support.microsoft.com/en-us/help/5000685', '2021-02-25', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 23'), (14, 3370, 'RTM CU22 GDR', 'https://support.microsoft.com/en-us/help/4583457', '2021-01-12', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 22 GDR'), (14, 3356, 'RTM CU22', 'https://support.microsoft.com/en-us/help/4577467', '2020-09-10', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 22'), (14, 3335, 'RTM CU21', 'https://support.microsoft.com/en-us/help/4557397', '2020-07-01', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 21'), @@ -37690,7 +38648,7 @@ BEGIN SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.02', @VersionDate = '20210322'; +SELECT @Version = '8.03', @VersionDate = '20210420'; IF(@VersionCheckMode = 1) BEGIN @@ -42101,8 +43059,8 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, [FindingsGroup] , [Finding] , [URL] , - CAST(@StockDetailsHeader + [Details] + @StockDetailsFooter AS NVARCHAR(MAX)) AS Details, - CAST([HowToStopIt] AS NVARCHAR(MAX)) AS HowToStopIt, + CAST(LEFT(@StockDetailsHeader + [Details] + @StockDetailsFooter,32000) AS TEXT) AS Details, + CAST(LEFT([HowToStopIt],32000) AS TEXT) AS HowToStopIt, CAST([QueryText] AS NVARCHAR(MAX)) AS QueryText, CAST([QueryPlan] AS NVARCHAR(MAX)) AS QueryPlan FROM #BlitzFirstResults diff --git a/Install-Core-Blitz-No-Query-Store.sql b/Install-Core-Blitz-No-Query-Store.sql index 61725a9f0..f36272652 100755 --- a/Install-Core-Blitz-No-Query-Store.sql +++ b/Install-Core-Blitz-No-Query-Store.sql @@ -37,7 +37,7 @@ AS SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.02', @VersionDate = '20210322'; + SELECT @Version = '8.03', @VersionDate = '20210420'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -634,8 +634,8 @@ AS DROP TABLE #driveInfo; CREATE TABLE #driveInfo ( - drive NVARCHAR, - logical_volume_name NVARCHAR(32), --Limit is 32 for NTFS, 11 for FAT + drive NVARCHAR(2), + logical_volume_name NVARCHAR(36), --Limit is 32 for NTFS, 11 for FAT available_MB DECIMAL(18, 0), total_MB DECIMAL(18, 0), used_percent DECIMAL(18, 2) @@ -3632,8 +3632,8 @@ AS BEGIN SET @user_perm_sql += N' - SELECT @user_perm_gb = CASE WHEN (pages_kb / 128.0 / 1024.) >= 2. - THEN CONVERT(DECIMAL(38, 2), (pages_kb / 128.0 / 1024.)) + SELECT @user_perm_gb = CASE WHEN (pages_kb / 1024. / 1024.) >= 2. + THEN CONVERT(DECIMAL(38, 2), (pages_kb / 1024. / 1024.)) ELSE NULL END FROM sys.dm_os_memory_clerks @@ -4463,7 +4463,7 @@ IF @ProductVersionMajor >= 10 'Informational' AS [FindingsGroup] , 'SQL Server is running under an NT Service account' AS [Finding] , 'https://www.brentozar.com/go/setup' AS [URL] , - ( 'I''m running as ' + [service_account] + '. I wish I had an Active Directory service account instead.' + ( 'I''m running as ' + [service_account] + '.' ) AS [Details] FROM [sys].[dm_server_services] @@ -4503,7 +4503,7 @@ IF @ProductVersionMajor >= 10 'Informational' AS [FindingsGroup] , 'SQL Server Agent is running under an NT Service account' AS [Finding] , 'https://www.brentozar.com/go/setup' AS [URL] , - ( 'I''m running as ' + [service_account] + '. I wish I had an Active Directory service account instead.' + ( 'I''m running as ' + [service_account] + '.' ) AS [Details] FROM [sys].[dm_server_services] @@ -8404,10 +8404,10 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 inner join ( SELECT DISTINCT SUBSTRING(volume_mount_point, 1, 1) AS volume_mount_point - ,CASE WHEN ISNULL(logical_volume_name,'''') = '''' THEN '''' ELSE '' ('' + logical_volume_name + '')'' END AS logical_volume_name + ,CASE WHEN ISNULL(logical_volume_name,'''') = '''' THEN '''' ELSE ''('' + logical_volume_name + '')'' END AS logical_volume_name ,total_bytes/1024/1024 AS total_MB ,available_bytes/1024/1024 AS available_MB - ,(CONVERT(DECIMAL(4,2),(total_bytes/1.0 - available_bytes)/total_bytes * 100)) AS used_percent + ,(CONVERT(DECIMAL(5,2),(total_bytes/1.0 - available_bytes)/total_bytes * 100)) AS used_percent FROM (SELECT TOP 1 WITH TIES database_id @@ -8441,7 +8441,7 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 + '' drive'' ELSE CAST(CAST(i.available_MB/1024 AS NUMERIC(18,2)) AS VARCHAR(30)) + '' GB free on '' + i.drive - + '' drive'' + i.logical_volume_name + + '' drive '' + i.logical_volume_name + '' out of '' + CAST(CAST(i.total_MB/1024 AS NUMERIC(18,2)) AS VARCHAR(30)) + '' GB total ('' + CAST(i.used_percent AS VARCHAR(30)) + ''%)'' END AS Details @@ -9394,6 +9394,896 @@ EXEC [dbo].[sp_Blitz] @CheckProcedureCacheFilter = NULL, @CheckServerInfo = 1 */ +SET ANSI_NULLS ON; +SET QUOTED_IDENTIFIER ON + +IF NOT EXISTS (SELECT * FROM sys.objects WHERE [object_id] = OBJECT_ID(N'[dbo].[sp_BlitzAnalysis]') AND [type] in (N'P', N'PC')) +BEGIN +EXEC dbo.sp_executesql @statement = N'CREATE PROCEDURE [dbo].[sp_BlitzAnalysis] AS' +END +GO + +ALTER PROCEDURE [dbo].[sp_BlitzAnalysis] ( +@Help TINYINT = 0, +@StartDate DATETIMEOFFSET(7) = NULL, +@EndDate DATETIMEOFFSET(7) = NULL, +@OutputDatabaseName NVARCHAR(256) = 'DBAtools', +@OutputSchemaName NVARCHAR(256) = N'dbo', +@OutputTableNameBlitzFirst NVARCHAR(256) = N'BlitzFirst', +@OutputTableNameFileStats NVARCHAR(256) = N'BlitzFirst_FileStats', +@OutputTableNamePerfmonStats NVARCHAR(256) = N'BlitzFirst_PerfmonStats', +@OutputTableNameWaitStats NVARCHAR(256) = N'BlitzFirst_WaitStats', +@OutputTableNameBlitzCache NVARCHAR(256) = N'BlitzCache', +@OutputTableNameBlitzWho NVARCHAR(256) = N'BlitzWho', +@Servername NVARCHAR(128) = @@SERVERNAME, +@Databasename NVARCHAR(128) = NULL, +@BlitzCacheSortorder NVARCHAR(20) = N'cpu', +@MaxBlitzFirstPriority INT = 249, +@ReadLatencyThreshold INT = 100, +@WriteLatencyThreshold INT = 100, +@WaitStatsTop TINYINT = 10, +@Version VARCHAR(30) = NULL OUTPUT, +@VersionDate DATETIME = NULL OUTPUT, +@VersionCheckMode BIT = 0, +@BringThePain BIT = 0, +@Maxdop INT = 1, +@Debug BIT = 0 +) +AS +SET NOCOUNT ON; + +SELECT @Version = '8.03', @VersionDate = '20210420'; + +IF(@VersionCheckMode = 1) +BEGIN + RETURN; +END; + +IF (@Help = 1) +BEGIN + PRINT 'EXEC sp_BlitzAnalysis +@StartDate = NULL, /* Specify a datetime or NULL will get an hour ago */ +@EndDate = NULL, /* Specify a datetime or NULL will get an hour of data since @StartDate */ +@OutputDatabaseName = N''DBA'', /* Specify the database name where where we can find your logged blitz data */ +@OutputSchemaName = N''dbo'', /* Specify the schema */ +@OutputTableNameBlitzFirst = N''BlitzFirst'', /* Table name where you are storing sp_BlitzFirst output, Set to NULL to ignore */ +@OutputTableNameFileStats = N''BlitzFirst_FileStats'', /* Table name where you are storing sp_BlitzFirst filestats output, Set to NULL to ignore */ +@OutputTableNamePerfmonStats = N''BlitzFirst_PerfmonStats'', /* Table name where you are storing sp_BlitzFirst Perfmon output, Set to NULL to ignore */ +@OutputTableNameWaitStats = N''BlitzFirst_WaitStats'', /* Table name where you are storing sp_BlitzFirst Wait stats output, Set to NULL to ignore */ +@OutputTableNameBlitzCache = N''BlitzCache'', /* Table name where you are storing sp_BlitzCache output, Set to NULL to ignore */ +@OutputTableNameBlitzWho = N''BlitzWho'', /* Table name where you are storing sp_BlitzWho output, Set to NULL to ignore */ +@Databasename = NULL, /* Filters results for BlitzCache, FileStats (will also include tempdb), BlitzWho. Leave as NULL for all databases */ +@MaxBlitzFirstPriority = 249, /* Max priority to include in the results */ +@BlitzCacheSortorder = ''cpu'', /* Accepted values ''all'' ''cpu'' ''reads'' ''writes'' ''duration'' ''executions'' ''memory grant'' ''spills'' */ +@WaitStatsTop = 3, /* Controls the top for wait stats only */ +@Maxdop = 1, /* Control the degree of parallelism that the queries within this proc can use if they want to*/ +@Debug = 0; /* Show sp_BlitzAnalysis SQL commands in the messages tab as they execute */ + +/* +Additional parameters: +@ReadLatencyThreshold INT /* Default: 100 - Sets the threshold in ms to compare against io_stall_read_average_ms in your filestats table */ +@WriteLatencyThreshold INT /* Default: 100 - Sets the threshold in ms to compare against io_stall_write_average_ms in your filestats table */ +@BringThePain BIT /* Default: 0 - If you are getting more than 4 hours of data with blitzcachesortorder set to ''all'' you will need to set BringThePain to 1 */ +*/'; + RETURN; +END + +/* Declare all local variables required */ +DECLARE @FullOutputTableNameBlitzFirst NVARCHAR(1000); +DECLARE @FullOutputTableNameFileStats NVARCHAR(1000); +DECLARE @FullOutputTableNamePerfmonStats NVARCHAR(1000); +DECLARE @FullOutputTableNameWaitStats NVARCHAR(1000); +DECLARE @FullOutputTableNameBlitzCache NVARCHAR(1000); +DECLARE @FullOutputTableNameBlitzWho NVARCHAR(1000); +DECLARE @Sql NVARCHAR(MAX); +DECLARE @NewLine NVARCHAR(2) = CHAR(13); +DECLARE @IncludeMemoryGrants BIT; +DECLARE @IncludeSpills BIT; + +/* Validate the database name */ +IF (DB_ID(@OutputDatabaseName) IS NULL) +BEGIN + RAISERROR('Invalid database name provided for parameter @OutputDatabaseName: %s',11,0,@OutputDatabaseName); + RETURN; +END + +/* Set fully qualified table names */ +SET @FullOutputTableNameBlitzFirst = QUOTENAME(@OutputDatabaseName)+N'.'+QUOTENAME(@OutputSchemaName)+N'.'+QUOTENAME(@OutputTableNameBlitzFirst); +SET @FullOutputTableNameFileStats = QUOTENAME(@OutputDatabaseName)+N'.'+QUOTENAME(@OutputSchemaName)+N'.'+QUOTENAME(@OutputTableNameFileStats+N'_Deltas'); +SET @FullOutputTableNamePerfmonStats = QUOTENAME(@OutputDatabaseName)+N'.'+QUOTENAME(@OutputSchemaName)+N'.'+QUOTENAME(@OutputTableNamePerfmonStats+N'_Actuals'); +SET @FullOutputTableNameWaitStats = QUOTENAME(@OutputDatabaseName)+N'.'+QUOTENAME(@OutputSchemaName)+N'.'+QUOTENAME(@OutputTableNameWaitStats+N'_Deltas'); +SET @FullOutputTableNameBlitzCache = QUOTENAME(@OutputDatabaseName)+N'.'+QUOTENAME(@OutputSchemaName)+N'.'+QUOTENAME(@OutputTableNameBlitzCache); +SET @FullOutputTableNameBlitzWho = QUOTENAME(@OutputDatabaseName)+N'.'+QUOTENAME(@OutputSchemaName)+N'.'+QUOTENAME(@OutputTableNameBlitzWho+N'_Deltas'); + +IF OBJECT_ID('tempdb.dbo.#BlitzFirstCounts') IS NOT NULL +BEGIN + DROP TABLE #BlitzFirstCounts; +END + +CREATE TABLE #BlitzFirstCounts ( + [Priority] TINYINT NOT NULL, + [FindingsGroup] VARCHAR(50) NOT NULL, + [Finding] VARCHAR(200) NOT NULL, + [TotalOccurrences] INT NULL, + [FirstOccurrence] DATETIMEOFFSET(7) NULL, + [LastOccurrence] DATETIMEOFFSET(7) NULL +); + +/* Validate variables and set defaults as required */ +IF (@BlitzCacheSortorder IS NULL) +BEGIN + SET @BlitzCacheSortorder = N'cpu'; +END + +SET @BlitzCacheSortorder = LOWER(@BlitzCacheSortorder); + +IF (@OutputTableNameBlitzCache IS NOT NULL AND @BlitzCacheSortorder NOT IN (N'all',N'cpu',N'reads',N'writes',N'duration',N'executions',N'memory grant',N'spills')) +BEGIN + RAISERROR('Invalid sort option specified for @BlitzCacheSortorder, supported values are ''all'', ''cpu'', ''reads'', ''writes'', ''duration'', ''executions'', ''memory grant'', ''spills''',11,0) WITH NOWAIT; + RETURN; +END + +/* Set @Maxdop to 1 if NULL was passed in */ +IF (@Maxdop IS NULL) +BEGIN + SET @Maxdop = 1; +END + +/* iF @Maxdop is set higher than the core count just set it to 0 */ +IF (@Maxdop > (SELECT CAST(cpu_count AS INT) FROM sys.dm_os_sys_info)) +BEGIN + SET @Maxdop = 0; +END + +/* We need to check if your SQL version has memory grant and spills columns in sys.dm_exec_query_stats */ +SELECT @IncludeMemoryGrants = + CASE + WHEN (EXISTS(SELECT * FROM sys.all_columns WHERE [object_id] = OBJECT_ID('sys.dm_exec_query_stats') AND name = 'max_grant_kb')) THEN 1 + ELSE 0 + END; + +SELECT @IncludeSpills = + CASE + WHEN (EXISTS(SELECT * FROM sys.all_columns WHERE [object_id] = OBJECT_ID('sys.dm_exec_query_stats') AND name = 'max_spills')) THEN 1 + ELSE 0 + END; + + +IF (@StartDate IS NULL) +BEGIN + RAISERROR('Setting @StartDate to: 1 hour ago',0,0) WITH NOWAIT; + /* Set StartDate to be an hour ago */ + SET @StartDate = DATEADD(HOUR,-1,SYSDATETIMEOFFSET()); + + IF (@EndDate IS NULL) + BEGIN + RAISERROR('Setting @EndDate to: Now',0,0) WITH NOWAIT; + /* Get data right up to now */ + SET @EndDate = SYSDATETIMEOFFSET(); + END +END + +IF (@EndDate IS NULL) +BEGIN + /* Default to an hour of data or SYSDATETIMEOFFSET() if now is earlier than the hour added to @StartDate */ + IF(DATEADD(HOUR,1,@StartDate) < SYSDATETIMEOFFSET()) + BEGIN + RAISERROR('@EndDate was NULL - Setting to return 1 hour of information, if you want more then set @EndDate aswell',0,0) WITH NOWAIT; + SET @EndDate = DATEADD(HOUR,1,@StartDate); + END + ELSE + BEGIN + RAISERROR('@EndDate was NULL - Setting to SYSDATETIMEOFFSET()',0,0) WITH NOWAIT; + SET @EndDate = SYSDATETIMEOFFSET(); + END +END + +/* Default to dbo schema if NULL is passed in */ +IF (@OutputSchemaName IS NULL) +BEGIN + SET @OutputSchemaName = 'dbo'; +END + +/* Prompt the user for @BringThePain = 1 if they are searching a timeframe greater than 4 hours and they are using BlitzCacheSortorder = 'all' */ +IF(@BlitzCacheSortorder = 'all' AND DATEDIFF(HOUR,@StartDate,@EndDate) > 4 AND @BringThePain = 0) +BEGIN + RAISERROR('Wow! hold up now, are you sure you wanna do this? Are sure you want to query over 4 hours of data with @BlitzCacheSortorder set to ''all''? IF you do then set @BringThePain = 1 but I gotta warn you this might hurt a bit!',11,1) WITH NOWAIT; + RETURN; +END + +/* Output report window information */ +SELECT + @Servername AS [ServerToReportOn], + CAST(1 AS NVARCHAR(20)) + N' - '+ CAST(@MaxBlitzFirstPriority AS NVARCHAR(20)) AS [PrioritesToInclude], + @StartDate AS [StartDatetime], + @EndDate AS [EndDatetime];; + + +/* BlitzFirst data */ +SET @Sql = N' +INSERT INTO #BlitzFirstCounts ([Priority],[FindingsGroup],[Finding],[TotalOccurrences],[FirstOccurrence],[LastOccurrence]) +SELECT +[Priority], +[FindingsGroup], +[Finding], +COUNT(*) AS [TotalOccurrences], +MIN(CheckDate) AS [FirstOccurrence], +MAX(CheckDate) AS [LastOccurrence] +FROM '+@FullOutputTableNameBlitzFirst+N' +WHERE [ServerName] = @Servername +AND [Priority] BETWEEN 1 AND @MaxBlitzFirstPriority +AND CheckDate BETWEEN @StartDate AND @EndDate +AND [CheckID] > -1 +GROUP BY [Priority],[FindingsGroup],[Finding]; + +IF EXISTS(SELECT 1 FROM #BlitzFirstCounts) +BEGIN + SELECT + [Priority], + [FindingsGroup], + [Finding], + [TotalOccurrences], + [FirstOccurrence], + [LastOccurrence] + FROM #BlitzFirstCounts + ORDER BY [Priority] ASC,[TotalOccurrences] DESC; +END +ELSE +BEGIN + SELECT N''No findings with a priority between 1 and ''+CAST(@MaxBlitzFirstPriority AS NVARCHAR(10))+N'' found for this period''; +END +SELECT + [ServerName] +,[CheckDate] +,[CheckID] +,[Priority] +,[Finding] +,[URL] +,[Details] +,[HowToStopIt] +,[QueryPlan] +,[QueryText] +FROM '+@FullOutputTableNameBlitzFirst+N' Findings +WHERE [ServerName] = @Servername +AND [Priority] BETWEEN 1 AND @MaxBlitzFirstPriority +AND [CheckDate] BETWEEN @StartDate AND @EndDate +AND [CheckID] > -1 +ORDER BY CheckDate ASC,[Priority] ASC +OPTION (RECOMPILE, MAXDOP '+CAST(@Maxdop AS NVARCHAR(2))+N');'; + + +RAISERROR('Getting BlitzFirst info from %s',0,0,@FullOutputTableNameBlitzFirst) WITH NOWAIT; + +IF (@Debug = 1) +BEGIN + PRINT @Sql; +END + +IF (OBJECT_ID(@FullOutputTableNameBlitzFirst) IS NULL) +BEGIN + IF (@OutputTableNameBlitzFirst IS NULL) + BEGIN + RAISERROR('BlitzFirst data skipped',10,0); + SELECT N'Skipped logged BlitzFirst data as NULL was passed to parameter @OutputTableNameBlitzFirst'; + END + ELSE + BEGIN + RAISERROR('Table provided for BlitzFirst data: %s does not exist',10,0,@FullOutputTableNameBlitzFirst); + SELECT N'No BlitzFirst data available as the table cannot be found'; + END + +END +ELSE /* Table exists then run the query */ +BEGIN + EXEC sp_executesql @Sql, + N'@StartDate DATETIMEOFFSET(7), + @EndDate DATETIMEOFFSET(7), + @Servername NVARCHAR(128), + @MaxBlitzFirstPriority INT', + @StartDate=@StartDate, + @EndDate=@EndDate, + @Servername=@Servername, + @MaxBlitzFirstPriority = @MaxBlitzFirstPriority; +END + +/* Blitz WaitStats data */ +SET @Sql = N'SELECT +[ServerName], +[CheckDate], +[wait_type], +[WaitsRank], +[WaitCategory], +[Ignorable], +[ElapsedSeconds], +[wait_time_ms_delta], +[wait_time_minutes_delta], +[wait_time_minutes_per_minute], +[signal_wait_time_ms_delta], +[waiting_tasks_count_delta], +ISNULL((CAST([wait_time_ms_delta] AS DECIMAL(38,2))/NULLIF(CAST([waiting_tasks_count_delta] AS DECIMAL(38,2)),0)),0) AS [wait_time_ms_per_wait] +FROM +( + SELECT + [ServerName], + [CheckDate], + [wait_type], + [WaitCategory], + [Ignorable], + [ElapsedSeconds], + [wait_time_ms_delta], + [wait_time_minutes_delta], + [wait_time_minutes_per_minute], + [signal_wait_time_ms_delta], + [waiting_tasks_count_delta], + ROW_NUMBER() OVER(PARTITION BY [CheckDate] ORDER BY [CheckDate] ASC,[wait_time_ms_delta] DESC) AS [WaitsRank] + FROM '+@FullOutputTableNameWaitStats+N' AS [Waits] + WHERE [ServerName] = @Servername + AND [CheckDate] BETWEEN @StartDate AND @EndDate +) TopWaits +WHERE [WaitsRank] <= @WaitStatsTop +ORDER BY +[CheckDate] ASC, +[wait_time_ms_delta] DESC +OPTION(RECOMPILE, MAXDOP '+CAST(@Maxdop AS NVARCHAR(2))+N');' + +RAISERROR('Getting wait stats info from %s',0,0,@FullOutputTableNameWaitStats) WITH NOWAIT; + +IF (@Debug = 1) +BEGIN + PRINT @Sql; +END + +IF (OBJECT_ID(@FullOutputTableNameWaitStats) IS NULL) +BEGIN + IF (@OutputTableNameWaitStats IS NULL) + BEGIN + RAISERROR('Wait stats data skipped',10,0); + SELECT N'Skipped logged wait stats data as NULL was passed to parameter @OutputTableNameWaitStats'; + END + ELSE + BEGIN + RAISERROR('Table provided for wait stats data: %s does not exist',10,0,@FullOutputTableNameWaitStats); + SELECT N'No wait stats data available as the table cannot be found'; + END +END +ELSE /* Table exists then run the query */ +BEGIN + EXEC sp_executesql @Sql, + N'@StartDate DATETIMEOFFSET(7), + @EndDate DATETIMEOFFSET(7), + @Servername NVARCHAR(128), + @WaitStatsTop TINYINT', + @StartDate=@StartDate, + @EndDate=@EndDate, + @Servername=@Servername, + @WaitStatsTop=@WaitStatsTop; +END + +/* BlitzFileStats info */ +SET @Sql = N' +SELECT +[ServerName], +[CheckDate], +CASE + WHEN MAX([io_stall_read_ms_average]) > @ReadLatencyThreshold THEN ''Yes'' + WHEN MAX([io_stall_write_ms_average]) > @WriteLatencyThreshold THEN ''Yes'' + ELSE ''No'' +END AS [io_stall_ms_breached], +LEFT([PhysicalName],LEN([PhysicalName])-CHARINDEX(''\'',REVERSE([PhysicalName]))+1) AS [PhysicalPath], +SUM([SizeOnDiskMB]) AS [SizeOnDiskMB], +SUM([SizeOnDiskMBgrowth]) AS [SizeOnDiskMBgrowth], +MAX([io_stall_read_ms]) AS [max_io_stall_read_ms], +MAX([io_stall_read_ms_average]) AS [max_io_stall_read_ms_average], +@ReadLatencyThreshold AS [is_stall_read_ms_threshold], +SUM([num_of_reads]) AS [num_of_reads], +SUM([megabytes_read]) AS [megabytes_read], +MAX([io_stall_write_ms]) AS [max_io_stall_write_ms], +MAX([io_stall_write_ms_average]) AS [max_io_stall_write_ms_average], +@WriteLatencyThreshold AS [io_stall_write_ms_average], +SUM([num_of_writes]) AS [num_of_writes], +SUM([megabytes_written]) AS [megabytes_written] +FROM '+@FullOutputTableNameFileStats+N' +WHERE [ServerName] = @Servername +AND [CheckDate] BETWEEN @StartDate AND @EndDate +' ++CASE + WHEN @Databasename IS NOT NULL THEN N'AND [DatabaseName] IN (N''tempdb'',@Databasename) +' + ELSE N'' +END ++N'GROUP BY +[ServerName], +[CheckDate], +LEFT([PhysicalName],LEN([PhysicalName])-CHARINDEX(''\'',REVERSE([PhysicalName]))+1) +ORDER BY +[CheckDate] ASC +OPTION (RECOMPILE, MAXDOP '+CAST(@Maxdop AS NVARCHAR(2))+N');' + +RAISERROR('Getting FileStats info from %s',0,0,@FullOutputTableNameFileStats) WITH NOWAIT; + +IF (@Debug = 1) +BEGIN + PRINT @Sql; +END + +IF (OBJECT_ID(@FullOutputTableNameFileStats) IS NULL) +BEGIN + IF (@OutputTableNameFileStats IS NULL) + BEGIN + RAISERROR('File stats data skipped',10,0); + SELECT N'Skipped logged File stats data as NULL was passed to parameter @OutputTableNameFileStats'; + END + ELSE + BEGIN + RAISERROR('Table provided for FileStats data: %s does not exist',10,0,@FullOutputTableNameFileStats); + SELECT N'No File stats data available as the table cannot be found'; + END +END +ELSE /* Table exists then run the query */ +BEGIN + EXEC sp_executesql @Sql, + N'@StartDate DATETIMEOFFSET(7), + @EndDate DATETIMEOFFSET(7), + @Servername NVARCHAR(128), + @Databasename NVARCHAR(128), + @ReadLatencyThreshold INT, + @WriteLatencyThreshold INT', + @StartDate=@StartDate, + @EndDate=@EndDate, + @Servername=@Servername, + @Databasename = @Databasename, + @ReadLatencyThreshold = @ReadLatencyThreshold, + @WriteLatencyThreshold = @WriteLatencyThreshold; +END + +/* Blitz Perfmon stats*/ +SET @Sql = N' +SELECT + [ServerName] + ,[CheckDate] + ,[counter_name] + ,[object_name] + ,[instance_name] + ,[cntr_value] +FROM '+@FullOutputTableNamePerfmonStats+N' +WHERE [ServerName] = @Servername +AND CheckDate BETWEEN @StartDate AND @EndDate +ORDER BY + [CheckDate] ASC, + [counter_name] ASC +OPTION (RECOMPILE, MAXDOP '+CAST(@Maxdop AS NVARCHAR(2))+N');' + +RAISERROR('Getting Perfmon info from %s',0,0,@FullOutputTableNamePerfmonStats) WITH NOWAIT; + +IF (@Debug = 1) +BEGIN + PRINT @Sql; +END + +IF (OBJECT_ID(@FullOutputTableNamePerfmonStats) IS NULL) +BEGIN + IF (@OutputTableNamePerfmonStats IS NULL) + BEGIN + RAISERROR('Perfmon stats data skipped',10,0); + SELECT N'Skipped logged Perfmon stats data as NULL was passed to parameter @OutputTableNamePerfmonStats'; + END + ELSE + BEGIN + RAISERROR('Table provided for Perfmon stats data: %s does not exist',10,0,@FullOutputTableNamePerfmonStats); + SELECT N'No Perfmon data available as the table cannot be found'; + END +END +ELSE /* Table exists then run the query */ +BEGIN + EXEC sp_executesql @Sql, + N'@StartDate DATETIMEOFFSET(7), + @EndDate DATETIMEOFFSET(7), + @Servername NVARCHAR(128)', + @StartDate=@StartDate, + @EndDate=@EndDate, + @Servername=@Servername; +END + +/* Blitz cache data */ +RAISERROR('Sortorder for BlitzCache data: %s',0,0,@BlitzCacheSortorder) WITH NOWAIT; + +/* Set intial CTE */ +SET @Sql = N'WITH CheckDates AS ( +SELECT DISTINCT CheckDate +FROM ' ++@FullOutputTableNameBlitzCache ++N' +WHERE [ServerName] = @Servername +AND [CheckDate] BETWEEN @StartDate AND @EndDate' ++@NewLine ++CASE + WHEN @Databasename IS NOT NULL THEN N'AND [DatabaseName] = @Databasename'+@NewLine + ELSE N'' +END ++N')' +; + +SET @Sql += @NewLine; + +/* Append additional CTEs based on sortorder */ +SET @Sql += ( +SELECT CAST(N',' AS NVARCHAR(MAX)) ++[SortOptions].[Aliasname]+N' AS ( +SELECT + [ServerName] + ,'+[SortOptions].[Aliasname]+N'.[CheckDate] + ,[Sortorder] + ,[TimeFrameRank] + ,ROW_NUMBER() OVER(ORDER BY ['+[SortOptions].[Columnname]+N'] DESC) AS [OverallRank] + ,'+[SortOptions].[Aliasname]+N'.[QueryType] + ,'+[SortOptions].[Aliasname]+N'.[QueryText] + ,'+[SortOptions].[Aliasname]+N'.[DatabaseName] + ,'+[SortOptions].[Aliasname]+N'.[AverageCPU] + ,'+[SortOptions].[Aliasname]+N'.[TotalCPU] + ,'+[SortOptions].[Aliasname]+N'.[PercentCPUByType] + ,'+[SortOptions].[Aliasname]+N'.[AverageDuration] + ,'+[SortOptions].[Aliasname]+N'.[TotalDuration] + ,'+[SortOptions].[Aliasname]+N'.[PercentDurationByType] + ,'+[SortOptions].[Aliasname]+N'.[AverageReads] + ,'+[SortOptions].[Aliasname]+N'.[TotalReads] + ,'+[SortOptions].[Aliasname]+N'.[PercentReadsByType] + ,'+[SortOptions].[Aliasname]+N'.[AverageWrites] + ,'+[SortOptions].[Aliasname]+N'.[TotalWrites] + ,'+[SortOptions].[Aliasname]+N'.[PercentWritesByType] + ,'+[SortOptions].[Aliasname]+N'.[ExecutionCount] + ,'+[SortOptions].[Aliasname]+N'.[ExecutionWeight] + ,'+[SortOptions].[Aliasname]+N'.[PercentExecutionsByType] + ,'+[SortOptions].[Aliasname]+N'.[ExecutionsPerMinute] + ,'+[SortOptions].[Aliasname]+N'.[PlanCreationTime] + ,'+[SortOptions].[Aliasname]+N'.[PlanCreationTimeHours] + ,'+[SortOptions].[Aliasname]+N'.[LastExecutionTime] + ,'+[SortOptions].[Aliasname]+N'.[PlanHandle] + ,'+[SortOptions].[Aliasname]+N'.[SqlHandle] + ,'+[SortOptions].[Aliasname]+N'.[SQL Handle More Info] + ,'+[SortOptions].[Aliasname]+N'.[QueryHash] + ,'+[SortOptions].[Aliasname]+N'.[Query Hash More Info] + ,'+[SortOptions].[Aliasname]+N'.[QueryPlanHash] + ,'+[SortOptions].[Aliasname]+N'.[StatementStartOffset] + ,'+[SortOptions].[Aliasname]+N'.[StatementEndOffset] + ,'+[SortOptions].[Aliasname]+N'.[MinReturnedRows] + ,'+[SortOptions].[Aliasname]+N'.[MaxReturnedRows] + ,'+[SortOptions].[Aliasname]+N'.[AverageReturnedRows] + ,'+[SortOptions].[Aliasname]+N'.[TotalReturnedRows] + ,'+[SortOptions].[Aliasname]+N'.[QueryPlan] + ,'+[SortOptions].[Aliasname]+N'.[NumberOfPlans] + ,'+[SortOptions].[Aliasname]+N'.[NumberOfDistinctPlans] + ,'+[SortOptions].[Aliasname]+N'.[MinGrantKB] + ,'+[SortOptions].[Aliasname]+N'.[MaxGrantKB] + ,'+[SortOptions].[Aliasname]+N'.[MinUsedGrantKB] + ,'+[SortOptions].[Aliasname]+N'.[MaxUsedGrantKB] + ,'+[SortOptions].[Aliasname]+N'.[PercentMemoryGrantUsed] + ,'+[SortOptions].[Aliasname]+N'.[AvgMaxMemoryGrant] + ,'+[SortOptions].[Aliasname]+N'.[MinSpills] + ,'+[SortOptions].[Aliasname]+N'.[MaxSpills] + ,'+[SortOptions].[Aliasname]+N'.[TotalSpills] + ,'+[SortOptions].[Aliasname]+N'.[AvgSpills] + ,'+[SortOptions].[Aliasname]+N'.[QueryPlanCost] +FROM CheckDates +CROSS APPLY ( + SELECT TOP (5) + [ServerName] + ,'+[SortOptions].[Aliasname]+N'.[CheckDate] + ,'+QUOTENAME(UPPER([SortOptions].[Sortorder]),N'''')+N' AS [Sortorder] + ,ROW_NUMBER() OVER(ORDER BY ['+[SortOptions].[Columnname]+N'] DESC) AS [TimeFrameRank] + ,'+[SortOptions].[Aliasname]+N'.[QueryType] + ,'+[SortOptions].[Aliasname]+N'.[QueryText] + ,'+[SortOptions].[Aliasname]+N'.[DatabaseName] + ,'+[SortOptions].[Aliasname]+N'.[AverageCPU] + ,'+[SortOptions].[Aliasname]+N'.[TotalCPU] + ,'+[SortOptions].[Aliasname]+N'.[PercentCPUByType] + ,'+[SortOptions].[Aliasname]+N'.[AverageDuration] + ,'+[SortOptions].[Aliasname]+N'.[TotalDuration] + ,'+[SortOptions].[Aliasname]+N'.[PercentDurationByType] + ,'+[SortOptions].[Aliasname]+N'.[AverageReads] + ,'+[SortOptions].[Aliasname]+N'.[TotalReads] + ,'+[SortOptions].[Aliasname]+N'.[PercentReadsByType] + ,'+[SortOptions].[Aliasname]+N'.[AverageWrites] + ,'+[SortOptions].[Aliasname]+N'.[TotalWrites] + ,'+[SortOptions].[Aliasname]+N'.[PercentWritesByType] + ,'+[SortOptions].[Aliasname]+N'.[ExecutionCount] + ,'+[SortOptions].[Aliasname]+N'.[ExecutionWeight] + ,'+[SortOptions].[Aliasname]+N'.[PercentExecutionsByType] + ,'+[SortOptions].[Aliasname]+N'.[ExecutionsPerMinute] + ,'+[SortOptions].[Aliasname]+N'.[PlanCreationTime] + ,'+[SortOptions].[Aliasname]+N'.[PlanCreationTimeHours] + ,'+[SortOptions].[Aliasname]+N'.[LastExecutionTime] + ,'+[SortOptions].[Aliasname]+N'.[PlanHandle] + ,'+[SortOptions].[Aliasname]+N'.[SqlHandle] + ,'+[SortOptions].[Aliasname]+N'.[SQL Handle More Info] + ,'+[SortOptions].[Aliasname]+N'.[QueryHash] + ,'+[SortOptions].[Aliasname]+N'.[Query Hash More Info] + ,'+[SortOptions].[Aliasname]+N'.[QueryPlanHash] + ,'+[SortOptions].[Aliasname]+N'.[StatementStartOffset] + ,'+[SortOptions].[Aliasname]+N'.[StatementEndOffset] + ,'+[SortOptions].[Aliasname]+N'.[MinReturnedRows] + ,'+[SortOptions].[Aliasname]+N'.[MaxReturnedRows] + ,'+[SortOptions].[Aliasname]+N'.[AverageReturnedRows] + ,'+[SortOptions].[Aliasname]+N'.[TotalReturnedRows] + ,'+[SortOptions].[Aliasname]+N'.[QueryPlan] + ,'+[SortOptions].[Aliasname]+N'.[NumberOfPlans] + ,'+[SortOptions].[Aliasname]+N'.[NumberOfDistinctPlans] + ,'+[SortOptions].[Aliasname]+N'.[MinGrantKB] + ,'+[SortOptions].[Aliasname]+N'.[MaxGrantKB] + ,'+[SortOptions].[Aliasname]+N'.[MinUsedGrantKB] + ,'+[SortOptions].[Aliasname]+N'.[MaxUsedGrantKB] + ,'+[SortOptions].[Aliasname]+N'.[PercentMemoryGrantUsed] + ,'+[SortOptions].[Aliasname]+N'.[AvgMaxMemoryGrant] + ,'+[SortOptions].[Aliasname]+N'.[MinSpills] + ,'+[SortOptions].[Aliasname]+N'.[MaxSpills] + ,'+[SortOptions].[Aliasname]+N'.[TotalSpills] + ,'+[SortOptions].[Aliasname]+N'.[AvgSpills] + ,'+[SortOptions].[Aliasname]+N'.[QueryPlanCost] + FROM '+@FullOutputTableNameBlitzCache+N' AS '+[SortOptions].[Aliasname]+N' + WHERE [ServerName] = @Servername + AND [CheckDate] BETWEEN @StartDate AND @EndDate + AND ['+[SortOptions].[Aliasname]+N'].[CheckDate] = [CheckDates].[CheckDate]' + +@NewLine + +CASE + WHEN @Databasename IS NOT NULL THEN N'AND ['+[SortOptions].[Aliasname]+N'].[DatabaseName] = @Databasename'+@NewLine + ELSE N'' + END + +CASE + WHEN [Sortorder] = N'cpu' THEN N'AND [TotalCPU] > 0' + WHEN [Sortorder] = N'reads' THEN N'AND [TotalReads] > 0' + WHEN [Sortorder] = N'writes' THEN N'AND [TotalWrites] > 0' + WHEN [Sortorder] = N'duration' THEN N'AND [TotalDuration] > 0' + WHEN [Sortorder] = N'executions' THEN N'AND [ExecutionCount] > 0' + WHEN [Sortorder] = N'memory grant' THEN N'AND [MaxGrantKB] > 0' + WHEN [Sortorder] = N'spills' THEN N'AND [MaxSpills] > 0' + ELSE N'' + END + +N' + ORDER BY ['+[SortOptions].[Columnname]+N'] DESC) '+[SortOptions].[Aliasname]+N' +)' +FROM (VALUES + (N'cpu',N'TopCPU',N'TotalCPU'), + (N'reads',N'TopReads',N'TotalReads'), + (N'writes',N'TopWrites',N'TotalWrites'), + (N'duration',N'TopDuration',N'TotalDuration'), + (N'executions',N'TopExecutions',N'ExecutionCount'), + (N'memory grant',N'TopMemoryGrants',N'MaxGrantKB'), + (N'spills',N'TopSpills',N'MaxSpills') + ) SortOptions(Sortorder,Aliasname,Columnname) +WHERE + CASE /* for spills and memory grant sorts make sure the underlying columns exist in the DMV otherwise do not include them */ + WHEN (@IncludeMemoryGrants = 0 OR @IncludeMemoryGrants IS NULL) AND ([SortOptions].[Sortorder] = N'memory grant' OR [SortOptions].[Sortorder] = N'all') THEN NULL + WHEN (@IncludeSpills = 0 OR @IncludeSpills IS NULL) AND ([SortOptions].[Sortorder] = N'spills' OR [SortOptions].[Sortorder] = N'all') THEN NULL + ELSE [SortOptions].[Sortorder] + END = ISNULL(NULLIF(@BlitzCacheSortorder,N'all'),[SortOptions].[Sortorder]) +FOR XML PATH(N''), TYPE).value(N'.[1]', N'NVARCHAR(MAX)'); + +SET @Sql += @NewLine; + +/* Build the select statements to return the data after CTE declarations */ +SET @Sql += ( +SELECT STUFF(( +SELECT @NewLine ++N'UNION ALL' ++@NewLine ++N'SELECT * +FROM '+[SortOptions].[Aliasname] +FROM (VALUES + (N'cpu',N'TopCPU',N'TotalCPU'), + (N'reads',N'TopReads',N'TotalReads'), + (N'writes',N'TopWrites',N'TotalWrites'), + (N'duration',N'TopDuration',N'TotalDuration'), + (N'executions',N'TopExecutions',N'ExecutionCount'), + (N'memory grant',N'TopMemoryGrants',N'MaxGrantKB'), + (N'spills',N'TopSpills',N'MaxSpills') + ) SortOptions(Sortorder,Aliasname,Columnname) +WHERE + CASE /* for spills and memory grant sorts make sure the underlying columns exist in the DMV otherwise do not include them */ + WHEN (@IncludeMemoryGrants = 0 OR @IncludeMemoryGrants IS NULL) AND ([SortOptions].[Sortorder] = N'memory grant' OR [SortOptions].[Sortorder] = N'all') THEN NULL + WHEN (@IncludeSpills = 0 OR @IncludeSpills IS NULL) AND ([SortOptions].[Sortorder] = N'spills' OR [SortOptions].[Sortorder] = N'all') THEN NULL + ELSE [SortOptions].[Sortorder] + END = ISNULL(NULLIF(@BlitzCacheSortorder,N'all'),[SortOptions].[Sortorder]) +FOR XML PATH(N''), TYPE).value(N'.[1]', N'NVARCHAR(MAX)'),1,11,N'') +); + +/* Append Order By */ +SET @Sql += @NewLine ++N'ORDER BY + [Sortorder] ASC, + [CheckDate] ASC, + [TimeFrameRank] ASC'; + +/* Append OPTION(RECOMPILE, MAXDOP) to complete the statement */ +SET @Sql += @NewLine ++N'OPTION(RECOMPILE, MAXDOP '+CAST(@Maxdop AS NVARCHAR(2))+N');'; + +RAISERROR('Getting BlitzCache info from %s',0,0,@FullOutputTableNameBlitzCache) WITH NOWAIT; + +IF (@Debug = 1) +BEGIN + PRINT SUBSTRING(@Sql, 0, 4000); + PRINT SUBSTRING(@Sql, 4000, 8000); + PRINT SUBSTRING(@Sql, 8000, 12000); + PRINT SUBSTRING(@Sql, 12000, 16000); + PRINT SUBSTRING(@Sql, 16000, 20000); + PRINT SUBSTRING(@Sql, 20000, 24000); + PRINT SUBSTRING(@Sql, 24000, 28000); +END + +IF (OBJECT_ID(@FullOutputTableNameBlitzCache) IS NULL) +BEGIN + IF (@OutputTableNameBlitzCache IS NULL) + BEGIN + RAISERROR('BlitzCache data skipped',10,0); + SELECT N'Skipped logged BlitzCache data as NULL was passed to parameter @OutputTableNameBlitzCache'; + END + ELSE + BEGIN + RAISERROR('Table provided for BlitzCache data: %s does not exist',10,0,@FullOutputTableNameBlitzCache); + SELECT N'No BlitzCache data available as the table cannot be found'; + END +END +ELSE /* Table exists then run the query */ +BEGIN + EXEC sp_executesql @Sql, + N'@Servername NVARCHAR(128), + @Databasename NVARCHAR(128), + @BlitzCacheSortorder NVARCHAR(20), + @StartDate DATETIMEOFFSET(7), + @EndDate DATETIMEOFFSET(7)', + @Servername = @Servername, + @Databasename = @Databasename, + @BlitzCacheSortorder = @BlitzCacheSortorder, + @StartDate = @StartDate, + @EndDate = @EndDate; +END + + + +/* BlitzWho data */ +SET @Sql = N' +SELECT [ServerName] + ,[CheckDate] + ,[elapsed_time] + ,[session_id] + ,[database_name] + ,[query_text_snippet] + ,[query_plan] + ,[live_query_plan] + ,[query_cost] + ,[status] + ,[wait_info] + ,[top_session_waits] + ,[blocking_session_id] + ,[open_transaction_count] + ,[is_implicit_transaction] + ,[nt_domain] + ,[host_name] + ,[login_name] + ,[nt_user_name] + ,[program_name] + ,[fix_parameter_sniffing] + ,[client_interface_name] + ,[login_time] + ,[start_time] + ,[request_time] + ,[request_cpu_time] + ,[degree_of_parallelism] + ,[request_logical_reads] + ,[Logical_Reads_MB] + ,[request_writes] + ,[Logical_Writes_MB] + ,[request_physical_reads] + ,[Physical_reads_MB] + ,[session_cpu] + ,[session_logical_reads] + ,[session_logical_reads_MB] + ,[session_physical_reads] + ,[session_physical_reads_MB] + ,[session_writes] + ,[session_writes_MB] + ,[tempdb_allocations_mb] + ,[memory_usage] + ,[estimated_completion_time] + ,[percent_complete] + ,[deadlock_priority] + ,[transaction_isolation_level] + ,[last_dop] + ,[min_dop] + ,[max_dop] + ,[last_grant_kb] + ,[min_grant_kb] + ,[max_grant_kb] + ,[last_used_grant_kb] + ,[min_used_grant_kb] + ,[max_used_grant_kb] + ,[last_ideal_grant_kb] + ,[min_ideal_grant_kb] + ,[max_ideal_grant_kb] + ,[last_reserved_threads] + ,[min_reserved_threads] + ,[max_reserved_threads] + ,[last_used_threads] + ,[min_used_threads] + ,[max_used_threads] + ,[grant_time] + ,[requested_memory_kb] + ,[grant_memory_kb] + ,[is_request_granted] + ,[required_memory_kb] + ,[query_memory_grant_used_memory_kb] + ,[ideal_memory_kb] + ,[is_small] + ,[timeout_sec] + ,[resource_semaphore_id] + ,[wait_order] + ,[wait_time_ms] + ,[next_candidate_for_memory_grant] + ,[target_memory_kb] + ,[max_target_memory_kb] + ,[total_memory_kb] + ,[available_memory_kb] + ,[granted_memory_kb] + ,[query_resource_semaphore_used_memory_kb] + ,[grantee_count] + ,[waiter_count] + ,[timeout_error_count] + ,[forced_grant_count] + ,[workload_group_name] + ,[resource_pool_name] + ,[context_info] + ,[query_hash] + ,[query_plan_hash] + ,[sql_handle] + ,[plan_handle] + ,[statement_start_offset] + ,[statement_end_offset] + FROM '+@FullOutputTableNameBlitzWho+N' + WHERE [ServerName] = @Servername + AND ([CheckDate] BETWEEN @StartDate AND @EndDate OR [start_time] BETWEEN CAST(@StartDate AS DATETIME) AND CAST(@EndDate AS DATETIME)) + ' + +CASE + WHEN @Databasename IS NOT NULL THEN N'AND [database_name] = @Databasename + ' + ELSE N'' + END ++N'ORDER BY [CheckDate] ASC + OPTION (RECOMPILE, MAXDOP '+CAST(@Maxdop AS NVARCHAR(2))+N');'; + +RAISERROR('Getting BlitzWho info from %s',0,0,@FullOutputTableNameBlitzWho) WITH NOWAIT; + +IF (@Debug = 1) +BEGIN + PRINT @Sql; +END + +IF (OBJECT_ID(@FullOutputTableNameBlitzWho) IS NULL) +BEGIN + IF (@OutputTableNameBlitzWho IS NULL) + BEGIN + RAISERROR('BlitzWho data skipped',10,0); + SELECT N'Skipped logged BlitzWho data as NULL was passed to parameter @OutputTableNameBlitzWho'; + END + ELSE + BEGIN + RAISERROR('Table provided for BlitzWho data: %s does not exist',10,0,@FullOutputTableNameBlitzWho); + SELECT N'No BlitzWho data available as the table cannot be found'; + END +END +ELSE +BEGIN + EXEC sp_executesql @Sql, + N'@StartDate DATETIMEOFFSET(7), + @EndDate DATETIMEOFFSET(7), + @Servername NVARCHAR(128), + @Databasename NVARCHAR(128)', + @StartDate=@StartDate, + @EndDate=@EndDate, + @Servername=@Servername, + @Databasename = @Databasename; +END + + +GO IF OBJECT_ID('dbo.sp_BlitzBackups') IS NULL EXEC ('CREATE PROCEDURE dbo.sp_BlitzBackups AS RETURN 0;'); GO @@ -9419,7 +10309,7 @@ AS SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.02', @VersionDate = '20210322'; + SELECT @Version = '8.03', @VersionDate = '20210420'; IF(@VersionCheckMode = 1) BEGIN @@ -11199,7 +12089,7 @@ BEGIN SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.02', @VersionDate = '20210322'; +SELECT @Version = '8.03', @VersionDate = '20210420'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -14902,7 +15792,7 @@ SET s.variable_datatype = CASE WHEN s.variable_datatype LIKE '%(%)%' s.compile_time_value = CASE WHEN s.compile_time_value LIKE '%(%)%' THEN SUBSTRING(s.compile_time_value, CHARINDEX('(', s.compile_time_value) + 1, - CHARINDEX(')', s.compile_time_value) - 1 - CHARINDEX('(', s.compile_time_value) + CHARINDEX(')', s.compile_time_value, CHARINDEX('(', s.compile_time_value) + 1) - 1 - CHARINDEX('(', s.compile_time_value) ) WHEN variable_datatype NOT IN ('bit', 'tinyint', 'smallint', 'int', 'bigint') AND s.variable_datatype NOT LIKE '%binary%' @@ -17862,20 +18752,23 @@ ELSE IF @ValidOutputLocation = 1 BEGIN - SET @StringToExecute = 'USE ' + SET @StringToExecute = N'USE ' + @OutputDatabaseName - + '; IF EXISTS(SELECT * FROM ' + + N'; IF EXISTS(SELECT * FROM ' + @OutputDatabaseName - + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' + + N'.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' + @OutputSchemaName - + ''') AND NOT EXISTS (SELECT * FROM ' + + N''') AND NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName - + '.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''' - + @OutputSchemaName + ''' AND QUOTENAME(TABLE_NAME) = ''' - + @OutputTableName + ''') CREATE TABLE ' - + @OutputSchemaName + '.' + + N'.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''' + + @OutputSchemaName + N''' AND QUOTENAME(TABLE_NAME) = ''' + + @OutputTableName + N''') CREATE TABLE ' + + @OutputSchemaName + N'.' + @OutputTableName - + N'(ID bigint NOT NULL IDENTITY(1,1), + + CONVERT + ( + nvarchar(MAX), + N'(ID bigint NOT NULL IDENTITY(1,1), ServerName NVARCHAR(258), CheckDate DATETIMEOFFSET, Version NVARCHAR(258), @@ -17951,27 +18844,28 @@ ELSE AvgSpills MONEY, QueryPlanCost FLOAT, JoinKey AS ServerName + Cast(CheckDate AS NVARCHAR(50)), - CONSTRAINT [PK_' + REPLACE(REPLACE(@OutputTableName,'[',''),']','') + '] PRIMARY KEY CLUSTERED(ID ASC));'; + CONSTRAINT [PK_' + REPLACE(REPLACE(@OutputTableName,N'[',N''),N']',N'') + N'] PRIMARY KEY CLUSTERED(ID ASC));' + ); SET @StringToExecute += N'IF EXISTS(SELECT * FROM ' +@OutputDatabaseName +N'.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' +@OutputSchemaName - +''') AND EXISTS (SELECT * FROM ' + +N''') AND EXISTS (SELECT * FROM ' +@OutputDatabaseName+ N'.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''' +@OutputSchemaName - +''' AND QUOTENAME(TABLE_NAME) = ''' + +N''' AND QUOTENAME(TABLE_NAME) = ''' +@OutputTableName - +''') AND EXISTS (SELECT * FROM ' + +N''') AND EXISTS (SELECT * FROM ' +@OutputDatabaseName+ N'.sys.computed_columns WHERE [name] = N''PlanCreationTimeHours'' AND QUOTENAME(OBJECT_NAME(object_id)) = N''' +@OutputTableName - +''' AND [definition] = N''(datediff(hour,[PlanCreationTime],sysdatetime()))'') + +N''' AND [definition] = N''(datediff(hour,[PlanCreationTime],sysdatetime()))'') BEGIN RAISERROR(''We noticed that you are running an old computed column definition for PlanCreationTimeHours, fixing that now'',0,0) WITH NOWAIT; - ALTER TABLE '+@OutputDatabaseName+'.'+@OutputSchemaName+'.'+@OutputTableName+' DROP COLUMN [PlanCreationTimeHours]; - ALTER TABLE '+@OutputDatabaseName+'.'+@OutputSchemaName+'.'+@OutputTableName+' ADD [PlanCreationTimeHours] AS DATEDIFF(HOUR,CONVERT(DATETIMEOFFSET(7),[PlanCreationTime]),[CheckDate]); + ALTER TABLE '+@OutputDatabaseName+N'.'+@OutputSchemaName+N'.'+@OutputTableName+N' DROP COLUMN [PlanCreationTimeHours]; + ALTER TABLE '+@OutputDatabaseName+N'.'+@OutputSchemaName+N'.'+@OutputTableName+N' ADD [PlanCreationTimeHours] AS DATEDIFF(HOUR,CONVERT(DATETIMEOFFSET(7),[PlanCreationTime]),[CheckDate]); END '; IF @ValidOutputServer = 1 @@ -18381,6 +19275,7 @@ ALTER PROCEDURE dbo.sp_BlitzIndex @SkipPartitions BIT = 0, @SkipStatistics BIT = 1, @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, @IgnoreDatabases NVARCHAR(MAX) = NULL, /* Comma-delimited list of databases you want to skip */ @ThresholdMB INT = 250 /* Number of megabytes that an object must be before we include it in basic results */, @@ -18403,7 +19298,7 @@ AS SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.02', @VersionDate = '20210322'; +SELECT @Version = '8.03', @VersionDate = '20210420'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -20000,24 +20895,40 @@ BEGIN TRY AND ColumnNamesWithDataTypes.IndexColumnType = ''Included'' ) AS included_columns_with_data_type ' - /* Github #2780 BGO Removing SQL Server 2019's new missing index sample plan because it doesn't work yet: - IF NOT EXISTS (SELECT * FROM sys.all_objects WHERE name = 'dm_db_missing_index_group_stats_query') - */ - SET @dsql = @dsql + N' , NULL AS sample_query_plan ' - /* Github #2780 BGO Removing SQL Server 2019's new missing index sample plan because it doesn't work yet: + /* Github #2780 BGO Removing SQL Server 2019's new missing index sample plan because it doesn't work yet:*/ + IF NOT EXISTS + ( + SELECT + 1/0 + FROM sys.all_objects AS o + WHERE o.name = 'dm_db_missing_index_group_stats_query' + ) + SELECT + @dsql += N' , NULL AS sample_query_plan ' + /* Github #2780 BGO Removing SQL Server 2019's new missing index sample plan because it doesn't work yet:*/ ELSE BEGIN - SET @dsql = @dsql + N' , sample_query_plan = (SELECT TOP 1 p.query_plan - FROM sys.dm_db_missing_index_group_stats gs - CROSS APPLY (SELECT TOP 1 s.plan_handle - FROM sys.dm_db_missing_index_group_stats_query q - INNER JOIN sys.dm_exec_query_stats s ON q.query_plan_hash = s.query_plan_hash - WHERE gs.group_handle = q.group_handle - ORDER BY (q.user_seeks + q.user_scans) DESC, s.total_logical_reads DESC) q2 - CROSS APPLY sys.dm_exec_query_plan(q2.plan_handle) p - WHERE gs.group_handle = gs.group_handle) ' + SELECT + @dsql += N' + , sample_query_plan = + ( + SELECT TOP (1) + p.query_plan + FROM sys.dm_db_missing_index_group_stats gs + CROSS APPLY + ( + SELECT TOP (1) + s.plan_handle + FROM sys.dm_db_missing_index_group_stats_query q + INNER JOIN sys.dm_exec_query_stats s + ON q.query_plan_hash = s.query_plan_hash + WHERE gs.group_handle = q.group_handle + ORDER BY (q.user_seeks + q.user_scans) DESC, s.total_logical_reads DESC + ) q2 + CROSS APPLY sys.dm_exec_query_plan(q2.plan_handle) p + ) ' END - */ + SET @dsql = @dsql + N'FROM ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_groups ig @@ -20868,7 +21779,8 @@ BEGIN --We do a left join here in case this is a disabled NC. --In that case, it won't have any size info/pages allocated. - + IF (@ShowColumnstoreOnly = 0) + BEGIN WITH table_mode_cte AS ( SELECT s.db_schema_object_indexid, @@ -21059,7 +21971,8 @@ BEGIN WHERE s.object_id = @ObjectID ORDER BY s.auto_created, s.user_created, s.name, hist.step_number;'; EXEC sp_executesql @dsql, N'@ObjectID INT', @ObjectID; - END + END + END /* Visualize columnstore index contents. More info: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2584 */ IF 2 = (SELECT SUM(1) FROM sys.all_objects WHERE name IN ('column_store_row_groups','column_store_segments')) @@ -23531,7 +24444,7 @@ ELSE IF (@Mode=1) /*Summarize*/ ISNULL(i.index_name, '''') AS [Index Name], CASE WHEN i.is_primary_key = 1 AND i.index_definition <> ''[HEAP]'' - THEN N''-ALTER TABLE '' + QUOTENAME(i.[database_name]) + N''.'' + QUOTENAME(i.[schema_name]) + N''.'' + QUOTENAME(i.[object_name]) + + THEN N''--ALTER TABLE '' + QUOTENAME(i.[database_name]) + N''.'' + QUOTENAME(i.[schema_name]) + N''.'' + QUOTENAME(i.[object_name]) + N'' DROP CONSTRAINT '' + QUOTENAME(i.index_name) + N'';'' WHEN i.is_primary_key = 0 AND i.index_definition <> ''[HEAP]'' THEN N''--DROP INDEX ''+ QUOTENAME(i.index_name) + N'' ON '' + QUOTENAME(i.[database_name]) + N''.'' + @@ -23714,46 +24627,45 @@ ELSE IF (@Mode=1) /*Summarize*/ FROM #IndexSanity AS i --left join here so we don't lose disabled nc indexes LEFT JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id LEFT JOIN #IndexCreateTsql AS ict ON i.index_sanity_id = ict.index_sanity_id - ORDER BY CASE WHEN @SortDirection = 'desc' THEN - CASE WHEN @SortOrder = N'rows' THEN sz.total_rows - WHEN @SortOrder = N'reserved_mb' THEN sz.total_reserved_MB - WHEN @SortOrder = N'size' THEN sz.total_reserved_MB - WHEN @SortOrder = N'reserved_lob_mb' THEN sz.total_reserved_LOB_MB - WHEN @SortOrder = N'lob' THEN sz.total_reserved_LOB_MB - WHEN @SortOrder = N'total_row_lock_wait_in_ms' THEN COALESCE(sz.total_row_lock_wait_in_ms,0) - WHEN @SortOrder = N'total_page_lock_wait_in_ms' THEN COALESCE(sz.total_page_lock_wait_in_ms,0) - WHEN @SortOrder = N'lock_time' THEN (COALESCE(sz.total_row_lock_wait_in_ms,0) + COALESCE(sz.total_page_lock_wait_in_ms,0)) - WHEN @SortOrder = N'total_reads' THEN total_reads - WHEN @SortOrder = N'reads' THEN total_reads - WHEN @SortOrder = N'user_updates' THEN user_updates - WHEN @SortOrder = N'writes' THEN user_updates - WHEN @SortOrder = N'reads_per_write' THEN reads_per_write - WHEN @SortOrder = N'ratio' THEN reads_per_write - WHEN @SortOrder = N'forward_fetches' THEN sz.total_forwarded_fetch_count - WHEN @SortOrder = N'fetches' THEN sz.total_forwarded_fetch_count - ELSE NULL END - ELSE 1 END - DESC, /* Shout out to DHutmacher */ - CASE WHEN @SortDirection = 'asc' THEN - CASE WHEN @SortOrder = N'rows' THEN sz.total_rows - WHEN @SortOrder = N'reserved_mb' THEN sz.total_reserved_MB - WHEN @SortOrder = N'size' THEN sz.total_reserved_MB - WHEN @SortOrder = N'reserved_lob_mb' THEN sz.total_reserved_LOB_MB - WHEN @SortOrder = N'lob' THEN sz.total_reserved_LOB_MB - WHEN @SortOrder = N'total_row_lock_wait_in_ms' THEN COALESCE(sz.total_row_lock_wait_in_ms,0) - WHEN @SortOrder = N'total_page_lock_wait_in_ms' THEN COALESCE(sz.total_page_lock_wait_in_ms,0) - WHEN @SortOrder = N'lock_time' THEN (COALESCE(sz.total_row_lock_wait_in_ms,0) + COALESCE(sz.total_page_lock_wait_in_ms,0)) - WHEN @SortOrder = N'total_reads' THEN total_reads - WHEN @SortOrder = N'reads' THEN total_reads - WHEN @SortOrder = N'user_updates' THEN user_updates - WHEN @SortOrder = N'writes' THEN user_updates - WHEN @SortOrder = N'reads_per_write' THEN reads_per_write - WHEN @SortOrder = N'ratio' THEN reads_per_write - WHEN @SortOrder = N'forward_fetches' THEN sz.total_forwarded_fetch_count - WHEN @SortOrder = N'fetches' THEN sz.total_forwarded_fetch_count - ELSE NULL END - ELSE 1 END - ASC, + ORDER BY /* Shout out to DHutmacher */ + /*DESC*/ + CASE WHEN @SortOrder = N'rows' AND @SortDirection = N'desc' THEN sz.total_rows ELSE NULL END DESC, + CASE WHEN @SortOrder = N'reserved_mb' AND @SortDirection = N'desc' THEN sz.total_reserved_MB ELSE NULL END DESC, + CASE WHEN @SortOrder = N'size' AND @SortDirection = N'desc' THEN sz.total_reserved_MB ELSE NULL END DESC, + CASE WHEN @SortOrder = N'reserved_lob_mb' AND @SortDirection = N'desc' THEN sz.total_reserved_LOB_MB ELSE NULL END DESC, + CASE WHEN @SortOrder = N'lob' AND @SortDirection = N'desc' THEN sz.total_reserved_LOB_MB ELSE NULL END DESC, + CASE WHEN @SortOrder = N'total_row_lock_wait_in_ms' AND @SortDirection = N'desc' THEN COALESCE(sz.total_row_lock_wait_in_ms,0) ELSE NULL END DESC, + CASE WHEN @SortOrder = N'total_page_lock_wait_in_ms' AND @SortDirection = N'desc' THEN COALESCE(sz.total_page_lock_wait_in_ms,0) ELSE NULL END DESC, + CASE WHEN @SortOrder = N'lock_time' AND @SortDirection = N'desc' THEN (COALESCE(sz.total_row_lock_wait_in_ms,0) + COALESCE(sz.total_page_lock_wait_in_ms,0)) ELSE NULL END DESC, + CASE WHEN @SortOrder = N'total_reads' AND @SortDirection = N'desc' THEN total_reads ELSE NULL END DESC, + CASE WHEN @SortOrder = N'reads' AND @SortDirection = N'desc' THEN total_reads ELSE NULL END DESC, + CASE WHEN @SortOrder = N'user_updates' AND @SortDirection = N'desc' THEN user_updates ELSE NULL END DESC, + CASE WHEN @SortOrder = N'writes' AND @SortDirection = N'desc' THEN user_updates ELSE NULL END DESC, + CASE WHEN @SortOrder = N'reads_per_write' AND @SortDirection = N'desc' THEN reads_per_write ELSE NULL END DESC, + CASE WHEN @SortOrder = N'ratio' AND @SortDirection = N'desc' THEN reads_per_write ELSE NULL END DESC, + CASE WHEN @SortOrder = N'forward_fetches' AND @SortDirection = N'desc' THEN sz.total_forwarded_fetch_count ELSE NULL END DESC, + CASE WHEN @SortOrder = N'fetches' AND @SortDirection = N'desc' THEN sz.total_forwarded_fetch_count ELSE NULL END DESC, + CASE WHEN @SortOrder = N'create_date' AND @SortDirection = N'desc' THEN CONVERT(DATETIME, i.create_date) ELSE NULL END DESC, + CASE WHEN @SortOrder = N'modify_date' AND @SortDirection = N'desc' THEN CONVERT(DATETIME, i.modify_date) ELSE NULL END DESC, + /*ASC*/ + CASE WHEN @SortOrder = N'rows' AND @SortDirection = N'asc' THEN sz.total_rows ELSE NULL END ASC, + CASE WHEN @SortOrder = N'reserved_mb' AND @SortDirection = N'asc' THEN sz.total_reserved_MB ELSE NULL END ASC, + CASE WHEN @SortOrder = N'size' AND @SortDirection = N'asc' THEN sz.total_reserved_MB ELSE NULL END ASC, + CASE WHEN @SortOrder = N'reserved_lob_mb' AND @SortDirection = N'asc' THEN sz.total_reserved_LOB_MB ELSE NULL END ASC, + CASE WHEN @SortOrder = N'lob' AND @SortDirection = N'asc' THEN sz.total_reserved_LOB_MB ELSE NULL END ASC, + CASE WHEN @SortOrder = N'total_row_lock_wait_in_ms' AND @SortDirection = N'asc' THEN COALESCE(sz.total_row_lock_wait_in_ms,0) ELSE NULL END ASC, + CASE WHEN @SortOrder = N'total_page_lock_wait_in_ms' AND @SortDirection = N'asc' THEN COALESCE(sz.total_page_lock_wait_in_ms,0) ELSE NULL END ASC, + CASE WHEN @SortOrder = N'lock_time' AND @SortDirection = N'asc' THEN (COALESCE(sz.total_row_lock_wait_in_ms,0) + COALESCE(sz.total_page_lock_wait_in_ms,0)) ELSE NULL END ASC, + CASE WHEN @SortOrder = N'total_reads' AND @SortDirection = N'asc' THEN total_reads ELSE NULL END ASC, + CASE WHEN @SortOrder = N'reads' AND @SortDirection = N'asc' THEN total_reads ELSE NULL END ASC, + CASE WHEN @SortOrder = N'user_updates' AND @SortDirection = N'asc' THEN user_updates ELSE NULL END ASC, + CASE WHEN @SortOrder = N'writes' AND @SortDirection = N'asc' THEN user_updates ELSE NULL END ASC, + CASE WHEN @SortOrder = N'reads_per_write' AND @SortDirection = N'asc' THEN reads_per_write ELSE NULL END ASC, + CASE WHEN @SortOrder = N'ratio' AND @SortDirection = N'asc' THEN reads_per_write ELSE NULL END ASC, + CASE WHEN @SortOrder = N'forward_fetches' AND @SortDirection = N'asc' THEN sz.total_forwarded_fetch_count ELSE NULL END ASC, + CASE WHEN @SortOrder = N'fetches' AND @SortDirection = N'asc' THEN sz.total_forwarded_fetch_count ELSE NULL END ASC, + CASE WHEN @SortOrder = N'create_date' AND @SortDirection = N'asc' THEN CONVERT(DATETIME, i.create_date) ELSE NULL END ASC, + CASE WHEN @SortOrder = N'modify_date' AND @SortDirection = N'asc' THEN CONVERT(DATETIME, i.modify_date) ELSE NULL END ASC, i.[database_name], [Schema Name], [Object Name], [Index ID] OPTION (RECOMPILE); END; @@ -23878,7 +24790,7 @@ BEGIN SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.02', @VersionDate = '20210322'; +SELECT @Version = '8.03', @VersionDate = '20210420'; IF(@VersionCheckMode = 1) @@ -25648,6 +26560,7 @@ ALTER PROCEDURE dbo.sp_BlitzWho @MinRequestedMemoryKB INT = 0 , @MinBlockingSeconds INT = 0 , @CheckDateOverride DATETIMEOFFSET = NULL, + @ShowActualParameters BIT = 0, @Version VARCHAR(30) = NULL OUTPUT, @VersionDate DATETIME = NULL OUTPUT, @VersionCheckMode BIT = 0, @@ -25657,7 +26570,7 @@ BEGIN SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.02', @VersionDate = '20210322'; + SELECT @Version = '8.03', @VersionDate = '20210420'; IF(@VersionCheckMode = 1) BEGIN @@ -25739,8 +26652,6 @@ DECLARE @ProductVersion NVARCHAR(128) AND r.plan_handle = session_stats.plan_handle AND r.statement_start_offset = session_stats.statement_start_offset AND r.statement_end_offset = session_stats.statement_end_offset' - ,@QueryStatsXMLselect NVARCHAR(MAX) = N' CAST(COALESCE(qs_live.query_plan, '''') AS XML) AS live_query_plan , ' - ,@QueryStatsXMLSQL NVARCHAR(MAX) = N'OUTER APPLY sys.dm_exec_query_statistics_xml(s.session_id) qs_live' ,@ObjectFullName NVARCHAR(2000) ,@OutputTableNameQueryStats_View NVARCHAR(256) ,@LineFeed NVARCHAR(MAX) /* Had to set as MAX up from 10 as it was truncating the view creation*/; @@ -25751,16 +26662,6 @@ 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) -IF EXISTS (SELECT * FROM sys.all_columns WHERE object_id = OBJECT_ID('sys.dm_exec_query_statistics_xml') AND name = 'query_plan') - BEGIN - SET @QueryStatsXMLselect = N' CAST(COALESCE(qs_live.query_plan, '''') AS XML) AS live_query_plan , '; - SET @QueryStatsXMLSQL = N'OUTER APPLY sys.dm_exec_query_statistics_xml(s.session_id) qs_live'; - END - ELSE - BEGIN - SET @QueryStatsXMLselect = N' NULL AS live_query_plan , '; - SET @QueryStatsXMLSQL = N' '; - END SELECT @OutputTableNameQueryStats_View = QUOTENAME(@OutputTableName + '_Deltas'), @@ -25801,6 +26702,8 @@ IF @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @Output [query_text] [nvarchar](max) NULL, [query_plan] [xml] NULL, [live_query_plan] [xml] NULL, + [cached_parameter_info] [nvarchar](max) NULL, + [live_parameter_info] [nvarchar](max) NULL, [query_cost] [float] NULL, [status] [nvarchar](30) NOT NULL, [wait_info] [nvarchar](max) NULL, @@ -25899,6 +26802,20 @@ IF @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @Output ALTER TABLE ' + @ObjectFullName + N' ADD JoinKey AS ServerName + CAST(CheckDate AS NVARCHAR(50));'; EXEC(@StringToExecute); + /* If the table doesn't have the new cached_parameter_info computed column, add it. See Github #2842. */ + SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableName; + SET @StringToExecute = N'IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns + WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''cached_parameter_info'') + ALTER TABLE ' + @ObjectFullName + N' ADD cached_parameter_info NVARCHAR(MAX) NULL;'; + EXEC(@StringToExecute); + + /* If the table doesn't have the new live_parameter_info computed column, add it. See Github #2842. */ + SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableName; + SET @StringToExecute = N'IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns + WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''live_parameter_info'') + ALTER TABLE ' + @ObjectFullName + N' ADD live_parameter_info NVARCHAR(MAX) NULL;'; + EXEC(@StringToExecute); + /* Delete history older than @OutputTableRetentionDays */ SET @OutputTableCleanupDate = CAST( (DATEADD(DAY, -1 * @OutputTableRetentionDays, GETDATE() ) ) AS DATE); SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' @@ -26194,7 +27111,27 @@ SELECT @BlockingCheck = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; sys2.spid AS session_id, sys2.blocked AS blocking_session_id, sys2.lastwaittype, sys2.waittime, sys2.cpu, sys2.physical_io, sys2.memusage FROM sys.sysprocesses AS sys1 JOIN sys.sysprocesses AS sys2 - ON sys1.spid = sys2.blocked;'; + ON sys1.spid = sys2.blocked; + + + DECLARE @LiveQueryPlans TABLE + ( + Session_Id INT NOT NULL, + Query_Plan XML NOT NULL + ); + + ' + +IF EXISTS (SELECT * FROM sys.all_columns WHERE object_id = OBJECT_ID('sys.dm_exec_query_statistics_xml') AND name = 'query_plan') +BEGIN + SET @BlockingCheck = @BlockingCheck + N' + INSERT INTO @LiveQueryPlans + SELECT s.session_id, query_plan + FROM sys.dm_exec_sessions AS s + CROSS APPLY sys.dm_exec_query_statistics_xml(s.session_id) + WHERE s.session_id <> @@SPID;'; +END + IF @ProductVersionMajor > 9 and @ProductVersionMajor < 11 BEGIN @@ -26416,9 +27353,19 @@ IF @ProductVersionMajor >= 11 ELSE query_stats.statement_end_offset END - query_stats.statement_start_offset ) / 2 ) + 1), dest.text) AS query_text , - derp.query_plan ,' - + @QueryStatsXMLselect - +' + derp.query_plan , + CAST(COALESCE(qs_live.query_plan, '''') AS XML) AS live_query_plan , + STUFF((SELECT DISTINCT N'', '' + Node.Data.value(''(@Column)[1]'', ''NVARCHAR(4000)'') + N'' {'' + Node.Data.value(''(@ParameterDataType)[1]'', ''NVARCHAR(4000)'') + N''}: '' + Node.Data.value(''(@ParameterCompiledValue)[1]'', ''NVARCHAR(4000)'') + FROM derp.query_plan.nodes(''/*:ShowPlanXML/*:BatchSequence/*:Batch/*:Statements/*:StmtSimple/*:QueryPlan/*:ParameterList/*:ColumnReference'') AS Node(Data) + FOR XML PATH('''')), 1,2,'''') + AS Cached_Parameter_Info, + ' + IF @ShowActualParameters = 1 + BEGIN + SELECT @StringToExecute = @StringToExecute + N'qs_live.Live_Parameter_Info as Live_Parameter_Info,' + END + + SELECT @StringToExecute = @StringToExecute + N' qmg.query_cost , s.status , CASE @@ -26639,10 +27586,18 @@ IF @ProductVersionMajor >= 11 AND tsu.session_id = r.session_id AND tsu.session_id = s.session_id ) as tempdb_allocations - ' - + @QueryStatsXMLSQL - + - N' + + OUTER APPLY ( + SELECT TOP 1 query_plan, + STUFF((SELECT DISTINCT N'', '' + Node.Data.value(''(@Column)[1]'', ''NVARCHAR(4000)'') + N'' {'' + Node.Data.value(''(@ParameterDataType)[1]'', ''NVARCHAR(4000)'') + N''}: '' + Node.Data.value(''(@ParameterCompiledValue)[1]'', ''NVARCHAR(4000)'') + N'' (Actual: '' + Node.Data.value(''(@ParameterRuntimeValue)[1]'', ''NVARCHAR(4000)'') + N'')'' + FROM q.query_plan.nodes(''/*:ShowPlanXML/*:BatchSequence/*:Batch/*:Statements/*:StmtSimple/*:QueryPlan/*:ParameterList/*:ColumnReference'') AS Node(Data) + FOR XML PATH('''')), 1,2,'''') + AS Live_Parameter_Info + FROM @LiveQueryPlans q + WHERE (s.session_id = q.session_id) + + ) AS qs_live + WHERE s.session_id <> @@SPID AND s.host_name IS NOT NULL AND r.database_id NOT IN (SELECT database_id FROM #WhoReadableDBs) @@ -26732,6 +27687,8 @@ IF @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @Output ,[query_text] ,[query_plan]' + CASE WHEN @ProductVersionMajor >= 11 THEN N',[live_query_plan]' ELSE N'' END + N' + ,[Cached_Parameter_Info]' + + CASE WHEN @ProductVersionMajor >= 11 AND @ShowActualParameters = 1 THEN N',[Live_Parameter_Info]' ELSE N'' END + N' ,[query_cost] ,[status] ,[wait_info]' @@ -26898,6 +27855,7 @@ DELETE FROM dbo.SqlServerVersions; INSERT INTO dbo.SqlServerVersions (MajorVersionNumber, MinorVersionNumber, Branch, [Url], ReleaseDate, MainstreamSupportEndDate, ExtendedSupportEndDate, MajorVersionName, MinorVersionName) VALUES + (15, 4123, 'CU10', 'https://support.microsoft.com/en-us/help/5001090', '2021-02-11', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 10'), (15, 4102, 'CU9', 'https://support.microsoft.com/en-us/help/5000642', '2021-02-11', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 9 '), (15, 4073, 'CU8 GDR', 'https://support.microsoft.com/en-us/help/4583459', '2021-01-12', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 8 GDR '), (15, 4073, 'CU8', 'https://support.microsoft.com/en-us/help/4577194', '2020-10-01', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 8 '), @@ -26910,7 +27868,7 @@ VALUES (15, 4003, 'CU1', 'https://support.microsoft.com/en-us/help/4527376', '2020-01-07', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 1 '), (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 '), - (14, 3356, 'RTM CU23', 'https://support.microsoft.com/en-us/help/5000685', '2021-02-25', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 23'), + (14, 3381, 'RTM CU23', 'https://support.microsoft.com/en-us/help/5000685', '2021-02-25', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 23'), (14, 3370, 'RTM CU22 GDR', 'https://support.microsoft.com/en-us/help/4583457', '2021-01-12', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 22 GDR'), (14, 3356, 'RTM CU22', 'https://support.microsoft.com/en-us/help/4577467', '2020-09-10', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 22'), (14, 3335, 'RTM CU21', 'https://support.microsoft.com/en-us/help/4557397', '2020-07-01', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 21'), @@ -27286,7 +28244,7 @@ BEGIN SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.02', @VersionDate = '20210322'; +SELECT @Version = '8.03', @VersionDate = '20210420'; IF(@VersionCheckMode = 1) BEGIN @@ -31697,8 +32655,8 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, [FindingsGroup] , [Finding] , [URL] , - CAST(@StockDetailsHeader + [Details] + @StockDetailsFooter AS NVARCHAR(MAX)) AS Details, - CAST([HowToStopIt] AS NVARCHAR(MAX)) AS HowToStopIt, + CAST(LEFT(@StockDetailsHeader + [Details] + @StockDetailsFooter,32000) AS TEXT) AS Details, + CAST(LEFT([HowToStopIt],32000) AS TEXT) AS HowToStopIt, CAST([QueryText] AS NVARCHAR(MAX)) AS QueryText, CAST([QueryPlan] AS NVARCHAR(MAX)) AS QueryPlan FROM #BlitzFirstResults diff --git a/Install-Core-Blitz-With-Query-Store.sql b/Install-Core-Blitz-With-Query-Store.sql index ee68e740b..a3c7a46b5 100755 --- a/Install-Core-Blitz-With-Query-Store.sql +++ b/Install-Core-Blitz-With-Query-Store.sql @@ -37,7 +37,7 @@ AS SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.02', @VersionDate = '20210322'; + SELECT @Version = '8.03', @VersionDate = '20210420'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -634,8 +634,8 @@ AS DROP TABLE #driveInfo; CREATE TABLE #driveInfo ( - drive NVARCHAR, - logical_volume_name NVARCHAR(32), --Limit is 32 for NTFS, 11 for FAT + drive NVARCHAR(2), + logical_volume_name NVARCHAR(36), --Limit is 32 for NTFS, 11 for FAT available_MB DECIMAL(18, 0), total_MB DECIMAL(18, 0), used_percent DECIMAL(18, 2) @@ -3632,8 +3632,8 @@ AS BEGIN SET @user_perm_sql += N' - SELECT @user_perm_gb = CASE WHEN (pages_kb / 128.0 / 1024.) >= 2. - THEN CONVERT(DECIMAL(38, 2), (pages_kb / 128.0 / 1024.)) + SELECT @user_perm_gb = CASE WHEN (pages_kb / 1024. / 1024.) >= 2. + THEN CONVERT(DECIMAL(38, 2), (pages_kb / 1024. / 1024.)) ELSE NULL END FROM sys.dm_os_memory_clerks @@ -4463,7 +4463,7 @@ IF @ProductVersionMajor >= 10 'Informational' AS [FindingsGroup] , 'SQL Server is running under an NT Service account' AS [Finding] , 'https://www.brentozar.com/go/setup' AS [URL] , - ( 'I''m running as ' + [service_account] + '. I wish I had an Active Directory service account instead.' + ( 'I''m running as ' + [service_account] + '.' ) AS [Details] FROM [sys].[dm_server_services] @@ -4503,7 +4503,7 @@ IF @ProductVersionMajor >= 10 'Informational' AS [FindingsGroup] , 'SQL Server Agent is running under an NT Service account' AS [Finding] , 'https://www.brentozar.com/go/setup' AS [URL] , - ( 'I''m running as ' + [service_account] + '. I wish I had an Active Directory service account instead.' + ( 'I''m running as ' + [service_account] + '.' ) AS [Details] FROM [sys].[dm_server_services] @@ -8404,10 +8404,10 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 inner join ( SELECT DISTINCT SUBSTRING(volume_mount_point, 1, 1) AS volume_mount_point - ,CASE WHEN ISNULL(logical_volume_name,'''') = '''' THEN '''' ELSE '' ('' + logical_volume_name + '')'' END AS logical_volume_name + ,CASE WHEN ISNULL(logical_volume_name,'''') = '''' THEN '''' ELSE ''('' + logical_volume_name + '')'' END AS logical_volume_name ,total_bytes/1024/1024 AS total_MB ,available_bytes/1024/1024 AS available_MB - ,(CONVERT(DECIMAL(4,2),(total_bytes/1.0 - available_bytes)/total_bytes * 100)) AS used_percent + ,(CONVERT(DECIMAL(5,2),(total_bytes/1.0 - available_bytes)/total_bytes * 100)) AS used_percent FROM (SELECT TOP 1 WITH TIES database_id @@ -8441,7 +8441,7 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 + '' drive'' ELSE CAST(CAST(i.available_MB/1024 AS NUMERIC(18,2)) AS VARCHAR(30)) + '' GB free on '' + i.drive - + '' drive'' + i.logical_volume_name + + '' drive '' + i.logical_volume_name + '' out of '' + CAST(CAST(i.total_MB/1024 AS NUMERIC(18,2)) AS VARCHAR(30)) + '' GB total ('' + CAST(i.used_percent AS VARCHAR(30)) + ''%)'' END AS Details @@ -9394,6 +9394,896 @@ EXEC [dbo].[sp_Blitz] @CheckProcedureCacheFilter = NULL, @CheckServerInfo = 1 */ +SET ANSI_NULLS ON; +SET QUOTED_IDENTIFIER ON + +IF NOT EXISTS (SELECT * FROM sys.objects WHERE [object_id] = OBJECT_ID(N'[dbo].[sp_BlitzAnalysis]') AND [type] in (N'P', N'PC')) +BEGIN +EXEC dbo.sp_executesql @statement = N'CREATE PROCEDURE [dbo].[sp_BlitzAnalysis] AS' +END +GO + +ALTER PROCEDURE [dbo].[sp_BlitzAnalysis] ( +@Help TINYINT = 0, +@StartDate DATETIMEOFFSET(7) = NULL, +@EndDate DATETIMEOFFSET(7) = NULL, +@OutputDatabaseName NVARCHAR(256) = 'DBAtools', +@OutputSchemaName NVARCHAR(256) = N'dbo', +@OutputTableNameBlitzFirst NVARCHAR(256) = N'BlitzFirst', +@OutputTableNameFileStats NVARCHAR(256) = N'BlitzFirst_FileStats', +@OutputTableNamePerfmonStats NVARCHAR(256) = N'BlitzFirst_PerfmonStats', +@OutputTableNameWaitStats NVARCHAR(256) = N'BlitzFirst_WaitStats', +@OutputTableNameBlitzCache NVARCHAR(256) = N'BlitzCache', +@OutputTableNameBlitzWho NVARCHAR(256) = N'BlitzWho', +@Servername NVARCHAR(128) = @@SERVERNAME, +@Databasename NVARCHAR(128) = NULL, +@BlitzCacheSortorder NVARCHAR(20) = N'cpu', +@MaxBlitzFirstPriority INT = 249, +@ReadLatencyThreshold INT = 100, +@WriteLatencyThreshold INT = 100, +@WaitStatsTop TINYINT = 10, +@Version VARCHAR(30) = NULL OUTPUT, +@VersionDate DATETIME = NULL OUTPUT, +@VersionCheckMode BIT = 0, +@BringThePain BIT = 0, +@Maxdop INT = 1, +@Debug BIT = 0 +) +AS +SET NOCOUNT ON; + +SELECT @Version = '8.03', @VersionDate = '20210420'; + +IF(@VersionCheckMode = 1) +BEGIN + RETURN; +END; + +IF (@Help = 1) +BEGIN + PRINT 'EXEC sp_BlitzAnalysis +@StartDate = NULL, /* Specify a datetime or NULL will get an hour ago */ +@EndDate = NULL, /* Specify a datetime or NULL will get an hour of data since @StartDate */ +@OutputDatabaseName = N''DBA'', /* Specify the database name where where we can find your logged blitz data */ +@OutputSchemaName = N''dbo'', /* Specify the schema */ +@OutputTableNameBlitzFirst = N''BlitzFirst'', /* Table name where you are storing sp_BlitzFirst output, Set to NULL to ignore */ +@OutputTableNameFileStats = N''BlitzFirst_FileStats'', /* Table name where you are storing sp_BlitzFirst filestats output, Set to NULL to ignore */ +@OutputTableNamePerfmonStats = N''BlitzFirst_PerfmonStats'', /* Table name where you are storing sp_BlitzFirst Perfmon output, Set to NULL to ignore */ +@OutputTableNameWaitStats = N''BlitzFirst_WaitStats'', /* Table name where you are storing sp_BlitzFirst Wait stats output, Set to NULL to ignore */ +@OutputTableNameBlitzCache = N''BlitzCache'', /* Table name where you are storing sp_BlitzCache output, Set to NULL to ignore */ +@OutputTableNameBlitzWho = N''BlitzWho'', /* Table name where you are storing sp_BlitzWho output, Set to NULL to ignore */ +@Databasename = NULL, /* Filters results for BlitzCache, FileStats (will also include tempdb), BlitzWho. Leave as NULL for all databases */ +@MaxBlitzFirstPriority = 249, /* Max priority to include in the results */ +@BlitzCacheSortorder = ''cpu'', /* Accepted values ''all'' ''cpu'' ''reads'' ''writes'' ''duration'' ''executions'' ''memory grant'' ''spills'' */ +@WaitStatsTop = 3, /* Controls the top for wait stats only */ +@Maxdop = 1, /* Control the degree of parallelism that the queries within this proc can use if they want to*/ +@Debug = 0; /* Show sp_BlitzAnalysis SQL commands in the messages tab as they execute */ + +/* +Additional parameters: +@ReadLatencyThreshold INT /* Default: 100 - Sets the threshold in ms to compare against io_stall_read_average_ms in your filestats table */ +@WriteLatencyThreshold INT /* Default: 100 - Sets the threshold in ms to compare against io_stall_write_average_ms in your filestats table */ +@BringThePain BIT /* Default: 0 - If you are getting more than 4 hours of data with blitzcachesortorder set to ''all'' you will need to set BringThePain to 1 */ +*/'; + RETURN; +END + +/* Declare all local variables required */ +DECLARE @FullOutputTableNameBlitzFirst NVARCHAR(1000); +DECLARE @FullOutputTableNameFileStats NVARCHAR(1000); +DECLARE @FullOutputTableNamePerfmonStats NVARCHAR(1000); +DECLARE @FullOutputTableNameWaitStats NVARCHAR(1000); +DECLARE @FullOutputTableNameBlitzCache NVARCHAR(1000); +DECLARE @FullOutputTableNameBlitzWho NVARCHAR(1000); +DECLARE @Sql NVARCHAR(MAX); +DECLARE @NewLine NVARCHAR(2) = CHAR(13); +DECLARE @IncludeMemoryGrants BIT; +DECLARE @IncludeSpills BIT; + +/* Validate the database name */ +IF (DB_ID(@OutputDatabaseName) IS NULL) +BEGIN + RAISERROR('Invalid database name provided for parameter @OutputDatabaseName: %s',11,0,@OutputDatabaseName); + RETURN; +END + +/* Set fully qualified table names */ +SET @FullOutputTableNameBlitzFirst = QUOTENAME(@OutputDatabaseName)+N'.'+QUOTENAME(@OutputSchemaName)+N'.'+QUOTENAME(@OutputTableNameBlitzFirst); +SET @FullOutputTableNameFileStats = QUOTENAME(@OutputDatabaseName)+N'.'+QUOTENAME(@OutputSchemaName)+N'.'+QUOTENAME(@OutputTableNameFileStats+N'_Deltas'); +SET @FullOutputTableNamePerfmonStats = QUOTENAME(@OutputDatabaseName)+N'.'+QUOTENAME(@OutputSchemaName)+N'.'+QUOTENAME(@OutputTableNamePerfmonStats+N'_Actuals'); +SET @FullOutputTableNameWaitStats = QUOTENAME(@OutputDatabaseName)+N'.'+QUOTENAME(@OutputSchemaName)+N'.'+QUOTENAME(@OutputTableNameWaitStats+N'_Deltas'); +SET @FullOutputTableNameBlitzCache = QUOTENAME(@OutputDatabaseName)+N'.'+QUOTENAME(@OutputSchemaName)+N'.'+QUOTENAME(@OutputTableNameBlitzCache); +SET @FullOutputTableNameBlitzWho = QUOTENAME(@OutputDatabaseName)+N'.'+QUOTENAME(@OutputSchemaName)+N'.'+QUOTENAME(@OutputTableNameBlitzWho+N'_Deltas'); + +IF OBJECT_ID('tempdb.dbo.#BlitzFirstCounts') IS NOT NULL +BEGIN + DROP TABLE #BlitzFirstCounts; +END + +CREATE TABLE #BlitzFirstCounts ( + [Priority] TINYINT NOT NULL, + [FindingsGroup] VARCHAR(50) NOT NULL, + [Finding] VARCHAR(200) NOT NULL, + [TotalOccurrences] INT NULL, + [FirstOccurrence] DATETIMEOFFSET(7) NULL, + [LastOccurrence] DATETIMEOFFSET(7) NULL +); + +/* Validate variables and set defaults as required */ +IF (@BlitzCacheSortorder IS NULL) +BEGIN + SET @BlitzCacheSortorder = N'cpu'; +END + +SET @BlitzCacheSortorder = LOWER(@BlitzCacheSortorder); + +IF (@OutputTableNameBlitzCache IS NOT NULL AND @BlitzCacheSortorder NOT IN (N'all',N'cpu',N'reads',N'writes',N'duration',N'executions',N'memory grant',N'spills')) +BEGIN + RAISERROR('Invalid sort option specified for @BlitzCacheSortorder, supported values are ''all'', ''cpu'', ''reads'', ''writes'', ''duration'', ''executions'', ''memory grant'', ''spills''',11,0) WITH NOWAIT; + RETURN; +END + +/* Set @Maxdop to 1 if NULL was passed in */ +IF (@Maxdop IS NULL) +BEGIN + SET @Maxdop = 1; +END + +/* iF @Maxdop is set higher than the core count just set it to 0 */ +IF (@Maxdop > (SELECT CAST(cpu_count AS INT) FROM sys.dm_os_sys_info)) +BEGIN + SET @Maxdop = 0; +END + +/* We need to check if your SQL version has memory grant and spills columns in sys.dm_exec_query_stats */ +SELECT @IncludeMemoryGrants = + CASE + WHEN (EXISTS(SELECT * FROM sys.all_columns WHERE [object_id] = OBJECT_ID('sys.dm_exec_query_stats') AND name = 'max_grant_kb')) THEN 1 + ELSE 0 + END; + +SELECT @IncludeSpills = + CASE + WHEN (EXISTS(SELECT * FROM sys.all_columns WHERE [object_id] = OBJECT_ID('sys.dm_exec_query_stats') AND name = 'max_spills')) THEN 1 + ELSE 0 + END; + + +IF (@StartDate IS NULL) +BEGIN + RAISERROR('Setting @StartDate to: 1 hour ago',0,0) WITH NOWAIT; + /* Set StartDate to be an hour ago */ + SET @StartDate = DATEADD(HOUR,-1,SYSDATETIMEOFFSET()); + + IF (@EndDate IS NULL) + BEGIN + RAISERROR('Setting @EndDate to: Now',0,0) WITH NOWAIT; + /* Get data right up to now */ + SET @EndDate = SYSDATETIMEOFFSET(); + END +END + +IF (@EndDate IS NULL) +BEGIN + /* Default to an hour of data or SYSDATETIMEOFFSET() if now is earlier than the hour added to @StartDate */ + IF(DATEADD(HOUR,1,@StartDate) < SYSDATETIMEOFFSET()) + BEGIN + RAISERROR('@EndDate was NULL - Setting to return 1 hour of information, if you want more then set @EndDate aswell',0,0) WITH NOWAIT; + SET @EndDate = DATEADD(HOUR,1,@StartDate); + END + ELSE + BEGIN + RAISERROR('@EndDate was NULL - Setting to SYSDATETIMEOFFSET()',0,0) WITH NOWAIT; + SET @EndDate = SYSDATETIMEOFFSET(); + END +END + +/* Default to dbo schema if NULL is passed in */ +IF (@OutputSchemaName IS NULL) +BEGIN + SET @OutputSchemaName = 'dbo'; +END + +/* Prompt the user for @BringThePain = 1 if they are searching a timeframe greater than 4 hours and they are using BlitzCacheSortorder = 'all' */ +IF(@BlitzCacheSortorder = 'all' AND DATEDIFF(HOUR,@StartDate,@EndDate) > 4 AND @BringThePain = 0) +BEGIN + RAISERROR('Wow! hold up now, are you sure you wanna do this? Are sure you want to query over 4 hours of data with @BlitzCacheSortorder set to ''all''? IF you do then set @BringThePain = 1 but I gotta warn you this might hurt a bit!',11,1) WITH NOWAIT; + RETURN; +END + +/* Output report window information */ +SELECT + @Servername AS [ServerToReportOn], + CAST(1 AS NVARCHAR(20)) + N' - '+ CAST(@MaxBlitzFirstPriority AS NVARCHAR(20)) AS [PrioritesToInclude], + @StartDate AS [StartDatetime], + @EndDate AS [EndDatetime];; + + +/* BlitzFirst data */ +SET @Sql = N' +INSERT INTO #BlitzFirstCounts ([Priority],[FindingsGroup],[Finding],[TotalOccurrences],[FirstOccurrence],[LastOccurrence]) +SELECT +[Priority], +[FindingsGroup], +[Finding], +COUNT(*) AS [TotalOccurrences], +MIN(CheckDate) AS [FirstOccurrence], +MAX(CheckDate) AS [LastOccurrence] +FROM '+@FullOutputTableNameBlitzFirst+N' +WHERE [ServerName] = @Servername +AND [Priority] BETWEEN 1 AND @MaxBlitzFirstPriority +AND CheckDate BETWEEN @StartDate AND @EndDate +AND [CheckID] > -1 +GROUP BY [Priority],[FindingsGroup],[Finding]; + +IF EXISTS(SELECT 1 FROM #BlitzFirstCounts) +BEGIN + SELECT + [Priority], + [FindingsGroup], + [Finding], + [TotalOccurrences], + [FirstOccurrence], + [LastOccurrence] + FROM #BlitzFirstCounts + ORDER BY [Priority] ASC,[TotalOccurrences] DESC; +END +ELSE +BEGIN + SELECT N''No findings with a priority between 1 and ''+CAST(@MaxBlitzFirstPriority AS NVARCHAR(10))+N'' found for this period''; +END +SELECT + [ServerName] +,[CheckDate] +,[CheckID] +,[Priority] +,[Finding] +,[URL] +,[Details] +,[HowToStopIt] +,[QueryPlan] +,[QueryText] +FROM '+@FullOutputTableNameBlitzFirst+N' Findings +WHERE [ServerName] = @Servername +AND [Priority] BETWEEN 1 AND @MaxBlitzFirstPriority +AND [CheckDate] BETWEEN @StartDate AND @EndDate +AND [CheckID] > -1 +ORDER BY CheckDate ASC,[Priority] ASC +OPTION (RECOMPILE, MAXDOP '+CAST(@Maxdop AS NVARCHAR(2))+N');'; + + +RAISERROR('Getting BlitzFirst info from %s',0,0,@FullOutputTableNameBlitzFirst) WITH NOWAIT; + +IF (@Debug = 1) +BEGIN + PRINT @Sql; +END + +IF (OBJECT_ID(@FullOutputTableNameBlitzFirst) IS NULL) +BEGIN + IF (@OutputTableNameBlitzFirst IS NULL) + BEGIN + RAISERROR('BlitzFirst data skipped',10,0); + SELECT N'Skipped logged BlitzFirst data as NULL was passed to parameter @OutputTableNameBlitzFirst'; + END + ELSE + BEGIN + RAISERROR('Table provided for BlitzFirst data: %s does not exist',10,0,@FullOutputTableNameBlitzFirst); + SELECT N'No BlitzFirst data available as the table cannot be found'; + END + +END +ELSE /* Table exists then run the query */ +BEGIN + EXEC sp_executesql @Sql, + N'@StartDate DATETIMEOFFSET(7), + @EndDate DATETIMEOFFSET(7), + @Servername NVARCHAR(128), + @MaxBlitzFirstPriority INT', + @StartDate=@StartDate, + @EndDate=@EndDate, + @Servername=@Servername, + @MaxBlitzFirstPriority = @MaxBlitzFirstPriority; +END + +/* Blitz WaitStats data */ +SET @Sql = N'SELECT +[ServerName], +[CheckDate], +[wait_type], +[WaitsRank], +[WaitCategory], +[Ignorable], +[ElapsedSeconds], +[wait_time_ms_delta], +[wait_time_minutes_delta], +[wait_time_minutes_per_minute], +[signal_wait_time_ms_delta], +[waiting_tasks_count_delta], +ISNULL((CAST([wait_time_ms_delta] AS DECIMAL(38,2))/NULLIF(CAST([waiting_tasks_count_delta] AS DECIMAL(38,2)),0)),0) AS [wait_time_ms_per_wait] +FROM +( + SELECT + [ServerName], + [CheckDate], + [wait_type], + [WaitCategory], + [Ignorable], + [ElapsedSeconds], + [wait_time_ms_delta], + [wait_time_minutes_delta], + [wait_time_minutes_per_minute], + [signal_wait_time_ms_delta], + [waiting_tasks_count_delta], + ROW_NUMBER() OVER(PARTITION BY [CheckDate] ORDER BY [CheckDate] ASC,[wait_time_ms_delta] DESC) AS [WaitsRank] + FROM '+@FullOutputTableNameWaitStats+N' AS [Waits] + WHERE [ServerName] = @Servername + AND [CheckDate] BETWEEN @StartDate AND @EndDate +) TopWaits +WHERE [WaitsRank] <= @WaitStatsTop +ORDER BY +[CheckDate] ASC, +[wait_time_ms_delta] DESC +OPTION(RECOMPILE, MAXDOP '+CAST(@Maxdop AS NVARCHAR(2))+N');' + +RAISERROR('Getting wait stats info from %s',0,0,@FullOutputTableNameWaitStats) WITH NOWAIT; + +IF (@Debug = 1) +BEGIN + PRINT @Sql; +END + +IF (OBJECT_ID(@FullOutputTableNameWaitStats) IS NULL) +BEGIN + IF (@OutputTableNameWaitStats IS NULL) + BEGIN + RAISERROR('Wait stats data skipped',10,0); + SELECT N'Skipped logged wait stats data as NULL was passed to parameter @OutputTableNameWaitStats'; + END + ELSE + BEGIN + RAISERROR('Table provided for wait stats data: %s does not exist',10,0,@FullOutputTableNameWaitStats); + SELECT N'No wait stats data available as the table cannot be found'; + END +END +ELSE /* Table exists then run the query */ +BEGIN + EXEC sp_executesql @Sql, + N'@StartDate DATETIMEOFFSET(7), + @EndDate DATETIMEOFFSET(7), + @Servername NVARCHAR(128), + @WaitStatsTop TINYINT', + @StartDate=@StartDate, + @EndDate=@EndDate, + @Servername=@Servername, + @WaitStatsTop=@WaitStatsTop; +END + +/* BlitzFileStats info */ +SET @Sql = N' +SELECT +[ServerName], +[CheckDate], +CASE + WHEN MAX([io_stall_read_ms_average]) > @ReadLatencyThreshold THEN ''Yes'' + WHEN MAX([io_stall_write_ms_average]) > @WriteLatencyThreshold THEN ''Yes'' + ELSE ''No'' +END AS [io_stall_ms_breached], +LEFT([PhysicalName],LEN([PhysicalName])-CHARINDEX(''\'',REVERSE([PhysicalName]))+1) AS [PhysicalPath], +SUM([SizeOnDiskMB]) AS [SizeOnDiskMB], +SUM([SizeOnDiskMBgrowth]) AS [SizeOnDiskMBgrowth], +MAX([io_stall_read_ms]) AS [max_io_stall_read_ms], +MAX([io_stall_read_ms_average]) AS [max_io_stall_read_ms_average], +@ReadLatencyThreshold AS [is_stall_read_ms_threshold], +SUM([num_of_reads]) AS [num_of_reads], +SUM([megabytes_read]) AS [megabytes_read], +MAX([io_stall_write_ms]) AS [max_io_stall_write_ms], +MAX([io_stall_write_ms_average]) AS [max_io_stall_write_ms_average], +@WriteLatencyThreshold AS [io_stall_write_ms_average], +SUM([num_of_writes]) AS [num_of_writes], +SUM([megabytes_written]) AS [megabytes_written] +FROM '+@FullOutputTableNameFileStats+N' +WHERE [ServerName] = @Servername +AND [CheckDate] BETWEEN @StartDate AND @EndDate +' ++CASE + WHEN @Databasename IS NOT NULL THEN N'AND [DatabaseName] IN (N''tempdb'',@Databasename) +' + ELSE N'' +END ++N'GROUP BY +[ServerName], +[CheckDate], +LEFT([PhysicalName],LEN([PhysicalName])-CHARINDEX(''\'',REVERSE([PhysicalName]))+1) +ORDER BY +[CheckDate] ASC +OPTION (RECOMPILE, MAXDOP '+CAST(@Maxdop AS NVARCHAR(2))+N');' + +RAISERROR('Getting FileStats info from %s',0,0,@FullOutputTableNameFileStats) WITH NOWAIT; + +IF (@Debug = 1) +BEGIN + PRINT @Sql; +END + +IF (OBJECT_ID(@FullOutputTableNameFileStats) IS NULL) +BEGIN + IF (@OutputTableNameFileStats IS NULL) + BEGIN + RAISERROR('File stats data skipped',10,0); + SELECT N'Skipped logged File stats data as NULL was passed to parameter @OutputTableNameFileStats'; + END + ELSE + BEGIN + RAISERROR('Table provided for FileStats data: %s does not exist',10,0,@FullOutputTableNameFileStats); + SELECT N'No File stats data available as the table cannot be found'; + END +END +ELSE /* Table exists then run the query */ +BEGIN + EXEC sp_executesql @Sql, + N'@StartDate DATETIMEOFFSET(7), + @EndDate DATETIMEOFFSET(7), + @Servername NVARCHAR(128), + @Databasename NVARCHAR(128), + @ReadLatencyThreshold INT, + @WriteLatencyThreshold INT', + @StartDate=@StartDate, + @EndDate=@EndDate, + @Servername=@Servername, + @Databasename = @Databasename, + @ReadLatencyThreshold = @ReadLatencyThreshold, + @WriteLatencyThreshold = @WriteLatencyThreshold; +END + +/* Blitz Perfmon stats*/ +SET @Sql = N' +SELECT + [ServerName] + ,[CheckDate] + ,[counter_name] + ,[object_name] + ,[instance_name] + ,[cntr_value] +FROM '+@FullOutputTableNamePerfmonStats+N' +WHERE [ServerName] = @Servername +AND CheckDate BETWEEN @StartDate AND @EndDate +ORDER BY + [CheckDate] ASC, + [counter_name] ASC +OPTION (RECOMPILE, MAXDOP '+CAST(@Maxdop AS NVARCHAR(2))+N');' + +RAISERROR('Getting Perfmon info from %s',0,0,@FullOutputTableNamePerfmonStats) WITH NOWAIT; + +IF (@Debug = 1) +BEGIN + PRINT @Sql; +END + +IF (OBJECT_ID(@FullOutputTableNamePerfmonStats) IS NULL) +BEGIN + IF (@OutputTableNamePerfmonStats IS NULL) + BEGIN + RAISERROR('Perfmon stats data skipped',10,0); + SELECT N'Skipped logged Perfmon stats data as NULL was passed to parameter @OutputTableNamePerfmonStats'; + END + ELSE + BEGIN + RAISERROR('Table provided for Perfmon stats data: %s does not exist',10,0,@FullOutputTableNamePerfmonStats); + SELECT N'No Perfmon data available as the table cannot be found'; + END +END +ELSE /* Table exists then run the query */ +BEGIN + EXEC sp_executesql @Sql, + N'@StartDate DATETIMEOFFSET(7), + @EndDate DATETIMEOFFSET(7), + @Servername NVARCHAR(128)', + @StartDate=@StartDate, + @EndDate=@EndDate, + @Servername=@Servername; +END + +/* Blitz cache data */ +RAISERROR('Sortorder for BlitzCache data: %s',0,0,@BlitzCacheSortorder) WITH NOWAIT; + +/* Set intial CTE */ +SET @Sql = N'WITH CheckDates AS ( +SELECT DISTINCT CheckDate +FROM ' ++@FullOutputTableNameBlitzCache ++N' +WHERE [ServerName] = @Servername +AND [CheckDate] BETWEEN @StartDate AND @EndDate' ++@NewLine ++CASE + WHEN @Databasename IS NOT NULL THEN N'AND [DatabaseName] = @Databasename'+@NewLine + ELSE N'' +END ++N')' +; + +SET @Sql += @NewLine; + +/* Append additional CTEs based on sortorder */ +SET @Sql += ( +SELECT CAST(N',' AS NVARCHAR(MAX)) ++[SortOptions].[Aliasname]+N' AS ( +SELECT + [ServerName] + ,'+[SortOptions].[Aliasname]+N'.[CheckDate] + ,[Sortorder] + ,[TimeFrameRank] + ,ROW_NUMBER() OVER(ORDER BY ['+[SortOptions].[Columnname]+N'] DESC) AS [OverallRank] + ,'+[SortOptions].[Aliasname]+N'.[QueryType] + ,'+[SortOptions].[Aliasname]+N'.[QueryText] + ,'+[SortOptions].[Aliasname]+N'.[DatabaseName] + ,'+[SortOptions].[Aliasname]+N'.[AverageCPU] + ,'+[SortOptions].[Aliasname]+N'.[TotalCPU] + ,'+[SortOptions].[Aliasname]+N'.[PercentCPUByType] + ,'+[SortOptions].[Aliasname]+N'.[AverageDuration] + ,'+[SortOptions].[Aliasname]+N'.[TotalDuration] + ,'+[SortOptions].[Aliasname]+N'.[PercentDurationByType] + ,'+[SortOptions].[Aliasname]+N'.[AverageReads] + ,'+[SortOptions].[Aliasname]+N'.[TotalReads] + ,'+[SortOptions].[Aliasname]+N'.[PercentReadsByType] + ,'+[SortOptions].[Aliasname]+N'.[AverageWrites] + ,'+[SortOptions].[Aliasname]+N'.[TotalWrites] + ,'+[SortOptions].[Aliasname]+N'.[PercentWritesByType] + ,'+[SortOptions].[Aliasname]+N'.[ExecutionCount] + ,'+[SortOptions].[Aliasname]+N'.[ExecutionWeight] + ,'+[SortOptions].[Aliasname]+N'.[PercentExecutionsByType] + ,'+[SortOptions].[Aliasname]+N'.[ExecutionsPerMinute] + ,'+[SortOptions].[Aliasname]+N'.[PlanCreationTime] + ,'+[SortOptions].[Aliasname]+N'.[PlanCreationTimeHours] + ,'+[SortOptions].[Aliasname]+N'.[LastExecutionTime] + ,'+[SortOptions].[Aliasname]+N'.[PlanHandle] + ,'+[SortOptions].[Aliasname]+N'.[SqlHandle] + ,'+[SortOptions].[Aliasname]+N'.[SQL Handle More Info] + ,'+[SortOptions].[Aliasname]+N'.[QueryHash] + ,'+[SortOptions].[Aliasname]+N'.[Query Hash More Info] + ,'+[SortOptions].[Aliasname]+N'.[QueryPlanHash] + ,'+[SortOptions].[Aliasname]+N'.[StatementStartOffset] + ,'+[SortOptions].[Aliasname]+N'.[StatementEndOffset] + ,'+[SortOptions].[Aliasname]+N'.[MinReturnedRows] + ,'+[SortOptions].[Aliasname]+N'.[MaxReturnedRows] + ,'+[SortOptions].[Aliasname]+N'.[AverageReturnedRows] + ,'+[SortOptions].[Aliasname]+N'.[TotalReturnedRows] + ,'+[SortOptions].[Aliasname]+N'.[QueryPlan] + ,'+[SortOptions].[Aliasname]+N'.[NumberOfPlans] + ,'+[SortOptions].[Aliasname]+N'.[NumberOfDistinctPlans] + ,'+[SortOptions].[Aliasname]+N'.[MinGrantKB] + ,'+[SortOptions].[Aliasname]+N'.[MaxGrantKB] + ,'+[SortOptions].[Aliasname]+N'.[MinUsedGrantKB] + ,'+[SortOptions].[Aliasname]+N'.[MaxUsedGrantKB] + ,'+[SortOptions].[Aliasname]+N'.[PercentMemoryGrantUsed] + ,'+[SortOptions].[Aliasname]+N'.[AvgMaxMemoryGrant] + ,'+[SortOptions].[Aliasname]+N'.[MinSpills] + ,'+[SortOptions].[Aliasname]+N'.[MaxSpills] + ,'+[SortOptions].[Aliasname]+N'.[TotalSpills] + ,'+[SortOptions].[Aliasname]+N'.[AvgSpills] + ,'+[SortOptions].[Aliasname]+N'.[QueryPlanCost] +FROM CheckDates +CROSS APPLY ( + SELECT TOP (5) + [ServerName] + ,'+[SortOptions].[Aliasname]+N'.[CheckDate] + ,'+QUOTENAME(UPPER([SortOptions].[Sortorder]),N'''')+N' AS [Sortorder] + ,ROW_NUMBER() OVER(ORDER BY ['+[SortOptions].[Columnname]+N'] DESC) AS [TimeFrameRank] + ,'+[SortOptions].[Aliasname]+N'.[QueryType] + ,'+[SortOptions].[Aliasname]+N'.[QueryText] + ,'+[SortOptions].[Aliasname]+N'.[DatabaseName] + ,'+[SortOptions].[Aliasname]+N'.[AverageCPU] + ,'+[SortOptions].[Aliasname]+N'.[TotalCPU] + ,'+[SortOptions].[Aliasname]+N'.[PercentCPUByType] + ,'+[SortOptions].[Aliasname]+N'.[AverageDuration] + ,'+[SortOptions].[Aliasname]+N'.[TotalDuration] + ,'+[SortOptions].[Aliasname]+N'.[PercentDurationByType] + ,'+[SortOptions].[Aliasname]+N'.[AverageReads] + ,'+[SortOptions].[Aliasname]+N'.[TotalReads] + ,'+[SortOptions].[Aliasname]+N'.[PercentReadsByType] + ,'+[SortOptions].[Aliasname]+N'.[AverageWrites] + ,'+[SortOptions].[Aliasname]+N'.[TotalWrites] + ,'+[SortOptions].[Aliasname]+N'.[PercentWritesByType] + ,'+[SortOptions].[Aliasname]+N'.[ExecutionCount] + ,'+[SortOptions].[Aliasname]+N'.[ExecutionWeight] + ,'+[SortOptions].[Aliasname]+N'.[PercentExecutionsByType] + ,'+[SortOptions].[Aliasname]+N'.[ExecutionsPerMinute] + ,'+[SortOptions].[Aliasname]+N'.[PlanCreationTime] + ,'+[SortOptions].[Aliasname]+N'.[PlanCreationTimeHours] + ,'+[SortOptions].[Aliasname]+N'.[LastExecutionTime] + ,'+[SortOptions].[Aliasname]+N'.[PlanHandle] + ,'+[SortOptions].[Aliasname]+N'.[SqlHandle] + ,'+[SortOptions].[Aliasname]+N'.[SQL Handle More Info] + ,'+[SortOptions].[Aliasname]+N'.[QueryHash] + ,'+[SortOptions].[Aliasname]+N'.[Query Hash More Info] + ,'+[SortOptions].[Aliasname]+N'.[QueryPlanHash] + ,'+[SortOptions].[Aliasname]+N'.[StatementStartOffset] + ,'+[SortOptions].[Aliasname]+N'.[StatementEndOffset] + ,'+[SortOptions].[Aliasname]+N'.[MinReturnedRows] + ,'+[SortOptions].[Aliasname]+N'.[MaxReturnedRows] + ,'+[SortOptions].[Aliasname]+N'.[AverageReturnedRows] + ,'+[SortOptions].[Aliasname]+N'.[TotalReturnedRows] + ,'+[SortOptions].[Aliasname]+N'.[QueryPlan] + ,'+[SortOptions].[Aliasname]+N'.[NumberOfPlans] + ,'+[SortOptions].[Aliasname]+N'.[NumberOfDistinctPlans] + ,'+[SortOptions].[Aliasname]+N'.[MinGrantKB] + ,'+[SortOptions].[Aliasname]+N'.[MaxGrantKB] + ,'+[SortOptions].[Aliasname]+N'.[MinUsedGrantKB] + ,'+[SortOptions].[Aliasname]+N'.[MaxUsedGrantKB] + ,'+[SortOptions].[Aliasname]+N'.[PercentMemoryGrantUsed] + ,'+[SortOptions].[Aliasname]+N'.[AvgMaxMemoryGrant] + ,'+[SortOptions].[Aliasname]+N'.[MinSpills] + ,'+[SortOptions].[Aliasname]+N'.[MaxSpills] + ,'+[SortOptions].[Aliasname]+N'.[TotalSpills] + ,'+[SortOptions].[Aliasname]+N'.[AvgSpills] + ,'+[SortOptions].[Aliasname]+N'.[QueryPlanCost] + FROM '+@FullOutputTableNameBlitzCache+N' AS '+[SortOptions].[Aliasname]+N' + WHERE [ServerName] = @Servername + AND [CheckDate] BETWEEN @StartDate AND @EndDate + AND ['+[SortOptions].[Aliasname]+N'].[CheckDate] = [CheckDates].[CheckDate]' + +@NewLine + +CASE + WHEN @Databasename IS NOT NULL THEN N'AND ['+[SortOptions].[Aliasname]+N'].[DatabaseName] = @Databasename'+@NewLine + ELSE N'' + END + +CASE + WHEN [Sortorder] = N'cpu' THEN N'AND [TotalCPU] > 0' + WHEN [Sortorder] = N'reads' THEN N'AND [TotalReads] > 0' + WHEN [Sortorder] = N'writes' THEN N'AND [TotalWrites] > 0' + WHEN [Sortorder] = N'duration' THEN N'AND [TotalDuration] > 0' + WHEN [Sortorder] = N'executions' THEN N'AND [ExecutionCount] > 0' + WHEN [Sortorder] = N'memory grant' THEN N'AND [MaxGrantKB] > 0' + WHEN [Sortorder] = N'spills' THEN N'AND [MaxSpills] > 0' + ELSE N'' + END + +N' + ORDER BY ['+[SortOptions].[Columnname]+N'] DESC) '+[SortOptions].[Aliasname]+N' +)' +FROM (VALUES + (N'cpu',N'TopCPU',N'TotalCPU'), + (N'reads',N'TopReads',N'TotalReads'), + (N'writes',N'TopWrites',N'TotalWrites'), + (N'duration',N'TopDuration',N'TotalDuration'), + (N'executions',N'TopExecutions',N'ExecutionCount'), + (N'memory grant',N'TopMemoryGrants',N'MaxGrantKB'), + (N'spills',N'TopSpills',N'MaxSpills') + ) SortOptions(Sortorder,Aliasname,Columnname) +WHERE + CASE /* for spills and memory grant sorts make sure the underlying columns exist in the DMV otherwise do not include them */ + WHEN (@IncludeMemoryGrants = 0 OR @IncludeMemoryGrants IS NULL) AND ([SortOptions].[Sortorder] = N'memory grant' OR [SortOptions].[Sortorder] = N'all') THEN NULL + WHEN (@IncludeSpills = 0 OR @IncludeSpills IS NULL) AND ([SortOptions].[Sortorder] = N'spills' OR [SortOptions].[Sortorder] = N'all') THEN NULL + ELSE [SortOptions].[Sortorder] + END = ISNULL(NULLIF(@BlitzCacheSortorder,N'all'),[SortOptions].[Sortorder]) +FOR XML PATH(N''), TYPE).value(N'.[1]', N'NVARCHAR(MAX)'); + +SET @Sql += @NewLine; + +/* Build the select statements to return the data after CTE declarations */ +SET @Sql += ( +SELECT STUFF(( +SELECT @NewLine ++N'UNION ALL' ++@NewLine ++N'SELECT * +FROM '+[SortOptions].[Aliasname] +FROM (VALUES + (N'cpu',N'TopCPU',N'TotalCPU'), + (N'reads',N'TopReads',N'TotalReads'), + (N'writes',N'TopWrites',N'TotalWrites'), + (N'duration',N'TopDuration',N'TotalDuration'), + (N'executions',N'TopExecutions',N'ExecutionCount'), + (N'memory grant',N'TopMemoryGrants',N'MaxGrantKB'), + (N'spills',N'TopSpills',N'MaxSpills') + ) SortOptions(Sortorder,Aliasname,Columnname) +WHERE + CASE /* for spills and memory grant sorts make sure the underlying columns exist in the DMV otherwise do not include them */ + WHEN (@IncludeMemoryGrants = 0 OR @IncludeMemoryGrants IS NULL) AND ([SortOptions].[Sortorder] = N'memory grant' OR [SortOptions].[Sortorder] = N'all') THEN NULL + WHEN (@IncludeSpills = 0 OR @IncludeSpills IS NULL) AND ([SortOptions].[Sortorder] = N'spills' OR [SortOptions].[Sortorder] = N'all') THEN NULL + ELSE [SortOptions].[Sortorder] + END = ISNULL(NULLIF(@BlitzCacheSortorder,N'all'),[SortOptions].[Sortorder]) +FOR XML PATH(N''), TYPE).value(N'.[1]', N'NVARCHAR(MAX)'),1,11,N'') +); + +/* Append Order By */ +SET @Sql += @NewLine ++N'ORDER BY + [Sortorder] ASC, + [CheckDate] ASC, + [TimeFrameRank] ASC'; + +/* Append OPTION(RECOMPILE, MAXDOP) to complete the statement */ +SET @Sql += @NewLine ++N'OPTION(RECOMPILE, MAXDOP '+CAST(@Maxdop AS NVARCHAR(2))+N');'; + +RAISERROR('Getting BlitzCache info from %s',0,0,@FullOutputTableNameBlitzCache) WITH NOWAIT; + +IF (@Debug = 1) +BEGIN + PRINT SUBSTRING(@Sql, 0, 4000); + PRINT SUBSTRING(@Sql, 4000, 8000); + PRINT SUBSTRING(@Sql, 8000, 12000); + PRINT SUBSTRING(@Sql, 12000, 16000); + PRINT SUBSTRING(@Sql, 16000, 20000); + PRINT SUBSTRING(@Sql, 20000, 24000); + PRINT SUBSTRING(@Sql, 24000, 28000); +END + +IF (OBJECT_ID(@FullOutputTableNameBlitzCache) IS NULL) +BEGIN + IF (@OutputTableNameBlitzCache IS NULL) + BEGIN + RAISERROR('BlitzCache data skipped',10,0); + SELECT N'Skipped logged BlitzCache data as NULL was passed to parameter @OutputTableNameBlitzCache'; + END + ELSE + BEGIN + RAISERROR('Table provided for BlitzCache data: %s does not exist',10,0,@FullOutputTableNameBlitzCache); + SELECT N'No BlitzCache data available as the table cannot be found'; + END +END +ELSE /* Table exists then run the query */ +BEGIN + EXEC sp_executesql @Sql, + N'@Servername NVARCHAR(128), + @Databasename NVARCHAR(128), + @BlitzCacheSortorder NVARCHAR(20), + @StartDate DATETIMEOFFSET(7), + @EndDate DATETIMEOFFSET(7)', + @Servername = @Servername, + @Databasename = @Databasename, + @BlitzCacheSortorder = @BlitzCacheSortorder, + @StartDate = @StartDate, + @EndDate = @EndDate; +END + + + +/* BlitzWho data */ +SET @Sql = N' +SELECT [ServerName] + ,[CheckDate] + ,[elapsed_time] + ,[session_id] + ,[database_name] + ,[query_text_snippet] + ,[query_plan] + ,[live_query_plan] + ,[query_cost] + ,[status] + ,[wait_info] + ,[top_session_waits] + ,[blocking_session_id] + ,[open_transaction_count] + ,[is_implicit_transaction] + ,[nt_domain] + ,[host_name] + ,[login_name] + ,[nt_user_name] + ,[program_name] + ,[fix_parameter_sniffing] + ,[client_interface_name] + ,[login_time] + ,[start_time] + ,[request_time] + ,[request_cpu_time] + ,[degree_of_parallelism] + ,[request_logical_reads] + ,[Logical_Reads_MB] + ,[request_writes] + ,[Logical_Writes_MB] + ,[request_physical_reads] + ,[Physical_reads_MB] + ,[session_cpu] + ,[session_logical_reads] + ,[session_logical_reads_MB] + ,[session_physical_reads] + ,[session_physical_reads_MB] + ,[session_writes] + ,[session_writes_MB] + ,[tempdb_allocations_mb] + ,[memory_usage] + ,[estimated_completion_time] + ,[percent_complete] + ,[deadlock_priority] + ,[transaction_isolation_level] + ,[last_dop] + ,[min_dop] + ,[max_dop] + ,[last_grant_kb] + ,[min_grant_kb] + ,[max_grant_kb] + ,[last_used_grant_kb] + ,[min_used_grant_kb] + ,[max_used_grant_kb] + ,[last_ideal_grant_kb] + ,[min_ideal_grant_kb] + ,[max_ideal_grant_kb] + ,[last_reserved_threads] + ,[min_reserved_threads] + ,[max_reserved_threads] + ,[last_used_threads] + ,[min_used_threads] + ,[max_used_threads] + ,[grant_time] + ,[requested_memory_kb] + ,[grant_memory_kb] + ,[is_request_granted] + ,[required_memory_kb] + ,[query_memory_grant_used_memory_kb] + ,[ideal_memory_kb] + ,[is_small] + ,[timeout_sec] + ,[resource_semaphore_id] + ,[wait_order] + ,[wait_time_ms] + ,[next_candidate_for_memory_grant] + ,[target_memory_kb] + ,[max_target_memory_kb] + ,[total_memory_kb] + ,[available_memory_kb] + ,[granted_memory_kb] + ,[query_resource_semaphore_used_memory_kb] + ,[grantee_count] + ,[waiter_count] + ,[timeout_error_count] + ,[forced_grant_count] + ,[workload_group_name] + ,[resource_pool_name] + ,[context_info] + ,[query_hash] + ,[query_plan_hash] + ,[sql_handle] + ,[plan_handle] + ,[statement_start_offset] + ,[statement_end_offset] + FROM '+@FullOutputTableNameBlitzWho+N' + WHERE [ServerName] = @Servername + AND ([CheckDate] BETWEEN @StartDate AND @EndDate OR [start_time] BETWEEN CAST(@StartDate AS DATETIME) AND CAST(@EndDate AS DATETIME)) + ' + +CASE + WHEN @Databasename IS NOT NULL THEN N'AND [database_name] = @Databasename + ' + ELSE N'' + END ++N'ORDER BY [CheckDate] ASC + OPTION (RECOMPILE, MAXDOP '+CAST(@Maxdop AS NVARCHAR(2))+N');'; + +RAISERROR('Getting BlitzWho info from %s',0,0,@FullOutputTableNameBlitzWho) WITH NOWAIT; + +IF (@Debug = 1) +BEGIN + PRINT @Sql; +END + +IF (OBJECT_ID(@FullOutputTableNameBlitzWho) IS NULL) +BEGIN + IF (@OutputTableNameBlitzWho IS NULL) + BEGIN + RAISERROR('BlitzWho data skipped',10,0); + SELECT N'Skipped logged BlitzWho data as NULL was passed to parameter @OutputTableNameBlitzWho'; + END + ELSE + BEGIN + RAISERROR('Table provided for BlitzWho data: %s does not exist',10,0,@FullOutputTableNameBlitzWho); + SELECT N'No BlitzWho data available as the table cannot be found'; + END +END +ELSE +BEGIN + EXEC sp_executesql @Sql, + N'@StartDate DATETIMEOFFSET(7), + @EndDate DATETIMEOFFSET(7), + @Servername NVARCHAR(128), + @Databasename NVARCHAR(128)', + @StartDate=@StartDate, + @EndDate=@EndDate, + @Servername=@Servername, + @Databasename = @Databasename; +END + + +GO IF OBJECT_ID('dbo.sp_BlitzBackups') IS NULL EXEC ('CREATE PROCEDURE dbo.sp_BlitzBackups AS RETURN 0;'); GO @@ -9419,7 +10309,7 @@ AS SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.02', @VersionDate = '20210322'; + SELECT @Version = '8.03', @VersionDate = '20210420'; IF(@VersionCheckMode = 1) BEGIN @@ -11199,7 +12089,7 @@ BEGIN SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.02', @VersionDate = '20210322'; +SELECT @Version = '8.03', @VersionDate = '20210420'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -14902,7 +15792,7 @@ SET s.variable_datatype = CASE WHEN s.variable_datatype LIKE '%(%)%' s.compile_time_value = CASE WHEN s.compile_time_value LIKE '%(%)%' THEN SUBSTRING(s.compile_time_value, CHARINDEX('(', s.compile_time_value) + 1, - CHARINDEX(')', s.compile_time_value) - 1 - CHARINDEX('(', s.compile_time_value) + CHARINDEX(')', s.compile_time_value, CHARINDEX('(', s.compile_time_value) + 1) - 1 - CHARINDEX('(', s.compile_time_value) ) WHEN variable_datatype NOT IN ('bit', 'tinyint', 'smallint', 'int', 'bigint') AND s.variable_datatype NOT LIKE '%binary%' @@ -17862,20 +18752,23 @@ ELSE IF @ValidOutputLocation = 1 BEGIN - SET @StringToExecute = 'USE ' + SET @StringToExecute = N'USE ' + @OutputDatabaseName - + '; IF EXISTS(SELECT * FROM ' + + N'; IF EXISTS(SELECT * FROM ' + @OutputDatabaseName - + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' + + N'.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' + @OutputSchemaName - + ''') AND NOT EXISTS (SELECT * FROM ' + + N''') AND NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName - + '.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''' - + @OutputSchemaName + ''' AND QUOTENAME(TABLE_NAME) = ''' - + @OutputTableName + ''') CREATE TABLE ' - + @OutputSchemaName + '.' + + N'.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''' + + @OutputSchemaName + N''' AND QUOTENAME(TABLE_NAME) = ''' + + @OutputTableName + N''') CREATE TABLE ' + + @OutputSchemaName + N'.' + @OutputTableName - + N'(ID bigint NOT NULL IDENTITY(1,1), + + CONVERT + ( + nvarchar(MAX), + N'(ID bigint NOT NULL IDENTITY(1,1), ServerName NVARCHAR(258), CheckDate DATETIMEOFFSET, Version NVARCHAR(258), @@ -17951,27 +18844,28 @@ ELSE AvgSpills MONEY, QueryPlanCost FLOAT, JoinKey AS ServerName + Cast(CheckDate AS NVARCHAR(50)), - CONSTRAINT [PK_' + REPLACE(REPLACE(@OutputTableName,'[',''),']','') + '] PRIMARY KEY CLUSTERED(ID ASC));'; + CONSTRAINT [PK_' + REPLACE(REPLACE(@OutputTableName,N'[',N''),N']',N'') + N'] PRIMARY KEY CLUSTERED(ID ASC));' + ); SET @StringToExecute += N'IF EXISTS(SELECT * FROM ' +@OutputDatabaseName +N'.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' +@OutputSchemaName - +''') AND EXISTS (SELECT * FROM ' + +N''') AND EXISTS (SELECT * FROM ' +@OutputDatabaseName+ N'.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''' +@OutputSchemaName - +''' AND QUOTENAME(TABLE_NAME) = ''' + +N''' AND QUOTENAME(TABLE_NAME) = ''' +@OutputTableName - +''') AND EXISTS (SELECT * FROM ' + +N''') AND EXISTS (SELECT * FROM ' +@OutputDatabaseName+ N'.sys.computed_columns WHERE [name] = N''PlanCreationTimeHours'' AND QUOTENAME(OBJECT_NAME(object_id)) = N''' +@OutputTableName - +''' AND [definition] = N''(datediff(hour,[PlanCreationTime],sysdatetime()))'') + +N''' AND [definition] = N''(datediff(hour,[PlanCreationTime],sysdatetime()))'') BEGIN RAISERROR(''We noticed that you are running an old computed column definition for PlanCreationTimeHours, fixing that now'',0,0) WITH NOWAIT; - ALTER TABLE '+@OutputDatabaseName+'.'+@OutputSchemaName+'.'+@OutputTableName+' DROP COLUMN [PlanCreationTimeHours]; - ALTER TABLE '+@OutputDatabaseName+'.'+@OutputSchemaName+'.'+@OutputTableName+' ADD [PlanCreationTimeHours] AS DATEDIFF(HOUR,CONVERT(DATETIMEOFFSET(7),[PlanCreationTime]),[CheckDate]); + ALTER TABLE '+@OutputDatabaseName+N'.'+@OutputSchemaName+N'.'+@OutputTableName+N' DROP COLUMN [PlanCreationTimeHours]; + ALTER TABLE '+@OutputDatabaseName+N'.'+@OutputSchemaName+N'.'+@OutputTableName+N' ADD [PlanCreationTimeHours] AS DATEDIFF(HOUR,CONVERT(DATETIMEOFFSET(7),[PlanCreationTime]),[CheckDate]); END '; IF @ValidOutputServer = 1 @@ -18381,6 +19275,7 @@ ALTER PROCEDURE dbo.sp_BlitzIndex @SkipPartitions BIT = 0, @SkipStatistics BIT = 1, @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, @IgnoreDatabases NVARCHAR(MAX) = NULL, /* Comma-delimited list of databases you want to skip */ @ThresholdMB INT = 250 /* Number of megabytes that an object must be before we include it in basic results */, @@ -18403,7 +19298,7 @@ AS SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.02', @VersionDate = '20210322'; +SELECT @Version = '8.03', @VersionDate = '20210420'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -20000,24 +20895,40 @@ BEGIN TRY AND ColumnNamesWithDataTypes.IndexColumnType = ''Included'' ) AS included_columns_with_data_type ' - /* Github #2780 BGO Removing SQL Server 2019's new missing index sample plan because it doesn't work yet: - IF NOT EXISTS (SELECT * FROM sys.all_objects WHERE name = 'dm_db_missing_index_group_stats_query') - */ - SET @dsql = @dsql + N' , NULL AS sample_query_plan ' - /* Github #2780 BGO Removing SQL Server 2019's new missing index sample plan because it doesn't work yet: + /* Github #2780 BGO Removing SQL Server 2019's new missing index sample plan because it doesn't work yet:*/ + IF NOT EXISTS + ( + SELECT + 1/0 + FROM sys.all_objects AS o + WHERE o.name = 'dm_db_missing_index_group_stats_query' + ) + SELECT + @dsql += N' , NULL AS sample_query_plan ' + /* Github #2780 BGO Removing SQL Server 2019's new missing index sample plan because it doesn't work yet:*/ ELSE BEGIN - SET @dsql = @dsql + N' , sample_query_plan = (SELECT TOP 1 p.query_plan - FROM sys.dm_db_missing_index_group_stats gs - CROSS APPLY (SELECT TOP 1 s.plan_handle - FROM sys.dm_db_missing_index_group_stats_query q - INNER JOIN sys.dm_exec_query_stats s ON q.query_plan_hash = s.query_plan_hash - WHERE gs.group_handle = q.group_handle - ORDER BY (q.user_seeks + q.user_scans) DESC, s.total_logical_reads DESC) q2 - CROSS APPLY sys.dm_exec_query_plan(q2.plan_handle) p - WHERE gs.group_handle = gs.group_handle) ' + SELECT + @dsql += N' + , sample_query_plan = + ( + SELECT TOP (1) + p.query_plan + FROM sys.dm_db_missing_index_group_stats gs + CROSS APPLY + ( + SELECT TOP (1) + s.plan_handle + FROM sys.dm_db_missing_index_group_stats_query q + INNER JOIN sys.dm_exec_query_stats s + ON q.query_plan_hash = s.query_plan_hash + WHERE gs.group_handle = q.group_handle + ORDER BY (q.user_seeks + q.user_scans) DESC, s.total_logical_reads DESC + ) q2 + CROSS APPLY sys.dm_exec_query_plan(q2.plan_handle) p + ) ' END - */ + SET @dsql = @dsql + N'FROM ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_groups ig @@ -20868,7 +21779,8 @@ BEGIN --We do a left join here in case this is a disabled NC. --In that case, it won't have any size info/pages allocated. - + IF (@ShowColumnstoreOnly = 0) + BEGIN WITH table_mode_cte AS ( SELECT s.db_schema_object_indexid, @@ -21059,7 +21971,8 @@ BEGIN WHERE s.object_id = @ObjectID ORDER BY s.auto_created, s.user_created, s.name, hist.step_number;'; EXEC sp_executesql @dsql, N'@ObjectID INT', @ObjectID; - END + END + END /* Visualize columnstore index contents. More info: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2584 */ IF 2 = (SELECT SUM(1) FROM sys.all_objects WHERE name IN ('column_store_row_groups','column_store_segments')) @@ -23531,7 +24444,7 @@ ELSE IF (@Mode=1) /*Summarize*/ ISNULL(i.index_name, '''') AS [Index Name], CASE WHEN i.is_primary_key = 1 AND i.index_definition <> ''[HEAP]'' - THEN N''-ALTER TABLE '' + QUOTENAME(i.[database_name]) + N''.'' + QUOTENAME(i.[schema_name]) + N''.'' + QUOTENAME(i.[object_name]) + + THEN N''--ALTER TABLE '' + QUOTENAME(i.[database_name]) + N''.'' + QUOTENAME(i.[schema_name]) + N''.'' + QUOTENAME(i.[object_name]) + N'' DROP CONSTRAINT '' + QUOTENAME(i.index_name) + N'';'' WHEN i.is_primary_key = 0 AND i.index_definition <> ''[HEAP]'' THEN N''--DROP INDEX ''+ QUOTENAME(i.index_name) + N'' ON '' + QUOTENAME(i.[database_name]) + N''.'' + @@ -23714,46 +24627,45 @@ ELSE IF (@Mode=1) /*Summarize*/ FROM #IndexSanity AS i --left join here so we don't lose disabled nc indexes LEFT JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id LEFT JOIN #IndexCreateTsql AS ict ON i.index_sanity_id = ict.index_sanity_id - ORDER BY CASE WHEN @SortDirection = 'desc' THEN - CASE WHEN @SortOrder = N'rows' THEN sz.total_rows - WHEN @SortOrder = N'reserved_mb' THEN sz.total_reserved_MB - WHEN @SortOrder = N'size' THEN sz.total_reserved_MB - WHEN @SortOrder = N'reserved_lob_mb' THEN sz.total_reserved_LOB_MB - WHEN @SortOrder = N'lob' THEN sz.total_reserved_LOB_MB - WHEN @SortOrder = N'total_row_lock_wait_in_ms' THEN COALESCE(sz.total_row_lock_wait_in_ms,0) - WHEN @SortOrder = N'total_page_lock_wait_in_ms' THEN COALESCE(sz.total_page_lock_wait_in_ms,0) - WHEN @SortOrder = N'lock_time' THEN (COALESCE(sz.total_row_lock_wait_in_ms,0) + COALESCE(sz.total_page_lock_wait_in_ms,0)) - WHEN @SortOrder = N'total_reads' THEN total_reads - WHEN @SortOrder = N'reads' THEN total_reads - WHEN @SortOrder = N'user_updates' THEN user_updates - WHEN @SortOrder = N'writes' THEN user_updates - WHEN @SortOrder = N'reads_per_write' THEN reads_per_write - WHEN @SortOrder = N'ratio' THEN reads_per_write - WHEN @SortOrder = N'forward_fetches' THEN sz.total_forwarded_fetch_count - WHEN @SortOrder = N'fetches' THEN sz.total_forwarded_fetch_count - ELSE NULL END - ELSE 1 END - DESC, /* Shout out to DHutmacher */ - CASE WHEN @SortDirection = 'asc' THEN - CASE WHEN @SortOrder = N'rows' THEN sz.total_rows - WHEN @SortOrder = N'reserved_mb' THEN sz.total_reserved_MB - WHEN @SortOrder = N'size' THEN sz.total_reserved_MB - WHEN @SortOrder = N'reserved_lob_mb' THEN sz.total_reserved_LOB_MB - WHEN @SortOrder = N'lob' THEN sz.total_reserved_LOB_MB - WHEN @SortOrder = N'total_row_lock_wait_in_ms' THEN COALESCE(sz.total_row_lock_wait_in_ms,0) - WHEN @SortOrder = N'total_page_lock_wait_in_ms' THEN COALESCE(sz.total_page_lock_wait_in_ms,0) - WHEN @SortOrder = N'lock_time' THEN (COALESCE(sz.total_row_lock_wait_in_ms,0) + COALESCE(sz.total_page_lock_wait_in_ms,0)) - WHEN @SortOrder = N'total_reads' THEN total_reads - WHEN @SortOrder = N'reads' THEN total_reads - WHEN @SortOrder = N'user_updates' THEN user_updates - WHEN @SortOrder = N'writes' THEN user_updates - WHEN @SortOrder = N'reads_per_write' THEN reads_per_write - WHEN @SortOrder = N'ratio' THEN reads_per_write - WHEN @SortOrder = N'forward_fetches' THEN sz.total_forwarded_fetch_count - WHEN @SortOrder = N'fetches' THEN sz.total_forwarded_fetch_count - ELSE NULL END - ELSE 1 END - ASC, + ORDER BY /* Shout out to DHutmacher */ + /*DESC*/ + CASE WHEN @SortOrder = N'rows' AND @SortDirection = N'desc' THEN sz.total_rows ELSE NULL END DESC, + CASE WHEN @SortOrder = N'reserved_mb' AND @SortDirection = N'desc' THEN sz.total_reserved_MB ELSE NULL END DESC, + CASE WHEN @SortOrder = N'size' AND @SortDirection = N'desc' THEN sz.total_reserved_MB ELSE NULL END DESC, + CASE WHEN @SortOrder = N'reserved_lob_mb' AND @SortDirection = N'desc' THEN sz.total_reserved_LOB_MB ELSE NULL END DESC, + CASE WHEN @SortOrder = N'lob' AND @SortDirection = N'desc' THEN sz.total_reserved_LOB_MB ELSE NULL END DESC, + CASE WHEN @SortOrder = N'total_row_lock_wait_in_ms' AND @SortDirection = N'desc' THEN COALESCE(sz.total_row_lock_wait_in_ms,0) ELSE NULL END DESC, + CASE WHEN @SortOrder = N'total_page_lock_wait_in_ms' AND @SortDirection = N'desc' THEN COALESCE(sz.total_page_lock_wait_in_ms,0) ELSE NULL END DESC, + CASE WHEN @SortOrder = N'lock_time' AND @SortDirection = N'desc' THEN (COALESCE(sz.total_row_lock_wait_in_ms,0) + COALESCE(sz.total_page_lock_wait_in_ms,0)) ELSE NULL END DESC, + CASE WHEN @SortOrder = N'total_reads' AND @SortDirection = N'desc' THEN total_reads ELSE NULL END DESC, + CASE WHEN @SortOrder = N'reads' AND @SortDirection = N'desc' THEN total_reads ELSE NULL END DESC, + CASE WHEN @SortOrder = N'user_updates' AND @SortDirection = N'desc' THEN user_updates ELSE NULL END DESC, + CASE WHEN @SortOrder = N'writes' AND @SortDirection = N'desc' THEN user_updates ELSE NULL END DESC, + CASE WHEN @SortOrder = N'reads_per_write' AND @SortDirection = N'desc' THEN reads_per_write ELSE NULL END DESC, + CASE WHEN @SortOrder = N'ratio' AND @SortDirection = N'desc' THEN reads_per_write ELSE NULL END DESC, + CASE WHEN @SortOrder = N'forward_fetches' AND @SortDirection = N'desc' THEN sz.total_forwarded_fetch_count ELSE NULL END DESC, + CASE WHEN @SortOrder = N'fetches' AND @SortDirection = N'desc' THEN sz.total_forwarded_fetch_count ELSE NULL END DESC, + CASE WHEN @SortOrder = N'create_date' AND @SortDirection = N'desc' THEN CONVERT(DATETIME, i.create_date) ELSE NULL END DESC, + CASE WHEN @SortOrder = N'modify_date' AND @SortDirection = N'desc' THEN CONVERT(DATETIME, i.modify_date) ELSE NULL END DESC, + /*ASC*/ + CASE WHEN @SortOrder = N'rows' AND @SortDirection = N'asc' THEN sz.total_rows ELSE NULL END ASC, + CASE WHEN @SortOrder = N'reserved_mb' AND @SortDirection = N'asc' THEN sz.total_reserved_MB ELSE NULL END ASC, + CASE WHEN @SortOrder = N'size' AND @SortDirection = N'asc' THEN sz.total_reserved_MB ELSE NULL END ASC, + CASE WHEN @SortOrder = N'reserved_lob_mb' AND @SortDirection = N'asc' THEN sz.total_reserved_LOB_MB ELSE NULL END ASC, + CASE WHEN @SortOrder = N'lob' AND @SortDirection = N'asc' THEN sz.total_reserved_LOB_MB ELSE NULL END ASC, + CASE WHEN @SortOrder = N'total_row_lock_wait_in_ms' AND @SortDirection = N'asc' THEN COALESCE(sz.total_row_lock_wait_in_ms,0) ELSE NULL END ASC, + CASE WHEN @SortOrder = N'total_page_lock_wait_in_ms' AND @SortDirection = N'asc' THEN COALESCE(sz.total_page_lock_wait_in_ms,0) ELSE NULL END ASC, + CASE WHEN @SortOrder = N'lock_time' AND @SortDirection = N'asc' THEN (COALESCE(sz.total_row_lock_wait_in_ms,0) + COALESCE(sz.total_page_lock_wait_in_ms,0)) ELSE NULL END ASC, + CASE WHEN @SortOrder = N'total_reads' AND @SortDirection = N'asc' THEN total_reads ELSE NULL END ASC, + CASE WHEN @SortOrder = N'reads' AND @SortDirection = N'asc' THEN total_reads ELSE NULL END ASC, + CASE WHEN @SortOrder = N'user_updates' AND @SortDirection = N'asc' THEN user_updates ELSE NULL END ASC, + CASE WHEN @SortOrder = N'writes' AND @SortDirection = N'asc' THEN user_updates ELSE NULL END ASC, + CASE WHEN @SortOrder = N'reads_per_write' AND @SortDirection = N'asc' THEN reads_per_write ELSE NULL END ASC, + CASE WHEN @SortOrder = N'ratio' AND @SortDirection = N'asc' THEN reads_per_write ELSE NULL END ASC, + CASE WHEN @SortOrder = N'forward_fetches' AND @SortDirection = N'asc' THEN sz.total_forwarded_fetch_count ELSE NULL END ASC, + CASE WHEN @SortOrder = N'fetches' AND @SortDirection = N'asc' THEN sz.total_forwarded_fetch_count ELSE NULL END ASC, + CASE WHEN @SortOrder = N'create_date' AND @SortDirection = N'asc' THEN CONVERT(DATETIME, i.create_date) ELSE NULL END ASC, + CASE WHEN @SortOrder = N'modify_date' AND @SortDirection = N'asc' THEN CONVERT(DATETIME, i.modify_date) ELSE NULL END ASC, i.[database_name], [Schema Name], [Object Name], [Index ID] OPTION (RECOMPILE); END; @@ -23878,7 +24790,7 @@ BEGIN SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.02', @VersionDate = '20210322'; +SELECT @Version = '8.03', @VersionDate = '20210420'; IF(@VersionCheckMode = 1) @@ -25684,7 +26596,7 @@ BEGIN /*First BEGIN*/ SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.02', @VersionDate = '20210322'; +SELECT @Version = '8.03', @VersionDate = '20210420'; IF(@VersionCheckMode = 1) BEGIN RETURN; @@ -31402,6 +32314,7 @@ ALTER PROCEDURE dbo.sp_BlitzWho @MinRequestedMemoryKB INT = 0 , @MinBlockingSeconds INT = 0 , @CheckDateOverride DATETIMEOFFSET = NULL, + @ShowActualParameters BIT = 0, @Version VARCHAR(30) = NULL OUTPUT, @VersionDate DATETIME = NULL OUTPUT, @VersionCheckMode BIT = 0, @@ -31411,7 +32324,7 @@ BEGIN SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.02', @VersionDate = '20210322'; + SELECT @Version = '8.03', @VersionDate = '20210420'; IF(@VersionCheckMode = 1) BEGIN @@ -31493,8 +32406,6 @@ DECLARE @ProductVersion NVARCHAR(128) AND r.plan_handle = session_stats.plan_handle AND r.statement_start_offset = session_stats.statement_start_offset AND r.statement_end_offset = session_stats.statement_end_offset' - ,@QueryStatsXMLselect NVARCHAR(MAX) = N' CAST(COALESCE(qs_live.query_plan, '''') AS XML) AS live_query_plan , ' - ,@QueryStatsXMLSQL NVARCHAR(MAX) = N'OUTER APPLY sys.dm_exec_query_statistics_xml(s.session_id) qs_live' ,@ObjectFullName NVARCHAR(2000) ,@OutputTableNameQueryStats_View NVARCHAR(256) ,@LineFeed NVARCHAR(MAX) /* Had to set as MAX up from 10 as it was truncating the view creation*/; @@ -31505,16 +32416,6 @@ 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) -IF EXISTS (SELECT * FROM sys.all_columns WHERE object_id = OBJECT_ID('sys.dm_exec_query_statistics_xml') AND name = 'query_plan') - BEGIN - SET @QueryStatsXMLselect = N' CAST(COALESCE(qs_live.query_plan, '''') AS XML) AS live_query_plan , '; - SET @QueryStatsXMLSQL = N'OUTER APPLY sys.dm_exec_query_statistics_xml(s.session_id) qs_live'; - END - ELSE - BEGIN - SET @QueryStatsXMLselect = N' NULL AS live_query_plan , '; - SET @QueryStatsXMLSQL = N' '; - END SELECT @OutputTableNameQueryStats_View = QUOTENAME(@OutputTableName + '_Deltas'), @@ -31555,6 +32456,8 @@ IF @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @Output [query_text] [nvarchar](max) NULL, [query_plan] [xml] NULL, [live_query_plan] [xml] NULL, + [cached_parameter_info] [nvarchar](max) NULL, + [live_parameter_info] [nvarchar](max) NULL, [query_cost] [float] NULL, [status] [nvarchar](30) NOT NULL, [wait_info] [nvarchar](max) NULL, @@ -31653,6 +32556,20 @@ IF @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @Output ALTER TABLE ' + @ObjectFullName + N' ADD JoinKey AS ServerName + CAST(CheckDate AS NVARCHAR(50));'; EXEC(@StringToExecute); + /* If the table doesn't have the new cached_parameter_info computed column, add it. See Github #2842. */ + SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableName; + SET @StringToExecute = N'IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns + WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''cached_parameter_info'') + ALTER TABLE ' + @ObjectFullName + N' ADD cached_parameter_info NVARCHAR(MAX) NULL;'; + EXEC(@StringToExecute); + + /* If the table doesn't have the new live_parameter_info computed column, add it. See Github #2842. */ + SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableName; + SET @StringToExecute = N'IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns + WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''live_parameter_info'') + ALTER TABLE ' + @ObjectFullName + N' ADD live_parameter_info NVARCHAR(MAX) NULL;'; + EXEC(@StringToExecute); + /* Delete history older than @OutputTableRetentionDays */ SET @OutputTableCleanupDate = CAST( (DATEADD(DAY, -1 * @OutputTableRetentionDays, GETDATE() ) ) AS DATE); SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' @@ -31948,7 +32865,27 @@ SELECT @BlockingCheck = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; sys2.spid AS session_id, sys2.blocked AS blocking_session_id, sys2.lastwaittype, sys2.waittime, sys2.cpu, sys2.physical_io, sys2.memusage FROM sys.sysprocesses AS sys1 JOIN sys.sysprocesses AS sys2 - ON sys1.spid = sys2.blocked;'; + ON sys1.spid = sys2.blocked; + + + DECLARE @LiveQueryPlans TABLE + ( + Session_Id INT NOT NULL, + Query_Plan XML NOT NULL + ); + + ' + +IF EXISTS (SELECT * FROM sys.all_columns WHERE object_id = OBJECT_ID('sys.dm_exec_query_statistics_xml') AND name = 'query_plan') +BEGIN + SET @BlockingCheck = @BlockingCheck + N' + INSERT INTO @LiveQueryPlans + SELECT s.session_id, query_plan + FROM sys.dm_exec_sessions AS s + CROSS APPLY sys.dm_exec_query_statistics_xml(s.session_id) + WHERE s.session_id <> @@SPID;'; +END + IF @ProductVersionMajor > 9 and @ProductVersionMajor < 11 BEGIN @@ -32170,9 +33107,19 @@ IF @ProductVersionMajor >= 11 ELSE query_stats.statement_end_offset END - query_stats.statement_start_offset ) / 2 ) + 1), dest.text) AS query_text , - derp.query_plan ,' - + @QueryStatsXMLselect - +' + derp.query_plan , + CAST(COALESCE(qs_live.query_plan, '''') AS XML) AS live_query_plan , + STUFF((SELECT DISTINCT N'', '' + Node.Data.value(''(@Column)[1]'', ''NVARCHAR(4000)'') + N'' {'' + Node.Data.value(''(@ParameterDataType)[1]'', ''NVARCHAR(4000)'') + N''}: '' + Node.Data.value(''(@ParameterCompiledValue)[1]'', ''NVARCHAR(4000)'') + FROM derp.query_plan.nodes(''/*:ShowPlanXML/*:BatchSequence/*:Batch/*:Statements/*:StmtSimple/*:QueryPlan/*:ParameterList/*:ColumnReference'') AS Node(Data) + FOR XML PATH('''')), 1,2,'''') + AS Cached_Parameter_Info, + ' + IF @ShowActualParameters = 1 + BEGIN + SELECT @StringToExecute = @StringToExecute + N'qs_live.Live_Parameter_Info as Live_Parameter_Info,' + END + + SELECT @StringToExecute = @StringToExecute + N' qmg.query_cost , s.status , CASE @@ -32393,10 +33340,18 @@ IF @ProductVersionMajor >= 11 AND tsu.session_id = r.session_id AND tsu.session_id = s.session_id ) as tempdb_allocations - ' - + @QueryStatsXMLSQL - + - N' + + OUTER APPLY ( + SELECT TOP 1 query_plan, + STUFF((SELECT DISTINCT N'', '' + Node.Data.value(''(@Column)[1]'', ''NVARCHAR(4000)'') + N'' {'' + Node.Data.value(''(@ParameterDataType)[1]'', ''NVARCHAR(4000)'') + N''}: '' + Node.Data.value(''(@ParameterCompiledValue)[1]'', ''NVARCHAR(4000)'') + N'' (Actual: '' + Node.Data.value(''(@ParameterRuntimeValue)[1]'', ''NVARCHAR(4000)'') + N'')'' + FROM q.query_plan.nodes(''/*:ShowPlanXML/*:BatchSequence/*:Batch/*:Statements/*:StmtSimple/*:QueryPlan/*:ParameterList/*:ColumnReference'') AS Node(Data) + FOR XML PATH('''')), 1,2,'''') + AS Live_Parameter_Info + FROM @LiveQueryPlans q + WHERE (s.session_id = q.session_id) + + ) AS qs_live + WHERE s.session_id <> @@SPID AND s.host_name IS NOT NULL AND r.database_id NOT IN (SELECT database_id FROM #WhoReadableDBs) @@ -32486,6 +33441,8 @@ IF @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @Output ,[query_text] ,[query_plan]' + CASE WHEN @ProductVersionMajor >= 11 THEN N',[live_query_plan]' ELSE N'' END + N' + ,[Cached_Parameter_Info]' + + CASE WHEN @ProductVersionMajor >= 11 AND @ShowActualParameters = 1 THEN N',[Live_Parameter_Info]' ELSE N'' END + N' ,[query_cost] ,[status] ,[wait_info]' @@ -32652,6 +33609,7 @@ DELETE FROM dbo.SqlServerVersions; INSERT INTO dbo.SqlServerVersions (MajorVersionNumber, MinorVersionNumber, Branch, [Url], ReleaseDate, MainstreamSupportEndDate, ExtendedSupportEndDate, MajorVersionName, MinorVersionName) VALUES + (15, 4123, 'CU10', 'https://support.microsoft.com/en-us/help/5001090', '2021-02-11', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 10'), (15, 4102, 'CU9', 'https://support.microsoft.com/en-us/help/5000642', '2021-02-11', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 9 '), (15, 4073, 'CU8 GDR', 'https://support.microsoft.com/en-us/help/4583459', '2021-01-12', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 8 GDR '), (15, 4073, 'CU8', 'https://support.microsoft.com/en-us/help/4577194', '2020-10-01', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 8 '), @@ -32664,7 +33622,7 @@ VALUES (15, 4003, 'CU1', 'https://support.microsoft.com/en-us/help/4527376', '2020-01-07', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 1 '), (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 '), - (14, 3356, 'RTM CU23', 'https://support.microsoft.com/en-us/help/5000685', '2021-02-25', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 23'), + (14, 3381, 'RTM CU23', 'https://support.microsoft.com/en-us/help/5000685', '2021-02-25', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 23'), (14, 3370, 'RTM CU22 GDR', 'https://support.microsoft.com/en-us/help/4583457', '2021-01-12', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 22 GDR'), (14, 3356, 'RTM CU22', 'https://support.microsoft.com/en-us/help/4577467', '2020-09-10', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 22'), (14, 3335, 'RTM CU21', 'https://support.microsoft.com/en-us/help/4557397', '2020-07-01', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 21'), @@ -33040,7 +33998,7 @@ BEGIN SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.02', @VersionDate = '20210322'; +SELECT @Version = '8.03', @VersionDate = '20210420'; IF(@VersionCheckMode = 1) BEGIN @@ -37451,8 +38409,8 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, [FindingsGroup] , [Finding] , [URL] , - CAST(@StockDetailsHeader + [Details] + @StockDetailsFooter AS NVARCHAR(MAX)) AS Details, - CAST([HowToStopIt] AS NVARCHAR(MAX)) AS HowToStopIt, + CAST(LEFT(@StockDetailsHeader + [Details] + @StockDetailsFooter,32000) AS TEXT) AS Details, + CAST(LEFT([HowToStopIt],32000) AS TEXT) AS HowToStopIt, CAST([QueryText] AS NVARCHAR(MAX)) AS QueryText, CAST([QueryPlan] AS NVARCHAR(MAX)) AS QueryPlan FROM #BlitzFirstResults diff --git a/sp_AllNightLog.sql b/sp_AllNightLog.sql index 22b93309f..a755cb0dc 100644 --- a/sp_AllNightLog.sql +++ b/sp_AllNightLog.sql @@ -30,7 +30,7 @@ SET NOCOUNT ON; BEGIN; -SELECT @Version = '8.02', @VersionDate = '20210322'; +SELECT @Version = '8.03', @VersionDate = '20210420'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_AllNightLog_Setup.sql b/sp_AllNightLog_Setup.sql index 617ec7614..0fddb7aa4 100644 --- a/sp_AllNightLog_Setup.sql +++ b/sp_AllNightLog_Setup.sql @@ -36,7 +36,7 @@ SET NOCOUNT ON; BEGIN; -SELECT @Version = '8.02', @VersionDate = '20210322'; +SELECT @Version = '8.03', @VersionDate = '20210420'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_Blitz.sql b/sp_Blitz.sql index 08ece0248..b0cc65ef2 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -37,7 +37,7 @@ AS SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.02', @VersionDate = '20210322'; + SELECT @Version = '8.03', @VersionDate = '20210420'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) diff --git a/sp_BlitzAnalysis.sql b/sp_BlitzAnalysis.sql index 472ad1646..3984bf29f 100644 --- a/sp_BlitzAnalysis.sql +++ b/sp_BlitzAnalysis.sql @@ -36,7 +36,7 @@ ALTER PROCEDURE [dbo].[sp_BlitzAnalysis] ( AS SET NOCOUNT ON; -SELECT @Version = '8.02', @VersionDate = '20210322'; +SELECT @Version = '8.03', @VersionDate = '20210420'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_BlitzBackups.sql b/sp_BlitzBackups.sql index a86a0207b..9b35e8b12 100755 --- a/sp_BlitzBackups.sql +++ b/sp_BlitzBackups.sql @@ -23,7 +23,7 @@ AS SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.02', @VersionDate = '20210322'; + SELECT @Version = '8.03', @VersionDate = '20210420'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_BlitzCache.sql b/sp_BlitzCache.sql index ded7ad613..1119cce6b 100644 --- a/sp_BlitzCache.sql +++ b/sp_BlitzCache.sql @@ -279,7 +279,7 @@ BEGIN SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.02', @VersionDate = '20210322'; +SELECT @Version = '8.03', @VersionDate = '20210420'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) diff --git a/sp_BlitzFirst.sql b/sp_BlitzFirst.sql index 5c47656c8..909a3ad7d 100644 --- a/sp_BlitzFirst.sql +++ b/sp_BlitzFirst.sql @@ -45,7 +45,7 @@ BEGIN SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.02', @VersionDate = '20210322'; +SELECT @Version = '8.03', @VersionDate = '20210420'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_BlitzInMemoryOLTP.sql b/sp_BlitzInMemoryOLTP.sql index 59d3658af..1018cb95d 100644 --- a/sp_BlitzInMemoryOLTP.sql +++ b/sp_BlitzInMemoryOLTP.sql @@ -82,7 +82,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI */ AS DECLARE @ScriptVersion VARCHAR(30); -SELECT @ScriptVersion = '1.8', @VersionDate = '20210322'; +SELECT @ScriptVersion = '1.8', @VersionDate = '20210420'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index 31a74cf1c..74b5d8118 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -46,7 +46,7 @@ AS SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.02', @VersionDate = '20210322'; +SELECT @Version = '8.03', @VersionDate = '20210420'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index f5185e0f9..99e4868dd 100644 --- a/sp_BlitzLock.sql +++ b/sp_BlitzLock.sql @@ -32,7 +32,7 @@ BEGIN SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.02', @VersionDate = '20210322'; +SELECT @Version = '8.03', @VersionDate = '20210420'; IF(@VersionCheckMode = 1) diff --git a/sp_BlitzQueryStore.sql b/sp_BlitzQueryStore.sql index 90af487a9..3a348f8a9 100644 --- a/sp_BlitzQueryStore.sql +++ b/sp_BlitzQueryStore.sql @@ -56,7 +56,7 @@ BEGIN /*First BEGIN*/ SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.02', @VersionDate = '20210322'; +SELECT @Version = '8.03', @VersionDate = '20210420'; IF(@VersionCheckMode = 1) BEGIN RETURN; diff --git a/sp_BlitzWho.sql b/sp_BlitzWho.sql index c8e3b9748..ee84c334f 100644 --- a/sp_BlitzWho.sql +++ b/sp_BlitzWho.sql @@ -30,7 +30,7 @@ BEGIN SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.02', @VersionDate = '20210322'; + SELECT @Version = '8.03', @VersionDate = '20210420'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_DatabaseRestore.sql b/sp_DatabaseRestore.sql index 6caa3a634..2728266c3 100755 --- a/sp_DatabaseRestore.sql +++ b/sp_DatabaseRestore.sql @@ -40,7 +40,7 @@ SET NOCOUNT ON; /*Versioning details*/ -SELECT @Version = '8.02', @VersionDate = '20210322'; +SELECT @Version = '8.03', @VersionDate = '20210420'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_ineachdb.sql b/sp_ineachdb.sql index 8e4b65860..061d28eb8 100644 --- a/sp_ineachdb.sql +++ b/sp_ineachdb.sql @@ -34,7 +34,7 @@ AS BEGIN SET NOCOUNT ON; - SELECT @Version = '8.02', @VersionDate = '20210322'; + SELECT @Version = '8.03', @VersionDate = '20210420'; IF(@VersionCheckMode = 1) BEGIN From ec9ccf5213b7bc99246a3d922e25d07b36088dba Mon Sep 17 00:00:00 2001 From: AdeDBA <33764798+Adedba@users.noreply.github.com> Date: Tue, 20 Apr 2021 15:32:53 +0100 Subject: [PATCH 169/662] #2870 sp_BlitzWho - Failing on case sensitive instances #2870 Corrected casing for columns: Query_Plan Session_Id cached_parameter_info --- sp_BlitzWho.sql | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/sp_BlitzWho.sql b/sp_BlitzWho.sql index ee84c334f..d384aa1bf 100644 --- a/sp_BlitzWho.sql +++ b/sp_BlitzWho.sql @@ -814,7 +814,7 @@ IF @ProductVersionMajor >= 11 END - query_stats.statement_start_offset ) / 2 ) + 1), dest.text) AS query_text , derp.query_plan , - CAST(COALESCE(qs_live.query_plan, '''') AS XML) AS live_query_plan , + CAST(COALESCE(qs_live.Query_Plan, '''') AS XML) AS live_query_plan , STUFF((SELECT DISTINCT N'', '' + Node.Data.value(''(@Column)[1]'', ''NVARCHAR(4000)'') + N'' {'' + Node.Data.value(''(@ParameterDataType)[1]'', ''NVARCHAR(4000)'') + N''}: '' + Node.Data.value(''(@ParameterCompiledValue)[1]'', ''NVARCHAR(4000)'') FROM derp.query_plan.nodes(''/*:ShowPlanXML/*:BatchSequence/*:Batch/*:Statements/*:StmtSimple/*:QueryPlan/*:ParameterList/*:ColumnReference'') AS Node(Data) FOR XML PATH('''')), 1,2,'''') @@ -1048,13 +1048,13 @@ IF @ProductVersionMajor >= 11 ) as tempdb_allocations OUTER APPLY ( - SELECT TOP 1 query_plan, + SELECT TOP 1 Query_Plan, STUFF((SELECT DISTINCT N'', '' + Node.Data.value(''(@Column)[1]'', ''NVARCHAR(4000)'') + N'' {'' + Node.Data.value(''(@ParameterDataType)[1]'', ''NVARCHAR(4000)'') + N''}: '' + Node.Data.value(''(@ParameterCompiledValue)[1]'', ''NVARCHAR(4000)'') + N'' (Actual: '' + Node.Data.value(''(@ParameterRuntimeValue)[1]'', ''NVARCHAR(4000)'') + N'')'' - FROM q.query_plan.nodes(''/*:ShowPlanXML/*:BatchSequence/*:Batch/*:Statements/*:StmtSimple/*:QueryPlan/*:ParameterList/*:ColumnReference'') AS Node(Data) + FROM q.Query_Plan.nodes(''/*:ShowPlanXML/*:BatchSequence/*:Batch/*:Statements/*:StmtSimple/*:QueryPlan/*:ParameterList/*:ColumnReference'') AS Node(Data) FOR XML PATH('''')), 1,2,'''') AS Live_Parameter_Info FROM @LiveQueryPlans q - WHERE (s.session_id = q.session_id) + WHERE (s.session_id = q.Session_Id) ) AS qs_live @@ -1147,7 +1147,7 @@ IF @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @Output ,[query_text] ,[query_plan]' + CASE WHEN @ProductVersionMajor >= 11 THEN N',[live_query_plan]' ELSE N'' END + N' - ,[Cached_Parameter_Info]' + ,[cached_parameter_info]' + CASE WHEN @ProductVersionMajor >= 11 AND @ShowActualParameters = 1 THEN N',[Live_Parameter_Info]' ELSE N'' END + N' ,[query_cost] ,[status] From 597a7bf08bde715210fbd4d3a5d71632f80e4a2f Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Tue, 20 Apr 2021 07:47:30 -0700 Subject: [PATCH 170/662] Including sp_BlitzWho fix for 2870. Rebuilding installer scripts. --- Install-All-Scripts.sql | 10 +++++----- Install-Core-Blitz-No-Query-Store.sql | 10 +++++----- Install-Core-Blitz-With-Query-Store.sql | 10 +++++----- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/Install-All-Scripts.sql b/Install-All-Scripts.sql index d24d7f49e..7654de90a 100755 --- a/Install-All-Scripts.sql +++ b/Install-All-Scripts.sql @@ -35927,7 +35927,7 @@ IF @ProductVersionMajor >= 11 END - query_stats.statement_start_offset ) / 2 ) + 1), dest.text) AS query_text , derp.query_plan , - CAST(COALESCE(qs_live.query_plan, '''') AS XML) AS live_query_plan , + CAST(COALESCE(qs_live.Query_Plan, '''') AS XML) AS live_query_plan , STUFF((SELECT DISTINCT N'', '' + Node.Data.value(''(@Column)[1]'', ''NVARCHAR(4000)'') + N'' {'' + Node.Data.value(''(@ParameterDataType)[1]'', ''NVARCHAR(4000)'') + N''}: '' + Node.Data.value(''(@ParameterCompiledValue)[1]'', ''NVARCHAR(4000)'') FROM derp.query_plan.nodes(''/*:ShowPlanXML/*:BatchSequence/*:Batch/*:Statements/*:StmtSimple/*:QueryPlan/*:ParameterList/*:ColumnReference'') AS Node(Data) FOR XML PATH('''')), 1,2,'''') @@ -36161,13 +36161,13 @@ IF @ProductVersionMajor >= 11 ) as tempdb_allocations OUTER APPLY ( - SELECT TOP 1 query_plan, + SELECT TOP 1 Query_Plan, STUFF((SELECT DISTINCT N'', '' + Node.Data.value(''(@Column)[1]'', ''NVARCHAR(4000)'') + N'' {'' + Node.Data.value(''(@ParameterDataType)[1]'', ''NVARCHAR(4000)'') + N''}: '' + Node.Data.value(''(@ParameterCompiledValue)[1]'', ''NVARCHAR(4000)'') + N'' (Actual: '' + Node.Data.value(''(@ParameterRuntimeValue)[1]'', ''NVARCHAR(4000)'') + N'')'' - FROM q.query_plan.nodes(''/*:ShowPlanXML/*:BatchSequence/*:Batch/*:Statements/*:StmtSimple/*:QueryPlan/*:ParameterList/*:ColumnReference'') AS Node(Data) + FROM q.Query_Plan.nodes(''/*:ShowPlanXML/*:BatchSequence/*:Batch/*:Statements/*:StmtSimple/*:QueryPlan/*:ParameterList/*:ColumnReference'') AS Node(Data) FOR XML PATH('''')), 1,2,'''') AS Live_Parameter_Info FROM @LiveQueryPlans q - WHERE (s.session_id = q.session_id) + WHERE (s.session_id = q.Session_Id) ) AS qs_live @@ -36260,7 +36260,7 @@ IF @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @Output ,[query_text] ,[query_plan]' + CASE WHEN @ProductVersionMajor >= 11 THEN N',[live_query_plan]' ELSE N'' END + N' - ,[Cached_Parameter_Info]' + ,[cached_parameter_info]' + CASE WHEN @ProductVersionMajor >= 11 AND @ShowActualParameters = 1 THEN N',[Live_Parameter_Info]' ELSE N'' END + N' ,[query_cost] ,[status] diff --git a/Install-Core-Blitz-No-Query-Store.sql b/Install-Core-Blitz-No-Query-Store.sql index f36272652..6607a89ff 100755 --- a/Install-Core-Blitz-No-Query-Store.sql +++ b/Install-Core-Blitz-No-Query-Store.sql @@ -27354,7 +27354,7 @@ IF @ProductVersionMajor >= 11 END - query_stats.statement_start_offset ) / 2 ) + 1), dest.text) AS query_text , derp.query_plan , - CAST(COALESCE(qs_live.query_plan, '''') AS XML) AS live_query_plan , + CAST(COALESCE(qs_live.Query_Plan, '''') AS XML) AS live_query_plan , STUFF((SELECT DISTINCT N'', '' + Node.Data.value(''(@Column)[1]'', ''NVARCHAR(4000)'') + N'' {'' + Node.Data.value(''(@ParameterDataType)[1]'', ''NVARCHAR(4000)'') + N''}: '' + Node.Data.value(''(@ParameterCompiledValue)[1]'', ''NVARCHAR(4000)'') FROM derp.query_plan.nodes(''/*:ShowPlanXML/*:BatchSequence/*:Batch/*:Statements/*:StmtSimple/*:QueryPlan/*:ParameterList/*:ColumnReference'') AS Node(Data) FOR XML PATH('''')), 1,2,'''') @@ -27588,13 +27588,13 @@ IF @ProductVersionMajor >= 11 ) as tempdb_allocations OUTER APPLY ( - SELECT TOP 1 query_plan, + SELECT TOP 1 Query_Plan, STUFF((SELECT DISTINCT N'', '' + Node.Data.value(''(@Column)[1]'', ''NVARCHAR(4000)'') + N'' {'' + Node.Data.value(''(@ParameterDataType)[1]'', ''NVARCHAR(4000)'') + N''}: '' + Node.Data.value(''(@ParameterCompiledValue)[1]'', ''NVARCHAR(4000)'') + N'' (Actual: '' + Node.Data.value(''(@ParameterRuntimeValue)[1]'', ''NVARCHAR(4000)'') + N'')'' - FROM q.query_plan.nodes(''/*:ShowPlanXML/*:BatchSequence/*:Batch/*:Statements/*:StmtSimple/*:QueryPlan/*:ParameterList/*:ColumnReference'') AS Node(Data) + FROM q.Query_Plan.nodes(''/*:ShowPlanXML/*:BatchSequence/*:Batch/*:Statements/*:StmtSimple/*:QueryPlan/*:ParameterList/*:ColumnReference'') AS Node(Data) FOR XML PATH('''')), 1,2,'''') AS Live_Parameter_Info FROM @LiveQueryPlans q - WHERE (s.session_id = q.session_id) + WHERE (s.session_id = q.Session_Id) ) AS qs_live @@ -27687,7 +27687,7 @@ IF @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @Output ,[query_text] ,[query_plan]' + CASE WHEN @ProductVersionMajor >= 11 THEN N',[live_query_plan]' ELSE N'' END + N' - ,[Cached_Parameter_Info]' + ,[cached_parameter_info]' + CASE WHEN @ProductVersionMajor >= 11 AND @ShowActualParameters = 1 THEN N',[Live_Parameter_Info]' ELSE N'' END + N' ,[query_cost] ,[status] diff --git a/Install-Core-Blitz-With-Query-Store.sql b/Install-Core-Blitz-With-Query-Store.sql index a3c7a46b5..722aa50f2 100755 --- a/Install-Core-Blitz-With-Query-Store.sql +++ b/Install-Core-Blitz-With-Query-Store.sql @@ -33108,7 +33108,7 @@ IF @ProductVersionMajor >= 11 END - query_stats.statement_start_offset ) / 2 ) + 1), dest.text) AS query_text , derp.query_plan , - CAST(COALESCE(qs_live.query_plan, '''') AS XML) AS live_query_plan , + CAST(COALESCE(qs_live.Query_Plan, '''') AS XML) AS live_query_plan , STUFF((SELECT DISTINCT N'', '' + Node.Data.value(''(@Column)[1]'', ''NVARCHAR(4000)'') + N'' {'' + Node.Data.value(''(@ParameterDataType)[1]'', ''NVARCHAR(4000)'') + N''}: '' + Node.Data.value(''(@ParameterCompiledValue)[1]'', ''NVARCHAR(4000)'') FROM derp.query_plan.nodes(''/*:ShowPlanXML/*:BatchSequence/*:Batch/*:Statements/*:StmtSimple/*:QueryPlan/*:ParameterList/*:ColumnReference'') AS Node(Data) FOR XML PATH('''')), 1,2,'''') @@ -33342,13 +33342,13 @@ IF @ProductVersionMajor >= 11 ) as tempdb_allocations OUTER APPLY ( - SELECT TOP 1 query_plan, + SELECT TOP 1 Query_Plan, STUFF((SELECT DISTINCT N'', '' + Node.Data.value(''(@Column)[1]'', ''NVARCHAR(4000)'') + N'' {'' + Node.Data.value(''(@ParameterDataType)[1]'', ''NVARCHAR(4000)'') + N''}: '' + Node.Data.value(''(@ParameterCompiledValue)[1]'', ''NVARCHAR(4000)'') + N'' (Actual: '' + Node.Data.value(''(@ParameterRuntimeValue)[1]'', ''NVARCHAR(4000)'') + N'')'' - FROM q.query_plan.nodes(''/*:ShowPlanXML/*:BatchSequence/*:Batch/*:Statements/*:StmtSimple/*:QueryPlan/*:ParameterList/*:ColumnReference'') AS Node(Data) + FROM q.Query_Plan.nodes(''/*:ShowPlanXML/*:BatchSequence/*:Batch/*:Statements/*:StmtSimple/*:QueryPlan/*:ParameterList/*:ColumnReference'') AS Node(Data) FOR XML PATH('''')), 1,2,'''') AS Live_Parameter_Info FROM @LiveQueryPlans q - WHERE (s.session_id = q.session_id) + WHERE (s.session_id = q.Session_Id) ) AS qs_live @@ -33441,7 +33441,7 @@ IF @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @Output ,[query_text] ,[query_plan]' + CASE WHEN @ProductVersionMajor >= 11 THEN N',[live_query_plan]' ELSE N'' END + N' - ,[Cached_Parameter_Info]' + ,[cached_parameter_info]' + CASE WHEN @ProductVersionMajor >= 11 AND @ShowActualParameters = 1 THEN N',[Live_Parameter_Info]' ELSE N'' END + N' ,[query_cost] ,[status] From c0016bb65648df8b366ed4613fec7e4bde05c8cb Mon Sep 17 00:00:00 2001 From: Vladimir Vissoultchev Date: Mon, 26 Apr 2021 17:21:30 +0300 Subject: [PATCH 171/662] Remove cached_parameter_info from insert column list on SQL2008R2 --- sp_BlitzWho.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sp_BlitzWho.sql b/sp_BlitzWho.sql index d384aa1bf..522b5db73 100644 --- a/sp_BlitzWho.sql +++ b/sp_BlitzWho.sql @@ -1147,7 +1147,7 @@ IF @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @Output ,[query_text] ,[query_plan]' + CASE WHEN @ProductVersionMajor >= 11 THEN N',[live_query_plan]' ELSE N'' END + N' - ,[cached_parameter_info]' + + CASE WHEN @ProductVersionMajor >= 11 THEN N',[cached_parameter_info]' ELSE N'' END + CASE WHEN @ProductVersionMajor >= 11 AND @ShowActualParameters = 1 THEN N',[Live_Parameter_Info]' ELSE N'' END + N' ,[query_cost] ,[status] From 8f2861f828514afd59bea327ac8d66d93672a182 Mon Sep 17 00:00:00 2001 From: AdeDBA <33764798+Adedba@users.noreply.github.com> Date: Thu, 29 Apr 2021 14:19:01 +0100 Subject: [PATCH 172/662] 2876 - Incorrect timestamp in the HowToStopIt column #2876 fix timestamp ordering in the HowToStopIt column --- sp_BlitzFirst.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sp_BlitzFirst.sql b/sp_BlitzFirst.sql index 909a3ad7d..c7f18d040 100644 --- a/sp_BlitzFirst.sql +++ b/sp_BlitzFirst.sql @@ -2291,7 +2291,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, CONVERT(VARCHAR(8000), rb.record) AS record FROM ( SELECT CONVERT(XML, dorb.record) AS record, - DATEADD(ms, ( ts.ms_ticks - dorb.timestamp ), GETDATE()) AS event_date + DATEADD(ms, -( ts.ms_ticks - dorb.timestamp ), GETDATE()) AS event_date FROM sys.dm_os_ring_buffers AS dorb CROSS JOIN ( SELECT dosi.ms_ticks FROM sys.dm_os_sys_info AS dosi ) AS ts From 4712e0af7977fad47dfaa2bfa8fe119093920676 Mon Sep 17 00:00:00 2001 From: AdeDBA <33764798+Adedba@users.noreply.github.com> Date: Thu, 29 Apr 2021 16:54:49 +0100 Subject: [PATCH 173/662] CPU HowToStopIt ordering #2878 Corrected CPU HowToStopIt ordering --- sp_BlitzFirst.sql | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/sp_BlitzFirst.sql b/sp_BlitzFirst.sql index 909a3ad7d..4946feaad 100644 --- a/sp_BlitzFirst.sql +++ b/sp_BlitzFirst.sql @@ -2288,7 +2288,8 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, ( SELECT CONVERT(VARCHAR(5), 100 - ca.c.value('.', 'INT')) AS system_idle, CONVERT(VARCHAR(30), rb.event_date) AS event_date, - CONVERT(VARCHAR(8000), rb.record) AS record + CONVERT(VARCHAR(8000), rb.record) AS record, + event_date as event_date_raw FROM ( SELECT CONVERT(XML, dorb.record) AS record, DATEADD(ms, ( ts.ms_ticks - dorb.timestamp ), GETDATE()) AS event_date @@ -2316,10 +2317,10 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, + ' Ring buffer details: ' + y2.record FROM y AS y2 - ORDER BY y2.event_date DESC + ORDER BY y2.event_date_raw DESC FOR XML PATH(N''), TYPE ).value(N'.[1]', N'VARCHAR(MAX)'), 1, 1, N'') AS query FROM y - ORDER BY y.event_date DESC; + ORDER BY y.event_date_raw DESC; /* Highlight if non SQL processes are using >25% CPU - CheckID 28 */ From 5da64128ad4121de460d025b5b849c5557739f46 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Fri, 30 Apr 2021 08:06:09 +0000 Subject: [PATCH 174/662] #2879 sp_BlitzFirst on RDS Skip check for Instant File Initialization. Closes #2879. --- sp_Blitz.sql | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index b0cc65ef2..4f2ef3b92 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -450,7 +450,7 @@ AS INSERT INTO #SkipChecks (CheckID) VALUES (177); INSERT INTO #SkipChecks (CheckID) VALUES (180); /* 180/181 are maintenance plans */ INSERT INTO #SkipChecks (CheckID) VALUES (181); - INSERT INTO #SkipChecks (CheckID) VALUES (184); /* xp_readerrorlog checking for IFI */ + INSERT INTO #SkipChecks (CheckID) VALUES (193); /* xp_readerrorlog checking for IFI */ INSERT INTO #SkipChecks (CheckID) VALUES (211); /* xp_regread checking for power saving */ INSERT INTO #SkipChecks (CheckID) VALUES (212); /* xp_regread */ INSERT INTO #SkipChecks (CheckID) VALUES (219); @@ -8130,11 +8130,11 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 */ IF NOT EXISTS ( SELECT 1 FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 184 ) - AND (@ProductVersionMajor >= 13) OR (@ProductVersionMajor = 12 AND @ProductVersionMinor >= 5000) + WHERE DatabaseName IS NULL AND CheckID = 193 ) + AND ((@ProductVersionMajor >= 13) OR (@ProductVersionMajor = 12 AND @ProductVersionMinor >= 5000)) BEGIN - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 184) WITH NOWAIT; + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 193) WITH NOWAIT; INSERT INTO #ErrorLog EXEC sys.xp_readerrorlog 0, 1, N'Database Instant File Initialization: enabled'; From 4180a6300b5201388249a6f6300d7fb1268fc20d Mon Sep 17 00:00:00 2001 From: Greg Dodd Date: Tue, 4 May 2021 11:09:16 +1000 Subject: [PATCH 175/662] Update sp_BlitzIndex.sql Filter the list of missing index query plans to just the missing index for this record --- sp_BlitzIndex.sql | 1 + 1 file changed, 1 insertion(+) diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index 74b5d8118..bce27f2a7 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -1674,6 +1674,7 @@ BEGIN TRY ORDER BY (q.user_seeks + q.user_scans) DESC, s.total_logical_reads DESC ) q2 CROSS APPLY sys.dm_exec_query_plan(q2.plan_handle) p + WHERE ig.index_group_handle = gs.group_handle ) ' END From b820a86e02fb679a97ac6eeeb0d84c877276f7d9 Mon Sep 17 00:00:00 2001 From: AdeDBA <33764798+Adedba@users.noreply.github.com> Date: Wed, 5 May 2021 22:09:02 +0100 Subject: [PATCH 176/662] Initial mock up for get outer command #2887 An initial mock up for get outer command addition. Not a finished version this is just putting in place the main code less any log to table elements. --- sp_BlitzWho.sql | 76 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 75 insertions(+), 1 deletion(-) diff --git a/sp_BlitzWho.sql b/sp_BlitzWho.sql index d384aa1bf..fcdf70935 100644 --- a/sp_BlitzWho.sql +++ b/sp_BlitzWho.sql @@ -21,6 +21,7 @@ ALTER PROCEDURE dbo.sp_BlitzWho @MinBlockingSeconds INT = 0 , @CheckDateOverride DATETIMEOFFSET = NULL, @ShowActualParameters BIT = 0, + @GetOuterCommand BIT = 0, @Version VARCHAR(30) = NULL OUTPUT, @VersionDate DATETIME = NULL OUTPUT, @VersionCheckMode BIT = 0, @@ -573,6 +574,56 @@ SELECT @BlockingCheck = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; JOIN sys.sysprocesses AS sys2 ON sys1.spid = sys2.blocked; + '+CASE + WHEN (@GetOuterCommand = 1 AND (NOT EXISTS(SELECT 1 FROM sys.all_objects WHERE [name] = N'dm_exec_input_buffer'))) THEN N'DECLARE @Iteration INT = 1; + DECLARE @DBCCCommand NVARCHAR(50); + DECLARE @Sessioncount INT; + DECLARE @SessionID INT = 0; + + DECLARE @Sessions TABLE + ( + session_id INT + ); + + DECLARE @inputbuffer TABLE + ( + ID INT IDENTITY(1,1), + session_id INT, + event_type NVARCHAR(30), + parameters SMALLINT, + event_info NVARCHAR(4000) + ); + + INSERT INTO @Sessions (session_id) + SELECT session_id + FROM sys.dm_exec_sessions + WHERE session_id <> @@SPID + AND session_id > 50; + + SELECT @Sessioncount = COUNT(*) FROM @Sessions; + + WHILE (@Iteration <= @Sessioncount) + BEGIN + SELECT TOP 1 @SessionID = session_id + FROM @Sessions + WHERE session_id > @SessionID + ORDER BY session_id ASC; + + SET @DBCCCommand = ''DBCC INPUTBUFFER (''+CAST(@SessionID AS NVARCHAR(10))+'') WITH NO_INFOMSGS;''; + + INSERT INTO @inputbuffer (event_type,parameters,event_info) + EXEC(@DBCCCommand); + + UPDATE @inputbuffer + SET session_id = @SessionID + WHERE ID = SCOPE_IDENTITY(); + + SET @Iteration += 1; + + END' + ELSE N'' + END+ + N' DECLARE @LiveQueryPlans TABLE ( @@ -581,7 +632,6 @@ SELECT @BlockingCheck = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; ); ' - IF EXISTS (SELECT * FROM sys.all_columns WHERE object_id = OBJECT_ID('sys.dm_exec_query_statistics_xml') AND name = 'query_plan') BEGIN SET @BlockingCheck = @BlockingCheck + N' @@ -608,6 +658,10 @@ BEGIN ELSE query_stats.statement_end_offset END - query_stats.statement_start_offset ) / 2 ) + 1), dest.text) AS query_text , + '+CASE + WHEN @GetOuterCommand = 1 THEN N'event_info AS outer_command,' + ELSE N'' + END+N' derp.query_plan , qmg.query_cost , s.status , @@ -727,6 +781,14 @@ BEGIN SET @StringToExecute += N'FROM sys.dm_exec_sessions AS s + '+ + CASE + WHEN @GetOuterCommand = 1 THEN CASE + WHEN EXISTS(SELECT 1 FROM sys.all_objects WHERE [name] = N'dm_exec_input_buffer') THEN N'OUTER APPLY sys.dm_exec_input_buffer (s.session_id, 0) AS ib' + ELSE N'LEFT JOIN @inputbuffer ib ON s.session_id = ib.session_id' + END + ELSE N'' + END+N' LEFT JOIN sys.dm_exec_requests AS r ON r.session_id = s.session_id LEFT JOIN ( SELECT DISTINCT @@ -813,6 +875,10 @@ IF @ProductVersionMajor >= 11 ELSE query_stats.statement_end_offset END - query_stats.statement_start_offset ) / 2 ) + 1), dest.text) AS query_text , + '+CASE + WHEN @GetOuterCommand = 1 THEN N'event_info AS outer_command,' + ELSE N'' + END+N' derp.query_plan , CAST(COALESCE(qs_live.Query_Plan, '''') AS XML) AS live_query_plan , STUFF((SELECT DISTINCT N'', '' + Node.Data.value(''(@Column)[1]'', ''NVARCHAR(4000)'') + N'' {'' + Node.Data.value(''(@ParameterDataType)[1]'', ''NVARCHAR(4000)'') + N''}: '' + Node.Data.value(''(@ParameterCompiledValue)[1]'', ''NVARCHAR(4000)'') @@ -991,6 +1057,14 @@ IF @ProductVersionMajor >= 11 SET @StringToExecute += N' FROM sys.dm_exec_sessions AS s + '+ + CASE + WHEN @GetOuterCommand = 1 THEN CASE + WHEN EXISTS(SELECT 1 FROM sys.all_objects WHERE [name] = N'dm_exec_input_buffer') THEN N'OUTER APPLY sys.dm_exec_input_buffer (s.session_id, 0) AS ib' + ELSE N'LEFT JOIN @inputbuffer ib ON s.session_id = ib.session_id' + END + ELSE N'' + END+N' LEFT JOIN sys.dm_exec_requests AS r ON r.session_id = s.session_id LEFT JOIN ( SELECT DISTINCT From 025c137d9d21a131b62d6a06e521f72bcc3faddd Mon Sep 17 00:00:00 2001 From: rrankins <62151171+rrankins@users.noreply.github.com> Date: Thu, 6 May 2021 01:29:16 -0400 Subject: [PATCH 177/662] Update sp_Blitz.sql --- sp_Blitz.sql | 104 ++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 77 insertions(+), 27 deletions(-) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index 4f2ef3b92..02df288d6 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -428,33 +428,38 @@ AS AND db_id('rdsadmin') IS NOT NULL AND EXISTS(SELECT * FROM master.sys.all_objects WHERE name IN ('rds_startup_tasks', 'rds_help_revlogin', 'rds_hexadecimal', 'rds_failover_tracking', 'rds_database_tracking', 'rds_track_change')) BEGIN - INSERT INTO #SkipChecks (CheckID) VALUES (6); - INSERT INTO #SkipChecks (CheckID) VALUES (29); - INSERT INTO #SkipChecks (CheckID) VALUES (30); - INSERT INTO #SkipChecks (CheckID) VALUES (31); - INSERT INTO #SkipChecks (CheckID) VALUES (40); /* TempDB only has one data file */ - INSERT INTO #SkipChecks (CheckID) VALUES (57); - INSERT INTO #SkipChecks (CheckID) VALUES (59); - INSERT INTO #SkipChecks (CheckID) VALUES (61); - INSERT INTO #SkipChecks (CheckID) VALUES (62); - INSERT INTO #SkipChecks (CheckID) VALUES (68); - INSERT INTO #SkipChecks (CheckID) VALUES (69); - INSERT INTO #SkipChecks (CheckID) VALUES (73); - INSERT INTO #SkipChecks (CheckID) VALUES (79); - INSERT INTO #SkipChecks (CheckID) VALUES (92); - INSERT INTO #SkipChecks (CheckID) VALUES (94); - INSERT INTO #SkipChecks (CheckID) VALUES (96); - INSERT INTO #SkipChecks (CheckID) VALUES (98); + 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 (29); /* tables in model database created by users - not allowed */ + INSERT INTO #SkipChecks (CheckID) VALUES (40); /* TempDB only has one data file in RDS */ + INSERT INTO #SkipChecks (CheckID) VALUES (62); /* Database compatibility level - cannot change in RDS */ + INSERT INTO #SkipChecks (CheckID) VALUES (68); /*Check for the last good DBCC CHECKDB date - can't run DBCC DBINFO() */ + INSERT INTO #SkipChecks (CheckID) VALUES (69); /* High VLF check - requires DBCC LOGINFO permission */ + INSERT INTO #SkipChecks (CheckID) VALUES (73); /* No Failsafe Operator Configured check */ + INSERT INTO #SkipChecks (CheckID) VALUES (92); /* Drive info check - requires xp_Fixeddrives permission */ INSERT INTO #SkipChecks (CheckID) VALUES (100); /* Remote DAC disabled */ - INSERT INTO #SkipChecks (CheckID) VALUES (123); - INSERT INTO #SkipChecks (CheckID) VALUES (177); - INSERT INTO #SkipChecks (CheckID) VALUES (180); /* 180/181 are maintenance plans */ - INSERT INTO #SkipChecks (CheckID) VALUES (181); - INSERT INTO #SkipChecks (CheckID) VALUES (193); /* xp_readerrorlog checking for IFI */ - INSERT INTO #SkipChecks (CheckID) VALUES (211); /* xp_regread checking for power saving */ - INSERT INTO #SkipChecks (CheckID) VALUES (212); /* xp_regread */ - INSERT INTO #SkipChecks (CheckID) VALUES (219); + INSERT INTO #SkipChecks (CheckID) VALUES (177); /* Disabled Internal Monitoring Features check - requires dm_server_registry access */ + INSERT INTO #SkipChecks (CheckID) VALUES (180); /* 180/181 are maintenance plans checks - Maint plans not available in RDS*/ + INSERT INTO #SkipChecks (CheckID) VALUES (181); /*Find repetitive maintenance tasks*/ + + -- can check errorlog using rdsadmin.dbo.rds_read_error_log, so allow this check + --INSERT INTO #SkipChecks (CheckID) VALUES (193); /* xp_readerrorlog checking for IFI */ + + INSERT INTO #SkipChecks (CheckID) VALUES (211); /* xp_regread not allowed - checking for power saving */ + INSERT INTO #SkipChecks (CheckID) VALUES (212); /* xp_regread not allowed - checking for additional instances */ INSERT INTO #SkipChecks (CheckID) VALUES (2301); /* sp_validatelogins called by Invalid login defined with Windows Authentication */ + + -- Following are skipped due to limited permissions in msdb/SQLAgent in RDS + INSERT INTO #SkipChecks (CheckID) VALUES (30); /* SQL Server Agent alerts not configured */ + INSERT INTO #SkipChecks (CheckID) VALUES (31); /* check whether we have NO ENABLED operators */ + INSERT INTO #SkipChecks (CheckID) VALUES (57); /* SQL Agent Job Runs at Startup */ + INSERT INTO #SkipChecks (CheckID) VALUES (59); /* Alerts Configured without Follow Up */ + INSERT INTO #SkipChecks (CheckID) VALUES (61); /*SQL Server Agent alerts do not exist for severity levels 19 through 25*/ + INSERT INTO #SkipChecks (CheckID) VALUES (79); /* Shrink Database Job check */ + INSERT INTO #SkipChecks (CheckID) VALUES (94); /* job failure without operator notification check */ + INSERT INTO #SkipChecks (CheckID) VALUES (96); /* Agent alerts for corruption */ + INSERT INTO #SkipChecks (CheckID) VALUES (98); /* check for disabled alerts */ + INSERT INTO #SkipChecks (CheckID) VALUES (123); /* Agent Jobs Starting Simultaneously */ + INSERT INTO #SkipChecks (CheckID) VALUES (219); /* check for alerts that do NOT include event descriptions in their outputs via email/pager/net-send */ INSERT INTO #BlitzResults ( CheckID , Priority , @@ -8136,10 +8141,24 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 193) WITH NOWAIT; - INSERT INTO #ErrorLog - EXEC sys.xp_readerrorlog 0, 1, N'Database Instant File Initialization: enabled'; + -- If this is Amazon RDS, use rdsadmin.dbo.rds_read_error_log + IF LEFT(CAST(SERVERPROPERTY('ComputerNamePhysicalNetBIOS') AS VARCHAR(8000)), 8) = 'EC2AMAZ-' + AND LEFT(CAST(SERVERPROPERTY('MachineName') AS VARCHAR(8000)), 8) = 'EC2AMAZ-' + AND LEFT(CAST(SERVERPROPERTY('ServerName') AS VARCHAR(8000)), 8) = 'EC2AMAZ-' + AND db_id('rdsadmin') IS NOT NULL + AND EXISTS(SELECT * FROM master.sys.all_objects WHERE name IN ('rds_startup_tasks', 'rds_help_revlogin', 'rds_hexadecimal', 'rds_failover_tracking', 'rds_database_tracking', 'rds_track_change')) + BEGIN + INSERT INTO #ErrorLog + EXEC rdsadmin.dbo.rds_read_error_log 0, 1, N'Database Instant File Initialization: enabled'; + END + ELSE + BEGIN + INSERT INTO #ErrorLog + EXEC sys.xp_readerrorlog 0, 1, N'Database Instant File Initialization: enabled'; + END IF @@ROWCOUNT > 0 + begin INSERT INTO #BlitzResults ( CheckID , [Priority] , @@ -8155,6 +8174,37 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 'Instant File Initialization Enabled' AS [Finding] , 'https://www.brentozar.com/go/instant' AS [URL] , 'The service account has the Perform Volume Maintenance Tasks permission.'; + end + else -- if version of sql server has instant_file_initialization_enabled column in dm_server_services, check that too + -- in the event the error log has been cycled and the startup messages are not in the current error log + begin + if EXISTS ( SELECT * + FROM sys.all_objects o + INNER JOIN sys.all_columns c ON o.object_id = c.object_id + WHERE o.name = 'dm_server_services' + AND c.name = 'instant_file_initialization_enabled' ) + begin + INSERT INTO #BlitzResults + ( CheckID , + [Priority] , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT + 193 AS [CheckID] , + 250 AS [Priority] , + 'Server Info' AS [FindingsGroup] , + 'Instant File Initialization Enabled' AS [Finding] , + 'https://www.brentozar.com/go/instant' AS [URL] , + 'The service account has the Perform Volume Maintenance Tasks permission.' + where exists (select 1 FROM sys.dm_server_services + WHERE instant_file_initialization_enabled = 'Y' + AND filename LIKE '%sqlservr.exe%') + OPTION (RECOMPILE); + end; + end; END; /* Server Info - Instant File Initialization Not Enabled - Check 192 - SQL Server 2016 SP1 and newer */ From ddf9ac5fff8f9a6b393754a078763af8b953d14e Mon Sep 17 00:00:00 2001 From: AdeDBA <33764798+Adedba@users.noreply.github.com> Date: Fri, 7 May 2021 10:50:36 +0100 Subject: [PATCH 178/662] Logic changes and Log to table code changes added #2887 Replaced the dbcc inputbuffer loop with a cursor to reduce additional reads for retrieving the next session_id to execute. Added a new column to the output table 'outer_command' Added relevant code to allow log to table for both @GetOuterCommand - 0 and = 1 --- sp_BlitzWho.sql | 80 +++++++++++++++++++++++++++++-------------------- 1 file changed, 48 insertions(+), 32 deletions(-) diff --git a/sp_BlitzWho.sql b/sp_BlitzWho.sql index fcdf70935..37a50d563 100644 --- a/sp_BlitzWho.sql +++ b/sp_BlitzWho.sql @@ -161,6 +161,7 @@ IF @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @Output [session_id] [smallint] NOT NULL, [database_name] [nvarchar](128) NULL, [query_text] [nvarchar](max) NULL, + [outer_command] NVARCHAR(4000) NULL, [query_plan] [xml] NULL, [live_query_plan] [xml] NULL, [cached_parameter_info] [nvarchar](max) NULL, @@ -277,6 +278,13 @@ IF @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @Output ALTER TABLE ' + @ObjectFullName + N' ADD live_parameter_info NVARCHAR(MAX) NULL;'; EXEC(@StringToExecute); + /* If the table doesn't have the new outer_command column, add it. See Github #2887. */ + SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableName; + SET @StringToExecute = N'IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns + WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''outer_command'') + ALTER TABLE ' + @ObjectFullName + N' ADD outer_command NVARCHAR(4000) NULL;'; + EXEC(@StringToExecute); + /* Delete history older than @OutputTableRetentionDays */ SET @OutputTableCleanupDate = CAST( (DATEADD(DAY, -1 * @OutputTableRetentionDays, GETDATE() ) ) AS DATE); SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' @@ -573,13 +581,9 @@ SELECT @BlockingCheck = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; FROM sys.sysprocesses AS sys1 JOIN sys.sysprocesses AS sys2 ON sys1.spid = sys2.blocked; - '+CASE - WHEN (@GetOuterCommand = 1 AND (NOT EXISTS(SELECT 1 FROM sys.all_objects WHERE [name] = N'dm_exec_input_buffer'))) THEN N'DECLARE @Iteration INT = 1; - DECLARE @DBCCCommand NVARCHAR(50); - DECLARE @Sessioncount INT; - DECLARE @SessionID INT = 0; - + WHEN (@GetOuterCommand = 1 AND (NOT EXISTS(SELECT 1 FROM sys.all_objects WHERE [name] = N'dm_exec_input_buffer'))) THEN N' + DECLARE @session_id SMALLINT; DECLARE @Sessions TABLE ( session_id INT @@ -594,33 +598,44 @@ SELECT @BlockingCheck = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; event_info NVARCHAR(4000) ); - INSERT INTO @Sessions (session_id) + DECLARE inputbuffer_cursor + + CURSOR LOCAL FAST_FORWARD + FOR SELECT session_id FROM sys.dm_exec_sessions WHERE session_id <> @@SPID - AND session_id > 50; - - SELECT @Sessioncount = COUNT(*) FROM @Sessions; - - WHILE (@Iteration <= @Sessioncount) - BEGIN - SELECT TOP 1 @SessionID = session_id - FROM @Sessions - WHERE session_id > @SessionID - ORDER BY session_id ASC; - - SET @DBCCCommand = ''DBCC INPUTBUFFER (''+CAST(@SessionID AS NVARCHAR(10))+'') WITH NO_INFOMSGS;''; - - INSERT INTO @inputbuffer (event_type,parameters,event_info) - EXEC(@DBCCCommand); - - UPDATE @inputbuffer - SET session_id = @SessionID - WHERE ID = SCOPE_IDENTITY(); - - SET @Iteration += 1; - - END' + AND is_user_process = 1; + + OPEN inputbuffer_cursor; + + FETCH NEXT FROM inputbuffer_cursor INTO @session_id; + + WHILE (@@FETCH_STATUS = 0) + BEGIN; + BEGIN TRY; + + INSERT INTO @inputbuffer ([event_type],[parameters],[event_info]) + EXEC sp_executesql + N''DBCC INPUTBUFFER(@session_id) WITH NO_INFOMSGS;'', + N''@session_id SMALLINT'', + @session_id; + + UPDATE @inputbuffer + SET session_id = @session_id + WHERE ID = SCOPE_IDENTITY(); + + END TRY + BEGIN CATCH + RAISERROR(''DBCC inputbuffer failed for session %d'',0,0,@session_id) WITH NOWAIT; + END CATCH; + + FETCH NEXT FROM inputbuffer_cursor INTO @session_id + + END; + + CLOSE inputbuffer_cursor; + DEALLOCATE inputbuffer_cursor;' ELSE N'' END+ N' @@ -786,7 +801,7 @@ BEGIN WHEN @GetOuterCommand = 1 THEN CASE WHEN EXISTS(SELECT 1 FROM sys.all_objects WHERE [name] = N'dm_exec_input_buffer') THEN N'OUTER APPLY sys.dm_exec_input_buffer (s.session_id, 0) AS ib' ELSE N'LEFT JOIN @inputbuffer ib ON s.session_id = ib.session_id' - END + END ELSE N'' END+N' LEFT JOIN sys.dm_exec_requests AS r @@ -1218,7 +1233,8 @@ IF @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @Output ,[elapsed_time] ,[session_id] ,[database_name] - ,[query_text] + ,[query_text]' + + CASE WHEN @GetOuterCommand = 1 THEN N',[outer_command]' ELSE N'' END + N' ,[query_plan]' + CASE WHEN @ProductVersionMajor >= 11 THEN N',[live_query_plan]' ELSE N'' END + N' ,[cached_parameter_info]' From b95bdf39df1c78657661e619e1bfc490197b779a Mon Sep 17 00:00:00 2001 From: AdeDBA <33764798+Adedba@users.noreply.github.com> Date: Fri, 7 May 2021 11:07:35 +0100 Subject: [PATCH 179/662] cast event_info as Nvarchar(4000) to stop truncation errors #2887 Truncation errors were occurring when executing against sys.dm_exec_input_buffer as the event_info from this dmv is an NVARCHAR(MAX) versus DBCC INPUTBUFFER which is NVARCHAR(4000). Added a cast to ensure we are always getting a max of 4000 chars. --- sp_BlitzWho.sql | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/sp_BlitzWho.sql b/sp_BlitzWho.sql index 37a50d563..a382e8891 100644 --- a/sp_BlitzWho.sql +++ b/sp_BlitzWho.sql @@ -674,7 +674,7 @@ BEGIN END - query_stats.statement_start_offset ) / 2 ) + 1), dest.text) AS query_text , '+CASE - WHEN @GetOuterCommand = 1 THEN N'event_info AS outer_command,' + WHEN @GetOuterCommand = 1 THEN N'CAST(event_info AS NVARCHAR(4000)) AS outer_command,' ELSE N'' END+N' derp.query_plan , @@ -891,7 +891,7 @@ IF @ProductVersionMajor >= 11 END - query_stats.statement_start_offset ) / 2 ) + 1), dest.text) AS query_text , '+CASE - WHEN @GetOuterCommand = 1 THEN N'event_info AS outer_command,' + WHEN @GetOuterCommand = 1 THEN N'CAST(event_info AS NVARCHAR(4000)) AS outer_command,' ELSE N'' END+N' derp.query_plan , @@ -1071,13 +1071,14 @@ IF @ProductVersionMajor >= 11 END /* IF @ExpertMode = 1 */ SET @StringToExecute += - N' FROM sys.dm_exec_sessions AS s - '+ + N' FROM sys.dm_exec_sessions AS s'+ CASE WHEN @GetOuterCommand = 1 THEN CASE - WHEN EXISTS(SELECT 1 FROM sys.all_objects WHERE [name] = N'dm_exec_input_buffer') THEN N'OUTER APPLY sys.dm_exec_input_buffer (s.session_id, 0) AS ib' - ELSE N'LEFT JOIN @inputbuffer ib ON s.session_id = ib.session_id' - END + WHEN EXISTS(SELECT 1 FROM sys.all_objects WHERE [name] = N'dm_exec_input_buffer') THEN N' + OUTER APPLY sys.dm_exec_input_buffer (s.session_id, 0) AS ib' + ELSE N' + LEFT JOIN @inputbuffer ib ON s.session_id = ib.session_id' + END ELSE N'' END+N' LEFT JOIN sys.dm_exec_requests AS r From f9d4816986dcc64e314a15a91c7f13187c9478da Mon Sep 17 00:00:00 2001 From: RonMacNeil Date: Fri, 7 May 2021 11:22:38 -0400 Subject: [PATCH 180/662] Update sp_BlitzCache.sql Fix for Issue #2890. --- sp_BlitzCache.sql | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sp_BlitzCache.sql b/sp_BlitzCache.sql index 1119cce6b..1cc6952d3 100644 --- a/sp_BlitzCache.sql +++ b/sp_BlitzCache.sql @@ -2015,7 +2015,7 @@ IF @DurationFilter IS NOT NULL IF @MinutesBack IS NOT NULL BEGIN RAISERROR(N'Setting minutes back filter', 0, 1) WITH NOWAIT; - SET @body += N' AND DATEADD(MILLISECOND, (x.last_elapsed_time / 1000.), x.last_execution_time) >= DATEADD(MINUTE, @min_back, GETDATE()) ' + @nl ; + SET @body += N' AND DATEADD(SECOND, (x.last_elapsed_time / 1000000.), x.last_execution_time) >= DATEADD(MINUTE, @min_back, GETDATE()) ' + @nl ; END; IF @SlowlySearchPlansFor IS NOT NULL @@ -2134,7 +2134,7 @@ SELECT TOP (@Top) END AS WritesPerMinute, qs.cached_time AS PlanCreationTime, qs.last_execution_time AS LastExecutionTime, - DATEADD(MILLISECOND, (qs.last_elapsed_time / 1000.), qs.last_execution_time) AS LastCompletionTime, + DATEADD(SECOND, (qs.last_elapsed_time / 1000000.), qs.last_execution_time) AS LastCompletionTime, NULL AS StatementStartOffset, NULL AS StatementEndOffset, NULL AS PlanGenerationNum, @@ -2245,7 +2245,7 @@ BEGIN END AS WritesPerMinute, qs.creation_time AS PlanCreationTime, qs.last_execution_time AS LastExecutionTime, - DATEADD(MILLISECOND, (qs.last_elapsed_time / 1000.), qs.last_execution_time) AS LastCompletionTime, + DATEADD(SECOND, (qs.last_elapsed_time / 1000000.), qs.last_execution_time) AS LastCompletionTime, qs.statement_start_offset AS StatementStartOffset, qs.statement_end_offset AS StatementEndOffset, qs.plan_generation_num AS PlanGenerationNum, '; From d15724b56c68e9ac1092c73cb83265498330a8ee Mon Sep 17 00:00:00 2001 From: andreasjordan Date: Mon, 10 May 2021 19:36:15 +0200 Subject: [PATCH 181/662] Treat table SqlServerVersions like the procedures --- Uninstall.sql | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/Uninstall.sql b/Uninstall.sql index 3a1b77533..42ff2d9c5 100644 --- a/Uninstall.sql +++ b/Uninstall.sql @@ -39,6 +39,10 @@ BEGIN FROM sys.procedures P JOIN #ToDelete D ON D.ProcedureName = P.name; + SELECT @SQL += N'DROP TABLE dbo.SqlServerVersions;' + CHAR(10) + FROM sys.tables + WHERE schema_id = 1 AND name = 'SqlServerVersions'; + END ELSE BEGIN @@ -64,6 +68,12 @@ BEGIN EXEC sp_executesql @innerSQL, N'@SQL nvarchar(max) OUTPUT', @SQL = @SQL OUTPUT; + SET @innerSQL = N' SELECT @SQL += N''USE ' + @dbname + N';' + NCHAR(10) + N'DROP TABLE dbo.SqlServerVersions;'' + NCHAR(10) + FROM ' + @dbname + N'.sys.tables + WHERE schema_id = 1 AND name = ''SqlServerVersions'''; + + EXEC sp_executesql @innerSQL, N'@SQL nvarchar(max) OUTPUT', @SQL = @SQL OUTPUT; + FETCH NEXT FROM c INTO @dbname; END @@ -73,9 +83,6 @@ BEGIN END -IF OBJECT_ID('dbo.SqlServerVersions') IS NOT NULL - DROP TABLE dbo.SqlServerVersions; - PRINT @SQL; IF(@printOnly = 0) From 5726478749eac893085b91e00f4a0fcef19f0b24 Mon Sep 17 00:00:00 2001 From: andreasjordan Date: Mon, 10 May 2021 20:05:07 +0200 Subject: [PATCH 182/662] suppress output when saving to table --- sp_BlitzFirst.sql | 1 + 1 file changed, 1 insertion(+) diff --git a/sp_BlitzFirst.sql b/sp_BlitzFirst.sql index 3e47e675f..c3ebd14e9 100644 --- a/sp_BlitzFirst.sql +++ b/sp_BlitzFirst.sql @@ -4747,4 +4747,5 @@ EXEC sp_BlitzFirst , @OutputTableNameWaitStats = 'BlitzFirst_WaitStats' , @OutputTableNameBlitzCache = 'BlitzCache' , @OutputTableNameBlitzWho = 'BlitzWho' +, @OutputType = 'none' */ From 2bbd1b18d7aa6336c4d8e484c658931eccfcab23 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Tue, 11 May 2021 05:26:29 +0000 Subject: [PATCH 183/662] Add recent CUs --- SqlServerVersions.sql | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/SqlServerVersions.sql b/SqlServerVersions.sql index ba9aa1851..d0939050c 100644 --- a/SqlServerVersions.sql +++ b/SqlServerVersions.sql @@ -41,7 +41,7 @@ DELETE FROM dbo.SqlServerVersions; INSERT INTO dbo.SqlServerVersions (MajorVersionNumber, MinorVersionNumber, Branch, [Url], ReleaseDate, MainstreamSupportEndDate, ExtendedSupportEndDate, MajorVersionName, MinorVersionName) VALUES - (15, 4123, 'CU10', 'https://support.microsoft.com/en-us/help/5001090', '2021-02-11', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 10'), + (15, 4123, 'CU10', 'https://support.microsoft.com/en-us/help/5001090', '2021-04-06', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 10'), (15, 4102, 'CU9', 'https://support.microsoft.com/en-us/help/5000642', '2021-02-11', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 9 '), (15, 4073, 'CU8 GDR', 'https://support.microsoft.com/en-us/help/4583459', '2021-01-12', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 8 GDR '), (15, 4073, 'CU8', 'https://support.microsoft.com/en-us/help/4577194', '2020-10-01', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 8 '), @@ -54,6 +54,7 @@ VALUES (15, 4003, 'CU1', 'https://support.microsoft.com/en-us/help/4527376', '2020-01-07', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 1 '), (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 '), + (14, 3391, 'RTM CU24', 'https://support.microsoft.com/en-us/help/5001228', '2021-05-10', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 24'), (14, 3381, 'RTM CU23', 'https://support.microsoft.com/en-us/help/5000685', '2021-02-25', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 23'), (14, 3370, 'RTM CU22 GDR', 'https://support.microsoft.com/en-us/help/4583457', '2021-01-12', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 22 GDR'), (14, 3356, 'RTM CU22', 'https://support.microsoft.com/en-us/help/4577467', '2020-09-10', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 22'), @@ -79,6 +80,7 @@ VALUES (14, 3008, 'RTM CU2', 'https://support.microsoft.com/en-us/help/4052574', '2017-11-28', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 2'), (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 '), + (13, 5882, 'SP2 CU17', 'https://support.microsoft.com/en-us/help/5001092', '2021-03-29', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 17'), (13, 5882, 'SP2 CU16', 'https://support.microsoft.com/en-us/help/5000645', '2021-02-11', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 16'), (13, 5865, 'SP2 CU15 GDR', 'https://support.microsoft.com/en-us/help/4583461', '2021-01-12', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 15 GDR'), (13, 5850, 'SP2 CU15', 'https://support.microsoft.com/en-us/help/4577775', '2020-09-28', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 15'), From 83cc7ccceee8839273c3c4a337349048f6299212 Mon Sep 17 00:00:00 2001 From: Anthony Green <40231097+Ant-Green@users.noreply.github.com> Date: Thu, 13 May 2021 09:36:28 +0100 Subject: [PATCH 184/662] FixedBuildNumFor2016CU17 FixedBuildNumFor2016CU17 --- SqlServerVersions.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SqlServerVersions.sql b/SqlServerVersions.sql index d0939050c..61789e5e3 100644 --- a/SqlServerVersions.sql +++ b/SqlServerVersions.sql @@ -80,7 +80,7 @@ VALUES (14, 3008, 'RTM CU2', 'https://support.microsoft.com/en-us/help/4052574', '2017-11-28', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 2'), (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 '), - (13, 5882, 'SP2 CU17', 'https://support.microsoft.com/en-us/help/5001092', '2021-03-29', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 17'), + (13, 5888, 'SP2 CU17', 'https://support.microsoft.com/en-us/help/5001092', '2021-03-29', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 17'), (13, 5882, 'SP2 CU16', 'https://support.microsoft.com/en-us/help/5000645', '2021-02-11', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 16'), (13, 5865, 'SP2 CU15 GDR', 'https://support.microsoft.com/en-us/help/4583461', '2021-01-12', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 15 GDR'), (13, 5850, 'SP2 CU15', 'https://support.microsoft.com/en-us/help/4577775', '2020-09-28', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 15'), From ffc08acdf228781c6c75f83e7fb1c33abc76069c Mon Sep 17 00:00:00 2001 From: Greg Dodd Date: Fri, 14 May 2021 16:40:51 +1000 Subject: [PATCH 185/662] Update sp_databaseRestore to correctly get Diff File to restore and to skip log files that need to be skipped --- sp_DatabaseRestore.sql | 56 ++++++++++++++---------------------------- 1 file changed, 18 insertions(+), 38 deletions(-) diff --git a/sp_DatabaseRestore.sql b/sp_DatabaseRestore.sql index 2728266c3..dd494c32f 100755 --- a/sp_DatabaseRestore.sql +++ b/sp_DatabaseRestore.sql @@ -998,7 +998,11 @@ BEGIN END /*End folder sanity check*/ -- Find latest diff backup - SELECT @LastDiffBackup = MAX(BackupFile) + SELECT @LastDiffBackup = MAX(CASE + WHEN @StopAt IS NULL OR REPLACE( RIGHT( REPLACE( BackupFile, RIGHT( BackupFile, PATINDEX( '%_[0-9][0-9]%', REVERSE( BackupFile ) ) ), '' ), 16 ), '_', '' ) <= @StopAt THEN + BackupFile + ELSE '' + END) FROM @FileList WHERE BackupFile LIKE N'%.bak' AND @@ -1245,10 +1249,21 @@ BEGIN END - +IF (@StopAt IS NOT NULL) +BEGIN + + IF @Execute = 'Y' OR @Debug = 1 RAISERROR('@OnlyLogsAfter is NOT NULL, deleting from @FileList', 0, 1) WITH NOWAIT; + + DELETE fl + FROM @FileList AS fl + WHERE BackupFile LIKE N'%.trn' + AND BackupFile LIKE N'%' + @Database + N'%' + AND REPLACE( RIGHT( REPLACE( fl.BackupFile, RIGHT( fl.BackupFile, PATINDEX( '%_[0-9][0-9]%', REVERSE( fl.BackupFile ) ) ), '' ), 16 ), '_', '' ) > @StopAt; + +END -- Check for log backups -IF(@StopAt IS NULL AND @OnlyLogsAfter IS NULL) +IF(@BackupDateTime IS NOT NULL AND @BackupDateTime <> '') BEGIN DELETE FROM @FileList WHERE BackupFile LIKE N'%.trn' @@ -1257,41 +1272,6 @@ IF(@StopAt IS NULL AND @OnlyLogsAfter IS NULL) END; -IF (@StopAt IS NULL AND @OnlyLogsAfter IS NOT NULL) - BEGIN - DELETE FROM @FileList - WHERE BackupFile LIKE N'%.trn' - AND BackupFile LIKE N'%' + @Database + N'%' - AND NOT (@ContinueLogs = 1 OR (@ContinueLogs = 0 AND REPLACE( RIGHT( REPLACE( BackupFile, RIGHT( BackupFile, PATINDEX( '%_[0-9][0-9]%', REVERSE( BackupFile ) ) ), '' ), 16 ), '_', '' ) >= @OnlyLogsAfter)); - END; - - -IF (@StopAt IS NOT NULL AND @OnlyLogsAfter IS NULL) - BEGIN - DELETE FROM @FileList - WHERE BackupFile LIKE N'%.trn' - AND BackupFile LIKE N'%' + @Database + N'%' - AND NOT (@ContinueLogs = 1 OR (@ContinueLogs = 0 AND REPLACE( RIGHT( REPLACE( BackupFile, RIGHT( BackupFile, PATINDEX( '%_[0-9][0-9]%', REVERSE( BackupFile ) ) ), '' ), 16 ), '_', '' ) >= @BackupDateTime) AND REPLACE( RIGHT( REPLACE( BackupFile, RIGHT( BackupFile, PATINDEX( '%_[0-9][0-9]%', REVERSE( BackupFile ) ) ), '' ), 16 ), '_', '' ) <= @StopAt) - AND NOT ((@ContinueLogs = 1 AND REPLACE( RIGHT( REPLACE( BackupFile, RIGHT( BackupFile, PATINDEX( '%_[0-9][0-9]%', REVERSE( BackupFile ) ) ), '' ), 16 ), '_', '' ) <= @StopAt) OR (@ContinueLogs = 0 AND REPLACE( RIGHT( REPLACE( BackupFile, RIGHT( BackupFile, PATINDEX( '%_[0-9][0-9]%', REVERSE( BackupFile ) ) ), '' ), 16 ), '_', '' ) >= @BackupDateTime) AND REPLACE( RIGHT( REPLACE( BackupFile, RIGHT( BackupFile, PATINDEX( '%_[0-9][0-9]%', REVERSE( BackupFile ) ) ), '' ), 16 ), '_', '' ) <= @StopAt) - - END; - -IF (@StopAt IS NOT NULL AND @OnlyLogsAfter IS NOT NULL) - BEGIN - DECLARE BackupFiles CURSOR FOR - SELECT BackupFile - FROM @FileList - WHERE BackupFile LIKE N'%.trn' - AND BackupFile LIKE N'%' + @Database + N'%' - AND (@ContinueLogs = 1 OR (@ContinueLogs = 0 AND REPLACE(LEFT(RIGHT(BackupFile, 19), 15),'_','') >= @BackupDateTime) AND REPLACE(LEFT(RIGHT(BackupFile, 19), 15),'_','') <= @StopAt) - AND ((@ContinueLogs = 1 AND REPLACE(LEFT(RIGHT(BackupFile, 19), 15),'_','') <= @StopAt) OR (@ContinueLogs = 0 AND REPLACE(LEFT(RIGHT(BackupFile, 19), 15),'_','') >= @BackupDateTime) AND REPLACE(LEFT(RIGHT(BackupFile, 19), 15),'_','') <= @StopAt) - AND (@ContinueLogs = 1 OR (@ContinueLogs = 0 AND REPLACE(LEFT(RIGHT(BackupFile, 19), 15),'_','') >= @OnlyLogsAfter)) - ORDER BY BackupFile; - - OPEN BackupFiles; - END; - - IF (@StandbyMode = 1) BEGIN IF (@StandbyUndoPath IS NULL) From 73b63617000f451c7eeff2988839bc6b93ed784e Mon Sep 17 00:00:00 2001 From: Erik Darling Date: Sat, 15 May 2021 11:54:48 -0400 Subject: [PATCH 186/662] Fixes #2900 Counting plans should count plan hashes and not query hashes. We should also count items from the base view and not `##BlitzCacheProcs` --- sp_BlitzCache.sql | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/sp_BlitzCache.sql b/sp_BlitzCache.sql index 1cc6952d3..6aad5835d 100644 --- a/sp_BlitzCache.sql +++ b/sp_BlitzCache.sql @@ -2828,17 +2828,25 @@ UPDATE ##BlitzCacheProcs SET NumberOfDistinctPlans = distinct_plan_count, NumberOfPlans = number_of_plans , plan_multiple_plans = CASE WHEN distinct_plan_count < number_of_plans THEN number_of_plans END -FROM ( - SELECT COUNT(DISTINCT QueryHash) AS distinct_plan_count, - COUNT(QueryHash) AS number_of_plans, - QueryHash, - DatabaseName - FROM ##BlitzCacheProcs - WHERE SPID = @@SPID - GROUP BY QueryHash, - DatabaseName +FROM + ( + SELECT + DatabaseName = + DB_NAME(CONVERT(int, pa.value)), + QueryPlanHash = + qs.query_plan_hash, + number_of_plans = + COUNT_BIG(qs.query_plan_hash), + distinct_plan_count = + COUNT_BIG(DISTINCT qs.query_plan_hash) + FROM sys.dm_exec_query_stats AS qs + CROSS APPLY sys.dm_exec_plan_attributes(qs.plan_handle) pa + WHERE pa.attribute = 'dbid' + GROUP BY + DB_NAME(CONVERT(int, pa.value)), + qs.query_plan_hash ) AS x -WHERE ##BlitzCacheProcs.QueryHash = x.QueryHash +WHERE ##BlitzCacheProcs.QueryPlanHash = x.QueryPlanHash AND ##BlitzCacheProcs.DatabaseName = x.DatabaseName OPTION (RECOMPILE) ; From d113dfd1bd8f3f34dd56ca017eb241dde8aa9682 Mon Sep 17 00:00:00 2001 From: Scott Holiday <40552063+holidasa@users.noreply.github.com> Date: Sun, 16 May 2021 21:41:54 -0500 Subject: [PATCH 187/662] Update sp_BlitzIndex.sql This is for bug #2880 for sp_BlitzIndex. Modified the char-based INCLUDED column definition to match how char-based key columns are displayed (i.e. {nvarchar 80} versus {nvarchar (40)}). Added max size for non-char-based columns (i.e. {bigint 8}) for key, include, and secret columns. --- sp_BlitzIndex.sql | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index 74b5d8118..2ce118213 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -2118,12 +2118,12 @@ UPDATE #IndexSanity SET key_column_names = D1.key_column_names FROM #IndexSanity si CROSS APPLY ( SELECT RTRIM(STUFF( (SELECT N', ' + c.column_name - + N' {' + system_type_name + N' ' + - CASE max_length WHEN -1 THEN N'(max)' ELSE + + N' {' + system_type_name + + CASE max_length WHEN -1 THEN N' (max)' ELSE CASE - WHEN system_type_name IN (N'char',N'varchar',N'binary',N'varbinary') THEN N'(' + CAST(max_length AS NVARCHAR(20)) + N')' - WHEN system_type_name IN (N'nchar',N'nvarchar') THEN N'(' + CAST(max_length/2 AS NVARCHAR(20)) + N')' - ELSE '' + WHEN system_type_name IN (N'char',N'varchar',N'binary',N'varbinary') THEN N' (' + CAST(max_length AS NVARCHAR(20)) + N')' + WHEN system_type_name IN (N'nchar',N'nvarchar') THEN N' (' + CAST(max_length/2 AS NVARCHAR(20)) + N')' + ELSE N' ' + CAST(max_length AS NVARCHAR(50)) END END + N'}' @@ -2162,12 +2162,12 @@ FROM #IndexSanity si WHEN 1 THEN N' DESC' ELSE N'' END - + N' {' + system_type_name + N' ' + - CASE max_length WHEN -1 THEN N'(max)' ELSE + + N' {' + system_type_name + + CASE max_length WHEN -1 THEN N' (max)' ELSE CASE - WHEN system_type_name IN (N'char',N'varchar',N'binary',N'varbinary') THEN N'(' + CAST(max_length AS NVARCHAR(20)) + N')' - WHEN system_type_name IN (N'nchar',N'nvarchar') THEN N'(' + CAST(max_length/2 AS NVARCHAR(20)) + N')' - ELSE '' + WHEN system_type_name IN (N'char',N'varchar',N'binary',N'varbinary') THEN N' (' + CAST(max_length AS NVARCHAR(20)) + N')' + WHEN system_type_name IN (N'nchar',N'nvarchar') THEN N' (' + CAST(max_length/2 AS NVARCHAR(20)) + N')' + ELSE N' ' + CAST(max_length AS NVARCHAR(50)) END END + N'}' @@ -2207,7 +2207,15 @@ UPDATE #IndexSanity SET include_column_names = D3.include_column_names FROM #IndexSanity si CROSS APPLY ( SELECT RTRIM(STUFF( (SELECT N', ' + c.column_name - + N' {' + system_type_name + N' ' + CAST(max_length AS NVARCHAR(50)) + N'}' + + N' {' + system_type_name + + CASE max_length WHEN -1 THEN N' (max)' ELSE + CASE + WHEN system_type_name IN (N'char',N'varchar',N'binary',N'varbinary') THEN N' (' + CAST(max_length AS NVARCHAR(20)) + N')' + WHEN system_type_name IN (N'nchar',N'nvarchar') THEN N' (' + CAST(max_length/2 AS NVARCHAR(20)) + N')' + ELSE N' ' + CAST(max_length AS NVARCHAR(50)) + END + END + + N'}' FROM #IndexColumns c WHERE c.database_id= si.database_id AND c.schema_name = si.schema_name From cc02d5f50d517b329713e507265039f37cb69dd9 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Mon, 17 May 2021 09:23:54 +0000 Subject: [PATCH 188/662] 2907 sp_BlitzWho lock timeout Added 1 second lock timeout when gathering live query plans. Working on #2907. --- sp_BlitzWho.sql | 1 + 1 file changed, 1 insertion(+) diff --git a/sp_BlitzWho.sql b/sp_BlitzWho.sql index 522b5db73..b90230e80 100644 --- a/sp_BlitzWho.sql +++ b/sp_BlitzWho.sql @@ -550,6 +550,7 @@ END SELECT @BlockingCheck = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + SET LOCK_TIMEOUT 1000; /* To avoid blocking on live query plans. See Github issue #2907. */ DECLARE @blocked TABLE ( dbid SMALLINT NOT NULL, From 611f0e5d92b205fa9af039877c4a8b9f27b46b1b Mon Sep 17 00:00:00 2001 From: Erik Darling Date: Mon, 17 May 2021 12:42:08 -0400 Subject: [PATCH 189/662] Issue 2909 Hopefully fixes the plan cache count calculations. --- sp_BlitzCache.sql | 90 +++++++++++++++++++++++++++++++++-------------- 1 file changed, 63 insertions(+), 27 deletions(-) diff --git a/sp_BlitzCache.sql b/sp_BlitzCache.sql index 1cc6952d3..dc036f1f2 100644 --- a/sp_BlitzCache.sql +++ b/sp_BlitzCache.sql @@ -1563,10 +1563,10 @@ database_id INT CREATE TABLE #plan_usage ( - duplicate_plan_handles BIGINT NULL, - percent_duplicate NUMERIC(7, 2) NULL, + duplicate_plan_hashes BIGINT NULL, + percent_duplicate DECIMAL(5, 2) NULL, single_use_plan_count BIGINT NULL, - percent_single NUMERIC(7, 2) NULL, + percent_single DECIMAL(5, 2) NULL, total_plans BIGINT NULL, spid INT ); @@ -1600,49 +1600,85 @@ OPTION (RECOMPILE); RAISERROR(N'Checking for single use plans and plans with many queries', 0, 1) WITH NOWAIT; WITH total_plans AS ( - SELECT COUNT_BIG(*) AS total_plans - FROM sys.dm_exec_cached_plans AS deqs - WHERE deqs.cacheobjtype = N'Compiled Plan' + SELECT + COUNT_BIG(deqs.query_plan_hash) AS total_plans + FROM sys.dm_exec_query_stats AS deqs ), many_plans AS ( - SELECT SUM(x.duplicate_plan_handles) AS duplicate_plan_handles - FROM ( - SELECT COUNT_BIG(DISTINCT plan_handle) AS duplicate_plan_handles + SELECT + SUM(x.duplicate_plan_hashes) AS duplicate_plan_hashes + FROM + ( + SELECT + COUNT_BIG(DISTINCT qs.query_plan_hash) AS duplicate_plan_hashes FROM sys.dm_exec_query_stats qs - CROSS APPLY sys.dm_exec_plan_attributes(qs.plan_handle) pa + CROSS APPLY sys.dm_exec_plan_attributes(qs.plan_handle) pa WHERE pa.attribute = N'dbid' - GROUP BY qs.query_hash, pa.value - HAVING COUNT_BIG(DISTINCT plan_handle) > 5 + GROUP BY + qs.query_plan_hash, + pa.value + HAVING COUNT_BIG(DISTINCT qs.query_plan_hash) > 5 ) AS x ), single_use_plans AS ( - SELECT COUNT_BIG(*) AS single_use_plan_count + SELECT + COUNT_BIG(*) AS single_use_plan_count FROM sys.dm_exec_cached_plans AS cp WHERE cp.usecounts = 1 AND cp.objtype = N'Adhoc' - AND EXISTS ( SELECT 1/0 - FROM sys.configurations AS c - WHERE c.name = N'optimize for ad hoc workloads' - AND c.value_in_use = 0 ) + AND EXISTS + ( + SELECT + 1/0 + FROM sys.configurations AS c + WHERE c.name = N'optimize for ad hoc workloads' + AND c.value_in_use = 0 + ) HAVING COUNT_BIG(*) > 1 ) -INSERT #plan_usage ( duplicate_plan_handles, percent_duplicate, single_use_plan_count, percent_single, total_plans, spid ) -SELECT m.duplicate_plan_handles, - CONVERT(DECIMAL(5,2), m.duplicate_plan_handles / (1. * NULLIF(t.total_plans, 0))) * 100. AS percent_duplicate, - s.single_use_plan_count, - CONVERT(DECIMAL(5,2), s.single_use_plan_count / (1. * NULLIF(t.total_plans, 0))) * 100. AS percent_single, - t.total_plans, - @@SPID -FROM many_plans AS m - CROSS APPLY single_use_plans AS s - CROSS APPLY total_plans AS t; +INSERT + #plan_usage +( + duplicate_plan_hashes, + percent_duplicate, + single_use_plan_count, + percent_single, + total_plans, + spid +) +SELECT + m.duplicate_plan_hashes, + CONVERT + ( + decimal(5,2), + m.duplicate_plan_hashes + / (1. * NULLIF(t.total_plans, 0)) + ) * 100. AS percent_duplicate, + s.single_use_plan_count, + CONVERT + ( + decimal(5,2), + s.single_use_plan_count + / (1. * NULLIF(t.total_plans, 0)) + ) * 100. AS percent_single, + t.total_plans, + @@SPID +FROM many_plans AS m +CROSS APPLY single_use_plans AS s +CROSS APPLY total_plans AS t; +/* +Erik Darling: + Quoting this out to see if the above query fixes the issue + 2021-05-17, Issue #2909 + UPDATE #plan_usage SET percent_duplicate = CASE WHEN percent_duplicate > 100 THEN 100 ELSE percent_duplicate END, percent_single = CASE WHEN percent_duplicate > 100 THEN 100 ELSE percent_duplicate END; +*/ SET @OnlySqlHandles = LTRIM(RTRIM(@OnlySqlHandles)) ; SET @OnlyQueryHashes = LTRIM(RTRIM(@OnlyQueryHashes)) ; From 59282faa8ef41afde7a14766255b0e61dd4d96ca Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Thu, 20 May 2021 06:10:57 +0000 Subject: [PATCH 190/662] 2900 sp_BlitzCache - Multiple Plans warning Now shows total of all plans in the cache, not just the ones in the top 10. --- sp_BlitzCache.sql | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sp_BlitzCache.sql b/sp_BlitzCache.sql index 6aad5835d..984d5f184 100644 --- a/sp_BlitzCache.sql +++ b/sp_BlitzCache.sql @@ -2833,8 +2833,8 @@ FROM SELECT DatabaseName = DB_NAME(CONVERT(int, pa.value)), - QueryPlanHash = - qs.query_plan_hash, + QueryHash = + qs.query_hash, number_of_plans = COUNT_BIG(qs.query_plan_hash), distinct_plan_count = @@ -2844,9 +2844,9 @@ FROM WHERE pa.attribute = 'dbid' GROUP BY DB_NAME(CONVERT(int, pa.value)), - qs.query_plan_hash + qs.query_hash ) AS x -WHERE ##BlitzCacheProcs.QueryPlanHash = x.QueryPlanHash +WHERE ##BlitzCacheProcs.QueryHash = x.QueryHash AND ##BlitzCacheProcs.DatabaseName = x.DatabaseName OPTION (RECOMPILE) ; From 0f8680a2e3eef2e3a35df7bdfc30fd7deb57ccbf Mon Sep 17 00:00:00 2001 From: Mike Scalise <36201112+MikeScalise@users.noreply.github.com> Date: Thu, 20 May 2021 08:59:22 -0400 Subject: [PATCH 191/662] Removed dependency on schedules for evaluating jobs w/o failure emails --- sp_Blitz.sql | 5 ----- 1 file changed, 5 deletions(-) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index 02df288d6..afb3765f8 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -3253,11 +3253,6 @@ AS 'The job ' + [name] + ' has not been set up to notify an operator if it fails.' AS Details FROM msdb.[dbo].[sysjobs] j - INNER JOIN ( SELECT DISTINCT - [job_id] - FROM [msdb].[dbo].[sysjobschedules] - WHERE next_run_date > 0 - ) s ON j.job_id = s.job_id WHERE j.enabled = 1 AND j.notify_email_operator_id = 0 AND j.notify_netsend_operator_id = 0 From 7a2d4cb9b4c7097c9ddfe8d6e78e453368c9c631 Mon Sep 17 00:00:00 2001 From: Erik Darling Date: Sat, 22 May 2021 10:23:46 -0400 Subject: [PATCH 192/662] Attempting to fix duplicate plan thing See [this comment](https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2909#issuecomment-846415337) for details --- sp_BlitzCache.sql | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/sp_BlitzCache.sql b/sp_BlitzCache.sql index dc036f1f2..7fea8e09e 100644 --- a/sp_BlitzCache.sql +++ b/sp_BlitzCache.sql @@ -1611,14 +1611,19 @@ WITH total_plans AS FROM ( SELECT - COUNT_BIG(DISTINCT qs.query_plan_hash) AS duplicate_plan_hashes + COUNT_BIG(qs.query_plan_hash) AS duplicate_plan_hashes FROM sys.dm_exec_query_stats qs + LEFT JOIN sys.dm_exec_procedure_stats ps + ON qs.sql_handle = ps.sql_handle CROSS APPLY sys.dm_exec_plan_attributes(qs.plan_handle) pa WHERE pa.attribute = N'dbid' + AND qs.query_plan_hash <> 0x0000000000000000 GROUP BY qs.query_plan_hash, - pa.value - HAVING COUNT_BIG(DISTINCT qs.query_plan_hash) > 5 + qs.query_hash, + ps.object_id, + pa.value + HAVING COUNT_BIG(qs.query_plan_hash) > 5 ) AS x ), single_use_plans AS @@ -1666,8 +1671,8 @@ SELECT t.total_plans, @@SPID FROM many_plans AS m -CROSS APPLY single_use_plans AS s -CROSS APPLY total_plans AS t; +CROSS JOIN single_use_plans AS s +CROSS JOIN total_plans AS t; /* From c93ffd059915975065525923b9844e49d0737a23 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Mon, 24 May 2021 08:09:24 +0000 Subject: [PATCH 193/662] sp_BlitzCache fixing Many Duplicate Plans warning Commenting out one group by. --- sp_BlitzCache.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sp_BlitzCache.sql b/sp_BlitzCache.sql index 7fea8e09e..bbb6fbd9b 100644 --- a/sp_BlitzCache.sql +++ b/sp_BlitzCache.sql @@ -1619,7 +1619,7 @@ WITH total_plans AS WHERE pa.attribute = N'dbid' AND qs.query_plan_hash <> 0x0000000000000000 GROUP BY - qs.query_plan_hash, + /* qs.query_plan_hash, BGO 20210524 commenting this out to fix #2909 */ qs.query_hash, ps.object_id, pa.value From 92726f0771f54e8eff05fb59ca286be075c655be Mon Sep 17 00:00:00 2001 From: Igor L Marques Date: Tue, 25 May 2021 08:31:43 -0400 Subject: [PATCH 194/662] Fix error: Msg 102, Level 15, State 1, Procedure sp_BlitzWho, Line 1148 [Batch Start Line 3] Incorrect syntax near ','. Msg 102, Level 15, State 1, Procedure sp_BlitzWho, Line 1247 [Batch Start Line 3] Incorrect syntax near ':'. Msg 105, Level 15, State 1, Procedure sp_BlitzWho, Line 1268 [Batch Start Line 3] Unclosed quotation mark after the character string ', @CheckDateOverride; END GO '. --- sp_BlitzWho.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sp_BlitzWho.sql b/sp_BlitzWho.sql index b90230e80..c0e805f71 100644 --- a/sp_BlitzWho.sql +++ b/sp_BlitzWho.sql @@ -1147,7 +1147,7 @@ IF @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @Output ,[database_name] ,[query_text] ,[query_plan]' - + CASE WHEN @ProductVersionMajor >= 11 THEN N',[live_query_plan]' ELSE N'' END + N' + + CASE WHEN @ProductVersionMajor >= 11 THEN N',[live_query_plan]' ELSE N'' END + N'' + CASE WHEN @ProductVersionMajor >= 11 THEN N',[cached_parameter_info]' ELSE N'' END + CASE WHEN @ProductVersionMajor >= 11 AND @ShowActualParameters = 1 THEN N',[Live_Parameter_Info]' ELSE N'' END + N' ,[query_cost] From 60f2047dd7c34d10f30c9040d5790c021cadade3 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Wed, 26 May 2021 05:37:56 +0000 Subject: [PATCH 195/662] sp_BlitzWho - removing unneccessary empty space --- sp_BlitzWho.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sp_BlitzWho.sql b/sp_BlitzWho.sql index c0e805f71..7138f276f 100644 --- a/sp_BlitzWho.sql +++ b/sp_BlitzWho.sql @@ -1147,7 +1147,7 @@ IF @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @Output ,[database_name] ,[query_text] ,[query_plan]' - + CASE WHEN @ProductVersionMajor >= 11 THEN N',[live_query_plan]' ELSE N'' END + N'' + + CASE WHEN @ProductVersionMajor >= 11 THEN N',[live_query_plan]' ELSE N'' END + CASE WHEN @ProductVersionMajor >= 11 THEN N',[cached_parameter_info]' ELSE N'' END + CASE WHEN @ProductVersionMajor >= 11 AND @ShowActualParameters = 1 THEN N',[Live_Parameter_Info]' ELSE N'' END + N' ,[query_cost] From 15369420e1e9e64b4324ed0332b52895b4ad6e43 Mon Sep 17 00:00:00 2001 From: Erik Darling Date: Wed, 26 May 2021 14:34:59 -0400 Subject: [PATCH 196/662] Add bigint conversion to sums Closes #2916 --- sp_BlitzLock.sql | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index 99e4868dd..4cb46b5c2 100644 --- a/sp_BlitzLock.sql +++ b/sp_BlitzLock.sql @@ -1164,8 +1164,8 @@ You need to use an Azure storage account, and the path has to look like this: ht SELECT DISTINCT PARSENAME(dow.object_name, 3) AS database_name, dow.object_name, - CONVERT(VARCHAR(10), (SUM(DISTINCT dp.wait_time) / 1000) / 86400) AS wait_days, - CONVERT(VARCHAR(20), DATEADD(SECOND, (SUM(DISTINCT dp.wait_time) / 1000), 0), 108) AS wait_time_hms + CONVERT(VARCHAR(10), (SUM(DISTINCT CONVERT(BIGINT, dp.wait_time)) / 1000) / 86400) AS wait_days, + CONVERT(VARCHAR(20), DATEADD(SECOND, (SUM(DISTINCT CONVERT(BIGINT, dp.wait_time)) / 1000), 0), 108) AS wait_time_hms FROM #deadlock_owner_waiter AS dow JOIN #deadlock_process AS dp ON (dp.id = dow.owner_id OR dp.victim_id = dow.waiter_id) @@ -1217,8 +1217,8 @@ You need to use an Azure storage account, and the path has to look like this: ht '-' AS object_name, 'Total database deadlock wait time' AS finding_group, 'This database has had ' - + CONVERT(VARCHAR(10), (SUM(DISTINCT wt.total_wait_time_ms) / 1000) / 86400) - + ':' + CONVERT(VARCHAR(20), DATEADD(SECOND, (SUM(DISTINCT wt.total_wait_time_ms) / 1000), 0), 108) + + CONVERT(VARCHAR(10), (SUM(DISTINCT CONVERT(BIGINT, wt.total_wait_time_ms)) / 1000) / 86400) + + ':' + CONVERT(VARCHAR(20), DATEADD(SECOND, (SUM(DISTINCT CONVERT(BIGINT, wt.total_wait_time_ms)) / 1000), 0), 108) + ' [d/h/m/s] of deadlock wait time.' FROM wait_time AS wt GROUP BY wt.database_name From 299de032afd3fdd8bc8065f1062534b29c819609 Mon Sep 17 00:00:00 2001 From: Erik Darling Date: Wed, 26 May 2021 14:41:55 -0400 Subject: [PATCH 197/662] Turn off plans in procs Closes #2911 --- sp_AllNightLog.sql | 1 + sp_AllNightLog_Setup.sql | 1 + sp_Blitz.sql | 1 + sp_BlitzAnalysis.sql | 1 + sp_BlitzBackups.sql | 1 + sp_BlitzCache.sql | 1 + sp_BlitzFirst.sql | 1 + sp_BlitzInMemoryOLTP.sql | 1 + sp_BlitzIndex.sql | 1 + sp_BlitzLock.sql | 1 + sp_BlitzQueryStore.sql | 1 + sp_BlitzWho.sql | 1 + sp_DatabaseRestore.sql | 1 + sp_ineachdb.sql | 1 + 14 files changed, 14 insertions(+) diff --git a/sp_AllNightLog.sql b/sp_AllNightLog.sql index a755cb0dc..b9efdeab2 100644 --- a/sp_AllNightLog.sql +++ b/sp_AllNightLog.sql @@ -26,6 +26,7 @@ ALTER PROCEDURE dbo.sp_AllNightLog WITH RECOMPILE AS SET NOCOUNT ON; +SET STATISTICS XML OFF; BEGIN; diff --git a/sp_AllNightLog_Setup.sql b/sp_AllNightLog_Setup.sql index 0fddb7aa4..e9c606227 100644 --- a/sp_AllNightLog_Setup.sql +++ b/sp_AllNightLog_Setup.sql @@ -33,6 +33,7 @@ ALTER PROCEDURE dbo.sp_AllNightLog_Setup WITH RECOMPILE AS SET NOCOUNT ON; +SET STATISTICS XML OFF; BEGIN; diff --git a/sp_Blitz.sql b/sp_Blitz.sql index 02df288d6..63abfea88 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -34,6 +34,7 @@ ALTER PROCEDURE [dbo].[sp_Blitz] WITH RECOMPILE AS SET NOCOUNT ON; + SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; diff --git a/sp_BlitzAnalysis.sql b/sp_BlitzAnalysis.sql index 3984bf29f..8d6594575 100644 --- a/sp_BlitzAnalysis.sql +++ b/sp_BlitzAnalysis.sql @@ -35,6 +35,7 @@ ALTER PROCEDURE [dbo].[sp_BlitzAnalysis] ( ) AS SET NOCOUNT ON; +SET STATISTICS XML OFF; SELECT @Version = '8.03', @VersionDate = '20210420'; diff --git a/sp_BlitzBackups.sql b/sp_BlitzBackups.sql index 9b35e8b12..7d71c7972 100755 --- a/sp_BlitzBackups.sql +++ b/sp_BlitzBackups.sql @@ -21,6 +21,7 @@ WITH RECOMPILE AS BEGIN SET NOCOUNT ON; + SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; SELECT @Version = '8.03', @VersionDate = '20210420'; diff --git a/sp_BlitzCache.sql b/sp_BlitzCache.sql index 984d5f184..65ba8927d 100644 --- a/sp_BlitzCache.sql +++ b/sp_BlitzCache.sql @@ -277,6 +277,7 @@ WITH RECOMPILE AS BEGIN SET NOCOUNT ON; +SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; SELECT @Version = '8.03', @VersionDate = '20210420'; diff --git a/sp_BlitzFirst.sql b/sp_BlitzFirst.sql index c3ebd14e9..4667e476f 100644 --- a/sp_BlitzFirst.sql +++ b/sp_BlitzFirst.sql @@ -43,6 +43,7 @@ ALTER PROCEDURE [dbo].[sp_BlitzFirst] AS BEGIN SET NOCOUNT ON; +SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; SELECT @Version = '8.03', @VersionDate = '20210420'; diff --git a/sp_BlitzInMemoryOLTP.sql b/sp_BlitzInMemoryOLTP.sql index 1018cb95d..bb66ec88d 100644 --- a/sp_BlitzInMemoryOLTP.sql +++ b/sp_BlitzInMemoryOLTP.sql @@ -93,6 +93,7 @@ END; BEGIN TRY SET NOCOUNT ON; + SET STATISTICS XML OFF; DECLARE @RunningOnAzureSQLDB BIT = 0; diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index 2ce118213..6e379eec2 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -44,6 +44,7 @@ ALTER PROCEDURE dbo.sp_BlitzIndex WITH RECOMPILE AS SET NOCOUNT ON; +SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; SELECT @Version = '8.03', @VersionDate = '20210420'; diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index 99e4868dd..41ed88649 100644 --- a/sp_BlitzLock.sql +++ b/sp_BlitzLock.sql @@ -30,6 +30,7 @@ AS BEGIN SET NOCOUNT ON; +SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; SELECT @Version = '8.03', @VersionDate = '20210420'; diff --git a/sp_BlitzQueryStore.sql b/sp_BlitzQueryStore.sql index 3a348f8a9..f005d3761 100644 --- a/sp_BlitzQueryStore.sql +++ b/sp_BlitzQueryStore.sql @@ -54,6 +54,7 @@ AS BEGIN /*First BEGIN*/ SET NOCOUNT ON; +SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; SELECT @Version = '8.03', @VersionDate = '20210420'; diff --git a/sp_BlitzWho.sql b/sp_BlitzWho.sql index 7138f276f..310e85e15 100644 --- a/sp_BlitzWho.sql +++ b/sp_BlitzWho.sql @@ -28,6 +28,7 @@ ALTER PROCEDURE dbo.sp_BlitzWho AS BEGIN SET NOCOUNT ON; + SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; SELECT @Version = '8.03', @VersionDate = '20210420'; diff --git a/sp_DatabaseRestore.sql b/sp_DatabaseRestore.sql index dd494c32f..943579063 100755 --- a/sp_DatabaseRestore.sql +++ b/sp_DatabaseRestore.sql @@ -37,6 +37,7 @@ ALTER PROCEDURE [dbo].[sp_DatabaseRestore] @VersionCheckMode BIT = 0 AS SET NOCOUNT ON; +SET STATISTICS XML OFF; /*Versioning details*/ diff --git a/sp_ineachdb.sql b/sp_ineachdb.sql index 061d28eb8..15b3379b8 100644 --- a/sp_ineachdb.sql +++ b/sp_ineachdb.sql @@ -33,6 +33,7 @@ ALTER PROCEDURE [dbo].[sp_ineachdb] AS BEGIN SET NOCOUNT ON; + SET STATISTICS XML OFF; SELECT @Version = '8.03', @VersionDate = '20210420'; From a9ce16248ea48feb1498902570cc7a9ba776a6c5 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Sat, 29 May 2021 05:37:49 +0000 Subject: [PATCH 198/662] #2860 sp_BlitzIndex columnstore partition ranges Removed IIF for SQL Server 2008 compatibility, moved the partition ranges column to be right after the partition number. --- sp_BlitzIndex.sql | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index 796be3493..6af9bdd7a 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -2777,8 +2777,11 @@ BEGIN SET @ColumnList = LEFT(@ColumnList, LEN(@ColumnList) - 1); SET @dsql = N'USE ' + QUOTENAME(@DatabaseName) + N'; - SELECT partition_number, row_group_id, total_rows, deleted_rows, ' + @ColumnList + IIF(@ShowPartitionRanges = 1, N', - COALESCE(range_start_op + '' '' + range_start + '' '', '''') + COALESCE(range_end_op + '' '' + range_end, '''') AS partition_range + SELECT partition_number, ' + + CASE WHEN @ShowPartitionRanges = 1 THEN N' COALESCE(range_start_op + '' '' + range_start + '' '', '''') + COALESCE(range_end_op + '' '' + range_end, '''') AS partition_range, ' ELSE N' ' END + + N' row_group_id, total_rows, deleted_rows, ' + + @ColumnList + + CASE WHEN @ShowPartitionRanges = 1 THEN N' FROM ( SELECT column_name, partition_number, row_group_id, total_rows, deleted_rows, details, range_start_op, @@ -2788,10 +2791,11 @@ BEGIN range_end_op, CASE WHEN format_type IS NULL THEN CAST(range_end_value AS NVARCHAR(4000)) - ELSE CONVERT(NVARCHAR(4000), range_end_value, format_type) END range_end', '') + N' + ELSE CONVERT(NVARCHAR(4000), range_end_value, format_type) END range_end' ELSE N' ' END + N' FROM ( SELECT c.name AS column_name, p.partition_number, rg.row_group_id, rg.total_rows, rg.deleted_rows, - details = CAST(seg.min_data_id AS VARCHAR(20)) + '' to '' + CAST(seg.max_data_id AS VARCHAR(20)) + '', '' + CAST(CAST((seg.on_disk_size / 1024.0 / 1024) AS DECIMAL(18,0)) AS VARCHAR(20)) + '' MB''' + IIF(@ShowPartitionRanges = 1, N', + details = CAST(seg.min_data_id AS VARCHAR(20)) + '' to '' + CAST(seg.max_data_id AS VARCHAR(20)) + '', '' + CAST(CAST((seg.on_disk_size / 1024.0 / 1024) AS DECIMAL(18,0)) AS VARCHAR(20)) + '' MB''' + + CASE WHEN @ShowPartitionRanges = 1 THEN N', CASE WHEN pp.system_type_id IN (40, 41, 42, 43, 58, 61) THEN 126 WHEN pp.system_type_id IN (59, 62) THEN 3 @@ -2800,20 +2804,21 @@ BEGIN CASE WHEN pf.boundary_value_on_right = 0 THEN ''>'' ELSE ''>='' END range_start_op, prvs.value range_start_value, CASE WHEN pf.boundary_value_on_right = 0 THEN ''<='' ELSE ''<'' END range_end_op, - prve.value range_end_value', '') + N' + prve.value range_end_value ' ELSE N' ' END + N' FROM ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_row_groups rg INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns c ON rg.object_id = c.object_id INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partitions p ON rg.object_id = p.object_id AND rg.partition_number = p.partition_number - INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.index_columns ic on ic.column_id = c.column_id AND ic.object_id = c.object_id AND ic.index_id = p.index_id' + IIF(@ShowPartitionRanges = 1, N' + INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.index_columns ic on ic.column_id = c.column_id AND ic.object_id = c.object_id AND ic.index_id = p.index_id ' + CASE WHEN @ShowPartitionRanges = 1 THEN N' LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.indexes i ON i.object_id = rg.object_id AND i.index_id = rg.index_id LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partition_schemes ps ON ps.data_space_id = i.data_space_id LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partition_functions pf ON pf.function_id = ps.function_id LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partition_parameters pp ON pp.function_id = pf.function_id LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partition_range_values prvs ON prvs.function_id = pf.function_id AND prvs.boundary_id = p.partition_number - 1 - LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partition_range_values prve ON prve.function_id = pf.function_id AND prve.boundary_id = p.partition_number', '') + N' - LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_segments seg ON p.partition_id = seg.partition_id AND ic.index_column_id = seg.column_id AND rg.row_group_id = seg.segment_id - WHERE rg.object_id = @ObjectID' + IIF(@ShowPartitionRanges = 1, N' - ) AS y', '') + N' + LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partition_range_values prve ON prve.function_id = pf.function_id AND prve.boundary_id = p.partition_number ' ELSE N' ' END + + N' LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_segments seg ON p.partition_id = seg.partition_id AND ic.index_column_id = seg.column_id AND rg.row_group_id = seg.segment_id + WHERE rg.object_id = @ObjectID' + + CASE WHEN @ShowPartitionRanges = 1 THEN N' + ) AS y ' ELSE N' ' END + N' ) AS x PIVOT (MAX(details) FOR column_name IN ( ' + @ColumnList + N')) AS pivot1 ORDER BY partition_number, row_group_id;'; From 7f29b6be829247cf69943473d956cf144e17f2b7 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Sun, 30 May 2021 08:18:08 +0000 Subject: [PATCH 199/662] 2021-05-30 release Updating dates and version numbers. --- Install-All-Scripts.sql | 457 +++++++++++++++--------- Install-Core-Blitz-No-Query-Store.sql | 386 ++++++++++++++------ Install-Core-Blitz-With-Query-Store.sql | 389 ++++++++++++++------ sp_AllNightLog.sql | 2 +- sp_AllNightLog_Setup.sql | 2 +- sp_Blitz.sql | 2 +- sp_BlitzAnalysis.sql | 2 +- sp_BlitzBackups.sql | 2 +- sp_BlitzCache.sql | 2 +- sp_BlitzFirst.sql | 2 +- sp_BlitzInMemoryOLTP.sql | 2 +- sp_BlitzIndex.sql | 2 +- sp_BlitzLock.sql | 2 +- sp_BlitzQueryStore.sql | 2 +- sp_BlitzWho.sql | 2 +- sp_DatabaseRestore.sql | 2 +- sp_ineachdb.sql | 2 +- 17 files changed, 854 insertions(+), 406 deletions(-) diff --git a/Install-All-Scripts.sql b/Install-All-Scripts.sql index 7654de90a..017fa71e0 100755 --- a/Install-All-Scripts.sql +++ b/Install-All-Scripts.sql @@ -26,11 +26,12 @@ ALTER PROCEDURE dbo.sp_AllNightLog WITH RECOMPILE AS SET NOCOUNT ON; +SET STATISTICS XML OFF; BEGIN; -SELECT @Version = '8.03', @VersionDate = '20210420'; +SELECT @Version = '8.04', @VersionDate = '20210530'; IF(@VersionCheckMode = 1) BEGIN @@ -1521,10 +1522,11 @@ ALTER PROCEDURE dbo.sp_AllNightLog_Setup WITH RECOMPILE AS SET NOCOUNT ON; +SET STATISTICS XML OFF; BEGIN; -SELECT @Version = '8.03', @VersionDate = '20210420'; +SELECT @Version = '8.04', @VersionDate = '20210530'; IF(@VersionCheckMode = 1) BEGIN @@ -2853,10 +2855,11 @@ ALTER PROCEDURE [dbo].[sp_Blitz] WITH RECOMPILE AS SET NOCOUNT ON; + SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.03', @VersionDate = '20210420'; + SELECT @Version = '8.04', @VersionDate = '20210530'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -3247,33 +3250,38 @@ AS AND db_id('rdsadmin') IS NOT NULL AND EXISTS(SELECT * FROM master.sys.all_objects WHERE name IN ('rds_startup_tasks', 'rds_help_revlogin', 'rds_hexadecimal', 'rds_failover_tracking', 'rds_database_tracking', 'rds_track_change')) BEGIN - INSERT INTO #SkipChecks (CheckID) VALUES (6); - INSERT INTO #SkipChecks (CheckID) VALUES (29); - INSERT INTO #SkipChecks (CheckID) VALUES (30); - INSERT INTO #SkipChecks (CheckID) VALUES (31); - INSERT INTO #SkipChecks (CheckID) VALUES (40); /* TempDB only has one data file */ - INSERT INTO #SkipChecks (CheckID) VALUES (57); - INSERT INTO #SkipChecks (CheckID) VALUES (59); - INSERT INTO #SkipChecks (CheckID) VALUES (61); - INSERT INTO #SkipChecks (CheckID) VALUES (62); - INSERT INTO #SkipChecks (CheckID) VALUES (68); - INSERT INTO #SkipChecks (CheckID) VALUES (69); - INSERT INTO #SkipChecks (CheckID) VALUES (73); - INSERT INTO #SkipChecks (CheckID) VALUES (79); - INSERT INTO #SkipChecks (CheckID) VALUES (92); - INSERT INTO #SkipChecks (CheckID) VALUES (94); - INSERT INTO #SkipChecks (CheckID) VALUES (96); - INSERT INTO #SkipChecks (CheckID) VALUES (98); + 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 (29); /* tables in model database created by users - not allowed */ + INSERT INTO #SkipChecks (CheckID) VALUES (40); /* TempDB only has one data file in RDS */ + INSERT INTO #SkipChecks (CheckID) VALUES (62); /* Database compatibility level - cannot change in RDS */ + INSERT INTO #SkipChecks (CheckID) VALUES (68); /*Check for the last good DBCC CHECKDB date - can't run DBCC DBINFO() */ + INSERT INTO #SkipChecks (CheckID) VALUES (69); /* High VLF check - requires DBCC LOGINFO permission */ + INSERT INTO #SkipChecks (CheckID) VALUES (73); /* No Failsafe Operator Configured check */ + INSERT INTO #SkipChecks (CheckID) VALUES (92); /* Drive info check - requires xp_Fixeddrives permission */ INSERT INTO #SkipChecks (CheckID) VALUES (100); /* Remote DAC disabled */ - INSERT INTO #SkipChecks (CheckID) VALUES (123); - INSERT INTO #SkipChecks (CheckID) VALUES (177); - INSERT INTO #SkipChecks (CheckID) VALUES (180); /* 180/181 are maintenance plans */ - INSERT INTO #SkipChecks (CheckID) VALUES (181); - INSERT INTO #SkipChecks (CheckID) VALUES (184); /* xp_readerrorlog checking for IFI */ - INSERT INTO #SkipChecks (CheckID) VALUES (211); /* xp_regread checking for power saving */ - INSERT INTO #SkipChecks (CheckID) VALUES (212); /* xp_regread */ - INSERT INTO #SkipChecks (CheckID) VALUES (219); + INSERT INTO #SkipChecks (CheckID) VALUES (177); /* Disabled Internal Monitoring Features check - requires dm_server_registry access */ + INSERT INTO #SkipChecks (CheckID) VALUES (180); /* 180/181 are maintenance plans checks - Maint plans not available in RDS*/ + INSERT INTO #SkipChecks (CheckID) VALUES (181); /*Find repetitive maintenance tasks*/ + + -- can check errorlog using rdsadmin.dbo.rds_read_error_log, so allow this check + --INSERT INTO #SkipChecks (CheckID) VALUES (193); /* xp_readerrorlog checking for IFI */ + + INSERT INTO #SkipChecks (CheckID) VALUES (211); /* xp_regread not allowed - checking for power saving */ + INSERT INTO #SkipChecks (CheckID) VALUES (212); /* xp_regread not allowed - checking for additional instances */ INSERT INTO #SkipChecks (CheckID) VALUES (2301); /* sp_validatelogins called by Invalid login defined with Windows Authentication */ + + -- Following are skipped due to limited permissions in msdb/SQLAgent in RDS + INSERT INTO #SkipChecks (CheckID) VALUES (30); /* SQL Server Agent alerts not configured */ + INSERT INTO #SkipChecks (CheckID) VALUES (31); /* check whether we have NO ENABLED operators */ + INSERT INTO #SkipChecks (CheckID) VALUES (57); /* SQL Agent Job Runs at Startup */ + INSERT INTO #SkipChecks (CheckID) VALUES (59); /* Alerts Configured without Follow Up */ + INSERT INTO #SkipChecks (CheckID) VALUES (61); /*SQL Server Agent alerts do not exist for severity levels 19 through 25*/ + INSERT INTO #SkipChecks (CheckID) VALUES (79); /* Shrink Database Job check */ + INSERT INTO #SkipChecks (CheckID) VALUES (94); /* job failure without operator notification check */ + INSERT INTO #SkipChecks (CheckID) VALUES (96); /* Agent alerts for corruption */ + INSERT INTO #SkipChecks (CheckID) VALUES (98); /* check for disabled alerts */ + INSERT INTO #SkipChecks (CheckID) VALUES (123); /* Agent Jobs Starting Simultaneously */ + INSERT INTO #SkipChecks (CheckID) VALUES (219); /* check for alerts that do NOT include event descriptions in their outputs via email/pager/net-send */ INSERT INTO #BlitzResults ( CheckID , Priority , @@ -6067,11 +6075,6 @@ AS 'The job ' + [name] + ' has not been set up to notify an operator if it fails.' AS Details FROM msdb.[dbo].[sysjobs] j - INNER JOIN ( SELECT DISTINCT - [job_id] - FROM [msdb].[dbo].[sysjobschedules] - WHERE next_run_date > 0 - ) s ON j.job_id = s.job_id WHERE j.enabled = 1 AND j.notify_email_operator_id = 0 AND j.notify_netsend_operator_id = 0 @@ -10949,16 +10952,30 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 */ IF NOT EXISTS ( SELECT 1 FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 184 ) - AND (@ProductVersionMajor >= 13) OR (@ProductVersionMajor = 12 AND @ProductVersionMinor >= 5000) + WHERE DatabaseName IS NULL AND CheckID = 193 ) + AND ((@ProductVersionMajor >= 13) OR (@ProductVersionMajor = 12 AND @ProductVersionMinor >= 5000)) BEGIN - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 184) WITH NOWAIT; + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 193) WITH NOWAIT; - INSERT INTO #ErrorLog - EXEC sys.xp_readerrorlog 0, 1, N'Database Instant File Initialization: enabled'; + -- If this is Amazon RDS, use rdsadmin.dbo.rds_read_error_log + IF LEFT(CAST(SERVERPROPERTY('ComputerNamePhysicalNetBIOS') AS VARCHAR(8000)), 8) = 'EC2AMAZ-' + AND LEFT(CAST(SERVERPROPERTY('MachineName') AS VARCHAR(8000)), 8) = 'EC2AMAZ-' + AND LEFT(CAST(SERVERPROPERTY('ServerName') AS VARCHAR(8000)), 8) = 'EC2AMAZ-' + AND db_id('rdsadmin') IS NOT NULL + AND EXISTS(SELECT * FROM master.sys.all_objects WHERE name IN ('rds_startup_tasks', 'rds_help_revlogin', 'rds_hexadecimal', 'rds_failover_tracking', 'rds_database_tracking', 'rds_track_change')) + BEGIN + INSERT INTO #ErrorLog + EXEC rdsadmin.dbo.rds_read_error_log 0, 1, N'Database Instant File Initialization: enabled'; + END + ELSE + BEGIN + INSERT INTO #ErrorLog + EXEC sys.xp_readerrorlog 0, 1, N'Database Instant File Initialization: enabled'; + END IF @@ROWCOUNT > 0 + begin INSERT INTO #BlitzResults ( CheckID , [Priority] , @@ -10974,6 +10991,37 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 'Instant File Initialization Enabled' AS [Finding] , 'https://www.brentozar.com/go/instant' AS [URL] , 'The service account has the Perform Volume Maintenance Tasks permission.'; + end + else -- if version of sql server has instant_file_initialization_enabled column in dm_server_services, check that too + -- in the event the error log has been cycled and the startup messages are not in the current error log + begin + if EXISTS ( SELECT * + FROM sys.all_objects o + INNER JOIN sys.all_columns c ON o.object_id = c.object_id + WHERE o.name = 'dm_server_services' + AND c.name = 'instant_file_initialization_enabled' ) + begin + INSERT INTO #BlitzResults + ( CheckID , + [Priority] , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT + 193 AS [CheckID] , + 250 AS [Priority] , + 'Server Info' AS [FindingsGroup] , + 'Instant File Initialization Enabled' AS [Finding] , + 'https://www.brentozar.com/go/instant' AS [URL] , + 'The service account has the Perform Volume Maintenance Tasks permission.' + where exists (select 1 FROM sys.dm_server_services + WHERE instant_file_initialization_enabled = 'Y' + AND filename LIKE '%sqlservr.exe%') + OPTION (RECOMPILE); + end; + end; END; /* Server Info - Instant File Initialization Not Enabled - Check 192 - SQL Server 2016 SP1 and newer */ @@ -12250,8 +12298,9 @@ ALTER PROCEDURE [dbo].[sp_BlitzAnalysis] ( ) AS SET NOCOUNT ON; +SET STATISTICS XML OFF; -SELECT @Version = '8.03', @VersionDate = '20210420'; +SELECT @Version = '8.04', @VersionDate = '20210530'; IF(@VersionCheckMode = 1) BEGIN @@ -13126,9 +13175,10 @@ WITH RECOMPILE AS BEGIN SET NOCOUNT ON; + SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.03', @VersionDate = '20210420'; + SELECT @Version = '8.04', @VersionDate = '20210530'; IF(@VersionCheckMode = 1) BEGIN @@ -14906,9 +14956,10 @@ WITH RECOMPILE AS BEGIN SET NOCOUNT ON; +SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.03', @VersionDate = '20210420'; +SELECT @Version = '8.04', @VersionDate = '20210530'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -16192,10 +16243,10 @@ database_id INT CREATE TABLE #plan_usage ( - duplicate_plan_handles BIGINT NULL, - percent_duplicate NUMERIC(7, 2) NULL, + duplicate_plan_hashes BIGINT NULL, + percent_duplicate DECIMAL(5, 2) NULL, single_use_plan_count BIGINT NULL, - percent_single NUMERIC(7, 2) NULL, + percent_single DECIMAL(5, 2) NULL, total_plans BIGINT NULL, spid INT ); @@ -16229,49 +16280,90 @@ OPTION (RECOMPILE); RAISERROR(N'Checking for single use plans and plans with many queries', 0, 1) WITH NOWAIT; WITH total_plans AS ( - SELECT COUNT_BIG(*) AS total_plans - FROM sys.dm_exec_cached_plans AS deqs - WHERE deqs.cacheobjtype = N'Compiled Plan' + SELECT + COUNT_BIG(deqs.query_plan_hash) AS total_plans + FROM sys.dm_exec_query_stats AS deqs ), many_plans AS ( - SELECT SUM(x.duplicate_plan_handles) AS duplicate_plan_handles - FROM ( - SELECT COUNT_BIG(DISTINCT plan_handle) AS duplicate_plan_handles + SELECT + SUM(x.duplicate_plan_hashes) AS duplicate_plan_hashes + FROM + ( + SELECT + COUNT_BIG(qs.query_plan_hash) AS duplicate_plan_hashes FROM sys.dm_exec_query_stats qs - CROSS APPLY sys.dm_exec_plan_attributes(qs.plan_handle) pa + LEFT JOIN sys.dm_exec_procedure_stats ps + ON qs.sql_handle = ps.sql_handle + CROSS APPLY sys.dm_exec_plan_attributes(qs.plan_handle) pa WHERE pa.attribute = N'dbid' - GROUP BY qs.query_hash, pa.value - HAVING COUNT_BIG(DISTINCT plan_handle) > 5 + AND qs.query_plan_hash <> 0x0000000000000000 + GROUP BY + /* qs.query_plan_hash, BGO 20210524 commenting this out to fix #2909 */ + qs.query_hash, + ps.object_id, + pa.value + HAVING COUNT_BIG(qs.query_plan_hash) > 5 ) AS x ), single_use_plans AS ( - SELECT COUNT_BIG(*) AS single_use_plan_count + SELECT + COUNT_BIG(*) AS single_use_plan_count FROM sys.dm_exec_cached_plans AS cp WHERE cp.usecounts = 1 AND cp.objtype = N'Adhoc' - AND EXISTS ( SELECT 1/0 - FROM sys.configurations AS c - WHERE c.name = N'optimize for ad hoc workloads' - AND c.value_in_use = 0 ) + AND EXISTS + ( + SELECT + 1/0 + FROM sys.configurations AS c + WHERE c.name = N'optimize for ad hoc workloads' + AND c.value_in_use = 0 + ) HAVING COUNT_BIG(*) > 1 ) -INSERT #plan_usage ( duplicate_plan_handles, percent_duplicate, single_use_plan_count, percent_single, total_plans, spid ) -SELECT m.duplicate_plan_handles, - CONVERT(DECIMAL(5,2), m.duplicate_plan_handles / (1. * NULLIF(t.total_plans, 0))) * 100. AS percent_duplicate, - s.single_use_plan_count, - CONVERT(DECIMAL(5,2), s.single_use_plan_count / (1. * NULLIF(t.total_plans, 0))) * 100. AS percent_single, - t.total_plans, - @@SPID -FROM many_plans AS m - CROSS APPLY single_use_plans AS s - CROSS APPLY total_plans AS t; +INSERT + #plan_usage +( + duplicate_plan_hashes, + percent_duplicate, + single_use_plan_count, + percent_single, + total_plans, + spid +) +SELECT + m.duplicate_plan_hashes, + CONVERT + ( + decimal(5,2), + m.duplicate_plan_hashes + / (1. * NULLIF(t.total_plans, 0)) + ) * 100. AS percent_duplicate, + s.single_use_plan_count, + CONVERT + ( + decimal(5,2), + s.single_use_plan_count + / (1. * NULLIF(t.total_plans, 0)) + ) * 100. AS percent_single, + t.total_plans, + @@SPID +FROM many_plans AS m +CROSS JOIN single_use_plans AS s +CROSS JOIN total_plans AS t; +/* +Erik Darling: + Quoting this out to see if the above query fixes the issue + 2021-05-17, Issue #2909 + UPDATE #plan_usage SET percent_duplicate = CASE WHEN percent_duplicate > 100 THEN 100 ELSE percent_duplicate END, percent_single = CASE WHEN percent_duplicate > 100 THEN 100 ELSE percent_duplicate END; +*/ SET @OnlySqlHandles = LTRIM(RTRIM(@OnlySqlHandles)) ; SET @OnlyQueryHashes = LTRIM(RTRIM(@OnlyQueryHashes)) ; @@ -16644,7 +16736,7 @@ IF @DurationFilter IS NOT NULL IF @MinutesBack IS NOT NULL BEGIN RAISERROR(N'Setting minutes back filter', 0, 1) WITH NOWAIT; - SET @body += N' AND DATEADD(MILLISECOND, (x.last_elapsed_time / 1000.), x.last_execution_time) >= DATEADD(MINUTE, @min_back, GETDATE()) ' + @nl ; + SET @body += N' AND DATEADD(SECOND, (x.last_elapsed_time / 1000000.), x.last_execution_time) >= DATEADD(MINUTE, @min_back, GETDATE()) ' + @nl ; END; IF @SlowlySearchPlansFor IS NOT NULL @@ -16763,7 +16855,7 @@ SELECT TOP (@Top) END AS WritesPerMinute, qs.cached_time AS PlanCreationTime, qs.last_execution_time AS LastExecutionTime, - DATEADD(MILLISECOND, (qs.last_elapsed_time / 1000.), qs.last_execution_time) AS LastCompletionTime, + DATEADD(SECOND, (qs.last_elapsed_time / 1000000.), qs.last_execution_time) AS LastCompletionTime, NULL AS StatementStartOffset, NULL AS StatementEndOffset, NULL AS PlanGenerationNum, @@ -16874,7 +16966,7 @@ BEGIN END AS WritesPerMinute, qs.creation_time AS PlanCreationTime, qs.last_execution_time AS LastExecutionTime, - DATEADD(MILLISECOND, (qs.last_elapsed_time / 1000.), qs.last_execution_time) AS LastCompletionTime, + DATEADD(SECOND, (qs.last_elapsed_time / 1000000.), qs.last_execution_time) AS LastCompletionTime, qs.statement_start_offset AS StatementStartOffset, qs.statement_end_offset AS StatementEndOffset, qs.plan_generation_num AS PlanGenerationNum, '; @@ -17457,15 +17549,23 @@ UPDATE ##BlitzCacheProcs SET NumberOfDistinctPlans = distinct_plan_count, NumberOfPlans = number_of_plans , plan_multiple_plans = CASE WHEN distinct_plan_count < number_of_plans THEN number_of_plans END -FROM ( - SELECT COUNT(DISTINCT QueryHash) AS distinct_plan_count, - COUNT(QueryHash) AS number_of_plans, - QueryHash, - DatabaseName - FROM ##BlitzCacheProcs - WHERE SPID = @@SPID - GROUP BY QueryHash, - DatabaseName +FROM + ( + SELECT + DatabaseName = + DB_NAME(CONVERT(int, pa.value)), + QueryHash = + qs.query_hash, + number_of_plans = + COUNT_BIG(qs.query_plan_hash), + distinct_plan_count = + COUNT_BIG(DISTINCT qs.query_plan_hash) + FROM sys.dm_exec_query_stats AS qs + CROSS APPLY sys.dm_exec_plan_attributes(qs.plan_handle) pa + WHERE pa.attribute = 'dbid' + GROUP BY + DB_NAME(CONVERT(int, pa.value)), + qs.query_hash ) AS x WHERE ##BlitzCacheProcs.QueryHash = x.QueryHash AND ##BlitzCacheProcs.DatabaseName = x.DatabaseName @@ -22105,6 +22205,7 @@ ALTER PROCEDURE dbo.sp_BlitzIndex @OutputTableName NVARCHAR(256) = NULL , @IncludeInactiveIndexes BIT = 0 /* Will skip indexes with no reads or writes */, @ShowAllMissingIndexRequests BIT = 0 /*Will make all missing index requests show up*/, + @ShowPartitionRanges BIT = 0 /* Will add partition range values column to columnstore visualization */, @SortOrder NVARCHAR(50) = NULL, /* Only affects @Mode = 2. */ @SortDirection NVARCHAR(4) = 'DESC', /* Only affects @Mode = 2. */ @Help TINYINT = 0, @@ -22115,9 +22216,10 @@ ALTER PROCEDURE dbo.sp_BlitzIndex WITH RECOMPILE AS SET NOCOUNT ON; +SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.03', @VersionDate = '20210420'; +SELECT @Version = '8.04', @VersionDate = '20210530'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -22196,6 +22298,7 @@ DECLARE @LineFeed NVARCHAR(5); DECLARE @DaysUptimeInsertValue NVARCHAR(256); DECLARE @DatabaseToIgnore NVARCHAR(MAX); DECLARE @ColumnList NVARCHAR(MAX); +DECLARE @PartitionCount INT; /* Let's get @SortOrder set to lower case here for comparisons later */ SET @SortOrder = REPLACE(LOWER(@SortOrder), N' ', N'_'); @@ -23745,6 +23848,7 @@ BEGIN TRY ORDER BY (q.user_seeks + q.user_scans) DESC, s.total_logical_reads DESC ) q2 CROSS APPLY sys.dm_exec_query_plan(q2.plan_handle) p + WHERE ig.index_group_handle = gs.group_handle ) ' END @@ -24189,12 +24293,12 @@ UPDATE #IndexSanity SET key_column_names = D1.key_column_names FROM #IndexSanity si CROSS APPLY ( SELECT RTRIM(STUFF( (SELECT N', ' + c.column_name - + N' {' + system_type_name + N' ' + - CASE max_length WHEN -1 THEN N'(max)' ELSE + + N' {' + system_type_name + + CASE max_length WHEN -1 THEN N' (max)' ELSE CASE - WHEN system_type_name IN (N'char',N'varchar',N'binary',N'varbinary') THEN N'(' + CAST(max_length AS NVARCHAR(20)) + N')' - WHEN system_type_name IN (N'nchar',N'nvarchar') THEN N'(' + CAST(max_length/2 AS NVARCHAR(20)) + N')' - ELSE '' + WHEN system_type_name IN (N'char',N'varchar',N'binary',N'varbinary') THEN N' (' + CAST(max_length AS NVARCHAR(20)) + N')' + WHEN system_type_name IN (N'nchar',N'nvarchar') THEN N' (' + CAST(max_length/2 AS NVARCHAR(20)) + N')' + ELSE N' ' + CAST(max_length AS NVARCHAR(50)) END END + N'}' @@ -24233,12 +24337,12 @@ FROM #IndexSanity si WHEN 1 THEN N' DESC' ELSE N'' END - + N' {' + system_type_name + N' ' + - CASE max_length WHEN -1 THEN N'(max)' ELSE + + N' {' + system_type_name + + CASE max_length WHEN -1 THEN N' (max)' ELSE CASE - WHEN system_type_name IN (N'char',N'varchar',N'binary',N'varbinary') THEN N'(' + CAST(max_length AS NVARCHAR(20)) + N')' - WHEN system_type_name IN (N'nchar',N'nvarchar') THEN N'(' + CAST(max_length/2 AS NVARCHAR(20)) + N')' - ELSE '' + WHEN system_type_name IN (N'char',N'varchar',N'binary',N'varbinary') THEN N' (' + CAST(max_length AS NVARCHAR(20)) + N')' + WHEN system_type_name IN (N'nchar',N'nvarchar') THEN N' (' + CAST(max_length/2 AS NVARCHAR(20)) + N')' + ELSE N' ' + CAST(max_length AS NVARCHAR(50)) END END + N'}' @@ -24278,7 +24382,15 @@ UPDATE #IndexSanity SET include_column_names = D3.include_column_names FROM #IndexSanity si CROSS APPLY ( SELECT RTRIM(STUFF( (SELECT N', ' + c.column_name - + N' {' + system_type_name + N' ' + CAST(max_length AS NVARCHAR(50)) + N'}' + + N' {' + system_type_name + + CASE max_length WHEN -1 THEN N' (max)' ELSE + CASE + WHEN system_type_name IN (N'char',N'varchar',N'binary',N'varbinary') THEN N' (' + CAST(max_length AS NVARCHAR(20)) + N')' + WHEN system_type_name IN (N'nchar',N'nvarchar') THEN N' (' + CAST(max_length/2 AS NVARCHAR(20)) + N')' + ELSE N' ' + CAST(max_length AS NVARCHAR(50)) + END + END + + N'}' FROM #IndexColumns c WHERE c.database_id= si.database_id AND c.schema_name = si.schema_name @@ -24815,6 +24927,7 @@ BEGIN SELECT @ColumnList = @ColumnList + column_name + N'', '' FROM DistinctColumns ORDER BY column_id; + SELECT @PartitionCount = COUNT(1) FROM ' + QUOTENAME(@DatabaseName) + N'.sys.partitions WHERE object_id = @ObjectID AND data_compression IN (3,4); END'; IF @Debug = 1 @@ -24831,27 +24944,62 @@ BEGIN PRINT SUBSTRING(@dsql, 36000, 40000); END; - EXEC sp_executesql @dsql, N'@ObjectID INT, @ColumnList NVARCHAR(MAX) OUTPUT', @ObjectID, @ColumnList OUTPUT; + EXEC sp_executesql @dsql, N'@ObjectID INT, @ColumnList NVARCHAR(MAX) OUTPUT, @PartitionCount INT OUTPUT', @ObjectID, @ColumnList OUTPUT, @PartitionCount OUTPUT; + + IF @PartitionCount < 2 + SET @ShowPartitionRanges = 0; IF @Debug = 1 - SELECT @ColumnList AS ColumnstoreColumnList; + SELECT @ColumnList AS ColumnstoreColumnList, @PartitionCount AS PartitionCount, @ShowPartitionRanges AS ShowPartitionRanges; IF @ColumnList <> '' BEGIN /* Remove the trailing comma */ SET @ColumnList = LEFT(@ColumnList, LEN(@ColumnList) - 1); - SET @dsql = N'USE ' + QUOTENAME(@DatabaseName) + N'; SELECT partition_number, row_group_id, total_rows, deleted_rows, ' + @ColumnList + N' + SET @dsql = N'USE ' + QUOTENAME(@DatabaseName) + N'; + SELECT partition_number, ' + + CASE WHEN @ShowPartitionRanges = 1 THEN N' COALESCE(range_start_op + '' '' + range_start + '' '', '''') + COALESCE(range_end_op + '' '' + range_end, '''') AS partition_range, ' ELSE N' ' END + + N' row_group_id, total_rows, deleted_rows, ' + + @ColumnList + + CASE WHEN @ShowPartitionRanges = 1 THEN N' FROM ( - SELECT c.name AS column_name, p.partition_number, - rg.row_group_id, rg.total_rows, rg.deleted_rows, - details = CAST(seg.min_data_id AS VARCHAR(20)) + '' to '' + CAST(seg.max_data_id AS VARCHAR(20)) + '', '' + CAST(CAST((seg.on_disk_size / 1024.0 / 1024) AS DECIMAL(18,0)) AS VARCHAR(20)) + '' MB'' - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_row_groups rg - INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns c ON rg.object_id = c.object_id - INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partitions p ON rg.object_id = p.object_id AND rg.partition_number = p.partition_number - INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.index_columns ic on ic.column_id = c.column_id AND ic.object_id = c.object_id AND ic.index_id = p.index_id - LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_segments seg ON p.partition_id = seg.partition_id AND ic.index_column_id = seg.column_id AND rg.row_group_id = seg.segment_id - WHERE rg.object_id = @ObjectID + SELECT column_name, partition_number, row_group_id, total_rows, deleted_rows, details, + range_start_op, + CASE + WHEN format_type IS NULL THEN CAST(range_start_value AS NVARCHAR(4000)) + ELSE CONVERT(NVARCHAR(4000), range_start_value, format_type) END range_start, + range_end_op, + CASE + WHEN format_type IS NULL THEN CAST(range_end_value AS NVARCHAR(4000)) + ELSE CONVERT(NVARCHAR(4000), range_end_value, format_type) END range_end' ELSE N' ' END + N' + FROM ( + SELECT c.name AS column_name, p.partition_number, rg.row_group_id, rg.total_rows, rg.deleted_rows, + details = CAST(seg.min_data_id AS VARCHAR(20)) + '' to '' + CAST(seg.max_data_id AS VARCHAR(20)) + '', '' + CAST(CAST((seg.on_disk_size / 1024.0 / 1024) AS DECIMAL(18,0)) AS VARCHAR(20)) + '' MB''' + + CASE WHEN @ShowPartitionRanges = 1 THEN N', + CASE + WHEN pp.system_type_id IN (40, 41, 42, 43, 58, 61) THEN 126 + WHEN pp.system_type_id IN (59, 62) THEN 3 + WHEN pp.system_type_id IN (60, 122) THEN 2 + ELSE NULL END format_type, + CASE WHEN pf.boundary_value_on_right = 0 THEN ''>'' ELSE ''>='' END range_start_op, + prvs.value range_start_value, + CASE WHEN pf.boundary_value_on_right = 0 THEN ''<='' ELSE ''<'' END range_end_op, + prve.value range_end_value ' ELSE N' ' END + N' + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_row_groups rg + INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns c ON rg.object_id = c.object_id + INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partitions p ON rg.object_id = p.object_id AND rg.partition_number = p.partition_number + INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.index_columns ic on ic.column_id = c.column_id AND ic.object_id = c.object_id AND ic.index_id = p.index_id ' + CASE WHEN @ShowPartitionRanges = 1 THEN N' + LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.indexes i ON i.object_id = rg.object_id AND i.index_id = rg.index_id + LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partition_schemes ps ON ps.data_space_id = i.data_space_id + LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partition_functions pf ON pf.function_id = ps.function_id + LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partition_parameters pp ON pp.function_id = pf.function_id + LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partition_range_values prvs ON prvs.function_id = pf.function_id AND prvs.boundary_id = p.partition_number - 1 + LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partition_range_values prve ON prve.function_id = pf.function_id AND prve.boundary_id = p.partition_number ' ELSE N' ' END + + N' LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_segments seg ON p.partition_id = seg.partition_id AND ic.index_column_id = seg.column_id AND rg.row_group_id = seg.segment_id + WHERE rg.object_id = @ObjectID' + + CASE WHEN @ShowPartitionRanges = 1 THEN N' + ) AS y ' ELSE N' ' END + N' ) AS x PIVOT (MAX(details) FOR column_name IN ( ' + @ColumnList + N')) AS pivot1 ORDER BY partition_number, row_group_id;'; @@ -27607,9 +27755,10 @@ AS BEGIN SET NOCOUNT ON; +SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.03', @VersionDate = '20210420'; +SELECT @Version = '8.04', @VersionDate = '20210530'; IF(@VersionCheckMode = 1) @@ -28741,8 +28890,8 @@ You need to use an Azure storage account, and the path has to look like this: ht SELECT DISTINCT PARSENAME(dow.object_name, 3) AS database_name, dow.object_name, - CONVERT(VARCHAR(10), (SUM(DISTINCT dp.wait_time) / 1000) / 86400) AS wait_days, - CONVERT(VARCHAR(20), DATEADD(SECOND, (SUM(DISTINCT dp.wait_time) / 1000), 0), 108) AS wait_time_hms + CONVERT(VARCHAR(10), (SUM(DISTINCT CONVERT(BIGINT, dp.wait_time)) / 1000) / 86400) AS wait_days, + CONVERT(VARCHAR(20), DATEADD(SECOND, (SUM(DISTINCT CONVERT(BIGINT, dp.wait_time)) / 1000), 0), 108) AS wait_time_hms FROM #deadlock_owner_waiter AS dow JOIN #deadlock_process AS dp ON (dp.id = dow.owner_id OR dp.victim_id = dow.waiter_id) @@ -28794,8 +28943,8 @@ You need to use an Azure storage account, and the path has to look like this: ht '-' AS object_name, 'Total database deadlock wait time' AS finding_group, 'This database has had ' - + CONVERT(VARCHAR(10), (SUM(DISTINCT wt.total_wait_time_ms) / 1000) / 86400) - + ':' + CONVERT(VARCHAR(20), DATEADD(SECOND, (SUM(DISTINCT wt.total_wait_time_ms) / 1000), 0), 108) + + CONVERT(VARCHAR(10), (SUM(DISTINCT CONVERT(BIGINT, wt.total_wait_time_ms)) / 1000) / 86400) + + ':' + CONVERT(VARCHAR(20), DATEADD(SECOND, (SUM(DISTINCT CONVERT(BIGINT, wt.total_wait_time_ms)) / 1000), 0), 108) + ' [d/h/m/s] of deadlock wait time.' FROM wait_time AS wt GROUP BY wt.database_name @@ -29413,9 +29562,10 @@ AS BEGIN /*First BEGIN*/ SET NOCOUNT ON; +SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.03', @VersionDate = '20210420'; +SELECT @Version = '8.04', @VersionDate = '20210530'; IF(@VersionCheckMode = 1) BEGIN RETURN; @@ -35141,9 +35291,10 @@ ALTER PROCEDURE dbo.sp_BlitzWho AS BEGIN SET NOCOUNT ON; + SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.03', @VersionDate = '20210420'; + SELECT @Version = '8.04', @VersionDate = '20210530'; IF(@VersionCheckMode = 1) BEGIN @@ -35663,6 +35814,7 @@ END SELECT @BlockingCheck = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + SET LOCK_TIMEOUT 1000; /* To avoid blocking on live query plans. See Github issue #2907. */ DECLARE @blocked TABLE ( dbid SMALLINT NOT NULL, @@ -36259,8 +36411,8 @@ IF @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @Output ,[database_name] ,[query_text] ,[query_plan]' - + CASE WHEN @ProductVersionMajor >= 11 THEN N',[live_query_plan]' ELSE N'' END + N' - ,[cached_parameter_info]' + + CASE WHEN @ProductVersionMajor >= 11 THEN N',[live_query_plan]' ELSE N'' END + + CASE WHEN @ProductVersionMajor >= 11 THEN N',[cached_parameter_info]' ELSE N'' END + CASE WHEN @ProductVersionMajor >= 11 AND @ShowActualParameters = 1 THEN N',[Live_Parameter_Info]' ELSE N'' END + N' ,[query_cost] ,[status] @@ -36424,10 +36576,11 @@ ALTER PROCEDURE [dbo].[sp_DatabaseRestore] @VersionCheckMode BIT = 0 AS SET NOCOUNT ON; +SET STATISTICS XML OFF; /*Versioning details*/ -SELECT @Version = '8.03', @VersionDate = '20210420'; +SELECT @Version = '8.04', @VersionDate = '20210530'; IF(@VersionCheckMode = 1) BEGIN @@ -37385,7 +37538,11 @@ BEGIN END /*End folder sanity check*/ -- Find latest diff backup - SELECT @LastDiffBackup = MAX(BackupFile) + SELECT @LastDiffBackup = MAX(CASE + WHEN @StopAt IS NULL OR REPLACE( RIGHT( REPLACE( BackupFile, RIGHT( BackupFile, PATINDEX( '%_[0-9][0-9]%', REVERSE( BackupFile ) ) ), '' ), 16 ), '_', '' ) <= @StopAt THEN + BackupFile + ELSE '' + END) FROM @FileList WHERE BackupFile LIKE N'%.bak' AND @@ -37632,10 +37789,21 @@ BEGIN END - +IF (@StopAt IS NOT NULL) +BEGIN + + IF @Execute = 'Y' OR @Debug = 1 RAISERROR('@OnlyLogsAfter is NOT NULL, deleting from @FileList', 0, 1) WITH NOWAIT; + + DELETE fl + FROM @FileList AS fl + WHERE BackupFile LIKE N'%.trn' + AND BackupFile LIKE N'%' + @Database + N'%' + AND REPLACE( RIGHT( REPLACE( fl.BackupFile, RIGHT( fl.BackupFile, PATINDEX( '%_[0-9][0-9]%', REVERSE( fl.BackupFile ) ) ), '' ), 16 ), '_', '' ) > @StopAt; + +END -- Check for log backups -IF(@StopAt IS NULL AND @OnlyLogsAfter IS NULL) +IF(@BackupDateTime IS NOT NULL AND @BackupDateTime <> '') BEGIN DELETE FROM @FileList WHERE BackupFile LIKE N'%.trn' @@ -37644,41 +37812,6 @@ IF(@StopAt IS NULL AND @OnlyLogsAfter IS NULL) END; -IF (@StopAt IS NULL AND @OnlyLogsAfter IS NOT NULL) - BEGIN - DELETE FROM @FileList - WHERE BackupFile LIKE N'%.trn' - AND BackupFile LIKE N'%' + @Database + N'%' - AND NOT (@ContinueLogs = 1 OR (@ContinueLogs = 0 AND REPLACE( RIGHT( REPLACE( BackupFile, RIGHT( BackupFile, PATINDEX( '%_[0-9][0-9]%', REVERSE( BackupFile ) ) ), '' ), 16 ), '_', '' ) >= @OnlyLogsAfter)); - END; - - -IF (@StopAt IS NOT NULL AND @OnlyLogsAfter IS NULL) - BEGIN - DELETE FROM @FileList - WHERE BackupFile LIKE N'%.trn' - AND BackupFile LIKE N'%' + @Database + N'%' - AND NOT (@ContinueLogs = 1 OR (@ContinueLogs = 0 AND REPLACE( RIGHT( REPLACE( BackupFile, RIGHT( BackupFile, PATINDEX( '%_[0-9][0-9]%', REVERSE( BackupFile ) ) ), '' ), 16 ), '_', '' ) >= @BackupDateTime) AND REPLACE( RIGHT( REPLACE( BackupFile, RIGHT( BackupFile, PATINDEX( '%_[0-9][0-9]%', REVERSE( BackupFile ) ) ), '' ), 16 ), '_', '' ) <= @StopAt) - AND NOT ((@ContinueLogs = 1 AND REPLACE( RIGHT( REPLACE( BackupFile, RIGHT( BackupFile, PATINDEX( '%_[0-9][0-9]%', REVERSE( BackupFile ) ) ), '' ), 16 ), '_', '' ) <= @StopAt) OR (@ContinueLogs = 0 AND REPLACE( RIGHT( REPLACE( BackupFile, RIGHT( BackupFile, PATINDEX( '%_[0-9][0-9]%', REVERSE( BackupFile ) ) ), '' ), 16 ), '_', '' ) >= @BackupDateTime) AND REPLACE( RIGHT( REPLACE( BackupFile, RIGHT( BackupFile, PATINDEX( '%_[0-9][0-9]%', REVERSE( BackupFile ) ) ), '' ), 16 ), '_', '' ) <= @StopAt) - - END; - -IF (@StopAt IS NOT NULL AND @OnlyLogsAfter IS NOT NULL) - BEGIN - DECLARE BackupFiles CURSOR FOR - SELECT BackupFile - FROM @FileList - WHERE BackupFile LIKE N'%.trn' - AND BackupFile LIKE N'%' + @Database + N'%' - AND (@ContinueLogs = 1 OR (@ContinueLogs = 0 AND REPLACE(LEFT(RIGHT(BackupFile, 19), 15),'_','') >= @BackupDateTime) AND REPLACE(LEFT(RIGHT(BackupFile, 19), 15),'_','') <= @StopAt) - AND ((@ContinueLogs = 1 AND REPLACE(LEFT(RIGHT(BackupFile, 19), 15),'_','') <= @StopAt) OR (@ContinueLogs = 0 AND REPLACE(LEFT(RIGHT(BackupFile, 19), 15),'_','') >= @BackupDateTime) AND REPLACE(LEFT(RIGHT(BackupFile, 19), 15),'_','') <= @StopAt) - AND (@ContinueLogs = 1 OR (@ContinueLogs = 0 AND REPLACE(LEFT(RIGHT(BackupFile, 19), 15),'_','') >= @OnlyLogsAfter)) - ORDER BY BackupFile; - - OPEN BackupFiles; - END; - - IF (@StandbyMode = 1) BEGIN IF (@StandbyUndoPath IS NULL) @@ -37912,8 +38045,9 @@ ALTER PROCEDURE [dbo].[sp_ineachdb] AS BEGIN SET NOCOUNT ON; + SET STATISTICS XML OFF; - SELECT @Version = '8.03', @VersionDate = '20210420'; + SELECT @Version = '8.04', @VersionDate = '20210530'; IF(@VersionCheckMode = 1) BEGIN @@ -38259,7 +38393,7 @@ DELETE FROM dbo.SqlServerVersions; INSERT INTO dbo.SqlServerVersions (MajorVersionNumber, MinorVersionNumber, Branch, [Url], ReleaseDate, MainstreamSupportEndDate, ExtendedSupportEndDate, MajorVersionName, MinorVersionName) VALUES - (15, 4123, 'CU10', 'https://support.microsoft.com/en-us/help/5001090', '2021-02-11', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 10'), + (15, 4123, 'CU10', 'https://support.microsoft.com/en-us/help/5001090', '2021-04-06', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 10'), (15, 4102, 'CU9', 'https://support.microsoft.com/en-us/help/5000642', '2021-02-11', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 9 '), (15, 4073, 'CU8 GDR', 'https://support.microsoft.com/en-us/help/4583459', '2021-01-12', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 8 GDR '), (15, 4073, 'CU8', 'https://support.microsoft.com/en-us/help/4577194', '2020-10-01', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 8 '), @@ -38272,6 +38406,7 @@ VALUES (15, 4003, 'CU1', 'https://support.microsoft.com/en-us/help/4527376', '2020-01-07', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 1 '), (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 '), + (14, 3391, 'RTM CU24', 'https://support.microsoft.com/en-us/help/5001228', '2021-05-10', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 24'), (14, 3381, 'RTM CU23', 'https://support.microsoft.com/en-us/help/5000685', '2021-02-25', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 23'), (14, 3370, 'RTM CU22 GDR', 'https://support.microsoft.com/en-us/help/4583457', '2021-01-12', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 22 GDR'), (14, 3356, 'RTM CU22', 'https://support.microsoft.com/en-us/help/4577467', '2020-09-10', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 22'), @@ -38297,6 +38432,7 @@ VALUES (14, 3008, 'RTM CU2', 'https://support.microsoft.com/en-us/help/4052574', '2017-11-28', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 2'), (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 '), + (13, 5888, 'SP2 CU17', 'https://support.microsoft.com/en-us/help/5001092', '2021-03-29', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 17'), (13, 5882, 'SP2 CU16', 'https://support.microsoft.com/en-us/help/5000645', '2021-02-11', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 16'), (13, 5865, 'SP2 CU15 GDR', 'https://support.microsoft.com/en-us/help/4583461', '2021-01-12', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 15 GDR'), (13, 5850, 'SP2 CU15', 'https://support.microsoft.com/en-us/help/4577775', '2020-09-28', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 15'), @@ -38646,9 +38782,10 @@ ALTER PROCEDURE [dbo].[sp_BlitzFirst] AS BEGIN SET NOCOUNT ON; +SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.03', @VersionDate = '20210420'; +SELECT @Version = '8.04', @VersionDate = '20210530'; IF(@VersionCheckMode = 1) BEGIN @@ -40891,10 +41028,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, ( SELECT CONVERT(VARCHAR(5), 100 - ca.c.value('.', 'INT')) AS system_idle, CONVERT(VARCHAR(30), rb.event_date) AS event_date, - CONVERT(VARCHAR(8000), rb.record) AS record + CONVERT(VARCHAR(8000), rb.record) AS record, + event_date as event_date_raw FROM ( SELECT CONVERT(XML, dorb.record) AS record, - DATEADD(ms, ( ts.ms_ticks - dorb.timestamp ), GETDATE()) AS event_date + DATEADD(ms, -( ts.ms_ticks - dorb.timestamp ), GETDATE()) AS event_date FROM sys.dm_os_ring_buffers AS dorb CROSS JOIN ( SELECT dosi.ms_ticks FROM sys.dm_os_sys_info AS dosi ) AS ts @@ -40919,10 +41057,10 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, + ' Ring buffer details: ' + y2.record FROM y AS y2 - ORDER BY y2.event_date DESC + ORDER BY y2.event_date_raw DESC FOR XML PATH(N''), TYPE ).value(N'.[1]', N'VARCHAR(MAX)'), 1, 1, N'') AS query FROM y - ORDER BY y.event_date DESC; + ORDER BY y.event_date_raw DESC; /* Highlight if non SQL processes are using >25% CPU - CheckID 28 */ @@ -43349,4 +43487,5 @@ EXEC sp_BlitzFirst , @OutputTableNameWaitStats = 'BlitzFirst_WaitStats' , @OutputTableNameBlitzCache = 'BlitzCache' , @OutputTableNameBlitzWho = 'BlitzWho' +, @OutputType = 'none' */ diff --git a/Install-Core-Blitz-No-Query-Store.sql b/Install-Core-Blitz-No-Query-Store.sql index 6607a89ff..465677839 100755 --- a/Install-Core-Blitz-No-Query-Store.sql +++ b/Install-Core-Blitz-No-Query-Store.sql @@ -34,10 +34,11 @@ ALTER PROCEDURE [dbo].[sp_Blitz] WITH RECOMPILE AS SET NOCOUNT ON; + SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.03', @VersionDate = '20210420'; + SELECT @Version = '8.04', @VersionDate = '20210530'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -428,33 +429,38 @@ AS AND db_id('rdsadmin') IS NOT NULL AND EXISTS(SELECT * FROM master.sys.all_objects WHERE name IN ('rds_startup_tasks', 'rds_help_revlogin', 'rds_hexadecimal', 'rds_failover_tracking', 'rds_database_tracking', 'rds_track_change')) BEGIN - INSERT INTO #SkipChecks (CheckID) VALUES (6); - INSERT INTO #SkipChecks (CheckID) VALUES (29); - INSERT INTO #SkipChecks (CheckID) VALUES (30); - INSERT INTO #SkipChecks (CheckID) VALUES (31); - INSERT INTO #SkipChecks (CheckID) VALUES (40); /* TempDB only has one data file */ - INSERT INTO #SkipChecks (CheckID) VALUES (57); - INSERT INTO #SkipChecks (CheckID) VALUES (59); - INSERT INTO #SkipChecks (CheckID) VALUES (61); - INSERT INTO #SkipChecks (CheckID) VALUES (62); - INSERT INTO #SkipChecks (CheckID) VALUES (68); - INSERT INTO #SkipChecks (CheckID) VALUES (69); - INSERT INTO #SkipChecks (CheckID) VALUES (73); - INSERT INTO #SkipChecks (CheckID) VALUES (79); - INSERT INTO #SkipChecks (CheckID) VALUES (92); - INSERT INTO #SkipChecks (CheckID) VALUES (94); - INSERT INTO #SkipChecks (CheckID) VALUES (96); - INSERT INTO #SkipChecks (CheckID) VALUES (98); + 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 (29); /* tables in model database created by users - not allowed */ + INSERT INTO #SkipChecks (CheckID) VALUES (40); /* TempDB only has one data file in RDS */ + INSERT INTO #SkipChecks (CheckID) VALUES (62); /* Database compatibility level - cannot change in RDS */ + INSERT INTO #SkipChecks (CheckID) VALUES (68); /*Check for the last good DBCC CHECKDB date - can't run DBCC DBINFO() */ + INSERT INTO #SkipChecks (CheckID) VALUES (69); /* High VLF check - requires DBCC LOGINFO permission */ + INSERT INTO #SkipChecks (CheckID) VALUES (73); /* No Failsafe Operator Configured check */ + INSERT INTO #SkipChecks (CheckID) VALUES (92); /* Drive info check - requires xp_Fixeddrives permission */ INSERT INTO #SkipChecks (CheckID) VALUES (100); /* Remote DAC disabled */ - INSERT INTO #SkipChecks (CheckID) VALUES (123); - INSERT INTO #SkipChecks (CheckID) VALUES (177); - INSERT INTO #SkipChecks (CheckID) VALUES (180); /* 180/181 are maintenance plans */ - INSERT INTO #SkipChecks (CheckID) VALUES (181); - INSERT INTO #SkipChecks (CheckID) VALUES (184); /* xp_readerrorlog checking for IFI */ - INSERT INTO #SkipChecks (CheckID) VALUES (211); /* xp_regread checking for power saving */ - INSERT INTO #SkipChecks (CheckID) VALUES (212); /* xp_regread */ - INSERT INTO #SkipChecks (CheckID) VALUES (219); + INSERT INTO #SkipChecks (CheckID) VALUES (177); /* Disabled Internal Monitoring Features check - requires dm_server_registry access */ + INSERT INTO #SkipChecks (CheckID) VALUES (180); /* 180/181 are maintenance plans checks - Maint plans not available in RDS*/ + INSERT INTO #SkipChecks (CheckID) VALUES (181); /*Find repetitive maintenance tasks*/ + + -- can check errorlog using rdsadmin.dbo.rds_read_error_log, so allow this check + --INSERT INTO #SkipChecks (CheckID) VALUES (193); /* xp_readerrorlog checking for IFI */ + + INSERT INTO #SkipChecks (CheckID) VALUES (211); /* xp_regread not allowed - checking for power saving */ + INSERT INTO #SkipChecks (CheckID) VALUES (212); /* xp_regread not allowed - checking for additional instances */ INSERT INTO #SkipChecks (CheckID) VALUES (2301); /* sp_validatelogins called by Invalid login defined with Windows Authentication */ + + -- Following are skipped due to limited permissions in msdb/SQLAgent in RDS + INSERT INTO #SkipChecks (CheckID) VALUES (30); /* SQL Server Agent alerts not configured */ + INSERT INTO #SkipChecks (CheckID) VALUES (31); /* check whether we have NO ENABLED operators */ + INSERT INTO #SkipChecks (CheckID) VALUES (57); /* SQL Agent Job Runs at Startup */ + INSERT INTO #SkipChecks (CheckID) VALUES (59); /* Alerts Configured without Follow Up */ + INSERT INTO #SkipChecks (CheckID) VALUES (61); /*SQL Server Agent alerts do not exist for severity levels 19 through 25*/ + INSERT INTO #SkipChecks (CheckID) VALUES (79); /* Shrink Database Job check */ + INSERT INTO #SkipChecks (CheckID) VALUES (94); /* job failure without operator notification check */ + INSERT INTO #SkipChecks (CheckID) VALUES (96); /* Agent alerts for corruption */ + INSERT INTO #SkipChecks (CheckID) VALUES (98); /* check for disabled alerts */ + INSERT INTO #SkipChecks (CheckID) VALUES (123); /* Agent Jobs Starting Simultaneously */ + INSERT INTO #SkipChecks (CheckID) VALUES (219); /* check for alerts that do NOT include event descriptions in their outputs via email/pager/net-send */ INSERT INTO #BlitzResults ( CheckID , Priority , @@ -3248,11 +3254,6 @@ AS 'The job ' + [name] + ' has not been set up to notify an operator if it fails.' AS Details FROM msdb.[dbo].[sysjobs] j - INNER JOIN ( SELECT DISTINCT - [job_id] - FROM [msdb].[dbo].[sysjobschedules] - WHERE next_run_date > 0 - ) s ON j.job_id = s.job_id WHERE j.enabled = 1 AND j.notify_email_operator_id = 0 AND j.notify_netsend_operator_id = 0 @@ -8130,16 +8131,30 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 */ IF NOT EXISTS ( SELECT 1 FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 184 ) - AND (@ProductVersionMajor >= 13) OR (@ProductVersionMajor = 12 AND @ProductVersionMinor >= 5000) + WHERE DatabaseName IS NULL AND CheckID = 193 ) + AND ((@ProductVersionMajor >= 13) OR (@ProductVersionMajor = 12 AND @ProductVersionMinor >= 5000)) BEGIN - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 184) WITH NOWAIT; + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 193) WITH NOWAIT; - INSERT INTO #ErrorLog - EXEC sys.xp_readerrorlog 0, 1, N'Database Instant File Initialization: enabled'; + -- If this is Amazon RDS, use rdsadmin.dbo.rds_read_error_log + IF LEFT(CAST(SERVERPROPERTY('ComputerNamePhysicalNetBIOS') AS VARCHAR(8000)), 8) = 'EC2AMAZ-' + AND LEFT(CAST(SERVERPROPERTY('MachineName') AS VARCHAR(8000)), 8) = 'EC2AMAZ-' + AND LEFT(CAST(SERVERPROPERTY('ServerName') AS VARCHAR(8000)), 8) = 'EC2AMAZ-' + AND db_id('rdsadmin') IS NOT NULL + AND EXISTS(SELECT * FROM master.sys.all_objects WHERE name IN ('rds_startup_tasks', 'rds_help_revlogin', 'rds_hexadecimal', 'rds_failover_tracking', 'rds_database_tracking', 'rds_track_change')) + BEGIN + INSERT INTO #ErrorLog + EXEC rdsadmin.dbo.rds_read_error_log 0, 1, N'Database Instant File Initialization: enabled'; + END + ELSE + BEGIN + INSERT INTO #ErrorLog + EXEC sys.xp_readerrorlog 0, 1, N'Database Instant File Initialization: enabled'; + END IF @@ROWCOUNT > 0 + begin INSERT INTO #BlitzResults ( CheckID , [Priority] , @@ -8155,6 +8170,37 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 'Instant File Initialization Enabled' AS [Finding] , 'https://www.brentozar.com/go/instant' AS [URL] , 'The service account has the Perform Volume Maintenance Tasks permission.'; + end + else -- if version of sql server has instant_file_initialization_enabled column in dm_server_services, check that too + -- in the event the error log has been cycled and the startup messages are not in the current error log + begin + if EXISTS ( SELECT * + FROM sys.all_objects o + INNER JOIN sys.all_columns c ON o.object_id = c.object_id + WHERE o.name = 'dm_server_services' + AND c.name = 'instant_file_initialization_enabled' ) + begin + INSERT INTO #BlitzResults + ( CheckID , + [Priority] , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT + 193 AS [CheckID] , + 250 AS [Priority] , + 'Server Info' AS [FindingsGroup] , + 'Instant File Initialization Enabled' AS [Finding] , + 'https://www.brentozar.com/go/instant' AS [URL] , + 'The service account has the Perform Volume Maintenance Tasks permission.' + where exists (select 1 FROM sys.dm_server_services + WHERE instant_file_initialization_enabled = 'Y' + AND filename LIKE '%sqlservr.exe%') + OPTION (RECOMPILE); + end; + end; END; /* Server Info - Instant File Initialization Not Enabled - Check 192 - SQL Server 2016 SP1 and newer */ @@ -9431,8 +9477,9 @@ ALTER PROCEDURE [dbo].[sp_BlitzAnalysis] ( ) AS SET NOCOUNT ON; +SET STATISTICS XML OFF; -SELECT @Version = '8.03', @VersionDate = '20210420'; +SELECT @Version = '8.04', @VersionDate = '20210530'; IF(@VersionCheckMode = 1) BEGIN @@ -10307,9 +10354,10 @@ WITH RECOMPILE AS BEGIN SET NOCOUNT ON; + SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.03', @VersionDate = '20210420'; + SELECT @Version = '8.04', @VersionDate = '20210530'; IF(@VersionCheckMode = 1) BEGIN @@ -12087,9 +12135,10 @@ WITH RECOMPILE AS BEGIN SET NOCOUNT ON; +SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.03', @VersionDate = '20210420'; +SELECT @Version = '8.04', @VersionDate = '20210530'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -13373,10 +13422,10 @@ database_id INT CREATE TABLE #plan_usage ( - duplicate_plan_handles BIGINT NULL, - percent_duplicate NUMERIC(7, 2) NULL, + duplicate_plan_hashes BIGINT NULL, + percent_duplicate DECIMAL(5, 2) NULL, single_use_plan_count BIGINT NULL, - percent_single NUMERIC(7, 2) NULL, + percent_single DECIMAL(5, 2) NULL, total_plans BIGINT NULL, spid INT ); @@ -13410,49 +13459,90 @@ OPTION (RECOMPILE); RAISERROR(N'Checking for single use plans and plans with many queries', 0, 1) WITH NOWAIT; WITH total_plans AS ( - SELECT COUNT_BIG(*) AS total_plans - FROM sys.dm_exec_cached_plans AS deqs - WHERE deqs.cacheobjtype = N'Compiled Plan' + SELECT + COUNT_BIG(deqs.query_plan_hash) AS total_plans + FROM sys.dm_exec_query_stats AS deqs ), many_plans AS ( - SELECT SUM(x.duplicate_plan_handles) AS duplicate_plan_handles - FROM ( - SELECT COUNT_BIG(DISTINCT plan_handle) AS duplicate_plan_handles + SELECT + SUM(x.duplicate_plan_hashes) AS duplicate_plan_hashes + FROM + ( + SELECT + COUNT_BIG(qs.query_plan_hash) AS duplicate_plan_hashes FROM sys.dm_exec_query_stats qs - CROSS APPLY sys.dm_exec_plan_attributes(qs.plan_handle) pa + LEFT JOIN sys.dm_exec_procedure_stats ps + ON qs.sql_handle = ps.sql_handle + CROSS APPLY sys.dm_exec_plan_attributes(qs.plan_handle) pa WHERE pa.attribute = N'dbid' - GROUP BY qs.query_hash, pa.value - HAVING COUNT_BIG(DISTINCT plan_handle) > 5 + AND qs.query_plan_hash <> 0x0000000000000000 + GROUP BY + /* qs.query_plan_hash, BGO 20210524 commenting this out to fix #2909 */ + qs.query_hash, + ps.object_id, + pa.value + HAVING COUNT_BIG(qs.query_plan_hash) > 5 ) AS x ), single_use_plans AS ( - SELECT COUNT_BIG(*) AS single_use_plan_count + SELECT + COUNT_BIG(*) AS single_use_plan_count FROM sys.dm_exec_cached_plans AS cp WHERE cp.usecounts = 1 AND cp.objtype = N'Adhoc' - AND EXISTS ( SELECT 1/0 - FROM sys.configurations AS c - WHERE c.name = N'optimize for ad hoc workloads' - AND c.value_in_use = 0 ) + AND EXISTS + ( + SELECT + 1/0 + FROM sys.configurations AS c + WHERE c.name = N'optimize for ad hoc workloads' + AND c.value_in_use = 0 + ) HAVING COUNT_BIG(*) > 1 ) -INSERT #plan_usage ( duplicate_plan_handles, percent_duplicate, single_use_plan_count, percent_single, total_plans, spid ) -SELECT m.duplicate_plan_handles, - CONVERT(DECIMAL(5,2), m.duplicate_plan_handles / (1. * NULLIF(t.total_plans, 0))) * 100. AS percent_duplicate, - s.single_use_plan_count, - CONVERT(DECIMAL(5,2), s.single_use_plan_count / (1. * NULLIF(t.total_plans, 0))) * 100. AS percent_single, - t.total_plans, - @@SPID -FROM many_plans AS m - CROSS APPLY single_use_plans AS s - CROSS APPLY total_plans AS t; +INSERT + #plan_usage +( + duplicate_plan_hashes, + percent_duplicate, + single_use_plan_count, + percent_single, + total_plans, + spid +) +SELECT + m.duplicate_plan_hashes, + CONVERT + ( + decimal(5,2), + m.duplicate_plan_hashes + / (1. * NULLIF(t.total_plans, 0)) + ) * 100. AS percent_duplicate, + s.single_use_plan_count, + CONVERT + ( + decimal(5,2), + s.single_use_plan_count + / (1. * NULLIF(t.total_plans, 0)) + ) * 100. AS percent_single, + t.total_plans, + @@SPID +FROM many_plans AS m +CROSS JOIN single_use_plans AS s +CROSS JOIN total_plans AS t; +/* +Erik Darling: + Quoting this out to see if the above query fixes the issue + 2021-05-17, Issue #2909 + UPDATE #plan_usage SET percent_duplicate = CASE WHEN percent_duplicate > 100 THEN 100 ELSE percent_duplicate END, percent_single = CASE WHEN percent_duplicate > 100 THEN 100 ELSE percent_duplicate END; +*/ SET @OnlySqlHandles = LTRIM(RTRIM(@OnlySqlHandles)) ; SET @OnlyQueryHashes = LTRIM(RTRIM(@OnlyQueryHashes)) ; @@ -13825,7 +13915,7 @@ IF @DurationFilter IS NOT NULL IF @MinutesBack IS NOT NULL BEGIN RAISERROR(N'Setting minutes back filter', 0, 1) WITH NOWAIT; - SET @body += N' AND DATEADD(MILLISECOND, (x.last_elapsed_time / 1000.), x.last_execution_time) >= DATEADD(MINUTE, @min_back, GETDATE()) ' + @nl ; + SET @body += N' AND DATEADD(SECOND, (x.last_elapsed_time / 1000000.), x.last_execution_time) >= DATEADD(MINUTE, @min_back, GETDATE()) ' + @nl ; END; IF @SlowlySearchPlansFor IS NOT NULL @@ -13944,7 +14034,7 @@ SELECT TOP (@Top) END AS WritesPerMinute, qs.cached_time AS PlanCreationTime, qs.last_execution_time AS LastExecutionTime, - DATEADD(MILLISECOND, (qs.last_elapsed_time / 1000.), qs.last_execution_time) AS LastCompletionTime, + DATEADD(SECOND, (qs.last_elapsed_time / 1000000.), qs.last_execution_time) AS LastCompletionTime, NULL AS StatementStartOffset, NULL AS StatementEndOffset, NULL AS PlanGenerationNum, @@ -14055,7 +14145,7 @@ BEGIN END AS WritesPerMinute, qs.creation_time AS PlanCreationTime, qs.last_execution_time AS LastExecutionTime, - DATEADD(MILLISECOND, (qs.last_elapsed_time / 1000.), qs.last_execution_time) AS LastCompletionTime, + DATEADD(SECOND, (qs.last_elapsed_time / 1000000.), qs.last_execution_time) AS LastCompletionTime, qs.statement_start_offset AS StatementStartOffset, qs.statement_end_offset AS StatementEndOffset, qs.plan_generation_num AS PlanGenerationNum, '; @@ -14638,15 +14728,23 @@ UPDATE ##BlitzCacheProcs SET NumberOfDistinctPlans = distinct_plan_count, NumberOfPlans = number_of_plans , plan_multiple_plans = CASE WHEN distinct_plan_count < number_of_plans THEN number_of_plans END -FROM ( - SELECT COUNT(DISTINCT QueryHash) AS distinct_plan_count, - COUNT(QueryHash) AS number_of_plans, - QueryHash, - DatabaseName - FROM ##BlitzCacheProcs - WHERE SPID = @@SPID - GROUP BY QueryHash, - DatabaseName +FROM + ( + SELECT + DatabaseName = + DB_NAME(CONVERT(int, pa.value)), + QueryHash = + qs.query_hash, + number_of_plans = + COUNT_BIG(qs.query_plan_hash), + distinct_plan_count = + COUNT_BIG(DISTINCT qs.query_plan_hash) + FROM sys.dm_exec_query_stats AS qs + CROSS APPLY sys.dm_exec_plan_attributes(qs.plan_handle) pa + WHERE pa.attribute = 'dbid' + GROUP BY + DB_NAME(CONVERT(int, pa.value)), + qs.query_hash ) AS x WHERE ##BlitzCacheProcs.QueryHash = x.QueryHash AND ##BlitzCacheProcs.DatabaseName = x.DatabaseName @@ -19286,6 +19384,7 @@ ALTER PROCEDURE dbo.sp_BlitzIndex @OutputTableName NVARCHAR(256) = NULL , @IncludeInactiveIndexes BIT = 0 /* Will skip indexes with no reads or writes */, @ShowAllMissingIndexRequests BIT = 0 /*Will make all missing index requests show up*/, + @ShowPartitionRanges BIT = 0 /* Will add partition range values column to columnstore visualization */, @SortOrder NVARCHAR(50) = NULL, /* Only affects @Mode = 2. */ @SortDirection NVARCHAR(4) = 'DESC', /* Only affects @Mode = 2. */ @Help TINYINT = 0, @@ -19296,9 +19395,10 @@ ALTER PROCEDURE dbo.sp_BlitzIndex WITH RECOMPILE AS SET NOCOUNT ON; +SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.03', @VersionDate = '20210420'; +SELECT @Version = '8.04', @VersionDate = '20210530'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -19377,6 +19477,7 @@ DECLARE @LineFeed NVARCHAR(5); DECLARE @DaysUptimeInsertValue NVARCHAR(256); DECLARE @DatabaseToIgnore NVARCHAR(MAX); DECLARE @ColumnList NVARCHAR(MAX); +DECLARE @PartitionCount INT; /* Let's get @SortOrder set to lower case here for comparisons later */ SET @SortOrder = REPLACE(LOWER(@SortOrder), N' ', N'_'); @@ -20926,6 +21027,7 @@ BEGIN TRY ORDER BY (q.user_seeks + q.user_scans) DESC, s.total_logical_reads DESC ) q2 CROSS APPLY sys.dm_exec_query_plan(q2.plan_handle) p + WHERE ig.index_group_handle = gs.group_handle ) ' END @@ -21370,12 +21472,12 @@ UPDATE #IndexSanity SET key_column_names = D1.key_column_names FROM #IndexSanity si CROSS APPLY ( SELECT RTRIM(STUFF( (SELECT N', ' + c.column_name - + N' {' + system_type_name + N' ' + - CASE max_length WHEN -1 THEN N'(max)' ELSE + + N' {' + system_type_name + + CASE max_length WHEN -1 THEN N' (max)' ELSE CASE - WHEN system_type_name IN (N'char',N'varchar',N'binary',N'varbinary') THEN N'(' + CAST(max_length AS NVARCHAR(20)) + N')' - WHEN system_type_name IN (N'nchar',N'nvarchar') THEN N'(' + CAST(max_length/2 AS NVARCHAR(20)) + N')' - ELSE '' + WHEN system_type_name IN (N'char',N'varchar',N'binary',N'varbinary') THEN N' (' + CAST(max_length AS NVARCHAR(20)) + N')' + WHEN system_type_name IN (N'nchar',N'nvarchar') THEN N' (' + CAST(max_length/2 AS NVARCHAR(20)) + N')' + ELSE N' ' + CAST(max_length AS NVARCHAR(50)) END END + N'}' @@ -21414,12 +21516,12 @@ FROM #IndexSanity si WHEN 1 THEN N' DESC' ELSE N'' END - + N' {' + system_type_name + N' ' + - CASE max_length WHEN -1 THEN N'(max)' ELSE + + N' {' + system_type_name + + CASE max_length WHEN -1 THEN N' (max)' ELSE CASE - WHEN system_type_name IN (N'char',N'varchar',N'binary',N'varbinary') THEN N'(' + CAST(max_length AS NVARCHAR(20)) + N')' - WHEN system_type_name IN (N'nchar',N'nvarchar') THEN N'(' + CAST(max_length/2 AS NVARCHAR(20)) + N')' - ELSE '' + WHEN system_type_name IN (N'char',N'varchar',N'binary',N'varbinary') THEN N' (' + CAST(max_length AS NVARCHAR(20)) + N')' + WHEN system_type_name IN (N'nchar',N'nvarchar') THEN N' (' + CAST(max_length/2 AS NVARCHAR(20)) + N')' + ELSE N' ' + CAST(max_length AS NVARCHAR(50)) END END + N'}' @@ -21459,7 +21561,15 @@ UPDATE #IndexSanity SET include_column_names = D3.include_column_names FROM #IndexSanity si CROSS APPLY ( SELECT RTRIM(STUFF( (SELECT N', ' + c.column_name - + N' {' + system_type_name + N' ' + CAST(max_length AS NVARCHAR(50)) + N'}' + + N' {' + system_type_name + + CASE max_length WHEN -1 THEN N' (max)' ELSE + CASE + WHEN system_type_name IN (N'char',N'varchar',N'binary',N'varbinary') THEN N' (' + CAST(max_length AS NVARCHAR(20)) + N')' + WHEN system_type_name IN (N'nchar',N'nvarchar') THEN N' (' + CAST(max_length/2 AS NVARCHAR(20)) + N')' + ELSE N' ' + CAST(max_length AS NVARCHAR(50)) + END + END + + N'}' FROM #IndexColumns c WHERE c.database_id= si.database_id AND c.schema_name = si.schema_name @@ -21996,6 +22106,7 @@ BEGIN SELECT @ColumnList = @ColumnList + column_name + N'', '' FROM DistinctColumns ORDER BY column_id; + SELECT @PartitionCount = COUNT(1) FROM ' + QUOTENAME(@DatabaseName) + N'.sys.partitions WHERE object_id = @ObjectID AND data_compression IN (3,4); END'; IF @Debug = 1 @@ -22012,27 +22123,62 @@ BEGIN PRINT SUBSTRING(@dsql, 36000, 40000); END; - EXEC sp_executesql @dsql, N'@ObjectID INT, @ColumnList NVARCHAR(MAX) OUTPUT', @ObjectID, @ColumnList OUTPUT; + EXEC sp_executesql @dsql, N'@ObjectID INT, @ColumnList NVARCHAR(MAX) OUTPUT, @PartitionCount INT OUTPUT', @ObjectID, @ColumnList OUTPUT, @PartitionCount OUTPUT; + + IF @PartitionCount < 2 + SET @ShowPartitionRanges = 0; IF @Debug = 1 - SELECT @ColumnList AS ColumnstoreColumnList; + SELECT @ColumnList AS ColumnstoreColumnList, @PartitionCount AS PartitionCount, @ShowPartitionRanges AS ShowPartitionRanges; IF @ColumnList <> '' BEGIN /* Remove the trailing comma */ SET @ColumnList = LEFT(@ColumnList, LEN(@ColumnList) - 1); - SET @dsql = N'USE ' + QUOTENAME(@DatabaseName) + N'; SELECT partition_number, row_group_id, total_rows, deleted_rows, ' + @ColumnList + N' + SET @dsql = N'USE ' + QUOTENAME(@DatabaseName) + N'; + SELECT partition_number, ' + + CASE WHEN @ShowPartitionRanges = 1 THEN N' COALESCE(range_start_op + '' '' + range_start + '' '', '''') + COALESCE(range_end_op + '' '' + range_end, '''') AS partition_range, ' ELSE N' ' END + + N' row_group_id, total_rows, deleted_rows, ' + + @ColumnList + + CASE WHEN @ShowPartitionRanges = 1 THEN N' FROM ( - SELECT c.name AS column_name, p.partition_number, - rg.row_group_id, rg.total_rows, rg.deleted_rows, - details = CAST(seg.min_data_id AS VARCHAR(20)) + '' to '' + CAST(seg.max_data_id AS VARCHAR(20)) + '', '' + CAST(CAST((seg.on_disk_size / 1024.0 / 1024) AS DECIMAL(18,0)) AS VARCHAR(20)) + '' MB'' - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_row_groups rg - INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns c ON rg.object_id = c.object_id - INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partitions p ON rg.object_id = p.object_id AND rg.partition_number = p.partition_number - INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.index_columns ic on ic.column_id = c.column_id AND ic.object_id = c.object_id AND ic.index_id = p.index_id - LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_segments seg ON p.partition_id = seg.partition_id AND ic.index_column_id = seg.column_id AND rg.row_group_id = seg.segment_id - WHERE rg.object_id = @ObjectID + SELECT column_name, partition_number, row_group_id, total_rows, deleted_rows, details, + range_start_op, + CASE + WHEN format_type IS NULL THEN CAST(range_start_value AS NVARCHAR(4000)) + ELSE CONVERT(NVARCHAR(4000), range_start_value, format_type) END range_start, + range_end_op, + CASE + WHEN format_type IS NULL THEN CAST(range_end_value AS NVARCHAR(4000)) + ELSE CONVERT(NVARCHAR(4000), range_end_value, format_type) END range_end' ELSE N' ' END + N' + FROM ( + SELECT c.name AS column_name, p.partition_number, rg.row_group_id, rg.total_rows, rg.deleted_rows, + details = CAST(seg.min_data_id AS VARCHAR(20)) + '' to '' + CAST(seg.max_data_id AS VARCHAR(20)) + '', '' + CAST(CAST((seg.on_disk_size / 1024.0 / 1024) AS DECIMAL(18,0)) AS VARCHAR(20)) + '' MB''' + + CASE WHEN @ShowPartitionRanges = 1 THEN N', + CASE + WHEN pp.system_type_id IN (40, 41, 42, 43, 58, 61) THEN 126 + WHEN pp.system_type_id IN (59, 62) THEN 3 + WHEN pp.system_type_id IN (60, 122) THEN 2 + ELSE NULL END format_type, + CASE WHEN pf.boundary_value_on_right = 0 THEN ''>'' ELSE ''>='' END range_start_op, + prvs.value range_start_value, + CASE WHEN pf.boundary_value_on_right = 0 THEN ''<='' ELSE ''<'' END range_end_op, + prve.value range_end_value ' ELSE N' ' END + N' + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_row_groups rg + INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns c ON rg.object_id = c.object_id + INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partitions p ON rg.object_id = p.object_id AND rg.partition_number = p.partition_number + INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.index_columns ic on ic.column_id = c.column_id AND ic.object_id = c.object_id AND ic.index_id = p.index_id ' + CASE WHEN @ShowPartitionRanges = 1 THEN N' + LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.indexes i ON i.object_id = rg.object_id AND i.index_id = rg.index_id + LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partition_schemes ps ON ps.data_space_id = i.data_space_id + LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partition_functions pf ON pf.function_id = ps.function_id + LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partition_parameters pp ON pp.function_id = pf.function_id + LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partition_range_values prvs ON prvs.function_id = pf.function_id AND prvs.boundary_id = p.partition_number - 1 + LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partition_range_values prve ON prve.function_id = pf.function_id AND prve.boundary_id = p.partition_number ' ELSE N' ' END + + N' LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_segments seg ON p.partition_id = seg.partition_id AND ic.index_column_id = seg.column_id AND rg.row_group_id = seg.segment_id + WHERE rg.object_id = @ObjectID' + + CASE WHEN @ShowPartitionRanges = 1 THEN N' + ) AS y ' ELSE N' ' END + N' ) AS x PIVOT (MAX(details) FOR column_name IN ( ' + @ColumnList + N')) AS pivot1 ORDER BY partition_number, row_group_id;'; @@ -24788,9 +24934,10 @@ AS BEGIN SET NOCOUNT ON; +SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.03', @VersionDate = '20210420'; +SELECT @Version = '8.04', @VersionDate = '20210530'; IF(@VersionCheckMode = 1) @@ -25922,8 +26069,8 @@ You need to use an Azure storage account, and the path has to look like this: ht SELECT DISTINCT PARSENAME(dow.object_name, 3) AS database_name, dow.object_name, - CONVERT(VARCHAR(10), (SUM(DISTINCT dp.wait_time) / 1000) / 86400) AS wait_days, - CONVERT(VARCHAR(20), DATEADD(SECOND, (SUM(DISTINCT dp.wait_time) / 1000), 0), 108) AS wait_time_hms + CONVERT(VARCHAR(10), (SUM(DISTINCT CONVERT(BIGINT, dp.wait_time)) / 1000) / 86400) AS wait_days, + CONVERT(VARCHAR(20), DATEADD(SECOND, (SUM(DISTINCT CONVERT(BIGINT, dp.wait_time)) / 1000), 0), 108) AS wait_time_hms FROM #deadlock_owner_waiter AS dow JOIN #deadlock_process AS dp ON (dp.id = dow.owner_id OR dp.victim_id = dow.waiter_id) @@ -25975,8 +26122,8 @@ You need to use an Azure storage account, and the path has to look like this: ht '-' AS object_name, 'Total database deadlock wait time' AS finding_group, 'This database has had ' - + CONVERT(VARCHAR(10), (SUM(DISTINCT wt.total_wait_time_ms) / 1000) / 86400) - + ':' + CONVERT(VARCHAR(20), DATEADD(SECOND, (SUM(DISTINCT wt.total_wait_time_ms) / 1000), 0), 108) + + CONVERT(VARCHAR(10), (SUM(DISTINCT CONVERT(BIGINT, wt.total_wait_time_ms)) / 1000) / 86400) + + ':' + CONVERT(VARCHAR(20), DATEADD(SECOND, (SUM(DISTINCT CONVERT(BIGINT, wt.total_wait_time_ms)) / 1000), 0), 108) + ' [d/h/m/s] of deadlock wait time.' FROM wait_time AS wt GROUP BY wt.database_name @@ -26568,9 +26715,10 @@ ALTER PROCEDURE dbo.sp_BlitzWho AS BEGIN SET NOCOUNT ON; + SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.03', @VersionDate = '20210420'; + SELECT @Version = '8.04', @VersionDate = '20210530'; IF(@VersionCheckMode = 1) BEGIN @@ -27090,6 +27238,7 @@ END SELECT @BlockingCheck = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + SET LOCK_TIMEOUT 1000; /* To avoid blocking on live query plans. See Github issue #2907. */ DECLARE @blocked TABLE ( dbid SMALLINT NOT NULL, @@ -27686,8 +27835,8 @@ IF @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @Output ,[database_name] ,[query_text] ,[query_plan]' - + CASE WHEN @ProductVersionMajor >= 11 THEN N',[live_query_plan]' ELSE N'' END + N' - ,[cached_parameter_info]' + + CASE WHEN @ProductVersionMajor >= 11 THEN N',[live_query_plan]' ELSE N'' END + + CASE WHEN @ProductVersionMajor >= 11 THEN N',[cached_parameter_info]' ELSE N'' END + CASE WHEN @ProductVersionMajor >= 11 AND @ShowActualParameters = 1 THEN N',[Live_Parameter_Info]' ELSE N'' END + N' ,[query_cost] ,[status] @@ -27855,7 +28004,7 @@ DELETE FROM dbo.SqlServerVersions; INSERT INTO dbo.SqlServerVersions (MajorVersionNumber, MinorVersionNumber, Branch, [Url], ReleaseDate, MainstreamSupportEndDate, ExtendedSupportEndDate, MajorVersionName, MinorVersionName) VALUES - (15, 4123, 'CU10', 'https://support.microsoft.com/en-us/help/5001090', '2021-02-11', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 10'), + (15, 4123, 'CU10', 'https://support.microsoft.com/en-us/help/5001090', '2021-04-06', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 10'), (15, 4102, 'CU9', 'https://support.microsoft.com/en-us/help/5000642', '2021-02-11', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 9 '), (15, 4073, 'CU8 GDR', 'https://support.microsoft.com/en-us/help/4583459', '2021-01-12', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 8 GDR '), (15, 4073, 'CU8', 'https://support.microsoft.com/en-us/help/4577194', '2020-10-01', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 8 '), @@ -27868,6 +28017,7 @@ VALUES (15, 4003, 'CU1', 'https://support.microsoft.com/en-us/help/4527376', '2020-01-07', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 1 '), (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 '), + (14, 3391, 'RTM CU24', 'https://support.microsoft.com/en-us/help/5001228', '2021-05-10', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 24'), (14, 3381, 'RTM CU23', 'https://support.microsoft.com/en-us/help/5000685', '2021-02-25', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 23'), (14, 3370, 'RTM CU22 GDR', 'https://support.microsoft.com/en-us/help/4583457', '2021-01-12', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 22 GDR'), (14, 3356, 'RTM CU22', 'https://support.microsoft.com/en-us/help/4577467', '2020-09-10', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 22'), @@ -27893,6 +28043,7 @@ VALUES (14, 3008, 'RTM CU2', 'https://support.microsoft.com/en-us/help/4052574', '2017-11-28', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 2'), (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 '), + (13, 5888, 'SP2 CU17', 'https://support.microsoft.com/en-us/help/5001092', '2021-03-29', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 17'), (13, 5882, 'SP2 CU16', 'https://support.microsoft.com/en-us/help/5000645', '2021-02-11', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 16'), (13, 5865, 'SP2 CU15 GDR', 'https://support.microsoft.com/en-us/help/4583461', '2021-01-12', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 15 GDR'), (13, 5850, 'SP2 CU15', 'https://support.microsoft.com/en-us/help/4577775', '2020-09-28', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 15'), @@ -28242,9 +28393,10 @@ ALTER PROCEDURE [dbo].[sp_BlitzFirst] AS BEGIN SET NOCOUNT ON; +SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.03', @VersionDate = '20210420'; +SELECT @Version = '8.04', @VersionDate = '20210530'; IF(@VersionCheckMode = 1) BEGIN @@ -30487,10 +30639,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, ( SELECT CONVERT(VARCHAR(5), 100 - ca.c.value('.', 'INT')) AS system_idle, CONVERT(VARCHAR(30), rb.event_date) AS event_date, - CONVERT(VARCHAR(8000), rb.record) AS record + CONVERT(VARCHAR(8000), rb.record) AS record, + event_date as event_date_raw FROM ( SELECT CONVERT(XML, dorb.record) AS record, - DATEADD(ms, ( ts.ms_ticks - dorb.timestamp ), GETDATE()) AS event_date + DATEADD(ms, -( ts.ms_ticks - dorb.timestamp ), GETDATE()) AS event_date FROM sys.dm_os_ring_buffers AS dorb CROSS JOIN ( SELECT dosi.ms_ticks FROM sys.dm_os_sys_info AS dosi ) AS ts @@ -30515,10 +30668,10 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, + ' Ring buffer details: ' + y2.record FROM y AS y2 - ORDER BY y2.event_date DESC + ORDER BY y2.event_date_raw DESC FOR XML PATH(N''), TYPE ).value(N'.[1]', N'VARCHAR(MAX)'), 1, 1, N'') AS query FROM y - ORDER BY y.event_date DESC; + ORDER BY y.event_date_raw DESC; /* Highlight if non SQL processes are using >25% CPU - CheckID 28 */ @@ -32945,4 +33098,5 @@ EXEC sp_BlitzFirst , @OutputTableNameWaitStats = 'BlitzFirst_WaitStats' , @OutputTableNameBlitzCache = 'BlitzCache' , @OutputTableNameBlitzWho = 'BlitzWho' +, @OutputType = 'none' */ diff --git a/Install-Core-Blitz-With-Query-Store.sql b/Install-Core-Blitz-With-Query-Store.sql index 722aa50f2..a17e9128e 100755 --- a/Install-Core-Blitz-With-Query-Store.sql +++ b/Install-Core-Blitz-With-Query-Store.sql @@ -34,10 +34,11 @@ ALTER PROCEDURE [dbo].[sp_Blitz] WITH RECOMPILE AS SET NOCOUNT ON; + SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.03', @VersionDate = '20210420'; + SELECT @Version = '8.04', @VersionDate = '20210530'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -428,33 +429,38 @@ AS AND db_id('rdsadmin') IS NOT NULL AND EXISTS(SELECT * FROM master.sys.all_objects WHERE name IN ('rds_startup_tasks', 'rds_help_revlogin', 'rds_hexadecimal', 'rds_failover_tracking', 'rds_database_tracking', 'rds_track_change')) BEGIN - INSERT INTO #SkipChecks (CheckID) VALUES (6); - INSERT INTO #SkipChecks (CheckID) VALUES (29); - INSERT INTO #SkipChecks (CheckID) VALUES (30); - INSERT INTO #SkipChecks (CheckID) VALUES (31); - INSERT INTO #SkipChecks (CheckID) VALUES (40); /* TempDB only has one data file */ - INSERT INTO #SkipChecks (CheckID) VALUES (57); - INSERT INTO #SkipChecks (CheckID) VALUES (59); - INSERT INTO #SkipChecks (CheckID) VALUES (61); - INSERT INTO #SkipChecks (CheckID) VALUES (62); - INSERT INTO #SkipChecks (CheckID) VALUES (68); - INSERT INTO #SkipChecks (CheckID) VALUES (69); - INSERT INTO #SkipChecks (CheckID) VALUES (73); - INSERT INTO #SkipChecks (CheckID) VALUES (79); - INSERT INTO #SkipChecks (CheckID) VALUES (92); - INSERT INTO #SkipChecks (CheckID) VALUES (94); - INSERT INTO #SkipChecks (CheckID) VALUES (96); - INSERT INTO #SkipChecks (CheckID) VALUES (98); + 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 (29); /* tables in model database created by users - not allowed */ + INSERT INTO #SkipChecks (CheckID) VALUES (40); /* TempDB only has one data file in RDS */ + INSERT INTO #SkipChecks (CheckID) VALUES (62); /* Database compatibility level - cannot change in RDS */ + INSERT INTO #SkipChecks (CheckID) VALUES (68); /*Check for the last good DBCC CHECKDB date - can't run DBCC DBINFO() */ + INSERT INTO #SkipChecks (CheckID) VALUES (69); /* High VLF check - requires DBCC LOGINFO permission */ + INSERT INTO #SkipChecks (CheckID) VALUES (73); /* No Failsafe Operator Configured check */ + INSERT INTO #SkipChecks (CheckID) VALUES (92); /* Drive info check - requires xp_Fixeddrives permission */ INSERT INTO #SkipChecks (CheckID) VALUES (100); /* Remote DAC disabled */ - INSERT INTO #SkipChecks (CheckID) VALUES (123); - INSERT INTO #SkipChecks (CheckID) VALUES (177); - INSERT INTO #SkipChecks (CheckID) VALUES (180); /* 180/181 are maintenance plans */ - INSERT INTO #SkipChecks (CheckID) VALUES (181); - INSERT INTO #SkipChecks (CheckID) VALUES (184); /* xp_readerrorlog checking for IFI */ - INSERT INTO #SkipChecks (CheckID) VALUES (211); /* xp_regread checking for power saving */ - INSERT INTO #SkipChecks (CheckID) VALUES (212); /* xp_regread */ - INSERT INTO #SkipChecks (CheckID) VALUES (219); + INSERT INTO #SkipChecks (CheckID) VALUES (177); /* Disabled Internal Monitoring Features check - requires dm_server_registry access */ + INSERT INTO #SkipChecks (CheckID) VALUES (180); /* 180/181 are maintenance plans checks - Maint plans not available in RDS*/ + INSERT INTO #SkipChecks (CheckID) VALUES (181); /*Find repetitive maintenance tasks*/ + + -- can check errorlog using rdsadmin.dbo.rds_read_error_log, so allow this check + --INSERT INTO #SkipChecks (CheckID) VALUES (193); /* xp_readerrorlog checking for IFI */ + + INSERT INTO #SkipChecks (CheckID) VALUES (211); /* xp_regread not allowed - checking for power saving */ + INSERT INTO #SkipChecks (CheckID) VALUES (212); /* xp_regread not allowed - checking for additional instances */ INSERT INTO #SkipChecks (CheckID) VALUES (2301); /* sp_validatelogins called by Invalid login defined with Windows Authentication */ + + -- Following are skipped due to limited permissions in msdb/SQLAgent in RDS + INSERT INTO #SkipChecks (CheckID) VALUES (30); /* SQL Server Agent alerts not configured */ + INSERT INTO #SkipChecks (CheckID) VALUES (31); /* check whether we have NO ENABLED operators */ + INSERT INTO #SkipChecks (CheckID) VALUES (57); /* SQL Agent Job Runs at Startup */ + INSERT INTO #SkipChecks (CheckID) VALUES (59); /* Alerts Configured without Follow Up */ + INSERT INTO #SkipChecks (CheckID) VALUES (61); /*SQL Server Agent alerts do not exist for severity levels 19 through 25*/ + INSERT INTO #SkipChecks (CheckID) VALUES (79); /* Shrink Database Job check */ + INSERT INTO #SkipChecks (CheckID) VALUES (94); /* job failure without operator notification check */ + INSERT INTO #SkipChecks (CheckID) VALUES (96); /* Agent alerts for corruption */ + INSERT INTO #SkipChecks (CheckID) VALUES (98); /* check for disabled alerts */ + INSERT INTO #SkipChecks (CheckID) VALUES (123); /* Agent Jobs Starting Simultaneously */ + INSERT INTO #SkipChecks (CheckID) VALUES (219); /* check for alerts that do NOT include event descriptions in their outputs via email/pager/net-send */ INSERT INTO #BlitzResults ( CheckID , Priority , @@ -3248,11 +3254,6 @@ AS 'The job ' + [name] + ' has not been set up to notify an operator if it fails.' AS Details FROM msdb.[dbo].[sysjobs] j - INNER JOIN ( SELECT DISTINCT - [job_id] - FROM [msdb].[dbo].[sysjobschedules] - WHERE next_run_date > 0 - ) s ON j.job_id = s.job_id WHERE j.enabled = 1 AND j.notify_email_operator_id = 0 AND j.notify_netsend_operator_id = 0 @@ -8130,16 +8131,30 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 */ IF NOT EXISTS ( SELECT 1 FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 184 ) - AND (@ProductVersionMajor >= 13) OR (@ProductVersionMajor = 12 AND @ProductVersionMinor >= 5000) + WHERE DatabaseName IS NULL AND CheckID = 193 ) + AND ((@ProductVersionMajor >= 13) OR (@ProductVersionMajor = 12 AND @ProductVersionMinor >= 5000)) BEGIN - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 184) WITH NOWAIT; + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 193) WITH NOWAIT; - INSERT INTO #ErrorLog - EXEC sys.xp_readerrorlog 0, 1, N'Database Instant File Initialization: enabled'; + -- If this is Amazon RDS, use rdsadmin.dbo.rds_read_error_log + IF LEFT(CAST(SERVERPROPERTY('ComputerNamePhysicalNetBIOS') AS VARCHAR(8000)), 8) = 'EC2AMAZ-' + AND LEFT(CAST(SERVERPROPERTY('MachineName') AS VARCHAR(8000)), 8) = 'EC2AMAZ-' + AND LEFT(CAST(SERVERPROPERTY('ServerName') AS VARCHAR(8000)), 8) = 'EC2AMAZ-' + AND db_id('rdsadmin') IS NOT NULL + AND EXISTS(SELECT * FROM master.sys.all_objects WHERE name IN ('rds_startup_tasks', 'rds_help_revlogin', 'rds_hexadecimal', 'rds_failover_tracking', 'rds_database_tracking', 'rds_track_change')) + BEGIN + INSERT INTO #ErrorLog + EXEC rdsadmin.dbo.rds_read_error_log 0, 1, N'Database Instant File Initialization: enabled'; + END + ELSE + BEGIN + INSERT INTO #ErrorLog + EXEC sys.xp_readerrorlog 0, 1, N'Database Instant File Initialization: enabled'; + END IF @@ROWCOUNT > 0 + begin INSERT INTO #BlitzResults ( CheckID , [Priority] , @@ -8155,6 +8170,37 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 'Instant File Initialization Enabled' AS [Finding] , 'https://www.brentozar.com/go/instant' AS [URL] , 'The service account has the Perform Volume Maintenance Tasks permission.'; + end + else -- if version of sql server has instant_file_initialization_enabled column in dm_server_services, check that too + -- in the event the error log has been cycled and the startup messages are not in the current error log + begin + if EXISTS ( SELECT * + FROM sys.all_objects o + INNER JOIN sys.all_columns c ON o.object_id = c.object_id + WHERE o.name = 'dm_server_services' + AND c.name = 'instant_file_initialization_enabled' ) + begin + INSERT INTO #BlitzResults + ( CheckID , + [Priority] , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT + 193 AS [CheckID] , + 250 AS [Priority] , + 'Server Info' AS [FindingsGroup] , + 'Instant File Initialization Enabled' AS [Finding] , + 'https://www.brentozar.com/go/instant' AS [URL] , + 'The service account has the Perform Volume Maintenance Tasks permission.' + where exists (select 1 FROM sys.dm_server_services + WHERE instant_file_initialization_enabled = 'Y' + AND filename LIKE '%sqlservr.exe%') + OPTION (RECOMPILE); + end; + end; END; /* Server Info - Instant File Initialization Not Enabled - Check 192 - SQL Server 2016 SP1 and newer */ @@ -9431,8 +9477,9 @@ ALTER PROCEDURE [dbo].[sp_BlitzAnalysis] ( ) AS SET NOCOUNT ON; +SET STATISTICS XML OFF; -SELECT @Version = '8.03', @VersionDate = '20210420'; +SELECT @Version = '8.04', @VersionDate = '20210530'; IF(@VersionCheckMode = 1) BEGIN @@ -10307,9 +10354,10 @@ WITH RECOMPILE AS BEGIN SET NOCOUNT ON; + SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.03', @VersionDate = '20210420'; + SELECT @Version = '8.04', @VersionDate = '20210530'; IF(@VersionCheckMode = 1) BEGIN @@ -12087,9 +12135,10 @@ WITH RECOMPILE AS BEGIN SET NOCOUNT ON; +SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.03', @VersionDate = '20210420'; +SELECT @Version = '8.04', @VersionDate = '20210530'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -13373,10 +13422,10 @@ database_id INT CREATE TABLE #plan_usage ( - duplicate_plan_handles BIGINT NULL, - percent_duplicate NUMERIC(7, 2) NULL, + duplicate_plan_hashes BIGINT NULL, + percent_duplicate DECIMAL(5, 2) NULL, single_use_plan_count BIGINT NULL, - percent_single NUMERIC(7, 2) NULL, + percent_single DECIMAL(5, 2) NULL, total_plans BIGINT NULL, spid INT ); @@ -13410,49 +13459,90 @@ OPTION (RECOMPILE); RAISERROR(N'Checking for single use plans and plans with many queries', 0, 1) WITH NOWAIT; WITH total_plans AS ( - SELECT COUNT_BIG(*) AS total_plans - FROM sys.dm_exec_cached_plans AS deqs - WHERE deqs.cacheobjtype = N'Compiled Plan' + SELECT + COUNT_BIG(deqs.query_plan_hash) AS total_plans + FROM sys.dm_exec_query_stats AS deqs ), many_plans AS ( - SELECT SUM(x.duplicate_plan_handles) AS duplicate_plan_handles - FROM ( - SELECT COUNT_BIG(DISTINCT plan_handle) AS duplicate_plan_handles + SELECT + SUM(x.duplicate_plan_hashes) AS duplicate_plan_hashes + FROM + ( + SELECT + COUNT_BIG(qs.query_plan_hash) AS duplicate_plan_hashes FROM sys.dm_exec_query_stats qs - CROSS APPLY sys.dm_exec_plan_attributes(qs.plan_handle) pa + LEFT JOIN sys.dm_exec_procedure_stats ps + ON qs.sql_handle = ps.sql_handle + CROSS APPLY sys.dm_exec_plan_attributes(qs.plan_handle) pa WHERE pa.attribute = N'dbid' - GROUP BY qs.query_hash, pa.value - HAVING COUNT_BIG(DISTINCT plan_handle) > 5 + AND qs.query_plan_hash <> 0x0000000000000000 + GROUP BY + /* qs.query_plan_hash, BGO 20210524 commenting this out to fix #2909 */ + qs.query_hash, + ps.object_id, + pa.value + HAVING COUNT_BIG(qs.query_plan_hash) > 5 ) AS x ), single_use_plans AS ( - SELECT COUNT_BIG(*) AS single_use_plan_count + SELECT + COUNT_BIG(*) AS single_use_plan_count FROM sys.dm_exec_cached_plans AS cp WHERE cp.usecounts = 1 AND cp.objtype = N'Adhoc' - AND EXISTS ( SELECT 1/0 - FROM sys.configurations AS c - WHERE c.name = N'optimize for ad hoc workloads' - AND c.value_in_use = 0 ) + AND EXISTS + ( + SELECT + 1/0 + FROM sys.configurations AS c + WHERE c.name = N'optimize for ad hoc workloads' + AND c.value_in_use = 0 + ) HAVING COUNT_BIG(*) > 1 ) -INSERT #plan_usage ( duplicate_plan_handles, percent_duplicate, single_use_plan_count, percent_single, total_plans, spid ) -SELECT m.duplicate_plan_handles, - CONVERT(DECIMAL(5,2), m.duplicate_plan_handles / (1. * NULLIF(t.total_plans, 0))) * 100. AS percent_duplicate, - s.single_use_plan_count, - CONVERT(DECIMAL(5,2), s.single_use_plan_count / (1. * NULLIF(t.total_plans, 0))) * 100. AS percent_single, - t.total_plans, - @@SPID -FROM many_plans AS m - CROSS APPLY single_use_plans AS s - CROSS APPLY total_plans AS t; +INSERT + #plan_usage +( + duplicate_plan_hashes, + percent_duplicate, + single_use_plan_count, + percent_single, + total_plans, + spid +) +SELECT + m.duplicate_plan_hashes, + CONVERT + ( + decimal(5,2), + m.duplicate_plan_hashes + / (1. * NULLIF(t.total_plans, 0)) + ) * 100. AS percent_duplicate, + s.single_use_plan_count, + CONVERT + ( + decimal(5,2), + s.single_use_plan_count + / (1. * NULLIF(t.total_plans, 0)) + ) * 100. AS percent_single, + t.total_plans, + @@SPID +FROM many_plans AS m +CROSS JOIN single_use_plans AS s +CROSS JOIN total_plans AS t; +/* +Erik Darling: + Quoting this out to see if the above query fixes the issue + 2021-05-17, Issue #2909 + UPDATE #plan_usage SET percent_duplicate = CASE WHEN percent_duplicate > 100 THEN 100 ELSE percent_duplicate END, percent_single = CASE WHEN percent_duplicate > 100 THEN 100 ELSE percent_duplicate END; +*/ SET @OnlySqlHandles = LTRIM(RTRIM(@OnlySqlHandles)) ; SET @OnlyQueryHashes = LTRIM(RTRIM(@OnlyQueryHashes)) ; @@ -13825,7 +13915,7 @@ IF @DurationFilter IS NOT NULL IF @MinutesBack IS NOT NULL BEGIN RAISERROR(N'Setting minutes back filter', 0, 1) WITH NOWAIT; - SET @body += N' AND DATEADD(MILLISECOND, (x.last_elapsed_time / 1000.), x.last_execution_time) >= DATEADD(MINUTE, @min_back, GETDATE()) ' + @nl ; + SET @body += N' AND DATEADD(SECOND, (x.last_elapsed_time / 1000000.), x.last_execution_time) >= DATEADD(MINUTE, @min_back, GETDATE()) ' + @nl ; END; IF @SlowlySearchPlansFor IS NOT NULL @@ -13944,7 +14034,7 @@ SELECT TOP (@Top) END AS WritesPerMinute, qs.cached_time AS PlanCreationTime, qs.last_execution_time AS LastExecutionTime, - DATEADD(MILLISECOND, (qs.last_elapsed_time / 1000.), qs.last_execution_time) AS LastCompletionTime, + DATEADD(SECOND, (qs.last_elapsed_time / 1000000.), qs.last_execution_time) AS LastCompletionTime, NULL AS StatementStartOffset, NULL AS StatementEndOffset, NULL AS PlanGenerationNum, @@ -14055,7 +14145,7 @@ BEGIN END AS WritesPerMinute, qs.creation_time AS PlanCreationTime, qs.last_execution_time AS LastExecutionTime, - DATEADD(MILLISECOND, (qs.last_elapsed_time / 1000.), qs.last_execution_time) AS LastCompletionTime, + DATEADD(SECOND, (qs.last_elapsed_time / 1000000.), qs.last_execution_time) AS LastCompletionTime, qs.statement_start_offset AS StatementStartOffset, qs.statement_end_offset AS StatementEndOffset, qs.plan_generation_num AS PlanGenerationNum, '; @@ -14638,15 +14728,23 @@ UPDATE ##BlitzCacheProcs SET NumberOfDistinctPlans = distinct_plan_count, NumberOfPlans = number_of_plans , plan_multiple_plans = CASE WHEN distinct_plan_count < number_of_plans THEN number_of_plans END -FROM ( - SELECT COUNT(DISTINCT QueryHash) AS distinct_plan_count, - COUNT(QueryHash) AS number_of_plans, - QueryHash, - DatabaseName - FROM ##BlitzCacheProcs - WHERE SPID = @@SPID - GROUP BY QueryHash, - DatabaseName +FROM + ( + SELECT + DatabaseName = + DB_NAME(CONVERT(int, pa.value)), + QueryHash = + qs.query_hash, + number_of_plans = + COUNT_BIG(qs.query_plan_hash), + distinct_plan_count = + COUNT_BIG(DISTINCT qs.query_plan_hash) + FROM sys.dm_exec_query_stats AS qs + CROSS APPLY sys.dm_exec_plan_attributes(qs.plan_handle) pa + WHERE pa.attribute = 'dbid' + GROUP BY + DB_NAME(CONVERT(int, pa.value)), + qs.query_hash ) AS x WHERE ##BlitzCacheProcs.QueryHash = x.QueryHash AND ##BlitzCacheProcs.DatabaseName = x.DatabaseName @@ -19286,6 +19384,7 @@ ALTER PROCEDURE dbo.sp_BlitzIndex @OutputTableName NVARCHAR(256) = NULL , @IncludeInactiveIndexes BIT = 0 /* Will skip indexes with no reads or writes */, @ShowAllMissingIndexRequests BIT = 0 /*Will make all missing index requests show up*/, + @ShowPartitionRanges BIT = 0 /* Will add partition range values column to columnstore visualization */, @SortOrder NVARCHAR(50) = NULL, /* Only affects @Mode = 2. */ @SortDirection NVARCHAR(4) = 'DESC', /* Only affects @Mode = 2. */ @Help TINYINT = 0, @@ -19296,9 +19395,10 @@ ALTER PROCEDURE dbo.sp_BlitzIndex WITH RECOMPILE AS SET NOCOUNT ON; +SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.03', @VersionDate = '20210420'; +SELECT @Version = '8.04', @VersionDate = '20210530'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -19377,6 +19477,7 @@ DECLARE @LineFeed NVARCHAR(5); DECLARE @DaysUptimeInsertValue NVARCHAR(256); DECLARE @DatabaseToIgnore NVARCHAR(MAX); DECLARE @ColumnList NVARCHAR(MAX); +DECLARE @PartitionCount INT; /* Let's get @SortOrder set to lower case here for comparisons later */ SET @SortOrder = REPLACE(LOWER(@SortOrder), N' ', N'_'); @@ -20926,6 +21027,7 @@ BEGIN TRY ORDER BY (q.user_seeks + q.user_scans) DESC, s.total_logical_reads DESC ) q2 CROSS APPLY sys.dm_exec_query_plan(q2.plan_handle) p + WHERE ig.index_group_handle = gs.group_handle ) ' END @@ -21370,12 +21472,12 @@ UPDATE #IndexSanity SET key_column_names = D1.key_column_names FROM #IndexSanity si CROSS APPLY ( SELECT RTRIM(STUFF( (SELECT N', ' + c.column_name - + N' {' + system_type_name + N' ' + - CASE max_length WHEN -1 THEN N'(max)' ELSE + + N' {' + system_type_name + + CASE max_length WHEN -1 THEN N' (max)' ELSE CASE - WHEN system_type_name IN (N'char',N'varchar',N'binary',N'varbinary') THEN N'(' + CAST(max_length AS NVARCHAR(20)) + N')' - WHEN system_type_name IN (N'nchar',N'nvarchar') THEN N'(' + CAST(max_length/2 AS NVARCHAR(20)) + N')' - ELSE '' + WHEN system_type_name IN (N'char',N'varchar',N'binary',N'varbinary') THEN N' (' + CAST(max_length AS NVARCHAR(20)) + N')' + WHEN system_type_name IN (N'nchar',N'nvarchar') THEN N' (' + CAST(max_length/2 AS NVARCHAR(20)) + N')' + ELSE N' ' + CAST(max_length AS NVARCHAR(50)) END END + N'}' @@ -21414,12 +21516,12 @@ FROM #IndexSanity si WHEN 1 THEN N' DESC' ELSE N'' END - + N' {' + system_type_name + N' ' + - CASE max_length WHEN -1 THEN N'(max)' ELSE + + N' {' + system_type_name + + CASE max_length WHEN -1 THEN N' (max)' ELSE CASE - WHEN system_type_name IN (N'char',N'varchar',N'binary',N'varbinary') THEN N'(' + CAST(max_length AS NVARCHAR(20)) + N')' - WHEN system_type_name IN (N'nchar',N'nvarchar') THEN N'(' + CAST(max_length/2 AS NVARCHAR(20)) + N')' - ELSE '' + WHEN system_type_name IN (N'char',N'varchar',N'binary',N'varbinary') THEN N' (' + CAST(max_length AS NVARCHAR(20)) + N')' + WHEN system_type_name IN (N'nchar',N'nvarchar') THEN N' (' + CAST(max_length/2 AS NVARCHAR(20)) + N')' + ELSE N' ' + CAST(max_length AS NVARCHAR(50)) END END + N'}' @@ -21459,7 +21561,15 @@ UPDATE #IndexSanity SET include_column_names = D3.include_column_names FROM #IndexSanity si CROSS APPLY ( SELECT RTRIM(STUFF( (SELECT N', ' + c.column_name - + N' {' + system_type_name + N' ' + CAST(max_length AS NVARCHAR(50)) + N'}' + + N' {' + system_type_name + + CASE max_length WHEN -1 THEN N' (max)' ELSE + CASE + WHEN system_type_name IN (N'char',N'varchar',N'binary',N'varbinary') THEN N' (' + CAST(max_length AS NVARCHAR(20)) + N')' + WHEN system_type_name IN (N'nchar',N'nvarchar') THEN N' (' + CAST(max_length/2 AS NVARCHAR(20)) + N')' + ELSE N' ' + CAST(max_length AS NVARCHAR(50)) + END + END + + N'}' FROM #IndexColumns c WHERE c.database_id= si.database_id AND c.schema_name = si.schema_name @@ -21996,6 +22106,7 @@ BEGIN SELECT @ColumnList = @ColumnList + column_name + N'', '' FROM DistinctColumns ORDER BY column_id; + SELECT @PartitionCount = COUNT(1) FROM ' + QUOTENAME(@DatabaseName) + N'.sys.partitions WHERE object_id = @ObjectID AND data_compression IN (3,4); END'; IF @Debug = 1 @@ -22012,27 +22123,62 @@ BEGIN PRINT SUBSTRING(@dsql, 36000, 40000); END; - EXEC sp_executesql @dsql, N'@ObjectID INT, @ColumnList NVARCHAR(MAX) OUTPUT', @ObjectID, @ColumnList OUTPUT; + EXEC sp_executesql @dsql, N'@ObjectID INT, @ColumnList NVARCHAR(MAX) OUTPUT, @PartitionCount INT OUTPUT', @ObjectID, @ColumnList OUTPUT, @PartitionCount OUTPUT; + + IF @PartitionCount < 2 + SET @ShowPartitionRanges = 0; IF @Debug = 1 - SELECT @ColumnList AS ColumnstoreColumnList; + SELECT @ColumnList AS ColumnstoreColumnList, @PartitionCount AS PartitionCount, @ShowPartitionRanges AS ShowPartitionRanges; IF @ColumnList <> '' BEGIN /* Remove the trailing comma */ SET @ColumnList = LEFT(@ColumnList, LEN(@ColumnList) - 1); - SET @dsql = N'USE ' + QUOTENAME(@DatabaseName) + N'; SELECT partition_number, row_group_id, total_rows, deleted_rows, ' + @ColumnList + N' + SET @dsql = N'USE ' + QUOTENAME(@DatabaseName) + N'; + SELECT partition_number, ' + + CASE WHEN @ShowPartitionRanges = 1 THEN N' COALESCE(range_start_op + '' '' + range_start + '' '', '''') + COALESCE(range_end_op + '' '' + range_end, '''') AS partition_range, ' ELSE N' ' END + + N' row_group_id, total_rows, deleted_rows, ' + + @ColumnList + + CASE WHEN @ShowPartitionRanges = 1 THEN N' FROM ( - SELECT c.name AS column_name, p.partition_number, - rg.row_group_id, rg.total_rows, rg.deleted_rows, - details = CAST(seg.min_data_id AS VARCHAR(20)) + '' to '' + CAST(seg.max_data_id AS VARCHAR(20)) + '', '' + CAST(CAST((seg.on_disk_size / 1024.0 / 1024) AS DECIMAL(18,0)) AS VARCHAR(20)) + '' MB'' - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_row_groups rg - INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns c ON rg.object_id = c.object_id - INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partitions p ON rg.object_id = p.object_id AND rg.partition_number = p.partition_number - INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.index_columns ic on ic.column_id = c.column_id AND ic.object_id = c.object_id AND ic.index_id = p.index_id - LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_segments seg ON p.partition_id = seg.partition_id AND ic.index_column_id = seg.column_id AND rg.row_group_id = seg.segment_id - WHERE rg.object_id = @ObjectID + SELECT column_name, partition_number, row_group_id, total_rows, deleted_rows, details, + range_start_op, + CASE + WHEN format_type IS NULL THEN CAST(range_start_value AS NVARCHAR(4000)) + ELSE CONVERT(NVARCHAR(4000), range_start_value, format_type) END range_start, + range_end_op, + CASE + WHEN format_type IS NULL THEN CAST(range_end_value AS NVARCHAR(4000)) + ELSE CONVERT(NVARCHAR(4000), range_end_value, format_type) END range_end' ELSE N' ' END + N' + FROM ( + SELECT c.name AS column_name, p.partition_number, rg.row_group_id, rg.total_rows, rg.deleted_rows, + details = CAST(seg.min_data_id AS VARCHAR(20)) + '' to '' + CAST(seg.max_data_id AS VARCHAR(20)) + '', '' + CAST(CAST((seg.on_disk_size / 1024.0 / 1024) AS DECIMAL(18,0)) AS VARCHAR(20)) + '' MB''' + + CASE WHEN @ShowPartitionRanges = 1 THEN N', + CASE + WHEN pp.system_type_id IN (40, 41, 42, 43, 58, 61) THEN 126 + WHEN pp.system_type_id IN (59, 62) THEN 3 + WHEN pp.system_type_id IN (60, 122) THEN 2 + ELSE NULL END format_type, + CASE WHEN pf.boundary_value_on_right = 0 THEN ''>'' ELSE ''>='' END range_start_op, + prvs.value range_start_value, + CASE WHEN pf.boundary_value_on_right = 0 THEN ''<='' ELSE ''<'' END range_end_op, + prve.value range_end_value ' ELSE N' ' END + N' + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_row_groups rg + INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns c ON rg.object_id = c.object_id + INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partitions p ON rg.object_id = p.object_id AND rg.partition_number = p.partition_number + INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.index_columns ic on ic.column_id = c.column_id AND ic.object_id = c.object_id AND ic.index_id = p.index_id ' + CASE WHEN @ShowPartitionRanges = 1 THEN N' + LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.indexes i ON i.object_id = rg.object_id AND i.index_id = rg.index_id + LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partition_schemes ps ON ps.data_space_id = i.data_space_id + LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partition_functions pf ON pf.function_id = ps.function_id + LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partition_parameters pp ON pp.function_id = pf.function_id + LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partition_range_values prvs ON prvs.function_id = pf.function_id AND prvs.boundary_id = p.partition_number - 1 + LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partition_range_values prve ON prve.function_id = pf.function_id AND prve.boundary_id = p.partition_number ' ELSE N' ' END + + N' LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_segments seg ON p.partition_id = seg.partition_id AND ic.index_column_id = seg.column_id AND rg.row_group_id = seg.segment_id + WHERE rg.object_id = @ObjectID' + + CASE WHEN @ShowPartitionRanges = 1 THEN N' + ) AS y ' ELSE N' ' END + N' ) AS x PIVOT (MAX(details) FOR column_name IN ( ' + @ColumnList + N')) AS pivot1 ORDER BY partition_number, row_group_id;'; @@ -24788,9 +24934,10 @@ AS BEGIN SET NOCOUNT ON; +SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.03', @VersionDate = '20210420'; +SELECT @Version = '8.04', @VersionDate = '20210530'; IF(@VersionCheckMode = 1) @@ -25922,8 +26069,8 @@ You need to use an Azure storage account, and the path has to look like this: ht SELECT DISTINCT PARSENAME(dow.object_name, 3) AS database_name, dow.object_name, - CONVERT(VARCHAR(10), (SUM(DISTINCT dp.wait_time) / 1000) / 86400) AS wait_days, - CONVERT(VARCHAR(20), DATEADD(SECOND, (SUM(DISTINCT dp.wait_time) / 1000), 0), 108) AS wait_time_hms + CONVERT(VARCHAR(10), (SUM(DISTINCT CONVERT(BIGINT, dp.wait_time)) / 1000) / 86400) AS wait_days, + CONVERT(VARCHAR(20), DATEADD(SECOND, (SUM(DISTINCT CONVERT(BIGINT, dp.wait_time)) / 1000), 0), 108) AS wait_time_hms FROM #deadlock_owner_waiter AS dow JOIN #deadlock_process AS dp ON (dp.id = dow.owner_id OR dp.victim_id = dow.waiter_id) @@ -25975,8 +26122,8 @@ You need to use an Azure storage account, and the path has to look like this: ht '-' AS object_name, 'Total database deadlock wait time' AS finding_group, 'This database has had ' - + CONVERT(VARCHAR(10), (SUM(DISTINCT wt.total_wait_time_ms) / 1000) / 86400) - + ':' + CONVERT(VARCHAR(20), DATEADD(SECOND, (SUM(DISTINCT wt.total_wait_time_ms) / 1000), 0), 108) + + CONVERT(VARCHAR(10), (SUM(DISTINCT CONVERT(BIGINT, wt.total_wait_time_ms)) / 1000) / 86400) + + ':' + CONVERT(VARCHAR(20), DATEADD(SECOND, (SUM(DISTINCT CONVERT(BIGINT, wt.total_wait_time_ms)) / 1000), 0), 108) + ' [d/h/m/s] of deadlock wait time.' FROM wait_time AS wt GROUP BY wt.database_name @@ -26594,9 +26741,10 @@ AS BEGIN /*First BEGIN*/ SET NOCOUNT ON; +SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.03', @VersionDate = '20210420'; +SELECT @Version = '8.04', @VersionDate = '20210530'; IF(@VersionCheckMode = 1) BEGIN RETURN; @@ -32322,9 +32470,10 @@ ALTER PROCEDURE dbo.sp_BlitzWho AS BEGIN SET NOCOUNT ON; + SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.03', @VersionDate = '20210420'; + SELECT @Version = '8.04', @VersionDate = '20210530'; IF(@VersionCheckMode = 1) BEGIN @@ -32844,6 +32993,7 @@ END SELECT @BlockingCheck = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + SET LOCK_TIMEOUT 1000; /* To avoid blocking on live query plans. See Github issue #2907. */ DECLARE @blocked TABLE ( dbid SMALLINT NOT NULL, @@ -33440,8 +33590,8 @@ IF @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @Output ,[database_name] ,[query_text] ,[query_plan]' - + CASE WHEN @ProductVersionMajor >= 11 THEN N',[live_query_plan]' ELSE N'' END + N' - ,[cached_parameter_info]' + + CASE WHEN @ProductVersionMajor >= 11 THEN N',[live_query_plan]' ELSE N'' END + + CASE WHEN @ProductVersionMajor >= 11 THEN N',[cached_parameter_info]' ELSE N'' END + CASE WHEN @ProductVersionMajor >= 11 AND @ShowActualParameters = 1 THEN N',[Live_Parameter_Info]' ELSE N'' END + N' ,[query_cost] ,[status] @@ -33609,7 +33759,7 @@ DELETE FROM dbo.SqlServerVersions; INSERT INTO dbo.SqlServerVersions (MajorVersionNumber, MinorVersionNumber, Branch, [Url], ReleaseDate, MainstreamSupportEndDate, ExtendedSupportEndDate, MajorVersionName, MinorVersionName) VALUES - (15, 4123, 'CU10', 'https://support.microsoft.com/en-us/help/5001090', '2021-02-11', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 10'), + (15, 4123, 'CU10', 'https://support.microsoft.com/en-us/help/5001090', '2021-04-06', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 10'), (15, 4102, 'CU9', 'https://support.microsoft.com/en-us/help/5000642', '2021-02-11', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 9 '), (15, 4073, 'CU8 GDR', 'https://support.microsoft.com/en-us/help/4583459', '2021-01-12', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 8 GDR '), (15, 4073, 'CU8', 'https://support.microsoft.com/en-us/help/4577194', '2020-10-01', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 8 '), @@ -33622,6 +33772,7 @@ VALUES (15, 4003, 'CU1', 'https://support.microsoft.com/en-us/help/4527376', '2020-01-07', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 1 '), (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 '), + (14, 3391, 'RTM CU24', 'https://support.microsoft.com/en-us/help/5001228', '2021-05-10', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 24'), (14, 3381, 'RTM CU23', 'https://support.microsoft.com/en-us/help/5000685', '2021-02-25', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 23'), (14, 3370, 'RTM CU22 GDR', 'https://support.microsoft.com/en-us/help/4583457', '2021-01-12', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 22 GDR'), (14, 3356, 'RTM CU22', 'https://support.microsoft.com/en-us/help/4577467', '2020-09-10', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 22'), @@ -33647,6 +33798,7 @@ VALUES (14, 3008, 'RTM CU2', 'https://support.microsoft.com/en-us/help/4052574', '2017-11-28', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 2'), (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 '), + (13, 5888, 'SP2 CU17', 'https://support.microsoft.com/en-us/help/5001092', '2021-03-29', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 17'), (13, 5882, 'SP2 CU16', 'https://support.microsoft.com/en-us/help/5000645', '2021-02-11', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 16'), (13, 5865, 'SP2 CU15 GDR', 'https://support.microsoft.com/en-us/help/4583461', '2021-01-12', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 15 GDR'), (13, 5850, 'SP2 CU15', 'https://support.microsoft.com/en-us/help/4577775', '2020-09-28', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 15'), @@ -33996,9 +34148,10 @@ ALTER PROCEDURE [dbo].[sp_BlitzFirst] AS BEGIN SET NOCOUNT ON; +SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.03', @VersionDate = '20210420'; +SELECT @Version = '8.04', @VersionDate = '20210530'; IF(@VersionCheckMode = 1) BEGIN @@ -36241,10 +36394,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, ( SELECT CONVERT(VARCHAR(5), 100 - ca.c.value('.', 'INT')) AS system_idle, CONVERT(VARCHAR(30), rb.event_date) AS event_date, - CONVERT(VARCHAR(8000), rb.record) AS record + CONVERT(VARCHAR(8000), rb.record) AS record, + event_date as event_date_raw FROM ( SELECT CONVERT(XML, dorb.record) AS record, - DATEADD(ms, ( ts.ms_ticks - dorb.timestamp ), GETDATE()) AS event_date + DATEADD(ms, -( ts.ms_ticks - dorb.timestamp ), GETDATE()) AS event_date FROM sys.dm_os_ring_buffers AS dorb CROSS JOIN ( SELECT dosi.ms_ticks FROM sys.dm_os_sys_info AS dosi ) AS ts @@ -36269,10 +36423,10 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, + ' Ring buffer details: ' + y2.record FROM y AS y2 - ORDER BY y2.event_date DESC + ORDER BY y2.event_date_raw DESC FOR XML PATH(N''), TYPE ).value(N'.[1]', N'VARCHAR(MAX)'), 1, 1, N'') AS query FROM y - ORDER BY y.event_date DESC; + ORDER BY y.event_date_raw DESC; /* Highlight if non SQL processes are using >25% CPU - CheckID 28 */ @@ -38699,4 +38853,5 @@ EXEC sp_BlitzFirst , @OutputTableNameWaitStats = 'BlitzFirst_WaitStats' , @OutputTableNameBlitzCache = 'BlitzCache' , @OutputTableNameBlitzWho = 'BlitzWho' +, @OutputType = 'none' */ diff --git a/sp_AllNightLog.sql b/sp_AllNightLog.sql index b9efdeab2..2252ab61c 100644 --- a/sp_AllNightLog.sql +++ b/sp_AllNightLog.sql @@ -31,7 +31,7 @@ SET STATISTICS XML OFF; BEGIN; -SELECT @Version = '8.03', @VersionDate = '20210420'; +SELECT @Version = '8.04', @VersionDate = '20210530'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_AllNightLog_Setup.sql b/sp_AllNightLog_Setup.sql index e9c606227..a1c85022d 100644 --- a/sp_AllNightLog_Setup.sql +++ b/sp_AllNightLog_Setup.sql @@ -37,7 +37,7 @@ SET STATISTICS XML OFF; BEGIN; -SELECT @Version = '8.03', @VersionDate = '20210420'; +SELECT @Version = '8.04', @VersionDate = '20210530'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_Blitz.sql b/sp_Blitz.sql index c37c6ef84..16dcbf32d 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -38,7 +38,7 @@ AS SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.03', @VersionDate = '20210420'; + SELECT @Version = '8.04', @VersionDate = '20210530'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) diff --git a/sp_BlitzAnalysis.sql b/sp_BlitzAnalysis.sql index 8d6594575..22fbb3020 100644 --- a/sp_BlitzAnalysis.sql +++ b/sp_BlitzAnalysis.sql @@ -37,7 +37,7 @@ AS SET NOCOUNT ON; SET STATISTICS XML OFF; -SELECT @Version = '8.03', @VersionDate = '20210420'; +SELECT @Version = '8.04', @VersionDate = '20210530'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_BlitzBackups.sql b/sp_BlitzBackups.sql index 7d71c7972..a18bf533b 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.03', @VersionDate = '20210420'; + SELECT @Version = '8.04', @VersionDate = '20210530'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_BlitzCache.sql b/sp_BlitzCache.sql index 6cc7b7a22..4ec3d3c91 100644 --- a/sp_BlitzCache.sql +++ b/sp_BlitzCache.sql @@ -280,7 +280,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.03', @VersionDate = '20210420'; +SELECT @Version = '8.04', @VersionDate = '20210530'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) diff --git a/sp_BlitzFirst.sql b/sp_BlitzFirst.sql index 4667e476f..84e67a065 100644 --- a/sp_BlitzFirst.sql +++ b/sp_BlitzFirst.sql @@ -46,7 +46,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.03', @VersionDate = '20210420'; +SELECT @Version = '8.04', @VersionDate = '20210530'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_BlitzInMemoryOLTP.sql b/sp_BlitzInMemoryOLTP.sql index bb66ec88d..728ff7b53 100644 --- a/sp_BlitzInMemoryOLTP.sql +++ b/sp_BlitzInMemoryOLTP.sql @@ -82,7 +82,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI */ AS DECLARE @ScriptVersion VARCHAR(30); -SELECT @ScriptVersion = '1.8', @VersionDate = '20210420'; +SELECT @ScriptVersion = '1.8', @VersionDate = '20210530'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index ef78a16de..a4241a6f3 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -48,7 +48,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.03', @VersionDate = '20210420'; +SELECT @Version = '8.04', @VersionDate = '20210530'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index a54645d8a..8b5a235e2 100644 --- a/sp_BlitzLock.sql +++ b/sp_BlitzLock.sql @@ -33,7 +33,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.03', @VersionDate = '20210420'; +SELECT @Version = '8.04', @VersionDate = '20210530'; IF(@VersionCheckMode = 1) diff --git a/sp_BlitzQueryStore.sql b/sp_BlitzQueryStore.sql index f005d3761..7dd310749 100644 --- a/sp_BlitzQueryStore.sql +++ b/sp_BlitzQueryStore.sql @@ -57,7 +57,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.03', @VersionDate = '20210420'; +SELECT @Version = '8.04', @VersionDate = '20210530'; IF(@VersionCheckMode = 1) BEGIN RETURN; diff --git a/sp_BlitzWho.sql b/sp_BlitzWho.sql index 310e85e15..67d334183 100644 --- a/sp_BlitzWho.sql +++ b/sp_BlitzWho.sql @@ -31,7 +31,7 @@ BEGIN SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.03', @VersionDate = '20210420'; + SELECT @Version = '8.04', @VersionDate = '20210530'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_DatabaseRestore.sql b/sp_DatabaseRestore.sql index 943579063..545062a63 100755 --- a/sp_DatabaseRestore.sql +++ b/sp_DatabaseRestore.sql @@ -41,7 +41,7 @@ SET STATISTICS XML OFF; /*Versioning details*/ -SELECT @Version = '8.03', @VersionDate = '20210420'; +SELECT @Version = '8.04', @VersionDate = '20210530'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_ineachdb.sql b/sp_ineachdb.sql index 15b3379b8..98b459027 100644 --- a/sp_ineachdb.sql +++ b/sp_ineachdb.sql @@ -35,7 +35,7 @@ BEGIN SET NOCOUNT ON; SET STATISTICS XML OFF; - SELECT @Version = '8.03', @VersionDate = '20210420'; + SELECT @Version = '8.04', @VersionDate = '20210530'; IF(@VersionCheckMode = 1) BEGIN From 85ee040c0f7f97c2c815d9cdaf2218c473e9d6d3 Mon Sep 17 00:00:00 2001 From: Eitan Blumin Date: Tue, 1 Jun 2021 18:55:46 +0300 Subject: [PATCH 200/662] Fix #2922 sp_BlitzFirst - "Sleeping Query with Open Transactions" is not checked correctly https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2922 --- sp_BlitzFirst.sql | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sp_BlitzFirst.sql b/sp_BlitzFirst.sql index 84e67a065..9b56b1141 100644 --- a/sp_BlitzFirst.sql +++ b/sp_BlitzFirst.sql @@ -1666,9 +1666,8 @@ BEGIN db.[resource_database_id] AS DatabaseID, DB_NAME(db.resource_database_id) AS DatabaseName, (SELECT TOP 1 [text] FROM sys.dm_exec_sql_text(c.most_recent_sql_handle)) AS QueryText, - sessions_with_transactions.open_transaction_count AS OpenTransactionCount - FROM (SELECT session_id, SUM(open_transaction_count) AS open_transaction_count FROM sys.dm_exec_requests WHERE open_transaction_count > 0 GROUP BY session_id) AS sessions_with_transactions - INNER JOIN sys.dm_exec_sessions s ON sessions_with_transactions.session_id = s.session_id + s.open_transaction_count AS OpenTransactionCount + FROM sys.dm_exec_sessions s INNER JOIN sys.dm_exec_connections c ON s.session_id = c.session_id INNER JOIN ( SELECT DISTINCT request_session_id, resource_database_id @@ -1678,6 +1677,7 @@ BEGIN AND request_status = N'GRANT' AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.session_id = db.request_session_id WHERE s.status = 'sleeping' + AND s.open_transaction_count > 0 AND s.last_request_end_time < DATEADD(ss, -10, SYSDATETIME()) AND EXISTS(SELECT * FROM sys.dm_tran_locks WHERE request_session_id = s.session_id AND NOT (resource_type = N'DATABASE' AND request_mode = N'S' AND request_status = N'GRANT' AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE')); From dfb1f87042bda7446fcbde314ebb5a7c208f816c Mon Sep 17 00:00:00 2001 From: Erik Darling Date: Wed, 2 Jun 2021 10:14:34 -0400 Subject: [PATCH 201/662] Add thread time to wait stats views Initial attempt! --- sp_BlitzFirst.sql | 118 ++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 103 insertions(+), 15 deletions(-) diff --git a/sp_BlitzFirst.sql b/sp_BlitzFirst.sql index 84e67a065..319b153b7 100644 --- a/sp_BlitzFirst.sql +++ b/sp_BlitzFirst.sql @@ -139,7 +139,9 @@ DECLARE @StringToExecute NVARCHAR(MAX), @UnquotedOutputDatabaseName NVARCHAR(256) = @OutputDatabaseName , @UnquotedOutputSchemaName NVARCHAR(256) = @OutputSchemaName , @LocalServerName NVARCHAR(128) = CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128)), - @dm_exec_query_statistics_xml BIT = 0; + @dm_exec_query_statistics_xml BIT = 0, + @thread_time_sql NVARCHAR(MAX) = N'', + @thread_time_ms BIGINT /* Sanitize our inputs */ SELECT @@ -342,7 +344,15 @@ BEGIN IF OBJECT_ID('tempdb..#WaitStats') IS NOT NULL DROP TABLE #WaitStats; - CREATE TABLE #WaitStats (Pass TINYINT NOT NULL, wait_type NVARCHAR(60), wait_time_ms BIGINT, signal_wait_time_ms BIGINT, waiting_tasks_count BIGINT, SampleTime DATETIMEOFFSET); + CREATE TABLE #WaitStats ( + Pass TINYINT NOT NULL, + wait_type NVARCHAR(60), + wait_time_ms BIGINT, + thread_time_ms BIGINT, + signal_wait_time_ms BIGINT, + waiting_tasks_count BIGINT, + SampleTime datetimeoffset + ); IF OBJECT_ID('tempdb..#FileStats') IS NOT NULL DROP TABLE #FileStats; @@ -1293,16 +1303,63 @@ BEGIN INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP Transactions','Transactions created/sec',NULL); END; + SET @thread_time_sql = N' + SELECT + @thread_time_ms = + t.total_thread_time_ms + FROM + ( + SELECT + total_thread_time_ms = + CONVERT + ( + float, + -- time spent waiting by threads running user queries + SUM(w.wait_time_ms) + + -- time spent running on a visible scheduler + ( + SELECT + SUM(s.total_cpu_usage_ms) + FROM sys.dm_os_schedulers AS s + WHERE s.status = ''VISIBLE ONLINE'' + AND s.is_online = 1 + ) + ) + FROM sys.dm_os_wait_stats AS w + WHERE w.waiting_tasks_count > 0 + AND NOT EXISTS + ( + -- Exclude waits unrelated to user queries + SELECT + 1/0 + FROM ##WaitCategories AS wc + WHERE wc.WaitType = w.wait_type + AND wc.Ignorable = 1 + ) + ) AS t;' + + IF SERVERPROPERTY('Edition') = 'SQL Azure' + BEGIN + SET @thread_time_sql = REPLACE(@thread_time_sql, N'sys.dm_os_wait_stats', N'sys.dm_db_wait_stats'); + END + + EXEC sys.sp_executesql + @thread_time_sql, + N'@thread_time_ms BIGINT OUTPUT', + @thread_time_ms OUT; + + /* Populate #FileStats, #PerfmonStats, #WaitStats with DMV data. After we finish doing our checks, we'll take another sample and compare them. */ RAISERROR('Capturing first pass of wait stats, perfmon counters, file stats',10,1) WITH NOWAIT; SET @StringToExecute = N' - INSERT #WaitStats(Pass, SampleTime, wait_type, wait_time_ms, signal_wait_time_ms, waiting_tasks_count) + INSERT #WaitStats(Pass, SampleTime, wait_type, wait_time_ms, thread_time_ms, signal_wait_time_ms, waiting_tasks_count) SELECT x.Pass, x.SampleTime, x.wait_type, SUM(x.sum_wait_time_ms) AS sum_wait_time_ms, + CASE @Seconds WHEN 0 THEN 0 ELSE @thread_time_ms END AS thread_time_ms, SUM(x.sum_signal_wait_time_ms) AS sum_signal_wait_time_ms, SUM(x.sum_waiting_tasks) AS sum_waiting_tasks FROM ( @@ -1342,7 +1399,15 @@ BEGIN ) GROUP BY x.Pass, x.SampleTime, x.wait_type ORDER BY sum_wait_time_ms DESC;' - EXEC sp_executesql @StringToExecute, N'@StartSampleTime DATETIMEOFFSET, @Seconds INT', @StartSampleTime, @Seconds; + + EXEC sp_executesql + @StringToExecute, + N'@StartSampleTime DATETIMEOFFSET, + @Seconds INT, + @thread_time_ms BIGINT', + @StartSampleTime, + @Seconds, + @thread_time_ms; INSERT INTO #FileStats (Pass, SampleTime, DatabaseID, FileID, DatabaseName, FileLogicalName, SizeOnDiskMB, io_stall_read_ms , @@ -2450,15 +2515,22 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, WAITFOR TIME @FinishSampleTimeWaitFor; END; + EXEC sys.sp_executesql + @thread_time_sql, + N'@thread_time_ms BIGINT OUTPUT', + @thread_time_ms OUT; + + RAISERROR('Capturing second pass of wait stats, perfmon counters, file stats',10,1) WITH NOWAIT; /* Populate #FileStats, #PerfmonStats, #WaitStats with DMV data. In a second, we'll compare these. */ SET @StringToExecute = N' - INSERT #WaitStats(Pass, SampleTime, wait_type, wait_time_ms, signal_wait_time_ms, waiting_tasks_count) + INSERT #WaitStats(Pass, SampleTime, wait_type, wait_time_ms, thread_time_ms, signal_wait_time_ms, waiting_tasks_count) SELECT x.Pass, x.SampleTime, x.wait_type, SUM(x.sum_wait_time_ms) AS sum_wait_time_ms, + @thread_time_ms AS thread_time_ms, SUM(x.sum_signal_wait_time_ms) AS sum_signal_wait_time_ms, SUM(x.sum_waiting_tasks) AS sum_waiting_tasks FROM ( @@ -2498,7 +2570,14 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, ) GROUP BY x.Pass, x.SampleTime, x.wait_type ORDER BY sum_wait_time_ms DESC;'; - EXEC sp_executesql @StringToExecute, N'@Seconds INT', @Seconds; + + EXEC sp_executesql + @StringToExecute, + N'@Seconds INT, + @thread_time_ms BIGINT', + @Seconds, + @thread_time_ms; + INSERT INTO #FileStats (Pass, SampleTime, DatabaseID, FileID, DatabaseName, FileLogicalName, SizeOnDiskMB, io_stall_read_ms , num_of_reads, [bytes_read] , io_stall_write_ms,num_of_writes, [bytes_written], PhysicalName, TypeDesc, avg_stall_read_ms, avg_stall_write_ms) @@ -4405,10 +4484,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, ) SELECT TOP 10 CAST(DATEDIFF(mi,wd1.SampleTime, wd2.SampleTime) / 60.0 AS DECIMAL(18,1)) AS [Hours Sample], + CAST(c.[Total Thread Time (Seconds)] / 60. / 60. AS DECIMAL(18,1)) AS [Thread Time (Hours)], wd1.wait_type, COALESCE(wcat.WaitCategory, 'Other') AS wait_category, - CAST(c.[Wait Time (Seconds)] / 60.0 / 60 AS DECIMAL(18,1)) AS [Wait Time (Hours)], - CAST((wd2.wait_time_ms - wd1.wait_time_ms) / 1000.0 / cores.cpu_count / DATEDIFF(ss, wd1.SampleTime, wd2.SampleTime) AS DECIMAL(18,1)) AS [Per Core Per Hour], + CAST(c.[Wait Time (Seconds)] / 60. / 60. AS DECIMAL(18,1)) AS [Wait Time (Hours)], + CAST((wd2.wait_time_ms - wd1.wait_time_ms) / 1000.0 / cores.cpu_count / DATEDIFF(ss, wd1.SampleTime, wd2.SampleTime) AS DECIMAL(18,1)) AS [Per Core Per Hour], (wd2.waiting_tasks_count - wd1.waiting_tasks_count) AS [Number of Waits], CASE WHEN (wd2.waiting_tasks_count - wd1.waiting_tasks_count) > 0 THEN @@ -4423,8 +4503,10 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, wd2.SampleTime > wd1.SampleTime CROSS APPLY (SELECT SUM(1) AS cpu_count FROM sys.dm_os_schedulers WHERE status = 'VISIBLE ONLINE' AND is_online = 1) AS cores CROSS APPLY (SELECT - CAST((wd2.wait_time_ms-wd1.wait_time_ms)/1000. AS NUMERIC(12,1)) AS [Wait Time (Seconds)], - CAST((wd2.signal_wait_time_ms - wd1.signal_wait_time_ms)/1000. AS NUMERIC(12,1)) AS [Signal Wait Time (Seconds)]) AS c + CAST((wd2.wait_time_ms-wd1.wait_time_ms)/1000. AS DECIMAL(18,1)) AS [Wait Time (Seconds)], + CAST((wd2.signal_wait_time_ms - wd1.signal_wait_time_ms)/1000. AS DECIMAL(18,1)) AS [Signal Wait Time (Seconds)], + CAST((wd2.thread_time_ms - wd1.thread_time_ms)/1000. AS DECIMAL(18,1)) AS [Total Thread Time (Seconds)] + ) AS c LEFT OUTER JOIN ##WaitCategories wcat ON wd1.wait_type = wcat.WaitType WHERE (wd2.waiting_tasks_count - wd1.waiting_tasks_count) > 0 AND wd2.wait_time_ms-wd1.wait_time_ms > 0 @@ -4546,10 +4628,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, SELECT 'WAIT STATS' AS Pattern, b.SampleTime AS [Sample Ended], - CAST(DATEDIFF(mi,wd1.SampleTime, wd2.SampleTime) / 60.0 AS DECIMAL(18,1)) AS [Hours Sample], + CAST(DATEDIFF(mi,wd1.SampleTime, wd2.SampleTime) / 60. AS DECIMAL(18,1)) AS [Hours Sample], + CAST(c.[Total Thread Time (Seconds)] / 60. / 60. AS DECIMAL(18,1)) AS [Thread Time (Hours)], wd1.wait_type, COALESCE(wcat.WaitCategory, 'Other') AS wait_category, - CAST(c.[Wait Time (Seconds)] / 60.0 / 60 AS DECIMAL(18,1)) AS [Wait Time (Hours)], + CAST(c.[Wait Time (Seconds)] / 60. / 60. AS DECIMAL(18,1)) AS [Wait Time (Hours)], CAST((wd2.wait_time_ms - wd1.wait_time_ms) / 1000.0 / cores.cpu_count / DATEDIFF(ss, wd1.SampleTime, wd2.SampleTime) AS DECIMAL(18,1)) AS [Per Core Per Hour], CAST(c.[Signal Wait Time (Seconds)] / 60.0 / 60 AS DECIMAL(18,1)) AS [Signal Wait Time (Hours)], CASE WHEN c.[Wait Time (Seconds)] > 0 @@ -4570,8 +4653,10 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, wd2.SampleTime > wd1.SampleTime CROSS APPLY (SELECT SUM(1) AS cpu_count FROM sys.dm_os_schedulers WHERE status = 'VISIBLE ONLINE' AND is_online = 1) AS cores CROSS APPLY (SELECT - CAST((wd2.wait_time_ms-wd1.wait_time_ms)/1000. AS NUMERIC(12,1)) AS [Wait Time (Seconds)], - CAST((wd2.signal_wait_time_ms - wd1.signal_wait_time_ms)/1000. AS NUMERIC(12,1)) AS [Signal Wait Time (Seconds)]) AS c + CAST((wd2.wait_time_ms-wd1.wait_time_ms)/1000. AS DECIMAL(18,1)) AS [Wait Time (Seconds)], + CAST((wd2.signal_wait_time_ms - wd1.signal_wait_time_ms)/1000. AS DECIMAL(18,1)) AS [Signal Wait Time (Seconds)], + CAST((wd2.thread_time_ms - wd1.thread_time_ms)/1000. AS DECIMAL(18,1)) AS [Total Thread Time (Seconds)] + ) AS c LEFT OUTER JOIN ##WaitCategories wcat ON wd1.wait_type = wcat.WaitType WHERE (wd2.waiting_tasks_count - wd1.waiting_tasks_count) > 0 AND wd2.wait_time_ms-wd1.wait_time_ms > 0 @@ -4588,6 +4673,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 'WAIT STATS' AS Pattern, b.SampleTime AS [Sample Ended], DATEDIFF(ss,wd1.SampleTime, wd2.SampleTime) AS [Seconds Sample], + c.[Total Thread Time (Seconds)], wd1.wait_type, COALESCE(wcat.WaitCategory, 'Other') AS wait_category, c.[Wait Time (Seconds)], @@ -4612,7 +4698,9 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, CROSS APPLY (SELECT SUM(1) AS cpu_count FROM sys.dm_os_schedulers WHERE status = 'VISIBLE ONLINE' AND is_online = 1) AS cores CROSS APPLY (SELECT CAST((wd2.wait_time_ms-wd1.wait_time_ms)/1000. AS NUMERIC(12,1)) AS [Wait Time (Seconds)], - CAST((wd2.signal_wait_time_ms - wd1.signal_wait_time_ms)/1000. AS NUMERIC(12,1)) AS [Signal Wait Time (Seconds)]) AS c + CAST((wd2.signal_wait_time_ms - wd1.signal_wait_time_ms)/1000. AS NUMERIC(12,1)) AS [Signal Wait Time (Seconds)], + CAST((wd2.thread_time_ms - wd1.thread_time_ms)/1000. AS DECIMAL(18,1)) AS [Total Thread Time (Seconds)] + ) AS c LEFT OUTER JOIN ##WaitCategories wcat ON wd1.wait_type = wcat.WaitType WHERE (wd2.waiting_tasks_count - wd1.waiting_tasks_count) > 0 AND wd2.wait_time_ms-wd1.wait_time_ms > 0 From 8201e748f19b4291467980079922dc54027c76e3 Mon Sep 17 00:00:00 2001 From: Erik Darling Date: Wed, 2 Jun 2021 20:32:31 -0400 Subject: [PATCH 202/662] Working, probably I spent a while on this trying to understand why wait stats + cpu time were coming back so much lower, and it looks like I don't need to do a first pass here. All I need to do is grab it on the second pass. That seems to accurately reflect waits + cpu. --- sp_BlitzFirst.sql | 67 +++++++++++++++++++++++++++++------------------ 1 file changed, 41 insertions(+), 26 deletions(-) diff --git a/sp_BlitzFirst.sql b/sp_BlitzFirst.sql index 319b153b7..1e0342be5 100644 --- a/sp_BlitzFirst.sql +++ b/sp_BlitzFirst.sql @@ -141,7 +141,7 @@ DECLARE @StringToExecute NVARCHAR(MAX), @LocalServerName NVARCHAR(128) = CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128)), @dm_exec_query_statistics_xml BIT = 0, @thread_time_sql NVARCHAR(MAX) = N'', - @thread_time_ms BIGINT + @thread_time_ms FLOAT /* Sanitize our inputs */ SELECT @@ -348,7 +348,7 @@ BEGIN Pass TINYINT NOT NULL, wait_type NVARCHAR(60), wait_time_ms BIGINT, - thread_time_ms BIGINT, + thread_time_ms FLOAT, signal_wait_time_ms BIGINT, waiting_tasks_count BIGINT, SampleTime datetimeoffset @@ -1304,9 +1304,15 @@ BEGIN END; SET @thread_time_sql = N' - SELECT + SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + + SELECT @thread_time_ms = - t.total_thread_time_ms + ROUND + ( + t.total_thread_time_ms, + 2 + ) FROM ( SELECT @@ -1325,9 +1331,25 @@ BEGIN AND s.is_online = 1 ) ) - FROM sys.dm_os_wait_stats AS w - WHERE w.waiting_tasks_count > 0 - AND NOT EXISTS + FROM + ( + SELECT + os.wait_type, + wait_time_ms = + os.wait_time_ms + FROM sys.dm_os_wait_stats AS os + + UNION ALL + + SELECT + owt.wait_type, + wait_time_ms = + owt.wait_duration_ms + FROM sys.dm_os_waiting_tasks AS owt + WHERE owt.session_id > 50 + + ) AS w + WHERE NOT EXISTS ( -- Exclude waits unrelated to user queries SELECT @@ -1340,15 +1362,10 @@ BEGIN IF SERVERPROPERTY('Edition') = 'SQL Azure' BEGIN - SET @thread_time_sql = REPLACE(@thread_time_sql, N'sys.dm_os_wait_stats', N'sys.dm_db_wait_stats'); + SET @thread_time_sql = + REPLACE(@thread_time_sql, N'sys.dm_os_wait_stats', N'sys.dm_db_wait_stats'); END - EXEC sys.sp_executesql - @thread_time_sql, - N'@thread_time_ms BIGINT OUTPUT', - @thread_time_ms OUT; - - /* Populate #FileStats, #PerfmonStats, #WaitStats with DMV data. After we finish doing our checks, we'll take another sample and compare them. */ RAISERROR('Capturing first pass of wait stats, perfmon counters, file stats',10,1) WITH NOWAIT; @@ -1359,7 +1376,7 @@ BEGIN x.SampleTime, x.wait_type, SUM(x.sum_wait_time_ms) AS sum_wait_time_ms, - CASE @Seconds WHEN 0 THEN 0 ELSE @thread_time_ms END AS thread_time_ms, + 0 AS thread_time_ms, SUM(x.sum_signal_wait_time_ms) AS sum_signal_wait_time_ms, SUM(x.sum_waiting_tasks) AS sum_waiting_tasks FROM ( @@ -1403,11 +1420,9 @@ BEGIN EXEC sp_executesql @StringToExecute, N'@StartSampleTime DATETIMEOFFSET, - @Seconds INT, - @thread_time_ms BIGINT', + @Seconds INT', @StartSampleTime, - @Seconds, - @thread_time_ms; + @Seconds; INSERT INTO #FileStats (Pass, SampleTime, DatabaseID, FileID, DatabaseName, FileLogicalName, SizeOnDiskMB, io_stall_read_ms , @@ -2517,7 +2532,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, EXEC sys.sp_executesql @thread_time_sql, - N'@thread_time_ms BIGINT OUTPUT', + N'@thread_time_ms FLOAT OUTPUT', @thread_time_ms OUT; @@ -2574,7 +2589,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, EXEC sp_executesql @StringToExecute, N'@Seconds INT, - @thread_time_ms BIGINT', + @thread_time_ms FLOAT', @Seconds, @thread_time_ms; @@ -4505,7 +4520,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, CROSS APPLY (SELECT CAST((wd2.wait_time_ms-wd1.wait_time_ms)/1000. AS DECIMAL(18,1)) AS [Wait Time (Seconds)], CAST((wd2.signal_wait_time_ms - wd1.signal_wait_time_ms)/1000. AS DECIMAL(18,1)) AS [Signal Wait Time (Seconds)], - CAST((wd2.thread_time_ms - wd1.thread_time_ms)/1000. AS DECIMAL(18,1)) AS [Total Thread Time (Seconds)] + CAST((wd2.thread_time_ms)/1000. AS DECIMAL(18,1)) AS [Total Thread Time (Seconds)] ) AS c LEFT OUTER JOIN ##WaitCategories wcat ON wd1.wait_type = wcat.WaitType WHERE (wd2.waiting_tasks_count - wd1.waiting_tasks_count) > 0 @@ -4655,7 +4670,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, CROSS APPLY (SELECT CAST((wd2.wait_time_ms-wd1.wait_time_ms)/1000. AS DECIMAL(18,1)) AS [Wait Time (Seconds)], CAST((wd2.signal_wait_time_ms - wd1.signal_wait_time_ms)/1000. AS DECIMAL(18,1)) AS [Signal Wait Time (Seconds)], - CAST((wd2.thread_time_ms - wd1.thread_time_ms)/1000. AS DECIMAL(18,1)) AS [Total Thread Time (Seconds)] + CAST((wd2.thread_time_ms)/1000. AS DECIMAL(18,1)) AS [Total Thread Time (Seconds)] ) AS c LEFT OUTER JOIN ##WaitCategories wcat ON wd1.wait_type = wcat.WaitType WHERE (wd2.waiting_tasks_count - wd1.waiting_tasks_count) > 0 @@ -4697,9 +4712,9 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, wd2.SampleTime > wd1.SampleTime CROSS APPLY (SELECT SUM(1) AS cpu_count FROM sys.dm_os_schedulers WHERE status = 'VISIBLE ONLINE' AND is_online = 1) AS cores CROSS APPLY (SELECT - CAST((wd2.wait_time_ms-wd1.wait_time_ms)/1000. AS NUMERIC(12,1)) AS [Wait Time (Seconds)], - CAST((wd2.signal_wait_time_ms - wd1.signal_wait_time_ms)/1000. AS NUMERIC(12,1)) AS [Signal Wait Time (Seconds)], - CAST((wd2.thread_time_ms - wd1.thread_time_ms)/1000. AS DECIMAL(18,1)) AS [Total Thread Time (Seconds)] + CAST((wd2.wait_time_ms-wd1.wait_time_ms)/1000. AS DECIMAL(18,1)) AS [Wait Time (Seconds)], + CAST((wd2.signal_wait_time_ms - wd1.signal_wait_time_ms)/1000. AS DECIMAL(18,1)) AS [Signal Wait Time (Seconds)], + CAST((wd2.thread_time_ms)/1000. AS DECIMAL(18,1)) AS [Total Thread Time (Seconds)] ) AS c LEFT OUTER JOIN ##WaitCategories wcat ON wd1.wait_type = wcat.WaitType WHERE (wd2.waiting_tasks_count - wd1.waiting_tasks_count) > 0 From bd91c842e2e9df0e4d48eb243331b971c91c3fc9 Mon Sep 17 00:00:00 2001 From: Erik Darling Date: Wed, 2 Jun 2021 20:59:30 -0400 Subject: [PATCH 203/662] Nope I was wrong about the last PR. I do need to grab first and second pass, otherwise time just keeps on tacking on. This is obvious in retrospect, but I got excited when it looked correct for a few runs early on. --- sp_BlitzFirst.sql | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/sp_BlitzFirst.sql b/sp_BlitzFirst.sql index 1e0342be5..b65cdae3e 100644 --- a/sp_BlitzFirst.sql +++ b/sp_BlitzFirst.sql @@ -1366,6 +1366,11 @@ BEGIN REPLACE(@thread_time_sql, N'sys.dm_os_wait_stats', N'sys.dm_db_wait_stats'); END + EXEC sys.sp_executesql + @thread_time_sql, + N'@thread_time_ms FLOAT OUTPUT', + @thread_time_ms OUT; + /* Populate #FileStats, #PerfmonStats, #WaitStats with DMV data. After we finish doing our checks, we'll take another sample and compare them. */ RAISERROR('Capturing first pass of wait stats, perfmon counters, file stats',10,1) WITH NOWAIT; @@ -1376,7 +1381,7 @@ BEGIN x.SampleTime, x.wait_type, SUM(x.sum_wait_time_ms) AS sum_wait_time_ms, - 0 AS thread_time_ms, + CASE @Seconds WHEN 0 THEN 0 ELSE @thread_time_ms END AS thread_time_ms, SUM(x.sum_signal_wait_time_ms) AS sum_signal_wait_time_ms, SUM(x.sum_waiting_tasks) AS sum_waiting_tasks FROM ( @@ -1420,9 +1425,11 @@ BEGIN EXEC sp_executesql @StringToExecute, N'@StartSampleTime DATETIMEOFFSET, - @Seconds INT', + @Seconds INT, + @thread_time_ms float', @StartSampleTime, - @Seconds; + @Seconds, + @thread_time_ms; INSERT INTO #FileStats (Pass, SampleTime, DatabaseID, FileID, DatabaseName, FileLogicalName, SizeOnDiskMB, io_stall_read_ms , @@ -4714,7 +4721,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, CROSS APPLY (SELECT CAST((wd2.wait_time_ms-wd1.wait_time_ms)/1000. AS DECIMAL(18,1)) AS [Wait Time (Seconds)], CAST((wd2.signal_wait_time_ms - wd1.signal_wait_time_ms)/1000. AS DECIMAL(18,1)) AS [Signal Wait Time (Seconds)], - CAST((wd2.thread_time_ms)/1000. AS DECIMAL(18,1)) AS [Total Thread Time (Seconds)] + CAST((wd2.thread_time_ms - wd1.thread_time_ms)/1000. AS DECIMAL(18,1)) AS [Total Thread Time (Seconds)] ) AS c LEFT OUTER JOIN ##WaitCategories wcat ON wd1.wait_type = wcat.WaitType WHERE (wd2.waiting_tasks_count - wd1.waiting_tasks_count) > 0 From b0b61282de6ddc45f36960b3cab4732b140e2af9 Mon Sep 17 00:00:00 2001 From: Erik Darling Date: Wed, 2 Jun 2021 21:35:28 -0400 Subject: [PATCH 204/662] erik stopped being dumb Notes in issue --- sp_BlitzFirst.sql | 147 +++++++++++++++++++--------------------------- 1 file changed, 62 insertions(+), 85 deletions(-) diff --git a/sp_BlitzFirst.sql b/sp_BlitzFirst.sql index b65cdae3e..3c203fe72 100644 --- a/sp_BlitzFirst.sql +++ b/sp_BlitzFirst.sql @@ -139,9 +139,7 @@ DECLARE @StringToExecute NVARCHAR(MAX), @UnquotedOutputDatabaseName NVARCHAR(256) = @OutputDatabaseName , @UnquotedOutputSchemaName NVARCHAR(256) = @OutputSchemaName , @LocalServerName NVARCHAR(128) = CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128)), - @dm_exec_query_statistics_xml BIT = 0, - @thread_time_sql NVARCHAR(MAX) = N'', - @thread_time_ms FLOAT + @dm_exec_query_statistics_xml BIT = 0; /* Sanitize our inputs */ SELECT @@ -1303,73 +1301,6 @@ BEGIN INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP Transactions','Transactions created/sec',NULL); END; - SET @thread_time_sql = N' - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - - SELECT - @thread_time_ms = - ROUND - ( - t.total_thread_time_ms, - 2 - ) - FROM - ( - SELECT - total_thread_time_ms = - CONVERT - ( - float, - -- time spent waiting by threads running user queries - SUM(w.wait_time_ms) + - -- time spent running on a visible scheduler - ( - SELECT - SUM(s.total_cpu_usage_ms) - FROM sys.dm_os_schedulers AS s - WHERE s.status = ''VISIBLE ONLINE'' - AND s.is_online = 1 - ) - ) - FROM - ( - SELECT - os.wait_type, - wait_time_ms = - os.wait_time_ms - FROM sys.dm_os_wait_stats AS os - - UNION ALL - - SELECT - owt.wait_type, - wait_time_ms = - owt.wait_duration_ms - FROM sys.dm_os_waiting_tasks AS owt - WHERE owt.session_id > 50 - - ) AS w - WHERE NOT EXISTS - ( - -- Exclude waits unrelated to user queries - SELECT - 1/0 - FROM ##WaitCategories AS wc - WHERE wc.WaitType = w.wait_type - AND wc.Ignorable = 1 - ) - ) AS t;' - - IF SERVERPROPERTY('Edition') = 'SQL Azure' - BEGIN - SET @thread_time_sql = - REPLACE(@thread_time_sql, N'sys.dm_os_wait_stats', N'sys.dm_db_wait_stats'); - END - - EXEC sys.sp_executesql - @thread_time_sql, - N'@thread_time_ms FLOAT OUTPUT', - @thread_time_ms OUT; /* Populate #FileStats, #PerfmonStats, #WaitStats with DMV data. After we finish doing our checks, we'll take another sample and compare them. */ @@ -1381,7 +1312,18 @@ BEGIN x.SampleTime, x.wait_type, SUM(x.sum_wait_time_ms) AS sum_wait_time_ms, - CASE @Seconds WHEN 0 THEN 0 ELSE @thread_time_ms END AS thread_time_ms, + CASE @Seconds + WHEN 0 + THEN 0 + ELSE + ( + SELECT + SUM(s.total_cpu_usage_ms) + FROM sys.dm_os_schedulers AS s + WHERE s.status = ''VISIBLE ONLINE'' + AND s.is_online = 1 + ) + END AS thread_time_ms, SUM(x.sum_signal_wait_time_ms) AS sum_signal_wait_time_ms, SUM(x.sum_waiting_tasks) AS sum_waiting_tasks FROM ( @@ -1425,12 +1367,29 @@ BEGIN EXEC sp_executesql @StringToExecute, N'@StartSampleTime DATETIMEOFFSET, - @Seconds INT, - @thread_time_ms float', + @Seconds INT', @StartSampleTime, - @Seconds, - @thread_time_ms; + @Seconds; + WITH w AS + ( + SELECT + total_waits = + CONVERT + ( + FLOAT, + SUM(ws.wait_time_ms) + ) + FROM #WaitStats AS ws + WHERE Pass = 1 + ) + UPDATE ws + SET ws.thread_time_ms = + ws.thread_time_ms + w.total_waits + FROM #WaitStats AS ws + CROSS JOIN w + WHERE ws.Pass = 1 + OPTION(RECOMPILE); INSERT INTO #FileStats (Pass, SampleTime, DatabaseID, FileID, DatabaseName, FileLogicalName, SizeOnDiskMB, io_stall_read_ms , num_of_reads, [bytes_read] , io_stall_write_ms,num_of_writes, [bytes_written], PhysicalName, TypeDesc) @@ -2537,11 +2496,6 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, WAITFOR TIME @FinishSampleTimeWaitFor; END; - EXEC sys.sp_executesql - @thread_time_sql, - N'@thread_time_ms FLOAT OUTPUT', - @thread_time_ms OUT; - RAISERROR('Capturing second pass of wait stats, perfmon counters, file stats',10,1) WITH NOWAIT; /* Populate #FileStats, #PerfmonStats, #WaitStats with DMV data. In a second, we'll compare these. */ @@ -2552,7 +2506,13 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, x.SampleTime, x.wait_type, SUM(x.sum_wait_time_ms) AS sum_wait_time_ms, - @thread_time_ms AS thread_time_ms, + ( + SELECT + SUM(s.total_cpu_usage_ms) + FROM sys.dm_os_schedulers AS s + WHERE s.status = ''VISIBLE ONLINE'' + AND s.is_online = 1 + ) AS thread_time_ms, SUM(x.sum_signal_wait_time_ms) AS sum_signal_wait_time_ms, SUM(x.sum_waiting_tasks) AS sum_waiting_tasks FROM ( @@ -2595,11 +2555,28 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, EXEC sp_executesql @StringToExecute, - N'@Seconds INT, - @thread_time_ms FLOAT', - @Seconds, - @thread_time_ms; + N'@Seconds INT', + @Seconds; + WITH w AS + ( + SELECT + total_waits = + CONVERT + ( + FLOAT, + SUM(ws.wait_time_ms) + ) + FROM #WaitStats AS ws + WHERE Pass = 2 + ) + UPDATE ws + SET ws.thread_time_ms = + ws.thread_time_ms + w.total_waits + FROM #WaitStats AS ws + CROSS JOIN w + WHERE ws.Pass = 2 + OPTION(RECOMPILE); INSERT INTO #FileStats (Pass, SampleTime, DatabaseID, FileID, DatabaseName, FileLogicalName, SizeOnDiskMB, io_stall_read_ms , num_of_reads, [bytes_read] , io_stall_write_ms,num_of_writes, [bytes_written], PhysicalName, TypeDesc, avg_stall_read_ms, avg_stall_write_ms) From d5e019ff8f80b8ec2b8ed5dcea84b5f380e3c2b3 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Thu, 3 Jun 2021 02:14:03 -0700 Subject: [PATCH 205/662] #2921 sp_Blitz Instant File Initialization Turning the IFI check into dynamic SQL to avoid errors on unpatched versions. Closes #2921. --- sp_Blitz.sql | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index 16dcbf32d..586bf8af5 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -8180,6 +8180,7 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 WHERE o.name = 'dm_server_services' AND c.name = 'instant_file_initialization_enabled' ) begin + SET @StringToExecute = N' INSERT INTO #BlitzResults ( CheckID , [Priority] , @@ -8191,14 +8192,15 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 SELECT 193 AS [CheckID] , 250 AS [Priority] , - 'Server Info' AS [FindingsGroup] , - 'Instant File Initialization Enabled' AS [Finding] , - 'https://www.brentozar.com/go/instant' AS [URL] , - 'The service account has the Perform Volume Maintenance Tasks permission.' + ''Server Info'' AS [FindingsGroup] , + ''Instant File Initialization Enabled'' AS [Finding] , + ''https://www.brentozar.com/go/instant'' AS [URL] , + ''The service account has the Perform Volume Maintenance Tasks permission.'' where exists (select 1 FROM sys.dm_server_services - WHERE instant_file_initialization_enabled = 'Y' - AND filename LIKE '%sqlservr.exe%') - OPTION (RECOMPILE); + WHERE instant_file_initialization_enabled = ''Y'' + AND filename LIKE ''%sqlservr.exe%'') + OPTION (RECOMPILE);'; + EXEC(@StringToExecute); end; end; END; From 632ec5c1d8371f821a89fed0c74501dd620f6d2e Mon Sep 17 00:00:00 2001 From: Erik Darling Date: Thu, 3 Jun 2021 10:25:26 -0400 Subject: [PATCH 206/662] read committed is a garbage isolation level i had to make one more tweak for reasons detailed in the issue. --- sp_BlitzFirst.sql | 43 +++++++++++++++++++++++++++++++++++-------- 1 file changed, 35 insertions(+), 8 deletions(-) diff --git a/sp_BlitzFirst.sql b/sp_BlitzFirst.sql index 3c203fe72..e4321d22c 100644 --- a/sp_BlitzFirst.sql +++ b/sp_BlitzFirst.sql @@ -1318,7 +1318,11 @@ BEGIN ELSE ( SELECT - SUM(s.total_cpu_usage_ms) + CONVERT + ( + FLOAT, + SUM(s.total_cpu_usage_ms) + ) FROM sys.dm_os_schedulers AS s WHERE s.status = ''VISIBLE ONLINE'' AND s.is_online = 1 @@ -1363,14 +1367,14 @@ BEGIN ) GROUP BY x.Pass, x.SampleTime, x.wait_type ORDER BY sum_wait_time_ms DESC;' - + EXEC sp_executesql @StringToExecute, N'@StartSampleTime DATETIMEOFFSET, @Seconds INT', @StartSampleTime, @Seconds; - + WITH w AS ( SELECT @@ -1382,15 +1386,24 @@ BEGIN ) FROM #WaitStats AS ws WHERE Pass = 1 + ), + m AS + ( + SELECT + max_thread = + MAX(ws.thread_time_ms) + FROM #WaitStats AS ws + WHERE Pass = 1 ) UPDATE ws SET ws.thread_time_ms = - ws.thread_time_ms + w.total_waits + m.max_thread + w.total_waits FROM #WaitStats AS ws CROSS JOIN w + CROSS JOIN m WHERE ws.Pass = 1 OPTION(RECOMPILE); - + INSERT INTO #FileStats (Pass, SampleTime, DatabaseID, FileID, DatabaseName, FileLogicalName, SizeOnDiskMB, io_stall_read_ms , num_of_reads, [bytes_read] , io_stall_write_ms,num_of_writes, [bytes_written], PhysicalName, TypeDesc) SELECT @@ -2508,7 +2521,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, SUM(x.sum_wait_time_ms) AS sum_wait_time_ms, ( SELECT - SUM(s.total_cpu_usage_ms) + CONVERT + ( + FLOAT, + SUM(s.total_cpu_usage_ms) + ) FROM sys.dm_os_schedulers AS s WHERE s.status = ''VISIBLE ONLINE'' AND s.is_online = 1 @@ -2552,7 +2569,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, ) GROUP BY x.Pass, x.SampleTime, x.wait_type ORDER BY sum_wait_time_ms DESC;'; - + EXEC sp_executesql @StringToExecute, N'@Seconds INT', @@ -2569,14 +2586,24 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, ) FROM #WaitStats AS ws WHERE Pass = 2 + ), + m AS + ( + SELECT + max_thread = + MAX(ws.thread_time_ms) + FROM #WaitStats AS ws + WHERE Pass = 2 ) UPDATE ws SET ws.thread_time_ms = - ws.thread_time_ms + w.total_waits + m.max_thread + w.total_waits FROM #WaitStats AS ws CROSS JOIN w + CROSS JOIN m WHERE ws.Pass = 2 OPTION(RECOMPILE); + INSERT INTO #FileStats (Pass, SampleTime, DatabaseID, FileID, DatabaseName, FileLogicalName, SizeOnDiskMB, io_stall_read_ms , num_of_reads, [bytes_read] , io_stall_write_ms,num_of_writes, [bytes_written], PhysicalName, TypeDesc, avg_stall_read_ms, avg_stall_write_ms) From 26a16aafac65771d9921c689d5a2d72f4895e500 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Mon, 7 Jun 2021 06:22:42 +0000 Subject: [PATCH 207/662] Update sp_BlitzIndex.sql --- sp_BlitzIndex.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index a4241a6f3..a2f408797 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -3988,7 +3988,7 @@ BEGIN; i.index_sanity_id, 150 AS Priority, N'Index Hoarder' AS findings_group, - N'Non-Unique Clustered JIndex' AS finding, + N'Non-Unique Clustered Index' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/IndexHoarder' AS URL, N'Uniquifiers will be required! Clustered index: ' + i.db_schema_object_name From 506574c7ee76486a6636d38803936ff3ad21e4b0 Mon Sep 17 00:00:00 2001 From: David Wiseman Date: Tue, 8 Jun 2021 14:48:08 +0100 Subject: [PATCH 208/662] Fix @DaysUptime in sp_BlitzIndex for AzureDB Days uptime is now calculated from sys.dm_os_sys_info which works for AzureDB. For older instances (<2008) we can fallback to create_date of tempdb. #2933 --- sp_BlitzIndex.sql | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index a2f408797..3f42fb285 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -959,9 +959,17 @@ FROM #Ignore_Databases i; /* Last startup */ -SELECT @DaysUptime = CAST(DATEDIFF(HOUR, create_date, GETDATE()) / 24. AS NUMERIC (23,2)) -FROM sys.databases -WHERE database_id = 2; +IF COLUMNPROPERTY(OBJECT_ID('sys.dm_os_sys_info'),'sqlserver_start_time','ColumnID') IS NOT NULL +BEGIN + SELECT @DaysUptime = CAST(DATEDIFF(HOUR, sqlserver_start_time, GETDATE()) / 24. AS NUMERIC (23,2)) + FROM sys.dm_os_sys_info; +END +ELSE +BEGIN + SELECT @DaysUptime = CAST(DATEDIFF(HOUR, create_date, GETDATE()) / 24. AS NUMERIC (23,2)) + FROM sys.databases + WHERE database_id = 2; +END IF @DaysUptime = 0 OR @DaysUptime IS NULL SET @DaysUptime = .01; From fd3e51efea5f7bc7798448ea591e74a148c93fb9 Mon Sep 17 00:00:00 2001 From: Erik Darling Date: Sat, 12 Jun 2021 09:32:54 -0400 Subject: [PATCH 209/662] Fix #plan_usage definition Change to `DECIMAL(9, 2)` to avoid overflows. --- sp_BlitzCache.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sp_BlitzCache.sql b/sp_BlitzCache.sql index 4ec3d3c91..bde971165 100644 --- a/sp_BlitzCache.sql +++ b/sp_BlitzCache.sql @@ -1565,9 +1565,9 @@ database_id INT CREATE TABLE #plan_usage ( duplicate_plan_hashes BIGINT NULL, - percent_duplicate DECIMAL(5, 2) NULL, + percent_duplicate DECIMAL(9, 2) NULL, single_use_plan_count BIGINT NULL, - percent_single DECIMAL(5, 2) NULL, + percent_single DECIMAL(9, 2) NULL, total_plans BIGINT NULL, spid INT ); From e26c03d8771e9b7a37c680436ec7ca94c61b9a6e Mon Sep 17 00:00:00 2001 From: Erik Darling Date: Sat, 12 Jun 2021 12:44:27 -0400 Subject: [PATCH 210/662] Tweaking the thread usage stuff This commit has the following changes: * I changed the way I get thread time to assign it to a single variable and pass that to the dynamic SQL to prevent needing to update the temp table later (this was explained in an earlier commit) * Checks to make sure the `total_cpu_usage` column exists in `sys.dm_os_schedulers` before executing * Adds an informational message to "headline news" section letting people on <2016 instances know why thread time is 0 --- sp_BlitzFirst.sql | 150 +++++++++++++++++++++++++++++----------------- 1 file changed, 94 insertions(+), 56 deletions(-) diff --git a/sp_BlitzFirst.sql b/sp_BlitzFirst.sql index e4321d22c..34a9b17eb 100644 --- a/sp_BlitzFirst.sql +++ b/sp_BlitzFirst.sql @@ -139,7 +139,10 @@ DECLARE @StringToExecute NVARCHAR(MAX), @UnquotedOutputDatabaseName NVARCHAR(256) = @OutputDatabaseName , @UnquotedOutputSchemaName NVARCHAR(256) = @OutputSchemaName , @LocalServerName NVARCHAR(128) = CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128)), - @dm_exec_query_statistics_xml BIT = 0; + @dm_exec_query_statistics_xml BIT = 0, + @total_cpu_usage BIT = 0, + @get_thread_time_ms NVARCHAR(MAX) = N'', + @thread_time_ms FLOAT = 0; /* Sanitize our inputs */ SELECT @@ -293,6 +296,36 @@ BEGIN @FinishSampleTimeWaitFor = DATEADD(ss, @Seconds, GETDATE()); + IF EXISTS + ( + + SELECT + 1/0 + FROM sys.all_columns AS ac + WHERE ac.object_id = OBJECT_ID('sys.dm_os_schedulers') + AND ac.name = 'total_cpu_usage_ms' + + ) + BEGIN + + SELECT + @total_cpu_usage = 1, + @get_thread_time_ms += + N' + SELECT + @thread_time_ms = + CONVERT + ( + FLOAT, + SUM(s.total_cpu_usage_ms) + ) + FROM sys.dm_os_schedulers AS s + WHERE s.status = ''VISIBLE ONLINE'' + AND s.is_online = 1; + '; + + END + RAISERROR('Now starting diagnostic analysis',10,1) WITH NOWAIT; /* @@ -1302,6 +1335,14 @@ BEGIN END; + IF @total_cpu_usage = 1 + BEGIN + EXEC sys.sp_executesql + @get_thread_time_ms, + N'@thread_time_ms FLOAT OUTPUT', + @thread_time_ms OUTPUT; + END + /* Populate #FileStats, #PerfmonStats, #WaitStats with DMV data. After we finish doing our checks, we'll take another sample and compare them. */ RAISERROR('Capturing first pass of wait stats, perfmon counters, file stats',10,1) WITH NOWAIT; @@ -1315,18 +1356,7 @@ BEGIN CASE @Seconds WHEN 0 THEN 0 - ELSE - ( - SELECT - CONVERT - ( - FLOAT, - SUM(s.total_cpu_usage_ms) - ) - FROM sys.dm_os_schedulers AS s - WHERE s.status = ''VISIBLE ONLINE'' - AND s.is_online = 1 - ) + ELSE @thread_time_ms END AS thread_time_ms, SUM(x.sum_signal_wait_time_ms) AS sum_signal_wait_time_ms, SUM(x.sum_waiting_tasks) AS sum_waiting_tasks @@ -1368,12 +1398,14 @@ BEGIN GROUP BY x.Pass, x.SampleTime, x.wait_type ORDER BY sum_wait_time_ms DESC;' - EXEC sp_executesql - @StringToExecute, - N'@StartSampleTime DATETIMEOFFSET, - @Seconds INT', - @StartSampleTime, - @Seconds; + EXEC sys.sp_executesql + @StringToExecute, + N'@StartSampleTime DATETIMEOFFSET, + @Seconds INT, + @thread_time_ms FLOAT', + @StartSampleTime, + @Seconds, + @thread_time_ms; WITH w AS ( @@ -1386,21 +1418,11 @@ BEGIN ) FROM #WaitStats AS ws WHERE Pass = 1 - ), - m AS - ( - SELECT - max_thread = - MAX(ws.thread_time_ms) - FROM #WaitStats AS ws - WHERE Pass = 1 ) UPDATE ws - SET ws.thread_time_ms = - m.max_thread + w.total_waits + SET ws.thread_time_ms += w.total_waits FROM #WaitStats AS ws CROSS JOIN w - CROSS JOIN m WHERE ws.Pass = 1 OPTION(RECOMPILE); @@ -2509,6 +2531,13 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, WAITFOR TIME @FinishSampleTimeWaitFor; END; + IF @total_cpu_usage = 1 + BEGIN + EXEC sys.sp_executesql + @get_thread_time_ms, + N'@thread_time_ms FLOAT OUTPUT', + @thread_time_ms OUTPUT; + END RAISERROR('Capturing second pass of wait stats, perfmon counters, file stats',10,1) WITH NOWAIT; /* Populate #FileStats, #PerfmonStats, #WaitStats with DMV data. In a second, we'll compare these. */ @@ -2519,17 +2548,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, x.SampleTime, x.wait_type, SUM(x.sum_wait_time_ms) AS sum_wait_time_ms, - ( - SELECT - CONVERT - ( - FLOAT, - SUM(s.total_cpu_usage_ms) - ) - FROM sys.dm_os_schedulers AS s - WHERE s.status = ''VISIBLE ONLINE'' - AND s.is_online = 1 - ) AS thread_time_ms, + @thread_time_ms AS thread_time_ms, SUM(x.sum_signal_wait_time_ms) AS sum_signal_wait_time_ms, SUM(x.sum_waiting_tasks) AS sum_waiting_tasks FROM ( @@ -2570,10 +2589,14 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, GROUP BY x.Pass, x.SampleTime, x.wait_type ORDER BY sum_wait_time_ms DESC;'; - EXEC sp_executesql - @StringToExecute, - N'@Seconds INT', - @Seconds; + EXEC sys.sp_executesql + @StringToExecute, + N'@StartSampleTime DATETIMEOFFSET, + @Seconds INT, + @thread_time_ms FLOAT', + @StartSampleTime, + @Seconds, + @thread_time_ms; WITH w AS ( @@ -2586,21 +2609,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, ) FROM #WaitStats AS ws WHERE Pass = 2 - ), - m AS - ( - SELECT - max_thread = - MAX(ws.thread_time_ms) - FROM #WaitStats AS ws - WHERE Pass = 2 ) UPDATE ws - SET ws.thread_time_ms = - m.max_thread + w.total_waits + SET ws.thread_time_ms += w.total_waits FROM #WaitStats AS ws CROSS JOIN w - CROSS JOIN m WHERE ws.Pass = 2 OPTION(RECOMPILE); @@ -3401,6 +3414,31 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, END; /* IF @Seconds >= 30 */ + IF /* Let people on <2016 know about the thread time column */ + ( + @Seconds > 0 + AND @total_cpu_usage = 0 + ) + BEGIN + INSERT INTO + #BlitzFirstResults + ( + CheckID, + Priority, + FindingsGroup, + Finding, + Details, + URL + ) + SELECT + 48, + 254, + N'Informational', + N'Thread Time will always be 0 in versions prior to SQL Server 2016', + N'sys.dm_os_schedulers doesn''t have total_cpu_usage_ms', + N'https://docs.microsoft.com/en-us/sql/relational-databases/system-dynamic-management-views/sys-dm-os-schedulers-transact-sql' + + END /* Let people on <2016 know about the thread time column */ /* If we didn't find anything, apologize. */ IF NOT EXISTS (SELECT * FROM #BlitzFirstResults WHERE Priority < 250) From 0eddc9d471cdb641dc5cefc6a343d7dadfc60207 Mon Sep 17 00:00:00 2001 From: Erik Darling Date: Tue, 22 Jun 2021 18:37:20 -0400 Subject: [PATCH 211/662] Use the cache Update to use the plan cache if < 2016 Make the informational section a little more helpful for people to gauge usefulness --- sp_BlitzFirst.sql | 35 +++++++++++++++++++++++++++++------ 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/sp_BlitzFirst.sql b/sp_BlitzFirst.sql index 34a9b17eb..684744617 100644 --- a/sp_BlitzFirst.sql +++ b/sp_BlitzFirst.sql @@ -321,10 +321,28 @@ BEGIN ) FROM sys.dm_os_schedulers AS s WHERE s.status = ''VISIBLE ONLINE'' - AND s.is_online = 1; + AND s.is_online = 1 + OPTION(RECOMPILE); '; END + ELSE + BEGIN + SELECT + @total_cpu_usage = 0, + @get_thread_time_ms += + N' + SELECT + @thread_time_ms = + CONVERT + ( + FLOAT, + SUM(s.total_worker_time / 1000.) + ) + FROM sys.dm_exec_query_stats AS s + OPTION(RECOMPILE); + '; + END RAISERROR('Now starting diagnostic analysis',10,1) WITH NOWAIT; @@ -1335,7 +1353,7 @@ BEGIN END; - IF @total_cpu_usage = 1 + IF @total_cpu_usage IN (0, 1) BEGIN EXEC sys.sp_executesql @get_thread_time_ms, @@ -2531,7 +2549,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, WAITFOR TIME @FinishSampleTimeWaitFor; END; - IF @total_cpu_usage = 1 + IF @total_cpu_usage IN (0, 1) BEGIN EXEC sys.sp_executesql @get_thread_time_ms, @@ -3434,10 +3452,15 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 48, 254, N'Informational', - N'Thread Time will always be 0 in versions prior to SQL Server 2016', - N'sys.dm_os_schedulers doesn''t have total_cpu_usage_ms', + N'Thread Time comes from the plan cache in versions earlier than 2016, and is not as reliable', + N'The oldest plan in your cache is from ' + + CONVERT(nvarchar(30), MIN(s.creation_time)) + + N' and your server was last restarted on ' + + CONVERT(nvarchar(30), MAX(o.sqlserver_start_time)), N'https://docs.microsoft.com/en-us/sql/relational-databases/system-dynamic-management-views/sys-dm-os-schedulers-transact-sql' - + FROM sys.dm_exec_query_stats AS s + CROSS JOIN sys.dm_os_sys_info AS o + OPTION(RECOMPILE); END /* Let people on <2016 know about the thread time column */ /* If we didn't find anything, apologize. */ From 3706ae20bf56047c9cf173956e0c1338737acfbc Mon Sep 17 00:00:00 2001 From: Johan Parlevliet <36136880+parlevjo2@users.noreply.github.com> Date: Sun, 27 Jun 2021 14:10:40 +0200 Subject: [PATCH 212/662] Update sp_Blitz.sql Changes which makes the sp_Blitz output table order the same as onscreen table --- sp_Blitz.sql | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index 586bf8af5..efdf8e1d0 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -9029,7 +9029,7 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 IF (OBJECT_ID('tempdb..##BlitzResults', 'U') IS NOT NULL) DROP TABLE ##BlitzResults; SELECT * INTO ##BlitzResults FROM #BlitzResults; SET @query_result_separator = char(9); - SET @StringToExecute = 'SET NOCOUNT ON;SELECT [Priority] , [FindingsGroup] , [Finding] , [DatabaseName] , [URL] , [Details] , CheckID FROM ##BlitzResults ORDER BY Priority , FindingsGroup, Finding, Details; SET NOCOUNT OFF;'; + SET @StringToExecute = 'SET NOCOUNT ON;SELECT [Priority] , [FindingsGroup] , [Finding] , [DatabaseName] , [URL] , [Details] , CheckID FROM ##BlitzResults ORDER BY Priority , FindingsGroup , Finding , DatabaseName , Details; SET NOCOUNT OFF;'; SET @EmailSubject = 'sp_Blitz Results for ' + @@SERVERNAME; SET @EmailBody = 'sp_Blitz ' + CAST(CONVERT(DATETIME, @VersionDate, 102) AS VARCHAR(100)) + '. http://FirstResponderKit.org'; IF @EmailProfile IS NULL @@ -9174,7 +9174,7 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 + @OutputTableName + ' (ServerName, CheckDate, CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details, QueryPlan, QueryPlanFiltered) SELECT ''' + CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128)) - + ''', SYSDATETIMEOFFSET(), CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details, CAST(QueryPlan AS NVARCHAR(MAX)), QueryPlanFiltered FROM #BlitzResults ORDER BY Priority , FindingsGroup , Finding , Details'; + + ''', SYSDATETIMEOFFSET(), CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details, CAST(QueryPlan AS NVARCHAR(MAX)), QueryPlanFiltered FROM #BlitzResults ORDER BY Priority , FindingsGroup , Finding , DatabaseName , Details'; EXEC(@StringToExecute); END; @@ -9191,7 +9191,7 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 + @OutputTableName + ' (ServerName, CheckDate, CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details, QueryPlan, QueryPlanFiltered) SELECT ''' + CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128)) - + ''', SYSDATETIMEOFFSET(), CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details, CAST(QueryPlan AS NVARCHAR(MAX)), QueryPlanFiltered FROM #BlitzResults ORDER BY Priority , FindingsGroup , Finding , Details'; + + ''', SYSDATETIMEOFFSET(), CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details, CAST(QueryPlan AS NVARCHAR(MAX)), QueryPlanFiltered FROM #BlitzResults ORDER BY Priority , FindingsGroup , Finding , DatabaseName , Details'; END; ELSE begin @@ -9204,7 +9204,7 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 + @OutputTableName + ' (ServerName, CheckDate, CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details, QueryPlan, QueryPlanFiltered) SELECT ''' + CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128)) - + ''', SYSDATETIMEOFFSET(), CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details, QueryPlan, QueryPlanFiltered FROM #BlitzResults ORDER BY Priority , FindingsGroup , Finding , Details'; + + ''', SYSDATETIMEOFFSET(), CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details, QueryPlan, QueryPlanFiltered FROM #BlitzResults ORDER BY Priority , FindingsGroup , Finding , DatabaseName , Details'; END; EXEC(@StringToExecute); @@ -9240,7 +9240,7 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 + @OutputTableName + ' (ServerName, CheckDate, CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details, QueryPlan, QueryPlanFiltered) SELECT ''' + CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128)) - + ''', SYSDATETIMEOFFSET(), CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details, QueryPlan, QueryPlanFiltered FROM #BlitzResults ORDER BY Priority , FindingsGroup , Finding , Details'; + + ''', SYSDATETIMEOFFSET(), CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details, QueryPlan, QueryPlanFiltered FROM #BlitzResults ORDER BY Priority , FindingsGroup , Finding , DatabaseName , Details'; EXEC(@StringToExecute); END; From cd0481e7d69d40fb2612183b40ea019dea9106e9 Mon Sep 17 00:00:00 2001 From: AdeDBA <33764798+Adedba@users.noreply.github.com> Date: Thu, 1 Jul 2021 21:52:38 +0100 Subject: [PATCH 213/662] #2941 - Addresses incorrect number of backups running #2941 - Added additional join clause to Check 1 so that the correct amount of backups running is shown when the database context of the backup command is ran from a user database. --- sp_BlitzFirst.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sp_BlitzFirst.sql b/sp_BlitzFirst.sql index 9b56b1141..34ade87cc 100644 --- a/sp_BlitzFirst.sql +++ b/sp_BlitzFirst.sql @@ -1439,7 +1439,7 @@ BEGIN WHERE resource_type = N'DATABASE' AND request_mode = N'S' AND request_status = N'GRANT' - AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.session_id = db.request_session_id + AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.session_id = db.request_session_id AND s.database_id = db.resource_database_id CROSS APPLY sys.dm_exec_query_plan(r.plan_handle) pl WHERE r.command LIKE 'BACKUP%' AND r.start_time <= DATEADD(minute, -5, GETDATE()) From f3a259b773d3d64fb420450b232c61d45a2e4fc0 Mon Sep 17 00:00:00 2001 From: Erik Darling Date: Fri, 9 Jul 2021 10:23:55 -0400 Subject: [PATCH 214/662] add target recovery interval check Adds a check when available (2012+) to see if target recovery interval is <> 60, and suggest setting it there. Sure, it could be 61 or something, but I'm not going down that path. I quoted out the current check for target recovery interval that looks to see if it's not 0 or 60, to not give seemingly conflicting advice. Right now, the URL is to Aaron's post on the subject. @BrentOzar will need to go code it for uniformity. I've also updated the documentation to reflect the new high check id, etc. --- Documentation/sp_Blitz_Checks_by_Priority.md | 5 +- sp_Blitz.sql | 81 +++++++++++++++++++- 2 files changed, 80 insertions(+), 6 deletions(-) diff --git a/Documentation/sp_Blitz_Checks_by_Priority.md b/Documentation/sp_Blitz_Checks_by_Priority.md index 05fd9b64d..c4c954e42 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: 256. -If you want to add a new one, start at 257. +CURRENT HIGH CHECKID: 257. +If you want to add a new one, start at 258. | Priority | FindingsGroup | Finding | URL | CheckID | |----------|-----------------------------|---------------------------------------------------------|------------------------------------------------------------------------|----------| @@ -64,6 +64,7 @@ If you want to add a new one, start at 257. | 50 | Performance | Snapshotting Too Many Databases | https://www.BrentOzar.com/go/toomanysnaps | 236 | | 50 | Performance | Too Much Free Memory | https://www.BrentOzar.com/go/freememory | 165 | | 50 | Performance | Wait Stats Cleared Recently| | 205 | +| 50 | Performance | Suboptimal recovery interval| https://sqlperformance.com/2020/05/system-configuration/0-to-60-switching-to-indirect-checkpoints | 257 | | 50 | Reliability | Full Text Indexes Not Updating | https://www.BrentOzar.com/go/fulltext | 113 | | 50 | Reliability | Page Verification Not Optimal | https://www.BrentOzar.com/go/torn | 14 | | 50 | Reliability | Possibly Broken Log Shipping | https://www.BrentOzar.com/go/shipping | 111 | diff --git a/sp_Blitz.sql b/sp_Blitz.sql index efdf8e1d0..03728fed8 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -4312,10 +4312,10 @@ AS SELECT 'containment', 0, 141, 210, 'Containment Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL FROM sys.all_columns WHERE name = 'containment' AND object_id = OBJECT_ID('sys.databases'); - INSERT INTO #DatabaseDefaults - SELECT 'target_recovery_time_in_seconds', 0, 142, 210, 'Target Recovery Time Changed', 'https://www.brentozar.com/go/dbdefaults', NULL - FROM sys.all_columns - WHERE name = 'target_recovery_time_in_seconds' AND object_id = OBJECT_ID('sys.databases'); + --INSERT INTO #DatabaseDefaults + -- SELECT 'target_recovery_time_in_seconds', 0, 142, 210, 'Target Recovery Time Changed', 'https://www.brentozar.com/go/dbdefaults', NULL + -- FROM sys.all_columns + -- WHERE name = 'target_recovery_time_in_seconds' AND object_id = OBJECT_ID('sys.databases'); INSERT INTO #DatabaseDefaults SELECT 'delayed_durability', 0, 143, 210, 'Delayed Durability Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL FROM sys.all_columns @@ -4359,6 +4359,79 @@ AS CLOSE DatabaseDefaultsLoop; DEALLOCATE DatabaseDefaultsLoop; + +/* Check if target recovery interval <> 60 */ +IF + @ProductVersionMajor >= 10 + AND NOT EXISTS + ( + SELECT + 1/0 + FROM #SkipChecks AS sc + WHERE sc.DatabaseName IS NULL + AND sc.CheckID = 257 + ) + BEGIN + IF EXISTS + ( + SELECT + 1/0 + FROM sys.all_columns AS ac + WHERE ac.name = 'target_recovery_time_in_seconds' + AND ac.object_id = OBJECT_ID('sys.databases') + ) + BEGIN + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 257) WITH NOWAIT; + + DECLARE + @tri nvarchar(max) = N' + SELECT + DatabaseName = + d.name, + CheckId = + 257, + Priority = + 50, + FindingsGroup = + N''Performance'', + Finding = + N''Suboptimal recovery interval'', + URL = + N''https://sqlperformance.com/2020/05/system-configuration/0-to-60-switching-to-indirect-checkpoints'', + Details = + N''The database '' + + QUOTENAME(d.name) + + N'' has a target recovery interval of '' + + RTRIM(d.target_recovery_time_in_seconds) + + CASE + WHEN d.target_recovery_time_in_seconds = 0 + THEN N'', which is a legacy default, and should be changed to 60.'' + WHEN d.target_recovery_time_in_seconds <> 0 + THEN N'', which is probably a mistake, and should be changed to 60.'' + END + FROM sys.databases AS d + WHERE d.database_id > 4 + AND d.is_read_only = 0 + AND d.is_in_standby = 0 + AND d.target_recovery_time_in_seconds <> 60; + '; + + INSERT INTO + #BlitzResults + ( + DatabaseName, + CheckID, + Priority, + FindingsGroup, + Finding, + URL, + Details + ) + EXEC sys.sp_executesql + @tri; + + END; + END; /*This checks to see if Agent is Offline*/ From 3efe2072e6ade4746a17cf1ebd1288c73399da51 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Tue, 13 Jul 2021 08:00:22 +0000 Subject: [PATCH 215/662] sp_Blitz check list for recovery interval Just tweaking the title and putting it in alphabetical order. --- Documentation/sp_Blitz_Checks_by_Priority.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/sp_Blitz_Checks_by_Priority.md b/Documentation/sp_Blitz_Checks_by_Priority.md index c4c954e42..f870b3fea 100644 --- a/Documentation/sp_Blitz_Checks_by_Priority.md +++ b/Documentation/sp_Blitz_Checks_by_Priority.md @@ -61,10 +61,10 @@ If you want to add a new one, start at 258. | 50 | Performance | Poison Wait Detected | https://www.BrentOzar.com/go/poison | 107 | | 50 | Performance | Poison Wait Detected: CMEMTHREAD & NUMA | https://www.BrentOzar.com/go/poison | 162 | | 50 | Performance | Poison Wait Detected: Serializable Locking | https://www.BrentOzar.com/go/serializable | 121 | +| 50 | Performance | Recovery Interval Not Optimal| https://sqlperformance.com/2020/05/system-configuration/0-to-60-switching-to-indirect-checkpoints | 257 | | 50 | Performance | Snapshotting Too Many Databases | https://www.BrentOzar.com/go/toomanysnaps | 236 | | 50 | Performance | Too Much Free Memory | https://www.BrentOzar.com/go/freememory | 165 | | 50 | Performance | Wait Stats Cleared Recently| | 205 | -| 50 | Performance | Suboptimal recovery interval| https://sqlperformance.com/2020/05/system-configuration/0-to-60-switching-to-indirect-checkpoints | 257 | | 50 | Reliability | Full Text Indexes Not Updating | https://www.BrentOzar.com/go/fulltext | 113 | | 50 | Reliability | Page Verification Not Optimal | https://www.BrentOzar.com/go/torn | 14 | | 50 | Reliability | Possibly Broken Log Shipping | https://www.BrentOzar.com/go/shipping | 111 | From 9bba9e502081a8e2aa47bd8ab7293f681b274398 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Tue, 13 Jul 2021 08:01:55 +0000 Subject: [PATCH 216/662] sp_Blitz recovery interval title tweak Just changing it to match some of the other checks. --- sp_Blitz.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index 03728fed8..a2c1da7ce 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -4395,7 +4395,7 @@ IF FindingsGroup = N''Performance'', Finding = - N''Suboptimal recovery interval'', + N''Recovery Interval Not Optimal'', URL = N''https://sqlperformance.com/2020/05/system-configuration/0-to-60-switching-to-indirect-checkpoints'', Details = From 0eeb5a5532fa2c58e3157c7a107c861db72890c3 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Tue, 13 Jul 2021 08:06:20 +0000 Subject: [PATCH 217/662] #2946 sp_Blitz query store rtm check Only alert for SQL Server 2016. Closes #2946. --- sp_Blitz.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index a2c1da7ce..305abef37 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -6158,7 +6158,7 @@ IF @ProductVersionMajor >= 10 END; - IF @ProductVersionMajor >= 13 AND @ProductVersionMinor < 2149 --CU1 has the fix in it + IF @ProductVersionMajor = 13 AND @ProductVersionMinor < 2149 --2016 CU1 has the fix in it AND NOT EXISTS ( SELECT 1 FROM #SkipChecks WHERE DatabaseName IS NULL AND CheckID = 182 ) From e4e390b80cedb50d0e827019a3b152bb2aeec8ac Mon Sep 17 00:00:00 2001 From: Anthony Green <40231097+Ant-Green@users.noreply.github.com> Date: Tue, 13 Jul 2021 10:58:07 +0100 Subject: [PATCH 218/662] New 2019CU11, 2017CU25 New 2019CU11, 2017CU25 --- SqlServerVersions.sql | 2 ++ 1 file changed, 2 insertions(+) diff --git a/SqlServerVersions.sql b/SqlServerVersions.sql index 61789e5e3..5768df9e0 100644 --- a/SqlServerVersions.sql +++ b/SqlServerVersions.sql @@ -41,6 +41,7 @@ DELETE FROM dbo.SqlServerVersions; INSERT INTO dbo.SqlServerVersions (MajorVersionNumber, MinorVersionNumber, Branch, [Url], ReleaseDate, MainstreamSupportEndDate, ExtendedSupportEndDate, MajorVersionName, MinorVersionName) VALUES + (15, 4138, 'CU11', 'https://support.microsoft.com/en-us/help/5003249', '2021-06-10', '2025-01-01', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 11'), (15, 4123, 'CU10', 'https://support.microsoft.com/en-us/help/5001090', '2021-04-06', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 10'), (15, 4102, 'CU9', 'https://support.microsoft.com/en-us/help/5000642', '2021-02-11', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 9 '), (15, 4073, 'CU8 GDR', 'https://support.microsoft.com/en-us/help/4583459', '2021-01-12', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 8 GDR '), @@ -54,6 +55,7 @@ VALUES (15, 4003, 'CU1', 'https://support.microsoft.com/en-us/help/4527376', '2020-01-07', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 1 '), (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 '), + (14, 3410, 'RTM CU25', 'https://support.microsoft.com/en-us/help/5003830', '2021-07-12', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 25'), (14, 3391, 'RTM CU24', 'https://support.microsoft.com/en-us/help/5001228', '2021-05-10', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 24'), (14, 3381, 'RTM CU23', 'https://support.microsoft.com/en-us/help/5000685', '2021-02-25', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 23'), (14, 3370, 'RTM CU22 GDR', 'https://support.microsoft.com/en-us/help/4583457', '2021-01-12', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 22 GDR'), From f51954f068a50095194acc3bf8580fc98fa15bb2 Mon Sep 17 00:00:00 2001 From: DavidSchanzer <68979631+DavidSchanzer@users.noreply.github.com> Date: Wed, 14 Jul 2021 16:21:25 +1000 Subject: [PATCH 219/662] Fixed bug caused by lack of implicit casting between NVARCHAR (for ServerName) and sql_variant (for SERVERPROPERTY) - need to explicitly cast so that equality condition works. --- sp_Blitz.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index 305abef37..a84350a2f 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -356,7 +356,7 @@ AS SET @StringToExecute += QUOTENAME(@SkipChecksServer) + N'.'; END SET @StringToExecute += QUOTENAME(@SkipChecksDatabase) + N'.' + QUOTENAME(@SkipChecksSchema) + N'.' + QUOTENAME(@SkipChecksTable) - + N' WHERE ServerName IS NULL OR ServerName = SERVERPROPERTY(''ServerName'') OPTION (RECOMPILE);'; + + N' WHERE ServerName IS NULL OR ServerName = CAST(SERVERPROPERTY(''ServerName'') AS NVARCHAR(128)) OPTION (RECOMPILE);'; EXEC(@StringToExecute); END; From 5d68b52deb00a3cbd95b9bc8a84ca197ffc25a35 Mon Sep 17 00:00:00 2001 From: John McCall Date: Tue, 20 Jul 2021 12:55:22 -0400 Subject: [PATCH 220/662] fix trailing slash for root drives Fixes #2952 --- sp_DatabaseRestore.sql | 92 +++++++++++++++++++++--------------------- 1 file changed, 46 insertions(+), 46 deletions(-) diff --git a/sp_DatabaseRestore.sql b/sp_DatabaseRestore.sql index 545062a63..6f765c00b 100755 --- a/sp_DatabaseRestore.sql +++ b/sp_DatabaseRestore.sql @@ -344,116 +344,116 @@ CREATE TABLE #Headers ); /* -Correct paths in case people forget a final "\" +Correct paths in case people forget a final "\" or "/" */ /*Full*/ -IF (SELECT RIGHT(@BackupPathFull, 1)) <> '\' AND CHARINDEX('\', @BackupPathFull) > 0 --Has to end in a '\' -BEGIN - IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Fixing @BackupPathFull to add a "\"', 0, 1) WITH NOWAIT; - SET @BackupPathFull += N'\'; -END; -ELSE IF (SELECT RIGHT(@BackupPathFull, 1)) <> '/' AND CHARINDEX('/', @BackupPathFull) > 0 --Has to end in a '/' +IF (SELECT RIGHT(@BackupPathFull, 1)) <> '/' AND CHARINDEX('/', @BackupPathFull) > 0 --Has to end in a '/' BEGIN IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Fixing @BackupPathFull to add a "/"', 0, 1) WITH NOWAIT; SET @BackupPathFull += N'/'; END; -/*Diff*/ -IF (SELECT RIGHT(@BackupPathDiff, 1)) <> '\' AND CHARINDEX('\', @BackupPathDiff) > 0 --Has to end in a '\' +ELSE IF (SELECT RIGHT(@BackupPathFull, 1)) <> '\' --Has to end in a '\' BEGIN - IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Fixing @BackupPathDiff to add a "\"', 0, 1) WITH NOWAIT; - SET @BackupPathDiff += N'\'; + IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Fixing @BackupPathFull to add a "\"', 0, 1) WITH NOWAIT; + SET @BackupPathFull += N'\'; END; -ELSE IF (SELECT RIGHT(@BackupPathDiff, 1)) <> '/' AND CHARINDEX('/', @BackupPathDiff) > 0 --Has to end in a '/' +/*Diff*/ +IF (SELECT RIGHT(@BackupPathDiff, 1)) <> '/' AND CHARINDEX('/', @BackupPathDiff) > 0 --Has to end in a '/' BEGIN IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Fixing @BackupPathDiff to add a "/"', 0, 1) WITH NOWAIT; SET @BackupPathDiff += N'/'; END; -/*Log*/ -IF (SELECT RIGHT(@BackupPathLog, 1)) <> '\' AND CHARINDEX('\', @BackupPathLog) > 0 --Has to end in a '\' +ELSE IF (SELECT RIGHT(@BackupPathDiff, 1)) <> '\' --Has to end in a '\' BEGIN - IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Fixing @BackupPathLog to add a "\"', 0, 1) WITH NOWAIT; - SET @BackupPathLog += N'\'; + IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Fixing @BackupPathDiff to add a "\"', 0, 1) WITH NOWAIT; + SET @BackupPathDiff += N'\'; END; -ELSE IF (SELECT RIGHT(@BackupPathLog, 1)) <> '/' AND CHARINDEX('/', @BackupPathLog) > 0 --Has to end in a '/' +/*Log*/ +IF (SELECT RIGHT(@BackupPathLog, 1)) <> '/' AND CHARINDEX('/', @BackupPathLog) > 0 --Has to end in a '/' BEGIN IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Fixing @BackupPathLog to add a "/"', 0, 1) WITH NOWAIT; SET @BackupPathLog += N'/'; END; +IF (SELECT RIGHT(@BackupPathLog, 1)) <> '\' --Has to end in a '\' +BEGIN + IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Fixing @BackupPathLog to add a "\"', 0, 1) WITH NOWAIT; + SET @BackupPathLog += N'\'; +END; /*Move Data File*/ IF NULLIF(@MoveDataDrive, '') IS NULL BEGIN IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Getting default data drive for @MoveDataDrive', 0, 1) WITH NOWAIT; SET @MoveDataDrive = CAST(SERVERPROPERTY('InstanceDefaultDataPath') AS nvarchar(260)); END; -IF (SELECT RIGHT(@MoveDataDrive, 1)) <> '\' AND CHARINDEX('\', @MoveDataDrive) > 0 --Has to end in a '\' -BEGIN - IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Fixing @MoveDataDrive to add a "\"', 0, 1) WITH NOWAIT; - SET @MoveDataDrive += N'\'; -END; -ELSE IF (SELECT RIGHT(@MoveDataDrive, 1)) <> '/' AND CHARINDEX('/', @MoveDataDrive) > 0 --Has to end in a '/' +IF (SELECT RIGHT(@MoveDataDrive, 1)) <> '/' AND CHARINDEX('/', @MoveDataDrive) > 0 --Has to end in a '/' BEGIN IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Fixing @MoveDataDrive to add a "/"', 0, 1) WITH NOWAIT; SET @MoveDataDrive += N'/'; END; +ELSE IF (SELECT RIGHT(@MoveDataDrive, 1)) <> '\' --Has to end in a '\' +BEGIN + IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Fixing @MoveDataDrive to add a "\"', 0, 1) WITH NOWAIT; + SET @MoveDataDrive += N'\'; +END; /*Move Log File*/ IF NULLIF(@MoveLogDrive, '') IS NULL BEGIN IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Getting default log drive for @MoveLogDrive', 0, 1) WITH NOWAIT; SET @MoveLogDrive = CAST(SERVERPROPERTY('InstanceDefaultLogPath') AS nvarchar(260)); END; -IF (SELECT RIGHT(@MoveLogDrive, 1)) <> '\' AND CHARINDEX('\', @MoveLogDrive) > 0 --Has to end in a '\' -BEGIN - IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Fixing @MoveLogDrive to add a "\"', 0, 1) WITH NOWAIT; - SET @MoveLogDrive += N'\'; -END; -ELSE IF (SELECT RIGHT(@MoveLogDrive, 1)) <> '/' AND CHARINDEX('/', @MoveLogDrive) > 0 --Has to end in a '/' +IF (SELECT RIGHT(@MoveLogDrive, 1)) <> '/' AND CHARINDEX('/', @MoveLogDrive) > 0 --Has to end in a '/' BEGIN IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Fixing@MoveLogDrive to add a "/"', 0, 1) WITH NOWAIT; SET @MoveLogDrive += N'/'; END; +ELSE IF (SELECT RIGHT(@MoveLogDrive, 1)) <> '\' --Has to end in a '\' +BEGIN + IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Fixing @MoveLogDrive to add a "\"', 0, 1) WITH NOWAIT; + SET @MoveLogDrive += N'\'; +END; /*Move Filestream File*/ IF NULLIF(@MoveFilestreamDrive, '') IS NULL BEGIN IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Setting default data drive for @MoveFilestreamDrive', 0, 1) WITH NOWAIT; SET @MoveFilestreamDrive = CAST(SERVERPROPERTY('InstanceDefaultDataPath') AS nvarchar(260)); END; -IF (SELECT RIGHT(@MoveFilestreamDrive, 1)) <> '\' AND CHARINDEX('\', @MoveFilestreamDrive) > 0 --Has to end in a '\' -BEGIN - IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Fixing @MoveFilestreamDrive to add a "\"', 0, 1) WITH NOWAIT; - SET @MoveFilestreamDrive += N'\'; -END; -ELSE IF (SELECT RIGHT(@MoveFilestreamDrive, 1)) <> '/' AND CHARINDEX('/', @MoveFilestreamDrive) > 0 --Has to end in a '/' +IF (SELECT RIGHT(@MoveFilestreamDrive, 1)) <> '/' AND CHARINDEX('/', @MoveFilestreamDrive) > 0 --Has to end in a '/' BEGIN IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Fixing @MoveFilestreamDrive to add a "/"', 0, 1) WITH NOWAIT; SET @MoveFilestreamDrive += N'/'; END; +ELSE IF (SELECT RIGHT(@MoveFilestreamDrive, 1)) <> '\' --Has to end in a '\' +BEGIN + IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Fixing @MoveFilestreamDrive to add a "\"', 0, 1) WITH NOWAIT; + SET @MoveFilestreamDrive += N'\'; +END; /*Move FullText Catalog File*/ IF NULLIF(@MoveFullTextCatalogDrive, '') IS NULL BEGIN IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Setting default data drive for @MoveFullTextCatalogDrive', 0, 1) WITH NOWAIT; SET @MoveFullTextCatalogDrive = CAST(SERVERPROPERTY('InstanceDefaultDataPath') AS nvarchar(260)); END; -IF (SELECT RIGHT(@MoveFullTextCatalogDrive, 1)) <> '\' AND CHARINDEX('\', @MoveFullTextCatalogDrive) > 0 --Has to end in a '\' -BEGIN - IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Fixing @MoveFullTextCatalogDrive to add a "\"', 0, 1) WITH NOWAIT; - SET @MoveFullTextCatalogDrive += N'\'; -END; -ELSE IF (SELECT RIGHT(@MoveFullTextCatalogDrive, 1)) <> '/' AND CHARINDEX('/', @MoveFullTextCatalogDrive) > 0 --Has to end in a '/' +IF (SELECT RIGHT(@MoveFullTextCatalogDrive, 1)) <> '/' AND CHARINDEX('/', @MoveFullTextCatalogDrive) > 0 --Has to end in a '/' BEGIN IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Fixing @MoveFullTextCatalogDrive to add a "/"', 0, 1) WITH NOWAIT; SET @MoveFullTextCatalogDrive += N'/'; END; -/*Standby Undo File*/ -IF (SELECT RIGHT(@StandbyUndoPath, 1)) <> '\' AND CHARINDEX('\', @StandbyUndoPath) > 0 --Has to end in a '\' +IF (SELECT RIGHT(@MoveFullTextCatalogDrive, 1)) <> '\' --Has to end in a '\' BEGIN - IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Fixing @StandbyUndoPath to add a "\"', 0, 1) WITH NOWAIT; - SET @StandbyUndoPath += N'\'; + IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Fixing @MoveFullTextCatalogDrive to add a "\"', 0, 1) WITH NOWAIT; + SET @MoveFullTextCatalogDrive += N'\'; END; -ELSE IF (SELECT RIGHT(@StandbyUndoPath, 1)) <> '/' AND CHARINDEX('/', @StandbyUndoPath) > 0 --Has to end in a '/' +/*Standby Undo File*/ +IF (SELECT RIGHT(@StandbyUndoPath, 1)) <> '/' AND CHARINDEX('/', @StandbyUndoPath) > 0 --Has to end in a '/' BEGIN IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Fixing @StandbyUndoPath to add a "/"', 0, 1) WITH NOWAIT; SET @StandbyUndoPath += N'/'; END; +IF (SELECT RIGHT(@StandbyUndoPath, 1)) <> '\' --Has to end in a '\' +BEGIN + IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Fixing @StandbyUndoPath to add a "\"', 0, 1) WITH NOWAIT; + SET @StandbyUndoPath += N'\'; +END; IF @RestoreDatabaseName IS NULL OR @RestoreDatabaseName LIKE N'' /*use LIKE instead of =, otherwise N'' = N' '. See: https://www.brentozar.com/archive/2017/04/surprising-behavior-trailing-spaces/ */ BEGIN From 1bf6fcfd60449067a4a5fe08921fa635ed386ce2 Mon Sep 17 00:00:00 2001 From: Orestes Date: Wed, 21 Jul 2021 18:11:25 +0100 Subject: [PATCH 221/662] fix for issue 2954 --- sp_BlitzIndex.sql | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index 3f42fb285..e8cb35c05 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -2725,17 +2725,18 @@ BEGIN /* Show histograms for all stats on this table. More info: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/1900 */ IF EXISTS (SELECT * FROM sys.all_objects WHERE name = 'dm_db_stats_histogram') BEGIN - SET @dsql=N'SELECT s.name AS [Stat Name], c.name AS [Leading Column Name], hist.step_number AS [Step Number], + SET @dsql = N'USE ' + QUOTENAME(@DatabaseName) + N'; + SELECT s.name AS [Stat Name], c.name AS [Leading Column Name], hist.step_number AS [Step Number], hist.range_high_key AS [Range High Key], hist.range_rows AS [Range Rows], hist.equal_rows AS [Equal Rows], hist.distinct_range_rows AS [Distinct Range Rows], hist.average_range_rows AS [Average Range Rows], s.auto_created AS [Auto-Created], s.user_created AS [User-Created], props.last_updated AS [Last Updated], props.modification_counter AS [Modification Counter], props.rows AS [Table Rows], props.rows_sampled AS [Rows Sampled], s.stats_id AS [StatsID] - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.stats AS s - INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.stats_columns sc ON s.object_id = sc.object_id AND s.stats_id = sc.stats_id AND sc.stats_column_id = 1 - INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns c ON sc.object_id = c.object_id AND sc.column_id = c.column_id - CROSS APPLY ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_stats_properties(s.object_id, s.stats_id) AS props - CROSS APPLY ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_stats_histogram(s.[object_id], s.stats_id) AS hist + FROM sys.stats AS s + INNER JOIN sys.stats_columns sc ON s.object_id = sc.object_id AND s.stats_id = sc.stats_id AND sc.stats_column_id = 1 + INNER JOIN sys.columns c ON sc.object_id = c.object_id AND sc.column_id = c.column_id + CROSS APPLY sys.dm_db_stats_properties(s.object_id, s.stats_id) AS props + CROSS APPLY sys.dm_db_stats_histogram(s.[object_id], s.stats_id) AS hist WHERE s.object_id = @ObjectID ORDER BY s.auto_created, s.user_created, s.name, hist.step_number;'; EXEC sp_executesql @dsql, N'@ObjectID INT', @ObjectID; From 8cca7b95a8632f5cd13ae64606c8765944db083e Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Sun, 25 Jul 2021 08:41:35 +0000 Subject: [PATCH 222/662] Bumping version numbers and dates Working on the 2021-07-25 release. --- Install-All-Scripts.sql | 580 +++++++++++++++++++----- Install-Core-Blitz-No-Query-Store.sql | 478 ++++++++++++++++--- Install-Core-Blitz-With-Query-Store.sql | 480 +++++++++++++++++--- sp_AllNightLog.sql | 2 +- sp_AllNightLog_Setup.sql | 2 +- sp_Blitz.sql | 2 +- sp_BlitzAnalysis.sql | 2 +- sp_BlitzBackups.sql | 2 +- sp_BlitzCache.sql | 2 +- sp_BlitzFirst.sql | 2 +- sp_BlitzInMemoryOLTP.sql | 2 +- sp_BlitzIndex.sql | 2 +- sp_BlitzLock.sql | 2 +- sp_BlitzQueryStore.sql | 2 +- sp_BlitzWho.sql | 2 +- sp_DatabaseRestore.sql | 2 +- sp_ineachdb.sql | 2 +- 17 files changed, 1311 insertions(+), 255 deletions(-) diff --git a/Install-All-Scripts.sql b/Install-All-Scripts.sql index 017fa71e0..9e6916928 100755 --- a/Install-All-Scripts.sql +++ b/Install-All-Scripts.sql @@ -31,7 +31,7 @@ SET STATISTICS XML OFF; BEGIN; -SELECT @Version = '8.04', @VersionDate = '20210530'; +SELECT @Version = '8.05', @VersionDate = '20210725'; IF(@VersionCheckMode = 1) BEGIN @@ -1526,7 +1526,7 @@ SET STATISTICS XML OFF; BEGIN; -SELECT @Version = '8.04', @VersionDate = '20210530'; +SELECT @Version = '8.05', @VersionDate = '20210725'; IF(@VersionCheckMode = 1) BEGIN @@ -2859,7 +2859,7 @@ AS SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.04', @VersionDate = '20210530'; + SELECT @Version = '8.05', @VersionDate = '20210725'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -3177,7 +3177,7 @@ AS SET @StringToExecute += QUOTENAME(@SkipChecksServer) + N'.'; END SET @StringToExecute += QUOTENAME(@SkipChecksDatabase) + N'.' + QUOTENAME(@SkipChecksSchema) + N'.' + QUOTENAME(@SkipChecksTable) - + N' WHERE ServerName IS NULL OR ServerName = SERVERPROPERTY(''ServerName'') OPTION (RECOMPILE);'; + + N' WHERE ServerName IS NULL OR ServerName = CAST(SERVERPROPERTY(''ServerName'') AS NVARCHAR(128)) OPTION (RECOMPILE);'; EXEC(@StringToExecute); END; @@ -7133,10 +7133,10 @@ AS SELECT 'containment', 0, 141, 210, 'Containment Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL FROM sys.all_columns WHERE name = 'containment' AND object_id = OBJECT_ID('sys.databases'); - INSERT INTO #DatabaseDefaults - SELECT 'target_recovery_time_in_seconds', 0, 142, 210, 'Target Recovery Time Changed', 'https://www.brentozar.com/go/dbdefaults', NULL - FROM sys.all_columns - WHERE name = 'target_recovery_time_in_seconds' AND object_id = OBJECT_ID('sys.databases'); + --INSERT INTO #DatabaseDefaults + -- SELECT 'target_recovery_time_in_seconds', 0, 142, 210, 'Target Recovery Time Changed', 'https://www.brentozar.com/go/dbdefaults', NULL + -- FROM sys.all_columns + -- WHERE name = 'target_recovery_time_in_seconds' AND object_id = OBJECT_ID('sys.databases'); INSERT INTO #DatabaseDefaults SELECT 'delayed_durability', 0, 143, 210, 'Delayed Durability Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL FROM sys.all_columns @@ -7180,6 +7180,79 @@ AS CLOSE DatabaseDefaultsLoop; DEALLOCATE DatabaseDefaultsLoop; + +/* Check if target recovery interval <> 60 */ +IF + @ProductVersionMajor >= 10 + AND NOT EXISTS + ( + SELECT + 1/0 + FROM #SkipChecks AS sc + WHERE sc.DatabaseName IS NULL + AND sc.CheckID = 257 + ) + BEGIN + IF EXISTS + ( + SELECT + 1/0 + FROM sys.all_columns AS ac + WHERE ac.name = 'target_recovery_time_in_seconds' + AND ac.object_id = OBJECT_ID('sys.databases') + ) + BEGIN + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 257) WITH NOWAIT; + + DECLARE + @tri nvarchar(max) = N' + SELECT + DatabaseName = + d.name, + CheckId = + 257, + Priority = + 50, + FindingsGroup = + N''Performance'', + Finding = + N''Recovery Interval Not Optimal'', + URL = + N''https://sqlperformance.com/2020/05/system-configuration/0-to-60-switching-to-indirect-checkpoints'', + Details = + N''The database '' + + QUOTENAME(d.name) + + N'' has a target recovery interval of '' + + RTRIM(d.target_recovery_time_in_seconds) + + CASE + WHEN d.target_recovery_time_in_seconds = 0 + THEN N'', which is a legacy default, and should be changed to 60.'' + WHEN d.target_recovery_time_in_seconds <> 0 + THEN N'', which is probably a mistake, and should be changed to 60.'' + END + FROM sys.databases AS d + WHERE d.database_id > 4 + AND d.is_read_only = 0 + AND d.is_in_standby = 0 + AND d.target_recovery_time_in_seconds <> 60; + '; + + INSERT INTO + #BlitzResults + ( + DatabaseName, + CheckID, + Priority, + FindingsGroup, + Finding, + URL, + Details + ) + EXEC sys.sp_executesql + @tri; + + END; + END; /*This checks to see if Agent is Offline*/ @@ -8906,7 +8979,7 @@ IF @ProductVersionMajor >= 10 END; - IF @ProductVersionMajor >= 13 AND @ProductVersionMinor < 2149 --CU1 has the fix in it + IF @ProductVersionMajor = 13 AND @ProductVersionMinor < 2149 --2016 CU1 has the fix in it AND NOT EXISTS ( SELECT 1 FROM #SkipChecks WHERE DatabaseName IS NULL AND CheckID = 182 ) @@ -11001,6 +11074,7 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 WHERE o.name = 'dm_server_services' AND c.name = 'instant_file_initialization_enabled' ) begin + SET @StringToExecute = N' INSERT INTO #BlitzResults ( CheckID , [Priority] , @@ -11012,14 +11086,15 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 SELECT 193 AS [CheckID] , 250 AS [Priority] , - 'Server Info' AS [FindingsGroup] , - 'Instant File Initialization Enabled' AS [Finding] , - 'https://www.brentozar.com/go/instant' AS [URL] , - 'The service account has the Perform Volume Maintenance Tasks permission.' + ''Server Info'' AS [FindingsGroup] , + ''Instant File Initialization Enabled'' AS [Finding] , + ''https://www.brentozar.com/go/instant'' AS [URL] , + ''The service account has the Perform Volume Maintenance Tasks permission.'' where exists (select 1 FROM sys.dm_server_services - WHERE instant_file_initialization_enabled = 'Y' - AND filename LIKE '%sqlservr.exe%') - OPTION (RECOMPILE); + WHERE instant_file_initialization_enabled = ''Y'' + AND filename LIKE ''%sqlservr.exe%'') + OPTION (RECOMPILE);'; + EXEC(@StringToExecute); end; end; END; @@ -11848,7 +11923,7 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 IF (OBJECT_ID('tempdb..##BlitzResults', 'U') IS NOT NULL) DROP TABLE ##BlitzResults; SELECT * INTO ##BlitzResults FROM #BlitzResults; SET @query_result_separator = char(9); - SET @StringToExecute = 'SET NOCOUNT ON;SELECT [Priority] , [FindingsGroup] , [Finding] , [DatabaseName] , [URL] , [Details] , CheckID FROM ##BlitzResults ORDER BY Priority , FindingsGroup, Finding, Details; SET NOCOUNT OFF;'; + SET @StringToExecute = 'SET NOCOUNT ON;SELECT [Priority] , [FindingsGroup] , [Finding] , [DatabaseName] , [URL] , [Details] , CheckID FROM ##BlitzResults ORDER BY Priority , FindingsGroup , Finding , DatabaseName , Details; SET NOCOUNT OFF;'; SET @EmailSubject = 'sp_Blitz Results for ' + @@SERVERNAME; SET @EmailBody = 'sp_Blitz ' + CAST(CONVERT(DATETIME, @VersionDate, 102) AS VARCHAR(100)) + '. http://FirstResponderKit.org'; IF @EmailProfile IS NULL @@ -11993,7 +12068,7 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 + @OutputTableName + ' (ServerName, CheckDate, CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details, QueryPlan, QueryPlanFiltered) SELECT ''' + CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128)) - + ''', SYSDATETIMEOFFSET(), CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details, CAST(QueryPlan AS NVARCHAR(MAX)), QueryPlanFiltered FROM #BlitzResults ORDER BY Priority , FindingsGroup , Finding , Details'; + + ''', SYSDATETIMEOFFSET(), CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details, CAST(QueryPlan AS NVARCHAR(MAX)), QueryPlanFiltered FROM #BlitzResults ORDER BY Priority , FindingsGroup , Finding , DatabaseName , Details'; EXEC(@StringToExecute); END; @@ -12010,7 +12085,7 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 + @OutputTableName + ' (ServerName, CheckDate, CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details, QueryPlan, QueryPlanFiltered) SELECT ''' + CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128)) - + ''', SYSDATETIMEOFFSET(), CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details, CAST(QueryPlan AS NVARCHAR(MAX)), QueryPlanFiltered FROM #BlitzResults ORDER BY Priority , FindingsGroup , Finding , Details'; + + ''', SYSDATETIMEOFFSET(), CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details, CAST(QueryPlan AS NVARCHAR(MAX)), QueryPlanFiltered FROM #BlitzResults ORDER BY Priority , FindingsGroup , Finding , DatabaseName , Details'; END; ELSE begin @@ -12023,7 +12098,7 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 + @OutputTableName + ' (ServerName, CheckDate, CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details, QueryPlan, QueryPlanFiltered) SELECT ''' + CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128)) - + ''', SYSDATETIMEOFFSET(), CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details, QueryPlan, QueryPlanFiltered FROM #BlitzResults ORDER BY Priority , FindingsGroup , Finding , Details'; + + ''', SYSDATETIMEOFFSET(), CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details, QueryPlan, QueryPlanFiltered FROM #BlitzResults ORDER BY Priority , FindingsGroup , Finding , DatabaseName , Details'; END; EXEC(@StringToExecute); @@ -12059,7 +12134,7 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 + @OutputTableName + ' (ServerName, CheckDate, CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details, QueryPlan, QueryPlanFiltered) SELECT ''' + CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128)) - + ''', SYSDATETIMEOFFSET(), CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details, QueryPlan, QueryPlanFiltered FROM #BlitzResults ORDER BY Priority , FindingsGroup , Finding , Details'; + + ''', SYSDATETIMEOFFSET(), CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details, QueryPlan, QueryPlanFiltered FROM #BlitzResults ORDER BY Priority , FindingsGroup , Finding , DatabaseName , Details'; EXEC(@StringToExecute); END; @@ -12300,7 +12375,7 @@ AS SET NOCOUNT ON; SET STATISTICS XML OFF; -SELECT @Version = '8.04', @VersionDate = '20210530'; +SELECT @Version = '8.05', @VersionDate = '20210725'; IF(@VersionCheckMode = 1) BEGIN @@ -13178,7 +13253,7 @@ AS SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.04', @VersionDate = '20210530'; + SELECT @Version = '8.05', @VersionDate = '20210725'; IF(@VersionCheckMode = 1) BEGIN @@ -14959,7 +15034,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.04', @VersionDate = '20210530'; +SELECT @Version = '8.05', @VersionDate = '20210725'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -16244,9 +16319,9 @@ database_id INT CREATE TABLE #plan_usage ( duplicate_plan_hashes BIGINT NULL, - percent_duplicate DECIMAL(5, 2) NULL, + percent_duplicate DECIMAL(9, 2) NULL, single_use_plan_count BIGINT NULL, - percent_single DECIMAL(5, 2) NULL, + percent_single DECIMAL(9, 2) NULL, total_plans BIGINT NULL, spid INT ); @@ -22219,7 +22294,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.04', @VersionDate = '20210530'; +SELECT @Version = '8.05', @VersionDate = '20210725'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -23130,9 +23205,17 @@ FROM #Ignore_Databases i; /* Last startup */ -SELECT @DaysUptime = CAST(DATEDIFF(HOUR, create_date, GETDATE()) / 24. AS NUMERIC (23,2)) -FROM sys.databases -WHERE database_id = 2; +IF COLUMNPROPERTY(OBJECT_ID('sys.dm_os_sys_info'),'sqlserver_start_time','ColumnID') IS NOT NULL +BEGIN + SELECT @DaysUptime = CAST(DATEDIFF(HOUR, sqlserver_start_time, GETDATE()) / 24. AS NUMERIC (23,2)) + FROM sys.dm_os_sys_info; +END +ELSE +BEGIN + SELECT @DaysUptime = CAST(DATEDIFF(HOUR, create_date, GETDATE()) / 24. AS NUMERIC (23,2)) + FROM sys.databases + WHERE database_id = 2; +END IF @DaysUptime = 0 OR @DaysUptime IS NULL SET @DaysUptime = .01; @@ -24888,17 +24971,18 @@ BEGIN /* Show histograms for all stats on this table. More info: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/1900 */ IF EXISTS (SELECT * FROM sys.all_objects WHERE name = 'dm_db_stats_histogram') BEGIN - SET @dsql=N'SELECT s.name AS [Stat Name], c.name AS [Leading Column Name], hist.step_number AS [Step Number], + SET @dsql = N'USE ' + QUOTENAME(@DatabaseName) + N'; + SELECT s.name AS [Stat Name], c.name AS [Leading Column Name], hist.step_number AS [Step Number], hist.range_high_key AS [Range High Key], hist.range_rows AS [Range Rows], hist.equal_rows AS [Equal Rows], hist.distinct_range_rows AS [Distinct Range Rows], hist.average_range_rows AS [Average Range Rows], s.auto_created AS [Auto-Created], s.user_created AS [User-Created], props.last_updated AS [Last Updated], props.modification_counter AS [Modification Counter], props.rows AS [Table Rows], props.rows_sampled AS [Rows Sampled], s.stats_id AS [StatsID] - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.stats AS s - INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.stats_columns sc ON s.object_id = sc.object_id AND s.stats_id = sc.stats_id AND sc.stats_column_id = 1 - INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns c ON sc.object_id = c.object_id AND sc.column_id = c.column_id - CROSS APPLY ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_stats_properties(s.object_id, s.stats_id) AS props - CROSS APPLY ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_stats_histogram(s.[object_id], s.stats_id) AS hist + FROM sys.stats AS s + INNER JOIN sys.stats_columns sc ON s.object_id = sc.object_id AND s.stats_id = sc.stats_id AND sc.stats_column_id = 1 + INNER JOIN sys.columns c ON sc.object_id = c.object_id AND sc.column_id = c.column_id + CROSS APPLY sys.dm_db_stats_properties(s.object_id, s.stats_id) AS props + CROSS APPLY sys.dm_db_stats_histogram(s.[object_id], s.stats_id) AS hist WHERE s.object_id = @ObjectID ORDER BY s.auto_created, s.user_created, s.name, hist.step_number;'; EXEC sp_executesql @dsql, N'@ObjectID INT', @ObjectID; @@ -26159,7 +26243,7 @@ BEGIN; i.index_sanity_id, 150 AS Priority, N'Index Hoarder' AS findings_group, - N'Non-Unique Clustered JIndex' AS finding, + N'Non-Unique Clustered Index' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/IndexHoarder' AS URL, N'Uniquifiers will be required! Clustered index: ' + i.db_schema_object_name @@ -27758,7 +27842,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.04', @VersionDate = '20210530'; +SELECT @Version = '8.05', @VersionDate = '20210725'; IF(@VersionCheckMode = 1) @@ -29565,7 +29649,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.04', @VersionDate = '20210530'; +SELECT @Version = '8.05', @VersionDate = '20210725'; IF(@VersionCheckMode = 1) BEGIN RETURN; @@ -35284,6 +35368,7 @@ ALTER PROCEDURE dbo.sp_BlitzWho @MinBlockingSeconds INT = 0 , @CheckDateOverride DATETIMEOFFSET = NULL, @ShowActualParameters BIT = 0, + @GetOuterCommand BIT = 0, @Version VARCHAR(30) = NULL OUTPUT, @VersionDate DATETIME = NULL OUTPUT, @VersionCheckMode BIT = 0, @@ -35294,7 +35379,7 @@ BEGIN SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.04', @VersionDate = '20210530'; + SELECT @Version = '8.05', @VersionDate = '20210725'; IF(@VersionCheckMode = 1) BEGIN @@ -35424,6 +35509,7 @@ IF @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @Output [session_id] [smallint] NOT NULL, [database_name] [nvarchar](128) NULL, [query_text] [nvarchar](max) NULL, + [outer_command] NVARCHAR(4000) NULL, [query_plan] [xml] NULL, [live_query_plan] [xml] NULL, [cached_parameter_info] [nvarchar](max) NULL, @@ -35540,6 +35626,13 @@ IF @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @Output ALTER TABLE ' + @ObjectFullName + N' ADD live_parameter_info NVARCHAR(MAX) NULL;'; EXEC(@StringToExecute); + /* If the table doesn't have the new outer_command column, add it. See Github #2887. */ + SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableName; + SET @StringToExecute = N'IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns + WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''outer_command'') + ALTER TABLE ' + @ObjectFullName + N' ADD outer_command NVARCHAR(4000) NULL;'; + EXEC(@StringToExecute); + /* Delete history older than @OutputTableRetentionDays */ SET @OutputTableCleanupDate = CAST( (DATEADD(DAY, -1 * @OutputTableRetentionDays, GETDATE() ) ) AS DATE); SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' @@ -35837,7 +35930,64 @@ SELECT @BlockingCheck = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; FROM sys.sysprocesses AS sys1 JOIN sys.sysprocesses AS sys2 ON sys1.spid = sys2.blocked; + '+CASE + WHEN (@GetOuterCommand = 1 AND (NOT EXISTS(SELECT 1 FROM sys.all_objects WHERE [name] = N'dm_exec_input_buffer'))) THEN N' + DECLARE @session_id SMALLINT; + DECLARE @Sessions TABLE + ( + session_id INT + ); + DECLARE @inputbuffer TABLE + ( + ID INT IDENTITY(1,1), + session_id INT, + event_type NVARCHAR(30), + parameters SMALLINT, + event_info NVARCHAR(4000) + ); + + DECLARE inputbuffer_cursor + + CURSOR LOCAL FAST_FORWARD + FOR + SELECT session_id + FROM sys.dm_exec_sessions + WHERE session_id <> @@SPID + AND is_user_process = 1; + + OPEN inputbuffer_cursor; + + FETCH NEXT FROM inputbuffer_cursor INTO @session_id; + + WHILE (@@FETCH_STATUS = 0) + BEGIN; + BEGIN TRY; + + INSERT INTO @inputbuffer ([event_type],[parameters],[event_info]) + EXEC sp_executesql + N''DBCC INPUTBUFFER(@session_id) WITH NO_INFOMSGS;'', + N''@session_id SMALLINT'', + @session_id; + + UPDATE @inputbuffer + SET session_id = @session_id + WHERE ID = SCOPE_IDENTITY(); + + END TRY + BEGIN CATCH + RAISERROR(''DBCC inputbuffer failed for session %d'',0,0,@session_id) WITH NOWAIT; + END CATCH; + + FETCH NEXT FROM inputbuffer_cursor INTO @session_id + + END; + + CLOSE inputbuffer_cursor; + DEALLOCATE inputbuffer_cursor;' + ELSE N'' + END+ + N' DECLARE @LiveQueryPlans TABLE ( @@ -35846,7 +35996,6 @@ SELECT @BlockingCheck = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; ); ' - IF EXISTS (SELECT * FROM sys.all_columns WHERE object_id = OBJECT_ID('sys.dm_exec_query_statistics_xml') AND name = 'query_plan') BEGIN SET @BlockingCheck = @BlockingCheck + N' @@ -35873,6 +36022,10 @@ BEGIN ELSE query_stats.statement_end_offset END - query_stats.statement_start_offset ) / 2 ) + 1), dest.text) AS query_text , + '+CASE + WHEN @GetOuterCommand = 1 THEN N'CAST(event_info AS NVARCHAR(4000)) AS outer_command,' + ELSE N'' + END+N' derp.query_plan , qmg.query_cost , s.status , @@ -35992,6 +36145,14 @@ BEGIN SET @StringToExecute += N'FROM sys.dm_exec_sessions AS s + '+ + CASE + WHEN @GetOuterCommand = 1 THEN CASE + WHEN EXISTS(SELECT 1 FROM sys.all_objects WHERE [name] = N'dm_exec_input_buffer') THEN N'OUTER APPLY sys.dm_exec_input_buffer (s.session_id, 0) AS ib' + ELSE N'LEFT JOIN @inputbuffer ib ON s.session_id = ib.session_id' + END + ELSE N'' + END+N' LEFT JOIN sys.dm_exec_requests AS r ON r.session_id = s.session_id LEFT JOIN ( SELECT DISTINCT @@ -36078,6 +36239,10 @@ IF @ProductVersionMajor >= 11 ELSE query_stats.statement_end_offset END - query_stats.statement_start_offset ) / 2 ) + 1), dest.text) AS query_text , + '+CASE + WHEN @GetOuterCommand = 1 THEN N'CAST(event_info AS NVARCHAR(4000)) AS outer_command,' + ELSE N'' + END+N' derp.query_plan , CAST(COALESCE(qs_live.Query_Plan, '''') AS XML) AS live_query_plan , STUFF((SELECT DISTINCT N'', '' + Node.Data.value(''(@Column)[1]'', ''NVARCHAR(4000)'') + N'' {'' + Node.Data.value(''(@ParameterDataType)[1]'', ''NVARCHAR(4000)'') + N''}: '' + Node.Data.value(''(@ParameterCompiledValue)[1]'', ''NVARCHAR(4000)'') @@ -36255,7 +36420,16 @@ IF @ProductVersionMajor >= 11 END /* IF @ExpertMode = 1 */ SET @StringToExecute += - N' FROM sys.dm_exec_sessions AS s + N' FROM sys.dm_exec_sessions AS s'+ + CASE + WHEN @GetOuterCommand = 1 THEN CASE + WHEN EXISTS(SELECT 1 FROM sys.all_objects WHERE [name] = N'dm_exec_input_buffer') THEN N' + OUTER APPLY sys.dm_exec_input_buffer (s.session_id, 0) AS ib' + ELSE N' + LEFT JOIN @inputbuffer ib ON s.session_id = ib.session_id' + END + ELSE N'' + END+N' LEFT JOIN sys.dm_exec_requests AS r ON r.session_id = s.session_id LEFT JOIN ( SELECT DISTINCT @@ -36409,7 +36583,8 @@ IF @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @Output ,[elapsed_time] ,[session_id] ,[database_name] - ,[query_text] + ,[query_text]' + + CASE WHEN @GetOuterCommand = 1 THEN N',[outer_command]' ELSE N'' END + N' ,[query_plan]' + CASE WHEN @ProductVersionMajor >= 11 THEN N',[live_query_plan]' ELSE N'' END + CASE WHEN @ProductVersionMajor >= 11 THEN N',[cached_parameter_info]' ELSE N'' END @@ -36580,7 +36755,7 @@ SET STATISTICS XML OFF; /*Versioning details*/ -SELECT @Version = '8.04', @VersionDate = '20210530'; +SELECT @Version = '8.05', @VersionDate = '20210725'; IF(@VersionCheckMode = 1) BEGIN @@ -36883,116 +37058,116 @@ CREATE TABLE #Headers ); /* -Correct paths in case people forget a final "\" +Correct paths in case people forget a final "\" or "/" */ /*Full*/ -IF (SELECT RIGHT(@BackupPathFull, 1)) <> '\' AND CHARINDEX('\', @BackupPathFull) > 0 --Has to end in a '\' -BEGIN - IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Fixing @BackupPathFull to add a "\"', 0, 1) WITH NOWAIT; - SET @BackupPathFull += N'\'; -END; -ELSE IF (SELECT RIGHT(@BackupPathFull, 1)) <> '/' AND CHARINDEX('/', @BackupPathFull) > 0 --Has to end in a '/' +IF (SELECT RIGHT(@BackupPathFull, 1)) <> '/' AND CHARINDEX('/', @BackupPathFull) > 0 --Has to end in a '/' BEGIN IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Fixing @BackupPathFull to add a "/"', 0, 1) WITH NOWAIT; SET @BackupPathFull += N'/'; END; -/*Diff*/ -IF (SELECT RIGHT(@BackupPathDiff, 1)) <> '\' AND CHARINDEX('\', @BackupPathDiff) > 0 --Has to end in a '\' +ELSE IF (SELECT RIGHT(@BackupPathFull, 1)) <> '\' --Has to end in a '\' BEGIN - IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Fixing @BackupPathDiff to add a "\"', 0, 1) WITH NOWAIT; - SET @BackupPathDiff += N'\'; + IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Fixing @BackupPathFull to add a "\"', 0, 1) WITH NOWAIT; + SET @BackupPathFull += N'\'; END; -ELSE IF (SELECT RIGHT(@BackupPathDiff, 1)) <> '/' AND CHARINDEX('/', @BackupPathDiff) > 0 --Has to end in a '/' +/*Diff*/ +IF (SELECT RIGHT(@BackupPathDiff, 1)) <> '/' AND CHARINDEX('/', @BackupPathDiff) > 0 --Has to end in a '/' BEGIN IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Fixing @BackupPathDiff to add a "/"', 0, 1) WITH NOWAIT; SET @BackupPathDiff += N'/'; END; -/*Log*/ -IF (SELECT RIGHT(@BackupPathLog, 1)) <> '\' AND CHARINDEX('\', @BackupPathLog) > 0 --Has to end in a '\' +ELSE IF (SELECT RIGHT(@BackupPathDiff, 1)) <> '\' --Has to end in a '\' BEGIN - IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Fixing @BackupPathLog to add a "\"', 0, 1) WITH NOWAIT; - SET @BackupPathLog += N'\'; + IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Fixing @BackupPathDiff to add a "\"', 0, 1) WITH NOWAIT; + SET @BackupPathDiff += N'\'; END; -ELSE IF (SELECT RIGHT(@BackupPathLog, 1)) <> '/' AND CHARINDEX('/', @BackupPathLog) > 0 --Has to end in a '/' +/*Log*/ +IF (SELECT RIGHT(@BackupPathLog, 1)) <> '/' AND CHARINDEX('/', @BackupPathLog) > 0 --Has to end in a '/' BEGIN IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Fixing @BackupPathLog to add a "/"', 0, 1) WITH NOWAIT; SET @BackupPathLog += N'/'; END; +IF (SELECT RIGHT(@BackupPathLog, 1)) <> '\' --Has to end in a '\' +BEGIN + IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Fixing @BackupPathLog to add a "\"', 0, 1) WITH NOWAIT; + SET @BackupPathLog += N'\'; +END; /*Move Data File*/ IF NULLIF(@MoveDataDrive, '') IS NULL BEGIN IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Getting default data drive for @MoveDataDrive', 0, 1) WITH NOWAIT; SET @MoveDataDrive = CAST(SERVERPROPERTY('InstanceDefaultDataPath') AS nvarchar(260)); END; -IF (SELECT RIGHT(@MoveDataDrive, 1)) <> '\' AND CHARINDEX('\', @MoveDataDrive) > 0 --Has to end in a '\' -BEGIN - IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Fixing @MoveDataDrive to add a "\"', 0, 1) WITH NOWAIT; - SET @MoveDataDrive += N'\'; -END; -ELSE IF (SELECT RIGHT(@MoveDataDrive, 1)) <> '/' AND CHARINDEX('/', @MoveDataDrive) > 0 --Has to end in a '/' +IF (SELECT RIGHT(@MoveDataDrive, 1)) <> '/' AND CHARINDEX('/', @MoveDataDrive) > 0 --Has to end in a '/' BEGIN IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Fixing @MoveDataDrive to add a "/"', 0, 1) WITH NOWAIT; SET @MoveDataDrive += N'/'; END; +ELSE IF (SELECT RIGHT(@MoveDataDrive, 1)) <> '\' --Has to end in a '\' +BEGIN + IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Fixing @MoveDataDrive to add a "\"', 0, 1) WITH NOWAIT; + SET @MoveDataDrive += N'\'; +END; /*Move Log File*/ IF NULLIF(@MoveLogDrive, '') IS NULL BEGIN IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Getting default log drive for @MoveLogDrive', 0, 1) WITH NOWAIT; SET @MoveLogDrive = CAST(SERVERPROPERTY('InstanceDefaultLogPath') AS nvarchar(260)); END; -IF (SELECT RIGHT(@MoveLogDrive, 1)) <> '\' AND CHARINDEX('\', @MoveLogDrive) > 0 --Has to end in a '\' -BEGIN - IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Fixing @MoveLogDrive to add a "\"', 0, 1) WITH NOWAIT; - SET @MoveLogDrive += N'\'; -END; -ELSE IF (SELECT RIGHT(@MoveLogDrive, 1)) <> '/' AND CHARINDEX('/', @MoveLogDrive) > 0 --Has to end in a '/' +IF (SELECT RIGHT(@MoveLogDrive, 1)) <> '/' AND CHARINDEX('/', @MoveLogDrive) > 0 --Has to end in a '/' BEGIN IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Fixing@MoveLogDrive to add a "/"', 0, 1) WITH NOWAIT; SET @MoveLogDrive += N'/'; END; +ELSE IF (SELECT RIGHT(@MoveLogDrive, 1)) <> '\' --Has to end in a '\' +BEGIN + IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Fixing @MoveLogDrive to add a "\"', 0, 1) WITH NOWAIT; + SET @MoveLogDrive += N'\'; +END; /*Move Filestream File*/ IF NULLIF(@MoveFilestreamDrive, '') IS NULL BEGIN IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Setting default data drive for @MoveFilestreamDrive', 0, 1) WITH NOWAIT; SET @MoveFilestreamDrive = CAST(SERVERPROPERTY('InstanceDefaultDataPath') AS nvarchar(260)); END; -IF (SELECT RIGHT(@MoveFilestreamDrive, 1)) <> '\' AND CHARINDEX('\', @MoveFilestreamDrive) > 0 --Has to end in a '\' -BEGIN - IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Fixing @MoveFilestreamDrive to add a "\"', 0, 1) WITH NOWAIT; - SET @MoveFilestreamDrive += N'\'; -END; -ELSE IF (SELECT RIGHT(@MoveFilestreamDrive, 1)) <> '/' AND CHARINDEX('/', @MoveFilestreamDrive) > 0 --Has to end in a '/' +IF (SELECT RIGHT(@MoveFilestreamDrive, 1)) <> '/' AND CHARINDEX('/', @MoveFilestreamDrive) > 0 --Has to end in a '/' BEGIN IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Fixing @MoveFilestreamDrive to add a "/"', 0, 1) WITH NOWAIT; SET @MoveFilestreamDrive += N'/'; END; +ELSE IF (SELECT RIGHT(@MoveFilestreamDrive, 1)) <> '\' --Has to end in a '\' +BEGIN + IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Fixing @MoveFilestreamDrive to add a "\"', 0, 1) WITH NOWAIT; + SET @MoveFilestreamDrive += N'\'; +END; /*Move FullText Catalog File*/ IF NULLIF(@MoveFullTextCatalogDrive, '') IS NULL BEGIN IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Setting default data drive for @MoveFullTextCatalogDrive', 0, 1) WITH NOWAIT; SET @MoveFullTextCatalogDrive = CAST(SERVERPROPERTY('InstanceDefaultDataPath') AS nvarchar(260)); END; -IF (SELECT RIGHT(@MoveFullTextCatalogDrive, 1)) <> '\' AND CHARINDEX('\', @MoveFullTextCatalogDrive) > 0 --Has to end in a '\' -BEGIN - IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Fixing @MoveFullTextCatalogDrive to add a "\"', 0, 1) WITH NOWAIT; - SET @MoveFullTextCatalogDrive += N'\'; -END; -ELSE IF (SELECT RIGHT(@MoveFullTextCatalogDrive, 1)) <> '/' AND CHARINDEX('/', @MoveFullTextCatalogDrive) > 0 --Has to end in a '/' +IF (SELECT RIGHT(@MoveFullTextCatalogDrive, 1)) <> '/' AND CHARINDEX('/', @MoveFullTextCatalogDrive) > 0 --Has to end in a '/' BEGIN IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Fixing @MoveFullTextCatalogDrive to add a "/"', 0, 1) WITH NOWAIT; SET @MoveFullTextCatalogDrive += N'/'; END; -/*Standby Undo File*/ -IF (SELECT RIGHT(@StandbyUndoPath, 1)) <> '\' AND CHARINDEX('\', @StandbyUndoPath) > 0 --Has to end in a '\' +IF (SELECT RIGHT(@MoveFullTextCatalogDrive, 1)) <> '\' --Has to end in a '\' BEGIN - IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Fixing @StandbyUndoPath to add a "\"', 0, 1) WITH NOWAIT; - SET @StandbyUndoPath += N'\'; + IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Fixing @MoveFullTextCatalogDrive to add a "\"', 0, 1) WITH NOWAIT; + SET @MoveFullTextCatalogDrive += N'\'; END; -ELSE IF (SELECT RIGHT(@StandbyUndoPath, 1)) <> '/' AND CHARINDEX('/', @StandbyUndoPath) > 0 --Has to end in a '/' +/*Standby Undo File*/ +IF (SELECT RIGHT(@StandbyUndoPath, 1)) <> '/' AND CHARINDEX('/', @StandbyUndoPath) > 0 --Has to end in a '/' BEGIN IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Fixing @StandbyUndoPath to add a "/"', 0, 1) WITH NOWAIT; SET @StandbyUndoPath += N'/'; END; +IF (SELECT RIGHT(@StandbyUndoPath, 1)) <> '\' --Has to end in a '\' +BEGIN + IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Fixing @StandbyUndoPath to add a "\"', 0, 1) WITH NOWAIT; + SET @StandbyUndoPath += N'\'; +END; IF @RestoreDatabaseName IS NULL OR @RestoreDatabaseName LIKE N'' /*use LIKE instead of =, otherwise N'' = N' '. See: https://www.brentozar.com/archive/2017/04/surprising-behavior-trailing-spaces/ */ BEGIN @@ -38047,7 +38222,7 @@ BEGIN SET NOCOUNT ON; SET STATISTICS XML OFF; - SELECT @Version = '8.04', @VersionDate = '20210530'; + SELECT @Version = '8.05', @VersionDate = '20210725'; IF(@VersionCheckMode = 1) BEGIN @@ -38393,6 +38568,7 @@ DELETE FROM dbo.SqlServerVersions; INSERT INTO dbo.SqlServerVersions (MajorVersionNumber, MinorVersionNumber, Branch, [Url], ReleaseDate, MainstreamSupportEndDate, ExtendedSupportEndDate, MajorVersionName, MinorVersionName) VALUES + (15, 4138, 'CU11', 'https://support.microsoft.com/en-us/help/5003249', '2021-06-10', '2025-01-01', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 11'), (15, 4123, 'CU10', 'https://support.microsoft.com/en-us/help/5001090', '2021-04-06', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 10'), (15, 4102, 'CU9', 'https://support.microsoft.com/en-us/help/5000642', '2021-02-11', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 9 '), (15, 4073, 'CU8 GDR', 'https://support.microsoft.com/en-us/help/4583459', '2021-01-12', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 8 GDR '), @@ -38406,6 +38582,7 @@ VALUES (15, 4003, 'CU1', 'https://support.microsoft.com/en-us/help/4527376', '2020-01-07', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 1 '), (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 '), + (14, 3410, 'RTM CU25', 'https://support.microsoft.com/en-us/help/5003830', '2021-07-12', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 25'), (14, 3391, 'RTM CU24', 'https://support.microsoft.com/en-us/help/5001228', '2021-05-10', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 24'), (14, 3381, 'RTM CU23', 'https://support.microsoft.com/en-us/help/5000685', '2021-02-25', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 23'), (14, 3370, 'RTM CU22 GDR', 'https://support.microsoft.com/en-us/help/4583457', '2021-01-12', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 22 GDR'), @@ -38785,7 +38962,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.04', @VersionDate = '20210530'; +SELECT @Version = '8.05', @VersionDate = '20210725'; IF(@VersionCheckMode = 1) BEGIN @@ -38878,7 +39055,10 @@ DECLARE @StringToExecute NVARCHAR(MAX), @UnquotedOutputDatabaseName NVARCHAR(256) = @OutputDatabaseName , @UnquotedOutputSchemaName NVARCHAR(256) = @OutputSchemaName , @LocalServerName NVARCHAR(128) = CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128)), - @dm_exec_query_statistics_xml BIT = 0; + @dm_exec_query_statistics_xml BIT = 0, + @total_cpu_usage BIT = 0, + @get_thread_time_ms NVARCHAR(MAX) = N'', + @thread_time_ms FLOAT = 0; /* Sanitize our inputs */ SELECT @@ -39032,6 +39212,54 @@ BEGIN @FinishSampleTimeWaitFor = DATEADD(ss, @Seconds, GETDATE()); + IF EXISTS + ( + + SELECT + 1/0 + FROM sys.all_columns AS ac + WHERE ac.object_id = OBJECT_ID('sys.dm_os_schedulers') + AND ac.name = 'total_cpu_usage_ms' + + ) + BEGIN + + SELECT + @total_cpu_usage = 1, + @get_thread_time_ms += + N' + SELECT + @thread_time_ms = + CONVERT + ( + FLOAT, + SUM(s.total_cpu_usage_ms) + ) + FROM sys.dm_os_schedulers AS s + WHERE s.status = ''VISIBLE ONLINE'' + AND s.is_online = 1 + OPTION(RECOMPILE); + '; + + END + ELSE + BEGIN + SELECT + @total_cpu_usage = 0, + @get_thread_time_ms += + N' + SELECT + @thread_time_ms = + CONVERT + ( + FLOAT, + SUM(s.total_worker_time / 1000.) + ) + FROM sys.dm_exec_query_stats AS s + OPTION(RECOMPILE); + '; + END + RAISERROR('Now starting diagnostic analysis',10,1) WITH NOWAIT; /* @@ -39081,7 +39309,15 @@ BEGIN IF OBJECT_ID('tempdb..#WaitStats') IS NOT NULL DROP TABLE #WaitStats; - CREATE TABLE #WaitStats (Pass TINYINT NOT NULL, wait_type NVARCHAR(60), wait_time_ms BIGINT, signal_wait_time_ms BIGINT, waiting_tasks_count BIGINT, SampleTime DATETIMEOFFSET); + CREATE TABLE #WaitStats ( + Pass TINYINT NOT NULL, + wait_type NVARCHAR(60), + wait_time_ms BIGINT, + thread_time_ms FLOAT, + signal_wait_time_ms BIGINT, + waiting_tasks_count BIGINT, + SampleTime datetimeoffset + ); IF OBJECT_ID('tempdb..#FileStats') IS NOT NULL DROP TABLE #FileStats; @@ -40032,16 +40268,30 @@ BEGIN INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP Transactions','Transactions created/sec',NULL); END; + + IF @total_cpu_usage IN (0, 1) + BEGIN + EXEC sys.sp_executesql + @get_thread_time_ms, + N'@thread_time_ms FLOAT OUTPUT', + @thread_time_ms OUTPUT; + END + /* Populate #FileStats, #PerfmonStats, #WaitStats with DMV data. After we finish doing our checks, we'll take another sample and compare them. */ RAISERROR('Capturing first pass of wait stats, perfmon counters, file stats',10,1) WITH NOWAIT; SET @StringToExecute = N' - INSERT #WaitStats(Pass, SampleTime, wait_type, wait_time_ms, signal_wait_time_ms, waiting_tasks_count) + INSERT #WaitStats(Pass, SampleTime, wait_type, wait_time_ms, thread_time_ms, signal_wait_time_ms, waiting_tasks_count) SELECT x.Pass, x.SampleTime, x.wait_type, SUM(x.sum_wait_time_ms) AS sum_wait_time_ms, + CASE @Seconds + WHEN 0 + THEN 0 + ELSE @thread_time_ms + END AS thread_time_ms, SUM(x.sum_signal_wait_time_ms) AS sum_signal_wait_time_ms, SUM(x.sum_waiting_tasks) AS sum_waiting_tasks FROM ( @@ -40081,9 +40331,35 @@ BEGIN ) GROUP BY x.Pass, x.SampleTime, x.wait_type ORDER BY sum_wait_time_ms DESC;' - EXEC sp_executesql @StringToExecute, N'@StartSampleTime DATETIMEOFFSET, @Seconds INT', @StartSampleTime, @Seconds; - - + + EXEC sys.sp_executesql + @StringToExecute, + N'@StartSampleTime DATETIMEOFFSET, + @Seconds INT, + @thread_time_ms FLOAT', + @StartSampleTime, + @Seconds, + @thread_time_ms; + + WITH w AS + ( + SELECT + total_waits = + CONVERT + ( + FLOAT, + SUM(ws.wait_time_ms) + ) + FROM #WaitStats AS ws + WHERE Pass = 1 + ) + UPDATE ws + SET ws.thread_time_ms += w.total_waits + FROM #WaitStats AS ws + CROSS JOIN w + WHERE ws.Pass = 1 + OPTION(RECOMPILE); + INSERT INTO #FileStats (Pass, SampleTime, DatabaseID, FileID, DatabaseName, FileLogicalName, SizeOnDiskMB, io_stall_read_ms , num_of_reads, [bytes_read] , io_stall_write_ms,num_of_writes, [bytes_written], PhysicalName, TypeDesc) SELECT @@ -40178,7 +40454,7 @@ BEGIN WHERE resource_type = N'DATABASE' AND request_mode = N'S' AND request_status = N'GRANT' - AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.session_id = db.request_session_id + AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.session_id = db.request_session_id AND s.database_id = db.resource_database_id CROSS APPLY sys.dm_exec_query_plan(r.plan_handle) pl WHERE r.command LIKE 'BACKUP%' AND r.start_time <= DATEADD(minute, -5, GETDATE()) @@ -40405,9 +40681,8 @@ BEGIN db.[resource_database_id] AS DatabaseID, DB_NAME(db.resource_database_id) AS DatabaseName, (SELECT TOP 1 [text] FROM sys.dm_exec_sql_text(c.most_recent_sql_handle)) AS QueryText, - sessions_with_transactions.open_transaction_count AS OpenTransactionCount - FROM (SELECT session_id, SUM(open_transaction_count) AS open_transaction_count FROM sys.dm_exec_requests WHERE open_transaction_count > 0 GROUP BY session_id) AS sessions_with_transactions - INNER JOIN sys.dm_exec_sessions s ON sessions_with_transactions.session_id = s.session_id + s.open_transaction_count AS OpenTransactionCount + FROM sys.dm_exec_sessions s INNER JOIN sys.dm_exec_connections c ON s.session_id = c.session_id INNER JOIN ( SELECT DISTINCT request_session_id, resource_database_id @@ -40417,6 +40692,7 @@ BEGIN AND request_status = N'GRANT' AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.session_id = db.request_session_id WHERE s.status = 'sleeping' + AND s.open_transaction_count > 0 AND s.last_request_end_time < DATEADD(ss, -10, SYSDATETIME()) AND EXISTS(SELECT * FROM sys.dm_tran_locks WHERE request_session_id = s.session_id AND NOT (resource_type = N'DATABASE' AND request_mode = N'S' AND request_status = N'GRANT' AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE')); @@ -41189,15 +41465,24 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, WAITFOR TIME @FinishSampleTimeWaitFor; END; + IF @total_cpu_usage IN (0, 1) + BEGIN + EXEC sys.sp_executesql + @get_thread_time_ms, + N'@thread_time_ms FLOAT OUTPUT', + @thread_time_ms OUTPUT; + END + RAISERROR('Capturing second pass of wait stats, perfmon counters, file stats',10,1) WITH NOWAIT; /* Populate #FileStats, #PerfmonStats, #WaitStats with DMV data. In a second, we'll compare these. */ SET @StringToExecute = N' - INSERT #WaitStats(Pass, SampleTime, wait_type, wait_time_ms, signal_wait_time_ms, waiting_tasks_count) + INSERT #WaitStats(Pass, SampleTime, wait_type, wait_time_ms, thread_time_ms, signal_wait_time_ms, waiting_tasks_count) SELECT x.Pass, x.SampleTime, x.wait_type, SUM(x.sum_wait_time_ms) AS sum_wait_time_ms, + @thread_time_ms AS thread_time_ms, SUM(x.sum_signal_wait_time_ms) AS sum_signal_wait_time_ms, SUM(x.sum_waiting_tasks) AS sum_waiting_tasks FROM ( @@ -41237,7 +41522,35 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, ) GROUP BY x.Pass, x.SampleTime, x.wait_type ORDER BY sum_wait_time_ms DESC;'; - EXEC sp_executesql @StringToExecute, N'@Seconds INT', @Seconds; + + EXEC sys.sp_executesql + @StringToExecute, + N'@StartSampleTime DATETIMEOFFSET, + @Seconds INT, + @thread_time_ms FLOAT', + @StartSampleTime, + @Seconds, + @thread_time_ms; + + WITH w AS + ( + SELECT + total_waits = + CONVERT + ( + FLOAT, + SUM(ws.wait_time_ms) + ) + FROM #WaitStats AS ws + WHERE Pass = 2 + ) + UPDATE ws + SET ws.thread_time_ms += w.total_waits + FROM #WaitStats AS ws + CROSS JOIN w + WHERE ws.Pass = 2 + OPTION(RECOMPILE); + INSERT INTO #FileStats (Pass, SampleTime, DatabaseID, FileID, DatabaseName, FileLogicalName, SizeOnDiskMB, io_stall_read_ms , num_of_reads, [bytes_read] , io_stall_write_ms,num_of_writes, [bytes_written], PhysicalName, TypeDesc, avg_stall_read_ms, avg_stall_write_ms) @@ -42035,6 +42348,36 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, END; /* IF @Seconds >= 30 */ + IF /* Let people on <2016 know about the thread time column */ + ( + @Seconds > 0 + AND @total_cpu_usage = 0 + ) + BEGIN + INSERT INTO + #BlitzFirstResults + ( + CheckID, + Priority, + FindingsGroup, + Finding, + Details, + URL + ) + SELECT + 48, + 254, + N'Informational', + N'Thread Time comes from the plan cache in versions earlier than 2016, and is not as reliable', + N'The oldest plan in your cache is from ' + + CONVERT(nvarchar(30), MIN(s.creation_time)) + + N' and your server was last restarted on ' + + CONVERT(nvarchar(30), MAX(o.sqlserver_start_time)), + N'https://docs.microsoft.com/en-us/sql/relational-databases/system-dynamic-management-views/sys-dm-os-schedulers-transact-sql' + FROM sys.dm_exec_query_stats AS s + CROSS JOIN sys.dm_os_sys_info AS o + OPTION(RECOMPILE); + END /* Let people on <2016 know about the thread time column */ /* If we didn't find anything, apologize. */ IF NOT EXISTS (SELECT * FROM #BlitzFirstResults WHERE Priority < 250) @@ -43144,10 +43487,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, ) SELECT TOP 10 CAST(DATEDIFF(mi,wd1.SampleTime, wd2.SampleTime) / 60.0 AS DECIMAL(18,1)) AS [Hours Sample], + CAST(c.[Total Thread Time (Seconds)] / 60. / 60. AS DECIMAL(18,1)) AS [Thread Time (Hours)], wd1.wait_type, COALESCE(wcat.WaitCategory, 'Other') AS wait_category, - CAST(c.[Wait Time (Seconds)] / 60.0 / 60 AS DECIMAL(18,1)) AS [Wait Time (Hours)], - CAST((wd2.wait_time_ms - wd1.wait_time_ms) / 1000.0 / cores.cpu_count / DATEDIFF(ss, wd1.SampleTime, wd2.SampleTime) AS DECIMAL(18,1)) AS [Per Core Per Hour], + CAST(c.[Wait Time (Seconds)] / 60. / 60. AS DECIMAL(18,1)) AS [Wait Time (Hours)], + CAST((wd2.wait_time_ms - wd1.wait_time_ms) / 1000.0 / cores.cpu_count / DATEDIFF(ss, wd1.SampleTime, wd2.SampleTime) AS DECIMAL(18,1)) AS [Per Core Per Hour], (wd2.waiting_tasks_count - wd1.waiting_tasks_count) AS [Number of Waits], CASE WHEN (wd2.waiting_tasks_count - wd1.waiting_tasks_count) > 0 THEN @@ -43162,8 +43506,10 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, wd2.SampleTime > wd1.SampleTime CROSS APPLY (SELECT SUM(1) AS cpu_count FROM sys.dm_os_schedulers WHERE status = 'VISIBLE ONLINE' AND is_online = 1) AS cores CROSS APPLY (SELECT - CAST((wd2.wait_time_ms-wd1.wait_time_ms)/1000. AS NUMERIC(12,1)) AS [Wait Time (Seconds)], - CAST((wd2.signal_wait_time_ms - wd1.signal_wait_time_ms)/1000. AS NUMERIC(12,1)) AS [Signal Wait Time (Seconds)]) AS c + CAST((wd2.wait_time_ms-wd1.wait_time_ms)/1000. AS DECIMAL(18,1)) AS [Wait Time (Seconds)], + CAST((wd2.signal_wait_time_ms - wd1.signal_wait_time_ms)/1000. AS DECIMAL(18,1)) AS [Signal Wait Time (Seconds)], + CAST((wd2.thread_time_ms)/1000. AS DECIMAL(18,1)) AS [Total Thread Time (Seconds)] + ) AS c LEFT OUTER JOIN ##WaitCategories wcat ON wd1.wait_type = wcat.WaitType WHERE (wd2.waiting_tasks_count - wd1.waiting_tasks_count) > 0 AND wd2.wait_time_ms-wd1.wait_time_ms > 0 @@ -43285,10 +43631,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, SELECT 'WAIT STATS' AS Pattern, b.SampleTime AS [Sample Ended], - CAST(DATEDIFF(mi,wd1.SampleTime, wd2.SampleTime) / 60.0 AS DECIMAL(18,1)) AS [Hours Sample], + CAST(DATEDIFF(mi,wd1.SampleTime, wd2.SampleTime) / 60. AS DECIMAL(18,1)) AS [Hours Sample], + CAST(c.[Total Thread Time (Seconds)] / 60. / 60. AS DECIMAL(18,1)) AS [Thread Time (Hours)], wd1.wait_type, COALESCE(wcat.WaitCategory, 'Other') AS wait_category, - CAST(c.[Wait Time (Seconds)] / 60.0 / 60 AS DECIMAL(18,1)) AS [Wait Time (Hours)], + CAST(c.[Wait Time (Seconds)] / 60. / 60. AS DECIMAL(18,1)) AS [Wait Time (Hours)], CAST((wd2.wait_time_ms - wd1.wait_time_ms) / 1000.0 / cores.cpu_count / DATEDIFF(ss, wd1.SampleTime, wd2.SampleTime) AS DECIMAL(18,1)) AS [Per Core Per Hour], CAST(c.[Signal Wait Time (Seconds)] / 60.0 / 60 AS DECIMAL(18,1)) AS [Signal Wait Time (Hours)], CASE WHEN c.[Wait Time (Seconds)] > 0 @@ -43309,8 +43656,10 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, wd2.SampleTime > wd1.SampleTime CROSS APPLY (SELECT SUM(1) AS cpu_count FROM sys.dm_os_schedulers WHERE status = 'VISIBLE ONLINE' AND is_online = 1) AS cores CROSS APPLY (SELECT - CAST((wd2.wait_time_ms-wd1.wait_time_ms)/1000. AS NUMERIC(12,1)) AS [Wait Time (Seconds)], - CAST((wd2.signal_wait_time_ms - wd1.signal_wait_time_ms)/1000. AS NUMERIC(12,1)) AS [Signal Wait Time (Seconds)]) AS c + CAST((wd2.wait_time_ms-wd1.wait_time_ms)/1000. AS DECIMAL(18,1)) AS [Wait Time (Seconds)], + CAST((wd2.signal_wait_time_ms - wd1.signal_wait_time_ms)/1000. AS DECIMAL(18,1)) AS [Signal Wait Time (Seconds)], + CAST((wd2.thread_time_ms)/1000. AS DECIMAL(18,1)) AS [Total Thread Time (Seconds)] + ) AS c LEFT OUTER JOIN ##WaitCategories wcat ON wd1.wait_type = wcat.WaitType WHERE (wd2.waiting_tasks_count - wd1.waiting_tasks_count) > 0 AND wd2.wait_time_ms-wd1.wait_time_ms > 0 @@ -43327,6 +43676,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 'WAIT STATS' AS Pattern, b.SampleTime AS [Sample Ended], DATEDIFF(ss,wd1.SampleTime, wd2.SampleTime) AS [Seconds Sample], + c.[Total Thread Time (Seconds)], wd1.wait_type, COALESCE(wcat.WaitCategory, 'Other') AS wait_category, c.[Wait Time (Seconds)], @@ -43350,8 +43700,10 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, wd2.SampleTime > wd1.SampleTime CROSS APPLY (SELECT SUM(1) AS cpu_count FROM sys.dm_os_schedulers WHERE status = 'VISIBLE ONLINE' AND is_online = 1) AS cores CROSS APPLY (SELECT - CAST((wd2.wait_time_ms-wd1.wait_time_ms)/1000. AS NUMERIC(12,1)) AS [Wait Time (Seconds)], - CAST((wd2.signal_wait_time_ms - wd1.signal_wait_time_ms)/1000. AS NUMERIC(12,1)) AS [Signal Wait Time (Seconds)]) AS c + CAST((wd2.wait_time_ms-wd1.wait_time_ms)/1000. AS DECIMAL(18,1)) AS [Wait Time (Seconds)], + CAST((wd2.signal_wait_time_ms - wd1.signal_wait_time_ms)/1000. AS DECIMAL(18,1)) AS [Signal Wait Time (Seconds)], + CAST((wd2.thread_time_ms - wd1.thread_time_ms)/1000. AS DECIMAL(18,1)) AS [Total Thread Time (Seconds)] + ) AS c LEFT OUTER JOIN ##WaitCategories wcat ON wd1.wait_type = wcat.WaitType WHERE (wd2.waiting_tasks_count - wd1.waiting_tasks_count) > 0 AND wd2.wait_time_ms-wd1.wait_time_ms > 0 diff --git a/Install-Core-Blitz-No-Query-Store.sql b/Install-Core-Blitz-No-Query-Store.sql index 465677839..c77729328 100755 --- a/Install-Core-Blitz-No-Query-Store.sql +++ b/Install-Core-Blitz-No-Query-Store.sql @@ -38,7 +38,7 @@ AS SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.04', @VersionDate = '20210530'; + SELECT @Version = '8.05', @VersionDate = '20210725'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -356,7 +356,7 @@ AS SET @StringToExecute += QUOTENAME(@SkipChecksServer) + N'.'; END SET @StringToExecute += QUOTENAME(@SkipChecksDatabase) + N'.' + QUOTENAME(@SkipChecksSchema) + N'.' + QUOTENAME(@SkipChecksTable) - + N' WHERE ServerName IS NULL OR ServerName = SERVERPROPERTY(''ServerName'') OPTION (RECOMPILE);'; + + N' WHERE ServerName IS NULL OR ServerName = CAST(SERVERPROPERTY(''ServerName'') AS NVARCHAR(128)) OPTION (RECOMPILE);'; EXEC(@StringToExecute); END; @@ -4312,10 +4312,10 @@ AS SELECT 'containment', 0, 141, 210, 'Containment Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL FROM sys.all_columns WHERE name = 'containment' AND object_id = OBJECT_ID('sys.databases'); - INSERT INTO #DatabaseDefaults - SELECT 'target_recovery_time_in_seconds', 0, 142, 210, 'Target Recovery Time Changed', 'https://www.brentozar.com/go/dbdefaults', NULL - FROM sys.all_columns - WHERE name = 'target_recovery_time_in_seconds' AND object_id = OBJECT_ID('sys.databases'); + --INSERT INTO #DatabaseDefaults + -- SELECT 'target_recovery_time_in_seconds', 0, 142, 210, 'Target Recovery Time Changed', 'https://www.brentozar.com/go/dbdefaults', NULL + -- FROM sys.all_columns + -- WHERE name = 'target_recovery_time_in_seconds' AND object_id = OBJECT_ID('sys.databases'); INSERT INTO #DatabaseDefaults SELECT 'delayed_durability', 0, 143, 210, 'Delayed Durability Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL FROM sys.all_columns @@ -4359,6 +4359,79 @@ AS CLOSE DatabaseDefaultsLoop; DEALLOCATE DatabaseDefaultsLoop; + +/* Check if target recovery interval <> 60 */ +IF + @ProductVersionMajor >= 10 + AND NOT EXISTS + ( + SELECT + 1/0 + FROM #SkipChecks AS sc + WHERE sc.DatabaseName IS NULL + AND sc.CheckID = 257 + ) + BEGIN + IF EXISTS + ( + SELECT + 1/0 + FROM sys.all_columns AS ac + WHERE ac.name = 'target_recovery_time_in_seconds' + AND ac.object_id = OBJECT_ID('sys.databases') + ) + BEGIN + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 257) WITH NOWAIT; + + DECLARE + @tri nvarchar(max) = N' + SELECT + DatabaseName = + d.name, + CheckId = + 257, + Priority = + 50, + FindingsGroup = + N''Performance'', + Finding = + N''Recovery Interval Not Optimal'', + URL = + N''https://sqlperformance.com/2020/05/system-configuration/0-to-60-switching-to-indirect-checkpoints'', + Details = + N''The database '' + + QUOTENAME(d.name) + + N'' has a target recovery interval of '' + + RTRIM(d.target_recovery_time_in_seconds) + + CASE + WHEN d.target_recovery_time_in_seconds = 0 + THEN N'', which is a legacy default, and should be changed to 60.'' + WHEN d.target_recovery_time_in_seconds <> 0 + THEN N'', which is probably a mistake, and should be changed to 60.'' + END + FROM sys.databases AS d + WHERE d.database_id > 4 + AND d.is_read_only = 0 + AND d.is_in_standby = 0 + AND d.target_recovery_time_in_seconds <> 60; + '; + + INSERT INTO + #BlitzResults + ( + DatabaseName, + CheckID, + Priority, + FindingsGroup, + Finding, + URL, + Details + ) + EXEC sys.sp_executesql + @tri; + + END; + END; /*This checks to see if Agent is Offline*/ @@ -6085,7 +6158,7 @@ IF @ProductVersionMajor >= 10 END; - IF @ProductVersionMajor >= 13 AND @ProductVersionMinor < 2149 --CU1 has the fix in it + IF @ProductVersionMajor = 13 AND @ProductVersionMinor < 2149 --2016 CU1 has the fix in it AND NOT EXISTS ( SELECT 1 FROM #SkipChecks WHERE DatabaseName IS NULL AND CheckID = 182 ) @@ -8180,6 +8253,7 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 WHERE o.name = 'dm_server_services' AND c.name = 'instant_file_initialization_enabled' ) begin + SET @StringToExecute = N' INSERT INTO #BlitzResults ( CheckID , [Priority] , @@ -8191,14 +8265,15 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 SELECT 193 AS [CheckID] , 250 AS [Priority] , - 'Server Info' AS [FindingsGroup] , - 'Instant File Initialization Enabled' AS [Finding] , - 'https://www.brentozar.com/go/instant' AS [URL] , - 'The service account has the Perform Volume Maintenance Tasks permission.' + ''Server Info'' AS [FindingsGroup] , + ''Instant File Initialization Enabled'' AS [Finding] , + ''https://www.brentozar.com/go/instant'' AS [URL] , + ''The service account has the Perform Volume Maintenance Tasks permission.'' where exists (select 1 FROM sys.dm_server_services - WHERE instant_file_initialization_enabled = 'Y' - AND filename LIKE '%sqlservr.exe%') - OPTION (RECOMPILE); + WHERE instant_file_initialization_enabled = ''Y'' + AND filename LIKE ''%sqlservr.exe%'') + OPTION (RECOMPILE);'; + EXEC(@StringToExecute); end; end; END; @@ -9027,7 +9102,7 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 IF (OBJECT_ID('tempdb..##BlitzResults', 'U') IS NOT NULL) DROP TABLE ##BlitzResults; SELECT * INTO ##BlitzResults FROM #BlitzResults; SET @query_result_separator = char(9); - SET @StringToExecute = 'SET NOCOUNT ON;SELECT [Priority] , [FindingsGroup] , [Finding] , [DatabaseName] , [URL] , [Details] , CheckID FROM ##BlitzResults ORDER BY Priority , FindingsGroup, Finding, Details; SET NOCOUNT OFF;'; + SET @StringToExecute = 'SET NOCOUNT ON;SELECT [Priority] , [FindingsGroup] , [Finding] , [DatabaseName] , [URL] , [Details] , CheckID FROM ##BlitzResults ORDER BY Priority , FindingsGroup , Finding , DatabaseName , Details; SET NOCOUNT OFF;'; SET @EmailSubject = 'sp_Blitz Results for ' + @@SERVERNAME; SET @EmailBody = 'sp_Blitz ' + CAST(CONVERT(DATETIME, @VersionDate, 102) AS VARCHAR(100)) + '. http://FirstResponderKit.org'; IF @EmailProfile IS NULL @@ -9172,7 +9247,7 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 + @OutputTableName + ' (ServerName, CheckDate, CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details, QueryPlan, QueryPlanFiltered) SELECT ''' + CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128)) - + ''', SYSDATETIMEOFFSET(), CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details, CAST(QueryPlan AS NVARCHAR(MAX)), QueryPlanFiltered FROM #BlitzResults ORDER BY Priority , FindingsGroup , Finding , Details'; + + ''', SYSDATETIMEOFFSET(), CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details, CAST(QueryPlan AS NVARCHAR(MAX)), QueryPlanFiltered FROM #BlitzResults ORDER BY Priority , FindingsGroup , Finding , DatabaseName , Details'; EXEC(@StringToExecute); END; @@ -9189,7 +9264,7 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 + @OutputTableName + ' (ServerName, CheckDate, CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details, QueryPlan, QueryPlanFiltered) SELECT ''' + CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128)) - + ''', SYSDATETIMEOFFSET(), CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details, CAST(QueryPlan AS NVARCHAR(MAX)), QueryPlanFiltered FROM #BlitzResults ORDER BY Priority , FindingsGroup , Finding , Details'; + + ''', SYSDATETIMEOFFSET(), CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details, CAST(QueryPlan AS NVARCHAR(MAX)), QueryPlanFiltered FROM #BlitzResults ORDER BY Priority , FindingsGroup , Finding , DatabaseName , Details'; END; ELSE begin @@ -9202,7 +9277,7 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 + @OutputTableName + ' (ServerName, CheckDate, CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details, QueryPlan, QueryPlanFiltered) SELECT ''' + CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128)) - + ''', SYSDATETIMEOFFSET(), CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details, QueryPlan, QueryPlanFiltered FROM #BlitzResults ORDER BY Priority , FindingsGroup , Finding , Details'; + + ''', SYSDATETIMEOFFSET(), CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details, QueryPlan, QueryPlanFiltered FROM #BlitzResults ORDER BY Priority , FindingsGroup , Finding , DatabaseName , Details'; END; EXEC(@StringToExecute); @@ -9238,7 +9313,7 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 + @OutputTableName + ' (ServerName, CheckDate, CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details, QueryPlan, QueryPlanFiltered) SELECT ''' + CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128)) - + ''', SYSDATETIMEOFFSET(), CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details, QueryPlan, QueryPlanFiltered FROM #BlitzResults ORDER BY Priority , FindingsGroup , Finding , Details'; + + ''', SYSDATETIMEOFFSET(), CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details, QueryPlan, QueryPlanFiltered FROM #BlitzResults ORDER BY Priority , FindingsGroup , Finding , DatabaseName , Details'; EXEC(@StringToExecute); END; @@ -9479,7 +9554,7 @@ AS SET NOCOUNT ON; SET STATISTICS XML OFF; -SELECT @Version = '8.04', @VersionDate = '20210530'; +SELECT @Version = '8.05', @VersionDate = '20210725'; IF(@VersionCheckMode = 1) BEGIN @@ -10357,7 +10432,7 @@ AS SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.04', @VersionDate = '20210530'; + SELECT @Version = '8.05', @VersionDate = '20210725'; IF(@VersionCheckMode = 1) BEGIN @@ -12138,7 +12213,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.04', @VersionDate = '20210530'; +SELECT @Version = '8.05', @VersionDate = '20210725'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -13423,9 +13498,9 @@ database_id INT CREATE TABLE #plan_usage ( duplicate_plan_hashes BIGINT NULL, - percent_duplicate DECIMAL(5, 2) NULL, + percent_duplicate DECIMAL(9, 2) NULL, single_use_plan_count BIGINT NULL, - percent_single DECIMAL(5, 2) NULL, + percent_single DECIMAL(9, 2) NULL, total_plans BIGINT NULL, spid INT ); @@ -19398,7 +19473,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.04', @VersionDate = '20210530'; +SELECT @Version = '8.05', @VersionDate = '20210725'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -20309,9 +20384,17 @@ FROM #Ignore_Databases i; /* Last startup */ -SELECT @DaysUptime = CAST(DATEDIFF(HOUR, create_date, GETDATE()) / 24. AS NUMERIC (23,2)) -FROM sys.databases -WHERE database_id = 2; +IF COLUMNPROPERTY(OBJECT_ID('sys.dm_os_sys_info'),'sqlserver_start_time','ColumnID') IS NOT NULL +BEGIN + SELECT @DaysUptime = CAST(DATEDIFF(HOUR, sqlserver_start_time, GETDATE()) / 24. AS NUMERIC (23,2)) + FROM sys.dm_os_sys_info; +END +ELSE +BEGIN + SELECT @DaysUptime = CAST(DATEDIFF(HOUR, create_date, GETDATE()) / 24. AS NUMERIC (23,2)) + FROM sys.databases + WHERE database_id = 2; +END IF @DaysUptime = 0 OR @DaysUptime IS NULL SET @DaysUptime = .01; @@ -22067,17 +22150,18 @@ BEGIN /* Show histograms for all stats on this table. More info: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/1900 */ IF EXISTS (SELECT * FROM sys.all_objects WHERE name = 'dm_db_stats_histogram') BEGIN - SET @dsql=N'SELECT s.name AS [Stat Name], c.name AS [Leading Column Name], hist.step_number AS [Step Number], + SET @dsql = N'USE ' + QUOTENAME(@DatabaseName) + N'; + SELECT s.name AS [Stat Name], c.name AS [Leading Column Name], hist.step_number AS [Step Number], hist.range_high_key AS [Range High Key], hist.range_rows AS [Range Rows], hist.equal_rows AS [Equal Rows], hist.distinct_range_rows AS [Distinct Range Rows], hist.average_range_rows AS [Average Range Rows], s.auto_created AS [Auto-Created], s.user_created AS [User-Created], props.last_updated AS [Last Updated], props.modification_counter AS [Modification Counter], props.rows AS [Table Rows], props.rows_sampled AS [Rows Sampled], s.stats_id AS [StatsID] - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.stats AS s - INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.stats_columns sc ON s.object_id = sc.object_id AND s.stats_id = sc.stats_id AND sc.stats_column_id = 1 - INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns c ON sc.object_id = c.object_id AND sc.column_id = c.column_id - CROSS APPLY ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_stats_properties(s.object_id, s.stats_id) AS props - CROSS APPLY ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_stats_histogram(s.[object_id], s.stats_id) AS hist + FROM sys.stats AS s + INNER JOIN sys.stats_columns sc ON s.object_id = sc.object_id AND s.stats_id = sc.stats_id AND sc.stats_column_id = 1 + INNER JOIN sys.columns c ON sc.object_id = c.object_id AND sc.column_id = c.column_id + CROSS APPLY sys.dm_db_stats_properties(s.object_id, s.stats_id) AS props + CROSS APPLY sys.dm_db_stats_histogram(s.[object_id], s.stats_id) AS hist WHERE s.object_id = @ObjectID ORDER BY s.auto_created, s.user_created, s.name, hist.step_number;'; EXEC sp_executesql @dsql, N'@ObjectID INT', @ObjectID; @@ -23338,7 +23422,7 @@ BEGIN; i.index_sanity_id, 150 AS Priority, N'Index Hoarder' AS findings_group, - N'Non-Unique Clustered JIndex' AS finding, + N'Non-Unique Clustered Index' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/IndexHoarder' AS URL, N'Uniquifiers will be required! Clustered index: ' + i.db_schema_object_name @@ -24937,7 +25021,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.04', @VersionDate = '20210530'; +SELECT @Version = '8.05', @VersionDate = '20210725'; IF(@VersionCheckMode = 1) @@ -26708,6 +26792,7 @@ ALTER PROCEDURE dbo.sp_BlitzWho @MinBlockingSeconds INT = 0 , @CheckDateOverride DATETIMEOFFSET = NULL, @ShowActualParameters BIT = 0, + @GetOuterCommand BIT = 0, @Version VARCHAR(30) = NULL OUTPUT, @VersionDate DATETIME = NULL OUTPUT, @VersionCheckMode BIT = 0, @@ -26718,7 +26803,7 @@ BEGIN SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.04', @VersionDate = '20210530'; + SELECT @Version = '8.05', @VersionDate = '20210725'; IF(@VersionCheckMode = 1) BEGIN @@ -26848,6 +26933,7 @@ IF @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @Output [session_id] [smallint] NOT NULL, [database_name] [nvarchar](128) NULL, [query_text] [nvarchar](max) NULL, + [outer_command] NVARCHAR(4000) NULL, [query_plan] [xml] NULL, [live_query_plan] [xml] NULL, [cached_parameter_info] [nvarchar](max) NULL, @@ -26964,6 +27050,13 @@ IF @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @Output ALTER TABLE ' + @ObjectFullName + N' ADD live_parameter_info NVARCHAR(MAX) NULL;'; EXEC(@StringToExecute); + /* If the table doesn't have the new outer_command column, add it. See Github #2887. */ + SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableName; + SET @StringToExecute = N'IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns + WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''outer_command'') + ALTER TABLE ' + @ObjectFullName + N' ADD outer_command NVARCHAR(4000) NULL;'; + EXEC(@StringToExecute); + /* Delete history older than @OutputTableRetentionDays */ SET @OutputTableCleanupDate = CAST( (DATEADD(DAY, -1 * @OutputTableRetentionDays, GETDATE() ) ) AS DATE); SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' @@ -27261,7 +27354,64 @@ SELECT @BlockingCheck = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; FROM sys.sysprocesses AS sys1 JOIN sys.sysprocesses AS sys2 ON sys1.spid = sys2.blocked; + '+CASE + WHEN (@GetOuterCommand = 1 AND (NOT EXISTS(SELECT 1 FROM sys.all_objects WHERE [name] = N'dm_exec_input_buffer'))) THEN N' + DECLARE @session_id SMALLINT; + DECLARE @Sessions TABLE + ( + session_id INT + ); + + DECLARE @inputbuffer TABLE + ( + ID INT IDENTITY(1,1), + session_id INT, + event_type NVARCHAR(30), + parameters SMALLINT, + event_info NVARCHAR(4000) + ); + DECLARE inputbuffer_cursor + + CURSOR LOCAL FAST_FORWARD + FOR + SELECT session_id + FROM sys.dm_exec_sessions + WHERE session_id <> @@SPID + AND is_user_process = 1; + + OPEN inputbuffer_cursor; + + FETCH NEXT FROM inputbuffer_cursor INTO @session_id; + + WHILE (@@FETCH_STATUS = 0) + BEGIN; + BEGIN TRY; + + INSERT INTO @inputbuffer ([event_type],[parameters],[event_info]) + EXEC sp_executesql + N''DBCC INPUTBUFFER(@session_id) WITH NO_INFOMSGS;'', + N''@session_id SMALLINT'', + @session_id; + + UPDATE @inputbuffer + SET session_id = @session_id + WHERE ID = SCOPE_IDENTITY(); + + END TRY + BEGIN CATCH + RAISERROR(''DBCC inputbuffer failed for session %d'',0,0,@session_id) WITH NOWAIT; + END CATCH; + + FETCH NEXT FROM inputbuffer_cursor INTO @session_id + + END; + + CLOSE inputbuffer_cursor; + DEALLOCATE inputbuffer_cursor;' + ELSE N'' + END+ + N' DECLARE @LiveQueryPlans TABLE ( @@ -27270,7 +27420,6 @@ SELECT @BlockingCheck = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; ); ' - IF EXISTS (SELECT * FROM sys.all_columns WHERE object_id = OBJECT_ID('sys.dm_exec_query_statistics_xml') AND name = 'query_plan') BEGIN SET @BlockingCheck = @BlockingCheck + N' @@ -27297,6 +27446,10 @@ BEGIN ELSE query_stats.statement_end_offset END - query_stats.statement_start_offset ) / 2 ) + 1), dest.text) AS query_text , + '+CASE + WHEN @GetOuterCommand = 1 THEN N'CAST(event_info AS NVARCHAR(4000)) AS outer_command,' + ELSE N'' + END+N' derp.query_plan , qmg.query_cost , s.status , @@ -27416,6 +27569,14 @@ BEGIN SET @StringToExecute += N'FROM sys.dm_exec_sessions AS s + '+ + CASE + WHEN @GetOuterCommand = 1 THEN CASE + WHEN EXISTS(SELECT 1 FROM sys.all_objects WHERE [name] = N'dm_exec_input_buffer') THEN N'OUTER APPLY sys.dm_exec_input_buffer (s.session_id, 0) AS ib' + ELSE N'LEFT JOIN @inputbuffer ib ON s.session_id = ib.session_id' + END + ELSE N'' + END+N' LEFT JOIN sys.dm_exec_requests AS r ON r.session_id = s.session_id LEFT JOIN ( SELECT DISTINCT @@ -27502,6 +27663,10 @@ IF @ProductVersionMajor >= 11 ELSE query_stats.statement_end_offset END - query_stats.statement_start_offset ) / 2 ) + 1), dest.text) AS query_text , + '+CASE + WHEN @GetOuterCommand = 1 THEN N'CAST(event_info AS NVARCHAR(4000)) AS outer_command,' + ELSE N'' + END+N' derp.query_plan , CAST(COALESCE(qs_live.Query_Plan, '''') AS XML) AS live_query_plan , STUFF((SELECT DISTINCT N'', '' + Node.Data.value(''(@Column)[1]'', ''NVARCHAR(4000)'') + N'' {'' + Node.Data.value(''(@ParameterDataType)[1]'', ''NVARCHAR(4000)'') + N''}: '' + Node.Data.value(''(@ParameterCompiledValue)[1]'', ''NVARCHAR(4000)'') @@ -27679,7 +27844,16 @@ IF @ProductVersionMajor >= 11 END /* IF @ExpertMode = 1 */ SET @StringToExecute += - N' FROM sys.dm_exec_sessions AS s + N' FROM sys.dm_exec_sessions AS s'+ + CASE + WHEN @GetOuterCommand = 1 THEN CASE + WHEN EXISTS(SELECT 1 FROM sys.all_objects WHERE [name] = N'dm_exec_input_buffer') THEN N' + OUTER APPLY sys.dm_exec_input_buffer (s.session_id, 0) AS ib' + ELSE N' + LEFT JOIN @inputbuffer ib ON s.session_id = ib.session_id' + END + ELSE N'' + END+N' LEFT JOIN sys.dm_exec_requests AS r ON r.session_id = s.session_id LEFT JOIN ( SELECT DISTINCT @@ -27833,7 +28007,8 @@ IF @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @Output ,[elapsed_time] ,[session_id] ,[database_name] - ,[query_text] + ,[query_text]' + + CASE WHEN @GetOuterCommand = 1 THEN N',[outer_command]' ELSE N'' END + N' ,[query_plan]' + CASE WHEN @ProductVersionMajor >= 11 THEN N',[live_query_plan]' ELSE N'' END + CASE WHEN @ProductVersionMajor >= 11 THEN N',[cached_parameter_info]' ELSE N'' END @@ -28004,6 +28179,7 @@ DELETE FROM dbo.SqlServerVersions; INSERT INTO dbo.SqlServerVersions (MajorVersionNumber, MinorVersionNumber, Branch, [Url], ReleaseDate, MainstreamSupportEndDate, ExtendedSupportEndDate, MajorVersionName, MinorVersionName) VALUES + (15, 4138, 'CU11', 'https://support.microsoft.com/en-us/help/5003249', '2021-06-10', '2025-01-01', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 11'), (15, 4123, 'CU10', 'https://support.microsoft.com/en-us/help/5001090', '2021-04-06', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 10'), (15, 4102, 'CU9', 'https://support.microsoft.com/en-us/help/5000642', '2021-02-11', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 9 '), (15, 4073, 'CU8 GDR', 'https://support.microsoft.com/en-us/help/4583459', '2021-01-12', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 8 GDR '), @@ -28017,6 +28193,7 @@ VALUES (15, 4003, 'CU1', 'https://support.microsoft.com/en-us/help/4527376', '2020-01-07', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 1 '), (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 '), + (14, 3410, 'RTM CU25', 'https://support.microsoft.com/en-us/help/5003830', '2021-07-12', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 25'), (14, 3391, 'RTM CU24', 'https://support.microsoft.com/en-us/help/5001228', '2021-05-10', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 24'), (14, 3381, 'RTM CU23', 'https://support.microsoft.com/en-us/help/5000685', '2021-02-25', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 23'), (14, 3370, 'RTM CU22 GDR', 'https://support.microsoft.com/en-us/help/4583457', '2021-01-12', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 22 GDR'), @@ -28396,7 +28573,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.04', @VersionDate = '20210530'; +SELECT @Version = '8.05', @VersionDate = '20210725'; IF(@VersionCheckMode = 1) BEGIN @@ -28489,7 +28666,10 @@ DECLARE @StringToExecute NVARCHAR(MAX), @UnquotedOutputDatabaseName NVARCHAR(256) = @OutputDatabaseName , @UnquotedOutputSchemaName NVARCHAR(256) = @OutputSchemaName , @LocalServerName NVARCHAR(128) = CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128)), - @dm_exec_query_statistics_xml BIT = 0; + @dm_exec_query_statistics_xml BIT = 0, + @total_cpu_usage BIT = 0, + @get_thread_time_ms NVARCHAR(MAX) = N'', + @thread_time_ms FLOAT = 0; /* Sanitize our inputs */ SELECT @@ -28643,6 +28823,54 @@ BEGIN @FinishSampleTimeWaitFor = DATEADD(ss, @Seconds, GETDATE()); + IF EXISTS + ( + + SELECT + 1/0 + FROM sys.all_columns AS ac + WHERE ac.object_id = OBJECT_ID('sys.dm_os_schedulers') + AND ac.name = 'total_cpu_usage_ms' + + ) + BEGIN + + SELECT + @total_cpu_usage = 1, + @get_thread_time_ms += + N' + SELECT + @thread_time_ms = + CONVERT + ( + FLOAT, + SUM(s.total_cpu_usage_ms) + ) + FROM sys.dm_os_schedulers AS s + WHERE s.status = ''VISIBLE ONLINE'' + AND s.is_online = 1 + OPTION(RECOMPILE); + '; + + END + ELSE + BEGIN + SELECT + @total_cpu_usage = 0, + @get_thread_time_ms += + N' + SELECT + @thread_time_ms = + CONVERT + ( + FLOAT, + SUM(s.total_worker_time / 1000.) + ) + FROM sys.dm_exec_query_stats AS s + OPTION(RECOMPILE); + '; + END + RAISERROR('Now starting diagnostic analysis',10,1) WITH NOWAIT; /* @@ -28692,7 +28920,15 @@ BEGIN IF OBJECT_ID('tempdb..#WaitStats') IS NOT NULL DROP TABLE #WaitStats; - CREATE TABLE #WaitStats (Pass TINYINT NOT NULL, wait_type NVARCHAR(60), wait_time_ms BIGINT, signal_wait_time_ms BIGINT, waiting_tasks_count BIGINT, SampleTime DATETIMEOFFSET); + CREATE TABLE #WaitStats ( + Pass TINYINT NOT NULL, + wait_type NVARCHAR(60), + wait_time_ms BIGINT, + thread_time_ms FLOAT, + signal_wait_time_ms BIGINT, + waiting_tasks_count BIGINT, + SampleTime datetimeoffset + ); IF OBJECT_ID('tempdb..#FileStats') IS NOT NULL DROP TABLE #FileStats; @@ -29643,16 +29879,30 @@ BEGIN INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP Transactions','Transactions created/sec',NULL); END; + + IF @total_cpu_usage IN (0, 1) + BEGIN + EXEC sys.sp_executesql + @get_thread_time_ms, + N'@thread_time_ms FLOAT OUTPUT', + @thread_time_ms OUTPUT; + END + /* Populate #FileStats, #PerfmonStats, #WaitStats with DMV data. After we finish doing our checks, we'll take another sample and compare them. */ RAISERROR('Capturing first pass of wait stats, perfmon counters, file stats',10,1) WITH NOWAIT; SET @StringToExecute = N' - INSERT #WaitStats(Pass, SampleTime, wait_type, wait_time_ms, signal_wait_time_ms, waiting_tasks_count) + INSERT #WaitStats(Pass, SampleTime, wait_type, wait_time_ms, thread_time_ms, signal_wait_time_ms, waiting_tasks_count) SELECT x.Pass, x.SampleTime, x.wait_type, SUM(x.sum_wait_time_ms) AS sum_wait_time_ms, + CASE @Seconds + WHEN 0 + THEN 0 + ELSE @thread_time_ms + END AS thread_time_ms, SUM(x.sum_signal_wait_time_ms) AS sum_signal_wait_time_ms, SUM(x.sum_waiting_tasks) AS sum_waiting_tasks FROM ( @@ -29692,9 +29942,35 @@ BEGIN ) GROUP BY x.Pass, x.SampleTime, x.wait_type ORDER BY sum_wait_time_ms DESC;' - EXEC sp_executesql @StringToExecute, N'@StartSampleTime DATETIMEOFFSET, @Seconds INT', @StartSampleTime, @Seconds; - - + + EXEC sys.sp_executesql + @StringToExecute, + N'@StartSampleTime DATETIMEOFFSET, + @Seconds INT, + @thread_time_ms FLOAT', + @StartSampleTime, + @Seconds, + @thread_time_ms; + + WITH w AS + ( + SELECT + total_waits = + CONVERT + ( + FLOAT, + SUM(ws.wait_time_ms) + ) + FROM #WaitStats AS ws + WHERE Pass = 1 + ) + UPDATE ws + SET ws.thread_time_ms += w.total_waits + FROM #WaitStats AS ws + CROSS JOIN w + WHERE ws.Pass = 1 + OPTION(RECOMPILE); + INSERT INTO #FileStats (Pass, SampleTime, DatabaseID, FileID, DatabaseName, FileLogicalName, SizeOnDiskMB, io_stall_read_ms , num_of_reads, [bytes_read] , io_stall_write_ms,num_of_writes, [bytes_written], PhysicalName, TypeDesc) SELECT @@ -29789,7 +30065,7 @@ BEGIN WHERE resource_type = N'DATABASE' AND request_mode = N'S' AND request_status = N'GRANT' - AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.session_id = db.request_session_id + AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.session_id = db.request_session_id AND s.database_id = db.resource_database_id CROSS APPLY sys.dm_exec_query_plan(r.plan_handle) pl WHERE r.command LIKE 'BACKUP%' AND r.start_time <= DATEADD(minute, -5, GETDATE()) @@ -30016,9 +30292,8 @@ BEGIN db.[resource_database_id] AS DatabaseID, DB_NAME(db.resource_database_id) AS DatabaseName, (SELECT TOP 1 [text] FROM sys.dm_exec_sql_text(c.most_recent_sql_handle)) AS QueryText, - sessions_with_transactions.open_transaction_count AS OpenTransactionCount - FROM (SELECT session_id, SUM(open_transaction_count) AS open_transaction_count FROM sys.dm_exec_requests WHERE open_transaction_count > 0 GROUP BY session_id) AS sessions_with_transactions - INNER JOIN sys.dm_exec_sessions s ON sessions_with_transactions.session_id = s.session_id + s.open_transaction_count AS OpenTransactionCount + FROM sys.dm_exec_sessions s INNER JOIN sys.dm_exec_connections c ON s.session_id = c.session_id INNER JOIN ( SELECT DISTINCT request_session_id, resource_database_id @@ -30028,6 +30303,7 @@ BEGIN AND request_status = N'GRANT' AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.session_id = db.request_session_id WHERE s.status = 'sleeping' + AND s.open_transaction_count > 0 AND s.last_request_end_time < DATEADD(ss, -10, SYSDATETIME()) AND EXISTS(SELECT * FROM sys.dm_tran_locks WHERE request_session_id = s.session_id AND NOT (resource_type = N'DATABASE' AND request_mode = N'S' AND request_status = N'GRANT' AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE')); @@ -30800,15 +31076,24 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, WAITFOR TIME @FinishSampleTimeWaitFor; END; + IF @total_cpu_usage IN (0, 1) + BEGIN + EXEC sys.sp_executesql + @get_thread_time_ms, + N'@thread_time_ms FLOAT OUTPUT', + @thread_time_ms OUTPUT; + END + RAISERROR('Capturing second pass of wait stats, perfmon counters, file stats',10,1) WITH NOWAIT; /* Populate #FileStats, #PerfmonStats, #WaitStats with DMV data. In a second, we'll compare these. */ SET @StringToExecute = N' - INSERT #WaitStats(Pass, SampleTime, wait_type, wait_time_ms, signal_wait_time_ms, waiting_tasks_count) + INSERT #WaitStats(Pass, SampleTime, wait_type, wait_time_ms, thread_time_ms, signal_wait_time_ms, waiting_tasks_count) SELECT x.Pass, x.SampleTime, x.wait_type, SUM(x.sum_wait_time_ms) AS sum_wait_time_ms, + @thread_time_ms AS thread_time_ms, SUM(x.sum_signal_wait_time_ms) AS sum_signal_wait_time_ms, SUM(x.sum_waiting_tasks) AS sum_waiting_tasks FROM ( @@ -30848,7 +31133,35 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, ) GROUP BY x.Pass, x.SampleTime, x.wait_type ORDER BY sum_wait_time_ms DESC;'; - EXEC sp_executesql @StringToExecute, N'@Seconds INT', @Seconds; + + EXEC sys.sp_executesql + @StringToExecute, + N'@StartSampleTime DATETIMEOFFSET, + @Seconds INT, + @thread_time_ms FLOAT', + @StartSampleTime, + @Seconds, + @thread_time_ms; + + WITH w AS + ( + SELECT + total_waits = + CONVERT + ( + FLOAT, + SUM(ws.wait_time_ms) + ) + FROM #WaitStats AS ws + WHERE Pass = 2 + ) + UPDATE ws + SET ws.thread_time_ms += w.total_waits + FROM #WaitStats AS ws + CROSS JOIN w + WHERE ws.Pass = 2 + OPTION(RECOMPILE); + INSERT INTO #FileStats (Pass, SampleTime, DatabaseID, FileID, DatabaseName, FileLogicalName, SizeOnDiskMB, io_stall_read_ms , num_of_reads, [bytes_read] , io_stall_write_ms,num_of_writes, [bytes_written], PhysicalName, TypeDesc, avg_stall_read_ms, avg_stall_write_ms) @@ -31646,6 +31959,36 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, END; /* IF @Seconds >= 30 */ + IF /* Let people on <2016 know about the thread time column */ + ( + @Seconds > 0 + AND @total_cpu_usage = 0 + ) + BEGIN + INSERT INTO + #BlitzFirstResults + ( + CheckID, + Priority, + FindingsGroup, + Finding, + Details, + URL + ) + SELECT + 48, + 254, + N'Informational', + N'Thread Time comes from the plan cache in versions earlier than 2016, and is not as reliable', + N'The oldest plan in your cache is from ' + + CONVERT(nvarchar(30), MIN(s.creation_time)) + + N' and your server was last restarted on ' + + CONVERT(nvarchar(30), MAX(o.sqlserver_start_time)), + N'https://docs.microsoft.com/en-us/sql/relational-databases/system-dynamic-management-views/sys-dm-os-schedulers-transact-sql' + FROM sys.dm_exec_query_stats AS s + CROSS JOIN sys.dm_os_sys_info AS o + OPTION(RECOMPILE); + END /* Let people on <2016 know about the thread time column */ /* If we didn't find anything, apologize. */ IF NOT EXISTS (SELECT * FROM #BlitzFirstResults WHERE Priority < 250) @@ -32755,10 +33098,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, ) SELECT TOP 10 CAST(DATEDIFF(mi,wd1.SampleTime, wd2.SampleTime) / 60.0 AS DECIMAL(18,1)) AS [Hours Sample], + CAST(c.[Total Thread Time (Seconds)] / 60. / 60. AS DECIMAL(18,1)) AS [Thread Time (Hours)], wd1.wait_type, COALESCE(wcat.WaitCategory, 'Other') AS wait_category, - CAST(c.[Wait Time (Seconds)] / 60.0 / 60 AS DECIMAL(18,1)) AS [Wait Time (Hours)], - CAST((wd2.wait_time_ms - wd1.wait_time_ms) / 1000.0 / cores.cpu_count / DATEDIFF(ss, wd1.SampleTime, wd2.SampleTime) AS DECIMAL(18,1)) AS [Per Core Per Hour], + CAST(c.[Wait Time (Seconds)] / 60. / 60. AS DECIMAL(18,1)) AS [Wait Time (Hours)], + CAST((wd2.wait_time_ms - wd1.wait_time_ms) / 1000.0 / cores.cpu_count / DATEDIFF(ss, wd1.SampleTime, wd2.SampleTime) AS DECIMAL(18,1)) AS [Per Core Per Hour], (wd2.waiting_tasks_count - wd1.waiting_tasks_count) AS [Number of Waits], CASE WHEN (wd2.waiting_tasks_count - wd1.waiting_tasks_count) > 0 THEN @@ -32773,8 +33117,10 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, wd2.SampleTime > wd1.SampleTime CROSS APPLY (SELECT SUM(1) AS cpu_count FROM sys.dm_os_schedulers WHERE status = 'VISIBLE ONLINE' AND is_online = 1) AS cores CROSS APPLY (SELECT - CAST((wd2.wait_time_ms-wd1.wait_time_ms)/1000. AS NUMERIC(12,1)) AS [Wait Time (Seconds)], - CAST((wd2.signal_wait_time_ms - wd1.signal_wait_time_ms)/1000. AS NUMERIC(12,1)) AS [Signal Wait Time (Seconds)]) AS c + CAST((wd2.wait_time_ms-wd1.wait_time_ms)/1000. AS DECIMAL(18,1)) AS [Wait Time (Seconds)], + CAST((wd2.signal_wait_time_ms - wd1.signal_wait_time_ms)/1000. AS DECIMAL(18,1)) AS [Signal Wait Time (Seconds)], + CAST((wd2.thread_time_ms)/1000. AS DECIMAL(18,1)) AS [Total Thread Time (Seconds)] + ) AS c LEFT OUTER JOIN ##WaitCategories wcat ON wd1.wait_type = wcat.WaitType WHERE (wd2.waiting_tasks_count - wd1.waiting_tasks_count) > 0 AND wd2.wait_time_ms-wd1.wait_time_ms > 0 @@ -32896,10 +33242,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, SELECT 'WAIT STATS' AS Pattern, b.SampleTime AS [Sample Ended], - CAST(DATEDIFF(mi,wd1.SampleTime, wd2.SampleTime) / 60.0 AS DECIMAL(18,1)) AS [Hours Sample], + CAST(DATEDIFF(mi,wd1.SampleTime, wd2.SampleTime) / 60. AS DECIMAL(18,1)) AS [Hours Sample], + CAST(c.[Total Thread Time (Seconds)] / 60. / 60. AS DECIMAL(18,1)) AS [Thread Time (Hours)], wd1.wait_type, COALESCE(wcat.WaitCategory, 'Other') AS wait_category, - CAST(c.[Wait Time (Seconds)] / 60.0 / 60 AS DECIMAL(18,1)) AS [Wait Time (Hours)], + CAST(c.[Wait Time (Seconds)] / 60. / 60. AS DECIMAL(18,1)) AS [Wait Time (Hours)], CAST((wd2.wait_time_ms - wd1.wait_time_ms) / 1000.0 / cores.cpu_count / DATEDIFF(ss, wd1.SampleTime, wd2.SampleTime) AS DECIMAL(18,1)) AS [Per Core Per Hour], CAST(c.[Signal Wait Time (Seconds)] / 60.0 / 60 AS DECIMAL(18,1)) AS [Signal Wait Time (Hours)], CASE WHEN c.[Wait Time (Seconds)] > 0 @@ -32920,8 +33267,10 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, wd2.SampleTime > wd1.SampleTime CROSS APPLY (SELECT SUM(1) AS cpu_count FROM sys.dm_os_schedulers WHERE status = 'VISIBLE ONLINE' AND is_online = 1) AS cores CROSS APPLY (SELECT - CAST((wd2.wait_time_ms-wd1.wait_time_ms)/1000. AS NUMERIC(12,1)) AS [Wait Time (Seconds)], - CAST((wd2.signal_wait_time_ms - wd1.signal_wait_time_ms)/1000. AS NUMERIC(12,1)) AS [Signal Wait Time (Seconds)]) AS c + CAST((wd2.wait_time_ms-wd1.wait_time_ms)/1000. AS DECIMAL(18,1)) AS [Wait Time (Seconds)], + CAST((wd2.signal_wait_time_ms - wd1.signal_wait_time_ms)/1000. AS DECIMAL(18,1)) AS [Signal Wait Time (Seconds)], + CAST((wd2.thread_time_ms)/1000. AS DECIMAL(18,1)) AS [Total Thread Time (Seconds)] + ) AS c LEFT OUTER JOIN ##WaitCategories wcat ON wd1.wait_type = wcat.WaitType WHERE (wd2.waiting_tasks_count - wd1.waiting_tasks_count) > 0 AND wd2.wait_time_ms-wd1.wait_time_ms > 0 @@ -32938,6 +33287,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 'WAIT STATS' AS Pattern, b.SampleTime AS [Sample Ended], DATEDIFF(ss,wd1.SampleTime, wd2.SampleTime) AS [Seconds Sample], + c.[Total Thread Time (Seconds)], wd1.wait_type, COALESCE(wcat.WaitCategory, 'Other') AS wait_category, c.[Wait Time (Seconds)], @@ -32961,8 +33311,10 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, wd2.SampleTime > wd1.SampleTime CROSS APPLY (SELECT SUM(1) AS cpu_count FROM sys.dm_os_schedulers WHERE status = 'VISIBLE ONLINE' AND is_online = 1) AS cores CROSS APPLY (SELECT - CAST((wd2.wait_time_ms-wd1.wait_time_ms)/1000. AS NUMERIC(12,1)) AS [Wait Time (Seconds)], - CAST((wd2.signal_wait_time_ms - wd1.signal_wait_time_ms)/1000. AS NUMERIC(12,1)) AS [Signal Wait Time (Seconds)]) AS c + CAST((wd2.wait_time_ms-wd1.wait_time_ms)/1000. AS DECIMAL(18,1)) AS [Wait Time (Seconds)], + CAST((wd2.signal_wait_time_ms - wd1.signal_wait_time_ms)/1000. AS DECIMAL(18,1)) AS [Signal Wait Time (Seconds)], + CAST((wd2.thread_time_ms - wd1.thread_time_ms)/1000. AS DECIMAL(18,1)) AS [Total Thread Time (Seconds)] + ) AS c LEFT OUTER JOIN ##WaitCategories wcat ON wd1.wait_type = wcat.WaitType WHERE (wd2.waiting_tasks_count - wd1.waiting_tasks_count) > 0 AND wd2.wait_time_ms-wd1.wait_time_ms > 0 diff --git a/Install-Core-Blitz-With-Query-Store.sql b/Install-Core-Blitz-With-Query-Store.sql index a17e9128e..75e10ceb7 100755 --- a/Install-Core-Blitz-With-Query-Store.sql +++ b/Install-Core-Blitz-With-Query-Store.sql @@ -38,7 +38,7 @@ AS SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.04', @VersionDate = '20210530'; + SELECT @Version = '8.05', @VersionDate = '20210725'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -356,7 +356,7 @@ AS SET @StringToExecute += QUOTENAME(@SkipChecksServer) + N'.'; END SET @StringToExecute += QUOTENAME(@SkipChecksDatabase) + N'.' + QUOTENAME(@SkipChecksSchema) + N'.' + QUOTENAME(@SkipChecksTable) - + N' WHERE ServerName IS NULL OR ServerName = SERVERPROPERTY(''ServerName'') OPTION (RECOMPILE);'; + + N' WHERE ServerName IS NULL OR ServerName = CAST(SERVERPROPERTY(''ServerName'') AS NVARCHAR(128)) OPTION (RECOMPILE);'; EXEC(@StringToExecute); END; @@ -4312,10 +4312,10 @@ AS SELECT 'containment', 0, 141, 210, 'Containment Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL FROM sys.all_columns WHERE name = 'containment' AND object_id = OBJECT_ID('sys.databases'); - INSERT INTO #DatabaseDefaults - SELECT 'target_recovery_time_in_seconds', 0, 142, 210, 'Target Recovery Time Changed', 'https://www.brentozar.com/go/dbdefaults', NULL - FROM sys.all_columns - WHERE name = 'target_recovery_time_in_seconds' AND object_id = OBJECT_ID('sys.databases'); + --INSERT INTO #DatabaseDefaults + -- SELECT 'target_recovery_time_in_seconds', 0, 142, 210, 'Target Recovery Time Changed', 'https://www.brentozar.com/go/dbdefaults', NULL + -- FROM sys.all_columns + -- WHERE name = 'target_recovery_time_in_seconds' AND object_id = OBJECT_ID('sys.databases'); INSERT INTO #DatabaseDefaults SELECT 'delayed_durability', 0, 143, 210, 'Delayed Durability Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL FROM sys.all_columns @@ -4359,6 +4359,79 @@ AS CLOSE DatabaseDefaultsLoop; DEALLOCATE DatabaseDefaultsLoop; + +/* Check if target recovery interval <> 60 */ +IF + @ProductVersionMajor >= 10 + AND NOT EXISTS + ( + SELECT + 1/0 + FROM #SkipChecks AS sc + WHERE sc.DatabaseName IS NULL + AND sc.CheckID = 257 + ) + BEGIN + IF EXISTS + ( + SELECT + 1/0 + FROM sys.all_columns AS ac + WHERE ac.name = 'target_recovery_time_in_seconds' + AND ac.object_id = OBJECT_ID('sys.databases') + ) + BEGIN + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 257) WITH NOWAIT; + + DECLARE + @tri nvarchar(max) = N' + SELECT + DatabaseName = + d.name, + CheckId = + 257, + Priority = + 50, + FindingsGroup = + N''Performance'', + Finding = + N''Recovery Interval Not Optimal'', + URL = + N''https://sqlperformance.com/2020/05/system-configuration/0-to-60-switching-to-indirect-checkpoints'', + Details = + N''The database '' + + QUOTENAME(d.name) + + N'' has a target recovery interval of '' + + RTRIM(d.target_recovery_time_in_seconds) + + CASE + WHEN d.target_recovery_time_in_seconds = 0 + THEN N'', which is a legacy default, and should be changed to 60.'' + WHEN d.target_recovery_time_in_seconds <> 0 + THEN N'', which is probably a mistake, and should be changed to 60.'' + END + FROM sys.databases AS d + WHERE d.database_id > 4 + AND d.is_read_only = 0 + AND d.is_in_standby = 0 + AND d.target_recovery_time_in_seconds <> 60; + '; + + INSERT INTO + #BlitzResults + ( + DatabaseName, + CheckID, + Priority, + FindingsGroup, + Finding, + URL, + Details + ) + EXEC sys.sp_executesql + @tri; + + END; + END; /*This checks to see if Agent is Offline*/ @@ -6085,7 +6158,7 @@ IF @ProductVersionMajor >= 10 END; - IF @ProductVersionMajor >= 13 AND @ProductVersionMinor < 2149 --CU1 has the fix in it + IF @ProductVersionMajor = 13 AND @ProductVersionMinor < 2149 --2016 CU1 has the fix in it AND NOT EXISTS ( SELECT 1 FROM #SkipChecks WHERE DatabaseName IS NULL AND CheckID = 182 ) @@ -8180,6 +8253,7 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 WHERE o.name = 'dm_server_services' AND c.name = 'instant_file_initialization_enabled' ) begin + SET @StringToExecute = N' INSERT INTO #BlitzResults ( CheckID , [Priority] , @@ -8191,14 +8265,15 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 SELECT 193 AS [CheckID] , 250 AS [Priority] , - 'Server Info' AS [FindingsGroup] , - 'Instant File Initialization Enabled' AS [Finding] , - 'https://www.brentozar.com/go/instant' AS [URL] , - 'The service account has the Perform Volume Maintenance Tasks permission.' + ''Server Info'' AS [FindingsGroup] , + ''Instant File Initialization Enabled'' AS [Finding] , + ''https://www.brentozar.com/go/instant'' AS [URL] , + ''The service account has the Perform Volume Maintenance Tasks permission.'' where exists (select 1 FROM sys.dm_server_services - WHERE instant_file_initialization_enabled = 'Y' - AND filename LIKE '%sqlservr.exe%') - OPTION (RECOMPILE); + WHERE instant_file_initialization_enabled = ''Y'' + AND filename LIKE ''%sqlservr.exe%'') + OPTION (RECOMPILE);'; + EXEC(@StringToExecute); end; end; END; @@ -9027,7 +9102,7 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 IF (OBJECT_ID('tempdb..##BlitzResults', 'U') IS NOT NULL) DROP TABLE ##BlitzResults; SELECT * INTO ##BlitzResults FROM #BlitzResults; SET @query_result_separator = char(9); - SET @StringToExecute = 'SET NOCOUNT ON;SELECT [Priority] , [FindingsGroup] , [Finding] , [DatabaseName] , [URL] , [Details] , CheckID FROM ##BlitzResults ORDER BY Priority , FindingsGroup, Finding, Details; SET NOCOUNT OFF;'; + SET @StringToExecute = 'SET NOCOUNT ON;SELECT [Priority] , [FindingsGroup] , [Finding] , [DatabaseName] , [URL] , [Details] , CheckID FROM ##BlitzResults ORDER BY Priority , FindingsGroup , Finding , DatabaseName , Details; SET NOCOUNT OFF;'; SET @EmailSubject = 'sp_Blitz Results for ' + @@SERVERNAME; SET @EmailBody = 'sp_Blitz ' + CAST(CONVERT(DATETIME, @VersionDate, 102) AS VARCHAR(100)) + '. http://FirstResponderKit.org'; IF @EmailProfile IS NULL @@ -9172,7 +9247,7 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 + @OutputTableName + ' (ServerName, CheckDate, CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details, QueryPlan, QueryPlanFiltered) SELECT ''' + CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128)) - + ''', SYSDATETIMEOFFSET(), CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details, CAST(QueryPlan AS NVARCHAR(MAX)), QueryPlanFiltered FROM #BlitzResults ORDER BY Priority , FindingsGroup , Finding , Details'; + + ''', SYSDATETIMEOFFSET(), CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details, CAST(QueryPlan AS NVARCHAR(MAX)), QueryPlanFiltered FROM #BlitzResults ORDER BY Priority , FindingsGroup , Finding , DatabaseName , Details'; EXEC(@StringToExecute); END; @@ -9189,7 +9264,7 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 + @OutputTableName + ' (ServerName, CheckDate, CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details, QueryPlan, QueryPlanFiltered) SELECT ''' + CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128)) - + ''', SYSDATETIMEOFFSET(), CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details, CAST(QueryPlan AS NVARCHAR(MAX)), QueryPlanFiltered FROM #BlitzResults ORDER BY Priority , FindingsGroup , Finding , Details'; + + ''', SYSDATETIMEOFFSET(), CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details, CAST(QueryPlan AS NVARCHAR(MAX)), QueryPlanFiltered FROM #BlitzResults ORDER BY Priority , FindingsGroup , Finding , DatabaseName , Details'; END; ELSE begin @@ -9202,7 +9277,7 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 + @OutputTableName + ' (ServerName, CheckDate, CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details, QueryPlan, QueryPlanFiltered) SELECT ''' + CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128)) - + ''', SYSDATETIMEOFFSET(), CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details, QueryPlan, QueryPlanFiltered FROM #BlitzResults ORDER BY Priority , FindingsGroup , Finding , Details'; + + ''', SYSDATETIMEOFFSET(), CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details, QueryPlan, QueryPlanFiltered FROM #BlitzResults ORDER BY Priority , FindingsGroup , Finding , DatabaseName , Details'; END; EXEC(@StringToExecute); @@ -9238,7 +9313,7 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 + @OutputTableName + ' (ServerName, CheckDate, CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details, QueryPlan, QueryPlanFiltered) SELECT ''' + CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128)) - + ''', SYSDATETIMEOFFSET(), CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details, QueryPlan, QueryPlanFiltered FROM #BlitzResults ORDER BY Priority , FindingsGroup , Finding , Details'; + + ''', SYSDATETIMEOFFSET(), CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details, QueryPlan, QueryPlanFiltered FROM #BlitzResults ORDER BY Priority , FindingsGroup , Finding , DatabaseName , Details'; EXEC(@StringToExecute); END; @@ -9479,7 +9554,7 @@ AS SET NOCOUNT ON; SET STATISTICS XML OFF; -SELECT @Version = '8.04', @VersionDate = '20210530'; +SELECT @Version = '8.05', @VersionDate = '20210725'; IF(@VersionCheckMode = 1) BEGIN @@ -10357,7 +10432,7 @@ AS SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.04', @VersionDate = '20210530'; + SELECT @Version = '8.05', @VersionDate = '20210725'; IF(@VersionCheckMode = 1) BEGIN @@ -12138,7 +12213,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.04', @VersionDate = '20210530'; +SELECT @Version = '8.05', @VersionDate = '20210725'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -13423,9 +13498,9 @@ database_id INT CREATE TABLE #plan_usage ( duplicate_plan_hashes BIGINT NULL, - percent_duplicate DECIMAL(5, 2) NULL, + percent_duplicate DECIMAL(9, 2) NULL, single_use_plan_count BIGINT NULL, - percent_single DECIMAL(5, 2) NULL, + percent_single DECIMAL(9, 2) NULL, total_plans BIGINT NULL, spid INT ); @@ -19398,7 +19473,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.04', @VersionDate = '20210530'; +SELECT @Version = '8.05', @VersionDate = '20210725'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -20309,9 +20384,17 @@ FROM #Ignore_Databases i; /* Last startup */ -SELECT @DaysUptime = CAST(DATEDIFF(HOUR, create_date, GETDATE()) / 24. AS NUMERIC (23,2)) -FROM sys.databases -WHERE database_id = 2; +IF COLUMNPROPERTY(OBJECT_ID('sys.dm_os_sys_info'),'sqlserver_start_time','ColumnID') IS NOT NULL +BEGIN + SELECT @DaysUptime = CAST(DATEDIFF(HOUR, sqlserver_start_time, GETDATE()) / 24. AS NUMERIC (23,2)) + FROM sys.dm_os_sys_info; +END +ELSE +BEGIN + SELECT @DaysUptime = CAST(DATEDIFF(HOUR, create_date, GETDATE()) / 24. AS NUMERIC (23,2)) + FROM sys.databases + WHERE database_id = 2; +END IF @DaysUptime = 0 OR @DaysUptime IS NULL SET @DaysUptime = .01; @@ -22067,17 +22150,18 @@ BEGIN /* Show histograms for all stats on this table. More info: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/1900 */ IF EXISTS (SELECT * FROM sys.all_objects WHERE name = 'dm_db_stats_histogram') BEGIN - SET @dsql=N'SELECT s.name AS [Stat Name], c.name AS [Leading Column Name], hist.step_number AS [Step Number], + SET @dsql = N'USE ' + QUOTENAME(@DatabaseName) + N'; + SELECT s.name AS [Stat Name], c.name AS [Leading Column Name], hist.step_number AS [Step Number], hist.range_high_key AS [Range High Key], hist.range_rows AS [Range Rows], hist.equal_rows AS [Equal Rows], hist.distinct_range_rows AS [Distinct Range Rows], hist.average_range_rows AS [Average Range Rows], s.auto_created AS [Auto-Created], s.user_created AS [User-Created], props.last_updated AS [Last Updated], props.modification_counter AS [Modification Counter], props.rows AS [Table Rows], props.rows_sampled AS [Rows Sampled], s.stats_id AS [StatsID] - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.stats AS s - INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.stats_columns sc ON s.object_id = sc.object_id AND s.stats_id = sc.stats_id AND sc.stats_column_id = 1 - INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns c ON sc.object_id = c.object_id AND sc.column_id = c.column_id - CROSS APPLY ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_stats_properties(s.object_id, s.stats_id) AS props - CROSS APPLY ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_stats_histogram(s.[object_id], s.stats_id) AS hist + FROM sys.stats AS s + INNER JOIN sys.stats_columns sc ON s.object_id = sc.object_id AND s.stats_id = sc.stats_id AND sc.stats_column_id = 1 + INNER JOIN sys.columns c ON sc.object_id = c.object_id AND sc.column_id = c.column_id + CROSS APPLY sys.dm_db_stats_properties(s.object_id, s.stats_id) AS props + CROSS APPLY sys.dm_db_stats_histogram(s.[object_id], s.stats_id) AS hist WHERE s.object_id = @ObjectID ORDER BY s.auto_created, s.user_created, s.name, hist.step_number;'; EXEC sp_executesql @dsql, N'@ObjectID INT', @ObjectID; @@ -23338,7 +23422,7 @@ BEGIN; i.index_sanity_id, 150 AS Priority, N'Index Hoarder' AS findings_group, - N'Non-Unique Clustered JIndex' AS finding, + N'Non-Unique Clustered Index' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/IndexHoarder' AS URL, N'Uniquifiers will be required! Clustered index: ' + i.db_schema_object_name @@ -24937,7 +25021,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.04', @VersionDate = '20210530'; +SELECT @Version = '8.05', @VersionDate = '20210725'; IF(@VersionCheckMode = 1) @@ -26744,7 +26828,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.04', @VersionDate = '20210530'; +SELECT @Version = '8.05', @VersionDate = '20210725'; IF(@VersionCheckMode = 1) BEGIN RETURN; @@ -32463,6 +32547,7 @@ ALTER PROCEDURE dbo.sp_BlitzWho @MinBlockingSeconds INT = 0 , @CheckDateOverride DATETIMEOFFSET = NULL, @ShowActualParameters BIT = 0, + @GetOuterCommand BIT = 0, @Version VARCHAR(30) = NULL OUTPUT, @VersionDate DATETIME = NULL OUTPUT, @VersionCheckMode BIT = 0, @@ -32473,7 +32558,7 @@ BEGIN SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.04', @VersionDate = '20210530'; + SELECT @Version = '8.05', @VersionDate = '20210725'; IF(@VersionCheckMode = 1) BEGIN @@ -32603,6 +32688,7 @@ IF @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @Output [session_id] [smallint] NOT NULL, [database_name] [nvarchar](128) NULL, [query_text] [nvarchar](max) NULL, + [outer_command] NVARCHAR(4000) NULL, [query_plan] [xml] NULL, [live_query_plan] [xml] NULL, [cached_parameter_info] [nvarchar](max) NULL, @@ -32719,6 +32805,13 @@ IF @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @Output ALTER TABLE ' + @ObjectFullName + N' ADD live_parameter_info NVARCHAR(MAX) NULL;'; EXEC(@StringToExecute); + /* If the table doesn't have the new outer_command column, add it. See Github #2887. */ + SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableName; + SET @StringToExecute = N'IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns + WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''outer_command'') + ALTER TABLE ' + @ObjectFullName + N' ADD outer_command NVARCHAR(4000) NULL;'; + EXEC(@StringToExecute); + /* Delete history older than @OutputTableRetentionDays */ SET @OutputTableCleanupDate = CAST( (DATEADD(DAY, -1 * @OutputTableRetentionDays, GETDATE() ) ) AS DATE); SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' @@ -33016,7 +33109,64 @@ SELECT @BlockingCheck = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; FROM sys.sysprocesses AS sys1 JOIN sys.sysprocesses AS sys2 ON sys1.spid = sys2.blocked; + '+CASE + WHEN (@GetOuterCommand = 1 AND (NOT EXISTS(SELECT 1 FROM sys.all_objects WHERE [name] = N'dm_exec_input_buffer'))) THEN N' + DECLARE @session_id SMALLINT; + DECLARE @Sessions TABLE + ( + session_id INT + ); + + DECLARE @inputbuffer TABLE + ( + ID INT IDENTITY(1,1), + session_id INT, + event_type NVARCHAR(30), + parameters SMALLINT, + event_info NVARCHAR(4000) + ); + DECLARE inputbuffer_cursor + + CURSOR LOCAL FAST_FORWARD + FOR + SELECT session_id + FROM sys.dm_exec_sessions + WHERE session_id <> @@SPID + AND is_user_process = 1; + + OPEN inputbuffer_cursor; + + FETCH NEXT FROM inputbuffer_cursor INTO @session_id; + + WHILE (@@FETCH_STATUS = 0) + BEGIN; + BEGIN TRY; + + INSERT INTO @inputbuffer ([event_type],[parameters],[event_info]) + EXEC sp_executesql + N''DBCC INPUTBUFFER(@session_id) WITH NO_INFOMSGS;'', + N''@session_id SMALLINT'', + @session_id; + + UPDATE @inputbuffer + SET session_id = @session_id + WHERE ID = SCOPE_IDENTITY(); + + END TRY + BEGIN CATCH + RAISERROR(''DBCC inputbuffer failed for session %d'',0,0,@session_id) WITH NOWAIT; + END CATCH; + + FETCH NEXT FROM inputbuffer_cursor INTO @session_id + + END; + + CLOSE inputbuffer_cursor; + DEALLOCATE inputbuffer_cursor;' + ELSE N'' + END+ + N' DECLARE @LiveQueryPlans TABLE ( @@ -33025,7 +33175,6 @@ SELECT @BlockingCheck = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; ); ' - IF EXISTS (SELECT * FROM sys.all_columns WHERE object_id = OBJECT_ID('sys.dm_exec_query_statistics_xml') AND name = 'query_plan') BEGIN SET @BlockingCheck = @BlockingCheck + N' @@ -33052,6 +33201,10 @@ BEGIN ELSE query_stats.statement_end_offset END - query_stats.statement_start_offset ) / 2 ) + 1), dest.text) AS query_text , + '+CASE + WHEN @GetOuterCommand = 1 THEN N'CAST(event_info AS NVARCHAR(4000)) AS outer_command,' + ELSE N'' + END+N' derp.query_plan , qmg.query_cost , s.status , @@ -33171,6 +33324,14 @@ BEGIN SET @StringToExecute += N'FROM sys.dm_exec_sessions AS s + '+ + CASE + WHEN @GetOuterCommand = 1 THEN CASE + WHEN EXISTS(SELECT 1 FROM sys.all_objects WHERE [name] = N'dm_exec_input_buffer') THEN N'OUTER APPLY sys.dm_exec_input_buffer (s.session_id, 0) AS ib' + ELSE N'LEFT JOIN @inputbuffer ib ON s.session_id = ib.session_id' + END + ELSE N'' + END+N' LEFT JOIN sys.dm_exec_requests AS r ON r.session_id = s.session_id LEFT JOIN ( SELECT DISTINCT @@ -33257,6 +33418,10 @@ IF @ProductVersionMajor >= 11 ELSE query_stats.statement_end_offset END - query_stats.statement_start_offset ) / 2 ) + 1), dest.text) AS query_text , + '+CASE + WHEN @GetOuterCommand = 1 THEN N'CAST(event_info AS NVARCHAR(4000)) AS outer_command,' + ELSE N'' + END+N' derp.query_plan , CAST(COALESCE(qs_live.Query_Plan, '''') AS XML) AS live_query_plan , STUFF((SELECT DISTINCT N'', '' + Node.Data.value(''(@Column)[1]'', ''NVARCHAR(4000)'') + N'' {'' + Node.Data.value(''(@ParameterDataType)[1]'', ''NVARCHAR(4000)'') + N''}: '' + Node.Data.value(''(@ParameterCompiledValue)[1]'', ''NVARCHAR(4000)'') @@ -33434,7 +33599,16 @@ IF @ProductVersionMajor >= 11 END /* IF @ExpertMode = 1 */ SET @StringToExecute += - N' FROM sys.dm_exec_sessions AS s + N' FROM sys.dm_exec_sessions AS s'+ + CASE + WHEN @GetOuterCommand = 1 THEN CASE + WHEN EXISTS(SELECT 1 FROM sys.all_objects WHERE [name] = N'dm_exec_input_buffer') THEN N' + OUTER APPLY sys.dm_exec_input_buffer (s.session_id, 0) AS ib' + ELSE N' + LEFT JOIN @inputbuffer ib ON s.session_id = ib.session_id' + END + ELSE N'' + END+N' LEFT JOIN sys.dm_exec_requests AS r ON r.session_id = s.session_id LEFT JOIN ( SELECT DISTINCT @@ -33588,7 +33762,8 @@ IF @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @Output ,[elapsed_time] ,[session_id] ,[database_name] - ,[query_text] + ,[query_text]' + + CASE WHEN @GetOuterCommand = 1 THEN N',[outer_command]' ELSE N'' END + N' ,[query_plan]' + CASE WHEN @ProductVersionMajor >= 11 THEN N',[live_query_plan]' ELSE N'' END + CASE WHEN @ProductVersionMajor >= 11 THEN N',[cached_parameter_info]' ELSE N'' END @@ -33759,6 +33934,7 @@ DELETE FROM dbo.SqlServerVersions; INSERT INTO dbo.SqlServerVersions (MajorVersionNumber, MinorVersionNumber, Branch, [Url], ReleaseDate, MainstreamSupportEndDate, ExtendedSupportEndDate, MajorVersionName, MinorVersionName) VALUES + (15, 4138, 'CU11', 'https://support.microsoft.com/en-us/help/5003249', '2021-06-10', '2025-01-01', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 11'), (15, 4123, 'CU10', 'https://support.microsoft.com/en-us/help/5001090', '2021-04-06', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 10'), (15, 4102, 'CU9', 'https://support.microsoft.com/en-us/help/5000642', '2021-02-11', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 9 '), (15, 4073, 'CU8 GDR', 'https://support.microsoft.com/en-us/help/4583459', '2021-01-12', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 8 GDR '), @@ -33772,6 +33948,7 @@ VALUES (15, 4003, 'CU1', 'https://support.microsoft.com/en-us/help/4527376', '2020-01-07', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 1 '), (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 '), + (14, 3410, 'RTM CU25', 'https://support.microsoft.com/en-us/help/5003830', '2021-07-12', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 25'), (14, 3391, 'RTM CU24', 'https://support.microsoft.com/en-us/help/5001228', '2021-05-10', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 24'), (14, 3381, 'RTM CU23', 'https://support.microsoft.com/en-us/help/5000685', '2021-02-25', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 23'), (14, 3370, 'RTM CU22 GDR', 'https://support.microsoft.com/en-us/help/4583457', '2021-01-12', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 22 GDR'), @@ -34151,7 +34328,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.04', @VersionDate = '20210530'; +SELECT @Version = '8.05', @VersionDate = '20210725'; IF(@VersionCheckMode = 1) BEGIN @@ -34244,7 +34421,10 @@ DECLARE @StringToExecute NVARCHAR(MAX), @UnquotedOutputDatabaseName NVARCHAR(256) = @OutputDatabaseName , @UnquotedOutputSchemaName NVARCHAR(256) = @OutputSchemaName , @LocalServerName NVARCHAR(128) = CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128)), - @dm_exec_query_statistics_xml BIT = 0; + @dm_exec_query_statistics_xml BIT = 0, + @total_cpu_usage BIT = 0, + @get_thread_time_ms NVARCHAR(MAX) = N'', + @thread_time_ms FLOAT = 0; /* Sanitize our inputs */ SELECT @@ -34398,6 +34578,54 @@ BEGIN @FinishSampleTimeWaitFor = DATEADD(ss, @Seconds, GETDATE()); + IF EXISTS + ( + + SELECT + 1/0 + FROM sys.all_columns AS ac + WHERE ac.object_id = OBJECT_ID('sys.dm_os_schedulers') + AND ac.name = 'total_cpu_usage_ms' + + ) + BEGIN + + SELECT + @total_cpu_usage = 1, + @get_thread_time_ms += + N' + SELECT + @thread_time_ms = + CONVERT + ( + FLOAT, + SUM(s.total_cpu_usage_ms) + ) + FROM sys.dm_os_schedulers AS s + WHERE s.status = ''VISIBLE ONLINE'' + AND s.is_online = 1 + OPTION(RECOMPILE); + '; + + END + ELSE + BEGIN + SELECT + @total_cpu_usage = 0, + @get_thread_time_ms += + N' + SELECT + @thread_time_ms = + CONVERT + ( + FLOAT, + SUM(s.total_worker_time / 1000.) + ) + FROM sys.dm_exec_query_stats AS s + OPTION(RECOMPILE); + '; + END + RAISERROR('Now starting diagnostic analysis',10,1) WITH NOWAIT; /* @@ -34447,7 +34675,15 @@ BEGIN IF OBJECT_ID('tempdb..#WaitStats') IS NOT NULL DROP TABLE #WaitStats; - CREATE TABLE #WaitStats (Pass TINYINT NOT NULL, wait_type NVARCHAR(60), wait_time_ms BIGINT, signal_wait_time_ms BIGINT, waiting_tasks_count BIGINT, SampleTime DATETIMEOFFSET); + CREATE TABLE #WaitStats ( + Pass TINYINT NOT NULL, + wait_type NVARCHAR(60), + wait_time_ms BIGINT, + thread_time_ms FLOAT, + signal_wait_time_ms BIGINT, + waiting_tasks_count BIGINT, + SampleTime datetimeoffset + ); IF OBJECT_ID('tempdb..#FileStats') IS NOT NULL DROP TABLE #FileStats; @@ -35398,16 +35634,30 @@ BEGIN INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP Transactions','Transactions created/sec',NULL); END; + + IF @total_cpu_usage IN (0, 1) + BEGIN + EXEC sys.sp_executesql + @get_thread_time_ms, + N'@thread_time_ms FLOAT OUTPUT', + @thread_time_ms OUTPUT; + END + /* Populate #FileStats, #PerfmonStats, #WaitStats with DMV data. After we finish doing our checks, we'll take another sample and compare them. */ RAISERROR('Capturing first pass of wait stats, perfmon counters, file stats',10,1) WITH NOWAIT; SET @StringToExecute = N' - INSERT #WaitStats(Pass, SampleTime, wait_type, wait_time_ms, signal_wait_time_ms, waiting_tasks_count) + INSERT #WaitStats(Pass, SampleTime, wait_type, wait_time_ms, thread_time_ms, signal_wait_time_ms, waiting_tasks_count) SELECT x.Pass, x.SampleTime, x.wait_type, SUM(x.sum_wait_time_ms) AS sum_wait_time_ms, + CASE @Seconds + WHEN 0 + THEN 0 + ELSE @thread_time_ms + END AS thread_time_ms, SUM(x.sum_signal_wait_time_ms) AS sum_signal_wait_time_ms, SUM(x.sum_waiting_tasks) AS sum_waiting_tasks FROM ( @@ -35447,9 +35697,35 @@ BEGIN ) GROUP BY x.Pass, x.SampleTime, x.wait_type ORDER BY sum_wait_time_ms DESC;' - EXEC sp_executesql @StringToExecute, N'@StartSampleTime DATETIMEOFFSET, @Seconds INT', @StartSampleTime, @Seconds; - - + + EXEC sys.sp_executesql + @StringToExecute, + N'@StartSampleTime DATETIMEOFFSET, + @Seconds INT, + @thread_time_ms FLOAT', + @StartSampleTime, + @Seconds, + @thread_time_ms; + + WITH w AS + ( + SELECT + total_waits = + CONVERT + ( + FLOAT, + SUM(ws.wait_time_ms) + ) + FROM #WaitStats AS ws + WHERE Pass = 1 + ) + UPDATE ws + SET ws.thread_time_ms += w.total_waits + FROM #WaitStats AS ws + CROSS JOIN w + WHERE ws.Pass = 1 + OPTION(RECOMPILE); + INSERT INTO #FileStats (Pass, SampleTime, DatabaseID, FileID, DatabaseName, FileLogicalName, SizeOnDiskMB, io_stall_read_ms , num_of_reads, [bytes_read] , io_stall_write_ms,num_of_writes, [bytes_written], PhysicalName, TypeDesc) SELECT @@ -35544,7 +35820,7 @@ BEGIN WHERE resource_type = N'DATABASE' AND request_mode = N'S' AND request_status = N'GRANT' - AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.session_id = db.request_session_id + AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.session_id = db.request_session_id AND s.database_id = db.resource_database_id CROSS APPLY sys.dm_exec_query_plan(r.plan_handle) pl WHERE r.command LIKE 'BACKUP%' AND r.start_time <= DATEADD(minute, -5, GETDATE()) @@ -35771,9 +36047,8 @@ BEGIN db.[resource_database_id] AS DatabaseID, DB_NAME(db.resource_database_id) AS DatabaseName, (SELECT TOP 1 [text] FROM sys.dm_exec_sql_text(c.most_recent_sql_handle)) AS QueryText, - sessions_with_transactions.open_transaction_count AS OpenTransactionCount - FROM (SELECT session_id, SUM(open_transaction_count) AS open_transaction_count FROM sys.dm_exec_requests WHERE open_transaction_count > 0 GROUP BY session_id) AS sessions_with_transactions - INNER JOIN sys.dm_exec_sessions s ON sessions_with_transactions.session_id = s.session_id + s.open_transaction_count AS OpenTransactionCount + FROM sys.dm_exec_sessions s INNER JOIN sys.dm_exec_connections c ON s.session_id = c.session_id INNER JOIN ( SELECT DISTINCT request_session_id, resource_database_id @@ -35783,6 +36058,7 @@ BEGIN AND request_status = N'GRANT' AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.session_id = db.request_session_id WHERE s.status = 'sleeping' + AND s.open_transaction_count > 0 AND s.last_request_end_time < DATEADD(ss, -10, SYSDATETIME()) AND EXISTS(SELECT * FROM sys.dm_tran_locks WHERE request_session_id = s.session_id AND NOT (resource_type = N'DATABASE' AND request_mode = N'S' AND request_status = N'GRANT' AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE')); @@ -36555,15 +36831,24 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, WAITFOR TIME @FinishSampleTimeWaitFor; END; + IF @total_cpu_usage IN (0, 1) + BEGIN + EXEC sys.sp_executesql + @get_thread_time_ms, + N'@thread_time_ms FLOAT OUTPUT', + @thread_time_ms OUTPUT; + END + RAISERROR('Capturing second pass of wait stats, perfmon counters, file stats',10,1) WITH NOWAIT; /* Populate #FileStats, #PerfmonStats, #WaitStats with DMV data. In a second, we'll compare these. */ SET @StringToExecute = N' - INSERT #WaitStats(Pass, SampleTime, wait_type, wait_time_ms, signal_wait_time_ms, waiting_tasks_count) + INSERT #WaitStats(Pass, SampleTime, wait_type, wait_time_ms, thread_time_ms, signal_wait_time_ms, waiting_tasks_count) SELECT x.Pass, x.SampleTime, x.wait_type, SUM(x.sum_wait_time_ms) AS sum_wait_time_ms, + @thread_time_ms AS thread_time_ms, SUM(x.sum_signal_wait_time_ms) AS sum_signal_wait_time_ms, SUM(x.sum_waiting_tasks) AS sum_waiting_tasks FROM ( @@ -36603,7 +36888,35 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, ) GROUP BY x.Pass, x.SampleTime, x.wait_type ORDER BY sum_wait_time_ms DESC;'; - EXEC sp_executesql @StringToExecute, N'@Seconds INT', @Seconds; + + EXEC sys.sp_executesql + @StringToExecute, + N'@StartSampleTime DATETIMEOFFSET, + @Seconds INT, + @thread_time_ms FLOAT', + @StartSampleTime, + @Seconds, + @thread_time_ms; + + WITH w AS + ( + SELECT + total_waits = + CONVERT + ( + FLOAT, + SUM(ws.wait_time_ms) + ) + FROM #WaitStats AS ws + WHERE Pass = 2 + ) + UPDATE ws + SET ws.thread_time_ms += w.total_waits + FROM #WaitStats AS ws + CROSS JOIN w + WHERE ws.Pass = 2 + OPTION(RECOMPILE); + INSERT INTO #FileStats (Pass, SampleTime, DatabaseID, FileID, DatabaseName, FileLogicalName, SizeOnDiskMB, io_stall_read_ms , num_of_reads, [bytes_read] , io_stall_write_ms,num_of_writes, [bytes_written], PhysicalName, TypeDesc, avg_stall_read_ms, avg_stall_write_ms) @@ -37401,6 +37714,36 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, END; /* IF @Seconds >= 30 */ + IF /* Let people on <2016 know about the thread time column */ + ( + @Seconds > 0 + AND @total_cpu_usage = 0 + ) + BEGIN + INSERT INTO + #BlitzFirstResults + ( + CheckID, + Priority, + FindingsGroup, + Finding, + Details, + URL + ) + SELECT + 48, + 254, + N'Informational', + N'Thread Time comes from the plan cache in versions earlier than 2016, and is not as reliable', + N'The oldest plan in your cache is from ' + + CONVERT(nvarchar(30), MIN(s.creation_time)) + + N' and your server was last restarted on ' + + CONVERT(nvarchar(30), MAX(o.sqlserver_start_time)), + N'https://docs.microsoft.com/en-us/sql/relational-databases/system-dynamic-management-views/sys-dm-os-schedulers-transact-sql' + FROM sys.dm_exec_query_stats AS s + CROSS JOIN sys.dm_os_sys_info AS o + OPTION(RECOMPILE); + END /* Let people on <2016 know about the thread time column */ /* If we didn't find anything, apologize. */ IF NOT EXISTS (SELECT * FROM #BlitzFirstResults WHERE Priority < 250) @@ -38510,10 +38853,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, ) SELECT TOP 10 CAST(DATEDIFF(mi,wd1.SampleTime, wd2.SampleTime) / 60.0 AS DECIMAL(18,1)) AS [Hours Sample], + CAST(c.[Total Thread Time (Seconds)] / 60. / 60. AS DECIMAL(18,1)) AS [Thread Time (Hours)], wd1.wait_type, COALESCE(wcat.WaitCategory, 'Other') AS wait_category, - CAST(c.[Wait Time (Seconds)] / 60.0 / 60 AS DECIMAL(18,1)) AS [Wait Time (Hours)], - CAST((wd2.wait_time_ms - wd1.wait_time_ms) / 1000.0 / cores.cpu_count / DATEDIFF(ss, wd1.SampleTime, wd2.SampleTime) AS DECIMAL(18,1)) AS [Per Core Per Hour], + CAST(c.[Wait Time (Seconds)] / 60. / 60. AS DECIMAL(18,1)) AS [Wait Time (Hours)], + CAST((wd2.wait_time_ms - wd1.wait_time_ms) / 1000.0 / cores.cpu_count / DATEDIFF(ss, wd1.SampleTime, wd2.SampleTime) AS DECIMAL(18,1)) AS [Per Core Per Hour], (wd2.waiting_tasks_count - wd1.waiting_tasks_count) AS [Number of Waits], CASE WHEN (wd2.waiting_tasks_count - wd1.waiting_tasks_count) > 0 THEN @@ -38528,8 +38872,10 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, wd2.SampleTime > wd1.SampleTime CROSS APPLY (SELECT SUM(1) AS cpu_count FROM sys.dm_os_schedulers WHERE status = 'VISIBLE ONLINE' AND is_online = 1) AS cores CROSS APPLY (SELECT - CAST((wd2.wait_time_ms-wd1.wait_time_ms)/1000. AS NUMERIC(12,1)) AS [Wait Time (Seconds)], - CAST((wd2.signal_wait_time_ms - wd1.signal_wait_time_ms)/1000. AS NUMERIC(12,1)) AS [Signal Wait Time (Seconds)]) AS c + CAST((wd2.wait_time_ms-wd1.wait_time_ms)/1000. AS DECIMAL(18,1)) AS [Wait Time (Seconds)], + CAST((wd2.signal_wait_time_ms - wd1.signal_wait_time_ms)/1000. AS DECIMAL(18,1)) AS [Signal Wait Time (Seconds)], + CAST((wd2.thread_time_ms)/1000. AS DECIMAL(18,1)) AS [Total Thread Time (Seconds)] + ) AS c LEFT OUTER JOIN ##WaitCategories wcat ON wd1.wait_type = wcat.WaitType WHERE (wd2.waiting_tasks_count - wd1.waiting_tasks_count) > 0 AND wd2.wait_time_ms-wd1.wait_time_ms > 0 @@ -38651,10 +38997,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, SELECT 'WAIT STATS' AS Pattern, b.SampleTime AS [Sample Ended], - CAST(DATEDIFF(mi,wd1.SampleTime, wd2.SampleTime) / 60.0 AS DECIMAL(18,1)) AS [Hours Sample], + CAST(DATEDIFF(mi,wd1.SampleTime, wd2.SampleTime) / 60. AS DECIMAL(18,1)) AS [Hours Sample], + CAST(c.[Total Thread Time (Seconds)] / 60. / 60. AS DECIMAL(18,1)) AS [Thread Time (Hours)], wd1.wait_type, COALESCE(wcat.WaitCategory, 'Other') AS wait_category, - CAST(c.[Wait Time (Seconds)] / 60.0 / 60 AS DECIMAL(18,1)) AS [Wait Time (Hours)], + CAST(c.[Wait Time (Seconds)] / 60. / 60. AS DECIMAL(18,1)) AS [Wait Time (Hours)], CAST((wd2.wait_time_ms - wd1.wait_time_ms) / 1000.0 / cores.cpu_count / DATEDIFF(ss, wd1.SampleTime, wd2.SampleTime) AS DECIMAL(18,1)) AS [Per Core Per Hour], CAST(c.[Signal Wait Time (Seconds)] / 60.0 / 60 AS DECIMAL(18,1)) AS [Signal Wait Time (Hours)], CASE WHEN c.[Wait Time (Seconds)] > 0 @@ -38675,8 +39022,10 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, wd2.SampleTime > wd1.SampleTime CROSS APPLY (SELECT SUM(1) AS cpu_count FROM sys.dm_os_schedulers WHERE status = 'VISIBLE ONLINE' AND is_online = 1) AS cores CROSS APPLY (SELECT - CAST((wd2.wait_time_ms-wd1.wait_time_ms)/1000. AS NUMERIC(12,1)) AS [Wait Time (Seconds)], - CAST((wd2.signal_wait_time_ms - wd1.signal_wait_time_ms)/1000. AS NUMERIC(12,1)) AS [Signal Wait Time (Seconds)]) AS c + CAST((wd2.wait_time_ms-wd1.wait_time_ms)/1000. AS DECIMAL(18,1)) AS [Wait Time (Seconds)], + CAST((wd2.signal_wait_time_ms - wd1.signal_wait_time_ms)/1000. AS DECIMAL(18,1)) AS [Signal Wait Time (Seconds)], + CAST((wd2.thread_time_ms)/1000. AS DECIMAL(18,1)) AS [Total Thread Time (Seconds)] + ) AS c LEFT OUTER JOIN ##WaitCategories wcat ON wd1.wait_type = wcat.WaitType WHERE (wd2.waiting_tasks_count - wd1.waiting_tasks_count) > 0 AND wd2.wait_time_ms-wd1.wait_time_ms > 0 @@ -38693,6 +39042,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 'WAIT STATS' AS Pattern, b.SampleTime AS [Sample Ended], DATEDIFF(ss,wd1.SampleTime, wd2.SampleTime) AS [Seconds Sample], + c.[Total Thread Time (Seconds)], wd1.wait_type, COALESCE(wcat.WaitCategory, 'Other') AS wait_category, c.[Wait Time (Seconds)], @@ -38716,8 +39066,10 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, wd2.SampleTime > wd1.SampleTime CROSS APPLY (SELECT SUM(1) AS cpu_count FROM sys.dm_os_schedulers WHERE status = 'VISIBLE ONLINE' AND is_online = 1) AS cores CROSS APPLY (SELECT - CAST((wd2.wait_time_ms-wd1.wait_time_ms)/1000. AS NUMERIC(12,1)) AS [Wait Time (Seconds)], - CAST((wd2.signal_wait_time_ms - wd1.signal_wait_time_ms)/1000. AS NUMERIC(12,1)) AS [Signal Wait Time (Seconds)]) AS c + CAST((wd2.wait_time_ms-wd1.wait_time_ms)/1000. AS DECIMAL(18,1)) AS [Wait Time (Seconds)], + CAST((wd2.signal_wait_time_ms - wd1.signal_wait_time_ms)/1000. AS DECIMAL(18,1)) AS [Signal Wait Time (Seconds)], + CAST((wd2.thread_time_ms - wd1.thread_time_ms)/1000. AS DECIMAL(18,1)) AS [Total Thread Time (Seconds)] + ) AS c LEFT OUTER JOIN ##WaitCategories wcat ON wd1.wait_type = wcat.WaitType WHERE (wd2.waiting_tasks_count - wd1.waiting_tasks_count) > 0 AND wd2.wait_time_ms-wd1.wait_time_ms > 0 diff --git a/sp_AllNightLog.sql b/sp_AllNightLog.sql index 2252ab61c..98f7e4156 100644 --- a/sp_AllNightLog.sql +++ b/sp_AllNightLog.sql @@ -31,7 +31,7 @@ SET STATISTICS XML OFF; BEGIN; -SELECT @Version = '8.04', @VersionDate = '20210530'; +SELECT @Version = '8.05', @VersionDate = '20210725'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_AllNightLog_Setup.sql b/sp_AllNightLog_Setup.sql index a1c85022d..e309d8931 100644 --- a/sp_AllNightLog_Setup.sql +++ b/sp_AllNightLog_Setup.sql @@ -37,7 +37,7 @@ SET STATISTICS XML OFF; BEGIN; -SELECT @Version = '8.04', @VersionDate = '20210530'; +SELECT @Version = '8.05', @VersionDate = '20210725'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_Blitz.sql b/sp_Blitz.sql index a84350a2f..a978485df 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -38,7 +38,7 @@ AS SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.04', @VersionDate = '20210530'; + SELECT @Version = '8.05', @VersionDate = '20210725'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) diff --git a/sp_BlitzAnalysis.sql b/sp_BlitzAnalysis.sql index 22fbb3020..7f22ce5f0 100644 --- a/sp_BlitzAnalysis.sql +++ b/sp_BlitzAnalysis.sql @@ -37,7 +37,7 @@ AS SET NOCOUNT ON; SET STATISTICS XML OFF; -SELECT @Version = '8.04', @VersionDate = '20210530'; +SELECT @Version = '8.05', @VersionDate = '20210725'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_BlitzBackups.sql b/sp_BlitzBackups.sql index a18bf533b..036a89d9c 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.04', @VersionDate = '20210530'; + SELECT @Version = '8.05', @VersionDate = '20210725'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_BlitzCache.sql b/sp_BlitzCache.sql index bde971165..1a9d7e92a 100644 --- a/sp_BlitzCache.sql +++ b/sp_BlitzCache.sql @@ -280,7 +280,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.04', @VersionDate = '20210530'; +SELECT @Version = '8.05', @VersionDate = '20210725'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) diff --git a/sp_BlitzFirst.sql b/sp_BlitzFirst.sql index fb23b66e7..aea3da81e 100644 --- a/sp_BlitzFirst.sql +++ b/sp_BlitzFirst.sql @@ -46,7 +46,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.04', @VersionDate = '20210530'; +SELECT @Version = '8.05', @VersionDate = '20210725'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_BlitzInMemoryOLTP.sql b/sp_BlitzInMemoryOLTP.sql index 728ff7b53..d0e538859 100644 --- a/sp_BlitzInMemoryOLTP.sql +++ b/sp_BlitzInMemoryOLTP.sql @@ -82,7 +82,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI */ AS DECLARE @ScriptVersion VARCHAR(30); -SELECT @ScriptVersion = '1.8', @VersionDate = '20210530'; +SELECT @ScriptVersion = '1.8', @VersionDate = '20210725'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index e8cb35c05..7dde59708 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -48,7 +48,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.04', @VersionDate = '20210530'; +SELECT @Version = '8.05', @VersionDate = '20210725'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index 8b5a235e2..41bee1532 100644 --- a/sp_BlitzLock.sql +++ b/sp_BlitzLock.sql @@ -33,7 +33,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.04', @VersionDate = '20210530'; +SELECT @Version = '8.05', @VersionDate = '20210725'; IF(@VersionCheckMode = 1) diff --git a/sp_BlitzQueryStore.sql b/sp_BlitzQueryStore.sql index 7dd310749..3b414f7e1 100644 --- a/sp_BlitzQueryStore.sql +++ b/sp_BlitzQueryStore.sql @@ -57,7 +57,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.04', @VersionDate = '20210530'; +SELECT @Version = '8.05', @VersionDate = '20210725'; IF(@VersionCheckMode = 1) BEGIN RETURN; diff --git a/sp_BlitzWho.sql b/sp_BlitzWho.sql index 5ccbe1fd2..64d021b7e 100644 --- a/sp_BlitzWho.sql +++ b/sp_BlitzWho.sql @@ -32,7 +32,7 @@ BEGIN SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.04', @VersionDate = '20210530'; + SELECT @Version = '8.05', @VersionDate = '20210725'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_DatabaseRestore.sql b/sp_DatabaseRestore.sql index 6f765c00b..ff6c5ff36 100755 --- a/sp_DatabaseRestore.sql +++ b/sp_DatabaseRestore.sql @@ -41,7 +41,7 @@ SET STATISTICS XML OFF; /*Versioning details*/ -SELECT @Version = '8.04', @VersionDate = '20210530'; +SELECT @Version = '8.05', @VersionDate = '20210725'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_ineachdb.sql b/sp_ineachdb.sql index 98b459027..4d1bb9634 100644 --- a/sp_ineachdb.sql +++ b/sp_ineachdb.sql @@ -35,7 +35,7 @@ BEGIN SET NOCOUNT ON; SET STATISTICS XML OFF; - SELECT @Version = '8.04', @VersionDate = '20210530'; + SELECT @Version = '8.05', @VersionDate = '20210725'; IF(@VersionCheckMode = 1) BEGIN From 6319a562342334047c4f0b3fff7948b4963c7b61 Mon Sep 17 00:00:00 2001 From: Erik Darling Date: Sun, 1 Aug 2021 12:28:23 -0400 Subject: [PATCH 223/662] Add unique constraint awareness This adds `is_unique_constraint` from `sys.indexes` to a couple of the operating modes. Note that this also adds the new column etc. to the remote server code for Mode 2 as well, and should be announced as a breaking change (of removed, whatever). --- sp_BlitzIndex.sql | 37 ++++++++++++++++++++++++++++++------- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index 7dde59708..d679260e2 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -263,7 +263,8 @@ IF OBJECT_ID('tempdb..#Ignore_Databases') IS NOT NULL is_indexed_view BIT NOT NULL , is_unique BIT NOT NULL , is_primary_key BIT NOT NULL , - is_XML BIT NOT NULL, + is_unique_constraint BIT NOT NULL , + is_XML bit NOT NULL, is_spatial BIT NOT NULL, is_NC_columnstore BIT NOT NULL, is_CX_columnstore BIT NOT NULL, @@ -311,7 +312,8 @@ IF OBJECT_ID('tempdb..#Ignore_Databases') IS NOT NULL ELSE N'' END + CASE WHEN is_in_memory_oltp = 1 THEN N'[IN-MEMORY] ' ELSE N'' END + CASE WHEN is_disabled = 1 THEN N'[DISABLED] ' ELSE N'' END + CASE WHEN is_hypothetical = 1 THEN N'[HYPOTHETICAL] ' - ELSE N'' END + CASE WHEN is_unique = 1 AND is_primary_key = 0 THEN N'[UNIQUE] ' + ELSE N'' END + CASE WHEN is_unique = 1 AND is_primary_key = 0 AND is_unique_constraint = 0 THEN N'[UNIQUE] ' + ELSE N'' END + CASE WHEN is_unique_constraint = 1 AND is_primary_key = 0 THEN N'[UNIQUE CONSTRAINT] ' ELSE N'' END + CASE WHEN count_key_columns > 0 THEN N'[' + CAST(count_key_columns AS NVARCHAR(10)) + N' KEY' + CASE WHEN count_key_columns > 1 THEN N'S' ELSE N'' END @@ -1275,7 +1277,8 @@ BEGIN TRY CASE WHEN so.[type] = CAST(''V'' AS CHAR(2)) THEN 1 ELSE 0 END, si.is_unique, si.is_primary_key, - CASE when si.type = 3 THEN 1 ELSE 0 END AS is_XML, + si.is_unique_constraint, + CASE when si.type = 3 THEN 1 ELSE 0 END AS is_XML, CASE when si.type = 4 THEN 1 ELSE 0 END AS is_spatial, CASE when si.type = 6 THEN 1 ELSE 0 END AS is_NC_columnstore, CASE when si.type = 5 then 1 else 0 end as is_CX_columnstore, @@ -1335,7 +1338,7 @@ BEGIN TRY PRINT SUBSTRING(@dsql, 36000, 40000); END; INSERT #IndexSanity ( [database_id], [object_id], [index_id], [index_type], [database_name], [schema_name], [object_name], - index_name, is_indexed_view, is_unique, is_primary_key, is_XML, is_spatial, is_NC_columnstore, is_CX_columnstore, is_in_memory_oltp, + index_name, is_indexed_view, is_unique, is_primary_key, is_unique_constraint, is_XML, is_spatial, is_NC_columnstore, is_CX_columnstore, is_in_memory_oltp, is_disabled, is_hypothetical, is_padded, fill_factor, filter_definition, user_seeks, user_scans, user_lookups, user_updates, last_user_seek, last_user_scan, last_user_lookup, last_user_update, create_date, modify_date ) @@ -2404,7 +2407,14 @@ SELECT N'] PRIMARY KEY ' + CASE WHEN index_id=1 THEN N'CLUSTERED (' ELSE N'(' END + key_column_names_with_sort_order_no_types + N' )' - WHEN is_CX_columnstore= 1 THEN + WHEN is_unique_constraint = 1 AND is_primary_key = 0 + THEN + N'ALTER TABLE ' + QUOTENAME([database_name]) + N'.' + QUOTENAME([schema_name]) + + N'.' + QUOTENAME([object_name]) + + N' ADD CONSTRAINT [' + + index_name + + N'] UNIQUE;' + WHEN is_CX_columnstore= 1 THEN N'CREATE CLUSTERED COLUMNSTORE INDEX ' + QUOTENAME(index_name) + N' on ' + QUOTENAME([database_name]) + N'.' + QUOTENAME([schema_name]) + N'.' + QUOTENAME([object_name]) ELSE /*Else not a PK or cx columnstore */ N'CREATE ' + @@ -2578,6 +2588,9 @@ BEGIN ct.create_tsql, CASE WHEN s.is_primary_key = 1 AND s.index_definition <> '[HEAP]' + THEN N'--ALTER TABLE ' + QUOTENAME(s.[database_name]) + N'.' + QUOTENAME(s.[schema_name]) + N'.' + QUOTENAME(s.[object_name]) + + N' DROP CONSTRAINT ' + QUOTENAME(s.index_name) + N';' + WHEN s.is_primary_key = 0 AND is_unique_constraint = 1 AND s.index_definition <> '[HEAP]' THEN N'--ALTER TABLE ' + QUOTENAME(s.[database_name]) + N'.' + QUOTENAME(s.[schema_name]) + N'.' + QUOTENAME(s.[object_name]) + N' DROP CONSTRAINT ' + QUOTENAME(s.index_name) + N';' WHEN s.is_primary_key = 0 AND s.index_definition <> '[HEAP]' @@ -5078,7 +5091,8 @@ ELSE IF (@Mode=1) /*Summarize*/ [partition_key_column_name] NVARCHAR(MAX), [filter_definition] NVARCHAR(MAX), [is_indexed_view] BIT, - [is_primary_key] BIT, + [is_primary_key] BIT, + [is_unique_constraint] BIT, [is_XML] BIT, [is_spatial] BIT, [is_NC_columnstore] BIT, @@ -5188,7 +5202,8 @@ ELSE IF (@Mode=1) /*Summarize*/ [partition_key_column_name], [filter_definition], [is_indexed_view], - [is_primary_key], + [is_primary_key], + [is_unique_constraint], [is_XML], [is_spatial], [is_NC_columnstore], @@ -5251,6 +5266,9 @@ ELSE IF (@Mode=1) /*Summarize*/ WHEN i.is_primary_key = 1 AND i.index_definition <> ''[HEAP]'' THEN N''--ALTER TABLE '' + QUOTENAME(i.[database_name]) + N''.'' + QUOTENAME(i.[schema_name]) + N''.'' + QUOTENAME(i.[object_name]) + N'' DROP CONSTRAINT '' + QUOTENAME(i.index_name) + N'';'' + WHEN i.is_primary_key = 0 AND i.is_unique_constraint = 1 AND i.index_definition <> ''[HEAP]'' + THEN N''--ALTER TABLE '' + QUOTENAME(i.[database_name]) + N''.'' + QUOTENAME(i.[schema_name]) + N''.'' + QUOTENAME(i.[object_name]) + + N'' DROP CONSTRAINT '' + QUOTENAME(i.index_name) + N'';'' WHEN i.is_primary_key = 0 AND i.index_definition <> ''[HEAP]'' THEN N''--DROP INDEX ''+ QUOTENAME(i.index_name) + N'' ON '' + QUOTENAME(i.[database_name]) + N''.'' + QUOTENAME(i.[schema_name]) + N''.'' + QUOTENAME(i.[object_name]) + N'';'' @@ -5275,6 +5293,7 @@ ELSE IF (@Mode=1) /*Summarize*/ ISNULL(filter_definition, '''') AS [Filter Definition], is_indexed_view AS [Is Indexed View], is_primary_key AS [Is Primary Key], + is_unique_constraint AS [Is Unique Constraint], is_XML AS [Is XML], is_spatial AS [Is Spatial], is_NC_columnstore AS [Is NC Columnstore], @@ -5368,6 +5387,7 @@ ELSE IF (@Mode=1) /*Summarize*/ ISNULL(filter_definition, '') AS [Filter Definition], is_indexed_view AS [Is Indexed View], is_primary_key AS [Is Primary Key], + is_unique_constraint AS [Is Unique Constraint] , is_XML AS [Is XML], is_spatial AS [Is Spatial], is_NC_columnstore AS [Is NC Columnstore], @@ -5420,6 +5440,9 @@ ELSE IF (@Mode=1) /*Summarize*/ WHEN i.is_primary_key = 1 AND i.index_definition <> '[HEAP]' THEN N'--ALTER TABLE ' + QUOTENAME(i.[database_name]) + N'.' + QUOTENAME(i.[schema_name]) + N'.' + QUOTENAME(i.[object_name]) + N' DROP CONSTRAINT ' + QUOTENAME(i.index_name) + N';' + WHEN i.is_primary_key = 0 AND i.is_unique_constraint = 1 AND i.index_definition <> '[HEAP]' + THEN N'--ALTER TABLE ' + QUOTENAME(i.[database_name]) + N'.' + QUOTENAME(i.[schema_name]) + N'.' + QUOTENAME(i.[object_name]) + + N' DROP CONSTRAINT ' + QUOTENAME(i.index_name) + N';' WHEN i.is_primary_key = 0 AND i.index_definition <> '[HEAP]' THEN N'--DROP INDEX '+ QUOTENAME(i.index_name) + N' ON ' + QUOTENAME(i.[database_name]) + N'.' + QUOTENAME(i.[schema_name]) + N'.' + QUOTENAME(i.[object_name]) + N';' From e322e04d4f3246856d45c2e9f55ac5a9677079ec Mon Sep 17 00:00:00 2001 From: Erik Darling Date: Sun, 1 Aug 2021 22:18:06 -0400 Subject: [PATCH 224/662] Add optimize for sequential key Purely informational. Nothing else to do here now. --- .../sp_BlitzIndex_Checks_by_Priority.md | 5 +- sp_BlitzIndex.sql | 50 +++++++++++++++++-- 2 files changed, 48 insertions(+), 7 deletions(-) diff --git a/Documentation/sp_BlitzIndex_Checks_by_Priority.md b/Documentation/sp_BlitzIndex_Checks_by_Priority.md index c949a1227..7cfa23738 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: 120 -If you want to add a new check, start at 121. +CURRENT HIGH CHECKID: 121 +If you want to add a new check, start at 122. | Priority | FindingsGroup | Finding | URL | CheckID | |----------|---------------------------------|---------------------------------------|-------------------------------------------------|----------| @@ -67,3 +67,4 @@ If you want to add a new check, start at 121. | 250 | Feature-Phobic Indexes | No Filtered Indexes or Indexed Views | https://www.brentozar.com/go/IndexFeatures | 32 | | 250 | Feature-Phobic Indexes | No Indexes Use Includes | https://www.brentozar.com/go/IndexFeatures | 30 | | 250 | Feature-Phobic Indexes | Potential Filtered Index (Based on Column Name) | https://www.brentozar.com/go/IndexFeatures | 33 | +| 250 | Medicated Indexes | Optimized For Sequential Keys | | 121 | diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index 7dde59708..20cafdb58 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -128,6 +128,7 @@ DECLARE @DaysUptimeInsertValue NVARCHAR(256); DECLARE @DatabaseToIgnore NVARCHAR(MAX); DECLARE @ColumnList NVARCHAR(MAX); DECLARE @PartitionCount INT; +DECLARE @OptimizeForSequentialKey BIT = 0; /* Let's get @SortOrder set to lower case here for comparisons later */ SET @SortOrder = REPLACE(LOWER(@SortOrder), N' ', N'_'); @@ -140,6 +141,20 @@ SET @FilterMB=250; SELECT @ScriptVersionName = 'sp_BlitzIndex(TM) v' + @Version + ' - ' + DATENAME(MM, @VersionDate) + ' ' + RIGHT('0'+DATENAME(DD, @VersionDate),2) + ', ' + DATENAME(YY, @VersionDate); SET @IgnoreDatabases = REPLACE(REPLACE(LTRIM(RTRIM(@IgnoreDatabases)), CHAR(10), ''), CHAR(13), ''); +SELECT + @OptimizeForSequentialKey = + CASE WHEN EXISTS + ( + SELECT + 1/0 + FROM sys.all_columns AS ac + WHERE ac.object_id = OBJECT_ID('sys.indexes') + AND ac.name = N'optimize_for_sequential_key' + ) + THEN 1 + ELSE 0 + END; + RAISERROR(N'Starting run. %s', 0,1, @ScriptVersionName) WITH NOWAIT; IF(@OutputType NOT IN ('TABLE','NONE')) @@ -260,7 +275,8 @@ IF OBJECT_ID('tempdb..#Ignore_Databases') IS NOT NULL count_included_columns INT NULL , partition_key_column_name NVARCHAR(MAX) NULL, filter_definition NVARCHAR(MAX) NOT NULL , - is_indexed_view BIT NOT NULL , + optimize_for_sequential_key BIT NULL, + is_indexed_view BIT NOT NULL , is_unique BIT NOT NULL , is_primary_key BIT NOT NULL , is_XML BIT NOT NULL, @@ -1287,8 +1303,14 @@ BEGIN TRY + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N' CASE WHEN si.filter_definition IS NOT NULL THEN si.filter_definition ELSE N'''' - END AS filter_definition' ELSE N''''' AS filter_definition' END + N' - , ISNULL(us.user_seeks, 0), + END AS filter_definition' ELSE N''''' AS filter_definition' END + + CASE + WHEN @OptimizeForSequentialKey = 1 + THEN N', si.optimize_for_sequential_key' + ELSE N', CONVERT(BIT, NULL) AS optimize_for_sequential_key' + END + + N', + ISNULL(us.user_seeks, 0), ISNULL(us.user_scans, 0), ISNULL(us.user_lookups, 0), ISNULL(us.user_updates, 0), @@ -1336,7 +1358,7 @@ BEGIN TRY END; INSERT #IndexSanity ( [database_id], [object_id], [index_id], [index_type], [database_name], [schema_name], [object_name], index_name, is_indexed_view, is_unique, is_primary_key, is_XML, is_spatial, is_NC_columnstore, is_CX_columnstore, is_in_memory_oltp, - is_disabled, is_hypothetical, is_padded, fill_factor, filter_definition, user_seeks, user_scans, + is_disabled, is_hypothetical, is_padded, fill_factor, filter_definition, optimize_for_sequential_key, user_seeks, user_scans, user_lookups, user_updates, last_user_seek, last_user_scan, last_user_lookup, last_user_update, create_date, modify_date ) EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; @@ -2512,6 +2534,7 @@ FROM #IndexSanity si IF @Debug = 1 BEGIN + SELECT '#BlitzIndexResults' AS table_name, * FROM #BlitzIndexResults AS bir; SELECT '#IndexSanity' AS table_name, * FROM #IndexSanity; SELECT '#IndexPartitionSanity' AS table_name, * FROM #IndexPartitionSanity; SELECT '#IndexSanitySize' AS table_name, * FROM #IndexSanitySize; @@ -4134,7 +4157,6 @@ BEGIN; OPTION ( RECOMPILE ); RAISERROR(N'check_id 33: Potential filtered indexes based on column names.', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, secret_columns, index_usage_summary, index_size_summary ) SELECT 33 AS check_id, @@ -4718,6 +4740,24 @@ BEGIN; FROM #TemporalTables AS t OPTION ( RECOMPILE ); + RAISERROR(N'check_id 121: Optimized For Sequental Keys.', 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 121 AS check_id, + 200 AS Priority, + 'Medicated Indexes' AS findings_group, + 'Optimized For Sequential Keys', + i.database_name, + '' AS URL, + 'The table ' + QUOTENAME(i.schema_name) + '.' + QUOTENAME(i.object_name) + ' is optimized for sequential keys.' AS details, + '' AS index_definition, + 'N/A' AS secret_columns, + 'N/A' AS index_usage_summary, + 'N/A' AS index_size_summary + FROM #IndexSanity AS i + WHERE i.optimize_for_sequential_key = 1 + OPTION ( RECOMPILE ); From 4bad01c7c04de0c81a11455753f06d2cfd04f425 Mon Sep 17 00:00:00 2001 From: Erik Darling Date: Mon, 2 Aug 2021 09:36:10 -0400 Subject: [PATCH 225/662] Add check for unindexed foreign keys Check is just a slight variation on the current check for FKs with cascading actions. --- .../sp_BlitzIndex_Checks_by_Priority.md | 1 + sp_BlitzIndex.sql | 115 +++++++++++++++++- 2 files changed, 113 insertions(+), 3 deletions(-) diff --git a/Documentation/sp_BlitzIndex_Checks_by_Priority.md b/Documentation/sp_BlitzIndex_Checks_by_Priority.md index c949a1227..31afa75cf 100644 --- a/Documentation/sp_BlitzIndex_Checks_by_Priority.md +++ b/Documentation/sp_BlitzIndex_Checks_by_Priority.md @@ -35,6 +35,7 @@ If you want to add a new check, start at 121. | 100 | Serial Forcer | Computed Column with Scalar UDF | https://www.brentozar.com/go/serialudf | 99 | | 100 | Serial Forcer | Check Constraint with Scalar UDF | https://www.brentozar.com/go/computedscalar | 94 | | 150 | Abnormal Psychology | Cascading Updates or Deletes | https://www.brentozar.com/go/AbnormalPsychology | 71 | +| 100 | Abnormal Psychology | Unindexed Foreign Keys | https://www.brentozar.com/go/AbnormalPsychology | 72 | | 150 | Abnormal Psychology | Columnstore Index | https://www.brentozar.com/go/AbnormalPsychology | 61 | | 150 | Abnormal Psychology | Column Collation Does Not Match Database Collation| https://www.brentozar.com/go/AbnormalPsychology | 69 | | 150 | Abnormal Psychology | Compressed Index | https://www.brentozar.com/go/AbnormalPsychology | 63 | diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index 7dde59708..0f206db63 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -187,6 +187,9 @@ IF OBJECT_ID('tempdb..#MissingIndexes') IS NOT NULL IF OBJECT_ID('tempdb..#ForeignKeys') IS NOT NULL DROP TABLE #ForeignKeys; +IF OBJECT_ID('tempdb..#UnindexedForeignKeys') IS NOT NULL + DROP TABLE #UnindexedForeignKeys; + IF OBJECT_ID('tempdb..#BlitzIndexResults') IS NOT NULL DROP TABLE #BlitzIndexResults; @@ -631,6 +634,18 @@ IF OBJECT_ID('tempdb..#Ignore_Databases') IS NOT NULL update_referential_action_desc NVARCHAR(16), delete_referential_action_desc NVARCHAR(60) ); + + CREATE TABLE #UnindexedForeignKeys + ( + [database_id] INT NOT NULL, + [database_name] NVARCHAR(128) NOT NULL , + [schema_name] NVARCHAR(128) NOT NULL , + foreign_key_name NVARCHAR(256), + parent_object_name NVARCHAR(256), + parent_object_id INT, + referenced_object_name NVARCHAR(256), + referenced_object_id INT + ); CREATE TABLE #IndexCreateTsql ( index_sanity_id INT NOT NULL, @@ -1792,6 +1807,77 @@ BEGIN TRY EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; + SET @dsql = N' + SELECT + DB_ID(N' + QUOTENAME(@DatabaseName,'''') + N') AS [database_id], + @i_DatabaseName AS database_name, + foreign_key_schema = + s.name, + foreign_key_name = + fk.name, + foreign_key_table = + OBJECT_NAME(fk.parent_object_id, DB_ID(N' + QUOTENAME(@DatabaseName,'''') + N')), + fk.parent_object_id, + foreign_key_referenced_table = + OBJECT_NAME(fk.referenced_object_id, DB_ID(N' + QUOTENAME(@DatabaseName,'''') + N')), + fk.referenced_object_id + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.foreign_keys fk + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s + ON s.schema_id = fk.schema_id + WHERE fk.is_disabled = 0 + AND EXISTS + ( + SELECT + 1/0 + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.foreign_key_columns fkc + WHERE fkc.constraint_object_id = fk.object_id + AND NOT EXISTS + ( + SELECT + 1/0 + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.index_columns ic + WHERE ic.object_id = fkc.parent_object_id + AND ic.column_id = fkc.parent_column_id + AND ic.index_column_id = fkc.constraint_column_id + ) + ) + OPTION (RECOMPILE);' + IF @dsql IS NULL + RAISERROR('@dsql is null',16,1); + + RAISERROR (N'Inserting data into #ForeignKeys',0,1) WITH NOWAIT; + IF @Debug = 1 + BEGIN + PRINT SUBSTRING(@dsql, 0, 4000); + PRINT SUBSTRING(@dsql, 4000, 8000); + PRINT SUBSTRING(@dsql, 8000, 12000); + PRINT SUBSTRING(@dsql, 12000, 16000); + PRINT SUBSTRING(@dsql, 16000, 20000); + PRINT SUBSTRING(@dsql, 20000, 24000); + PRINT SUBSTRING(@dsql, 24000, 28000); + PRINT SUBSTRING(@dsql, 28000, 32000); + PRINT SUBSTRING(@dsql, 32000, 36000); + PRINT SUBSTRING(@dsql, 36000, 40000); + END; + + INSERT + #UnindexedForeignKeys + ( + database_id, + database_name, + schema_name, + foreign_key_name, + parent_object_name, + parent_object_id, + referenced_object_name, + referenced_object_id + ) + EXEC sys.sp_executesql + @dsql, + N'@i_DatabaseName sysname', + @DatabaseName; + + IF @SkipStatistics = 0 /* AND DB_NAME() = @DatabaseName /* Can only get stats in the current database - see https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/1947 */ */ BEGIN IF ((PARSENAME(@SQLServerProductVersion, 4) >= 12) @@ -2518,6 +2604,7 @@ BEGIN SELECT '#IndexColumns' AS table_name, * FROM #IndexColumns; SELECT '#MissingIndexes' AS table_name, * FROM #MissingIndexes; SELECT '#ForeignKeys' AS table_name, * FROM #ForeignKeys; + SELECT '#UnindexedForeignKeys' AS table_name, * FROM #UnindexedForeignKeys; SELECT '#BlitzIndexResults' AS table_name, * FROM #BlitzIndexResults; SELECT '#IndexCreateTsql' AS table_name, * FROM #IndexCreateTsql; SELECT '#DatabaseList' AS table_name, * FROM #DatabaseList; @@ -4510,15 +4597,14 @@ BEGIN; N'Cascading Updates or Deletes' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, - N'Foreign Key ' + foreign_key_name + + N'Foreign Key ' + QUOTENAME(foreign_key_name) + N' on ' + QUOTENAME(parent_object_name) + N'(' + LTRIM(parent_fk_columns) + N')' + N' referencing ' + QUOTENAME(referenced_object_name) + N'(' + LTRIM(referenced_fk_columns) + N')' + N' has settings:' + CASE [delete_referential_action_desc] WHEN N'NO_ACTION' THEN N'' ELSE N' ON DELETE ' +[delete_referential_action_desc] END + CASE [update_referential_action_desc] WHEN N'NO_ACTION' THEN N'' ELSE N' ON UPDATE ' + [update_referential_action_desc] END AS details, - [fk].[database_name] - AS index_definition, + [fk].[database_name] AS index_definition, N'N/A' AS secret_columns, N'N/A' AS index_usage_summary, N'N/A' AS index_size_summary, @@ -4529,6 +4615,29 @@ BEGIN; OR [update_referential_action_desc] <> N'NO_ACTION') OPTION ( RECOMPILE ); + RAISERROR(N'check_id 72: Unindexed foreign keys.', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary, more_info ) + SELECT 72 AS check_id, + NULL AS index_sanity_id, + 100 AS Priority, + N'Abnormal Psychology' AS findings_group, + N'Unindexed Foreign Keys' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, + N'Foreign Key ' + QUOTENAME(foreign_key_name) + + N' on ' + QUOTENAME(parent_object_name) + N'' + + N' referencing ' + QUOTENAME(referenced_object_name) + N'' + + N' does not appear to have a supporting index.' AS details, + N'N/A' AS index_definition, + N'N/A' AS secret_columns, + N'N/A' AS index_usage_summary, + N'N/A' AS index_size_summary, + (SELECT TOP 1 more_info FROM #IndexSanity i WHERE i.object_id=fk.parent_object_id AND i.database_id = fk.database_id AND i.schema_name = fk.schema_name) + AS more_info + FROM #UnindexedForeignKeys AS fk + OPTION ( RECOMPILE ); + RAISERROR(N'check_id 73: In-Memory OLTP', 0,1) WITH NOWAIT; INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, From ab005356a8f73e8b9dcbc9c9e1f66cc8fb77606c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Nacimiento?= <56126077+jesusnac@users.noreply.github.com> Date: Tue, 3 Aug 2021 09:54:37 +0100 Subject: [PATCH 226/662] Fixing #2960 --- .gitignore | 1 + sp_DatabaseRestore.sql | 30 +++++++++++++++++++++++++----- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index d959d3a6c..c4639039a 100644 --- a/.gitignore +++ b/.gitignore @@ -131,3 +131,4 @@ test-results/* x64/ ~$* ~*.* +/.vs diff --git a/sp_DatabaseRestore.sql b/sp_DatabaseRestore.sql index ff6c5ff36..d899f8c73 100755 --- a/sp_DatabaseRestore.sql +++ b/sp_DatabaseRestore.sql @@ -41,7 +41,7 @@ SET STATISTICS XML OFF; /*Versioning details*/ -SELECT @Version = '8.05', @VersionDate = '20210725'; +SELECT @Version = '8.06', @VersionDate = '20210803'; IF(@VersionCheckMode = 1) BEGIN @@ -774,8 +774,18 @@ BEGIN IF @sql IS NULL PRINT '@sql is NULL for SINGLE_USER'; PRINT @sql; END; - IF @Debug IN (0, 1) AND @Execute = 'Y' AND DATABASEPROPERTYEX(@RestoreDatabaseName,'STATUS') != 'RESTORING' - EXECUTE @sql = [dbo].[CommandExecute] @DatabaseContext=N'master', @Command = @sql, @CommandType = 'ALTER DATABASE SINGLE_USER', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y'; + IF @Debug IN (0, 1) AND @Execute = 'Y' + BEGIN + IF DATABASEPROPERTYEX(@UnquotedRestoreDatabaseName,'STATUS') != 'RESTORING' + BEGIN + EXECUTE @sql = [dbo].[CommandExecute] @DatabaseContext=N'master', @Command = @sql, @CommandType = 'ALTER DATABASE SINGLE_USER', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y'; + END + ELSE IF @Debug = 1 + BEGIN + IF DATABASEPROPERTYEX(@UnquotedRestoreDatabaseName,'STATUS') IS NULL PRINT 'Unable to retrieve STATUS from "' + @UnquotedRestoreDatabaseName + '" database. Skiping setting database to SINGLE_USER'; + ELSE IF DATABASEPROPERTYEX(@UnquotedRestoreDatabaseName,'STATUS') = 'RESTORING' PRINT @UnquotedRestoreDatabaseName + ' database STATUS is RESTORING. Skiping setting database to SINGLE_USER'; + END + END END IF @ExistingDBAction IN (2, 3) BEGIN @@ -819,8 +829,18 @@ BEGIN IF @sql IS NULL PRINT '@sql is NULL for Offline database'; PRINT @sql; END; - IF @Debug IN (0, 1) AND @Execute = 'Y' AND DATABASEPROPERTYEX(@RestoreDatabaseName,'STATUS') != 'RESTORING' - EXECUTE @sql = [dbo].[CommandExecute] @DatabaseContext=N'master', @Command = @sql, @CommandType = 'OFFLINE DATABASE', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y'; + IF @Debug IN (0, 1) AND @Execute = 'Y' + BEGIN + IF DATABASEPROPERTYEX(@UnquotedRestoreDatabaseName,'STATUS') != 'RESTORING' + BEGIN + EXECUTE @sql = [dbo].[CommandExecute] @DatabaseContext=N'master', @Command = @sql, @CommandType = 'OFFLINE DATABASE', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y'; + END + ELSE IF @Debug = 1 + BEGIN + IF DATABASEPROPERTYEX(@UnquotedRestoreDatabaseName,'STATUS') IS NULL PRINT 'Unable to retrieve STATUS from "' + @UnquotedRestoreDatabaseName + '" database. Skiping setting database OFFLINE'; + ELSE IF DATABASEPROPERTYEX(@UnquotedRestoreDatabaseName,'STATUS') = 'RESTORING' PRINT @UnquotedRestoreDatabaseName + ' database STATUS is RESTORING. Skiping setting database OFFLINE'; + END + END END; END ELSE From ba04505352be1310855ab147b3d41d7648324bbb Mon Sep 17 00:00:00 2001 From: David Hooey Date: Wed, 4 Aug 2021 14:11:48 -0400 Subject: [PATCH 227/662] Issue #2970 sp_BlitzWho - Add dm_exec_requests.wait_resource to report and output table. --- Install-All-Scripts.sql | 11 +++++++++-- Install-Core-Blitz-No-Query-Store.sql | 11 +++++++++-- Install-Core-Blitz-With-Query-Store.sql | 11 +++++++++-- sp_BlitzWho.sql | 10 ++++++++-- 4 files changed, 35 insertions(+), 8 deletions(-) diff --git a/Install-All-Scripts.sql b/Install-All-Scripts.sql index 9e6916928..89c5022d8 100755 --- a/Install-All-Scripts.sql +++ b/Install-All-Scripts.sql @@ -13095,6 +13095,7 @@ SELECT [ServerName] ,[query_cost] ,[status] ,[wait_info] + ,[wait_resource] ,[top_session_waits] ,[blocking_session_id] ,[open_transaction_count] @@ -35517,6 +35518,7 @@ IF @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @Output [query_cost] [float] NULL, [status] [nvarchar](30) NOT NULL, [wait_info] [nvarchar](max) NULL, + [wait_resource] [nvarchar](max) NULL, [top_session_waits] [nvarchar](max) NULL, [blocking_session_id] [smallint] NULL, [open_transaction_count] [int] NULL, @@ -35688,6 +35690,7 @@ IF @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @Output + N' [query_cost], ' + @LineFeed + N' [status], ' + @LineFeed + N' [wait_info], ' + @LineFeed + + N' [wait_resource], ' + @LineFeed + N' [top_session_waits], ' + @LineFeed + N' [blocking_session_id], ' + @LineFeed + N' [open_transaction_count], ' + @LineFeed @@ -35789,6 +35792,7 @@ IF @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @Output + N' [query_cost], ' + @LineFeed + N' [status], ' + @LineFeed + N' [wait_info], ' + @LineFeed + + N' [wait_resource], ' + @LineFeed + N' [top_session_waits], ' + @LineFeed + N' [blocking_session_id], ' + @LineFeed + N' [open_transaction_count], ' + @LineFeed @@ -36033,6 +36037,7 @@ BEGIN WHEN s.status <> ''sleeping'' THEN COALESCE(wt.wait_info, RTRIM(blocked.lastwaittype) + '' ('' + CONVERT(VARCHAR(10), blocked.waittime) + '')'' ) ELSE NULL END AS wait_info , + r.wait_resource , CASE WHEN r.blocking_session_id <> 0 AND blocked.session_id IS NULL THEN r.blocking_session_id WHEN r.blocking_session_id <> 0 AND s.session_id <> blocked.blocking_session_id @@ -36261,7 +36266,8 @@ IF @ProductVersionMajor >= 11 CASE WHEN s.status <> ''sleeping'' THEN COALESCE(wt.wait_info, RTRIM(blocked.lastwaittype) + '' ('' + CONVERT(VARCHAR(10), blocked.waittime) + '')'' ) ELSE NULL - END AS wait_info ,' + END AS wait_info , + r.wait_resource ,' + CASE @SessionWaits WHEN 1 THEN + N'SUBSTRING(wt2.session_wait_info, 0, LEN(wt2.session_wait_info) ) AS top_session_waits ,' @@ -36591,7 +36597,8 @@ IF @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @Output + CASE WHEN @ProductVersionMajor >= 11 AND @ShowActualParameters = 1 THEN N',[Live_Parameter_Info]' ELSE N'' END + N' ,[query_cost] ,[status] - ,[wait_info]' + ,[wait_info] + ,[wait_resource]' + CASE WHEN @ProductVersionMajor >= 11 THEN N',[top_session_waits]' ELSE N'' END + N' ,[blocking_session_id] ,[open_transaction_count] diff --git a/Install-Core-Blitz-No-Query-Store.sql b/Install-Core-Blitz-No-Query-Store.sql index c77729328..cc4bbf47c 100755 --- a/Install-Core-Blitz-No-Query-Store.sql +++ b/Install-Core-Blitz-No-Query-Store.sql @@ -10274,6 +10274,7 @@ SELECT [ServerName] ,[query_cost] ,[status] ,[wait_info] + ,[wait_resource] ,[top_session_waits] ,[blocking_session_id] ,[open_transaction_count] @@ -26941,6 +26942,7 @@ IF @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @Output [query_cost] [float] NULL, [status] [nvarchar](30) NOT NULL, [wait_info] [nvarchar](max) NULL, + [wait_resource] [nvarchar](max) NULL, [top_session_waits] [nvarchar](max) NULL, [blocking_session_id] [smallint] NULL, [open_transaction_count] [int] NULL, @@ -27112,6 +27114,7 @@ IF @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @Output + N' [query_cost], ' + @LineFeed + N' [status], ' + @LineFeed + N' [wait_info], ' + @LineFeed + + N' [wait_resource], ' + @LineFeed + N' [top_session_waits], ' + @LineFeed + N' [blocking_session_id], ' + @LineFeed + N' [open_transaction_count], ' + @LineFeed @@ -27213,6 +27216,7 @@ IF @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @Output + N' [query_cost], ' + @LineFeed + N' [status], ' + @LineFeed + N' [wait_info], ' + @LineFeed + + N' [wait_resource], ' + @LineFeed + N' [top_session_waits], ' + @LineFeed + N' [blocking_session_id], ' + @LineFeed + N' [open_transaction_count], ' + @LineFeed @@ -27457,6 +27461,7 @@ BEGIN WHEN s.status <> ''sleeping'' THEN COALESCE(wt.wait_info, RTRIM(blocked.lastwaittype) + '' ('' + CONVERT(VARCHAR(10), blocked.waittime) + '')'' ) ELSE NULL END AS wait_info , + r.wait_resource , CASE WHEN r.blocking_session_id <> 0 AND blocked.session_id IS NULL THEN r.blocking_session_id WHEN r.blocking_session_id <> 0 AND s.session_id <> blocked.blocking_session_id @@ -27685,7 +27690,8 @@ IF @ProductVersionMajor >= 11 CASE WHEN s.status <> ''sleeping'' THEN COALESCE(wt.wait_info, RTRIM(blocked.lastwaittype) + '' ('' + CONVERT(VARCHAR(10), blocked.waittime) + '')'' ) ELSE NULL - END AS wait_info ,' + END AS wait_info , + r.wait_resource ,' + CASE @SessionWaits WHEN 1 THEN + N'SUBSTRING(wt2.session_wait_info, 0, LEN(wt2.session_wait_info) ) AS top_session_waits ,' @@ -28015,7 +28021,8 @@ IF @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @Output + CASE WHEN @ProductVersionMajor >= 11 AND @ShowActualParameters = 1 THEN N',[Live_Parameter_Info]' ELSE N'' END + N' ,[query_cost] ,[status] - ,[wait_info]' + ,[wait_info] + ,[wait_resource]' + CASE WHEN @ProductVersionMajor >= 11 THEN N',[top_session_waits]' ELSE N'' END + N' ,[blocking_session_id] ,[open_transaction_count] diff --git a/Install-Core-Blitz-With-Query-Store.sql b/Install-Core-Blitz-With-Query-Store.sql index 75e10ceb7..df642dc9d 100755 --- a/Install-Core-Blitz-With-Query-Store.sql +++ b/Install-Core-Blitz-With-Query-Store.sql @@ -10274,6 +10274,7 @@ SELECT [ServerName] ,[query_cost] ,[status] ,[wait_info] + ,[wait_resource] ,[top_session_waits] ,[blocking_session_id] ,[open_transaction_count] @@ -32696,6 +32697,7 @@ IF @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @Output [query_cost] [float] NULL, [status] [nvarchar](30) NOT NULL, [wait_info] [nvarchar](max) NULL, + [wait_resource] [nvarchar](max) NULL, [top_session_waits] [nvarchar](max) NULL, [blocking_session_id] [smallint] NULL, [open_transaction_count] [int] NULL, @@ -32867,6 +32869,7 @@ IF @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @Output + N' [query_cost], ' + @LineFeed + N' [status], ' + @LineFeed + N' [wait_info], ' + @LineFeed + + N' [wait_resource], ' + @LineFeed + N' [top_session_waits], ' + @LineFeed + N' [blocking_session_id], ' + @LineFeed + N' [open_transaction_count], ' + @LineFeed @@ -32968,6 +32971,7 @@ IF @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @Output + N' [query_cost], ' + @LineFeed + N' [status], ' + @LineFeed + N' [wait_info], ' + @LineFeed + + N' [wait_resource], ' + @LineFeed + N' [top_session_waits], ' + @LineFeed + N' [blocking_session_id], ' + @LineFeed + N' [open_transaction_count], ' + @LineFeed @@ -33212,6 +33216,7 @@ BEGIN WHEN s.status <> ''sleeping'' THEN COALESCE(wt.wait_info, RTRIM(blocked.lastwaittype) + '' ('' + CONVERT(VARCHAR(10), blocked.waittime) + '')'' ) ELSE NULL END AS wait_info , + r.wait_resource , CASE WHEN r.blocking_session_id <> 0 AND blocked.session_id IS NULL THEN r.blocking_session_id WHEN r.blocking_session_id <> 0 AND s.session_id <> blocked.blocking_session_id @@ -33440,7 +33445,8 @@ IF @ProductVersionMajor >= 11 CASE WHEN s.status <> ''sleeping'' THEN COALESCE(wt.wait_info, RTRIM(blocked.lastwaittype) + '' ('' + CONVERT(VARCHAR(10), blocked.waittime) + '')'' ) ELSE NULL - END AS wait_info ,' + END AS wait_info , + r.wait_resource ,' + CASE @SessionWaits WHEN 1 THEN + N'SUBSTRING(wt2.session_wait_info, 0, LEN(wt2.session_wait_info) ) AS top_session_waits ,' @@ -33770,7 +33776,8 @@ IF @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @Output + CASE WHEN @ProductVersionMajor >= 11 AND @ShowActualParameters = 1 THEN N',[Live_Parameter_Info]' ELSE N'' END + N' ,[query_cost] ,[status] - ,[wait_info]' + ,[wait_info] + ,[wait_resource]' + CASE WHEN @ProductVersionMajor >= 11 THEN N',[top_session_waits]' ELSE N'' END + N' ,[blocking_session_id] ,[open_transaction_count] diff --git a/sp_BlitzWho.sql b/sp_BlitzWho.sql index 64d021b7e..1a897f2d2 100644 --- a/sp_BlitzWho.sql +++ b/sp_BlitzWho.sql @@ -170,6 +170,7 @@ IF @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @Output [query_cost] [float] NULL, [status] [nvarchar](30) NOT NULL, [wait_info] [nvarchar](max) NULL, + [wait_resource] [nvarchar](max) NULL, [top_session_waits] [nvarchar](max) NULL, [blocking_session_id] [smallint] NULL, [open_transaction_count] [int] NULL, @@ -341,6 +342,7 @@ IF @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @Output + N' [query_cost], ' + @LineFeed + N' [status], ' + @LineFeed + N' [wait_info], ' + @LineFeed + + N' [wait_resource], ' + @LineFeed + N' [top_session_waits], ' + @LineFeed + N' [blocking_session_id], ' + @LineFeed + N' [open_transaction_count], ' + @LineFeed @@ -442,6 +444,7 @@ IF @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @Output + N' [query_cost], ' + @LineFeed + N' [status], ' + @LineFeed + N' [wait_info], ' + @LineFeed + + N' [wait_resource], ' + @LineFeed + N' [top_session_waits], ' + @LineFeed + N' [blocking_session_id], ' + @LineFeed + N' [open_transaction_count], ' + @LineFeed @@ -686,6 +689,7 @@ BEGIN WHEN s.status <> ''sleeping'' THEN COALESCE(wt.wait_info, RTRIM(blocked.lastwaittype) + '' ('' + CONVERT(VARCHAR(10), blocked.waittime) + '')'' ) ELSE NULL END AS wait_info , + r.wait_resource , CASE WHEN r.blocking_session_id <> 0 AND blocked.session_id IS NULL THEN r.blocking_session_id WHEN r.blocking_session_id <> 0 AND s.session_id <> blocked.blocking_session_id @@ -914,7 +918,8 @@ IF @ProductVersionMajor >= 11 CASE WHEN s.status <> ''sleeping'' THEN COALESCE(wt.wait_info, RTRIM(blocked.lastwaittype) + '' ('' + CONVERT(VARCHAR(10), blocked.waittime) + '')'' ) ELSE NULL - END AS wait_info ,' + END AS wait_info , + r.wait_resource ,' + CASE @SessionWaits WHEN 1 THEN + N'SUBSTRING(wt2.session_wait_info, 0, LEN(wt2.session_wait_info) ) AS top_session_waits ,' @@ -1244,7 +1249,8 @@ IF @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @Output + CASE WHEN @ProductVersionMajor >= 11 AND @ShowActualParameters = 1 THEN N',[Live_Parameter_Info]' ELSE N'' END + N' ,[query_cost] ,[status] - ,[wait_info]' + ,[wait_info] + ,[wait_resource]' + CASE WHEN @ProductVersionMajor >= 11 THEN N',[top_session_waits]' ELSE N'' END + N' ,[blocking_session_id] ,[open_transaction_count] From f3eb8e1b203db9d63a69988a868119e50a6b9e9b Mon Sep 17 00:00:00 2001 From: DavidSchanzer <68979631+DavidSchanzer@users.noreply.github.com> Date: Thu, 5 Aug 2021 11:12:27 +1000 Subject: [PATCH 228/662] Correct minor version number for SQL 2017 CU25 from 3410 to 3401 as indicated in https://support.microsoft.com/en-us/help/5003830 --- SqlServerVersions.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SqlServerVersions.sql b/SqlServerVersions.sql index 5768df9e0..134bd6925 100644 --- a/SqlServerVersions.sql +++ b/SqlServerVersions.sql @@ -55,7 +55,7 @@ VALUES (15, 4003, 'CU1', 'https://support.microsoft.com/en-us/help/4527376', '2020-01-07', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 1 '), (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 '), - (14, 3410, 'RTM CU25', 'https://support.microsoft.com/en-us/help/5003830', '2021-07-12', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 25'), + (14, 3401, 'RTM CU25', 'https://support.microsoft.com/en-us/help/5003830', '2021-07-12', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 25'), (14, 3391, 'RTM CU24', 'https://support.microsoft.com/en-us/help/5001228', '2021-05-10', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 24'), (14, 3381, 'RTM CU23', 'https://support.microsoft.com/en-us/help/5000685', '2021-02-25', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 23'), (14, 3370, 'RTM CU22 GDR', 'https://support.microsoft.com/en-us/help/4583457', '2021-01-12', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 22 GDR'), From d5992b34dd01ff03e89ff9c062af2758fd153fb3 Mon Sep 17 00:00:00 2001 From: Erik Darling Date: Sat, 7 Aug 2021 11:12:24 -0400 Subject: [PATCH 229/662] Remove USE Make it cloudy Closes #2971 --- sp_BlitzFirst.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sp_BlitzFirst.sql b/sp_BlitzFirst.sql index aea3da81e..084070d4a 100644 --- a/sp_BlitzFirst.sql +++ b/sp_BlitzFirst.sql @@ -3126,7 +3126,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, This has to be done as dynamic SQL because we have to execute OBJECT_NAME inside TempDB. */ IF @@ROWCOUNT > 0 BEGIN - SET @StringToExecute = N'USE tempdb; + SET @StringToExecute = N' INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) SELECT TOP 10 29 AS CheckID, 40 AS Priority, From a58f8c1fade9c986fde8093c9f8d3e1c06cbcf43 Mon Sep 17 00:00:00 2001 From: Eitan Blumin Date: Mon, 9 Aug 2021 12:46:42 +0300 Subject: [PATCH 230/662] Fix #2962 Replace usage of `sys.dm_exec_sessions` with `sys.sysprocesses` to add support for older SQL versions --- sp_BlitzFirst.sql | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/sp_BlitzFirst.sql b/sp_BlitzFirst.sql index aea3da81e..d186087d2 100644 --- a/sp_BlitzFirst.sql +++ b/sp_BlitzFirst.sql @@ -1755,30 +1755,30 @@ BEGIN 'Query Problems' AS FindingGroup, 'Sleeping Query with Open Transactions' AS Finding, 'https://www.brentozar.com/askbrent/sleeping-query-with-open-transactions/' AS URL, - 'Database: ' + DB_NAME(db.resource_database_id) + @LineFeed + 'Host: ' + s.[host_name] + @LineFeed + 'Program: ' + s.[program_name] + @LineFeed + 'Asleep with open transactions and locks since ' + CAST(s.last_request_end_time AS NVARCHAR(100)) + '. ' AS Details, - 'KILL ' + CAST(s.session_id AS NVARCHAR(100)) + ';' AS HowToStopIt, - s.last_request_start_time AS StartTime, - s.login_name AS LoginName, - s.nt_user_name AS NTUserName, + 'Database: ' + DB_NAME(db.resource_database_id) + @LineFeed + 'Host: ' + s.hostname + @LineFeed + 'Program: ' + s.[program_name] + @LineFeed + 'Asleep with open transactions and locks since ' + CAST(s.last_batch AS NVARCHAR(100)) + '. ' AS Details, + 'KILL ' + CAST(s.spid AS NVARCHAR(100)) + ';' AS HowToStopIt, + s.last_batch AS StartTime, + s.loginame AS LoginName, + s.nt_username AS NTUserName, s.[program_name] AS ProgramName, - s.[host_name] AS HostName, + s.hostname AS HostName, db.[resource_database_id] AS DatabaseID, DB_NAME(db.resource_database_id) AS DatabaseName, (SELECT TOP 1 [text] FROM sys.dm_exec_sql_text(c.most_recent_sql_handle)) AS QueryText, - s.open_transaction_count AS OpenTransactionCount - FROM sys.dm_exec_sessions s - INNER JOIN sys.dm_exec_connections c ON s.session_id = c.session_id + s.open_tran AS OpenTransactionCount + FROM sys.sysprocesses s + INNER JOIN sys.dm_exec_connections c ON s.spid = c.session_id INNER JOIN ( SELECT DISTINCT request_session_id, resource_database_id FROM sys.dm_tran_locks WHERE resource_type = N'DATABASE' AND request_mode = N'S' AND request_status = N'GRANT' - AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.session_id = db.request_session_id + AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.spid = db.request_session_id WHERE s.status = 'sleeping' - AND s.open_transaction_count > 0 - AND s.last_request_end_time < DATEADD(ss, -10, SYSDATETIME()) - AND EXISTS(SELECT * FROM sys.dm_tran_locks WHERE request_session_id = s.session_id + AND s.open_tran > 0 + AND s.last_batch < DATEADD(ss, -10, SYSDATETIME()) + AND EXISTS(SELECT * FROM sys.dm_tran_locks WHERE request_session_id = s.spid AND NOT (resource_type = N'DATABASE' AND request_mode = N'S' AND request_status = N'GRANT' AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE')); END From c30f9cbbdf77986148f57e9318b0fac64a34eec7 Mon Sep 17 00:00:00 2001 From: Erik Darling Date: Thu, 12 Aug 2021 15:07:22 -0400 Subject: [PATCH 231/662] Small fix Extraneous semicolon --- sp_BlitzIndex.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index d679260e2..30be1df15 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -2413,7 +2413,7 @@ SELECT N'.' + QUOTENAME([object_name]) + N' ADD CONSTRAINT [' + index_name + - N'] UNIQUE;' + N'] UNIQUE' WHEN is_CX_columnstore= 1 THEN N'CREATE CLUSTERED COLUMNSTORE INDEX ' + QUOTENAME(index_name) + N' on ' + QUOTENAME([database_name]) + N'.' + QUOTENAME([schema_name]) + N'.' + QUOTENAME([object_name]) ELSE /*Else not a PK or cx columnstore */ From ab2023821540809ffbbd7ae123b0fddab7e95cea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Nacimiento?= <56126077+jesusnac@users.noreply.github.com> Date: Thu, 26 Aug 2021 10:18:34 +0100 Subject: [PATCH 232/662] Issue #2981 Add "SET TRUSTWORTHY ON" option on SP_DATABASERESTORE --- .gitignore | 1 + sp_DatabaseRestore.sql | 29 +++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/.gitignore b/.gitignore index d959d3a6c..39e23f01f 100644 --- a/.gitignore +++ b/.gitignore @@ -131,3 +131,4 @@ test-results/* x64/ ~$* ~*.* +/.vs/slnx.sqlite diff --git a/sp_DatabaseRestore.sql b/sp_DatabaseRestore.sql index ff6c5ff36..34144960c 100755 --- a/sp_DatabaseRestore.sql +++ b/sp_DatabaseRestore.sql @@ -29,6 +29,7 @@ ALTER PROCEDURE [dbo].[sp_DatabaseRestore] @SimpleFolderEnumeration BIT = 0, @SkipBackupsAlreadyInMsdb BIT = 0, @DatabaseOwner sysname = NULL, + @SetTrustworthyON BIT = 0, @Execute CHAR(1) = Y, @Debug INT = 0, @Help BIT = 0, @@ -1450,6 +1451,34 @@ IF @DatabaseOwner IS NOT NULL END END; + IF @SetTrustworthyON = 1 + BEGIN + IF @RunRecovery = 1 + BEGIN + IF IS_SRVROLEMEMBER('sysadmin') = 1 + BEGIN + SET @sql = N'ALTER DATABASE ' + @RestoreDatabaseName + N' SET TRUSTWORTHY ON;'; + + IF @Debug = 1 OR @Execute = 'N' + BEGIN + IF @sql IS NULL PRINT '@sql is NULL for SET TRUSTWORTHY ON'; + PRINT @sql; + END; + + IF @Debug IN (0, 1) AND @Execute = 'Y' + EXECUTE @sql = [dbo].[CommandExecute] @DatabaseContext=N'master', @Command = @sql, @CommandType = 'ALTER DATABASE', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y'; + END + ELSE + BEGIN + PRINT 'Current user''s login is NOT a member of the sysadmin role. Database TRUSTWORHY bit has not been enabled.'; + END + END + ELSE + BEGIN + PRINT @RestoreDatabaseName + ' is still in Recovery, so we are unable to enable the TRUSTWORHY bit.'; + END + END; + -- If test restore then blow the database away (be careful) IF @TestRestore = 1 BEGIN From 41a3d8d3aa62283bb361a14fdb2dd583a05ddfb3 Mon Sep 17 00:00:00 2001 From: DavidSchanzer <68979631+DavidSchanzer@users.noreply.github.com> Date: Thu, 2 Sep 2021 10:30:23 +1000 Subject: [PATCH 233/662] Prevent DB non-default values msg for offline DBs --- sp_Blitz.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index 305abef37..cd037f495 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -4342,12 +4342,12 @@ AS SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details) SELECT ' + CAST(@CurrentCheckID AS NVARCHAR(200)) + ', d.[name], ' + CAST(@CurrentPriority AS NVARCHAR(200)) + ', ''Non-Default Database Config'', ''' + @CurrentFinding + ''',''' + @CurrentURL + ''',''' + COALESCE(@CurrentDetails, 'This database setting is not the default.') + ''' FROM sys.databases d - WHERE d.database_id > 4 AND d.state <> 1 AND (d.[' + @CurrentName + '] NOT IN (0, 60) OR d.[' + @CurrentName + '] IS NULL) OPTION (RECOMPILE);'; + WHERE d.database_id > 4 AND d.state = 0 AND (d.[' + @CurrentName + '] NOT IN (0, 60) OR d.[' + @CurrentName + '] IS NULL) OPTION (RECOMPILE);'; ELSE SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details) SELECT ' + CAST(@CurrentCheckID AS NVARCHAR(200)) + ', d.[name], ' + CAST(@CurrentPriority AS NVARCHAR(200)) + ', ''Non-Default Database Config'', ''' + @CurrentFinding + ''',''' + @CurrentURL + ''',''' + COALESCE(@CurrentDetails, 'This database setting is not the default.') + ''' FROM sys.databases d - WHERE d.database_id > 4 AND d.state <> 1 AND (d.[' + @CurrentName + '] <> ' + @CurrentDefaultValue + ' OR d.[' + @CurrentName + '] IS NULL) OPTION (RECOMPILE);'; + WHERE d.database_id > 4 AND d.state = 0 AND (d.[' + @CurrentName + '] <> ' + @CurrentDefaultValue + ' OR d.[' + @CurrentName + '] IS NULL) OPTION (RECOMPILE);'; IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; From b491d96d14fcc2d0049415ca56e930bd85e87a77 Mon Sep 17 00:00:00 2001 From: Erik Darling Date: Sat, 4 Sep 2021 12:12:18 -0400 Subject: [PATCH 234/662] Add 1204 to TF list Closes #2985 --- sp_Blitz.sql | 1 + 1 file changed, 1 insertion(+) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index a978485df..b0d3accb6 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -7770,6 +7770,7 @@ IF @ProductVersionMajor >= 10 WHEN [T].[TraceFlag] = '1117' THEN '1117 enabled globally, which grows all files in a filegroup at the same time.' WHEN [T].[TraceFlag] = '1118' THEN '1118 enabled globally, which tries to reduce SGAM waits.' WHEN [T].[TraceFlag] = '1211' THEN '1211 enabled globally, which disables lock escalation when you least expect it. This is usually a very bad idea.' + WHEN [T].[TraceFlag] = '1204' THEN '1222 enabled globally, which captures deadlock graphs in the error log.' WHEN [T].[TraceFlag] = '1222' THEN '1222 enabled globally, which captures deadlock graphs in the error log.' WHEN [T].[TraceFlag] = '1224' THEN '1224 enabled globally, which disables lock escalation until the server has memory pressure. This is usually a very bad idea.' WHEN [T].[TraceFlag] = '1806' THEN '1806 enabled globally, which disables Instant File Initialization, causing restores and file growths to take longer. This is usually a very bad idea.' From 812c51747dcfa1c516000da16977008756351fc8 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Mon, 6 Sep 2021 05:48:16 +0000 Subject: [PATCH 235/662] sp_BlitzIndex documentation - fixing typo in priority New unindexed foreign key was in the 150s section but had priority 100, no biggie. --- Documentation/sp_BlitzIndex_Checks_by_Priority.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/sp_BlitzIndex_Checks_by_Priority.md b/Documentation/sp_BlitzIndex_Checks_by_Priority.md index 31afa75cf..bfbfd8af1 100644 --- a/Documentation/sp_BlitzIndex_Checks_by_Priority.md +++ b/Documentation/sp_BlitzIndex_Checks_by_Priority.md @@ -35,7 +35,7 @@ If you want to add a new check, start at 121. | 100 | Serial Forcer | Computed Column with Scalar UDF | https://www.brentozar.com/go/serialudf | 99 | | 100 | Serial Forcer | Check Constraint with Scalar UDF | https://www.brentozar.com/go/computedscalar | 94 | | 150 | Abnormal Psychology | Cascading Updates or Deletes | https://www.brentozar.com/go/AbnormalPsychology | 71 | -| 100 | Abnormal Psychology | Unindexed Foreign Keys | https://www.brentozar.com/go/AbnormalPsychology | 72 | +| 150 | Abnormal Psychology | Unindexed Foreign Keys | https://www.brentozar.com/go/AbnormalPsychology | 72 | | 150 | Abnormal Psychology | Columnstore Index | https://www.brentozar.com/go/AbnormalPsychology | 61 | | 150 | Abnormal Psychology | Column Collation Does Not Match Database Collation| https://www.brentozar.com/go/AbnormalPsychology | 69 | | 150 | Abnormal Psychology | Compressed Index | https://www.brentozar.com/go/AbnormalPsychology | 63 | From 081753312f7a7657f5fa65db1de96687e56a242c Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Mon, 6 Sep 2021 05:49:44 +0000 Subject: [PATCH 236/662] sp_BlitzIndex - priority 150 unindexed foreign keys Making it match the other foreign key rules. --- sp_BlitzIndex.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index 0f206db63..95d764e60 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -4620,7 +4620,7 @@ BEGIN; secret_columns, index_usage_summary, index_size_summary, more_info ) SELECT 72 AS check_id, NULL AS index_sanity_id, - 100 AS Priority, + 150 AS Priority, N'Abnormal Psychology' AS findings_group, N'Unindexed Foreign Keys' AS finding, [database_name] AS [Database Name], From 85c496647fb4512d9194747b7b86dd878268a22c Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Mon, 6 Sep 2021 05:59:31 +0000 Subject: [PATCH 237/662] Update .gitignore Removing unrelated change. --- .gitignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitignore b/.gitignore index c4639039a..d959d3a6c 100644 --- a/.gitignore +++ b/.gitignore @@ -131,4 +131,3 @@ test-results/* x64/ ~$* ~*.* -/.vs From 455b158403602e904ce00190dd62d863f0dccadc Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Mon, 6 Sep 2021 06:00:19 +0000 Subject: [PATCH 238/662] sp_DatabaseRestore - undo version change We bump the version numbers & dates when it's time for the monthly release. --- sp_DatabaseRestore.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sp_DatabaseRestore.sql b/sp_DatabaseRestore.sql index d899f8c73..d26c8c07b 100755 --- a/sp_DatabaseRestore.sql +++ b/sp_DatabaseRestore.sql @@ -41,7 +41,7 @@ SET STATISTICS XML OFF; /*Versioning details*/ -SELECT @Version = '8.06', @VersionDate = '20210803'; +SELECT @Version = '8.05', @VersionDate = '20210725'; IF(@VersionCheckMode = 1) BEGIN From 2305dcfca63287118887a42be701fae2b016a91c Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Mon, 6 Sep 2021 06:03:34 +0000 Subject: [PATCH 239/662] sp_DatabaseRestore - fixing typos Changing "skiping" to "skipping". --- sp_DatabaseRestore.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sp_DatabaseRestore.sql b/sp_DatabaseRestore.sql index d26c8c07b..6af31bfc2 100755 --- a/sp_DatabaseRestore.sql +++ b/sp_DatabaseRestore.sql @@ -782,7 +782,7 @@ BEGIN END ELSE IF @Debug = 1 BEGIN - IF DATABASEPROPERTYEX(@UnquotedRestoreDatabaseName,'STATUS') IS NULL PRINT 'Unable to retrieve STATUS from "' + @UnquotedRestoreDatabaseName + '" database. Skiping setting database to SINGLE_USER'; + IF DATABASEPROPERTYEX(@UnquotedRestoreDatabaseName,'STATUS') IS NULL PRINT 'Unable to retrieve STATUS from "' + @UnquotedRestoreDatabaseName + '" database. Skipping setting database to SINGLE_USER'; ELSE IF DATABASEPROPERTYEX(@UnquotedRestoreDatabaseName,'STATUS') = 'RESTORING' PRINT @UnquotedRestoreDatabaseName + ' database STATUS is RESTORING. Skiping setting database to SINGLE_USER'; END END @@ -837,7 +837,7 @@ BEGIN END ELSE IF @Debug = 1 BEGIN - IF DATABASEPROPERTYEX(@UnquotedRestoreDatabaseName,'STATUS') IS NULL PRINT 'Unable to retrieve STATUS from "' + @UnquotedRestoreDatabaseName + '" database. Skiping setting database OFFLINE'; + IF DATABASEPROPERTYEX(@UnquotedRestoreDatabaseName,'STATUS') IS NULL PRINT 'Unable to retrieve STATUS from "' + @UnquotedRestoreDatabaseName + '" database. Skipping setting database OFFLINE'; ELSE IF DATABASEPROPERTYEX(@UnquotedRestoreDatabaseName,'STATUS') = 'RESTORING' PRINT @UnquotedRestoreDatabaseName + ' database STATUS is RESTORING. Skiping setting database OFFLINE'; END END From 81c9f74cbef012b2354918fdb649d336290ff058 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Mon, 6 Sep 2021 06:20:09 +0000 Subject: [PATCH 240/662] #2970 sp_BlitzWho add column If the output table already exists and it doesn't have the wait_resource column, add it. Closes #2970. --- sp_BlitzWho.sql | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/sp_BlitzWho.sql b/sp_BlitzWho.sql index 1a897f2d2..2f7dccb60 100644 --- a/sp_BlitzWho.sql +++ b/sp_BlitzWho.sql @@ -287,6 +287,13 @@ IF @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @Output ALTER TABLE ' + @ObjectFullName + N' ADD outer_command NVARCHAR(4000) NULL;'; EXEC(@StringToExecute); + /* If the table doesn't have the new wait_resource column, add it. See Github #2970. */ + SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableName; + SET @StringToExecute = N'IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns + WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''wait_resource'') + ALTER TABLE ' + @ObjectFullName + N' ADD wait_resource NVARCHAR(MAX) NULL;'; + EXEC(@StringToExecute); + /* Delete history older than @OutputTableRetentionDays */ SET @OutputTableCleanupDate = CAST( (DATEADD(DAY, -1 * @OutputTableRetentionDays, GETDATE() ) ) AS DATE); SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' From 28949d9e88124b9b19006e32a8ac94534c0b1b3c Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Mon, 6 Sep 2021 06:28:15 +0000 Subject: [PATCH 241/662] Update .gitignore Removing unrelated change. --- .gitignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitignore b/.gitignore index 39e23f01f..d959d3a6c 100644 --- a/.gitignore +++ b/.gitignore @@ -131,4 +131,3 @@ test-results/* x64/ ~$* ~*.* -/.vs/slnx.sqlite From 20cb3635da84a450f6772dadd3656604fe211226 Mon Sep 17 00:00:00 2001 From: Erik Darling Date: Sat, 11 Sep 2021 11:10:39 -0400 Subject: [PATCH 242/662] Use sysprocesses instead Closes #2961 --- sp_BlitzFirst.sql | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/sp_BlitzFirst.sql b/sp_BlitzFirst.sql index ffd19d806..109096b5c 100644 --- a/sp_BlitzFirst.sql +++ b/sp_BlitzFirst.sql @@ -1517,28 +1517,32 @@ BEGIN 'https://www.brentozar.com/askbrent/backups/' AS URL, 'Backup of ' + DB_NAME(db.resource_database_id) + ' database (' + (SELECT CAST(CAST(SUM(size * 8.0 / 1024 / 1024) AS BIGINT) AS NVARCHAR) FROM #MasterFiles WHERE database_id = db.resource_database_id) + 'GB) ' + @LineFeed + CAST(r.percent_complete AS NVARCHAR(100)) + '% complete, has been running since ' + CAST(r.start_time AS NVARCHAR(100)) + '. ' + @LineFeed - + CASE WHEN COALESCE(s.nt_user_name, s.login_name) IS NOT NULL THEN (' Login: ' + COALESCE(s.nt_user_name, s.login_name) + ' ') ELSE '' END AS Details, + + CASE WHEN COALESCE(s.nt_username, s.loginame) IS NOT NULL THEN (' Login: ' + COALESCE(s.nt_username, s.loginame) + ' ') ELSE '' END AS Details, 'KILL ' + CAST(r.session_id AS NVARCHAR(100)) + ';' AS HowToStopIt, pl.query_plan AS QueryPlan, r.start_time AS StartTime, - s.login_name AS LoginName, - s.nt_user_name AS NTUserName, + s.loginame AS LoginName, + s.nt_username AS NTUserName, s.[program_name] AS ProgramName, - s.[host_name] AS HostName, + s.[hostname] AS HostName, db.[resource_database_id] AS DatabaseID, DB_NAME(db.resource_database_id) AS DatabaseName, 0 AS OpenTransactionCount, r.query_hash FROM sys.dm_exec_requests r INNER JOIN sys.dm_exec_connections c ON r.session_id = c.session_id - INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id - INNER JOIN ( - SELECT DISTINCT request_session_id, resource_database_id - FROM sys.dm_tran_locks - WHERE resource_type = N'DATABASE' - AND request_mode = N'S' - AND request_status = N'GRANT' - AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.session_id = db.request_session_id AND s.database_id = db.resource_database_id + INNER JOIN sys.sysprocesses AS s ON r.session_id = s.spid AND s.ecid = 0 + INNER JOIN + ( + SELECT DISTINCT + t.request_session_id, + t.resource_database_id + FROM sys.dm_tran_locks AS t + WHERE t.resource_type = N'DATABASE' + AND t.request_mode = N'S' + AND t.request_status = N'GRANT' + AND t.request_owner_type = N'SHARED_TRANSACTION_WORKSPACE' + ) AS db ON s.spid = db.request_session_id AND s.dbid = db.resource_database_id CROSS APPLY sys.dm_exec_query_plan(r.plan_handle) pl WHERE r.command LIKE 'BACKUP%' AND r.start_time <= DATEADD(minute, -5, GETDATE()) From 11a53d63444b8ab0d64fb1c51a4bf277b78fcb4e Mon Sep 17 00:00:00 2001 From: Erik Darling Date: Sun, 12 Sep 2021 11:34:18 -0400 Subject: [PATCH 243/662] Fix #2991 Close #2991 --- sp_BlitzIndex.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index 0ca8aab69..677078152 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -1376,7 +1376,7 @@ BEGIN TRY END; INSERT #IndexSanity ( [database_id], [object_id], [index_id], [index_type], [database_name], [schema_name], [object_name], index_name, is_indexed_view, is_unique, is_primary_key, is_unique_constraint, is_XML, is_spatial, is_NC_columnstore, is_CX_columnstore, is_in_memory_oltp, - is_disabled, is_hypothetical, is_padded, fill_factor, filter_definition, user_seeks, user_scans, + is_disabled, is_hypothetical, is_padded, fill_factor, filter_definition, [optimize_for_sequential_key], user_seeks, user_scans, user_lookups, user_updates, last_user_seek, last_user_scan, last_user_lookup, last_user_update, create_date, modify_date ) EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; From 23bafa8e82eb36249b473fefb9addafabc65d0da Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Tue, 14 Sep 2021 07:55:02 +0000 Subject: [PATCH 244/662] Adding 2019 CU12 to versions file --- SqlServerVersions.sql | 1 + 1 file changed, 1 insertion(+) diff --git a/SqlServerVersions.sql b/SqlServerVersions.sql index 134bd6925..0be6fb83d 100644 --- a/SqlServerVersions.sql +++ b/SqlServerVersions.sql @@ -41,6 +41,7 @@ DELETE FROM dbo.SqlServerVersions; INSERT INTO dbo.SqlServerVersions (MajorVersionNumber, MinorVersionNumber, Branch, [Url], ReleaseDate, MainstreamSupportEndDate, ExtendedSupportEndDate, MajorVersionName, MinorVersionName) VALUES + (15, 4153, 'CU12', 'https://support.microsoft.com/en-us/help/5004524', '2021-08-04', '2025-01-01', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 12'), (15, 4138, 'CU11', 'https://support.microsoft.com/en-us/help/5003249', '2021-06-10', '2025-01-01', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 11'), (15, 4123, 'CU10', 'https://support.microsoft.com/en-us/help/5001090', '2021-04-06', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 10'), (15, 4102, 'CU9', 'https://support.microsoft.com/en-us/help/5000642', '2021-02-11', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 9 '), From 61f6f8980ddaac4aae398979a859b0b2512a3851 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Tue, 14 Sep 2021 08:16:11 +0000 Subject: [PATCH 245/662] 20210914 release Bumping version numbers, dates, and install scripts. --- Install-All-Scripts.sql | 357 ++++++++++++++++++++---- Install-Core-Blitz-No-Query-Store.sql | 290 +++++++++++++++---- Install-Core-Blitz-With-Query-Store.sql | 292 +++++++++++++++---- sp_AllNightLog.sql | 2 +- sp_AllNightLog_Setup.sql | 2 +- sp_Blitz.sql | 2 +- sp_BlitzAnalysis.sql | 2 +- sp_BlitzBackups.sql | 2 +- sp_BlitzCache.sql | 2 +- sp_BlitzFirst.sql | 2 +- sp_BlitzInMemoryOLTP.sql | 2 +- sp_BlitzIndex.sql | 2 +- sp_BlitzLock.sql | 2 +- sp_BlitzQueryStore.sql | 2 +- sp_BlitzWho.sql | 2 +- sp_DatabaseRestore.sql | 2 +- sp_ineachdb.sql | 2 +- 17 files changed, 784 insertions(+), 183 deletions(-) diff --git a/Install-All-Scripts.sql b/Install-All-Scripts.sql index 89c5022d8..8744b662d 100755 --- a/Install-All-Scripts.sql +++ b/Install-All-Scripts.sql @@ -31,7 +31,7 @@ SET STATISTICS XML OFF; BEGIN; -SELECT @Version = '8.05', @VersionDate = '20210725'; +SELECT @Version = '8.06', @VersionDate = '20210914'; IF(@VersionCheckMode = 1) BEGIN @@ -1526,7 +1526,7 @@ SET STATISTICS XML OFF; BEGIN; -SELECT @Version = '8.05', @VersionDate = '20210725'; +SELECT @Version = '8.06', @VersionDate = '20210914'; IF(@VersionCheckMode = 1) BEGIN @@ -2859,7 +2859,7 @@ AS SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.05', @VersionDate = '20210725'; + SELECT @Version = '8.06', @VersionDate = '20210914'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -7163,12 +7163,12 @@ AS SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details) SELECT ' + CAST(@CurrentCheckID AS NVARCHAR(200)) + ', d.[name], ' + CAST(@CurrentPriority AS NVARCHAR(200)) + ', ''Non-Default Database Config'', ''' + @CurrentFinding + ''',''' + @CurrentURL + ''',''' + COALESCE(@CurrentDetails, 'This database setting is not the default.') + ''' FROM sys.databases d - WHERE d.database_id > 4 AND d.state <> 1 AND (d.[' + @CurrentName + '] NOT IN (0, 60) OR d.[' + @CurrentName + '] IS NULL) OPTION (RECOMPILE);'; + WHERE d.database_id > 4 AND d.state = 0 AND (d.[' + @CurrentName + '] NOT IN (0, 60) OR d.[' + @CurrentName + '] IS NULL) OPTION (RECOMPILE);'; ELSE SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details) SELECT ' + CAST(@CurrentCheckID AS NVARCHAR(200)) + ', d.[name], ' + CAST(@CurrentPriority AS NVARCHAR(200)) + ', ''Non-Default Database Config'', ''' + @CurrentFinding + ''',''' + @CurrentURL + ''',''' + COALESCE(@CurrentDetails, 'This database setting is not the default.') + ''' FROM sys.databases d - WHERE d.database_id > 4 AND d.state <> 1 AND (d.[' + @CurrentName + '] <> ' + @CurrentDefaultValue + ' OR d.[' + @CurrentName + '] IS NULL) OPTION (RECOMPILE);'; + WHERE d.database_id > 4 AND d.state = 0 AND (d.[' + @CurrentName + '] <> ' + @CurrentDefaultValue + ' OR d.[' + @CurrentName + '] IS NULL) OPTION (RECOMPILE);'; IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; @@ -10591,6 +10591,7 @@ IF @ProductVersionMajor >= 10 WHEN [T].[TraceFlag] = '1117' THEN '1117 enabled globally, which grows all files in a filegroup at the same time.' WHEN [T].[TraceFlag] = '1118' THEN '1118 enabled globally, which tries to reduce SGAM waits.' WHEN [T].[TraceFlag] = '1211' THEN '1211 enabled globally, which disables lock escalation when you least expect it. This is usually a very bad idea.' + WHEN [T].[TraceFlag] = '1204' THEN '1222 enabled globally, which captures deadlock graphs in the error log.' WHEN [T].[TraceFlag] = '1222' THEN '1222 enabled globally, which captures deadlock graphs in the error log.' WHEN [T].[TraceFlag] = '1224' THEN '1224 enabled globally, which disables lock escalation until the server has memory pressure. This is usually a very bad idea.' WHEN [T].[TraceFlag] = '1806' THEN '1806 enabled globally, which disables Instant File Initialization, causing restores and file growths to take longer. This is usually a very bad idea.' @@ -12375,7 +12376,7 @@ AS SET NOCOUNT ON; SET STATISTICS XML OFF; -SELECT @Version = '8.05', @VersionDate = '20210725'; +SELECT @Version = '8.06', @VersionDate = '20210914'; IF(@VersionCheckMode = 1) BEGIN @@ -13095,7 +13096,6 @@ SELECT [ServerName] ,[query_cost] ,[status] ,[wait_info] - ,[wait_resource] ,[top_session_waits] ,[blocking_session_id] ,[open_transaction_count] @@ -13254,7 +13254,7 @@ AS SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.05', @VersionDate = '20210725'; + SELECT @Version = '8.06', @VersionDate = '20210914'; IF(@VersionCheckMode = 1) BEGIN @@ -15035,7 +15035,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.05', @VersionDate = '20210725'; +SELECT @Version = '8.06', @VersionDate = '20210914'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -22295,7 +22295,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.05', @VersionDate = '20210725'; +SELECT @Version = '8.06', @VersionDate = '20210914'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -22375,6 +22375,7 @@ DECLARE @DaysUptimeInsertValue NVARCHAR(256); DECLARE @DatabaseToIgnore NVARCHAR(MAX); DECLARE @ColumnList NVARCHAR(MAX); DECLARE @PartitionCount INT; +DECLARE @OptimizeForSequentialKey BIT = 0; /* Let's get @SortOrder set to lower case here for comparisons later */ SET @SortOrder = REPLACE(LOWER(@SortOrder), N' ', N'_'); @@ -22387,6 +22388,20 @@ SET @FilterMB=250; SELECT @ScriptVersionName = 'sp_BlitzIndex(TM) v' + @Version + ' - ' + DATENAME(MM, @VersionDate) + ' ' + RIGHT('0'+DATENAME(DD, @VersionDate),2) + ', ' + DATENAME(YY, @VersionDate); SET @IgnoreDatabases = REPLACE(REPLACE(LTRIM(RTRIM(@IgnoreDatabases)), CHAR(10), ''), CHAR(13), ''); +SELECT + @OptimizeForSequentialKey = + CASE WHEN EXISTS + ( + SELECT + 1/0 + FROM sys.all_columns AS ac + WHERE ac.object_id = OBJECT_ID('sys.indexes') + AND ac.name = N'optimize_for_sequential_key' + ) + THEN 1 + ELSE 0 + END; + RAISERROR(N'Starting run. %s', 0,1, @ScriptVersionName) WITH NOWAIT; IF(@OutputType NOT IN ('TABLE','NONE')) @@ -22434,6 +22449,9 @@ IF OBJECT_ID('tempdb..#MissingIndexes') IS NOT NULL IF OBJECT_ID('tempdb..#ForeignKeys') IS NOT NULL DROP TABLE #ForeignKeys; +IF OBJECT_ID('tempdb..#UnindexedForeignKeys') IS NOT NULL + DROP TABLE #UnindexedForeignKeys; + IF OBJECT_ID('tempdb..#BlitzIndexResults') IS NOT NULL DROP TABLE #BlitzIndexResults; @@ -22507,10 +22525,12 @@ IF OBJECT_ID('tempdb..#Ignore_Databases') IS NOT NULL count_included_columns INT NULL , partition_key_column_name NVARCHAR(MAX) NULL, filter_definition NVARCHAR(MAX) NOT NULL , - is_indexed_view BIT NOT NULL , + optimize_for_sequential_key BIT NULL, + is_indexed_view BIT NOT NULL , is_unique BIT NOT NULL , is_primary_key BIT NOT NULL , - is_XML BIT NOT NULL, + is_unique_constraint BIT NOT NULL , + is_XML bit NOT NULL, is_spatial BIT NOT NULL, is_NC_columnstore BIT NOT NULL, is_CX_columnstore BIT NOT NULL, @@ -22558,7 +22578,8 @@ IF OBJECT_ID('tempdb..#Ignore_Databases') IS NOT NULL ELSE N'' END + CASE WHEN is_in_memory_oltp = 1 THEN N'[IN-MEMORY] ' ELSE N'' END + CASE WHEN is_disabled = 1 THEN N'[DISABLED] ' ELSE N'' END + CASE WHEN is_hypothetical = 1 THEN N'[HYPOTHETICAL] ' - ELSE N'' END + CASE WHEN is_unique = 1 AND is_primary_key = 0 THEN N'[UNIQUE] ' + ELSE N'' END + CASE WHEN is_unique = 1 AND is_primary_key = 0 AND is_unique_constraint = 0 THEN N'[UNIQUE] ' + ELSE N'' END + CASE WHEN is_unique_constraint = 1 AND is_primary_key = 0 THEN N'[UNIQUE CONSTRAINT] ' ELSE N'' END + CASE WHEN count_key_columns > 0 THEN N'[' + CAST(count_key_columns AS NVARCHAR(10)) + N' KEY' + CASE WHEN count_key_columns > 1 THEN N'S' ELSE N'' END @@ -22878,6 +22899,18 @@ IF OBJECT_ID('tempdb..#Ignore_Databases') IS NOT NULL update_referential_action_desc NVARCHAR(16), delete_referential_action_desc NVARCHAR(60) ); + + CREATE TABLE #UnindexedForeignKeys + ( + [database_id] INT NOT NULL, + [database_name] NVARCHAR(128) NOT NULL , + [schema_name] NVARCHAR(128) NOT NULL , + foreign_key_name NVARCHAR(256), + parent_object_name NVARCHAR(256), + parent_object_id INT, + referenced_object_name NVARCHAR(256), + referenced_object_id INT + ); CREATE TABLE #IndexCreateTsql ( index_sanity_id INT NOT NULL, @@ -23522,7 +23555,8 @@ BEGIN TRY CASE WHEN so.[type] = CAST(''V'' AS CHAR(2)) THEN 1 ELSE 0 END, si.is_unique, si.is_primary_key, - CASE when si.type = 3 THEN 1 ELSE 0 END AS is_XML, + si.is_unique_constraint, + CASE when si.type = 3 THEN 1 ELSE 0 END AS is_XML, CASE when si.type = 4 THEN 1 ELSE 0 END AS is_spatial, CASE when si.type = 6 THEN 1 ELSE 0 END AS is_NC_columnstore, CASE when si.type = 5 then 1 else 0 end as is_CX_columnstore, @@ -23534,8 +23568,14 @@ BEGIN TRY + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N' CASE WHEN si.filter_definition IS NOT NULL THEN si.filter_definition ELSE N'''' - END AS filter_definition' ELSE N''''' AS filter_definition' END + N' - , ISNULL(us.user_seeks, 0), + END AS filter_definition' ELSE N''''' AS filter_definition' END + + CASE + WHEN @OptimizeForSequentialKey = 1 + THEN N', si.optimize_for_sequential_key' + ELSE N', CONVERT(BIT, NULL) AS optimize_for_sequential_key' + END + + N', + ISNULL(us.user_seeks, 0), ISNULL(us.user_scans, 0), ISNULL(us.user_lookups, 0), ISNULL(us.user_updates, 0), @@ -23582,8 +23622,8 @@ BEGIN TRY PRINT SUBSTRING(@dsql, 36000, 40000); END; INSERT #IndexSanity ( [database_id], [object_id], [index_id], [index_type], [database_name], [schema_name], [object_name], - index_name, is_indexed_view, is_unique, is_primary_key, is_XML, is_spatial, is_NC_columnstore, is_CX_columnstore, is_in_memory_oltp, - is_disabled, is_hypothetical, is_padded, fill_factor, filter_definition, user_seeks, user_scans, + index_name, is_indexed_view, is_unique, is_primary_key, is_unique_constraint, is_XML, is_spatial, is_NC_columnstore, is_CX_columnstore, is_in_memory_oltp, + is_disabled, is_hypothetical, is_padded, fill_factor, filter_definition, [optimize_for_sequential_key], user_seeks, user_scans, user_lookups, user_updates, last_user_seek, last_user_scan, last_user_lookup, last_user_update, create_date, modify_date ) EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; @@ -24039,6 +24079,77 @@ BEGIN TRY EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; + SET @dsql = N' + SELECT + DB_ID(N' + QUOTENAME(@DatabaseName,'''') + N') AS [database_id], + @i_DatabaseName AS database_name, + foreign_key_schema = + s.name, + foreign_key_name = + fk.name, + foreign_key_table = + OBJECT_NAME(fk.parent_object_id, DB_ID(N' + QUOTENAME(@DatabaseName,'''') + N')), + fk.parent_object_id, + foreign_key_referenced_table = + OBJECT_NAME(fk.referenced_object_id, DB_ID(N' + QUOTENAME(@DatabaseName,'''') + N')), + fk.referenced_object_id + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.foreign_keys fk + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s + ON s.schema_id = fk.schema_id + WHERE fk.is_disabled = 0 + AND EXISTS + ( + SELECT + 1/0 + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.foreign_key_columns fkc + WHERE fkc.constraint_object_id = fk.object_id + AND NOT EXISTS + ( + SELECT + 1/0 + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.index_columns ic + WHERE ic.object_id = fkc.parent_object_id + AND ic.column_id = fkc.parent_column_id + AND ic.index_column_id = fkc.constraint_column_id + ) + ) + OPTION (RECOMPILE);' + IF @dsql IS NULL + RAISERROR('@dsql is null',16,1); + + RAISERROR (N'Inserting data into #ForeignKeys',0,1) WITH NOWAIT; + IF @Debug = 1 + BEGIN + PRINT SUBSTRING(@dsql, 0, 4000); + PRINT SUBSTRING(@dsql, 4000, 8000); + PRINT SUBSTRING(@dsql, 8000, 12000); + PRINT SUBSTRING(@dsql, 12000, 16000); + PRINT SUBSTRING(@dsql, 16000, 20000); + PRINT SUBSTRING(@dsql, 20000, 24000); + PRINT SUBSTRING(@dsql, 24000, 28000); + PRINT SUBSTRING(@dsql, 28000, 32000); + PRINT SUBSTRING(@dsql, 32000, 36000); + PRINT SUBSTRING(@dsql, 36000, 40000); + END; + + INSERT + #UnindexedForeignKeys + ( + database_id, + database_name, + schema_name, + foreign_key_name, + parent_object_name, + parent_object_id, + referenced_object_name, + referenced_object_id + ) + EXEC sys.sp_executesql + @dsql, + N'@i_DatabaseName sysname', + @DatabaseName; + + IF @SkipStatistics = 0 /* AND DB_NAME() = @DatabaseName /* Can only get stats in the current database - see https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/1947 */ */ BEGIN IF ((PARSENAME(@SQLServerProductVersion, 4) >= 12) @@ -24651,7 +24762,14 @@ SELECT N'] PRIMARY KEY ' + CASE WHEN index_id=1 THEN N'CLUSTERED (' ELSE N'(' END + key_column_names_with_sort_order_no_types + N' )' - WHEN is_CX_columnstore= 1 THEN + WHEN is_unique_constraint = 1 AND is_primary_key = 0 + THEN + N'ALTER TABLE ' + QUOTENAME([database_name]) + N'.' + QUOTENAME([schema_name]) + + N'.' + QUOTENAME([object_name]) + + N' ADD CONSTRAINT [' + + index_name + + N'] UNIQUE' + WHEN is_CX_columnstore= 1 THEN N'CREATE CLUSTERED COLUMNSTORE INDEX ' + QUOTENAME(index_name) + N' on ' + QUOTENAME([database_name]) + N'.' + QUOTENAME([schema_name]) + N'.' + QUOTENAME([object_name]) ELSE /*Else not a PK or cx columnstore */ N'CREATE ' + @@ -24759,12 +24877,14 @@ FROM #IndexSanity si IF @Debug = 1 BEGIN + SELECT '#BlitzIndexResults' AS table_name, * FROM #BlitzIndexResults AS bir; SELECT '#IndexSanity' AS table_name, * FROM #IndexSanity; SELECT '#IndexPartitionSanity' AS table_name, * FROM #IndexPartitionSanity; SELECT '#IndexSanitySize' AS table_name, * FROM #IndexSanitySize; SELECT '#IndexColumns' AS table_name, * FROM #IndexColumns; SELECT '#MissingIndexes' AS table_name, * FROM #MissingIndexes; SELECT '#ForeignKeys' AS table_name, * FROM #ForeignKeys; + SELECT '#UnindexedForeignKeys' AS table_name, * FROM #UnindexedForeignKeys; SELECT '#BlitzIndexResults' AS table_name, * FROM #BlitzIndexResults; SELECT '#IndexCreateTsql' AS table_name, * FROM #IndexCreateTsql; SELECT '#DatabaseList' AS table_name, * FROM #DatabaseList; @@ -24825,6 +24945,9 @@ BEGIN ct.create_tsql, CASE WHEN s.is_primary_key = 1 AND s.index_definition <> '[HEAP]' + THEN N'--ALTER TABLE ' + QUOTENAME(s.[database_name]) + N'.' + QUOTENAME(s.[schema_name]) + N'.' + QUOTENAME(s.[object_name]) + + N' DROP CONSTRAINT ' + QUOTENAME(s.index_name) + N';' + WHEN s.is_primary_key = 0 AND is_unique_constraint = 1 AND s.index_definition <> '[HEAP]' THEN N'--ALTER TABLE ' + QUOTENAME(s.[database_name]) + N'.' + QUOTENAME(s.[schema_name]) + N'.' + QUOTENAME(s.[object_name]) + N' DROP CONSTRAINT ' + QUOTENAME(s.index_name) + N';' WHEN s.is_primary_key = 0 AND s.index_definition <> '[HEAP]' @@ -26381,7 +26504,6 @@ BEGIN; OPTION ( RECOMPILE ); RAISERROR(N'check_id 33: Potential filtered indexes based on column names.', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, secret_columns, index_usage_summary, index_size_summary ) SELECT 33 AS check_id, @@ -26757,15 +26879,14 @@ BEGIN; N'Cascading Updates or Deletes' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, - N'Foreign Key ' + foreign_key_name + + N'Foreign Key ' + QUOTENAME(foreign_key_name) + N' on ' + QUOTENAME(parent_object_name) + N'(' + LTRIM(parent_fk_columns) + N')' + N' referencing ' + QUOTENAME(referenced_object_name) + N'(' + LTRIM(referenced_fk_columns) + N')' + N' has settings:' + CASE [delete_referential_action_desc] WHEN N'NO_ACTION' THEN N'' ELSE N' ON DELETE ' +[delete_referential_action_desc] END + CASE [update_referential_action_desc] WHEN N'NO_ACTION' THEN N'' ELSE N' ON UPDATE ' + [update_referential_action_desc] END AS details, - [fk].[database_name] - AS index_definition, + [fk].[database_name] AS index_definition, N'N/A' AS secret_columns, N'N/A' AS index_usage_summary, N'N/A' AS index_size_summary, @@ -26776,6 +26897,29 @@ BEGIN; OR [update_referential_action_desc] <> N'NO_ACTION') OPTION ( RECOMPILE ); + RAISERROR(N'check_id 72: Unindexed foreign keys.', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary, more_info ) + SELECT 72 AS check_id, + NULL AS index_sanity_id, + 150 AS Priority, + N'Abnormal Psychology' AS findings_group, + N'Unindexed Foreign Keys' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, + N'Foreign Key ' + QUOTENAME(foreign_key_name) + + N' on ' + QUOTENAME(parent_object_name) + N'' + + N' referencing ' + QUOTENAME(referenced_object_name) + N'' + + N' does not appear to have a supporting index.' AS details, + N'N/A' AS index_definition, + N'N/A' AS secret_columns, + N'N/A' AS index_usage_summary, + N'N/A' AS index_size_summary, + (SELECT TOP 1 more_info FROM #IndexSanity i WHERE i.object_id=fk.parent_object_id AND i.database_id = fk.database_id AND i.schema_name = fk.schema_name) + AS more_info + FROM #UnindexedForeignKeys AS fk + OPTION ( RECOMPILE ); + RAISERROR(N'check_id 73: In-Memory OLTP', 0,1) WITH NOWAIT; INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, @@ -26965,6 +27109,24 @@ BEGIN; FROM #TemporalTables AS t OPTION ( RECOMPILE ); + RAISERROR(N'check_id 121: Optimized For Sequental Keys.', 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 121 AS check_id, + 200 AS Priority, + 'Medicated Indexes' AS findings_group, + 'Optimized For Sequential Keys', + i.database_name, + '' AS URL, + 'The table ' + QUOTENAME(i.schema_name) + '.' + QUOTENAME(i.object_name) + ' is optimized for sequential keys.' AS details, + '' AS index_definition, + 'N/A' AS secret_columns, + 'N/A' AS index_usage_summary, + 'N/A' AS index_size_summary + FROM #IndexSanity AS i + WHERE i.optimize_for_sequential_key = 1 + OPTION ( RECOMPILE ); @@ -27325,7 +27487,8 @@ ELSE IF (@Mode=1) /*Summarize*/ [partition_key_column_name] NVARCHAR(MAX), [filter_definition] NVARCHAR(MAX), [is_indexed_view] BIT, - [is_primary_key] BIT, + [is_primary_key] BIT, + [is_unique_constraint] BIT, [is_XML] BIT, [is_spatial] BIT, [is_NC_columnstore] BIT, @@ -27435,7 +27598,8 @@ ELSE IF (@Mode=1) /*Summarize*/ [partition_key_column_name], [filter_definition], [is_indexed_view], - [is_primary_key], + [is_primary_key], + [is_unique_constraint], [is_XML], [is_spatial], [is_NC_columnstore], @@ -27498,6 +27662,9 @@ ELSE IF (@Mode=1) /*Summarize*/ WHEN i.is_primary_key = 1 AND i.index_definition <> ''[HEAP]'' THEN N''--ALTER TABLE '' + QUOTENAME(i.[database_name]) + N''.'' + QUOTENAME(i.[schema_name]) + N''.'' + QUOTENAME(i.[object_name]) + N'' DROP CONSTRAINT '' + QUOTENAME(i.index_name) + N'';'' + WHEN i.is_primary_key = 0 AND i.is_unique_constraint = 1 AND i.index_definition <> ''[HEAP]'' + THEN N''--ALTER TABLE '' + QUOTENAME(i.[database_name]) + N''.'' + QUOTENAME(i.[schema_name]) + N''.'' + QUOTENAME(i.[object_name]) + + N'' DROP CONSTRAINT '' + QUOTENAME(i.index_name) + N'';'' WHEN i.is_primary_key = 0 AND i.index_definition <> ''[HEAP]'' THEN N''--DROP INDEX ''+ QUOTENAME(i.index_name) + N'' ON '' + QUOTENAME(i.[database_name]) + N''.'' + QUOTENAME(i.[schema_name]) + N''.'' + QUOTENAME(i.[object_name]) + N'';'' @@ -27522,6 +27689,7 @@ ELSE IF (@Mode=1) /*Summarize*/ ISNULL(filter_definition, '''') AS [Filter Definition], is_indexed_view AS [Is Indexed View], is_primary_key AS [Is Primary Key], + is_unique_constraint AS [Is Unique Constraint], is_XML AS [Is XML], is_spatial AS [Is Spatial], is_NC_columnstore AS [Is NC Columnstore], @@ -27615,6 +27783,7 @@ ELSE IF (@Mode=1) /*Summarize*/ ISNULL(filter_definition, '') AS [Filter Definition], is_indexed_view AS [Is Indexed View], is_primary_key AS [Is Primary Key], + is_unique_constraint AS [Is Unique Constraint] , is_XML AS [Is XML], is_spatial AS [Is Spatial], is_NC_columnstore AS [Is NC Columnstore], @@ -27667,6 +27836,9 @@ ELSE IF (@Mode=1) /*Summarize*/ WHEN i.is_primary_key = 1 AND i.index_definition <> '[HEAP]' THEN N'--ALTER TABLE ' + QUOTENAME(i.[database_name]) + N'.' + QUOTENAME(i.[schema_name]) + N'.' + QUOTENAME(i.[object_name]) + N' DROP CONSTRAINT ' + QUOTENAME(i.index_name) + N';' + WHEN i.is_primary_key = 0 AND i.is_unique_constraint = 1 AND i.index_definition <> '[HEAP]' + THEN N'--ALTER TABLE ' + QUOTENAME(i.[database_name]) + N'.' + QUOTENAME(i.[schema_name]) + N'.' + QUOTENAME(i.[object_name]) + + N' DROP CONSTRAINT ' + QUOTENAME(i.index_name) + N';' WHEN i.is_primary_key = 0 AND i.index_definition <> '[HEAP]' THEN N'--DROP INDEX '+ QUOTENAME(i.index_name) + N' ON ' + QUOTENAME(i.[database_name]) + N'.' + QUOTENAME(i.[schema_name]) + N'.' + QUOTENAME(i.[object_name]) + N';' @@ -27843,7 +28015,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.05', @VersionDate = '20210725'; +SELECT @Version = '8.06', @VersionDate = '20210914'; IF(@VersionCheckMode = 1) @@ -29650,7 +29822,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.05', @VersionDate = '20210725'; +SELECT @Version = '8.06', @VersionDate = '20210914'; IF(@VersionCheckMode = 1) BEGIN RETURN; @@ -35380,7 +35552,7 @@ BEGIN SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.05', @VersionDate = '20210725'; + SELECT @Version = '8.06', @VersionDate = '20210914'; IF(@VersionCheckMode = 1) BEGIN @@ -35635,6 +35807,13 @@ IF @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @Output ALTER TABLE ' + @ObjectFullName + N' ADD outer_command NVARCHAR(4000) NULL;'; EXEC(@StringToExecute); + /* If the table doesn't have the new wait_resource column, add it. See Github #2970. */ + SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableName; + SET @StringToExecute = N'IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns + WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''wait_resource'') + ALTER TABLE ' + @ObjectFullName + N' ADD wait_resource NVARCHAR(MAX) NULL;'; + EXEC(@StringToExecute); + /* Delete history older than @OutputTableRetentionDays */ SET @OutputTableCleanupDate = CAST( (DATEADD(DAY, -1 * @OutputTableRetentionDays, GETDATE() ) ) AS DATE); SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' @@ -36750,6 +36929,7 @@ ALTER PROCEDURE [dbo].[sp_DatabaseRestore] @SimpleFolderEnumeration BIT = 0, @SkipBackupsAlreadyInMsdb BIT = 0, @DatabaseOwner sysname = NULL, + @SetTrustworthyON BIT = 0, @Execute CHAR(1) = Y, @Debug INT = 0, @Help BIT = 0, @@ -36762,7 +36942,7 @@ SET STATISTICS XML OFF; /*Versioning details*/ -SELECT @Version = '8.05', @VersionDate = '20210725'; +SELECT @Version = '8.06', @VersionDate = '20210914'; IF(@VersionCheckMode = 1) BEGIN @@ -37495,8 +37675,18 @@ BEGIN IF @sql IS NULL PRINT '@sql is NULL for SINGLE_USER'; PRINT @sql; END; - IF @Debug IN (0, 1) AND @Execute = 'Y' AND DATABASEPROPERTYEX(@RestoreDatabaseName,'STATUS') != 'RESTORING' - EXECUTE @sql = [dbo].[CommandExecute] @DatabaseContext=N'master', @Command = @sql, @CommandType = 'ALTER DATABASE SINGLE_USER', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y'; + IF @Debug IN (0, 1) AND @Execute = 'Y' + BEGIN + IF DATABASEPROPERTYEX(@UnquotedRestoreDatabaseName,'STATUS') != 'RESTORING' + BEGIN + EXECUTE @sql = [dbo].[CommandExecute] @DatabaseContext=N'master', @Command = @sql, @CommandType = 'ALTER DATABASE SINGLE_USER', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y'; + END + ELSE IF @Debug = 1 + BEGIN + IF DATABASEPROPERTYEX(@UnquotedRestoreDatabaseName,'STATUS') IS NULL PRINT 'Unable to retrieve STATUS from "' + @UnquotedRestoreDatabaseName + '" database. Skipping setting database to SINGLE_USER'; + ELSE IF DATABASEPROPERTYEX(@UnquotedRestoreDatabaseName,'STATUS') = 'RESTORING' PRINT @UnquotedRestoreDatabaseName + ' database STATUS is RESTORING. Skiping setting database to SINGLE_USER'; + END + END END IF @ExistingDBAction IN (2, 3) BEGIN @@ -37540,8 +37730,18 @@ BEGIN IF @sql IS NULL PRINT '@sql is NULL for Offline database'; PRINT @sql; END; - IF @Debug IN (0, 1) AND @Execute = 'Y' AND DATABASEPROPERTYEX(@RestoreDatabaseName,'STATUS') != 'RESTORING' - EXECUTE @sql = [dbo].[CommandExecute] @DatabaseContext=N'master', @Command = @sql, @CommandType = 'OFFLINE DATABASE', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y'; + IF @Debug IN (0, 1) AND @Execute = 'Y' + BEGIN + IF DATABASEPROPERTYEX(@UnquotedRestoreDatabaseName,'STATUS') != 'RESTORING' + BEGIN + EXECUTE @sql = [dbo].[CommandExecute] @DatabaseContext=N'master', @Command = @sql, @CommandType = 'OFFLINE DATABASE', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y'; + END + ELSE IF @Debug = 1 + BEGIN + IF DATABASEPROPERTYEX(@UnquotedRestoreDatabaseName,'STATUS') IS NULL PRINT 'Unable to retrieve STATUS from "' + @UnquotedRestoreDatabaseName + '" database. Skipping setting database OFFLINE'; + ELSE IF DATABASEPROPERTYEX(@UnquotedRestoreDatabaseName,'STATUS') = 'RESTORING' PRINT @UnquotedRestoreDatabaseName + ' database STATUS is RESTORING. Skiping setting database OFFLINE'; + END + END END; END ELSE @@ -38171,6 +38371,34 @@ IF @DatabaseOwner IS NOT NULL END END; + IF @SetTrustworthyON = 1 + BEGIN + IF @RunRecovery = 1 + BEGIN + IF IS_SRVROLEMEMBER('sysadmin') = 1 + BEGIN + SET @sql = N'ALTER DATABASE ' + @RestoreDatabaseName + N' SET TRUSTWORTHY ON;'; + + IF @Debug = 1 OR @Execute = 'N' + BEGIN + IF @sql IS NULL PRINT '@sql is NULL for SET TRUSTWORTHY ON'; + PRINT @sql; + END; + + IF @Debug IN (0, 1) AND @Execute = 'Y' + EXECUTE @sql = [dbo].[CommandExecute] @DatabaseContext=N'master', @Command = @sql, @CommandType = 'ALTER DATABASE', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y'; + END + ELSE + BEGIN + PRINT 'Current user''s login is NOT a member of the sysadmin role. Database TRUSTWORHY bit has not been enabled.'; + END + END + ELSE + BEGIN + PRINT @RestoreDatabaseName + ' is still in Recovery, so we are unable to enable the TRUSTWORHY bit.'; + END + END; + -- If test restore then blow the database away (be careful) IF @TestRestore = 1 BEGIN @@ -38229,7 +38457,7 @@ BEGIN SET NOCOUNT ON; SET STATISTICS XML OFF; - SELECT @Version = '8.05', @VersionDate = '20210725'; + SELECT @Version = '8.06', @VersionDate = '20210914'; IF(@VersionCheckMode = 1) BEGIN @@ -38575,6 +38803,7 @@ DELETE FROM dbo.SqlServerVersions; INSERT INTO dbo.SqlServerVersions (MajorVersionNumber, MinorVersionNumber, Branch, [Url], ReleaseDate, MainstreamSupportEndDate, ExtendedSupportEndDate, MajorVersionName, MinorVersionName) VALUES + (15, 4153, 'CU12', 'https://support.microsoft.com/en-us/help/5004524', '2021-08-04', '2025-01-01', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 12'), (15, 4138, 'CU11', 'https://support.microsoft.com/en-us/help/5003249', '2021-06-10', '2025-01-01', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 11'), (15, 4123, 'CU10', 'https://support.microsoft.com/en-us/help/5001090', '2021-04-06', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 10'), (15, 4102, 'CU9', 'https://support.microsoft.com/en-us/help/5000642', '2021-02-11', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 9 '), @@ -38589,7 +38818,7 @@ VALUES (15, 4003, 'CU1', 'https://support.microsoft.com/en-us/help/4527376', '2020-01-07', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 1 '), (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 '), - (14, 3410, 'RTM CU25', 'https://support.microsoft.com/en-us/help/5003830', '2021-07-12', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 25'), + (14, 3401, 'RTM CU25', 'https://support.microsoft.com/en-us/help/5003830', '2021-07-12', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 25'), (14, 3391, 'RTM CU24', 'https://support.microsoft.com/en-us/help/5001228', '2021-05-10', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 24'), (14, 3381, 'RTM CU23', 'https://support.microsoft.com/en-us/help/5000685', '2021-02-25', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 23'), (14, 3370, 'RTM CU22 GDR', 'https://support.microsoft.com/en-us/help/4583457', '2021-01-12', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 22 GDR'), @@ -38969,7 +39198,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.05', @VersionDate = '20210725'; +SELECT @Version = '8.06', @VersionDate = '20210914'; IF(@VersionCheckMode = 1) BEGIN @@ -40440,28 +40669,32 @@ BEGIN 'https://www.brentozar.com/askbrent/backups/' AS URL, 'Backup of ' + DB_NAME(db.resource_database_id) + ' database (' + (SELECT CAST(CAST(SUM(size * 8.0 / 1024 / 1024) AS BIGINT) AS NVARCHAR) FROM #MasterFiles WHERE database_id = db.resource_database_id) + 'GB) ' + @LineFeed + CAST(r.percent_complete AS NVARCHAR(100)) + '% complete, has been running since ' + CAST(r.start_time AS NVARCHAR(100)) + '. ' + @LineFeed - + CASE WHEN COALESCE(s.nt_user_name, s.login_name) IS NOT NULL THEN (' Login: ' + COALESCE(s.nt_user_name, s.login_name) + ' ') ELSE '' END AS Details, + + CASE WHEN COALESCE(s.nt_username, s.loginame) IS NOT NULL THEN (' Login: ' + COALESCE(s.nt_username, s.loginame) + ' ') ELSE '' END AS Details, 'KILL ' + CAST(r.session_id AS NVARCHAR(100)) + ';' AS HowToStopIt, pl.query_plan AS QueryPlan, r.start_time AS StartTime, - s.login_name AS LoginName, - s.nt_user_name AS NTUserName, + s.loginame AS LoginName, + s.nt_username AS NTUserName, s.[program_name] AS ProgramName, - s.[host_name] AS HostName, + s.[hostname] AS HostName, db.[resource_database_id] AS DatabaseID, DB_NAME(db.resource_database_id) AS DatabaseName, 0 AS OpenTransactionCount, r.query_hash FROM sys.dm_exec_requests r INNER JOIN sys.dm_exec_connections c ON r.session_id = c.session_id - INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id - INNER JOIN ( - SELECT DISTINCT request_session_id, resource_database_id - FROM sys.dm_tran_locks - WHERE resource_type = N'DATABASE' - AND request_mode = N'S' - AND request_status = N'GRANT' - AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.session_id = db.request_session_id AND s.database_id = db.resource_database_id + INNER JOIN sys.sysprocesses AS s ON r.session_id = s.spid AND s.ecid = 0 + INNER JOIN + ( + SELECT DISTINCT + t.request_session_id, + t.resource_database_id + FROM sys.dm_tran_locks AS t + WHERE t.resource_type = N'DATABASE' + AND t.request_mode = N'S' + AND t.request_status = N'GRANT' + AND t.request_owner_type = N'SHARED_TRANSACTION_WORKSPACE' + ) AS db ON s.spid = db.request_session_id AND s.dbid = db.resource_database_id CROSS APPLY sys.dm_exec_query_plan(r.plan_handle) pl WHERE r.command LIKE 'BACKUP%' AND r.start_time <= DATEADD(minute, -5, GETDATE()) @@ -40678,30 +40911,30 @@ BEGIN 'Query Problems' AS FindingGroup, 'Sleeping Query with Open Transactions' AS Finding, 'https://www.brentozar.com/askbrent/sleeping-query-with-open-transactions/' AS URL, - 'Database: ' + DB_NAME(db.resource_database_id) + @LineFeed + 'Host: ' + s.[host_name] + @LineFeed + 'Program: ' + s.[program_name] + @LineFeed + 'Asleep with open transactions and locks since ' + CAST(s.last_request_end_time AS NVARCHAR(100)) + '. ' AS Details, - 'KILL ' + CAST(s.session_id AS NVARCHAR(100)) + ';' AS HowToStopIt, - s.last_request_start_time AS StartTime, - s.login_name AS LoginName, - s.nt_user_name AS NTUserName, + 'Database: ' + DB_NAME(db.resource_database_id) + @LineFeed + 'Host: ' + s.hostname + @LineFeed + 'Program: ' + s.[program_name] + @LineFeed + 'Asleep with open transactions and locks since ' + CAST(s.last_batch AS NVARCHAR(100)) + '. ' AS Details, + 'KILL ' + CAST(s.spid AS NVARCHAR(100)) + ';' AS HowToStopIt, + s.last_batch AS StartTime, + s.loginame AS LoginName, + s.nt_username AS NTUserName, s.[program_name] AS ProgramName, - s.[host_name] AS HostName, + s.hostname AS HostName, db.[resource_database_id] AS DatabaseID, DB_NAME(db.resource_database_id) AS DatabaseName, (SELECT TOP 1 [text] FROM sys.dm_exec_sql_text(c.most_recent_sql_handle)) AS QueryText, - s.open_transaction_count AS OpenTransactionCount - FROM sys.dm_exec_sessions s - INNER JOIN sys.dm_exec_connections c ON s.session_id = c.session_id + s.open_tran AS OpenTransactionCount + FROM sys.sysprocesses s + INNER JOIN sys.dm_exec_connections c ON s.spid = c.session_id INNER JOIN ( SELECT DISTINCT request_session_id, resource_database_id FROM sys.dm_tran_locks WHERE resource_type = N'DATABASE' AND request_mode = N'S' AND request_status = N'GRANT' - AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.session_id = db.request_session_id + AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.spid = db.request_session_id WHERE s.status = 'sleeping' - AND s.open_transaction_count > 0 - AND s.last_request_end_time < DATEADD(ss, -10, SYSDATETIME()) - AND EXISTS(SELECT * FROM sys.dm_tran_locks WHERE request_session_id = s.session_id + AND s.open_tran > 0 + AND s.last_batch < DATEADD(ss, -10, SYSDATETIME()) + AND EXISTS(SELECT * FROM sys.dm_tran_locks WHERE request_session_id = s.spid AND NOT (resource_type = N'DATABASE' AND request_mode = N'S' AND request_status = N'GRANT' AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE')); END @@ -42049,7 +42282,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, This has to be done as dynamic SQL because we have to execute OBJECT_NAME inside TempDB. */ IF @@ROWCOUNT > 0 BEGIN - SET @StringToExecute = N'USE tempdb; + SET @StringToExecute = N' INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) SELECT TOP 10 29 AS CheckID, 40 AS Priority, diff --git a/Install-Core-Blitz-No-Query-Store.sql b/Install-Core-Blitz-No-Query-Store.sql index cc4bbf47c..55c4660af 100755 --- a/Install-Core-Blitz-No-Query-Store.sql +++ b/Install-Core-Blitz-No-Query-Store.sql @@ -38,7 +38,7 @@ AS SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.05', @VersionDate = '20210725'; + SELECT @Version = '8.06', @VersionDate = '20210914'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -4342,12 +4342,12 @@ AS SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details) SELECT ' + CAST(@CurrentCheckID AS NVARCHAR(200)) + ', d.[name], ' + CAST(@CurrentPriority AS NVARCHAR(200)) + ', ''Non-Default Database Config'', ''' + @CurrentFinding + ''',''' + @CurrentURL + ''',''' + COALESCE(@CurrentDetails, 'This database setting is not the default.') + ''' FROM sys.databases d - WHERE d.database_id > 4 AND d.state <> 1 AND (d.[' + @CurrentName + '] NOT IN (0, 60) OR d.[' + @CurrentName + '] IS NULL) OPTION (RECOMPILE);'; + WHERE d.database_id > 4 AND d.state = 0 AND (d.[' + @CurrentName + '] NOT IN (0, 60) OR d.[' + @CurrentName + '] IS NULL) OPTION (RECOMPILE);'; ELSE SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details) SELECT ' + CAST(@CurrentCheckID AS NVARCHAR(200)) + ', d.[name], ' + CAST(@CurrentPriority AS NVARCHAR(200)) + ', ''Non-Default Database Config'', ''' + @CurrentFinding + ''',''' + @CurrentURL + ''',''' + COALESCE(@CurrentDetails, 'This database setting is not the default.') + ''' FROM sys.databases d - WHERE d.database_id > 4 AND d.state <> 1 AND (d.[' + @CurrentName + '] <> ' + @CurrentDefaultValue + ' OR d.[' + @CurrentName + '] IS NULL) OPTION (RECOMPILE);'; + WHERE d.database_id > 4 AND d.state = 0 AND (d.[' + @CurrentName + '] <> ' + @CurrentDefaultValue + ' OR d.[' + @CurrentName + '] IS NULL) OPTION (RECOMPILE);'; IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; @@ -7770,6 +7770,7 @@ IF @ProductVersionMajor >= 10 WHEN [T].[TraceFlag] = '1117' THEN '1117 enabled globally, which grows all files in a filegroup at the same time.' WHEN [T].[TraceFlag] = '1118' THEN '1118 enabled globally, which tries to reduce SGAM waits.' WHEN [T].[TraceFlag] = '1211' THEN '1211 enabled globally, which disables lock escalation when you least expect it. This is usually a very bad idea.' + WHEN [T].[TraceFlag] = '1204' THEN '1222 enabled globally, which captures deadlock graphs in the error log.' WHEN [T].[TraceFlag] = '1222' THEN '1222 enabled globally, which captures deadlock graphs in the error log.' WHEN [T].[TraceFlag] = '1224' THEN '1224 enabled globally, which disables lock escalation until the server has memory pressure. This is usually a very bad idea.' WHEN [T].[TraceFlag] = '1806' THEN '1806 enabled globally, which disables Instant File Initialization, causing restores and file growths to take longer. This is usually a very bad idea.' @@ -9554,7 +9555,7 @@ AS SET NOCOUNT ON; SET STATISTICS XML OFF; -SELECT @Version = '8.05', @VersionDate = '20210725'; +SELECT @Version = '8.06', @VersionDate = '20210914'; IF(@VersionCheckMode = 1) BEGIN @@ -10274,7 +10275,6 @@ SELECT [ServerName] ,[query_cost] ,[status] ,[wait_info] - ,[wait_resource] ,[top_session_waits] ,[blocking_session_id] ,[open_transaction_count] @@ -10433,7 +10433,7 @@ AS SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.05', @VersionDate = '20210725'; + SELECT @Version = '8.06', @VersionDate = '20210914'; IF(@VersionCheckMode = 1) BEGIN @@ -12214,7 +12214,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.05', @VersionDate = '20210725'; +SELECT @Version = '8.06', @VersionDate = '20210914'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -19474,7 +19474,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.05', @VersionDate = '20210725'; +SELECT @Version = '8.06', @VersionDate = '20210914'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -19554,6 +19554,7 @@ DECLARE @DaysUptimeInsertValue NVARCHAR(256); DECLARE @DatabaseToIgnore NVARCHAR(MAX); DECLARE @ColumnList NVARCHAR(MAX); DECLARE @PartitionCount INT; +DECLARE @OptimizeForSequentialKey BIT = 0; /* Let's get @SortOrder set to lower case here for comparisons later */ SET @SortOrder = REPLACE(LOWER(@SortOrder), N' ', N'_'); @@ -19566,6 +19567,20 @@ SET @FilterMB=250; SELECT @ScriptVersionName = 'sp_BlitzIndex(TM) v' + @Version + ' - ' + DATENAME(MM, @VersionDate) + ' ' + RIGHT('0'+DATENAME(DD, @VersionDate),2) + ', ' + DATENAME(YY, @VersionDate); SET @IgnoreDatabases = REPLACE(REPLACE(LTRIM(RTRIM(@IgnoreDatabases)), CHAR(10), ''), CHAR(13), ''); +SELECT + @OptimizeForSequentialKey = + CASE WHEN EXISTS + ( + SELECT + 1/0 + FROM sys.all_columns AS ac + WHERE ac.object_id = OBJECT_ID('sys.indexes') + AND ac.name = N'optimize_for_sequential_key' + ) + THEN 1 + ELSE 0 + END; + RAISERROR(N'Starting run. %s', 0,1, @ScriptVersionName) WITH NOWAIT; IF(@OutputType NOT IN ('TABLE','NONE')) @@ -19613,6 +19628,9 @@ IF OBJECT_ID('tempdb..#MissingIndexes') IS NOT NULL IF OBJECT_ID('tempdb..#ForeignKeys') IS NOT NULL DROP TABLE #ForeignKeys; +IF OBJECT_ID('tempdb..#UnindexedForeignKeys') IS NOT NULL + DROP TABLE #UnindexedForeignKeys; + IF OBJECT_ID('tempdb..#BlitzIndexResults') IS NOT NULL DROP TABLE #BlitzIndexResults; @@ -19686,10 +19704,12 @@ IF OBJECT_ID('tempdb..#Ignore_Databases') IS NOT NULL count_included_columns INT NULL , partition_key_column_name NVARCHAR(MAX) NULL, filter_definition NVARCHAR(MAX) NOT NULL , - is_indexed_view BIT NOT NULL , + optimize_for_sequential_key BIT NULL, + is_indexed_view BIT NOT NULL , is_unique BIT NOT NULL , is_primary_key BIT NOT NULL , - is_XML BIT NOT NULL, + is_unique_constraint BIT NOT NULL , + is_XML bit NOT NULL, is_spatial BIT NOT NULL, is_NC_columnstore BIT NOT NULL, is_CX_columnstore BIT NOT NULL, @@ -19737,7 +19757,8 @@ IF OBJECT_ID('tempdb..#Ignore_Databases') IS NOT NULL ELSE N'' END + CASE WHEN is_in_memory_oltp = 1 THEN N'[IN-MEMORY] ' ELSE N'' END + CASE WHEN is_disabled = 1 THEN N'[DISABLED] ' ELSE N'' END + CASE WHEN is_hypothetical = 1 THEN N'[HYPOTHETICAL] ' - ELSE N'' END + CASE WHEN is_unique = 1 AND is_primary_key = 0 THEN N'[UNIQUE] ' + ELSE N'' END + CASE WHEN is_unique = 1 AND is_primary_key = 0 AND is_unique_constraint = 0 THEN N'[UNIQUE] ' + ELSE N'' END + CASE WHEN is_unique_constraint = 1 AND is_primary_key = 0 THEN N'[UNIQUE CONSTRAINT] ' ELSE N'' END + CASE WHEN count_key_columns > 0 THEN N'[' + CAST(count_key_columns AS NVARCHAR(10)) + N' KEY' + CASE WHEN count_key_columns > 1 THEN N'S' ELSE N'' END @@ -20057,6 +20078,18 @@ IF OBJECT_ID('tempdb..#Ignore_Databases') IS NOT NULL update_referential_action_desc NVARCHAR(16), delete_referential_action_desc NVARCHAR(60) ); + + CREATE TABLE #UnindexedForeignKeys + ( + [database_id] INT NOT NULL, + [database_name] NVARCHAR(128) NOT NULL , + [schema_name] NVARCHAR(128) NOT NULL , + foreign_key_name NVARCHAR(256), + parent_object_name NVARCHAR(256), + parent_object_id INT, + referenced_object_name NVARCHAR(256), + referenced_object_id INT + ); CREATE TABLE #IndexCreateTsql ( index_sanity_id INT NOT NULL, @@ -20701,7 +20734,8 @@ BEGIN TRY CASE WHEN so.[type] = CAST(''V'' AS CHAR(2)) THEN 1 ELSE 0 END, si.is_unique, si.is_primary_key, - CASE when si.type = 3 THEN 1 ELSE 0 END AS is_XML, + si.is_unique_constraint, + CASE when si.type = 3 THEN 1 ELSE 0 END AS is_XML, CASE when si.type = 4 THEN 1 ELSE 0 END AS is_spatial, CASE when si.type = 6 THEN 1 ELSE 0 END AS is_NC_columnstore, CASE when si.type = 5 then 1 else 0 end as is_CX_columnstore, @@ -20713,8 +20747,14 @@ BEGIN TRY + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N' CASE WHEN si.filter_definition IS NOT NULL THEN si.filter_definition ELSE N'''' - END AS filter_definition' ELSE N''''' AS filter_definition' END + N' - , ISNULL(us.user_seeks, 0), + END AS filter_definition' ELSE N''''' AS filter_definition' END + + CASE + WHEN @OptimizeForSequentialKey = 1 + THEN N', si.optimize_for_sequential_key' + ELSE N', CONVERT(BIT, NULL) AS optimize_for_sequential_key' + END + + N', + ISNULL(us.user_seeks, 0), ISNULL(us.user_scans, 0), ISNULL(us.user_lookups, 0), ISNULL(us.user_updates, 0), @@ -20761,8 +20801,8 @@ BEGIN TRY PRINT SUBSTRING(@dsql, 36000, 40000); END; INSERT #IndexSanity ( [database_id], [object_id], [index_id], [index_type], [database_name], [schema_name], [object_name], - index_name, is_indexed_view, is_unique, is_primary_key, is_XML, is_spatial, is_NC_columnstore, is_CX_columnstore, is_in_memory_oltp, - is_disabled, is_hypothetical, is_padded, fill_factor, filter_definition, user_seeks, user_scans, + index_name, is_indexed_view, is_unique, is_primary_key, is_unique_constraint, is_XML, is_spatial, is_NC_columnstore, is_CX_columnstore, is_in_memory_oltp, + is_disabled, is_hypothetical, is_padded, fill_factor, filter_definition, [optimize_for_sequential_key], user_seeks, user_scans, user_lookups, user_updates, last_user_seek, last_user_scan, last_user_lookup, last_user_update, create_date, modify_date ) EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; @@ -21218,6 +21258,77 @@ BEGIN TRY EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; + SET @dsql = N' + SELECT + DB_ID(N' + QUOTENAME(@DatabaseName,'''') + N') AS [database_id], + @i_DatabaseName AS database_name, + foreign_key_schema = + s.name, + foreign_key_name = + fk.name, + foreign_key_table = + OBJECT_NAME(fk.parent_object_id, DB_ID(N' + QUOTENAME(@DatabaseName,'''') + N')), + fk.parent_object_id, + foreign_key_referenced_table = + OBJECT_NAME(fk.referenced_object_id, DB_ID(N' + QUOTENAME(@DatabaseName,'''') + N')), + fk.referenced_object_id + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.foreign_keys fk + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s + ON s.schema_id = fk.schema_id + WHERE fk.is_disabled = 0 + AND EXISTS + ( + SELECT + 1/0 + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.foreign_key_columns fkc + WHERE fkc.constraint_object_id = fk.object_id + AND NOT EXISTS + ( + SELECT + 1/0 + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.index_columns ic + WHERE ic.object_id = fkc.parent_object_id + AND ic.column_id = fkc.parent_column_id + AND ic.index_column_id = fkc.constraint_column_id + ) + ) + OPTION (RECOMPILE);' + IF @dsql IS NULL + RAISERROR('@dsql is null',16,1); + + RAISERROR (N'Inserting data into #ForeignKeys',0,1) WITH NOWAIT; + IF @Debug = 1 + BEGIN + PRINT SUBSTRING(@dsql, 0, 4000); + PRINT SUBSTRING(@dsql, 4000, 8000); + PRINT SUBSTRING(@dsql, 8000, 12000); + PRINT SUBSTRING(@dsql, 12000, 16000); + PRINT SUBSTRING(@dsql, 16000, 20000); + PRINT SUBSTRING(@dsql, 20000, 24000); + PRINT SUBSTRING(@dsql, 24000, 28000); + PRINT SUBSTRING(@dsql, 28000, 32000); + PRINT SUBSTRING(@dsql, 32000, 36000); + PRINT SUBSTRING(@dsql, 36000, 40000); + END; + + INSERT + #UnindexedForeignKeys + ( + database_id, + database_name, + schema_name, + foreign_key_name, + parent_object_name, + parent_object_id, + referenced_object_name, + referenced_object_id + ) + EXEC sys.sp_executesql + @dsql, + N'@i_DatabaseName sysname', + @DatabaseName; + + IF @SkipStatistics = 0 /* AND DB_NAME() = @DatabaseName /* Can only get stats in the current database - see https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/1947 */ */ BEGIN IF ((PARSENAME(@SQLServerProductVersion, 4) >= 12) @@ -21830,7 +21941,14 @@ SELECT N'] PRIMARY KEY ' + CASE WHEN index_id=1 THEN N'CLUSTERED (' ELSE N'(' END + key_column_names_with_sort_order_no_types + N' )' - WHEN is_CX_columnstore= 1 THEN + WHEN is_unique_constraint = 1 AND is_primary_key = 0 + THEN + N'ALTER TABLE ' + QUOTENAME([database_name]) + N'.' + QUOTENAME([schema_name]) + + N'.' + QUOTENAME([object_name]) + + N' ADD CONSTRAINT [' + + index_name + + N'] UNIQUE' + WHEN is_CX_columnstore= 1 THEN N'CREATE CLUSTERED COLUMNSTORE INDEX ' + QUOTENAME(index_name) + N' on ' + QUOTENAME([database_name]) + N'.' + QUOTENAME([schema_name]) + N'.' + QUOTENAME([object_name]) ELSE /*Else not a PK or cx columnstore */ N'CREATE ' + @@ -21938,12 +22056,14 @@ FROM #IndexSanity si IF @Debug = 1 BEGIN + SELECT '#BlitzIndexResults' AS table_name, * FROM #BlitzIndexResults AS bir; SELECT '#IndexSanity' AS table_name, * FROM #IndexSanity; SELECT '#IndexPartitionSanity' AS table_name, * FROM #IndexPartitionSanity; SELECT '#IndexSanitySize' AS table_name, * FROM #IndexSanitySize; SELECT '#IndexColumns' AS table_name, * FROM #IndexColumns; SELECT '#MissingIndexes' AS table_name, * FROM #MissingIndexes; SELECT '#ForeignKeys' AS table_name, * FROM #ForeignKeys; + SELECT '#UnindexedForeignKeys' AS table_name, * FROM #UnindexedForeignKeys; SELECT '#BlitzIndexResults' AS table_name, * FROM #BlitzIndexResults; SELECT '#IndexCreateTsql' AS table_name, * FROM #IndexCreateTsql; SELECT '#DatabaseList' AS table_name, * FROM #DatabaseList; @@ -22004,6 +22124,9 @@ BEGIN ct.create_tsql, CASE WHEN s.is_primary_key = 1 AND s.index_definition <> '[HEAP]' + THEN N'--ALTER TABLE ' + QUOTENAME(s.[database_name]) + N'.' + QUOTENAME(s.[schema_name]) + N'.' + QUOTENAME(s.[object_name]) + + N' DROP CONSTRAINT ' + QUOTENAME(s.index_name) + N';' + WHEN s.is_primary_key = 0 AND is_unique_constraint = 1 AND s.index_definition <> '[HEAP]' THEN N'--ALTER TABLE ' + QUOTENAME(s.[database_name]) + N'.' + QUOTENAME(s.[schema_name]) + N'.' + QUOTENAME(s.[object_name]) + N' DROP CONSTRAINT ' + QUOTENAME(s.index_name) + N';' WHEN s.is_primary_key = 0 AND s.index_definition <> '[HEAP]' @@ -23560,7 +23683,6 @@ BEGIN; OPTION ( RECOMPILE ); RAISERROR(N'check_id 33: Potential filtered indexes based on column names.', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, secret_columns, index_usage_summary, index_size_summary ) SELECT 33 AS check_id, @@ -23936,15 +24058,14 @@ BEGIN; N'Cascading Updates or Deletes' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, - N'Foreign Key ' + foreign_key_name + + N'Foreign Key ' + QUOTENAME(foreign_key_name) + N' on ' + QUOTENAME(parent_object_name) + N'(' + LTRIM(parent_fk_columns) + N')' + N' referencing ' + QUOTENAME(referenced_object_name) + N'(' + LTRIM(referenced_fk_columns) + N')' + N' has settings:' + CASE [delete_referential_action_desc] WHEN N'NO_ACTION' THEN N'' ELSE N' ON DELETE ' +[delete_referential_action_desc] END + CASE [update_referential_action_desc] WHEN N'NO_ACTION' THEN N'' ELSE N' ON UPDATE ' + [update_referential_action_desc] END AS details, - [fk].[database_name] - AS index_definition, + [fk].[database_name] AS index_definition, N'N/A' AS secret_columns, N'N/A' AS index_usage_summary, N'N/A' AS index_size_summary, @@ -23955,6 +24076,29 @@ BEGIN; OR [update_referential_action_desc] <> N'NO_ACTION') OPTION ( RECOMPILE ); + RAISERROR(N'check_id 72: Unindexed foreign keys.', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary, more_info ) + SELECT 72 AS check_id, + NULL AS index_sanity_id, + 150 AS Priority, + N'Abnormal Psychology' AS findings_group, + N'Unindexed Foreign Keys' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, + N'Foreign Key ' + QUOTENAME(foreign_key_name) + + N' on ' + QUOTENAME(parent_object_name) + N'' + + N' referencing ' + QUOTENAME(referenced_object_name) + N'' + + N' does not appear to have a supporting index.' AS details, + N'N/A' AS index_definition, + N'N/A' AS secret_columns, + N'N/A' AS index_usage_summary, + N'N/A' AS index_size_summary, + (SELECT TOP 1 more_info FROM #IndexSanity i WHERE i.object_id=fk.parent_object_id AND i.database_id = fk.database_id AND i.schema_name = fk.schema_name) + AS more_info + FROM #UnindexedForeignKeys AS fk + OPTION ( RECOMPILE ); + RAISERROR(N'check_id 73: In-Memory OLTP', 0,1) WITH NOWAIT; INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, @@ -24144,6 +24288,24 @@ BEGIN; FROM #TemporalTables AS t OPTION ( RECOMPILE ); + RAISERROR(N'check_id 121: Optimized For Sequental Keys.', 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 121 AS check_id, + 200 AS Priority, + 'Medicated Indexes' AS findings_group, + 'Optimized For Sequential Keys', + i.database_name, + '' AS URL, + 'The table ' + QUOTENAME(i.schema_name) + '.' + QUOTENAME(i.object_name) + ' is optimized for sequential keys.' AS details, + '' AS index_definition, + 'N/A' AS secret_columns, + 'N/A' AS index_usage_summary, + 'N/A' AS index_size_summary + FROM #IndexSanity AS i + WHERE i.optimize_for_sequential_key = 1 + OPTION ( RECOMPILE ); @@ -24504,7 +24666,8 @@ ELSE IF (@Mode=1) /*Summarize*/ [partition_key_column_name] NVARCHAR(MAX), [filter_definition] NVARCHAR(MAX), [is_indexed_view] BIT, - [is_primary_key] BIT, + [is_primary_key] BIT, + [is_unique_constraint] BIT, [is_XML] BIT, [is_spatial] BIT, [is_NC_columnstore] BIT, @@ -24614,7 +24777,8 @@ ELSE IF (@Mode=1) /*Summarize*/ [partition_key_column_name], [filter_definition], [is_indexed_view], - [is_primary_key], + [is_primary_key], + [is_unique_constraint], [is_XML], [is_spatial], [is_NC_columnstore], @@ -24677,6 +24841,9 @@ ELSE IF (@Mode=1) /*Summarize*/ WHEN i.is_primary_key = 1 AND i.index_definition <> ''[HEAP]'' THEN N''--ALTER TABLE '' + QUOTENAME(i.[database_name]) + N''.'' + QUOTENAME(i.[schema_name]) + N''.'' + QUOTENAME(i.[object_name]) + N'' DROP CONSTRAINT '' + QUOTENAME(i.index_name) + N'';'' + WHEN i.is_primary_key = 0 AND i.is_unique_constraint = 1 AND i.index_definition <> ''[HEAP]'' + THEN N''--ALTER TABLE '' + QUOTENAME(i.[database_name]) + N''.'' + QUOTENAME(i.[schema_name]) + N''.'' + QUOTENAME(i.[object_name]) + + N'' DROP CONSTRAINT '' + QUOTENAME(i.index_name) + N'';'' WHEN i.is_primary_key = 0 AND i.index_definition <> ''[HEAP]'' THEN N''--DROP INDEX ''+ QUOTENAME(i.index_name) + N'' ON '' + QUOTENAME(i.[database_name]) + N''.'' + QUOTENAME(i.[schema_name]) + N''.'' + QUOTENAME(i.[object_name]) + N'';'' @@ -24701,6 +24868,7 @@ ELSE IF (@Mode=1) /*Summarize*/ ISNULL(filter_definition, '''') AS [Filter Definition], is_indexed_view AS [Is Indexed View], is_primary_key AS [Is Primary Key], + is_unique_constraint AS [Is Unique Constraint], is_XML AS [Is XML], is_spatial AS [Is Spatial], is_NC_columnstore AS [Is NC Columnstore], @@ -24794,6 +24962,7 @@ ELSE IF (@Mode=1) /*Summarize*/ ISNULL(filter_definition, '') AS [Filter Definition], is_indexed_view AS [Is Indexed View], is_primary_key AS [Is Primary Key], + is_unique_constraint AS [Is Unique Constraint] , is_XML AS [Is XML], is_spatial AS [Is Spatial], is_NC_columnstore AS [Is NC Columnstore], @@ -24846,6 +25015,9 @@ ELSE IF (@Mode=1) /*Summarize*/ WHEN i.is_primary_key = 1 AND i.index_definition <> '[HEAP]' THEN N'--ALTER TABLE ' + QUOTENAME(i.[database_name]) + N'.' + QUOTENAME(i.[schema_name]) + N'.' + QUOTENAME(i.[object_name]) + N' DROP CONSTRAINT ' + QUOTENAME(i.index_name) + N';' + WHEN i.is_primary_key = 0 AND i.is_unique_constraint = 1 AND i.index_definition <> '[HEAP]' + THEN N'--ALTER TABLE ' + QUOTENAME(i.[database_name]) + N'.' + QUOTENAME(i.[schema_name]) + N'.' + QUOTENAME(i.[object_name]) + + N' DROP CONSTRAINT ' + QUOTENAME(i.index_name) + N';' WHEN i.is_primary_key = 0 AND i.index_definition <> '[HEAP]' THEN N'--DROP INDEX '+ QUOTENAME(i.index_name) + N' ON ' + QUOTENAME(i.[database_name]) + N'.' + QUOTENAME(i.[schema_name]) + N'.' + QUOTENAME(i.[object_name]) + N';' @@ -25022,7 +25194,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.05', @VersionDate = '20210725'; +SELECT @Version = '8.06', @VersionDate = '20210914'; IF(@VersionCheckMode = 1) @@ -26804,7 +26976,7 @@ BEGIN SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.05', @VersionDate = '20210725'; + SELECT @Version = '8.06', @VersionDate = '20210914'; IF(@VersionCheckMode = 1) BEGIN @@ -27059,6 +27231,13 @@ IF @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @Output ALTER TABLE ' + @ObjectFullName + N' ADD outer_command NVARCHAR(4000) NULL;'; EXEC(@StringToExecute); + /* If the table doesn't have the new wait_resource column, add it. See Github #2970. */ + SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableName; + SET @StringToExecute = N'IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns + WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''wait_resource'') + ALTER TABLE ' + @ObjectFullName + N' ADD wait_resource NVARCHAR(MAX) NULL;'; + EXEC(@StringToExecute); + /* Delete history older than @OutputTableRetentionDays */ SET @OutputTableCleanupDate = CAST( (DATEADD(DAY, -1 * @OutputTableRetentionDays, GETDATE() ) ) AS DATE); SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' @@ -28186,6 +28365,7 @@ DELETE FROM dbo.SqlServerVersions; INSERT INTO dbo.SqlServerVersions (MajorVersionNumber, MinorVersionNumber, Branch, [Url], ReleaseDate, MainstreamSupportEndDate, ExtendedSupportEndDate, MajorVersionName, MinorVersionName) VALUES + (15, 4153, 'CU12', 'https://support.microsoft.com/en-us/help/5004524', '2021-08-04', '2025-01-01', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 12'), (15, 4138, 'CU11', 'https://support.microsoft.com/en-us/help/5003249', '2021-06-10', '2025-01-01', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 11'), (15, 4123, 'CU10', 'https://support.microsoft.com/en-us/help/5001090', '2021-04-06', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 10'), (15, 4102, 'CU9', 'https://support.microsoft.com/en-us/help/5000642', '2021-02-11', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 9 '), @@ -28200,7 +28380,7 @@ VALUES (15, 4003, 'CU1', 'https://support.microsoft.com/en-us/help/4527376', '2020-01-07', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 1 '), (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 '), - (14, 3410, 'RTM CU25', 'https://support.microsoft.com/en-us/help/5003830', '2021-07-12', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 25'), + (14, 3401, 'RTM CU25', 'https://support.microsoft.com/en-us/help/5003830', '2021-07-12', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 25'), (14, 3391, 'RTM CU24', 'https://support.microsoft.com/en-us/help/5001228', '2021-05-10', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 24'), (14, 3381, 'RTM CU23', 'https://support.microsoft.com/en-us/help/5000685', '2021-02-25', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 23'), (14, 3370, 'RTM CU22 GDR', 'https://support.microsoft.com/en-us/help/4583457', '2021-01-12', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 22 GDR'), @@ -28580,7 +28760,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.05', @VersionDate = '20210725'; +SELECT @Version = '8.06', @VersionDate = '20210914'; IF(@VersionCheckMode = 1) BEGIN @@ -30051,28 +30231,32 @@ BEGIN 'https://www.brentozar.com/askbrent/backups/' AS URL, 'Backup of ' + DB_NAME(db.resource_database_id) + ' database (' + (SELECT CAST(CAST(SUM(size * 8.0 / 1024 / 1024) AS BIGINT) AS NVARCHAR) FROM #MasterFiles WHERE database_id = db.resource_database_id) + 'GB) ' + @LineFeed + CAST(r.percent_complete AS NVARCHAR(100)) + '% complete, has been running since ' + CAST(r.start_time AS NVARCHAR(100)) + '. ' + @LineFeed - + CASE WHEN COALESCE(s.nt_user_name, s.login_name) IS NOT NULL THEN (' Login: ' + COALESCE(s.nt_user_name, s.login_name) + ' ') ELSE '' END AS Details, + + CASE WHEN COALESCE(s.nt_username, s.loginame) IS NOT NULL THEN (' Login: ' + COALESCE(s.nt_username, s.loginame) + ' ') ELSE '' END AS Details, 'KILL ' + CAST(r.session_id AS NVARCHAR(100)) + ';' AS HowToStopIt, pl.query_plan AS QueryPlan, r.start_time AS StartTime, - s.login_name AS LoginName, - s.nt_user_name AS NTUserName, + s.loginame AS LoginName, + s.nt_username AS NTUserName, s.[program_name] AS ProgramName, - s.[host_name] AS HostName, + s.[hostname] AS HostName, db.[resource_database_id] AS DatabaseID, DB_NAME(db.resource_database_id) AS DatabaseName, 0 AS OpenTransactionCount, r.query_hash FROM sys.dm_exec_requests r INNER JOIN sys.dm_exec_connections c ON r.session_id = c.session_id - INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id - INNER JOIN ( - SELECT DISTINCT request_session_id, resource_database_id - FROM sys.dm_tran_locks - WHERE resource_type = N'DATABASE' - AND request_mode = N'S' - AND request_status = N'GRANT' - AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.session_id = db.request_session_id AND s.database_id = db.resource_database_id + INNER JOIN sys.sysprocesses AS s ON r.session_id = s.spid AND s.ecid = 0 + INNER JOIN + ( + SELECT DISTINCT + t.request_session_id, + t.resource_database_id + FROM sys.dm_tran_locks AS t + WHERE t.resource_type = N'DATABASE' + AND t.request_mode = N'S' + AND t.request_status = N'GRANT' + AND t.request_owner_type = N'SHARED_TRANSACTION_WORKSPACE' + ) AS db ON s.spid = db.request_session_id AND s.dbid = db.resource_database_id CROSS APPLY sys.dm_exec_query_plan(r.plan_handle) pl WHERE r.command LIKE 'BACKUP%' AND r.start_time <= DATEADD(minute, -5, GETDATE()) @@ -30289,30 +30473,30 @@ BEGIN 'Query Problems' AS FindingGroup, 'Sleeping Query with Open Transactions' AS Finding, 'https://www.brentozar.com/askbrent/sleeping-query-with-open-transactions/' AS URL, - 'Database: ' + DB_NAME(db.resource_database_id) + @LineFeed + 'Host: ' + s.[host_name] + @LineFeed + 'Program: ' + s.[program_name] + @LineFeed + 'Asleep with open transactions and locks since ' + CAST(s.last_request_end_time AS NVARCHAR(100)) + '. ' AS Details, - 'KILL ' + CAST(s.session_id AS NVARCHAR(100)) + ';' AS HowToStopIt, - s.last_request_start_time AS StartTime, - s.login_name AS LoginName, - s.nt_user_name AS NTUserName, + 'Database: ' + DB_NAME(db.resource_database_id) + @LineFeed + 'Host: ' + s.hostname + @LineFeed + 'Program: ' + s.[program_name] + @LineFeed + 'Asleep with open transactions and locks since ' + CAST(s.last_batch AS NVARCHAR(100)) + '. ' AS Details, + 'KILL ' + CAST(s.spid AS NVARCHAR(100)) + ';' AS HowToStopIt, + s.last_batch AS StartTime, + s.loginame AS LoginName, + s.nt_username AS NTUserName, s.[program_name] AS ProgramName, - s.[host_name] AS HostName, + s.hostname AS HostName, db.[resource_database_id] AS DatabaseID, DB_NAME(db.resource_database_id) AS DatabaseName, (SELECT TOP 1 [text] FROM sys.dm_exec_sql_text(c.most_recent_sql_handle)) AS QueryText, - s.open_transaction_count AS OpenTransactionCount - FROM sys.dm_exec_sessions s - INNER JOIN sys.dm_exec_connections c ON s.session_id = c.session_id + s.open_tran AS OpenTransactionCount + FROM sys.sysprocesses s + INNER JOIN sys.dm_exec_connections c ON s.spid = c.session_id INNER JOIN ( SELECT DISTINCT request_session_id, resource_database_id FROM sys.dm_tran_locks WHERE resource_type = N'DATABASE' AND request_mode = N'S' AND request_status = N'GRANT' - AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.session_id = db.request_session_id + AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.spid = db.request_session_id WHERE s.status = 'sleeping' - AND s.open_transaction_count > 0 - AND s.last_request_end_time < DATEADD(ss, -10, SYSDATETIME()) - AND EXISTS(SELECT * FROM sys.dm_tran_locks WHERE request_session_id = s.session_id + AND s.open_tran > 0 + AND s.last_batch < DATEADD(ss, -10, SYSDATETIME()) + AND EXISTS(SELECT * FROM sys.dm_tran_locks WHERE request_session_id = s.spid AND NOT (resource_type = N'DATABASE' AND request_mode = N'S' AND request_status = N'GRANT' AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE')); END @@ -31660,7 +31844,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, This has to be done as dynamic SQL because we have to execute OBJECT_NAME inside TempDB. */ IF @@ROWCOUNT > 0 BEGIN - SET @StringToExecute = N'USE tempdb; + SET @StringToExecute = N' INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) SELECT TOP 10 29 AS CheckID, 40 AS Priority, diff --git a/Install-Core-Blitz-With-Query-Store.sql b/Install-Core-Blitz-With-Query-Store.sql index df642dc9d..33a9f801c 100755 --- a/Install-Core-Blitz-With-Query-Store.sql +++ b/Install-Core-Blitz-With-Query-Store.sql @@ -38,7 +38,7 @@ AS SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.05', @VersionDate = '20210725'; + SELECT @Version = '8.06', @VersionDate = '20210914'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -4342,12 +4342,12 @@ AS SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details) SELECT ' + CAST(@CurrentCheckID AS NVARCHAR(200)) + ', d.[name], ' + CAST(@CurrentPriority AS NVARCHAR(200)) + ', ''Non-Default Database Config'', ''' + @CurrentFinding + ''',''' + @CurrentURL + ''',''' + COALESCE(@CurrentDetails, 'This database setting is not the default.') + ''' FROM sys.databases d - WHERE d.database_id > 4 AND d.state <> 1 AND (d.[' + @CurrentName + '] NOT IN (0, 60) OR d.[' + @CurrentName + '] IS NULL) OPTION (RECOMPILE);'; + WHERE d.database_id > 4 AND d.state = 0 AND (d.[' + @CurrentName + '] NOT IN (0, 60) OR d.[' + @CurrentName + '] IS NULL) OPTION (RECOMPILE);'; ELSE SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details) SELECT ' + CAST(@CurrentCheckID AS NVARCHAR(200)) + ', d.[name], ' + CAST(@CurrentPriority AS NVARCHAR(200)) + ', ''Non-Default Database Config'', ''' + @CurrentFinding + ''',''' + @CurrentURL + ''',''' + COALESCE(@CurrentDetails, 'This database setting is not the default.') + ''' FROM sys.databases d - WHERE d.database_id > 4 AND d.state <> 1 AND (d.[' + @CurrentName + '] <> ' + @CurrentDefaultValue + ' OR d.[' + @CurrentName + '] IS NULL) OPTION (RECOMPILE);'; + WHERE d.database_id > 4 AND d.state = 0 AND (d.[' + @CurrentName + '] <> ' + @CurrentDefaultValue + ' OR d.[' + @CurrentName + '] IS NULL) OPTION (RECOMPILE);'; IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; @@ -7770,6 +7770,7 @@ IF @ProductVersionMajor >= 10 WHEN [T].[TraceFlag] = '1117' THEN '1117 enabled globally, which grows all files in a filegroup at the same time.' WHEN [T].[TraceFlag] = '1118' THEN '1118 enabled globally, which tries to reduce SGAM waits.' WHEN [T].[TraceFlag] = '1211' THEN '1211 enabled globally, which disables lock escalation when you least expect it. This is usually a very bad idea.' + WHEN [T].[TraceFlag] = '1204' THEN '1222 enabled globally, which captures deadlock graphs in the error log.' WHEN [T].[TraceFlag] = '1222' THEN '1222 enabled globally, which captures deadlock graphs in the error log.' WHEN [T].[TraceFlag] = '1224' THEN '1224 enabled globally, which disables lock escalation until the server has memory pressure. This is usually a very bad idea.' WHEN [T].[TraceFlag] = '1806' THEN '1806 enabled globally, which disables Instant File Initialization, causing restores and file growths to take longer. This is usually a very bad idea.' @@ -9554,7 +9555,7 @@ AS SET NOCOUNT ON; SET STATISTICS XML OFF; -SELECT @Version = '8.05', @VersionDate = '20210725'; +SELECT @Version = '8.06', @VersionDate = '20210914'; IF(@VersionCheckMode = 1) BEGIN @@ -10274,7 +10275,6 @@ SELECT [ServerName] ,[query_cost] ,[status] ,[wait_info] - ,[wait_resource] ,[top_session_waits] ,[blocking_session_id] ,[open_transaction_count] @@ -10433,7 +10433,7 @@ AS SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.05', @VersionDate = '20210725'; + SELECT @Version = '8.06', @VersionDate = '20210914'; IF(@VersionCheckMode = 1) BEGIN @@ -12214,7 +12214,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.05', @VersionDate = '20210725'; +SELECT @Version = '8.06', @VersionDate = '20210914'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -19474,7 +19474,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.05', @VersionDate = '20210725'; +SELECT @Version = '8.06', @VersionDate = '20210914'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -19554,6 +19554,7 @@ DECLARE @DaysUptimeInsertValue NVARCHAR(256); DECLARE @DatabaseToIgnore NVARCHAR(MAX); DECLARE @ColumnList NVARCHAR(MAX); DECLARE @PartitionCount INT; +DECLARE @OptimizeForSequentialKey BIT = 0; /* Let's get @SortOrder set to lower case here for comparisons later */ SET @SortOrder = REPLACE(LOWER(@SortOrder), N' ', N'_'); @@ -19566,6 +19567,20 @@ SET @FilterMB=250; SELECT @ScriptVersionName = 'sp_BlitzIndex(TM) v' + @Version + ' - ' + DATENAME(MM, @VersionDate) + ' ' + RIGHT('0'+DATENAME(DD, @VersionDate),2) + ', ' + DATENAME(YY, @VersionDate); SET @IgnoreDatabases = REPLACE(REPLACE(LTRIM(RTRIM(@IgnoreDatabases)), CHAR(10), ''), CHAR(13), ''); +SELECT + @OptimizeForSequentialKey = + CASE WHEN EXISTS + ( + SELECT + 1/0 + FROM sys.all_columns AS ac + WHERE ac.object_id = OBJECT_ID('sys.indexes') + AND ac.name = N'optimize_for_sequential_key' + ) + THEN 1 + ELSE 0 + END; + RAISERROR(N'Starting run. %s', 0,1, @ScriptVersionName) WITH NOWAIT; IF(@OutputType NOT IN ('TABLE','NONE')) @@ -19613,6 +19628,9 @@ IF OBJECT_ID('tempdb..#MissingIndexes') IS NOT NULL IF OBJECT_ID('tempdb..#ForeignKeys') IS NOT NULL DROP TABLE #ForeignKeys; +IF OBJECT_ID('tempdb..#UnindexedForeignKeys') IS NOT NULL + DROP TABLE #UnindexedForeignKeys; + IF OBJECT_ID('tempdb..#BlitzIndexResults') IS NOT NULL DROP TABLE #BlitzIndexResults; @@ -19686,10 +19704,12 @@ IF OBJECT_ID('tempdb..#Ignore_Databases') IS NOT NULL count_included_columns INT NULL , partition_key_column_name NVARCHAR(MAX) NULL, filter_definition NVARCHAR(MAX) NOT NULL , - is_indexed_view BIT NOT NULL , + optimize_for_sequential_key BIT NULL, + is_indexed_view BIT NOT NULL , is_unique BIT NOT NULL , is_primary_key BIT NOT NULL , - is_XML BIT NOT NULL, + is_unique_constraint BIT NOT NULL , + is_XML bit NOT NULL, is_spatial BIT NOT NULL, is_NC_columnstore BIT NOT NULL, is_CX_columnstore BIT NOT NULL, @@ -19737,7 +19757,8 @@ IF OBJECT_ID('tempdb..#Ignore_Databases') IS NOT NULL ELSE N'' END + CASE WHEN is_in_memory_oltp = 1 THEN N'[IN-MEMORY] ' ELSE N'' END + CASE WHEN is_disabled = 1 THEN N'[DISABLED] ' ELSE N'' END + CASE WHEN is_hypothetical = 1 THEN N'[HYPOTHETICAL] ' - ELSE N'' END + CASE WHEN is_unique = 1 AND is_primary_key = 0 THEN N'[UNIQUE] ' + ELSE N'' END + CASE WHEN is_unique = 1 AND is_primary_key = 0 AND is_unique_constraint = 0 THEN N'[UNIQUE] ' + ELSE N'' END + CASE WHEN is_unique_constraint = 1 AND is_primary_key = 0 THEN N'[UNIQUE CONSTRAINT] ' ELSE N'' END + CASE WHEN count_key_columns > 0 THEN N'[' + CAST(count_key_columns AS NVARCHAR(10)) + N' KEY' + CASE WHEN count_key_columns > 1 THEN N'S' ELSE N'' END @@ -20057,6 +20078,18 @@ IF OBJECT_ID('tempdb..#Ignore_Databases') IS NOT NULL update_referential_action_desc NVARCHAR(16), delete_referential_action_desc NVARCHAR(60) ); + + CREATE TABLE #UnindexedForeignKeys + ( + [database_id] INT NOT NULL, + [database_name] NVARCHAR(128) NOT NULL , + [schema_name] NVARCHAR(128) NOT NULL , + foreign_key_name NVARCHAR(256), + parent_object_name NVARCHAR(256), + parent_object_id INT, + referenced_object_name NVARCHAR(256), + referenced_object_id INT + ); CREATE TABLE #IndexCreateTsql ( index_sanity_id INT NOT NULL, @@ -20701,7 +20734,8 @@ BEGIN TRY CASE WHEN so.[type] = CAST(''V'' AS CHAR(2)) THEN 1 ELSE 0 END, si.is_unique, si.is_primary_key, - CASE when si.type = 3 THEN 1 ELSE 0 END AS is_XML, + si.is_unique_constraint, + CASE when si.type = 3 THEN 1 ELSE 0 END AS is_XML, CASE when si.type = 4 THEN 1 ELSE 0 END AS is_spatial, CASE when si.type = 6 THEN 1 ELSE 0 END AS is_NC_columnstore, CASE when si.type = 5 then 1 else 0 end as is_CX_columnstore, @@ -20713,8 +20747,14 @@ BEGIN TRY + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N' CASE WHEN si.filter_definition IS NOT NULL THEN si.filter_definition ELSE N'''' - END AS filter_definition' ELSE N''''' AS filter_definition' END + N' - , ISNULL(us.user_seeks, 0), + END AS filter_definition' ELSE N''''' AS filter_definition' END + + CASE + WHEN @OptimizeForSequentialKey = 1 + THEN N', si.optimize_for_sequential_key' + ELSE N', CONVERT(BIT, NULL) AS optimize_for_sequential_key' + END + + N', + ISNULL(us.user_seeks, 0), ISNULL(us.user_scans, 0), ISNULL(us.user_lookups, 0), ISNULL(us.user_updates, 0), @@ -20761,8 +20801,8 @@ BEGIN TRY PRINT SUBSTRING(@dsql, 36000, 40000); END; INSERT #IndexSanity ( [database_id], [object_id], [index_id], [index_type], [database_name], [schema_name], [object_name], - index_name, is_indexed_view, is_unique, is_primary_key, is_XML, is_spatial, is_NC_columnstore, is_CX_columnstore, is_in_memory_oltp, - is_disabled, is_hypothetical, is_padded, fill_factor, filter_definition, user_seeks, user_scans, + index_name, is_indexed_view, is_unique, is_primary_key, is_unique_constraint, is_XML, is_spatial, is_NC_columnstore, is_CX_columnstore, is_in_memory_oltp, + is_disabled, is_hypothetical, is_padded, fill_factor, filter_definition, [optimize_for_sequential_key], user_seeks, user_scans, user_lookups, user_updates, last_user_seek, last_user_scan, last_user_lookup, last_user_update, create_date, modify_date ) EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; @@ -21218,6 +21258,77 @@ BEGIN TRY EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; + SET @dsql = N' + SELECT + DB_ID(N' + QUOTENAME(@DatabaseName,'''') + N') AS [database_id], + @i_DatabaseName AS database_name, + foreign_key_schema = + s.name, + foreign_key_name = + fk.name, + foreign_key_table = + OBJECT_NAME(fk.parent_object_id, DB_ID(N' + QUOTENAME(@DatabaseName,'''') + N')), + fk.parent_object_id, + foreign_key_referenced_table = + OBJECT_NAME(fk.referenced_object_id, DB_ID(N' + QUOTENAME(@DatabaseName,'''') + N')), + fk.referenced_object_id + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.foreign_keys fk + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s + ON s.schema_id = fk.schema_id + WHERE fk.is_disabled = 0 + AND EXISTS + ( + SELECT + 1/0 + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.foreign_key_columns fkc + WHERE fkc.constraint_object_id = fk.object_id + AND NOT EXISTS + ( + SELECT + 1/0 + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.index_columns ic + WHERE ic.object_id = fkc.parent_object_id + AND ic.column_id = fkc.parent_column_id + AND ic.index_column_id = fkc.constraint_column_id + ) + ) + OPTION (RECOMPILE);' + IF @dsql IS NULL + RAISERROR('@dsql is null',16,1); + + RAISERROR (N'Inserting data into #ForeignKeys',0,1) WITH NOWAIT; + IF @Debug = 1 + BEGIN + PRINT SUBSTRING(@dsql, 0, 4000); + PRINT SUBSTRING(@dsql, 4000, 8000); + PRINT SUBSTRING(@dsql, 8000, 12000); + PRINT SUBSTRING(@dsql, 12000, 16000); + PRINT SUBSTRING(@dsql, 16000, 20000); + PRINT SUBSTRING(@dsql, 20000, 24000); + PRINT SUBSTRING(@dsql, 24000, 28000); + PRINT SUBSTRING(@dsql, 28000, 32000); + PRINT SUBSTRING(@dsql, 32000, 36000); + PRINT SUBSTRING(@dsql, 36000, 40000); + END; + + INSERT + #UnindexedForeignKeys + ( + database_id, + database_name, + schema_name, + foreign_key_name, + parent_object_name, + parent_object_id, + referenced_object_name, + referenced_object_id + ) + EXEC sys.sp_executesql + @dsql, + N'@i_DatabaseName sysname', + @DatabaseName; + + IF @SkipStatistics = 0 /* AND DB_NAME() = @DatabaseName /* Can only get stats in the current database - see https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/1947 */ */ BEGIN IF ((PARSENAME(@SQLServerProductVersion, 4) >= 12) @@ -21830,7 +21941,14 @@ SELECT N'] PRIMARY KEY ' + CASE WHEN index_id=1 THEN N'CLUSTERED (' ELSE N'(' END + key_column_names_with_sort_order_no_types + N' )' - WHEN is_CX_columnstore= 1 THEN + WHEN is_unique_constraint = 1 AND is_primary_key = 0 + THEN + N'ALTER TABLE ' + QUOTENAME([database_name]) + N'.' + QUOTENAME([schema_name]) + + N'.' + QUOTENAME([object_name]) + + N' ADD CONSTRAINT [' + + index_name + + N'] UNIQUE' + WHEN is_CX_columnstore= 1 THEN N'CREATE CLUSTERED COLUMNSTORE INDEX ' + QUOTENAME(index_name) + N' on ' + QUOTENAME([database_name]) + N'.' + QUOTENAME([schema_name]) + N'.' + QUOTENAME([object_name]) ELSE /*Else not a PK or cx columnstore */ N'CREATE ' + @@ -21938,12 +22056,14 @@ FROM #IndexSanity si IF @Debug = 1 BEGIN + SELECT '#BlitzIndexResults' AS table_name, * FROM #BlitzIndexResults AS bir; SELECT '#IndexSanity' AS table_name, * FROM #IndexSanity; SELECT '#IndexPartitionSanity' AS table_name, * FROM #IndexPartitionSanity; SELECT '#IndexSanitySize' AS table_name, * FROM #IndexSanitySize; SELECT '#IndexColumns' AS table_name, * FROM #IndexColumns; SELECT '#MissingIndexes' AS table_name, * FROM #MissingIndexes; SELECT '#ForeignKeys' AS table_name, * FROM #ForeignKeys; + SELECT '#UnindexedForeignKeys' AS table_name, * FROM #UnindexedForeignKeys; SELECT '#BlitzIndexResults' AS table_name, * FROM #BlitzIndexResults; SELECT '#IndexCreateTsql' AS table_name, * FROM #IndexCreateTsql; SELECT '#DatabaseList' AS table_name, * FROM #DatabaseList; @@ -22004,6 +22124,9 @@ BEGIN ct.create_tsql, CASE WHEN s.is_primary_key = 1 AND s.index_definition <> '[HEAP]' + THEN N'--ALTER TABLE ' + QUOTENAME(s.[database_name]) + N'.' + QUOTENAME(s.[schema_name]) + N'.' + QUOTENAME(s.[object_name]) + + N' DROP CONSTRAINT ' + QUOTENAME(s.index_name) + N';' + WHEN s.is_primary_key = 0 AND is_unique_constraint = 1 AND s.index_definition <> '[HEAP]' THEN N'--ALTER TABLE ' + QUOTENAME(s.[database_name]) + N'.' + QUOTENAME(s.[schema_name]) + N'.' + QUOTENAME(s.[object_name]) + N' DROP CONSTRAINT ' + QUOTENAME(s.index_name) + N';' WHEN s.is_primary_key = 0 AND s.index_definition <> '[HEAP]' @@ -23560,7 +23683,6 @@ BEGIN; OPTION ( RECOMPILE ); RAISERROR(N'check_id 33: Potential filtered indexes based on column names.', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, secret_columns, index_usage_summary, index_size_summary ) SELECT 33 AS check_id, @@ -23936,15 +24058,14 @@ BEGIN; N'Cascading Updates or Deletes' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, - N'Foreign Key ' + foreign_key_name + + N'Foreign Key ' + QUOTENAME(foreign_key_name) + N' on ' + QUOTENAME(parent_object_name) + N'(' + LTRIM(parent_fk_columns) + N')' + N' referencing ' + QUOTENAME(referenced_object_name) + N'(' + LTRIM(referenced_fk_columns) + N')' + N' has settings:' + CASE [delete_referential_action_desc] WHEN N'NO_ACTION' THEN N'' ELSE N' ON DELETE ' +[delete_referential_action_desc] END + CASE [update_referential_action_desc] WHEN N'NO_ACTION' THEN N'' ELSE N' ON UPDATE ' + [update_referential_action_desc] END AS details, - [fk].[database_name] - AS index_definition, + [fk].[database_name] AS index_definition, N'N/A' AS secret_columns, N'N/A' AS index_usage_summary, N'N/A' AS index_size_summary, @@ -23955,6 +24076,29 @@ BEGIN; OR [update_referential_action_desc] <> N'NO_ACTION') OPTION ( RECOMPILE ); + RAISERROR(N'check_id 72: Unindexed foreign keys.', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary, more_info ) + SELECT 72 AS check_id, + NULL AS index_sanity_id, + 150 AS Priority, + N'Abnormal Psychology' AS findings_group, + N'Unindexed Foreign Keys' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, + N'Foreign Key ' + QUOTENAME(foreign_key_name) + + N' on ' + QUOTENAME(parent_object_name) + N'' + + N' referencing ' + QUOTENAME(referenced_object_name) + N'' + + N' does not appear to have a supporting index.' AS details, + N'N/A' AS index_definition, + N'N/A' AS secret_columns, + N'N/A' AS index_usage_summary, + N'N/A' AS index_size_summary, + (SELECT TOP 1 more_info FROM #IndexSanity i WHERE i.object_id=fk.parent_object_id AND i.database_id = fk.database_id AND i.schema_name = fk.schema_name) + AS more_info + FROM #UnindexedForeignKeys AS fk + OPTION ( RECOMPILE ); + RAISERROR(N'check_id 73: In-Memory OLTP', 0,1) WITH NOWAIT; INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, @@ -24144,6 +24288,24 @@ BEGIN; FROM #TemporalTables AS t OPTION ( RECOMPILE ); + RAISERROR(N'check_id 121: Optimized For Sequental Keys.', 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 121 AS check_id, + 200 AS Priority, + 'Medicated Indexes' AS findings_group, + 'Optimized For Sequential Keys', + i.database_name, + '' AS URL, + 'The table ' + QUOTENAME(i.schema_name) + '.' + QUOTENAME(i.object_name) + ' is optimized for sequential keys.' AS details, + '' AS index_definition, + 'N/A' AS secret_columns, + 'N/A' AS index_usage_summary, + 'N/A' AS index_size_summary + FROM #IndexSanity AS i + WHERE i.optimize_for_sequential_key = 1 + OPTION ( RECOMPILE ); @@ -24504,7 +24666,8 @@ ELSE IF (@Mode=1) /*Summarize*/ [partition_key_column_name] NVARCHAR(MAX), [filter_definition] NVARCHAR(MAX), [is_indexed_view] BIT, - [is_primary_key] BIT, + [is_primary_key] BIT, + [is_unique_constraint] BIT, [is_XML] BIT, [is_spatial] BIT, [is_NC_columnstore] BIT, @@ -24614,7 +24777,8 @@ ELSE IF (@Mode=1) /*Summarize*/ [partition_key_column_name], [filter_definition], [is_indexed_view], - [is_primary_key], + [is_primary_key], + [is_unique_constraint], [is_XML], [is_spatial], [is_NC_columnstore], @@ -24677,6 +24841,9 @@ ELSE IF (@Mode=1) /*Summarize*/ WHEN i.is_primary_key = 1 AND i.index_definition <> ''[HEAP]'' THEN N''--ALTER TABLE '' + QUOTENAME(i.[database_name]) + N''.'' + QUOTENAME(i.[schema_name]) + N''.'' + QUOTENAME(i.[object_name]) + N'' DROP CONSTRAINT '' + QUOTENAME(i.index_name) + N'';'' + WHEN i.is_primary_key = 0 AND i.is_unique_constraint = 1 AND i.index_definition <> ''[HEAP]'' + THEN N''--ALTER TABLE '' + QUOTENAME(i.[database_name]) + N''.'' + QUOTENAME(i.[schema_name]) + N''.'' + QUOTENAME(i.[object_name]) + + N'' DROP CONSTRAINT '' + QUOTENAME(i.index_name) + N'';'' WHEN i.is_primary_key = 0 AND i.index_definition <> ''[HEAP]'' THEN N''--DROP INDEX ''+ QUOTENAME(i.index_name) + N'' ON '' + QUOTENAME(i.[database_name]) + N''.'' + QUOTENAME(i.[schema_name]) + N''.'' + QUOTENAME(i.[object_name]) + N'';'' @@ -24701,6 +24868,7 @@ ELSE IF (@Mode=1) /*Summarize*/ ISNULL(filter_definition, '''') AS [Filter Definition], is_indexed_view AS [Is Indexed View], is_primary_key AS [Is Primary Key], + is_unique_constraint AS [Is Unique Constraint], is_XML AS [Is XML], is_spatial AS [Is Spatial], is_NC_columnstore AS [Is NC Columnstore], @@ -24794,6 +24962,7 @@ ELSE IF (@Mode=1) /*Summarize*/ ISNULL(filter_definition, '') AS [Filter Definition], is_indexed_view AS [Is Indexed View], is_primary_key AS [Is Primary Key], + is_unique_constraint AS [Is Unique Constraint] , is_XML AS [Is XML], is_spatial AS [Is Spatial], is_NC_columnstore AS [Is NC Columnstore], @@ -24846,6 +25015,9 @@ ELSE IF (@Mode=1) /*Summarize*/ WHEN i.is_primary_key = 1 AND i.index_definition <> '[HEAP]' THEN N'--ALTER TABLE ' + QUOTENAME(i.[database_name]) + N'.' + QUOTENAME(i.[schema_name]) + N'.' + QUOTENAME(i.[object_name]) + N' DROP CONSTRAINT ' + QUOTENAME(i.index_name) + N';' + WHEN i.is_primary_key = 0 AND i.is_unique_constraint = 1 AND i.index_definition <> '[HEAP]' + THEN N'--ALTER TABLE ' + QUOTENAME(i.[database_name]) + N'.' + QUOTENAME(i.[schema_name]) + N'.' + QUOTENAME(i.[object_name]) + + N' DROP CONSTRAINT ' + QUOTENAME(i.index_name) + N';' WHEN i.is_primary_key = 0 AND i.index_definition <> '[HEAP]' THEN N'--DROP INDEX '+ QUOTENAME(i.index_name) + N' ON ' + QUOTENAME(i.[database_name]) + N'.' + QUOTENAME(i.[schema_name]) + N'.' + QUOTENAME(i.[object_name]) + N';' @@ -25022,7 +25194,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.05', @VersionDate = '20210725'; +SELECT @Version = '8.06', @VersionDate = '20210914'; IF(@VersionCheckMode = 1) @@ -26829,7 +27001,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.05', @VersionDate = '20210725'; +SELECT @Version = '8.06', @VersionDate = '20210914'; IF(@VersionCheckMode = 1) BEGIN RETURN; @@ -32559,7 +32731,7 @@ BEGIN SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.05', @VersionDate = '20210725'; + SELECT @Version = '8.06', @VersionDate = '20210914'; IF(@VersionCheckMode = 1) BEGIN @@ -32814,6 +32986,13 @@ IF @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @Output ALTER TABLE ' + @ObjectFullName + N' ADD outer_command NVARCHAR(4000) NULL;'; EXEC(@StringToExecute); + /* If the table doesn't have the new wait_resource column, add it. See Github #2970. */ + SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableName; + SET @StringToExecute = N'IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns + WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''wait_resource'') + ALTER TABLE ' + @ObjectFullName + N' ADD wait_resource NVARCHAR(MAX) NULL;'; + EXEC(@StringToExecute); + /* Delete history older than @OutputTableRetentionDays */ SET @OutputTableCleanupDate = CAST( (DATEADD(DAY, -1 * @OutputTableRetentionDays, GETDATE() ) ) AS DATE); SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' @@ -33941,6 +34120,7 @@ DELETE FROM dbo.SqlServerVersions; INSERT INTO dbo.SqlServerVersions (MajorVersionNumber, MinorVersionNumber, Branch, [Url], ReleaseDate, MainstreamSupportEndDate, ExtendedSupportEndDate, MajorVersionName, MinorVersionName) VALUES + (15, 4153, 'CU12', 'https://support.microsoft.com/en-us/help/5004524', '2021-08-04', '2025-01-01', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 12'), (15, 4138, 'CU11', 'https://support.microsoft.com/en-us/help/5003249', '2021-06-10', '2025-01-01', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 11'), (15, 4123, 'CU10', 'https://support.microsoft.com/en-us/help/5001090', '2021-04-06', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 10'), (15, 4102, 'CU9', 'https://support.microsoft.com/en-us/help/5000642', '2021-02-11', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 9 '), @@ -33955,7 +34135,7 @@ VALUES (15, 4003, 'CU1', 'https://support.microsoft.com/en-us/help/4527376', '2020-01-07', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 1 '), (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 '), - (14, 3410, 'RTM CU25', 'https://support.microsoft.com/en-us/help/5003830', '2021-07-12', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 25'), + (14, 3401, 'RTM CU25', 'https://support.microsoft.com/en-us/help/5003830', '2021-07-12', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 25'), (14, 3391, 'RTM CU24', 'https://support.microsoft.com/en-us/help/5001228', '2021-05-10', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 24'), (14, 3381, 'RTM CU23', 'https://support.microsoft.com/en-us/help/5000685', '2021-02-25', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 23'), (14, 3370, 'RTM CU22 GDR', 'https://support.microsoft.com/en-us/help/4583457', '2021-01-12', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 22 GDR'), @@ -34335,7 +34515,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.05', @VersionDate = '20210725'; +SELECT @Version = '8.06', @VersionDate = '20210914'; IF(@VersionCheckMode = 1) BEGIN @@ -35806,28 +35986,32 @@ BEGIN 'https://www.brentozar.com/askbrent/backups/' AS URL, 'Backup of ' + DB_NAME(db.resource_database_id) + ' database (' + (SELECT CAST(CAST(SUM(size * 8.0 / 1024 / 1024) AS BIGINT) AS NVARCHAR) FROM #MasterFiles WHERE database_id = db.resource_database_id) + 'GB) ' + @LineFeed + CAST(r.percent_complete AS NVARCHAR(100)) + '% complete, has been running since ' + CAST(r.start_time AS NVARCHAR(100)) + '. ' + @LineFeed - + CASE WHEN COALESCE(s.nt_user_name, s.login_name) IS NOT NULL THEN (' Login: ' + COALESCE(s.nt_user_name, s.login_name) + ' ') ELSE '' END AS Details, + + CASE WHEN COALESCE(s.nt_username, s.loginame) IS NOT NULL THEN (' Login: ' + COALESCE(s.nt_username, s.loginame) + ' ') ELSE '' END AS Details, 'KILL ' + CAST(r.session_id AS NVARCHAR(100)) + ';' AS HowToStopIt, pl.query_plan AS QueryPlan, r.start_time AS StartTime, - s.login_name AS LoginName, - s.nt_user_name AS NTUserName, + s.loginame AS LoginName, + s.nt_username AS NTUserName, s.[program_name] AS ProgramName, - s.[host_name] AS HostName, + s.[hostname] AS HostName, db.[resource_database_id] AS DatabaseID, DB_NAME(db.resource_database_id) AS DatabaseName, 0 AS OpenTransactionCount, r.query_hash FROM sys.dm_exec_requests r INNER JOIN sys.dm_exec_connections c ON r.session_id = c.session_id - INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id - INNER JOIN ( - SELECT DISTINCT request_session_id, resource_database_id - FROM sys.dm_tran_locks - WHERE resource_type = N'DATABASE' - AND request_mode = N'S' - AND request_status = N'GRANT' - AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.session_id = db.request_session_id AND s.database_id = db.resource_database_id + INNER JOIN sys.sysprocesses AS s ON r.session_id = s.spid AND s.ecid = 0 + INNER JOIN + ( + SELECT DISTINCT + t.request_session_id, + t.resource_database_id + FROM sys.dm_tran_locks AS t + WHERE t.resource_type = N'DATABASE' + AND t.request_mode = N'S' + AND t.request_status = N'GRANT' + AND t.request_owner_type = N'SHARED_TRANSACTION_WORKSPACE' + ) AS db ON s.spid = db.request_session_id AND s.dbid = db.resource_database_id CROSS APPLY sys.dm_exec_query_plan(r.plan_handle) pl WHERE r.command LIKE 'BACKUP%' AND r.start_time <= DATEADD(minute, -5, GETDATE()) @@ -36044,30 +36228,30 @@ BEGIN 'Query Problems' AS FindingGroup, 'Sleeping Query with Open Transactions' AS Finding, 'https://www.brentozar.com/askbrent/sleeping-query-with-open-transactions/' AS URL, - 'Database: ' + DB_NAME(db.resource_database_id) + @LineFeed + 'Host: ' + s.[host_name] + @LineFeed + 'Program: ' + s.[program_name] + @LineFeed + 'Asleep with open transactions and locks since ' + CAST(s.last_request_end_time AS NVARCHAR(100)) + '. ' AS Details, - 'KILL ' + CAST(s.session_id AS NVARCHAR(100)) + ';' AS HowToStopIt, - s.last_request_start_time AS StartTime, - s.login_name AS LoginName, - s.nt_user_name AS NTUserName, + 'Database: ' + DB_NAME(db.resource_database_id) + @LineFeed + 'Host: ' + s.hostname + @LineFeed + 'Program: ' + s.[program_name] + @LineFeed + 'Asleep with open transactions and locks since ' + CAST(s.last_batch AS NVARCHAR(100)) + '. ' AS Details, + 'KILL ' + CAST(s.spid AS NVARCHAR(100)) + ';' AS HowToStopIt, + s.last_batch AS StartTime, + s.loginame AS LoginName, + s.nt_username AS NTUserName, s.[program_name] AS ProgramName, - s.[host_name] AS HostName, + s.hostname AS HostName, db.[resource_database_id] AS DatabaseID, DB_NAME(db.resource_database_id) AS DatabaseName, (SELECT TOP 1 [text] FROM sys.dm_exec_sql_text(c.most_recent_sql_handle)) AS QueryText, - s.open_transaction_count AS OpenTransactionCount - FROM sys.dm_exec_sessions s - INNER JOIN sys.dm_exec_connections c ON s.session_id = c.session_id + s.open_tran AS OpenTransactionCount + FROM sys.sysprocesses s + INNER JOIN sys.dm_exec_connections c ON s.spid = c.session_id INNER JOIN ( SELECT DISTINCT request_session_id, resource_database_id FROM sys.dm_tran_locks WHERE resource_type = N'DATABASE' AND request_mode = N'S' AND request_status = N'GRANT' - AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.session_id = db.request_session_id + AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.spid = db.request_session_id WHERE s.status = 'sleeping' - AND s.open_transaction_count > 0 - AND s.last_request_end_time < DATEADD(ss, -10, SYSDATETIME()) - AND EXISTS(SELECT * FROM sys.dm_tran_locks WHERE request_session_id = s.session_id + AND s.open_tran > 0 + AND s.last_batch < DATEADD(ss, -10, SYSDATETIME()) + AND EXISTS(SELECT * FROM sys.dm_tran_locks WHERE request_session_id = s.spid AND NOT (resource_type = N'DATABASE' AND request_mode = N'S' AND request_status = N'GRANT' AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE')); END @@ -37415,7 +37599,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, This has to be done as dynamic SQL because we have to execute OBJECT_NAME inside TempDB. */ IF @@ROWCOUNT > 0 BEGIN - SET @StringToExecute = N'USE tempdb; + SET @StringToExecute = N' INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) SELECT TOP 10 29 AS CheckID, 40 AS Priority, diff --git a/sp_AllNightLog.sql b/sp_AllNightLog.sql index 98f7e4156..8ff4e525b 100644 --- a/sp_AllNightLog.sql +++ b/sp_AllNightLog.sql @@ -31,7 +31,7 @@ SET STATISTICS XML OFF; BEGIN; -SELECT @Version = '8.05', @VersionDate = '20210725'; +SELECT @Version = '8.06', @VersionDate = '20210914'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_AllNightLog_Setup.sql b/sp_AllNightLog_Setup.sql index e309d8931..de581dea4 100644 --- a/sp_AllNightLog_Setup.sql +++ b/sp_AllNightLog_Setup.sql @@ -37,7 +37,7 @@ SET STATISTICS XML OFF; BEGIN; -SELECT @Version = '8.05', @VersionDate = '20210725'; +SELECT @Version = '8.06', @VersionDate = '20210914'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_Blitz.sql b/sp_Blitz.sql index 3e295effc..daaf7fed3 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -38,7 +38,7 @@ AS SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.05', @VersionDate = '20210725'; + SELECT @Version = '8.06', @VersionDate = '20210914'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) diff --git a/sp_BlitzAnalysis.sql b/sp_BlitzAnalysis.sql index 7f22ce5f0..89cd5a5ec 100644 --- a/sp_BlitzAnalysis.sql +++ b/sp_BlitzAnalysis.sql @@ -37,7 +37,7 @@ AS SET NOCOUNT ON; SET STATISTICS XML OFF; -SELECT @Version = '8.05', @VersionDate = '20210725'; +SELECT @Version = '8.06', @VersionDate = '20210914'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_BlitzBackups.sql b/sp_BlitzBackups.sql index 036a89d9c..ae281a1de 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.05', @VersionDate = '20210725'; + SELECT @Version = '8.06', @VersionDate = '20210914'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_BlitzCache.sql b/sp_BlitzCache.sql index 1a9d7e92a..5c35679cd 100644 --- a/sp_BlitzCache.sql +++ b/sp_BlitzCache.sql @@ -280,7 +280,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.05', @VersionDate = '20210725'; +SELECT @Version = '8.06', @VersionDate = '20210914'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) diff --git a/sp_BlitzFirst.sql b/sp_BlitzFirst.sql index 109096b5c..07b58b281 100644 --- a/sp_BlitzFirst.sql +++ b/sp_BlitzFirst.sql @@ -46,7 +46,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.05', @VersionDate = '20210725'; +SELECT @Version = '8.06', @VersionDate = '20210914'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_BlitzInMemoryOLTP.sql b/sp_BlitzInMemoryOLTP.sql index d0e538859..3a961cd4e 100644 --- a/sp_BlitzInMemoryOLTP.sql +++ b/sp_BlitzInMemoryOLTP.sql @@ -82,7 +82,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI */ AS DECLARE @ScriptVersion VARCHAR(30); -SELECT @ScriptVersion = '1.8', @VersionDate = '20210725'; +SELECT @ScriptVersion = '1.8', @VersionDate = '20210914'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index 677078152..2dcf98f4d 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -48,7 +48,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.05', @VersionDate = '20210725'; +SELECT @Version = '8.06', @VersionDate = '20210914'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index 41bee1532..48e0de2a6 100644 --- a/sp_BlitzLock.sql +++ b/sp_BlitzLock.sql @@ -33,7 +33,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.05', @VersionDate = '20210725'; +SELECT @Version = '8.06', @VersionDate = '20210914'; IF(@VersionCheckMode = 1) diff --git a/sp_BlitzQueryStore.sql b/sp_BlitzQueryStore.sql index 3b414f7e1..00f01357d 100644 --- a/sp_BlitzQueryStore.sql +++ b/sp_BlitzQueryStore.sql @@ -57,7 +57,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.05', @VersionDate = '20210725'; +SELECT @Version = '8.06', @VersionDate = '20210914'; IF(@VersionCheckMode = 1) BEGIN RETURN; diff --git a/sp_BlitzWho.sql b/sp_BlitzWho.sql index 2f7dccb60..a67d6c562 100644 --- a/sp_BlitzWho.sql +++ b/sp_BlitzWho.sql @@ -32,7 +32,7 @@ BEGIN SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.05', @VersionDate = '20210725'; + SELECT @Version = '8.06', @VersionDate = '20210914'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_DatabaseRestore.sql b/sp_DatabaseRestore.sql index d518db4e7..4d39f57ec 100755 --- a/sp_DatabaseRestore.sql +++ b/sp_DatabaseRestore.sql @@ -42,7 +42,7 @@ SET STATISTICS XML OFF; /*Versioning details*/ -SELECT @Version = '8.05', @VersionDate = '20210725'; +SELECT @Version = '8.06', @VersionDate = '20210914'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_ineachdb.sql b/sp_ineachdb.sql index 4d1bb9634..b9aff6189 100644 --- a/sp_ineachdb.sql +++ b/sp_ineachdb.sql @@ -35,7 +35,7 @@ BEGIN SET NOCOUNT ON; SET STATISTICS XML OFF; - SELECT @Version = '8.05', @VersionDate = '20210725'; + SELECT @Version = '8.06', @VersionDate = '20210914'; IF(@VersionCheckMode = 1) BEGIN From a27d939418aee765ca8f34e82b5b597d65ab3a7b Mon Sep 17 00:00:00 2001 From: Anthony Green <40231097+Ant-Green@users.noreply.github.com> Date: Thu, 16 Sep 2021 08:12:13 +0100 Subject: [PATCH 246/662] Add 17-CU26, 16-SP3 Add 17-CU26, 16-SP3 --- SqlServerVersions.sql | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/SqlServerVersions.sql b/SqlServerVersions.sql index 0be6fb83d..69ebfd6b5 100644 --- a/SqlServerVersions.sql +++ b/SqlServerVersions.sql @@ -56,7 +56,8 @@ VALUES (15, 4003, 'CU1', 'https://support.microsoft.com/en-us/help/4527376', '2020-01-07', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 1 '), (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 '), - (14, 3401, 'RTM CU25', 'https://support.microsoft.com/en-us/help/5003830', '2021-07-12', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 25'), + (14, 3411, 'RTM CU26', 'https://support.microsoft.com/en-us/help/5005226', '2021-09-14', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 26'), + (14, 3401, 'RTM CU25', 'https://support.microsoft.com/en-us/help/5003830', '2021-07-12', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 25'), (14, 3391, 'RTM CU24', 'https://support.microsoft.com/en-us/help/5001228', '2021-05-10', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 24'), (14, 3381, 'RTM CU23', 'https://support.microsoft.com/en-us/help/5000685', '2021-02-25', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 23'), (14, 3370, 'RTM CU22 GDR', 'https://support.microsoft.com/en-us/help/4583457', '2021-01-12', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 22 GDR'), @@ -83,7 +84,8 @@ VALUES (14, 3008, 'RTM CU2', 'https://support.microsoft.com/en-us/help/4052574', '2017-11-28', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 2'), (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 '), - (13, 5888, 'SP2 CU17', 'https://support.microsoft.com/en-us/help/5001092', '2021-03-29', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 17'), + (13, 6300, 'SP3 ', 'https://support.microsoft.com/en-us/help/5003279', '2021-09-15', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3'), + (13, 5888, 'SP2 CU17', 'https://support.microsoft.com/en-us/help/5001092', '2021-03-29', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 17'), (13, 5882, 'SP2 CU16', 'https://support.microsoft.com/en-us/help/5000645', '2021-02-11', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 16'), (13, 5865, 'SP2 CU15 GDR', 'https://support.microsoft.com/en-us/help/4583461', '2021-01-12', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 15 GDR'), (13, 5850, 'SP2 CU15', 'https://support.microsoft.com/en-us/help/4577775', '2020-09-28', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 15'), From 2254bdb8e3c9e59ba375d9baf3aefe4e1e531c2d Mon Sep 17 00:00:00 2001 From: andreasjordan Date: Mon, 20 Sep 2021 17:57:14 +0200 Subject: [PATCH 247/662] Fix output to table if called from sp_BlitzFirst --- sp_BlitzCache.sql | 41 ++++++++++++++++++++++------------------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/sp_BlitzCache.sql b/sp_BlitzCache.sql index 5c35679cd..24738e282 100644 --- a/sp_BlitzCache.sql +++ b/sp_BlitzCache.sql @@ -6715,15 +6715,17 @@ SET @AllSortSql += N' END; END; - SET @AllSortSql += N' SELECT DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters,ExecutionCount, ExecutionsPerMinute, ExecutionWeight, - TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, - ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache, Pattern - FROM #bou_allsort - ORDER BY Id - OPTION(RECOMPILE); '; - - + + IF(@OutputType <> 'NONE') + BEGIN + SET @AllSortSql += N' SELECT DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters,ExecutionCount, ExecutionsPerMinute, ExecutionWeight, + TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, + ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, + MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache, Pattern + FROM #bou_allsort + ORDER BY Id + OPTION(RECOMPILE); '; + END; END; @@ -6891,13 +6893,16 @@ SET @AllSortSql += N' END; - SET @AllSortSql += N' SELECT DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters,ExecutionCount, ExecutionsPerMinute, ExecutionWeight, - TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, - ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache, Pattern - FROM #bou_allsort - ORDER BY Id - OPTION(RECOMPILE); '; + IF(@OutputType <> 'NONE') + BEGIN + SET @AllSortSql += N' SELECT DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters,ExecutionCount, ExecutionsPerMinute, ExecutionWeight, + TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, + ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, + MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache, Pattern + FROM #bou_allsort + ORDER BY Id + OPTION(RECOMPILE); '; + END; END; IF @Debug = 1 @@ -6913,12 +6918,10 @@ END; PRINT SUBSTRING(@AllSortSql, 32000, 36000); PRINT SUBSTRING(@AllSortSql, 36000, 40000); END; -IF(@OutputType <> 'NONE') -BEGIN + EXEC sys.sp_executesql @stmt = @AllSortSql, @params = N'@i_DatabaseName NVARCHAR(128), @i_Top INT, @i_SkipAnalysis BIT, @i_OutputDatabaseName NVARCHAR(258), @i_OutputSchemaName NVARCHAR(258), @i_OutputTableName NVARCHAR(258), @i_CheckDateOverride DATETIMEOFFSET, @i_MinutesBack INT ', @i_DatabaseName = @DatabaseName, @i_Top = @Top, @i_SkipAnalysis = @SkipAnalysis, @i_OutputDatabaseName = @OutputDatabaseName, @i_OutputSchemaName = @OutputSchemaName, @i_OutputTableName = @OutputTableName, @i_CheckDateOverride = @CheckDateOverride, @i_MinutesBack = @MinutesBack; -END; /*End of AllSort section*/ From 1aef8f9e76e1481fad86f1b8357887667eee0147 Mon Sep 17 00:00:00 2001 From: Greg Dodd Date: Thu, 23 Sep 2021 10:26:13 +1000 Subject: [PATCH 248/662] Add parameter to optionally include live query plans Fetching and shredding the XML on Live Query Plans sometimes causes the query to die and a stack dump to be created for the query, so we should only include Live Query Plans if the user asks for them. --- sp_BlitzWho.sql | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/sp_BlitzWho.sql b/sp_BlitzWho.sql index a67d6c562..06aad298c 100644 --- a/sp_BlitzWho.sql +++ b/sp_BlitzWho.sql @@ -22,6 +22,7 @@ ALTER PROCEDURE dbo.sp_BlitzWho @CheckDateOverride DATETIMEOFFSET = NULL, @ShowActualParameters BIT = 0, @GetOuterCommand BIT = 0, + @IncludeLiveQueryPlan BIT = 0, @Version VARCHAR(30) = NULL OUTPUT, @VersionDate DATETIME = NULL OUTPUT, @VersionCheckMode BIT = 0, @@ -659,7 +660,7 @@ SELECT @BlockingCheck = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; ); ' -IF EXISTS (SELECT * FROM sys.all_columns WHERE object_id = OBJECT_ID('sys.dm_exec_query_statistics_xml') AND name = 'query_plan') +IF EXISTS (SELECT * FROM sys.all_columns WHERE object_id = OBJECT_ID('sys.dm_exec_query_statistics_xml') AND name = 'query_plan' AND @IncludeLiveQueryPlan=1) BEGIN SET @BlockingCheck = @BlockingCheck + N' INSERT INTO @LiveQueryPlans @@ -908,7 +909,14 @@ IF @ProductVersionMajor >= 11 ELSE N'' END+N' derp.query_plan , - CAST(COALESCE(qs_live.Query_Plan, '''') AS XML) AS live_query_plan , + CAST(COALESCE(qs_live.Query_Plan, ' + CASE WHEN @IncludeLiveQueryPlan=1 + THEN '''''' + ELSE '''''' + END + +') AS XML + + + ) AS live_query_plan , STUFF((SELECT DISTINCT N'', '' + Node.Data.value(''(@Column)[1]'', ''NVARCHAR(4000)'') + N'' {'' + Node.Data.value(''(@ParameterDataType)[1]'', ''NVARCHAR(4000)'') + N''}: '' + Node.Data.value(''(@ParameterCompiledValue)[1]'', ''NVARCHAR(4000)'') FROM derp.query_plan.nodes(''/*:ShowPlanXML/*:BatchSequence/*:Batch/*:Statements/*:StmtSimple/*:QueryPlan/*:ParameterList/*:ColumnReference'') AS Node(Data) FOR XML PATH('''')), 1,2,'''') From e50319ffb1cb19fda468e8b01c707647c2f039ba Mon Sep 17 00:00:00 2001 From: Erik Darling Date: Tue, 5 Oct 2021 15:29:03 -0400 Subject: [PATCH 249/662] Closes #3006 Adds columns to unique constraint script definition Closes #3006 --- sp_BlitzIndex.sql | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index 0ca8aab69..dfe75052e 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -2521,7 +2521,9 @@ SELECT N'.' + QUOTENAME([object_name]) + N' ADD CONSTRAINT [' + index_name + - N'] UNIQUE' + N'] UNIQUE ' + + CASE WHEN index_id=1 THEN N'CLUSTERED (' ELSE N'(' END + + key_column_names_with_sort_order_no_types + N' )' WHEN is_CX_columnstore= 1 THEN N'CREATE CLUSTERED COLUMNSTORE INDEX ' + QUOTENAME(index_name) + N' on ' + QUOTENAME([database_name]) + N'.' + QUOTENAME([schema_name]) + N'.' + QUOTENAME([object_name]) ELSE /*Else not a PK or cx columnstore */ From 7e7fb9030f96e8bfea3c603b052a3767ae777ee7 Mon Sep 17 00:00:00 2001 From: Anthony Green <40231097+Ant-Green@users.noreply.github.com> Date: Wed, 6 Oct 2021 08:05:46 +0100 Subject: [PATCH 250/662] Added Cu13 for 2019 Added Cu13 for 2019 and fixed mainstream dates for cu11/cu12 --- SqlServerVersions.sql | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/SqlServerVersions.sql b/SqlServerVersions.sql index 69ebfd6b5..8d43493c3 100644 --- a/SqlServerVersions.sql +++ b/SqlServerVersions.sql @@ -41,8 +41,9 @@ DELETE FROM dbo.SqlServerVersions; INSERT INTO dbo.SqlServerVersions (MajorVersionNumber, MinorVersionNumber, Branch, [Url], ReleaseDate, MainstreamSupportEndDate, ExtendedSupportEndDate, MajorVersionName, MinorVersionName) VALUES - (15, 4153, 'CU12', 'https://support.microsoft.com/en-us/help/5004524', '2021-08-04', '2025-01-01', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 12'), - (15, 4138, 'CU11', 'https://support.microsoft.com/en-us/help/5003249', '2021-06-10', '2025-01-01', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 11'), + (15, 4178, 'CU13', 'https://support.microsoft.com/en-us/help/5005679', '2021-10-05', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 13'), + (15, 4153, 'CU12', 'https://support.microsoft.com/en-us/help/5004524', '2021-08-04', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 12'), + (15, 4138, 'CU11', 'https://support.microsoft.com/en-us/help/5003249', '2021-06-10', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 11'), (15, 4123, 'CU10', 'https://support.microsoft.com/en-us/help/5001090', '2021-04-06', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 10'), (15, 4102, 'CU9', 'https://support.microsoft.com/en-us/help/5000642', '2021-02-11', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 9 '), (15, 4073, 'CU8 GDR', 'https://support.microsoft.com/en-us/help/4583459', '2021-01-12', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 8 GDR '), From 4c2ee65c64f17ff9bd259a8fec49ecf42073aa62 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Sat, 16 Oct 2021 03:45:34 -0700 Subject: [PATCH 251/662] Updated documentation for sp_BlitzCache About read-only intent databases. --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 901cfffce..efa032621 100644 --- a/README.md +++ b/README.md @@ -194,6 +194,10 @@ In addition to the [parameters common to many of the stored procedures](#paramet * @DatabaseName - if you only want to analyze plans in a single database. However, keep in mind that this is only the database context. A single query that runs in Database1 can join across objects in Database2 and Database3, but we can only know that it ran in Database1. * @SlowlySearchPlansFor - lets you search for strings, but will not find all results due to a [bug in the way SQL Server removes spaces from XML.](https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2202) If your search string includes spaces, SQL Server may remove those before the search runs, unfortunately. +### sp_BlitzCache Known Issues + +* We skip databases in an Availability Group that require read-only intent. If you wanted to contribute code to enable read-only intent databases to work, look for this phrase in the code: "Checking for Read intent databases to exclude". + [*Back to top*](#header1) ## sp_BlitzFirst: Real-Time Performance Advice @@ -443,7 +447,7 @@ ORDER BY [CheckDate] ASC, [counter_name] ASC ``` - + [*Back to top*](#header1) From 06f8a04d9d1856ccb1e4d464f323f2f397c1aeda Mon Sep 17 00:00:00 2001 From: Greg Dodd Date: Sat, 16 Oct 2021 22:04:39 +1100 Subject: [PATCH 252/662] Removed sp_DatabaseRestore change --- sp_DatabaseRestore.sql | 3 --- 1 file changed, 3 deletions(-) diff --git a/sp_DatabaseRestore.sql b/sp_DatabaseRestore.sql index 642cb94d6..4d39f57ec 100755 --- a/sp_DatabaseRestore.sql +++ b/sp_DatabaseRestore.sql @@ -27,7 +27,6 @@ ALTER PROCEDURE [dbo].[sp_DatabaseRestore] @StopAt NVARCHAR(14) = NULL, @OnlyLogsAfter NVARCHAR(14) = NULL, @SimpleFolderEnumeration BIT = 0, - @DatabaseOwner SYSNAME = NULL, @SkipBackupsAlreadyInMsdb BIT = 0, @DatabaseOwner sysname = NULL, @SetTrustworthyON BIT = 0, @@ -1448,8 +1447,6 @@ IF @DatabaseOwner IS NOT NULL BEGIN IF @RunRecovery = 1 BEGIN - SET @sql = N'ALTER AUTHORIZATION ON DATABASE::' + @RestoreDatabaseName + ' TO [' + @databaseowner + ']'; - SET @sql = N'ALTER AUTHORIZATION ON DATABASE::' + @RestoreDatabaseName + ' TO [' + @DatabaseOwner + ']'; IF EXISTS (SELECT * FROM master.dbo.syslogins WHERE syslogins.loginname = @DatabaseOwner) BEGIN SET @sql = N'ALTER AUTHORIZATION ON DATABASE::' + @RestoreDatabaseName + ' TO [' + @DatabaseOwner + ']'; From ee1e41895fdcfac766b1a8c6a5da1f24061f6f31 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Sat, 16 Oct 2021 04:17:02 -0700 Subject: [PATCH 253/662] #2591 sp_BlitzWho GetLiveQueryPlan Changed the IncludeLiveQueryPlan to GetLiveQueryPlan just to make it more consistent with other params. Closes #2591. --- sp_BlitzWho.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sp_BlitzWho.sql b/sp_BlitzWho.sql index 06aad298c..e0e2bfe9d 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, - @IncludeLiveQueryPlan BIT = 0, + @GetLiveQueryPlan BIT = 0, @Version VARCHAR(30) = NULL OUTPUT, @VersionDate DATETIME = NULL OUTPUT, @VersionCheckMode BIT = 0, From c01bcd5de735ed1b5baac5a08fe0135c3c9ee7b1 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Sat, 16 Oct 2021 04:18:59 -0700 Subject: [PATCH 254/662] #2591 sp_BlitzWho GetLiveQueryPlan I'm an idiot. I changed the param name but not all the places it was used in code. Closes #2591 again. --- sp_BlitzWho.sql | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sp_BlitzWho.sql b/sp_BlitzWho.sql index e0e2bfe9d..c70db5a5a 100644 --- a/sp_BlitzWho.sql +++ b/sp_BlitzWho.sql @@ -660,7 +660,7 @@ SELECT @BlockingCheck = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; ); ' -IF EXISTS (SELECT * FROM sys.all_columns WHERE object_id = OBJECT_ID('sys.dm_exec_query_statistics_xml') AND name = 'query_plan' AND @IncludeLiveQueryPlan=1) +IF EXISTS (SELECT * FROM sys.all_columns WHERE object_id = OBJECT_ID('sys.dm_exec_query_statistics_xml') AND name = 'query_plan' AND @GetLiveQueryPlan=1) BEGIN SET @BlockingCheck = @BlockingCheck + N' INSERT INTO @LiveQueryPlans @@ -909,9 +909,9 @@ IF @ProductVersionMajor >= 11 ELSE N'' END+N' derp.query_plan , - CAST(COALESCE(qs_live.Query_Plan, ' + CASE WHEN @IncludeLiveQueryPlan=1 + CAST(COALESCE(qs_live.Query_Plan, ' + CASE WHEN @GetLiveQueryPlan=1 THEN '''''' - ELSE '''''' + ELSE '''''' END +') AS XML From 99c3073b26d255fde928ef0e3076e7600e687596 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Wed, 20 Oct 2021 07:01:30 -0700 Subject: [PATCH 255/662] Removing Power BI Dashboard SInce we no longer support that. --- README.md | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index efa032621..2265b40d6 100644 --- a/README.md +++ b/README.md @@ -220,7 +220,7 @@ Common sp_BlitzFirst parameters include: ### Logging sp_BlitzFirst to Tables -You can log sp_BlitzFirst performance data to tables and then analyze the results with the Power BI dashboard. To do it, schedule an Agent job to run sp_BlitzFirst every 15 minutes with these parameters populated: +You can log sp_BlitzFirst performance data to tables by scheduling an Agent job to run sp_BlitzFirst every 15 minutes with these parameters populated: * @OutputDatabaseName = typically 'DBAtools' * @OutputSchemaName = 'dbo' @@ -233,16 +233,14 @@ You can log sp_BlitzFirst performance data to tables and then analyze the result All of the above OutputTableName parameters are optional: if you don't want to collect all of the stats, you don't have to. Keep in mind that the sp_BlitzCache results will get large, fast, because each execution plan is megabytes in size. -Then fire up the [First Responder Kit Power BI dashboard.](https://www.brentozar.com/first-aid/first-responder-kit-power-bi-dashboard/) - ### Logging Performance Tuning Activities -On the Power BI Dashboard, you can show lines for your own activities like tuning queries, adding indexes, or changing configuration settings. To do it, run sp_BlitzFirst with these parameters: +You can also log your own activities like tuning queries, adding indexes, or changing configuration settings. To do it, run sp_BlitzFirst with these parameters: * @OutputDatabaseName = typically 'DBAtools' * @OutputSchemaName = 'dbo' * @OutputTableName = 'BlitzFirst' - the quick diagnosis result set goes here -* @LogMessage = 'Whatever you wanna show in the Power BI dashboard' +* @LogMessage = 'Whatever you wanna show in your monitoring tool' Optionally, you can also pass in: From d74a9a79bc38e553ce53b3d91ad5b60ab6911a67 Mon Sep 17 00:00:00 2001 From: James Gallagher Date: Wed, 20 Oct 2021 16:29:00 +0100 Subject: [PATCH 256/662] Correct 1204 TF list reporting --- sp_Blitz.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index daaf7fed3..5919a40e3 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -7770,7 +7770,7 @@ IF @ProductVersionMajor >= 10 WHEN [T].[TraceFlag] = '1117' THEN '1117 enabled globally, which grows all files in a filegroup at the same time.' WHEN [T].[TraceFlag] = '1118' THEN '1118 enabled globally, which tries to reduce SGAM waits.' WHEN [T].[TraceFlag] = '1211' THEN '1211 enabled globally, which disables lock escalation when you least expect it. This is usually a very bad idea.' - WHEN [T].[TraceFlag] = '1204' THEN '1222 enabled globally, which captures deadlock graphs in the error log.' + WHEN [T].[TraceFlag] = '1204' THEN '1204 enabled globally, which captures deadlock graphs in the error log.' WHEN [T].[TraceFlag] = '1222' THEN '1222 enabled globally, which captures deadlock graphs in the error log.' WHEN [T].[TraceFlag] = '1224' THEN '1224 enabled globally, which disables lock escalation until the server has memory pressure. This is usually a very bad idea.' WHEN [T].[TraceFlag] = '1806' THEN '1806 enabled globally, which disables Instant File Initialization, causing restores and file growths to take longer. This is usually a very bad idea.' From 4adcc1729211f0a002befd2f616e4b2cc196440d Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Sat, 23 Oct 2021 05:31:07 -0700 Subject: [PATCH 257/662] #3017 readme.md remove image Closes #3017. --- README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index 2265b40d6..94fc307a6 100644 --- a/README.md +++ b/README.md @@ -73,9 +73,7 @@ When you have a question about what the scripts found, first make sure you read ## sp_Blitz: Overall Health Check -Run sp_Blitz daily or weekly for an overall health check. Just run it from SQL Server Management Studio, and you'll get a prioritized list of issues on your server right now: - -![sp_Blitz](http://u.brentozar.com/github-images/sp_Blitz.png) +Run sp_Blitz daily or weekly for an overall health check. Just run it from SQL Server Management Studio, and you'll get a prioritized list of issues on your server right now. Output columns include: From d6414aaaca30ce81e751e2d854628100cb2baa56 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Sat, 23 Oct 2021 05:35:16 -0700 Subject: [PATCH 258/662] #3018 sp_Blitz rundate in markdown Closes #3018. --- sp_Blitz.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index daaf7fed3..b2327e0d0 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -9092,7 +9092,7 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 'Rundate' , GETDATE() , 'http://FirstResponderKit.org/' , - 'Captain''s log: stardate something and something...'; + GETDATE(); IF @EmailRecipients IS NOT NULL BEGIN From c7a2137381b774a7c6f3d3219bb4716078706eef Mon Sep 17 00:00:00 2001 From: Anthony Green <40231097+Ant-Green@users.noreply.github.com> Date: Thu, 28 Oct 2021 10:28:33 +0100 Subject: [PATCH 259/662] New Updates Added CU27 for SQL 2017 Added hot fix for SQL 2016 SP3 --- SqlServerVersions.sql | 2 ++ 1 file changed, 2 insertions(+) diff --git a/SqlServerVersions.sql b/SqlServerVersions.sql index 8d43493c3..857f8feb6 100644 --- a/SqlServerVersions.sql +++ b/SqlServerVersions.sql @@ -57,6 +57,7 @@ VALUES (15, 4003, 'CU1', 'https://support.microsoft.com/en-us/help/4527376', '2020-01-07', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 1 '), (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 '), + (14, 3421, 'RTM CU27', 'https://support.microsoft.com/en-us/help/5006944', '2021-10-27', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 27'), (14, 3411, 'RTM CU26', 'https://support.microsoft.com/en-us/help/5005226', '2021-09-14', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 26'), (14, 3401, 'RTM CU25', 'https://support.microsoft.com/en-us/help/5003830', '2021-07-12', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 25'), (14, 3391, 'RTM CU24', 'https://support.microsoft.com/en-us/help/5001228', '2021-05-10', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 24'), @@ -85,6 +86,7 @@ VALUES (14, 3008, 'RTM CU2', 'https://support.microsoft.com/en-us/help/4052574', '2017-11-28', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 2'), (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 '), + (13, 6404, 'SP3 GDR', 'https://support.microsoft.com/en-us/help/5006943', '2021-10-27', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3 GDR'), (13, 6300, 'SP3 ', 'https://support.microsoft.com/en-us/help/5003279', '2021-09-15', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3'), (13, 5888, 'SP2 CU17', 'https://support.microsoft.com/en-us/help/5001092', '2021-03-29', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 17'), (13, 5882, 'SP2 CU16', 'https://support.microsoft.com/en-us/help/5000645', '2021-02-11', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 16'), From f0ee89a972bcff0e7b497cd79703accc8d221a8f Mon Sep 17 00:00:00 2001 From: andreasjordan Date: Fri, 29 Oct 2021 08:03:44 +0200 Subject: [PATCH 260/662] Revert #3020 --- sp_Blitz.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index e63c4bd7b..5919a40e3 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -9092,7 +9092,7 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 'Rundate' , GETDATE() , 'http://FirstResponderKit.org/' , - GETDATE(); + 'Captain''s log: stardate something and something...'; IF @EmailRecipients IS NOT NULL BEGIN From 6290f2a1ea462cdde47d7c65e72b4c2248708321 Mon Sep 17 00:00:00 2001 From: andreasjordan Date: Fri, 29 Oct 2021 08:04:19 +0200 Subject: [PATCH 261/662] Fix #3018 --- sp_Blitz.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index 5919a40e3..d863615b2 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -9383,7 +9383,7 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 WHEN r.Priority <> COALESCE(rPrior.Priority, 0) OR r.FindingsGroup <> rPrior.FindingsGroup THEN @crlf + N'**Priority ' + CAST(COALESCE(r.Priority,N'') AS NVARCHAR(5)) + N': ' + COALESCE(r.FindingsGroup,N'') + N'**:' + @crlf + @crlf ELSE N'' END - + CASE WHEN r.Finding <> COALESCE(rPrior.Finding,N'') AND r.Finding <> rNext.Finding THEN N'- ' + COALESCE(r.Finding,N'') + N' ' + COALESCE(r.DatabaseName, N'') + N' - ' + COALESCE(r.Details,N'') + @crlf + + CASE WHEN r.Finding <> COALESCE(rPrior.Finding,N'') AND r.Finding <> COALESCE(rNext.Finding,N'') THEN N'- ' + COALESCE(r.Finding,N'') + N' ' + COALESCE(r.DatabaseName, N'') + N' - ' + COALESCE(r.Details,N'') + @crlf WHEN r.Finding <> COALESCE(rPrior.Finding,N'') AND r.Finding = rNext.Finding AND r.Details = rNext.Details THEN N'- ' + COALESCE(r.Finding,N'') + N' - ' + COALESCE(r.Details,N'') + @crlf + @crlf + N' * ' + COALESCE(r.DatabaseName, N'') + @crlf WHEN r.Finding <> COALESCE(rPrior.Finding,N'') AND r.Finding = rNext.Finding THEN N'- ' + COALESCE(r.Finding,N'') + @crlf + CASE WHEN r.DatabaseName IS NULL THEN N'' ELSE N' * ' + COALESCE(r.DatabaseName,N'') END + CASE WHEN r.Details <> rPrior.Details THEN N' - ' + COALESCE(r.Details,N'') + @crlf ELSE '' END ELSE CASE WHEN r.DatabaseName IS NULL THEN N'' ELSE N' * ' + COALESCE(r.DatabaseName,N'') END + CASE WHEN r.Details <> rPrior.Details THEN N' - ' + COALESCE(r.Details,N'') + @crlf ELSE N'' + @crlf END From 87ee4d95270d90af246ea9bbbd5545dc8a597b8b Mon Sep 17 00:00:00 2001 From: andreasjordan Date: Fri, 29 Oct 2021 08:04:46 +0200 Subject: [PATCH 262/662] Fix #3022 --- sp_Blitz.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index d863615b2..093686553 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -9385,8 +9385,8 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 END + CASE WHEN r.Finding <> COALESCE(rPrior.Finding,N'') AND r.Finding <> COALESCE(rNext.Finding,N'') THEN N'- ' + COALESCE(r.Finding,N'') + N' ' + COALESCE(r.DatabaseName, N'') + N' - ' + COALESCE(r.Details,N'') + @crlf WHEN r.Finding <> COALESCE(rPrior.Finding,N'') AND r.Finding = rNext.Finding AND r.Details = rNext.Details THEN N'- ' + COALESCE(r.Finding,N'') + N' - ' + COALESCE(r.Details,N'') + @crlf + @crlf + N' * ' + COALESCE(r.DatabaseName, N'') + @crlf - WHEN r.Finding <> COALESCE(rPrior.Finding,N'') AND r.Finding = rNext.Finding THEN N'- ' + COALESCE(r.Finding,N'') + @crlf + CASE WHEN r.DatabaseName IS NULL THEN N'' ELSE N' * ' + COALESCE(r.DatabaseName,N'') END + CASE WHEN r.Details <> rPrior.Details THEN N' - ' + COALESCE(r.Details,N'') + @crlf ELSE '' END - ELSE CASE WHEN r.DatabaseName IS NULL THEN N'' ELSE N' * ' + COALESCE(r.DatabaseName,N'') END + CASE WHEN r.Details <> rPrior.Details THEN N' - ' + COALESCE(r.Details,N'') + @crlf ELSE N'' + @crlf END + WHEN r.Finding <> COALESCE(rPrior.Finding,N'') AND r.Finding = rNext.Finding THEN N'- ' + COALESCE(r.Finding,N'') + @crlf + CASE WHEN r.DatabaseName IS NULL THEN N'' ELSE N' * ' + COALESCE(r.DatabaseName,N'') END + CASE WHEN r.Details <> rPrior.Details THEN N' - ' + COALESCE(r.Details,N'') + @crlf ELSE '' END + ELSE CASE WHEN r.DatabaseName IS NULL THEN N'' ELSE N' * ' + COALESCE(r.DatabaseName,N'') END + CASE WHEN r.Details <> rPrior.Details THEN N' - ' + COALESCE(r.Details,N'') + @crlf ELSE N'' + @crlf END END + @crlf FROM Results r LEFT OUTER JOIN Results rPrior ON r.rownum = rPrior.rownum + 1 From 39dab83d09355d53eedb7d4916744de846000590 Mon Sep 17 00:00:00 2001 From: andreasjordan Date: Fri, 29 Oct 2021 08:12:50 +0200 Subject: [PATCH 263/662] Fix #3021 --- sp_Blitz.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index 093686553..abc1542a4 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -82,7 +82,7 @@ AS @CheckProcedureCache 1=top 20-50 resource-intensive cache plans and analyze them for common performance issues. @OutputProcedureCache 1=output the top 20-50 resource-intensive plans even if they did not trigger an alarm @CheckProcedureCacheFilter ''CPU'' | ''Reads'' | ''Duration'' | ''ExecCount'' - @OutputType ''TABLE''=table | ''COUNT''=row with number found | ''MARKDOWN''=bulleted list | ''SCHEMA''=version and field list | ''XML'' =table output as XML | ''NONE'' = none + @OutputType ''TABLE''=table | ''COUNT''=row with number found | ''MARKDOWN''=bulleted list (including server info, excluding security findings) | ''SCHEMA''=version and field list | ''XML'' =table output as XML | ''NONE'' = none @IgnorePrioritiesBelow 50=ignore priorities below 50 @IgnorePrioritiesAbove 50=ignore priorities above 50 For the rest of the parameters, see https://www.BrentOzar.com/blitz/documentation for details. From 326c707efd865b5fe43059e5d235740fdfd49607 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Sat, 6 Nov 2021 05:09:44 -0700 Subject: [PATCH 264/662] 2021-11-06 release --- Documentation/Development/_TestBed.sql | 75 ++++++----------- Install-All-Scripts.sql | 104 ++++++++++++++---------- Install-Core-Blitz-No-Query-Store.sql | 94 ++++++++++++--------- Install-Core-Blitz-With-Query-Store.sql | 96 +++++++++++++--------- sp_AllNightLog.sql | 2 +- sp_AllNightLog_Setup.sql | 2 +- sp_Blitz.sql | 2 +- sp_BlitzAnalysis.sql | 2 +- sp_BlitzBackups.sql | 2 +- sp_BlitzCache.sql | 2 +- sp_BlitzFirst.sql | 2 +- sp_BlitzInMemoryOLTP.sql | 2 +- sp_BlitzIndex.sql | 2 +- sp_BlitzLock.sql | 2 +- sp_BlitzQueryStore.sql | 2 +- sp_BlitzWho.sql | 2 +- sp_DatabaseRestore.sql | 2 +- sp_ineachdb.sql | 2 +- 18 files changed, 215 insertions(+), 182 deletions(-) diff --git a/Documentation/Development/_TestBed.sql b/Documentation/Development/_TestBed.sql index a2ad13748..cea449c9f 100644 --- a/Documentation/Development/_TestBed.sql +++ b/Documentation/Development/_TestBed.sql @@ -1,30 +1,22 @@ /*Blitz*/ EXEC dbo.sp_Blitz @CheckUserDatabaseObjects = 1, @CheckServerInfo = 1; - +GO EXEC dbo.sp_Blitz @CheckUserDatabaseObjects = 1, @CheckServerInfo = 1, @OutputDatabaseName = 'DBAtools', @OutputSchemaName = 'dbo', @OutputTableName = 'Blitz'; - -EXEC dbo.sp_Blitz @CheckUserDatabaseObjects = 1, @CheckServerInfo = 1, @Debug = 1; - -EXEC dbo.sp_Blitz @CheckUserDatabaseObjects = 1, @CheckServerInfo = 1, @Debug = 2; +GO /*BlitzWho*/ - -EXEC dbo.sp_BlitzWho @ExpertMode = 1, @Debug = 1; - -EXEC dbo.sp_BlitzWho @ExpertMode = 0, @Debug = 1; - +EXEC dbo.sp_BlitzWho @ExpertMode = 1; +GO +EXEC dbo.sp_BlitzWho @ExpertMode = 0; +GO EXEC dbo.sp_BlitzWho @OutputDatabaseName = 'DBAtools', @OutputSchemaName = 'dbo', @OutputTableName = 'BlitzWho_Results'; - +GO /*BlitzFirst*/ EXEC dbo.sp_BlitzFirst @Seconds = 5, @ExpertMode = 1; - +GO EXEC dbo.sp_BlitzFirst @SinceStartup = 1; - -EXEC dbo.sp_BlitzFirst @Seconds = 5, @ExpertMode = 1, @ShowSleepingSPIDs = 1; - -CREATE DATABASE DBAtools; - +GO EXEC dbo.sp_BlitzFirst @OutputDatabaseName = 'DBAtools', @OutputSchemaName = 'dbo', @OutputTableName = 'BlitzFirst', @@ -33,52 +25,39 @@ EXEC dbo.sp_BlitzFirst @OutputDatabaseName = 'DBAtools', @OutputTableNameWaitStats = 'BlitzFirst_WaitStats', @OutputTableNameBlitzCache = 'BlitzCache', @OutputTableNameBlitzWho = 'BlitzWho'; - -SELECT TOP 100 * FROM DBAtools.dbo.BlitzFirst ORDER BY 1 DESC; -SELECT TOP 100 * FROM DBAtools.dbo.BlitzFirst_FileStats ORDER BY 1 DESC; -SELECT TOP 100 * FROM DBAtools.dbo.BlitzFirst_PerfmonStats ORDER BY 1 DESC; -SELECT TOP 100 * FROM DBAtools.dbo.BlitzFirst_WaitStats ORDER BY 1 DESC; -SELECT TOP 100 * FROM DBAtools.dbo.BlitzCache ORDER BY 1 DESC; -SELECT TOP 100 * FROM DBAtools.dbo.BlitzWho ORDER BY 1 DESC; +GO /*BlitzIndex*/ -EXEC dbo.sp_BlitzIndex @GetAllDatabases = 1, @Mode = 4; - -EXEC dbo.sp_BlitzIndex @DatabaseName = 'StackOverflow', @Mode = 4; - -EXEC dbo.sp_BlitzIndex @DatabaseName = 'StackOverflow', @Mode = 4, @SkipPartitions = 0, @SkipStatistics = 0; - +EXEC dbo.sp_BlitzIndex @GetAllDatabases = 1, @Mode = 0; +GO EXEC dbo.sp_BlitzIndex @GetAllDatabases = 1, @Mode = 1; - +GO EXEC dbo.sp_BlitzIndex @GetAllDatabases = 1, @Mode = 2; - +GO EXEC dbo.sp_BlitzIndex @GetAllDatabases = 1, @Mode = 3; +GO +EXEC dbo.sp_BlitzIndex @GetAllDatabases = 1, @Mode = 4; +GO +EXEC dbo.sp_BlitzIndex @DatabaseName = 'StackOverflow', @TableName = 'Users' +GO /*BlitzCache*/ EXEC dbo.sp_BlitzCache @SortOrder = 'all'; - -EXEC dbo.sp_BlitzCache @SortOrder = 'all avg', @Debug = 1; - -EXEC dbo.sp_BlitzCache @MinimumExecutionCount = 10; - -EXEC dbo.sp_BlitzCache @DatabaseName = N'StackOverflow'; - +GO EXEC dbo.sp_BlitzCache @OutputDatabaseName = 'DBAtools', @OutputSchemaName = 'dbo', @OutputTableName = 'BlitzCache'; +GO -EXEC dbo.sp_BlitzCache @ExpertMode = 1; - -EXEC dbo.sp_BlitzCache @ExpertMode = 2; - -/*BlitzQueryStore*/ +/*BlitzQueryStore - uncomment this when testing on 2016+ instances: EXEC dbo.sp_BlitzQueryStore @DatabaseName = 'StackOverflow'; +GO +*/ /*BlitzBackups*/ EXEC dbo.sp_BlitzBackups @HoursBack = 1000000; - -/*sp_AllNightLog_Setup*/ -EXEC dbo.sp_AllNightLog_Setup @RPOSeconds = 30, @RTOSeconds = 30, @BackupPath = 'D:\Backup', @RestorePath = 'D:\Backup', @RunSetup = 1; +GO /*sp_BlitzLock*/ -EXEC dbo.sp_BlitzLock @Debug = 1; +EXEC dbo.sp_BlitzLock; +GO diff --git a/Install-All-Scripts.sql b/Install-All-Scripts.sql index 8744b662d..03d8a4be3 100755 --- a/Install-All-Scripts.sql +++ b/Install-All-Scripts.sql @@ -31,7 +31,7 @@ SET STATISTICS XML OFF; BEGIN; -SELECT @Version = '8.06', @VersionDate = '20210914'; +SELECT @Version = '8.07', @VersionDate = '20211106'; IF(@VersionCheckMode = 1) BEGIN @@ -1526,7 +1526,7 @@ SET STATISTICS XML OFF; BEGIN; -SELECT @Version = '8.06', @VersionDate = '20210914'; +SELECT @Version = '8.07', @VersionDate = '20211106'; IF(@VersionCheckMode = 1) BEGIN @@ -2859,7 +2859,7 @@ AS SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.06', @VersionDate = '20210914'; + SELECT @Version = '8.07', @VersionDate = '20211106'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -2903,7 +2903,7 @@ AS @CheckProcedureCache 1=top 20-50 resource-intensive cache plans and analyze them for common performance issues. @OutputProcedureCache 1=output the top 20-50 resource-intensive plans even if they did not trigger an alarm @CheckProcedureCacheFilter ''CPU'' | ''Reads'' | ''Duration'' | ''ExecCount'' - @OutputType ''TABLE''=table | ''COUNT''=row with number found | ''MARKDOWN''=bulleted list | ''SCHEMA''=version and field list | ''XML'' =table output as XML | ''NONE'' = none + @OutputType ''TABLE''=table | ''COUNT''=row with number found | ''MARKDOWN''=bulleted list (including server info, excluding security findings) | ''SCHEMA''=version and field list | ''XML'' =table output as XML | ''NONE'' = none @IgnorePrioritiesBelow 50=ignore priorities below 50 @IgnorePrioritiesAbove 50=ignore priorities above 50 For the rest of the parameters, see https://www.BrentOzar.com/blitz/documentation for details. @@ -10591,7 +10591,7 @@ IF @ProductVersionMajor >= 10 WHEN [T].[TraceFlag] = '1117' THEN '1117 enabled globally, which grows all files in a filegroup at the same time.' WHEN [T].[TraceFlag] = '1118' THEN '1118 enabled globally, which tries to reduce SGAM waits.' WHEN [T].[TraceFlag] = '1211' THEN '1211 enabled globally, which disables lock escalation when you least expect it. This is usually a very bad idea.' - WHEN [T].[TraceFlag] = '1204' THEN '1222 enabled globally, which captures deadlock graphs in the error log.' + WHEN [T].[TraceFlag] = '1204' THEN '1204 enabled globally, which captures deadlock graphs in the error log.' WHEN [T].[TraceFlag] = '1222' THEN '1222 enabled globally, which captures deadlock graphs in the error log.' WHEN [T].[TraceFlag] = '1224' THEN '1224 enabled globally, which disables lock escalation until the server has memory pressure. This is usually a very bad idea.' WHEN [T].[TraceFlag] = '1806' THEN '1806 enabled globally, which disables Instant File Initialization, causing restores and file growths to take longer. This is usually a very bad idea.' @@ -12204,10 +12204,10 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 WHEN r.Priority <> COALESCE(rPrior.Priority, 0) OR r.FindingsGroup <> rPrior.FindingsGroup THEN @crlf + N'**Priority ' + CAST(COALESCE(r.Priority,N'') AS NVARCHAR(5)) + N': ' + COALESCE(r.FindingsGroup,N'') + N'**:' + @crlf + @crlf ELSE N'' END - + CASE WHEN r.Finding <> COALESCE(rPrior.Finding,N'') AND r.Finding <> rNext.Finding THEN N'- ' + COALESCE(r.Finding,N'') + N' ' + COALESCE(r.DatabaseName, N'') + N' - ' + COALESCE(r.Details,N'') + @crlf + + CASE WHEN r.Finding <> COALESCE(rPrior.Finding,N'') AND r.Finding <> COALESCE(rNext.Finding,N'') THEN N'- ' + COALESCE(r.Finding,N'') + N' ' + COALESCE(r.DatabaseName, N'') + N' - ' + COALESCE(r.Details,N'') + @crlf WHEN r.Finding <> COALESCE(rPrior.Finding,N'') AND r.Finding = rNext.Finding AND r.Details = rNext.Details THEN N'- ' + COALESCE(r.Finding,N'') + N' - ' + COALESCE(r.Details,N'') + @crlf + @crlf + N' * ' + COALESCE(r.DatabaseName, N'') + @crlf - WHEN r.Finding <> COALESCE(rPrior.Finding,N'') AND r.Finding = rNext.Finding THEN N'- ' + COALESCE(r.Finding,N'') + @crlf + CASE WHEN r.DatabaseName IS NULL THEN N'' ELSE N' * ' + COALESCE(r.DatabaseName,N'') END + CASE WHEN r.Details <> rPrior.Details THEN N' - ' + COALESCE(r.Details,N'') + @crlf ELSE '' END - ELSE CASE WHEN r.DatabaseName IS NULL THEN N'' ELSE N' * ' + COALESCE(r.DatabaseName,N'') END + CASE WHEN r.Details <> rPrior.Details THEN N' - ' + COALESCE(r.Details,N'') + @crlf ELSE N'' + @crlf END + WHEN r.Finding <> COALESCE(rPrior.Finding,N'') AND r.Finding = rNext.Finding THEN N'- ' + COALESCE(r.Finding,N'') + @crlf + CASE WHEN r.DatabaseName IS NULL THEN N'' ELSE N' * ' + COALESCE(r.DatabaseName,N'') END + CASE WHEN r.Details <> rPrior.Details THEN N' - ' + COALESCE(r.Details,N'') + @crlf ELSE '' END + ELSE CASE WHEN r.DatabaseName IS NULL THEN N'' ELSE N' * ' + COALESCE(r.DatabaseName,N'') END + CASE WHEN r.Details <> rPrior.Details THEN N' - ' + COALESCE(r.Details,N'') + @crlf ELSE N'' + @crlf END END + @crlf FROM Results r LEFT OUTER JOIN Results rPrior ON r.rownum = rPrior.rownum + 1 @@ -12376,7 +12376,7 @@ AS SET NOCOUNT ON; SET STATISTICS XML OFF; -SELECT @Version = '8.06', @VersionDate = '20210914'; +SELECT @Version = '8.07', @VersionDate = '20211106'; IF(@VersionCheckMode = 1) BEGIN @@ -13254,7 +13254,7 @@ AS SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.06', @VersionDate = '20210914'; + SELECT @Version = '8.07', @VersionDate = '20211106'; IF(@VersionCheckMode = 1) BEGIN @@ -15035,7 +15035,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.06', @VersionDate = '20210914'; +SELECT @Version = '8.07', @VersionDate = '20211106'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -21470,15 +21470,17 @@ SET @AllSortSql += N' END; END; - SET @AllSortSql += N' SELECT DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters,ExecutionCount, ExecutionsPerMinute, ExecutionWeight, - TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, - ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache, Pattern - FROM #bou_allsort - ORDER BY Id - OPTION(RECOMPILE); '; - + IF(@OutputType <> 'NONE') + BEGIN + SET @AllSortSql += N' SELECT DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters,ExecutionCount, ExecutionsPerMinute, ExecutionWeight, + TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, + ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, + MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache, Pattern + FROM #bou_allsort + ORDER BY Id + OPTION(RECOMPILE); '; + END; END; @@ -21646,13 +21648,16 @@ SET @AllSortSql += N' END; - SET @AllSortSql += N' SELECT DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters,ExecutionCount, ExecutionsPerMinute, ExecutionWeight, - TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, - ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache, Pattern - FROM #bou_allsort - ORDER BY Id - OPTION(RECOMPILE); '; + IF(@OutputType <> 'NONE') + BEGIN + SET @AllSortSql += N' SELECT DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters,ExecutionCount, ExecutionsPerMinute, ExecutionWeight, + TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, + ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, + MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache, Pattern + FROM #bou_allsort + ORDER BY Id + OPTION(RECOMPILE); '; + END; END; IF @Debug = 1 @@ -21668,12 +21673,10 @@ END; PRINT SUBSTRING(@AllSortSql, 32000, 36000); PRINT SUBSTRING(@AllSortSql, 36000, 40000); END; -IF(@OutputType <> 'NONE') -BEGIN + EXEC sys.sp_executesql @stmt = @AllSortSql, @params = N'@i_DatabaseName NVARCHAR(128), @i_Top INT, @i_SkipAnalysis BIT, @i_OutputDatabaseName NVARCHAR(258), @i_OutputSchemaName NVARCHAR(258), @i_OutputTableName NVARCHAR(258), @i_CheckDateOverride DATETIMEOFFSET, @i_MinutesBack INT ', @i_DatabaseName = @DatabaseName, @i_Top = @Top, @i_SkipAnalysis = @SkipAnalysis, @i_OutputDatabaseName = @OutputDatabaseName, @i_OutputSchemaName = @OutputSchemaName, @i_OutputTableName = @OutputTableName, @i_CheckDateOverride = @CheckDateOverride, @i_MinutesBack = @MinutesBack; -END; /*End of AllSort section*/ @@ -22295,7 +22298,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.06', @VersionDate = '20210914'; +SELECT @Version = '8.07', @VersionDate = '20211106'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -24768,7 +24771,9 @@ SELECT N'.' + QUOTENAME([object_name]) + N' ADD CONSTRAINT [' + index_name + - N'] UNIQUE' + N'] UNIQUE ' + + CASE WHEN index_id=1 THEN N'CLUSTERED (' ELSE N'(' END + + key_column_names_with_sort_order_no_types + N' )' WHEN is_CX_columnstore= 1 THEN N'CREATE CLUSTERED COLUMNSTORE INDEX ' + QUOTENAME(index_name) + N' on ' + QUOTENAME([database_name]) + N'.' + QUOTENAME([schema_name]) + N'.' + QUOTENAME([object_name]) ELSE /*Else not a PK or cx columnstore */ @@ -28015,7 +28020,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.06', @VersionDate = '20210914'; +SELECT @Version = '8.07', @VersionDate = '20211106'; IF(@VersionCheckMode = 1) @@ -29822,7 +29827,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.06', @VersionDate = '20210914'; +SELECT @Version = '8.07', @VersionDate = '20211106'; IF(@VersionCheckMode = 1) BEGIN RETURN; @@ -35542,6 +35547,7 @@ ALTER PROCEDURE dbo.sp_BlitzWho @CheckDateOverride DATETIMEOFFSET = NULL, @ShowActualParameters BIT = 0, @GetOuterCommand BIT = 0, + @GetLiveQueryPlan BIT = 0, @Version VARCHAR(30) = NULL OUTPUT, @VersionDate DATETIME = NULL OUTPUT, @VersionCheckMode BIT = 0, @@ -35552,7 +35558,7 @@ BEGIN SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.06', @VersionDate = '20210914'; + SELECT @Version = '8.07', @VersionDate = '20211106'; IF(@VersionCheckMode = 1) BEGIN @@ -36179,7 +36185,7 @@ SELECT @BlockingCheck = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; ); ' -IF EXISTS (SELECT * FROM sys.all_columns WHERE object_id = OBJECT_ID('sys.dm_exec_query_statistics_xml') AND name = 'query_plan') +IF EXISTS (SELECT * FROM sys.all_columns WHERE object_id = OBJECT_ID('sys.dm_exec_query_statistics_xml') AND name = 'query_plan' AND @GetLiveQueryPlan=1) BEGIN SET @BlockingCheck = @BlockingCheck + N' INSERT INTO @LiveQueryPlans @@ -36428,7 +36434,14 @@ IF @ProductVersionMajor >= 11 ELSE N'' END+N' derp.query_plan , - CAST(COALESCE(qs_live.Query_Plan, '''') AS XML) AS live_query_plan , + CAST(COALESCE(qs_live.Query_Plan, ' + CASE WHEN @GetLiveQueryPlan=1 + THEN '''''' + ELSE '''''' + END + +') AS XML + + + ) AS live_query_plan , STUFF((SELECT DISTINCT N'', '' + Node.Data.value(''(@Column)[1]'', ''NVARCHAR(4000)'') + N'' {'' + Node.Data.value(''(@ParameterDataType)[1]'', ''NVARCHAR(4000)'') + N''}: '' + Node.Data.value(''(@ParameterCompiledValue)[1]'', ''NVARCHAR(4000)'') FROM derp.query_plan.nodes(''/*:ShowPlanXML/*:BatchSequence/*:Batch/*:Statements/*:StmtSimple/*:QueryPlan/*:ParameterList/*:ColumnReference'') AS Node(Data) FOR XML PATH('''')), 1,2,'''') @@ -36942,7 +36955,7 @@ SET STATISTICS XML OFF; /*Versioning details*/ -SELECT @Version = '8.06', @VersionDate = '20210914'; +SELECT @Version = '8.07', @VersionDate = '20211106'; IF(@VersionCheckMode = 1) BEGIN @@ -38457,7 +38470,7 @@ BEGIN SET NOCOUNT ON; SET STATISTICS XML OFF; - SELECT @Version = '8.06', @VersionDate = '20210914'; + SELECT @Version = '8.07', @VersionDate = '20211106'; IF(@VersionCheckMode = 1) BEGIN @@ -38803,8 +38816,9 @@ DELETE FROM dbo.SqlServerVersions; INSERT INTO dbo.SqlServerVersions (MajorVersionNumber, MinorVersionNumber, Branch, [Url], ReleaseDate, MainstreamSupportEndDate, ExtendedSupportEndDate, MajorVersionName, MinorVersionName) VALUES - (15, 4153, 'CU12', 'https://support.microsoft.com/en-us/help/5004524', '2021-08-04', '2025-01-01', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 12'), - (15, 4138, 'CU11', 'https://support.microsoft.com/en-us/help/5003249', '2021-06-10', '2025-01-01', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 11'), + (15, 4178, 'CU13', 'https://support.microsoft.com/en-us/help/5005679', '2021-10-05', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 13'), + (15, 4153, 'CU12', 'https://support.microsoft.com/en-us/help/5004524', '2021-08-04', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 12'), + (15, 4138, 'CU11', 'https://support.microsoft.com/en-us/help/5003249', '2021-06-10', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 11'), (15, 4123, 'CU10', 'https://support.microsoft.com/en-us/help/5001090', '2021-04-06', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 10'), (15, 4102, 'CU9', 'https://support.microsoft.com/en-us/help/5000642', '2021-02-11', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 9 '), (15, 4073, 'CU8 GDR', 'https://support.microsoft.com/en-us/help/4583459', '2021-01-12', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 8 GDR '), @@ -38818,7 +38832,9 @@ VALUES (15, 4003, 'CU1', 'https://support.microsoft.com/en-us/help/4527376', '2020-01-07', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 1 '), (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 '), - (14, 3401, 'RTM CU25', 'https://support.microsoft.com/en-us/help/5003830', '2021-07-12', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 25'), + (14, 3421, 'RTM CU27', 'https://support.microsoft.com/en-us/help/5006944', '2021-10-27', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 27'), + (14, 3411, 'RTM CU26', 'https://support.microsoft.com/en-us/help/5005226', '2021-09-14', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 26'), + (14, 3401, 'RTM CU25', 'https://support.microsoft.com/en-us/help/5003830', '2021-07-12', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 25'), (14, 3391, 'RTM CU24', 'https://support.microsoft.com/en-us/help/5001228', '2021-05-10', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 24'), (14, 3381, 'RTM CU23', 'https://support.microsoft.com/en-us/help/5000685', '2021-02-25', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 23'), (14, 3370, 'RTM CU22 GDR', 'https://support.microsoft.com/en-us/help/4583457', '2021-01-12', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 22 GDR'), @@ -38845,7 +38861,9 @@ VALUES (14, 3008, 'RTM CU2', 'https://support.microsoft.com/en-us/help/4052574', '2017-11-28', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 2'), (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 '), - (13, 5888, 'SP2 CU17', 'https://support.microsoft.com/en-us/help/5001092', '2021-03-29', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 17'), + (13, 6404, 'SP3 GDR', 'https://support.microsoft.com/en-us/help/5006943', '2021-10-27', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3 GDR'), + (13, 6300, 'SP3 ', 'https://support.microsoft.com/en-us/help/5003279', '2021-09-15', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3'), + (13, 5888, 'SP2 CU17', 'https://support.microsoft.com/en-us/help/5001092', '2021-03-29', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 17'), (13, 5882, 'SP2 CU16', 'https://support.microsoft.com/en-us/help/5000645', '2021-02-11', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 16'), (13, 5865, 'SP2 CU15 GDR', 'https://support.microsoft.com/en-us/help/4583461', '2021-01-12', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 15 GDR'), (13, 5850, 'SP2 CU15', 'https://support.microsoft.com/en-us/help/4577775', '2020-09-28', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 15'), @@ -39198,7 +39216,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.06', @VersionDate = '20210914'; +SELECT @Version = '8.07', @VersionDate = '20211106'; IF(@VersionCheckMode = 1) BEGIN diff --git a/Install-Core-Blitz-No-Query-Store.sql b/Install-Core-Blitz-No-Query-Store.sql index 55c4660af..d9b38ad25 100755 --- a/Install-Core-Blitz-No-Query-Store.sql +++ b/Install-Core-Blitz-No-Query-Store.sql @@ -38,7 +38,7 @@ AS SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.06', @VersionDate = '20210914'; + SELECT @Version = '8.07', @VersionDate = '20211106'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -82,7 +82,7 @@ AS @CheckProcedureCache 1=top 20-50 resource-intensive cache plans and analyze them for common performance issues. @OutputProcedureCache 1=output the top 20-50 resource-intensive plans even if they did not trigger an alarm @CheckProcedureCacheFilter ''CPU'' | ''Reads'' | ''Duration'' | ''ExecCount'' - @OutputType ''TABLE''=table | ''COUNT''=row with number found | ''MARKDOWN''=bulleted list | ''SCHEMA''=version and field list | ''XML'' =table output as XML | ''NONE'' = none + @OutputType ''TABLE''=table | ''COUNT''=row with number found | ''MARKDOWN''=bulleted list (including server info, excluding security findings) | ''SCHEMA''=version and field list | ''XML'' =table output as XML | ''NONE'' = none @IgnorePrioritiesBelow 50=ignore priorities below 50 @IgnorePrioritiesAbove 50=ignore priorities above 50 For the rest of the parameters, see https://www.BrentOzar.com/blitz/documentation for details. @@ -7770,7 +7770,7 @@ IF @ProductVersionMajor >= 10 WHEN [T].[TraceFlag] = '1117' THEN '1117 enabled globally, which grows all files in a filegroup at the same time.' WHEN [T].[TraceFlag] = '1118' THEN '1118 enabled globally, which tries to reduce SGAM waits.' WHEN [T].[TraceFlag] = '1211' THEN '1211 enabled globally, which disables lock escalation when you least expect it. This is usually a very bad idea.' - WHEN [T].[TraceFlag] = '1204' THEN '1222 enabled globally, which captures deadlock graphs in the error log.' + WHEN [T].[TraceFlag] = '1204' THEN '1204 enabled globally, which captures deadlock graphs in the error log.' WHEN [T].[TraceFlag] = '1222' THEN '1222 enabled globally, which captures deadlock graphs in the error log.' WHEN [T].[TraceFlag] = '1224' THEN '1224 enabled globally, which disables lock escalation until the server has memory pressure. This is usually a very bad idea.' WHEN [T].[TraceFlag] = '1806' THEN '1806 enabled globally, which disables Instant File Initialization, causing restores and file growths to take longer. This is usually a very bad idea.' @@ -9383,10 +9383,10 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 WHEN r.Priority <> COALESCE(rPrior.Priority, 0) OR r.FindingsGroup <> rPrior.FindingsGroup THEN @crlf + N'**Priority ' + CAST(COALESCE(r.Priority,N'') AS NVARCHAR(5)) + N': ' + COALESCE(r.FindingsGroup,N'') + N'**:' + @crlf + @crlf ELSE N'' END - + CASE WHEN r.Finding <> COALESCE(rPrior.Finding,N'') AND r.Finding <> rNext.Finding THEN N'- ' + COALESCE(r.Finding,N'') + N' ' + COALESCE(r.DatabaseName, N'') + N' - ' + COALESCE(r.Details,N'') + @crlf + + CASE WHEN r.Finding <> COALESCE(rPrior.Finding,N'') AND r.Finding <> COALESCE(rNext.Finding,N'') THEN N'- ' + COALESCE(r.Finding,N'') + N' ' + COALESCE(r.DatabaseName, N'') + N' - ' + COALESCE(r.Details,N'') + @crlf WHEN r.Finding <> COALESCE(rPrior.Finding,N'') AND r.Finding = rNext.Finding AND r.Details = rNext.Details THEN N'- ' + COALESCE(r.Finding,N'') + N' - ' + COALESCE(r.Details,N'') + @crlf + @crlf + N' * ' + COALESCE(r.DatabaseName, N'') + @crlf - WHEN r.Finding <> COALESCE(rPrior.Finding,N'') AND r.Finding = rNext.Finding THEN N'- ' + COALESCE(r.Finding,N'') + @crlf + CASE WHEN r.DatabaseName IS NULL THEN N'' ELSE N' * ' + COALESCE(r.DatabaseName,N'') END + CASE WHEN r.Details <> rPrior.Details THEN N' - ' + COALESCE(r.Details,N'') + @crlf ELSE '' END - ELSE CASE WHEN r.DatabaseName IS NULL THEN N'' ELSE N' * ' + COALESCE(r.DatabaseName,N'') END + CASE WHEN r.Details <> rPrior.Details THEN N' - ' + COALESCE(r.Details,N'') + @crlf ELSE N'' + @crlf END + WHEN r.Finding <> COALESCE(rPrior.Finding,N'') AND r.Finding = rNext.Finding THEN N'- ' + COALESCE(r.Finding,N'') + @crlf + CASE WHEN r.DatabaseName IS NULL THEN N'' ELSE N' * ' + COALESCE(r.DatabaseName,N'') END + CASE WHEN r.Details <> rPrior.Details THEN N' - ' + COALESCE(r.Details,N'') + @crlf ELSE '' END + ELSE CASE WHEN r.DatabaseName IS NULL THEN N'' ELSE N' * ' + COALESCE(r.DatabaseName,N'') END + CASE WHEN r.Details <> rPrior.Details THEN N' - ' + COALESCE(r.Details,N'') + @crlf ELSE N'' + @crlf END END + @crlf FROM Results r LEFT OUTER JOIN Results rPrior ON r.rownum = rPrior.rownum + 1 @@ -9555,7 +9555,7 @@ AS SET NOCOUNT ON; SET STATISTICS XML OFF; -SELECT @Version = '8.06', @VersionDate = '20210914'; +SELECT @Version = '8.07', @VersionDate = '20211106'; IF(@VersionCheckMode = 1) BEGIN @@ -10433,7 +10433,7 @@ AS SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.06', @VersionDate = '20210914'; + SELECT @Version = '8.07', @VersionDate = '20211106'; IF(@VersionCheckMode = 1) BEGIN @@ -12214,7 +12214,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.06', @VersionDate = '20210914'; +SELECT @Version = '8.07', @VersionDate = '20211106'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -18649,15 +18649,17 @@ SET @AllSortSql += N' END; END; - SET @AllSortSql += N' SELECT DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters,ExecutionCount, ExecutionsPerMinute, ExecutionWeight, - TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, - ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache, Pattern - FROM #bou_allsort - ORDER BY Id - OPTION(RECOMPILE); '; - + IF(@OutputType <> 'NONE') + BEGIN + SET @AllSortSql += N' SELECT DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters,ExecutionCount, ExecutionsPerMinute, ExecutionWeight, + TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, + ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, + MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache, Pattern + FROM #bou_allsort + ORDER BY Id + OPTION(RECOMPILE); '; + END; END; @@ -18825,13 +18827,16 @@ SET @AllSortSql += N' END; - SET @AllSortSql += N' SELECT DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters,ExecutionCount, ExecutionsPerMinute, ExecutionWeight, - TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, - ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache, Pattern - FROM #bou_allsort - ORDER BY Id - OPTION(RECOMPILE); '; + IF(@OutputType <> 'NONE') + BEGIN + SET @AllSortSql += N' SELECT DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters,ExecutionCount, ExecutionsPerMinute, ExecutionWeight, + TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, + ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, + MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache, Pattern + FROM #bou_allsort + ORDER BY Id + OPTION(RECOMPILE); '; + END; END; IF @Debug = 1 @@ -18847,12 +18852,10 @@ END; PRINT SUBSTRING(@AllSortSql, 32000, 36000); PRINT SUBSTRING(@AllSortSql, 36000, 40000); END; -IF(@OutputType <> 'NONE') -BEGIN + EXEC sys.sp_executesql @stmt = @AllSortSql, @params = N'@i_DatabaseName NVARCHAR(128), @i_Top INT, @i_SkipAnalysis BIT, @i_OutputDatabaseName NVARCHAR(258), @i_OutputSchemaName NVARCHAR(258), @i_OutputTableName NVARCHAR(258), @i_CheckDateOverride DATETIMEOFFSET, @i_MinutesBack INT ', @i_DatabaseName = @DatabaseName, @i_Top = @Top, @i_SkipAnalysis = @SkipAnalysis, @i_OutputDatabaseName = @OutputDatabaseName, @i_OutputSchemaName = @OutputSchemaName, @i_OutputTableName = @OutputTableName, @i_CheckDateOverride = @CheckDateOverride, @i_MinutesBack = @MinutesBack; -END; /*End of AllSort section*/ @@ -19474,7 +19477,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.06', @VersionDate = '20210914'; +SELECT @Version = '8.07', @VersionDate = '20211106'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -21947,7 +21950,9 @@ SELECT N'.' + QUOTENAME([object_name]) + N' ADD CONSTRAINT [' + index_name + - N'] UNIQUE' + N'] UNIQUE ' + + CASE WHEN index_id=1 THEN N'CLUSTERED (' ELSE N'(' END + + key_column_names_with_sort_order_no_types + N' )' WHEN is_CX_columnstore= 1 THEN N'CREATE CLUSTERED COLUMNSTORE INDEX ' + QUOTENAME(index_name) + N' on ' + QUOTENAME([database_name]) + N'.' + QUOTENAME([schema_name]) + N'.' + QUOTENAME([object_name]) ELSE /*Else not a PK or cx columnstore */ @@ -25194,7 +25199,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.06', @VersionDate = '20210914'; +SELECT @Version = '8.07', @VersionDate = '20211106'; IF(@VersionCheckMode = 1) @@ -26966,6 +26971,7 @@ ALTER PROCEDURE dbo.sp_BlitzWho @CheckDateOverride DATETIMEOFFSET = NULL, @ShowActualParameters BIT = 0, @GetOuterCommand BIT = 0, + @GetLiveQueryPlan BIT = 0, @Version VARCHAR(30) = NULL OUTPUT, @VersionDate DATETIME = NULL OUTPUT, @VersionCheckMode BIT = 0, @@ -26976,7 +26982,7 @@ BEGIN SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.06', @VersionDate = '20210914'; + SELECT @Version = '8.07', @VersionDate = '20211106'; IF(@VersionCheckMode = 1) BEGIN @@ -27603,7 +27609,7 @@ SELECT @BlockingCheck = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; ); ' -IF EXISTS (SELECT * FROM sys.all_columns WHERE object_id = OBJECT_ID('sys.dm_exec_query_statistics_xml') AND name = 'query_plan') +IF EXISTS (SELECT * FROM sys.all_columns WHERE object_id = OBJECT_ID('sys.dm_exec_query_statistics_xml') AND name = 'query_plan' AND @GetLiveQueryPlan=1) BEGIN SET @BlockingCheck = @BlockingCheck + N' INSERT INTO @LiveQueryPlans @@ -27852,7 +27858,14 @@ IF @ProductVersionMajor >= 11 ELSE N'' END+N' derp.query_plan , - CAST(COALESCE(qs_live.Query_Plan, '''') AS XML) AS live_query_plan , + CAST(COALESCE(qs_live.Query_Plan, ' + CASE WHEN @GetLiveQueryPlan=1 + THEN '''''' + ELSE '''''' + END + +') AS XML + + + ) AS live_query_plan , STUFF((SELECT DISTINCT N'', '' + Node.Data.value(''(@Column)[1]'', ''NVARCHAR(4000)'') + N'' {'' + Node.Data.value(''(@ParameterDataType)[1]'', ''NVARCHAR(4000)'') + N''}: '' + Node.Data.value(''(@ParameterCompiledValue)[1]'', ''NVARCHAR(4000)'') FROM derp.query_plan.nodes(''/*:ShowPlanXML/*:BatchSequence/*:Batch/*:Statements/*:StmtSimple/*:QueryPlan/*:ParameterList/*:ColumnReference'') AS Node(Data) FOR XML PATH('''')), 1,2,'''') @@ -28365,8 +28378,9 @@ DELETE FROM dbo.SqlServerVersions; INSERT INTO dbo.SqlServerVersions (MajorVersionNumber, MinorVersionNumber, Branch, [Url], ReleaseDate, MainstreamSupportEndDate, ExtendedSupportEndDate, MajorVersionName, MinorVersionName) VALUES - (15, 4153, 'CU12', 'https://support.microsoft.com/en-us/help/5004524', '2021-08-04', '2025-01-01', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 12'), - (15, 4138, 'CU11', 'https://support.microsoft.com/en-us/help/5003249', '2021-06-10', '2025-01-01', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 11'), + (15, 4178, 'CU13', 'https://support.microsoft.com/en-us/help/5005679', '2021-10-05', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 13'), + (15, 4153, 'CU12', 'https://support.microsoft.com/en-us/help/5004524', '2021-08-04', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 12'), + (15, 4138, 'CU11', 'https://support.microsoft.com/en-us/help/5003249', '2021-06-10', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 11'), (15, 4123, 'CU10', 'https://support.microsoft.com/en-us/help/5001090', '2021-04-06', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 10'), (15, 4102, 'CU9', 'https://support.microsoft.com/en-us/help/5000642', '2021-02-11', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 9 '), (15, 4073, 'CU8 GDR', 'https://support.microsoft.com/en-us/help/4583459', '2021-01-12', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 8 GDR '), @@ -28380,7 +28394,9 @@ VALUES (15, 4003, 'CU1', 'https://support.microsoft.com/en-us/help/4527376', '2020-01-07', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 1 '), (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 '), - (14, 3401, 'RTM CU25', 'https://support.microsoft.com/en-us/help/5003830', '2021-07-12', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 25'), + (14, 3421, 'RTM CU27', 'https://support.microsoft.com/en-us/help/5006944', '2021-10-27', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 27'), + (14, 3411, 'RTM CU26', 'https://support.microsoft.com/en-us/help/5005226', '2021-09-14', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 26'), + (14, 3401, 'RTM CU25', 'https://support.microsoft.com/en-us/help/5003830', '2021-07-12', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 25'), (14, 3391, 'RTM CU24', 'https://support.microsoft.com/en-us/help/5001228', '2021-05-10', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 24'), (14, 3381, 'RTM CU23', 'https://support.microsoft.com/en-us/help/5000685', '2021-02-25', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 23'), (14, 3370, 'RTM CU22 GDR', 'https://support.microsoft.com/en-us/help/4583457', '2021-01-12', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 22 GDR'), @@ -28407,7 +28423,9 @@ VALUES (14, 3008, 'RTM CU2', 'https://support.microsoft.com/en-us/help/4052574', '2017-11-28', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 2'), (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 '), - (13, 5888, 'SP2 CU17', 'https://support.microsoft.com/en-us/help/5001092', '2021-03-29', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 17'), + (13, 6404, 'SP3 GDR', 'https://support.microsoft.com/en-us/help/5006943', '2021-10-27', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3 GDR'), + (13, 6300, 'SP3 ', 'https://support.microsoft.com/en-us/help/5003279', '2021-09-15', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3'), + (13, 5888, 'SP2 CU17', 'https://support.microsoft.com/en-us/help/5001092', '2021-03-29', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 17'), (13, 5882, 'SP2 CU16', 'https://support.microsoft.com/en-us/help/5000645', '2021-02-11', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 16'), (13, 5865, 'SP2 CU15 GDR', 'https://support.microsoft.com/en-us/help/4583461', '2021-01-12', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 15 GDR'), (13, 5850, 'SP2 CU15', 'https://support.microsoft.com/en-us/help/4577775', '2020-09-28', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 15'), @@ -28760,7 +28778,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.06', @VersionDate = '20210914'; +SELECT @Version = '8.07', @VersionDate = '20211106'; IF(@VersionCheckMode = 1) BEGIN diff --git a/Install-Core-Blitz-With-Query-Store.sql b/Install-Core-Blitz-With-Query-Store.sql index 33a9f801c..c35d33835 100755 --- a/Install-Core-Blitz-With-Query-Store.sql +++ b/Install-Core-Blitz-With-Query-Store.sql @@ -38,7 +38,7 @@ AS SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.06', @VersionDate = '20210914'; + SELECT @Version = '8.07', @VersionDate = '20211106'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -82,7 +82,7 @@ AS @CheckProcedureCache 1=top 20-50 resource-intensive cache plans and analyze them for common performance issues. @OutputProcedureCache 1=output the top 20-50 resource-intensive plans even if they did not trigger an alarm @CheckProcedureCacheFilter ''CPU'' | ''Reads'' | ''Duration'' | ''ExecCount'' - @OutputType ''TABLE''=table | ''COUNT''=row with number found | ''MARKDOWN''=bulleted list | ''SCHEMA''=version and field list | ''XML'' =table output as XML | ''NONE'' = none + @OutputType ''TABLE''=table | ''COUNT''=row with number found | ''MARKDOWN''=bulleted list (including server info, excluding security findings) | ''SCHEMA''=version and field list | ''XML'' =table output as XML | ''NONE'' = none @IgnorePrioritiesBelow 50=ignore priorities below 50 @IgnorePrioritiesAbove 50=ignore priorities above 50 For the rest of the parameters, see https://www.BrentOzar.com/blitz/documentation for details. @@ -7770,7 +7770,7 @@ IF @ProductVersionMajor >= 10 WHEN [T].[TraceFlag] = '1117' THEN '1117 enabled globally, which grows all files in a filegroup at the same time.' WHEN [T].[TraceFlag] = '1118' THEN '1118 enabled globally, which tries to reduce SGAM waits.' WHEN [T].[TraceFlag] = '1211' THEN '1211 enabled globally, which disables lock escalation when you least expect it. This is usually a very bad idea.' - WHEN [T].[TraceFlag] = '1204' THEN '1222 enabled globally, which captures deadlock graphs in the error log.' + WHEN [T].[TraceFlag] = '1204' THEN '1204 enabled globally, which captures deadlock graphs in the error log.' WHEN [T].[TraceFlag] = '1222' THEN '1222 enabled globally, which captures deadlock graphs in the error log.' WHEN [T].[TraceFlag] = '1224' THEN '1224 enabled globally, which disables lock escalation until the server has memory pressure. This is usually a very bad idea.' WHEN [T].[TraceFlag] = '1806' THEN '1806 enabled globally, which disables Instant File Initialization, causing restores and file growths to take longer. This is usually a very bad idea.' @@ -9383,10 +9383,10 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 WHEN r.Priority <> COALESCE(rPrior.Priority, 0) OR r.FindingsGroup <> rPrior.FindingsGroup THEN @crlf + N'**Priority ' + CAST(COALESCE(r.Priority,N'') AS NVARCHAR(5)) + N': ' + COALESCE(r.FindingsGroup,N'') + N'**:' + @crlf + @crlf ELSE N'' END - + CASE WHEN r.Finding <> COALESCE(rPrior.Finding,N'') AND r.Finding <> rNext.Finding THEN N'- ' + COALESCE(r.Finding,N'') + N' ' + COALESCE(r.DatabaseName, N'') + N' - ' + COALESCE(r.Details,N'') + @crlf + + CASE WHEN r.Finding <> COALESCE(rPrior.Finding,N'') AND r.Finding <> COALESCE(rNext.Finding,N'') THEN N'- ' + COALESCE(r.Finding,N'') + N' ' + COALESCE(r.DatabaseName, N'') + N' - ' + COALESCE(r.Details,N'') + @crlf WHEN r.Finding <> COALESCE(rPrior.Finding,N'') AND r.Finding = rNext.Finding AND r.Details = rNext.Details THEN N'- ' + COALESCE(r.Finding,N'') + N' - ' + COALESCE(r.Details,N'') + @crlf + @crlf + N' * ' + COALESCE(r.DatabaseName, N'') + @crlf - WHEN r.Finding <> COALESCE(rPrior.Finding,N'') AND r.Finding = rNext.Finding THEN N'- ' + COALESCE(r.Finding,N'') + @crlf + CASE WHEN r.DatabaseName IS NULL THEN N'' ELSE N' * ' + COALESCE(r.DatabaseName,N'') END + CASE WHEN r.Details <> rPrior.Details THEN N' - ' + COALESCE(r.Details,N'') + @crlf ELSE '' END - ELSE CASE WHEN r.DatabaseName IS NULL THEN N'' ELSE N' * ' + COALESCE(r.DatabaseName,N'') END + CASE WHEN r.Details <> rPrior.Details THEN N' - ' + COALESCE(r.Details,N'') + @crlf ELSE N'' + @crlf END + WHEN r.Finding <> COALESCE(rPrior.Finding,N'') AND r.Finding = rNext.Finding THEN N'- ' + COALESCE(r.Finding,N'') + @crlf + CASE WHEN r.DatabaseName IS NULL THEN N'' ELSE N' * ' + COALESCE(r.DatabaseName,N'') END + CASE WHEN r.Details <> rPrior.Details THEN N' - ' + COALESCE(r.Details,N'') + @crlf ELSE '' END + ELSE CASE WHEN r.DatabaseName IS NULL THEN N'' ELSE N' * ' + COALESCE(r.DatabaseName,N'') END + CASE WHEN r.Details <> rPrior.Details THEN N' - ' + COALESCE(r.Details,N'') + @crlf ELSE N'' + @crlf END END + @crlf FROM Results r LEFT OUTER JOIN Results rPrior ON r.rownum = rPrior.rownum + 1 @@ -9555,7 +9555,7 @@ AS SET NOCOUNT ON; SET STATISTICS XML OFF; -SELECT @Version = '8.06', @VersionDate = '20210914'; +SELECT @Version = '8.07', @VersionDate = '20211106'; IF(@VersionCheckMode = 1) BEGIN @@ -10433,7 +10433,7 @@ AS SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.06', @VersionDate = '20210914'; + SELECT @Version = '8.07', @VersionDate = '20211106'; IF(@VersionCheckMode = 1) BEGIN @@ -12214,7 +12214,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.06', @VersionDate = '20210914'; +SELECT @Version = '8.07', @VersionDate = '20211106'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -18649,15 +18649,17 @@ SET @AllSortSql += N' END; END; - SET @AllSortSql += N' SELECT DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters,ExecutionCount, ExecutionsPerMinute, ExecutionWeight, - TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, - ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache, Pattern - FROM #bou_allsort - ORDER BY Id - OPTION(RECOMPILE); '; - + IF(@OutputType <> 'NONE') + BEGIN + SET @AllSortSql += N' SELECT DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters,ExecutionCount, ExecutionsPerMinute, ExecutionWeight, + TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, + ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, + MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache, Pattern + FROM #bou_allsort + ORDER BY Id + OPTION(RECOMPILE); '; + END; END; @@ -18825,13 +18827,16 @@ SET @AllSortSql += N' END; - SET @AllSortSql += N' SELECT DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters,ExecutionCount, ExecutionsPerMinute, ExecutionWeight, - TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, - ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache, Pattern - FROM #bou_allsort - ORDER BY Id - OPTION(RECOMPILE); '; + IF(@OutputType <> 'NONE') + BEGIN + SET @AllSortSql += N' SELECT DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters,ExecutionCount, ExecutionsPerMinute, ExecutionWeight, + TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, + ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, + MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache, Pattern + FROM #bou_allsort + ORDER BY Id + OPTION(RECOMPILE); '; + END; END; IF @Debug = 1 @@ -18847,12 +18852,10 @@ END; PRINT SUBSTRING(@AllSortSql, 32000, 36000); PRINT SUBSTRING(@AllSortSql, 36000, 40000); END; -IF(@OutputType <> 'NONE') -BEGIN + EXEC sys.sp_executesql @stmt = @AllSortSql, @params = N'@i_DatabaseName NVARCHAR(128), @i_Top INT, @i_SkipAnalysis BIT, @i_OutputDatabaseName NVARCHAR(258), @i_OutputSchemaName NVARCHAR(258), @i_OutputTableName NVARCHAR(258), @i_CheckDateOverride DATETIMEOFFSET, @i_MinutesBack INT ', @i_DatabaseName = @DatabaseName, @i_Top = @Top, @i_SkipAnalysis = @SkipAnalysis, @i_OutputDatabaseName = @OutputDatabaseName, @i_OutputSchemaName = @OutputSchemaName, @i_OutputTableName = @OutputTableName, @i_CheckDateOverride = @CheckDateOverride, @i_MinutesBack = @MinutesBack; -END; /*End of AllSort section*/ @@ -19474,7 +19477,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.06', @VersionDate = '20210914'; +SELECT @Version = '8.07', @VersionDate = '20211106'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -21947,7 +21950,9 @@ SELECT N'.' + QUOTENAME([object_name]) + N' ADD CONSTRAINT [' + index_name + - N'] UNIQUE' + N'] UNIQUE ' + + CASE WHEN index_id=1 THEN N'CLUSTERED (' ELSE N'(' END + + key_column_names_with_sort_order_no_types + N' )' WHEN is_CX_columnstore= 1 THEN N'CREATE CLUSTERED COLUMNSTORE INDEX ' + QUOTENAME(index_name) + N' on ' + QUOTENAME([database_name]) + N'.' + QUOTENAME([schema_name]) + N'.' + QUOTENAME([object_name]) ELSE /*Else not a PK or cx columnstore */ @@ -25194,7 +25199,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.06', @VersionDate = '20210914'; +SELECT @Version = '8.07', @VersionDate = '20211106'; IF(@VersionCheckMode = 1) @@ -27001,7 +27006,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.06', @VersionDate = '20210914'; +SELECT @Version = '8.07', @VersionDate = '20211106'; IF(@VersionCheckMode = 1) BEGIN RETURN; @@ -32721,6 +32726,7 @@ ALTER PROCEDURE dbo.sp_BlitzWho @CheckDateOverride DATETIMEOFFSET = NULL, @ShowActualParameters BIT = 0, @GetOuterCommand BIT = 0, + @GetLiveQueryPlan BIT = 0, @Version VARCHAR(30) = NULL OUTPUT, @VersionDate DATETIME = NULL OUTPUT, @VersionCheckMode BIT = 0, @@ -32731,7 +32737,7 @@ BEGIN SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.06', @VersionDate = '20210914'; + SELECT @Version = '8.07', @VersionDate = '20211106'; IF(@VersionCheckMode = 1) BEGIN @@ -33358,7 +33364,7 @@ SELECT @BlockingCheck = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; ); ' -IF EXISTS (SELECT * FROM sys.all_columns WHERE object_id = OBJECT_ID('sys.dm_exec_query_statistics_xml') AND name = 'query_plan') +IF EXISTS (SELECT * FROM sys.all_columns WHERE object_id = OBJECT_ID('sys.dm_exec_query_statistics_xml') AND name = 'query_plan' AND @GetLiveQueryPlan=1) BEGIN SET @BlockingCheck = @BlockingCheck + N' INSERT INTO @LiveQueryPlans @@ -33607,7 +33613,14 @@ IF @ProductVersionMajor >= 11 ELSE N'' END+N' derp.query_plan , - CAST(COALESCE(qs_live.Query_Plan, '''') AS XML) AS live_query_plan , + CAST(COALESCE(qs_live.Query_Plan, ' + CASE WHEN @GetLiveQueryPlan=1 + THEN '''''' + ELSE '''''' + END + +') AS XML + + + ) AS live_query_plan , STUFF((SELECT DISTINCT N'', '' + Node.Data.value(''(@Column)[1]'', ''NVARCHAR(4000)'') + N'' {'' + Node.Data.value(''(@ParameterDataType)[1]'', ''NVARCHAR(4000)'') + N''}: '' + Node.Data.value(''(@ParameterCompiledValue)[1]'', ''NVARCHAR(4000)'') FROM derp.query_plan.nodes(''/*:ShowPlanXML/*:BatchSequence/*:Batch/*:Statements/*:StmtSimple/*:QueryPlan/*:ParameterList/*:ColumnReference'') AS Node(Data) FOR XML PATH('''')), 1,2,'''') @@ -34120,8 +34133,9 @@ DELETE FROM dbo.SqlServerVersions; INSERT INTO dbo.SqlServerVersions (MajorVersionNumber, MinorVersionNumber, Branch, [Url], ReleaseDate, MainstreamSupportEndDate, ExtendedSupportEndDate, MajorVersionName, MinorVersionName) VALUES - (15, 4153, 'CU12', 'https://support.microsoft.com/en-us/help/5004524', '2021-08-04', '2025-01-01', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 12'), - (15, 4138, 'CU11', 'https://support.microsoft.com/en-us/help/5003249', '2021-06-10', '2025-01-01', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 11'), + (15, 4178, 'CU13', 'https://support.microsoft.com/en-us/help/5005679', '2021-10-05', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 13'), + (15, 4153, 'CU12', 'https://support.microsoft.com/en-us/help/5004524', '2021-08-04', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 12'), + (15, 4138, 'CU11', 'https://support.microsoft.com/en-us/help/5003249', '2021-06-10', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 11'), (15, 4123, 'CU10', 'https://support.microsoft.com/en-us/help/5001090', '2021-04-06', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 10'), (15, 4102, 'CU9', 'https://support.microsoft.com/en-us/help/5000642', '2021-02-11', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 9 '), (15, 4073, 'CU8 GDR', 'https://support.microsoft.com/en-us/help/4583459', '2021-01-12', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 8 GDR '), @@ -34135,7 +34149,9 @@ VALUES (15, 4003, 'CU1', 'https://support.microsoft.com/en-us/help/4527376', '2020-01-07', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 1 '), (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 '), - (14, 3401, 'RTM CU25', 'https://support.microsoft.com/en-us/help/5003830', '2021-07-12', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 25'), + (14, 3421, 'RTM CU27', 'https://support.microsoft.com/en-us/help/5006944', '2021-10-27', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 27'), + (14, 3411, 'RTM CU26', 'https://support.microsoft.com/en-us/help/5005226', '2021-09-14', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 26'), + (14, 3401, 'RTM CU25', 'https://support.microsoft.com/en-us/help/5003830', '2021-07-12', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 25'), (14, 3391, 'RTM CU24', 'https://support.microsoft.com/en-us/help/5001228', '2021-05-10', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 24'), (14, 3381, 'RTM CU23', 'https://support.microsoft.com/en-us/help/5000685', '2021-02-25', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 23'), (14, 3370, 'RTM CU22 GDR', 'https://support.microsoft.com/en-us/help/4583457', '2021-01-12', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 22 GDR'), @@ -34162,7 +34178,9 @@ VALUES (14, 3008, 'RTM CU2', 'https://support.microsoft.com/en-us/help/4052574', '2017-11-28', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 2'), (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 '), - (13, 5888, 'SP2 CU17', 'https://support.microsoft.com/en-us/help/5001092', '2021-03-29', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 17'), + (13, 6404, 'SP3 GDR', 'https://support.microsoft.com/en-us/help/5006943', '2021-10-27', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3 GDR'), + (13, 6300, 'SP3 ', 'https://support.microsoft.com/en-us/help/5003279', '2021-09-15', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3'), + (13, 5888, 'SP2 CU17', 'https://support.microsoft.com/en-us/help/5001092', '2021-03-29', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 17'), (13, 5882, 'SP2 CU16', 'https://support.microsoft.com/en-us/help/5000645', '2021-02-11', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 16'), (13, 5865, 'SP2 CU15 GDR', 'https://support.microsoft.com/en-us/help/4583461', '2021-01-12', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 15 GDR'), (13, 5850, 'SP2 CU15', 'https://support.microsoft.com/en-us/help/4577775', '2020-09-28', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 15'), @@ -34515,7 +34533,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.06', @VersionDate = '20210914'; +SELECT @Version = '8.07', @VersionDate = '20211106'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_AllNightLog.sql b/sp_AllNightLog.sql index 8ff4e525b..5a742c930 100644 --- a/sp_AllNightLog.sql +++ b/sp_AllNightLog.sql @@ -31,7 +31,7 @@ SET STATISTICS XML OFF; BEGIN; -SELECT @Version = '8.06', @VersionDate = '20210914'; +SELECT @Version = '8.07', @VersionDate = '20211106'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_AllNightLog_Setup.sql b/sp_AllNightLog_Setup.sql index de581dea4..f56fc4d60 100644 --- a/sp_AllNightLog_Setup.sql +++ b/sp_AllNightLog_Setup.sql @@ -37,7 +37,7 @@ SET STATISTICS XML OFF; BEGIN; -SELECT @Version = '8.06', @VersionDate = '20210914'; +SELECT @Version = '8.07', @VersionDate = '20211106'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_Blitz.sql b/sp_Blitz.sql index abc1542a4..ab9d97733 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -38,7 +38,7 @@ AS SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.06', @VersionDate = '20210914'; + SELECT @Version = '8.07', @VersionDate = '20211106'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) diff --git a/sp_BlitzAnalysis.sql b/sp_BlitzAnalysis.sql index 89cd5a5ec..62a6e7f85 100644 --- a/sp_BlitzAnalysis.sql +++ b/sp_BlitzAnalysis.sql @@ -37,7 +37,7 @@ AS SET NOCOUNT ON; SET STATISTICS XML OFF; -SELECT @Version = '8.06', @VersionDate = '20210914'; +SELECT @Version = '8.07', @VersionDate = '20211106'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_BlitzBackups.sql b/sp_BlitzBackups.sql index ae281a1de..8ea2e3126 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.06', @VersionDate = '20210914'; + SELECT @Version = '8.07', @VersionDate = '20211106'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_BlitzCache.sql b/sp_BlitzCache.sql index 24738e282..470b71b94 100644 --- a/sp_BlitzCache.sql +++ b/sp_BlitzCache.sql @@ -280,7 +280,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.06', @VersionDate = '20210914'; +SELECT @Version = '8.07', @VersionDate = '20211106'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) diff --git a/sp_BlitzFirst.sql b/sp_BlitzFirst.sql index 07b58b281..ba9a09fdc 100644 --- a/sp_BlitzFirst.sql +++ b/sp_BlitzFirst.sql @@ -46,7 +46,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.06', @VersionDate = '20210914'; +SELECT @Version = '8.07', @VersionDate = '20211106'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_BlitzInMemoryOLTP.sql b/sp_BlitzInMemoryOLTP.sql index 3a961cd4e..6b8263a95 100644 --- a/sp_BlitzInMemoryOLTP.sql +++ b/sp_BlitzInMemoryOLTP.sql @@ -82,7 +82,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI */ AS DECLARE @ScriptVersion VARCHAR(30); -SELECT @ScriptVersion = '1.8', @VersionDate = '20210914'; +SELECT @ScriptVersion = '1.8', @VersionDate = '20211106'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index 8a06e4f4b..6c9704ea7 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -48,7 +48,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.06', @VersionDate = '20210914'; +SELECT @Version = '8.07', @VersionDate = '20211106'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index 48e0de2a6..e3905ca52 100644 --- a/sp_BlitzLock.sql +++ b/sp_BlitzLock.sql @@ -33,7 +33,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.06', @VersionDate = '20210914'; +SELECT @Version = '8.07', @VersionDate = '20211106'; IF(@VersionCheckMode = 1) diff --git a/sp_BlitzQueryStore.sql b/sp_BlitzQueryStore.sql index 00f01357d..1eaf6084f 100644 --- a/sp_BlitzQueryStore.sql +++ b/sp_BlitzQueryStore.sql @@ -57,7 +57,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.06', @VersionDate = '20210914'; +SELECT @Version = '8.07', @VersionDate = '20211106'; IF(@VersionCheckMode = 1) BEGIN RETURN; diff --git a/sp_BlitzWho.sql b/sp_BlitzWho.sql index c70db5a5a..d602068eb 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.06', @VersionDate = '20210914'; + SELECT @Version = '8.07', @VersionDate = '20211106'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_DatabaseRestore.sql b/sp_DatabaseRestore.sql index 4d39f57ec..b236c79f5 100755 --- a/sp_DatabaseRestore.sql +++ b/sp_DatabaseRestore.sql @@ -42,7 +42,7 @@ SET STATISTICS XML OFF; /*Versioning details*/ -SELECT @Version = '8.06', @VersionDate = '20210914'; +SELECT @Version = '8.07', @VersionDate = '20211106'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_ineachdb.sql b/sp_ineachdb.sql index b9aff6189..7bd202f34 100644 --- a/sp_ineachdb.sql +++ b/sp_ineachdb.sql @@ -35,7 +35,7 @@ BEGIN SET NOCOUNT ON; SET STATISTICS XML OFF; - SELECT @Version = '8.06', @VersionDate = '20210914'; + SELECT @Version = '8.07', @VersionDate = '20211106'; IF(@VersionCheckMode = 1) BEGIN From 08a3ffcfb6e51cb42d958fc5600ea3a58490224e Mon Sep 17 00:00:00 2001 From: RATG <88596567+ragatilao@users.noreply.github.com> Date: Mon, 8 Nov 2021 21:16:08 -0600 Subject: [PATCH 265/662] Update sp_Blitz.sql Added cluster info --- sp_Blitz.sql | 158 +++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 142 insertions(+), 16 deletions(-) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index ab9d97733..0e20e54f5 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -3028,22 +3028,148 @@ AS IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 53) WITH NOWAIT; - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT TOP 1 - 53 AS CheckID , - 200 AS Priority , - 'Informational' AS FindingsGroup , - 'Cluster Node' AS Finding , - 'https://www.brentozar.com/go/node' AS URL , - 'This is a node in a cluster.' AS Details - FROM sys.dm_os_cluster_nodes; + --INSERT INTO #BlitzResults + -- ( CheckID , + -- Priority , + -- FindingsGroup , + -- Finding , + -- URL , + -- Details + -- ) + -- SELECT TOP 1 + -- 53 AS CheckID , + -- 200 AS Priority , + -- 'Informational' AS FindingsGroup , + -- 'Cluster Node' AS Finding , + -- 'https://BrentOzar.com/go/node' AS URL , + -- 'This is a node in a cluster.' AS Details + -- FROM sys.dm_os_cluster_nodes; + + DECLARE @AOFCI AS INT, @AOAG AS INT, @HAType AS VARCHAR(10), @errmsg AS VARCHAR(200) + + SELECT @AOAG = CAST(SERVERPROPERTY('IsHadrEnabled') AS INT) + SELECT @AOFCI = CAST(SERVERPROPERTY('IsClustered') AS INT) + + IF (SELECT COUNT(DISTINCT join_state_desc) FROM sys.dm_hadr_availability_replica_cluster_states) = 2 + BEGIN --if count is 2 both JOINED_STANDALONE and JOINED_FCI is configured + SET @AOFCI = 1 + END + + SELECT @HAType = + CASE + WHEN @AOFCI = 1 AND @AOAG =1 THEN 'FCIAG' + WHEN @AOFCI = 1 AND @AOAG =0 THEN 'FCI' + WHEN @AOFCI = 0 AND @AOAG =1 THEN 'AG' + ELSE 'STANDALONE' + END + + IF (@HAType IN ('FCIAG','FCI','AG')) + BEGIN + + INSERT INTO #BlitzResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + + SELECT 53 AS CheckID , + 200 AS Priority , + 'Informational' AS FindingsGroup , + 'Cluster Node' AS Finding , + 'https://BrentOzar.com/go/node' AS URL , + 'This is a node in a cluster.' AS Details + + IF @HAType = 'AG' + BEGIN + INSERT INTO #BlitzResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT 53 AS CheckID , + 200 AS Priority , + 'Informational' AS FindingsGroup , + 'Cluster Node Info' AS Finding , + 'https://BrentOzar.com/go/node' AS URL, + 'The cluster nodes are: ' + STUFF((SELECT ', ' + CASE ar.replica_server_name WHEN dhags.primary_replica THEN 'PRIMARY' + ELSE 'SECONDARY' + END + '=' + UPPER(ar.replica_server_name) + FROM sys.availability_groups AS ag + LEFT OUTER JOIN sys.availability_replicas AS ar ON ag.group_id = ar.group_id + LEFT OUTER JOIN sys.dm_hadr_availability_group_states as dhags ON ag.group_id = dhags.group_id + ORDER BY 1 + FOR XML PATH ('') + ), 1, 1, '') + ' - High Availability solution used is AlwaysOn Availability Group (AG)' As Details + END + + IF @HAType = 'FCI' + BEGIN + INSERT INTO #BlitzResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT 53 AS CheckID , + 200 AS Priority , + 'Informational' AS FindingsGroup , + 'Cluster Node Info' AS Finding , + 'https://BrentOzar.com/go/node' AS URL, + 'The cluster nodes are: ' + STUFF((SELECT ', ' + CASE is_current_owner + WHEN 1 THEN 'PRIMARY' + ELSE 'SECONDARY' + END + '=' + UPPER(NodeName) + FROM sys.dm_os_cluster_nodes + ORDER BY 1 + FOR XML PATH ('') + ), 1, 1, '') + ' - High Availability solution used is AlwaysOn Failover Cluster Instance (FCI)' As Details + END + + IF @HAType = 'FCIAG' + BEGIN + INSERT INTO #BlitzResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT 53 AS CheckID , + 200 AS Priority , + 'Informational' AS FindingsGroup , + 'Cluster Node Info' AS Finding , + 'https://BrentOzar.com/go/node' AS URL, + 'The cluster nodes are: ' + STUFF((SELECT ', ' + HighAvailabilityRoleDesc + '=' + ServerName + FROM (SELECT UPPER(ar.replica_server_name) AS ServerName + ,CASE ar.replica_server_name WHEN dhags.primary_replica THEN 'PRIMARY' + ELSE 'SECONDARY' + END AS HighAvailabilityRoleDesc + FROM sys.availability_groups AS ag + LEFT OUTER JOIN sys.availability_replicas AS ar ON ag.group_id = ar.group_id + LEFT OUTER JOIN sys.dm_hadr_availability_group_states as dhags ON ag.group_id = dhags.group_id + WHERE UPPER(CAST(SERVERPROPERTY('ServerName') AS VARCHAR)) <> ar.replica_server_name + UNION ALL + SELECT UPPER(NodeName) AS ServerName + ,CASE is_current_owner + WHEN 1 THEN 'PRIMARY' + ELSE 'SECONDARY' + END AS HighAvailabilityRoleDesc + FROM sys.dm_os_cluster_nodes) AS Z + ORDER BY 1 + FOR XML PATH ('') + ), 1, 1, '') + ' - High Availability solution used is AlwaysOn FCI with AG (Failover Cluster Instance with Availability Group).'As Details + END + END + END; IF NOT EXISTS ( SELECT 1 From 3d38c7c01fa1ee6e4168ac0f8b610a197e45c012 Mon Sep 17 00:00:00 2001 From: "Fotopoulos, Nick" Date: Tue, 9 Nov 2021 10:01:23 -0600 Subject: [PATCH 266/662] Ola Hallengren scripts must be installed in the same database as First Responder Kit. --- README.md | 2 +- sp_AllNightLog.sql | 27 +++++++++++++++++---------- sp_AllNightLog_Setup.sql | 19 +++++++++++++------ 3 files changed, 31 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 94fc307a6..d700c604f 100644 --- a/README.md +++ b/README.md @@ -494,7 +494,7 @@ For more information about how this works, see [sp_AllNightLog documentation.](h Known issues: * The msdbCentral database name is hard-coded. -* sp_AllNightLog depends on Ola Hallengren's DatabaseBackup, which must be installed separately. (We're not checking for it right now.) +* sp_AllNightLog depends on Ola Hallengren's DatabaseBackup, which must be installed separately. (We expect it to be installed in the same database as the SQL Server First Responder Kit.) [*Back to top*](#header1) diff --git a/sp_AllNightLog.sql b/sp_AllNightLog.sql index 5a742c930..d7bab8d19 100644 --- a/sp_AllNightLog.sql +++ b/sp_AllNightLog.sql @@ -165,21 +165,28 @@ IF NOT EXISTS (SELECT * FROM sys.configurations WHERE name = 'xp_cmdshell' AND v END /* -Make sure Ola Hallengren's scripts are installed in master +Make sure Ola Hallengren's scripts are installed in same database */ -IF 2 <> (SELECT COUNT(*) FROM master.sys.procedures WHERE name IN('CommandExecute', 'DatabaseBackup')) +DECLARE @CurrentDatabaseContext nvarchar(128) = DB_NAME(); +IF NOT EXISTS (SELECT * FROM sys.procedures WHERE name = 'CommandExecute') BEGIN - RAISERROR('Ola Hallengren''s CommandExecute and DatabaseBackup must be installed in the master database. More info: http://ola.hallengren.com', 0, 1) WITH NOWAIT + RAISERROR('Ola Hallengren''s CommandExecute must be installed in the same database (%s) as SQL Server First Responder Kit. More info: http://ola.hallengren.com', 0, 1, @CurrentDatabaseContext) WITH NOWAIT; + + RETURN; + END +IF NOT EXISTS (SELECT * FROM sys.procedures WHERE name = 'DatabaseBackup') + BEGIN + RAISERROR('Ola Hallengren''s DatabaseBackup must be installed in the same database (%s) as SQL Server First Responder Kit. More info: http://ola.hallengren.com', 0, 1, @CurrentDatabaseContext) WITH NOWAIT; RETURN; END /* -Make sure sp_DatabaseRestore is installed in master +Make sure sp_DatabaseRestore is installed in same database */ -IF NOT EXISTS (SELECT * FROM master.sys.procedures WHERE name = 'sp_DatabaseRestore') +IF NOT EXISTS (SELECT * FROM sys.procedures WHERE name = 'sp_DatabaseRestore') BEGIN - RAISERROR('sp_DatabaseRestore must be installed in master. To get it: http://FirstResponderKit.org', 0, 1) WITH NOWAIT + RAISERROR('sp_DatabaseRestore must be installed in the same database (%s) as SQL Server First Responder Kit. To get it: http://FirstResponderKit.org', 0, 1, @CurrentDatabaseContext) WITH NOWAIT; RETURN; END @@ -912,7 +919,7 @@ LogShamer: */ IF @encrypt = 'Y' - EXEC master.dbo.DatabaseBackup @Databases = @database, --Database we're working on + EXEC dbo.DatabaseBackup @Databases = @database, --Database we're working on @BackupType = 'LOG', --Going for the LOGs @Directory = @backup_path, --The path we need to back up to @Verify = 'N', --We don't want to verify these, it eats into job time @@ -925,7 +932,7 @@ LogShamer: @ServerCertificate = @servercertificate; ELSE - EXEC master.dbo.DatabaseBackup @Databases = @database, --Database we're working on + EXEC dbo.DatabaseBackup @Databases = @database, --Database we're working on @BackupType = 'LOG', --Going for the LOGs @Directory = @backup_path, --The path we need to back up to @Verify = 'N', --We don't want to verify these, it eats into job time @@ -1311,7 +1318,7 @@ IF @Restore = 1 IF @Debug = 1 RAISERROR('Starting Log only restores', 0, 1) WITH NOWAIT; - EXEC master.dbo.sp_DatabaseRestore @Database = @database, + EXEC dbo.sp_DatabaseRestore @Database = @database, @BackupPathFull = @restore_path_full, @BackupPathLog = @restore_path_log, @ContinueLogs = 1, @@ -1328,7 +1335,7 @@ IF @Restore = 1 IF @Debug = 1 RAISERROR('Starting first Full restore from: ', 0, 1) WITH NOWAIT; IF @Debug = 1 RAISERROR(@restore_path_full, 0, 1) WITH NOWAIT; - EXEC master.dbo.sp_DatabaseRestore @Database = @database, + EXEC dbo.sp_DatabaseRestore @Database = @database, @BackupPathFull = @restore_path_full, @BackupPathLog = @restore_path_log, @ContinueLogs = 0, diff --git a/sp_AllNightLog_Setup.sql b/sp_AllNightLog_Setup.sql index f56fc4d60..73941252c 100644 --- a/sp_AllNightLog_Setup.sql +++ b/sp_AllNightLog_Setup.sql @@ -264,21 +264,28 @@ IF NOT EXISTS (SELECT * FROM sys.configurations WHERE name = 'xp_cmdshell' AND v END /* -Make sure Ola Hallengren's scripts are installed in master +Make sure Ola Hallengren's scripts are installed in same database */ -IF 2 <> (SELECT COUNT(*) FROM master.sys.procedures WHERE name IN('CommandExecute', 'DatabaseBackup')) +DECLARE @CurrentDatabaseContext nvarchar(128) = DB_NAME(); +IF NOT EXISTS (SELECT * FROM sys.procedures WHERE name = 'CommandExecute') BEGIN - RAISERROR('Ola Hallengren''s CommandExecute and DatabaseBackup must be installed in the master database. More info: http://ola.hallengren.com', 0, 1) WITH NOWAIT + RAISERROR('Ola Hallengren''s CommandExecute must be installed in the same database (%s) as SQL Server First Responder Kit. More info: http://ola.hallengren.com', 0, 1, @CurrentDatabaseContext) WITH NOWAIT; + + RETURN; + END +IF NOT EXISTS (SELECT * FROM sys.procedures WHERE name = 'DatabaseBackup') + BEGIN + RAISERROR('Ola Hallengren''s DatabaseBackup must be installed in the same database (%s) as SQL Server First Responder Kit. More info: http://ola.hallengren.com', 0, 1, @CurrentDatabaseContext) WITH NOWAIT; RETURN; END /* -Make sure sp_DatabaseRestore is installed in master +Make sure sp_DatabaseRestore is installed in same database */ -IF NOT EXISTS (SELECT * FROM master.sys.procedures WHERE name = 'sp_DatabaseRestore') +IF NOT EXISTS (SELECT * FROM sys.procedures WHERE name = 'sp_DatabaseRestore') BEGIN - RAISERROR('sp_DatabaseRestore must be installed in master. To get it: http://FirstResponderKit.org', 0, 1) WITH NOWAIT + RAISERROR('sp_DatabaseRestore must be installed in the same database (%s) as SQL Server First Responder Kit. To get it: http://FirstResponderKit.org', 0, 1, @CurrentDatabaseContext) WITH NOWAIT; RETURN; END From 15b1f980341e0c51ad0a16d1d677a1d9e70045a0 Mon Sep 17 00:00:00 2001 From: Razvan Socol <29515341+rsocol@users.noreply.github.com> Date: Mon, 22 Nov 2021 07:11:44 +0200 Subject: [PATCH 267/662] gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index d959d3a6c..c4639039a 100644 --- a/.gitignore +++ b/.gitignore @@ -131,3 +131,4 @@ test-results/* x64/ ~$* ~*.* +/.vs From de1fbc6ea74e8b348873ac994d94495a9d894938 Mon Sep 17 00:00:00 2001 From: Razvan Socol <29515341+rsocol@users.noreply.github.com> Date: Mon, 22 Nov 2021 07:12:54 +0200 Subject: [PATCH 268/662] improvement for issue #3043 --- sp_BlitzIndex.sql | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index 6c9704ea7..7d1df8ca4 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -2960,7 +2960,8 @@ BEGIN LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partition_range_values prvs ON prvs.function_id = pf.function_id AND prvs.boundary_id = p.partition_number - 1 LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partition_range_values prve ON prve.function_id = pf.function_id AND prve.boundary_id = p.partition_number ' ELSE N' ' END + N' LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_segments seg ON p.partition_id = seg.partition_id AND ic.index_column_id = seg.column_id AND rg.row_group_id = seg.segment_id - WHERE rg.object_id = @ObjectID' + WHERE rg.object_id = @ObjectID + AND c.name IN ( ' + REPLACE(REPLACE(@ColumnList,'[',''''),']','''') + N')' + CASE WHEN @ShowPartitionRanges = 1 THEN N' ) AS y ' ELSE N' ' END + N' ) AS x From 9434afb9f72ebde2991b2c0b4ee1ec62cd2fe861 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Mon, 22 Nov 2021 05:48:51 -0800 Subject: [PATCH 269/662] README.md - adding another backslash Closes #3036. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 94fc307a6..603e8e42d 100644 --- a/README.md +++ b/README.md @@ -508,7 +508,7 @@ Parameters include: * @Database - the database's name, like LogShipMe * @RestoreDatabaseName -* @BackupPathFull - typically a UNC path like '\\FILESERVER\BACKUPS\SQL2016PROD1A\LogShipMe\FULL\' that points to where the full backups are stored. Note that if the path doesn't exist, we don't create it, and the query might take 30+ seconds if you specify an invalid server name. +* @BackupPathFull - typically a UNC path like '\\\\FILESERVER\BACKUPS\SQL2016PROD1A\LogShipMe\FULL\' that points to where the full backups are stored. Note that if the path doesn't exist, we don't create it, and the query might take 30+ seconds if you specify an invalid server name. * @BackupPathDiff, @BackupPathLog - as with the Full, this should be set to the exact path where the differentials and logs are stored. We don't append anything to these parameters. * @MoveFiles, @MoveDataDrive, @MoveLogDrive - if you want to restore to somewhere other than your default database locations. * @RunCheckDB - default 0. When set to 1, we run Ola Hallengren's DatabaseIntegrityCheck stored procedure on this database, and log the results to table. We use that stored proc's default parameters, nothing fancy. From 0e5a58e3e7a30debf2a8b2296ac72ce3f31ac2aa Mon Sep 17 00:00:00 2001 From: Anthony Green <40231097+Ant-Green@users.noreply.github.com> Date: Tue, 23 Nov 2021 09:40:28 +0000 Subject: [PATCH 270/662] AddedCU14For2019 AddedCU14For2019 --- SqlServerVersions.sql | 1 + 1 file changed, 1 insertion(+) diff --git a/SqlServerVersions.sql b/SqlServerVersions.sql index 857f8feb6..774cf6130 100644 --- a/SqlServerVersions.sql +++ b/SqlServerVersions.sql @@ -41,6 +41,7 @@ DELETE FROM dbo.SqlServerVersions; INSERT INTO dbo.SqlServerVersions (MajorVersionNumber, MinorVersionNumber, Branch, [Url], ReleaseDate, MainstreamSupportEndDate, ExtendedSupportEndDate, MajorVersionName, MinorVersionName) VALUES + (15, 4188, 'CU14', 'https://support.microsoft.com/en-us/help/5007182', '2021-11-22', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 14'), (15, 4178, 'CU13', 'https://support.microsoft.com/en-us/help/5005679', '2021-10-05', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 13'), (15, 4153, 'CU12', 'https://support.microsoft.com/en-us/help/5004524', '2021-08-04', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 12'), (15, 4138, 'CU11', 'https://support.microsoft.com/en-us/help/5003249', '2021-06-10', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 11'), From 93be8571afecee8cf3958d9ba84cb368fe2f3490 Mon Sep 17 00:00:00 2001 From: Dale Hirt Date: Mon, 29 Nov 2021 13:10:18 -0800 Subject: [PATCH 271/662] feat: Enable configuration item to allow data/log files to be moved to default locations. If set to 0, they are not moved, but use existing paths. --- sp_AllNightLog.sql | 15 ++++++++++++++- sp_AllNightLog_Setup.sql | 5 +++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/sp_AllNightLog.sql b/sp_AllNightLog.sql index 5a742c930..eca19c6d3 100644 --- a/sp_AllNightLog.sql +++ b/sp_AllNightLog.sql @@ -124,6 +124,7 @@ DECLARE @servercertificate NVARCHAR(MAX); --Config table: server certificate tha DECLARE @restore_path_base NVARCHAR(MAX); --Used to hold the base backup path in our configuration table DECLARE @restore_path_full NVARCHAR(MAX); --Used to hold the full backup path in our configuration table DECLARE @restore_path_log NVARCHAR(MAX); --Used to hold the log backup path in our configuration table +DECLARE @restore_move_files INT; -- used to hold the move files bit in our configuration table DECLARE @db_sql NVARCHAR(MAX) = N''; --Used to hold the dynamic SQL to create msdbCentral DECLARE @tbl_sql NVARCHAR(MAX) = N''; --Used to hold the dynamic SQL that creates tables in msdbCentral DECLARE @database_name NVARCHAR(256) = N'msdbCentral'; --Used to hold the name of the database we create to centralize data @@ -236,7 +237,17 @@ IF (@PollDiskForNewDatabases = 1 OR @Restore = 1) AND OBJECT_ID('msdb.dbo.restor END; END /* IF CHARINDEX('**', @restore_path_base) <> 0 */ - + + SELECT @restore_move_files = CONVERT(NVARCHAR(512), configuration_setting) + FROM msdb.dbo.restore_configuration c + WHERE configuration_name = N'move files'; + + IF @restore_move_files is NULL + BEGIN + -- Set to default value of 1 + SET @restore_move_files = 1 + END + END /* IF @PollDiskForNewDatabases = 1 OR @Restore = 1 */ @@ -1317,6 +1328,7 @@ IF @Restore = 1 @ContinueLogs = 1, @RunRecovery = 0, @OnlyLogsAfter = @only_logs_after, + @MoveFiles = @restore_move_files, @Debug = @Debug END @@ -1333,6 +1345,7 @@ IF @Restore = 1 @BackupPathLog = @restore_path_log, @ContinueLogs = 0, @RunRecovery = 0, + @MoveFiles = @restore_move_files, @Debug = @Debug END diff --git a/sp_AllNightLog_Setup.sql b/sp_AllNightLog_Setup.sql index f56fc4d60..271b131b6 100644 --- a/sp_AllNightLog_Setup.sql +++ b/sp_AllNightLog_Setup.sql @@ -26,6 +26,7 @@ ALTER PROCEDURE dbo.sp_AllNightLog_Setup @Debug BIT = 0, @FirstFullBackup BIT = 0, @FirstDiffBackup BIT = 0, + @MoveFiles BIT = 1, @Help BIT = 0, @Version VARCHAR(30) = NULL OUTPUT, @VersionDate DATETIME = NULL OUTPUT, @@ -72,6 +73,7 @@ BEGIN * Holds variables used by stored proc to make runtime decisions * RTO: Seconds, how often to look for log backups to restore * Restore Path: The path we feed to sp_DatabaseRestore + * Move Files: Whether to move files to default data/log directories. * dbo.restore_worker * Holds list of databases and some information that helps our Agent jobs figure out if they need to look for files to restore @@ -102,6 +104,7 @@ BEGIN @UpdateSetup BIT, defaults to 0. When set to 1, will update existing configs for RPO/RTO and database backup/restore paths. @RPOSeconds BIGINT, defaults to 30. Value in seconds you want to use to determine if a new log backup needs to be taken. @BackupPath NVARCHAR(MAX), This is REQUIRED if @Runsetup=1. This tells Ola''s job where to put backups. + @MoveFiles BIT, defaults to 1. When this is set to 1, it will move files to default data/log directories @Debug BIT, defaults to 0. Whent this is set to 1, it prints out dynamic SQL commands Sample call: @@ -619,6 +622,8 @@ BEGIN INSERT msdb.dbo.restore_configuration (database_name, configuration_name, configuration_description, configuration_setting) VALUES ('all', 'log restore path', 'The path to which Log Restores come from.', @RestorePath); + INSERT msdb.dbo.restore_configuration (database_name, configuration_name, configuration_description, configuration_setting) + VALUES ('all', 'move files', 'Determines if we move database files to default data/log directories.', @MoveFiles); IF OBJECT_ID('msdb.dbo.restore_worker') IS NULL From e365481f2436d768c0f4e8b27178d5de1a4a8dfb Mon Sep 17 00:00:00 2001 From: Dale Hirt Date: Mon, 29 Nov 2021 13:16:36 -0800 Subject: [PATCH 272/662] fix: wrong data type conversion --- sp_AllNightLog.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sp_AllNightLog.sql b/sp_AllNightLog.sql index eca19c6d3..ab5e20917 100644 --- a/sp_AllNightLog.sql +++ b/sp_AllNightLog.sql @@ -238,7 +238,7 @@ IF (@PollDiskForNewDatabases = 1 OR @Restore = 1) AND OBJECT_ID('msdb.dbo.restor END /* IF CHARINDEX('**', @restore_path_base) <> 0 */ - SELECT @restore_move_files = CONVERT(NVARCHAR(512), configuration_setting) + SELECT @restore_move_files = CONVERT(BIT, configuration_setting) FROM msdb.dbo.restore_configuration c WHERE configuration_name = N'move files'; From 9d24e32d0ad6814ee6adc2c3cc3746c349e53d62 Mon Sep 17 00:00:00 2001 From: Razvan Socol <29515341+rsocol@users.noreply.github.com> Date: Tue, 30 Nov 2021 15:44:40 +0200 Subject: [PATCH 273/662] better approach for issue #3043 --- sp_BlitzIndex.sql | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index 7d1df8ca4..b7b460030 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -127,6 +127,7 @@ DECLARE @LineFeed NVARCHAR(5); DECLARE @DaysUptimeInsertValue NVARCHAR(256); DECLARE @DatabaseToIgnore NVARCHAR(MAX); DECLARE @ColumnList NVARCHAR(MAX); +DECLARE @ColumnListWithApostrophes NVARCHAR(MAX); DECLARE @PartitionCount INT; DECLARE @OptimizeForSequentialKey BIT = 0; @@ -2876,9 +2877,9 @@ BEGIN SET @dsql = N'USE ' + QUOTENAME(@DatabaseName) + N'; IF EXISTS(SELECT * FROM ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_row_groups WHERE object_id = @ObjectID) BEGIN - SET @ColumnList = N''''; + SELECT @ColumnList = N'''', @ColumnListWithApostrophes = N''''; WITH DistinctColumns AS ( - SELECT DISTINCT QUOTENAME(c.name) AS column_name, c.column_id + SELECT DISTINCT QUOTENAME(c.name) AS column_name, QUOTENAME(c.name,'''''''') AS ColumnNameWithApostrophes, c.column_id FROM ' + QUOTENAME(@DatabaseName) + N'.sys.partitions p INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns c ON p.object_id = c.object_id INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.index_columns ic on ic.column_id = c.column_id and ic.object_id = c.object_id AND ic.index_id = p.index_id @@ -2887,9 +2888,10 @@ BEGIN AND EXISTS (SELECT * FROM ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_segments seg WHERE p.partition_id = seg.partition_id AND seg.column_id = ic.index_column_id) AND p.data_compression IN (3,4) ) - SELECT @ColumnList = @ColumnList + column_name + N'', '' - FROM DistinctColumns - ORDER BY column_id; + SELECT @ColumnList = @ColumnList + column_name + N'', '', + @ColumnListWithApostrophes = @ColumnListWithApostrophes + ColumnNameWithApostrophes + N'', '' + FROM DistinctColumns + ORDER BY column_id; SELECT @PartitionCount = COUNT(1) FROM ' + QUOTENAME(@DatabaseName) + N'.sys.partitions WHERE object_id = @ObjectID AND data_compression IN (3,4); END'; @@ -2907,18 +2909,19 @@ BEGIN PRINT SUBSTRING(@dsql, 36000, 40000); END; - EXEC sp_executesql @dsql, N'@ObjectID INT, @ColumnList NVARCHAR(MAX) OUTPUT, @PartitionCount INT OUTPUT', @ObjectID, @ColumnList OUTPUT, @PartitionCount OUTPUT; + EXEC sp_executesql @dsql, N'@ObjectID INT, @ColumnList NVARCHAR(MAX) OUTPUT, @ColumnListWithApostrophes NVARCHAR(MAX) OUTPUT, @PartitionCount INT OUTPUT', @ObjectID, @ColumnList OUTPUT, @ColumnListWithApostrophes OUTPUT, @PartitionCount OUTPUT; IF @PartitionCount < 2 SET @ShowPartitionRanges = 0; IF @Debug = 1 - SELECT @ColumnList AS ColumnstoreColumnList, @PartitionCount AS PartitionCount, @ShowPartitionRanges AS ShowPartitionRanges; + SELECT @ColumnList AS ColumnstoreColumnList, @ColumnListWithApostrophes AS ColumnstoreColumnListWithApostrophes, @PartitionCount AS PartitionCount, @ShowPartitionRanges AS ShowPartitionRanges; IF @ColumnList <> '' BEGIN /* Remove the trailing comma */ SET @ColumnList = LEFT(@ColumnList, LEN(@ColumnList) - 1); + SET @ColumnListWithApostrophes = LEFT(@ColumnListWithApostrophes, LEN(@ColumnListWithApostrophes) - 1); SET @dsql = N'USE ' + QUOTENAME(@DatabaseName) + N'; SELECT partition_number, ' @@ -2961,7 +2964,7 @@ BEGIN LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partition_range_values prve ON prve.function_id = pf.function_id AND prve.boundary_id = p.partition_number ' ELSE N' ' END + N' LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_segments seg ON p.partition_id = seg.partition_id AND ic.index_column_id = seg.column_id AND rg.row_group_id = seg.segment_id WHERE rg.object_id = @ObjectID - AND c.name IN ( ' + REPLACE(REPLACE(@ColumnList,'[',''''),']','''') + N')' + AND c.name IN ( ' + @ColumnListWithApostrophes + N')' + CASE WHEN @ShowPartitionRanges = 1 THEN N' ) AS y ' ELSE N' ' END + N' ) AS x From 5940a41cce759905a0b07ef1873f90203b878012 Mon Sep 17 00:00:00 2001 From: Daniel van der Meulen Date: Wed, 1 Dec 2021 15:07:24 +0100 Subject: [PATCH 274/662] Changed the way the @StopAt param is used. 1. we now check that it is a valid datetime 2. add extra log file to restore (that contains transactions up to @StopAt) 3. add STOPAT parameter to the RESTORE LOG command --- sp_DatabaseRestore.sql | 70 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 57 insertions(+), 13 deletions(-) diff --git a/sp_DatabaseRestore.sql b/sp_DatabaseRestore.sql index b236c79f5..7cc0a30f0 100755 --- a/sp_DatabaseRestore.sql +++ b/sp_DatabaseRestore.sql @@ -1271,19 +1271,6 @@ BEGIN END -IF (@StopAt IS NOT NULL) -BEGIN - - IF @Execute = 'Y' OR @Debug = 1 RAISERROR('@OnlyLogsAfter is NOT NULL, deleting from @FileList', 0, 1) WITH NOWAIT; - - DELETE fl - FROM @FileList AS fl - WHERE BackupFile LIKE N'%.trn' - AND BackupFile LIKE N'%' + @Database + N'%' - AND REPLACE( RIGHT( REPLACE( fl.BackupFile, RIGHT( fl.BackupFile, PATINDEX( '%_[0-9][0-9]%', REVERSE( fl.BackupFile ) ) ), '' ), 16 ), '_', '' ) > @StopAt; - -END - -- Check for log backups IF(@BackupDateTime IS NOT NULL AND @BackupDateTime <> '') BEGIN @@ -1309,6 +1296,63 @@ IF (@LogRecoveryOption = N'') SET @LogRecoveryOption = N'NORECOVERY'; END; +IF (@StopAt IS NOT NULL) +BEGIN + + IF @Execute = 'Y' OR @Debug = 1 RAISERROR('@OnlyLogsAfter is NOT NULL, deleting from @FileList', 0, 1) WITH NOWAIT; + + IF LEN(@StopAt) <> 14 OR PATINDEX('%[^0-9]%', @StopAt) > 0 + BEGIN + RAISERROR('@StopAt parameter is incorrect. It should contain exactly 14 digits in the format yyyyMMddhhmmss.', 16, 1) WITH NOWAIT; + RETURN + END + + IF ISDATE(STUFF(STUFF(STUFF(@StopAt, 13, 0, ':'), 11, 0, ':'), 9, 0, ' ')) = 0 + BEGIN + RAISERROR('@StopAt is not a valid datetime.', 16, 1) WITH NOWAIT; + RETURN + END + + -- Add the STOPAT parameter to the log recovery options but change the value to a valid DATETIME, e.g. '20211118040230' -> '20211118 04:02:30' + SET @LogRecoveryOption += ', STOPAT = ''' + STUFF(STUFF(STUFF(@StopAt, 13, 0, ':'), 11, 0, ':'), 9, 0, ' ') + '''' + + IF @BackupDateTime = @StopAt + BEGIN + IF @Debug = 1 + BEGIN + RAISERROR('@StopAt is the end time of a FULL backup, no log files will be restored.', 0, 1) WITH NOWAIT; + END + END + ELSE + BEGIN + DECLARE @ExtraLogFile NVARCHAR(255) + SELECT TOP 1 @ExtraLogFile = fl.BackupFile + FROM @FileList AS fl + WHERE BackupFile LIKE N'%.trn' + AND BackupFile LIKE N'%' + @Database + N'%' + AND REPLACE( RIGHT( REPLACE( fl.BackupFile, RIGHT( fl.BackupFile, PATINDEX( '%_[0-9][0-9]%', REVERSE( fl.BackupFile ) ) ), '' ), 16 ), '_', '' ) > @StopAt + ORDER BY BackupFile; + END + + IF @ExtraLogFile IS NULL + BEGIN + DELETE fl + FROM @FileList AS fl + WHERE BackupFile LIKE N'%.trn' + AND BackupFile LIKE N'%' + @Database + N'%' + AND REPLACE( RIGHT( REPLACE( fl.BackupFile, RIGHT( fl.BackupFile, PATINDEX( '%_[0-9][0-9]%', REVERSE( fl.BackupFile ) ) ), '' ), 16 ), '_', '' ) > @StopAt; + END + ELSE + BEGIN + DELETE fl + FROM @FileList AS fl + WHERE BackupFile LIKE N'%.trn' + AND BackupFile LIKE N'%' + @Database + N'%' + AND fl.BackupFile > @ExtraLogFile + END +END + + -- Group Ordering based on Backup File Name excluding Index {#} to construct coma separated string in "Restore Log" Command SELECT BackupPath,BackupFile,DENSE_RANK() OVER (ORDER BY REPLACE( RIGHT( REPLACE( BackupFile, RIGHT( BackupFile, PATINDEX( '%_[0-9][0-9]%', REVERSE( BackupFile ) ) ), '' ), 16 ), '_', '' )) AS DenseRank INTO #SplitLogBackups FROM @FileList From fc1c5dc3c453fc7ad71459640291631c68461baa Mon Sep 17 00:00:00 2001 From: andreasjordan Date: Wed, 1 Dec 2021 19:37:46 +0100 Subject: [PATCH 275/662] Suppress message if registry key not found --- sp_Blitz.sql | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index ab9d97733..48324b1ef 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -8690,7 +8690,8 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 EXEC master.sys.xp_regread @rootkey = 'HKEY_LOCAL_MACHINE', @key = 'SOFTWARE\Policies\Microsoft\Power\PowerSettings', @value_name = 'ActivePowerScheme', - @value = @outval OUTPUT; + @value = @outval OUTPUT, + @no_output = 'no_output'; IF @outval IS NULL /* If power plan was not set by group policy, get local value [Git Hub Issue #1620]*/ EXEC master.sys.xp_regread @rootkey = 'HKEY_LOCAL_MACHINE', From 0325a3096960365d4def017fbc9af44ff878ca31 Mon Sep 17 00:00:00 2001 From: Konstantin Taranov Date: Wed, 8 Dec 2021 11:21:50 +0300 Subject: [PATCH 276/662] Add contributors badge and fix code formating --- README.md | 62 +++++++++++++++++++++++++++++-------------------------- 1 file changed, 33 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index 603e8e42d..94c5084ac 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,7 @@ [![stars badge]][stars] [![forks badge]][forks] [![issues badge]][issues] +[![contributors_badge]][contributors] Navigation - [How to Install the Scripts](#how-to-install-the-scripts) @@ -104,8 +105,8 @@ In addition to the [parameters common to many of the stored procedures](#paramet #### Writing sp_Blitz Output to a Table -```SQL -sp_Blitz @OutputDatabaseName = 'DBAtools', @OutputSchemaName = 'dbo', @OutputTableName = 'BlitzResults'; +```tsql +EXEC sp_Blitz @OutputDatabaseName = 'DBAtools', @OutputSchemaName = 'dbo', @OutputTableName = 'BlitzResults'; ``` Checks for the existence of a table DBAtools.dbo.BlitzResults, creates it if necessary, then adds the output of sp_Blitz into this table. This table is designed to support multiple outputs from multiple servers, so you can track your server's configuration history over time. @@ -114,16 +115,16 @@ Checks for the existence of a table DBAtools.dbo.BlitzResults, creates it if nec #### Skipping Checks or Databases -```SQL +```tsql CREATE TABLE dbo.BlitzChecksToSkip ( -ServerName NVARCHAR(128), -DatabaseName NVARCHAR(128), -CheckID INT + ServerName NVARCHAR(128), + DatabaseName NVARCHAR(128), + CheckID INT ); GO INSERT INTO dbo.BlitzChecksToSkip (ServerName, DatabaseName, CheckID) VALUES (NULL, 'SalesDB', 50) -sp_Blitz @SkipChecksDatabase = 'DBAtools', @SkipChecksSchema = 'dbo', @SkipChecksTable = 'BlitzChecksToSkip' +sp_Blitz @SkipChecksDatabase = 'DBAtools', @SkipChecksSchema = 'dbo', @SkipChecksTable = 'BlitzChecksToSkip'; ``` Checks for the existence of a table named Fred - just kidding, named DBAtools.dbo.BlitzChecksToSkip. The table needs at least the columns shown above (ServerName, DatabaseName, and CheckID). For each row: @@ -396,7 +397,7 @@ Example calls: Get information for the last hour from all sp_BlitzFirst output tables -```SQL +```tsql EXEC sp_BlitzAnalysis @StartDate = NULL, @EndDate = NULL, @@ -411,7 +412,7 @@ EXEC sp_BlitzAnalysis Exclude specific tables e.g lets exclude PerfmonStats by setting to NULL, no lookup will occur against the table and a skipped message will appear in the resultset -```SQL +```tsql EXEC sp_BlitzAnalysis @StartDate = NULL, @EndDate = NULL, @@ -429,19 +430,19 @@ We are likely to be hitting some big tables here and some of these queries will I have noticed that the Perfmon query can ask for a big memory grant so be mindful when including this table with large volumes of data: -```SQL +```tsql SELECT - [ServerName] - ,[CheckDate] - ,[counter_name] - ,[object_name] - ,[instance_name] - ,[cntr_value] + [ServerName] + , [CheckDate] + , [counter_name] + , [object_name] + , [instance_name] + , [cntr_value] FROM [dbo].[BlitzFirst_PerfmonStats_Actuals] WHERE CheckDate BETWEEN @FromDate AND @ToDate ORDER BY - [CheckDate] ASC, - [counter_name] ASC + [CheckDate] ASC + , [counter_name] ASC; ``` [*Back to top*](#header1) @@ -464,11 +465,11 @@ Parameters include: An example run of sp_BlitzBackups to push data looks like this: -``` -EXEC sp_BlitzBackups @PushBackupHistoryToListener = 1, -- Turn it on! - @WriteBackupsToListenerName = 'AG_LISTENER_NAME', -- Name of AG Listener and Linked Server - @WriteBackupsToDatabaseName = 'FAKE_MSDB_NAME', -- Fake MSDB name you want to push to. Remember, can't be real MSDB. - @WriteBackupsLastHours = -24 -- Hours back in time you want to go +```tsql +EXEC sp_BlitzBackups @PushBackupHistoryToListener = 1, -- Turn it on! + @WriteBackupsToListenerName = 'AG_LISTENER_NAME', -- Name of AG Listener and Linked Server + @WriteBackupsToDatabaseName = 'FAKE_MSDB_NAME', -- Fake MSDB name you want to push to. Remember, can't be real MSDB. + @WriteBackupsLastHours = -24 -- Hours back in time you want to go ``` In an effort to not clog your servers up, we've taken some care in batching things as we move data. Inspired by [Michael J. Swart's Take Care When Scripting Batches](http://michaeljswart.com/2014/09/take-care-when-scripting-batches/), we only move data in 10 minute intervals. @@ -536,14 +537,15 @@ For information about how this works, see [Tara Kizer's white paper on Log Shipp To check versions of any of the stored procedures, use their output parameters for Version and VersionDate like this: -``` +```tsql DECLARE @VersionOutput VARCHAR(30), @VersionDateOutput DATETIME; EXEC sp_Blitz - @Version = @VersionOutput OUTPUT, - @VersionDate = @VersionDateOutput OUTPUT, - @VersionCheckMode = 1; -SELECT @VersionOutput AS Version, - @VersionDateOutput AS VersionDate; + @Version = @VersionOutput OUTPUT, + @VersionDate = @VersionDateOutput OUTPUT, + @VersionCheckMode = 1; +SELECT + @VersionOutput AS Version, + @VersionDateOutput AS VersionDate; ``` [*Back to top*](#header1) @@ -560,8 +562,10 @@ SELECT @VersionOutput AS Version, [stars badge]:https://img.shields.io/github/stars/BrentOzarULTD/SQL-Server-First-Responder-Kit.svg [forks badge]:https://img.shields.io/github/forks/BrentOzarULTD/SQL-Server-First-Responder-Kit.svg [issues badge]:https://img.shields.io/github/issues/BrentOzarULTD/SQL-Server-First-Responder-Kit.svg +[contributors_badge]:https://img.shields.io/github/contributors/BrentOzarULTD/SQL-Server-First-Responder-Kit.svg [licence]:https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/blob/master/LICENSE.md [stars]:https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/stargazers [forks]:https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/network [issues]:https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues +[contributors]:https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/graphs/contributors From abbe4b9a9cfc2a8d7f6bfd9438832544d16f5f03 Mon Sep 17 00:00:00 2001 From: Vlad Vissoultchev Date: Fri, 17 Dec 2021 12:47:14 +0200 Subject: [PATCH 277/662] Add `total_forwarded_fetch_count` to output table of sp_BlitzIndex Fixes BrentOzarULTD/SQL-Server-First-Responder-Kit#3053 --- sp_BlitzIndex.sql | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index 6c9704ea7..5ff0e9b55 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -5203,7 +5203,12 @@ ELSE IF (@Mode=1) /*Summarize*/ IF EXISTS(SELECT * FROM @@@OutputServerName@@@.@@@OutputDatabaseName@@@.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''@@@OutputSchemaName@@@'') SET @SchemaExists = 1 IF EXISTS (SELECT * FROM @@@OutputServerName@@@.@@@OutputDatabaseName@@@.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''@@@OutputSchemaName@@@'' AND QUOTENAME(TABLE_NAME) = ''@@@OutputTableName@@@'') - SET @TableExists = 1'; + BEGIN + SET @TableExists = 1 + IF NOT EXISTS(SELECT * FROM @@@OutputServerName@@@.@@@OutputDatabaseName@@@.INFORMATION_SCHEMA.COLUMNS WHERE QUOTENAME(TABLE_SCHEMA) = ''@@@OutputSchemaName@@@'' + AND QUOTENAME(TABLE_NAME) = ''@@@OutputTableName@@@'' AND QUOTENAME(COLUMN_NAME) = ''[total_forwarded_fetch_count]'') + EXEC @@@OutputServerName@@@.@@@OutputDatabaseName@@@.dbo.sp_executesql N''ALTER TABLE @@@OutputSchemaName@@@.@@@OutputTableName@@@ ADD [total_forwarded_fetch_count] BIGINT'' + END'; SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputServerName@@@', @OutputServerName); SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName); @@ -5283,6 +5288,7 @@ ELSE IF (@Mode=1) /*Summarize*/ [avg_page_lock_wait_in_ms] BIGINT, [total_index_lock_promotion_attempt_count] BIGINT, [total_index_lock_promotion_count] BIGINT, + [total_forwarded_fetch_count] BIGINT, [data_compression_desc] NVARCHAR(4000), [page_latch_wait_count] BIGINT, [page_latch_wait_in_ms] BIGINT, @@ -5394,6 +5400,7 @@ ELSE IF (@Mode=1) /*Summarize*/ [avg_page_lock_wait_in_ms], [total_index_lock_promotion_attempt_count], [total_index_lock_promotion_count], + [total_forwarded_fetch_count], [data_compression_desc], [page_latch_wait_count], [page_latch_wait_in_ms], @@ -5484,6 +5491,7 @@ ELSE IF (@Mode=1) /*Summarize*/ sz.avg_page_lock_wait_in_ms AS [Avg Page Lock Wait ms], sz.total_index_lock_promotion_attempt_count AS [Lock Escalation Attempts], sz.total_index_lock_promotion_count AS [Lock Escalations], + sz.total_forwarded_fetch_count AS [Forwarded Fetches], sz.data_compression_desc AS [Data Compression], sz.page_latch_wait_count, sz.page_latch_wait_in_ms, From 4fc196f909949b44184480a112494ca6cbc0df0d Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Sat, 8 Jan 2022 09:32:11 -0800 Subject: [PATCH 278/662] 2022-01 Release Bumping version dates and numbers. --- Install-All-Scripts.sql | 354 +++++++++++++++++++----- Install-Core-Blitz-No-Query-Store.sql | 208 +++++++++++--- Install-Core-Blitz-With-Query-Store.sql | 210 +++++++++++--- LICENSE.md | 6 +- sp_AllNightLog.sql | 2 +- sp_AllNightLog_Setup.sql | 2 +- sp_Blitz.sql | 2 +- sp_BlitzAnalysis.sql | 2 +- sp_BlitzBackups.sql | 2 +- sp_BlitzCache.sql | 2 +- sp_BlitzFirst.sql | 2 +- sp_BlitzInMemoryOLTP.sql | 2 +- sp_BlitzIndex.sql | 2 +- sp_BlitzLock.sql | 2 +- sp_BlitzQueryStore.sql | 2 +- sp_BlitzWho.sql | 2 +- sp_DatabaseRestore.sql | 2 +- sp_ineachdb.sql | 2 +- 18 files changed, 651 insertions(+), 155 deletions(-) mode change 100755 => 100644 Install-All-Scripts.sql mode change 100755 => 100644 Install-Core-Blitz-No-Query-Store.sql mode change 100755 => 100644 Install-Core-Blitz-With-Query-Store.sql diff --git a/Install-All-Scripts.sql b/Install-All-Scripts.sql old mode 100755 new mode 100644 index 03d8a4be3..1428aa776 --- a/Install-All-Scripts.sql +++ b/Install-All-Scripts.sql @@ -31,7 +31,7 @@ SET STATISTICS XML OFF; BEGIN; -SELECT @Version = '8.07', @VersionDate = '20211106'; +SELECT @Version = '8.08', @VersionDate = '20220108'; IF(@VersionCheckMode = 1) BEGIN @@ -124,6 +124,7 @@ DECLARE @servercertificate NVARCHAR(MAX); --Config table: server certificate tha DECLARE @restore_path_base NVARCHAR(MAX); --Used to hold the base backup path in our configuration table DECLARE @restore_path_full NVARCHAR(MAX); --Used to hold the full backup path in our configuration table DECLARE @restore_path_log NVARCHAR(MAX); --Used to hold the log backup path in our configuration table +DECLARE @restore_move_files INT; -- used to hold the move files bit in our configuration table DECLARE @db_sql NVARCHAR(MAX) = N''; --Used to hold the dynamic SQL to create msdbCentral DECLARE @tbl_sql NVARCHAR(MAX) = N''; --Used to hold the dynamic SQL that creates tables in msdbCentral DECLARE @database_name NVARCHAR(256) = N'msdbCentral'; --Used to hold the name of the database we create to centralize data @@ -165,21 +166,28 @@ IF NOT EXISTS (SELECT * FROM sys.configurations WHERE name = 'xp_cmdshell' AND v END /* -Make sure Ola Hallengren's scripts are installed in master +Make sure Ola Hallengren's scripts are installed in same database */ -IF 2 <> (SELECT COUNT(*) FROM master.sys.procedures WHERE name IN('CommandExecute', 'DatabaseBackup')) +DECLARE @CurrentDatabaseContext nvarchar(128) = DB_NAME(); +IF NOT EXISTS (SELECT * FROM sys.procedures WHERE name = 'CommandExecute') BEGIN - RAISERROR('Ola Hallengren''s CommandExecute and DatabaseBackup must be installed in the master database. More info: http://ola.hallengren.com', 0, 1) WITH NOWAIT + RAISERROR('Ola Hallengren''s CommandExecute must be installed in the same database (%s) as SQL Server First Responder Kit. More info: http://ola.hallengren.com', 0, 1, @CurrentDatabaseContext) WITH NOWAIT; + + RETURN; + END +IF NOT EXISTS (SELECT * FROM sys.procedures WHERE name = 'DatabaseBackup') + BEGIN + RAISERROR('Ola Hallengren''s DatabaseBackup must be installed in the same database (%s) as SQL Server First Responder Kit. More info: http://ola.hallengren.com', 0, 1, @CurrentDatabaseContext) WITH NOWAIT; RETURN; END /* -Make sure sp_DatabaseRestore is installed in master +Make sure sp_DatabaseRestore is installed in same database */ -IF NOT EXISTS (SELECT * FROM master.sys.procedures WHERE name = 'sp_DatabaseRestore') +IF NOT EXISTS (SELECT * FROM sys.procedures WHERE name = 'sp_DatabaseRestore') BEGIN - RAISERROR('sp_DatabaseRestore must be installed in master. To get it: http://FirstResponderKit.org', 0, 1) WITH NOWAIT + RAISERROR('sp_DatabaseRestore must be installed in the same database (%s) as SQL Server First Responder Kit. To get it: http://FirstResponderKit.org', 0, 1, @CurrentDatabaseContext) WITH NOWAIT; RETURN; END @@ -236,7 +244,17 @@ IF (@PollDiskForNewDatabases = 1 OR @Restore = 1) AND OBJECT_ID('msdb.dbo.restor END; END /* IF CHARINDEX('**', @restore_path_base) <> 0 */ - + + SELECT @restore_move_files = CONVERT(BIT, configuration_setting) + FROM msdb.dbo.restore_configuration c + WHERE configuration_name = N'move files'; + + IF @restore_move_files is NULL + BEGIN + -- Set to default value of 1 + SET @restore_move_files = 1 + END + END /* IF @PollDiskForNewDatabases = 1 OR @Restore = 1 */ @@ -912,7 +930,7 @@ LogShamer: */ IF @encrypt = 'Y' - EXEC master.dbo.DatabaseBackup @Databases = @database, --Database we're working on + EXEC dbo.DatabaseBackup @Databases = @database, --Database we're working on @BackupType = 'LOG', --Going for the LOGs @Directory = @backup_path, --The path we need to back up to @Verify = 'N', --We don't want to verify these, it eats into job time @@ -925,7 +943,7 @@ LogShamer: @ServerCertificate = @servercertificate; ELSE - EXEC master.dbo.DatabaseBackup @Databases = @database, --Database we're working on + EXEC dbo.DatabaseBackup @Databases = @database, --Database we're working on @BackupType = 'LOG', --Going for the LOGs @Directory = @backup_path, --The path we need to back up to @Verify = 'N', --We don't want to verify these, it eats into job time @@ -1311,12 +1329,13 @@ IF @Restore = 1 IF @Debug = 1 RAISERROR('Starting Log only restores', 0, 1) WITH NOWAIT; - EXEC master.dbo.sp_DatabaseRestore @Database = @database, + EXEC dbo.sp_DatabaseRestore @Database = @database, @BackupPathFull = @restore_path_full, @BackupPathLog = @restore_path_log, @ContinueLogs = 1, @RunRecovery = 0, @OnlyLogsAfter = @only_logs_after, + @MoveFiles = @restore_move_files, @Debug = @Debug END @@ -1328,11 +1347,12 @@ IF @Restore = 1 IF @Debug = 1 RAISERROR('Starting first Full restore from: ', 0, 1) WITH NOWAIT; IF @Debug = 1 RAISERROR(@restore_path_full, 0, 1) WITH NOWAIT; - EXEC master.dbo.sp_DatabaseRestore @Database = @database, + EXEC dbo.sp_DatabaseRestore @Database = @database, @BackupPathFull = @restore_path_full, @BackupPathLog = @restore_path_log, @ContinueLogs = 0, @RunRecovery = 0, + @MoveFiles = @restore_move_files, @Debug = @Debug END @@ -1515,6 +1535,7 @@ ALTER PROCEDURE dbo.sp_AllNightLog_Setup @Debug BIT = 0, @FirstFullBackup BIT = 0, @FirstDiffBackup BIT = 0, + @MoveFiles BIT = 1, @Help BIT = 0, @Version VARCHAR(30) = NULL OUTPUT, @VersionDate DATETIME = NULL OUTPUT, @@ -1526,7 +1547,7 @@ SET STATISTICS XML OFF; BEGIN; -SELECT @Version = '8.07', @VersionDate = '20211106'; +SELECT @Version = '8.08', @VersionDate = '20220108'; IF(@VersionCheckMode = 1) BEGIN @@ -1561,6 +1582,7 @@ BEGIN * Holds variables used by stored proc to make runtime decisions * RTO: Seconds, how often to look for log backups to restore * Restore Path: The path we feed to sp_DatabaseRestore + * Move Files: Whether to move files to default data/log directories. * dbo.restore_worker * Holds list of databases and some information that helps our Agent jobs figure out if they need to look for files to restore @@ -1591,6 +1613,7 @@ BEGIN @UpdateSetup BIT, defaults to 0. When set to 1, will update existing configs for RPO/RTO and database backup/restore paths. @RPOSeconds BIGINT, defaults to 30. Value in seconds you want to use to determine if a new log backup needs to be taken. @BackupPath NVARCHAR(MAX), This is REQUIRED if @Runsetup=1. This tells Ola''s job where to put backups. + @MoveFiles BIT, defaults to 1. When this is set to 1, it will move files to default data/log directories @Debug BIT, defaults to 0. Whent this is set to 1, it prints out dynamic SQL commands Sample call: @@ -1753,21 +1776,28 @@ IF NOT EXISTS (SELECT * FROM sys.configurations WHERE name = 'xp_cmdshell' AND v END /* -Make sure Ola Hallengren's scripts are installed in master +Make sure Ola Hallengren's scripts are installed in same database */ -IF 2 <> (SELECT COUNT(*) FROM master.sys.procedures WHERE name IN('CommandExecute', 'DatabaseBackup')) +DECLARE @CurrentDatabaseContext nvarchar(128) = DB_NAME(); +IF NOT EXISTS (SELECT * FROM sys.procedures WHERE name = 'CommandExecute') + BEGIN + RAISERROR('Ola Hallengren''s CommandExecute must be installed in the same database (%s) as SQL Server First Responder Kit. More info: http://ola.hallengren.com', 0, 1, @CurrentDatabaseContext) WITH NOWAIT; + + RETURN; + END +IF NOT EXISTS (SELECT * FROM sys.procedures WHERE name = 'DatabaseBackup') BEGIN - RAISERROR('Ola Hallengren''s CommandExecute and DatabaseBackup must be installed in the master database. More info: http://ola.hallengren.com', 0, 1) WITH NOWAIT + RAISERROR('Ola Hallengren''s DatabaseBackup must be installed in the same database (%s) as SQL Server First Responder Kit. More info: http://ola.hallengren.com', 0, 1, @CurrentDatabaseContext) WITH NOWAIT; RETURN; END /* -Make sure sp_DatabaseRestore is installed in master +Make sure sp_DatabaseRestore is installed in same database */ -IF NOT EXISTS (SELECT * FROM master.sys.procedures WHERE name = 'sp_DatabaseRestore') +IF NOT EXISTS (SELECT * FROM sys.procedures WHERE name = 'sp_DatabaseRestore') BEGIN - RAISERROR('sp_DatabaseRestore must be installed in master. To get it: http://FirstResponderKit.org', 0, 1) WITH NOWAIT + RAISERROR('sp_DatabaseRestore must be installed in the same database (%s) as SQL Server First Responder Kit. To get it: http://FirstResponderKit.org', 0, 1, @CurrentDatabaseContext) WITH NOWAIT; RETURN; END @@ -2108,6 +2138,8 @@ BEGIN INSERT msdb.dbo.restore_configuration (database_name, configuration_name, configuration_description, configuration_setting) VALUES ('all', 'log restore path', 'The path to which Log Restores come from.', @RestorePath); + INSERT msdb.dbo.restore_configuration (database_name, configuration_name, configuration_description, configuration_setting) + VALUES ('all', 'move files', 'Determines if we move database files to default data/log directories.', @MoveFiles); IF OBJECT_ID('msdb.dbo.restore_worker') IS NULL @@ -2859,7 +2891,7 @@ AS SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.07', @VersionDate = '20211106'; + SELECT @Version = '8.08', @VersionDate = '20220108'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -5849,22 +5881,148 @@ AS IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 53) WITH NOWAIT; - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT TOP 1 - 53 AS CheckID , - 200 AS Priority , - 'Informational' AS FindingsGroup , - 'Cluster Node' AS Finding , - 'https://www.brentozar.com/go/node' AS URL , - 'This is a node in a cluster.' AS Details - FROM sys.dm_os_cluster_nodes; + --INSERT INTO #BlitzResults + -- ( CheckID , + -- Priority , + -- FindingsGroup , + -- Finding , + -- URL , + -- Details + -- ) + -- SELECT TOP 1 + -- 53 AS CheckID , + -- 200 AS Priority , + -- 'Informational' AS FindingsGroup , + -- 'Cluster Node' AS Finding , + -- 'https://BrentOzar.com/go/node' AS URL , + -- 'This is a node in a cluster.' AS Details + -- FROM sys.dm_os_cluster_nodes; + + DECLARE @AOFCI AS INT, @AOAG AS INT, @HAType AS VARCHAR(10), @errmsg AS VARCHAR(200) + + SELECT @AOAG = CAST(SERVERPROPERTY('IsHadrEnabled') AS INT) + SELECT @AOFCI = CAST(SERVERPROPERTY('IsClustered') AS INT) + + IF (SELECT COUNT(DISTINCT join_state_desc) FROM sys.dm_hadr_availability_replica_cluster_states) = 2 + BEGIN --if count is 2 both JOINED_STANDALONE and JOINED_FCI is configured + SET @AOFCI = 1 + END + + SELECT @HAType = + CASE + WHEN @AOFCI = 1 AND @AOAG =1 THEN 'FCIAG' + WHEN @AOFCI = 1 AND @AOAG =0 THEN 'FCI' + WHEN @AOFCI = 0 AND @AOAG =1 THEN 'AG' + ELSE 'STANDALONE' + END + + IF (@HAType IN ('FCIAG','FCI','AG')) + BEGIN + + INSERT INTO #BlitzResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + + SELECT 53 AS CheckID , + 200 AS Priority , + 'Informational' AS FindingsGroup , + 'Cluster Node' AS Finding , + 'https://BrentOzar.com/go/node' AS URL , + 'This is a node in a cluster.' AS Details + + IF @HAType = 'AG' + BEGIN + INSERT INTO #BlitzResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT 53 AS CheckID , + 200 AS Priority , + 'Informational' AS FindingsGroup , + 'Cluster Node Info' AS Finding , + 'https://BrentOzar.com/go/node' AS URL, + 'The cluster nodes are: ' + STUFF((SELECT ', ' + CASE ar.replica_server_name WHEN dhags.primary_replica THEN 'PRIMARY' + ELSE 'SECONDARY' + END + '=' + UPPER(ar.replica_server_name) + FROM sys.availability_groups AS ag + LEFT OUTER JOIN sys.availability_replicas AS ar ON ag.group_id = ar.group_id + LEFT OUTER JOIN sys.dm_hadr_availability_group_states as dhags ON ag.group_id = dhags.group_id + ORDER BY 1 + FOR XML PATH ('') + ), 1, 1, '') + ' - High Availability solution used is AlwaysOn Availability Group (AG)' As Details + END + + IF @HAType = 'FCI' + BEGIN + INSERT INTO #BlitzResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT 53 AS CheckID , + 200 AS Priority , + 'Informational' AS FindingsGroup , + 'Cluster Node Info' AS Finding , + 'https://BrentOzar.com/go/node' AS URL, + 'The cluster nodes are: ' + STUFF((SELECT ', ' + CASE is_current_owner + WHEN 1 THEN 'PRIMARY' + ELSE 'SECONDARY' + END + '=' + UPPER(NodeName) + FROM sys.dm_os_cluster_nodes + ORDER BY 1 + FOR XML PATH ('') + ), 1, 1, '') + ' - High Availability solution used is AlwaysOn Failover Cluster Instance (FCI)' As Details + END + + IF @HAType = 'FCIAG' + BEGIN + INSERT INTO #BlitzResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT 53 AS CheckID , + 200 AS Priority , + 'Informational' AS FindingsGroup , + 'Cluster Node Info' AS Finding , + 'https://BrentOzar.com/go/node' AS URL, + 'The cluster nodes are: ' + STUFF((SELECT ', ' + HighAvailabilityRoleDesc + '=' + ServerName + FROM (SELECT UPPER(ar.replica_server_name) AS ServerName + ,CASE ar.replica_server_name WHEN dhags.primary_replica THEN 'PRIMARY' + ELSE 'SECONDARY' + END AS HighAvailabilityRoleDesc + FROM sys.availability_groups AS ag + LEFT OUTER JOIN sys.availability_replicas AS ar ON ag.group_id = ar.group_id + LEFT OUTER JOIN sys.dm_hadr_availability_group_states as dhags ON ag.group_id = dhags.group_id + WHERE UPPER(CAST(SERVERPROPERTY('ServerName') AS VARCHAR)) <> ar.replica_server_name + UNION ALL + SELECT UPPER(NodeName) AS ServerName + ,CASE is_current_owner + WHEN 1 THEN 'PRIMARY' + ELSE 'SECONDARY' + END AS HighAvailabilityRoleDesc + FROM sys.dm_os_cluster_nodes) AS Z + ORDER BY 1 + FOR XML PATH ('') + ), 1, 1, '') + ' - High Availability solution used is AlwaysOn FCI with AG (Failover Cluster Instance with Availability Group).'As Details + END + END + END; IF NOT EXISTS ( SELECT 1 @@ -11511,7 +11669,8 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 EXEC master.sys.xp_regread @rootkey = 'HKEY_LOCAL_MACHINE', @key = 'SOFTWARE\Policies\Microsoft\Power\PowerSettings', @value_name = 'ActivePowerScheme', - @value = @outval OUTPUT; + @value = @outval OUTPUT, + @no_output = 'no_output'; IF @outval IS NULL /* If power plan was not set by group policy, get local value [Git Hub Issue #1620]*/ EXEC master.sys.xp_regread @rootkey = 'HKEY_LOCAL_MACHINE', @@ -12376,7 +12535,7 @@ AS SET NOCOUNT ON; SET STATISTICS XML OFF; -SELECT @Version = '8.07', @VersionDate = '20211106'; +SELECT @Version = '8.08', @VersionDate = '20220108'; IF(@VersionCheckMode = 1) BEGIN @@ -13254,7 +13413,7 @@ AS SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.07', @VersionDate = '20211106'; + SELECT @Version = '8.08', @VersionDate = '20220108'; IF(@VersionCheckMode = 1) BEGIN @@ -15035,7 +15194,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.07', @VersionDate = '20211106'; +SELECT @Version = '8.08', @VersionDate = '20220108'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -22298,7 +22457,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.07', @VersionDate = '20211106'; +SELECT @Version = '8.08', @VersionDate = '20220108'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -22377,6 +22536,7 @@ DECLARE @LineFeed NVARCHAR(5); DECLARE @DaysUptimeInsertValue NVARCHAR(256); DECLARE @DatabaseToIgnore NVARCHAR(MAX); DECLARE @ColumnList NVARCHAR(MAX); +DECLARE @ColumnListWithApostrophes NVARCHAR(MAX); DECLARE @PartitionCount INT; DECLARE @OptimizeForSequentialKey BIT = 0; @@ -25126,9 +25286,9 @@ BEGIN SET @dsql = N'USE ' + QUOTENAME(@DatabaseName) + N'; IF EXISTS(SELECT * FROM ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_row_groups WHERE object_id = @ObjectID) BEGIN - SET @ColumnList = N''''; + SELECT @ColumnList = N'''', @ColumnListWithApostrophes = N''''; WITH DistinctColumns AS ( - SELECT DISTINCT QUOTENAME(c.name) AS column_name, c.column_id + SELECT DISTINCT QUOTENAME(c.name) AS column_name, QUOTENAME(c.name,'''''''') AS ColumnNameWithApostrophes, c.column_id FROM ' + QUOTENAME(@DatabaseName) + N'.sys.partitions p INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns c ON p.object_id = c.object_id INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.index_columns ic on ic.column_id = c.column_id and ic.object_id = c.object_id AND ic.index_id = p.index_id @@ -25137,9 +25297,10 @@ BEGIN AND EXISTS (SELECT * FROM ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_segments seg WHERE p.partition_id = seg.partition_id AND seg.column_id = ic.index_column_id) AND p.data_compression IN (3,4) ) - SELECT @ColumnList = @ColumnList + column_name + N'', '' - FROM DistinctColumns - ORDER BY column_id; + SELECT @ColumnList = @ColumnList + column_name + N'', '', + @ColumnListWithApostrophes = @ColumnListWithApostrophes + ColumnNameWithApostrophes + N'', '' + FROM DistinctColumns + ORDER BY column_id; SELECT @PartitionCount = COUNT(1) FROM ' + QUOTENAME(@DatabaseName) + N'.sys.partitions WHERE object_id = @ObjectID AND data_compression IN (3,4); END'; @@ -25157,18 +25318,19 @@ BEGIN PRINT SUBSTRING(@dsql, 36000, 40000); END; - EXEC sp_executesql @dsql, N'@ObjectID INT, @ColumnList NVARCHAR(MAX) OUTPUT, @PartitionCount INT OUTPUT', @ObjectID, @ColumnList OUTPUT, @PartitionCount OUTPUT; + EXEC sp_executesql @dsql, N'@ObjectID INT, @ColumnList NVARCHAR(MAX) OUTPUT, @ColumnListWithApostrophes NVARCHAR(MAX) OUTPUT, @PartitionCount INT OUTPUT', @ObjectID, @ColumnList OUTPUT, @ColumnListWithApostrophes OUTPUT, @PartitionCount OUTPUT; IF @PartitionCount < 2 SET @ShowPartitionRanges = 0; IF @Debug = 1 - SELECT @ColumnList AS ColumnstoreColumnList, @PartitionCount AS PartitionCount, @ShowPartitionRanges AS ShowPartitionRanges; + SELECT @ColumnList AS ColumnstoreColumnList, @ColumnListWithApostrophes AS ColumnstoreColumnListWithApostrophes, @PartitionCount AS PartitionCount, @ShowPartitionRanges AS ShowPartitionRanges; IF @ColumnList <> '' BEGIN /* Remove the trailing comma */ SET @ColumnList = LEFT(@ColumnList, LEN(@ColumnList) - 1); + SET @ColumnListWithApostrophes = LEFT(@ColumnListWithApostrophes, LEN(@ColumnListWithApostrophes) - 1); SET @dsql = N'USE ' + QUOTENAME(@DatabaseName) + N'; SELECT partition_number, ' @@ -25210,7 +25372,8 @@ BEGIN LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partition_range_values prvs ON prvs.function_id = pf.function_id AND prvs.boundary_id = p.partition_number - 1 LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partition_range_values prve ON prve.function_id = pf.function_id AND prve.boundary_id = p.partition_number ' ELSE N' ' END + N' LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_segments seg ON p.partition_id = seg.partition_id AND ic.index_column_id = seg.column_id AND rg.row_group_id = seg.segment_id - WHERE rg.object_id = @ObjectID' + WHERE rg.object_id = @ObjectID + AND c.name IN ( ' + @ColumnListWithApostrophes + N')' + CASE WHEN @ShowPartitionRanges = 1 THEN N' ) AS y ' ELSE N' ' END + N' ) AS x @@ -27453,7 +27616,12 @@ ELSE IF (@Mode=1) /*Summarize*/ IF EXISTS(SELECT * FROM @@@OutputServerName@@@.@@@OutputDatabaseName@@@.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''@@@OutputSchemaName@@@'') SET @SchemaExists = 1 IF EXISTS (SELECT * FROM @@@OutputServerName@@@.@@@OutputDatabaseName@@@.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''@@@OutputSchemaName@@@'' AND QUOTENAME(TABLE_NAME) = ''@@@OutputTableName@@@'') - SET @TableExists = 1'; + BEGIN + SET @TableExists = 1 + IF NOT EXISTS(SELECT * FROM @@@OutputServerName@@@.@@@OutputDatabaseName@@@.INFORMATION_SCHEMA.COLUMNS WHERE QUOTENAME(TABLE_SCHEMA) = ''@@@OutputSchemaName@@@'' + AND QUOTENAME(TABLE_NAME) = ''@@@OutputTableName@@@'' AND QUOTENAME(COLUMN_NAME) = ''[total_forwarded_fetch_count]'') + EXEC @@@OutputServerName@@@.@@@OutputDatabaseName@@@.dbo.sp_executesql N''ALTER TABLE @@@OutputSchemaName@@@.@@@OutputTableName@@@ ADD [total_forwarded_fetch_count] BIGINT'' + END'; SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputServerName@@@', @OutputServerName); SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName); @@ -27533,6 +27701,7 @@ ELSE IF (@Mode=1) /*Summarize*/ [avg_page_lock_wait_in_ms] BIGINT, [total_index_lock_promotion_attempt_count] BIGINT, [total_index_lock_promotion_count] BIGINT, + [total_forwarded_fetch_count] BIGINT, [data_compression_desc] NVARCHAR(4000), [page_latch_wait_count] BIGINT, [page_latch_wait_in_ms] BIGINT, @@ -27644,6 +27813,7 @@ ELSE IF (@Mode=1) /*Summarize*/ [avg_page_lock_wait_in_ms], [total_index_lock_promotion_attempt_count], [total_index_lock_promotion_count], + [total_forwarded_fetch_count], [data_compression_desc], [page_latch_wait_count], [page_latch_wait_in_ms], @@ -27734,6 +27904,7 @@ ELSE IF (@Mode=1) /*Summarize*/ sz.avg_page_lock_wait_in_ms AS [Avg Page Lock Wait ms], sz.total_index_lock_promotion_attempt_count AS [Lock Escalation Attempts], sz.total_index_lock_promotion_count AS [Lock Escalations], + sz.total_forwarded_fetch_count AS [Forwarded Fetches], sz.data_compression_desc AS [Data Compression], sz.page_latch_wait_count, sz.page_latch_wait_in_ms, @@ -28020,7 +28191,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.07', @VersionDate = '20211106'; +SELECT @Version = '8.08', @VersionDate = '20220108'; IF(@VersionCheckMode = 1) @@ -29827,7 +29998,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.07', @VersionDate = '20211106'; +SELECT @Version = '8.08', @VersionDate = '20220108'; IF(@VersionCheckMode = 1) BEGIN RETURN; @@ -35558,7 +35729,7 @@ BEGIN SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.07', @VersionDate = '20211106'; + SELECT @Version = '8.08', @VersionDate = '20220108'; IF(@VersionCheckMode = 1) BEGIN @@ -36955,7 +37126,7 @@ SET STATISTICS XML OFF; /*Versioning details*/ -SELECT @Version = '8.07', @VersionDate = '20211106'; +SELECT @Version = '8.08', @VersionDate = '20220108'; IF(@VersionCheckMode = 1) BEGIN @@ -38184,19 +38355,6 @@ BEGIN END -IF (@StopAt IS NOT NULL) -BEGIN - - IF @Execute = 'Y' OR @Debug = 1 RAISERROR('@OnlyLogsAfter is NOT NULL, deleting from @FileList', 0, 1) WITH NOWAIT; - - DELETE fl - FROM @FileList AS fl - WHERE BackupFile LIKE N'%.trn' - AND BackupFile LIKE N'%' + @Database + N'%' - AND REPLACE( RIGHT( REPLACE( fl.BackupFile, RIGHT( fl.BackupFile, PATINDEX( '%_[0-9][0-9]%', REVERSE( fl.BackupFile ) ) ), '' ), 16 ), '_', '' ) > @StopAt; - -END - -- Check for log backups IF(@BackupDateTime IS NOT NULL AND @BackupDateTime <> '') BEGIN @@ -38222,6 +38380,63 @@ IF (@LogRecoveryOption = N'') SET @LogRecoveryOption = N'NORECOVERY'; END; +IF (@StopAt IS NOT NULL) +BEGIN + + IF @Execute = 'Y' OR @Debug = 1 RAISERROR('@OnlyLogsAfter is NOT NULL, deleting from @FileList', 0, 1) WITH NOWAIT; + + IF LEN(@StopAt) <> 14 OR PATINDEX('%[^0-9]%', @StopAt) > 0 + BEGIN + RAISERROR('@StopAt parameter is incorrect. It should contain exactly 14 digits in the format yyyyMMddhhmmss.', 16, 1) WITH NOWAIT; + RETURN + END + + IF ISDATE(STUFF(STUFF(STUFF(@StopAt, 13, 0, ':'), 11, 0, ':'), 9, 0, ' ')) = 0 + BEGIN + RAISERROR('@StopAt is not a valid datetime.', 16, 1) WITH NOWAIT; + RETURN + END + + -- Add the STOPAT parameter to the log recovery options but change the value to a valid DATETIME, e.g. '20211118040230' -> '20211118 04:02:30' + SET @LogRecoveryOption += ', STOPAT = ''' + STUFF(STUFF(STUFF(@StopAt, 13, 0, ':'), 11, 0, ':'), 9, 0, ' ') + '''' + + IF @BackupDateTime = @StopAt + BEGIN + IF @Debug = 1 + BEGIN + RAISERROR('@StopAt is the end time of a FULL backup, no log files will be restored.', 0, 1) WITH NOWAIT; + END + END + ELSE + BEGIN + DECLARE @ExtraLogFile NVARCHAR(255) + SELECT TOP 1 @ExtraLogFile = fl.BackupFile + FROM @FileList AS fl + WHERE BackupFile LIKE N'%.trn' + AND BackupFile LIKE N'%' + @Database + N'%' + AND REPLACE( RIGHT( REPLACE( fl.BackupFile, RIGHT( fl.BackupFile, PATINDEX( '%_[0-9][0-9]%', REVERSE( fl.BackupFile ) ) ), '' ), 16 ), '_', '' ) > @StopAt + ORDER BY BackupFile; + END + + IF @ExtraLogFile IS NULL + BEGIN + DELETE fl + FROM @FileList AS fl + WHERE BackupFile LIKE N'%.trn' + AND BackupFile LIKE N'%' + @Database + N'%' + AND REPLACE( RIGHT( REPLACE( fl.BackupFile, RIGHT( fl.BackupFile, PATINDEX( '%_[0-9][0-9]%', REVERSE( fl.BackupFile ) ) ), '' ), 16 ), '_', '' ) > @StopAt; + END + ELSE + BEGIN + DELETE fl + FROM @FileList AS fl + WHERE BackupFile LIKE N'%.trn' + AND BackupFile LIKE N'%' + @Database + N'%' + AND fl.BackupFile > @ExtraLogFile + END +END + + -- Group Ordering based on Backup File Name excluding Index {#} to construct coma separated string in "Restore Log" Command SELECT BackupPath,BackupFile,DENSE_RANK() OVER (ORDER BY REPLACE( RIGHT( REPLACE( BackupFile, RIGHT( BackupFile, PATINDEX( '%_[0-9][0-9]%', REVERSE( BackupFile ) ) ), '' ), 16 ), '_', '' )) AS DenseRank INTO #SplitLogBackups FROM @FileList @@ -38470,7 +38685,7 @@ BEGIN SET NOCOUNT ON; SET STATISTICS XML OFF; - SELECT @Version = '8.07', @VersionDate = '20211106'; + SELECT @Version = '8.08', @VersionDate = '20220108'; IF(@VersionCheckMode = 1) BEGIN @@ -38816,6 +39031,7 @@ DELETE FROM dbo.SqlServerVersions; INSERT INTO dbo.SqlServerVersions (MajorVersionNumber, MinorVersionNumber, Branch, [Url], ReleaseDate, MainstreamSupportEndDate, ExtendedSupportEndDate, MajorVersionName, MinorVersionName) VALUES + (15, 4188, 'CU14', 'https://support.microsoft.com/en-us/help/5007182', '2021-11-22', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 14'), (15, 4178, 'CU13', 'https://support.microsoft.com/en-us/help/5005679', '2021-10-05', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 13'), (15, 4153, 'CU12', 'https://support.microsoft.com/en-us/help/5004524', '2021-08-04', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 12'), (15, 4138, 'CU11', 'https://support.microsoft.com/en-us/help/5003249', '2021-06-10', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 11'), @@ -39216,7 +39432,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.07', @VersionDate = '20211106'; +SELECT @Version = '8.08', @VersionDate = '20220108'; IF(@VersionCheckMode = 1) BEGIN diff --git a/Install-Core-Blitz-No-Query-Store.sql b/Install-Core-Blitz-No-Query-Store.sql old mode 100755 new mode 100644 index d9b38ad25..feca10f79 --- a/Install-Core-Blitz-No-Query-Store.sql +++ b/Install-Core-Blitz-No-Query-Store.sql @@ -38,7 +38,7 @@ AS SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.07', @VersionDate = '20211106'; + SELECT @Version = '8.08', @VersionDate = '20220108'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -3028,22 +3028,148 @@ AS IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 53) WITH NOWAIT; - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT TOP 1 - 53 AS CheckID , - 200 AS Priority , - 'Informational' AS FindingsGroup , - 'Cluster Node' AS Finding , - 'https://www.brentozar.com/go/node' AS URL , - 'This is a node in a cluster.' AS Details - FROM sys.dm_os_cluster_nodes; + --INSERT INTO #BlitzResults + -- ( CheckID , + -- Priority , + -- FindingsGroup , + -- Finding , + -- URL , + -- Details + -- ) + -- SELECT TOP 1 + -- 53 AS CheckID , + -- 200 AS Priority , + -- 'Informational' AS FindingsGroup , + -- 'Cluster Node' AS Finding , + -- 'https://BrentOzar.com/go/node' AS URL , + -- 'This is a node in a cluster.' AS Details + -- FROM sys.dm_os_cluster_nodes; + + DECLARE @AOFCI AS INT, @AOAG AS INT, @HAType AS VARCHAR(10), @errmsg AS VARCHAR(200) + + SELECT @AOAG = CAST(SERVERPROPERTY('IsHadrEnabled') AS INT) + SELECT @AOFCI = CAST(SERVERPROPERTY('IsClustered') AS INT) + + IF (SELECT COUNT(DISTINCT join_state_desc) FROM sys.dm_hadr_availability_replica_cluster_states) = 2 + BEGIN --if count is 2 both JOINED_STANDALONE and JOINED_FCI is configured + SET @AOFCI = 1 + END + + SELECT @HAType = + CASE + WHEN @AOFCI = 1 AND @AOAG =1 THEN 'FCIAG' + WHEN @AOFCI = 1 AND @AOAG =0 THEN 'FCI' + WHEN @AOFCI = 0 AND @AOAG =1 THEN 'AG' + ELSE 'STANDALONE' + END + + IF (@HAType IN ('FCIAG','FCI','AG')) + BEGIN + + INSERT INTO #BlitzResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + + SELECT 53 AS CheckID , + 200 AS Priority , + 'Informational' AS FindingsGroup , + 'Cluster Node' AS Finding , + 'https://BrentOzar.com/go/node' AS URL , + 'This is a node in a cluster.' AS Details + + IF @HAType = 'AG' + BEGIN + INSERT INTO #BlitzResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT 53 AS CheckID , + 200 AS Priority , + 'Informational' AS FindingsGroup , + 'Cluster Node Info' AS Finding , + 'https://BrentOzar.com/go/node' AS URL, + 'The cluster nodes are: ' + STUFF((SELECT ', ' + CASE ar.replica_server_name WHEN dhags.primary_replica THEN 'PRIMARY' + ELSE 'SECONDARY' + END + '=' + UPPER(ar.replica_server_name) + FROM sys.availability_groups AS ag + LEFT OUTER JOIN sys.availability_replicas AS ar ON ag.group_id = ar.group_id + LEFT OUTER JOIN sys.dm_hadr_availability_group_states as dhags ON ag.group_id = dhags.group_id + ORDER BY 1 + FOR XML PATH ('') + ), 1, 1, '') + ' - High Availability solution used is AlwaysOn Availability Group (AG)' As Details + END + + IF @HAType = 'FCI' + BEGIN + INSERT INTO #BlitzResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT 53 AS CheckID , + 200 AS Priority , + 'Informational' AS FindingsGroup , + 'Cluster Node Info' AS Finding , + 'https://BrentOzar.com/go/node' AS URL, + 'The cluster nodes are: ' + STUFF((SELECT ', ' + CASE is_current_owner + WHEN 1 THEN 'PRIMARY' + ELSE 'SECONDARY' + END + '=' + UPPER(NodeName) + FROM sys.dm_os_cluster_nodes + ORDER BY 1 + FOR XML PATH ('') + ), 1, 1, '') + ' - High Availability solution used is AlwaysOn Failover Cluster Instance (FCI)' As Details + END + + IF @HAType = 'FCIAG' + BEGIN + INSERT INTO #BlitzResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT 53 AS CheckID , + 200 AS Priority , + 'Informational' AS FindingsGroup , + 'Cluster Node Info' AS Finding , + 'https://BrentOzar.com/go/node' AS URL, + 'The cluster nodes are: ' + STUFF((SELECT ', ' + HighAvailabilityRoleDesc + '=' + ServerName + FROM (SELECT UPPER(ar.replica_server_name) AS ServerName + ,CASE ar.replica_server_name WHEN dhags.primary_replica THEN 'PRIMARY' + ELSE 'SECONDARY' + END AS HighAvailabilityRoleDesc + FROM sys.availability_groups AS ag + LEFT OUTER JOIN sys.availability_replicas AS ar ON ag.group_id = ar.group_id + LEFT OUTER JOIN sys.dm_hadr_availability_group_states as dhags ON ag.group_id = dhags.group_id + WHERE UPPER(CAST(SERVERPROPERTY('ServerName') AS VARCHAR)) <> ar.replica_server_name + UNION ALL + SELECT UPPER(NodeName) AS ServerName + ,CASE is_current_owner + WHEN 1 THEN 'PRIMARY' + ELSE 'SECONDARY' + END AS HighAvailabilityRoleDesc + FROM sys.dm_os_cluster_nodes) AS Z + ORDER BY 1 + FOR XML PATH ('') + ), 1, 1, '') + ' - High Availability solution used is AlwaysOn FCI with AG (Failover Cluster Instance with Availability Group).'As Details + END + END + END; IF NOT EXISTS ( SELECT 1 @@ -8690,7 +8816,8 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 EXEC master.sys.xp_regread @rootkey = 'HKEY_LOCAL_MACHINE', @key = 'SOFTWARE\Policies\Microsoft\Power\PowerSettings', @value_name = 'ActivePowerScheme', - @value = @outval OUTPUT; + @value = @outval OUTPUT, + @no_output = 'no_output'; IF @outval IS NULL /* If power plan was not set by group policy, get local value [Git Hub Issue #1620]*/ EXEC master.sys.xp_regread @rootkey = 'HKEY_LOCAL_MACHINE', @@ -9555,7 +9682,7 @@ AS SET NOCOUNT ON; SET STATISTICS XML OFF; -SELECT @Version = '8.07', @VersionDate = '20211106'; +SELECT @Version = '8.08', @VersionDate = '20220108'; IF(@VersionCheckMode = 1) BEGIN @@ -10433,7 +10560,7 @@ AS SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.07', @VersionDate = '20211106'; + SELECT @Version = '8.08', @VersionDate = '20220108'; IF(@VersionCheckMode = 1) BEGIN @@ -12214,7 +12341,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.07', @VersionDate = '20211106'; +SELECT @Version = '8.08', @VersionDate = '20220108'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -19477,7 +19604,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.07', @VersionDate = '20211106'; +SELECT @Version = '8.08', @VersionDate = '20220108'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -19556,6 +19683,7 @@ DECLARE @LineFeed NVARCHAR(5); DECLARE @DaysUptimeInsertValue NVARCHAR(256); DECLARE @DatabaseToIgnore NVARCHAR(MAX); DECLARE @ColumnList NVARCHAR(MAX); +DECLARE @ColumnListWithApostrophes NVARCHAR(MAX); DECLARE @PartitionCount INT; DECLARE @OptimizeForSequentialKey BIT = 0; @@ -22305,9 +22433,9 @@ BEGIN SET @dsql = N'USE ' + QUOTENAME(@DatabaseName) + N'; IF EXISTS(SELECT * FROM ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_row_groups WHERE object_id = @ObjectID) BEGIN - SET @ColumnList = N''''; + SELECT @ColumnList = N'''', @ColumnListWithApostrophes = N''''; WITH DistinctColumns AS ( - SELECT DISTINCT QUOTENAME(c.name) AS column_name, c.column_id + SELECT DISTINCT QUOTENAME(c.name) AS column_name, QUOTENAME(c.name,'''''''') AS ColumnNameWithApostrophes, c.column_id FROM ' + QUOTENAME(@DatabaseName) + N'.sys.partitions p INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns c ON p.object_id = c.object_id INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.index_columns ic on ic.column_id = c.column_id and ic.object_id = c.object_id AND ic.index_id = p.index_id @@ -22316,9 +22444,10 @@ BEGIN AND EXISTS (SELECT * FROM ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_segments seg WHERE p.partition_id = seg.partition_id AND seg.column_id = ic.index_column_id) AND p.data_compression IN (3,4) ) - SELECT @ColumnList = @ColumnList + column_name + N'', '' - FROM DistinctColumns - ORDER BY column_id; + SELECT @ColumnList = @ColumnList + column_name + N'', '', + @ColumnListWithApostrophes = @ColumnListWithApostrophes + ColumnNameWithApostrophes + N'', '' + FROM DistinctColumns + ORDER BY column_id; SELECT @PartitionCount = COUNT(1) FROM ' + QUOTENAME(@DatabaseName) + N'.sys.partitions WHERE object_id = @ObjectID AND data_compression IN (3,4); END'; @@ -22336,18 +22465,19 @@ BEGIN PRINT SUBSTRING(@dsql, 36000, 40000); END; - EXEC sp_executesql @dsql, N'@ObjectID INT, @ColumnList NVARCHAR(MAX) OUTPUT, @PartitionCount INT OUTPUT', @ObjectID, @ColumnList OUTPUT, @PartitionCount OUTPUT; + EXEC sp_executesql @dsql, N'@ObjectID INT, @ColumnList NVARCHAR(MAX) OUTPUT, @ColumnListWithApostrophes NVARCHAR(MAX) OUTPUT, @PartitionCount INT OUTPUT', @ObjectID, @ColumnList OUTPUT, @ColumnListWithApostrophes OUTPUT, @PartitionCount OUTPUT; IF @PartitionCount < 2 SET @ShowPartitionRanges = 0; IF @Debug = 1 - SELECT @ColumnList AS ColumnstoreColumnList, @PartitionCount AS PartitionCount, @ShowPartitionRanges AS ShowPartitionRanges; + SELECT @ColumnList AS ColumnstoreColumnList, @ColumnListWithApostrophes AS ColumnstoreColumnListWithApostrophes, @PartitionCount AS PartitionCount, @ShowPartitionRanges AS ShowPartitionRanges; IF @ColumnList <> '' BEGIN /* Remove the trailing comma */ SET @ColumnList = LEFT(@ColumnList, LEN(@ColumnList) - 1); + SET @ColumnListWithApostrophes = LEFT(@ColumnListWithApostrophes, LEN(@ColumnListWithApostrophes) - 1); SET @dsql = N'USE ' + QUOTENAME(@DatabaseName) + N'; SELECT partition_number, ' @@ -22389,7 +22519,8 @@ BEGIN LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partition_range_values prvs ON prvs.function_id = pf.function_id AND prvs.boundary_id = p.partition_number - 1 LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partition_range_values prve ON prve.function_id = pf.function_id AND prve.boundary_id = p.partition_number ' ELSE N' ' END + N' LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_segments seg ON p.partition_id = seg.partition_id AND ic.index_column_id = seg.column_id AND rg.row_group_id = seg.segment_id - WHERE rg.object_id = @ObjectID' + WHERE rg.object_id = @ObjectID + AND c.name IN ( ' + @ColumnListWithApostrophes + N')' + CASE WHEN @ShowPartitionRanges = 1 THEN N' ) AS y ' ELSE N' ' END + N' ) AS x @@ -24632,7 +24763,12 @@ ELSE IF (@Mode=1) /*Summarize*/ IF EXISTS(SELECT * FROM @@@OutputServerName@@@.@@@OutputDatabaseName@@@.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''@@@OutputSchemaName@@@'') SET @SchemaExists = 1 IF EXISTS (SELECT * FROM @@@OutputServerName@@@.@@@OutputDatabaseName@@@.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''@@@OutputSchemaName@@@'' AND QUOTENAME(TABLE_NAME) = ''@@@OutputTableName@@@'') - SET @TableExists = 1'; + BEGIN + SET @TableExists = 1 + IF NOT EXISTS(SELECT * FROM @@@OutputServerName@@@.@@@OutputDatabaseName@@@.INFORMATION_SCHEMA.COLUMNS WHERE QUOTENAME(TABLE_SCHEMA) = ''@@@OutputSchemaName@@@'' + AND QUOTENAME(TABLE_NAME) = ''@@@OutputTableName@@@'' AND QUOTENAME(COLUMN_NAME) = ''[total_forwarded_fetch_count]'') + EXEC @@@OutputServerName@@@.@@@OutputDatabaseName@@@.dbo.sp_executesql N''ALTER TABLE @@@OutputSchemaName@@@.@@@OutputTableName@@@ ADD [total_forwarded_fetch_count] BIGINT'' + END'; SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputServerName@@@', @OutputServerName); SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName); @@ -24712,6 +24848,7 @@ ELSE IF (@Mode=1) /*Summarize*/ [avg_page_lock_wait_in_ms] BIGINT, [total_index_lock_promotion_attempt_count] BIGINT, [total_index_lock_promotion_count] BIGINT, + [total_forwarded_fetch_count] BIGINT, [data_compression_desc] NVARCHAR(4000), [page_latch_wait_count] BIGINT, [page_latch_wait_in_ms] BIGINT, @@ -24823,6 +24960,7 @@ ELSE IF (@Mode=1) /*Summarize*/ [avg_page_lock_wait_in_ms], [total_index_lock_promotion_attempt_count], [total_index_lock_promotion_count], + [total_forwarded_fetch_count], [data_compression_desc], [page_latch_wait_count], [page_latch_wait_in_ms], @@ -24913,6 +25051,7 @@ ELSE IF (@Mode=1) /*Summarize*/ sz.avg_page_lock_wait_in_ms AS [Avg Page Lock Wait ms], sz.total_index_lock_promotion_attempt_count AS [Lock Escalation Attempts], sz.total_index_lock_promotion_count AS [Lock Escalations], + sz.total_forwarded_fetch_count AS [Forwarded Fetches], sz.data_compression_desc AS [Data Compression], sz.page_latch_wait_count, sz.page_latch_wait_in_ms, @@ -25199,7 +25338,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.07', @VersionDate = '20211106'; +SELECT @Version = '8.08', @VersionDate = '20220108'; IF(@VersionCheckMode = 1) @@ -26982,7 +27121,7 @@ BEGIN SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.07', @VersionDate = '20211106'; + SELECT @Version = '8.08', @VersionDate = '20220108'; IF(@VersionCheckMode = 1) BEGIN @@ -28378,6 +28517,7 @@ DELETE FROM dbo.SqlServerVersions; INSERT INTO dbo.SqlServerVersions (MajorVersionNumber, MinorVersionNumber, Branch, [Url], ReleaseDate, MainstreamSupportEndDate, ExtendedSupportEndDate, MajorVersionName, MinorVersionName) VALUES + (15, 4188, 'CU14', 'https://support.microsoft.com/en-us/help/5007182', '2021-11-22', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 14'), (15, 4178, 'CU13', 'https://support.microsoft.com/en-us/help/5005679', '2021-10-05', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 13'), (15, 4153, 'CU12', 'https://support.microsoft.com/en-us/help/5004524', '2021-08-04', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 12'), (15, 4138, 'CU11', 'https://support.microsoft.com/en-us/help/5003249', '2021-06-10', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 11'), @@ -28778,7 +28918,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.07', @VersionDate = '20211106'; +SELECT @Version = '8.08', @VersionDate = '20220108'; IF(@VersionCheckMode = 1) BEGIN diff --git a/Install-Core-Blitz-With-Query-Store.sql b/Install-Core-Blitz-With-Query-Store.sql old mode 100755 new mode 100644 index c35d33835..bb2a13332 --- a/Install-Core-Blitz-With-Query-Store.sql +++ b/Install-Core-Blitz-With-Query-Store.sql @@ -38,7 +38,7 @@ AS SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.07', @VersionDate = '20211106'; + SELECT @Version = '8.08', @VersionDate = '20220108'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -3028,22 +3028,148 @@ AS IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 53) WITH NOWAIT; - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT TOP 1 - 53 AS CheckID , - 200 AS Priority , - 'Informational' AS FindingsGroup , - 'Cluster Node' AS Finding , - 'https://www.brentozar.com/go/node' AS URL , - 'This is a node in a cluster.' AS Details - FROM sys.dm_os_cluster_nodes; + --INSERT INTO #BlitzResults + -- ( CheckID , + -- Priority , + -- FindingsGroup , + -- Finding , + -- URL , + -- Details + -- ) + -- SELECT TOP 1 + -- 53 AS CheckID , + -- 200 AS Priority , + -- 'Informational' AS FindingsGroup , + -- 'Cluster Node' AS Finding , + -- 'https://BrentOzar.com/go/node' AS URL , + -- 'This is a node in a cluster.' AS Details + -- FROM sys.dm_os_cluster_nodes; + + DECLARE @AOFCI AS INT, @AOAG AS INT, @HAType AS VARCHAR(10), @errmsg AS VARCHAR(200) + + SELECT @AOAG = CAST(SERVERPROPERTY('IsHadrEnabled') AS INT) + SELECT @AOFCI = CAST(SERVERPROPERTY('IsClustered') AS INT) + + IF (SELECT COUNT(DISTINCT join_state_desc) FROM sys.dm_hadr_availability_replica_cluster_states) = 2 + BEGIN --if count is 2 both JOINED_STANDALONE and JOINED_FCI is configured + SET @AOFCI = 1 + END + + SELECT @HAType = + CASE + WHEN @AOFCI = 1 AND @AOAG =1 THEN 'FCIAG' + WHEN @AOFCI = 1 AND @AOAG =0 THEN 'FCI' + WHEN @AOFCI = 0 AND @AOAG =1 THEN 'AG' + ELSE 'STANDALONE' + END + + IF (@HAType IN ('FCIAG','FCI','AG')) + BEGIN + + INSERT INTO #BlitzResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + + SELECT 53 AS CheckID , + 200 AS Priority , + 'Informational' AS FindingsGroup , + 'Cluster Node' AS Finding , + 'https://BrentOzar.com/go/node' AS URL , + 'This is a node in a cluster.' AS Details + + IF @HAType = 'AG' + BEGIN + INSERT INTO #BlitzResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT 53 AS CheckID , + 200 AS Priority , + 'Informational' AS FindingsGroup , + 'Cluster Node Info' AS Finding , + 'https://BrentOzar.com/go/node' AS URL, + 'The cluster nodes are: ' + STUFF((SELECT ', ' + CASE ar.replica_server_name WHEN dhags.primary_replica THEN 'PRIMARY' + ELSE 'SECONDARY' + END + '=' + UPPER(ar.replica_server_name) + FROM sys.availability_groups AS ag + LEFT OUTER JOIN sys.availability_replicas AS ar ON ag.group_id = ar.group_id + LEFT OUTER JOIN sys.dm_hadr_availability_group_states as dhags ON ag.group_id = dhags.group_id + ORDER BY 1 + FOR XML PATH ('') + ), 1, 1, '') + ' - High Availability solution used is AlwaysOn Availability Group (AG)' As Details + END + + IF @HAType = 'FCI' + BEGIN + INSERT INTO #BlitzResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT 53 AS CheckID , + 200 AS Priority , + 'Informational' AS FindingsGroup , + 'Cluster Node Info' AS Finding , + 'https://BrentOzar.com/go/node' AS URL, + 'The cluster nodes are: ' + STUFF((SELECT ', ' + CASE is_current_owner + WHEN 1 THEN 'PRIMARY' + ELSE 'SECONDARY' + END + '=' + UPPER(NodeName) + FROM sys.dm_os_cluster_nodes + ORDER BY 1 + FOR XML PATH ('') + ), 1, 1, '') + ' - High Availability solution used is AlwaysOn Failover Cluster Instance (FCI)' As Details + END + + IF @HAType = 'FCIAG' + BEGIN + INSERT INTO #BlitzResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT 53 AS CheckID , + 200 AS Priority , + 'Informational' AS FindingsGroup , + 'Cluster Node Info' AS Finding , + 'https://BrentOzar.com/go/node' AS URL, + 'The cluster nodes are: ' + STUFF((SELECT ', ' + HighAvailabilityRoleDesc + '=' + ServerName + FROM (SELECT UPPER(ar.replica_server_name) AS ServerName + ,CASE ar.replica_server_name WHEN dhags.primary_replica THEN 'PRIMARY' + ELSE 'SECONDARY' + END AS HighAvailabilityRoleDesc + FROM sys.availability_groups AS ag + LEFT OUTER JOIN sys.availability_replicas AS ar ON ag.group_id = ar.group_id + LEFT OUTER JOIN sys.dm_hadr_availability_group_states as dhags ON ag.group_id = dhags.group_id + WHERE UPPER(CAST(SERVERPROPERTY('ServerName') AS VARCHAR)) <> ar.replica_server_name + UNION ALL + SELECT UPPER(NodeName) AS ServerName + ,CASE is_current_owner + WHEN 1 THEN 'PRIMARY' + ELSE 'SECONDARY' + END AS HighAvailabilityRoleDesc + FROM sys.dm_os_cluster_nodes) AS Z + ORDER BY 1 + FOR XML PATH ('') + ), 1, 1, '') + ' - High Availability solution used is AlwaysOn FCI with AG (Failover Cluster Instance with Availability Group).'As Details + END + END + END; IF NOT EXISTS ( SELECT 1 @@ -8690,7 +8816,8 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 EXEC master.sys.xp_regread @rootkey = 'HKEY_LOCAL_MACHINE', @key = 'SOFTWARE\Policies\Microsoft\Power\PowerSettings', @value_name = 'ActivePowerScheme', - @value = @outval OUTPUT; + @value = @outval OUTPUT, + @no_output = 'no_output'; IF @outval IS NULL /* If power plan was not set by group policy, get local value [Git Hub Issue #1620]*/ EXEC master.sys.xp_regread @rootkey = 'HKEY_LOCAL_MACHINE', @@ -9555,7 +9682,7 @@ AS SET NOCOUNT ON; SET STATISTICS XML OFF; -SELECT @Version = '8.07', @VersionDate = '20211106'; +SELECT @Version = '8.08', @VersionDate = '20220108'; IF(@VersionCheckMode = 1) BEGIN @@ -10433,7 +10560,7 @@ AS SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.07', @VersionDate = '20211106'; + SELECT @Version = '8.08', @VersionDate = '20220108'; IF(@VersionCheckMode = 1) BEGIN @@ -12214,7 +12341,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.07', @VersionDate = '20211106'; +SELECT @Version = '8.08', @VersionDate = '20220108'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -19477,7 +19604,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.07', @VersionDate = '20211106'; +SELECT @Version = '8.08', @VersionDate = '20220108'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -19556,6 +19683,7 @@ DECLARE @LineFeed NVARCHAR(5); DECLARE @DaysUptimeInsertValue NVARCHAR(256); DECLARE @DatabaseToIgnore NVARCHAR(MAX); DECLARE @ColumnList NVARCHAR(MAX); +DECLARE @ColumnListWithApostrophes NVARCHAR(MAX); DECLARE @PartitionCount INT; DECLARE @OptimizeForSequentialKey BIT = 0; @@ -22305,9 +22433,9 @@ BEGIN SET @dsql = N'USE ' + QUOTENAME(@DatabaseName) + N'; IF EXISTS(SELECT * FROM ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_row_groups WHERE object_id = @ObjectID) BEGIN - SET @ColumnList = N''''; + SELECT @ColumnList = N'''', @ColumnListWithApostrophes = N''''; WITH DistinctColumns AS ( - SELECT DISTINCT QUOTENAME(c.name) AS column_name, c.column_id + SELECT DISTINCT QUOTENAME(c.name) AS column_name, QUOTENAME(c.name,'''''''') AS ColumnNameWithApostrophes, c.column_id FROM ' + QUOTENAME(@DatabaseName) + N'.sys.partitions p INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns c ON p.object_id = c.object_id INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.index_columns ic on ic.column_id = c.column_id and ic.object_id = c.object_id AND ic.index_id = p.index_id @@ -22316,9 +22444,10 @@ BEGIN AND EXISTS (SELECT * FROM ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_segments seg WHERE p.partition_id = seg.partition_id AND seg.column_id = ic.index_column_id) AND p.data_compression IN (3,4) ) - SELECT @ColumnList = @ColumnList + column_name + N'', '' - FROM DistinctColumns - ORDER BY column_id; + SELECT @ColumnList = @ColumnList + column_name + N'', '', + @ColumnListWithApostrophes = @ColumnListWithApostrophes + ColumnNameWithApostrophes + N'', '' + FROM DistinctColumns + ORDER BY column_id; SELECT @PartitionCount = COUNT(1) FROM ' + QUOTENAME(@DatabaseName) + N'.sys.partitions WHERE object_id = @ObjectID AND data_compression IN (3,4); END'; @@ -22336,18 +22465,19 @@ BEGIN PRINT SUBSTRING(@dsql, 36000, 40000); END; - EXEC sp_executesql @dsql, N'@ObjectID INT, @ColumnList NVARCHAR(MAX) OUTPUT, @PartitionCount INT OUTPUT', @ObjectID, @ColumnList OUTPUT, @PartitionCount OUTPUT; + EXEC sp_executesql @dsql, N'@ObjectID INT, @ColumnList NVARCHAR(MAX) OUTPUT, @ColumnListWithApostrophes NVARCHAR(MAX) OUTPUT, @PartitionCount INT OUTPUT', @ObjectID, @ColumnList OUTPUT, @ColumnListWithApostrophes OUTPUT, @PartitionCount OUTPUT; IF @PartitionCount < 2 SET @ShowPartitionRanges = 0; IF @Debug = 1 - SELECT @ColumnList AS ColumnstoreColumnList, @PartitionCount AS PartitionCount, @ShowPartitionRanges AS ShowPartitionRanges; + SELECT @ColumnList AS ColumnstoreColumnList, @ColumnListWithApostrophes AS ColumnstoreColumnListWithApostrophes, @PartitionCount AS PartitionCount, @ShowPartitionRanges AS ShowPartitionRanges; IF @ColumnList <> '' BEGIN /* Remove the trailing comma */ SET @ColumnList = LEFT(@ColumnList, LEN(@ColumnList) - 1); + SET @ColumnListWithApostrophes = LEFT(@ColumnListWithApostrophes, LEN(@ColumnListWithApostrophes) - 1); SET @dsql = N'USE ' + QUOTENAME(@DatabaseName) + N'; SELECT partition_number, ' @@ -22389,7 +22519,8 @@ BEGIN LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partition_range_values prvs ON prvs.function_id = pf.function_id AND prvs.boundary_id = p.partition_number - 1 LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partition_range_values prve ON prve.function_id = pf.function_id AND prve.boundary_id = p.partition_number ' ELSE N' ' END + N' LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_segments seg ON p.partition_id = seg.partition_id AND ic.index_column_id = seg.column_id AND rg.row_group_id = seg.segment_id - WHERE rg.object_id = @ObjectID' + WHERE rg.object_id = @ObjectID + AND c.name IN ( ' + @ColumnListWithApostrophes + N')' + CASE WHEN @ShowPartitionRanges = 1 THEN N' ) AS y ' ELSE N' ' END + N' ) AS x @@ -24632,7 +24763,12 @@ ELSE IF (@Mode=1) /*Summarize*/ IF EXISTS(SELECT * FROM @@@OutputServerName@@@.@@@OutputDatabaseName@@@.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''@@@OutputSchemaName@@@'') SET @SchemaExists = 1 IF EXISTS (SELECT * FROM @@@OutputServerName@@@.@@@OutputDatabaseName@@@.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''@@@OutputSchemaName@@@'' AND QUOTENAME(TABLE_NAME) = ''@@@OutputTableName@@@'') - SET @TableExists = 1'; + BEGIN + SET @TableExists = 1 + IF NOT EXISTS(SELECT * FROM @@@OutputServerName@@@.@@@OutputDatabaseName@@@.INFORMATION_SCHEMA.COLUMNS WHERE QUOTENAME(TABLE_SCHEMA) = ''@@@OutputSchemaName@@@'' + AND QUOTENAME(TABLE_NAME) = ''@@@OutputTableName@@@'' AND QUOTENAME(COLUMN_NAME) = ''[total_forwarded_fetch_count]'') + EXEC @@@OutputServerName@@@.@@@OutputDatabaseName@@@.dbo.sp_executesql N''ALTER TABLE @@@OutputSchemaName@@@.@@@OutputTableName@@@ ADD [total_forwarded_fetch_count] BIGINT'' + END'; SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputServerName@@@', @OutputServerName); SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName); @@ -24712,6 +24848,7 @@ ELSE IF (@Mode=1) /*Summarize*/ [avg_page_lock_wait_in_ms] BIGINT, [total_index_lock_promotion_attempt_count] BIGINT, [total_index_lock_promotion_count] BIGINT, + [total_forwarded_fetch_count] BIGINT, [data_compression_desc] NVARCHAR(4000), [page_latch_wait_count] BIGINT, [page_latch_wait_in_ms] BIGINT, @@ -24823,6 +24960,7 @@ ELSE IF (@Mode=1) /*Summarize*/ [avg_page_lock_wait_in_ms], [total_index_lock_promotion_attempt_count], [total_index_lock_promotion_count], + [total_forwarded_fetch_count], [data_compression_desc], [page_latch_wait_count], [page_latch_wait_in_ms], @@ -24913,6 +25051,7 @@ ELSE IF (@Mode=1) /*Summarize*/ sz.avg_page_lock_wait_in_ms AS [Avg Page Lock Wait ms], sz.total_index_lock_promotion_attempt_count AS [Lock Escalation Attempts], sz.total_index_lock_promotion_count AS [Lock Escalations], + sz.total_forwarded_fetch_count AS [Forwarded Fetches], sz.data_compression_desc AS [Data Compression], sz.page_latch_wait_count, sz.page_latch_wait_in_ms, @@ -25199,7 +25338,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.07', @VersionDate = '20211106'; +SELECT @Version = '8.08', @VersionDate = '20220108'; IF(@VersionCheckMode = 1) @@ -27006,7 +27145,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.07', @VersionDate = '20211106'; +SELECT @Version = '8.08', @VersionDate = '20220108'; IF(@VersionCheckMode = 1) BEGIN RETURN; @@ -32737,7 +32876,7 @@ BEGIN SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.07', @VersionDate = '20211106'; + SELECT @Version = '8.08', @VersionDate = '20220108'; IF(@VersionCheckMode = 1) BEGIN @@ -34133,6 +34272,7 @@ DELETE FROM dbo.SqlServerVersions; INSERT INTO dbo.SqlServerVersions (MajorVersionNumber, MinorVersionNumber, Branch, [Url], ReleaseDate, MainstreamSupportEndDate, ExtendedSupportEndDate, MajorVersionName, MinorVersionName) VALUES + (15, 4188, 'CU14', 'https://support.microsoft.com/en-us/help/5007182', '2021-11-22', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 14'), (15, 4178, 'CU13', 'https://support.microsoft.com/en-us/help/5005679', '2021-10-05', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 13'), (15, 4153, 'CU12', 'https://support.microsoft.com/en-us/help/5004524', '2021-08-04', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 12'), (15, 4138, 'CU11', 'https://support.microsoft.com/en-us/help/5003249', '2021-06-10', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 11'), @@ -34533,7 +34673,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.07', @VersionDate = '20211106'; +SELECT @Version = '8.08', @VersionDate = '20220108'; IF(@VersionCheckMode = 1) BEGIN diff --git a/LICENSE.md b/LICENSE.md index 0dd845f31..a6a54d558 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -3,13 +3,13 @@ MIT License Copyright for portions of sp_Blitz are held by Microsoft as part of project tigertoolbox and are provided under the MIT license: https://github.com/Microsoft/tigertoolbox -All other copyrights for sp_Blitz are held by Brent Ozar Unlimited, 2021 as +All other copyrights for sp_Blitz are held by Brent Ozar Unlimited, 2022 as described below. Copyright for portions of DatabaseRestore are held by GregWhiteDBA as part of project MSSQLAutoRestore and are provided under the MIT license: https://github.com/GregWhiteDBA/MSSQLAutoRestore -All other copyrights for DatabaseRestore are held by Brent Ozar Unlimited, 2021 +All other copyrights for DatabaseRestore are held by Brent Ozar Unlimited, 2022 as described below. Copyright for sp_BlitzInMemoryOLTP are held by Ned Otter and Konstantin @@ -21,7 +21,7 @@ project SqlServerVersionScript and are provided under the MIT license: https://github.com/jadarnel27/SqlServerVersionScript -Copyright (c) 2021 Brent Ozar Unlimited +Copyright (c) 2022 Brent Ozar Unlimited Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/sp_AllNightLog.sql b/sp_AllNightLog.sql index 4ceff5be6..230716552 100644 --- a/sp_AllNightLog.sql +++ b/sp_AllNightLog.sql @@ -31,7 +31,7 @@ SET STATISTICS XML OFF; BEGIN; -SELECT @Version = '8.07', @VersionDate = '20211106'; +SELECT @Version = '8.08', @VersionDate = '20220108'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_AllNightLog_Setup.sql b/sp_AllNightLog_Setup.sql index ee4b8a959..449142e38 100644 --- a/sp_AllNightLog_Setup.sql +++ b/sp_AllNightLog_Setup.sql @@ -38,7 +38,7 @@ SET STATISTICS XML OFF; BEGIN; -SELECT @Version = '8.07', @VersionDate = '20211106'; +SELECT @Version = '8.08', @VersionDate = '20220108'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_Blitz.sql b/sp_Blitz.sql index becd7a322..d4b503246 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -38,7 +38,7 @@ AS SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.07', @VersionDate = '20211106'; + SELECT @Version = '8.08', @VersionDate = '20220108'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) diff --git a/sp_BlitzAnalysis.sql b/sp_BlitzAnalysis.sql index 62a6e7f85..39e86d871 100644 --- a/sp_BlitzAnalysis.sql +++ b/sp_BlitzAnalysis.sql @@ -37,7 +37,7 @@ AS SET NOCOUNT ON; SET STATISTICS XML OFF; -SELECT @Version = '8.07', @VersionDate = '20211106'; +SELECT @Version = '8.08', @VersionDate = '20220108'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_BlitzBackups.sql b/sp_BlitzBackups.sql index 8ea2e3126..ffa515fe0 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.07', @VersionDate = '20211106'; + SELECT @Version = '8.08', @VersionDate = '20220108'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_BlitzCache.sql b/sp_BlitzCache.sql index 470b71b94..590e270c3 100644 --- a/sp_BlitzCache.sql +++ b/sp_BlitzCache.sql @@ -280,7 +280,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.07', @VersionDate = '20211106'; +SELECT @Version = '8.08', @VersionDate = '20220108'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) diff --git a/sp_BlitzFirst.sql b/sp_BlitzFirst.sql index ba9a09fdc..3425bd06d 100644 --- a/sp_BlitzFirst.sql +++ b/sp_BlitzFirst.sql @@ -46,7 +46,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.07', @VersionDate = '20211106'; +SELECT @Version = '8.08', @VersionDate = '20220108'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_BlitzInMemoryOLTP.sql b/sp_BlitzInMemoryOLTP.sql index 6b8263a95..6c7b3270b 100644 --- a/sp_BlitzInMemoryOLTP.sql +++ b/sp_BlitzInMemoryOLTP.sql @@ -82,7 +82,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI */ AS DECLARE @ScriptVersion VARCHAR(30); -SELECT @ScriptVersion = '1.8', @VersionDate = '20211106'; +SELECT @ScriptVersion = '1.8', @VersionDate = '20220108'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index 4c98114ab..da83ffe95 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -48,7 +48,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.07', @VersionDate = '20211106'; +SELECT @Version = '8.08', @VersionDate = '20220108'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index e3905ca52..3760ee4ce 100644 --- a/sp_BlitzLock.sql +++ b/sp_BlitzLock.sql @@ -33,7 +33,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.07', @VersionDate = '20211106'; +SELECT @Version = '8.08', @VersionDate = '20220108'; IF(@VersionCheckMode = 1) diff --git a/sp_BlitzQueryStore.sql b/sp_BlitzQueryStore.sql index 1eaf6084f..423fd9f5c 100644 --- a/sp_BlitzQueryStore.sql +++ b/sp_BlitzQueryStore.sql @@ -57,7 +57,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.07', @VersionDate = '20211106'; +SELECT @Version = '8.08', @VersionDate = '20220108'; IF(@VersionCheckMode = 1) BEGIN RETURN; diff --git a/sp_BlitzWho.sql b/sp_BlitzWho.sql index d602068eb..9812ccfe6 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.07', @VersionDate = '20211106'; + SELECT @Version = '8.08', @VersionDate = '20220108'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_DatabaseRestore.sql b/sp_DatabaseRestore.sql index 7cc0a30f0..f2e3ae5bb 100755 --- a/sp_DatabaseRestore.sql +++ b/sp_DatabaseRestore.sql @@ -42,7 +42,7 @@ SET STATISTICS XML OFF; /*Versioning details*/ -SELECT @Version = '8.07', @VersionDate = '20211106'; +SELECT @Version = '8.08', @VersionDate = '20220108'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_ineachdb.sql b/sp_ineachdb.sql index 7bd202f34..1b38a61bb 100644 --- a/sp_ineachdb.sql +++ b/sp_ineachdb.sql @@ -35,7 +35,7 @@ BEGIN SET NOCOUNT ON; SET STATISTICS XML OFF; - SELECT @Version = '8.07', @VersionDate = '20211106'; + SELECT @Version = '8.08', @VersionDate = '20220108'; IF(@VersionCheckMode = 1) BEGIN From be5e63ea7045261a881efd78812d4e489dab52f5 Mon Sep 17 00:00:00 2001 From: Anthony Green <40231097+Ant-Green@users.noreply.github.com> Date: Fri, 14 Jan 2022 08:57:04 +0000 Subject: [PATCH 279/662] Add 2017CU28 Added 2017 CU28 and normalised line beginnings to be spaces not tabs --- SqlServerVersions.sql | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/SqlServerVersions.sql b/SqlServerVersions.sql index 774cf6130..c521bc3b5 100644 --- a/SqlServerVersions.sql +++ b/SqlServerVersions.sql @@ -58,9 +58,10 @@ VALUES (15, 4003, 'CU1', 'https://support.microsoft.com/en-us/help/4527376', '2020-01-07', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 1 '), (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 '), - (14, 3421, 'RTM CU27', 'https://support.microsoft.com/en-us/help/5006944', '2021-10-27', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 27'), - (14, 3411, 'RTM CU26', 'https://support.microsoft.com/en-us/help/5005226', '2021-09-14', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 26'), - (14, 3401, 'RTM CU25', 'https://support.microsoft.com/en-us/help/5003830', '2021-07-12', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 25'), + (14, 3430, 'RTM CU28', 'https://support.microsoft.com/en-us/help/5006944', '2022-01-13', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 28'), + (14, 3421, 'RTM CU27', 'https://support.microsoft.com/en-us/help/5006944', '2021-10-27', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 27'), + (14, 3411, 'RTM CU26', 'https://support.microsoft.com/en-us/help/5005226', '2021-09-14', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 26'), + (14, 3401, 'RTM CU25', 'https://support.microsoft.com/en-us/help/5003830', '2021-07-12', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 25'), (14, 3391, 'RTM CU24', 'https://support.microsoft.com/en-us/help/5001228', '2021-05-10', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 24'), (14, 3381, 'RTM CU23', 'https://support.microsoft.com/en-us/help/5000685', '2021-02-25', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 23'), (14, 3370, 'RTM CU22 GDR', 'https://support.microsoft.com/en-us/help/4583457', '2021-01-12', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 22 GDR'), @@ -87,9 +88,9 @@ VALUES (14, 3008, 'RTM CU2', 'https://support.microsoft.com/en-us/help/4052574', '2017-11-28', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 2'), (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 '), - (13, 6404, 'SP3 GDR', 'https://support.microsoft.com/en-us/help/5006943', '2021-10-27', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3 GDR'), - (13, 6300, 'SP3 ', 'https://support.microsoft.com/en-us/help/5003279', '2021-09-15', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3'), - (13, 5888, 'SP2 CU17', 'https://support.microsoft.com/en-us/help/5001092', '2021-03-29', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 17'), + (13, 6404, 'SP3 GDR', 'https://support.microsoft.com/en-us/help/5006943', '2021-10-27', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3 GDR'), + (13, 6300, 'SP3 ', 'https://support.microsoft.com/en-us/help/5003279', '2021-09-15', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3'), + (13, 5888, 'SP2 CU17', 'https://support.microsoft.com/en-us/help/5001092', '2021-03-29', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 17'), (13, 5882, 'SP2 CU16', 'https://support.microsoft.com/en-us/help/5000645', '2021-02-11', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 16'), (13, 5865, 'SP2 CU15 GDR', 'https://support.microsoft.com/en-us/help/4583461', '2021-01-12', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 15 GDR'), (13, 5850, 'SP2 CU15', 'https://support.microsoft.com/en-us/help/4577775', '2020-09-28', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 15'), From 6fadc85b4acbf9257176d94ecb7d739814458b4d Mon Sep 17 00:00:00 2001 From: Robert Hague Date: Wed, 19 Jan 2022 17:58:45 +0000 Subject: [PATCH 280/662] sp_DatabaseRestore: Fix backup file selection for @StopAt and non-split diff backups --- sp_DatabaseRestore.sql | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/sp_DatabaseRestore.sql b/sp_DatabaseRestore.sql index f2e3ae5bb..99c689471 100755 --- a/sp_DatabaseRestore.sql +++ b/sp_DatabaseRestore.sql @@ -1020,17 +1020,14 @@ BEGIN END /*End folder sanity check*/ -- Find latest diff backup - SELECT @LastDiffBackup = MAX(CASE - WHEN @StopAt IS NULL OR REPLACE( RIGHT( REPLACE( BackupFile, RIGHT( BackupFile, PATINDEX( '%_[0-9][0-9]%', REVERSE( BackupFile ) ) ), '' ), 16 ), '_', '' ) <= @StopAt THEN - BackupFile - ELSE '' - END) + SELECT TOP 1 @LastDiffBackup = BackupFile, @CurrentBackupPathDiff = BackupPath FROM @FileList WHERE BackupFile LIKE N'%.bak' AND BackupFile LIKE N'%' + @Database + '%' AND - (@StopAt IS NULL OR REPLACE( RIGHT( REPLACE( @LastDiffBackup, RIGHT( @LastDiffBackup, PATINDEX( '%_[0-9][0-9]%', REVERSE( @LastDiffBackup ) ) ), '' ), 16 ), '_', '' ) <= @StopAt); + (@StopAt IS NULL OR REPLACE( RIGHT( REPLACE( BackupFile, RIGHT( BackupFile, PATINDEX( '%_[0-9][0-9]%', REVERSE( BackupFile ) ) ), '' ), 16 ), '_', '' ) <= @StopAt) + ORDER BY BackUpFile DESC; -- Load FileList data into Temp Table sorted by DateTime Stamp desc SELECT BackupPath, BackupFile INTO #SplitDiffBackups @@ -1042,10 +1039,6 @@ BEGIN --No file = no backup to restore SET @LastDiffBackupDateTime = REPLACE( RIGHT( REPLACE( @LastDiffBackup, RIGHT( @LastDiffBackup, PATINDEX( '%_[0-9][0-9]%', REVERSE( @LastDiffBackup ) ) ), '' ), 16 ), '_', '' ); - -- Get the TOP record to use in "Restore HeaderOnly/FileListOnly" statement as well as non-split backups - SELECT TOP 1 @CurrentBackupPathDiff = BackupPath, @LastDiffBackup = BackupFile FROM @FileList - ORDER BY REPLACE( RIGHT( REPLACE( BackupFile, RIGHT( BackupFile, PATINDEX( '%_[0-9][0-9]%', REVERSE( BackupFile ) ) ), '' ), 16 ), '_', '' ) DESC; - IF @RestoreDiff = 1 AND @BackupDateTime < @LastDiffBackupDateTime BEGIN From 137ce0eec92f08854ae2557214154ea1eef33654 Mon Sep 17 00:00:00 2001 From: DavidAPoole <73661937+DavidAPoole@users.noreply.github.com> Date: Fri, 21 Jan 2022 10:38:12 +0000 Subject: [PATCH 281/662] #3063 sp_Blitz Arithmetic Overflow Error Check 158 Fixes #3063 --- sp_Blitz.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index d4b503246..04207319f 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -6460,8 +6460,8 @@ IF @ProductVersionMajor >= 10 ''File Configuration'' AS FindingsGroup, ''File growth set to 1MB'', ''https://www.brentozar.com/go/percentgrowth'' AS URL, - ''The ['' + DB_NAME() + ''] database file '' + f.physical_name + '' is using 1MB filegrowth settings, but it has grown to '' + CAST((f.size * 8 / 1000000) AS NVARCHAR(10)) + '' GB. Time to up the growth amount.'' - FROM [?].sys.database_files f + ''The ['' + DB_NAME() + ''] database file '' + f.physical_name + '' is using 1MB filegrowth settings, but it has grown to '' + CAST((CAST(f.size AS BIGINT) * 8 / 1000000) AS NVARCHAR(10)) + '' GB. Time to up the growth amount.'' + FROM [?].sys.database_files f WHERE is_percent_growth = 0 and growth=128 and size > 128000 OPTION (RECOMPILE);'; END; From f80432d0be13b23ca10ef99d83602bac4f082087 Mon Sep 17 00:00:00 2001 From: Franz Renesnicek Date: Mon, 31 Jan 2022 08:48:02 +0100 Subject: [PATCH 282/662] Set QUOTED_IDENTIFIER ON for CheckID = 164 to avoid a server crash (dump) when column sys.plan_guides.hints contains double quotes. --- sp_Blitz.sql | 1 + 1 file changed, 1 insertion(+) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index d4b503246..540f650ff 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -6593,6 +6593,7 @@ IF @ProductVersionMajor >= 10 EXEC dbo.sp_MSforeachdb 'USE [?]; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + SET QUOTED_IDENTIFIER ON; INSERT INTO #BlitzResults (CheckID, DatabaseName, From d30edb0ac134bf7c9dffbcaba13a27f6d852e3c2 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Thu, 3 Mar 2022 07:12:59 -0700 Subject: [PATCH 283/662] #3075 sp_BlitzFirst stats check Add a USE to the top of the dynamic SQL so it runs in every database. Closes #3075. --- sp_BlitzFirst.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sp_BlitzFirst.sql b/sp_BlitzFirst.sql index 3425bd06d..4581a223d 100644 --- a/sp_BlitzFirst.sql +++ b/sp_BlitzFirst.sql @@ -2470,7 +2470,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, ELSE SET @StringToExecute = N''; - SET @StringToExecute = @StringToExecute + 'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; SET LOCK_TIMEOUT 1000;' + @LineFeed + + SET @StringToExecute = @StringToExecute + 'USE [?]; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; SET LOCK_TIMEOUT 1000;' + @LineFeed + 'BEGIN TRY' + @LineFeed + ' INSERT INTO #UpdatedStats(HowToStopIt, RowsForSorting)' + @LineFeed + ' SELECT HowToStopIt = ' + @LineFeed + From 7d31708b4d93ed0aeff181f0f3ccfdc08508429c Mon Sep 17 00:00:00 2001 From: Fiander <51764122+Fiander@users.noreply.github.com> Date: Mon, 7 Mar 2022 12:31:39 +0100 Subject: [PATCH 284/662] Update Uninstall.sql Fixes #3077 --- Uninstall.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Uninstall.sql b/Uninstall.sql index 42ff2d9c5..f33b68851 100644 --- a/Uninstall.sql +++ b/Uninstall.sql @@ -37,7 +37,7 @@ BEGIN SELECT @SQL += N'DROP PROCEDURE dbo.' + D.ProcedureName + ';' + CHAR(10) FROM sys.procedures P - JOIN #ToDelete D ON D.ProcedureName = P.name; + JOIN #ToDelete D ON D.ProcedureName = P.name COLLATE DATABASE_DEFAULT; SELECT @SQL += N'DROP TABLE dbo.SqlServerVersions;' + CHAR(10) FROM sys.tables From 6f2b376ebe59549b491dff61001e7572bb499fbc Mon Sep 17 00:00:00 2001 From: Anthony Green <40231097+Ant-Green@users.noreply.github.com> Date: Fri, 1 Apr 2022 07:33:57 +0100 Subject: [PATCH 285/662] Added 2017 CU29 Added 2017 CU29 --- SqlServerVersions.sql | 1 + 1 file changed, 1 insertion(+) diff --git a/SqlServerVersions.sql b/SqlServerVersions.sql index c521bc3b5..f225c6440 100644 --- a/SqlServerVersions.sql +++ b/SqlServerVersions.sql @@ -58,6 +58,7 @@ VALUES (15, 4003, 'CU1', 'https://support.microsoft.com/en-us/help/4527376', '2020-01-07', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 1 '), (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 '), + (14, 3436, 'RTM CU29', 'https://support.microsoft.com/en-us/help/5010786', '2022-03-31', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 29'), (14, 3430, 'RTM CU28', 'https://support.microsoft.com/en-us/help/5006944', '2022-01-13', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 28'), (14, 3421, 'RTM CU27', 'https://support.microsoft.com/en-us/help/5006944', '2021-10-27', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 27'), (14, 3411, 'RTM CU26', 'https://support.microsoft.com/en-us/help/5005226', '2021-09-14', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 26'), From f2b25cdb477ad09a4b5a2d459812b2302384d499 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Fri, 8 Apr 2022 01:53:46 -0700 Subject: [PATCH 286/662] #3081 sp_Blitz large log files are OK Multiple log files on the same drive are OK if they're 2TB or larger. Closes #3081. --- sp_Blitz.sql | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index 6e89cb968..3e5fda1c5 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -6377,7 +6377,8 @@ IF @ProductVersionMajor >= 10 FROM [?].sys.database_files WHERE type_desc = ''LOG'' AND N''?'' <> ''[tempdb]'' GROUP BY LEFT(physical_name, 1) - HAVING COUNT(*) > 1 OPTION (RECOMPILE);'; + HAVING COUNT(*) > 1 + AND SUM(size) < 268435456 OPTION (RECOMPILE);'; END; IF NOT EXISTS ( SELECT 1 From 17fa71a54a2a872938e19484f1dd14cbd4b5c4aa Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Fri, 8 Apr 2022 03:34:20 -0700 Subject: [PATCH 287/662] 2022-04-08 Release --- Install-All-Scripts.sql | 65 ++++++++++++------------- Install-Core-Blitz-No-Query-Store.sql | 42 ++++++++-------- Install-Core-Blitz-With-Query-Store.sql | 44 +++++++++-------- SqlServerVersions.sql | 1 + sp_AllNightLog.sql | 2 +- sp_AllNightLog_Setup.sql | 2 +- sp_Blitz.sql | 2 +- sp_BlitzAnalysis.sql | 2 +- sp_BlitzBackups.sql | 2 +- sp_BlitzCache.sql | 2 +- sp_BlitzFirst.sql | 2 +- sp_BlitzInMemoryOLTP.sql | 2 +- sp_BlitzIndex.sql | 2 +- sp_BlitzLock.sql | 2 +- sp_BlitzQueryStore.sql | 2 +- sp_BlitzWho.sql | 2 +- sp_DatabaseRestore.sql | 2 +- sp_ineachdb.sql | 2 +- 18 files changed, 93 insertions(+), 87 deletions(-) diff --git a/Install-All-Scripts.sql b/Install-All-Scripts.sql index 1428aa776..50728bec3 100644 --- a/Install-All-Scripts.sql +++ b/Install-All-Scripts.sql @@ -31,7 +31,7 @@ SET STATISTICS XML OFF; BEGIN; -SELECT @Version = '8.08', @VersionDate = '20220108'; +SELECT @Version = '8.09', @VersionDate = '20220408'; IF(@VersionCheckMode = 1) BEGIN @@ -1547,7 +1547,7 @@ SET STATISTICS XML OFF; BEGIN; -SELECT @Version = '8.08', @VersionDate = '20220108'; +SELECT @Version = '8.09', @VersionDate = '20220408'; IF(@VersionCheckMode = 1) BEGIN @@ -2891,7 +2891,7 @@ AS SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.08', @VersionDate = '20220108'; + SELECT @Version = '8.09', @VersionDate = '20220408'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -9230,7 +9230,8 @@ IF @ProductVersionMajor >= 10 FROM [?].sys.database_files WHERE type_desc = ''LOG'' AND N''?'' <> ''[tempdb]'' GROUP BY LEFT(physical_name, 1) - HAVING COUNT(*) > 1 OPTION (RECOMPILE);'; + HAVING COUNT(*) > 1 + AND SUM(size) < 268435456 OPTION (RECOMPILE);'; END; IF NOT EXISTS ( SELECT 1 @@ -9313,8 +9314,8 @@ IF @ProductVersionMajor >= 10 ''File Configuration'' AS FindingsGroup, ''File growth set to 1MB'', ''https://www.brentozar.com/go/percentgrowth'' AS URL, - ''The ['' + DB_NAME() + ''] database file '' + f.physical_name + '' is using 1MB filegrowth settings, but it has grown to '' + CAST((f.size * 8 / 1000000) AS NVARCHAR(10)) + '' GB. Time to up the growth amount.'' - FROM [?].sys.database_files f + ''The ['' + DB_NAME() + ''] database file '' + f.physical_name + '' is using 1MB filegrowth settings, but it has grown to '' + CAST((CAST(f.size AS BIGINT) * 8 / 1000000) AS NVARCHAR(10)) + '' GB. Time to up the growth amount.'' + FROM [?].sys.database_files f WHERE is_percent_growth = 0 and growth=128 and size > 128000 OPTION (RECOMPILE);'; END; @@ -9446,6 +9447,7 @@ IF @ProductVersionMajor >= 10 EXEC dbo.sp_MSforeachdb 'USE [?]; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + SET QUOTED_IDENTIFIER ON; INSERT INTO #BlitzResults (CheckID, DatabaseName, @@ -12535,7 +12537,7 @@ AS SET NOCOUNT ON; SET STATISTICS XML OFF; -SELECT @Version = '8.08', @VersionDate = '20220108'; +SELECT @Version = '8.09', @VersionDate = '20220408'; IF(@VersionCheckMode = 1) BEGIN @@ -13413,7 +13415,7 @@ AS SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.08', @VersionDate = '20220108'; + SELECT @Version = '8.09', @VersionDate = '20220408'; IF(@VersionCheckMode = 1) BEGIN @@ -15194,7 +15196,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.08', @VersionDate = '20220108'; +SELECT @Version = '8.09', @VersionDate = '20220408'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -22457,7 +22459,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.08', @VersionDate = '20220108'; +SELECT @Version = '8.09', @VersionDate = '20220408'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -28191,7 +28193,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.08', @VersionDate = '20220108'; +SELECT @Version = '8.09', @VersionDate = '20220408'; IF(@VersionCheckMode = 1) @@ -28244,7 +28246,6 @@ END; I took a long look at this one, and: 1) Trying to account for all the weird places these could crop up is a losing effort. 2) Replace is slow af on lots of XML. - - Your mom. @@ -29998,7 +29999,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.08', @VersionDate = '20220108'; +SELECT @Version = '8.09', @VersionDate = '20220408'; IF(@VersionCheckMode = 1) BEGIN RETURN; @@ -35729,7 +35730,7 @@ BEGIN SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.08', @VersionDate = '20220108'; + SELECT @Version = '8.09', @VersionDate = '20220408'; IF(@VersionCheckMode = 1) BEGIN @@ -37126,7 +37127,7 @@ SET STATISTICS XML OFF; /*Versioning details*/ -SELECT @Version = '8.08', @VersionDate = '20220108'; +SELECT @Version = '8.09', @VersionDate = '20220408'; IF(@VersionCheckMode = 1) BEGIN @@ -38104,17 +38105,14 @@ BEGIN END /*End folder sanity check*/ -- Find latest diff backup - SELECT @LastDiffBackup = MAX(CASE - WHEN @StopAt IS NULL OR REPLACE( RIGHT( REPLACE( BackupFile, RIGHT( BackupFile, PATINDEX( '%_[0-9][0-9]%', REVERSE( BackupFile ) ) ), '' ), 16 ), '_', '' ) <= @StopAt THEN - BackupFile - ELSE '' - END) + SELECT TOP 1 @LastDiffBackup = BackupFile, @CurrentBackupPathDiff = BackupPath FROM @FileList WHERE BackupFile LIKE N'%.bak' AND BackupFile LIKE N'%' + @Database + '%' AND - (@StopAt IS NULL OR REPLACE( RIGHT( REPLACE( @LastDiffBackup, RIGHT( @LastDiffBackup, PATINDEX( '%_[0-9][0-9]%', REVERSE( @LastDiffBackup ) ) ), '' ), 16 ), '_', '' ) <= @StopAt); + (@StopAt IS NULL OR REPLACE( RIGHT( REPLACE( BackupFile, RIGHT( BackupFile, PATINDEX( '%_[0-9][0-9]%', REVERSE( BackupFile ) ) ), '' ), 16 ), '_', '' ) <= @StopAt) + ORDER BY BackUpFile DESC; -- Load FileList data into Temp Table sorted by DateTime Stamp desc SELECT BackupPath, BackupFile INTO #SplitDiffBackups @@ -38126,10 +38124,6 @@ BEGIN --No file = no backup to restore SET @LastDiffBackupDateTime = REPLACE( RIGHT( REPLACE( @LastDiffBackup, RIGHT( @LastDiffBackup, PATINDEX( '%_[0-9][0-9]%', REVERSE( @LastDiffBackup ) ) ), '' ), 16 ), '_', '' ); - -- Get the TOP record to use in "Restore HeaderOnly/FileListOnly" statement as well as non-split backups - SELECT TOP 1 @CurrentBackupPathDiff = BackupPath, @LastDiffBackup = BackupFile FROM @FileList - ORDER BY REPLACE( RIGHT( REPLACE( BackupFile, RIGHT( BackupFile, PATINDEX( '%_[0-9][0-9]%', REVERSE( BackupFile ) ) ), '' ), 16 ), '_', '' ) DESC; - IF @RestoreDiff = 1 AND @BackupDateTime < @LastDiffBackupDateTime BEGIN @@ -38685,7 +38679,7 @@ BEGIN SET NOCOUNT ON; SET STATISTICS XML OFF; - SELECT @Version = '8.08', @VersionDate = '20220108'; + SELECT @Version = '8.09', @VersionDate = '20220408'; IF(@VersionCheckMode = 1) BEGIN @@ -39031,6 +39025,7 @@ DELETE FROM dbo.SqlServerVersions; INSERT INTO dbo.SqlServerVersions (MajorVersionNumber, MinorVersionNumber, Branch, [Url], ReleaseDate, MainstreamSupportEndDate, ExtendedSupportEndDate, MajorVersionName, MinorVersionName) VALUES + (15, 4198, 'CU15', 'https://support.microsoft.com/en-us/help/5008996', '2022-01-07', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 15'), (15, 4188, 'CU14', 'https://support.microsoft.com/en-us/help/5007182', '2021-11-22', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 14'), (15, 4178, 'CU13', 'https://support.microsoft.com/en-us/help/5005679', '2021-10-05', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 13'), (15, 4153, 'CU12', 'https://support.microsoft.com/en-us/help/5004524', '2021-08-04', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 12'), @@ -39048,9 +39043,11 @@ VALUES (15, 4003, 'CU1', 'https://support.microsoft.com/en-us/help/4527376', '2020-01-07', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 1 '), (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 '), - (14, 3421, 'RTM CU27', 'https://support.microsoft.com/en-us/help/5006944', '2021-10-27', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 27'), - (14, 3411, 'RTM CU26', 'https://support.microsoft.com/en-us/help/5005226', '2021-09-14', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 26'), - (14, 3401, 'RTM CU25', 'https://support.microsoft.com/en-us/help/5003830', '2021-07-12', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 25'), + (14, 3436, 'RTM CU29', 'https://support.microsoft.com/en-us/help/5010786', '2022-03-31', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 29'), + (14, 3430, 'RTM CU28', 'https://support.microsoft.com/en-us/help/5006944', '2022-01-13', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 28'), + (14, 3421, 'RTM CU27', 'https://support.microsoft.com/en-us/help/5006944', '2021-10-27', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 27'), + (14, 3411, 'RTM CU26', 'https://support.microsoft.com/en-us/help/5005226', '2021-09-14', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 26'), + (14, 3401, 'RTM CU25', 'https://support.microsoft.com/en-us/help/5003830', '2021-07-12', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 25'), (14, 3391, 'RTM CU24', 'https://support.microsoft.com/en-us/help/5001228', '2021-05-10', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 24'), (14, 3381, 'RTM CU23', 'https://support.microsoft.com/en-us/help/5000685', '2021-02-25', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 23'), (14, 3370, 'RTM CU22 GDR', 'https://support.microsoft.com/en-us/help/4583457', '2021-01-12', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 22 GDR'), @@ -39077,9 +39074,9 @@ VALUES (14, 3008, 'RTM CU2', 'https://support.microsoft.com/en-us/help/4052574', '2017-11-28', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 2'), (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 '), - (13, 6404, 'SP3 GDR', 'https://support.microsoft.com/en-us/help/5006943', '2021-10-27', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3 GDR'), - (13, 6300, 'SP3 ', 'https://support.microsoft.com/en-us/help/5003279', '2021-09-15', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3'), - (13, 5888, 'SP2 CU17', 'https://support.microsoft.com/en-us/help/5001092', '2021-03-29', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 17'), + (13, 6404, 'SP3 GDR', 'https://support.microsoft.com/en-us/help/5006943', '2021-10-27', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3 GDR'), + (13, 6300, 'SP3 ', 'https://support.microsoft.com/en-us/help/5003279', '2021-09-15', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3'), + (13, 5888, 'SP2 CU17', 'https://support.microsoft.com/en-us/help/5001092', '2021-03-29', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 17'), (13, 5882, 'SP2 CU16', 'https://support.microsoft.com/en-us/help/5000645', '2021-02-11', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 16'), (13, 5865, 'SP2 CU15 GDR', 'https://support.microsoft.com/en-us/help/4583461', '2021-01-12', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 15 GDR'), (13, 5850, 'SP2 CU15', 'https://support.microsoft.com/en-us/help/4577775', '2020-09-28', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 15'), @@ -39432,7 +39429,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.08', @VersionDate = '20220108'; +SELECT @Version = '8.09', @VersionDate = '20220408'; IF(@VersionCheckMode = 1) BEGIN @@ -41856,7 +41853,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, ELSE SET @StringToExecute = N''; - SET @StringToExecute = @StringToExecute + 'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; SET LOCK_TIMEOUT 1000;' + @LineFeed + + SET @StringToExecute = @StringToExecute + 'USE [?]; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; SET LOCK_TIMEOUT 1000;' + @LineFeed + 'BEGIN TRY' + @LineFeed + ' INSERT INTO #UpdatedStats(HowToStopIt, RowsForSorting)' + @LineFeed + ' SELECT HowToStopIt = ' + @LineFeed + diff --git a/Install-Core-Blitz-No-Query-Store.sql b/Install-Core-Blitz-No-Query-Store.sql index feca10f79..75d45085f 100644 --- a/Install-Core-Blitz-No-Query-Store.sql +++ b/Install-Core-Blitz-No-Query-Store.sql @@ -38,7 +38,7 @@ AS SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.08', @VersionDate = '20220108'; + SELECT @Version = '8.09', @VersionDate = '20220408'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -6377,7 +6377,8 @@ IF @ProductVersionMajor >= 10 FROM [?].sys.database_files WHERE type_desc = ''LOG'' AND N''?'' <> ''[tempdb]'' GROUP BY LEFT(physical_name, 1) - HAVING COUNT(*) > 1 OPTION (RECOMPILE);'; + HAVING COUNT(*) > 1 + AND SUM(size) < 268435456 OPTION (RECOMPILE);'; END; IF NOT EXISTS ( SELECT 1 @@ -6460,8 +6461,8 @@ IF @ProductVersionMajor >= 10 ''File Configuration'' AS FindingsGroup, ''File growth set to 1MB'', ''https://www.brentozar.com/go/percentgrowth'' AS URL, - ''The ['' + DB_NAME() + ''] database file '' + f.physical_name + '' is using 1MB filegrowth settings, but it has grown to '' + CAST((f.size * 8 / 1000000) AS NVARCHAR(10)) + '' GB. Time to up the growth amount.'' - FROM [?].sys.database_files f + ''The ['' + DB_NAME() + ''] database file '' + f.physical_name + '' is using 1MB filegrowth settings, but it has grown to '' + CAST((CAST(f.size AS BIGINT) * 8 / 1000000) AS NVARCHAR(10)) + '' GB. Time to up the growth amount.'' + FROM [?].sys.database_files f WHERE is_percent_growth = 0 and growth=128 and size > 128000 OPTION (RECOMPILE);'; END; @@ -6593,6 +6594,7 @@ IF @ProductVersionMajor >= 10 EXEC dbo.sp_MSforeachdb 'USE [?]; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + SET QUOTED_IDENTIFIER ON; INSERT INTO #BlitzResults (CheckID, DatabaseName, @@ -9682,7 +9684,7 @@ AS SET NOCOUNT ON; SET STATISTICS XML OFF; -SELECT @Version = '8.08', @VersionDate = '20220108'; +SELECT @Version = '8.09', @VersionDate = '20220408'; IF(@VersionCheckMode = 1) BEGIN @@ -10560,7 +10562,7 @@ AS SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.08', @VersionDate = '20220108'; + SELECT @Version = '8.09', @VersionDate = '20220408'; IF(@VersionCheckMode = 1) BEGIN @@ -12341,7 +12343,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.08', @VersionDate = '20220108'; +SELECT @Version = '8.09', @VersionDate = '20220408'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -19604,7 +19606,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.08', @VersionDate = '20220108'; +SELECT @Version = '8.09', @VersionDate = '20220408'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -25338,7 +25340,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.08', @VersionDate = '20220108'; +SELECT @Version = '8.09', @VersionDate = '20220408'; IF(@VersionCheckMode = 1) @@ -25391,7 +25393,6 @@ END; I took a long look at this one, and: 1) Trying to account for all the weird places these could crop up is a losing effort. 2) Replace is slow af on lots of XML. - - Your mom. @@ -27121,7 +27122,7 @@ BEGIN SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.08', @VersionDate = '20220108'; + SELECT @Version = '8.09', @VersionDate = '20220408'; IF(@VersionCheckMode = 1) BEGIN @@ -28517,6 +28518,7 @@ DELETE FROM dbo.SqlServerVersions; INSERT INTO dbo.SqlServerVersions (MajorVersionNumber, MinorVersionNumber, Branch, [Url], ReleaseDate, MainstreamSupportEndDate, ExtendedSupportEndDate, MajorVersionName, MinorVersionName) VALUES + (15, 4198, 'CU15', 'https://support.microsoft.com/en-us/help/5008996', '2022-01-07', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 15'), (15, 4188, 'CU14', 'https://support.microsoft.com/en-us/help/5007182', '2021-11-22', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 14'), (15, 4178, 'CU13', 'https://support.microsoft.com/en-us/help/5005679', '2021-10-05', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 13'), (15, 4153, 'CU12', 'https://support.microsoft.com/en-us/help/5004524', '2021-08-04', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 12'), @@ -28534,9 +28536,11 @@ VALUES (15, 4003, 'CU1', 'https://support.microsoft.com/en-us/help/4527376', '2020-01-07', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 1 '), (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 '), - (14, 3421, 'RTM CU27', 'https://support.microsoft.com/en-us/help/5006944', '2021-10-27', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 27'), - (14, 3411, 'RTM CU26', 'https://support.microsoft.com/en-us/help/5005226', '2021-09-14', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 26'), - (14, 3401, 'RTM CU25', 'https://support.microsoft.com/en-us/help/5003830', '2021-07-12', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 25'), + (14, 3436, 'RTM CU29', 'https://support.microsoft.com/en-us/help/5010786', '2022-03-31', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 29'), + (14, 3430, 'RTM CU28', 'https://support.microsoft.com/en-us/help/5006944', '2022-01-13', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 28'), + (14, 3421, 'RTM CU27', 'https://support.microsoft.com/en-us/help/5006944', '2021-10-27', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 27'), + (14, 3411, 'RTM CU26', 'https://support.microsoft.com/en-us/help/5005226', '2021-09-14', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 26'), + (14, 3401, 'RTM CU25', 'https://support.microsoft.com/en-us/help/5003830', '2021-07-12', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 25'), (14, 3391, 'RTM CU24', 'https://support.microsoft.com/en-us/help/5001228', '2021-05-10', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 24'), (14, 3381, 'RTM CU23', 'https://support.microsoft.com/en-us/help/5000685', '2021-02-25', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 23'), (14, 3370, 'RTM CU22 GDR', 'https://support.microsoft.com/en-us/help/4583457', '2021-01-12', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 22 GDR'), @@ -28563,9 +28567,9 @@ VALUES (14, 3008, 'RTM CU2', 'https://support.microsoft.com/en-us/help/4052574', '2017-11-28', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 2'), (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 '), - (13, 6404, 'SP3 GDR', 'https://support.microsoft.com/en-us/help/5006943', '2021-10-27', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3 GDR'), - (13, 6300, 'SP3 ', 'https://support.microsoft.com/en-us/help/5003279', '2021-09-15', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3'), - (13, 5888, 'SP2 CU17', 'https://support.microsoft.com/en-us/help/5001092', '2021-03-29', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 17'), + (13, 6404, 'SP3 GDR', 'https://support.microsoft.com/en-us/help/5006943', '2021-10-27', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3 GDR'), + (13, 6300, 'SP3 ', 'https://support.microsoft.com/en-us/help/5003279', '2021-09-15', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3'), + (13, 5888, 'SP2 CU17', 'https://support.microsoft.com/en-us/help/5001092', '2021-03-29', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 17'), (13, 5882, 'SP2 CU16', 'https://support.microsoft.com/en-us/help/5000645', '2021-02-11', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 16'), (13, 5865, 'SP2 CU15 GDR', 'https://support.microsoft.com/en-us/help/4583461', '2021-01-12', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 15 GDR'), (13, 5850, 'SP2 CU15', 'https://support.microsoft.com/en-us/help/4577775', '2020-09-28', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 15'), @@ -28918,7 +28922,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.08', @VersionDate = '20220108'; +SELECT @Version = '8.09', @VersionDate = '20220408'; IF(@VersionCheckMode = 1) BEGIN @@ -31342,7 +31346,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, ELSE SET @StringToExecute = N''; - SET @StringToExecute = @StringToExecute + 'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; SET LOCK_TIMEOUT 1000;' + @LineFeed + + SET @StringToExecute = @StringToExecute + 'USE [?]; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; SET LOCK_TIMEOUT 1000;' + @LineFeed + 'BEGIN TRY' + @LineFeed + ' INSERT INTO #UpdatedStats(HowToStopIt, RowsForSorting)' + @LineFeed + ' SELECT HowToStopIt = ' + @LineFeed + diff --git a/Install-Core-Blitz-With-Query-Store.sql b/Install-Core-Blitz-With-Query-Store.sql index bb2a13332..f3ac34958 100644 --- a/Install-Core-Blitz-With-Query-Store.sql +++ b/Install-Core-Blitz-With-Query-Store.sql @@ -38,7 +38,7 @@ AS SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.08', @VersionDate = '20220108'; + SELECT @Version = '8.09', @VersionDate = '20220408'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -6377,7 +6377,8 @@ IF @ProductVersionMajor >= 10 FROM [?].sys.database_files WHERE type_desc = ''LOG'' AND N''?'' <> ''[tempdb]'' GROUP BY LEFT(physical_name, 1) - HAVING COUNT(*) > 1 OPTION (RECOMPILE);'; + HAVING COUNT(*) > 1 + AND SUM(size) < 268435456 OPTION (RECOMPILE);'; END; IF NOT EXISTS ( SELECT 1 @@ -6460,8 +6461,8 @@ IF @ProductVersionMajor >= 10 ''File Configuration'' AS FindingsGroup, ''File growth set to 1MB'', ''https://www.brentozar.com/go/percentgrowth'' AS URL, - ''The ['' + DB_NAME() + ''] database file '' + f.physical_name + '' is using 1MB filegrowth settings, but it has grown to '' + CAST((f.size * 8 / 1000000) AS NVARCHAR(10)) + '' GB. Time to up the growth amount.'' - FROM [?].sys.database_files f + ''The ['' + DB_NAME() + ''] database file '' + f.physical_name + '' is using 1MB filegrowth settings, but it has grown to '' + CAST((CAST(f.size AS BIGINT) * 8 / 1000000) AS NVARCHAR(10)) + '' GB. Time to up the growth amount.'' + FROM [?].sys.database_files f WHERE is_percent_growth = 0 and growth=128 and size > 128000 OPTION (RECOMPILE);'; END; @@ -6593,6 +6594,7 @@ IF @ProductVersionMajor >= 10 EXEC dbo.sp_MSforeachdb 'USE [?]; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + SET QUOTED_IDENTIFIER ON; INSERT INTO #BlitzResults (CheckID, DatabaseName, @@ -9682,7 +9684,7 @@ AS SET NOCOUNT ON; SET STATISTICS XML OFF; -SELECT @Version = '8.08', @VersionDate = '20220108'; +SELECT @Version = '8.09', @VersionDate = '20220408'; IF(@VersionCheckMode = 1) BEGIN @@ -10560,7 +10562,7 @@ AS SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.08', @VersionDate = '20220108'; + SELECT @Version = '8.09', @VersionDate = '20220408'; IF(@VersionCheckMode = 1) BEGIN @@ -12341,7 +12343,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.08', @VersionDate = '20220108'; +SELECT @Version = '8.09', @VersionDate = '20220408'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -19604,7 +19606,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.08', @VersionDate = '20220108'; +SELECT @Version = '8.09', @VersionDate = '20220408'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -25338,7 +25340,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.08', @VersionDate = '20220108'; +SELECT @Version = '8.09', @VersionDate = '20220408'; IF(@VersionCheckMode = 1) @@ -25391,7 +25393,6 @@ END; I took a long look at this one, and: 1) Trying to account for all the weird places these could crop up is a losing effort. 2) Replace is slow af on lots of XML. - - Your mom. @@ -27145,7 +27146,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.08', @VersionDate = '20220108'; +SELECT @Version = '8.09', @VersionDate = '20220408'; IF(@VersionCheckMode = 1) BEGIN RETURN; @@ -32876,7 +32877,7 @@ BEGIN SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.08', @VersionDate = '20220108'; + SELECT @Version = '8.09', @VersionDate = '20220408'; IF(@VersionCheckMode = 1) BEGIN @@ -34272,6 +34273,7 @@ DELETE FROM dbo.SqlServerVersions; INSERT INTO dbo.SqlServerVersions (MajorVersionNumber, MinorVersionNumber, Branch, [Url], ReleaseDate, MainstreamSupportEndDate, ExtendedSupportEndDate, MajorVersionName, MinorVersionName) VALUES + (15, 4198, 'CU15', 'https://support.microsoft.com/en-us/help/5008996', '2022-01-07', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 15'), (15, 4188, 'CU14', 'https://support.microsoft.com/en-us/help/5007182', '2021-11-22', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 14'), (15, 4178, 'CU13', 'https://support.microsoft.com/en-us/help/5005679', '2021-10-05', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 13'), (15, 4153, 'CU12', 'https://support.microsoft.com/en-us/help/5004524', '2021-08-04', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 12'), @@ -34289,9 +34291,11 @@ VALUES (15, 4003, 'CU1', 'https://support.microsoft.com/en-us/help/4527376', '2020-01-07', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 1 '), (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 '), - (14, 3421, 'RTM CU27', 'https://support.microsoft.com/en-us/help/5006944', '2021-10-27', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 27'), - (14, 3411, 'RTM CU26', 'https://support.microsoft.com/en-us/help/5005226', '2021-09-14', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 26'), - (14, 3401, 'RTM CU25', 'https://support.microsoft.com/en-us/help/5003830', '2021-07-12', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 25'), + (14, 3436, 'RTM CU29', 'https://support.microsoft.com/en-us/help/5010786', '2022-03-31', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 29'), + (14, 3430, 'RTM CU28', 'https://support.microsoft.com/en-us/help/5006944', '2022-01-13', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 28'), + (14, 3421, 'RTM CU27', 'https://support.microsoft.com/en-us/help/5006944', '2021-10-27', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 27'), + (14, 3411, 'RTM CU26', 'https://support.microsoft.com/en-us/help/5005226', '2021-09-14', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 26'), + (14, 3401, 'RTM CU25', 'https://support.microsoft.com/en-us/help/5003830', '2021-07-12', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 25'), (14, 3391, 'RTM CU24', 'https://support.microsoft.com/en-us/help/5001228', '2021-05-10', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 24'), (14, 3381, 'RTM CU23', 'https://support.microsoft.com/en-us/help/5000685', '2021-02-25', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 23'), (14, 3370, 'RTM CU22 GDR', 'https://support.microsoft.com/en-us/help/4583457', '2021-01-12', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 22 GDR'), @@ -34318,9 +34322,9 @@ VALUES (14, 3008, 'RTM CU2', 'https://support.microsoft.com/en-us/help/4052574', '2017-11-28', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 2'), (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 '), - (13, 6404, 'SP3 GDR', 'https://support.microsoft.com/en-us/help/5006943', '2021-10-27', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3 GDR'), - (13, 6300, 'SP3 ', 'https://support.microsoft.com/en-us/help/5003279', '2021-09-15', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3'), - (13, 5888, 'SP2 CU17', 'https://support.microsoft.com/en-us/help/5001092', '2021-03-29', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 17'), + (13, 6404, 'SP3 GDR', 'https://support.microsoft.com/en-us/help/5006943', '2021-10-27', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3 GDR'), + (13, 6300, 'SP3 ', 'https://support.microsoft.com/en-us/help/5003279', '2021-09-15', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3'), + (13, 5888, 'SP2 CU17', 'https://support.microsoft.com/en-us/help/5001092', '2021-03-29', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 17'), (13, 5882, 'SP2 CU16', 'https://support.microsoft.com/en-us/help/5000645', '2021-02-11', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 16'), (13, 5865, 'SP2 CU15 GDR', 'https://support.microsoft.com/en-us/help/4583461', '2021-01-12', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 15 GDR'), (13, 5850, 'SP2 CU15', 'https://support.microsoft.com/en-us/help/4577775', '2020-09-28', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 15'), @@ -34673,7 +34677,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.08', @VersionDate = '20220108'; +SELECT @Version = '8.09', @VersionDate = '20220408'; IF(@VersionCheckMode = 1) BEGIN @@ -37097,7 +37101,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, ELSE SET @StringToExecute = N''; - SET @StringToExecute = @StringToExecute + 'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; SET LOCK_TIMEOUT 1000;' + @LineFeed + + SET @StringToExecute = @StringToExecute + 'USE [?]; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; SET LOCK_TIMEOUT 1000;' + @LineFeed + 'BEGIN TRY' + @LineFeed + ' INSERT INTO #UpdatedStats(HowToStopIt, RowsForSorting)' + @LineFeed + ' SELECT HowToStopIt = ' + @LineFeed + diff --git a/SqlServerVersions.sql b/SqlServerVersions.sql index f225c6440..1c5b98c24 100644 --- a/SqlServerVersions.sql +++ b/SqlServerVersions.sql @@ -41,6 +41,7 @@ DELETE FROM dbo.SqlServerVersions; INSERT INTO dbo.SqlServerVersions (MajorVersionNumber, MinorVersionNumber, Branch, [Url], ReleaseDate, MainstreamSupportEndDate, ExtendedSupportEndDate, MajorVersionName, MinorVersionName) VALUES + (15, 4198, 'CU15', 'https://support.microsoft.com/en-us/help/5008996', '2022-01-07', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 15'), (15, 4188, 'CU14', 'https://support.microsoft.com/en-us/help/5007182', '2021-11-22', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 14'), (15, 4178, 'CU13', 'https://support.microsoft.com/en-us/help/5005679', '2021-10-05', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 13'), (15, 4153, 'CU12', 'https://support.microsoft.com/en-us/help/5004524', '2021-08-04', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 12'), diff --git a/sp_AllNightLog.sql b/sp_AllNightLog.sql index 230716552..8ec538a22 100644 --- a/sp_AllNightLog.sql +++ b/sp_AllNightLog.sql @@ -31,7 +31,7 @@ SET STATISTICS XML OFF; BEGIN; -SELECT @Version = '8.08', @VersionDate = '20220108'; +SELECT @Version = '8.09', @VersionDate = '20220408'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_AllNightLog_Setup.sql b/sp_AllNightLog_Setup.sql index 449142e38..424a9b1d0 100644 --- a/sp_AllNightLog_Setup.sql +++ b/sp_AllNightLog_Setup.sql @@ -38,7 +38,7 @@ SET STATISTICS XML OFF; BEGIN; -SELECT @Version = '8.08', @VersionDate = '20220108'; +SELECT @Version = '8.09', @VersionDate = '20220408'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_Blitz.sql b/sp_Blitz.sql index 3e5fda1c5..6930d2094 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -38,7 +38,7 @@ AS SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.08', @VersionDate = '20220108'; + SELECT @Version = '8.09', @VersionDate = '20220408'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) diff --git a/sp_BlitzAnalysis.sql b/sp_BlitzAnalysis.sql index 39e86d871..bac004cf1 100644 --- a/sp_BlitzAnalysis.sql +++ b/sp_BlitzAnalysis.sql @@ -37,7 +37,7 @@ AS SET NOCOUNT ON; SET STATISTICS XML OFF; -SELECT @Version = '8.08', @VersionDate = '20220108'; +SELECT @Version = '8.09', @VersionDate = '20220408'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_BlitzBackups.sql b/sp_BlitzBackups.sql index ffa515fe0..e3367133a 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.08', @VersionDate = '20220108'; + SELECT @Version = '8.09', @VersionDate = '20220408'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_BlitzCache.sql b/sp_BlitzCache.sql index 590e270c3..97c82c134 100644 --- a/sp_BlitzCache.sql +++ b/sp_BlitzCache.sql @@ -280,7 +280,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.08', @VersionDate = '20220108'; +SELECT @Version = '8.09', @VersionDate = '20220408'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) diff --git a/sp_BlitzFirst.sql b/sp_BlitzFirst.sql index 4581a223d..7069ec0a3 100644 --- a/sp_BlitzFirst.sql +++ b/sp_BlitzFirst.sql @@ -46,7 +46,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.08', @VersionDate = '20220108'; +SELECT @Version = '8.09', @VersionDate = '20220408'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_BlitzInMemoryOLTP.sql b/sp_BlitzInMemoryOLTP.sql index 6c7b3270b..57e44dd8d 100644 --- a/sp_BlitzInMemoryOLTP.sql +++ b/sp_BlitzInMemoryOLTP.sql @@ -82,7 +82,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI */ AS DECLARE @ScriptVersion VARCHAR(30); -SELECT @ScriptVersion = '1.8', @VersionDate = '20220108'; +SELECT @ScriptVersion = '1.8', @VersionDate = '20220408'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index da83ffe95..f88a15c30 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -48,7 +48,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.08', @VersionDate = '20220108'; +SELECT @Version = '8.09', @VersionDate = '20220408'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index a60693f0a..4f86017b4 100644 --- a/sp_BlitzLock.sql +++ b/sp_BlitzLock.sql @@ -33,7 +33,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.08', @VersionDate = '20220108'; +SELECT @Version = '8.09', @VersionDate = '20220408'; IF(@VersionCheckMode = 1) diff --git a/sp_BlitzQueryStore.sql b/sp_BlitzQueryStore.sql index 423fd9f5c..e5f393349 100644 --- a/sp_BlitzQueryStore.sql +++ b/sp_BlitzQueryStore.sql @@ -57,7 +57,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.08', @VersionDate = '20220108'; +SELECT @Version = '8.09', @VersionDate = '20220408'; IF(@VersionCheckMode = 1) BEGIN RETURN; diff --git a/sp_BlitzWho.sql b/sp_BlitzWho.sql index 9812ccfe6..ab0a6e22c 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.08', @VersionDate = '20220108'; + SELECT @Version = '8.09', @VersionDate = '20220408'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_DatabaseRestore.sql b/sp_DatabaseRestore.sql index 99c689471..93149764d 100755 --- a/sp_DatabaseRestore.sql +++ b/sp_DatabaseRestore.sql @@ -42,7 +42,7 @@ SET STATISTICS XML OFF; /*Versioning details*/ -SELECT @Version = '8.08', @VersionDate = '20220108'; +SELECT @Version = '8.09', @VersionDate = '20220408'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_ineachdb.sql b/sp_ineachdb.sql index 1b38a61bb..0339d149d 100644 --- a/sp_ineachdb.sql +++ b/sp_ineachdb.sql @@ -35,7 +35,7 @@ BEGIN SET NOCOUNT ON; SET STATISTICS XML OFF; - SELECT @Version = '8.08', @VersionDate = '20220108'; + SELECT @Version = '8.09', @VersionDate = '20220408'; IF(@VersionCheckMode = 1) BEGIN From b84c03e99ca98f9c2040c3011ca8095d2444290f Mon Sep 17 00:00:00 2001 From: Anthony Green <40231097+Ant-Green@users.noreply.github.com> Date: Wed, 20 Apr 2022 08:15:35 +0100 Subject: [PATCH 288/662] Added 19 CU16 Added 19 CU16 --- SqlServerVersions.sql | 1 + 1 file changed, 1 insertion(+) diff --git a/SqlServerVersions.sql b/SqlServerVersions.sql index 1c5b98c24..0d4f3e9fc 100644 --- a/SqlServerVersions.sql +++ b/SqlServerVersions.sql @@ -41,6 +41,7 @@ DELETE FROM dbo.SqlServerVersions; INSERT INTO dbo.SqlServerVersions (MajorVersionNumber, MinorVersionNumber, Branch, [Url], ReleaseDate, MainstreamSupportEndDate, ExtendedSupportEndDate, MajorVersionName, MinorVersionName) VALUES + (15, 4223, 'CU16', 'https://support.microsoft.com/en-us/help/5011644', '2022-04-18', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 16'), (15, 4198, 'CU15', 'https://support.microsoft.com/en-us/help/5008996', '2022-01-07', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 15'), (15, 4188, 'CU14', 'https://support.microsoft.com/en-us/help/5007182', '2021-11-22', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 14'), (15, 4178, 'CU13', 'https://support.microsoft.com/en-us/help/5005679', '2021-10-05', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 13'), From 9506d15c8fc1d427fc41e567ba7307c7b8f8937c Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Tue, 26 Apr 2022 05:16:45 -0700 Subject: [PATCH 289/662] #3092 sp_BlitzFirst WaitCategories table Avoid erasing & repopulating contents if there are MORE rows in the table than we expect. Closes #3092. --- sp_BlitzFirst.sql | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/sp_BlitzFirst.sql b/sp_BlitzFirst.sql index 7069ec0a3..06149f53e 100644 --- a/sp_BlitzFirst.sql +++ b/sp_BlitzFirst.sql @@ -478,17 +478,6 @@ BEGIN DROP TABLE #FilterPlansByDatabase; CREATE TABLE #FilterPlansByDatabase (DatabaseID INT PRIMARY KEY CLUSTERED); - IF OBJECT_ID('tempdb..##WaitCategories') IS NULL - BEGIN - /* We reuse this one by default rather than recreate it every time. */ - CREATE TABLE ##WaitCategories - ( - WaitType NVARCHAR(60) PRIMARY KEY CLUSTERED, - WaitCategory NVARCHAR(128) NOT NULL, - Ignorable BIT DEFAULT 0 - ); - END; /* IF OBJECT_ID('tempdb..##WaitCategories') IS NULL */ - IF OBJECT_ID ('tempdb..#checkversion') IS NOT NULL DROP TABLE #checkversion; CREATE TABLE #checkversion ( @@ -500,7 +489,18 @@ BEGIN revision AS PARSENAME(CONVERT(VARCHAR(32), version), 1) ); - IF 527 <> (SELECT COALESCE(SUM(1),0) FROM ##WaitCategories) + IF OBJECT_ID('tempdb..##WaitCategories') IS NULL + BEGIN + /* We reuse this one by default rather than recreate it every time. */ + CREATE TABLE ##WaitCategories + ( + WaitType NVARCHAR(60) PRIMARY KEY CLUSTERED, + WaitCategory NVARCHAR(128) NOT NULL, + Ignorable BIT DEFAULT 0 + ); + END; /* IF OBJECT_ID('tempdb..##WaitCategories') IS NULL */ + + IF 527 > (SELECT COALESCE(SUM(1),0) FROM ##WaitCategories) BEGIN TRUNCATE TABLE ##WaitCategories; INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('ASYNC_IO_COMPLETION','Other Disk IO',0); From a22beafe1428beb6e0ea8ba55b287262c945745b Mon Sep 17 00:00:00 2001 From: Ben <60398078+bwiggins10@users.noreply.github.com> Date: Tue, 26 Apr 2022 14:10:20 -0400 Subject: [PATCH 290/662] Update sp_DatabaseRestore.sql Added in check for CommandExecute stored procedure so that it checks for the stored procedure BEFORE it tries to read through all the backup files and then error. Also fixed a spelling mistake in the examples section. --- sp_DatabaseRestore.sql | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/sp_DatabaseRestore.sql b/sp_DatabaseRestore.sql index 93149764d..baeb2f8ff 100755 --- a/sp_DatabaseRestore.sql +++ b/sp_DatabaseRestore.sql @@ -180,7 +180,7 @@ BEGIN @StopAt = ''20170508201501'', @Debug = 1; - --This example NOT execute the restore. Commands will be printed in a copy/paste ready format only + --This example will NOT execute the restore. Commands will be printed in a copy/paste ready format only EXEC dbo.sp_DatabaseRestore @Database = ''DBA'', @BackupPathFull = ''\\StorageServer\LogShipMe\FULL\'', @@ -212,6 +212,11 @@ BEGIN RETURN; END; +IF NOT EXISTS (SELECT name FROM sys.objects WHERE type = 'P' AND name = 'CommandExecute') +BEGIN + RAISERROR('DatabaseRestore requires the CommandExecute stored procedure from the OLA Hallengren Maintenance solution, are you using the correct database?', 15, 1); + RETURN; +END; DECLARE @cmd NVARCHAR(4000) = N'', --Holds xp_cmdshell command @sql NVARCHAR(MAX) = N'', --Holds executable SQL commands From c093c5f59e87bf18ca47956ae2563709b708fd20 Mon Sep 17 00:00:00 2001 From: Ben <60398078+bwiggins10@users.noreply.github.com> Date: Tue, 26 Apr 2022 14:36:42 -0400 Subject: [PATCH 291/662] sp_DatabaseRestore - add check for CommandExec in current database Needed to change the check, as you can execute the blitz scripts from the context of another database, but the previous check would only check if the stored procedure existed in the database where the DatabaseRestore SP resided, and wouldn't check if it existed in the database you were currently using at execution (So it would fail even if the database you were using had it, as it was only checking if it existed in where the toolkit was deployed). --- sp_DatabaseRestore.sql | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/sp_DatabaseRestore.sql b/sp_DatabaseRestore.sql index baeb2f8ff..877286f3b 100755 --- a/sp_DatabaseRestore.sql +++ b/sp_DatabaseRestore.sql @@ -212,11 +212,15 @@ BEGIN RETURN; END; -IF NOT EXISTS (SELECT name FROM sys.objects WHERE type = 'P' AND name = 'CommandExecute') +DECLARE @CurrentDatabaseContext AS VARCHAR(128) = (SELECT DB_NAME()); +DECLARE @CommandExecuteCheck VARCHAR(315) + +SET @CommandExecuteCheck = 'IF NOT EXISTS (SELECT name FROM ' +@CurrentDatabaseContext+'.sys.objects WHERE type = ''P'' AND name = ''CommandExecute'') BEGIN - RAISERROR('DatabaseRestore requires the CommandExecute stored procedure from the OLA Hallengren Maintenance solution, are you using the correct database?', 15, 1); + RAISERROR(''DatabaseRestore requires the CommandExecute stored procedure from the OLA Hallengren Maintenance solution, are you using the correct database?'', 15, 1); RETURN; -END; +END;' +EXEC (@CommandExecuteCheck) DECLARE @cmd NVARCHAR(4000) = N'', --Holds xp_cmdshell command @sql NVARCHAR(MAX) = N'', --Holds executable SQL commands From c3abc93aa4414221cc19623c8f17078627887baa Mon Sep 17 00:00:00 2001 From: Ben <60398078+bwiggins10@users.noreply.github.com> Date: Tue, 26 Apr 2022 14:50:26 -0400 Subject: [PATCH 292/662] Update sp_DatabaseRestore.sql Needed to change the check, as you can execute the blitz scripts from the context of another database, but the previous check would only check if the stored procedure existed in the database where the DatabaseRestore SP resided, and wouldn't check if it existed in the database you were currently using at execution (So it would fail even if the database you were using had it, as it was only checking if it existed in where the FRTK was deployed). --- sp_DatabaseRestore.sql | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/sp_DatabaseRestore.sql b/sp_DatabaseRestore.sql index 877286f3b..94ef6cbc9 100755 --- a/sp_DatabaseRestore.sql +++ b/sp_DatabaseRestore.sql @@ -212,15 +212,20 @@ BEGIN RETURN; END; +BEGIN TRY DECLARE @CurrentDatabaseContext AS VARCHAR(128) = (SELECT DB_NAME()); DECLARE @CommandExecuteCheck VARCHAR(315) SET @CommandExecuteCheck = 'IF NOT EXISTS (SELECT name FROM ' +@CurrentDatabaseContext+'.sys.objects WHERE type = ''P'' AND name = ''CommandExecute'') BEGIN - RAISERROR(''DatabaseRestore requires the CommandExecute stored procedure from the OLA Hallengren Maintenance solution, are you using the correct database?'', 15, 1); + RAISERROR (''DatabaseRestore requires the CommandExecute stored procedure from the OLA Hallengren Maintenance solution, are you using the correct database?'', 15, 1); RETURN; END;' EXEC (@CommandExecuteCheck) +END TRY +BEGIN CATCH +THROW; +END CATCH DECLARE @cmd NVARCHAR(4000) = N'', --Holds xp_cmdshell command @sql NVARCHAR(MAX) = N'', --Holds executable SQL commands From 7abdfdbcb2ca713978d053c540cf0528fc356e7e Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Thu, 5 May 2022 18:52:58 -0700 Subject: [PATCH 293/662] #3096 sp_BlitzCache capitalization Making Row Estimate Mismatch and Calls X Function(s) consistent. Closes #3096. --- sp_BlitzCache.sql | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sp_BlitzCache.sql b/sp_BlitzCache.sql index 97c82c134..4192b6ef1 100644 --- a/sp_BlitzCache.sql +++ b/sp_BlitzCache.sql @@ -4749,8 +4749,8 @@ SELECT DISTINCT CASE WHEN is_remote_query_expensive = 1 THEN ', Expensive Remote Query' ELSE '' END + CASE WHEN trace_flags_session IS NOT NULL THEN ', Session Level Trace Flag(s) Enabled: ' + trace_flags_session ELSE '' END + CASE WHEN is_unused_grant = 1 THEN ', Unused Memory Grant' ELSE '' END + - CASE WHEN function_count > 0 THEN ', Calls ' + CONVERT(VARCHAR(10), (SELECT SUM(b2.function_count) FROM ##BlitzCacheProcs AS b2 WHERE b2.SqlHandle = b.SqlHandle AND b2.QueryHash IS NOT NULL AND SPID = @@SPID) ) + ' function(s)' ELSE '' END + - CASE WHEN clr_function_count > 0 THEN ', Calls ' + CONVERT(VARCHAR(10), (SELECT SUM(b2.clr_function_count) FROM ##BlitzCacheProcs AS b2 WHERE b2.SqlHandle = b.SqlHandle AND b2.QueryHash IS NOT NULL AND SPID = @@SPID) ) + ' CLR function(s)' ELSE '' END + + CASE WHEN function_count > 0 THEN ', Calls ' + CONVERT(VARCHAR(10), (SELECT SUM(b2.function_count) FROM ##BlitzCacheProcs AS b2 WHERE b2.SqlHandle = b.SqlHandle AND b2.QueryHash IS NOT NULL AND SPID = @@SPID) ) + ' Function(s)' ELSE '' END + + CASE WHEN clr_function_count > 0 THEN ', Calls ' + CONVERT(VARCHAR(10), (SELECT SUM(b2.clr_function_count) FROM ##BlitzCacheProcs AS b2 WHERE b2.SqlHandle = b.SqlHandle AND b2.QueryHash IS NOT NULL AND SPID = @@SPID) ) + ' CLR Function(s)' ELSE '' END + CASE WHEN PlanCreationTimeHours <= 4 THEN ', Plan created last 4hrs' ELSE '' END + CASE WHEN is_table_variable = 1 THEN ', Table Variables' ELSE '' END + CASE WHEN no_stats_warning = 1 THEN ', Columns With No Statistics' ELSE '' END + @@ -4777,7 +4777,7 @@ SELECT DISTINCT CASE WHEN is_spool_more_rows = 1 THEN + ', Large Index Row Spool' ELSE '' END + CASE WHEN is_table_spool_expensive = 1 THEN + ', Expensive Table Spool' ELSE '' END + CASE WHEN is_table_spool_more_rows = 1 THEN + ', Many Rows Table Spool' ELSE '' END + - CASE WHEN is_bad_estimate = 1 THEN + ', Row estimate mismatch' ELSE '' END + + CASE WHEN is_bad_estimate = 1 THEN + ', Row Estimate Mismatch' ELSE '' END + CASE WHEN is_paul_white_electric = 1 THEN ', SWITCH!' ELSE '' END + CASE WHEN is_row_goal = 1 THEN ', Row Goals' ELSE '' END + CASE WHEN is_big_spills = 1 THEN ', >500mb spills' ELSE '' END + From a927b4d2b45b29a7bb136d5dc2efe8d4aaa9918a Mon Sep 17 00:00:00 2001 From: David Hooey Date: Tue, 24 May 2022 16:22:13 -0400 Subject: [PATCH 294/662] Adding spid and wait_resource to query results and to output table. --- sp_BlitzLock.sql | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index 4f86017b4..6b2db7841 100644 --- a/sp_BlitzLock.sql +++ b/sp_BlitzLock.sql @@ -221,6 +221,7 @@ You need to use an Azure storage account, and the path has to look like this: ht deadlock_type NVARCHAR(256), event_date datetime, database_name NVARCHAR(256), + spid SMALLINT, deadlock_group NVARCHAR(256), query XML, object_names XML, @@ -232,6 +233,7 @@ You need to use an Azure storage account, and the path has to look like this: ht host_name NVARCHAR(256), client_app NVARCHAR(256), wait_time BIGINT, + wait_resource NVARCHAR(max), priority smallint, log_used BIGINT, last_tran_started datetime, @@ -359,6 +361,7 @@ You need to use an Azure storage account, and the path has to look like this: ht CONVERT(BIT, q.is_parallel) AS is_parallel, q.deadlock_graph, q.id, + q.spid, q.database_id, q.priority, q.log_used, @@ -383,6 +386,7 @@ You need to use an Azure storage account, and the path has to look like this: ht CONVERT(tinyint, dd.is_parallel) + CONVERT(tinyint, dd.is_parallel_batch) AS is_parallel, dd.deadlock_graph, ca.dp.value('@id', 'NVARCHAR(256)') AS id, + ca.dp.value('@spid', 'SMALLINT') AS spid, ca.dp.value('@currentdb', 'BIGINT') AS database_id, ca.dp.value('@priority', 'SMALLINT') AS priority, ca.dp.value('@logused', 'BIGINT') AS log_used, @@ -1285,6 +1289,7 @@ You need to use an Azure storage account, and the path has to look like this: ht dp.event_date, dp.id, dp.victim_id, + dp.spid, dp.database_id, dp.priority, dp.log_used, @@ -1342,6 +1347,7 @@ You need to use an Azure storage account, and the path has to look like this: ht dp.event_date, dp.id, dp.victim_id, + dp.spid, dp.database_id, dp.priority, dp.log_used, @@ -1398,6 +1404,7 @@ You need to use an Azure storage account, and the path has to look like this: ht deadlock_type, event_date, database_name, + spid, deadlock_group, query, object_names, @@ -1409,6 +1416,7 @@ You need to use an Azure storage account, and the path has to look like this: ht host_name, client_app, wait_time, + wait_resource, priority, log_used, last_tran_started, @@ -1433,6 +1441,7 @@ You need to use an Azure storage account, and the path has to look like this: ht d.deadlock_type, d.event_date, DB_NAME(d.database_id) AS database_name, + d.spid, 'Deadlock #' + CONVERT(NVARCHAR(10), d.en) + ', Query #' @@ -1449,6 +1458,7 @@ You need to use an Azure storage account, and the path has to look like this: ht d.host_name, d.client_app, d.wait_time, + d.wait_resource, d.priority, d.log_used, d.last_tran_started, @@ -1508,6 +1518,7 @@ ELSE --Output to database is not set output to client app dp.event_date, dp.id, dp.victim_id, + dp.spid, dp.database_id, dp.priority, dp.log_used, @@ -1565,6 +1576,7 @@ ELSE --Output to database is not set output to client app dp.event_date, dp.id, dp.victim_id, + dp.spid, dp.database_id, dp.priority, dp.log_used, @@ -1620,6 +1632,7 @@ ELSE --Output to database is not set output to client app SELECT d.deadlock_type, d.event_date, DB_NAME(d.database_id) AS database_name, + d.spid, 'Deadlock #' + CONVERT(NVARCHAR(10), d.en) + ', Query #' @@ -1637,6 +1650,7 @@ ELSE --Output to database is not set output to client app d.host_name, d.client_app, d.wait_time, + d.wait_resource, d.priority, d.log_used, d.last_tran_started, @@ -1680,6 +1694,7 @@ ELSE --Output to database is not set output to client app dr.deadlock_type, dr.event_date, dr.database_name, + dr.spid, dr.deadlock_group, ' + CASE @ExportToExcel @@ -1698,6 +1713,7 @@ ELSE --Output to database is not set output to client app dr.host_name, dr.client_app, dr.wait_time, + dr.wait_resource, dr.priority, dr.log_used, dr.last_tran_started, From 63bef1bdde4970f43de9a6c68778735c3bf31ae9 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Sun, 5 Jun 2022 10:43:51 -0700 Subject: [PATCH 295/662] #3105 POPULATE_LOCK_ORDINALS Add this ignorable 2022 wait type. Closes #3105. --- SqlServerVersions.sql | 1 + sp_Blitz.sql | 1 + sp_BlitzFirst.sql | 1 + 3 files changed, 3 insertions(+) diff --git a/SqlServerVersions.sql b/SqlServerVersions.sql index 0d4f3e9fc..95e6a05f8 100644 --- a/SqlServerVersions.sql +++ b/SqlServerVersions.sql @@ -41,6 +41,7 @@ DELETE FROM dbo.SqlServerVersions; INSERT INTO dbo.SqlServerVersions (MajorVersionNumber, MinorVersionNumber, Branch, [Url], ReleaseDate, MainstreamSupportEndDate, ExtendedSupportEndDate, MajorVersionName, MinorVersionName) VALUES + (16, 600, 'CTP2', 'https://support.microsoft.com/en-us/help/5011644', '2022-05-24', '2022-05-24', '2022-05-24', 'SQL Server 2022', 'CTP 2.0'), (15, 4223, 'CU16', 'https://support.microsoft.com/en-us/help/5011644', '2022-04-18', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 16'), (15, 4198, 'CU15', 'https://support.microsoft.com/en-us/help/5008996', '2022-01-07', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 15'), (15, 4188, 'CU14', 'https://support.microsoft.com/en-us/help/5007182', '2021-11-22', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 14'), diff --git a/sp_Blitz.sql b/sp_Blitz.sql index 6930d2094..423faae87 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -768,6 +768,7 @@ AS INSERT INTO #IgnorableWaits VALUES ('PARALLEL_REDO_TRAN_LIST'); INSERT INTO #IgnorableWaits VALUES ('PARALLEL_REDO_WORKER_SYNC'); INSERT INTO #IgnorableWaits VALUES ('PARALLEL_REDO_WORKER_WAIT_WORK'); + INSERT INTO #IgnorableWaits VALUES ('POPULATE_LOCK_ORDINALS'); INSERT INTO #IgnorableWaits VALUES ('PREEMPTIVE_HADR_LEASE_MECHANISM'); INSERT INTO #IgnorableWaits VALUES ('PREEMPTIVE_SP_SERVER_DIAGNOSTICS'); INSERT INTO #IgnorableWaits VALUES ('QDS_ASYNC_QUEUE'); diff --git a/sp_BlitzFirst.sql b/sp_BlitzFirst.sql index 06149f53e..d5be30608 100644 --- a/sp_BlitzFirst.sql +++ b/sp_BlitzFirst.sql @@ -751,6 +751,7 @@ BEGIN INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PARALLEL_REDO_WORKER_SYNC','Replication',1); INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PARALLEL_REDO_WORKER_WAIT_WORK','Replication',1); INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('POOL_LOG_RATE_GOVERNOR','Log Rate Governor',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('POPULATE_LOCK_ORDINALS','Idle',1); INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_ABR','Preemptive',0); INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_CLOSEBACKUPMEDIA','Preemptive',0); INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_CLOSEBACKUPTAPE','Preemptive',0); From 202c6a9cdd810deca5d1d446215b330e4041cf52 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Mon, 6 Jun 2022 04:26:57 -0700 Subject: [PATCH 296/662] #3101 sp_BlitzLock add columns For spid and wait_resource to the output table if they don't already exist. Closes #3101. --- sp_BlitzLock.sql | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index 6b2db7841..8f2acba5d 100644 --- a/sp_BlitzLock.sql +++ b/sp_BlitzLock.sql @@ -120,9 +120,11 @@ END; RETURN; END; /* @Help = 1 */ - DECLARE @ProductVersion NVARCHAR(128); - DECLARE @ProductVersionMajor FLOAT; - DECLARE @ProductVersionMinor INT; + DECLARE @ProductVersion NVARCHAR(128), + @ProductVersionMajor FLOAT, + @ProductVersionMinor INT, + @ObjectFullName NVARCHAR(2000); + SET @ProductVersion = CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)); @@ -214,7 +216,23 @@ You need to use an Azure storage account, and the path has to look like this: ht SELECT @OutputDatabaseName = QUOTENAME(@OutputDatabaseName), @OutputTableName = QUOTENAME(@OutputTableName), @OutputSchemaName = QUOTENAME(@OutputSchemaName) - if(@r is null) --if it is null there is no table, create it from above execution + if(@r is not null) --if it is not null, there is a table, so check for newly added columns + BEGIN + /* If the table doesn't have the new spid column, add it. See Github #3101. */ + SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableName; + SET @StringToExecute = N'IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns + WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + ''')) AND name = ''spid'') + ALTER TABLE ' + @ObjectFullName + N' ADD spid SMALLINT NULL;'; + EXEC(@StringToExecute); + + /* If the table doesn't have the new wait_resource column, add it. See Github #3101. */ + SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableName; + SET @StringToExecute = N'IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns + WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + ''')) AND name = ''wait_resource'') + ALTER TABLE ' + @ObjectFullName + N' ADD wait_resource NVARCHAR(MAX) NULL;'; + EXEC(@StringToExecute); + END + ELSE --if(@r is not null) --if it is null there is no table, create it from above execution BEGIN select @StringToExecute = N'use ' + @OutputDatabaseName + ';create table ' + @OutputSchemaName + '.' + @OutputTableName + ' ( ServerName NVARCHAR(256), From d45cda9b6f9a64aad7af7d02aa78632a1206a92d Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Mon, 6 Jun 2022 04:34:38 -0700 Subject: [PATCH 297/662] #3100 sp_DatabaseRestore BackUpFile typo Fixes one occurrence of BackUpFile instead of BackupFile that broke case-sensitive installs. Closes #3100. --- sp_DatabaseRestore.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sp_DatabaseRestore.sql b/sp_DatabaseRestore.sql index 94ef6cbc9..2c2db162f 100755 --- a/sp_DatabaseRestore.sql +++ b/sp_DatabaseRestore.sql @@ -1041,7 +1041,7 @@ BEGIN BackupFile LIKE N'%' + @Database + '%' AND (@StopAt IS NULL OR REPLACE( RIGHT( REPLACE( BackupFile, RIGHT( BackupFile, PATINDEX( '%_[0-9][0-9]%', REVERSE( BackupFile ) ) ), '' ), 16 ), '_', '' ) <= @StopAt) - ORDER BY BackUpFile DESC; + ORDER BY BackupFile DESC; -- Load FileList data into Temp Table sorted by DateTime Stamp desc SELECT BackupPath, BackupFile INTO #SplitDiffBackups From 17e5f0508dd1576ff36b789889c0a6a16ba1d767 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Mon, 6 Jun 2022 05:22:20 -0700 Subject: [PATCH 298/662] #3085 sp_BlitzIndex too many missing indexes Don't query missing index plans if there are over 1000. Closes #3085. --- sp_BlitzIndex.sql | 78 ++++++++++++++++++++++++++++------------------- 1 file changed, 47 insertions(+), 31 deletions(-) diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index f88a15c30..69a8a8f63 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -130,6 +130,8 @@ DECLARE @ColumnList NVARCHAR(MAX); DECLARE @ColumnListWithApostrophes NVARCHAR(MAX); DECLARE @PartitionCount INT; DECLARE @OptimizeForSequentialKey BIT = 0; +DECLARE @StringToExecute NVARCHAR(MAX); + /* Let's get @SortOrder set to lower case here for comparisons later */ SET @SortOrder = REPLACE(LOWER(@SortOrder), N' ', N'_'); @@ -256,7 +258,8 @@ IF OBJECT_ID('tempdb..#Ignore_Databases') IS NOT NULL index_usage_summary NVARCHAR(MAX) NULL, index_size_summary NVARCHAR(MAX) NULL, create_tsql NVARCHAR(MAX) NULL, - more_info NVARCHAR(MAX)NULL + more_info NVARCHAR(MAX) NULL, + sample_query_plan XML NULL ); CREATE TABLE #IndexSanity @@ -1695,7 +1698,7 @@ BEGIN TRY AND ColumnNamesWithDataTypes.IndexColumnType = ''Included'' ) AS included_columns_with_data_type ' - /* Github #2780 BGO Removing SQL Server 2019's new missing index sample plan because it doesn't work yet:*/ + /* Get the sample query plan if it's available, and if there are less than 1,000 rows in the DMV: */ IF NOT EXISTS ( SELECT @@ -1703,31 +1706,42 @@ BEGIN TRY FROM sys.all_objects AS o WHERE o.name = 'dm_db_missing_index_group_stats_query' ) - SELECT - @dsql += N' , NULL AS sample_query_plan ' - /* Github #2780 BGO Removing SQL Server 2019's new missing index sample plan because it doesn't work yet:*/ + SELECT + @dsql += N' , NULL AS sample_query_plan ' ELSE BEGIN - SELECT - @dsql += N' - , sample_query_plan = - ( - SELECT TOP (1) - p.query_plan - FROM sys.dm_db_missing_index_group_stats gs - CROSS APPLY - ( - SELECT TOP (1) - s.plan_handle - FROM sys.dm_db_missing_index_group_stats_query q - INNER JOIN sys.dm_exec_query_stats s - ON q.query_plan_hash = s.query_plan_hash - WHERE gs.group_handle = q.group_handle - ORDER BY (q.user_seeks + q.user_scans) DESC, s.total_logical_reads DESC - ) q2 - CROSS APPLY sys.dm_exec_query_plan(q2.plan_handle) p - WHERE ig.index_group_handle = gs.group_handle - ) ' + /* The DMV is only supposed to have 600 rows in it. If it's got more, + they could see performance slowdowns - see Github #3085. */ + DECLARE @MissingIndexPlans BIGINT; + SET @StringToExecute = N'SELECT @MissingIndexPlans = COUNT(*) FROM ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_group_stats_query;' + EXEC sp_executesql @StringToExecute, N'@MissingIndexPlans BIGINT OUT', @MissingIndexPlans; + + IF @MissingIndexPlans > 1000 + BEGIN + SELECT @dsql += N' , NULL AS sample_query_plan /* Over 1000 plans found, skipping */ '; + RAISERROR (N'Over 1000 plans found in sys.dm_db_missing_index_group_stats_query - your SQL Server is hitting a bug: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/3085',0,1) WITH NOWAIT; + END + ELSE + SELECT + @dsql += N' + , sample_query_plan = + ( + SELECT TOP (1) + p.query_plan + FROM sys.dm_db_missing_index_group_stats gs + CROSS APPLY + ( + SELECT TOP (1) + s.plan_handle + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_group_stats_query q + INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.dm_exec_query_stats s + ON q.query_plan_hash = s.query_plan_hash + WHERE gs.group_handle = q.group_handle + ORDER BY (q.user_seeks + q.user_scans) DESC, s.total_logical_reads DESC + ) q2 + CROSS APPLY sys.dm_exec_query_plan(q2.plan_handle) p + WHERE ig.index_group_handle = gs.group_handle + ) ' END @@ -3596,10 +3610,10 @@ BEGIN; AND i.is_disabled = 0 GROUP BY i.database_id, i.schema_name, i.[object_id]) INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - index_usage_summary, index_size_summary, create_tsql, more_info ) + index_usage_summary, index_size_summary, create_tsql, more_info, sample_query_plan ) SELECT check_id, t.index_sanity_id, t.check_id, t.findings_group, t.finding, t.[Database Name], t.URL, t.details, t.[definition], - index_estimated_impact, t.index_size_summary, create_tsql, more_info + index_estimated_impact, t.index_size_summary, create_tsql, more_info, sample_query_plan FROM ( SELECT ROW_NUMBER() OVER (ORDER BY magic_benefit_number DESC) AS rownum, @@ -3623,7 +3637,8 @@ BEGIN; mi.create_tsql, mi.more_info, magic_benefit_number, - mi.is_low + mi.is_low, + mi.sample_query_plan FROM #MissingIndexes mi LEFT JOIN index_size_cte sz ON mi.[object_id] = sz.object_id AND mi.database_id = sz.database_id @@ -5002,7 +5017,8 @@ BEGIN; br.index_size_summary AS [Size], COALESCE(br.more_info,sn.more_info,'') AS [More Info], br.URL, - COALESCE(br.create_tsql,ts.create_tsql,'') AS [Create TSQL] + COALESCE(br.create_tsql,ts.create_tsql,'') AS [Create TSQL], + br.sample_query_plan AS [Sample Query Plan] FROM #BlitzIndexResults br LEFT JOIN #IndexSanity sn ON br.index_sanity_id=sn.index_sanity_id @@ -5027,7 +5043,8 @@ BEGIN; br.index_size_summary AS [Size], COALESCE(br.more_info,sn.more_info,'') AS [More Info], br.URL, - COALESCE(br.create_tsql,ts.create_tsql,'') AS [Create TSQL] + COALESCE(br.create_tsql,ts.create_tsql,'') AS [Create TSQL], + br.sample_query_plan AS [Sample Query Plan] FROM #BlitzIndexResults br LEFT JOIN #IndexSanity sn ON br.index_sanity_id=sn.index_sanity_id @@ -5119,7 +5136,6 @@ ELSE IF (@Mode=1) /*Summarize*/ DECLARE @LinkedServerDBCheck NVARCHAR(2000); DECLARE @ValidLinkedServerDB INT; DECLARE @tmpdbchk TABLE (cnt INT); - DECLARE @StringToExecute NVARCHAR(MAX); IF @OutputServerName IS NOT NULL BEGIN From f9f852fef62ad4ee54cf7b619e868dbe4c39989d Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Tue, 7 Jun 2022 05:12:53 -0700 Subject: [PATCH 299/662] #3110 sp_Blitz SQL 2022 In-Memory OLTP Adjusting thresholds to 1GB before we report that it's in use. Closes #3110. --- sp_Blitz.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index 423faae87..83ea30ffe 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -4001,11 +4001,11 @@ AS ''Performance'' AS FindingsGroup, ''In-Memory OLTP (Hekaton) In Use'' AS Finding, ''https://www.brentozar.com/go/hekaton'' AS URL, - CAST(CAST((SUM(mem.pages_kb / 1024.0) / CAST(value_in_use AS INT) * 100) AS INT) AS NVARCHAR(100)) + ''% of your '' + CAST(CAST((CAST(value_in_use AS DECIMAL(38,1)) / 1024) AS MONEY) AS NVARCHAR(100)) + ''GB of your max server memory is being used for in-memory OLTP tables (Hekaton).'' AS Details + CAST(CAST((SUM(mem.pages_kb / 1024.0) / CAST(value_in_use AS INT) * 100) AS DECIMAL(6,1)) AS NVARCHAR(100)) + ''% of your '' + CAST(CAST((CAST(value_in_use AS DECIMAL(38,1)) / 1024) AS MONEY) AS NVARCHAR(100)) + ''GB of your max server memory is being used for in-memory OLTP tables (Hekaton).'' AS Details FROM sys.configurations c INNER JOIN sys.dm_os_memory_clerks mem ON mem.type = ''MEMORYCLERK_XTP'' WHERE c.name = ''max server memory (MB)'' GROUP BY c.value_in_use - HAVING SUM(mem.pages_kb / 1024.0) > 10 OPTION (RECOMPILE)'; + HAVING SUM(mem.pages_kb / 1024.0) > 1000 OPTION (RECOMPILE)'; IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; From 22170f91fabe04997dc795fae49bc288522d9fc4 Mon Sep 17 00:00:00 2001 From: andreasjordan Date: Fri, 1 Jul 2022 16:13:52 +0200 Subject: [PATCH 300/662] Add "used" to make information clearer --- sp_Blitz.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index 83ea30ffe..19730a5fa 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -8694,7 +8694,7 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 + '' GB free on '' + i.drive + '' drive '' + i.logical_volume_name + '' out of '' + CAST(CAST(i.total_MB/1024 AS NUMERIC(18,2)) AS VARCHAR(30)) - + '' GB total ('' + CAST(i.used_percent AS VARCHAR(30)) + ''%)'' END + + '' GB total ('' + CAST(i.used_percent AS VARCHAR(30)) + ''% used)'' END AS Details FROM #driveInfo AS i;' From 85ff71ad92da6f158ff42f83763944cc837336da Mon Sep 17 00:00:00 2001 From: Anthony Green <40231097+Ant-Green@users.noreply.github.com> Date: Fri, 15 Jul 2022 09:22:32 +0100 Subject: [PATCH 301/662] Updated for new 17CU and GDRs Updated for new 17CU and GDRs --- SqlServerVersions.sql | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/SqlServerVersions.sql b/SqlServerVersions.sql index 95e6a05f8..98bb5d9b3 100644 --- a/SqlServerVersions.sql +++ b/SqlServerVersions.sql @@ -42,6 +42,7 @@ INSERT INTO dbo.SqlServerVersions (MajorVersionNumber, MinorVersionNumber, Branch, [Url], ReleaseDate, MainstreamSupportEndDate, ExtendedSupportEndDate, MajorVersionName, MinorVersionName) VALUES (16, 600, 'CTP2', 'https://support.microsoft.com/en-us/help/5011644', '2022-05-24', '2022-05-24', '2022-05-24', 'SQL Server 2022', 'CTP 2.0'), + (15, 4236, 'CU16 GDR', 'https://support.microsoft.com/en-us/help/5014353', '2022-06-14', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 16 GDR'), (15, 4223, 'CU16', 'https://support.microsoft.com/en-us/help/5011644', '2022-04-18', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 16'), (15, 4198, 'CU15', 'https://support.microsoft.com/en-us/help/5008996', '2022-01-07', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 15'), (15, 4188, 'CU14', 'https://support.microsoft.com/en-us/help/5007182', '2021-11-22', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 14'), @@ -61,6 +62,8 @@ VALUES (15, 4003, 'CU1', 'https://support.microsoft.com/en-us/help/4527376', '2020-01-07', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 1 '), (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 '), + (14, 3451, 'RTM CU30', 'https://support.microsoft.com/en-us/help/5013756', '2022-07-13', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 30'), + (14, 3445, 'RTM CU29 GDR', 'https://support.microsoft.com/en-us/help/5014553', '2022-06-14', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 29 GDR'), (14, 3436, 'RTM CU29', 'https://support.microsoft.com/en-us/help/5010786', '2022-03-31', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 29'), (14, 3430, 'RTM CU28', 'https://support.microsoft.com/en-us/help/5006944', '2022-01-13', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 28'), (14, 3421, 'RTM CU27', 'https://support.microsoft.com/en-us/help/5006944', '2021-10-27', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 27'), @@ -92,6 +95,9 @@ VALUES (14, 3008, 'RTM CU2', 'https://support.microsoft.com/en-us/help/4052574', '2017-11-28', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 2'), (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 '), + (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, 6419, 'SP3 GDR', 'https://support.microsoft.com/en-us/help/5014355', '2022-06-14', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3 GDR'), (13, 6404, 'SP3 GDR', 'https://support.microsoft.com/en-us/help/5006943', '2021-10-27', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3 GDR'), (13, 6300, 'SP3 ', 'https://support.microsoft.com/en-us/help/5003279', '2021-09-15', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3'), (13, 5888, 'SP2 CU17', 'https://support.microsoft.com/en-us/help/5001092', '2021-03-29', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 17'), @@ -142,6 +148,7 @@ VALUES (13, 2164, 'RTM CU2', 'https://support.microsoft.com/en-us/help/3182270 ', '2016-09-22', '2018-01-09', '2018-01-09', 'SQL Server 2016', 'RTM Cumulative Update 2'), (13, 2149, 'RTM CU1', 'https://support.microsoft.com/en-us/help/3164674 ', '2016-07-25', '2018-01-09', '2018-01-09', 'SQL Server 2016', 'RTM Cumulative Update 1'), (13, 1601, 'RTM ', '', '2016-06-01', '2019-01-09', '2019-01-09', 'SQL Server 2016', 'RTM '), + (12, 6439, 'SP3 CU4 GDR', 'https://support.microsoft.com/en-us/help/5014164', '2022-06-14', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 4 GDR'), (12, 6433, 'SP3 CU4 GDR', 'https://support.microsoft.com/en-us/help/4583462', '2021-01-12', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 4 GDR'), (12, 6372, 'SP3 CU4 GDR', 'https://support.microsoft.com/en-us/help/4535288', '2020-02-11', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 4 GDR'), (12, 6329, 'SP3 CU4', 'https://support.microsoft.com/en-us/help/4500181', '2019-07-29', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 4'), From f90b7a52e667bc936e33a002b0b9635342e7a506 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Fri, 15 Jul 2022 01:57:59 -0700 Subject: [PATCH 302/662] #3120 sp_BlitzCache AvgMaxMemoryGrant Was dividing the max by the number of executions, instead of dividing the total. Closes #3120. --- sp_BlitzCache.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sp_BlitzCache.sql b/sp_BlitzCache.sql index 4192b6ef1..6f7ff23fb 100644 --- a/sp_BlitzCache.sql +++ b/sp_BlitzCache.sql @@ -2322,7 +2322,7 @@ BEGIN min_used_grant_kb AS MinUsedGrantKB, max_used_grant_kb AS MaxUsedGrantKB, CAST(ISNULL(NULLIF(( max_used_grant_kb * 1.00 ), 0) / NULLIF(min_grant_kb, 0), 0) * 100. AS MONEY) AS PercentMemoryGrantUsed, - CAST(ISNULL(NULLIF(( max_grant_kb * 1. ), 0) / NULLIF(execution_count, 0), 0) AS MONEY) AS AvgMaxMemoryGrant, '; + CAST(ISNULL(NULLIF(( total_grant_kb * 1. ), 0) / NULLIF(execution_count, 0), 0) AS MONEY) AS AvgMaxMemoryGrant, '; END; ELSE BEGIN From d0486c5d0b7517e2e8b63c8a92bf50c69af391cb Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Mon, 18 Jul 2022 09:52:59 -0700 Subject: [PATCH 303/662] #3119 sp_Blitz ignore recompile For procs in system databases. Closes #3119. --- sp_Blitz.sql | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index 19730a5fa..1fcd93724 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -6819,7 +6819,9 @@ IF @ProductVersionMajor >= 10 URL = 'https://www.brentozar.com/go/recompile', Details = '[' + DBName + '].[' + SPSchema + '].[' + ProcName + '] has WITH RECOMPILE in the stored procedure code, which may cause increased CPU usage due to constant recompiles of the code.', CheckID = '78' - FROM #Recompile AS TR WHERE ProcName NOT LIKE 'sp_AllNightLog%' AND ProcName NOT LIKE 'sp_AskBrent%' AND ProcName NOT LIKE 'sp_Blitz%'; + FROM #Recompile AS TR + WHERE ProcName NOT LIKE 'sp_AllNightLog%' AND ProcName NOT LIKE 'sp_AskBrent%' AND ProcName NOT LIKE 'sp_Blitz%' + AND DBName NOT IN ('master', 'model', 'msdb', 'tempdb'); DROP TABLE #Recompile; END; From f16d6c94321bc5abaf9c4fee30c64352137ada18 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Mon, 18 Jul 2022 10:01:22 -0700 Subject: [PATCH 304/662] #3085 sp_BlitzIndex Output Param Oops! Almost missed the last minute addition from @paulneering. Closes #3085. --- sp_BlitzIndex.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index 69a8a8f63..3a8a71b14 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -1714,7 +1714,7 @@ BEGIN TRY they could see performance slowdowns - see Github #3085. */ DECLARE @MissingIndexPlans BIGINT; SET @StringToExecute = N'SELECT @MissingIndexPlans = COUNT(*) FROM ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_group_stats_query;' - EXEC sp_executesql @StringToExecute, N'@MissingIndexPlans BIGINT OUT', @MissingIndexPlans; + EXEC sp_executesql @StringToExecute, N'@MissingIndexPlans BIGINT OUT', @MissingIndexPlans OUT; IF @MissingIndexPlans > 1000 BEGIN From 7bfdf0618a18faee44ba5ed22485bee6fbbb04ef Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Mon, 18 Jul 2022 10:06:01 -0700 Subject: [PATCH 305/662] #2980 sp_BlitzCache arithmetic overflows Using decimal(30) for total reads and writes instead of bigint. Closes #2980. --- sp_BlitzCache.sql | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sp_BlitzCache.sql b/sp_BlitzCache.sql index 6f7ff23fb..e1636e2f7 100644 --- a/sp_BlitzCache.sql +++ b/sp_BlitzCache.sql @@ -124,9 +124,9 @@ CREATE TABLE ##BlitzCacheProcs ( */ TotalWorkerTimeForType BIGINT, TotalElapsedTimeForType BIGINT, - TotalReadsForType BIGINT, + TotalReadsForType DECIMAL(30), TotalExecutionCountForType BIGINT, - TotalWritesForType BIGINT, + TotalWritesForType DECIMAL(30), NumberOfPlans INT, NumberOfDistinctPlans INT, SerialDesiredMemory FLOAT, @@ -2110,8 +2110,8 @@ SET @body += N') AS qs CROSS JOIN(SELECT SUM(execution_count) AS t_TotalExecs, SUM(CAST(total_elapsed_time AS BIGINT) / 1000.0) AS t_TotalElapsed, SUM(CAST(total_worker_time AS BIGINT) / 1000.0) AS t_TotalWorker, - SUM(CAST(total_logical_reads AS BIGINT)) AS t_TotalReads, - SUM(CAST(total_logical_writes AS BIGINT)) AS t_TotalWrites + SUM(CAST(total_logical_reads AS DECIMAL(30))) AS t_TotalReads, + SUM(CAST(total_logical_writes AS DECIMAL(30))) AS t_TotalWrites FROM sys.#view#) AS t CROSS APPLY sys.dm_exec_plan_attributes(qs.plan_handle) AS pa CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) AS st From e1da9f4746d1fe49631dd149bbaa948412d4574a Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Mon, 18 Jul 2022 10:51:19 -0700 Subject: [PATCH 306/662] #3118 sp_BlitzFirst filestats dbname Add database name to end of file stats output. Closes #3118. --- sp_BlitzFirst.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sp_BlitzFirst.sql b/sp_BlitzFirst.sql index d5be30608..c05adfc1a 100644 --- a/sp_BlitzFirst.sql +++ b/sp_BlitzFirst.sql @@ -4846,11 +4846,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, AND wd1.FileID = wd2.FileID ) SELECT - Pattern, [Sample Time], [Sample (seconds)], [File Name], [Drive], [# Reads/Writes],[MB Read/Written],[Avg Stall (ms)], [file physical name], [StallRank] + Pattern, [Sample Time], [Sample (seconds)], [File Name], [Drive], [# Reads/Writes],[MB Read/Written],[Avg Stall (ms)], [file physical name], [DatabaseName], [StallRank] FROM readstats WHERE StallRank <=20 AND [MB Read/Written] > 0 UNION ALL - SELECT Pattern, [Sample Time], [Sample (seconds)], [File Name], [Drive], [# Reads/Writes],[MB Read/Written],[Avg Stall (ms)], [file physical name], [StallRank] + SELECT Pattern, [Sample Time], [Sample (seconds)], [File Name], [Drive], [# Reads/Writes],[MB Read/Written],[Avg Stall (ms)], [file physical name], [DatabaseName], [StallRank] FROM writestats WHERE StallRank <=20 AND [MB Read/Written] > 0 ORDER BY Pattern, StallRank; From c9ed2e6d78c277aa034043b27631a919ac43304a Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Mon, 18 Jul 2022 14:50:40 -0700 Subject: [PATCH 307/662] 2022-07-18 Release Bumping version numbers & dates, building install scripts. --- Install-All-Scripts.sql | 229 ++++++++++++++++-------- Install-Core-Blitz-No-Query-Store.sql | 201 ++++++++++++++------- Install-Core-Blitz-With-Query-Store.sql | 203 +++++++++++++-------- sp_AllNightLog.sql | 2 +- sp_AllNightLog_Setup.sql | 2 +- sp_Blitz.sql | 2 +- sp_BlitzAnalysis.sql | 2 +- sp_BlitzBackups.sql | 2 +- sp_BlitzCache.sql | 2 +- sp_BlitzFirst.sql | 2 +- sp_BlitzInMemoryOLTP.sql | 2 +- sp_BlitzIndex.sql | 2 +- sp_BlitzLock.sql | 2 +- sp_BlitzQueryStore.sql | 2 +- sp_BlitzWho.sql | 2 +- sp_DatabaseRestore.sql | 2 +- sp_ineachdb.sql | 2 +- 17 files changed, 432 insertions(+), 229 deletions(-) diff --git a/Install-All-Scripts.sql b/Install-All-Scripts.sql index 50728bec3..e14eb3071 100644 --- a/Install-All-Scripts.sql +++ b/Install-All-Scripts.sql @@ -31,7 +31,7 @@ SET STATISTICS XML OFF; BEGIN; -SELECT @Version = '8.09', @VersionDate = '20220408'; +SELECT @Version = '8.10', @VersionDate = '20220718'; IF(@VersionCheckMode = 1) BEGIN @@ -1547,7 +1547,7 @@ SET STATISTICS XML OFF; BEGIN; -SELECT @Version = '8.09', @VersionDate = '20220408'; +SELECT @Version = '8.10', @VersionDate = '20220718'; IF(@VersionCheckMode = 1) BEGIN @@ -2891,7 +2891,7 @@ AS SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.09', @VersionDate = '20220408'; + SELECT @Version = '8.10', @VersionDate = '20220718'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -3621,6 +3621,7 @@ AS INSERT INTO #IgnorableWaits VALUES ('PARALLEL_REDO_TRAN_LIST'); INSERT INTO #IgnorableWaits VALUES ('PARALLEL_REDO_WORKER_SYNC'); INSERT INTO #IgnorableWaits VALUES ('PARALLEL_REDO_WORKER_WAIT_WORK'); + INSERT INTO #IgnorableWaits VALUES ('POPULATE_LOCK_ORDINALS'); INSERT INTO #IgnorableWaits VALUES ('PREEMPTIVE_HADR_LEASE_MECHANISM'); INSERT INTO #IgnorableWaits VALUES ('PREEMPTIVE_SP_SERVER_DIAGNOSTICS'); INSERT INTO #IgnorableWaits VALUES ('QDS_ASYNC_QUEUE'); @@ -6853,11 +6854,11 @@ AS ''Performance'' AS FindingsGroup, ''In-Memory OLTP (Hekaton) In Use'' AS Finding, ''https://www.brentozar.com/go/hekaton'' AS URL, - CAST(CAST((SUM(mem.pages_kb / 1024.0) / CAST(value_in_use AS INT) * 100) AS INT) AS NVARCHAR(100)) + ''% of your '' + CAST(CAST((CAST(value_in_use AS DECIMAL(38,1)) / 1024) AS MONEY) AS NVARCHAR(100)) + ''GB of your max server memory is being used for in-memory OLTP tables (Hekaton).'' AS Details + CAST(CAST((SUM(mem.pages_kb / 1024.0) / CAST(value_in_use AS INT) * 100) AS DECIMAL(6,1)) AS NVARCHAR(100)) + ''% of your '' + CAST(CAST((CAST(value_in_use AS DECIMAL(38,1)) / 1024) AS MONEY) AS NVARCHAR(100)) + ''GB of your max server memory is being used for in-memory OLTP tables (Hekaton).'' AS Details FROM sys.configurations c INNER JOIN sys.dm_os_memory_clerks mem ON mem.type = ''MEMORYCLERK_XTP'' WHERE c.name = ''max server memory (MB)'' GROUP BY c.value_in_use - HAVING SUM(mem.pages_kb / 1024.0) > 10 OPTION (RECOMPILE)'; + HAVING SUM(mem.pages_kb / 1024.0) > 1000 OPTION (RECOMPILE)'; IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; @@ -9671,7 +9672,9 @@ IF @ProductVersionMajor >= 10 URL = 'https://www.brentozar.com/go/recompile', Details = '[' + DBName + '].[' + SPSchema + '].[' + ProcName + '] has WITH RECOMPILE in the stored procedure code, which may cause increased CPU usage due to constant recompiles of the code.', CheckID = '78' - FROM #Recompile AS TR WHERE ProcName NOT LIKE 'sp_AllNightLog%' AND ProcName NOT LIKE 'sp_AskBrent%' AND ProcName NOT LIKE 'sp_Blitz%'; + FROM #Recompile AS TR + WHERE ProcName NOT LIKE 'sp_AllNightLog%' AND ProcName NOT LIKE 'sp_AskBrent%' AND ProcName NOT LIKE 'sp_Blitz%' + AND DBName NOT IN ('master', 'model', 'msdb', 'tempdb'); DROP TABLE #Recompile; END; @@ -11546,7 +11549,7 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 + '' GB free on '' + i.drive + '' drive '' + i.logical_volume_name + '' out of '' + CAST(CAST(i.total_MB/1024 AS NUMERIC(18,2)) AS VARCHAR(30)) - + '' GB total ('' + CAST(i.used_percent AS VARCHAR(30)) + ''%)'' END + + '' GB total ('' + CAST(i.used_percent AS VARCHAR(30)) + ''% used)'' END AS Details FROM #driveInfo AS i;' @@ -12537,7 +12540,7 @@ AS SET NOCOUNT ON; SET STATISTICS XML OFF; -SELECT @Version = '8.09', @VersionDate = '20220408'; +SELECT @Version = '8.10', @VersionDate = '20220718'; IF(@VersionCheckMode = 1) BEGIN @@ -13415,7 +13418,7 @@ AS SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.09', @VersionDate = '20220408'; + SELECT @Version = '8.10', @VersionDate = '20220718'; IF(@VersionCheckMode = 1) BEGIN @@ -15040,9 +15043,9 @@ CREATE TABLE ##BlitzCacheProcs ( */ TotalWorkerTimeForType BIGINT, TotalElapsedTimeForType BIGINT, - TotalReadsForType BIGINT, + TotalReadsForType DECIMAL(30), TotalExecutionCountForType BIGINT, - TotalWritesForType BIGINT, + TotalWritesForType DECIMAL(30), NumberOfPlans INT, NumberOfDistinctPlans INT, SerialDesiredMemory FLOAT, @@ -15196,7 +15199,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.09', @VersionDate = '20220408'; +SELECT @Version = '8.10', @VersionDate = '20220718'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -17026,8 +17029,8 @@ SET @body += N') AS qs CROSS JOIN(SELECT SUM(execution_count) AS t_TotalExecs, SUM(CAST(total_elapsed_time AS BIGINT) / 1000.0) AS t_TotalElapsed, SUM(CAST(total_worker_time AS BIGINT) / 1000.0) AS t_TotalWorker, - SUM(CAST(total_logical_reads AS BIGINT)) AS t_TotalReads, - SUM(CAST(total_logical_writes AS BIGINT)) AS t_TotalWrites + SUM(CAST(total_logical_reads AS DECIMAL(30))) AS t_TotalReads, + SUM(CAST(total_logical_writes AS DECIMAL(30))) AS t_TotalWrites FROM sys.#view#) AS t CROSS APPLY sys.dm_exec_plan_attributes(qs.plan_handle) AS pa CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) AS st @@ -17238,7 +17241,7 @@ BEGIN min_used_grant_kb AS MinUsedGrantKB, max_used_grant_kb AS MaxUsedGrantKB, CAST(ISNULL(NULLIF(( max_used_grant_kb * 1.00 ), 0) / NULLIF(min_grant_kb, 0), 0) * 100. AS MONEY) AS PercentMemoryGrantUsed, - CAST(ISNULL(NULLIF(( max_grant_kb * 1. ), 0) / NULLIF(execution_count, 0), 0) AS MONEY) AS AvgMaxMemoryGrant, '; + CAST(ISNULL(NULLIF(( total_grant_kb * 1. ), 0) / NULLIF(execution_count, 0), 0) AS MONEY) AS AvgMaxMemoryGrant, '; END; ELSE BEGIN @@ -19665,8 +19668,8 @@ SELECT DISTINCT CASE WHEN is_remote_query_expensive = 1 THEN ', Expensive Remote Query' ELSE '' END + CASE WHEN trace_flags_session IS NOT NULL THEN ', Session Level Trace Flag(s) Enabled: ' + trace_flags_session ELSE '' END + CASE WHEN is_unused_grant = 1 THEN ', Unused Memory Grant' ELSE '' END + - CASE WHEN function_count > 0 THEN ', Calls ' + CONVERT(VARCHAR(10), (SELECT SUM(b2.function_count) FROM ##BlitzCacheProcs AS b2 WHERE b2.SqlHandle = b.SqlHandle AND b2.QueryHash IS NOT NULL AND SPID = @@SPID) ) + ' function(s)' ELSE '' END + - CASE WHEN clr_function_count > 0 THEN ', Calls ' + CONVERT(VARCHAR(10), (SELECT SUM(b2.clr_function_count) FROM ##BlitzCacheProcs AS b2 WHERE b2.SqlHandle = b.SqlHandle AND b2.QueryHash IS NOT NULL AND SPID = @@SPID) ) + ' CLR function(s)' ELSE '' END + + CASE WHEN function_count > 0 THEN ', Calls ' + CONVERT(VARCHAR(10), (SELECT SUM(b2.function_count) FROM ##BlitzCacheProcs AS b2 WHERE b2.SqlHandle = b.SqlHandle AND b2.QueryHash IS NOT NULL AND SPID = @@SPID) ) + ' Function(s)' ELSE '' END + + CASE WHEN clr_function_count > 0 THEN ', Calls ' + CONVERT(VARCHAR(10), (SELECT SUM(b2.clr_function_count) FROM ##BlitzCacheProcs AS b2 WHERE b2.SqlHandle = b.SqlHandle AND b2.QueryHash IS NOT NULL AND SPID = @@SPID) ) + ' CLR Function(s)' ELSE '' END + CASE WHEN PlanCreationTimeHours <= 4 THEN ', Plan created last 4hrs' ELSE '' END + CASE WHEN is_table_variable = 1 THEN ', Table Variables' ELSE '' END + CASE WHEN no_stats_warning = 1 THEN ', Columns With No Statistics' ELSE '' END + @@ -19693,7 +19696,7 @@ SELECT DISTINCT CASE WHEN is_spool_more_rows = 1 THEN + ', Large Index Row Spool' ELSE '' END + CASE WHEN is_table_spool_expensive = 1 THEN + ', Expensive Table Spool' ELSE '' END + CASE WHEN is_table_spool_more_rows = 1 THEN + ', Many Rows Table Spool' ELSE '' END + - CASE WHEN is_bad_estimate = 1 THEN + ', Row estimate mismatch' ELSE '' END + + CASE WHEN is_bad_estimate = 1 THEN + ', Row Estimate Mismatch' ELSE '' END + CASE WHEN is_paul_white_electric = 1 THEN ', SWITCH!' ELSE '' END + CASE WHEN is_row_goal = 1 THEN ', Row Goals' ELSE '' END + CASE WHEN is_big_spills = 1 THEN ', >500mb spills' ELSE '' END + @@ -22459,7 +22462,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.09', @VersionDate = '20220408'; +SELECT @Version = '8.10', @VersionDate = '20220718'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -22541,6 +22544,8 @@ DECLARE @ColumnList NVARCHAR(MAX); DECLARE @ColumnListWithApostrophes NVARCHAR(MAX); DECLARE @PartitionCount INT; DECLARE @OptimizeForSequentialKey BIT = 0; +DECLARE @StringToExecute NVARCHAR(MAX); + /* Let's get @SortOrder set to lower case here for comparisons later */ SET @SortOrder = REPLACE(LOWER(@SortOrder), N' ', N'_'); @@ -22667,7 +22672,8 @@ IF OBJECT_ID('tempdb..#Ignore_Databases') IS NOT NULL index_usage_summary NVARCHAR(MAX) NULL, index_size_summary NVARCHAR(MAX) NULL, create_tsql NVARCHAR(MAX) NULL, - more_info NVARCHAR(MAX)NULL + more_info NVARCHAR(MAX) NULL, + sample_query_plan XML NULL ); CREATE TABLE #IndexSanity @@ -24106,7 +24112,7 @@ BEGIN TRY AND ColumnNamesWithDataTypes.IndexColumnType = ''Included'' ) AS included_columns_with_data_type ' - /* Github #2780 BGO Removing SQL Server 2019's new missing index sample plan because it doesn't work yet:*/ + /* Get the sample query plan if it's available, and if there are less than 1,000 rows in the DMV: */ IF NOT EXISTS ( SELECT @@ -24114,31 +24120,42 @@ BEGIN TRY FROM sys.all_objects AS o WHERE o.name = 'dm_db_missing_index_group_stats_query' ) - SELECT - @dsql += N' , NULL AS sample_query_plan ' - /* Github #2780 BGO Removing SQL Server 2019's new missing index sample plan because it doesn't work yet:*/ + SELECT + @dsql += N' , NULL AS sample_query_plan ' ELSE BEGIN - SELECT - @dsql += N' - , sample_query_plan = - ( - SELECT TOP (1) - p.query_plan - FROM sys.dm_db_missing_index_group_stats gs - CROSS APPLY - ( - SELECT TOP (1) - s.plan_handle - FROM sys.dm_db_missing_index_group_stats_query q - INNER JOIN sys.dm_exec_query_stats s - ON q.query_plan_hash = s.query_plan_hash - WHERE gs.group_handle = q.group_handle - ORDER BY (q.user_seeks + q.user_scans) DESC, s.total_logical_reads DESC - ) q2 - CROSS APPLY sys.dm_exec_query_plan(q2.plan_handle) p - WHERE ig.index_group_handle = gs.group_handle - ) ' + /* The DMV is only supposed to have 600 rows in it. If it's got more, + they could see performance slowdowns - see Github #3085. */ + DECLARE @MissingIndexPlans BIGINT; + SET @StringToExecute = N'SELECT @MissingIndexPlans = COUNT(*) FROM ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_group_stats_query;' + EXEC sp_executesql @StringToExecute, N'@MissingIndexPlans BIGINT OUT', @MissingIndexPlans OUT; + + IF @MissingIndexPlans > 1000 + BEGIN + SELECT @dsql += N' , NULL AS sample_query_plan /* Over 1000 plans found, skipping */ '; + RAISERROR (N'Over 1000 plans found in sys.dm_db_missing_index_group_stats_query - your SQL Server is hitting a bug: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/3085',0,1) WITH NOWAIT; + END + ELSE + SELECT + @dsql += N' + , sample_query_plan = + ( + SELECT TOP (1) + p.query_plan + FROM sys.dm_db_missing_index_group_stats gs + CROSS APPLY + ( + SELECT TOP (1) + s.plan_handle + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_group_stats_query q + INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.dm_exec_query_stats s + ON q.query_plan_hash = s.query_plan_hash + WHERE gs.group_handle = q.group_handle + ORDER BY (q.user_seeks + q.user_scans) DESC, s.total_logical_reads DESC + ) q2 + CROSS APPLY sys.dm_exec_query_plan(q2.plan_handle) p + WHERE ig.index_group_handle = gs.group_handle + ) ' END @@ -26007,10 +26024,10 @@ BEGIN; AND i.is_disabled = 0 GROUP BY i.database_id, i.schema_name, i.[object_id]) INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - index_usage_summary, index_size_summary, create_tsql, more_info ) + index_usage_summary, index_size_summary, create_tsql, more_info, sample_query_plan ) SELECT check_id, t.index_sanity_id, t.check_id, t.findings_group, t.finding, t.[Database Name], t.URL, t.details, t.[definition], - index_estimated_impact, t.index_size_summary, create_tsql, more_info + index_estimated_impact, t.index_size_summary, create_tsql, more_info, sample_query_plan FROM ( SELECT ROW_NUMBER() OVER (ORDER BY magic_benefit_number DESC) AS rownum, @@ -26034,7 +26051,8 @@ BEGIN; mi.create_tsql, mi.more_info, magic_benefit_number, - mi.is_low + mi.is_low, + mi.sample_query_plan FROM #MissingIndexes mi LEFT JOIN index_size_cte sz ON mi.[object_id] = sz.object_id AND mi.database_id = sz.database_id @@ -27413,7 +27431,8 @@ BEGIN; br.index_size_summary AS [Size], COALESCE(br.more_info,sn.more_info,'') AS [More Info], br.URL, - COALESCE(br.create_tsql,ts.create_tsql,'') AS [Create TSQL] + COALESCE(br.create_tsql,ts.create_tsql,'') AS [Create TSQL], + br.sample_query_plan AS [Sample Query Plan] FROM #BlitzIndexResults br LEFT JOIN #IndexSanity sn ON br.index_sanity_id=sn.index_sanity_id @@ -27438,7 +27457,8 @@ BEGIN; br.index_size_summary AS [Size], COALESCE(br.more_info,sn.more_info,'') AS [More Info], br.URL, - COALESCE(br.create_tsql,ts.create_tsql,'') AS [Create TSQL] + COALESCE(br.create_tsql,ts.create_tsql,'') AS [Create TSQL], + br.sample_query_plan AS [Sample Query Plan] FROM #BlitzIndexResults br LEFT JOIN #IndexSanity sn ON br.index_sanity_id=sn.index_sanity_id @@ -27530,7 +27550,6 @@ ELSE IF (@Mode=1) /*Summarize*/ DECLARE @LinkedServerDBCheck NVARCHAR(2000); DECLARE @ValidLinkedServerDB INT; DECLARE @tmpdbchk TABLE (cnt INT); - DECLARE @StringToExecute NVARCHAR(MAX); IF @OutputServerName IS NOT NULL BEGIN @@ -28193,7 +28212,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.09', @VersionDate = '20220408'; +SELECT @Version = '8.10', @VersionDate = '20220718'; IF(@VersionCheckMode = 1) @@ -28280,9 +28299,11 @@ END; RETURN; END; /* @Help = 1 */ - DECLARE @ProductVersion NVARCHAR(128); - DECLARE @ProductVersionMajor FLOAT; - DECLARE @ProductVersionMinor INT; + DECLARE @ProductVersion NVARCHAR(128), + @ProductVersionMajor FLOAT, + @ProductVersionMinor INT, + @ObjectFullName NVARCHAR(2000); + SET @ProductVersion = CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)); @@ -28374,13 +28395,30 @@ You need to use an Azure storage account, and the path has to look like this: ht SELECT @OutputDatabaseName = QUOTENAME(@OutputDatabaseName), @OutputTableName = QUOTENAME(@OutputTableName), @OutputSchemaName = QUOTENAME(@OutputSchemaName) - if(@r is null) --if it is null there is no table, create it from above execution + if(@r is not null) --if it is not null, there is a table, so check for newly added columns + BEGIN + /* If the table doesn't have the new spid column, add it. See Github #3101. */ + SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableName; + SET @StringToExecute = N'IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns + WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + ''')) AND name = ''spid'') + ALTER TABLE ' + @ObjectFullName + N' ADD spid SMALLINT NULL;'; + EXEC(@StringToExecute); + + /* If the table doesn't have the new wait_resource column, add it. See Github #3101. */ + SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableName; + SET @StringToExecute = N'IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns + WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + ''')) AND name = ''wait_resource'') + ALTER TABLE ' + @ObjectFullName + N' ADD wait_resource NVARCHAR(MAX) NULL;'; + EXEC(@StringToExecute); + END + ELSE --if(@r is not null) --if it is null there is no table, create it from above execution BEGIN select @StringToExecute = N'use ' + @OutputDatabaseName + ';create table ' + @OutputSchemaName + '.' + @OutputTableName + ' ( ServerName NVARCHAR(256), deadlock_type NVARCHAR(256), event_date datetime, database_name NVARCHAR(256), + spid SMALLINT, deadlock_group NVARCHAR(256), query XML, object_names XML, @@ -28392,6 +28430,7 @@ You need to use an Azure storage account, and the path has to look like this: ht host_name NVARCHAR(256), client_app NVARCHAR(256), wait_time BIGINT, + wait_resource NVARCHAR(max), priority smallint, log_used BIGINT, last_tran_started datetime, @@ -28519,6 +28558,7 @@ You need to use an Azure storage account, and the path has to look like this: ht CONVERT(BIT, q.is_parallel) AS is_parallel, q.deadlock_graph, q.id, + q.spid, q.database_id, q.priority, q.log_used, @@ -28543,6 +28583,7 @@ You need to use an Azure storage account, and the path has to look like this: ht CONVERT(tinyint, dd.is_parallel) + CONVERT(tinyint, dd.is_parallel_batch) AS is_parallel, dd.deadlock_graph, ca.dp.value('@id', 'NVARCHAR(256)') AS id, + ca.dp.value('@spid', 'SMALLINT') AS spid, ca.dp.value('@currentdb', 'BIGINT') AS database_id, ca.dp.value('@priority', 'SMALLINT') AS priority, ca.dp.value('@logused', 'BIGINT') AS log_used, @@ -29445,6 +29486,7 @@ You need to use an Azure storage account, and the path has to look like this: ht dp.event_date, dp.id, dp.victim_id, + dp.spid, dp.database_id, dp.priority, dp.log_used, @@ -29502,6 +29544,7 @@ You need to use an Azure storage account, and the path has to look like this: ht dp.event_date, dp.id, dp.victim_id, + dp.spid, dp.database_id, dp.priority, dp.log_used, @@ -29558,6 +29601,7 @@ You need to use an Azure storage account, and the path has to look like this: ht deadlock_type, event_date, database_name, + spid, deadlock_group, query, object_names, @@ -29569,6 +29613,7 @@ You need to use an Azure storage account, and the path has to look like this: ht host_name, client_app, wait_time, + wait_resource, priority, log_used, last_tran_started, @@ -29593,6 +29638,7 @@ You need to use an Azure storage account, and the path has to look like this: ht d.deadlock_type, d.event_date, DB_NAME(d.database_id) AS database_name, + d.spid, 'Deadlock #' + CONVERT(NVARCHAR(10), d.en) + ', Query #' @@ -29609,6 +29655,7 @@ You need to use an Azure storage account, and the path has to look like this: ht d.host_name, d.client_app, d.wait_time, + d.wait_resource, d.priority, d.log_used, d.last_tran_started, @@ -29668,6 +29715,7 @@ ELSE --Output to database is not set output to client app dp.event_date, dp.id, dp.victim_id, + dp.spid, dp.database_id, dp.priority, dp.log_used, @@ -29725,6 +29773,7 @@ ELSE --Output to database is not set output to client app dp.event_date, dp.id, dp.victim_id, + dp.spid, dp.database_id, dp.priority, dp.log_used, @@ -29780,6 +29829,7 @@ ELSE --Output to database is not set output to client app SELECT d.deadlock_type, d.event_date, DB_NAME(d.database_id) AS database_name, + d.spid, 'Deadlock #' + CONVERT(NVARCHAR(10), d.en) + ', Query #' @@ -29797,6 +29847,7 @@ ELSE --Output to database is not set output to client app d.host_name, d.client_app, d.wait_time, + d.wait_resource, d.priority, d.log_used, d.last_tran_started, @@ -29840,6 +29891,7 @@ ELSE --Output to database is not set output to client app dr.deadlock_type, dr.event_date, dr.database_name, + dr.spid, dr.deadlock_group, ' + CASE @ExportToExcel @@ -29858,6 +29910,7 @@ ELSE --Output to database is not set output to client app dr.host_name, dr.client_app, dr.wait_time, + dr.wait_resource, dr.priority, dr.log_used, dr.last_tran_started, @@ -29999,7 +30052,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.09', @VersionDate = '20220408'; +SELECT @Version = '8.10', @VersionDate = '20220718'; IF(@VersionCheckMode = 1) BEGIN RETURN; @@ -35730,7 +35783,7 @@ BEGIN SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.09', @VersionDate = '20220408'; + SELECT @Version = '8.10', @VersionDate = '20220718'; IF(@VersionCheckMode = 1) BEGIN @@ -37127,7 +37180,7 @@ SET STATISTICS XML OFF; /*Versioning details*/ -SELECT @Version = '8.09', @VersionDate = '20220408'; +SELECT @Version = '8.10', @VersionDate = '20220718'; IF(@VersionCheckMode = 1) BEGIN @@ -37265,7 +37318,7 @@ BEGIN @StopAt = ''20170508201501'', @Debug = 1; - --This example NOT execute the restore. Commands will be printed in a copy/paste ready format only + --This example will NOT execute the restore. Commands will be printed in a copy/paste ready format only EXEC dbo.sp_DatabaseRestore @Database = ''DBA'', @BackupPathFull = ''\\StorageServer\LogShipMe\FULL\'', @@ -37297,6 +37350,20 @@ BEGIN RETURN; END; +BEGIN TRY +DECLARE @CurrentDatabaseContext AS VARCHAR(128) = (SELECT DB_NAME()); +DECLARE @CommandExecuteCheck VARCHAR(315) + +SET @CommandExecuteCheck = 'IF NOT EXISTS (SELECT name FROM ' +@CurrentDatabaseContext+'.sys.objects WHERE type = ''P'' AND name = ''CommandExecute'') +BEGIN + RAISERROR (''DatabaseRestore requires the CommandExecute stored procedure from the OLA Hallengren Maintenance solution, are you using the correct database?'', 15, 1); + RETURN; +END;' +EXEC (@CommandExecuteCheck) +END TRY +BEGIN CATCH +THROW; +END CATCH DECLARE @cmd NVARCHAR(4000) = N'', --Holds xp_cmdshell command @sql NVARCHAR(MAX) = N'', --Holds executable SQL commands @@ -38112,7 +38179,7 @@ BEGIN BackupFile LIKE N'%' + @Database + '%' AND (@StopAt IS NULL OR REPLACE( RIGHT( REPLACE( BackupFile, RIGHT( BackupFile, PATINDEX( '%_[0-9][0-9]%', REVERSE( BackupFile ) ) ), '' ), 16 ), '_', '' ) <= @StopAt) - ORDER BY BackUpFile DESC; + ORDER BY BackupFile DESC; -- Load FileList data into Temp Table sorted by DateTime Stamp desc SELECT BackupPath, BackupFile INTO #SplitDiffBackups @@ -38679,7 +38746,7 @@ BEGIN SET NOCOUNT ON; SET STATISTICS XML OFF; - SELECT @Version = '8.09', @VersionDate = '20220408'; + SELECT @Version = '8.10', @VersionDate = '20220718'; IF(@VersionCheckMode = 1) BEGIN @@ -39025,6 +39092,9 @@ DELETE FROM dbo.SqlServerVersions; INSERT INTO dbo.SqlServerVersions (MajorVersionNumber, MinorVersionNumber, Branch, [Url], ReleaseDate, MainstreamSupportEndDate, ExtendedSupportEndDate, MajorVersionName, MinorVersionName) VALUES + (16, 600, 'CTP2', 'https://support.microsoft.com/en-us/help/5011644', '2022-05-24', '2022-05-24', '2022-05-24', 'SQL Server 2022', 'CTP 2.0'), + (15, 4236, 'CU16 GDR', 'https://support.microsoft.com/en-us/help/5014353', '2022-06-14', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 16 GDR'), + (15, 4223, 'CU16', 'https://support.microsoft.com/en-us/help/5011644', '2022-04-18', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 16'), (15, 4198, 'CU15', 'https://support.microsoft.com/en-us/help/5008996', '2022-01-07', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 15'), (15, 4188, 'CU14', 'https://support.microsoft.com/en-us/help/5007182', '2021-11-22', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 14'), (15, 4178, 'CU13', 'https://support.microsoft.com/en-us/help/5005679', '2021-10-05', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 13'), @@ -39043,6 +39113,8 @@ VALUES (15, 4003, 'CU1', 'https://support.microsoft.com/en-us/help/4527376', '2020-01-07', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 1 '), (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 '), + (14, 3451, 'RTM CU30', 'https://support.microsoft.com/en-us/help/5013756', '2022-07-13', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 30'), + (14, 3445, 'RTM CU29 GDR', 'https://support.microsoft.com/en-us/help/5014553', '2022-06-14', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 29 GDR'), (14, 3436, 'RTM CU29', 'https://support.microsoft.com/en-us/help/5010786', '2022-03-31', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 29'), (14, 3430, 'RTM CU28', 'https://support.microsoft.com/en-us/help/5006944', '2022-01-13', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 28'), (14, 3421, 'RTM CU27', 'https://support.microsoft.com/en-us/help/5006944', '2021-10-27', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 27'), @@ -39074,6 +39146,9 @@ VALUES (14, 3008, 'RTM CU2', 'https://support.microsoft.com/en-us/help/4052574', '2017-11-28', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 2'), (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 '), + (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, 6419, 'SP3 GDR', 'https://support.microsoft.com/en-us/help/5014355', '2022-06-14', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3 GDR'), (13, 6404, 'SP3 GDR', 'https://support.microsoft.com/en-us/help/5006943', '2021-10-27', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3 GDR'), (13, 6300, 'SP3 ', 'https://support.microsoft.com/en-us/help/5003279', '2021-09-15', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3'), (13, 5888, 'SP2 CU17', 'https://support.microsoft.com/en-us/help/5001092', '2021-03-29', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 17'), @@ -39124,6 +39199,7 @@ VALUES (13, 2164, 'RTM CU2', 'https://support.microsoft.com/en-us/help/3182270 ', '2016-09-22', '2018-01-09', '2018-01-09', 'SQL Server 2016', 'RTM Cumulative Update 2'), (13, 2149, 'RTM CU1', 'https://support.microsoft.com/en-us/help/3164674 ', '2016-07-25', '2018-01-09', '2018-01-09', 'SQL Server 2016', 'RTM Cumulative Update 1'), (13, 1601, 'RTM ', '', '2016-06-01', '2019-01-09', '2019-01-09', 'SQL Server 2016', 'RTM '), + (12, 6439, 'SP3 CU4 GDR', 'https://support.microsoft.com/en-us/help/5014164', '2022-06-14', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 4 GDR'), (12, 6433, 'SP3 CU4 GDR', 'https://support.microsoft.com/en-us/help/4583462', '2021-01-12', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 4 GDR'), (12, 6372, 'SP3 CU4 GDR', 'https://support.microsoft.com/en-us/help/4535288', '2020-02-11', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 4 GDR'), (12, 6329, 'SP3 CU4', 'https://support.microsoft.com/en-us/help/4500181', '2019-07-29', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 4'), @@ -39429,7 +39505,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.09', @VersionDate = '20220408'; +SELECT @Version = '8.10', @VersionDate = '20220718'; IF(@VersionCheckMode = 1) BEGIN @@ -39861,17 +39937,6 @@ BEGIN DROP TABLE #FilterPlansByDatabase; CREATE TABLE #FilterPlansByDatabase (DatabaseID INT PRIMARY KEY CLUSTERED); - IF OBJECT_ID('tempdb..##WaitCategories') IS NULL - BEGIN - /* We reuse this one by default rather than recreate it every time. */ - CREATE TABLE ##WaitCategories - ( - WaitType NVARCHAR(60) PRIMARY KEY CLUSTERED, - WaitCategory NVARCHAR(128) NOT NULL, - Ignorable BIT DEFAULT 0 - ); - END; /* IF OBJECT_ID('tempdb..##WaitCategories') IS NULL */ - IF OBJECT_ID ('tempdb..#checkversion') IS NOT NULL DROP TABLE #checkversion; CREATE TABLE #checkversion ( @@ -39883,7 +39948,18 @@ BEGIN revision AS PARSENAME(CONVERT(VARCHAR(32), version), 1) ); - IF 527 <> (SELECT COALESCE(SUM(1),0) FROM ##WaitCategories) + IF OBJECT_ID('tempdb..##WaitCategories') IS NULL + BEGIN + /* We reuse this one by default rather than recreate it every time. */ + CREATE TABLE ##WaitCategories + ( + WaitType NVARCHAR(60) PRIMARY KEY CLUSTERED, + WaitCategory NVARCHAR(128) NOT NULL, + Ignorable BIT DEFAULT 0 + ); + END; /* IF OBJECT_ID('tempdb..##WaitCategories') IS NULL */ + + IF 527 > (SELECT COALESCE(SUM(1),0) FROM ##WaitCategories) BEGIN TRUNCATE TABLE ##WaitCategories; INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('ASYNC_IO_COMPLETION','Other Disk IO',0); @@ -40134,6 +40210,7 @@ BEGIN INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PARALLEL_REDO_WORKER_SYNC','Replication',1); INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PARALLEL_REDO_WORKER_WAIT_WORK','Replication',1); INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('POOL_LOG_RATE_GOVERNOR','Log Rate Governor',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('POPULATE_LOCK_ORDINALS','Idle',1); INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_ABR','Preemptive',0); INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_CLOSEBACKUPMEDIA','Preemptive',0); INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_CLOSEBACKUPTAPE','Preemptive',0); @@ -44228,11 +44305,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, AND wd1.FileID = wd2.FileID ) SELECT - Pattern, [Sample Time], [Sample (seconds)], [File Name], [Drive], [# Reads/Writes],[MB Read/Written],[Avg Stall (ms)], [file physical name], [StallRank] + Pattern, [Sample Time], [Sample (seconds)], [File Name], [Drive], [# Reads/Writes],[MB Read/Written],[Avg Stall (ms)], [file physical name], [DatabaseName], [StallRank] FROM readstats WHERE StallRank <=20 AND [MB Read/Written] > 0 UNION ALL - SELECT Pattern, [Sample Time], [Sample (seconds)], [File Name], [Drive], [# Reads/Writes],[MB Read/Written],[Avg Stall (ms)], [file physical name], [StallRank] + SELECT Pattern, [Sample Time], [Sample (seconds)], [File Name], [Drive], [# Reads/Writes],[MB Read/Written],[Avg Stall (ms)], [file physical name], [DatabaseName], [StallRank] FROM writestats WHERE StallRank <=20 AND [MB Read/Written] > 0 ORDER BY Pattern, StallRank; diff --git a/Install-Core-Blitz-No-Query-Store.sql b/Install-Core-Blitz-No-Query-Store.sql index 75d45085f..7ad3bef75 100644 --- a/Install-Core-Blitz-No-Query-Store.sql +++ b/Install-Core-Blitz-No-Query-Store.sql @@ -38,7 +38,7 @@ AS SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.09', @VersionDate = '20220408'; + SELECT @Version = '8.10', @VersionDate = '20220718'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -768,6 +768,7 @@ AS INSERT INTO #IgnorableWaits VALUES ('PARALLEL_REDO_TRAN_LIST'); INSERT INTO #IgnorableWaits VALUES ('PARALLEL_REDO_WORKER_SYNC'); INSERT INTO #IgnorableWaits VALUES ('PARALLEL_REDO_WORKER_WAIT_WORK'); + INSERT INTO #IgnorableWaits VALUES ('POPULATE_LOCK_ORDINALS'); INSERT INTO #IgnorableWaits VALUES ('PREEMPTIVE_HADR_LEASE_MECHANISM'); INSERT INTO #IgnorableWaits VALUES ('PREEMPTIVE_SP_SERVER_DIAGNOSTICS'); INSERT INTO #IgnorableWaits VALUES ('QDS_ASYNC_QUEUE'); @@ -4000,11 +4001,11 @@ AS ''Performance'' AS FindingsGroup, ''In-Memory OLTP (Hekaton) In Use'' AS Finding, ''https://www.brentozar.com/go/hekaton'' AS URL, - CAST(CAST((SUM(mem.pages_kb / 1024.0) / CAST(value_in_use AS INT) * 100) AS INT) AS NVARCHAR(100)) + ''% of your '' + CAST(CAST((CAST(value_in_use AS DECIMAL(38,1)) / 1024) AS MONEY) AS NVARCHAR(100)) + ''GB of your max server memory is being used for in-memory OLTP tables (Hekaton).'' AS Details + CAST(CAST((SUM(mem.pages_kb / 1024.0) / CAST(value_in_use AS INT) * 100) AS DECIMAL(6,1)) AS NVARCHAR(100)) + ''% of your '' + CAST(CAST((CAST(value_in_use AS DECIMAL(38,1)) / 1024) AS MONEY) AS NVARCHAR(100)) + ''GB of your max server memory is being used for in-memory OLTP tables (Hekaton).'' AS Details FROM sys.configurations c INNER JOIN sys.dm_os_memory_clerks mem ON mem.type = ''MEMORYCLERK_XTP'' WHERE c.name = ''max server memory (MB)'' GROUP BY c.value_in_use - HAVING SUM(mem.pages_kb / 1024.0) > 10 OPTION (RECOMPILE)'; + HAVING SUM(mem.pages_kb / 1024.0) > 1000 OPTION (RECOMPILE)'; IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; @@ -6818,7 +6819,9 @@ IF @ProductVersionMajor >= 10 URL = 'https://www.brentozar.com/go/recompile', Details = '[' + DBName + '].[' + SPSchema + '].[' + ProcName + '] has WITH RECOMPILE in the stored procedure code, which may cause increased CPU usage due to constant recompiles of the code.', CheckID = '78' - FROM #Recompile AS TR WHERE ProcName NOT LIKE 'sp_AllNightLog%' AND ProcName NOT LIKE 'sp_AskBrent%' AND ProcName NOT LIKE 'sp_Blitz%'; + FROM #Recompile AS TR + WHERE ProcName NOT LIKE 'sp_AllNightLog%' AND ProcName NOT LIKE 'sp_AskBrent%' AND ProcName NOT LIKE 'sp_Blitz%' + AND DBName NOT IN ('master', 'model', 'msdb', 'tempdb'); DROP TABLE #Recompile; END; @@ -8693,7 +8696,7 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 + '' GB free on '' + i.drive + '' drive '' + i.logical_volume_name + '' out of '' + CAST(CAST(i.total_MB/1024 AS NUMERIC(18,2)) AS VARCHAR(30)) - + '' GB total ('' + CAST(i.used_percent AS VARCHAR(30)) + ''%)'' END + + '' GB total ('' + CAST(i.used_percent AS VARCHAR(30)) + ''% used)'' END AS Details FROM #driveInfo AS i;' @@ -9684,7 +9687,7 @@ AS SET NOCOUNT ON; SET STATISTICS XML OFF; -SELECT @Version = '8.09', @VersionDate = '20220408'; +SELECT @Version = '8.10', @VersionDate = '20220718'; IF(@VersionCheckMode = 1) BEGIN @@ -10562,7 +10565,7 @@ AS SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.09', @VersionDate = '20220408'; + SELECT @Version = '8.10', @VersionDate = '20220718'; IF(@VersionCheckMode = 1) BEGIN @@ -12187,9 +12190,9 @@ CREATE TABLE ##BlitzCacheProcs ( */ TotalWorkerTimeForType BIGINT, TotalElapsedTimeForType BIGINT, - TotalReadsForType BIGINT, + TotalReadsForType DECIMAL(30), TotalExecutionCountForType BIGINT, - TotalWritesForType BIGINT, + TotalWritesForType DECIMAL(30), NumberOfPlans INT, NumberOfDistinctPlans INT, SerialDesiredMemory FLOAT, @@ -12343,7 +12346,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.09', @VersionDate = '20220408'; +SELECT @Version = '8.10', @VersionDate = '20220718'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -14173,8 +14176,8 @@ SET @body += N') AS qs CROSS JOIN(SELECT SUM(execution_count) AS t_TotalExecs, SUM(CAST(total_elapsed_time AS BIGINT) / 1000.0) AS t_TotalElapsed, SUM(CAST(total_worker_time AS BIGINT) / 1000.0) AS t_TotalWorker, - SUM(CAST(total_logical_reads AS BIGINT)) AS t_TotalReads, - SUM(CAST(total_logical_writes AS BIGINT)) AS t_TotalWrites + SUM(CAST(total_logical_reads AS DECIMAL(30))) AS t_TotalReads, + SUM(CAST(total_logical_writes AS DECIMAL(30))) AS t_TotalWrites FROM sys.#view#) AS t CROSS APPLY sys.dm_exec_plan_attributes(qs.plan_handle) AS pa CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) AS st @@ -14385,7 +14388,7 @@ BEGIN min_used_grant_kb AS MinUsedGrantKB, max_used_grant_kb AS MaxUsedGrantKB, CAST(ISNULL(NULLIF(( max_used_grant_kb * 1.00 ), 0) / NULLIF(min_grant_kb, 0), 0) * 100. AS MONEY) AS PercentMemoryGrantUsed, - CAST(ISNULL(NULLIF(( max_grant_kb * 1. ), 0) / NULLIF(execution_count, 0), 0) AS MONEY) AS AvgMaxMemoryGrant, '; + CAST(ISNULL(NULLIF(( total_grant_kb * 1. ), 0) / NULLIF(execution_count, 0), 0) AS MONEY) AS AvgMaxMemoryGrant, '; END; ELSE BEGIN @@ -16812,8 +16815,8 @@ SELECT DISTINCT CASE WHEN is_remote_query_expensive = 1 THEN ', Expensive Remote Query' ELSE '' END + CASE WHEN trace_flags_session IS NOT NULL THEN ', Session Level Trace Flag(s) Enabled: ' + trace_flags_session ELSE '' END + CASE WHEN is_unused_grant = 1 THEN ', Unused Memory Grant' ELSE '' END + - CASE WHEN function_count > 0 THEN ', Calls ' + CONVERT(VARCHAR(10), (SELECT SUM(b2.function_count) FROM ##BlitzCacheProcs AS b2 WHERE b2.SqlHandle = b.SqlHandle AND b2.QueryHash IS NOT NULL AND SPID = @@SPID) ) + ' function(s)' ELSE '' END + - CASE WHEN clr_function_count > 0 THEN ', Calls ' + CONVERT(VARCHAR(10), (SELECT SUM(b2.clr_function_count) FROM ##BlitzCacheProcs AS b2 WHERE b2.SqlHandle = b.SqlHandle AND b2.QueryHash IS NOT NULL AND SPID = @@SPID) ) + ' CLR function(s)' ELSE '' END + + CASE WHEN function_count > 0 THEN ', Calls ' + CONVERT(VARCHAR(10), (SELECT SUM(b2.function_count) FROM ##BlitzCacheProcs AS b2 WHERE b2.SqlHandle = b.SqlHandle AND b2.QueryHash IS NOT NULL AND SPID = @@SPID) ) + ' Function(s)' ELSE '' END + + CASE WHEN clr_function_count > 0 THEN ', Calls ' + CONVERT(VARCHAR(10), (SELECT SUM(b2.clr_function_count) FROM ##BlitzCacheProcs AS b2 WHERE b2.SqlHandle = b.SqlHandle AND b2.QueryHash IS NOT NULL AND SPID = @@SPID) ) + ' CLR Function(s)' ELSE '' END + CASE WHEN PlanCreationTimeHours <= 4 THEN ', Plan created last 4hrs' ELSE '' END + CASE WHEN is_table_variable = 1 THEN ', Table Variables' ELSE '' END + CASE WHEN no_stats_warning = 1 THEN ', Columns With No Statistics' ELSE '' END + @@ -16840,7 +16843,7 @@ SELECT DISTINCT CASE WHEN is_spool_more_rows = 1 THEN + ', Large Index Row Spool' ELSE '' END + CASE WHEN is_table_spool_expensive = 1 THEN + ', Expensive Table Spool' ELSE '' END + CASE WHEN is_table_spool_more_rows = 1 THEN + ', Many Rows Table Spool' ELSE '' END + - CASE WHEN is_bad_estimate = 1 THEN + ', Row estimate mismatch' ELSE '' END + + CASE WHEN is_bad_estimate = 1 THEN + ', Row Estimate Mismatch' ELSE '' END + CASE WHEN is_paul_white_electric = 1 THEN ', SWITCH!' ELSE '' END + CASE WHEN is_row_goal = 1 THEN ', Row Goals' ELSE '' END + CASE WHEN is_big_spills = 1 THEN ', >500mb spills' ELSE '' END + @@ -19606,7 +19609,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.09', @VersionDate = '20220408'; +SELECT @Version = '8.10', @VersionDate = '20220718'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -19688,6 +19691,8 @@ DECLARE @ColumnList NVARCHAR(MAX); DECLARE @ColumnListWithApostrophes NVARCHAR(MAX); DECLARE @PartitionCount INT; DECLARE @OptimizeForSequentialKey BIT = 0; +DECLARE @StringToExecute NVARCHAR(MAX); + /* Let's get @SortOrder set to lower case here for comparisons later */ SET @SortOrder = REPLACE(LOWER(@SortOrder), N' ', N'_'); @@ -19814,7 +19819,8 @@ IF OBJECT_ID('tempdb..#Ignore_Databases') IS NOT NULL index_usage_summary NVARCHAR(MAX) NULL, index_size_summary NVARCHAR(MAX) NULL, create_tsql NVARCHAR(MAX) NULL, - more_info NVARCHAR(MAX)NULL + more_info NVARCHAR(MAX) NULL, + sample_query_plan XML NULL ); CREATE TABLE #IndexSanity @@ -21253,7 +21259,7 @@ BEGIN TRY AND ColumnNamesWithDataTypes.IndexColumnType = ''Included'' ) AS included_columns_with_data_type ' - /* Github #2780 BGO Removing SQL Server 2019's new missing index sample plan because it doesn't work yet:*/ + /* Get the sample query plan if it's available, and if there are less than 1,000 rows in the DMV: */ IF NOT EXISTS ( SELECT @@ -21261,31 +21267,42 @@ BEGIN TRY FROM sys.all_objects AS o WHERE o.name = 'dm_db_missing_index_group_stats_query' ) - SELECT - @dsql += N' , NULL AS sample_query_plan ' - /* Github #2780 BGO Removing SQL Server 2019's new missing index sample plan because it doesn't work yet:*/ + SELECT + @dsql += N' , NULL AS sample_query_plan ' ELSE BEGIN - SELECT - @dsql += N' - , sample_query_plan = - ( - SELECT TOP (1) - p.query_plan - FROM sys.dm_db_missing_index_group_stats gs - CROSS APPLY - ( - SELECT TOP (1) - s.plan_handle - FROM sys.dm_db_missing_index_group_stats_query q - INNER JOIN sys.dm_exec_query_stats s - ON q.query_plan_hash = s.query_plan_hash - WHERE gs.group_handle = q.group_handle - ORDER BY (q.user_seeks + q.user_scans) DESC, s.total_logical_reads DESC - ) q2 - CROSS APPLY sys.dm_exec_query_plan(q2.plan_handle) p - WHERE ig.index_group_handle = gs.group_handle - ) ' + /* The DMV is only supposed to have 600 rows in it. If it's got more, + they could see performance slowdowns - see Github #3085. */ + DECLARE @MissingIndexPlans BIGINT; + SET @StringToExecute = N'SELECT @MissingIndexPlans = COUNT(*) FROM ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_group_stats_query;' + EXEC sp_executesql @StringToExecute, N'@MissingIndexPlans BIGINT OUT', @MissingIndexPlans OUT; + + IF @MissingIndexPlans > 1000 + BEGIN + SELECT @dsql += N' , NULL AS sample_query_plan /* Over 1000 plans found, skipping */ '; + RAISERROR (N'Over 1000 plans found in sys.dm_db_missing_index_group_stats_query - your SQL Server is hitting a bug: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/3085',0,1) WITH NOWAIT; + END + ELSE + SELECT + @dsql += N' + , sample_query_plan = + ( + SELECT TOP (1) + p.query_plan + FROM sys.dm_db_missing_index_group_stats gs + CROSS APPLY + ( + SELECT TOP (1) + s.plan_handle + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_group_stats_query q + INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.dm_exec_query_stats s + ON q.query_plan_hash = s.query_plan_hash + WHERE gs.group_handle = q.group_handle + ORDER BY (q.user_seeks + q.user_scans) DESC, s.total_logical_reads DESC + ) q2 + CROSS APPLY sys.dm_exec_query_plan(q2.plan_handle) p + WHERE ig.index_group_handle = gs.group_handle + ) ' END @@ -23154,10 +23171,10 @@ BEGIN; AND i.is_disabled = 0 GROUP BY i.database_id, i.schema_name, i.[object_id]) INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - index_usage_summary, index_size_summary, create_tsql, more_info ) + index_usage_summary, index_size_summary, create_tsql, more_info, sample_query_plan ) SELECT check_id, t.index_sanity_id, t.check_id, t.findings_group, t.finding, t.[Database Name], t.URL, t.details, t.[definition], - index_estimated_impact, t.index_size_summary, create_tsql, more_info + index_estimated_impact, t.index_size_summary, create_tsql, more_info, sample_query_plan FROM ( SELECT ROW_NUMBER() OVER (ORDER BY magic_benefit_number DESC) AS rownum, @@ -23181,7 +23198,8 @@ BEGIN; mi.create_tsql, mi.more_info, magic_benefit_number, - mi.is_low + mi.is_low, + mi.sample_query_plan FROM #MissingIndexes mi LEFT JOIN index_size_cte sz ON mi.[object_id] = sz.object_id AND mi.database_id = sz.database_id @@ -24560,7 +24578,8 @@ BEGIN; br.index_size_summary AS [Size], COALESCE(br.more_info,sn.more_info,'') AS [More Info], br.URL, - COALESCE(br.create_tsql,ts.create_tsql,'') AS [Create TSQL] + COALESCE(br.create_tsql,ts.create_tsql,'') AS [Create TSQL], + br.sample_query_plan AS [Sample Query Plan] FROM #BlitzIndexResults br LEFT JOIN #IndexSanity sn ON br.index_sanity_id=sn.index_sanity_id @@ -24585,7 +24604,8 @@ BEGIN; br.index_size_summary AS [Size], COALESCE(br.more_info,sn.more_info,'') AS [More Info], br.URL, - COALESCE(br.create_tsql,ts.create_tsql,'') AS [Create TSQL] + COALESCE(br.create_tsql,ts.create_tsql,'') AS [Create TSQL], + br.sample_query_plan AS [Sample Query Plan] FROM #BlitzIndexResults br LEFT JOIN #IndexSanity sn ON br.index_sanity_id=sn.index_sanity_id @@ -24677,7 +24697,6 @@ ELSE IF (@Mode=1) /*Summarize*/ DECLARE @LinkedServerDBCheck NVARCHAR(2000); DECLARE @ValidLinkedServerDB INT; DECLARE @tmpdbchk TABLE (cnt INT); - DECLARE @StringToExecute NVARCHAR(MAX); IF @OutputServerName IS NOT NULL BEGIN @@ -25340,7 +25359,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.09', @VersionDate = '20220408'; +SELECT @Version = '8.10', @VersionDate = '20220718'; IF(@VersionCheckMode = 1) @@ -25427,9 +25446,11 @@ END; RETURN; END; /* @Help = 1 */ - DECLARE @ProductVersion NVARCHAR(128); - DECLARE @ProductVersionMajor FLOAT; - DECLARE @ProductVersionMinor INT; + DECLARE @ProductVersion NVARCHAR(128), + @ProductVersionMajor FLOAT, + @ProductVersionMinor INT, + @ObjectFullName NVARCHAR(2000); + SET @ProductVersion = CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)); @@ -25521,13 +25542,30 @@ You need to use an Azure storage account, and the path has to look like this: ht SELECT @OutputDatabaseName = QUOTENAME(@OutputDatabaseName), @OutputTableName = QUOTENAME(@OutputTableName), @OutputSchemaName = QUOTENAME(@OutputSchemaName) - if(@r is null) --if it is null there is no table, create it from above execution + if(@r is not null) --if it is not null, there is a table, so check for newly added columns + BEGIN + /* If the table doesn't have the new spid column, add it. See Github #3101. */ + SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableName; + SET @StringToExecute = N'IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns + WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + ''')) AND name = ''spid'') + ALTER TABLE ' + @ObjectFullName + N' ADD spid SMALLINT NULL;'; + EXEC(@StringToExecute); + + /* If the table doesn't have the new wait_resource column, add it. See Github #3101. */ + SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableName; + SET @StringToExecute = N'IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns + WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + ''')) AND name = ''wait_resource'') + ALTER TABLE ' + @ObjectFullName + N' ADD wait_resource NVARCHAR(MAX) NULL;'; + EXEC(@StringToExecute); + END + ELSE --if(@r is not null) --if it is null there is no table, create it from above execution BEGIN select @StringToExecute = N'use ' + @OutputDatabaseName + ';create table ' + @OutputSchemaName + '.' + @OutputTableName + ' ( ServerName NVARCHAR(256), deadlock_type NVARCHAR(256), event_date datetime, database_name NVARCHAR(256), + spid SMALLINT, deadlock_group NVARCHAR(256), query XML, object_names XML, @@ -25539,6 +25577,7 @@ You need to use an Azure storage account, and the path has to look like this: ht host_name NVARCHAR(256), client_app NVARCHAR(256), wait_time BIGINT, + wait_resource NVARCHAR(max), priority smallint, log_used BIGINT, last_tran_started datetime, @@ -25666,6 +25705,7 @@ You need to use an Azure storage account, and the path has to look like this: ht CONVERT(BIT, q.is_parallel) AS is_parallel, q.deadlock_graph, q.id, + q.spid, q.database_id, q.priority, q.log_used, @@ -25690,6 +25730,7 @@ You need to use an Azure storage account, and the path has to look like this: ht CONVERT(tinyint, dd.is_parallel) + CONVERT(tinyint, dd.is_parallel_batch) AS is_parallel, dd.deadlock_graph, ca.dp.value('@id', 'NVARCHAR(256)') AS id, + ca.dp.value('@spid', 'SMALLINT') AS spid, ca.dp.value('@currentdb', 'BIGINT') AS database_id, ca.dp.value('@priority', 'SMALLINT') AS priority, ca.dp.value('@logused', 'BIGINT') AS log_used, @@ -26592,6 +26633,7 @@ You need to use an Azure storage account, and the path has to look like this: ht dp.event_date, dp.id, dp.victim_id, + dp.spid, dp.database_id, dp.priority, dp.log_used, @@ -26649,6 +26691,7 @@ You need to use an Azure storage account, and the path has to look like this: ht dp.event_date, dp.id, dp.victim_id, + dp.spid, dp.database_id, dp.priority, dp.log_used, @@ -26705,6 +26748,7 @@ You need to use an Azure storage account, and the path has to look like this: ht deadlock_type, event_date, database_name, + spid, deadlock_group, query, object_names, @@ -26716,6 +26760,7 @@ You need to use an Azure storage account, and the path has to look like this: ht host_name, client_app, wait_time, + wait_resource, priority, log_used, last_tran_started, @@ -26740,6 +26785,7 @@ You need to use an Azure storage account, and the path has to look like this: ht d.deadlock_type, d.event_date, DB_NAME(d.database_id) AS database_name, + d.spid, 'Deadlock #' + CONVERT(NVARCHAR(10), d.en) + ', Query #' @@ -26756,6 +26802,7 @@ You need to use an Azure storage account, and the path has to look like this: ht d.host_name, d.client_app, d.wait_time, + d.wait_resource, d.priority, d.log_used, d.last_tran_started, @@ -26815,6 +26862,7 @@ ELSE --Output to database is not set output to client app dp.event_date, dp.id, dp.victim_id, + dp.spid, dp.database_id, dp.priority, dp.log_used, @@ -26872,6 +26920,7 @@ ELSE --Output to database is not set output to client app dp.event_date, dp.id, dp.victim_id, + dp.spid, dp.database_id, dp.priority, dp.log_used, @@ -26927,6 +26976,7 @@ ELSE --Output to database is not set output to client app SELECT d.deadlock_type, d.event_date, DB_NAME(d.database_id) AS database_name, + d.spid, 'Deadlock #' + CONVERT(NVARCHAR(10), d.en) + ', Query #' @@ -26944,6 +26994,7 @@ ELSE --Output to database is not set output to client app d.host_name, d.client_app, d.wait_time, + d.wait_resource, d.priority, d.log_used, d.last_tran_started, @@ -26987,6 +27038,7 @@ ELSE --Output to database is not set output to client app dr.deadlock_type, dr.event_date, dr.database_name, + dr.spid, dr.deadlock_group, ' + CASE @ExportToExcel @@ -27005,6 +27057,7 @@ ELSE --Output to database is not set output to client app dr.host_name, dr.client_app, dr.wait_time, + dr.wait_resource, dr.priority, dr.log_used, dr.last_tran_started, @@ -27122,7 +27175,7 @@ BEGIN SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.09', @VersionDate = '20220408'; + SELECT @Version = '8.10', @VersionDate = '20220718'; IF(@VersionCheckMode = 1) BEGIN @@ -28518,6 +28571,9 @@ DELETE FROM dbo.SqlServerVersions; INSERT INTO dbo.SqlServerVersions (MajorVersionNumber, MinorVersionNumber, Branch, [Url], ReleaseDate, MainstreamSupportEndDate, ExtendedSupportEndDate, MajorVersionName, MinorVersionName) VALUES + (16, 600, 'CTP2', 'https://support.microsoft.com/en-us/help/5011644', '2022-05-24', '2022-05-24', '2022-05-24', 'SQL Server 2022', 'CTP 2.0'), + (15, 4236, 'CU16 GDR', 'https://support.microsoft.com/en-us/help/5014353', '2022-06-14', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 16 GDR'), + (15, 4223, 'CU16', 'https://support.microsoft.com/en-us/help/5011644', '2022-04-18', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 16'), (15, 4198, 'CU15', 'https://support.microsoft.com/en-us/help/5008996', '2022-01-07', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 15'), (15, 4188, 'CU14', 'https://support.microsoft.com/en-us/help/5007182', '2021-11-22', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 14'), (15, 4178, 'CU13', 'https://support.microsoft.com/en-us/help/5005679', '2021-10-05', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 13'), @@ -28536,6 +28592,8 @@ VALUES (15, 4003, 'CU1', 'https://support.microsoft.com/en-us/help/4527376', '2020-01-07', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 1 '), (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 '), + (14, 3451, 'RTM CU30', 'https://support.microsoft.com/en-us/help/5013756', '2022-07-13', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 30'), + (14, 3445, 'RTM CU29 GDR', 'https://support.microsoft.com/en-us/help/5014553', '2022-06-14', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 29 GDR'), (14, 3436, 'RTM CU29', 'https://support.microsoft.com/en-us/help/5010786', '2022-03-31', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 29'), (14, 3430, 'RTM CU28', 'https://support.microsoft.com/en-us/help/5006944', '2022-01-13', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 28'), (14, 3421, 'RTM CU27', 'https://support.microsoft.com/en-us/help/5006944', '2021-10-27', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 27'), @@ -28567,6 +28625,9 @@ VALUES (14, 3008, 'RTM CU2', 'https://support.microsoft.com/en-us/help/4052574', '2017-11-28', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 2'), (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 '), + (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, 6419, 'SP3 GDR', 'https://support.microsoft.com/en-us/help/5014355', '2022-06-14', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3 GDR'), (13, 6404, 'SP3 GDR', 'https://support.microsoft.com/en-us/help/5006943', '2021-10-27', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3 GDR'), (13, 6300, 'SP3 ', 'https://support.microsoft.com/en-us/help/5003279', '2021-09-15', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3'), (13, 5888, 'SP2 CU17', 'https://support.microsoft.com/en-us/help/5001092', '2021-03-29', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 17'), @@ -28617,6 +28678,7 @@ VALUES (13, 2164, 'RTM CU2', 'https://support.microsoft.com/en-us/help/3182270 ', '2016-09-22', '2018-01-09', '2018-01-09', 'SQL Server 2016', 'RTM Cumulative Update 2'), (13, 2149, 'RTM CU1', 'https://support.microsoft.com/en-us/help/3164674 ', '2016-07-25', '2018-01-09', '2018-01-09', 'SQL Server 2016', 'RTM Cumulative Update 1'), (13, 1601, 'RTM ', '', '2016-06-01', '2019-01-09', '2019-01-09', 'SQL Server 2016', 'RTM '), + (12, 6439, 'SP3 CU4 GDR', 'https://support.microsoft.com/en-us/help/5014164', '2022-06-14', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 4 GDR'), (12, 6433, 'SP3 CU4 GDR', 'https://support.microsoft.com/en-us/help/4583462', '2021-01-12', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 4 GDR'), (12, 6372, 'SP3 CU4 GDR', 'https://support.microsoft.com/en-us/help/4535288', '2020-02-11', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 4 GDR'), (12, 6329, 'SP3 CU4', 'https://support.microsoft.com/en-us/help/4500181', '2019-07-29', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 4'), @@ -28922,7 +28984,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.09', @VersionDate = '20220408'; +SELECT @Version = '8.10', @VersionDate = '20220718'; IF(@VersionCheckMode = 1) BEGIN @@ -29354,17 +29416,6 @@ BEGIN DROP TABLE #FilterPlansByDatabase; CREATE TABLE #FilterPlansByDatabase (DatabaseID INT PRIMARY KEY CLUSTERED); - IF OBJECT_ID('tempdb..##WaitCategories') IS NULL - BEGIN - /* We reuse this one by default rather than recreate it every time. */ - CREATE TABLE ##WaitCategories - ( - WaitType NVARCHAR(60) PRIMARY KEY CLUSTERED, - WaitCategory NVARCHAR(128) NOT NULL, - Ignorable BIT DEFAULT 0 - ); - END; /* IF OBJECT_ID('tempdb..##WaitCategories') IS NULL */ - IF OBJECT_ID ('tempdb..#checkversion') IS NOT NULL DROP TABLE #checkversion; CREATE TABLE #checkversion ( @@ -29376,7 +29427,18 @@ BEGIN revision AS PARSENAME(CONVERT(VARCHAR(32), version), 1) ); - IF 527 <> (SELECT COALESCE(SUM(1),0) FROM ##WaitCategories) + IF OBJECT_ID('tempdb..##WaitCategories') IS NULL + BEGIN + /* We reuse this one by default rather than recreate it every time. */ + CREATE TABLE ##WaitCategories + ( + WaitType NVARCHAR(60) PRIMARY KEY CLUSTERED, + WaitCategory NVARCHAR(128) NOT NULL, + Ignorable BIT DEFAULT 0 + ); + END; /* IF OBJECT_ID('tempdb..##WaitCategories') IS NULL */ + + IF 527 > (SELECT COALESCE(SUM(1),0) FROM ##WaitCategories) BEGIN TRUNCATE TABLE ##WaitCategories; INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('ASYNC_IO_COMPLETION','Other Disk IO',0); @@ -29627,6 +29689,7 @@ BEGIN INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PARALLEL_REDO_WORKER_SYNC','Replication',1); INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PARALLEL_REDO_WORKER_WAIT_WORK','Replication',1); INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('POOL_LOG_RATE_GOVERNOR','Log Rate Governor',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('POPULATE_LOCK_ORDINALS','Idle',1); INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_ABR','Preemptive',0); INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_CLOSEBACKUPMEDIA','Preemptive',0); INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_CLOSEBACKUPTAPE','Preemptive',0); @@ -33721,11 +33784,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, AND wd1.FileID = wd2.FileID ) SELECT - Pattern, [Sample Time], [Sample (seconds)], [File Name], [Drive], [# Reads/Writes],[MB Read/Written],[Avg Stall (ms)], [file physical name], [StallRank] + Pattern, [Sample Time], [Sample (seconds)], [File Name], [Drive], [# Reads/Writes],[MB Read/Written],[Avg Stall (ms)], [file physical name], [DatabaseName], [StallRank] FROM readstats WHERE StallRank <=20 AND [MB Read/Written] > 0 UNION ALL - SELECT Pattern, [Sample Time], [Sample (seconds)], [File Name], [Drive], [# Reads/Writes],[MB Read/Written],[Avg Stall (ms)], [file physical name], [StallRank] + SELECT Pattern, [Sample Time], [Sample (seconds)], [File Name], [Drive], [# Reads/Writes],[MB Read/Written],[Avg Stall (ms)], [file physical name], [DatabaseName], [StallRank] FROM writestats WHERE StallRank <=20 AND [MB Read/Written] > 0 ORDER BY Pattern, StallRank; diff --git a/Install-Core-Blitz-With-Query-Store.sql b/Install-Core-Blitz-With-Query-Store.sql index f3ac34958..8a9d7dc47 100644 --- a/Install-Core-Blitz-With-Query-Store.sql +++ b/Install-Core-Blitz-With-Query-Store.sql @@ -38,7 +38,7 @@ AS SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.09', @VersionDate = '20220408'; + SELECT @Version = '8.10', @VersionDate = '20220718'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -768,6 +768,7 @@ AS INSERT INTO #IgnorableWaits VALUES ('PARALLEL_REDO_TRAN_LIST'); INSERT INTO #IgnorableWaits VALUES ('PARALLEL_REDO_WORKER_SYNC'); INSERT INTO #IgnorableWaits VALUES ('PARALLEL_REDO_WORKER_WAIT_WORK'); + INSERT INTO #IgnorableWaits VALUES ('POPULATE_LOCK_ORDINALS'); INSERT INTO #IgnorableWaits VALUES ('PREEMPTIVE_HADR_LEASE_MECHANISM'); INSERT INTO #IgnorableWaits VALUES ('PREEMPTIVE_SP_SERVER_DIAGNOSTICS'); INSERT INTO #IgnorableWaits VALUES ('QDS_ASYNC_QUEUE'); @@ -4000,11 +4001,11 @@ AS ''Performance'' AS FindingsGroup, ''In-Memory OLTP (Hekaton) In Use'' AS Finding, ''https://www.brentozar.com/go/hekaton'' AS URL, - CAST(CAST((SUM(mem.pages_kb / 1024.0) / CAST(value_in_use AS INT) * 100) AS INT) AS NVARCHAR(100)) + ''% of your '' + CAST(CAST((CAST(value_in_use AS DECIMAL(38,1)) / 1024) AS MONEY) AS NVARCHAR(100)) + ''GB of your max server memory is being used for in-memory OLTP tables (Hekaton).'' AS Details + CAST(CAST((SUM(mem.pages_kb / 1024.0) / CAST(value_in_use AS INT) * 100) AS DECIMAL(6,1)) AS NVARCHAR(100)) + ''% of your '' + CAST(CAST((CAST(value_in_use AS DECIMAL(38,1)) / 1024) AS MONEY) AS NVARCHAR(100)) + ''GB of your max server memory is being used for in-memory OLTP tables (Hekaton).'' AS Details FROM sys.configurations c INNER JOIN sys.dm_os_memory_clerks mem ON mem.type = ''MEMORYCLERK_XTP'' WHERE c.name = ''max server memory (MB)'' GROUP BY c.value_in_use - HAVING SUM(mem.pages_kb / 1024.0) > 10 OPTION (RECOMPILE)'; + HAVING SUM(mem.pages_kb / 1024.0) > 1000 OPTION (RECOMPILE)'; IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; @@ -6818,7 +6819,9 @@ IF @ProductVersionMajor >= 10 URL = 'https://www.brentozar.com/go/recompile', Details = '[' + DBName + '].[' + SPSchema + '].[' + ProcName + '] has WITH RECOMPILE in the stored procedure code, which may cause increased CPU usage due to constant recompiles of the code.', CheckID = '78' - FROM #Recompile AS TR WHERE ProcName NOT LIKE 'sp_AllNightLog%' AND ProcName NOT LIKE 'sp_AskBrent%' AND ProcName NOT LIKE 'sp_Blitz%'; + FROM #Recompile AS TR + WHERE ProcName NOT LIKE 'sp_AllNightLog%' AND ProcName NOT LIKE 'sp_AskBrent%' AND ProcName NOT LIKE 'sp_Blitz%' + AND DBName NOT IN ('master', 'model', 'msdb', 'tempdb'); DROP TABLE #Recompile; END; @@ -8693,7 +8696,7 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 + '' GB free on '' + i.drive + '' drive '' + i.logical_volume_name + '' out of '' + CAST(CAST(i.total_MB/1024 AS NUMERIC(18,2)) AS VARCHAR(30)) - + '' GB total ('' + CAST(i.used_percent AS VARCHAR(30)) + ''%)'' END + + '' GB total ('' + CAST(i.used_percent AS VARCHAR(30)) + ''% used)'' END AS Details FROM #driveInfo AS i;' @@ -9684,7 +9687,7 @@ AS SET NOCOUNT ON; SET STATISTICS XML OFF; -SELECT @Version = '8.09', @VersionDate = '20220408'; +SELECT @Version = '8.10', @VersionDate = '20220718'; IF(@VersionCheckMode = 1) BEGIN @@ -10562,7 +10565,7 @@ AS SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.09', @VersionDate = '20220408'; + SELECT @Version = '8.10', @VersionDate = '20220718'; IF(@VersionCheckMode = 1) BEGIN @@ -12187,9 +12190,9 @@ CREATE TABLE ##BlitzCacheProcs ( */ TotalWorkerTimeForType BIGINT, TotalElapsedTimeForType BIGINT, - TotalReadsForType BIGINT, + TotalReadsForType DECIMAL(30), TotalExecutionCountForType BIGINT, - TotalWritesForType BIGINT, + TotalWritesForType DECIMAL(30), NumberOfPlans INT, NumberOfDistinctPlans INT, SerialDesiredMemory FLOAT, @@ -12343,7 +12346,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.09', @VersionDate = '20220408'; +SELECT @Version = '8.10', @VersionDate = '20220718'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -14173,8 +14176,8 @@ SET @body += N') AS qs CROSS JOIN(SELECT SUM(execution_count) AS t_TotalExecs, SUM(CAST(total_elapsed_time AS BIGINT) / 1000.0) AS t_TotalElapsed, SUM(CAST(total_worker_time AS BIGINT) / 1000.0) AS t_TotalWorker, - SUM(CAST(total_logical_reads AS BIGINT)) AS t_TotalReads, - SUM(CAST(total_logical_writes AS BIGINT)) AS t_TotalWrites + SUM(CAST(total_logical_reads AS DECIMAL(30))) AS t_TotalReads, + SUM(CAST(total_logical_writes AS DECIMAL(30))) AS t_TotalWrites FROM sys.#view#) AS t CROSS APPLY sys.dm_exec_plan_attributes(qs.plan_handle) AS pa CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) AS st @@ -14385,7 +14388,7 @@ BEGIN min_used_grant_kb AS MinUsedGrantKB, max_used_grant_kb AS MaxUsedGrantKB, CAST(ISNULL(NULLIF(( max_used_grant_kb * 1.00 ), 0) / NULLIF(min_grant_kb, 0), 0) * 100. AS MONEY) AS PercentMemoryGrantUsed, - CAST(ISNULL(NULLIF(( max_grant_kb * 1. ), 0) / NULLIF(execution_count, 0), 0) AS MONEY) AS AvgMaxMemoryGrant, '; + CAST(ISNULL(NULLIF(( total_grant_kb * 1. ), 0) / NULLIF(execution_count, 0), 0) AS MONEY) AS AvgMaxMemoryGrant, '; END; ELSE BEGIN @@ -16812,8 +16815,8 @@ SELECT DISTINCT CASE WHEN is_remote_query_expensive = 1 THEN ', Expensive Remote Query' ELSE '' END + CASE WHEN trace_flags_session IS NOT NULL THEN ', Session Level Trace Flag(s) Enabled: ' + trace_flags_session ELSE '' END + CASE WHEN is_unused_grant = 1 THEN ', Unused Memory Grant' ELSE '' END + - CASE WHEN function_count > 0 THEN ', Calls ' + CONVERT(VARCHAR(10), (SELECT SUM(b2.function_count) FROM ##BlitzCacheProcs AS b2 WHERE b2.SqlHandle = b.SqlHandle AND b2.QueryHash IS NOT NULL AND SPID = @@SPID) ) + ' function(s)' ELSE '' END + - CASE WHEN clr_function_count > 0 THEN ', Calls ' + CONVERT(VARCHAR(10), (SELECT SUM(b2.clr_function_count) FROM ##BlitzCacheProcs AS b2 WHERE b2.SqlHandle = b.SqlHandle AND b2.QueryHash IS NOT NULL AND SPID = @@SPID) ) + ' CLR function(s)' ELSE '' END + + CASE WHEN function_count > 0 THEN ', Calls ' + CONVERT(VARCHAR(10), (SELECT SUM(b2.function_count) FROM ##BlitzCacheProcs AS b2 WHERE b2.SqlHandle = b.SqlHandle AND b2.QueryHash IS NOT NULL AND SPID = @@SPID) ) + ' Function(s)' ELSE '' END + + CASE WHEN clr_function_count > 0 THEN ', Calls ' + CONVERT(VARCHAR(10), (SELECT SUM(b2.clr_function_count) FROM ##BlitzCacheProcs AS b2 WHERE b2.SqlHandle = b.SqlHandle AND b2.QueryHash IS NOT NULL AND SPID = @@SPID) ) + ' CLR Function(s)' ELSE '' END + CASE WHEN PlanCreationTimeHours <= 4 THEN ', Plan created last 4hrs' ELSE '' END + CASE WHEN is_table_variable = 1 THEN ', Table Variables' ELSE '' END + CASE WHEN no_stats_warning = 1 THEN ', Columns With No Statistics' ELSE '' END + @@ -16840,7 +16843,7 @@ SELECT DISTINCT CASE WHEN is_spool_more_rows = 1 THEN + ', Large Index Row Spool' ELSE '' END + CASE WHEN is_table_spool_expensive = 1 THEN + ', Expensive Table Spool' ELSE '' END + CASE WHEN is_table_spool_more_rows = 1 THEN + ', Many Rows Table Spool' ELSE '' END + - CASE WHEN is_bad_estimate = 1 THEN + ', Row estimate mismatch' ELSE '' END + + CASE WHEN is_bad_estimate = 1 THEN + ', Row Estimate Mismatch' ELSE '' END + CASE WHEN is_paul_white_electric = 1 THEN ', SWITCH!' ELSE '' END + CASE WHEN is_row_goal = 1 THEN ', Row Goals' ELSE '' END + CASE WHEN is_big_spills = 1 THEN ', >500mb spills' ELSE '' END + @@ -19606,7 +19609,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.09', @VersionDate = '20220408'; +SELECT @Version = '8.10', @VersionDate = '20220718'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -19688,6 +19691,8 @@ DECLARE @ColumnList NVARCHAR(MAX); DECLARE @ColumnListWithApostrophes NVARCHAR(MAX); DECLARE @PartitionCount INT; DECLARE @OptimizeForSequentialKey BIT = 0; +DECLARE @StringToExecute NVARCHAR(MAX); + /* Let's get @SortOrder set to lower case here for comparisons later */ SET @SortOrder = REPLACE(LOWER(@SortOrder), N' ', N'_'); @@ -19814,7 +19819,8 @@ IF OBJECT_ID('tempdb..#Ignore_Databases') IS NOT NULL index_usage_summary NVARCHAR(MAX) NULL, index_size_summary NVARCHAR(MAX) NULL, create_tsql NVARCHAR(MAX) NULL, - more_info NVARCHAR(MAX)NULL + more_info NVARCHAR(MAX) NULL, + sample_query_plan XML NULL ); CREATE TABLE #IndexSanity @@ -21253,7 +21259,7 @@ BEGIN TRY AND ColumnNamesWithDataTypes.IndexColumnType = ''Included'' ) AS included_columns_with_data_type ' - /* Github #2780 BGO Removing SQL Server 2019's new missing index sample plan because it doesn't work yet:*/ + /* Get the sample query plan if it's available, and if there are less than 1,000 rows in the DMV: */ IF NOT EXISTS ( SELECT @@ -21261,31 +21267,42 @@ BEGIN TRY FROM sys.all_objects AS o WHERE o.name = 'dm_db_missing_index_group_stats_query' ) - SELECT - @dsql += N' , NULL AS sample_query_plan ' - /* Github #2780 BGO Removing SQL Server 2019's new missing index sample plan because it doesn't work yet:*/ + SELECT + @dsql += N' , NULL AS sample_query_plan ' ELSE BEGIN - SELECT - @dsql += N' - , sample_query_plan = - ( - SELECT TOP (1) - p.query_plan - FROM sys.dm_db_missing_index_group_stats gs - CROSS APPLY - ( - SELECT TOP (1) - s.plan_handle - FROM sys.dm_db_missing_index_group_stats_query q - INNER JOIN sys.dm_exec_query_stats s - ON q.query_plan_hash = s.query_plan_hash - WHERE gs.group_handle = q.group_handle - ORDER BY (q.user_seeks + q.user_scans) DESC, s.total_logical_reads DESC - ) q2 - CROSS APPLY sys.dm_exec_query_plan(q2.plan_handle) p - WHERE ig.index_group_handle = gs.group_handle - ) ' + /* The DMV is only supposed to have 600 rows in it. If it's got more, + they could see performance slowdowns - see Github #3085. */ + DECLARE @MissingIndexPlans BIGINT; + SET @StringToExecute = N'SELECT @MissingIndexPlans = COUNT(*) FROM ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_group_stats_query;' + EXEC sp_executesql @StringToExecute, N'@MissingIndexPlans BIGINT OUT', @MissingIndexPlans OUT; + + IF @MissingIndexPlans > 1000 + BEGIN + SELECT @dsql += N' , NULL AS sample_query_plan /* Over 1000 plans found, skipping */ '; + RAISERROR (N'Over 1000 plans found in sys.dm_db_missing_index_group_stats_query - your SQL Server is hitting a bug: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/3085',0,1) WITH NOWAIT; + END + ELSE + SELECT + @dsql += N' + , sample_query_plan = + ( + SELECT TOP (1) + p.query_plan + FROM sys.dm_db_missing_index_group_stats gs + CROSS APPLY + ( + SELECT TOP (1) + s.plan_handle + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_group_stats_query q + INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.dm_exec_query_stats s + ON q.query_plan_hash = s.query_plan_hash + WHERE gs.group_handle = q.group_handle + ORDER BY (q.user_seeks + q.user_scans) DESC, s.total_logical_reads DESC + ) q2 + CROSS APPLY sys.dm_exec_query_plan(q2.plan_handle) p + WHERE ig.index_group_handle = gs.group_handle + ) ' END @@ -23154,10 +23171,10 @@ BEGIN; AND i.is_disabled = 0 GROUP BY i.database_id, i.schema_name, i.[object_id]) INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - index_usage_summary, index_size_summary, create_tsql, more_info ) + index_usage_summary, index_size_summary, create_tsql, more_info, sample_query_plan ) SELECT check_id, t.index_sanity_id, t.check_id, t.findings_group, t.finding, t.[Database Name], t.URL, t.details, t.[definition], - index_estimated_impact, t.index_size_summary, create_tsql, more_info + index_estimated_impact, t.index_size_summary, create_tsql, more_info, sample_query_plan FROM ( SELECT ROW_NUMBER() OVER (ORDER BY magic_benefit_number DESC) AS rownum, @@ -23181,7 +23198,8 @@ BEGIN; mi.create_tsql, mi.more_info, magic_benefit_number, - mi.is_low + mi.is_low, + mi.sample_query_plan FROM #MissingIndexes mi LEFT JOIN index_size_cte sz ON mi.[object_id] = sz.object_id AND mi.database_id = sz.database_id @@ -24560,7 +24578,8 @@ BEGIN; br.index_size_summary AS [Size], COALESCE(br.more_info,sn.more_info,'') AS [More Info], br.URL, - COALESCE(br.create_tsql,ts.create_tsql,'') AS [Create TSQL] + COALESCE(br.create_tsql,ts.create_tsql,'') AS [Create TSQL], + br.sample_query_plan AS [Sample Query Plan] FROM #BlitzIndexResults br LEFT JOIN #IndexSanity sn ON br.index_sanity_id=sn.index_sanity_id @@ -24585,7 +24604,8 @@ BEGIN; br.index_size_summary AS [Size], COALESCE(br.more_info,sn.more_info,'') AS [More Info], br.URL, - COALESCE(br.create_tsql,ts.create_tsql,'') AS [Create TSQL] + COALESCE(br.create_tsql,ts.create_tsql,'') AS [Create TSQL], + br.sample_query_plan AS [Sample Query Plan] FROM #BlitzIndexResults br LEFT JOIN #IndexSanity sn ON br.index_sanity_id=sn.index_sanity_id @@ -24677,7 +24697,6 @@ ELSE IF (@Mode=1) /*Summarize*/ DECLARE @LinkedServerDBCheck NVARCHAR(2000); DECLARE @ValidLinkedServerDB INT; DECLARE @tmpdbchk TABLE (cnt INT); - DECLARE @StringToExecute NVARCHAR(MAX); IF @OutputServerName IS NOT NULL BEGIN @@ -25340,7 +25359,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.09', @VersionDate = '20220408'; +SELECT @Version = '8.10', @VersionDate = '20220718'; IF(@VersionCheckMode = 1) @@ -25427,9 +25446,11 @@ END; RETURN; END; /* @Help = 1 */ - DECLARE @ProductVersion NVARCHAR(128); - DECLARE @ProductVersionMajor FLOAT; - DECLARE @ProductVersionMinor INT; + DECLARE @ProductVersion NVARCHAR(128), + @ProductVersionMajor FLOAT, + @ProductVersionMinor INT, + @ObjectFullName NVARCHAR(2000); + SET @ProductVersion = CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)); @@ -25521,13 +25542,30 @@ You need to use an Azure storage account, and the path has to look like this: ht SELECT @OutputDatabaseName = QUOTENAME(@OutputDatabaseName), @OutputTableName = QUOTENAME(@OutputTableName), @OutputSchemaName = QUOTENAME(@OutputSchemaName) - if(@r is null) --if it is null there is no table, create it from above execution + if(@r is not null) --if it is not null, there is a table, so check for newly added columns + BEGIN + /* If the table doesn't have the new spid column, add it. See Github #3101. */ + SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableName; + SET @StringToExecute = N'IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns + WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + ''')) AND name = ''spid'') + ALTER TABLE ' + @ObjectFullName + N' ADD spid SMALLINT NULL;'; + EXEC(@StringToExecute); + + /* If the table doesn't have the new wait_resource column, add it. See Github #3101. */ + SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableName; + SET @StringToExecute = N'IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns + WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + ''')) AND name = ''wait_resource'') + ALTER TABLE ' + @ObjectFullName + N' ADD wait_resource NVARCHAR(MAX) NULL;'; + EXEC(@StringToExecute); + END + ELSE --if(@r is not null) --if it is null there is no table, create it from above execution BEGIN select @StringToExecute = N'use ' + @OutputDatabaseName + ';create table ' + @OutputSchemaName + '.' + @OutputTableName + ' ( ServerName NVARCHAR(256), deadlock_type NVARCHAR(256), event_date datetime, database_name NVARCHAR(256), + spid SMALLINT, deadlock_group NVARCHAR(256), query XML, object_names XML, @@ -25539,6 +25577,7 @@ You need to use an Azure storage account, and the path has to look like this: ht host_name NVARCHAR(256), client_app NVARCHAR(256), wait_time BIGINT, + wait_resource NVARCHAR(max), priority smallint, log_used BIGINT, last_tran_started datetime, @@ -25666,6 +25705,7 @@ You need to use an Azure storage account, and the path has to look like this: ht CONVERT(BIT, q.is_parallel) AS is_parallel, q.deadlock_graph, q.id, + q.spid, q.database_id, q.priority, q.log_used, @@ -25690,6 +25730,7 @@ You need to use an Azure storage account, and the path has to look like this: ht CONVERT(tinyint, dd.is_parallel) + CONVERT(tinyint, dd.is_parallel_batch) AS is_parallel, dd.deadlock_graph, ca.dp.value('@id', 'NVARCHAR(256)') AS id, + ca.dp.value('@spid', 'SMALLINT') AS spid, ca.dp.value('@currentdb', 'BIGINT') AS database_id, ca.dp.value('@priority', 'SMALLINT') AS priority, ca.dp.value('@logused', 'BIGINT') AS log_used, @@ -26592,6 +26633,7 @@ You need to use an Azure storage account, and the path has to look like this: ht dp.event_date, dp.id, dp.victim_id, + dp.spid, dp.database_id, dp.priority, dp.log_used, @@ -26649,6 +26691,7 @@ You need to use an Azure storage account, and the path has to look like this: ht dp.event_date, dp.id, dp.victim_id, + dp.spid, dp.database_id, dp.priority, dp.log_used, @@ -26705,6 +26748,7 @@ You need to use an Azure storage account, and the path has to look like this: ht deadlock_type, event_date, database_name, + spid, deadlock_group, query, object_names, @@ -26716,6 +26760,7 @@ You need to use an Azure storage account, and the path has to look like this: ht host_name, client_app, wait_time, + wait_resource, priority, log_used, last_tran_started, @@ -26740,6 +26785,7 @@ You need to use an Azure storage account, and the path has to look like this: ht d.deadlock_type, d.event_date, DB_NAME(d.database_id) AS database_name, + d.spid, 'Deadlock #' + CONVERT(NVARCHAR(10), d.en) + ', Query #' @@ -26756,6 +26802,7 @@ You need to use an Azure storage account, and the path has to look like this: ht d.host_name, d.client_app, d.wait_time, + d.wait_resource, d.priority, d.log_used, d.last_tran_started, @@ -26815,6 +26862,7 @@ ELSE --Output to database is not set output to client app dp.event_date, dp.id, dp.victim_id, + dp.spid, dp.database_id, dp.priority, dp.log_used, @@ -26872,6 +26920,7 @@ ELSE --Output to database is not set output to client app dp.event_date, dp.id, dp.victim_id, + dp.spid, dp.database_id, dp.priority, dp.log_used, @@ -26927,6 +26976,7 @@ ELSE --Output to database is not set output to client app SELECT d.deadlock_type, d.event_date, DB_NAME(d.database_id) AS database_name, + d.spid, 'Deadlock #' + CONVERT(NVARCHAR(10), d.en) + ', Query #' @@ -26944,6 +26994,7 @@ ELSE --Output to database is not set output to client app d.host_name, d.client_app, d.wait_time, + d.wait_resource, d.priority, d.log_used, d.last_tran_started, @@ -26987,6 +27038,7 @@ ELSE --Output to database is not set output to client app dr.deadlock_type, dr.event_date, dr.database_name, + dr.spid, dr.deadlock_group, ' + CASE @ExportToExcel @@ -27005,6 +27057,7 @@ ELSE --Output to database is not set output to client app dr.host_name, dr.client_app, dr.wait_time, + dr.wait_resource, dr.priority, dr.log_used, dr.last_tran_started, @@ -27146,7 +27199,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.09', @VersionDate = '20220408'; +SELECT @Version = '8.10', @VersionDate = '20220718'; IF(@VersionCheckMode = 1) BEGIN RETURN; @@ -32877,7 +32930,7 @@ BEGIN SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.09', @VersionDate = '20220408'; + SELECT @Version = '8.10', @VersionDate = '20220718'; IF(@VersionCheckMode = 1) BEGIN @@ -34273,6 +34326,9 @@ DELETE FROM dbo.SqlServerVersions; INSERT INTO dbo.SqlServerVersions (MajorVersionNumber, MinorVersionNumber, Branch, [Url], ReleaseDate, MainstreamSupportEndDate, ExtendedSupportEndDate, MajorVersionName, MinorVersionName) VALUES + (16, 600, 'CTP2', 'https://support.microsoft.com/en-us/help/5011644', '2022-05-24', '2022-05-24', '2022-05-24', 'SQL Server 2022', 'CTP 2.0'), + (15, 4236, 'CU16 GDR', 'https://support.microsoft.com/en-us/help/5014353', '2022-06-14', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 16 GDR'), + (15, 4223, 'CU16', 'https://support.microsoft.com/en-us/help/5011644', '2022-04-18', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 16'), (15, 4198, 'CU15', 'https://support.microsoft.com/en-us/help/5008996', '2022-01-07', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 15'), (15, 4188, 'CU14', 'https://support.microsoft.com/en-us/help/5007182', '2021-11-22', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 14'), (15, 4178, 'CU13', 'https://support.microsoft.com/en-us/help/5005679', '2021-10-05', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 13'), @@ -34291,6 +34347,8 @@ VALUES (15, 4003, 'CU1', 'https://support.microsoft.com/en-us/help/4527376', '2020-01-07', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 1 '), (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 '), + (14, 3451, 'RTM CU30', 'https://support.microsoft.com/en-us/help/5013756', '2022-07-13', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 30'), + (14, 3445, 'RTM CU29 GDR', 'https://support.microsoft.com/en-us/help/5014553', '2022-06-14', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 29 GDR'), (14, 3436, 'RTM CU29', 'https://support.microsoft.com/en-us/help/5010786', '2022-03-31', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 29'), (14, 3430, 'RTM CU28', 'https://support.microsoft.com/en-us/help/5006944', '2022-01-13', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 28'), (14, 3421, 'RTM CU27', 'https://support.microsoft.com/en-us/help/5006944', '2021-10-27', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 27'), @@ -34322,6 +34380,9 @@ VALUES (14, 3008, 'RTM CU2', 'https://support.microsoft.com/en-us/help/4052574', '2017-11-28', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 2'), (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 '), + (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, 6419, 'SP3 GDR', 'https://support.microsoft.com/en-us/help/5014355', '2022-06-14', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3 GDR'), (13, 6404, 'SP3 GDR', 'https://support.microsoft.com/en-us/help/5006943', '2021-10-27', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3 GDR'), (13, 6300, 'SP3 ', 'https://support.microsoft.com/en-us/help/5003279', '2021-09-15', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3'), (13, 5888, 'SP2 CU17', 'https://support.microsoft.com/en-us/help/5001092', '2021-03-29', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 17'), @@ -34372,6 +34433,7 @@ VALUES (13, 2164, 'RTM CU2', 'https://support.microsoft.com/en-us/help/3182270 ', '2016-09-22', '2018-01-09', '2018-01-09', 'SQL Server 2016', 'RTM Cumulative Update 2'), (13, 2149, 'RTM CU1', 'https://support.microsoft.com/en-us/help/3164674 ', '2016-07-25', '2018-01-09', '2018-01-09', 'SQL Server 2016', 'RTM Cumulative Update 1'), (13, 1601, 'RTM ', '', '2016-06-01', '2019-01-09', '2019-01-09', 'SQL Server 2016', 'RTM '), + (12, 6439, 'SP3 CU4 GDR', 'https://support.microsoft.com/en-us/help/5014164', '2022-06-14', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 4 GDR'), (12, 6433, 'SP3 CU4 GDR', 'https://support.microsoft.com/en-us/help/4583462', '2021-01-12', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 4 GDR'), (12, 6372, 'SP3 CU4 GDR', 'https://support.microsoft.com/en-us/help/4535288', '2020-02-11', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 4 GDR'), (12, 6329, 'SP3 CU4', 'https://support.microsoft.com/en-us/help/4500181', '2019-07-29', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 4'), @@ -34677,7 +34739,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.09', @VersionDate = '20220408'; +SELECT @Version = '8.10', @VersionDate = '20220718'; IF(@VersionCheckMode = 1) BEGIN @@ -35109,17 +35171,6 @@ BEGIN DROP TABLE #FilterPlansByDatabase; CREATE TABLE #FilterPlansByDatabase (DatabaseID INT PRIMARY KEY CLUSTERED); - IF OBJECT_ID('tempdb..##WaitCategories') IS NULL - BEGIN - /* We reuse this one by default rather than recreate it every time. */ - CREATE TABLE ##WaitCategories - ( - WaitType NVARCHAR(60) PRIMARY KEY CLUSTERED, - WaitCategory NVARCHAR(128) NOT NULL, - Ignorable BIT DEFAULT 0 - ); - END; /* IF OBJECT_ID('tempdb..##WaitCategories') IS NULL */ - IF OBJECT_ID ('tempdb..#checkversion') IS NOT NULL DROP TABLE #checkversion; CREATE TABLE #checkversion ( @@ -35131,7 +35182,18 @@ BEGIN revision AS PARSENAME(CONVERT(VARCHAR(32), version), 1) ); - IF 527 <> (SELECT COALESCE(SUM(1),0) FROM ##WaitCategories) + IF OBJECT_ID('tempdb..##WaitCategories') IS NULL + BEGIN + /* We reuse this one by default rather than recreate it every time. */ + CREATE TABLE ##WaitCategories + ( + WaitType NVARCHAR(60) PRIMARY KEY CLUSTERED, + WaitCategory NVARCHAR(128) NOT NULL, + Ignorable BIT DEFAULT 0 + ); + END; /* IF OBJECT_ID('tempdb..##WaitCategories') IS NULL */ + + IF 527 > (SELECT COALESCE(SUM(1),0) FROM ##WaitCategories) BEGIN TRUNCATE TABLE ##WaitCategories; INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('ASYNC_IO_COMPLETION','Other Disk IO',0); @@ -35382,6 +35444,7 @@ BEGIN INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PARALLEL_REDO_WORKER_SYNC','Replication',1); INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PARALLEL_REDO_WORKER_WAIT_WORK','Replication',1); INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('POOL_LOG_RATE_GOVERNOR','Log Rate Governor',0); + INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('POPULATE_LOCK_ORDINALS','Idle',1); INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_ABR','Preemptive',0); INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_CLOSEBACKUPMEDIA','Preemptive',0); INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_CLOSEBACKUPTAPE','Preemptive',0); @@ -39476,11 +39539,11 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, AND wd1.FileID = wd2.FileID ) SELECT - Pattern, [Sample Time], [Sample (seconds)], [File Name], [Drive], [# Reads/Writes],[MB Read/Written],[Avg Stall (ms)], [file physical name], [StallRank] + Pattern, [Sample Time], [Sample (seconds)], [File Name], [Drive], [# Reads/Writes],[MB Read/Written],[Avg Stall (ms)], [file physical name], [DatabaseName], [StallRank] FROM readstats WHERE StallRank <=20 AND [MB Read/Written] > 0 UNION ALL - SELECT Pattern, [Sample Time], [Sample (seconds)], [File Name], [Drive], [# Reads/Writes],[MB Read/Written],[Avg Stall (ms)], [file physical name], [StallRank] + SELECT Pattern, [Sample Time], [Sample (seconds)], [File Name], [Drive], [# Reads/Writes],[MB Read/Written],[Avg Stall (ms)], [file physical name], [DatabaseName], [StallRank] FROM writestats WHERE StallRank <=20 AND [MB Read/Written] > 0 ORDER BY Pattern, StallRank; diff --git a/sp_AllNightLog.sql b/sp_AllNightLog.sql index 8ec538a22..c8f14a2bc 100644 --- a/sp_AllNightLog.sql +++ b/sp_AllNightLog.sql @@ -31,7 +31,7 @@ SET STATISTICS XML OFF; BEGIN; -SELECT @Version = '8.09', @VersionDate = '20220408'; +SELECT @Version = '8.10', @VersionDate = '20220718'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_AllNightLog_Setup.sql b/sp_AllNightLog_Setup.sql index 424a9b1d0..cedbdde3e 100644 --- a/sp_AllNightLog_Setup.sql +++ b/sp_AllNightLog_Setup.sql @@ -38,7 +38,7 @@ SET STATISTICS XML OFF; BEGIN; -SELECT @Version = '8.09', @VersionDate = '20220408'; +SELECT @Version = '8.10', @VersionDate = '20220718'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_Blitz.sql b/sp_Blitz.sql index 1fcd93724..03305cf49 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -38,7 +38,7 @@ AS SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.09', @VersionDate = '20220408'; + SELECT @Version = '8.10', @VersionDate = '20220718'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) diff --git a/sp_BlitzAnalysis.sql b/sp_BlitzAnalysis.sql index bac004cf1..08e4769ae 100644 --- a/sp_BlitzAnalysis.sql +++ b/sp_BlitzAnalysis.sql @@ -37,7 +37,7 @@ AS SET NOCOUNT ON; SET STATISTICS XML OFF; -SELECT @Version = '8.09', @VersionDate = '20220408'; +SELECT @Version = '8.10', @VersionDate = '20220718'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_BlitzBackups.sql b/sp_BlitzBackups.sql index e3367133a..7dc1674a6 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.09', @VersionDate = '20220408'; + SELECT @Version = '8.10', @VersionDate = '20220718'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_BlitzCache.sql b/sp_BlitzCache.sql index e1636e2f7..9f591ef6a 100644 --- a/sp_BlitzCache.sql +++ b/sp_BlitzCache.sql @@ -280,7 +280,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.09', @VersionDate = '20220408'; +SELECT @Version = '8.10', @VersionDate = '20220718'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) diff --git a/sp_BlitzFirst.sql b/sp_BlitzFirst.sql index c05adfc1a..84f6284f1 100644 --- a/sp_BlitzFirst.sql +++ b/sp_BlitzFirst.sql @@ -46,7 +46,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.09', @VersionDate = '20220408'; +SELECT @Version = '8.10', @VersionDate = '20220718'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_BlitzInMemoryOLTP.sql b/sp_BlitzInMemoryOLTP.sql index 57e44dd8d..28f8d3c5b 100644 --- a/sp_BlitzInMemoryOLTP.sql +++ b/sp_BlitzInMemoryOLTP.sql @@ -82,7 +82,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI */ AS DECLARE @ScriptVersion VARCHAR(30); -SELECT @ScriptVersion = '1.8', @VersionDate = '20220408'; +SELECT @ScriptVersion = '1.8', @VersionDate = '202204718'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index 3a8a71b14..faf9d2b1e 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -48,7 +48,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.09', @VersionDate = '20220408'; +SELECT @Version = '8.10', @VersionDate = '20220718'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index 8f2acba5d..ba236c508 100644 --- a/sp_BlitzLock.sql +++ b/sp_BlitzLock.sql @@ -33,7 +33,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.09', @VersionDate = '20220408'; +SELECT @Version = '8.10', @VersionDate = '20220718'; IF(@VersionCheckMode = 1) diff --git a/sp_BlitzQueryStore.sql b/sp_BlitzQueryStore.sql index e5f393349..82b43c418 100644 --- a/sp_BlitzQueryStore.sql +++ b/sp_BlitzQueryStore.sql @@ -57,7 +57,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.09', @VersionDate = '20220408'; +SELECT @Version = '8.10', @VersionDate = '20220718'; IF(@VersionCheckMode = 1) BEGIN RETURN; diff --git a/sp_BlitzWho.sql b/sp_BlitzWho.sql index ab0a6e22c..6021cdb34 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.09', @VersionDate = '20220408'; + SELECT @Version = '8.10', @VersionDate = '20220718'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_DatabaseRestore.sql b/sp_DatabaseRestore.sql index 2c2db162f..262304090 100755 --- a/sp_DatabaseRestore.sql +++ b/sp_DatabaseRestore.sql @@ -42,7 +42,7 @@ SET STATISTICS XML OFF; /*Versioning details*/ -SELECT @Version = '8.09', @VersionDate = '20220408'; +SELECT @Version = '8.10', @VersionDate = '20220718'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_ineachdb.sql b/sp_ineachdb.sql index 0339d149d..a162bb682 100644 --- a/sp_ineachdb.sql +++ b/sp_ineachdb.sql @@ -35,7 +35,7 @@ BEGIN SET NOCOUNT ON; SET STATISTICS XML OFF; - SELECT @Version = '8.09', @VersionDate = '20220408'; + SELECT @Version = '8.10', @VersionDate = '20220718'; IF(@VersionCheckMode = 1) BEGIN From 6adce6c2d37ca3f1423797c881bd8bf2926a7d58 Mon Sep 17 00:00:00 2001 From: dmonlineuk <93737102+dmonlineuk@users.noreply.github.com> Date: Fri, 22 Jul 2022 17:52:10 +0100 Subject: [PATCH 308/662] Update sp_BlitzFirst.sql Updated check 44 to continue preventing the use of sp_MSforeachdb for Azure SQL DBs, but enable it for Azure SQL Managed Instances. --- sp_BlitzFirst.sql | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sp_BlitzFirst.sql b/sp_BlitzFirst.sql index 84f6284f1..6715eddfc 100644 --- a/sp_BlitzFirst.sql +++ b/sp_BlitzFirst.sql @@ -2466,12 +2466,12 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, /* We don't want to hang around to obtain locks */ SET LOCK_TIMEOUT 0; - IF SERVERPROPERTY('Edition') <> 'SQL Azure' + IF SERVERPROPERTY('EngineEdition') <> 5 SET @StringToExecute = N'USE [?];' + @LineFeed; ELSE SET @StringToExecute = N''; - SET @StringToExecute = @StringToExecute + 'USE [?]; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; SET LOCK_TIMEOUT 1000;' + @LineFeed + + SET @StringToExecute = @StringToExecute + 'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; SET LOCK_TIMEOUT 1000;' + @LineFeed + 'BEGIN TRY' + @LineFeed + ' INSERT INTO #UpdatedStats(HowToStopIt, RowsForSorting)' + @LineFeed + ' SELECT HowToStopIt = ' + @LineFeed + @@ -2515,7 +2515,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 'END CATCH' ; - IF SERVERPROPERTY('Edition') <> 'SQL Azure' + IF SERVERPROPERTY('EngineEdition') <> 5 EXEC sp_MSforeachdb @StringToExecute; ELSE EXEC(@StringToExecute); From b616ace43401b11d0b381761726ad5d3ec7deff0 Mon Sep 17 00:00:00 2001 From: dmonlineuk <93737102+dmonlineuk@users.noreply.github.com> Date: Fri, 22 Jul 2022 18:05:52 +0100 Subject: [PATCH 309/662] Update sp_BlitzFirst.sql Replaced all IF statements with comparisons to SERVERPROPERTY('Edition') and the value of 'SQL Azure' for IF statements with comparisons to SERVERPROPERTY('Edition') and the value of 5. Works on my SQL MI and SQL DBs as expected. --- sp_BlitzFirst.sql | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/sp_BlitzFirst.sql b/sp_BlitzFirst.sql index 6715eddfc..3054818e2 100644 --- a/sp_BlitzFirst.sql +++ b/sp_BlitzFirst.sql @@ -275,7 +275,7 @@ BEGIN END; /* IF @SinceStartup = 0 AND @Seconds > 0 AND @ExpertMode = 1 AND @OutputType <> 'NONE' - What's running right now? This is the first and last result set. */ /* Set start/finish times AFTER sp_BlitzWho runs. For more info: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2244 */ - IF @Seconds = 0 AND SERVERPROPERTY('Edition') = 'SQL Azure' + IF @Seconds = 0 AND SERVERPROPERTY('EngineEdition') = 5 /*SERVERPROPERTY('Edition') = 'SQL Azure'*/ WITH WaitTimes AS ( SELECT wait_type, wait_time_ms, NTILE(3) OVER(ORDER BY wait_time_ms) AS grouper @@ -286,7 +286,7 @@ BEGIN SELECT @StartSampleTime = DATEADD(mi, AVG(-wait_time_ms / 1000 / 60), SYSDATETIMEOFFSET()), @FinishSampleTime = SYSDATETIMEOFFSET() FROM WaitTimes WHERE grouper = 2; - ELSE IF @Seconds = 0 AND SERVERPROPERTY('Edition') <> 'SQL Azure' + ELSE IF @Seconds = 0 AND SERVERPROPERTY('EngineEdition') <> 5 /*SERVERPROPERTY('Edition') <> 'SQL Azure'*/ SELECT @StartSampleTime = DATEADD(MINUTE,DATEDIFF(MINUTE, GETDATE(), GETUTCDATE()),create_date) , @FinishSampleTime = SYSDATETIMEOFFSET() FROM sys.databases WHERE database_id = 2; @@ -1039,7 +1039,7 @@ BEGIN DROP TABLE #MasterFiles; CREATE TABLE #MasterFiles (database_id INT, file_id INT, type_desc NVARCHAR(50), name NVARCHAR(255), physical_name NVARCHAR(255), size BIGINT); /* Azure SQL Database doesn't have sys.master_files, so we have to build our own. */ - IF ((SERVERPROPERTY('Edition')) = 'SQL Azure' + IF (SERVERPROPERTY('EngineEdition') = 5 /*(SERVERPROPERTY('Edition')) = 'SQL Azure' */ AND (OBJECT_ID('sys.master_files') IS NULL)) SET @StringToExecute = 'INSERT INTO #MasterFiles (database_id, file_id, type_desc, name, physical_name, size) SELECT DB_ID(), file_id, type_desc, name, physical_name, size FROM sys.database_files;'; ELSE @@ -1131,7 +1131,7 @@ BEGIN @StockDetailsFooter = @StockDetailsFooter + @LineFeed + ' -- ?>'; /* Get the instance name to use as a Perfmon counter prefix. */ - IF CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) = 'SQL Azure' + IF SERVERPROPERTY('EngineEdition') = 5 /*CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) = 'SQL Azure'*/ SELECT TOP 1 @ServiceName = LEFT(object_name, (CHARINDEX(':', object_name) - 1)) FROM sys.dm_os_performance_counters; ELSE @@ -1400,7 +1400,7 @@ BEGIN CASE @Seconds WHEN 0 THEN 0 ELSE SUM(os.signal_wait_time_ms) OVER (PARTITION BY os.wait_type ) END AS sum_signal_wait_time_ms, CASE @Seconds WHEN 0 THEN 0 ELSE SUM(os.waiting_tasks_count) OVER (PARTITION BY os.wait_type) END AS sum_waiting_tasks '; - IF SERVERPROPERTY('Edition') = 'SQL Azure' + IF SERVERPROPERTY('EngineEdition') = 5 /*SERVERPROPERTY('Edition') = 'SQL Azure'*/ SET @StringToExecute = @StringToExecute + N' FROM sys.dm_db_wait_stats os '; ELSE SET @StringToExecute = @StringToExecute + N' FROM sys.dm_os_wait_stats os '; @@ -1551,7 +1551,7 @@ BEGIN END /* If there's a backup running, add details explaining how long full backup has been taking in the last month. */ - IF @Seconds > 0 AND CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) <> 'SQL Azure' + IF @Seconds > 0 AND SERVERPROPERTY('EngineEdition') <> 5 /*CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) <> 'SQL Azure'*/ BEGIN SET @StringToExecute = 'UPDATE #BlitzFirstResults SET Details = Details + '' Over the last 60 days, the full backup usually takes '' + CAST((SELECT AVG(DATEDIFF(mi, bs.backup_start_date, bs.backup_finish_date)) FROM msdb.dbo.backupset bs WHERE abr.DatabaseName = bs.database_name AND bs.type = ''D'' AND bs.backup_start_date > DATEADD(dd, -60, SYSDATETIMEOFFSET()) AND bs.backup_finish_date IS NOT NULL) AS NVARCHAR(100)) + '' minutes.'' FROM #BlitzFirstResults abr WHERE abr.CheckID = 1 AND EXISTS (SELECT * FROM msdb.dbo.backupset bs WHERE bs.type = ''D'' AND bs.backup_start_date > DATEADD(dd, -60, SYSDATETIMEOFFSET()) AND bs.backup_finish_date IS NOT NULL AND abr.DatabaseName = bs.database_name AND DATEDIFF(mi, bs.backup_start_date, bs.backup_finish_date) > 1)'; EXEC(@StringToExecute); @@ -1679,7 +1679,7 @@ BEGIN END /* Query Problems - Long-Running Query Blocking Others - CheckID 5 */ - IF SERVERPROPERTY('Edition') <> 'SQL Azure' AND @Seconds > 0 AND EXISTS(SELECT * FROM sys.dm_os_waiting_tasks WHERE wait_type LIKE 'LCK%' AND wait_duration_ms > 30000) + IF SERVERPROPERTY('EngineEdition') <> 5 /*SERVERPROPERTY('Edition') <> 'SQL Azure'*/ AND @Seconds > 0 AND EXISTS(SELECT * FROM sys.dm_os_waiting_tasks WHERE wait_type LIKE 'LCK%' AND wait_duration_ms > 30000) BEGIN IF (@Debug = 1) BEGIN @@ -1939,7 +1939,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, AND CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) NOT LIKE '%Standard%'; /* Server Performance - Target Memory Lower Than Max - CheckID 35 */ - IF SERVERPROPERTY('Edition') <> 'SQL Azure' + IF SERVERPROPERTY('EngineEdition') <> 5 /*SERVERPROPERTY('Edition') <> 'SQL Azure'*/ BEGIN IF (@Debug = 1) BEGIN @@ -2387,7 +2387,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, RAISERROR('Running CheckID 23',10,1) WITH NOWAIT; END - IF SERVERPROPERTY('Edition') <> 'SQL Azure' + IF SERVERPROPERTY('EngineEdition') <> 5 /*SERVERPROPERTY('Edition') <> 'SQL Azure'*/ WITH y AS ( @@ -2466,7 +2466,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, /* We don't want to hang around to obtain locks */ SET LOCK_TIMEOUT 0; - IF SERVERPROPERTY('EngineEdition') <> 5 + IF SERVERPROPERTY('EngineEdition') <> 5 /*SERVERPROPERTY('Edition') <> 'SQL Azure'*/ SET @StringToExecute = N'USE [?];' + @LineFeed; ELSE SET @StringToExecute = N''; @@ -2515,7 +2515,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 'END CATCH' ; - IF SERVERPROPERTY('EngineEdition') <> 5 + IF SERVERPROPERTY('EngineEdition') <> 5 /*SERVERPROPERTY('Edition') <> 'SQL Azure'*/ EXEC sp_MSforeachdb @StringToExecute; ELSE EXEC(@StringToExecute); @@ -2595,7 +2595,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, SUM(os.signal_wait_time_ms) OVER (PARTITION BY os.wait_type ) AS sum_signal_wait_time_ms, SUM(os.waiting_tasks_count) OVER (PARTITION BY os.wait_type) AS sum_waiting_tasks '; - IF SERVERPROPERTY('Edition') = 'SQL Azure' + IF SERVERPROPERTY('EngineEdition') = 5 /*SERVERPROPERTY('Edition') = 'SQL Azure'*/ SET @StringToExecute = @StringToExecute + N' FROM sys.dm_db_wait_stats os '; ELSE SET @StringToExecute = @StringToExecute + N' FROM sys.dm_os_wait_stats os '; @@ -3222,7 +3222,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, AND ps.value_delta > (10 * @Seconds); /* Ignore servers sitting idle */ /* Azure Performance - Database is Maxed Out - CheckID 41 */ - IF SERVERPROPERTY('Edition') = 'SQL Azure' + IF SERVERPROPERTY('EngineEdition') = 5 /*SERVERPROPERTY('Edition') = 'SQL Azure'*/ BEGIN IF (@Debug = 1) BEGIN From dacb459c3424b3ccd853a529e5838e8a74d40f01 Mon Sep 17 00:00:00 2001 From: Bjorn Nordblom Date: Mon, 25 Jul 2022 10:28:44 +0200 Subject: [PATCH 310/662] Fixes a collection error when server collation is not set to Latin1_General_CI_AI. --- sp_AllNightLog.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sp_AllNightLog.sql b/sp_AllNightLog.sql index c8f14a2bc..a60ea3001 100644 --- a/sp_AllNightLog.sql +++ b/sp_AllNightLog.sql @@ -578,7 +578,7 @@ DiskPollster: SELECT fl.BackupFile FROM @FileList AS fl WHERE fl.BackupFile IS NOT NULL - AND fl.BackupFile NOT IN (SELECT name from sys.databases where database_id < 5) + AND fl.BackupFile COLLATE DATABASE_DEFAULT NOT IN (SELECT name from sys.databases where database_id < 5) AND NOT EXISTS ( SELECT 1 From 3032a58493c0fbe330f1d911deccac9cccba4286 Mon Sep 17 00:00:00 2001 From: Anthony Green <40231097+Ant-Green@users.noreply.github.com> Date: Fri, 12 Aug 2022 08:42:55 +0100 Subject: [PATCH 311/662] Added 2019CU17 Added 2019CU17 --- SqlServerVersions.sql | 1 + 1 file changed, 1 insertion(+) diff --git a/SqlServerVersions.sql b/SqlServerVersions.sql index 98bb5d9b3..a83db6e22 100644 --- a/SqlServerVersions.sql +++ b/SqlServerVersions.sql @@ -42,6 +42,7 @@ INSERT INTO dbo.SqlServerVersions (MajorVersionNumber, MinorVersionNumber, Branch, [Url], ReleaseDate, MainstreamSupportEndDate, ExtendedSupportEndDate, MajorVersionName, MinorVersionName) VALUES (16, 600, 'CTP2', 'https://support.microsoft.com/en-us/help/5011644', '2022-05-24', '2022-05-24', '2022-05-24', 'SQL Server 2022', 'CTP 2.0'), + (15, 4249, 'CU17', 'https://support.microsoft.com/en-us/help/5016394', '2022-08-11', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 17'), (15, 4236, 'CU16 GDR', 'https://support.microsoft.com/en-us/help/5014353', '2022-06-14', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 16 GDR'), (15, 4223, 'CU16', 'https://support.microsoft.com/en-us/help/5011644', '2022-04-18', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 16'), (15, 4198, 'CU15', 'https://support.microsoft.com/en-us/help/5008996', '2022-01-07', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 15'), From 844567fc7c75dfb4f0fcc7bc589a25923b98b90a Mon Sep 17 00:00:00 2001 From: ChrisMayIVCE <99744523+chrismayivce@users.noreply.github.com> Date: Thu, 25 Aug 2022 18:18:33 +0100 Subject: [PATCH 312/662] Allow @DatabaseName to be used to Azure MI --- sp_BlitzQueryStore.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sp_BlitzQueryStore.sql b/sp_BlitzQueryStore.sql index 82b43c418..7b8f77c2b 100644 --- a/sp_BlitzQueryStore.sql +++ b/sp_BlitzQueryStore.sql @@ -205,12 +205,12 @@ IF ( SELECT COUNT(*) /*Making sure your databases are using QDS.*/ RAISERROR('Checking database validity', 0, 1) WITH NOWAIT; -IF (@is_azure_db = 1) +IF (@is_azure_db = 1 AND SERVERPROPERTY ('ENGINEEDITION') <> 8) SET @DatabaseName = DB_NAME(); ELSE BEGIN - /*If we're on Azure we don't need to check all this @DatabaseName stuff...*/ + /*If we're on Azure SQL DB we don't need to check all this @DatabaseName stuff...*/ SET @DatabaseName = LTRIM(RTRIM(@DatabaseName)); From 608ca6c8c708a7a59cb6aca4d08f0e98e779b169 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Tue, 13 Sep 2022 09:41:14 +0300 Subject: [PATCH 313/662] #3138 sp_BlitzFirst Azure USE Remove extra USE statement. Closes #3138. --- sp_BlitzFirst.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sp_BlitzFirst.sql b/sp_BlitzFirst.sql index 84f6284f1..97a1326bc 100644 --- a/sp_BlitzFirst.sql +++ b/sp_BlitzFirst.sql @@ -2471,7 +2471,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, ELSE SET @StringToExecute = N''; - SET @StringToExecute = @StringToExecute + 'USE [?]; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; SET LOCK_TIMEOUT 1000;' + @LineFeed + + SET @StringToExecute = @StringToExecute + 'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; SET LOCK_TIMEOUT 1000;' + @LineFeed + 'BEGIN TRY' + @LineFeed + ' INSERT INTO #UpdatedStats(HowToStopIt, RowsForSorting)' + @LineFeed + ' SELECT HowToStopIt = ' + @LineFeed + From 244348b15b32c4c0e36ebec5ba0c0f70f96e914e Mon Sep 17 00:00:00 2001 From: Bo Anderson Date: Tue, 13 Sep 2022 12:00:41 -0400 Subject: [PATCH 314/662] Remove RDS ServerName Check (sp_Blitz) Removing the ServerName Check so the script will work on RDS Instances with renamed ServerName --- sp_Blitz.sql | 2 -- 1 file changed, 2 deletions(-) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index 03305cf49..95d1136d5 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -425,7 +425,6 @@ AS /* If the server is Amazon RDS, skip checks that it doesn't allow */ IF LEFT(CAST(SERVERPROPERTY('ComputerNamePhysicalNetBIOS') AS VARCHAR(8000)), 8) = 'EC2AMAZ-' AND LEFT(CAST(SERVERPROPERTY('MachineName') AS VARCHAR(8000)), 8) = 'EC2AMAZ-' - AND LEFT(CAST(SERVERPROPERTY('ServerName') AS VARCHAR(8000)), 8) = 'EC2AMAZ-' AND db_id('rdsadmin') IS NOT NULL AND EXISTS(SELECT * FROM master.sys.all_objects WHERE name IN ('rds_startup_tasks', 'rds_help_revlogin', 'rds_hexadecimal', 'rds_failover_tracking', 'rds_database_tracking', 'rds_track_change')) BEGIN @@ -8345,7 +8344,6 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 -- If this is Amazon RDS, use rdsadmin.dbo.rds_read_error_log IF LEFT(CAST(SERVERPROPERTY('ComputerNamePhysicalNetBIOS') AS VARCHAR(8000)), 8) = 'EC2AMAZ-' AND LEFT(CAST(SERVERPROPERTY('MachineName') AS VARCHAR(8000)), 8) = 'EC2AMAZ-' - AND LEFT(CAST(SERVERPROPERTY('ServerName') AS VARCHAR(8000)), 8) = 'EC2AMAZ-' AND db_id('rdsadmin') IS NOT NULL AND EXISTS(SELECT * FROM master.sys.all_objects WHERE name IN ('rds_startup_tasks', 'rds_help_revlogin', 'rds_hexadecimal', 'rds_failover_tracking', 'rds_database_tracking', 'rds_track_change')) BEGIN From 6e0b3a5fcf85e9fe334d0bd5d0a9ba6cb2be1687 Mon Sep 17 00:00:00 2001 From: Bo Anderson Date: Tue, 13 Sep 2022 12:00:56 -0400 Subject: [PATCH 315/662] Remove ServerName Check for RDS (sp_BlitzLock) Removing the ServerName Check so the script will work on RDS Instances with renamed ServerName --- sp_BlitzLock.sql | 2 -- 1 file changed, 2 deletions(-) diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index ba236c508..c13d713c2 100644 --- a/sp_BlitzLock.sql +++ b/sp_BlitzLock.sql @@ -320,7 +320,6 @@ You need to use an Azure storage account, and the path has to look like this: ht /* WITH ROWCOUNT doesn't work on Amazon RDS - see: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2037 */ IF LEFT(CAST(SERVERPROPERTY('ComputerNamePhysicalNetBIOS') AS VARCHAR(8000)), 8) <> 'EC2AMAZ-' AND LEFT(CAST(SERVERPROPERTY('MachineName') AS VARCHAR(8000)), 8) <> 'EC2AMAZ-' - AND LEFT(CAST(SERVERPROPERTY('ServerName') AS VARCHAR(8000)), 8) <> 'EC2AMAZ-' AND db_id('rdsadmin') IS NULL BEGIN; BEGIN TRY; @@ -768,7 +767,6 @@ You need to use an Azure storage account, and the path has to look like this: ht IF SERVERPROPERTY('EngineEdition') NOT IN (5, 6) /* Azure SQL DB doesn't support querying jobs */ AND NOT (LEFT(CAST(SERVERPROPERTY('ComputerNamePhysicalNetBIOS') AS VARCHAR(8000)), 8) = 'EC2AMAZ-' /* Neither does Amazon RDS Express Edition */ AND LEFT(CAST(SERVERPROPERTY('MachineName') AS VARCHAR(8000)), 8) = 'EC2AMAZ-' - AND LEFT(CAST(SERVERPROPERTY('ServerName') AS VARCHAR(8000)), 8) = 'EC2AMAZ-' AND db_id('rdsadmin') IS NOT NULL AND EXISTS(SELECT * FROM master.sys.all_objects WHERE name IN ('rds_startup_tasks', 'rds_help_revlogin', 'rds_hexadecimal', 'rds_failover_tracking', 'rds_database_tracking', 'rds_track_change')) ) From 259981a8508f8475e32b9e0dcba12249f4d4650a Mon Sep 17 00:00:00 2001 From: Anthony Green <40231097+Ant-Green@users.noreply.github.com> Date: Wed, 21 Sep 2022 08:54:07 +0100 Subject: [PATCH 316/662] Added 2017 CU31 Added the last CU 2017 before it drops into extended support; --- SqlServerVersions.sql | 1 + 1 file changed, 1 insertion(+) diff --git a/SqlServerVersions.sql b/SqlServerVersions.sql index a83db6e22..0f3dab5c9 100644 --- a/SqlServerVersions.sql +++ b/SqlServerVersions.sql @@ -63,6 +63,7 @@ VALUES (15, 4003, 'CU1', 'https://support.microsoft.com/en-us/help/4527376', '2020-01-07', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 1 '), (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 '), + (14, 3456, 'RTM CU31', 'https://support.microsoft.com/en-us/help/5016884', '2022-09-20', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 31'), (14, 3451, 'RTM CU30', 'https://support.microsoft.com/en-us/help/5013756', '2022-07-13', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 30'), (14, 3445, 'RTM CU29 GDR', 'https://support.microsoft.com/en-us/help/5014553', '2022-06-14', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 29 GDR'), (14, 3436, 'RTM CU29', 'https://support.microsoft.com/en-us/help/5010786', '2022-03-31', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 29'), From 3a823b8fbfc57d57e5901389f0f1ca672a115385 Mon Sep 17 00:00:00 2001 From: Paul Lambert Date: Wed, 21 Sep 2022 19:43:30 +0800 Subject: [PATCH 317/662] Check ID 2 refer DBCC DBINFO data for log backup --- sp_Blitz.sql | 77 ++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 62 insertions(+), 15 deletions(-) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index 03305cf49..bcfe04a48 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -888,6 +888,40 @@ AS ) BEGIN + /* + Extract DBCC DBINFO data from the server. This data is used for check 2 using + the dbi_LastLogBackupTime field and check 68 using the dbi_LastKnownGood field. + NB: DBCC DBINFO is not available on AWS RDS databases so if the server is RDS + (which will have previously triggered inserting a checkID 223 record) and at + least one of the relevant checks is not being skipped then we can extract the + dbinfo information. + */ + IF NOT EXISTS ( SELECT 1 + FROM #BlitzResults + WHERE CheckID = 223 AND URL = 'https://aws.amazon.com/rds/sqlserver/') + AND ( + NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 2 ) + OR NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 68 ) + ) + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Extracting DBCC DBINFO data (used in checks 2 and 68).', 0, 1, 68) WITH NOWAIT; + + EXEC sp_MSforeachdb N'USE [?]; + SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + INSERT #DBCCs + (ParentObject, + Object, + Field, + Value) + EXEC (''DBCC DBInfo() With TableResults, NO_INFOMSGS''); + UPDATE #DBCCs SET DbName = N''?'' WHERE DbName IS NULL OPTION (RECOMPILE);'; + END + /* Our very first check! We'll put more comments in this one just to explain exactly how it works. First, we check to see if we're @@ -1033,6 +1067,7 @@ AS 'https://www.brentozar.com/go/biglogs' AS URL , ( 'The ' + CAST(CAST((SELECT ((SUM([mf].[size]) * 8.) / 1024.) FROM sys.[master_files] AS [mf] WHERE [mf].[database_id] = d.[database_id] AND [mf].[type_desc] = 'LOG') AS DECIMAL(18,2)) AS VARCHAR(30)) + 'MB log file has not been backed up in the last week.' ) AS Details FROM master.sys.databases d + LEFT JOIN #DBCCs ll On ll.DbName = d.name And ll.Field = 'dbi_LastLogBackupTime' WHERE d.recovery_model IN ( 1, 2 ) AND d.database_id NOT IN ( 2, 3 ) AND d.source_database_id IS NULL @@ -1043,12 +1078,23 @@ AS DatabaseName FROM #SkipChecks WHERE CheckID IS NULL OR CheckID = 2) - AND NOT EXISTS ( SELECT * - FROM msdb.dbo.backupset b - WHERE d.name COLLATE SQL_Latin1_General_CP1_CI_AS = b.database_name COLLATE SQL_Latin1_General_CP1_CI_AS - AND b.type = 'L' - AND b.backup_finish_date >= DATEADD(dd, - -7, GETDATE()) ); + AND ( + ( + /* We couldn't get a value from the DBCC DBINFO data so let's check the msdb backup history information */ + [ll].[Value] Is Null + AND NOT EXISTS ( SELECT * + FROM msdb.dbo.backupset b + WHERE d.name COLLATE SQL_Latin1_General_CP1_CI_AS = b.database_name COLLATE SQL_Latin1_General_CP1_CI_AS + AND b.type = 'L' + AND b.backup_finish_date >= DATEADD(dd,-7, GETDATE()) + ) + ) + OR + ( + Convert(datetime,ll.Value) < DATEADD(dd,-7, GETDATE()) + ) + + ); END; /* @@ -7286,15 +7332,16 @@ IF @ProductVersionMajor >= 10 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 68) WITH NOWAIT; - EXEC sp_MSforeachdb N'USE [?]; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - INSERT #DBCCs - (ParentObject, - Object, - Field, - Value) - EXEC (''DBCC DBInfo() With TableResults, NO_INFOMSGS''); - UPDATE #DBCCs SET DbName = N''?'' WHERE DbName IS NULL OPTION (RECOMPILE);'; + /* Removed as populating the #DBCCs table now done in advance as data is uses for multiple checks*/ + --EXEC sp_MSforeachdb N'USE [?]; + --SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + --INSERT #DBCCs + -- (ParentObject, + -- Object, + -- Field, + -- Value) + --EXEC (''DBCC DBInfo() With TableResults, NO_INFOMSGS''); + --UPDATE #DBCCs SET DbName = N''?'' WHERE DbName IS NULL OPTION (RECOMPILE);'; WITH DB2 AS ( SELECT DISTINCT From a15d91d2b16bb671d6d8434b45ed223ac658ada5 Mon Sep 17 00:00:00 2001 From: Anthony Green <40231097+Ant-Green@users.noreply.github.com> Date: Thu, 29 Sep 2022 08:04:44 +0100 Subject: [PATCH 318/662] Added CU18 for 2019 as not merged yet Added 2019 CU18 --- SqlServerVersions.sql | 1 + 1 file changed, 1 insertion(+) diff --git a/SqlServerVersions.sql b/SqlServerVersions.sql index 0f3dab5c9..dd47a238c 100644 --- a/SqlServerVersions.sql +++ b/SqlServerVersions.sql @@ -42,6 +42,7 @@ INSERT INTO dbo.SqlServerVersions (MajorVersionNumber, MinorVersionNumber, Branch, [Url], ReleaseDate, MainstreamSupportEndDate, ExtendedSupportEndDate, MajorVersionName, MinorVersionName) VALUES (16, 600, 'CTP2', 'https://support.microsoft.com/en-us/help/5011644', '2022-05-24', '2022-05-24', '2022-05-24', 'SQL Server 2022', 'CTP 2.0'), + (15, 4261, 'CU18', 'https://support.microsoft.com/en-us/help/5017593', '2022-09-28', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 18'), (15, 4249, 'CU17', 'https://support.microsoft.com/en-us/help/5016394', '2022-08-11', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 17'), (15, 4236, 'CU16 GDR', 'https://support.microsoft.com/en-us/help/5014353', '2022-06-14', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 16 GDR'), (15, 4223, 'CU16', 'https://support.microsoft.com/en-us/help/5011644', '2022-04-18', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 16'), From f92c7067d70ca89e4ef2fb01be443275406034f1 Mon Sep 17 00:00:00 2001 From: David Wiseman Date: Fri, 7 Oct 2022 13:44:43 +0100 Subject: [PATCH 319/662] sp_Blitz - Update Dangerous Third Party Modules Include modules listed here: https://learn.microsoft.com/en-us/troubleshoot/sql/performance/performance-consistency-issues-filter-drivers-modules #3147 --- sp_Blitz.sql | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index 03305cf49..bdd17b70b 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -5472,11 +5472,15 @@ IF @ProductVersionMajor >= 10 'https://support.microsoft.com/en-us/kb/2033238' AS [URL] , ( COALESCE(company, '') + ' - ' + COALESCE(description, '') + ' - ' + COALESCE(name, '') + ' - suspected dangerous third party module is installed.') AS [Details] FROM sys.dm_os_loaded_modules - WHERE UPPER(name) LIKE UPPER('%\ENTAPI.DLL') /* McAfee VirusScan Enterprise */ + WHERE UPPER(name) LIKE UPPER('%\ENTAPI.DLL') OR UPPER(name) LIKE '%MFEBOPK.SYS' /* McAfee VirusScan Enterprise */ OR UPPER(name) LIKE UPPER('%\HIPI.DLL') OR UPPER(name) LIKE UPPER('%\HcSQL.dll') OR UPPER(name) LIKE UPPER('%\HcApi.dll') OR UPPER(name) LIKE UPPER('%\HcThe.dll') /* McAfee Host Intrusion */ OR UPPER(name) LIKE UPPER('%\SOPHOS_DETOURED.DLL') OR UPPER(name) LIKE UPPER('%\SOPHOS_DETOURED_x64.DLL') OR UPPER(name) LIKE UPPER('%\SWI_IFSLSP_64.dll') OR UPPER(name) LIKE UPPER('%\SOPHOS~%.dll') /* Sophos AV */ - OR UPPER(name) LIKE UPPER('%\PIOLEDB.DLL') OR UPPER(name) LIKE UPPER('%\PISDK.DLL'); /* OSISoft PI data access */ - + OR UPPER(name) LIKE UPPER('%\PIOLEDB.DLL') OR UPPER(name) LIKE UPPER('%\PISDK.DLL') /* OSISoft PI data access */ + OR UPPER(name) LIKE UPPER('%ScriptControl%.dll') OR UPPER(name) LIKE UPPER('%umppc%.dll') /* CrowdStrike */ + OR UPPER(name) LIKE UPPER('%perfiCrcPerfMonMgr.DLL') /* Trend Micro OfficeScan */ + OR UPPER(name) LIKE UPPER('%NLEMSQL.SYS') /* NetLib Encryptionizer-Software. */ + OR UPPER(name) LIKE UPPER('%MFETDIK.SYS'); /* McAfee Anti-Virus Mini-Firewall */ + /* MS docs link for blacklisted modules: https://learn.microsoft.com/en-us/troubleshoot/sql/performance/performance-consistency-issues-filter-drivers-modules */ END; /*Find shrink database tasks*/ From a01ce33d54af67fc62d9a51a59d8a01742fbb5cb Mon Sep 17 00:00:00 2001 From: David Wiseman Date: Fri, 7 Oct 2022 15:10:48 +0100 Subject: [PATCH 320/662] sp_Blitz - Update Dangerous Third Party Modules for AntiVirus To detect sqlmaggieAntiVirus_64.dll (malware) or anything else labelled AntiVirus. #3149 --- sp_Blitz.sql | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index bdd17b70b..05cba63b6 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -5479,7 +5479,8 @@ IF @ProductVersionMajor >= 10 OR UPPER(name) LIKE UPPER('%ScriptControl%.dll') OR UPPER(name) LIKE UPPER('%umppc%.dll') /* CrowdStrike */ OR UPPER(name) LIKE UPPER('%perfiCrcPerfMonMgr.DLL') /* Trend Micro OfficeScan */ OR UPPER(name) LIKE UPPER('%NLEMSQL.SYS') /* NetLib Encryptionizer-Software. */ - OR UPPER(name) LIKE UPPER('%MFETDIK.SYS'); /* McAfee Anti-Virus Mini-Firewall */ + OR UPPER(name) LIKE UPPER('%MFETDIK.SYS') /* McAfee Anti-Virus Mini-Firewall */ + OR UPPER(name) LIKE UPPER('%ANTIVIRUS%'); /* To pick up sqlmaggieAntiVirus_64.dll (malware) or anything else labelled AntiVirus */ /* MS docs link for blacklisted modules: https://learn.microsoft.com/en-us/troubleshoot/sql/performance/performance-consistency-issues-filter-drivers-modules */ END; From 3745088163c0330680feda2bcc1fee302b53c742 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Thu, 13 Oct 2022 09:43:07 -0400 Subject: [PATCH 321/662] 2022-10-13 October Release Bumping version numbers and dates. --- Install-All-Scripts.sql | 155 ++++++++++++++++-------- Install-Core-Blitz-No-Query-Store.sql | 139 ++++++++++++++------- Install-Core-Blitz-With-Query-Store.sql | 145 +++++++++++++++------- sp_AllNightLog.sql | 2 +- sp_AllNightLog_Setup.sql | 2 +- sp_Blitz.sql | 2 +- sp_BlitzAnalysis.sql | 2 +- sp_BlitzBackups.sql | 2 +- sp_BlitzCache.sql | 2 +- sp_BlitzFirst.sql | 2 +- sp_BlitzInMemoryOLTP.sql | 2 +- sp_BlitzIndex.sql | 2 +- sp_BlitzLock.sql | 2 +- sp_BlitzQueryStore.sql | 2 +- sp_BlitzWho.sql | 2 +- sp_DatabaseRestore.sql | 2 +- sp_ineachdb.sql | 2 +- 17 files changed, 310 insertions(+), 157 deletions(-) diff --git a/Install-All-Scripts.sql b/Install-All-Scripts.sql index e14eb3071..768123592 100644 --- a/Install-All-Scripts.sql +++ b/Install-All-Scripts.sql @@ -31,7 +31,7 @@ SET STATISTICS XML OFF; BEGIN; -SELECT @Version = '8.10', @VersionDate = '20220718'; +SELECT @Version = '8.11', @VersionDate = '20221013'; IF(@VersionCheckMode = 1) BEGIN @@ -578,7 +578,7 @@ DiskPollster: SELECT fl.BackupFile FROM @FileList AS fl WHERE fl.BackupFile IS NOT NULL - AND fl.BackupFile NOT IN (SELECT name from sys.databases where database_id < 5) + AND fl.BackupFile COLLATE DATABASE_DEFAULT NOT IN (SELECT name from sys.databases where database_id < 5) AND NOT EXISTS ( SELECT 1 @@ -1547,7 +1547,7 @@ SET STATISTICS XML OFF; BEGIN; -SELECT @Version = '8.10', @VersionDate = '20220718'; +SELECT @Version = '8.11', @VersionDate = '20221013'; IF(@VersionCheckMode = 1) BEGIN @@ -2891,7 +2891,7 @@ AS SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.10', @VersionDate = '20220718'; + SELECT @Version = '8.11', @VersionDate = '20221013'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -3278,7 +3278,6 @@ AS /* If the server is Amazon RDS, skip checks that it doesn't allow */ IF LEFT(CAST(SERVERPROPERTY('ComputerNamePhysicalNetBIOS') AS VARCHAR(8000)), 8) = 'EC2AMAZ-' AND LEFT(CAST(SERVERPROPERTY('MachineName') AS VARCHAR(8000)), 8) = 'EC2AMAZ-' - AND LEFT(CAST(SERVERPROPERTY('ServerName') AS VARCHAR(8000)), 8) = 'EC2AMAZ-' AND db_id('rdsadmin') IS NOT NULL AND EXISTS(SELECT * FROM master.sys.all_objects WHERE name IN ('rds_startup_tasks', 'rds_help_revlogin', 'rds_hexadecimal', 'rds_failover_tracking', 'rds_database_tracking', 'rds_track_change')) BEGIN @@ -3741,6 +3740,40 @@ AS ) BEGIN + /* + Extract DBCC DBINFO data from the server. This data is used for check 2 using + the dbi_LastLogBackupTime field and check 68 using the dbi_LastKnownGood field. + NB: DBCC DBINFO is not available on AWS RDS databases so if the server is RDS + (which will have previously triggered inserting a checkID 223 record) and at + least one of the relevant checks is not being skipped then we can extract the + dbinfo information. + */ + IF NOT EXISTS ( SELECT 1 + FROM #BlitzResults + WHERE CheckID = 223 AND URL = 'https://aws.amazon.com/rds/sqlserver/') + AND ( + NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 2 ) + OR NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 68 ) + ) + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Extracting DBCC DBINFO data (used in checks 2 and 68).', 0, 1, 68) WITH NOWAIT; + + EXEC sp_MSforeachdb N'USE [?]; + SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + INSERT #DBCCs + (ParentObject, + Object, + Field, + Value) + EXEC (''DBCC DBInfo() With TableResults, NO_INFOMSGS''); + UPDATE #DBCCs SET DbName = N''?'' WHERE DbName IS NULL OPTION (RECOMPILE);'; + END + /* Our very first check! We'll put more comments in this one just to explain exactly how it works. First, we check to see if we're @@ -3886,6 +3919,7 @@ AS 'https://www.brentozar.com/go/biglogs' AS URL , ( 'The ' + CAST(CAST((SELECT ((SUM([mf].[size]) * 8.) / 1024.) FROM sys.[master_files] AS [mf] WHERE [mf].[database_id] = d.[database_id] AND [mf].[type_desc] = 'LOG') AS DECIMAL(18,2)) AS VARCHAR(30)) + 'MB log file has not been backed up in the last week.' ) AS Details FROM master.sys.databases d + LEFT JOIN #DBCCs ll On ll.DbName = d.name And ll.Field = 'dbi_LastLogBackupTime' WHERE d.recovery_model IN ( 1, 2 ) AND d.database_id NOT IN ( 2, 3 ) AND d.source_database_id IS NULL @@ -3896,12 +3930,23 @@ AS DatabaseName FROM #SkipChecks WHERE CheckID IS NULL OR CheckID = 2) - AND NOT EXISTS ( SELECT * - FROM msdb.dbo.backupset b - WHERE d.name COLLATE SQL_Latin1_General_CP1_CI_AS = b.database_name COLLATE SQL_Latin1_General_CP1_CI_AS - AND b.type = 'L' - AND b.backup_finish_date >= DATEADD(dd, - -7, GETDATE()) ); + AND ( + ( + /* We couldn't get a value from the DBCC DBINFO data so let's check the msdb backup history information */ + [ll].[Value] Is Null + AND NOT EXISTS ( SELECT * + FROM msdb.dbo.backupset b + WHERE d.name COLLATE SQL_Latin1_General_CP1_CI_AS = b.database_name COLLATE SQL_Latin1_General_CP1_CI_AS + AND b.type = 'L' + AND b.backup_finish_date >= DATEADD(dd,-7, GETDATE()) + ) + ) + OR + ( + Convert(datetime,ll.Value) < DATEADD(dd,-7, GETDATE()) + ) + + ); END; /* @@ -8325,11 +8370,16 @@ IF @ProductVersionMajor >= 10 'https://support.microsoft.com/en-us/kb/2033238' AS [URL] , ( COALESCE(company, '') + ' - ' + COALESCE(description, '') + ' - ' + COALESCE(name, '') + ' - suspected dangerous third party module is installed.') AS [Details] FROM sys.dm_os_loaded_modules - WHERE UPPER(name) LIKE UPPER('%\ENTAPI.DLL') /* McAfee VirusScan Enterprise */ + WHERE UPPER(name) LIKE UPPER('%\ENTAPI.DLL') OR UPPER(name) LIKE '%MFEBOPK.SYS' /* McAfee VirusScan Enterprise */ OR UPPER(name) LIKE UPPER('%\HIPI.DLL') OR UPPER(name) LIKE UPPER('%\HcSQL.dll') OR UPPER(name) LIKE UPPER('%\HcApi.dll') OR UPPER(name) LIKE UPPER('%\HcThe.dll') /* McAfee Host Intrusion */ OR UPPER(name) LIKE UPPER('%\SOPHOS_DETOURED.DLL') OR UPPER(name) LIKE UPPER('%\SOPHOS_DETOURED_x64.DLL') OR UPPER(name) LIKE UPPER('%\SWI_IFSLSP_64.dll') OR UPPER(name) LIKE UPPER('%\SOPHOS~%.dll') /* Sophos AV */ - OR UPPER(name) LIKE UPPER('%\PIOLEDB.DLL') OR UPPER(name) LIKE UPPER('%\PISDK.DLL'); /* OSISoft PI data access */ - + OR UPPER(name) LIKE UPPER('%\PIOLEDB.DLL') OR UPPER(name) LIKE UPPER('%\PISDK.DLL') /* OSISoft PI data access */ + OR UPPER(name) LIKE UPPER('%ScriptControl%.dll') OR UPPER(name) LIKE UPPER('%umppc%.dll') /* CrowdStrike */ + OR UPPER(name) LIKE UPPER('%perfiCrcPerfMonMgr.DLL') /* Trend Micro OfficeScan */ + OR UPPER(name) LIKE UPPER('%NLEMSQL.SYS') /* NetLib Encryptionizer-Software. */ + OR UPPER(name) LIKE UPPER('%MFETDIK.SYS') /* McAfee Anti-Virus Mini-Firewall */ + OR UPPER(name) LIKE UPPER('%ANTIVIRUS%'); /* To pick up sqlmaggieAntiVirus_64.dll (malware) or anything else labelled AntiVirus */ + /* MS docs link for blacklisted modules: https://learn.microsoft.com/en-us/troubleshoot/sql/performance/performance-consistency-issues-filter-drivers-modules */ END; /*Find shrink database tasks*/ @@ -10139,15 +10189,16 @@ IF @ProductVersionMajor >= 10 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 68) WITH NOWAIT; - EXEC sp_MSforeachdb N'USE [?]; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - INSERT #DBCCs - (ParentObject, - Object, - Field, - Value) - EXEC (''DBCC DBInfo() With TableResults, NO_INFOMSGS''); - UPDATE #DBCCs SET DbName = N''?'' WHERE DbName IS NULL OPTION (RECOMPILE);'; + /* Removed as populating the #DBCCs table now done in advance as data is uses for multiple checks*/ + --EXEC sp_MSforeachdb N'USE [?]; + --SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + --INSERT #DBCCs + -- (ParentObject, + -- Object, + -- Field, + -- Value) + --EXEC (''DBCC DBInfo() With TableResults, NO_INFOMSGS''); + --UPDATE #DBCCs SET DbName = N''?'' WHERE DbName IS NULL OPTION (RECOMPILE);'; WITH DB2 AS ( SELECT DISTINCT @@ -11198,7 +11249,6 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 -- If this is Amazon RDS, use rdsadmin.dbo.rds_read_error_log IF LEFT(CAST(SERVERPROPERTY('ComputerNamePhysicalNetBIOS') AS VARCHAR(8000)), 8) = 'EC2AMAZ-' AND LEFT(CAST(SERVERPROPERTY('MachineName') AS VARCHAR(8000)), 8) = 'EC2AMAZ-' - AND LEFT(CAST(SERVERPROPERTY('ServerName') AS VARCHAR(8000)), 8) = 'EC2AMAZ-' AND db_id('rdsadmin') IS NOT NULL AND EXISTS(SELECT * FROM master.sys.all_objects WHERE name IN ('rds_startup_tasks', 'rds_help_revlogin', 'rds_hexadecimal', 'rds_failover_tracking', 'rds_database_tracking', 'rds_track_change')) BEGIN @@ -12540,7 +12590,7 @@ AS SET NOCOUNT ON; SET STATISTICS XML OFF; -SELECT @Version = '8.10', @VersionDate = '20220718'; +SELECT @Version = '8.11', @VersionDate = '20221013'; IF(@VersionCheckMode = 1) BEGIN @@ -13418,7 +13468,7 @@ AS SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.10', @VersionDate = '20220718'; + SELECT @Version = '8.11', @VersionDate = '20221013'; IF(@VersionCheckMode = 1) BEGIN @@ -15199,7 +15249,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.10', @VersionDate = '20220718'; +SELECT @Version = '8.11', @VersionDate = '20221013'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -22462,7 +22512,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.10', @VersionDate = '20220718'; +SELECT @Version = '8.11', @VersionDate = '20221013'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -28212,7 +28262,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.10', @VersionDate = '20220718'; +SELECT @Version = '8.11', @VersionDate = '20221013'; IF(@VersionCheckMode = 1) @@ -28499,7 +28549,6 @@ You need to use an Azure storage account, and the path has to look like this: ht /* WITH ROWCOUNT doesn't work on Amazon RDS - see: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2037 */ IF LEFT(CAST(SERVERPROPERTY('ComputerNamePhysicalNetBIOS') AS VARCHAR(8000)), 8) <> 'EC2AMAZ-' AND LEFT(CAST(SERVERPROPERTY('MachineName') AS VARCHAR(8000)), 8) <> 'EC2AMAZ-' - AND LEFT(CAST(SERVERPROPERTY('ServerName') AS VARCHAR(8000)), 8) <> 'EC2AMAZ-' AND db_id('rdsadmin') IS NULL BEGIN; BEGIN TRY; @@ -28947,7 +28996,6 @@ You need to use an Azure storage account, and the path has to look like this: ht IF SERVERPROPERTY('EngineEdition') NOT IN (5, 6) /* Azure SQL DB doesn't support querying jobs */ AND NOT (LEFT(CAST(SERVERPROPERTY('ComputerNamePhysicalNetBIOS') AS VARCHAR(8000)), 8) = 'EC2AMAZ-' /* Neither does Amazon RDS Express Edition */ AND LEFT(CAST(SERVERPROPERTY('MachineName') AS VARCHAR(8000)), 8) = 'EC2AMAZ-' - AND LEFT(CAST(SERVERPROPERTY('ServerName') AS VARCHAR(8000)), 8) = 'EC2AMAZ-' AND db_id('rdsadmin') IS NOT NULL AND EXISTS(SELECT * FROM master.sys.all_objects WHERE name IN ('rds_startup_tasks', 'rds_help_revlogin', 'rds_hexadecimal', 'rds_failover_tracking', 'rds_database_tracking', 'rds_track_change')) ) @@ -30052,7 +30100,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.10', @VersionDate = '20220718'; +SELECT @Version = '8.11', @VersionDate = '20221013'; IF(@VersionCheckMode = 1) BEGIN RETURN; @@ -30200,12 +30248,12 @@ IF ( SELECT COUNT(*) /*Making sure your databases are using QDS.*/ RAISERROR('Checking database validity', 0, 1) WITH NOWAIT; -IF (@is_azure_db = 1) +IF (@is_azure_db = 1 AND SERVERPROPERTY ('ENGINEEDITION') <> 8) SET @DatabaseName = DB_NAME(); ELSE BEGIN - /*If we're on Azure we don't need to check all this @DatabaseName stuff...*/ + /*If we're on Azure SQL DB we don't need to check all this @DatabaseName stuff...*/ SET @DatabaseName = LTRIM(RTRIM(@DatabaseName)); @@ -35783,7 +35831,7 @@ BEGIN SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.10', @VersionDate = '20220718'; + SELECT @Version = '8.11', @VersionDate = '20221013'; IF(@VersionCheckMode = 1) BEGIN @@ -37180,7 +37228,7 @@ SET STATISTICS XML OFF; /*Versioning details*/ -SELECT @Version = '8.10', @VersionDate = '20220718'; +SELECT @Version = '8.11', @VersionDate = '20221013'; IF(@VersionCheckMode = 1) BEGIN @@ -38746,7 +38794,7 @@ BEGIN SET NOCOUNT ON; SET STATISTICS XML OFF; - SELECT @Version = '8.10', @VersionDate = '20220718'; + SELECT @Version = '8.11', @VersionDate = '20221013'; IF(@VersionCheckMode = 1) BEGIN @@ -39093,6 +39141,8 @@ INSERT INTO dbo.SqlServerVersions (MajorVersionNumber, MinorVersionNumber, Branch, [Url], ReleaseDate, MainstreamSupportEndDate, ExtendedSupportEndDate, MajorVersionName, MinorVersionName) VALUES (16, 600, 'CTP2', 'https://support.microsoft.com/en-us/help/5011644', '2022-05-24', '2022-05-24', '2022-05-24', 'SQL Server 2022', 'CTP 2.0'), + (15, 4261, 'CU18', 'https://support.microsoft.com/en-us/help/5017593', '2022-09-28', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 18'), + (15, 4249, 'CU17', 'https://support.microsoft.com/en-us/help/5016394', '2022-08-11', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 17'), (15, 4236, 'CU16 GDR', 'https://support.microsoft.com/en-us/help/5014353', '2022-06-14', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 16 GDR'), (15, 4223, 'CU16', 'https://support.microsoft.com/en-us/help/5011644', '2022-04-18', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 16'), (15, 4198, 'CU15', 'https://support.microsoft.com/en-us/help/5008996', '2022-01-07', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 15'), @@ -39113,6 +39163,7 @@ VALUES (15, 4003, 'CU1', 'https://support.microsoft.com/en-us/help/4527376', '2020-01-07', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 1 '), (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 '), + (14, 3456, 'RTM CU31', 'https://support.microsoft.com/en-us/help/5016884', '2022-09-20', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 31'), (14, 3451, 'RTM CU30', 'https://support.microsoft.com/en-us/help/5013756', '2022-07-13', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 30'), (14, 3445, 'RTM CU29 GDR', 'https://support.microsoft.com/en-us/help/5014553', '2022-06-14', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 29 GDR'), (14, 3436, 'RTM CU29', 'https://support.microsoft.com/en-us/help/5010786', '2022-03-31', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 29'), @@ -39505,7 +39556,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.10', @VersionDate = '20220718'; +SELECT @Version = '8.11', @VersionDate = '20221013'; IF(@VersionCheckMode = 1) BEGIN @@ -39734,7 +39785,7 @@ BEGIN END; /* IF @SinceStartup = 0 AND @Seconds > 0 AND @ExpertMode = 1 AND @OutputType <> 'NONE' - What's running right now? This is the first and last result set. */ /* Set start/finish times AFTER sp_BlitzWho runs. For more info: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2244 */ - IF @Seconds = 0 AND SERVERPROPERTY('Edition') = 'SQL Azure' + IF @Seconds = 0 AND SERVERPROPERTY('EngineEdition') = 5 /*SERVERPROPERTY('Edition') = 'SQL Azure'*/ WITH WaitTimes AS ( SELECT wait_type, wait_time_ms, NTILE(3) OVER(ORDER BY wait_time_ms) AS grouper @@ -39745,7 +39796,7 @@ BEGIN SELECT @StartSampleTime = DATEADD(mi, AVG(-wait_time_ms / 1000 / 60), SYSDATETIMEOFFSET()), @FinishSampleTime = SYSDATETIMEOFFSET() FROM WaitTimes WHERE grouper = 2; - ELSE IF @Seconds = 0 AND SERVERPROPERTY('Edition') <> 'SQL Azure' + ELSE IF @Seconds = 0 AND SERVERPROPERTY('EngineEdition') <> 5 /*SERVERPROPERTY('Edition') <> 'SQL Azure'*/ SELECT @StartSampleTime = DATEADD(MINUTE,DATEDIFF(MINUTE, GETDATE(), GETUTCDATE()),create_date) , @FinishSampleTime = SYSDATETIMEOFFSET() FROM sys.databases WHERE database_id = 2; @@ -40498,7 +40549,7 @@ BEGIN DROP TABLE #MasterFiles; CREATE TABLE #MasterFiles (database_id INT, file_id INT, type_desc NVARCHAR(50), name NVARCHAR(255), physical_name NVARCHAR(255), size BIGINT); /* Azure SQL Database doesn't have sys.master_files, so we have to build our own. */ - IF ((SERVERPROPERTY('Edition')) = 'SQL Azure' + IF (SERVERPROPERTY('EngineEdition') = 5 /*(SERVERPROPERTY('Edition')) = 'SQL Azure' */ AND (OBJECT_ID('sys.master_files') IS NULL)) SET @StringToExecute = 'INSERT INTO #MasterFiles (database_id, file_id, type_desc, name, physical_name, size) SELECT DB_ID(), file_id, type_desc, name, physical_name, size FROM sys.database_files;'; ELSE @@ -40590,7 +40641,7 @@ BEGIN @StockDetailsFooter = @StockDetailsFooter + @LineFeed + ' -- ?>'; /* Get the instance name to use as a Perfmon counter prefix. */ - IF CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) = 'SQL Azure' + IF SERVERPROPERTY('EngineEdition') = 5 /*CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) = 'SQL Azure'*/ SELECT TOP 1 @ServiceName = LEFT(object_name, (CHARINDEX(':', object_name) - 1)) FROM sys.dm_os_performance_counters; ELSE @@ -40859,7 +40910,7 @@ BEGIN CASE @Seconds WHEN 0 THEN 0 ELSE SUM(os.signal_wait_time_ms) OVER (PARTITION BY os.wait_type ) END AS sum_signal_wait_time_ms, CASE @Seconds WHEN 0 THEN 0 ELSE SUM(os.waiting_tasks_count) OVER (PARTITION BY os.wait_type) END AS sum_waiting_tasks '; - IF SERVERPROPERTY('Edition') = 'SQL Azure' + IF SERVERPROPERTY('EngineEdition') = 5 /*SERVERPROPERTY('Edition') = 'SQL Azure'*/ SET @StringToExecute = @StringToExecute + N' FROM sys.dm_db_wait_stats os '; ELSE SET @StringToExecute = @StringToExecute + N' FROM sys.dm_os_wait_stats os '; @@ -41010,7 +41061,7 @@ BEGIN END /* If there's a backup running, add details explaining how long full backup has been taking in the last month. */ - IF @Seconds > 0 AND CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) <> 'SQL Azure' + IF @Seconds > 0 AND SERVERPROPERTY('EngineEdition') <> 5 /*CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) <> 'SQL Azure'*/ BEGIN SET @StringToExecute = 'UPDATE #BlitzFirstResults SET Details = Details + '' Over the last 60 days, the full backup usually takes '' + CAST((SELECT AVG(DATEDIFF(mi, bs.backup_start_date, bs.backup_finish_date)) FROM msdb.dbo.backupset bs WHERE abr.DatabaseName = bs.database_name AND bs.type = ''D'' AND bs.backup_start_date > DATEADD(dd, -60, SYSDATETIMEOFFSET()) AND bs.backup_finish_date IS NOT NULL) AS NVARCHAR(100)) + '' minutes.'' FROM #BlitzFirstResults abr WHERE abr.CheckID = 1 AND EXISTS (SELECT * FROM msdb.dbo.backupset bs WHERE bs.type = ''D'' AND bs.backup_start_date > DATEADD(dd, -60, SYSDATETIMEOFFSET()) AND bs.backup_finish_date IS NOT NULL AND abr.DatabaseName = bs.database_name AND DATEDIFF(mi, bs.backup_start_date, bs.backup_finish_date) > 1)'; EXEC(@StringToExecute); @@ -41138,7 +41189,7 @@ BEGIN END /* Query Problems - Long-Running Query Blocking Others - CheckID 5 */ - IF SERVERPROPERTY('Edition') <> 'SQL Azure' AND @Seconds > 0 AND EXISTS(SELECT * FROM sys.dm_os_waiting_tasks WHERE wait_type LIKE 'LCK%' AND wait_duration_ms > 30000) + IF SERVERPROPERTY('EngineEdition') <> 5 /*SERVERPROPERTY('Edition') <> 'SQL Azure'*/ AND @Seconds > 0 AND EXISTS(SELECT * FROM sys.dm_os_waiting_tasks WHERE wait_type LIKE 'LCK%' AND wait_duration_ms > 30000) BEGIN IF (@Debug = 1) BEGIN @@ -41398,7 +41449,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, AND CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) NOT LIKE '%Standard%'; /* Server Performance - Target Memory Lower Than Max - CheckID 35 */ - IF SERVERPROPERTY('Edition') <> 'SQL Azure' + IF SERVERPROPERTY('EngineEdition') <> 5 /*SERVERPROPERTY('Edition') <> 'SQL Azure'*/ BEGIN IF (@Debug = 1) BEGIN @@ -41846,7 +41897,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, RAISERROR('Running CheckID 23',10,1) WITH NOWAIT; END - IF SERVERPROPERTY('Edition') <> 'SQL Azure' + IF SERVERPROPERTY('EngineEdition') <> 5 /*SERVERPROPERTY('Edition') <> 'SQL Azure'*/ WITH y AS ( @@ -41925,12 +41976,12 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, /* We don't want to hang around to obtain locks */ SET LOCK_TIMEOUT 0; - IF SERVERPROPERTY('Edition') <> 'SQL Azure' + IF SERVERPROPERTY('EngineEdition') <> 5 /*SERVERPROPERTY('Edition') <> 'SQL Azure'*/ SET @StringToExecute = N'USE [?];' + @LineFeed; ELSE SET @StringToExecute = N''; - SET @StringToExecute = @StringToExecute + 'USE [?]; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; SET LOCK_TIMEOUT 1000;' + @LineFeed + + SET @StringToExecute = @StringToExecute + 'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; SET LOCK_TIMEOUT 1000;' + @LineFeed + 'BEGIN TRY' + @LineFeed + ' INSERT INTO #UpdatedStats(HowToStopIt, RowsForSorting)' + @LineFeed + ' SELECT HowToStopIt = ' + @LineFeed + @@ -41974,7 +42025,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 'END CATCH' ; - IF SERVERPROPERTY('Edition') <> 'SQL Azure' + IF SERVERPROPERTY('EngineEdition') <> 5 /*SERVERPROPERTY('Edition') <> 'SQL Azure'*/ EXEC sp_MSforeachdb @StringToExecute; ELSE EXEC(@StringToExecute); @@ -42054,7 +42105,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, SUM(os.signal_wait_time_ms) OVER (PARTITION BY os.wait_type ) AS sum_signal_wait_time_ms, SUM(os.waiting_tasks_count) OVER (PARTITION BY os.wait_type) AS sum_waiting_tasks '; - IF SERVERPROPERTY('Edition') = 'SQL Azure' + IF SERVERPROPERTY('EngineEdition') = 5 /*SERVERPROPERTY('Edition') = 'SQL Azure'*/ SET @StringToExecute = @StringToExecute + N' FROM sys.dm_db_wait_stats os '; ELSE SET @StringToExecute = @StringToExecute + N' FROM sys.dm_os_wait_stats os '; @@ -42681,7 +42732,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, AND ps.value_delta > (10 * @Seconds); /* Ignore servers sitting idle */ /* Azure Performance - Database is Maxed Out - CheckID 41 */ - IF SERVERPROPERTY('Edition') = 'SQL Azure' + IF SERVERPROPERTY('EngineEdition') = 5 /*SERVERPROPERTY('Edition') = 'SQL Azure'*/ BEGIN IF (@Debug = 1) BEGIN diff --git a/Install-Core-Blitz-No-Query-Store.sql b/Install-Core-Blitz-No-Query-Store.sql index 7ad3bef75..73407f16d 100644 --- a/Install-Core-Blitz-No-Query-Store.sql +++ b/Install-Core-Blitz-No-Query-Store.sql @@ -38,7 +38,7 @@ AS SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.10', @VersionDate = '20220718'; + SELECT @Version = '8.11', @VersionDate = '20221013'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -425,7 +425,6 @@ AS /* If the server is Amazon RDS, skip checks that it doesn't allow */ IF LEFT(CAST(SERVERPROPERTY('ComputerNamePhysicalNetBIOS') AS VARCHAR(8000)), 8) = 'EC2AMAZ-' AND LEFT(CAST(SERVERPROPERTY('MachineName') AS VARCHAR(8000)), 8) = 'EC2AMAZ-' - AND LEFT(CAST(SERVERPROPERTY('ServerName') AS VARCHAR(8000)), 8) = 'EC2AMAZ-' AND db_id('rdsadmin') IS NOT NULL AND EXISTS(SELECT * FROM master.sys.all_objects WHERE name IN ('rds_startup_tasks', 'rds_help_revlogin', 'rds_hexadecimal', 'rds_failover_tracking', 'rds_database_tracking', 'rds_track_change')) BEGIN @@ -888,6 +887,40 @@ AS ) BEGIN + /* + Extract DBCC DBINFO data from the server. This data is used for check 2 using + the dbi_LastLogBackupTime field and check 68 using the dbi_LastKnownGood field. + NB: DBCC DBINFO is not available on AWS RDS databases so if the server is RDS + (which will have previously triggered inserting a checkID 223 record) and at + least one of the relevant checks is not being skipped then we can extract the + dbinfo information. + */ + IF NOT EXISTS ( SELECT 1 + FROM #BlitzResults + WHERE CheckID = 223 AND URL = 'https://aws.amazon.com/rds/sqlserver/') + AND ( + NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 2 ) + OR NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 68 ) + ) + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Extracting DBCC DBINFO data (used in checks 2 and 68).', 0, 1, 68) WITH NOWAIT; + + EXEC sp_MSforeachdb N'USE [?]; + SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + INSERT #DBCCs + (ParentObject, + Object, + Field, + Value) + EXEC (''DBCC DBInfo() With TableResults, NO_INFOMSGS''); + UPDATE #DBCCs SET DbName = N''?'' WHERE DbName IS NULL OPTION (RECOMPILE);'; + END + /* Our very first check! We'll put more comments in this one just to explain exactly how it works. First, we check to see if we're @@ -1033,6 +1066,7 @@ AS 'https://www.brentozar.com/go/biglogs' AS URL , ( 'The ' + CAST(CAST((SELECT ((SUM([mf].[size]) * 8.) / 1024.) FROM sys.[master_files] AS [mf] WHERE [mf].[database_id] = d.[database_id] AND [mf].[type_desc] = 'LOG') AS DECIMAL(18,2)) AS VARCHAR(30)) + 'MB log file has not been backed up in the last week.' ) AS Details FROM master.sys.databases d + LEFT JOIN #DBCCs ll On ll.DbName = d.name And ll.Field = 'dbi_LastLogBackupTime' WHERE d.recovery_model IN ( 1, 2 ) AND d.database_id NOT IN ( 2, 3 ) AND d.source_database_id IS NULL @@ -1043,12 +1077,23 @@ AS DatabaseName FROM #SkipChecks WHERE CheckID IS NULL OR CheckID = 2) - AND NOT EXISTS ( SELECT * - FROM msdb.dbo.backupset b - WHERE d.name COLLATE SQL_Latin1_General_CP1_CI_AS = b.database_name COLLATE SQL_Latin1_General_CP1_CI_AS - AND b.type = 'L' - AND b.backup_finish_date >= DATEADD(dd, - -7, GETDATE()) ); + AND ( + ( + /* We couldn't get a value from the DBCC DBINFO data so let's check the msdb backup history information */ + [ll].[Value] Is Null + AND NOT EXISTS ( SELECT * + FROM msdb.dbo.backupset b + WHERE d.name COLLATE SQL_Latin1_General_CP1_CI_AS = b.database_name COLLATE SQL_Latin1_General_CP1_CI_AS + AND b.type = 'L' + AND b.backup_finish_date >= DATEADD(dd,-7, GETDATE()) + ) + ) + OR + ( + Convert(datetime,ll.Value) < DATEADD(dd,-7, GETDATE()) + ) + + ); END; /* @@ -5472,11 +5517,16 @@ IF @ProductVersionMajor >= 10 'https://support.microsoft.com/en-us/kb/2033238' AS [URL] , ( COALESCE(company, '') + ' - ' + COALESCE(description, '') + ' - ' + COALESCE(name, '') + ' - suspected dangerous third party module is installed.') AS [Details] FROM sys.dm_os_loaded_modules - WHERE UPPER(name) LIKE UPPER('%\ENTAPI.DLL') /* McAfee VirusScan Enterprise */ + WHERE UPPER(name) LIKE UPPER('%\ENTAPI.DLL') OR UPPER(name) LIKE '%MFEBOPK.SYS' /* McAfee VirusScan Enterprise */ OR UPPER(name) LIKE UPPER('%\HIPI.DLL') OR UPPER(name) LIKE UPPER('%\HcSQL.dll') OR UPPER(name) LIKE UPPER('%\HcApi.dll') OR UPPER(name) LIKE UPPER('%\HcThe.dll') /* McAfee Host Intrusion */ OR UPPER(name) LIKE UPPER('%\SOPHOS_DETOURED.DLL') OR UPPER(name) LIKE UPPER('%\SOPHOS_DETOURED_x64.DLL') OR UPPER(name) LIKE UPPER('%\SWI_IFSLSP_64.dll') OR UPPER(name) LIKE UPPER('%\SOPHOS~%.dll') /* Sophos AV */ - OR UPPER(name) LIKE UPPER('%\PIOLEDB.DLL') OR UPPER(name) LIKE UPPER('%\PISDK.DLL'); /* OSISoft PI data access */ - + OR UPPER(name) LIKE UPPER('%\PIOLEDB.DLL') OR UPPER(name) LIKE UPPER('%\PISDK.DLL') /* OSISoft PI data access */ + OR UPPER(name) LIKE UPPER('%ScriptControl%.dll') OR UPPER(name) LIKE UPPER('%umppc%.dll') /* CrowdStrike */ + OR UPPER(name) LIKE UPPER('%perfiCrcPerfMonMgr.DLL') /* Trend Micro OfficeScan */ + OR UPPER(name) LIKE UPPER('%NLEMSQL.SYS') /* NetLib Encryptionizer-Software. */ + OR UPPER(name) LIKE UPPER('%MFETDIK.SYS') /* McAfee Anti-Virus Mini-Firewall */ + OR UPPER(name) LIKE UPPER('%ANTIVIRUS%'); /* To pick up sqlmaggieAntiVirus_64.dll (malware) or anything else labelled AntiVirus */ + /* MS docs link for blacklisted modules: https://learn.microsoft.com/en-us/troubleshoot/sql/performance/performance-consistency-issues-filter-drivers-modules */ END; /*Find shrink database tasks*/ @@ -7286,15 +7336,16 @@ IF @ProductVersionMajor >= 10 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 68) WITH NOWAIT; - EXEC sp_MSforeachdb N'USE [?]; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - INSERT #DBCCs - (ParentObject, - Object, - Field, - Value) - EXEC (''DBCC DBInfo() With TableResults, NO_INFOMSGS''); - UPDATE #DBCCs SET DbName = N''?'' WHERE DbName IS NULL OPTION (RECOMPILE);'; + /* Removed as populating the #DBCCs table now done in advance as data is uses for multiple checks*/ + --EXEC sp_MSforeachdb N'USE [?]; + --SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + --INSERT #DBCCs + -- (ParentObject, + -- Object, + -- Field, + -- Value) + --EXEC (''DBCC DBInfo() With TableResults, NO_INFOMSGS''); + --UPDATE #DBCCs SET DbName = N''?'' WHERE DbName IS NULL OPTION (RECOMPILE);'; WITH DB2 AS ( SELECT DISTINCT @@ -8345,7 +8396,6 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 -- If this is Amazon RDS, use rdsadmin.dbo.rds_read_error_log IF LEFT(CAST(SERVERPROPERTY('ComputerNamePhysicalNetBIOS') AS VARCHAR(8000)), 8) = 'EC2AMAZ-' AND LEFT(CAST(SERVERPROPERTY('MachineName') AS VARCHAR(8000)), 8) = 'EC2AMAZ-' - AND LEFT(CAST(SERVERPROPERTY('ServerName') AS VARCHAR(8000)), 8) = 'EC2AMAZ-' AND db_id('rdsadmin') IS NOT NULL AND EXISTS(SELECT * FROM master.sys.all_objects WHERE name IN ('rds_startup_tasks', 'rds_help_revlogin', 'rds_hexadecimal', 'rds_failover_tracking', 'rds_database_tracking', 'rds_track_change')) BEGIN @@ -9687,7 +9737,7 @@ AS SET NOCOUNT ON; SET STATISTICS XML OFF; -SELECT @Version = '8.10', @VersionDate = '20220718'; +SELECT @Version = '8.11', @VersionDate = '20221013'; IF(@VersionCheckMode = 1) BEGIN @@ -10565,7 +10615,7 @@ AS SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.10', @VersionDate = '20220718'; + SELECT @Version = '8.11', @VersionDate = '20221013'; IF(@VersionCheckMode = 1) BEGIN @@ -12346,7 +12396,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.10', @VersionDate = '20220718'; +SELECT @Version = '8.11', @VersionDate = '20221013'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -19609,7 +19659,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.10', @VersionDate = '20220718'; +SELECT @Version = '8.11', @VersionDate = '20221013'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -25359,7 +25409,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.10', @VersionDate = '20220718'; +SELECT @Version = '8.11', @VersionDate = '20221013'; IF(@VersionCheckMode = 1) @@ -25646,7 +25696,6 @@ You need to use an Azure storage account, and the path has to look like this: ht /* WITH ROWCOUNT doesn't work on Amazon RDS - see: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2037 */ IF LEFT(CAST(SERVERPROPERTY('ComputerNamePhysicalNetBIOS') AS VARCHAR(8000)), 8) <> 'EC2AMAZ-' AND LEFT(CAST(SERVERPROPERTY('MachineName') AS VARCHAR(8000)), 8) <> 'EC2AMAZ-' - AND LEFT(CAST(SERVERPROPERTY('ServerName') AS VARCHAR(8000)), 8) <> 'EC2AMAZ-' AND db_id('rdsadmin') IS NULL BEGIN; BEGIN TRY; @@ -26094,7 +26143,6 @@ You need to use an Azure storage account, and the path has to look like this: ht IF SERVERPROPERTY('EngineEdition') NOT IN (5, 6) /* Azure SQL DB doesn't support querying jobs */ AND NOT (LEFT(CAST(SERVERPROPERTY('ComputerNamePhysicalNetBIOS') AS VARCHAR(8000)), 8) = 'EC2AMAZ-' /* Neither does Amazon RDS Express Edition */ AND LEFT(CAST(SERVERPROPERTY('MachineName') AS VARCHAR(8000)), 8) = 'EC2AMAZ-' - AND LEFT(CAST(SERVERPROPERTY('ServerName') AS VARCHAR(8000)), 8) = 'EC2AMAZ-' AND db_id('rdsadmin') IS NOT NULL AND EXISTS(SELECT * FROM master.sys.all_objects WHERE name IN ('rds_startup_tasks', 'rds_help_revlogin', 'rds_hexadecimal', 'rds_failover_tracking', 'rds_database_tracking', 'rds_track_change')) ) @@ -27175,7 +27223,7 @@ BEGIN SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.10', @VersionDate = '20220718'; + SELECT @Version = '8.11', @VersionDate = '20221013'; IF(@VersionCheckMode = 1) BEGIN @@ -28572,6 +28620,8 @@ INSERT INTO dbo.SqlServerVersions (MajorVersionNumber, MinorVersionNumber, Branch, [Url], ReleaseDate, MainstreamSupportEndDate, ExtendedSupportEndDate, MajorVersionName, MinorVersionName) VALUES (16, 600, 'CTP2', 'https://support.microsoft.com/en-us/help/5011644', '2022-05-24', '2022-05-24', '2022-05-24', 'SQL Server 2022', 'CTP 2.0'), + (15, 4261, 'CU18', 'https://support.microsoft.com/en-us/help/5017593', '2022-09-28', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 18'), + (15, 4249, 'CU17', 'https://support.microsoft.com/en-us/help/5016394', '2022-08-11', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 17'), (15, 4236, 'CU16 GDR', 'https://support.microsoft.com/en-us/help/5014353', '2022-06-14', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 16 GDR'), (15, 4223, 'CU16', 'https://support.microsoft.com/en-us/help/5011644', '2022-04-18', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 16'), (15, 4198, 'CU15', 'https://support.microsoft.com/en-us/help/5008996', '2022-01-07', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 15'), @@ -28592,6 +28642,7 @@ VALUES (15, 4003, 'CU1', 'https://support.microsoft.com/en-us/help/4527376', '2020-01-07', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 1 '), (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 '), + (14, 3456, 'RTM CU31', 'https://support.microsoft.com/en-us/help/5016884', '2022-09-20', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 31'), (14, 3451, 'RTM CU30', 'https://support.microsoft.com/en-us/help/5013756', '2022-07-13', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 30'), (14, 3445, 'RTM CU29 GDR', 'https://support.microsoft.com/en-us/help/5014553', '2022-06-14', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 29 GDR'), (14, 3436, 'RTM CU29', 'https://support.microsoft.com/en-us/help/5010786', '2022-03-31', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 29'), @@ -28984,7 +29035,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.10', @VersionDate = '20220718'; +SELECT @Version = '8.11', @VersionDate = '20221013'; IF(@VersionCheckMode = 1) BEGIN @@ -29213,7 +29264,7 @@ BEGIN END; /* IF @SinceStartup = 0 AND @Seconds > 0 AND @ExpertMode = 1 AND @OutputType <> 'NONE' - What's running right now? This is the first and last result set. */ /* Set start/finish times AFTER sp_BlitzWho runs. For more info: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2244 */ - IF @Seconds = 0 AND SERVERPROPERTY('Edition') = 'SQL Azure' + IF @Seconds = 0 AND SERVERPROPERTY('EngineEdition') = 5 /*SERVERPROPERTY('Edition') = 'SQL Azure'*/ WITH WaitTimes AS ( SELECT wait_type, wait_time_ms, NTILE(3) OVER(ORDER BY wait_time_ms) AS grouper @@ -29224,7 +29275,7 @@ BEGIN SELECT @StartSampleTime = DATEADD(mi, AVG(-wait_time_ms / 1000 / 60), SYSDATETIMEOFFSET()), @FinishSampleTime = SYSDATETIMEOFFSET() FROM WaitTimes WHERE grouper = 2; - ELSE IF @Seconds = 0 AND SERVERPROPERTY('Edition') <> 'SQL Azure' + ELSE IF @Seconds = 0 AND SERVERPROPERTY('EngineEdition') <> 5 /*SERVERPROPERTY('Edition') <> 'SQL Azure'*/ SELECT @StartSampleTime = DATEADD(MINUTE,DATEDIFF(MINUTE, GETDATE(), GETUTCDATE()),create_date) , @FinishSampleTime = SYSDATETIMEOFFSET() FROM sys.databases WHERE database_id = 2; @@ -29977,7 +30028,7 @@ BEGIN DROP TABLE #MasterFiles; CREATE TABLE #MasterFiles (database_id INT, file_id INT, type_desc NVARCHAR(50), name NVARCHAR(255), physical_name NVARCHAR(255), size BIGINT); /* Azure SQL Database doesn't have sys.master_files, so we have to build our own. */ - IF ((SERVERPROPERTY('Edition')) = 'SQL Azure' + IF (SERVERPROPERTY('EngineEdition') = 5 /*(SERVERPROPERTY('Edition')) = 'SQL Azure' */ AND (OBJECT_ID('sys.master_files') IS NULL)) SET @StringToExecute = 'INSERT INTO #MasterFiles (database_id, file_id, type_desc, name, physical_name, size) SELECT DB_ID(), file_id, type_desc, name, physical_name, size FROM sys.database_files;'; ELSE @@ -30069,7 +30120,7 @@ BEGIN @StockDetailsFooter = @StockDetailsFooter + @LineFeed + ' -- ?>'; /* Get the instance name to use as a Perfmon counter prefix. */ - IF CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) = 'SQL Azure' + IF SERVERPROPERTY('EngineEdition') = 5 /*CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) = 'SQL Azure'*/ SELECT TOP 1 @ServiceName = LEFT(object_name, (CHARINDEX(':', object_name) - 1)) FROM sys.dm_os_performance_counters; ELSE @@ -30338,7 +30389,7 @@ BEGIN CASE @Seconds WHEN 0 THEN 0 ELSE SUM(os.signal_wait_time_ms) OVER (PARTITION BY os.wait_type ) END AS sum_signal_wait_time_ms, CASE @Seconds WHEN 0 THEN 0 ELSE SUM(os.waiting_tasks_count) OVER (PARTITION BY os.wait_type) END AS sum_waiting_tasks '; - IF SERVERPROPERTY('Edition') = 'SQL Azure' + IF SERVERPROPERTY('EngineEdition') = 5 /*SERVERPROPERTY('Edition') = 'SQL Azure'*/ SET @StringToExecute = @StringToExecute + N' FROM sys.dm_db_wait_stats os '; ELSE SET @StringToExecute = @StringToExecute + N' FROM sys.dm_os_wait_stats os '; @@ -30489,7 +30540,7 @@ BEGIN END /* If there's a backup running, add details explaining how long full backup has been taking in the last month. */ - IF @Seconds > 0 AND CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) <> 'SQL Azure' + IF @Seconds > 0 AND SERVERPROPERTY('EngineEdition') <> 5 /*CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) <> 'SQL Azure'*/ BEGIN SET @StringToExecute = 'UPDATE #BlitzFirstResults SET Details = Details + '' Over the last 60 days, the full backup usually takes '' + CAST((SELECT AVG(DATEDIFF(mi, bs.backup_start_date, bs.backup_finish_date)) FROM msdb.dbo.backupset bs WHERE abr.DatabaseName = bs.database_name AND bs.type = ''D'' AND bs.backup_start_date > DATEADD(dd, -60, SYSDATETIMEOFFSET()) AND bs.backup_finish_date IS NOT NULL) AS NVARCHAR(100)) + '' minutes.'' FROM #BlitzFirstResults abr WHERE abr.CheckID = 1 AND EXISTS (SELECT * FROM msdb.dbo.backupset bs WHERE bs.type = ''D'' AND bs.backup_start_date > DATEADD(dd, -60, SYSDATETIMEOFFSET()) AND bs.backup_finish_date IS NOT NULL AND abr.DatabaseName = bs.database_name AND DATEDIFF(mi, bs.backup_start_date, bs.backup_finish_date) > 1)'; EXEC(@StringToExecute); @@ -30617,7 +30668,7 @@ BEGIN END /* Query Problems - Long-Running Query Blocking Others - CheckID 5 */ - IF SERVERPROPERTY('Edition') <> 'SQL Azure' AND @Seconds > 0 AND EXISTS(SELECT * FROM sys.dm_os_waiting_tasks WHERE wait_type LIKE 'LCK%' AND wait_duration_ms > 30000) + IF SERVERPROPERTY('EngineEdition') <> 5 /*SERVERPROPERTY('Edition') <> 'SQL Azure'*/ AND @Seconds > 0 AND EXISTS(SELECT * FROM sys.dm_os_waiting_tasks WHERE wait_type LIKE 'LCK%' AND wait_duration_ms > 30000) BEGIN IF (@Debug = 1) BEGIN @@ -30877,7 +30928,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, AND CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) NOT LIKE '%Standard%'; /* Server Performance - Target Memory Lower Than Max - CheckID 35 */ - IF SERVERPROPERTY('Edition') <> 'SQL Azure' + IF SERVERPROPERTY('EngineEdition') <> 5 /*SERVERPROPERTY('Edition') <> 'SQL Azure'*/ BEGIN IF (@Debug = 1) BEGIN @@ -31325,7 +31376,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, RAISERROR('Running CheckID 23',10,1) WITH NOWAIT; END - IF SERVERPROPERTY('Edition') <> 'SQL Azure' + IF SERVERPROPERTY('EngineEdition') <> 5 /*SERVERPROPERTY('Edition') <> 'SQL Azure'*/ WITH y AS ( @@ -31404,12 +31455,12 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, /* We don't want to hang around to obtain locks */ SET LOCK_TIMEOUT 0; - IF SERVERPROPERTY('Edition') <> 'SQL Azure' + IF SERVERPROPERTY('EngineEdition') <> 5 /*SERVERPROPERTY('Edition') <> 'SQL Azure'*/ SET @StringToExecute = N'USE [?];' + @LineFeed; ELSE SET @StringToExecute = N''; - SET @StringToExecute = @StringToExecute + 'USE [?]; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; SET LOCK_TIMEOUT 1000;' + @LineFeed + + SET @StringToExecute = @StringToExecute + 'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; SET LOCK_TIMEOUT 1000;' + @LineFeed + 'BEGIN TRY' + @LineFeed + ' INSERT INTO #UpdatedStats(HowToStopIt, RowsForSorting)' + @LineFeed + ' SELECT HowToStopIt = ' + @LineFeed + @@ -31453,7 +31504,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 'END CATCH' ; - IF SERVERPROPERTY('Edition') <> 'SQL Azure' + IF SERVERPROPERTY('EngineEdition') <> 5 /*SERVERPROPERTY('Edition') <> 'SQL Azure'*/ EXEC sp_MSforeachdb @StringToExecute; ELSE EXEC(@StringToExecute); @@ -31533,7 +31584,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, SUM(os.signal_wait_time_ms) OVER (PARTITION BY os.wait_type ) AS sum_signal_wait_time_ms, SUM(os.waiting_tasks_count) OVER (PARTITION BY os.wait_type) AS sum_waiting_tasks '; - IF SERVERPROPERTY('Edition') = 'SQL Azure' + IF SERVERPROPERTY('EngineEdition') = 5 /*SERVERPROPERTY('Edition') = 'SQL Azure'*/ SET @StringToExecute = @StringToExecute + N' FROM sys.dm_db_wait_stats os '; ELSE SET @StringToExecute = @StringToExecute + N' FROM sys.dm_os_wait_stats os '; @@ -32160,7 +32211,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, AND ps.value_delta > (10 * @Seconds); /* Ignore servers sitting idle */ /* Azure Performance - Database is Maxed Out - CheckID 41 */ - IF SERVERPROPERTY('Edition') = 'SQL Azure' + IF SERVERPROPERTY('EngineEdition') = 5 /*SERVERPROPERTY('Edition') = 'SQL Azure'*/ BEGIN IF (@Debug = 1) BEGIN diff --git a/Install-Core-Blitz-With-Query-Store.sql b/Install-Core-Blitz-With-Query-Store.sql index 8a9d7dc47..4fa480bb3 100644 --- a/Install-Core-Blitz-With-Query-Store.sql +++ b/Install-Core-Blitz-With-Query-Store.sql @@ -38,7 +38,7 @@ AS SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.10', @VersionDate = '20220718'; + SELECT @Version = '8.11', @VersionDate = '20221013'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -425,7 +425,6 @@ AS /* If the server is Amazon RDS, skip checks that it doesn't allow */ IF LEFT(CAST(SERVERPROPERTY('ComputerNamePhysicalNetBIOS') AS VARCHAR(8000)), 8) = 'EC2AMAZ-' AND LEFT(CAST(SERVERPROPERTY('MachineName') AS VARCHAR(8000)), 8) = 'EC2AMAZ-' - AND LEFT(CAST(SERVERPROPERTY('ServerName') AS VARCHAR(8000)), 8) = 'EC2AMAZ-' AND db_id('rdsadmin') IS NOT NULL AND EXISTS(SELECT * FROM master.sys.all_objects WHERE name IN ('rds_startup_tasks', 'rds_help_revlogin', 'rds_hexadecimal', 'rds_failover_tracking', 'rds_database_tracking', 'rds_track_change')) BEGIN @@ -888,6 +887,40 @@ AS ) BEGIN + /* + Extract DBCC DBINFO data from the server. This data is used for check 2 using + the dbi_LastLogBackupTime field and check 68 using the dbi_LastKnownGood field. + NB: DBCC DBINFO is not available on AWS RDS databases so if the server is RDS + (which will have previously triggered inserting a checkID 223 record) and at + least one of the relevant checks is not being skipped then we can extract the + dbinfo information. + */ + IF NOT EXISTS ( SELECT 1 + FROM #BlitzResults + WHERE CheckID = 223 AND URL = 'https://aws.amazon.com/rds/sqlserver/') + AND ( + NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 2 ) + OR NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 68 ) + ) + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Extracting DBCC DBINFO data (used in checks 2 and 68).', 0, 1, 68) WITH NOWAIT; + + EXEC sp_MSforeachdb N'USE [?]; + SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + INSERT #DBCCs + (ParentObject, + Object, + Field, + Value) + EXEC (''DBCC DBInfo() With TableResults, NO_INFOMSGS''); + UPDATE #DBCCs SET DbName = N''?'' WHERE DbName IS NULL OPTION (RECOMPILE);'; + END + /* Our very first check! We'll put more comments in this one just to explain exactly how it works. First, we check to see if we're @@ -1033,6 +1066,7 @@ AS 'https://www.brentozar.com/go/biglogs' AS URL , ( 'The ' + CAST(CAST((SELECT ((SUM([mf].[size]) * 8.) / 1024.) FROM sys.[master_files] AS [mf] WHERE [mf].[database_id] = d.[database_id] AND [mf].[type_desc] = 'LOG') AS DECIMAL(18,2)) AS VARCHAR(30)) + 'MB log file has not been backed up in the last week.' ) AS Details FROM master.sys.databases d + LEFT JOIN #DBCCs ll On ll.DbName = d.name And ll.Field = 'dbi_LastLogBackupTime' WHERE d.recovery_model IN ( 1, 2 ) AND d.database_id NOT IN ( 2, 3 ) AND d.source_database_id IS NULL @@ -1043,12 +1077,23 @@ AS DatabaseName FROM #SkipChecks WHERE CheckID IS NULL OR CheckID = 2) - AND NOT EXISTS ( SELECT * - FROM msdb.dbo.backupset b - WHERE d.name COLLATE SQL_Latin1_General_CP1_CI_AS = b.database_name COLLATE SQL_Latin1_General_CP1_CI_AS - AND b.type = 'L' - AND b.backup_finish_date >= DATEADD(dd, - -7, GETDATE()) ); + AND ( + ( + /* We couldn't get a value from the DBCC DBINFO data so let's check the msdb backup history information */ + [ll].[Value] Is Null + AND NOT EXISTS ( SELECT * + FROM msdb.dbo.backupset b + WHERE d.name COLLATE SQL_Latin1_General_CP1_CI_AS = b.database_name COLLATE SQL_Latin1_General_CP1_CI_AS + AND b.type = 'L' + AND b.backup_finish_date >= DATEADD(dd,-7, GETDATE()) + ) + ) + OR + ( + Convert(datetime,ll.Value) < DATEADD(dd,-7, GETDATE()) + ) + + ); END; /* @@ -5472,11 +5517,16 @@ IF @ProductVersionMajor >= 10 'https://support.microsoft.com/en-us/kb/2033238' AS [URL] , ( COALESCE(company, '') + ' - ' + COALESCE(description, '') + ' - ' + COALESCE(name, '') + ' - suspected dangerous third party module is installed.') AS [Details] FROM sys.dm_os_loaded_modules - WHERE UPPER(name) LIKE UPPER('%\ENTAPI.DLL') /* McAfee VirusScan Enterprise */ + WHERE UPPER(name) LIKE UPPER('%\ENTAPI.DLL') OR UPPER(name) LIKE '%MFEBOPK.SYS' /* McAfee VirusScan Enterprise */ OR UPPER(name) LIKE UPPER('%\HIPI.DLL') OR UPPER(name) LIKE UPPER('%\HcSQL.dll') OR UPPER(name) LIKE UPPER('%\HcApi.dll') OR UPPER(name) LIKE UPPER('%\HcThe.dll') /* McAfee Host Intrusion */ OR UPPER(name) LIKE UPPER('%\SOPHOS_DETOURED.DLL') OR UPPER(name) LIKE UPPER('%\SOPHOS_DETOURED_x64.DLL') OR UPPER(name) LIKE UPPER('%\SWI_IFSLSP_64.dll') OR UPPER(name) LIKE UPPER('%\SOPHOS~%.dll') /* Sophos AV */ - OR UPPER(name) LIKE UPPER('%\PIOLEDB.DLL') OR UPPER(name) LIKE UPPER('%\PISDK.DLL'); /* OSISoft PI data access */ - + OR UPPER(name) LIKE UPPER('%\PIOLEDB.DLL') OR UPPER(name) LIKE UPPER('%\PISDK.DLL') /* OSISoft PI data access */ + OR UPPER(name) LIKE UPPER('%ScriptControl%.dll') OR UPPER(name) LIKE UPPER('%umppc%.dll') /* CrowdStrike */ + OR UPPER(name) LIKE UPPER('%perfiCrcPerfMonMgr.DLL') /* Trend Micro OfficeScan */ + OR UPPER(name) LIKE UPPER('%NLEMSQL.SYS') /* NetLib Encryptionizer-Software. */ + OR UPPER(name) LIKE UPPER('%MFETDIK.SYS') /* McAfee Anti-Virus Mini-Firewall */ + OR UPPER(name) LIKE UPPER('%ANTIVIRUS%'); /* To pick up sqlmaggieAntiVirus_64.dll (malware) or anything else labelled AntiVirus */ + /* MS docs link for blacklisted modules: https://learn.microsoft.com/en-us/troubleshoot/sql/performance/performance-consistency-issues-filter-drivers-modules */ END; /*Find shrink database tasks*/ @@ -7286,15 +7336,16 @@ IF @ProductVersionMajor >= 10 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 68) WITH NOWAIT; - EXEC sp_MSforeachdb N'USE [?]; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - INSERT #DBCCs - (ParentObject, - Object, - Field, - Value) - EXEC (''DBCC DBInfo() With TableResults, NO_INFOMSGS''); - UPDATE #DBCCs SET DbName = N''?'' WHERE DbName IS NULL OPTION (RECOMPILE);'; + /* Removed as populating the #DBCCs table now done in advance as data is uses for multiple checks*/ + --EXEC sp_MSforeachdb N'USE [?]; + --SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + --INSERT #DBCCs + -- (ParentObject, + -- Object, + -- Field, + -- Value) + --EXEC (''DBCC DBInfo() With TableResults, NO_INFOMSGS''); + --UPDATE #DBCCs SET DbName = N''?'' WHERE DbName IS NULL OPTION (RECOMPILE);'; WITH DB2 AS ( SELECT DISTINCT @@ -8345,7 +8396,6 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 -- If this is Amazon RDS, use rdsadmin.dbo.rds_read_error_log IF LEFT(CAST(SERVERPROPERTY('ComputerNamePhysicalNetBIOS') AS VARCHAR(8000)), 8) = 'EC2AMAZ-' AND LEFT(CAST(SERVERPROPERTY('MachineName') AS VARCHAR(8000)), 8) = 'EC2AMAZ-' - AND LEFT(CAST(SERVERPROPERTY('ServerName') AS VARCHAR(8000)), 8) = 'EC2AMAZ-' AND db_id('rdsadmin') IS NOT NULL AND EXISTS(SELECT * FROM master.sys.all_objects WHERE name IN ('rds_startup_tasks', 'rds_help_revlogin', 'rds_hexadecimal', 'rds_failover_tracking', 'rds_database_tracking', 'rds_track_change')) BEGIN @@ -9687,7 +9737,7 @@ AS SET NOCOUNT ON; SET STATISTICS XML OFF; -SELECT @Version = '8.10', @VersionDate = '20220718'; +SELECT @Version = '8.11', @VersionDate = '20221013'; IF(@VersionCheckMode = 1) BEGIN @@ -10565,7 +10615,7 @@ AS SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.10', @VersionDate = '20220718'; + SELECT @Version = '8.11', @VersionDate = '20221013'; IF(@VersionCheckMode = 1) BEGIN @@ -12346,7 +12396,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.10', @VersionDate = '20220718'; +SELECT @Version = '8.11', @VersionDate = '20221013'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -19609,7 +19659,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.10', @VersionDate = '20220718'; +SELECT @Version = '8.11', @VersionDate = '20221013'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -25359,7 +25409,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.10', @VersionDate = '20220718'; +SELECT @Version = '8.11', @VersionDate = '20221013'; IF(@VersionCheckMode = 1) @@ -25646,7 +25696,6 @@ You need to use an Azure storage account, and the path has to look like this: ht /* WITH ROWCOUNT doesn't work on Amazon RDS - see: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2037 */ IF LEFT(CAST(SERVERPROPERTY('ComputerNamePhysicalNetBIOS') AS VARCHAR(8000)), 8) <> 'EC2AMAZ-' AND LEFT(CAST(SERVERPROPERTY('MachineName') AS VARCHAR(8000)), 8) <> 'EC2AMAZ-' - AND LEFT(CAST(SERVERPROPERTY('ServerName') AS VARCHAR(8000)), 8) <> 'EC2AMAZ-' AND db_id('rdsadmin') IS NULL BEGIN; BEGIN TRY; @@ -26094,7 +26143,6 @@ You need to use an Azure storage account, and the path has to look like this: ht IF SERVERPROPERTY('EngineEdition') NOT IN (5, 6) /* Azure SQL DB doesn't support querying jobs */ AND NOT (LEFT(CAST(SERVERPROPERTY('ComputerNamePhysicalNetBIOS') AS VARCHAR(8000)), 8) = 'EC2AMAZ-' /* Neither does Amazon RDS Express Edition */ AND LEFT(CAST(SERVERPROPERTY('MachineName') AS VARCHAR(8000)), 8) = 'EC2AMAZ-' - AND LEFT(CAST(SERVERPROPERTY('ServerName') AS VARCHAR(8000)), 8) = 'EC2AMAZ-' AND db_id('rdsadmin') IS NOT NULL AND EXISTS(SELECT * FROM master.sys.all_objects WHERE name IN ('rds_startup_tasks', 'rds_help_revlogin', 'rds_hexadecimal', 'rds_failover_tracking', 'rds_database_tracking', 'rds_track_change')) ) @@ -27199,7 +27247,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.10', @VersionDate = '20220718'; +SELECT @Version = '8.11', @VersionDate = '20221013'; IF(@VersionCheckMode = 1) BEGIN RETURN; @@ -27347,12 +27395,12 @@ IF ( SELECT COUNT(*) /*Making sure your databases are using QDS.*/ RAISERROR('Checking database validity', 0, 1) WITH NOWAIT; -IF (@is_azure_db = 1) +IF (@is_azure_db = 1 AND SERVERPROPERTY ('ENGINEEDITION') <> 8) SET @DatabaseName = DB_NAME(); ELSE BEGIN - /*If we're on Azure we don't need to check all this @DatabaseName stuff...*/ + /*If we're on Azure SQL DB we don't need to check all this @DatabaseName stuff...*/ SET @DatabaseName = LTRIM(RTRIM(@DatabaseName)); @@ -32930,7 +32978,7 @@ BEGIN SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.10', @VersionDate = '20220718'; + SELECT @Version = '8.11', @VersionDate = '20221013'; IF(@VersionCheckMode = 1) BEGIN @@ -34327,6 +34375,8 @@ INSERT INTO dbo.SqlServerVersions (MajorVersionNumber, MinorVersionNumber, Branch, [Url], ReleaseDate, MainstreamSupportEndDate, ExtendedSupportEndDate, MajorVersionName, MinorVersionName) VALUES (16, 600, 'CTP2', 'https://support.microsoft.com/en-us/help/5011644', '2022-05-24', '2022-05-24', '2022-05-24', 'SQL Server 2022', 'CTP 2.0'), + (15, 4261, 'CU18', 'https://support.microsoft.com/en-us/help/5017593', '2022-09-28', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 18'), + (15, 4249, 'CU17', 'https://support.microsoft.com/en-us/help/5016394', '2022-08-11', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 17'), (15, 4236, 'CU16 GDR', 'https://support.microsoft.com/en-us/help/5014353', '2022-06-14', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 16 GDR'), (15, 4223, 'CU16', 'https://support.microsoft.com/en-us/help/5011644', '2022-04-18', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 16'), (15, 4198, 'CU15', 'https://support.microsoft.com/en-us/help/5008996', '2022-01-07', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 15'), @@ -34347,6 +34397,7 @@ VALUES (15, 4003, 'CU1', 'https://support.microsoft.com/en-us/help/4527376', '2020-01-07', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 1 '), (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 '), + (14, 3456, 'RTM CU31', 'https://support.microsoft.com/en-us/help/5016884', '2022-09-20', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 31'), (14, 3451, 'RTM CU30', 'https://support.microsoft.com/en-us/help/5013756', '2022-07-13', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 30'), (14, 3445, 'RTM CU29 GDR', 'https://support.microsoft.com/en-us/help/5014553', '2022-06-14', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 29 GDR'), (14, 3436, 'RTM CU29', 'https://support.microsoft.com/en-us/help/5010786', '2022-03-31', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 29'), @@ -34739,7 +34790,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.10', @VersionDate = '20220718'; +SELECT @Version = '8.11', @VersionDate = '20221013'; IF(@VersionCheckMode = 1) BEGIN @@ -34968,7 +35019,7 @@ BEGIN END; /* IF @SinceStartup = 0 AND @Seconds > 0 AND @ExpertMode = 1 AND @OutputType <> 'NONE' - What's running right now? This is the first and last result set. */ /* Set start/finish times AFTER sp_BlitzWho runs. For more info: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2244 */ - IF @Seconds = 0 AND SERVERPROPERTY('Edition') = 'SQL Azure' + IF @Seconds = 0 AND SERVERPROPERTY('EngineEdition') = 5 /*SERVERPROPERTY('Edition') = 'SQL Azure'*/ WITH WaitTimes AS ( SELECT wait_type, wait_time_ms, NTILE(3) OVER(ORDER BY wait_time_ms) AS grouper @@ -34979,7 +35030,7 @@ BEGIN SELECT @StartSampleTime = DATEADD(mi, AVG(-wait_time_ms / 1000 / 60), SYSDATETIMEOFFSET()), @FinishSampleTime = SYSDATETIMEOFFSET() FROM WaitTimes WHERE grouper = 2; - ELSE IF @Seconds = 0 AND SERVERPROPERTY('Edition') <> 'SQL Azure' + ELSE IF @Seconds = 0 AND SERVERPROPERTY('EngineEdition') <> 5 /*SERVERPROPERTY('Edition') <> 'SQL Azure'*/ SELECT @StartSampleTime = DATEADD(MINUTE,DATEDIFF(MINUTE, GETDATE(), GETUTCDATE()),create_date) , @FinishSampleTime = SYSDATETIMEOFFSET() FROM sys.databases WHERE database_id = 2; @@ -35732,7 +35783,7 @@ BEGIN DROP TABLE #MasterFiles; CREATE TABLE #MasterFiles (database_id INT, file_id INT, type_desc NVARCHAR(50), name NVARCHAR(255), physical_name NVARCHAR(255), size BIGINT); /* Azure SQL Database doesn't have sys.master_files, so we have to build our own. */ - IF ((SERVERPROPERTY('Edition')) = 'SQL Azure' + IF (SERVERPROPERTY('EngineEdition') = 5 /*(SERVERPROPERTY('Edition')) = 'SQL Azure' */ AND (OBJECT_ID('sys.master_files') IS NULL)) SET @StringToExecute = 'INSERT INTO #MasterFiles (database_id, file_id, type_desc, name, physical_name, size) SELECT DB_ID(), file_id, type_desc, name, physical_name, size FROM sys.database_files;'; ELSE @@ -35824,7 +35875,7 @@ BEGIN @StockDetailsFooter = @StockDetailsFooter + @LineFeed + ' -- ?>'; /* Get the instance name to use as a Perfmon counter prefix. */ - IF CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) = 'SQL Azure' + IF SERVERPROPERTY('EngineEdition') = 5 /*CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) = 'SQL Azure'*/ SELECT TOP 1 @ServiceName = LEFT(object_name, (CHARINDEX(':', object_name) - 1)) FROM sys.dm_os_performance_counters; ELSE @@ -36093,7 +36144,7 @@ BEGIN CASE @Seconds WHEN 0 THEN 0 ELSE SUM(os.signal_wait_time_ms) OVER (PARTITION BY os.wait_type ) END AS sum_signal_wait_time_ms, CASE @Seconds WHEN 0 THEN 0 ELSE SUM(os.waiting_tasks_count) OVER (PARTITION BY os.wait_type) END AS sum_waiting_tasks '; - IF SERVERPROPERTY('Edition') = 'SQL Azure' + IF SERVERPROPERTY('EngineEdition') = 5 /*SERVERPROPERTY('Edition') = 'SQL Azure'*/ SET @StringToExecute = @StringToExecute + N' FROM sys.dm_db_wait_stats os '; ELSE SET @StringToExecute = @StringToExecute + N' FROM sys.dm_os_wait_stats os '; @@ -36244,7 +36295,7 @@ BEGIN END /* If there's a backup running, add details explaining how long full backup has been taking in the last month. */ - IF @Seconds > 0 AND CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) <> 'SQL Azure' + IF @Seconds > 0 AND SERVERPROPERTY('EngineEdition') <> 5 /*CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) <> 'SQL Azure'*/ BEGIN SET @StringToExecute = 'UPDATE #BlitzFirstResults SET Details = Details + '' Over the last 60 days, the full backup usually takes '' + CAST((SELECT AVG(DATEDIFF(mi, bs.backup_start_date, bs.backup_finish_date)) FROM msdb.dbo.backupset bs WHERE abr.DatabaseName = bs.database_name AND bs.type = ''D'' AND bs.backup_start_date > DATEADD(dd, -60, SYSDATETIMEOFFSET()) AND bs.backup_finish_date IS NOT NULL) AS NVARCHAR(100)) + '' minutes.'' FROM #BlitzFirstResults abr WHERE abr.CheckID = 1 AND EXISTS (SELECT * FROM msdb.dbo.backupset bs WHERE bs.type = ''D'' AND bs.backup_start_date > DATEADD(dd, -60, SYSDATETIMEOFFSET()) AND bs.backup_finish_date IS NOT NULL AND abr.DatabaseName = bs.database_name AND DATEDIFF(mi, bs.backup_start_date, bs.backup_finish_date) > 1)'; EXEC(@StringToExecute); @@ -36372,7 +36423,7 @@ BEGIN END /* Query Problems - Long-Running Query Blocking Others - CheckID 5 */ - IF SERVERPROPERTY('Edition') <> 'SQL Azure' AND @Seconds > 0 AND EXISTS(SELECT * FROM sys.dm_os_waiting_tasks WHERE wait_type LIKE 'LCK%' AND wait_duration_ms > 30000) + IF SERVERPROPERTY('EngineEdition') <> 5 /*SERVERPROPERTY('Edition') <> 'SQL Azure'*/ AND @Seconds > 0 AND EXISTS(SELECT * FROM sys.dm_os_waiting_tasks WHERE wait_type LIKE 'LCK%' AND wait_duration_ms > 30000) BEGIN IF (@Debug = 1) BEGIN @@ -36632,7 +36683,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, AND CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) NOT LIKE '%Standard%'; /* Server Performance - Target Memory Lower Than Max - CheckID 35 */ - IF SERVERPROPERTY('Edition') <> 'SQL Azure' + IF SERVERPROPERTY('EngineEdition') <> 5 /*SERVERPROPERTY('Edition') <> 'SQL Azure'*/ BEGIN IF (@Debug = 1) BEGIN @@ -37080,7 +37131,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, RAISERROR('Running CheckID 23',10,1) WITH NOWAIT; END - IF SERVERPROPERTY('Edition') <> 'SQL Azure' + IF SERVERPROPERTY('EngineEdition') <> 5 /*SERVERPROPERTY('Edition') <> 'SQL Azure'*/ WITH y AS ( @@ -37159,12 +37210,12 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, /* We don't want to hang around to obtain locks */ SET LOCK_TIMEOUT 0; - IF SERVERPROPERTY('Edition') <> 'SQL Azure' + IF SERVERPROPERTY('EngineEdition') <> 5 /*SERVERPROPERTY('Edition') <> 'SQL Azure'*/ SET @StringToExecute = N'USE [?];' + @LineFeed; ELSE SET @StringToExecute = N''; - SET @StringToExecute = @StringToExecute + 'USE [?]; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; SET LOCK_TIMEOUT 1000;' + @LineFeed + + SET @StringToExecute = @StringToExecute + 'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; SET LOCK_TIMEOUT 1000;' + @LineFeed + 'BEGIN TRY' + @LineFeed + ' INSERT INTO #UpdatedStats(HowToStopIt, RowsForSorting)' + @LineFeed + ' SELECT HowToStopIt = ' + @LineFeed + @@ -37208,7 +37259,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 'END CATCH' ; - IF SERVERPROPERTY('Edition') <> 'SQL Azure' + IF SERVERPROPERTY('EngineEdition') <> 5 /*SERVERPROPERTY('Edition') <> 'SQL Azure'*/ EXEC sp_MSforeachdb @StringToExecute; ELSE EXEC(@StringToExecute); @@ -37288,7 +37339,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, SUM(os.signal_wait_time_ms) OVER (PARTITION BY os.wait_type ) AS sum_signal_wait_time_ms, SUM(os.waiting_tasks_count) OVER (PARTITION BY os.wait_type) AS sum_waiting_tasks '; - IF SERVERPROPERTY('Edition') = 'SQL Azure' + IF SERVERPROPERTY('EngineEdition') = 5 /*SERVERPROPERTY('Edition') = 'SQL Azure'*/ SET @StringToExecute = @StringToExecute + N' FROM sys.dm_db_wait_stats os '; ELSE SET @StringToExecute = @StringToExecute + N' FROM sys.dm_os_wait_stats os '; @@ -37915,7 +37966,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, AND ps.value_delta > (10 * @Seconds); /* Ignore servers sitting idle */ /* Azure Performance - Database is Maxed Out - CheckID 41 */ - IF SERVERPROPERTY('Edition') = 'SQL Azure' + IF SERVERPROPERTY('EngineEdition') = 5 /*SERVERPROPERTY('Edition') = 'SQL Azure'*/ BEGIN IF (@Debug = 1) BEGIN diff --git a/sp_AllNightLog.sql b/sp_AllNightLog.sql index a60ea3001..29ae2e959 100644 --- a/sp_AllNightLog.sql +++ b/sp_AllNightLog.sql @@ -31,7 +31,7 @@ SET STATISTICS XML OFF; BEGIN; -SELECT @Version = '8.10', @VersionDate = '20220718'; +SELECT @Version = '8.11', @VersionDate = '20221013'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_AllNightLog_Setup.sql b/sp_AllNightLog_Setup.sql index cedbdde3e..cafb44392 100644 --- a/sp_AllNightLog_Setup.sql +++ b/sp_AllNightLog_Setup.sql @@ -38,7 +38,7 @@ SET STATISTICS XML OFF; BEGIN; -SELECT @Version = '8.10', @VersionDate = '20220718'; +SELECT @Version = '8.11', @VersionDate = '20221013'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_Blitz.sql b/sp_Blitz.sql index a788b0899..49e4e53bd 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -38,7 +38,7 @@ AS SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.10', @VersionDate = '20220718'; + SELECT @Version = '8.11', @VersionDate = '20221013'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) diff --git a/sp_BlitzAnalysis.sql b/sp_BlitzAnalysis.sql index 08e4769ae..0e81a263d 100644 --- a/sp_BlitzAnalysis.sql +++ b/sp_BlitzAnalysis.sql @@ -37,7 +37,7 @@ AS SET NOCOUNT ON; SET STATISTICS XML OFF; -SELECT @Version = '8.10', @VersionDate = '20220718'; +SELECT @Version = '8.11', @VersionDate = '20221013'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_BlitzBackups.sql b/sp_BlitzBackups.sql index 7dc1674a6..3ab03dee8 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.10', @VersionDate = '20220718'; + SELECT @Version = '8.11', @VersionDate = '20221013'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_BlitzCache.sql b/sp_BlitzCache.sql index 9f591ef6a..5993b4906 100644 --- a/sp_BlitzCache.sql +++ b/sp_BlitzCache.sql @@ -280,7 +280,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.10', @VersionDate = '20220718'; +SELECT @Version = '8.11', @VersionDate = '20221013'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) diff --git a/sp_BlitzFirst.sql b/sp_BlitzFirst.sql index 3054818e2..6c34c761c 100644 --- a/sp_BlitzFirst.sql +++ b/sp_BlitzFirst.sql @@ -46,7 +46,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.10', @VersionDate = '20220718'; +SELECT @Version = '8.11', @VersionDate = '20221013'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_BlitzInMemoryOLTP.sql b/sp_BlitzInMemoryOLTP.sql index 28f8d3c5b..192150ddf 100644 --- a/sp_BlitzInMemoryOLTP.sql +++ b/sp_BlitzInMemoryOLTP.sql @@ -82,7 +82,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI */ AS DECLARE @ScriptVersion VARCHAR(30); -SELECT @ScriptVersion = '1.8', @VersionDate = '202204718'; +SELECT @ScriptVersion = '1.8', @VersionDate = '20221013'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index faf9d2b1e..729b2ec17 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -48,7 +48,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.10', @VersionDate = '20220718'; +SELECT @Version = '8.11', @VersionDate = '20221013'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index c13d713c2..fef2f4510 100644 --- a/sp_BlitzLock.sql +++ b/sp_BlitzLock.sql @@ -33,7 +33,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.10', @VersionDate = '20220718'; +SELECT @Version = '8.11', @VersionDate = '20221013'; IF(@VersionCheckMode = 1) diff --git a/sp_BlitzQueryStore.sql b/sp_BlitzQueryStore.sql index 7b8f77c2b..7baa05f13 100644 --- a/sp_BlitzQueryStore.sql +++ b/sp_BlitzQueryStore.sql @@ -57,7 +57,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.10', @VersionDate = '20220718'; +SELECT @Version = '8.11', @VersionDate = '20221013'; IF(@VersionCheckMode = 1) BEGIN RETURN; diff --git a/sp_BlitzWho.sql b/sp_BlitzWho.sql index 6021cdb34..8fa278823 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.10', @VersionDate = '20220718'; + SELECT @Version = '8.11', @VersionDate = '20221013'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_DatabaseRestore.sql b/sp_DatabaseRestore.sql index 262304090..b63aac6d3 100755 --- a/sp_DatabaseRestore.sql +++ b/sp_DatabaseRestore.sql @@ -42,7 +42,7 @@ SET STATISTICS XML OFF; /*Versioning details*/ -SELECT @Version = '8.10', @VersionDate = '20220718'; +SELECT @Version = '8.11', @VersionDate = '20221013'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_ineachdb.sql b/sp_ineachdb.sql index a162bb682..f4b7cde04 100644 --- a/sp_ineachdb.sql +++ b/sp_ineachdb.sql @@ -35,7 +35,7 @@ BEGIN SET NOCOUNT ON; SET STATISTICS XML OFF; - SELECT @Version = '8.10', @VersionDate = '20220718'; + SELECT @Version = '8.11', @VersionDate = '20221013'; IF(@VersionCheckMode = 1) BEGIN From 2c2d8eb03667cb4376cfbefd27cb4b2ac97a8f7d Mon Sep 17 00:00:00 2001 From: Tor-Erik Hagen Date: Sun, 23 Oct 2022 02:45:16 +0200 Subject: [PATCH 322/662] Implemented output support for remaining modes in sp_blitzIndex --- sp_BlitzIndex.sql | 904 ++++++++++++++++++++++++++++++++++------------ 1 file changed, 664 insertions(+), 240 deletions(-) diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index 729b2ec17..5e21926d6 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -159,25 +159,41 @@ SELECT END; RAISERROR(N'Starting run. %s', 0,1, @ScriptVersionName) WITH NOWAIT; - + + IF(@OutputType NOT IN ('TABLE','NONE')) BEGIN RAISERROR('Invalid value for parameter @OutputType. Expected: (TABLE;NONE)',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; + SET @OutputType = 'NONE' +END; IF(@OutputType = 'NONE') BEGIN + + IF ((@OutputServerName IS NOT NULL) AND (@OutputTableName IS NULL OR @OutputSchemaName IS NULL OR @OutputDatabaseName IS NULL)) + BEGIN + RAISERROR('Parameter @OutputServerName is specified, rest of @Output* parameters needs to also be specified',12,1); + RETURN; + END; + IF(@OutputTableName IS NULL OR @OutputSchemaName IS NULL OR @OutputDatabaseName IS NULL) BEGIN - RAISERROR('This procedure should be called with a value for @Output* parameters, as @OutputType is set to NONE',12,1); + RAISERROR('This procedure should be called with a value for @OutputTableName, @OutputSchemaName and @OutputDatabaseName parameters, as @OutputType is set to NONE',12,1); RETURN; END; + /* Output is supported for all modes, no reason to not bring pain and output IF(@BringThePain = 1) BEGIN RAISERROR('Incompatible Parameters: @BringThePain set to 1 and @OutputType set to NONE',12,1); RETURN; END; + */ /* Eventually limit by mode IF(@Mode not in (0,4)) BEGIN @@ -3022,32 +3038,142 @@ END; /* IF @TableName IS NOT NULL */ +ELSE /* @TableName IS NULL, so we operate in normal mode 0/1/2/3/4 */ +BEGIN +/* Validate and check table output params */ + /* Checks if @OutputServerName is populated with a valid linked server, and that the database name specified is valid */ + DECLARE @ValidOutputServer BIT; + DECLARE @ValidOutputLocation BIT; + DECLARE @LinkedServerDBCheck NVARCHAR(2000); + DECLARE @ValidLinkedServerDB INT; + DECLARE @tmpdbchk TABLE (cnt INT); + + IF @OutputServerName IS NOT NULL + BEGIN + IF (SUBSTRING(@OutputTableName, 2, 1) = '#') + BEGIN + RAISERROR('Due to the nature of temporary tables, outputting to a linked server requires a permanent table.', 16, 0); + END; + ELSE IF EXISTS (SELECT server_id FROM sys.servers WHERE QUOTENAME([name]) = @OutputServerName) + BEGIN + SET @LinkedServerDBCheck = 'SELECT 1 WHERE EXISTS (SELECT * FROM '+@OutputServerName+'.master.sys.databases WHERE QUOTENAME([name]) = '''+@OutputDatabaseName+''')'; + INSERT INTO @tmpdbchk EXEC sys.sp_executesql @LinkedServerDBCheck; + SET @ValidLinkedServerDB = (SELECT COUNT(*) FROM @tmpdbchk); + IF (@ValidLinkedServerDB > 0) + BEGIN + SET @ValidOutputServer = 1; + SET @ValidOutputLocation = 1; + END; + ELSE + RAISERROR('The specified database was not found on the output server', 16, 0); + END; + ELSE + BEGIN + RAISERROR('The specified output server was not found', 16, 0); + END; + END; + ELSE + BEGIN + IF (SUBSTRING(@OutputTableName, 2, 2) = '##') + BEGIN + SET @StringToExecute = N' IF (OBJECT_ID(''[tempdb].[dbo].@@@OutputTableName@@@'') IS NOT NULL) DROP TABLE @@@OutputTableName@@@'; + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); + EXEC(@StringToExecute); + + SET @OutputServerName = QUOTENAME(CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128))); + SET @OutputDatabaseName = '[tempdb]'; + SET @OutputSchemaName = '[dbo]'; + SET @ValidOutputLocation = 1; + END; + ELSE IF (SUBSTRING(@OutputTableName, 2, 1) = '#') + BEGIN + RAISERROR('Due to the nature of Dymamic SQL, only global (i.e. double pound (##)) temp tables are supported for @OutputTableName', 16, 0); + END; + ELSE IF @OutputDatabaseName IS NOT NULL + AND @OutputSchemaName IS NOT NULL + AND @OutputTableName IS NOT NULL + AND EXISTS ( SELECT * + FROM sys.databases + WHERE QUOTENAME([name]) = @OutputDatabaseName) + BEGIN + SET @ValidOutputLocation = 1; + SET @OutputServerName = QUOTENAME(CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128))); + END; + ELSE IF @OutputDatabaseName IS NOT NULL + AND @OutputSchemaName IS NOT NULL + AND @OutputTableName IS NOT NULL + AND NOT EXISTS ( SELECT * + FROM sys.databases + WHERE QUOTENAME([name]) = @OutputDatabaseName) + BEGIN + RAISERROR('The specified output database was not found on this server', 16, 0); + END; + ELSE + BEGIN + SET @ValidOutputLocation = 0; + END; + END; + + IF (@ValidOutputLocation = 0 AND @OutputType = 'NONE') + BEGIN + RAISERROR('Invalid output location and no output asked',12,1); + RETURN; + END; + + /* @OutputTableName lets us export the results to a permanent table */ + DECLARE @RunID UNIQUEIDENTIFIER; + SET @RunID = NEWID(); + DECLARE @TableExists BIT; + DECLARE @SchemaExists BIT; + DECLARE @TableExistsSql NVARCHAR(MAX); + IF (@ValidOutputLocation = 1 AND COALESCE(@OutputServerName, @OutputDatabaseName, @OutputSchemaName, @OutputTableName) IS NOT NULL) + BEGIN + SET @StringToExecute = + N'SET @SchemaExists = 0; + SET @TableExists = 0; + IF EXISTS(SELECT * FROM @@@OutputServerName@@@.@@@OutputDatabaseName@@@.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''@@@OutputSchemaName@@@'') + SET @SchemaExists = 1 + IF EXISTS (SELECT * FROM @@@OutputServerName@@@.@@@OutputDatabaseName@@@.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''@@@OutputSchemaName@@@'' AND QUOTENAME(TABLE_NAME) = ''@@@OutputTableName@@@'') + BEGIN + SET @TableExists = 1 + IF NOT EXISTS(SELECT * FROM @@@OutputServerName@@@.@@@OutputDatabaseName@@@.INFORMATION_SCHEMA.COLUMNS WHERE QUOTENAME(TABLE_SCHEMA) = ''@@@OutputSchemaName@@@'' + AND QUOTENAME(TABLE_NAME) = ''@@@OutputTableName@@@'' AND QUOTENAME(COLUMN_NAME) = ''[total_forwarded_fetch_count]'') + EXEC @@@OutputServerName@@@.@@@OutputDatabaseName@@@.dbo.sp_executesql N''ALTER TABLE @@@OutputSchemaName@@@.@@@OutputTableName@@@ ADD [total_forwarded_fetch_count] BIGINT'' + END'; + + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputServerName@@@', @OutputServerName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputSchemaName@@@', @OutputSchemaName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); + + EXEC sp_executesql @StringToExecute, N'@TableExists BIT OUTPUT, @SchemaExists BIT OUTPUT', @TableExists OUTPUT, @SchemaExists OUTPUT; + SET @TableExistsSql = + N'IF EXISTS(SELECT * FROM @@@OutputServerName@@@.@@@OutputDatabaseName@@@.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''@@@OutputSchemaName@@@'') + AND NOT EXISTS (SELECT * FROM @@@OutputServerName@@@.@@@OutputDatabaseName@@@.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''@@@OutputSchemaName@@@'' AND QUOTENAME(TABLE_NAME) = ''@@@OutputTableName@@@'') + SET @TableExists = 0 + ELSE + SET @TableExists = 1'; + + SET @TableExistsSql = REPLACE(@TableExistsSql, '@@@OutputServerName@@@', @OutputServerName); + SET @TableExistsSql = REPLACE(@TableExistsSql, '@@@OutputDatabaseName@@@', @OutputDatabaseName); + SET @TableExistsSql = REPLACE(@TableExistsSql, '@@@OutputSchemaName@@@', @OutputSchemaName); + SET @TableExistsSql = REPLACE(@TableExistsSql, '@@@OutputTableName@@@', @OutputTableName); + END - - - - - - - - - - - -ELSE IF @Mode IN (0, 4) /* DIAGNOSE*//* IF @TableName IS NOT NULL, so @TableName must not be null */ -BEGIN; + IF @Mode IN (0, 4) /* DIAGNOSE */ + BEGIN; IF @Mode IN (0, 4) /* DIAGNOSE priorities 1-100 */ BEGIN; RAISERROR(N'@Mode=0 or 4, running rules for priorities 1-100.', 0,1) WITH NOWAIT; @@ -3859,23 +3985,6 @@ BEGIN; - - - - - - - - - - - - - - - - - @@ -4912,27 +5021,7 @@ BEGIN; - - - - - - - - - - - - - - - - - - - - - + RAISERROR(N'Insert a row to help people find help', 0,1) WITH NOWAIT; IF DATEDIFF(MM, @VersionDate, GETDATE()) > 6 BEGIN @@ -5002,65 +5091,345 @@ BEGIN; RAISERROR(N'Returning results.', 0,1) WITH NOWAIT; /*Return results.*/ - IF (@Mode = 0) - BEGIN - IF(@OutputType <> 'NONE') + IF (@ValidOutputLocation = 1 AND COALESCE(@OutputServerName, @OutputDatabaseName, @OutputSchemaName, @OutputTableName) IS NOT NULL) BEGIN - SELECT Priority, ISNULL(br.findings_group,N'') + - CASE WHEN ISNULL(br.finding,N'') <> N'' THEN N': ' ELSE N'' END - + br.finding AS [Finding], - br.[database_name] AS [Database Name], - br.details AS [Details: schema.table.index(indexid)], - br.index_definition AS [Definition: [Property]] ColumnName {datatype maxbytes}], - ISNULL(br.secret_columns,'') AS [Secret Columns], - br.index_usage_summary AS [Usage], - br.index_size_summary AS [Size], - COALESCE(br.more_info,sn.more_info,'') AS [More Info], - br.URL, - COALESCE(br.create_tsql,ts.create_tsql,'') AS [Create TSQL], - br.sample_query_plan AS [Sample Query Plan] - FROM #BlitzIndexResults br - LEFT JOIN #IndexSanity sn ON - br.index_sanity_id=sn.index_sanity_id - LEFT JOIN #IndexCreateTsql ts ON - br.index_sanity_id=ts.index_sanity_id - ORDER BY br.Priority ASC, br.check_id ASC, br.blitz_result_id ASC, br.findings_group ASC - OPTION (RECOMPILE); - END; + IF NOT @SchemaExists = 1 + BEGIN + RAISERROR (N'Invalid schema name, data could not be saved.', 16, 0); + RETURN; + END - END; - ELSE IF (@Mode = 4) - IF(@OutputType <> 'NONE') - BEGIN - SELECT Priority, ISNULL(br.findings_group,N'') + - CASE WHEN ISNULL(br.finding,N'') <> N'' THEN N': ' ELSE N'' END - + br.finding AS [Finding], - br.[database_name] AS [Database Name], - br.details AS [Details: schema.table.index(indexid)], - br.index_definition AS [Definition: [Property]] ColumnName {datatype maxbytes}], - ISNULL(br.secret_columns,'') AS [Secret Columns], - br.index_usage_summary AS [Usage], - br.index_size_summary AS [Size], - COALESCE(br.more_info,sn.more_info,'') AS [More Info], - br.URL, - COALESCE(br.create_tsql,ts.create_tsql,'') AS [Create TSQL], - br.sample_query_plan AS [Sample Query Plan] - FROM #BlitzIndexResults br - LEFT JOIN #IndexSanity sn ON - br.index_sanity_id=sn.index_sanity_id - LEFT JOIN #IndexCreateTsql ts ON - br.index_sanity_id=ts.index_sanity_id - ORDER BY br.Priority ASC, br.check_id ASC, br.blitz_result_id ASC, br.findings_group ASC - OPTION (RECOMPILE); - END; - -END /* End @Mode=0 or 4 (diagnose)*/ - -ELSE IF (@Mode=1) /*Summarize*/ + IF @TableExists = 0 + BEGIN + SET @StringToExecute = + N'CREATE TABLE @@@OutputDatabaseName@@@.@@@OutputSchemaName@@@.@@@OutputTableName@@@ + ( + [id] INT IDENTITY(1,1) NOT NULL, + [run_id] UNIQUEIDENTIFIER, + [run_datetime] DATETIME, + [server_name] NVARCHAR(128), + [priority] INT, + [finding] NVARCHAR(4000), + [database_name] NVARCHAR(128), + [details] NVARCHAR(MAX), + [index_definition] NVARCHAR(MAX), + [secret_columns] NVARCHAR(MAX), + [index_usage_summary] NVARCHAR(MAX), + [index_size_summary] NVARCHAR(MAX), + [more_info] NVARCHAR(MAX), + [url] NVARCHAR(MAX), + [create_tsql] NVARCHAR(MAX), + [sample_query_plan] XML, + CONSTRAINT [PK_ID_@@@RunID@@@] PRIMARY KEY CLUSTERED ([id] ASC) + );'; + + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputSchemaName@@@', @OutputSchemaName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@RunID@@@', @RunID); + + IF @ValidOutputServer = 1 + BEGIN + SET @StringToExecute = REPLACE(@StringToExecute,'''',''''''); + EXEC('EXEC('''+@StringToExecute+''') AT ' + @OutputServerName); + END; + ELSE + BEGIN + EXEC(@StringToExecute); + END; + END; /* @TableExists = 0 */ + + -- Re-check that table now exists (if not we failed creating it) + SET @TableExists = NULL; + EXEC sp_executesql @TableExistsSql, N'@TableExists BIT OUTPUT', @TableExists OUTPUT; + + IF NOT @TableExists = 1 + BEGIN + RAISERROR('Creation of the output table failed.', 16, 0); + RETURN; + END; + + SET @StringToExecute = + N'INSERT @@@OutputServerName@@@.@@@OutputDatabaseName@@@.@@@OutputSchemaName@@@.@@@OutputTableName@@@ + ( + [run_id], + [run_datetime], + [server_name], + [priority], + [finding], + [database_name], + [details], + [index_definition], + [secret_columns], + [index_usage_summary], + [index_size_summary], + [more_info], + [url], + [create_tsql], + [sample_query_plan] + ) + SELECT + ''@@@RunID@@@'', + ''@@@GETDATE@@@'', + ''@@@LocalServerName@@@'', + -- Below should be a copy/paste of the real query + -- Make sure all quotes are escaped + Priority, ISNULL(br.findings_group,N'''') + + CASE WHEN ISNULL(br.finding,N'''') <> N'''' THEN N'': '' ELSE N'''' END + + br.finding AS [Finding], + br.[database_name] AS [Database Name], + br.details AS [Details: schema.table.index(indexid)], + br.index_definition AS [Definition: [Property]] ColumnName {datatype maxbytes}], + ISNULL(br.secret_columns,'''') AS [Secret Columns], + br.index_usage_summary AS [Usage], + br.index_size_summary AS [Size], + COALESCE(br.more_info,sn.more_info,'''') AS [More Info], + br.URL, + COALESCE(br.create_tsql,ts.create_tsql,'''') AS [Create TSQL], + br.sample_query_plan AS [Sample Query Plan] + FROM #BlitzIndexResults br + LEFT JOIN #IndexSanity sn ON + br.index_sanity_id=sn.index_sanity_id + LEFT JOIN #IndexCreateTsql ts ON + br.index_sanity_id=ts.index_sanity_id + ORDER BY br.Priority ASC, br.check_id ASC, br.blitz_result_id ASC, br.findings_group ASC + OPTION (RECOMPILE);'; + + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputServerName@@@', @OutputServerName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputSchemaName@@@', @OutputSchemaName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@RunID@@@', @RunID); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@GETDATE@@@', GETDATE()); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@LocalServerName@@@', CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128))); + EXEC(@StringToExecute); + + END + ELSE + BEGIN + IF(@OutputType <> 'NONE') + BEGIN + SELECT Priority, ISNULL(br.findings_group,N'') + + CASE WHEN ISNULL(br.finding,N'') <> N'' THEN N': ' ELSE N'' END + + br.finding AS [Finding], + br.[database_name] AS [Database Name], + br.details AS [Details: schema.table.index(indexid)], + br.index_definition AS [Definition: [Property]] ColumnName {datatype maxbytes}], + ISNULL(br.secret_columns,'') AS [Secret Columns], + br.index_usage_summary AS [Usage], + br.index_size_summary AS [Size], + COALESCE(br.more_info,sn.more_info,'') AS [More Info], + br.URL, + COALESCE(br.create_tsql,ts.create_tsql,'') AS [Create TSQL], + br.sample_query_plan AS [Sample Query Plan] + FROM #BlitzIndexResults br + LEFT JOIN #IndexSanity sn ON + br.index_sanity_id=sn.index_sanity_id + LEFT JOIN #IndexCreateTsql ts ON + br.index_sanity_id=ts.index_sanity_id + ORDER BY br.Priority ASC, br.check_id ASC, br.blitz_result_id ASC, br.findings_group ASC + OPTION (RECOMPILE); + END; + + END; + + END /* End @Mode=0 or 4 (diagnose)*/ + + + + + + + + + ELSE IF (@Mode=1) /*Summarize*/ BEGIN --This mode is to give some overall stats on the database. - IF(@OutputType <> 'NONE') - BEGIN + IF (@ValidOutputLocation = 1 AND COALESCE(@OutputServerName, @OutputDatabaseName, @OutputSchemaName, @OutputTableName) IS NOT NULL) + BEGIN + + IF NOT @SchemaExists = 1 + BEGIN + RAISERROR (N'Invalid schema name, data could not be saved.', 16, 0); + RETURN; + END + + IF @TableExists = 0 + BEGIN + SET @StringToExecute = + N'CREATE TABLE @@@OutputDatabaseName@@@.@@@OutputSchemaName@@@.@@@OutputTableName@@@ + ( + [id] INT IDENTITY(1,1) NOT NULL, + [run_id] UNIQUEIDENTIFIER, + [run_datetime] DATETIME, + [server_name] NVARCHAR(128), + [database_name] NVARCHAR(128), + [object_count] INT, + [reserved_gb] NUMERIC(29,1), + [reserved_lob_gb] NUMERIC(29,1), + [reserved_row_overflow_gb] NUMERIC(29,1), + [clustered_table_count] INT, + [clustered_table_gb] NUMERIC(29,1), + [nc_index_count] INT, + [nc_index_gb] NUMERIC(29,1), + [table_nc_index_ratio] NUMERIC(29,1), + [heap_count] INT, + [heap_gb] NUMERIC(29,1), + [partioned_table_count] INT, + [partioned_nc_count] INT, + [partioned_gb] NUMERIC(29,1), + [filtered_index_count] INT, + [indexed_view_count] INT, + [max_table_row_count] INT, + [max_table_gb] NUMERIC(29,1), + [max_nc_index_gb] NUMERIC(29,1), + [table_count_over_1gb] INT, + [table_count_over_10gb] INT, + [table_count_over_100gb] INT, + [nc_index_count_over_1gb] INT, + [nc_index_count_over_10gb] INT, + [nc_index_count_over_100gb] INT, + [min_create_date] DATETIME, + [max_create_date] DATETIME, + [max_modify_date] DATETIME, + [display_order] INT, + CONSTRAINT [PK_ID_@@@RunID@@@] PRIMARY KEY CLUSTERED ([id] ASC) + );'; + + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputSchemaName@@@', @OutputSchemaName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@RunID@@@', @RunID); + + IF @ValidOutputServer = 1 + BEGIN + SET @StringToExecute = REPLACE(@StringToExecute,'''',''''''); + EXEC('EXEC('''+@StringToExecute+''') AT ' + @OutputServerName); + END; + ELSE + BEGIN + EXEC(@StringToExecute); + END; + END; /* @TableExists = 0 */ + + -- Re-check that table now exists (if not we failed creating it) + SET @TableExists = NULL; + EXEC sp_executesql @TableExistsSql, N'@TableExists BIT OUTPUT', @TableExists OUTPUT; + + IF NOT @TableExists = 1 + BEGIN + RAISERROR('Creation of the output table failed.', 16, 0); + RETURN; + END; + + SET @StringToExecute = + N'INSERT @@@OutputServerName@@@.@@@OutputDatabaseName@@@.@@@OutputSchemaName@@@.@@@OutputTableName@@@ + ( + [run_id], + [run_datetime], + [server_name], + [database_name], + [object_count], + [reserved_gb], + [reserved_lob_gb], + [reserved_row_overflow_gb], + [clustered_table_count], + [clustered_table_gb], + [nc_index_count], + [nc_index_gb], + [table_nc_index_ratio], + [heap_count], + [heap_gb], + [partioned_table_count], + [partioned_nc_count], + [partioned_gb], + [filtered_index_count], + [indexed_view_count], + [max_table_row_count], + [max_table_gb], + [max_nc_index_gb], + [table_count_over_1gb], + [table_count_over_10gb], + [table_count_over_100gb], + [nc_index_count_over_1gb], + [nc_index_count_over_10gb], + [nc_index_count_over_100gb], + [min_create_date], + [max_create_date], + [max_modify_date], + [display_order] + ) + SELECT ''@@@RunID@@@'', + ''@@@GETDATE@@@'', + ''@@@LocalServerName@@@'', + -- Below should be a copy/paste of the real query + -- Make sure all quotes are escaped + -- NOTE! information line is skipped from output and the query below + -- NOTE! initial columns are not casted to nvarchar due to not outputing informational line + DB_NAME(i.database_id) AS [Database Name], + COUNT(*) AS [Number Objects], + CAST(SUM(sz.total_reserved_MB)/ + 1024. AS NUMERIC(29,1)) AS [All GB], + CAST(SUM(sz.total_reserved_LOB_MB)/ + 1024. AS NUMERIC(29,1)) AS [LOB GB], + CAST(SUM(sz.total_reserved_row_overflow_MB)/ + 1024. AS NUMERIC(29,1)) AS [Row Overflow GB], + SUM(CASE WHEN index_id=1 THEN 1 ELSE 0 END) AS [Clustered Tables], + CAST(SUM(CASE WHEN index_id=1 THEN sz.total_reserved_MB ELSE 0 END) + /1024. AS NUMERIC(29,1)) AS [Clustered Tables GB], + SUM(CASE WHEN index_id NOT IN (0,1) THEN 1 ELSE 0 END) AS [NC Indexes], + CAST(SUM(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) + /1024. AS NUMERIC(29,1)) AS [NC Indexes GB], + CASE WHEN SUM(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) > 0 THEN + CAST(SUM(CASE WHEN index_id IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) + / SUM(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) AS NUMERIC(29,1)) + ELSE 0 END AS [ratio table: NC Indexes], + SUM(CASE WHEN index_id=0 THEN 1 ELSE 0 END) AS [Heaps], + CAST(SUM(CASE WHEN index_id=0 THEN sz.total_reserved_MB ELSE 0 END) + /1024. AS NUMERIC(29,1)) AS [Heaps GB], + SUM(CASE WHEN index_id IN (0,1) AND partition_key_column_name IS NOT NULL THEN 1 ELSE 0 END) AS [Partitioned Tables], + SUM(CASE WHEN index_id NOT IN (0,1) AND partition_key_column_name IS NOT NULL THEN 1 ELSE 0 END) AS [Partitioned NCs], + CAST(SUM(CASE WHEN partition_key_column_name IS NOT NULL THEN sz.total_reserved_MB ELSE 0 END)/1024. AS NUMERIC(29,1)) AS [Partitioned GB], + SUM(CASE WHEN filter_definition <> '''' THEN 1 ELSE 0 END) AS [Filtered Indexes], + SUM(CASE WHEN is_indexed_view=1 THEN 1 ELSE 0 END) AS [Indexed Views], + MAX(total_rows) AS [Max Row Count], + CAST(MAX(CASE WHEN index_id IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) + /1024. AS NUMERIC(29,1)) AS [Max Table GB], + CAST(MAX(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) + /1024. AS NUMERIC(29,1)) AS [Max NC Index GB], + SUM(CASE WHEN index_id IN (0,1) AND sz.total_reserved_MB > 1024 THEN 1 ELSE 0 END) AS [Count Tables > 1GB], + SUM(CASE WHEN index_id IN (0,1) AND sz.total_reserved_MB > 10240 THEN 1 ELSE 0 END) AS [Count Tables > 10GB], + SUM(CASE WHEN index_id IN (0,1) AND sz.total_reserved_MB > 102400 THEN 1 ELSE 0 END) AS [Count Tables > 100GB], + SUM(CASE WHEN index_id NOT IN (0,1) AND sz.total_reserved_MB > 1024 THEN 1 ELSE 0 END) AS [Count NCs > 1GB], + SUM(CASE WHEN index_id NOT IN (0,1) AND sz.total_reserved_MB > 10240 THEN 1 ELSE 0 END) AS [Count NCs > 10GB], + SUM(CASE WHEN index_id NOT IN (0,1) AND sz.total_reserved_MB > 102400 THEN 1 ELSE 0 END) AS [Count NCs > 100GB], + MIN(create_date) AS [Oldest Create Date], + MAX(create_date) AS [Most Recent Create Date], + MAX(modify_date) AS [Most Recent Modify Date], + 1 AS [Display Order] + FROM #IndexSanity AS i + --left join here so we don''t lose disabled nc indexes + LEFT JOIN #IndexSanitySize AS sz + ON i.index_sanity_id=sz.index_sanity_id + GROUP BY DB_NAME(i.database_id) + ORDER BY [Display Order] ASC + OPTION (RECOMPILE);'; + + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputServerName@@@', @OutputServerName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputSchemaName@@@', @OutputSchemaName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@RunID@@@', @RunID); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@GETDATE@@@', GETDATE()); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@LocalServerName@@@', CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128))); + EXEC(@StringToExecute); + + END; /* @ValidOutputLocation = 1 */ + ELSE + BEGIN + IF(@OutputType <> 'NONE') + BEGIN + RAISERROR(N'@Mode=1, we are summarizing.', 0,1) WITH NOWAIT; SELECT DB_NAME(i.database_id) AS [Database Name], @@ -5120,123 +5489,26 @@ ELSE IF (@Mode=1) /*Summarize*/ NULL,NULL,0 AS display_order ORDER BY [Display Order] ASC OPTION (RECOMPILE); - END; - + END; + END; + END; /* End @Mode=1 (summarize)*/ - ELSE IF (@Mode=2) /*Index Detail*/ + + + + + + + + + ELSE IF (@Mode=2) /*Index Detail*/ BEGIN --This mode just spits out all the detail without filters. --This supports slicing AND dicing in Excel RAISERROR(N'@Mode=2, here''s the details on existing indexes.', 0,1) WITH NOWAIT; - - /* Checks if @OutputServerName is populated with a valid linked server, and that the database name specified is valid */ - DECLARE @ValidOutputServer BIT; - DECLARE @ValidOutputLocation BIT; - DECLARE @LinkedServerDBCheck NVARCHAR(2000); - DECLARE @ValidLinkedServerDB INT; - DECLARE @tmpdbchk TABLE (cnt INT); - - IF @OutputServerName IS NOT NULL - BEGIN - IF (SUBSTRING(@OutputTableName, 2, 1) = '#') - BEGIN - RAISERROR('Due to the nature of temporary tables, outputting to a linked server requires a permanent table.', 16, 0); - END; - ELSE IF EXISTS (SELECT server_id FROM sys.servers WHERE QUOTENAME([name]) = @OutputServerName) - BEGIN - SET @LinkedServerDBCheck = 'SELECT 1 WHERE EXISTS (SELECT * FROM '+@OutputServerName+'.master.sys.databases WHERE QUOTENAME([name]) = '''+@OutputDatabaseName+''')'; - INSERT INTO @tmpdbchk EXEC sys.sp_executesql @LinkedServerDBCheck; - SET @ValidLinkedServerDB = (SELECT COUNT(*) FROM @tmpdbchk); - IF (@ValidLinkedServerDB > 0) - BEGIN - SET @ValidOutputServer = 1; - SET @ValidOutputLocation = 1; - END; - ELSE - RAISERROR('The specified database was not found on the output server', 16, 0); - END; - ELSE - BEGIN - RAISERROR('The specified output server was not found', 16, 0); - END; - END; - ELSE - BEGIN - IF (SUBSTRING(@OutputTableName, 2, 2) = '##') - BEGIN - SET @StringToExecute = N' IF (OBJECT_ID(''[tempdb].[dbo].@@@OutputTableName@@@'') IS NOT NULL) DROP TABLE @@@OutputTableName@@@'; - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); - EXEC(@StringToExecute); - - SET @OutputServerName = QUOTENAME(CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128))); - SET @OutputDatabaseName = '[tempdb]'; - SET @OutputSchemaName = '[dbo]'; - SET @ValidOutputLocation = 1; - END; - ELSE IF (SUBSTRING(@OutputTableName, 2, 1) = '#') - BEGIN - RAISERROR('Due to the nature of Dymamic SQL, only global (i.e. double pound (##)) temp tables are supported for @OutputTableName', 16, 0); - END; - ELSE IF @OutputDatabaseName IS NOT NULL - AND @OutputSchemaName IS NOT NULL - AND @OutputTableName IS NOT NULL - AND EXISTS ( SELECT * - FROM sys.databases - WHERE QUOTENAME([name]) = @OutputDatabaseName) - BEGIN - SET @ValidOutputLocation = 1; - SET @OutputServerName = QUOTENAME(CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128))); - END; - ELSE IF @OutputDatabaseName IS NOT NULL - AND @OutputSchemaName IS NOT NULL - AND @OutputTableName IS NOT NULL - AND NOT EXISTS ( SELECT * - FROM sys.databases - WHERE QUOTENAME([name]) = @OutputDatabaseName) - BEGIN - RAISERROR('The specified output database was not found on this server', 16, 0); - END; - ELSE - BEGIN - SET @ValidOutputLocation = 0; - END; - END; - - IF (@ValidOutputLocation = 0 AND @OutputType = 'NONE') - BEGIN - RAISERROR('Invalid output location and no output asked',12,1); - RETURN; - END; - - /* @OutputTableName lets us export the results to a permanent table */ - DECLARE @RunID UNIQUEIDENTIFIER; - SET @RunID = NEWID(); - IF (@ValidOutputLocation = 1 AND COALESCE(@OutputServerName, @OutputDatabaseName, @OutputSchemaName, @OutputTableName) IS NOT NULL) BEGIN - DECLARE @TableExists BIT; - DECLARE @SchemaExists BIT; - SET @StringToExecute = - N'SET @SchemaExists = 0; - SET @TableExists = 0; - IF EXISTS(SELECT * FROM @@@OutputServerName@@@.@@@OutputDatabaseName@@@.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''@@@OutputSchemaName@@@'') - SET @SchemaExists = 1 - IF EXISTS (SELECT * FROM @@@OutputServerName@@@.@@@OutputDatabaseName@@@.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''@@@OutputSchemaName@@@'' AND QUOTENAME(TABLE_NAME) = ''@@@OutputTableName@@@'') - BEGIN - SET @TableExists = 1 - IF NOT EXISTS(SELECT * FROM @@@OutputServerName@@@.@@@OutputDatabaseName@@@.INFORMATION_SCHEMA.COLUMNS WHERE QUOTENAME(TABLE_SCHEMA) = ''@@@OutputSchemaName@@@'' - AND QUOTENAME(TABLE_NAME) = ''@@@OutputTableName@@@'' AND QUOTENAME(COLUMN_NAME) = ''[total_forwarded_fetch_count]'') - EXEC @@@OutputServerName@@@.@@@OutputDatabaseName@@@.dbo.sp_executesql N''ALTER TABLE @@@OutputSchemaName@@@.@@@OutputTableName@@@ ADD [total_forwarded_fetch_count] BIGINT'' - END'; - - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputServerName@@@', @OutputServerName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputSchemaName@@@', @OutputSchemaName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); - - EXEC sp_executesql @StringToExecute, N'@TableExists BIT OUTPUT, @SchemaExists BIT OUTPUT', @TableExists OUTPUT, @SchemaExists OUTPUT; - IF @SchemaExists = 1 BEGIN IF @TableExists = 0 @@ -5337,20 +5609,10 @@ ELSE IF (@Mode=1) /*Summarize*/ END; END; /* @TableExists = 0 */ - SET @StringToExecute = - N'IF EXISTS(SELECT * FROM @@@OutputServerName@@@.@@@OutputDatabaseName@@@.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''@@@OutputSchemaName@@@'') - AND NOT EXISTS (SELECT * FROM @@@OutputServerName@@@.@@@OutputDatabaseName@@@.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''@@@OutputSchemaName@@@'' AND QUOTENAME(TABLE_NAME) = ''@@@OutputTableName@@@'') - SET @TableExists = 0 - ELSE - SET @TableExists = 1'; - + + -- Re-check that table now exists (if not we failed creating it) SET @TableExists = NULL; - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputServerName@@@', @OutputServerName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputSchemaName@@@', @OutputSchemaName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); - - EXEC sp_executesql @StringToExecute, N'@TableExists BIT OUTPUT', @TableExists OUTPUT; + EXEC sp_executesql @TableExistsSql, N'@TableExists BIT OUTPUT', @TableExists OUTPUT; IF @TableExists = 1 BEGIN @@ -5678,10 +5940,167 @@ ELSE IF (@Mode=1) /*Summarize*/ END; END; /* End @Mode=2 (index detail)*/ + + + + + + + + ELSE IF (@Mode=3) /*Missing index Detail*/ BEGIN - IF(@OutputType <> 'NONE') - BEGIN; + IF (@ValidOutputLocation = 1 AND COALESCE(@OutputServerName, @OutputDatabaseName, @OutputSchemaName, @OutputTableName) IS NOT NULL) + BEGIN + + IF NOT @SchemaExists = 1 + BEGIN + RAISERROR (N'Invalid schema name, data could not be saved.', 16, 0); + RETURN; + END + + IF @TableExists = 0 + BEGIN + SET @StringToExecute = + N'CREATE TABLE @@@OutputDatabaseName@@@.@@@OutputSchemaName@@@.@@@OutputTableName@@@ + ( + [id] INT IDENTITY(1,1) NOT NULL, + [run_id] UNIQUEIDENTIFIER, + [run_datetime] DATETIME, + [server_name] NVARCHAR(128), + [database_name] NVARCHAR(128), + [schema_name] NVARCHAR(128), + [table_name] NVARCHAR(128), + [magic_benefit_number] BIGINT, + [missing_index_details] NVARCHAR(MAX), + [avg_total_user_cost] NUMERIC(29,4), + [avg_user_impact] NUMERIC(29,1), + [user_seeks] BIGINT, + [user_scans] BIGINT, + [unique_compiles] BIGINT, + [equality_columns_with_data_type] NVARCHAR(MAX), + [inequality_columns_with_data_type] NVARCHAR(MAX), + [included_columns_with_data_type] NVARCHAR(MAX), + [index_estimated_impact] NVARCHAR(256), + [create_tsql] NVARCHAR(MAX), + [more_info] NVARCHAR(600), + [display_order] INT, + [is_low] BIT, + [sample_query_plan] XML, + CONSTRAINT [PK_ID_@@@RunID@@@] PRIMARY KEY CLUSTERED ([id] ASC) + );'; + + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputSchemaName@@@', @OutputSchemaName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@RunID@@@', @RunID); + + IF @ValidOutputServer = 1 + BEGIN + SET @StringToExecute = REPLACE(@StringToExecute,'''',''''''); + EXEC('EXEC('''+@StringToExecute+''') AT ' + @OutputServerName); + END; + ELSE + BEGIN + EXEC(@StringToExecute); + END; + END; /* @TableExists = 0 */ + + -- Re-check that table now exists (if not we failed creating it) + SET @TableExists = NULL; + EXEC sp_executesql @TableExistsSql, N'@TableExists BIT OUTPUT', @TableExists OUTPUT; + + IF NOT @TableExists = 1 + BEGIN + RAISERROR('Creation of the output table failed.', 16, 0); + RETURN; + END; + SET @StringToExecute = + N'WITH create_date AS ( + SELECT i.database_id, + i.schema_name, + i.[object_id], + ISNULL(NULLIF(MAX(DATEDIFF(DAY, i.create_date, SYSDATETIME())), 0), 1) AS create_days + FROM #IndexSanity AS i + GROUP BY i.database_id, i.schema_name, i.object_id + ) + INSERT @@@OutputServerName@@@.@@@OutputDatabaseName@@@.@@@OutputSchemaName@@@.@@@OutputTableName@@@ + ( + [run_id], + [run_datetime], + [server_name], + [database_name], + [schema_name], + [table_name], + [magic_benefit_number], + [missing_index_details], + [avg_total_user_cost], + [avg_user_impact], + [user_seeks], + [user_scans], + [unique_compiles], + [equality_columns_with_data_type], + [inequality_columns_with_data_type], + [included_columns_with_data_type], + [index_estimated_impact], + [create_tsql], + [more_info], + [display_order], + [is_low], + [sample_query_plan] + ) + SELECT ''@@@RunID@@@'', + ''@@@GETDATE@@@'', + ''@@@LocalServerName@@@'', + -- Below should be a copy/paste of the real query + -- Make sure all quotes are escaped + -- NOTE! information line is skipped from output and the query below + -- NOTE! CTE block is above insert in the copied SQL + mi.database_name AS [Database Name], + mi.[schema_name] AS [Schema], + mi.table_name AS [Table], + CAST((mi.magic_benefit_number / CASE WHEN cd.create_days < @DaysUptime THEN cd.create_days ELSE @DaysUptime END) AS BIGINT) + AS [Magic Benefit Number], + mi.missing_index_details AS [Missing Index Details], + mi.avg_total_user_cost AS [Avg Query Cost], + mi.avg_user_impact AS [Est Index Improvement], + mi.user_seeks AS [Seeks], + mi.user_scans AS [Scans], + mi.unique_compiles AS [Compiles], + mi.equality_columns_with_data_type AS [Equality Columns], + mi.inequality_columns_with_data_type AS [Inequality Columns], + mi.included_columns_with_data_type AS [Included Columns], + mi.index_estimated_impact AS [Estimated Impact], + mi.create_tsql AS [Create TSQL], + mi.more_info AS [More Info], + 1 AS [Display Order], + mi.is_low, + mi.sample_query_plan AS [Sample Query Plan] + FROM #MissingIndexes AS mi + LEFT JOIN create_date AS cd + ON mi.[object_id] = cd.object_id + AND mi.database_id = cd.database_id + AND mi.schema_name = cd.schema_name + /* Minimum benefit threshold = 100k/day of uptime OR since table creation date, whichever is lower*/ + WHERE @ShowAllMissingIndexRequests=1 + OR (mi.magic_benefit_number / CASE WHEN cd.create_days < @DaysUptime THEN cd.create_days ELSE @DaysUptime END) >= 100000 + ORDER BY [Display Order] ASC, [Magic Benefit Number] DESC + OPTION (RECOMPILE);'; + + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputServerName@@@', @OutputServerName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputSchemaName@@@', @OutputSchemaName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@RunID@@@', @RunID); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@GETDATE@@@', GETDATE()); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@LocalServerName@@@', CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128))); + EXEC sp_executesql @StringToExecute, N'@DaysUptime NUMERIC(23,2), @ShowAllMissingIndexRequests BIT', @DaysUptime = @DaysUptime, @ShowAllMissingIndexRequests = @ShowAllMissingIndexRequests; + + END; /* @ValidOutputLocation = 1 */ + ELSE + BEGIN + IF(@OutputType <> 'NONE') + BEGIN WITH create_date AS ( SELECT i.database_id, i.schema_name, @@ -5730,21 +6149,26 @@ ELSE IF (@Mode=1) /*Summarize*/ NULL, NULL, NULL, NULL, 0 AS [Display Order], NULL AS is_low, NULL ORDER BY [Display Order] ASC, [Magic Benefit Number] DESC OPTION (RECOMPILE); - END; + END; + + + IF (@BringThePain = 1 + AND @DatabaseName IS NOT NULL + AND @GetAllDatabases = 0) + + BEGIN + EXEC sp_BlitzCache @SortOrder = 'sp_BlitzIndex', @DatabaseName = @DatabaseName, @BringThePain = 1, @QueryFilter = 'statement', @HideSummary = 1; + END; + + END; - IF (@BringThePain = 1 - AND @DatabaseName IS NOT NULL - AND @GetAllDatabases = 0) - BEGIN - EXEC sp_BlitzCache @SortOrder = 'sp_BlitzIndex', @DatabaseName = @DatabaseName, @BringThePain = 1, @QueryFilter = 'statement', @HideSummary = 1; - - END; END; /* End @Mode=3 (index detail)*/ +END /* End @TableName IS NULL (mode 0/1/2/3/4) */ END TRY BEGIN CATCH From ec5613f65ffcec3892a957affed3168d5c687caf Mon Sep 17 00:00:00 2001 From: jayh Date: Fri, 28 Oct 2022 15:16:49 -0400 Subject: [PATCH 323/662] Added missing "QUOTENAME" function around the "@Databasename" in the "USE" statement for the two statistics queries. Without it, DB names like 'my-stuff' will fail. --- sp_BlitzIndex.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index 729b2ec17..43f29c153 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -1925,7 +1925,7 @@ BEGIN TRY OR (PARSENAME(@SQLServerProductVersion, 4) = 10 AND PARSENAME(@SQLServerProductVersion, 3) = 50 AND PARSENAME(@SQLServerProductVersion, 2) >= 2500)) BEGIN RAISERROR (N'Gathering Statistics Info With Newer Syntax.',0,1) WITH NOWAIT; - SET @dsql=N'USE ' + @DatabaseName + N'; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + 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, 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, @@ -2000,7 +2000,7 @@ BEGIN TRY ELSE BEGIN RAISERROR (N'Gathering Statistics Info With Older Syntax.',0,1) WITH NOWAIT; - SET @dsql=N'USE ' + @DatabaseName + N'; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + 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, days_since_last_stats_update, rows, modification_counter, percent_modifications, modifications_before_auto_update, index_type_desc, table_create_date, table_modify_date, From 65a72b7fe3cd0d3d7b27f03c23072d98c11a323b Mon Sep 17 00:00:00 2001 From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com> Date: Wed, 9 Nov 2022 14:10:09 -0500 Subject: [PATCH 324/662] Switch to TRY_CAST To avoid embarrassing odors --- sp_BlitzQueryStore.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sp_BlitzQueryStore.sql b/sp_BlitzQueryStore.sql index 7baa05f13..548f0d570 100644 --- a/sp_BlitzQueryStore.sql +++ b/sp_BlitzQueryStore.sql @@ -2306,7 +2306,7 @@ RAISERROR(N'Gathering working plans', 0, 1) WITH NOWAIT; SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; SET @sql_select += N' SELECT ' + QUOTENAME(@DatabaseName, '''') + N' AS database_name, wp.plan_id, wp.query_id, - qsp.plan_group_id, qsp.engine_version, qsp.compatibility_level, qsp.query_plan_hash, TRY_CONVERT(XML, qsp.query_plan), qsp.is_online_index_plan, qsp.is_trivial_plan, + qsp.plan_group_id, qsp.engine_version, qsp.compatibility_level, qsp.query_plan_hash, TRY_CAST(qsp.query_plan AS XML), qsp.is_online_index_plan, qsp.is_trivial_plan, qsp.is_parallel_plan, qsp.is_forced_plan, qsp.is_natively_compiled, qsp.force_failure_count, qsp.last_force_failure_reason_desc, qsp.count_compiles, qsp.initial_compile_start_time, qsp.last_compile_start_time, qsp.last_execution_time, (qsp.avg_compile_duration / 1000.), From 5271a752d3335e9cc47e32821c09504d83a05e56 Mon Sep 17 00:00:00 2001 From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com> Date: Wed, 9 Nov 2022 20:10:48 -0500 Subject: [PATCH 325/662] Update sp_BlitzLock.sql Initial work --- sp_BlitzLock.sql | 3292 +++++++++++++++++++++++++--------------------- 1 file changed, 1820 insertions(+), 1472 deletions(-) diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index fef2f4510..e8326e424 100644 --- a/sp_BlitzLock.sql +++ b/sp_BlitzLock.sql @@ -4,378 +4,726 @@ GO ALTER PROCEDURE dbo.sp_BlitzLock ( - @Top INT = 2147483647, - @DatabaseName NVARCHAR(256) = NULL, - @StartDate DATETIME = '19000101', - @EndDate DATETIME = '99991231', - @ObjectName NVARCHAR(1000) = NULL, - @StoredProcName NVARCHAR(1000) = NULL, - @AppName NVARCHAR(256) = NULL, - @HostName NVARCHAR(256) = NULL, - @LoginName NVARCHAR(256) = NULL, - @EventSessionPath VARCHAR(256) = 'system_health*.xel', - @VictimsOnly BIT = 0, - @Debug BIT = 0, - @Help BIT = 0, - @Version VARCHAR(30) = NULL OUTPUT, - @VersionDate DATETIME = NULL OUTPUT, - @VersionCheckMode BIT = 0, - @OutputDatabaseName NVARCHAR(256) = NULL , - @OutputSchemaName NVARCHAR(256) = 'dbo' , --ditto as below - @OutputTableName NVARCHAR(256) = 'BlitzLock', --put a standard here no need to check later in the script - @ExportToExcel BIT = 0 + @Top bigint = 9223372036854775807, + @DatabaseName nvarchar(256) = NULL, + @StartDate datetime = '19000101', + @EndDate datetime = '99991231', + @ObjectName nvarchar(1000) = NULL, + @StoredProcName nvarchar(1000) = NULL, + @AppName nvarchar(256) = NULL, + @HostName nvarchar(256) = NULL, + @LoginName nvarchar(256) = NULL, + @EventSessionName varchar(256) = 'system_health', + @TargetSessionType varchar(20) = 'event_file', + @VictimsOnly bit = 0, + @Debug bit = 0, + @Help bit = 0, + @Version varchar(30) = NULL OUTPUT, + @VersionDate datetime = NULL OUTPUT, + @VersionCheckMode bit = 0, + @OutputDatabaseName nvarchar(256) = NULL , + @OutputSchemaName nvarchar(256) = 'dbo' , --ditto as below + @OutputTableName nvarchar(256) = 'BlitzLock', --put a standard here no need to check later in the script + @ExportToExcel bit = 0 ) WITH RECOMPILE AS BEGIN -SET NOCOUNT ON; -SET STATISTICS XML OFF; +SET STATISTICS xml OFF; +SET NOCOUNT, XACT_ABORT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.11', @VersionDate = '20221013'; +SELECT + @Version = '8.11', + @VersionDate = '20221013'; IF(@VersionCheckMode = 1) BEGIN - RETURN; + RETURN; END; - IF @Help = 1 - BEGIN - PRINT ' - /* - sp_BlitzLock from http://FirstResponderKit.org - - This script checks for and analyzes deadlocks from the system health session or a custom extended event path - Variables you can use: - @Top: Use if you want to limit the number of deadlocks to return. - This is ordered by event date ascending +IF @Help = 1 + BEGIN + PRINT ' + /* + sp_BlitzLock from http://FirstResponderKit.org + + This script checks for and analyzes deadlocks from the system health session or a custom extended event path - @DatabaseName: If you want to filter to a specific database + Variables you can use: - @StartDate: The date you want to start searching on. + @Top: Limit the number of rows to return - @EndDate: The date you want to stop searching on. + @DatabaseName: If you want to filter to a specific database - @ObjectName: If you want to filter to a specific able. - The object name has to be fully qualified ''Database.Schema.Table'' + @StartDate: The date you want to start searching on. - @StoredProcName: If you want to search for a single stored proc - The proc name has to be fully qualified ''Database.Schema.Sproc'' - - @AppName: If you want to filter to a specific application - - @HostName: If you want to filter to a specific host - - @LoginName: If you want to filter to a specific login + @EndDate: The date you want to stop searching on. - @EventSessionPath: If you want to point this at an XE session rather than the system health session. - - @OutputDatabaseName: If you want to output information to a specific database - @OutputSchemaName: Specify a schema name to output information to a specific Schema - @OutputTableName: Specify table name to to output information to a specific table - - To learn more, visit http://FirstResponderKit.org where you can download new - versions for free, watch training videos on how it works, get more info on - the findings, contribute your own code, and more. - - Known limitations of this version: - - Only SQL Server 2012 and newer is supported - - If your tables have weird characters in them (https://en.wikipedia.org/wiki/List_of_XML_and_HTML_character_entity_references) you may get errors trying to parse the XML. - I took a long look at this one, and: - 1) Trying to account for all the weird places these could crop up is a losing effort. - 2) Replace is slow af on lots of XML. - - - - - Unknown limitations of this version: - - None. (If we knew them, they would be known. Duh.) + @ObjectName: If you want to filter to a specific able. + The object name has to be fully qualified ''Database.Schema.Table'' + @StoredProcName: If you want to search for a single stored proc + The proc name has to be fully qualified ''Database.Schema.Sproc'' + + @AppName: If you want to filter to a specific application + + @HostName: If you want to filter to a specific host + + @LoginName: If you want to filter to a specific login + + @EventSessionPath: If you want to point this at an XE session rather than the system health session. + + @TargetSessionType: Can be ring_buffer or event_file. + + @OutputDatabaseName: If you want to output information to a specific database + @OutputSchemaName: Specify a schema name to output information to a specific Schema + @OutputTableName: Specify table name to to output information to a specific table + + To learn more, visit http://FirstResponderKit.org where you can download new + versions for free, watch training videos on how it works, get more info on + the findings, contribute your own code, and more. + + Known limitations of this version: + - Only SQL Server 2012 and newer is supported + - If your tables have weird characters in them (https://en.wikipedia.org/wiki/List_of_xml_and_HTML_character_entity_references) you may get errors trying to parse the xml. + I took a long look at this one, and: + 1) Trying to account for all the weird places these could crop up is a losing effort. + 2) Replace is slow af on lots of xml. + + Unknown limitations of this version: + - None. (If we knew them, they would be known. Duh.) MIT License - - Copyright (c) 2021 Brent Ozar Unlimited - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE. - - */'; - RETURN; - END; /* @Help = 1 */ - - DECLARE @ProductVersion NVARCHAR(128), - @ProductVersionMajor FLOAT, - @ProductVersionMinor INT, - @ObjectFullName NVARCHAR(2000); - - - SET @ProductVersion = CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)); - - SELECT @ProductVersionMajor = SUBSTRING(@ProductVersion, 1, CHARINDEX('.', @ProductVersion) + 1), - @ProductVersionMinor = PARSENAME(CONVERT(VARCHAR(32), @ProductVersion), 2); + + Copyright (c) 2021 Brent Ozar Unlimited + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + + */'; + RETURN; + END; /* @Help = 1 */ + + DECLARE + @ProductVersion nvarchar(128) = + CAST(SERVERPROPERTY('ProductVersion') AS nvarchar(128)), + @ProductVersionMajor float = + SUBSTRING(CAST(SERVERPROPERTY('ProductVersion') AS nvarchar(128)), 1, CHARINDEX('.', CAST(SERVERPROPERTY('ProductVersion') AS nvarchar(128))) + 1), + @ProductVersionMinor int = + PARSENAME(CONVERT(varchar(32), CAST(SERVERPROPERTY('ProductVersion') AS nvarchar(128))), 2), + @ObjectFullName nvarchar(MAX) = N'', + @Azure bit = CASE WHEN (SELECT SERVERPROPERTY ('EDITION')) = 'SQL Azure' THEN 1 ELSE 0 END, + @RDS bigint = + CASE + WHEN LEFT(CAST(SERVERPROPERTY('ComputerNamePhysicalNetBIOS') AS varchar(8000)), 8) <> 'EC2AMAZ-' + AND LEFT(CAST(SERVERPROPERTY('MachineName') AS varchar(8000)), 8) <> 'EC2AMAZ-' + AND DB_ID('rdsadmin') IS NULL + THEN 0 + ELSE 1 + END, + @d varchar(40) = '', + @StringToExecute nvarchar(4000) = '', + @StringToExecuteParams nvarchar(500) = N'', + @r nvarchar(200) = N'', + @OutputTableFindings nvarchar(100) = N'[BlitzLockFindings]', + @DeadlockCount int = 0, + @ServerName nvarchar(256) = @@SERVERNAME, + @OutputDatabaseCheck bit = NULL, + @SessionId int, + @TargetSessionId int, + @FileName nvarchar(4000), + @NC10 nvarchar(1) = CONVERT(nvarchar(1), 0x0a00, 0); + + CREATE TABLE + #x + ( + x xml + ); + + CREATE TABLE + #deadlock_data + ( + deadlock_xml xml, + event_time datetime + ); + + IF @StartDate IS NULL + BEGIN + SET @StartDate = '19000101'; + END; + + IF @EndDate IS NULL + BEGIN + SET @EndDate = '99991231'; + END + + + CREATE TABLE + #deadlock_findings + ( + id int IDENTITY(1, 1) PRIMARY KEY CLUSTERED, + check_id int NOT NULL, + database_name nvarchar(256), + object_name nvarchar(1000), + finding_group nvarchar(100), + finding nvarchar(4000) + ); + + SET @d = CONVERT(varchar(40), GETDATE(), 109); + IF(@OutputDatabaseName IS NOT NULL) + BEGIN --if databaseName is set do some sanity checks and put [] around def. + IF NOT EXISTS + ( + SELECT + 1/0 + FROM sys.databases + WHERE name = @OutputDatabaseName + ) --if database is invalid raiserror and set bitcheck + BEGIN + RAISERROR('Database Name for output of table is invalid please correct, Output to Table will not be preformed', 0, 1, @d) WITH NOWAIT; + SET @OutputDatabaseCheck = -1; -- -1 invalid/false, 0 = good/true + END; + ELSE + BEGIN + SET @OutputDatabaseCheck = 0; + + SELECT + @StringToExecute = + N'SELECT @r = name FROM ' + + N'' + + @OutputDatabaseName + + N'' + + N'.sys.objects WHERE type_desc=''USER_TABLE'' AND name=' + + N'''' + + @OutputTableName + + N'''', + @StringToExecuteParams = + N'@OutputDatabaseName nvarchar(200), + @OutputTableName nvarchar(200), + @r nvarchar(200) OUTPUT'; + + EXEC sys.sp_executesql + @StringToExecute, + @StringToExecuteParams, + @OutputDatabaseName, + @OutputTableName, + @r OUTPUT; + + --put covers around all before. + SELECT + @OutputDatabaseName = QUOTENAME(@OutputDatabaseName), + @OutputTableName = QUOTENAME(@OutputTableName), + @OutputSchemaName = QUOTENAME(@OutputSchemaName); + + IF(@r IS NOT NULL) --if it is not null, there is a table, so check for newly added columns + BEGIN + /* If the table doesn't have the new spid column, add it. See Github #3101. */ + SET @ObjectFullName = + @OutputDatabaseName + + N'.' + + @OutputSchemaName + + N'.' + + @OutputTableName; + + SET @StringToExecute = + N'IF NOT EXISTS (SELECT * FROM ' + + @OutputDatabaseName + + N'.sys.all_columns WHERE object_id = (OBJECT_ID(''' + + @ObjectFullName + + N''')) AND name = ''spid'') + /*Add spid column*/ + ALTER TABLE ' + + @ObjectFullName + N' + ADD spid smallint NULL;'; + + EXEC sys.sp_executesql + @StringToExecute; + + /* If the table doesn't have the new wait_resource column, add it. See Github #3101. */ + SET @ObjectFullName = + @OutputDatabaseName + + N'.' + + @OutputSchemaName + + N'.' + + @OutputTableName; + + SET @StringToExecute = + N'IF NOT EXISTS (SELECT * FROM ' + + @OutputDatabaseName + + N'.sys.all_columns WHERE object_id = (OBJECT_ID(''' + + @ObjectFullName + + N''')) AND name = ''wait_resource'') + /*Add wait_resource column*/ + ALTER TABLE ' + + @ObjectFullName + + N' ADD wait_resource nvarchar(MAX) NULL;'; + EXEC(@StringToExecute); + END; + ELSE --if(@r is not null) --if it is null there is no table, create it from above execution + BEGIN + SELECT + @StringToExecute = + N'USE ' + + @OutputDatabaseName + + N'; + CREATE TABLE ' + + @OutputSchemaName + + N'.' + + @OutputTableName + + N' ( + ServerName nvarchar(256), + deadlock_type nvarchar(256), + event_date datetime, + database_name nvarchar(256), + spid SMALLINT, + deadlock_group nvarchar(256), + query xml, + object_names xml, + isolation_level nvarchar(256), + owner_mode nvarchar(256), + waiter_mode nvarchar(256), + transaction_count bigint, + login_name nvarchar(256), + host_name nvarchar(256), + client_app nvarchar(256), + wait_time bigint, + wait_resource nvarchar(max), + priority smallint, + log_used bigint, + last_tran_started datetime, + last_batch_started datetime, + last_batch_completed datetime, + transaction_name nvarchar(256), + owner_waiter_type nvarchar(256), + owner_activity nvarchar(256), + owner_waiter_activity nvarchar(256), + owner_merging nvarchar(256), + owner_spilling nvarchar(256), + owner_waiting_to_close nvarchar(256), + waiter_waiter_type nvarchar(256), + waiter_owner_activity nvarchar(256), + waiter_waiter_activity nvarchar(256), + waiter_merging nvarchar(256), + waiter_spilling nvarchar(256), + waiter_waiting_to_close nvarchar(256), + deadlock_graph xml + )', + @StringToExecuteParams = + N'@OutputDatabaseName nvarchar(200), + @OutputSchemaName nvarchar(100), + @OutputTableName nvarchar(200)'; + + EXEC sys.sp_executesql + @StringToExecute, + @StringToExecuteParams, + @OutputDatabaseName, + @OutputSchemaName, + @OutputTableName; + --table created. + + SELECT + @StringToExecute = + N'SELECT @r = name + FROM ' + + N'' + + @OutputDatabaseName + + N'' + + N'.sys.objects WHERE type_desc = ''USER_TABLE'' AND name = ''BlitzLockFindings''', + @StringToExecuteParams = + N'@OutputDatabaseName nvarchar(200), + @r nvarchar(200) OUTPUT'; + + EXEC sys.sp_executesql + @StringToExecute, + @StringToExecuteParams, + @OutputDatabaseName, + @r OUTPUT; + + IF(@r IS NULL) --if table does not exist + BEGIN + SELECT + @OutputTableFindings = N'[BlitzLockFindings]', + @StringToExecute = + N'USE ' + + @OutputDatabaseName + + '; + CREATE TABLE ' + + @OutputSchemaName + + N'.' + + @OutputTableFindings + + N' ( + ServerName nvarchar(256), + check_id INT, + database_name nvarchar(256), + object_name nvarchar(1000), + finding_group nvarchar(100), + finding nvarchar(4000) + );', + @StringToExecuteParams = + N'@OutputDatabaseName nvarchar(200), + @OutputSchemaName nvarchar(100), + @OutputTableFindings nvarchar(200)'; + + EXEC sys.sp_executesql + @StringToExecute, + @StringToExecuteParams, + @OutputDatabaseName, + @OutputSchemaName, + @OutputTableFindings; + END; + END; + --create synonym for deadlockfindings. + IF EXISTS + ( + SELECT + 1/0 + FROM sys.objects + WHERE name = 'DeadlockFindings' + AND type_desc='SYNONYM' + ) + BEGIN + RAISERROR('Found Synonym', 0, 1) WITH NOWAIT; + DROP SYNONYM DeadlockFindings; + END; + + SET @StringToExecute = + N'CREATE SYNONYM DeadlockFindings FOR ' + + @OutputDatabaseName + + N'.' + + @OutputSchemaName + + N'.' + + @OutputTableFindings; + + EXEC sys.sp_executesql + @StringToExecute; + + --create synonym for deadlock table. + IF EXISTS + ( + SELECT + 1/0 + FROM sys.objects + WHERE name = 'DeadLockTbl' + AND type_desc='SYNONYM' + ) + BEGIN + DROP SYNONYM DeadLockTbl; + END; + + SET @StringToExecute = + N'CREATE SYNONYM DeadLockTbl FOR ' + + @OutputDatabaseName + + N'.' + + @OutputSchemaName + + N'.' + @OutputTableName; + + EXEC sys.sp_executesql + @StringToExecute; + END; + END; + + CREATE TABLE + #t + ( + id int NOT NULL + ); - IF @ProductVersionMajor < 11.0 + /* WITH ROWCOUNT doesn't work on Amazon RDS - see: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2037 */ + IF @RDS = 0 + BEGIN; + BEGIN TRY; + UPDATE STATISTICS #t WITH ROWCOUNT = 100000000, PAGECOUNT = 100000000; + END TRY + BEGIN CATCH; + /* Misleading error returned, if run without permissions to update statistics the error returned is "Cannot find object". + Catching specific error, and returning message with better info. If any other error is returned, then throw as normal */ + IF (ERROR_NUMBER() = 1088) + BEGIN; + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Cannot run UPDATE STATISTICS on a #temp table without db_owner or sysadmin permissions %s', 0, 1, @d) WITH NOWAIT; + END; + ELSE + BEGIN; + THROW; + END; + END CATCH; + END; + + + IF @TargetSessionType IS NULL + BEGIN + IF @Azure = 0 + BEGIN + SELECT TOP (1) + @TargetSessionType = + t.target_name + FROM sys.dm_xe_sessions AS s + JOIN sys.dm_xe_session_targets AS t + ON s.address = t.event_session_address + WHERE s.name = @EventSessionName + ORDER BY t.target_name; + END; + + IF @Azure = 1 BEGIN - RAISERROR( - 'sp_BlitzLock will throw a bunch of angry errors on versions of SQL Server earlier than 2012.', - 0, - 1) WITH NOWAIT; - RETURN; + SELECT TOP (1) + @TargetSessionType = + t.target_name + FROM sys.dm_xe_database_sessions AS s + JOIN sys.dm_xe_database_session_targets AS t + ON s.address = t.event_session_address + WHERE s.name = @EventSessionName + ORDER BY t.target_name; END; + END + + IF (@TargetSessionType LIKE 'ring%' AND @EventSessionName NOT LIKE 'system_health%') + BEGIN + IF @Azure = 0 + BEGIN + INSERT + #x WITH(TABLOCK) + ( + x + ) + SELECT + x = + TRY_CAST + ( + t.target_data + AS xml + ) + FROM sys.dm_xe_session_targets AS t + JOIN sys.dm_xe_sessions AS s + ON s.address = t.event_session_address + WHERE s.name = @EventSessionName + AND t.target_name = N'ring_buffer'; + END; + + IF @Azure = 1 + BEGIN + INSERT + #x WITH(TABLOCK) + ( + x + ) + SELECT + x = + TRY_CAST + ( + t.target_data + AS xml + ) + FROM sys.dm_xe_database_session_targets AS t + JOIN sys.dm_xe_database_sessions AS s + ON s.address = t.event_session_address + WHERE s.name = @EventSessionName + AND t.target_name = N'ring_buffer'; + END; + END; - IF ((SELECT SERVERPROPERTY ('EDITION')) = 'SQL Azure' - AND - LOWER(@EventSessionPath) NOT LIKE 'http%') + IF (@TargetSessionType LIKE 'event%' AND @EventSessionName NOT LIKE 'system_health%') + BEGIN + IF @Azure = 0 BEGIN - RAISERROR( - 'The default storage path doesn''t work in Azure SQLDB/Managed instances. -You need to use an Azure storage account, and the path has to look like this: https://StorageAccount.blob.core.windows.net/Container/FileName.xel', - 0, - 1) WITH NOWAIT; - RETURN; + SELECT + @SessionId = t.event_session_id, + @TargetSessionId = t.target_id + FROM sys.server_event_session_targets t + JOIN sys.server_event_sessions s + ON s.event_session_id = t.event_session_id + WHERE t.name = @TargetSessionType + AND s.name = @EventSessionName; + + SELECT + @FileName = + CASE + WHEN f.file_name LIKE '%.xel' + THEN REPLACE(f.file_name, N'.xel', N'*.xel') + ELSE f.file_name + N'*.xel' + END + FROM + ( + SELECT + file_name = + CONVERT + ( + nvarchar(4000), + f.value + ) + FROM sys.server_event_session_fields AS f + WHERE f.event_session_id = @SessionId + AND f.object_id = @TargetSessionId + AND f.name = N'filename' + ) AS f; END; + + IF @Azure = 1 + BEGIN + SELECT + @SessionId = t.event_session_id, + @TargetSessionId = t.target_id + FROM sys.dm_xe_database_session_targets t + JOIN sys.dm_xe_database_sessions s + ON s.event_session_id = t.event_session_id + WHERE t.name = @TargetSessionType + AND s.name = @EventSessionName; + + SELECT + @FileName = + CASE + WHEN f.file_name LIKE '%.xel' + THEN REPLACE(f.file_name, N'.xel', N'*.xel') + ELSE f.file_name + N'*.xel' + END + FROM + ( + SELECT + file_name = + CONVERT + ( + nvarchar(4000), + f.value + ) + FROM sys.server_event_session_fields AS f + WHERE f.event_session_id = @SessionId + AND f.object_id = @TargetSessionId + AND f.name = N'filename' + ) AS f; + END; + + INSERT + #x WITH(TABLOCK) + ( + x + ) + SELECT + x = + TRY_CAST + ( + f.event_data + AS xml + ) + FROM sys.fn_xe_file_target_read_file + ( + @FileName, + NULL, + NULL, + NULL + ) AS f; + END; + + + IF (@TargetSessionType LIKE 'ring%' AND @EventSessionName NOT LIKE 'system_health%') + BEGIN + INSERT + #deadlock_data + ( + deadlock_xml + ) + SELECT + deadlock_xml = + e.x.query('.') + FROM #x AS x + CROSS APPLY x.x.nodes('/RingBufferTarget/event') AS e(x); + END; + + IF (@TargetSessionType LIKE 'event_file%' AND @EventSessionName NOT LIKE 'system_health%') + BEGIN + INSERT + #deadlock_data + ( + deadlock_xml + ) + SELECT + deadlock_xml = + e.x.query('.') + FROM #x AS x + CROSS APPLY x.x.nodes('/event') AS e(x); + END; + + IF @Debug = 1 + BEGIN + SELECT table_name = N'#deadlock_data', bx.* FROM #deadlock_data AS bx; + END; + /*Grab the initial set of xml to parse*/ + IF (@TargetSessionType LIKE 'event%' AND @EventSessionName LIKE 'system_health%') + BEGIN + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Grab the initial set of xml to parse at %s', 0, 1, @d) WITH NOWAIT; - IF @Top IS NULL - SET @Top = 2147483647; - - IF @StartDate IS NULL - SET @StartDate = '19000101'; - - IF @EndDate IS NULL - SET @EndDate = '99991231'; - - - IF OBJECT_ID('tempdb..#deadlock_data') IS NOT NULL - DROP TABLE #deadlock_data; - - IF OBJECT_ID('tempdb..#deadlock_process') IS NOT NULL - DROP TABLE #deadlock_process; - - IF OBJECT_ID('tempdb..#deadlock_stack') IS NOT NULL - DROP TABLE #deadlock_stack; - - IF OBJECT_ID('tempdb..#deadlock_resource') IS NOT NULL - DROP TABLE #deadlock_resource; - - IF OBJECT_ID('tempdb..#deadlock_owner_waiter') IS NOT NULL - DROP TABLE #deadlock_owner_waiter; - - IF OBJECT_ID('tempdb..#deadlock_findings') IS NOT NULL - DROP TABLE #deadlock_findings; - - CREATE TABLE #deadlock_findings + WITH + xml AS ( - id INT IDENTITY(1, 1) PRIMARY KEY CLUSTERED, - check_id INT NOT NULL, - database_name NVARCHAR(256), - object_name NVARCHAR(1000), - finding_group NVARCHAR(100), - finding NVARCHAR(4000) - ); - - DECLARE @d VARCHAR(40), @StringToExecute NVARCHAR(4000),@StringToExecuteParams NVARCHAR(500),@r NVARCHAR(200),@OutputTableFindings NVARCHAR(100),@DeadlockCount INT; - DECLARE @ServerName NVARCHAR(256) - DECLARE @OutputDatabaseCheck BIT; - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - SET @OutputTableFindings = '[BlitzLockFindings]' - SET @ServerName = (select @@ServerName) - if(@OutputDatabaseName is not null) - BEGIN --if databaseName is set do some sanity checks and put [] around def. - if( (select name from sys.databases where name=@OutputDatabaseName) is null ) --if database is invalid raiserror and set bitcheck - BEGIN - RAISERROR('Database Name for output of table is invalid please correct, Output to Table will not be preformed', 0, 1, @d) WITH NOWAIT; - set @OutputDatabaseCheck = -1 -- -1 invalid/false, 0 = good/true - END - ELSE - BEGIN - set @OutputDatabaseCheck = 0 - select @StringToExecute = N'select @r = name from ' + '' + @OutputDatabaseName + - '' + '.sys.objects where type_desc=''USER_TABLE'' and name=' + '''' + @OutputTableName + '''', - @StringToExecuteParams = N'@OutputDatabaseName NVARCHAR(200),@OutputTableName NVARCHAR(200),@r NVARCHAR(200) OUTPUT' - exec sp_executesql @StringToExecute,@StringToExecuteParams,@OutputDatabaseName,@OutputTableName,@r OUTPUT - --put covers around all before. - SELECT @OutputDatabaseName = QUOTENAME(@OutputDatabaseName), - @OutputTableName = QUOTENAME(@OutputTableName), - @OutputSchemaName = QUOTENAME(@OutputSchemaName) - if(@r is not null) --if it is not null, there is a table, so check for newly added columns - BEGIN - /* If the table doesn't have the new spid column, add it. See Github #3101. */ - SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableName; - SET @StringToExecute = N'IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns - WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + ''')) AND name = ''spid'') - ALTER TABLE ' + @ObjectFullName + N' ADD spid SMALLINT NULL;'; - EXEC(@StringToExecute); - - /* If the table doesn't have the new wait_resource column, add it. See Github #3101. */ - SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableName; - SET @StringToExecute = N'IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns - WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + ''')) AND name = ''wait_resource'') - ALTER TABLE ' + @ObjectFullName + N' ADD wait_resource NVARCHAR(MAX) NULL;'; - EXEC(@StringToExecute); - END - ELSE --if(@r is not null) --if it is null there is no table, create it from above execution - BEGIN - select @StringToExecute = N'use ' + @OutputDatabaseName + ';create table ' + @OutputSchemaName + '.' + @OutputTableName + ' ( - ServerName NVARCHAR(256), - deadlock_type NVARCHAR(256), - event_date datetime, - database_name NVARCHAR(256), - spid SMALLINT, - deadlock_group NVARCHAR(256), - query XML, - object_names XML, - isolation_level NVARCHAR(256), - owner_mode NVARCHAR(256), - waiter_mode NVARCHAR(256), - transaction_count bigint, - login_name NVARCHAR(256), - host_name NVARCHAR(256), - client_app NVARCHAR(256), - wait_time BIGINT, - wait_resource NVARCHAR(max), - priority smallint, - log_used BIGINT, - last_tran_started datetime, - last_batch_started datetime, - last_batch_completed datetime, - transaction_name NVARCHAR(256), - owner_waiter_type NVARCHAR(256), - owner_activity NVARCHAR(256), - owner_waiter_activity NVARCHAR(256), - owner_merging NVARCHAR(256), - owner_spilling NVARCHAR(256), - owner_waiting_to_close NVARCHAR(256), - waiter_waiter_type NVARCHAR(256), - waiter_owner_activity NVARCHAR(256), - waiter_waiter_activity NVARCHAR(256), - waiter_merging NVARCHAR(256), - waiter_spilling NVARCHAR(256), - waiter_waiting_to_close NVARCHAR(256), - deadlock_graph XML)', - @StringToExecuteParams = N'@OutputDatabaseName NVARCHAR(200),@OutputSchemaName NVARCHAR(100),@OutputTableName NVARCHAR(200)' - exec sp_executesql @StringToExecute, @StringToExecuteParams,@OutputDatabaseName,@OutputSchemaName,@OutputTableName - --table created. - select @StringToExecute = N'select @r = name from ' + '' + @OutputDatabaseName + - '' + '.sys.objects where type_desc=''USER_TABLE'' and name=''BlitzLockFindings''', - @StringToExecuteParams = N'@OutputDatabaseName NVARCHAR(200),@r NVARCHAR(200) OUTPUT' - exec sp_executesql @StringToExecute,@StringToExecuteParams,@OutputDatabaseName,@r OUTPUT - if(@r is null) --if table does not excist - BEGIN - select @OutputTableFindings=N'[BlitzLockFindings]', - @StringToExecute = N'use ' + @OutputDatabaseName + ';create table ' + @OutputSchemaName + '.' + @OutputTableFindings + ' ( - ServerName NVARCHAR(256), - check_id INT, - database_name NVARCHAR(256), - object_name NVARCHAR(1000), - finding_group NVARCHAR(100), - finding NVARCHAR(4000))', - @StringToExecuteParams = N'@OutputDatabaseName NVARCHAR(200),@OutputSchemaName NVARCHAR(100),@OutputTableFindings NVARCHAR(200)' - exec sp_executesql @StringToExecute, @StringToExecuteParams, @OutputDatabaseName,@OutputSchemaName,@OutputTableFindings - - END - - END - --create synonym for deadlockfindings. - if((select name from sys.objects where name='DeadlockFindings' and type_desc='SYNONYM')IS NOT NULL) - BEGIN - RAISERROR('found synonym', 0, 1) WITH NOWAIT; - drop synonym DeadlockFindings; - END - set @StringToExecute = 'CREATE SYNONYM DeadlockFindings FOR ' + @OutputDatabaseName + '.' + @OutputSchemaName + '.' + @OutputTableFindings; - exec sp_executesql @StringToExecute - - --create synonym for deadlock table. - if((select name from sys.objects where name='DeadLockTbl' and type_desc='SYNONYM') IS NOT NULL) - BEGIN - drop SYNONYM DeadLockTbl; - END - set @StringToExecute = 'CREATE SYNONYM DeadLockTbl FOR ' + @OutputDatabaseName + '.' + @OutputSchemaName + '.' + @OutputTableName; - exec sp_executesql @StringToExecute - - END - END - - - CREATE TABLE #t (id INT NOT NULL); - - /* WITH ROWCOUNT doesn't work on Amazon RDS - see: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2037 */ - IF LEFT(CAST(SERVERPROPERTY('ComputerNamePhysicalNetBIOS') AS VARCHAR(8000)), 8) <> 'EC2AMAZ-' - AND LEFT(CAST(SERVERPROPERTY('MachineName') AS VARCHAR(8000)), 8) <> 'EC2AMAZ-' - AND db_id('rdsadmin') IS NULL - BEGIN; - BEGIN TRY; - UPDATE STATISTICS #t WITH ROWCOUNT = 100000000, PAGECOUNT = 100000000; - END TRY - BEGIN CATCH; - /* Misleading error returned, if run without permissions to update statistics the error returned is "Cannot find object". - Catching specific error, and returning message with better info. If any other error is returned, then throw as normal */ - IF (ERROR_NUMBER() = 1088) - BEGIN; - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Cannot run UPDATE STATISTICS on temp table without db_owner or sysadmin permissions %s', 0, 1, @d) WITH NOWAIT; - END; - ELSE - BEGIN; - THROW; - END; - END CATCH; - END; - - /*Grab the initial set of XML to parse*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Grab the initial set of XML to parse at %s', 0, 1, @d) WITH NOWAIT; - WITH xml - AS ( SELECT CONVERT(XML, event_data) AS deadlock_xml - FROM sys.fn_xe_file_target_read_file(@EventSessionPath, NULL, NULL, NULL) ) - SELECT ISNULL(xml.deadlock_xml, '') AS deadlock_xml - INTO #deadlock_data - FROM xml + SELECT + deadlock_xml = + TRY_CAST(event_data AS xml) + FROM sys.fn_xe_file_target_read_file + ( + 'system_health*.xel', + NULL, + NULL, + NULL + ) + ) + INSERT + #deadlock_data WITH(TABLOCK) + SELECT + deadlock_xml = + xml.deadlock_xml, + event_time = + xml.deadlock_xml.value('(/event/@timestamp)[1]', 'datetime') + FROM xml LEFT JOIN #t AS t - ON 1 = 1 + ON 1 = 0 CROSS APPLY xml.deadlock_xml.nodes('/event/@name') AS x(c) - WHERE 1 = 1 - AND x.c.exist('/event/@name[ .= "xml_deadlock_report"]') = 1 - AND CONVERT(datetime, SWITCHOFFSET(CONVERT(datetimeoffset, xml.deadlock_xml.value('(/event/@timestamp)[1]', 'datetime') ), DATENAME(TzOffset, SYSDATETIMEOFFSET()))) > @StartDate - AND CONVERT(datetime, SWITCHOFFSET(CONVERT(datetimeoffset, xml.deadlock_xml.value('(/event/@timestamp)[1]', 'datetime') ), DATENAME(TzOffset, SYSDATETIMEOFFSET()))) < @EndDate - ORDER BY xml.deadlock_xml.value('(/event/@timestamp)[1]', 'datetime') DESC - OPTION ( RECOMPILE ); - - /*Optimization: if we got back more rows than @Top, remove them. This seems to be better than using @Top in the query above as that results in excessive memory grant*/ - SET @DeadlockCount = @@ROWCOUNT - IF( @Top < @DeadlockCount ) BEGIN - WITH T - AS ( - SELECT TOP ( @DeadlockCount - @Top) * - FROM #deadlock_data - ORDER BY #deadlock_data.deadlock_xml.value('(/event/@timestamp)[1]', 'datetime') ASC) - DELETE FROM T - END - - /*Parse process and input buffer XML*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Parse process and input buffer XML %s', 0, 1, @d) WITH NOWAIT; + WHERE x.c.exist('/event/@name[ .= "xml_deadlock_report"]') = 1 + AND xml.deadlock_xml.value('(/event/@timestamp)[1]', 'datetime') >= DATEADD(MINUTE, DATEDIFF(MINUTE, GETUTCDATE(), SYSDATETIME()), @StartDate) + AND xml.deadlock_xml.value('(/event/@timestamp)[1]', 'datetime') < DATEADD(MINUTE, DATEDIFF(MINUTE, GETUTCDATE(), SYSDATETIME()), @EndDate) + OPTION (RECOMPILE); + + SET @DeadlockCount = @@ROWCOUNT; + + /*Optimization: if we got back more rows than @Top, remove them. This seems to be better than using @Top in the query above as that results in excessive memory grant*/ + IF(@Top < @DeadlockCount) + BEGIN + WITH + t AS + ( + SELECT TOP (@DeadlockCount - @Top) + dd.* + FROM #deadlock_data AS dd + ORDER BY dd.event_time ASC + ) + DELETE FROM t; + END; + + /*Parse process and input buffer xml*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Parse process and input buffer xml %s', 0, 1, @d) WITH NOWAIT; SELECT q.event_date, q.victim_id, - CONVERT(BIT, q.is_parallel) AS is_parallel, + CONVERT(bit, q.is_parallel) AS is_parallel, q.deadlock_graph, q.id, q.spid, @@ -398,344 +746,344 @@ You need to use an Azure storage account, and the path has to look like this: ht ISNULL(ca2.ib.query('.'), '') AS input_buffer INTO #deadlock_process FROM ( SELECT dd.deadlock_xml, - CONVERT(DATETIME2(7), SWITCHOFFSET(CONVERT(datetimeoffset, dd.event_date ), DATENAME(TzOffset, SYSDATETIMEOFFSET()))) AS event_date, + CONVERT(datetime2(7), SWITCHOFFSET(CONVERT(datetimeoffset, dd.event_date ), DATENAME(TZOFFSET, SYSDATETIMEOFFSET()))) AS event_date, dd.victim_id, - CONVERT(tinyint, dd.is_parallel) + CONVERT(tinyint, dd.is_parallel_batch) AS is_parallel, + CONVERT(tinyint, dd.is_parallel) + CONVERT(tinyint, dd.is_parallel_batch) AS is_parallel, dd.deadlock_graph, - ca.dp.value('@id', 'NVARCHAR(256)') AS id, - ca.dp.value('@spid', 'SMALLINT') AS spid, - ca.dp.value('@currentdb', 'BIGINT') AS database_id, + ca.dp.value('@id', 'nvarchar(256)') AS id, + ca.dp.value('@spid', 'SMALLINT') AS spid, + ca.dp.value('@currentdb', 'bigint') AS database_id, ca.dp.value('@priority', 'SMALLINT') AS priority, - ca.dp.value('@logused', 'BIGINT') AS log_used, - ca.dp.value('@waitresource', 'NVARCHAR(256)') AS wait_resource, - ca.dp.value('@waittime', 'BIGINT') AS wait_time, - ca.dp.value('@transactionname', 'NVARCHAR(256)') AS transaction_name, + ca.dp.value('@logused', 'bigint') AS log_used, + ca.dp.value('@waitresource', 'nvarchar(256)') AS wait_resource, + ca.dp.value('@waittime', 'bigint') AS wait_time, + ca.dp.value('@transactionname', 'nvarchar(256)') AS transaction_name, ca.dp.value('@lasttranstarted', 'DATETIME2(7)') AS last_tran_started, ca.dp.value('@lastbatchstarted', 'DATETIME2(7)') AS last_batch_started, ca.dp.value('@lastbatchcompleted', 'DATETIME2(7)') AS last_batch_completed, - ca.dp.value('@lockMode', 'NVARCHAR(256)') AS lock_mode, - ca.dp.value('@trancount', 'BIGINT') AS transaction_count, - ca.dp.value('@clientapp', 'NVARCHAR(256)') AS client_app, - ca.dp.value('@hostname', 'NVARCHAR(256)') AS host_name, - ca.dp.value('@loginname', 'NVARCHAR(256)') AS login_name, - ca.dp.value('@isolationlevel', 'NVARCHAR(256)') AS isolation_level, + ca.dp.value('@lockMode', 'nvarchar(256)') AS lock_mode, + ca.dp.value('@trancount', 'bigint') AS transaction_count, + ca.dp.value('@clientapp', 'nvarchar(256)') AS client_app, + ca.dp.value('@hostname', 'nvarchar(256)') AS host_name, + ca.dp.value('@loginname', 'nvarchar(256)') AS login_name, + ca.dp.value('@isolationlevel', 'nvarchar(256)') AS isolation_level, ISNULL(ca.dp.query('.'), '') AS process_xml FROM ( SELECT d1.deadlock_xml, d1.deadlock_xml.value('(event/@timestamp)[1]', 'DATETIME2') AS event_date, - d1.deadlock_xml.value('(//deadlock/victim-list/victimProcess/@id)[1]', 'NVARCHAR(256)') AS victim_id, - d1.deadlock_xml.exist('//deadlock/resource-list/exchangeEvent') AS is_parallel, - d1.deadlock_xml.exist('//deadlock/resource-list/SyncPoint') AS is_parallel_batch, + d1.deadlock_xml.value('(//deadlock/victim-list/victimProcess/@id)[1]', 'nvarchar(256)') AS victim_id, + d1.deadlock_xml.exist('//deadlock/resource-list/exchangeEvent') AS is_parallel, + d1.deadlock_xml.exist('//deadlock/resource-list/SyncPoint') AS is_parallel_batch, d1.deadlock_xml.query('/event/data/value/deadlock') AS deadlock_graph FROM #deadlock_data AS d1 ) AS dd CROSS APPLY dd.deadlock_xml.nodes('//deadlock/process-list/process') AS ca(dp) - WHERE (ca.dp.value('@currentdb', 'BIGINT') = DB_ID(@DatabaseName) OR @DatabaseName IS NULL) - AND (ca.dp.value('@clientapp', 'NVARCHAR(256)') = @AppName OR @AppName IS NULL) - AND (ca.dp.value('@hostname', 'NVARCHAR(256)') = @HostName OR @HostName IS NULL) - AND (ca.dp.value('@loginname', 'NVARCHAR(256)') = @LoginName OR @LoginName IS NULL) + WHERE (ca.dp.value('@currentdb', 'bigint') = DB_ID(@DatabaseName) OR @DatabaseName IS NULL) + AND (ca.dp.value('@clientapp', 'nvarchar(256)') = @AppName OR @AppName IS NULL) + AND (ca.dp.value('@hostname', 'nvarchar(256)') = @HostName OR @HostName IS NULL) + AND (ca.dp.value('@loginname', 'nvarchar(256)') = @LoginName OR @LoginName IS NULL) ) AS q CROSS APPLY q.deadlock_xml.nodes('//deadlock/process-list/process/inputbuf') AS ca2(ib) - OPTION ( RECOMPILE ); + OPTION (RECOMPILE); - /*Parse execution stack XML*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Parse execution stack XML %s', 0, 1, @d) WITH NOWAIT; + /*Parse execution stack xml*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Parse execution stack xml %s', 0, 1, @d) WITH NOWAIT; SELECT DISTINCT - dp.id, - dp.event_date, - ca.dp.value('@procname', 'NVARCHAR(1000)') AS proc_name, - ca.dp.value('@sqlhandle', 'NVARCHAR(128)') AS sql_handle + dp.id, + dp.event_date, + ca.dp.value('@procname', 'nvarchar(1000)') AS proc_name, + ca.dp.value('@sqlhandle', 'nvarchar(128)') AS sql_handle INTO #deadlock_stack FROM #deadlock_process AS dp CROSS APPLY dp.process_xml.nodes('//executionStack/frame') AS ca(dp) - WHERE (ca.dp.value('@procname', 'NVARCHAR(256)') = @StoredProcName OR @StoredProcName IS NULL) - OPTION ( RECOMPILE ); + WHERE (ca.dp.value('@procname', 'nvarchar(256)') = @StoredProcName OR @StoredProcName IS NULL) + OPTION (RECOMPILE); - /*Grab the full resource list*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + /*Grab the full resource list*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Grab the full resource list %s', 0, 1, @d) WITH NOWAIT; SELECT - CONVERT(DATETIME2(7), SWITCHOFFSET(CONVERT(datetimeoffset, dr.event_date), DATENAME(TzOffset, SYSDATETIMEOFFSET()))) AS event_date, + CONVERT(datetime2(7), SWITCHOFFSET(CONVERT(datetimeoffset, dr.event_date), DATENAME(TZOFFSET, SYSDATETIMEOFFSET()))) AS event_date, dr.victim_id, dr.resource_xml INTO #deadlock_resource FROM ( SELECT dd.deadlock_xml.value('(event/@timestamp)[1]', 'DATETIME2') AS event_date, - dd.deadlock_xml.value('(//deadlock/victim-list/victimProcess/@id)[1]', 'NVARCHAR(256)') AS victim_id, - ISNULL(ca.dp.query('.'), '') AS resource_xml + dd.deadlock_xml.value('(//deadlock/victim-list/victimProcess/@id)[1]', 'nvarchar(256)') AS victim_id, + ISNULL(ca.dp.query('.'), '') AS resource_xml FROM #deadlock_data AS dd CROSS APPLY dd.deadlock_xml.nodes('//deadlock/resource-list') AS ca(dp) ) AS dr - OPTION ( RECOMPILE ); + OPTION (RECOMPILE); - /*Parse object locks*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + /*Parse object locks*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Parse object locks %s', 0, 1, @d) WITH NOWAIT; SELECT DISTINCT - ca.event_date, - ca.database_id, - ca.object_name, - ca.lock_mode, - ca.index_name, - ca.associatedObjectId, - w.l.value('@id', 'NVARCHAR(256)') AS waiter_id, - w.l.value('@mode', 'NVARCHAR(256)') AS waiter_mode, - o.l.value('@id', 'NVARCHAR(256)') AS owner_id, - o.l.value('@mode', 'NVARCHAR(256)') AS owner_mode, - N'OBJECT' AS lock_type + ca.event_date, + ca.database_id, + ca.object_name, + ca.lock_mode, + ca.index_name, + ca.associatedObjectId, + w.l.value('@id', 'nvarchar(256)') AS waiter_id, + w.l.value('@mode', 'nvarchar(256)') AS waiter_mode, + o.l.value('@id', 'nvarchar(256)') AS owner_id, + o.l.value('@mode', 'nvarchar(256)') AS owner_mode, + N'OBJECT' AS lock_type INTO #deadlock_owner_waiter - FROM ( + FROM ( SELECT dr.event_date, - ca.dr.value('@dbid', 'BIGINT') AS database_id, - ca.dr.value('@objectname', 'NVARCHAR(256)') AS object_name, - ca.dr.value('@mode', 'NVARCHAR(256)') AS lock_mode, - ca.dr.value('@indexname', 'NVARCHAR(256)') AS index_name, - ca.dr.value('@associatedObjectId', 'BIGINT') AS associatedObjectId, - ca.dr.query('.') AS dr + ca.dr.value('@dbid', 'bigint') AS database_id, + ca.dr.value('@objectname', 'nvarchar(256)') AS object_name, + ca.dr.value('@mode', 'nvarchar(256)') AS lock_mode, + ca.dr.value('@indexname', 'nvarchar(256)') AS index_name, + ca.dr.value('@associatedObjectId', 'bigint') AS associatedObjectId, + ca.dr.query('.') AS dr FROM #deadlock_resource AS dr CROSS APPLY dr.resource_xml.nodes('//resource-list/objectlock') AS ca(dr) - ) AS ca + ) AS ca CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l) CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l) - WHERE (ca.object_name = @ObjectName OR @ObjectName IS NULL) - OPTION ( RECOMPILE ); + WHERE (ca.object_name = @ObjectName OR @ObjectName IS NULL) + OPTION (RECOMPILE); - /*Parse page locks*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + /*Parse page locks*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Parse page locks %s', 0, 1, @d) WITH NOWAIT; INSERT #deadlock_owner_waiter WITH(TABLOCKX) SELECT DISTINCT - ca.event_date, - ca.database_id, - ca.object_name, - ca.lock_mode, - ca.index_name, - ca.associatedObjectId, - w.l.value('@id', 'NVARCHAR(256)') AS waiter_id, - w.l.value('@mode', 'NVARCHAR(256)') AS waiter_mode, - o.l.value('@id', 'NVARCHAR(256)') AS owner_id, - o.l.value('@mode', 'NVARCHAR(256)') AS owner_mode, - N'PAGE' AS lock_type - FROM ( + ca.event_date, + ca.database_id, + ca.object_name, + ca.lock_mode, + ca.index_name, + ca.associatedObjectId, + w.l.value('@id', 'nvarchar(256)') AS waiter_id, + w.l.value('@mode', 'nvarchar(256)') AS waiter_mode, + o.l.value('@id', 'nvarchar(256)') AS owner_id, + o.l.value('@mode', 'nvarchar(256)') AS owner_mode, + N'PAGE' AS lock_type + FROM ( SELECT dr.event_date, - ca.dr.value('@dbid', 'BIGINT') AS database_id, - ca.dr.value('@objectname', 'NVARCHAR(256)') AS object_name, - ca.dr.value('@mode', 'NVARCHAR(256)') AS lock_mode, - ca.dr.value('@indexname', 'NVARCHAR(256)') AS index_name, - ca.dr.value('@associatedObjectId', 'BIGINT') AS associatedObjectId, - ca.dr.query('.') AS dr + ca.dr.value('@dbid', 'bigint') AS database_id, + ca.dr.value('@objectname', 'nvarchar(256)') AS object_name, + ca.dr.value('@mode', 'nvarchar(256)') AS lock_mode, + ca.dr.value('@indexname', 'nvarchar(256)') AS index_name, + ca.dr.value('@associatedObjectId', 'bigint') AS associatedObjectId, + ca.dr.query('.') AS dr FROM #deadlock_resource AS dr CROSS APPLY dr.resource_xml.nodes('//resource-list/pagelock') AS ca(dr) - ) AS ca + ) AS ca CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l) CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l) - OPTION ( RECOMPILE ); + OPTION (RECOMPILE); - /*Parse key locks*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + /*Parse key locks*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Parse key locks %s', 0, 1, @d) WITH NOWAIT; INSERT #deadlock_owner_waiter WITH(TABLOCKX) SELECT DISTINCT - ca.event_date, - ca.database_id, - ca.object_name, - ca.lock_mode, - ca.index_name, - ca.associatedObjectId, - w.l.value('@id', 'NVARCHAR(256)') AS waiter_id, - w.l.value('@mode', 'NVARCHAR(256)') AS waiter_mode, - o.l.value('@id', 'NVARCHAR(256)') AS owner_id, - o.l.value('@mode', 'NVARCHAR(256)') AS owner_mode, - N'KEY' AS lock_type - FROM ( + ca.event_date, + ca.database_id, + ca.object_name, + ca.lock_mode, + ca.index_name, + ca.associatedObjectId, + w.l.value('@id', 'nvarchar(256)') AS waiter_id, + w.l.value('@mode', 'nvarchar(256)') AS waiter_mode, + o.l.value('@id', 'nvarchar(256)') AS owner_id, + o.l.value('@mode', 'nvarchar(256)') AS owner_mode, + N'KEY' AS lock_type + FROM ( SELECT dr.event_date, - ca.dr.value('@dbid', 'BIGINT') AS database_id, - ca.dr.value('@objectname', 'NVARCHAR(256)') AS object_name, - ca.dr.value('@mode', 'NVARCHAR(256)') AS lock_mode, - ca.dr.value('@indexname', 'NVARCHAR(256)') AS index_name, - ca.dr.value('@associatedObjectId', 'BIGINT') AS associatedObjectId, - ca.dr.query('.') AS dr + ca.dr.value('@dbid', 'bigint') AS database_id, + ca.dr.value('@objectname', 'nvarchar(256)') AS object_name, + ca.dr.value('@mode', 'nvarchar(256)') AS lock_mode, + ca.dr.value('@indexname', 'nvarchar(256)') AS index_name, + ca.dr.value('@associatedObjectId', 'bigint') AS associatedObjectId, + ca.dr.query('.') AS dr FROM #deadlock_resource AS dr CROSS APPLY dr.resource_xml.nodes('//resource-list/keylock') AS ca(dr) - ) AS ca + ) AS ca CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l) CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l) - OPTION ( RECOMPILE ); + OPTION (RECOMPILE); - /*Parse RID locks*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + /*Parse RID locks*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Parse RID locks %s', 0, 1, @d) WITH NOWAIT; INSERT #deadlock_owner_waiter WITH(TABLOCKX) SELECT DISTINCT - ca.event_date, - ca.database_id, - ca.object_name, - ca.lock_mode, - ca.index_name, - ca.associatedObjectId, - w.l.value('@id', 'NVARCHAR(256)') AS waiter_id, - w.l.value('@mode', 'NVARCHAR(256)') AS waiter_mode, - o.l.value('@id', 'NVARCHAR(256)') AS owner_id, - o.l.value('@mode', 'NVARCHAR(256)') AS owner_mode, - N'RID' AS lock_type - FROM ( + ca.event_date, + ca.database_id, + ca.object_name, + ca.lock_mode, + ca.index_name, + ca.associatedObjectId, + w.l.value('@id', 'nvarchar(256)') AS waiter_id, + w.l.value('@mode', 'nvarchar(256)') AS waiter_mode, + o.l.value('@id', 'nvarchar(256)') AS owner_id, + o.l.value('@mode', 'nvarchar(256)') AS owner_mode, + N'RID' AS lock_type + FROM ( SELECT dr.event_date, - ca.dr.value('@dbid', 'BIGINT') AS database_id, - ca.dr.value('@objectname', 'NVARCHAR(256)') AS object_name, - ca.dr.value('@mode', 'NVARCHAR(256)') AS lock_mode, - ca.dr.value('@indexname', 'NVARCHAR(256)') AS index_name, - ca.dr.value('@associatedObjectId', 'BIGINT') AS associatedObjectId, - ca.dr.query('.') AS dr + ca.dr.value('@dbid', 'bigint') AS database_id, + ca.dr.value('@objectname', 'nvarchar(256)') AS object_name, + ca.dr.value('@mode', 'nvarchar(256)') AS lock_mode, + ca.dr.value('@indexname', 'nvarchar(256)') AS index_name, + ca.dr.value('@associatedObjectId', 'bigint') AS associatedObjectId, + ca.dr.query('.') AS dr FROM #deadlock_resource AS dr CROSS APPLY dr.resource_xml.nodes('//resource-list/ridlock') AS ca(dr) - ) AS ca + ) AS ca CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l) CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l) - OPTION ( RECOMPILE ); + OPTION (RECOMPILE); - /*Parse row group locks*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + /*Parse row group locks*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Parse row group locks %s', 0, 1, @d) WITH NOWAIT; INSERT #deadlock_owner_waiter WITH(TABLOCKX) SELECT DISTINCT - ca.event_date, - ca.database_id, - ca.object_name, - ca.lock_mode, - ca.index_name, - ca.associatedObjectId, - w.l.value('@id', 'NVARCHAR(256)') AS waiter_id, - w.l.value('@mode', 'NVARCHAR(256)') AS waiter_mode, - o.l.value('@id', 'NVARCHAR(256)') AS owner_id, - o.l.value('@mode', 'NVARCHAR(256)') AS owner_mode, - N'ROWGROUP' AS lock_type - FROM ( + ca.event_date, + ca.database_id, + ca.object_name, + ca.lock_mode, + ca.index_name, + ca.associatedObjectId, + w.l.value('@id', 'nvarchar(256)') AS waiter_id, + w.l.value('@mode', 'nvarchar(256)') AS waiter_mode, + o.l.value('@id', 'nvarchar(256)') AS owner_id, + o.l.value('@mode', 'nvarchar(256)') AS owner_mode, + N'ROWGROUP' AS lock_type + FROM ( SELECT dr.event_date, - ca.dr.value('@dbid', 'BIGINT') AS database_id, - ca.dr.value('@objectname', 'NVARCHAR(256)') AS object_name, - ca.dr.value('@mode', 'NVARCHAR(256)') AS lock_mode, - ca.dr.value('@indexname', 'NVARCHAR(256)') AS index_name, - ca.dr.value('@associatedObjectId', 'BIGINT') AS associatedObjectId, - ca.dr.query('.') AS dr + ca.dr.value('@dbid', 'bigint') AS database_id, + ca.dr.value('@objectname', 'nvarchar(256)') AS object_name, + ca.dr.value('@mode', 'nvarchar(256)') AS lock_mode, + ca.dr.value('@indexname', 'nvarchar(256)') AS index_name, + ca.dr.value('@associatedObjectId', 'bigint') AS associatedObjectId, + ca.dr.query('.') AS dr FROM #deadlock_resource AS dr CROSS APPLY dr.resource_xml.nodes('//resource-list/rowgrouplock') AS ca(dr) - ) AS ca + ) AS ca CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l) CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l) - OPTION ( RECOMPILE ); + OPTION (RECOMPILE); - UPDATE d - SET d.index_name = d.object_name - + '.HEAP' - FROM #deadlock_owner_waiter AS d - WHERE lock_type IN (N'HEAP', N'RID') - OPTION(RECOMPILE); + UPDATE d + SET d.index_name = d.object_name + + '.HEAP' + FROM #deadlock_owner_waiter AS d + WHERE d.lock_type IN (N'HEAP', N'RID') + OPTION(RECOMPILE); - /*Parse parallel deadlocks*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + /*Parse parallel deadlocks*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Parse parallel deadlocks %s', 0, 1, @d) WITH NOWAIT; SELECT DISTINCT - ca.id, - ca.event_date, - ca.wait_type, - ca.node_id, - ca.waiter_type, - ca.owner_activity, - ca.waiter_activity, - ca.merging, - ca.spilling, - ca.waiting_to_close, - w.l.value('@id', 'NVARCHAR(256)') AS waiter_id, - o.l.value('@id', 'NVARCHAR(256)') AS owner_id - INTO #deadlock_resource_parallel - FROM ( - SELECT dr.event_date, - ca.dr.value('@id', 'NVARCHAR(256)') AS id, - ca.dr.value('@WaitType', 'NVARCHAR(256)') AS wait_type, - ca.dr.value('@nodeId', 'BIGINT') AS node_id, - /* These columns are in 2017 CU5 ONLY */ - ca.dr.value('@waiterType', 'NVARCHAR(256)') AS waiter_type, - ca.dr.value('@ownerActivity', 'NVARCHAR(256)') AS owner_activity, - ca.dr.value('@waiterActivity', 'NVARCHAR(256)') AS waiter_activity, - ca.dr.value('@merging', 'NVARCHAR(256)') AS merging, - ca.dr.value('@spilling', 'NVARCHAR(256)') AS spilling, - ca.dr.value('@waitingToClose', 'NVARCHAR(256)') AS waiting_to_close, + ca.id, + ca.event_date, + ca.wait_type, + ca.node_id, + ca.waiter_type, + ca.owner_activity, + ca.waiter_activity, + ca.merging, + ca.spilling, + ca.waiting_to_close, + w.l.value('@id', 'nvarchar(256)') AS waiter_id, + o.l.value('@id', 'nvarchar(256)') AS owner_id + INTO #deadlock_resource_parallel + FROM ( + SELECT dr.event_date, + ca.dr.value('@id', 'nvarchar(256)') AS id, + ca.dr.value('@WaitType', 'nvarchar(256)') AS wait_type, + ca.dr.value('@nodeId', 'bigint') AS node_id, + /* These columns are in 2017 CU5 ONLY */ + ca.dr.value('@waiterType', 'nvarchar(256)') AS waiter_type, + ca.dr.value('@ownerActivity', 'nvarchar(256)') AS owner_activity, + ca.dr.value('@waiterActivity', 'nvarchar(256)') AS waiter_activity, + ca.dr.value('@merging', 'nvarchar(256)') AS merging, + ca.dr.value('@spilling', 'nvarchar(256)') AS spilling, + ca.dr.value('@waitingToClose', 'nvarchar(256)') AS waiting_to_close, /* */ - ca.dr.query('.') AS dr - FROM #deadlock_resource AS dr + ca.dr.query('.') AS dr + FROM #deadlock_resource AS dr CROSS APPLY dr.resource_xml.nodes('//resource-list/exchangeEvent') AS ca(dr) - ) AS ca + ) AS ca CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l) CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l) - OPTION ( RECOMPILE ); + OPTION (RECOMPILE); - /*Get rid of parallel noise*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + /*Get rid of parallel noise*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Get rid of parallel noise %s', 0, 1, @d) WITH NOWAIT; - WITH c - AS - ( - SELECT *, ROW_NUMBER() OVER ( PARTITION BY drp.owner_id, drp.waiter_id ORDER BY drp.event_date ) AS rn - FROM #deadlock_resource_parallel AS drp - ) - DELETE FROM c - WHERE c.rn > 1 - OPTION ( RECOMPILE ); - - - /*Get rid of nonsense*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + WITH c + AS + ( + SELECT *, ROW_NUMBER() OVER ( PARTITION BY drp.owner_id, drp.waiter_id ORDER BY drp.event_date ) AS rn + FROM #deadlock_resource_parallel AS drp + ) + DELETE FROM c + WHERE c.rn > 1 + OPTION (RECOMPILE); + + + /*Get rid of nonsense*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Get rid of nonsense %s', 0, 1, @d) WITH NOWAIT; - DELETE dow - FROM #deadlock_owner_waiter AS dow - WHERE dow.owner_id = dow.waiter_id - OPTION ( RECOMPILE ); - - /*Add some nonsense*/ - ALTER TABLE #deadlock_process - ADD waiter_mode NVARCHAR(256), - owner_mode NVARCHAR(256), - is_victim AS CONVERT(BIT, CASE WHEN id = victim_id THEN 1 ELSE 0 END); - - /*Update some nonsense*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + DELETE dow + FROM #deadlock_owner_waiter AS dow + WHERE dow.owner_id = dow.waiter_id + OPTION (RECOMPILE); + + /*Add some nonsense*/ + ALTER TABLE #deadlock_process + ADD waiter_mode nvarchar(256), + owner_mode nvarchar(256), + is_victim AS CONVERT(bit, CASE WHEN id = victim_id THEN 1 ELSE 0 END); + + /*Update some nonsense*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Update some nonsense part 1 %s', 0, 1, @d) WITH NOWAIT; - UPDATE dp - SET dp.owner_mode = dow.owner_mode - FROM #deadlock_process AS dp - JOIN #deadlock_owner_waiter AS dow - ON dp.id = dow.owner_id - AND dp.event_date = dow.event_date - WHERE dp.is_victim = 0 - OPTION ( RECOMPILE ); - - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + UPDATE dp + SET dp.owner_mode = dow.owner_mode + FROM #deadlock_process AS dp + JOIN #deadlock_owner_waiter AS dow + ON dp.id = dow.owner_id + AND dp.event_date = dow.event_date + WHERE dp.is_victim = 0 + OPTION (RECOMPILE); + + SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Update some nonsense part 2 %s', 0, 1, @d) WITH NOWAIT; - UPDATE dp - SET dp.waiter_mode = dow.waiter_mode - FROM #deadlock_process AS dp - JOIN #deadlock_owner_waiter AS dow - ON dp.victim_id = dow.waiter_id - AND dp.event_date = dow.event_date - WHERE dp.is_victim = 1 - OPTION ( RECOMPILE ); - - /*Get Agent Job and Step names*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + UPDATE dp + SET dp.waiter_mode = dow.waiter_mode + FROM #deadlock_process AS dp + JOIN #deadlock_owner_waiter AS dow + ON dp.victim_id = dow.waiter_id + AND dp.event_date = dow.event_date + WHERE dp.is_victim = 1 + OPTION (RECOMPILE); + + /*Get Agent Job and Step names*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Get Agent Job and Step names %s', 0, 1, @d) WITH NOWAIT; SELECT *, - CONVERT(UNIQUEIDENTIFIER, - CONVERT(XML, '').value('xs:hexBinary(substring(sql:column("x.job_id"), 0) )', 'BINARY(16)') + CONVERT(uniqueidentifier, + CONVERT(xml, '').value('xs:hexBinary(substring(sql:column("x.job_id"), 0) )', 'BINARY(16)') ) AS job_id_guid INTO #agent_job FROM ( @@ -748,7 +1096,7 @@ You need to use an Azure storage account, and the path has to look like this: ht CHARINDEX('0x', dp.client_app) + LEN('0x'), 32 ) AS job_id, - SUBSTRING(dp.client_app, + SUBSTRING(dp.client_app, CHARINDEX(': Step ', dp.client_app) + LEN(': Step '), CHARINDEX(')', dp.client_app, CHARINDEX(': Step ', dp.client_app)) - (CHARINDEX(': Step ', dp.client_app) @@ -756,33 +1104,33 @@ You need to use an Azure storage account, and the path has to look like this: ht ) AS step_id FROM #deadlock_process AS dp WHERE dp.client_app LIKE 'SQLAgent - %' - AND dp.client_app <> 'SQLAgent - Initial Boot Probe' + AND dp.client_app <> 'SQLAgent - Initial Boot Probe' ) AS x - OPTION ( RECOMPILE ); + OPTION (RECOMPILE); - ALTER TABLE #agent_job ADD job_name NVARCHAR(256), - step_name NVARCHAR(256); + ALTER TABLE #agent_job ADD job_name nvarchar(256), + step_name nvarchar(256); IF SERVERPROPERTY('EngineEdition') NOT IN (5, 6) /* Azure SQL DB doesn't support querying jobs */ - AND NOT (LEFT(CAST(SERVERPROPERTY('ComputerNamePhysicalNetBIOS') AS VARCHAR(8000)), 8) = 'EC2AMAZ-' /* Neither does Amazon RDS Express Edition */ - AND LEFT(CAST(SERVERPROPERTY('MachineName') AS VARCHAR(8000)), 8) = 'EC2AMAZ-' - AND db_id('rdsadmin') IS NOT NULL - AND EXISTS(SELECT * FROM master.sys.all_objects WHERE name IN ('rds_startup_tasks', 'rds_help_revlogin', 'rds_hexadecimal', 'rds_failover_tracking', 'rds_database_tracking', 'rds_track_change')) - ) + AND NOT (LEFT(CAST(SERVERPROPERTY('ComputerNamePhysicalNetBIOS') AS varchar(8000)), 8) = 'EC2AMAZ-' /* Neither does Amazon RDS Express Edition */ + AND LEFT(CAST(SERVERPROPERTY('MachineName') AS varchar(8000)), 8) = 'EC2AMAZ-' + AND DB_ID('rdsadmin') IS NOT NULL + AND EXISTS(SELECT * FROM master.sys.all_objects WHERE name IN ('rds_startup_tasks', 'rds_help_revlogin', 'rds_hexadecimal', 'rds_failover_tracking', 'rds_database_tracking', 'rds_track_change')) + ) BEGIN SET @StringToExecute = N'UPDATE aj SET aj.job_name = j.name, aj.step_name = s.step_name - FROM msdb.dbo.sysjobs AS j - JOIN msdb.dbo.sysjobsteps AS s + FROM msdb.dbo.sysjobs AS j + JOIN msdb.dbo.sysjobsteps AS s ON j.job_id = s.job_id JOIN #agent_job AS aj ON aj.job_id_guid = j.job_id AND aj.step_id = s.step_id - OPTION ( RECOMPILE );'; + OPTION (RECOMPILE);'; EXEC(@StringToExecute); - END + END; UPDATE dp SET dp.client_app = @@ -798,456 +1146,456 @@ You need to use an Azure storage account, and the path has to look like this: ht ON dp.event_date = aj.event_date AND dp.victim_id = aj.victim_id AND dp.id = aj.id - OPTION ( RECOMPILE ); + OPTION (RECOMPILE); - /*Get each and every table of all databases*/ - DECLARE @sysAssObjId AS TABLE (database_id bigint, partition_id bigint, schema_name varchar(255), table_name varchar(255)); - INSERT into @sysAssObjId EXECUTE sp_MSforeachdb - N'USE [?]; - SELECT DB_ID() as database_id, p.partition_id, s.name as schema_name, t.name as table_name - FROM sys.partitions p - LEFT JOIN sys.tables t ON t.object_id = p.object_id - LEFT JOIN sys.schemas s ON s.schema_id = t.schema_id - WHERE s.name is not NULL AND t.name is not NULL'; + /*Get each and every table of all databases*/ + DECLARE @sysAssObjId AS table (database_id bigint, partition_id bigint, schema_name varchar(255), table_name varchar(255)); + INSERT INTO @sysAssObjId EXECUTE sys.sp_MSforeachdb + N'USE [?]; + SELECT DB_ID() as database_id, p.partition_id, s.name as schema_name, t.name as table_name + FROM sys.partitions p + LEFT JOIN sys.tables t ON t.object_id = p.object_id + LEFT JOIN sys.schemas s ON s.schema_id = t.schema_id + WHERE s.name is not NULL AND t.name is not NULL'; - /*Begin checks based on parsed values*/ + /*Begin checks based on parsed values*/ - /*Check 1 is deadlocks by database*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + /*Check 1 is deadlocks by database*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Check 1 %s', 0, 1, @d) WITH NOWAIT; - INSERT #deadlock_findings WITH (TABLOCKX) - ( check_id, database_name, object_name, finding_group, finding ) - SELECT 1 AS check_id, - DB_NAME(dp.database_id) AS database_name, - '-' AS object_name, - 'Total database locks' AS finding_group, - 'This database had ' - + CONVERT(NVARCHAR(20), COUNT_BIG(DISTINCT dp.event_date)) - + ' deadlocks.' + INSERT #deadlock_findings WITH (TABLOCKX) + ( check_id, database_name, object_name, finding_group, finding ) + SELECT 1 AS check_id, + DB_NAME(dp.database_id) AS database_name, + '-' AS object_name, + 'Total database locks' AS finding_group, + 'This database had ' + + CONVERT(nvarchar(20), COUNT_BIG(DISTINCT dp.event_date)) + + ' deadlocks.' FROM #deadlock_process AS dp - WHERE 1 = 1 - AND (DB_NAME(dp.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (dp.event_date >= @StartDate OR @StartDate IS NULL) - AND (dp.event_date < @EndDate OR @EndDate IS NULL) - AND (dp.client_app = @AppName OR @AppName IS NULL) - AND (dp.host_name = @HostName OR @HostName IS NULL) - AND (dp.login_name = @LoginName OR @LoginName IS NULL) - GROUP BY DB_NAME(dp.database_id) - OPTION ( RECOMPILE ); - - /*Check 2 is deadlocks by object*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + WHERE 1 = 1 + AND (DB_NAME(dp.database_id) = @DatabaseName OR @DatabaseName IS NULL) + AND (dp.event_date >= @StartDate OR @StartDate IS NULL) + AND (dp.event_date < @EndDate OR @EndDate IS NULL) + AND (dp.client_app = @AppName OR @AppName IS NULL) + AND (dp.host_name = @HostName OR @HostName IS NULL) + AND (dp.login_name = @LoginName OR @LoginName IS NULL) + GROUP BY DB_NAME(dp.database_id) + OPTION (RECOMPILE); + + /*Check 2 is deadlocks by object*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Check 2 objects %s', 0, 1, @d) WITH NOWAIT; - INSERT #deadlock_findings WITH (TABLOCKX) - ( check_id, database_name, object_name, finding_group, finding ) - SELECT 2 AS check_id, - ISNULL(DB_NAME(dow.database_id), 'UNKNOWN') AS database_name, - ISNULL(dow.object_name, 'UNKNOWN') AS object_name, - 'Total object deadlocks' AS finding_group, - 'This object was involved in ' - + CONVERT(NVARCHAR(20), COUNT_BIG(DISTINCT dow.event_date)) - + ' deadlock(s).' + INSERT #deadlock_findings WITH (TABLOCKX) + ( check_id, database_name, object_name, finding_group, finding ) + SELECT 2 AS check_id, + ISNULL(DB_NAME(dow.database_id), 'UNKNOWN') AS database_name, + ISNULL(dow.object_name, 'UNKNOWN') AS object_name, + 'Total object deadlocks' AS finding_group, + 'This object was involved in ' + + CONVERT(nvarchar(20), COUNT_BIG(DISTINCT dow.event_date)) + + ' deadlock(s).' FROM #deadlock_owner_waiter AS dow - WHERE 1 = 1 - AND (DB_NAME(dow.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (dow.event_date >= @StartDate OR @StartDate IS NULL) - AND (dow.event_date < @EndDate OR @EndDate IS NULL) - AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) - GROUP BY DB_NAME(dow.database_id), dow.object_name - OPTION ( RECOMPILE ); - - /*Check 2 continuation, number of locks per index*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + WHERE 1 = 1 + AND (DB_NAME(dow.database_id) = @DatabaseName OR @DatabaseName IS NULL) + AND (dow.event_date >= @StartDate OR @StartDate IS NULL) + AND (dow.event_date < @EndDate OR @EndDate IS NULL) + AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) + GROUP BY DB_NAME(dow.database_id), dow.object_name + OPTION (RECOMPILE); + + /*Check 2 continuation, number of locks per index*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Check 2 indexes %s', 0, 1, @d) WITH NOWAIT; - INSERT #deadlock_findings WITH (TABLOCKX) - ( check_id, database_name, object_name, finding_group, finding ) - SELECT 2 AS check_id, - ISNULL(DB_NAME(dow.database_id), 'UNKNOWN') AS database_name, - dow.index_name AS index_name, - 'Total index deadlocks' AS finding_group, - 'This index was involved in ' - + CONVERT(NVARCHAR(20), COUNT_BIG(DISTINCT dow.event_date)) - + ' deadlock(s).' + INSERT #deadlock_findings WITH (TABLOCKX) + ( check_id, database_name, object_name, finding_group, finding ) + SELECT 2 AS check_id, + ISNULL(DB_NAME(dow.database_id), 'UNKNOWN') AS database_name, + dow.index_name AS index_name, + 'Total index deadlocks' AS finding_group, + 'This index was involved in ' + + CONVERT(nvarchar(20), COUNT_BIG(DISTINCT dow.event_date)) + + ' deadlock(s).' FROM #deadlock_owner_waiter AS dow - WHERE 1 = 1 - AND (DB_NAME(dow.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (dow.event_date >= @StartDate OR @StartDate IS NULL) - AND (dow.event_date < @EndDate OR @EndDate IS NULL) - AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) - AND dow.lock_type NOT IN (N'HEAP', N'RID') - AND dow.index_name is not null - GROUP BY DB_NAME(dow.database_id), dow.index_name - OPTION ( RECOMPILE ); - - - /*Check 2 continuation, number of locks per heap*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + WHERE 1 = 1 + AND (DB_NAME(dow.database_id) = @DatabaseName OR @DatabaseName IS NULL) + AND (dow.event_date >= @StartDate OR @StartDate IS NULL) + AND (dow.event_date < @EndDate OR @EndDate IS NULL) + AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) + AND dow.lock_type NOT IN (N'HEAP', N'RID') + AND dow.index_name IS NOT NULL + GROUP BY DB_NAME(dow.database_id), dow.index_name + OPTION (RECOMPILE); + + + /*Check 2 continuation, number of locks per heap*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Check 2 heaps %s', 0, 1, @d) WITH NOWAIT; - INSERT #deadlock_findings WITH (TABLOCKX) - ( check_id, database_name, object_name, finding_group, finding ) - SELECT 2 AS check_id, - ISNULL(DB_NAME(dow.database_id), 'UNKNOWN') AS database_name, - dow.index_name AS index_name, - 'Total heap deadlocks' AS finding_group, - 'This heap was involved in ' - + CONVERT(NVARCHAR(20), COUNT_BIG(DISTINCT dow.event_date)) - + ' deadlock(s).' + INSERT #deadlock_findings WITH (TABLOCKX) + ( check_id, database_name, object_name, finding_group, finding ) + SELECT 2 AS check_id, + ISNULL(DB_NAME(dow.database_id), 'UNKNOWN') AS database_name, + dow.index_name AS index_name, + 'Total heap deadlocks' AS finding_group, + 'This heap was involved in ' + + CONVERT(nvarchar(20), COUNT_BIG(DISTINCT dow.event_date)) + + ' deadlock(s).' FROM #deadlock_owner_waiter AS dow - WHERE 1 = 1 - AND (DB_NAME(dow.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (dow.event_date >= @StartDate OR @StartDate IS NULL) - AND (dow.event_date < @EndDate OR @EndDate IS NULL) - AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) - AND dow.lock_type IN (N'HEAP', N'RID') - GROUP BY DB_NAME(dow.database_id), dow.index_name - OPTION ( RECOMPILE ); - - - /*Check 3 looks for Serializable locking*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + WHERE 1 = 1 + AND (DB_NAME(dow.database_id) = @DatabaseName OR @DatabaseName IS NULL) + AND (dow.event_date >= @StartDate OR @StartDate IS NULL) + AND (dow.event_date < @EndDate OR @EndDate IS NULL) + AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) + AND dow.lock_type IN (N'HEAP', N'RID') + GROUP BY DB_NAME(dow.database_id), dow.index_name + OPTION (RECOMPILE); + + + /*Check 3 looks for Serializable locking*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Check 3 %s', 0, 1, @d) WITH NOWAIT; - INSERT #deadlock_findings WITH (TABLOCKX) + INSERT #deadlock_findings WITH (TABLOCKX) ( check_id, database_name, object_name, finding_group, finding ) - SELECT 3 AS check_id, - DB_NAME(dp.database_id) AS database_name, - '-' AS object_name, - 'Serializable locking' AS finding_group, - 'This database has had ' + - CONVERT(NVARCHAR(20), COUNT_BIG(*)) + - ' instances of serializable deadlocks.' - AS finding - FROM #deadlock_process AS dp - WHERE dp.isolation_level LIKE 'serializable%' - AND (DB_NAME(dp.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (dp.event_date >= @StartDate OR @StartDate IS NULL) - AND (dp.event_date < @EndDate OR @EndDate IS NULL) - AND (dp.client_app = @AppName OR @AppName IS NULL) - AND (dp.host_name = @HostName OR @HostName IS NULL) - AND (dp.login_name = @LoginName OR @LoginName IS NULL) - GROUP BY DB_NAME(dp.database_id) - OPTION ( RECOMPILE ); - - - /*Check 4 looks for Repeatable Read locking*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + SELECT 3 AS check_id, + DB_NAME(dp.database_id) AS database_name, + '-' AS object_name, + 'Serializable locking' AS finding_group, + 'This database has had ' + + CONVERT(nvarchar(20), COUNT_BIG(*)) + + ' instances of serializable deadlocks.' + AS finding + FROM #deadlock_process AS dp + WHERE dp.isolation_level LIKE 'serializable%' + AND (DB_NAME(dp.database_id) = @DatabaseName OR @DatabaseName IS NULL) + AND (dp.event_date >= @StartDate OR @StartDate IS NULL) + AND (dp.event_date < @EndDate OR @EndDate IS NULL) + AND (dp.client_app = @AppName OR @AppName IS NULL) + AND (dp.host_name = @HostName OR @HostName IS NULL) + AND (dp.login_name = @LoginName OR @LoginName IS NULL) + GROUP BY DB_NAME(dp.database_id) + OPTION (RECOMPILE); + + + /*Check 4 looks for Repeatable Read locking*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Check 4 %s', 0, 1, @d) WITH NOWAIT; - INSERT #deadlock_findings WITH (TABLOCKX) + INSERT #deadlock_findings WITH (TABLOCKX) ( check_id, database_name, object_name, finding_group, finding ) - SELECT 4 AS check_id, - DB_NAME(dp.database_id) AS database_name, - '-' AS object_name, - 'Repeatable Read locking' AS finding_group, - 'This database has had ' + - CONVERT(NVARCHAR(20), COUNT_BIG(*)) + - ' instances of repeatable read deadlocks.' - AS finding - FROM #deadlock_process AS dp - WHERE dp.isolation_level LIKE 'repeatable read%' - AND (DB_NAME(dp.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (dp.event_date >= @StartDate OR @StartDate IS NULL) - AND (dp.event_date < @EndDate OR @EndDate IS NULL) - AND (dp.client_app = @AppName OR @AppName IS NULL) - AND (dp.host_name = @HostName OR @HostName IS NULL) - AND (dp.login_name = @LoginName OR @LoginName IS NULL) - GROUP BY DB_NAME(dp.database_id) - OPTION ( RECOMPILE ); - - - /*Check 5 breaks down app, host, and login information*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + SELECT 4 AS check_id, + DB_NAME(dp.database_id) AS database_name, + '-' AS object_name, + 'Repeatable Read locking' AS finding_group, + 'This database has had ' + + CONVERT(nvarchar(20), COUNT_BIG(*)) + + ' instances of repeatable read deadlocks.' + AS finding + FROM #deadlock_process AS dp + WHERE dp.isolation_level LIKE 'repeatable read%' + AND (DB_NAME(dp.database_id) = @DatabaseName OR @DatabaseName IS NULL) + AND (dp.event_date >= @StartDate OR @StartDate IS NULL) + AND (dp.event_date < @EndDate OR @EndDate IS NULL) + AND (dp.client_app = @AppName OR @AppName IS NULL) + AND (dp.host_name = @HostName OR @HostName IS NULL) + AND (dp.login_name = @LoginName OR @LoginName IS NULL) + GROUP BY DB_NAME(dp.database_id) + OPTION (RECOMPILE); + + + /*Check 5 breaks down app, host, and login information*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Check 5 %s', 0, 1, @d) WITH NOWAIT; - INSERT #deadlock_findings WITH (TABLOCKX) + INSERT #deadlock_findings WITH (TABLOCKX) ( check_id, database_name, object_name, finding_group, finding ) - SELECT 5 AS check_id, - DB_NAME(dp.database_id) AS database_name, - '-' AS object_name, - 'Login, App, and Host locking' AS finding_group, - 'This database has had ' + - CONVERT(NVARCHAR(20), COUNT_BIG(DISTINCT dp.event_date)) + - ' instances of deadlocks involving the login ' + - ISNULL(dp.login_name, 'UNKNOWN') + - ' from the application ' + - ISNULL(dp.client_app, 'UNKNOWN') + - ' on host ' + - ISNULL(dp.host_name, 'UNKNOWN') - AS finding - FROM #deadlock_process AS dp - WHERE 1 = 1 - AND (DB_NAME(dp.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (dp.event_date >= @StartDate OR @StartDate IS NULL) - AND (dp.event_date < @EndDate OR @EndDate IS NULL) - AND (dp.client_app = @AppName OR @AppName IS NULL) - AND (dp.host_name = @HostName OR @HostName IS NULL) - AND (dp.login_name = @LoginName OR @LoginName IS NULL) - GROUP BY DB_NAME(dp.database_id), dp.login_name, dp.client_app, dp.host_name - OPTION ( RECOMPILE ); - - - /*Check 6 breaks down the types of locks (object, page, key, etc.)*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + SELECT 5 AS check_id, + DB_NAME(dp.database_id) AS database_name, + '-' AS object_name, + 'Login, App, and Host locking' AS finding_group, + 'This database has had ' + + CONVERT(nvarchar(20), COUNT_BIG(DISTINCT dp.event_date)) + + ' instances of deadlocks involving the login ' + + ISNULL(dp.login_name, 'UNKNOWN') + + ' from the application ' + + ISNULL(dp.client_app, 'UNKNOWN') + + ' on host ' + + ISNULL(dp.host_name, 'UNKNOWN') + AS finding + FROM #deadlock_process AS dp + WHERE 1 = 1 + AND (DB_NAME(dp.database_id) = @DatabaseName OR @DatabaseName IS NULL) + AND (dp.event_date >= @StartDate OR @StartDate IS NULL) + AND (dp.event_date < @EndDate OR @EndDate IS NULL) + AND (dp.client_app = @AppName OR @AppName IS NULL) + AND (dp.host_name = @HostName OR @HostName IS NULL) + AND (dp.login_name = @LoginName OR @LoginName IS NULL) + GROUP BY DB_NAME(dp.database_id), dp.login_name, dp.client_app, dp.host_name + OPTION (RECOMPILE); + + + /*Check 6 breaks down the types of locks (object, page, key, etc.)*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Check 6 %s', 0, 1, @d) WITH NOWAIT; - WITH lock_types AS ( - SELECT DB_NAME(dp.database_id) AS database_name, - dow.object_name, - SUBSTRING(dp.wait_resource, 1, CHARINDEX(':', dp.wait_resource) -1) AS lock, - CONVERT(NVARCHAR(20), COUNT_BIG(DISTINCT dp.id)) AS lock_count - FROM #deadlock_process AS dp - JOIN #deadlock_owner_waiter AS dow - ON dp.id = dow.owner_id - AND dp.event_date = dow.event_date - WHERE 1 = 1 - AND (DB_NAME(dp.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (dp.event_date >= @StartDate OR @StartDate IS NULL) - AND (dp.event_date < @EndDate OR @EndDate IS NULL) - AND (dp.client_app = @AppName OR @AppName IS NULL) - AND (dp.host_name = @HostName OR @HostName IS NULL) - AND (dp.login_name = @LoginName OR @LoginName IS NULL) - AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) - AND dow.object_name IS NOT NULL - GROUP BY DB_NAME(dp.database_id), SUBSTRING(dp.wait_resource, 1, CHARINDEX(':', dp.wait_resource) - 1), dow.object_name - ) - INSERT #deadlock_findings WITH (TABLOCKX) + WITH lock_types AS ( + SELECT DB_NAME(dp.database_id) AS database_name, + dow.object_name, + SUBSTRING(dp.wait_resource, 1, CHARINDEX(':', dp.wait_resource) -1) AS lock, + CONVERT(nvarchar(20), COUNT_BIG(DISTINCT dp.id)) AS lock_count + FROM #deadlock_process AS dp + JOIN #deadlock_owner_waiter AS dow + ON dp.id = dow.owner_id + AND dp.event_date = dow.event_date + WHERE 1 = 1 + AND (DB_NAME(dp.database_id) = @DatabaseName OR @DatabaseName IS NULL) + AND (dp.event_date >= @StartDate OR @StartDate IS NULL) + AND (dp.event_date < @EndDate OR @EndDate IS NULL) + AND (dp.client_app = @AppName OR @AppName IS NULL) + AND (dp.host_name = @HostName OR @HostName IS NULL) + AND (dp.login_name = @LoginName OR @LoginName IS NULL) + AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) + AND dow.object_name IS NOT NULL + GROUP BY DB_NAME(dp.database_id), SUBSTRING(dp.wait_resource, 1, CHARINDEX(':', dp.wait_resource) - 1), dow.object_name + ) + INSERT #deadlock_findings WITH (TABLOCKX) ( check_id, database_name, object_name, finding_group, finding ) - SELECT DISTINCT 6 AS check_id, - lt.database_name, - lt.object_name, - 'Types of locks by object' AS finding_group, - 'This object has had ' + - STUFF((SELECT DISTINCT N', ' + lt2.lock_count + ' ' + lt2.lock - FROM lock_types AS lt2 - WHERE lt2.database_name = lt.database_name - AND lt2.object_name = lt.object_name - FOR XML PATH(N''), TYPE).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 1, N'') - + ' locks' - FROM lock_types AS lt - OPTION ( RECOMPILE ); - - - /*Check 7 gives you more info queries for sp_BlitzCache & BlitzQueryStore*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + SELECT DISTINCT 6 AS check_id, + lt.database_name, + lt.object_name, + 'Types of locks by object' AS finding_group, + 'This object has had ' + + STUFF((SELECT DISTINCT N', ' + lt2.lock_count + ' ' + lt2.lock + FROM lock_types AS lt2 + WHERE lt2.database_name = lt.database_name + AND lt2.object_name = lt.object_name + FOR xml PATH(N''), TYPE).value(N'.[1]', N'nvarchar(MAX)'), 1, 1, N'') + + ' locks' + FROM lock_types AS lt + OPTION (RECOMPILE); + + + /*Check 7 gives you more info queries for sp_BlitzCache & BlitzQueryStore*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Check 7 part 1 %s', 0, 1, @d) WITH NOWAIT; - WITH deadlock_stack AS ( - SELECT DISTINCT - ds.id, - ds.proc_name, - ds.event_date, - PARSENAME(ds.proc_name, 3) AS database_name, - PARSENAME(ds.proc_name, 2) AS schema_name, - PARSENAME(ds.proc_name, 1) AS proc_only_name, - '''' + STUFF((SELECT DISTINCT N',' + ds2.sql_handle - FROM #deadlock_stack AS ds2 - WHERE ds2.id = ds.id - AND ds2.event_date = ds.event_date - FOR XML PATH(N''), TYPE).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 1, N'') + '''' AS sql_handle_csv - FROM #deadlock_stack AS ds - GROUP BY PARSENAME(ds.proc_name, 3), + WITH deadlock_stack AS ( + SELECT DISTINCT + ds.id, + ds.proc_name, + ds.event_date, + PARSENAME(ds.proc_name, 3) AS database_name, + PARSENAME(ds.proc_name, 2) AS schema_name, + PARSENAME(ds.proc_name, 1) AS proc_only_name, + '''' + STUFF((SELECT DISTINCT N',' + ds2.sql_handle + FROM #deadlock_stack AS ds2 + WHERE ds2.id = ds.id + AND ds2.event_date = ds.event_date + FOR xml PATH(N''), TYPE).value(N'.[1]', N'nvarchar(MAX)'), 1, 1, N'') + '''' AS sql_handle_csv + FROM #deadlock_stack AS ds + GROUP BY PARSENAME(ds.proc_name, 3), PARSENAME(ds.proc_name, 2), PARSENAME(ds.proc_name, 1), ds.id, ds.proc_name, ds.event_date - ) - INSERT #deadlock_findings WITH (TABLOCKX) + ) + INSERT #deadlock_findings WITH (TABLOCKX) ( check_id, database_name, object_name, finding_group, finding ) - SELECT DISTINCT 7 AS check_id, - ISNULL(DB_NAME(dow.database_id), 'UNKNOWN') AS database_name, - ds.proc_name AS object_name, - 'More Info - Query' AS finding_group, - 'EXEC sp_BlitzCache ' + - CASE WHEN ds.proc_name = 'adhoc' - THEN ' @OnlySqlHandles = ' + ds.sql_handle_csv - ELSE '@StoredProcName = ' + - QUOTENAME(ds.proc_only_name, '''') - END + - ';' AS finding - FROM deadlock_stack AS ds - JOIN #deadlock_owner_waiter AS dow - ON dow.owner_id = ds.id - AND dow.event_date = ds.event_date - WHERE 1 = 1 - AND (DB_NAME(dow.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (dow.event_date >= @StartDate OR @StartDate IS NULL) - AND (dow.event_date < @EndDate OR @EndDate IS NULL) - AND (dow.object_name = @StoredProcName OR @StoredProcName IS NULL) - OPTION ( RECOMPILE ); - - IF @ProductVersionMajor >= 13 - BEGIN - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + SELECT DISTINCT 7 AS check_id, + ISNULL(DB_NAME(dow.database_id), 'UNKNOWN') AS database_name, + ds.proc_name AS object_name, + 'More Info - Query' AS finding_group, + 'EXEC sp_BlitzCache ' + + CASE WHEN ds.proc_name = 'adhoc' + THEN ' @OnlySqlHandles = ' + ds.sql_handle_csv + ELSE '@StoredProcName = ' + + QUOTENAME(ds.proc_only_name, '''') + END + + ';' AS finding + FROM deadlock_stack AS ds + JOIN #deadlock_owner_waiter AS dow + ON dow.owner_id = ds.id + AND dow.event_date = ds.event_date + WHERE 1 = 1 + AND (DB_NAME(dow.database_id) = @DatabaseName OR @DatabaseName IS NULL) + AND (dow.event_date >= @StartDate OR @StartDate IS NULL) + AND (dow.event_date < @EndDate OR @EndDate IS NULL) + AND (dow.object_name = @StoredProcName OR @StoredProcName IS NULL) + OPTION (RECOMPILE); + + IF @ProductVersionMajor >= 13 + BEGIN + SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Check 7 part 2 %s', 0, 1, @d) WITH NOWAIT; - WITH deadlock_stack AS ( - SELECT DISTINCT - ds.id, - ds.sql_handle, - ds.proc_name, - ds.event_date, - PARSENAME(ds.proc_name, 3) AS database_name, - PARSENAME(ds.proc_name, 2) AS schema_name, - PARSENAME(ds.proc_name, 1) AS proc_only_name - FROM #deadlock_stack AS ds - ) - INSERT #deadlock_findings WITH (TABLOCKX) + WITH deadlock_stack AS ( + SELECT DISTINCT + ds.id, + ds.sql_handle, + ds.proc_name, + ds.event_date, + PARSENAME(ds.proc_name, 3) AS database_name, + PARSENAME(ds.proc_name, 2) AS schema_name, + PARSENAME(ds.proc_name, 1) AS proc_only_name + FROM #deadlock_stack AS ds + ) + INSERT #deadlock_findings WITH (TABLOCKX) ( check_id, database_name, object_name, finding_group, finding ) - SELECT DISTINCT 7 AS check_id, - DB_NAME(dow.database_id) AS database_name, - ds.proc_name AS object_name, - 'More Info - Query' AS finding_group, - 'EXEC sp_BlitzQueryStore ' - + '@DatabaseName = ' - + QUOTENAME(ds.database_name, '''') - + ', ' - + '@StoredProcName = ' - + QUOTENAME(ds.proc_only_name, '''') - + ';' AS finding - FROM deadlock_stack AS ds - JOIN #deadlock_owner_waiter AS dow - ON dow.owner_id = ds.id - AND dow.event_date = ds.event_date - WHERE ds.proc_name <> 'adhoc' - AND (DB_NAME(dow.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (dow.event_date >= @StartDate OR @StartDate IS NULL) - AND (dow.event_date < @EndDate OR @EndDate IS NULL) - AND (dow.object_name = @StoredProcName OR @StoredProcName IS NULL) - OPTION ( RECOMPILE ); - END; - - - /*Check 8 gives you stored proc deadlock counts*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + SELECT DISTINCT 7 AS check_id, + DB_NAME(dow.database_id) AS database_name, + ds.proc_name AS object_name, + 'More Info - Query' AS finding_group, + 'EXEC sp_BlitzQueryStore ' + + '@DatabaseName = ' + + QUOTENAME(ds.database_name, '''') + + ', ' + + '@StoredProcName = ' + + QUOTENAME(ds.proc_only_name, '''') + + ';' AS finding + FROM deadlock_stack AS ds + JOIN #deadlock_owner_waiter AS dow + ON dow.owner_id = ds.id + AND dow.event_date = ds.event_date + WHERE ds.proc_name <> 'adhoc' + AND (DB_NAME(dow.database_id) = @DatabaseName OR @DatabaseName IS NULL) + AND (dow.event_date >= @StartDate OR @StartDate IS NULL) + AND (dow.event_date < @EndDate OR @EndDate IS NULL) + AND (dow.object_name = @StoredProcName OR @StoredProcName IS NULL) + OPTION (RECOMPILE); + END; + + + /*Check 8 gives you stored proc deadlock counts*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Check 8 %s', 0, 1, @d) WITH NOWAIT; - INSERT #deadlock_findings WITH (TABLOCKX) + INSERT #deadlock_findings WITH (TABLOCKX) ( check_id, database_name, object_name, finding_group, finding ) - SELECT 8 AS check_id, - DB_NAME(dp.database_id) AS database_name, - ds.proc_name, - 'Stored Procedure Deadlocks', - 'The stored procedure ' - + PARSENAME(ds.proc_name, 2) - + '.' - + PARSENAME(ds.proc_name, 1) - + ' has been involved in ' - + CONVERT(NVARCHAR(10), COUNT_BIG(DISTINCT ds.id)) - + ' deadlocks.' - FROM #deadlock_stack AS ds - JOIN #deadlock_process AS dp - ON dp.id = ds.id - AND ds.event_date = dp.event_date - WHERE ds.proc_name <> 'adhoc' - AND (ds.proc_name = @StoredProcName OR @StoredProcName IS NULL) - AND (DB_NAME(dp.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (dp.event_date >= @StartDate OR @StartDate IS NULL) - AND (dp.event_date < @EndDate OR @EndDate IS NULL) - AND (dp.client_app = @AppName OR @AppName IS NULL) - AND (dp.host_name = @HostName OR @HostName IS NULL) - AND (dp.login_name = @LoginName OR @LoginName IS NULL) - GROUP BY DB_NAME(dp.database_id), ds.proc_name - OPTION(RECOMPILE); - - - /*Check 9 gives you more info queries for sp_BlitzIndex */ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + SELECT 8 AS check_id, + DB_NAME(dp.database_id) AS database_name, + ds.proc_name, + 'Stored Procedure Deadlocks', + 'The stored procedure ' + + PARSENAME(ds.proc_name, 2) + + '.' + + PARSENAME(ds.proc_name, 1) + + ' has been involved in ' + + CONVERT(nvarchar(10), COUNT_BIG(DISTINCT ds.id)) + + ' deadlocks.' + FROM #deadlock_stack AS ds + JOIN #deadlock_process AS dp + ON dp.id = ds.id + AND ds.event_date = dp.event_date + WHERE ds.proc_name <> 'adhoc' + AND (ds.proc_name = @StoredProcName OR @StoredProcName IS NULL) + AND (DB_NAME(dp.database_id) = @DatabaseName OR @DatabaseName IS NULL) + AND (dp.event_date >= @StartDate OR @StartDate IS NULL) + AND (dp.event_date < @EndDate OR @EndDate IS NULL) + AND (dp.client_app = @AppName OR @AppName IS NULL) + AND (dp.host_name = @HostName OR @HostName IS NULL) + AND (dp.login_name = @LoginName OR @LoginName IS NULL) + GROUP BY DB_NAME(dp.database_id), ds.proc_name + OPTION(RECOMPILE); + + + /*Check 9 gives you more info queries for sp_BlitzIndex */ + SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Check 9 %s', 0, 1, @d) WITH NOWAIT; - WITH bi AS ( - SELECT DISTINCT - dow.object_name, - DB_NAME(dow.database_id) as database_name, - a.schema_name AS schema_name, - a.table_name AS table_name - FROM #deadlock_owner_waiter AS dow - LEFT JOIN @sysAssObjId a ON a.database_id=dow.database_id AND a.partition_id = dow.associatedObjectId - WHERE 1 = 1 - AND (DB_NAME(dow.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (dow.event_date >= @StartDate OR @StartDate IS NULL) - AND (dow.event_date < @EndDate OR @EndDate IS NULL) - AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) - AND dow.object_name IS NOT NULL - ) - INSERT #deadlock_findings WITH (TABLOCKX) + WITH bi AS ( + SELECT DISTINCT + dow.object_name, + DB_NAME(dow.database_id) AS database_name, + a.schema_name AS schema_name, + a.table_name AS table_name + FROM #deadlock_owner_waiter AS dow + LEFT JOIN @sysAssObjId a ON a.database_id=dow.database_id AND a.partition_id = dow.associatedObjectId + WHERE 1 = 1 + AND (DB_NAME(dow.database_id) = @DatabaseName OR @DatabaseName IS NULL) + AND (dow.event_date >= @StartDate OR @StartDate IS NULL) + AND (dow.event_date < @EndDate OR @EndDate IS NULL) + AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) + AND dow.object_name IS NOT NULL + ) + INSERT #deadlock_findings WITH (TABLOCKX) ( check_id, database_name, object_name, finding_group, finding ) - SELECT 9 AS check_id, - bi.database_name, - bi.object_name, - 'More Info - Table' AS finding_group, - 'EXEC sp_BlitzIndex ' + - '@DatabaseName = ' + QUOTENAME(bi.database_name, '''') + - ', @SchemaName = ' + QUOTENAME(bi.schema_name, '''') + - ', @TableName = ' + QUOTENAME(bi.table_name, '''') + - ';' AS finding - FROM bi - OPTION ( RECOMPILE ); - - /*Check 10 gets total deadlock wait time per object*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + SELECT 9 AS check_id, + bi.database_name, + bi.object_name, + 'More Info - Table' AS finding_group, + 'EXEC sp_BlitzIndex ' + + '@DatabaseName = ' + QUOTENAME(bi.database_name, '''') + + ', @SchemaName = ' + QUOTENAME(bi.schema_name, '''') + + ', @TableName = ' + QUOTENAME(bi.table_name, '''') + + ';' AS finding + FROM bi + OPTION (RECOMPILE); + + /*Check 10 gets total deadlock wait time per object*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Check 10 %s', 0, 1, @d) WITH NOWAIT; - WITH chopsuey AS ( - SELECT DISTINCT - PARSENAME(dow.object_name, 3) AS database_name, - dow.object_name, - CONVERT(VARCHAR(10), (SUM(DISTINCT CONVERT(BIGINT, dp.wait_time)) / 1000) / 86400) AS wait_days, - CONVERT(VARCHAR(20), DATEADD(SECOND, (SUM(DISTINCT CONVERT(BIGINT, dp.wait_time)) / 1000), 0), 108) AS wait_time_hms - FROM #deadlock_owner_waiter AS dow - JOIN #deadlock_process AS dp - ON (dp.id = dow.owner_id OR dp.victim_id = dow.waiter_id) - AND dp.event_date = dow.event_date - WHERE 1 = 1 - AND (DB_NAME(dow.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (dow.event_date >= @StartDate OR @StartDate IS NULL) - AND (dow.event_date < @EndDate OR @EndDate IS NULL) - AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) - AND (dp.client_app = @AppName OR @AppName IS NULL) - AND (dp.host_name = @HostName OR @HostName IS NULL) - AND (dp.login_name = @LoginName OR @LoginName IS NULL) - GROUP BY PARSENAME(dow.object_name, 3), dow.object_name - ) - INSERT #deadlock_findings WITH (TABLOCKX) + WITH chopsuey AS ( + SELECT DISTINCT + PARSENAME(dow.object_name, 3) AS database_name, + dow.object_name, + CONVERT(varchar(10), (SUM(DISTINCT CONVERT(bigint, dp.wait_time)) / 1000) / 86400) AS wait_days, + CONVERT(varchar(20), DATEADD(SECOND, (SUM(DISTINCT CONVERT(bigint, dp.wait_time)) / 1000), 0), 108) AS wait_time_hms + FROM #deadlock_owner_waiter AS dow + JOIN #deadlock_process AS dp + ON (dp.id = dow.owner_id OR dp.victim_id = dow.waiter_id) + AND dp.event_date = dow.event_date + WHERE 1 = 1 + AND (DB_NAME(dow.database_id) = @DatabaseName OR @DatabaseName IS NULL) + AND (dow.event_date >= @StartDate OR @StartDate IS NULL) + AND (dow.event_date < @EndDate OR @EndDate IS NULL) + AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) + AND (dp.client_app = @AppName OR @AppName IS NULL) + AND (dp.host_name = @HostName OR @HostName IS NULL) + AND (dp.login_name = @LoginName OR @LoginName IS NULL) + GROUP BY PARSENAME(dow.object_name, 3), dow.object_name + ) + INSERT #deadlock_findings WITH (TABLOCKX) ( check_id, database_name, object_name, finding_group, finding ) - SELECT 10 AS check_id, - cs.database_name, - cs.object_name, - 'Total object deadlock wait time' AS finding_group, - 'This object has had ' - + CONVERT(VARCHAR(10), cs.wait_days) - + ':' + CONVERT(VARCHAR(20), cs.wait_time_hms, 108) - + ' [d/h/m/s] of deadlock wait time.' AS finding - FROM chopsuey AS cs - WHERE cs.object_name IS NOT NULL - OPTION ( RECOMPILE ); - - /*Check 11 gets total deadlock wait time per database*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + SELECT 10 AS check_id, + cs.database_name, + cs.object_name, + 'Total object deadlock wait time' AS finding_group, + 'This object has had ' + + CONVERT(varchar(10), cs.wait_days) + + ':' + CONVERT(varchar(20), cs.wait_time_hms, 108) + + ' [d/h/m/s] of deadlock wait time.' AS finding + FROM chopsuey AS cs + WHERE cs.object_name IS NOT NULL + OPTION (RECOMPILE); + + /*Check 11 gets total deadlock wait time per database*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Check 11 %s', 0, 1, @d) WITH NOWAIT; - WITH wait_time AS ( - SELECT DB_NAME(dp.database_id) AS database_name, - SUM(CONVERT(BIGINT, dp.wait_time)) AS total_wait_time_ms - FROM #deadlock_process AS dp - WHERE 1 = 1 - AND (DB_NAME(dp.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (dp.event_date >= @StartDate OR @StartDate IS NULL) - AND (dp.event_date < @EndDate OR @EndDate IS NULL) - AND (dp.client_app = @AppName OR @AppName IS NULL) - AND (dp.host_name = @HostName OR @HostName IS NULL) - AND (dp.login_name = @LoginName OR @LoginName IS NULL) - GROUP BY DB_NAME(dp.database_id) - ) - INSERT #deadlock_findings WITH (TABLOCKX) + WITH wait_time AS ( + SELECT DB_NAME(dp.database_id) AS database_name, + SUM(CONVERT(bigint, dp.wait_time)) AS total_wait_time_ms + FROM #deadlock_process AS dp + WHERE 1 = 1 + AND (DB_NAME(dp.database_id) = @DatabaseName OR @DatabaseName IS NULL) + AND (dp.event_date >= @StartDate OR @StartDate IS NULL) + AND (dp.event_date < @EndDate OR @EndDate IS NULL) + AND (dp.client_app = @AppName OR @AppName IS NULL) + AND (dp.host_name = @HostName OR @HostName IS NULL) + AND (dp.login_name = @LoginName OR @LoginName IS NULL) + GROUP BY DB_NAME(dp.database_id) + ) + INSERT #deadlock_findings WITH (TABLOCKX) ( check_id, database_name, object_name, finding_group, finding ) - SELECT 11 AS check_id, - wt.database_name, - '-' AS object_name, - 'Total database deadlock wait time' AS finding_group, - 'This database has had ' - + CONVERT(VARCHAR(10), (SUM(DISTINCT CONVERT(BIGINT, wt.total_wait_time_ms)) / 1000) / 86400) - + ':' + CONVERT(VARCHAR(20), DATEADD(SECOND, (SUM(DISTINCT CONVERT(BIGINT, wt.total_wait_time_ms)) / 1000), 0), 108) - + ' [d/h/m/s] of deadlock wait time.' - FROM wait_time AS wt - GROUP BY wt.database_name - OPTION ( RECOMPILE ); - - /*Check 12 gets total deadlock wait time for SQL Agent*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + SELECT 11 AS check_id, + wt.database_name, + '-' AS object_name, + 'Total database deadlock wait time' AS finding_group, + 'This database has had ' + + CONVERT(varchar(10), (SUM(DISTINCT CONVERT(bigint, wt.total_wait_time_ms)) / 1000) / 86400) + + ':' + CONVERT(varchar(20), DATEADD(SECOND, (SUM(DISTINCT CONVERT(bigint, wt.total_wait_time_ms)) / 1000), 0), 108) + + ' [d/h/m/s] of deadlock wait time.' + FROM wait_time AS wt + GROUP BY wt.database_name + OPTION (RECOMPILE); + + /*Check 12 gets total deadlock wait time for SQL Agent*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Check 12 %s', 0, 1, @d) WITH NOWAIT; - INSERT #deadlock_findings WITH (TABLOCKX) + INSERT #deadlock_findings WITH (TABLOCKX) ( check_id, database_name, object_name, finding_group, finding ) SELECT 12, DB_NAME(aj.database_id), @@ -1259,468 +1607,468 @@ You need to use an Azure storage account, and the path has to look like this: ht RTRIM(COUNT(*)) + ' deadlocks from this Agent Job and Step' FROM #agent_job AS aj GROUP BY DB_NAME(aj.database_id), aj.job_name, aj.step_name - OPTION ( RECOMPILE ); + OPTION (RECOMPILE); - /*Check 13 is total parallel deadlocks*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + /*Check 13 is total parallel deadlocks*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Check 13 %s', 0, 1, @d) WITH NOWAIT; - INSERT #deadlock_findings WITH (TABLOCKX) - ( check_id, database_name, object_name, finding_group, finding ) - SELECT 13 AS check_id, - N'-' AS database_name, - '-' AS object_name, - 'Total parallel deadlocks' AS finding_group, - 'There have been ' - + CONVERT(NVARCHAR(20), COUNT_BIG(DISTINCT drp.event_date)) - + ' parallel deadlocks.' + INSERT #deadlock_findings WITH (TABLOCKX) + ( check_id, database_name, object_name, finding_group, finding ) + SELECT 13 AS check_id, + N'-' AS database_name, + '-' AS object_name, + 'Total parallel deadlocks' AS finding_group, + 'There have been ' + + CONVERT(nvarchar(20), COUNT_BIG(DISTINCT drp.event_date)) + + ' parallel deadlocks.' FROM #deadlock_resource_parallel AS drp - WHERE 1 = 1 - HAVING COUNT_BIG(DISTINCT drp.event_date) > 0 - OPTION ( RECOMPILE ); + WHERE 1 = 1 + HAVING COUNT_BIG(DISTINCT drp.event_date) > 0 + OPTION (RECOMPILE); - /*Thank you goodnight*/ - INSERT #deadlock_findings WITH (TABLOCKX) + /*Thank you goodnight*/ + INSERT #deadlock_findings WITH (TABLOCKX) ( check_id, database_name, object_name, finding_group, finding ) - VALUES ( -1, - N'sp_BlitzLock ' + CAST(CONVERT(DATETIME, @VersionDate, 102) AS VARCHAR(100)), - N'SQL Server First Responder Kit', - N'http://FirstResponderKit.org/', - N'To get help or add your own contributions, join us at http://FirstResponderKit.org.'); + VALUES ( -1, + N'sp_BlitzLock ' + CAST(CONVERT(datetime, @VersionDate, 102) AS varchar(100)), + N'SQL Server First Responder Kit', + N'http://FirstResponderKit.org/', + N'To get help or add your own contributions, join us at http://FirstResponderKit.org.'); - + - /*Results*/ + /*Results*/ /*Break in case of emergency*/ --CREATE CLUSTERED INDEX cx_whatever ON #deadlock_process (event_date, id); --CREATE CLUSTERED INDEX cx_whatever ON #deadlock_resource_parallel (event_date, owner_id); --CREATE CLUSTERED INDEX cx_whatever ON #deadlock_owner_waiter (event_date, owner_id, waiter_id); - IF(@OutputDatabaseCheck = 0) - BEGIN - - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + IF(@OutputDatabaseCheck = 0) + BEGIN + + SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Results 1 %s', 0, 1, @d) WITH NOWAIT; - WITH deadlocks - AS ( SELECT N'Regular Deadlock' AS deadlock_type, - dp.event_date, - dp.id, - dp.victim_id, - dp.spid, - dp.database_id, - dp.priority, - dp.log_used, - dp.wait_resource COLLATE DATABASE_DEFAULT AS wait_resource, - CONVERT( - XML, - STUFF(( SELECT DISTINCT NCHAR(10) - + N' ' - + ISNULL(c.object_name, N'') - + N' ' COLLATE DATABASE_DEFAULT AS object_name - FROM #deadlock_owner_waiter AS c - WHERE ( dp.id = c.owner_id - OR dp.victim_id = c.waiter_id ) - AND dp.event_date = c.event_date - FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(4000)'), - 1, 1, N'')) AS object_names, - dp.wait_time, - dp.transaction_name, - dp.last_tran_started, - dp.last_batch_started, - dp.last_batch_completed, - dp.lock_mode, - dp.transaction_count, - dp.client_app, - dp.host_name, - dp.login_name, - dp.isolation_level, - dp.process_xml.value('(//process/inputbuf/text())[1]', 'NVARCHAR(MAX)') AS inputbuf, - DENSE_RANK() OVER ( ORDER BY dp.event_date ) AS en, - ROW_NUMBER() OVER ( PARTITION BY dp.event_date ORDER BY dp.event_date ) -1 AS qn, - ROW_NUMBER() OVER ( PARTITION BY dp.event_date, dp.id ORDER BY dp.event_date ) AS dn, - dp.is_victim, - ISNULL(dp.owner_mode, '-') AS owner_mode, - NULL AS owner_waiter_type, - NULL AS owner_activity, - NULL AS owner_waiter_activity, - NULL AS owner_merging, - NULL AS owner_spilling, - NULL AS owner_waiting_to_close, - ISNULL(dp.waiter_mode, '-') AS waiter_mode, - NULL AS waiter_waiter_type, - NULL AS waiter_owner_activity, - NULL AS waiter_waiter_activity, - NULL AS waiter_merging, - NULL AS waiter_spilling, - NULL AS waiter_waiting_to_close, - dp.deadlock_graph - FROM #deadlock_process AS dp - WHERE dp.victim_id IS NOT NULL - AND dp.is_parallel = 0 - - UNION ALL - - SELECT N'Parallel Deadlock' AS deadlock_type, - dp.event_date, - dp.id, - dp.victim_id, - dp.spid, - dp.database_id, - dp.priority, - dp.log_used, - dp.wait_resource COLLATE DATABASE_DEFAULT, - CONVERT( - XML, - STUFF(( SELECT DISTINCT NCHAR(10) - + N' ' - + ISNULL(c.object_name, N'') - + N' ' COLLATE DATABASE_DEFAULT AS object_name - FROM #deadlock_owner_waiter AS c - WHERE ( dp.id = c.owner_id - OR dp.victim_id = c.waiter_id ) - AND dp.event_date = c.event_date - FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(4000)'), - 1, 1, N'')) AS object_names, - dp.wait_time, - dp.transaction_name, - dp.last_tran_started, - dp.last_batch_started, - dp.last_batch_completed, - dp.lock_mode, - dp.transaction_count, - dp.client_app, - dp.host_name, - dp.login_name, - dp.isolation_level, - dp.process_xml.value('(//process/inputbuf/text())[1]', 'NVARCHAR(MAX)') AS inputbuf, - DENSE_RANK() OVER ( ORDER BY dp.event_date ) AS en, - ROW_NUMBER() OVER ( PARTITION BY dp.event_date ORDER BY dp.event_date ) -1 AS qn, - ROW_NUMBER() OVER ( PARTITION BY dp.event_date, dp.id ORDER BY dp.event_date ) AS dn, - 1 AS is_victim, - cao.wait_type COLLATE DATABASE_DEFAULT AS owner_mode, - cao.waiter_type AS owner_waiter_type, - cao.owner_activity AS owner_activity, - cao.waiter_activity AS owner_waiter_activity, - cao.merging AS owner_merging, - cao.spilling AS owner_spilling, - cao.waiting_to_close AS owner_waiting_to_close, - caw.wait_type COLLATE DATABASE_DEFAULT AS waiter_mode, - caw.waiter_type AS waiter_waiter_type, - caw.owner_activity AS waiter_owner_activity, - caw.waiter_activity AS waiter_waiter_activity, - caw.merging AS waiter_merging, - caw.spilling AS waiter_spilling, - caw.waiting_to_close AS waiter_waiting_to_close, - dp.deadlock_graph - FROM #deadlock_process AS dp - CROSS APPLY (SELECT TOP 1 * FROM #deadlock_resource_parallel AS drp WHERE drp.owner_id = dp.id AND drp.wait_type = 'e_waitPipeNewRow' ORDER BY drp.event_date) AS cao - CROSS APPLY (SELECT TOP 1 * FROM #deadlock_resource_parallel AS drp WHERE drp.owner_id = dp.id AND drp.wait_type = 'e_waitPipeGetRow' ORDER BY drp.event_date) AS caw - WHERE dp.is_parallel = 1 ) - insert into DeadLockTbl ( - ServerName, - deadlock_type, - event_date, - database_name, - spid, - deadlock_group, - query, - object_names, - isolation_level, - owner_mode, - waiter_mode, - transaction_count, - login_name, - host_name, - client_app, - wait_time, - wait_resource, - priority, - log_used, - last_tran_started, - last_batch_started, - last_batch_completed, - transaction_name, - owner_waiter_type, - owner_activity, - owner_waiter_activity, - owner_merging, - owner_spilling, - owner_waiting_to_close, - waiter_waiter_type, - waiter_owner_activity, - waiter_waiter_activity, - waiter_merging, - waiter_spilling, - waiter_waiting_to_close, - deadlock_graph - ) - SELECT @ServerName, - d.deadlock_type, - d.event_date, - DB_NAME(d.database_id) AS database_name, - d.spid, - 'Deadlock #' - + CONVERT(NVARCHAR(10), d.en) - + ', Query #' - + CASE WHEN d.qn = 0 THEN N'1' ELSE CONVERT(NVARCHAR(10), d.qn) END - + CASE WHEN d.is_victim = 1 THEN ' - VICTIM' ELSE '' END - AS deadlock_group, - CONVERT(XML, N'') AS query, - d.object_names, - d.isolation_level, - d.owner_mode, - d.waiter_mode, - d.transaction_count, - d.login_name, - d.host_name, - d.client_app, - d.wait_time, - d.wait_resource, - d.priority, - d.log_used, - d.last_tran_started, - d.last_batch_started, - d.last_batch_completed, - d.transaction_name, - /*These columns will be NULL for regular (non-parallel) deadlocks*/ - d.owner_waiter_type, - d.owner_activity, - d.owner_waiter_activity, - d.owner_merging, - d.owner_spilling, - d.owner_waiting_to_close, - d.waiter_waiter_type, - d.waiter_owner_activity, - d.waiter_waiter_activity, - d.waiter_merging, - d.waiter_spilling, - d.waiter_waiting_to_close, - d.deadlock_graph - FROM deadlocks AS d - WHERE d.dn = 1 - AND (is_victim = @VictimsOnly OR @VictimsOnly = 0) - AND d.qn < CASE WHEN d.deadlock_type = N'Parallel Deadlock' THEN 2 ELSE 2147483647 END - AND (DB_NAME(d.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (d.event_date >= @StartDate OR @StartDate IS NULL) - AND (d.event_date < @EndDate OR @EndDate IS NULL) - AND (CONVERT(NVARCHAR(MAX), d.object_names) LIKE '%' + @ObjectName + '%' OR @ObjectName IS NULL) - AND (d.client_app = @AppName OR @AppName IS NULL) - AND (d.host_name = @HostName OR @HostName IS NULL) - AND (d.login_name = @LoginName OR @LoginName IS NULL) - ORDER BY d.event_date, is_victim DESC - OPTION ( RECOMPILE ); - - drop SYNONYM DeadLockTbl; --done insert into blitzlock table going to insert into findings table first create synonym. - - -- RAISERROR('att deadlock findings', 0, 1) WITH NOWAIT; - - - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + WITH deadlocks + AS ( SELECT N'Regular Deadlock' AS deadlock_type, + dp.event_date, + dp.id, + dp.victim_id, + dp.spid, + dp.database_id, + dp.priority, + dp.log_used, + dp.wait_resource COLLATE DATABASE_DEFAULT AS wait_resource, + CONVERT( + xml, + STUFF(( SELECT DISTINCT NCHAR(10) + + N' ' + + ISNULL(c.object_name, N'') + + N' ' COLLATE DATABASE_DEFAULT AS object_name + FROM #deadlock_owner_waiter AS c + WHERE ( dp.id = c.owner_id + OR dp.victim_id = c.waiter_id ) + AND dp.event_date = c.event_date + FOR xml PATH(N''), TYPE ).value(N'.[1]', N'nvarchar(4000)'), + 1, 1, N'')) AS object_names, + dp.wait_time, + dp.transaction_name, + dp.last_tran_started, + dp.last_batch_started, + dp.last_batch_completed, + dp.lock_mode, + dp.transaction_count, + dp.client_app, + dp.host_name, + dp.login_name, + dp.isolation_level, + dp.process_xml.value('(//process/inputbuf/text())[1]', 'nvarchar(MAX)') AS inputbuf, + DENSE_RANK() OVER ( ORDER BY dp.event_date ) AS en, + ROW_NUMBER() OVER ( PARTITION BY dp.event_date ORDER BY dp.event_date ) -1 AS qn, + ROW_NUMBER() OVER ( PARTITION BY dp.event_date, dp.id ORDER BY dp.event_date ) AS dn, + dp.is_victim, + ISNULL(dp.owner_mode, '-') AS owner_mode, + NULL AS owner_waiter_type, + NULL AS owner_activity, + NULL AS owner_waiter_activity, + NULL AS owner_merging, + NULL AS owner_spilling, + NULL AS owner_waiting_to_close, + ISNULL(dp.waiter_mode, '-') AS waiter_mode, + NULL AS waiter_waiter_type, + NULL AS waiter_owner_activity, + NULL AS waiter_waiter_activity, + NULL AS waiter_merging, + NULL AS waiter_spilling, + NULL AS waiter_waiting_to_close, + dp.deadlock_graph + FROM #deadlock_process AS dp + WHERE dp.victim_id IS NOT NULL + AND dp.is_parallel = 0 + + UNION ALL + + SELECT N'Parallel Deadlock' AS deadlock_type, + dp.event_date, + dp.id, + dp.victim_id, + dp.spid, + dp.database_id, + dp.priority, + dp.log_used, + dp.wait_resource COLLATE DATABASE_DEFAULT, + CONVERT( + xml, + STUFF(( SELECT DISTINCT NCHAR(10) + + N' ' + + ISNULL(c.object_name, N'') + + N' ' COLLATE DATABASE_DEFAULT AS object_name + FROM #deadlock_owner_waiter AS c + WHERE ( dp.id = c.owner_id + OR dp.victim_id = c.waiter_id ) + AND dp.event_date = c.event_date + FOR xml PATH(N''), TYPE ).value(N'.[1]', N'nvarchar(4000)'), + 1, 1, N'')) AS object_names, + dp.wait_time, + dp.transaction_name, + dp.last_tran_started, + dp.last_batch_started, + dp.last_batch_completed, + dp.lock_mode, + dp.transaction_count, + dp.client_app, + dp.host_name, + dp.login_name, + dp.isolation_level, + dp.process_xml.value('(//process/inputbuf/text())[1]', 'nvarchar(MAX)') AS inputbuf, + DENSE_RANK() OVER ( ORDER BY dp.event_date ) AS en, + ROW_NUMBER() OVER ( PARTITION BY dp.event_date ORDER BY dp.event_date ) -1 AS qn, + ROW_NUMBER() OVER ( PARTITION BY dp.event_date, dp.id ORDER BY dp.event_date ) AS dn, + 1 AS is_victim, + cao.wait_type COLLATE DATABASE_DEFAULT AS owner_mode, + cao.waiter_type AS owner_waiter_type, + cao.owner_activity AS owner_activity, + cao.waiter_activity AS owner_waiter_activity, + cao.merging AS owner_merging, + cao.spilling AS owner_spilling, + cao.waiting_to_close AS owner_waiting_to_close, + caw.wait_type COLLATE DATABASE_DEFAULT AS waiter_mode, + caw.waiter_type AS waiter_waiter_type, + caw.owner_activity AS waiter_owner_activity, + caw.waiter_activity AS waiter_waiter_activity, + caw.merging AS waiter_merging, + caw.spilling AS waiter_spilling, + caw.waiting_to_close AS waiter_waiting_to_close, + dp.deadlock_graph + FROM #deadlock_process AS dp + CROSS APPLY (SELECT TOP 1 * FROM #deadlock_resource_parallel AS drp WHERE drp.owner_id = dp.id AND drp.wait_type = 'e_waitPipeNewRow' ORDER BY drp.event_date) AS cao + CROSS APPLY (SELECT TOP 1 * FROM #deadlock_resource_parallel AS drp WHERE drp.owner_id = dp.id AND drp.wait_type = 'e_waitPipeGetRow' ORDER BY drp.event_date) AS caw + WHERE dp.is_parallel = 1 ) + INSERT INTO DeadLockTbl ( + ServerName, + deadlock_type, + event_date, + database_name, + spid, + deadlock_group, + query, + object_names, + isolation_level, + owner_mode, + waiter_mode, + transaction_count, + login_name, + host_name, + client_app, + wait_time, + wait_resource, + priority, + log_used, + last_tran_started, + last_batch_started, + last_batch_completed, + transaction_name, + owner_waiter_type, + owner_activity, + owner_waiter_activity, + owner_merging, + owner_spilling, + owner_waiting_to_close, + waiter_waiter_type, + waiter_owner_activity, + waiter_waiter_activity, + waiter_merging, + waiter_spilling, + waiter_waiting_to_close, + deadlock_graph + ) + SELECT @ServerName, + d.deadlock_type, + d.event_date, + DB_NAME(d.database_id) AS database_name, + d.spid, + 'Deadlock #' + + CONVERT(nvarchar(10), d.en) + + ', Query #' + + CASE WHEN d.qn = 0 THEN N'1' ELSE CONVERT(nvarchar(10), d.qn) END + + CASE WHEN d.is_victim = 1 THEN ' - VICTIM' ELSE '' END + AS deadlock_group, + CONVERT(xml, N'') AS query, + d.object_names, + d.isolation_level, + d.owner_mode, + d.waiter_mode, + d.transaction_count, + d.login_name, + d.host_name, + d.client_app, + d.wait_time, + d.wait_resource, + d.priority, + d.log_used, + d.last_tran_started, + d.last_batch_started, + d.last_batch_completed, + d.transaction_name, + /*These columns will be NULL for regular (non-parallel) deadlocks*/ + d.owner_waiter_type, + d.owner_activity, + d.owner_waiter_activity, + d.owner_merging, + d.owner_spilling, + d.owner_waiting_to_close, + d.waiter_waiter_type, + d.waiter_owner_activity, + d.waiter_waiter_activity, + d.waiter_merging, + d.waiter_spilling, + d.waiter_waiting_to_close, + d.deadlock_graph + FROM deadlocks AS d + WHERE d.dn = 1 + AND (is_victim = @VictimsOnly OR @VictimsOnly = 0) + AND d.qn < CASE WHEN d.deadlock_type = N'Parallel Deadlock' THEN 2 ELSE 2147483647 END + AND (DB_NAME(d.database_id) = @DatabaseName OR @DatabaseName IS NULL) + AND (d.event_date >= @StartDate OR @StartDate IS NULL) + AND (d.event_date < @EndDate OR @EndDate IS NULL) + AND (CONVERT(nvarchar(MAX), d.object_names) LIKE '%' + @ObjectName + '%' OR @ObjectName IS NULL) + AND (d.client_app = @AppName OR @AppName IS NULL) + AND (d.host_name = @HostName OR @HostName IS NULL) + AND (d.login_name = @LoginName OR @LoginName IS NULL) + ORDER BY d.event_date, is_victim DESC + OPTION (RECOMPILE); + + DROP SYNONYM DeadLockTbl; --done insert into blitzlock table going to insert into findings table first create synonym. + + -- RAISERROR('att deadlock findings', 0, 1) WITH NOWAIT; + + + SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Findings %s', 0, 1, @d) WITH NOWAIT; - Insert into DeadlockFindings (ServerName,check_id,database_name,object_name,finding_group,finding) - SELECT @ServerName,df.check_id, df.database_name, df.object_name, df.finding_group, df.finding - FROM #deadlock_findings AS df - ORDER BY df.check_id - OPTION ( RECOMPILE ); + INSERT INTO DeadlockFindings (ServerName,check_id,database_name,object_name,finding_group,finding) + SELECT @ServerName,df.check_id, df.database_name, df.object_name, df.finding_group, df.finding + FROM #deadlock_findings AS df + ORDER BY df.check_id + OPTION (RECOMPILE); - drop SYNONYM DeadlockFindings; --done with inserting. -END + DROP SYNONYM DeadlockFindings; --done with inserting. +END; ELSE --Output to database is not set output to client app - BEGIN - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Results 1 %s', 0, 1, @d) WITH NOWAIT; - WITH deadlocks - AS ( SELECT N'Regular Deadlock' AS deadlock_type, - dp.event_date, - dp.id, - dp.victim_id, - dp.spid, - dp.database_id, - dp.priority, - dp.log_used, - dp.wait_resource COLLATE DATABASE_DEFAULT AS wait_resource, - CONVERT( - XML, - STUFF(( SELECT DISTINCT NCHAR(10) - + N' ' - + ISNULL(c.object_name, N'') - + N' ' COLLATE DATABASE_DEFAULT AS object_name - FROM #deadlock_owner_waiter AS c - WHERE ( dp.id = c.owner_id - OR dp.victim_id = c.waiter_id ) - AND dp.event_date = c.event_date - FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(4000)'), - 1, 1, N'')) AS object_names, - dp.wait_time, - dp.transaction_name, - dp.last_tran_started, - dp.last_batch_started, - dp.last_batch_completed, - dp.lock_mode, - dp.transaction_count, - dp.client_app, - dp.host_name, - dp.login_name, - dp.isolation_level, - dp.process_xml.value('(//process/inputbuf/text())[1]', 'NVARCHAR(MAX)') AS inputbuf, - DENSE_RANK() OVER ( ORDER BY dp.event_date ) AS en, - ROW_NUMBER() OVER ( PARTITION BY dp.event_date ORDER BY dp.event_date ) -1 AS qn, - ROW_NUMBER() OVER ( PARTITION BY dp.event_date, dp.id ORDER BY dp.event_date ) AS dn, - dp.is_victim, - ISNULL(dp.owner_mode, '-') AS owner_mode, - NULL AS owner_waiter_type, - NULL AS owner_activity, - NULL AS owner_waiter_activity, - NULL AS owner_merging, - NULL AS owner_spilling, - NULL AS owner_waiting_to_close, - ISNULL(dp.waiter_mode, '-') AS waiter_mode, - NULL AS waiter_waiter_type, - NULL AS waiter_owner_activity, - NULL AS waiter_waiter_activity, - NULL AS waiter_merging, - NULL AS waiter_spilling, - NULL AS waiter_waiting_to_close, - dp.deadlock_graph - FROM #deadlock_process AS dp - WHERE dp.victim_id IS NOT NULL - AND dp.is_parallel = 0 - - UNION ALL - - SELECT N'Parallel Deadlock' AS deadlock_type, - dp.event_date, - dp.id, - dp.victim_id, - dp.spid, - dp.database_id, - dp.priority, - dp.log_used, - dp.wait_resource COLLATE DATABASE_DEFAULT, - CONVERT( - XML, - STUFF(( SELECT DISTINCT NCHAR(10) - + N' ' - + ISNULL(c.object_name, N'') - + N' ' COLLATE DATABASE_DEFAULT AS object_name - FROM #deadlock_owner_waiter AS c - WHERE ( dp.id = c.owner_id - OR dp.victim_id = c.waiter_id ) - AND dp.event_date = c.event_date - FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(4000)'), - 1, 1, N'')) AS object_names, - dp.wait_time, - dp.transaction_name, - dp.last_tran_started, - dp.last_batch_started, - dp.last_batch_completed, - dp.lock_mode, - dp.transaction_count, - dp.client_app, - dp.host_name, - dp.login_name, - dp.isolation_level, - dp.process_xml.value('(//process/inputbuf/text())[1]', 'NVARCHAR(MAX)') AS inputbuf, - DENSE_RANK() OVER ( ORDER BY dp.event_date ) AS en, - ROW_NUMBER() OVER ( PARTITION BY dp.event_date ORDER BY dp.event_date ) -1 AS qn, - ROW_NUMBER() OVER ( PARTITION BY dp.event_date, dp.id ORDER BY dp.event_date ) AS dn, - 1 AS is_victim, - cao.wait_type COLLATE DATABASE_DEFAULT AS owner_mode, - cao.waiter_type AS owner_waiter_type, - cao.owner_activity AS owner_activity, - cao.waiter_activity AS owner_waiter_activity, - cao.merging AS owner_merging, - cao.spilling AS owner_spilling, - cao.waiting_to_close AS owner_waiting_to_close, - caw.wait_type COLLATE DATABASE_DEFAULT AS waiter_mode, - caw.waiter_type AS waiter_waiter_type, - caw.owner_activity AS waiter_owner_activity, - caw.waiter_activity AS waiter_waiter_activity, - caw.merging AS waiter_merging, - caw.spilling AS waiter_spilling, - caw.waiting_to_close AS waiter_waiting_to_close, - dp.deadlock_graph - FROM #deadlock_process AS dp - OUTER APPLY (SELECT TOP 1 * FROM #deadlock_resource_parallel AS drp WHERE drp.owner_id = dp.id AND drp.wait_type IN ('e_waitPortOpen', 'e_waitPipeNewRow') ORDER BY drp.event_date) AS cao - OUTER APPLY (SELECT TOP 1 * FROM #deadlock_resource_parallel AS drp WHERE drp.owner_id = dp.id AND drp.wait_type IN ('e_waitPortOpen', 'e_waitPipeGetRow') ORDER BY drp.event_date) AS caw - WHERE dp.is_parallel = 1 - ) - SELECT d.deadlock_type, - d.event_date, - DB_NAME(d.database_id) AS database_name, - d.spid, - 'Deadlock #' - + CONVERT(NVARCHAR(10), d.en) - + ', Query #' - + CASE WHEN d.qn = 0 THEN N'1' ELSE CONVERT(NVARCHAR(10), d.qn) END - + CASE WHEN d.is_victim = 1 THEN ' - VICTIM' ELSE '' END - AS deadlock_group, - CONVERT(XML, N'') AS query_xml, - d.inputbuf AS query_string, - d.object_names, - d.isolation_level, - d.owner_mode, - d.waiter_mode, - d.transaction_count, - d.login_name, - d.host_name, - d.client_app, - d.wait_time, - d.wait_resource, - d.priority, - d.log_used, - d.last_tran_started, - d.last_batch_started, - d.last_batch_completed, - d.transaction_name, - /*These columns will be NULL for regular (non-parallel) deadlocks*/ - d.owner_waiter_type, - d.owner_activity, - d.owner_waiter_activity, - d.owner_merging, - d.owner_spilling, - d.owner_waiting_to_close, - d.waiter_waiter_type, - d.waiter_owner_activity, - d.waiter_waiter_activity, - d.waiter_merging, - d.waiter_spilling, - d.waiter_waiting_to_close, - d.deadlock_graph, - d.is_victim - INTO #deadlock_results - FROM deadlocks AS d - WHERE d.dn = 1 - AND (d.is_victim = @VictimsOnly OR @VictimsOnly = 0) - AND d.qn < CASE WHEN d.deadlock_type = N'Parallel Deadlock' THEN 2 ELSE 2147483647 END - AND (DB_NAME(d.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (d.event_date >= @StartDate OR @StartDate IS NULL) - AND (d.event_date < @EndDate OR @EndDate IS NULL) - AND (CONVERT(NVARCHAR(MAX), d.object_names) LIKE '%' + @ObjectName + '%' OR @ObjectName IS NULL) - AND (d.client_app = @AppName OR @AppName IS NULL) - AND (d.host_name = @HostName OR @HostName IS NULL) - AND (d.login_name = @LoginName OR @LoginName IS NULL) - OPTION ( RECOMPILE ); - - - DECLARE @deadlock_result NVARCHAR(MAX) = N'' - - SET @deadlock_result += N' - SELECT - dr.deadlock_type, + BEGIN + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Results 1 %s', 0, 1, @d) WITH NOWAIT; + WITH deadlocks + AS ( SELECT N'Regular Deadlock' AS deadlock_type, + dp.event_date, + dp.id, + dp.victim_id, + dp.spid, + dp.database_id, + dp.priority, + dp.log_used, + dp.wait_resource COLLATE DATABASE_DEFAULT AS wait_resource, + CONVERT( + xml, + STUFF(( SELECT DISTINCT NCHAR(10) + + N' ' + + ISNULL(c.object_name, N'') + + N' ' COLLATE DATABASE_DEFAULT AS object_name + FROM #deadlock_owner_waiter AS c + WHERE ( dp.id = c.owner_id + OR dp.victim_id = c.waiter_id ) + AND dp.event_date = c.event_date + FOR xml PATH(N''), TYPE ).value(N'.[1]', N'nvarchar(4000)'), + 1, 1, N'')) AS object_names, + dp.wait_time, + dp.transaction_name, + dp.last_tran_started, + dp.last_batch_started, + dp.last_batch_completed, + dp.lock_mode, + dp.transaction_count, + dp.client_app, + dp.host_name, + dp.login_name, + dp.isolation_level, + dp.process_xml.value('(//process/inputbuf/text())[1]', 'nvarchar(MAX)') AS inputbuf, + DENSE_RANK() OVER ( ORDER BY dp.event_date ) AS en, + ROW_NUMBER() OVER ( PARTITION BY dp.event_date ORDER BY dp.event_date ) -1 AS qn, + ROW_NUMBER() OVER ( PARTITION BY dp.event_date, dp.id ORDER BY dp.event_date ) AS dn, + dp.is_victim, + ISNULL(dp.owner_mode, '-') AS owner_mode, + NULL AS owner_waiter_type, + NULL AS owner_activity, + NULL AS owner_waiter_activity, + NULL AS owner_merging, + NULL AS owner_spilling, + NULL AS owner_waiting_to_close, + ISNULL(dp.waiter_mode, '-') AS waiter_mode, + NULL AS waiter_waiter_type, + NULL AS waiter_owner_activity, + NULL AS waiter_waiter_activity, + NULL AS waiter_merging, + NULL AS waiter_spilling, + NULL AS waiter_waiting_to_close, + dp.deadlock_graph + FROM #deadlock_process AS dp + WHERE dp.victim_id IS NOT NULL + AND dp.is_parallel = 0 + + UNION ALL + + SELECT N'Parallel Deadlock' AS deadlock_type, + dp.event_date, + dp.id, + dp.victim_id, + dp.spid, + dp.database_id, + dp.priority, + dp.log_used, + dp.wait_resource COLLATE DATABASE_DEFAULT, + CONVERT( + xml, + STUFF(( SELECT DISTINCT NCHAR(10) + + N' ' + + ISNULL(c.object_name, N'') + + N' ' COLLATE DATABASE_DEFAULT AS object_name + FROM #deadlock_owner_waiter AS c + WHERE ( dp.id = c.owner_id + OR dp.victim_id = c.waiter_id ) + AND dp.event_date = c.event_date + FOR xml PATH(N''), TYPE ).value(N'.[1]', N'nvarchar(4000)'), + 1, 1, N'')) AS object_names, + dp.wait_time, + dp.transaction_name, + dp.last_tran_started, + dp.last_batch_started, + dp.last_batch_completed, + dp.lock_mode, + dp.transaction_count, + dp.client_app, + dp.host_name, + dp.login_name, + dp.isolation_level, + dp.process_xml.value('(//process/inputbuf/text())[1]', 'nvarchar(MAX)') AS inputbuf, + DENSE_RANK() OVER ( ORDER BY dp.event_date ) AS en, + ROW_NUMBER() OVER ( PARTITION BY dp.event_date ORDER BY dp.event_date ) -1 AS qn, + ROW_NUMBER() OVER ( PARTITION BY dp.event_date, dp.id ORDER BY dp.event_date ) AS dn, + 1 AS is_victim, + cao.wait_type COLLATE DATABASE_DEFAULT AS owner_mode, + cao.waiter_type AS owner_waiter_type, + cao.owner_activity AS owner_activity, + cao.waiter_activity AS owner_waiter_activity, + cao.merging AS owner_merging, + cao.spilling AS owner_spilling, + cao.waiting_to_close AS owner_waiting_to_close, + caw.wait_type COLLATE DATABASE_DEFAULT AS waiter_mode, + caw.waiter_type AS waiter_waiter_type, + caw.owner_activity AS waiter_owner_activity, + caw.waiter_activity AS waiter_waiter_activity, + caw.merging AS waiter_merging, + caw.spilling AS waiter_spilling, + caw.waiting_to_close AS waiter_waiting_to_close, + dp.deadlock_graph + FROM #deadlock_process AS dp + OUTER APPLY (SELECT TOP 1 * FROM #deadlock_resource_parallel AS drp WHERE drp.owner_id = dp.id AND drp.wait_type IN ('e_waitPortOpen', 'e_waitPipeNewRow') ORDER BY drp.event_date) AS cao + OUTER APPLY (SELECT TOP 1 * FROM #deadlock_resource_parallel AS drp WHERE drp.owner_id = dp.id AND drp.wait_type IN ('e_waitPortOpen', 'e_waitPipeGetRow') ORDER BY drp.event_date) AS caw + WHERE dp.is_parallel = 1 + ) + SELECT d.deadlock_type, + d.event_date, + DB_NAME(d.database_id) AS database_name, + d.spid, + 'Deadlock #' + + CONVERT(nvarchar(10), d.en) + + ', Query #' + + CASE WHEN d.qn = 0 THEN N'1' ELSE CONVERT(nvarchar(10), d.qn) END + + CASE WHEN d.is_victim = 1 THEN ' - VICTIM' ELSE '' END + AS deadlock_group, + CONVERT(xml, N'') AS query_xml, + d.inputbuf AS query_string, + d.object_names, + d.isolation_level, + d.owner_mode, + d.waiter_mode, + d.transaction_count, + d.login_name, + d.host_name, + d.client_app, + d.wait_time, + d.wait_resource, + d.priority, + d.log_used, + d.last_tran_started, + d.last_batch_started, + d.last_batch_completed, + d.transaction_name, + /*These columns will be NULL for regular (non-parallel) deadlocks*/ + d.owner_waiter_type, + d.owner_activity, + d.owner_waiter_activity, + d.owner_merging, + d.owner_spilling, + d.owner_waiting_to_close, + d.waiter_waiter_type, + d.waiter_owner_activity, + d.waiter_waiter_activity, + d.waiter_merging, + d.waiter_spilling, + d.waiter_waiting_to_close, + d.deadlock_graph, + d.is_victim + INTO #deadlock_results + FROM deadlocks AS d + WHERE d.dn = 1 + AND (d.is_victim = @VictimsOnly OR @VictimsOnly = 0) + AND d.qn < CASE WHEN d.deadlock_type = N'Parallel Deadlock' THEN 2 ELSE 2147483647 END + AND (DB_NAME(d.database_id) = @DatabaseName OR @DatabaseName IS NULL) + AND (d.event_date >= @StartDate OR @StartDate IS NULL) + AND (d.event_date < @EndDate OR @EndDate IS NULL) + AND (CONVERT(nvarchar(MAX), d.object_names) LIKE '%' + @ObjectName + '%' OR @ObjectName IS NULL) + AND (d.client_app = @AppName OR @AppName IS NULL) + AND (d.host_name = @HostName OR @HostName IS NULL) + AND (d.login_name = @LoginName OR @LoginName IS NULL) + OPTION (RECOMPILE); + + + DECLARE @deadlock_result nvarchar(MAX) = N''; + + SET @deadlock_result += N' + SELECT + dr.deadlock_type, dr.event_date, dr.database_name, dr.spid, dr.deadlock_group, - ' - + CASE @ExportToExcel - WHEN 1 - THEN N'dr.query_string AS query, - REPLACE(REPLACE(CONVERT(NVARCHAR(MAX), dr.object_names), '''', ''''), '''', '''') AS object_names,' - ELSE N'dr.query_xml AS query, - dr.object_names,' - END + - N' + ' + + CASE @ExportToExcel + WHEN 1 + THEN N'dr.query_string AS query, + REPLACE(REPLACE(CONVERT(nvarchar(MAX), dr.object_names), '''', ''''), '''', '''') AS object_names,' + ELSE N'dr.query_xml AS query, + dr.object_names,' + END + + N' dr.isolation_level, dr.owner_mode, dr.waiter_mode, @@ -1748,31 +2096,31 @@ ELSE --Output to database is not set output to client app dr.waiter_merging, dr.waiter_spilling, dr.waiter_waiting_to_close' - + CASE @ExportToExcel - WHEN 1 - THEN N'' - ELSE N', - dr.deadlock_graph' - END + - ' - FROM #deadlock_results AS dr - ORDER BY dr.event_date, dr.is_victim DESC - OPTION(RECOMPILE); - ' - - EXEC sys.sp_executesql - @deadlock_result; - - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Findings %s', 0, 1, @d) WITH NOWAIT; - SELECT df.check_id, df.database_name, df.object_name, df.finding_group, df.finding - FROM #deadlock_findings AS df - ORDER BY df.check_id - OPTION ( RECOMPILE ); - - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Done %s', 0, 1, @d) WITH NOWAIT; - END --done with output to client app. + + CASE @ExportToExcel + WHEN 1 + THEN N'' + ELSE N', + dr.deadlock_graph' + END + + ' + FROM #deadlock_results AS dr + ORDER BY dr.event_date, dr.is_victim DESC + OPTION(RECOMPILE); + '; + + EXEC sys.sp_executesql + @deadlock_result; + + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Findings %s', 0, 1, @d) WITH NOWAIT; + SELECT df.check_id, df.database_name, df.object_name, df.finding_group, df.finding + FROM #deadlock_findings AS df + ORDER BY df.check_id + OPTION (RECOMPILE); + + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Done %s', 0, 1, @d) WITH NOWAIT; + END; --done with output to client app. @@ -1781,31 +2129,31 @@ ELSE --Output to database is not set output to client app SELECT '#deadlock_data' AS table_name, * FROM #deadlock_data AS dd - OPTION ( RECOMPILE ); + OPTION (RECOMPILE); SELECT '#deadlock_resource' AS table_name, * FROM #deadlock_resource AS dr - OPTION ( RECOMPILE ); + OPTION (RECOMPILE); SELECT '#deadlock_resource_parallel' AS table_name, * FROM #deadlock_resource_parallel AS drp - OPTION ( RECOMPILE ); + OPTION (RECOMPILE); SELECT '#deadlock_owner_waiter' AS table_name, * FROM #deadlock_owner_waiter AS dow - OPTION ( RECOMPILE ); + OPTION (RECOMPILE); SELECT '#deadlock_process' AS table_name, * FROM #deadlock_process AS dp - OPTION ( RECOMPILE ); + OPTION (RECOMPILE); SELECT '#deadlock_stack' AS table_name, * FROM #deadlock_stack AS ds - OPTION ( RECOMPILE ); + OPTION (RECOMPILE); SELECT '#deadlock_results' AS table_name, * FROM #deadlock_results AS dr - OPTION ( RECOMPILE ); + OPTION (RECOMPILE); END; -- End debug From 6b9b25c4ba3611cbba26b2bcdd0cf3e7759688dd Mon Sep 17 00:00:00 2001 From: David Hooey Date: Fri, 11 Nov 2022 09:26:48 -0500 Subject: [PATCH 326/662] Issue #3159 - Move blocking_session_id in BlitzWho output beside session_id for easier block identification --- sp_BlitzWho.sql | 48 ++++++++++++++++++++++++------------------------ 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/sp_BlitzWho.sql b/sp_BlitzWho.sql index 8fa278823..86d763d07 100644 --- a/sp_BlitzWho.sql +++ b/sp_BlitzWho.sql @@ -678,6 +678,16 @@ BEGIN */ SET @StringToExecute = N'COALESCE( RIGHT(''00'' + CONVERT(VARCHAR(20), (ABS(r.total_elapsed_time) / 1000) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), (DATEADD(SECOND, (r.total_elapsed_time / 1000), 0) + DATEADD(MILLISECOND, (r.total_elapsed_time % 1000), 0)), 114), RIGHT(''00'' + CONVERT(VARCHAR(20), DATEDIFF(SECOND, s.last_request_start_time, GETDATE()) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), DATEADD(SECOND, DATEDIFF(SECOND, s.last_request_start_time, GETDATE()), 0), 114) ) AS [elapsed_time] , s.session_id , + CASE WHEN r.blocking_session_id <> 0 AND blocked.session_id IS NULL + THEN r.blocking_session_id + WHEN r.blocking_session_id <> 0 AND s.session_id <> blocked.blocking_session_id + THEN blocked.blocking_session_id + WHEN r.blocking_session_id = 0 AND s.session_id = blocked.session_id + THEN blocked.blocking_session_id + WHEN r.blocking_session_id <> 0 AND s.session_id = blocked.blocking_session_id + THEN r.blocking_session_id + ELSE NULL + END AS blocking_session_id, COALESCE(DB_NAME(r.database_id), DB_NAME(blocked.dbid), ''N/A'') AS database_name, ISNULL(SUBSTRING(dest.text, ( query_stats.statement_start_offset / 2 ) + 1, @@ -698,16 +708,6 @@ BEGIN ELSE NULL END AS wait_info , r.wait_resource , - CASE WHEN r.blocking_session_id <> 0 AND blocked.session_id IS NULL - THEN r.blocking_session_id - WHEN r.blocking_session_id <> 0 AND s.session_id <> blocked.blocking_session_id - THEN blocked.blocking_session_id - WHEN r.blocking_session_id = 0 AND s.session_id = blocked.session_id - THEN blocked.blocking_session_id - WHEN r.blocking_session_id <> 0 AND s.session_id = blocked.blocking_session_id - THEN r.blocking_session_id - ELSE NULL - END AS blocking_session_id, COALESCE(r.open_transaction_count, blocked.open_tran) AS open_transaction_count , CASE WHEN EXISTS ( SELECT 1 FROM sys.dm_tran_active_transactions AS tat @@ -896,8 +896,18 @@ IF @ProductVersionMajor >= 11 */ SELECT @StringToExecute = N'COALESCE( RIGHT(''00'' + CONVERT(VARCHAR(20), (ABS(r.total_elapsed_time) / 1000) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), (DATEADD(SECOND, (r.total_elapsed_time / 1000), 0) + DATEADD(MILLISECOND, (r.total_elapsed_time % 1000), 0)), 114), RIGHT(''00'' + CONVERT(VARCHAR(20), DATEDIFF(SECOND, s.last_request_start_time, GETDATE()) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), DATEADD(SECOND, DATEDIFF(SECOND, s.last_request_start_time, GETDATE()), 0), 114) ) AS [elapsed_time] , s.session_id , - COALESCE(DB_NAME(r.database_id), DB_NAME(blocked.dbid), ''N/A'') AS database_name, - ISNULL(SUBSTRING(dest.text, + CASE WHEN r.blocking_session_id <> 0 AND blocked.session_id IS NULL + THEN r.blocking_session_id + WHEN r.blocking_session_id <> 0 AND s.session_id <> blocked.blocking_session_id + THEN blocked.blocking_session_id + WHEN r.blocking_session_id = 0 AND s.session_id = blocked.session_id + THEN blocked.blocking_session_id + WHEN r.blocking_session_id <> 0 AND s.session_id = blocked.blocking_session_id + THEN r.blocking_session_id + ELSE NULL + END AS blocking_session_id, + COALESCE(DB_NAME(r.database_id), DB_NAME(blocked.dbid), ''N/A'') AS database_name, + ISNULL(SUBSTRING(dest.text, ( query_stats.statement_start_offset / 2 ) + 1, ( ( CASE query_stats.statement_end_offset WHEN -1 THEN DATALENGTH(dest.text) @@ -941,17 +951,7 @@ IF @ProductVersionMajor >= 11 ELSE N' NULL AS top_session_waits ,' END + - N'CASE WHEN r.blocking_session_id <> 0 AND blocked.session_id IS NULL - THEN r.blocking_session_id - WHEN r.blocking_session_id <> 0 AND s.session_id <> blocked.blocking_session_id - THEN blocked.blocking_session_id - WHEN r.blocking_session_id = 0 AND s.session_id = blocked.session_id - THEN blocked.blocking_session_id - WHEN r.blocking_session_id <> 0 AND s.session_id = blocked.blocking_session_id - THEN r.blocking_session_id - ELSE NULL - END AS blocking_session_id, - COALESCE(r.open_transaction_count, blocked.open_tran) AS open_transaction_count , + N'COALESCE(r.open_transaction_count, blocked.open_tran) AS open_transaction_count , CASE WHEN EXISTS ( SELECT 1 FROM sys.dm_tran_active_transactions AS tat JOIN sys.dm_tran_session_transactions AS tst @@ -1255,6 +1255,7 @@ IF @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @Output ,CheckDate ,[elapsed_time] ,[session_id] + ,[blocking_session_id] ,[database_name] ,[query_text]' + CASE WHEN @GetOuterCommand = 1 THEN N',[outer_command]' ELSE N'' END + N' @@ -1267,7 +1268,6 @@ IF @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @Output ,[wait_info] ,[wait_resource]' + CASE WHEN @ProductVersionMajor >= 11 THEN N',[top_session_waits]' ELSE N'' END + N' - ,[blocking_session_id] ,[open_transaction_count] ,[is_implicit_transaction] ,[nt_domain] From c4375c906d806652d8a118d9057f4688f6e502b4 Mon Sep 17 00:00:00 2001 From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com> Date: Fri, 11 Nov 2022 13:53:34 -0500 Subject: [PATCH 327/662] Update sp_BlitzLock.sql WIP --- sp_BlitzLock.sql | 3621 ++++++++++++++++++++++++++-------------------- 1 file changed, 2075 insertions(+), 1546 deletions(-) diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index e8326e424..e5a804bcf 100644 --- a/sp_BlitzLock.sql +++ b/sp_BlitzLock.sql @@ -1,52 +1,51 @@ IF OBJECT_ID('dbo.sp_BlitzLock') IS NULL - EXEC ('CREATE PROCEDURE dbo.sp_BlitzLock AS RETURN 0;'); + EXEC ('CREATE PROCEDURE dbo.sp_BlitzLock AS RETURN 0;'); GO + ALTER PROCEDURE dbo.sp_BlitzLock ( @Top bigint = 9223372036854775807, @DatabaseName nvarchar(256) = NULL, - @StartDate datetime = '19000101', - @EndDate datetime = '99991231', + @StartDate datetime = '19000101', + @EndDate datetime = '99991231', @ObjectName nvarchar(1000) = NULL, @StoredProcName nvarchar(1000) = NULL, @AppName nvarchar(256) = NULL, @HostName nvarchar(256) = NULL, @LoginName nvarchar(256) = NULL, @EventSessionName varchar(256) = 'system_health', - @TargetSessionType varchar(20) = 'event_file', + @TargetSessionType varchar(20) = 'event_file', @VictimsOnly bit = 0, - @Debug bit = 0, + @Debug bit = 0, @Help bit = 0, @Version varchar(30) = NULL OUTPUT, @VersionDate datetime = NULL OUTPUT, @VersionCheckMode bit = 0, - @OutputDatabaseName nvarchar(256) = NULL , - @OutputSchemaName nvarchar(256) = 'dbo' , --ditto as below - @OutputTableName nvarchar(256) = 'BlitzLock', --put a standard here no need to check later in the script + @OutputDatabaseName nvarchar(256) = NULL, + @OutputSchemaName nvarchar(256) = 'dbo', --ditto as below + @OutputTableName nvarchar(256) = 'BlitzLock', --put a standard here no need to check later in the script @ExportToExcel bit = 0 ) WITH RECOMPILE AS BEGIN + SET STATISTICS XML OFF; + SET NOCOUNT, XACT_ABORT ON; + SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SET STATISTICS xml OFF; -SET NOCOUNT, XACT_ABORT ON; -SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + SELECT @Version = '8.11', @VersionDate = '20221013'; -SELECT - @Version = '8.11', - @VersionDate = '20221013'; + IF (@VersionCheckMode = 1) + BEGIN + RETURN; + END; -IF(@VersionCheckMode = 1) -BEGIN - RETURN; -END; -IF @Help = 1 + IF @Help = 1 BEGIN - PRINT ' + PRINT ' /* sp_BlitzLock from http://FirstResponderKit.org @@ -54,9 +53,9 @@ IF @Help = 1 Variables you can use: - @Top: Limit the number of rows to return + @Top: Limit the number of rows to return - @DatabaseName: If you want to filter to a specific database + @DatabaseName: If you want to filter to a specific database @StartDate: The date you want to start searching on. @@ -76,7 +75,7 @@ IF @Help = 1 @EventSessionPath: If you want to point this at an XE session rather than the system health session. - @TargetSessionType: Can be ring_buffer or event_file. + @TargetSessionType: Can be ring_buffer or event_file. @OutputDatabaseName: If you want to output information to a specific database @OutputSchemaName: Specify a schema name to output information to a specific Schema @@ -118,668 +117,716 @@ IF @Help = 1 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */'; - RETURN; - END; /* @Help = 1 */ - - DECLARE - @ProductVersion nvarchar(128) = - CAST(SERVERPROPERTY('ProductVersion') AS nvarchar(128)), - @ProductVersionMajor float = - SUBSTRING(CAST(SERVERPROPERTY('ProductVersion') AS nvarchar(128)), 1, CHARINDEX('.', CAST(SERVERPROPERTY('ProductVersion') AS nvarchar(128))) + 1), - @ProductVersionMinor int = - PARSENAME(CONVERT(varchar(32), CAST(SERVERPROPERTY('ProductVersion') AS nvarchar(128))), 2), - @ObjectFullName nvarchar(MAX) = N'', - @Azure bit = CASE WHEN (SELECT SERVERPROPERTY ('EDITION')) = 'SQL Azure' THEN 1 ELSE 0 END, - @RDS bigint = - CASE - WHEN LEFT(CAST(SERVERPROPERTY('ComputerNamePhysicalNetBIOS') AS varchar(8000)), 8) <> 'EC2AMAZ-' - AND LEFT(CAST(SERVERPROPERTY('MachineName') AS varchar(8000)), 8) <> 'EC2AMAZ-' - AND DB_ID('rdsadmin') IS NULL - THEN 0 - ELSE 1 - END, - @d varchar(40) = '', - @StringToExecute nvarchar(4000) = '', - @StringToExecuteParams nvarchar(500) = N'', - @r nvarchar(200) = N'', - @OutputTableFindings nvarchar(100) = N'[BlitzLockFindings]', - @DeadlockCount int = 0, - @ServerName nvarchar(256) = @@SERVERNAME, - @OutputDatabaseCheck bit = NULL, - @SessionId int, - @TargetSessionId int, - @FileName nvarchar(4000), - @NC10 nvarchar(1) = CONVERT(nvarchar(1), 0x0a00, 0); - - CREATE TABLE - #x - ( - x xml - ); - - CREATE TABLE - #deadlock_data - ( - deadlock_xml xml, - event_time datetime - ); - - IF @StartDate IS NULL + */' ; + + + RETURN; + END; /* @Help = 1 */ + + + DECLARE + @ProductVersion nvarchar(128) = + CAST(SERVERPROPERTY('ProductVersion') AS nvarchar(128)), + @ProductVersionMajor float = + SUBSTRING + ( + CAST(SERVERPROPERTY('ProductVersion') AS nvarchar(128)), + 1, + CHARINDEX('.', CAST(SERVERPROPERTY('ProductVersion') AS nvarchar(128))) + 1 + ), + @ProductVersionMinor int = + PARSENAME + ( + CONVERT + ( + varchar(32), + CAST(SERVERPROPERTY('ProductVersion') AS nvarchar(128)) + ), + 2 + ), + @ObjectFullName nvarchar(MAX) = N'', + @Azure bit = + CASE + WHEN + ( + SELECT + SERVERPROPERTY('EDITION') + ) = 'SQL Azure' + THEN 1 + ELSE 0 + END, + @RDS bigint = + CASE + WHEN LEFT(CAST(SERVERPROPERTY('ComputerNamePhysicalNetBIOS') AS varchar(8000)), 8) <> 'EC2AMAZ-' + AND LEFT(CAST(SERVERPROPERTY('MachineName') AS varchar(8000)), 8) <> 'EC2AMAZ-' + AND DB_ID('rdsadmin') IS NULL + THEN 0 + ELSE 1 + END, + @d varchar(40) = '', + @StringToExecute nvarchar(4000) = N'', + @StringToExecuteParams nvarchar(500) = N'', + @r nvarchar(200) = N'', + @OutputTableFindings nvarchar(100) = N'[BlitzLockFindings]', + @DeadlockCount int = 0, + @ServerName nvarchar(256) = @@SERVERNAME, + @OutputDatabaseCheck bit = NULL, + @SessionId int, + @TargetSessionId int, + @FileName nvarchar(4000), + @NC10 nvarchar(1) = CONVERT(nvarchar(1), 0x0a00, 0), + @deadlock_result nvarchar(MAX) = N''; + + DECLARE + @sysAssObjId AS table + ( + database_id bigint, + partition_id bigint, + schema_name varchar(255), + table_name varchar(255) + ); + + + CREATE TABLE + #x + ( + x xml + ); + + + CREATE TABLE + #deadlock_data + ( + deadlock_xml xml, + event_date datetime + ); + + IF @StartDate IS NULL + BEGIN + SET @StartDate = '19000101'; + END; + + IF @EndDate IS NULL + BEGIN + SET @EndDate = '99991231'; + END; + + CREATE TABLE + #deadlock_findings + ( + id int IDENTITY PRIMARY KEY, + check_id int NOT NULL, + database_name nvarchar(256), + object_name nvarchar(1000), + finding_group nvarchar(100), + finding nvarchar(4000) + ); + + SET @d = CONVERT(varchar(40), GETDATE(), 109); + + IF (@OutputDatabaseName IS NOT NULL) + BEGIN --if databaseName is set do some sanity checks and put [] around def. + IF NOT EXISTS + ( + SELECT + 1/0 + FROM sys.databases WHERE name = @OutputDatabaseName + ) --if database is invalid raiserror and set bitcheck BEGIN - SET @StartDate = '19000101'; - END; + RAISERROR('Database Name for output of table is invalid please correct, Output to Table will not be preformed', 0, 1, @d) WITH NOWAIT; - IF @EndDate IS NULL + SET @OutputDatabaseCheck = -1; -- -1 invalid/false, 0 = good/true + END; + ELSE BEGIN - SET @EndDate = '99991231'; - END + SET @OutputDatabaseCheck = 0; + SELECT + @StringToExecute = + N'SELECT @r = name FROM ' + + N'' + + @OutputDatabaseName + + N'' + + N'.sys.objects WHERE type_desc = ''USER_TABLE'' AND name= ' + + N'''' + + @OutputTableName + + N'''', + @StringToExecuteParams = + N'@OutputDatabaseName nvarchar(200), + @OutputTableName nvarchar(200), + @r nvarchar(200) OUTPUT'; - CREATE TABLE - #deadlock_findings - ( - id int IDENTITY(1, 1) PRIMARY KEY CLUSTERED, - check_id int NOT NULL, - database_name nvarchar(256), - object_name nvarchar(1000), - finding_group nvarchar(100), - finding nvarchar(4000) - ); + EXEC sys.sp_executesql + @StringToExecute, + @StringToExecuteParams, + @OutputDatabaseName, + @OutputTableName, + @r OUTPUT; - SET @d = CONVERT(varchar(40), GETDATE(), 109); - IF(@OutputDatabaseName IS NOT NULL) - BEGIN --if databaseName is set do some sanity checks and put [] around def. - IF NOT EXISTS - ( + --put covers around all before. + SELECT + @OutputDatabaseName = QUOTENAME(@OutputDatabaseName), + @OutputTableName = QUOTENAME(@OutputTableName), + @OutputSchemaName = QUOTENAME(@OutputSchemaName); + + IF (@r IS NOT NULL) --if it is not null, there is a table, so check for newly added columns + BEGIN + /* If the table doesn't have the new spid column, add it. See Github #3101. */ + SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableName; + SET @StringToExecute = + N'IF NOT EXISTS (SELECT * FROM ' + + @OutputDatabaseName + + N'.sys.all_columns WHERE object_id = (OBJECT_ID(''' + + @ObjectFullName + + N''')) AND name = ''spid'') + /*Add spid column*/ + ALTER TABLE ' + + @ObjectFullName + + N'ADD spid smallint NULL;'; + + EXEC sys.sp_executesql + @StringToExecute; + + /* If the table doesn't have the new wait_resource column, add it. See Github #3101. */ + SET @ObjectFullName = + @OutputDatabaseName + + N'.' + + @OutputSchemaName + + N'.' + + @OutputTableName; + + SET @StringToExecute = + N'IF NOT EXISTS (SELECT * FROM ' + + @OutputDatabaseName + + N'.sys.all_columns WHERE object_id = (OBJECT_ID(''' + + @ObjectFullName + + N''')) AND name = ''wait_resource'') + /*Add wait_resource column*/ + ALTER TABLE ' + + @ObjectFullName + + N' ADD wait_resource nvarchar(MAX) NULL;'; + + EXEC sp_executesql + @StringToExecute; + END; + ELSE --if(@r is not null) --if it is null there is no table, create it from above execution + BEGIN SELECT - 1/0 - FROM sys.databases - WHERE name = @OutputDatabaseName - ) --if database is invalid raiserror and set bitcheck - BEGIN - RAISERROR('Database Name for output of table is invalid please correct, Output to Table will not be preformed', 0, 1, @d) WITH NOWAIT; - SET @OutputDatabaseCheck = -1; -- -1 invalid/false, 0 = good/true - END; - ELSE + @StringToExecute = + N'USE ' + + @OutputDatabaseName + + N'; + CREATE TABLE ' + + @OutputSchemaName + + N'.' + + @OutputTableName + + N' ( + ServerName nvarchar(256), + deadlock_type nvarchar(256), + event_date datetime, + database_name nvarchar(256), + spid smallint, + deadlock_group nvarchar(256), + query xml, + object_names xml, + isolation_level nvarchar(256), + owner_mode nvarchar(256), + waiter_mode nvarchar(256), + transaction_count bigint, + login_name nvarchar(256), + host_name nvarchar(256), + client_app nvarchar(256), + wait_time bigint, + wait_resource nvarchar(max), + priority smallint, + log_used bigint, + last_tran_started datetime, + last_batch_started datetime, + last_batch_completed datetime, + transaction_name nvarchar(256), + owner_waiter_type nvarchar(256), + owner_activity nvarchar(256), + owner_waiter_activity nvarchar(256), + owner_merging nvarchar(256), + owner_spilling nvarchar(256), + owner_waiting_to_close nvarchar(256), + waiter_waiter_type nvarchar(256), + waiter_owner_activity nvarchar(256), + waiter_waiter_activity nvarchar(256), + waiter_merging nvarchar(256), + waiter_spilling nvarchar(256), + waiter_waiting_to_close nvarchar(256), + deadlock_graph xml + )', + @StringToExecuteParams = + N'@OutputDatabaseName nvarchar(200), + @OutputSchemaName nvarchar(100), + @OutputTableName nvarchar(200)'; + + EXEC sys.sp_executesql + @StringToExecute, + @StringToExecuteParams, + @OutputDatabaseName, + @OutputSchemaName, + @OutputTableName; + + --table created. + SELECT + @StringToExecute = + N'SELECT @r = name FROM ' + + N'' + + @OutputDatabaseName + + N'' + + N'.sys.objects WHERE type_desc = ''USER_TABLE'' AND name = ''BlitzLockFindings''', + @StringToExecuteParams = + N'@OutputDatabaseName nvarchar(200), + @r nvarchar(200) OUTPUT'; + + EXEC sys.sp_executesql + @StringToExecute, + @StringToExecuteParams, + @OutputDatabaseName, + @r OUTPUT; + + IF (@r IS NULL) --if table does not exist BEGIN - SET @OutputDatabaseCheck = 0; - SELECT + @OutputTableFindings = N'[BlitzLockFindings]', @StringToExecute = - N'SELECT @r = name FROM ' + - N'' + - @OutputDatabaseName + - N'' + - N'.sys.objects WHERE type_desc=''USER_TABLE'' AND name=' + - N'''' + - @OutputTableName + - N'''', + N'USE ' + + @OutputDatabaseName + + N'; + CREATE TABLE ' + + @OutputSchemaName + + N'.' + + @OutputTableFindings + + N' ( + ServerName nvarchar(256), + check_id INT, + database_name nvarchar(256), + object_name nvarchar(1000), + finding_group nvarchar(100), + finding nvarchar(4000) + );', @StringToExecuteParams = N'@OutputDatabaseName nvarchar(200), - @OutputTableName nvarchar(200), - @r nvarchar(200) OUTPUT'; + @OutputSchemaName nvarchar(100), + @OutputTableFindings nvarchar(200)'; EXEC sys.sp_executesql @StringToExecute, @StringToExecuteParams, @OutputDatabaseName, - @OutputTableName, - @r OUTPUT; - - --put covers around all before. - SELECT - @OutputDatabaseName = QUOTENAME(@OutputDatabaseName), - @OutputTableName = QUOTENAME(@OutputTableName), - @OutputSchemaName = QUOTENAME(@OutputSchemaName); - - IF(@r IS NOT NULL) --if it is not null, there is a table, so check for newly added columns - BEGIN - /* If the table doesn't have the new spid column, add it. See Github #3101. */ - SET @ObjectFullName = - @OutputDatabaseName + - N'.' + - @OutputSchemaName + - N'.' + - @OutputTableName; - - SET @StringToExecute = - N'IF NOT EXISTS (SELECT * FROM ' + - @OutputDatabaseName + - N'.sys.all_columns WHERE object_id = (OBJECT_ID(''' + - @ObjectFullName + - N''')) AND name = ''spid'') - /*Add spid column*/ - ALTER TABLE ' + - @ObjectFullName + N' - ADD spid smallint NULL;'; - - EXEC sys.sp_executesql - @StringToExecute; - - /* If the table doesn't have the new wait_resource column, add it. See Github #3101. */ - SET @ObjectFullName = - @OutputDatabaseName + - N'.' + - @OutputSchemaName + - N'.' + - @OutputTableName; - - SET @StringToExecute = - N'IF NOT EXISTS (SELECT * FROM ' + - @OutputDatabaseName + - N'.sys.all_columns WHERE object_id = (OBJECT_ID(''' + - @ObjectFullName + - N''')) AND name = ''wait_resource'') - /*Add wait_resource column*/ - ALTER TABLE ' + - @ObjectFullName + - N' ADD wait_resource nvarchar(MAX) NULL;'; - EXEC(@StringToExecute); - END; - ELSE --if(@r is not null) --if it is null there is no table, create it from above execution - BEGIN - SELECT - @StringToExecute = - N'USE ' + - @OutputDatabaseName + - N'; - CREATE TABLE ' + - @OutputSchemaName + - N'.' + - @OutputTableName + - N' ( - ServerName nvarchar(256), - deadlock_type nvarchar(256), - event_date datetime, - database_name nvarchar(256), - spid SMALLINT, - deadlock_group nvarchar(256), - query xml, - object_names xml, - isolation_level nvarchar(256), - owner_mode nvarchar(256), - waiter_mode nvarchar(256), - transaction_count bigint, - login_name nvarchar(256), - host_name nvarchar(256), - client_app nvarchar(256), - wait_time bigint, - wait_resource nvarchar(max), - priority smallint, - log_used bigint, - last_tran_started datetime, - last_batch_started datetime, - last_batch_completed datetime, - transaction_name nvarchar(256), - owner_waiter_type nvarchar(256), - owner_activity nvarchar(256), - owner_waiter_activity nvarchar(256), - owner_merging nvarchar(256), - owner_spilling nvarchar(256), - owner_waiting_to_close nvarchar(256), - waiter_waiter_type nvarchar(256), - waiter_owner_activity nvarchar(256), - waiter_waiter_activity nvarchar(256), - waiter_merging nvarchar(256), - waiter_spilling nvarchar(256), - waiter_waiting_to_close nvarchar(256), - deadlock_graph xml - )', - @StringToExecuteParams = - N'@OutputDatabaseName nvarchar(200), - @OutputSchemaName nvarchar(100), - @OutputTableName nvarchar(200)'; - - EXEC sys.sp_executesql - @StringToExecute, - @StringToExecuteParams, - @OutputDatabaseName, - @OutputSchemaName, - @OutputTableName; - --table created. - - SELECT - @StringToExecute = - N'SELECT @r = name - FROM ' + - N'' + - @OutputDatabaseName + - N'' + - N'.sys.objects WHERE type_desc = ''USER_TABLE'' AND name = ''BlitzLockFindings''', - @StringToExecuteParams = - N'@OutputDatabaseName nvarchar(200), - @r nvarchar(200) OUTPUT'; - - EXEC sys.sp_executesql - @StringToExecute, - @StringToExecuteParams, - @OutputDatabaseName, - @r OUTPUT; - - IF(@r IS NULL) --if table does not exist - BEGIN - SELECT - @OutputTableFindings = N'[BlitzLockFindings]', - @StringToExecute = - N'USE ' + - @OutputDatabaseName + - '; - CREATE TABLE ' + - @OutputSchemaName + - N'.' + - @OutputTableFindings + - N' ( - ServerName nvarchar(256), - check_id INT, - database_name nvarchar(256), - object_name nvarchar(1000), - finding_group nvarchar(100), - finding nvarchar(4000) - );', - @StringToExecuteParams = - N'@OutputDatabaseName nvarchar(200), - @OutputSchemaName nvarchar(100), - @OutputTableFindings nvarchar(200)'; - - EXEC sys.sp_executesql - @StringToExecute, - @StringToExecuteParams, - @OutputDatabaseName, - @OutputSchemaName, - @OutputTableFindings; - END; - END; - --create synonym for deadlockfindings. - IF EXISTS - ( - SELECT - 1/0 - FROM sys.objects - WHERE name = 'DeadlockFindings' - AND type_desc='SYNONYM' - ) - BEGIN - RAISERROR('Found Synonym', 0, 1) WITH NOWAIT; - DROP SYNONYM DeadlockFindings; - END; - - SET @StringToExecute = - N'CREATE SYNONYM DeadlockFindings FOR ' + - @OutputDatabaseName + - N'.' + - @OutputSchemaName + - N'.' + - @OutputTableFindings; - - EXEC sys.sp_executesql - @StringToExecute; - - --create synonym for deadlock table. - IF EXISTS - ( - SELECT - 1/0 - FROM sys.objects - WHERE name = 'DeadLockTbl' - AND type_desc='SYNONYM' - ) - BEGIN - DROP SYNONYM DeadLockTbl; - END; - - SET @StringToExecute = - N'CREATE SYNONYM DeadLockTbl FOR ' + - @OutputDatabaseName + - N'.' + - @OutputSchemaName + - N'.' + @OutputTableName; - - EXEC sys.sp_executesql - @StringToExecute; + @OutputSchemaName, + @OutputTableFindings; END; - END; - - - CREATE TABLE - #t - ( - id int NOT NULL - ); - - /* WITH ROWCOUNT doesn't work on Amazon RDS - see: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2037 */ - IF @RDS = 0 - BEGIN; - BEGIN TRY; - UPDATE STATISTICS #t WITH ROWCOUNT = 100000000, PAGECOUNT = 100000000; - END TRY - BEGIN CATCH; - /* Misleading error returned, if run without permissions to update statistics the error returned is "Cannot find object". - Catching specific error, and returning message with better info. If any other error is returned, then throw as normal */ - IF (ERROR_NUMBER() = 1088) - BEGIN; - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Cannot run UPDATE STATISTICS on a #temp table without db_owner or sysadmin permissions %s', 0, 1, @d) WITH NOWAIT; - END; - ELSE - BEGIN; - THROW; - END; - END CATCH; - END; + END; - IF @TargetSessionType IS NULL - BEGIN - IF @Azure = 0 - BEGIN - SELECT TOP (1) - @TargetSessionType = - t.target_name - FROM sys.dm_xe_sessions AS s - JOIN sys.dm_xe_session_targets AS t - ON s.address = t.event_session_address - WHERE s.name = @EventSessionName - ORDER BY t.target_name; - END; - - IF @Azure = 1 + --create synonym for deadlockfindings. + IF EXISTS + ( + SELECT + 1/0 + FROM sys.objects + WHERE name = 'DeadlockFindings' + AND type_desc = 'SYNONYM' + ) BEGIN - SELECT TOP (1) - @TargetSessionType = - t.target_name - FROM sys.dm_xe_database_sessions AS s - JOIN sys.dm_xe_database_session_targets AS t - ON s.address = t.event_session_address - WHERE s.name = @EventSessionName - ORDER BY t.target_name; + RAISERROR('Found Synonym', 0, 1) WITH NOWAIT; + DROP SYNONYM DeadlockFindings; END; - END - IF (@TargetSessionType LIKE 'ring%' AND @EventSessionName NOT LIKE 'system_health%') - BEGIN - IF @Azure = 0 - BEGIN - INSERT - #x WITH(TABLOCK) - ( - x - ) - SELECT - x = - TRY_CAST - ( - t.target_data - AS xml - ) - FROM sys.dm_xe_session_targets AS t - JOIN sys.dm_xe_sessions AS s - ON s.address = t.event_session_address - WHERE s.name = @EventSessionName - AND t.target_name = N'ring_buffer'; - END; - - IF @Azure = 1 + SET @StringToExecute = + N'CREATE SYNONYM DeadlockFindings FOR ' + + @OutputDatabaseName + + N'.' + + @OutputSchemaName + + N'.' + + @OutputTableFindings; + + EXEC sys.sp_executesql + @StringToExecute; + + --create synonym for deadlock table. + IF EXISTS + ( + SELECT + 1/0 + FROM sys.objects + WHERE name = 'DeadLockTbl' + AND type_desc = 'SYNONYM' + ) BEGIN - INSERT - #x WITH(TABLOCK) - ( - x - ) - SELECT - x = - TRY_CAST - ( - t.target_data - AS xml - ) - FROM sys.dm_xe_database_session_targets AS t - JOIN sys.dm_xe_database_sessions AS s - ON s.address = t.event_session_address - WHERE s.name = @EventSessionName - AND t.target_name = N'ring_buffer'; + DROP SYNONYM DeadLockTbl; END; + + SET @StringToExecute = + N'CREATE SYNONYM DeadLockTbl FOR ' + + @OutputDatabaseName + + N'.' + + @OutputSchemaName + + N'.' + + @OutputTableName; + + EXEC sys.sp_executesql + @StringToExecute; END; - - IF (@TargetSessionType LIKE 'event%' AND @EventSessionName NOT LIKE 'system_health%') - BEGIN - IF @Azure = 0 - BEGIN - SELECT - @SessionId = t.event_session_id, - @TargetSessionId = t.target_id - FROM sys.server_event_session_targets t - JOIN sys.server_event_sessions s - ON s.event_session_id = t.event_session_id - WHERE t.name = @TargetSessionType - AND s.name = @EventSessionName; - - SELECT - @FileName = - CASE - WHEN f.file_name LIKE '%.xel' - THEN REPLACE(f.file_name, N'.xel', N'*.xel') - ELSE f.file_name + N'*.xel' - END - FROM - ( - SELECT - file_name = - CONVERT - ( - nvarchar(4000), - f.value - ) - FROM sys.server_event_session_fields AS f - WHERE f.event_session_id = @SessionId - AND f.object_id = @TargetSessionId - AND f.name = N'filename' - ) AS f; + END; + + + CREATE TABLE + #t + ( + id int NOT NULL + ); + + + /* WITH ROWCOUNT doesn't work on Amazon RDS - see: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2037 */ + IF @RDS = 0 + BEGIN; + BEGIN TRY; + UPDATE STATISTICS #t + WITH + ROWCOUNT = 100000000, + PAGECOUNT = 100000000; + END TRY + BEGIN CATCH; + /* Misleading error returned, if run without permissions to update statistics the error returned is "Cannot find object". + Catching specific error, and returning message with better info. If any other error is returned, then throw as normal */ + IF (ERROR_NUMBER() = 1088) + BEGIN; + SET @d = CONVERT(varchar(40), GETDATE(), 109); + + + RAISERROR('Cannot run UPDATE STATISTICS on a #temp table without db_owner or sysadmin permissions %s', 0, 1, @d) WITH NOWAIT; END; - - IF @Azure = 1 - BEGIN - SELECT - @SessionId = t.event_session_id, - @TargetSessionId = t.target_id - FROM sys.dm_xe_database_session_targets t - JOIN sys.dm_xe_database_sessions s - ON s.event_session_id = t.event_session_id - WHERE t.name = @TargetSessionType - AND s.name = @EventSessionName; - - SELECT - @FileName = - CASE - WHEN f.file_name LIKE '%.xel' - THEN REPLACE(f.file_name, N'.xel', N'*.xel') - ELSE f.file_name + N'*.xel' - END - FROM - ( - SELECT - file_name = - CONVERT - ( - nvarchar(4000), - f.value - ) - FROM sys.server_event_session_fields AS f - WHERE f.event_session_id = @SessionId - AND f.object_id = @TargetSessionId - AND f.name = N'filename' - ) AS f; + ELSE + BEGIN; + THROW; END; - + END CATCH; + END; + + + IF @TargetSessionType IS NULL + BEGIN + IF @Azure = 0 + BEGIN + SELECT TOP (1) + @TargetSessionType = t.target_name + FROM sys.dm_xe_sessions AS s + JOIN sys.dm_xe_session_targets AS t + ON s.address = t.event_session_address + WHERE s.name = @EventSessionName + ORDER BY t.target_name; + END; + + IF @Azure = 1 + BEGIN + SELECT TOP (1) + @TargetSessionType = t.target_name + FROM sys.dm_xe_database_sessions AS s + JOIN sys.dm_xe_database_session_targets AS t + ON s.address = t.event_session_address + WHERE s.name = @EventSessionName + ORDER BY t.target_name; + END; + END; + + + IF + ( + @TargetSessionType LIKE 'ring%' + AND @EventSessionName NOT LIKE 'system_health%' + ) + BEGIN + IF @Azure = 0 + BEGIN INSERT - #x WITH(TABLOCK) + #x WITH (TABLOCK) ( x - ) + ) SELECT - x = - TRY_CAST - ( - f.event_data - AS xml - ) - FROM sys.fn_xe_file_target_read_file - ( - @FileName, - NULL, - NULL, - NULL - ) AS f; + x = TRY_CAST(t.target_data AS xml) + FROM sys.dm_xe_session_targets AS t + JOIN sys.dm_xe_sessions AS s + ON s.address = t.event_session_address + WHERE s.name = @EventSessionName + AND t.target_name = N'ring_buffer'; END; - - - IF (@TargetSessionType LIKE 'ring%' AND @EventSessionName NOT LIKE 'system_health%') + + + IF @Azure = 1 BEGIN INSERT - #deadlock_data + #x WITH (TABLOCK) ( - deadlock_xml + x ) - SELECT - deadlock_xml = - e.x.query('.') - FROM #x AS x - CROSS APPLY x.x.nodes('/RingBufferTarget/event') AS e(x); + SELECT + x = TRY_CAST(t.target_data AS xml) + FROM sys.dm_xe_database_session_targets AS t + JOIN sys.dm_xe_database_sessions AS s + ON s.address = t.event_session_address + WHERE s.name = @EventSessionName + AND t.target_name = N'ring_buffer'; END; - - IF (@TargetSessionType LIKE 'event_file%' AND @EventSessionName NOT LIKE 'system_health%') + END; + + + IF + ( + @TargetSessionType LIKE 'event%' + AND @EventSessionName NOT LIKE 'system_health%' + ) + BEGIN + IF @Azure = 0 BEGIN - INSERT - #deadlock_data + SELECT + @SessionId = t.event_session_id, + @TargetSessionId = t.target_id + FROM sys.server_event_session_targets AS t + JOIN sys.server_event_sessions AS s + ON s.event_session_id = t.event_session_id + WHERE t.name = @TargetSessionType + AND s.name = @EventSessionName; + + + SELECT + @FileName = + CASE + WHEN f.file_name LIKE '%.xel' + THEN REPLACE(f.file_name, N'.xel', N'*.xel') + ELSE f.file_name + N'*.xel' + END + FROM ( - deadlock_xml - ) - SELECT - deadlock_xml = - e.x.query('.') - FROM #x AS x - CROSS APPLY x.x.nodes('/event') AS e(x); + SELECT + file_name = CONVERT(nvarchar(4000), f.value) + FROM sys.server_event_session_fields AS f + WHERE f.event_session_id = @SessionId + AND f.object_id = @TargetSessionId + AND f.name = N'filename' + ) AS f; END; - - IF @Debug = 1 + + + IF @Azure = 1 BEGIN - SELECT table_name = N'#deadlock_data', bx.* FROM #deadlock_data AS bx; + SELECT + @SessionId = t.event_session_id, + @TargetSessionId = t.target_id + FROM sys.dm_xe_database_session_targets AS t + JOIN sys.dm_xe_database_sessions AS s + ON s.event_session_id = t.event_session_id + WHERE t.name = @TargetSessionType + AND s.name = @EventSessionName; + + + SELECT + @FileName = + CASE + WHEN f.file_name LIKE '%.xel' + THEN REPLACE(f.file_name, N'.xel', N'*.xel') + ELSE f.file_name + N'*.xel' + END + FROM + ( + SELECT + file_name = CONVERT(nvarchar(4000), f.value) + FROM sys.server_event_session_fields AS f + WHERE f.event_session_id = @SessionId + AND f.object_id = @TargetSessionId + AND f.name = N'filename' + ) AS f; END; - /*Grab the initial set of xml to parse*/ - IF (@TargetSessionType LIKE 'event%' AND @EventSessionName LIKE 'system_health%') - BEGIN - SET @d = CONVERT(varchar(40), GETDATE(), 109); + + INSERT + #x WITH (TABLOCK) + ( + x + ) + SELECT + x = TRY_CAST(f.event_data AS xml) + FROM sys.fn_xe_file_target_read_file(@FileName, NULL, NULL, NULL) AS f; + END; + + + IF + ( + @TargetSessionType LIKE 'ring%' + AND @EventSessionName NOT LIKE 'system_health%' + ) + BEGIN + INSERT + #deadlock_data WITH(TABLOCK) + ( + deadlock_xml + ) + SELECT + deadlock_xml = e.x.query('.') + FROM #x AS x + CROSS APPLY x.x.nodes('/RingBufferTarget/event') AS e(x); + END; + + + IF + ( + @TargetSessionType LIKE 'event_file%' + AND @EventSessionName NOT LIKE 'system_health%' + ) + BEGIN + INSERT + #deadlock_data WITH(TABLOCK) + ( + deadlock_xml + ) + SELECT + deadlock_xml = e.x.query('.') + FROM #x AS x + CROSS APPLY x.x.nodes('/event') AS e(x); + END; + + + IF @Debug = 1 + BEGIN + SELECT table_name = N'#deadlock_data', bx.* FROM #deadlock_data AS bx; + END; + + + /*Grab the initial set of xml to parse*/ + IF + ( + @TargetSessionType LIKE 'event%' + AND @EventSessionName LIKE 'system_health%' + ) + BEGIN + SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Grab the initial set of xml to parse at %s', 0, 1, @d) WITH NOWAIT; - WITH - xml AS - ( - SELECT - deadlock_xml = - TRY_CAST(event_data AS xml) - FROM sys.fn_xe_file_target_read_file - ( - 'system_health*.xel', - NULL, - NULL, - NULL - ) - ) - INSERT - #deadlock_data WITH(TABLOCK) - SELECT - deadlock_xml = - xml.deadlock_xml, - event_time = - xml.deadlock_xml.value('(/event/@timestamp)[1]', 'datetime') + WITH + xml AS + ( + SELECT + deadlock_xml = TRY_CAST(event_data AS xml) + FROM sys.fn_xe_file_target_read_file('system_health*.xel', NULL, NULL, NULL) + ) + INSERT + #deadlock_data WITH (TABLOCK) + SELECT + deadlock_xml = xml.deadlock_xml, + event_date = xml.deadlock_xml.value('(/event/@timestamp)[1]', 'datetime') FROM xml LEFT JOIN #t AS t - ON 1 = 0 + ON 1 = 1 CROSS APPLY xml.deadlock_xml.nodes('/event/@name') AS x(c) WHERE x.c.exist('/event/@name[ .= "xml_deadlock_report"]') = 1 - AND xml.deadlock_xml.value('(/event/@timestamp)[1]', 'datetime') >= DATEADD(MINUTE, DATEDIFF(MINUTE, GETUTCDATE(), SYSDATETIME()), @StartDate) - AND xml.deadlock_xml.value('(/event/@timestamp)[1]', 'datetime') < DATEADD(MINUTE, DATEDIFF(MINUTE, GETUTCDATE(), SYSDATETIME()), @EndDate) + AND xml.deadlock_xml.value('(/event/@timestamp)[1]', 'datetime') >= DATEADD(MINUTE, DATEDIFF(MINUTE, GETUTCDATE(), SYSDATETIME()), @StartDate) + AND xml.deadlock_xml.value('(/event/@timestamp)[1]', 'datetime') < DATEADD(MINUTE, DATEDIFF(MINUTE, GETUTCDATE(), SYSDATETIME()), @EndDate) OPTION (RECOMPILE); - SET @DeadlockCount = @@ROWCOUNT; + SET @DeadlockCount = @@ROWCOUNT; /*Optimization: if we got back more rows than @Top, remove them. This seems to be better than using @Top in the query above as that results in excessive memory grant*/ - IF(@Top < @DeadlockCount) - BEGIN + IF (@Top < @DeadlockCount) + BEGIN WITH - t AS - ( + t AS + ( SELECT TOP (@DeadlockCount - @Top) - dd.* + dd.* FROM #deadlock_data AS dd - ORDER BY dd.event_time ASC - ) - DELETE FROM t; + ORDER BY dd.event_date ASC + ) + DELETE + FROM t; END; + /*Parse process and input buffer xml*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Parse process and input buffer xml %s', 0, 1, @d) WITH NOWAIT; - SELECT q.event_date, - q.victim_id, - CONVERT(bit, q.is_parallel) AS is_parallel, - q.deadlock_graph, - q.id, - q.spid, - q.database_id, - q.priority, - q.log_used, - q.wait_resource, - q.wait_time, - q.transaction_name, - q.last_tran_started, - q.last_batch_started, - q.last_batch_completed, - q.lock_mode, - q.transaction_count, - q.client_app, - q.host_name, - q.login_name, - q.isolation_level, - q.process_xml, - ISNULL(ca2.ib.query('.'), '') AS input_buffer - INTO #deadlock_process - FROM ( SELECT dd.deadlock_xml, - CONVERT(datetime2(7), SWITCHOFFSET(CONVERT(datetimeoffset, dd.event_date ), DATENAME(TZOFFSET, SYSDATETIMEOFFSET()))) AS event_date, - dd.victim_id, - CONVERT(tinyint, dd.is_parallel) + CONVERT(tinyint, dd.is_parallel_batch) AS is_parallel, - dd.deadlock_graph, - ca.dp.value('@id', 'nvarchar(256)') AS id, - ca.dp.value('@spid', 'SMALLINT') AS spid, - ca.dp.value('@currentdb', 'bigint') AS database_id, - ca.dp.value('@priority', 'SMALLINT') AS priority, - ca.dp.value('@logused', 'bigint') AS log_used, - ca.dp.value('@waitresource', 'nvarchar(256)') AS wait_resource, - ca.dp.value('@waittime', 'bigint') AS wait_time, - ca.dp.value('@transactionname', 'nvarchar(256)') AS transaction_name, - ca.dp.value('@lasttranstarted', 'DATETIME2(7)') AS last_tran_started, - ca.dp.value('@lastbatchstarted', 'DATETIME2(7)') AS last_batch_started, - ca.dp.value('@lastbatchcompleted', 'DATETIME2(7)') AS last_batch_completed, - ca.dp.value('@lockMode', 'nvarchar(256)') AS lock_mode, - ca.dp.value('@trancount', 'bigint') AS transaction_count, - ca.dp.value('@clientapp', 'nvarchar(256)') AS client_app, - ca.dp.value('@hostname', 'nvarchar(256)') AS host_name, - ca.dp.value('@loginname', 'nvarchar(256)') AS login_name, - ca.dp.value('@isolationlevel', 'nvarchar(256)') AS isolation_level, - ISNULL(ca.dp.query('.'), '') AS process_xml - FROM ( SELECT d1.deadlock_xml, - d1.deadlock_xml.value('(event/@timestamp)[1]', 'DATETIME2') AS event_date, - d1.deadlock_xml.value('(//deadlock/victim-list/victimProcess/@id)[1]', 'nvarchar(256)') AS victim_id, - d1.deadlock_xml.exist('//deadlock/resource-list/exchangeEvent') AS is_parallel, - d1.deadlock_xml.exist('//deadlock/resource-list/SyncPoint') AS is_parallel_batch, - d1.deadlock_xml.query('/event/data/value/deadlock') AS deadlock_graph - FROM #deadlock_data AS d1 ) AS dd - CROSS APPLY dd.deadlock_xml.nodes('//deadlock/process-list/process') AS ca(dp) - WHERE (ca.dp.value('@currentdb', 'bigint') = DB_ID(@DatabaseName) OR @DatabaseName IS NULL) - AND (ca.dp.value('@clientapp', 'nvarchar(256)') = @AppName OR @AppName IS NULL) - AND (ca.dp.value('@hostname', 'nvarchar(256)') = @HostName OR @HostName IS NULL) - AND (ca.dp.value('@loginname', 'nvarchar(256)') = @LoginName OR @LoginName IS NULL) + + SELECT + q.event_date, + q.victim_id, + is_parallel = + CONVERT(bit, q.is_parallel), + q.deadlock_graph, + q.id, + q.spid, + q.database_id, + q.priority, + q.log_used, + q.wait_resource, + q.wait_time, + q.transaction_name, + q.last_tran_started, + q.last_batch_started, + q.last_batch_completed, + q.lock_mode, + q.transaction_count, + q.client_app, + q.host_name, + q.login_name, + q.isolation_level, + q.process_xml, + input_buffer = + ISNULL(ca2.ib.query('.'), '') + INTO #deadlock_process + FROM + ( + SELECT + dd.deadlock_xml, + event_date = + DATEADD + ( + MINUTE, + DATEDIFF(MINUTE, GETUTCDATE(), SYSDATETIME()), + c.value('@timestamp', 'datetime') + ), + dd.victim_id, + is_parallel = + CONVERT(tinyint, dd.is_parallel) + + CONVERT(tinyint, dd.is_parallel_batch), + dd.deadlock_graph, + id = ca.dp.value('@id', 'nvarchar(256)'), + spid = ca.dp.value('@spid', 'smallint'), + database_id = ca.dp.value('@currentdb', 'bigint'), + priority = ca.dp.value('@priority', 'smallint'), + log_used = ca.dp.value('@logused', 'bigint'), + wait_resource = ca.dp.value('@waitresource', 'nvarchar(256)'), + wait_time = ca.dp.value('@waittime', 'bigint'), + transaction_name = ca.dp.value('@transactionname', 'nvarchar(256)'), + last_tran_started = ca.dp.value('@lasttranstarted', 'datetime'), + last_batch_started = ca.dp.value('@lastbatchstarted', 'datetime'), + last_batch_completed = ca.dp.value('@lastbatchcompleted', 'datetime'), + lock_mode = ca.dp.value('@lockMode', 'nvarchar(256)'), + transaction_count = ca.dp.value('@trancount', 'bigint'), + client_app = ca.dp.value('@clientapp', 'nvarchar(256)'), + host_name = ca.dp.value('@hostname', 'nvarchar(256)'), + login_name = ca.dp.value('@loginname', 'nvarchar(256)'), + isolation_level = ca.dp.value('@isolationlevel', 'nvarchar(256)'), + process_xml = ISNULL(ca.dp.query('.'), '') + FROM + ( + SELECT + d1.deadlock_xml, + event_date = d1.deadlock_xml.value('(event/@timestamp)[1]', 'datetime2'), + victim_id = d1.deadlock_xml.value('(//deadlock/victim-list/victimProcess/@id)[1]', 'nvarchar(256)'), + is_parallel = d1.deadlock_xml.exist('//deadlock/resource-list/exchangeEvent'), + is_parallel_batch = d1.deadlock_xml.exist('//deadlock/resource-list/SyncPoint'), + deadlock_graph = d1.deadlock_xml.query('/event/data/value/deadlock') + FROM #deadlock_data AS d1 + ) AS dd + CROSS APPLY dd.deadlock_xml.nodes('//deadlock/process-list/process') AS ca(dp) + WHERE (ca.dp.value('@currentdb', 'bigint') = DB_ID(@DatabaseName) OR @DatabaseName IS NULL) + AND (ca.dp.value('@clientapp', 'nvarchar(256)') = @AppName OR @AppName IS NULL) + AND (ca.dp.value('@hostname', 'nvarchar(256)') = @HostName OR @HostName IS NULL) + AND (ca.dp.value('@loginname', 'nvarchar(256)') = @LoginName OR @LoginName IS NULL) ) AS q CROSS APPLY q.deadlock_xml.nodes('//deadlock/process-list/process/inputbuf') AS ca2(ib) OPTION (RECOMPILE); @@ -788,64 +835,75 @@ IF @Help = 1 /*Parse execution stack xml*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Parse execution stack xml %s', 0, 1, @d) WITH NOWAIT; - SELECT DISTINCT - dp.id, - dp.event_date, - ca.dp.value('@procname', 'nvarchar(1000)') AS proc_name, - ca.dp.value('@sqlhandle', 'nvarchar(128)') AS sql_handle - INTO #deadlock_stack - FROM #deadlock_process AS dp + + SELECT DISTINCT + dp.id, + dp.event_date, + proc_name = ca.dp.value('@procname', 'nvarchar(1000)'), + sql_handle = ca.dp.value('@sqlhandle', 'nvarchar(128)') + INTO #deadlock_stack + FROM #deadlock_process AS dp CROSS APPLY dp.process_xml.nodes('//executionStack/frame') AS ca(dp) WHERE (ca.dp.value('@procname', 'nvarchar(256)') = @StoredProcName OR @StoredProcName IS NULL) OPTION (RECOMPILE); - - /*Grab the full resource list*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Grab the full resource list %s', 0, 1, @d) WITH NOWAIT; - SELECT - CONVERT(datetime2(7), SWITCHOFFSET(CONVERT(datetimeoffset, dr.event_date), DATENAME(TZOFFSET, SYSDATETIMEOFFSET()))) AS event_date, - dr.victim_id, - dr.resource_xml - INTO #deadlock_resource - FROM + + SELECT + event_date = + DATEADD + ( + MINUTE, + DATEDIFF(MINUTE, GETUTCDATE(), SYSDATETIME()), + c.value('@timestamp', 'datetime') + ), + dr.victim_id, + dr.resource_xml + INTO + #deadlock_resource + FROM ( - SELECT dd.deadlock_xml.value('(event/@timestamp)[1]', 'DATETIME2') AS event_date, - dd.deadlock_xml.value('(//deadlock/victim-list/victimProcess/@id)[1]', 'nvarchar(256)') AS victim_id, - ISNULL(ca.dp.query('.'), '') AS resource_xml - FROM #deadlock_data AS dd - CROSS APPLY dd.deadlock_xml.nodes('//deadlock/resource-list') AS ca(dp) + SELECT + event_date = dd.deadlock_xml.value('(event/@timestamp)[1]', 'datetime2'), + victim_id = dd.deadlock_xml.value('(//deadlock/victim-list/victimProcess/@id)[1]', 'nvarchar(256)'), + resource_xml = ISNULL(ca.dp.query('.'), '') + FROM #deadlock_data AS dd + CROSS APPLY dd.deadlock_xml.nodes('//deadlock/resource-list') AS ca(dp) ) AS dr OPTION (RECOMPILE); - /*Parse object locks*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Parse object locks %s', 0, 1, @d) WITH NOWAIT; - SELECT DISTINCT - ca.event_date, - ca.database_id, - ca.object_name, - ca.lock_mode, - ca.index_name, - ca.associatedObjectId, - w.l.value('@id', 'nvarchar(256)') AS waiter_id, - w.l.value('@mode', 'nvarchar(256)') AS waiter_mode, - o.l.value('@id', 'nvarchar(256)') AS owner_id, - o.l.value('@mode', 'nvarchar(256)') AS owner_mode, - N'OBJECT' AS lock_type - INTO #deadlock_owner_waiter - FROM ( - SELECT dr.event_date, - ca.dr.value('@dbid', 'bigint') AS database_id, - ca.dr.value('@objectname', 'nvarchar(256)') AS object_name, - ca.dr.value('@mode', 'nvarchar(256)') AS lock_mode, - ca.dr.value('@indexname', 'nvarchar(256)') AS index_name, - ca.dr.value('@associatedObjectId', 'bigint') AS associatedObjectId, - ca.dr.query('.') AS dr - FROM #deadlock_resource AS dr - CROSS APPLY dr.resource_xml.nodes('//resource-list/objectlock') AS ca(dr) + + SELECT DISTINCT + ca.event_date, + ca.database_id, + ca.object_name, + ca.lock_mode, + ca.index_name, + ca.associatedObjectId, + waiter_id = w.l.value('@id', 'nvarchar(256)'), + waiter_mode = w.l.value('@mode', 'nvarchar(256)'), + owner_id = o.l.value('@id', 'nvarchar(256)'), + owner_mode = o.l.value('@mode', 'nvarchar(256)'), + lock_type = N'OBJECT' + INTO #deadlock_owner_waiter + FROM + ( + SELECT + dr.event_date, + database_id = ca.dr.value('@dbid', 'bigint'), + object_name = ca.dr.value('@objectname', 'nvarchar(256)'), + lock_mode = ca.dr.value('@mode', 'nvarchar(256)'), + index_name = ca.dr.value('@indexname', 'nvarchar(256)'), + associatedObjectId = ca.dr.value('@associatedObjectId', 'bigint'), + dr = ca.dr.query('.') + FROM #deadlock_resource AS dr + CROSS APPLY dr.resource_xml.nodes('//resource-list/objectlock') AS ca(dr) ) AS ca CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l) CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l) @@ -853,33 +911,36 @@ IF @Help = 1 OPTION (RECOMPILE); - /*Parse page locks*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Parse page locks %s', 0, 1, @d) WITH NOWAIT; - INSERT #deadlock_owner_waiter WITH(TABLOCKX) - SELECT DISTINCT - ca.event_date, - ca.database_id, - ca.object_name, - ca.lock_mode, - ca.index_name, - ca.associatedObjectId, - w.l.value('@id', 'nvarchar(256)') AS waiter_id, - w.l.value('@mode', 'nvarchar(256)') AS waiter_mode, - o.l.value('@id', 'nvarchar(256)') AS owner_id, - o.l.value('@mode', 'nvarchar(256)') AS owner_mode, - N'PAGE' AS lock_type - FROM ( - SELECT dr.event_date, - ca.dr.value('@dbid', 'bigint') AS database_id, - ca.dr.value('@objectname', 'nvarchar(256)') AS object_name, - ca.dr.value('@mode', 'nvarchar(256)') AS lock_mode, - ca.dr.value('@indexname', 'nvarchar(256)') AS index_name, - ca.dr.value('@associatedObjectId', 'bigint') AS associatedObjectId, - ca.dr.query('.') AS dr - FROM #deadlock_resource AS dr - CROSS APPLY dr.resource_xml.nodes('//resource-list/pagelock') AS ca(dr) + + INSERT + #deadlock_owner_waiter WITH (TABLOCKX) + SELECT DISTINCT + ca.event_date, + ca.database_id, + ca.object_name, + ca.lock_mode, + ca.index_name, + ca.associatedObjectId, + waiter_id = w.l.value('@id', 'nvarchar(256)'), + waiter_mode = w.l.value('@mode', 'nvarchar(256)'), + owner_id = o.l.value('@id', 'nvarchar(256)'), + owner_mode = o.l.value('@mode', 'nvarchar(256)'), + lock_type = N'PAGE' + FROM + ( + SELECT + dr.event_date, + database_id = ca.dr.value('@dbid', 'bigint'), + object_name = ca.dr.value('@objectname', 'nvarchar(256)'), + lock_mode = ca.dr.value('@mode', 'nvarchar(256)'), + index_name = ca.dr.value('@indexname', 'nvarchar(256)'), + associatedObjectId = ca.dr.value('@associatedObjectId', 'bigint'), + dr = ca.dr.query('.') + FROM #deadlock_resource AS dr + CROSS APPLY dr.resource_xml.nodes('//resource-list/pagelock') AS ca(dr) ) AS ca CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l) CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l) @@ -888,30 +949,35 @@ IF @Help = 1 /*Parse key locks*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); + + RAISERROR('Parse key locks %s', 0, 1, @d) WITH NOWAIT; - INSERT #deadlock_owner_waiter WITH(TABLOCKX) - SELECT DISTINCT - ca.event_date, - ca.database_id, - ca.object_name, - ca.lock_mode, - ca.index_name, - ca.associatedObjectId, - w.l.value('@id', 'nvarchar(256)') AS waiter_id, - w.l.value('@mode', 'nvarchar(256)') AS waiter_mode, - o.l.value('@id', 'nvarchar(256)') AS owner_id, - o.l.value('@mode', 'nvarchar(256)') AS owner_mode, - N'KEY' AS lock_type - FROM ( - SELECT dr.event_date, - ca.dr.value('@dbid', 'bigint') AS database_id, - ca.dr.value('@objectname', 'nvarchar(256)') AS object_name, - ca.dr.value('@mode', 'nvarchar(256)') AS lock_mode, - ca.dr.value('@indexname', 'nvarchar(256)') AS index_name, - ca.dr.value('@associatedObjectId', 'bigint') AS associatedObjectId, - ca.dr.query('.') AS dr - FROM #deadlock_resource AS dr - CROSS APPLY dr.resource_xml.nodes('//resource-list/keylock') AS ca(dr) + INSERT + #deadlock_owner_waiter WITH (TABLOCKX) + SELECT DISTINCT + ca.event_date, + ca.database_id, + ca.object_name, + ca.lock_mode, + ca.index_name, + ca.associatedObjectId, + waiter_id = w.l.value('@id', 'nvarchar(256)'), + waiter_mode = w.l.value('@mode', 'nvarchar(256)'), + owner_id = o.l.value('@id', 'nvarchar(256)'), + owner_mode = o.l.value('@mode', 'nvarchar(256)'), + lock_type = N'KEY' + FROM + ( + SELECT + dr.event_date, + database_id = ca.dr.value('@dbid', 'bigint'), + object_name = ca.dr.value('@objectname', 'nvarchar(256)'), + lock_mode = ca.dr.value('@mode', 'nvarchar(256)'), + index_name = ca.dr.value('@indexname', 'nvarchar(256)'), + associatedObjectId = ca.dr.value('@associatedObjectId', 'bigint'), + dr = ca.dr.query('.') + FROM #deadlock_resource AS dr + CROSS APPLY dr.resource_xml.nodes('//resource-list/keylock') AS ca(dr) ) AS ca CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l) CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l) @@ -921,29 +987,34 @@ IF @Help = 1 /*Parse RID locks*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Parse RID locks %s', 0, 1, @d) WITH NOWAIT; - INSERT #deadlock_owner_waiter WITH(TABLOCKX) - SELECT DISTINCT - ca.event_date, - ca.database_id, - ca.object_name, - ca.lock_mode, - ca.index_name, - ca.associatedObjectId, - w.l.value('@id', 'nvarchar(256)') AS waiter_id, - w.l.value('@mode', 'nvarchar(256)') AS waiter_mode, - o.l.value('@id', 'nvarchar(256)') AS owner_id, - o.l.value('@mode', 'nvarchar(256)') AS owner_mode, - N'RID' AS lock_type - FROM ( - SELECT dr.event_date, - ca.dr.value('@dbid', 'bigint') AS database_id, - ca.dr.value('@objectname', 'nvarchar(256)') AS object_name, - ca.dr.value('@mode', 'nvarchar(256)') AS lock_mode, - ca.dr.value('@indexname', 'nvarchar(256)') AS index_name, - ca.dr.value('@associatedObjectId', 'bigint') AS associatedObjectId, - ca.dr.query('.') AS dr - FROM #deadlock_resource AS dr - CROSS APPLY dr.resource_xml.nodes('//resource-list/ridlock') AS ca(dr) + + + INSERT + #deadlock_owner_waiter WITH (TABLOCKX) + SELECT DISTINCT + ca.event_date, + ca.database_id, + ca.object_name, + ca.lock_mode, + ca.index_name, + ca.associatedObjectId, + waiter_id = w.l.value('@id', 'nvarchar(256)'), + waiter_mode = w.l.value('@mode', 'nvarchar(256)'), + owner_id = o.l.value('@id', 'nvarchar(256)'), + owner_mode = o.l.value('@mode', 'nvarchar(256)'), + lock_type = N'RID' + FROM + ( + SELECT + dr.event_date, + database_id = ca.dr.value('@dbid', 'bigint'), + object_name = ca.dr.value('@objectname', 'nvarchar(256)'), + lock_mode = ca.dr.value('@mode', 'nvarchar(256)'), + index_name = ca.dr.value('@indexname', 'nvarchar(256)'), + associatedObjectId = ca.dr.value('@associatedObjectId', 'bigint'), + dr = ca.dr.query('.') + FROM #deadlock_resource AS dr + CROSS APPLY dr.resource_xml.nodes('//resource-list/ridlock') AS ca(dr) ) AS ca CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l) CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l) @@ -953,90 +1024,117 @@ IF @Help = 1 /*Parse row group locks*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Parse row group locks %s', 0, 1, @d) WITH NOWAIT; - INSERT #deadlock_owner_waiter WITH(TABLOCKX) - SELECT DISTINCT - ca.event_date, - ca.database_id, - ca.object_name, - ca.lock_mode, - ca.index_name, - ca.associatedObjectId, - w.l.value('@id', 'nvarchar(256)') AS waiter_id, - w.l.value('@mode', 'nvarchar(256)') AS waiter_mode, - o.l.value('@id', 'nvarchar(256)') AS owner_id, - o.l.value('@mode', 'nvarchar(256)') AS owner_mode, - N'ROWGROUP' AS lock_type - FROM ( - SELECT dr.event_date, - ca.dr.value('@dbid', 'bigint') AS database_id, - ca.dr.value('@objectname', 'nvarchar(256)') AS object_name, - ca.dr.value('@mode', 'nvarchar(256)') AS lock_mode, - ca.dr.value('@indexname', 'nvarchar(256)') AS index_name, - ca.dr.value('@associatedObjectId', 'bigint') AS associatedObjectId, - ca.dr.query('.') AS dr - FROM #deadlock_resource AS dr - CROSS APPLY dr.resource_xml.nodes('//resource-list/rowgrouplock') AS ca(dr) + + INSERT + #deadlock_owner_waiter WITH (TABLOCKX) + SELECT DISTINCT + ca.event_date, + ca.database_id, + ca.object_name, + ca.lock_mode, + ca.index_name, + ca.associatedObjectId, + waiter_id = w.l.value('@id', 'nvarchar(256)'), + waiter_mode = w.l.value('@mode', 'nvarchar(256)'), + owner_id = o.l.value('@id', 'nvarchar(256)'), + owner_mode = o.l.value('@mode', 'nvarchar(256)'), + lock_type = N'ROWGROUP' + FROM + ( + SELECT + dr.event_date, + database_id = ca.dr.value('@dbid', 'bigint'), + object_name = ca.dr.value('@objectname', 'nvarchar(256)'), + lock_mode = ca.dr.value('@mode', 'nvarchar(256)'), + index_name = ca.dr.value('@indexname', 'nvarchar(256)'), + associatedObjectId = ca.dr.value('@associatedObjectId', 'bigint'), + dr = ca.dr.query('.') + FROM #deadlock_resource AS dr + CROSS APPLY dr.resource_xml.nodes('//resource-list/rowgrouplock') AS ca(dr) ) AS ca CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l) CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l) OPTION (RECOMPILE); - UPDATE d - SET d.index_name = d.object_name - + '.HEAP' + + UPDATE + d + SET + d.index_name = + d.object_name + '.HEAP' FROM #deadlock_owner_waiter AS d - WHERE d.lock_type IN (N'HEAP', N'RID') - OPTION(RECOMPILE); + WHERE d.lock_type IN + ( + N'HEAP', + N'RID' + ) + OPTION (RECOMPILE); + /*Parse parallel deadlocks*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Parse parallel deadlocks %s', 0, 1, @d) WITH NOWAIT; - SELECT DISTINCT - ca.id, - ca.event_date, - ca.wait_type, - ca.node_id, - ca.waiter_type, - ca.owner_activity, - ca.waiter_activity, - ca.merging, - ca.spilling, - ca.waiting_to_close, - w.l.value('@id', 'nvarchar(256)') AS waiter_id, - o.l.value('@id', 'nvarchar(256)') AS owner_id + + SELECT DISTINCT + ca.id, + ca.event_date, + ca.wait_type, + ca.node_id, + ca.waiter_type, + ca.owner_activity, + ca.waiter_activity, + ca.merging, + ca.spilling, + ca.waiting_to_close, + waiter_id = w.l.value('@id', 'nvarchar(256)'), + owner_id = o.l.value('@id', 'nvarchar(256)') INTO #deadlock_resource_parallel - FROM ( - SELECT dr.event_date, - ca.dr.value('@id', 'nvarchar(256)') AS id, - ca.dr.value('@WaitType', 'nvarchar(256)') AS wait_type, - ca.dr.value('@nodeId', 'bigint') AS node_id, - /* These columns are in 2017 CU5 ONLY */ - ca.dr.value('@waiterType', 'nvarchar(256)') AS waiter_type, - ca.dr.value('@ownerActivity', 'nvarchar(256)') AS owner_activity, - ca.dr.value('@waiterActivity', 'nvarchar(256)') AS waiter_activity, - ca.dr.value('@merging', 'nvarchar(256)') AS merging, - ca.dr.value('@spilling', 'nvarchar(256)') AS spilling, - ca.dr.value('@waitingToClose', 'nvarchar(256)') AS waiting_to_close, - /* */ - ca.dr.query('.') AS dr - FROM #deadlock_resource AS dr - CROSS APPLY dr.resource_xml.nodes('//resource-list/exchangeEvent') AS ca(dr) - ) AS ca - CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l) - CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l) - OPTION (RECOMPILE); + FROM + ( + SELECT + dr.event_date, + id = ca.dr.value('@id', 'nvarchar(256)'), + wait_type = ca.dr.value('@WaitType', 'nvarchar(256)'), + node_id = ca.dr.value('@nodeId', 'bigint'), + /* These columns are in 2017 CU5+ ONLY */ + waiter_type = ca.dr.value('@waiterType', 'nvarchar(256)'), + owner_activity = ca.dr.value('@ownerActivity', 'nvarchar(256)'), + waiter_activity = ca.dr.value('@waiterActivity', 'nvarchar(256)'), + merging = ca.dr.value('@merging', 'nvarchar(256)'), + spilling = ca.dr.value('@spilling', 'nvarchar(256)'), + waiting_to_close = ca.dr.value('@waitingToClose', 'nvarchar(256)'), + /* These columns are in 2017 CU5+ ONLY */ + dr = ca.dr.query('.') + FROM #deadlock_resource AS dr + CROSS APPLY dr.resource_xml.nodes('//resource-list/exchangeEvent') AS ca(dr) + ) AS ca + CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l) + CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l) + OPTION (RECOMPILE); /*Get rid of parallel noise*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Get rid of parallel noise %s', 0, 1, @d) WITH NOWAIT; - WITH c - AS - ( - SELECT *, ROW_NUMBER() OVER ( PARTITION BY drp.owner_id, drp.waiter_id ORDER BY drp.event_date ) AS rn - FROM #deadlock_resource_parallel AS drp - ) - DELETE FROM c + + WITH + c AS + ( + SELECT + *, + rn = + ROW_NUMBER() OVER + ( + PARTITION BY + drp.owner_id, + drp.waiter_id + ORDER BY + drp.event_date + ) + FROM #deadlock_resource_parallel AS drp + ) + DELETE + FROM c WHERE c.rn > 1 OPTION (RECOMPILE); @@ -1044,64 +1142,97 @@ IF @Help = 1 /*Get rid of nonsense*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Get rid of nonsense %s', 0, 1, @d) WITH NOWAIT; + DELETE dow FROM #deadlock_owner_waiter AS dow WHERE dow.owner_id = dow.waiter_id OPTION (RECOMPILE); + /*Add some nonsense*/ ALTER TABLE #deadlock_process - ADD waiter_mode nvarchar(256), + ADD + waiter_mode nvarchar(256), owner_mode nvarchar(256), - is_victim AS CONVERT(bit, CASE WHEN id = victim_id THEN 1 ELSE 0 END); + is_victim AS + CONVERT + ( + bit, + CASE + WHEN id = victim_id + THEN 1 + ELSE 0 + END + ); + /*Update some nonsense*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Update some nonsense part 1 %s', 0, 1, @d) WITH NOWAIT; - UPDATE dp - SET dp.owner_mode = dow.owner_mode + + UPDATE + dp + SET + dp.owner_mode = dow.owner_mode FROM #deadlock_process AS dp JOIN #deadlock_owner_waiter AS dow - ON dp.id = dow.owner_id - AND dp.event_date = dow.event_date + ON dp.id = dow.owner_id + AND dp.event_date = dow.event_date WHERE dp.is_victim = 0 OPTION (RECOMPILE); + SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Update some nonsense part 2 %s', 0, 1, @d) WITH NOWAIT; - UPDATE dp - SET dp.waiter_mode = dow.waiter_mode + + UPDATE + dp + SET + dp.waiter_mode = dow.waiter_mode FROM #deadlock_process AS dp JOIN #deadlock_owner_waiter AS dow - ON dp.victim_id = dow.waiter_id - AND dp.event_date = dow.event_date + ON dp.victim_id = dow.waiter_id + AND dp.event_date = dow.event_date WHERE dp.is_victim = 1 OPTION (RECOMPILE); + /*Get Agent Job and Step names*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); + + RAISERROR('Get Agent Job and Step names %s', 0, 1, @d) WITH NOWAIT; - SELECT *, - CONVERT(uniqueidentifier, - CONVERT(xml, '').value('xs:hexBinary(substring(sql:column("x.job_id"), 0) )', 'BINARY(16)') - ) AS job_id_guid + SELECT + *, + job_id_guid = + CONVERT + ( + uniqueidentifier, + CONVERT(xml, '').value('xs:hexBinary(substring(sql:column("x.job_id"), 0) )','BINARY(16)')) INTO #agent_job - FROM ( - SELECT dp.event_date, - dp.victim_id, - dp.id, - dp.database_id, - dp.client_app, - SUBSTRING(dp.client_app, - CHARINDEX('0x', dp.client_app) + LEN('0x'), - 32 - ) AS job_id, - SUBSTRING(dp.client_app, - CHARINDEX(': Step ', dp.client_app) + LEN(': Step '), - CHARINDEX(')', dp.client_app, CHARINDEX(': Step ', dp.client_app)) - - (CHARINDEX(': Step ', dp.client_app) - + LEN(': Step ')) - ) AS step_id + FROM + ( + SELECT + dp.event_date, + dp.victim_id, + dp.id, + dp.database_id, + dp.client_app, + job_id = + SUBSTRING + ( + dp.client_app, + CHARINDEX('0x', dp.client_app) + LEN('0x'), + 32 + ), + step_id = + SUBSTRING + ( + dp.client_app, + CHARINDEX(': Step ', dp.client_app) + LEN(': Step '), + CHARINDEX(')', dp.client_app, CHARINDEX(': Step ', dp.client_app)) - + (CHARINDEX(': Step ', dp.client_app) + LEN(': Step ')) + ) FROM #deadlock_process AS dp WHERE dp.client_app LIKE 'SQLAgent - %' AND dp.client_app <> 'SQLAgent - Initial Boot Probe' @@ -1109,54 +1240,73 @@ IF @Help = 1 OPTION (RECOMPILE); - ALTER TABLE #agent_job ADD job_name nvarchar(256), - step_name nvarchar(256); + ALTER TABLE + #agent_job + ADD + job_name nvarchar(256), + step_name nvarchar(256); - IF SERVERPROPERTY('EngineEdition') NOT IN (5, 6) /* Azure SQL DB doesn't support querying jobs */ - AND NOT (LEFT(CAST(SERVERPROPERTY('ComputerNamePhysicalNetBIOS') AS varchar(8000)), 8) = 'EC2AMAZ-' /* Neither does Amazon RDS Express Edition */ - AND LEFT(CAST(SERVERPROPERTY('MachineName') AS varchar(8000)), 8) = 'EC2AMAZ-' - AND DB_ID('rdsadmin') IS NOT NULL - AND EXISTS(SELECT * FROM master.sys.all_objects WHERE name IN ('rds_startup_tasks', 'rds_help_revlogin', 'rds_hexadecimal', 'rds_failover_tracking', 'rds_database_tracking', 'rds_track_change')) - ) - BEGIN - SET @StringToExecute = N'UPDATE aj - SET aj.job_name = j.name, - aj.step_name = s.step_name - FROM msdb.dbo.sysjobs AS j - JOIN msdb.dbo.sysjobsteps AS s - ON j.job_id = s.job_id - JOIN #agent_job AS aj - ON aj.job_id_guid = j.job_id - AND aj.step_id = s.step_id - OPTION (RECOMPILE);'; - EXEC(@StringToExecute); - END; - UPDATE dp - SET dp.client_app = - CASE WHEN dp.client_app LIKE N'SQLAgent - %' - THEN N'SQLAgent - Job: ' - + aj.job_name - + N' Step: ' - + aj.step_name + IF + ( + @Azure = 0 + AND @RDS = 0 + ) + BEGIN + SET @StringToExecute = + N'UPDATE + aj + SET + aj.job_name = j.name, + aj.step_name = s.step_name + FROM msdb.dbo.sysjobs AS j + JOIN msdb.dbo.sysjobsteps AS s + ON j.job_id = s.job_id + JOIN #agent_job AS aj + ON aj.job_id_guid = j.job_id + AND aj.step_id = s.step_id + OPTION (RECOMPILE);'; + + EXEC sys.sp_executesql + @StringToExecute; + END; + + + UPDATE + dp + SET + dp.client_app = + CASE + WHEN dp.client_app LIKE N'SQLAgent - %' + THEN N'SQLAgent - Job: ' + aj.job_name + N' Step: ' + aj.step_name ELSE dp.client_app - END + END FROM #deadlock_process AS dp JOIN #agent_job AS aj - ON dp.event_date = aj.event_date - AND dp.victim_id = aj.victim_id - AND dp.id = aj.id + ON dp.event_date = aj.event_date + AND dp.victim_id = aj.victim_id + AND dp.id = aj.id OPTION (RECOMPILE); + /*Get each and every table of all databases*/ - DECLARE @sysAssObjId AS table (database_id bigint, partition_id bigint, schema_name varchar(255), table_name varchar(255)); - INSERT INTO @sysAssObjId EXECUTE sys.sp_MSforeachdb - N'USE [?]; - SELECT DB_ID() as database_id, p.partition_id, s.name as schema_name, t.name as table_name + + INSERT INTO + @sysAssObjId + EXECUTE sys.sp_MSforeachdb + N'USE [?]; + SELECT + DB_ID() as database_id, + p.partition_id, + s.name as schema_name, + t.name as table_name FROM sys.partitions p - LEFT JOIN sys.tables t ON t.object_id = p.object_id - LEFT JOIN sys.schemas s ON s.schema_id = t.schema_id - WHERE s.name is not NULL AND t.name is not NULL'; + JOIN sys.tables t + ON t.object_id = p.object_id + JOIN sys.schemas s + ON s.schema_id = t.schema_id + WHERE s.name is not NULL + AND t.name is not NULL'; /*Begin checks based on parsed values*/ @@ -1164,16 +1314,23 @@ IF @Help = 1 /*Check 1 is deadlocks by database*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Check 1 %s', 0, 1, @d) WITH NOWAIT; - INSERT #deadlock_findings WITH (TABLOCKX) - ( check_id, database_name, object_name, finding_group, finding ) - SELECT 1 AS check_id, - DB_NAME(dp.database_id) AS database_name, - '-' AS object_name, - 'Total database locks' AS finding_group, - 'This database had ' - + CONVERT(nvarchar(20), COUNT_BIG(DISTINCT dp.event_date)) - + ' deadlocks.' - FROM #deadlock_process AS dp + + INSERT + #deadlock_findings WITH (TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding + ) + SELECT + check_id = 1, + database_name = DB_NAME(dp.database_id), + object_name = '-', + finding_group = 'Total database locks', + 'This database had ' + CONVERT(nvarchar(20), COUNT_BIG(DISTINCT dp.event_date)) + ' deadlocks.' + FROM #deadlock_process AS dp WHERE 1 = 1 AND (DB_NAME(dp.database_id) = @DatabaseName OR @DatabaseName IS NULL) AND (dp.event_date >= @StartDate OR @StartDate IS NULL) @@ -1184,92 +1341,138 @@ IF @Help = 1 GROUP BY DB_NAME(dp.database_id) OPTION (RECOMPILE); + /*Check 2 is deadlocks by object*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Check 2 objects %s', 0, 1, @d) WITH NOWAIT; - INSERT #deadlock_findings WITH (TABLOCKX) - ( check_id, database_name, object_name, finding_group, finding ) - SELECT 2 AS check_id, - ISNULL(DB_NAME(dow.database_id), 'UNKNOWN') AS database_name, - ISNULL(dow.object_name, 'UNKNOWN') AS object_name, - 'Total object deadlocks' AS finding_group, - 'This object was involved in ' - + CONVERT(nvarchar(20), COUNT_BIG(DISTINCT dow.event_date)) - + ' deadlock(s).' - FROM #deadlock_owner_waiter AS dow + + INSERT + #deadlock_findings WITH (TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding + ) + SELECT + check_id = 2, + database_name = ISNULL(DB_NAME(dow.database_id), 'UNKNOWN'), + object_name = ISNULL(dow.object_name, 'UNKNOWN'), + finding_group = 'Total object deadlocks', + 'This object was involved in ' + CONVERT(nvarchar(20), COUNT_BIG(DISTINCT dow.event_date)) + + ' deadlock(s).' + FROM #deadlock_owner_waiter AS dow WHERE 1 = 1 AND (DB_NAME(dow.database_id) = @DatabaseName OR @DatabaseName IS NULL) AND (dow.event_date >= @StartDate OR @StartDate IS NULL) AND (dow.event_date < @EndDate OR @EndDate IS NULL) AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) - GROUP BY DB_NAME(dow.database_id), dow.object_name + GROUP BY + DB_NAME(dow.database_id), + dow.object_name OPTION (RECOMPILE); + /*Check 2 continuation, number of locks per index*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Check 2 indexes %s', 0, 1, @d) WITH NOWAIT; - INSERT #deadlock_findings WITH (TABLOCKX) - ( check_id, database_name, object_name, finding_group, finding ) - SELECT 2 AS check_id, - ISNULL(DB_NAME(dow.database_id), 'UNKNOWN') AS database_name, - dow.index_name AS index_name, - 'Total index deadlocks' AS finding_group, - 'This index was involved in ' - + CONVERT(nvarchar(20), COUNT_BIG(DISTINCT dow.event_date)) - + ' deadlock(s).' - FROM #deadlock_owner_waiter AS dow + + INSERT + #deadlock_findings WITH (TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding + ) + SELECT + check_id = 2, + database_name = ISNULL(DB_NAME(dow.database_id), 'UNKNOWN'), + index_name = dow.index_name, + finding_group = 'Total index deadlocks', + 'This index was involved in ' + CONVERT(nvarchar(20), COUNT_BIG(DISTINCT dow.event_date)) + ' deadlock(s).' + FROM #deadlock_owner_waiter AS dow WHERE 1 = 1 AND (DB_NAME(dow.database_id) = @DatabaseName OR @DatabaseName IS NULL) AND (dow.event_date >= @StartDate OR @StartDate IS NULL) AND (dow.event_date < @EndDate OR @EndDate IS NULL) AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) - AND dow.lock_type NOT IN (N'HEAP', N'RID') + AND dow.lock_type NOT IN + ( + N'HEAP', + N'RID' + ) AND dow.index_name IS NOT NULL - GROUP BY DB_NAME(dow.database_id), dow.index_name + GROUP BY + DB_NAME(dow.database_id), + dow.index_name OPTION (RECOMPILE); /*Check 2 continuation, number of locks per heap*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Check 2 heaps %s', 0, 1, @d) WITH NOWAIT; - INSERT #deadlock_findings WITH (TABLOCKX) - ( check_id, database_name, object_name, finding_group, finding ) - SELECT 2 AS check_id, - ISNULL(DB_NAME(dow.database_id), 'UNKNOWN') AS database_name, - dow.index_name AS index_name, - 'Total heap deadlocks' AS finding_group, - 'This heap was involved in ' - + CONVERT(nvarchar(20), COUNT_BIG(DISTINCT dow.event_date)) - + ' deadlock(s).' - FROM #deadlock_owner_waiter AS dow + + + INSERT + #deadlock_findings WITH (TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding + ) + SELECT + check_id = 2, + database_name = ISNULL(DB_NAME(dow.database_id), 'UNKNOWN'), + index_name = dow.index_name, + finding_group = 'Total heap deadlocks', + 'This heap was involved in ' + CONVERT(nvarchar(20), COUNT_BIG(DISTINCT dow.event_date)) + ' deadlock(s).' + FROM #deadlock_owner_waiter AS dow WHERE 1 = 1 AND (DB_NAME(dow.database_id) = @DatabaseName OR @DatabaseName IS NULL) AND (dow.event_date >= @StartDate OR @StartDate IS NULL) AND (dow.event_date < @EndDate OR @EndDate IS NULL) AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) - AND dow.lock_type IN (N'HEAP', N'RID') - GROUP BY DB_NAME(dow.database_id), dow.index_name + AND dow.lock_type IN + ( + N'HEAP', + N'RID' + ) + GROUP BY + DB_NAME(dow.database_id), + dow.index_name OPTION (RECOMPILE); - + /*Check 3 looks for Serializable locking*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Check 3 %s', 0, 1, @d) WITH NOWAIT; - INSERT #deadlock_findings WITH (TABLOCKX) - ( check_id, database_name, object_name, finding_group, finding ) - SELECT 3 AS check_id, - DB_NAME(dp.database_id) AS database_name, - '-' AS object_name, - 'Serializable locking' AS finding_group, - 'This database has had ' + - CONVERT(nvarchar(20), COUNT_BIG(*)) + - ' instances of serializable deadlocks.' - AS finding + + INSERT + #deadlock_findings WITH (TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding + ) + SELECT + check_id = 3, + database_name = DB_NAME(dp.database_id), + object_name = '-', + finding_group = 'Serializable locking', + finding = 'This database has had ' + CONVERT(nvarchar(20), COUNT_BIG(*)) + + ' instances of serializable deadlocks.' FROM #deadlock_process AS dp WHERE dp.isolation_level LIKE 'serializable%' - AND (DB_NAME(dp.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (dp.event_date >= @StartDate OR @StartDate IS NULL) - AND (dp.event_date < @EndDate OR @EndDate IS NULL) + AND (DB_NAME(dow.database_id) = @DatabaseName OR @DatabaseName IS NULL) + AND (dow.event_date >= @StartDate OR @StartDate IS NULL) + AND (dow.event_date < @EndDate OR @EndDate IS NULL) AND (dp.client_app = @AppName OR @AppName IS NULL) AND (dp.host_name = @HostName OR @HostName IS NULL) AND (dp.login_name = @LoginName OR @LoginName IS NULL) @@ -1280,21 +1483,28 @@ IF @Help = 1 /*Check 4 looks for Repeatable Read locking*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Check 4 %s', 0, 1, @d) WITH NOWAIT; - INSERT #deadlock_findings WITH (TABLOCKX) - ( check_id, database_name, object_name, finding_group, finding ) - SELECT 4 AS check_id, - DB_NAME(dp.database_id) AS database_name, - '-' AS object_name, - 'Repeatable Read locking' AS finding_group, - 'This database has had ' + - CONVERT(nvarchar(20), COUNT_BIG(*)) + - ' instances of repeatable read deadlocks.' - AS finding + + INSERT + #deadlock_findings WITH (TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding + ) + SELECT + check_id = 4, + database_name = DB_NAME(dp.database_id), + object_name = '-', + finding_group = 'Repeatable Read locking', + finding = 'This database has had ' + CONVERT(nvarchar(20), COUNT_BIG(*)) + + ' instances of repeatable read deadlocks.' FROM #deadlock_process AS dp WHERE dp.isolation_level LIKE 'repeatable read%' - AND (DB_NAME(dp.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (dp.event_date >= @StartDate OR @StartDate IS NULL) - AND (dp.event_date < @EndDate OR @EndDate IS NULL) + AND (DB_NAME(dow.database_id) = @DatabaseName OR @DatabaseName IS NULL) + AND (dow.event_date >= @StartDate OR @StartDate IS NULL) + AND (dow.event_date < @EndDate OR @EndDate IS NULL) AND (dp.client_app = @AppName OR @AppName IS NULL) AND (dp.host_name = @HostName OR @HostName IS NULL) AND (dp.login_name = @LoginName OR @LoginName IS NULL) @@ -1304,70 +1514,110 @@ IF @Help = 1 /*Check 5 breaks down app, host, and login information*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); + + RAISERROR('Check 5 %s', 0, 1, @d) WITH NOWAIT; - INSERT #deadlock_findings WITH (TABLOCKX) - ( check_id, database_name, object_name, finding_group, finding ) - SELECT 5 AS check_id, - DB_NAME(dp.database_id) AS database_name, - '-' AS object_name, - 'Login, App, and Host locking' AS finding_group, - 'This database has had ' + - CONVERT(nvarchar(20), COUNT_BIG(DISTINCT dp.event_date)) + - ' instances of deadlocks involving the login ' + - ISNULL(dp.login_name, 'UNKNOWN') + - ' from the application ' + - ISNULL(dp.client_app, 'UNKNOWN') + - ' on host ' + - ISNULL(dp.host_name, 'UNKNOWN') - AS finding + + + INSERT + #deadlock_findings WITH (TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding + ) + SELECT + check_id = 5, + database_name = DB_NAME(dp.database_id), + object_name = '-', + finding_group = 'Login, App, and Host locking', + finding = 'This database has had ' + CONVERT(nvarchar(20), COUNT_BIG(DISTINCT dp.event_date)) + + ' instances of deadlocks involving the login ' + ISNULL(dp.login_name, 'UNKNOWN') + + ' from the application ' + ISNULL(dp.client_app, 'UNKNOWN') + ' on host ' + + ISNULL(dp.host_name, 'UNKNOWN') FROM #deadlock_process AS dp WHERE 1 = 1 - AND (DB_NAME(dp.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (dp.event_date >= @StartDate OR @StartDate IS NULL) - AND (dp.event_date < @EndDate OR @EndDate IS NULL) + AND (DB_NAME(dow.database_id) = @DatabaseName OR @DatabaseName IS NULL) + AND (dow.event_date >= @StartDate OR @StartDate IS NULL) + AND (dow.event_date < @EndDate OR @EndDate IS NULL) AND (dp.client_app = @AppName OR @AppName IS NULL) AND (dp.host_name = @HostName OR @HostName IS NULL) AND (dp.login_name = @LoginName OR @LoginName IS NULL) - GROUP BY DB_NAME(dp.database_id), dp.login_name, dp.client_app, dp.host_name + GROUP BY + DB_NAME(dp.database_id), + dp.login_name, + dp.client_app, + dp.host_name OPTION (RECOMPILE); /*Check 6 breaks down the types of locks (object, page, key, etc.)*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Check 6 %s', 0, 1, @d) WITH NOWAIT; - WITH lock_types AS ( - SELECT DB_NAME(dp.database_id) AS database_name, - dow.object_name, - SUBSTRING(dp.wait_resource, 1, CHARINDEX(':', dp.wait_resource) -1) AS lock, - CONVERT(nvarchar(20), COUNT_BIG(DISTINCT dp.id)) AS lock_count - FROM #deadlock_process AS dp - JOIN #deadlock_owner_waiter AS dow + + + WITH + lock_types AS + ( + SELECT + database_name = DB_NAME(dp.database_id), + dow.object_name, + lock = SUBSTRING(dp.wait_resource, 1, CHARINDEX(':', dp.wait_resource) - 1), + lock_count = CONVERT(nvarchar(20), COUNT_BIG(DISTINCT dp.id)) + FROM #deadlock_process AS dp + JOIN #deadlock_owner_waiter AS dow ON dp.id = dow.owner_id AND dp.event_date = dow.event_date - WHERE 1 = 1 - AND (DB_NAME(dp.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (dp.event_date >= @StartDate OR @StartDate IS NULL) - AND (dp.event_date < @EndDate OR @EndDate IS NULL) - AND (dp.client_app = @AppName OR @AppName IS NULL) - AND (dp.host_name = @HostName OR @HostName IS NULL) - AND (dp.login_name = @LoginName OR @LoginName IS NULL) - AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) - AND dow.object_name IS NOT NULL - GROUP BY DB_NAME(dp.database_id), SUBSTRING(dp.wait_resource, 1, CHARINDEX(':', dp.wait_resource) - 1), dow.object_name - ) - INSERT #deadlock_findings WITH (TABLOCKX) - ( check_id, database_name, object_name, finding_group, finding ) - SELECT DISTINCT 6 AS check_id, - lt.database_name, - lt.object_name, - 'Types of locks by object' AS finding_group, - 'This object has had ' + - STUFF((SELECT DISTINCT N', ' + lt2.lock_count + ' ' + lt2.lock - FROM lock_types AS lt2 - WHERE lt2.database_name = lt.database_name - AND lt2.object_name = lt.object_name - FOR xml PATH(N''), TYPE).value(N'.[1]', N'nvarchar(MAX)'), 1, 1, N'') - + ' locks' + WHERE 1 = 1 + AND (DB_NAME(dow.database_id) = @DatabaseName OR @DatabaseName IS NULL) + AND (dow.event_date >= @StartDate OR @StartDate IS NULL) + AND (dow.event_date < @EndDate OR @EndDate IS NULL) + AND (dp.client_app = @AppName OR @AppName IS NULL) + AND (dp.host_name = @HostName OR @HostName IS NULL) + AND (dp.login_name = @LoginName OR @LoginName IS NULL) + AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) + AND dow.object_name IS NOT NULL + GROUP BY + DB_NAME(dp.database_id), + SUBSTRING(dp.wait_resource, 1, CHARINDEX(':', dp.wait_resource) - 1), + dow.object_name + ) + INSERT + #deadlock_findings WITH (TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding + ) + SELECT DISTINCT + check_id = 6, + lt.database_name, + lt.object_name, + finding_group = 'Types of locks by object', + N'This object has had ' + + STUFF + ( + ( + SELECT DISTINCT + N', ' + + lt2.lock_count + + N' ' + + lt2.lock + FROM lock_types AS lt2 + WHERE lt2.database_name = lt.database_name + AND lt2.object_name = lt.object_name + FOR XML + PATH(N''), + TYPE + ).value(N'.[1]', N'nvarchar(MAX)'), + 1, + 1, + N'' + ) + N' locks' FROM lock_types AS lt OPTION (RECOMPILE); @@ -1375,44 +1625,71 @@ IF @Help = 1 /*Check 7 gives you more info queries for sp_BlitzCache & BlitzQueryStore*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Check 7 part 1 %s', 0, 1, @d) WITH NOWAIT; - WITH deadlock_stack AS ( - SELECT DISTINCT + + + WITH + deadlock_stack AS + ( + SELECT DISTINCT ds.id, ds.proc_name, ds.event_date, - PARSENAME(ds.proc_name, 3) AS database_name, - PARSENAME(ds.proc_name, 2) AS schema_name, - PARSENAME(ds.proc_name, 1) AS proc_only_name, - '''' + STUFF((SELECT DISTINCT N',' + ds2.sql_handle - FROM #deadlock_stack AS ds2 - WHERE ds2.id = ds.id - AND ds2.event_date = ds.event_date - FOR xml PATH(N''), TYPE).value(N'.[1]', N'nvarchar(MAX)'), 1, 1, N'') + '''' AS sql_handle_csv - FROM #deadlock_stack AS ds - GROUP BY PARSENAME(ds.proc_name, 3), - PARSENAME(ds.proc_name, 2), - PARSENAME(ds.proc_name, 1), - ds.id, - ds.proc_name, - ds.event_date - ) - INSERT #deadlock_findings WITH (TABLOCKX) - ( check_id, database_name, object_name, finding_group, finding ) - SELECT DISTINCT 7 AS check_id, - ISNULL(DB_NAME(dow.database_id), 'UNKNOWN') AS database_name, - ds.proc_name AS object_name, - 'More Info - Query' AS finding_group, - 'EXEC sp_BlitzCache ' + - CASE WHEN ds.proc_name = 'adhoc' - THEN ' @OnlySqlHandles = ' + ds.sql_handle_csv - ELSE '@StoredProcName = ' + - QUOTENAME(ds.proc_only_name, '''') - END + - ';' AS finding + database_name = PARSENAME(ds.proc_name, 3), + schema_name = PARSENAME(ds.proc_name, 2), + proc_only_name = PARSENAME(ds.proc_name, 1), + sql_handle_csv = N'''' + + STUFF + ( + ( + SELECT DISTINCT + N',' + + ds2.sql_handle + FROM #deadlock_stack AS ds2 + WHERE ds2.id = ds.id + AND ds2.event_date = ds.event_date + AND ds2.sql_handle <> 0x + FOR XML + PATH(N''), + TYPE + ).value(N'.[1]', N'nvarchar(MAX)'), + 1, + 1, + N'' + ) + N'''' + FROM #deadlock_stack AS ds + WHERE ds.sql_handle <> 0x + GROUP BY + PARSENAME(ds.proc_name, 3), + PARSENAME(ds.proc_name, 2), + PARSENAME(ds.proc_name, 1), + ds.id, + ds.proc_name, + ds.event_date + ) + INSERT + #deadlock_findings WITH (TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding + ) + SELECT DISTINCT + check_id = 7, + database_name = ISNULL(DB_NAME(dow.database_id), 'UNKNOWN'), + object_name = ds.proc_name, + finding_group = 'More Info - Query', + finding = 'EXEC sp_BlitzCache ' + + CASE + WHEN ds.proc_name = 'adhoc' + THEN ' @OnlySqlHandles = ' + ds.sql_handle_csv + ELSE '@StoredProcName = ' + QUOTENAME(ds.proc_only_name, '''') + END + ';' FROM deadlock_stack AS ds JOIN #deadlock_owner_waiter AS dow - ON dow.owner_id = ds.id - AND dow.event_date = ds.event_date + ON dow.owner_id = ds.id + AND dow.event_date = ds.event_date WHERE 1 = 1 AND (DB_NAME(dow.database_id) = @DatabaseName OR @DatabaseName IS NULL) AND (dow.event_date >= @StartDate OR @StartDate IS NULL) @@ -1420,67 +1697,79 @@ IF @Help = 1 AND (dow.object_name = @StoredProcName OR @StoredProcName IS NULL) OPTION (RECOMPILE); - IF @ProductVersionMajor >= 13 + + IF (@ProductVersionMajor >= 13 OR @Azure = 1) BEGIN - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Check 7 part 2 %s', 0, 1, @d) WITH NOWAIT; - WITH deadlock_stack AS ( - SELECT DISTINCT + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Check 7 part 2 %s', 0, 1, @d) WITH NOWAIT; + + WITH + deadlock_stack AS + ( + SELECT DISTINCT ds.id, ds.sql_handle, ds.proc_name, ds.event_date, - PARSENAME(ds.proc_name, 3) AS database_name, - PARSENAME(ds.proc_name, 2) AS schema_name, - PARSENAME(ds.proc_name, 1) AS proc_only_name - FROM #deadlock_stack AS ds - ) - INSERT #deadlock_findings WITH (TABLOCKX) - ( check_id, database_name, object_name, finding_group, finding ) - SELECT DISTINCT 7 AS check_id, - DB_NAME(dow.database_id) AS database_name, - ds.proc_name AS object_name, - 'More Info - Query' AS finding_group, - 'EXEC sp_BlitzQueryStore ' - + '@DatabaseName = ' - + QUOTENAME(ds.database_name, '''') - + ', ' - + '@StoredProcName = ' - + QUOTENAME(ds.proc_only_name, '''') - + ';' AS finding - FROM deadlock_stack AS ds - JOIN #deadlock_owner_waiter AS dow - ON dow.owner_id = ds.id - AND dow.event_date = ds.event_date - WHERE ds.proc_name <> 'adhoc' - AND (DB_NAME(dow.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (dow.event_date >= @StartDate OR @StartDate IS NULL) - AND (dow.event_date < @EndDate OR @EndDate IS NULL) - AND (dow.object_name = @StoredProcName OR @StoredProcName IS NULL) - OPTION (RECOMPILE); + database_name = PARSENAME(ds.proc_name, 3), + schema_name = PARSENAME(ds.proc_name, 2), + proc_only_name = PARSENAME(ds.proc_name, 1) + FROM #deadlock_stack AS ds + ) + INSERT + #deadlock_findings WITH (TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding + ) + SELECT DISTINCT + check_id = 7, + database_name = DB_NAME(dow.database_id), + object_name = ds.proc_name, + finding_group = 'More Info - Query', + finding = 'EXEC sp_BlitzQueryStore ' + '@DatabaseName = ' + QUOTENAME(ds.database_name, '''') + ', ' + + '@StoredProcName = ' + QUOTENAME(ds.proc_only_name, '''') + ';' + FROM deadlock_stack AS ds + JOIN #deadlock_owner_waiter AS dow + ON dow.owner_id = ds.id + AND dow.event_date = ds.event_date + WHERE ds.proc_name <> 'adhoc' + AND (DB_NAME(dow.database_id) = @DatabaseName OR @DatabaseName IS NULL) + AND (dow.event_date >= @StartDate OR @StartDate IS NULL) + AND (dow.event_date < @EndDate OR @EndDate IS NULL) + AND (dow.object_name = @StoredProcName OR @StoredProcName IS NULL) + OPTION (RECOMPILE); END; - + /*Check 8 gives you stored proc deadlock counts*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Check 8 %s', 0, 1, @d) WITH NOWAIT; - INSERT #deadlock_findings WITH (TABLOCKX) - ( check_id, database_name, object_name, finding_group, finding ) - SELECT 8 AS check_id, - DB_NAME(dp.database_id) AS database_name, - ds.proc_name, - 'Stored Procedure Deadlocks', - 'The stored procedure ' - + PARSENAME(ds.proc_name, 2) - + '.' - + PARSENAME(ds.proc_name, 1) - + ' has been involved in ' - + CONVERT(nvarchar(10), COUNT_BIG(DISTINCT ds.id)) - + ' deadlocks.' + + + INSERT + #deadlock_findings WITH (TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding + ) + SELECT + check_id = 8, + database_name = DB_NAME(dp.database_id), + ds.proc_name, + 'Stored Procedure Deadlocks', + 'The stored procedure ' + PARSENAME(ds.proc_name, 2) + '.' + PARSENAME(ds.proc_name, 1) + + ' has been involved in ' + CONVERT(nvarchar(10), COUNT_BIG(DISTINCT ds.id)) + ' deadlocks.' FROM #deadlock_stack AS ds JOIN #deadlock_process AS dp - ON dp.id = ds.id - AND ds.event_date = dp.event_date + ON dp.id = ds.id + AND ds.event_date = dp.event_date WHERE ds.proc_name <> 'adhoc' AND (ds.proc_name = @StoredProcName OR @StoredProcName IS NULL) AND (DB_NAME(dp.database_id) = @DatabaseName OR @DatabaseName IS NULL) @@ -1489,167 +1778,251 @@ IF @Help = 1 AND (dp.client_app = @AppName OR @AppName IS NULL) AND (dp.host_name = @HostName OR @HostName IS NULL) AND (dp.login_name = @LoginName OR @LoginName IS NULL) - GROUP BY DB_NAME(dp.database_id), ds.proc_name - OPTION(RECOMPILE); + GROUP BY + DB_NAME(dp.database_id), + ds.proc_name + OPTION (RECOMPILE); /*Check 9 gives you more info queries for sp_BlitzIndex */ SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Check 9 %s', 0, 1, @d) WITH NOWAIT; - WITH bi AS ( - SELECT DISTINCT + + + WITH + bi AS + ( + SELECT DISTINCT dow.object_name, - DB_NAME(dow.database_id) AS database_name, - a.schema_name AS schema_name, - a.table_name AS table_name + database_name = DB_NAME(dow.database_id), + schema_name = a.schema_name, + table_name = a.table_name FROM #deadlock_owner_waiter AS dow - LEFT JOIN @sysAssObjId a ON a.database_id=dow.database_id AND a.partition_id = dow.associatedObjectId + LEFT JOIN @sysAssObjId AS a + ON a.database_id = dow.database_id + AND a.partition_id = dow.associatedObjectId WHERE 1 = 1 - AND (DB_NAME(dow.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (dow.event_date >= @StartDate OR @StartDate IS NULL) - AND (dow.event_date < @EndDate OR @EndDate IS NULL) + AND (DB_NAME(dp.database_id) = @DatabaseName OR @DatabaseName IS NULL) + AND (dp.event_date >= @StartDate OR @StartDate IS NULL) + AND (dp.event_date < @EndDate OR @EndDate IS NULL) AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) AND dow.object_name IS NOT NULL + ) + INSERT + #deadlock_findings WITH (TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding ) - INSERT #deadlock_findings WITH (TABLOCKX) - ( check_id, database_name, object_name, finding_group, finding ) - SELECT 9 AS check_id, - bi.database_name, - bi.object_name, - 'More Info - Table' AS finding_group, - 'EXEC sp_BlitzIndex ' + - '@DatabaseName = ' + QUOTENAME(bi.database_name, '''') + - ', @SchemaName = ' + QUOTENAME(bi.schema_name, '''') + - ', @TableName = ' + QUOTENAME(bi.table_name, '''') + - ';' AS finding + SELECT DISTINCT + check_id = 9, + bi.database_name, + bi.object_name, + finding_group = 'More Info - Table', + finding = 'EXEC sp_BlitzIndex ' + '@DatabaseName = ' + QUOTENAME(bi.database_name, '''') + + ', @SchemaName = ' + QUOTENAME(bi.schema_name, '''') + ', @TableName = ' + + QUOTENAME(bi.table_name, '''') + ';' FROM bi OPTION (RECOMPILE); + /*Check 10 gets total deadlock wait time per object*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Check 10 %s', 0, 1, @d) WITH NOWAIT; - WITH chopsuey AS ( - SELECT DISTINCT - PARSENAME(dow.object_name, 3) AS database_name, + + + WITH + chopsuey AS + ( + + + SELECT DISTINCT + database_name = PARSENAME(dow.object_name, 3), dow.object_name, - CONVERT(varchar(10), (SUM(DISTINCT CONVERT(bigint, dp.wait_time)) / 1000) / 86400) AS wait_days, - CONVERT(varchar(20), DATEADD(SECOND, (SUM(DISTINCT CONVERT(bigint, dp.wait_time)) / 1000), 0), 108) AS wait_time_hms - FROM #deadlock_owner_waiter AS dow - JOIN #deadlock_process AS dp - ON (dp.id = dow.owner_id OR dp.victim_id = dow.waiter_id) - AND dp.event_date = dow.event_date - WHERE 1 = 1 - AND (DB_NAME(dow.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (dow.event_date >= @StartDate OR @StartDate IS NULL) - AND (dow.event_date < @EndDate OR @EndDate IS NULL) - AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) - AND (dp.client_app = @AppName OR @AppName IS NULL) - AND (dp.host_name = @HostName OR @HostName IS NULL) - AND (dp.login_name = @LoginName OR @LoginName IS NULL) - GROUP BY PARSENAME(dow.object_name, 3), dow.object_name - ) - INSERT #deadlock_findings WITH (TABLOCKX) - ( check_id, database_name, object_name, finding_group, finding ) - SELECT 10 AS check_id, - cs.database_name, - cs.object_name, - 'Total object deadlock wait time' AS finding_group, - 'This object has had ' - + CONVERT(varchar(10), cs.wait_days) - + ':' + CONVERT(varchar(20), cs.wait_time_hms, 108) - + ' [d/h/m/s] of deadlock wait time.' AS finding - FROM chopsuey AS cs - WHERE cs.object_name IS NOT NULL - OPTION (RECOMPILE); + wait_days = CONVERT(varchar(10), (SUM(DISTINCT CONVERT(bigint, dp.wait_time)) / 1000) / 86400), + wait_time_hms = + CONVERT + ( + varchar(20), + DATEADD(SECOND, (SUM(DISTINCT CONVERT(bigint, dp.wait_time)) / 1000), 0), + 108 + ) + FROM #deadlock_owner_waiter AS dow + JOIN #deadlock_process AS dp + ON (dp.id = dow.owner_id + OR dp.victim_id = dow.waiter_id) + AND dp.event_date = dow.event_date + WHERE 1 = 1 + AND (DB_NAME(dp.database_id) = @DatabaseName OR @DatabaseName IS NULL) + AND (dp.event_date >= @StartDate OR @StartDate IS NULL) + AND (dp.event_date < @EndDate OR @EndDate IS NULL) + AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) + AND (dp.client_app = @AppName OR @AppName IS NULL) + AND (dp.host_name = @HostName OR @HostName IS NULL) + AND (dp.login_name = @LoginName OR @LoginName IS NULL) + GROUP BY + PARSENAME(dow.object_name, 3), + dow.object_name + ) + INSERT + #deadlock_findings WITH (TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding + ) + SELECT DISTINCT + check_id = 10, + cs.database_name, + cs.object_name, + finding_group = 'Total object deadlock wait time', + finding = 'This object has had ' + CONVERT(varchar(10), cs.wait_days) + ':' + + CONVERT(varchar(20), cs.wait_time_hms, 108) + ' [d/h/m/s] of deadlock wait time.' + FROM chopsuey AS cs + WHERE cs.object_name IS NOT NULL + OPTION (RECOMPILE); + /*Check 11 gets total deadlock wait time per database*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Check 11 %s', 0, 1, @d) WITH NOWAIT; - WITH wait_time AS ( - SELECT DB_NAME(dp.database_id) AS database_name, - SUM(CONVERT(bigint, dp.wait_time)) AS total_wait_time_ms - FROM #deadlock_process AS dp - WHERE 1 = 1 - AND (DB_NAME(dp.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (dp.event_date >= @StartDate OR @StartDate IS NULL) - AND (dp.event_date < @EndDate OR @EndDate IS NULL) - AND (dp.client_app = @AppName OR @AppName IS NULL) - AND (dp.host_name = @HostName OR @HostName IS NULL) - AND (dp.login_name = @LoginName OR @LoginName IS NULL) - GROUP BY DB_NAME(dp.database_id) - ) - INSERT #deadlock_findings WITH (TABLOCKX) - ( check_id, database_name, object_name, finding_group, finding ) - SELECT 11 AS check_id, - wt.database_name, - '-' AS object_name, - 'Total database deadlock wait time' AS finding_group, - 'This database has had ' - + CONVERT(varchar(10), (SUM(DISTINCT CONVERT(bigint, wt.total_wait_time_ms)) / 1000) / 86400) - + ':' + CONVERT(varchar(20), DATEADD(SECOND, (SUM(DISTINCT CONVERT(bigint, wt.total_wait_time_ms)) / 1000), 0), 108) - + ' [d/h/m/s] of deadlock wait time.' + + + WITH + wait_time AS + ( + SELECT + database_name = DB_NAME(dp.database_id), + total_wait_time_ms = SUM(CONVERT(bigint, dp.wait_time)) + FROM #deadlock_process AS dp + WHERE 1 = 1 + AND (DB_NAME(dp.database_id) = @DatabaseName OR @DatabaseName IS NULL) + AND (dp.event_date >= @StartDate OR @StartDate IS NULL) + AND (dp.event_date < @EndDate OR @EndDate IS NULL) + AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) + AND (dp.client_app = @AppName OR @AppName IS NULL) + AND (dp.host_name = @HostName OR @HostName IS NULL) + AND (dp.login_name = @LoginName OR @LoginName IS NULL) + GROUP BY DB_NAME(dp.database_id) + ) + INSERT + #deadlock_findings WITH (TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding + ) + SELECT + check_id = 11, + wt.database_name, + object_name = '-', + finding_group = 'Total database deadlock wait time', + 'This database has had ' + + CONVERT(varchar(10), (SUM(DISTINCT CONVERT(bigint, wt.total_wait_time_ms)) / 1000) / 86400) + ':' + + CONVERT + ( + varchar(20), + DATEADD(SECOND, (SUM(DISTINCT CONVERT(bigint, wt.total_wait_time_ms)) / 1000), 0), + 108 + ) + ' [d/h/m/s] of deadlock wait time.' FROM wait_time AS wt GROUP BY wt.database_name OPTION (RECOMPILE); + /*Check 12 gets total deadlock wait time for SQL Agent*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Check 12 %s', 0, 1, @d) WITH NOWAIT; - INSERT #deadlock_findings WITH (TABLOCKX) - ( check_id, database_name, object_name, finding_group, finding ) - SELECT 12, - DB_NAME(aj.database_id), - 'SQLAgent - Job: ' - + aj.job_name - + ' Step: ' - + aj.step_name, - 'Agent Job Deadlocks', - RTRIM(COUNT(*)) + ' deadlocks from this Agent Job and Step' + + INSERT + #deadlock_findings WITH (TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding + ) + SELECT + 12, + DB_NAME(aj.database_id), + 'SQLAgent - Job: ' + aj.job_name + ' Step: ' + aj.step_name, + 'Agent Job Deadlocks', + RTRIM(COUNT_BIG(*)) + ' deadlocks from this Agent Job and Step' FROM #agent_job AS aj - GROUP BY DB_NAME(aj.database_id), aj.job_name, aj.step_name + GROUP BY + DB_NAME(aj.database_id), + aj.job_name, + aj.step_name OPTION (RECOMPILE); + /*Check 13 is total parallel deadlocks*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Check 13 %s', 0, 1, @d) WITH NOWAIT; - INSERT #deadlock_findings WITH (TABLOCKX) - ( check_id, database_name, object_name, finding_group, finding ) - SELECT 13 AS check_id, - N'-' AS database_name, - '-' AS object_name, - 'Total parallel deadlocks' AS finding_group, - 'There have been ' - + CONVERT(nvarchar(20), COUNT_BIG(DISTINCT drp.event_date)) - + ' parallel deadlocks.' - FROM #deadlock_resource_parallel AS drp + + INSERT + #deadlock_findings WITH (TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding + ) + SELECT + check_id = 13, + database_name = N'-', + object_name = '-', + finding_group = 'Total parallel deadlocks', + 'There have been ' + CONVERT(nvarchar(20), COUNT_BIG(DISTINCT drp.event_date)) + ' parallel deadlocks.' + FROM #deadlock_resource_parallel AS drp WHERE 1 = 1 HAVING COUNT_BIG(DISTINCT drp.event_date) > 0 OPTION (RECOMPILE); - /*Thank you goodnight*/ - INSERT #deadlock_findings WITH (TABLOCKX) - ( check_id, database_name, object_name, finding_group, finding ) - VALUES ( -1, - N'sp_BlitzLock ' + CAST(CONVERT(datetime, @VersionDate, 102) AS varchar(100)), - N'SQL Server First Responder Kit', - N'http://FirstResponderKit.org/', - N'To get help or add your own contributions, join us at http://FirstResponderKit.org.'); - + /*Thank you goodnight*/ + INSERT + #deadlock_findings WITH (TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding + ) + VALUES + ( + -1, + N'sp_BlitzLock ' + CAST(CONVERT(datetime, @VersionDate, 102) AS varchar(100)), + N'SQL Server First Responder Kit', N'http://FirstResponderKit.org/', + N'To get help or add your own contributions, join us at http://FirstResponderKit.org.' + ); /*Results*/ - /*Break in case of emergency*/ - --CREATE CLUSTERED INDEX cx_whatever ON #deadlock_process (event_date, id); - --CREATE CLUSTERED INDEX cx_whatever ON #deadlock_resource_parallel (event_date, owner_id); - --CREATE CLUSTERED INDEX cx_whatever ON #deadlock_owner_waiter (event_date, owner_id, waiter_id); - IF(@OutputDatabaseCheck = 0) - BEGIN - - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Results 1 %s', 0, 1, @d) WITH NOWAIT; - WITH deadlocks - AS ( SELECT N'Regular Deadlock' AS deadlock_type, + CREATE CLUSTERED INDEX cx_whatever ON #deadlock_process (event_date, id); + CREATE CLUSTERED INDEX cx_whatever ON #deadlock_resource_parallel (event_date, owner_id); + CREATE CLUSTERED INDEX cx_whatever ON #deadlock_owner_waiter (event_date, owner_id, waiter_id); + IF (@OutputDatabaseCheck = 0) + BEGIN + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Results 1 %s', 0, 1, @d) WITH NOWAIT; + + + WITH + deadlocks AS + ( + SELECT + deadlock_type = N'Regular Deadlock', dp.event_date, dp.id, dp.victim_id, @@ -1657,19 +2030,33 @@ IF @Help = 1 dp.database_id, dp.priority, dp.log_used, - dp.wait_resource COLLATE DATABASE_DEFAULT AS wait_resource, - CONVERT( + wait_resource = dp.wait_resource COLLATE DATABASE_DEFAULT, + object_names = + CONVERT + ( xml, - STUFF(( SELECT DISTINCT NCHAR(10) - + N' ' - + ISNULL(c.object_name, N'') - + N' ' COLLATE DATABASE_DEFAULT AS object_name - FROM #deadlock_owner_waiter AS c - WHERE ( dp.id = c.owner_id - OR dp.victim_id = c.waiter_id ) + STUFF + ( + ( + SELECT DISTINCT + object_name = + NCHAR(10) + + N' ' + + ISNULL(c.object_name, N'') + + N' ' COLLATE DATABASE_DEFAULT + FROM #deadlock_owner_waiter AS c + WHERE (dp.id = c.owner_id + OR dp.victim_id = c.waiter_id) AND dp.event_date = c.event_date - FOR xml PATH(N''), TYPE ).value(N'.[1]', N'nvarchar(4000)'), - 1, 1, N'')) AS object_names, + FOR XML + PATH(N''), + TYPE + ).value(N'.[1]', N'nvarchar(4000)'), + 1, + 1, + N'' + ) + ), dp.wait_time, dp.transaction_name, dp.last_tran_started, @@ -1681,33 +2068,34 @@ IF @Help = 1 dp.host_name, dp.login_name, dp.isolation_level, - dp.process_xml.value('(//process/inputbuf/text())[1]', 'nvarchar(MAX)') AS inputbuf, - DENSE_RANK() OVER ( ORDER BY dp.event_date ) AS en, - ROW_NUMBER() OVER ( PARTITION BY dp.event_date ORDER BY dp.event_date ) -1 AS qn, - ROW_NUMBER() OVER ( PARTITION BY dp.event_date, dp.id ORDER BY dp.event_date ) AS dn, + inputbuf = dp.process_xml.value('(//process/inputbuf/text())[1]', 'nvarchar(MAX)'), + en = DENSE_RANK() OVER (ORDER BY dp.event_date), + qn = ROW_NUMBER() OVER (PARTITION BY dp.event_date ORDER BY dp.event_date) - 1, + dn = ROW_NUMBER() OVER (PARTITION BY dp.event_date, dp.id ORDER BY dp.event_date), dp.is_victim, - ISNULL(dp.owner_mode, '-') AS owner_mode, - NULL AS owner_waiter_type, - NULL AS owner_activity, - NULL AS owner_waiter_activity, - NULL AS owner_merging, - NULL AS owner_spilling, - NULL AS owner_waiting_to_close, - ISNULL(dp.waiter_mode, '-') AS waiter_mode, - NULL AS waiter_waiter_type, - NULL AS waiter_owner_activity, - NULL AS waiter_waiter_activity, - NULL AS waiter_merging, - NULL AS waiter_spilling, - NULL AS waiter_waiting_to_close, + owner_mode = ISNULL(dp.owner_mode, '-'), + owner_waiter_type = NULL, + owner_activity = NULL, + owner_waiter_activity = NULL, + owner_merging = NULL, + owner_spilling = NULL, + owner_waiting_to_close = NULL, + waiter_mode = ISNULL(dp.waiter_mode, '-'), + waiter_waiter_type = NULL, + waiter_owner_activity = NULL, + waiter_waiter_activity = NULL, + waiter_merging = NULL, + waiter_spilling = NULL, + waiter_waiting_to_close = NULL, dp.deadlock_graph - FROM #deadlock_process AS dp - WHERE dp.victim_id IS NOT NULL - AND dp.is_parallel = 0 - - UNION ALL - - SELECT N'Parallel Deadlock' AS deadlock_type, + FROM #deadlock_process AS dp + WHERE dp.victim_id IS NOT NULL + AND dp.is_parallel = 0 + + UNION ALL + + SELECT + deadlock_type = N'Parallel Deadlock', dp.event_date, dp.id, dp.victim_id, @@ -1716,18 +2104,32 @@ IF @Help = 1 dp.priority, dp.log_used, dp.wait_resource COLLATE DATABASE_DEFAULT, - CONVERT( - xml, - STUFF(( SELECT DISTINCT NCHAR(10) - + N' ' - + ISNULL(c.object_name, N'') - + N' ' COLLATE DATABASE_DEFAULT AS object_name - FROM #deadlock_owner_waiter AS c - WHERE ( dp.id = c.owner_id - OR dp.victim_id = c.waiter_id ) - AND dp.event_date = c.event_date - FOR xml PATH(N''), TYPE ).value(N'.[1]', N'nvarchar(4000)'), - 1, 1, N'')) AS object_names, + object_names = + CONVERT + ( + xml, + STUFF + ( + ( + SELECT DISTINCT + object_name = + NCHAR(10) + + N' ' + + ISNULL(c.object_name, N'') + + N' ' COLLATE DATABASE_DEFAULT + FROM #deadlock_owner_waiter AS c + WHERE (dp.id = c.owner_id + OR dp.victim_id = c.waiter_id) + AND dp.event_date = c.event_date + FOR XML + PATH(N''), + TYPE + ).value(N'.[1]', N'nvarchar(4000)'), + 1, + 1, + N'' + ) + ), dp.wait_time, dp.transaction_name, dp.last_tran_started, @@ -1739,272 +2141,389 @@ IF @Help = 1 dp.host_name, dp.login_name, dp.isolation_level, - dp.process_xml.value('(//process/inputbuf/text())[1]', 'nvarchar(MAX)') AS inputbuf, - DENSE_RANK() OVER ( ORDER BY dp.event_date ) AS en, - ROW_NUMBER() OVER ( PARTITION BY dp.event_date ORDER BY dp.event_date ) -1 AS qn, - ROW_NUMBER() OVER ( PARTITION BY dp.event_date, dp.id ORDER BY dp.event_date ) AS dn, - 1 AS is_victim, - cao.wait_type COLLATE DATABASE_DEFAULT AS owner_mode, - cao.waiter_type AS owner_waiter_type, - cao.owner_activity AS owner_activity, - cao.waiter_activity AS owner_waiter_activity, - cao.merging AS owner_merging, - cao.spilling AS owner_spilling, - cao.waiting_to_close AS owner_waiting_to_close, - caw.wait_type COLLATE DATABASE_DEFAULT AS waiter_mode, - caw.waiter_type AS waiter_waiter_type, - caw.owner_activity AS waiter_owner_activity, - caw.waiter_activity AS waiter_waiter_activity, - caw.merging AS waiter_merging, - caw.spilling AS waiter_spilling, - caw.waiting_to_close AS waiter_waiting_to_close, + inputbuf = dp.process_xml.value('(//process/inputbuf/text())[1]', 'nvarchar(MAX)'), + en = DENSE_RANK() OVER (ORDER BY dp.event_date), + qn = ROW_NUMBER() OVER (PARTITION BY dp.event_date ORDER BY dp.event_date) - 1, + dn = ROW_NUMBER() OVER (PARTITION BY dp.event_date, dp.id ORDER BY dp.event_date), + is_victim = 1, + owner_mode = cao.wait_type COLLATE DATABASE_DEFAULT, + owner_waiter_type = cao.waiter_type, + owner_activity = cao.owner_activity, + owner_waiter_activity = cao.waiter_activity, + owner_merging = cao.merging, + owner_spilling = cao.spilling, + owner_waiting_to_close = cao.waiting_to_close, + waiter_mode = caw.wait_type COLLATE DATABASE_DEFAULT, + waiter_waiter_type = caw.waiter_type, + waiter_owner_activity = caw.owner_activity, + waiter_waiter_activity = caw.waiter_activity, + waiter_merging = caw.merging, + waiter_spilling = caw.spilling, + waiter_waiting_to_close = caw.waiting_to_close, dp.deadlock_graph - FROM #deadlock_process AS dp - CROSS APPLY (SELECT TOP 1 * FROM #deadlock_resource_parallel AS drp WHERE drp.owner_id = dp.id AND drp.wait_type = 'e_waitPipeNewRow' ORDER BY drp.event_date) AS cao - CROSS APPLY (SELECT TOP 1 * FROM #deadlock_resource_parallel AS drp WHERE drp.owner_id = dp.id AND drp.wait_type = 'e_waitPipeGetRow' ORDER BY drp.event_date) AS caw - WHERE dp.is_parallel = 1 ) - INSERT INTO DeadLockTbl ( - ServerName, - deadlock_type, - event_date, - database_name, - spid, - deadlock_group, - query, - object_names, - isolation_level, - owner_mode, - waiter_mode, - transaction_count, - login_name, - host_name, - client_app, - wait_time, - wait_resource, - priority, - log_used, - last_tran_started, - last_batch_started, - last_batch_completed, - transaction_name, - owner_waiter_type, - owner_activity, - owner_waiter_activity, - owner_merging, - owner_spilling, - owner_waiting_to_close, - waiter_waiter_type, - waiter_owner_activity, - waiter_waiter_activity, - waiter_merging, - waiter_spilling, - waiter_waiting_to_close, - deadlock_graph + FROM #deadlock_process AS dp + CROSS APPLY + ( + SELECT TOP (1) + * + FROM #deadlock_resource_parallel AS drp + WHERE drp.owner_id = dp.id + AND drp.wait_type = 'e_waitPipeNewRow' + ORDER BY drp.event_date + ) AS cao + CROSS APPLY + ( + SELECT TOP (1) + * + FROM #deadlock_resource_parallel AS drp + WHERE drp.owner_id = dp.id + AND drp.wait_type = 'e_waitPipeGetRow' + ORDER BY drp.event_date + ) AS caw + WHERE dp.is_parallel = 1 ) - SELECT @ServerName, - d.deadlock_type, - d.event_date, - DB_NAME(d.database_id) AS database_name, - d.spid, - 'Deadlock #' - + CONVERT(nvarchar(10), d.en) - + ', Query #' - + CASE WHEN d.qn = 0 THEN N'1' ELSE CONVERT(nvarchar(10), d.qn) END - + CASE WHEN d.is_victim = 1 THEN ' - VICTIM' ELSE '' END - AS deadlock_group, - CONVERT(xml, N'') AS query, - d.object_names, - d.isolation_level, - d.owner_mode, - d.waiter_mode, - d.transaction_count, - d.login_name, - d.host_name, - d.client_app, - d.wait_time, - d.wait_resource, - d.priority, - d.log_used, - d.last_tran_started, - d.last_batch_started, - d.last_batch_completed, - d.transaction_name, - /*These columns will be NULL for regular (non-parallel) deadlocks*/ - d.owner_waiter_type, - d.owner_activity, - d.owner_waiter_activity, - d.owner_merging, - d.owner_spilling, - d.owner_waiting_to_close, - d.waiter_waiter_type, - d.waiter_owner_activity, - d.waiter_waiter_activity, - d.waiter_merging, - d.waiter_spilling, - d.waiter_waiting_to_close, - d.deadlock_graph - FROM deadlocks AS d - WHERE d.dn = 1 - AND (is_victim = @VictimsOnly OR @VictimsOnly = 0) - AND d.qn < CASE WHEN d.deadlock_type = N'Parallel Deadlock' THEN 2 ELSE 2147483647 END - AND (DB_NAME(d.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (d.event_date >= @StartDate OR @StartDate IS NULL) - AND (d.event_date < @EndDate OR @EndDate IS NULL) - AND (CONVERT(nvarchar(MAX), d.object_names) LIKE '%' + @ObjectName + '%' OR @ObjectName IS NULL) - AND (d.client_app = @AppName OR @AppName IS NULL) - AND (d.host_name = @HostName OR @HostName IS NULL) - AND (d.login_name = @LoginName OR @LoginName IS NULL) - ORDER BY d.event_date, is_victim DESC - OPTION (RECOMPILE); - - DROP SYNONYM DeadLockTbl; --done insert into blitzlock table going to insert into findings table first create synonym. + INSERT INTO + DeadLockTbl + ( + ServerName, + deadlock_type, + event_date, + database_name, + spid, + deadlock_group, + query, + object_names, + isolation_level, + owner_mode, + waiter_mode, + transaction_count, + login_name, + host_name, + client_app, + wait_time, + wait_resource, + priority, + log_used, + last_tran_started, + last_batch_started, + last_batch_completed, + transaction_name, + owner_waiter_type, + owner_activity, + owner_waiter_activity, + owner_merging, + owner_spilling, + owner_waiting_to_close, + waiter_waiter_type, + waiter_owner_activity, + waiter_waiter_activity, + waiter_merging, + waiter_spilling, + waiter_waiting_to_close, + deadlock_graph + ) + SELECT + @ServerName, + d.deadlock_type, + d.event_date, + database_name = DB_NAME(d.database_id), + d.spid, + deadlock_group = + 'Deadlock #' + + CONVERT(nvarchar(10), d.en) + + ', Query #' + + CASE + WHEN d.qn = 0 + THEN N'1' + ELSE CONVERT(nvarchar(10), d.qn) + END + CASE + WHEN d.is_victim = 1 + THEN ' - VICTIM' + ELSE '' + END, + query = CONVERT(xml, N''), + d.object_names, + d.isolation_level, + d.owner_mode, + d.waiter_mode, + d.transaction_count, + d.login_name, + d.host_name, + d.client_app, + d.wait_time, + d.wait_resource, + d.priority, + d.log_used, + d.last_tran_started, + d.last_batch_started, + d.last_batch_completed, + d.transaction_name, + /*These columns will be NULL for regular (non-parallel) deadlocks*/ + d.owner_waiter_type, + d.owner_activity, + d.owner_waiter_activity, + d.owner_merging, + d.owner_spilling, + d.owner_waiting_to_close, + d.waiter_waiter_type, + d.waiter_owner_activity, + d.waiter_waiter_activity, + d.waiter_merging, + d.waiter_spilling, + d.waiter_waiting_to_close, + d.deadlock_graph + FROM deadlocks AS d + WHERE d.dn = 1 + AND (is_victim = @VictimsOnly + OR @VictimsOnly = 0) + AND d.qn < CASE + WHEN d.deadlock_type = N'Parallel Deadlock' + THEN 2 + ELSE 2147483647 + END + AND (DB_NAME(d.database_id) = @DatabaseName OR @DatabaseName IS NULL) + AND (d.event_date >= @StartDate OR @StartDate IS NULL) + AND (d.event_date < @EndDate OR @EndDate IS NULL) + AND (CONVERT(nvarchar(MAX), d.object_names) LIKE '%' + @ObjectName + '%' OR @ObjectName IS NULL) + AND (d.client_app = @AppName OR @AppName IS NULL) + AND (d.host_name = @HostName OR @HostName IS NULL) + AND (d.login_name = @LoginName OR @LoginName IS NULL) + ORDER BY + d.event_date, + is_victim DESC + OPTION (RECOMPILE, LOOP JOIN, HASH JOIN); - -- RAISERROR('att deadlock findings', 0, 1) WITH NOWAIT; - + DROP SYNONYM DeadLockTbl; + --done insert into blitzlock table going to insert into findings table first create synonym. - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Findings %s', 0, 1, @d) WITH NOWAIT; + -- RAISERROR('att deadlock findings', 0, 1) WITH NOWAIT; + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Findings %s', 0, 1, @d) WITH NOWAIT; - INSERT INTO DeadlockFindings (ServerName,check_id,database_name,object_name,finding_group,finding) - SELECT @ServerName,df.check_id, df.database_name, df.object_name, df.finding_group, df.finding - FROM #deadlock_findings AS df - ORDER BY df.check_id - OPTION (RECOMPILE); + INSERT INTO + DeadlockFindings + ( + ServerName, + check_id, + database_name, + object_name, + finding_group, + finding + ) + SELECT + @ServerName, + df.check_id, + df.database_name, + df.object_name, + df.finding_group, + df.finding + FROM #deadlock_findings AS df + ORDER BY df.check_id + OPTION (RECOMPILE); - DROP SYNONYM DeadlockFindings; --done with inserting. -END; -ELSE --Output to database is not set output to client app - BEGIN + DROP SYNONYM DeadlockFindings; --done with inserting. + END; + ELSE --Output to database is not set output to client app + BEGIN SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Results 1 %s', 0, 1, @d) WITH NOWAIT; - WITH deadlocks - AS ( SELECT N'Regular Deadlock' AS deadlock_type, - dp.event_date, - dp.id, - dp.victim_id, - dp.spid, - dp.database_id, - dp.priority, - dp.log_used, - dp.wait_resource COLLATE DATABASE_DEFAULT AS wait_resource, - CONVERT( - xml, - STUFF(( SELECT DISTINCT NCHAR(10) - + N' ' - + ISNULL(c.object_name, N'') - + N' ' COLLATE DATABASE_DEFAULT AS object_name - FROM #deadlock_owner_waiter AS c - WHERE ( dp.id = c.owner_id - OR dp.victim_id = c.waiter_id ) - AND dp.event_date = c.event_date - FOR xml PATH(N''), TYPE ).value(N'.[1]', N'nvarchar(4000)'), - 1, 1, N'')) AS object_names, - dp.wait_time, - dp.transaction_name, - dp.last_tran_started, - dp.last_batch_started, - dp.last_batch_completed, - dp.lock_mode, - dp.transaction_count, - dp.client_app, - dp.host_name, - dp.login_name, - dp.isolation_level, - dp.process_xml.value('(//process/inputbuf/text())[1]', 'nvarchar(MAX)') AS inputbuf, - DENSE_RANK() OVER ( ORDER BY dp.event_date ) AS en, - ROW_NUMBER() OVER ( PARTITION BY dp.event_date ORDER BY dp.event_date ) -1 AS qn, - ROW_NUMBER() OVER ( PARTITION BY dp.event_date, dp.id ORDER BY dp.event_date ) AS dn, - dp.is_victim, - ISNULL(dp.owner_mode, '-') AS owner_mode, - NULL AS owner_waiter_type, - NULL AS owner_activity, - NULL AS owner_waiter_activity, - NULL AS owner_merging, - NULL AS owner_spilling, - NULL AS owner_waiting_to_close, - ISNULL(dp.waiter_mode, '-') AS waiter_mode, - NULL AS waiter_waiter_type, - NULL AS waiter_owner_activity, - NULL AS waiter_waiter_activity, - NULL AS waiter_merging, - NULL AS waiter_spilling, - NULL AS waiter_waiting_to_close, - dp.deadlock_graph - FROM #deadlock_process AS dp + + WITH + deadlocks AS + ( + SELECT + deadlock_type = N'Regular Deadlock', + dp.event_date, + dp.id, + dp.victim_id, + dp.spid, + dp.database_id, + dp.priority, + dp.log_used, + wait_resource = dp.wait_resource COLLATE DATABASE_DEFAULT, + object_names = + CONVERT + ( + xml, + STUFF + ( + ( + SELECT DISTINCT + object_name = + NCHAR(10) + + N' ' + + ISNULL(c.object_name, N'') + + N' ' COLLATE DATABASE_DEFAULT + FROM #deadlock_owner_waiter AS c + WHERE (dp.id = c.owner_id + OR dp.victim_id = c.waiter_id) + AND dp.event_date = c.event_date + FOR XML + PATH(N''), + TYPE + ).value(N'.[1]', N'nvarchar(4000)'), + 1, + 1, + N'' + ) + ), + dp.wait_time, + dp.transaction_name, + dp.last_tran_started, + dp.last_batch_started, + dp.last_batch_completed, + dp.lock_mode, + dp.transaction_count, + dp.client_app, + dp.host_name, + dp.login_name, + dp.isolation_level, + inputbuf = dp.process_xml.value('(//process/inputbuf/text())[1]', 'nvarchar(MAX)'), + en = DENSE_RANK() OVER (ORDER BY dp.event_date), + qn = ROW_NUMBER() OVER (PARTITION BY dp.event_date ORDER BY dp.event_date) - 1, + dn = ROW_NUMBER() OVER (PARTITION BY dp.event_date, dp.id ORDER BY dp.event_date), + dp.is_victim, + owner_mode = ISNULL(dp.owner_mode, '-'), + owner_waiter_type = NULL, + owner_activity = NULL, + owner_waiter_activity = NULL, + owner_merging = NULL, + owner_spilling = NULL, + owner_waiting_to_close = NULL, + waiter_mode = ISNULL(dp.waiter_mode, '-'), + waiter_waiter_type = NULL, + waiter_owner_activity = NULL, + waiter_waiter_activity = NULL, + waiter_merging = NULL, + waiter_spilling = NULL, + waiter_waiting_to_close = NULL, + dp.deadlock_graph + FROM #deadlock_process AS dp WHERE dp.victim_id IS NOT NULL - AND dp.is_parallel = 0 + AND dp.is_parallel = 0 - UNION ALL - - SELECT N'Parallel Deadlock' AS deadlock_type, - dp.event_date, - dp.id, - dp.victim_id, - dp.spid, - dp.database_id, - dp.priority, - dp.log_used, - dp.wait_resource COLLATE DATABASE_DEFAULT, - CONVERT( - xml, - STUFF(( SELECT DISTINCT NCHAR(10) - + N' ' - + ISNULL(c.object_name, N'') - + N' ' COLLATE DATABASE_DEFAULT AS object_name - FROM #deadlock_owner_waiter AS c - WHERE ( dp.id = c.owner_id - OR dp.victim_id = c.waiter_id ) - AND dp.event_date = c.event_date - FOR xml PATH(N''), TYPE ).value(N'.[1]', N'nvarchar(4000)'), - 1, 1, N'')) AS object_names, - dp.wait_time, - dp.transaction_name, - dp.last_tran_started, - dp.last_batch_started, - dp.last_batch_completed, - dp.lock_mode, - dp.transaction_count, - dp.client_app, - dp.host_name, - dp.login_name, - dp.isolation_level, - dp.process_xml.value('(//process/inputbuf/text())[1]', 'nvarchar(MAX)') AS inputbuf, - DENSE_RANK() OVER ( ORDER BY dp.event_date ) AS en, - ROW_NUMBER() OVER ( PARTITION BY dp.event_date ORDER BY dp.event_date ) -1 AS qn, - ROW_NUMBER() OVER ( PARTITION BY dp.event_date, dp.id ORDER BY dp.event_date ) AS dn, - 1 AS is_victim, - cao.wait_type COLLATE DATABASE_DEFAULT AS owner_mode, - cao.waiter_type AS owner_waiter_type, - cao.owner_activity AS owner_activity, - cao.waiter_activity AS owner_waiter_activity, - cao.merging AS owner_merging, - cao.spilling AS owner_spilling, - cao.waiting_to_close AS owner_waiting_to_close, - caw.wait_type COLLATE DATABASE_DEFAULT AS waiter_mode, - caw.waiter_type AS waiter_waiter_type, - caw.owner_activity AS waiter_owner_activity, - caw.waiter_activity AS waiter_waiter_activity, - caw.merging AS waiter_merging, - caw.spilling AS waiter_spilling, - caw.waiting_to_close AS waiter_waiting_to_close, - dp.deadlock_graph - FROM #deadlock_process AS dp - OUTER APPLY (SELECT TOP 1 * FROM #deadlock_resource_parallel AS drp WHERE drp.owner_id = dp.id AND drp.wait_type IN ('e_waitPortOpen', 'e_waitPipeNewRow') ORDER BY drp.event_date) AS cao - OUTER APPLY (SELECT TOP 1 * FROM #deadlock_resource_parallel AS drp WHERE drp.owner_id = dp.id AND drp.wait_type IN ('e_waitPortOpen', 'e_waitPipeGetRow') ORDER BY drp.event_date) AS caw + UNION ALL + + SELECT + deadlock_type = N'Parallel Deadlock', + dp.event_date, + dp.id, + dp.victim_id, + dp.spid, + dp.database_id, + dp.priority, + dp.log_used, + dp.wait_resource COLLATE DATABASE_DEFAULT, + object_names = + CONVERT + ( + xml, + STUFF + ( + ( + SELECT DISTINCT + object_name = + NCHAR(10) + + N' ' + + ISNULL(c.object_name, N'') + + N' ' COLLATE DATABASE_DEFAULT + FROM #deadlock_owner_waiter AS c + WHERE (dp.id = c.owner_id + OR dp.victim_id = c.waiter_id) + AND dp.event_date = c.event_date + FOR XML + PATH(N''), + TYPE + ).value(N'.[1]', N'nvarchar(4000)'), + 1, + 1, + N'' + ) + ), + dp.wait_time, + dp.transaction_name, + dp.last_tran_started, + dp.last_batch_started, + dp.last_batch_completed, + dp.lock_mode, + dp.transaction_count, + dp.client_app, + dp.host_name, + dp.login_name, + dp.isolation_level, + inputbuf = dp.process_xml.value('(//process/inputbuf/text())[1]', 'nvarchar(MAX)'), + en = DENSE_RANK() OVER (ORDER BY dp.event_date), + qn = ROW_NUMBER() OVER (PARTITION BY dp.event_date ORDER BY dp.event_date) - 1, + dn = ROW_NUMBER() OVER (PARTITION BY dp.event_date, dp.id ORDER BY dp.event_date), + is_victim = 1, + owner_mode = cao.wait_type COLLATE DATABASE_DEFAULT, + owner_waiter_type = cao.waiter_type, + owner_activity = cao.owner_activity, + owner_waiter_activity = cao.waiter_activity, + owner_merging = cao.merging, + owner_spilling = cao.spilling, + owner_waiting_to_close = cao.waiting_to_close, + waiter_mode = caw.wait_type COLLATE DATABASE_DEFAULT, + waiter_waiter_type = caw.waiter_type, + waiter_owner_activity = caw.owner_activity, + waiter_waiter_activity = caw.waiter_activity, + waiter_merging = caw.merging, + waiter_spilling = caw.spilling, + waiter_waiting_to_close = caw.waiting_to_close, + dp.deadlock_graph + FROM #deadlock_process AS dp + OUTER APPLY + ( + SELECT TOP (1) + * + FROM #deadlock_resource_parallel AS drp + WHERE drp.owner_id = dp.id + AND drp.wait_type IN + ( + 'e_waitPortOpen', + 'e_waitPipeNewRow' + ) + ORDER BY drp.event_date + ) AS cao + OUTER APPLY + ( + SELECT TOP (1) + * + FROM #deadlock_resource_parallel AS drp + WHERE drp.owner_id = dp.id + AND drp.wait_type IN + ( + 'e_waitPortOpen', + 'e_waitPipeGetRow' + ) + ORDER BY drp.event_date + ) AS caw WHERE dp.is_parallel = 1 - ) - SELECT d.deadlock_type, + ) + SELECT + d.deadlock_type, d.event_date, - DB_NAME(d.database_id) AS database_name, + database_name = DB_NAME(d.database_id), d.spid, - 'Deadlock #' - + CONVERT(nvarchar(10), d.en) - + ', Query #' - + CASE WHEN d.qn = 0 THEN N'1' ELSE CONVERT(nvarchar(10), d.qn) END - + CASE WHEN d.is_victim = 1 THEN ' - VICTIM' ELSE '' END - AS deadlock_group, - CONVERT(xml, N'') AS query_xml, - d.inputbuf AS query_string, + deadlock_group = + 'Deadlock #' + + CONVERT + ( + nvarchar(10), + d.en + ) + + ', Query #' + + CASE + WHEN d.qn = 0 + THEN N'1' + ELSE CONVERT(nvarchar(10), d.qn) + END + CASE + WHEN d.is_victim = 1 + THEN ' - VICTIM' + ELSE '' + END, + query_xml = CONVERT(xml, N''), + query_string = d.inputbuf, d.object_names, d.isolation_level, d.owner_mode, @@ -2038,9 +2557,14 @@ ELSE --Output to database is not set output to client app d.is_victim INTO #deadlock_results FROM deadlocks AS d - WHERE d.dn = 1 - AND (d.is_victim = @VictimsOnly OR @VictimsOnly = 0) - AND d.qn < CASE WHEN d.deadlock_type = N'Parallel Deadlock' THEN 2 ELSE 2147483647 END + WHERE d.dn = 1 + AND (d.is_victim = @VictimsOnly + OR @VictimsOnly = 0) + AND d.qn < CASE + WHEN d.deadlock_type = N'Parallel Deadlock' + THEN 2 + ELSE 2147483647 + END AND (DB_NAME(d.database_id) = @DatabaseName OR @DatabaseName IS NULL) AND (d.event_date >= @StartDate OR @StartDate IS NULL) AND (d.event_date < @EndDate OR @EndDate IS NULL) @@ -2048,10 +2572,7 @@ ELSE --Output to database is not set output to client app AND (d.client_app = @AppName OR @AppName IS NULL) AND (d.host_name = @HostName OR @HostName IS NULL) AND (d.login_name = @LoginName OR @LoginName IS NULL) - OPTION (RECOMPILE); - - - DECLARE @deadlock_result nvarchar(MAX) = N''; + OPTION (RECOMPILE, LOOP JOIN, HASH JOIN); SET @deadlock_result += N' SELECT @@ -2060,15 +2581,13 @@ ELSE --Output to database is not set output to client app dr.database_name, dr.spid, dr.deadlock_group, - ' - + CASE @ExportToExcel - WHEN 1 - THEN N'dr.query_string AS query, + ' + CASE @ExportToExcel + WHEN 1 + THEN N'dr.query_string AS query, REPLACE(REPLACE(CONVERT(nvarchar(MAX), dr.object_names), '''', ''''), '''', '''') AS object_names,' - ELSE N'dr.query_xml AS query, + ELSE N'dr.query_xml AS query, dr.object_names,' - END + - N' + END + N' dr.isolation_level, dr.owner_mode, dr.waiter_mode, @@ -2095,25 +2614,34 @@ ELSE --Output to database is not set output to client app dr.waiter_waiter_activity, dr.waiter_merging, dr.waiter_spilling, - dr.waiter_waiting_to_close' - + CASE @ExportToExcel - WHEN 1 - THEN N'' - ELSE N', - dr.deadlock_graph' - END + - ' + dr.waiter_waiting_to_close' + + CASE + @ExportToExcel + WHEN 1 + THEN N'' + ELSE N', + dr.deadlock_graph' + END + + N' FROM #deadlock_results AS dr ORDER BY dr.event_date, dr.is_victim DESC - OPTION(RECOMPILE); + OPTION(RECOMPILE, LOOP JOIN, HASH JOIN); '; + EXEC sys.sp_executesql - @deadlock_result; + @deadlock_result; + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Findings %s', 0, 1, @d) WITH NOWAIT; - SELECT df.check_id, df.database_name, df.object_name, df.finding_group, df.finding + SELECT + df.check_id, + df.database_name, + df.object_name, + df.finding_group, + df.finding FROM #deadlock_findings AS df ORDER BY df.check_id OPTION (RECOMPILE); @@ -2123,40 +2651,41 @@ ELSE --Output to database is not set output to client app END; --done with output to client app. - IF @Debug = 1 - BEGIN + BEGIN + SELECT table_name = '#deadlock_data', * FROM #deadlock_data AS dd + OPTION (RECOMPILE); - SELECT '#deadlock_data' AS table_name, * - FROM #deadlock_data AS dd - OPTION (RECOMPILE); - SELECT '#deadlock_resource' AS table_name, * - FROM #deadlock_resource AS dr - OPTION (RECOMPILE); + SELECT table_name = '#deadlock_resource', * FROM #deadlock_resource AS dr + OPTION (RECOMPILE); - SELECT '#deadlock_resource_parallel' AS table_name, * - FROM #deadlock_resource_parallel AS drp - OPTION (RECOMPILE); - SELECT '#deadlock_owner_waiter' AS table_name, * - FROM #deadlock_owner_waiter AS dow - OPTION (RECOMPILE); + SELECT + table_name = '#deadlock_resource_parallel', + * + FROM #deadlock_resource_parallel AS drp + OPTION (RECOMPILE); + + + SELECT + table_name = '#deadlock_owner_waiter', + * + FROM #deadlock_owner_waiter AS dow + OPTION (RECOMPILE); - SELECT '#deadlock_process' AS table_name, * - FROM #deadlock_process AS dp - OPTION (RECOMPILE); - SELECT '#deadlock_stack' AS table_name, * - FROM #deadlock_stack AS ds - OPTION (RECOMPILE); + SELECT table_name = '#deadlock_process', * FROM #deadlock_process AS dp + OPTION (RECOMPILE); - SELECT '#deadlock_results' AS table_name, * - FROM #deadlock_results AS dr - OPTION (RECOMPILE); - END; -- End debug + SELECT table_name = '#deadlock_stack', * FROM #deadlock_stack AS ds + OPTION (RECOMPILE); + + SELECT table_name = '#deadlock_results', * FROM #deadlock_results AS dr + OPTION (RECOMPILE); + END; -- End debug END; --Final End GO From eab0b556a9c725905b64811167940938cd25f299 Mon Sep 17 00:00:00 2001 From: Christian Specht Date: Fri, 11 Nov 2022 23:44:56 +0100 Subject: [PATCH 328/662] Fix date conversion, so it works in all languages Fixes #3161 --- sp_Blitz.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index 49e4e53bd..568939264 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -1090,7 +1090,7 @@ AS ) OR ( - Convert(datetime,ll.Value) < DATEADD(dd,-7, GETDATE()) + Convert(datetime,ll.Value,21) < DATEADD(dd,-7, GETDATE()) ) ); From a729369c5b81965580d8616ff9927198a780f6b1 Mon Sep 17 00:00:00 2001 From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com> Date: Fri, 11 Nov 2022 18:19:55 -0500 Subject: [PATCH 329/662] Update sp_BlitzLock.sql WIP --- sp_BlitzLock.sql | 381 +++++++++++++++++++++++++---------------------- 1 file changed, 205 insertions(+), 176 deletions(-) diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index e5a804bcf..e20849dc4 100644 --- a/sp_BlitzLock.sql +++ b/sp_BlitzLock.sql @@ -1,11 +1,13 @@ IF OBJECT_ID('dbo.sp_BlitzLock') IS NULL +BEGIN EXEC ('CREATE PROCEDURE dbo.sp_BlitzLock AS RETURN 0;'); +END; GO -ALTER PROCEDURE dbo.sp_BlitzLock +ALTER PROCEDURE + dbo.sp_BlitzLock ( - @Top bigint = 9223372036854775807, @DatabaseName nvarchar(256) = NULL, @StartDate datetime = '19000101', @EndDate datetime = '99991231', @@ -22,9 +24,9 @@ ALTER PROCEDURE dbo.sp_BlitzLock @Version varchar(30) = NULL OUTPUT, @VersionDate datetime = NULL OUTPUT, @VersionCheckMode bit = 0, - @OutputDatabaseName nvarchar(256) = NULL, - @OutputSchemaName nvarchar(256) = 'dbo', --ditto as below - @OutputTableName nvarchar(256) = 'BlitzLock', --put a standard here no need to check later in the script + @OutputDatabaseName sysname = NULL, + @OutputSchemaName sysname = 'dbo', --ditto as below + @OutputTableName sysname = 'BlitzLock', --put a standard here no need to check later in the script @ExportToExcel bit = 0 ) WITH RECOMPILE @@ -155,7 +157,7 @@ BEGIN THEN 1 ELSE 0 END, - @RDS bigint = + @RDS bit = CASE WHEN LEFT(CAST(SERVERPROPERTY('ComputerNamePhysicalNetBIOS') AS varchar(8000)), 8) <> 'EC2AMAZ-' AND LEFT(CAST(SERVERPROPERTY('MachineName') AS varchar(8000)), 8) <> 'EC2AMAZ-' @@ -166,7 +168,7 @@ BEGIN @d varchar(40) = '', @StringToExecute nvarchar(4000) = N'', @StringToExecuteParams nvarchar(500) = N'', - @r nvarchar(200) = N'', + @r sysname = N'', @OutputTableFindings nvarchar(100) = N'[BlitzLockFindings]', @DeadlockCount int = 0, @ServerName nvarchar(256) = @@SERVERNAME, @@ -175,7 +177,7 @@ BEGIN @TargetSessionId int, @FileName nvarchar(4000), @NC10 nvarchar(1) = CONVERT(nvarchar(1), 0x0a00, 0), - @deadlock_result nvarchar(MAX) = N''; + @deadlock_result nvarchar(MAX) = N''; DECLARE @sysAssObjId AS table @@ -201,15 +203,37 @@ BEGIN event_date datetime ); - IF @StartDate IS NULL - BEGIN - SET @StartDate = '19000101'; - END; + CREATE TABLE + #t + ( + id int NOT NULL + ); - IF @EndDate IS NULL - BEGIN - SET @EndDate = '99991231'; - END; + SET @StartDate = + DATEADD + ( + MINUTE, + DATEDIFF + ( + MINUTE, + SYSDATETIME(), + GETUTCDATE() + ), + ISNULL(@StartDate, '19000101') + ); + + SET @EndDate = + DATEADD + ( + MINUTE, + DATEDIFF + ( + MINUTE, + SYSDATETIME(), + GETUTCDATE() + ), + ISNULL(@EndDate, '99991231') + ); CREATE TABLE #deadlock_findings @@ -222,19 +246,20 @@ BEGIN finding nvarchar(4000) ); - SET @d = CONVERT(varchar(40), GETDATE(), 109); - IF (@OutputDatabaseName IS NOT NULL) BEGIN --if databaseName is set do some sanity checks and put [] around def. - IF NOT EXISTS + RAISERROR('@OutputDatabaseName set to %s, checking validity', 0, 1, @OutputDatabaseName) WITH NOWAIT; + SET @d = CONVERT(varchar(40), GETDATE(), 109); + + IF NOT EXISTS ( SELECT 1/0 - FROM sys.databases WHERE name = @OutputDatabaseName + FROM sys.databases AS d + WHERE d.name = @OutputDatabaseName ) --if database is invalid raiserror and set bitcheck BEGIN - RAISERROR('Database Name for output of table is invalid please correct, Output to Table will not be preformed', 0, 1, @d) WITH NOWAIT; - + RAISERROR('Database Name (%s) for output of table is invalid please, Output to Table will not be performed', 0, 1, @OutputDatabaseName) WITH NOWAIT; SET @OutputDatabaseCheck = -1; -- -1 invalid/false, 0 = good/true END; ELSE @@ -247,14 +272,17 @@ BEGIN N'' + @OutputDatabaseName + N'' + - N'.sys.objects WHERE type_desc = ''USER_TABLE'' AND name= ' + + N'.sys.objects WHERE type_desc = ''USER_TABLE'' AND name = ' + N'''' + @OutputTableName + - N'''', + N'''' + + N' AND schema_id = SCHEMA_ID(' + + N'''' + + @OutputSchemaName + + N'''' + + N');', @StringToExecuteParams = - N'@OutputDatabaseName nvarchar(200), - @OutputTableName nvarchar(200), - @r nvarchar(200) OUTPUT'; + N'@r sysname OUTPUT'; EXEC sys.sp_executesql @StringToExecute, @@ -272,9 +300,15 @@ BEGIN IF (@r IS NOT NULL) --if it is not null, there is a table, so check for newly added columns BEGIN /* If the table doesn't have the new spid column, add it. See Github #3101. */ - SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableName; + SET @ObjectFullName = + @OutputDatabaseName + + N'.' + + @OutputSchemaName + + N'.' + + @OutputTableName; + SET @StringToExecute = - N'IF NOT EXISTS (SELECT * FROM ' + + N'IF NOT EXISTS (SELECT 1/0 FROM ' + @OutputDatabaseName + N'.sys.all_columns WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + @@ -288,23 +322,16 @@ BEGIN @StringToExecute; /* If the table doesn't have the new wait_resource column, add it. See Github #3101. */ - SET @ObjectFullName = - @OutputDatabaseName + - N'.' + - @OutputSchemaName + - N'.' + - @OutputTableName; - SET @StringToExecute = - N'IF NOT EXISTS (SELECT * FROM ' + - @OutputDatabaseName + - N'.sys.all_columns WHERE object_id = (OBJECT_ID(''' + - @ObjectFullName + - N''')) AND name = ''wait_resource'') - /*Add wait_resource column*/ - ALTER TABLE ' + - @ObjectFullName + - N' ADD wait_resource nvarchar(MAX) NULL;'; + N'IF NOT EXISTS (SELECT 1/0 FROM ' + + @OutputDatabaseName + + N'.sys.all_columns WHERE object_id = (OBJECT_ID(''' + + @ObjectFullName + + N''')) AND name = ''wait_resource'') + /*Add wait_resource column*/ + ALTER TABLE ' + + @ObjectFullName + + N' ADD wait_resource nvarchar(MAX) NULL;'; EXEC sp_executesql @StringToExecute; @@ -357,18 +384,10 @@ BEGIN waiter_spilling nvarchar(256), waiter_waiting_to_close nvarchar(256), deadlock_graph xml - )', - @StringToExecuteParams = - N'@OutputDatabaseName nvarchar(200), - @OutputSchemaName nvarchar(100), - @OutputTableName nvarchar(200)'; + )'; EXEC sys.sp_executesql - @StringToExecute, - @StringToExecuteParams, - @OutputDatabaseName, - @OutputSchemaName, - @OutputTableName; + @StringToExecute; --table created. SELECT @@ -379,19 +398,18 @@ BEGIN N'' + N'.sys.objects WHERE type_desc = ''USER_TABLE'' AND name = ''BlitzLockFindings''', @StringToExecuteParams = - N'@OutputDatabaseName nvarchar(200), - @r nvarchar(200) OUTPUT'; + N'@r sysname OUTPUT'; EXEC sys.sp_executesql @StringToExecute, @StringToExecuteParams, - @OutputDatabaseName, @r OUTPUT; IF (@r IS NULL) --if table does not exist BEGIN SELECT - @OutputTableFindings = N'[BlitzLockFindings]', + @OutputTableFindings = + QUOTENAME(N'BlitzLockFindings'), @StringToExecute = N'USE ' + @OutputDatabaseName + @@ -407,18 +425,10 @@ BEGIN object_name nvarchar(1000), finding_group nvarchar(100), finding nvarchar(4000) - );', - @StringToExecuteParams = - N'@OutputDatabaseName nvarchar(200), - @OutputSchemaName nvarchar(100), - @OutputTableFindings nvarchar(200)'; + );'; EXEC sys.sp_executesql - @StringToExecute, - @StringToExecuteParams, - @OutputDatabaseName, - @OutputSchemaName, - @OutputTableFindings; + @StringToExecute; END; END; @@ -428,22 +438,23 @@ BEGIN ( SELECT 1/0 - FROM sys.objects - WHERE name = 'DeadlockFindings' - AND type_desc = 'SYNONYM' + FROM sys.objects AS o + WHERE o.name = 'DeadlockFindings' + AND o.type_desc = 'SYNONYM' ) BEGIN - RAISERROR('Found Synonym', 0, 1) WITH NOWAIT; + RAISERROR('Found synonym DeadlockFindings, dropping', 0, 1) WITH NOWAIT; DROP SYNONYM DeadlockFindings; END; + RAISERROR('Creating synonym DeadlockFindings', 0, 1) WITH NOWAIT; SET @StringToExecute = - N'CREATE SYNONYM DeadlockFindings FOR ' + - @OutputDatabaseName + - N'.' + - @OutputSchemaName + - N'.' + - @OutputTableFindings; + N'CREATE SYNONYM DeadlockFindings FOR ' + + @OutputDatabaseName + + N'.' + + @OutputSchemaName + + N'.' + + @OutputTableFindings; EXEC sys.sp_executesql @StringToExecute; @@ -453,21 +464,23 @@ BEGIN ( SELECT 1/0 - FROM sys.objects - WHERE name = 'DeadLockTbl' - AND type_desc = 'SYNONYM' + FROM sys.objects AS o + WHERE o.name = 'DeadLockTbl' + AND o.type_desc = 'SYNONYM' ) BEGIN + RAISERROR('Found synonym DeadLockTbl, dropping', 0, 1) WITH NOWAIT; DROP SYNONYM DeadLockTbl; END; + RAISERROR('Creating synonym DeadLockTbl', 0, 1) WITH NOWAIT; SET @StringToExecute = - N'CREATE SYNONYM DeadLockTbl FOR ' + - @OutputDatabaseName + - N'.' + - @OutputSchemaName + - N'.' + - @OutputTableName; + N'CREATE SYNONYM DeadLockTbl FOR ' + + @OutputDatabaseName + + N'.' + + @OutputSchemaName + + N'.' + + @OutputTableName; EXEC sys.sp_executesql @StringToExecute; @@ -475,20 +488,15 @@ BEGIN END; - CREATE TABLE - #t - ( - id int NOT NULL - ); - - /* WITH ROWCOUNT doesn't work on Amazon RDS - see: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2037 */ IF @RDS = 0 BEGIN; BEGIN TRY; - UPDATE STATISTICS #t + RAISERROR('@RDS = 0, updating #t with high row and page counts', 0, 1) WITH NOWAIT; + UPDATE STATISTICS + #t WITH - ROWCOUNT = 100000000, + ROWCOUNT = 100000000, PAGECOUNT = 100000000; END TRY BEGIN CATCH; @@ -497,8 +505,6 @@ BEGIN IF (ERROR_NUMBER() = 1088) BEGIN; SET @d = CONVERT(varchar(40), GETDATE(), 109); - - RAISERROR('Cannot run UPDATE STATISTICS on a #temp table without db_owner or sysadmin permissions %s', 0, 1, @d) WITH NOWAIT; END; ELSE @@ -508,11 +514,11 @@ BEGIN END CATCH; END; - IF @TargetSessionType IS NULL BEGIN IF @Azure = 0 BEGIN + RAISERROR('@TargetSessionType is NULL, assigning for non-Azure instance', 0, 1) WITH NOWAIT; SELECT TOP (1) @TargetSessionType = t.target_name FROM sys.dm_xe_sessions AS s @@ -523,6 +529,7 @@ BEGIN END; IF @Azure = 1 + RAISERROR('@TargetSessionType is NULL, assigning for Azure instance', 0, 1) WITH NOWAIT; BEGIN SELECT TOP (1) @TargetSessionType = t.target_name @@ -534,7 +541,6 @@ BEGIN END; END; - IF ( @TargetSessionType LIKE 'ring%' @@ -543,8 +549,10 @@ BEGIN BEGIN IF @Azure = 0 BEGIN + RAISERROR('@TargetSessionType is ring_buffer, inserting XML for non-Azure', 0, 1) WITH NOWAIT; + INSERT - #x WITH (TABLOCK) + #x WITH(TABLOCKX) ( x ) @@ -554,14 +562,18 @@ BEGIN JOIN sys.dm_xe_sessions AS s ON s.address = t.event_session_address WHERE s.name = @EventSessionName - AND t.target_name = N'ring_buffer'; - END; + AND t.target_name = N'ring_buffer'; + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + END; IF @Azure = 1 BEGIN + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('@TargetSessionType is ring_buffer, inserting XML for Azure', 0, 1) WITH NOWAIT; + INSERT - #x WITH (TABLOCK) + #x WITH(TABLOCKX) ( x ) @@ -571,7 +583,9 @@ BEGIN JOIN sys.dm_xe_database_sessions AS s ON s.address = t.event_session_address WHERE s.name = @EventSessionName - AND t.target_name = N'ring_buffer'; + AND t.target_name = N'ring_buffer'; + + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; END; END; @@ -584,6 +598,7 @@ BEGIN BEGIN IF @Azure = 0 BEGIN + RAISERROR('@TargetSessionType is event_file, assigning XML for non-Azure', 0, 1) WITH NOWAIT; SELECT @SessionId = t.event_session_id, @TargetSessionId = t.target_id @@ -591,9 +606,9 @@ BEGIN JOIN sys.server_event_sessions AS s ON s.event_session_id = t.event_session_id WHERE t.name = @TargetSessionType - AND s.name = @EventSessionName; - + AND s.name = @EventSessionName; + RAISERROR('Assigning @FileName...', 0, 1) WITH NOWAIT; SELECT @FileName = CASE @@ -607,14 +622,15 @@ BEGIN file_name = CONVERT(nvarchar(4000), f.value) FROM sys.server_event_session_fields AS f WHERE f.event_session_id = @SessionId - AND f.object_id = @TargetSessionId - AND f.name = N'filename' + AND f.object_id = @TargetSessionId + AND f.name = N'filename' ) AS f; END; IF @Azure = 1 BEGIN + RAISERROR('@TargetSessionType is event_ile, assigning XML for Azure', 0, 1) WITH NOWAIT; SELECT @SessionId = t.event_session_id, @TargetSessionId = t.target_id @@ -622,9 +638,9 @@ BEGIN JOIN sys.dm_xe_database_sessions AS s ON s.event_session_id = t.event_session_id WHERE t.name = @TargetSessionType - AND s.name = @EventSessionName; - + AND s.name = @EventSessionName; + RAISERROR('Assigning @FileName...', 0, 1) WITH NOWAIT; SELECT @FileName = CASE @@ -638,22 +654,32 @@ BEGIN file_name = CONVERT(nvarchar(4000), f.value) FROM sys.server_event_session_fields AS f WHERE f.event_session_id = @SessionId - AND f.object_id = @TargetSessionId - AND f.name = N'filename' + AND f.object_id = @TargetSessionId + AND f.name = N'filename' ) AS f; END; + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Reading for event_file %s', 0, 1, @FileName) WITH NOWAIT; INSERT - #x WITH (TABLOCK) + #x WITH(TABLOCKX) ( x ) SELECT x = TRY_CAST(f.event_data AS xml) - FROM sys.fn_xe_file_target_read_file(@FileName, NULL, NULL, NULL) AS f; + FROM sys.fn_xe_file_target_read_file(@FileName, NULL, NULL, NULL) AS f + LEFT JOIN #t AS t + ON 1 = 1; + + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; END; + IF @Debug = 1 + BEGIN + SELECT table_name = N'#x', bx.* FROM #x AS bx; + END; IF ( @@ -661,6 +687,9 @@ BEGIN AND @EventSessionName NOT LIKE 'system_health%' ) BEGIN + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Inserting to #deadlock_data for ring buffer data', 0, 1) WITH NOWAIT; + INSERT #deadlock_data WITH(TABLOCK) ( @@ -669,7 +698,14 @@ BEGIN SELECT deadlock_xml = e.x.query('.') FROM #x AS x - CROSS APPLY x.x.nodes('/RingBufferTarget/event') AS e(x); + LEFT JOIN #t AS t + ON 1 = 1 + CROSS APPLY x.x.nodes('/RingBufferTarget/event') AS e(x) + WHERE e.x.exist('/event/@name[ .= "xml_deadlock_report"]') = 1 + AND e.x.exist('/event/@timestamp[. >= sql:variable("@StartDate")]') = 1 + AND e.x.value('/event/@timestamp[. >= sql:variable("@EndDate")]') = 1; + + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; END; @@ -679,6 +715,9 @@ BEGIN AND @EventSessionName NOT LIKE 'system_health%' ) BEGIN + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Inserting to #deadlock_data for event file data', 0, 1) WITH NOWAIT + INSERT #deadlock_data WITH(TABLOCK) ( @@ -687,16 +726,21 @@ BEGIN SELECT deadlock_xml = e.x.query('.') FROM #x AS x - CROSS APPLY x.x.nodes('/event') AS e(x); - END; + LEFT JOIN #t AS t + ON 1 = 1 + CROSS APPLY x.x.nodes('/event') AS e(x) + WHERE e.x.exist('/event/@name[ .= "xml_deadlock_report"]') = 1 + AND e.x.exist('/event/@timestamp[. >= sql:variable("@StartDate")]') = 1 + AND e.x.value('/event/@timestamp[. >= sql:variable("@EndDate")]') = 1; + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + END; IF @Debug = 1 BEGIN SELECT table_name = N'#deadlock_data', bx.* FROM #deadlock_data AS bx; END; - /*Grab the initial set of xml to parse*/ IF ( @@ -715,36 +759,20 @@ BEGIN FROM sys.fn_xe_file_target_read_file('system_health*.xel', NULL, NULL, NULL) ) INSERT - #deadlock_data WITH (TABLOCK) + #deadlock_data WITH(TABLOCKX) SELECT - deadlock_xml = xml.deadlock_xml, - event_date = xml.deadlock_xml.value('(/event/@timestamp)[1]', 'datetime') + deadlock_xml = xml.deadlock_xml FROM xml LEFT JOIN #t AS t ON 1 = 1 - CROSS APPLY xml.deadlock_xml.nodes('/event/@name') AS x(c) - WHERE x.c.exist('/event/@name[ .= "xml_deadlock_report"]') = 1 - AND xml.deadlock_xml.value('(/event/@timestamp)[1]', 'datetime') >= DATEADD(MINUTE, DATEDIFF(MINUTE, GETUTCDATE(), SYSDATETIME()), @StartDate) - AND xml.deadlock_xml.value('(/event/@timestamp)[1]', 'datetime') < DATEADD(MINUTE, DATEDIFF(MINUTE, GETUTCDATE(), SYSDATETIME()), @EndDate) - OPTION (RECOMPILE); - - SET @DeadlockCount = @@ROWCOUNT; - - /*Optimization: if we got back more rows than @Top, remove them. This seems to be better than using @Top in the query above as that results in excessive memory grant*/ - IF (@Top < @DeadlockCount) - BEGIN - WITH - t AS - ( - SELECT TOP (@DeadlockCount - @Top) - dd.* - FROM #deadlock_data AS dd - ORDER BY dd.event_date ASC - ) - DELETE - FROM t; - END; + CROSS APPLY xml.deadlock_xml.nodes('/event/@name') AS e(x) + WHERE e.x.exist('/event/@name[ .= "xml_deadlock_report"]') = 1 + AND e.x.exist('/event/@timestamp[. >= sql:variable("@StartDate")]') = 1 + AND e.x.value('/event/@timestamp[. >= sql:variable("@EndDate")]') = 1 + OPTION(RECOMPILE); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + END; /*Parse process and input buffer xml*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); @@ -2012,6 +2040,7 @@ BEGIN CREATE CLUSTERED INDEX cx_whatever ON #deadlock_process (event_date, id); CREATE CLUSTERED INDEX cx_whatever ON #deadlock_resource_parallel (event_date, owner_id); CREATE CLUSTERED INDEX cx_whatever ON #deadlock_owner_waiter (event_date, owner_id, waiter_id); + IF (@OutputDatabaseCheck = 0) BEGIN SET @d = CONVERT(varchar(40), GETDATE(), 109); @@ -2229,9 +2258,9 @@ BEGIN database_name = DB_NAME(d.database_id), d.spid, deadlock_group = - 'Deadlock #' + - CONVERT(nvarchar(10), d.en) + - ', Query #' + 'Deadlock #' + + CONVERT(nvarchar(10), d.en) + + ', Query #' + CASE WHEN d.qn = 0 THEN N'1' @@ -2342,16 +2371,16 @@ BEGIN dp.log_used, wait_resource = dp.wait_resource COLLATE DATABASE_DEFAULT, object_names = - CONVERT - ( + CONVERT + ( xml, STUFF - ( + ( ( SELECT DISTINCT object_name = - NCHAR(10) + - N' ' + + NCHAR(10) + + N' ' + ISNULL(c.object_name, N'') + N' ' COLLATE DATABASE_DEFAULT FROM #deadlock_owner_waiter AS c @@ -2359,8 +2388,8 @@ BEGIN OR dp.victim_id = c.waiter_id) AND dp.event_date = c.event_date FOR XML - PATH(N''), - TYPE + PATH(N''), + TYPE ).value(N'.[1]', N'nvarchar(4000)'), 1, 1, @@ -2402,9 +2431,9 @@ BEGIN WHERE dp.victim_id IS NOT NULL AND dp.is_parallel = 0 - UNION ALL + UNION ALL - SELECT + SELECT deadlock_type = N'Parallel Deadlock', dp.event_date, dp.id, @@ -2415,16 +2444,16 @@ BEGIN dp.log_used, dp.wait_resource COLLATE DATABASE_DEFAULT, object_names = - CONVERT - ( + CONVERT + ( xml, STUFF - ( + ( ( SELECT DISTINCT object_name = - NCHAR(10) + - N' ' + + NCHAR(10) + + N' ' + ISNULL(c.object_name, N'') + N' ' COLLATE DATABASE_DEFAULT FROM #deadlock_owner_waiter AS c @@ -2432,8 +2461,8 @@ BEGIN OR dp.victim_id = c.waiter_id) AND dp.event_date = c.event_date FOR XML - PATH(N''), - TYPE + PATH(N''), + TYPE ).value(N'.[1]', N'nvarchar(4000)'), 1, 1, @@ -2481,7 +2510,7 @@ BEGIN AND drp.wait_type IN ( 'e_waitPortOpen', - 'e_waitPipeNewRow' + 'e_waitPipeNewRow' ) ORDER BY drp.event_date ) AS cao @@ -2494,7 +2523,7 @@ BEGIN AND drp.wait_type IN ( 'e_waitPortOpen', - 'e_waitPipeGetRow' + 'e_waitPipeGetRow' ) ORDER BY drp.event_date ) AS caw @@ -2506,13 +2535,13 @@ BEGIN database_name = DB_NAME(d.database_id), d.spid, deadlock_group = - 'Deadlock #' + - CONVERT - ( - nvarchar(10), - d.en - ) + - ', Query #' + 'Deadlock #' + + CONVERT + ( + nvarchar(10), + d.en + ) + + ', Query #' + CASE WHEN d.qn = 0 THEN N'1' @@ -2615,8 +2644,8 @@ BEGIN dr.waiter_merging, dr.waiter_spilling, dr.waiter_waiting_to_close' + - CASE - @ExportToExcel + CASE + @ExportToExcel WHEN 1 THEN N'' ELSE N', @@ -2630,7 +2659,7 @@ BEGIN EXEC sys.sp_executesql - @deadlock_result; + @deadlock_result; SET @d = CONVERT(varchar(40), GETDATE(), 109); From ee809750a9179a4d136a4e08191f09d641a3a3dd Mon Sep 17 00:00:00 2001 From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com> Date: Fri, 11 Nov 2022 19:28:52 -0500 Subject: [PATCH 330/662] Update sp_BlitzLock.sql WIP --- sp_BlitzLock.sql | 120 ++++++++++++++++++++++++----------------------- 1 file changed, 62 insertions(+), 58 deletions(-) diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index e20849dc4..a0899aada 100644 --- a/sp_BlitzLock.sql +++ b/sp_BlitzLock.sql @@ -192,15 +192,14 @@ BEGIN CREATE TABLE #x ( - x xml + x xml NOT NULL DEFAULT ' ' ); CREATE TABLE #deadlock_data ( - deadlock_xml xml, - event_date datetime + deadlock_xml xml NOT NULL DEFAULT ' ' ); CREATE TABLE @@ -251,7 +250,7 @@ BEGIN RAISERROR('@OutputDatabaseName set to %s, checking validity', 0, 1, @OutputDatabaseName) WITH NOWAIT; SET @d = CONVERT(varchar(40), GETDATE(), 109); - IF NOT EXISTS + IF NOT EXISTS ( SELECT 1/0 @@ -496,8 +495,8 @@ BEGIN UPDATE STATISTICS #t WITH - ROWCOUNT = 100000000, - PAGECOUNT = 100000000; + ROWCOUNT = 9223372036854775807, + PAGECOUNT = 9223372036854775807; END TRY BEGIN CATCH; /* Misleading error returned, if run without permissions to update statistics the error returned is "Cannot find object". @@ -691,7 +690,7 @@ BEGIN RAISERROR('Inserting to #deadlock_data for ring buffer data', 0, 1) WITH NOWAIT; INSERT - #deadlock_data WITH(TABLOCK) + #deadlock_data WITH(TABLOCKX) ( deadlock_xml ) @@ -703,7 +702,7 @@ BEGIN CROSS APPLY x.x.nodes('/RingBufferTarget/event') AS e(x) WHERE e.x.exist('/event/@name[ .= "xml_deadlock_report"]') = 1 AND e.x.exist('/event/@timestamp[. >= sql:variable("@StartDate")]') = 1 - AND e.x.value('/event/@timestamp[. >= sql:variable("@EndDate")]') = 1; + AND e.x.exist('/event/@timestamp[. < sql:variable("@EndDate")]') = 1; RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; END; @@ -719,7 +718,7 @@ BEGIN RAISERROR('Inserting to #deadlock_data for event file data', 0, 1) WITH NOWAIT INSERT - #deadlock_data WITH(TABLOCK) + #deadlock_data WITH(TABLOCKX) ( deadlock_xml ) @@ -731,7 +730,7 @@ BEGIN CROSS APPLY x.x.nodes('/event') AS e(x) WHERE e.x.exist('/event/@name[ .= "xml_deadlock_report"]') = 1 AND e.x.exist('/event/@timestamp[. >= sql:variable("@StartDate")]') = 1 - AND e.x.value('/event/@timestamp[. >= sql:variable("@EndDate")]') = 1; + AND e.x.exist('/event/@timestamp[. < sql:variable("@EndDate")]') = 1; RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; END; @@ -751,12 +750,24 @@ BEGIN SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Grab the initial set of xml to parse at %s', 0, 1, @d) WITH NOWAIT; + SET STATISTICS XML ON; + + SELECT + deadlock_xml = TRY_CAST(event_data AS xml) + INTO #xml + FROM sys.fn_xe_file_target_read_file('system_health*.xel', NULL, NULL, NULL) + LEFT JOIN #t AS t + ON 1 = 1; + WITH xml AS ( SELECT - deadlock_xml = TRY_CAST(event_data AS xml) - FROM sys.fn_xe_file_target_read_file('system_health*.xel', NULL, NULL, NULL) + x.* + FROM #xml AS x + LEFT JOIN #t AS t + ON 1 = 1 + WHERE x.deadlock_xml IS NOT NULL ) INSERT #deadlock_data WITH(TABLOCKX) @@ -766,11 +777,13 @@ BEGIN LEFT JOIN #t AS t ON 1 = 1 CROSS APPLY xml.deadlock_xml.nodes('/event/@name') AS e(x) - WHERE e.x.exist('/event/@name[ .= "xml_deadlock_report"]') = 1 + WHERE e.x.exist('/event/@name[ . = "xml_deadlock_report"]') = 1 AND e.x.exist('/event/@timestamp[. >= sql:variable("@StartDate")]') = 1 - AND e.x.value('/event/@timestamp[. >= sql:variable("@EndDate")]') = 1 + AND e.x.exist('/event/@timestamp[. < sql:variable("@EndDate")]') = 1 OPTION(RECOMPILE); + SET STATISTICS XML OFF; + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; END; @@ -814,7 +827,7 @@ BEGIN ( MINUTE, DATEDIFF(MINUTE, GETUTCDATE(), SYSDATETIME()), - c.value('@timestamp', 'datetime') + dd.event_date ), dd.victim_id, is_parallel = @@ -853,7 +866,7 @@ BEGIN CROSS APPLY dd.deadlock_xml.nodes('//deadlock/process-list/process') AS ca(dp) WHERE (ca.dp.value('@currentdb', 'bigint') = DB_ID(@DatabaseName) OR @DatabaseName IS NULL) AND (ca.dp.value('@clientapp', 'nvarchar(256)') = @AppName OR @AppName IS NULL) - AND (ca.dp.value('@hostname', 'nvarchar(256)') = @HostName OR @HostName IS NULL) + AND (ca.dp.value('@hostname', 'nvarchar(256)') = @HostName OR @HostName IS NULL) AND (ca.dp.value('@loginname', 'nvarchar(256)') = @LoginName OR @LoginName IS NULL) ) AS q CROSS APPLY q.deadlock_xml.nodes('//deadlock/process-list/process/inputbuf') AS ca2(ib) @@ -886,7 +899,7 @@ BEGIN ( MINUTE, DATEDIFF(MINUTE, GETUTCDATE(), SYSDATETIME()), - c.value('@timestamp', 'datetime') + dr.event_date ), dr.victim_id, dr.resource_xml @@ -1498,9 +1511,9 @@ BEGIN + ' instances of serializable deadlocks.' FROM #deadlock_process AS dp WHERE dp.isolation_level LIKE 'serializable%' - AND (DB_NAME(dow.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (dow.event_date >= @StartDate OR @StartDate IS NULL) - AND (dow.event_date < @EndDate OR @EndDate IS NULL) + AND (DB_NAME(dp.database_id) = @DatabaseName OR @DatabaseName IS NULL) + AND (dp.event_date >= @StartDate OR @StartDate IS NULL) + AND (dp.event_date < @EndDate OR @EndDate IS NULL) AND (dp.client_app = @AppName OR @AppName IS NULL) AND (dp.host_name = @HostName OR @HostName IS NULL) AND (dp.login_name = @LoginName OR @LoginName IS NULL) @@ -1530,9 +1543,9 @@ BEGIN + ' instances of repeatable read deadlocks.' FROM #deadlock_process AS dp WHERE dp.isolation_level LIKE 'repeatable read%' - AND (DB_NAME(dow.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (dow.event_date >= @StartDate OR @StartDate IS NULL) - AND (dow.event_date < @EndDate OR @EndDate IS NULL) + AND (DB_NAME(dp.database_id) = @DatabaseName OR @DatabaseName IS NULL) + AND (dp.event_date >= @StartDate OR @StartDate IS NULL) + AND (dp.event_date < @EndDate OR @EndDate IS NULL) AND (dp.client_app = @AppName OR @AppName IS NULL) AND (dp.host_name = @HostName OR @HostName IS NULL) AND (dp.login_name = @LoginName OR @LoginName IS NULL) @@ -1567,9 +1580,9 @@ BEGIN + ISNULL(dp.host_name, 'UNKNOWN') FROM #deadlock_process AS dp WHERE 1 = 1 - AND (DB_NAME(dow.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (dow.event_date >= @StartDate OR @StartDate IS NULL) - AND (dow.event_date < @EndDate OR @EndDate IS NULL) + AND (DB_NAME(dp.database_id) = @DatabaseName OR @DatabaseName IS NULL) + AND (dp.event_date >= @StartDate OR @StartDate IS NULL) + AND (dp.event_date < @EndDate OR @EndDate IS NULL) AND (dp.client_app = @AppName OR @AppName IS NULL) AND (dp.host_name = @HostName OR @HostName IS NULL) AND (dp.login_name = @LoginName OR @LoginName IS NULL) @@ -1599,9 +1612,9 @@ BEGIN ON dp.id = dow.owner_id AND dp.event_date = dow.event_date WHERE 1 = 1 - AND (DB_NAME(dow.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (dow.event_date >= @StartDate OR @StartDate IS NULL) - AND (dow.event_date < @EndDate OR @EndDate IS NULL) + AND (DB_NAME(dp.database_id) = @DatabaseName OR @DatabaseName IS NULL) + AND (dp.event_date >= @StartDate OR @StartDate IS NULL) + AND (dp.event_date < @EndDate OR @EndDate IS NULL) AND (dp.client_app = @AppName OR @AppName IS NULL) AND (dp.host_name = @HostName OR @HostName IS NULL) AND (dp.login_name = @LoginName OR @LoginName IS NULL) @@ -1649,12 +1662,10 @@ BEGIN FROM lock_types AS lt OPTION (RECOMPILE); - /*Check 7 gives you more info queries for sp_BlitzCache & BlitzQueryStore*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Check 7 part 1 %s', 0, 1, @d) WITH NOWAIT; - WITH deadlock_stack AS ( @@ -1830,9 +1841,9 @@ BEGIN ON a.database_id = dow.database_id AND a.partition_id = dow.associatedObjectId WHERE 1 = 1 - AND (DB_NAME(dp.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (dp.event_date >= @StartDate OR @StartDate IS NULL) - AND (dp.event_date < @EndDate OR @EndDate IS NULL) + AND (DB_NAME(dow.database_id) = @DatabaseName OR @DatabaseName IS NULL) + AND (dow.event_date >= @StartDate OR @StartDate IS NULL) + AND (dow.event_date < @EndDate OR @EndDate IS NULL) AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) AND dow.object_name IS NOT NULL ) @@ -1868,16 +1879,21 @@ BEGIN SELECT DISTINCT - database_name = PARSENAME(dow.object_name, 3), - dow.object_name, - wait_days = CONVERT(varchar(10), (SUM(DISTINCT CONVERT(bigint, dp.wait_time)) / 1000) / 86400), - wait_time_hms = - CONVERT - ( - varchar(20), - DATEADD(SECOND, (SUM(DISTINCT CONVERT(bigint, dp.wait_time)) / 1000), 0), - 108 - ) + database_name = PARSENAME(dow.object_name, 3), + dow.object_name, + wait_days = + CONVERT + ( + varchar(10), + (SUM(DISTINCT CONVERT(bigint, dp.wait_time)) / 1000) / 86400 + ), + wait_time_hms = + CONVERT + ( + varchar(20), + DATEADD(SECOND, (SUM(DISTINCT CONVERT(bigint, dp.wait_time)) / 1000), 0), + 108 + ) FROM #deadlock_owner_waiter AS dow JOIN #deadlock_process AS dp ON (dp.id = dow.owner_id @@ -1932,7 +1948,6 @@ BEGIN AND (DB_NAME(dp.database_id) = @DatabaseName OR @DatabaseName IS NULL) AND (dp.event_date >= @StartDate OR @StartDate IS NULL) AND (dp.event_date < @EndDate OR @EndDate IS NULL) - AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) AND (dp.client_app = @AppName OR @AppName IS NULL) AND (dp.host_name = @HostName OR @HostName IS NULL) AND (dp.login_name = @LoginName OR @LoginName IS NULL) @@ -2012,7 +2027,6 @@ BEGIN finding_group = 'Total parallel deadlocks', 'There have been ' + CONVERT(nvarchar(20), COUNT_BIG(DISTINCT drp.event_date)) + ' parallel deadlocks.' FROM #deadlock_resource_parallel AS drp - WHERE 1 = 1 HAVING COUNT_BIG(DISTINCT drp.event_date) > 0 OPTION (RECOMPILE); @@ -2685,33 +2699,23 @@ BEGIN SELECT table_name = '#deadlock_data', * FROM #deadlock_data AS dd OPTION (RECOMPILE); - SELECT table_name = '#deadlock_resource', * FROM #deadlock_resource AS dr OPTION (RECOMPILE); - - SELECT - table_name = '#deadlock_resource_parallel', - * + SELECT table_name = '#deadlock_resource_parallel', * FROM #deadlock_resource_parallel AS drp OPTION (RECOMPILE); - - SELECT - table_name = '#deadlock_owner_waiter', - * + SELECT table_name = '#deadlock_owner_waiter', * FROM #deadlock_owner_waiter AS dow OPTION (RECOMPILE); - SELECT table_name = '#deadlock_process', * FROM #deadlock_process AS dp OPTION (RECOMPILE); - SELECT table_name = '#deadlock_stack', * FROM #deadlock_stack AS ds OPTION (RECOMPILE); - SELECT table_name = '#deadlock_results', * FROM #deadlock_results AS dr OPTION (RECOMPILE); END; -- End debug From 3d5cbfdf5a3bff56995b721a13668135e83d9e7c Mon Sep 17 00:00:00 2001 From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com> Date: Tue, 15 Nov 2022 18:59:55 -0500 Subject: [PATCH 331/662] Update sp_BlitzLock.sql WIP --- sp_BlitzLock.sql | 1048 ++++++++++++++++++++-------------------------- 1 file changed, 447 insertions(+), 601 deletions(-) diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index a0899aada..4c5689841 100644 --- a/sp_BlitzLock.sql +++ b/sp_BlitzLock.sql @@ -9,8 +9,8 @@ ALTER PROCEDURE dbo.sp_BlitzLock ( @DatabaseName nvarchar(256) = NULL, - @StartDate datetime = '19000101', - @EndDate datetime = '99991231', + @StartDate datetime = NULL, + @EndDate datetime = NULL, @ObjectName nvarchar(1000) = NULL, @StoredProcName nvarchar(1000) = NULL, @AppName nvarchar(256) = NULL, @@ -55,13 +55,11 @@ BEGIN Variables you can use: - @Top: Limit the number of rows to return - @DatabaseName: If you want to filter to a specific database - @StartDate: The date you want to start searching on. + @StartDate: The date you want to start searching on, defaults to last 7 days - @EndDate: The date you want to stop searching on. + @EndDate: The date you want to stop searching on, defaults to current date @ObjectName: If you want to filter to a specific able. The object name has to be fully qualified ''Database.Schema.Table'' @@ -75,12 +73,14 @@ BEGIN @LoginName: If you want to filter to a specific login - @EventSessionPath: If you want to point this at an XE session rather than the system health session. + @EventSessionName: If you want to point this at an XE session rather than the system health session. - @TargetSessionType: Can be ring_buffer or event_file. + @TargetSessionType: Can be ''ring_buffer'' or ''event_file'' @OutputDatabaseName: If you want to output information to a specific database + @OutputSchemaName: Specify a schema name to output information to a specific Schema + @OutputTableName: Specify table name to to output information to a specific table To learn more, visit http://FirstResponderKit.org where you can download new @@ -127,6 +127,8 @@ BEGIN DECLARE + @DatabaseId int = + DB_ID(@DatabaseName), @ProductVersion nvarchar(128) = CAST(SERVERPROPERTY('ProductVersion') AS nvarchar(128)), @ProductVersionMajor float = @@ -160,8 +162,8 @@ BEGIN @RDS bit = CASE WHEN LEFT(CAST(SERVERPROPERTY('ComputerNamePhysicalNetBIOS') AS varchar(8000)), 8) <> 'EC2AMAZ-' - AND LEFT(CAST(SERVERPROPERTY('MachineName') AS varchar(8000)), 8) <> 'EC2AMAZ-' - AND DB_ID('rdsadmin') IS NULL + AND LEFT(CAST(SERVERPROPERTY('MachineName') AS varchar(8000)), 8) <> 'EC2AMAZ-' + AND DB_ID('rdsadmin') IS NULL THEN 0 ELSE 1 END, @@ -171,11 +173,11 @@ BEGIN @r sysname = N'', @OutputTableFindings nvarchar(100) = N'[BlitzLockFindings]', @DeadlockCount int = 0, - @ServerName nvarchar(256) = @@SERVERNAME, - @OutputDatabaseCheck bit = NULL, - @SessionId int, - @TargetSessionId int, - @FileName nvarchar(4000), + @ServerName sysname = @@SERVERNAME, + @OutputDatabaseCheck bit = -1, + @SessionId int = 0, + @TargetSessionId int = 0, + @FileName nvarchar(4000) = N'', @NC10 nvarchar(1) = CONVERT(nvarchar(1), 0x0a00, 0), @deadlock_result nvarchar(MAX) = N''; @@ -192,14 +194,16 @@ BEGIN CREATE TABLE #x ( - x xml NOT NULL DEFAULT ' ' + x xml NOT NULL + DEFAULT ' ' ); CREATE TABLE #deadlock_data ( - deadlock_xml xml NOT NULL DEFAULT ' ' + deadlock_xml xml NOT NULL + DEFAULT ' ' ); CREATE TABLE @@ -208,7 +212,8 @@ BEGIN id int NOT NULL ); - SET @StartDate = + SELECT + @StartDate = DATEADD ( MINUTE, @@ -218,10 +223,18 @@ BEGIN SYSDATETIME(), GETUTCDATE() ), - ISNULL(@StartDate, '19000101') - ); - - SET @EndDate = + ISNULL + ( + @StartDate, + DATEADD + ( + DAY, + -7, + SYSDATETIME() + ) + ) + ), + @EndDate = DATEADD ( MINUTE, @@ -231,7 +244,11 @@ BEGIN SYSDATETIME(), GETUTCDATE() ), - ISNULL(@EndDate, '99991231') + ISNULL + ( + @EndDate, + SYSDATETIME() + ) ); CREATE TABLE @@ -268,17 +285,19 @@ BEGIN SELECT @StringToExecute = N'SELECT @r = name FROM ' + - N'' + @OutputDatabaseName + - N'' + N'.sys.objects WHERE type_desc = ''USER_TABLE'' AND name = ' + - N'''' + - @OutputTableName + - N'''' + + QUOTENAME + ( + @OutputTableName, + N'''' + ) + N' AND schema_id = SCHEMA_ID(' + - N'''' + - @OutputSchemaName + - N'''' + + QUOTENAME + ( + @OutputSchemaName, + N'''' + ) + N');', @StringToExecuteParams = N'@r sysname OUTPUT'; @@ -292,19 +311,22 @@ BEGIN --put covers around all before. SELECT - @OutputDatabaseName = QUOTENAME(@OutputDatabaseName), - @OutputTableName = QUOTENAME(@OutputTableName), - @OutputSchemaName = QUOTENAME(@OutputSchemaName); + @ObjectFullName = + QUOTENAME(@OutputDatabaseName) + + N'.' + + QUOTENAME(@OutputTableName) + + N'.' + + QUOTENAME(@OutputSchemaName), + @OutputDatabaseName = + QUOTENAME(@OutputDatabaseName), + @OutputTableName = + QUOTENAME(@OutputTableName), + @OutputSchemaName = + QUOTENAME(@OutputSchemaName); IF (@r IS NOT NULL) --if it is not null, there is a table, so check for newly added columns BEGIN /* If the table doesn't have the new spid column, add it. See Github #3101. */ - SET @ObjectFullName = - @OutputDatabaseName + - N'.' + - @OutputSchemaName + - N'.' + - @OutputTableName; SET @StringToExecute = N'IF NOT EXISTS (SELECT 1/0 FROM ' + @@ -315,7 +337,7 @@ BEGIN /*Add spid column*/ ALTER TABLE ' + @ObjectFullName + - N'ADD spid smallint NULL;'; + N' ADD spid smallint NULL;'; EXEC sys.sp_executesql @StringToExecute; @@ -332,7 +354,7 @@ BEGIN @ObjectFullName + N' ADD wait_resource nvarchar(MAX) NULL;'; - EXEC sp_executesql + EXEC sys.sp_executesql @StringToExecute; END; ELSE --if(@r is not null) --if it is null there is no table, create it from above execution @@ -392,9 +414,7 @@ BEGIN SELECT @StringToExecute = N'SELECT @r = name FROM ' + - N'' + @OutputDatabaseName + - N'' + N'.sys.objects WHERE type_desc = ''USER_TABLE'' AND name = ''BlitzLockFindings''', @StringToExecuteParams = N'@r sysname OUTPUT'; @@ -629,7 +649,7 @@ BEGIN IF @Azure = 1 BEGIN - RAISERROR('@TargetSessionType is event_ile, assigning XML for Azure', 0, 1) WITH NOWAIT; + RAISERROR('@TargetSessionType is event_file, assigning XML for Azure', 0, 1) WITH NOWAIT; SELECT @SessionId = t.event_session_id, @TargetSessionId = t.target_id @@ -757,23 +777,17 @@ BEGIN INTO #xml FROM sys.fn_xe_file_target_read_file('system_health*.xel', NULL, NULL, NULL) LEFT JOIN #t AS t - ON 1 = 1; + ON 1 = 1; + + DELETE x + FROM #xml AS x + WHERE x.deadlock_xml IS NULL - WITH - xml AS - ( - SELECT - x.* - FROM #xml AS x - LEFT JOIN #t AS t - ON 1 = 1 - WHERE x.deadlock_xml IS NOT NULL - ) INSERT #deadlock_data WITH(TABLOCKX) SELECT deadlock_xml = xml.deadlock_xml - FROM xml + FROM #xml AS xml LEFT JOIN #t AS t ON 1 = 1 CROSS APPLY xml.deadlock_xml.nodes('/event/@name') AS e(x) @@ -791,6 +805,16 @@ BEGIN SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Parse process and input buffer xml %s', 0, 1, @d) WITH NOWAIT; + SELECT + d1.deadlock_xml, + event_date = d1.deadlock_xml.value('(event/@timestamp)[1]', 'datetime2'), + victim_id = d1.deadlock_xml.value('(//deadlock/victim-list/victimProcess/@id)[1]', 'nvarchar(256)'), + is_parallel = d1.deadlock_xml.exist('//deadlock/resource-list/exchangeEvent'), + is_parallel_batch = d1.deadlock_xml.exist('//deadlock/resource-list/SyncPoint'), + deadlock_graph = d1.deadlock_xml.query('/event/data/value/deadlock') + INTO #dd + FROM #deadlock_data AS d1; + SELECT q.event_date, q.victim_id, @@ -852,25 +876,15 @@ BEGIN login_name = ca.dp.value('@loginname', 'nvarchar(256)'), isolation_level = ca.dp.value('@isolationlevel', 'nvarchar(256)'), process_xml = ISNULL(ca.dp.query('.'), '') - FROM - ( - SELECT - d1.deadlock_xml, - event_date = d1.deadlock_xml.value('(event/@timestamp)[1]', 'datetime2'), - victim_id = d1.deadlock_xml.value('(//deadlock/victim-list/victimProcess/@id)[1]', 'nvarchar(256)'), - is_parallel = d1.deadlock_xml.exist('//deadlock/resource-list/exchangeEvent'), - is_parallel_batch = d1.deadlock_xml.exist('//deadlock/resource-list/SyncPoint'), - deadlock_graph = d1.deadlock_xml.query('/event/data/value/deadlock') - FROM #deadlock_data AS d1 - ) AS dd + FROM #dd AS dd CROSS APPLY dd.deadlock_xml.nodes('//deadlock/process-list/process') AS ca(dp) - WHERE (ca.dp.value('@currentdb', 'bigint') = DB_ID(@DatabaseName) OR @DatabaseName IS NULL) - AND (ca.dp.value('@clientapp', 'nvarchar(256)') = @AppName OR @AppName IS NULL) - AND (ca.dp.value('@hostname', 'nvarchar(256)') = @HostName OR @HostName IS NULL) - AND (ca.dp.value('@loginname', 'nvarchar(256)') = @LoginName OR @LoginName IS NULL) + WHERE (ca.dp.exist('@currentdb[. = sql:variable("@DatabaseId")]') = 1 OR @DatabaseName IS NULL) + AND (ca.dp.exist('@clientapp[. = sql:variable("@AppName")]') = 1 OR @AppName IS NULL) + AND (ca.dp.exist('@hostname[. = sql:variable("@HostName")]') = 1 OR @HostName IS NULL) + AND (ca.dp.exist('@loginname[. = sql:variable("@LoginName")]') = 1 OR @LoginName IS NULL) ) AS q CROSS APPLY q.deadlock_xml.nodes('//deadlock/process-list/process/inputbuf') AS ca2(ib) - OPTION (RECOMPILE); + OPTION(RECOMPILE); /*Parse execution stack xml*/ @@ -881,12 +895,12 @@ BEGIN dp.id, dp.event_date, proc_name = ca.dp.value('@procname', 'nvarchar(1000)'), - sql_handle = ca.dp.value('@sqlhandle', 'nvarchar(128)') + sql_handle = ca.dp.value('@sqlhandle', 'nvarchar(131)') INTO #deadlock_stack FROM #deadlock_process AS dp CROSS APPLY dp.process_xml.nodes('//executionStack/frame') AS ca(dp) - WHERE (ca.dp.value('@procname', 'nvarchar(256)') = @StoredProcName OR @StoredProcName IS NULL) - OPTION (RECOMPILE); + WHERE (ca.dp.exist('@procname[. = sql:variable("@StoredProcName")]') = 1 OR @StoredProcName IS NULL) + OPTION(RECOMPILE); /*Grab the full resource list*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); @@ -914,7 +928,7 @@ BEGIN FROM #deadlock_data AS dd CROSS APPLY dd.deadlock_xml.nodes('//deadlock/resource-list') AS ca(dp) ) AS dr - OPTION (RECOMPILE); + OPTION(RECOMPILE); /*Parse object locks*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); @@ -949,7 +963,7 @@ BEGIN CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l) CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l) WHERE (ca.object_name = @ObjectName OR @ObjectName IS NULL) - OPTION (RECOMPILE); + OPTION(RECOMPILE); /*Parse page locks*/ @@ -957,7 +971,7 @@ BEGIN RAISERROR('Parse page locks %s', 0, 1, @d) WITH NOWAIT; INSERT - #deadlock_owner_waiter WITH (TABLOCKX) + #deadlock_owner_waiter WITH(TABLOCKX) SELECT DISTINCT ca.event_date, ca.database_id, @@ -985,16 +999,15 @@ BEGIN ) AS ca CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l) CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l) - OPTION (RECOMPILE); + OPTION(RECOMPILE); /*Parse key locks*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Parse key locks %s', 0, 1, @d) WITH NOWAIT; INSERT - #deadlock_owner_waiter WITH (TABLOCKX) + #deadlock_owner_waiter WITH(TABLOCKX) SELECT DISTINCT ca.event_date, ca.database_id, @@ -1022,16 +1035,15 @@ BEGIN ) AS ca CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l) CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l) - OPTION (RECOMPILE); + OPTION(RECOMPILE); /*Parse RID locks*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Parse RID locks %s', 0, 1, @d) WITH NOWAIT; - INSERT - #deadlock_owner_waiter WITH (TABLOCKX) + #deadlock_owner_waiter WITH(TABLOCKX) SELECT DISTINCT ca.event_date, ca.database_id, @@ -1059,7 +1071,7 @@ BEGIN ) AS ca CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l) CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l) - OPTION (RECOMPILE); + OPTION(RECOMPILE); /*Parse row group locks*/ @@ -1067,7 +1079,7 @@ BEGIN RAISERROR('Parse row group locks %s', 0, 1, @d) WITH NOWAIT; INSERT - #deadlock_owner_waiter WITH (TABLOCKX) + #deadlock_owner_waiter WITH(TABLOCKX) SELECT DISTINCT ca.event_date, ca.database_id, @@ -1095,7 +1107,7 @@ BEGIN ) AS ca CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l) CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l) - OPTION (RECOMPILE); + OPTION(RECOMPILE); UPDATE @@ -1109,7 +1121,7 @@ BEGIN N'HEAP', N'RID' ) - OPTION (RECOMPILE); + OPTION(RECOMPILE); /*Parse parallel deadlocks*/ @@ -1151,7 +1163,7 @@ BEGIN ) AS ca CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l) CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l) - OPTION (RECOMPILE); + OPTION(RECOMPILE); /*Get rid of parallel noise*/ @@ -1177,7 +1189,7 @@ BEGIN DELETE FROM c WHERE c.rn > 1 - OPTION (RECOMPILE); + OPTION(RECOMPILE); /*Get rid of nonsense*/ @@ -1187,11 +1199,12 @@ BEGIN DELETE dow FROM #deadlock_owner_waiter AS dow WHERE dow.owner_id = dow.waiter_id - OPTION (RECOMPILE); + OPTION(RECOMPILE); /*Add some nonsense*/ - ALTER TABLE #deadlock_process + ALTER TABLE + #deadlock_process ADD waiter_mode nvarchar(256), owner_mode nvarchar(256), @@ -1204,7 +1217,7 @@ BEGIN THEN 1 ELSE 0 END - ); + ) PERSISTED; /*Update some nonsense*/ @@ -1220,7 +1233,7 @@ BEGIN ON dp.id = dow.owner_id AND dp.event_date = dow.event_date WHERE dp.is_victim = 0 - OPTION (RECOMPILE); + OPTION(RECOMPILE); SET @d = CONVERT(varchar(40), GETDATE(), 109); @@ -1235,13 +1248,12 @@ BEGIN ON dp.victim_id = dow.waiter_id AND dp.event_date = dow.event_date WHERE dp.is_victim = 1 - OPTION (RECOMPILE); + OPTION(RECOMPILE); /*Get Agent Job and Step names*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Get Agent Job and Step names %s', 0, 1, @d) WITH NOWAIT; SELECT *, @@ -1278,7 +1290,7 @@ BEGIN WHERE dp.client_app LIKE 'SQLAgent - %' AND dp.client_app <> 'SQLAgent - Initial Boot Probe' ) AS x - OPTION (RECOMPILE); + OPTION(RECOMPILE); ALTER TABLE @@ -1306,7 +1318,7 @@ BEGIN JOIN #agent_job AS aj ON aj.job_id_guid = j.job_id AND aj.step_id = s.step_id - OPTION (RECOMPILE);'; + OPTION(RECOMPILE);'; EXEC sys.sp_executesql @StringToExecute; @@ -1319,7 +1331,10 @@ BEGIN dp.client_app = CASE WHEN dp.client_app LIKE N'SQLAgent - %' - THEN N'SQLAgent - Job: ' + aj.job_name + N' Step: ' + aj.step_name + THEN N'SQLAgent - Job: ' + + aj.job_name + + N' Step: ' + + aj.step_name ELSE dp.client_app END FROM #deadlock_process AS dp @@ -1327,7 +1342,7 @@ BEGIN ON dp.event_date = aj.event_date AND dp.victim_id = aj.victim_id AND dp.id = aj.id - OPTION (RECOMPILE); + OPTION(RECOMPILE); /*Get each and every table of all databases*/ @@ -1357,7 +1372,7 @@ BEGIN RAISERROR('Check 1 %s', 0, 1, @d) WITH NOWAIT; INSERT - #deadlock_findings WITH (TABLOCKX) + #deadlock_findings WITH(TABLOCKX) ( check_id, database_name, @@ -1368,9 +1383,12 @@ BEGIN SELECT check_id = 1, database_name = DB_NAME(dp.database_id), - object_name = '-', - finding_group = 'Total database locks', - 'This database had ' + CONVERT(nvarchar(20), COUNT_BIG(DISTINCT dp.event_date)) + ' deadlocks.' + object_name = N'-', + finding_group = N'Total database locks', + finding = + N'This database had ' + + CONVERT(nvarchar(20), COUNT_BIG(DISTINCT dp.event_date)) + + N' deadlocks.' FROM #deadlock_process AS dp WHERE 1 = 1 AND (DB_NAME(dp.database_id) = @DatabaseName OR @DatabaseName IS NULL) @@ -1380,7 +1398,7 @@ BEGIN AND (dp.host_name = @HostName OR @HostName IS NULL) AND (dp.login_name = @LoginName OR @LoginName IS NULL) GROUP BY DB_NAME(dp.database_id) - OPTION (RECOMPILE); + OPTION(RECOMPILE); /*Check 2 is deadlocks by object*/ @@ -1388,7 +1406,7 @@ BEGIN RAISERROR('Check 2 objects %s', 0, 1, @d) WITH NOWAIT; INSERT - #deadlock_findings WITH (TABLOCKX) + #deadlock_findings WITH(TABLOCKX) ( check_id, database_name, @@ -1398,11 +1416,13 @@ BEGIN ) SELECT check_id = 2, - database_name = ISNULL(DB_NAME(dow.database_id), 'UNKNOWN'), - object_name = ISNULL(dow.object_name, 'UNKNOWN'), - finding_group = 'Total object deadlocks', - 'This object was involved in ' + CONVERT(nvarchar(20), COUNT_BIG(DISTINCT dow.event_date)) - + ' deadlock(s).' + database_name = ISNULL(DB_NAME(dow.database_id), N'UNKNOWN'), + object_name = ISNULL(dow.object_name, N'UNKNOWN'), + finding_group = N'Total object deadlocks', + finding = + N'This object was involved in ' + + CONVERT(nvarchar(20), COUNT_BIG(DISTINCT dow.event_date)) + + N' deadlock(s).' FROM #deadlock_owner_waiter AS dow WHERE 1 = 1 AND (DB_NAME(dow.database_id) = @DatabaseName OR @DatabaseName IS NULL) @@ -1412,7 +1432,7 @@ BEGIN GROUP BY DB_NAME(dow.database_id), dow.object_name - OPTION (RECOMPILE); + OPTION(RECOMPILE); /*Check 2 continuation, number of locks per index*/ @@ -1420,7 +1440,7 @@ BEGIN RAISERROR('Check 2 indexes %s', 0, 1, @d) WITH NOWAIT; INSERT - #deadlock_findings WITH (TABLOCKX) + #deadlock_findings WITH(TABLOCKX) ( check_id, database_name, @@ -1430,10 +1450,13 @@ BEGIN ) SELECT check_id = 2, - database_name = ISNULL(DB_NAME(dow.database_id), 'UNKNOWN'), + database_name = ISNULL(DB_NAME(dow.database_id), N'UNKNOWN'), index_name = dow.index_name, - finding_group = 'Total index deadlocks', - 'This index was involved in ' + CONVERT(nvarchar(20), COUNT_BIG(DISTINCT dow.event_date)) + ' deadlock(s).' + finding_group = N'Total index deadlocks', + finding = + N'This index was involved in ' + + CONVERT(nvarchar(20), COUNT_BIG(DISTINCT dow.event_date)) + + N' deadlock(s).' FROM #deadlock_owner_waiter AS dow WHERE 1 = 1 AND (DB_NAME(dow.database_id) = @DatabaseName OR @DatabaseName IS NULL) @@ -1449,7 +1472,7 @@ BEGIN GROUP BY DB_NAME(dow.database_id), dow.index_name - OPTION (RECOMPILE); + OPTION(RECOMPILE); /*Check 2 continuation, number of locks per heap*/ @@ -1458,7 +1481,7 @@ BEGIN INSERT - #deadlock_findings WITH (TABLOCKX) + #deadlock_findings WITH(TABLOCKX) ( check_id, database_name, @@ -1468,10 +1491,13 @@ BEGIN ) SELECT check_id = 2, - database_name = ISNULL(DB_NAME(dow.database_id), 'UNKNOWN'), + database_name = ISNULL(DB_NAME(dow.database_id), N'UNKNOWN'), index_name = dow.index_name, - finding_group = 'Total heap deadlocks', - 'This heap was involved in ' + CONVERT(nvarchar(20), COUNT_BIG(DISTINCT dow.event_date)) + ' deadlock(s).' + finding_group = N'Total heap deadlocks', + finding = + N'This heap was involved in ' + + CONVERT(nvarchar(20), COUNT_BIG(DISTINCT dow.event_date)) + + N' deadlock(s).' FROM #deadlock_owner_waiter AS dow WHERE 1 = 1 AND (DB_NAME(dow.database_id) = @DatabaseName OR @DatabaseName IS NULL) @@ -1486,7 +1512,7 @@ BEGIN GROUP BY DB_NAME(dow.database_id), dow.index_name - OPTION (RECOMPILE); + OPTION(RECOMPILE); /*Check 3 looks for Serializable locking*/ @@ -1494,7 +1520,7 @@ BEGIN RAISERROR('Check 3 %s', 0, 1, @d) WITH NOWAIT; INSERT - #deadlock_findings WITH (TABLOCKX) + #deadlock_findings WITH(TABLOCKX) ( check_id, database_name, @@ -1505,10 +1531,12 @@ BEGIN SELECT check_id = 3, database_name = DB_NAME(dp.database_id), - object_name = '-', - finding_group = 'Serializable locking', - finding = 'This database has had ' + CONVERT(nvarchar(20), COUNT_BIG(*)) - + ' instances of serializable deadlocks.' + object_name = N'-', + finding_group = N'Serializable locking', + finding = + N'This database has had ' + + CONVERT(nvarchar(20), COUNT_BIG(*)) + + N' instances of serializable deadlocks.' FROM #deadlock_process AS dp WHERE dp.isolation_level LIKE 'serializable%' AND (DB_NAME(dp.database_id) = @DatabaseName OR @DatabaseName IS NULL) @@ -1518,7 +1546,7 @@ BEGIN AND (dp.host_name = @HostName OR @HostName IS NULL) AND (dp.login_name = @LoginName OR @LoginName IS NULL) GROUP BY DB_NAME(dp.database_id) - OPTION (RECOMPILE); + OPTION(RECOMPILE); /*Check 4 looks for Repeatable Read locking*/ @@ -1526,7 +1554,7 @@ BEGIN RAISERROR('Check 4 %s', 0, 1, @d) WITH NOWAIT; INSERT - #deadlock_findings WITH (TABLOCKX) + #deadlock_findings WITH(TABLOCKX) ( check_id, database_name, @@ -1537,12 +1565,14 @@ BEGIN SELECT check_id = 4, database_name = DB_NAME(dp.database_id), - object_name = '-', - finding_group = 'Repeatable Read locking', - finding = 'This database has had ' + CONVERT(nvarchar(20), COUNT_BIG(*)) - + ' instances of repeatable read deadlocks.' + object_name = N'-', + finding_group = N'Repeatable Read locking', + finding = + N'This database has had ' + + CONVERT(nvarchar(20), COUNT_BIG(*)) + + N' instances of repeatable read deadlocks.' FROM #deadlock_process AS dp - WHERE dp.isolation_level LIKE 'repeatable read%' + WHERE dp.isolation_level LIKE N'repeatable read%' AND (DB_NAME(dp.database_id) = @DatabaseName OR @DatabaseName IS NULL) AND (dp.event_date >= @StartDate OR @StartDate IS NULL) AND (dp.event_date < @EndDate OR @EndDate IS NULL) @@ -1550,18 +1580,15 @@ BEGIN AND (dp.host_name = @HostName OR @HostName IS NULL) AND (dp.login_name = @LoginName OR @LoginName IS NULL) GROUP BY DB_NAME(dp.database_id) - OPTION (RECOMPILE); + OPTION(RECOMPILE); /*Check 5 breaks down app, host, and login information*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); - - RAISERROR('Check 5 %s', 0, 1, @d) WITH NOWAIT; - INSERT - #deadlock_findings WITH (TABLOCKX) + #deadlock_findings WITH(TABLOCKX) ( check_id, database_name, @@ -1572,12 +1599,17 @@ BEGIN SELECT check_id = 5, database_name = DB_NAME(dp.database_id), - object_name = '-', - finding_group = 'Login, App, and Host locking', - finding = 'This database has had ' + CONVERT(nvarchar(20), COUNT_BIG(DISTINCT dp.event_date)) - + ' instances of deadlocks involving the login ' + ISNULL(dp.login_name, 'UNKNOWN') - + ' from the application ' + ISNULL(dp.client_app, 'UNKNOWN') + ' on host ' - + ISNULL(dp.host_name, 'UNKNOWN') + object_name = N'-', + finding_group = N'Login, App, and Host locking', + finding = + N'This database has had ' + + CONVERT(nvarchar(20), COUNT_BIG(DISTINCT dp.event_date)) + + N' instances of deadlocks involving the login ' + + ISNULL(dp.login_name, N'UNKNOWN') + + N' from the application ' + + ISNULL(dp.client_app, N'UNKNOWN') + + N' on host ' + + ISNULL(dp.host_name, N'UNKNOWN') FROM #deadlock_process AS dp WHERE 1 = 1 AND (DB_NAME(dp.database_id) = @DatabaseName OR @DatabaseName IS NULL) @@ -1591,7 +1623,7 @@ BEGIN dp.login_name, dp.client_app, dp.host_name - OPTION (RECOMPILE); + OPTION(RECOMPILE); /*Check 6 breaks down the types of locks (object, page, key, etc.)*/ @@ -1605,7 +1637,12 @@ BEGIN SELECT database_name = DB_NAME(dp.database_id), dow.object_name, - lock = SUBSTRING(dp.wait_resource, 1, CHARINDEX(':', dp.wait_resource) - 1), + lock = + CASE + WHEN CHARINDEX(N':', dp.wait_resource) > 0 + THEN SUBSTRING(dp.wait_resource, 1, CHARINDEX(N':', dp.wait_resource) - 1) + ELSE dp.wait_resource + END, lock_count = CONVERT(nvarchar(20), COUNT_BIG(DISTINCT dp.id)) FROM #deadlock_process AS dp JOIN #deadlock_owner_waiter AS dow @@ -1626,7 +1663,7 @@ BEGIN dow.object_name ) INSERT - #deadlock_findings WITH (TABLOCKX) + #deadlock_findings WITH(TABLOCKX) ( check_id, database_name, @@ -1638,29 +1675,30 @@ BEGIN check_id = 6, lt.database_name, lt.object_name, - finding_group = 'Types of locks by object', - N'This object has had ' + - STUFF - ( + finding_group = N'Types of locks by object', + finding = + N'This object has had ' + + STUFF ( - SELECT DISTINCT - N', ' + - lt2.lock_count + - N' ' + - lt2.lock - FROM lock_types AS lt2 - WHERE lt2.database_name = lt.database_name - AND lt2.object_name = lt.object_name - FOR XML - PATH(N''), - TYPE - ).value(N'.[1]', N'nvarchar(MAX)'), - 1, - 1, - N'' - ) + N' locks' + ( + SELECT DISTINCT + N', ' + + lt2.lock_count + + N' ' + + lt2.lock + FROM lock_types AS lt2 + WHERE lt2.database_name = lt.database_name + AND lt2.object_name = lt.object_name + FOR XML + PATH(N''), + TYPE + ).value(N'.[1]', N'nvarchar(MAX)'), + 1, + 1, + N'' + ) + N' locks.' FROM lock_types AS lt - OPTION (RECOMPILE); + OPTION(RECOMPILE); /*Check 7 gives you more info queries for sp_BlitzCache & BlitzQueryStore*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); @@ -1676,7 +1714,8 @@ BEGIN database_name = PARSENAME(ds.proc_name, 3), schema_name = PARSENAME(ds.proc_name, 2), proc_only_name = PARSENAME(ds.proc_name, 1), - sql_handle_csv = N'''' + + sql_handle_csv = + N'''' + STUFF ( ( @@ -1694,7 +1733,8 @@ BEGIN 1, 1, N'' - ) + N'''' + ) + + N'''' FROM #deadlock_stack AS ds WHERE ds.sql_handle <> 0x GROUP BY @@ -1706,7 +1746,7 @@ BEGIN ds.event_date ) INSERT - #deadlock_findings WITH (TABLOCKX) + #deadlock_findings WITH(TABLOCKX) ( check_id, database_name, @@ -1716,15 +1756,15 @@ BEGIN ) SELECT DISTINCT check_id = 7, - database_name = ISNULL(DB_NAME(dow.database_id), 'UNKNOWN'), + database_name = ISNULL(DB_NAME(dow.database_id), N'UNKNOWN'), object_name = ds.proc_name, - finding_group = 'More Info - Query', - finding = 'EXEC sp_BlitzCache ' + + finding_group = N'More Info - Query', + finding = N'EXEC sp_BlitzCache ' + CASE - WHEN ds.proc_name = 'adhoc' - THEN ' @OnlySqlHandles = ' + ds.sql_handle_csv - ELSE '@StoredProcName = ' + QUOTENAME(ds.proc_only_name, '''') - END + ';' + WHEN ds.proc_name = N'adhoc' + THEN N' @OnlySqlHandles = ' + ds.sql_handle_csv + ELSE N'@StoredProcName = ' + QUOTENAME(ds.proc_only_name, N'''') + END + N';' FROM deadlock_stack AS ds JOIN #deadlock_owner_waiter AS dow ON dow.owner_id = ds.id @@ -1734,7 +1774,7 @@ BEGIN AND (dow.event_date >= @StartDate OR @StartDate IS NULL) AND (dow.event_date < @EndDate OR @EndDate IS NULL) AND (dow.object_name = @StoredProcName OR @StoredProcName IS NULL) - OPTION (RECOMPILE); + OPTION(RECOMPILE); IF (@ProductVersionMajor >= 13 OR @Azure = 1) @@ -1756,7 +1796,7 @@ BEGIN FROM #deadlock_stack AS ds ) INSERT - #deadlock_findings WITH (TABLOCKX) + #deadlock_findings WITH(TABLOCKX) ( check_id, database_name, @@ -1768,9 +1808,15 @@ BEGIN check_id = 7, database_name = DB_NAME(dow.database_id), object_name = ds.proc_name, - finding_group = 'More Info - Query', - finding = 'EXEC sp_BlitzQueryStore ' + '@DatabaseName = ' + QUOTENAME(ds.database_name, '''') + ', ' - + '@StoredProcName = ' + QUOTENAME(ds.proc_only_name, '''') + ';' + finding_group = N'More Info - Query', + finding = + N'EXEC sp_BlitzQueryStore ' + + N'@DatabaseName = ' + + QUOTENAME(ds.database_name, N'''') + + N', ' + + N'@StoredProcName = ' + + QUOTENAME(ds.proc_only_name, N'''') + + N';' FROM deadlock_stack AS ds JOIN #deadlock_owner_waiter AS dow ON dow.owner_id = ds.id @@ -1780,7 +1826,7 @@ BEGIN AND (dow.event_date >= @StartDate OR @StartDate IS NULL) AND (dow.event_date < @EndDate OR @EndDate IS NULL) AND (dow.object_name = @StoredProcName OR @StoredProcName IS NULL) - OPTION (RECOMPILE); + OPTION(RECOMPILE); END; @@ -1788,9 +1834,8 @@ BEGIN SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Check 8 %s', 0, 1, @d) WITH NOWAIT; - INSERT - #deadlock_findings WITH (TABLOCKX) + #deadlock_findings WITH(TABLOCKX) ( check_id, database_name, @@ -1801,10 +1846,16 @@ BEGIN SELECT check_id = 8, database_name = DB_NAME(dp.database_id), - ds.proc_name, - 'Stored Procedure Deadlocks', - 'The stored procedure ' + PARSENAME(ds.proc_name, 2) + '.' + PARSENAME(ds.proc_name, 1) - + ' has been involved in ' + CONVERT(nvarchar(10), COUNT_BIG(DISTINCT ds.id)) + ' deadlocks.' + object_name = ds.proc_name, + finding_group = 'Stored Procedure Deadlocks', + finding = + N'The stored procedure ' + + PARSENAME(ds.proc_name, 2) + + N'.' + + PARSENAME(ds.proc_name, 1) + + N' has been involved in ' + + CONVERT(nvarchar(10), COUNT_BIG(DISTINCT ds.id)) + + N' deadlocks.' FROM #deadlock_stack AS ds JOIN #deadlock_process AS dp ON dp.id = ds.id @@ -1820,7 +1871,7 @@ BEGIN GROUP BY DB_NAME(dp.database_id), ds.proc_name - OPTION (RECOMPILE); + OPTION(RECOMPILE); /*Check 9 gives you more info queries for sp_BlitzIndex */ @@ -1848,7 +1899,7 @@ BEGIN AND dow.object_name IS NOT NULL ) INSERT - #deadlock_findings WITH (TABLOCKX) + #deadlock_findings WITH(TABLOCKX) ( check_id, database_name, @@ -1860,19 +1911,24 @@ BEGIN check_id = 9, bi.database_name, bi.object_name, - finding_group = 'More Info - Table', - finding = 'EXEC sp_BlitzIndex ' + '@DatabaseName = ' + QUOTENAME(bi.database_name, '''') - + ', @SchemaName = ' + QUOTENAME(bi.schema_name, '''') + ', @TableName = ' - + QUOTENAME(bi.table_name, '''') + ';' + finding_group = N'More Info - Table', + finding = + N'EXEC sp_BlitzIndex ' + + N'@DatabaseName = ' + + QUOTENAME(bi.database_name, N'''') + + N', @SchemaName = ' + + QUOTENAME(bi.schema_name, N'''') + + N', @TableName = ' + + QUOTENAME(bi.table_name, N'''') + + N';' FROM bi - OPTION (RECOMPILE); + OPTION(RECOMPILE); /*Check 10 gets total deadlock wait time per object*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Check 10 %s', 0, 1, @d) WITH NOWAIT; - WITH chopsuey AS ( @@ -1884,13 +1940,13 @@ BEGIN wait_days = CONVERT ( - varchar(10), + nvarchar(10), (SUM(DISTINCT CONVERT(bigint, dp.wait_time)) / 1000) / 86400 ), wait_time_hms = CONVERT ( - varchar(20), + nvarchar(20), DATEADD(SECOND, (SUM(DISTINCT CONVERT(bigint, dp.wait_time)) / 1000), 0), 108 ) @@ -1912,7 +1968,7 @@ BEGIN dow.object_name ) INSERT - #deadlock_findings WITH (TABLOCKX) + #deadlock_findings WITH(TABLOCKX) ( check_id, database_name, @@ -1924,12 +1980,16 @@ BEGIN check_id = 10, cs.database_name, cs.object_name, - finding_group = 'Total object deadlock wait time', - finding = 'This object has had ' + CONVERT(varchar(10), cs.wait_days) + ':' - + CONVERT(varchar(20), cs.wait_time_hms, 108) + ' [d/h/m/s] of deadlock wait time.' + finding_group = N'Total object deadlock wait time', + finding = + N'This object has had ' + + CONVERT(nvarchar(10), cs.wait_days) + + N':' + + CONVERT(nvarchar(20), cs.wait_time_hms, 108) + + N' [d/h/m/s] of deadlock wait time.' FROM chopsuey AS cs WHERE cs.object_name IS NOT NULL - OPTION (RECOMPILE); + OPTION(RECOMPILE); /*Check 11 gets total deadlock wait time per database*/ @@ -1954,7 +2014,7 @@ BEGIN GROUP BY DB_NAME(dp.database_id) ) INSERT - #deadlock_findings WITH (TABLOCKX) + #deadlock_findings WITH(TABLOCKX) ( check_id, database_name, @@ -1965,19 +2025,21 @@ BEGIN SELECT check_id = 11, wt.database_name, - object_name = '-', - finding_group = 'Total database deadlock wait time', - 'This database has had ' - + CONVERT(varchar(10), (SUM(DISTINCT CONVERT(bigint, wt.total_wait_time_ms)) / 1000) / 86400) + ':' - + CONVERT + object_name = N'-', + finding_group = N'Total database deadlock wait time', + N'This database has had ' + + CONVERT(nvarchar(10), (SUM(DISTINCT CONVERT(bigint, wt.total_wait_time_ms)) / 1000) / 86400) + + ':' + + CONVERT ( - varchar(20), + nvarchar(20), DATEADD(SECOND, (SUM(DISTINCT CONVERT(bigint, wt.total_wait_time_ms)) / 1000), 0), 108 - ) + ' [d/h/m/s] of deadlock wait time.' + ) + + N' [d/h/m/s] of deadlock wait time.' FROM wait_time AS wt GROUP BY wt.database_name - OPTION (RECOMPILE); + OPTION(RECOMPILE); /*Check 12 gets total deadlock wait time for SQL Agent*/ @@ -1985,7 +2047,7 @@ BEGIN RAISERROR('Check 12 %s', 0, 1, @d) WITH NOWAIT; INSERT - #deadlock_findings WITH (TABLOCKX) + #deadlock_findings WITH(TABLOCKX) ( check_id, database_name, @@ -1994,17 +2056,24 @@ BEGIN finding ) SELECT - 12, - DB_NAME(aj.database_id), - 'SQLAgent - Job: ' + aj.job_name + ' Step: ' + aj.step_name, - 'Agent Job Deadlocks', - RTRIM(COUNT_BIG(*)) + ' deadlocks from this Agent Job and Step' + check_id = 12, + database_name = DB_NAME(aj.database_id), + object_name = + N'SQLAgent - Job: ' + + aj.job_name + + N' Step: ' + + aj.step_name, + finding_group = N'Agent Job Deadlocks', + finding = + N'There have been ' + + RTRIM(COUNT_BIG(*)) + + N' deadlocks from this Agent Job and Step.' FROM #agent_job AS aj GROUP BY DB_NAME(aj.database_id), aj.job_name, aj.step_name - OPTION (RECOMPILE); + OPTION(RECOMPILE); /*Check 13 is total parallel deadlocks*/ @@ -2012,7 +2081,7 @@ BEGIN RAISERROR('Check 13 %s', 0, 1, @d) WITH NOWAIT; INSERT - #deadlock_findings WITH (TABLOCKX) + #deadlock_findings WITH(TABLOCKX) ( check_id, database_name, @@ -2023,17 +2092,19 @@ BEGIN SELECT check_id = 13, database_name = N'-', - object_name = '-', - finding_group = 'Total parallel deadlocks', - 'There have been ' + CONVERT(nvarchar(20), COUNT_BIG(DISTINCT drp.event_date)) + ' parallel deadlocks.' + object_name = N'-', + finding_group = N'Total parallel deadlocks', + finding = + N'There have been ' + CONVERT(nvarchar(20), COUNT_BIG(DISTINCT drp.event_date)) + + N' parallel deadlocks.' FROM #deadlock_resource_parallel AS drp HAVING COUNT_BIG(DISTINCT drp.event_date) > 0 - OPTION (RECOMPILE); + OPTION(RECOMPILE); /*Thank you goodnight*/ INSERT - #deadlock_findings WITH (TABLOCKX) + #deadlock_findings WITH(TABLOCKX) ( check_id, database_name, @@ -2055,318 +2126,6 @@ BEGIN CREATE CLUSTERED INDEX cx_whatever ON #deadlock_resource_parallel (event_date, owner_id); CREATE CLUSTERED INDEX cx_whatever ON #deadlock_owner_waiter (event_date, owner_id, waiter_id); - IF (@OutputDatabaseCheck = 0) - BEGIN - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Results 1 %s', 0, 1, @d) WITH NOWAIT; - - - WITH - deadlocks AS - ( - SELECT - deadlock_type = N'Regular Deadlock', - dp.event_date, - dp.id, - dp.victim_id, - dp.spid, - dp.database_id, - dp.priority, - dp.log_used, - wait_resource = dp.wait_resource COLLATE DATABASE_DEFAULT, - object_names = - CONVERT - ( - xml, - STUFF - ( - ( - SELECT DISTINCT - object_name = - NCHAR(10) + - N' ' - + ISNULL(c.object_name, N'') - + N' ' COLLATE DATABASE_DEFAULT - FROM #deadlock_owner_waiter AS c - WHERE (dp.id = c.owner_id - OR dp.victim_id = c.waiter_id) - AND dp.event_date = c.event_date - FOR XML - PATH(N''), - TYPE - ).value(N'.[1]', N'nvarchar(4000)'), - 1, - 1, - N'' - ) - ), - dp.wait_time, - dp.transaction_name, - dp.last_tran_started, - dp.last_batch_started, - dp.last_batch_completed, - dp.lock_mode, - dp.transaction_count, - dp.client_app, - dp.host_name, - dp.login_name, - dp.isolation_level, - inputbuf = dp.process_xml.value('(//process/inputbuf/text())[1]', 'nvarchar(MAX)'), - en = DENSE_RANK() OVER (ORDER BY dp.event_date), - qn = ROW_NUMBER() OVER (PARTITION BY dp.event_date ORDER BY dp.event_date) - 1, - dn = ROW_NUMBER() OVER (PARTITION BY dp.event_date, dp.id ORDER BY dp.event_date), - dp.is_victim, - owner_mode = ISNULL(dp.owner_mode, '-'), - owner_waiter_type = NULL, - owner_activity = NULL, - owner_waiter_activity = NULL, - owner_merging = NULL, - owner_spilling = NULL, - owner_waiting_to_close = NULL, - waiter_mode = ISNULL(dp.waiter_mode, '-'), - waiter_waiter_type = NULL, - waiter_owner_activity = NULL, - waiter_waiter_activity = NULL, - waiter_merging = NULL, - waiter_spilling = NULL, - waiter_waiting_to_close = NULL, - dp.deadlock_graph - FROM #deadlock_process AS dp - WHERE dp.victim_id IS NOT NULL - AND dp.is_parallel = 0 - - UNION ALL - - SELECT - deadlock_type = N'Parallel Deadlock', - dp.event_date, - dp.id, - dp.victim_id, - dp.spid, - dp.database_id, - dp.priority, - dp.log_used, - dp.wait_resource COLLATE DATABASE_DEFAULT, - object_names = - CONVERT - ( - xml, - STUFF - ( - ( - SELECT DISTINCT - object_name = - NCHAR(10) + - N' ' + - ISNULL(c.object_name, N'') + - N' ' COLLATE DATABASE_DEFAULT - FROM #deadlock_owner_waiter AS c - WHERE (dp.id = c.owner_id - OR dp.victim_id = c.waiter_id) - AND dp.event_date = c.event_date - FOR XML - PATH(N''), - TYPE - ).value(N'.[1]', N'nvarchar(4000)'), - 1, - 1, - N'' - ) - ), - dp.wait_time, - dp.transaction_name, - dp.last_tran_started, - dp.last_batch_started, - dp.last_batch_completed, - dp.lock_mode, - dp.transaction_count, - dp.client_app, - dp.host_name, - dp.login_name, - dp.isolation_level, - inputbuf = dp.process_xml.value('(//process/inputbuf/text())[1]', 'nvarchar(MAX)'), - en = DENSE_RANK() OVER (ORDER BY dp.event_date), - qn = ROW_NUMBER() OVER (PARTITION BY dp.event_date ORDER BY dp.event_date) - 1, - dn = ROW_NUMBER() OVER (PARTITION BY dp.event_date, dp.id ORDER BY dp.event_date), - is_victim = 1, - owner_mode = cao.wait_type COLLATE DATABASE_DEFAULT, - owner_waiter_type = cao.waiter_type, - owner_activity = cao.owner_activity, - owner_waiter_activity = cao.waiter_activity, - owner_merging = cao.merging, - owner_spilling = cao.spilling, - owner_waiting_to_close = cao.waiting_to_close, - waiter_mode = caw.wait_type COLLATE DATABASE_DEFAULT, - waiter_waiter_type = caw.waiter_type, - waiter_owner_activity = caw.owner_activity, - waiter_waiter_activity = caw.waiter_activity, - waiter_merging = caw.merging, - waiter_spilling = caw.spilling, - waiter_waiting_to_close = caw.waiting_to_close, - dp.deadlock_graph - FROM #deadlock_process AS dp - CROSS APPLY - ( - SELECT TOP (1) - * - FROM #deadlock_resource_parallel AS drp - WHERE drp.owner_id = dp.id - AND drp.wait_type = 'e_waitPipeNewRow' - ORDER BY drp.event_date - ) AS cao - CROSS APPLY - ( - SELECT TOP (1) - * - FROM #deadlock_resource_parallel AS drp - WHERE drp.owner_id = dp.id - AND drp.wait_type = 'e_waitPipeGetRow' - ORDER BY drp.event_date - ) AS caw - WHERE dp.is_parallel = 1 - ) - INSERT INTO - DeadLockTbl - ( - ServerName, - deadlock_type, - event_date, - database_name, - spid, - deadlock_group, - query, - object_names, - isolation_level, - owner_mode, - waiter_mode, - transaction_count, - login_name, - host_name, - client_app, - wait_time, - wait_resource, - priority, - log_used, - last_tran_started, - last_batch_started, - last_batch_completed, - transaction_name, - owner_waiter_type, - owner_activity, - owner_waiter_activity, - owner_merging, - owner_spilling, - owner_waiting_to_close, - waiter_waiter_type, - waiter_owner_activity, - waiter_waiter_activity, - waiter_merging, - waiter_spilling, - waiter_waiting_to_close, - deadlock_graph - ) - SELECT - @ServerName, - d.deadlock_type, - d.event_date, - database_name = DB_NAME(d.database_id), - d.spid, - deadlock_group = - 'Deadlock #' + - CONVERT(nvarchar(10), d.en) + - ', Query #' - + CASE - WHEN d.qn = 0 - THEN N'1' - ELSE CONVERT(nvarchar(10), d.qn) - END + CASE - WHEN d.is_victim = 1 - THEN ' - VICTIM' - ELSE '' - END, - query = CONVERT(xml, N''), - d.object_names, - d.isolation_level, - d.owner_mode, - d.waiter_mode, - d.transaction_count, - d.login_name, - d.host_name, - d.client_app, - d.wait_time, - d.wait_resource, - d.priority, - d.log_used, - d.last_tran_started, - d.last_batch_started, - d.last_batch_completed, - d.transaction_name, - /*These columns will be NULL for regular (non-parallel) deadlocks*/ - d.owner_waiter_type, - d.owner_activity, - d.owner_waiter_activity, - d.owner_merging, - d.owner_spilling, - d.owner_waiting_to_close, - d.waiter_waiter_type, - d.waiter_owner_activity, - d.waiter_waiter_activity, - d.waiter_merging, - d.waiter_spilling, - d.waiter_waiting_to_close, - d.deadlock_graph - FROM deadlocks AS d - WHERE d.dn = 1 - AND (is_victim = @VictimsOnly - OR @VictimsOnly = 0) - AND d.qn < CASE - WHEN d.deadlock_type = N'Parallel Deadlock' - THEN 2 - ELSE 2147483647 - END - AND (DB_NAME(d.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (d.event_date >= @StartDate OR @StartDate IS NULL) - AND (d.event_date < @EndDate OR @EndDate IS NULL) - AND (CONVERT(nvarchar(MAX), d.object_names) LIKE '%' + @ObjectName + '%' OR @ObjectName IS NULL) - AND (d.client_app = @AppName OR @AppName IS NULL) - AND (d.host_name = @HostName OR @HostName IS NULL) - AND (d.login_name = @LoginName OR @LoginName IS NULL) - ORDER BY - d.event_date, - is_victim DESC - OPTION (RECOMPILE, LOOP JOIN, HASH JOIN); - - DROP SYNONYM DeadLockTbl; - --done insert into blitzlock table going to insert into findings table first create synonym. - - -- RAISERROR('att deadlock findings', 0, 1) WITH NOWAIT; - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Findings %s', 0, 1, @d) WITH NOWAIT; - - INSERT INTO - DeadlockFindings - ( - ServerName, - check_id, - database_name, - object_name, - finding_group, - finding - ) - SELECT - @ServerName, - df.check_id, - df.database_name, - df.object_name, - df.finding_group, - df.finding - FROM #deadlock_findings AS df - ORDER BY df.check_id - OPTION (RECOMPILE); - - DROP SYNONYM DeadlockFindings; --done with inserting. - END; - ELSE --Output to database is not set output to client app BEGIN SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Results 1 %s', 0, 1, @d) WITH NOWAIT; @@ -2426,14 +2185,14 @@ BEGIN qn = ROW_NUMBER() OVER (PARTITION BY dp.event_date ORDER BY dp.event_date) - 1, dn = ROW_NUMBER() OVER (PARTITION BY dp.event_date, dp.id ORDER BY dp.event_date), dp.is_victim, - owner_mode = ISNULL(dp.owner_mode, '-'), + owner_mode = ISNULL(dp.owner_mode, N'-'), owner_waiter_type = NULL, owner_activity = NULL, owner_waiter_activity = NULL, owner_merging = NULL, owner_spilling = NULL, owner_waiting_to_close = NULL, - waiter_mode = ISNULL(dp.waiter_mode, '-'), + waiter_mode = ISNULL(dp.waiter_mode, N'-'), waiter_waiter_type = NULL, waiter_owner_activity = NULL, waiter_waiter_activity = NULL, @@ -2518,7 +2277,7 @@ BEGIN OUTER APPLY ( SELECT TOP (1) - * + drp.* FROM #deadlock_resource_parallel AS drp WHERE drp.owner_id = dp.id AND drp.wait_type IN @@ -2531,7 +2290,7 @@ BEGIN OUTER APPLY ( SELECT TOP (1) - * + drp.* FROM #deadlock_resource_parallel AS drp WHERE drp.owner_id = dp.id AND drp.wait_type IN @@ -2617,60 +2376,147 @@ BEGIN AND (d.login_name = @LoginName OR @LoginName IS NULL) OPTION (RECOMPILE, LOOP JOIN, HASH JOIN); - SET @deadlock_result += N' + IF (@OutputDatabaseCheck = 0) + BEGIN + SET @ExportToExcel = 1 + END; + + SET @deadlock_result += + N' SELECT - dr.deadlock_type, - dr.event_date, - dr.database_name, - dr.spid, - dr.deadlock_group, - ' + CASE @ExportToExcel - WHEN 1 - THEN N'dr.query_string AS query, - REPLACE(REPLACE(CONVERT(nvarchar(MAX), dr.object_names), '''', ''''), '''', '''') AS object_names,' - ELSE N'dr.query_xml AS query, - dr.object_names,' - END + N' - dr.isolation_level, - dr.owner_mode, - dr.waiter_mode, - dr.transaction_count, - dr.login_name, - dr.host_name, - dr.client_app, - dr.wait_time, - dr.wait_resource, - dr.priority, - dr.log_used, - dr.last_tran_started, - dr.last_batch_started, - dr.last_batch_completed, - dr.transaction_name, - dr.owner_waiter_type, - dr.owner_activity, - dr.owner_waiter_activity, - dr.owner_merging, - dr.owner_spilling, - dr.owner_waiting_to_close, - dr.waiter_waiter_type, - dr.waiter_owner_activity, - dr.waiter_waiter_activity, - dr.waiter_merging, - dr.waiter_spilling, - dr.waiter_waiting_to_close' + - CASE - @ExportToExcel - WHEN 1 - THEN N'' - ELSE N', - dr.deadlock_graph' - END - + N' + server_name = + @@SERVERNAME, + dr.deadlock_type, + dr.event_date, + dr.database_name, + dr.spid, + dr.deadlock_group, + ' + CASE @ExportToExcel + WHEN 1 + THEN N'dr.query_string AS query, + REPLACE(REPLACE(CONVERT(nvarchar(MAX), dr.object_names), '''', ''''), '''', '''') AS object_names,' + ELSE N'dr.query_xml AS query, + dr.object_names,' + END + N' + dr.isolation_level, + dr.owner_mode, + dr.waiter_mode, + dr.transaction_count, + dr.login_name, + dr.host_name, + dr.client_app, + dr.wait_time, + dr.wait_resource, + dr.priority, + dr.log_used, + dr.last_tran_started, + dr.last_batch_started, + dr.last_batch_completed, + dr.transaction_name, + dr.owner_waiter_type, + dr.owner_activity, + dr.owner_waiter_activity, + dr.owner_merging, + dr.owner_spilling, + dr.owner_waiting_to_close, + dr.waiter_waiter_type, + dr.waiter_owner_activity, + dr.waiter_waiter_activity, + dr.waiter_merging, + dr.waiter_spilling, + dr.waiter_waiting_to_close' + + CASE + @ExportToExcel + WHEN 1 + THEN N'' + ELSE N', + dr.deadlock_graph' + END + + N' FROM #deadlock_results AS dr ORDER BY dr.event_date, dr.is_victim DESC OPTION(RECOMPILE, LOOP JOIN, HASH JOIN); '; + IF (@OutputDatabaseCheck = 0) + BEGIN + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Results 1 %s', 0, 1, @d) WITH NOWAIT; + + INSERT INTO + DeadLockTbl + ( + ServerName, + deadlock_type, + event_date, + database_name, + spid, + deadlock_group, + query, + object_names, + isolation_level, + owner_mode, + waiter_mode, + transaction_count, + login_name, + host_name, + client_app, + wait_time, + wait_resource, + priority, + log_used, + last_tran_started, + last_batch_started, + last_batch_completed, + transaction_name, + owner_waiter_type, + owner_activity, + owner_waiter_activity, + owner_merging, + owner_spilling, + owner_waiting_to_close, + waiter_waiter_type, + waiter_owner_activity, + waiter_waiter_activity, + waiter_merging, + waiter_spilling, + waiter_waiting_to_close, + deadlock_graph + ) + EXEC sys.sp_executesql + @deadlock_result; + + DROP SYNONYM DeadLockTbl; + --done insert into blitzlock table going to insert into findings table first create synonym. + + -- RAISERROR('att deadlock findings', 0, 1) WITH NOWAIT; + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Findings %s', 0, 1, @d) WITH NOWAIT; + + INSERT INTO + DeadlockFindings + ( + ServerName, + check_id, + database_name, + object_name, + finding_group, + finding + ) + SELECT + @ServerName, + df.check_id, + df.database_name, + df.object_name, + df.finding_group, + df.finding + FROM #deadlock_findings AS df + ORDER BY df.check_id + OPTION(RECOMPILE); + + DROP SYNONYM DeadlockFindings; --done with inserting. + END; + ELSE --Output to database is not set output to client app EXEC sys.sp_executesql @deadlock_result; @@ -2687,7 +2533,7 @@ BEGIN df.finding FROM #deadlock_findings AS df ORDER BY df.check_id - OPTION (RECOMPILE); + OPTION(RECOMPILE); SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Done %s', 0, 1, @d) WITH NOWAIT; @@ -2697,27 +2543,27 @@ BEGIN IF @Debug = 1 BEGIN SELECT table_name = '#deadlock_data', * FROM #deadlock_data AS dd - OPTION (RECOMPILE); + OPTION(RECOMPILE); SELECT table_name = '#deadlock_resource', * FROM #deadlock_resource AS dr - OPTION (RECOMPILE); + OPTION(RECOMPILE); SELECT table_name = '#deadlock_resource_parallel', * FROM #deadlock_resource_parallel AS drp - OPTION (RECOMPILE); + OPTION(RECOMPILE); SELECT table_name = '#deadlock_owner_waiter', * FROM #deadlock_owner_waiter AS dow - OPTION (RECOMPILE); + OPTION(RECOMPILE); SELECT table_name = '#deadlock_process', * FROM #deadlock_process AS dp - OPTION (RECOMPILE); + OPTION(RECOMPILE); SELECT table_name = '#deadlock_stack', * FROM #deadlock_stack AS ds - OPTION (RECOMPILE); + OPTION(RECOMPILE); SELECT table_name = '#deadlock_results', * FROM #deadlock_results AS dr - OPTION (RECOMPILE); + OPTION(RECOMPILE); END; -- End debug END; --Final End From 36f79755e4e0cbcf6a0552522ab6c86c84fe3d71 Mon Sep 17 00:00:00 2001 From: Adrian Buckman <33764798+Adedba@users.noreply.github.com> Date: Fri, 18 Nov 2022 22:46:08 +0000 Subject: [PATCH 332/662] Include tempdb internal allocations #3174 - Included tempdb internal object allocations to tempdb_allocations_mb column. --- sp_BlitzWho.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sp_BlitzWho.sql b/sp_BlitzWho.sql index 8fa278823..c144846fd 100644 --- a/sp_BlitzWho.sql +++ b/sp_BlitzWho.sql @@ -859,7 +859,7 @@ BEGIN OUTER APPLY sys.dm_exec_sql_text(COALESCE(r.sql_handle, blocked.sql_handle)) AS dest OUTER APPLY sys.dm_exec_query_plan(r.plan_handle) AS derp OUTER APPLY ( - SELECT CONVERT(DECIMAL(38,2), SUM( (((tsu.user_objects_alloc_page_count - user_objects_dealloc_page_count) * 8) / 1024.)) ) AS tempdb_allocations_mb + SELECT CONVERT(DECIMAL(38,2), SUM( ((((tsu.user_objects_alloc_page_count - user_objects_dealloc_page_count) + (tsu.internal_objects_alloc_page_count - internal_objects_dealloc_page_count)) * 8) / 1024.)) ) AS tempdb_allocations_mb FROM sys.dm_db_task_space_usage tsu WHERE tsu.request_id = r.request_id AND tsu.session_id = r.session_id @@ -1152,7 +1152,7 @@ IF @ProductVersionMajor >= 11 OUTER APPLY sys.dm_exec_sql_text(COALESCE(r.sql_handle, blocked.sql_handle)) AS dest OUTER APPLY sys.dm_exec_query_plan(r.plan_handle) AS derp OUTER APPLY ( - SELECT CONVERT(DECIMAL(38,2), SUM( (((tsu.user_objects_alloc_page_count - user_objects_dealloc_page_count) * 8) / 1024.)) ) AS tempdb_allocations_mb + SELECT CONVERT(DECIMAL(38,2), SUM( ((((tsu.user_objects_alloc_page_count - user_objects_dealloc_page_count) + (tsu.internal_objects_alloc_page_count - internal_objects_dealloc_page_count)) * 8) / 1024.)) ) AS tempdb_allocations_mb FROM sys.dm_db_task_space_usage tsu WHERE tsu.request_id = r.request_id AND tsu.session_id = r.session_id From 23ccb7d37a752cdbdb1670a7e554ed744bfc4f89 Mon Sep 17 00:00:00 2001 From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com> Date: Mon, 21 Nov 2022 17:51:30 -0500 Subject: [PATCH 333/662] Update sp_BlitzLock.sql Fixes a transient truncation error with agent job names. --- sp_BlitzLock.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index 4c5689841..82d247ea6 100644 --- a/sp_BlitzLock.sql +++ b/sp_BlitzLock.sql @@ -383,7 +383,7 @@ BEGIN transaction_count bigint, login_name nvarchar(256), host_name nvarchar(256), - client_app nvarchar(256), + client_app nvarchar(512), wait_time bigint, wait_resource nvarchar(max), priority smallint, @@ -871,7 +871,7 @@ BEGIN last_batch_completed = ca.dp.value('@lastbatchcompleted', 'datetime'), lock_mode = ca.dp.value('@lockMode', 'nvarchar(256)'), transaction_count = ca.dp.value('@trancount', 'bigint'), - client_app = ca.dp.value('@clientapp', 'nvarchar(256)'), + client_app = ca.dp.value('@clientapp', 'nvarchar(1024)'), host_name = ca.dp.value('@hostname', 'nvarchar(256)'), login_name = ca.dp.value('@loginname', 'nvarchar(256)'), isolation_level = ca.dp.value('@isolationlevel', 'nvarchar(256)'), From 53f5df6becc698a3f685ee27805354b221b46bbd Mon Sep 17 00:00:00 2001 From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com> Date: Mon, 21 Nov 2022 20:43:51 -0500 Subject: [PATCH 334/662] Update sp_BlitzLock.sql WIP --- sp_BlitzLock.sql | 726 ++++++++++++++++++++++++++++++++++------------- 1 file changed, 526 insertions(+), 200 deletions(-) diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index 82d247ea6..6d7f806b7 100644 --- a/sp_BlitzLock.sql +++ b/sp_BlitzLock.sql @@ -4,7 +4,6 @@ BEGIN END; GO - ALTER PROCEDURE dbo.sp_BlitzLock ( @@ -36,8 +35,9 @@ BEGIN SET NOCOUNT, XACT_ABORT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.11', @VersionDate = '20221013'; - + SELECT + @Version = '8.11', + @VersionDate = '20221013'; IF (@VersionCheckMode = 1) BEGIN @@ -99,7 +99,7 @@ BEGIN MIT License - Copyright (c) 2021 Brent Ozar Unlimited + Copyright (c) 2022 Brent Ozar Unlimited Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -302,6 +302,7 @@ BEGIN @StringToExecuteParams = N'@r sysname OUTPUT'; + IF @Debug = 1 BEGIN PRINT @StringToExecute; END; EXEC sys.sp_executesql @StringToExecute, @StringToExecuteParams, @@ -339,6 +340,7 @@ BEGIN @ObjectFullName + N' ADD spid smallint NULL;'; + IF @Debug = 1 BEGIN PRINT @StringToExecute; END; EXEC sys.sp_executesql @StringToExecute; @@ -354,6 +356,7 @@ BEGIN @ObjectFullName + N' ADD wait_resource nvarchar(MAX) NULL;'; + IF @Debug = 1 BEGIN PRINT @StringToExecute; END; EXEC sys.sp_executesql @StringToExecute; END; @@ -407,6 +410,7 @@ BEGIN deadlock_graph xml )'; + IF @Debug = 1 BEGIN PRINT @StringToExecute; END; EXEC sys.sp_executesql @StringToExecute; @@ -419,6 +423,7 @@ BEGIN @StringToExecuteParams = N'@r sysname OUTPUT'; + IF @Debug = 1 BEGIN PRINT @StringToExecute; END; EXEC sys.sp_executesql @StringToExecute, @StringToExecuteParams, @@ -446,6 +451,7 @@ BEGIN finding nvarchar(4000) );'; + IF @Debug = 1 BEGIN PRINT @StringToExecute; END; EXEC sys.sp_executesql @StringToExecute; END; @@ -475,6 +481,7 @@ BEGIN N'.' + @OutputTableFindings; + IF @Debug = 1 BEGIN PRINT @StringToExecute; END; EXEC sys.sp_executesql @StringToExecute; @@ -501,6 +508,7 @@ BEGIN N'.' + @OutputTableName; + IF @Debug = 1 BEGIN PRINT @StringToExecute; END; EXEC sys.sp_executesql @StringToExecute; END; @@ -568,7 +576,8 @@ BEGIN BEGIN IF @Azure = 0 BEGIN - RAISERROR('@TargetSessionType is ring_buffer, inserting XML for non-Azure', 0, 1) WITH NOWAIT; + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('@TargetSessionType is ring_buffer, inserting XML for non-Azure at %s', 0, 1, @d) WITH NOWAIT; INSERT #x WITH(TABLOCKX) @@ -583,13 +592,14 @@ BEGIN WHERE s.name = @EventSessionName AND t.target_name = N'ring_buffer'; + SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; END; IF @Azure = 1 BEGIN SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('@TargetSessionType is ring_buffer, inserting XML for Azure', 0, 1) WITH NOWAIT; + RAISERROR('@TargetSessionType is ring_buffer, inserting XML for Azure at %s', 0, 1, @d) WITH NOWAIT; INSERT #x WITH(TABLOCKX) @@ -604,11 +614,11 @@ BEGIN WHERE s.name = @EventSessionName AND t.target_name = N'ring_buffer'; + SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; END; END; - IF ( @TargetSessionType LIKE 'event%' @@ -617,7 +627,8 @@ BEGIN BEGIN IF @Azure = 0 BEGIN - RAISERROR('@TargetSessionType is event_file, assigning XML for non-Azure', 0, 1) WITH NOWAIT; + RAISERROR('@TargetSessionType is event_file, assigning XML for non-Azure at', 0, 1) WITH NOWAIT; + SELECT @SessionId = t.event_session_id, @TargetSessionId = t.target_id @@ -627,7 +638,7 @@ BEGIN WHERE t.name = @TargetSessionType AND s.name = @EventSessionName; - RAISERROR('Assigning @FileName...', 0, 1) WITH NOWAIT; + RAISERROR('Assigning @FileName...', 0, 1) WITH NOWAIT; SELECT @FileName = CASE @@ -659,7 +670,7 @@ BEGIN WHERE t.name = @TargetSessionType AND s.name = @EventSessionName; - RAISERROR('Assigning @FileName...', 0, 1) WITH NOWAIT; + RAISERROR('Assigning @FileName...', 0, 1) WITH NOWAIT; SELECT @FileName = CASE @@ -692,6 +703,7 @@ BEGIN LEFT JOIN #t AS t ON 1 = 1; + SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; END; @@ -724,6 +736,7 @@ BEGIN AND e.x.exist('/event/@timestamp[. >= sql:variable("@StartDate")]') = 1 AND e.x.exist('/event/@timestamp[. < sql:variable("@EndDate")]') = 1; + SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; END; @@ -735,7 +748,7 @@ BEGIN ) BEGIN SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Inserting to #deadlock_data for event file data', 0, 1) WITH NOWAIT + RAISERROR('Inserting to #deadlock_data for event file data', 0, 1) WITH NOWAIT; INSERT #deadlock_data WITH(TABLOCKX) @@ -752,14 +765,10 @@ BEGIN AND e.x.exist('/event/@timestamp[. >= sql:variable("@StartDate")]') = 1 AND e.x.exist('/event/@timestamp[. < sql:variable("@EndDate")]') = 1; + SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; END; - IF @Debug = 1 - BEGIN - SELECT table_name = N'#deadlock_data', bx.* FROM #deadlock_data AS bx; - END; - /*Grab the initial set of xml to parse*/ IF ( @@ -770,10 +779,11 @@ BEGIN SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Grab the initial set of xml to parse at %s', 0, 1, @d) WITH NOWAIT; - SET STATISTICS XML ON; + IF @Debug = 1 BEGIN SET STATISTICS XML ON; END; SELECT - deadlock_xml = TRY_CAST(event_data AS xml) + deadlock_xml = + TRY_CAST(event_data AS xml) INTO #xml FROM sys.fn_xe_file_target_read_file('system_health*.xel', NULL, NULL, NULL) LEFT JOIN #t AS t @@ -781,7 +791,7 @@ BEGIN DELETE x FROM #xml AS x - WHERE x.deadlock_xml IS NULL + WHERE x.deadlock_xml IS NULL; INSERT #deadlock_data WITH(TABLOCKX) @@ -796,14 +806,15 @@ BEGIN AND e.x.exist('/event/@timestamp[. < sql:variable("@EndDate")]') = 1 OPTION(RECOMPILE); - SET STATISTICS XML OFF; + IF @Debug = 1 BEGIN SET STATISTICS XML OFF; END; + SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; END; /*Parse process and input buffer xml*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Parse process and input buffer xml %s', 0, 1, @d) WITH NOWAIT; + RAISERROR('Initial Parse process and input buffer xml %s', 0, 1, @d) WITH NOWAIT; SELECT d1.deadlock_xml, @@ -813,7 +824,15 @@ BEGIN is_parallel_batch = d1.deadlock_xml.exist('//deadlock/resource-list/SyncPoint'), deadlock_graph = d1.deadlock_xml.query('/event/data/value/deadlock') INTO #dd - FROM #deadlock_data AS d1; + FROM #deadlock_data AS d1 + LEFT JOIN #t AS t + ON 1 = 1; + + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Final Parse process and input buffer xml %s', 0, 1, @d) WITH NOWAIT; SELECT q.event_date, @@ -824,6 +843,12 @@ BEGIN q.id, q.spid, q.database_id, + database_name = + ISNULL + ( + DB_NAME(q.database_id), + N'UNKNOWN' + ), q.priority, q.log_used, q.wait_resource, @@ -884,8 +909,12 @@ BEGIN AND (ca.dp.exist('@loginname[. = sql:variable("@LoginName")]') = 1 OR @LoginName IS NULL) ) AS q CROSS APPLY q.deadlock_xml.nodes('//deadlock/process-list/process/inputbuf') AS ca2(ib) + LEFT JOIN #t AS t + ON 1 = 1 OPTION(RECOMPILE); + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; /*Parse execution stack xml*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); @@ -902,6 +931,9 @@ BEGIN WHERE (ca.dp.exist('@procname[. = sql:variable("@StoredProcName")]') = 1 OR @StoredProcName IS NULL) OPTION(RECOMPILE); + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + /*Grab the full resource list*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); @@ -930,6 +962,9 @@ BEGIN ) AS dr OPTION(RECOMPILE); + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + /*Parse object locks*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Parse object locks %s', 0, 1, @d) WITH NOWAIT; @@ -937,6 +972,12 @@ BEGIN SELECT DISTINCT ca.event_date, ca.database_id, + database_name = + ISNULL + ( + DB_NAME(ca.database_id), + N'UNKNOWN' + ), ca.object_name, ca.lock_mode, ca.index_name, @@ -965,6 +1006,8 @@ BEGIN WHERE (ca.object_name = @ObjectName OR @ObjectName IS NULL) OPTION(RECOMPILE); + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; /*Parse page locks*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); @@ -975,6 +1018,12 @@ BEGIN SELECT DISTINCT ca.event_date, ca.database_id, + database_name = + ISNULL + ( + DB_NAME(ca.database_id), + N'UNKNOWN' + ), ca.object_name, ca.lock_mode, ca.index_name, @@ -1001,16 +1050,24 @@ BEGIN CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l) OPTION(RECOMPILE); + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; /*Parse key locks*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Parse key locks %s', 0, 1, @d) WITH NOWAIT; + INSERT #deadlock_owner_waiter WITH(TABLOCKX) SELECT DISTINCT ca.event_date, ca.database_id, + database_name = + ISNULL + ( + DB_NAME(ca.database_id), + N'UNKNOWN' + ), ca.object_name, ca.lock_mode, ca.index_name, @@ -1037,6 +1094,8 @@ BEGIN CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l) OPTION(RECOMPILE); + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; /*Parse RID locks*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); @@ -1047,6 +1106,12 @@ BEGIN SELECT DISTINCT ca.event_date, ca.database_id, + database_name = + ISNULL + ( + DB_NAME(ca.database_id), + N'UNKNOWN' + ), ca.object_name, ca.lock_mode, ca.index_name, @@ -1073,6 +1138,8 @@ BEGIN CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l) OPTION(RECOMPILE); + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; /*Parse row group locks*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); @@ -1083,6 +1150,12 @@ BEGIN SELECT DISTINCT ca.event_date, ca.database_id, + database_name = + ISNULL + ( + DB_NAME(ca.database_id), + N'UNKNOWN' + ), ca.object_name, ca.lock_mode, ca.index_name, @@ -1109,6 +1182,11 @@ BEGIN CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l) OPTION(RECOMPILE); + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Fixing heaps in #deadlock_owner_waiter %s', 0, 1, @d) WITH NOWAIT; UPDATE d @@ -1123,6 +1201,8 @@ BEGIN ) OPTION(RECOMPILE); + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; /*Parse parallel deadlocks*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); @@ -1165,6 +1245,8 @@ BEGIN CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l) OPTION(RECOMPILE); + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; /*Get rid of parallel noise*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); @@ -1191,6 +1273,8 @@ BEGIN WHERE c.rn > 1 OPTION(RECOMPILE); + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; /*Get rid of nonsense*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); @@ -1201,6 +1285,8 @@ BEGIN WHERE dow.owner_id = dow.waiter_id OPTION(RECOMPILE); + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; /*Add some nonsense*/ ALTER TABLE @@ -1235,6 +1321,8 @@ BEGIN WHERE dp.is_victim = 0 OPTION(RECOMPILE); + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Update some nonsense part 2 %s', 0, 1, @d) WITH NOWAIT; @@ -1250,18 +1338,26 @@ BEGIN WHERE dp.is_victim = 1 OPTION(RECOMPILE); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; /*Get Agent Job and Step names*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Get Agent Job and Step names %s', 0, 1, @d) WITH NOWAIT; + SELECT - *, + x.event_date, + x.victim_id, + x.id, + x.database_id, + x.client_app, + x.job_id, + x.step_id, job_id_guid = CONVERT ( uniqueidentifier, - CONVERT(xml, '').value('xs:hexBinary(substring(sql:column("x.job_id"), 0) )','BINARY(16)')) + TRY_CAST(N'' AS xml).value('xs:hexBinary(substring(sql:column("x.job_id"), 0) )','BINARY(16)') + ) INTO #agent_job FROM ( @@ -1275,31 +1371,29 @@ BEGIN SUBSTRING ( dp.client_app, - CHARINDEX('0x', dp.client_app) + LEN('0x'), + CHARINDEX(N'0x', dp.client_app) + LEN(N'0x'), 32 ), step_id = SUBSTRING ( dp.client_app, - CHARINDEX(': Step ', dp.client_app) + LEN(': Step '), - CHARINDEX(')', dp.client_app, CHARINDEX(': Step ', dp.client_app)) - - (CHARINDEX(': Step ', dp.client_app) + LEN(': Step ')) + CHARINDEX(N': Step ', dp.client_app) + LEN(N': Step '), + CHARINDEX(N')', dp.client_app, CHARINDEX(N': Step ', dp.client_app)) - + (CHARINDEX(N': Step ', dp.client_app) + LEN(N': Step ')) ) FROM #deadlock_process AS dp - WHERE dp.client_app LIKE 'SQLAgent - %' - AND dp.client_app <> 'SQLAgent - Initial Boot Probe' + WHERE dp.client_app LIKE N'SQLAgent - %' + AND dp.client_app <> N'SQLAgent - Initial Boot Probe' ) AS x OPTION(RECOMPILE); - ALTER TABLE #agent_job ADD job_name nvarchar(256), step_name nvarchar(256); - IF ( @Azure = 0 @@ -1322,8 +1416,8 @@ BEGIN EXEC sys.sp_executesql @StringToExecute; - END; + END; UPDATE dp @@ -1344,26 +1438,43 @@ BEGIN AND dp.id = aj.id OPTION(RECOMPILE); + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; /*Get each and every table of all databases*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Inserting to @sysAssObjId %s', 0, 1, @d) WITH NOWAIT; + INSERT INTO @sysAssObjId EXECUTE sys.sp_MSforeachdb - N'USE [?]; - SELECT - DB_ID() as database_id, - p.partition_id, - s.name as schema_name, - t.name as table_name - FROM sys.partitions p - JOIN sys.tables t - ON t.object_id = p.object_id - JOIN sys.schemas s - ON s.schema_id = t.schema_id - WHERE s.name is not NULL - AND t.name is not NULL'; + N' + USE [?]; + + SELECT + database_id = + DB_ID(), + p.partition_id, + s.name as schema_name, + t.name as table_name + FROM sys.partitions p + JOIN sys.tables t + ON t.object_id = p.object_id + JOIN sys.schemas s + ON s.schema_id = t.schema_id + WHERE s.name is not NULL + AND t.name is not NULL + AND EXISTS + ( + SELECT + 1/0 + FROM #deadlock_process AS dp + WHERE dp.database_id = DB_ID() + );'; + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; /*Begin checks based on parsed values*/ @@ -1382,24 +1493,29 @@ BEGIN ) SELECT check_id = 1, - database_name = DB_NAME(dp.database_id), + dp.database_name, object_name = N'-', - finding_group = N'Total database locks', + finding_group = N'Total Database Deadlocks', finding = N'This database had ' + - CONVERT(nvarchar(20), COUNT_BIG(DISTINCT dp.event_date)) + + CONVERT + ( + nvarchar(20), + COUNT_BIG(DISTINCT dp.event_date) + ) + N' deadlocks.' FROM #deadlock_process AS dp WHERE 1 = 1 - AND (DB_NAME(dp.database_id) = @DatabaseName OR @DatabaseName IS NULL) + AND (dp.database_name = @DatabaseName OR @DatabaseName IS NULL) AND (dp.event_date >= @StartDate OR @StartDate IS NULL) AND (dp.event_date < @EndDate OR @EndDate IS NULL) AND (dp.client_app = @AppName OR @AppName IS NULL) AND (dp.host_name = @HostName OR @HostName IS NULL) AND (dp.login_name = @LoginName OR @LoginName IS NULL) - GROUP BY DB_NAME(dp.database_id) + GROUP BY dp.database_name OPTION(RECOMPILE); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; /*Check 2 is deadlocks by object*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); @@ -1416,24 +1532,34 @@ BEGIN ) SELECT check_id = 2, - database_name = ISNULL(DB_NAME(dow.database_id), N'UNKNOWN'), - object_name = ISNULL(dow.object_name, N'UNKNOWN'), - finding_group = N'Total object deadlocks', + dow.database_name, + object_name = + ISNULL + ( + dow.object_name, + N'UNKNOWN' + ), + finding_group = N'Total Object Deadlocks', finding = N'This object was involved in ' + - CONVERT(nvarchar(20), COUNT_BIG(DISTINCT dow.event_date)) + + CONVERT + ( + nvarchar(20), + COUNT_BIG(DISTINCT dow.event_date) + ) + N' deadlock(s).' FROM #deadlock_owner_waiter AS dow WHERE 1 = 1 - AND (DB_NAME(dow.database_id) = @DatabaseName OR @DatabaseName IS NULL) + AND (dow.database_id = @DatabaseName OR @DatabaseName IS NULL) AND (dow.event_date >= @StartDate OR @StartDate IS NULL) AND (dow.event_date < @EndDate OR @EndDate IS NULL) AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) GROUP BY - DB_NAME(dow.database_id), + dow.database_name, dow.object_name OPTION(RECOMPILE); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; /*Check 2 continuation, number of locks per index*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); @@ -1450,16 +1576,20 @@ BEGIN ) SELECT check_id = 2, - database_name = ISNULL(DB_NAME(dow.database_id), N'UNKNOWN'), + dow.database_name, index_name = dow.index_name, - finding_group = N'Total index deadlocks', + finding_group = N'Total Index Deadlocks', finding = N'This index was involved in ' + - CONVERT(nvarchar(20), COUNT_BIG(DISTINCT dow.event_date)) + + CONVERT + ( + nvarchar(20), + COUNT_BIG(DISTINCT dow.event_date) + ) + N' deadlock(s).' FROM #deadlock_owner_waiter AS dow WHERE 1 = 1 - AND (DB_NAME(dow.database_id) = @DatabaseName OR @DatabaseName IS NULL) + AND (dow.database_id = @DatabaseName OR @DatabaseName IS NULL) AND (dow.event_date >= @StartDate OR @StartDate IS NULL) AND (dow.event_date < @EndDate OR @EndDate IS NULL) AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) @@ -1470,16 +1600,16 @@ BEGIN ) AND dow.index_name IS NOT NULL GROUP BY - DB_NAME(dow.database_id), + dow.database_name, dow.index_name OPTION(RECOMPILE); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; /*Check 2 continuation, number of locks per heap*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Check 2 heaps %s', 0, 1, @d) WITH NOWAIT; - INSERT #deadlock_findings WITH(TABLOCKX) ( @@ -1491,16 +1621,20 @@ BEGIN ) SELECT check_id = 2, - database_name = ISNULL(DB_NAME(dow.database_id), N'UNKNOWN'), + dow.database_name, index_name = dow.index_name, - finding_group = N'Total heap deadlocks', + finding_group = N'Total Heap Deadlocks', finding = N'This heap was involved in ' + - CONVERT(nvarchar(20), COUNT_BIG(DISTINCT dow.event_date)) + + CONVERT + ( + nvarchar(20), + COUNT_BIG(DISTINCT dow.event_date) + ) + N' deadlock(s).' FROM #deadlock_owner_waiter AS dow WHERE 1 = 1 - AND (DB_NAME(dow.database_id) = @DatabaseName OR @DatabaseName IS NULL) + AND (dow.database_id = @DatabaseName OR @DatabaseName IS NULL) AND (dow.event_date >= @StartDate OR @StartDate IS NULL) AND (dow.event_date < @EndDate OR @EndDate IS NULL) AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) @@ -1510,10 +1644,11 @@ BEGIN N'RID' ) GROUP BY - DB_NAME(dow.database_id), + dow.database_name, dow.index_name OPTION(RECOMPILE); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; /*Check 3 looks for Serializable locking*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); @@ -1530,24 +1665,30 @@ BEGIN ) SELECT check_id = 3, - database_name = DB_NAME(dp.database_id), + database_name = + dp.database_name, object_name = N'-', - finding_group = N'Serializable locking', + finding_group = N'Serializable Deadlocking', finding = N'This database has had ' + - CONVERT(nvarchar(20), COUNT_BIG(*)) + - N' instances of serializable deadlocks.' + CONVERT + ( + nvarchar(20), + COUNT_BIG(*) + ) + + N' instances of Serializable deadlocks.' FROM #deadlock_process AS dp WHERE dp.isolation_level LIKE 'serializable%' - AND (DB_NAME(dp.database_id) = @DatabaseName OR @DatabaseName IS NULL) + AND (dp.database_name = @DatabaseName OR @DatabaseName IS NULL) AND (dp.event_date >= @StartDate OR @StartDate IS NULL) AND (dp.event_date < @EndDate OR @EndDate IS NULL) AND (dp.client_app = @AppName OR @AppName IS NULL) AND (dp.host_name = @HostName OR @HostName IS NULL) AND (dp.login_name = @LoginName OR @LoginName IS NULL) - GROUP BY DB_NAME(dp.database_id) + GROUP BY dp.database_name OPTION(RECOMPILE); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; /*Check 4 looks for Repeatable Read locking*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); @@ -1564,24 +1705,25 @@ BEGIN ) SELECT check_id = 4, - database_name = DB_NAME(dp.database_id), + dp.database_name, object_name = N'-', - finding_group = N'Repeatable Read locking', + finding_group = N'Repeatable Read Deadlocking', finding = N'This database has had ' + CONVERT(nvarchar(20), COUNT_BIG(*)) + - N' instances of repeatable read deadlocks.' + N' instances of Repeatable Read deadlocks.' FROM #deadlock_process AS dp WHERE dp.isolation_level LIKE N'repeatable read%' - AND (DB_NAME(dp.database_id) = @DatabaseName OR @DatabaseName IS NULL) + AND (dp.database_name = @DatabaseName OR @DatabaseName IS NULL) AND (dp.event_date >= @StartDate OR @StartDate IS NULL) AND (dp.event_date < @EndDate OR @EndDate IS NULL) AND (dp.client_app = @AppName OR @AppName IS NULL) AND (dp.host_name = @HostName OR @HostName IS NULL) AND (dp.login_name = @LoginName OR @LoginName IS NULL) - GROUP BY DB_NAME(dp.database_id) + GROUP BY dp.database_name OPTION(RECOMPILE); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; /*Check 5 breaks down app, host, and login information*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); @@ -1598,58 +1740,80 @@ BEGIN ) SELECT check_id = 5, - database_name = DB_NAME(dp.database_id), + database_name = + dp.database_name, object_name = N'-', finding_group = N'Login, App, and Host locking', finding = N'This database has had ' + - CONVERT(nvarchar(20), COUNT_BIG(DISTINCT dp.event_date)) + + CONVERT + ( + nvarchar(20), + COUNT_BIG(DISTINCT dp.event_date) + ) + N' instances of deadlocks involving the login ' + - ISNULL(dp.login_name, N'UNKNOWN') + + ISNULL + ( + dp.login_name, N'UNKNOWN' + ) + N' from the application ' + - ISNULL(dp.client_app, N'UNKNOWN') + + ISNULL + ( + dp.client_app, N'UNKNOWN' + ) + N' on host ' + - ISNULL(dp.host_name, N'UNKNOWN') + ISNULL + ( + dp.host_name, N'UNKNOWN' + ) + + N'.' FROM #deadlock_process AS dp WHERE 1 = 1 - AND (DB_NAME(dp.database_id) = @DatabaseName OR @DatabaseName IS NULL) + AND (dp.database_name = @DatabaseName OR @DatabaseName IS NULL) AND (dp.event_date >= @StartDate OR @StartDate IS NULL) AND (dp.event_date < @EndDate OR @EndDate IS NULL) AND (dp.client_app = @AppName OR @AppName IS NULL) AND (dp.host_name = @HostName OR @HostName IS NULL) AND (dp.login_name = @LoginName OR @LoginName IS NULL) GROUP BY - DB_NAME(dp.database_id), + dp.database_name, dp.login_name, dp.client_app, dp.host_name OPTION(RECOMPILE); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; /*Check 6 breaks down the types of locks (object, page, key, etc.)*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Check 6 %s', 0, 1, @d) WITH NOWAIT; - WITH lock_types AS ( SELECT - database_name = DB_NAME(dp.database_id), + database_name = + dp.database_name, dow.object_name, lock = CASE WHEN CHARINDEX(N':', dp.wait_resource) > 0 - THEN SUBSTRING(dp.wait_resource, 1, CHARINDEX(N':', dp.wait_resource) - 1) + THEN SUBSTRING + ( + dp.wait_resource, + 1, + CHARINDEX(N':', dp.wait_resource) - 1 + ) ELSE dp.wait_resource END, lock_count = CONVERT(nvarchar(20), COUNT_BIG(DISTINCT dp.id)) FROM #deadlock_process AS dp JOIN #deadlock_owner_waiter AS dow - ON dp.id = dow.owner_id - AND dp.event_date = dow.event_date + ON (dp.id = dow.owner_id + OR dp.victim_id = dow.waiter_id) + AND dp.event_date = dow.event_date WHERE 1 = 1 - AND (DB_NAME(dp.database_id) = @DatabaseName OR @DatabaseName IS NULL) + AND (dp.database_name = @DatabaseName OR @DatabaseName IS NULL) AND (dp.event_date >= @StartDate OR @StartDate IS NULL) AND (dp.event_date < @EndDate OR @EndDate IS NULL) AND (dp.client_app = @AppName OR @AppName IS NULL) @@ -1658,8 +1822,17 @@ BEGIN AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) AND dow.object_name IS NOT NULL GROUP BY - DB_NAME(dp.database_id), - SUBSTRING(dp.wait_resource, 1, CHARINDEX(':', dp.wait_resource) - 1), + dp.database_name, + CASE + WHEN CHARINDEX(N':', dp.wait_resource) > 0 + THEN SUBSTRING + ( + dp.wait_resource, + 1, + CHARINDEX(N':', dp.wait_resource) - 1 + ) + ELSE dp.wait_resource + END, dow.object_name ) INSERT @@ -1671,17 +1844,17 @@ BEGIN finding_group, finding ) - SELECT DISTINCT + SELECT check_id = 6, lt.database_name, lt.object_name, finding_group = N'Types of locks by object', finding = - N'This object has had ' + + N'This object has had ' + STUFF ( ( - SELECT DISTINCT + SELECT N', ' + lt2.lock_count + N' ' + @@ -1700,6 +1873,8 @@ BEGIN FROM lock_types AS lt OPTION(RECOMPILE); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + /*Check 7 gives you more info queries for sp_BlitzCache & BlitzQueryStore*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Check 7 part 1 %s', 0, 1, @d) WITH NOWAIT; @@ -1711,11 +1886,14 @@ BEGIN ds.id, ds.proc_name, ds.event_date, - database_name = PARSENAME(ds.proc_name, 3), - schema_name = PARSENAME(ds.proc_name, 2), - proc_only_name = PARSENAME(ds.proc_name, 1), + database_name = + PARSENAME(ds.proc_name, 3), + schema_name = + PARSENAME(ds.proc_name, 2), + proc_only_name = + PARSENAME(ds.proc_name, 1), sql_handle_csv = - N'''' + + N'''' + STUFF ( ( @@ -1734,7 +1912,7 @@ BEGIN 1, N'' ) + - N'''' + N'''' FROM #deadlock_stack AS ds WHERE ds.sql_handle <> 0x GROUP BY @@ -1756,7 +1934,7 @@ BEGIN ) SELECT DISTINCT check_id = 7, - database_name = ISNULL(DB_NAME(dow.database_id), N'UNKNOWN'), + dow.database_name, object_name = ds.proc_name, finding_group = N'More Info - Query', finding = N'EXEC sp_BlitzCache ' + @@ -1770,12 +1948,13 @@ BEGIN ON dow.owner_id = ds.id AND dow.event_date = ds.event_date WHERE 1 = 1 - AND (DB_NAME(dow.database_id) = @DatabaseName OR @DatabaseName IS NULL) + AND (dow.database_id = @DatabaseName OR @DatabaseName IS NULL) AND (dow.event_date >= @StartDate OR @StartDate IS NULL) AND (dow.event_date < @EndDate OR @EndDate IS NULL) AND (dow.object_name = @StoredProcName OR @StoredProcName IS NULL) OPTION(RECOMPILE); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; IF (@ProductVersionMajor >= 13 OR @Azure = 1) BEGIN @@ -1790,9 +1969,12 @@ BEGIN ds.sql_handle, ds.proc_name, ds.event_date, - database_name = PARSENAME(ds.proc_name, 3), - schema_name = PARSENAME(ds.proc_name, 2), - proc_only_name = PARSENAME(ds.proc_name, 1) + database_name = + PARSENAME(ds.proc_name, 3), + schema_name = + PARSENAME(ds.proc_name, 2), + proc_only_name = + PARSENAME(ds.proc_name, 1) FROM #deadlock_stack AS ds ) INSERT @@ -1806,27 +1988,29 @@ BEGIN ) SELECT DISTINCT check_id = 7, - database_name = DB_NAME(dow.database_id), + dow.database_name, object_name = ds.proc_name, finding_group = N'More Info - Query', finding = - N'EXEC sp_BlitzQueryStore ' + - N'@DatabaseName = ' + - QUOTENAME(ds.database_name, N'''') + - N', ' + - N'@StoredProcName = ' + - QUOTENAME(ds.proc_only_name, N'''') + - N';' + N'EXEC sp_BlitzQueryStore ' + + N'@DatabaseName = ' + + QUOTENAME(ds.database_name, N'''') + + N', ' + + N'@StoredProcName = ' + + QUOTENAME(ds.proc_only_name, N'''') + + N';' FROM deadlock_stack AS ds JOIN #deadlock_owner_waiter AS dow ON dow.owner_id = ds.id AND dow.event_date = ds.event_date WHERE ds.proc_name <> 'adhoc' - AND (DB_NAME(dow.database_id) = @DatabaseName OR @DatabaseName IS NULL) + AND (dow.database_id = @DatabaseName OR @DatabaseName IS NULL) AND (dow.event_date >= @StartDate OR @StartDate IS NULL) AND (dow.event_date < @EndDate OR @EndDate IS NULL) AND (dow.object_name = @StoredProcName OR @StoredProcName IS NULL) OPTION(RECOMPILE); + + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; END; @@ -1845,46 +2029,50 @@ BEGIN ) SELECT check_id = 8, - database_name = DB_NAME(dp.database_id), + database_name = + dp.database_name, object_name = ds.proc_name, finding_group = 'Stored Procedure Deadlocks', finding = - N'The stored procedure ' + - PARSENAME(ds.proc_name, 2) + - N'.' + - PARSENAME(ds.proc_name, 1) + + N'The stored procedure ' + + PARSENAME(ds.proc_name, 2) + + N'.' + + PARSENAME(ds.proc_name, 1) + N' has been involved in ' + - CONVERT(nvarchar(10), COUNT_BIG(DISTINCT ds.id)) + - N' deadlocks.' + CONVERT + ( + nvarchar(10), COUNT_BIG(DISTINCT ds.id) + ) + + N' deadlocks.' FROM #deadlock_stack AS ds JOIN #deadlock_process AS dp ON dp.id = ds.id AND ds.event_date = dp.event_date WHERE ds.proc_name <> 'adhoc' AND (ds.proc_name = @StoredProcName OR @StoredProcName IS NULL) - AND (DB_NAME(dp.database_id) = @DatabaseName OR @DatabaseName IS NULL) + AND (dp.database_name = @DatabaseName OR @DatabaseName IS NULL) AND (dp.event_date >= @StartDate OR @StartDate IS NULL) AND (dp.event_date < @EndDate OR @EndDate IS NULL) AND (dp.client_app = @AppName OR @AppName IS NULL) AND (dp.host_name = @HostName OR @HostName IS NULL) AND (dp.login_name = @LoginName OR @LoginName IS NULL) GROUP BY - DB_NAME(dp.database_id), + dp.database_name, ds.proc_name OPTION(RECOMPILE); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; /*Check 9 gives you more info queries for sp_BlitzIndex */ SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Check 9 %s', 0, 1, @d) WITH NOWAIT; - WITH bi AS ( SELECT DISTINCT dow.object_name, - database_name = DB_NAME(dow.database_id), + dow.database_name, schema_name = a.schema_name, table_name = a.table_name FROM #deadlock_owner_waiter AS dow @@ -1892,7 +2080,7 @@ BEGIN ON a.database_id = dow.database_id AND a.partition_id = dow.associatedObjectId WHERE 1 = 1 - AND (DB_NAME(dow.database_id) = @DatabaseName OR @DatabaseName IS NULL) + AND (dow.database_id = @DatabaseName OR @DatabaseName IS NULL) AND (dow.event_date >= @StartDate OR @StartDate IS NULL) AND (dow.event_date < @EndDate OR @EndDate IS NULL) AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) @@ -1913,17 +2101,18 @@ BEGIN bi.object_name, finding_group = N'More Info - Table', finding = - N'EXEC sp_BlitzIndex ' + - N'@DatabaseName = ' + - QUOTENAME(bi.database_name, N'''') + + N'EXEC sp_BlitzIndex ' + + N'@DatabaseName = ' + + QUOTENAME(bi.database_name, N'''') + N', @SchemaName = ' + - QUOTENAME(bi.schema_name, N'''') + - N', @TableName = ' + + QUOTENAME(bi.schema_name, N'''') + + N', @TableName = ' + QUOTENAME(bi.table_name, N'''') + - N';' + N';' FROM bi OPTION(RECOMPILE); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; /*Check 10 gets total deadlock wait time per object*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); @@ -1934,29 +2123,53 @@ BEGIN ( - SELECT DISTINCT - database_name = PARSENAME(dow.object_name, 3), + SELECT + database_name = + dp.database_name, dow.object_name, wait_days = CONVERT ( nvarchar(10), - (SUM(DISTINCT CONVERT(bigint, dp.wait_time)) / 1000) / 86400 + ( + SUM + ( + CONVERT + ( + bigint, + dp.wait_time + ) + ) / 1000 / 86400 + ) ), wait_time_hms = CONVERT ( nvarchar(20), - DATEADD(SECOND, (SUM(DISTINCT CONVERT(bigint, dp.wait_time)) / 1000), 0), - 108 + DATEADD + ( + SECOND, + ( + SUM + ( + CONVERT + ( + bigint, + dp.wait_time + ) + ) / 1000 + ), + 0 + ), + 108 ) FROM #deadlock_owner_waiter AS dow JOIN #deadlock_process AS dp - ON (dp.id = dow.owner_id - OR dp.victim_id = dow.waiter_id) - AND dp.event_date = dow.event_date + ON (dp.id = dow.owner_id + OR dp.victim_id = dow.waiter_id) + AND dp.event_date = dow.event_date WHERE 1 = 1 - AND (DB_NAME(dp.database_id) = @DatabaseName OR @DatabaseName IS NULL) + AND (dp.database_name = @DatabaseName OR @DatabaseName IS NULL) AND (dp.event_date >= @StartDate OR @StartDate IS NULL) AND (dp.event_date < @EndDate OR @EndDate IS NULL) AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) @@ -1964,7 +2177,7 @@ BEGIN AND (dp.host_name = @HostName OR @HostName IS NULL) AND (dp.login_name = @LoginName OR @LoginName IS NULL) GROUP BY - PARSENAME(dow.object_name, 3), + dp.database_name, dow.object_name ) INSERT @@ -1976,42 +2189,56 @@ BEGIN finding_group, finding ) - SELECT DISTINCT + SELECT check_id = 10, cs.database_name, cs.object_name, finding_group = N'Total object deadlock wait time', finding = - N'This object has had ' + - CONVERT(nvarchar(10), cs.wait_days) + - N':' + - CONVERT(nvarchar(20), cs.wait_time_hms, 108) + - N' [d/h/m/s] of deadlock wait time.' + N'This object has had ' + + CONVERT(nvarchar(20), cs.wait_days) + + N':' + + CONVERT(nvarchar(20), cs.wait_time_hms, 108) + + N' [d/h/m/s] of deadlock wait time.' FROM chopsuey AS cs WHERE cs.object_name IS NOT NULL OPTION(RECOMPILE); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; /*Check 11 gets total deadlock wait time per database*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Check 11 %s', 0, 1, @d) WITH NOWAIT; - WITH wait_time AS ( SELECT - database_name = DB_NAME(dp.database_id), - total_wait_time_ms = SUM(CONVERT(bigint, dp.wait_time)) - FROM #deadlock_process AS dp + database_name = + dp.database_name, + total_wait_time_ms = + SUM + ( + CONVERT + ( + bigint, + dp.wait_time + ) + ) + FROM #deadlock_owner_waiter AS dow + JOIN #deadlock_process AS dp + ON (dp.id = dow.owner_id + OR dp.victim_id = dow.waiter_id) + AND dp.event_date = dow.event_date WHERE 1 = 1 - AND (DB_NAME(dp.database_id) = @DatabaseName OR @DatabaseName IS NULL) + AND (dp.database_name = @DatabaseName OR @DatabaseName IS NULL) AND (dp.event_date >= @StartDate OR @StartDate IS NULL) AND (dp.event_date < @EndDate OR @EndDate IS NULL) AND (dp.client_app = @AppName OR @AppName IS NULL) AND (dp.host_name = @HostName OR @HostName IS NULL) AND (dp.login_name = @LoginName OR @LoginName IS NULL) - GROUP BY DB_NAME(dp.database_id) + GROUP BY + dp.database_name ) INSERT #deadlock_findings WITH(TABLOCKX) @@ -2028,19 +2255,48 @@ BEGIN object_name = N'-', finding_group = N'Total database deadlock wait time', N'This database has had ' + - CONVERT(nvarchar(10), (SUM(DISTINCT CONVERT(bigint, wt.total_wait_time_ms)) / 1000) / 86400) + - ':' + + CONVERT + ( + nvarchar(10), + ( + SUM + ( + CONVERT + ( + bigint, + wt.total_wait_time_ms + ) + ) / 1000 / 86400 + ) + ) + + N':' + CONVERT ( nvarchar(20), - DATEADD(SECOND, (SUM(DISTINCT CONVERT(bigint, wt.total_wait_time_ms)) / 1000), 0), + DATEADD + ( + SECOND, + ( + SUM + ( + CONVERT + ( + bigint, + wt.total_wait_time_ms + ) + ) / 1000 + ), + 0 + ), 108 ) + - N' [d/h/m/s] of deadlock wait time.' + N' [d/h/m/s] of deadlock wait time.' FROM wait_time AS wt - GROUP BY wt.database_name + GROUP BY + wt.database_name OPTION(RECOMPILE); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; /*Check 12 gets total deadlock wait time for SQL Agent*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); @@ -2057,17 +2313,18 @@ BEGIN ) SELECT check_id = 12, - database_name = DB_NAME(aj.database_id), + database_name = + DB_NAME(aj.database_id), object_name = - N'SQLAgent - Job: ' + - aj.job_name + - N' Step: ' + - aj.step_name, + N'SQLAgent - Job: ' + + aj.job_name + + N' Step: ' + + aj.step_name, finding_group = N'Agent Job Deadlocks', finding = - N'There have been ' + - RTRIM(COUNT_BIG(*)) + - N' deadlocks from this Agent Job and Step.' + N'There have been ' + + RTRIM(COUNT_BIG(*)) + + N' deadlocks from this Agent Job and Step.' FROM #agent_job AS aj GROUP BY DB_NAME(aj.database_id), @@ -2075,6 +2332,7 @@ BEGIN aj.step_name OPTION(RECOMPILE); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; /*Check 13 is total parallel deadlocks*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); @@ -2095,12 +2353,18 @@ BEGIN object_name = N'-', finding_group = N'Total parallel deadlocks', finding = - N'There have been ' + CONVERT(nvarchar(20), COUNT_BIG(DISTINCT drp.event_date)) + - N' parallel deadlocks.' + N'There have been ' + + CONVERT + ( + nvarchar(20), + COUNT_BIG(DISTINCT drp.event_date) + ) + + N' parallel deadlocks.' FROM #deadlock_resource_parallel AS drp HAVING COUNT_BIG(DISTINCT drp.event_date) > 0 OPTION(RECOMPILE); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; /*Thank you goodnight*/ INSERT @@ -2130,6 +2394,8 @@ BEGIN SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Results 1 %s', 0, 1, @d) WITH NOWAIT; + IF @Debug = 1 BEGIN SET STATISTICS XML ON; END; + WITH deadlocks AS ( @@ -2305,26 +2571,28 @@ BEGIN SELECT d.deadlock_type, d.event_date, - database_name = DB_NAME(d.database_id), + database_name = + DB_NAME(d.database_id), d.spid, deadlock_group = - 'Deadlock #' + + N'Deadlock #' + CONVERT ( nvarchar(10), d.en ) + - ', Query #' + N', Query #' + CASE WHEN d.qn = 0 THEN N'1' ELSE CONVERT(nvarchar(10), d.qn) END + CASE WHEN d.is_victim = 1 - THEN ' - VICTIM' - ELSE '' + THEN N' - VICTIM' + ELSE N'' END, - query_xml = CONVERT(xml, N''), + query_xml = + TRY_CAST(N'' + d.inputbuf + N'' AS xml), query_string = d.inputbuf, d.object_names, d.isolation_level, @@ -2376,17 +2644,19 @@ BEGIN AND (d.login_name = @LoginName OR @LoginName IS NULL) OPTION (RECOMPILE, LOOP JOIN, HASH JOIN); - IF (@OutputDatabaseCheck = 0) - BEGIN - SET @ExportToExcel = 1 - END; + IF @Debug = 1 BEGIN SET STATISTICS XML OFF; END; + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + + IF (@OutputDatabaseCheck = 0) + BEGIN + SET @ExportToExcel = 1; + END; - SET @deadlock_result += - N' + SET @deadlock_result += N' SELECT - server_name = - @@SERVERNAME, - dr.deadlock_type, + server_name = + @@SERVERNAME, + dr.deadlock_type, dr.event_date, dr.database_name, dr.spid, @@ -2441,7 +2711,13 @@ BEGIN IF (@OutputDatabaseCheck = 0) BEGIN SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Results 1 %s', 0, 1, @d) WITH NOWAIT; + RAISERROR('Results to table %s', 0, 1, @d) WITH NOWAIT; + + IF @Debug = 1 + BEGIN + PRINT @deadlock_result; + SET STATISTICS XML ON; + END; INSERT INTO DeadLockTbl @@ -2486,12 +2762,17 @@ BEGIN EXEC sys.sp_executesql @deadlock_result; + IF @Debug = 1 + BEGIN + SET STATISTICS XML OFF; + END; + + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + DROP SYNONYM DeadLockTbl; - --done insert into blitzlock table going to insert into findings table first create synonym. - -- RAISERROR('att deadlock findings', 0, 1) WITH NOWAIT; SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Findings %s', 0, 1, @d) WITH NOWAIT; + RAISERROR('Findings to table %s', 0, 1, @d) WITH NOWAIT; INSERT INTO DeadlockFindings @@ -2514,17 +2795,31 @@ BEGIN ORDER BY df.check_id OPTION(RECOMPILE); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + DROP SYNONYM DeadlockFindings; --done with inserting. END; ELSE --Output to database is not set output to client app + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Results to client %s', 0, 1, @d) WITH NOWAIT; + + IF @Debug = 1 BEGIN SET STATISTICS XML ON; END; EXEC sys.sp_executesql @deadlock_result; + IF @Debug = 1 + BEGIN + SET STATISTICS XML OFF; + PRINT @deadlock_result; + END; + + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - SET @d = CONVERT(varchar(40), GETDATE(), 109); + SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Findings %s', 0, 1, @d) WITH NOWAIT; + SELECT df.check_id, df.database_name, @@ -2542,28 +2837,59 @@ BEGIN IF @Debug = 1 BEGIN - SELECT table_name = '#deadlock_data', * FROM #deadlock_data AS dd + SELECT + table_name = N'#deadlock_data', + * + FROM #deadlock_data AS bx; + + SELECT + table_name = '#deadlock_data', + * + FROM #deadlock_data AS dd OPTION(RECOMPILE); - SELECT table_name = '#deadlock_resource', * FROM #deadlock_resource AS dr + SELECT + table_name = '#deadlock_resource', + * + FROM #deadlock_resource AS dr OPTION(RECOMPILE); - SELECT table_name = '#deadlock_resource_parallel', * + SELECT + table_name = '#deadlock_resource_parallel', + * FROM #deadlock_resource_parallel AS drp OPTION(RECOMPILE); - SELECT table_name = '#deadlock_owner_waiter', * + SELECT + table_name = '#deadlock_owner_waiter', + * FROM #deadlock_owner_waiter AS dow OPTION(RECOMPILE); - SELECT table_name = '#deadlock_process', * FROM #deadlock_process AS dp + SELECT + table_name = '#deadlock_process', + * + FROM #deadlock_process AS dp OPTION(RECOMPILE); - SELECT table_name = '#deadlock_stack', * FROM #deadlock_stack AS ds + SELECT + table_name = '#deadlock_stack', + * + FROM #deadlock_stack AS ds OPTION(RECOMPILE); - SELECT table_name = '#deadlock_results', * FROM #deadlock_results AS dr + SELECT + table_name = '#deadlock_results', + * + FROM #deadlock_results AS dr + OPTION(RECOMPILE); + + SELECT + table_name = '@sysAssObjId', + * + FROM @sysAssObjId AS s OPTION(RECOMPILE); + END; -- End debug END; --Final End From 41d0fb8eaea31ec25f85cea2d3447795afe8a1f1 Mon Sep 17 00:00:00 2001 From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com> Date: Mon, 21 Nov 2022 20:49:09 -0500 Subject: [PATCH 335/662] Update sp_BlitzLock.sql WIP --- sp_BlitzLock.sql | 49 ++++++++++++++++++++++++++++-------------------- 1 file changed, 29 insertions(+), 20 deletions(-) diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index 6d7f806b7..24fa1c3d0 100644 --- a/sp_BlitzLock.sql +++ b/sp_BlitzLock.sql @@ -1448,30 +1448,39 @@ BEGIN INSERT INTO @sysAssObjId + ( + database_id, + partition_id, + schema_name, + table_name + ) EXECUTE sys.sp_MSforeachdb N' USE [?]; - SELECT - database_id = - DB_ID(), - p.partition_id, - s.name as schema_name, - t.name as table_name - FROM sys.partitions p - JOIN sys.tables t - ON t.object_id = p.object_id - JOIN sys.schemas s - ON s.schema_id = t.schema_id - WHERE s.name is not NULL - AND t.name is not NULL - AND EXISTS - ( - SELECT - 1/0 - FROM #deadlock_process AS dp - WHERE dp.database_id = DB_ID() - );'; + IF EXISTS + ( + SELECT + 1/0 + FROM #deadlock_process AS dp + WHERE dp.database_id = DB_ID() + ) + BEGIN + SELECT + database_id = + DB_ID(), + p.partition_id, + s.name as schema_name, + t.name as table_name + FROM sys.partitions p + JOIN sys.tables t + ON t.object_id = p.object_id + JOIN sys.schemas s + ON s.schema_id = t.schema_id + WHERE s.name is not NULL + AND t.name is not NULL + OPTION(RECOMPILE); + END;'; SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; From 3d7fcffc31a68d822edf09b159e974fe8d0cc2d4 Mon Sep 17 00:00:00 2001 From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com> Date: Mon, 21 Nov 2022 22:33:33 -0500 Subject: [PATCH 336/662] Update sp_BlitzLock.sql WIP --- sp_BlitzLock.sql | 357 ++++++++++++++++++++++++++--------------------- 1 file changed, 197 insertions(+), 160 deletions(-) diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index 24fa1c3d0..6362463f9 100644 --- a/sp_BlitzLock.sql +++ b/sp_BlitzLock.sql @@ -10,13 +10,13 @@ ALTER PROCEDURE @DatabaseName nvarchar(256) = NULL, @StartDate datetime = NULL, @EndDate datetime = NULL, - @ObjectName nvarchar(1000) = NULL, - @StoredProcName nvarchar(1000) = NULL, - @AppName nvarchar(256) = NULL, - @HostName nvarchar(256) = NULL, - @LoginName nvarchar(256) = NULL, - @EventSessionName varchar(256) = 'system_health', - @TargetSessionType varchar(20) = 'event_file', + @ObjectName nvarchar(1024) = NULL, + @StoredProcName nvarchar(1024) = NULL, + @AppName sysname = NULL, + @HostName sysname = NULL, + @LoginName sysname = NULL, + @EventSessionName sysname = 'system_health', + @TargetSessionType sysname = NULL, @VictimsOnly bit = 0, @Debug bit = 0, @Help bit = 0, @@ -24,8 +24,8 @@ ALTER PROCEDURE @VersionDate datetime = NULL OUTPUT, @VersionCheckMode bit = 0, @OutputDatabaseName sysname = NULL, - @OutputSchemaName sysname = 'dbo', --ditto as below - @OutputTableName sysname = 'BlitzLock', --put a standard here no need to check later in the script + @OutputSchemaName sysname = N'dbo', /*ditto as below*/ + @OutputTableName sysname = N'BlitzLock', /*put a standard here no need to check later in the script*/ @ExportToExcel bit = 0 ) WITH RECOMPILE @@ -39,15 +39,14 @@ BEGIN @Version = '8.11', @VersionDate = '20221013'; - IF (@VersionCheckMode = 1) + IF @VersionCheckMode = 1 BEGIN RETURN; END; - IF @Help = 1 BEGIN - PRINT ' + PRINT N' /* sp_BlitzLock from http://FirstResponderKit.org @@ -75,7 +74,7 @@ BEGIN @EventSessionName: If you want to point this at an XE session rather than the system health session. - @TargetSessionType: Can be ''ring_buffer'' or ''event_file'' + @TargetSessionType: Can be ''ring_buffer'' or ''event_file''. Leave NULL to auto-detect. @OutputDatabaseName: If you want to output information to a specific database @@ -119,13 +118,12 @@ BEGIN OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */' ; - + */'; RETURN; END; /* @Help = 1 */ - + /*Declare local variables used in the procudure*/ DECLARE @DatabaseId int = DB_ID(@DatabaseName), @@ -181,29 +179,28 @@ BEGIN @NC10 nvarchar(1) = CONVERT(nvarchar(1), 0x0a00, 0), @deadlock_result nvarchar(MAX) = N''; + /*Temporary objects used in the procedure*/ DECLARE @sysAssObjId AS table ( - database_id bigint, + database_id int, partition_id bigint, - schema_name varchar(255), - table_name varchar(255) + schema_name sysname, + table_name sysname ); - CREATE TABLE #x ( x xml NOT NULL - DEFAULT ' ' + DEFAULT N'x' ); - CREATE TABLE #deadlock_data ( deadlock_xml xml NOT NULL - DEFAULT ' ' + DEFAULT N'x' ); CREATE TABLE @@ -212,6 +209,19 @@ BEGIN id int NOT NULL ); + CREATE TABLE + #deadlock_findings + ( + id int IDENTITY PRIMARY KEY, + check_id int NOT NULL, + database_name nvarchar(256), + object_name nvarchar(1000), + finding_group nvarchar(100), + finding nvarchar(4000) + ); + + /*Set these to some sane defaults if NULLs are passed in*/ + /*Normally I'd hate this, but we RECOMPILE everything*/ SELECT @StartDate = DATEADD @@ -251,21 +261,10 @@ BEGIN ) ); - CREATE TABLE - #deadlock_findings - ( - id int IDENTITY PRIMARY KEY, - check_id int NOT NULL, - database_name nvarchar(256), - object_name nvarchar(1000), - finding_group nvarchar(100), - finding nvarchar(4000) - ); - - IF (@OutputDatabaseName IS NOT NULL) - BEGIN --if databaseName is set do some sanity checks and put [] around def. - RAISERROR('@OutputDatabaseName set to %s, checking validity', 0, 1, @OutputDatabaseName) WITH NOWAIT; + IF @OutputDatabaseName IS NOT NULL + BEGIN /*IF databaseName is set, do some sanity checks and put [] around def.*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('@OutputDatabaseName set to %s, checking validity', 0, 1, @OutputDatabaseName) WITH NOWAIT; IF NOT EXISTS ( @@ -273,10 +272,10 @@ BEGIN 1/0 FROM sys.databases AS d WHERE d.name = @OutputDatabaseName - ) --if database is invalid raiserror and set bitcheck + ) /*If database is invalid raiserror and set bitcheck*/ BEGIN RAISERROR('Database Name (%s) for output of table is invalid please, Output to Table will not be performed', 0, 1, @OutputDatabaseName) WITH NOWAIT; - SET @OutputDatabaseCheck = -1; -- -1 invalid/false, 0 = good/true + SET @OutputDatabaseCheck = -1; /* -1 invalid/false, 0 = good/true */ END; ELSE BEGIN @@ -284,15 +283,15 @@ BEGIN SELECT @StringToExecute = - N'SELECT @r = name FROM ' + + N'SELECT @r = o.name FROM ' + @OutputDatabaseName + - N'.sys.objects WHERE type_desc = ''USER_TABLE'' AND name = ' + + N'.sys.objects AS o WHERE o.type_desc = N''USER_TABLE'' AND o.name = ' + QUOTENAME ( @OutputTableName, N'''' ) + - N' AND schema_id = SCHEMA_ID(' + + N' AND o.schema_id = SCHEMA_ID(' + QUOTENAME ( @OutputSchemaName, @@ -310,7 +309,7 @@ BEGIN @OutputTableName, @r OUTPUT; - --put covers around all before. + /*put covers around all before.*/ SELECT @ObjectFullName = QUOTENAME(@OutputDatabaseName) + @@ -325,16 +324,15 @@ BEGIN @OutputSchemaName = QUOTENAME(@OutputSchemaName); - IF (@r IS NOT NULL) --if it is not null, there is a table, so check for newly added columns + IF (@r IS NOT NULL) /*if it is not null, there is a table, so check for newly added columns*/ BEGIN /* If the table doesn't have the new spid column, add it. See Github #3101. */ - SET @StringToExecute = N'IF NOT EXISTS (SELECT 1/0 FROM ' + @OutputDatabaseName + - N'.sys.all_columns WHERE object_id = (OBJECT_ID(''' + + N'.sys.all_columns AS o WHERE o.object_id = (OBJECT_ID(''' + @ObjectFullName + - N''')) AND name = ''spid'') + N''')) AND o.name = N''spid'') /*Add spid column*/ ALTER TABLE ' + @ObjectFullName + @@ -348,9 +346,9 @@ BEGIN SET @StringToExecute = N'IF NOT EXISTS (SELECT 1/0 FROM ' + @OutputDatabaseName + - N'.sys.all_columns WHERE object_id = (OBJECT_ID(''' + + N'.sys.all_columns AS o WHERE o.object_id = (OBJECT_ID(''' + @ObjectFullName + - N''')) AND name = ''wait_resource'') + N''')) AND o.name = N''wait_resource'') /*Add wait_resource column*/ ALTER TABLE ' + @ObjectFullName + @@ -360,7 +358,7 @@ BEGIN EXEC sys.sp_executesql @StringToExecute; END; - ELSE --if(@r is not null) --if it is null there is no table, create it from above execution + ELSE /* end if @r is not null. if it is null there is no table, create it from above execution */ BEGIN SELECT @StringToExecute = @@ -386,7 +384,7 @@ BEGIN transaction_count bigint, login_name nvarchar(256), host_name nvarchar(256), - client_app nvarchar(512), + client_app nvarchar(1024), wait_time bigint, wait_resource nvarchar(max), priority smallint, @@ -414,12 +412,12 @@ BEGIN EXEC sys.sp_executesql @StringToExecute; - --table created. + /*table created.*/ SELECT @StringToExecute = - N'SELECT @r = name FROM ' + + N'SELECT @r = o.name FROM ' + @OutputDatabaseName + - N'.sys.objects WHERE type_desc = ''USER_TABLE'' AND name = ''BlitzLockFindings''', + N'.sys.objects AS o WHERE o.type_desc = N''USER_TABLE'' AND o.name = N''BlitzLockFindings''', @StringToExecuteParams = N'@r sysname OUTPUT'; @@ -429,7 +427,7 @@ BEGIN @StringToExecuteParams, @r OUTPUT; - IF (@r IS NULL) --if table does not exist + IF (@r IS NULL) /*if table does not exist*/ BEGIN SELECT @OutputTableFindings = @@ -457,15 +455,14 @@ BEGIN END; END; - - --create synonym for deadlockfindings. + /*create synonym for deadlockfindings.*/ IF EXISTS ( SELECT 1/0 FROM sys.objects AS o - WHERE o.name = 'DeadlockFindings' - AND o.type_desc = 'SYNONYM' + WHERE o.name = N'DeadlockFindings' + AND o.type_desc = N'SYNONYM' ) BEGIN RAISERROR('Found synonym DeadlockFindings, dropping', 0, 1) WITH NOWAIT; @@ -485,14 +482,14 @@ BEGIN EXEC sys.sp_executesql @StringToExecute; - --create synonym for deadlock table. + /*create synonym for deadlock table.*/ IF EXISTS ( SELECT 1/0 FROM sys.objects AS o - WHERE o.name = 'DeadLockTbl' - AND o.type_desc = 'SYNONYM' + WHERE o.name = N'DeadLockTbl' + AND o.type_desc = N'SYNONYM' ) BEGIN RAISERROR('Found synonym DeadLockTbl, dropping', 0, 1) WITH NOWAIT; @@ -514,7 +511,6 @@ BEGIN END; END; - /* WITH ROWCOUNT doesn't work on Amazon RDS - see: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2037 */ IF @RDS = 0 BEGIN; @@ -541,9 +537,14 @@ BEGIN END CATCH; END; - IF @TargetSessionType IS NULL - BEGIN - IF @Azure = 0 + /*If @TargetSessionType, we need to figure out if it's ring buffer or event file*/ + /*Azure has differently named views, so we need to separate. Thanks, Azure.*/ + + IF + ( + @Azure = 0 + AND @TargetSessionType IS NULL + ) BEGIN RAISERROR('@TargetSessionType is NULL, assigning for non-Azure instance', 0, 1) WITH NOWAIT; SELECT TOP (1) @@ -552,26 +553,42 @@ BEGIN JOIN sys.dm_xe_session_targets AS t ON s.address = t.event_session_address WHERE s.name = @EventSessionName - ORDER BY t.target_name; + AND t.target_name IN (N'event_file', N'ring_buffer') + ORDER BY t.target_name + OPTION(RECOMPILE); + + RAISERROR('@TargetSessionType assigned as %s for non-Azure', 0, 1, @TargetSessionType) WITH NOWAIT; END; - IF @Azure = 1 - RAISERROR('@TargetSessionType is NULL, assigning for Azure instance', 0, 1) WITH NOWAIT; + IF + ( + @Azure = 1 + AND @TargetSessionType IS NULL + ) BEGIN + RAISERROR('@TargetSessionType is NULL, assigning for Azure instance', 0, 1) WITH NOWAIT; SELECT TOP (1) @TargetSessionType = t.target_name FROM sys.dm_xe_database_sessions AS s JOIN sys.dm_xe_database_session_targets AS t ON s.address = t.event_session_address WHERE s.name = @EventSessionName - ORDER BY t.target_name; + AND t.target_name IN (N'event_file', N'ring_buffer') + ORDER BY t.target_name + OPTION(RECOMPILE); + + RAISERROR('@TargetSessionType assigned as %s for Azure', 0, 1, @TargetSessionType) WITH NOWAIT; END; - END; + + /*The system health stuff gets handled different from user extended events.*/ + /*These next sections deal with user events, dependent on target.*/ + + /*If ring buffers*/ IF ( - @TargetSessionType LIKE 'ring%' - AND @EventSessionName NOT LIKE 'system_health%' + @TargetSessionType LIKE N'ring%' + AND @EventSessionName NOT LIKE N'system_health%' ) BEGIN IF @Azure = 0 @@ -590,7 +607,8 @@ BEGIN JOIN sys.dm_xe_sessions AS s ON s.address = t.event_session_address WHERE s.name = @EventSessionName - AND t.target_name = N'ring_buffer'; + AND t.target_name = N'ring_buffer' + OPTION(RECOMPILE); SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; @@ -612,22 +630,25 @@ BEGIN JOIN sys.dm_xe_database_sessions AS s ON s.address = t.event_session_address WHERE s.name = @EventSessionName - AND t.target_name = N'ring_buffer'; + AND t.target_name = N'ring_buffer' + OPTION(RECOMPILE); SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; END; END; + + /*If event file*/ IF ( - @TargetSessionType LIKE 'event%' - AND @EventSessionName NOT LIKE 'system_health%' + @TargetSessionType LIKE N'event%' + AND @EventSessionName NOT LIKE N'system_health%' ) BEGIN IF @Azure = 0 BEGIN - RAISERROR('@TargetSessionType is event_file, assigning XML for non-Azure at', 0, 1) WITH NOWAIT; + RAISERROR('@TargetSessionType is event_file, assigning XML for non-Azure', 0, 1) WITH NOWAIT; SELECT @SessionId = t.event_session_id, @@ -636,28 +657,35 @@ BEGIN JOIN sys.server_event_sessions AS s ON s.event_session_id = t.event_session_id WHERE t.name = @TargetSessionType - AND s.name = @EventSessionName; + AND s.name = @EventSessionName + OPTION(RECOMPILE); + /*We get the file name automatically, here*/ RAISERROR('Assigning @FileName...', 0, 1) WITH NOWAIT; SELECT @FileName = CASE - WHEN f.file_name LIKE '%.xel' + WHEN f.file_name LIKE N'%.xel' THEN REPLACE(f.file_name, N'.xel', N'*.xel') ELSE f.file_name + N'*.xel' END FROM ( SELECT - file_name = CONVERT(nvarchar(4000), f.value) + file_name = + CONVERT + ( + nvarchar(4000), + f.value + ) FROM sys.server_event_session_fields AS f WHERE f.event_session_id = @SessionId AND f.object_id = @TargetSessionId AND f.name = N'filename' - ) AS f; + ) AS f + OPTION(RECOMPILE); END; - IF @Azure = 1 BEGIN RAISERROR('@TargetSessionType is event_file, assigning XML for Azure', 0, 1) WITH NOWAIT; @@ -668,13 +696,15 @@ BEGIN JOIN sys.dm_xe_database_sessions AS s ON s.event_session_id = t.event_session_id WHERE t.name = @TargetSessionType - AND s.name = @EventSessionName; + AND s.name = @EventSessionName + OPTION(RECOMPILE); + /*We get the file name automatically, here*/ RAISERROR('Assigning @FileName...', 0, 1) WITH NOWAIT; SELECT @FileName = CASE - WHEN f.file_name LIKE '%.xel' + WHEN f.file_name LIKE N'%.xel' THEN REPLACE(f.file_name, N'.xel', N'*.xel') ELSE f.file_name + N'*.xel' END @@ -686,7 +716,8 @@ BEGIN WHERE f.event_session_id = @SessionId AND f.object_id = @TargetSessionId AND f.name = N'filename' - ) AS f; + ) AS f + OPTION(RECOMPILE); END; SET @d = CONVERT(varchar(40), GETDATE(), 109); @@ -701,21 +732,20 @@ BEGIN x = TRY_CAST(f.event_data AS xml) FROM sys.fn_xe_file_target_read_file(@FileName, NULL, NULL, NULL) AS f LEFT JOIN #t AS t - ON 1 = 1; + ON 1 = 1 + OPTION(RECOMPILE); SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; END; - IF @Debug = 1 - BEGIN - SELECT table_name = N'#x', bx.* FROM #x AS bx; - END; + /*The XML is parsed differently if it comes from the event file or ring buffer*/ + /*If ring buffers*/ IF ( - @TargetSessionType LIKE 'ring%' - AND @EventSessionName NOT LIKE 'system_health%' + @TargetSessionType LIKE N'ring%' + AND @EventSessionName NOT LIKE N'system_health%' ) BEGIN SET @d = CONVERT(varchar(40), GETDATE(), 109); @@ -727,24 +757,25 @@ BEGIN deadlock_xml ) SELECT - deadlock_xml = e.x.query('.') + deadlock_xml = e.x.query(N'.') FROM #x AS x LEFT JOIN #t AS t ON 1 = 1 CROSS APPLY x.x.nodes('/RingBufferTarget/event') AS e(x) - WHERE e.x.exist('/event/@name[ .= "xml_deadlock_report"]') = 1 - AND e.x.exist('/event/@timestamp[. >= sql:variable("@StartDate")]') = 1 - AND e.x.exist('/event/@timestamp[. < sql:variable("@EndDate")]') = 1; + WHERE e.x.exist('@name[ .= "xml_deadlock_report"]') = 1 + AND e.x.exist('@timestamp[. >= sql:variable("@StartDate")]') = 1 + AND e.x.exist('@timestamp[. < sql:variable("@EndDate")]') = 1 + OPTION(RECOMPILE); SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; END; - + /*If event file*/ IF ( - @TargetSessionType LIKE 'event_file%' - AND @EventSessionName NOT LIKE 'system_health%' + @TargetSessionType LIKE N'event_file%' + AND @EventSessionName NOT LIKE N'system_health%' ) BEGIN SET @d = CONVERT(varchar(40), GETDATE(), 109); @@ -763,17 +794,18 @@ BEGIN CROSS APPLY x.x.nodes('/event') AS e(x) WHERE e.x.exist('/event/@name[ .= "xml_deadlock_report"]') = 1 AND e.x.exist('/event/@timestamp[. >= sql:variable("@StartDate")]') = 1 - AND e.x.exist('/event/@timestamp[. < sql:variable("@EndDate")]') = 1; + AND e.x.exist('/event/@timestamp[. < sql:variable("@EndDate")]') = 1 + OPTION(RECOMPILE); SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; END; - /*Grab the initial set of xml to parse*/ + /*This section deals with event file*/ IF ( - @TargetSessionType LIKE 'event%' - AND @EventSessionName LIKE 'system_health%' + @TargetSessionType LIKE N'event%' + AND @EventSessionName LIKE N'system_health%' ) BEGIN SET @d = CONVERT(varchar(40), GETDATE(), 109); @@ -787,11 +819,8 @@ BEGIN INTO #xml FROM sys.fn_xe_file_target_read_file('system_health*.xel', NULL, NULL, NULL) LEFT JOIN #t AS t - ON 1 = 1; - - DELETE x - FROM #xml AS x - WHERE x.deadlock_xml IS NULL; + ON 1 = 1 + OPTION(RECOMPILE); INSERT #deadlock_data WITH(TABLOCKX) @@ -826,7 +855,8 @@ BEGIN INTO #dd FROM #deadlock_data AS d1 LEFT JOIN #t AS t - ON 1 = 1; + ON 1 = 1 + OPTION(RECOMPILE); SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; @@ -865,7 +895,7 @@ BEGIN q.isolation_level, q.process_xml, input_buffer = - ISNULL(ca2.ib.query('.'), '') + ISNULL(ca2.ib.query(N'.'), N'') INTO #deadlock_process FROM ( @@ -900,7 +930,7 @@ BEGIN host_name = ca.dp.value('@hostname', 'nvarchar(256)'), login_name = ca.dp.value('@loginname', 'nvarchar(256)'), isolation_level = ca.dp.value('@isolationlevel', 'nvarchar(256)'), - process_xml = ISNULL(ca.dp.query('.'), '') + process_xml = ISNULL(ca.dp.query(N'.'), N'') FROM #dd AS dd CROSS APPLY dd.deadlock_xml.nodes('//deadlock/process-list/process') AS ca(dp) WHERE (ca.dp.exist('@currentdb[. = sql:variable("@DatabaseId")]') = 1 OR @DatabaseName IS NULL) @@ -923,7 +953,7 @@ BEGIN SELECT DISTINCT dp.id, dp.event_date, - proc_name = ca.dp.value('@procname', 'nvarchar(1000)'), + proc_name = ca.dp.value('@procname', 'nvarchar(1024)'), sql_handle = ca.dp.value('@sqlhandle', 'nvarchar(131)') INTO #deadlock_stack FROM #deadlock_process AS dp @@ -956,7 +986,7 @@ BEGIN SELECT event_date = dd.deadlock_xml.value('(event/@timestamp)[1]', 'datetime2'), victim_id = dd.deadlock_xml.value('(//deadlock/victim-list/victimProcess/@id)[1]', 'nvarchar(256)'), - resource_xml = ISNULL(ca.dp.query('.'), '') + resource_xml = ISNULL(ca.dp.query(N'.'), N'') FROM #deadlock_data AS dd CROSS APPLY dd.deadlock_xml.nodes('//deadlock/resource-list') AS ca(dp) ) AS dr @@ -993,17 +1023,17 @@ BEGIN SELECT dr.event_date, database_id = ca.dr.value('@dbid', 'bigint'), - object_name = ca.dr.value('@objectname', 'nvarchar(256)'), + object_name = ca.dr.value('@objectname', 'nvarchar(1024)'), lock_mode = ca.dr.value('@mode', 'nvarchar(256)'), index_name = ca.dr.value('@indexname', 'nvarchar(256)'), associatedObjectId = ca.dr.value('@associatedObjectId', 'bigint'), dr = ca.dr.query('.') FROM #deadlock_resource AS dr CROSS APPLY dr.resource_xml.nodes('//resource-list/objectlock') AS ca(dr) + WHERE (ca.dr.exist('@objectname[. = sql:variable("@ObjectName")]') = 1 OR @ObjectName IS NULL) ) AS ca CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l) CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l) - WHERE (ca.object_name = @ObjectName OR @ObjectName IS NULL) OPTION(RECOMPILE); SET @d = CONVERT(varchar(40), GETDATE(), 109); @@ -1038,13 +1068,14 @@ BEGIN SELECT dr.event_date, database_id = ca.dr.value('@dbid', 'bigint'), - object_name = ca.dr.value('@objectname', 'nvarchar(256)'), + object_name = ca.dr.value('@objectname', 'nvarchar(1024)'), lock_mode = ca.dr.value('@mode', 'nvarchar(256)'), index_name = ca.dr.value('@indexname', 'nvarchar(256)'), associatedObjectId = ca.dr.value('@associatedObjectId', 'bigint'), dr = ca.dr.query('.') FROM #deadlock_resource AS dr CROSS APPLY dr.resource_xml.nodes('//resource-list/pagelock') AS ca(dr) + WHERE (ca.dr.exist('@objectname[. = sql:variable("@ObjectName")]') = 1 OR @ObjectName IS NULL) ) AS ca CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l) CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l) @@ -1082,13 +1113,14 @@ BEGIN SELECT dr.event_date, database_id = ca.dr.value('@dbid', 'bigint'), - object_name = ca.dr.value('@objectname', 'nvarchar(256)'), + object_name = ca.dr.value('@objectname', 'nvarchar(1024)'), lock_mode = ca.dr.value('@mode', 'nvarchar(256)'), index_name = ca.dr.value('@indexname', 'nvarchar(256)'), associatedObjectId = ca.dr.value('@associatedObjectId', 'bigint'), dr = ca.dr.query('.') FROM #deadlock_resource AS dr CROSS APPLY dr.resource_xml.nodes('//resource-list/keylock') AS ca(dr) + WHERE (ca.dr.exist('@objectname[. = sql:variable("@ObjectName")]') = 1 OR @ObjectName IS NULL) ) AS ca CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l) CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l) @@ -1126,13 +1158,14 @@ BEGIN SELECT dr.event_date, database_id = ca.dr.value('@dbid', 'bigint'), - object_name = ca.dr.value('@objectname', 'nvarchar(256)'), + object_name = ca.dr.value('@objectname', 'nvarchar(1024)'), lock_mode = ca.dr.value('@mode', 'nvarchar(256)'), index_name = ca.dr.value('@indexname', 'nvarchar(256)'), associatedObjectId = ca.dr.value('@associatedObjectId', 'bigint'), dr = ca.dr.query('.') FROM #deadlock_resource AS dr CROSS APPLY dr.resource_xml.nodes('//resource-list/ridlock') AS ca(dr) + WHERE (ca.dr.exist('@objectname[. = sql:variable("@ObjectName")]') = 1 OR @ObjectName IS NULL) ) AS ca CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l) CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l) @@ -1170,13 +1203,14 @@ BEGIN SELECT dr.event_date, database_id = ca.dr.value('@dbid', 'bigint'), - object_name = ca.dr.value('@objectname', 'nvarchar(256)'), + object_name = ca.dr.value('@objectname', 'nvarchar(1024)'), lock_mode = ca.dr.value('@mode', 'nvarchar(256)'), index_name = ca.dr.value('@indexname', 'nvarchar(256)'), associatedObjectId = ca.dr.value('@associatedObjectId', 'bigint'), dr = ca.dr.query('.') FROM #deadlock_resource AS dr CROSS APPLY dr.resource_xml.nodes('//resource-list/rowgrouplock') AS ca(dr) + WHERE (ca.dr.exist('@objectname[. = sql:variable("@ObjectName")]') = 1 OR @ObjectName IS NULL) ) AS ca CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l) CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l) @@ -1192,7 +1226,7 @@ BEGIN d SET d.index_name = - d.object_name + '.HEAP' + d.object_name + N'.HEAP' FROM #deadlock_owner_waiter AS d WHERE d.lock_type IN ( @@ -1305,7 +1339,6 @@ BEGIN END ) PERSISTED; - /*Update some nonsense*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Update some nonsense part 1 %s', 0, 1, @d) WITH NOWAIT; @@ -1401,7 +1434,8 @@ BEGIN ) BEGIN SET @StringToExecute = - N'UPDATE + N' + UPDATE aj SET aj.job_name = j.name, @@ -1412,8 +1446,10 @@ BEGIN JOIN #agent_job AS aj ON aj.job_id_guid = j.job_id AND aj.step_id = s.step_id - OPTION(RECOMPILE);'; + OPTION(RECOMPILE); + '; + IF @Debug = 1 BEGIN PRINT @StringToExecute; END; EXEC sys.sp_executesql @StringToExecute; @@ -1465,7 +1501,7 @@ BEGIN FROM #deadlock_process AS dp WHERE dp.database_id = DB_ID() ) - BEGIN + BEGIN SELECT database_id = DB_ID(), @@ -1479,8 +1515,8 @@ BEGIN ON s.schema_id = t.schema_id WHERE s.name is not NULL AND t.name is not NULL - OPTION(RECOMPILE); - END;'; + OPTION(RECOMPILE); + END;'; SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; @@ -1687,7 +1723,7 @@ BEGIN ) + N' instances of Serializable deadlocks.' FROM #deadlock_process AS dp - WHERE dp.isolation_level LIKE 'serializable%' + WHERE dp.isolation_level LIKE N'serializable%' AND (dp.database_name = @DatabaseName OR @DatabaseName IS NULL) AND (dp.event_date >= @StartDate OR @StartDate IS NULL) AND (dp.event_date < @EndDate OR @EndDate IS NULL) @@ -2012,7 +2048,7 @@ BEGIN JOIN #deadlock_owner_waiter AS dow ON dow.owner_id = ds.id AND dow.event_date = ds.event_date - WHERE ds.proc_name <> 'adhoc' + WHERE ds.proc_name <> N'adhoc' AND (dow.database_id = @DatabaseName OR @DatabaseName IS NULL) AND (dow.event_date >= @StartDate OR @StartDate IS NULL) AND (dow.event_date < @EndDate OR @EndDate IS NULL) @@ -2022,7 +2058,6 @@ BEGIN RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; END; - /*Check 8 gives you stored proc deadlock counts*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Check 8 %s', 0, 1, @d) WITH NOWAIT; @@ -2041,7 +2076,7 @@ BEGIN database_name = dp.database_name, object_name = ds.proc_name, - finding_group = 'Stored Procedure Deadlocks', + finding_group = N'Stored Procedure Deadlocks', finding = N'The stored procedure ' + PARSENAME(ds.proc_name, 2) + @@ -2057,7 +2092,7 @@ BEGIN JOIN #deadlock_process AS dp ON dp.id = ds.id AND ds.event_date = dp.event_date - WHERE ds.proc_name <> 'adhoc' + WHERE ds.proc_name <> N'adhoc' AND (ds.proc_name = @StoredProcName OR @StoredProcName IS NULL) AND (dp.database_name = @DatabaseName OR @DatabaseName IS NULL) AND (dp.event_date >= @StartDate OR @StartDate IS NULL) @@ -2131,7 +2166,6 @@ BEGIN chopsuey AS ( - SELECT database_name = dp.database_name, @@ -2388,16 +2422,15 @@ BEGIN VALUES ( -1, - N'sp_BlitzLock ' + CAST(CONVERT(datetime, @VersionDate, 102) AS varchar(100)), + N'sp_BlitzLock ' + CAST(CONVERT(datetime, @VersionDate, 102) AS nvarchar(100)), N'SQL Server First Responder Kit', N'http://FirstResponderKit.org/', N'To get help or add your own contributions, join us at http://FirstResponderKit.org.' ); - /*Results*/ - CREATE CLUSTERED INDEX cx_whatever ON #deadlock_process (event_date, id); - CREATE CLUSTERED INDEX cx_whatever ON #deadlock_resource_parallel (event_date, owner_id); - CREATE CLUSTERED INDEX cx_whatever ON #deadlock_owner_waiter (event_date, owner_id, waiter_id); + CREATE CLUSTERED INDEX cx_whatever_dp ON #deadlock_process (event_date, id); + CREATE CLUSTERED INDEX cx_whatever_drp ON #deadlock_resource_parallel (event_date, owner_id); + CREATE CLUSTERED INDEX cx_whatever_dow ON #deadlock_owner_waiter (event_date, owner_id, waiter_id); BEGIN SET @d = CONVERT(varchar(40), GETDATE(), 109); @@ -2557,8 +2590,8 @@ BEGIN WHERE drp.owner_id = dp.id AND drp.wait_type IN ( - 'e_waitPortOpen', - 'e_waitPipeNewRow' + N'e_waitPortOpen', + N'e_waitPipeNewRow' ) ORDER BY drp.event_date ) AS cao @@ -2570,8 +2603,8 @@ BEGIN WHERE drp.owner_id = dp.id AND drp.wait_type IN ( - 'e_waitPortOpen', - 'e_waitPipeGetRow' + N'e_waitPortOpen', + N'e_waitPipeGetRow' ) ORDER BY drp.event_date ) AS caw @@ -2647,7 +2680,7 @@ BEGIN AND (DB_NAME(d.database_id) = @DatabaseName OR @DatabaseName IS NULL) AND (d.event_date >= @StartDate OR @StartDate IS NULL) AND (d.event_date < @EndDate OR @EndDate IS NULL) - AND (CONVERT(nvarchar(MAX), d.object_names) LIKE '%' + @ObjectName + '%' OR @ObjectName IS NULL) + AND (CONVERT(nvarchar(MAX), d.object_names) LIKE N'%' + @ObjectName + N'%' OR @ObjectName IS NULL) AND (d.client_app = @AppName OR @AppName IS NULL) AND (d.host_name = @HostName OR @HostName IS NULL) AND (d.login_name = @LoginName OR @LoginName IS NULL) @@ -2806,9 +2839,9 @@ BEGIN RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - DROP SYNONYM DeadlockFindings; --done with inserting. + DROP SYNONYM DeadlockFindings; /*done with inserting.*/ END; - ELSE --Output to database is not set output to client app + ELSE /*Output to database is not set output to client app*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Results to client %s', 0, 1, @d) WITH NOWAIT; @@ -2825,9 +2858,8 @@ BEGIN RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Findings %s', 0, 1, @d) WITH NOWAIT; + RAISERROR('Returning findings %s', 0, 1, @d) WITH NOWAIT; SELECT df.check_id, @@ -2840,9 +2872,8 @@ BEGIN OPTION(RECOMPILE); SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Done %s', 0, 1, @d) WITH NOWAIT; - END; --done with output to client app. - + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + END; /*done with output to client app.*/ IF @Debug = 1 BEGIN @@ -2852,54 +2883,60 @@ BEGIN FROM #deadlock_data AS bx; SELECT - table_name = '#deadlock_data', + table_name = N'#deadlock_data', * FROM #deadlock_data AS dd OPTION(RECOMPILE); SELECT - table_name = '#deadlock_resource', + table_name = N'#deadlock_resource', * FROM #deadlock_resource AS dr OPTION(RECOMPILE); SELECT - table_name = '#deadlock_resource_parallel', + table_name = N'#deadlock_resource_parallel', * FROM #deadlock_resource_parallel AS drp OPTION(RECOMPILE); SELECT - table_name = '#deadlock_owner_waiter', + table_name = N'#deadlock_owner_waiter', * FROM #deadlock_owner_waiter AS dow OPTION(RECOMPILE); SELECT - table_name = '#deadlock_process', + table_name = N'#deadlock_process', * FROM #deadlock_process AS dp OPTION(RECOMPILE); SELECT - table_name = '#deadlock_stack', + table_name = N'#deadlock_stack', * FROM #deadlock_stack AS ds OPTION(RECOMPILE); SELECT - table_name = '#deadlock_results', + table_name = N'#deadlock_results', * FROM #deadlock_results AS dr OPTION(RECOMPILE); SELECT - table_name = '@sysAssObjId', + table_name = N'#x', + * + FROM #x AS x + OPTION(RECOMPILE); + + SELECT + table_name = N'@sysAssObjId', * FROM @sysAssObjId AS s OPTION(RECOMPILE); - END; -- End debug - END; --Final End + END; /*End debug*/ + END; /*Final End*/ GO From 6fc20206d24ba44111d4b6c514efbe8b6d249dc7 Mon Sep 17 00:00:00 2001 From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com> Date: Mon, 21 Nov 2022 22:38:07 -0500 Subject: [PATCH 337/662] Update sp_BlitzLock.sql WIP --- sp_BlitzLock.sql | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index 6362463f9..0bb89e3fd 100644 --- a/sp_BlitzLock.sql +++ b/sp_BlitzLock.sql @@ -1435,18 +1435,18 @@ BEGIN BEGIN SET @StringToExecute = N' - UPDATE - aj - SET - aj.job_name = j.name, - aj.step_name = s.step_name - FROM msdb.dbo.sysjobs AS j - JOIN msdb.dbo.sysjobsteps AS s - ON j.job_id = s.job_id - JOIN #agent_job AS aj - ON aj.job_id_guid = j.job_id - AND aj.step_id = s.step_id - OPTION(RECOMPILE); + UPDATE + aj + SET + aj.job_name = j.name, + aj.step_name = s.step_name + FROM msdb.dbo.sysjobs AS j + JOIN msdb.dbo.sysjobsteps AS s + ON j.job_id = s.job_id + JOIN #agent_job AS aj + ON aj.job_id_guid = j.job_id + AND aj.step_id = s.step_id + OPTION(RECOMPILE); '; IF @Debug = 1 BEGIN PRINT @StringToExecute; END; @@ -1492,6 +1492,8 @@ BEGIN ) EXECUTE sys.sp_MSforeachdb N' + SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + USE [?]; IF EXISTS From a2f55609aef6da5cb9d299a70bd61777074934ff Mon Sep 17 00:00:00 2001 From: Cody Konior Date: Tue, 22 Nov 2022 16:43:05 +0800 Subject: [PATCH 338/662] Add SQL 2022 PSPO parsing --- sp_BlitzCache.sql | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/sp_BlitzCache.sql b/sp_BlitzCache.sql index 5993b4906..9999a0f43 100644 --- a/sp_BlitzCache.sql +++ b/sp_BlitzCache.sql @@ -3753,6 +3753,35 @@ WHERE QueryType = 'Statement' AND SPID = @@SPID OPTION (RECOMPILE); +/* Update to grab stored procedure name for individual statements when PSPO is detected */ +UPDATE p +SET QueryType = QueryType + ' (parent ' + + + QUOTENAME(OBJECT_SCHEMA_NAME(s.object_id, s.database_id)) + + '.' + + QUOTENAME(OBJECT_NAME(s.object_id, s.database_id)) + ')' +FROM ##BlitzCacheProcs p + OUTER APPLY ( + SELECT REPLACE(REPLACE(REPLACE(REPLACE(p.QueryText, ' (', '('), '( ', '('), ' =', '='), '= ', '=') AS NormalizedQueryText + ) a + OUTER APPLY ( + SELECT CHARINDEX('option(PLAN PER VALUE(ObjectID=', a.NormalizedQueryText) AS OptionStart + ) b + OUTER APPLY ( + SELECT SUBSTRING(a.NormalizedQueryText, b.OptionStart + 31, LEN(a.NormalizedQueryText) - b.OptionStart - 30) AS OptionSubstring + WHERE b.OptionStart > 0 + ) c + OUTER APPLY ( + SELECT PATINDEX('%[^0-9]%', c.OptionSubstring) AS ObjectLength + ) d + OUTER APPLY ( + SELECT CONVERT(INT, SUBSTRING(OptionSubstring, 1, d.ObjectLength - 1)) AS ObjectId + ) e + JOIN sys.dm_exec_procedure_stats s ON DB_ID(p.DatabaseName) = s.database_id AND e.ObjectId = s.object_id +WHERE p.QueryType = 'Statement' +AND p.SPID = @@SPID +AND s.object_id IS NOT NULL +OPTION (RECOMPILE); + RAISERROR(N'Attempting to get function name for individual statements', 0, 1) WITH NOWAIT; DECLARE @function_update_sql NVARCHAR(MAX) = N'' IF EXISTS (SELECT 1/0 FROM sys.all_objects AS o WHERE o.name = 'dm_exec_function_stats') From 52ea9d144352f59126f3c42b12222fc6bb13fa2f Mon Sep 17 00:00:00 2001 From: Cody Konior Date: Tue, 22 Nov 2022 16:51:46 +0800 Subject: [PATCH 339/662] Change convert to try_convert, just in case --- sp_BlitzCache.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sp_BlitzCache.sql b/sp_BlitzCache.sql index 9999a0f43..650e300c4 100644 --- a/sp_BlitzCache.sql +++ b/sp_BlitzCache.sql @@ -3774,7 +3774,7 @@ FROM ##BlitzCacheProcs p SELECT PATINDEX('%[^0-9]%', c.OptionSubstring) AS ObjectLength ) d OUTER APPLY ( - SELECT CONVERT(INT, SUBSTRING(OptionSubstring, 1, d.ObjectLength - 1)) AS ObjectId + SELECT TRY_CONVERT(INT, SUBSTRING(OptionSubstring, 1, d.ObjectLength - 1)) AS ObjectId ) e JOIN sys.dm_exec_procedure_stats s ON DB_ID(p.DatabaseName) = s.database_id AND e.ObjectId = s.object_id WHERE p.QueryType = 'Statement' From 911ab3cebd7d5e02ad2e6e6e45a5e898ef96a092 Mon Sep 17 00:00:00 2001 From: Cody Konior Date: Wed, 23 Nov 2022 01:17:32 +0800 Subject: [PATCH 340/662] Update to TRY_CAST instead of TRY_CONVERT --- sp_BlitzCache.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sp_BlitzCache.sql b/sp_BlitzCache.sql index 650e300c4..864cb8c33 100644 --- a/sp_BlitzCache.sql +++ b/sp_BlitzCache.sql @@ -3774,7 +3774,7 @@ FROM ##BlitzCacheProcs p SELECT PATINDEX('%[^0-9]%', c.OptionSubstring) AS ObjectLength ) d OUTER APPLY ( - SELECT TRY_CONVERT(INT, SUBSTRING(OptionSubstring, 1, d.ObjectLength - 1)) AS ObjectId + SELECT TRY_CAST(SUBSTRING(OptionSubstring, 1, d.ObjectLength - 1) AS INT) AS ObjectId ) e JOIN sys.dm_exec_procedure_stats s ON DB_ID(p.DatabaseName) = s.database_id AND e.ObjectId = s.object_id WHERE p.QueryType = 'Statement' From c4b0eb329b855b8bdb9116f3afed1b409c2e11b7 Mon Sep 17 00:00:00 2001 From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com> Date: Thu, 24 Nov 2022 12:00:46 -0500 Subject: [PATCH 341/662] Update sp_BlitzLock.sql --- sp_BlitzLock.sql | 130 +++++++++++++++++++++++------------------------ 1 file changed, 65 insertions(+), 65 deletions(-) diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index 0bb89e3fd..3e396494e 100644 --- a/sp_BlitzLock.sql +++ b/sp_BlitzLock.sql @@ -49,7 +49,7 @@ BEGIN PRINT N' /* sp_BlitzLock from http://FirstResponderKit.org - + This script checks for and analyzes deadlocks from the system health session or a custom extended event path Variables you can use: @@ -60,28 +60,28 @@ BEGIN @EndDate: The date you want to stop searching on, defaults to current date - @ObjectName: If you want to filter to a specific able. + @ObjectName: If you want to filter to a specific able. The object name has to be fully qualified ''Database.Schema.Table'' @StoredProcName: If you want to search for a single stored proc The proc name has to be fully qualified ''Database.Schema.Sproc'' - + @AppName: If you want to filter to a specific application - + @HostName: If you want to filter to a specific host - + @LoginName: If you want to filter to a specific login @EventSessionName: If you want to point this at an XE session rather than the system health session. @TargetSessionType: Can be ''ring_buffer'' or ''event_file''. Leave NULL to auto-detect. - + @OutputDatabaseName: If you want to output information to a specific database @OutputSchemaName: Specify a schema name to output information to a specific Schema @OutputTableName: Specify table name to to output information to a specific table - + To learn more, visit http://FirstResponderKit.org where you can download new versions for free, watch training videos on how it works, get more info on the findings, contribute your own code, and more. @@ -90,14 +90,14 @@ BEGIN - Only SQL Server 2012 and newer is supported - If your tables have weird characters in them (https://en.wikipedia.org/wiki/List_of_xml_and_HTML_character_entity_references) you may get errors trying to parse the xml. I took a long look at this one, and: - 1) Trying to account for all the weird places these could crop up is a losing effort. + 1) Trying to account for all the weird places these could crop up is a losing effort. 2) Replace is slow af on lots of xml. Unknown limitations of this version: - None. (If we knew them, they would be known. Duh.) MIT License - + Copyright (c) 2022 Brent Ozar Unlimited Permission is hereby granted, free of charge, to any person obtaining a copy @@ -223,16 +223,16 @@ BEGIN /*Set these to some sane defaults if NULLs are passed in*/ /*Normally I'd hate this, but we RECOMPILE everything*/ SELECT - @StartDate = + @StartDate = DATEADD ( - MINUTE, + MINUTE, DATEDIFF ( - MINUTE, - SYSDATETIME(), + MINUTE, + SYSDATETIME(), GETUTCDATE() - ), + ), ISNULL ( @StartDate, @@ -247,13 +247,13 @@ BEGIN @EndDate = DATEADD ( - MINUTE, + MINUTE, DATEDIFF ( - MINUTE, - SYSDATETIME(), + MINUTE, + SYSDATETIME(), GETUTCDATE() - ), + ), ISNULL ( @EndDate, @@ -290,13 +290,13 @@ BEGIN ( @OutputTableName, N'''' - ) + + ) + N' AND o.schema_id = SCHEMA_ID(' + QUOTENAME ( @OutputSchemaName, N'''' - ) + + ) + N');', @StringToExecuteParams = N'@r sysname OUTPUT'; @@ -311,11 +311,11 @@ BEGIN /*put covers around all before.*/ SELECT - @ObjectFullName = + @ObjectFullName = QUOTENAME(@OutputDatabaseName) + - N'.' + + N'.' + QUOTENAME(@OutputTableName) + - N'.' + + N'.' + QUOTENAME(@OutputSchemaName), @OutputDatabaseName = QUOTENAME(@OutputDatabaseName), @@ -442,10 +442,10 @@ BEGIN @OutputTableFindings + N' ( ServerName nvarchar(256), - check_id INT, - database_name nvarchar(256), - object_name nvarchar(1000), - finding_group nvarchar(100), + check_id INT, + database_name nvarchar(256), + object_name nvarchar(1000), + finding_group nvarchar(100), finding nvarchar(4000) );'; @@ -541,10 +541,10 @@ BEGIN /*Azure has differently named views, so we need to separate. Thanks, Azure.*/ IF - ( - @Azure = 0 - AND @TargetSessionType IS NULL - ) + ( + @Azure = 0 + AND @TargetSessionType IS NULL + ) BEGIN RAISERROR('@TargetSessionType is NULL, assigning for non-Azure instance', 0, 1) WITH NOWAIT; SELECT TOP (1) @@ -555,16 +555,16 @@ BEGIN WHERE s.name = @EventSessionName AND t.target_name IN (N'event_file', N'ring_buffer') ORDER BY t.target_name - OPTION(RECOMPILE); + OPTION(RECOMPILE); RAISERROR('@TargetSessionType assigned as %s for non-Azure', 0, 1, @TargetSessionType) WITH NOWAIT; END; IF - ( - @Azure = 1 - AND @TargetSessionType IS NULL - ) + ( + @Azure = 1 + AND @TargetSessionType IS NULL + ) BEGIN RAISERROR('@TargetSessionType is NULL, assigning for Azure instance', 0, 1) WITH NOWAIT; SELECT TOP (1) @@ -575,7 +575,7 @@ BEGIN WHERE s.name = @EventSessionName AND t.target_name IN (N'event_file', N'ring_buffer') ORDER BY t.target_name - OPTION(RECOMPILE); + OPTION(RECOMPILE); RAISERROR('@TargetSessionType assigned as %s for Azure', 0, 1, @TargetSessionType) WITH NOWAIT; END; @@ -608,7 +608,7 @@ BEGIN ON s.address = t.event_session_address WHERE s.name = @EventSessionName AND t.target_name = N'ring_buffer' - OPTION(RECOMPILE); + OPTION(RECOMPILE); SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; @@ -631,7 +631,7 @@ BEGIN ON s.address = t.event_session_address WHERE s.name = @EventSessionName AND t.target_name = N'ring_buffer' - OPTION(RECOMPILE); + OPTION(RECOMPILE); SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; @@ -658,7 +658,7 @@ BEGIN ON s.event_session_id = t.event_session_id WHERE t.name = @TargetSessionType AND s.name = @EventSessionName - OPTION(RECOMPILE); + OPTION(RECOMPILE); /*We get the file name automatically, here*/ RAISERROR('Assigning @FileName...', 0, 1) WITH NOWAIT; @@ -683,7 +683,7 @@ BEGIN AND f.object_id = @TargetSessionId AND f.name = N'filename' ) AS f - OPTION(RECOMPILE); + OPTION(RECOMPILE); END; IF @Azure = 1 @@ -697,7 +697,7 @@ BEGIN ON s.event_session_id = t.event_session_id WHERE t.name = @TargetSessionType AND s.name = @EventSessionName - OPTION(RECOMPILE); + OPTION(RECOMPILE); /*We get the file name automatically, here*/ RAISERROR('Assigning @FileName...', 0, 1) WITH NOWAIT; @@ -717,7 +717,7 @@ BEGIN AND f.object_id = @TargetSessionId AND f.name = N'filename' ) AS f - OPTION(RECOMPILE); + OPTION(RECOMPILE); END; SET @d = CONVERT(varchar(40), GETDATE(), 109); @@ -733,7 +733,7 @@ BEGIN FROM sys.fn_xe_file_target_read_file(@FileName, NULL, NULL, NULL) AS f LEFT JOIN #t AS t ON 1 = 1 - OPTION(RECOMPILE); + OPTION(RECOMPILE); SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; @@ -1438,10 +1438,10 @@ BEGIN UPDATE aj SET - aj.job_name = j.name, + aj.job_name = j.name, aj.step_name = s.step_name FROM msdb.dbo.sysjobs AS j - JOIN msdb.dbo.sysjobsteps AS s + JOIN msdb.dbo.sysjobsteps AS s ON j.job_id = s.job_id JOIN #agent_job AS aj ON aj.job_id_guid = j.job_id @@ -1492,7 +1492,7 @@ BEGIN ) EXECUTE sys.sp_MSforeachdb N' - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; USE [?]; @@ -1512,9 +1512,9 @@ BEGIN t.name as table_name FROM sys.partitions p JOIN sys.tables t - ON t.object_id = p.object_id + ON t.object_id = p.object_id JOIN sys.schemas s - ON s.schema_id = t.schema_id + ON s.schema_id = t.schema_id WHERE s.name is not NULL AND t.name is not NULL OPTION(RECOMPILE); @@ -1722,7 +1722,7 @@ BEGIN ( nvarchar(20), COUNT_BIG(*) - ) + + ) + N' instances of Serializable deadlocks.' FROM #deadlock_process AS dp WHERE dp.isolation_level LIKE N'serializable%' @@ -1757,7 +1757,7 @@ BEGIN finding_group = N'Repeatable Read Deadlocking', finding = N'This database has had ' + - CONVERT(nvarchar(20), COUNT_BIG(*)) + + CONVERT(nvarchar(20), COUNT_BIG(*)) + N' instances of Repeatable Read deadlocks.' FROM #deadlock_process AS dp WHERE dp.isolation_level LIKE N'repeatable read%' @@ -1797,18 +1797,18 @@ BEGIN ( nvarchar(20), COUNT_BIG(DISTINCT dp.event_date) - ) + + ) + N' instances of deadlocks involving the login ' + ISNULL ( dp.login_name, N'UNKNOWN' - ) + + ) + N' from the application ' + ISNULL ( dp.client_app, N'UNKNOWN' ) + - N' on host ' + + N' on host ' + ISNULL ( dp.host_name, N'UNKNOWN' @@ -2083,7 +2083,7 @@ BEGIN N'The stored procedure ' + PARSENAME(ds.proc_name, 2) + N'.' + - PARSENAME(ds.proc_name, 1) + + PARSENAME(ds.proc_name, 1) + N' has been involved in ' + CONVERT ( @@ -2149,10 +2149,10 @@ BEGIN finding = N'EXEC sp_BlitzIndex ' + N'@DatabaseName = ' + - QUOTENAME(bi.database_name, N'''') + + QUOTENAME(bi.database_name, N'''') + N', @SchemaName = ' + - QUOTENAME(bi.schema_name, N'''') + - N', @TableName = ' + + QUOTENAME(bi.schema_name, N'''') + + N', @TableName = ' + QUOTENAME(bi.table_name, N'''') + N';' FROM bi @@ -2185,7 +2185,7 @@ BEGIN dp.wait_time ) ) / 1000 / 86400 - ) + ) ), wait_time_hms = CONVERT @@ -2299,22 +2299,22 @@ BEGIN wt.database_name, object_name = N'-', finding_group = N'Total database deadlock wait time', - N'This database has had ' + + N'This database has had ' + CONVERT ( nvarchar(10), ( SUM - ( + ( CONVERT ( bigint, wt.total_wait_time_ms ) ) / 1000 / 86400 - ) + ) ) + - N':' + + N':' + CONVERT ( nvarchar(20), @@ -2464,7 +2464,7 @@ BEGIN object_name = NCHAR(10) + N' ' + - ISNULL(c.object_name, N'') + + ISNULL(c.object_name, N'') + N' ' COLLATE DATABASE_DEFAULT FROM #deadlock_owner_waiter AS c WHERE (dp.id = c.owner_id @@ -2537,7 +2537,7 @@ BEGIN object_name = NCHAR(10) + N' ' + - ISNULL(c.object_name, N'') + + ISNULL(c.object_name, N'') + N' ' COLLATE DATABASE_DEFAULT FROM #deadlock_owner_waiter AS c WHERE (dp.id = c.owner_id From faae542efdbf9cf7214e67aa051611d74b4690f7 Mon Sep 17 00:00:00 2001 From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com> Date: Fri, 25 Nov 2022 13:43:58 -0500 Subject: [PATCH 342/662] Update sp_BlitzLock.sql This does some more work to bring parity with other scripts I use, like decoding client options, resolving those weird "database id proc id" lines, etc. Also gets rid of some more useless code. I know that at some point I'm going to need to review changes to the output and add additional columns to logging tables. I'll do that when I have a final version ready to go. --- sp_BlitzLock.sql | 388 +++++++++++++++++++++++++++++++++++++---------- 1 file changed, 305 insertions(+), 83 deletions(-) diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index 3e396494e..5c56c0f2f 100644 --- a/sp_BlitzLock.sql +++ b/sp_BlitzLock.sql @@ -15,7 +15,7 @@ ALTER PROCEDURE @AppName sysname = NULL, @HostName sysname = NULL, @LoginName sysname = NULL, - @EventSessionName sysname = 'system_health', + @EventSessionName sysname = N'system_health', @TargetSessionType sysname = NULL, @VictimsOnly bit = 0, @Debug bit = 0, @@ -176,7 +176,7 @@ BEGIN @SessionId int = 0, @TargetSessionId int = 0, @FileName nvarchar(4000) = N'', - @NC10 nvarchar(1) = CONVERT(nvarchar(1), 0x0a00, 0), + @inputbuf_bom nvarchar(1) = CONVERT(nvarchar(1), 0x0a00, 0), @deadlock_result nvarchar(MAX) = N''; /*Temporary objects used in the procedure*/ @@ -893,9 +893,50 @@ BEGIN q.host_name, q.login_name, q.isolation_level, - q.process_xml, - input_buffer = - ISNULL(ca2.ib.query(N'.'), N'') + client_option_1 = + SUBSTRING + ( + CASE WHEN q.clientoption1 & 1 = 1 THEN ', DISABLE_DEF_CNST_CHECK' ELSE '' END + + CASE WHEN q.clientoption1 & 2 = 2 THEN ', IMPLICIT_TRANSACTIONS' ELSE '' END + + CASE WHEN q.clientoption1 & 4 = 4 THEN ', CURSOR_CLOSE_ON_COMMIT' ELSE '' END + + CASE WHEN q.clientoption1 & 8 = 8 THEN ', ANSI_WARNINGS' ELSE '' END + + CASE WHEN q.clientoption1 & 16 = 16 THEN ', ANSI_PADDING' ELSE '' END + + CASE WHEN q.clientoption1 & 32 = 32 THEN ', ANSI_NULLS' ELSE '' END + + CASE WHEN q.clientoption1 & 64 = 64 THEN ', ARITHABORT' ELSE '' END + + CASE WHEN q.clientoption1 & 128 = 128 THEN ', ARITHIGNORE' ELSE '' END + + CASE WHEN q.clientoption1 & 256 = 256 THEN ', QUOTED_IDENTIFIER' ELSE '' END + + CASE WHEN q.clientoption1 & 512 = 512 THEN ', NOCOUNT' ELSE '' END + + CASE WHEN q.clientoption1 & 1024 = 1024 THEN ', ANSI_NULL_DFLT_ON' ELSE '' END + + CASE WHEN q.clientoption1 & 2048 = 2048 THEN ', ANSI_NULL_DFLT_OFF' ELSE '' END + + CASE WHEN q.clientoption1 & 4096 = 4096 THEN ', CONCAT_NULL_YIELDS_NULL' ELSE '' END + + CASE WHEN q.clientoption1 & 8192 = 8192 THEN ', NUMERIC_ROUNDABORT' ELSE '' END + + CASE WHEN q.clientoption1 & 16384 = 16384 THEN ', XACT_ABORT' ELSE '' END, + 3, + 8000 + ), + client_option_2 = + SUBSTRING + ( + CASE WHEN q.clientoption2 & 1024 = 1024 THEN ', DB CHAINING' ELSE '' END + + CASE WHEN q.clientoption2 & 2048 = 2048 THEN ', NUMERIC ROUNDABORT' ELSE '' END + + CASE WHEN q.clientoption2 & 4096 = 4096 THEN ', ARITHABORT' ELSE '' END + + CASE WHEN q.clientoption2 & 8192 = 8192 THEN ', ANSI PADDING' ELSE '' END + + CASE WHEN q.clientoption2 & 16384 = 16384 THEN ', ANSI NULL DEFAULT' ELSE '' END + + CASE WHEN q.clientoption2 & 65536 = 65536 THEN ', CONCAT NULL YIELDS NULL' ELSE '' END + + CASE WHEN q.clientoption2 & 131072 = 131072 THEN ', RECURSIVE TRIGGERS' ELSE '' END + + CASE WHEN q.clientoption2 & 1048576 = 1048576 THEN ', DEFAULT TO LOCAL CURSOR' ELSE '' END + + CASE WHEN q.clientoption2 & 8388608 = 8388608 THEN ', QUOTED IDENTIFIER' ELSE '' END + + CASE WHEN q.clientoption2 & 16777216 = 16777216 THEN ', AUTO CREATE STATISTICS' ELSE '' END + + CASE WHEN q.clientoption2 & 33554432 = 33554432 THEN ', CURSOR CLOSE ON COMMIT' ELSE '' END + + CASE WHEN q.clientoption2 & 67108864 = 67108864 THEN ', ANSI NULLS' ELSE '' END + + CASE WHEN q.clientoption2 & 268435456 = 268435456 THEN ', ANSI WARNINGS' ELSE '' END + + CASE WHEN q.clientoption2 & 536870912 = 536870912 THEN ', FULL TEXT ENABLED' ELSE '' END + + CASE WHEN q.clientoption2 & 1073741824 = 1073741824 THEN ', AUTO UPDATE STATISTICS' ELSE '' END + + CASE WHEN q.clientoption2 & 1469283328 = 1469283328 THEN ', ALL SETTABLE OPTIONS' ELSE '' END, + 3, + 8000 + ), + q.process_xml INTO #deadlock_process FROM ( @@ -930,6 +971,8 @@ BEGIN host_name = ca.dp.value('@hostname', 'nvarchar(256)'), login_name = ca.dp.value('@loginname', 'nvarchar(256)'), isolation_level = ca.dp.value('@isolationlevel', 'nvarchar(256)'), + clientoption1 = ca.dp.value('@clientoption1', 'bigint'), + clientoption2 = ca.dp.value('@clientoption2', 'bigint'), process_xml = ISNULL(ca.dp.query(N'.'), N'') FROM #dd AS dd CROSS APPLY dd.deadlock_xml.nodes('//deadlock/process-list/process') AS ca(dp) @@ -938,11 +981,11 @@ BEGIN AND (ca.dp.exist('@hostname[. = sql:variable("@HostName")]') = 1 OR @HostName IS NULL) AND (ca.dp.exist('@loginname[. = sql:variable("@LoginName")]') = 1 OR @LoginName IS NULL) ) AS q - CROSS APPLY q.deadlock_xml.nodes('//deadlock/process-list/process/inputbuf') AS ca2(ib) LEFT JOIN #t AS t ON 1 = 1 OPTION(RECOMPILE); + SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; @@ -971,12 +1014,17 @@ BEGIN SELECT event_date = - DATEADD - ( - MINUTE, - DATEDIFF(MINUTE, GETUTCDATE(), SYSDATETIME()), - dr.event_date - ), + DATEADD + ( + MINUTE, + DATEDIFF + ( + MINUTE, + GETUTCDATE(), + SYSDATETIME() + ), + dr.event_date + ), dr.victim_id, dr.resource_xml INTO @@ -1008,7 +1056,17 @@ BEGIN DB_NAME(ca.database_id), N'UNKNOWN' ), - ca.object_name, + object_name = + REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE( + REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE( + REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE( + ca.object_name COLLATE Latin1_General_BIN2,, + NCHAR(31), N'?'), NCHAR(30), N'?'), NCHAR(29), N'?'), NCHAR(28), N'?'), NCHAR(27), N'?'), + NCHAR(26), N'?'), NCHAR(25), N'?'), NCHAR(24), N'?'), NCHAR(23), N'?'), NCHAR(22), N'?'), + NCHAR(21), N'?'), NCHAR(20), N'?'), NCHAR(19), N'?'), NCHAR(18), N'?'), NCHAR(17), N'?'), + NCHAR(16), N'?'), NCHAR(15), N'?'), NCHAR(14), N'?'), NCHAR(12), N'?'), NCHAR(11), N'?'), + NCHAR(8), N'?'), NCHAR(7), N'?'), NCHAR(6), N'?'), NCHAR(5), N'?'), NCHAR(4), N'?'), + NCHAR(3), N'?'), NCHAR(2), N'?'), NCHAR(1), N'?'), NCHAR(0), N'?'), ca.lock_mode, ca.index_name, ca.associatedObjectId, @@ -1389,7 +1447,11 @@ BEGIN CONVERT ( uniqueidentifier, - TRY_CAST(N'' AS xml).value('xs:hexBinary(substring(sql:column("x.job_id"), 0) )','BINARY(16)') + TRY_CAST + ( + N'' + AS xml + ).value('xs:hexBinary(substring(sql:column("x.job_id"), 0))', 'binary(16)') ) INTO #agent_job FROM @@ -1478,7 +1540,8 @@ BEGIN RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; /*Get each and every table of all databases*/ - + IF @Azure = 0 + BEGIN SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Inserting to @sysAssObjId %s', 0, 1, @d) WITH NOWAIT; @@ -1492,36 +1555,70 @@ BEGIN ) EXECUTE sys.sp_MSforeachdb N' - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - - USE [?]; - - IF EXISTS - ( - SELECT - 1/0 - FROM #deadlock_process AS dp - WHERE dp.database_id = DB_ID() - ) - BEGIN - SELECT - database_id = - DB_ID(), - p.partition_id, - s.name as schema_name, - t.name as table_name - FROM sys.partitions p - JOIN sys.tables t - ON t.object_id = p.object_id - JOIN sys.schemas s - ON s.schema_id = t.schema_id - WHERE s.name is not NULL - AND t.name is not NULL - OPTION(RECOMPILE); - END;'; + SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + + USE [?]; + + IF EXISTS + ( + SELECT + 1/0 + FROM #deadlock_process AS dp + WHERE dp.database_id = DB_ID() + ) + BEGIN + SELECT + database_id = + DB_ID(), + p.partition_id, + s.name as schema_name, + t.name as table_name + FROM sys.partitions p + JOIN sys.tables t + ON t.object_id = p.object_id + JOIN sys.schemas s + ON s.schema_id = t.schema_id + WHERE s.name is not NULL + AND t.name is not NULL + OPTION(RECOMPILE); + END; + '; SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + END; + + IF @Azure = 1 + BEGIN + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Inserting to @sysAssObjId %s', 0, 1, @d) WITH NOWAIT; + + INSERT INTO + @sysAssObjId + ( + database_id, + partition_id, + schema_name, + table_name + ) + SELECT + database_id = + DB_ID(), + p.partition_id, + s.name as schema_name, + t.name as table_name + FROM sys.partitions p + JOIN sys.tables t + ON t.object_id = p.object_id + JOIN sys.schemas s + ON s.schema_id = t.schema_id + WHERE s.name is not NULL + AND t.name is not NULL + OPTION(RECOMPILE); + + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + END; /*Begin checks based on parsed values*/ @@ -2430,7 +2527,7 @@ BEGIN ); /*Results*/ - CREATE CLUSTERED INDEX cx_whatever_dp ON #deadlock_process (event_date, id); + CREATE CLUSTERED INDEX cx_whatever_dp ON #deadlock_process (event_date, id); CREATE CLUSTERED INDEX cx_whatever_drp ON #deadlock_resource_parallel (event_date, owner_id); CREATE CLUSTERED INDEX cx_whatever_dow ON #deadlock_owner_waiter (event_date, owner_id, waiter_id); @@ -2444,17 +2541,20 @@ BEGIN deadlocks AS ( SELECT - deadlock_type = N'Regular Deadlock', + deadlock_type = + N'Regular Deadlock', dp.event_date, dp.id, dp.victim_id, dp.spid, dp.database_id, + dp.database_name, dp.priority, dp.log_used, - wait_resource = dp.wait_resource COLLATE DATABASE_DEFAULT, + wait_resource = + dp.wait_resource COLLATE DATABASE_DEFAULT, object_names = - CONVERT + CONVERT ( xml, STUFF @@ -2490,19 +2590,27 @@ BEGIN dp.host_name, dp.login_name, dp.isolation_level, - inputbuf = dp.process_xml.value('(//process/inputbuf/text())[1]', 'nvarchar(MAX)'), - en = DENSE_RANK() OVER (ORDER BY dp.event_date), - qn = ROW_NUMBER() OVER (PARTITION BY dp.event_date ORDER BY dp.event_date) - 1, - dn = ROW_NUMBER() OVER (PARTITION BY dp.event_date, dp.id ORDER BY dp.event_date), + dp.client_option_1, + dp.client_option_2, + inputbuf = + dp.process_xml.value('(//process/inputbuf/text())[1]', 'nvarchar(MAX)'), + en = + DENSE_RANK() OVER (ORDER BY dp.event_date), + qn = + ROW_NUMBER() OVER (PARTITION BY dp.event_date ORDER BY dp.event_date) - 1, + dn = + ROW_NUMBER() OVER (PARTITION BY dp.event_date, dp.id ORDER BY dp.event_date), dp.is_victim, - owner_mode = ISNULL(dp.owner_mode, N'-'), + owner_mode = + ISNULL(dp.owner_mode, N'-'), owner_waiter_type = NULL, owner_activity = NULL, owner_waiter_activity = NULL, owner_merging = NULL, owner_spilling = NULL, owner_waiting_to_close = NULL, - waiter_mode = ISNULL(dp.waiter_mode, N'-'), + waiter_mode = + ISNULL(dp.waiter_mode, N'-'), waiter_waiter_type = NULL, waiter_owner_activity = NULL, waiter_waiter_activity = NULL, @@ -2512,17 +2620,19 @@ BEGIN dp.deadlock_graph FROM #deadlock_process AS dp WHERE dp.victim_id IS NOT NULL - AND dp.is_parallel = 0 + AND dp.is_parallel = 0 UNION ALL SELECT - deadlock_type = N'Parallel Deadlock', + deadlock_type = + N'Parallel Deadlock', dp.event_date, dp.id, dp.victim_id, dp.spid, dp.database_id, + dp.database_name, dp.priority, dp.log_used, dp.wait_resource COLLATE DATABASE_DEFAULT, @@ -2563,10 +2673,16 @@ BEGIN dp.host_name, dp.login_name, dp.isolation_level, - inputbuf = dp.process_xml.value('(//process/inputbuf/text())[1]', 'nvarchar(MAX)'), - en = DENSE_RANK() OVER (ORDER BY dp.event_date), - qn = ROW_NUMBER() OVER (PARTITION BY dp.event_date ORDER BY dp.event_date) - 1, - dn = ROW_NUMBER() OVER (PARTITION BY dp.event_date, dp.id ORDER BY dp.event_date), + dp.client_option_1, + dp.client_option_2, + inputbuf = + dp.process_xml.value('(//process/inputbuf/text())[1]', 'nvarchar(MAX)'), + en = + DENSE_RANK() OVER (ORDER BY dp.event_date), + qn = + ROW_NUMBER() OVER (PARTITION BY dp.event_date ORDER BY dp.event_date) - 1, + dn = + ROW_NUMBER() OVER (PARTITION BY dp.event_date, dp.id ORDER BY dp.event_date), is_victim = 1, owner_mode = cao.wait_type COLLATE DATABASE_DEFAULT, owner_waiter_type = cao.waiter_type, @@ -2615,8 +2731,8 @@ BEGIN SELECT d.deadlock_type, d.event_date, - database_name = - DB_NAME(d.database_id), + d.id, + d.victim_id, d.spid, deadlock_group = N'Deadlock #' + @@ -2635,9 +2751,128 @@ BEGIN THEN N' - VICTIM' ELSE N'' END, + d.database_id, + d.database_name, + d.priority, + d.log_used, + d.wait_resource, + d.object_names, + d.wait_time, + d.transaction_name, + d.last_tran_started, + d.last_batch_started, + d.last_batch_completed, + d.lock_mode, + d.transaction_count, + d.client_app, + d.host_name, + d.login_name, + d.isolation_level, + d.client_option_1, + d.client_option_2, + inputbuf = + CASE + WHEN d.inputbuf + LIKE @inputbuf_bom + N'Proc |[Database Id = %' ESCAPE N'|' + THEN + OBJECT_SCHEMA_NAME + ( + SUBSTRING + ( + d.inputbuf, + CHARINDEX(N'Object Id = ', d.inputbuf) + 12, + LEN(d.inputbuf) - (CHARINDEX(N'Object Id = ', d.inputbuf) + 12) + ) + , + SUBSTRING + ( + d.inputbuf, + CHARINDEX(N'Database Id = ', d.inputbuf) + 14, + CHARINDEX(N'Object Id', d.inputbuf) - (CHARINDEX(N'Database Id = ', d.inputbuf) + 14) + ) + ) + + N'.' + + OBJECT_NAME + ( + SUBSTRING + ( + d.inputbuf, + CHARINDEX(N'Object Id = ', d.inputbuf) + 12, + LEN(d.inputbuf) - (CHARINDEX(N'Object Id = ', d.inputbuf) + 12) + ) + , + SUBSTRING + ( + d.inputbuf, + CHARINDEX(N'Database Id = ', d.inputbuf) + 14, + CHARINDEX(N'Object Id', d.inputbuf) - (CHARINDEX(N'Database Id = ', d.inputbuf) + 14) + ) + ) + ELSE d.inputbuf + END COLLATE Latin1_General_BIN2, + d.owner_mode, + d.owner_waiter_type, + d.owner_activity, + d.owner_waiter_activity, + d.owner_merging, + d.owner_spilling, + d.owner_waiting_to_close, + d.waiter_mode, + d.waiter_waiter_type, + d.waiter_owner_activity, + d.waiter_waiter_activity, + d.waiter_merging, + d.waiter_spilling, + d.waiter_waiting_to_close, + d.deadlock_graph, + d.is_victim + INTO #deadlocks + FROM deadlocks AS d + WHERE d.dn = 1 + AND (d.is_victim = @VictimsOnly + OR @VictimsOnly = 0) + AND d.qn < CASE + WHEN d.deadlock_type = N'Parallel Deadlock' + THEN 2 + ELSE 2147483647 + END + AND (DB_NAME(d.database_id) = @DatabaseName OR @DatabaseName IS NULL) + AND (d.event_date >= @StartDate OR @StartDate IS NULL) + AND (d.event_date < @EndDate OR @EndDate IS NULL) + AND (CONVERT(nvarchar(MAX), d.object_names) LIKE N'%' + @ObjectName + N'%' OR @ObjectName IS NULL) + AND (d.client_app = @AppName OR @AppName IS NULL) + AND (d.host_name = @HostName OR @HostName IS NULL) + AND (d.login_name = @LoginName OR @LoginName IS NULL) + OPTION (RECOMPILE, LOOP JOIN, HASH JOIN) + + UPDATE d + SET d.inputbuf = + REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE( + REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE( + REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE( + d.inputbuf, + NCHAR(31), N'?'), NCHAR(30), N'?'), NCHAR(29), N'?'), NCHAR(28), N'?'), NCHAR(27), N'?'), + NCHAR(26), N'?'), NCHAR(25), N'?'), NCHAR(24), N'?'), NCHAR(23), N'?'), NCHAR(22), N'?'), + NCHAR(21), N'?'), NCHAR(20), N'?'), NCHAR(19), N'?'), NCHAR(18), N'?'), NCHAR(17), N'?'), + NCHAR(16), N'?'), NCHAR(15), N'?'), NCHAR(14), N'?'), NCHAR(12), N'?'), NCHAR(11), N'?'), + NCHAR(8), N'?'), NCHAR(7), N'?'), NCHAR(6), N'?'), NCHAR(5), N'?'), NCHAR(4), N'?'), + NCHAR(3), N'?'), NCHAR(2), N'?'), NCHAR(1), N'?'), NCHAR(0), N'?') + FROM #deadlocks AS d + OPTION(RECOMPILE); + + SELECT + d.deadlock_type, + d.event_date, + database_name = + DB_NAME(d.database_id), + d.spid, + d.deadlock_group, + d.client_option_1, + d.client_option_2, query_xml = TRY_CAST(N'' + d.inputbuf + N'' AS xml), - query_string = d.inputbuf, + query_string = + d.inputbuf, d.object_names, d.isolation_level, d.owner_mode, @@ -2670,23 +2905,7 @@ BEGIN d.deadlock_graph, d.is_victim INTO #deadlock_results - FROM deadlocks AS d - WHERE d.dn = 1 - AND (d.is_victim = @VictimsOnly - OR @VictimsOnly = 0) - AND d.qn < CASE - WHEN d.deadlock_type = N'Parallel Deadlock' - THEN 2 - ELSE 2147483647 - END - AND (DB_NAME(d.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (d.event_date >= @StartDate OR @StartDate IS NULL) - AND (d.event_date < @EndDate OR @EndDate IS NULL) - AND (CONVERT(nvarchar(MAX), d.object_names) LIKE N'%' + @ObjectName + N'%' OR @ObjectName IS NULL) - AND (d.client_app = @AppName OR @AppName IS NULL) - AND (d.host_name = @HostName OR @HostName IS NULL) - AND (d.login_name = @LoginName OR @LoginName IS NULL) - OPTION (RECOMPILE, LOOP JOIN, HASH JOIN); + FROM #deadlocks AS d; IF @Debug = 1 BEGIN SET STATISTICS XML OFF; END; RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; @@ -2707,15 +2926,17 @@ BEGIN dr.deadlock_group, ' + CASE @ExportToExcel WHEN 1 - THEN N'dr.query_string AS query, - REPLACE(REPLACE(CONVERT(nvarchar(MAX), dr.object_names), '''', ''''), '''', '''') AS object_names,' - ELSE N'dr.query_xml AS query, + THEN N'query = dr.query_string, + object_names = REPLACE(REPLACE(CONVERT(nvarchar(MAX), dr.object_names), '''', ''''), '''', ''''),' + ELSE N'query = dr.query_xml, dr.object_names,' END + N' dr.isolation_level, dr.owner_mode, dr.waiter_mode, dr.transaction_count, + dr.client_option_1, + dr.client_option_2, dr.login_name, dr.host_name, dr.client_app, @@ -2880,9 +3101,10 @@ BEGIN IF @Debug = 1 BEGIN SELECT - table_name = N'#deadlock_data', + table_name = N'#dd', * - FROM #deadlock_data AS bx; + FROM #dd AS d + OPTION(RECOMPILE); SELECT table_name = N'#deadlock_data', From 060e5208d13f6a83d01c677c665c1ed27360a0e4 Mon Sep 17 00:00:00 2001 From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com> Date: Sun, 27 Nov 2022 16:32:33 -0500 Subject: [PATCH 343/662] Update sp_BlitzLock.sql Stupid comma --- sp_BlitzLock.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index 5c56c0f2f..f0ba8d896 100644 --- a/sp_BlitzLock.sql +++ b/sp_BlitzLock.sql @@ -1060,7 +1060,7 @@ BEGIN REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE( REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE( REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE( - ca.object_name COLLATE Latin1_General_BIN2,, + ca.object_name COLLATE Latin1_General_BIN2, NCHAR(31), N'?'), NCHAR(30), N'?'), NCHAR(29), N'?'), NCHAR(28), N'?'), NCHAR(27), N'?'), NCHAR(26), N'?'), NCHAR(25), N'?'), NCHAR(24), N'?'), NCHAR(23), N'?'), NCHAR(22), N'?'), NCHAR(21), N'?'), NCHAR(20), N'?'), NCHAR(19), N'?'), NCHAR(18), N'?'), NCHAR(17), N'?'), From d1df2f527c17f80e9e34c86a54e589d14db859a8 Mon Sep 17 00:00:00 2001 From: Cody Konior Date: Mon, 28 Nov 2022 14:53:27 +0800 Subject: [PATCH 344/662] Align spacing --- sp_BlitzCache.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sp_BlitzCache.sql b/sp_BlitzCache.sql index 864cb8c33..d93d710d1 100644 --- a/sp_BlitzCache.sql +++ b/sp_BlitzCache.sql @@ -3771,12 +3771,12 @@ FROM ##BlitzCacheProcs p WHERE b.OptionStart > 0 ) c OUTER APPLY ( - SELECT PATINDEX('%[^0-9]%', c.OptionSubstring) AS ObjectLength + SELECT PATINDEX('%[^0-9]%', c.OptionSubstring) AS ObjectLength ) d OUTER APPLY ( SELECT TRY_CAST(SUBSTRING(OptionSubstring, 1, d.ObjectLength - 1) AS INT) AS ObjectId ) e - JOIN sys.dm_exec_procedure_stats s ON DB_ID(p.DatabaseName) = s.database_id AND e.ObjectId = s.object_id + JOIN sys.dm_exec_procedure_stats s ON DB_ID(p.DatabaseName) = s.database_id AND e.ObjectId = s.object_id WHERE p.QueryType = 'Statement' AND p.SPID = @@SPID AND s.object_id IS NOT NULL From 28ca28caae8ed070602baffcb56e0d91ff5b23ef Mon Sep 17 00:00:00 2001 From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com> Date: Mon, 28 Nov 2022 14:02:22 -0500 Subject: [PATCH 345/662] Update sp_BlitzLock.sql Formatting and logic fixes --- sp_BlitzLock.sql | 267 ++++++++++++++++++++++++++--------------------- 1 file changed, 147 insertions(+), 120 deletions(-) diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index f0ba8d896..f0aa3b808 100644 --- a/sp_BlitzLock.sql +++ b/sp_BlitzLock.sql @@ -224,47 +224,57 @@ BEGIN /*Normally I'd hate this, but we RECOMPILE everything*/ SELECT @StartDate = - DATEADD - ( - MINUTE, - DATEDIFF - ( - MINUTE, - SYSDATETIME(), - GETUTCDATE() - ), - ISNULL - ( - @StartDate, + CASE + WHEN @StartDate IS NULL + THEN DATEADD ( - DAY, - -7, - SYSDATETIME() + MINUTE, + DATEDIFF + ( + MINUTE, + SYSDATETIME(), + GETUTCDATE() + ), + ISNULL + ( + @StartDate, + DATEADD + ( + DAY, + -7, + SYSDATETIME() + ) + ) ) - ) - ), + ELSE @StartDate + END, @EndDate = - DATEADD - ( - MINUTE, - DATEDIFF - ( - MINUTE, - SYSDATETIME(), - GETUTCDATE() - ), - ISNULL - ( - @EndDate, - SYSDATETIME() - ) - ); + CASE + WHEN @EndDate IS NULL + THEN + DATEADD + ( + MINUTE, + DATEDIFF + ( + MINUTE, + SYSDATETIME(), + GETUTCDATE() + ), + ISNULL + ( + @EndDate, + SYSDATETIME() + ) + ) + ELSE @EndDate + END; IF @OutputDatabaseName IS NOT NULL BEGIN /*IF databaseName is set, do some sanity checks and put [] around def.*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('@OutputDatabaseName set to %s, checking validity', 0, 1, @OutputDatabaseName) WITH NOWAIT; + RAISERROR('@OutputDatabaseName set to %s, checking validity at %s', 0, 1, @OutputDatabaseName, @d) WITH NOWAIT; IF NOT EXISTS ( @@ -551,7 +561,7 @@ BEGIN @TargetSessionType = t.target_name FROM sys.dm_xe_sessions AS s JOIN sys.dm_xe_session_targets AS t - ON s.address = t.event_session_address + ON s.address = t.event_session_address WHERE s.name = @EventSessionName AND t.target_name IN (N'event_file', N'ring_buffer') ORDER BY t.target_name @@ -571,7 +581,7 @@ BEGIN @TargetSessionType = t.target_name FROM sys.dm_xe_database_sessions AS s JOIN sys.dm_xe_database_session_targets AS t - ON s.address = t.event_session_address + ON s.address = t.event_session_address WHERE s.name = @EventSessionName AND t.target_name IN (N'event_file', N'ring_buffer') ORDER BY t.target_name @@ -605,7 +615,7 @@ BEGIN x = TRY_CAST(t.target_data AS xml) FROM sys.dm_xe_session_targets AS t JOIN sys.dm_xe_sessions AS s - ON s.address = t.event_session_address + ON s.address = t.event_session_address WHERE s.name = @EventSessionName AND t.target_name = N'ring_buffer' OPTION(RECOMPILE); @@ -628,7 +638,7 @@ BEGIN x = TRY_CAST(t.target_data AS xml) FROM sys.dm_xe_database_session_targets AS t JOIN sys.dm_xe_database_sessions AS s - ON s.address = t.event_session_address + ON s.address = t.event_session_address WHERE s.name = @EventSessionName AND t.target_name = N'ring_buffer' OPTION(RECOMPILE); @@ -655,7 +665,7 @@ BEGIN @TargetSessionId = t.target_id FROM sys.server_event_session_targets AS t JOIN sys.server_event_sessions AS s - ON s.event_session_id = t.event_session_id + ON s.event_session_id = t.event_session_id WHERE t.name = @TargetSessionType AND s.name = @EventSessionName OPTION(RECOMPILE); @@ -694,7 +704,7 @@ BEGIN @TargetSessionId = t.target_id FROM sys.dm_xe_database_session_targets AS t JOIN sys.dm_xe_database_sessions AS s - ON s.event_session_id = t.event_session_id + ON s.event_session_id = t.event_session_id WHERE t.name = @TargetSessionType AND s.name = @EventSessionName OPTION(RECOMPILE); @@ -732,7 +742,7 @@ BEGIN x = TRY_CAST(f.event_data AS xml) FROM sys.fn_xe_file_target_read_file(@FileName, NULL, NULL, NULL) AS f LEFT JOIN #t AS t - ON 1 = 1 + ON 1 = 1 OPTION(RECOMPILE); SET @d = CONVERT(varchar(40), GETDATE(), 109); @@ -760,7 +770,7 @@ BEGIN deadlock_xml = e.x.query(N'.') FROM #x AS x LEFT JOIN #t AS t - ON 1 = 1 + ON 1 = 1 CROSS APPLY x.x.nodes('/RingBufferTarget/event') AS e(x) WHERE e.x.exist('@name[ .= "xml_deadlock_report"]') = 1 AND e.x.exist('@timestamp[. >= sql:variable("@StartDate")]') = 1 @@ -790,7 +800,7 @@ BEGIN deadlock_xml = e.x.query('.') FROM #x AS x LEFT JOIN #t AS t - ON 1 = 1 + ON 1 = 1 CROSS APPLY x.x.nodes('/event') AS e(x) WHERE e.x.exist('/event/@name[ .= "xml_deadlock_report"]') = 1 AND e.x.exist('/event/@timestamp[. >= sql:variable("@StartDate")]') = 1 @@ -819,7 +829,7 @@ BEGIN INTO #xml FROM sys.fn_xe_file_target_read_file('system_health*.xel', NULL, NULL, NULL) LEFT JOIN #t AS t - ON 1 = 1 + ON 1 = 1 OPTION(RECOMPILE); INSERT @@ -828,7 +838,7 @@ BEGIN deadlock_xml = xml.deadlock_xml FROM #xml AS xml LEFT JOIN #t AS t - ON 1 = 1 + ON 1 = 1 CROSS APPLY xml.deadlock_xml.nodes('/event/@name') AS e(x) WHERE e.x.exist('/event/@name[ . = "xml_deadlock_report"]') = 1 AND e.x.exist('/event/@timestamp[. >= sql:variable("@StartDate")]') = 1 @@ -855,7 +865,7 @@ BEGIN INTO #dd FROM #deadlock_data AS d1 LEFT JOIN #t AS t - ON 1 = 1 + ON 1 = 1 OPTION(RECOMPILE); SET @d = CONVERT(varchar(40), GETDATE(), 109); @@ -893,49 +903,49 @@ BEGIN q.host_name, q.login_name, q.isolation_level, - client_option_1 = - SUBSTRING - ( - CASE WHEN q.clientoption1 & 1 = 1 THEN ', DISABLE_DEF_CNST_CHECK' ELSE '' END + - CASE WHEN q.clientoption1 & 2 = 2 THEN ', IMPLICIT_TRANSACTIONS' ELSE '' END + - CASE WHEN q.clientoption1 & 4 = 4 THEN ', CURSOR_CLOSE_ON_COMMIT' ELSE '' END + - CASE WHEN q.clientoption1 & 8 = 8 THEN ', ANSI_WARNINGS' ELSE '' END + - CASE WHEN q.clientoption1 & 16 = 16 THEN ', ANSI_PADDING' ELSE '' END + - CASE WHEN q.clientoption1 & 32 = 32 THEN ', ANSI_NULLS' ELSE '' END + - CASE WHEN q.clientoption1 & 64 = 64 THEN ', ARITHABORT' ELSE '' END + - CASE WHEN q.clientoption1 & 128 = 128 THEN ', ARITHIGNORE' ELSE '' END + - CASE WHEN q.clientoption1 & 256 = 256 THEN ', QUOTED_IDENTIFIER' ELSE '' END + - CASE WHEN q.clientoption1 & 512 = 512 THEN ', NOCOUNT' ELSE '' END + - CASE WHEN q.clientoption1 & 1024 = 1024 THEN ', ANSI_NULL_DFLT_ON' ELSE '' END + - CASE WHEN q.clientoption1 & 2048 = 2048 THEN ', ANSI_NULL_DFLT_OFF' ELSE '' END + - CASE WHEN q.clientoption1 & 4096 = 4096 THEN ', CONCAT_NULL_YIELDS_NULL' ELSE '' END + - CASE WHEN q.clientoption1 & 8192 = 8192 THEN ', NUMERIC_ROUNDABORT' ELSE '' END + - CASE WHEN q.clientoption1 & 16384 = 16384 THEN ', XACT_ABORT' ELSE '' END, - 3, - 8000 - ), - client_option_2 = - SUBSTRING - ( - CASE WHEN q.clientoption2 & 1024 = 1024 THEN ', DB CHAINING' ELSE '' END + - CASE WHEN q.clientoption2 & 2048 = 2048 THEN ', NUMERIC ROUNDABORT' ELSE '' END + - CASE WHEN q.clientoption2 & 4096 = 4096 THEN ', ARITHABORT' ELSE '' END + - CASE WHEN q.clientoption2 & 8192 = 8192 THEN ', ANSI PADDING' ELSE '' END + - CASE WHEN q.clientoption2 & 16384 = 16384 THEN ', ANSI NULL DEFAULT' ELSE '' END + - CASE WHEN q.clientoption2 & 65536 = 65536 THEN ', CONCAT NULL YIELDS NULL' ELSE '' END + - CASE WHEN q.clientoption2 & 131072 = 131072 THEN ', RECURSIVE TRIGGERS' ELSE '' END + - CASE WHEN q.clientoption2 & 1048576 = 1048576 THEN ', DEFAULT TO LOCAL CURSOR' ELSE '' END + - CASE WHEN q.clientoption2 & 8388608 = 8388608 THEN ', QUOTED IDENTIFIER' ELSE '' END + - CASE WHEN q.clientoption2 & 16777216 = 16777216 THEN ', AUTO CREATE STATISTICS' ELSE '' END + - CASE WHEN q.clientoption2 & 33554432 = 33554432 THEN ', CURSOR CLOSE ON COMMIT' ELSE '' END + - CASE WHEN q.clientoption2 & 67108864 = 67108864 THEN ', ANSI NULLS' ELSE '' END + - CASE WHEN q.clientoption2 & 268435456 = 268435456 THEN ', ANSI WARNINGS' ELSE '' END + - CASE WHEN q.clientoption2 & 536870912 = 536870912 THEN ', FULL TEXT ENABLED' ELSE '' END + - CASE WHEN q.clientoption2 & 1073741824 = 1073741824 THEN ', AUTO UPDATE STATISTICS' ELSE '' END + - CASE WHEN q.clientoption2 & 1469283328 = 1469283328 THEN ', ALL SETTABLE OPTIONS' ELSE '' END, - 3, - 8000 - ), + client_option_1 = + SUBSTRING + ( + CASE WHEN q.clientoption1 & 1 = 1 THEN ', DISABLE_DEF_CNST_CHECK' ELSE '' END + + CASE WHEN q.clientoption1 & 2 = 2 THEN ', IMPLICIT_TRANSACTIONS' ELSE '' END + + CASE WHEN q.clientoption1 & 4 = 4 THEN ', CURSOR_CLOSE_ON_COMMIT' ELSE '' END + + CASE WHEN q.clientoption1 & 8 = 8 THEN ', ANSI_WARNINGS' ELSE '' END + + CASE WHEN q.clientoption1 & 16 = 16 THEN ', ANSI_PADDING' ELSE '' END + + CASE WHEN q.clientoption1 & 32 = 32 THEN ', ANSI_NULLS' ELSE '' END + + CASE WHEN q.clientoption1 & 64 = 64 THEN ', ARITHABORT' ELSE '' END + + CASE WHEN q.clientoption1 & 128 = 128 THEN ', ARITHIGNORE' ELSE '' END + + CASE WHEN q.clientoption1 & 256 = 256 THEN ', QUOTED_IDENTIFIER' ELSE '' END + + CASE WHEN q.clientoption1 & 512 = 512 THEN ', NOCOUNT' ELSE '' END + + CASE WHEN q.clientoption1 & 1024 = 1024 THEN ', ANSI_NULL_DFLT_ON' ELSE '' END + + CASE WHEN q.clientoption1 & 2048 = 2048 THEN ', ANSI_NULL_DFLT_OFF' ELSE '' END + + CASE WHEN q.clientoption1 & 4096 = 4096 THEN ', CONCAT_NULL_YIELDS_NULL' ELSE '' END + + CASE WHEN q.clientoption1 & 8192 = 8192 THEN ', NUMERIC_ROUNDABORT' ELSE '' END + + CASE WHEN q.clientoption1 & 16384 = 16384 THEN ', XACT_ABORT' ELSE '' END, + 3, + 8000 + ), + client_option_2 = + SUBSTRING + ( + CASE WHEN q.clientoption2 & 1024 = 1024 THEN ', DB CHAINING' ELSE '' END + + CASE WHEN q.clientoption2 & 2048 = 2048 THEN ', NUMERIC ROUNDABORT' ELSE '' END + + CASE WHEN q.clientoption2 & 4096 = 4096 THEN ', ARITHABORT' ELSE '' END + + CASE WHEN q.clientoption2 & 8192 = 8192 THEN ', ANSI PADDING' ELSE '' END + + CASE WHEN q.clientoption2 & 16384 = 16384 THEN ', ANSI NULL DEFAULT' ELSE '' END + + CASE WHEN q.clientoption2 & 65536 = 65536 THEN ', CONCAT NULL YIELDS NULL' ELSE '' END + + CASE WHEN q.clientoption2 & 131072 = 131072 THEN ', RECURSIVE TRIGGERS' ELSE '' END + + CASE WHEN q.clientoption2 & 1048576 = 1048576 THEN ', DEFAULT TO LOCAL CURSOR' ELSE '' END + + CASE WHEN q.clientoption2 & 8388608 = 8388608 THEN ', QUOTED IDENTIFIER' ELSE '' END + + CASE WHEN q.clientoption2 & 16777216 = 16777216 THEN ', AUTO CREATE STATISTICS' ELSE '' END + + CASE WHEN q.clientoption2 & 33554432 = 33554432 THEN ', CURSOR CLOSE ON COMMIT' ELSE '' END + + CASE WHEN q.clientoption2 & 67108864 = 67108864 THEN ', ANSI NULLS' ELSE '' END + + CASE WHEN q.clientoption2 & 268435456 = 268435456 THEN ', ANSI WARNINGS' ELSE '' END + + CASE WHEN q.clientoption2 & 536870912 = 536870912 THEN ', FULL TEXT ENABLED' ELSE '' END + + CASE WHEN q.clientoption2 & 1073741824 = 1073741824 THEN ', AUTO UPDATE STATISTICS' ELSE '' END + + CASE WHEN q.clientoption2 & 1469283328 = 1469283328 THEN ', ALL SETTABLE OPTIONS' ELSE '' END, + 3, + 8000 + ), q.process_xml INTO #deadlock_process FROM @@ -982,7 +992,7 @@ BEGIN AND (ca.dp.exist('@loginname[. = sql:variable("@LoginName")]') = 1 OR @LoginName IS NULL) ) AS q LEFT JOIN #t AS t - ON 1 = 1 + ON 1 = 1 OPTION(RECOMPILE); @@ -1066,7 +1076,7 @@ BEGIN NCHAR(21), N'?'), NCHAR(20), N'?'), NCHAR(19), N'?'), NCHAR(18), N'?'), NCHAR(17), N'?'), NCHAR(16), N'?'), NCHAR(15), N'?'), NCHAR(14), N'?'), NCHAR(12), N'?'), NCHAR(11), N'?'), NCHAR(8), N'?'), NCHAR(7), N'?'), NCHAR(6), N'?'), NCHAR(5), N'?'), NCHAR(4), N'?'), - NCHAR(3), N'?'), NCHAR(2), N'?'), NCHAR(1), N'?'), NCHAR(0), N'?'), + NCHAR(3), N'?'), NCHAR(2), N'?'), NCHAR(1), N'?'), NCHAR(0), N'?'), ca.lock_mode, ca.index_name, ca.associatedObjectId, @@ -1556,9 +1566,9 @@ BEGIN EXECUTE sys.sp_MSforeachdb N' SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - + USE [?]; - + IF EXISTS ( SELECT @@ -1571,15 +1581,17 @@ BEGIN database_id = DB_ID(), p.partition_id, - s.name as schema_name, - t.name as table_name + schema_name = + s.name, + table_name = + t.name FROM sys.partitions p JOIN sys.tables t ON t.object_id = p.object_id JOIN sys.schemas s ON s.schema_id = t.schema_id - WHERE s.name is not NULL - AND t.name is not NULL + WHERE s.name IS NOT NULL + AND t.name IS NOT NULL OPTION(RECOMPILE); END; '; @@ -1605,15 +1617,17 @@ BEGIN database_id = DB_ID(), p.partition_id, - s.name as schema_name, - t.name as table_name + schema_name = + s.name, + table_name = + t.name FROM sys.partitions p JOIN sys.tables t ON t.object_id = p.object_id JOIN sys.schemas s ON s.schema_id = t.schema_id - WHERE s.name is not NULL - AND t.name is not NULL + WHERE s.name IS NOT NULL + AND t.name IS NOT NULL OPTION(RECOMPILE); SET @d = CONVERT(varchar(40), GETDATE(), 109); @@ -1898,17 +1912,20 @@ BEGIN N' instances of deadlocks involving the login ' + ISNULL ( - dp.login_name, N'UNKNOWN' + dp.login_name, + N'UNKNOWN' ) + N' from the application ' + ISNULL ( - dp.client_app, N'UNKNOWN' + dp.client_app, + N'UNKNOWN' ) + N' on host ' + ISNULL ( - dp.host_name, N'UNKNOWN' + dp.host_name, + N'UNKNOWN' ) + N'.' FROM #deadlock_process AS dp @@ -2089,8 +2106,8 @@ BEGIN END + N';' FROM deadlock_stack AS ds JOIN #deadlock_owner_waiter AS dow - ON dow.owner_id = ds.id - AND dow.event_date = ds.event_date + ON dow.owner_id = ds.id + AND dow.event_date = ds.event_date WHERE 1 = 1 AND (dow.database_id = @DatabaseName OR @DatabaseName IS NULL) AND (dow.event_date >= @StartDate OR @StartDate IS NULL) @@ -2220,8 +2237,8 @@ BEGIN table_name = a.table_name FROM #deadlock_owner_waiter AS dow LEFT JOIN @sysAssObjId AS a - ON a.database_id = dow.database_id - AND a.partition_id = dow.associatedObjectId + ON a.database_id = dow.database_id + AND a.partition_id = dow.associatedObjectId WHERE 1 = 1 AND (dow.database_id = @DatabaseName OR @DatabaseName IS NULL) AND (dow.event_date >= @StartDate OR @StartDate IS NULL) @@ -2771,7 +2788,7 @@ BEGIN d.client_option_1, d.client_option_2, inputbuf = - CASE + CASE WHEN d.inputbuf LIKE @inputbuf_bom + N'Proc |[Database Id = %' ESCAPE N'|' THEN @@ -2786,12 +2803,12 @@ BEGIN , SUBSTRING ( - d.inputbuf, - CHARINDEX(N'Database Id = ', d.inputbuf) + 14, + d.inputbuf, + CHARINDEX(N'Database Id = ', d.inputbuf) + 14, CHARINDEX(N'Object Id', d.inputbuf) - (CHARINDEX(N'Database Id = ', d.inputbuf) + 14) ) - ) + - N'.' + + ) + + N'.' + OBJECT_NAME ( SUBSTRING @@ -2803,8 +2820,8 @@ BEGIN , SUBSTRING ( - d.inputbuf, - CHARINDEX(N'Database Id = ', d.inputbuf) + 14, + d.inputbuf, + CHARINDEX(N'Database Id = ', d.inputbuf) + 14, CHARINDEX(N'Object Id', d.inputbuf) - (CHARINDEX(N'Database Id = ', d.inputbuf) + 14) ) ) @@ -2843,7 +2860,7 @@ BEGIN AND (d.client_app = @AppName OR @AppName IS NULL) AND (d.host_name = @HostName OR @HostName IS NULL) AND (d.login_name = @LoginName OR @LoginName IS NULL) - OPTION (RECOMPILE, LOOP JOIN, HASH JOIN) + OPTION (RECOMPILE, LOOP JOIN, HASH JOIN); UPDATE d SET d.inputbuf = @@ -2856,7 +2873,7 @@ BEGIN NCHAR(21), N'?'), NCHAR(20), N'?'), NCHAR(19), N'?'), NCHAR(18), N'?'), NCHAR(17), N'?'), NCHAR(16), N'?'), NCHAR(15), N'?'), NCHAR(14), N'?'), NCHAR(12), N'?'), NCHAR(11), N'?'), NCHAR(8), N'?'), NCHAR(7), N'?'), NCHAR(6), N'?'), NCHAR(5), N'?'), NCHAR(4), N'?'), - NCHAR(3), N'?'), NCHAR(2), N'?'), NCHAR(1), N'?'), NCHAR(0), N'?') + NCHAR(3), N'?'), NCHAR(2), N'?'), NCHAR(1), N'?'), NCHAR(0), N'?') FROM #deadlocks AS d OPTION(RECOMPILE); @@ -2959,17 +2976,27 @@ BEGIN dr.waiter_waiter_activity, dr.waiter_merging, dr.waiter_spilling, - dr.waiter_waiting_to_close' + + dr.waiter_waiting_to_close,' + CASE @ExportToExcel WHEN 1 - THEN N'' - ELSE N', + THEN N' + REPLACE(REPLACE(REPLACE(REPLACE( + CONVERT + ( + nvarchar(MAX), + dr.deadlock_graph COLLATE Latin1_General_BIN2 + ), + ''NCHAR(10)'', ''''), ''NCHAR(13)'', ''''), + ''CHAR(10)'', ''''), ''CHAR(13)'', '''')' + ELSE N' dr.deadlock_graph' END + N' FROM #deadlock_results AS dr - ORDER BY dr.event_date, dr.is_victim DESC + ORDER BY + dr.event_date, + dr.is_victim DESC OPTION(RECOMPILE, LOOP JOIN, HASH JOIN); '; From f812b71c41d49c235c796760801539ece56ec37c Mon Sep 17 00:00:00 2001 From: Wilfred van Dijk Date: Mon, 28 Nov 2022 20:09:45 +0100 Subject: [PATCH 346/662] Update sp_DatabaseRestore.sql fixed ticket 3156 sp_databaserestore don't see backups which consists of more than 10 backupfiles when using @stopAt --- sp_DatabaseRestore.sql | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/sp_DatabaseRestore.sql b/sp_DatabaseRestore.sql index b63aac6d3..5dbd9939c 100755 --- a/sp_DatabaseRestore.sql +++ b/sp_DatabaseRestore.sql @@ -636,15 +636,19 @@ BEGIN /*End folder sanity check*/ IF @StopAt IS NOT NULL - BEGIN - DELETE - FROM @FileList - WHERE BackupFile LIKE N'%.bak' - AND - BackupFile LIKE N'%' + @Database + N'%' - AND - (REPLACE( RIGHT( REPLACE( BackupFile, RIGHT( BackupFile, PATINDEX( '%_[0-9][0-9]%', REVERSE( BackupFile ) ) ), '' ), 16 ), '_', '' ) > @StopAt); - END + BEGIN + DELETE + FROM @FileList + WHERE BackupFile LIKE N'%[_][0-9].bak' + AND BackupFile LIKE N'%' + @Database + N'%' + AND (REPLACE( RIGHT( REPLACE( BackupFile, RIGHT( BackupFile, PATINDEX( '%_[0-9][0-9]%', REVERSE( BackupFile ) ) ), '' ), 16 ), '_', '' ) > @StopAt); + + DELETE + FROM @FileList + WHERE BackupFile LIKE N'%[_][0-9][0-9].bak' + AND BackupFile LIKE N'%' + @Database + N'%' + AND (REPLACE( RIGHT( REPLACE( BackupFile, RIGHT( BackupFile, PATINDEX( '%_[0-9][0-9]%', REVERSE( BackupFile ) ) ), '' ), 18 ), '_', '' ) > @StopAt); + END; -- Find latest full backup SELECT @LastFullBackup = MAX(BackupFile) From 87c59a6c2409b745b0c60164959e084486ba6f36 Mon Sep 17 00:00:00 2001 From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com> Date: Mon, 28 Nov 2022 14:19:51 -0500 Subject: [PATCH 347/662] Update sp_BlitzLock.sql Trying to get the file read for system health faster. Seems futile, sort of. --- sp_BlitzLock.sql | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index f0aa3b808..6570e8dd0 100644 --- a/sp_BlitzLock.sql +++ b/sp_BlitzLock.sql @@ -823,13 +823,22 @@ BEGIN IF @Debug = 1 BEGIN SET STATISTICS XML ON; END; - SELECT - deadlock_xml = - TRY_CAST(event_data AS xml) - INTO #xml - FROM sys.fn_xe_file_target_read_file('system_health*.xel', NULL, NULL, NULL) - LEFT JOIN #t AS t - ON 1 = 1 + SELECT + xml.deadlock_xml + INTO #xml + FROM + ( + SELECT + deadlock_xml = + TRY_CAST(event_data AS xml) + FROM sys.fn_xe_file_target_read_file('system_health*.xel', NULL, NULL, NULL) + LEFT JOIN #t AS t + ON 1 = 1 + ) AS xml + CROSS APPLY xml.deadlock_xml.nodes('/event') AS e(x) + WHERE e.x.exist('@name[ . = "xml_deadlock_report"]') = 1 + AND e.x.exist('@timestamp[. >= sql:variable("@StartDate")]') = 1 + AND e.x.exist('@timestamp[. < sql:variable("@EndDate")]') = 1 OPTION(RECOMPILE); INSERT @@ -839,10 +848,7 @@ BEGIN FROM #xml AS xml LEFT JOIN #t AS t ON 1 = 1 - CROSS APPLY xml.deadlock_xml.nodes('/event/@name') AS e(x) - WHERE e.x.exist('/event/@name[ . = "xml_deadlock_report"]') = 1 - AND e.x.exist('/event/@timestamp[. >= sql:variable("@StartDate")]') = 1 - AND e.x.exist('/event/@timestamp[. < sql:variable("@EndDate")]') = 1 + WHERE xml.deadlock_xml IS NOT NULL OPTION(RECOMPILE); IF @Debug = 1 BEGIN SET STATISTICS XML OFF; END; From 6f212fb8e7c2b8b78e69dc6ab050648e6ed2a5e0 Mon Sep 17 00:00:00 2001 From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com> Date: Mon, 28 Nov 2022 14:23:22 -0500 Subject: [PATCH 348/662] Update sp_BlitzLock.sql Meh --- sp_BlitzLock.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index 6570e8dd0..b11cbd61e 100644 --- a/sp_BlitzLock.sql +++ b/sp_BlitzLock.sql @@ -830,8 +830,8 @@ BEGIN ( SELECT deadlock_xml = - TRY_CAST(event_data AS xml) - FROM sys.fn_xe_file_target_read_file('system_health*.xel', NULL, NULL, NULL) + TRY_CAST(fx.event_data AS xml) + FROM sys.fn_xe_file_target_read_file('system_health*.xel', NULL, NULL, NULL) AS fx LEFT JOIN #t AS t ON 1 = 1 ) AS xml From d3807dc6328b174a1bf59cdcd1fb74ad43bd235d Mon Sep 17 00:00:00 2001 From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com> Date: Mon, 28 Nov 2022 15:04:01 -0500 Subject: [PATCH 349/662] Update sp_BlitzLock.sql Add a count of deadlocks involving selects to help determine if RCSI would be useful. --- sp_BlitzLock.sql | 143 +++++++++++++++++++++++++++++++---------------- 1 file changed, 95 insertions(+), 48 deletions(-) diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index b11cbd61e..fc2a3dc84 100644 --- a/sp_BlitzLock.sql +++ b/sp_BlitzLock.sql @@ -1609,7 +1609,7 @@ BEGIN IF @Azure = 1 BEGIN SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Inserting to @sysAssObjId %s', 0, 1, @d) WITH NOWAIT; + RAISERROR('Inserting to @sysAssObjId at %s', 0, 1, @d) WITH NOWAIT; INSERT INTO @sysAssObjId @@ -1644,7 +1644,7 @@ BEGIN /*Check 1 is deadlocks by database*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Check 1 %s', 0, 1, @d) WITH NOWAIT; + RAISERROR('Check 1 databases %s', 0, 1, @d) WITH NOWAIT; INSERT #deadlock_findings WITH(TABLOCKX) @@ -1681,9 +1681,9 @@ BEGIN RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - /*Check 2 is deadlocks by object*/ + /*Check 2 is deadlocks with selects*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Check 2 objects %s', 0, 1, @d) WITH NOWAIT; + RAISERROR('Check 2 selects %s', 0, 1, @d) WITH NOWAIT; INSERT #deadlock_findings WITH(TABLOCKX) @@ -1697,6 +1697,48 @@ BEGIN SELECT check_id = 2, dow.database_name, + object_name = + N'You Might Need RCSI', + finding_group = N'Total Deadlocks Involving Selects', + finding = + N'There have been ' + + CONVERT + ( + nvarchar(20), + COUNT_BIG(DISTINCT dow.event_date) + ) + + N' deadlock(s) between read queries and modification queries.' + FROM #deadlock_owner_waiter AS dow + WHERE 1 = 1 + AND dow.lock_mode IN + ( + N'S', + N'IS' + ) + AND (dow.database_id = @DatabaseName OR @DatabaseName IS NULL) + AND (dow.event_date >= @StartDate OR @StartDate IS NULL) + AND (dow.event_date < @EndDate OR @EndDate IS NULL) + AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) + GROUP BY + dow.database_name + OPTION(RECOMPILE); + + /*Check 3 is deadlocks by object*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Check 3 objects %s', 0, 1, @d) WITH NOWAIT; + + INSERT + #deadlock_findings WITH(TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding + ) + SELECT + check_id = 3, + dow.database_name, object_name = ISNULL ( @@ -1725,9 +1767,9 @@ BEGIN RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - /*Check 2 continuation, number of locks per index*/ + /*Check 3 continuation, number of locks per index*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Check 2 indexes %s', 0, 1, @d) WITH NOWAIT; + RAISERROR('Check 3 indexes %s', 0, 1, @d) WITH NOWAIT; INSERT #deadlock_findings WITH(TABLOCKX) @@ -1739,7 +1781,7 @@ BEGIN finding ) SELECT - check_id = 2, + check_id = 3, dow.database_name, index_name = dow.index_name, finding_group = N'Total Index Deadlocks', @@ -1770,9 +1812,9 @@ BEGIN RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - /*Check 2 continuation, number of locks per heap*/ + /*Check 3 continuation, number of locks per heap*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Check 2 heaps %s', 0, 1, @d) WITH NOWAIT; + RAISERROR('Check 3 heaps %s', 0, 1, @d) WITH NOWAIT; INSERT #deadlock_findings WITH(TABLOCKX) @@ -1784,7 +1826,7 @@ BEGIN finding ) SELECT - check_id = 2, + check_id = 3, dow.database_name, index_name = dow.index_name, finding_group = N'Total Heap Deadlocks', @@ -1814,9 +1856,9 @@ BEGIN RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - /*Check 3 looks for Serializable locking*/ + /*Check 4 looks for Serializable locking*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Check 3 %s', 0, 1, @d) WITH NOWAIT; + RAISERROR('Check 4 %s', 0, 1, @d) WITH NOWAIT; INSERT #deadlock_findings WITH(TABLOCKX) @@ -1828,7 +1870,7 @@ BEGIN finding ) SELECT - check_id = 3, + check_id = 4, database_name = dp.database_name, object_name = N'-', @@ -1838,7 +1880,7 @@ BEGIN CONVERT ( nvarchar(20), - COUNT_BIG(*) + COUNT_BIG(DISTINCT dp.event_date) ) + N' instances of Serializable deadlocks.' FROM #deadlock_process AS dp @@ -1854,9 +1896,9 @@ BEGIN RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - /*Check 4 looks for Repeatable Read locking*/ + /*Check 5 looks for Repeatable Read locking*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Check 4 %s', 0, 1, @d) WITH NOWAIT; + RAISERROR('Check 5 %s', 0, 1, @d) WITH NOWAIT; INSERT #deadlock_findings WITH(TABLOCKX) @@ -1868,13 +1910,17 @@ BEGIN finding ) SELECT - check_id = 4, + check_id = 5, dp.database_name, object_name = N'-', finding_group = N'Repeatable Read Deadlocking', finding = N'This database has had ' + - CONVERT(nvarchar(20), COUNT_BIG(*)) + + CONVERT + ( + nvarchar(20), + COUNT_BIG(DISTINCT dp.event_date) + ) + N' instances of Repeatable Read deadlocks.' FROM #deadlock_process AS dp WHERE dp.isolation_level LIKE N'repeatable read%' @@ -1889,9 +1935,9 @@ BEGIN RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - /*Check 5 breaks down app, host, and login information*/ + /*Check 6 breaks down app, host, and login information*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Check 5 %s', 0, 1, @d) WITH NOWAIT; + RAISERROR('Check 6 %s', 0, 1, @d) WITH NOWAIT; INSERT #deadlock_findings WITH(TABLOCKX) @@ -1903,7 +1949,7 @@ BEGIN finding ) SELECT - check_id = 5, + check_id = 6, database_name = dp.database_name, object_name = N'-', @@ -1951,9 +1997,9 @@ BEGIN RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - /*Check 6 breaks down the types of locks (object, page, key, etc.)*/ + /*Check 7 breaks down the types of locks (object, page, key, etc.)*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Check 6 %s', 0, 1, @d) WITH NOWAIT; + RAISERROR('Check 7 %s', 0, 1, @d) WITH NOWAIT; WITH lock_types AS @@ -2012,7 +2058,7 @@ BEGIN finding ) SELECT - check_id = 6, + check_id = 7, lt.database_name, lt.object_name, finding_group = N'Types of locks by object', @@ -2042,9 +2088,9 @@ BEGIN RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - /*Check 7 gives you more info queries for sp_BlitzCache & BlitzQueryStore*/ + /*Check 8 gives you more info queries for sp_BlitzCache & BlitzQueryStore*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Check 7 part 1 %s', 0, 1, @d) WITH NOWAIT; + RAISERROR('Check 8 part 1 %s', 0, 1, @d) WITH NOWAIT; WITH deadlock_stack AS @@ -2100,7 +2146,7 @@ BEGIN finding ) SELECT DISTINCT - check_id = 7, + check_id = 8, dow.database_name, object_name = ds.proc_name, finding_group = N'More Info - Query', @@ -2126,7 +2172,7 @@ BEGIN IF (@ProductVersionMajor >= 13 OR @Azure = 1) BEGIN SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Check 7 part 2 %s', 0, 1, @d) WITH NOWAIT; + RAISERROR('Check 8 part 2 %s', 0, 1, @d) WITH NOWAIT; WITH deadlock_stack AS @@ -2154,7 +2200,7 @@ BEGIN finding ) SELECT DISTINCT - check_id = 7, + check_id = 8, dow.database_name, object_name = ds.proc_name, finding_group = N'More Info - Query', @@ -2180,9 +2226,9 @@ BEGIN RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; END; - /*Check 8 gives you stored proc deadlock counts*/ + /*Check 9 gives you stored procedure deadlock counts*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Check 8 %s', 0, 1, @d) WITH NOWAIT; + RAISERROR('Check 9 procedure deadlocks %s', 0, 1, @d) WITH NOWAIT; INSERT #deadlock_findings WITH(TABLOCKX) @@ -2194,7 +2240,7 @@ BEGIN finding ) SELECT - check_id = 8, + check_id = 9, database_name = dp.database_name, object_name = ds.proc_name, @@ -2229,9 +2275,9 @@ BEGIN RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - /*Check 9 gives you more info queries for sp_BlitzIndex */ + /*Check 10 gives you more info queries for sp_BlitzIndex */ SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Check 9 %s', 0, 1, @d) WITH NOWAIT; + RAISERROR('Check 10 more info, blitzindex %s', 0, 1, @d) WITH NOWAIT; WITH bi AS @@ -2262,7 +2308,7 @@ BEGIN finding ) SELECT DISTINCT - check_id = 9, + check_id = 10, bi.database_name, bi.object_name, finding_group = N'More Info - Table', @@ -2280,9 +2326,9 @@ BEGIN RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - /*Check 10 gets total deadlock wait time per object*/ + /*Check 11 gets total deadlock wait time per object*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Check 10 %s', 0, 1, @d) WITH NOWAIT; + RAISERROR('Check 11 deadlock wait time per object %s', 0, 1, @d) WITH NOWAIT; WITH chopsuey AS @@ -2355,7 +2401,7 @@ BEGIN finding ) SELECT - check_id = 10, + check_id = 11, cs.database_name, cs.object_name, finding_group = N'Total object deadlock wait time', @@ -2371,9 +2417,9 @@ BEGIN RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - /*Check 11 gets total deadlock wait time per database*/ + /*Check 12 gets total deadlock wait time per database*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Check 11 %s', 0, 1, @d) WITH NOWAIT; + RAISERROR('Check 12 deadlock wait time per database %s', 0, 1, @d) WITH NOWAIT; WITH wait_time AS @@ -2415,7 +2461,7 @@ BEGIN finding ) SELECT - check_id = 11, + check_id = 12, wt.database_name, object_name = N'-', finding_group = N'Total database deadlock wait time', @@ -2463,9 +2509,9 @@ BEGIN RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - /*Check 12 gets total deadlock wait time for SQL Agent*/ + /*Check 13 gets total deadlock wait time for SQL Agent*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Check 12 %s', 0, 1, @d) WITH NOWAIT; + RAISERROR('Check 13 deadlock wait time for SQL Agent %s', 0, 1, @d) WITH NOWAIT; INSERT #deadlock_findings WITH(TABLOCKX) @@ -2477,7 +2523,7 @@ BEGIN finding ) SELECT - check_id = 12, + check_id = 13, database_name = DB_NAME(aj.database_id), object_name = @@ -2499,9 +2545,9 @@ BEGIN RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - /*Check 13 is total parallel deadlocks*/ + /*Check 14 is total parallel deadlocks*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Check 13 %s', 0, 1, @d) WITH NOWAIT; + RAISERROR('Check 14 parallel deadlocks %s', 0, 1, @d) WITH NOWAIT; INSERT #deadlock_findings WITH(TABLOCKX) @@ -2513,7 +2559,7 @@ BEGIN finding ) SELECT - check_id = 13, + check_id = 14, database_name = N'-', object_name = N'-', finding_group = N'Total parallel deadlocks', @@ -2545,7 +2591,8 @@ BEGIN ( -1, N'sp_BlitzLock ' + CAST(CONVERT(datetime, @VersionDate, 102) AS nvarchar(100)), - N'SQL Server First Responder Kit', N'http://FirstResponderKit.org/', + N'SQL Server First Responder Kit', + N'http://FirstResponderKit.org/', N'To get help or add your own contributions, join us at http://FirstResponderKit.org.' ); From 67c938bbf169d21ca75d475dccb59a2580de07be Mon Sep 17 00:00:00 2001 From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com> Date: Mon, 28 Nov 2022 15:04:45 -0500 Subject: [PATCH 350/662] Update sp_BlitzLock.sql Remove incorrect count_big(*) --- sp_BlitzLock.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index fc2a3dc84..474ceaaec 100644 --- a/sp_BlitzLock.sql +++ b/sp_BlitzLock.sql @@ -2534,7 +2534,7 @@ BEGIN finding_group = N'Agent Job Deadlocks', finding = N'There have been ' + - RTRIM(COUNT_BIG(*)) + + RTRIM(COUNT_BIG(DISTINCT aj.event_date)) + N' deadlocks from this Agent Job and Step.' FROM #agent_job AS aj GROUP BY From 4505a4f76d246a5876307b43b69319e9fbed91db Mon Sep 17 00:00:00 2001 From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com> Date: Mon, 28 Nov 2022 17:34:11 -0500 Subject: [PATCH 351/662] Update sp_BlitzLock.sql Pull status out of deadlock XML Add warnings for deadlocks involving sleeping sessions and implicit transactions --- sp_BlitzLock.sql | 85 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 84 insertions(+), 1 deletion(-) diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index 474ceaaec..34aea9312 100644 --- a/sp_BlitzLock.sql +++ b/sp_BlitzLock.sql @@ -895,6 +895,7 @@ BEGIN DB_NAME(q.database_id), N'UNKNOWN' ), + q.current_database_name, q.priority, q.log_used, q.wait_resource, @@ -904,6 +905,7 @@ BEGIN q.last_batch_started, q.last_batch_completed, q.lock_mode, + q.status, q.transaction_count, q.client_app, q.host_name, @@ -973,6 +975,7 @@ BEGIN id = ca.dp.value('@id', 'nvarchar(256)'), spid = ca.dp.value('@spid', 'smallint'), database_id = ca.dp.value('@currentdb', 'bigint'), + current_database_name = ca.dp.value('@currentdbname', 'nvarchar(256)'), priority = ca.dp.value('@priority', 'smallint'), log_used = ca.dp.value('@logused', 'bigint'), wait_resource = ca.dp.value('@waitresource', 'nvarchar(256)'), @@ -982,6 +985,7 @@ BEGIN last_batch_started = ca.dp.value('@lastbatchstarted', 'datetime'), last_batch_completed = ca.dp.value('@lastbatchcompleted', 'datetime'), lock_mode = ca.dp.value('@lockMode', 'nvarchar(256)'), + status = ca.dp.value('@status', 'nvarchar(256)'), transaction_count = ca.dp.value('@trancount', 'bigint'), client_app = ca.dp.value('@clientapp', 'nvarchar(1024)'), host_name = ca.dp.value('@hostname', 'nvarchar(256)'), @@ -2575,6 +2579,68 @@ BEGIN HAVING COUNT_BIG(DISTINCT drp.event_date) > 0 OPTION(RECOMPILE); + /*Check 15 is total deadlocks involving sleeping sessions*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Check 15 parallel deadlocks %s', 0, 1, @d) WITH NOWAIT; + + INSERT + #deadlock_findings WITH(TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding + ) + SELECT + check_id = 15, + database_name = N'-', + object_name = N'-', + finding_group = N'Total deadlocks involving sleeping sessions', + finding = + N'There have been ' + + CONVERT + ( + nvarchar(20), + COUNT_BIG(DISTINCT dp.event_date) + ) + + N' sleepy deadlocks.' + FROM #deadlock_process AS dp + WHERE dp.status = N'sleeping' + HAVING COUNT_BIG(DISTINCT dp.event_date) > 0 + OPTION(RECOMPILE); + + /*Check 16 is total deadlocks involving implicit transactions*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Check 16 implicit transaction deadlocks %s', 0, 1, @d) WITH NOWAIT; + + INSERT + #deadlock_findings WITH(TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding + ) + SELECT + check_id = 14, + database_name = N'-', + object_name = N'-', + finding_group = N'Total implicit transaction deadlocks', + finding = + N'There have been ' + + CONVERT + ( + nvarchar(20), + COUNT_BIG(DISTINCT dp.event_date) + ) + + N' implicit transaction deadlocks.' + FROM #deadlock_process AS dp + WHERE dp.transaction_name = N'implicit_transaction' + HAVING COUNT_BIG(DISTINCT dp.event_date) > 0 + OPTION(RECOMPILE); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; /*Thank you goodnight*/ @@ -2619,6 +2685,7 @@ BEGIN dp.spid, dp.database_id, dp.database_name, + dp.current_database_name, dp.priority, dp.log_used, wait_resource = @@ -2651,6 +2718,7 @@ BEGIN ), dp.wait_time, dp.transaction_name, + dp.status, dp.last_tran_started, dp.last_batch_started, dp.last_batch_completed, @@ -2703,6 +2771,7 @@ BEGIN dp.spid, dp.database_id, dp.database_name, + dp.current_database_name, dp.priority, dp.log_used, dp.wait_resource COLLATE DATABASE_DEFAULT, @@ -2734,6 +2803,7 @@ BEGIN ), dp.wait_time, dp.transaction_name, + dp.status, dp.last_tran_started, dp.last_batch_started, dp.last_batch_completed, @@ -2823,12 +2893,14 @@ BEGIN END, d.database_id, d.database_name, + d.current_database_name, d.priority, d.log_used, d.wait_resource, d.object_names, d.wait_time, d.transaction_name, + d.status, d.last_tran_started, d.last_batch_started, d.last_batch_completed, @@ -2935,6 +3007,9 @@ BEGIN d.event_date, database_name = DB_NAME(d.database_id), + database_name_x = + d.database_name, + d.current_database_name, d.spid, d.deadlock_group, d.client_option_1, @@ -2959,6 +3034,7 @@ BEGIN d.last_batch_started, d.last_batch_completed, d.transaction_name, + d.status, /*These columns will be NULL for regular (non-parallel) deadlocks*/ d.owner_waiter_type, d.owner_activity, @@ -2991,7 +3067,13 @@ BEGIN @@SERVERNAME, dr.deadlock_type, dr.event_date, - dr.database_name, + database_name = + COALESCE + ( + dr.database_name, + dr.database_name_x, + dr.current_database_name + ), dr.spid, dr.deadlock_group, ' + CASE @ExportToExcel @@ -3018,6 +3100,7 @@ BEGIN dr.last_batch_started, dr.last_batch_completed, dr.transaction_name, + dr.status, dr.owner_waiter_type, dr.owner_activity, dr.owner_waiter_activity, From cd470990cb75cdb39e5eba3097fad30b56b81ea6 Mon Sep 17 00:00:00 2001 From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com> Date: Mon, 28 Nov 2022 19:54:35 -0500 Subject: [PATCH 352/662] Update sp_BlitzLock.sql Fixes some lazy raiserror messages, and corrects the date math stuff to aggregate wait times by database and object --- sp_BlitzLock.sql | 84 +++++++++++++++++++++++++++--------------------- 1 file changed, 47 insertions(+), 37 deletions(-) diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index 34aea9312..0d05a6b6e 100644 --- a/sp_BlitzLock.sql +++ b/sp_BlitzLock.sql @@ -1648,7 +1648,7 @@ BEGIN /*Check 1 is deadlocks by database*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Check 1 databases %s', 0, 1, @d) WITH NOWAIT; + RAISERROR('Check 1 database deadlocks %s', 0, 1, @d) WITH NOWAIT; INSERT #deadlock_findings WITH(TABLOCKX) @@ -1687,7 +1687,7 @@ BEGIN /*Check 2 is deadlocks with selects*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Check 2 selects %s', 0, 1, @d) WITH NOWAIT; + RAISERROR('Check 2 select deadlocks %s', 0, 1, @d) WITH NOWAIT; INSERT #deadlock_findings WITH(TABLOCKX) @@ -1729,7 +1729,7 @@ BEGIN /*Check 3 is deadlocks by object*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Check 3 objects %s', 0, 1, @d) WITH NOWAIT; + RAISERROR('Check 3 object deadlocks %s', 0, 1, @d) WITH NOWAIT; INSERT #deadlock_findings WITH(TABLOCKX) @@ -1771,9 +1771,9 @@ BEGIN RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - /*Check 3 continuation, number of locks per index*/ + /*Check 3 continuation, number of deadlocks per index*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Check 3 indexes %s', 0, 1, @d) WITH NOWAIT; + RAISERROR('Check 3 index deadlocks %s', 0, 1, @d) WITH NOWAIT; INSERT #deadlock_findings WITH(TABLOCKX) @@ -1816,9 +1816,9 @@ BEGIN RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - /*Check 3 continuation, number of locks per heap*/ + /*Check 3 continuation, number of deadlocks per heap*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Check 3 heaps %s', 0, 1, @d) WITH NOWAIT; + RAISERROR('Check 3 heap deadlocks %s', 0, 1, @d) WITH NOWAIT; INSERT #deadlock_findings WITH(TABLOCKX) @@ -1860,9 +1860,9 @@ BEGIN RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - /*Check 4 looks for Serializable locking*/ + /*Check 4 looks for Serializable deadlocks*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Check 4 %s', 0, 1, @d) WITH NOWAIT; + RAISERROR('Check 4 serializable deadlocks %s', 0, 1, @d) WITH NOWAIT; INSERT #deadlock_findings WITH(TABLOCKX) @@ -1900,9 +1900,9 @@ BEGIN RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - /*Check 5 looks for Repeatable Read locking*/ + /*Check 5 looks for Repeatable Read deadlocks*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Check 5 %s', 0, 1, @d) WITH NOWAIT; + RAISERROR('Check 5 repeatable read deadlocks %s', 0, 1, @d) WITH NOWAIT; INSERT #deadlock_findings WITH(TABLOCKX) @@ -1957,7 +1957,7 @@ BEGIN database_name = dp.database_name, object_name = N'-', - finding_group = N'Login, App, and Host locking', + finding_group = N'Login, App, and Host deadlocks', finding = N'This database has had ' + CONVERT @@ -2001,9 +2001,9 @@ BEGIN RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - /*Check 7 breaks down the types of locks (object, page, key, etc.)*/ + /*Check 7 breaks down the types of deadlocks (object, page, key, etc.)*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Check 7 %s', 0, 1, @d) WITH NOWAIT; + RAISERROR('Check 7 types of deadlocks %s', 0, 1, @d) WITH NOWAIT; WITH lock_types AS @@ -2023,7 +2023,8 @@ BEGIN ) ELSE dp.wait_resource END, - lock_count = CONVERT(nvarchar(20), COUNT_BIG(DISTINCT dp.id)) + lock_count = + CONVERT(nvarchar(20), COUNT_BIG(DISTINCT dp.event_date)) FROM #deadlock_process AS dp JOIN #deadlock_owner_waiter AS dow ON (dp.id = dow.owner_id @@ -2094,7 +2095,7 @@ BEGIN /*Check 8 gives you more info queries for sp_BlitzCache & BlitzQueryStore*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Check 8 part 1 %s', 0, 1, @d) WITH NOWAIT; + RAISERROR('Check 8 more info part 1 BlitzCache %s', 0, 1, @d) WITH NOWAIT; WITH deadlock_stack AS @@ -2176,7 +2177,7 @@ BEGIN IF (@ProductVersionMajor >= 13 OR @Azure = 1) BEGIN SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Check 8 part 2 %s', 0, 1, @d) WITH NOWAIT; + RAISERROR('Check 8 more info part 2 BlitzQueryStore %s', 0, 1, @d) WITH NOWAIT; WITH deadlock_stack AS @@ -2232,7 +2233,7 @@ BEGIN /*Check 9 gives you stored procedure deadlock counts*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Check 9 procedure deadlocks %s', 0, 1, @d) WITH NOWAIT; + RAISERROR('Check 9 stored procedure deadlocks %s', 0, 1, @d) WITH NOWAIT; INSERT #deadlock_findings WITH(TABLOCKX) @@ -2281,7 +2282,7 @@ BEGIN /*Check 10 gives you more info queries for sp_BlitzIndex */ SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Check 10 more info, blitzindex %s', 0, 1, @d) WITH NOWAIT; + RAISERROR('Check 10 more info, BlitzIndex %s', 0, 1, @d) WITH NOWAIT; WITH bi AS @@ -2345,7 +2346,7 @@ BEGIN wait_days = CONVERT ( - nvarchar(10), + nvarchar(30), ( SUM ( @@ -2360,10 +2361,10 @@ BEGIN wait_time_hms = CONVERT ( - nvarchar(20), + nvarchar(30), DATEADD ( - SECOND, + MILLISECOND, ( SUM ( @@ -2372,11 +2373,11 @@ BEGIN bigint, dp.wait_time ) - ) / 1000 + ) ), 0 ), - 108 + 14 ) FROM #deadlock_owner_waiter AS dow JOIN #deadlock_process AS dp @@ -2411,10 +2412,19 @@ BEGIN finding_group = N'Total object deadlock wait time', finding = N'This object has had ' + - CONVERT(nvarchar(20), cs.wait_days) + - N':' + - CONVERT(nvarchar(20), cs.wait_time_hms, 108) + - N' [d/h/m/s] of deadlock wait time.' + CONVERT + ( + nvarchar(30), + cs.wait_days + ) + + N' ' + + CONVERT + ( + nvarchar(30), + cs.wait_time_hms, + 14 + ) + + N' [dd hh:mm:ss:ms] of deadlock wait time.' FROM chopsuey AS cs WHERE cs.object_name IS NOT NULL OPTION(RECOMPILE); @@ -2472,7 +2482,7 @@ BEGIN N'This database has had ' + CONVERT ( - nvarchar(10), + nvarchar(30), ( SUM ( @@ -2484,13 +2494,13 @@ BEGIN ) / 1000 / 86400 ) ) + - N':' + + N' ' + CONVERT ( - nvarchar(20), + nvarchar(30), DATEADD ( - SECOND, + MILLISECOND, ( SUM ( @@ -2499,13 +2509,13 @@ BEGIN bigint, wt.total_wait_time_ms ) - ) / 1000 + ) ), 0 ), - 108 + 14 ) + - N' [d/h/m/s] of deadlock wait time.' + N' [dd hh:mm:ss:ms] of deadlock wait time.' FROM wait_time AS wt GROUP BY wt.database_name @@ -2515,7 +2525,7 @@ BEGIN /*Check 13 gets total deadlock wait time for SQL Agent*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Check 13 deadlock wait time for SQL Agent %s', 0, 1, @d) WITH NOWAIT; + RAISERROR('Check 13 deadlock counte for SQL Agent %s', 0, 1, @d) WITH NOWAIT; INSERT #deadlock_findings WITH(TABLOCKX) @@ -2581,7 +2591,7 @@ BEGIN /*Check 15 is total deadlocks involving sleeping sessions*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Check 15 parallel deadlocks %s', 0, 1, @d) WITH NOWAIT; + RAISERROR('Check 15 sleeping deadlocks %s', 0, 1, @d) WITH NOWAIT; INSERT #deadlock_findings WITH(TABLOCKX) From 2d308578ce999d0622a4af5b20f4270e5940c892 Mon Sep 17 00:00:00 2001 From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com> Date: Mon, 28 Nov 2022 20:10:55 -0500 Subject: [PATCH 353/662] Update sp_BlitzLock.sql Matchy matchy --- sp_BlitzLock.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index 0d05a6b6e..1801d8c32 100644 --- a/sp_BlitzLock.sql +++ b/sp_BlitzLock.sql @@ -1927,7 +1927,7 @@ BEGIN ) + N' instances of Repeatable Read deadlocks.' FROM #deadlock_process AS dp - WHERE dp.isolation_level LIKE N'repeatable read%' + WHERE dp.isolation_level LIKE N'repeatable%' AND (dp.database_name = @DatabaseName OR @DatabaseName IS NULL) AND (dp.event_date >= @StartDate OR @StartDate IS NULL) AND (dp.event_date < @EndDate OR @EndDate IS NULL) From 41ccd29fe3c0dbf26c355f6bae92c94f1c46c40a Mon Sep 17 00:00:00 2001 From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com> Date: Mon, 28 Nov 2022 20:12:25 -0500 Subject: [PATCH 354/662] Update sp_BlitzLock.sql I don't understand where these things keep coming from. --- sp_BlitzLock.sql | 102 +++++++++++++++++++++++------------------------ 1 file changed, 51 insertions(+), 51 deletions(-) diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index 1801d8c32..f7eaf8569 100644 --- a/sp_BlitzLock.sql +++ b/sp_BlitzLock.sql @@ -823,18 +823,18 @@ BEGIN IF @Debug = 1 BEGIN SET STATISTICS XML ON; END; - SELECT - xml.deadlock_xml - INTO #xml - FROM - ( - SELECT + SELECT + xml.deadlock_xml + INTO #xml + FROM + ( + SELECT deadlock_xml = TRY_CAST(fx.event_data AS xml) FROM sys.fn_xe_file_target_read_file('system_health*.xel', NULL, NULL, NULL) AS fx LEFT JOIN #t AS t ON 1 = 1 - ) AS xml + ) AS xml CROSS APPLY xml.deadlock_xml.nodes('/event') AS e(x) WHERE e.x.exist('@name[ . = "xml_deadlock_report"]') = 1 AND e.x.exist('@timestamp[. >= sql:variable("@StartDate")]') = 1 @@ -848,7 +848,7 @@ BEGIN FROM #xml AS xml LEFT JOIN #t AS t ON 1 = 1 - WHERE xml.deadlock_xml IS NOT NULL + WHERE xml.deadlock_xml IS NOT NULL OPTION(RECOMPILE); IF @Debug = 1 BEGIN SET STATISTICS XML OFF; END; @@ -895,7 +895,7 @@ BEGIN DB_NAME(q.database_id), N'UNKNOWN' ), - q.current_database_name, + q.current_database_name, q.priority, q.log_used, q.wait_resource, @@ -905,7 +905,7 @@ BEGIN q.last_batch_started, q.last_batch_completed, q.lock_mode, - q.status, + q.status, q.transaction_count, q.client_app, q.host_name, @@ -975,7 +975,7 @@ BEGIN id = ca.dp.value('@id', 'nvarchar(256)'), spid = ca.dp.value('@spid', 'smallint'), database_id = ca.dp.value('@currentdb', 'bigint'), - current_database_name = ca.dp.value('@currentdbname', 'nvarchar(256)'), + current_database_name = ca.dp.value('@currentdbname', 'nvarchar(256)'), priority = ca.dp.value('@priority', 'smallint'), log_used = ca.dp.value('@logused', 'bigint'), wait_resource = ca.dp.value('@waitresource', 'nvarchar(256)'), @@ -985,7 +985,7 @@ BEGIN last_batch_started = ca.dp.value('@lastbatchstarted', 'datetime'), last_batch_completed = ca.dp.value('@lastbatchcompleted', 'datetime'), lock_mode = ca.dp.value('@lockMode', 'nvarchar(256)'), - status = ca.dp.value('@status', 'nvarchar(256)'), + status = ca.dp.value('@status', 'nvarchar(256)'), transaction_count = ca.dp.value('@trancount', 'bigint'), client_app = ca.dp.value('@clientapp', 'nvarchar(1024)'), host_name = ca.dp.value('@hostname', 'nvarchar(256)'), @@ -1714,11 +1714,11 @@ BEGIN N' deadlock(s) between read queries and modification queries.' FROM #deadlock_owner_waiter AS dow WHERE 1 = 1 - AND dow.lock_mode IN - ( - N'S', - N'IS' - ) + AND dow.lock_mode IN + ( + N'S', + N'IS' + ) AND (dow.database_id = @DatabaseName OR @DatabaseName IS NULL) AND (dow.event_date >= @StartDate OR @StartDate IS NULL) AND (dow.event_date < @EndDate OR @EndDate IS NULL) @@ -1921,10 +1921,10 @@ BEGIN finding = N'This database has had ' + CONVERT - ( - nvarchar(20), - COUNT_BIG(DISTINCT dp.event_date) - ) + + ( + nvarchar(20), + COUNT_BIG(DISTINCT dp.event_date) + ) + N' instances of Repeatable Read deadlocks.' FROM #deadlock_process AS dp WHERE dp.isolation_level LIKE N'repeatable%' @@ -2024,7 +2024,7 @@ BEGIN ELSE dp.wait_resource END, lock_count = - CONVERT(nvarchar(20), COUNT_BIG(DISTINCT dp.event_date)) + CONVERT(nvarchar(20), COUNT_BIG(DISTINCT dp.event_date)) FROM #deadlock_process AS dp JOIN #deadlock_owner_waiter AS dow ON (dp.id = dow.owner_id @@ -2413,17 +2413,17 @@ BEGIN finding = N'This object has had ' + CONVERT - ( - nvarchar(30), - cs.wait_days - ) + + ( + nvarchar(30), + cs.wait_days + ) + N' ' + CONVERT - ( - nvarchar(30), - cs.wait_time_hms, - 14 - ) + + ( + nvarchar(30), + cs.wait_time_hms, + 14 + ) + N' [dd hh:mm:ss:ms] of deadlock wait time.' FROM chopsuey AS cs WHERE cs.object_name IS NOT NULL @@ -2616,7 +2616,7 @@ BEGIN ) + N' sleepy deadlocks.' FROM #deadlock_process AS dp - WHERE dp.status = N'sleeping' + WHERE dp.status = N'sleeping' HAVING COUNT_BIG(DISTINCT dp.event_date) > 0 OPTION(RECOMPILE); @@ -2647,7 +2647,7 @@ BEGIN ) + N' implicit transaction deadlocks.' FROM #deadlock_process AS dp - WHERE dp.transaction_name = N'implicit_transaction' + WHERE dp.transaction_name = N'implicit_transaction' HAVING COUNT_BIG(DISTINCT dp.event_date) > 0 OPTION(RECOMPILE); @@ -2668,7 +2668,7 @@ BEGIN -1, N'sp_BlitzLock ' + CAST(CONVERT(datetime, @VersionDate, 102) AS nvarchar(100)), N'SQL Server First Responder Kit', - N'http://FirstResponderKit.org/', + N'http://FirstResponderKit.org/', N'To get help or add your own contributions, join us at http://FirstResponderKit.org.' ); @@ -2695,7 +2695,7 @@ BEGIN dp.spid, dp.database_id, dp.database_name, - dp.current_database_name, + dp.current_database_name, dp.priority, dp.log_used, wait_resource = @@ -2728,7 +2728,7 @@ BEGIN ), dp.wait_time, dp.transaction_name, - dp.status, + dp.status, dp.last_tran_started, dp.last_batch_started, dp.last_batch_completed, @@ -2781,7 +2781,7 @@ BEGIN dp.spid, dp.database_id, dp.database_name, - dp.current_database_name, + dp.current_database_name, dp.priority, dp.log_used, dp.wait_resource COLLATE DATABASE_DEFAULT, @@ -2813,7 +2813,7 @@ BEGIN ), dp.wait_time, dp.transaction_name, - dp.status, + dp.status, dp.last_tran_started, dp.last_batch_started, dp.last_batch_completed, @@ -2903,14 +2903,14 @@ BEGIN END, d.database_id, d.database_name, - d.current_database_name, + d.current_database_name, d.priority, d.log_used, d.wait_resource, d.object_names, d.wait_time, d.transaction_name, - d.status, + d.status, d.last_tran_started, d.last_batch_started, d.last_batch_completed, @@ -3017,9 +3017,9 @@ BEGIN d.event_date, database_name = DB_NAME(d.database_id), - database_name_x = - d.database_name, - d.current_database_name, + database_name_x = + d.database_name, + d.current_database_name, d.spid, d.deadlock_group, d.client_option_1, @@ -3044,7 +3044,7 @@ BEGIN d.last_batch_started, d.last_batch_completed, d.transaction_name, - d.status, + d.status, /*These columns will be NULL for regular (non-parallel) deadlocks*/ d.owner_waiter_type, d.owner_activity, @@ -3078,12 +3078,12 @@ BEGIN dr.deadlock_type, dr.event_date, database_name = - COALESCE - ( - dr.database_name, - dr.database_name_x, - dr.current_database_name - ), + COALESCE + ( + dr.database_name, + dr.database_name_x, + dr.current_database_name + ), dr.spid, dr.deadlock_group, ' + CASE @ExportToExcel @@ -3110,7 +3110,7 @@ BEGIN dr.last_batch_started, dr.last_batch_completed, dr.transaction_name, - dr.status, + dr.status, dr.owner_waiter_type, dr.owner_activity, dr.owner_waiter_activity, From c5c7c3b2140c5f5c859aefb98fa8a60481ea0293 Mon Sep 17 00:00:00 2001 From: Cody Konior Date: Thu, 1 Dec 2022 21:19:43 +0800 Subject: [PATCH 355/662] Prevent lock failure in sp_msforeachdb --- sp_BlitzFirst.sql | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/sp_BlitzFirst.sql b/sp_BlitzFirst.sql index 6c34c761c..97e084786 100644 --- a/sp_BlitzFirst.sql +++ b/sp_BlitzFirst.sql @@ -2516,7 +2516,16 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, ; IF SERVERPROPERTY('EngineEdition') <> 5 /*SERVERPROPERTY('Edition') <> 'SQL Azure'*/ - EXEC sp_MSforeachdb @StringToExecute; + BEGIN + BEGIN TRY + EXEC sp_MSforeachdb @StringToExecute; + END TRY + BEGIN CATCH + INSERT INTO #UpdatedStats(HowToStopIt, RowsForSorting) + SELECT HowToStopIt = N' No information could be retrieved as the lock timeout was exceeded while iterating databases,' + + N' this is likely due to an Index operation in Progress', -1; + END CATCH + END ELSE EXEC(@StringToExecute); From 3c35c956d905e20dc95860e755d4b1daf4a68e9e Mon Sep 17 00:00:00 2001 From: Cody Konior Date: Thu, 1 Dec 2022 21:29:37 +0800 Subject: [PATCH 356/662] Spacing --- sp_BlitzFirst.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sp_BlitzFirst.sql b/sp_BlitzFirst.sql index 97e084786..6fb6ac082 100644 --- a/sp_BlitzFirst.sql +++ b/sp_BlitzFirst.sql @@ -2525,7 +2525,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, SELECT HowToStopIt = N' No information could be retrieved as the lock timeout was exceeded while iterating databases,' + N' this is likely due to an Index operation in Progress', -1; END CATCH - END + END ELSE EXEC(@StringToExecute); From a2170c2a2ae580a634bfc37fa948131e0a69f7b2 Mon Sep 17 00:00:00 2001 From: Cody Konior Date: Thu, 1 Dec 2022 21:32:54 +0800 Subject: [PATCH 357/662] Spacing --- sp_BlitzFirst.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sp_BlitzFirst.sql b/sp_BlitzFirst.sql index 6fb6ac082..11881337f 100644 --- a/sp_BlitzFirst.sql +++ b/sp_BlitzFirst.sql @@ -2522,8 +2522,8 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, END TRY BEGIN CATCH INSERT INTO #UpdatedStats(HowToStopIt, RowsForSorting) - SELECT HowToStopIt = N' No information could be retrieved as the lock timeout was exceeded while iterating databases,' + - N' this is likely due to an Index operation in Progress', -1; + SELECT HowToStopIt = N'No information could be retrieved as the lock timeout was exceeded while iterating databases,' + + N' this is likely due to an Index operation in Progress', -1; END CATCH END ELSE From c101000d0d749a9a0def3ccd31e1673d025213be Mon Sep 17 00:00:00 2001 From: Cody Konior Date: Thu, 1 Dec 2022 22:22:38 +0800 Subject: [PATCH 358/662] Only swallow lock timeout, re-throw anything else --- sp_BlitzFirst.sql | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/sp_BlitzFirst.sql b/sp_BlitzFirst.sql index 11881337f..dc5b27643 100644 --- a/sp_BlitzFirst.sql +++ b/sp_BlitzFirst.sql @@ -2521,9 +2521,16 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, EXEC sp_MSforeachdb @StringToExecute; END TRY BEGIN CATCH - INSERT INTO #UpdatedStats(HowToStopIt, RowsForSorting) - SELECT HowToStopIt = N'No information could be retrieved as the lock timeout was exceeded while iterating databases,' + - N' this is likely due to an Index operation in Progress', -1; + IF (ERROR_NUMBER() = 1222) + BEGIN + INSERT INTO #UpdatedStats(HowToStopIt, RowsForSorting) + SELECT HowToStopIt = N'No information could be retrieved as the lock timeout was exceeded while iterating databases,' + + N' this is likely due to an Index operation in Progress', -1; + END + ELSE + BEGIN + THROW; + END END CATCH END ELSE From 8ac7b6914cd42c238e3b899edf5d491b51949e3d Mon Sep 17 00:00:00 2001 From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com> Date: Mon, 5 Dec 2022 12:37:46 -0500 Subject: [PATCH 359/662] Update sp_BlitzLock.sql Rearrange the rollup info bar a little bit to include the version number instead of the version date, because it was unclear what was being shown there. Also adding in the start and end dates for the period of analysis, in case anyone thinks the results are incomplete, or since server startup. --- sp_BlitzLock.sql | 36 +++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index f7eaf8569..c64ecc1e4 100644 --- a/sp_BlitzLock.sql +++ b/sp_BlitzLock.sql @@ -557,7 +557,8 @@ BEGIN ) BEGIN RAISERROR('@TargetSessionType is NULL, assigning for non-Azure instance', 0, 1) WITH NOWAIT; - SELECT TOP (1) + + SELECT TOP (1) @TargetSessionType = t.target_name FROM sys.dm_xe_sessions AS s JOIN sys.dm_xe_session_targets AS t @@ -577,7 +578,8 @@ BEGIN ) BEGIN RAISERROR('@TargetSessionType is NULL, assigning for Azure instance', 0, 1) WITH NOWAIT; - SELECT TOP (1) + + SELECT TOP (1) @TargetSessionType = t.target_name FROM sys.dm_xe_database_sessions AS s JOIN sys.dm_xe_database_session_targets AS t @@ -683,11 +685,7 @@ BEGIN ( SELECT file_name = - CONVERT - ( - nvarchar(4000), - f.value - ) + CONVERT(nvarchar(4000), f.value) FROM sys.server_event_session_fields AS f WHERE f.event_session_id = @SessionId AND f.object_id = @TargetSessionId @@ -721,7 +719,8 @@ BEGIN FROM ( SELECT - file_name = CONVERT(nvarchar(4000), f.value) + file_name = + CONVERT(nvarchar(4000), f.value) FROM sys.server_event_session_fields AS f WHERE f.event_session_id = @SessionId AND f.object_id = @TargetSessionId @@ -767,7 +766,8 @@ BEGIN deadlock_xml ) SELECT - deadlock_xml = e.x.query(N'.') + deadlock_xml = + e.x.query(N'.') FROM #x AS x LEFT JOIN #t AS t ON 1 = 1 @@ -797,7 +797,8 @@ BEGIN deadlock_xml ) SELECT - deadlock_xml = e.x.query('.') + deadlock_xml = + e.x.query('.') FROM #x AS x LEFT JOIN #t AS t ON 1 = 1 @@ -844,7 +845,8 @@ BEGIN INSERT #deadlock_data WITH(TABLOCKX) SELECT - deadlock_xml = xml.deadlock_xml + deadlock_xml = + xml.deadlock_xml FROM #xml AS xml LEFT JOIN #t AS t ON 1 = 1 @@ -1085,8 +1087,8 @@ BEGIN NCHAR(26), N'?'), NCHAR(25), N'?'), NCHAR(24), N'?'), NCHAR(23), N'?'), NCHAR(22), N'?'), NCHAR(21), N'?'), NCHAR(20), N'?'), NCHAR(19), N'?'), NCHAR(18), N'?'), NCHAR(17), N'?'), NCHAR(16), N'?'), NCHAR(15), N'?'), NCHAR(14), N'?'), NCHAR(12), N'?'), NCHAR(11), N'?'), - NCHAR(8), N'?'), NCHAR(7), N'?'), NCHAR(6), N'?'), NCHAR(5), N'?'), NCHAR(4), N'?'), - NCHAR(3), N'?'), NCHAR(2), N'?'), NCHAR(1), N'?'), NCHAR(0), N'?'), + NCHAR(8), N'?'), NCHAR(7), N'?'), NCHAR(6), N'?'), NCHAR(5), N'?'), NCHAR(4), N'?'), + NCHAR(3), N'?'), NCHAR(2), N'?'), NCHAR(1), N'?'), NCHAR(0), N'?'), ca.lock_mode, ca.index_name, ca.associatedObjectId, @@ -2158,7 +2160,7 @@ BEGIN finding = N'EXEC sp_BlitzCache ' + CASE WHEN ds.proc_name = N'adhoc' - THEN N' @OnlySqlHandles = ' + ds.sql_handle_csv + THEN N'@OnlySqlHandles = ' + ds.sql_handle_csv ELSE N'@StoredProcName = ' + QUOTENAME(ds.proc_only_name, N'''') END + N';' FROM deadlock_stack AS ds @@ -2666,10 +2668,10 @@ BEGIN VALUES ( -1, - N'sp_BlitzLock ' + CAST(CONVERT(datetime, @VersionDate, 102) AS nvarchar(100)), - N'SQL Server First Responder Kit', + N'sp_BlitzLock version ' + CONVERT(nvarchar(10), @Version), + N'Results for ' + CONVERT(nvarchar(10), @StartDate, 23) + N' through ' + CONVERT(nvarchar(10), @EndDate, 23), N'http://FirstResponderKit.org/', - N'To get help or add your own contributions, join us at http://FirstResponderKit.org.' + N'To get help or add your own contributions to the SQL Server First Responder Kit, join us at http://FirstResponderKit.org.' ); /*Results*/ From 7abf15fb001fc33571e33748521a02d3ebee7ba5 Mon Sep 17 00:00:00 2001 From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com> Date: Tue, 6 Dec 2022 12:02:40 -0500 Subject: [PATCH 360/662] Update sp_BlitzLock.sql Fizx Azure query --- sp_BlitzLock.sql | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index c64ecc1e4..304a2c2db 100644 --- a/sp_BlitzLock.sql +++ b/sp_BlitzLock.sql @@ -698,12 +698,14 @@ BEGIN BEGIN RAISERROR('@TargetSessionType is event_file, assigning XML for Azure', 0, 1) WITH NOWAIT; SELECT - @SessionId = t.event_session_id, - @TargetSessionId = t.target_id - FROM sys.dm_xe_database_session_targets AS t - JOIN sys.dm_xe_database_sessions AS s - ON s.event_session_id = t.event_session_id - WHERE t.name = @TargetSessionType + @SessionId = + t.event_session_address, + @TargetSessionId = + t.target_name + FROM sys.dm_xe_database_session_targets t + JOIN sys.dm_xe_database_sessions s + ON s.address = t.event_session_address + WHERE t.target_name = @TargetSessionType AND s.name = @EventSessionName OPTION(RECOMPILE); From ba82b5a20749efec57ac22536047b785acee9c4d Mon Sep 17 00:00:00 2001 From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com> Date: Tue, 6 Dec 2022 17:08:32 -0500 Subject: [PATCH 361/662] Update sp_BlitzLock.sql * Fix some bugs with the table creation/alter portion of the code * Fix some errors and omissions in various raiserror calls * Change script to wrap up all the parallel deadlock columns to an XML clickable when not inserting to a table or exporting to Excel * Remove unnecessary bit flip for output to table * Some code formatting * Fix final select list --- sp_BlitzLock.sql | 175 +++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 145 insertions(+), 30 deletions(-) diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index 304a2c2db..8f6c72064 100644 --- a/sp_BlitzLock.sql +++ b/sp_BlitzLock.sql @@ -168,7 +168,7 @@ BEGIN @d varchar(40) = '', @StringToExecute nvarchar(4000) = N'', @StringToExecuteParams nvarchar(500) = N'', - @r sysname = N'', + @r sysname = NULL, @OutputTableFindings nvarchar(100) = N'[BlitzLockFindings]', @DeadlockCount int = 0, @ServerName sysname = @@SERVERNAME, @@ -315,18 +315,18 @@ BEGIN EXEC sys.sp_executesql @StringToExecute, @StringToExecuteParams, - @OutputDatabaseName, - @OutputTableName, @r OUTPUT; + RAISERROR('@r is: %s', 0, 1, @r) WITH NOWAIT; + /*put covers around all before.*/ SELECT @ObjectFullName = QUOTENAME(@OutputDatabaseName) + N'.' + - QUOTENAME(@OutputTableName) + + QUOTENAME(@OutputSchemaName) + N'.' + - QUOTENAME(@OutputSchemaName), + QUOTENAME(@OutputTableName), @OutputDatabaseName = QUOTENAME(@OutputDatabaseName), @OutputTableName = @@ -367,6 +367,70 @@ BEGIN IF @Debug = 1 BEGIN PRINT @StringToExecute; END; EXEC sys.sp_executesql @StringToExecute; + + /* If the table doesn't have the new client option column, add it. See Github #3101. */ + SET @StringToExecute = + N'IF NOT EXISTS (SELECT 1/0 FROM ' + + @OutputDatabaseName + + N'.sys.all_columns AS o WHERE o.object_id = (OBJECT_ID(''' + + @ObjectFullName + + N''')) AND o.name = N''client_option_1'') + /*Add wait_resource column*/ + ALTER TABLE ' + + @ObjectFullName + + N' ADD client_option_1 nvarchar(8000) NULL;'; + + IF @Debug = 1 BEGIN PRINT @StringToExecute; END; + EXEC sys.sp_executesql + @StringToExecute; + + /* If the table doesn't have the new client option column, add it. See Github #3101. */ + SET @StringToExecute = + N'IF NOT EXISTS (SELECT 1/0 FROM ' + + @OutputDatabaseName + + N'.sys.all_columns AS o WHERE o.object_id = (OBJECT_ID(''' + + @ObjectFullName + + N''')) AND o.name = N''client_option_2'') + /*Add wait_resource column*/ + ALTER TABLE ' + + @ObjectFullName + + N' ADD client_option_2 nvarchar(8000) NULL;'; + + IF @Debug = 1 BEGIN PRINT @StringToExecute; END; + EXEC sys.sp_executesql + @StringToExecute; + + /* If the table doesn't have the new lock mode column, add it. See Github #3101. */ + SET @StringToExecute = + N'IF NOT EXISTS (SELECT 1/0 FROM ' + + @OutputDatabaseName + + N'.sys.all_columns AS o WHERE o.object_id = (OBJECT_ID(''' + + @ObjectFullName + + N''')) AND o.name = N''lock_mode'') + /*Add wait_resource column*/ + ALTER TABLE ' + + @ObjectFullName + + N' ADD lock_mode nvarchar(256) NULL;'; + + IF @Debug = 1 BEGIN PRINT @StringToExecute; END; + EXEC sys.sp_executesql + @StringToExecute; + + /* If the table doesn't have the new status column, add it. See Github #3101. */ + SET @StringToExecute = + N'IF NOT EXISTS (SELECT 1/0 FROM ' + + @OutputDatabaseName + + N'.sys.all_columns AS o WHERE o.object_id = (OBJECT_ID(''' + + @ObjectFullName + + N''')) AND o.name = N''status'') + /*Add wait_resource column*/ + ALTER TABLE ' + + @ObjectFullName + + N' ADD status nvarchar(256) NULL;'; + + IF @Debug = 1 BEGIN PRINT @StringToExecute; END; + EXEC sys.sp_executesql + @StringToExecute; END; ELSE /* end if @r is not null. if it is null there is no table, create it from above execution */ BEGIN @@ -391,7 +455,10 @@ BEGIN isolation_level nvarchar(256), owner_mode nvarchar(256), waiter_mode nvarchar(256), + lock_mode nvarchar(256), transaction_count bigint, + client_option_1 varchar(2000), + client_option_2 varchar(2000), login_name nvarchar(256), host_name nvarchar(256), client_app nvarchar(1024), @@ -403,6 +470,7 @@ BEGIN last_batch_started datetime, last_batch_completed datetime, transaction_name nvarchar(256), + status nvarchar(256), owner_waiter_type nvarchar(256), owner_activity nvarchar(256), owner_waiter_activity nvarchar(256), @@ -427,7 +495,9 @@ 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''BlitzLockFindings''', + N'.sys.objects AS o + WHERE o.type_desc = N''USER_TABLE'' + AND o.name = N''BlitzLockFindings''', @StringToExecuteParams = N'@r sysname OUTPUT'; @@ -1731,6 +1801,8 @@ BEGIN dow.database_name OPTION(RECOMPILE); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + /*Check 3 is deadlocks by object*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Check 3 object deadlocks %s', 0, 1, @d) WITH NOWAIT; @@ -1777,7 +1849,7 @@ BEGIN /*Check 3 continuation, number of deadlocks per index*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Check 3 index deadlocks %s', 0, 1, @d) WITH NOWAIT; + RAISERROR('Check 3 (continued) index deadlocks %s', 0, 1, @d) WITH NOWAIT; INSERT #deadlock_findings WITH(TABLOCKX) @@ -1822,7 +1894,7 @@ BEGIN /*Check 3 continuation, number of deadlocks per heap*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Check 3 heap deadlocks %s', 0, 1, @d) WITH NOWAIT; + RAISERROR('Check 3 (continued) heap deadlocks %s', 0, 1, @d) WITH NOWAIT; INSERT #deadlock_findings WITH(TABLOCKX) @@ -1945,7 +2017,7 @@ BEGIN /*Check 6 breaks down app, host, and login information*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Check 6 %s', 0, 1, @d) WITH NOWAIT; + RAISERROR('Check 6 app/host/login deadlocks %s', 0, 1, @d) WITH NOWAIT; INSERT #deadlock_findings WITH(TABLOCKX) @@ -2529,7 +2601,7 @@ BEGIN /*Check 13 gets total deadlock wait time for SQL Agent*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Check 13 deadlock counte for SQL Agent %s', 0, 1, @d) WITH NOWAIT; + RAISERROR('Check 13 deadlock count for SQL Agent %s', 0, 1, @d) WITH NOWAIT; INSERT #deadlock_findings WITH(TABLOCKX) @@ -2593,6 +2665,8 @@ BEGIN HAVING COUNT_BIG(DISTINCT drp.event_date) > 0 OPTION(RECOMPILE); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + /*Check 15 is total deadlocks involving sleeping sessions*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Check 15 sleeping deadlocks %s', 0, 1, @d) WITH NOWAIT; @@ -2624,6 +2698,8 @@ BEGIN HAVING COUNT_BIG(DISTINCT dp.event_date) > 0 OPTION(RECOMPILE); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + /*Check 16 is total deadlocks involving implicit transactions*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Check 16 implicit transaction deadlocks %s', 0, 1, @d) WITH NOWAIT; @@ -2676,15 +2752,17 @@ BEGIN N'To get help or add your own contributions to the SQL Server First Responder Kit, join us at http://FirstResponderKit.org.' ); - /*Results*/ - CREATE CLUSTERED INDEX cx_whatever_dp ON #deadlock_process (event_date, id); - CREATE CLUSTERED INDEX cx_whatever_drp ON #deadlock_resource_parallel (event_date, owner_id); - CREATE CLUSTERED INDEX cx_whatever_dow ON #deadlock_owner_waiter (event_date, owner_id, waiter_id); + RAISERROR('Finished rollup at %s', 0, 1, @d) WITH NOWAIT; + /*Results*/ BEGIN SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Results 1 %s', 0, 1, @d) WITH NOWAIT; + CREATE CLUSTERED INDEX cx_whatever_dp ON #deadlock_process (event_date, id); + CREATE CLUSTERED INDEX cx_whatever_drp ON #deadlock_resource_parallel (event_date, owner_id); + CREATE CLUSTERED INDEX cx_whatever_dow ON #deadlock_owner_waiter (event_date, owner_id, waiter_id); + IF @Debug = 1 BEGIN SET STATISTICS XML ON; END; WITH @@ -3028,7 +3106,8 @@ BEGIN d.deadlock_group, d.client_option_1, d.client_option_2, - query_xml = + d.lock_mode, + query_xml = TRY_CAST(N'' + d.inputbuf + N'' AS xml), query_string = d.inputbuf, @@ -3050,6 +3129,25 @@ BEGIN d.transaction_name, d.status, /*These columns will be NULL for regular (non-parallel) deadlocks*/ + parallel_deadlock_details = + ( + SELECT + d.owner_waiter_type, + d.owner_activity, + d.owner_waiter_activity, + d.owner_merging, + d.owner_spilling, + d.owner_waiting_to_close, + d.waiter_waiter_type, + d.waiter_owner_activity, + d.waiter_waiter_activity, + d.waiter_merging, + d.waiter_spilling, + d.waiter_waiting_to_close + FOR XML + PATH('parallel_deadlock_details'), + TYPE + ), d.owner_waiter_type, d.owner_activity, d.owner_waiter_activity, @@ -3070,11 +3168,6 @@ BEGIN IF @Debug = 1 BEGIN SET STATISTICS XML OFF; END; RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - IF (@OutputDatabaseCheck = 0) - BEGIN - SET @ExportToExcel = 1; - END; - SET @deadlock_result += N' SELECT server_name = @@ -3093,13 +3186,23 @@ BEGIN ' + CASE @ExportToExcel WHEN 1 THEN N'query = dr.query_string, - object_names = REPLACE(REPLACE(CONVERT(nvarchar(MAX), dr.object_names), '''', ''''), '''', ''''),' + object_names = + REPLACE( + REPLACE( + CONVERT + ( + nvarchar(MAX), + dr.object_names + ), + '''', ''''), + '''', ''''),' ELSE N'query = dr.query_xml, - dr.object_names,' + dr.object_names,' END + N' dr.isolation_level, dr.owner_mode, dr.waiter_mode, + dr.lock_mode, dr.transaction_count, dr.client_option_1, dr.client_option_2, @@ -3114,7 +3217,11 @@ BEGIN dr.last_batch_started, dr.last_batch_completed, dr.transaction_name, - dr.status, + dr.status,' + + CASE + WHEN (@ExportToExcel = 1 + OR @OutputDatabaseCheck = 0) + THEN N' dr.owner_waiter_type, dr.owner_activity, dr.owner_waiter_activity, @@ -3126,23 +3233,27 @@ BEGIN dr.waiter_waiter_activity, dr.waiter_merging, dr.waiter_spilling, - dr.waiter_waiting_to_close,' + + dr.waiter_waiting_to_close,' + ELSE N' + dr.parallel_deadlock_details,' + END + CASE @ExportToExcel WHEN 1 THEN N' - REPLACE(REPLACE(REPLACE(REPLACE( + deadlock_graph = + REPLACE(REPLACE( + REPLACE(REPLACE( CONVERT ( nvarchar(MAX), dr.deadlock_graph COLLATE Latin1_General_BIN2 ), - ''NCHAR(10)'', ''''), ''NCHAR(13)'', ''''), - ''CHAR(10)'', ''''), ''CHAR(13)'', '''')' + ''NCHAR(10)'', ''''), ''NCHAR(13)'', ''''), + ''CHAR(10)'', ''''), ''CHAR(13)'', '''')' ELSE N' dr.deadlock_graph' - END - + N' + END + N' FROM #deadlock_results AS dr ORDER BY dr.event_date, @@ -3175,7 +3286,10 @@ BEGIN isolation_level, owner_mode, waiter_mode, + lock_mode, transaction_count, + client_option_1, + client_option_2, login_name, host_name, client_app, @@ -3187,6 +3301,7 @@ BEGIN last_batch_started, last_batch_completed, transaction_name, + status, owner_waiter_type, owner_activity, owner_waiter_activity, @@ -3227,7 +3342,7 @@ BEGIN finding ) SELECT - @ServerName, + @@SERVERNAME, df.check_id, df.database_name, df.object_name, From 2c17ca8c8114de3f28db609350ab6ae05a18fe64 Mon Sep 17 00:00:00 2001 From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com> Date: Tue, 6 Dec 2022 18:09:16 -0500 Subject: [PATCH 362/662] Update sp_BlitzLock.sql Fix tabs and carriages --- sp_BlitzLock.sql | 102 +++++++++++++++++++++++------------------------ 1 file changed, 51 insertions(+), 51 deletions(-) diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index 8f6c72064..e7ecf279d 100644 --- a/sp_BlitzLock.sql +++ b/sp_BlitzLock.sql @@ -317,7 +317,7 @@ BEGIN @StringToExecuteParams, @r OUTPUT; - RAISERROR('@r is: %s', 0, 1, @r) WITH NOWAIT; + RAISERROR('@r is: %s', 0, 1, @r) WITH NOWAIT; /*put covers around all before.*/ SELECT @@ -455,10 +455,10 @@ BEGIN isolation_level nvarchar(256), owner_mode nvarchar(256), waiter_mode nvarchar(256), - lock_mode nvarchar(256), + lock_mode nvarchar(256), transaction_count bigint, - client_option_1 varchar(2000), - client_option_2 varchar(2000), + client_option_1 varchar(2000), + client_option_2 varchar(2000), login_name nvarchar(256), host_name nvarchar(256), client_app nvarchar(1024), @@ -470,7 +470,7 @@ BEGIN last_batch_started datetime, last_batch_completed datetime, transaction_name nvarchar(256), - status nvarchar(256), + status nvarchar(256), owner_waiter_type nvarchar(256), owner_activity nvarchar(256), owner_waiter_activity nvarchar(256), @@ -496,8 +496,8 @@ BEGIN N'SELECT @r = o.name FROM ' + @OutputDatabaseName + N'.sys.objects AS o - WHERE o.type_desc = N''USER_TABLE'' - AND o.name = N''BlitzLockFindings''', + WHERE o.type_desc = N''USER_TABLE'' + AND o.name = N''BlitzLockFindings''', @StringToExecuteParams = N'@r sysname OUTPUT'; @@ -628,7 +628,7 @@ BEGIN BEGIN RAISERROR('@TargetSessionType is NULL, assigning for non-Azure instance', 0, 1) WITH NOWAIT; - SELECT TOP (1) + SELECT TOP (1) @TargetSessionType = t.target_name FROM sys.dm_xe_sessions AS s JOIN sys.dm_xe_session_targets AS t @@ -649,7 +649,7 @@ BEGIN BEGIN RAISERROR('@TargetSessionType is NULL, assigning for Azure instance', 0, 1) WITH NOWAIT; - SELECT TOP (1) + SELECT TOP (1) @TargetSessionType = t.target_name FROM sys.dm_xe_database_sessions AS s JOIN sys.dm_xe_database_session_targets AS t @@ -768,14 +768,14 @@ BEGIN BEGIN RAISERROR('@TargetSessionType is event_file, assigning XML for Azure', 0, 1) WITH NOWAIT; SELECT - @SessionId = + @SessionId = t.event_session_address, - @TargetSessionId = + @TargetSessionId = t.target_name FROM sys.dm_xe_database_session_targets t - JOIN sys.dm_xe_database_sessions s + JOIN sys.dm_xe_database_sessions s ON s.address = t.event_session_address - WHERE t.target_name = @TargetSessionType + WHERE t.target_name = @TargetSessionType AND s.name = @EventSessionName OPTION(RECOMPILE); @@ -792,7 +792,7 @@ BEGIN ( SELECT file_name = - CONVERT(nvarchar(4000), f.value) + CONVERT(nvarchar(4000), f.value) FROM sys.server_event_session_fields AS f WHERE f.event_session_id = @SessionId AND f.object_id = @TargetSessionId @@ -839,7 +839,7 @@ BEGIN ) SELECT deadlock_xml = - e.x.query(N'.') + e.x.query(N'.') FROM #x AS x LEFT JOIN #t AS t ON 1 = 1 @@ -870,7 +870,7 @@ BEGIN ) SELECT deadlock_xml = - e.x.query('.') + e.x.query('.') FROM #x AS x LEFT JOIN #t AS t ON 1 = 1 @@ -918,7 +918,7 @@ BEGIN #deadlock_data WITH(TABLOCKX) SELECT deadlock_xml = - xml.deadlock_xml + xml.deadlock_xml FROM #xml AS xml LEFT JOIN #t AS t ON 1 = 1 @@ -2665,7 +2665,7 @@ BEGIN HAVING COUNT_BIG(DISTINCT drp.event_date) > 0 OPTION(RECOMPILE); - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; /*Check 15 is total deadlocks involving sleeping sessions*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); @@ -2698,7 +2698,7 @@ BEGIN HAVING COUNT_BIG(DISTINCT dp.event_date) > 0 OPTION(RECOMPILE); - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; /*Check 16 is total deadlocks involving implicit transactions*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); @@ -2752,7 +2752,7 @@ BEGIN N'To get help or add your own contributions to the SQL Server First Responder Kit, join us at http://FirstResponderKit.org.' ); - RAISERROR('Finished rollup at %s', 0, 1, @d) WITH NOWAIT; + RAISERROR('Finished rollup at %s', 0, 1, @d) WITH NOWAIT; /*Results*/ BEGIN @@ -3106,8 +3106,8 @@ BEGIN d.deadlock_group, d.client_option_1, d.client_option_2, - d.lock_mode, - query_xml = + d.lock_mode, + query_xml = TRY_CAST(N'' + d.inputbuf + N'' AS xml), query_string = d.inputbuf, @@ -3130,8 +3130,8 @@ BEGIN d.status, /*These columns will be NULL for regular (non-parallel) deadlocks*/ parallel_deadlock_details = - ( - SELECT + ( + SELECT d.owner_waiter_type, d.owner_activity, d.owner_waiter_activity, @@ -3144,10 +3144,10 @@ BEGIN d.waiter_merging, d.waiter_spilling, d.waiter_waiting_to_close - FOR XML - PATH('parallel_deadlock_details'), - TYPE - ), + FOR XML + PATH('parallel_deadlock_details'), + TYPE + ), d.owner_waiter_type, d.owner_activity, d.owner_waiter_activity, @@ -3187,22 +3187,22 @@ BEGIN WHEN 1 THEN N'query = dr.query_string, object_names = - REPLACE( - REPLACE( - CONVERT - ( - nvarchar(MAX), - dr.object_names - ), - '''', ''''), - '''', ''''),' + REPLACE( + REPLACE( + CONVERT + ( + nvarchar(MAX), + dr.object_names + ), + '''', ''''), + '''', ''''),' ELSE N'query = dr.query_xml, dr.object_names,' END + N' dr.isolation_level, dr.owner_mode, dr.waiter_mode, - dr.lock_mode, + dr.lock_mode, dr.transaction_count, dr.client_option_1, dr.client_option_2, @@ -3218,10 +3218,10 @@ BEGIN dr.last_batch_completed, dr.transaction_name, dr.status,' + - CASE - WHEN (@ExportToExcel = 1 - OR @OutputDatabaseCheck = 0) - THEN N' + CASE + WHEN (@ExportToExcel = 1 + OR @OutputDatabaseCheck = 0) + THEN N' dr.owner_waiter_type, dr.owner_activity, dr.owner_waiter_activity, @@ -3234,16 +3234,16 @@ BEGIN dr.waiter_merging, dr.waiter_spilling, dr.waiter_waiting_to_close,' - ELSE N' - dr.parallel_deadlock_details,' - END + + ELSE N' + dr.parallel_deadlock_details,' + END + CASE @ExportToExcel WHEN 1 THEN N' deadlock_graph = - REPLACE(REPLACE( - REPLACE(REPLACE( + REPLACE(REPLACE( + REPLACE(REPLACE( CONVERT ( nvarchar(MAX), @@ -3286,10 +3286,10 @@ BEGIN isolation_level, owner_mode, waiter_mode, - lock_mode, + lock_mode, transaction_count, - client_option_1, - client_option_2, + client_option_1, + client_option_2, login_name, host_name, client_app, @@ -3301,7 +3301,7 @@ BEGIN last_batch_started, last_batch_completed, transaction_name, - status, + status, owner_waiter_type, owner_activity, owner_waiter_activity, From 3a1c68a5c2fcf1526d96f6c3317698201ab8c325 Mon Sep 17 00:00:00 2001 From: Cody Konior Date: Wed, 7 Dec 2022 11:06:41 +0800 Subject: [PATCH 363/662] Update check for managed instances I've checked against mine and today[tm] the azure-style prefix is valid. e.g. it returns MSSQL$CCCCCCCCCCC which is a string of hex numbers. --- sp_BlitzFirst.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sp_BlitzFirst.sql b/sp_BlitzFirst.sql index 6c34c761c..840a62a2c 100644 --- a/sp_BlitzFirst.sql +++ b/sp_BlitzFirst.sql @@ -1131,7 +1131,7 @@ BEGIN @StockDetailsFooter = @StockDetailsFooter + @LineFeed + ' -- ?>'; /* Get the instance name to use as a Perfmon counter prefix. */ - IF SERVERPROPERTY('EngineEdition') = 5 /*CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) = 'SQL Azure'*/ + IF SERVERPROPERTY('EngineEdition') IN (5, 8) /*CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) = 'SQL Azure'*/ SELECT TOP 1 @ServiceName = LEFT(object_name, (CHARINDEX(':', object_name) - 1)) FROM sys.dm_os_performance_counters; ELSE From ac4c8236170b791fb8e0bb252b5b8d31e79b82a6 Mon Sep 17 00:00:00 2001 From: David Wiseman Date: Thu, 8 Dec 2022 14:54:27 +0000 Subject: [PATCH 364/662] sp_AllNightLog - Exclude existing databases if they are not in a restoring or standby state Add validation check to exclude existing databases if they are not in a restoring or standby state. Fixes known issue 'overwrites existing databases without warning.' Ensure ignore_database option is always respected by wrapping OR condition in brackets. #3187 --- sp_AllNightLog.sql | 41 +++++++++++++++++++++++++---------------- 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/sp_AllNightLog.sql b/sp_AllNightLog.sql index 29ae2e959..72563f09c 100644 --- a/sp_AllNightLog.sql +++ b/sp_AllNightLog.sql @@ -1186,22 +1186,31 @@ IF @Restore = 1 ELSE 0 END FROM msdb.dbo.restore_worker rw WITH (UPDLOCK, HOLDLOCK, ROWLOCK) - WHERE - ( /*This section works on databases already part of the backup cycle*/ - rw.is_started = 0 - AND rw.is_completed = 1 - AND rw.last_log_restore_start_time < DATEADD(SECOND, (@rto * -1), GETDATE()) - AND (rw.error_number IS NULL OR rw.error_number > 0) /* negative numbers indicate human attention required */ - ) - OR - ( /*This section picks up newly added databases by DiskPollster*/ - rw.is_started = 0 - AND rw.is_completed = 0 - AND rw.last_log_restore_start_time = '1900-01-01 00:00:00.000' - AND rw.last_log_restore_finish_time = '9999-12-31 00:00:00.000' - AND (rw.error_number IS NULL OR rw.error_number > 0) /* negative numbers indicate human attention required */ - ) - AND rw.ignore_database = 0 + WHERE ( + ( /*This section works on databases already part of the backup cycle*/ + rw.is_started = 0 + AND rw.is_completed = 1 + AND rw.last_log_restore_start_time < DATEADD(SECOND, (@rto * -1), GETDATE()) + AND (rw.error_number IS NULL OR rw.error_number > 0) /* negative numbers indicate human attention required */ + ) + OR + ( /*This section picks up newly added databases by DiskPollster*/ + rw.is_started = 0 + AND rw.is_completed = 0 + AND rw.last_log_restore_start_time = '1900-01-01 00:00:00.000' + AND rw.last_log_restore_finish_time = '9999-12-31 00:00:00.000' + AND (rw.error_number IS NULL OR rw.error_number > 0) /* negative numbers indicate human attention required */ + ) + ) + AND rw.ignore_database = 0 + AND NOT EXISTS ( + /* Validation check to ensure the database either doesn't exist or is in a restoring/standby state */ + SELECT 1 + FROM sys.databases d + WHERE d.name = rw.database_name COLLATE SQL_Latin1_General_CP1_CI_AS + AND state <> 1 /* Restoring */ + AND NOT (state=0 AND d.is_in_standby=1) /* standby mode */ + ) ORDER BY rw.last_log_restore_start_time ASC, rw.last_log_restore_finish_time ASC, rw.database_name ASC; From bde848371fb672375631a338ba1f0da35b23ba4c Mon Sep 17 00:00:00 2001 From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com> Date: Thu, 8 Dec 2022 13:13:35 -0500 Subject: [PATCH 365/662] Update sp_BlitzLock.sql * fix database name data type * adjust some raiserror output * fix join to table variable * fix try_cast to xml not working for some queries with inequality operators * fix bug with export to excel and collate clause --- sp_BlitzLock.sql | 41 +++++++++++++++++++++++++++++------------ 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index e7ecf279d..3b9f2c8d5 100644 --- a/sp_BlitzLock.sql +++ b/sp_BlitzLock.sql @@ -7,7 +7,7 @@ GO ALTER PROCEDURE dbo.sp_BlitzLock ( - @DatabaseName nvarchar(256) = NULL, + @DatabaseName sysname = NULL, @StartDate datetime = NULL, @EndDate datetime = NULL, @ObjectName nvarchar(1024) = NULL, @@ -317,9 +317,12 @@ BEGIN @StringToExecuteParams, @r OUTPUT; - RAISERROR('@r is: %s', 0, 1, @r) WITH NOWAIT; + IF @Debug = 1 + BEGIN + RAISERROR('@r is set to: %s for schema name % and table name %s', 0, 1, @r, @OutputSchemaName, @OutputTableName) WITH NOWAIT; + END; - /*put covers around all before.*/ + /*protection spells*/ SELECT @ObjectFullName = QUOTENAME(@OutputDatabaseName) + @@ -608,7 +611,7 @@ BEGIN IF (ERROR_NUMBER() = 1088) BEGIN; SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Cannot run UPDATE STATISTICS on a #temp table without db_owner or sysadmin permissions %s', 0, 1, @d) WITH NOWAIT; + RAISERROR('Cannot run UPDATE STATISTICS on a #temp table without db_owner or sysadmin permissions', 0, 1) WITH NOWAIT; END; ELSE BEGIN; @@ -2366,12 +2369,12 @@ BEGIN SELECT DISTINCT dow.object_name, dow.database_name, - schema_name = a.schema_name, - table_name = a.table_name + schema_name = s.schema_name, + table_name = s.table_name FROM #deadlock_owner_waiter AS dow - LEFT JOIN @sysAssObjId AS a - ON a.database_id = dow.database_id - AND a.partition_id = dow.associatedObjectId + JOIN @sysAssObjId AS s + ON s.database_id = dow.database_id + AND s.partition_id = dow.associatedObjectId WHERE 1 = 1 AND (dow.database_id = @DatabaseName OR @DatabaseName IS NULL) AND (dow.event_date >= @StartDate OR @StartDate IS NULL) @@ -3108,7 +3111,14 @@ BEGIN d.client_option_2, d.lock_mode, query_xml = - TRY_CAST(N'' + d.inputbuf + N'' AS xml), + ( + SELECT + [processing-instruction(query)] = + d.inputbuf + FOR XML + PATH(N''), + TYPE + ), query_string = d.inputbuf, d.object_names, @@ -3160,6 +3170,7 @@ BEGIN d.waiter_merging, d.waiter_spilling, d.waiter_waiting_to_close, + /*end parallel deadlock columns*/ d.deadlock_graph, d.is_victim INTO #deadlock_results @@ -3247,8 +3258,8 @@ BEGIN CONVERT ( nvarchar(MAX), - dr.deadlock_graph COLLATE Latin1_General_BIN2 - ), + dr.deadlock_graph + ) COLLATE Latin1_General_BIN2, ''NCHAR(10)'', ''''), ''NCHAR(13)'', ''''), ''CHAR(10)'', ''''), ''CHAR(13)'', '''')' ELSE N' @@ -3434,6 +3445,12 @@ BEGIN FROM #deadlock_stack AS ds OPTION(RECOMPILE); + SELECT + table_name = N'#deadlocks', + * + FROM #deadlocks AS d + OPTION(RECOMPILE); + SELECT table_name = N'#deadlock_results', * From f5cf7bccf15499f08194b69fa77ba6af1c7d99b4 Mon Sep 17 00:00:00 2001 From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com> Date: Thu, 8 Dec 2022 16:29:17 -0500 Subject: [PATCH 366/662] Update sp_BlitzLock.sql There were come cross-contamination bugs if you tried to output to a table and also export to excel. No one should need to do that, since we're not going cross-server. --- sp_BlitzLock.sql | 54 +++++++++++++++++++++++++++++------------------- 1 file changed, 33 insertions(+), 21 deletions(-) diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index 3b9f2c8d5..29cad0d05 100644 --- a/sp_BlitzLock.sql +++ b/sp_BlitzLock.sql @@ -319,7 +319,7 @@ BEGIN IF @Debug = 1 BEGIN - RAISERROR('@r is set to: %s for schema name % and table name %s', 0, 1, @r, @OutputSchemaName, @OutputTableName) WITH NOWAIT; + RAISERROR('@r is set to: %s for schema name %s and table name %s', 0, 1, @r, @OutputSchemaName, @OutputTableName) WITH NOWAIT; END; /*protection spells*/ @@ -2103,7 +2103,11 @@ BEGIN ELSE dp.wait_resource END, lock_count = - CONVERT(nvarchar(20), COUNT_BIG(DISTINCT dp.event_date)) + CONVERT + ( + nvarchar(20), + COUNT_BIG(DISTINCT dp.event_date) + ) FROM #deadlock_process AS dp JOIN #deadlock_owner_waiter AS dow ON (dp.id = dow.owner_id @@ -2337,7 +2341,8 @@ BEGIN N' has been involved in ' + CONVERT ( - nvarchar(10), COUNT_BIG(DISTINCT ds.id) + nvarchar(10), + COUNT_BIG(DISTINCT ds.id) ) + N' deadlocks.' FROM #deadlock_stack AS ds @@ -3076,7 +3081,7 @@ BEGIN AND (DB_NAME(d.database_id) = @DatabaseName OR @DatabaseName IS NULL) AND (d.event_date >= @StartDate OR @StartDate IS NULL) AND (d.event_date < @EndDate OR @EndDate IS NULL) - AND (CONVERT(nvarchar(MAX), d.object_names) LIKE N'%' + @ObjectName + N'%' OR @ObjectName IS NULL) + AND (CONVERT(nvarchar(MAX), d.object_names) COLLATE Latin1_General_BIN2 LIKE N'%' + @ObjectName + N'%' OR @ObjectName IS NULL) AND (d.client_app = @AppName OR @AppName IS NULL) AND (d.host_name = @HostName OR @HostName IS NULL) AND (d.login_name = @LoginName OR @LoginName IS NULL) @@ -3170,7 +3175,7 @@ BEGIN d.waiter_merging, d.waiter_spilling, d.waiter_waiting_to_close, - /*end parallel deadlock columns*/ + /*end parallel deadlock columns*/ d.deadlock_graph, d.is_victim INTO #deadlock_results @@ -3179,6 +3184,12 @@ BEGIN IF @Debug = 1 BEGIN SET STATISTICS XML OFF; END; RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + /*There's too much risk of errors sending the*/ + IF @OutputDatabaseCheck = 0 + BEGIN + SET @ExportToExcel = 0; + END; + SET @deadlock_result += N' SELECT server_name = @@ -3196,17 +3207,18 @@ BEGIN dr.deadlock_group, ' + CASE @ExportToExcel WHEN 1 - THEN N'query = dr.query_string, - object_names = - REPLACE( - REPLACE( - CONVERT - ( - nvarchar(MAX), - dr.object_names - ), - '''', ''''), - '''', ''''),' + THEN N' + query = dr.query_string, + object_names = + REPLACE( + REPLACE( + CONVERT + ( + nvarchar(MAX), + dr.object_names + ) COLLATE Latin1_General_BIN2, + '''', ''''), + '''', ''''),' ELSE N'query = dr.query_xml, dr.object_names,' END + N' @@ -3255,11 +3267,11 @@ BEGIN deadlock_graph = REPLACE(REPLACE( REPLACE(REPLACE( - CONVERT - ( - nvarchar(MAX), - dr.deadlock_graph - ) COLLATE Latin1_General_BIN2, + CONVERT + ( + nvarchar(MAX), + dr.deadlock_graph + ) COLLATE Latin1_General_BIN2, ''NCHAR(10)'', ''''), ''NCHAR(13)'', ''''), ''CHAR(10)'', ''''), ''CHAR(13)'', '''')' ELSE N' From 587d5590950a615d47e53482028a425db33c5ed1 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Sun, 11 Dec 2022 12:18:14 -0800 Subject: [PATCH 367/662] #3190 sp_DatabaseRestore SQL Server 2022 Adds new undocumented columns. Fixes #3190. --- sp_DatabaseRestore.sql | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/sp_DatabaseRestore.sql b/sp_DatabaseRestore.sql index b63aac6d3..80b72e6eb 100755 --- a/sp_DatabaseRestore.sql +++ b/sp_DatabaseRestore.sql @@ -352,6 +352,9 @@ CREATE TABLE #Headers KeyAlgorithm NVARCHAR(32), EncryptorThumbprint VARBINARY(20), EncryptorType NVARCHAR(32), + LastValidRestoreTime DATETIME, + TimeZone NVARCHAR(256), + CompressionAlgorithm NVARCHAR(256), -- -- Seq added to retain order by -- @@ -521,6 +524,9 @@ IF @MajorVersion >= 11 IF @MajorVersion >= 13 OR (@MajorVersion = 12 AND @BuildVersion >= 2342) SET @HeadersSQL += N', KeyAlgorithm, EncryptorThumbprint, EncryptorType'; +IF @MajorVersion >= 16 + SET @HeadersSQL += N', LastValidRestoreTime, TimeZone, CompressionAlgorithm'; + SET @HeadersSQL += N')' + NCHAR(13) + NCHAR(10); SET @HeadersSQL += N'EXEC (''RESTORE HEADERONLY FROM DISK=''''{Path}'''''')'; From a775ae72d5dc26808922f70b3d3c0b7a9bb2759c Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Tue, 13 Dec 2022 03:53:39 -0800 Subject: [PATCH 368/662] sp_AllNightLog - remove hard coded collation I get a little nervous when we hard-code database collations, especially on a join between master.sys.databases and something in msdb. If you have two different collations in those two system databases, you've got bigger problems. --- sp_AllNightLog.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sp_AllNightLog.sql b/sp_AllNightLog.sql index 72563f09c..b3e501125 100644 --- a/sp_AllNightLog.sql +++ b/sp_AllNightLog.sql @@ -1207,7 +1207,7 @@ IF @Restore = 1 /* Validation check to ensure the database either doesn't exist or is in a restoring/standby state */ SELECT 1 FROM sys.databases d - WHERE d.name = rw.database_name COLLATE SQL_Latin1_General_CP1_CI_AS + WHERE d.name = rw.database_name AND state <> 1 /* Restoring */ AND NOT (state=0 AND d.is_in_standby=1) /* standby mode */ ) From 51c8623b604448e0c5219a96fee7631dc4787ac6 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Tue, 13 Dec 2022 04:15:53 -0800 Subject: [PATCH 369/662] #3189 sp_BlitzIndex hide tombstones Don't show inactive rowgroups on columnstore indexes. Closes #3189. --- sp_BlitzIndex.sql | 1 + 1 file changed, 1 insertion(+) diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index 7ad498d79..47d974713 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -2994,6 +2994,7 @@ BEGIN LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partition_range_values prve ON prve.function_id = pf.function_id AND prve.boundary_id = p.partition_number ' ELSE N' ' END + N' LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_segments seg ON p.partition_id = seg.partition_id AND ic.index_column_id = seg.column_id AND rg.row_group_id = seg.segment_id WHERE rg.object_id = @ObjectID + AND rg.state IN (1, 2, 3) AND c.name IN ( ' + @ColumnListWithApostrophes + N')' + CASE WHEN @ShowPartitionRanges = 1 THEN N' ) AS y ' ELSE N' ' END + N' From a6e79a107cc921b2d6d93d8f0dc90240319ee096 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Tue, 13 Dec 2022 04:40:23 -0800 Subject: [PATCH 370/662] #3160 sp_BlitzCache unused grant save Save results to table if we have an unpredicted sort order. Closes #3160. --- sp_BlitzCache.sql | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/sp_BlitzCache.sql b/sp_BlitzCache.sql index d93d710d1..5dbb0f604 100644 --- a/sp_BlitzCache.sql +++ b/sp_BlitzCache.sql @@ -7269,7 +7269,9 @@ END '; WHEN N'avg duration' THEN N' AverageDuration' WHEN N'avg executions' THEN N' ExecutionsPerMinute' WHEN N'avg memory grant' THEN N' AvgMaxMemoryGrant' - WHEN 'avg spills' THEN N' AvgSpills' + WHEN N'avg spills' THEN N' AvgSpills' + WHEN N'unused grant' THEN N' MaxGrantKB - MaxUsedGrantKB' + ELSE N' TotalCPU ' END + N' DESC '; SET @StringToExecute += N' OPTION (RECOMPILE) ; '; @@ -7335,7 +7337,9 @@ END '; WHEN N'avg duration' THEN N' AverageDuration' WHEN N'avg executions' THEN N' ExecutionsPerMinute' WHEN N'avg memory grant' THEN N' AvgMaxMemoryGrant' - WHEN 'avg spills' THEN N' AvgSpills' + WHEN N'avg spills' THEN N' AvgSpills' + WHEN N'unused grant' THEN N' MaxGrantKB - MaxUsedGrantKB' + ELSE N' TotalCPU ' END + N' DESC '; SET @StringToExecute += N' OPTION (RECOMPILE) ; '; @@ -7492,7 +7496,9 @@ END '; WHEN N'avg duration' THEN N' AverageDuration' WHEN N'avg executions' THEN N' ExecutionsPerMinute' WHEN N'avg memory grant' THEN N' AvgMaxMemoryGrant' - WHEN 'avg spills' THEN N' AvgSpills' + WHEN N'avg spills' THEN N' AvgSpills' + WHEN N'unused grant' THEN N' MaxGrantKB - MaxUsedGrantKB' + ELSE N' TotalCPU ' END + N' DESC '; SET @StringToExecute += N' OPTION (RECOMPILE) ; '; From f6adc1dcee3849d9c5d38b9f1f06163c353d8600 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Tue, 13 Dec 2022 04:46:54 -0800 Subject: [PATCH 371/662] Readme - supported versions Updating to clarify which versions are supported. --- README.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index d5a605355..9bc8bab63 100644 --- a/README.md +++ b/README.md @@ -43,10 +43,9 @@ To install, [download the latest release ZIP](https://github.com/BrentOzarULTD/S The First Responder Kit runs on: -* SQL Server 2012, 2014, 2016, 2017, 2019 on Windows - fully supported. -* SQL Server 2017, 2019 on Linux - yes, fully supported except sp_AllNightLog and sp_DatabaseRestore, which require xp_cmdshell, which Microsoft doesn't provide on Linux. -* SQL Server 2008, 2008R2 - not officially supported since it's out of Microsoft support, but we try not to make changes that would break functionality here. -* SQL Server 2000, 2005 - not supported at all. +* SQL Server 2012, 2014, 2016, 2017, 2019, 2022 on Windows - fully supported. +* SQL Server on Linux - yes, fully supported except sp_AllNightLog and sp_DatabaseRestore, which require xp_cmdshell, which Microsoft doesn't provide on Linux. +* SQL Server 2008 R2 and earlier - not supported since it's out of Microsoft support, but check the Deprecated folder for older versions of the scripts which may work, depending on your versions and compatibility levels. * Amazon RDS SQL Server - fully supported. * Azure SQL DB - not supported. Some of the procedures work, but some don't, and Microsoft has a tendency to change DMVs in Azure without warning, so we don't put any effort into supporting it. If it works, great! If not, any changes to make it work would be on you. [See the contributing.md file](CONTRIBUTING.md) for how to do that. From 154f2ff4ad27109f302440071e76d53741465967 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Tue, 13 Dec 2022 04:58:59 -0800 Subject: [PATCH 372/662] SQLServerVersions - add 2022 RTM --- SqlServerVersions.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SqlServerVersions.sql b/SqlServerVersions.sql index dd47a238c..4db8ad366 100644 --- a/SqlServerVersions.sql +++ b/SqlServerVersions.sql @@ -41,7 +41,7 @@ DELETE FROM dbo.SqlServerVersions; INSERT INTO dbo.SqlServerVersions (MajorVersionNumber, MinorVersionNumber, Branch, [Url], ReleaseDate, MainstreamSupportEndDate, ExtendedSupportEndDate, MajorVersionName, MinorVersionName) VALUES - (16, 600, 'CTP2', 'https://support.microsoft.com/en-us/help/5011644', '2022-05-24', '2022-05-24', '2022-05-24', 'SQL Server 2022', 'CTP 2.0'), + (16, 1000, 'RTM', '', '2022-11-15', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'RTM'), (15, 4261, 'CU18', 'https://support.microsoft.com/en-us/help/5017593', '2022-09-28', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 18'), (15, 4249, 'CU17', 'https://support.microsoft.com/en-us/help/5016394', '2022-08-11', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 17'), (15, 4236, 'CU16 GDR', 'https://support.microsoft.com/en-us/help/5014353', '2022-06-14', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 16 GDR'), From c32b58f2f2c65885a943d33ce2c8698377c7ea60 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Tue, 13 Dec 2022 07:16:24 -0800 Subject: [PATCH 373/662] 2022-12-13 release Bumping version numbers, dates, building install script. --- Install-All-Scripts.sql | 6082 ++++++++++++++-------- Install-Core-Blitz-No-Query-Store.sql | 6109 +++++++++++++++-------- Install-Core-Blitz-With-Query-Store.sql | 6005 +++++++++++++++------- sp_AllNightLog.sql | 2 +- sp_AllNightLog_Setup.sql | 2 +- sp_Blitz.sql | 2 +- sp_BlitzAnalysis.sql | 2 +- sp_BlitzBackups.sql | 2 +- sp_BlitzCache.sql | 2 +- sp_BlitzFirst.sql | 2 +- sp_BlitzInMemoryOLTP.sql | 2 +- sp_BlitzIndex.sql | 2 +- sp_BlitzLock.sql | 4 +- sp_BlitzQueryStore.sql | 2 +- sp_BlitzWho.sql | 2 +- sp_DatabaseRestore.sql | 2 +- sp_ineachdb.sql | 2 +- 17 files changed, 12342 insertions(+), 5884 deletions(-) diff --git a/Install-All-Scripts.sql b/Install-All-Scripts.sql index 768123592..a78d6e77f 100644 --- a/Install-All-Scripts.sql +++ b/Install-All-Scripts.sql @@ -31,7 +31,7 @@ SET STATISTICS XML OFF; BEGIN; -SELECT @Version = '8.11', @VersionDate = '20221013'; +SELECT @Version = '8.12', @VersionDate = '20221213'; IF(@VersionCheckMode = 1) BEGIN @@ -1186,22 +1186,31 @@ IF @Restore = 1 ELSE 0 END FROM msdb.dbo.restore_worker rw WITH (UPDLOCK, HOLDLOCK, ROWLOCK) - WHERE - ( /*This section works on databases already part of the backup cycle*/ - rw.is_started = 0 - AND rw.is_completed = 1 - AND rw.last_log_restore_start_time < DATEADD(SECOND, (@rto * -1), GETDATE()) - AND (rw.error_number IS NULL OR rw.error_number > 0) /* negative numbers indicate human attention required */ - ) - OR - ( /*This section picks up newly added databases by DiskPollster*/ - rw.is_started = 0 - AND rw.is_completed = 0 - AND rw.last_log_restore_start_time = '1900-01-01 00:00:00.000' - AND rw.last_log_restore_finish_time = '9999-12-31 00:00:00.000' - AND (rw.error_number IS NULL OR rw.error_number > 0) /* negative numbers indicate human attention required */ - ) - AND rw.ignore_database = 0 + WHERE ( + ( /*This section works on databases already part of the backup cycle*/ + rw.is_started = 0 + AND rw.is_completed = 1 + AND rw.last_log_restore_start_time < DATEADD(SECOND, (@rto * -1), GETDATE()) + AND (rw.error_number IS NULL OR rw.error_number > 0) /* negative numbers indicate human attention required */ + ) + OR + ( /*This section picks up newly added databases by DiskPollster*/ + rw.is_started = 0 + AND rw.is_completed = 0 + AND rw.last_log_restore_start_time = '1900-01-01 00:00:00.000' + AND rw.last_log_restore_finish_time = '9999-12-31 00:00:00.000' + AND (rw.error_number IS NULL OR rw.error_number > 0) /* negative numbers indicate human attention required */ + ) + ) + AND rw.ignore_database = 0 + AND NOT EXISTS ( + /* Validation check to ensure the database either doesn't exist or is in a restoring/standby state */ + SELECT 1 + FROM sys.databases d + WHERE d.name = rw.database_name + AND state <> 1 /* Restoring */ + AND NOT (state=0 AND d.is_in_standby=1) /* standby mode */ + ) ORDER BY rw.last_log_restore_start_time ASC, rw.last_log_restore_finish_time ASC, rw.database_name ASC; @@ -1547,7 +1556,7 @@ SET STATISTICS XML OFF; BEGIN; -SELECT @Version = '8.11', @VersionDate = '20221013'; +SELECT @Version = '8.12', @VersionDate = '20221213'; IF(@VersionCheckMode = 1) BEGIN @@ -2891,7 +2900,7 @@ AS SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.11', @VersionDate = '20221013'; + SELECT @Version = '8.12', @VersionDate = '20221213'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -3943,7 +3952,7 @@ AS ) OR ( - Convert(datetime,ll.Value) < DATEADD(dd,-7, GETDATE()) + Convert(datetime,ll.Value,21) < DATEADD(dd,-7, GETDATE()) ) ); @@ -12590,7 +12599,7 @@ AS SET NOCOUNT ON; SET STATISTICS XML OFF; -SELECT @Version = '8.11', @VersionDate = '20221013'; +SELECT @Version = '8.12', @VersionDate = '20221213'; IF(@VersionCheckMode = 1) BEGIN @@ -13468,7 +13477,7 @@ AS SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.11', @VersionDate = '20221013'; + SELECT @Version = '8.12', @VersionDate = '20221213'; IF(@VersionCheckMode = 1) BEGIN @@ -15249,7 +15258,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.11', @VersionDate = '20221013'; +SELECT @Version = '8.12', @VersionDate = '20221213'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -18722,6 +18731,35 @@ WHERE QueryType = 'Statement' AND SPID = @@SPID OPTION (RECOMPILE); +/* Update to grab stored procedure name for individual statements when PSPO is detected */ +UPDATE p +SET QueryType = QueryType + ' (parent ' + + + QUOTENAME(OBJECT_SCHEMA_NAME(s.object_id, s.database_id)) + + '.' + + QUOTENAME(OBJECT_NAME(s.object_id, s.database_id)) + ')' +FROM ##BlitzCacheProcs p + OUTER APPLY ( + SELECT REPLACE(REPLACE(REPLACE(REPLACE(p.QueryText, ' (', '('), '( ', '('), ' =', '='), '= ', '=') AS NormalizedQueryText + ) a + OUTER APPLY ( + SELECT CHARINDEX('option(PLAN PER VALUE(ObjectID=', a.NormalizedQueryText) AS OptionStart + ) b + OUTER APPLY ( + SELECT SUBSTRING(a.NormalizedQueryText, b.OptionStart + 31, LEN(a.NormalizedQueryText) - b.OptionStart - 30) AS OptionSubstring + WHERE b.OptionStart > 0 + ) c + OUTER APPLY ( + SELECT PATINDEX('%[^0-9]%', c.OptionSubstring) AS ObjectLength + ) d + OUTER APPLY ( + SELECT TRY_CAST(SUBSTRING(OptionSubstring, 1, d.ObjectLength - 1) AS INT) AS ObjectId + ) e + JOIN sys.dm_exec_procedure_stats s ON DB_ID(p.DatabaseName) = s.database_id AND e.ObjectId = s.object_id +WHERE p.QueryType = 'Statement' +AND p.SPID = @@SPID +AND s.object_id IS NOT NULL +OPTION (RECOMPILE); + RAISERROR(N'Attempting to get function name for individual statements', 0, 1) WITH NOWAIT; DECLARE @function_update_sql NVARCHAR(MAX) = N'' IF EXISTS (SELECT 1/0 FROM sys.all_objects AS o WHERE o.name = 'dm_exec_function_stats') @@ -22209,7 +22247,9 @@ END '; WHEN N'avg duration' THEN N' AverageDuration' WHEN N'avg executions' THEN N' ExecutionsPerMinute' WHEN N'avg memory grant' THEN N' AvgMaxMemoryGrant' - WHEN 'avg spills' THEN N' AvgSpills' + WHEN N'avg spills' THEN N' AvgSpills' + WHEN N'unused grant' THEN N' MaxGrantKB - MaxUsedGrantKB' + ELSE N' TotalCPU ' END + N' DESC '; SET @StringToExecute += N' OPTION (RECOMPILE) ; '; @@ -22275,7 +22315,9 @@ END '; WHEN N'avg duration' THEN N' AverageDuration' WHEN N'avg executions' THEN N' ExecutionsPerMinute' WHEN N'avg memory grant' THEN N' AvgMaxMemoryGrant' - WHEN 'avg spills' THEN N' AvgSpills' + WHEN N'avg spills' THEN N' AvgSpills' + WHEN N'unused grant' THEN N' MaxGrantKB - MaxUsedGrantKB' + ELSE N' TotalCPU ' END + N' DESC '; SET @StringToExecute += N' OPTION (RECOMPILE) ; '; @@ -22432,7 +22474,9 @@ END '; WHEN N'avg duration' THEN N' AverageDuration' WHEN N'avg executions' THEN N' ExecutionsPerMinute' WHEN N'avg memory grant' THEN N' AvgMaxMemoryGrant' - WHEN 'avg spills' THEN N' AvgSpills' + WHEN N'avg spills' THEN N' AvgSpills' + WHEN N'unused grant' THEN N' MaxGrantKB - MaxUsedGrantKB' + ELSE N' TotalCPU ' END + N' DESC '; SET @StringToExecute += N' OPTION (RECOMPILE) ; '; @@ -22512,7 +22556,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.11', @VersionDate = '20221013'; +SELECT @Version = '8.12', @VersionDate = '20221213'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -22623,25 +22667,41 @@ SELECT END; RAISERROR(N'Starting run. %s', 0,1, @ScriptVersionName) WITH NOWAIT; - + + IF(@OutputType NOT IN ('TABLE','NONE')) BEGIN RAISERROR('Invalid value for parameter @OutputType. Expected: (TABLE;NONE)',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; + SET @OutputType = 'NONE' +END; IF(@OutputType = 'NONE') BEGIN + + IF ((@OutputServerName IS NOT NULL) AND (@OutputTableName IS NULL OR @OutputSchemaName IS NULL OR @OutputDatabaseName IS NULL)) + BEGIN + RAISERROR('Parameter @OutputServerName is specified, rest of @Output* parameters needs to also be specified',12,1); + RETURN; + END; + IF(@OutputTableName IS NULL OR @OutputSchemaName IS NULL OR @OutputDatabaseName IS NULL) BEGIN - RAISERROR('This procedure should be called with a value for @Output* parameters, as @OutputType is set to NONE',12,1); + RAISERROR('This procedure should be called with a value for @OutputTableName, @OutputSchemaName and @OutputDatabaseName parameters, as @OutputType is set to NONE',12,1); RETURN; END; + /* Output is supported for all modes, no reason to not bring pain and output IF(@BringThePain = 1) BEGIN RAISERROR('Incompatible Parameters: @BringThePain set to 1 and @OutputType set to NONE',12,1); RETURN; END; + */ /* Eventually limit by mode IF(@Mode not in (0,4)) BEGIN @@ -24389,7 +24449,7 @@ BEGIN TRY OR (PARSENAME(@SQLServerProductVersion, 4) = 10 AND PARSENAME(@SQLServerProductVersion, 3) = 50 AND PARSENAME(@SQLServerProductVersion, 2) >= 2500)) BEGIN RAISERROR (N'Gathering Statistics Info With Newer Syntax.',0,1) WITH NOWAIT; - SET @dsql=N'USE ' + @DatabaseName + N'; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + 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, 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, @@ -24464,7 +24524,7 @@ BEGIN TRY ELSE BEGIN RAISERROR (N'Gathering Statistics Info With Older Syntax.',0,1) WITH NOWAIT; - SET @dsql=N'USE ' + @DatabaseName + N'; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + 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, days_since_last_stats_update, rows, modification_counter, percent_modifications, modifications_before_auto_update, index_type_desc, table_create_date, table_modify_date, @@ -25442,6 +25502,7 @@ BEGIN LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partition_range_values prve ON prve.function_id = pf.function_id AND prve.boundary_id = p.partition_number ' ELSE N' ' END + N' LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_segments seg ON p.partition_id = seg.partition_id AND ic.index_column_id = seg.column_id AND rg.row_group_id = seg.segment_id WHERE rg.object_id = @ObjectID + AND rg.state IN (1, 2, 3) AND c.name IN ( ' + @ColumnListWithApostrophes + N')' + CASE WHEN @ShowPartitionRanges = 1 THEN N' ) AS y ' ELSE N' ' END + N' @@ -25486,32 +25547,142 @@ END; /* IF @TableName IS NOT NULL */ +ELSE /* @TableName IS NULL, so we operate in normal mode 0/1/2/3/4 */ +BEGIN +/* Validate and check table output params */ + /* Checks if @OutputServerName is populated with a valid linked server, and that the database name specified is valid */ + DECLARE @ValidOutputServer BIT; + DECLARE @ValidOutputLocation BIT; + DECLARE @LinkedServerDBCheck NVARCHAR(2000); + DECLARE @ValidLinkedServerDB INT; + DECLARE @tmpdbchk TABLE (cnt INT); + + IF @OutputServerName IS NOT NULL + BEGIN + IF (SUBSTRING(@OutputTableName, 2, 1) = '#') + BEGIN + RAISERROR('Due to the nature of temporary tables, outputting to a linked server requires a permanent table.', 16, 0); + END; + ELSE IF EXISTS (SELECT server_id FROM sys.servers WHERE QUOTENAME([name]) = @OutputServerName) + BEGIN + SET @LinkedServerDBCheck = 'SELECT 1 WHERE EXISTS (SELECT * FROM '+@OutputServerName+'.master.sys.databases WHERE QUOTENAME([name]) = '''+@OutputDatabaseName+''')'; + INSERT INTO @tmpdbchk EXEC sys.sp_executesql @LinkedServerDBCheck; + SET @ValidLinkedServerDB = (SELECT COUNT(*) FROM @tmpdbchk); + IF (@ValidLinkedServerDB > 0) + BEGIN + SET @ValidOutputServer = 1; + SET @ValidOutputLocation = 1; + END; + ELSE + RAISERROR('The specified database was not found on the output server', 16, 0); + END; + ELSE + BEGIN + RAISERROR('The specified output server was not found', 16, 0); + END; + END; + ELSE + BEGIN + IF (SUBSTRING(@OutputTableName, 2, 2) = '##') + BEGIN + SET @StringToExecute = N' IF (OBJECT_ID(''[tempdb].[dbo].@@@OutputTableName@@@'') IS NOT NULL) DROP TABLE @@@OutputTableName@@@'; + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); + EXEC(@StringToExecute); + + SET @OutputServerName = QUOTENAME(CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128))); + SET @OutputDatabaseName = '[tempdb]'; + SET @OutputSchemaName = '[dbo]'; + SET @ValidOutputLocation = 1; + END; + ELSE IF (SUBSTRING(@OutputTableName, 2, 1) = '#') + BEGIN + RAISERROR('Due to the nature of Dymamic SQL, only global (i.e. double pound (##)) temp tables are supported for @OutputTableName', 16, 0); + END; + ELSE IF @OutputDatabaseName IS NOT NULL + AND @OutputSchemaName IS NOT NULL + AND @OutputTableName IS NOT NULL + AND EXISTS ( SELECT * + FROM sys.databases + WHERE QUOTENAME([name]) = @OutputDatabaseName) + BEGIN + SET @ValidOutputLocation = 1; + SET @OutputServerName = QUOTENAME(CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128))); + END; + ELSE IF @OutputDatabaseName IS NOT NULL + AND @OutputSchemaName IS NOT NULL + AND @OutputTableName IS NOT NULL + AND NOT EXISTS ( SELECT * + FROM sys.databases + WHERE QUOTENAME([name]) = @OutputDatabaseName) + BEGIN + RAISERROR('The specified output database was not found on this server', 16, 0); + END; + ELSE + BEGIN + SET @ValidOutputLocation = 0; + END; + END; + + IF (@ValidOutputLocation = 0 AND @OutputType = 'NONE') + BEGIN + RAISERROR('Invalid output location and no output asked',12,1); + RETURN; + END; + + /* @OutputTableName lets us export the results to a permanent table */ + DECLARE @RunID UNIQUEIDENTIFIER; + SET @RunID = NEWID(); + DECLARE @TableExists BIT; + DECLARE @SchemaExists BIT; + DECLARE @TableExistsSql NVARCHAR(MAX); + IF (@ValidOutputLocation = 1 AND COALESCE(@OutputServerName, @OutputDatabaseName, @OutputSchemaName, @OutputTableName) IS NOT NULL) + BEGIN + SET @StringToExecute = + N'SET @SchemaExists = 0; + SET @TableExists = 0; + IF EXISTS(SELECT * FROM @@@OutputServerName@@@.@@@OutputDatabaseName@@@.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''@@@OutputSchemaName@@@'') + SET @SchemaExists = 1 + IF EXISTS (SELECT * FROM @@@OutputServerName@@@.@@@OutputDatabaseName@@@.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''@@@OutputSchemaName@@@'' AND QUOTENAME(TABLE_NAME) = ''@@@OutputTableName@@@'') + BEGIN + SET @TableExists = 1 + IF NOT EXISTS(SELECT * FROM @@@OutputServerName@@@.@@@OutputDatabaseName@@@.INFORMATION_SCHEMA.COLUMNS WHERE QUOTENAME(TABLE_SCHEMA) = ''@@@OutputSchemaName@@@'' + AND QUOTENAME(TABLE_NAME) = ''@@@OutputTableName@@@'' AND QUOTENAME(COLUMN_NAME) = ''[total_forwarded_fetch_count]'') + EXEC @@@OutputServerName@@@.@@@OutputDatabaseName@@@.dbo.sp_executesql N''ALTER TABLE @@@OutputSchemaName@@@.@@@OutputTableName@@@ ADD [total_forwarded_fetch_count] BIGINT'' + END'; + + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputServerName@@@', @OutputServerName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputSchemaName@@@', @OutputSchemaName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); + + EXEC sp_executesql @StringToExecute, N'@TableExists BIT OUTPUT, @SchemaExists BIT OUTPUT', @TableExists OUTPUT, @SchemaExists OUTPUT; + SET @TableExistsSql = + N'IF EXISTS(SELECT * FROM @@@OutputServerName@@@.@@@OutputDatabaseName@@@.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''@@@OutputSchemaName@@@'') + AND NOT EXISTS (SELECT * FROM @@@OutputServerName@@@.@@@OutputDatabaseName@@@.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''@@@OutputSchemaName@@@'' AND QUOTENAME(TABLE_NAME) = ''@@@OutputTableName@@@'') + SET @TableExists = 0 + ELSE + SET @TableExists = 1'; + + SET @TableExistsSql = REPLACE(@TableExistsSql, '@@@OutputServerName@@@', @OutputServerName); + SET @TableExistsSql = REPLACE(@TableExistsSql, '@@@OutputDatabaseName@@@', @OutputDatabaseName); + SET @TableExistsSql = REPLACE(@TableExistsSql, '@@@OutputSchemaName@@@', @OutputSchemaName); + SET @TableExistsSql = REPLACE(@TableExistsSql, '@@@OutputTableName@@@', @OutputTableName); + END - - - - - - - - - - - -ELSE IF @Mode IN (0, 4) /* DIAGNOSE*//* IF @TableName IS NOT NULL, so @TableName must not be null */ -BEGIN; + IF @Mode IN (0, 4) /* DIAGNOSE */ + BEGIN; IF @Mode IN (0, 4) /* DIAGNOSE priorities 1-100 */ BEGIN; RAISERROR(N'@Mode=0 or 4, running rules for priorities 1-100.', 0,1) WITH NOWAIT; @@ -26323,23 +26494,6 @@ BEGIN; - - - - - - - - - - - - - - - - - @@ -27376,27 +27530,7 @@ BEGIN; - - - - - - - - - - - - - - - - - - - - - + RAISERROR(N'Insert a row to help people find help', 0,1) WITH NOWAIT; IF DATEDIFF(MM, @VersionDate, GETDATE()) > 6 BEGIN @@ -27466,65 +27600,345 @@ BEGIN; RAISERROR(N'Returning results.', 0,1) WITH NOWAIT; /*Return results.*/ - IF (@Mode = 0) - BEGIN - IF(@OutputType <> 'NONE') + IF (@ValidOutputLocation = 1 AND COALESCE(@OutputServerName, @OutputDatabaseName, @OutputSchemaName, @OutputTableName) IS NOT NULL) BEGIN - SELECT Priority, ISNULL(br.findings_group,N'') + - CASE WHEN ISNULL(br.finding,N'') <> N'' THEN N': ' ELSE N'' END - + br.finding AS [Finding], - br.[database_name] AS [Database Name], - br.details AS [Details: schema.table.index(indexid)], - br.index_definition AS [Definition: [Property]] ColumnName {datatype maxbytes}], - ISNULL(br.secret_columns,'') AS [Secret Columns], - br.index_usage_summary AS [Usage], - br.index_size_summary AS [Size], - COALESCE(br.more_info,sn.more_info,'') AS [More Info], - br.URL, - COALESCE(br.create_tsql,ts.create_tsql,'') AS [Create TSQL], - br.sample_query_plan AS [Sample Query Plan] - FROM #BlitzIndexResults br - LEFT JOIN #IndexSanity sn ON - br.index_sanity_id=sn.index_sanity_id - LEFT JOIN #IndexCreateTsql ts ON - br.index_sanity_id=ts.index_sanity_id - ORDER BY br.Priority ASC, br.check_id ASC, br.blitz_result_id ASC, br.findings_group ASC - OPTION (RECOMPILE); - END; + IF NOT @SchemaExists = 1 + BEGIN + RAISERROR (N'Invalid schema name, data could not be saved.', 16, 0); + RETURN; + END - END; - ELSE IF (@Mode = 4) - IF(@OutputType <> 'NONE') - BEGIN - SELECT Priority, ISNULL(br.findings_group,N'') + - CASE WHEN ISNULL(br.finding,N'') <> N'' THEN N': ' ELSE N'' END - + br.finding AS [Finding], - br.[database_name] AS [Database Name], - br.details AS [Details: schema.table.index(indexid)], - br.index_definition AS [Definition: [Property]] ColumnName {datatype maxbytes}], - ISNULL(br.secret_columns,'') AS [Secret Columns], - br.index_usage_summary AS [Usage], - br.index_size_summary AS [Size], - COALESCE(br.more_info,sn.more_info,'') AS [More Info], - br.URL, - COALESCE(br.create_tsql,ts.create_tsql,'') AS [Create TSQL], - br.sample_query_plan AS [Sample Query Plan] - FROM #BlitzIndexResults br - LEFT JOIN #IndexSanity sn ON - br.index_sanity_id=sn.index_sanity_id - LEFT JOIN #IndexCreateTsql ts ON - br.index_sanity_id=ts.index_sanity_id - ORDER BY br.Priority ASC, br.check_id ASC, br.blitz_result_id ASC, br.findings_group ASC - OPTION (RECOMPILE); - END; - -END /* End @Mode=0 or 4 (diagnose)*/ - -ELSE IF (@Mode=1) /*Summarize*/ + IF @TableExists = 0 + BEGIN + SET @StringToExecute = + N'CREATE TABLE @@@OutputDatabaseName@@@.@@@OutputSchemaName@@@.@@@OutputTableName@@@ + ( + [id] INT IDENTITY(1,1) NOT NULL, + [run_id] UNIQUEIDENTIFIER, + [run_datetime] DATETIME, + [server_name] NVARCHAR(128), + [priority] INT, + [finding] NVARCHAR(4000), + [database_name] NVARCHAR(128), + [details] NVARCHAR(MAX), + [index_definition] NVARCHAR(MAX), + [secret_columns] NVARCHAR(MAX), + [index_usage_summary] NVARCHAR(MAX), + [index_size_summary] NVARCHAR(MAX), + [more_info] NVARCHAR(MAX), + [url] NVARCHAR(MAX), + [create_tsql] NVARCHAR(MAX), + [sample_query_plan] XML, + CONSTRAINT [PK_ID_@@@RunID@@@] PRIMARY KEY CLUSTERED ([id] ASC) + );'; + + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputSchemaName@@@', @OutputSchemaName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@RunID@@@', @RunID); + + IF @ValidOutputServer = 1 + BEGIN + SET @StringToExecute = REPLACE(@StringToExecute,'''',''''''); + EXEC('EXEC('''+@StringToExecute+''') AT ' + @OutputServerName); + END; + ELSE + BEGIN + EXEC(@StringToExecute); + END; + END; /* @TableExists = 0 */ + + -- Re-check that table now exists (if not we failed creating it) + SET @TableExists = NULL; + EXEC sp_executesql @TableExistsSql, N'@TableExists BIT OUTPUT', @TableExists OUTPUT; + + IF NOT @TableExists = 1 + BEGIN + RAISERROR('Creation of the output table failed.', 16, 0); + RETURN; + END; + + SET @StringToExecute = + N'INSERT @@@OutputServerName@@@.@@@OutputDatabaseName@@@.@@@OutputSchemaName@@@.@@@OutputTableName@@@ + ( + [run_id], + [run_datetime], + [server_name], + [priority], + [finding], + [database_name], + [details], + [index_definition], + [secret_columns], + [index_usage_summary], + [index_size_summary], + [more_info], + [url], + [create_tsql], + [sample_query_plan] + ) + SELECT + ''@@@RunID@@@'', + ''@@@GETDATE@@@'', + ''@@@LocalServerName@@@'', + -- Below should be a copy/paste of the real query + -- Make sure all quotes are escaped + Priority, ISNULL(br.findings_group,N'''') + + CASE WHEN ISNULL(br.finding,N'''') <> N'''' THEN N'': '' ELSE N'''' END + + br.finding AS [Finding], + br.[database_name] AS [Database Name], + br.details AS [Details: schema.table.index(indexid)], + br.index_definition AS [Definition: [Property]] ColumnName {datatype maxbytes}], + ISNULL(br.secret_columns,'''') AS [Secret Columns], + br.index_usage_summary AS [Usage], + br.index_size_summary AS [Size], + COALESCE(br.more_info,sn.more_info,'''') AS [More Info], + br.URL, + COALESCE(br.create_tsql,ts.create_tsql,'''') AS [Create TSQL], + br.sample_query_plan AS [Sample Query Plan] + FROM #BlitzIndexResults br + LEFT JOIN #IndexSanity sn ON + br.index_sanity_id=sn.index_sanity_id + LEFT JOIN #IndexCreateTsql ts ON + br.index_sanity_id=ts.index_sanity_id + ORDER BY br.Priority ASC, br.check_id ASC, br.blitz_result_id ASC, br.findings_group ASC + OPTION (RECOMPILE);'; + + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputServerName@@@', @OutputServerName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputSchemaName@@@', @OutputSchemaName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@RunID@@@', @RunID); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@GETDATE@@@', GETDATE()); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@LocalServerName@@@', CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128))); + EXEC(@StringToExecute); + + END + ELSE + BEGIN + IF(@OutputType <> 'NONE') + BEGIN + SELECT Priority, ISNULL(br.findings_group,N'') + + CASE WHEN ISNULL(br.finding,N'') <> N'' THEN N': ' ELSE N'' END + + br.finding AS [Finding], + br.[database_name] AS [Database Name], + br.details AS [Details: schema.table.index(indexid)], + br.index_definition AS [Definition: [Property]] ColumnName {datatype maxbytes}], + ISNULL(br.secret_columns,'') AS [Secret Columns], + br.index_usage_summary AS [Usage], + br.index_size_summary AS [Size], + COALESCE(br.more_info,sn.more_info,'') AS [More Info], + br.URL, + COALESCE(br.create_tsql,ts.create_tsql,'') AS [Create TSQL], + br.sample_query_plan AS [Sample Query Plan] + FROM #BlitzIndexResults br + LEFT JOIN #IndexSanity sn ON + br.index_sanity_id=sn.index_sanity_id + LEFT JOIN #IndexCreateTsql ts ON + br.index_sanity_id=ts.index_sanity_id + ORDER BY br.Priority ASC, br.check_id ASC, br.blitz_result_id ASC, br.findings_group ASC + OPTION (RECOMPILE); + END; + + END; + + END /* End @Mode=0 or 4 (diagnose)*/ + + + + + + + + + ELSE IF (@Mode=1) /*Summarize*/ BEGIN --This mode is to give some overall stats on the database. - IF(@OutputType <> 'NONE') - BEGIN + IF (@ValidOutputLocation = 1 AND COALESCE(@OutputServerName, @OutputDatabaseName, @OutputSchemaName, @OutputTableName) IS NOT NULL) + BEGIN + + IF NOT @SchemaExists = 1 + BEGIN + RAISERROR (N'Invalid schema name, data could not be saved.', 16, 0); + RETURN; + END + + IF @TableExists = 0 + BEGIN + SET @StringToExecute = + N'CREATE TABLE @@@OutputDatabaseName@@@.@@@OutputSchemaName@@@.@@@OutputTableName@@@ + ( + [id] INT IDENTITY(1,1) NOT NULL, + [run_id] UNIQUEIDENTIFIER, + [run_datetime] DATETIME, + [server_name] NVARCHAR(128), + [database_name] NVARCHAR(128), + [object_count] INT, + [reserved_gb] NUMERIC(29,1), + [reserved_lob_gb] NUMERIC(29,1), + [reserved_row_overflow_gb] NUMERIC(29,1), + [clustered_table_count] INT, + [clustered_table_gb] NUMERIC(29,1), + [nc_index_count] INT, + [nc_index_gb] NUMERIC(29,1), + [table_nc_index_ratio] NUMERIC(29,1), + [heap_count] INT, + [heap_gb] NUMERIC(29,1), + [partioned_table_count] INT, + [partioned_nc_count] INT, + [partioned_gb] NUMERIC(29,1), + [filtered_index_count] INT, + [indexed_view_count] INT, + [max_table_row_count] INT, + [max_table_gb] NUMERIC(29,1), + [max_nc_index_gb] NUMERIC(29,1), + [table_count_over_1gb] INT, + [table_count_over_10gb] INT, + [table_count_over_100gb] INT, + [nc_index_count_over_1gb] INT, + [nc_index_count_over_10gb] INT, + [nc_index_count_over_100gb] INT, + [min_create_date] DATETIME, + [max_create_date] DATETIME, + [max_modify_date] DATETIME, + [display_order] INT, + CONSTRAINT [PK_ID_@@@RunID@@@] PRIMARY KEY CLUSTERED ([id] ASC) + );'; + + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputSchemaName@@@', @OutputSchemaName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@RunID@@@', @RunID); + + IF @ValidOutputServer = 1 + BEGIN + SET @StringToExecute = REPLACE(@StringToExecute,'''',''''''); + EXEC('EXEC('''+@StringToExecute+''') AT ' + @OutputServerName); + END; + ELSE + BEGIN + EXEC(@StringToExecute); + END; + END; /* @TableExists = 0 */ + + -- Re-check that table now exists (if not we failed creating it) + SET @TableExists = NULL; + EXEC sp_executesql @TableExistsSql, N'@TableExists BIT OUTPUT', @TableExists OUTPUT; + + IF NOT @TableExists = 1 + BEGIN + RAISERROR('Creation of the output table failed.', 16, 0); + RETURN; + END; + + SET @StringToExecute = + N'INSERT @@@OutputServerName@@@.@@@OutputDatabaseName@@@.@@@OutputSchemaName@@@.@@@OutputTableName@@@ + ( + [run_id], + [run_datetime], + [server_name], + [database_name], + [object_count], + [reserved_gb], + [reserved_lob_gb], + [reserved_row_overflow_gb], + [clustered_table_count], + [clustered_table_gb], + [nc_index_count], + [nc_index_gb], + [table_nc_index_ratio], + [heap_count], + [heap_gb], + [partioned_table_count], + [partioned_nc_count], + [partioned_gb], + [filtered_index_count], + [indexed_view_count], + [max_table_row_count], + [max_table_gb], + [max_nc_index_gb], + [table_count_over_1gb], + [table_count_over_10gb], + [table_count_over_100gb], + [nc_index_count_over_1gb], + [nc_index_count_over_10gb], + [nc_index_count_over_100gb], + [min_create_date], + [max_create_date], + [max_modify_date], + [display_order] + ) + SELECT ''@@@RunID@@@'', + ''@@@GETDATE@@@'', + ''@@@LocalServerName@@@'', + -- Below should be a copy/paste of the real query + -- Make sure all quotes are escaped + -- NOTE! information line is skipped from output and the query below + -- NOTE! initial columns are not casted to nvarchar due to not outputing informational line + DB_NAME(i.database_id) AS [Database Name], + COUNT(*) AS [Number Objects], + CAST(SUM(sz.total_reserved_MB)/ + 1024. AS NUMERIC(29,1)) AS [All GB], + CAST(SUM(sz.total_reserved_LOB_MB)/ + 1024. AS NUMERIC(29,1)) AS [LOB GB], + CAST(SUM(sz.total_reserved_row_overflow_MB)/ + 1024. AS NUMERIC(29,1)) AS [Row Overflow GB], + SUM(CASE WHEN index_id=1 THEN 1 ELSE 0 END) AS [Clustered Tables], + CAST(SUM(CASE WHEN index_id=1 THEN sz.total_reserved_MB ELSE 0 END) + /1024. AS NUMERIC(29,1)) AS [Clustered Tables GB], + SUM(CASE WHEN index_id NOT IN (0,1) THEN 1 ELSE 0 END) AS [NC Indexes], + CAST(SUM(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) + /1024. AS NUMERIC(29,1)) AS [NC Indexes GB], + CASE WHEN SUM(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) > 0 THEN + CAST(SUM(CASE WHEN index_id IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) + / SUM(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) AS NUMERIC(29,1)) + ELSE 0 END AS [ratio table: NC Indexes], + SUM(CASE WHEN index_id=0 THEN 1 ELSE 0 END) AS [Heaps], + CAST(SUM(CASE WHEN index_id=0 THEN sz.total_reserved_MB ELSE 0 END) + /1024. AS NUMERIC(29,1)) AS [Heaps GB], + SUM(CASE WHEN index_id IN (0,1) AND partition_key_column_name IS NOT NULL THEN 1 ELSE 0 END) AS [Partitioned Tables], + SUM(CASE WHEN index_id NOT IN (0,1) AND partition_key_column_name IS NOT NULL THEN 1 ELSE 0 END) AS [Partitioned NCs], + CAST(SUM(CASE WHEN partition_key_column_name IS NOT NULL THEN sz.total_reserved_MB ELSE 0 END)/1024. AS NUMERIC(29,1)) AS [Partitioned GB], + SUM(CASE WHEN filter_definition <> '''' THEN 1 ELSE 0 END) AS [Filtered Indexes], + SUM(CASE WHEN is_indexed_view=1 THEN 1 ELSE 0 END) AS [Indexed Views], + MAX(total_rows) AS [Max Row Count], + CAST(MAX(CASE WHEN index_id IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) + /1024. AS NUMERIC(29,1)) AS [Max Table GB], + CAST(MAX(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) + /1024. AS NUMERIC(29,1)) AS [Max NC Index GB], + SUM(CASE WHEN index_id IN (0,1) AND sz.total_reserved_MB > 1024 THEN 1 ELSE 0 END) AS [Count Tables > 1GB], + SUM(CASE WHEN index_id IN (0,1) AND sz.total_reserved_MB > 10240 THEN 1 ELSE 0 END) AS [Count Tables > 10GB], + SUM(CASE WHEN index_id IN (0,1) AND sz.total_reserved_MB > 102400 THEN 1 ELSE 0 END) AS [Count Tables > 100GB], + SUM(CASE WHEN index_id NOT IN (0,1) AND sz.total_reserved_MB > 1024 THEN 1 ELSE 0 END) AS [Count NCs > 1GB], + SUM(CASE WHEN index_id NOT IN (0,1) AND sz.total_reserved_MB > 10240 THEN 1 ELSE 0 END) AS [Count NCs > 10GB], + SUM(CASE WHEN index_id NOT IN (0,1) AND sz.total_reserved_MB > 102400 THEN 1 ELSE 0 END) AS [Count NCs > 100GB], + MIN(create_date) AS [Oldest Create Date], + MAX(create_date) AS [Most Recent Create Date], + MAX(modify_date) AS [Most Recent Modify Date], + 1 AS [Display Order] + FROM #IndexSanity AS i + --left join here so we don''t lose disabled nc indexes + LEFT JOIN #IndexSanitySize AS sz + ON i.index_sanity_id=sz.index_sanity_id + GROUP BY DB_NAME(i.database_id) + ORDER BY [Display Order] ASC + OPTION (RECOMPILE);'; + + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputServerName@@@', @OutputServerName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputSchemaName@@@', @OutputSchemaName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@RunID@@@', @RunID); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@GETDATE@@@', GETDATE()); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@LocalServerName@@@', CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128))); + EXEC(@StringToExecute); + + END; /* @ValidOutputLocation = 1 */ + ELSE + BEGIN + IF(@OutputType <> 'NONE') + BEGIN + RAISERROR(N'@Mode=1, we are summarizing.', 0,1) WITH NOWAIT; SELECT DB_NAME(i.database_id) AS [Database Name], @@ -27584,123 +27998,26 @@ ELSE IF (@Mode=1) /*Summarize*/ NULL,NULL,0 AS display_order ORDER BY [Display Order] ASC OPTION (RECOMPILE); - END; - + END; + END; + END; /* End @Mode=1 (summarize)*/ - ELSE IF (@Mode=2) /*Index Detail*/ + + + + + + + + + ELSE IF (@Mode=2) /*Index Detail*/ BEGIN --This mode just spits out all the detail without filters. --This supports slicing AND dicing in Excel RAISERROR(N'@Mode=2, here''s the details on existing indexes.', 0,1) WITH NOWAIT; - - /* Checks if @OutputServerName is populated with a valid linked server, and that the database name specified is valid */ - DECLARE @ValidOutputServer BIT; - DECLARE @ValidOutputLocation BIT; - DECLARE @LinkedServerDBCheck NVARCHAR(2000); - DECLARE @ValidLinkedServerDB INT; - DECLARE @tmpdbchk TABLE (cnt INT); - - IF @OutputServerName IS NOT NULL - BEGIN - IF (SUBSTRING(@OutputTableName, 2, 1) = '#') - BEGIN - RAISERROR('Due to the nature of temporary tables, outputting to a linked server requires a permanent table.', 16, 0); - END; - ELSE IF EXISTS (SELECT server_id FROM sys.servers WHERE QUOTENAME([name]) = @OutputServerName) - BEGIN - SET @LinkedServerDBCheck = 'SELECT 1 WHERE EXISTS (SELECT * FROM '+@OutputServerName+'.master.sys.databases WHERE QUOTENAME([name]) = '''+@OutputDatabaseName+''')'; - INSERT INTO @tmpdbchk EXEC sys.sp_executesql @LinkedServerDBCheck; - SET @ValidLinkedServerDB = (SELECT COUNT(*) FROM @tmpdbchk); - IF (@ValidLinkedServerDB > 0) - BEGIN - SET @ValidOutputServer = 1; - SET @ValidOutputLocation = 1; - END; - ELSE - RAISERROR('The specified database was not found on the output server', 16, 0); - END; - ELSE - BEGIN - RAISERROR('The specified output server was not found', 16, 0); - END; - END; - ELSE - BEGIN - IF (SUBSTRING(@OutputTableName, 2, 2) = '##') - BEGIN - SET @StringToExecute = N' IF (OBJECT_ID(''[tempdb].[dbo].@@@OutputTableName@@@'') IS NOT NULL) DROP TABLE @@@OutputTableName@@@'; - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); - EXEC(@StringToExecute); - - SET @OutputServerName = QUOTENAME(CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128))); - SET @OutputDatabaseName = '[tempdb]'; - SET @OutputSchemaName = '[dbo]'; - SET @ValidOutputLocation = 1; - END; - ELSE IF (SUBSTRING(@OutputTableName, 2, 1) = '#') - BEGIN - RAISERROR('Due to the nature of Dymamic SQL, only global (i.e. double pound (##)) temp tables are supported for @OutputTableName', 16, 0); - END; - ELSE IF @OutputDatabaseName IS NOT NULL - AND @OutputSchemaName IS NOT NULL - AND @OutputTableName IS NOT NULL - AND EXISTS ( SELECT * - FROM sys.databases - WHERE QUOTENAME([name]) = @OutputDatabaseName) - BEGIN - SET @ValidOutputLocation = 1; - SET @OutputServerName = QUOTENAME(CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128))); - END; - ELSE IF @OutputDatabaseName IS NOT NULL - AND @OutputSchemaName IS NOT NULL - AND @OutputTableName IS NOT NULL - AND NOT EXISTS ( SELECT * - FROM sys.databases - WHERE QUOTENAME([name]) = @OutputDatabaseName) - BEGIN - RAISERROR('The specified output database was not found on this server', 16, 0); - END; - ELSE - BEGIN - SET @ValidOutputLocation = 0; - END; - END; - - IF (@ValidOutputLocation = 0 AND @OutputType = 'NONE') - BEGIN - RAISERROR('Invalid output location and no output asked',12,1); - RETURN; - END; - - /* @OutputTableName lets us export the results to a permanent table */ - DECLARE @RunID UNIQUEIDENTIFIER; - SET @RunID = NEWID(); - IF (@ValidOutputLocation = 1 AND COALESCE(@OutputServerName, @OutputDatabaseName, @OutputSchemaName, @OutputTableName) IS NOT NULL) BEGIN - DECLARE @TableExists BIT; - DECLARE @SchemaExists BIT; - SET @StringToExecute = - N'SET @SchemaExists = 0; - SET @TableExists = 0; - IF EXISTS(SELECT * FROM @@@OutputServerName@@@.@@@OutputDatabaseName@@@.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''@@@OutputSchemaName@@@'') - SET @SchemaExists = 1 - IF EXISTS (SELECT * FROM @@@OutputServerName@@@.@@@OutputDatabaseName@@@.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''@@@OutputSchemaName@@@'' AND QUOTENAME(TABLE_NAME) = ''@@@OutputTableName@@@'') - BEGIN - SET @TableExists = 1 - IF NOT EXISTS(SELECT * FROM @@@OutputServerName@@@.@@@OutputDatabaseName@@@.INFORMATION_SCHEMA.COLUMNS WHERE QUOTENAME(TABLE_SCHEMA) = ''@@@OutputSchemaName@@@'' - AND QUOTENAME(TABLE_NAME) = ''@@@OutputTableName@@@'' AND QUOTENAME(COLUMN_NAME) = ''[total_forwarded_fetch_count]'') - EXEC @@@OutputServerName@@@.@@@OutputDatabaseName@@@.dbo.sp_executesql N''ALTER TABLE @@@OutputSchemaName@@@.@@@OutputTableName@@@ ADD [total_forwarded_fetch_count] BIGINT'' - END'; - - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputServerName@@@', @OutputServerName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputSchemaName@@@', @OutputSchemaName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); - - EXEC sp_executesql @StringToExecute, N'@TableExists BIT OUTPUT, @SchemaExists BIT OUTPUT', @TableExists OUTPUT, @SchemaExists OUTPUT; - IF @SchemaExists = 1 BEGIN IF @TableExists = 0 @@ -27801,20 +28118,10 @@ ELSE IF (@Mode=1) /*Summarize*/ END; END; /* @TableExists = 0 */ - SET @StringToExecute = - N'IF EXISTS(SELECT * FROM @@@OutputServerName@@@.@@@OutputDatabaseName@@@.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''@@@OutputSchemaName@@@'') - AND NOT EXISTS (SELECT * FROM @@@OutputServerName@@@.@@@OutputDatabaseName@@@.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''@@@OutputSchemaName@@@'' AND QUOTENAME(TABLE_NAME) = ''@@@OutputTableName@@@'') - SET @TableExists = 0 - ELSE - SET @TableExists = 1'; - + + -- Re-check that table now exists (if not we failed creating it) SET @TableExists = NULL; - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputServerName@@@', @OutputServerName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputSchemaName@@@', @OutputSchemaName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); - - EXEC sp_executesql @StringToExecute, N'@TableExists BIT OUTPUT', @TableExists OUTPUT; + EXEC sp_executesql @TableExistsSql, N'@TableExists BIT OUTPUT', @TableExists OUTPUT; IF @TableExists = 1 BEGIN @@ -28142,10 +28449,167 @@ ELSE IF (@Mode=1) /*Summarize*/ END; END; /* End @Mode=2 (index detail)*/ + + + + + + + + ELSE IF (@Mode=3) /*Missing index Detail*/ BEGIN - IF(@OutputType <> 'NONE') - BEGIN; + IF (@ValidOutputLocation = 1 AND COALESCE(@OutputServerName, @OutputDatabaseName, @OutputSchemaName, @OutputTableName) IS NOT NULL) + BEGIN + + IF NOT @SchemaExists = 1 + BEGIN + RAISERROR (N'Invalid schema name, data could not be saved.', 16, 0); + RETURN; + END + + IF @TableExists = 0 + BEGIN + SET @StringToExecute = + N'CREATE TABLE @@@OutputDatabaseName@@@.@@@OutputSchemaName@@@.@@@OutputTableName@@@ + ( + [id] INT IDENTITY(1,1) NOT NULL, + [run_id] UNIQUEIDENTIFIER, + [run_datetime] DATETIME, + [server_name] NVARCHAR(128), + [database_name] NVARCHAR(128), + [schema_name] NVARCHAR(128), + [table_name] NVARCHAR(128), + [magic_benefit_number] BIGINT, + [missing_index_details] NVARCHAR(MAX), + [avg_total_user_cost] NUMERIC(29,4), + [avg_user_impact] NUMERIC(29,1), + [user_seeks] BIGINT, + [user_scans] BIGINT, + [unique_compiles] BIGINT, + [equality_columns_with_data_type] NVARCHAR(MAX), + [inequality_columns_with_data_type] NVARCHAR(MAX), + [included_columns_with_data_type] NVARCHAR(MAX), + [index_estimated_impact] NVARCHAR(256), + [create_tsql] NVARCHAR(MAX), + [more_info] NVARCHAR(600), + [display_order] INT, + [is_low] BIT, + [sample_query_plan] XML, + CONSTRAINT [PK_ID_@@@RunID@@@] PRIMARY KEY CLUSTERED ([id] ASC) + );'; + + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputSchemaName@@@', @OutputSchemaName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@RunID@@@', @RunID); + + IF @ValidOutputServer = 1 + BEGIN + SET @StringToExecute = REPLACE(@StringToExecute,'''',''''''); + EXEC('EXEC('''+@StringToExecute+''') AT ' + @OutputServerName); + END; + ELSE + BEGIN + EXEC(@StringToExecute); + END; + END; /* @TableExists = 0 */ + + -- Re-check that table now exists (if not we failed creating it) + SET @TableExists = NULL; + EXEC sp_executesql @TableExistsSql, N'@TableExists BIT OUTPUT', @TableExists OUTPUT; + + IF NOT @TableExists = 1 + BEGIN + RAISERROR('Creation of the output table failed.', 16, 0); + RETURN; + END; + SET @StringToExecute = + N'WITH create_date AS ( + SELECT i.database_id, + i.schema_name, + i.[object_id], + ISNULL(NULLIF(MAX(DATEDIFF(DAY, i.create_date, SYSDATETIME())), 0), 1) AS create_days + FROM #IndexSanity AS i + GROUP BY i.database_id, i.schema_name, i.object_id + ) + INSERT @@@OutputServerName@@@.@@@OutputDatabaseName@@@.@@@OutputSchemaName@@@.@@@OutputTableName@@@ + ( + [run_id], + [run_datetime], + [server_name], + [database_name], + [schema_name], + [table_name], + [magic_benefit_number], + [missing_index_details], + [avg_total_user_cost], + [avg_user_impact], + [user_seeks], + [user_scans], + [unique_compiles], + [equality_columns_with_data_type], + [inequality_columns_with_data_type], + [included_columns_with_data_type], + [index_estimated_impact], + [create_tsql], + [more_info], + [display_order], + [is_low], + [sample_query_plan] + ) + SELECT ''@@@RunID@@@'', + ''@@@GETDATE@@@'', + ''@@@LocalServerName@@@'', + -- Below should be a copy/paste of the real query + -- Make sure all quotes are escaped + -- NOTE! information line is skipped from output and the query below + -- NOTE! CTE block is above insert in the copied SQL + mi.database_name AS [Database Name], + mi.[schema_name] AS [Schema], + mi.table_name AS [Table], + CAST((mi.magic_benefit_number / CASE WHEN cd.create_days < @DaysUptime THEN cd.create_days ELSE @DaysUptime END) AS BIGINT) + AS [Magic Benefit Number], + mi.missing_index_details AS [Missing Index Details], + mi.avg_total_user_cost AS [Avg Query Cost], + mi.avg_user_impact AS [Est Index Improvement], + mi.user_seeks AS [Seeks], + mi.user_scans AS [Scans], + mi.unique_compiles AS [Compiles], + mi.equality_columns_with_data_type AS [Equality Columns], + mi.inequality_columns_with_data_type AS [Inequality Columns], + mi.included_columns_with_data_type AS [Included Columns], + mi.index_estimated_impact AS [Estimated Impact], + mi.create_tsql AS [Create TSQL], + mi.more_info AS [More Info], + 1 AS [Display Order], + mi.is_low, + mi.sample_query_plan AS [Sample Query Plan] + FROM #MissingIndexes AS mi + LEFT JOIN create_date AS cd + ON mi.[object_id] = cd.object_id + AND mi.database_id = cd.database_id + AND mi.schema_name = cd.schema_name + /* Minimum benefit threshold = 100k/day of uptime OR since table creation date, whichever is lower*/ + WHERE @ShowAllMissingIndexRequests=1 + OR (mi.magic_benefit_number / CASE WHEN cd.create_days < @DaysUptime THEN cd.create_days ELSE @DaysUptime END) >= 100000 + ORDER BY [Display Order] ASC, [Magic Benefit Number] DESC + OPTION (RECOMPILE);'; + + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputServerName@@@', @OutputServerName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputSchemaName@@@', @OutputSchemaName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@RunID@@@', @RunID); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@GETDATE@@@', GETDATE()); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@LocalServerName@@@', CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128))); + EXEC sp_executesql @StringToExecute, N'@DaysUptime NUMERIC(23,2), @ShowAllMissingIndexRequests BIT', @DaysUptime = @DaysUptime, @ShowAllMissingIndexRequests = @ShowAllMissingIndexRequests; + + END; /* @ValidOutputLocation = 1 */ + ELSE + BEGIN + IF(@OutputType <> 'NONE') + BEGIN WITH create_date AS ( SELECT i.database_id, i.schema_name, @@ -28194,21 +28658,26 @@ ELSE IF (@Mode=1) /*Summarize*/ NULL, NULL, NULL, NULL, 0 AS [Display Order], NULL AS is_low, NULL ORDER BY [Display Order] ASC, [Magic Benefit Number] DESC OPTION (RECOMPILE); - END; + END; + + + IF (@BringThePain = 1 + AND @DatabaseName IS NOT NULL + AND @GetAllDatabases = 0) + + BEGIN + EXEC sp_BlitzCache @SortOrder = 'sp_BlitzIndex', @DatabaseName = @DatabaseName, @BringThePain = 1, @QueryFilter = 'statement', @HideSummary = 1; + END; + + END; - IF (@BringThePain = 1 - AND @DatabaseName IS NOT NULL - AND @GetAllDatabases = 0) - BEGIN - EXEC sp_BlitzCache @SortOrder = 'sp_BlitzIndex', @DatabaseName = @DatabaseName, @BringThePain = 1, @QueryFilter = 'statement', @HideSummary = 1; - - END; END; /* End @Mode=3 (index detail)*/ +END /* End @TableName IS NULL (mode 0/1/2/3/4) */ END TRY BEGIN CATCH @@ -28228,1817 +28697,3488 @@ BEGIN CATCH END CATCH; GO IF OBJECT_ID('dbo.sp_BlitzLock') IS NULL - EXEC ('CREATE PROCEDURE dbo.sp_BlitzLock AS RETURN 0;'); +BEGIN + EXEC ('CREATE PROCEDURE dbo.sp_BlitzLock AS RETURN 0;'); +END; GO -ALTER PROCEDURE dbo.sp_BlitzLock +ALTER PROCEDURE + dbo.sp_BlitzLock ( - @Top INT = 2147483647, - @DatabaseName NVARCHAR(256) = NULL, - @StartDate DATETIME = '19000101', - @EndDate DATETIME = '99991231', - @ObjectName NVARCHAR(1000) = NULL, - @StoredProcName NVARCHAR(1000) = NULL, - @AppName NVARCHAR(256) = NULL, - @HostName NVARCHAR(256) = NULL, - @LoginName NVARCHAR(256) = NULL, - @EventSessionPath VARCHAR(256) = 'system_health*.xel', - @VictimsOnly BIT = 0, - @Debug BIT = 0, - @Help BIT = 0, - @Version VARCHAR(30) = NULL OUTPUT, - @VersionDate DATETIME = NULL OUTPUT, - @VersionCheckMode BIT = 0, - @OutputDatabaseName NVARCHAR(256) = NULL , - @OutputSchemaName NVARCHAR(256) = 'dbo' , --ditto as below - @OutputTableName NVARCHAR(256) = 'BlitzLock', --put a standard here no need to check later in the script - @ExportToExcel BIT = 0 + @DatabaseName sysname = NULL, + @StartDate datetime = NULL, + @EndDate datetime = NULL, + @ObjectName nvarchar(1024) = NULL, + @StoredProcName nvarchar(1024) = NULL, + @AppName sysname = NULL, + @HostName sysname = NULL, + @LoginName sysname = NULL, + @EventSessionName sysname = N'system_health', + @TargetSessionType sysname = NULL, + @VictimsOnly bit = 0, + @Debug bit = 0, + @Help bit = 0, + @Version varchar(30) = NULL OUTPUT, + @VersionDate datetime = NULL OUTPUT, + @VersionCheckMode bit = 0, + @OutputDatabaseName sysname = NULL, + @OutputSchemaName sysname = N'dbo', /*ditto as below*/ + @OutputTableName sysname = N'BlitzLock', /*put a standard here no need to check later in the script*/ + @ExportToExcel bit = 0 ) WITH RECOMPILE AS BEGIN + SET STATISTICS XML OFF; + SET NOCOUNT, XACT_ABORT ON; + SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SET NOCOUNT ON; -SET STATISTICS XML OFF; -SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + SELECT @Version = '8.12', @VersionDate = '20221213'; -SELECT @Version = '8.11', @VersionDate = '20221013'; + IF @VersionCheckMode = 1 + BEGIN + RETURN; + END; + IF @Help = 1 + BEGIN + PRINT N' + /* + sp_BlitzLock from http://FirstResponderKit.org -IF(@VersionCheckMode = 1) -BEGIN - RETURN; -END; - IF @Help = 1 - BEGIN - PRINT ' - /* - sp_BlitzLock from http://FirstResponderKit.org - - This script checks for and analyzes deadlocks from the system health session or a custom extended event path + This script checks for and analyzes deadlocks from the system health session or a custom extended event path - Variables you can use: - @Top: Use if you want to limit the number of deadlocks to return. - This is ordered by event date ascending + Variables you can use: - @DatabaseName: If you want to filter to a specific database + @DatabaseName: If you want to filter to a specific database - @StartDate: The date you want to start searching on. + @StartDate: The date you want to start searching on, defaults to last 7 days - @EndDate: The date you want to stop searching on. + @EndDate: The date you want to stop searching on, defaults to current date - @ObjectName: If you want to filter to a specific able. - The object name has to be fully qualified ''Database.Schema.Table'' + @ObjectName: If you want to filter to a specific able. + The object name has to be fully qualified ''Database.Schema.Table'' - @StoredProcName: If you want to search for a single stored proc - The proc name has to be fully qualified ''Database.Schema.Sproc'' - - @AppName: If you want to filter to a specific application - - @HostName: If you want to filter to a specific host - - @LoginName: If you want to filter to a specific login + @StoredProcName: If you want to search for a single stored proc + The proc name has to be fully qualified ''Database.Schema.Sproc'' - @EventSessionPath: If you want to point this at an XE session rather than the system health session. - - @OutputDatabaseName: If you want to output information to a specific database - @OutputSchemaName: Specify a schema name to output information to a specific Schema - @OutputTableName: Specify table name to to output information to a specific table - - To learn more, visit http://FirstResponderKit.org where you can download new - versions for free, watch training videos on how it works, get more info on - the findings, contribute your own code, and more. + @AppName: If you want to filter to a specific application - Known limitations of this version: - - Only SQL Server 2012 and newer is supported - - If your tables have weird characters in them (https://en.wikipedia.org/wiki/List_of_XML_and_HTML_character_entity_references) you may get errors trying to parse the XML. - I took a long look at this one, and: - 1) Trying to account for all the weird places these could crop up is a losing effort. - 2) Replace is slow af on lots of XML. + @HostName: If you want to filter to a specific host + @LoginName: If you want to filter to a specific login + @EventSessionName: If you want to point this at an XE session rather than the system health session. + @TargetSessionType: Can be ''ring_buffer'' or ''event_file''. Leave NULL to auto-detect. - Unknown limitations of this version: - - None. (If we knew them, they would be known. Duh.) + @OutputDatabaseName: If you want to output information to a specific database + + @OutputSchemaName: Specify a schema name to output information to a specific Schema + + @OutputTableName: Specify table name to to output information to a specific table + + To learn more, visit http://FirstResponderKit.org where you can download new + versions for free, watch training videos on how it works, get more info on + the findings, contribute your own code, and more. + Known limitations of this version: + - Only SQL Server 2012 and newer is supported + - If your tables have weird characters in them (https://en.wikipedia.org/wiki/List_of_xml_and_HTML_character_entity_references) you may get errors trying to parse the xml. + I took a long look at this one, and: + 1) Trying to account for all the weird places these could crop up is a losing effort. + 2) Replace is slow af on lots of xml. + + Unknown limitations of this version: + - None. (If we knew them, they would be known. Duh.) MIT License - - Copyright (c) 2021 Brent Ozar Unlimited - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: + Copyright (c) 2022 Brent Ozar Unlimited - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE. + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. - */'; - RETURN; - END; /* @Help = 1 */ - - DECLARE @ProductVersion NVARCHAR(128), - @ProductVersionMajor FLOAT, - @ProductVersionMinor INT, - @ObjectFullName NVARCHAR(2000); + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + + */'; + + RETURN; + END; /* @Help = 1 */ + + /*Declare local variables used in the procudure*/ + DECLARE + @DatabaseId int = + DB_ID(@DatabaseName), + @ProductVersion nvarchar(128) = + CAST(SERVERPROPERTY('ProductVersion') AS nvarchar(128)), + @ProductVersionMajor float = + SUBSTRING + ( + CAST(SERVERPROPERTY('ProductVersion') AS nvarchar(128)), + 1, + CHARINDEX('.', CAST(SERVERPROPERTY('ProductVersion') AS nvarchar(128))) + 1 + ), + @ProductVersionMinor int = + PARSENAME + ( + CONVERT + ( + varchar(32), + CAST(SERVERPROPERTY('ProductVersion') AS nvarchar(128)) + ), + 2 + ), + @ObjectFullName nvarchar(MAX) = N'', + @Azure bit = + CASE + WHEN + ( + SELECT + SERVERPROPERTY('EDITION') + ) = 'SQL Azure' + THEN 1 + ELSE 0 + END, + @RDS bit = + CASE + WHEN LEFT(CAST(SERVERPROPERTY('ComputerNamePhysicalNetBIOS') AS varchar(8000)), 8) <> 'EC2AMAZ-' + AND LEFT(CAST(SERVERPROPERTY('MachineName') AS varchar(8000)), 8) <> 'EC2AMAZ-' + AND DB_ID('rdsadmin') IS NULL + THEN 0 + ELSE 1 + END, + @d varchar(40) = '', + @StringToExecute nvarchar(4000) = N'', + @StringToExecuteParams nvarchar(500) = N'', + @r sysname = NULL, + @OutputTableFindings nvarchar(100) = N'[BlitzLockFindings]', + @DeadlockCount int = 0, + @ServerName sysname = @@SERVERNAME, + @OutputDatabaseCheck bit = -1, + @SessionId int = 0, + @TargetSessionId int = 0, + @FileName nvarchar(4000) = N'', + @inputbuf_bom nvarchar(1) = CONVERT(nvarchar(1), 0x0a00, 0), + @deadlock_result nvarchar(MAX) = N''; + + /*Temporary objects used in the procedure*/ + DECLARE + @sysAssObjId AS table + ( + database_id int, + partition_id bigint, + schema_name sysname, + table_name sysname + ); + + CREATE TABLE + #x + ( + x xml NOT NULL + DEFAULT N'x' + ); + + CREATE TABLE + #deadlock_data + ( + deadlock_xml xml NOT NULL + DEFAULT N'x' + ); + + CREATE TABLE + #t + ( + id int NOT NULL + ); + + CREATE TABLE + #deadlock_findings + ( + id int IDENTITY PRIMARY KEY, + check_id int NOT NULL, + database_name nvarchar(256), + object_name nvarchar(1000), + finding_group nvarchar(100), + finding nvarchar(4000) + ); + + /*Set these to some sane defaults if NULLs are passed in*/ + /*Normally I'd hate this, but we RECOMPILE everything*/ + SELECT + @StartDate = + CASE + WHEN @StartDate IS NULL + THEN + DATEADD + ( + MINUTE, + DATEDIFF + ( + MINUTE, + SYSDATETIME(), + GETUTCDATE() + ), + ISNULL + ( + @StartDate, + DATEADD + ( + DAY, + -7, + SYSDATETIME() + ) + ) + ) + ELSE @StartDate + END, + @EndDate = + CASE + WHEN @EndDate IS NULL + THEN + DATEADD + ( + MINUTE, + DATEDIFF + ( + MINUTE, + SYSDATETIME(), + GETUTCDATE() + ), + ISNULL + ( + @EndDate, + SYSDATETIME() + ) + ) + ELSE @EndDate + END; + IF @OutputDatabaseName IS NOT NULL + BEGIN /*IF databaseName is set, do some sanity checks and put [] around def.*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('@OutputDatabaseName set to %s, checking validity at %s', 0, 1, @OutputDatabaseName, @d) WITH NOWAIT; - SET @ProductVersion = CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)); + IF NOT EXISTS + ( + SELECT + 1/0 + FROM sys.databases AS d + WHERE d.name = @OutputDatabaseName + ) /*If database is invalid raiserror and set bitcheck*/ + BEGIN + RAISERROR('Database Name (%s) for output of table is invalid please, Output to Table will not be performed', 0, 1, @OutputDatabaseName) WITH NOWAIT; + SET @OutputDatabaseCheck = -1; /* -1 invalid/false, 0 = good/true */ + END; + ELSE + BEGIN + SET @OutputDatabaseCheck = 0; - SELECT @ProductVersionMajor = SUBSTRING(@ProductVersion, 1, CHARINDEX('.', @ProductVersion) + 1), - @ProductVersionMinor = PARSENAME(CONVERT(VARCHAR(32), @ProductVersion), 2); + SELECT + @StringToExecute = + N'SELECT @r = o.name FROM ' + + @OutputDatabaseName + + N'.sys.objects AS o WHERE o.type_desc = N''USER_TABLE'' AND o.name = ' + + QUOTENAME + ( + @OutputTableName, + N'''' + ) + + N' AND o.schema_id = SCHEMA_ID(' + + QUOTENAME + ( + @OutputSchemaName, + N'''' + ) + + N');', + @StringToExecuteParams = + N'@r sysname OUTPUT'; + + IF @Debug = 1 BEGIN PRINT @StringToExecute; END; + EXEC sys.sp_executesql + @StringToExecute, + @StringToExecuteParams, + @r OUTPUT; + IF @Debug = 1 + BEGIN + RAISERROR('@r is set to: %s for schema name %s and table name %s', 0, 1, @r, @OutputSchemaName, @OutputTableName) WITH NOWAIT; + END; - IF @ProductVersionMajor < 11.0 + /*protection spells*/ + SELECT + @ObjectFullName = + QUOTENAME(@OutputDatabaseName) + + N'.' + + QUOTENAME(@OutputSchemaName) + + N'.' + + QUOTENAME(@OutputTableName), + @OutputDatabaseName = + QUOTENAME(@OutputDatabaseName), + @OutputTableName = + QUOTENAME(@OutputTableName), + @OutputSchemaName = + QUOTENAME(@OutputSchemaName); + + IF (@r IS NOT NULL) /*if it is not null, there is a table, so check for newly added columns*/ BEGIN - RAISERROR( - 'sp_BlitzLock will throw a bunch of angry errors on versions of SQL Server earlier than 2012.', - 0, - 1) WITH NOWAIT; - RETURN; + /* If the table doesn't have the new spid column, add it. See Github #3101. */ + SET @StringToExecute = + N'IF NOT EXISTS (SELECT 1/0 FROM ' + + @OutputDatabaseName + + N'.sys.all_columns AS o WHERE o.object_id = (OBJECT_ID(''' + + @ObjectFullName + + N''')) AND o.name = N''spid'') + /*Add spid column*/ + ALTER TABLE ' + + @ObjectFullName + + N' ADD spid smallint NULL;'; + + IF @Debug = 1 BEGIN PRINT @StringToExecute; END; + EXEC sys.sp_executesql + @StringToExecute; + + /* If the table doesn't have the new wait_resource column, add it. See Github #3101. */ + SET @StringToExecute = + N'IF NOT EXISTS (SELECT 1/0 FROM ' + + @OutputDatabaseName + + N'.sys.all_columns AS o WHERE o.object_id = (OBJECT_ID(''' + + @ObjectFullName + + N''')) AND o.name = N''wait_resource'') + /*Add wait_resource column*/ + ALTER TABLE ' + + @ObjectFullName + + N' ADD wait_resource nvarchar(MAX) NULL;'; + + IF @Debug = 1 BEGIN PRINT @StringToExecute; END; + EXEC sys.sp_executesql + @StringToExecute; + + /* If the table doesn't have the new client option column, add it. See Github #3101. */ + SET @StringToExecute = + N'IF NOT EXISTS (SELECT 1/0 FROM ' + + @OutputDatabaseName + + N'.sys.all_columns AS o WHERE o.object_id = (OBJECT_ID(''' + + @ObjectFullName + + N''')) AND o.name = N''client_option_1'') + /*Add wait_resource column*/ + ALTER TABLE ' + + @ObjectFullName + + N' ADD client_option_1 nvarchar(8000) NULL;'; + + IF @Debug = 1 BEGIN PRINT @StringToExecute; END; + EXEC sys.sp_executesql + @StringToExecute; + + /* If the table doesn't have the new client option column, add it. See Github #3101. */ + SET @StringToExecute = + N'IF NOT EXISTS (SELECT 1/0 FROM ' + + @OutputDatabaseName + + N'.sys.all_columns AS o WHERE o.object_id = (OBJECT_ID(''' + + @ObjectFullName + + N''')) AND o.name = N''client_option_2'') + /*Add wait_resource column*/ + ALTER TABLE ' + + @ObjectFullName + + N' ADD client_option_2 nvarchar(8000) NULL;'; + + IF @Debug = 1 BEGIN PRINT @StringToExecute; END; + EXEC sys.sp_executesql + @StringToExecute; + + /* If the table doesn't have the new lock mode column, add it. See Github #3101. */ + SET @StringToExecute = + N'IF NOT EXISTS (SELECT 1/0 FROM ' + + @OutputDatabaseName + + N'.sys.all_columns AS o WHERE o.object_id = (OBJECT_ID(''' + + @ObjectFullName + + N''')) AND o.name = N''lock_mode'') + /*Add wait_resource column*/ + ALTER TABLE ' + + @ObjectFullName + + N' ADD lock_mode nvarchar(256) NULL;'; + + IF @Debug = 1 BEGIN PRINT @StringToExecute; END; + EXEC sys.sp_executesql + @StringToExecute; + + /* If the table doesn't have the new status column, add it. See Github #3101. */ + SET @StringToExecute = + N'IF NOT EXISTS (SELECT 1/0 FROM ' + + @OutputDatabaseName + + N'.sys.all_columns AS o WHERE o.object_id = (OBJECT_ID(''' + + @ObjectFullName + + N''')) AND o.name = N''status'') + /*Add wait_resource column*/ + ALTER TABLE ' + + @ObjectFullName + + N' ADD status nvarchar(256) NULL;'; + + IF @Debug = 1 BEGIN PRINT @StringToExecute; END; + EXEC sys.sp_executesql + @StringToExecute; END; - - IF ((SELECT SERVERPROPERTY ('EDITION')) = 'SQL Azure' - AND - LOWER(@EventSessionPath) NOT LIKE 'http%') + ELSE /* end if @r is not null. if it is null there is no table, create it from above execution */ BEGIN - RAISERROR( - 'The default storage path doesn''t work in Azure SQLDB/Managed instances. -You need to use an Azure storage account, and the path has to look like this: https://StorageAccount.blob.core.windows.net/Container/FileName.xel', - 0, - 1) WITH NOWAIT; - RETURN; + SELECT + @StringToExecute = + N'USE ' + + @OutputDatabaseName + + N'; + CREATE TABLE ' + + @OutputSchemaName + + N'.' + + @OutputTableName + + N' ( + ServerName nvarchar(256), + deadlock_type nvarchar(256), + event_date datetime, + database_name nvarchar(256), + spid smallint, + deadlock_group nvarchar(256), + query xml, + object_names xml, + isolation_level nvarchar(256), + owner_mode nvarchar(256), + waiter_mode nvarchar(256), + lock_mode nvarchar(256), + transaction_count bigint, + client_option_1 varchar(2000), + client_option_2 varchar(2000), + login_name nvarchar(256), + host_name nvarchar(256), + client_app nvarchar(1024), + wait_time bigint, + wait_resource nvarchar(max), + priority smallint, + log_used bigint, + last_tran_started datetime, + last_batch_started datetime, + last_batch_completed datetime, + transaction_name nvarchar(256), + status nvarchar(256), + owner_waiter_type nvarchar(256), + owner_activity nvarchar(256), + owner_waiter_activity nvarchar(256), + owner_merging nvarchar(256), + owner_spilling nvarchar(256), + owner_waiting_to_close nvarchar(256), + waiter_waiter_type nvarchar(256), + waiter_owner_activity nvarchar(256), + waiter_waiter_activity nvarchar(256), + waiter_merging nvarchar(256), + waiter_spilling nvarchar(256), + waiter_waiting_to_close nvarchar(256), + deadlock_graph xml + )'; + + IF @Debug = 1 BEGIN PRINT @StringToExecute; END; + EXEC sys.sp_executesql + @StringToExecute; + + /*table created.*/ + SELECT + @StringToExecute = + N'SELECT @r = o.name FROM ' + + @OutputDatabaseName + + N'.sys.objects AS o + WHERE o.type_desc = N''USER_TABLE'' + AND o.name = N''BlitzLockFindings''', + @StringToExecuteParams = + N'@r sysname OUTPUT'; + + IF @Debug = 1 BEGIN PRINT @StringToExecute; END; + EXEC sys.sp_executesql + @StringToExecute, + @StringToExecuteParams, + @r OUTPUT; + + IF (@r IS NULL) /*if table does not exist*/ + BEGIN + SELECT + @OutputTableFindings = + QUOTENAME(N'BlitzLockFindings'), + @StringToExecute = + N'USE ' + + @OutputDatabaseName + + N'; + CREATE TABLE ' + + @OutputSchemaName + + N'.' + + @OutputTableFindings + + N' ( + ServerName nvarchar(256), + check_id INT, + database_name nvarchar(256), + object_name nvarchar(1000), + finding_group nvarchar(100), + finding nvarchar(4000) + );'; + + IF @Debug = 1 BEGIN PRINT @StringToExecute; END; + EXEC sys.sp_executesql + @StringToExecute; + END; END; + /*create synonym for deadlockfindings.*/ + IF EXISTS + ( + SELECT + 1/0 + FROM sys.objects AS o + WHERE o.name = N'DeadlockFindings' + AND o.type_desc = N'SYNONYM' + ) + BEGIN + RAISERROR('Found synonym DeadlockFindings, dropping', 0, 1) WITH NOWAIT; + DROP SYNONYM DeadlockFindings; + END; - IF @Top IS NULL - SET @Top = 2147483647; + RAISERROR('Creating synonym DeadlockFindings', 0, 1) WITH NOWAIT; + SET @StringToExecute = + N'CREATE SYNONYM DeadlockFindings FOR ' + + @OutputDatabaseName + + N'.' + + @OutputSchemaName + + N'.' + + @OutputTableFindings; + + IF @Debug = 1 BEGIN PRINT @StringToExecute; END; + EXEC sys.sp_executesql + @StringToExecute; + + /*create synonym for deadlock table.*/ + IF EXISTS + ( + SELECT + 1/0 + FROM sys.objects AS o + WHERE o.name = N'DeadLockTbl' + AND o.type_desc = N'SYNONYM' + ) + BEGIN + RAISERROR('Found synonym DeadLockTbl, dropping', 0, 1) WITH NOWAIT; + DROP SYNONYM DeadLockTbl; + END; - IF @StartDate IS NULL - SET @StartDate = '19000101'; + RAISERROR('Creating synonym DeadLockTbl', 0, 1) WITH NOWAIT; + SET @StringToExecute = + N'CREATE SYNONYM DeadLockTbl FOR ' + + @OutputDatabaseName + + N'.' + + @OutputSchemaName + + N'.' + + @OutputTableName; + + IF @Debug = 1 BEGIN PRINT @StringToExecute; END; + EXEC sys.sp_executesql + @StringToExecute; + END; + END; - IF @EndDate IS NULL - SET @EndDate = '99991231'; - + /* WITH ROWCOUNT doesn't work on Amazon RDS - see: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2037 */ + IF @RDS = 0 + BEGIN; + BEGIN TRY; + RAISERROR('@RDS = 0, updating #t with high row and page counts', 0, 1) WITH NOWAIT; + UPDATE STATISTICS + #t + WITH + ROWCOUNT = 9223372036854775807, + PAGECOUNT = 9223372036854775807; + END TRY + BEGIN CATCH; + /* Misleading error returned, if run without permissions to update statistics the error returned is "Cannot find object". + Catching specific error, and returning message with better info. If any other error is returned, then throw as normal */ + IF (ERROR_NUMBER() = 1088) + BEGIN; + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Cannot run UPDATE STATISTICS on a #temp table without db_owner or sysadmin permissions', 0, 1) WITH NOWAIT; + END; + ELSE + BEGIN; + THROW; + END; + END CATCH; + END; - IF OBJECT_ID('tempdb..#deadlock_data') IS NOT NULL - DROP TABLE #deadlock_data; + /*If @TargetSessionType, we need to figure out if it's ring buffer or event file*/ + /*Azure has differently named views, so we need to separate. Thanks, Azure.*/ - IF OBJECT_ID('tempdb..#deadlock_process') IS NOT NULL - DROP TABLE #deadlock_process; + IF + ( + @Azure = 0 + AND @TargetSessionType IS NULL + ) + BEGIN + RAISERROR('@TargetSessionType is NULL, assigning for non-Azure instance', 0, 1) WITH NOWAIT; + + SELECT TOP (1) + @TargetSessionType = t.target_name + FROM sys.dm_xe_sessions AS s + JOIN sys.dm_xe_session_targets AS t + ON s.address = t.event_session_address + WHERE s.name = @EventSessionName + AND t.target_name IN (N'event_file', N'ring_buffer') + ORDER BY t.target_name + OPTION(RECOMPILE); + + RAISERROR('@TargetSessionType assigned as %s for non-Azure', 0, 1, @TargetSessionType) WITH NOWAIT; + END; - IF OBJECT_ID('tempdb..#deadlock_stack') IS NOT NULL - DROP TABLE #deadlock_stack; + IF + ( + @Azure = 1 + AND @TargetSessionType IS NULL + ) + BEGIN + RAISERROR('@TargetSessionType is NULL, assigning for Azure instance', 0, 1) WITH NOWAIT; + + SELECT TOP (1) + @TargetSessionType = t.target_name + FROM sys.dm_xe_database_sessions AS s + JOIN sys.dm_xe_database_session_targets AS t + ON s.address = t.event_session_address + WHERE s.name = @EventSessionName + AND t.target_name IN (N'event_file', N'ring_buffer') + ORDER BY t.target_name + OPTION(RECOMPILE); + + RAISERROR('@TargetSessionType assigned as %s for Azure', 0, 1, @TargetSessionType) WITH NOWAIT; + END; - IF OBJECT_ID('tempdb..#deadlock_resource') IS NOT NULL - DROP TABLE #deadlock_resource; - IF OBJECT_ID('tempdb..#deadlock_owner_waiter') IS NOT NULL - DROP TABLE #deadlock_owner_waiter; + /*The system health stuff gets handled different from user extended events.*/ + /*These next sections deal with user events, dependent on target.*/ - IF OBJECT_ID('tempdb..#deadlock_findings') IS NOT NULL - DROP TABLE #deadlock_findings; + /*If ring buffers*/ + IF + ( + @TargetSessionType LIKE N'ring%' + AND @EventSessionName NOT LIKE N'system_health%' + ) + BEGIN + IF @Azure = 0 + BEGIN + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('@TargetSessionType is ring_buffer, inserting XML for non-Azure at %s', 0, 1, @d) WITH NOWAIT; - CREATE TABLE #deadlock_findings - ( - id INT IDENTITY(1, 1) PRIMARY KEY CLUSTERED, - check_id INT NOT NULL, - database_name NVARCHAR(256), - object_name NVARCHAR(1000), - finding_group NVARCHAR(100), - finding NVARCHAR(4000) - ); + INSERT + #x WITH(TABLOCKX) + ( + x + ) + SELECT + x = TRY_CAST(t.target_data AS xml) + FROM sys.dm_xe_session_targets AS t + JOIN sys.dm_xe_sessions AS s + ON s.address = t.event_session_address + WHERE s.name = @EventSessionName + AND t.target_name = N'ring_buffer' + OPTION(RECOMPILE); + + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + END; - DECLARE @d VARCHAR(40), @StringToExecute NVARCHAR(4000),@StringToExecuteParams NVARCHAR(500),@r NVARCHAR(200),@OutputTableFindings NVARCHAR(100),@DeadlockCount INT; - DECLARE @ServerName NVARCHAR(256) - DECLARE @OutputDatabaseCheck BIT; - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - SET @OutputTableFindings = '[BlitzLockFindings]' - SET @ServerName = (select @@ServerName) - if(@OutputDatabaseName is not null) - BEGIN --if databaseName is set do some sanity checks and put [] around def. - if( (select name from sys.databases where name=@OutputDatabaseName) is null ) --if database is invalid raiserror and set bitcheck - BEGIN - RAISERROR('Database Name for output of table is invalid please correct, Output to Table will not be preformed', 0, 1, @d) WITH NOWAIT; - set @OutputDatabaseCheck = -1 -- -1 invalid/false, 0 = good/true - END - ELSE - BEGIN - set @OutputDatabaseCheck = 0 - select @StringToExecute = N'select @r = name from ' + '' + @OutputDatabaseName + - '' + '.sys.objects where type_desc=''USER_TABLE'' and name=' + '''' + @OutputTableName + '''', - @StringToExecuteParams = N'@OutputDatabaseName NVARCHAR(200),@OutputTableName NVARCHAR(200),@r NVARCHAR(200) OUTPUT' - exec sp_executesql @StringToExecute,@StringToExecuteParams,@OutputDatabaseName,@OutputTableName,@r OUTPUT - --put covers around all before. - SELECT @OutputDatabaseName = QUOTENAME(@OutputDatabaseName), - @OutputTableName = QUOTENAME(@OutputTableName), - @OutputSchemaName = QUOTENAME(@OutputSchemaName) - if(@r is not null) --if it is not null, there is a table, so check for newly added columns - BEGIN - /* If the table doesn't have the new spid column, add it. See Github #3101. */ - SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableName; - SET @StringToExecute = N'IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns - WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + ''')) AND name = ''spid'') - ALTER TABLE ' + @ObjectFullName + N' ADD spid SMALLINT NULL;'; - EXEC(@StringToExecute); + IF @Azure = 1 + BEGIN + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('@TargetSessionType is ring_buffer, inserting XML for Azure at %s', 0, 1, @d) WITH NOWAIT; - /* If the table doesn't have the new wait_resource column, add it. See Github #3101. */ - SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableName; - SET @StringToExecute = N'IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns - WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + ''')) AND name = ''wait_resource'') - ALTER TABLE ' + @ObjectFullName + N' ADD wait_resource NVARCHAR(MAX) NULL;'; - EXEC(@StringToExecute); - END - ELSE --if(@r is not null) --if it is null there is no table, create it from above execution - BEGIN - select @StringToExecute = N'use ' + @OutputDatabaseName + ';create table ' + @OutputSchemaName + '.' + @OutputTableName + ' ( - ServerName NVARCHAR(256), - deadlock_type NVARCHAR(256), - event_date datetime, - database_name NVARCHAR(256), - spid SMALLINT, - deadlock_group NVARCHAR(256), - query XML, - object_names XML, - isolation_level NVARCHAR(256), - owner_mode NVARCHAR(256), - waiter_mode NVARCHAR(256), - transaction_count bigint, - login_name NVARCHAR(256), - host_name NVARCHAR(256), - client_app NVARCHAR(256), - wait_time BIGINT, - wait_resource NVARCHAR(max), - priority smallint, - log_used BIGINT, - last_tran_started datetime, - last_batch_started datetime, - last_batch_completed datetime, - transaction_name NVARCHAR(256), - owner_waiter_type NVARCHAR(256), - owner_activity NVARCHAR(256), - owner_waiter_activity NVARCHAR(256), - owner_merging NVARCHAR(256), - owner_spilling NVARCHAR(256), - owner_waiting_to_close NVARCHAR(256), - waiter_waiter_type NVARCHAR(256), - waiter_owner_activity NVARCHAR(256), - waiter_waiter_activity NVARCHAR(256), - waiter_merging NVARCHAR(256), - waiter_spilling NVARCHAR(256), - waiter_waiting_to_close NVARCHAR(256), - deadlock_graph XML)', - @StringToExecuteParams = N'@OutputDatabaseName NVARCHAR(200),@OutputSchemaName NVARCHAR(100),@OutputTableName NVARCHAR(200)' - exec sp_executesql @StringToExecute, @StringToExecuteParams,@OutputDatabaseName,@OutputSchemaName,@OutputTableName - --table created. - select @StringToExecute = N'select @r = name from ' + '' + @OutputDatabaseName + - '' + '.sys.objects where type_desc=''USER_TABLE'' and name=''BlitzLockFindings''', - @StringToExecuteParams = N'@OutputDatabaseName NVARCHAR(200),@r NVARCHAR(200) OUTPUT' - exec sp_executesql @StringToExecute,@StringToExecuteParams,@OutputDatabaseName,@r OUTPUT - if(@r is null) --if table does not excist - BEGIN - select @OutputTableFindings=N'[BlitzLockFindings]', - @StringToExecute = N'use ' + @OutputDatabaseName + ';create table ' + @OutputSchemaName + '.' + @OutputTableFindings + ' ( - ServerName NVARCHAR(256), - check_id INT, - database_name NVARCHAR(256), - object_name NVARCHAR(1000), - finding_group NVARCHAR(100), - finding NVARCHAR(4000))', - @StringToExecuteParams = N'@OutputDatabaseName NVARCHAR(200),@OutputSchemaName NVARCHAR(100),@OutputTableFindings NVARCHAR(200)' - exec sp_executesql @StringToExecute, @StringToExecuteParams, @OutputDatabaseName,@OutputSchemaName,@OutputTableFindings - - END + INSERT + #x WITH(TABLOCKX) + ( + x + ) + SELECT + x = TRY_CAST(t.target_data AS xml) + FROM sys.dm_xe_database_session_targets AS t + JOIN sys.dm_xe_database_sessions AS s + ON s.address = t.event_session_address + WHERE s.name = @EventSessionName + AND t.target_name = N'ring_buffer' + OPTION(RECOMPILE); + + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + END; + END; - END - --create synonym for deadlockfindings. - if((select name from sys.objects where name='DeadlockFindings' and type_desc='SYNONYM')IS NOT NULL) - BEGIN - RAISERROR('found synonym', 0, 1) WITH NOWAIT; - drop synonym DeadlockFindings; - END - set @StringToExecute = 'CREATE SYNONYM DeadlockFindings FOR ' + @OutputDatabaseName + '.' + @OutputSchemaName + '.' + @OutputTableFindings; - exec sp_executesql @StringToExecute - - --create synonym for deadlock table. - if((select name from sys.objects where name='DeadLockTbl' and type_desc='SYNONYM') IS NOT NULL) - BEGIN - drop SYNONYM DeadLockTbl; - END - set @StringToExecute = 'CREATE SYNONYM DeadLockTbl FOR ' + @OutputDatabaseName + '.' + @OutputSchemaName + '.' + @OutputTableName; - exec sp_executesql @StringToExecute - - END - END - - CREATE TABLE #t (id INT NOT NULL); + /*If event file*/ + IF + ( + @TargetSessionType LIKE N'event%' + AND @EventSessionName NOT LIKE N'system_health%' + ) + BEGIN + IF @Azure = 0 + BEGIN + RAISERROR('@TargetSessionType is event_file, assigning XML for non-Azure', 0, 1) WITH NOWAIT; - /* WITH ROWCOUNT doesn't work on Amazon RDS - see: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2037 */ - IF LEFT(CAST(SERVERPROPERTY('ComputerNamePhysicalNetBIOS') AS VARCHAR(8000)), 8) <> 'EC2AMAZ-' - AND LEFT(CAST(SERVERPROPERTY('MachineName') AS VARCHAR(8000)), 8) <> 'EC2AMAZ-' - AND db_id('rdsadmin') IS NULL - BEGIN; - BEGIN TRY; - UPDATE STATISTICS #t WITH ROWCOUNT = 100000000, PAGECOUNT = 100000000; - END TRY - BEGIN CATCH; - /* Misleading error returned, if run without permissions to update statistics the error returned is "Cannot find object". - Catching specific error, and returning message with better info. If any other error is returned, then throw as normal */ - IF (ERROR_NUMBER() = 1088) - BEGIN; - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Cannot run UPDATE STATISTICS on temp table without db_owner or sysadmin permissions %s', 0, 1, @d) WITH NOWAIT; - END; - ELSE - BEGIN; - THROW; - END; - END CATCH; - END; + SELECT + @SessionId = t.event_session_id, + @TargetSessionId = t.target_id + FROM sys.server_event_session_targets AS t + JOIN sys.server_event_sessions AS s + ON s.event_session_id = t.event_session_id + WHERE t.name = @TargetSessionType + AND s.name = @EventSessionName + OPTION(RECOMPILE); + + /*We get the file name automatically, here*/ + RAISERROR('Assigning @FileName...', 0, 1) WITH NOWAIT; + SELECT + @FileName = + CASE + WHEN f.file_name LIKE N'%.xel' + THEN REPLACE(f.file_name, N'.xel', N'*.xel') + ELSE f.file_name + N'*.xel' + END + FROM + ( + SELECT + file_name = + CONVERT(nvarchar(4000), f.value) + FROM sys.server_event_session_fields AS f + WHERE f.event_session_id = @SessionId + AND f.object_id = @TargetSessionId + AND f.name = N'filename' + ) AS f + OPTION(RECOMPILE); + END; - /*Grab the initial set of XML to parse*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Grab the initial set of XML to parse at %s', 0, 1, @d) WITH NOWAIT; - WITH xml - AS ( SELECT CONVERT(XML, event_data) AS deadlock_xml - FROM sys.fn_xe_file_target_read_file(@EventSessionPath, NULL, NULL, NULL) ) - SELECT ISNULL(xml.deadlock_xml, '') AS deadlock_xml - INTO #deadlock_data - FROM xml + IF @Azure = 1 + BEGIN + RAISERROR('@TargetSessionType is event_file, assigning XML for Azure', 0, 1) WITH NOWAIT; + SELECT + @SessionId = + t.event_session_address, + @TargetSessionId = + t.target_name + FROM sys.dm_xe_database_session_targets t + JOIN sys.dm_xe_database_sessions s + ON s.address = t.event_session_address + WHERE t.target_name = @TargetSessionType + AND s.name = @EventSessionName + OPTION(RECOMPILE); + + /*We get the file name automatically, here*/ + RAISERROR('Assigning @FileName...', 0, 1) WITH NOWAIT; + SELECT + @FileName = + CASE + WHEN f.file_name LIKE N'%.xel' + THEN REPLACE(f.file_name, N'.xel', N'*.xel') + ELSE f.file_name + N'*.xel' + END + FROM + ( + SELECT + file_name = + CONVERT(nvarchar(4000), f.value) + FROM sys.server_event_session_fields AS f + WHERE f.event_session_id = @SessionId + AND f.object_id = @TargetSessionId + AND f.name = N'filename' + ) AS f + OPTION(RECOMPILE); + END; + + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Reading for event_file %s', 0, 1, @FileName) WITH NOWAIT; + + INSERT + #x WITH(TABLOCKX) + ( + x + ) + SELECT + x = TRY_CAST(f.event_data AS xml) + FROM sys.fn_xe_file_target_read_file(@FileName, NULL, NULL, NULL) AS f LEFT JOIN #t AS t - ON 1 = 1 - CROSS APPLY xml.deadlock_xml.nodes('/event/@name') AS x(c) - WHERE 1 = 1 - AND x.c.exist('/event/@name[ .= "xml_deadlock_report"]') = 1 - AND CONVERT(datetime, SWITCHOFFSET(CONVERT(datetimeoffset, xml.deadlock_xml.value('(/event/@timestamp)[1]', 'datetime') ), DATENAME(TzOffset, SYSDATETIMEOFFSET()))) > @StartDate - AND CONVERT(datetime, SWITCHOFFSET(CONVERT(datetimeoffset, xml.deadlock_xml.value('(/event/@timestamp)[1]', 'datetime') ), DATENAME(TzOffset, SYSDATETIMEOFFSET()))) < @EndDate - ORDER BY xml.deadlock_xml.value('(/event/@timestamp)[1]', 'datetime') DESC - OPTION ( RECOMPILE ); + ON 1 = 1 + OPTION(RECOMPILE); - /*Optimization: if we got back more rows than @Top, remove them. This seems to be better than using @Top in the query above as that results in excessive memory grant*/ - SET @DeadlockCount = @@ROWCOUNT - IF( @Top < @DeadlockCount ) BEGIN - WITH T - AS ( - SELECT TOP ( @DeadlockCount - @Top) * - FROM #deadlock_data - ORDER BY #deadlock_data.deadlock_xml.value('(/event/@timestamp)[1]', 'datetime') ASC) - DELETE FROM T - END + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + END; + + /*The XML is parsed differently if it comes from the event file or ring buffer*/ + + /*If ring buffers*/ + IF + ( + @TargetSessionType LIKE N'ring%' + AND @EventSessionName NOT LIKE N'system_health%' + ) + BEGIN + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Inserting to #deadlock_data for ring buffer data', 0, 1) WITH NOWAIT; + + INSERT + #deadlock_data WITH(TABLOCKX) + ( + deadlock_xml + ) + SELECT + deadlock_xml = + e.x.query(N'.') + FROM #x AS x + LEFT JOIN #t AS t + ON 1 = 1 + CROSS APPLY x.x.nodes('/RingBufferTarget/event') AS e(x) + WHERE e.x.exist('@name[ .= "xml_deadlock_report"]') = 1 + AND e.x.exist('@timestamp[. >= sql:variable("@StartDate")]') = 1 + AND e.x.exist('@timestamp[. < sql:variable("@EndDate")]') = 1 + OPTION(RECOMPILE); + + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + END; + + /*If event file*/ + IF + ( + @TargetSessionType LIKE N'event_file%' + AND @EventSessionName NOT LIKE N'system_health%' + ) + BEGIN + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Inserting to #deadlock_data for event file data', 0, 1) WITH NOWAIT; + + INSERT + #deadlock_data WITH(TABLOCKX) + ( + deadlock_xml + ) + SELECT + deadlock_xml = + e.x.query('.') + FROM #x AS x + LEFT JOIN #t AS t + ON 1 = 1 + CROSS APPLY x.x.nodes('/event') AS e(x) + WHERE e.x.exist('/event/@name[ .= "xml_deadlock_report"]') = 1 + AND e.x.exist('/event/@timestamp[. >= sql:variable("@StartDate")]') = 1 + AND e.x.exist('/event/@timestamp[. < sql:variable("@EndDate")]') = 1 + OPTION(RECOMPILE); + + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + END; + + /*This section deals with event file*/ + IF + ( + @TargetSessionType LIKE N'event%' + AND @EventSessionName LIKE N'system_health%' + ) + BEGIN + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Grab the initial set of xml to parse at %s', 0, 1, @d) WITH NOWAIT; + + IF @Debug = 1 BEGIN SET STATISTICS XML ON; END; + + SELECT + xml.deadlock_xml + INTO #xml + FROM + ( + SELECT + deadlock_xml = + TRY_CAST(fx.event_data AS xml) + FROM sys.fn_xe_file_target_read_file('system_health*.xel', NULL, NULL, NULL) AS fx + LEFT JOIN #t AS t + ON 1 = 1 + ) AS xml + CROSS APPLY xml.deadlock_xml.nodes('/event') AS e(x) + WHERE e.x.exist('@name[ . = "xml_deadlock_report"]') = 1 + AND e.x.exist('@timestamp[. >= sql:variable("@StartDate")]') = 1 + AND e.x.exist('@timestamp[. < sql:variable("@EndDate")]') = 1 + OPTION(RECOMPILE); + + INSERT + #deadlock_data WITH(TABLOCKX) + SELECT + deadlock_xml = + xml.deadlock_xml + FROM #xml AS xml + LEFT JOIN #t AS t + ON 1 = 1 + WHERE xml.deadlock_xml IS NOT NULL + OPTION(RECOMPILE); + + IF @Debug = 1 BEGIN SET STATISTICS XML OFF; END; + + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + END; - /*Parse process and input buffer XML*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Parse process and input buffer XML %s', 0, 1, @d) WITH NOWAIT; - SELECT q.event_date, - q.victim_id, - CONVERT(BIT, q.is_parallel) AS is_parallel, - q.deadlock_graph, - q.id, - q.spid, - q.database_id, - q.priority, - q.log_used, - q.wait_resource, - q.wait_time, - q.transaction_name, - q.last_tran_started, - q.last_batch_started, - q.last_batch_completed, - q.lock_mode, - q.transaction_count, - q.client_app, - q.host_name, - q.login_name, - q.isolation_level, - q.process_xml, - ISNULL(ca2.ib.query('.'), '') AS input_buffer - INTO #deadlock_process - FROM ( SELECT dd.deadlock_xml, - CONVERT(DATETIME2(7), SWITCHOFFSET(CONVERT(datetimeoffset, dd.event_date ), DATENAME(TzOffset, SYSDATETIMEOFFSET()))) AS event_date, - dd.victim_id, - CONVERT(tinyint, dd.is_parallel) + CONVERT(tinyint, dd.is_parallel_batch) AS is_parallel, - dd.deadlock_graph, - ca.dp.value('@id', 'NVARCHAR(256)') AS id, - ca.dp.value('@spid', 'SMALLINT') AS spid, - ca.dp.value('@currentdb', 'BIGINT') AS database_id, - ca.dp.value('@priority', 'SMALLINT') AS priority, - ca.dp.value('@logused', 'BIGINT') AS log_used, - ca.dp.value('@waitresource', 'NVARCHAR(256)') AS wait_resource, - ca.dp.value('@waittime', 'BIGINT') AS wait_time, - ca.dp.value('@transactionname', 'NVARCHAR(256)') AS transaction_name, - ca.dp.value('@lasttranstarted', 'DATETIME2(7)') AS last_tran_started, - ca.dp.value('@lastbatchstarted', 'DATETIME2(7)') AS last_batch_started, - ca.dp.value('@lastbatchcompleted', 'DATETIME2(7)') AS last_batch_completed, - ca.dp.value('@lockMode', 'NVARCHAR(256)') AS lock_mode, - ca.dp.value('@trancount', 'BIGINT') AS transaction_count, - ca.dp.value('@clientapp', 'NVARCHAR(256)') AS client_app, - ca.dp.value('@hostname', 'NVARCHAR(256)') AS host_name, - ca.dp.value('@loginname', 'NVARCHAR(256)') AS login_name, - ca.dp.value('@isolationlevel', 'NVARCHAR(256)') AS isolation_level, - ISNULL(ca.dp.query('.'), '') AS process_xml - FROM ( SELECT d1.deadlock_xml, - d1.deadlock_xml.value('(event/@timestamp)[1]', 'DATETIME2') AS event_date, - d1.deadlock_xml.value('(//deadlock/victim-list/victimProcess/@id)[1]', 'NVARCHAR(256)') AS victim_id, - d1.deadlock_xml.exist('//deadlock/resource-list/exchangeEvent') AS is_parallel, - d1.deadlock_xml.exist('//deadlock/resource-list/SyncPoint') AS is_parallel_batch, - d1.deadlock_xml.query('/event/data/value/deadlock') AS deadlock_graph - FROM #deadlock_data AS d1 ) AS dd - CROSS APPLY dd.deadlock_xml.nodes('//deadlock/process-list/process') AS ca(dp) - WHERE (ca.dp.value('@currentdb', 'BIGINT') = DB_ID(@DatabaseName) OR @DatabaseName IS NULL) - AND (ca.dp.value('@clientapp', 'NVARCHAR(256)') = @AppName OR @AppName IS NULL) - AND (ca.dp.value('@hostname', 'NVARCHAR(256)') = @HostName OR @HostName IS NULL) - AND (ca.dp.value('@loginname', 'NVARCHAR(256)') = @LoginName OR @LoginName IS NULL) + /*Parse process and input buffer xml*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Initial Parse process and input buffer xml %s', 0, 1, @d) WITH NOWAIT; + + SELECT + d1.deadlock_xml, + event_date = d1.deadlock_xml.value('(event/@timestamp)[1]', 'datetime2'), + victim_id = d1.deadlock_xml.value('(//deadlock/victim-list/victimProcess/@id)[1]', 'nvarchar(256)'), + is_parallel = d1.deadlock_xml.exist('//deadlock/resource-list/exchangeEvent'), + is_parallel_batch = d1.deadlock_xml.exist('//deadlock/resource-list/SyncPoint'), + deadlock_graph = d1.deadlock_xml.query('/event/data/value/deadlock') + INTO #dd + FROM #deadlock_data AS d1 + LEFT JOIN #t AS t + ON 1 = 1 + OPTION(RECOMPILE); + + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Final Parse process and input buffer xml %s', 0, 1, @d) WITH NOWAIT; + + SELECT + q.event_date, + q.victim_id, + is_parallel = + CONVERT(bit, q.is_parallel), + q.deadlock_graph, + q.id, + q.spid, + q.database_id, + database_name = + ISNULL + ( + DB_NAME(q.database_id), + N'UNKNOWN' + ), + q.current_database_name, + q.priority, + q.log_used, + q.wait_resource, + q.wait_time, + q.transaction_name, + q.last_tran_started, + q.last_batch_started, + q.last_batch_completed, + q.lock_mode, + q.status, + q.transaction_count, + q.client_app, + q.host_name, + q.login_name, + q.isolation_level, + client_option_1 = + SUBSTRING + ( + CASE WHEN q.clientoption1 & 1 = 1 THEN ', DISABLE_DEF_CNST_CHECK' ELSE '' END + + CASE WHEN q.clientoption1 & 2 = 2 THEN ', IMPLICIT_TRANSACTIONS' ELSE '' END + + CASE WHEN q.clientoption1 & 4 = 4 THEN ', CURSOR_CLOSE_ON_COMMIT' ELSE '' END + + CASE WHEN q.clientoption1 & 8 = 8 THEN ', ANSI_WARNINGS' ELSE '' END + + CASE WHEN q.clientoption1 & 16 = 16 THEN ', ANSI_PADDING' ELSE '' END + + CASE WHEN q.clientoption1 & 32 = 32 THEN ', ANSI_NULLS' ELSE '' END + + CASE WHEN q.clientoption1 & 64 = 64 THEN ', ARITHABORT' ELSE '' END + + CASE WHEN q.clientoption1 & 128 = 128 THEN ', ARITHIGNORE' ELSE '' END + + CASE WHEN q.clientoption1 & 256 = 256 THEN ', QUOTED_IDENTIFIER' ELSE '' END + + CASE WHEN q.clientoption1 & 512 = 512 THEN ', NOCOUNT' ELSE '' END + + CASE WHEN q.clientoption1 & 1024 = 1024 THEN ', ANSI_NULL_DFLT_ON' ELSE '' END + + CASE WHEN q.clientoption1 & 2048 = 2048 THEN ', ANSI_NULL_DFLT_OFF' ELSE '' END + + CASE WHEN q.clientoption1 & 4096 = 4096 THEN ', CONCAT_NULL_YIELDS_NULL' ELSE '' END + + CASE WHEN q.clientoption1 & 8192 = 8192 THEN ', NUMERIC_ROUNDABORT' ELSE '' END + + CASE WHEN q.clientoption1 & 16384 = 16384 THEN ', XACT_ABORT' ELSE '' END, + 3, + 8000 + ), + client_option_2 = + SUBSTRING + ( + CASE WHEN q.clientoption2 & 1024 = 1024 THEN ', DB CHAINING' ELSE '' END + + CASE WHEN q.clientoption2 & 2048 = 2048 THEN ', NUMERIC ROUNDABORT' ELSE '' END + + CASE WHEN q.clientoption2 & 4096 = 4096 THEN ', ARITHABORT' ELSE '' END + + CASE WHEN q.clientoption2 & 8192 = 8192 THEN ', ANSI PADDING' ELSE '' END + + CASE WHEN q.clientoption2 & 16384 = 16384 THEN ', ANSI NULL DEFAULT' ELSE '' END + + CASE WHEN q.clientoption2 & 65536 = 65536 THEN ', CONCAT NULL YIELDS NULL' ELSE '' END + + CASE WHEN q.clientoption2 & 131072 = 131072 THEN ', RECURSIVE TRIGGERS' ELSE '' END + + CASE WHEN q.clientoption2 & 1048576 = 1048576 THEN ', DEFAULT TO LOCAL CURSOR' ELSE '' END + + CASE WHEN q.clientoption2 & 8388608 = 8388608 THEN ', QUOTED IDENTIFIER' ELSE '' END + + CASE WHEN q.clientoption2 & 16777216 = 16777216 THEN ', AUTO CREATE STATISTICS' ELSE '' END + + CASE WHEN q.clientoption2 & 33554432 = 33554432 THEN ', CURSOR CLOSE ON COMMIT' ELSE '' END + + CASE WHEN q.clientoption2 & 67108864 = 67108864 THEN ', ANSI NULLS' ELSE '' END + + CASE WHEN q.clientoption2 & 268435456 = 268435456 THEN ', ANSI WARNINGS' ELSE '' END + + CASE WHEN q.clientoption2 & 536870912 = 536870912 THEN ', FULL TEXT ENABLED' ELSE '' END + + CASE WHEN q.clientoption2 & 1073741824 = 1073741824 THEN ', AUTO UPDATE STATISTICS' ELSE '' END + + CASE WHEN q.clientoption2 & 1469283328 = 1469283328 THEN ', ALL SETTABLE OPTIONS' ELSE '' END, + 3, + 8000 + ), + q.process_xml + INTO #deadlock_process + FROM + ( + SELECT + dd.deadlock_xml, + event_date = + DATEADD + ( + MINUTE, + DATEDIFF(MINUTE, GETUTCDATE(), SYSDATETIME()), + dd.event_date + ), + dd.victim_id, + is_parallel = + CONVERT(tinyint, dd.is_parallel) + + CONVERT(tinyint, dd.is_parallel_batch), + dd.deadlock_graph, + id = ca.dp.value('@id', 'nvarchar(256)'), + spid = ca.dp.value('@spid', 'smallint'), + database_id = ca.dp.value('@currentdb', 'bigint'), + current_database_name = ca.dp.value('@currentdbname', 'nvarchar(256)'), + priority = ca.dp.value('@priority', 'smallint'), + log_used = ca.dp.value('@logused', 'bigint'), + wait_resource = ca.dp.value('@waitresource', 'nvarchar(256)'), + wait_time = ca.dp.value('@waittime', 'bigint'), + transaction_name = ca.dp.value('@transactionname', 'nvarchar(256)'), + last_tran_started = ca.dp.value('@lasttranstarted', 'datetime'), + last_batch_started = ca.dp.value('@lastbatchstarted', 'datetime'), + last_batch_completed = ca.dp.value('@lastbatchcompleted', 'datetime'), + lock_mode = ca.dp.value('@lockMode', 'nvarchar(256)'), + status = ca.dp.value('@status', 'nvarchar(256)'), + transaction_count = ca.dp.value('@trancount', 'bigint'), + client_app = ca.dp.value('@clientapp', 'nvarchar(1024)'), + host_name = ca.dp.value('@hostname', 'nvarchar(256)'), + login_name = ca.dp.value('@loginname', 'nvarchar(256)'), + isolation_level = ca.dp.value('@isolationlevel', 'nvarchar(256)'), + clientoption1 = ca.dp.value('@clientoption1', 'bigint'), + clientoption2 = ca.dp.value('@clientoption2', 'bigint'), + process_xml = ISNULL(ca.dp.query(N'.'), N'') + FROM #dd AS dd + CROSS APPLY dd.deadlock_xml.nodes('//deadlock/process-list/process') AS ca(dp) + WHERE (ca.dp.exist('@currentdb[. = sql:variable("@DatabaseId")]') = 1 OR @DatabaseName IS NULL) + AND (ca.dp.exist('@clientapp[. = sql:variable("@AppName")]') = 1 OR @AppName IS NULL) + AND (ca.dp.exist('@hostname[. = sql:variable("@HostName")]') = 1 OR @HostName IS NULL) + AND (ca.dp.exist('@loginname[. = sql:variable("@LoginName")]') = 1 OR @LoginName IS NULL) ) AS q - CROSS APPLY q.deadlock_xml.nodes('//deadlock/process-list/process/inputbuf') AS ca2(ib) - OPTION ( RECOMPILE ); + LEFT JOIN #t AS t + ON 1 = 1 + OPTION(RECOMPILE); - /*Parse execution stack XML*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Parse execution stack XML %s', 0, 1, @d) WITH NOWAIT; - SELECT DISTINCT - dp.id, - dp.event_date, - ca.dp.value('@procname', 'NVARCHAR(1000)') AS proc_name, - ca.dp.value('@sqlhandle', 'NVARCHAR(128)') AS sql_handle - INTO #deadlock_stack - FROM #deadlock_process AS dp + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + + /*Parse execution stack xml*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Parse execution stack xml %s', 0, 1, @d) WITH NOWAIT; + + SELECT DISTINCT + dp.id, + dp.event_date, + proc_name = ca.dp.value('@procname', 'nvarchar(1024)'), + sql_handle = ca.dp.value('@sqlhandle', 'nvarchar(131)') + INTO #deadlock_stack + FROM #deadlock_process AS dp CROSS APPLY dp.process_xml.nodes('//executionStack/frame') AS ca(dp) - WHERE (ca.dp.value('@procname', 'NVARCHAR(256)') = @StoredProcName OR @StoredProcName IS NULL) - OPTION ( RECOMPILE ); + WHERE (ca.dp.exist('@procname[. = sql:variable("@StoredProcName")]') = 1 OR @StoredProcName IS NULL) + OPTION(RECOMPILE); + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + /*Grab the full resource list*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); - /*Grab the full resource list*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); RAISERROR('Grab the full resource list %s', 0, 1, @d) WITH NOWAIT; - SELECT - CONVERT(DATETIME2(7), SWITCHOFFSET(CONVERT(datetimeoffset, dr.event_date), DATENAME(TzOffset, SYSDATETIMEOFFSET()))) AS event_date, - dr.victim_id, - dr.resource_xml - INTO #deadlock_resource - FROM + + SELECT + event_date = + DATEADD + ( + MINUTE, + DATEDIFF + ( + MINUTE, + GETUTCDATE(), + SYSDATETIME() + ), + dr.event_date + ), + dr.victim_id, + dr.resource_xml + INTO + #deadlock_resource + FROM ( - SELECT dd.deadlock_xml.value('(event/@timestamp)[1]', 'DATETIME2') AS event_date, - dd.deadlock_xml.value('(//deadlock/victim-list/victimProcess/@id)[1]', 'NVARCHAR(256)') AS victim_id, - ISNULL(ca.dp.query('.'), '') AS resource_xml - FROM #deadlock_data AS dd - CROSS APPLY dd.deadlock_xml.nodes('//deadlock/resource-list') AS ca(dp) + SELECT + event_date = dd.deadlock_xml.value('(event/@timestamp)[1]', 'datetime2'), + victim_id = dd.deadlock_xml.value('(//deadlock/victim-list/victimProcess/@id)[1]', 'nvarchar(256)'), + resource_xml = ISNULL(ca.dp.query(N'.'), N'') + FROM #deadlock_data AS dd + CROSS APPLY dd.deadlock_xml.nodes('//deadlock/resource-list') AS ca(dp) ) AS dr - OPTION ( RECOMPILE ); + OPTION(RECOMPILE); + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - /*Parse object locks*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + /*Parse object locks*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Parse object locks %s', 0, 1, @d) WITH NOWAIT; - SELECT DISTINCT - ca.event_date, - ca.database_id, - ca.object_name, - ca.lock_mode, - ca.index_name, - ca.associatedObjectId, - w.l.value('@id', 'NVARCHAR(256)') AS waiter_id, - w.l.value('@mode', 'NVARCHAR(256)') AS waiter_mode, - o.l.value('@id', 'NVARCHAR(256)') AS owner_id, - o.l.value('@mode', 'NVARCHAR(256)') AS owner_mode, - N'OBJECT' AS lock_type - INTO #deadlock_owner_waiter - FROM ( - SELECT dr.event_date, - ca.dr.value('@dbid', 'BIGINT') AS database_id, - ca.dr.value('@objectname', 'NVARCHAR(256)') AS object_name, - ca.dr.value('@mode', 'NVARCHAR(256)') AS lock_mode, - ca.dr.value('@indexname', 'NVARCHAR(256)') AS index_name, - ca.dr.value('@associatedObjectId', 'BIGINT') AS associatedObjectId, - ca.dr.query('.') AS dr - FROM #deadlock_resource AS dr - CROSS APPLY dr.resource_xml.nodes('//resource-list/objectlock') AS ca(dr) - ) AS ca + + SELECT DISTINCT + ca.event_date, + ca.database_id, + database_name = + ISNULL + ( + DB_NAME(ca.database_id), + N'UNKNOWN' + ), + object_name = + REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE( + REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE( + REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE( + ca.object_name COLLATE Latin1_General_BIN2, + NCHAR(31), N'?'), NCHAR(30), N'?'), NCHAR(29), N'?'), NCHAR(28), N'?'), NCHAR(27), N'?'), + NCHAR(26), N'?'), NCHAR(25), N'?'), NCHAR(24), N'?'), NCHAR(23), N'?'), NCHAR(22), N'?'), + NCHAR(21), N'?'), NCHAR(20), N'?'), NCHAR(19), N'?'), NCHAR(18), N'?'), NCHAR(17), N'?'), + NCHAR(16), N'?'), NCHAR(15), N'?'), NCHAR(14), N'?'), NCHAR(12), N'?'), NCHAR(11), N'?'), + NCHAR(8), N'?'), NCHAR(7), N'?'), NCHAR(6), N'?'), NCHAR(5), N'?'), NCHAR(4), N'?'), + NCHAR(3), N'?'), NCHAR(2), N'?'), NCHAR(1), N'?'), NCHAR(0), N'?'), + ca.lock_mode, + ca.index_name, + ca.associatedObjectId, + waiter_id = w.l.value('@id', 'nvarchar(256)'), + waiter_mode = w.l.value('@mode', 'nvarchar(256)'), + owner_id = o.l.value('@id', 'nvarchar(256)'), + owner_mode = o.l.value('@mode', 'nvarchar(256)'), + lock_type = N'OBJECT' + INTO #deadlock_owner_waiter + FROM + ( + SELECT + dr.event_date, + database_id = ca.dr.value('@dbid', 'bigint'), + object_name = ca.dr.value('@objectname', 'nvarchar(1024)'), + lock_mode = ca.dr.value('@mode', 'nvarchar(256)'), + index_name = ca.dr.value('@indexname', 'nvarchar(256)'), + associatedObjectId = ca.dr.value('@associatedObjectId', 'bigint'), + dr = ca.dr.query('.') + FROM #deadlock_resource AS dr + CROSS APPLY dr.resource_xml.nodes('//resource-list/objectlock') AS ca(dr) + WHERE (ca.dr.exist('@objectname[. = sql:variable("@ObjectName")]') = 1 OR @ObjectName IS NULL) + ) AS ca CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l) CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l) - WHERE (ca.object_name = @ObjectName OR @ObjectName IS NULL) - OPTION ( RECOMPILE ); - + OPTION(RECOMPILE); + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - /*Parse page locks*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + /*Parse page locks*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Parse page locks %s', 0, 1, @d) WITH NOWAIT; - INSERT #deadlock_owner_waiter WITH(TABLOCKX) - SELECT DISTINCT - ca.event_date, - ca.database_id, - ca.object_name, - ca.lock_mode, - ca.index_name, - ca.associatedObjectId, - w.l.value('@id', 'NVARCHAR(256)') AS waiter_id, - w.l.value('@mode', 'NVARCHAR(256)') AS waiter_mode, - o.l.value('@id', 'NVARCHAR(256)') AS owner_id, - o.l.value('@mode', 'NVARCHAR(256)') AS owner_mode, - N'PAGE' AS lock_type - FROM ( - SELECT dr.event_date, - ca.dr.value('@dbid', 'BIGINT') AS database_id, - ca.dr.value('@objectname', 'NVARCHAR(256)') AS object_name, - ca.dr.value('@mode', 'NVARCHAR(256)') AS lock_mode, - ca.dr.value('@indexname', 'NVARCHAR(256)') AS index_name, - ca.dr.value('@associatedObjectId', 'BIGINT') AS associatedObjectId, - ca.dr.query('.') AS dr - FROM #deadlock_resource AS dr - CROSS APPLY dr.resource_xml.nodes('//resource-list/pagelock') AS ca(dr) - ) AS ca + + INSERT + #deadlock_owner_waiter WITH(TABLOCKX) + SELECT DISTINCT + ca.event_date, + ca.database_id, + database_name = + ISNULL + ( + DB_NAME(ca.database_id), + N'UNKNOWN' + ), + ca.object_name, + ca.lock_mode, + ca.index_name, + ca.associatedObjectId, + waiter_id = w.l.value('@id', 'nvarchar(256)'), + waiter_mode = w.l.value('@mode', 'nvarchar(256)'), + owner_id = o.l.value('@id', 'nvarchar(256)'), + owner_mode = o.l.value('@mode', 'nvarchar(256)'), + lock_type = N'PAGE' + FROM + ( + SELECT + dr.event_date, + database_id = ca.dr.value('@dbid', 'bigint'), + object_name = ca.dr.value('@objectname', 'nvarchar(1024)'), + lock_mode = ca.dr.value('@mode', 'nvarchar(256)'), + index_name = ca.dr.value('@indexname', 'nvarchar(256)'), + associatedObjectId = ca.dr.value('@associatedObjectId', 'bigint'), + dr = ca.dr.query('.') + FROM #deadlock_resource AS dr + CROSS APPLY dr.resource_xml.nodes('//resource-list/pagelock') AS ca(dr) + WHERE (ca.dr.exist('@objectname[. = sql:variable("@ObjectName")]') = 1 OR @ObjectName IS NULL) + ) AS ca CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l) CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l) - OPTION ( RECOMPILE ); + OPTION(RECOMPILE); + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - /*Parse key locks*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + /*Parse key locks*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Parse key locks %s', 0, 1, @d) WITH NOWAIT; - INSERT #deadlock_owner_waiter WITH(TABLOCKX) - SELECT DISTINCT - ca.event_date, - ca.database_id, - ca.object_name, - ca.lock_mode, - ca.index_name, - ca.associatedObjectId, - w.l.value('@id', 'NVARCHAR(256)') AS waiter_id, - w.l.value('@mode', 'NVARCHAR(256)') AS waiter_mode, - o.l.value('@id', 'NVARCHAR(256)') AS owner_id, - o.l.value('@mode', 'NVARCHAR(256)') AS owner_mode, - N'KEY' AS lock_type - FROM ( - SELECT dr.event_date, - ca.dr.value('@dbid', 'BIGINT') AS database_id, - ca.dr.value('@objectname', 'NVARCHAR(256)') AS object_name, - ca.dr.value('@mode', 'NVARCHAR(256)') AS lock_mode, - ca.dr.value('@indexname', 'NVARCHAR(256)') AS index_name, - ca.dr.value('@associatedObjectId', 'BIGINT') AS associatedObjectId, - ca.dr.query('.') AS dr - FROM #deadlock_resource AS dr - CROSS APPLY dr.resource_xml.nodes('//resource-list/keylock') AS ca(dr) - ) AS ca + + INSERT + #deadlock_owner_waiter WITH(TABLOCKX) + SELECT DISTINCT + ca.event_date, + ca.database_id, + database_name = + ISNULL + ( + DB_NAME(ca.database_id), + N'UNKNOWN' + ), + ca.object_name, + ca.lock_mode, + ca.index_name, + ca.associatedObjectId, + waiter_id = w.l.value('@id', 'nvarchar(256)'), + waiter_mode = w.l.value('@mode', 'nvarchar(256)'), + owner_id = o.l.value('@id', 'nvarchar(256)'), + owner_mode = o.l.value('@mode', 'nvarchar(256)'), + lock_type = N'KEY' + FROM + ( + SELECT + dr.event_date, + database_id = ca.dr.value('@dbid', 'bigint'), + object_name = ca.dr.value('@objectname', 'nvarchar(1024)'), + lock_mode = ca.dr.value('@mode', 'nvarchar(256)'), + index_name = ca.dr.value('@indexname', 'nvarchar(256)'), + associatedObjectId = ca.dr.value('@associatedObjectId', 'bigint'), + dr = ca.dr.query('.') + FROM #deadlock_resource AS dr + CROSS APPLY dr.resource_xml.nodes('//resource-list/keylock') AS ca(dr) + WHERE (ca.dr.exist('@objectname[. = sql:variable("@ObjectName")]') = 1 OR @ObjectName IS NULL) + ) AS ca CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l) CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l) - OPTION ( RECOMPILE ); + OPTION(RECOMPILE); + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - /*Parse RID locks*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + /*Parse RID locks*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Parse RID locks %s', 0, 1, @d) WITH NOWAIT; - INSERT #deadlock_owner_waiter WITH(TABLOCKX) - SELECT DISTINCT - ca.event_date, - ca.database_id, - ca.object_name, - ca.lock_mode, - ca.index_name, - ca.associatedObjectId, - w.l.value('@id', 'NVARCHAR(256)') AS waiter_id, - w.l.value('@mode', 'NVARCHAR(256)') AS waiter_mode, - o.l.value('@id', 'NVARCHAR(256)') AS owner_id, - o.l.value('@mode', 'NVARCHAR(256)') AS owner_mode, - N'RID' AS lock_type - FROM ( - SELECT dr.event_date, - ca.dr.value('@dbid', 'BIGINT') AS database_id, - ca.dr.value('@objectname', 'NVARCHAR(256)') AS object_name, - ca.dr.value('@mode', 'NVARCHAR(256)') AS lock_mode, - ca.dr.value('@indexname', 'NVARCHAR(256)') AS index_name, - ca.dr.value('@associatedObjectId', 'BIGINT') AS associatedObjectId, - ca.dr.query('.') AS dr - FROM #deadlock_resource AS dr - CROSS APPLY dr.resource_xml.nodes('//resource-list/ridlock') AS ca(dr) - ) AS ca + + INSERT + #deadlock_owner_waiter WITH(TABLOCKX) + SELECT DISTINCT + ca.event_date, + ca.database_id, + database_name = + ISNULL + ( + DB_NAME(ca.database_id), + N'UNKNOWN' + ), + ca.object_name, + ca.lock_mode, + ca.index_name, + ca.associatedObjectId, + waiter_id = w.l.value('@id', 'nvarchar(256)'), + waiter_mode = w.l.value('@mode', 'nvarchar(256)'), + owner_id = o.l.value('@id', 'nvarchar(256)'), + owner_mode = o.l.value('@mode', 'nvarchar(256)'), + lock_type = N'RID' + FROM + ( + SELECT + dr.event_date, + database_id = ca.dr.value('@dbid', 'bigint'), + object_name = ca.dr.value('@objectname', 'nvarchar(1024)'), + lock_mode = ca.dr.value('@mode', 'nvarchar(256)'), + index_name = ca.dr.value('@indexname', 'nvarchar(256)'), + associatedObjectId = ca.dr.value('@associatedObjectId', 'bigint'), + dr = ca.dr.query('.') + FROM #deadlock_resource AS dr + CROSS APPLY dr.resource_xml.nodes('//resource-list/ridlock') AS ca(dr) + WHERE (ca.dr.exist('@objectname[. = sql:variable("@ObjectName")]') = 1 OR @ObjectName IS NULL) + ) AS ca CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l) CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l) - OPTION ( RECOMPILE ); + OPTION(RECOMPILE); + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - /*Parse row group locks*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + /*Parse row group locks*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Parse row group locks %s', 0, 1, @d) WITH NOWAIT; - INSERT #deadlock_owner_waiter WITH(TABLOCKX) - SELECT DISTINCT - ca.event_date, - ca.database_id, - ca.object_name, - ca.lock_mode, - ca.index_name, - ca.associatedObjectId, - w.l.value('@id', 'NVARCHAR(256)') AS waiter_id, - w.l.value('@mode', 'NVARCHAR(256)') AS waiter_mode, - o.l.value('@id', 'NVARCHAR(256)') AS owner_id, - o.l.value('@mode', 'NVARCHAR(256)') AS owner_mode, - N'ROWGROUP' AS lock_type - FROM ( - SELECT dr.event_date, - ca.dr.value('@dbid', 'BIGINT') AS database_id, - ca.dr.value('@objectname', 'NVARCHAR(256)') AS object_name, - ca.dr.value('@mode', 'NVARCHAR(256)') AS lock_mode, - ca.dr.value('@indexname', 'NVARCHAR(256)') AS index_name, - ca.dr.value('@associatedObjectId', 'BIGINT') AS associatedObjectId, - ca.dr.query('.') AS dr - FROM #deadlock_resource AS dr - CROSS APPLY dr.resource_xml.nodes('//resource-list/rowgrouplock') AS ca(dr) - ) AS ca + + INSERT + #deadlock_owner_waiter WITH(TABLOCKX) + SELECT DISTINCT + ca.event_date, + ca.database_id, + database_name = + ISNULL + ( + DB_NAME(ca.database_id), + N'UNKNOWN' + ), + ca.object_name, + ca.lock_mode, + ca.index_name, + ca.associatedObjectId, + waiter_id = w.l.value('@id', 'nvarchar(256)'), + waiter_mode = w.l.value('@mode', 'nvarchar(256)'), + owner_id = o.l.value('@id', 'nvarchar(256)'), + owner_mode = o.l.value('@mode', 'nvarchar(256)'), + lock_type = N'ROWGROUP' + FROM + ( + SELECT + dr.event_date, + database_id = ca.dr.value('@dbid', 'bigint'), + object_name = ca.dr.value('@objectname', 'nvarchar(1024)'), + lock_mode = ca.dr.value('@mode', 'nvarchar(256)'), + index_name = ca.dr.value('@indexname', 'nvarchar(256)'), + associatedObjectId = ca.dr.value('@associatedObjectId', 'bigint'), + dr = ca.dr.query('.') + FROM #deadlock_resource AS dr + CROSS APPLY dr.resource_xml.nodes('//resource-list/rowgrouplock') AS ca(dr) + WHERE (ca.dr.exist('@objectname[. = sql:variable("@ObjectName")]') = 1 OR @ObjectName IS NULL) + ) AS ca CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l) CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l) - OPTION ( RECOMPILE ); + OPTION(RECOMPILE); - UPDATE d - SET d.index_name = d.object_name - + '.HEAP' - FROM #deadlock_owner_waiter AS d - WHERE lock_type IN (N'HEAP', N'RID') - OPTION(RECOMPILE); + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Fixing heaps in #deadlock_owner_waiter %s', 0, 1, @d) WITH NOWAIT; + + UPDATE + d + SET + d.index_name = + d.object_name + N'.HEAP' + FROM #deadlock_owner_waiter AS d + WHERE d.lock_type IN + ( + N'HEAP', + N'RID' + ) + OPTION(RECOMPILE); + + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - /*Parse parallel deadlocks*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + /*Parse parallel deadlocks*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Parse parallel deadlocks %s', 0, 1, @d) WITH NOWAIT; - SELECT DISTINCT - ca.id, - ca.event_date, - ca.wait_type, - ca.node_id, - ca.waiter_type, - ca.owner_activity, - ca.waiter_activity, - ca.merging, - ca.spilling, - ca.waiting_to_close, - w.l.value('@id', 'NVARCHAR(256)') AS waiter_id, - o.l.value('@id', 'NVARCHAR(256)') AS owner_id - INTO #deadlock_resource_parallel - FROM ( - SELECT dr.event_date, - ca.dr.value('@id', 'NVARCHAR(256)') AS id, - ca.dr.value('@WaitType', 'NVARCHAR(256)') AS wait_type, - ca.dr.value('@nodeId', 'BIGINT') AS node_id, - /* These columns are in 2017 CU5 ONLY */ - ca.dr.value('@waiterType', 'NVARCHAR(256)') AS waiter_type, - ca.dr.value('@ownerActivity', 'NVARCHAR(256)') AS owner_activity, - ca.dr.value('@waiterActivity', 'NVARCHAR(256)') AS waiter_activity, - ca.dr.value('@merging', 'NVARCHAR(256)') AS merging, - ca.dr.value('@spilling', 'NVARCHAR(256)') AS spilling, - ca.dr.value('@waitingToClose', 'NVARCHAR(256)') AS waiting_to_close, - /* */ - ca.dr.query('.') AS dr - FROM #deadlock_resource AS dr - CROSS APPLY dr.resource_xml.nodes('//resource-list/exchangeEvent') AS ca(dr) - ) AS ca - CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l) - CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l) - OPTION ( RECOMPILE ); - - - /*Get rid of parallel noise*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + + SELECT DISTINCT + ca.id, + ca.event_date, + ca.wait_type, + ca.node_id, + ca.waiter_type, + ca.owner_activity, + ca.waiter_activity, + ca.merging, + ca.spilling, + ca.waiting_to_close, + waiter_id = w.l.value('@id', 'nvarchar(256)'), + owner_id = o.l.value('@id', 'nvarchar(256)') + INTO #deadlock_resource_parallel + FROM + ( + SELECT + dr.event_date, + id = ca.dr.value('@id', 'nvarchar(256)'), + wait_type = ca.dr.value('@WaitType', 'nvarchar(256)'), + node_id = ca.dr.value('@nodeId', 'bigint'), + /* These columns are in 2017 CU5+ ONLY */ + waiter_type = ca.dr.value('@waiterType', 'nvarchar(256)'), + owner_activity = ca.dr.value('@ownerActivity', 'nvarchar(256)'), + waiter_activity = ca.dr.value('@waiterActivity', 'nvarchar(256)'), + merging = ca.dr.value('@merging', 'nvarchar(256)'), + spilling = ca.dr.value('@spilling', 'nvarchar(256)'), + waiting_to_close = ca.dr.value('@waitingToClose', 'nvarchar(256)'), + /* These columns are in 2017 CU5+ ONLY */ + dr = ca.dr.query('.') + FROM #deadlock_resource AS dr + CROSS APPLY dr.resource_xml.nodes('//resource-list/exchangeEvent') AS ca(dr) + ) AS ca + CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l) + CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l) + OPTION(RECOMPILE); + + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + + /*Get rid of parallel noise*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Get rid of parallel noise %s', 0, 1, @d) WITH NOWAIT; - WITH c - AS - ( - SELECT *, ROW_NUMBER() OVER ( PARTITION BY drp.owner_id, drp.waiter_id ORDER BY drp.event_date ) AS rn - FROM #deadlock_resource_parallel AS drp - ) - DELETE FROM c - WHERE c.rn > 1 - OPTION ( RECOMPILE ); + WITH + c AS + ( + SELECT + *, + rn = + ROW_NUMBER() OVER + ( + PARTITION BY + drp.owner_id, + drp.waiter_id + ORDER BY + drp.event_date + ) + FROM #deadlock_resource_parallel AS drp + ) + DELETE + FROM c + WHERE c.rn > 1 + OPTION(RECOMPILE); + + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - /*Get rid of nonsense*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + /*Get rid of nonsense*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Get rid of nonsense %s', 0, 1, @d) WITH NOWAIT; - DELETE dow - FROM #deadlock_owner_waiter AS dow - WHERE dow.owner_id = dow.waiter_id - OPTION ( RECOMPILE ); - /*Add some nonsense*/ - ALTER TABLE #deadlock_process - ADD waiter_mode NVARCHAR(256), - owner_mode NVARCHAR(256), - is_victim AS CONVERT(BIT, CASE WHEN id = victim_id THEN 1 ELSE 0 END); + DELETE dow + FROM #deadlock_owner_waiter AS dow + WHERE dow.owner_id = dow.waiter_id + OPTION(RECOMPILE); + + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + + /*Add some nonsense*/ + ALTER TABLE + #deadlock_process + ADD + waiter_mode nvarchar(256), + owner_mode nvarchar(256), + is_victim AS + CONVERT + ( + bit, + CASE + WHEN id = victim_id + THEN 1 + ELSE 0 + END + ) PERSISTED; - /*Update some nonsense*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + /*Update some nonsense*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Update some nonsense part 1 %s', 0, 1, @d) WITH NOWAIT; - UPDATE dp - SET dp.owner_mode = dow.owner_mode - FROM #deadlock_process AS dp - JOIN #deadlock_owner_waiter AS dow - ON dp.id = dow.owner_id - AND dp.event_date = dow.event_date - WHERE dp.is_victim = 0 - OPTION ( RECOMPILE ); - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + UPDATE + dp + SET + dp.owner_mode = dow.owner_mode + FROM #deadlock_process AS dp + JOIN #deadlock_owner_waiter AS dow + ON dp.id = dow.owner_id + AND dp.event_date = dow.event_date + WHERE dp.is_victim = 0 + OPTION(RECOMPILE); + + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + + SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Update some nonsense part 2 %s', 0, 1, @d) WITH NOWAIT; - UPDATE dp - SET dp.waiter_mode = dow.waiter_mode - FROM #deadlock_process AS dp - JOIN #deadlock_owner_waiter AS dow - ON dp.victim_id = dow.waiter_id - AND dp.event_date = dow.event_date - WHERE dp.is_victim = 1 - OPTION ( RECOMPILE ); - /*Get Agent Job and Step names*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + UPDATE + dp + SET + dp.waiter_mode = dow.waiter_mode + FROM #deadlock_process AS dp + JOIN #deadlock_owner_waiter AS dow + ON dp.victim_id = dow.waiter_id + AND dp.event_date = dow.event_date + WHERE dp.is_victim = 1 + OPTION(RECOMPILE); + + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + + /*Get Agent Job and Step names*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Get Agent Job and Step names %s', 0, 1, @d) WITH NOWAIT; - SELECT *, - CONVERT(UNIQUEIDENTIFIER, - CONVERT(XML, '').value('xs:hexBinary(substring(sql:column("x.job_id"), 0) )', 'BINARY(16)') - ) AS job_id_guid + + SELECT + x.event_date, + x.victim_id, + x.id, + x.database_id, + x.client_app, + x.job_id, + x.step_id, + job_id_guid = + CONVERT + ( + uniqueidentifier, + TRY_CAST + ( + N'' + AS xml + ).value('xs:hexBinary(substring(sql:column("x.job_id"), 0))', 'binary(16)') + ) INTO #agent_job - FROM ( - SELECT dp.event_date, - dp.victim_id, - dp.id, - dp.database_id, - dp.client_app, - SUBSTRING(dp.client_app, - CHARINDEX('0x', dp.client_app) + LEN('0x'), - 32 - ) AS job_id, - SUBSTRING(dp.client_app, - CHARINDEX(': Step ', dp.client_app) + LEN(': Step '), - CHARINDEX(')', dp.client_app, CHARINDEX(': Step ', dp.client_app)) - - (CHARINDEX(': Step ', dp.client_app) - + LEN(': Step ')) - ) AS step_id + FROM + ( + SELECT + dp.event_date, + dp.victim_id, + dp.id, + dp.database_id, + dp.client_app, + job_id = + SUBSTRING + ( + dp.client_app, + CHARINDEX(N'0x', dp.client_app) + LEN(N'0x'), + 32 + ), + step_id = + SUBSTRING + ( + dp.client_app, + CHARINDEX(N': Step ', dp.client_app) + LEN(N': Step '), + CHARINDEX(N')', dp.client_app, CHARINDEX(N': Step ', dp.client_app)) - + (CHARINDEX(N': Step ', dp.client_app) + LEN(N': Step ')) + ) FROM #deadlock_process AS dp - WHERE dp.client_app LIKE 'SQLAgent - %' - AND dp.client_app <> 'SQLAgent - Initial Boot Probe' + WHERE dp.client_app LIKE N'SQLAgent - %' + AND dp.client_app <> N'SQLAgent - Initial Boot Probe' ) AS x - OPTION ( RECOMPILE ); + OPTION(RECOMPILE); + ALTER TABLE + #agent_job + ADD + job_name nvarchar(256), + step_name nvarchar(256); - ALTER TABLE #agent_job ADD job_name NVARCHAR(256), - step_name NVARCHAR(256); + IF + ( + @Azure = 0 + AND @RDS = 0 + ) + BEGIN + SET @StringToExecute = + N' + UPDATE + aj + SET + aj.job_name = j.name, + aj.step_name = s.step_name + FROM msdb.dbo.sysjobs AS j + JOIN msdb.dbo.sysjobsteps AS s + ON j.job_id = s.job_id + JOIN #agent_job AS aj + ON aj.job_id_guid = j.job_id + AND aj.step_id = s.step_id + OPTION(RECOMPILE); + '; + + IF @Debug = 1 BEGIN PRINT @StringToExecute; END; + EXEC sys.sp_executesql + @StringToExecute; - IF SERVERPROPERTY('EngineEdition') NOT IN (5, 6) /* Azure SQL DB doesn't support querying jobs */ - AND NOT (LEFT(CAST(SERVERPROPERTY('ComputerNamePhysicalNetBIOS') AS VARCHAR(8000)), 8) = 'EC2AMAZ-' /* Neither does Amazon RDS Express Edition */ - AND LEFT(CAST(SERVERPROPERTY('MachineName') AS VARCHAR(8000)), 8) = 'EC2AMAZ-' - AND db_id('rdsadmin') IS NOT NULL - AND EXISTS(SELECT * FROM master.sys.all_objects WHERE name IN ('rds_startup_tasks', 'rds_help_revlogin', 'rds_hexadecimal', 'rds_failover_tracking', 'rds_database_tracking', 'rds_track_change')) - ) - BEGIN - SET @StringToExecute = N'UPDATE aj - SET aj.job_name = j.name, - aj.step_name = s.step_name - FROM msdb.dbo.sysjobs AS j - JOIN msdb.dbo.sysjobsteps AS s - ON j.job_id = s.job_id - JOIN #agent_job AS aj - ON aj.job_id_guid = j.job_id - AND aj.step_id = s.step_id - OPTION ( RECOMPILE );'; - EXEC(@StringToExecute); - END + END; - UPDATE dp - SET dp.client_app = - CASE WHEN dp.client_app LIKE N'SQLAgent - %' - THEN N'SQLAgent - Job: ' - + aj.job_name - + N' Step: ' - + aj.step_name + UPDATE + dp + SET + dp.client_app = + CASE + WHEN dp.client_app LIKE N'SQLAgent - %' + THEN N'SQLAgent - Job: ' + + aj.job_name + + N' Step: ' + + aj.step_name ELSE dp.client_app - END + END FROM #deadlock_process AS dp JOIN #agent_job AS aj - ON dp.event_date = aj.event_date - AND dp.victim_id = aj.victim_id - AND dp.id = aj.id - OPTION ( RECOMPILE ); + ON dp.event_date = aj.event_date + AND dp.victim_id = aj.victim_id + AND dp.id = aj.id + OPTION(RECOMPILE); - /*Get each and every table of all databases*/ - DECLARE @sysAssObjId AS TABLE (database_id bigint, partition_id bigint, schema_name varchar(255), table_name varchar(255)); - INSERT into @sysAssObjId EXECUTE sp_MSforeachdb - N'USE [?]; - SELECT DB_ID() as database_id, p.partition_id, s.name as schema_name, t.name as table_name - FROM sys.partitions p - LEFT JOIN sys.tables t ON t.object_id = p.object_id - LEFT JOIN sys.schemas s ON s.schema_id = t.schema_id - WHERE s.name is not NULL AND t.name is not NULL'; - - - /*Begin checks based on parsed values*/ - - /*Check 1 is deadlocks by database*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Check 1 %s', 0, 1, @d) WITH NOWAIT; - INSERT #deadlock_findings WITH (TABLOCKX) - ( check_id, database_name, object_name, finding_group, finding ) - SELECT 1 AS check_id, - DB_NAME(dp.database_id) AS database_name, - '-' AS object_name, - 'Total database locks' AS finding_group, - 'This database had ' - + CONVERT(NVARCHAR(20), COUNT_BIG(DISTINCT dp.event_date)) - + ' deadlocks.' - FROM #deadlock_process AS dp - WHERE 1 = 1 - AND (DB_NAME(dp.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (dp.event_date >= @StartDate OR @StartDate IS NULL) - AND (dp.event_date < @EndDate OR @EndDate IS NULL) - AND (dp.client_app = @AppName OR @AppName IS NULL) - AND (dp.host_name = @HostName OR @HostName IS NULL) - AND (dp.login_name = @LoginName OR @LoginName IS NULL) - GROUP BY DB_NAME(dp.database_id) - OPTION ( RECOMPILE ); + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - /*Check 2 is deadlocks by object*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Check 2 objects %s', 0, 1, @d) WITH NOWAIT; - INSERT #deadlock_findings WITH (TABLOCKX) - ( check_id, database_name, object_name, finding_group, finding ) - SELECT 2 AS check_id, - ISNULL(DB_NAME(dow.database_id), 'UNKNOWN') AS database_name, - ISNULL(dow.object_name, 'UNKNOWN') AS object_name, - 'Total object deadlocks' AS finding_group, - 'This object was involved in ' - + CONVERT(NVARCHAR(20), COUNT_BIG(DISTINCT dow.event_date)) - + ' deadlock(s).' - FROM #deadlock_owner_waiter AS dow - WHERE 1 = 1 - AND (DB_NAME(dow.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (dow.event_date >= @StartDate OR @StartDate IS NULL) - AND (dow.event_date < @EndDate OR @EndDate IS NULL) - AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) - GROUP BY DB_NAME(dow.database_id), dow.object_name - OPTION ( RECOMPILE ); + /*Get each and every table of all databases*/ + IF @Azure = 0 + BEGIN + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Inserting to @sysAssObjId %s', 0, 1, @d) WITH NOWAIT; - /*Check 2 continuation, number of locks per index*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Check 2 indexes %s', 0, 1, @d) WITH NOWAIT; - INSERT #deadlock_findings WITH (TABLOCKX) - ( check_id, database_name, object_name, finding_group, finding ) - SELECT 2 AS check_id, - ISNULL(DB_NAME(dow.database_id), 'UNKNOWN') AS database_name, - dow.index_name AS index_name, - 'Total index deadlocks' AS finding_group, - 'This index was involved in ' - + CONVERT(NVARCHAR(20), COUNT_BIG(DISTINCT dow.event_date)) - + ' deadlock(s).' - FROM #deadlock_owner_waiter AS dow - WHERE 1 = 1 - AND (DB_NAME(dow.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (dow.event_date >= @StartDate OR @StartDate IS NULL) - AND (dow.event_date < @EndDate OR @EndDate IS NULL) - AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) - AND dow.lock_type NOT IN (N'HEAP', N'RID') - AND dow.index_name is not null - GROUP BY DB_NAME(dow.database_id), dow.index_name - OPTION ( RECOMPILE ); + INSERT INTO + @sysAssObjId + ( + database_id, + partition_id, + schema_name, + table_name + ) + EXECUTE sys.sp_MSforeachdb + N' + SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + USE [?]; - /*Check 2 continuation, number of locks per heap*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Check 2 heaps %s', 0, 1, @d) WITH NOWAIT; - INSERT #deadlock_findings WITH (TABLOCKX) - ( check_id, database_name, object_name, finding_group, finding ) - SELECT 2 AS check_id, - ISNULL(DB_NAME(dow.database_id), 'UNKNOWN') AS database_name, - dow.index_name AS index_name, - 'Total heap deadlocks' AS finding_group, - 'This heap was involved in ' - + CONVERT(NVARCHAR(20), COUNT_BIG(DISTINCT dow.event_date)) - + ' deadlock(s).' - FROM #deadlock_owner_waiter AS dow - WHERE 1 = 1 - AND (DB_NAME(dow.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (dow.event_date >= @StartDate OR @StartDate IS NULL) - AND (dow.event_date < @EndDate OR @EndDate IS NULL) - AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) - AND dow.lock_type IN (N'HEAP', N'RID') - GROUP BY DB_NAME(dow.database_id), dow.index_name - OPTION ( RECOMPILE ); - + IF EXISTS + ( + SELECT + 1/0 + FROM #deadlock_process AS dp + WHERE dp.database_id = DB_ID() + ) + BEGIN + SELECT + database_id = + DB_ID(), + p.partition_id, + schema_name = + s.name, + table_name = + t.name + FROM sys.partitions p + JOIN sys.tables t + ON t.object_id = p.object_id + JOIN sys.schemas s + ON s.schema_id = t.schema_id + WHERE s.name IS NOT NULL + AND t.name IS NOT NULL + OPTION(RECOMPILE); + END; + '; - /*Check 3 looks for Serializable locking*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Check 3 %s', 0, 1, @d) WITH NOWAIT; - INSERT #deadlock_findings WITH (TABLOCKX) - ( check_id, database_name, object_name, finding_group, finding ) - SELECT 3 AS check_id, - DB_NAME(dp.database_id) AS database_name, - '-' AS object_name, - 'Serializable locking' AS finding_group, - 'This database has had ' + - CONVERT(NVARCHAR(20), COUNT_BIG(*)) + - ' instances of serializable deadlocks.' - AS finding - FROM #deadlock_process AS dp - WHERE dp.isolation_level LIKE 'serializable%' - AND (DB_NAME(dp.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (dp.event_date >= @StartDate OR @StartDate IS NULL) - AND (dp.event_date < @EndDate OR @EndDate IS NULL) - AND (dp.client_app = @AppName OR @AppName IS NULL) - AND (dp.host_name = @HostName OR @HostName IS NULL) - AND (dp.login_name = @LoginName OR @LoginName IS NULL) - GROUP BY DB_NAME(dp.database_id) - OPTION ( RECOMPILE ); + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + END; + IF @Azure = 1 + BEGIN + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Inserting to @sysAssObjId at %s', 0, 1, @d) WITH NOWAIT; - /*Check 4 looks for Repeatable Read locking*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Check 4 %s', 0, 1, @d) WITH NOWAIT; - INSERT #deadlock_findings WITH (TABLOCKX) - ( check_id, database_name, object_name, finding_group, finding ) - SELECT 4 AS check_id, - DB_NAME(dp.database_id) AS database_name, - '-' AS object_name, - 'Repeatable Read locking' AS finding_group, - 'This database has had ' + - CONVERT(NVARCHAR(20), COUNT_BIG(*)) + - ' instances of repeatable read deadlocks.' - AS finding - FROM #deadlock_process AS dp - WHERE dp.isolation_level LIKE 'repeatable read%' - AND (DB_NAME(dp.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (dp.event_date >= @StartDate OR @StartDate IS NULL) - AND (dp.event_date < @EndDate OR @EndDate IS NULL) - AND (dp.client_app = @AppName OR @AppName IS NULL) - AND (dp.host_name = @HostName OR @HostName IS NULL) - AND (dp.login_name = @LoginName OR @LoginName IS NULL) - GROUP BY DB_NAME(dp.database_id) - OPTION ( RECOMPILE ); + INSERT INTO + @sysAssObjId + ( + database_id, + partition_id, + schema_name, + table_name + ) + SELECT + database_id = + DB_ID(), + p.partition_id, + schema_name = + s.name, + table_name = + t.name + FROM sys.partitions p + JOIN sys.tables t + ON t.object_id = p.object_id + JOIN sys.schemas s + ON s.schema_id = t.schema_id + WHERE s.name IS NOT NULL + AND t.name IS NOT NULL + OPTION(RECOMPILE); + + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + END; + /*Begin checks based on parsed values*/ - /*Check 5 breaks down app, host, and login information*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Check 5 %s', 0, 1, @d) WITH NOWAIT; - INSERT #deadlock_findings WITH (TABLOCKX) - ( check_id, database_name, object_name, finding_group, finding ) - SELECT 5 AS check_id, - DB_NAME(dp.database_id) AS database_name, - '-' AS object_name, - 'Login, App, and Host locking' AS finding_group, - 'This database has had ' + - CONVERT(NVARCHAR(20), COUNT_BIG(DISTINCT dp.event_date)) + - ' instances of deadlocks involving the login ' + - ISNULL(dp.login_name, 'UNKNOWN') + - ' from the application ' + - ISNULL(dp.client_app, 'UNKNOWN') + - ' on host ' + - ISNULL(dp.host_name, 'UNKNOWN') - AS finding - FROM #deadlock_process AS dp - WHERE 1 = 1 - AND (DB_NAME(dp.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (dp.event_date >= @StartDate OR @StartDate IS NULL) - AND (dp.event_date < @EndDate OR @EndDate IS NULL) - AND (dp.client_app = @AppName OR @AppName IS NULL) - AND (dp.host_name = @HostName OR @HostName IS NULL) - AND (dp.login_name = @LoginName OR @LoginName IS NULL) - GROUP BY DB_NAME(dp.database_id), dp.login_name, dp.client_app, dp.host_name - OPTION ( RECOMPILE ); + /*Check 1 is deadlocks by database*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Check 1 database deadlocks %s', 0, 1, @d) WITH NOWAIT; + INSERT + #deadlock_findings WITH(TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding + ) + SELECT + check_id = 1, + dp.database_name, + object_name = N'-', + finding_group = N'Total Database Deadlocks', + finding = + N'This database had ' + + CONVERT + ( + nvarchar(20), + COUNT_BIG(DISTINCT dp.event_date) + ) + + N' deadlocks.' + FROM #deadlock_process AS dp + WHERE 1 = 1 + AND (dp.database_name = @DatabaseName OR @DatabaseName IS NULL) + AND (dp.event_date >= @StartDate OR @StartDate IS NULL) + AND (dp.event_date < @EndDate OR @EndDate IS NULL) + AND (dp.client_app = @AppName OR @AppName IS NULL) + AND (dp.host_name = @HostName OR @HostName IS NULL) + AND (dp.login_name = @LoginName OR @LoginName IS NULL) + GROUP BY dp.database_name + OPTION(RECOMPILE); - /*Check 6 breaks down the types of locks (object, page, key, etc.)*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Check 6 %s', 0, 1, @d) WITH NOWAIT; - WITH lock_types AS ( - SELECT DB_NAME(dp.database_id) AS database_name, - dow.object_name, - SUBSTRING(dp.wait_resource, 1, CHARINDEX(':', dp.wait_resource) -1) AS lock, - CONVERT(NVARCHAR(20), COUNT_BIG(DISTINCT dp.id)) AS lock_count - FROM #deadlock_process AS dp - JOIN #deadlock_owner_waiter AS dow - ON dp.id = dow.owner_id - AND dp.event_date = dow.event_date - WHERE 1 = 1 - AND (DB_NAME(dp.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (dp.event_date >= @StartDate OR @StartDate IS NULL) - AND (dp.event_date < @EndDate OR @EndDate IS NULL) - AND (dp.client_app = @AppName OR @AppName IS NULL) - AND (dp.host_name = @HostName OR @HostName IS NULL) - AND (dp.login_name = @LoginName OR @LoginName IS NULL) - AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) - AND dow.object_name IS NOT NULL - GROUP BY DB_NAME(dp.database_id), SUBSTRING(dp.wait_resource, 1, CHARINDEX(':', dp.wait_resource) - 1), dow.object_name - ) - INSERT #deadlock_findings WITH (TABLOCKX) - ( check_id, database_name, object_name, finding_group, finding ) - SELECT DISTINCT 6 AS check_id, - lt.database_name, - lt.object_name, - 'Types of locks by object' AS finding_group, - 'This object has had ' + - STUFF((SELECT DISTINCT N', ' + lt2.lock_count + ' ' + lt2.lock - FROM lock_types AS lt2 - WHERE lt2.database_name = lt.database_name - AND lt2.object_name = lt.object_name - FOR XML PATH(N''), TYPE).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 1, N'') - + ' locks' - FROM lock_types AS lt - OPTION ( RECOMPILE ); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + /*Check 2 is deadlocks with selects*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Check 2 select deadlocks %s', 0, 1, @d) WITH NOWAIT; - /*Check 7 gives you more info queries for sp_BlitzCache & BlitzQueryStore*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Check 7 part 1 %s', 0, 1, @d) WITH NOWAIT; - WITH deadlock_stack AS ( - SELECT DISTINCT - ds.id, - ds.proc_name, - ds.event_date, - PARSENAME(ds.proc_name, 3) AS database_name, - PARSENAME(ds.proc_name, 2) AS schema_name, - PARSENAME(ds.proc_name, 1) AS proc_only_name, - '''' + STUFF((SELECT DISTINCT N',' + ds2.sql_handle - FROM #deadlock_stack AS ds2 - WHERE ds2.id = ds.id - AND ds2.event_date = ds.event_date - FOR XML PATH(N''), TYPE).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 1, N'') + '''' AS sql_handle_csv - FROM #deadlock_stack AS ds - GROUP BY PARSENAME(ds.proc_name, 3), - PARSENAME(ds.proc_name, 2), - PARSENAME(ds.proc_name, 1), - ds.id, - ds.proc_name, - ds.event_date - ) - INSERT #deadlock_findings WITH (TABLOCKX) - ( check_id, database_name, object_name, finding_group, finding ) - SELECT DISTINCT 7 AS check_id, - ISNULL(DB_NAME(dow.database_id), 'UNKNOWN') AS database_name, - ds.proc_name AS object_name, - 'More Info - Query' AS finding_group, - 'EXEC sp_BlitzCache ' + - CASE WHEN ds.proc_name = 'adhoc' - THEN ' @OnlySqlHandles = ' + ds.sql_handle_csv - ELSE '@StoredProcName = ' + - QUOTENAME(ds.proc_only_name, '''') - END + - ';' AS finding - FROM deadlock_stack AS ds - JOIN #deadlock_owner_waiter AS dow - ON dow.owner_id = ds.id - AND dow.event_date = ds.event_date - WHERE 1 = 1 - AND (DB_NAME(dow.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (dow.event_date >= @StartDate OR @StartDate IS NULL) - AND (dow.event_date < @EndDate OR @EndDate IS NULL) - AND (dow.object_name = @StoredProcName OR @StoredProcName IS NULL) - OPTION ( RECOMPILE ); + INSERT + #deadlock_findings WITH(TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding + ) + SELECT + check_id = 2, + dow.database_name, + object_name = + N'You Might Need RCSI', + finding_group = N'Total Deadlocks Involving Selects', + finding = + N'There have been ' + + CONVERT + ( + nvarchar(20), + COUNT_BIG(DISTINCT dow.event_date) + ) + + N' deadlock(s) between read queries and modification queries.' + FROM #deadlock_owner_waiter AS dow + WHERE 1 = 1 + AND dow.lock_mode IN + ( + N'S', + N'IS' + ) + AND (dow.database_id = @DatabaseName OR @DatabaseName IS NULL) + AND (dow.event_date >= @StartDate OR @StartDate IS NULL) + AND (dow.event_date < @EndDate OR @EndDate IS NULL) + AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) + GROUP BY + dow.database_name + OPTION(RECOMPILE); - IF @ProductVersionMajor >= 13 - BEGIN - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Check 7 part 2 %s', 0, 1, @d) WITH NOWAIT; - WITH deadlock_stack AS ( - SELECT DISTINCT - ds.id, - ds.sql_handle, - ds.proc_name, - ds.event_date, - PARSENAME(ds.proc_name, 3) AS database_name, - PARSENAME(ds.proc_name, 2) AS schema_name, - PARSENAME(ds.proc_name, 1) AS proc_only_name - FROM #deadlock_stack AS ds - ) - INSERT #deadlock_findings WITH (TABLOCKX) - ( check_id, database_name, object_name, finding_group, finding ) - SELECT DISTINCT 7 AS check_id, - DB_NAME(dow.database_id) AS database_name, - ds.proc_name AS object_name, - 'More Info - Query' AS finding_group, - 'EXEC sp_BlitzQueryStore ' - + '@DatabaseName = ' - + QUOTENAME(ds.database_name, '''') - + ', ' - + '@StoredProcName = ' - + QUOTENAME(ds.proc_only_name, '''') - + ';' AS finding - FROM deadlock_stack AS ds - JOIN #deadlock_owner_waiter AS dow - ON dow.owner_id = ds.id - AND dow.event_date = ds.event_date - WHERE ds.proc_name <> 'adhoc' - AND (DB_NAME(dow.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (dow.event_date >= @StartDate OR @StartDate IS NULL) - AND (dow.event_date < @EndDate OR @EndDate IS NULL) - AND (dow.object_name = @StoredProcName OR @StoredProcName IS NULL) - OPTION ( RECOMPILE ); - END; - + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - /*Check 8 gives you stored proc deadlock counts*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Check 8 %s', 0, 1, @d) WITH NOWAIT; - INSERT #deadlock_findings WITH (TABLOCKX) - ( check_id, database_name, object_name, finding_group, finding ) - SELECT 8 AS check_id, - DB_NAME(dp.database_id) AS database_name, - ds.proc_name, - 'Stored Procedure Deadlocks', - 'The stored procedure ' - + PARSENAME(ds.proc_name, 2) - + '.' - + PARSENAME(ds.proc_name, 1) - + ' has been involved in ' - + CONVERT(NVARCHAR(10), COUNT_BIG(DISTINCT ds.id)) - + ' deadlocks.' - FROM #deadlock_stack AS ds - JOIN #deadlock_process AS dp - ON dp.id = ds.id - AND ds.event_date = dp.event_date - WHERE ds.proc_name <> 'adhoc' - AND (ds.proc_name = @StoredProcName OR @StoredProcName IS NULL) - AND (DB_NAME(dp.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (dp.event_date >= @StartDate OR @StartDate IS NULL) - AND (dp.event_date < @EndDate OR @EndDate IS NULL) - AND (dp.client_app = @AppName OR @AppName IS NULL) - AND (dp.host_name = @HostName OR @HostName IS NULL) - AND (dp.login_name = @LoginName OR @LoginName IS NULL) - GROUP BY DB_NAME(dp.database_id), ds.proc_name - OPTION(RECOMPILE); + /*Check 3 is deadlocks by object*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Check 3 object deadlocks %s', 0, 1, @d) WITH NOWAIT; + INSERT + #deadlock_findings WITH(TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding + ) + SELECT + check_id = 3, + dow.database_name, + object_name = + ISNULL + ( + dow.object_name, + N'UNKNOWN' + ), + finding_group = N'Total Object Deadlocks', + finding = + N'This object was involved in ' + + CONVERT + ( + nvarchar(20), + COUNT_BIG(DISTINCT dow.event_date) + ) + + N' deadlock(s).' + FROM #deadlock_owner_waiter AS dow + WHERE 1 = 1 + AND (dow.database_id = @DatabaseName OR @DatabaseName IS NULL) + AND (dow.event_date >= @StartDate OR @StartDate IS NULL) + AND (dow.event_date < @EndDate OR @EndDate IS NULL) + AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) + GROUP BY + dow.database_name, + dow.object_name + OPTION(RECOMPILE); - /*Check 9 gives you more info queries for sp_BlitzIndex */ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Check 9 %s', 0, 1, @d) WITH NOWAIT; - WITH bi AS ( - SELECT DISTINCT - dow.object_name, - DB_NAME(dow.database_id) as database_name, - a.schema_name AS schema_name, - a.table_name AS table_name - FROM #deadlock_owner_waiter AS dow - LEFT JOIN @sysAssObjId a ON a.database_id=dow.database_id AND a.partition_id = dow.associatedObjectId - WHERE 1 = 1 - AND (DB_NAME(dow.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (dow.event_date >= @StartDate OR @StartDate IS NULL) - AND (dow.event_date < @EndDate OR @EndDate IS NULL) - AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) - AND dow.object_name IS NOT NULL - ) - INSERT #deadlock_findings WITH (TABLOCKX) - ( check_id, database_name, object_name, finding_group, finding ) - SELECT 9 AS check_id, - bi.database_name, - bi.object_name, - 'More Info - Table' AS finding_group, - 'EXEC sp_BlitzIndex ' + - '@DatabaseName = ' + QUOTENAME(bi.database_name, '''') + - ', @SchemaName = ' + QUOTENAME(bi.schema_name, '''') + - ', @TableName = ' + QUOTENAME(bi.table_name, '''') + - ';' AS finding - FROM bi - OPTION ( RECOMPILE ); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - /*Check 10 gets total deadlock wait time per object*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Check 10 %s', 0, 1, @d) WITH NOWAIT; - WITH chopsuey AS ( - SELECT DISTINCT - PARSENAME(dow.object_name, 3) AS database_name, - dow.object_name, - CONVERT(VARCHAR(10), (SUM(DISTINCT CONVERT(BIGINT, dp.wait_time)) / 1000) / 86400) AS wait_days, - CONVERT(VARCHAR(20), DATEADD(SECOND, (SUM(DISTINCT CONVERT(BIGINT, dp.wait_time)) / 1000), 0), 108) AS wait_time_hms - FROM #deadlock_owner_waiter AS dow - JOIN #deadlock_process AS dp - ON (dp.id = dow.owner_id OR dp.victim_id = dow.waiter_id) - AND dp.event_date = dow.event_date - WHERE 1 = 1 - AND (DB_NAME(dow.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (dow.event_date >= @StartDate OR @StartDate IS NULL) - AND (dow.event_date < @EndDate OR @EndDate IS NULL) - AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) - AND (dp.client_app = @AppName OR @AppName IS NULL) - AND (dp.host_name = @HostName OR @HostName IS NULL) - AND (dp.login_name = @LoginName OR @LoginName IS NULL) - GROUP BY PARSENAME(dow.object_name, 3), dow.object_name - ) - INSERT #deadlock_findings WITH (TABLOCKX) - ( check_id, database_name, object_name, finding_group, finding ) - SELECT 10 AS check_id, - cs.database_name, - cs.object_name, - 'Total object deadlock wait time' AS finding_group, - 'This object has had ' - + CONVERT(VARCHAR(10), cs.wait_days) - + ':' + CONVERT(VARCHAR(20), cs.wait_time_hms, 108) - + ' [d/h/m/s] of deadlock wait time.' AS finding - FROM chopsuey AS cs - WHERE cs.object_name IS NOT NULL - OPTION ( RECOMPILE ); - - /*Check 11 gets total deadlock wait time per database*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Check 11 %s', 0, 1, @d) WITH NOWAIT; - WITH wait_time AS ( - SELECT DB_NAME(dp.database_id) AS database_name, - SUM(CONVERT(BIGINT, dp.wait_time)) AS total_wait_time_ms - FROM #deadlock_process AS dp - WHERE 1 = 1 - AND (DB_NAME(dp.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (dp.event_date >= @StartDate OR @StartDate IS NULL) - AND (dp.event_date < @EndDate OR @EndDate IS NULL) - AND (dp.client_app = @AppName OR @AppName IS NULL) - AND (dp.host_name = @HostName OR @HostName IS NULL) - AND (dp.login_name = @LoginName OR @LoginName IS NULL) - GROUP BY DB_NAME(dp.database_id) - ) - INSERT #deadlock_findings WITH (TABLOCKX) - ( check_id, database_name, object_name, finding_group, finding ) - SELECT 11 AS check_id, - wt.database_name, - '-' AS object_name, - 'Total database deadlock wait time' AS finding_group, - 'This database has had ' - + CONVERT(VARCHAR(10), (SUM(DISTINCT CONVERT(BIGINT, wt.total_wait_time_ms)) / 1000) / 86400) - + ':' + CONVERT(VARCHAR(20), DATEADD(SECOND, (SUM(DISTINCT CONVERT(BIGINT, wt.total_wait_time_ms)) / 1000), 0), 108) - + ' [d/h/m/s] of deadlock wait time.' - FROM wait_time AS wt - GROUP BY wt.database_name - OPTION ( RECOMPILE ); + /*Check 3 continuation, number of deadlocks per index*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Check 3 (continued) index deadlocks %s', 0, 1, @d) WITH NOWAIT; + + INSERT + #deadlock_findings WITH(TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding + ) + SELECT + check_id = 3, + dow.database_name, + index_name = dow.index_name, + finding_group = N'Total Index Deadlocks', + finding = + N'This index was involved in ' + + CONVERT + ( + nvarchar(20), + COUNT_BIG(DISTINCT dow.event_date) + ) + + N' deadlock(s).' + FROM #deadlock_owner_waiter AS dow + WHERE 1 = 1 + AND (dow.database_id = @DatabaseName OR @DatabaseName IS NULL) + AND (dow.event_date >= @StartDate OR @StartDate IS NULL) + AND (dow.event_date < @EndDate OR @EndDate IS NULL) + AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) + AND dow.lock_type NOT IN + ( + N'HEAP', + N'RID' + ) + AND dow.index_name IS NOT NULL + GROUP BY + dow.database_name, + dow.index_name + OPTION(RECOMPILE); + + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + + /*Check 3 continuation, number of deadlocks per heap*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Check 3 (continued) heap deadlocks %s', 0, 1, @d) WITH NOWAIT; + + INSERT + #deadlock_findings WITH(TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding + ) + SELECT + check_id = 3, + dow.database_name, + index_name = dow.index_name, + finding_group = N'Total Heap Deadlocks', + finding = + N'This heap was involved in ' + + CONVERT + ( + nvarchar(20), + COUNT_BIG(DISTINCT dow.event_date) + ) + + N' deadlock(s).' + FROM #deadlock_owner_waiter AS dow + WHERE 1 = 1 + AND (dow.database_id = @DatabaseName OR @DatabaseName IS NULL) + AND (dow.event_date >= @StartDate OR @StartDate IS NULL) + AND (dow.event_date < @EndDate OR @EndDate IS NULL) + AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) + AND dow.lock_type IN + ( + N'HEAP', + N'RID' + ) + GROUP BY + dow.database_name, + dow.index_name + OPTION(RECOMPILE); + + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + + /*Check 4 looks for Serializable deadlocks*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Check 4 serializable deadlocks %s', 0, 1, @d) WITH NOWAIT; + + INSERT + #deadlock_findings WITH(TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding + ) + SELECT + check_id = 4, + database_name = + dp.database_name, + object_name = N'-', + finding_group = N'Serializable Deadlocking', + finding = + N'This database has had ' + + CONVERT + ( + nvarchar(20), + COUNT_BIG(DISTINCT dp.event_date) + ) + + N' instances of Serializable deadlocks.' + FROM #deadlock_process AS dp + WHERE dp.isolation_level LIKE N'serializable%' + AND (dp.database_name = @DatabaseName OR @DatabaseName IS NULL) + AND (dp.event_date >= @StartDate OR @StartDate IS NULL) + AND (dp.event_date < @EndDate OR @EndDate IS NULL) + AND (dp.client_app = @AppName OR @AppName IS NULL) + AND (dp.host_name = @HostName OR @HostName IS NULL) + AND (dp.login_name = @LoginName OR @LoginName IS NULL) + GROUP BY dp.database_name + OPTION(RECOMPILE); + + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + + /*Check 5 looks for Repeatable Read deadlocks*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Check 5 repeatable read deadlocks %s', 0, 1, @d) WITH NOWAIT; + + INSERT + #deadlock_findings WITH(TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding + ) + SELECT + check_id = 5, + dp.database_name, + object_name = N'-', + finding_group = N'Repeatable Read Deadlocking', + finding = + N'This database has had ' + + CONVERT + ( + nvarchar(20), + COUNT_BIG(DISTINCT dp.event_date) + ) + + N' instances of Repeatable Read deadlocks.' + FROM #deadlock_process AS dp + WHERE dp.isolation_level LIKE N'repeatable%' + AND (dp.database_name = @DatabaseName OR @DatabaseName IS NULL) + AND (dp.event_date >= @StartDate OR @StartDate IS NULL) + AND (dp.event_date < @EndDate OR @EndDate IS NULL) + AND (dp.client_app = @AppName OR @AppName IS NULL) + AND (dp.host_name = @HostName OR @HostName IS NULL) + AND (dp.login_name = @LoginName OR @LoginName IS NULL) + GROUP BY dp.database_name + OPTION(RECOMPILE); + + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + + /*Check 6 breaks down app, host, and login information*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Check 6 app/host/login deadlocks %s', 0, 1, @d) WITH NOWAIT; + + INSERT + #deadlock_findings WITH(TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding + ) + SELECT + check_id = 6, + database_name = + dp.database_name, + object_name = N'-', + finding_group = N'Login, App, and Host deadlocks', + finding = + N'This database has had ' + + CONVERT + ( + nvarchar(20), + COUNT_BIG(DISTINCT dp.event_date) + ) + + N' instances of deadlocks involving the login ' + + ISNULL + ( + dp.login_name, + N'UNKNOWN' + ) + + N' from the application ' + + ISNULL + ( + dp.client_app, + N'UNKNOWN' + ) + + N' on host ' + + ISNULL + ( + dp.host_name, + N'UNKNOWN' + ) + + N'.' + FROM #deadlock_process AS dp + WHERE 1 = 1 + AND (dp.database_name = @DatabaseName OR @DatabaseName IS NULL) + AND (dp.event_date >= @StartDate OR @StartDate IS NULL) + AND (dp.event_date < @EndDate OR @EndDate IS NULL) + AND (dp.client_app = @AppName OR @AppName IS NULL) + AND (dp.host_name = @HostName OR @HostName IS NULL) + AND (dp.login_name = @LoginName OR @LoginName IS NULL) + GROUP BY + dp.database_name, + dp.login_name, + dp.client_app, + dp.host_name + OPTION(RECOMPILE); - /*Check 12 gets total deadlock wait time for SQL Agent*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Check 12 %s', 0, 1, @d) WITH NOWAIT; - INSERT #deadlock_findings WITH (TABLOCKX) - ( check_id, database_name, object_name, finding_group, finding ) - SELECT 12, - DB_NAME(aj.database_id), - 'SQLAgent - Job: ' - + aj.job_name - + ' Step: ' - + aj.step_name, - 'Agent Job Deadlocks', - RTRIM(COUNT(*)) + ' deadlocks from this Agent Job and Step' + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + + /*Check 7 breaks down the types of deadlocks (object, page, key, etc.)*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Check 7 types of deadlocks %s', 0, 1, @d) WITH NOWAIT; + + WITH + lock_types AS + ( + SELECT + database_name = + dp.database_name, + dow.object_name, + lock = + CASE + WHEN CHARINDEX(N':', dp.wait_resource) > 0 + THEN SUBSTRING + ( + dp.wait_resource, + 1, + CHARINDEX(N':', dp.wait_resource) - 1 + ) + ELSE dp.wait_resource + END, + lock_count = + CONVERT + ( + nvarchar(20), + COUNT_BIG(DISTINCT dp.event_date) + ) + FROM #deadlock_process AS dp + JOIN #deadlock_owner_waiter AS dow + ON (dp.id = dow.owner_id + OR dp.victim_id = dow.waiter_id) + AND dp.event_date = dow.event_date + WHERE 1 = 1 + AND (dp.database_name = @DatabaseName OR @DatabaseName IS NULL) + AND (dp.event_date >= @StartDate OR @StartDate IS NULL) + AND (dp.event_date < @EndDate OR @EndDate IS NULL) + AND (dp.client_app = @AppName OR @AppName IS NULL) + AND (dp.host_name = @HostName OR @HostName IS NULL) + AND (dp.login_name = @LoginName OR @LoginName IS NULL) + AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) + AND dow.object_name IS NOT NULL + GROUP BY + dp.database_name, + CASE + WHEN CHARINDEX(N':', dp.wait_resource) > 0 + THEN SUBSTRING + ( + dp.wait_resource, + 1, + CHARINDEX(N':', dp.wait_resource) - 1 + ) + ELSE dp.wait_resource + END, + dow.object_name + ) + INSERT + #deadlock_findings WITH(TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding + ) + SELECT + check_id = 7, + lt.database_name, + lt.object_name, + finding_group = N'Types of locks by object', + finding = + N'This object has had ' + + STUFF + ( + ( + SELECT + N', ' + + lt2.lock_count + + N' ' + + lt2.lock + FROM lock_types AS lt2 + WHERE lt2.database_name = lt.database_name + AND lt2.object_name = lt.object_name + FOR XML + PATH(N''), + TYPE + ).value(N'.[1]', N'nvarchar(MAX)'), + 1, + 1, + N'' + ) + N' locks.' + FROM lock_types AS lt + OPTION(RECOMPILE); + + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + + /*Check 8 gives you more info queries for sp_BlitzCache & BlitzQueryStore*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Check 8 more info part 1 BlitzCache %s', 0, 1, @d) WITH NOWAIT; + + WITH + deadlock_stack AS + ( + SELECT DISTINCT + ds.id, + ds.proc_name, + ds.event_date, + database_name = + PARSENAME(ds.proc_name, 3), + schema_name = + PARSENAME(ds.proc_name, 2), + proc_only_name = + PARSENAME(ds.proc_name, 1), + sql_handle_csv = + N'''' + + STUFF + ( + ( + SELECT DISTINCT + N',' + + ds2.sql_handle + FROM #deadlock_stack AS ds2 + WHERE ds2.id = ds.id + AND ds2.event_date = ds.event_date + AND ds2.sql_handle <> 0x + FOR XML + PATH(N''), + TYPE + ).value(N'.[1]', N'nvarchar(MAX)'), + 1, + 1, + N'' + ) + + N'''' + FROM #deadlock_stack AS ds + WHERE ds.sql_handle <> 0x + GROUP BY + PARSENAME(ds.proc_name, 3), + PARSENAME(ds.proc_name, 2), + PARSENAME(ds.proc_name, 1), + ds.id, + ds.proc_name, + ds.event_date + ) + INSERT + #deadlock_findings WITH(TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding + ) + SELECT DISTINCT + check_id = 8, + dow.database_name, + object_name = ds.proc_name, + finding_group = N'More Info - Query', + finding = N'EXEC sp_BlitzCache ' + + CASE + WHEN ds.proc_name = N'adhoc' + THEN N'@OnlySqlHandles = ' + ds.sql_handle_csv + ELSE N'@StoredProcName = ' + QUOTENAME(ds.proc_only_name, N'''') + END + N';' + FROM deadlock_stack AS ds + JOIN #deadlock_owner_waiter AS dow + ON dow.owner_id = ds.id + AND dow.event_date = ds.event_date + WHERE 1 = 1 + AND (dow.database_id = @DatabaseName OR @DatabaseName IS NULL) + AND (dow.event_date >= @StartDate OR @StartDate IS NULL) + AND (dow.event_date < @EndDate OR @EndDate IS NULL) + AND (dow.object_name = @StoredProcName OR @StoredProcName IS NULL) + OPTION(RECOMPILE); + + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + + IF (@ProductVersionMajor >= 13 OR @Azure = 1) + BEGIN + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Check 8 more info part 2 BlitzQueryStore %s', 0, 1, @d) WITH NOWAIT; + + WITH + deadlock_stack AS + ( + SELECT DISTINCT + ds.id, + ds.sql_handle, + ds.proc_name, + ds.event_date, + database_name = + PARSENAME(ds.proc_name, 3), + schema_name = + PARSENAME(ds.proc_name, 2), + proc_only_name = + PARSENAME(ds.proc_name, 1) + FROM #deadlock_stack AS ds + ) + INSERT + #deadlock_findings WITH(TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding + ) + SELECT DISTINCT + check_id = 8, + dow.database_name, + object_name = ds.proc_name, + finding_group = N'More Info - Query', + finding = + N'EXEC sp_BlitzQueryStore ' + + N'@DatabaseName = ' + + QUOTENAME(ds.database_name, N'''') + + N', ' + + N'@StoredProcName = ' + + QUOTENAME(ds.proc_only_name, N'''') + + N';' + FROM deadlock_stack AS ds + JOIN #deadlock_owner_waiter AS dow + ON dow.owner_id = ds.id + AND dow.event_date = ds.event_date + WHERE ds.proc_name <> N'adhoc' + AND (dow.database_id = @DatabaseName OR @DatabaseName IS NULL) + AND (dow.event_date >= @StartDate OR @StartDate IS NULL) + AND (dow.event_date < @EndDate OR @EndDate IS NULL) + AND (dow.object_name = @StoredProcName OR @StoredProcName IS NULL) + OPTION(RECOMPILE); + + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + END; + + /*Check 9 gives you stored procedure deadlock counts*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Check 9 stored procedure deadlocks %s', 0, 1, @d) WITH NOWAIT; + + INSERT + #deadlock_findings WITH(TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding + ) + SELECT + check_id = 9, + database_name = + dp.database_name, + object_name = ds.proc_name, + finding_group = N'Stored Procedure Deadlocks', + finding = + N'The stored procedure ' + + PARSENAME(ds.proc_name, 2) + + N'.' + + PARSENAME(ds.proc_name, 1) + + N' has been involved in ' + + CONVERT + ( + nvarchar(10), + COUNT_BIG(DISTINCT ds.id) + ) + + N' deadlocks.' + FROM #deadlock_stack AS ds + JOIN #deadlock_process AS dp + ON dp.id = ds.id + AND ds.event_date = dp.event_date + WHERE ds.proc_name <> N'adhoc' + AND (ds.proc_name = @StoredProcName OR @StoredProcName IS NULL) + AND (dp.database_name = @DatabaseName OR @DatabaseName IS NULL) + AND (dp.event_date >= @StartDate OR @StartDate IS NULL) + AND (dp.event_date < @EndDate OR @EndDate IS NULL) + AND (dp.client_app = @AppName OR @AppName IS NULL) + AND (dp.host_name = @HostName OR @HostName IS NULL) + AND (dp.login_name = @LoginName OR @LoginName IS NULL) + GROUP BY + dp.database_name, + ds.proc_name + OPTION(RECOMPILE); + + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + + /*Check 10 gives you more info queries for sp_BlitzIndex */ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Check 10 more info, BlitzIndex %s', 0, 1, @d) WITH NOWAIT; + + WITH + bi AS + ( + SELECT DISTINCT + dow.object_name, + dow.database_name, + schema_name = s.schema_name, + table_name = s.table_name + FROM #deadlock_owner_waiter AS dow + JOIN @sysAssObjId AS s + ON s.database_id = dow.database_id + AND s.partition_id = dow.associatedObjectId + WHERE 1 = 1 + AND (dow.database_id = @DatabaseName OR @DatabaseName IS NULL) + AND (dow.event_date >= @StartDate OR @StartDate IS NULL) + AND (dow.event_date < @EndDate OR @EndDate IS NULL) + AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) + AND dow.object_name IS NOT NULL + ) + INSERT + #deadlock_findings WITH(TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding + ) + SELECT DISTINCT + check_id = 10, + bi.database_name, + bi.object_name, + finding_group = N'More Info - Table', + finding = + N'EXEC sp_BlitzIndex ' + + N'@DatabaseName = ' + + QUOTENAME(bi.database_name, N'''') + + N', @SchemaName = ' + + QUOTENAME(bi.schema_name, N'''') + + N', @TableName = ' + + QUOTENAME(bi.table_name, N'''') + + N';' + FROM bi + OPTION(RECOMPILE); + + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + + /*Check 11 gets total deadlock wait time per object*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Check 11 deadlock wait time per object %s', 0, 1, @d) WITH NOWAIT; + + WITH + chopsuey AS + ( + + SELECT + database_name = + dp.database_name, + dow.object_name, + wait_days = + CONVERT + ( + nvarchar(30), + ( + SUM + ( + CONVERT + ( + bigint, + dp.wait_time + ) + ) / 1000 / 86400 + ) + ), + wait_time_hms = + CONVERT + ( + nvarchar(30), + DATEADD + ( + MILLISECOND, + ( + SUM + ( + CONVERT + ( + bigint, + dp.wait_time + ) + ) + ), + 0 + ), + 14 + ) + FROM #deadlock_owner_waiter AS dow + JOIN #deadlock_process AS dp + ON (dp.id = dow.owner_id + OR dp.victim_id = dow.waiter_id) + AND dp.event_date = dow.event_date + WHERE 1 = 1 + AND (dp.database_name = @DatabaseName OR @DatabaseName IS NULL) + AND (dp.event_date >= @StartDate OR @StartDate IS NULL) + AND (dp.event_date < @EndDate OR @EndDate IS NULL) + AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) + AND (dp.client_app = @AppName OR @AppName IS NULL) + AND (dp.host_name = @HostName OR @HostName IS NULL) + AND (dp.login_name = @LoginName OR @LoginName IS NULL) + GROUP BY + dp.database_name, + dow.object_name + ) + INSERT + #deadlock_findings WITH(TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding + ) + SELECT + check_id = 11, + cs.database_name, + cs.object_name, + finding_group = N'Total object deadlock wait time', + finding = + N'This object has had ' + + CONVERT + ( + nvarchar(30), + cs.wait_days + ) + + N' ' + + CONVERT + ( + nvarchar(30), + cs.wait_time_hms, + 14 + ) + + N' [dd hh:mm:ss:ms] of deadlock wait time.' + FROM chopsuey AS cs + WHERE cs.object_name IS NOT NULL + OPTION(RECOMPILE); + + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + + /*Check 12 gets total deadlock wait time per database*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Check 12 deadlock wait time per database %s', 0, 1, @d) WITH NOWAIT; + + WITH + wait_time AS + ( + SELECT + database_name = + dp.database_name, + total_wait_time_ms = + SUM + ( + CONVERT + ( + bigint, + dp.wait_time + ) + ) + FROM #deadlock_owner_waiter AS dow + JOIN #deadlock_process AS dp + ON (dp.id = dow.owner_id + OR dp.victim_id = dow.waiter_id) + AND dp.event_date = dow.event_date + WHERE 1 = 1 + AND (dp.database_name = @DatabaseName OR @DatabaseName IS NULL) + AND (dp.event_date >= @StartDate OR @StartDate IS NULL) + AND (dp.event_date < @EndDate OR @EndDate IS NULL) + AND (dp.client_app = @AppName OR @AppName IS NULL) + AND (dp.host_name = @HostName OR @HostName IS NULL) + AND (dp.login_name = @LoginName OR @LoginName IS NULL) + GROUP BY + dp.database_name + ) + INSERT + #deadlock_findings WITH(TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding + ) + SELECT + check_id = 12, + wt.database_name, + object_name = N'-', + finding_group = N'Total database deadlock wait time', + N'This database has had ' + + CONVERT + ( + nvarchar(30), + ( + SUM + ( + CONVERT + ( + bigint, + wt.total_wait_time_ms + ) + ) / 1000 / 86400 + ) + ) + + N' ' + + CONVERT + ( + nvarchar(30), + DATEADD + ( + MILLISECOND, + ( + SUM + ( + CONVERT + ( + bigint, + wt.total_wait_time_ms + ) + ) + ), + 0 + ), + 14 + ) + + N' [dd hh:mm:ss:ms] of deadlock wait time.' + FROM wait_time AS wt + GROUP BY + wt.database_name + OPTION(RECOMPILE); + + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + + /*Check 13 gets total deadlock wait time for SQL Agent*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Check 13 deadlock count for SQL Agent %s', 0, 1, @d) WITH NOWAIT; + + INSERT + #deadlock_findings WITH(TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding + ) + SELECT + check_id = 13, + database_name = + DB_NAME(aj.database_id), + object_name = + N'SQLAgent - Job: ' + + aj.job_name + + N' Step: ' + + aj.step_name, + finding_group = N'Agent Job Deadlocks', + finding = + N'There have been ' + + RTRIM(COUNT_BIG(DISTINCT aj.event_date)) + + N' deadlocks from this Agent Job and Step.' FROM #agent_job AS aj - GROUP BY DB_NAME(aj.database_id), aj.job_name, aj.step_name - OPTION ( RECOMPILE ); + GROUP BY + DB_NAME(aj.database_id), + aj.job_name, + aj.step_name + OPTION(RECOMPILE); - /*Check 13 is total parallel deadlocks*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Check 13 %s', 0, 1, @d) WITH NOWAIT; - INSERT #deadlock_findings WITH (TABLOCKX) - ( check_id, database_name, object_name, finding_group, finding ) - SELECT 13 AS check_id, - N'-' AS database_name, - '-' AS object_name, - 'Total parallel deadlocks' AS finding_group, - 'There have been ' - + CONVERT(NVARCHAR(20), COUNT_BIG(DISTINCT drp.event_date)) - + ' parallel deadlocks.' - FROM #deadlock_resource_parallel AS drp - WHERE 1 = 1 - HAVING COUNT_BIG(DISTINCT drp.event_date) > 0 - OPTION ( RECOMPILE ); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - /*Thank you goodnight*/ - INSERT #deadlock_findings WITH (TABLOCKX) - ( check_id, database_name, object_name, finding_group, finding ) - VALUES ( -1, - N'sp_BlitzLock ' + CAST(CONVERT(DATETIME, @VersionDate, 102) AS VARCHAR(100)), - N'SQL Server First Responder Kit', - N'http://FirstResponderKit.org/', - N'To get help or add your own contributions, join us at http://FirstResponderKit.org.'); + /*Check 14 is total parallel deadlocks*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Check 14 parallel deadlocks %s', 0, 1, @d) WITH NOWAIT; - + INSERT + #deadlock_findings WITH(TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding + ) + SELECT + check_id = 14, + database_name = N'-', + object_name = N'-', + finding_group = N'Total parallel deadlocks', + finding = + N'There have been ' + + CONVERT + ( + nvarchar(20), + COUNT_BIG(DISTINCT drp.event_date) + ) + + N' parallel deadlocks.' + FROM #deadlock_resource_parallel AS drp + HAVING COUNT_BIG(DISTINCT drp.event_date) > 0 + OPTION(RECOMPILE); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - /*Results*/ - /*Break in case of emergency*/ - --CREATE CLUSTERED INDEX cx_whatever ON #deadlock_process (event_date, id); - --CREATE CLUSTERED INDEX cx_whatever ON #deadlock_resource_parallel (event_date, owner_id); - --CREATE CLUSTERED INDEX cx_whatever ON #deadlock_owner_waiter (event_date, owner_id, waiter_id); - IF(@OutputDatabaseCheck = 0) - BEGIN - - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Results 1 %s', 0, 1, @d) WITH NOWAIT; - WITH deadlocks - AS ( SELECT N'Regular Deadlock' AS deadlock_type, - dp.event_date, - dp.id, - dp.victim_id, - dp.spid, - dp.database_id, - dp.priority, - dp.log_used, - dp.wait_resource COLLATE DATABASE_DEFAULT AS wait_resource, - CONVERT( - XML, - STUFF(( SELECT DISTINCT NCHAR(10) - + N' ' - + ISNULL(c.object_name, N'') - + N' ' COLLATE DATABASE_DEFAULT AS object_name - FROM #deadlock_owner_waiter AS c - WHERE ( dp.id = c.owner_id - OR dp.victim_id = c.waiter_id ) - AND dp.event_date = c.event_date - FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(4000)'), - 1, 1, N'')) AS object_names, - dp.wait_time, - dp.transaction_name, - dp.last_tran_started, - dp.last_batch_started, - dp.last_batch_completed, - dp.lock_mode, - dp.transaction_count, - dp.client_app, - dp.host_name, - dp.login_name, - dp.isolation_level, - dp.process_xml.value('(//process/inputbuf/text())[1]', 'NVARCHAR(MAX)') AS inputbuf, - DENSE_RANK() OVER ( ORDER BY dp.event_date ) AS en, - ROW_NUMBER() OVER ( PARTITION BY dp.event_date ORDER BY dp.event_date ) -1 AS qn, - ROW_NUMBER() OVER ( PARTITION BY dp.event_date, dp.id ORDER BY dp.event_date ) AS dn, - dp.is_victim, - ISNULL(dp.owner_mode, '-') AS owner_mode, - NULL AS owner_waiter_type, - NULL AS owner_activity, - NULL AS owner_waiter_activity, - NULL AS owner_merging, - NULL AS owner_spilling, - NULL AS owner_waiting_to_close, - ISNULL(dp.waiter_mode, '-') AS waiter_mode, - NULL AS waiter_waiter_type, - NULL AS waiter_owner_activity, - NULL AS waiter_waiter_activity, - NULL AS waiter_merging, - NULL AS waiter_spilling, - NULL AS waiter_waiting_to_close, - dp.deadlock_graph - FROM #deadlock_process AS dp - WHERE dp.victim_id IS NOT NULL - AND dp.is_parallel = 0 - - UNION ALL - - SELECT N'Parallel Deadlock' AS deadlock_type, - dp.event_date, - dp.id, - dp.victim_id, - dp.spid, - dp.database_id, - dp.priority, - dp.log_used, - dp.wait_resource COLLATE DATABASE_DEFAULT, - CONVERT( - XML, - STUFF(( SELECT DISTINCT NCHAR(10) - + N' ' - + ISNULL(c.object_name, N'') - + N' ' COLLATE DATABASE_DEFAULT AS object_name - FROM #deadlock_owner_waiter AS c - WHERE ( dp.id = c.owner_id - OR dp.victim_id = c.waiter_id ) - AND dp.event_date = c.event_date - FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(4000)'), - 1, 1, N'')) AS object_names, - dp.wait_time, - dp.transaction_name, - dp.last_tran_started, - dp.last_batch_started, - dp.last_batch_completed, - dp.lock_mode, - dp.transaction_count, - dp.client_app, - dp.host_name, - dp.login_name, - dp.isolation_level, - dp.process_xml.value('(//process/inputbuf/text())[1]', 'NVARCHAR(MAX)') AS inputbuf, - DENSE_RANK() OVER ( ORDER BY dp.event_date ) AS en, - ROW_NUMBER() OVER ( PARTITION BY dp.event_date ORDER BY dp.event_date ) -1 AS qn, - ROW_NUMBER() OVER ( PARTITION BY dp.event_date, dp.id ORDER BY dp.event_date ) AS dn, - 1 AS is_victim, - cao.wait_type COLLATE DATABASE_DEFAULT AS owner_mode, - cao.waiter_type AS owner_waiter_type, - cao.owner_activity AS owner_activity, - cao.waiter_activity AS owner_waiter_activity, - cao.merging AS owner_merging, - cao.spilling AS owner_spilling, - cao.waiting_to_close AS owner_waiting_to_close, - caw.wait_type COLLATE DATABASE_DEFAULT AS waiter_mode, - caw.waiter_type AS waiter_waiter_type, - caw.owner_activity AS waiter_owner_activity, - caw.waiter_activity AS waiter_waiter_activity, - caw.merging AS waiter_merging, - caw.spilling AS waiter_spilling, - caw.waiting_to_close AS waiter_waiting_to_close, - dp.deadlock_graph - FROM #deadlock_process AS dp - CROSS APPLY (SELECT TOP 1 * FROM #deadlock_resource_parallel AS drp WHERE drp.owner_id = dp.id AND drp.wait_type = 'e_waitPipeNewRow' ORDER BY drp.event_date) AS cao - CROSS APPLY (SELECT TOP 1 * FROM #deadlock_resource_parallel AS drp WHERE drp.owner_id = dp.id AND drp.wait_type = 'e_waitPipeGetRow' ORDER BY drp.event_date) AS caw - WHERE dp.is_parallel = 1 ) - insert into DeadLockTbl ( - ServerName, - deadlock_type, - event_date, - database_name, - spid, - deadlock_group, - query, - object_names, - isolation_level, - owner_mode, - waiter_mode, - transaction_count, - login_name, - host_name, - client_app, - wait_time, - wait_resource, - priority, - log_used, - last_tran_started, - last_batch_started, - last_batch_completed, - transaction_name, - owner_waiter_type, - owner_activity, - owner_waiter_activity, - owner_merging, - owner_spilling, - owner_waiting_to_close, - waiter_waiter_type, - waiter_owner_activity, - waiter_waiter_activity, - waiter_merging, - waiter_spilling, - waiter_waiting_to_close, - deadlock_graph - ) - SELECT @ServerName, - d.deadlock_type, - d.event_date, - DB_NAME(d.database_id) AS database_name, - d.spid, - 'Deadlock #' - + CONVERT(NVARCHAR(10), d.en) - + ', Query #' - + CASE WHEN d.qn = 0 THEN N'1' ELSE CONVERT(NVARCHAR(10), d.qn) END - + CASE WHEN d.is_victim = 1 THEN ' - VICTIM' ELSE '' END - AS deadlock_group, - CONVERT(XML, N'') AS query, - d.object_names, - d.isolation_level, - d.owner_mode, - d.waiter_mode, - d.transaction_count, - d.login_name, - d.host_name, - d.client_app, - d.wait_time, - d.wait_resource, - d.priority, - d.log_used, - d.last_tran_started, - d.last_batch_started, - d.last_batch_completed, - d.transaction_name, - /*These columns will be NULL for regular (non-parallel) deadlocks*/ - d.owner_waiter_type, - d.owner_activity, - d.owner_waiter_activity, - d.owner_merging, - d.owner_spilling, - d.owner_waiting_to_close, - d.waiter_waiter_type, - d.waiter_owner_activity, - d.waiter_waiter_activity, - d.waiter_merging, - d.waiter_spilling, - d.waiter_waiting_to_close, - d.deadlock_graph - FROM deadlocks AS d - WHERE d.dn = 1 - AND (is_victim = @VictimsOnly OR @VictimsOnly = 0) - AND d.qn < CASE WHEN d.deadlock_type = N'Parallel Deadlock' THEN 2 ELSE 2147483647 END - AND (DB_NAME(d.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (d.event_date >= @StartDate OR @StartDate IS NULL) - AND (d.event_date < @EndDate OR @EndDate IS NULL) - AND (CONVERT(NVARCHAR(MAX), d.object_names) LIKE '%' + @ObjectName + '%' OR @ObjectName IS NULL) - AND (d.client_app = @AppName OR @AppName IS NULL) - AND (d.host_name = @HostName OR @HostName IS NULL) - AND (d.login_name = @LoginName OR @LoginName IS NULL) - ORDER BY d.event_date, is_victim DESC - OPTION ( RECOMPILE ); - - drop SYNONYM DeadLockTbl; --done insert into blitzlock table going to insert into findings table first create synonym. + /*Check 15 is total deadlocks involving sleeping sessions*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Check 15 sleeping deadlocks %s', 0, 1, @d) WITH NOWAIT; - -- RAISERROR('att deadlock findings', 0, 1) WITH NOWAIT; - + INSERT + #deadlock_findings WITH(TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding + ) + SELECT + check_id = 15, + database_name = N'-', + object_name = N'-', + finding_group = N'Total deadlocks involving sleeping sessions', + finding = + N'There have been ' + + CONVERT + ( + nvarchar(20), + COUNT_BIG(DISTINCT dp.event_date) + ) + + N' sleepy deadlocks.' + FROM #deadlock_process AS dp + WHERE dp.status = N'sleeping' + HAVING COUNT_BIG(DISTINCT dp.event_date) > 0 + OPTION(RECOMPILE); - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Findings %s', 0, 1, @d) WITH NOWAIT; + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - Insert into DeadlockFindings (ServerName,check_id,database_name,object_name,finding_group,finding) - SELECT @ServerName,df.check_id, df.database_name, df.object_name, df.finding_group, df.finding - FROM #deadlock_findings AS df - ORDER BY df.check_id - OPTION ( RECOMPILE ); + /*Check 16 is total deadlocks involving implicit transactions*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Check 16 implicit transaction deadlocks %s', 0, 1, @d) WITH NOWAIT; - drop SYNONYM DeadlockFindings; --done with inserting. -END -ELSE --Output to database is not set output to client app - BEGIN - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Results 1 %s', 0, 1, @d) WITH NOWAIT; - WITH deadlocks - AS ( SELECT N'Regular Deadlock' AS deadlock_type, - dp.event_date, - dp.id, - dp.victim_id, - dp.spid, - dp.database_id, - dp.priority, - dp.log_used, - dp.wait_resource COLLATE DATABASE_DEFAULT AS wait_resource, - CONVERT( - XML, - STUFF(( SELECT DISTINCT NCHAR(10) - + N' ' - + ISNULL(c.object_name, N'') - + N' ' COLLATE DATABASE_DEFAULT AS object_name - FROM #deadlock_owner_waiter AS c - WHERE ( dp.id = c.owner_id - OR dp.victim_id = c.waiter_id ) - AND dp.event_date = c.event_date - FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(4000)'), - 1, 1, N'')) AS object_names, - dp.wait_time, - dp.transaction_name, - dp.last_tran_started, - dp.last_batch_started, - dp.last_batch_completed, - dp.lock_mode, - dp.transaction_count, - dp.client_app, - dp.host_name, - dp.login_name, - dp.isolation_level, - dp.process_xml.value('(//process/inputbuf/text())[1]', 'NVARCHAR(MAX)') AS inputbuf, - DENSE_RANK() OVER ( ORDER BY dp.event_date ) AS en, - ROW_NUMBER() OVER ( PARTITION BY dp.event_date ORDER BY dp.event_date ) -1 AS qn, - ROW_NUMBER() OVER ( PARTITION BY dp.event_date, dp.id ORDER BY dp.event_date ) AS dn, - dp.is_victim, - ISNULL(dp.owner_mode, '-') AS owner_mode, - NULL AS owner_waiter_type, - NULL AS owner_activity, - NULL AS owner_waiter_activity, - NULL AS owner_merging, - NULL AS owner_spilling, - NULL AS owner_waiting_to_close, - ISNULL(dp.waiter_mode, '-') AS waiter_mode, - NULL AS waiter_waiter_type, - NULL AS waiter_owner_activity, - NULL AS waiter_waiter_activity, - NULL AS waiter_merging, - NULL AS waiter_spilling, - NULL AS waiter_waiting_to_close, - dp.deadlock_graph - FROM #deadlock_process AS dp - WHERE dp.victim_id IS NOT NULL - AND dp.is_parallel = 0 + INSERT + #deadlock_findings WITH(TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding + ) + SELECT + check_id = 14, + database_name = N'-', + object_name = N'-', + finding_group = N'Total implicit transaction deadlocks', + finding = + N'There have been ' + + CONVERT + ( + nvarchar(20), + COUNT_BIG(DISTINCT dp.event_date) + ) + + N' implicit transaction deadlocks.' + FROM #deadlock_process AS dp + WHERE dp.transaction_name = N'implicit_transaction' + HAVING COUNT_BIG(DISTINCT dp.event_date) > 0 + OPTION(RECOMPILE); - UNION ALL - - SELECT N'Parallel Deadlock' AS deadlock_type, - dp.event_date, - dp.id, - dp.victim_id, - dp.spid, - dp.database_id, - dp.priority, - dp.log_used, - dp.wait_resource COLLATE DATABASE_DEFAULT, - CONVERT( - XML, - STUFF(( SELECT DISTINCT NCHAR(10) - + N' ' - + ISNULL(c.object_name, N'') - + N' ' COLLATE DATABASE_DEFAULT AS object_name - FROM #deadlock_owner_waiter AS c - WHERE ( dp.id = c.owner_id - OR dp.victim_id = c.waiter_id ) - AND dp.event_date = c.event_date - FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(4000)'), - 1, 1, N'')) AS object_names, - dp.wait_time, - dp.transaction_name, - dp.last_tran_started, - dp.last_batch_started, - dp.last_batch_completed, - dp.lock_mode, - dp.transaction_count, - dp.client_app, - dp.host_name, - dp.login_name, - dp.isolation_level, - dp.process_xml.value('(//process/inputbuf/text())[1]', 'NVARCHAR(MAX)') AS inputbuf, - DENSE_RANK() OVER ( ORDER BY dp.event_date ) AS en, - ROW_NUMBER() OVER ( PARTITION BY dp.event_date ORDER BY dp.event_date ) -1 AS qn, - ROW_NUMBER() OVER ( PARTITION BY dp.event_date, dp.id ORDER BY dp.event_date ) AS dn, - 1 AS is_victim, - cao.wait_type COLLATE DATABASE_DEFAULT AS owner_mode, - cao.waiter_type AS owner_waiter_type, - cao.owner_activity AS owner_activity, - cao.waiter_activity AS owner_waiter_activity, - cao.merging AS owner_merging, - cao.spilling AS owner_spilling, - cao.waiting_to_close AS owner_waiting_to_close, - caw.wait_type COLLATE DATABASE_DEFAULT AS waiter_mode, - caw.waiter_type AS waiter_waiter_type, - caw.owner_activity AS waiter_owner_activity, - caw.waiter_activity AS waiter_waiter_activity, - caw.merging AS waiter_merging, - caw.spilling AS waiter_spilling, - caw.waiting_to_close AS waiter_waiting_to_close, - dp.deadlock_graph - FROM #deadlock_process AS dp - OUTER APPLY (SELECT TOP 1 * FROM #deadlock_resource_parallel AS drp WHERE drp.owner_id = dp.id AND drp.wait_type IN ('e_waitPortOpen', 'e_waitPipeNewRow') ORDER BY drp.event_date) AS cao - OUTER APPLY (SELECT TOP 1 * FROM #deadlock_resource_parallel AS drp WHERE drp.owner_id = dp.id AND drp.wait_type IN ('e_waitPortOpen', 'e_waitPipeGetRow') ORDER BY drp.event_date) AS caw - WHERE dp.is_parallel = 1 - ) - SELECT d.deadlock_type, - d.event_date, - DB_NAME(d.database_id) AS database_name, - d.spid, - 'Deadlock #' - + CONVERT(NVARCHAR(10), d.en) - + ', Query #' - + CASE WHEN d.qn = 0 THEN N'1' ELSE CONVERT(NVARCHAR(10), d.qn) END - + CASE WHEN d.is_victim = 1 THEN ' - VICTIM' ELSE '' END - AS deadlock_group, - CONVERT(XML, N'') AS query_xml, - d.inputbuf AS query_string, - d.object_names, - d.isolation_level, - d.owner_mode, - d.waiter_mode, - d.transaction_count, - d.login_name, - d.host_name, - d.client_app, - d.wait_time, - d.wait_resource, - d.priority, - d.log_used, - d.last_tran_started, - d.last_batch_started, - d.last_batch_completed, - d.transaction_name, - /*These columns will be NULL for regular (non-parallel) deadlocks*/ - d.owner_waiter_type, - d.owner_activity, - d.owner_waiter_activity, - d.owner_merging, - d.owner_spilling, - d.owner_waiting_to_close, - d.waiter_waiter_type, - d.waiter_owner_activity, - d.waiter_waiter_activity, - d.waiter_merging, - d.waiter_spilling, - d.waiter_waiting_to_close, - d.deadlock_graph, - d.is_victim - INTO #deadlock_results - FROM deadlocks AS d - WHERE d.dn = 1 - AND (d.is_victim = @VictimsOnly OR @VictimsOnly = 0) - AND d.qn < CASE WHEN d.deadlock_type = N'Parallel Deadlock' THEN 2 ELSE 2147483647 END - AND (DB_NAME(d.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (d.event_date >= @StartDate OR @StartDate IS NULL) - AND (d.event_date < @EndDate OR @EndDate IS NULL) - AND (CONVERT(NVARCHAR(MAX), d.object_names) LIKE '%' + @ObjectName + '%' OR @ObjectName IS NULL) - AND (d.client_app = @AppName OR @AppName IS NULL) - AND (d.host_name = @HostName OR @HostName IS NULL) - AND (d.login_name = @LoginName OR @LoginName IS NULL) - OPTION ( RECOMPILE ); - + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - DECLARE @deadlock_result NVARCHAR(MAX) = N'' + /*Thank you goodnight*/ + INSERT + #deadlock_findings WITH(TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding + ) + VALUES + ( + -1, + N'sp_BlitzLock version ' + CONVERT(nvarchar(10), @Version), + N'Results for ' + CONVERT(nvarchar(10), @StartDate, 23) + N' through ' + CONVERT(nvarchar(10), @EndDate, 23), + N'http://FirstResponderKit.org/', + N'To get help or add your own contributions to the SQL Server First Responder Kit, join us at http://FirstResponderKit.org.' + ); - SET @deadlock_result += N' - SELECT - dr.deadlock_type, - dr.event_date, - dr.database_name, - dr.spid, - dr.deadlock_group, - ' - + CASE @ExportToExcel - WHEN 1 - THEN N'dr.query_string AS query, - REPLACE(REPLACE(CONVERT(NVARCHAR(MAX), dr.object_names), '''', ''''), '''', '''') AS object_names,' - ELSE N'dr.query_xml AS query, - dr.object_names,' - END + - N' - dr.isolation_level, - dr.owner_mode, - dr.waiter_mode, - dr.transaction_count, - dr.login_name, - dr.host_name, - dr.client_app, - dr.wait_time, - dr.wait_resource, - dr.priority, - dr.log_used, - dr.last_tran_started, - dr.last_batch_started, - dr.last_batch_completed, - dr.transaction_name, - dr.owner_waiter_type, - dr.owner_activity, - dr.owner_waiter_activity, - dr.owner_merging, - dr.owner_spilling, - dr.owner_waiting_to_close, - dr.waiter_waiter_type, - dr.waiter_owner_activity, - dr.waiter_waiter_activity, - dr.waiter_merging, - dr.waiter_spilling, - dr.waiter_waiting_to_close' - + CASE @ExportToExcel - WHEN 1 - THEN N'' - ELSE N', - dr.deadlock_graph' - END + - ' - FROM #deadlock_results AS dr - ORDER BY dr.event_date, dr.is_victim DESC - OPTION(RECOMPILE); - ' - - EXEC sys.sp_executesql - @deadlock_result; - - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Findings %s', 0, 1, @d) WITH NOWAIT; - SELECT df.check_id, df.database_name, df.object_name, df.finding_group, df.finding - FROM #deadlock_findings AS df - ORDER BY df.check_id - OPTION ( RECOMPILE ); - - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Done %s', 0, 1, @d) WITH NOWAIT; - END --done with output to client app. + RAISERROR('Finished rollup at %s', 0, 1, @d) WITH NOWAIT; + /*Results*/ + BEGIN + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Results 1 %s', 0, 1, @d) WITH NOWAIT; + CREATE CLUSTERED INDEX cx_whatever_dp ON #deadlock_process (event_date, id); + CREATE CLUSTERED INDEX cx_whatever_drp ON #deadlock_resource_parallel (event_date, owner_id); + CREATE CLUSTERED INDEX cx_whatever_dow ON #deadlock_owner_waiter (event_date, owner_id, waiter_id); - IF @Debug = 1 + IF @Debug = 1 BEGIN SET STATISTICS XML ON; END; + + WITH + deadlocks AS + ( + SELECT + deadlock_type = + N'Regular Deadlock', + dp.event_date, + dp.id, + dp.victim_id, + dp.spid, + dp.database_id, + dp.database_name, + dp.current_database_name, + dp.priority, + dp.log_used, + wait_resource = + dp.wait_resource COLLATE DATABASE_DEFAULT, + object_names = + CONVERT + ( + xml, + STUFF + ( + ( + SELECT DISTINCT + object_name = + NCHAR(10) + + N' ' + + ISNULL(c.object_name, N'') + + N' ' COLLATE DATABASE_DEFAULT + FROM #deadlock_owner_waiter AS c + WHERE (dp.id = c.owner_id + OR dp.victim_id = c.waiter_id) + AND dp.event_date = c.event_date + FOR XML + PATH(N''), + TYPE + ).value(N'.[1]', N'nvarchar(4000)'), + 1, + 1, + N'' + ) + ), + dp.wait_time, + dp.transaction_name, + dp.status, + dp.last_tran_started, + dp.last_batch_started, + dp.last_batch_completed, + dp.lock_mode, + dp.transaction_count, + dp.client_app, + dp.host_name, + dp.login_name, + dp.isolation_level, + dp.client_option_1, + dp.client_option_2, + inputbuf = + dp.process_xml.value('(//process/inputbuf/text())[1]', 'nvarchar(MAX)'), + en = + DENSE_RANK() OVER (ORDER BY dp.event_date), + qn = + ROW_NUMBER() OVER (PARTITION BY dp.event_date ORDER BY dp.event_date) - 1, + dn = + ROW_NUMBER() OVER (PARTITION BY dp.event_date, dp.id ORDER BY dp.event_date), + dp.is_victim, + owner_mode = + ISNULL(dp.owner_mode, N'-'), + owner_waiter_type = NULL, + owner_activity = NULL, + owner_waiter_activity = NULL, + owner_merging = NULL, + owner_spilling = NULL, + owner_waiting_to_close = NULL, + waiter_mode = + ISNULL(dp.waiter_mode, N'-'), + waiter_waiter_type = NULL, + waiter_owner_activity = NULL, + waiter_waiter_activity = NULL, + waiter_merging = NULL, + waiter_spilling = NULL, + waiter_waiting_to_close = NULL, + dp.deadlock_graph + FROM #deadlock_process AS dp + WHERE dp.victim_id IS NOT NULL + AND dp.is_parallel = 0 + + UNION ALL + + SELECT + deadlock_type = + N'Parallel Deadlock', + dp.event_date, + dp.id, + dp.victim_id, + dp.spid, + dp.database_id, + dp.database_name, + dp.current_database_name, + dp.priority, + dp.log_used, + dp.wait_resource COLLATE DATABASE_DEFAULT, + object_names = + CONVERT + ( + xml, + STUFF + ( + ( + SELECT DISTINCT + object_name = + NCHAR(10) + + N' ' + + ISNULL(c.object_name, N'') + + N' ' COLLATE DATABASE_DEFAULT + FROM #deadlock_owner_waiter AS c + WHERE (dp.id = c.owner_id + OR dp.victim_id = c.waiter_id) + AND dp.event_date = c.event_date + FOR XML + PATH(N''), + TYPE + ).value(N'.[1]', N'nvarchar(4000)'), + 1, + 1, + N'' + ) + ), + dp.wait_time, + dp.transaction_name, + dp.status, + dp.last_tran_started, + dp.last_batch_started, + dp.last_batch_completed, + dp.lock_mode, + dp.transaction_count, + dp.client_app, + dp.host_name, + dp.login_name, + dp.isolation_level, + dp.client_option_1, + dp.client_option_2, + inputbuf = + dp.process_xml.value('(//process/inputbuf/text())[1]', 'nvarchar(MAX)'), + en = + DENSE_RANK() OVER (ORDER BY dp.event_date), + qn = + ROW_NUMBER() OVER (PARTITION BY dp.event_date ORDER BY dp.event_date) - 1, + dn = + ROW_NUMBER() OVER (PARTITION BY dp.event_date, dp.id ORDER BY dp.event_date), + is_victim = 1, + owner_mode = cao.wait_type COLLATE DATABASE_DEFAULT, + owner_waiter_type = cao.waiter_type, + owner_activity = cao.owner_activity, + owner_waiter_activity = cao.waiter_activity, + owner_merging = cao.merging, + owner_spilling = cao.spilling, + owner_waiting_to_close = cao.waiting_to_close, + waiter_mode = caw.wait_type COLLATE DATABASE_DEFAULT, + waiter_waiter_type = caw.waiter_type, + waiter_owner_activity = caw.owner_activity, + waiter_waiter_activity = caw.waiter_activity, + waiter_merging = caw.merging, + waiter_spilling = caw.spilling, + waiter_waiting_to_close = caw.waiting_to_close, + dp.deadlock_graph + FROM #deadlock_process AS dp + OUTER APPLY + ( + SELECT TOP (1) + drp.* + FROM #deadlock_resource_parallel AS drp + WHERE drp.owner_id = dp.id + AND drp.wait_type IN + ( + N'e_waitPortOpen', + N'e_waitPipeNewRow' + ) + ORDER BY drp.event_date + ) AS cao + OUTER APPLY + ( + SELECT TOP (1) + drp.* + FROM #deadlock_resource_parallel AS drp + WHERE drp.owner_id = dp.id + AND drp.wait_type IN + ( + N'e_waitPortOpen', + N'e_waitPipeGetRow' + ) + ORDER BY drp.event_date + ) AS caw + WHERE dp.is_parallel = 1 + ) + SELECT + d.deadlock_type, + d.event_date, + d.id, + d.victim_id, + d.spid, + deadlock_group = + N'Deadlock #' + + CONVERT + ( + nvarchar(10), + d.en + ) + + N', Query #' + + CASE + WHEN d.qn = 0 + THEN N'1' + ELSE CONVERT(nvarchar(10), d.qn) + END + CASE + WHEN d.is_victim = 1 + THEN N' - VICTIM' + ELSE N'' + END, + d.database_id, + d.database_name, + d.current_database_name, + d.priority, + d.log_used, + d.wait_resource, + d.object_names, + d.wait_time, + d.transaction_name, + d.status, + d.last_tran_started, + d.last_batch_started, + d.last_batch_completed, + d.lock_mode, + d.transaction_count, + d.client_app, + d.host_name, + d.login_name, + d.isolation_level, + d.client_option_1, + d.client_option_2, + inputbuf = + CASE + WHEN d.inputbuf + LIKE @inputbuf_bom + N'Proc |[Database Id = %' ESCAPE N'|' + THEN + OBJECT_SCHEMA_NAME + ( + SUBSTRING + ( + d.inputbuf, + CHARINDEX(N'Object Id = ', d.inputbuf) + 12, + LEN(d.inputbuf) - (CHARINDEX(N'Object Id = ', d.inputbuf) + 12) + ) + , + SUBSTRING + ( + d.inputbuf, + CHARINDEX(N'Database Id = ', d.inputbuf) + 14, + CHARINDEX(N'Object Id', d.inputbuf) - (CHARINDEX(N'Database Id = ', d.inputbuf) + 14) + ) + ) + + N'.' + + OBJECT_NAME + ( + SUBSTRING + ( + d.inputbuf, + CHARINDEX(N'Object Id = ', d.inputbuf) + 12, + LEN(d.inputbuf) - (CHARINDEX(N'Object Id = ', d.inputbuf) + 12) + ) + , + SUBSTRING + ( + d.inputbuf, + CHARINDEX(N'Database Id = ', d.inputbuf) + 14, + CHARINDEX(N'Object Id', d.inputbuf) - (CHARINDEX(N'Database Id = ', d.inputbuf) + 14) + ) + ) + ELSE d.inputbuf + END COLLATE Latin1_General_BIN2, + d.owner_mode, + d.owner_waiter_type, + d.owner_activity, + d.owner_waiter_activity, + d.owner_merging, + d.owner_spilling, + d.owner_waiting_to_close, + d.waiter_mode, + d.waiter_waiter_type, + d.waiter_owner_activity, + d.waiter_waiter_activity, + d.waiter_merging, + d.waiter_spilling, + d.waiter_waiting_to_close, + d.deadlock_graph, + d.is_victim + INTO #deadlocks + FROM deadlocks AS d + WHERE d.dn = 1 + AND (d.is_victim = @VictimsOnly + OR @VictimsOnly = 0) + AND d.qn < CASE + WHEN d.deadlock_type = N'Parallel Deadlock' + THEN 2 + ELSE 2147483647 + END + AND (DB_NAME(d.database_id) = @DatabaseName OR @DatabaseName IS NULL) + AND (d.event_date >= @StartDate OR @StartDate IS NULL) + AND (d.event_date < @EndDate OR @EndDate IS NULL) + AND (CONVERT(nvarchar(MAX), d.object_names) COLLATE Latin1_General_BIN2 LIKE N'%' + @ObjectName + N'%' OR @ObjectName IS NULL) + AND (d.client_app = @AppName OR @AppName IS NULL) + AND (d.host_name = @HostName OR @HostName IS NULL) + AND (d.login_name = @LoginName OR @LoginName IS NULL) + OPTION (RECOMPILE, LOOP JOIN, HASH JOIN); + + UPDATE d + SET d.inputbuf = + REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE( + REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE( + REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE( + d.inputbuf, + NCHAR(31), N'?'), NCHAR(30), N'?'), NCHAR(29), N'?'), NCHAR(28), N'?'), NCHAR(27), N'?'), + NCHAR(26), N'?'), NCHAR(25), N'?'), NCHAR(24), N'?'), NCHAR(23), N'?'), NCHAR(22), N'?'), + NCHAR(21), N'?'), NCHAR(20), N'?'), NCHAR(19), N'?'), NCHAR(18), N'?'), NCHAR(17), N'?'), + NCHAR(16), N'?'), NCHAR(15), N'?'), NCHAR(14), N'?'), NCHAR(12), N'?'), NCHAR(11), N'?'), + NCHAR(8), N'?'), NCHAR(7), N'?'), NCHAR(6), N'?'), NCHAR(5), N'?'), NCHAR(4), N'?'), + NCHAR(3), N'?'), NCHAR(2), N'?'), NCHAR(1), N'?'), NCHAR(0), N'?') + FROM #deadlocks AS d + OPTION(RECOMPILE); + + SELECT + d.deadlock_type, + d.event_date, + database_name = + DB_NAME(d.database_id), + database_name_x = + d.database_name, + d.current_database_name, + d.spid, + d.deadlock_group, + d.client_option_1, + d.client_option_2, + d.lock_mode, + query_xml = + ( + SELECT + [processing-instruction(query)] = + d.inputbuf + FOR XML + PATH(N''), + TYPE + ), + query_string = + d.inputbuf, + d.object_names, + d.isolation_level, + d.owner_mode, + d.waiter_mode, + d.transaction_count, + d.login_name, + d.host_name, + d.client_app, + d.wait_time, + d.wait_resource, + d.priority, + d.log_used, + d.last_tran_started, + d.last_batch_started, + d.last_batch_completed, + d.transaction_name, + d.status, + /*These columns will be NULL for regular (non-parallel) deadlocks*/ + parallel_deadlock_details = + ( + SELECT + d.owner_waiter_type, + d.owner_activity, + d.owner_waiter_activity, + d.owner_merging, + d.owner_spilling, + d.owner_waiting_to_close, + d.waiter_waiter_type, + d.waiter_owner_activity, + d.waiter_waiter_activity, + d.waiter_merging, + d.waiter_spilling, + d.waiter_waiting_to_close + FOR XML + PATH('parallel_deadlock_details'), + TYPE + ), + d.owner_waiter_type, + d.owner_activity, + d.owner_waiter_activity, + d.owner_merging, + d.owner_spilling, + d.owner_waiting_to_close, + d.waiter_waiter_type, + d.waiter_owner_activity, + d.waiter_waiter_activity, + d.waiter_merging, + d.waiter_spilling, + d.waiter_waiting_to_close, + /*end parallel deadlock columns*/ + d.deadlock_graph, + d.is_victim + INTO #deadlock_results + FROM #deadlocks AS d; + + IF @Debug = 1 BEGIN SET STATISTICS XML OFF; END; + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + + /*There's too much risk of errors sending the*/ + IF @OutputDatabaseCheck = 0 + BEGIN + SET @ExportToExcel = 0; + END; + + SET @deadlock_result += N' + SELECT + server_name = + @@SERVERNAME, + dr.deadlock_type, + dr.event_date, + database_name = + COALESCE + ( + dr.database_name, + dr.database_name_x, + dr.current_database_name + ), + dr.spid, + dr.deadlock_group, + ' + CASE @ExportToExcel + WHEN 1 + THEN N' + query = dr.query_string, + object_names = + REPLACE( + REPLACE( + CONVERT + ( + nvarchar(MAX), + dr.object_names + ) COLLATE Latin1_General_BIN2, + '''', ''''), + '''', ''''),' + ELSE N'query = dr.query_xml, + dr.object_names,' + END + N' + dr.isolation_level, + dr.owner_mode, + dr.waiter_mode, + dr.lock_mode, + dr.transaction_count, + dr.client_option_1, + dr.client_option_2, + dr.login_name, + dr.host_name, + dr.client_app, + dr.wait_time, + dr.wait_resource, + dr.priority, + dr.log_used, + dr.last_tran_started, + dr.last_batch_started, + dr.last_batch_completed, + dr.transaction_name, + dr.status,' + + CASE + WHEN (@ExportToExcel = 1 + OR @OutputDatabaseCheck = 0) + THEN N' + dr.owner_waiter_type, + dr.owner_activity, + dr.owner_waiter_activity, + dr.owner_merging, + dr.owner_spilling, + dr.owner_waiting_to_close, + dr.waiter_waiter_type, + dr.waiter_owner_activity, + dr.waiter_waiter_activity, + dr.waiter_merging, + dr.waiter_spilling, + dr.waiter_waiting_to_close,' + ELSE N' + dr.parallel_deadlock_details,' + END + + CASE + @ExportToExcel + WHEN 1 + THEN N' + deadlock_graph = + REPLACE(REPLACE( + REPLACE(REPLACE( + CONVERT + ( + nvarchar(MAX), + dr.deadlock_graph + ) COLLATE Latin1_General_BIN2, + ''NCHAR(10)'', ''''), ''NCHAR(13)'', ''''), + ''CHAR(10)'', ''''), ''CHAR(13)'', '''')' + ELSE N' + dr.deadlock_graph' + END + N' + FROM #deadlock_results AS dr + ORDER BY + dr.event_date, + dr.is_victim DESC + OPTION(RECOMPILE, LOOP JOIN, HASH JOIN); + '; + + IF (@OutputDatabaseCheck = 0) + BEGIN + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Results to table %s', 0, 1, @d) WITH NOWAIT; + + IF @Debug = 1 + BEGIN + PRINT @deadlock_result; + SET STATISTICS XML ON; + END; + + INSERT INTO + DeadLockTbl + ( + ServerName, + deadlock_type, + event_date, + database_name, + spid, + deadlock_group, + query, + object_names, + isolation_level, + owner_mode, + waiter_mode, + lock_mode, + transaction_count, + client_option_1, + client_option_2, + login_name, + host_name, + client_app, + wait_time, + wait_resource, + priority, + log_used, + last_tran_started, + last_batch_started, + last_batch_completed, + transaction_name, + status, + owner_waiter_type, + owner_activity, + owner_waiter_activity, + owner_merging, + owner_spilling, + owner_waiting_to_close, + waiter_waiter_type, + waiter_owner_activity, + waiter_waiter_activity, + waiter_merging, + waiter_spilling, + waiter_waiting_to_close, + deadlock_graph + ) + EXEC sys.sp_executesql + @deadlock_result; + + IF @Debug = 1 BEGIN + SET STATISTICS XML OFF; + END; + + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - SELECT '#deadlock_data' AS table_name, * - FROM #deadlock_data AS dd - OPTION ( RECOMPILE ); + DROP SYNONYM DeadLockTbl; - SELECT '#deadlock_resource' AS table_name, * - FROM #deadlock_resource AS dr - OPTION ( RECOMPILE ); + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Findings to table %s', 0, 1, @d) WITH NOWAIT; + + INSERT INTO + DeadlockFindings + ( + ServerName, + check_id, + database_name, + object_name, + finding_group, + finding + ) + SELECT + @@SERVERNAME, + df.check_id, + df.database_name, + df.object_name, + df.finding_group, + df.finding + FROM #deadlock_findings AS df + ORDER BY df.check_id + OPTION(RECOMPILE); + + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + + DROP SYNONYM DeadlockFindings; /*done with inserting.*/ + END; + ELSE /*Output to database is not set output to client app*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Results to client %s', 0, 1, @d) WITH NOWAIT; - SELECT '#deadlock_resource_parallel' AS table_name, * - FROM #deadlock_resource_parallel AS drp - OPTION ( RECOMPILE ); + IF @Debug = 1 BEGIN SET STATISTICS XML ON; END; - SELECT '#deadlock_owner_waiter' AS table_name, * - FROM #deadlock_owner_waiter AS dow - OPTION ( RECOMPILE ); + EXEC sys.sp_executesql + @deadlock_result; - SELECT '#deadlock_process' AS table_name, * - FROM #deadlock_process AS dp - OPTION ( RECOMPILE ); + IF @Debug = 1 + BEGIN + SET STATISTICS XML OFF; + PRINT @deadlock_result; + END; - SELECT '#deadlock_stack' AS table_name, * - FROM #deadlock_stack AS ds - OPTION ( RECOMPILE ); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - SELECT '#deadlock_results' AS table_name, * - FROM #deadlock_results AS dr - OPTION ( RECOMPILE ); + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Returning findings %s', 0, 1, @d) WITH NOWAIT; - END; -- End debug + SELECT + df.check_id, + df.database_name, + df.object_name, + df.finding_group, + df.finding + FROM #deadlock_findings AS df + ORDER BY df.check_id + OPTION(RECOMPILE); + + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + END; /*done with output to client app.*/ - END; --Final End + IF @Debug = 1 + BEGIN + SELECT + table_name = N'#dd', + * + FROM #dd AS d + OPTION(RECOMPILE); + + SELECT + table_name = N'#deadlock_data', + * + FROM #deadlock_data AS dd + OPTION(RECOMPILE); + + SELECT + table_name = N'#deadlock_resource', + * + FROM #deadlock_resource AS dr + OPTION(RECOMPILE); + + SELECT + table_name = N'#deadlock_resource_parallel', + * + FROM #deadlock_resource_parallel AS drp + OPTION(RECOMPILE); + + SELECT + table_name = N'#deadlock_owner_waiter', + * + FROM #deadlock_owner_waiter AS dow + OPTION(RECOMPILE); + + SELECT + table_name = N'#deadlock_process', + * + FROM #deadlock_process AS dp + OPTION(RECOMPILE); + + SELECT + table_name = N'#deadlock_stack', + * + FROM #deadlock_stack AS ds + OPTION(RECOMPILE); + + SELECT + table_name = N'#deadlocks', + * + FROM #deadlocks AS d + OPTION(RECOMPILE); + + SELECT + table_name = N'#deadlock_results', + * + FROM #deadlock_results AS dr + OPTION(RECOMPILE); + + SELECT + table_name = N'#x', + * + FROM #x AS x + OPTION(RECOMPILE); + + SELECT + table_name = N'@sysAssObjId', + * + FROM @sysAssObjId AS s + OPTION(RECOMPILE); + + END; /*End debug*/ + END; /*Final End*/ GO SET ANSI_NULLS ON; @@ -30100,7 +32240,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.11', @VersionDate = '20221013'; +SELECT @Version = '8.12', @VersionDate = '20221213'; IF(@VersionCheckMode = 1) BEGIN RETURN; @@ -32349,7 +34489,7 @@ RAISERROR(N'Gathering working plans', 0, 1) WITH NOWAIT; SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; SET @sql_select += N' SELECT ' + QUOTENAME(@DatabaseName, '''') + N' AS database_name, wp.plan_id, wp.query_id, - qsp.plan_group_id, qsp.engine_version, qsp.compatibility_level, qsp.query_plan_hash, TRY_CONVERT(XML, qsp.query_plan), qsp.is_online_index_plan, qsp.is_trivial_plan, + qsp.plan_group_id, qsp.engine_version, qsp.compatibility_level, qsp.query_plan_hash, TRY_CAST(qsp.query_plan AS XML), qsp.is_online_index_plan, qsp.is_trivial_plan, qsp.is_parallel_plan, qsp.is_forced_plan, qsp.is_natively_compiled, qsp.force_failure_count, qsp.last_force_failure_reason_desc, qsp.count_compiles, qsp.initial_compile_start_time, qsp.last_compile_start_time, qsp.last_execution_time, (qsp.avg_compile_duration / 1000.), @@ -35831,7 +37971,7 @@ BEGIN SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.11', @VersionDate = '20221013'; + SELECT @Version = '8.12', @VersionDate = '20221213'; IF(@VersionCheckMode = 1) BEGIN @@ -36476,6 +38616,16 @@ BEGIN */ SET @StringToExecute = N'COALESCE( RIGHT(''00'' + CONVERT(VARCHAR(20), (ABS(r.total_elapsed_time) / 1000) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), (DATEADD(SECOND, (r.total_elapsed_time / 1000), 0) + DATEADD(MILLISECOND, (r.total_elapsed_time % 1000), 0)), 114), RIGHT(''00'' + CONVERT(VARCHAR(20), DATEDIFF(SECOND, s.last_request_start_time, GETDATE()) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), DATEADD(SECOND, DATEDIFF(SECOND, s.last_request_start_time, GETDATE()), 0), 114) ) AS [elapsed_time] , s.session_id , + CASE WHEN r.blocking_session_id <> 0 AND blocked.session_id IS NULL + THEN r.blocking_session_id + WHEN r.blocking_session_id <> 0 AND s.session_id <> blocked.blocking_session_id + THEN blocked.blocking_session_id + WHEN r.blocking_session_id = 0 AND s.session_id = blocked.session_id + THEN blocked.blocking_session_id + WHEN r.blocking_session_id <> 0 AND s.session_id = blocked.blocking_session_id + THEN r.blocking_session_id + ELSE NULL + END AS blocking_session_id, COALESCE(DB_NAME(r.database_id), DB_NAME(blocked.dbid), ''N/A'') AS database_name, ISNULL(SUBSTRING(dest.text, ( query_stats.statement_start_offset / 2 ) + 1, @@ -36496,16 +38646,6 @@ BEGIN ELSE NULL END AS wait_info , r.wait_resource , - CASE WHEN r.blocking_session_id <> 0 AND blocked.session_id IS NULL - THEN r.blocking_session_id - WHEN r.blocking_session_id <> 0 AND s.session_id <> blocked.blocking_session_id - THEN blocked.blocking_session_id - WHEN r.blocking_session_id = 0 AND s.session_id = blocked.session_id - THEN blocked.blocking_session_id - WHEN r.blocking_session_id <> 0 AND s.session_id = blocked.blocking_session_id - THEN r.blocking_session_id - ELSE NULL - END AS blocking_session_id, COALESCE(r.open_transaction_count, blocked.open_tran) AS open_transaction_count , CASE WHEN EXISTS ( SELECT 1 FROM sys.dm_tran_active_transactions AS tat @@ -36694,8 +38834,18 @@ IF @ProductVersionMajor >= 11 */ SELECT @StringToExecute = N'COALESCE( RIGHT(''00'' + CONVERT(VARCHAR(20), (ABS(r.total_elapsed_time) / 1000) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), (DATEADD(SECOND, (r.total_elapsed_time / 1000), 0) + DATEADD(MILLISECOND, (r.total_elapsed_time % 1000), 0)), 114), RIGHT(''00'' + CONVERT(VARCHAR(20), DATEDIFF(SECOND, s.last_request_start_time, GETDATE()) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), DATEADD(SECOND, DATEDIFF(SECOND, s.last_request_start_time, GETDATE()), 0), 114) ) AS [elapsed_time] , s.session_id , - COALESCE(DB_NAME(r.database_id), DB_NAME(blocked.dbid), ''N/A'') AS database_name, - ISNULL(SUBSTRING(dest.text, + CASE WHEN r.blocking_session_id <> 0 AND blocked.session_id IS NULL + THEN r.blocking_session_id + WHEN r.blocking_session_id <> 0 AND s.session_id <> blocked.blocking_session_id + THEN blocked.blocking_session_id + WHEN r.blocking_session_id = 0 AND s.session_id = blocked.session_id + THEN blocked.blocking_session_id + WHEN r.blocking_session_id <> 0 AND s.session_id = blocked.blocking_session_id + THEN r.blocking_session_id + ELSE NULL + END AS blocking_session_id, + COALESCE(DB_NAME(r.database_id), DB_NAME(blocked.dbid), ''N/A'') AS database_name, + ISNULL(SUBSTRING(dest.text, ( query_stats.statement_start_offset / 2 ) + 1, ( ( CASE query_stats.statement_end_offset WHEN -1 THEN DATALENGTH(dest.text) @@ -36739,17 +38889,7 @@ IF @ProductVersionMajor >= 11 ELSE N' NULL AS top_session_waits ,' END + - N'CASE WHEN r.blocking_session_id <> 0 AND blocked.session_id IS NULL - THEN r.blocking_session_id - WHEN r.blocking_session_id <> 0 AND s.session_id <> blocked.blocking_session_id - THEN blocked.blocking_session_id - WHEN r.blocking_session_id = 0 AND s.session_id = blocked.session_id - THEN blocked.blocking_session_id - WHEN r.blocking_session_id <> 0 AND s.session_id = blocked.blocking_session_id - THEN r.blocking_session_id - ELSE NULL - END AS blocking_session_id, - COALESCE(r.open_transaction_count, blocked.open_tran) AS open_transaction_count , + N'COALESCE(r.open_transaction_count, blocked.open_tran) AS open_transaction_count , CASE WHEN EXISTS ( SELECT 1 FROM sys.dm_tran_active_transactions AS tat JOIN sys.dm_tran_session_transactions AS tst @@ -37053,6 +39193,7 @@ IF @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @Output ,CheckDate ,[elapsed_time] ,[session_id] + ,[blocking_session_id] ,[database_name] ,[query_text]' + CASE WHEN @GetOuterCommand = 1 THEN N',[outer_command]' ELSE N'' END + N' @@ -37065,7 +39206,6 @@ IF @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @Output ,[wait_info] ,[wait_resource]' + CASE WHEN @ProductVersionMajor >= 11 THEN N',[top_session_waits]' ELSE N'' END + N' - ,[blocking_session_id] ,[open_transaction_count] ,[is_implicit_transaction] ,[nt_domain] @@ -37228,7 +39368,7 @@ SET STATISTICS XML OFF; /*Versioning details*/ -SELECT @Version = '8.11', @VersionDate = '20221013'; +SELECT @Version = '8.12', @VersionDate = '20221213'; IF(@VersionCheckMode = 1) BEGIN @@ -37538,6 +39678,9 @@ CREATE TABLE #Headers KeyAlgorithm NVARCHAR(32), EncryptorThumbprint VARBINARY(20), EncryptorType NVARCHAR(32), + LastValidRestoreTime DATETIME, + TimeZone NVARCHAR(256), + CompressionAlgorithm NVARCHAR(256), -- -- Seq added to retain order by -- @@ -37707,6 +39850,9 @@ IF @MajorVersion >= 11 IF @MajorVersion >= 13 OR (@MajorVersion = 12 AND @BuildVersion >= 2342) SET @HeadersSQL += N', KeyAlgorithm, EncryptorThumbprint, EncryptorType'; +IF @MajorVersion >= 16 + SET @HeadersSQL += N', LastValidRestoreTime, TimeZone, CompressionAlgorithm'; + SET @HeadersSQL += N')' + NCHAR(13) + NCHAR(10); SET @HeadersSQL += N'EXEC (''RESTORE HEADERONLY FROM DISK=''''{Path}'''''')'; @@ -37822,15 +39968,19 @@ BEGIN /*End folder sanity check*/ IF @StopAt IS NOT NULL - BEGIN - DELETE - FROM @FileList - WHERE BackupFile LIKE N'%.bak' - AND - BackupFile LIKE N'%' + @Database + N'%' - AND - (REPLACE( RIGHT( REPLACE( BackupFile, RIGHT( BackupFile, PATINDEX( '%_[0-9][0-9]%', REVERSE( BackupFile ) ) ), '' ), 16 ), '_', '' ) > @StopAt); - END + BEGIN + DELETE + FROM @FileList + WHERE BackupFile LIKE N'%[_][0-9].bak' + AND BackupFile LIKE N'%' + @Database + N'%' + AND (REPLACE( RIGHT( REPLACE( BackupFile, RIGHT( BackupFile, PATINDEX( '%_[0-9][0-9]%', REVERSE( BackupFile ) ) ), '' ), 16 ), '_', '' ) > @StopAt); + + DELETE + FROM @FileList + WHERE BackupFile LIKE N'%[_][0-9][0-9].bak' + AND BackupFile LIKE N'%' + @Database + N'%' + AND (REPLACE( RIGHT( REPLACE( BackupFile, RIGHT( BackupFile, PATINDEX( '%_[0-9][0-9]%', REVERSE( BackupFile ) ) ), '' ), 18 ), '_', '' ) > @StopAt); + END; -- Find latest full backup SELECT @LastFullBackup = MAX(BackupFile) @@ -38794,7 +40944,7 @@ BEGIN SET NOCOUNT ON; SET STATISTICS XML OFF; - SELECT @Version = '8.11', @VersionDate = '20221013'; + SELECT @Version = '8.12', @VersionDate = '20221213'; IF(@VersionCheckMode = 1) BEGIN @@ -39140,7 +41290,7 @@ DELETE FROM dbo.SqlServerVersions; INSERT INTO dbo.SqlServerVersions (MajorVersionNumber, MinorVersionNumber, Branch, [Url], ReleaseDate, MainstreamSupportEndDate, ExtendedSupportEndDate, MajorVersionName, MinorVersionName) VALUES - (16, 600, 'CTP2', 'https://support.microsoft.com/en-us/help/5011644', '2022-05-24', '2022-05-24', '2022-05-24', 'SQL Server 2022', 'CTP 2.0'), + (16, 1000, 'RTM', '', '2022-11-15', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'RTM'), (15, 4261, 'CU18', 'https://support.microsoft.com/en-us/help/5017593', '2022-09-28', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 18'), (15, 4249, 'CU17', 'https://support.microsoft.com/en-us/help/5016394', '2022-08-11', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 17'), (15, 4236, 'CU16 GDR', 'https://support.microsoft.com/en-us/help/5014353', '2022-06-14', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 16 GDR'), @@ -39556,7 +41706,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.11', @VersionDate = '20221013'; +SELECT @Version = '8.12', @VersionDate = '20221213'; IF(@VersionCheckMode = 1) BEGIN @@ -40641,7 +42791,7 @@ BEGIN @StockDetailsFooter = @StockDetailsFooter + @LineFeed + ' -- ?>'; /* Get the instance name to use as a Perfmon counter prefix. */ - IF SERVERPROPERTY('EngineEdition') = 5 /*CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) = 'SQL Azure'*/ + IF SERVERPROPERTY('EngineEdition') IN (5, 8) /*CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) = 'SQL Azure'*/ SELECT TOP 1 @ServiceName = LEFT(object_name, (CHARINDEX(':', object_name) - 1)) FROM sys.dm_os_performance_counters; ELSE @@ -42026,7 +44176,23 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, ; IF SERVERPROPERTY('EngineEdition') <> 5 /*SERVERPROPERTY('Edition') <> 'SQL Azure'*/ - EXEC sp_MSforeachdb @StringToExecute; + BEGIN + BEGIN TRY + EXEC sp_MSforeachdb @StringToExecute; + END TRY + BEGIN CATCH + IF (ERROR_NUMBER() = 1222) + BEGIN + INSERT INTO #UpdatedStats(HowToStopIt, RowsForSorting) + SELECT HowToStopIt = N'No information could be retrieved as the lock timeout was exceeded while iterating databases,' + + N' this is likely due to an Index operation in Progress', -1; + END + ELSE + BEGIN + THROW; + END + END CATCH + END ELSE EXEC(@StringToExecute); diff --git a/Install-Core-Blitz-No-Query-Store.sql b/Install-Core-Blitz-No-Query-Store.sql index 73407f16d..9529b2a44 100644 --- a/Install-Core-Blitz-No-Query-Store.sql +++ b/Install-Core-Blitz-No-Query-Store.sql @@ -38,7 +38,7 @@ AS SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.11', @VersionDate = '20221013'; + SELECT @Version = '8.12', @VersionDate = '20221213'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -1090,7 +1090,7 @@ AS ) OR ( - Convert(datetime,ll.Value) < DATEADD(dd,-7, GETDATE()) + Convert(datetime,ll.Value,21) < DATEADD(dd,-7, GETDATE()) ) ); @@ -9737,7 +9737,7 @@ AS SET NOCOUNT ON; SET STATISTICS XML OFF; -SELECT @Version = '8.11', @VersionDate = '20221013'; +SELECT @Version = '8.12', @VersionDate = '20221213'; IF(@VersionCheckMode = 1) BEGIN @@ -10615,7 +10615,7 @@ AS SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.11', @VersionDate = '20221013'; + SELECT @Version = '8.12', @VersionDate = '20221213'; IF(@VersionCheckMode = 1) BEGIN @@ -12396,7 +12396,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.11', @VersionDate = '20221013'; +SELECT @Version = '8.12', @VersionDate = '20221213'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -15869,6 +15869,35 @@ WHERE QueryType = 'Statement' AND SPID = @@SPID OPTION (RECOMPILE); +/* Update to grab stored procedure name for individual statements when PSPO is detected */ +UPDATE p +SET QueryType = QueryType + ' (parent ' + + + QUOTENAME(OBJECT_SCHEMA_NAME(s.object_id, s.database_id)) + + '.' + + QUOTENAME(OBJECT_NAME(s.object_id, s.database_id)) + ')' +FROM ##BlitzCacheProcs p + OUTER APPLY ( + SELECT REPLACE(REPLACE(REPLACE(REPLACE(p.QueryText, ' (', '('), '( ', '('), ' =', '='), '= ', '=') AS NormalizedQueryText + ) a + OUTER APPLY ( + SELECT CHARINDEX('option(PLAN PER VALUE(ObjectID=', a.NormalizedQueryText) AS OptionStart + ) b + OUTER APPLY ( + SELECT SUBSTRING(a.NormalizedQueryText, b.OptionStart + 31, LEN(a.NormalizedQueryText) - b.OptionStart - 30) AS OptionSubstring + WHERE b.OptionStart > 0 + ) c + OUTER APPLY ( + SELECT PATINDEX('%[^0-9]%', c.OptionSubstring) AS ObjectLength + ) d + OUTER APPLY ( + SELECT TRY_CAST(SUBSTRING(OptionSubstring, 1, d.ObjectLength - 1) AS INT) AS ObjectId + ) e + JOIN sys.dm_exec_procedure_stats s ON DB_ID(p.DatabaseName) = s.database_id AND e.ObjectId = s.object_id +WHERE p.QueryType = 'Statement' +AND p.SPID = @@SPID +AND s.object_id IS NOT NULL +OPTION (RECOMPILE); + RAISERROR(N'Attempting to get function name for individual statements', 0, 1) WITH NOWAIT; DECLARE @function_update_sql NVARCHAR(MAX) = N'' IF EXISTS (SELECT 1/0 FROM sys.all_objects AS o WHERE o.name = 'dm_exec_function_stats') @@ -19356,7 +19385,9 @@ END '; WHEN N'avg duration' THEN N' AverageDuration' WHEN N'avg executions' THEN N' ExecutionsPerMinute' WHEN N'avg memory grant' THEN N' AvgMaxMemoryGrant' - WHEN 'avg spills' THEN N' AvgSpills' + WHEN N'avg spills' THEN N' AvgSpills' + WHEN N'unused grant' THEN N' MaxGrantKB - MaxUsedGrantKB' + ELSE N' TotalCPU ' END + N' DESC '; SET @StringToExecute += N' OPTION (RECOMPILE) ; '; @@ -19422,7 +19453,9 @@ END '; WHEN N'avg duration' THEN N' AverageDuration' WHEN N'avg executions' THEN N' ExecutionsPerMinute' WHEN N'avg memory grant' THEN N' AvgMaxMemoryGrant' - WHEN 'avg spills' THEN N' AvgSpills' + WHEN N'avg spills' THEN N' AvgSpills' + WHEN N'unused grant' THEN N' MaxGrantKB - MaxUsedGrantKB' + ELSE N' TotalCPU ' END + N' DESC '; SET @StringToExecute += N' OPTION (RECOMPILE) ; '; @@ -19579,7 +19612,9 @@ END '; WHEN N'avg duration' THEN N' AverageDuration' WHEN N'avg executions' THEN N' ExecutionsPerMinute' WHEN N'avg memory grant' THEN N' AvgMaxMemoryGrant' - WHEN 'avg spills' THEN N' AvgSpills' + WHEN N'avg spills' THEN N' AvgSpills' + WHEN N'unused grant' THEN N' MaxGrantKB - MaxUsedGrantKB' + ELSE N' TotalCPU ' END + N' DESC '; SET @StringToExecute += N' OPTION (RECOMPILE) ; '; @@ -19659,7 +19694,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.11', @VersionDate = '20221013'; +SELECT @Version = '8.12', @VersionDate = '20221213'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -19770,25 +19805,41 @@ SELECT END; RAISERROR(N'Starting run. %s', 0,1, @ScriptVersionName) WITH NOWAIT; - + + IF(@OutputType NOT IN ('TABLE','NONE')) BEGIN RAISERROR('Invalid value for parameter @OutputType. Expected: (TABLE;NONE)',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; + SET @OutputType = 'NONE' +END; IF(@OutputType = 'NONE') BEGIN + + IF ((@OutputServerName IS NOT NULL) AND (@OutputTableName IS NULL OR @OutputSchemaName IS NULL OR @OutputDatabaseName IS NULL)) + BEGIN + RAISERROR('Parameter @OutputServerName is specified, rest of @Output* parameters needs to also be specified',12,1); + RETURN; + END; + IF(@OutputTableName IS NULL OR @OutputSchemaName IS NULL OR @OutputDatabaseName IS NULL) BEGIN - RAISERROR('This procedure should be called with a value for @Output* parameters, as @OutputType is set to NONE',12,1); + RAISERROR('This procedure should be called with a value for @OutputTableName, @OutputSchemaName and @OutputDatabaseName parameters, as @OutputType is set to NONE',12,1); RETURN; END; + /* Output is supported for all modes, no reason to not bring pain and output IF(@BringThePain = 1) BEGIN RAISERROR('Incompatible Parameters: @BringThePain set to 1 and @OutputType set to NONE',12,1); RETURN; END; + */ /* Eventually limit by mode IF(@Mode not in (0,4)) BEGIN @@ -21536,7 +21587,7 @@ BEGIN TRY OR (PARSENAME(@SQLServerProductVersion, 4) = 10 AND PARSENAME(@SQLServerProductVersion, 3) = 50 AND PARSENAME(@SQLServerProductVersion, 2) >= 2500)) BEGIN RAISERROR (N'Gathering Statistics Info With Newer Syntax.',0,1) WITH NOWAIT; - SET @dsql=N'USE ' + @DatabaseName + N'; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + 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, 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, @@ -21611,7 +21662,7 @@ BEGIN TRY ELSE BEGIN RAISERROR (N'Gathering Statistics Info With Older Syntax.',0,1) WITH NOWAIT; - SET @dsql=N'USE ' + @DatabaseName + N'; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + 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, days_since_last_stats_update, rows, modification_counter, percent_modifications, modifications_before_auto_update, index_type_desc, table_create_date, table_modify_date, @@ -22589,6 +22640,7 @@ BEGIN LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partition_range_values prve ON prve.function_id = pf.function_id AND prve.boundary_id = p.partition_number ' ELSE N' ' END + N' LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_segments seg ON p.partition_id = seg.partition_id AND ic.index_column_id = seg.column_id AND rg.row_group_id = seg.segment_id WHERE rg.object_id = @ObjectID + AND rg.state IN (1, 2, 3) AND c.name IN ( ' + @ColumnListWithApostrophes + N')' + CASE WHEN @ShowPartitionRanges = 1 THEN N' ) AS y ' ELSE N' ' END + N' @@ -22633,32 +22685,142 @@ END; /* IF @TableName IS NOT NULL */ +ELSE /* @TableName IS NULL, so we operate in normal mode 0/1/2/3/4 */ +BEGIN +/* Validate and check table output params */ + /* Checks if @OutputServerName is populated with a valid linked server, and that the database name specified is valid */ + DECLARE @ValidOutputServer BIT; + DECLARE @ValidOutputLocation BIT; + DECLARE @LinkedServerDBCheck NVARCHAR(2000); + DECLARE @ValidLinkedServerDB INT; + DECLARE @tmpdbchk TABLE (cnt INT); + + IF @OutputServerName IS NOT NULL + BEGIN + IF (SUBSTRING(@OutputTableName, 2, 1) = '#') + BEGIN + RAISERROR('Due to the nature of temporary tables, outputting to a linked server requires a permanent table.', 16, 0); + END; + ELSE IF EXISTS (SELECT server_id FROM sys.servers WHERE QUOTENAME([name]) = @OutputServerName) + BEGIN + SET @LinkedServerDBCheck = 'SELECT 1 WHERE EXISTS (SELECT * FROM '+@OutputServerName+'.master.sys.databases WHERE QUOTENAME([name]) = '''+@OutputDatabaseName+''')'; + INSERT INTO @tmpdbchk EXEC sys.sp_executesql @LinkedServerDBCheck; + SET @ValidLinkedServerDB = (SELECT COUNT(*) FROM @tmpdbchk); + IF (@ValidLinkedServerDB > 0) + BEGIN + SET @ValidOutputServer = 1; + SET @ValidOutputLocation = 1; + END; + ELSE + RAISERROR('The specified database was not found on the output server', 16, 0); + END; + ELSE + BEGIN + RAISERROR('The specified output server was not found', 16, 0); + END; + END; + ELSE + BEGIN + IF (SUBSTRING(@OutputTableName, 2, 2) = '##') + BEGIN + SET @StringToExecute = N' IF (OBJECT_ID(''[tempdb].[dbo].@@@OutputTableName@@@'') IS NOT NULL) DROP TABLE @@@OutputTableName@@@'; + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); + EXEC(@StringToExecute); + + SET @OutputServerName = QUOTENAME(CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128))); + SET @OutputDatabaseName = '[tempdb]'; + SET @OutputSchemaName = '[dbo]'; + SET @ValidOutputLocation = 1; + END; + ELSE IF (SUBSTRING(@OutputTableName, 2, 1) = '#') + BEGIN + RAISERROR('Due to the nature of Dymamic SQL, only global (i.e. double pound (##)) temp tables are supported for @OutputTableName', 16, 0); + END; + ELSE IF @OutputDatabaseName IS NOT NULL + AND @OutputSchemaName IS NOT NULL + AND @OutputTableName IS NOT NULL + AND EXISTS ( SELECT * + FROM sys.databases + WHERE QUOTENAME([name]) = @OutputDatabaseName) + BEGIN + SET @ValidOutputLocation = 1; + SET @OutputServerName = QUOTENAME(CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128))); + END; + ELSE IF @OutputDatabaseName IS NOT NULL + AND @OutputSchemaName IS NOT NULL + AND @OutputTableName IS NOT NULL + AND NOT EXISTS ( SELECT * + FROM sys.databases + WHERE QUOTENAME([name]) = @OutputDatabaseName) + BEGIN + RAISERROR('The specified output database was not found on this server', 16, 0); + END; + ELSE + BEGIN + SET @ValidOutputLocation = 0; + END; + END; + + IF (@ValidOutputLocation = 0 AND @OutputType = 'NONE') + BEGIN + RAISERROR('Invalid output location and no output asked',12,1); + RETURN; + END; + + /* @OutputTableName lets us export the results to a permanent table */ + DECLARE @RunID UNIQUEIDENTIFIER; + SET @RunID = NEWID(); + DECLARE @TableExists BIT; + DECLARE @SchemaExists BIT; + DECLARE @TableExistsSql NVARCHAR(MAX); + IF (@ValidOutputLocation = 1 AND COALESCE(@OutputServerName, @OutputDatabaseName, @OutputSchemaName, @OutputTableName) IS NOT NULL) + BEGIN + SET @StringToExecute = + N'SET @SchemaExists = 0; + SET @TableExists = 0; + IF EXISTS(SELECT * FROM @@@OutputServerName@@@.@@@OutputDatabaseName@@@.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''@@@OutputSchemaName@@@'') + SET @SchemaExists = 1 + IF EXISTS (SELECT * FROM @@@OutputServerName@@@.@@@OutputDatabaseName@@@.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''@@@OutputSchemaName@@@'' AND QUOTENAME(TABLE_NAME) = ''@@@OutputTableName@@@'') + BEGIN + SET @TableExists = 1 + IF NOT EXISTS(SELECT * FROM @@@OutputServerName@@@.@@@OutputDatabaseName@@@.INFORMATION_SCHEMA.COLUMNS WHERE QUOTENAME(TABLE_SCHEMA) = ''@@@OutputSchemaName@@@'' + AND QUOTENAME(TABLE_NAME) = ''@@@OutputTableName@@@'' AND QUOTENAME(COLUMN_NAME) = ''[total_forwarded_fetch_count]'') + EXEC @@@OutputServerName@@@.@@@OutputDatabaseName@@@.dbo.sp_executesql N''ALTER TABLE @@@OutputSchemaName@@@.@@@OutputTableName@@@ ADD [total_forwarded_fetch_count] BIGINT'' + END'; + + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputServerName@@@', @OutputServerName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputSchemaName@@@', @OutputSchemaName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); + + EXEC sp_executesql @StringToExecute, N'@TableExists BIT OUTPUT, @SchemaExists BIT OUTPUT', @TableExists OUTPUT, @SchemaExists OUTPUT; + SET @TableExistsSql = + N'IF EXISTS(SELECT * FROM @@@OutputServerName@@@.@@@OutputDatabaseName@@@.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''@@@OutputSchemaName@@@'') + AND NOT EXISTS (SELECT * FROM @@@OutputServerName@@@.@@@OutputDatabaseName@@@.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''@@@OutputSchemaName@@@'' AND QUOTENAME(TABLE_NAME) = ''@@@OutputTableName@@@'') + SET @TableExists = 0 + ELSE + SET @TableExists = 1'; + + SET @TableExistsSql = REPLACE(@TableExistsSql, '@@@OutputServerName@@@', @OutputServerName); + SET @TableExistsSql = REPLACE(@TableExistsSql, '@@@OutputDatabaseName@@@', @OutputDatabaseName); + SET @TableExistsSql = REPLACE(@TableExistsSql, '@@@OutputSchemaName@@@', @OutputSchemaName); + SET @TableExistsSql = REPLACE(@TableExistsSql, '@@@OutputTableName@@@', @OutputTableName); + END - - - - - - - - - - - -ELSE IF @Mode IN (0, 4) /* DIAGNOSE*//* IF @TableName IS NOT NULL, so @TableName must not be null */ -BEGIN; + IF @Mode IN (0, 4) /* DIAGNOSE */ + BEGIN; IF @Mode IN (0, 4) /* DIAGNOSE priorities 1-100 */ BEGIN; RAISERROR(N'@Mode=0 or 4, running rules for priorities 1-100.', 0,1) WITH NOWAIT; @@ -23470,23 +23632,6 @@ BEGIN; - - - - - - - - - - - - - - - - - @@ -24523,27 +24668,7 @@ BEGIN; - - - - - - - - - - - - - - - - - - - - - + RAISERROR(N'Insert a row to help people find help', 0,1) WITH NOWAIT; IF DATEDIFF(MM, @VersionDate, GETDATE()) > 6 BEGIN @@ -24613,89 +24738,369 @@ BEGIN; RAISERROR(N'Returning results.', 0,1) WITH NOWAIT; /*Return results.*/ - IF (@Mode = 0) - BEGIN - IF(@OutputType <> 'NONE') + IF (@ValidOutputLocation = 1 AND COALESCE(@OutputServerName, @OutputDatabaseName, @OutputSchemaName, @OutputTableName) IS NOT NULL) BEGIN - SELECT Priority, ISNULL(br.findings_group,N'') + - CASE WHEN ISNULL(br.finding,N'') <> N'' THEN N': ' ELSE N'' END - + br.finding AS [Finding], - br.[database_name] AS [Database Name], - br.details AS [Details: schema.table.index(indexid)], - br.index_definition AS [Definition: [Property]] ColumnName {datatype maxbytes}], - ISNULL(br.secret_columns,'') AS [Secret Columns], - br.index_usage_summary AS [Usage], - br.index_size_summary AS [Size], - COALESCE(br.more_info,sn.more_info,'') AS [More Info], - br.URL, - COALESCE(br.create_tsql,ts.create_tsql,'') AS [Create TSQL], - br.sample_query_plan AS [Sample Query Plan] - FROM #BlitzIndexResults br - LEFT JOIN #IndexSanity sn ON - br.index_sanity_id=sn.index_sanity_id - LEFT JOIN #IndexCreateTsql ts ON - br.index_sanity_id=ts.index_sanity_id - ORDER BY br.Priority ASC, br.check_id ASC, br.blitz_result_id ASC, br.findings_group ASC - OPTION (RECOMPILE); - END; + IF NOT @SchemaExists = 1 + BEGIN + RAISERROR (N'Invalid schema name, data could not be saved.', 16, 0); + RETURN; + END - END; - ELSE IF (@Mode = 4) - IF(@OutputType <> 'NONE') - BEGIN - SELECT Priority, ISNULL(br.findings_group,N'') + - CASE WHEN ISNULL(br.finding,N'') <> N'' THEN N': ' ELSE N'' END - + br.finding AS [Finding], - br.[database_name] AS [Database Name], - br.details AS [Details: schema.table.index(indexid)], - br.index_definition AS [Definition: [Property]] ColumnName {datatype maxbytes}], - ISNULL(br.secret_columns,'') AS [Secret Columns], - br.index_usage_summary AS [Usage], - br.index_size_summary AS [Size], - COALESCE(br.more_info,sn.more_info,'') AS [More Info], - br.URL, - COALESCE(br.create_tsql,ts.create_tsql,'') AS [Create TSQL], - br.sample_query_plan AS [Sample Query Plan] - FROM #BlitzIndexResults br - LEFT JOIN #IndexSanity sn ON - br.index_sanity_id=sn.index_sanity_id - LEFT JOIN #IndexCreateTsql ts ON - br.index_sanity_id=ts.index_sanity_id - ORDER BY br.Priority ASC, br.check_id ASC, br.blitz_result_id ASC, br.findings_group ASC - OPTION (RECOMPILE); - END; - -END /* End @Mode=0 or 4 (diagnose)*/ - -ELSE IF (@Mode=1) /*Summarize*/ + IF @TableExists = 0 + BEGIN + SET @StringToExecute = + N'CREATE TABLE @@@OutputDatabaseName@@@.@@@OutputSchemaName@@@.@@@OutputTableName@@@ + ( + [id] INT IDENTITY(1,1) NOT NULL, + [run_id] UNIQUEIDENTIFIER, + [run_datetime] DATETIME, + [server_name] NVARCHAR(128), + [priority] INT, + [finding] NVARCHAR(4000), + [database_name] NVARCHAR(128), + [details] NVARCHAR(MAX), + [index_definition] NVARCHAR(MAX), + [secret_columns] NVARCHAR(MAX), + [index_usage_summary] NVARCHAR(MAX), + [index_size_summary] NVARCHAR(MAX), + [more_info] NVARCHAR(MAX), + [url] NVARCHAR(MAX), + [create_tsql] NVARCHAR(MAX), + [sample_query_plan] XML, + CONSTRAINT [PK_ID_@@@RunID@@@] PRIMARY KEY CLUSTERED ([id] ASC) + );'; + + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputSchemaName@@@', @OutputSchemaName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@RunID@@@', @RunID); + + IF @ValidOutputServer = 1 + BEGIN + SET @StringToExecute = REPLACE(@StringToExecute,'''',''''''); + EXEC('EXEC('''+@StringToExecute+''') AT ' + @OutputServerName); + END; + ELSE + BEGIN + EXEC(@StringToExecute); + END; + END; /* @TableExists = 0 */ + + -- Re-check that table now exists (if not we failed creating it) + SET @TableExists = NULL; + EXEC sp_executesql @TableExistsSql, N'@TableExists BIT OUTPUT', @TableExists OUTPUT; + + IF NOT @TableExists = 1 + BEGIN + RAISERROR('Creation of the output table failed.', 16, 0); + RETURN; + END; + + SET @StringToExecute = + N'INSERT @@@OutputServerName@@@.@@@OutputDatabaseName@@@.@@@OutputSchemaName@@@.@@@OutputTableName@@@ + ( + [run_id], + [run_datetime], + [server_name], + [priority], + [finding], + [database_name], + [details], + [index_definition], + [secret_columns], + [index_usage_summary], + [index_size_summary], + [more_info], + [url], + [create_tsql], + [sample_query_plan] + ) + SELECT + ''@@@RunID@@@'', + ''@@@GETDATE@@@'', + ''@@@LocalServerName@@@'', + -- Below should be a copy/paste of the real query + -- Make sure all quotes are escaped + Priority, ISNULL(br.findings_group,N'''') + + CASE WHEN ISNULL(br.finding,N'''') <> N'''' THEN N'': '' ELSE N'''' END + + br.finding AS [Finding], + br.[database_name] AS [Database Name], + br.details AS [Details: schema.table.index(indexid)], + br.index_definition AS [Definition: [Property]] ColumnName {datatype maxbytes}], + ISNULL(br.secret_columns,'''') AS [Secret Columns], + br.index_usage_summary AS [Usage], + br.index_size_summary AS [Size], + COALESCE(br.more_info,sn.more_info,'''') AS [More Info], + br.URL, + COALESCE(br.create_tsql,ts.create_tsql,'''') AS [Create TSQL], + br.sample_query_plan AS [Sample Query Plan] + FROM #BlitzIndexResults br + LEFT JOIN #IndexSanity sn ON + br.index_sanity_id=sn.index_sanity_id + LEFT JOIN #IndexCreateTsql ts ON + br.index_sanity_id=ts.index_sanity_id + ORDER BY br.Priority ASC, br.check_id ASC, br.blitz_result_id ASC, br.findings_group ASC + OPTION (RECOMPILE);'; + + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputServerName@@@', @OutputServerName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputSchemaName@@@', @OutputSchemaName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@RunID@@@', @RunID); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@GETDATE@@@', GETDATE()); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@LocalServerName@@@', CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128))); + EXEC(@StringToExecute); + + END + ELSE + BEGIN + IF(@OutputType <> 'NONE') + BEGIN + SELECT Priority, ISNULL(br.findings_group,N'') + + CASE WHEN ISNULL(br.finding,N'') <> N'' THEN N': ' ELSE N'' END + + br.finding AS [Finding], + br.[database_name] AS [Database Name], + br.details AS [Details: schema.table.index(indexid)], + br.index_definition AS [Definition: [Property]] ColumnName {datatype maxbytes}], + ISNULL(br.secret_columns,'') AS [Secret Columns], + br.index_usage_summary AS [Usage], + br.index_size_summary AS [Size], + COALESCE(br.more_info,sn.more_info,'') AS [More Info], + br.URL, + COALESCE(br.create_tsql,ts.create_tsql,'') AS [Create TSQL], + br.sample_query_plan AS [Sample Query Plan] + FROM #BlitzIndexResults br + LEFT JOIN #IndexSanity sn ON + br.index_sanity_id=sn.index_sanity_id + LEFT JOIN #IndexCreateTsql ts ON + br.index_sanity_id=ts.index_sanity_id + ORDER BY br.Priority ASC, br.check_id ASC, br.blitz_result_id ASC, br.findings_group ASC + OPTION (RECOMPILE); + END; + + END; + + END /* End @Mode=0 or 4 (diagnose)*/ + + + + + + + + + ELSE IF (@Mode=1) /*Summarize*/ BEGIN --This mode is to give some overall stats on the database. - IF(@OutputType <> 'NONE') - BEGIN - RAISERROR(N'@Mode=1, we are summarizing.', 0,1) WITH NOWAIT; + IF (@ValidOutputLocation = 1 AND COALESCE(@OutputServerName, @OutputDatabaseName, @OutputSchemaName, @OutputTableName) IS NOT NULL) + BEGIN - SELECT DB_NAME(i.database_id) AS [Database Name], - CAST((COUNT(*)) AS NVARCHAR(256)) AS [Number Objects], - CAST(CAST(SUM(sz.total_reserved_MB)/ - 1024. AS NUMERIC(29,1)) AS NVARCHAR(500)) AS [All GB], - CAST(CAST(SUM(sz.total_reserved_LOB_MB)/ - 1024. AS NUMERIC(29,1)) AS NVARCHAR(500)) AS [LOB GB], - CAST(CAST(SUM(sz.total_reserved_row_overflow_MB)/ - 1024. AS NUMERIC(29,1)) AS NVARCHAR(500)) AS [Row Overflow GB], - CAST(SUM(CASE WHEN index_id=1 THEN 1 ELSE 0 END)AS NVARCHAR(50)) AS [Clustered Tables], - CAST(SUM(CASE WHEN index_id=1 THEN sz.total_reserved_MB ELSE 0 END) - /1024. AS NUMERIC(29,1)) AS [Clustered Tables GB], - SUM(CASE WHEN index_id NOT IN (0,1) THEN 1 ELSE 0 END) AS [NC Indexes], - CAST(SUM(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) - /1024. AS NUMERIC(29,1)) AS [NC Indexes GB], - CASE WHEN SUM(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) > 0 THEN - CAST(SUM(CASE WHEN index_id IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) - / SUM(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) AS NUMERIC(29,1)) - ELSE 0 END AS [ratio table: NC Indexes], - SUM(CASE WHEN index_id=0 THEN 1 ELSE 0 END) AS [Heaps], - CAST(SUM(CASE WHEN index_id=0 THEN sz.total_reserved_MB ELSE 0 END) - /1024. AS NUMERIC(29,1)) AS [Heaps GB], - SUM(CASE WHEN index_id IN (0,1) AND partition_key_column_name IS NOT NULL THEN 1 ELSE 0 END) AS [Partitioned Tables], + IF NOT @SchemaExists = 1 + BEGIN + RAISERROR (N'Invalid schema name, data could not be saved.', 16, 0); + RETURN; + END + + IF @TableExists = 0 + BEGIN + SET @StringToExecute = + N'CREATE TABLE @@@OutputDatabaseName@@@.@@@OutputSchemaName@@@.@@@OutputTableName@@@ + ( + [id] INT IDENTITY(1,1) NOT NULL, + [run_id] UNIQUEIDENTIFIER, + [run_datetime] DATETIME, + [server_name] NVARCHAR(128), + [database_name] NVARCHAR(128), + [object_count] INT, + [reserved_gb] NUMERIC(29,1), + [reserved_lob_gb] NUMERIC(29,1), + [reserved_row_overflow_gb] NUMERIC(29,1), + [clustered_table_count] INT, + [clustered_table_gb] NUMERIC(29,1), + [nc_index_count] INT, + [nc_index_gb] NUMERIC(29,1), + [table_nc_index_ratio] NUMERIC(29,1), + [heap_count] INT, + [heap_gb] NUMERIC(29,1), + [partioned_table_count] INT, + [partioned_nc_count] INT, + [partioned_gb] NUMERIC(29,1), + [filtered_index_count] INT, + [indexed_view_count] INT, + [max_table_row_count] INT, + [max_table_gb] NUMERIC(29,1), + [max_nc_index_gb] NUMERIC(29,1), + [table_count_over_1gb] INT, + [table_count_over_10gb] INT, + [table_count_over_100gb] INT, + [nc_index_count_over_1gb] INT, + [nc_index_count_over_10gb] INT, + [nc_index_count_over_100gb] INT, + [min_create_date] DATETIME, + [max_create_date] DATETIME, + [max_modify_date] DATETIME, + [display_order] INT, + CONSTRAINT [PK_ID_@@@RunID@@@] PRIMARY KEY CLUSTERED ([id] ASC) + );'; + + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputSchemaName@@@', @OutputSchemaName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@RunID@@@', @RunID); + + IF @ValidOutputServer = 1 + BEGIN + SET @StringToExecute = REPLACE(@StringToExecute,'''',''''''); + EXEC('EXEC('''+@StringToExecute+''') AT ' + @OutputServerName); + END; + ELSE + BEGIN + EXEC(@StringToExecute); + END; + END; /* @TableExists = 0 */ + + -- Re-check that table now exists (if not we failed creating it) + SET @TableExists = NULL; + EXEC sp_executesql @TableExistsSql, N'@TableExists BIT OUTPUT', @TableExists OUTPUT; + + IF NOT @TableExists = 1 + BEGIN + RAISERROR('Creation of the output table failed.', 16, 0); + RETURN; + END; + + SET @StringToExecute = + N'INSERT @@@OutputServerName@@@.@@@OutputDatabaseName@@@.@@@OutputSchemaName@@@.@@@OutputTableName@@@ + ( + [run_id], + [run_datetime], + [server_name], + [database_name], + [object_count], + [reserved_gb], + [reserved_lob_gb], + [reserved_row_overflow_gb], + [clustered_table_count], + [clustered_table_gb], + [nc_index_count], + [nc_index_gb], + [table_nc_index_ratio], + [heap_count], + [heap_gb], + [partioned_table_count], + [partioned_nc_count], + [partioned_gb], + [filtered_index_count], + [indexed_view_count], + [max_table_row_count], + [max_table_gb], + [max_nc_index_gb], + [table_count_over_1gb], + [table_count_over_10gb], + [table_count_over_100gb], + [nc_index_count_over_1gb], + [nc_index_count_over_10gb], + [nc_index_count_over_100gb], + [min_create_date], + [max_create_date], + [max_modify_date], + [display_order] + ) + SELECT ''@@@RunID@@@'', + ''@@@GETDATE@@@'', + ''@@@LocalServerName@@@'', + -- Below should be a copy/paste of the real query + -- Make sure all quotes are escaped + -- NOTE! information line is skipped from output and the query below + -- NOTE! initial columns are not casted to nvarchar due to not outputing informational line + DB_NAME(i.database_id) AS [Database Name], + COUNT(*) AS [Number Objects], + CAST(SUM(sz.total_reserved_MB)/ + 1024. AS NUMERIC(29,1)) AS [All GB], + CAST(SUM(sz.total_reserved_LOB_MB)/ + 1024. AS NUMERIC(29,1)) AS [LOB GB], + CAST(SUM(sz.total_reserved_row_overflow_MB)/ + 1024. AS NUMERIC(29,1)) AS [Row Overflow GB], + SUM(CASE WHEN index_id=1 THEN 1 ELSE 0 END) AS [Clustered Tables], + CAST(SUM(CASE WHEN index_id=1 THEN sz.total_reserved_MB ELSE 0 END) + /1024. AS NUMERIC(29,1)) AS [Clustered Tables GB], + SUM(CASE WHEN index_id NOT IN (0,1) THEN 1 ELSE 0 END) AS [NC Indexes], + CAST(SUM(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) + /1024. AS NUMERIC(29,1)) AS [NC Indexes GB], + CASE WHEN SUM(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) > 0 THEN + CAST(SUM(CASE WHEN index_id IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) + / SUM(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) AS NUMERIC(29,1)) + ELSE 0 END AS [ratio table: NC Indexes], + SUM(CASE WHEN index_id=0 THEN 1 ELSE 0 END) AS [Heaps], + CAST(SUM(CASE WHEN index_id=0 THEN sz.total_reserved_MB ELSE 0 END) + /1024. AS NUMERIC(29,1)) AS [Heaps GB], + SUM(CASE WHEN index_id IN (0,1) AND partition_key_column_name IS NOT NULL THEN 1 ELSE 0 END) AS [Partitioned Tables], + SUM(CASE WHEN index_id NOT IN (0,1) AND partition_key_column_name IS NOT NULL THEN 1 ELSE 0 END) AS [Partitioned NCs], + CAST(SUM(CASE WHEN partition_key_column_name IS NOT NULL THEN sz.total_reserved_MB ELSE 0 END)/1024. AS NUMERIC(29,1)) AS [Partitioned GB], + SUM(CASE WHEN filter_definition <> '''' THEN 1 ELSE 0 END) AS [Filtered Indexes], + SUM(CASE WHEN is_indexed_view=1 THEN 1 ELSE 0 END) AS [Indexed Views], + MAX(total_rows) AS [Max Row Count], + CAST(MAX(CASE WHEN index_id IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) + /1024. AS NUMERIC(29,1)) AS [Max Table GB], + CAST(MAX(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) + /1024. AS NUMERIC(29,1)) AS [Max NC Index GB], + SUM(CASE WHEN index_id IN (0,1) AND sz.total_reserved_MB > 1024 THEN 1 ELSE 0 END) AS [Count Tables > 1GB], + SUM(CASE WHEN index_id IN (0,1) AND sz.total_reserved_MB > 10240 THEN 1 ELSE 0 END) AS [Count Tables > 10GB], + SUM(CASE WHEN index_id IN (0,1) AND sz.total_reserved_MB > 102400 THEN 1 ELSE 0 END) AS [Count Tables > 100GB], + SUM(CASE WHEN index_id NOT IN (0,1) AND sz.total_reserved_MB > 1024 THEN 1 ELSE 0 END) AS [Count NCs > 1GB], + SUM(CASE WHEN index_id NOT IN (0,1) AND sz.total_reserved_MB > 10240 THEN 1 ELSE 0 END) AS [Count NCs > 10GB], + SUM(CASE WHEN index_id NOT IN (0,1) AND sz.total_reserved_MB > 102400 THEN 1 ELSE 0 END) AS [Count NCs > 100GB], + MIN(create_date) AS [Oldest Create Date], + MAX(create_date) AS [Most Recent Create Date], + MAX(modify_date) AS [Most Recent Modify Date], + 1 AS [Display Order] + FROM #IndexSanity AS i + --left join here so we don''t lose disabled nc indexes + LEFT JOIN #IndexSanitySize AS sz + ON i.index_sanity_id=sz.index_sanity_id + GROUP BY DB_NAME(i.database_id) + ORDER BY [Display Order] ASC + OPTION (RECOMPILE);'; + + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputServerName@@@', @OutputServerName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputSchemaName@@@', @OutputSchemaName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@RunID@@@', @RunID); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@GETDATE@@@', GETDATE()); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@LocalServerName@@@', CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128))); + EXEC(@StringToExecute); + + END; /* @ValidOutputLocation = 1 */ + ELSE + BEGIN + IF(@OutputType <> 'NONE') + BEGIN + + RAISERROR(N'@Mode=1, we are summarizing.', 0,1) WITH NOWAIT; + + SELECT DB_NAME(i.database_id) AS [Database Name], + CAST((COUNT(*)) AS NVARCHAR(256)) AS [Number Objects], + CAST(CAST(SUM(sz.total_reserved_MB)/ + 1024. AS NUMERIC(29,1)) AS NVARCHAR(500)) AS [All GB], + CAST(CAST(SUM(sz.total_reserved_LOB_MB)/ + 1024. AS NUMERIC(29,1)) AS NVARCHAR(500)) AS [LOB GB], + CAST(CAST(SUM(sz.total_reserved_row_overflow_MB)/ + 1024. AS NUMERIC(29,1)) AS NVARCHAR(500)) AS [Row Overflow GB], + CAST(SUM(CASE WHEN index_id=1 THEN 1 ELSE 0 END)AS NVARCHAR(50)) AS [Clustered Tables], + CAST(SUM(CASE WHEN index_id=1 THEN sz.total_reserved_MB ELSE 0 END) + /1024. AS NUMERIC(29,1)) AS [Clustered Tables GB], + SUM(CASE WHEN index_id NOT IN (0,1) THEN 1 ELSE 0 END) AS [NC Indexes], + CAST(SUM(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) + /1024. AS NUMERIC(29,1)) AS [NC Indexes GB], + CASE WHEN SUM(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) > 0 THEN + CAST(SUM(CASE WHEN index_id IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) + / SUM(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) AS NUMERIC(29,1)) + ELSE 0 END AS [ratio table: NC Indexes], + SUM(CASE WHEN index_id=0 THEN 1 ELSE 0 END) AS [Heaps], + CAST(SUM(CASE WHEN index_id=0 THEN sz.total_reserved_MB ELSE 0 END) + /1024. AS NUMERIC(29,1)) AS [Heaps GB], + SUM(CASE WHEN index_id IN (0,1) AND partition_key_column_name IS NOT NULL THEN 1 ELSE 0 END) AS [Partitioned Tables], SUM(CASE WHEN index_id NOT IN (0,1) AND partition_key_column_name IS NOT NULL THEN 1 ELSE 0 END) AS [Partitioned NCs], CAST(SUM(CASE WHEN partition_key_column_name IS NOT NULL THEN sz.total_reserved_MB ELSE 0 END)/1024. AS NUMERIC(29,1)) AS [Partitioned GB], SUM(CASE WHEN filter_definition <> '' THEN 1 ELSE 0 END) AS [Filtered Indexes], @@ -24731,123 +25136,26 @@ ELSE IF (@Mode=1) /*Summarize*/ NULL,NULL,0 AS display_order ORDER BY [Display Order] ASC OPTION (RECOMPILE); - END; - + END; + END; + END; /* End @Mode=1 (summarize)*/ - ELSE IF (@Mode=2) /*Index Detail*/ + + + + + + + + + ELSE IF (@Mode=2) /*Index Detail*/ BEGIN --This mode just spits out all the detail without filters. --This supports slicing AND dicing in Excel RAISERROR(N'@Mode=2, here''s the details on existing indexes.', 0,1) WITH NOWAIT; - - /* Checks if @OutputServerName is populated with a valid linked server, and that the database name specified is valid */ - DECLARE @ValidOutputServer BIT; - DECLARE @ValidOutputLocation BIT; - DECLARE @LinkedServerDBCheck NVARCHAR(2000); - DECLARE @ValidLinkedServerDB INT; - DECLARE @tmpdbchk TABLE (cnt INT); - - IF @OutputServerName IS NOT NULL - BEGIN - IF (SUBSTRING(@OutputTableName, 2, 1) = '#') - BEGIN - RAISERROR('Due to the nature of temporary tables, outputting to a linked server requires a permanent table.', 16, 0); - END; - ELSE IF EXISTS (SELECT server_id FROM sys.servers WHERE QUOTENAME([name]) = @OutputServerName) - BEGIN - SET @LinkedServerDBCheck = 'SELECT 1 WHERE EXISTS (SELECT * FROM '+@OutputServerName+'.master.sys.databases WHERE QUOTENAME([name]) = '''+@OutputDatabaseName+''')'; - INSERT INTO @tmpdbchk EXEC sys.sp_executesql @LinkedServerDBCheck; - SET @ValidLinkedServerDB = (SELECT COUNT(*) FROM @tmpdbchk); - IF (@ValidLinkedServerDB > 0) - BEGIN - SET @ValidOutputServer = 1; - SET @ValidOutputLocation = 1; - END; - ELSE - RAISERROR('The specified database was not found on the output server', 16, 0); - END; - ELSE - BEGIN - RAISERROR('The specified output server was not found', 16, 0); - END; - END; - ELSE - BEGIN - IF (SUBSTRING(@OutputTableName, 2, 2) = '##') - BEGIN - SET @StringToExecute = N' IF (OBJECT_ID(''[tempdb].[dbo].@@@OutputTableName@@@'') IS NOT NULL) DROP TABLE @@@OutputTableName@@@'; - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); - EXEC(@StringToExecute); - - SET @OutputServerName = QUOTENAME(CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128))); - SET @OutputDatabaseName = '[tempdb]'; - SET @OutputSchemaName = '[dbo]'; - SET @ValidOutputLocation = 1; - END; - ELSE IF (SUBSTRING(@OutputTableName, 2, 1) = '#') - BEGIN - RAISERROR('Due to the nature of Dymamic SQL, only global (i.e. double pound (##)) temp tables are supported for @OutputTableName', 16, 0); - END; - ELSE IF @OutputDatabaseName IS NOT NULL - AND @OutputSchemaName IS NOT NULL - AND @OutputTableName IS NOT NULL - AND EXISTS ( SELECT * - FROM sys.databases - WHERE QUOTENAME([name]) = @OutputDatabaseName) - BEGIN - SET @ValidOutputLocation = 1; - SET @OutputServerName = QUOTENAME(CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128))); - END; - ELSE IF @OutputDatabaseName IS NOT NULL - AND @OutputSchemaName IS NOT NULL - AND @OutputTableName IS NOT NULL - AND NOT EXISTS ( SELECT * - FROM sys.databases - WHERE QUOTENAME([name]) = @OutputDatabaseName) - BEGIN - RAISERROR('The specified output database was not found on this server', 16, 0); - END; - ELSE - BEGIN - SET @ValidOutputLocation = 0; - END; - END; - - IF (@ValidOutputLocation = 0 AND @OutputType = 'NONE') - BEGIN - RAISERROR('Invalid output location and no output asked',12,1); - RETURN; - END; - - /* @OutputTableName lets us export the results to a permanent table */ - DECLARE @RunID UNIQUEIDENTIFIER; - SET @RunID = NEWID(); - IF (@ValidOutputLocation = 1 AND COALESCE(@OutputServerName, @OutputDatabaseName, @OutputSchemaName, @OutputTableName) IS NOT NULL) BEGIN - DECLARE @TableExists BIT; - DECLARE @SchemaExists BIT; - SET @StringToExecute = - N'SET @SchemaExists = 0; - SET @TableExists = 0; - IF EXISTS(SELECT * FROM @@@OutputServerName@@@.@@@OutputDatabaseName@@@.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''@@@OutputSchemaName@@@'') - SET @SchemaExists = 1 - IF EXISTS (SELECT * FROM @@@OutputServerName@@@.@@@OutputDatabaseName@@@.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''@@@OutputSchemaName@@@'' AND QUOTENAME(TABLE_NAME) = ''@@@OutputTableName@@@'') - BEGIN - SET @TableExists = 1 - IF NOT EXISTS(SELECT * FROM @@@OutputServerName@@@.@@@OutputDatabaseName@@@.INFORMATION_SCHEMA.COLUMNS WHERE QUOTENAME(TABLE_SCHEMA) = ''@@@OutputSchemaName@@@'' - AND QUOTENAME(TABLE_NAME) = ''@@@OutputTableName@@@'' AND QUOTENAME(COLUMN_NAME) = ''[total_forwarded_fetch_count]'') - EXEC @@@OutputServerName@@@.@@@OutputDatabaseName@@@.dbo.sp_executesql N''ALTER TABLE @@@OutputSchemaName@@@.@@@OutputTableName@@@ ADD [total_forwarded_fetch_count] BIGINT'' - END'; - - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputServerName@@@', @OutputServerName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputSchemaName@@@', @OutputSchemaName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); - - EXEC sp_executesql @StringToExecute, N'@TableExists BIT OUTPUT, @SchemaExists BIT OUTPUT', @TableExists OUTPUT, @SchemaExists OUTPUT; - IF @SchemaExists = 1 BEGIN IF @TableExists = 0 @@ -24948,20 +25256,10 @@ ELSE IF (@Mode=1) /*Summarize*/ END; END; /* @TableExists = 0 */ - SET @StringToExecute = - N'IF EXISTS(SELECT * FROM @@@OutputServerName@@@.@@@OutputDatabaseName@@@.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''@@@OutputSchemaName@@@'') - AND NOT EXISTS (SELECT * FROM @@@OutputServerName@@@.@@@OutputDatabaseName@@@.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''@@@OutputSchemaName@@@'' AND QUOTENAME(TABLE_NAME) = ''@@@OutputTableName@@@'') - SET @TableExists = 0 - ELSE - SET @TableExists = 1'; - + + -- Re-check that table now exists (if not we failed creating it) SET @TableExists = NULL; - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputServerName@@@', @OutputServerName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputSchemaName@@@', @OutputSchemaName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); - - EXEC sp_executesql @StringToExecute, N'@TableExists BIT OUTPUT', @TableExists OUTPUT; + EXEC sp_executesql @TableExistsSql, N'@TableExists BIT OUTPUT', @TableExists OUTPUT; IF @TableExists = 1 BEGIN @@ -25289,10 +25587,167 @@ ELSE IF (@Mode=1) /*Summarize*/ END; END; /* End @Mode=2 (index detail)*/ + + + + + + + + ELSE IF (@Mode=3) /*Missing index Detail*/ BEGIN - IF(@OutputType <> 'NONE') - BEGIN; + IF (@ValidOutputLocation = 1 AND COALESCE(@OutputServerName, @OutputDatabaseName, @OutputSchemaName, @OutputTableName) IS NOT NULL) + BEGIN + + IF NOT @SchemaExists = 1 + BEGIN + RAISERROR (N'Invalid schema name, data could not be saved.', 16, 0); + RETURN; + END + + IF @TableExists = 0 + BEGIN + SET @StringToExecute = + N'CREATE TABLE @@@OutputDatabaseName@@@.@@@OutputSchemaName@@@.@@@OutputTableName@@@ + ( + [id] INT IDENTITY(1,1) NOT NULL, + [run_id] UNIQUEIDENTIFIER, + [run_datetime] DATETIME, + [server_name] NVARCHAR(128), + [database_name] NVARCHAR(128), + [schema_name] NVARCHAR(128), + [table_name] NVARCHAR(128), + [magic_benefit_number] BIGINT, + [missing_index_details] NVARCHAR(MAX), + [avg_total_user_cost] NUMERIC(29,4), + [avg_user_impact] NUMERIC(29,1), + [user_seeks] BIGINT, + [user_scans] BIGINT, + [unique_compiles] BIGINT, + [equality_columns_with_data_type] NVARCHAR(MAX), + [inequality_columns_with_data_type] NVARCHAR(MAX), + [included_columns_with_data_type] NVARCHAR(MAX), + [index_estimated_impact] NVARCHAR(256), + [create_tsql] NVARCHAR(MAX), + [more_info] NVARCHAR(600), + [display_order] INT, + [is_low] BIT, + [sample_query_plan] XML, + CONSTRAINT [PK_ID_@@@RunID@@@] PRIMARY KEY CLUSTERED ([id] ASC) + );'; + + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputSchemaName@@@', @OutputSchemaName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@RunID@@@', @RunID); + + IF @ValidOutputServer = 1 + BEGIN + SET @StringToExecute = REPLACE(@StringToExecute,'''',''''''); + EXEC('EXEC('''+@StringToExecute+''') AT ' + @OutputServerName); + END; + ELSE + BEGIN + EXEC(@StringToExecute); + END; + END; /* @TableExists = 0 */ + + -- Re-check that table now exists (if not we failed creating it) + SET @TableExists = NULL; + EXEC sp_executesql @TableExistsSql, N'@TableExists BIT OUTPUT', @TableExists OUTPUT; + + IF NOT @TableExists = 1 + BEGIN + RAISERROR('Creation of the output table failed.', 16, 0); + RETURN; + END; + SET @StringToExecute = + N'WITH create_date AS ( + SELECT i.database_id, + i.schema_name, + i.[object_id], + ISNULL(NULLIF(MAX(DATEDIFF(DAY, i.create_date, SYSDATETIME())), 0), 1) AS create_days + FROM #IndexSanity AS i + GROUP BY i.database_id, i.schema_name, i.object_id + ) + INSERT @@@OutputServerName@@@.@@@OutputDatabaseName@@@.@@@OutputSchemaName@@@.@@@OutputTableName@@@ + ( + [run_id], + [run_datetime], + [server_name], + [database_name], + [schema_name], + [table_name], + [magic_benefit_number], + [missing_index_details], + [avg_total_user_cost], + [avg_user_impact], + [user_seeks], + [user_scans], + [unique_compiles], + [equality_columns_with_data_type], + [inequality_columns_with_data_type], + [included_columns_with_data_type], + [index_estimated_impact], + [create_tsql], + [more_info], + [display_order], + [is_low], + [sample_query_plan] + ) + SELECT ''@@@RunID@@@'', + ''@@@GETDATE@@@'', + ''@@@LocalServerName@@@'', + -- Below should be a copy/paste of the real query + -- Make sure all quotes are escaped + -- NOTE! information line is skipped from output and the query below + -- NOTE! CTE block is above insert in the copied SQL + mi.database_name AS [Database Name], + mi.[schema_name] AS [Schema], + mi.table_name AS [Table], + CAST((mi.magic_benefit_number / CASE WHEN cd.create_days < @DaysUptime THEN cd.create_days ELSE @DaysUptime END) AS BIGINT) + AS [Magic Benefit Number], + mi.missing_index_details AS [Missing Index Details], + mi.avg_total_user_cost AS [Avg Query Cost], + mi.avg_user_impact AS [Est Index Improvement], + mi.user_seeks AS [Seeks], + mi.user_scans AS [Scans], + mi.unique_compiles AS [Compiles], + mi.equality_columns_with_data_type AS [Equality Columns], + mi.inequality_columns_with_data_type AS [Inequality Columns], + mi.included_columns_with_data_type AS [Included Columns], + mi.index_estimated_impact AS [Estimated Impact], + mi.create_tsql AS [Create TSQL], + mi.more_info AS [More Info], + 1 AS [Display Order], + mi.is_low, + mi.sample_query_plan AS [Sample Query Plan] + FROM #MissingIndexes AS mi + LEFT JOIN create_date AS cd + ON mi.[object_id] = cd.object_id + AND mi.database_id = cd.database_id + AND mi.schema_name = cd.schema_name + /* Minimum benefit threshold = 100k/day of uptime OR since table creation date, whichever is lower*/ + WHERE @ShowAllMissingIndexRequests=1 + OR (mi.magic_benefit_number / CASE WHEN cd.create_days < @DaysUptime THEN cd.create_days ELSE @DaysUptime END) >= 100000 + ORDER BY [Display Order] ASC, [Magic Benefit Number] DESC + OPTION (RECOMPILE);'; + + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputServerName@@@', @OutputServerName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputSchemaName@@@', @OutputSchemaName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@RunID@@@', @RunID); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@GETDATE@@@', GETDATE()); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@LocalServerName@@@', CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128))); + EXEC sp_executesql @StringToExecute, N'@DaysUptime NUMERIC(23,2), @ShowAllMissingIndexRequests BIT', @DaysUptime = @DaysUptime, @ShowAllMissingIndexRequests = @ShowAllMissingIndexRequests; + + END; /* @ValidOutputLocation = 1 */ + ELSE + BEGIN + IF(@OutputType <> 'NONE') + BEGIN WITH create_date AS ( SELECT i.database_id, i.schema_name, @@ -25341,21 +25796,26 @@ ELSE IF (@Mode=1) /*Summarize*/ NULL, NULL, NULL, NULL, 0 AS [Display Order], NULL AS is_low, NULL ORDER BY [Display Order] ASC, [Magic Benefit Number] DESC OPTION (RECOMPILE); - END; + END; + + + IF (@BringThePain = 1 + AND @DatabaseName IS NOT NULL + AND @GetAllDatabases = 0) + + BEGIN + EXEC sp_BlitzCache @SortOrder = 'sp_BlitzIndex', @DatabaseName = @DatabaseName, @BringThePain = 1, @QueryFilter = 'statement', @HideSummary = 1; + END; + + END; - IF (@BringThePain = 1 - AND @DatabaseName IS NOT NULL - AND @GetAllDatabases = 0) - BEGIN - EXEC sp_BlitzCache @SortOrder = 'sp_BlitzIndex', @DatabaseName = @DatabaseName, @BringThePain = 1, @QueryFilter = 'statement', @HideSummary = 1; - - END; END; /* End @Mode=3 (index detail)*/ +END /* End @TableName IS NULL (mode 0/1/2/3/4) */ END TRY BEGIN CATCH @@ -25375,1817 +25835,3488 @@ BEGIN CATCH END CATCH; GO IF OBJECT_ID('dbo.sp_BlitzLock') IS NULL - EXEC ('CREATE PROCEDURE dbo.sp_BlitzLock AS RETURN 0;'); +BEGIN + EXEC ('CREATE PROCEDURE dbo.sp_BlitzLock AS RETURN 0;'); +END; GO -ALTER PROCEDURE dbo.sp_BlitzLock +ALTER PROCEDURE + dbo.sp_BlitzLock ( - @Top INT = 2147483647, - @DatabaseName NVARCHAR(256) = NULL, - @StartDate DATETIME = '19000101', - @EndDate DATETIME = '99991231', - @ObjectName NVARCHAR(1000) = NULL, - @StoredProcName NVARCHAR(1000) = NULL, - @AppName NVARCHAR(256) = NULL, - @HostName NVARCHAR(256) = NULL, - @LoginName NVARCHAR(256) = NULL, - @EventSessionPath VARCHAR(256) = 'system_health*.xel', - @VictimsOnly BIT = 0, - @Debug BIT = 0, - @Help BIT = 0, - @Version VARCHAR(30) = NULL OUTPUT, - @VersionDate DATETIME = NULL OUTPUT, - @VersionCheckMode BIT = 0, - @OutputDatabaseName NVARCHAR(256) = NULL , - @OutputSchemaName NVARCHAR(256) = 'dbo' , --ditto as below - @OutputTableName NVARCHAR(256) = 'BlitzLock', --put a standard here no need to check later in the script - @ExportToExcel BIT = 0 + @DatabaseName sysname = NULL, + @StartDate datetime = NULL, + @EndDate datetime = NULL, + @ObjectName nvarchar(1024) = NULL, + @StoredProcName nvarchar(1024) = NULL, + @AppName sysname = NULL, + @HostName sysname = NULL, + @LoginName sysname = NULL, + @EventSessionName sysname = N'system_health', + @TargetSessionType sysname = NULL, + @VictimsOnly bit = 0, + @Debug bit = 0, + @Help bit = 0, + @Version varchar(30) = NULL OUTPUT, + @VersionDate datetime = NULL OUTPUT, + @VersionCheckMode bit = 0, + @OutputDatabaseName sysname = NULL, + @OutputSchemaName sysname = N'dbo', /*ditto as below*/ + @OutputTableName sysname = N'BlitzLock', /*put a standard here no need to check later in the script*/ + @ExportToExcel bit = 0 ) WITH RECOMPILE AS BEGIN + SET STATISTICS XML OFF; + SET NOCOUNT, XACT_ABORT ON; + SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SET NOCOUNT ON; -SET STATISTICS XML OFF; -SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + SELECT @Version = '8.12', @VersionDate = '20221213'; -SELECT @Version = '8.11', @VersionDate = '20221013'; + IF @VersionCheckMode = 1 + BEGIN + RETURN; + END; + IF @Help = 1 + BEGIN + PRINT N' + /* + sp_BlitzLock from http://FirstResponderKit.org -IF(@VersionCheckMode = 1) -BEGIN - RETURN; -END; - IF @Help = 1 - BEGIN - PRINT ' - /* - sp_BlitzLock from http://FirstResponderKit.org - - This script checks for and analyzes deadlocks from the system health session or a custom extended event path + This script checks for and analyzes deadlocks from the system health session or a custom extended event path - Variables you can use: - @Top: Use if you want to limit the number of deadlocks to return. - This is ordered by event date ascending + Variables you can use: - @DatabaseName: If you want to filter to a specific database + @DatabaseName: If you want to filter to a specific database - @StartDate: The date you want to start searching on. + @StartDate: The date you want to start searching on, defaults to last 7 days - @EndDate: The date you want to stop searching on. + @EndDate: The date you want to stop searching on, defaults to current date - @ObjectName: If you want to filter to a specific able. - The object name has to be fully qualified ''Database.Schema.Table'' + @ObjectName: If you want to filter to a specific able. + The object name has to be fully qualified ''Database.Schema.Table'' - @StoredProcName: If you want to search for a single stored proc - The proc name has to be fully qualified ''Database.Schema.Sproc'' - - @AppName: If you want to filter to a specific application - - @HostName: If you want to filter to a specific host - - @LoginName: If you want to filter to a specific login + @StoredProcName: If you want to search for a single stored proc + The proc name has to be fully qualified ''Database.Schema.Sproc'' - @EventSessionPath: If you want to point this at an XE session rather than the system health session. - - @OutputDatabaseName: If you want to output information to a specific database - @OutputSchemaName: Specify a schema name to output information to a specific Schema - @OutputTableName: Specify table name to to output information to a specific table - - To learn more, visit http://FirstResponderKit.org where you can download new - versions for free, watch training videos on how it works, get more info on - the findings, contribute your own code, and more. + @AppName: If you want to filter to a specific application - Known limitations of this version: - - Only SQL Server 2012 and newer is supported - - If your tables have weird characters in them (https://en.wikipedia.org/wiki/List_of_XML_and_HTML_character_entity_references) you may get errors trying to parse the XML. - I took a long look at this one, and: - 1) Trying to account for all the weird places these could crop up is a losing effort. - 2) Replace is slow af on lots of XML. + @HostName: If you want to filter to a specific host + @LoginName: If you want to filter to a specific login + @EventSessionName: If you want to point this at an XE session rather than the system health session. + @TargetSessionType: Can be ''ring_buffer'' or ''event_file''. Leave NULL to auto-detect. - Unknown limitations of this version: - - None. (If we knew them, they would be known. Duh.) + @OutputDatabaseName: If you want to output information to a specific database + + @OutputSchemaName: Specify a schema name to output information to a specific Schema + + @OutputTableName: Specify table name to to output information to a specific table + + To learn more, visit http://FirstResponderKit.org where you can download new + versions for free, watch training videos on how it works, get more info on + the findings, contribute your own code, and more. + Known limitations of this version: + - Only SQL Server 2012 and newer is supported + - If your tables have weird characters in them (https://en.wikipedia.org/wiki/List_of_xml_and_HTML_character_entity_references) you may get errors trying to parse the xml. + I took a long look at this one, and: + 1) Trying to account for all the weird places these could crop up is a losing effort. + 2) Replace is slow af on lots of xml. + + Unknown limitations of this version: + - None. (If we knew them, they would be known. Duh.) MIT License - - Copyright (c) 2021 Brent Ozar Unlimited - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: + Copyright (c) 2022 Brent Ozar Unlimited - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE. + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. - */'; - RETURN; - END; /* @Help = 1 */ - - DECLARE @ProductVersion NVARCHAR(128), - @ProductVersionMajor FLOAT, - @ProductVersionMinor INT, - @ObjectFullName NVARCHAR(2000); + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + + */'; + + RETURN; + END; /* @Help = 1 */ + + /*Declare local variables used in the procudure*/ + DECLARE + @DatabaseId int = + DB_ID(@DatabaseName), + @ProductVersion nvarchar(128) = + CAST(SERVERPROPERTY('ProductVersion') AS nvarchar(128)), + @ProductVersionMajor float = + SUBSTRING + ( + CAST(SERVERPROPERTY('ProductVersion') AS nvarchar(128)), + 1, + CHARINDEX('.', CAST(SERVERPROPERTY('ProductVersion') AS nvarchar(128))) + 1 + ), + @ProductVersionMinor int = + PARSENAME + ( + CONVERT + ( + varchar(32), + CAST(SERVERPROPERTY('ProductVersion') AS nvarchar(128)) + ), + 2 + ), + @ObjectFullName nvarchar(MAX) = N'', + @Azure bit = + CASE + WHEN + ( + SELECT + SERVERPROPERTY('EDITION') + ) = 'SQL Azure' + THEN 1 + ELSE 0 + END, + @RDS bit = + CASE + WHEN LEFT(CAST(SERVERPROPERTY('ComputerNamePhysicalNetBIOS') AS varchar(8000)), 8) <> 'EC2AMAZ-' + AND LEFT(CAST(SERVERPROPERTY('MachineName') AS varchar(8000)), 8) <> 'EC2AMAZ-' + AND DB_ID('rdsadmin') IS NULL + THEN 0 + ELSE 1 + END, + @d varchar(40) = '', + @StringToExecute nvarchar(4000) = N'', + @StringToExecuteParams nvarchar(500) = N'', + @r sysname = NULL, + @OutputTableFindings nvarchar(100) = N'[BlitzLockFindings]', + @DeadlockCount int = 0, + @ServerName sysname = @@SERVERNAME, + @OutputDatabaseCheck bit = -1, + @SessionId int = 0, + @TargetSessionId int = 0, + @FileName nvarchar(4000) = N'', + @inputbuf_bom nvarchar(1) = CONVERT(nvarchar(1), 0x0a00, 0), + @deadlock_result nvarchar(MAX) = N''; + + /*Temporary objects used in the procedure*/ + DECLARE + @sysAssObjId AS table + ( + database_id int, + partition_id bigint, + schema_name sysname, + table_name sysname + ); + + CREATE TABLE + #x + ( + x xml NOT NULL + DEFAULT N'x' + ); + + CREATE TABLE + #deadlock_data + ( + deadlock_xml xml NOT NULL + DEFAULT N'x' + ); + + CREATE TABLE + #t + ( + id int NOT NULL + ); + + CREATE TABLE + #deadlock_findings + ( + id int IDENTITY PRIMARY KEY, + check_id int NOT NULL, + database_name nvarchar(256), + object_name nvarchar(1000), + finding_group nvarchar(100), + finding nvarchar(4000) + ); + /*Set these to some sane defaults if NULLs are passed in*/ + /*Normally I'd hate this, but we RECOMPILE everything*/ + SELECT + @StartDate = + CASE + WHEN @StartDate IS NULL + THEN + DATEADD + ( + MINUTE, + DATEDIFF + ( + MINUTE, + SYSDATETIME(), + GETUTCDATE() + ), + ISNULL + ( + @StartDate, + DATEADD + ( + DAY, + -7, + SYSDATETIME() + ) + ) + ) + ELSE @StartDate + END, + @EndDate = + CASE + WHEN @EndDate IS NULL + THEN + DATEADD + ( + MINUTE, + DATEDIFF + ( + MINUTE, + SYSDATETIME(), + GETUTCDATE() + ), + ISNULL + ( + @EndDate, + SYSDATETIME() + ) + ) + ELSE @EndDate + END; - SET @ProductVersion = CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)); + IF @OutputDatabaseName IS NOT NULL + BEGIN /*IF databaseName is set, do some sanity checks and put [] around def.*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('@OutputDatabaseName set to %s, checking validity at %s', 0, 1, @OutputDatabaseName, @d) WITH NOWAIT; + + IF NOT EXISTS + ( + SELECT + 1/0 + FROM sys.databases AS d + WHERE d.name = @OutputDatabaseName + ) /*If database is invalid raiserror and set bitcheck*/ + BEGIN + RAISERROR('Database Name (%s) for output of table is invalid please, Output to Table will not be performed', 0, 1, @OutputDatabaseName) WITH NOWAIT; + SET @OutputDatabaseCheck = -1; /* -1 invalid/false, 0 = good/true */ + END; + ELSE + BEGIN + SET @OutputDatabaseCheck = 0; - SELECT @ProductVersionMajor = SUBSTRING(@ProductVersion, 1, CHARINDEX('.', @ProductVersion) + 1), - @ProductVersionMinor = PARSENAME(CONVERT(VARCHAR(32), @ProductVersion), 2); + SELECT + @StringToExecute = + N'SELECT @r = o.name FROM ' + + @OutputDatabaseName + + N'.sys.objects AS o WHERE o.type_desc = N''USER_TABLE'' AND o.name = ' + + QUOTENAME + ( + @OutputTableName, + N'''' + ) + + N' AND o.schema_id = SCHEMA_ID(' + + QUOTENAME + ( + @OutputSchemaName, + N'''' + ) + + N');', + @StringToExecuteParams = + N'@r sysname OUTPUT'; + + IF @Debug = 1 BEGIN PRINT @StringToExecute; END; + EXEC sys.sp_executesql + @StringToExecute, + @StringToExecuteParams, + @r OUTPUT; + IF @Debug = 1 + BEGIN + RAISERROR('@r is set to: %s for schema name %s and table name %s', 0, 1, @r, @OutputSchemaName, @OutputTableName) WITH NOWAIT; + END; - IF @ProductVersionMajor < 11.0 + /*protection spells*/ + SELECT + @ObjectFullName = + QUOTENAME(@OutputDatabaseName) + + N'.' + + QUOTENAME(@OutputSchemaName) + + N'.' + + QUOTENAME(@OutputTableName), + @OutputDatabaseName = + QUOTENAME(@OutputDatabaseName), + @OutputTableName = + QUOTENAME(@OutputTableName), + @OutputSchemaName = + QUOTENAME(@OutputSchemaName); + + IF (@r IS NOT NULL) /*if it is not null, there is a table, so check for newly added columns*/ BEGIN - RAISERROR( - 'sp_BlitzLock will throw a bunch of angry errors on versions of SQL Server earlier than 2012.', - 0, - 1) WITH NOWAIT; - RETURN; + /* If the table doesn't have the new spid column, add it. See Github #3101. */ + SET @StringToExecute = + N'IF NOT EXISTS (SELECT 1/0 FROM ' + + @OutputDatabaseName + + N'.sys.all_columns AS o WHERE o.object_id = (OBJECT_ID(''' + + @ObjectFullName + + N''')) AND o.name = N''spid'') + /*Add spid column*/ + ALTER TABLE ' + + @ObjectFullName + + N' ADD spid smallint NULL;'; + + IF @Debug = 1 BEGIN PRINT @StringToExecute; END; + EXEC sys.sp_executesql + @StringToExecute; + + /* If the table doesn't have the new wait_resource column, add it. See Github #3101. */ + SET @StringToExecute = + N'IF NOT EXISTS (SELECT 1/0 FROM ' + + @OutputDatabaseName + + N'.sys.all_columns AS o WHERE o.object_id = (OBJECT_ID(''' + + @ObjectFullName + + N''')) AND o.name = N''wait_resource'') + /*Add wait_resource column*/ + ALTER TABLE ' + + @ObjectFullName + + N' ADD wait_resource nvarchar(MAX) NULL;'; + + IF @Debug = 1 BEGIN PRINT @StringToExecute; END; + EXEC sys.sp_executesql + @StringToExecute; + + /* If the table doesn't have the new client option column, add it. See Github #3101. */ + SET @StringToExecute = + N'IF NOT EXISTS (SELECT 1/0 FROM ' + + @OutputDatabaseName + + N'.sys.all_columns AS o WHERE o.object_id = (OBJECT_ID(''' + + @ObjectFullName + + N''')) AND o.name = N''client_option_1'') + /*Add wait_resource column*/ + ALTER TABLE ' + + @ObjectFullName + + N' ADD client_option_1 nvarchar(8000) NULL;'; + + IF @Debug = 1 BEGIN PRINT @StringToExecute; END; + EXEC sys.sp_executesql + @StringToExecute; + + /* If the table doesn't have the new client option column, add it. See Github #3101. */ + SET @StringToExecute = + N'IF NOT EXISTS (SELECT 1/0 FROM ' + + @OutputDatabaseName + + N'.sys.all_columns AS o WHERE o.object_id = (OBJECT_ID(''' + + @ObjectFullName + + N''')) AND o.name = N''client_option_2'') + /*Add wait_resource column*/ + ALTER TABLE ' + + @ObjectFullName + + N' ADD client_option_2 nvarchar(8000) NULL;'; + + IF @Debug = 1 BEGIN PRINT @StringToExecute; END; + EXEC sys.sp_executesql + @StringToExecute; + + /* If the table doesn't have the new lock mode column, add it. See Github #3101. */ + SET @StringToExecute = + N'IF NOT EXISTS (SELECT 1/0 FROM ' + + @OutputDatabaseName + + N'.sys.all_columns AS o WHERE o.object_id = (OBJECT_ID(''' + + @ObjectFullName + + N''')) AND o.name = N''lock_mode'') + /*Add wait_resource column*/ + ALTER TABLE ' + + @ObjectFullName + + N' ADD lock_mode nvarchar(256) NULL;'; + + IF @Debug = 1 BEGIN PRINT @StringToExecute; END; + EXEC sys.sp_executesql + @StringToExecute; + + /* If the table doesn't have the new status column, add it. See Github #3101. */ + SET @StringToExecute = + N'IF NOT EXISTS (SELECT 1/0 FROM ' + + @OutputDatabaseName + + N'.sys.all_columns AS o WHERE o.object_id = (OBJECT_ID(''' + + @ObjectFullName + + N''')) AND o.name = N''status'') + /*Add wait_resource column*/ + ALTER TABLE ' + + @ObjectFullName + + N' ADD status nvarchar(256) NULL;'; + + IF @Debug = 1 BEGIN PRINT @StringToExecute; END; + EXEC sys.sp_executesql + @StringToExecute; END; - - IF ((SELECT SERVERPROPERTY ('EDITION')) = 'SQL Azure' - AND - LOWER(@EventSessionPath) NOT LIKE 'http%') + ELSE /* end if @r is not null. if it is null there is no table, create it from above execution */ + BEGIN + SELECT + @StringToExecute = + N'USE ' + + @OutputDatabaseName + + N'; + CREATE TABLE ' + + @OutputSchemaName + + N'.' + + @OutputTableName + + N' ( + ServerName nvarchar(256), + deadlock_type nvarchar(256), + event_date datetime, + database_name nvarchar(256), + spid smallint, + deadlock_group nvarchar(256), + query xml, + object_names xml, + isolation_level nvarchar(256), + owner_mode nvarchar(256), + waiter_mode nvarchar(256), + lock_mode nvarchar(256), + transaction_count bigint, + client_option_1 varchar(2000), + client_option_2 varchar(2000), + login_name nvarchar(256), + host_name nvarchar(256), + client_app nvarchar(1024), + wait_time bigint, + wait_resource nvarchar(max), + priority smallint, + log_used bigint, + last_tran_started datetime, + last_batch_started datetime, + last_batch_completed datetime, + transaction_name nvarchar(256), + status nvarchar(256), + owner_waiter_type nvarchar(256), + owner_activity nvarchar(256), + owner_waiter_activity nvarchar(256), + owner_merging nvarchar(256), + owner_spilling nvarchar(256), + owner_waiting_to_close nvarchar(256), + waiter_waiter_type nvarchar(256), + waiter_owner_activity nvarchar(256), + waiter_waiter_activity nvarchar(256), + waiter_merging nvarchar(256), + waiter_spilling nvarchar(256), + waiter_waiting_to_close nvarchar(256), + deadlock_graph xml + )'; + + IF @Debug = 1 BEGIN PRINT @StringToExecute; END; + EXEC sys.sp_executesql + @StringToExecute; + + /*table created.*/ + SELECT + @StringToExecute = + N'SELECT @r = o.name FROM ' + + @OutputDatabaseName + + N'.sys.objects AS o + WHERE o.type_desc = N''USER_TABLE'' + AND o.name = N''BlitzLockFindings''', + @StringToExecuteParams = + N'@r sysname OUTPUT'; + + IF @Debug = 1 BEGIN PRINT @StringToExecute; END; + EXEC sys.sp_executesql + @StringToExecute, + @StringToExecuteParams, + @r OUTPUT; + + IF (@r IS NULL) /*if table does not exist*/ + BEGIN + SELECT + @OutputTableFindings = + QUOTENAME(N'BlitzLockFindings'), + @StringToExecute = + N'USE ' + + @OutputDatabaseName + + N'; + CREATE TABLE ' + + @OutputSchemaName + + N'.' + + @OutputTableFindings + + N' ( + ServerName nvarchar(256), + check_id INT, + database_name nvarchar(256), + object_name nvarchar(1000), + finding_group nvarchar(100), + finding nvarchar(4000) + );'; + + IF @Debug = 1 BEGIN PRINT @StringToExecute; END; + EXEC sys.sp_executesql + @StringToExecute; + END; + END; + + /*create synonym for deadlockfindings.*/ + IF EXISTS + ( + SELECT + 1/0 + FROM sys.objects AS o + WHERE o.name = N'DeadlockFindings' + AND o.type_desc = N'SYNONYM' + ) BEGIN - RAISERROR( - 'The default storage path doesn''t work in Azure SQLDB/Managed instances. -You need to use an Azure storage account, and the path has to look like this: https://StorageAccount.blob.core.windows.net/Container/FileName.xel', - 0, - 1) WITH NOWAIT; - RETURN; + RAISERROR('Found synonym DeadlockFindings, dropping', 0, 1) WITH NOWAIT; + DROP SYNONYM DeadlockFindings; + END; + + RAISERROR('Creating synonym DeadlockFindings', 0, 1) WITH NOWAIT; + SET @StringToExecute = + N'CREATE SYNONYM DeadlockFindings FOR ' + + @OutputDatabaseName + + N'.' + + @OutputSchemaName + + N'.' + + @OutputTableFindings; + + IF @Debug = 1 BEGIN PRINT @StringToExecute; END; + EXEC sys.sp_executesql + @StringToExecute; + + /*create synonym for deadlock table.*/ + IF EXISTS + ( + SELECT + 1/0 + FROM sys.objects AS o + WHERE o.name = N'DeadLockTbl' + AND o.type_desc = N'SYNONYM' + ) + BEGIN + RAISERROR('Found synonym DeadLockTbl, dropping', 0, 1) WITH NOWAIT; + DROP SYNONYM DeadLockTbl; + END; + + RAISERROR('Creating synonym DeadLockTbl', 0, 1) WITH NOWAIT; + SET @StringToExecute = + N'CREATE SYNONYM DeadLockTbl FOR ' + + @OutputDatabaseName + + N'.' + + @OutputSchemaName + + N'.' + + @OutputTableName; + + IF @Debug = 1 BEGIN PRINT @StringToExecute; END; + EXEC sys.sp_executesql + @StringToExecute; + END; + END; + + /* WITH ROWCOUNT doesn't work on Amazon RDS - see: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2037 */ + IF @RDS = 0 + BEGIN; + BEGIN TRY; + RAISERROR('@RDS = 0, updating #t with high row and page counts', 0, 1) WITH NOWAIT; + UPDATE STATISTICS + #t + WITH + ROWCOUNT = 9223372036854775807, + PAGECOUNT = 9223372036854775807; + END TRY + BEGIN CATCH; + /* Misleading error returned, if run without permissions to update statistics the error returned is "Cannot find object". + Catching specific error, and returning message with better info. If any other error is returned, then throw as normal */ + IF (ERROR_NUMBER() = 1088) + BEGIN; + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Cannot run UPDATE STATISTICS on a #temp table without db_owner or sysadmin permissions', 0, 1) WITH NOWAIT; END; + ELSE + BEGIN; + THROW; + END; + END CATCH; + END; + /*If @TargetSessionType, we need to figure out if it's ring buffer or event file*/ + /*Azure has differently named views, so we need to separate. Thanks, Azure.*/ - IF @Top IS NULL - SET @Top = 2147483647; + IF + ( + @Azure = 0 + AND @TargetSessionType IS NULL + ) + BEGIN + RAISERROR('@TargetSessionType is NULL, assigning for non-Azure instance', 0, 1) WITH NOWAIT; + + SELECT TOP (1) + @TargetSessionType = t.target_name + FROM sys.dm_xe_sessions AS s + JOIN sys.dm_xe_session_targets AS t + ON s.address = t.event_session_address + WHERE s.name = @EventSessionName + AND t.target_name IN (N'event_file', N'ring_buffer') + ORDER BY t.target_name + OPTION(RECOMPILE); + + RAISERROR('@TargetSessionType assigned as %s for non-Azure', 0, 1, @TargetSessionType) WITH NOWAIT; + END; - IF @StartDate IS NULL - SET @StartDate = '19000101'; + IF + ( + @Azure = 1 + AND @TargetSessionType IS NULL + ) + BEGIN + RAISERROR('@TargetSessionType is NULL, assigning for Azure instance', 0, 1) WITH NOWAIT; + + SELECT TOP (1) + @TargetSessionType = t.target_name + FROM sys.dm_xe_database_sessions AS s + JOIN sys.dm_xe_database_session_targets AS t + ON s.address = t.event_session_address + WHERE s.name = @EventSessionName + AND t.target_name IN (N'event_file', N'ring_buffer') + ORDER BY t.target_name + OPTION(RECOMPILE); + + RAISERROR('@TargetSessionType assigned as %s for Azure', 0, 1, @TargetSessionType) WITH NOWAIT; + END; - IF @EndDate IS NULL - SET @EndDate = '99991231'; - - IF OBJECT_ID('tempdb..#deadlock_data') IS NOT NULL - DROP TABLE #deadlock_data; + /*The system health stuff gets handled different from user extended events.*/ + /*These next sections deal with user events, dependent on target.*/ - IF OBJECT_ID('tempdb..#deadlock_process') IS NOT NULL - DROP TABLE #deadlock_process; + /*If ring buffers*/ + IF + ( + @TargetSessionType LIKE N'ring%' + AND @EventSessionName NOT LIKE N'system_health%' + ) + BEGIN + IF @Azure = 0 + BEGIN + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('@TargetSessionType is ring_buffer, inserting XML for non-Azure at %s', 0, 1, @d) WITH NOWAIT; - IF OBJECT_ID('tempdb..#deadlock_stack') IS NOT NULL - DROP TABLE #deadlock_stack; + INSERT + #x WITH(TABLOCKX) + ( + x + ) + SELECT + x = TRY_CAST(t.target_data AS xml) + FROM sys.dm_xe_session_targets AS t + JOIN sys.dm_xe_sessions AS s + ON s.address = t.event_session_address + WHERE s.name = @EventSessionName + AND t.target_name = N'ring_buffer' + OPTION(RECOMPILE); + + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + END; - IF OBJECT_ID('tempdb..#deadlock_resource') IS NOT NULL - DROP TABLE #deadlock_resource; + IF @Azure = 1 + BEGIN + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('@TargetSessionType is ring_buffer, inserting XML for Azure at %s', 0, 1, @d) WITH NOWAIT; - IF OBJECT_ID('tempdb..#deadlock_owner_waiter') IS NOT NULL - DROP TABLE #deadlock_owner_waiter; + INSERT + #x WITH(TABLOCKX) + ( + x + ) + SELECT + x = TRY_CAST(t.target_data AS xml) + FROM sys.dm_xe_database_session_targets AS t + JOIN sys.dm_xe_database_sessions AS s + ON s.address = t.event_session_address + WHERE s.name = @EventSessionName + AND t.target_name = N'ring_buffer' + OPTION(RECOMPILE); + + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + END; + END; - IF OBJECT_ID('tempdb..#deadlock_findings') IS NOT NULL - DROP TABLE #deadlock_findings; - CREATE TABLE #deadlock_findings - ( - id INT IDENTITY(1, 1) PRIMARY KEY CLUSTERED, - check_id INT NOT NULL, - database_name NVARCHAR(256), - object_name NVARCHAR(1000), - finding_group NVARCHAR(100), - finding NVARCHAR(4000) - ); + /*If event file*/ + IF + ( + @TargetSessionType LIKE N'event%' + AND @EventSessionName NOT LIKE N'system_health%' + ) + BEGIN + IF @Azure = 0 + BEGIN + RAISERROR('@TargetSessionType is event_file, assigning XML for non-Azure', 0, 1) WITH NOWAIT; - DECLARE @d VARCHAR(40), @StringToExecute NVARCHAR(4000),@StringToExecuteParams NVARCHAR(500),@r NVARCHAR(200),@OutputTableFindings NVARCHAR(100),@DeadlockCount INT; - DECLARE @ServerName NVARCHAR(256) - DECLARE @OutputDatabaseCheck BIT; - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - SET @OutputTableFindings = '[BlitzLockFindings]' - SET @ServerName = (select @@ServerName) - if(@OutputDatabaseName is not null) - BEGIN --if databaseName is set do some sanity checks and put [] around def. - if( (select name from sys.databases where name=@OutputDatabaseName) is null ) --if database is invalid raiserror and set bitcheck - BEGIN - RAISERROR('Database Name for output of table is invalid please correct, Output to Table will not be preformed', 0, 1, @d) WITH NOWAIT; - set @OutputDatabaseCheck = -1 -- -1 invalid/false, 0 = good/true - END - ELSE - BEGIN - set @OutputDatabaseCheck = 0 - select @StringToExecute = N'select @r = name from ' + '' + @OutputDatabaseName + - '' + '.sys.objects where type_desc=''USER_TABLE'' and name=' + '''' + @OutputTableName + '''', - @StringToExecuteParams = N'@OutputDatabaseName NVARCHAR(200),@OutputTableName NVARCHAR(200),@r NVARCHAR(200) OUTPUT' - exec sp_executesql @StringToExecute,@StringToExecuteParams,@OutputDatabaseName,@OutputTableName,@r OUTPUT - --put covers around all before. - SELECT @OutputDatabaseName = QUOTENAME(@OutputDatabaseName), - @OutputTableName = QUOTENAME(@OutputTableName), - @OutputSchemaName = QUOTENAME(@OutputSchemaName) - if(@r is not null) --if it is not null, there is a table, so check for newly added columns - BEGIN - /* If the table doesn't have the new spid column, add it. See Github #3101. */ - SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableName; - SET @StringToExecute = N'IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns - WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + ''')) AND name = ''spid'') - ALTER TABLE ' + @ObjectFullName + N' ADD spid SMALLINT NULL;'; - EXEC(@StringToExecute); + SELECT + @SessionId = t.event_session_id, + @TargetSessionId = t.target_id + FROM sys.server_event_session_targets AS t + JOIN sys.server_event_sessions AS s + ON s.event_session_id = t.event_session_id + WHERE t.name = @TargetSessionType + AND s.name = @EventSessionName + OPTION(RECOMPILE); + + /*We get the file name automatically, here*/ + RAISERROR('Assigning @FileName...', 0, 1) WITH NOWAIT; + SELECT + @FileName = + CASE + WHEN f.file_name LIKE N'%.xel' + THEN REPLACE(f.file_name, N'.xel', N'*.xel') + ELSE f.file_name + N'*.xel' + END + FROM + ( + SELECT + file_name = + CONVERT(nvarchar(4000), f.value) + FROM sys.server_event_session_fields AS f + WHERE f.event_session_id = @SessionId + AND f.object_id = @TargetSessionId + AND f.name = N'filename' + ) AS f + OPTION(RECOMPILE); + END; - /* If the table doesn't have the new wait_resource column, add it. See Github #3101. */ - SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableName; - SET @StringToExecute = N'IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns - WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + ''')) AND name = ''wait_resource'') - ALTER TABLE ' + @ObjectFullName + N' ADD wait_resource NVARCHAR(MAX) NULL;'; - EXEC(@StringToExecute); - END - ELSE --if(@r is not null) --if it is null there is no table, create it from above execution - BEGIN - select @StringToExecute = N'use ' + @OutputDatabaseName + ';create table ' + @OutputSchemaName + '.' + @OutputTableName + ' ( - ServerName NVARCHAR(256), - deadlock_type NVARCHAR(256), - event_date datetime, - database_name NVARCHAR(256), - spid SMALLINT, - deadlock_group NVARCHAR(256), - query XML, - object_names XML, - isolation_level NVARCHAR(256), - owner_mode NVARCHAR(256), - waiter_mode NVARCHAR(256), - transaction_count bigint, - login_name NVARCHAR(256), - host_name NVARCHAR(256), - client_app NVARCHAR(256), - wait_time BIGINT, - wait_resource NVARCHAR(max), - priority smallint, - log_used BIGINT, - last_tran_started datetime, - last_batch_started datetime, - last_batch_completed datetime, - transaction_name NVARCHAR(256), - owner_waiter_type NVARCHAR(256), - owner_activity NVARCHAR(256), - owner_waiter_activity NVARCHAR(256), - owner_merging NVARCHAR(256), - owner_spilling NVARCHAR(256), - owner_waiting_to_close NVARCHAR(256), - waiter_waiter_type NVARCHAR(256), - waiter_owner_activity NVARCHAR(256), - waiter_waiter_activity NVARCHAR(256), - waiter_merging NVARCHAR(256), - waiter_spilling NVARCHAR(256), - waiter_waiting_to_close NVARCHAR(256), - deadlock_graph XML)', - @StringToExecuteParams = N'@OutputDatabaseName NVARCHAR(200),@OutputSchemaName NVARCHAR(100),@OutputTableName NVARCHAR(200)' - exec sp_executesql @StringToExecute, @StringToExecuteParams,@OutputDatabaseName,@OutputSchemaName,@OutputTableName - --table created. - select @StringToExecute = N'select @r = name from ' + '' + @OutputDatabaseName + - '' + '.sys.objects where type_desc=''USER_TABLE'' and name=''BlitzLockFindings''', - @StringToExecuteParams = N'@OutputDatabaseName NVARCHAR(200),@r NVARCHAR(200) OUTPUT' - exec sp_executesql @StringToExecute,@StringToExecuteParams,@OutputDatabaseName,@r OUTPUT - if(@r is null) --if table does not excist - BEGIN - select @OutputTableFindings=N'[BlitzLockFindings]', - @StringToExecute = N'use ' + @OutputDatabaseName + ';create table ' + @OutputSchemaName + '.' + @OutputTableFindings + ' ( - ServerName NVARCHAR(256), - check_id INT, - database_name NVARCHAR(256), - object_name NVARCHAR(1000), - finding_group NVARCHAR(100), - finding NVARCHAR(4000))', - @StringToExecuteParams = N'@OutputDatabaseName NVARCHAR(200),@OutputSchemaName NVARCHAR(100),@OutputTableFindings NVARCHAR(200)' - exec sp_executesql @StringToExecute, @StringToExecuteParams, @OutputDatabaseName,@OutputSchemaName,@OutputTableFindings - - END + IF @Azure = 1 + BEGIN + RAISERROR('@TargetSessionType is event_file, assigning XML for Azure', 0, 1) WITH NOWAIT; + SELECT + @SessionId = + t.event_session_address, + @TargetSessionId = + t.target_name + FROM sys.dm_xe_database_session_targets t + JOIN sys.dm_xe_database_sessions s + ON s.address = t.event_session_address + WHERE t.target_name = @TargetSessionType + AND s.name = @EventSessionName + OPTION(RECOMPILE); + + /*We get the file name automatically, here*/ + RAISERROR('Assigning @FileName...', 0, 1) WITH NOWAIT; + SELECT + @FileName = + CASE + WHEN f.file_name LIKE N'%.xel' + THEN REPLACE(f.file_name, N'.xel', N'*.xel') + ELSE f.file_name + N'*.xel' + END + FROM + ( + SELECT + file_name = + CONVERT(nvarchar(4000), f.value) + FROM sys.server_event_session_fields AS f + WHERE f.event_session_id = @SessionId + AND f.object_id = @TargetSessionId + AND f.name = N'filename' + ) AS f + OPTION(RECOMPILE); + END; - END - --create synonym for deadlockfindings. - if((select name from sys.objects where name='DeadlockFindings' and type_desc='SYNONYM')IS NOT NULL) - BEGIN - RAISERROR('found synonym', 0, 1) WITH NOWAIT; - drop synonym DeadlockFindings; - END - set @StringToExecute = 'CREATE SYNONYM DeadlockFindings FOR ' + @OutputDatabaseName + '.' + @OutputSchemaName + '.' + @OutputTableFindings; - exec sp_executesql @StringToExecute - - --create synonym for deadlock table. - if((select name from sys.objects where name='DeadLockTbl' and type_desc='SYNONYM') IS NOT NULL) - BEGIN - drop SYNONYM DeadLockTbl; - END - set @StringToExecute = 'CREATE SYNONYM DeadLockTbl FOR ' + @OutputDatabaseName + '.' + @OutputSchemaName + '.' + @OutputTableName; - exec sp_executesql @StringToExecute - - END - END - + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Reading for event_file %s', 0, 1, @FileName) WITH NOWAIT; - CREATE TABLE #t (id INT NOT NULL); + INSERT + #x WITH(TABLOCKX) + ( + x + ) + SELECT + x = TRY_CAST(f.event_data AS xml) + FROM sys.fn_xe_file_target_read_file(@FileName, NULL, NULL, NULL) AS f + LEFT JOIN #t AS t + ON 1 = 1 + OPTION(RECOMPILE); - /* WITH ROWCOUNT doesn't work on Amazon RDS - see: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2037 */ - IF LEFT(CAST(SERVERPROPERTY('ComputerNamePhysicalNetBIOS') AS VARCHAR(8000)), 8) <> 'EC2AMAZ-' - AND LEFT(CAST(SERVERPROPERTY('MachineName') AS VARCHAR(8000)), 8) <> 'EC2AMAZ-' - AND db_id('rdsadmin') IS NULL - BEGIN; - BEGIN TRY; - UPDATE STATISTICS #t WITH ROWCOUNT = 100000000, PAGECOUNT = 100000000; - END TRY - BEGIN CATCH; - /* Misleading error returned, if run without permissions to update statistics the error returned is "Cannot find object". - Catching specific error, and returning message with better info. If any other error is returned, then throw as normal */ - IF (ERROR_NUMBER() = 1088) - BEGIN; - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Cannot run UPDATE STATISTICS on temp table without db_owner or sysadmin permissions %s', 0, 1, @d) WITH NOWAIT; - END; - ELSE - BEGIN; - THROW; - END; - END CATCH; - END; + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + END; + + /*The XML is parsed differently if it comes from the event file or ring buffer*/ + + /*If ring buffers*/ + IF + ( + @TargetSessionType LIKE N'ring%' + AND @EventSessionName NOT LIKE N'system_health%' + ) + BEGIN + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Inserting to #deadlock_data for ring buffer data', 0, 1) WITH NOWAIT; - /*Grab the initial set of XML to parse*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Grab the initial set of XML to parse at %s', 0, 1, @d) WITH NOWAIT; - WITH xml - AS ( SELECT CONVERT(XML, event_data) AS deadlock_xml - FROM sys.fn_xe_file_target_read_file(@EventSessionPath, NULL, NULL, NULL) ) - SELECT ISNULL(xml.deadlock_xml, '') AS deadlock_xml - INTO #deadlock_data - FROM xml + INSERT + #deadlock_data WITH(TABLOCKX) + ( + deadlock_xml + ) + SELECT + deadlock_xml = + e.x.query(N'.') + FROM #x AS x LEFT JOIN #t AS t - ON 1 = 1 - CROSS APPLY xml.deadlock_xml.nodes('/event/@name') AS x(c) - WHERE 1 = 1 - AND x.c.exist('/event/@name[ .= "xml_deadlock_report"]') = 1 - AND CONVERT(datetime, SWITCHOFFSET(CONVERT(datetimeoffset, xml.deadlock_xml.value('(/event/@timestamp)[1]', 'datetime') ), DATENAME(TzOffset, SYSDATETIMEOFFSET()))) > @StartDate - AND CONVERT(datetime, SWITCHOFFSET(CONVERT(datetimeoffset, xml.deadlock_xml.value('(/event/@timestamp)[1]', 'datetime') ), DATENAME(TzOffset, SYSDATETIMEOFFSET()))) < @EndDate - ORDER BY xml.deadlock_xml.value('(/event/@timestamp)[1]', 'datetime') DESC - OPTION ( RECOMPILE ); + ON 1 = 1 + CROSS APPLY x.x.nodes('/RingBufferTarget/event') AS e(x) + WHERE e.x.exist('@name[ .= "xml_deadlock_report"]') = 1 + AND e.x.exist('@timestamp[. >= sql:variable("@StartDate")]') = 1 + AND e.x.exist('@timestamp[. < sql:variable("@EndDate")]') = 1 + OPTION(RECOMPILE); - /*Optimization: if we got back more rows than @Top, remove them. This seems to be better than using @Top in the query above as that results in excessive memory grant*/ - SET @DeadlockCount = @@ROWCOUNT - IF( @Top < @DeadlockCount ) BEGIN - WITH T - AS ( - SELECT TOP ( @DeadlockCount - @Top) * - FROM #deadlock_data - ORDER BY #deadlock_data.deadlock_xml.value('(/event/@timestamp)[1]', 'datetime') ASC) - DELETE FROM T - END + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + END; - /*Parse process and input buffer XML*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Parse process and input buffer XML %s', 0, 1, @d) WITH NOWAIT; - SELECT q.event_date, - q.victim_id, - CONVERT(BIT, q.is_parallel) AS is_parallel, - q.deadlock_graph, - q.id, - q.spid, - q.database_id, - q.priority, - q.log_used, - q.wait_resource, - q.wait_time, - q.transaction_name, - q.last_tran_started, - q.last_batch_started, - q.last_batch_completed, - q.lock_mode, - q.transaction_count, - q.client_app, - q.host_name, - q.login_name, - q.isolation_level, - q.process_xml, - ISNULL(ca2.ib.query('.'), '') AS input_buffer - INTO #deadlock_process - FROM ( SELECT dd.deadlock_xml, - CONVERT(DATETIME2(7), SWITCHOFFSET(CONVERT(datetimeoffset, dd.event_date ), DATENAME(TzOffset, SYSDATETIMEOFFSET()))) AS event_date, - dd.victim_id, - CONVERT(tinyint, dd.is_parallel) + CONVERT(tinyint, dd.is_parallel_batch) AS is_parallel, - dd.deadlock_graph, - ca.dp.value('@id', 'NVARCHAR(256)') AS id, - ca.dp.value('@spid', 'SMALLINT') AS spid, - ca.dp.value('@currentdb', 'BIGINT') AS database_id, - ca.dp.value('@priority', 'SMALLINT') AS priority, - ca.dp.value('@logused', 'BIGINT') AS log_used, - ca.dp.value('@waitresource', 'NVARCHAR(256)') AS wait_resource, - ca.dp.value('@waittime', 'BIGINT') AS wait_time, - ca.dp.value('@transactionname', 'NVARCHAR(256)') AS transaction_name, - ca.dp.value('@lasttranstarted', 'DATETIME2(7)') AS last_tran_started, - ca.dp.value('@lastbatchstarted', 'DATETIME2(7)') AS last_batch_started, - ca.dp.value('@lastbatchcompleted', 'DATETIME2(7)') AS last_batch_completed, - ca.dp.value('@lockMode', 'NVARCHAR(256)') AS lock_mode, - ca.dp.value('@trancount', 'BIGINT') AS transaction_count, - ca.dp.value('@clientapp', 'NVARCHAR(256)') AS client_app, - ca.dp.value('@hostname', 'NVARCHAR(256)') AS host_name, - ca.dp.value('@loginname', 'NVARCHAR(256)') AS login_name, - ca.dp.value('@isolationlevel', 'NVARCHAR(256)') AS isolation_level, - ISNULL(ca.dp.query('.'), '') AS process_xml - FROM ( SELECT d1.deadlock_xml, - d1.deadlock_xml.value('(event/@timestamp)[1]', 'DATETIME2') AS event_date, - d1.deadlock_xml.value('(//deadlock/victim-list/victimProcess/@id)[1]', 'NVARCHAR(256)') AS victim_id, - d1.deadlock_xml.exist('//deadlock/resource-list/exchangeEvent') AS is_parallel, - d1.deadlock_xml.exist('//deadlock/resource-list/SyncPoint') AS is_parallel_batch, - d1.deadlock_xml.query('/event/data/value/deadlock') AS deadlock_graph - FROM #deadlock_data AS d1 ) AS dd - CROSS APPLY dd.deadlock_xml.nodes('//deadlock/process-list/process') AS ca(dp) - WHERE (ca.dp.value('@currentdb', 'BIGINT') = DB_ID(@DatabaseName) OR @DatabaseName IS NULL) - AND (ca.dp.value('@clientapp', 'NVARCHAR(256)') = @AppName OR @AppName IS NULL) - AND (ca.dp.value('@hostname', 'NVARCHAR(256)') = @HostName OR @HostName IS NULL) - AND (ca.dp.value('@loginname', 'NVARCHAR(256)') = @LoginName OR @LoginName IS NULL) - ) AS q - CROSS APPLY q.deadlock_xml.nodes('//deadlock/process-list/process/inputbuf') AS ca2(ib) - OPTION ( RECOMPILE ); + /*If event file*/ + IF + ( + @TargetSessionType LIKE N'event_file%' + AND @EventSessionName NOT LIKE N'system_health%' + ) + BEGIN + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Inserting to #deadlock_data for event file data', 0, 1) WITH NOWAIT; + INSERT + #deadlock_data WITH(TABLOCKX) + ( + deadlock_xml + ) + SELECT + deadlock_xml = + e.x.query('.') + FROM #x AS x + LEFT JOIN #t AS t + ON 1 = 1 + CROSS APPLY x.x.nodes('/event') AS e(x) + WHERE e.x.exist('/event/@name[ .= "xml_deadlock_report"]') = 1 + AND e.x.exist('/event/@timestamp[. >= sql:variable("@StartDate")]') = 1 + AND e.x.exist('/event/@timestamp[. < sql:variable("@EndDate")]') = 1 + OPTION(RECOMPILE); - /*Parse execution stack XML*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Parse execution stack XML %s', 0, 1, @d) WITH NOWAIT; - SELECT DISTINCT - dp.id, - dp.event_date, - ca.dp.value('@procname', 'NVARCHAR(1000)') AS proc_name, - ca.dp.value('@sqlhandle', 'NVARCHAR(128)') AS sql_handle - INTO #deadlock_stack - FROM #deadlock_process AS dp - CROSS APPLY dp.process_xml.nodes('//executionStack/frame') AS ca(dp) - WHERE (ca.dp.value('@procname', 'NVARCHAR(256)') = @StoredProcName OR @StoredProcName IS NULL) - OPTION ( RECOMPILE ); + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + END; + /*This section deals with event file*/ + IF + ( + @TargetSessionType LIKE N'event%' + AND @EventSessionName LIKE N'system_health%' + ) + BEGIN + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Grab the initial set of xml to parse at %s', 0, 1, @d) WITH NOWAIT; + IF @Debug = 1 BEGIN SET STATISTICS XML ON; END; - /*Grab the full resource list*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Grab the full resource list %s', 0, 1, @d) WITH NOWAIT; - SELECT - CONVERT(DATETIME2(7), SWITCHOFFSET(CONVERT(datetimeoffset, dr.event_date), DATENAME(TzOffset, SYSDATETIMEOFFSET()))) AS event_date, - dr.victim_id, - dr.resource_xml - INTO #deadlock_resource - FROM + SELECT + xml.deadlock_xml + INTO #xml + FROM ( - SELECT dd.deadlock_xml.value('(event/@timestamp)[1]', 'DATETIME2') AS event_date, - dd.deadlock_xml.value('(//deadlock/victim-list/victimProcess/@id)[1]', 'NVARCHAR(256)') AS victim_id, - ISNULL(ca.dp.query('.'), '') AS resource_xml - FROM #deadlock_data AS dd - CROSS APPLY dd.deadlock_xml.nodes('//deadlock/resource-list') AS ca(dp) - ) AS dr - OPTION ( RECOMPILE ); + SELECT + deadlock_xml = + TRY_CAST(fx.event_data AS xml) + FROM sys.fn_xe_file_target_read_file('system_health*.xel', NULL, NULL, NULL) AS fx + LEFT JOIN #t AS t + ON 1 = 1 + ) AS xml + CROSS APPLY xml.deadlock_xml.nodes('/event') AS e(x) + WHERE e.x.exist('@name[ . = "xml_deadlock_report"]') = 1 + AND e.x.exist('@timestamp[. >= sql:variable("@StartDate")]') = 1 + AND e.x.exist('@timestamp[. < sql:variable("@EndDate")]') = 1 + OPTION(RECOMPILE); + INSERT + #deadlock_data WITH(TABLOCKX) + SELECT + deadlock_xml = + xml.deadlock_xml + FROM #xml AS xml + LEFT JOIN #t AS t + ON 1 = 1 + WHERE xml.deadlock_xml IS NOT NULL + OPTION(RECOMPILE); - /*Parse object locks*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Parse object locks %s', 0, 1, @d) WITH NOWAIT; - SELECT DISTINCT - ca.event_date, - ca.database_id, - ca.object_name, - ca.lock_mode, - ca.index_name, - ca.associatedObjectId, - w.l.value('@id', 'NVARCHAR(256)') AS waiter_id, - w.l.value('@mode', 'NVARCHAR(256)') AS waiter_mode, - o.l.value('@id', 'NVARCHAR(256)') AS owner_id, - o.l.value('@mode', 'NVARCHAR(256)') AS owner_mode, - N'OBJECT' AS lock_type - INTO #deadlock_owner_waiter - FROM ( - SELECT dr.event_date, - ca.dr.value('@dbid', 'BIGINT') AS database_id, - ca.dr.value('@objectname', 'NVARCHAR(256)') AS object_name, - ca.dr.value('@mode', 'NVARCHAR(256)') AS lock_mode, - ca.dr.value('@indexname', 'NVARCHAR(256)') AS index_name, - ca.dr.value('@associatedObjectId', 'BIGINT') AS associatedObjectId, - ca.dr.query('.') AS dr - FROM #deadlock_resource AS dr - CROSS APPLY dr.resource_xml.nodes('//resource-list/objectlock') AS ca(dr) - ) AS ca - CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l) - CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l) - WHERE (ca.object_name = @ObjectName OR @ObjectName IS NULL) - OPTION ( RECOMPILE ); + IF @Debug = 1 BEGIN SET STATISTICS XML OFF; END; + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + END; + /*Parse process and input buffer xml*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Initial Parse process and input buffer xml %s', 0, 1, @d) WITH NOWAIT; - /*Parse page locks*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Parse page locks %s', 0, 1, @d) WITH NOWAIT; - INSERT #deadlock_owner_waiter WITH(TABLOCKX) - SELECT DISTINCT - ca.event_date, - ca.database_id, - ca.object_name, - ca.lock_mode, - ca.index_name, - ca.associatedObjectId, - w.l.value('@id', 'NVARCHAR(256)') AS waiter_id, - w.l.value('@mode', 'NVARCHAR(256)') AS waiter_mode, - o.l.value('@id', 'NVARCHAR(256)') AS owner_id, - o.l.value('@mode', 'NVARCHAR(256)') AS owner_mode, - N'PAGE' AS lock_type - FROM ( - SELECT dr.event_date, - ca.dr.value('@dbid', 'BIGINT') AS database_id, - ca.dr.value('@objectname', 'NVARCHAR(256)') AS object_name, - ca.dr.value('@mode', 'NVARCHAR(256)') AS lock_mode, - ca.dr.value('@indexname', 'NVARCHAR(256)') AS index_name, - ca.dr.value('@associatedObjectId', 'BIGINT') AS associatedObjectId, - ca.dr.query('.') AS dr - FROM #deadlock_resource AS dr - CROSS APPLY dr.resource_xml.nodes('//resource-list/pagelock') AS ca(dr) - ) AS ca - CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l) - CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l) - OPTION ( RECOMPILE ); + SELECT + d1.deadlock_xml, + event_date = d1.deadlock_xml.value('(event/@timestamp)[1]', 'datetime2'), + victim_id = d1.deadlock_xml.value('(//deadlock/victim-list/victimProcess/@id)[1]', 'nvarchar(256)'), + is_parallel = d1.deadlock_xml.exist('//deadlock/resource-list/exchangeEvent'), + is_parallel_batch = d1.deadlock_xml.exist('//deadlock/resource-list/SyncPoint'), + deadlock_graph = d1.deadlock_xml.query('/event/data/value/deadlock') + INTO #dd + FROM #deadlock_data AS d1 + LEFT JOIN #t AS t + ON 1 = 1 + OPTION(RECOMPILE); + + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Final Parse process and input buffer xml %s', 0, 1, @d) WITH NOWAIT; + + SELECT + q.event_date, + q.victim_id, + is_parallel = + CONVERT(bit, q.is_parallel), + q.deadlock_graph, + q.id, + q.spid, + q.database_id, + database_name = + ISNULL + ( + DB_NAME(q.database_id), + N'UNKNOWN' + ), + q.current_database_name, + q.priority, + q.log_used, + q.wait_resource, + q.wait_time, + q.transaction_name, + q.last_tran_started, + q.last_batch_started, + q.last_batch_completed, + q.lock_mode, + q.status, + q.transaction_count, + q.client_app, + q.host_name, + q.login_name, + q.isolation_level, + client_option_1 = + SUBSTRING + ( + CASE WHEN q.clientoption1 & 1 = 1 THEN ', DISABLE_DEF_CNST_CHECK' ELSE '' END + + CASE WHEN q.clientoption1 & 2 = 2 THEN ', IMPLICIT_TRANSACTIONS' ELSE '' END + + CASE WHEN q.clientoption1 & 4 = 4 THEN ', CURSOR_CLOSE_ON_COMMIT' ELSE '' END + + CASE WHEN q.clientoption1 & 8 = 8 THEN ', ANSI_WARNINGS' ELSE '' END + + CASE WHEN q.clientoption1 & 16 = 16 THEN ', ANSI_PADDING' ELSE '' END + + CASE WHEN q.clientoption1 & 32 = 32 THEN ', ANSI_NULLS' ELSE '' END + + CASE WHEN q.clientoption1 & 64 = 64 THEN ', ARITHABORT' ELSE '' END + + CASE WHEN q.clientoption1 & 128 = 128 THEN ', ARITHIGNORE' ELSE '' END + + CASE WHEN q.clientoption1 & 256 = 256 THEN ', QUOTED_IDENTIFIER' ELSE '' END + + CASE WHEN q.clientoption1 & 512 = 512 THEN ', NOCOUNT' ELSE '' END + + CASE WHEN q.clientoption1 & 1024 = 1024 THEN ', ANSI_NULL_DFLT_ON' ELSE '' END + + CASE WHEN q.clientoption1 & 2048 = 2048 THEN ', ANSI_NULL_DFLT_OFF' ELSE '' END + + CASE WHEN q.clientoption1 & 4096 = 4096 THEN ', CONCAT_NULL_YIELDS_NULL' ELSE '' END + + CASE WHEN q.clientoption1 & 8192 = 8192 THEN ', NUMERIC_ROUNDABORT' ELSE '' END + + CASE WHEN q.clientoption1 & 16384 = 16384 THEN ', XACT_ABORT' ELSE '' END, + 3, + 8000 + ), + client_option_2 = + SUBSTRING + ( + CASE WHEN q.clientoption2 & 1024 = 1024 THEN ', DB CHAINING' ELSE '' END + + CASE WHEN q.clientoption2 & 2048 = 2048 THEN ', NUMERIC ROUNDABORT' ELSE '' END + + CASE WHEN q.clientoption2 & 4096 = 4096 THEN ', ARITHABORT' ELSE '' END + + CASE WHEN q.clientoption2 & 8192 = 8192 THEN ', ANSI PADDING' ELSE '' END + + CASE WHEN q.clientoption2 & 16384 = 16384 THEN ', ANSI NULL DEFAULT' ELSE '' END + + CASE WHEN q.clientoption2 & 65536 = 65536 THEN ', CONCAT NULL YIELDS NULL' ELSE '' END + + CASE WHEN q.clientoption2 & 131072 = 131072 THEN ', RECURSIVE TRIGGERS' ELSE '' END + + CASE WHEN q.clientoption2 & 1048576 = 1048576 THEN ', DEFAULT TO LOCAL CURSOR' ELSE '' END + + CASE WHEN q.clientoption2 & 8388608 = 8388608 THEN ', QUOTED IDENTIFIER' ELSE '' END + + CASE WHEN q.clientoption2 & 16777216 = 16777216 THEN ', AUTO CREATE STATISTICS' ELSE '' END + + CASE WHEN q.clientoption2 & 33554432 = 33554432 THEN ', CURSOR CLOSE ON COMMIT' ELSE '' END + + CASE WHEN q.clientoption2 & 67108864 = 67108864 THEN ', ANSI NULLS' ELSE '' END + + CASE WHEN q.clientoption2 & 268435456 = 268435456 THEN ', ANSI WARNINGS' ELSE '' END + + CASE WHEN q.clientoption2 & 536870912 = 536870912 THEN ', FULL TEXT ENABLED' ELSE '' END + + CASE WHEN q.clientoption2 & 1073741824 = 1073741824 THEN ', AUTO UPDATE STATISTICS' ELSE '' END + + CASE WHEN q.clientoption2 & 1469283328 = 1469283328 THEN ', ALL SETTABLE OPTIONS' ELSE '' END, + 3, + 8000 + ), + q.process_xml + INTO #deadlock_process + FROM + ( + SELECT + dd.deadlock_xml, + event_date = + DATEADD + ( + MINUTE, + DATEDIFF(MINUTE, GETUTCDATE(), SYSDATETIME()), + dd.event_date + ), + dd.victim_id, + is_parallel = + CONVERT(tinyint, dd.is_parallel) + + CONVERT(tinyint, dd.is_parallel_batch), + dd.deadlock_graph, + id = ca.dp.value('@id', 'nvarchar(256)'), + spid = ca.dp.value('@spid', 'smallint'), + database_id = ca.dp.value('@currentdb', 'bigint'), + current_database_name = ca.dp.value('@currentdbname', 'nvarchar(256)'), + priority = ca.dp.value('@priority', 'smallint'), + log_used = ca.dp.value('@logused', 'bigint'), + wait_resource = ca.dp.value('@waitresource', 'nvarchar(256)'), + wait_time = ca.dp.value('@waittime', 'bigint'), + transaction_name = ca.dp.value('@transactionname', 'nvarchar(256)'), + last_tran_started = ca.dp.value('@lasttranstarted', 'datetime'), + last_batch_started = ca.dp.value('@lastbatchstarted', 'datetime'), + last_batch_completed = ca.dp.value('@lastbatchcompleted', 'datetime'), + lock_mode = ca.dp.value('@lockMode', 'nvarchar(256)'), + status = ca.dp.value('@status', 'nvarchar(256)'), + transaction_count = ca.dp.value('@trancount', 'bigint'), + client_app = ca.dp.value('@clientapp', 'nvarchar(1024)'), + host_name = ca.dp.value('@hostname', 'nvarchar(256)'), + login_name = ca.dp.value('@loginname', 'nvarchar(256)'), + isolation_level = ca.dp.value('@isolationlevel', 'nvarchar(256)'), + clientoption1 = ca.dp.value('@clientoption1', 'bigint'), + clientoption2 = ca.dp.value('@clientoption2', 'bigint'), + process_xml = ISNULL(ca.dp.query(N'.'), N'') + FROM #dd AS dd + CROSS APPLY dd.deadlock_xml.nodes('//deadlock/process-list/process') AS ca(dp) + WHERE (ca.dp.exist('@currentdb[. = sql:variable("@DatabaseId")]') = 1 OR @DatabaseName IS NULL) + AND (ca.dp.exist('@clientapp[. = sql:variable("@AppName")]') = 1 OR @AppName IS NULL) + AND (ca.dp.exist('@hostname[. = sql:variable("@HostName")]') = 1 OR @HostName IS NULL) + AND (ca.dp.exist('@loginname[. = sql:variable("@LoginName")]') = 1 OR @LoginName IS NULL) + ) AS q + LEFT JOIN #t AS t + ON 1 = 1 + OPTION(RECOMPILE); + + + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + + /*Parse execution stack xml*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Parse execution stack xml %s', 0, 1, @d) WITH NOWAIT; + + SELECT DISTINCT + dp.id, + dp.event_date, + proc_name = ca.dp.value('@procname', 'nvarchar(1024)'), + sql_handle = ca.dp.value('@sqlhandle', 'nvarchar(131)') + INTO #deadlock_stack + FROM #deadlock_process AS dp + CROSS APPLY dp.process_xml.nodes('//executionStack/frame') AS ca(dp) + WHERE (ca.dp.exist('@procname[. = sql:variable("@StoredProcName")]') = 1 OR @StoredProcName IS NULL) + OPTION(RECOMPILE); + + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + + /*Grab the full resource list*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + + RAISERROR('Grab the full resource list %s', 0, 1, @d) WITH NOWAIT; + + SELECT + event_date = + DATEADD + ( + MINUTE, + DATEDIFF + ( + MINUTE, + GETUTCDATE(), + SYSDATETIME() + ), + dr.event_date + ), + dr.victim_id, + dr.resource_xml + INTO + #deadlock_resource + FROM + ( + SELECT + event_date = dd.deadlock_xml.value('(event/@timestamp)[1]', 'datetime2'), + victim_id = dd.deadlock_xml.value('(//deadlock/victim-list/victimProcess/@id)[1]', 'nvarchar(256)'), + resource_xml = ISNULL(ca.dp.query(N'.'), N'') + FROM #deadlock_data AS dd + CROSS APPLY dd.deadlock_xml.nodes('//deadlock/resource-list') AS ca(dp) + ) AS dr + OPTION(RECOMPILE); + + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + + /*Parse object locks*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Parse object locks %s', 0, 1, @d) WITH NOWAIT; + + SELECT DISTINCT + ca.event_date, + ca.database_id, + database_name = + ISNULL + ( + DB_NAME(ca.database_id), + N'UNKNOWN' + ), + object_name = + REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE( + REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE( + REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE( + ca.object_name COLLATE Latin1_General_BIN2, + NCHAR(31), N'?'), NCHAR(30), N'?'), NCHAR(29), N'?'), NCHAR(28), N'?'), NCHAR(27), N'?'), + NCHAR(26), N'?'), NCHAR(25), N'?'), NCHAR(24), N'?'), NCHAR(23), N'?'), NCHAR(22), N'?'), + NCHAR(21), N'?'), NCHAR(20), N'?'), NCHAR(19), N'?'), NCHAR(18), N'?'), NCHAR(17), N'?'), + NCHAR(16), N'?'), NCHAR(15), N'?'), NCHAR(14), N'?'), NCHAR(12), N'?'), NCHAR(11), N'?'), + NCHAR(8), N'?'), NCHAR(7), N'?'), NCHAR(6), N'?'), NCHAR(5), N'?'), NCHAR(4), N'?'), + NCHAR(3), N'?'), NCHAR(2), N'?'), NCHAR(1), N'?'), NCHAR(0), N'?'), + ca.lock_mode, + ca.index_name, + ca.associatedObjectId, + waiter_id = w.l.value('@id', 'nvarchar(256)'), + waiter_mode = w.l.value('@mode', 'nvarchar(256)'), + owner_id = o.l.value('@id', 'nvarchar(256)'), + owner_mode = o.l.value('@mode', 'nvarchar(256)'), + lock_type = N'OBJECT' + INTO #deadlock_owner_waiter + FROM + ( + SELECT + dr.event_date, + database_id = ca.dr.value('@dbid', 'bigint'), + object_name = ca.dr.value('@objectname', 'nvarchar(1024)'), + lock_mode = ca.dr.value('@mode', 'nvarchar(256)'), + index_name = ca.dr.value('@indexname', 'nvarchar(256)'), + associatedObjectId = ca.dr.value('@associatedObjectId', 'bigint'), + dr = ca.dr.query('.') + FROM #deadlock_resource AS dr + CROSS APPLY dr.resource_xml.nodes('//resource-list/objectlock') AS ca(dr) + WHERE (ca.dr.exist('@objectname[. = sql:variable("@ObjectName")]') = 1 OR @ObjectName IS NULL) + ) AS ca + CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l) + CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l) + OPTION(RECOMPILE); + + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + + /*Parse page locks*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Parse page locks %s', 0, 1, @d) WITH NOWAIT; + + INSERT + #deadlock_owner_waiter WITH(TABLOCKX) + SELECT DISTINCT + ca.event_date, + ca.database_id, + database_name = + ISNULL + ( + DB_NAME(ca.database_id), + N'UNKNOWN' + ), + ca.object_name, + ca.lock_mode, + ca.index_name, + ca.associatedObjectId, + waiter_id = w.l.value('@id', 'nvarchar(256)'), + waiter_mode = w.l.value('@mode', 'nvarchar(256)'), + owner_id = o.l.value('@id', 'nvarchar(256)'), + owner_mode = o.l.value('@mode', 'nvarchar(256)'), + lock_type = N'PAGE' + FROM + ( + SELECT + dr.event_date, + database_id = ca.dr.value('@dbid', 'bigint'), + object_name = ca.dr.value('@objectname', 'nvarchar(1024)'), + lock_mode = ca.dr.value('@mode', 'nvarchar(256)'), + index_name = ca.dr.value('@indexname', 'nvarchar(256)'), + associatedObjectId = ca.dr.value('@associatedObjectId', 'bigint'), + dr = ca.dr.query('.') + FROM #deadlock_resource AS dr + CROSS APPLY dr.resource_xml.nodes('//resource-list/pagelock') AS ca(dr) + WHERE (ca.dr.exist('@objectname[. = sql:variable("@ObjectName")]') = 1 OR @ObjectName IS NULL) + ) AS ca + CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l) + CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l) + OPTION(RECOMPILE); + + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + + /*Parse key locks*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Parse key locks %s', 0, 1, @d) WITH NOWAIT; + + INSERT + #deadlock_owner_waiter WITH(TABLOCKX) + SELECT DISTINCT + ca.event_date, + ca.database_id, + database_name = + ISNULL + ( + DB_NAME(ca.database_id), + N'UNKNOWN' + ), + ca.object_name, + ca.lock_mode, + ca.index_name, + ca.associatedObjectId, + waiter_id = w.l.value('@id', 'nvarchar(256)'), + waiter_mode = w.l.value('@mode', 'nvarchar(256)'), + owner_id = o.l.value('@id', 'nvarchar(256)'), + owner_mode = o.l.value('@mode', 'nvarchar(256)'), + lock_type = N'KEY' + FROM + ( + SELECT + dr.event_date, + database_id = ca.dr.value('@dbid', 'bigint'), + object_name = ca.dr.value('@objectname', 'nvarchar(1024)'), + lock_mode = ca.dr.value('@mode', 'nvarchar(256)'), + index_name = ca.dr.value('@indexname', 'nvarchar(256)'), + associatedObjectId = ca.dr.value('@associatedObjectId', 'bigint'), + dr = ca.dr.query('.') + FROM #deadlock_resource AS dr + CROSS APPLY dr.resource_xml.nodes('//resource-list/keylock') AS ca(dr) + WHERE (ca.dr.exist('@objectname[. = sql:variable("@ObjectName")]') = 1 OR @ObjectName IS NULL) + ) AS ca + CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l) + CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l) + OPTION(RECOMPILE); + + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + + /*Parse RID locks*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Parse RID locks %s', 0, 1, @d) WITH NOWAIT; + + INSERT + #deadlock_owner_waiter WITH(TABLOCKX) + SELECT DISTINCT + ca.event_date, + ca.database_id, + database_name = + ISNULL + ( + DB_NAME(ca.database_id), + N'UNKNOWN' + ), + ca.object_name, + ca.lock_mode, + ca.index_name, + ca.associatedObjectId, + waiter_id = w.l.value('@id', 'nvarchar(256)'), + waiter_mode = w.l.value('@mode', 'nvarchar(256)'), + owner_id = o.l.value('@id', 'nvarchar(256)'), + owner_mode = o.l.value('@mode', 'nvarchar(256)'), + lock_type = N'RID' + FROM + ( + SELECT + dr.event_date, + database_id = ca.dr.value('@dbid', 'bigint'), + object_name = ca.dr.value('@objectname', 'nvarchar(1024)'), + lock_mode = ca.dr.value('@mode', 'nvarchar(256)'), + index_name = ca.dr.value('@indexname', 'nvarchar(256)'), + associatedObjectId = ca.dr.value('@associatedObjectId', 'bigint'), + dr = ca.dr.query('.') + FROM #deadlock_resource AS dr + CROSS APPLY dr.resource_xml.nodes('//resource-list/ridlock') AS ca(dr) + WHERE (ca.dr.exist('@objectname[. = sql:variable("@ObjectName")]') = 1 OR @ObjectName IS NULL) + ) AS ca + CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l) + CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l) + OPTION(RECOMPILE); + + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + + /*Parse row group locks*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Parse row group locks %s', 0, 1, @d) WITH NOWAIT; + + INSERT + #deadlock_owner_waiter WITH(TABLOCKX) + SELECT DISTINCT + ca.event_date, + ca.database_id, + database_name = + ISNULL + ( + DB_NAME(ca.database_id), + N'UNKNOWN' + ), + ca.object_name, + ca.lock_mode, + ca.index_name, + ca.associatedObjectId, + waiter_id = w.l.value('@id', 'nvarchar(256)'), + waiter_mode = w.l.value('@mode', 'nvarchar(256)'), + owner_id = o.l.value('@id', 'nvarchar(256)'), + owner_mode = o.l.value('@mode', 'nvarchar(256)'), + lock_type = N'ROWGROUP' + FROM + ( + SELECT + dr.event_date, + database_id = ca.dr.value('@dbid', 'bigint'), + object_name = ca.dr.value('@objectname', 'nvarchar(1024)'), + lock_mode = ca.dr.value('@mode', 'nvarchar(256)'), + index_name = ca.dr.value('@indexname', 'nvarchar(256)'), + associatedObjectId = ca.dr.value('@associatedObjectId', 'bigint'), + dr = ca.dr.query('.') + FROM #deadlock_resource AS dr + CROSS APPLY dr.resource_xml.nodes('//resource-list/rowgrouplock') AS ca(dr) + WHERE (ca.dr.exist('@objectname[. = sql:variable("@ObjectName")]') = 1 OR @ObjectName IS NULL) + ) AS ca + CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l) + CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l) + OPTION(RECOMPILE); + + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Fixing heaps in #deadlock_owner_waiter %s', 0, 1, @d) WITH NOWAIT; + + UPDATE + d + SET + d.index_name = + d.object_name + N'.HEAP' + FROM #deadlock_owner_waiter AS d + WHERE d.lock_type IN + ( + N'HEAP', + N'RID' + ) + OPTION(RECOMPILE); + + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + + /*Parse parallel deadlocks*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Parse parallel deadlocks %s', 0, 1, @d) WITH NOWAIT; + + SELECT DISTINCT + ca.id, + ca.event_date, + ca.wait_type, + ca.node_id, + ca.waiter_type, + ca.owner_activity, + ca.waiter_activity, + ca.merging, + ca.spilling, + ca.waiting_to_close, + waiter_id = w.l.value('@id', 'nvarchar(256)'), + owner_id = o.l.value('@id', 'nvarchar(256)') + INTO #deadlock_resource_parallel + FROM + ( + SELECT + dr.event_date, + id = ca.dr.value('@id', 'nvarchar(256)'), + wait_type = ca.dr.value('@WaitType', 'nvarchar(256)'), + node_id = ca.dr.value('@nodeId', 'bigint'), + /* These columns are in 2017 CU5+ ONLY */ + waiter_type = ca.dr.value('@waiterType', 'nvarchar(256)'), + owner_activity = ca.dr.value('@ownerActivity', 'nvarchar(256)'), + waiter_activity = ca.dr.value('@waiterActivity', 'nvarchar(256)'), + merging = ca.dr.value('@merging', 'nvarchar(256)'), + spilling = ca.dr.value('@spilling', 'nvarchar(256)'), + waiting_to_close = ca.dr.value('@waitingToClose', 'nvarchar(256)'), + /* These columns are in 2017 CU5+ ONLY */ + dr = ca.dr.query('.') + FROM #deadlock_resource AS dr + CROSS APPLY dr.resource_xml.nodes('//resource-list/exchangeEvent') AS ca(dr) + ) AS ca + CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l) + CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l) + OPTION(RECOMPILE); + + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + + /*Get rid of parallel noise*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Get rid of parallel noise %s', 0, 1, @d) WITH NOWAIT; + + WITH + c AS + ( + SELECT + *, + rn = + ROW_NUMBER() OVER + ( + PARTITION BY + drp.owner_id, + drp.waiter_id + ORDER BY + drp.event_date + ) + FROM #deadlock_resource_parallel AS drp + ) + DELETE + FROM c + WHERE c.rn > 1 + OPTION(RECOMPILE); + + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + + /*Get rid of nonsense*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Get rid of nonsense %s', 0, 1, @d) WITH NOWAIT; + + DELETE dow + FROM #deadlock_owner_waiter AS dow + WHERE dow.owner_id = dow.waiter_id + OPTION(RECOMPILE); + + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + + /*Add some nonsense*/ + ALTER TABLE + #deadlock_process + ADD + waiter_mode nvarchar(256), + owner_mode nvarchar(256), + is_victim AS + CONVERT + ( + bit, + CASE + WHEN id = victim_id + THEN 1 + ELSE 0 + END + ) PERSISTED; + + /*Update some nonsense*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Update some nonsense part 1 %s', 0, 1, @d) WITH NOWAIT; + + UPDATE + dp + SET + dp.owner_mode = dow.owner_mode + FROM #deadlock_process AS dp + JOIN #deadlock_owner_waiter AS dow + ON dp.id = dow.owner_id + AND dp.event_date = dow.event_date + WHERE dp.is_victim = 0 + OPTION(RECOMPILE); + + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Update some nonsense part 2 %s', 0, 1, @d) WITH NOWAIT; + + UPDATE + dp + SET + dp.waiter_mode = dow.waiter_mode + FROM #deadlock_process AS dp + JOIN #deadlock_owner_waiter AS dow + ON dp.victim_id = dow.waiter_id + AND dp.event_date = dow.event_date + WHERE dp.is_victim = 1 + OPTION(RECOMPILE); + + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + + /*Get Agent Job and Step names*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Get Agent Job and Step names %s', 0, 1, @d) WITH NOWAIT; + + SELECT + x.event_date, + x.victim_id, + x.id, + x.database_id, + x.client_app, + x.job_id, + x.step_id, + job_id_guid = + CONVERT + ( + uniqueidentifier, + TRY_CAST + ( + N'' + AS xml + ).value('xs:hexBinary(substring(sql:column("x.job_id"), 0))', 'binary(16)') + ) + INTO #agent_job + FROM + ( + SELECT + dp.event_date, + dp.victim_id, + dp.id, + dp.database_id, + dp.client_app, + job_id = + SUBSTRING + ( + dp.client_app, + CHARINDEX(N'0x', dp.client_app) + LEN(N'0x'), + 32 + ), + step_id = + SUBSTRING + ( + dp.client_app, + CHARINDEX(N': Step ', dp.client_app) + LEN(N': Step '), + CHARINDEX(N')', dp.client_app, CHARINDEX(N': Step ', dp.client_app)) - + (CHARINDEX(N': Step ', dp.client_app) + LEN(N': Step ')) + ) + FROM #deadlock_process AS dp + WHERE dp.client_app LIKE N'SQLAgent - %' + AND dp.client_app <> N'SQLAgent - Initial Boot Probe' + ) AS x + OPTION(RECOMPILE); + + ALTER TABLE + #agent_job + ADD + job_name nvarchar(256), + step_name nvarchar(256); + + IF + ( + @Azure = 0 + AND @RDS = 0 + ) + BEGIN + SET @StringToExecute = + N' + UPDATE + aj + SET + aj.job_name = j.name, + aj.step_name = s.step_name + FROM msdb.dbo.sysjobs AS j + JOIN msdb.dbo.sysjobsteps AS s + ON j.job_id = s.job_id + JOIN #agent_job AS aj + ON aj.job_id_guid = j.job_id + AND aj.step_id = s.step_id + OPTION(RECOMPILE); + '; + + IF @Debug = 1 BEGIN PRINT @StringToExecute; END; + EXEC sys.sp_executesql + @StringToExecute; + + END; + + UPDATE + dp + SET + dp.client_app = + CASE + WHEN dp.client_app LIKE N'SQLAgent - %' + THEN N'SQLAgent - Job: ' + + aj.job_name + + N' Step: ' + + aj.step_name + ELSE dp.client_app + END + FROM #deadlock_process AS dp + JOIN #agent_job AS aj + ON dp.event_date = aj.event_date + AND dp.victim_id = aj.victim_id + AND dp.id = aj.id + OPTION(RECOMPILE); + + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + + /*Get each and every table of all databases*/ + IF @Azure = 0 + BEGIN + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Inserting to @sysAssObjId %s', 0, 1, @d) WITH NOWAIT; + + INSERT INTO + @sysAssObjId + ( + database_id, + partition_id, + schema_name, + table_name + ) + EXECUTE sys.sp_MSforeachdb + N' + SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + + USE [?]; + + IF EXISTS + ( + SELECT + 1/0 + FROM #deadlock_process AS dp + WHERE dp.database_id = DB_ID() + ) + BEGIN + SELECT + database_id = + DB_ID(), + p.partition_id, + schema_name = + s.name, + table_name = + t.name + FROM sys.partitions p + JOIN sys.tables t + ON t.object_id = p.object_id + JOIN sys.schemas s + ON s.schema_id = t.schema_id + WHERE s.name IS NOT NULL + AND t.name IS NOT NULL + OPTION(RECOMPILE); + END; + '; + + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + END; + + IF @Azure = 1 + BEGIN + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Inserting to @sysAssObjId at %s', 0, 1, @d) WITH NOWAIT; + + INSERT INTO + @sysAssObjId + ( + database_id, + partition_id, + schema_name, + table_name + ) + SELECT + database_id = + DB_ID(), + p.partition_id, + schema_name = + s.name, + table_name = + t.name + FROM sys.partitions p + JOIN sys.tables t + ON t.object_id = p.object_id + JOIN sys.schemas s + ON s.schema_id = t.schema_id + WHERE s.name IS NOT NULL + AND t.name IS NOT NULL + OPTION(RECOMPILE); + + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + END; + + /*Begin checks based on parsed values*/ + + /*Check 1 is deadlocks by database*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Check 1 database deadlocks %s', 0, 1, @d) WITH NOWAIT; + + INSERT + #deadlock_findings WITH(TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding + ) + SELECT + check_id = 1, + dp.database_name, + object_name = N'-', + finding_group = N'Total Database Deadlocks', + finding = + N'This database had ' + + CONVERT + ( + nvarchar(20), + COUNT_BIG(DISTINCT dp.event_date) + ) + + N' deadlocks.' + FROM #deadlock_process AS dp + WHERE 1 = 1 + AND (dp.database_name = @DatabaseName OR @DatabaseName IS NULL) + AND (dp.event_date >= @StartDate OR @StartDate IS NULL) + AND (dp.event_date < @EndDate OR @EndDate IS NULL) + AND (dp.client_app = @AppName OR @AppName IS NULL) + AND (dp.host_name = @HostName OR @HostName IS NULL) + AND (dp.login_name = @LoginName OR @LoginName IS NULL) + GROUP BY dp.database_name + OPTION(RECOMPILE); + + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + + /*Check 2 is deadlocks with selects*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Check 2 select deadlocks %s', 0, 1, @d) WITH NOWAIT; + + INSERT + #deadlock_findings WITH(TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding + ) + SELECT + check_id = 2, + dow.database_name, + object_name = + N'You Might Need RCSI', + finding_group = N'Total Deadlocks Involving Selects', + finding = + N'There have been ' + + CONVERT + ( + nvarchar(20), + COUNT_BIG(DISTINCT dow.event_date) + ) + + N' deadlock(s) between read queries and modification queries.' + FROM #deadlock_owner_waiter AS dow + WHERE 1 = 1 + AND dow.lock_mode IN + ( + N'S', + N'IS' + ) + AND (dow.database_id = @DatabaseName OR @DatabaseName IS NULL) + AND (dow.event_date >= @StartDate OR @StartDate IS NULL) + AND (dow.event_date < @EndDate OR @EndDate IS NULL) + AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) + GROUP BY + dow.database_name + OPTION(RECOMPILE); + + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + + /*Check 3 is deadlocks by object*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Check 3 object deadlocks %s', 0, 1, @d) WITH NOWAIT; + + INSERT + #deadlock_findings WITH(TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding + ) + SELECT + check_id = 3, + dow.database_name, + object_name = + ISNULL + ( + dow.object_name, + N'UNKNOWN' + ), + finding_group = N'Total Object Deadlocks', + finding = + N'This object was involved in ' + + CONVERT + ( + nvarchar(20), + COUNT_BIG(DISTINCT dow.event_date) + ) + + N' deadlock(s).' + FROM #deadlock_owner_waiter AS dow + WHERE 1 = 1 + AND (dow.database_id = @DatabaseName OR @DatabaseName IS NULL) + AND (dow.event_date >= @StartDate OR @StartDate IS NULL) + AND (dow.event_date < @EndDate OR @EndDate IS NULL) + AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) + GROUP BY + dow.database_name, + dow.object_name + OPTION(RECOMPILE); + + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + + /*Check 3 continuation, number of deadlocks per index*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Check 3 (continued) index deadlocks %s', 0, 1, @d) WITH NOWAIT; + + INSERT + #deadlock_findings WITH(TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding + ) + SELECT + check_id = 3, + dow.database_name, + index_name = dow.index_name, + finding_group = N'Total Index Deadlocks', + finding = + N'This index was involved in ' + + CONVERT + ( + nvarchar(20), + COUNT_BIG(DISTINCT dow.event_date) + ) + + N' deadlock(s).' + FROM #deadlock_owner_waiter AS dow + WHERE 1 = 1 + AND (dow.database_id = @DatabaseName OR @DatabaseName IS NULL) + AND (dow.event_date >= @StartDate OR @StartDate IS NULL) + AND (dow.event_date < @EndDate OR @EndDate IS NULL) + AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) + AND dow.lock_type NOT IN + ( + N'HEAP', + N'RID' + ) + AND dow.index_name IS NOT NULL + GROUP BY + dow.database_name, + dow.index_name + OPTION(RECOMPILE); + + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + + /*Check 3 continuation, number of deadlocks per heap*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Check 3 (continued) heap deadlocks %s', 0, 1, @d) WITH NOWAIT; + + INSERT + #deadlock_findings WITH(TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding + ) + SELECT + check_id = 3, + dow.database_name, + index_name = dow.index_name, + finding_group = N'Total Heap Deadlocks', + finding = + N'This heap was involved in ' + + CONVERT + ( + nvarchar(20), + COUNT_BIG(DISTINCT dow.event_date) + ) + + N' deadlock(s).' + FROM #deadlock_owner_waiter AS dow + WHERE 1 = 1 + AND (dow.database_id = @DatabaseName OR @DatabaseName IS NULL) + AND (dow.event_date >= @StartDate OR @StartDate IS NULL) + AND (dow.event_date < @EndDate OR @EndDate IS NULL) + AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) + AND dow.lock_type IN + ( + N'HEAP', + N'RID' + ) + GROUP BY + dow.database_name, + dow.index_name + OPTION(RECOMPILE); + + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + + /*Check 4 looks for Serializable deadlocks*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Check 4 serializable deadlocks %s', 0, 1, @d) WITH NOWAIT; + + INSERT + #deadlock_findings WITH(TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding + ) + SELECT + check_id = 4, + database_name = + dp.database_name, + object_name = N'-', + finding_group = N'Serializable Deadlocking', + finding = + N'This database has had ' + + CONVERT + ( + nvarchar(20), + COUNT_BIG(DISTINCT dp.event_date) + ) + + N' instances of Serializable deadlocks.' + FROM #deadlock_process AS dp + WHERE dp.isolation_level LIKE N'serializable%' + AND (dp.database_name = @DatabaseName OR @DatabaseName IS NULL) + AND (dp.event_date >= @StartDate OR @StartDate IS NULL) + AND (dp.event_date < @EndDate OR @EndDate IS NULL) + AND (dp.client_app = @AppName OR @AppName IS NULL) + AND (dp.host_name = @HostName OR @HostName IS NULL) + AND (dp.login_name = @LoginName OR @LoginName IS NULL) + GROUP BY dp.database_name + OPTION(RECOMPILE); + + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + + /*Check 5 looks for Repeatable Read deadlocks*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Check 5 repeatable read deadlocks %s', 0, 1, @d) WITH NOWAIT; + + INSERT + #deadlock_findings WITH(TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding + ) + SELECT + check_id = 5, + dp.database_name, + object_name = N'-', + finding_group = N'Repeatable Read Deadlocking', + finding = + N'This database has had ' + + CONVERT + ( + nvarchar(20), + COUNT_BIG(DISTINCT dp.event_date) + ) + + N' instances of Repeatable Read deadlocks.' + FROM #deadlock_process AS dp + WHERE dp.isolation_level LIKE N'repeatable%' + AND (dp.database_name = @DatabaseName OR @DatabaseName IS NULL) + AND (dp.event_date >= @StartDate OR @StartDate IS NULL) + AND (dp.event_date < @EndDate OR @EndDate IS NULL) + AND (dp.client_app = @AppName OR @AppName IS NULL) + AND (dp.host_name = @HostName OR @HostName IS NULL) + AND (dp.login_name = @LoginName OR @LoginName IS NULL) + GROUP BY dp.database_name + OPTION(RECOMPILE); + + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + + /*Check 6 breaks down app, host, and login information*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Check 6 app/host/login deadlocks %s', 0, 1, @d) WITH NOWAIT; + + INSERT + #deadlock_findings WITH(TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding + ) + SELECT + check_id = 6, + database_name = + dp.database_name, + object_name = N'-', + finding_group = N'Login, App, and Host deadlocks', + finding = + N'This database has had ' + + CONVERT + ( + nvarchar(20), + COUNT_BIG(DISTINCT dp.event_date) + ) + + N' instances of deadlocks involving the login ' + + ISNULL + ( + dp.login_name, + N'UNKNOWN' + ) + + N' from the application ' + + ISNULL + ( + dp.client_app, + N'UNKNOWN' + ) + + N' on host ' + + ISNULL + ( + dp.host_name, + N'UNKNOWN' + ) + + N'.' + FROM #deadlock_process AS dp + WHERE 1 = 1 + AND (dp.database_name = @DatabaseName OR @DatabaseName IS NULL) + AND (dp.event_date >= @StartDate OR @StartDate IS NULL) + AND (dp.event_date < @EndDate OR @EndDate IS NULL) + AND (dp.client_app = @AppName OR @AppName IS NULL) + AND (dp.host_name = @HostName OR @HostName IS NULL) + AND (dp.login_name = @LoginName OR @LoginName IS NULL) + GROUP BY + dp.database_name, + dp.login_name, + dp.client_app, + dp.host_name + OPTION(RECOMPILE); + + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + + /*Check 7 breaks down the types of deadlocks (object, page, key, etc.)*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Check 7 types of deadlocks %s', 0, 1, @d) WITH NOWAIT; + + WITH + lock_types AS + ( + SELECT + database_name = + dp.database_name, + dow.object_name, + lock = + CASE + WHEN CHARINDEX(N':', dp.wait_resource) > 0 + THEN SUBSTRING + ( + dp.wait_resource, + 1, + CHARINDEX(N':', dp.wait_resource) - 1 + ) + ELSE dp.wait_resource + END, + lock_count = + CONVERT + ( + nvarchar(20), + COUNT_BIG(DISTINCT dp.event_date) + ) + FROM #deadlock_process AS dp + JOIN #deadlock_owner_waiter AS dow + ON (dp.id = dow.owner_id + OR dp.victim_id = dow.waiter_id) + AND dp.event_date = dow.event_date + WHERE 1 = 1 + AND (dp.database_name = @DatabaseName OR @DatabaseName IS NULL) + AND (dp.event_date >= @StartDate OR @StartDate IS NULL) + AND (dp.event_date < @EndDate OR @EndDate IS NULL) + AND (dp.client_app = @AppName OR @AppName IS NULL) + AND (dp.host_name = @HostName OR @HostName IS NULL) + AND (dp.login_name = @LoginName OR @LoginName IS NULL) + AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) + AND dow.object_name IS NOT NULL + GROUP BY + dp.database_name, + CASE + WHEN CHARINDEX(N':', dp.wait_resource) > 0 + THEN SUBSTRING + ( + dp.wait_resource, + 1, + CHARINDEX(N':', dp.wait_resource) - 1 + ) + ELSE dp.wait_resource + END, + dow.object_name + ) + INSERT + #deadlock_findings WITH(TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding + ) + SELECT + check_id = 7, + lt.database_name, + lt.object_name, + finding_group = N'Types of locks by object', + finding = + N'This object has had ' + + STUFF + ( + ( + SELECT + N', ' + + lt2.lock_count + + N' ' + + lt2.lock + FROM lock_types AS lt2 + WHERE lt2.database_name = lt.database_name + AND lt2.object_name = lt.object_name + FOR XML + PATH(N''), + TYPE + ).value(N'.[1]', N'nvarchar(MAX)'), + 1, + 1, + N'' + ) + N' locks.' + FROM lock_types AS lt + OPTION(RECOMPILE); + + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + + /*Check 8 gives you more info queries for sp_BlitzCache & BlitzQueryStore*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Check 8 more info part 1 BlitzCache %s', 0, 1, @d) WITH NOWAIT; + + WITH + deadlock_stack AS + ( + SELECT DISTINCT + ds.id, + ds.proc_name, + ds.event_date, + database_name = + PARSENAME(ds.proc_name, 3), + schema_name = + PARSENAME(ds.proc_name, 2), + proc_only_name = + PARSENAME(ds.proc_name, 1), + sql_handle_csv = + N'''' + + STUFF + ( + ( + SELECT DISTINCT + N',' + + ds2.sql_handle + FROM #deadlock_stack AS ds2 + WHERE ds2.id = ds.id + AND ds2.event_date = ds.event_date + AND ds2.sql_handle <> 0x + FOR XML + PATH(N''), + TYPE + ).value(N'.[1]', N'nvarchar(MAX)'), + 1, + 1, + N'' + ) + + N'''' + FROM #deadlock_stack AS ds + WHERE ds.sql_handle <> 0x + GROUP BY + PARSENAME(ds.proc_name, 3), + PARSENAME(ds.proc_name, 2), + PARSENAME(ds.proc_name, 1), + ds.id, + ds.proc_name, + ds.event_date + ) + INSERT + #deadlock_findings WITH(TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding + ) + SELECT DISTINCT + check_id = 8, + dow.database_name, + object_name = ds.proc_name, + finding_group = N'More Info - Query', + finding = N'EXEC sp_BlitzCache ' + + CASE + WHEN ds.proc_name = N'adhoc' + THEN N'@OnlySqlHandles = ' + ds.sql_handle_csv + ELSE N'@StoredProcName = ' + QUOTENAME(ds.proc_only_name, N'''') + END + N';' + FROM deadlock_stack AS ds + JOIN #deadlock_owner_waiter AS dow + ON dow.owner_id = ds.id + AND dow.event_date = ds.event_date + WHERE 1 = 1 + AND (dow.database_id = @DatabaseName OR @DatabaseName IS NULL) + AND (dow.event_date >= @StartDate OR @StartDate IS NULL) + AND (dow.event_date < @EndDate OR @EndDate IS NULL) + AND (dow.object_name = @StoredProcName OR @StoredProcName IS NULL) + OPTION(RECOMPILE); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - /*Parse key locks*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Parse key locks %s', 0, 1, @d) WITH NOWAIT; - INSERT #deadlock_owner_waiter WITH(TABLOCKX) - SELECT DISTINCT - ca.event_date, - ca.database_id, - ca.object_name, - ca.lock_mode, - ca.index_name, - ca.associatedObjectId, - w.l.value('@id', 'NVARCHAR(256)') AS waiter_id, - w.l.value('@mode', 'NVARCHAR(256)') AS waiter_mode, - o.l.value('@id', 'NVARCHAR(256)') AS owner_id, - o.l.value('@mode', 'NVARCHAR(256)') AS owner_mode, - N'KEY' AS lock_type - FROM ( - SELECT dr.event_date, - ca.dr.value('@dbid', 'BIGINT') AS database_id, - ca.dr.value('@objectname', 'NVARCHAR(256)') AS object_name, - ca.dr.value('@mode', 'NVARCHAR(256)') AS lock_mode, - ca.dr.value('@indexname', 'NVARCHAR(256)') AS index_name, - ca.dr.value('@associatedObjectId', 'BIGINT') AS associatedObjectId, - ca.dr.query('.') AS dr - FROM #deadlock_resource AS dr - CROSS APPLY dr.resource_xml.nodes('//resource-list/keylock') AS ca(dr) - ) AS ca - CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l) - CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l) - OPTION ( RECOMPILE ); + IF (@ProductVersionMajor >= 13 OR @Azure = 1) + BEGIN + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Check 8 more info part 2 BlitzQueryStore %s', 0, 1, @d) WITH NOWAIT; + WITH + deadlock_stack AS + ( + SELECT DISTINCT + ds.id, + ds.sql_handle, + ds.proc_name, + ds.event_date, + database_name = + PARSENAME(ds.proc_name, 3), + schema_name = + PARSENAME(ds.proc_name, 2), + proc_only_name = + PARSENAME(ds.proc_name, 1) + FROM #deadlock_stack AS ds + ) + INSERT + #deadlock_findings WITH(TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding + ) + SELECT DISTINCT + check_id = 8, + dow.database_name, + object_name = ds.proc_name, + finding_group = N'More Info - Query', + finding = + N'EXEC sp_BlitzQueryStore ' + + N'@DatabaseName = ' + + QUOTENAME(ds.database_name, N'''') + + N', ' + + N'@StoredProcName = ' + + QUOTENAME(ds.proc_only_name, N'''') + + N';' + FROM deadlock_stack AS ds + JOIN #deadlock_owner_waiter AS dow + ON dow.owner_id = ds.id + AND dow.event_date = ds.event_date + WHERE ds.proc_name <> N'adhoc' + AND (dow.database_id = @DatabaseName OR @DatabaseName IS NULL) + AND (dow.event_date >= @StartDate OR @StartDate IS NULL) + AND (dow.event_date < @EndDate OR @EndDate IS NULL) + AND (dow.object_name = @StoredProcName OR @StoredProcName IS NULL) + OPTION(RECOMPILE); + + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + END; - /*Parse RID locks*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Parse RID locks %s', 0, 1, @d) WITH NOWAIT; - INSERT #deadlock_owner_waiter WITH(TABLOCKX) - SELECT DISTINCT - ca.event_date, - ca.database_id, - ca.object_name, - ca.lock_mode, - ca.index_name, - ca.associatedObjectId, - w.l.value('@id', 'NVARCHAR(256)') AS waiter_id, - w.l.value('@mode', 'NVARCHAR(256)') AS waiter_mode, - o.l.value('@id', 'NVARCHAR(256)') AS owner_id, - o.l.value('@mode', 'NVARCHAR(256)') AS owner_mode, - N'RID' AS lock_type - FROM ( - SELECT dr.event_date, - ca.dr.value('@dbid', 'BIGINT') AS database_id, - ca.dr.value('@objectname', 'NVARCHAR(256)') AS object_name, - ca.dr.value('@mode', 'NVARCHAR(256)') AS lock_mode, - ca.dr.value('@indexname', 'NVARCHAR(256)') AS index_name, - ca.dr.value('@associatedObjectId', 'BIGINT') AS associatedObjectId, - ca.dr.query('.') AS dr - FROM #deadlock_resource AS dr - CROSS APPLY dr.resource_xml.nodes('//resource-list/ridlock') AS ca(dr) - ) AS ca - CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l) - CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l) - OPTION ( RECOMPILE ); + /*Check 9 gives you stored procedure deadlock counts*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Check 9 stored procedure deadlocks %s', 0, 1, @d) WITH NOWAIT; + INSERT + #deadlock_findings WITH(TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding + ) + SELECT + check_id = 9, + database_name = + dp.database_name, + object_name = ds.proc_name, + finding_group = N'Stored Procedure Deadlocks', + finding = + N'The stored procedure ' + + PARSENAME(ds.proc_name, 2) + + N'.' + + PARSENAME(ds.proc_name, 1) + + N' has been involved in ' + + CONVERT + ( + nvarchar(10), + COUNT_BIG(DISTINCT ds.id) + ) + + N' deadlocks.' + FROM #deadlock_stack AS ds + JOIN #deadlock_process AS dp + ON dp.id = ds.id + AND ds.event_date = dp.event_date + WHERE ds.proc_name <> N'adhoc' + AND (ds.proc_name = @StoredProcName OR @StoredProcName IS NULL) + AND (dp.database_name = @DatabaseName OR @DatabaseName IS NULL) + AND (dp.event_date >= @StartDate OR @StartDate IS NULL) + AND (dp.event_date < @EndDate OR @EndDate IS NULL) + AND (dp.client_app = @AppName OR @AppName IS NULL) + AND (dp.host_name = @HostName OR @HostName IS NULL) + AND (dp.login_name = @LoginName OR @LoginName IS NULL) + GROUP BY + dp.database_name, + ds.proc_name + OPTION(RECOMPILE); - /*Parse row group locks*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Parse row group locks %s', 0, 1, @d) WITH NOWAIT; - INSERT #deadlock_owner_waiter WITH(TABLOCKX) - SELECT DISTINCT - ca.event_date, - ca.database_id, - ca.object_name, - ca.lock_mode, - ca.index_name, - ca.associatedObjectId, - w.l.value('@id', 'NVARCHAR(256)') AS waiter_id, - w.l.value('@mode', 'NVARCHAR(256)') AS waiter_mode, - o.l.value('@id', 'NVARCHAR(256)') AS owner_id, - o.l.value('@mode', 'NVARCHAR(256)') AS owner_mode, - N'ROWGROUP' AS lock_type - FROM ( - SELECT dr.event_date, - ca.dr.value('@dbid', 'BIGINT') AS database_id, - ca.dr.value('@objectname', 'NVARCHAR(256)') AS object_name, - ca.dr.value('@mode', 'NVARCHAR(256)') AS lock_mode, - ca.dr.value('@indexname', 'NVARCHAR(256)') AS index_name, - ca.dr.value('@associatedObjectId', 'BIGINT') AS associatedObjectId, - ca.dr.query('.') AS dr - FROM #deadlock_resource AS dr - CROSS APPLY dr.resource_xml.nodes('//resource-list/rowgrouplock') AS ca(dr) - ) AS ca - CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l) - CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l) - OPTION ( RECOMPILE ); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - UPDATE d - SET d.index_name = d.object_name - + '.HEAP' - FROM #deadlock_owner_waiter AS d - WHERE lock_type IN (N'HEAP', N'RID') - OPTION(RECOMPILE); + /*Check 10 gives you more info queries for sp_BlitzIndex */ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Check 10 more info, BlitzIndex %s', 0, 1, @d) WITH NOWAIT; - /*Parse parallel deadlocks*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Parse parallel deadlocks %s', 0, 1, @d) WITH NOWAIT; - SELECT DISTINCT - ca.id, - ca.event_date, - ca.wait_type, - ca.node_id, - ca.waiter_type, - ca.owner_activity, - ca.waiter_activity, - ca.merging, - ca.spilling, - ca.waiting_to_close, - w.l.value('@id', 'NVARCHAR(256)') AS waiter_id, - o.l.value('@id', 'NVARCHAR(256)') AS owner_id - INTO #deadlock_resource_parallel - FROM ( - SELECT dr.event_date, - ca.dr.value('@id', 'NVARCHAR(256)') AS id, - ca.dr.value('@WaitType', 'NVARCHAR(256)') AS wait_type, - ca.dr.value('@nodeId', 'BIGINT') AS node_id, - /* These columns are in 2017 CU5 ONLY */ - ca.dr.value('@waiterType', 'NVARCHAR(256)') AS waiter_type, - ca.dr.value('@ownerActivity', 'NVARCHAR(256)') AS owner_activity, - ca.dr.value('@waiterActivity', 'NVARCHAR(256)') AS waiter_activity, - ca.dr.value('@merging', 'NVARCHAR(256)') AS merging, - ca.dr.value('@spilling', 'NVARCHAR(256)') AS spilling, - ca.dr.value('@waitingToClose', 'NVARCHAR(256)') AS waiting_to_close, - /* */ - ca.dr.query('.') AS dr - FROM #deadlock_resource AS dr - CROSS APPLY dr.resource_xml.nodes('//resource-list/exchangeEvent') AS ca(dr) - ) AS ca - CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l) - CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l) - OPTION ( RECOMPILE ); - - - /*Get rid of parallel noise*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Get rid of parallel noise %s', 0, 1, @d) WITH NOWAIT; - WITH c - AS - ( - SELECT *, ROW_NUMBER() OVER ( PARTITION BY drp.owner_id, drp.waiter_id ORDER BY drp.event_date ) AS rn - FROM #deadlock_resource_parallel AS drp - ) - DELETE FROM c - WHERE c.rn > 1 - OPTION ( RECOMPILE ); + WITH + bi AS + ( + SELECT DISTINCT + dow.object_name, + dow.database_name, + schema_name = s.schema_name, + table_name = s.table_name + FROM #deadlock_owner_waiter AS dow + JOIN @sysAssObjId AS s + ON s.database_id = dow.database_id + AND s.partition_id = dow.associatedObjectId + WHERE 1 = 1 + AND (dow.database_id = @DatabaseName OR @DatabaseName IS NULL) + AND (dow.event_date >= @StartDate OR @StartDate IS NULL) + AND (dow.event_date < @EndDate OR @EndDate IS NULL) + AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) + AND dow.object_name IS NOT NULL + ) + INSERT + #deadlock_findings WITH(TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding + ) + SELECT DISTINCT + check_id = 10, + bi.database_name, + bi.object_name, + finding_group = N'More Info - Table', + finding = + N'EXEC sp_BlitzIndex ' + + N'@DatabaseName = ' + + QUOTENAME(bi.database_name, N'''') + + N', @SchemaName = ' + + QUOTENAME(bi.schema_name, N'''') + + N', @TableName = ' + + QUOTENAME(bi.table_name, N'''') + + N';' + FROM bi + OPTION(RECOMPILE); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - /*Get rid of nonsense*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Get rid of nonsense %s', 0, 1, @d) WITH NOWAIT; - DELETE dow - FROM #deadlock_owner_waiter AS dow - WHERE dow.owner_id = dow.waiter_id - OPTION ( RECOMPILE ); + /*Check 11 gets total deadlock wait time per object*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Check 11 deadlock wait time per object %s', 0, 1, @d) WITH NOWAIT; - /*Add some nonsense*/ - ALTER TABLE #deadlock_process - ADD waiter_mode NVARCHAR(256), - owner_mode NVARCHAR(256), - is_victim AS CONVERT(BIT, CASE WHEN id = victim_id THEN 1 ELSE 0 END); + WITH + chopsuey AS + ( - /*Update some nonsense*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Update some nonsense part 1 %s', 0, 1, @d) WITH NOWAIT; - UPDATE dp - SET dp.owner_mode = dow.owner_mode - FROM #deadlock_process AS dp - JOIN #deadlock_owner_waiter AS dow - ON dp.id = dow.owner_id - AND dp.event_date = dow.event_date - WHERE dp.is_victim = 0 - OPTION ( RECOMPILE ); + SELECT + database_name = + dp.database_name, + dow.object_name, + wait_days = + CONVERT + ( + nvarchar(30), + ( + SUM + ( + CONVERT + ( + bigint, + dp.wait_time + ) + ) / 1000 / 86400 + ) + ), + wait_time_hms = + CONVERT + ( + nvarchar(30), + DATEADD + ( + MILLISECOND, + ( + SUM + ( + CONVERT + ( + bigint, + dp.wait_time + ) + ) + ), + 0 + ), + 14 + ) + FROM #deadlock_owner_waiter AS dow + JOIN #deadlock_process AS dp + ON (dp.id = dow.owner_id + OR dp.victim_id = dow.waiter_id) + AND dp.event_date = dow.event_date + WHERE 1 = 1 + AND (dp.database_name = @DatabaseName OR @DatabaseName IS NULL) + AND (dp.event_date >= @StartDate OR @StartDate IS NULL) + AND (dp.event_date < @EndDate OR @EndDate IS NULL) + AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) + AND (dp.client_app = @AppName OR @AppName IS NULL) + AND (dp.host_name = @HostName OR @HostName IS NULL) + AND (dp.login_name = @LoginName OR @LoginName IS NULL) + GROUP BY + dp.database_name, + dow.object_name + ) + INSERT + #deadlock_findings WITH(TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding + ) + SELECT + check_id = 11, + cs.database_name, + cs.object_name, + finding_group = N'Total object deadlock wait time', + finding = + N'This object has had ' + + CONVERT + ( + nvarchar(30), + cs.wait_days + ) + + N' ' + + CONVERT + ( + nvarchar(30), + cs.wait_time_hms, + 14 + ) + + N' [dd hh:mm:ss:ms] of deadlock wait time.' + FROM chopsuey AS cs + WHERE cs.object_name IS NOT NULL + OPTION(RECOMPILE); - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Update some nonsense part 2 %s', 0, 1, @d) WITH NOWAIT; - UPDATE dp - SET dp.waiter_mode = dow.waiter_mode - FROM #deadlock_process AS dp - JOIN #deadlock_owner_waiter AS dow - ON dp.victim_id = dow.waiter_id - AND dp.event_date = dow.event_date - WHERE dp.is_victim = 1 - OPTION ( RECOMPILE ); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - /*Get Agent Job and Step names*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Get Agent Job and Step names %s', 0, 1, @d) WITH NOWAIT; - SELECT *, - CONVERT(UNIQUEIDENTIFIER, - CONVERT(XML, '').value('xs:hexBinary(substring(sql:column("x.job_id"), 0) )', 'BINARY(16)') - ) AS job_id_guid - INTO #agent_job - FROM ( - SELECT dp.event_date, - dp.victim_id, - dp.id, - dp.database_id, - dp.client_app, - SUBSTRING(dp.client_app, - CHARINDEX('0x', dp.client_app) + LEN('0x'), - 32 - ) AS job_id, - SUBSTRING(dp.client_app, - CHARINDEX(': Step ', dp.client_app) + LEN(': Step '), - CHARINDEX(')', dp.client_app, CHARINDEX(': Step ', dp.client_app)) - - (CHARINDEX(': Step ', dp.client_app) - + LEN(': Step ')) - ) AS step_id - FROM #deadlock_process AS dp - WHERE dp.client_app LIKE 'SQLAgent - %' - AND dp.client_app <> 'SQLAgent - Initial Boot Probe' - ) AS x - OPTION ( RECOMPILE ); + /*Check 12 gets total deadlock wait time per database*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Check 12 deadlock wait time per database %s', 0, 1, @d) WITH NOWAIT; + WITH + wait_time AS + ( + SELECT + database_name = + dp.database_name, + total_wait_time_ms = + SUM + ( + CONVERT + ( + bigint, + dp.wait_time + ) + ) + FROM #deadlock_owner_waiter AS dow + JOIN #deadlock_process AS dp + ON (dp.id = dow.owner_id + OR dp.victim_id = dow.waiter_id) + AND dp.event_date = dow.event_date + WHERE 1 = 1 + AND (dp.database_name = @DatabaseName OR @DatabaseName IS NULL) + AND (dp.event_date >= @StartDate OR @StartDate IS NULL) + AND (dp.event_date < @EndDate OR @EndDate IS NULL) + AND (dp.client_app = @AppName OR @AppName IS NULL) + AND (dp.host_name = @HostName OR @HostName IS NULL) + AND (dp.login_name = @LoginName OR @LoginName IS NULL) + GROUP BY + dp.database_name + ) + INSERT + #deadlock_findings WITH(TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding + ) + SELECT + check_id = 12, + wt.database_name, + object_name = N'-', + finding_group = N'Total database deadlock wait time', + N'This database has had ' + + CONVERT + ( + nvarchar(30), + ( + SUM + ( + CONVERT + ( + bigint, + wt.total_wait_time_ms + ) + ) / 1000 / 86400 + ) + ) + + N' ' + + CONVERT + ( + nvarchar(30), + DATEADD + ( + MILLISECOND, + ( + SUM + ( + CONVERT + ( + bigint, + wt.total_wait_time_ms + ) + ) + ), + 0 + ), + 14 + ) + + N' [dd hh:mm:ss:ms] of deadlock wait time.' + FROM wait_time AS wt + GROUP BY + wt.database_name + OPTION(RECOMPILE); - ALTER TABLE #agent_job ADD job_name NVARCHAR(256), - step_name NVARCHAR(256); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - IF SERVERPROPERTY('EngineEdition') NOT IN (5, 6) /* Azure SQL DB doesn't support querying jobs */ - AND NOT (LEFT(CAST(SERVERPROPERTY('ComputerNamePhysicalNetBIOS') AS VARCHAR(8000)), 8) = 'EC2AMAZ-' /* Neither does Amazon RDS Express Edition */ - AND LEFT(CAST(SERVERPROPERTY('MachineName') AS VARCHAR(8000)), 8) = 'EC2AMAZ-' - AND db_id('rdsadmin') IS NOT NULL - AND EXISTS(SELECT * FROM master.sys.all_objects WHERE name IN ('rds_startup_tasks', 'rds_help_revlogin', 'rds_hexadecimal', 'rds_failover_tracking', 'rds_database_tracking', 'rds_track_change')) - ) - BEGIN - SET @StringToExecute = N'UPDATE aj - SET aj.job_name = j.name, - aj.step_name = s.step_name - FROM msdb.dbo.sysjobs AS j - JOIN msdb.dbo.sysjobsteps AS s - ON j.job_id = s.job_id - JOIN #agent_job AS aj - ON aj.job_id_guid = j.job_id - AND aj.step_id = s.step_id - OPTION ( RECOMPILE );'; - EXEC(@StringToExecute); - END + /*Check 13 gets total deadlock wait time for SQL Agent*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Check 13 deadlock count for SQL Agent %s', 0, 1, @d) WITH NOWAIT; - UPDATE dp - SET dp.client_app = - CASE WHEN dp.client_app LIKE N'SQLAgent - %' - THEN N'SQLAgent - Job: ' - + aj.job_name - + N' Step: ' - + aj.step_name - ELSE dp.client_app - END - FROM #deadlock_process AS dp - JOIN #agent_job AS aj - ON dp.event_date = aj.event_date - AND dp.victim_id = aj.victim_id - AND dp.id = aj.id - OPTION ( RECOMPILE ); + INSERT + #deadlock_findings WITH(TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding + ) + SELECT + check_id = 13, + database_name = + DB_NAME(aj.database_id), + object_name = + N'SQLAgent - Job: ' + + aj.job_name + + N' Step: ' + + aj.step_name, + finding_group = N'Agent Job Deadlocks', + finding = + N'There have been ' + + RTRIM(COUNT_BIG(DISTINCT aj.event_date)) + + N' deadlocks from this Agent Job and Step.' + FROM #agent_job AS aj + GROUP BY + DB_NAME(aj.database_id), + aj.job_name, + aj.step_name + OPTION(RECOMPILE); - /*Get each and every table of all databases*/ - DECLARE @sysAssObjId AS TABLE (database_id bigint, partition_id bigint, schema_name varchar(255), table_name varchar(255)); - INSERT into @sysAssObjId EXECUTE sp_MSforeachdb - N'USE [?]; - SELECT DB_ID() as database_id, p.partition_id, s.name as schema_name, t.name as table_name - FROM sys.partitions p - LEFT JOIN sys.tables t ON t.object_id = p.object_id - LEFT JOIN sys.schemas s ON s.schema_id = t.schema_id - WHERE s.name is not NULL AND t.name is not NULL'; - - - /*Begin checks based on parsed values*/ - - /*Check 1 is deadlocks by database*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Check 1 %s', 0, 1, @d) WITH NOWAIT; - INSERT #deadlock_findings WITH (TABLOCKX) - ( check_id, database_name, object_name, finding_group, finding ) - SELECT 1 AS check_id, - DB_NAME(dp.database_id) AS database_name, - '-' AS object_name, - 'Total database locks' AS finding_group, - 'This database had ' - + CONVERT(NVARCHAR(20), COUNT_BIG(DISTINCT dp.event_date)) - + ' deadlocks.' - FROM #deadlock_process AS dp - WHERE 1 = 1 - AND (DB_NAME(dp.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (dp.event_date >= @StartDate OR @StartDate IS NULL) - AND (dp.event_date < @EndDate OR @EndDate IS NULL) - AND (dp.client_app = @AppName OR @AppName IS NULL) - AND (dp.host_name = @HostName OR @HostName IS NULL) - AND (dp.login_name = @LoginName OR @LoginName IS NULL) - GROUP BY DB_NAME(dp.database_id) - OPTION ( RECOMPILE ); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - /*Check 2 is deadlocks by object*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Check 2 objects %s', 0, 1, @d) WITH NOWAIT; - INSERT #deadlock_findings WITH (TABLOCKX) - ( check_id, database_name, object_name, finding_group, finding ) - SELECT 2 AS check_id, - ISNULL(DB_NAME(dow.database_id), 'UNKNOWN') AS database_name, - ISNULL(dow.object_name, 'UNKNOWN') AS object_name, - 'Total object deadlocks' AS finding_group, - 'This object was involved in ' - + CONVERT(NVARCHAR(20), COUNT_BIG(DISTINCT dow.event_date)) - + ' deadlock(s).' - FROM #deadlock_owner_waiter AS dow - WHERE 1 = 1 - AND (DB_NAME(dow.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (dow.event_date >= @StartDate OR @StartDate IS NULL) - AND (dow.event_date < @EndDate OR @EndDate IS NULL) - AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) - GROUP BY DB_NAME(dow.database_id), dow.object_name - OPTION ( RECOMPILE ); + /*Check 14 is total parallel deadlocks*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Check 14 parallel deadlocks %s', 0, 1, @d) WITH NOWAIT; - /*Check 2 continuation, number of locks per index*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Check 2 indexes %s', 0, 1, @d) WITH NOWAIT; - INSERT #deadlock_findings WITH (TABLOCKX) - ( check_id, database_name, object_name, finding_group, finding ) - SELECT 2 AS check_id, - ISNULL(DB_NAME(dow.database_id), 'UNKNOWN') AS database_name, - dow.index_name AS index_name, - 'Total index deadlocks' AS finding_group, - 'This index was involved in ' - + CONVERT(NVARCHAR(20), COUNT_BIG(DISTINCT dow.event_date)) - + ' deadlock(s).' - FROM #deadlock_owner_waiter AS dow - WHERE 1 = 1 - AND (DB_NAME(dow.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (dow.event_date >= @StartDate OR @StartDate IS NULL) - AND (dow.event_date < @EndDate OR @EndDate IS NULL) - AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) - AND dow.lock_type NOT IN (N'HEAP', N'RID') - AND dow.index_name is not null - GROUP BY DB_NAME(dow.database_id), dow.index_name - OPTION ( RECOMPILE ); + INSERT + #deadlock_findings WITH(TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding + ) + SELECT + check_id = 14, + database_name = N'-', + object_name = N'-', + finding_group = N'Total parallel deadlocks', + finding = + N'There have been ' + + CONVERT + ( + nvarchar(20), + COUNT_BIG(DISTINCT drp.event_date) + ) + + N' parallel deadlocks.' + FROM #deadlock_resource_parallel AS drp + HAVING COUNT_BIG(DISTINCT drp.event_date) > 0 + OPTION(RECOMPILE); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - /*Check 2 continuation, number of locks per heap*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Check 2 heaps %s', 0, 1, @d) WITH NOWAIT; - INSERT #deadlock_findings WITH (TABLOCKX) - ( check_id, database_name, object_name, finding_group, finding ) - SELECT 2 AS check_id, - ISNULL(DB_NAME(dow.database_id), 'UNKNOWN') AS database_name, - dow.index_name AS index_name, - 'Total heap deadlocks' AS finding_group, - 'This heap was involved in ' - + CONVERT(NVARCHAR(20), COUNT_BIG(DISTINCT dow.event_date)) - + ' deadlock(s).' - FROM #deadlock_owner_waiter AS dow - WHERE 1 = 1 - AND (DB_NAME(dow.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (dow.event_date >= @StartDate OR @StartDate IS NULL) - AND (dow.event_date < @EndDate OR @EndDate IS NULL) - AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) - AND dow.lock_type IN (N'HEAP', N'RID') - GROUP BY DB_NAME(dow.database_id), dow.index_name - OPTION ( RECOMPILE ); - + /*Check 15 is total deadlocks involving sleeping sessions*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Check 15 sleeping deadlocks %s', 0, 1, @d) WITH NOWAIT; - /*Check 3 looks for Serializable locking*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Check 3 %s', 0, 1, @d) WITH NOWAIT; - INSERT #deadlock_findings WITH (TABLOCKX) - ( check_id, database_name, object_name, finding_group, finding ) - SELECT 3 AS check_id, - DB_NAME(dp.database_id) AS database_name, - '-' AS object_name, - 'Serializable locking' AS finding_group, - 'This database has had ' + - CONVERT(NVARCHAR(20), COUNT_BIG(*)) + - ' instances of serializable deadlocks.' - AS finding - FROM #deadlock_process AS dp - WHERE dp.isolation_level LIKE 'serializable%' - AND (DB_NAME(dp.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (dp.event_date >= @StartDate OR @StartDate IS NULL) - AND (dp.event_date < @EndDate OR @EndDate IS NULL) - AND (dp.client_app = @AppName OR @AppName IS NULL) - AND (dp.host_name = @HostName OR @HostName IS NULL) - AND (dp.login_name = @LoginName OR @LoginName IS NULL) - GROUP BY DB_NAME(dp.database_id) - OPTION ( RECOMPILE ); + INSERT + #deadlock_findings WITH(TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding + ) + SELECT + check_id = 15, + database_name = N'-', + object_name = N'-', + finding_group = N'Total deadlocks involving sleeping sessions', + finding = + N'There have been ' + + CONVERT + ( + nvarchar(20), + COUNT_BIG(DISTINCT dp.event_date) + ) + + N' sleepy deadlocks.' + FROM #deadlock_process AS dp + WHERE dp.status = N'sleeping' + HAVING COUNT_BIG(DISTINCT dp.event_date) > 0 + OPTION(RECOMPILE); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - /*Check 4 looks for Repeatable Read locking*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Check 4 %s', 0, 1, @d) WITH NOWAIT; - INSERT #deadlock_findings WITH (TABLOCKX) - ( check_id, database_name, object_name, finding_group, finding ) - SELECT 4 AS check_id, - DB_NAME(dp.database_id) AS database_name, - '-' AS object_name, - 'Repeatable Read locking' AS finding_group, - 'This database has had ' + - CONVERT(NVARCHAR(20), COUNT_BIG(*)) + - ' instances of repeatable read deadlocks.' - AS finding - FROM #deadlock_process AS dp - WHERE dp.isolation_level LIKE 'repeatable read%' - AND (DB_NAME(dp.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (dp.event_date >= @StartDate OR @StartDate IS NULL) - AND (dp.event_date < @EndDate OR @EndDate IS NULL) - AND (dp.client_app = @AppName OR @AppName IS NULL) - AND (dp.host_name = @HostName OR @HostName IS NULL) - AND (dp.login_name = @LoginName OR @LoginName IS NULL) - GROUP BY DB_NAME(dp.database_id) - OPTION ( RECOMPILE ); + /*Check 16 is total deadlocks involving implicit transactions*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Check 16 implicit transaction deadlocks %s', 0, 1, @d) WITH NOWAIT; + INSERT + #deadlock_findings WITH(TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding + ) + SELECT + check_id = 14, + database_name = N'-', + object_name = N'-', + finding_group = N'Total implicit transaction deadlocks', + finding = + N'There have been ' + + CONVERT + ( + nvarchar(20), + COUNT_BIG(DISTINCT dp.event_date) + ) + + N' implicit transaction deadlocks.' + FROM #deadlock_process AS dp + WHERE dp.transaction_name = N'implicit_transaction' + HAVING COUNT_BIG(DISTINCT dp.event_date) > 0 + OPTION(RECOMPILE); - /*Check 5 breaks down app, host, and login information*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Check 5 %s', 0, 1, @d) WITH NOWAIT; - INSERT #deadlock_findings WITH (TABLOCKX) - ( check_id, database_name, object_name, finding_group, finding ) - SELECT 5 AS check_id, - DB_NAME(dp.database_id) AS database_name, - '-' AS object_name, - 'Login, App, and Host locking' AS finding_group, - 'This database has had ' + - CONVERT(NVARCHAR(20), COUNT_BIG(DISTINCT dp.event_date)) + - ' instances of deadlocks involving the login ' + - ISNULL(dp.login_name, 'UNKNOWN') + - ' from the application ' + - ISNULL(dp.client_app, 'UNKNOWN') + - ' on host ' + - ISNULL(dp.host_name, 'UNKNOWN') - AS finding - FROM #deadlock_process AS dp - WHERE 1 = 1 - AND (DB_NAME(dp.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (dp.event_date >= @StartDate OR @StartDate IS NULL) - AND (dp.event_date < @EndDate OR @EndDate IS NULL) - AND (dp.client_app = @AppName OR @AppName IS NULL) - AND (dp.host_name = @HostName OR @HostName IS NULL) - AND (dp.login_name = @LoginName OR @LoginName IS NULL) - GROUP BY DB_NAME(dp.database_id), dp.login_name, dp.client_app, dp.host_name - OPTION ( RECOMPILE ); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + /*Thank you goodnight*/ + INSERT + #deadlock_findings WITH(TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding + ) + VALUES + ( + -1, + N'sp_BlitzLock version ' + CONVERT(nvarchar(10), @Version), + N'Results for ' + CONVERT(nvarchar(10), @StartDate, 23) + N' through ' + CONVERT(nvarchar(10), @EndDate, 23), + N'http://FirstResponderKit.org/', + N'To get help or add your own contributions to the SQL Server First Responder Kit, join us at http://FirstResponderKit.org.' + ); - /*Check 6 breaks down the types of locks (object, page, key, etc.)*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Check 6 %s', 0, 1, @d) WITH NOWAIT; - WITH lock_types AS ( - SELECT DB_NAME(dp.database_id) AS database_name, - dow.object_name, - SUBSTRING(dp.wait_resource, 1, CHARINDEX(':', dp.wait_resource) -1) AS lock, - CONVERT(NVARCHAR(20), COUNT_BIG(DISTINCT dp.id)) AS lock_count - FROM #deadlock_process AS dp - JOIN #deadlock_owner_waiter AS dow - ON dp.id = dow.owner_id - AND dp.event_date = dow.event_date - WHERE 1 = 1 - AND (DB_NAME(dp.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (dp.event_date >= @StartDate OR @StartDate IS NULL) - AND (dp.event_date < @EndDate OR @EndDate IS NULL) - AND (dp.client_app = @AppName OR @AppName IS NULL) - AND (dp.host_name = @HostName OR @HostName IS NULL) - AND (dp.login_name = @LoginName OR @LoginName IS NULL) - AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) - AND dow.object_name IS NOT NULL - GROUP BY DB_NAME(dp.database_id), SUBSTRING(dp.wait_resource, 1, CHARINDEX(':', dp.wait_resource) - 1), dow.object_name - ) - INSERT #deadlock_findings WITH (TABLOCKX) - ( check_id, database_name, object_name, finding_group, finding ) - SELECT DISTINCT 6 AS check_id, - lt.database_name, - lt.object_name, - 'Types of locks by object' AS finding_group, - 'This object has had ' + - STUFF((SELECT DISTINCT N', ' + lt2.lock_count + ' ' + lt2.lock - FROM lock_types AS lt2 - WHERE lt2.database_name = lt.database_name - AND lt2.object_name = lt.object_name - FOR XML PATH(N''), TYPE).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 1, N'') - + ' locks' - FROM lock_types AS lt - OPTION ( RECOMPILE ); + RAISERROR('Finished rollup at %s', 0, 1, @d) WITH NOWAIT; + /*Results*/ + BEGIN + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Results 1 %s', 0, 1, @d) WITH NOWAIT; - /*Check 7 gives you more info queries for sp_BlitzCache & BlitzQueryStore*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Check 7 part 1 %s', 0, 1, @d) WITH NOWAIT; - WITH deadlock_stack AS ( - SELECT DISTINCT - ds.id, - ds.proc_name, - ds.event_date, - PARSENAME(ds.proc_name, 3) AS database_name, - PARSENAME(ds.proc_name, 2) AS schema_name, - PARSENAME(ds.proc_name, 1) AS proc_only_name, - '''' + STUFF((SELECT DISTINCT N',' + ds2.sql_handle - FROM #deadlock_stack AS ds2 - WHERE ds2.id = ds.id - AND ds2.event_date = ds.event_date - FOR XML PATH(N''), TYPE).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 1, N'') + '''' AS sql_handle_csv - FROM #deadlock_stack AS ds - GROUP BY PARSENAME(ds.proc_name, 3), - PARSENAME(ds.proc_name, 2), - PARSENAME(ds.proc_name, 1), - ds.id, - ds.proc_name, - ds.event_date - ) - INSERT #deadlock_findings WITH (TABLOCKX) - ( check_id, database_name, object_name, finding_group, finding ) - SELECT DISTINCT 7 AS check_id, - ISNULL(DB_NAME(dow.database_id), 'UNKNOWN') AS database_name, - ds.proc_name AS object_name, - 'More Info - Query' AS finding_group, - 'EXEC sp_BlitzCache ' + - CASE WHEN ds.proc_name = 'adhoc' - THEN ' @OnlySqlHandles = ' + ds.sql_handle_csv - ELSE '@StoredProcName = ' + - QUOTENAME(ds.proc_only_name, '''') - END + - ';' AS finding - FROM deadlock_stack AS ds - JOIN #deadlock_owner_waiter AS dow - ON dow.owner_id = ds.id - AND dow.event_date = ds.event_date - WHERE 1 = 1 - AND (DB_NAME(dow.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (dow.event_date >= @StartDate OR @StartDate IS NULL) - AND (dow.event_date < @EndDate OR @EndDate IS NULL) - AND (dow.object_name = @StoredProcName OR @StoredProcName IS NULL) - OPTION ( RECOMPILE ); + CREATE CLUSTERED INDEX cx_whatever_dp ON #deadlock_process (event_date, id); + CREATE CLUSTERED INDEX cx_whatever_drp ON #deadlock_resource_parallel (event_date, owner_id); + CREATE CLUSTERED INDEX cx_whatever_dow ON #deadlock_owner_waiter (event_date, owner_id, waiter_id); - IF @ProductVersionMajor >= 13 - BEGIN - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Check 7 part 2 %s', 0, 1, @d) WITH NOWAIT; - WITH deadlock_stack AS ( - SELECT DISTINCT - ds.id, - ds.sql_handle, - ds.proc_name, - ds.event_date, - PARSENAME(ds.proc_name, 3) AS database_name, - PARSENAME(ds.proc_name, 2) AS schema_name, - PARSENAME(ds.proc_name, 1) AS proc_only_name - FROM #deadlock_stack AS ds - ) - INSERT #deadlock_findings WITH (TABLOCKX) - ( check_id, database_name, object_name, finding_group, finding ) - SELECT DISTINCT 7 AS check_id, - DB_NAME(dow.database_id) AS database_name, - ds.proc_name AS object_name, - 'More Info - Query' AS finding_group, - 'EXEC sp_BlitzQueryStore ' - + '@DatabaseName = ' - + QUOTENAME(ds.database_name, '''') - + ', ' - + '@StoredProcName = ' - + QUOTENAME(ds.proc_only_name, '''') - + ';' AS finding - FROM deadlock_stack AS ds - JOIN #deadlock_owner_waiter AS dow - ON dow.owner_id = ds.id - AND dow.event_date = ds.event_date - WHERE ds.proc_name <> 'adhoc' - AND (DB_NAME(dow.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (dow.event_date >= @StartDate OR @StartDate IS NULL) - AND (dow.event_date < @EndDate OR @EndDate IS NULL) - AND (dow.object_name = @StoredProcName OR @StoredProcName IS NULL) - OPTION ( RECOMPILE ); - END; - + IF @Debug = 1 BEGIN SET STATISTICS XML ON; END; - /*Check 8 gives you stored proc deadlock counts*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Check 8 %s', 0, 1, @d) WITH NOWAIT; - INSERT #deadlock_findings WITH (TABLOCKX) - ( check_id, database_name, object_name, finding_group, finding ) - SELECT 8 AS check_id, - DB_NAME(dp.database_id) AS database_name, - ds.proc_name, - 'Stored Procedure Deadlocks', - 'The stored procedure ' - + PARSENAME(ds.proc_name, 2) - + '.' - + PARSENAME(ds.proc_name, 1) - + ' has been involved in ' - + CONVERT(NVARCHAR(10), COUNT_BIG(DISTINCT ds.id)) - + ' deadlocks.' - FROM #deadlock_stack AS ds - JOIN #deadlock_process AS dp - ON dp.id = ds.id - AND ds.event_date = dp.event_date - WHERE ds.proc_name <> 'adhoc' - AND (ds.proc_name = @StoredProcName OR @StoredProcName IS NULL) - AND (DB_NAME(dp.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (dp.event_date >= @StartDate OR @StartDate IS NULL) - AND (dp.event_date < @EndDate OR @EndDate IS NULL) - AND (dp.client_app = @AppName OR @AppName IS NULL) - AND (dp.host_name = @HostName OR @HostName IS NULL) - AND (dp.login_name = @LoginName OR @LoginName IS NULL) - GROUP BY DB_NAME(dp.database_id), ds.proc_name - OPTION(RECOMPILE); + WITH + deadlocks AS + ( + SELECT + deadlock_type = + N'Regular Deadlock', + dp.event_date, + dp.id, + dp.victim_id, + dp.spid, + dp.database_id, + dp.database_name, + dp.current_database_name, + dp.priority, + dp.log_used, + wait_resource = + dp.wait_resource COLLATE DATABASE_DEFAULT, + object_names = + CONVERT + ( + xml, + STUFF + ( + ( + SELECT DISTINCT + object_name = + NCHAR(10) + + N' ' + + ISNULL(c.object_name, N'') + + N' ' COLLATE DATABASE_DEFAULT + FROM #deadlock_owner_waiter AS c + WHERE (dp.id = c.owner_id + OR dp.victim_id = c.waiter_id) + AND dp.event_date = c.event_date + FOR XML + PATH(N''), + TYPE + ).value(N'.[1]', N'nvarchar(4000)'), + 1, + 1, + N'' + ) + ), + dp.wait_time, + dp.transaction_name, + dp.status, + dp.last_tran_started, + dp.last_batch_started, + dp.last_batch_completed, + dp.lock_mode, + dp.transaction_count, + dp.client_app, + dp.host_name, + dp.login_name, + dp.isolation_level, + dp.client_option_1, + dp.client_option_2, + inputbuf = + dp.process_xml.value('(//process/inputbuf/text())[1]', 'nvarchar(MAX)'), + en = + DENSE_RANK() OVER (ORDER BY dp.event_date), + qn = + ROW_NUMBER() OVER (PARTITION BY dp.event_date ORDER BY dp.event_date) - 1, + dn = + ROW_NUMBER() OVER (PARTITION BY dp.event_date, dp.id ORDER BY dp.event_date), + dp.is_victim, + owner_mode = + ISNULL(dp.owner_mode, N'-'), + owner_waiter_type = NULL, + owner_activity = NULL, + owner_waiter_activity = NULL, + owner_merging = NULL, + owner_spilling = NULL, + owner_waiting_to_close = NULL, + waiter_mode = + ISNULL(dp.waiter_mode, N'-'), + waiter_waiter_type = NULL, + waiter_owner_activity = NULL, + waiter_waiter_activity = NULL, + waiter_merging = NULL, + waiter_spilling = NULL, + waiter_waiting_to_close = NULL, + dp.deadlock_graph + FROM #deadlock_process AS dp + WHERE dp.victim_id IS NOT NULL + AND dp.is_parallel = 0 + UNION ALL - /*Check 9 gives you more info queries for sp_BlitzIndex */ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Check 9 %s', 0, 1, @d) WITH NOWAIT; - WITH bi AS ( - SELECT DISTINCT - dow.object_name, - DB_NAME(dow.database_id) as database_name, - a.schema_name AS schema_name, - a.table_name AS table_name - FROM #deadlock_owner_waiter AS dow - LEFT JOIN @sysAssObjId a ON a.database_id=dow.database_id AND a.partition_id = dow.associatedObjectId - WHERE 1 = 1 - AND (DB_NAME(dow.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (dow.event_date >= @StartDate OR @StartDate IS NULL) - AND (dow.event_date < @EndDate OR @EndDate IS NULL) - AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) - AND dow.object_name IS NOT NULL - ) - INSERT #deadlock_findings WITH (TABLOCKX) - ( check_id, database_name, object_name, finding_group, finding ) - SELECT 9 AS check_id, - bi.database_name, - bi.object_name, - 'More Info - Table' AS finding_group, - 'EXEC sp_BlitzIndex ' + - '@DatabaseName = ' + QUOTENAME(bi.database_name, '''') + - ', @SchemaName = ' + QUOTENAME(bi.schema_name, '''') + - ', @TableName = ' + QUOTENAME(bi.table_name, '''') + - ';' AS finding - FROM bi - OPTION ( RECOMPILE ); + SELECT + deadlock_type = + N'Parallel Deadlock', + dp.event_date, + dp.id, + dp.victim_id, + dp.spid, + dp.database_id, + dp.database_name, + dp.current_database_name, + dp.priority, + dp.log_used, + dp.wait_resource COLLATE DATABASE_DEFAULT, + object_names = + CONVERT + ( + xml, + STUFF + ( + ( + SELECT DISTINCT + object_name = + NCHAR(10) + + N' ' + + ISNULL(c.object_name, N'') + + N' ' COLLATE DATABASE_DEFAULT + FROM #deadlock_owner_waiter AS c + WHERE (dp.id = c.owner_id + OR dp.victim_id = c.waiter_id) + AND dp.event_date = c.event_date + FOR XML + PATH(N''), + TYPE + ).value(N'.[1]', N'nvarchar(4000)'), + 1, + 1, + N'' + ) + ), + dp.wait_time, + dp.transaction_name, + dp.status, + dp.last_tran_started, + dp.last_batch_started, + dp.last_batch_completed, + dp.lock_mode, + dp.transaction_count, + dp.client_app, + dp.host_name, + dp.login_name, + dp.isolation_level, + dp.client_option_1, + dp.client_option_2, + inputbuf = + dp.process_xml.value('(//process/inputbuf/text())[1]', 'nvarchar(MAX)'), + en = + DENSE_RANK() OVER (ORDER BY dp.event_date), + qn = + ROW_NUMBER() OVER (PARTITION BY dp.event_date ORDER BY dp.event_date) - 1, + dn = + ROW_NUMBER() OVER (PARTITION BY dp.event_date, dp.id ORDER BY dp.event_date), + is_victim = 1, + owner_mode = cao.wait_type COLLATE DATABASE_DEFAULT, + owner_waiter_type = cao.waiter_type, + owner_activity = cao.owner_activity, + owner_waiter_activity = cao.waiter_activity, + owner_merging = cao.merging, + owner_spilling = cao.spilling, + owner_waiting_to_close = cao.waiting_to_close, + waiter_mode = caw.wait_type COLLATE DATABASE_DEFAULT, + waiter_waiter_type = caw.waiter_type, + waiter_owner_activity = caw.owner_activity, + waiter_waiter_activity = caw.waiter_activity, + waiter_merging = caw.merging, + waiter_spilling = caw.spilling, + waiter_waiting_to_close = caw.waiting_to_close, + dp.deadlock_graph + FROM #deadlock_process AS dp + OUTER APPLY + ( + SELECT TOP (1) + drp.* + FROM #deadlock_resource_parallel AS drp + WHERE drp.owner_id = dp.id + AND drp.wait_type IN + ( + N'e_waitPortOpen', + N'e_waitPipeNewRow' + ) + ORDER BY drp.event_date + ) AS cao + OUTER APPLY + ( + SELECT TOP (1) + drp.* + FROM #deadlock_resource_parallel AS drp + WHERE drp.owner_id = dp.id + AND drp.wait_type IN + ( + N'e_waitPortOpen', + N'e_waitPipeGetRow' + ) + ORDER BY drp.event_date + ) AS caw + WHERE dp.is_parallel = 1 + ) + SELECT + d.deadlock_type, + d.event_date, + d.id, + d.victim_id, + d.spid, + deadlock_group = + N'Deadlock #' + + CONVERT + ( + nvarchar(10), + d.en + ) + + N', Query #' + + CASE + WHEN d.qn = 0 + THEN N'1' + ELSE CONVERT(nvarchar(10), d.qn) + END + CASE + WHEN d.is_victim = 1 + THEN N' - VICTIM' + ELSE N'' + END, + d.database_id, + d.database_name, + d.current_database_name, + d.priority, + d.log_used, + d.wait_resource, + d.object_names, + d.wait_time, + d.transaction_name, + d.status, + d.last_tran_started, + d.last_batch_started, + d.last_batch_completed, + d.lock_mode, + d.transaction_count, + d.client_app, + d.host_name, + d.login_name, + d.isolation_level, + d.client_option_1, + d.client_option_2, + inputbuf = + CASE + WHEN d.inputbuf + LIKE @inputbuf_bom + N'Proc |[Database Id = %' ESCAPE N'|' + THEN + OBJECT_SCHEMA_NAME + ( + SUBSTRING + ( + d.inputbuf, + CHARINDEX(N'Object Id = ', d.inputbuf) + 12, + LEN(d.inputbuf) - (CHARINDEX(N'Object Id = ', d.inputbuf) + 12) + ) + , + SUBSTRING + ( + d.inputbuf, + CHARINDEX(N'Database Id = ', d.inputbuf) + 14, + CHARINDEX(N'Object Id', d.inputbuf) - (CHARINDEX(N'Database Id = ', d.inputbuf) + 14) + ) + ) + + N'.' + + OBJECT_NAME + ( + SUBSTRING + ( + d.inputbuf, + CHARINDEX(N'Object Id = ', d.inputbuf) + 12, + LEN(d.inputbuf) - (CHARINDEX(N'Object Id = ', d.inputbuf) + 12) + ) + , + SUBSTRING + ( + d.inputbuf, + CHARINDEX(N'Database Id = ', d.inputbuf) + 14, + CHARINDEX(N'Object Id', d.inputbuf) - (CHARINDEX(N'Database Id = ', d.inputbuf) + 14) + ) + ) + ELSE d.inputbuf + END COLLATE Latin1_General_BIN2, + d.owner_mode, + d.owner_waiter_type, + d.owner_activity, + d.owner_waiter_activity, + d.owner_merging, + d.owner_spilling, + d.owner_waiting_to_close, + d.waiter_mode, + d.waiter_waiter_type, + d.waiter_owner_activity, + d.waiter_waiter_activity, + d.waiter_merging, + d.waiter_spilling, + d.waiter_waiting_to_close, + d.deadlock_graph, + d.is_victim + INTO #deadlocks + FROM deadlocks AS d + WHERE d.dn = 1 + AND (d.is_victim = @VictimsOnly + OR @VictimsOnly = 0) + AND d.qn < CASE + WHEN d.deadlock_type = N'Parallel Deadlock' + THEN 2 + ELSE 2147483647 + END + AND (DB_NAME(d.database_id) = @DatabaseName OR @DatabaseName IS NULL) + AND (d.event_date >= @StartDate OR @StartDate IS NULL) + AND (d.event_date < @EndDate OR @EndDate IS NULL) + AND (CONVERT(nvarchar(MAX), d.object_names) COLLATE Latin1_General_BIN2 LIKE N'%' + @ObjectName + N'%' OR @ObjectName IS NULL) + AND (d.client_app = @AppName OR @AppName IS NULL) + AND (d.host_name = @HostName OR @HostName IS NULL) + AND (d.login_name = @LoginName OR @LoginName IS NULL) + OPTION (RECOMPILE, LOOP JOIN, HASH JOIN); + + UPDATE d + SET d.inputbuf = + REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE( + REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE( + REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE( + d.inputbuf, + NCHAR(31), N'?'), NCHAR(30), N'?'), NCHAR(29), N'?'), NCHAR(28), N'?'), NCHAR(27), N'?'), + NCHAR(26), N'?'), NCHAR(25), N'?'), NCHAR(24), N'?'), NCHAR(23), N'?'), NCHAR(22), N'?'), + NCHAR(21), N'?'), NCHAR(20), N'?'), NCHAR(19), N'?'), NCHAR(18), N'?'), NCHAR(17), N'?'), + NCHAR(16), N'?'), NCHAR(15), N'?'), NCHAR(14), N'?'), NCHAR(12), N'?'), NCHAR(11), N'?'), + NCHAR(8), N'?'), NCHAR(7), N'?'), NCHAR(6), N'?'), NCHAR(5), N'?'), NCHAR(4), N'?'), + NCHAR(3), N'?'), NCHAR(2), N'?'), NCHAR(1), N'?'), NCHAR(0), N'?') + FROM #deadlocks AS d + OPTION(RECOMPILE); - /*Check 10 gets total deadlock wait time per object*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Check 10 %s', 0, 1, @d) WITH NOWAIT; - WITH chopsuey AS ( - SELECT DISTINCT - PARSENAME(dow.object_name, 3) AS database_name, - dow.object_name, - CONVERT(VARCHAR(10), (SUM(DISTINCT CONVERT(BIGINT, dp.wait_time)) / 1000) / 86400) AS wait_days, - CONVERT(VARCHAR(20), DATEADD(SECOND, (SUM(DISTINCT CONVERT(BIGINT, dp.wait_time)) / 1000), 0), 108) AS wait_time_hms - FROM #deadlock_owner_waiter AS dow - JOIN #deadlock_process AS dp - ON (dp.id = dow.owner_id OR dp.victim_id = dow.waiter_id) - AND dp.event_date = dow.event_date - WHERE 1 = 1 - AND (DB_NAME(dow.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (dow.event_date >= @StartDate OR @StartDate IS NULL) - AND (dow.event_date < @EndDate OR @EndDate IS NULL) - AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) - AND (dp.client_app = @AppName OR @AppName IS NULL) - AND (dp.host_name = @HostName OR @HostName IS NULL) - AND (dp.login_name = @LoginName OR @LoginName IS NULL) - GROUP BY PARSENAME(dow.object_name, 3), dow.object_name - ) - INSERT #deadlock_findings WITH (TABLOCKX) - ( check_id, database_name, object_name, finding_group, finding ) - SELECT 10 AS check_id, - cs.database_name, - cs.object_name, - 'Total object deadlock wait time' AS finding_group, - 'This object has had ' - + CONVERT(VARCHAR(10), cs.wait_days) - + ':' + CONVERT(VARCHAR(20), cs.wait_time_hms, 108) - + ' [d/h/m/s] of deadlock wait time.' AS finding - FROM chopsuey AS cs - WHERE cs.object_name IS NOT NULL - OPTION ( RECOMPILE ); - - /*Check 11 gets total deadlock wait time per database*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Check 11 %s', 0, 1, @d) WITH NOWAIT; - WITH wait_time AS ( - SELECT DB_NAME(dp.database_id) AS database_name, - SUM(CONVERT(BIGINT, dp.wait_time)) AS total_wait_time_ms - FROM #deadlock_process AS dp - WHERE 1 = 1 - AND (DB_NAME(dp.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (dp.event_date >= @StartDate OR @StartDate IS NULL) - AND (dp.event_date < @EndDate OR @EndDate IS NULL) - AND (dp.client_app = @AppName OR @AppName IS NULL) - AND (dp.host_name = @HostName OR @HostName IS NULL) - AND (dp.login_name = @LoginName OR @LoginName IS NULL) - GROUP BY DB_NAME(dp.database_id) - ) - INSERT #deadlock_findings WITH (TABLOCKX) - ( check_id, database_name, object_name, finding_group, finding ) - SELECT 11 AS check_id, - wt.database_name, - '-' AS object_name, - 'Total database deadlock wait time' AS finding_group, - 'This database has had ' - + CONVERT(VARCHAR(10), (SUM(DISTINCT CONVERT(BIGINT, wt.total_wait_time_ms)) / 1000) / 86400) - + ':' + CONVERT(VARCHAR(20), DATEADD(SECOND, (SUM(DISTINCT CONVERT(BIGINT, wt.total_wait_time_ms)) / 1000), 0), 108) - + ' [d/h/m/s] of deadlock wait time.' - FROM wait_time AS wt - GROUP BY wt.database_name - OPTION ( RECOMPILE ); + SELECT + d.deadlock_type, + d.event_date, + database_name = + DB_NAME(d.database_id), + database_name_x = + d.database_name, + d.current_database_name, + d.spid, + d.deadlock_group, + d.client_option_1, + d.client_option_2, + d.lock_mode, + query_xml = + ( + SELECT + [processing-instruction(query)] = + d.inputbuf + FOR XML + PATH(N''), + TYPE + ), + query_string = + d.inputbuf, + d.object_names, + d.isolation_level, + d.owner_mode, + d.waiter_mode, + d.transaction_count, + d.login_name, + d.host_name, + d.client_app, + d.wait_time, + d.wait_resource, + d.priority, + d.log_used, + d.last_tran_started, + d.last_batch_started, + d.last_batch_completed, + d.transaction_name, + d.status, + /*These columns will be NULL for regular (non-parallel) deadlocks*/ + parallel_deadlock_details = + ( + SELECT + d.owner_waiter_type, + d.owner_activity, + d.owner_waiter_activity, + d.owner_merging, + d.owner_spilling, + d.owner_waiting_to_close, + d.waiter_waiter_type, + d.waiter_owner_activity, + d.waiter_waiter_activity, + d.waiter_merging, + d.waiter_spilling, + d.waiter_waiting_to_close + FOR XML + PATH('parallel_deadlock_details'), + TYPE + ), + d.owner_waiter_type, + d.owner_activity, + d.owner_waiter_activity, + d.owner_merging, + d.owner_spilling, + d.owner_waiting_to_close, + d.waiter_waiter_type, + d.waiter_owner_activity, + d.waiter_waiter_activity, + d.waiter_merging, + d.waiter_spilling, + d.waiter_waiting_to_close, + /*end parallel deadlock columns*/ + d.deadlock_graph, + d.is_victim + INTO #deadlock_results + FROM #deadlocks AS d; + + IF @Debug = 1 BEGIN SET STATISTICS XML OFF; END; + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + + /*There's too much risk of errors sending the*/ + IF @OutputDatabaseCheck = 0 + BEGIN + SET @ExportToExcel = 0; + END; - /*Check 12 gets total deadlock wait time for SQL Agent*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Check 12 %s', 0, 1, @d) WITH NOWAIT; - INSERT #deadlock_findings WITH (TABLOCKX) - ( check_id, database_name, object_name, finding_group, finding ) - SELECT 12, - DB_NAME(aj.database_id), - 'SQLAgent - Job: ' - + aj.job_name - + ' Step: ' - + aj.step_name, - 'Agent Job Deadlocks', - RTRIM(COUNT(*)) + ' deadlocks from this Agent Job and Step' - FROM #agent_job AS aj - GROUP BY DB_NAME(aj.database_id), aj.job_name, aj.step_name - OPTION ( RECOMPILE ); + SET @deadlock_result += N' + SELECT + server_name = + @@SERVERNAME, + dr.deadlock_type, + dr.event_date, + database_name = + COALESCE + ( + dr.database_name, + dr.database_name_x, + dr.current_database_name + ), + dr.spid, + dr.deadlock_group, + ' + CASE @ExportToExcel + WHEN 1 + THEN N' + query = dr.query_string, + object_names = + REPLACE( + REPLACE( + CONVERT + ( + nvarchar(MAX), + dr.object_names + ) COLLATE Latin1_General_BIN2, + '''', ''''), + '''', ''''),' + ELSE N'query = dr.query_xml, + dr.object_names,' + END + N' + dr.isolation_level, + dr.owner_mode, + dr.waiter_mode, + dr.lock_mode, + dr.transaction_count, + dr.client_option_1, + dr.client_option_2, + dr.login_name, + dr.host_name, + dr.client_app, + dr.wait_time, + dr.wait_resource, + dr.priority, + dr.log_used, + dr.last_tran_started, + dr.last_batch_started, + dr.last_batch_completed, + dr.transaction_name, + dr.status,' + + CASE + WHEN (@ExportToExcel = 1 + OR @OutputDatabaseCheck = 0) + THEN N' + dr.owner_waiter_type, + dr.owner_activity, + dr.owner_waiter_activity, + dr.owner_merging, + dr.owner_spilling, + dr.owner_waiting_to_close, + dr.waiter_waiter_type, + dr.waiter_owner_activity, + dr.waiter_waiter_activity, + dr.waiter_merging, + dr.waiter_spilling, + dr.waiter_waiting_to_close,' + ELSE N' + dr.parallel_deadlock_details,' + END + + CASE + @ExportToExcel + WHEN 1 + THEN N' + deadlock_graph = + REPLACE(REPLACE( + REPLACE(REPLACE( + CONVERT + ( + nvarchar(MAX), + dr.deadlock_graph + ) COLLATE Latin1_General_BIN2, + ''NCHAR(10)'', ''''), ''NCHAR(13)'', ''''), + ''CHAR(10)'', ''''), ''CHAR(13)'', '''')' + ELSE N' + dr.deadlock_graph' + END + N' + FROM #deadlock_results AS dr + ORDER BY + dr.event_date, + dr.is_victim DESC + OPTION(RECOMPILE, LOOP JOIN, HASH JOIN); + '; - /*Check 13 is total parallel deadlocks*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Check 13 %s', 0, 1, @d) WITH NOWAIT; - INSERT #deadlock_findings WITH (TABLOCKX) - ( check_id, database_name, object_name, finding_group, finding ) - SELECT 13 AS check_id, - N'-' AS database_name, - '-' AS object_name, - 'Total parallel deadlocks' AS finding_group, - 'There have been ' - + CONVERT(NVARCHAR(20), COUNT_BIG(DISTINCT drp.event_date)) - + ' parallel deadlocks.' - FROM #deadlock_resource_parallel AS drp - WHERE 1 = 1 - HAVING COUNT_BIG(DISTINCT drp.event_date) > 0 - OPTION ( RECOMPILE ); + IF (@OutputDatabaseCheck = 0) + BEGIN + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Results to table %s', 0, 1, @d) WITH NOWAIT; - /*Thank you goodnight*/ - INSERT #deadlock_findings WITH (TABLOCKX) - ( check_id, database_name, object_name, finding_group, finding ) - VALUES ( -1, - N'sp_BlitzLock ' + CAST(CONVERT(DATETIME, @VersionDate, 102) AS VARCHAR(100)), - N'SQL Server First Responder Kit', - N'http://FirstResponderKit.org/', - N'To get help or add your own contributions, join us at http://FirstResponderKit.org.'); + IF @Debug = 1 + BEGIN + PRINT @deadlock_result; + SET STATISTICS XML ON; + END; - + INSERT INTO + DeadLockTbl + ( + ServerName, + deadlock_type, + event_date, + database_name, + spid, + deadlock_group, + query, + object_names, + isolation_level, + owner_mode, + waiter_mode, + lock_mode, + transaction_count, + client_option_1, + client_option_2, + login_name, + host_name, + client_app, + wait_time, + wait_resource, + priority, + log_used, + last_tran_started, + last_batch_started, + last_batch_completed, + transaction_name, + status, + owner_waiter_type, + owner_activity, + owner_waiter_activity, + owner_merging, + owner_spilling, + owner_waiting_to_close, + waiter_waiter_type, + waiter_owner_activity, + waiter_waiter_activity, + waiter_merging, + waiter_spilling, + waiter_waiting_to_close, + deadlock_graph + ) + EXEC sys.sp_executesql + @deadlock_result; + IF @Debug = 1 + BEGIN + SET STATISTICS XML OFF; + END; - /*Results*/ - /*Break in case of emergency*/ - --CREATE CLUSTERED INDEX cx_whatever ON #deadlock_process (event_date, id); - --CREATE CLUSTERED INDEX cx_whatever ON #deadlock_resource_parallel (event_date, owner_id); - --CREATE CLUSTERED INDEX cx_whatever ON #deadlock_owner_waiter (event_date, owner_id, waiter_id); - IF(@OutputDatabaseCheck = 0) - BEGIN - - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Results 1 %s', 0, 1, @d) WITH NOWAIT; - WITH deadlocks - AS ( SELECT N'Regular Deadlock' AS deadlock_type, - dp.event_date, - dp.id, - dp.victim_id, - dp.spid, - dp.database_id, - dp.priority, - dp.log_used, - dp.wait_resource COLLATE DATABASE_DEFAULT AS wait_resource, - CONVERT( - XML, - STUFF(( SELECT DISTINCT NCHAR(10) - + N' ' - + ISNULL(c.object_name, N'') - + N' ' COLLATE DATABASE_DEFAULT AS object_name - FROM #deadlock_owner_waiter AS c - WHERE ( dp.id = c.owner_id - OR dp.victim_id = c.waiter_id ) - AND dp.event_date = c.event_date - FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(4000)'), - 1, 1, N'')) AS object_names, - dp.wait_time, - dp.transaction_name, - dp.last_tran_started, - dp.last_batch_started, - dp.last_batch_completed, - dp.lock_mode, - dp.transaction_count, - dp.client_app, - dp.host_name, - dp.login_name, - dp.isolation_level, - dp.process_xml.value('(//process/inputbuf/text())[1]', 'NVARCHAR(MAX)') AS inputbuf, - DENSE_RANK() OVER ( ORDER BY dp.event_date ) AS en, - ROW_NUMBER() OVER ( PARTITION BY dp.event_date ORDER BY dp.event_date ) -1 AS qn, - ROW_NUMBER() OVER ( PARTITION BY dp.event_date, dp.id ORDER BY dp.event_date ) AS dn, - dp.is_victim, - ISNULL(dp.owner_mode, '-') AS owner_mode, - NULL AS owner_waiter_type, - NULL AS owner_activity, - NULL AS owner_waiter_activity, - NULL AS owner_merging, - NULL AS owner_spilling, - NULL AS owner_waiting_to_close, - ISNULL(dp.waiter_mode, '-') AS waiter_mode, - NULL AS waiter_waiter_type, - NULL AS waiter_owner_activity, - NULL AS waiter_waiter_activity, - NULL AS waiter_merging, - NULL AS waiter_spilling, - NULL AS waiter_waiting_to_close, - dp.deadlock_graph - FROM #deadlock_process AS dp - WHERE dp.victim_id IS NOT NULL - AND dp.is_parallel = 0 - - UNION ALL - - SELECT N'Parallel Deadlock' AS deadlock_type, - dp.event_date, - dp.id, - dp.victim_id, - dp.spid, - dp.database_id, - dp.priority, - dp.log_used, - dp.wait_resource COLLATE DATABASE_DEFAULT, - CONVERT( - XML, - STUFF(( SELECT DISTINCT NCHAR(10) - + N' ' - + ISNULL(c.object_name, N'') - + N' ' COLLATE DATABASE_DEFAULT AS object_name - FROM #deadlock_owner_waiter AS c - WHERE ( dp.id = c.owner_id - OR dp.victim_id = c.waiter_id ) - AND dp.event_date = c.event_date - FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(4000)'), - 1, 1, N'')) AS object_names, - dp.wait_time, - dp.transaction_name, - dp.last_tran_started, - dp.last_batch_started, - dp.last_batch_completed, - dp.lock_mode, - dp.transaction_count, - dp.client_app, - dp.host_name, - dp.login_name, - dp.isolation_level, - dp.process_xml.value('(//process/inputbuf/text())[1]', 'NVARCHAR(MAX)') AS inputbuf, - DENSE_RANK() OVER ( ORDER BY dp.event_date ) AS en, - ROW_NUMBER() OVER ( PARTITION BY dp.event_date ORDER BY dp.event_date ) -1 AS qn, - ROW_NUMBER() OVER ( PARTITION BY dp.event_date, dp.id ORDER BY dp.event_date ) AS dn, - 1 AS is_victim, - cao.wait_type COLLATE DATABASE_DEFAULT AS owner_mode, - cao.waiter_type AS owner_waiter_type, - cao.owner_activity AS owner_activity, - cao.waiter_activity AS owner_waiter_activity, - cao.merging AS owner_merging, - cao.spilling AS owner_spilling, - cao.waiting_to_close AS owner_waiting_to_close, - caw.wait_type COLLATE DATABASE_DEFAULT AS waiter_mode, - caw.waiter_type AS waiter_waiter_type, - caw.owner_activity AS waiter_owner_activity, - caw.waiter_activity AS waiter_waiter_activity, - caw.merging AS waiter_merging, - caw.spilling AS waiter_spilling, - caw.waiting_to_close AS waiter_waiting_to_close, - dp.deadlock_graph - FROM #deadlock_process AS dp - CROSS APPLY (SELECT TOP 1 * FROM #deadlock_resource_parallel AS drp WHERE drp.owner_id = dp.id AND drp.wait_type = 'e_waitPipeNewRow' ORDER BY drp.event_date) AS cao - CROSS APPLY (SELECT TOP 1 * FROM #deadlock_resource_parallel AS drp WHERE drp.owner_id = dp.id AND drp.wait_type = 'e_waitPipeGetRow' ORDER BY drp.event_date) AS caw - WHERE dp.is_parallel = 1 ) - insert into DeadLockTbl ( - ServerName, - deadlock_type, - event_date, - database_name, - spid, - deadlock_group, - query, - object_names, - isolation_level, - owner_mode, - waiter_mode, - transaction_count, - login_name, - host_name, - client_app, - wait_time, - wait_resource, - priority, - log_used, - last_tran_started, - last_batch_started, - last_batch_completed, - transaction_name, - owner_waiter_type, - owner_activity, - owner_waiter_activity, - owner_merging, - owner_spilling, - owner_waiting_to_close, - waiter_waiter_type, - waiter_owner_activity, - waiter_waiter_activity, - waiter_merging, - waiter_spilling, - waiter_waiting_to_close, - deadlock_graph - ) - SELECT @ServerName, - d.deadlock_type, - d.event_date, - DB_NAME(d.database_id) AS database_name, - d.spid, - 'Deadlock #' - + CONVERT(NVARCHAR(10), d.en) - + ', Query #' - + CASE WHEN d.qn = 0 THEN N'1' ELSE CONVERT(NVARCHAR(10), d.qn) END - + CASE WHEN d.is_victim = 1 THEN ' - VICTIM' ELSE '' END - AS deadlock_group, - CONVERT(XML, N'') AS query, - d.object_names, - d.isolation_level, - d.owner_mode, - d.waiter_mode, - d.transaction_count, - d.login_name, - d.host_name, - d.client_app, - d.wait_time, - d.wait_resource, - d.priority, - d.log_used, - d.last_tran_started, - d.last_batch_started, - d.last_batch_completed, - d.transaction_name, - /*These columns will be NULL for regular (non-parallel) deadlocks*/ - d.owner_waiter_type, - d.owner_activity, - d.owner_waiter_activity, - d.owner_merging, - d.owner_spilling, - d.owner_waiting_to_close, - d.waiter_waiter_type, - d.waiter_owner_activity, - d.waiter_waiter_activity, - d.waiter_merging, - d.waiter_spilling, - d.waiter_waiting_to_close, - d.deadlock_graph - FROM deadlocks AS d - WHERE d.dn = 1 - AND (is_victim = @VictimsOnly OR @VictimsOnly = 0) - AND d.qn < CASE WHEN d.deadlock_type = N'Parallel Deadlock' THEN 2 ELSE 2147483647 END - AND (DB_NAME(d.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (d.event_date >= @StartDate OR @StartDate IS NULL) - AND (d.event_date < @EndDate OR @EndDate IS NULL) - AND (CONVERT(NVARCHAR(MAX), d.object_names) LIKE '%' + @ObjectName + '%' OR @ObjectName IS NULL) - AND (d.client_app = @AppName OR @AppName IS NULL) - AND (d.host_name = @HostName OR @HostName IS NULL) - AND (d.login_name = @LoginName OR @LoginName IS NULL) - ORDER BY d.event_date, is_victim DESC - OPTION ( RECOMPILE ); - - drop SYNONYM DeadLockTbl; --done insert into blitzlock table going to insert into findings table first create synonym. + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - -- RAISERROR('att deadlock findings', 0, 1) WITH NOWAIT; - + DROP SYNONYM DeadLockTbl; - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Findings %s', 0, 1, @d) WITH NOWAIT; + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Findings to table %s', 0, 1, @d) WITH NOWAIT; - Insert into DeadlockFindings (ServerName,check_id,database_name,object_name,finding_group,finding) - SELECT @ServerName,df.check_id, df.database_name, df.object_name, df.finding_group, df.finding - FROM #deadlock_findings AS df - ORDER BY df.check_id - OPTION ( RECOMPILE ); + INSERT INTO + DeadlockFindings + ( + ServerName, + check_id, + database_name, + object_name, + finding_group, + finding + ) + SELECT + @@SERVERNAME, + df.check_id, + df.database_name, + df.object_name, + df.finding_group, + df.finding + FROM #deadlock_findings AS df + ORDER BY df.check_id + OPTION(RECOMPILE); + + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + + DROP SYNONYM DeadlockFindings; /*done with inserting.*/ + END; + ELSE /*Output to database is not set output to client app*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Results to client %s', 0, 1, @d) WITH NOWAIT; - drop SYNONYM DeadlockFindings; --done with inserting. -END -ELSE --Output to database is not set output to client app - BEGIN - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Results 1 %s', 0, 1, @d) WITH NOWAIT; - WITH deadlocks - AS ( SELECT N'Regular Deadlock' AS deadlock_type, - dp.event_date, - dp.id, - dp.victim_id, - dp.spid, - dp.database_id, - dp.priority, - dp.log_used, - dp.wait_resource COLLATE DATABASE_DEFAULT AS wait_resource, - CONVERT( - XML, - STUFF(( SELECT DISTINCT NCHAR(10) - + N' ' - + ISNULL(c.object_name, N'') - + N' ' COLLATE DATABASE_DEFAULT AS object_name - FROM #deadlock_owner_waiter AS c - WHERE ( dp.id = c.owner_id - OR dp.victim_id = c.waiter_id ) - AND dp.event_date = c.event_date - FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(4000)'), - 1, 1, N'')) AS object_names, - dp.wait_time, - dp.transaction_name, - dp.last_tran_started, - dp.last_batch_started, - dp.last_batch_completed, - dp.lock_mode, - dp.transaction_count, - dp.client_app, - dp.host_name, - dp.login_name, - dp.isolation_level, - dp.process_xml.value('(//process/inputbuf/text())[1]', 'NVARCHAR(MAX)') AS inputbuf, - DENSE_RANK() OVER ( ORDER BY dp.event_date ) AS en, - ROW_NUMBER() OVER ( PARTITION BY dp.event_date ORDER BY dp.event_date ) -1 AS qn, - ROW_NUMBER() OVER ( PARTITION BY dp.event_date, dp.id ORDER BY dp.event_date ) AS dn, - dp.is_victim, - ISNULL(dp.owner_mode, '-') AS owner_mode, - NULL AS owner_waiter_type, - NULL AS owner_activity, - NULL AS owner_waiter_activity, - NULL AS owner_merging, - NULL AS owner_spilling, - NULL AS owner_waiting_to_close, - ISNULL(dp.waiter_mode, '-') AS waiter_mode, - NULL AS waiter_waiter_type, - NULL AS waiter_owner_activity, - NULL AS waiter_waiter_activity, - NULL AS waiter_merging, - NULL AS waiter_spilling, - NULL AS waiter_waiting_to_close, - dp.deadlock_graph - FROM #deadlock_process AS dp - WHERE dp.victim_id IS NOT NULL - AND dp.is_parallel = 0 + IF @Debug = 1 BEGIN SET STATISTICS XML ON; END; - UNION ALL - - SELECT N'Parallel Deadlock' AS deadlock_type, - dp.event_date, - dp.id, - dp.victim_id, - dp.spid, - dp.database_id, - dp.priority, - dp.log_used, - dp.wait_resource COLLATE DATABASE_DEFAULT, - CONVERT( - XML, - STUFF(( SELECT DISTINCT NCHAR(10) - + N' ' - + ISNULL(c.object_name, N'') - + N' ' COLLATE DATABASE_DEFAULT AS object_name - FROM #deadlock_owner_waiter AS c - WHERE ( dp.id = c.owner_id - OR dp.victim_id = c.waiter_id ) - AND dp.event_date = c.event_date - FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(4000)'), - 1, 1, N'')) AS object_names, - dp.wait_time, - dp.transaction_name, - dp.last_tran_started, - dp.last_batch_started, - dp.last_batch_completed, - dp.lock_mode, - dp.transaction_count, - dp.client_app, - dp.host_name, - dp.login_name, - dp.isolation_level, - dp.process_xml.value('(//process/inputbuf/text())[1]', 'NVARCHAR(MAX)') AS inputbuf, - DENSE_RANK() OVER ( ORDER BY dp.event_date ) AS en, - ROW_NUMBER() OVER ( PARTITION BY dp.event_date ORDER BY dp.event_date ) -1 AS qn, - ROW_NUMBER() OVER ( PARTITION BY dp.event_date, dp.id ORDER BY dp.event_date ) AS dn, - 1 AS is_victim, - cao.wait_type COLLATE DATABASE_DEFAULT AS owner_mode, - cao.waiter_type AS owner_waiter_type, - cao.owner_activity AS owner_activity, - cao.waiter_activity AS owner_waiter_activity, - cao.merging AS owner_merging, - cao.spilling AS owner_spilling, - cao.waiting_to_close AS owner_waiting_to_close, - caw.wait_type COLLATE DATABASE_DEFAULT AS waiter_mode, - caw.waiter_type AS waiter_waiter_type, - caw.owner_activity AS waiter_owner_activity, - caw.waiter_activity AS waiter_waiter_activity, - caw.merging AS waiter_merging, - caw.spilling AS waiter_spilling, - caw.waiting_to_close AS waiter_waiting_to_close, - dp.deadlock_graph - FROM #deadlock_process AS dp - OUTER APPLY (SELECT TOP 1 * FROM #deadlock_resource_parallel AS drp WHERE drp.owner_id = dp.id AND drp.wait_type IN ('e_waitPortOpen', 'e_waitPipeNewRow') ORDER BY drp.event_date) AS cao - OUTER APPLY (SELECT TOP 1 * FROM #deadlock_resource_parallel AS drp WHERE drp.owner_id = dp.id AND drp.wait_type IN ('e_waitPortOpen', 'e_waitPipeGetRow') ORDER BY drp.event_date) AS caw - WHERE dp.is_parallel = 1 - ) - SELECT d.deadlock_type, - d.event_date, - DB_NAME(d.database_id) AS database_name, - d.spid, - 'Deadlock #' - + CONVERT(NVARCHAR(10), d.en) - + ', Query #' - + CASE WHEN d.qn = 0 THEN N'1' ELSE CONVERT(NVARCHAR(10), d.qn) END - + CASE WHEN d.is_victim = 1 THEN ' - VICTIM' ELSE '' END - AS deadlock_group, - CONVERT(XML, N'') AS query_xml, - d.inputbuf AS query_string, - d.object_names, - d.isolation_level, - d.owner_mode, - d.waiter_mode, - d.transaction_count, - d.login_name, - d.host_name, - d.client_app, - d.wait_time, - d.wait_resource, - d.priority, - d.log_used, - d.last_tran_started, - d.last_batch_started, - d.last_batch_completed, - d.transaction_name, - /*These columns will be NULL for regular (non-parallel) deadlocks*/ - d.owner_waiter_type, - d.owner_activity, - d.owner_waiter_activity, - d.owner_merging, - d.owner_spilling, - d.owner_waiting_to_close, - d.waiter_waiter_type, - d.waiter_owner_activity, - d.waiter_waiter_activity, - d.waiter_merging, - d.waiter_spilling, - d.waiter_waiting_to_close, - d.deadlock_graph, - d.is_victim - INTO #deadlock_results - FROM deadlocks AS d - WHERE d.dn = 1 - AND (d.is_victim = @VictimsOnly OR @VictimsOnly = 0) - AND d.qn < CASE WHEN d.deadlock_type = N'Parallel Deadlock' THEN 2 ELSE 2147483647 END - AND (DB_NAME(d.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (d.event_date >= @StartDate OR @StartDate IS NULL) - AND (d.event_date < @EndDate OR @EndDate IS NULL) - AND (CONVERT(NVARCHAR(MAX), d.object_names) LIKE '%' + @ObjectName + '%' OR @ObjectName IS NULL) - AND (d.client_app = @AppName OR @AppName IS NULL) - AND (d.host_name = @HostName OR @HostName IS NULL) - AND (d.login_name = @LoginName OR @LoginName IS NULL) - OPTION ( RECOMPILE ); - + EXEC sys.sp_executesql + @deadlock_result; - DECLARE @deadlock_result NVARCHAR(MAX) = N'' + IF @Debug = 1 + BEGIN + SET STATISTICS XML OFF; + PRINT @deadlock_result; + END; - SET @deadlock_result += N' - SELECT - dr.deadlock_type, - dr.event_date, - dr.database_name, - dr.spid, - dr.deadlock_group, - ' - + CASE @ExportToExcel - WHEN 1 - THEN N'dr.query_string AS query, - REPLACE(REPLACE(CONVERT(NVARCHAR(MAX), dr.object_names), '''', ''''), '''', '''') AS object_names,' - ELSE N'dr.query_xml AS query, - dr.object_names,' - END + - N' - dr.isolation_level, - dr.owner_mode, - dr.waiter_mode, - dr.transaction_count, - dr.login_name, - dr.host_name, - dr.client_app, - dr.wait_time, - dr.wait_resource, - dr.priority, - dr.log_used, - dr.last_tran_started, - dr.last_batch_started, - dr.last_batch_completed, - dr.transaction_name, - dr.owner_waiter_type, - dr.owner_activity, - dr.owner_waiter_activity, - dr.owner_merging, - dr.owner_spilling, - dr.owner_waiting_to_close, - dr.waiter_waiter_type, - dr.waiter_owner_activity, - dr.waiter_waiter_activity, - dr.waiter_merging, - dr.waiter_spilling, - dr.waiter_waiting_to_close' - + CASE @ExportToExcel - WHEN 1 - THEN N'' - ELSE N', - dr.deadlock_graph' - END + - ' - FROM #deadlock_results AS dr - ORDER BY dr.event_date, dr.is_victim DESC - OPTION(RECOMPILE); - ' - - EXEC sys.sp_executesql - @deadlock_result; - - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Findings %s', 0, 1, @d) WITH NOWAIT; - SELECT df.check_id, df.database_name, df.object_name, df.finding_group, df.finding - FROM #deadlock_findings AS df - ORDER BY df.check_id - OPTION ( RECOMPILE ); - - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Done %s', 0, 1, @d) WITH NOWAIT; - END --done with output to client app. + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Returning findings %s', 0, 1, @d) WITH NOWAIT; + SELECT + df.check_id, + df.database_name, + df.object_name, + df.finding_group, + df.finding + FROM #deadlock_findings AS df + ORDER BY df.check_id + OPTION(RECOMPILE); + + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + END; /*done with output to client app.*/ IF @Debug = 1 - BEGIN + BEGIN + SELECT + table_name = N'#dd', + * + FROM #dd AS d + OPTION(RECOMPILE); + + SELECT + table_name = N'#deadlock_data', + * + FROM #deadlock_data AS dd + OPTION(RECOMPILE); + + SELECT + table_name = N'#deadlock_resource', + * + FROM #deadlock_resource AS dr + OPTION(RECOMPILE); - SELECT '#deadlock_data' AS table_name, * - FROM #deadlock_data AS dd - OPTION ( RECOMPILE ); + SELECT + table_name = N'#deadlock_resource_parallel', + * + FROM #deadlock_resource_parallel AS drp + OPTION(RECOMPILE); - SELECT '#deadlock_resource' AS table_name, * - FROM #deadlock_resource AS dr - OPTION ( RECOMPILE ); + SELECT + table_name = N'#deadlock_owner_waiter', + * + FROM #deadlock_owner_waiter AS dow + OPTION(RECOMPILE); - SELECT '#deadlock_resource_parallel' AS table_name, * - FROM #deadlock_resource_parallel AS drp - OPTION ( RECOMPILE ); + SELECT + table_name = N'#deadlock_process', + * + FROM #deadlock_process AS dp + OPTION(RECOMPILE); - SELECT '#deadlock_owner_waiter' AS table_name, * - FROM #deadlock_owner_waiter AS dow - OPTION ( RECOMPILE ); + SELECT + table_name = N'#deadlock_stack', + * + FROM #deadlock_stack AS ds + OPTION(RECOMPILE); - SELECT '#deadlock_process' AS table_name, * - FROM #deadlock_process AS dp - OPTION ( RECOMPILE ); + SELECT + table_name = N'#deadlocks', + * + FROM #deadlocks AS d + OPTION(RECOMPILE); - SELECT '#deadlock_stack' AS table_name, * - FROM #deadlock_stack AS ds - OPTION ( RECOMPILE ); + SELECT + table_name = N'#deadlock_results', + * + FROM #deadlock_results AS dr + OPTION(RECOMPILE); - SELECT '#deadlock_results' AS table_name, * - FROM #deadlock_results AS dr - OPTION ( RECOMPILE ); + SELECT + table_name = N'#x', + * + FROM #x AS x + OPTION(RECOMPILE); - END; -- End debug + SELECT + table_name = N'@sysAssObjId', + * + FROM @sysAssObjId AS s + OPTION(RECOMPILE); - END; --Final End + END; /*End debug*/ + END; /*Final End*/ GO IF OBJECT_ID('dbo.sp_BlitzWho') IS NULL @@ -27223,7 +29354,7 @@ BEGIN SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.11', @VersionDate = '20221013'; + SELECT @Version = '8.12', @VersionDate = '20221213'; IF(@VersionCheckMode = 1) BEGIN @@ -27868,6 +29999,16 @@ BEGIN */ SET @StringToExecute = N'COALESCE( RIGHT(''00'' + CONVERT(VARCHAR(20), (ABS(r.total_elapsed_time) / 1000) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), (DATEADD(SECOND, (r.total_elapsed_time / 1000), 0) + DATEADD(MILLISECOND, (r.total_elapsed_time % 1000), 0)), 114), RIGHT(''00'' + CONVERT(VARCHAR(20), DATEDIFF(SECOND, s.last_request_start_time, GETDATE()) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), DATEADD(SECOND, DATEDIFF(SECOND, s.last_request_start_time, GETDATE()), 0), 114) ) AS [elapsed_time] , s.session_id , + CASE WHEN r.blocking_session_id <> 0 AND blocked.session_id IS NULL + THEN r.blocking_session_id + WHEN r.blocking_session_id <> 0 AND s.session_id <> blocked.blocking_session_id + THEN blocked.blocking_session_id + WHEN r.blocking_session_id = 0 AND s.session_id = blocked.session_id + THEN blocked.blocking_session_id + WHEN r.blocking_session_id <> 0 AND s.session_id = blocked.blocking_session_id + THEN r.blocking_session_id + ELSE NULL + END AS blocking_session_id, COALESCE(DB_NAME(r.database_id), DB_NAME(blocked.dbid), ''N/A'') AS database_name, ISNULL(SUBSTRING(dest.text, ( query_stats.statement_start_offset / 2 ) + 1, @@ -27888,16 +30029,6 @@ BEGIN ELSE NULL END AS wait_info , r.wait_resource , - CASE WHEN r.blocking_session_id <> 0 AND blocked.session_id IS NULL - THEN r.blocking_session_id - WHEN r.blocking_session_id <> 0 AND s.session_id <> blocked.blocking_session_id - THEN blocked.blocking_session_id - WHEN r.blocking_session_id = 0 AND s.session_id = blocked.session_id - THEN blocked.blocking_session_id - WHEN r.blocking_session_id <> 0 AND s.session_id = blocked.blocking_session_id - THEN r.blocking_session_id - ELSE NULL - END AS blocking_session_id, COALESCE(r.open_transaction_count, blocked.open_tran) AS open_transaction_count , CASE WHEN EXISTS ( SELECT 1 FROM sys.dm_tran_active_transactions AS tat @@ -28086,8 +30217,18 @@ IF @ProductVersionMajor >= 11 */ SELECT @StringToExecute = N'COALESCE( RIGHT(''00'' + CONVERT(VARCHAR(20), (ABS(r.total_elapsed_time) / 1000) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), (DATEADD(SECOND, (r.total_elapsed_time / 1000), 0) + DATEADD(MILLISECOND, (r.total_elapsed_time % 1000), 0)), 114), RIGHT(''00'' + CONVERT(VARCHAR(20), DATEDIFF(SECOND, s.last_request_start_time, GETDATE()) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), DATEADD(SECOND, DATEDIFF(SECOND, s.last_request_start_time, GETDATE()), 0), 114) ) AS [elapsed_time] , s.session_id , - COALESCE(DB_NAME(r.database_id), DB_NAME(blocked.dbid), ''N/A'') AS database_name, - ISNULL(SUBSTRING(dest.text, + CASE WHEN r.blocking_session_id <> 0 AND blocked.session_id IS NULL + THEN r.blocking_session_id + WHEN r.blocking_session_id <> 0 AND s.session_id <> blocked.blocking_session_id + THEN blocked.blocking_session_id + WHEN r.blocking_session_id = 0 AND s.session_id = blocked.session_id + THEN blocked.blocking_session_id + WHEN r.blocking_session_id <> 0 AND s.session_id = blocked.blocking_session_id + THEN r.blocking_session_id + ELSE NULL + END AS blocking_session_id, + COALESCE(DB_NAME(r.database_id), DB_NAME(blocked.dbid), ''N/A'') AS database_name, + ISNULL(SUBSTRING(dest.text, ( query_stats.statement_start_offset / 2 ) + 1, ( ( CASE query_stats.statement_end_offset WHEN -1 THEN DATALENGTH(dest.text) @@ -28131,17 +30272,7 @@ IF @ProductVersionMajor >= 11 ELSE N' NULL AS top_session_waits ,' END + - N'CASE WHEN r.blocking_session_id <> 0 AND blocked.session_id IS NULL - THEN r.blocking_session_id - WHEN r.blocking_session_id <> 0 AND s.session_id <> blocked.blocking_session_id - THEN blocked.blocking_session_id - WHEN r.blocking_session_id = 0 AND s.session_id = blocked.session_id - THEN blocked.blocking_session_id - WHEN r.blocking_session_id <> 0 AND s.session_id = blocked.blocking_session_id - THEN r.blocking_session_id - ELSE NULL - END AS blocking_session_id, - COALESCE(r.open_transaction_count, blocked.open_tran) AS open_transaction_count , + N'COALESCE(r.open_transaction_count, blocked.open_tran) AS open_transaction_count , CASE WHEN EXISTS ( SELECT 1 FROM sys.dm_tran_active_transactions AS tat JOIN sys.dm_tran_session_transactions AS tst @@ -28445,6 +30576,7 @@ IF @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @Output ,CheckDate ,[elapsed_time] ,[session_id] + ,[blocking_session_id] ,[database_name] ,[query_text]' + CASE WHEN @GetOuterCommand = 1 THEN N',[outer_command]' ELSE N'' END + N' @@ -28457,7 +30589,6 @@ IF @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @Output ,[wait_info] ,[wait_resource]' + CASE WHEN @ProductVersionMajor >= 11 THEN N',[top_session_waits]' ELSE N'' END + N' - ,[blocking_session_id] ,[open_transaction_count] ,[is_implicit_transaction] ,[nt_domain] @@ -28619,7 +30750,7 @@ DELETE FROM dbo.SqlServerVersions; INSERT INTO dbo.SqlServerVersions (MajorVersionNumber, MinorVersionNumber, Branch, [Url], ReleaseDate, MainstreamSupportEndDate, ExtendedSupportEndDate, MajorVersionName, MinorVersionName) VALUES - (16, 600, 'CTP2', 'https://support.microsoft.com/en-us/help/5011644', '2022-05-24', '2022-05-24', '2022-05-24', 'SQL Server 2022', 'CTP 2.0'), + (16, 1000, 'RTM', '', '2022-11-15', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'RTM'), (15, 4261, 'CU18', 'https://support.microsoft.com/en-us/help/5017593', '2022-09-28', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 18'), (15, 4249, 'CU17', 'https://support.microsoft.com/en-us/help/5016394', '2022-08-11', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 17'), (15, 4236, 'CU16 GDR', 'https://support.microsoft.com/en-us/help/5014353', '2022-06-14', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 16 GDR'), @@ -29035,7 +31166,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.11', @VersionDate = '20221013'; +SELECT @Version = '8.12', @VersionDate = '20221213'; IF(@VersionCheckMode = 1) BEGIN @@ -30120,7 +32251,7 @@ BEGIN @StockDetailsFooter = @StockDetailsFooter + @LineFeed + ' -- ?>'; /* Get the instance name to use as a Perfmon counter prefix. */ - IF SERVERPROPERTY('EngineEdition') = 5 /*CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) = 'SQL Azure'*/ + IF SERVERPROPERTY('EngineEdition') IN (5, 8) /*CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) = 'SQL Azure'*/ SELECT TOP 1 @ServiceName = LEFT(object_name, (CHARINDEX(':', object_name) - 1)) FROM sys.dm_os_performance_counters; ELSE @@ -31505,7 +33636,23 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, ; IF SERVERPROPERTY('EngineEdition') <> 5 /*SERVERPROPERTY('Edition') <> 'SQL Azure'*/ - EXEC sp_MSforeachdb @StringToExecute; + BEGIN + BEGIN TRY + EXEC sp_MSforeachdb @StringToExecute; + END TRY + BEGIN CATCH + IF (ERROR_NUMBER() = 1222) + BEGIN + INSERT INTO #UpdatedStats(HowToStopIt, RowsForSorting) + SELECT HowToStopIt = N'No information could be retrieved as the lock timeout was exceeded while iterating databases,' + + N' this is likely due to an Index operation in Progress', -1; + END + ELSE + BEGIN + THROW; + END + END CATCH + END ELSE EXEC(@StringToExecute); diff --git a/Install-Core-Blitz-With-Query-Store.sql b/Install-Core-Blitz-With-Query-Store.sql index 4fa480bb3..84ff56849 100644 --- a/Install-Core-Blitz-With-Query-Store.sql +++ b/Install-Core-Blitz-With-Query-Store.sql @@ -38,7 +38,7 @@ AS SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.11', @VersionDate = '20221013'; + SELECT @Version = '8.12', @VersionDate = '20221213'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -1090,7 +1090,7 @@ AS ) OR ( - Convert(datetime,ll.Value) < DATEADD(dd,-7, GETDATE()) + Convert(datetime,ll.Value,21) < DATEADD(dd,-7, GETDATE()) ) ); @@ -9737,7 +9737,7 @@ AS SET NOCOUNT ON; SET STATISTICS XML OFF; -SELECT @Version = '8.11', @VersionDate = '20221013'; +SELECT @Version = '8.12', @VersionDate = '20221213'; IF(@VersionCheckMode = 1) BEGIN @@ -10615,7 +10615,7 @@ AS SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.11', @VersionDate = '20221013'; + SELECT @Version = '8.12', @VersionDate = '20221213'; IF(@VersionCheckMode = 1) BEGIN @@ -12396,7 +12396,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.11', @VersionDate = '20221013'; +SELECT @Version = '8.12', @VersionDate = '20221213'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -15869,6 +15869,35 @@ WHERE QueryType = 'Statement' AND SPID = @@SPID OPTION (RECOMPILE); +/* Update to grab stored procedure name for individual statements when PSPO is detected */ +UPDATE p +SET QueryType = QueryType + ' (parent ' + + + QUOTENAME(OBJECT_SCHEMA_NAME(s.object_id, s.database_id)) + + '.' + + QUOTENAME(OBJECT_NAME(s.object_id, s.database_id)) + ')' +FROM ##BlitzCacheProcs p + OUTER APPLY ( + SELECT REPLACE(REPLACE(REPLACE(REPLACE(p.QueryText, ' (', '('), '( ', '('), ' =', '='), '= ', '=') AS NormalizedQueryText + ) a + OUTER APPLY ( + SELECT CHARINDEX('option(PLAN PER VALUE(ObjectID=', a.NormalizedQueryText) AS OptionStart + ) b + OUTER APPLY ( + SELECT SUBSTRING(a.NormalizedQueryText, b.OptionStart + 31, LEN(a.NormalizedQueryText) - b.OptionStart - 30) AS OptionSubstring + WHERE b.OptionStart > 0 + ) c + OUTER APPLY ( + SELECT PATINDEX('%[^0-9]%', c.OptionSubstring) AS ObjectLength + ) d + OUTER APPLY ( + SELECT TRY_CAST(SUBSTRING(OptionSubstring, 1, d.ObjectLength - 1) AS INT) AS ObjectId + ) e + JOIN sys.dm_exec_procedure_stats s ON DB_ID(p.DatabaseName) = s.database_id AND e.ObjectId = s.object_id +WHERE p.QueryType = 'Statement' +AND p.SPID = @@SPID +AND s.object_id IS NOT NULL +OPTION (RECOMPILE); + RAISERROR(N'Attempting to get function name for individual statements', 0, 1) WITH NOWAIT; DECLARE @function_update_sql NVARCHAR(MAX) = N'' IF EXISTS (SELECT 1/0 FROM sys.all_objects AS o WHERE o.name = 'dm_exec_function_stats') @@ -19356,7 +19385,9 @@ END '; WHEN N'avg duration' THEN N' AverageDuration' WHEN N'avg executions' THEN N' ExecutionsPerMinute' WHEN N'avg memory grant' THEN N' AvgMaxMemoryGrant' - WHEN 'avg spills' THEN N' AvgSpills' + WHEN N'avg spills' THEN N' AvgSpills' + WHEN N'unused grant' THEN N' MaxGrantKB - MaxUsedGrantKB' + ELSE N' TotalCPU ' END + N' DESC '; SET @StringToExecute += N' OPTION (RECOMPILE) ; '; @@ -19422,7 +19453,9 @@ END '; WHEN N'avg duration' THEN N' AverageDuration' WHEN N'avg executions' THEN N' ExecutionsPerMinute' WHEN N'avg memory grant' THEN N' AvgMaxMemoryGrant' - WHEN 'avg spills' THEN N' AvgSpills' + WHEN N'avg spills' THEN N' AvgSpills' + WHEN N'unused grant' THEN N' MaxGrantKB - MaxUsedGrantKB' + ELSE N' TotalCPU ' END + N' DESC '; SET @StringToExecute += N' OPTION (RECOMPILE) ; '; @@ -19579,7 +19612,9 @@ END '; WHEN N'avg duration' THEN N' AverageDuration' WHEN N'avg executions' THEN N' ExecutionsPerMinute' WHEN N'avg memory grant' THEN N' AvgMaxMemoryGrant' - WHEN 'avg spills' THEN N' AvgSpills' + WHEN N'avg spills' THEN N' AvgSpills' + WHEN N'unused grant' THEN N' MaxGrantKB - MaxUsedGrantKB' + ELSE N' TotalCPU ' END + N' DESC '; SET @StringToExecute += N' OPTION (RECOMPILE) ; '; @@ -19659,7 +19694,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.11', @VersionDate = '20221013'; +SELECT @Version = '8.12', @VersionDate = '20221213'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -19770,25 +19805,41 @@ SELECT END; RAISERROR(N'Starting run. %s', 0,1, @ScriptVersionName) WITH NOWAIT; - + + IF(@OutputType NOT IN ('TABLE','NONE')) BEGIN RAISERROR('Invalid value for parameter @OutputType. Expected: (TABLE;NONE)',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; + SET @OutputType = 'NONE' +END; IF(@OutputType = 'NONE') BEGIN + + IF ((@OutputServerName IS NOT NULL) AND (@OutputTableName IS NULL OR @OutputSchemaName IS NULL OR @OutputDatabaseName IS NULL)) + BEGIN + RAISERROR('Parameter @OutputServerName is specified, rest of @Output* parameters needs to also be specified',12,1); + RETURN; + END; + IF(@OutputTableName IS NULL OR @OutputSchemaName IS NULL OR @OutputDatabaseName IS NULL) BEGIN - RAISERROR('This procedure should be called with a value for @Output* parameters, as @OutputType is set to NONE',12,1); + RAISERROR('This procedure should be called with a value for @OutputTableName, @OutputSchemaName and @OutputDatabaseName parameters, as @OutputType is set to NONE',12,1); RETURN; END; + /* Output is supported for all modes, no reason to not bring pain and output IF(@BringThePain = 1) BEGIN RAISERROR('Incompatible Parameters: @BringThePain set to 1 and @OutputType set to NONE',12,1); RETURN; END; + */ /* Eventually limit by mode IF(@Mode not in (0,4)) BEGIN @@ -21536,7 +21587,7 @@ BEGIN TRY OR (PARSENAME(@SQLServerProductVersion, 4) = 10 AND PARSENAME(@SQLServerProductVersion, 3) = 50 AND PARSENAME(@SQLServerProductVersion, 2) >= 2500)) BEGIN RAISERROR (N'Gathering Statistics Info With Newer Syntax.',0,1) WITH NOWAIT; - SET @dsql=N'USE ' + @DatabaseName + N'; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + 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, 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, @@ -21611,7 +21662,7 @@ BEGIN TRY ELSE BEGIN RAISERROR (N'Gathering Statistics Info With Older Syntax.',0,1) WITH NOWAIT; - SET @dsql=N'USE ' + @DatabaseName + N'; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + 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, days_since_last_stats_update, rows, modification_counter, percent_modifications, modifications_before_auto_update, index_type_desc, table_create_date, table_modify_date, @@ -22589,6 +22640,7 @@ BEGIN LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partition_range_values prve ON prve.function_id = pf.function_id AND prve.boundary_id = p.partition_number ' ELSE N' ' END + N' LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_segments seg ON p.partition_id = seg.partition_id AND ic.index_column_id = seg.column_id AND rg.row_group_id = seg.segment_id WHERE rg.object_id = @ObjectID + AND rg.state IN (1, 2, 3) AND c.name IN ( ' + @ColumnListWithApostrophes + N')' + CASE WHEN @ShowPartitionRanges = 1 THEN N' ) AS y ' ELSE N' ' END + N' @@ -22633,32 +22685,142 @@ END; /* IF @TableName IS NOT NULL */ +ELSE /* @TableName IS NULL, so we operate in normal mode 0/1/2/3/4 */ +BEGIN +/* Validate and check table output params */ + /* Checks if @OutputServerName is populated with a valid linked server, and that the database name specified is valid */ + DECLARE @ValidOutputServer BIT; + DECLARE @ValidOutputLocation BIT; + DECLARE @LinkedServerDBCheck NVARCHAR(2000); + DECLARE @ValidLinkedServerDB INT; + DECLARE @tmpdbchk TABLE (cnt INT); + + IF @OutputServerName IS NOT NULL + BEGIN + IF (SUBSTRING(@OutputTableName, 2, 1) = '#') + BEGIN + RAISERROR('Due to the nature of temporary tables, outputting to a linked server requires a permanent table.', 16, 0); + END; + ELSE IF EXISTS (SELECT server_id FROM sys.servers WHERE QUOTENAME([name]) = @OutputServerName) + BEGIN + SET @LinkedServerDBCheck = 'SELECT 1 WHERE EXISTS (SELECT * FROM '+@OutputServerName+'.master.sys.databases WHERE QUOTENAME([name]) = '''+@OutputDatabaseName+''')'; + INSERT INTO @tmpdbchk EXEC sys.sp_executesql @LinkedServerDBCheck; + SET @ValidLinkedServerDB = (SELECT COUNT(*) FROM @tmpdbchk); + IF (@ValidLinkedServerDB > 0) + BEGIN + SET @ValidOutputServer = 1; + SET @ValidOutputLocation = 1; + END; + ELSE + RAISERROR('The specified database was not found on the output server', 16, 0); + END; + ELSE + BEGIN + RAISERROR('The specified output server was not found', 16, 0); + END; + END; + ELSE + BEGIN + IF (SUBSTRING(@OutputTableName, 2, 2) = '##') + BEGIN + SET @StringToExecute = N' IF (OBJECT_ID(''[tempdb].[dbo].@@@OutputTableName@@@'') IS NOT NULL) DROP TABLE @@@OutputTableName@@@'; + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); + EXEC(@StringToExecute); + + SET @OutputServerName = QUOTENAME(CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128))); + SET @OutputDatabaseName = '[tempdb]'; + SET @OutputSchemaName = '[dbo]'; + SET @ValidOutputLocation = 1; + END; + ELSE IF (SUBSTRING(@OutputTableName, 2, 1) = '#') + BEGIN + RAISERROR('Due to the nature of Dymamic SQL, only global (i.e. double pound (##)) temp tables are supported for @OutputTableName', 16, 0); + END; + ELSE IF @OutputDatabaseName IS NOT NULL + AND @OutputSchemaName IS NOT NULL + AND @OutputTableName IS NOT NULL + AND EXISTS ( SELECT * + FROM sys.databases + WHERE QUOTENAME([name]) = @OutputDatabaseName) + BEGIN + SET @ValidOutputLocation = 1; + SET @OutputServerName = QUOTENAME(CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128))); + END; + ELSE IF @OutputDatabaseName IS NOT NULL + AND @OutputSchemaName IS NOT NULL + AND @OutputTableName IS NOT NULL + AND NOT EXISTS ( SELECT * + FROM sys.databases + WHERE QUOTENAME([name]) = @OutputDatabaseName) + BEGIN + RAISERROR('The specified output database was not found on this server', 16, 0); + END; + ELSE + BEGIN + SET @ValidOutputLocation = 0; + END; + END; + + IF (@ValidOutputLocation = 0 AND @OutputType = 'NONE') + BEGIN + RAISERROR('Invalid output location and no output asked',12,1); + RETURN; + END; + + /* @OutputTableName lets us export the results to a permanent table */ + DECLARE @RunID UNIQUEIDENTIFIER; + SET @RunID = NEWID(); + DECLARE @TableExists BIT; + DECLARE @SchemaExists BIT; + DECLARE @TableExistsSql NVARCHAR(MAX); + IF (@ValidOutputLocation = 1 AND COALESCE(@OutputServerName, @OutputDatabaseName, @OutputSchemaName, @OutputTableName) IS NOT NULL) + BEGIN + SET @StringToExecute = + N'SET @SchemaExists = 0; + SET @TableExists = 0; + IF EXISTS(SELECT * FROM @@@OutputServerName@@@.@@@OutputDatabaseName@@@.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''@@@OutputSchemaName@@@'') + SET @SchemaExists = 1 + IF EXISTS (SELECT * FROM @@@OutputServerName@@@.@@@OutputDatabaseName@@@.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''@@@OutputSchemaName@@@'' AND QUOTENAME(TABLE_NAME) = ''@@@OutputTableName@@@'') + BEGIN + SET @TableExists = 1 + IF NOT EXISTS(SELECT * FROM @@@OutputServerName@@@.@@@OutputDatabaseName@@@.INFORMATION_SCHEMA.COLUMNS WHERE QUOTENAME(TABLE_SCHEMA) = ''@@@OutputSchemaName@@@'' + AND QUOTENAME(TABLE_NAME) = ''@@@OutputTableName@@@'' AND QUOTENAME(COLUMN_NAME) = ''[total_forwarded_fetch_count]'') + EXEC @@@OutputServerName@@@.@@@OutputDatabaseName@@@.dbo.sp_executesql N''ALTER TABLE @@@OutputSchemaName@@@.@@@OutputTableName@@@ ADD [total_forwarded_fetch_count] BIGINT'' + END'; + + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputServerName@@@', @OutputServerName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputSchemaName@@@', @OutputSchemaName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); + + EXEC sp_executesql @StringToExecute, N'@TableExists BIT OUTPUT, @SchemaExists BIT OUTPUT', @TableExists OUTPUT, @SchemaExists OUTPUT; + SET @TableExistsSql = + N'IF EXISTS(SELECT * FROM @@@OutputServerName@@@.@@@OutputDatabaseName@@@.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''@@@OutputSchemaName@@@'') + AND NOT EXISTS (SELECT * FROM @@@OutputServerName@@@.@@@OutputDatabaseName@@@.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''@@@OutputSchemaName@@@'' AND QUOTENAME(TABLE_NAME) = ''@@@OutputTableName@@@'') + SET @TableExists = 0 + ELSE + SET @TableExists = 1'; + + SET @TableExistsSql = REPLACE(@TableExistsSql, '@@@OutputServerName@@@', @OutputServerName); + SET @TableExistsSql = REPLACE(@TableExistsSql, '@@@OutputDatabaseName@@@', @OutputDatabaseName); + SET @TableExistsSql = REPLACE(@TableExistsSql, '@@@OutputSchemaName@@@', @OutputSchemaName); + SET @TableExistsSql = REPLACE(@TableExistsSql, '@@@OutputTableName@@@', @OutputTableName); + END - - - - - - - - - - - -ELSE IF @Mode IN (0, 4) /* DIAGNOSE*//* IF @TableName IS NOT NULL, so @TableName must not be null */ -BEGIN; + IF @Mode IN (0, 4) /* DIAGNOSE */ + BEGIN; IF @Mode IN (0, 4) /* DIAGNOSE priorities 1-100 */ BEGIN; RAISERROR(N'@Mode=0 or 4, running rules for priorities 1-100.', 0,1) WITH NOWAIT; @@ -23470,23 +23632,6 @@ BEGIN; - - - - - - - - - - - - - - - - - @@ -24523,27 +24668,7 @@ BEGIN; - - - - - - - - - - - - - - - - - - - - - + RAISERROR(N'Insert a row to help people find help', 0,1) WITH NOWAIT; IF DATEDIFF(MM, @VersionDate, GETDATE()) > 6 BEGIN @@ -24613,65 +24738,345 @@ BEGIN; RAISERROR(N'Returning results.', 0,1) WITH NOWAIT; /*Return results.*/ - IF (@Mode = 0) - BEGIN - IF(@OutputType <> 'NONE') + IF (@ValidOutputLocation = 1 AND COALESCE(@OutputServerName, @OutputDatabaseName, @OutputSchemaName, @OutputTableName) IS NOT NULL) BEGIN - SELECT Priority, ISNULL(br.findings_group,N'') + - CASE WHEN ISNULL(br.finding,N'') <> N'' THEN N': ' ELSE N'' END - + br.finding AS [Finding], - br.[database_name] AS [Database Name], - br.details AS [Details: schema.table.index(indexid)], - br.index_definition AS [Definition: [Property]] ColumnName {datatype maxbytes}], - ISNULL(br.secret_columns,'') AS [Secret Columns], - br.index_usage_summary AS [Usage], - br.index_size_summary AS [Size], - COALESCE(br.more_info,sn.more_info,'') AS [More Info], - br.URL, - COALESCE(br.create_tsql,ts.create_tsql,'') AS [Create TSQL], - br.sample_query_plan AS [Sample Query Plan] - FROM #BlitzIndexResults br - LEFT JOIN #IndexSanity sn ON - br.index_sanity_id=sn.index_sanity_id - LEFT JOIN #IndexCreateTsql ts ON - br.index_sanity_id=ts.index_sanity_id - ORDER BY br.Priority ASC, br.check_id ASC, br.blitz_result_id ASC, br.findings_group ASC - OPTION (RECOMPILE); - END; + IF NOT @SchemaExists = 1 + BEGIN + RAISERROR (N'Invalid schema name, data could not be saved.', 16, 0); + RETURN; + END - END; - ELSE IF (@Mode = 4) - IF(@OutputType <> 'NONE') - BEGIN - SELECT Priority, ISNULL(br.findings_group,N'') + - CASE WHEN ISNULL(br.finding,N'') <> N'' THEN N': ' ELSE N'' END - + br.finding AS [Finding], - br.[database_name] AS [Database Name], - br.details AS [Details: schema.table.index(indexid)], - br.index_definition AS [Definition: [Property]] ColumnName {datatype maxbytes}], - ISNULL(br.secret_columns,'') AS [Secret Columns], - br.index_usage_summary AS [Usage], - br.index_size_summary AS [Size], - COALESCE(br.more_info,sn.more_info,'') AS [More Info], - br.URL, - COALESCE(br.create_tsql,ts.create_tsql,'') AS [Create TSQL], - br.sample_query_plan AS [Sample Query Plan] - FROM #BlitzIndexResults br - LEFT JOIN #IndexSanity sn ON - br.index_sanity_id=sn.index_sanity_id - LEFT JOIN #IndexCreateTsql ts ON - br.index_sanity_id=ts.index_sanity_id - ORDER BY br.Priority ASC, br.check_id ASC, br.blitz_result_id ASC, br.findings_group ASC - OPTION (RECOMPILE); - END; - -END /* End @Mode=0 or 4 (diagnose)*/ - -ELSE IF (@Mode=1) /*Summarize*/ + IF @TableExists = 0 + BEGIN + SET @StringToExecute = + N'CREATE TABLE @@@OutputDatabaseName@@@.@@@OutputSchemaName@@@.@@@OutputTableName@@@ + ( + [id] INT IDENTITY(1,1) NOT NULL, + [run_id] UNIQUEIDENTIFIER, + [run_datetime] DATETIME, + [server_name] NVARCHAR(128), + [priority] INT, + [finding] NVARCHAR(4000), + [database_name] NVARCHAR(128), + [details] NVARCHAR(MAX), + [index_definition] NVARCHAR(MAX), + [secret_columns] NVARCHAR(MAX), + [index_usage_summary] NVARCHAR(MAX), + [index_size_summary] NVARCHAR(MAX), + [more_info] NVARCHAR(MAX), + [url] NVARCHAR(MAX), + [create_tsql] NVARCHAR(MAX), + [sample_query_plan] XML, + CONSTRAINT [PK_ID_@@@RunID@@@] PRIMARY KEY CLUSTERED ([id] ASC) + );'; + + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputSchemaName@@@', @OutputSchemaName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@RunID@@@', @RunID); + + IF @ValidOutputServer = 1 + BEGIN + SET @StringToExecute = REPLACE(@StringToExecute,'''',''''''); + EXEC('EXEC('''+@StringToExecute+''') AT ' + @OutputServerName); + END; + ELSE + BEGIN + EXEC(@StringToExecute); + END; + END; /* @TableExists = 0 */ + + -- Re-check that table now exists (if not we failed creating it) + SET @TableExists = NULL; + EXEC sp_executesql @TableExistsSql, N'@TableExists BIT OUTPUT', @TableExists OUTPUT; + + IF NOT @TableExists = 1 + BEGIN + RAISERROR('Creation of the output table failed.', 16, 0); + RETURN; + END; + + SET @StringToExecute = + N'INSERT @@@OutputServerName@@@.@@@OutputDatabaseName@@@.@@@OutputSchemaName@@@.@@@OutputTableName@@@ + ( + [run_id], + [run_datetime], + [server_name], + [priority], + [finding], + [database_name], + [details], + [index_definition], + [secret_columns], + [index_usage_summary], + [index_size_summary], + [more_info], + [url], + [create_tsql], + [sample_query_plan] + ) + SELECT + ''@@@RunID@@@'', + ''@@@GETDATE@@@'', + ''@@@LocalServerName@@@'', + -- Below should be a copy/paste of the real query + -- Make sure all quotes are escaped + Priority, ISNULL(br.findings_group,N'''') + + CASE WHEN ISNULL(br.finding,N'''') <> N'''' THEN N'': '' ELSE N'''' END + + br.finding AS [Finding], + br.[database_name] AS [Database Name], + br.details AS [Details: schema.table.index(indexid)], + br.index_definition AS [Definition: [Property]] ColumnName {datatype maxbytes}], + ISNULL(br.secret_columns,'''') AS [Secret Columns], + br.index_usage_summary AS [Usage], + br.index_size_summary AS [Size], + COALESCE(br.more_info,sn.more_info,'''') AS [More Info], + br.URL, + COALESCE(br.create_tsql,ts.create_tsql,'''') AS [Create TSQL], + br.sample_query_plan AS [Sample Query Plan] + FROM #BlitzIndexResults br + LEFT JOIN #IndexSanity sn ON + br.index_sanity_id=sn.index_sanity_id + LEFT JOIN #IndexCreateTsql ts ON + br.index_sanity_id=ts.index_sanity_id + ORDER BY br.Priority ASC, br.check_id ASC, br.blitz_result_id ASC, br.findings_group ASC + OPTION (RECOMPILE);'; + + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputServerName@@@', @OutputServerName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputSchemaName@@@', @OutputSchemaName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@RunID@@@', @RunID); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@GETDATE@@@', GETDATE()); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@LocalServerName@@@', CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128))); + EXEC(@StringToExecute); + + END + ELSE + BEGIN + IF(@OutputType <> 'NONE') + BEGIN + SELECT Priority, ISNULL(br.findings_group,N'') + + CASE WHEN ISNULL(br.finding,N'') <> N'' THEN N': ' ELSE N'' END + + br.finding AS [Finding], + br.[database_name] AS [Database Name], + br.details AS [Details: schema.table.index(indexid)], + br.index_definition AS [Definition: [Property]] ColumnName {datatype maxbytes}], + ISNULL(br.secret_columns,'') AS [Secret Columns], + br.index_usage_summary AS [Usage], + br.index_size_summary AS [Size], + COALESCE(br.more_info,sn.more_info,'') AS [More Info], + br.URL, + COALESCE(br.create_tsql,ts.create_tsql,'') AS [Create TSQL], + br.sample_query_plan AS [Sample Query Plan] + FROM #BlitzIndexResults br + LEFT JOIN #IndexSanity sn ON + br.index_sanity_id=sn.index_sanity_id + LEFT JOIN #IndexCreateTsql ts ON + br.index_sanity_id=ts.index_sanity_id + ORDER BY br.Priority ASC, br.check_id ASC, br.blitz_result_id ASC, br.findings_group ASC + OPTION (RECOMPILE); + END; + + END; + + END /* End @Mode=0 or 4 (diagnose)*/ + + + + + + + + + ELSE IF (@Mode=1) /*Summarize*/ BEGIN --This mode is to give some overall stats on the database. - IF(@OutputType <> 'NONE') - BEGIN + IF (@ValidOutputLocation = 1 AND COALESCE(@OutputServerName, @OutputDatabaseName, @OutputSchemaName, @OutputTableName) IS NOT NULL) + BEGIN + + IF NOT @SchemaExists = 1 + BEGIN + RAISERROR (N'Invalid schema name, data could not be saved.', 16, 0); + RETURN; + END + + IF @TableExists = 0 + BEGIN + SET @StringToExecute = + N'CREATE TABLE @@@OutputDatabaseName@@@.@@@OutputSchemaName@@@.@@@OutputTableName@@@ + ( + [id] INT IDENTITY(1,1) NOT NULL, + [run_id] UNIQUEIDENTIFIER, + [run_datetime] DATETIME, + [server_name] NVARCHAR(128), + [database_name] NVARCHAR(128), + [object_count] INT, + [reserved_gb] NUMERIC(29,1), + [reserved_lob_gb] NUMERIC(29,1), + [reserved_row_overflow_gb] NUMERIC(29,1), + [clustered_table_count] INT, + [clustered_table_gb] NUMERIC(29,1), + [nc_index_count] INT, + [nc_index_gb] NUMERIC(29,1), + [table_nc_index_ratio] NUMERIC(29,1), + [heap_count] INT, + [heap_gb] NUMERIC(29,1), + [partioned_table_count] INT, + [partioned_nc_count] INT, + [partioned_gb] NUMERIC(29,1), + [filtered_index_count] INT, + [indexed_view_count] INT, + [max_table_row_count] INT, + [max_table_gb] NUMERIC(29,1), + [max_nc_index_gb] NUMERIC(29,1), + [table_count_over_1gb] INT, + [table_count_over_10gb] INT, + [table_count_over_100gb] INT, + [nc_index_count_over_1gb] INT, + [nc_index_count_over_10gb] INT, + [nc_index_count_over_100gb] INT, + [min_create_date] DATETIME, + [max_create_date] DATETIME, + [max_modify_date] DATETIME, + [display_order] INT, + CONSTRAINT [PK_ID_@@@RunID@@@] PRIMARY KEY CLUSTERED ([id] ASC) + );'; + + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputSchemaName@@@', @OutputSchemaName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@RunID@@@', @RunID); + + IF @ValidOutputServer = 1 + BEGIN + SET @StringToExecute = REPLACE(@StringToExecute,'''',''''''); + EXEC('EXEC('''+@StringToExecute+''') AT ' + @OutputServerName); + END; + ELSE + BEGIN + EXEC(@StringToExecute); + END; + END; /* @TableExists = 0 */ + + -- Re-check that table now exists (if not we failed creating it) + SET @TableExists = NULL; + EXEC sp_executesql @TableExistsSql, N'@TableExists BIT OUTPUT', @TableExists OUTPUT; + + IF NOT @TableExists = 1 + BEGIN + RAISERROR('Creation of the output table failed.', 16, 0); + RETURN; + END; + + SET @StringToExecute = + N'INSERT @@@OutputServerName@@@.@@@OutputDatabaseName@@@.@@@OutputSchemaName@@@.@@@OutputTableName@@@ + ( + [run_id], + [run_datetime], + [server_name], + [database_name], + [object_count], + [reserved_gb], + [reserved_lob_gb], + [reserved_row_overflow_gb], + [clustered_table_count], + [clustered_table_gb], + [nc_index_count], + [nc_index_gb], + [table_nc_index_ratio], + [heap_count], + [heap_gb], + [partioned_table_count], + [partioned_nc_count], + [partioned_gb], + [filtered_index_count], + [indexed_view_count], + [max_table_row_count], + [max_table_gb], + [max_nc_index_gb], + [table_count_over_1gb], + [table_count_over_10gb], + [table_count_over_100gb], + [nc_index_count_over_1gb], + [nc_index_count_over_10gb], + [nc_index_count_over_100gb], + [min_create_date], + [max_create_date], + [max_modify_date], + [display_order] + ) + SELECT ''@@@RunID@@@'', + ''@@@GETDATE@@@'', + ''@@@LocalServerName@@@'', + -- Below should be a copy/paste of the real query + -- Make sure all quotes are escaped + -- NOTE! information line is skipped from output and the query below + -- NOTE! initial columns are not casted to nvarchar due to not outputing informational line + DB_NAME(i.database_id) AS [Database Name], + COUNT(*) AS [Number Objects], + CAST(SUM(sz.total_reserved_MB)/ + 1024. AS NUMERIC(29,1)) AS [All GB], + CAST(SUM(sz.total_reserved_LOB_MB)/ + 1024. AS NUMERIC(29,1)) AS [LOB GB], + CAST(SUM(sz.total_reserved_row_overflow_MB)/ + 1024. AS NUMERIC(29,1)) AS [Row Overflow GB], + SUM(CASE WHEN index_id=1 THEN 1 ELSE 0 END) AS [Clustered Tables], + CAST(SUM(CASE WHEN index_id=1 THEN sz.total_reserved_MB ELSE 0 END) + /1024. AS NUMERIC(29,1)) AS [Clustered Tables GB], + SUM(CASE WHEN index_id NOT IN (0,1) THEN 1 ELSE 0 END) AS [NC Indexes], + CAST(SUM(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) + /1024. AS NUMERIC(29,1)) AS [NC Indexes GB], + CASE WHEN SUM(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) > 0 THEN + CAST(SUM(CASE WHEN index_id IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) + / SUM(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) AS NUMERIC(29,1)) + ELSE 0 END AS [ratio table: NC Indexes], + SUM(CASE WHEN index_id=0 THEN 1 ELSE 0 END) AS [Heaps], + CAST(SUM(CASE WHEN index_id=0 THEN sz.total_reserved_MB ELSE 0 END) + /1024. AS NUMERIC(29,1)) AS [Heaps GB], + SUM(CASE WHEN index_id IN (0,1) AND partition_key_column_name IS NOT NULL THEN 1 ELSE 0 END) AS [Partitioned Tables], + SUM(CASE WHEN index_id NOT IN (0,1) AND partition_key_column_name IS NOT NULL THEN 1 ELSE 0 END) AS [Partitioned NCs], + CAST(SUM(CASE WHEN partition_key_column_name IS NOT NULL THEN sz.total_reserved_MB ELSE 0 END)/1024. AS NUMERIC(29,1)) AS [Partitioned GB], + SUM(CASE WHEN filter_definition <> '''' THEN 1 ELSE 0 END) AS [Filtered Indexes], + SUM(CASE WHEN is_indexed_view=1 THEN 1 ELSE 0 END) AS [Indexed Views], + MAX(total_rows) AS [Max Row Count], + CAST(MAX(CASE WHEN index_id IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) + /1024. AS NUMERIC(29,1)) AS [Max Table GB], + CAST(MAX(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) + /1024. AS NUMERIC(29,1)) AS [Max NC Index GB], + SUM(CASE WHEN index_id IN (0,1) AND sz.total_reserved_MB > 1024 THEN 1 ELSE 0 END) AS [Count Tables > 1GB], + SUM(CASE WHEN index_id IN (0,1) AND sz.total_reserved_MB > 10240 THEN 1 ELSE 0 END) AS [Count Tables > 10GB], + SUM(CASE WHEN index_id IN (0,1) AND sz.total_reserved_MB > 102400 THEN 1 ELSE 0 END) AS [Count Tables > 100GB], + SUM(CASE WHEN index_id NOT IN (0,1) AND sz.total_reserved_MB > 1024 THEN 1 ELSE 0 END) AS [Count NCs > 1GB], + SUM(CASE WHEN index_id NOT IN (0,1) AND sz.total_reserved_MB > 10240 THEN 1 ELSE 0 END) AS [Count NCs > 10GB], + SUM(CASE WHEN index_id NOT IN (0,1) AND sz.total_reserved_MB > 102400 THEN 1 ELSE 0 END) AS [Count NCs > 100GB], + MIN(create_date) AS [Oldest Create Date], + MAX(create_date) AS [Most Recent Create Date], + MAX(modify_date) AS [Most Recent Modify Date], + 1 AS [Display Order] + FROM #IndexSanity AS i + --left join here so we don''t lose disabled nc indexes + LEFT JOIN #IndexSanitySize AS sz + ON i.index_sanity_id=sz.index_sanity_id + GROUP BY DB_NAME(i.database_id) + ORDER BY [Display Order] ASC + OPTION (RECOMPILE);'; + + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputServerName@@@', @OutputServerName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputSchemaName@@@', @OutputSchemaName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@RunID@@@', @RunID); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@GETDATE@@@', GETDATE()); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@LocalServerName@@@', CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128))); + EXEC(@StringToExecute); + + END; /* @ValidOutputLocation = 1 */ + ELSE + BEGIN + IF(@OutputType <> 'NONE') + BEGIN + RAISERROR(N'@Mode=1, we are summarizing.', 0,1) WITH NOWAIT; SELECT DB_NAME(i.database_id) AS [Database Name], @@ -24731,123 +25136,26 @@ ELSE IF (@Mode=1) /*Summarize*/ NULL,NULL,0 AS display_order ORDER BY [Display Order] ASC OPTION (RECOMPILE); - END; - + END; + END; + END; /* End @Mode=1 (summarize)*/ - ELSE IF (@Mode=2) /*Index Detail*/ + + + + + + + + + ELSE IF (@Mode=2) /*Index Detail*/ BEGIN --This mode just spits out all the detail without filters. --This supports slicing AND dicing in Excel RAISERROR(N'@Mode=2, here''s the details on existing indexes.', 0,1) WITH NOWAIT; - - /* Checks if @OutputServerName is populated with a valid linked server, and that the database name specified is valid */ - DECLARE @ValidOutputServer BIT; - DECLARE @ValidOutputLocation BIT; - DECLARE @LinkedServerDBCheck NVARCHAR(2000); - DECLARE @ValidLinkedServerDB INT; - DECLARE @tmpdbchk TABLE (cnt INT); - - IF @OutputServerName IS NOT NULL - BEGIN - IF (SUBSTRING(@OutputTableName, 2, 1) = '#') - BEGIN - RAISERROR('Due to the nature of temporary tables, outputting to a linked server requires a permanent table.', 16, 0); - END; - ELSE IF EXISTS (SELECT server_id FROM sys.servers WHERE QUOTENAME([name]) = @OutputServerName) - BEGIN - SET @LinkedServerDBCheck = 'SELECT 1 WHERE EXISTS (SELECT * FROM '+@OutputServerName+'.master.sys.databases WHERE QUOTENAME([name]) = '''+@OutputDatabaseName+''')'; - INSERT INTO @tmpdbchk EXEC sys.sp_executesql @LinkedServerDBCheck; - SET @ValidLinkedServerDB = (SELECT COUNT(*) FROM @tmpdbchk); - IF (@ValidLinkedServerDB > 0) - BEGIN - SET @ValidOutputServer = 1; - SET @ValidOutputLocation = 1; - END; - ELSE - RAISERROR('The specified database was not found on the output server', 16, 0); - END; - ELSE - BEGIN - RAISERROR('The specified output server was not found', 16, 0); - END; - END; - ELSE - BEGIN - IF (SUBSTRING(@OutputTableName, 2, 2) = '##') - BEGIN - SET @StringToExecute = N' IF (OBJECT_ID(''[tempdb].[dbo].@@@OutputTableName@@@'') IS NOT NULL) DROP TABLE @@@OutputTableName@@@'; - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); - EXEC(@StringToExecute); - - SET @OutputServerName = QUOTENAME(CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128))); - SET @OutputDatabaseName = '[tempdb]'; - SET @OutputSchemaName = '[dbo]'; - SET @ValidOutputLocation = 1; - END; - ELSE IF (SUBSTRING(@OutputTableName, 2, 1) = '#') - BEGIN - RAISERROR('Due to the nature of Dymamic SQL, only global (i.e. double pound (##)) temp tables are supported for @OutputTableName', 16, 0); - END; - ELSE IF @OutputDatabaseName IS NOT NULL - AND @OutputSchemaName IS NOT NULL - AND @OutputTableName IS NOT NULL - AND EXISTS ( SELECT * - FROM sys.databases - WHERE QUOTENAME([name]) = @OutputDatabaseName) - BEGIN - SET @ValidOutputLocation = 1; - SET @OutputServerName = QUOTENAME(CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128))); - END; - ELSE IF @OutputDatabaseName IS NOT NULL - AND @OutputSchemaName IS NOT NULL - AND @OutputTableName IS NOT NULL - AND NOT EXISTS ( SELECT * - FROM sys.databases - WHERE QUOTENAME([name]) = @OutputDatabaseName) - BEGIN - RAISERROR('The specified output database was not found on this server', 16, 0); - END; - ELSE - BEGIN - SET @ValidOutputLocation = 0; - END; - END; - - IF (@ValidOutputLocation = 0 AND @OutputType = 'NONE') - BEGIN - RAISERROR('Invalid output location and no output asked',12,1); - RETURN; - END; - - /* @OutputTableName lets us export the results to a permanent table */ - DECLARE @RunID UNIQUEIDENTIFIER; - SET @RunID = NEWID(); - IF (@ValidOutputLocation = 1 AND COALESCE(@OutputServerName, @OutputDatabaseName, @OutputSchemaName, @OutputTableName) IS NOT NULL) BEGIN - DECLARE @TableExists BIT; - DECLARE @SchemaExists BIT; - SET @StringToExecute = - N'SET @SchemaExists = 0; - SET @TableExists = 0; - IF EXISTS(SELECT * FROM @@@OutputServerName@@@.@@@OutputDatabaseName@@@.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''@@@OutputSchemaName@@@'') - SET @SchemaExists = 1 - IF EXISTS (SELECT * FROM @@@OutputServerName@@@.@@@OutputDatabaseName@@@.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''@@@OutputSchemaName@@@'' AND QUOTENAME(TABLE_NAME) = ''@@@OutputTableName@@@'') - BEGIN - SET @TableExists = 1 - IF NOT EXISTS(SELECT * FROM @@@OutputServerName@@@.@@@OutputDatabaseName@@@.INFORMATION_SCHEMA.COLUMNS WHERE QUOTENAME(TABLE_SCHEMA) = ''@@@OutputSchemaName@@@'' - AND QUOTENAME(TABLE_NAME) = ''@@@OutputTableName@@@'' AND QUOTENAME(COLUMN_NAME) = ''[total_forwarded_fetch_count]'') - EXEC @@@OutputServerName@@@.@@@OutputDatabaseName@@@.dbo.sp_executesql N''ALTER TABLE @@@OutputSchemaName@@@.@@@OutputTableName@@@ ADD [total_forwarded_fetch_count] BIGINT'' - END'; - - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputServerName@@@', @OutputServerName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputSchemaName@@@', @OutputSchemaName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); - - EXEC sp_executesql @StringToExecute, N'@TableExists BIT OUTPUT, @SchemaExists BIT OUTPUT', @TableExists OUTPUT, @SchemaExists OUTPUT; - IF @SchemaExists = 1 BEGIN IF @TableExists = 0 @@ -24948,20 +25256,10 @@ ELSE IF (@Mode=1) /*Summarize*/ END; END; /* @TableExists = 0 */ - SET @StringToExecute = - N'IF EXISTS(SELECT * FROM @@@OutputServerName@@@.@@@OutputDatabaseName@@@.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''@@@OutputSchemaName@@@'') - AND NOT EXISTS (SELECT * FROM @@@OutputServerName@@@.@@@OutputDatabaseName@@@.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''@@@OutputSchemaName@@@'' AND QUOTENAME(TABLE_NAME) = ''@@@OutputTableName@@@'') - SET @TableExists = 0 - ELSE - SET @TableExists = 1'; - + + -- Re-check that table now exists (if not we failed creating it) SET @TableExists = NULL; - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputServerName@@@', @OutputServerName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputSchemaName@@@', @OutputSchemaName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); - - EXEC sp_executesql @StringToExecute, N'@TableExists BIT OUTPUT', @TableExists OUTPUT; + EXEC sp_executesql @TableExistsSql, N'@TableExists BIT OUTPUT', @TableExists OUTPUT; IF @TableExists = 1 BEGIN @@ -25289,10 +25587,167 @@ ELSE IF (@Mode=1) /*Summarize*/ END; END; /* End @Mode=2 (index detail)*/ + + + + + + + + ELSE IF (@Mode=3) /*Missing index Detail*/ BEGIN - IF(@OutputType <> 'NONE') - BEGIN; + IF (@ValidOutputLocation = 1 AND COALESCE(@OutputServerName, @OutputDatabaseName, @OutputSchemaName, @OutputTableName) IS NOT NULL) + BEGIN + + IF NOT @SchemaExists = 1 + BEGIN + RAISERROR (N'Invalid schema name, data could not be saved.', 16, 0); + RETURN; + END + + IF @TableExists = 0 + BEGIN + SET @StringToExecute = + N'CREATE TABLE @@@OutputDatabaseName@@@.@@@OutputSchemaName@@@.@@@OutputTableName@@@ + ( + [id] INT IDENTITY(1,1) NOT NULL, + [run_id] UNIQUEIDENTIFIER, + [run_datetime] DATETIME, + [server_name] NVARCHAR(128), + [database_name] NVARCHAR(128), + [schema_name] NVARCHAR(128), + [table_name] NVARCHAR(128), + [magic_benefit_number] BIGINT, + [missing_index_details] NVARCHAR(MAX), + [avg_total_user_cost] NUMERIC(29,4), + [avg_user_impact] NUMERIC(29,1), + [user_seeks] BIGINT, + [user_scans] BIGINT, + [unique_compiles] BIGINT, + [equality_columns_with_data_type] NVARCHAR(MAX), + [inequality_columns_with_data_type] NVARCHAR(MAX), + [included_columns_with_data_type] NVARCHAR(MAX), + [index_estimated_impact] NVARCHAR(256), + [create_tsql] NVARCHAR(MAX), + [more_info] NVARCHAR(600), + [display_order] INT, + [is_low] BIT, + [sample_query_plan] XML, + CONSTRAINT [PK_ID_@@@RunID@@@] PRIMARY KEY CLUSTERED ([id] ASC) + );'; + + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputSchemaName@@@', @OutputSchemaName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@RunID@@@', @RunID); + + IF @ValidOutputServer = 1 + BEGIN + SET @StringToExecute = REPLACE(@StringToExecute,'''',''''''); + EXEC('EXEC('''+@StringToExecute+''') AT ' + @OutputServerName); + END; + ELSE + BEGIN + EXEC(@StringToExecute); + END; + END; /* @TableExists = 0 */ + + -- Re-check that table now exists (if not we failed creating it) + SET @TableExists = NULL; + EXEC sp_executesql @TableExistsSql, N'@TableExists BIT OUTPUT', @TableExists OUTPUT; + + IF NOT @TableExists = 1 + BEGIN + RAISERROR('Creation of the output table failed.', 16, 0); + RETURN; + END; + SET @StringToExecute = + N'WITH create_date AS ( + SELECT i.database_id, + i.schema_name, + i.[object_id], + ISNULL(NULLIF(MAX(DATEDIFF(DAY, i.create_date, SYSDATETIME())), 0), 1) AS create_days + FROM #IndexSanity AS i + GROUP BY i.database_id, i.schema_name, i.object_id + ) + INSERT @@@OutputServerName@@@.@@@OutputDatabaseName@@@.@@@OutputSchemaName@@@.@@@OutputTableName@@@ + ( + [run_id], + [run_datetime], + [server_name], + [database_name], + [schema_name], + [table_name], + [magic_benefit_number], + [missing_index_details], + [avg_total_user_cost], + [avg_user_impact], + [user_seeks], + [user_scans], + [unique_compiles], + [equality_columns_with_data_type], + [inequality_columns_with_data_type], + [included_columns_with_data_type], + [index_estimated_impact], + [create_tsql], + [more_info], + [display_order], + [is_low], + [sample_query_plan] + ) + SELECT ''@@@RunID@@@'', + ''@@@GETDATE@@@'', + ''@@@LocalServerName@@@'', + -- Below should be a copy/paste of the real query + -- Make sure all quotes are escaped + -- NOTE! information line is skipped from output and the query below + -- NOTE! CTE block is above insert in the copied SQL + mi.database_name AS [Database Name], + mi.[schema_name] AS [Schema], + mi.table_name AS [Table], + CAST((mi.magic_benefit_number / CASE WHEN cd.create_days < @DaysUptime THEN cd.create_days ELSE @DaysUptime END) AS BIGINT) + AS [Magic Benefit Number], + mi.missing_index_details AS [Missing Index Details], + mi.avg_total_user_cost AS [Avg Query Cost], + mi.avg_user_impact AS [Est Index Improvement], + mi.user_seeks AS [Seeks], + mi.user_scans AS [Scans], + mi.unique_compiles AS [Compiles], + mi.equality_columns_with_data_type AS [Equality Columns], + mi.inequality_columns_with_data_type AS [Inequality Columns], + mi.included_columns_with_data_type AS [Included Columns], + mi.index_estimated_impact AS [Estimated Impact], + mi.create_tsql AS [Create TSQL], + mi.more_info AS [More Info], + 1 AS [Display Order], + mi.is_low, + mi.sample_query_plan AS [Sample Query Plan] + FROM #MissingIndexes AS mi + LEFT JOIN create_date AS cd + ON mi.[object_id] = cd.object_id + AND mi.database_id = cd.database_id + AND mi.schema_name = cd.schema_name + /* Minimum benefit threshold = 100k/day of uptime OR since table creation date, whichever is lower*/ + WHERE @ShowAllMissingIndexRequests=1 + OR (mi.magic_benefit_number / CASE WHEN cd.create_days < @DaysUptime THEN cd.create_days ELSE @DaysUptime END) >= 100000 + ORDER BY [Display Order] ASC, [Magic Benefit Number] DESC + OPTION (RECOMPILE);'; + + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputServerName@@@', @OutputServerName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputSchemaName@@@', @OutputSchemaName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@RunID@@@', @RunID); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@GETDATE@@@', GETDATE()); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@LocalServerName@@@', CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128))); + EXEC sp_executesql @StringToExecute, N'@DaysUptime NUMERIC(23,2), @ShowAllMissingIndexRequests BIT', @DaysUptime = @DaysUptime, @ShowAllMissingIndexRequests = @ShowAllMissingIndexRequests; + + END; /* @ValidOutputLocation = 1 */ + ELSE + BEGIN + IF(@OutputType <> 'NONE') + BEGIN WITH create_date AS ( SELECT i.database_id, i.schema_name, @@ -25341,21 +25796,26 @@ ELSE IF (@Mode=1) /*Summarize*/ NULL, NULL, NULL, NULL, 0 AS [Display Order], NULL AS is_low, NULL ORDER BY [Display Order] ASC, [Magic Benefit Number] DESC OPTION (RECOMPILE); - END; + END; + + + IF (@BringThePain = 1 + AND @DatabaseName IS NOT NULL + AND @GetAllDatabases = 0) + + BEGIN + EXEC sp_BlitzCache @SortOrder = 'sp_BlitzIndex', @DatabaseName = @DatabaseName, @BringThePain = 1, @QueryFilter = 'statement', @HideSummary = 1; + END; + + END; - IF (@BringThePain = 1 - AND @DatabaseName IS NOT NULL - AND @GetAllDatabases = 0) - BEGIN - EXEC sp_BlitzCache @SortOrder = 'sp_BlitzIndex', @DatabaseName = @DatabaseName, @BringThePain = 1, @QueryFilter = 'statement', @HideSummary = 1; - - END; END; /* End @Mode=3 (index detail)*/ +END /* End @TableName IS NULL (mode 0/1/2/3/4) */ END TRY BEGIN CATCH @@ -25375,1817 +25835,3488 @@ BEGIN CATCH END CATCH; GO IF OBJECT_ID('dbo.sp_BlitzLock') IS NULL - EXEC ('CREATE PROCEDURE dbo.sp_BlitzLock AS RETURN 0;'); +BEGIN + EXEC ('CREATE PROCEDURE dbo.sp_BlitzLock AS RETURN 0;'); +END; GO -ALTER PROCEDURE dbo.sp_BlitzLock +ALTER PROCEDURE + dbo.sp_BlitzLock ( - @Top INT = 2147483647, - @DatabaseName NVARCHAR(256) = NULL, - @StartDate DATETIME = '19000101', - @EndDate DATETIME = '99991231', - @ObjectName NVARCHAR(1000) = NULL, - @StoredProcName NVARCHAR(1000) = NULL, - @AppName NVARCHAR(256) = NULL, - @HostName NVARCHAR(256) = NULL, - @LoginName NVARCHAR(256) = NULL, - @EventSessionPath VARCHAR(256) = 'system_health*.xel', - @VictimsOnly BIT = 0, - @Debug BIT = 0, - @Help BIT = 0, - @Version VARCHAR(30) = NULL OUTPUT, - @VersionDate DATETIME = NULL OUTPUT, - @VersionCheckMode BIT = 0, - @OutputDatabaseName NVARCHAR(256) = NULL , - @OutputSchemaName NVARCHAR(256) = 'dbo' , --ditto as below - @OutputTableName NVARCHAR(256) = 'BlitzLock', --put a standard here no need to check later in the script - @ExportToExcel BIT = 0 + @DatabaseName sysname = NULL, + @StartDate datetime = NULL, + @EndDate datetime = NULL, + @ObjectName nvarchar(1024) = NULL, + @StoredProcName nvarchar(1024) = NULL, + @AppName sysname = NULL, + @HostName sysname = NULL, + @LoginName sysname = NULL, + @EventSessionName sysname = N'system_health', + @TargetSessionType sysname = NULL, + @VictimsOnly bit = 0, + @Debug bit = 0, + @Help bit = 0, + @Version varchar(30) = NULL OUTPUT, + @VersionDate datetime = NULL OUTPUT, + @VersionCheckMode bit = 0, + @OutputDatabaseName sysname = NULL, + @OutputSchemaName sysname = N'dbo', /*ditto as below*/ + @OutputTableName sysname = N'BlitzLock', /*put a standard here no need to check later in the script*/ + @ExportToExcel bit = 0 ) WITH RECOMPILE AS BEGIN + SET STATISTICS XML OFF; + SET NOCOUNT, XACT_ABORT ON; + SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SET NOCOUNT ON; -SET STATISTICS XML OFF; -SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + SELECT @Version = '8.12', @VersionDate = '20221213'; -SELECT @Version = '8.11', @VersionDate = '20221013'; + IF @VersionCheckMode = 1 + BEGIN + RETURN; + END; + IF @Help = 1 + BEGIN + PRINT N' + /* + sp_BlitzLock from http://FirstResponderKit.org -IF(@VersionCheckMode = 1) -BEGIN - RETURN; -END; - IF @Help = 1 - BEGIN - PRINT ' - /* - sp_BlitzLock from http://FirstResponderKit.org - - This script checks for and analyzes deadlocks from the system health session or a custom extended event path + This script checks for and analyzes deadlocks from the system health session or a custom extended event path - Variables you can use: - @Top: Use if you want to limit the number of deadlocks to return. - This is ordered by event date ascending + Variables you can use: - @DatabaseName: If you want to filter to a specific database + @DatabaseName: If you want to filter to a specific database - @StartDate: The date you want to start searching on. + @StartDate: The date you want to start searching on, defaults to last 7 days - @EndDate: The date you want to stop searching on. + @EndDate: The date you want to stop searching on, defaults to current date - @ObjectName: If you want to filter to a specific able. - The object name has to be fully qualified ''Database.Schema.Table'' + @ObjectName: If you want to filter to a specific able. + The object name has to be fully qualified ''Database.Schema.Table'' - @StoredProcName: If you want to search for a single stored proc - The proc name has to be fully qualified ''Database.Schema.Sproc'' - - @AppName: If you want to filter to a specific application - - @HostName: If you want to filter to a specific host - - @LoginName: If you want to filter to a specific login + @StoredProcName: If you want to search for a single stored proc + The proc name has to be fully qualified ''Database.Schema.Sproc'' - @EventSessionPath: If you want to point this at an XE session rather than the system health session. - - @OutputDatabaseName: If you want to output information to a specific database - @OutputSchemaName: Specify a schema name to output information to a specific Schema - @OutputTableName: Specify table name to to output information to a specific table - - To learn more, visit http://FirstResponderKit.org where you can download new - versions for free, watch training videos on how it works, get more info on - the findings, contribute your own code, and more. + @AppName: If you want to filter to a specific application - Known limitations of this version: - - Only SQL Server 2012 and newer is supported - - If your tables have weird characters in them (https://en.wikipedia.org/wiki/List_of_XML_and_HTML_character_entity_references) you may get errors trying to parse the XML. - I took a long look at this one, and: - 1) Trying to account for all the weird places these could crop up is a losing effort. - 2) Replace is slow af on lots of XML. + @HostName: If you want to filter to a specific host + @LoginName: If you want to filter to a specific login + @EventSessionName: If you want to point this at an XE session rather than the system health session. + @TargetSessionType: Can be ''ring_buffer'' or ''event_file''. Leave NULL to auto-detect. - Unknown limitations of this version: - - None. (If we knew them, they would be known. Duh.) + @OutputDatabaseName: If you want to output information to a specific database + + @OutputSchemaName: Specify a schema name to output information to a specific Schema + + @OutputTableName: Specify table name to to output information to a specific table + To learn more, visit http://FirstResponderKit.org where you can download new + versions for free, watch training videos on how it works, get more info on + the findings, contribute your own code, and more. + + Known limitations of this version: + - Only SQL Server 2012 and newer is supported + - If your tables have weird characters in them (https://en.wikipedia.org/wiki/List_of_xml_and_HTML_character_entity_references) you may get errors trying to parse the xml. + I took a long look at this one, and: + 1) Trying to account for all the weird places these could crop up is a losing effort. + 2) Replace is slow af on lots of xml. + + Unknown limitations of this version: + - None. (If we knew them, they would be known. Duh.) MIT License - - Copyright (c) 2021 Brent Ozar Unlimited - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: + Copyright (c) 2022 Brent Ozar Unlimited - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE. + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. - */'; - RETURN; - END; /* @Help = 1 */ - - DECLARE @ProductVersion NVARCHAR(128), - @ProductVersionMajor FLOAT, - @ProductVersionMinor INT, - @ObjectFullName NVARCHAR(2000); + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + */'; - SET @ProductVersion = CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)); + RETURN; + END; /* @Help = 1 */ + + /*Declare local variables used in the procudure*/ + DECLARE + @DatabaseId int = + DB_ID(@DatabaseName), + @ProductVersion nvarchar(128) = + CAST(SERVERPROPERTY('ProductVersion') AS nvarchar(128)), + @ProductVersionMajor float = + SUBSTRING + ( + CAST(SERVERPROPERTY('ProductVersion') AS nvarchar(128)), + 1, + CHARINDEX('.', CAST(SERVERPROPERTY('ProductVersion') AS nvarchar(128))) + 1 + ), + @ProductVersionMinor int = + PARSENAME + ( + CONVERT + ( + varchar(32), + CAST(SERVERPROPERTY('ProductVersion') AS nvarchar(128)) + ), + 2 + ), + @ObjectFullName nvarchar(MAX) = N'', + @Azure bit = + CASE + WHEN + ( + SELECT + SERVERPROPERTY('EDITION') + ) = 'SQL Azure' + THEN 1 + ELSE 0 + END, + @RDS bit = + CASE + WHEN LEFT(CAST(SERVERPROPERTY('ComputerNamePhysicalNetBIOS') AS varchar(8000)), 8) <> 'EC2AMAZ-' + AND LEFT(CAST(SERVERPROPERTY('MachineName') AS varchar(8000)), 8) <> 'EC2AMAZ-' + AND DB_ID('rdsadmin') IS NULL + THEN 0 + ELSE 1 + END, + @d varchar(40) = '', + @StringToExecute nvarchar(4000) = N'', + @StringToExecuteParams nvarchar(500) = N'', + @r sysname = NULL, + @OutputTableFindings nvarchar(100) = N'[BlitzLockFindings]', + @DeadlockCount int = 0, + @ServerName sysname = @@SERVERNAME, + @OutputDatabaseCheck bit = -1, + @SessionId int = 0, + @TargetSessionId int = 0, + @FileName nvarchar(4000) = N'', + @inputbuf_bom nvarchar(1) = CONVERT(nvarchar(1), 0x0a00, 0), + @deadlock_result nvarchar(MAX) = N''; + + /*Temporary objects used in the procedure*/ + DECLARE + @sysAssObjId AS table + ( + database_id int, + partition_id bigint, + schema_name sysname, + table_name sysname + ); + + CREATE TABLE + #x + ( + x xml NOT NULL + DEFAULT N'x' + ); - SELECT @ProductVersionMajor = SUBSTRING(@ProductVersion, 1, CHARINDEX('.', @ProductVersion) + 1), - @ProductVersionMinor = PARSENAME(CONVERT(VARCHAR(32), @ProductVersion), 2); + CREATE TABLE + #deadlock_data + ( + deadlock_xml xml NOT NULL + DEFAULT N'x' + ); + + CREATE TABLE + #t + ( + id int NOT NULL + ); + CREATE TABLE + #deadlock_findings + ( + id int IDENTITY PRIMARY KEY, + check_id int NOT NULL, + database_name nvarchar(256), + object_name nvarchar(1000), + finding_group nvarchar(100), + finding nvarchar(4000) + ); - IF @ProductVersionMajor < 11.0 + /*Set these to some sane defaults if NULLs are passed in*/ + /*Normally I'd hate this, but we RECOMPILE everything*/ + SELECT + @StartDate = + CASE + WHEN @StartDate IS NULL + THEN + DATEADD + ( + MINUTE, + DATEDIFF + ( + MINUTE, + SYSDATETIME(), + GETUTCDATE() + ), + ISNULL + ( + @StartDate, + DATEADD + ( + DAY, + -7, + SYSDATETIME() + ) + ) + ) + ELSE @StartDate + END, + @EndDate = + CASE + WHEN @EndDate IS NULL + THEN + DATEADD + ( + MINUTE, + DATEDIFF + ( + MINUTE, + SYSDATETIME(), + GETUTCDATE() + ), + ISNULL + ( + @EndDate, + SYSDATETIME() + ) + ) + ELSE @EndDate + END; + + IF @OutputDatabaseName IS NOT NULL + BEGIN /*IF databaseName is set, do some sanity checks and put [] around def.*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('@OutputDatabaseName set to %s, checking validity at %s', 0, 1, @OutputDatabaseName, @d) WITH NOWAIT; + + IF NOT EXISTS + ( + SELECT + 1/0 + FROM sys.databases AS d + WHERE d.name = @OutputDatabaseName + ) /*If database is invalid raiserror and set bitcheck*/ + BEGIN + RAISERROR('Database Name (%s) for output of table is invalid please, Output to Table will not be performed', 0, 1, @OutputDatabaseName) WITH NOWAIT; + SET @OutputDatabaseCheck = -1; /* -1 invalid/false, 0 = good/true */ + END; + ELSE + BEGIN + SET @OutputDatabaseCheck = 0; + + SELECT + @StringToExecute = + N'SELECT @r = o.name FROM ' + + @OutputDatabaseName + + N'.sys.objects AS o WHERE o.type_desc = N''USER_TABLE'' AND o.name = ' + + QUOTENAME + ( + @OutputTableName, + N'''' + ) + + N' AND o.schema_id = SCHEMA_ID(' + + QUOTENAME + ( + @OutputSchemaName, + N'''' + ) + + N');', + @StringToExecuteParams = + N'@r sysname OUTPUT'; + + IF @Debug = 1 BEGIN PRINT @StringToExecute; END; + EXEC sys.sp_executesql + @StringToExecute, + @StringToExecuteParams, + @r OUTPUT; + + IF @Debug = 1 BEGIN - RAISERROR( - 'sp_BlitzLock will throw a bunch of angry errors on versions of SQL Server earlier than 2012.', - 0, - 1) WITH NOWAIT; - RETURN; + RAISERROR('@r is set to: %s for schema name %s and table name %s', 0, 1, @r, @OutputSchemaName, @OutputTableName) WITH NOWAIT; END; - - IF ((SELECT SERVERPROPERTY ('EDITION')) = 'SQL Azure' - AND - LOWER(@EventSessionPath) NOT LIKE 'http%') + + /*protection spells*/ + SELECT + @ObjectFullName = + QUOTENAME(@OutputDatabaseName) + + N'.' + + QUOTENAME(@OutputSchemaName) + + N'.' + + QUOTENAME(@OutputTableName), + @OutputDatabaseName = + QUOTENAME(@OutputDatabaseName), + @OutputTableName = + QUOTENAME(@OutputTableName), + @OutputSchemaName = + QUOTENAME(@OutputSchemaName); + + IF (@r IS NOT NULL) /*if it is not null, there is a table, so check for newly added columns*/ BEGIN - RAISERROR( - 'The default storage path doesn''t work in Azure SQLDB/Managed instances. -You need to use an Azure storage account, and the path has to look like this: https://StorageAccount.blob.core.windows.net/Container/FileName.xel', - 0, - 1) WITH NOWAIT; - RETURN; + /* If the table doesn't have the new spid column, add it. See Github #3101. */ + SET @StringToExecute = + N'IF NOT EXISTS (SELECT 1/0 FROM ' + + @OutputDatabaseName + + N'.sys.all_columns AS o WHERE o.object_id = (OBJECT_ID(''' + + @ObjectFullName + + N''')) AND o.name = N''spid'') + /*Add spid column*/ + ALTER TABLE ' + + @ObjectFullName + + N' ADD spid smallint NULL;'; + + IF @Debug = 1 BEGIN PRINT @StringToExecute; END; + EXEC sys.sp_executesql + @StringToExecute; + + /* If the table doesn't have the new wait_resource column, add it. See Github #3101. */ + SET @StringToExecute = + N'IF NOT EXISTS (SELECT 1/0 FROM ' + + @OutputDatabaseName + + N'.sys.all_columns AS o WHERE o.object_id = (OBJECT_ID(''' + + @ObjectFullName + + N''')) AND o.name = N''wait_resource'') + /*Add wait_resource column*/ + ALTER TABLE ' + + @ObjectFullName + + N' ADD wait_resource nvarchar(MAX) NULL;'; + + IF @Debug = 1 BEGIN PRINT @StringToExecute; END; + EXEC sys.sp_executesql + @StringToExecute; + + /* If the table doesn't have the new client option column, add it. See Github #3101. */ + SET @StringToExecute = + N'IF NOT EXISTS (SELECT 1/0 FROM ' + + @OutputDatabaseName + + N'.sys.all_columns AS o WHERE o.object_id = (OBJECT_ID(''' + + @ObjectFullName + + N''')) AND o.name = N''client_option_1'') + /*Add wait_resource column*/ + ALTER TABLE ' + + @ObjectFullName + + N' ADD client_option_1 nvarchar(8000) NULL;'; + + IF @Debug = 1 BEGIN PRINT @StringToExecute; END; + EXEC sys.sp_executesql + @StringToExecute; + + /* If the table doesn't have the new client option column, add it. See Github #3101. */ + SET @StringToExecute = + N'IF NOT EXISTS (SELECT 1/0 FROM ' + + @OutputDatabaseName + + N'.sys.all_columns AS o WHERE o.object_id = (OBJECT_ID(''' + + @ObjectFullName + + N''')) AND o.name = N''client_option_2'') + /*Add wait_resource column*/ + ALTER TABLE ' + + @ObjectFullName + + N' ADD client_option_2 nvarchar(8000) NULL;'; + + IF @Debug = 1 BEGIN PRINT @StringToExecute; END; + EXEC sys.sp_executesql + @StringToExecute; + + /* If the table doesn't have the new lock mode column, add it. See Github #3101. */ + SET @StringToExecute = + N'IF NOT EXISTS (SELECT 1/0 FROM ' + + @OutputDatabaseName + + N'.sys.all_columns AS o WHERE o.object_id = (OBJECT_ID(''' + + @ObjectFullName + + N''')) AND o.name = N''lock_mode'') + /*Add wait_resource column*/ + ALTER TABLE ' + + @ObjectFullName + + N' ADD lock_mode nvarchar(256) NULL;'; + + IF @Debug = 1 BEGIN PRINT @StringToExecute; END; + EXEC sys.sp_executesql + @StringToExecute; + + /* If the table doesn't have the new status column, add it. See Github #3101. */ + SET @StringToExecute = + N'IF NOT EXISTS (SELECT 1/0 FROM ' + + @OutputDatabaseName + + N'.sys.all_columns AS o WHERE o.object_id = (OBJECT_ID(''' + + @ObjectFullName + + N''')) AND o.name = N''status'') + /*Add wait_resource column*/ + ALTER TABLE ' + + @ObjectFullName + + N' ADD status nvarchar(256) NULL;'; + + IF @Debug = 1 BEGIN PRINT @StringToExecute; END; + EXEC sys.sp_executesql + @StringToExecute; + END; + ELSE /* end if @r is not null. if it is null there is no table, create it from above execution */ + BEGIN + SELECT + @StringToExecute = + N'USE ' + + @OutputDatabaseName + + N'; + CREATE TABLE ' + + @OutputSchemaName + + N'.' + + @OutputTableName + + N' ( + ServerName nvarchar(256), + deadlock_type nvarchar(256), + event_date datetime, + database_name nvarchar(256), + spid smallint, + deadlock_group nvarchar(256), + query xml, + object_names xml, + isolation_level nvarchar(256), + owner_mode nvarchar(256), + waiter_mode nvarchar(256), + lock_mode nvarchar(256), + transaction_count bigint, + client_option_1 varchar(2000), + client_option_2 varchar(2000), + login_name nvarchar(256), + host_name nvarchar(256), + client_app nvarchar(1024), + wait_time bigint, + wait_resource nvarchar(max), + priority smallint, + log_used bigint, + last_tran_started datetime, + last_batch_started datetime, + last_batch_completed datetime, + transaction_name nvarchar(256), + status nvarchar(256), + owner_waiter_type nvarchar(256), + owner_activity nvarchar(256), + owner_waiter_activity nvarchar(256), + owner_merging nvarchar(256), + owner_spilling nvarchar(256), + owner_waiting_to_close nvarchar(256), + waiter_waiter_type nvarchar(256), + waiter_owner_activity nvarchar(256), + waiter_waiter_activity nvarchar(256), + waiter_merging nvarchar(256), + waiter_spilling nvarchar(256), + waiter_waiting_to_close nvarchar(256), + deadlock_graph xml + )'; + + IF @Debug = 1 BEGIN PRINT @StringToExecute; END; + EXEC sys.sp_executesql + @StringToExecute; + + /*table created.*/ + SELECT + @StringToExecute = + N'SELECT @r = o.name FROM ' + + @OutputDatabaseName + + N'.sys.objects AS o + WHERE o.type_desc = N''USER_TABLE'' + AND o.name = N''BlitzLockFindings''', + @StringToExecuteParams = + N'@r sysname OUTPUT'; + + IF @Debug = 1 BEGIN PRINT @StringToExecute; END; + EXEC sys.sp_executesql + @StringToExecute, + @StringToExecuteParams, + @r OUTPUT; + + IF (@r IS NULL) /*if table does not exist*/ + BEGIN + SELECT + @OutputTableFindings = + QUOTENAME(N'BlitzLockFindings'), + @StringToExecute = + N'USE ' + + @OutputDatabaseName + + N'; + CREATE TABLE ' + + @OutputSchemaName + + N'.' + + @OutputTableFindings + + N' ( + ServerName nvarchar(256), + check_id INT, + database_name nvarchar(256), + object_name nvarchar(1000), + finding_group nvarchar(100), + finding nvarchar(4000) + );'; + + IF @Debug = 1 BEGIN PRINT @StringToExecute; END; + EXEC sys.sp_executesql + @StringToExecute; + END; END; + /*create synonym for deadlockfindings.*/ + IF EXISTS + ( + SELECT + 1/0 + FROM sys.objects AS o + WHERE o.name = N'DeadlockFindings' + AND o.type_desc = N'SYNONYM' + ) + BEGIN + RAISERROR('Found synonym DeadlockFindings, dropping', 0, 1) WITH NOWAIT; + DROP SYNONYM DeadlockFindings; + END; - IF @Top IS NULL - SET @Top = 2147483647; + RAISERROR('Creating synonym DeadlockFindings', 0, 1) WITH NOWAIT; + SET @StringToExecute = + N'CREATE SYNONYM DeadlockFindings FOR ' + + @OutputDatabaseName + + N'.' + + @OutputSchemaName + + N'.' + + @OutputTableFindings; + + IF @Debug = 1 BEGIN PRINT @StringToExecute; END; + EXEC sys.sp_executesql + @StringToExecute; + + /*create synonym for deadlock table.*/ + IF EXISTS + ( + SELECT + 1/0 + FROM sys.objects AS o + WHERE o.name = N'DeadLockTbl' + AND o.type_desc = N'SYNONYM' + ) + BEGIN + RAISERROR('Found synonym DeadLockTbl, dropping', 0, 1) WITH NOWAIT; + DROP SYNONYM DeadLockTbl; + END; - IF @StartDate IS NULL - SET @StartDate = '19000101'; + RAISERROR('Creating synonym DeadLockTbl', 0, 1) WITH NOWAIT; + SET @StringToExecute = + N'CREATE SYNONYM DeadLockTbl FOR ' + + @OutputDatabaseName + + N'.' + + @OutputSchemaName + + N'.' + + @OutputTableName; + + IF @Debug = 1 BEGIN PRINT @StringToExecute; END; + EXEC sys.sp_executesql + @StringToExecute; + END; + END; - IF @EndDate IS NULL - SET @EndDate = '99991231'; - + /* WITH ROWCOUNT doesn't work on Amazon RDS - see: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2037 */ + IF @RDS = 0 + BEGIN; + BEGIN TRY; + RAISERROR('@RDS = 0, updating #t with high row and page counts', 0, 1) WITH NOWAIT; + UPDATE STATISTICS + #t + WITH + ROWCOUNT = 9223372036854775807, + PAGECOUNT = 9223372036854775807; + END TRY + BEGIN CATCH; + /* Misleading error returned, if run without permissions to update statistics the error returned is "Cannot find object". + Catching specific error, and returning message with better info. If any other error is returned, then throw as normal */ + IF (ERROR_NUMBER() = 1088) + BEGIN; + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Cannot run UPDATE STATISTICS on a #temp table without db_owner or sysadmin permissions', 0, 1) WITH NOWAIT; + END; + ELSE + BEGIN; + THROW; + END; + END CATCH; + END; - IF OBJECT_ID('tempdb..#deadlock_data') IS NOT NULL - DROP TABLE #deadlock_data; + /*If @TargetSessionType, we need to figure out if it's ring buffer or event file*/ + /*Azure has differently named views, so we need to separate. Thanks, Azure.*/ - IF OBJECT_ID('tempdb..#deadlock_process') IS NOT NULL - DROP TABLE #deadlock_process; + IF + ( + @Azure = 0 + AND @TargetSessionType IS NULL + ) + BEGIN + RAISERROR('@TargetSessionType is NULL, assigning for non-Azure instance', 0, 1) WITH NOWAIT; + + SELECT TOP (1) + @TargetSessionType = t.target_name + FROM sys.dm_xe_sessions AS s + JOIN sys.dm_xe_session_targets AS t + ON s.address = t.event_session_address + WHERE s.name = @EventSessionName + AND t.target_name IN (N'event_file', N'ring_buffer') + ORDER BY t.target_name + OPTION(RECOMPILE); + + RAISERROR('@TargetSessionType assigned as %s for non-Azure', 0, 1, @TargetSessionType) WITH NOWAIT; + END; - IF OBJECT_ID('tempdb..#deadlock_stack') IS NOT NULL - DROP TABLE #deadlock_stack; + IF + ( + @Azure = 1 + AND @TargetSessionType IS NULL + ) + BEGIN + RAISERROR('@TargetSessionType is NULL, assigning for Azure instance', 0, 1) WITH NOWAIT; + + SELECT TOP (1) + @TargetSessionType = t.target_name + FROM sys.dm_xe_database_sessions AS s + JOIN sys.dm_xe_database_session_targets AS t + ON s.address = t.event_session_address + WHERE s.name = @EventSessionName + AND t.target_name IN (N'event_file', N'ring_buffer') + ORDER BY t.target_name + OPTION(RECOMPILE); + + RAISERROR('@TargetSessionType assigned as %s for Azure', 0, 1, @TargetSessionType) WITH NOWAIT; + END; - IF OBJECT_ID('tempdb..#deadlock_resource') IS NOT NULL - DROP TABLE #deadlock_resource; - IF OBJECT_ID('tempdb..#deadlock_owner_waiter') IS NOT NULL - DROP TABLE #deadlock_owner_waiter; + /*The system health stuff gets handled different from user extended events.*/ + /*These next sections deal with user events, dependent on target.*/ - IF OBJECT_ID('tempdb..#deadlock_findings') IS NOT NULL - DROP TABLE #deadlock_findings; + /*If ring buffers*/ + IF + ( + @TargetSessionType LIKE N'ring%' + AND @EventSessionName NOT LIKE N'system_health%' + ) + BEGIN + IF @Azure = 0 + BEGIN + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('@TargetSessionType is ring_buffer, inserting XML for non-Azure at %s', 0, 1, @d) WITH NOWAIT; - CREATE TABLE #deadlock_findings - ( - id INT IDENTITY(1, 1) PRIMARY KEY CLUSTERED, - check_id INT NOT NULL, - database_name NVARCHAR(256), - object_name NVARCHAR(1000), - finding_group NVARCHAR(100), - finding NVARCHAR(4000) - ); + INSERT + #x WITH(TABLOCKX) + ( + x + ) + SELECT + x = TRY_CAST(t.target_data AS xml) + FROM sys.dm_xe_session_targets AS t + JOIN sys.dm_xe_sessions AS s + ON s.address = t.event_session_address + WHERE s.name = @EventSessionName + AND t.target_name = N'ring_buffer' + OPTION(RECOMPILE); + + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + END; - DECLARE @d VARCHAR(40), @StringToExecute NVARCHAR(4000),@StringToExecuteParams NVARCHAR(500),@r NVARCHAR(200),@OutputTableFindings NVARCHAR(100),@DeadlockCount INT; - DECLARE @ServerName NVARCHAR(256) - DECLARE @OutputDatabaseCheck BIT; - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - SET @OutputTableFindings = '[BlitzLockFindings]' - SET @ServerName = (select @@ServerName) - if(@OutputDatabaseName is not null) - BEGIN --if databaseName is set do some sanity checks and put [] around def. - if( (select name from sys.databases where name=@OutputDatabaseName) is null ) --if database is invalid raiserror and set bitcheck - BEGIN - RAISERROR('Database Name for output of table is invalid please correct, Output to Table will not be preformed', 0, 1, @d) WITH NOWAIT; - set @OutputDatabaseCheck = -1 -- -1 invalid/false, 0 = good/true - END - ELSE - BEGIN - set @OutputDatabaseCheck = 0 - select @StringToExecute = N'select @r = name from ' + '' + @OutputDatabaseName + - '' + '.sys.objects where type_desc=''USER_TABLE'' and name=' + '''' + @OutputTableName + '''', - @StringToExecuteParams = N'@OutputDatabaseName NVARCHAR(200),@OutputTableName NVARCHAR(200),@r NVARCHAR(200) OUTPUT' - exec sp_executesql @StringToExecute,@StringToExecuteParams,@OutputDatabaseName,@OutputTableName,@r OUTPUT - --put covers around all before. - SELECT @OutputDatabaseName = QUOTENAME(@OutputDatabaseName), - @OutputTableName = QUOTENAME(@OutputTableName), - @OutputSchemaName = QUOTENAME(@OutputSchemaName) - if(@r is not null) --if it is not null, there is a table, so check for newly added columns - BEGIN - /* If the table doesn't have the new spid column, add it. See Github #3101. */ - SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableName; - SET @StringToExecute = N'IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns - WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + ''')) AND name = ''spid'') - ALTER TABLE ' + @ObjectFullName + N' ADD spid SMALLINT NULL;'; - EXEC(@StringToExecute); + IF @Azure = 1 + BEGIN + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('@TargetSessionType is ring_buffer, inserting XML for Azure at %s', 0, 1, @d) WITH NOWAIT; - /* If the table doesn't have the new wait_resource column, add it. See Github #3101. */ - SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableName; - SET @StringToExecute = N'IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns - WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + ''')) AND name = ''wait_resource'') - ALTER TABLE ' + @ObjectFullName + N' ADD wait_resource NVARCHAR(MAX) NULL;'; - EXEC(@StringToExecute); - END - ELSE --if(@r is not null) --if it is null there is no table, create it from above execution - BEGIN - select @StringToExecute = N'use ' + @OutputDatabaseName + ';create table ' + @OutputSchemaName + '.' + @OutputTableName + ' ( - ServerName NVARCHAR(256), - deadlock_type NVARCHAR(256), - event_date datetime, - database_name NVARCHAR(256), - spid SMALLINT, - deadlock_group NVARCHAR(256), - query XML, - object_names XML, - isolation_level NVARCHAR(256), - owner_mode NVARCHAR(256), - waiter_mode NVARCHAR(256), - transaction_count bigint, - login_name NVARCHAR(256), - host_name NVARCHAR(256), - client_app NVARCHAR(256), - wait_time BIGINT, - wait_resource NVARCHAR(max), - priority smallint, - log_used BIGINT, - last_tran_started datetime, - last_batch_started datetime, - last_batch_completed datetime, - transaction_name NVARCHAR(256), - owner_waiter_type NVARCHAR(256), - owner_activity NVARCHAR(256), - owner_waiter_activity NVARCHAR(256), - owner_merging NVARCHAR(256), - owner_spilling NVARCHAR(256), - owner_waiting_to_close NVARCHAR(256), - waiter_waiter_type NVARCHAR(256), - waiter_owner_activity NVARCHAR(256), - waiter_waiter_activity NVARCHAR(256), - waiter_merging NVARCHAR(256), - waiter_spilling NVARCHAR(256), - waiter_waiting_to_close NVARCHAR(256), - deadlock_graph XML)', - @StringToExecuteParams = N'@OutputDatabaseName NVARCHAR(200),@OutputSchemaName NVARCHAR(100),@OutputTableName NVARCHAR(200)' - exec sp_executesql @StringToExecute, @StringToExecuteParams,@OutputDatabaseName,@OutputSchemaName,@OutputTableName - --table created. - select @StringToExecute = N'select @r = name from ' + '' + @OutputDatabaseName + - '' + '.sys.objects where type_desc=''USER_TABLE'' and name=''BlitzLockFindings''', - @StringToExecuteParams = N'@OutputDatabaseName NVARCHAR(200),@r NVARCHAR(200) OUTPUT' - exec sp_executesql @StringToExecute,@StringToExecuteParams,@OutputDatabaseName,@r OUTPUT - if(@r is null) --if table does not excist - BEGIN - select @OutputTableFindings=N'[BlitzLockFindings]', - @StringToExecute = N'use ' + @OutputDatabaseName + ';create table ' + @OutputSchemaName + '.' + @OutputTableFindings + ' ( - ServerName NVARCHAR(256), - check_id INT, - database_name NVARCHAR(256), - object_name NVARCHAR(1000), - finding_group NVARCHAR(100), - finding NVARCHAR(4000))', - @StringToExecuteParams = N'@OutputDatabaseName NVARCHAR(200),@OutputSchemaName NVARCHAR(100),@OutputTableFindings NVARCHAR(200)' - exec sp_executesql @StringToExecute, @StringToExecuteParams, @OutputDatabaseName,@OutputSchemaName,@OutputTableFindings - - END + INSERT + #x WITH(TABLOCKX) + ( + x + ) + SELECT + x = TRY_CAST(t.target_data AS xml) + FROM sys.dm_xe_database_session_targets AS t + JOIN sys.dm_xe_database_sessions AS s + ON s.address = t.event_session_address + WHERE s.name = @EventSessionName + AND t.target_name = N'ring_buffer' + OPTION(RECOMPILE); + + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + END; + END; - END - --create synonym for deadlockfindings. - if((select name from sys.objects where name='DeadlockFindings' and type_desc='SYNONYM')IS NOT NULL) - BEGIN - RAISERROR('found synonym', 0, 1) WITH NOWAIT; - drop synonym DeadlockFindings; - END - set @StringToExecute = 'CREATE SYNONYM DeadlockFindings FOR ' + @OutputDatabaseName + '.' + @OutputSchemaName + '.' + @OutputTableFindings; - exec sp_executesql @StringToExecute - - --create synonym for deadlock table. - if((select name from sys.objects where name='DeadLockTbl' and type_desc='SYNONYM') IS NOT NULL) - BEGIN - drop SYNONYM DeadLockTbl; - END - set @StringToExecute = 'CREATE SYNONYM DeadLockTbl FOR ' + @OutputDatabaseName + '.' + @OutputSchemaName + '.' + @OutputTableName; - exec sp_executesql @StringToExecute - - END - END - - CREATE TABLE #t (id INT NOT NULL); + /*If event file*/ + IF + ( + @TargetSessionType LIKE N'event%' + AND @EventSessionName NOT LIKE N'system_health%' + ) + BEGIN + IF @Azure = 0 + BEGIN + RAISERROR('@TargetSessionType is event_file, assigning XML for non-Azure', 0, 1) WITH NOWAIT; - /* WITH ROWCOUNT doesn't work on Amazon RDS - see: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2037 */ - IF LEFT(CAST(SERVERPROPERTY('ComputerNamePhysicalNetBIOS') AS VARCHAR(8000)), 8) <> 'EC2AMAZ-' - AND LEFT(CAST(SERVERPROPERTY('MachineName') AS VARCHAR(8000)), 8) <> 'EC2AMAZ-' - AND db_id('rdsadmin') IS NULL - BEGIN; - BEGIN TRY; - UPDATE STATISTICS #t WITH ROWCOUNT = 100000000, PAGECOUNT = 100000000; - END TRY - BEGIN CATCH; - /* Misleading error returned, if run without permissions to update statistics the error returned is "Cannot find object". - Catching specific error, and returning message with better info. If any other error is returned, then throw as normal */ - IF (ERROR_NUMBER() = 1088) - BEGIN; - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Cannot run UPDATE STATISTICS on temp table without db_owner or sysadmin permissions %s', 0, 1, @d) WITH NOWAIT; - END; - ELSE - BEGIN; - THROW; - END; - END CATCH; - END; + SELECT + @SessionId = t.event_session_id, + @TargetSessionId = t.target_id + FROM sys.server_event_session_targets AS t + JOIN sys.server_event_sessions AS s + ON s.event_session_id = t.event_session_id + WHERE t.name = @TargetSessionType + AND s.name = @EventSessionName + OPTION(RECOMPILE); + + /*We get the file name automatically, here*/ + RAISERROR('Assigning @FileName...', 0, 1) WITH NOWAIT; + SELECT + @FileName = + CASE + WHEN f.file_name LIKE N'%.xel' + THEN REPLACE(f.file_name, N'.xel', N'*.xel') + ELSE f.file_name + N'*.xel' + END + FROM + ( + SELECT + file_name = + CONVERT(nvarchar(4000), f.value) + FROM sys.server_event_session_fields AS f + WHERE f.event_session_id = @SessionId + AND f.object_id = @TargetSessionId + AND f.name = N'filename' + ) AS f + OPTION(RECOMPILE); + END; + + IF @Azure = 1 + BEGIN + RAISERROR('@TargetSessionType is event_file, assigning XML for Azure', 0, 1) WITH NOWAIT; + SELECT + @SessionId = + t.event_session_address, + @TargetSessionId = + t.target_name + FROM sys.dm_xe_database_session_targets t + JOIN sys.dm_xe_database_sessions s + ON s.address = t.event_session_address + WHERE t.target_name = @TargetSessionType + AND s.name = @EventSessionName + OPTION(RECOMPILE); + + /*We get the file name automatically, here*/ + RAISERROR('Assigning @FileName...', 0, 1) WITH NOWAIT; + SELECT + @FileName = + CASE + WHEN f.file_name LIKE N'%.xel' + THEN REPLACE(f.file_name, N'.xel', N'*.xel') + ELSE f.file_name + N'*.xel' + END + FROM + ( + SELECT + file_name = + CONVERT(nvarchar(4000), f.value) + FROM sys.server_event_session_fields AS f + WHERE f.event_session_id = @SessionId + AND f.object_id = @TargetSessionId + AND f.name = N'filename' + ) AS f + OPTION(RECOMPILE); + END; - /*Grab the initial set of XML to parse*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Grab the initial set of XML to parse at %s', 0, 1, @d) WITH NOWAIT; - WITH xml - AS ( SELECT CONVERT(XML, event_data) AS deadlock_xml - FROM sys.fn_xe_file_target_read_file(@EventSessionPath, NULL, NULL, NULL) ) - SELECT ISNULL(xml.deadlock_xml, '') AS deadlock_xml - INTO #deadlock_data - FROM xml + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Reading for event_file %s', 0, 1, @FileName) WITH NOWAIT; + + INSERT + #x WITH(TABLOCKX) + ( + x + ) + SELECT + x = TRY_CAST(f.event_data AS xml) + FROM sys.fn_xe_file_target_read_file(@FileName, NULL, NULL, NULL) AS f LEFT JOIN #t AS t - ON 1 = 1 - CROSS APPLY xml.deadlock_xml.nodes('/event/@name') AS x(c) - WHERE 1 = 1 - AND x.c.exist('/event/@name[ .= "xml_deadlock_report"]') = 1 - AND CONVERT(datetime, SWITCHOFFSET(CONVERT(datetimeoffset, xml.deadlock_xml.value('(/event/@timestamp)[1]', 'datetime') ), DATENAME(TzOffset, SYSDATETIMEOFFSET()))) > @StartDate - AND CONVERT(datetime, SWITCHOFFSET(CONVERT(datetimeoffset, xml.deadlock_xml.value('(/event/@timestamp)[1]', 'datetime') ), DATENAME(TzOffset, SYSDATETIMEOFFSET()))) < @EndDate - ORDER BY xml.deadlock_xml.value('(/event/@timestamp)[1]', 'datetime') DESC - OPTION ( RECOMPILE ); + ON 1 = 1 + OPTION(RECOMPILE); - /*Optimization: if we got back more rows than @Top, remove them. This seems to be better than using @Top in the query above as that results in excessive memory grant*/ - SET @DeadlockCount = @@ROWCOUNT - IF( @Top < @DeadlockCount ) BEGIN - WITH T - AS ( - SELECT TOP ( @DeadlockCount - @Top) * - FROM #deadlock_data - ORDER BY #deadlock_data.deadlock_xml.value('(/event/@timestamp)[1]', 'datetime') ASC) - DELETE FROM T - END + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + END; + + /*The XML is parsed differently if it comes from the event file or ring buffer*/ + + /*If ring buffers*/ + IF + ( + @TargetSessionType LIKE N'ring%' + AND @EventSessionName NOT LIKE N'system_health%' + ) + BEGIN + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Inserting to #deadlock_data for ring buffer data', 0, 1) WITH NOWAIT; + + INSERT + #deadlock_data WITH(TABLOCKX) + ( + deadlock_xml + ) + SELECT + deadlock_xml = + e.x.query(N'.') + FROM #x AS x + LEFT JOIN #t AS t + ON 1 = 1 + CROSS APPLY x.x.nodes('/RingBufferTarget/event') AS e(x) + WHERE e.x.exist('@name[ .= "xml_deadlock_report"]') = 1 + AND e.x.exist('@timestamp[. >= sql:variable("@StartDate")]') = 1 + AND e.x.exist('@timestamp[. < sql:variable("@EndDate")]') = 1 + OPTION(RECOMPILE); + + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + END; + + /*If event file*/ + IF + ( + @TargetSessionType LIKE N'event_file%' + AND @EventSessionName NOT LIKE N'system_health%' + ) + BEGIN + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Inserting to #deadlock_data for event file data', 0, 1) WITH NOWAIT; + + INSERT + #deadlock_data WITH(TABLOCKX) + ( + deadlock_xml + ) + SELECT + deadlock_xml = + e.x.query('.') + FROM #x AS x + LEFT JOIN #t AS t + ON 1 = 1 + CROSS APPLY x.x.nodes('/event') AS e(x) + WHERE e.x.exist('/event/@name[ .= "xml_deadlock_report"]') = 1 + AND e.x.exist('/event/@timestamp[. >= sql:variable("@StartDate")]') = 1 + AND e.x.exist('/event/@timestamp[. < sql:variable("@EndDate")]') = 1 + OPTION(RECOMPILE); + + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + END; + + /*This section deals with event file*/ + IF + ( + @TargetSessionType LIKE N'event%' + AND @EventSessionName LIKE N'system_health%' + ) + BEGIN + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Grab the initial set of xml to parse at %s', 0, 1, @d) WITH NOWAIT; + + IF @Debug = 1 BEGIN SET STATISTICS XML ON; END; + + SELECT + xml.deadlock_xml + INTO #xml + FROM + ( + SELECT + deadlock_xml = + TRY_CAST(fx.event_data AS xml) + FROM sys.fn_xe_file_target_read_file('system_health*.xel', NULL, NULL, NULL) AS fx + LEFT JOIN #t AS t + ON 1 = 1 + ) AS xml + CROSS APPLY xml.deadlock_xml.nodes('/event') AS e(x) + WHERE e.x.exist('@name[ . = "xml_deadlock_report"]') = 1 + AND e.x.exist('@timestamp[. >= sql:variable("@StartDate")]') = 1 + AND e.x.exist('@timestamp[. < sql:variable("@EndDate")]') = 1 + OPTION(RECOMPILE); + + INSERT + #deadlock_data WITH(TABLOCKX) + SELECT + deadlock_xml = + xml.deadlock_xml + FROM #xml AS xml + LEFT JOIN #t AS t + ON 1 = 1 + WHERE xml.deadlock_xml IS NOT NULL + OPTION(RECOMPILE); + + IF @Debug = 1 BEGIN SET STATISTICS XML OFF; END; - /*Parse process and input buffer XML*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Parse process and input buffer XML %s', 0, 1, @d) WITH NOWAIT; - SELECT q.event_date, - q.victim_id, - CONVERT(BIT, q.is_parallel) AS is_parallel, - q.deadlock_graph, - q.id, - q.spid, - q.database_id, - q.priority, - q.log_used, - q.wait_resource, - q.wait_time, - q.transaction_name, - q.last_tran_started, - q.last_batch_started, - q.last_batch_completed, - q.lock_mode, - q.transaction_count, - q.client_app, - q.host_name, - q.login_name, - q.isolation_level, - q.process_xml, - ISNULL(ca2.ib.query('.'), '') AS input_buffer - INTO #deadlock_process - FROM ( SELECT dd.deadlock_xml, - CONVERT(DATETIME2(7), SWITCHOFFSET(CONVERT(datetimeoffset, dd.event_date ), DATENAME(TzOffset, SYSDATETIMEOFFSET()))) AS event_date, - dd.victim_id, - CONVERT(tinyint, dd.is_parallel) + CONVERT(tinyint, dd.is_parallel_batch) AS is_parallel, - dd.deadlock_graph, - ca.dp.value('@id', 'NVARCHAR(256)') AS id, - ca.dp.value('@spid', 'SMALLINT') AS spid, - ca.dp.value('@currentdb', 'BIGINT') AS database_id, - ca.dp.value('@priority', 'SMALLINT') AS priority, - ca.dp.value('@logused', 'BIGINT') AS log_used, - ca.dp.value('@waitresource', 'NVARCHAR(256)') AS wait_resource, - ca.dp.value('@waittime', 'BIGINT') AS wait_time, - ca.dp.value('@transactionname', 'NVARCHAR(256)') AS transaction_name, - ca.dp.value('@lasttranstarted', 'DATETIME2(7)') AS last_tran_started, - ca.dp.value('@lastbatchstarted', 'DATETIME2(7)') AS last_batch_started, - ca.dp.value('@lastbatchcompleted', 'DATETIME2(7)') AS last_batch_completed, - ca.dp.value('@lockMode', 'NVARCHAR(256)') AS lock_mode, - ca.dp.value('@trancount', 'BIGINT') AS transaction_count, - ca.dp.value('@clientapp', 'NVARCHAR(256)') AS client_app, - ca.dp.value('@hostname', 'NVARCHAR(256)') AS host_name, - ca.dp.value('@loginname', 'NVARCHAR(256)') AS login_name, - ca.dp.value('@isolationlevel', 'NVARCHAR(256)') AS isolation_level, - ISNULL(ca.dp.query('.'), '') AS process_xml - FROM ( SELECT d1.deadlock_xml, - d1.deadlock_xml.value('(event/@timestamp)[1]', 'DATETIME2') AS event_date, - d1.deadlock_xml.value('(//deadlock/victim-list/victimProcess/@id)[1]', 'NVARCHAR(256)') AS victim_id, - d1.deadlock_xml.exist('//deadlock/resource-list/exchangeEvent') AS is_parallel, - d1.deadlock_xml.exist('//deadlock/resource-list/SyncPoint') AS is_parallel_batch, - d1.deadlock_xml.query('/event/data/value/deadlock') AS deadlock_graph - FROM #deadlock_data AS d1 ) AS dd - CROSS APPLY dd.deadlock_xml.nodes('//deadlock/process-list/process') AS ca(dp) - WHERE (ca.dp.value('@currentdb', 'BIGINT') = DB_ID(@DatabaseName) OR @DatabaseName IS NULL) - AND (ca.dp.value('@clientapp', 'NVARCHAR(256)') = @AppName OR @AppName IS NULL) - AND (ca.dp.value('@hostname', 'NVARCHAR(256)') = @HostName OR @HostName IS NULL) - AND (ca.dp.value('@loginname', 'NVARCHAR(256)') = @LoginName OR @LoginName IS NULL) + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + END; + + /*Parse process and input buffer xml*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Initial Parse process and input buffer xml %s', 0, 1, @d) WITH NOWAIT; + + SELECT + d1.deadlock_xml, + event_date = d1.deadlock_xml.value('(event/@timestamp)[1]', 'datetime2'), + victim_id = d1.deadlock_xml.value('(//deadlock/victim-list/victimProcess/@id)[1]', 'nvarchar(256)'), + is_parallel = d1.deadlock_xml.exist('//deadlock/resource-list/exchangeEvent'), + is_parallel_batch = d1.deadlock_xml.exist('//deadlock/resource-list/SyncPoint'), + deadlock_graph = d1.deadlock_xml.query('/event/data/value/deadlock') + INTO #dd + FROM #deadlock_data AS d1 + LEFT JOIN #t AS t + ON 1 = 1 + OPTION(RECOMPILE); + + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Final Parse process and input buffer xml %s', 0, 1, @d) WITH NOWAIT; + + SELECT + q.event_date, + q.victim_id, + is_parallel = + CONVERT(bit, q.is_parallel), + q.deadlock_graph, + q.id, + q.spid, + q.database_id, + database_name = + ISNULL + ( + DB_NAME(q.database_id), + N'UNKNOWN' + ), + q.current_database_name, + q.priority, + q.log_used, + q.wait_resource, + q.wait_time, + q.transaction_name, + q.last_tran_started, + q.last_batch_started, + q.last_batch_completed, + q.lock_mode, + q.status, + q.transaction_count, + q.client_app, + q.host_name, + q.login_name, + q.isolation_level, + client_option_1 = + SUBSTRING + ( + CASE WHEN q.clientoption1 & 1 = 1 THEN ', DISABLE_DEF_CNST_CHECK' ELSE '' END + + CASE WHEN q.clientoption1 & 2 = 2 THEN ', IMPLICIT_TRANSACTIONS' ELSE '' END + + CASE WHEN q.clientoption1 & 4 = 4 THEN ', CURSOR_CLOSE_ON_COMMIT' ELSE '' END + + CASE WHEN q.clientoption1 & 8 = 8 THEN ', ANSI_WARNINGS' ELSE '' END + + CASE WHEN q.clientoption1 & 16 = 16 THEN ', ANSI_PADDING' ELSE '' END + + CASE WHEN q.clientoption1 & 32 = 32 THEN ', ANSI_NULLS' ELSE '' END + + CASE WHEN q.clientoption1 & 64 = 64 THEN ', ARITHABORT' ELSE '' END + + CASE WHEN q.clientoption1 & 128 = 128 THEN ', ARITHIGNORE' ELSE '' END + + CASE WHEN q.clientoption1 & 256 = 256 THEN ', QUOTED_IDENTIFIER' ELSE '' END + + CASE WHEN q.clientoption1 & 512 = 512 THEN ', NOCOUNT' ELSE '' END + + CASE WHEN q.clientoption1 & 1024 = 1024 THEN ', ANSI_NULL_DFLT_ON' ELSE '' END + + CASE WHEN q.clientoption1 & 2048 = 2048 THEN ', ANSI_NULL_DFLT_OFF' ELSE '' END + + CASE WHEN q.clientoption1 & 4096 = 4096 THEN ', CONCAT_NULL_YIELDS_NULL' ELSE '' END + + CASE WHEN q.clientoption1 & 8192 = 8192 THEN ', NUMERIC_ROUNDABORT' ELSE '' END + + CASE WHEN q.clientoption1 & 16384 = 16384 THEN ', XACT_ABORT' ELSE '' END, + 3, + 8000 + ), + client_option_2 = + SUBSTRING + ( + CASE WHEN q.clientoption2 & 1024 = 1024 THEN ', DB CHAINING' ELSE '' END + + CASE WHEN q.clientoption2 & 2048 = 2048 THEN ', NUMERIC ROUNDABORT' ELSE '' END + + CASE WHEN q.clientoption2 & 4096 = 4096 THEN ', ARITHABORT' ELSE '' END + + CASE WHEN q.clientoption2 & 8192 = 8192 THEN ', ANSI PADDING' ELSE '' END + + CASE WHEN q.clientoption2 & 16384 = 16384 THEN ', ANSI NULL DEFAULT' ELSE '' END + + CASE WHEN q.clientoption2 & 65536 = 65536 THEN ', CONCAT NULL YIELDS NULL' ELSE '' END + + CASE WHEN q.clientoption2 & 131072 = 131072 THEN ', RECURSIVE TRIGGERS' ELSE '' END + + CASE WHEN q.clientoption2 & 1048576 = 1048576 THEN ', DEFAULT TO LOCAL CURSOR' ELSE '' END + + CASE WHEN q.clientoption2 & 8388608 = 8388608 THEN ', QUOTED IDENTIFIER' ELSE '' END + + CASE WHEN q.clientoption2 & 16777216 = 16777216 THEN ', AUTO CREATE STATISTICS' ELSE '' END + + CASE WHEN q.clientoption2 & 33554432 = 33554432 THEN ', CURSOR CLOSE ON COMMIT' ELSE '' END + + CASE WHEN q.clientoption2 & 67108864 = 67108864 THEN ', ANSI NULLS' ELSE '' END + + CASE WHEN q.clientoption2 & 268435456 = 268435456 THEN ', ANSI WARNINGS' ELSE '' END + + CASE WHEN q.clientoption2 & 536870912 = 536870912 THEN ', FULL TEXT ENABLED' ELSE '' END + + CASE WHEN q.clientoption2 & 1073741824 = 1073741824 THEN ', AUTO UPDATE STATISTICS' ELSE '' END + + CASE WHEN q.clientoption2 & 1469283328 = 1469283328 THEN ', ALL SETTABLE OPTIONS' ELSE '' END, + 3, + 8000 + ), + q.process_xml + INTO #deadlock_process + FROM + ( + SELECT + dd.deadlock_xml, + event_date = + DATEADD + ( + MINUTE, + DATEDIFF(MINUTE, GETUTCDATE(), SYSDATETIME()), + dd.event_date + ), + dd.victim_id, + is_parallel = + CONVERT(tinyint, dd.is_parallel) + + CONVERT(tinyint, dd.is_parallel_batch), + dd.deadlock_graph, + id = ca.dp.value('@id', 'nvarchar(256)'), + spid = ca.dp.value('@spid', 'smallint'), + database_id = ca.dp.value('@currentdb', 'bigint'), + current_database_name = ca.dp.value('@currentdbname', 'nvarchar(256)'), + priority = ca.dp.value('@priority', 'smallint'), + log_used = ca.dp.value('@logused', 'bigint'), + wait_resource = ca.dp.value('@waitresource', 'nvarchar(256)'), + wait_time = ca.dp.value('@waittime', 'bigint'), + transaction_name = ca.dp.value('@transactionname', 'nvarchar(256)'), + last_tran_started = ca.dp.value('@lasttranstarted', 'datetime'), + last_batch_started = ca.dp.value('@lastbatchstarted', 'datetime'), + last_batch_completed = ca.dp.value('@lastbatchcompleted', 'datetime'), + lock_mode = ca.dp.value('@lockMode', 'nvarchar(256)'), + status = ca.dp.value('@status', 'nvarchar(256)'), + transaction_count = ca.dp.value('@trancount', 'bigint'), + client_app = ca.dp.value('@clientapp', 'nvarchar(1024)'), + host_name = ca.dp.value('@hostname', 'nvarchar(256)'), + login_name = ca.dp.value('@loginname', 'nvarchar(256)'), + isolation_level = ca.dp.value('@isolationlevel', 'nvarchar(256)'), + clientoption1 = ca.dp.value('@clientoption1', 'bigint'), + clientoption2 = ca.dp.value('@clientoption2', 'bigint'), + process_xml = ISNULL(ca.dp.query(N'.'), N'') + FROM #dd AS dd + CROSS APPLY dd.deadlock_xml.nodes('//deadlock/process-list/process') AS ca(dp) + WHERE (ca.dp.exist('@currentdb[. = sql:variable("@DatabaseId")]') = 1 OR @DatabaseName IS NULL) + AND (ca.dp.exist('@clientapp[. = sql:variable("@AppName")]') = 1 OR @AppName IS NULL) + AND (ca.dp.exist('@hostname[. = sql:variable("@HostName")]') = 1 OR @HostName IS NULL) + AND (ca.dp.exist('@loginname[. = sql:variable("@LoginName")]') = 1 OR @LoginName IS NULL) ) AS q - CROSS APPLY q.deadlock_xml.nodes('//deadlock/process-list/process/inputbuf') AS ca2(ib) - OPTION ( RECOMPILE ); + LEFT JOIN #t AS t + ON 1 = 1 + OPTION(RECOMPILE); + + + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + /*Parse execution stack xml*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Parse execution stack xml %s', 0, 1, @d) WITH NOWAIT; - /*Parse execution stack XML*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Parse execution stack XML %s', 0, 1, @d) WITH NOWAIT; - SELECT DISTINCT - dp.id, - dp.event_date, - ca.dp.value('@procname', 'NVARCHAR(1000)') AS proc_name, - ca.dp.value('@sqlhandle', 'NVARCHAR(128)') AS sql_handle - INTO #deadlock_stack - FROM #deadlock_process AS dp + SELECT DISTINCT + dp.id, + dp.event_date, + proc_name = ca.dp.value('@procname', 'nvarchar(1024)'), + sql_handle = ca.dp.value('@sqlhandle', 'nvarchar(131)') + INTO #deadlock_stack + FROM #deadlock_process AS dp CROSS APPLY dp.process_xml.nodes('//executionStack/frame') AS ca(dp) - WHERE (ca.dp.value('@procname', 'NVARCHAR(256)') = @StoredProcName OR @StoredProcName IS NULL) - OPTION ( RECOMPILE ); + WHERE (ca.dp.exist('@procname[. = sql:variable("@StoredProcName")]') = 1 OR @StoredProcName IS NULL) + OPTION(RECOMPILE); + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + /*Grab the full resource list*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); - /*Grab the full resource list*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); RAISERROR('Grab the full resource list %s', 0, 1, @d) WITH NOWAIT; - SELECT - CONVERT(DATETIME2(7), SWITCHOFFSET(CONVERT(datetimeoffset, dr.event_date), DATENAME(TzOffset, SYSDATETIMEOFFSET()))) AS event_date, - dr.victim_id, - dr.resource_xml - INTO #deadlock_resource - FROM + + SELECT + event_date = + DATEADD + ( + MINUTE, + DATEDIFF + ( + MINUTE, + GETUTCDATE(), + SYSDATETIME() + ), + dr.event_date + ), + dr.victim_id, + dr.resource_xml + INTO + #deadlock_resource + FROM ( - SELECT dd.deadlock_xml.value('(event/@timestamp)[1]', 'DATETIME2') AS event_date, - dd.deadlock_xml.value('(//deadlock/victim-list/victimProcess/@id)[1]', 'NVARCHAR(256)') AS victim_id, - ISNULL(ca.dp.query('.'), '') AS resource_xml - FROM #deadlock_data AS dd - CROSS APPLY dd.deadlock_xml.nodes('//deadlock/resource-list') AS ca(dp) + SELECT + event_date = dd.deadlock_xml.value('(event/@timestamp)[1]', 'datetime2'), + victim_id = dd.deadlock_xml.value('(//deadlock/victim-list/victimProcess/@id)[1]', 'nvarchar(256)'), + resource_xml = ISNULL(ca.dp.query(N'.'), N'') + FROM #deadlock_data AS dd + CROSS APPLY dd.deadlock_xml.nodes('//deadlock/resource-list') AS ca(dp) ) AS dr - OPTION ( RECOMPILE ); + OPTION(RECOMPILE); + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - /*Parse object locks*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + /*Parse object locks*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Parse object locks %s', 0, 1, @d) WITH NOWAIT; - SELECT DISTINCT - ca.event_date, - ca.database_id, - ca.object_name, - ca.lock_mode, - ca.index_name, - ca.associatedObjectId, - w.l.value('@id', 'NVARCHAR(256)') AS waiter_id, - w.l.value('@mode', 'NVARCHAR(256)') AS waiter_mode, - o.l.value('@id', 'NVARCHAR(256)') AS owner_id, - o.l.value('@mode', 'NVARCHAR(256)') AS owner_mode, - N'OBJECT' AS lock_type - INTO #deadlock_owner_waiter - FROM ( - SELECT dr.event_date, - ca.dr.value('@dbid', 'BIGINT') AS database_id, - ca.dr.value('@objectname', 'NVARCHAR(256)') AS object_name, - ca.dr.value('@mode', 'NVARCHAR(256)') AS lock_mode, - ca.dr.value('@indexname', 'NVARCHAR(256)') AS index_name, - ca.dr.value('@associatedObjectId', 'BIGINT') AS associatedObjectId, - ca.dr.query('.') AS dr - FROM #deadlock_resource AS dr - CROSS APPLY dr.resource_xml.nodes('//resource-list/objectlock') AS ca(dr) - ) AS ca + + SELECT DISTINCT + ca.event_date, + ca.database_id, + database_name = + ISNULL + ( + DB_NAME(ca.database_id), + N'UNKNOWN' + ), + object_name = + REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE( + REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE( + REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE( + ca.object_name COLLATE Latin1_General_BIN2, + NCHAR(31), N'?'), NCHAR(30), N'?'), NCHAR(29), N'?'), NCHAR(28), N'?'), NCHAR(27), N'?'), + NCHAR(26), N'?'), NCHAR(25), N'?'), NCHAR(24), N'?'), NCHAR(23), N'?'), NCHAR(22), N'?'), + NCHAR(21), N'?'), NCHAR(20), N'?'), NCHAR(19), N'?'), NCHAR(18), N'?'), NCHAR(17), N'?'), + NCHAR(16), N'?'), NCHAR(15), N'?'), NCHAR(14), N'?'), NCHAR(12), N'?'), NCHAR(11), N'?'), + NCHAR(8), N'?'), NCHAR(7), N'?'), NCHAR(6), N'?'), NCHAR(5), N'?'), NCHAR(4), N'?'), + NCHAR(3), N'?'), NCHAR(2), N'?'), NCHAR(1), N'?'), NCHAR(0), N'?'), + ca.lock_mode, + ca.index_name, + ca.associatedObjectId, + waiter_id = w.l.value('@id', 'nvarchar(256)'), + waiter_mode = w.l.value('@mode', 'nvarchar(256)'), + owner_id = o.l.value('@id', 'nvarchar(256)'), + owner_mode = o.l.value('@mode', 'nvarchar(256)'), + lock_type = N'OBJECT' + INTO #deadlock_owner_waiter + FROM + ( + SELECT + dr.event_date, + database_id = ca.dr.value('@dbid', 'bigint'), + object_name = ca.dr.value('@objectname', 'nvarchar(1024)'), + lock_mode = ca.dr.value('@mode', 'nvarchar(256)'), + index_name = ca.dr.value('@indexname', 'nvarchar(256)'), + associatedObjectId = ca.dr.value('@associatedObjectId', 'bigint'), + dr = ca.dr.query('.') + FROM #deadlock_resource AS dr + CROSS APPLY dr.resource_xml.nodes('//resource-list/objectlock') AS ca(dr) + WHERE (ca.dr.exist('@objectname[. = sql:variable("@ObjectName")]') = 1 OR @ObjectName IS NULL) + ) AS ca CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l) CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l) - WHERE (ca.object_name = @ObjectName OR @ObjectName IS NULL) - OPTION ( RECOMPILE ); - + OPTION(RECOMPILE); + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - /*Parse page locks*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + /*Parse page locks*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Parse page locks %s', 0, 1, @d) WITH NOWAIT; - INSERT #deadlock_owner_waiter WITH(TABLOCKX) - SELECT DISTINCT - ca.event_date, - ca.database_id, - ca.object_name, - ca.lock_mode, - ca.index_name, - ca.associatedObjectId, - w.l.value('@id', 'NVARCHAR(256)') AS waiter_id, - w.l.value('@mode', 'NVARCHAR(256)') AS waiter_mode, - o.l.value('@id', 'NVARCHAR(256)') AS owner_id, - o.l.value('@mode', 'NVARCHAR(256)') AS owner_mode, - N'PAGE' AS lock_type - FROM ( - SELECT dr.event_date, - ca.dr.value('@dbid', 'BIGINT') AS database_id, - ca.dr.value('@objectname', 'NVARCHAR(256)') AS object_name, - ca.dr.value('@mode', 'NVARCHAR(256)') AS lock_mode, - ca.dr.value('@indexname', 'NVARCHAR(256)') AS index_name, - ca.dr.value('@associatedObjectId', 'BIGINT') AS associatedObjectId, - ca.dr.query('.') AS dr - FROM #deadlock_resource AS dr - CROSS APPLY dr.resource_xml.nodes('//resource-list/pagelock') AS ca(dr) - ) AS ca + + INSERT + #deadlock_owner_waiter WITH(TABLOCKX) + SELECT DISTINCT + ca.event_date, + ca.database_id, + database_name = + ISNULL + ( + DB_NAME(ca.database_id), + N'UNKNOWN' + ), + ca.object_name, + ca.lock_mode, + ca.index_name, + ca.associatedObjectId, + waiter_id = w.l.value('@id', 'nvarchar(256)'), + waiter_mode = w.l.value('@mode', 'nvarchar(256)'), + owner_id = o.l.value('@id', 'nvarchar(256)'), + owner_mode = o.l.value('@mode', 'nvarchar(256)'), + lock_type = N'PAGE' + FROM + ( + SELECT + dr.event_date, + database_id = ca.dr.value('@dbid', 'bigint'), + object_name = ca.dr.value('@objectname', 'nvarchar(1024)'), + lock_mode = ca.dr.value('@mode', 'nvarchar(256)'), + index_name = ca.dr.value('@indexname', 'nvarchar(256)'), + associatedObjectId = ca.dr.value('@associatedObjectId', 'bigint'), + dr = ca.dr.query('.') + FROM #deadlock_resource AS dr + CROSS APPLY dr.resource_xml.nodes('//resource-list/pagelock') AS ca(dr) + WHERE (ca.dr.exist('@objectname[. = sql:variable("@ObjectName")]') = 1 OR @ObjectName IS NULL) + ) AS ca CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l) CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l) - OPTION ( RECOMPILE ); + OPTION(RECOMPILE); + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - /*Parse key locks*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + /*Parse key locks*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Parse key locks %s', 0, 1, @d) WITH NOWAIT; - INSERT #deadlock_owner_waiter WITH(TABLOCKX) - SELECT DISTINCT - ca.event_date, - ca.database_id, - ca.object_name, - ca.lock_mode, - ca.index_name, - ca.associatedObjectId, - w.l.value('@id', 'NVARCHAR(256)') AS waiter_id, - w.l.value('@mode', 'NVARCHAR(256)') AS waiter_mode, - o.l.value('@id', 'NVARCHAR(256)') AS owner_id, - o.l.value('@mode', 'NVARCHAR(256)') AS owner_mode, - N'KEY' AS lock_type - FROM ( - SELECT dr.event_date, - ca.dr.value('@dbid', 'BIGINT') AS database_id, - ca.dr.value('@objectname', 'NVARCHAR(256)') AS object_name, - ca.dr.value('@mode', 'NVARCHAR(256)') AS lock_mode, - ca.dr.value('@indexname', 'NVARCHAR(256)') AS index_name, - ca.dr.value('@associatedObjectId', 'BIGINT') AS associatedObjectId, - ca.dr.query('.') AS dr - FROM #deadlock_resource AS dr - CROSS APPLY dr.resource_xml.nodes('//resource-list/keylock') AS ca(dr) - ) AS ca + + INSERT + #deadlock_owner_waiter WITH(TABLOCKX) + SELECT DISTINCT + ca.event_date, + ca.database_id, + database_name = + ISNULL + ( + DB_NAME(ca.database_id), + N'UNKNOWN' + ), + ca.object_name, + ca.lock_mode, + ca.index_name, + ca.associatedObjectId, + waiter_id = w.l.value('@id', 'nvarchar(256)'), + waiter_mode = w.l.value('@mode', 'nvarchar(256)'), + owner_id = o.l.value('@id', 'nvarchar(256)'), + owner_mode = o.l.value('@mode', 'nvarchar(256)'), + lock_type = N'KEY' + FROM + ( + SELECT + dr.event_date, + database_id = ca.dr.value('@dbid', 'bigint'), + object_name = ca.dr.value('@objectname', 'nvarchar(1024)'), + lock_mode = ca.dr.value('@mode', 'nvarchar(256)'), + index_name = ca.dr.value('@indexname', 'nvarchar(256)'), + associatedObjectId = ca.dr.value('@associatedObjectId', 'bigint'), + dr = ca.dr.query('.') + FROM #deadlock_resource AS dr + CROSS APPLY dr.resource_xml.nodes('//resource-list/keylock') AS ca(dr) + WHERE (ca.dr.exist('@objectname[. = sql:variable("@ObjectName")]') = 1 OR @ObjectName IS NULL) + ) AS ca CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l) CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l) - OPTION ( RECOMPILE ); + OPTION(RECOMPILE); + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - /*Parse RID locks*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + /*Parse RID locks*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Parse RID locks %s', 0, 1, @d) WITH NOWAIT; - INSERT #deadlock_owner_waiter WITH(TABLOCKX) - SELECT DISTINCT - ca.event_date, - ca.database_id, - ca.object_name, - ca.lock_mode, - ca.index_name, - ca.associatedObjectId, - w.l.value('@id', 'NVARCHAR(256)') AS waiter_id, - w.l.value('@mode', 'NVARCHAR(256)') AS waiter_mode, - o.l.value('@id', 'NVARCHAR(256)') AS owner_id, - o.l.value('@mode', 'NVARCHAR(256)') AS owner_mode, - N'RID' AS lock_type - FROM ( - SELECT dr.event_date, - ca.dr.value('@dbid', 'BIGINT') AS database_id, - ca.dr.value('@objectname', 'NVARCHAR(256)') AS object_name, - ca.dr.value('@mode', 'NVARCHAR(256)') AS lock_mode, - ca.dr.value('@indexname', 'NVARCHAR(256)') AS index_name, - ca.dr.value('@associatedObjectId', 'BIGINT') AS associatedObjectId, - ca.dr.query('.') AS dr - FROM #deadlock_resource AS dr - CROSS APPLY dr.resource_xml.nodes('//resource-list/ridlock') AS ca(dr) - ) AS ca + + INSERT + #deadlock_owner_waiter WITH(TABLOCKX) + SELECT DISTINCT + ca.event_date, + ca.database_id, + database_name = + ISNULL + ( + DB_NAME(ca.database_id), + N'UNKNOWN' + ), + ca.object_name, + ca.lock_mode, + ca.index_name, + ca.associatedObjectId, + waiter_id = w.l.value('@id', 'nvarchar(256)'), + waiter_mode = w.l.value('@mode', 'nvarchar(256)'), + owner_id = o.l.value('@id', 'nvarchar(256)'), + owner_mode = o.l.value('@mode', 'nvarchar(256)'), + lock_type = N'RID' + FROM + ( + SELECT + dr.event_date, + database_id = ca.dr.value('@dbid', 'bigint'), + object_name = ca.dr.value('@objectname', 'nvarchar(1024)'), + lock_mode = ca.dr.value('@mode', 'nvarchar(256)'), + index_name = ca.dr.value('@indexname', 'nvarchar(256)'), + associatedObjectId = ca.dr.value('@associatedObjectId', 'bigint'), + dr = ca.dr.query('.') + FROM #deadlock_resource AS dr + CROSS APPLY dr.resource_xml.nodes('//resource-list/ridlock') AS ca(dr) + WHERE (ca.dr.exist('@objectname[. = sql:variable("@ObjectName")]') = 1 OR @ObjectName IS NULL) + ) AS ca CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l) CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l) - OPTION ( RECOMPILE ); + OPTION(RECOMPILE); + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - /*Parse row group locks*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + /*Parse row group locks*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Parse row group locks %s', 0, 1, @d) WITH NOWAIT; - INSERT #deadlock_owner_waiter WITH(TABLOCKX) - SELECT DISTINCT - ca.event_date, - ca.database_id, - ca.object_name, - ca.lock_mode, - ca.index_name, - ca.associatedObjectId, - w.l.value('@id', 'NVARCHAR(256)') AS waiter_id, - w.l.value('@mode', 'NVARCHAR(256)') AS waiter_mode, - o.l.value('@id', 'NVARCHAR(256)') AS owner_id, - o.l.value('@mode', 'NVARCHAR(256)') AS owner_mode, - N'ROWGROUP' AS lock_type - FROM ( - SELECT dr.event_date, - ca.dr.value('@dbid', 'BIGINT') AS database_id, - ca.dr.value('@objectname', 'NVARCHAR(256)') AS object_name, - ca.dr.value('@mode', 'NVARCHAR(256)') AS lock_mode, - ca.dr.value('@indexname', 'NVARCHAR(256)') AS index_name, - ca.dr.value('@associatedObjectId', 'BIGINT') AS associatedObjectId, - ca.dr.query('.') AS dr - FROM #deadlock_resource AS dr - CROSS APPLY dr.resource_xml.nodes('//resource-list/rowgrouplock') AS ca(dr) - ) AS ca + + INSERT + #deadlock_owner_waiter WITH(TABLOCKX) + SELECT DISTINCT + ca.event_date, + ca.database_id, + database_name = + ISNULL + ( + DB_NAME(ca.database_id), + N'UNKNOWN' + ), + ca.object_name, + ca.lock_mode, + ca.index_name, + ca.associatedObjectId, + waiter_id = w.l.value('@id', 'nvarchar(256)'), + waiter_mode = w.l.value('@mode', 'nvarchar(256)'), + owner_id = o.l.value('@id', 'nvarchar(256)'), + owner_mode = o.l.value('@mode', 'nvarchar(256)'), + lock_type = N'ROWGROUP' + FROM + ( + SELECT + dr.event_date, + database_id = ca.dr.value('@dbid', 'bigint'), + object_name = ca.dr.value('@objectname', 'nvarchar(1024)'), + lock_mode = ca.dr.value('@mode', 'nvarchar(256)'), + index_name = ca.dr.value('@indexname', 'nvarchar(256)'), + associatedObjectId = ca.dr.value('@associatedObjectId', 'bigint'), + dr = ca.dr.query('.') + FROM #deadlock_resource AS dr + CROSS APPLY dr.resource_xml.nodes('//resource-list/rowgrouplock') AS ca(dr) + WHERE (ca.dr.exist('@objectname[. = sql:variable("@ObjectName")]') = 1 OR @ObjectName IS NULL) + ) AS ca CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l) CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l) - OPTION ( RECOMPILE ); + OPTION(RECOMPILE); - UPDATE d - SET d.index_name = d.object_name - + '.HEAP' - FROM #deadlock_owner_waiter AS d - WHERE lock_type IN (N'HEAP', N'RID') - OPTION(RECOMPILE); + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Fixing heaps in #deadlock_owner_waiter %s', 0, 1, @d) WITH NOWAIT; + + UPDATE + d + SET + d.index_name = + d.object_name + N'.HEAP' + FROM #deadlock_owner_waiter AS d + WHERE d.lock_type IN + ( + N'HEAP', + N'RID' + ) + OPTION(RECOMPILE); + + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - /*Parse parallel deadlocks*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + /*Parse parallel deadlocks*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Parse parallel deadlocks %s', 0, 1, @d) WITH NOWAIT; - SELECT DISTINCT - ca.id, - ca.event_date, - ca.wait_type, - ca.node_id, - ca.waiter_type, - ca.owner_activity, - ca.waiter_activity, - ca.merging, - ca.spilling, - ca.waiting_to_close, - w.l.value('@id', 'NVARCHAR(256)') AS waiter_id, - o.l.value('@id', 'NVARCHAR(256)') AS owner_id - INTO #deadlock_resource_parallel - FROM ( - SELECT dr.event_date, - ca.dr.value('@id', 'NVARCHAR(256)') AS id, - ca.dr.value('@WaitType', 'NVARCHAR(256)') AS wait_type, - ca.dr.value('@nodeId', 'BIGINT') AS node_id, - /* These columns are in 2017 CU5 ONLY */ - ca.dr.value('@waiterType', 'NVARCHAR(256)') AS waiter_type, - ca.dr.value('@ownerActivity', 'NVARCHAR(256)') AS owner_activity, - ca.dr.value('@waiterActivity', 'NVARCHAR(256)') AS waiter_activity, - ca.dr.value('@merging', 'NVARCHAR(256)') AS merging, - ca.dr.value('@spilling', 'NVARCHAR(256)') AS spilling, - ca.dr.value('@waitingToClose', 'NVARCHAR(256)') AS waiting_to_close, - /* */ - ca.dr.query('.') AS dr - FROM #deadlock_resource AS dr - CROSS APPLY dr.resource_xml.nodes('//resource-list/exchangeEvent') AS ca(dr) - ) AS ca - CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l) - CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l) - OPTION ( RECOMPILE ); - - - /*Get rid of parallel noise*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + + SELECT DISTINCT + ca.id, + ca.event_date, + ca.wait_type, + ca.node_id, + ca.waiter_type, + ca.owner_activity, + ca.waiter_activity, + ca.merging, + ca.spilling, + ca.waiting_to_close, + waiter_id = w.l.value('@id', 'nvarchar(256)'), + owner_id = o.l.value('@id', 'nvarchar(256)') + INTO #deadlock_resource_parallel + FROM + ( + SELECT + dr.event_date, + id = ca.dr.value('@id', 'nvarchar(256)'), + wait_type = ca.dr.value('@WaitType', 'nvarchar(256)'), + node_id = ca.dr.value('@nodeId', 'bigint'), + /* These columns are in 2017 CU5+ ONLY */ + waiter_type = ca.dr.value('@waiterType', 'nvarchar(256)'), + owner_activity = ca.dr.value('@ownerActivity', 'nvarchar(256)'), + waiter_activity = ca.dr.value('@waiterActivity', 'nvarchar(256)'), + merging = ca.dr.value('@merging', 'nvarchar(256)'), + spilling = ca.dr.value('@spilling', 'nvarchar(256)'), + waiting_to_close = ca.dr.value('@waitingToClose', 'nvarchar(256)'), + /* These columns are in 2017 CU5+ ONLY */ + dr = ca.dr.query('.') + FROM #deadlock_resource AS dr + CROSS APPLY dr.resource_xml.nodes('//resource-list/exchangeEvent') AS ca(dr) + ) AS ca + CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l) + CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l) + OPTION(RECOMPILE); + + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + + /*Get rid of parallel noise*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Get rid of parallel noise %s', 0, 1, @d) WITH NOWAIT; - WITH c - AS - ( - SELECT *, ROW_NUMBER() OVER ( PARTITION BY drp.owner_id, drp.waiter_id ORDER BY drp.event_date ) AS rn - FROM #deadlock_resource_parallel AS drp - ) - DELETE FROM c - WHERE c.rn > 1 - OPTION ( RECOMPILE ); + WITH + c AS + ( + SELECT + *, + rn = + ROW_NUMBER() OVER + ( + PARTITION BY + drp.owner_id, + drp.waiter_id + ORDER BY + drp.event_date + ) + FROM #deadlock_resource_parallel AS drp + ) + DELETE + FROM c + WHERE c.rn > 1 + OPTION(RECOMPILE); + + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - /*Get rid of nonsense*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + /*Get rid of nonsense*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Get rid of nonsense %s', 0, 1, @d) WITH NOWAIT; - DELETE dow - FROM #deadlock_owner_waiter AS dow - WHERE dow.owner_id = dow.waiter_id - OPTION ( RECOMPILE ); - /*Add some nonsense*/ - ALTER TABLE #deadlock_process - ADD waiter_mode NVARCHAR(256), - owner_mode NVARCHAR(256), - is_victim AS CONVERT(BIT, CASE WHEN id = victim_id THEN 1 ELSE 0 END); + DELETE dow + FROM #deadlock_owner_waiter AS dow + WHERE dow.owner_id = dow.waiter_id + OPTION(RECOMPILE); - /*Update some nonsense*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + + /*Add some nonsense*/ + ALTER TABLE + #deadlock_process + ADD + waiter_mode nvarchar(256), + owner_mode nvarchar(256), + is_victim AS + CONVERT + ( + bit, + CASE + WHEN id = victim_id + THEN 1 + ELSE 0 + END + ) PERSISTED; + + /*Update some nonsense*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Update some nonsense part 1 %s', 0, 1, @d) WITH NOWAIT; - UPDATE dp - SET dp.owner_mode = dow.owner_mode - FROM #deadlock_process AS dp - JOIN #deadlock_owner_waiter AS dow - ON dp.id = dow.owner_id - AND dp.event_date = dow.event_date - WHERE dp.is_victim = 0 - OPTION ( RECOMPILE ); - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + UPDATE + dp + SET + dp.owner_mode = dow.owner_mode + FROM #deadlock_process AS dp + JOIN #deadlock_owner_waiter AS dow + ON dp.id = dow.owner_id + AND dp.event_date = dow.event_date + WHERE dp.is_victim = 0 + OPTION(RECOMPILE); + + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + + SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Update some nonsense part 2 %s', 0, 1, @d) WITH NOWAIT; - UPDATE dp - SET dp.waiter_mode = dow.waiter_mode - FROM #deadlock_process AS dp - JOIN #deadlock_owner_waiter AS dow - ON dp.victim_id = dow.waiter_id - AND dp.event_date = dow.event_date - WHERE dp.is_victim = 1 - OPTION ( RECOMPILE ); - /*Get Agent Job and Step names*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); + UPDATE + dp + SET + dp.waiter_mode = dow.waiter_mode + FROM #deadlock_process AS dp + JOIN #deadlock_owner_waiter AS dow + ON dp.victim_id = dow.waiter_id + AND dp.event_date = dow.event_date + WHERE dp.is_victim = 1 + OPTION(RECOMPILE); + + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + + /*Get Agent Job and Step names*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Get Agent Job and Step names %s', 0, 1, @d) WITH NOWAIT; - SELECT *, - CONVERT(UNIQUEIDENTIFIER, - CONVERT(XML, '').value('xs:hexBinary(substring(sql:column("x.job_id"), 0) )', 'BINARY(16)') - ) AS job_id_guid + + SELECT + x.event_date, + x.victim_id, + x.id, + x.database_id, + x.client_app, + x.job_id, + x.step_id, + job_id_guid = + CONVERT + ( + uniqueidentifier, + TRY_CAST + ( + N'' + AS xml + ).value('xs:hexBinary(substring(sql:column("x.job_id"), 0))', 'binary(16)') + ) INTO #agent_job - FROM ( - SELECT dp.event_date, - dp.victim_id, - dp.id, - dp.database_id, - dp.client_app, - SUBSTRING(dp.client_app, - CHARINDEX('0x', dp.client_app) + LEN('0x'), - 32 - ) AS job_id, - SUBSTRING(dp.client_app, - CHARINDEX(': Step ', dp.client_app) + LEN(': Step '), - CHARINDEX(')', dp.client_app, CHARINDEX(': Step ', dp.client_app)) - - (CHARINDEX(': Step ', dp.client_app) - + LEN(': Step ')) - ) AS step_id + FROM + ( + SELECT + dp.event_date, + dp.victim_id, + dp.id, + dp.database_id, + dp.client_app, + job_id = + SUBSTRING + ( + dp.client_app, + CHARINDEX(N'0x', dp.client_app) + LEN(N'0x'), + 32 + ), + step_id = + SUBSTRING + ( + dp.client_app, + CHARINDEX(N': Step ', dp.client_app) + LEN(N': Step '), + CHARINDEX(N')', dp.client_app, CHARINDEX(N': Step ', dp.client_app)) - + (CHARINDEX(N': Step ', dp.client_app) + LEN(N': Step ')) + ) FROM #deadlock_process AS dp - WHERE dp.client_app LIKE 'SQLAgent - %' - AND dp.client_app <> 'SQLAgent - Initial Boot Probe' + WHERE dp.client_app LIKE N'SQLAgent - %' + AND dp.client_app <> N'SQLAgent - Initial Boot Probe' ) AS x - OPTION ( RECOMPILE ); + OPTION(RECOMPILE); + ALTER TABLE + #agent_job + ADD + job_name nvarchar(256), + step_name nvarchar(256); - ALTER TABLE #agent_job ADD job_name NVARCHAR(256), - step_name NVARCHAR(256); + IF + ( + @Azure = 0 + AND @RDS = 0 + ) + BEGIN + SET @StringToExecute = + N' + UPDATE + aj + SET + aj.job_name = j.name, + aj.step_name = s.step_name + FROM msdb.dbo.sysjobs AS j + JOIN msdb.dbo.sysjobsteps AS s + ON j.job_id = s.job_id + JOIN #agent_job AS aj + ON aj.job_id_guid = j.job_id + AND aj.step_id = s.step_id + OPTION(RECOMPILE); + '; + + IF @Debug = 1 BEGIN PRINT @StringToExecute; END; + EXEC sys.sp_executesql + @StringToExecute; - IF SERVERPROPERTY('EngineEdition') NOT IN (5, 6) /* Azure SQL DB doesn't support querying jobs */ - AND NOT (LEFT(CAST(SERVERPROPERTY('ComputerNamePhysicalNetBIOS') AS VARCHAR(8000)), 8) = 'EC2AMAZ-' /* Neither does Amazon RDS Express Edition */ - AND LEFT(CAST(SERVERPROPERTY('MachineName') AS VARCHAR(8000)), 8) = 'EC2AMAZ-' - AND db_id('rdsadmin') IS NOT NULL - AND EXISTS(SELECT * FROM master.sys.all_objects WHERE name IN ('rds_startup_tasks', 'rds_help_revlogin', 'rds_hexadecimal', 'rds_failover_tracking', 'rds_database_tracking', 'rds_track_change')) - ) - BEGIN - SET @StringToExecute = N'UPDATE aj - SET aj.job_name = j.name, - aj.step_name = s.step_name - FROM msdb.dbo.sysjobs AS j - JOIN msdb.dbo.sysjobsteps AS s - ON j.job_id = s.job_id - JOIN #agent_job AS aj - ON aj.job_id_guid = j.job_id - AND aj.step_id = s.step_id - OPTION ( RECOMPILE );'; - EXEC(@StringToExecute); - END + END; - UPDATE dp - SET dp.client_app = - CASE WHEN dp.client_app LIKE N'SQLAgent - %' - THEN N'SQLAgent - Job: ' - + aj.job_name - + N' Step: ' - + aj.step_name + UPDATE + dp + SET + dp.client_app = + CASE + WHEN dp.client_app LIKE N'SQLAgent - %' + THEN N'SQLAgent - Job: ' + + aj.job_name + + N' Step: ' + + aj.step_name ELSE dp.client_app - END + END FROM #deadlock_process AS dp JOIN #agent_job AS aj - ON dp.event_date = aj.event_date - AND dp.victim_id = aj.victim_id - AND dp.id = aj.id - OPTION ( RECOMPILE ); + ON dp.event_date = aj.event_date + AND dp.victim_id = aj.victim_id + AND dp.id = aj.id + OPTION(RECOMPILE); - /*Get each and every table of all databases*/ - DECLARE @sysAssObjId AS TABLE (database_id bigint, partition_id bigint, schema_name varchar(255), table_name varchar(255)); - INSERT into @sysAssObjId EXECUTE sp_MSforeachdb - N'USE [?]; - SELECT DB_ID() as database_id, p.partition_id, s.name as schema_name, t.name as table_name - FROM sys.partitions p - LEFT JOIN sys.tables t ON t.object_id = p.object_id - LEFT JOIN sys.schemas s ON s.schema_id = t.schema_id - WHERE s.name is not NULL AND t.name is not NULL'; - - - /*Begin checks based on parsed values*/ - - /*Check 1 is deadlocks by database*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Check 1 %s', 0, 1, @d) WITH NOWAIT; - INSERT #deadlock_findings WITH (TABLOCKX) - ( check_id, database_name, object_name, finding_group, finding ) - SELECT 1 AS check_id, - DB_NAME(dp.database_id) AS database_name, - '-' AS object_name, - 'Total database locks' AS finding_group, - 'This database had ' - + CONVERT(NVARCHAR(20), COUNT_BIG(DISTINCT dp.event_date)) - + ' deadlocks.' - FROM #deadlock_process AS dp - WHERE 1 = 1 - AND (DB_NAME(dp.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (dp.event_date >= @StartDate OR @StartDate IS NULL) - AND (dp.event_date < @EndDate OR @EndDate IS NULL) - AND (dp.client_app = @AppName OR @AppName IS NULL) - AND (dp.host_name = @HostName OR @HostName IS NULL) - AND (dp.login_name = @LoginName OR @LoginName IS NULL) - GROUP BY DB_NAME(dp.database_id) - OPTION ( RECOMPILE ); + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - /*Check 2 is deadlocks by object*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Check 2 objects %s', 0, 1, @d) WITH NOWAIT; - INSERT #deadlock_findings WITH (TABLOCKX) - ( check_id, database_name, object_name, finding_group, finding ) - SELECT 2 AS check_id, - ISNULL(DB_NAME(dow.database_id), 'UNKNOWN') AS database_name, - ISNULL(dow.object_name, 'UNKNOWN') AS object_name, - 'Total object deadlocks' AS finding_group, - 'This object was involved in ' - + CONVERT(NVARCHAR(20), COUNT_BIG(DISTINCT dow.event_date)) - + ' deadlock(s).' - FROM #deadlock_owner_waiter AS dow - WHERE 1 = 1 - AND (DB_NAME(dow.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (dow.event_date >= @StartDate OR @StartDate IS NULL) - AND (dow.event_date < @EndDate OR @EndDate IS NULL) - AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) - GROUP BY DB_NAME(dow.database_id), dow.object_name - OPTION ( RECOMPILE ); + /*Get each and every table of all databases*/ + IF @Azure = 0 + BEGIN + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Inserting to @sysAssObjId %s', 0, 1, @d) WITH NOWAIT; - /*Check 2 continuation, number of locks per index*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Check 2 indexes %s', 0, 1, @d) WITH NOWAIT; - INSERT #deadlock_findings WITH (TABLOCKX) - ( check_id, database_name, object_name, finding_group, finding ) - SELECT 2 AS check_id, - ISNULL(DB_NAME(dow.database_id), 'UNKNOWN') AS database_name, - dow.index_name AS index_name, - 'Total index deadlocks' AS finding_group, - 'This index was involved in ' - + CONVERT(NVARCHAR(20), COUNT_BIG(DISTINCT dow.event_date)) - + ' deadlock(s).' - FROM #deadlock_owner_waiter AS dow - WHERE 1 = 1 - AND (DB_NAME(dow.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (dow.event_date >= @StartDate OR @StartDate IS NULL) - AND (dow.event_date < @EndDate OR @EndDate IS NULL) - AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) - AND dow.lock_type NOT IN (N'HEAP', N'RID') - AND dow.index_name is not null - GROUP BY DB_NAME(dow.database_id), dow.index_name - OPTION ( RECOMPILE ); + INSERT INTO + @sysAssObjId + ( + database_id, + partition_id, + schema_name, + table_name + ) + EXECUTE sys.sp_MSforeachdb + N' + SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + USE [?]; - /*Check 2 continuation, number of locks per heap*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Check 2 heaps %s', 0, 1, @d) WITH NOWAIT; - INSERT #deadlock_findings WITH (TABLOCKX) - ( check_id, database_name, object_name, finding_group, finding ) - SELECT 2 AS check_id, - ISNULL(DB_NAME(dow.database_id), 'UNKNOWN') AS database_name, - dow.index_name AS index_name, - 'Total heap deadlocks' AS finding_group, - 'This heap was involved in ' - + CONVERT(NVARCHAR(20), COUNT_BIG(DISTINCT dow.event_date)) - + ' deadlock(s).' - FROM #deadlock_owner_waiter AS dow - WHERE 1 = 1 - AND (DB_NAME(dow.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (dow.event_date >= @StartDate OR @StartDate IS NULL) - AND (dow.event_date < @EndDate OR @EndDate IS NULL) - AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) - AND dow.lock_type IN (N'HEAP', N'RID') - GROUP BY DB_NAME(dow.database_id), dow.index_name - OPTION ( RECOMPILE ); - + IF EXISTS + ( + SELECT + 1/0 + FROM #deadlock_process AS dp + WHERE dp.database_id = DB_ID() + ) + BEGIN + SELECT + database_id = + DB_ID(), + p.partition_id, + schema_name = + s.name, + table_name = + t.name + FROM sys.partitions p + JOIN sys.tables t + ON t.object_id = p.object_id + JOIN sys.schemas s + ON s.schema_id = t.schema_id + WHERE s.name IS NOT NULL + AND t.name IS NOT NULL + OPTION(RECOMPILE); + END; + '; - /*Check 3 looks for Serializable locking*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Check 3 %s', 0, 1, @d) WITH NOWAIT; - INSERT #deadlock_findings WITH (TABLOCKX) - ( check_id, database_name, object_name, finding_group, finding ) - SELECT 3 AS check_id, - DB_NAME(dp.database_id) AS database_name, - '-' AS object_name, - 'Serializable locking' AS finding_group, - 'This database has had ' + - CONVERT(NVARCHAR(20), COUNT_BIG(*)) + - ' instances of serializable deadlocks.' - AS finding - FROM #deadlock_process AS dp - WHERE dp.isolation_level LIKE 'serializable%' - AND (DB_NAME(dp.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (dp.event_date >= @StartDate OR @StartDate IS NULL) - AND (dp.event_date < @EndDate OR @EndDate IS NULL) - AND (dp.client_app = @AppName OR @AppName IS NULL) - AND (dp.host_name = @HostName OR @HostName IS NULL) - AND (dp.login_name = @LoginName OR @LoginName IS NULL) - GROUP BY DB_NAME(dp.database_id) - OPTION ( RECOMPILE ); + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + END; + IF @Azure = 1 + BEGIN + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Inserting to @sysAssObjId at %s', 0, 1, @d) WITH NOWAIT; - /*Check 4 looks for Repeatable Read locking*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Check 4 %s', 0, 1, @d) WITH NOWAIT; - INSERT #deadlock_findings WITH (TABLOCKX) - ( check_id, database_name, object_name, finding_group, finding ) - SELECT 4 AS check_id, - DB_NAME(dp.database_id) AS database_name, - '-' AS object_name, - 'Repeatable Read locking' AS finding_group, - 'This database has had ' + - CONVERT(NVARCHAR(20), COUNT_BIG(*)) + - ' instances of repeatable read deadlocks.' - AS finding - FROM #deadlock_process AS dp - WHERE dp.isolation_level LIKE 'repeatable read%' - AND (DB_NAME(dp.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (dp.event_date >= @StartDate OR @StartDate IS NULL) - AND (dp.event_date < @EndDate OR @EndDate IS NULL) - AND (dp.client_app = @AppName OR @AppName IS NULL) - AND (dp.host_name = @HostName OR @HostName IS NULL) - AND (dp.login_name = @LoginName OR @LoginName IS NULL) - GROUP BY DB_NAME(dp.database_id) - OPTION ( RECOMPILE ); + INSERT INTO + @sysAssObjId + ( + database_id, + partition_id, + schema_name, + table_name + ) + SELECT + database_id = + DB_ID(), + p.partition_id, + schema_name = + s.name, + table_name = + t.name + FROM sys.partitions p + JOIN sys.tables t + ON t.object_id = p.object_id + JOIN sys.schemas s + ON s.schema_id = t.schema_id + WHERE s.name IS NOT NULL + AND t.name IS NOT NULL + OPTION(RECOMPILE); + + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + END; + /*Begin checks based on parsed values*/ - /*Check 5 breaks down app, host, and login information*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Check 5 %s', 0, 1, @d) WITH NOWAIT; - INSERT #deadlock_findings WITH (TABLOCKX) - ( check_id, database_name, object_name, finding_group, finding ) - SELECT 5 AS check_id, - DB_NAME(dp.database_id) AS database_name, - '-' AS object_name, - 'Login, App, and Host locking' AS finding_group, - 'This database has had ' + - CONVERT(NVARCHAR(20), COUNT_BIG(DISTINCT dp.event_date)) + - ' instances of deadlocks involving the login ' + - ISNULL(dp.login_name, 'UNKNOWN') + - ' from the application ' + - ISNULL(dp.client_app, 'UNKNOWN') + - ' on host ' + - ISNULL(dp.host_name, 'UNKNOWN') - AS finding - FROM #deadlock_process AS dp - WHERE 1 = 1 - AND (DB_NAME(dp.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (dp.event_date >= @StartDate OR @StartDate IS NULL) - AND (dp.event_date < @EndDate OR @EndDate IS NULL) - AND (dp.client_app = @AppName OR @AppName IS NULL) - AND (dp.host_name = @HostName OR @HostName IS NULL) - AND (dp.login_name = @LoginName OR @LoginName IS NULL) - GROUP BY DB_NAME(dp.database_id), dp.login_name, dp.client_app, dp.host_name - OPTION ( RECOMPILE ); + /*Check 1 is deadlocks by database*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Check 1 database deadlocks %s', 0, 1, @d) WITH NOWAIT; + INSERT + #deadlock_findings WITH(TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding + ) + SELECT + check_id = 1, + dp.database_name, + object_name = N'-', + finding_group = N'Total Database Deadlocks', + finding = + N'This database had ' + + CONVERT + ( + nvarchar(20), + COUNT_BIG(DISTINCT dp.event_date) + ) + + N' deadlocks.' + FROM #deadlock_process AS dp + WHERE 1 = 1 + AND (dp.database_name = @DatabaseName OR @DatabaseName IS NULL) + AND (dp.event_date >= @StartDate OR @StartDate IS NULL) + AND (dp.event_date < @EndDate OR @EndDate IS NULL) + AND (dp.client_app = @AppName OR @AppName IS NULL) + AND (dp.host_name = @HostName OR @HostName IS NULL) + AND (dp.login_name = @LoginName OR @LoginName IS NULL) + GROUP BY dp.database_name + OPTION(RECOMPILE); - /*Check 6 breaks down the types of locks (object, page, key, etc.)*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Check 6 %s', 0, 1, @d) WITH NOWAIT; - WITH lock_types AS ( - SELECT DB_NAME(dp.database_id) AS database_name, - dow.object_name, - SUBSTRING(dp.wait_resource, 1, CHARINDEX(':', dp.wait_resource) -1) AS lock, - CONVERT(NVARCHAR(20), COUNT_BIG(DISTINCT dp.id)) AS lock_count - FROM #deadlock_process AS dp - JOIN #deadlock_owner_waiter AS dow - ON dp.id = dow.owner_id - AND dp.event_date = dow.event_date - WHERE 1 = 1 - AND (DB_NAME(dp.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (dp.event_date >= @StartDate OR @StartDate IS NULL) - AND (dp.event_date < @EndDate OR @EndDate IS NULL) - AND (dp.client_app = @AppName OR @AppName IS NULL) - AND (dp.host_name = @HostName OR @HostName IS NULL) - AND (dp.login_name = @LoginName OR @LoginName IS NULL) - AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) - AND dow.object_name IS NOT NULL - GROUP BY DB_NAME(dp.database_id), SUBSTRING(dp.wait_resource, 1, CHARINDEX(':', dp.wait_resource) - 1), dow.object_name - ) - INSERT #deadlock_findings WITH (TABLOCKX) - ( check_id, database_name, object_name, finding_group, finding ) - SELECT DISTINCT 6 AS check_id, - lt.database_name, - lt.object_name, - 'Types of locks by object' AS finding_group, - 'This object has had ' + - STUFF((SELECT DISTINCT N', ' + lt2.lock_count + ' ' + lt2.lock - FROM lock_types AS lt2 - WHERE lt2.database_name = lt.database_name - AND lt2.object_name = lt.object_name - FOR XML PATH(N''), TYPE).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 1, N'') - + ' locks' - FROM lock_types AS lt - OPTION ( RECOMPILE ); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + /*Check 2 is deadlocks with selects*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Check 2 select deadlocks %s', 0, 1, @d) WITH NOWAIT; - /*Check 7 gives you more info queries for sp_BlitzCache & BlitzQueryStore*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Check 7 part 1 %s', 0, 1, @d) WITH NOWAIT; - WITH deadlock_stack AS ( - SELECT DISTINCT - ds.id, - ds.proc_name, - ds.event_date, - PARSENAME(ds.proc_name, 3) AS database_name, - PARSENAME(ds.proc_name, 2) AS schema_name, - PARSENAME(ds.proc_name, 1) AS proc_only_name, - '''' + STUFF((SELECT DISTINCT N',' + ds2.sql_handle - FROM #deadlock_stack AS ds2 - WHERE ds2.id = ds.id - AND ds2.event_date = ds.event_date - FOR XML PATH(N''), TYPE).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 1, N'') + '''' AS sql_handle_csv - FROM #deadlock_stack AS ds - GROUP BY PARSENAME(ds.proc_name, 3), - PARSENAME(ds.proc_name, 2), - PARSENAME(ds.proc_name, 1), - ds.id, - ds.proc_name, - ds.event_date - ) - INSERT #deadlock_findings WITH (TABLOCKX) - ( check_id, database_name, object_name, finding_group, finding ) - SELECT DISTINCT 7 AS check_id, - ISNULL(DB_NAME(dow.database_id), 'UNKNOWN') AS database_name, - ds.proc_name AS object_name, - 'More Info - Query' AS finding_group, - 'EXEC sp_BlitzCache ' + - CASE WHEN ds.proc_name = 'adhoc' - THEN ' @OnlySqlHandles = ' + ds.sql_handle_csv - ELSE '@StoredProcName = ' + - QUOTENAME(ds.proc_only_name, '''') - END + - ';' AS finding - FROM deadlock_stack AS ds - JOIN #deadlock_owner_waiter AS dow - ON dow.owner_id = ds.id - AND dow.event_date = ds.event_date - WHERE 1 = 1 - AND (DB_NAME(dow.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (dow.event_date >= @StartDate OR @StartDate IS NULL) - AND (dow.event_date < @EndDate OR @EndDate IS NULL) - AND (dow.object_name = @StoredProcName OR @StoredProcName IS NULL) - OPTION ( RECOMPILE ); + INSERT + #deadlock_findings WITH(TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding + ) + SELECT + check_id = 2, + dow.database_name, + object_name = + N'You Might Need RCSI', + finding_group = N'Total Deadlocks Involving Selects', + finding = + N'There have been ' + + CONVERT + ( + nvarchar(20), + COUNT_BIG(DISTINCT dow.event_date) + ) + + N' deadlock(s) between read queries and modification queries.' + FROM #deadlock_owner_waiter AS dow + WHERE 1 = 1 + AND dow.lock_mode IN + ( + N'S', + N'IS' + ) + AND (dow.database_id = @DatabaseName OR @DatabaseName IS NULL) + AND (dow.event_date >= @StartDate OR @StartDate IS NULL) + AND (dow.event_date < @EndDate OR @EndDate IS NULL) + AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) + GROUP BY + dow.database_name + OPTION(RECOMPILE); - IF @ProductVersionMajor >= 13 - BEGIN - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Check 7 part 2 %s', 0, 1, @d) WITH NOWAIT; - WITH deadlock_stack AS ( - SELECT DISTINCT - ds.id, - ds.sql_handle, - ds.proc_name, - ds.event_date, - PARSENAME(ds.proc_name, 3) AS database_name, - PARSENAME(ds.proc_name, 2) AS schema_name, - PARSENAME(ds.proc_name, 1) AS proc_only_name - FROM #deadlock_stack AS ds - ) - INSERT #deadlock_findings WITH (TABLOCKX) - ( check_id, database_name, object_name, finding_group, finding ) - SELECT DISTINCT 7 AS check_id, - DB_NAME(dow.database_id) AS database_name, - ds.proc_name AS object_name, - 'More Info - Query' AS finding_group, - 'EXEC sp_BlitzQueryStore ' - + '@DatabaseName = ' - + QUOTENAME(ds.database_name, '''') - + ', ' - + '@StoredProcName = ' - + QUOTENAME(ds.proc_only_name, '''') - + ';' AS finding - FROM deadlock_stack AS ds - JOIN #deadlock_owner_waiter AS dow - ON dow.owner_id = ds.id - AND dow.event_date = ds.event_date - WHERE ds.proc_name <> 'adhoc' - AND (DB_NAME(dow.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (dow.event_date >= @StartDate OR @StartDate IS NULL) - AND (dow.event_date < @EndDate OR @EndDate IS NULL) - AND (dow.object_name = @StoredProcName OR @StoredProcName IS NULL) - OPTION ( RECOMPILE ); - END; - + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - /*Check 8 gives you stored proc deadlock counts*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Check 8 %s', 0, 1, @d) WITH NOWAIT; - INSERT #deadlock_findings WITH (TABLOCKX) - ( check_id, database_name, object_name, finding_group, finding ) - SELECT 8 AS check_id, - DB_NAME(dp.database_id) AS database_name, - ds.proc_name, - 'Stored Procedure Deadlocks', - 'The stored procedure ' - + PARSENAME(ds.proc_name, 2) - + '.' - + PARSENAME(ds.proc_name, 1) - + ' has been involved in ' - + CONVERT(NVARCHAR(10), COUNT_BIG(DISTINCT ds.id)) - + ' deadlocks.' - FROM #deadlock_stack AS ds - JOIN #deadlock_process AS dp - ON dp.id = ds.id - AND ds.event_date = dp.event_date - WHERE ds.proc_name <> 'adhoc' - AND (ds.proc_name = @StoredProcName OR @StoredProcName IS NULL) - AND (DB_NAME(dp.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (dp.event_date >= @StartDate OR @StartDate IS NULL) - AND (dp.event_date < @EndDate OR @EndDate IS NULL) - AND (dp.client_app = @AppName OR @AppName IS NULL) - AND (dp.host_name = @HostName OR @HostName IS NULL) - AND (dp.login_name = @LoginName OR @LoginName IS NULL) - GROUP BY DB_NAME(dp.database_id), ds.proc_name - OPTION(RECOMPILE); + /*Check 3 is deadlocks by object*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Check 3 object deadlocks %s', 0, 1, @d) WITH NOWAIT; + + INSERT + #deadlock_findings WITH(TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding + ) + SELECT + check_id = 3, + dow.database_name, + object_name = + ISNULL + ( + dow.object_name, + N'UNKNOWN' + ), + finding_group = N'Total Object Deadlocks', + finding = + N'This object was involved in ' + + CONVERT + ( + nvarchar(20), + COUNT_BIG(DISTINCT dow.event_date) + ) + + N' deadlock(s).' + FROM #deadlock_owner_waiter AS dow + WHERE 1 = 1 + AND (dow.database_id = @DatabaseName OR @DatabaseName IS NULL) + AND (dow.event_date >= @StartDate OR @StartDate IS NULL) + AND (dow.event_date < @EndDate OR @EndDate IS NULL) + AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) + GROUP BY + dow.database_name, + dow.object_name + OPTION(RECOMPILE); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - /*Check 9 gives you more info queries for sp_BlitzIndex */ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Check 9 %s', 0, 1, @d) WITH NOWAIT; - WITH bi AS ( - SELECT DISTINCT - dow.object_name, - DB_NAME(dow.database_id) as database_name, - a.schema_name AS schema_name, - a.table_name AS table_name - FROM #deadlock_owner_waiter AS dow - LEFT JOIN @sysAssObjId a ON a.database_id=dow.database_id AND a.partition_id = dow.associatedObjectId - WHERE 1 = 1 - AND (DB_NAME(dow.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (dow.event_date >= @StartDate OR @StartDate IS NULL) - AND (dow.event_date < @EndDate OR @EndDate IS NULL) - AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) - AND dow.object_name IS NOT NULL - ) - INSERT #deadlock_findings WITH (TABLOCKX) - ( check_id, database_name, object_name, finding_group, finding ) - SELECT 9 AS check_id, - bi.database_name, - bi.object_name, - 'More Info - Table' AS finding_group, - 'EXEC sp_BlitzIndex ' + - '@DatabaseName = ' + QUOTENAME(bi.database_name, '''') + - ', @SchemaName = ' + QUOTENAME(bi.schema_name, '''') + - ', @TableName = ' + QUOTENAME(bi.table_name, '''') + - ';' AS finding - FROM bi - OPTION ( RECOMPILE ); + /*Check 3 continuation, number of deadlocks per index*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Check 3 (continued) index deadlocks %s', 0, 1, @d) WITH NOWAIT; - /*Check 10 gets total deadlock wait time per object*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Check 10 %s', 0, 1, @d) WITH NOWAIT; - WITH chopsuey AS ( - SELECT DISTINCT - PARSENAME(dow.object_name, 3) AS database_name, - dow.object_name, - CONVERT(VARCHAR(10), (SUM(DISTINCT CONVERT(BIGINT, dp.wait_time)) / 1000) / 86400) AS wait_days, - CONVERT(VARCHAR(20), DATEADD(SECOND, (SUM(DISTINCT CONVERT(BIGINT, dp.wait_time)) / 1000), 0), 108) AS wait_time_hms - FROM #deadlock_owner_waiter AS dow - JOIN #deadlock_process AS dp - ON (dp.id = dow.owner_id OR dp.victim_id = dow.waiter_id) - AND dp.event_date = dow.event_date - WHERE 1 = 1 - AND (DB_NAME(dow.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (dow.event_date >= @StartDate OR @StartDate IS NULL) - AND (dow.event_date < @EndDate OR @EndDate IS NULL) - AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) - AND (dp.client_app = @AppName OR @AppName IS NULL) - AND (dp.host_name = @HostName OR @HostName IS NULL) - AND (dp.login_name = @LoginName OR @LoginName IS NULL) - GROUP BY PARSENAME(dow.object_name, 3), dow.object_name - ) - INSERT #deadlock_findings WITH (TABLOCKX) - ( check_id, database_name, object_name, finding_group, finding ) - SELECT 10 AS check_id, - cs.database_name, - cs.object_name, - 'Total object deadlock wait time' AS finding_group, - 'This object has had ' - + CONVERT(VARCHAR(10), cs.wait_days) - + ':' + CONVERT(VARCHAR(20), cs.wait_time_hms, 108) - + ' [d/h/m/s] of deadlock wait time.' AS finding - FROM chopsuey AS cs - WHERE cs.object_name IS NOT NULL - OPTION ( RECOMPILE ); - - /*Check 11 gets total deadlock wait time per database*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Check 11 %s', 0, 1, @d) WITH NOWAIT; - WITH wait_time AS ( - SELECT DB_NAME(dp.database_id) AS database_name, - SUM(CONVERT(BIGINT, dp.wait_time)) AS total_wait_time_ms - FROM #deadlock_process AS dp - WHERE 1 = 1 - AND (DB_NAME(dp.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (dp.event_date >= @StartDate OR @StartDate IS NULL) - AND (dp.event_date < @EndDate OR @EndDate IS NULL) - AND (dp.client_app = @AppName OR @AppName IS NULL) - AND (dp.host_name = @HostName OR @HostName IS NULL) - AND (dp.login_name = @LoginName OR @LoginName IS NULL) - GROUP BY DB_NAME(dp.database_id) - ) - INSERT #deadlock_findings WITH (TABLOCKX) - ( check_id, database_name, object_name, finding_group, finding ) - SELECT 11 AS check_id, - wt.database_name, - '-' AS object_name, - 'Total database deadlock wait time' AS finding_group, - 'This database has had ' - + CONVERT(VARCHAR(10), (SUM(DISTINCT CONVERT(BIGINT, wt.total_wait_time_ms)) / 1000) / 86400) - + ':' + CONVERT(VARCHAR(20), DATEADD(SECOND, (SUM(DISTINCT CONVERT(BIGINT, wt.total_wait_time_ms)) / 1000), 0), 108) - + ' [d/h/m/s] of deadlock wait time.' - FROM wait_time AS wt - GROUP BY wt.database_name - OPTION ( RECOMPILE ); + INSERT + #deadlock_findings WITH(TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding + ) + SELECT + check_id = 3, + dow.database_name, + index_name = dow.index_name, + finding_group = N'Total Index Deadlocks', + finding = + N'This index was involved in ' + + CONVERT + ( + nvarchar(20), + COUNT_BIG(DISTINCT dow.event_date) + ) + + N' deadlock(s).' + FROM #deadlock_owner_waiter AS dow + WHERE 1 = 1 + AND (dow.database_id = @DatabaseName OR @DatabaseName IS NULL) + AND (dow.event_date >= @StartDate OR @StartDate IS NULL) + AND (dow.event_date < @EndDate OR @EndDate IS NULL) + AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) + AND dow.lock_type NOT IN + ( + N'HEAP', + N'RID' + ) + AND dow.index_name IS NOT NULL + GROUP BY + dow.database_name, + dow.index_name + OPTION(RECOMPILE); + + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + + /*Check 3 continuation, number of deadlocks per heap*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Check 3 (continued) heap deadlocks %s', 0, 1, @d) WITH NOWAIT; + + INSERT + #deadlock_findings WITH(TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding + ) + SELECT + check_id = 3, + dow.database_name, + index_name = dow.index_name, + finding_group = N'Total Heap Deadlocks', + finding = + N'This heap was involved in ' + + CONVERT + ( + nvarchar(20), + COUNT_BIG(DISTINCT dow.event_date) + ) + + N' deadlock(s).' + FROM #deadlock_owner_waiter AS dow + WHERE 1 = 1 + AND (dow.database_id = @DatabaseName OR @DatabaseName IS NULL) + AND (dow.event_date >= @StartDate OR @StartDate IS NULL) + AND (dow.event_date < @EndDate OR @EndDate IS NULL) + AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) + AND dow.lock_type IN + ( + N'HEAP', + N'RID' + ) + GROUP BY + dow.database_name, + dow.index_name + OPTION(RECOMPILE); + + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + + /*Check 4 looks for Serializable deadlocks*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Check 4 serializable deadlocks %s', 0, 1, @d) WITH NOWAIT; + + INSERT + #deadlock_findings WITH(TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding + ) + SELECT + check_id = 4, + database_name = + dp.database_name, + object_name = N'-', + finding_group = N'Serializable Deadlocking', + finding = + N'This database has had ' + + CONVERT + ( + nvarchar(20), + COUNT_BIG(DISTINCT dp.event_date) + ) + + N' instances of Serializable deadlocks.' + FROM #deadlock_process AS dp + WHERE dp.isolation_level LIKE N'serializable%' + AND (dp.database_name = @DatabaseName OR @DatabaseName IS NULL) + AND (dp.event_date >= @StartDate OR @StartDate IS NULL) + AND (dp.event_date < @EndDate OR @EndDate IS NULL) + AND (dp.client_app = @AppName OR @AppName IS NULL) + AND (dp.host_name = @HostName OR @HostName IS NULL) + AND (dp.login_name = @LoginName OR @LoginName IS NULL) + GROUP BY dp.database_name + OPTION(RECOMPILE); + + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + + /*Check 5 looks for Repeatable Read deadlocks*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Check 5 repeatable read deadlocks %s', 0, 1, @d) WITH NOWAIT; + + INSERT + #deadlock_findings WITH(TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding + ) + SELECT + check_id = 5, + dp.database_name, + object_name = N'-', + finding_group = N'Repeatable Read Deadlocking', + finding = + N'This database has had ' + + CONVERT + ( + nvarchar(20), + COUNT_BIG(DISTINCT dp.event_date) + ) + + N' instances of Repeatable Read deadlocks.' + FROM #deadlock_process AS dp + WHERE dp.isolation_level LIKE N'repeatable%' + AND (dp.database_name = @DatabaseName OR @DatabaseName IS NULL) + AND (dp.event_date >= @StartDate OR @StartDate IS NULL) + AND (dp.event_date < @EndDate OR @EndDate IS NULL) + AND (dp.client_app = @AppName OR @AppName IS NULL) + AND (dp.host_name = @HostName OR @HostName IS NULL) + AND (dp.login_name = @LoginName OR @LoginName IS NULL) + GROUP BY dp.database_name + OPTION(RECOMPILE); + + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + + /*Check 6 breaks down app, host, and login information*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Check 6 app/host/login deadlocks %s', 0, 1, @d) WITH NOWAIT; + + INSERT + #deadlock_findings WITH(TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding + ) + SELECT + check_id = 6, + database_name = + dp.database_name, + object_name = N'-', + finding_group = N'Login, App, and Host deadlocks', + finding = + N'This database has had ' + + CONVERT + ( + nvarchar(20), + COUNT_BIG(DISTINCT dp.event_date) + ) + + N' instances of deadlocks involving the login ' + + ISNULL + ( + dp.login_name, + N'UNKNOWN' + ) + + N' from the application ' + + ISNULL + ( + dp.client_app, + N'UNKNOWN' + ) + + N' on host ' + + ISNULL + ( + dp.host_name, + N'UNKNOWN' + ) + + N'.' + FROM #deadlock_process AS dp + WHERE 1 = 1 + AND (dp.database_name = @DatabaseName OR @DatabaseName IS NULL) + AND (dp.event_date >= @StartDate OR @StartDate IS NULL) + AND (dp.event_date < @EndDate OR @EndDate IS NULL) + AND (dp.client_app = @AppName OR @AppName IS NULL) + AND (dp.host_name = @HostName OR @HostName IS NULL) + AND (dp.login_name = @LoginName OR @LoginName IS NULL) + GROUP BY + dp.database_name, + dp.login_name, + dp.client_app, + dp.host_name + OPTION(RECOMPILE); + + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + + /*Check 7 breaks down the types of deadlocks (object, page, key, etc.)*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Check 7 types of deadlocks %s', 0, 1, @d) WITH NOWAIT; + + WITH + lock_types AS + ( + SELECT + database_name = + dp.database_name, + dow.object_name, + lock = + CASE + WHEN CHARINDEX(N':', dp.wait_resource) > 0 + THEN SUBSTRING + ( + dp.wait_resource, + 1, + CHARINDEX(N':', dp.wait_resource) - 1 + ) + ELSE dp.wait_resource + END, + lock_count = + CONVERT + ( + nvarchar(20), + COUNT_BIG(DISTINCT dp.event_date) + ) + FROM #deadlock_process AS dp + JOIN #deadlock_owner_waiter AS dow + ON (dp.id = dow.owner_id + OR dp.victim_id = dow.waiter_id) + AND dp.event_date = dow.event_date + WHERE 1 = 1 + AND (dp.database_name = @DatabaseName OR @DatabaseName IS NULL) + AND (dp.event_date >= @StartDate OR @StartDate IS NULL) + AND (dp.event_date < @EndDate OR @EndDate IS NULL) + AND (dp.client_app = @AppName OR @AppName IS NULL) + AND (dp.host_name = @HostName OR @HostName IS NULL) + AND (dp.login_name = @LoginName OR @LoginName IS NULL) + AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) + AND dow.object_name IS NOT NULL + GROUP BY + dp.database_name, + CASE + WHEN CHARINDEX(N':', dp.wait_resource) > 0 + THEN SUBSTRING + ( + dp.wait_resource, + 1, + CHARINDEX(N':', dp.wait_resource) - 1 + ) + ELSE dp.wait_resource + END, + dow.object_name + ) + INSERT + #deadlock_findings WITH(TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding + ) + SELECT + check_id = 7, + lt.database_name, + lt.object_name, + finding_group = N'Types of locks by object', + finding = + N'This object has had ' + + STUFF + ( + ( + SELECT + N', ' + + lt2.lock_count + + N' ' + + lt2.lock + FROM lock_types AS lt2 + WHERE lt2.database_name = lt.database_name + AND lt2.object_name = lt.object_name + FOR XML + PATH(N''), + TYPE + ).value(N'.[1]', N'nvarchar(MAX)'), + 1, + 1, + N'' + ) + N' locks.' + FROM lock_types AS lt + OPTION(RECOMPILE); + + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + + /*Check 8 gives you more info queries for sp_BlitzCache & BlitzQueryStore*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Check 8 more info part 1 BlitzCache %s', 0, 1, @d) WITH NOWAIT; + + WITH + deadlock_stack AS + ( + SELECT DISTINCT + ds.id, + ds.proc_name, + ds.event_date, + database_name = + PARSENAME(ds.proc_name, 3), + schema_name = + PARSENAME(ds.proc_name, 2), + proc_only_name = + PARSENAME(ds.proc_name, 1), + sql_handle_csv = + N'''' + + STUFF + ( + ( + SELECT DISTINCT + N',' + + ds2.sql_handle + FROM #deadlock_stack AS ds2 + WHERE ds2.id = ds.id + AND ds2.event_date = ds.event_date + AND ds2.sql_handle <> 0x + FOR XML + PATH(N''), + TYPE + ).value(N'.[1]', N'nvarchar(MAX)'), + 1, + 1, + N'' + ) + + N'''' + FROM #deadlock_stack AS ds + WHERE ds.sql_handle <> 0x + GROUP BY + PARSENAME(ds.proc_name, 3), + PARSENAME(ds.proc_name, 2), + PARSENAME(ds.proc_name, 1), + ds.id, + ds.proc_name, + ds.event_date + ) + INSERT + #deadlock_findings WITH(TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding + ) + SELECT DISTINCT + check_id = 8, + dow.database_name, + object_name = ds.proc_name, + finding_group = N'More Info - Query', + finding = N'EXEC sp_BlitzCache ' + + CASE + WHEN ds.proc_name = N'adhoc' + THEN N'@OnlySqlHandles = ' + ds.sql_handle_csv + ELSE N'@StoredProcName = ' + QUOTENAME(ds.proc_only_name, N'''') + END + N';' + FROM deadlock_stack AS ds + JOIN #deadlock_owner_waiter AS dow + ON dow.owner_id = ds.id + AND dow.event_date = ds.event_date + WHERE 1 = 1 + AND (dow.database_id = @DatabaseName OR @DatabaseName IS NULL) + AND (dow.event_date >= @StartDate OR @StartDate IS NULL) + AND (dow.event_date < @EndDate OR @EndDate IS NULL) + AND (dow.object_name = @StoredProcName OR @StoredProcName IS NULL) + OPTION(RECOMPILE); + + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + + IF (@ProductVersionMajor >= 13 OR @Azure = 1) + BEGIN + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Check 8 more info part 2 BlitzQueryStore %s', 0, 1, @d) WITH NOWAIT; + + WITH + deadlock_stack AS + ( + SELECT DISTINCT + ds.id, + ds.sql_handle, + ds.proc_name, + ds.event_date, + database_name = + PARSENAME(ds.proc_name, 3), + schema_name = + PARSENAME(ds.proc_name, 2), + proc_only_name = + PARSENAME(ds.proc_name, 1) + FROM #deadlock_stack AS ds + ) + INSERT + #deadlock_findings WITH(TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding + ) + SELECT DISTINCT + check_id = 8, + dow.database_name, + object_name = ds.proc_name, + finding_group = N'More Info - Query', + finding = + N'EXEC sp_BlitzQueryStore ' + + N'@DatabaseName = ' + + QUOTENAME(ds.database_name, N'''') + + N', ' + + N'@StoredProcName = ' + + QUOTENAME(ds.proc_only_name, N'''') + + N';' + FROM deadlock_stack AS ds + JOIN #deadlock_owner_waiter AS dow + ON dow.owner_id = ds.id + AND dow.event_date = ds.event_date + WHERE ds.proc_name <> N'adhoc' + AND (dow.database_id = @DatabaseName OR @DatabaseName IS NULL) + AND (dow.event_date >= @StartDate OR @StartDate IS NULL) + AND (dow.event_date < @EndDate OR @EndDate IS NULL) + AND (dow.object_name = @StoredProcName OR @StoredProcName IS NULL) + OPTION(RECOMPILE); + + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + END; - /*Check 12 gets total deadlock wait time for SQL Agent*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Check 12 %s', 0, 1, @d) WITH NOWAIT; - INSERT #deadlock_findings WITH (TABLOCKX) - ( check_id, database_name, object_name, finding_group, finding ) - SELECT 12, - DB_NAME(aj.database_id), - 'SQLAgent - Job: ' - + aj.job_name - + ' Step: ' - + aj.step_name, - 'Agent Job Deadlocks', - RTRIM(COUNT(*)) + ' deadlocks from this Agent Job and Step' + /*Check 9 gives you stored procedure deadlock counts*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Check 9 stored procedure deadlocks %s', 0, 1, @d) WITH NOWAIT; + + INSERT + #deadlock_findings WITH(TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding + ) + SELECT + check_id = 9, + database_name = + dp.database_name, + object_name = ds.proc_name, + finding_group = N'Stored Procedure Deadlocks', + finding = + N'The stored procedure ' + + PARSENAME(ds.proc_name, 2) + + N'.' + + PARSENAME(ds.proc_name, 1) + + N' has been involved in ' + + CONVERT + ( + nvarchar(10), + COUNT_BIG(DISTINCT ds.id) + ) + + N' deadlocks.' + FROM #deadlock_stack AS ds + JOIN #deadlock_process AS dp + ON dp.id = ds.id + AND ds.event_date = dp.event_date + WHERE ds.proc_name <> N'adhoc' + AND (ds.proc_name = @StoredProcName OR @StoredProcName IS NULL) + AND (dp.database_name = @DatabaseName OR @DatabaseName IS NULL) + AND (dp.event_date >= @StartDate OR @StartDate IS NULL) + AND (dp.event_date < @EndDate OR @EndDate IS NULL) + AND (dp.client_app = @AppName OR @AppName IS NULL) + AND (dp.host_name = @HostName OR @HostName IS NULL) + AND (dp.login_name = @LoginName OR @LoginName IS NULL) + GROUP BY + dp.database_name, + ds.proc_name + OPTION(RECOMPILE); + + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + + /*Check 10 gives you more info queries for sp_BlitzIndex */ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Check 10 more info, BlitzIndex %s', 0, 1, @d) WITH NOWAIT; + + WITH + bi AS + ( + SELECT DISTINCT + dow.object_name, + dow.database_name, + schema_name = s.schema_name, + table_name = s.table_name + FROM #deadlock_owner_waiter AS dow + JOIN @sysAssObjId AS s + ON s.database_id = dow.database_id + AND s.partition_id = dow.associatedObjectId + WHERE 1 = 1 + AND (dow.database_id = @DatabaseName OR @DatabaseName IS NULL) + AND (dow.event_date >= @StartDate OR @StartDate IS NULL) + AND (dow.event_date < @EndDate OR @EndDate IS NULL) + AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) + AND dow.object_name IS NOT NULL + ) + INSERT + #deadlock_findings WITH(TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding + ) + SELECT DISTINCT + check_id = 10, + bi.database_name, + bi.object_name, + finding_group = N'More Info - Table', + finding = + N'EXEC sp_BlitzIndex ' + + N'@DatabaseName = ' + + QUOTENAME(bi.database_name, N'''') + + N', @SchemaName = ' + + QUOTENAME(bi.schema_name, N'''') + + N', @TableName = ' + + QUOTENAME(bi.table_name, N'''') + + N';' + FROM bi + OPTION(RECOMPILE); + + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + + /*Check 11 gets total deadlock wait time per object*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Check 11 deadlock wait time per object %s', 0, 1, @d) WITH NOWAIT; + + WITH + chopsuey AS + ( + + SELECT + database_name = + dp.database_name, + dow.object_name, + wait_days = + CONVERT + ( + nvarchar(30), + ( + SUM + ( + CONVERT + ( + bigint, + dp.wait_time + ) + ) / 1000 / 86400 + ) + ), + wait_time_hms = + CONVERT + ( + nvarchar(30), + DATEADD + ( + MILLISECOND, + ( + SUM + ( + CONVERT + ( + bigint, + dp.wait_time + ) + ) + ), + 0 + ), + 14 + ) + FROM #deadlock_owner_waiter AS dow + JOIN #deadlock_process AS dp + ON (dp.id = dow.owner_id + OR dp.victim_id = dow.waiter_id) + AND dp.event_date = dow.event_date + WHERE 1 = 1 + AND (dp.database_name = @DatabaseName OR @DatabaseName IS NULL) + AND (dp.event_date >= @StartDate OR @StartDate IS NULL) + AND (dp.event_date < @EndDate OR @EndDate IS NULL) + AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) + AND (dp.client_app = @AppName OR @AppName IS NULL) + AND (dp.host_name = @HostName OR @HostName IS NULL) + AND (dp.login_name = @LoginName OR @LoginName IS NULL) + GROUP BY + dp.database_name, + dow.object_name + ) + INSERT + #deadlock_findings WITH(TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding + ) + SELECT + check_id = 11, + cs.database_name, + cs.object_name, + finding_group = N'Total object deadlock wait time', + finding = + N'This object has had ' + + CONVERT + ( + nvarchar(30), + cs.wait_days + ) + + N' ' + + CONVERT + ( + nvarchar(30), + cs.wait_time_hms, + 14 + ) + + N' [dd hh:mm:ss:ms] of deadlock wait time.' + FROM chopsuey AS cs + WHERE cs.object_name IS NOT NULL + OPTION(RECOMPILE); + + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + + /*Check 12 gets total deadlock wait time per database*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Check 12 deadlock wait time per database %s', 0, 1, @d) WITH NOWAIT; + + WITH + wait_time AS + ( + SELECT + database_name = + dp.database_name, + total_wait_time_ms = + SUM + ( + CONVERT + ( + bigint, + dp.wait_time + ) + ) + FROM #deadlock_owner_waiter AS dow + JOIN #deadlock_process AS dp + ON (dp.id = dow.owner_id + OR dp.victim_id = dow.waiter_id) + AND dp.event_date = dow.event_date + WHERE 1 = 1 + AND (dp.database_name = @DatabaseName OR @DatabaseName IS NULL) + AND (dp.event_date >= @StartDate OR @StartDate IS NULL) + AND (dp.event_date < @EndDate OR @EndDate IS NULL) + AND (dp.client_app = @AppName OR @AppName IS NULL) + AND (dp.host_name = @HostName OR @HostName IS NULL) + AND (dp.login_name = @LoginName OR @LoginName IS NULL) + GROUP BY + dp.database_name + ) + INSERT + #deadlock_findings WITH(TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding + ) + SELECT + check_id = 12, + wt.database_name, + object_name = N'-', + finding_group = N'Total database deadlock wait time', + N'This database has had ' + + CONVERT + ( + nvarchar(30), + ( + SUM + ( + CONVERT + ( + bigint, + wt.total_wait_time_ms + ) + ) / 1000 / 86400 + ) + ) + + N' ' + + CONVERT + ( + nvarchar(30), + DATEADD + ( + MILLISECOND, + ( + SUM + ( + CONVERT + ( + bigint, + wt.total_wait_time_ms + ) + ) + ), + 0 + ), + 14 + ) + + N' [dd hh:mm:ss:ms] of deadlock wait time.' + FROM wait_time AS wt + GROUP BY + wt.database_name + OPTION(RECOMPILE); + + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + + /*Check 13 gets total deadlock wait time for SQL Agent*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Check 13 deadlock count for SQL Agent %s', 0, 1, @d) WITH NOWAIT; + + INSERT + #deadlock_findings WITH(TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding + ) + SELECT + check_id = 13, + database_name = + DB_NAME(aj.database_id), + object_name = + N'SQLAgent - Job: ' + + aj.job_name + + N' Step: ' + + aj.step_name, + finding_group = N'Agent Job Deadlocks', + finding = + N'There have been ' + + RTRIM(COUNT_BIG(DISTINCT aj.event_date)) + + N' deadlocks from this Agent Job and Step.' FROM #agent_job AS aj - GROUP BY DB_NAME(aj.database_id), aj.job_name, aj.step_name - OPTION ( RECOMPILE ); + GROUP BY + DB_NAME(aj.database_id), + aj.job_name, + aj.step_name + OPTION(RECOMPILE); - /*Check 13 is total parallel deadlocks*/ - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Check 13 %s', 0, 1, @d) WITH NOWAIT; - INSERT #deadlock_findings WITH (TABLOCKX) - ( check_id, database_name, object_name, finding_group, finding ) - SELECT 13 AS check_id, - N'-' AS database_name, - '-' AS object_name, - 'Total parallel deadlocks' AS finding_group, - 'There have been ' - + CONVERT(NVARCHAR(20), COUNT_BIG(DISTINCT drp.event_date)) - + ' parallel deadlocks.' - FROM #deadlock_resource_parallel AS drp - WHERE 1 = 1 - HAVING COUNT_BIG(DISTINCT drp.event_date) > 0 - OPTION ( RECOMPILE ); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - /*Thank you goodnight*/ - INSERT #deadlock_findings WITH (TABLOCKX) - ( check_id, database_name, object_name, finding_group, finding ) - VALUES ( -1, - N'sp_BlitzLock ' + CAST(CONVERT(DATETIME, @VersionDate, 102) AS VARCHAR(100)), - N'SQL Server First Responder Kit', - N'http://FirstResponderKit.org/', - N'To get help or add your own contributions, join us at http://FirstResponderKit.org.'); + /*Check 14 is total parallel deadlocks*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Check 14 parallel deadlocks %s', 0, 1, @d) WITH NOWAIT; - + INSERT + #deadlock_findings WITH(TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding + ) + SELECT + check_id = 14, + database_name = N'-', + object_name = N'-', + finding_group = N'Total parallel deadlocks', + finding = + N'There have been ' + + CONVERT + ( + nvarchar(20), + COUNT_BIG(DISTINCT drp.event_date) + ) + + N' parallel deadlocks.' + FROM #deadlock_resource_parallel AS drp + HAVING COUNT_BIG(DISTINCT drp.event_date) > 0 + OPTION(RECOMPILE); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - /*Results*/ - /*Break in case of emergency*/ - --CREATE CLUSTERED INDEX cx_whatever ON #deadlock_process (event_date, id); - --CREATE CLUSTERED INDEX cx_whatever ON #deadlock_resource_parallel (event_date, owner_id); - --CREATE CLUSTERED INDEX cx_whatever ON #deadlock_owner_waiter (event_date, owner_id, waiter_id); - IF(@OutputDatabaseCheck = 0) - BEGIN - - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Results 1 %s', 0, 1, @d) WITH NOWAIT; - WITH deadlocks - AS ( SELECT N'Regular Deadlock' AS deadlock_type, - dp.event_date, - dp.id, - dp.victim_id, - dp.spid, - dp.database_id, - dp.priority, - dp.log_used, - dp.wait_resource COLLATE DATABASE_DEFAULT AS wait_resource, - CONVERT( - XML, - STUFF(( SELECT DISTINCT NCHAR(10) - + N' ' - + ISNULL(c.object_name, N'') - + N' ' COLLATE DATABASE_DEFAULT AS object_name - FROM #deadlock_owner_waiter AS c - WHERE ( dp.id = c.owner_id - OR dp.victim_id = c.waiter_id ) - AND dp.event_date = c.event_date - FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(4000)'), - 1, 1, N'')) AS object_names, - dp.wait_time, - dp.transaction_name, - dp.last_tran_started, - dp.last_batch_started, - dp.last_batch_completed, - dp.lock_mode, - dp.transaction_count, - dp.client_app, - dp.host_name, - dp.login_name, - dp.isolation_level, - dp.process_xml.value('(//process/inputbuf/text())[1]', 'NVARCHAR(MAX)') AS inputbuf, - DENSE_RANK() OVER ( ORDER BY dp.event_date ) AS en, - ROW_NUMBER() OVER ( PARTITION BY dp.event_date ORDER BY dp.event_date ) -1 AS qn, - ROW_NUMBER() OVER ( PARTITION BY dp.event_date, dp.id ORDER BY dp.event_date ) AS dn, - dp.is_victim, - ISNULL(dp.owner_mode, '-') AS owner_mode, - NULL AS owner_waiter_type, - NULL AS owner_activity, - NULL AS owner_waiter_activity, - NULL AS owner_merging, - NULL AS owner_spilling, - NULL AS owner_waiting_to_close, - ISNULL(dp.waiter_mode, '-') AS waiter_mode, - NULL AS waiter_waiter_type, - NULL AS waiter_owner_activity, - NULL AS waiter_waiter_activity, - NULL AS waiter_merging, - NULL AS waiter_spilling, - NULL AS waiter_waiting_to_close, - dp.deadlock_graph - FROM #deadlock_process AS dp - WHERE dp.victim_id IS NOT NULL - AND dp.is_parallel = 0 - - UNION ALL - - SELECT N'Parallel Deadlock' AS deadlock_type, - dp.event_date, - dp.id, - dp.victim_id, - dp.spid, - dp.database_id, - dp.priority, - dp.log_used, - dp.wait_resource COLLATE DATABASE_DEFAULT, - CONVERT( - XML, - STUFF(( SELECT DISTINCT NCHAR(10) - + N' ' - + ISNULL(c.object_name, N'') - + N' ' COLLATE DATABASE_DEFAULT AS object_name - FROM #deadlock_owner_waiter AS c - WHERE ( dp.id = c.owner_id - OR dp.victim_id = c.waiter_id ) - AND dp.event_date = c.event_date - FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(4000)'), - 1, 1, N'')) AS object_names, - dp.wait_time, - dp.transaction_name, - dp.last_tran_started, - dp.last_batch_started, - dp.last_batch_completed, - dp.lock_mode, - dp.transaction_count, - dp.client_app, - dp.host_name, - dp.login_name, - dp.isolation_level, - dp.process_xml.value('(//process/inputbuf/text())[1]', 'NVARCHAR(MAX)') AS inputbuf, - DENSE_RANK() OVER ( ORDER BY dp.event_date ) AS en, - ROW_NUMBER() OVER ( PARTITION BY dp.event_date ORDER BY dp.event_date ) -1 AS qn, - ROW_NUMBER() OVER ( PARTITION BY dp.event_date, dp.id ORDER BY dp.event_date ) AS dn, - 1 AS is_victim, - cao.wait_type COLLATE DATABASE_DEFAULT AS owner_mode, - cao.waiter_type AS owner_waiter_type, - cao.owner_activity AS owner_activity, - cao.waiter_activity AS owner_waiter_activity, - cao.merging AS owner_merging, - cao.spilling AS owner_spilling, - cao.waiting_to_close AS owner_waiting_to_close, - caw.wait_type COLLATE DATABASE_DEFAULT AS waiter_mode, - caw.waiter_type AS waiter_waiter_type, - caw.owner_activity AS waiter_owner_activity, - caw.waiter_activity AS waiter_waiter_activity, - caw.merging AS waiter_merging, - caw.spilling AS waiter_spilling, - caw.waiting_to_close AS waiter_waiting_to_close, - dp.deadlock_graph - FROM #deadlock_process AS dp - CROSS APPLY (SELECT TOP 1 * FROM #deadlock_resource_parallel AS drp WHERE drp.owner_id = dp.id AND drp.wait_type = 'e_waitPipeNewRow' ORDER BY drp.event_date) AS cao - CROSS APPLY (SELECT TOP 1 * FROM #deadlock_resource_parallel AS drp WHERE drp.owner_id = dp.id AND drp.wait_type = 'e_waitPipeGetRow' ORDER BY drp.event_date) AS caw - WHERE dp.is_parallel = 1 ) - insert into DeadLockTbl ( - ServerName, - deadlock_type, - event_date, - database_name, - spid, - deadlock_group, - query, - object_names, - isolation_level, - owner_mode, - waiter_mode, - transaction_count, - login_name, - host_name, - client_app, - wait_time, - wait_resource, - priority, - log_used, - last_tran_started, - last_batch_started, - last_batch_completed, - transaction_name, - owner_waiter_type, - owner_activity, - owner_waiter_activity, - owner_merging, - owner_spilling, - owner_waiting_to_close, - waiter_waiter_type, - waiter_owner_activity, - waiter_waiter_activity, - waiter_merging, - waiter_spilling, - waiter_waiting_to_close, - deadlock_graph - ) - SELECT @ServerName, - d.deadlock_type, - d.event_date, - DB_NAME(d.database_id) AS database_name, - d.spid, - 'Deadlock #' - + CONVERT(NVARCHAR(10), d.en) - + ', Query #' - + CASE WHEN d.qn = 0 THEN N'1' ELSE CONVERT(NVARCHAR(10), d.qn) END - + CASE WHEN d.is_victim = 1 THEN ' - VICTIM' ELSE '' END - AS deadlock_group, - CONVERT(XML, N'') AS query, - d.object_names, - d.isolation_level, - d.owner_mode, - d.waiter_mode, - d.transaction_count, - d.login_name, - d.host_name, - d.client_app, - d.wait_time, - d.wait_resource, - d.priority, - d.log_used, - d.last_tran_started, - d.last_batch_started, - d.last_batch_completed, - d.transaction_name, - /*These columns will be NULL for regular (non-parallel) deadlocks*/ - d.owner_waiter_type, - d.owner_activity, - d.owner_waiter_activity, - d.owner_merging, - d.owner_spilling, - d.owner_waiting_to_close, - d.waiter_waiter_type, - d.waiter_owner_activity, - d.waiter_waiter_activity, - d.waiter_merging, - d.waiter_spilling, - d.waiter_waiting_to_close, - d.deadlock_graph - FROM deadlocks AS d - WHERE d.dn = 1 - AND (is_victim = @VictimsOnly OR @VictimsOnly = 0) - AND d.qn < CASE WHEN d.deadlock_type = N'Parallel Deadlock' THEN 2 ELSE 2147483647 END - AND (DB_NAME(d.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (d.event_date >= @StartDate OR @StartDate IS NULL) - AND (d.event_date < @EndDate OR @EndDate IS NULL) - AND (CONVERT(NVARCHAR(MAX), d.object_names) LIKE '%' + @ObjectName + '%' OR @ObjectName IS NULL) - AND (d.client_app = @AppName OR @AppName IS NULL) - AND (d.host_name = @HostName OR @HostName IS NULL) - AND (d.login_name = @LoginName OR @LoginName IS NULL) - ORDER BY d.event_date, is_victim DESC - OPTION ( RECOMPILE ); - - drop SYNONYM DeadLockTbl; --done insert into blitzlock table going to insert into findings table first create synonym. + /*Check 15 is total deadlocks involving sleeping sessions*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Check 15 sleeping deadlocks %s', 0, 1, @d) WITH NOWAIT; - -- RAISERROR('att deadlock findings', 0, 1) WITH NOWAIT; - + INSERT + #deadlock_findings WITH(TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding + ) + SELECT + check_id = 15, + database_name = N'-', + object_name = N'-', + finding_group = N'Total deadlocks involving sleeping sessions', + finding = + N'There have been ' + + CONVERT + ( + nvarchar(20), + COUNT_BIG(DISTINCT dp.event_date) + ) + + N' sleepy deadlocks.' + FROM #deadlock_process AS dp + WHERE dp.status = N'sleeping' + HAVING COUNT_BIG(DISTINCT dp.event_date) > 0 + OPTION(RECOMPILE); - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Findings %s', 0, 1, @d) WITH NOWAIT; + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - Insert into DeadlockFindings (ServerName,check_id,database_name,object_name,finding_group,finding) - SELECT @ServerName,df.check_id, df.database_name, df.object_name, df.finding_group, df.finding - FROM #deadlock_findings AS df - ORDER BY df.check_id - OPTION ( RECOMPILE ); + /*Check 16 is total deadlocks involving implicit transactions*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Check 16 implicit transaction deadlocks %s', 0, 1, @d) WITH NOWAIT; - drop SYNONYM DeadlockFindings; --done with inserting. -END -ELSE --Output to database is not set output to client app - BEGIN - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Results 1 %s', 0, 1, @d) WITH NOWAIT; - WITH deadlocks - AS ( SELECT N'Regular Deadlock' AS deadlock_type, - dp.event_date, - dp.id, - dp.victim_id, - dp.spid, - dp.database_id, - dp.priority, - dp.log_used, - dp.wait_resource COLLATE DATABASE_DEFAULT AS wait_resource, - CONVERT( - XML, - STUFF(( SELECT DISTINCT NCHAR(10) - + N' ' - + ISNULL(c.object_name, N'') - + N' ' COLLATE DATABASE_DEFAULT AS object_name - FROM #deadlock_owner_waiter AS c - WHERE ( dp.id = c.owner_id - OR dp.victim_id = c.waiter_id ) - AND dp.event_date = c.event_date - FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(4000)'), - 1, 1, N'')) AS object_names, - dp.wait_time, - dp.transaction_name, - dp.last_tran_started, - dp.last_batch_started, - dp.last_batch_completed, - dp.lock_mode, - dp.transaction_count, - dp.client_app, - dp.host_name, - dp.login_name, - dp.isolation_level, - dp.process_xml.value('(//process/inputbuf/text())[1]', 'NVARCHAR(MAX)') AS inputbuf, - DENSE_RANK() OVER ( ORDER BY dp.event_date ) AS en, - ROW_NUMBER() OVER ( PARTITION BY dp.event_date ORDER BY dp.event_date ) -1 AS qn, - ROW_NUMBER() OVER ( PARTITION BY dp.event_date, dp.id ORDER BY dp.event_date ) AS dn, - dp.is_victim, - ISNULL(dp.owner_mode, '-') AS owner_mode, - NULL AS owner_waiter_type, - NULL AS owner_activity, - NULL AS owner_waiter_activity, - NULL AS owner_merging, - NULL AS owner_spilling, - NULL AS owner_waiting_to_close, - ISNULL(dp.waiter_mode, '-') AS waiter_mode, - NULL AS waiter_waiter_type, - NULL AS waiter_owner_activity, - NULL AS waiter_waiter_activity, - NULL AS waiter_merging, - NULL AS waiter_spilling, - NULL AS waiter_waiting_to_close, - dp.deadlock_graph - FROM #deadlock_process AS dp - WHERE dp.victim_id IS NOT NULL - AND dp.is_parallel = 0 + INSERT + #deadlock_findings WITH(TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding + ) + SELECT + check_id = 14, + database_name = N'-', + object_name = N'-', + finding_group = N'Total implicit transaction deadlocks', + finding = + N'There have been ' + + CONVERT + ( + nvarchar(20), + COUNT_BIG(DISTINCT dp.event_date) + ) + + N' implicit transaction deadlocks.' + FROM #deadlock_process AS dp + WHERE dp.transaction_name = N'implicit_transaction' + HAVING COUNT_BIG(DISTINCT dp.event_date) > 0 + OPTION(RECOMPILE); - UNION ALL - - SELECT N'Parallel Deadlock' AS deadlock_type, - dp.event_date, - dp.id, - dp.victim_id, - dp.spid, - dp.database_id, - dp.priority, - dp.log_used, - dp.wait_resource COLLATE DATABASE_DEFAULT, - CONVERT( - XML, - STUFF(( SELECT DISTINCT NCHAR(10) - + N' ' - + ISNULL(c.object_name, N'') - + N' ' COLLATE DATABASE_DEFAULT AS object_name - FROM #deadlock_owner_waiter AS c - WHERE ( dp.id = c.owner_id - OR dp.victim_id = c.waiter_id ) - AND dp.event_date = c.event_date - FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(4000)'), - 1, 1, N'')) AS object_names, - dp.wait_time, - dp.transaction_name, - dp.last_tran_started, - dp.last_batch_started, - dp.last_batch_completed, - dp.lock_mode, - dp.transaction_count, - dp.client_app, - dp.host_name, - dp.login_name, - dp.isolation_level, - dp.process_xml.value('(//process/inputbuf/text())[1]', 'NVARCHAR(MAX)') AS inputbuf, - DENSE_RANK() OVER ( ORDER BY dp.event_date ) AS en, - ROW_NUMBER() OVER ( PARTITION BY dp.event_date ORDER BY dp.event_date ) -1 AS qn, - ROW_NUMBER() OVER ( PARTITION BY dp.event_date, dp.id ORDER BY dp.event_date ) AS dn, - 1 AS is_victim, - cao.wait_type COLLATE DATABASE_DEFAULT AS owner_mode, - cao.waiter_type AS owner_waiter_type, - cao.owner_activity AS owner_activity, - cao.waiter_activity AS owner_waiter_activity, - cao.merging AS owner_merging, - cao.spilling AS owner_spilling, - cao.waiting_to_close AS owner_waiting_to_close, - caw.wait_type COLLATE DATABASE_DEFAULT AS waiter_mode, - caw.waiter_type AS waiter_waiter_type, - caw.owner_activity AS waiter_owner_activity, - caw.waiter_activity AS waiter_waiter_activity, - caw.merging AS waiter_merging, - caw.spilling AS waiter_spilling, - caw.waiting_to_close AS waiter_waiting_to_close, - dp.deadlock_graph - FROM #deadlock_process AS dp - OUTER APPLY (SELECT TOP 1 * FROM #deadlock_resource_parallel AS drp WHERE drp.owner_id = dp.id AND drp.wait_type IN ('e_waitPortOpen', 'e_waitPipeNewRow') ORDER BY drp.event_date) AS cao - OUTER APPLY (SELECT TOP 1 * FROM #deadlock_resource_parallel AS drp WHERE drp.owner_id = dp.id AND drp.wait_type IN ('e_waitPortOpen', 'e_waitPipeGetRow') ORDER BY drp.event_date) AS caw - WHERE dp.is_parallel = 1 - ) - SELECT d.deadlock_type, - d.event_date, - DB_NAME(d.database_id) AS database_name, - d.spid, - 'Deadlock #' - + CONVERT(NVARCHAR(10), d.en) - + ', Query #' - + CASE WHEN d.qn = 0 THEN N'1' ELSE CONVERT(NVARCHAR(10), d.qn) END - + CASE WHEN d.is_victim = 1 THEN ' - VICTIM' ELSE '' END - AS deadlock_group, - CONVERT(XML, N'') AS query_xml, - d.inputbuf AS query_string, - d.object_names, - d.isolation_level, - d.owner_mode, - d.waiter_mode, - d.transaction_count, - d.login_name, - d.host_name, - d.client_app, - d.wait_time, - d.wait_resource, - d.priority, - d.log_used, - d.last_tran_started, - d.last_batch_started, - d.last_batch_completed, - d.transaction_name, - /*These columns will be NULL for regular (non-parallel) deadlocks*/ - d.owner_waiter_type, - d.owner_activity, - d.owner_waiter_activity, - d.owner_merging, - d.owner_spilling, - d.owner_waiting_to_close, - d.waiter_waiter_type, - d.waiter_owner_activity, - d.waiter_waiter_activity, - d.waiter_merging, - d.waiter_spilling, - d.waiter_waiting_to_close, - d.deadlock_graph, - d.is_victim - INTO #deadlock_results - FROM deadlocks AS d - WHERE d.dn = 1 - AND (d.is_victim = @VictimsOnly OR @VictimsOnly = 0) - AND d.qn < CASE WHEN d.deadlock_type = N'Parallel Deadlock' THEN 2 ELSE 2147483647 END - AND (DB_NAME(d.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (d.event_date >= @StartDate OR @StartDate IS NULL) - AND (d.event_date < @EndDate OR @EndDate IS NULL) - AND (CONVERT(NVARCHAR(MAX), d.object_names) LIKE '%' + @ObjectName + '%' OR @ObjectName IS NULL) - AND (d.client_app = @AppName OR @AppName IS NULL) - AND (d.host_name = @HostName OR @HostName IS NULL) - AND (d.login_name = @LoginName OR @LoginName IS NULL) - OPTION ( RECOMPILE ); - + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - DECLARE @deadlock_result NVARCHAR(MAX) = N'' + /*Thank you goodnight*/ + INSERT + #deadlock_findings WITH(TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding + ) + VALUES + ( + -1, + N'sp_BlitzLock version ' + CONVERT(nvarchar(10), @Version), + N'Results for ' + CONVERT(nvarchar(10), @StartDate, 23) + N' through ' + CONVERT(nvarchar(10), @EndDate, 23), + N'http://FirstResponderKit.org/', + N'To get help or add your own contributions to the SQL Server First Responder Kit, join us at http://FirstResponderKit.org.' + ); - SET @deadlock_result += N' - SELECT - dr.deadlock_type, - dr.event_date, - dr.database_name, - dr.spid, - dr.deadlock_group, - ' - + CASE @ExportToExcel - WHEN 1 - THEN N'dr.query_string AS query, - REPLACE(REPLACE(CONVERT(NVARCHAR(MAX), dr.object_names), '''', ''''), '''', '''') AS object_names,' - ELSE N'dr.query_xml AS query, - dr.object_names,' - END + - N' - dr.isolation_level, - dr.owner_mode, - dr.waiter_mode, - dr.transaction_count, - dr.login_name, - dr.host_name, - dr.client_app, - dr.wait_time, - dr.wait_resource, - dr.priority, - dr.log_used, - dr.last_tran_started, - dr.last_batch_started, - dr.last_batch_completed, - dr.transaction_name, - dr.owner_waiter_type, - dr.owner_activity, - dr.owner_waiter_activity, - dr.owner_merging, - dr.owner_spilling, - dr.owner_waiting_to_close, - dr.waiter_waiter_type, - dr.waiter_owner_activity, - dr.waiter_waiter_activity, - dr.waiter_merging, - dr.waiter_spilling, - dr.waiter_waiting_to_close' - + CASE @ExportToExcel - WHEN 1 - THEN N'' - ELSE N', - dr.deadlock_graph' - END + - ' - FROM #deadlock_results AS dr - ORDER BY dr.event_date, dr.is_victim DESC - OPTION(RECOMPILE); - ' - - EXEC sys.sp_executesql - @deadlock_result; - - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Findings %s', 0, 1, @d) WITH NOWAIT; - SELECT df.check_id, df.database_name, df.object_name, df.finding_group, df.finding - FROM #deadlock_findings AS df - ORDER BY df.check_id - OPTION ( RECOMPILE ); - - SET @d = CONVERT(VARCHAR(40), GETDATE(), 109); - RAISERROR('Done %s', 0, 1, @d) WITH NOWAIT; - END --done with output to client app. + RAISERROR('Finished rollup at %s', 0, 1, @d) WITH NOWAIT; + /*Results*/ + BEGIN + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Results 1 %s', 0, 1, @d) WITH NOWAIT; + CREATE CLUSTERED INDEX cx_whatever_dp ON #deadlock_process (event_date, id); + CREATE CLUSTERED INDEX cx_whatever_drp ON #deadlock_resource_parallel (event_date, owner_id); + CREATE CLUSTERED INDEX cx_whatever_dow ON #deadlock_owner_waiter (event_date, owner_id, waiter_id); - IF @Debug = 1 + IF @Debug = 1 BEGIN SET STATISTICS XML ON; END; + + WITH + deadlocks AS + ( + SELECT + deadlock_type = + N'Regular Deadlock', + dp.event_date, + dp.id, + dp.victim_id, + dp.spid, + dp.database_id, + dp.database_name, + dp.current_database_name, + dp.priority, + dp.log_used, + wait_resource = + dp.wait_resource COLLATE DATABASE_DEFAULT, + object_names = + CONVERT + ( + xml, + STUFF + ( + ( + SELECT DISTINCT + object_name = + NCHAR(10) + + N' ' + + ISNULL(c.object_name, N'') + + N' ' COLLATE DATABASE_DEFAULT + FROM #deadlock_owner_waiter AS c + WHERE (dp.id = c.owner_id + OR dp.victim_id = c.waiter_id) + AND dp.event_date = c.event_date + FOR XML + PATH(N''), + TYPE + ).value(N'.[1]', N'nvarchar(4000)'), + 1, + 1, + N'' + ) + ), + dp.wait_time, + dp.transaction_name, + dp.status, + dp.last_tran_started, + dp.last_batch_started, + dp.last_batch_completed, + dp.lock_mode, + dp.transaction_count, + dp.client_app, + dp.host_name, + dp.login_name, + dp.isolation_level, + dp.client_option_1, + dp.client_option_2, + inputbuf = + dp.process_xml.value('(//process/inputbuf/text())[1]', 'nvarchar(MAX)'), + en = + DENSE_RANK() OVER (ORDER BY dp.event_date), + qn = + ROW_NUMBER() OVER (PARTITION BY dp.event_date ORDER BY dp.event_date) - 1, + dn = + ROW_NUMBER() OVER (PARTITION BY dp.event_date, dp.id ORDER BY dp.event_date), + dp.is_victim, + owner_mode = + ISNULL(dp.owner_mode, N'-'), + owner_waiter_type = NULL, + owner_activity = NULL, + owner_waiter_activity = NULL, + owner_merging = NULL, + owner_spilling = NULL, + owner_waiting_to_close = NULL, + waiter_mode = + ISNULL(dp.waiter_mode, N'-'), + waiter_waiter_type = NULL, + waiter_owner_activity = NULL, + waiter_waiter_activity = NULL, + waiter_merging = NULL, + waiter_spilling = NULL, + waiter_waiting_to_close = NULL, + dp.deadlock_graph + FROM #deadlock_process AS dp + WHERE dp.victim_id IS NOT NULL + AND dp.is_parallel = 0 + + UNION ALL + + SELECT + deadlock_type = + N'Parallel Deadlock', + dp.event_date, + dp.id, + dp.victim_id, + dp.spid, + dp.database_id, + dp.database_name, + dp.current_database_name, + dp.priority, + dp.log_used, + dp.wait_resource COLLATE DATABASE_DEFAULT, + object_names = + CONVERT + ( + xml, + STUFF + ( + ( + SELECT DISTINCT + object_name = + NCHAR(10) + + N' ' + + ISNULL(c.object_name, N'') + + N' ' COLLATE DATABASE_DEFAULT + FROM #deadlock_owner_waiter AS c + WHERE (dp.id = c.owner_id + OR dp.victim_id = c.waiter_id) + AND dp.event_date = c.event_date + FOR XML + PATH(N''), + TYPE + ).value(N'.[1]', N'nvarchar(4000)'), + 1, + 1, + N'' + ) + ), + dp.wait_time, + dp.transaction_name, + dp.status, + dp.last_tran_started, + dp.last_batch_started, + dp.last_batch_completed, + dp.lock_mode, + dp.transaction_count, + dp.client_app, + dp.host_name, + dp.login_name, + dp.isolation_level, + dp.client_option_1, + dp.client_option_2, + inputbuf = + dp.process_xml.value('(//process/inputbuf/text())[1]', 'nvarchar(MAX)'), + en = + DENSE_RANK() OVER (ORDER BY dp.event_date), + qn = + ROW_NUMBER() OVER (PARTITION BY dp.event_date ORDER BY dp.event_date) - 1, + dn = + ROW_NUMBER() OVER (PARTITION BY dp.event_date, dp.id ORDER BY dp.event_date), + is_victim = 1, + owner_mode = cao.wait_type COLLATE DATABASE_DEFAULT, + owner_waiter_type = cao.waiter_type, + owner_activity = cao.owner_activity, + owner_waiter_activity = cao.waiter_activity, + owner_merging = cao.merging, + owner_spilling = cao.spilling, + owner_waiting_to_close = cao.waiting_to_close, + waiter_mode = caw.wait_type COLLATE DATABASE_DEFAULT, + waiter_waiter_type = caw.waiter_type, + waiter_owner_activity = caw.owner_activity, + waiter_waiter_activity = caw.waiter_activity, + waiter_merging = caw.merging, + waiter_spilling = caw.spilling, + waiter_waiting_to_close = caw.waiting_to_close, + dp.deadlock_graph + FROM #deadlock_process AS dp + OUTER APPLY + ( + SELECT TOP (1) + drp.* + FROM #deadlock_resource_parallel AS drp + WHERE drp.owner_id = dp.id + AND drp.wait_type IN + ( + N'e_waitPortOpen', + N'e_waitPipeNewRow' + ) + ORDER BY drp.event_date + ) AS cao + OUTER APPLY + ( + SELECT TOP (1) + drp.* + FROM #deadlock_resource_parallel AS drp + WHERE drp.owner_id = dp.id + AND drp.wait_type IN + ( + N'e_waitPortOpen', + N'e_waitPipeGetRow' + ) + ORDER BY drp.event_date + ) AS caw + WHERE dp.is_parallel = 1 + ) + SELECT + d.deadlock_type, + d.event_date, + d.id, + d.victim_id, + d.spid, + deadlock_group = + N'Deadlock #' + + CONVERT + ( + nvarchar(10), + d.en + ) + + N', Query #' + + CASE + WHEN d.qn = 0 + THEN N'1' + ELSE CONVERT(nvarchar(10), d.qn) + END + CASE + WHEN d.is_victim = 1 + THEN N' - VICTIM' + ELSE N'' + END, + d.database_id, + d.database_name, + d.current_database_name, + d.priority, + d.log_used, + d.wait_resource, + d.object_names, + d.wait_time, + d.transaction_name, + d.status, + d.last_tran_started, + d.last_batch_started, + d.last_batch_completed, + d.lock_mode, + d.transaction_count, + d.client_app, + d.host_name, + d.login_name, + d.isolation_level, + d.client_option_1, + d.client_option_2, + inputbuf = + CASE + WHEN d.inputbuf + LIKE @inputbuf_bom + N'Proc |[Database Id = %' ESCAPE N'|' + THEN + OBJECT_SCHEMA_NAME + ( + SUBSTRING + ( + d.inputbuf, + CHARINDEX(N'Object Id = ', d.inputbuf) + 12, + LEN(d.inputbuf) - (CHARINDEX(N'Object Id = ', d.inputbuf) + 12) + ) + , + SUBSTRING + ( + d.inputbuf, + CHARINDEX(N'Database Id = ', d.inputbuf) + 14, + CHARINDEX(N'Object Id', d.inputbuf) - (CHARINDEX(N'Database Id = ', d.inputbuf) + 14) + ) + ) + + N'.' + + OBJECT_NAME + ( + SUBSTRING + ( + d.inputbuf, + CHARINDEX(N'Object Id = ', d.inputbuf) + 12, + LEN(d.inputbuf) - (CHARINDEX(N'Object Id = ', d.inputbuf) + 12) + ) + , + SUBSTRING + ( + d.inputbuf, + CHARINDEX(N'Database Id = ', d.inputbuf) + 14, + CHARINDEX(N'Object Id', d.inputbuf) - (CHARINDEX(N'Database Id = ', d.inputbuf) + 14) + ) + ) + ELSE d.inputbuf + END COLLATE Latin1_General_BIN2, + d.owner_mode, + d.owner_waiter_type, + d.owner_activity, + d.owner_waiter_activity, + d.owner_merging, + d.owner_spilling, + d.owner_waiting_to_close, + d.waiter_mode, + d.waiter_waiter_type, + d.waiter_owner_activity, + d.waiter_waiter_activity, + d.waiter_merging, + d.waiter_spilling, + d.waiter_waiting_to_close, + d.deadlock_graph, + d.is_victim + INTO #deadlocks + FROM deadlocks AS d + WHERE d.dn = 1 + AND (d.is_victim = @VictimsOnly + OR @VictimsOnly = 0) + AND d.qn < CASE + WHEN d.deadlock_type = N'Parallel Deadlock' + THEN 2 + ELSE 2147483647 + END + AND (DB_NAME(d.database_id) = @DatabaseName OR @DatabaseName IS NULL) + AND (d.event_date >= @StartDate OR @StartDate IS NULL) + AND (d.event_date < @EndDate OR @EndDate IS NULL) + AND (CONVERT(nvarchar(MAX), d.object_names) COLLATE Latin1_General_BIN2 LIKE N'%' + @ObjectName + N'%' OR @ObjectName IS NULL) + AND (d.client_app = @AppName OR @AppName IS NULL) + AND (d.host_name = @HostName OR @HostName IS NULL) + AND (d.login_name = @LoginName OR @LoginName IS NULL) + OPTION (RECOMPILE, LOOP JOIN, HASH JOIN); + + UPDATE d + SET d.inputbuf = + REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE( + REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE( + REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE( + d.inputbuf, + NCHAR(31), N'?'), NCHAR(30), N'?'), NCHAR(29), N'?'), NCHAR(28), N'?'), NCHAR(27), N'?'), + NCHAR(26), N'?'), NCHAR(25), N'?'), NCHAR(24), N'?'), NCHAR(23), N'?'), NCHAR(22), N'?'), + NCHAR(21), N'?'), NCHAR(20), N'?'), NCHAR(19), N'?'), NCHAR(18), N'?'), NCHAR(17), N'?'), + NCHAR(16), N'?'), NCHAR(15), N'?'), NCHAR(14), N'?'), NCHAR(12), N'?'), NCHAR(11), N'?'), + NCHAR(8), N'?'), NCHAR(7), N'?'), NCHAR(6), N'?'), NCHAR(5), N'?'), NCHAR(4), N'?'), + NCHAR(3), N'?'), NCHAR(2), N'?'), NCHAR(1), N'?'), NCHAR(0), N'?') + FROM #deadlocks AS d + OPTION(RECOMPILE); + + SELECT + d.deadlock_type, + d.event_date, + database_name = + DB_NAME(d.database_id), + database_name_x = + d.database_name, + d.current_database_name, + d.spid, + d.deadlock_group, + d.client_option_1, + d.client_option_2, + d.lock_mode, + query_xml = + ( + SELECT + [processing-instruction(query)] = + d.inputbuf + FOR XML + PATH(N''), + TYPE + ), + query_string = + d.inputbuf, + d.object_names, + d.isolation_level, + d.owner_mode, + d.waiter_mode, + d.transaction_count, + d.login_name, + d.host_name, + d.client_app, + d.wait_time, + d.wait_resource, + d.priority, + d.log_used, + d.last_tran_started, + d.last_batch_started, + d.last_batch_completed, + d.transaction_name, + d.status, + /*These columns will be NULL for regular (non-parallel) deadlocks*/ + parallel_deadlock_details = + ( + SELECT + d.owner_waiter_type, + d.owner_activity, + d.owner_waiter_activity, + d.owner_merging, + d.owner_spilling, + d.owner_waiting_to_close, + d.waiter_waiter_type, + d.waiter_owner_activity, + d.waiter_waiter_activity, + d.waiter_merging, + d.waiter_spilling, + d.waiter_waiting_to_close + FOR XML + PATH('parallel_deadlock_details'), + TYPE + ), + d.owner_waiter_type, + d.owner_activity, + d.owner_waiter_activity, + d.owner_merging, + d.owner_spilling, + d.owner_waiting_to_close, + d.waiter_waiter_type, + d.waiter_owner_activity, + d.waiter_waiter_activity, + d.waiter_merging, + d.waiter_spilling, + d.waiter_waiting_to_close, + /*end parallel deadlock columns*/ + d.deadlock_graph, + d.is_victim + INTO #deadlock_results + FROM #deadlocks AS d; + + IF @Debug = 1 BEGIN SET STATISTICS XML OFF; END; + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + + /*There's too much risk of errors sending the*/ + IF @OutputDatabaseCheck = 0 + BEGIN + SET @ExportToExcel = 0; + END; + + SET @deadlock_result += N' + SELECT + server_name = + @@SERVERNAME, + dr.deadlock_type, + dr.event_date, + database_name = + COALESCE + ( + dr.database_name, + dr.database_name_x, + dr.current_database_name + ), + dr.spid, + dr.deadlock_group, + ' + CASE @ExportToExcel + WHEN 1 + THEN N' + query = dr.query_string, + object_names = + REPLACE( + REPLACE( + CONVERT + ( + nvarchar(MAX), + dr.object_names + ) COLLATE Latin1_General_BIN2, + '''', ''''), + '''', ''''),' + ELSE N'query = dr.query_xml, + dr.object_names,' + END + N' + dr.isolation_level, + dr.owner_mode, + dr.waiter_mode, + dr.lock_mode, + dr.transaction_count, + dr.client_option_1, + dr.client_option_2, + dr.login_name, + dr.host_name, + dr.client_app, + dr.wait_time, + dr.wait_resource, + dr.priority, + dr.log_used, + dr.last_tran_started, + dr.last_batch_started, + dr.last_batch_completed, + dr.transaction_name, + dr.status,' + + CASE + WHEN (@ExportToExcel = 1 + OR @OutputDatabaseCheck = 0) + THEN N' + dr.owner_waiter_type, + dr.owner_activity, + dr.owner_waiter_activity, + dr.owner_merging, + dr.owner_spilling, + dr.owner_waiting_to_close, + dr.waiter_waiter_type, + dr.waiter_owner_activity, + dr.waiter_waiter_activity, + dr.waiter_merging, + dr.waiter_spilling, + dr.waiter_waiting_to_close,' + ELSE N' + dr.parallel_deadlock_details,' + END + + CASE + @ExportToExcel + WHEN 1 + THEN N' + deadlock_graph = + REPLACE(REPLACE( + REPLACE(REPLACE( + CONVERT + ( + nvarchar(MAX), + dr.deadlock_graph + ) COLLATE Latin1_General_BIN2, + ''NCHAR(10)'', ''''), ''NCHAR(13)'', ''''), + ''CHAR(10)'', ''''), ''CHAR(13)'', '''')' + ELSE N' + dr.deadlock_graph' + END + N' + FROM #deadlock_results AS dr + ORDER BY + dr.event_date, + dr.is_victim DESC + OPTION(RECOMPILE, LOOP JOIN, HASH JOIN); + '; + + IF (@OutputDatabaseCheck = 0) + BEGIN + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Results to table %s', 0, 1, @d) WITH NOWAIT; + + IF @Debug = 1 + BEGIN + PRINT @deadlock_result; + SET STATISTICS XML ON; + END; + + INSERT INTO + DeadLockTbl + ( + ServerName, + deadlock_type, + event_date, + database_name, + spid, + deadlock_group, + query, + object_names, + isolation_level, + owner_mode, + waiter_mode, + lock_mode, + transaction_count, + client_option_1, + client_option_2, + login_name, + host_name, + client_app, + wait_time, + wait_resource, + priority, + log_used, + last_tran_started, + last_batch_started, + last_batch_completed, + transaction_name, + status, + owner_waiter_type, + owner_activity, + owner_waiter_activity, + owner_merging, + owner_spilling, + owner_waiting_to_close, + waiter_waiter_type, + waiter_owner_activity, + waiter_waiter_activity, + waiter_merging, + waiter_spilling, + waiter_waiting_to_close, + deadlock_graph + ) + EXEC sys.sp_executesql + @deadlock_result; + + IF @Debug = 1 + BEGIN + SET STATISTICS XML OFF; + END; + + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + + DROP SYNONYM DeadLockTbl; + + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Findings to table %s', 0, 1, @d) WITH NOWAIT; + + INSERT INTO + DeadlockFindings + ( + ServerName, + check_id, + database_name, + object_name, + finding_group, + finding + ) + SELECT + @@SERVERNAME, + df.check_id, + df.database_name, + df.object_name, + df.finding_group, + df.finding + FROM #deadlock_findings AS df + ORDER BY df.check_id + OPTION(RECOMPILE); + + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + + DROP SYNONYM DeadlockFindings; /*done with inserting.*/ + END; + ELSE /*Output to database is not set output to client app*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Results to client %s', 0, 1, @d) WITH NOWAIT; + + IF @Debug = 1 BEGIN SET STATISTICS XML ON; END; + + EXEC sys.sp_executesql + @deadlock_result; + + IF @Debug = 1 BEGIN + SET STATISTICS XML OFF; + PRINT @deadlock_result; + END; + + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Returning findings %s', 0, 1, @d) WITH NOWAIT; + + SELECT + df.check_id, + df.database_name, + df.object_name, + df.finding_group, + df.finding + FROM #deadlock_findings AS df + ORDER BY df.check_id + OPTION(RECOMPILE); + + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + END; /*done with output to client app.*/ + + IF @Debug = 1 + BEGIN + SELECT + table_name = N'#dd', + * + FROM #dd AS d + OPTION(RECOMPILE); - SELECT '#deadlock_data' AS table_name, * - FROM #deadlock_data AS dd - OPTION ( RECOMPILE ); + SELECT + table_name = N'#deadlock_data', + * + FROM #deadlock_data AS dd + OPTION(RECOMPILE); - SELECT '#deadlock_resource' AS table_name, * - FROM #deadlock_resource AS dr - OPTION ( RECOMPILE ); + SELECT + table_name = N'#deadlock_resource', + * + FROM #deadlock_resource AS dr + OPTION(RECOMPILE); - SELECT '#deadlock_resource_parallel' AS table_name, * - FROM #deadlock_resource_parallel AS drp - OPTION ( RECOMPILE ); + SELECT + table_name = N'#deadlock_resource_parallel', + * + FROM #deadlock_resource_parallel AS drp + OPTION(RECOMPILE); - SELECT '#deadlock_owner_waiter' AS table_name, * - FROM #deadlock_owner_waiter AS dow - OPTION ( RECOMPILE ); + SELECT + table_name = N'#deadlock_owner_waiter', + * + FROM #deadlock_owner_waiter AS dow + OPTION(RECOMPILE); - SELECT '#deadlock_process' AS table_name, * - FROM #deadlock_process AS dp - OPTION ( RECOMPILE ); + SELECT + table_name = N'#deadlock_process', + * + FROM #deadlock_process AS dp + OPTION(RECOMPILE); + + SELECT + table_name = N'#deadlock_stack', + * + FROM #deadlock_stack AS ds + OPTION(RECOMPILE); - SELECT '#deadlock_stack' AS table_name, * - FROM #deadlock_stack AS ds - OPTION ( RECOMPILE ); + SELECT + table_name = N'#deadlocks', + * + FROM #deadlocks AS d + OPTION(RECOMPILE); - SELECT '#deadlock_results' AS table_name, * - FROM #deadlock_results AS dr - OPTION ( RECOMPILE ); + SELECT + table_name = N'#deadlock_results', + * + FROM #deadlock_results AS dr + OPTION(RECOMPILE); + + SELECT + table_name = N'#x', + * + FROM #x AS x + OPTION(RECOMPILE); - END; -- End debug + SELECT + table_name = N'@sysAssObjId', + * + FROM @sysAssObjId AS s + OPTION(RECOMPILE); - END; --Final End + END; /*End debug*/ + END; /*Final End*/ GO SET ANSI_NULLS ON; @@ -27247,7 +29378,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.11', @VersionDate = '20221013'; +SELECT @Version = '8.12', @VersionDate = '20221213'; IF(@VersionCheckMode = 1) BEGIN RETURN; @@ -29496,7 +31627,7 @@ RAISERROR(N'Gathering working plans', 0, 1) WITH NOWAIT; SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; SET @sql_select += N' SELECT ' + QUOTENAME(@DatabaseName, '''') + N' AS database_name, wp.plan_id, wp.query_id, - qsp.plan_group_id, qsp.engine_version, qsp.compatibility_level, qsp.query_plan_hash, TRY_CONVERT(XML, qsp.query_plan), qsp.is_online_index_plan, qsp.is_trivial_plan, + qsp.plan_group_id, qsp.engine_version, qsp.compatibility_level, qsp.query_plan_hash, TRY_CAST(qsp.query_plan AS XML), qsp.is_online_index_plan, qsp.is_trivial_plan, qsp.is_parallel_plan, qsp.is_forced_plan, qsp.is_natively_compiled, qsp.force_failure_count, qsp.last_force_failure_reason_desc, qsp.count_compiles, qsp.initial_compile_start_time, qsp.last_compile_start_time, qsp.last_execution_time, (qsp.avg_compile_duration / 1000.), @@ -32978,7 +35109,7 @@ BEGIN SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.11', @VersionDate = '20221013'; + SELECT @Version = '8.12', @VersionDate = '20221213'; IF(@VersionCheckMode = 1) BEGIN @@ -33623,6 +35754,16 @@ BEGIN */ SET @StringToExecute = N'COALESCE( RIGHT(''00'' + CONVERT(VARCHAR(20), (ABS(r.total_elapsed_time) / 1000) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), (DATEADD(SECOND, (r.total_elapsed_time / 1000), 0) + DATEADD(MILLISECOND, (r.total_elapsed_time % 1000), 0)), 114), RIGHT(''00'' + CONVERT(VARCHAR(20), DATEDIFF(SECOND, s.last_request_start_time, GETDATE()) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), DATEADD(SECOND, DATEDIFF(SECOND, s.last_request_start_time, GETDATE()), 0), 114) ) AS [elapsed_time] , s.session_id , + CASE WHEN r.blocking_session_id <> 0 AND blocked.session_id IS NULL + THEN r.blocking_session_id + WHEN r.blocking_session_id <> 0 AND s.session_id <> blocked.blocking_session_id + THEN blocked.blocking_session_id + WHEN r.blocking_session_id = 0 AND s.session_id = blocked.session_id + THEN blocked.blocking_session_id + WHEN r.blocking_session_id <> 0 AND s.session_id = blocked.blocking_session_id + THEN r.blocking_session_id + ELSE NULL + END AS blocking_session_id, COALESCE(DB_NAME(r.database_id), DB_NAME(blocked.dbid), ''N/A'') AS database_name, ISNULL(SUBSTRING(dest.text, ( query_stats.statement_start_offset / 2 ) + 1, @@ -33643,16 +35784,6 @@ BEGIN ELSE NULL END AS wait_info , r.wait_resource , - CASE WHEN r.blocking_session_id <> 0 AND blocked.session_id IS NULL - THEN r.blocking_session_id - WHEN r.blocking_session_id <> 0 AND s.session_id <> blocked.blocking_session_id - THEN blocked.blocking_session_id - WHEN r.blocking_session_id = 0 AND s.session_id = blocked.session_id - THEN blocked.blocking_session_id - WHEN r.blocking_session_id <> 0 AND s.session_id = blocked.blocking_session_id - THEN r.blocking_session_id - ELSE NULL - END AS blocking_session_id, COALESCE(r.open_transaction_count, blocked.open_tran) AS open_transaction_count , CASE WHEN EXISTS ( SELECT 1 FROM sys.dm_tran_active_transactions AS tat @@ -33841,8 +35972,18 @@ IF @ProductVersionMajor >= 11 */ SELECT @StringToExecute = N'COALESCE( RIGHT(''00'' + CONVERT(VARCHAR(20), (ABS(r.total_elapsed_time) / 1000) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), (DATEADD(SECOND, (r.total_elapsed_time / 1000), 0) + DATEADD(MILLISECOND, (r.total_elapsed_time % 1000), 0)), 114), RIGHT(''00'' + CONVERT(VARCHAR(20), DATEDIFF(SECOND, s.last_request_start_time, GETDATE()) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), DATEADD(SECOND, DATEDIFF(SECOND, s.last_request_start_time, GETDATE()), 0), 114) ) AS [elapsed_time] , s.session_id , - COALESCE(DB_NAME(r.database_id), DB_NAME(blocked.dbid), ''N/A'') AS database_name, - ISNULL(SUBSTRING(dest.text, + CASE WHEN r.blocking_session_id <> 0 AND blocked.session_id IS NULL + THEN r.blocking_session_id + WHEN r.blocking_session_id <> 0 AND s.session_id <> blocked.blocking_session_id + THEN blocked.blocking_session_id + WHEN r.blocking_session_id = 0 AND s.session_id = blocked.session_id + THEN blocked.blocking_session_id + WHEN r.blocking_session_id <> 0 AND s.session_id = blocked.blocking_session_id + THEN r.blocking_session_id + ELSE NULL + END AS blocking_session_id, + COALESCE(DB_NAME(r.database_id), DB_NAME(blocked.dbid), ''N/A'') AS database_name, + ISNULL(SUBSTRING(dest.text, ( query_stats.statement_start_offset / 2 ) + 1, ( ( CASE query_stats.statement_end_offset WHEN -1 THEN DATALENGTH(dest.text) @@ -33886,17 +36027,7 @@ IF @ProductVersionMajor >= 11 ELSE N' NULL AS top_session_waits ,' END + - N'CASE WHEN r.blocking_session_id <> 0 AND blocked.session_id IS NULL - THEN r.blocking_session_id - WHEN r.blocking_session_id <> 0 AND s.session_id <> blocked.blocking_session_id - THEN blocked.blocking_session_id - WHEN r.blocking_session_id = 0 AND s.session_id = blocked.session_id - THEN blocked.blocking_session_id - WHEN r.blocking_session_id <> 0 AND s.session_id = blocked.blocking_session_id - THEN r.blocking_session_id - ELSE NULL - END AS blocking_session_id, - COALESCE(r.open_transaction_count, blocked.open_tran) AS open_transaction_count , + N'COALESCE(r.open_transaction_count, blocked.open_tran) AS open_transaction_count , CASE WHEN EXISTS ( SELECT 1 FROM sys.dm_tran_active_transactions AS tat JOIN sys.dm_tran_session_transactions AS tst @@ -34200,6 +36331,7 @@ IF @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @Output ,CheckDate ,[elapsed_time] ,[session_id] + ,[blocking_session_id] ,[database_name] ,[query_text]' + CASE WHEN @GetOuterCommand = 1 THEN N',[outer_command]' ELSE N'' END + N' @@ -34212,7 +36344,6 @@ IF @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @Output ,[wait_info] ,[wait_resource]' + CASE WHEN @ProductVersionMajor >= 11 THEN N',[top_session_waits]' ELSE N'' END + N' - ,[blocking_session_id] ,[open_transaction_count] ,[is_implicit_transaction] ,[nt_domain] @@ -34374,7 +36505,7 @@ DELETE FROM dbo.SqlServerVersions; INSERT INTO dbo.SqlServerVersions (MajorVersionNumber, MinorVersionNumber, Branch, [Url], ReleaseDate, MainstreamSupportEndDate, ExtendedSupportEndDate, MajorVersionName, MinorVersionName) VALUES - (16, 600, 'CTP2', 'https://support.microsoft.com/en-us/help/5011644', '2022-05-24', '2022-05-24', '2022-05-24', 'SQL Server 2022', 'CTP 2.0'), + (16, 1000, 'RTM', '', '2022-11-15', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'RTM'), (15, 4261, 'CU18', 'https://support.microsoft.com/en-us/help/5017593', '2022-09-28', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 18'), (15, 4249, 'CU17', 'https://support.microsoft.com/en-us/help/5016394', '2022-08-11', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 17'), (15, 4236, 'CU16 GDR', 'https://support.microsoft.com/en-us/help/5014353', '2022-06-14', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 16 GDR'), @@ -34790,7 +36921,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.11', @VersionDate = '20221013'; +SELECT @Version = '8.12', @VersionDate = '20221213'; IF(@VersionCheckMode = 1) BEGIN @@ -35875,7 +38006,7 @@ BEGIN @StockDetailsFooter = @StockDetailsFooter + @LineFeed + ' -- ?>'; /* Get the instance name to use as a Perfmon counter prefix. */ - IF SERVERPROPERTY('EngineEdition') = 5 /*CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) = 'SQL Azure'*/ + IF SERVERPROPERTY('EngineEdition') IN (5, 8) /*CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) = 'SQL Azure'*/ SELECT TOP 1 @ServiceName = LEFT(object_name, (CHARINDEX(':', object_name) - 1)) FROM sys.dm_os_performance_counters; ELSE @@ -37260,7 +39391,23 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, ; IF SERVERPROPERTY('EngineEdition') <> 5 /*SERVERPROPERTY('Edition') <> 'SQL Azure'*/ - EXEC sp_MSforeachdb @StringToExecute; + BEGIN + BEGIN TRY + EXEC sp_MSforeachdb @StringToExecute; + END TRY + BEGIN CATCH + IF (ERROR_NUMBER() = 1222) + BEGIN + INSERT INTO #UpdatedStats(HowToStopIt, RowsForSorting) + SELECT HowToStopIt = N'No information could be retrieved as the lock timeout was exceeded while iterating databases,' + + N' this is likely due to an Index operation in Progress', -1; + END + ELSE + BEGIN + THROW; + END + END CATCH + END ELSE EXEC(@StringToExecute); diff --git a/sp_AllNightLog.sql b/sp_AllNightLog.sql index b3e501125..9e5df6eb6 100644 --- a/sp_AllNightLog.sql +++ b/sp_AllNightLog.sql @@ -31,7 +31,7 @@ SET STATISTICS XML OFF; BEGIN; -SELECT @Version = '8.11', @VersionDate = '20221013'; +SELECT @Version = '8.12', @VersionDate = '20221213'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_AllNightLog_Setup.sql b/sp_AllNightLog_Setup.sql index cafb44392..ba77dbfca 100644 --- a/sp_AllNightLog_Setup.sql +++ b/sp_AllNightLog_Setup.sql @@ -38,7 +38,7 @@ SET STATISTICS XML OFF; BEGIN; -SELECT @Version = '8.11', @VersionDate = '20221013'; +SELECT @Version = '8.12', @VersionDate = '20221213'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_Blitz.sql b/sp_Blitz.sql index 568939264..ab74b6ac3 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -38,7 +38,7 @@ AS SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.11', @VersionDate = '20221013'; + SELECT @Version = '8.12', @VersionDate = '20221213'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) diff --git a/sp_BlitzAnalysis.sql b/sp_BlitzAnalysis.sql index 0e81a263d..e11f351cd 100644 --- a/sp_BlitzAnalysis.sql +++ b/sp_BlitzAnalysis.sql @@ -37,7 +37,7 @@ AS SET NOCOUNT ON; SET STATISTICS XML OFF; -SELECT @Version = '8.11', @VersionDate = '20221013'; +SELECT @Version = '8.12', @VersionDate = '20221213'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_BlitzBackups.sql b/sp_BlitzBackups.sql index 3ab03dee8..a2d353e44 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.11', @VersionDate = '20221013'; + SELECT @Version = '8.12', @VersionDate = '20221213'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_BlitzCache.sql b/sp_BlitzCache.sql index 5dbb0f604..34ce77783 100644 --- a/sp_BlitzCache.sql +++ b/sp_BlitzCache.sql @@ -280,7 +280,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.11', @VersionDate = '20221013'; +SELECT @Version = '8.12', @VersionDate = '20221213'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) diff --git a/sp_BlitzFirst.sql b/sp_BlitzFirst.sql index 1188c1f39..93d0b8db4 100644 --- a/sp_BlitzFirst.sql +++ b/sp_BlitzFirst.sql @@ -46,7 +46,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.11', @VersionDate = '20221013'; +SELECT @Version = '8.12', @VersionDate = '20221213'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_BlitzInMemoryOLTP.sql b/sp_BlitzInMemoryOLTP.sql index 192150ddf..07d0a10aa 100644 --- a/sp_BlitzInMemoryOLTP.sql +++ b/sp_BlitzInMemoryOLTP.sql @@ -82,7 +82,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI */ AS DECLARE @ScriptVersion VARCHAR(30); -SELECT @ScriptVersion = '1.8', @VersionDate = '20221013'; +SELECT @ScriptVersion = '1.8', @VersionDate = '20221213'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index 47d974713..7d0d71f90 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -48,7 +48,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.11', @VersionDate = '20221013'; +SELECT @Version = '8.12', @VersionDate = '20221213'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index 29cad0d05..682d9f0b3 100644 --- a/sp_BlitzLock.sql +++ b/sp_BlitzLock.sql @@ -35,9 +35,7 @@ BEGIN SET NOCOUNT, XACT_ABORT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT - @Version = '8.11', - @VersionDate = '20221013'; + SELECT @Version = '8.12', @VersionDate = '20221213'; IF @VersionCheckMode = 1 BEGIN diff --git a/sp_BlitzQueryStore.sql b/sp_BlitzQueryStore.sql index 548f0d570..6a8ddd11f 100644 --- a/sp_BlitzQueryStore.sql +++ b/sp_BlitzQueryStore.sql @@ -57,7 +57,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.11', @VersionDate = '20221013'; +SELECT @Version = '8.12', @VersionDate = '20221213'; IF(@VersionCheckMode = 1) BEGIN RETURN; diff --git a/sp_BlitzWho.sql b/sp_BlitzWho.sql index 86d763d07..a642175a2 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.11', @VersionDate = '20221013'; + SELECT @Version = '8.12', @VersionDate = '20221213'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_DatabaseRestore.sql b/sp_DatabaseRestore.sql index 574606d45..8e5df5062 100755 --- a/sp_DatabaseRestore.sql +++ b/sp_DatabaseRestore.sql @@ -42,7 +42,7 @@ SET STATISTICS XML OFF; /*Versioning details*/ -SELECT @Version = '8.11', @VersionDate = '20221013'; +SELECT @Version = '8.12', @VersionDate = '20221213'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_ineachdb.sql b/sp_ineachdb.sql index f4b7cde04..2b5c8fe45 100644 --- a/sp_ineachdb.sql +++ b/sp_ineachdb.sql @@ -35,7 +35,7 @@ BEGIN SET NOCOUNT ON; SET STATISTICS XML OFF; - SELECT @Version = '8.11', @VersionDate = '20221013'; + SELECT @Version = '8.12', @VersionDate = '20221213'; IF(@VersionCheckMode = 1) BEGIN From a2795943835ccb59887458222b999b4c86cbda74 Mon Sep 17 00:00:00 2001 From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com> Date: Mon, 19 Dec 2022 14:26:11 -0500 Subject: [PATCH 374/662] Update sp_BlitzLock.sql Fix database name/database id comparisons. Closes #3201 --- sp_BlitzLock.sql | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index 682d9f0b3..74a25d7bf 100644 --- a/sp_BlitzLock.sql +++ b/sp_BlitzLock.sql @@ -1794,7 +1794,7 @@ BEGIN N'S', N'IS' ) - AND (dow.database_id = @DatabaseName OR @DatabaseName IS NULL) + AND (dow.database_id = @DatabaseId OR @DatabaseName IS NULL) AND (dow.event_date >= @StartDate OR @StartDate IS NULL) AND (dow.event_date < @EndDate OR @EndDate IS NULL) AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) @@ -1837,7 +1837,7 @@ BEGIN N' deadlock(s).' FROM #deadlock_owner_waiter AS dow WHERE 1 = 1 - AND (dow.database_id = @DatabaseName OR @DatabaseName IS NULL) + AND (dow.database_id = @DatabaseId OR @DatabaseName IS NULL) AND (dow.event_date >= @StartDate OR @StartDate IS NULL) AND (dow.event_date < @EndDate OR @EndDate IS NULL) AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) @@ -1876,7 +1876,7 @@ BEGIN N' deadlock(s).' FROM #deadlock_owner_waiter AS dow WHERE 1 = 1 - AND (dow.database_id = @DatabaseName OR @DatabaseName IS NULL) + AND (dow.database_id = @DatabaseId OR @DatabaseName IS NULL) AND (dow.event_date >= @StartDate OR @StartDate IS NULL) AND (dow.event_date < @EndDate OR @EndDate IS NULL) AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) @@ -1921,7 +1921,7 @@ BEGIN N' deadlock(s).' FROM #deadlock_owner_waiter AS dow WHERE 1 = 1 - AND (dow.database_id = @DatabaseName OR @DatabaseName IS NULL) + AND (dow.database_id = @DatabaseId OR @DatabaseName IS NULL) AND (dow.event_date >= @StartDate OR @StartDate IS NULL) AND (dow.event_date < @EndDate OR @EndDate IS NULL) AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) @@ -2247,7 +2247,7 @@ BEGIN ON dow.owner_id = ds.id AND dow.event_date = ds.event_date WHERE 1 = 1 - AND (dow.database_id = @DatabaseName OR @DatabaseName IS NULL) + AND (dow.database_id = @DatabaseId OR @DatabaseName IS NULL) AND (dow.event_date >= @StartDate OR @StartDate IS NULL) AND (dow.event_date < @EndDate OR @EndDate IS NULL) AND (dow.object_name = @StoredProcName OR @StoredProcName IS NULL) @@ -2303,7 +2303,7 @@ BEGIN ON dow.owner_id = ds.id AND dow.event_date = ds.event_date WHERE ds.proc_name <> N'adhoc' - AND (dow.database_id = @DatabaseName OR @DatabaseName IS NULL) + AND (dow.database_id = @DatabaseId OR @DatabaseName IS NULL) AND (dow.event_date >= @StartDate OR @StartDate IS NULL) AND (dow.event_date < @EndDate OR @EndDate IS NULL) AND (dow.object_name = @StoredProcName OR @StoredProcName IS NULL) @@ -2379,7 +2379,7 @@ BEGIN ON s.database_id = dow.database_id AND s.partition_id = dow.associatedObjectId WHERE 1 = 1 - AND (dow.database_id = @DatabaseName OR @DatabaseName IS NULL) + AND (dow.database_id = @DatabaseId OR @DatabaseName IS NULL) AND (dow.event_date >= @StartDate OR @StartDate IS NULL) AND (dow.event_date < @EndDate OR @EndDate IS NULL) AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) From 152129e993c50697c88aaf38b9ab7ffbd4fb525b Mon Sep 17 00:00:00 2001 From: Adrian Buckman <33764798+Adedba@users.noreply.github.com> Date: Fri, 6 Jan 2023 21:03:57 +0000 Subject: [PATCH 375/662] Ameneded query_text column to get statement offsets from sys.dm_exec_requests instead of sys.dm_exec_query_stats #3163 - Ameneded query_text column to get statement offsets from sys.dm_exec_requests instead of sys.dm_exec_query_stats --- sp_BlitzWho.sql | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/sp_BlitzWho.sql b/sp_BlitzWho.sql index a642175a2..ab02edd02 100644 --- a/sp_BlitzWho.sql +++ b/sp_BlitzWho.sql @@ -690,11 +690,11 @@ BEGIN END AS blocking_session_id, COALESCE(DB_NAME(r.database_id), DB_NAME(blocked.dbid), ''N/A'') AS database_name, ISNULL(SUBSTRING(dest.text, - ( query_stats.statement_start_offset / 2 ) + 1, - ( ( CASE query_stats.statement_end_offset + ( r.statement_start_offset / 2 ) + 1, + ( ( CASE r.statement_end_offset WHEN -1 THEN DATALENGTH(dest.text) - ELSE query_stats.statement_end_offset - END - query_stats.statement_start_offset ) + ELSE r.statement_end_offset + END - r.statement_start_offset ) / 2 ) + 1), dest.text) AS query_text , '+CASE WHEN @GetOuterCommand = 1 THEN N'CAST(event_info AS NVARCHAR(4000)) AS outer_command,' @@ -908,11 +908,11 @@ IF @ProductVersionMajor >= 11 END AS blocking_session_id, COALESCE(DB_NAME(r.database_id), DB_NAME(blocked.dbid), ''N/A'') AS database_name, ISNULL(SUBSTRING(dest.text, - ( query_stats.statement_start_offset / 2 ) + 1, - ( ( CASE query_stats.statement_end_offset + ( r.statement_start_offset / 2 ) + 1, + ( ( CASE r.statement_end_offset WHEN -1 THEN DATALENGTH(dest.text) - ELSE query_stats.statement_end_offset - END - query_stats.statement_start_offset ) + ELSE r.statement_end_offset + END - r.statement_start_offset ) / 2 ) + 1), dest.text) AS query_text , '+CASE WHEN @GetOuterCommand = 1 THEN N'CAST(event_info AS NVARCHAR(4000)) AS outer_command,' From daf5cf8592fbbe7a612163746b94b2c96845f123 Mon Sep 17 00:00:00 2001 From: Adrian Buckman <33764798+Adedba@users.noreply.github.com> Date: Tue, 24 Jan 2023 09:00:36 +0000 Subject: [PATCH 376/662] Add Pattern column for sortorder = all Add Pattern column for sortorder = all --- sp_BlitzCache.sql | 46 ++++++++++++++++++++++++++++++++++------------ 1 file changed, 34 insertions(+), 12 deletions(-) diff --git a/sp_BlitzCache.sql b/sp_BlitzCache.sql index 34ce77783..ff07f9485 100644 --- a/sp_BlitzCache.sql +++ b/sp_BlitzCache.sql @@ -233,7 +233,8 @@ CREATE TABLE ##BlitzCacheProcs ( cached_execution_parameters XML, missing_indexes XML, SetOptions VARCHAR(MAX), - Warnings VARCHAR(MAX) + Warnings VARCHAR(MAX), + Pattern NVARCHAR(20) ); GO @@ -1056,7 +1057,8 @@ BEGIN cached_execution_parameters XML, missing_indexes XML, SetOptions VARCHAR(MAX), - Warnings VARCHAR(MAX) + Warnings VARCHAR(MAX), + Pattern NVARCHAR(20) ); END; @@ -1975,7 +1977,7 @@ INSERT INTO ##BlitzCacheProcs (SPID, QueryType, DatabaseName, AverageCPU, TotalC LastReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, MaxUsedGrantKB, PercentMemoryGrantUsed, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, QueryText, QueryPlan, TotalWorkerTimeForType, TotalElapsedTimeForType, TotalReadsForType, TotalExecutionCountForType, TotalWritesForType, SqlHandle, PlanHandle, QueryHash, QueryPlanHash, - min_worker_time, max_worker_time, is_parallel, min_elapsed_time, max_elapsed_time, age_minutes, age_minutes_lifetime) ' ; + min_worker_time, max_worker_time, is_parallel, min_elapsed_time, max_elapsed_time, age_minutes, age_minutes_lifetime, Pattern) ' ; SET @body += N' FROM (SELECT TOP (@Top) x.*, xpa.*, @@ -2239,7 +2241,8 @@ SELECT TOP (@Top) qs.min_elapsed_time / 1000.0, qs.max_elapsed_time / 1000.0, age_minutes, - age_minutes_lifetime '; + age_minutes_lifetime, + @SortOrder '; IF LEFT(@QueryFilter, 3) IN ('all', 'sta') @@ -2387,7 +2390,8 @@ BEGIN qs.min_elapsed_time / 1000.0, qs.max_worker_time / 1000.0, age_minutes, - age_minutes_lifetime '; + age_minutes_lifetime, + @SortOrder '; SET @sql += REPLACE(REPLACE(@body, '#view#', 'dm_exec_query_stats'), 'cached_time', 'creation_time') ; @@ -2622,7 +2626,7 @@ IF @Reanalyze = 0 BEGIN RAISERROR('Collecting execution plan information.', 0, 1) WITH NOWAIT; - EXEC sp_executesql @sql, N'@Top INT, @min_duration INT, @min_back INT', @Top, @DurationFilter_i, @MinutesBack; + EXEC sp_executesql @sql, N'@Top INT, @min_duration INT, @min_back INT, @SortOrder NVARCHAR(20)', @Top, @DurationFilter_i, @MinutesBack, @SortOrder; END; IF @SkipAnalysis = 1 @@ -7115,6 +7119,7 @@ ELSE TotalSpills BIGINT, AvgSpills MONEY, QueryPlanCost FLOAT, + Pattern NVARCHAR(20), JoinKey AS ServerName + Cast(CheckDate AS NVARCHAR(50)), CONSTRAINT [PK_' + REPLACE(REPLACE(@OutputTableName,N'[',N''),N']',N'') + N'] PRIMARY KEY CLUSTERED(ID ASC));' ); @@ -7216,6 +7221,22 @@ END '; EXEC(@StringToExecute); END; + /* If the table doesn't have the new Pattern column, add it */ + SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableName; + SET @StringToExecute = N'IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns + WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''Pattern'') + ALTER TABLE ' + @ObjectFullName + N' ADD Pattern NVARCHAR(20) NULL;'; + IF @ValidOutputServer = 1 + BEGIN + SET @StringToExecute = REPLACE(@StringToExecute,'''Pattern''','''''Pattern'''''); + SET @StringToExecute = REPLACE(@StringToExecute,'''' + @ObjectFullName + '''','''''' + @ObjectFullName + ''''''); + EXEC('EXEC('''+@StringToExecute+''') AT ' + @OutputServerName); + END; + ELSE + BEGIN + EXEC(@StringToExecute); + END + IF @CheckDateOverride IS NULL BEGIN SET @CheckDateOverride = SYSDATETIMEOFFSET(); @@ -7235,14 +7256,14 @@ END '; + ' (ServerName, CheckDate, Version, QueryType, DatabaseName, AverageCPU, TotalCPU, PercentCPUByType, CPUWeight, AverageDuration, TotalDuration, DurationWeight, PercentDurationByType, AverageReads, TotalReads, ReadWeight, PercentReadsByType, ' + ' AverageWrites, TotalWrites, WriteWeight, PercentWritesByType, ExecutionCount, ExecutionWeight, PercentExecutionsByType, ' + ' ExecutionsPerMinute, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, QueryHash, QueryPlanHash, StatementStartOffset, StatementEndOffset, PlanGenerationNum, MinReturnedRows, MaxReturnedRows, AverageReturnedRows, TotalReturnedRows, QueryText, QueryPlan, NumberOfPlans, NumberOfDistinctPlans, Warnings, ' - + ' SerialRequiredMemory, SerialDesiredMemory, MinGrantKB, MaxGrantKB, MinUsedGrantKB, MaxUsedGrantKB, PercentMemoryGrantUsed, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, QueryPlanCost ) ' + + ' SerialRequiredMemory, SerialDesiredMemory, MinGrantKB, MaxGrantKB, MinUsedGrantKB, MaxUsedGrantKB, PercentMemoryGrantUsed, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, QueryPlanCost, Pattern ) ' + 'SELECT TOP (@Top) ' + QUOTENAME(CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128)), '''') + ', @CheckDateOverride, ' + QUOTENAME(CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)), '''') + ', ' + ' QueryType, DatabaseName, AverageCPU, TotalCPU, PercentCPUByType, PercentCPU, AverageDuration, TotalDuration, PercentDuration, PercentDurationByType, AverageReads, TotalReads, PercentReads, PercentReadsByType, ' + ' AverageWrites, TotalWrites, PercentWrites, PercentWritesByType, ExecutionCount, PercentExecutions, PercentExecutionsByType, ' + ' ExecutionsPerMinute, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, QueryHash, QueryPlanHash, StatementStartOffset, StatementEndOffset, PlanGenerationNum, MinReturnedRows, MaxReturnedRows, AverageReturnedRows, TotalReturnedRows, QueryText, CAST(QueryPlan AS NVARCHAR(MAX)), NumberOfPlans, NumberOfDistinctPlans, Warnings, ' - + ' SerialRequiredMemory, SerialDesiredMemory, MinGrantKB, MaxGrantKB, MinUsedGrantKB, MaxUsedGrantKB, PercentMemoryGrantUsed, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, QueryPlanCost ' + + ' SerialRequiredMemory, SerialDesiredMemory, MinGrantKB, MaxGrantKB, MinUsedGrantKB, MaxUsedGrantKB, PercentMemoryGrantUsed, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, QueryPlanCost, Pattern ' + ' FROM ##BlitzCacheProcs ' + ' WHERE 1=1 '; @@ -7303,14 +7324,14 @@ END '; + ' (ServerName, CheckDate, Version, QueryType, DatabaseName, AverageCPU, TotalCPU, PercentCPUByType, CPUWeight, AverageDuration, TotalDuration, DurationWeight, PercentDurationByType, AverageReads, TotalReads, ReadWeight, PercentReadsByType, ' + ' AverageWrites, TotalWrites, WriteWeight, PercentWritesByType, ExecutionCount, ExecutionWeight, PercentExecutionsByType, ' + ' ExecutionsPerMinute, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, QueryHash, QueryPlanHash, StatementStartOffset, StatementEndOffset, PlanGenerationNum, MinReturnedRows, MaxReturnedRows, AverageReturnedRows, TotalReturnedRows, QueryText, QueryPlan, NumberOfPlans, NumberOfDistinctPlans, Warnings, ' - + ' SerialRequiredMemory, SerialDesiredMemory, MinGrantKB, MaxGrantKB, MinUsedGrantKB, MaxUsedGrantKB, PercentMemoryGrantUsed, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, QueryPlanCost ) ' + + ' SerialRequiredMemory, SerialDesiredMemory, MinGrantKB, MaxGrantKB, MinUsedGrantKB, MaxUsedGrantKB, PercentMemoryGrantUsed, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, QueryPlanCost, Pattern ) ' + 'SELECT TOP (@Top) ' + QUOTENAME(CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128)), '''') + ', @CheckDateOverride, ' + QUOTENAME(CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)), '''') + ', ' + ' QueryType, DatabaseName, AverageCPU, TotalCPU, PercentCPUByType, PercentCPU, AverageDuration, TotalDuration, PercentDuration, PercentDurationByType, AverageReads, TotalReads, PercentReads, PercentReadsByType, ' + ' AverageWrites, TotalWrites, PercentWrites, PercentWritesByType, ExecutionCount, PercentExecutions, PercentExecutionsByType, ' + ' ExecutionsPerMinute, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, QueryHash, QueryPlanHash, StatementStartOffset, StatementEndOffset, PlanGenerationNum, MinReturnedRows, MaxReturnedRows, AverageReturnedRows, TotalReturnedRows, QueryText, QueryPlan, NumberOfPlans, NumberOfDistinctPlans, Warnings, ' - + ' SerialRequiredMemory, SerialDesiredMemory, MinGrantKB, MaxGrantKB, MinUsedGrantKB, MaxUsedGrantKB, PercentMemoryGrantUsed, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, QueryPlanCost ' + + ' SerialRequiredMemory, SerialDesiredMemory, MinGrantKB, MaxGrantKB, MinUsedGrantKB, MaxUsedGrantKB, PercentMemoryGrantUsed, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, QueryPlanCost, Pattern ' + ' FROM ##BlitzCacheProcs ' + ' WHERE 1=1 '; @@ -7452,6 +7473,7 @@ END '; TotalSpills BIGINT, AvgSpills MONEY, QueryPlanCost FLOAT, + Pattern NVARCHAR(20), JoinKey AS ServerName + Cast(CheckDate AS NVARCHAR(50)), CONSTRAINT [PK_' + REPLACE(REPLACE(@OutputTableName,'[',''),']','') + '] PRIMARY KEY CLUSTERED(ID ASC));'; SET @StringToExecute += N' INSERT ' @@ -7459,14 +7481,14 @@ END '; + ' (ServerName, CheckDate, Version, QueryType, DatabaseName, AverageCPU, TotalCPU, PercentCPUByType, CPUWeight, AverageDuration, TotalDuration, DurationWeight, PercentDurationByType, AverageReads, TotalReads, ReadWeight, PercentReadsByType, ' + ' AverageWrites, TotalWrites, WriteWeight, PercentWritesByType, ExecutionCount, ExecutionWeight, PercentExecutionsByType, ' + ' ExecutionsPerMinute, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, QueryHash, QueryPlanHash, StatementStartOffset, StatementEndOffset, PlanGenerationNum, MinReturnedRows, MaxReturnedRows, AverageReturnedRows, TotalReturnedRows, QueryText, QueryPlan, NumberOfPlans, NumberOfDistinctPlans, Warnings, ' - + ' SerialRequiredMemory, SerialDesiredMemory, MinGrantKB, MaxGrantKB, MinUsedGrantKB, MaxUsedGrantKB, PercentMemoryGrantUsed, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, QueryPlanCost ) ' + + ' SerialRequiredMemory, SerialDesiredMemory, MinGrantKB, MaxGrantKB, MinUsedGrantKB, MaxUsedGrantKB, PercentMemoryGrantUsed, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, QueryPlanCost, Pattern ) ' + 'SELECT TOP (@Top) ' + QUOTENAME(CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128)), '''') + ', @CheckDateOverride, ' + QUOTENAME(CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)), '''') + ', ' + ' QueryType, DatabaseName, AverageCPU, TotalCPU, PercentCPUByType, PercentCPU, AverageDuration, TotalDuration, PercentDuration, PercentDurationByType, AverageReads, TotalReads, PercentReads, PercentReadsByType, ' + ' AverageWrites, TotalWrites, PercentWrites, PercentWritesByType, ExecutionCount, PercentExecutions, PercentExecutionsByType, ' + ' ExecutionsPerMinute, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, QueryHash, QueryPlanHash, StatementStartOffset, StatementEndOffset, PlanGenerationNum, MinReturnedRows, MaxReturnedRows, AverageReturnedRows, TotalReturnedRows, QueryText, QueryPlan, NumberOfPlans, NumberOfDistinctPlans, Warnings, ' - + ' SerialRequiredMemory, SerialDesiredMemory, MinGrantKB, MaxGrantKB, MinUsedGrantKB, MaxUsedGrantKB, PercentMemoryGrantUsed, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, QueryPlanCost ' + + ' SerialRequiredMemory, SerialDesiredMemory, MinGrantKB, MaxGrantKB, MinUsedGrantKB, MaxUsedGrantKB, PercentMemoryGrantUsed, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, QueryPlanCost, Pattern ' + ' FROM ##BlitzCacheProcs ' + ' WHERE 1=1 '; From f2f98e4eeffdc3937e43e6e847bafcc70823af80 Mon Sep 17 00:00:00 2001 From: Vlad Drumea <48413726+VladDBA@users.noreply.github.com> Date: Thu, 26 Jan 2023 21:32:24 +0200 Subject: [PATCH 377/662] Add files via upload --- sp_BlitzLock.sql | 143 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 142 insertions(+), 1 deletion(-) diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index 74a25d7bf..09e1c6686 100644 --- a/sp_BlitzLock.sql +++ b/sp_BlitzLock.sql @@ -2441,6 +2441,76 @@ BEGIN ) ), wait_time_hms = + /*the more wait time you rack up the less accurate this gets, + it's either that or erroring out*/ + CASE + WHEN + SUM + ( + CONVERT + ( + bigint, + dp.wait_time + ) + )/1000 > 2147483647 + THEN + CONVERT + ( + nvarchar(30), + DATEADD + ( + MINUTE, + ( + ( + SUM + ( + CONVERT + ( + bigint, + dp.wait_time + ) + ) + )/ + 60000 + ), + 0 + ), + 14 + ) + WHEN + SUM + ( + CONVERT + ( + bigint, + dp.wait_time + ) + ) BETWEEN 2147483648 AND 2147483647000 + THEN + CONVERT + ( + nvarchar(30), + DATEADD + ( + SECOND, + ( + ( + SUM + ( + CONVERT + ( + bigint, + dp.wait_time + ) + ) + )/ + 1000 + ), + 0 + ), + 14 + ) + ELSE CONVERT ( nvarchar(30), @@ -2461,6 +2531,7 @@ BEGIN ), 14 ) + END FROM #deadlock_owner_waiter AS dow JOIN #deadlock_process AS dp ON (dp.id = dow.owner_id @@ -2577,6 +2648,76 @@ BEGIN ) ) + N' ' + + /*the more wait time you rack up the less accurate this gets, + it's either that or erroring out*/ + CASE + WHEN + SUM + ( + CONVERT + ( + bigint, + wt.total_wait_time_ms + ) + )/1000 > 2147483647 + THEN + CONVERT + ( + nvarchar(30), + DATEADD + ( + MINUTE, + ( + ( + SUM + ( + CONVERT + ( + bigint, + wt.total_wait_time_ms + ) + ) + )/ + 60000 + ), + 0 + ), + 14 + ) + WHEN + SUM + ( + CONVERT + ( + bigint, + wt.total_wait_time_ms + ) + ) BETWEEN 2147483648 AND 2147483647000 + THEN + CONVERT + ( + nvarchar(30), + DATEADD + ( + SECOND, + ( + ( + SUM + ( + CONVERT + ( + bigint, + wt.total_wait_time_ms + ) + ) + )/ + 1000 + ), + 0 + ), + 14 + ) + ELSE CONVERT ( nvarchar(30), @@ -2596,7 +2737,7 @@ BEGIN 0 ), 14 - ) + + ) END + N' [dd hh:mm:ss:ms] of deadlock wait time.' FROM wait_time AS wt GROUP BY From 381f1a8eb99954a616da19187c7af9d2f9d70c2a Mon Sep 17 00:00:00 2001 From: sqlslinger <64641794+sqlslinger@users.noreply.github.com> Date: Mon, 30 Jan 2023 12:09:05 -0500 Subject: [PATCH 378/662] Set LOCK_TIMEOUT SET LOCK_TIMEOUT 1000 when APPLY sys.dm_exec_query_statistics_xml to prevent mutex issues. Set GitHub #3210 --- sp_BlitzFirst.sql | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/sp_BlitzFirst.sql b/sp_BlitzFirst.sql index 93d0b8db4..7dbcfd5d1 100644 --- a/sp_BlitzFirst.sql +++ b/sp_BlitzFirst.sql @@ -2293,8 +2293,9 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, CROSS APPLY sys.dm_exec_query_plan(r.plan_handle) AS qp '; IF EXISTS (SELECT * FROM sys.all_objects WHERE name = 'dm_exec_query_statistics_xml') - SET @StringToExecute = @StringToExecute + N' OUTER APPLY sys.dm_exec_query_statistics_xml(s.session_id) qs_live '; - + /* GitHub #3210 */ + SET @StringToExecute = N' + SET LOCK_TIMEOUT 1000 ' + @StringToExecute + N' OUTER APPLY sys.dm_exec_query_statistics_xml(s.session_id) qs_live '; SET @StringToExecute = @StringToExecute + N'; From 6ad54978bd554265cdbb912ee2e81faeb3705a6c Mon Sep 17 00:00:00 2001 From: sm8680 Date: Thu, 9 Feb 2023 07:14:55 -0500 Subject: [PATCH 379/662] Update sp_BlitzCache.sql Fixed 3 case sensitive issues on column object_id for table sys.all_columns switching it from upper case to lower case to match the column in the table. --- sp_BlitzCache.sql | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sp_BlitzCache.sql b/sp_BlitzCache.sql index ff07f9485..ad39383be 100644 --- a/sp_BlitzCache.sql +++ b/sp_BlitzCache.sql @@ -1159,18 +1159,18 @@ IF @SkipAnalysis = 1 DECLARE @AllSortSql NVARCHAR(MAX) = N''; DECLARE @VersionShowsMemoryGrants BIT; -IF EXISTS(SELECT * FROM sys.all_columns WHERE OBJECT_ID = OBJECT_ID('sys.dm_exec_query_stats') AND name = 'max_grant_kb') +IF EXISTS(SELECT * FROM sys.all_columns WHERE object_id = OBJECT_ID('sys.dm_exec_query_stats') AND name = 'max_grant_kb') SET @VersionShowsMemoryGrants = 1; ELSE SET @VersionShowsMemoryGrants = 0; DECLARE @VersionShowsSpills BIT; -IF EXISTS(SELECT * FROM sys.all_columns WHERE OBJECT_ID = OBJECT_ID('sys.dm_exec_query_stats') AND name = 'max_spills') +IF EXISTS(SELECT * FROM sys.all_columns WHERE object_id = OBJECT_ID('sys.dm_exec_query_stats') AND name = 'max_spills') SET @VersionShowsSpills = 1; ELSE SET @VersionShowsSpills = 0; -IF EXISTS(SELECT * FROM sys.all_columns WHERE OBJECT_ID = OBJECT_ID('sys.dm_exec_query_plan_stats') AND name = 'query_plan') +IF EXISTS(SELECT * FROM sys.all_columns WHERE object_id = OBJECT_ID('sys.dm_exec_query_plan_stats') AND name = 'query_plan') SET @VersionShowsAirQuoteActualPlans = 1; ELSE SET @VersionShowsAirQuoteActualPlans = 0; From c16c3c542f85e3841c489f5ae3c2502575fada12 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Wed, 15 Feb 2023 09:02:01 -0800 Subject: [PATCH 380/662] #3206 sp_BlitzLock truncation Closes #3206. --- sp_BlitzLock.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index 09e1c6686..2d5f3b356 100644 --- a/sp_BlitzLock.sql +++ b/sp_BlitzLock.sql @@ -1169,7 +1169,7 @@ BEGIN waiter_mode = w.l.value('@mode', 'nvarchar(256)'), owner_id = o.l.value('@id', 'nvarchar(256)'), owner_mode = o.l.value('@mode', 'nvarchar(256)'), - lock_type = N'OBJECT' + lock_type = CAST(N'OBJECT' AS NVARCHAR(100)) INTO #deadlock_owner_waiter FROM ( From f94668490ca4d9032a0f4b45acd344b664647888 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Wed, 15 Feb 2023 09:18:28 -0800 Subject: [PATCH 381/662] 2023-02-15 release prep 1 Changing the Merge Blitz.ps1 to run on my Mac, and updating SqlServerVersions.sql with the latest GDR. --- Documentation/Development/Merge Blitz.ps1 | 24 +++++++++++------------ SqlServerVersions.sql | 5 +++++ 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/Documentation/Development/Merge Blitz.ps1 b/Documentation/Development/Merge Blitz.ps1 index 2916f0407..14ab2ec06 100644 --- a/Documentation/Development/Merge Blitz.ps1 +++ b/Documentation/Development/Merge Blitz.ps1 @@ -1,39 +1,39 @@ #Set your file path -$FilePath = "C:\temp\SQL-Server-First-Responder-Kit" -$SqlVersionsPath = "$FilePath\SqlServerVersions.sql" -$BlitzFirstPath = "$FilePath\sp_BlitzFirst.sql" +$FilePath = "/Users/brentozar/LocalOnly/Github/SQL-Server-First-Responder-Kit" +$SqlVersionsPath = "$FilePath/SqlServerVersions.sql" +$BlitzFirstPath = "$FilePath/sp_BlitzFirst.sql" #All Core Blitz Without sp_BlitzQueryStore Get-ChildItem -Path "$FilePath" -Filter "sp_Blitz*.sql" | Where-Object { $_.FullName -notlike "*sp_BlitzQueryStore.sql*" -and $_.FullName -notlike "*sp_BlitzInMemoryOLTP*" -and $_.FullName -notlike "*sp_BlitzFirst*"} | ForEach-Object { Get-Content $_.FullName } | -Set-Content -Path "$FilePath\Install-Core-Blitz-No-Query-Store.sql" -Force +Set-Content -Path "$FilePath/Install-Core-Blitz-No-Query-Store.sql" -Force #append script to (re-)create SqlServerVersions Table (https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2429) if ( test-path "$SqlVersionsPath") - { Add-Content -Path "$FilePath\Install-Core-Blitz-No-Query-Store.sql" -Value (Get-Content -Path "$SqlVersionsPath")} + { Add-Content -Path "$FilePath/Install-Core-Blitz-No-Query-Store.sql" -Value (Get-Content -Path "$SqlVersionsPath")} if ( test-path "$BlitzFirstPath") - { Add-Content -Path "$FilePath\Install-Core-Blitz-No-Query-Store.sql" -Value (Get-Content -Path "$BlitzFirstPath")} + { Add-Content -Path "$FilePath/Install-Core-Blitz-No-Query-Store.sql" -Value (Get-Content -Path "$BlitzFirstPath")} #All Core Blitz With sp_BlitzQueryStore Get-ChildItem -Path "$FilePath" -Filter "sp_Blitz*.sql" | Where-Object { $_.FullName -notlike "*sp_BlitzInMemoryOLTP*" -and $_.FullName -notlike "*sp_BlitzFirst*"} | ForEach-Object { Get-Content $_.FullName } | -Set-Content -Path "$FilePath\Install-Core-Blitz-With-Query-Store.sql" -Force +Set-Content -Path "$FilePath/Install-Core-Blitz-With-Query-Store.sql" -Force #append script to (re-)create SqlServerVersions Table (https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2429) if ( test-path "$SqlVersionsPath") - { Add-Content -Path "$FilePath\Install-Core-Blitz-With-Query-Store.sql" -Value (Get-Content -Path "$SqlVersionsPath")} + { Add-Content -Path "$FilePath/Install-Core-Blitz-With-Query-Store.sql" -Value (Get-Content -Path "$SqlVersionsPath")} if ( test-path "$BlitzFirstPath") - { Add-Content -Path "$FilePath\Install-Core-Blitz-With-Query-Store.sql" -Value (Get-Content -Path "$BlitzFirstPath")} + { Add-Content -Path "$FilePath/Install-Core-Blitz-With-Query-Store.sql" -Value (Get-Content -Path "$BlitzFirstPath")} #All Scripts Get-ChildItem -Path "$FilePath" -Filter "sp_*.sql" | Where-Object { $_.FullName -notlike "*sp_BlitzInMemoryOLTP*" -and $_.FullName -notlike "*sp_BlitzFirst*"} | ForEach-Object { Get-Content $_.FullName } | -Set-Content -Path "$FilePath\Install-All-Scripts.sql" -Force +Set-Content -Path "$FilePath/Install-All-Scripts.sql" -Force #append script to (re-)create SqlServerVersions Table (https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2429) if ( test-path "$SqlVersionsPath") - { Add-Content -Path "$FilePath\Install-All-Scripts.sql" -Value (Get-Content -Path "$SqlVersionsPath")} + { Add-Content -Path "$FilePath/Install-All-Scripts.sql" -Value (Get-Content -Path "$SqlVersionsPath")} if ( test-path "$BlitzFirstPath") - { Add-Content -Path "$FilePath\Install-All-Scripts.sql" -Value (Get-Content -Path "$BlitzFirstPath")} + { Add-Content -Path "$FilePath/Install-All-Scripts.sql" -Value (Get-Content -Path "$BlitzFirstPath")} diff --git a/SqlServerVersions.sql b/SqlServerVersions.sql index 4db8ad366..d7bffc635 100644 --- a/SqlServerVersions.sql +++ b/SqlServerVersions.sql @@ -41,7 +41,9 @@ DELETE FROM dbo.SqlServerVersions; INSERT INTO dbo.SqlServerVersions (MajorVersionNumber, MinorVersionNumber, Branch, [Url], ReleaseDate, MainstreamSupportEndDate, ExtendedSupportEndDate, MajorVersionName, MinorVersionName) 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'), + (15, 4280, 'CU18 GDR', 'https://support.microsoft.com/kb/5021124', '2023-02-14', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 18 GDR'), (15, 4261, 'CU18', 'https://support.microsoft.com/en-us/help/5017593', '2022-09-28', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 18'), (15, 4249, 'CU17', 'https://support.microsoft.com/en-us/help/5016394', '2022-08-11', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 17'), (15, 4236, 'CU16 GDR', 'https://support.microsoft.com/en-us/help/5014353', '2022-06-14', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 16 GDR'), @@ -64,6 +66,7 @@ VALUES (15, 4003, 'CU1', 'https://support.microsoft.com/en-us/help/4527376', '2020-01-07', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 1 '), (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 '), + (14, 3460, 'RTM CU31 GDR', 'https://support.microsoft.com/kb/5021126', '2023-02-14', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 31 GDR'), (14, 3456, 'RTM CU31', 'https://support.microsoft.com/en-us/help/5016884', '2022-09-20', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 31'), (14, 3451, 'RTM CU30', 'https://support.microsoft.com/en-us/help/5013756', '2022-07-13', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 30'), (14, 3445, 'RTM CU29 GDR', 'https://support.microsoft.com/en-us/help/5014553', '2022-06-14', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 29 GDR'), @@ -100,6 +103,7 @@ VALUES (14, 1000, 'RTM ', '', '2017-10-02', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM '), (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, 6430, 'SP3 GDR', 'https://support.microsoft.com/kb/5021129', '2023-02-14', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3 GDR'), (13, 6419, 'SP3 GDR', 'https://support.microsoft.com/en-us/help/5014355', '2022-06-14', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3 GDR'), (13, 6404, 'SP3 GDR', 'https://support.microsoft.com/en-us/help/5006943', '2021-10-27', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3 GDR'), (13, 6300, 'SP3 ', 'https://support.microsoft.com/en-us/help/5003279', '2021-09-15', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3'), @@ -151,6 +155,7 @@ VALUES (13, 2164, 'RTM CU2', 'https://support.microsoft.com/en-us/help/3182270 ', '2016-09-22', '2018-01-09', '2018-01-09', 'SQL Server 2016', 'RTM Cumulative Update 2'), (13, 2149, 'RTM CU1', 'https://support.microsoft.com/en-us/help/3164674 ', '2016-07-25', '2018-01-09', '2018-01-09', 'SQL Server 2016', 'RTM Cumulative Update 1'), (13, 1601, 'RTM ', '', '2016-06-01', '2019-01-09', '2019-01-09', 'SQL Server 2016', 'RTM '), + (12, 6444, 'SP3 CU4 GDR', 'https://support.microsoft.com/kb/5021045', '2023-02-14', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 4 GDR'), (12, 6439, 'SP3 CU4 GDR', 'https://support.microsoft.com/en-us/help/5014164', '2022-06-14', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 4 GDR'), (12, 6433, 'SP3 CU4 GDR', 'https://support.microsoft.com/en-us/help/4583462', '2021-01-12', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 4 GDR'), (12, 6372, 'SP3 CU4 GDR', 'https://support.microsoft.com/en-us/help/4535288', '2020-02-11', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 4 GDR'), From a9459c8ecc9d4c984b0ade23c9f82e7ee5dfd7a3 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Wed, 15 Feb 2023 09:33:49 -0800 Subject: [PATCH 382/662] 2023-02-15 release prep 2 Bumping version numbers and dates, building install scripts. --- Install-All-Scripts.sql | 4492 ++++++++++++----------- Install-Core-Blitz-No-Query-Store.sql | 274 +- Install-Core-Blitz-With-Query-Store.sql | 278 +- sp_AllNightLog.sql | 4 +- sp_AllNightLog_Setup.sql | 4 +- sp_Blitz.sql | 6 +- sp_BlitzAnalysis.sql | 2 +- sp_BlitzBackups.sql | 4 +- sp_BlitzCache.sql | 5 +- sp_BlitzFirst.sql | 4 +- sp_BlitzInMemoryOLTP.sql | 2 +- sp_BlitzIndex.sql | 4 +- sp_BlitzLock.sql | 4 +- sp_BlitzQueryStore.sql | 4 +- sp_BlitzWho.sql | 4 +- sp_DatabaseRestore.sql | 4 +- sp_ineachdb.sql | 4 +- 17 files changed, 2801 insertions(+), 2298 deletions(-) diff --git a/Install-All-Scripts.sql b/Install-All-Scripts.sql index a78d6e77f..cd6a0269d 100644 --- a/Install-All-Scripts.sql +++ b/Install-All-Scripts.sql @@ -8,21 +8,29 @@ SET STATISTICS IO OFF; SET STATISTICS TIME OFF; GO -IF OBJECT_ID('dbo.sp_AllNightLog') IS NULL - EXEC ('CREATE PROCEDURE dbo.sp_AllNightLog AS RETURN 0;') +IF OBJECT_ID('dbo.sp_AllNightLog_Setup') IS NULL + EXEC ('CREATE PROCEDURE dbo.sp_AllNightLog_Setup AS RETURN 0;'); GO -ALTER PROCEDURE dbo.sp_AllNightLog - @PollForNewDatabases BIT = 0, /* Formerly Pollster */ - @Backup BIT = 0, /* Formerly LogShaming */ - @PollDiskForNewDatabases BIT = 0, - @Restore BIT = 0, - @Debug BIT = 0, - @Help BIT = 0, - @Version VARCHAR(30) = NULL OUTPUT, - @VersionDate DATETIME = NULL OUTPUT, - @VersionCheckMode BIT = 0 +ALTER PROCEDURE dbo.sp_AllNightLog_Setup + @RPOSeconds BIGINT = 30, + @RTOSeconds BIGINT = 30, + @BackupPath NVARCHAR(MAX) = NULL, + @RestorePath NVARCHAR(MAX) = NULL, + @Jobs TINYINT = 10, + @RunSetup BIT = 0, + @UpdateSetup BIT = 0, + @EnableBackupJobs INT = NULL, + @EnableRestoreJobs INT = NULL, + @Debug BIT = 0, + @FirstFullBackup BIT = 0, + @FirstDiffBackup BIT = 0, + @MoveFiles BIT = 1, + @Help BIT = 0, + @Version VARCHAR(30) = NULL OUTPUT, + @VersionDate DATETIME = NULL OUTPUT, + @VersionCheckMode BIT = 0 WITH RECOMPILE AS SET NOCOUNT ON; @@ -30,8 +38,7 @@ SET STATISTICS XML OFF; BEGIN; - -SELECT @Version = '8.12', @VersionDate = '20221213'; +SELECT @Version = '8.13', @VersionDate = '20230215'; IF(@VersionCheckMode = 1) BEGIN @@ -46,15 +53,35 @@ BEGIN /* - sp_AllNightLog from http://FirstResponderKit.org + sp_AllNightLog_Setup from http://FirstResponderKit.org - * @PollForNewDatabases = 1 polls sys.databases for new entries - * Unfortunately no other way currently to automate new database additions when restored from backups - * No triggers or extended events that easily do this + This script sets up a database, tables, rows, and jobs for sp_AllNightLog, including: + + * Creates a database + * Right now it''s hard-coded to use msdbCentral, that might change later - * @Backup = 1 polls msdbCentral.dbo.backup_worker for databases not backed up in [RPO], takes LOG backups - * Will switch to a full backup if none exists + * Creates tables in that database! + * dbo.backup_configuration + * Hold variables used by stored proc to make runtime decisions + * RPO: Seconds, how often we look for databases that need log backups + * Backup Path: The path we feed to Ola H''s backup proc + * dbo.backup_worker + * Holds list of databases and some information that helps our Agent jobs figure out if they need to take another log backup + + * Creates tables in msdb + * dbo.restore_configuration + * Holds variables used by stored proc to make runtime decisions + * RTO: Seconds, how often to look for log backups to restore + * Restore Path: The path we feed to sp_DatabaseRestore + * Move Files: Whether to move files to default data/log directories. + * dbo.restore_worker + * Holds list of databases and some information that helps our Agent jobs figure out if they need to look for files to restore + * Creates agent jobs + * 1 job that polls sys.databases for new entries + * 10 jobs that run to take log backups + * Based on a queue table + * Requires Ola Hallengren''s Database Backup stored proc To learn more, visit http://FirstResponderKit.org where you can download new versions for free, watch training videos on how it works, get more info on @@ -62,7 +89,7 @@ BEGIN Known limitations of this version: - Only Microsoft-supported versions of SQL Server. Sorry, 2005 and 2000! And really, maybe not even anything less than 2016. Heh. - - When restoring encrypted backups, the encryption certificate must already be installed. + - The repository database name is hard-coded to msdbCentral. Unknown limitations of this version: - None. (If we knew them, they would be known. Duh.) @@ -73,17 +100,26 @@ BEGIN Parameter explanations: - @PollForNewDatabases BIT, defaults to 0. When this is set to 1, runs in a perma-loop to find new entries in sys.databases - @Backup BIT, defaults to 0. When this is set to 1, runs in a perma-loop checking the backup_worker table for databases that need to be backed up - @Debug BIT, defaults to 0. Whent this is set to 1, it prints out dynamic SQL commands + @RunSetup BIT, defaults to 0. When this is set to 1, it will run the setup portion to create database, tables, and worker jobs. + @UpdateSetup BIT, defaults to 0. When set to 1, will update existing configs for RPO/RTO and database backup/restore paths. @RPOSeconds BIGINT, defaults to 30. Value in seconds you want to use to determine if a new log backup needs to be taken. - @BackupPath NVARCHAR(MAX), defaults to = ''D:\Backup''. You 99.99999% will need to change this path to something else. This tells Ola''s job where to put backups. + @BackupPath NVARCHAR(MAX), This is REQUIRED if @Runsetup=1. This tells Ola''s job where to put backups. + @MoveFiles BIT, defaults to 1. When this is set to 1, it will move files to default data/log directories + @Debug BIT, defaults to 0. Whent this is set to 1, it prints out dynamic SQL commands + Sample call: + EXEC dbo.sp_AllNightLog_Setup + @RunSetup = 1, + @RPOSeconds = 30, + @BackupPath = N''M:\MSSQL\Backup'', + @Debug = 1 + + For more documentation: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/ MIT License - Copyright (c) 2021 Brent Ozar Unlimited + Copyright (c) Brent Ozar Unlimited Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -106,8 +142,8 @@ BEGIN */'; -RETURN -END +RETURN; +END; /* IF @Help = 1 */ DECLARE @database NVARCHAR(128) = NULL; --Holds the database that's currently being processed DECLARE @error_number INT = NULL; --Used for TRY/CATCH @@ -115,45 +151,110 @@ DECLARE @error_severity INT; --Used for TRY/CATCH DECLARE @error_state INT; --Used for TRY/CATCH DECLARE @msg NVARCHAR(4000) = N''; --Used for RAISERROR DECLARE @rpo INT; --Used to hold the RPO value in our configuration table -DECLARE @rto INT; --Used to hold the RPO value in our configuration table DECLARE @backup_path NVARCHAR(MAX); --Used to hold the backup path in our configuration table -DECLARE @changebackuptype NVARCHAR(MAX); --Config table: Y = escalate to full backup, MSDB = escalate if MSDB history doesn't show a recent full. -DECLARE @encrypt NVARCHAR(MAX); --Config table: Y = encrypt the backup. N (default) = do not encrypt. -DECLARE @encryptionalgorithm NVARCHAR(MAX); --Config table: native 2014 choices include TRIPLE_DES_3KEY, AES_128, AES_192, AES_256 -DECLARE @servercertificate NVARCHAR(MAX); --Config table: server certificate that is used to encrypt the backup -DECLARE @restore_path_base NVARCHAR(MAX); --Used to hold the base backup path in our configuration table -DECLARE @restore_path_full NVARCHAR(MAX); --Used to hold the full backup path in our configuration table -DECLARE @restore_path_log NVARCHAR(MAX); --Used to hold the log backup path in our configuration table -DECLARE @restore_move_files INT; -- used to hold the move files bit in our configuration table DECLARE @db_sql NVARCHAR(MAX) = N''; --Used to hold the dynamic SQL to create msdbCentral DECLARE @tbl_sql NVARCHAR(MAX) = N''; --Used to hold the dynamic SQL that creates tables in msdbCentral DECLARE @database_name NVARCHAR(256) = N'msdbCentral'; --Used to hold the name of the database we create to centralize data --Right now it's hardcoded to msdbCentral, but I made it dynamic in case that changes down the line -DECLARE @cmd NVARCHAR(4000) = N'' --Holds dir cmd -DECLARE @FileList TABLE ( BackupFile NVARCHAR(255) ); --Where we dump @cmd -DECLARE @restore_full BIT = 0 --We use this one -DECLARE @only_logs_after NVARCHAR(30) = N'' +/*These variables control the loop to create/modify jobs*/ +DECLARE @job_sql NVARCHAR(MAX) = N''; --Used to hold the dynamic SQL that creates Agent jobs +DECLARE @counter INT = 0; --For looping to create 10 Agent jobs +DECLARE @job_category NVARCHAR(MAX) = N'''Database Maintenance'''; --Job category +DECLARE @job_owner NVARCHAR(128) = QUOTENAME(SUSER_SNAME(0x01), ''''); -- Admin user/owner +DECLARE @jobs_to_change TABLE(name SYSNAME); -- list of jobs we need to enable or disable +DECLARE @current_job_name SYSNAME; -- While looping through Agent jobs to enable or disable +DECLARE @active_start_date INT = (CONVERT(INT, CONVERT(VARCHAR(10), GETDATE(), 112))); +DECLARE @started_waiting_for_jobs DATETIME; --We need to wait for a while when disabling jobs + +/*Specifically for Backups*/ +DECLARE @job_name_backups NVARCHAR(MAX) = N'''sp_AllNightLog_Backup_Job_'''; --Name of log backup job +DECLARE @job_description_backups NVARCHAR(MAX) = N'''This is a worker for the purposes of taking log backups from msdbCentral.dbo.backup_worker queue table.'''; --Job description +DECLARE @job_command_backups NVARCHAR(MAX) = N'''EXEC sp_AllNightLog @Backup = 1'''; --Command the Agent job will run + +/*Specifically for Restores*/ +DECLARE @job_name_restores NVARCHAR(MAX) = N'''sp_AllNightLog_Restore_Job_'''; --Name of log backup job +DECLARE @job_description_restores NVARCHAR(MAX) = N'''This is a worker for the purposes of restoring log backups from msdb.dbo.restore_worker queue table.'''; --Job description +DECLARE @job_command_restores NVARCHAR(MAX) = N'''EXEC sp_AllNightLog @Restore = 1'''; --Command the Agent job will run + /* -Make sure we're doing something +Sanity check some variables */ -IF ( - @PollForNewDatabases = 0 - AND @PollDiskForNewDatabases = 0 - AND @Backup = 0 - AND @Restore = 0 - AND @Help = 0 -) - BEGIN - RAISERROR('You don''t seem to have picked an action for this stored procedure to take.', 0, 1) WITH NOWAIT - + + +IF ((@RunSetup = 0 OR @RunSetup IS NULL) AND (@UpdateSetup = 0 OR @UpdateSetup IS NULL)) + + BEGIN + + RAISERROR('You have to either run setup or update setup. You can''t not do neither nor, if you follow. Or not.', 0, 1) WITH NOWAIT; + + RETURN; + + END; + + +/* + +Should be a positive number + +*/ + +IF (@RPOSeconds < 0) + + BEGIN + RAISERROR('Please choose a positive number for @RPOSeconds', 0, 1) WITH NOWAIT; + RETURN; - END + END; + + +/* + +Probably shouldn't be more than 20 + +*/ + +IF (@Jobs > 20) OR (@Jobs < 1) + + BEGIN + RAISERROR('We advise sticking with 1-20 jobs.', 0, 1) WITH NOWAIT; + + RETURN; + END; + +/* + +Probably shouldn't be more than 4 hours + +*/ + +IF (@RPOSeconds >= 14400) + BEGIN + + RAISERROR('If your RPO is really 4 hours, perhaps you''d be interested in a more modest recovery model, like SIMPLE?', 0, 1) WITH NOWAIT; + + RETURN; + END; + + +/* + +Can't enable both the backup and restore jobs at the same time + +*/ + +IF @EnableBackupJobs = 1 AND @EnableRestoreJobs = 1 + BEGIN + + RAISERROR('You are not allowed to enable both the backup and restore jobs at the same time. Pick one, bucko.', 0, 1) WITH NOWAIT; + + RETURN; + END; /* Make sure xp_cmdshell is enabled @@ -192,1330 +293,1055 @@ IF NOT EXISTS (SELECT * FROM sys.procedures WHERE name = 'sp_DatabaseRestore') RETURN; END +/* -IF (@PollDiskForNewDatabases = 1 OR @Restore = 1) AND OBJECT_ID('msdb.dbo.restore_configuration') IS NOT NULL - BEGIN - - IF @Debug = 1 RAISERROR('Checking restore path', 0, 1) WITH NOWAIT; - - SELECT @restore_path_base = CONVERT(NVARCHAR(512), configuration_setting) - FROM msdb.dbo.restore_configuration c - WHERE configuration_name = N'log restore path'; +Basic path sanity checks +*/ - IF @restore_path_base IS NULL - BEGIN - RAISERROR('@restore_path cannot be NULL. Please check the msdb.dbo.restore_configuration table', 0, 1) WITH NOWAIT; +IF @RunSetup = 1 and @BackupPath is NULL + BEGIN + + RAISERROR('@BackupPath is required during setup', 0, 1) WITH NOWAIT; + RETURN; - END; + END - IF CHARINDEX('**', @restore_path_base) <> 0 - BEGIN +IF (@BackupPath NOT LIKE '[c-zC-Z]:\%') --Local path, don't think anyone has A or B drives +AND (@BackupPath NOT LIKE '\\[a-zA-Z0-9]%\%') --UNC path + + BEGIN + RAISERROR('Are you sure that''s a real path?', 0, 1) WITH NOWAIT; + + RETURN; + END; - /* If they passed in a dynamic **DATABASENAME**, stop at that folder looking for databases. More info: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/993 */ - IF CHARINDEX('**DATABASENAME**', @restore_path_base) <> 0 - BEGIN - SET @restore_path_base = SUBSTRING(@restore_path_base, 1, CHARINDEX('**DATABASENAME**',@restore_path_base) - 2); - END; +/* - SET @restore_path_base = REPLACE(@restore_path_base, '**AVAILABILITYGROUP**', ''); - SET @restore_path_base = REPLACE(@restore_path_base, '**BACKUPTYPE**', 'FULL'); - SET @restore_path_base = REPLACE(@restore_path_base, '**SERVERNAME**', REPLACE(CAST(SERVERPROPERTY('servername') AS nvarchar(max)),'\','$')); +If you want to update the table, one of these has to not be NULL - IF CHARINDEX('\',CAST(SERVERPROPERTY('servername') AS nvarchar(max))) > 0 - BEGIN - SET @restore_path_base = REPLACE(@restore_path_base, '**SERVERNAMEWITHOUTINSTANCE**', SUBSTRING(CAST(SERVERPROPERTY('servername') AS nvarchar(max)), 1, (CHARINDEX('\',CAST(SERVERPROPERTY('servername') AS nvarchar(max))) - 1))); - SET @restore_path_base = REPLACE(@restore_path_base, '**INSTANCENAME**', SUBSTRING(CAST(SERVERPROPERTY('servername') AS nvarchar(max)), CHARINDEX('\',CAST(SERVERPROPERTY('servername') AS nvarchar(max))), (LEN(CAST(SERVERPROPERTY('servername') AS nvarchar(max))) - CHARINDEX('\',CAST(SERVERPROPERTY('servername') AS nvarchar(max)))) + 1)); - END - ELSE /* No instance installed */ - BEGIN - SET @restore_path_base = REPLACE(@restore_path_base, '**SERVERNAMEWITHOUTINSTANCE**', CAST(SERVERPROPERTY('servername') AS nvarchar(max))); - SET @restore_path_base = REPLACE(@restore_path_base, '**INSTANCENAME**', 'DEFAULT'); - END +*/ - IF CHARINDEX('**CLUSTER**', @restore_path_base) <> 0 - BEGIN - DECLARE @ClusterName NVARCHAR(128); - IF EXISTS(SELECT * FROM sys.all_objects WHERE name = 'dm_hadr_cluster') - BEGIN - SELECT @ClusterName = cluster_name FROM sys.dm_hadr_cluster; - END - SET @restore_path_base = REPLACE(@restore_path_base, '**CLUSTER**', COALESCE(@ClusterName,'')); - END; +IF @UpdateSetup = 1 + AND ( @RPOSeconds IS NULL + AND @BackupPath IS NULL + AND @RPOSeconds IS NULL + AND @RestorePath IS NULL + AND @EnableBackupJobs IS NULL + AND @EnableRestoreJobs IS NULL + ) - END /* IF CHARINDEX('**', @restore_path_base) <> 0 */ - - SELECT @restore_move_files = CONVERT(BIT, configuration_setting) - FROM msdb.dbo.restore_configuration c - WHERE configuration_name = N'move files'; - - IF @restore_move_files is NULL BEGIN - -- Set to default value of 1 - SET @restore_move_files = 1 - END - END /* IF @PollDiskForNewDatabases = 1 OR @Restore = 1 */ + RAISERROR('If you want to update configuration settings, they can''t be NULL. Please Make sure @RPOSeconds / @RTOSeconds or @BackupPath / @RestorePath has a value', 0, 1) WITH NOWAIT; + RETURN; -/* + END; -Certain variables necessarily skip to parts of this script that are irrelevant -in both directions to each other. They are used for other stuff. -*/ +IF @UpdateSetup = 1 + GOTO UpdateConfigs; +IF @RunSetup = 1 +BEGIN + BEGIN TRY -/* + BEGIN + -Pollster use happens strictly to check for new databases in sys.databases to place them in a worker queue + /* + + First check to see if Agent is running -- we'll get errors if it's not + + */ + + + IF ( SELECT 1 + FROM sys.all_objects + WHERE name = 'dm_server_services' ) IS NOT NULL -*/ + BEGIN -IF @PollForNewDatabases = 1 - GOTO Pollster; + IF EXISTS ( + SELECT 1 + FROM sys.dm_server_services + WHERE servicename LIKE 'SQL Server Agent%' + AND status_desc = 'Stopped' + ) + + BEGIN + + RAISERROR('SQL Server Agent is not currently running -- it needs to be enabled to add backup worker jobs and the new database polling job', 0, 1) WITH NOWAIT; + + RETURN; + + END; + + END + -/* + BEGIN -LogShamer happens when we need to find and assign work to a worker job for backups -*/ + /* + + Check to see if the database exists -IF @Backup = 1 - GOTO LogShamer; + */ + + RAISERROR('Checking for msdbCentral', 0, 1) WITH NOWAIT; -/* + SET @db_sql += N' -Pollster use happens strictly to check for new databases in sys.databases to place them in a worker queue + IF DATABASEPROPERTYEX(' + QUOTENAME(@database_name, '''') + ', ''Status'') IS NULL -*/ + BEGIN -IF @PollDiskForNewDatabases = 1 - GOTO DiskPollster; + RAISERROR(''Creating msdbCentral'', 0, 1) WITH NOWAIT; + CREATE DATABASE ' + QUOTENAME(@database_name) + '; + + ALTER DATABASE ' + QUOTENAME(@database_name) + ' SET RECOVERY FULL; + + END -/* + '; -Restoregasm Addict happens when we need to find and assign work to a worker job for restores -*/ + IF @Debug = 1 + BEGIN + RAISERROR(@db_sql, 0, 1) WITH NOWAIT; + END; -IF @Restore = 1 - GOTO Restoregasm_Addict; + IF @db_sql IS NULL + BEGIN + RAISERROR('@db_sql is NULL for some reason', 0, 1) WITH NOWAIT; + END; -/* + EXEC sp_executesql @db_sql; -Begin Polling section -*/ + /* + + Check for tables and stuff + */ + + RAISERROR('Checking for tables in msdbCentral', 0, 1) WITH NOWAIT; -/* + SET @tbl_sql += N' + + USE ' + QUOTENAME(@database_name) + ' + + + IF OBJECT_ID(''' + QUOTENAME(@database_name) + '.dbo.backup_configuration'') IS NULL + + BEGIN + + RAISERROR(''Creating table dbo.backup_configuration'', 0, 1) WITH NOWAIT; + + CREATE TABLE dbo.backup_configuration ( + database_name NVARCHAR(256), + configuration_name NVARCHAR(512), + configuration_description NVARCHAR(512), + configuration_setting NVARCHAR(MAX) + ); + + END + + ELSE + + BEGIN + + + RAISERROR(''Backup configuration table exists, truncating'', 0, 1) WITH NOWAIT; + + + TRUNCATE TABLE dbo.backup_configuration -This section runs in a loop checking for new databases added to the server, or broken backups + + END -*/ + RAISERROR(''Inserting configuration values'', 0, 1) WITH NOWAIT; -Pollster: + + INSERT dbo.backup_configuration (database_name, configuration_name, configuration_description, configuration_setting) + VALUES (''all'', ''log backup frequency'', ''The length of time in second between Log Backups.'', ''' + CONVERT(NVARCHAR(10), @RPOSeconds) + '''); + + INSERT dbo.backup_configuration (database_name, configuration_name, configuration_description, configuration_setting) + VALUES (''all'', ''log backup path'', ''The path to which Log Backups should go.'', ''' + @BackupPath + '''); + + INSERT dbo.backup_configuration (database_name, configuration_name, configuration_description, configuration_setting) + VALUES (''all'', ''change backup type'', ''For Ola Hallengren DatabaseBackup @ChangeBackupType param: Y = escalate to fulls, MSDB = escalate by checking msdb backup history.'', ''MSDB''); + + INSERT dbo.backup_configuration (database_name, configuration_name, configuration_description, configuration_setting) + VALUES (''all'', ''encrypt'', ''For Ola Hallengren DatabaseBackup: Y = encrypt the backup. N (default) = do not encrypt.'', NULL); + + INSERT dbo.backup_configuration (database_name, configuration_name, configuration_description, configuration_setting) + VALUES (''all'', ''encryptionalgorithm'', ''For Ola Hallengren DatabaseBackup: native 2014 choices include TRIPLE_DES_3KEY, AES_128, AES_192, AES_256.'', NULL); + + INSERT dbo.backup_configuration (database_name, configuration_name, configuration_description, configuration_setting) + VALUES (''all'', ''servercertificate'', ''For Ola Hallengren DatabaseBackup: server certificate that is used to encrypt the backup.'', NULL); + + + IF OBJECT_ID(''' + QUOTENAME(@database_name) + '.dbo.backup_worker'') IS NULL + + BEGIN + + + RAISERROR(''Creating table dbo.backup_worker'', 0, 1) WITH NOWAIT; + + CREATE TABLE dbo.backup_worker ( + id INT IDENTITY(1, 1) PRIMARY KEY CLUSTERED, + database_name NVARCHAR(256), + last_log_backup_start_time DATETIME DEFAULT ''19000101'', + last_log_backup_finish_time DATETIME DEFAULT ''99991231'', + is_started BIT DEFAULT 0, + is_completed BIT DEFAULT 0, + error_number INT DEFAULT NULL, + last_error_date DATETIME DEFAULT NULL, + ignore_database BIT DEFAULT 0, + full_backup_required BIT DEFAULT ' + CASE WHEN @FirstFullBackup = 0 THEN N'0,' ELSE N'1,' END + CHAR(10) + + N'diff_backup_required BIT DEFAULT ' + CASE WHEN @FirstDiffBackup = 0 THEN N'0' ELSE N'1' END + CHAR(10) + + N'); + + END; + + ELSE - IF @Debug = 1 RAISERROR('Beginning Pollster', 0, 1) WITH NOWAIT; - - IF OBJECT_ID('msdbCentral.dbo.backup_worker') IS NOT NULL - - BEGIN - - WHILE @PollForNewDatabases = 1 - - BEGIN - - BEGIN TRY - - IF @Debug = 1 RAISERROR('Checking for new databases...', 0, 1) WITH NOWAIT; + BEGIN - /* - - Look for new non-system databases -- there should probably be additional filters here for accessibility, etc. - */ - - INSERT msdbCentral.dbo.backup_worker (database_name) - SELECT d.name - FROM sys.databases d - WHERE NOT EXISTS ( - SELECT 1 - FROM msdbCentral.dbo.backup_worker bw - WHERE bw.database_name = d.name - ) - AND d.database_id > 4; + RAISERROR(''Backup worker table exists, truncating'', 0, 1) WITH NOWAIT; + + + TRUNCATE TABLE dbo.backup_worker - IF @Debug = 1 RAISERROR('Checking for wayward databases', 0, 1) WITH NOWAIT; - /* - - This section aims to find databases that have - * Had a log backup ever (the default for finish time is 9999-12-31, so anything with a more recent finish time has had a log backup) - * Not had a log backup start in the last 5 minutes (this could be trouble! or a really big log backup) - * Also checks msdb.dbo.backupset to make sure the database has a full backup associated with it (otherwise it's the first full, and we don't need to start taking log backups yet) + END - */ - - IF EXISTS ( - - SELECT 1 - FROM msdbCentral.dbo.backup_worker bw WITH (READPAST) - WHERE bw.last_log_backup_finish_time < '99991231' - AND bw.last_log_backup_start_time < DATEADD(SECOND, (@rpo * -1), GETDATE()) - AND EXISTS ( - SELECT 1 - FROM msdb.dbo.backupset b - WHERE b.database_name = bw.database_name - AND b.type = 'D' - ) - ) - - BEGIN + + RAISERROR(''Inserting databases for backups'', 0, 1) WITH NOWAIT; - IF @Debug = 1 RAISERROR('Resetting databases with a log backup and no log backup in the last 5 minutes', 0, 1) WITH NOWAIT; + INSERT ' + QUOTENAME(@database_name) + '.dbo.backup_worker (database_name) + SELECT d.name + FROM sys.databases d + WHERE NOT EXISTS ( + SELECT * + FROM msdbCentral.dbo.backup_worker bw + WHERE bw.database_name = d.name + ) + AND d.database_id > 4; + + '; - - UPDATE bw - SET bw.is_started = 0, - bw.is_completed = 1, - bw.last_log_backup_start_time = '19000101' - FROM msdbCentral.dbo.backup_worker bw - WHERE bw.last_log_backup_finish_time < '99991231' - AND bw.last_log_backup_start_time < DATEADD(SECOND, (@rpo * -1), GETDATE()) - AND EXISTS ( - SELECT 1 - FROM msdb.dbo.backupset b - WHERE b.database_name = bw.database_name - AND b.type = 'D' - ); + + IF @Debug = 1 + BEGIN + SET @msg = SUBSTRING(@tbl_sql, 0, 2044) + RAISERROR(@msg, 0, 1) WITH NOWAIT; + SET @msg = SUBSTRING(@tbl_sql, 2044, 4088) + RAISERROR(@msg, 0, 1) WITH NOWAIT; + SET @msg = SUBSTRING(@tbl_sql, 4088, 6132) + RAISERROR(@msg, 0, 1) WITH NOWAIT; + SET @msg = SUBSTRING(@tbl_sql, 6132, 8176) + RAISERROR(@msg, 0, 1) WITH NOWAIT; + END; - - END; --End check for wayward databases + + IF @tbl_sql IS NULL + BEGIN + RAISERROR('@tbl_sql is NULL for some reason', 0, 1) WITH NOWAIT; + END; + + EXEC sp_executesql @tbl_sql; + + /* - Wait 1 minute between runs, we don't need to be checking this constantly + This section creates tables for restore workers to work off of */ - - IF @Debug = 1 RAISERROR('Waiting for 1 minute', 0, 1) WITH NOWAIT; - - WAITFOR DELAY '00:01:00.000'; + + /* + + In search of msdb + + */ + + RAISERROR('Checking for msdb. Yeah, I know...', 0, 1) WITH NOWAIT; + + IF DATABASEPROPERTYEX('msdb', 'Status') IS NULL - END TRY + BEGIN - BEGIN CATCH + RAISERROR('YOU HAVE NO MSDB WHY?!', 0, 1) WITH NOWAIT; + RETURN; + + END; - SELECT @msg = N'Error inserting databases to msdbCentral.dbo.backup_worker, error number is ' + CONVERT(NVARCHAR(10), ERROR_NUMBER()) + ', error message is ' + ERROR_MESSAGE(), - @error_severity = ERROR_SEVERITY(), - @error_state = ERROR_STATE(); - RAISERROR(@msg, @error_severity, @error_state) WITH NOWAIT; + /* In search of restore_configuration */ - - WHILE @@TRANCOUNT > 0 - ROLLBACK; + RAISERROR('Checking for Restore Worker tables in msdb', 0, 1) WITH NOWAIT; + IF OBJECT_ID('msdb.dbo.restore_configuration') IS NULL - END CATCH; - - - END; + BEGIN - /* Check to make sure job is still enabled */ - IF NOT EXISTS ( - SELECT * - FROM msdb.dbo.sysjobs - WHERE name = 'sp_AllNightLog_PollForNewDatabases' - AND enabled = 1 - ) - BEGIN - RAISERROR('sp_AllNightLog_PollForNewDatabases job is disabled, so gracefully exiting. It feels graceful to me, anyway.', 0, 1) WITH NOWAIT; - RETURN; - END - - END;-- End Pollster loop - - ELSE - - BEGIN - - RAISERROR('msdbCentral.dbo.backup_worker does not exist, please create it.', 0, 1) WITH NOWAIT; - RETURN; - - END; - RETURN; + RAISERROR('Creating restore_configuration table in msdb', 0, 1) WITH NOWAIT; + CREATE TABLE msdb.dbo.restore_configuration ( + database_name NVARCHAR(256), + configuration_name NVARCHAR(512), + configuration_description NVARCHAR(512), + configuration_setting NVARCHAR(MAX) + ); -/* + END; -End of Pollster -*/ + ELSE -/* + BEGIN -Begin DiskPollster + RAISERROR('Restore configuration table exists, truncating', 0, 1) WITH NOWAIT; -*/ + TRUNCATE TABLE msdb.dbo.restore_configuration; + + END; -/* + RAISERROR('Inserting configuration values to msdb.dbo.restore_configuration', 0, 1) WITH NOWAIT; + + INSERT msdb.dbo.restore_configuration (database_name, configuration_name, configuration_description, configuration_setting) + VALUES ('all', 'log restore frequency', 'The length of time in second between Log Restores.', @RTOSeconds); + + INSERT msdb.dbo.restore_configuration (database_name, configuration_name, configuration_description, configuration_setting) + VALUES ('all', 'log restore path', 'The path to which Log Restores come from.', @RestorePath); -This section runs in a loop checking restore path for new databases added to the server, or broken restores + INSERT msdb.dbo.restore_configuration (database_name, configuration_name, configuration_description, configuration_setting) + VALUES ('all', 'move files', 'Determines if we move database files to default data/log directories.', @MoveFiles); -*/ + IF OBJECT_ID('msdb.dbo.restore_worker') IS NULL + + BEGIN + + + RAISERROR('Creating table msdb.dbo.restore_worker', 0, 1) WITH NOWAIT; + + CREATE TABLE msdb.dbo.restore_worker ( + id INT IDENTITY(1, 1) PRIMARY KEY CLUSTERED, + database_name NVARCHAR(256), + last_log_restore_start_time DATETIME DEFAULT '19000101', + last_log_restore_finish_time DATETIME DEFAULT '99991231', + is_started BIT DEFAULT 0, + is_completed BIT DEFAULT 0, + error_number INT DEFAULT NULL, + last_error_date DATETIME DEFAULT NULL, + ignore_database BIT DEFAULT 0, + full_backup_required BIT DEFAULT 0, + diff_backup_required BIT DEFAULT 0 + ); + + + RAISERROR('Inserting databases for restores', 0, 1) WITH NOWAIT; + + INSERT msdb.dbo.restore_worker (database_name) + SELECT d.name + FROM sys.databases d + WHERE NOT EXISTS ( + SELECT * + FROM msdb.dbo.restore_worker bw + WHERE bw.database_name = d.name + ) + AND d.database_id > 4; + + + END; -DiskPollster: - IF @Debug = 1 RAISERROR('Beginning DiskPollster', 0, 1) WITH NOWAIT; - - IF OBJECT_ID('msdb.dbo.restore_configuration') IS NOT NULL - - BEGIN - WHILE @PollDiskForNewDatabases = 1 + /* + + Add Jobs + + */ + + + + /* + + Look for our ten second schedule -- all jobs use this to restart themselves if they fail + + Fun fact: you can add the same schedule name multiple times, so we don't want to just stick it in there + + */ + + + RAISERROR('Checking for ten second schedule', 0, 1) WITH NOWAIT; + + IF NOT EXISTS ( + SELECT 1 + FROM msdb.dbo.sysschedules + WHERE name = 'ten_seconds' + ) - BEGIN + BEGIN + + + RAISERROR('Creating ten second schedule', 0, 1) WITH NOWAIT; + + + EXEC msdb.dbo.sp_add_schedule @schedule_name= ten_seconds, + @enabled = 1, + @freq_type = 4, + @freq_interval = 1, + @freq_subday_type = 2, + @freq_subday_interval = 10, + @freq_relative_interval = 0, + @freq_recurrence_factor = 0, + @active_start_date = @active_start_date, + @active_end_date = 99991231, + @active_start_time = 0, + @active_end_time = 235959; - BEGIN TRY + END; + - IF @Debug = 1 RAISERROR('Checking for new databases in: ', 0, 1) WITH NOWAIT; - IF @Debug = 1 RAISERROR(@restore_path_base, 0, 1) WITH NOWAIT; + /* + + Look for Backup Pollster job -- this job sets up our watcher for new databases to back up + + */ - /* + + RAISERROR('Checking for pollster job', 0, 1) WITH NOWAIT; + + + IF NOT EXISTS ( + SELECT 1 + FROM msdb.dbo.sysjobs + WHERE name = 'sp_AllNightLog_PollForNewDatabases' + ) + + + BEGIN - Look for new non-system databases -- there should probably be additional filters here for accessibility, etc. + + RAISERROR('Creating pollster job', 0, 1) WITH NOWAIT; + + IF @EnableBackupJobs = 1 + BEGIN + EXEC msdb.dbo.sp_add_job @job_name = sp_AllNightLog_PollForNewDatabases, + @description = 'This is a worker for the purposes of polling sys.databases for new entries to insert to the worker queue table.', + @category_name = 'Database Maintenance', + @owner_login_name = 'sa', + @enabled = 1; + END + ELSE + BEGIN + EXEC msdb.dbo.sp_add_job @job_name = sp_AllNightLog_PollForNewDatabases, + @description = 'This is a worker for the purposes of polling sys.databases for new entries to insert to the worker queue table.', + @category_name = 'Database Maintenance', + @owner_login_name = 'sa', + @enabled = 0; + END + + + RAISERROR('Adding job step', 0, 1) WITH NOWAIT; - */ - /* + EXEC msdb.dbo.sp_add_jobstep @job_name = sp_AllNightLog_PollForNewDatabases, + @step_name = sp_AllNightLog_PollForNewDatabases, + @subsystem = 'TSQL', + @command = 'EXEC sp_AllNightLog @PollForNewDatabases = 1'; + + + + RAISERROR('Adding job server', 0, 1) WITH NOWAIT; + - This setups up the @cmd variable to check the restore path for new folders + EXEC msdb.dbo.sp_add_jobserver @job_name = sp_AllNightLog_PollForNewDatabases; + + + + RAISERROR('Attaching schedule', 0, 1) WITH NOWAIT; + - In our case, a new folder means a new database, because we assume a pristine path + EXEC msdb.dbo.sp_attach_schedule @job_name = sp_AllNightLog_PollForNewDatabases, + @schedule_name = ten_seconds; + + + END; + - */ - SET @cmd = N'DIR /b "' + @restore_path_base + N'"'; - - IF @Debug = 1 - BEGIN - PRINT @cmd; - END - + /* + + Look for Restore Pollster job -- this job sets up our watcher for new databases to back up + + */ + + + RAISERROR('Checking for restore pollster job', 0, 1) WITH NOWAIT; + + + IF NOT EXISTS ( + SELECT 1 + FROM msdb.dbo.sysjobs + WHERE name = 'sp_AllNightLog_PollDiskForNewDatabases' + ) + + + BEGIN + + + RAISERROR('Creating restore pollster job', 0, 1) WITH NOWAIT; + - DELETE @FileList; - INSERT INTO @FileList (BackupFile) - EXEC master.sys.xp_cmdshell @cmd; + IF @EnableRestoreJobs = 1 + BEGIN + EXEC msdb.dbo.sp_add_job @job_name = sp_AllNightLog_PollDiskForNewDatabases, + @description = 'This is a worker for the purposes of polling your restore path for new entries to insert to the worker queue table.', + @category_name = 'Database Maintenance', + @owner_login_name = 'sa', + @enabled = 1; + END + ELSE + BEGIN + EXEC msdb.dbo.sp_add_job @job_name = sp_AllNightLog_PollDiskForNewDatabases, + @description = 'This is a worker for the purposes of polling your restore path for new entries to insert to the worker queue table.', + @category_name = 'Database Maintenance', + @owner_login_name = 'sa', + @enabled = 0; + END + + + + RAISERROR('Adding restore job step', 0, 1) WITH NOWAIT; + - IF ( - SELECT COUNT(*) - FROM @FileList AS fl - WHERE fl.BackupFile = 'The system cannot find the path specified.' - OR fl.BackupFile = 'File Not Found' - ) = 1 + EXEC msdb.dbo.sp_add_jobstep @job_name = sp_AllNightLog_PollDiskForNewDatabases, + @step_name = sp_AllNightLog_PollDiskForNewDatabases, + @subsystem = 'TSQL', + @command = 'EXEC sp_AllNightLog @PollDiskForNewDatabases = 1'; + + + + RAISERROR('Adding restore job server', 0, 1) WITH NOWAIT; - BEGIN - RAISERROR('No rows were returned for that database\path', 0, 1) WITH NOWAIT; + EXEC msdb.dbo.sp_add_jobserver @job_name = sp_AllNightLog_PollDiskForNewDatabases; - END; + + + RAISERROR('Attaching schedule', 0, 1) WITH NOWAIT; + + + EXEC msdb.dbo.sp_attach_schedule @job_name = sp_AllNightLog_PollDiskForNewDatabases, + @schedule_name = ten_seconds; + + + END; - IF ( - SELECT COUNT(*) - FROM @FileList AS fl - WHERE fl.BackupFile = 'Access is denied.' - ) = 1 - BEGIN - - RAISERROR('Access is denied to %s', 16, 1, @restore_path_base) WITH NOWAIT; - END; + /* + + This section creates @Jobs (quantity) of worker jobs to take log backups with - IF ( - SELECT COUNT(*) - FROM @FileList AS fl - ) = 1 - AND ( - SELECT COUNT(*) - FROM @FileList AS fl - WHERE fl.BackupFile IS NULL - ) = 1 + They work in a queue - BEGIN - - RAISERROR('That directory appears to be empty', 0, 1) WITH NOWAIT; - - RETURN; - - END + It's queuete + + */ - IF ( - SELECT COUNT(*) - FROM @FileList AS fl - WHERE fl.BackupFile = 'The user name or password is incorrect.' - ) = 1 - BEGIN - - RAISERROR('Incorrect user name or password for %s', 16, 1, @restore_path_base) WITH NOWAIT; + RAISERROR('Checking for sp_AllNightLog backup jobs', 0, 1) WITH NOWAIT; + + + SELECT @counter = COUNT(*) + 1 + FROM msdb.dbo.sysjobs + WHERE name LIKE 'sp[_]AllNightLog[_]Backup[_]%'; - END; + SET @msg = 'Found ' + CONVERT(NVARCHAR(10), (@counter - 1)) + ' backup jobs -- ' + CASE WHEN @counter < @Jobs THEN + 'starting loop!' + WHEN @counter >= @Jobs THEN 'skipping loop!' + ELSE 'Oh woah something weird happened!' + END; - INSERT msdb.dbo.restore_worker (database_name) - SELECT fl.BackupFile - FROM @FileList AS fl - WHERE fl.BackupFile IS NOT NULL - AND fl.BackupFile COLLATE DATABASE_DEFAULT NOT IN (SELECT name from sys.databases where database_id < 5) - AND NOT EXISTS - ( - SELECT 1 - FROM msdb.dbo.restore_worker rw - WHERE rw.database_name = fl.BackupFile - ) + RAISERROR(@msg, 0, 1) WITH NOWAIT; - IF @Debug = 1 RAISERROR('Checking for wayward databases', 0, 1) WITH NOWAIT; + + WHILE @counter <= @Jobs - /* - This section aims to find databases that have - * Had a log restore ever (the default for finish time is 9999-12-31, so anything with a more recent finish time has had a log restore) - * Not had a log restore start in the last 5 minutes (this could be trouble! or a really big log restore) - * Also checks msdb.dbo.backupset to make sure the database has a full backup associated with it (otherwise it's the first full, and we don't need to start adding log restores yet) + BEGIN - */ - - IF EXISTS ( - - SELECT 1 - FROM msdb.dbo.restore_worker rw WITH (READPAST) - WHERE rw.last_log_restore_finish_time < '99991231' - AND rw.last_log_restore_start_time < DATEADD(SECOND, (@rto * -1), GETDATE()) - AND EXISTS ( - SELECT 1 - FROM msdb.dbo.restorehistory r - WHERE r.destination_database_name = rw.database_name - AND r.restore_type = 'D' - ) - ) - - BEGIN - IF @Debug = 1 RAISERROR('Resetting databases with a log restore and no log restore in the last 5 minutes', 0, 1) WITH NOWAIT; - - - UPDATE rw - SET rw.is_started = 0, - rw.is_completed = 1, - rw.last_log_restore_start_time = '19000101' - FROM msdb.dbo.restore_worker rw - WHERE rw.last_log_restore_finish_time < '99991231' - AND rw.last_log_restore_start_time < DATEADD(SECOND, (@rto * -1), GETDATE()) - AND EXISTS ( - SELECT 1 - FROM msdb.dbo.restorehistory r - WHERE r.destination_database_name = rw.database_name - AND r.restore_type = 'D' - ); - - - END; --End check for wayward databases + RAISERROR('Setting job name', 0, 1) WITH NOWAIT; - /* - - Wait 1 minute between runs, we don't need to be checking this constantly - - */ + SET @job_name_backups = N'sp_AllNightLog_Backup_' + CASE WHEN @counter < 10 THEN N'0' + CONVERT(NVARCHAR(10), @counter) + WHEN @counter >= 10 THEN CONVERT(NVARCHAR(10), @counter) + END; + + + RAISERROR('Setting @job_sql', 0, 1) WITH NOWAIT; - /* Check to make sure job is still enabled */ - IF NOT EXISTS ( - SELECT * - FROM msdb.dbo.sysjobs - WHERE name = 'sp_AllNightLog_PollDiskForNewDatabases' - AND enabled = 1 - ) - BEGIN - RAISERROR('sp_AllNightLog_PollDiskForNewDatabases job is disabled, so gracefully exiting. It feels graceful to me, anyway.', 0, 1) WITH NOWAIT; - RETURN; - END - - IF @Debug = 1 RAISERROR('Waiting for 1 minute', 0, 1) WITH NOWAIT; - - WAITFOR DELAY '00:01:00.000'; + + SET @job_sql = N' + + EXEC msdb.dbo.sp_add_job @job_name = ' + @job_name_backups + ', + @description = ' + @job_description_backups + ', + @category_name = ' + @job_category + ', + @owner_login_name = ' + @job_owner + ','; + IF @EnableBackupJobs = 1 + BEGIN + SET @job_sql = @job_sql + ' @enabled = 1; '; + END + ELSE + BEGIN + SET @job_sql = @job_sql + ' @enabled = 0; '; + END + + + SET @job_sql = @job_sql + ' + EXEC msdb.dbo.sp_add_jobstep @job_name = ' + @job_name_backups + ', + @step_name = ' + @job_name_backups + ', + @subsystem = ''TSQL'', + @command = ' + @job_command_backups + '; + + + EXEC msdb.dbo.sp_add_jobserver @job_name = ' + @job_name_backups + '; + + + EXEC msdb.dbo.sp_attach_schedule @job_name = ' + @job_name_backups + ', + @schedule_name = ten_seconds; + + '; + + + SET @counter += 1; - END TRY + + IF @Debug = 1 + BEGIN + RAISERROR(@job_sql, 0, 1) WITH NOWAIT; + END; - BEGIN CATCH + + IF @job_sql IS NULL + BEGIN + RAISERROR('@job_sql is NULL for some reason', 0, 1) WITH NOWAIT; + END; - SELECT @msg = N'Error inserting databases to msdb.dbo.restore_worker, error number is ' + CONVERT(NVARCHAR(10), ERROR_NUMBER()) + ', error message is ' + ERROR_MESSAGE(), - @error_severity = ERROR_SEVERITY(), - @error_state = ERROR_STATE(); - - RAISERROR(@msg, @error_severity, @error_state) WITH NOWAIT; + EXEC sp_executesql @job_sql; - - WHILE @@TRANCOUNT > 0 - ROLLBACK; + + END; - END CATCH; - - - END; - - END;-- End Pollster loop - - ELSE - - BEGIN - - RAISERROR('msdb.dbo.restore_worker does not exist, please create it.', 0, 1) WITH NOWAIT; - RETURN; - - END; - RETURN; + /* + + This section creates @Jobs (quantity) of worker jobs to restore logs with + They too work in a queue -/* + Like a queue-t 3.14 + + */ -Begin LogShamer -*/ + RAISERROR('Checking for sp_AllNightLog Restore jobs', 0, 1) WITH NOWAIT; + + + SELECT @counter = COUNT(*) + 1 + FROM msdb.dbo.sysjobs + WHERE name LIKE 'sp[_]AllNightLog[_]Restore[_]%'; -LogShamer: + SET @msg = 'Found ' + CONVERT(NVARCHAR(10), (@counter - 1)) + ' restore jobs -- ' + CASE WHEN @counter < @Jobs THEN + 'starting loop!' + WHEN @counter >= @Jobs THEN 'skipping loop!' + ELSE 'Oh woah something weird happened!' + END; + + RAISERROR(@msg, 0, 1) WITH NOWAIT; + + + WHILE @counter <= @Jobs - IF @Debug = 1 RAISERROR('Beginning Backups', 0, 1) WITH NOWAIT; - - IF OBJECT_ID('msdbCentral.dbo.backup_worker') IS NOT NULL - - BEGIN - - /* - - Make sure configuration table exists... - - */ - - IF OBJECT_ID('msdbCentral.dbo.backup_configuration') IS NOT NULL - - BEGIN - - IF @Debug = 1 RAISERROR('Checking variables', 0, 1) WITH NOWAIT; - - /* - - These settings are configurable - - I haven't found a good way to find the default backup path that doesn't involve xp_regread - - */ - - SELECT @rpo = CONVERT(INT, configuration_setting) - FROM msdbCentral.dbo.backup_configuration c - WHERE configuration_name = N'log backup frequency' - AND database_name = N'all'; - - - IF @rpo IS NULL - BEGIN - RAISERROR('@rpo cannot be NULL. Please check the msdbCentral.dbo.backup_configuration table', 0, 1) WITH NOWAIT; - RETURN; - END; - - - SELECT @backup_path = CONVERT(NVARCHAR(512), configuration_setting) - FROM msdbCentral.dbo.backup_configuration c - WHERE configuration_name = N'log backup path' - AND database_name = N'all'; - - IF @backup_path IS NULL BEGIN - RAISERROR('@backup_path cannot be NULL. Please check the msdbCentral.dbo.backup_configuration table', 0, 1) WITH NOWAIT; - RETURN; - END; - SELECT @changebackuptype = configuration_setting - FROM msdbCentral.dbo.backup_configuration c - WHERE configuration_name = N'change backup type' - AND database_name = N'all'; + + RAISERROR('Setting job name', 0, 1) WITH NOWAIT; - SELECT @encrypt = configuration_setting - FROM msdbCentral.dbo.backup_configuration c - WHERE configuration_name = N'encrypt' - AND database_name = N'all'; + SET @job_name_restores = N'sp_AllNightLog_Restore_' + CASE WHEN @counter < 10 THEN N'0' + CONVERT(NVARCHAR(10), @counter) + WHEN @counter >= 10 THEN CONVERT(NVARCHAR(10), @counter) + END; + + + RAISERROR('Setting @job_sql', 0, 1) WITH NOWAIT; - SELECT @encryptionalgorithm = configuration_setting - FROM msdbCentral.dbo.backup_configuration c - WHERE configuration_name = N'encryptionalgorithm' - AND database_name = N'all'; + + SET @job_sql = N' + + EXEC msdb.dbo.sp_add_job @job_name = ' + @job_name_restores + ', + @description = ' + @job_description_restores + ', + @category_name = ' + @job_category + ', + @owner_login_name = ' + @job_owner + ','; + IF @EnableRestoreJobs = 1 + BEGIN + SET @job_sql = @job_sql + ' @enabled = 1; '; + END + ELSE + BEGIN + SET @job_sql = @job_sql + ' @enabled = 0; '; + END + + + SET @job_sql = @job_sql + ' + + EXEC msdb.dbo.sp_add_jobstep @job_name = ' + @job_name_restores + ', + @step_name = ' + @job_name_restores + ', + @subsystem = ''TSQL'', + @command = ' + @job_command_restores + '; + + + EXEC msdb.dbo.sp_add_jobserver @job_name = ' + @job_name_restores + '; + + + EXEC msdb.dbo.sp_attach_schedule @job_name = ' + @job_name_restores + ', + @schedule_name = ten_seconds; + + '; + + + SET @counter += 1; - SELECT @servercertificate = configuration_setting - FROM msdbCentral.dbo.backup_configuration c - WHERE configuration_name = N'servercertificate' - AND database_name = N'all'; + + IF @Debug = 1 + BEGIN + RAISERROR(@job_sql, 0, 1) WITH NOWAIT; + END; - IF @encrypt = N'Y' AND (@encryptionalgorithm IS NULL OR @servercertificate IS NULL) - BEGIN - RAISERROR('If encryption is Y, then both the encryptionalgorithm and servercertificate must be set. Please check the msdbCentral.dbo.backup_configuration table', 0, 1) WITH NOWAIT; - RETURN; - END; - - END; - - ELSE - - BEGIN - - RAISERROR('msdbCentral.dbo.backup_configuration does not exist, please run setup script', 0, 1) WITH NOWAIT; - RETURN; - - END; - - - WHILE @Backup = 1 + + IF @job_sql IS NULL + BEGIN + RAISERROR('@job_sql is NULL for some reason', 0, 1) WITH NOWAIT; + END; - /* - - Start loop to take log backups - */ + EXEC sp_executesql @job_sql; - - BEGIN - - BEGIN TRY - BEGIN TRAN; - - IF @Debug = 1 RAISERROR('Begin tran to grab a database to back up', 0, 1) WITH NOWAIT; + END; - /* - - This grabs a database for a worker to work on + RAISERROR('Setup complete!', 0, 1) WITH NOWAIT; + + END; --End for the Agent job creation - The locking hints hope to provide some isolation when 10+ workers are in action - - */ - - - SELECT TOP (1) - @database = bw.database_name - FROM msdbCentral.dbo.backup_worker bw WITH (UPDLOCK, HOLDLOCK, ROWLOCK) - WHERE - ( /*This section works on databases already part of the backup cycle*/ - bw.is_started = 0 - AND bw.is_completed = 1 - AND bw.last_log_backup_start_time < DATEADD(SECOND, (@rpo * -1), GETDATE()) - AND (bw.error_number IS NULL OR bw.error_number > 0) /* negative numbers indicate human attention required */ - AND bw.ignore_database = 0 - ) - OR - ( /*This section picks up newly added databases by Pollster*/ - bw.is_started = 0 - AND bw.is_completed = 0 - AND bw.last_log_backup_start_time = '1900-01-01 00:00:00.000' - AND bw.last_log_backup_finish_time = '9999-12-31 00:00:00.000' - AND (bw.error_number IS NULL OR bw.error_number > 0) /* negative numbers indicate human attention required */ - AND bw.ignore_database = 0 - ) - ORDER BY bw.last_log_backup_start_time ASC, bw.last_log_backup_finish_time ASC, bw.database_name ASC; - - - IF @database IS NOT NULL - BEGIN - SET @msg = N'Updating backup_worker for database ' + ISNULL(@database, 'UH OH NULL @database'); - IF @Debug = 1 RAISERROR(@msg, 0, 1) WITH NOWAIT; - - /* - - Update the worker table so other workers know a database is being backed up - - */ + END;--End for Database and Table creation - - UPDATE bw - SET bw.is_started = 1, - bw.is_completed = 0, - bw.last_log_backup_start_time = GETDATE() - FROM msdbCentral.dbo.backup_worker bw - WHERE bw.database_name = @database; - END - - COMMIT; - - END TRY - - BEGIN CATCH - - /* - - Do I need to build retry logic in here? Try to catch deadlocks? I don't know yet! - - */ + END TRY - SELECT @msg = N'Error securing a database to backup, error number is ' + CONVERT(NVARCHAR(10), ERROR_NUMBER()) + ', error message is ' + ERROR_MESSAGE(), - @error_severity = ERROR_SEVERITY(), - @error_state = ERROR_STATE(); - RAISERROR(@msg, @error_severity, @error_state) WITH NOWAIT; + BEGIN CATCH - SET @database = NULL; - - WHILE @@TRANCOUNT > 0 - ROLLBACK; - - END CATCH; + SELECT @msg = N'Error occurred during setup: ' + CONVERT(NVARCHAR(10), ERROR_NUMBER()) + ', error message is ' + ERROR_MESSAGE(), + @error_severity = ERROR_SEVERITY(), + @error_state = ERROR_STATE(); + + RAISERROR(@msg, @error_severity, @error_state) WITH NOWAIT; - /* If we don't find a database to work on, wait for a few seconds */ - IF @database IS NULL - BEGIN - IF @Debug = 1 RAISERROR('No databases to back up right now, starting 3 second throttle', 0, 1) WITH NOWAIT; - WAITFOR DELAY '00:00:03.000'; + WHILE @@TRANCOUNT > 0 + ROLLBACK; - /* Check to make sure job is still enabled */ - IF NOT EXISTS ( - SELECT * - FROM msdb.dbo.sysjobs - WHERE name LIKE 'sp_AllNightLog_Backup%' - AND enabled = 1 - ) - BEGIN - RAISERROR('sp_AllNightLog_Backup jobs are disabled, so gracefully exiting. It feels graceful to me, anyway.', 0, 1) WITH NOWAIT; - RETURN; - END + END CATCH; +END; /* IF @RunSetup = 1 */ - END - +RETURN; + + +UpdateConfigs: + +IF @UpdateSetup = 1 - BEGIN TRY + BEGIN + + /* If we're enabling backup jobs, we may need to run restore with recovery on msdbCentral to bring it online: */ + IF @EnableBackupJobs = 1 AND EXISTS (SELECT * FROM sys.databases WHERE name = 'msdbCentral' AND state = 1) + BEGIN + RAISERROR('msdbCentral exists, but is in restoring state. Running restore with recovery...', 0, 1) WITH NOWAIT; + + BEGIN TRY + RESTORE DATABASE [msdbCentral] WITH RECOVERY; + END TRY + + BEGIN CATCH + + SELECT @error_number = ERROR_NUMBER(), + @error_severity = ERROR_SEVERITY(), + @error_state = ERROR_STATE(); + + SELECT @msg = N'Error running restore with recovery on msdbCentral, error number is ' + CONVERT(NVARCHAR(10), ERROR_NUMBER()) + ', error message is ' + ERROR_MESSAGE(), + @error_severity = ERROR_SEVERITY(), + @error_state = ERROR_STATE(); - BEGIN - - IF @database IS NOT NULL + RAISERROR(@msg, @error_severity, @error_state) WITH NOWAIT; - /* - - Make sure we have a database to work on -- I should make this more robust so we do something if it is NULL, maybe - - */ + END CATCH; - - BEGIN - - SET @msg = N'Taking backup of ' + ISNULL(@database, 'UH OH NULL @database'); - IF @Debug = 1 RAISERROR(@msg, 0, 1) WITH NOWAIT; + END - /* - - Call Ola's proc to backup the database - - */ + /* Only check for this after trying to restore msdbCentral: */ + IF @EnableBackupJobs = 1 AND NOT EXISTS (SELECT * FROM sys.databases WHERE name = 'msdbCentral' AND state = 0) + BEGIN + RAISERROR('msdbCentral is not online. Repair that first, then try to enable backup jobs.', 0, 1) WITH NOWAIT; + RETURN + END - IF @encrypt = 'Y' - EXEC dbo.DatabaseBackup @Databases = @database, --Database we're working on - @BackupType = 'LOG', --Going for the LOGs - @Directory = @backup_path, --The path we need to back up to - @Verify = 'N', --We don't want to verify these, it eats into job time - @ChangeBackupType = @changebackuptype, --If we need to switch to a FULL because one hasn't been taken - @CheckSum = 'Y', --These are a good idea - @Compress = 'Y', --This is usually a good idea - @LogToTable = 'Y', --We should do this for posterity - @Encrypt = @encrypt, - @EncryptionAlgorithm = @encryptionalgorithm, - @ServerCertificate = @servercertificate; - ELSE - EXEC dbo.DatabaseBackup @Databases = @database, --Database we're working on - @BackupType = 'LOG', --Going for the LOGs - @Directory = @backup_path, --The path we need to back up to - @Verify = 'N', --We don't want to verify these, it eats into job time - @ChangeBackupType = @changebackuptype, --If we need to switch to a FULL because one hasn't been taken - @CheckSum = 'Y', --These are a good idea - @Compress = 'Y', --This is usually a good idea - @LogToTable = 'Y'; --We should do this for posterity - - - /* - - Catch any erroneous zones - - */ - - SELECT @error_number = ERROR_NUMBER(), - @error_severity = ERROR_SEVERITY(), - @error_state = ERROR_STATE(); - - END; --End call to dbo.DatabaseBackup - - END; --End successful check of @database (not NULL) - - END TRY - - BEGIN CATCH + IF OBJECT_ID('msdbCentral.dbo.backup_configuration') IS NOT NULL + + RAISERROR('Found backup config, checking variables...', 0, 1) WITH NOWAIT; - IF @error_number IS NOT NULL + BEGIN + + BEGIN TRY - /* - If the ERROR() function returns a number, update the table with it and the last error date. + IF @RPOSeconds IS NOT NULL - Also update the last start time to 1900-01-01 so it gets picked back up immediately -- the query to find a log backup to take sorts by start time - */ - BEGIN - - SET @msg = N'Error number is ' + CONVERT(NVARCHAR(10), ERROR_NUMBER()); - RAISERROR(@msg, @error_severity, @error_state) WITH NOWAIT; - - SET @msg = N'Updating backup_worker for database ' + ISNULL(@database, 'UH OH NULL @database') + ' for unsuccessful backup'; - RAISERROR(@msg, 0, 1) WITH NOWAIT; - - - UPDATE bw - SET bw.is_started = 0, - bw.is_completed = 1, - bw.last_log_backup_start_time = '19000101', - bw.error_number = @error_number, - bw.last_error_date = GETDATE() - FROM msdbCentral.dbo.backup_worker bw - WHERE bw.database_name = @database; + RAISERROR('Attempting to update RPO setting', 0, 1) WITH NOWAIT; - /* - - Set @database back to NULL to avoid variable assignment weirdness - - */ + UPDATE c + SET c.configuration_setting = CONVERT(NVARCHAR(10), @RPOSeconds) + FROM msdbCentral.dbo.backup_configuration AS c + WHERE c.configuration_name = N'log backup frequency'; - SET @database = NULL; + END; - - /* - - Wait around for a second so we're not just spinning wheels -- this only runs if the BEGIN CATCH is triggered by an error + + IF @BackupPath IS NOT NULL - */ - - IF @Debug = 1 RAISERROR('Starting 1 second throttle', 0, 1) WITH NOWAIT; - - WAITFOR DELAY '00:00:01.000'; + BEGIN + + RAISERROR('Attempting to update Backup Path setting', 0, 1) WITH NOWAIT; - END; -- End update of unsuccessful backup - - END CATCH; - - IF @database IS NOT NULL AND @error_number IS NULL - - /* - - If no error, update everything normally - - */ - - - BEGIN - - IF @Debug = 1 RAISERROR('Error number IS NULL', 0, 1) WITH NOWAIT; - - SET @msg = N'Updating backup_worker for database ' + ISNULL(@database, 'UH OH NULL @database') + ' for successful backup'; - IF @Debug = 1 RAISERROR(@msg, 0, 1) WITH NOWAIT; - - - UPDATE bw - SET bw.is_started = 0, - bw.is_completed = 1, - bw.last_log_backup_finish_time = GETDATE() - FROM msdbCentral.dbo.backup_worker bw - WHERE bw.database_name = @database; - - - /* - - Set @database back to NULL to avoid variable assignment weirdness - - */ + UPDATE c + SET c.configuration_setting = @BackupPath + FROM msdbCentral.dbo.backup_configuration AS c + WHERE c.configuration_name = N'log backup path'; - SET @database = NULL; + END; - END; -- End update for successful backup + END TRY - - END; -- End @Backup WHILE loop - - END; -- End successful check for backup_worker and subsequent code + BEGIN CATCH - - ELSE - - BEGIN - - RAISERROR('msdbCentral.dbo.backup_worker does not exist, please run setup script', 0, 1) WITH NOWAIT; - - RETURN; - - END; -RETURN; + SELECT @error_number = ERROR_NUMBER(), + @error_severity = ERROR_SEVERITY(), + @error_state = ERROR_STATE(); -/* + SELECT @msg = N'Error updating backup configuration setting, error number is ' + CONVERT(NVARCHAR(10), ERROR_NUMBER()) + ', error message is ' + ERROR_MESSAGE(), + @error_severity = ERROR_SEVERITY(), + @error_state = ERROR_STATE(); + + RAISERROR(@msg, @error_severity, @error_state) WITH NOWAIT; -Begin Restoregasm_Addict section -*/ + END CATCH; -Restoregasm_Addict: + END; -IF @Restore = 1 - IF @Debug = 1 RAISERROR('Beginning Restores', 0, 1) WITH NOWAIT; - - /* Check to make sure backup jobs aren't enabled */ - IF EXISTS ( - SELECT * - FROM msdb.dbo.sysjobs - WHERE name LIKE 'sp_AllNightLog_Backup%' - AND enabled = 1 - ) - BEGIN - RAISERROR('sp_AllNightLog_Backup jobs are enabled, so gracefully exiting. You do not want to accidentally do restores over top of the databases you are backing up.', 0, 1) WITH NOWAIT; - RETURN; - END - IF OBJECT_ID('msdb.dbo.restore_worker') IS NOT NULL - - BEGIN - - /* - - Make sure configuration table exists... - - */ - IF OBJECT_ID('msdb.dbo.restore_configuration') IS NOT NULL - - BEGIN - - IF @Debug = 1 RAISERROR('Checking variables', 0, 1) WITH NOWAIT; - - /* - - These settings are configurable - - */ - - SELECT @rto = CONVERT(INT, configuration_setting) - FROM msdb.dbo.restore_configuration c - WHERE configuration_name = N'log restore frequency'; - - - IF @rto IS NULL - BEGIN - RAISERROR('@rto cannot be NULL. Please check the msdb.dbo.restore_configuration table', 0, 1) WITH NOWAIT; - RETURN; - END; - - - END; - - ELSE - - BEGIN - - RAISERROR('msdb.dbo.restore_configuration does not exist, please run setup script', 0, 1) WITH NOWAIT; - - RETURN; - - END; - - - WHILE @Restore = 1 - - /* - - Start loop to restore log backups - */ + RAISERROR('Found restore config, checking variables...', 0, 1) WITH NOWAIT; - BEGIN - - BEGIN TRY - - BEGIN TRAN; - - IF @Debug = 1 RAISERROR('Begin tran to grab a database to restore', 0, 1) WITH NOWAIT; - - - /* - - This grabs a database for a worker to work on - - The locking hints hope to provide some isolation when 10+ workers are in action - - */ - - - SELECT TOP (1) - @database = rw.database_name, - @only_logs_after = REPLACE(REPLACE(REPLACE(CONVERT(NVARCHAR(30), rw.last_log_restore_start_time, 120), ' ', ''), '-', ''), ':', ''), - @restore_full = CASE WHEN rw.is_started = 0 - AND rw.is_completed = 0 - AND rw.last_log_restore_start_time = '1900-01-01 00:00:00.000' - AND rw.last_log_restore_finish_time = '9999-12-31 00:00:00.000' - THEN 1 - ELSE 0 - END - FROM msdb.dbo.restore_worker rw WITH (UPDLOCK, HOLDLOCK, ROWLOCK) - WHERE ( - ( /*This section works on databases already part of the backup cycle*/ - rw.is_started = 0 - AND rw.is_completed = 1 - AND rw.last_log_restore_start_time < DATEADD(SECOND, (@rto * -1), GETDATE()) - AND (rw.error_number IS NULL OR rw.error_number > 0) /* negative numbers indicate human attention required */ - ) - OR - ( /*This section picks up newly added databases by DiskPollster*/ - rw.is_started = 0 - AND rw.is_completed = 0 - AND rw.last_log_restore_start_time = '1900-01-01 00:00:00.000' - AND rw.last_log_restore_finish_time = '9999-12-31 00:00:00.000' - AND (rw.error_number IS NULL OR rw.error_number > 0) /* negative numbers indicate human attention required */ - ) - ) - AND rw.ignore_database = 0 - AND NOT EXISTS ( - /* Validation check to ensure the database either doesn't exist or is in a restoring/standby state */ - SELECT 1 - FROM sys.databases d - WHERE d.name = rw.database_name - AND state <> 1 /* Restoring */ - AND NOT (state=0 AND d.is_in_standby=1) /* standby mode */ - ) - ORDER BY rw.last_log_restore_start_time ASC, rw.last_log_restore_finish_time ASC, rw.database_name ASC; - - - IF @database IS NOT NULL - BEGIN - SET @msg = N'Updating restore_worker for database ' + ISNULL(@database, 'UH OH NULL @database'); - IF @Debug = 1 RAISERROR(@msg, 0, 1) WITH NOWAIT; - - /* - - Update the worker table so other workers know a database is being restored - - */ - - UPDATE rw - SET rw.is_started = 1, - rw.is_completed = 0, - rw.last_log_restore_start_time = GETDATE() - FROM msdb.dbo.restore_worker rw - WHERE rw.database_name = @database; - END - - COMMIT; - - END TRY - - BEGIN CATCH - - /* - - Do I need to build retry logic in here? Try to catch deadlocks? I don't know yet! - - */ + BEGIN TRY - SELECT @msg = N'Error securing a database to restore, error number is ' + CONVERT(NVARCHAR(10), ERROR_NUMBER()) + ', error message is ' + ERROR_MESSAGE(), - @error_severity = ERROR_SEVERITY(), - @error_state = ERROR_STATE(); - RAISERROR(@msg, @error_severity, @error_state) WITH NOWAIT; + EXEC msdb.dbo.sp_update_schedule @name = ten_seconds, @active_start_date = @active_start_date, @active_start_time = 000000; - SET @database = NULL; - - WHILE @@TRANCOUNT > 0 - ROLLBACK; - - END CATCH; + IF @EnableRestoreJobs IS NOT NULL + BEGIN + RAISERROR('Changing restore job status based on @EnableBackupJobs parameter...', 0, 1) WITH NOWAIT; + INSERT INTO @jobs_to_change(name) + SELECT name + FROM msdb.dbo.sysjobs + WHERE name LIKE 'sp_AllNightLog_Restore%' OR name = 'sp_AllNightLog_PollDiskForNewDatabases'; + DECLARE jobs_cursor CURSOR FOR + SELECT name + FROM @jobs_to_change + OPEN jobs_cursor + FETCH NEXT FROM jobs_cursor INTO @current_job_name - /* If we don't find a database to work on, wait for a few seconds */ - IF @database IS NULL + WHILE @@FETCH_STATUS = 0 + BEGIN + RAISERROR(@current_job_name, 0, 1) WITH NOWAIT; + EXEC msdb.dbo.sp_update_job @job_name=@current_job_name,@enabled = @EnableRestoreJobs; + FETCH NEXT FROM jobs_cursor INTO @current_job_name + END - BEGIN - IF @Debug = 1 RAISERROR('No databases to restore up right now, starting 3 second throttle', 0, 1) WITH NOWAIT; - WAITFOR DELAY '00:00:03.000'; + CLOSE jobs_cursor + DEALLOCATE jobs_cursor + DELETE @jobs_to_change; + END; - /* Check to make sure backup jobs aren't enabled */ - IF EXISTS ( - SELECT * - FROM msdb.dbo.sysjobs - WHERE name LIKE 'sp_AllNightLog_Backup%' - AND enabled = 1 - ) - BEGIN - RAISERROR('sp_AllNightLog_Backup jobs are enabled, so gracefully exiting. You do not want to accidentally do restores over top of the databases you are backing up.', 0, 1) WITH NOWAIT; - RETURN; - END + /* If they wanted to turn off restore jobs, wait to make sure that finishes before we start enabling the backup jobs */ + IF @EnableRestoreJobs = 0 + BEGIN + SET @started_waiting_for_jobs = GETDATE(); + SELECT @counter = COUNT(*) + FROM [msdb].[dbo].[sysjobactivity] [ja] + INNER JOIN [msdb].[dbo].[sysjobs] [j] + ON [ja].[job_id] = [j].[job_id] + WHERE [ja].[session_id] = ( + SELECT TOP 1 [session_id] + FROM [msdb].[dbo].[syssessions] + ORDER BY [agent_start_date] DESC + ) + AND [start_execution_date] IS NOT NULL + AND [stop_execution_date] IS NULL + AND [j].[name] LIKE 'sp_AllNightLog_Restore%'; - /* Check to make sure job is still enabled */ - IF NOT EXISTS ( - SELECT * - FROM msdb.dbo.sysjobs - WHERE name LIKE 'sp_AllNightLog_Restore%' - AND enabled = 1 - ) + WHILE @counter > 0 BEGIN - RAISERROR('sp_AllNightLog_Restore jobs are disabled, so gracefully exiting. It feels graceful to me, anyway.', 0, 1) WITH NOWAIT; - RETURN; - END - - END - - - BEGIN TRY - - BEGIN + IF DATEADD(SS, 120, @started_waiting_for_jobs) < GETDATE() + BEGIN + RAISERROR('OH NOES! We waited 2 minutes and restore jobs are still running. We are stopping here - get a meatbag involved to figure out if restore jobs need to be killed, and the backup jobs will need to be enabled manually.', 16, 1) WITH NOWAIT; + RETURN + END + SET @msg = N'Waiting for ' + CAST(@counter AS NVARCHAR(100)) + N' sp_AllNightLog_Restore job(s) to finish.' + RAISERROR(@msg, 0, 1) WITH NOWAIT; + WAITFOR DELAY '0:00:01'; -- Wait until the restore jobs are fully stopped - IF @database IS NOT NULL + SELECT @counter = COUNT(*) + FROM [msdb].[dbo].[sysjobactivity] [ja] + INNER JOIN [msdb].[dbo].[sysjobs] [j] + ON [ja].[job_id] = [j].[job_id] + WHERE [ja].[session_id] = ( + SELECT TOP 1 [session_id] + FROM [msdb].[dbo].[syssessions] + ORDER BY [agent_start_date] DESC + ) + AND [start_execution_date] IS NOT NULL + AND [stop_execution_date] IS NULL + AND [j].[name] LIKE 'sp_AllNightLog_Restore%'; + END + END /* IF @EnableRestoreJobs = 0 */ - /* - - Make sure we have a database to work on -- I should make this more robust so we do something if it is NULL, maybe - - */ - - BEGIN - - SET @msg = CASE WHEN @restore_full = 0 - THEN N'Restoring logs for ' - ELSE N'Restoring full backup for ' - END - + ISNULL(@database, 'UH OH NULL @database'); + IF @EnableBackupJobs IS NOT NULL + BEGIN + RAISERROR('Changing backup job status based on @EnableBackupJobs parameter...', 0, 1) WITH NOWAIT; + INSERT INTO @jobs_to_change(name) + SELECT name + FROM msdb.dbo.sysjobs + WHERE name LIKE 'sp_AllNightLog_Backup%' OR name = 'sp_AllNightLog_PollForNewDatabases'; + DECLARE jobs_cursor CURSOR FOR + SELECT name + FROM @jobs_to_change - IF @Debug = 1 RAISERROR(@msg, 0, 1) WITH NOWAIT; + OPEN jobs_cursor + FETCH NEXT FROM jobs_cursor INTO @current_job_name - /* - - Call sp_DatabaseRestore to backup the database - - */ + WHILE @@FETCH_STATUS = 0 + BEGIN + RAISERROR(@current_job_name, 0, 1) WITH NOWAIT; + EXEC msdb.dbo.sp_update_job @job_name=@current_job_name,@enabled = @EnableBackupJobs; + FETCH NEXT FROM jobs_cursor INTO @current_job_name + END - SET @restore_path_full = @restore_path_base + N'\' + @database + N'\' + N'FULL\' - - SET @msg = N'Path for FULL backups for ' + @database + N' is ' + @restore_path_full - IF @Debug = 1 RAISERROR(@msg, 0, 1) WITH NOWAIT; + CLOSE jobs_cursor + DEALLOCATE jobs_cursor + DELETE @jobs_to_change; + END; - SET @restore_path_log = @restore_path_base + N'\' + @database + N'\' + N'LOG\' - SET @msg = N'Path for LOG backups for ' + @database + N' is ' + @restore_path_log - IF @Debug = 1 RAISERROR(@msg, 0, 1) WITH NOWAIT; + + IF @RTOSeconds IS NOT NULL - IF @restore_full = 0 + BEGIN - BEGIN + RAISERROR('Attempting to update RTO setting', 0, 1) WITH NOWAIT; - IF @Debug = 1 RAISERROR('Starting Log only restores', 0, 1) WITH NOWAIT; + UPDATE c + SET c.configuration_setting = CONVERT(NVARCHAR(10), @RTOSeconds) + FROM msdb.dbo.restore_configuration AS c + WHERE c.configuration_name = N'log restore frequency'; - EXEC dbo.sp_DatabaseRestore @Database = @database, - @BackupPathFull = @restore_path_full, - @BackupPathLog = @restore_path_log, - @ContinueLogs = 1, - @RunRecovery = 0, - @OnlyLogsAfter = @only_logs_after, - @MoveFiles = @restore_move_files, - @Debug = @Debug - - END + END; - IF @restore_full = 1 + + IF @RestorePath IS NOT NULL - BEGIN + BEGIN + + RAISERROR('Attempting to update Restore Path setting', 0, 1) WITH NOWAIT; - IF @Debug = 1 RAISERROR('Starting first Full restore from: ', 0, 1) WITH NOWAIT; - IF @Debug = 1 RAISERROR(@restore_path_full, 0, 1) WITH NOWAIT; + UPDATE c + SET c.configuration_setting = @RestorePath + FROM msdb.dbo.restore_configuration AS c + WHERE c.configuration_name = N'log restore path'; - EXEC dbo.sp_DatabaseRestore @Database = @database, - @BackupPathFull = @restore_path_full, - @BackupPathLog = @restore_path_log, - @ContinueLogs = 0, - @RunRecovery = 0, - @MoveFiles = @restore_move_files, - @Debug = @Debug - - END + END; - - /* - - Catch any erroneous zones - - */ - - SELECT @error_number = ERROR_NUMBER(), - @error_severity = ERROR_SEVERITY(), - @error_state = ERROR_STATE(); - - END; --End call to dbo.sp_DatabaseRestore - - END; --End successful check of @database (not NULL) - END TRY - - BEGIN CATCH - - IF @error_number IS NOT NULL - /* - - If the ERROR() function returns a number, update the table with it and the last error date. - Also update the last start time to 1900-01-01 so it gets picked back up immediately -- the query to find a log restore to take sorts by start time + BEGIN CATCH - */ - - BEGIN - - SET @msg = N'Error number is ' + CONVERT(NVARCHAR(10), ERROR_NUMBER()); - RAISERROR(@msg, @error_severity, @error_state) WITH NOWAIT; - - SET @msg = N'Updating restore_worker for database ' + ISNULL(@database, 'UH OH NULL @database') + ' for unsuccessful backup'; - RAISERROR(@msg, 0, 1) WITH NOWAIT; - - - UPDATE rw - SET rw.is_started = 0, - rw.is_completed = 1, - rw.last_log_restore_start_time = '19000101', - rw.error_number = @error_number, - rw.last_error_date = GETDATE() - FROM msdb.dbo.restore_worker rw - WHERE rw.database_name = @database; + SELECT @error_number = ERROR_NUMBER(), + @error_severity = ERROR_SEVERITY(), + @error_state = ERROR_STATE(); - /* - - Set @database back to NULL to avoid variable assignment weirdness - - */ + SELECT @msg = N'Error updating restore configuration setting, error number is ' + CONVERT(NVARCHAR(10), ERROR_NUMBER()) + ', error message is ' + ERROR_MESSAGE(), + @error_severity = ERROR_SEVERITY(), + @error_state = ERROR_STATE(); + + RAISERROR(@msg, @error_severity, @error_state) WITH NOWAIT; - SET @database = NULL; - - /* - - Wait around for a second so we're not just spinning wheels -- this only runs if the BEGIN CATCH is triggered by an error + END CATCH; - */ - - IF @Debug = 1 RAISERROR('Starting 1 second throttle', 0, 1) WITH NOWAIT; - - WAITFOR DELAY '00:00:01.000'; + END; - END; -- End update of unsuccessful restore - - END CATCH; + RAISERROR('Update complete!', 0, 1) WITH NOWAIT; + RETURN; - IF @database IS NOT NULL AND @error_number IS NULL + END; --End updates to configuration table - /* - - If no error, update everything normally - - */ - - BEGIN - - IF @Debug = 1 RAISERROR('Error number IS NULL', 0, 1) WITH NOWAIT; +END; -- Final END for stored proc +GO - /* Make sure database actually exists and is in the restoring state */ - IF EXISTS (SELECT * FROM sys.databases WHERE name = @database AND state = 1) /* Restoring */ - BEGIN - SET @msg = N'Updating backup_worker for database ' + ISNULL(@database, 'UH OH NULL @database') + ' for successful backup'; - IF @Debug = 1 RAISERROR(@msg, 0, 1) WITH NOWAIT; - - UPDATE rw - SET rw.is_started = 0, - rw.is_completed = 1, - rw.last_log_restore_finish_time = GETDATE() - FROM msdb.dbo.restore_worker rw - WHERE rw.database_name = @database; - - END - ELSE /* The database doesn't exist, or it's not in the restoring state */ - BEGIN - SET @msg = N'Updating backup_worker for database ' + ISNULL(@database, 'UH OH NULL @database') + ' for UNsuccessful backup'; - IF @Debug = 1 RAISERROR(@msg, 0, 1) WITH NOWAIT; - - UPDATE rw - SET rw.is_started = 0, - rw.is_completed = 1, - rw.error_number = -1, /* unknown, human attention required */ - rw.last_error_date = GETDATE() - /* rw.last_log_restore_finish_time = GETDATE() don't change this - the last log may still be successful */ - FROM msdb.dbo.restore_worker rw - WHERE rw.database_name = @database; - END - - - - /* - - Set @database back to NULL to avoid variable assignment weirdness - - */ - - SET @database = NULL; - - - END; -- End update for successful backup - - END; -- End @Restore WHILE loop - - - END; -- End successful check for restore_worker and subsequent code - - - ELSE - - BEGIN - - RAISERROR('msdb.dbo.restore_worker does not exist, please run setup script', 0, 1) WITH NOWAIT; - - RETURN; - - END; -RETURN; - - - -END; -- Final END for stored proc - -GO SET ANSI_NULLS ON; SET ANSI_PADDING ON; SET ANSI_WARNINGS ON; @@ -1526,29 +1352,21 @@ SET STATISTICS IO OFF; SET STATISTICS TIME OFF; GO -IF OBJECT_ID('dbo.sp_AllNightLog_Setup') IS NULL - EXEC ('CREATE PROCEDURE dbo.sp_AllNightLog_Setup AS RETURN 0;'); +IF OBJECT_ID('dbo.sp_AllNightLog') IS NULL + EXEC ('CREATE PROCEDURE dbo.sp_AllNightLog AS RETURN 0;') GO -ALTER PROCEDURE dbo.sp_AllNightLog_Setup - @RPOSeconds BIGINT = 30, - @RTOSeconds BIGINT = 30, - @BackupPath NVARCHAR(MAX) = NULL, - @RestorePath NVARCHAR(MAX) = NULL, - @Jobs TINYINT = 10, - @RunSetup BIT = 0, - @UpdateSetup BIT = 0, - @EnableBackupJobs INT = NULL, - @EnableRestoreJobs INT = NULL, - @Debug BIT = 0, - @FirstFullBackup BIT = 0, - @FirstDiffBackup BIT = 0, - @MoveFiles BIT = 1, - @Help BIT = 0, - @Version VARCHAR(30) = NULL OUTPUT, - @VersionDate DATETIME = NULL OUTPUT, - @VersionCheckMode BIT = 0 +ALTER PROCEDURE dbo.sp_AllNightLog + @PollForNewDatabases BIT = 0, /* Formerly Pollster */ + @Backup BIT = 0, /* Formerly LogShaming */ + @PollDiskForNewDatabases BIT = 0, + @Restore BIT = 0, + @Debug BIT = 0, + @Help BIT = 0, + @Version VARCHAR(30) = NULL OUTPUT, + @VersionDate DATETIME = NULL OUTPUT, + @VersionCheckMode BIT = 0 WITH RECOMPILE AS SET NOCOUNT ON; @@ -1556,7 +1374,8 @@ SET STATISTICS XML OFF; BEGIN; -SELECT @Version = '8.12', @VersionDate = '20221213'; + +SELECT @Version = '8.13', @VersionDate = '20230215'; IF(@VersionCheckMode = 1) BEGIN @@ -1571,35 +1390,15 @@ BEGIN /* - sp_AllNightLog_Setup from http://FirstResponderKit.org + sp_AllNightLog from http://FirstResponderKit.org - This script sets up a database, tables, rows, and jobs for sp_AllNightLog, including: - - * Creates a database - * Right now it''s hard-coded to use msdbCentral, that might change later + * @PollForNewDatabases = 1 polls sys.databases for new entries + * Unfortunately no other way currently to automate new database additions when restored from backups + * No triggers or extended events that easily do this - * Creates tables in that database! - * dbo.backup_configuration - * Hold variables used by stored proc to make runtime decisions - * RPO: Seconds, how often we look for databases that need log backups - * Backup Path: The path we feed to Ola H''s backup proc - * dbo.backup_worker - * Holds list of databases and some information that helps our Agent jobs figure out if they need to take another log backup - - * Creates tables in msdb - * dbo.restore_configuration - * Holds variables used by stored proc to make runtime decisions - * RTO: Seconds, how often to look for log backups to restore - * Restore Path: The path we feed to sp_DatabaseRestore - * Move Files: Whether to move files to default data/log directories. - * dbo.restore_worker - * Holds list of databases and some information that helps our Agent jobs figure out if they need to look for files to restore + * @Backup = 1 polls msdbCentral.dbo.backup_worker for databases not backed up in [RPO], takes LOG backups + * Will switch to a full backup if none exists - * Creates agent jobs - * 1 job that polls sys.databases for new entries - * 10 jobs that run to take log backups - * Based on a queue table - * Requires Ola Hallengren''s Database Backup stored proc To learn more, visit http://FirstResponderKit.org where you can download new versions for free, watch training videos on how it works, get more info on @@ -1607,7 +1406,7 @@ BEGIN Known limitations of this version: - Only Microsoft-supported versions of SQL Server. Sorry, 2005 and 2000! And really, maybe not even anything less than 2016. Heh. - - The repository database name is hard-coded to msdbCentral. + - When restoring encrypted backups, the encryption certificate must already be installed. Unknown limitations of this version: - None. (If we knew them, they would be known. Duh.) @@ -1618,26 +1417,17 @@ BEGIN Parameter explanations: - @RunSetup BIT, defaults to 0. When this is set to 1, it will run the setup portion to create database, tables, and worker jobs. - @UpdateSetup BIT, defaults to 0. When set to 1, will update existing configs for RPO/RTO and database backup/restore paths. - @RPOSeconds BIGINT, defaults to 30. Value in seconds you want to use to determine if a new log backup needs to be taken. - @BackupPath NVARCHAR(MAX), This is REQUIRED if @Runsetup=1. This tells Ola''s job where to put backups. - @MoveFiles BIT, defaults to 1. When this is set to 1, it will move files to default data/log directories + @PollForNewDatabases BIT, defaults to 0. When this is set to 1, runs in a perma-loop to find new entries in sys.databases + @Backup BIT, defaults to 0. When this is set to 1, runs in a perma-loop checking the backup_worker table for databases that need to be backed up @Debug BIT, defaults to 0. Whent this is set to 1, it prints out dynamic SQL commands + @RPOSeconds BIGINT, defaults to 30. Value in seconds you want to use to determine if a new log backup needs to be taken. + @BackupPath NVARCHAR(MAX), defaults to = ''D:\Backup''. You 99.99999% will need to change this path to something else. This tells Ola''s job where to put backups. - Sample call: - EXEC dbo.sp_AllNightLog_Setup - @RunSetup = 1, - @RPOSeconds = 30, - @BackupPath = N''M:\MSSQL\Backup'', - @Debug = 1 - - For more documentation: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/ MIT License - Copyright (c) 2021 Brent Ozar Unlimited + Copyright (c) Brent Ozar Unlimited Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -1660,8 +1450,8 @@ BEGIN */'; -RETURN; -END; /* IF @Help = 1 */ +RETURN +END DECLARE @database NVARCHAR(128) = NULL; --Holds the database that's currently being processed DECLARE @error_number INT = NULL; --Used for TRY/CATCH @@ -1669,110 +1459,45 @@ DECLARE @error_severity INT; --Used for TRY/CATCH DECLARE @error_state INT; --Used for TRY/CATCH DECLARE @msg NVARCHAR(4000) = N''; --Used for RAISERROR DECLARE @rpo INT; --Used to hold the RPO value in our configuration table +DECLARE @rto INT; --Used to hold the RPO value in our configuration table DECLARE @backup_path NVARCHAR(MAX); --Used to hold the backup path in our configuration table +DECLARE @changebackuptype NVARCHAR(MAX); --Config table: Y = escalate to full backup, MSDB = escalate if MSDB history doesn't show a recent full. +DECLARE @encrypt NVARCHAR(MAX); --Config table: Y = encrypt the backup. N (default) = do not encrypt. +DECLARE @encryptionalgorithm NVARCHAR(MAX); --Config table: native 2014 choices include TRIPLE_DES_3KEY, AES_128, AES_192, AES_256 +DECLARE @servercertificate NVARCHAR(MAX); --Config table: server certificate that is used to encrypt the backup +DECLARE @restore_path_base NVARCHAR(MAX); --Used to hold the base backup path in our configuration table +DECLARE @restore_path_full NVARCHAR(MAX); --Used to hold the full backup path in our configuration table +DECLARE @restore_path_log NVARCHAR(MAX); --Used to hold the log backup path in our configuration table +DECLARE @restore_move_files INT; -- used to hold the move files bit in our configuration table DECLARE @db_sql NVARCHAR(MAX) = N''; --Used to hold the dynamic SQL to create msdbCentral DECLARE @tbl_sql NVARCHAR(MAX) = N''; --Used to hold the dynamic SQL that creates tables in msdbCentral DECLARE @database_name NVARCHAR(256) = N'msdbCentral'; --Used to hold the name of the database we create to centralize data --Right now it's hardcoded to msdbCentral, but I made it dynamic in case that changes down the line +DECLARE @cmd NVARCHAR(4000) = N'' --Holds dir cmd +DECLARE @FileList TABLE ( BackupFile NVARCHAR(255) ); --Where we dump @cmd +DECLARE @restore_full BIT = 0 --We use this one +DECLARE @only_logs_after NVARCHAR(30) = N'' -/*These variables control the loop to create/modify jobs*/ -DECLARE @job_sql NVARCHAR(MAX) = N''; --Used to hold the dynamic SQL that creates Agent jobs -DECLARE @counter INT = 0; --For looping to create 10 Agent jobs -DECLARE @job_category NVARCHAR(MAX) = N'''Database Maintenance'''; --Job category -DECLARE @job_owner NVARCHAR(128) = QUOTENAME(SUSER_SNAME(0x01), ''''); -- Admin user/owner -DECLARE @jobs_to_change TABLE(name SYSNAME); -- list of jobs we need to enable or disable -DECLARE @current_job_name SYSNAME; -- While looping through Agent jobs to enable or disable -DECLARE @active_start_date INT = (CONVERT(INT, CONVERT(VARCHAR(10), GETDATE(), 112))); -DECLARE @started_waiting_for_jobs DATETIME; --We need to wait for a while when disabling jobs - -/*Specifically for Backups*/ -DECLARE @job_name_backups NVARCHAR(MAX) = N'''sp_AllNightLog_Backup_Job_'''; --Name of log backup job -DECLARE @job_description_backups NVARCHAR(MAX) = N'''This is a worker for the purposes of taking log backups from msdbCentral.dbo.backup_worker queue table.'''; --Job description -DECLARE @job_command_backups NVARCHAR(MAX) = N'''EXEC sp_AllNightLog @Backup = 1'''; --Command the Agent job will run - -/*Specifically for Restores*/ -DECLARE @job_name_restores NVARCHAR(MAX) = N'''sp_AllNightLog_Restore_Job_'''; --Name of log backup job -DECLARE @job_description_restores NVARCHAR(MAX) = N'''This is a worker for the purposes of restoring log backups from msdb.dbo.restore_worker queue table.'''; --Job description -DECLARE @job_command_restores NVARCHAR(MAX) = N'''EXEC sp_AllNightLog @Restore = 1'''; --Command the Agent job will run - - -/* - -Sanity check some variables - -*/ - - - -IF ((@RunSetup = 0 OR @RunSetup IS NULL) AND (@UpdateSetup = 0 OR @UpdateSetup IS NULL)) - - BEGIN - - RAISERROR('You have to either run setup or update setup. You can''t not do neither nor, if you follow. Or not.', 0, 1) WITH NOWAIT; - - RETURN; - - END; - - -/* - -Should be a positive number - -*/ - -IF (@RPOSeconds < 0) - - BEGIN - RAISERROR('Please choose a positive number for @RPOSeconds', 0, 1) WITH NOWAIT; - - RETURN; - END; - - -/* - -Probably shouldn't be more than 20 - -*/ - -IF (@Jobs > 20) OR (@Jobs < 1) - - BEGIN - RAISERROR('We advise sticking with 1-20 jobs.', 0, 1) WITH NOWAIT; - - RETURN; - END; - -/* - -Probably shouldn't be more than 4 hours - -*/ - -IF (@RPOSeconds >= 14400) - BEGIN - - RAISERROR('If your RPO is really 4 hours, perhaps you''d be interested in a more modest recovery model, like SIMPLE?', 0, 1) WITH NOWAIT; - - RETURN; - END; - /* -Can't enable both the backup and restore jobs at the same time +Make sure we're doing something */ -IF @EnableBackupJobs = 1 AND @EnableRestoreJobs = 1 - BEGIN - - RAISERROR('You are not allowed to enable both the backup and restore jobs at the same time. Pick one, bucko.', 0, 1) WITH NOWAIT; - +IF ( + @PollForNewDatabases = 0 + AND @PollDiskForNewDatabases = 0 + AND @Backup = 0 + AND @Restore = 0 + AND @Help = 0 +) + BEGIN + RAISERROR('You don''t seem to have picked an action for this stored procedure to take.', 0, 1) WITH NOWAIT + RETURN; - END; + END /* Make sure xp_cmdshell is enabled @@ -1811,1055 +1536,1330 @@ IF NOT EXISTS (SELECT * FROM sys.procedures WHERE name = 'sp_DatabaseRestore') RETURN; END -/* -Basic path sanity checks +IF (@PollDiskForNewDatabases = 1 OR @Restore = 1) AND OBJECT_ID('msdb.dbo.restore_configuration') IS NOT NULL + BEGIN -*/ + IF @Debug = 1 RAISERROR('Checking restore path', 0, 1) WITH NOWAIT; -IF @RunSetup = 1 and @BackupPath is NULL - BEGIN - - RAISERROR('@BackupPath is required during setup', 0, 1) WITH NOWAIT; - - RETURN; - END + SELECT @restore_path_base = CONVERT(NVARCHAR(512), configuration_setting) + FROM msdb.dbo.restore_configuration c + WHERE configuration_name = N'log restore path'; -IF (@BackupPath NOT LIKE '[c-zC-Z]:\%') --Local path, don't think anyone has A or B drives -AND (@BackupPath NOT LIKE '\\[a-zA-Z0-9]%\%') --UNC path - - BEGIN - RAISERROR('Are you sure that''s a real path?', 0, 1) WITH NOWAIT; - + + IF @restore_path_base IS NULL + BEGIN + RAISERROR('@restore_path cannot be NULL. Please check the msdb.dbo.restore_configuration table', 0, 1) WITH NOWAIT; RETURN; - END; + END; -/* + IF CHARINDEX('**', @restore_path_base) <> 0 + BEGIN -If you want to update the table, one of these has to not be NULL + /* If they passed in a dynamic **DATABASENAME**, stop at that folder looking for databases. More info: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/993 */ + IF CHARINDEX('**DATABASENAME**', @restore_path_base) <> 0 + BEGIN + SET @restore_path_base = SUBSTRING(@restore_path_base, 1, CHARINDEX('**DATABASENAME**',@restore_path_base) - 2); + END; -*/ + SET @restore_path_base = REPLACE(@restore_path_base, '**AVAILABILITYGROUP**', ''); + SET @restore_path_base = REPLACE(@restore_path_base, '**BACKUPTYPE**', 'FULL'); + SET @restore_path_base = REPLACE(@restore_path_base, '**SERVERNAME**', REPLACE(CAST(SERVERPROPERTY('servername') AS nvarchar(max)),'\','$')); -IF @UpdateSetup = 1 - AND ( @RPOSeconds IS NULL - AND @BackupPath IS NULL - AND @RPOSeconds IS NULL - AND @RestorePath IS NULL - AND @EnableBackupJobs IS NULL - AND @EnableRestoreJobs IS NULL - ) + IF CHARINDEX('\',CAST(SERVERPROPERTY('servername') AS nvarchar(max))) > 0 + BEGIN + SET @restore_path_base = REPLACE(@restore_path_base, '**SERVERNAMEWITHOUTINSTANCE**', SUBSTRING(CAST(SERVERPROPERTY('servername') AS nvarchar(max)), 1, (CHARINDEX('\',CAST(SERVERPROPERTY('servername') AS nvarchar(max))) - 1))); + SET @restore_path_base = REPLACE(@restore_path_base, '**INSTANCENAME**', SUBSTRING(CAST(SERVERPROPERTY('servername') AS nvarchar(max)), CHARINDEX('\',CAST(SERVERPROPERTY('servername') AS nvarchar(max))), (LEN(CAST(SERVERPROPERTY('servername') AS nvarchar(max))) - CHARINDEX('\',CAST(SERVERPROPERTY('servername') AS nvarchar(max)))) + 1)); + END + ELSE /* No instance installed */ + BEGIN + SET @restore_path_base = REPLACE(@restore_path_base, '**SERVERNAMEWITHOUTINSTANCE**', CAST(SERVERPROPERTY('servername') AS nvarchar(max))); + SET @restore_path_base = REPLACE(@restore_path_base, '**INSTANCENAME**', 'DEFAULT'); + END + + IF CHARINDEX('**CLUSTER**', @restore_path_base) <> 0 + BEGIN + DECLARE @ClusterName NVARCHAR(128); + IF EXISTS(SELECT * FROM sys.all_objects WHERE name = 'dm_hadr_cluster') + BEGIN + SELECT @ClusterName = cluster_name FROM sys.dm_hadr_cluster; + END + SET @restore_path_base = REPLACE(@restore_path_base, '**CLUSTER**', COALESCE(@ClusterName,'')); + END; + END /* IF CHARINDEX('**', @restore_path_base) <> 0 */ + + SELECT @restore_move_files = CONVERT(BIT, configuration_setting) + FROM msdb.dbo.restore_configuration c + WHERE configuration_name = N'move files'; + + IF @restore_move_files is NULL BEGIN + -- Set to default value of 1 + SET @restore_move_files = 1 + END - RAISERROR('If you want to update configuration settings, they can''t be NULL. Please Make sure @RPOSeconds / @RTOSeconds or @BackupPath / @RestorePath has a value', 0, 1) WITH NOWAIT; + END /* IF @PollDiskForNewDatabases = 1 OR @Restore = 1 */ - RETURN; - END; +/* +Certain variables necessarily skip to parts of this script that are irrelevant +in both directions to each other. They are used for other stuff. -IF @UpdateSetup = 1 - GOTO UpdateConfigs; +*/ -IF @RunSetup = 1 -BEGIN - BEGIN TRY - BEGIN - +/* - /* - - First check to see if Agent is running -- we'll get errors if it's not - - */ - - - IF ( SELECT 1 - FROM sys.all_objects - WHERE name = 'dm_server_services' ) IS NOT NULL +Pollster use happens strictly to check for new databases in sys.databases to place them in a worker queue - BEGIN +*/ - IF EXISTS ( - SELECT 1 - FROM sys.dm_server_services - WHERE servicename LIKE 'SQL Server Agent%' - AND status_desc = 'Stopped' - ) - - BEGIN - - RAISERROR('SQL Server Agent is not currently running -- it needs to be enabled to add backup worker jobs and the new database polling job', 0, 1) WITH NOWAIT; - - RETURN; - - END; - - END - +IF @PollForNewDatabases = 1 + GOTO Pollster; - BEGIN +/* +LogShamer happens when we need to find and assign work to a worker job for backups - /* - - Check to see if the database exists +*/ - */ - - RAISERROR('Checking for msdbCentral', 0, 1) WITH NOWAIT; +IF @Backup = 1 + GOTO LogShamer; - SET @db_sql += N' +/* - IF DATABASEPROPERTYEX(' + QUOTENAME(@database_name, '''') + ', ''Status'') IS NULL +Pollster use happens strictly to check for new databases in sys.databases to place them in a worker queue - BEGIN +*/ - RAISERROR(''Creating msdbCentral'', 0, 1) WITH NOWAIT; +IF @PollDiskForNewDatabases = 1 + GOTO DiskPollster; - CREATE DATABASE ' + QUOTENAME(@database_name) + '; - - ALTER DATABASE ' + QUOTENAME(@database_name) + ' SET RECOVERY FULL; - - END - '; +/* +Restoregasm Addict happens when we need to find and assign work to a worker job for restores - IF @Debug = 1 - BEGIN - RAISERROR(@db_sql, 0, 1) WITH NOWAIT; - END; +*/ +IF @Restore = 1 + GOTO Restoregasm_Addict; - IF @db_sql IS NULL - BEGIN - RAISERROR('@db_sql is NULL for some reason', 0, 1) WITH NOWAIT; - END; - EXEC sp_executesql @db_sql; +/* +Begin Polling section - /* - - Check for tables and stuff +*/ - */ - - RAISERROR('Checking for tables in msdbCentral', 0, 1) WITH NOWAIT; - SET @tbl_sql += N' - - USE ' + QUOTENAME(@database_name) + ' - - - IF OBJECT_ID(''' + QUOTENAME(@database_name) + '.dbo.backup_configuration'') IS NULL - - BEGIN - - RAISERROR(''Creating table dbo.backup_configuration'', 0, 1) WITH NOWAIT; - - CREATE TABLE dbo.backup_configuration ( - database_name NVARCHAR(256), - configuration_name NVARCHAR(512), - configuration_description NVARCHAR(512), - configuration_setting NVARCHAR(MAX) - ); - - END - - ELSE - - BEGIN - - - RAISERROR(''Backup configuration table exists, truncating'', 0, 1) WITH NOWAIT; - - - TRUNCATE TABLE dbo.backup_configuration +/* - - END +This section runs in a loop checking for new databases added to the server, or broken backups +*/ - RAISERROR(''Inserting configuration values'', 0, 1) WITH NOWAIT; - - INSERT dbo.backup_configuration (database_name, configuration_name, configuration_description, configuration_setting) - VALUES (''all'', ''log backup frequency'', ''The length of time in second between Log Backups.'', ''' + CONVERT(NVARCHAR(10), @RPOSeconds) + '''); - - INSERT dbo.backup_configuration (database_name, configuration_name, configuration_description, configuration_setting) - VALUES (''all'', ''log backup path'', ''The path to which Log Backups should go.'', ''' + @BackupPath + '''); - - INSERT dbo.backup_configuration (database_name, configuration_name, configuration_description, configuration_setting) - VALUES (''all'', ''change backup type'', ''For Ola Hallengren DatabaseBackup @ChangeBackupType param: Y = escalate to fulls, MSDB = escalate by checking msdb backup history.'', ''MSDB''); - - INSERT dbo.backup_configuration (database_name, configuration_name, configuration_description, configuration_setting) - VALUES (''all'', ''encrypt'', ''For Ola Hallengren DatabaseBackup: Y = encrypt the backup. N (default) = do not encrypt.'', NULL); - - INSERT dbo.backup_configuration (database_name, configuration_name, configuration_description, configuration_setting) - VALUES (''all'', ''encryptionalgorithm'', ''For Ola Hallengren DatabaseBackup: native 2014 choices include TRIPLE_DES_3KEY, AES_128, AES_192, AES_256.'', NULL); - - INSERT dbo.backup_configuration (database_name, configuration_name, configuration_description, configuration_setting) - VALUES (''all'', ''servercertificate'', ''For Ola Hallengren DatabaseBackup: server certificate that is used to encrypt the backup.'', NULL); - - - IF OBJECT_ID(''' + QUOTENAME(@database_name) + '.dbo.backup_worker'') IS NULL - - BEGIN - - - RAISERROR(''Creating table dbo.backup_worker'', 0, 1) WITH NOWAIT; - - CREATE TABLE dbo.backup_worker ( - id INT IDENTITY(1, 1) PRIMARY KEY CLUSTERED, - database_name NVARCHAR(256), - last_log_backup_start_time DATETIME DEFAULT ''19000101'', - last_log_backup_finish_time DATETIME DEFAULT ''99991231'', - is_started BIT DEFAULT 0, - is_completed BIT DEFAULT 0, - error_number INT DEFAULT NULL, - last_error_date DATETIME DEFAULT NULL, - ignore_database BIT DEFAULT 0, - full_backup_required BIT DEFAULT ' + CASE WHEN @FirstFullBackup = 0 THEN N'0,' ELSE N'1,' END + CHAR(10) + - N'diff_backup_required BIT DEFAULT ' + CASE WHEN @FirstDiffBackup = 0 THEN N'0' ELSE N'1' END + CHAR(10) + - N'); - - END; - - ELSE +Pollster: - BEGIN + IF @Debug = 1 RAISERROR('Beginning Pollster', 0, 1) WITH NOWAIT; + + IF OBJECT_ID('msdbCentral.dbo.backup_worker') IS NOT NULL + + BEGIN + + WHILE @PollForNewDatabases = 1 + + BEGIN + + BEGIN TRY + + IF @Debug = 1 RAISERROR('Checking for new databases...', 0, 1) WITH NOWAIT; + /* + + Look for new non-system databases -- there should probably be additional filters here for accessibility, etc. - RAISERROR(''Backup worker table exists, truncating'', 0, 1) WITH NOWAIT; - - - TRUNCATE TABLE dbo.backup_worker + */ + + INSERT msdbCentral.dbo.backup_worker (database_name) + SELECT d.name + FROM sys.databases d + WHERE NOT EXISTS ( + SELECT 1 + FROM msdbCentral.dbo.backup_worker bw + WHERE bw.database_name = d.name + ) + AND d.database_id > 4; + IF @Debug = 1 RAISERROR('Checking for wayward databases', 0, 1) WITH NOWAIT; - END + /* + + This section aims to find databases that have + * Had a log backup ever (the default for finish time is 9999-12-31, so anything with a more recent finish time has had a log backup) + * Not had a log backup start in the last 5 minutes (this could be trouble! or a really big log backup) + * Also checks msdb.dbo.backupset to make sure the database has a full backup associated with it (otherwise it's the first full, and we don't need to start taking log backups yet) - - RAISERROR(''Inserting databases for backups'', 0, 1) WITH NOWAIT; - - INSERT ' + QUOTENAME(@database_name) + '.dbo.backup_worker (database_name) - SELECT d.name - FROM sys.databases d - WHERE NOT EXISTS ( - SELECT * - FROM msdbCentral.dbo.backup_worker bw - WHERE bw.database_name = d.name - ) - AND d.database_id > 4; + */ + + IF EXISTS ( + + SELECT 1 + FROM msdbCentral.dbo.backup_worker bw WITH (READPAST) + WHERE bw.last_log_backup_finish_time < '99991231' + AND bw.last_log_backup_start_time < DATEADD(SECOND, (@rpo * -1), GETDATE()) + AND EXISTS ( + SELECT 1 + FROM msdb.dbo.backupset b + WHERE b.database_name = bw.database_name + AND b.type = 'D' + ) + ) + + BEGIN - '; + IF @Debug = 1 RAISERROR('Resetting databases with a log backup and no log backup in the last 5 minutes', 0, 1) WITH NOWAIT; - - IF @Debug = 1 - BEGIN - SET @msg = SUBSTRING(@tbl_sql, 0, 2044) - RAISERROR(@msg, 0, 1) WITH NOWAIT; - SET @msg = SUBSTRING(@tbl_sql, 2044, 4088) - RAISERROR(@msg, 0, 1) WITH NOWAIT; - SET @msg = SUBSTRING(@tbl_sql, 4088, 6132) - RAISERROR(@msg, 0, 1) WITH NOWAIT; - SET @msg = SUBSTRING(@tbl_sql, 6132, 8176) - RAISERROR(@msg, 0, 1) WITH NOWAIT; - END; + + UPDATE bw + SET bw.is_started = 0, + bw.is_completed = 1, + bw.last_log_backup_start_time = '19000101' + FROM msdbCentral.dbo.backup_worker bw + WHERE bw.last_log_backup_finish_time < '99991231' + AND bw.last_log_backup_start_time < DATEADD(SECOND, (@rpo * -1), GETDATE()) + AND EXISTS ( + SELECT 1 + FROM msdb.dbo.backupset b + WHERE b.database_name = bw.database_name + AND b.type = 'D' + ); - - IF @tbl_sql IS NULL - BEGIN - RAISERROR('@tbl_sql is NULL for some reason', 0, 1) WITH NOWAIT; - END; + + END; --End check for wayward databases + + /* + + Wait 1 minute between runs, we don't need to be checking this constantly + + */ + + IF @Debug = 1 RAISERROR('Waiting for 1 minute', 0, 1) WITH NOWAIT; + + WAITFOR DELAY '00:01:00.000'; - EXEC sp_executesql @tbl_sql; + END TRY + BEGIN CATCH + + + SELECT @msg = N'Error inserting databases to msdbCentral.dbo.backup_worker, error number is ' + CONVERT(NVARCHAR(10), ERROR_NUMBER()) + ', error message is ' + ERROR_MESSAGE(), + @error_severity = ERROR_SEVERITY(), + @error_state = ERROR_STATE(); + + RAISERROR(@msg, @error_severity, @error_state) WITH NOWAIT; + + + WHILE @@TRANCOUNT > 0 + ROLLBACK; + + + END CATCH; + + + END; + + /* Check to make sure job is still enabled */ + IF NOT EXISTS ( + SELECT * + FROM msdb.dbo.sysjobs + WHERE name = 'sp_AllNightLog_PollForNewDatabases' + AND enabled = 1 + ) + BEGIN + RAISERROR('sp_AllNightLog_PollForNewDatabases job is disabled, so gracefully exiting. It feels graceful to me, anyway.', 0, 1) WITH NOWAIT; + RETURN; + END + + END;-- End Pollster loop + + ELSE + + BEGIN + + RAISERROR('msdbCentral.dbo.backup_worker does not exist, please create it.', 0, 1) WITH NOWAIT; + RETURN; + + END; + RETURN; + + +/* + +End of Pollster + +*/ + + +/* + +Begin DiskPollster + +*/ + + +/* + +This section runs in a loop checking restore path for new databases added to the server, or broken restores + +*/ + +DiskPollster: + + IF @Debug = 1 RAISERROR('Beginning DiskPollster', 0, 1) WITH NOWAIT; + + IF OBJECT_ID('msdb.dbo.restore_configuration') IS NOT NULL + + BEGIN + + WHILE @PollDiskForNewDatabases = 1 + + BEGIN + + BEGIN TRY + + IF @Debug = 1 RAISERROR('Checking for new databases in: ', 0, 1) WITH NOWAIT; + IF @Debug = 1 RAISERROR(@restore_path_base, 0, 1) WITH NOWAIT; + + /* + + Look for new non-system databases -- there should probably be additional filters here for accessibility, etc. + + */ /* - This section creates tables for restore workers to work off of + This setups up the @cmd variable to check the restore path for new folders + In our case, a new folder means a new database, because we assume a pristine path + */ + SET @cmd = N'DIR /b "' + @restore_path_base + N'"'; - /* - - In search of msdb + IF @Debug = 1 + BEGIN + PRINT @cmd; + END - */ - RAISERROR('Checking for msdb. Yeah, I know...', 0, 1) WITH NOWAIT; + DELETE @FileList; + INSERT INTO @FileList (BackupFile) + EXEC master.sys.xp_cmdshell @cmd; - IF DATABASEPROPERTYEX('msdb', 'Status') IS NULL + IF ( + SELECT COUNT(*) + FROM @FileList AS fl + WHERE fl.BackupFile = 'The system cannot find the path specified.' + OR fl.BackupFile = 'File Not Found' + ) = 1 BEGIN - - RAISERROR('YOU HAVE NO MSDB WHY?!', 0, 1) WITH NOWAIT; - - RETURN; + + RAISERROR('No rows were returned for that database\path', 0, 1) WITH NOWAIT; END; + IF ( + SELECT COUNT(*) + FROM @FileList AS fl + WHERE fl.BackupFile = 'Access is denied.' + ) = 1 + + BEGIN - /* In search of restore_configuration */ + RAISERROR('Access is denied to %s', 16, 1, @restore_path_base) WITH NOWAIT; - RAISERROR('Checking for Restore Worker tables in msdb', 0, 1) WITH NOWAIT; + END; - IF OBJECT_ID('msdb.dbo.restore_configuration') IS NULL + IF ( + SELECT COUNT(*) + FROM @FileList AS fl + ) = 1 + AND ( + SELECT COUNT(*) + FROM @FileList AS fl + WHERE fl.BackupFile IS NULL + ) = 1 BEGIN + + RAISERROR('That directory appears to be empty', 0, 1) WITH NOWAIT; + + RETURN; + + END - RAISERROR('Creating restore_configuration table in msdb', 0, 1) WITH NOWAIT; + IF ( + SELECT COUNT(*) + FROM @FileList AS fl + WHERE fl.BackupFile = 'The user name or password is incorrect.' + ) = 1 - CREATE TABLE msdb.dbo.restore_configuration ( - database_name NVARCHAR(256), - configuration_name NVARCHAR(512), - configuration_description NVARCHAR(512), - configuration_setting NVARCHAR(MAX) - ); + BEGIN + + RAISERROR('Incorrect user name or password for %s', 16, 1, @restore_path_base) WITH NOWAIT; END; + INSERT msdb.dbo.restore_worker (database_name) + SELECT fl.BackupFile + FROM @FileList AS fl + WHERE fl.BackupFile IS NOT NULL + AND fl.BackupFile COLLATE DATABASE_DEFAULT NOT IN (SELECT name from sys.databases where database_id < 5) + AND NOT EXISTS + ( + SELECT 1 + FROM msdb.dbo.restore_worker rw + WHERE rw.database_name = fl.BackupFile + ) - ELSE + IF @Debug = 1 RAISERROR('Checking for wayward databases', 0, 1) WITH NOWAIT; + /* + + This section aims to find databases that have + * Had a log restore ever (the default for finish time is 9999-12-31, so anything with a more recent finish time has had a log restore) + * Not had a log restore start in the last 5 minutes (this could be trouble! or a really big log restore) + * Also checks msdb.dbo.backupset to make sure the database has a full backup associated with it (otherwise it's the first full, and we don't need to start adding log restores yet) + */ + + IF EXISTS ( + + SELECT 1 + FROM msdb.dbo.restore_worker rw WITH (READPAST) + WHERE rw.last_log_restore_finish_time < '99991231' + AND rw.last_log_restore_start_time < DATEADD(SECOND, (@rto * -1), GETDATE()) + AND EXISTS ( + SELECT 1 + FROM msdb.dbo.restorehistory r + WHERE r.destination_database_name = rw.database_name + AND r.restore_type = 'D' + ) + ) + BEGIN + + IF @Debug = 1 RAISERROR('Resetting databases with a log restore and no log restore in the last 5 minutes', 0, 1) WITH NOWAIT; - RAISERROR('Restore configuration table exists, truncating', 0, 1) WITH NOWAIT; + + UPDATE rw + SET rw.is_started = 0, + rw.is_completed = 1, + rw.last_log_restore_start_time = '19000101' + FROM msdb.dbo.restore_worker rw + WHERE rw.last_log_restore_finish_time < '99991231' + AND rw.last_log_restore_start_time < DATEADD(SECOND, (@rto * -1), GETDATE()) + AND EXISTS ( + SELECT 1 + FROM msdb.dbo.restorehistory r + WHERE r.destination_database_name = rw.database_name + AND r.restore_type = 'D' + ); - TRUNCATE TABLE msdb.dbo.restore_configuration; + + END; --End check for wayward databases + + /* - END; + Wait 1 minute between runs, we don't need to be checking this constantly + + */ + /* Check to make sure job is still enabled */ + IF NOT EXISTS ( + SELECT * + FROM msdb.dbo.sysjobs + WHERE name = 'sp_AllNightLog_PollDiskForNewDatabases' + AND enabled = 1 + ) + BEGIN + RAISERROR('sp_AllNightLog_PollDiskForNewDatabases job is disabled, so gracefully exiting. It feels graceful to me, anyway.', 0, 1) WITH NOWAIT; + RETURN; + END + + IF @Debug = 1 RAISERROR('Waiting for 1 minute', 0, 1) WITH NOWAIT; + + WAITFOR DELAY '00:01:00.000'; - RAISERROR('Inserting configuration values to msdb.dbo.restore_configuration', 0, 1) WITH NOWAIT; - - INSERT msdb.dbo.restore_configuration (database_name, configuration_name, configuration_description, configuration_setting) - VALUES ('all', 'log restore frequency', 'The length of time in second between Log Restores.', @RTOSeconds); - - INSERT msdb.dbo.restore_configuration (database_name, configuration_name, configuration_description, configuration_setting) - VALUES ('all', 'log restore path', 'The path to which Log Restores come from.', @RestorePath); + END TRY - INSERT msdb.dbo.restore_configuration (database_name, configuration_name, configuration_description, configuration_setting) - VALUES ('all', 'move files', 'Determines if we move database files to default data/log directories.', @MoveFiles); + BEGIN CATCH - IF OBJECT_ID('msdb.dbo.restore_worker') IS NULL - - BEGIN - - - RAISERROR('Creating table msdb.dbo.restore_worker', 0, 1) WITH NOWAIT; - - CREATE TABLE msdb.dbo.restore_worker ( - id INT IDENTITY(1, 1) PRIMARY KEY CLUSTERED, - database_name NVARCHAR(256), - last_log_restore_start_time DATETIME DEFAULT '19000101', - last_log_restore_finish_time DATETIME DEFAULT '99991231', - is_started BIT DEFAULT 0, - is_completed BIT DEFAULT 0, - error_number INT DEFAULT NULL, - last_error_date DATETIME DEFAULT NULL, - ignore_database BIT DEFAULT 0, - full_backup_required BIT DEFAULT 0, - diff_backup_required BIT DEFAULT 0 - ); - - RAISERROR('Inserting databases for restores', 0, 1) WITH NOWAIT; + SELECT @msg = N'Error inserting databases to msdb.dbo.restore_worker, error number is ' + CONVERT(NVARCHAR(10), ERROR_NUMBER()) + ', error message is ' + ERROR_MESSAGE(), + @error_severity = ERROR_SEVERITY(), + @error_state = ERROR_STATE(); - INSERT msdb.dbo.restore_worker (database_name) - SELECT d.name - FROM sys.databases d - WHERE NOT EXISTS ( - SELECT * - FROM msdb.dbo.restore_worker bw - WHERE bw.database_name = d.name - ) - AND d.database_id > 4; - - - END; + RAISERROR(@msg, @error_severity, @error_state) WITH NOWAIT; + + WHILE @@TRANCOUNT > 0 + ROLLBACK; - - /* - - Add Jobs - - */ - + END CATCH; + + + END; - /* - - Look for our ten second schedule -- all jobs use this to restart themselves if they fail + END;-- End Pollster loop + + ELSE + + BEGIN + + RAISERROR('msdb.dbo.restore_worker does not exist, please create it.', 0, 1) WITH NOWAIT; + RETURN; + + END; + RETURN; - Fun fact: you can add the same schedule name multiple times, so we don't want to just stick it in there - - */ - RAISERROR('Checking for ten second schedule', 0, 1) WITH NOWAIT; +/* - IF NOT EXISTS ( - SELECT 1 - FROM msdb.dbo.sysschedules - WHERE name = 'ten_seconds' - ) - - BEGIN - - - RAISERROR('Creating ten second schedule', 0, 1) WITH NOWAIT; +Begin LogShamer - - EXEC msdb.dbo.sp_add_schedule @schedule_name= ten_seconds, - @enabled = 1, - @freq_type = 4, - @freq_interval = 1, - @freq_subday_type = 2, - @freq_subday_interval = 10, - @freq_relative_interval = 0, - @freq_recurrence_factor = 0, - @active_start_date = @active_start_date, - @active_end_date = 99991231, - @active_start_time = 0, - @active_end_time = 235959; - - END; +*/ + +LogShamer: + + IF @Debug = 1 RAISERROR('Beginning Backups', 0, 1) WITH NOWAIT; + + IF OBJECT_ID('msdbCentral.dbo.backup_worker') IS NOT NULL + + BEGIN - /* - Look for Backup Pollster job -- this job sets up our watcher for new databases to back up + Make sure configuration table exists... */ - + + IF OBJECT_ID('msdbCentral.dbo.backup_configuration') IS NOT NULL + + BEGIN + + IF @Debug = 1 RAISERROR('Checking variables', 0, 1) WITH NOWAIT; + + /* - RAISERROR('Checking for pollster job', 0, 1) WITH NOWAIT; - + These settings are configurable + + I haven't found a good way to find the default backup path that doesn't involve xp_regread - IF NOT EXISTS ( - SELECT 1 - FROM msdb.dbo.sysjobs - WHERE name = 'sp_AllNightLog_PollForNewDatabases' - ) - - - BEGIN - - - RAISERROR('Creating pollster job', 0, 1) WITH NOWAIT; - - IF @EnableBackupJobs = 1 - BEGIN - EXEC msdb.dbo.sp_add_job @job_name = sp_AllNightLog_PollForNewDatabases, - @description = 'This is a worker for the purposes of polling sys.databases for new entries to insert to the worker queue table.', - @category_name = 'Database Maintenance', - @owner_login_name = 'sa', - @enabled = 1; - END - ELSE - BEGIN - EXEC msdb.dbo.sp_add_job @job_name = sp_AllNightLog_PollForNewDatabases, - @description = 'This is a worker for the purposes of polling sys.databases for new entries to insert to the worker queue table.', - @category_name = 'Database Maintenance', - @owner_login_name = 'sa', - @enabled = 0; - END - - - RAISERROR('Adding job step', 0, 1) WITH NOWAIT; + */ + + SELECT @rpo = CONVERT(INT, configuration_setting) + FROM msdbCentral.dbo.backup_configuration c + WHERE configuration_name = N'log backup frequency' + AND database_name = N'all'; + + + IF @rpo IS NULL + BEGIN + RAISERROR('@rpo cannot be NULL. Please check the msdbCentral.dbo.backup_configuration table', 0, 1) WITH NOWAIT; + RETURN; + END; + + + SELECT @backup_path = CONVERT(NVARCHAR(512), configuration_setting) + FROM msdbCentral.dbo.backup_configuration c + WHERE configuration_name = N'log backup path' + AND database_name = N'all'; + + + IF @backup_path IS NULL + BEGIN + RAISERROR('@backup_path cannot be NULL. Please check the msdbCentral.dbo.backup_configuration table', 0, 1) WITH NOWAIT; + RETURN; + END; - - EXEC msdb.dbo.sp_add_jobstep @job_name = sp_AllNightLog_PollForNewDatabases, - @step_name = sp_AllNightLog_PollForNewDatabases, - @subsystem = 'TSQL', - @command = 'EXEC sp_AllNightLog @PollForNewDatabases = 1'; - - - - RAISERROR('Adding job server', 0, 1) WITH NOWAIT; + SELECT @changebackuptype = configuration_setting + FROM msdbCentral.dbo.backup_configuration c + WHERE configuration_name = N'change backup type' + AND database_name = N'all'; - - EXEC msdb.dbo.sp_add_jobserver @job_name = sp_AllNightLog_PollForNewDatabases; + SELECT @encrypt = configuration_setting + FROM msdbCentral.dbo.backup_configuration c + WHERE configuration_name = N'encrypt' + AND database_name = N'all'; - - - RAISERROR('Attaching schedule', 0, 1) WITH NOWAIT; - - - EXEC msdb.dbo.sp_attach_schedule @job_name = sp_AllNightLog_PollForNewDatabases, - @schedule_name = ten_seconds; - - - END; - + SELECT @encryptionalgorithm = configuration_setting + FROM msdbCentral.dbo.backup_configuration c + WHERE configuration_name = N'encryptionalgorithm' + AND database_name = N'all'; + SELECT @servercertificate = configuration_setting + FROM msdbCentral.dbo.backup_configuration c + WHERE configuration_name = N'servercertificate' + AND database_name = N'all'; + + IF @encrypt = N'Y' AND (@encryptionalgorithm IS NULL OR @servercertificate IS NULL) + BEGIN + RAISERROR('If encryption is Y, then both the encryptionalgorithm and servercertificate must be set. Please check the msdbCentral.dbo.backup_configuration table', 0, 1) WITH NOWAIT; + RETURN; + END; + + END; + + ELSE + + BEGIN + + RAISERROR('msdbCentral.dbo.backup_configuration does not exist, please run setup script', 0, 1) WITH NOWAIT; + RETURN; + + END; + + + WHILE @Backup = 1 /* - Look for Restore Pollster job -- this job sets up our watcher for new databases to back up - - */ + Start loop to take log backups - - RAISERROR('Checking for restore pollster job', 0, 1) WITH NOWAIT; + */ - IF NOT EXISTS ( - SELECT 1 - FROM msdb.dbo.sysjobs - WHERE name = 'sp_AllNightLog_PollDiskForNewDatabases' - ) - - BEGIN - - - RAISERROR('Creating restore pollster job', 0, 1) WITH NOWAIT; + + BEGIN TRY + + BEGIN TRAN; + + IF @Debug = 1 RAISERROR('Begin tran to grab a database to back up', 0, 1) WITH NOWAIT; - - IF @EnableRestoreJobs = 1 - BEGIN - EXEC msdb.dbo.sp_add_job @job_name = sp_AllNightLog_PollDiskForNewDatabases, - @description = 'This is a worker for the purposes of polling your restore path for new entries to insert to the worker queue table.', - @category_name = 'Database Maintenance', - @owner_login_name = 'sa', - @enabled = 1; - END - ELSE - BEGIN - EXEC msdb.dbo.sp_add_job @job_name = sp_AllNightLog_PollDiskForNewDatabases, - @description = 'This is a worker for the purposes of polling your restore path for new entries to insert to the worker queue table.', - @category_name = 'Database Maintenance', - @owner_login_name = 'sa', - @enabled = 0; - END - - - - RAISERROR('Adding restore job step', 0, 1) WITH NOWAIT; - - EXEC msdb.dbo.sp_add_jobstep @job_name = sp_AllNightLog_PollDiskForNewDatabases, - @step_name = sp_AllNightLog_PollDiskForNewDatabases, - @subsystem = 'TSQL', - @command = 'EXEC sp_AllNightLog @PollDiskForNewDatabases = 1'; - - - - RAISERROR('Adding restore job server', 0, 1) WITH NOWAIT; + /* + + This grabs a database for a worker to work on - - EXEC msdb.dbo.sp_add_jobserver @job_name = sp_AllNightLog_PollDiskForNewDatabases; + The locking hints hope to provide some isolation when 10+ workers are in action + + */ + + + SELECT TOP (1) + @database = bw.database_name + FROM msdbCentral.dbo.backup_worker bw WITH (UPDLOCK, HOLDLOCK, ROWLOCK) + WHERE + ( /*This section works on databases already part of the backup cycle*/ + bw.is_started = 0 + AND bw.is_completed = 1 + AND bw.last_log_backup_start_time < DATEADD(SECOND, (@rpo * -1), GETDATE()) + AND (bw.error_number IS NULL OR bw.error_number > 0) /* negative numbers indicate human attention required */ + AND bw.ignore_database = 0 + ) + OR + ( /*This section picks up newly added databases by Pollster*/ + bw.is_started = 0 + AND bw.is_completed = 0 + AND bw.last_log_backup_start_time = '1900-01-01 00:00:00.000' + AND bw.last_log_backup_finish_time = '9999-12-31 00:00:00.000' + AND (bw.error_number IS NULL OR bw.error_number > 0) /* negative numbers indicate human attention required */ + AND bw.ignore_database = 0 + ) + ORDER BY bw.last_log_backup_start_time ASC, bw.last_log_backup_finish_time ASC, bw.database_name ASC; + + + IF @database IS NOT NULL + BEGIN + SET @msg = N'Updating backup_worker for database ' + ISNULL(@database, 'UH OH NULL @database'); + IF @Debug = 1 RAISERROR(@msg, 0, 1) WITH NOWAIT; + + /* + + Update the worker table so other workers know a database is being backed up + + */ - - - RAISERROR('Attaching schedule', 0, 1) WITH NOWAIT; - + + UPDATE bw + SET bw.is_started = 1, + bw.is_completed = 0, + bw.last_log_backup_start_time = GETDATE() + FROM msdbCentral.dbo.backup_worker bw + WHERE bw.database_name = @database; + END + + COMMIT; + + END TRY + + BEGIN CATCH - EXEC msdb.dbo.sp_attach_schedule @job_name = sp_AllNightLog_PollDiskForNewDatabases, - @schedule_name = ten_seconds; - - - END; - - + /* + + Do I need to build retry logic in here? Try to catch deadlocks? I don't know yet! + + */ - /* - - This section creates @Jobs (quantity) of worker jobs to take log backups with + SELECT @msg = N'Error securing a database to backup, error number is ' + CONVERT(NVARCHAR(10), ERROR_NUMBER()) + ', error message is ' + ERROR_MESSAGE(), + @error_severity = ERROR_SEVERITY(), + @error_state = ERROR_STATE(); + RAISERROR(@msg, @error_severity, @error_state) WITH NOWAIT; - They work in a queue + SET @database = NULL; + + WHILE @@TRANCOUNT > 0 + ROLLBACK; + + END CATCH; - It's queuete - - */ + /* If we don't find a database to work on, wait for a few seconds */ + IF @database IS NULL - RAISERROR('Checking for sp_AllNightLog backup jobs', 0, 1) WITH NOWAIT; - - - SELECT @counter = COUNT(*) + 1 - FROM msdb.dbo.sysjobs - WHERE name LIKE 'sp[_]AllNightLog[_]Backup[_]%'; + BEGIN + IF @Debug = 1 RAISERROR('No databases to back up right now, starting 3 second throttle', 0, 1) WITH NOWAIT; + WAITFOR DELAY '00:00:03.000'; - SET @msg = 'Found ' + CONVERT(NVARCHAR(10), (@counter - 1)) + ' backup jobs -- ' + CASE WHEN @counter < @Jobs THEN + 'starting loop!' - WHEN @counter >= @Jobs THEN 'skipping loop!' - ELSE 'Oh woah something weird happened!' - END; + /* Check to make sure job is still enabled */ + IF NOT EXISTS ( + SELECT * + FROM msdb.dbo.sysjobs + WHERE name LIKE 'sp_AllNightLog_Backup%' + AND enabled = 1 + ) + BEGIN + RAISERROR('sp_AllNightLog_Backup jobs are disabled, so gracefully exiting. It feels graceful to me, anyway.', 0, 1) WITH NOWAIT; + RETURN; + END - RAISERROR(@msg, 0, 1) WITH NOWAIT; - - WHILE @counter <= @Jobs + END + + + BEGIN TRY + + BEGIN + + IF @database IS NOT NULL + /* - BEGIN + Make sure we have a database to work on -- I should make this more robust so we do something if it is NULL, maybe + + */ - - RAISERROR('Setting job name', 0, 1) WITH NOWAIT; + + BEGIN + + SET @msg = N'Taking backup of ' + ISNULL(@database, 'UH OH NULL @database'); + IF @Debug = 1 RAISERROR(@msg, 0, 1) WITH NOWAIT; - SET @job_name_backups = N'sp_AllNightLog_Backup_' + CASE WHEN @counter < 10 THEN N'0' + CONVERT(NVARCHAR(10), @counter) - WHEN @counter >= 10 THEN CONVERT(NVARCHAR(10), @counter) - END; - + /* - RAISERROR('Setting @job_sql', 0, 1) WITH NOWAIT; + Call Ola's proc to backup the database + + */ + + IF @encrypt = 'Y' + EXEC dbo.DatabaseBackup @Databases = @database, --Database we're working on + @BackupType = 'LOG', --Going for the LOGs + @Directory = @backup_path, --The path we need to back up to + @Verify = 'N', --We don't want to verify these, it eats into job time + @ChangeBackupType = @changebackuptype, --If we need to switch to a FULL because one hasn't been taken + @CheckSum = 'Y', --These are a good idea + @Compress = 'Y', --This is usually a good idea + @LogToTable = 'Y', --We should do this for posterity + @Encrypt = @encrypt, + @EncryptionAlgorithm = @encryptionalgorithm, + @ServerCertificate = @servercertificate; + ELSE + EXEC dbo.DatabaseBackup @Databases = @database, --Database we're working on + @BackupType = 'LOG', --Going for the LOGs + @Directory = @backup_path, --The path we need to back up to + @Verify = 'N', --We don't want to verify these, it eats into job time + @ChangeBackupType = @changebackuptype, --If we need to switch to a FULL because one hasn't been taken + @CheckSum = 'Y', --These are a good idea + @Compress = 'Y', --This is usually a good idea + @LogToTable = 'Y'; --We should do this for posterity + - SET @job_sql = N' - - EXEC msdb.dbo.sp_add_job @job_name = ' + @job_name_backups + ', - @description = ' + @job_description_backups + ', - @category_name = ' + @job_category + ', - @owner_login_name = ' + @job_owner + ','; - IF @EnableBackupJobs = 1 - BEGIN - SET @job_sql = @job_sql + ' @enabled = 1; '; - END - ELSE - BEGIN - SET @job_sql = @job_sql + ' @enabled = 0; '; - END - - - SET @job_sql = @job_sql + ' - EXEC msdb.dbo.sp_add_jobstep @job_name = ' + @job_name_backups + ', - @step_name = ' + @job_name_backups + ', - @subsystem = ''TSQL'', - @command = ' + @job_command_backups + '; - - - EXEC msdb.dbo.sp_add_jobserver @job_name = ' + @job_name_backups + '; - - - EXEC msdb.dbo.sp_attach_schedule @job_name = ' + @job_name_backups + ', - @schedule_name = ten_seconds; - - '; - + /* - SET @counter += 1; - + Catch any erroneous zones - IF @Debug = 1 - BEGIN - RAISERROR(@job_sql, 0, 1) WITH NOWAIT; - END; - - - IF @job_sql IS NULL - BEGIN - RAISERROR('@job_sql is NULL for some reason', 0, 1) WITH NOWAIT; - END; - - - EXEC sp_executesql @job_sql; - - - END; - - - - /* - - This section creates @Jobs (quantity) of worker jobs to restore logs with - - They too work in a queue - - Like a queue-t 3.14 - - */ - - - RAISERROR('Checking for sp_AllNightLog Restore jobs', 0, 1) WITH NOWAIT; - + */ + + SELECT @error_number = ERROR_NUMBER(), + @error_severity = ERROR_SEVERITY(), + @error_state = ERROR_STATE(); + + END; --End call to dbo.DatabaseBackup + + END; --End successful check of @database (not NULL) - SELECT @counter = COUNT(*) + 1 - FROM msdb.dbo.sysjobs - WHERE name LIKE 'sp[_]AllNightLog[_]Restore[_]%'; + END TRY + + BEGIN CATCH + + IF @error_number IS NOT NULL - SET @msg = 'Found ' + CONVERT(NVARCHAR(10), (@counter - 1)) + ' restore jobs -- ' + CASE WHEN @counter < @Jobs THEN + 'starting loop!' - WHEN @counter >= @Jobs THEN 'skipping loop!' - ELSE 'Oh woah something weird happened!' - END; + /* + + If the ERROR() function returns a number, update the table with it and the last error date. - RAISERROR(@msg, 0, 1) WITH NOWAIT; + Also update the last start time to 1900-01-01 so it gets picked back up immediately -- the query to find a log backup to take sorts by start time - - WHILE @counter <= @Jobs + */ + + BEGIN + + SET @msg = N'Error number is ' + CONVERT(NVARCHAR(10), ERROR_NUMBER()); + RAISERROR(@msg, @error_severity, @error_state) WITH NOWAIT; + + SET @msg = N'Updating backup_worker for database ' + ISNULL(@database, 'UH OH NULL @database') + ' for unsuccessful backup'; + RAISERROR(@msg, 0, 1) WITH NOWAIT; + + + UPDATE bw + SET bw.is_started = 0, + bw.is_completed = 1, + bw.last_log_backup_start_time = '19000101', + bw.error_number = @error_number, + bw.last_error_date = GETDATE() + FROM msdbCentral.dbo.backup_worker bw + WHERE bw.database_name = @database; - - BEGIN - - RAISERROR('Setting job name', 0, 1) WITH NOWAIT; + /* + + Set @database back to NULL to avoid variable assignment weirdness + + */ - SET @job_name_restores = N'sp_AllNightLog_Restore_' + CASE WHEN @counter < 10 THEN N'0' + CONVERT(NVARCHAR(10), @counter) - WHEN @counter >= 10 THEN CONVERT(NVARCHAR(10), @counter) - END; - - - RAISERROR('Setting @job_sql', 0, 1) WITH NOWAIT; + SET @database = NULL; - SET @job_sql = N' - - EXEC msdb.dbo.sp_add_job @job_name = ' + @job_name_restores + ', - @description = ' + @job_description_restores + ', - @category_name = ' + @job_category + ', - @owner_login_name = ' + @job_owner + ','; - IF @EnableRestoreJobs = 1 - BEGIN - SET @job_sql = @job_sql + ' @enabled = 1; '; - END - ELSE - BEGIN - SET @job_sql = @job_sql + ' @enabled = 0; '; - END - - - SET @job_sql = @job_sql + ' - - EXEC msdb.dbo.sp_add_jobstep @job_name = ' + @job_name_restores + ', - @step_name = ' + @job_name_restores + ', - @subsystem = ''TSQL'', - @command = ' + @job_command_restores + '; - - - EXEC msdb.dbo.sp_add_jobserver @job_name = ' + @job_name_restores + '; - - - EXEC msdb.dbo.sp_attach_schedule @job_name = ' + @job_name_restores + ', - @schedule_name = ten_seconds; - - '; - + /* - SET @counter += 1; + Wait around for a second so we're not just spinning wheels -- this only runs if the BEGIN CATCH is triggered by an error + */ - IF @Debug = 1 - BEGIN - RAISERROR(@job_sql, 0, 1) WITH NOWAIT; - END; - - - IF @job_sql IS NULL - BEGIN - RAISERROR('@job_sql is NULL for some reason', 0, 1) WITH NOWAIT; - END; + IF @Debug = 1 RAISERROR('Starting 1 second throttle', 0, 1) WITH NOWAIT; + + WAITFOR DELAY '00:00:01.000'; + END; -- End update of unsuccessful backup + + END CATCH; + + IF @database IS NOT NULL AND @error_number IS NULL - EXEC sp_executesql @job_sql; + /* + + If no error, update everything normally + + */ - END; + BEGIN + + IF @Debug = 1 RAISERROR('Error number IS NULL', 0, 1) WITH NOWAIT; + + SET @msg = N'Updating backup_worker for database ' + ISNULL(@database, 'UH OH NULL @database') + ' for successful backup'; + IF @Debug = 1 RAISERROR(@msg, 0, 1) WITH NOWAIT; + + + UPDATE bw + SET bw.is_started = 0, + bw.is_completed = 1, + bw.last_log_backup_finish_time = GETDATE() + FROM msdbCentral.dbo.backup_worker bw + WHERE bw.database_name = @database; + + /* + + Set @database back to NULL to avoid variable assignment weirdness + + */ - RAISERROR('Setup complete!', 0, 1) WITH NOWAIT; - - END; --End for the Agent job creation + SET @database = NULL; - END;--End for Database and Table creation - END TRY + END; -- End update for successful backup - BEGIN CATCH + + END; -- End @Backup WHILE loop + + END; -- End successful check for backup_worker and subsequent code - SELECT @msg = N'Error occurred during setup: ' + CONVERT(NVARCHAR(10), ERROR_NUMBER()) + ', error message is ' + ERROR_MESSAGE(), - @error_severity = ERROR_SEVERITY(), - @error_state = ERROR_STATE(); + + ELSE + + BEGIN + + RAISERROR('msdbCentral.dbo.backup_worker does not exist, please run setup script', 0, 1) WITH NOWAIT; + + RETURN; - RAISERROR(@msg, @error_severity, @error_state) WITH NOWAIT; - - - WHILE @@TRANCOUNT > 0 - ROLLBACK; - - END CATCH; - -END; /* IF @RunSetup = 1 */ - + END; RETURN; -UpdateConfigs: - -IF @UpdateSetup = 1 - - BEGIN - - /* If we're enabling backup jobs, we may need to run restore with recovery on msdbCentral to bring it online: */ - IF @EnableBackupJobs = 1 AND EXISTS (SELECT * FROM sys.databases WHERE name = 'msdbCentral' AND state = 1) - BEGIN - RAISERROR('msdbCentral exists, but is in restoring state. Running restore with recovery...', 0, 1) WITH NOWAIT; - - BEGIN TRY - RESTORE DATABASE [msdbCentral] WITH RECOVERY; - END TRY - - BEGIN CATCH +/* - SELECT @error_number = ERROR_NUMBER(), - @error_severity = ERROR_SEVERITY(), - @error_state = ERROR_STATE(); +Begin Restoregasm_Addict section - SELECT @msg = N'Error running restore with recovery on msdbCentral, error number is ' + CONVERT(NVARCHAR(10), ERROR_NUMBER()) + ', error message is ' + ERROR_MESSAGE(), - @error_severity = ERROR_SEVERITY(), - @error_state = ERROR_STATE(); - - RAISERROR(@msg, @error_severity, @error_state) WITH NOWAIT; +*/ - END CATCH; +Restoregasm_Addict: - END +IF @Restore = 1 + IF @Debug = 1 RAISERROR('Beginning Restores', 0, 1) WITH NOWAIT; + + /* Check to make sure backup jobs aren't enabled */ + IF EXISTS ( + SELECT * + FROM msdb.dbo.sysjobs + WHERE name LIKE 'sp_AllNightLog_Backup%' + AND enabled = 1 + ) + BEGIN + RAISERROR('sp_AllNightLog_Backup jobs are enabled, so gracefully exiting. You do not want to accidentally do restores over top of the databases you are backing up.', 0, 1) WITH NOWAIT; + RETURN; + END - /* Only check for this after trying to restore msdbCentral: */ - IF @EnableBackupJobs = 1 AND NOT EXISTS (SELECT * FROM sys.databases WHERE name = 'msdbCentral' AND state = 0) - BEGIN - RAISERROR('msdbCentral is not online. Repair that first, then try to enable backup jobs.', 0, 1) WITH NOWAIT; - RETURN - END + IF OBJECT_ID('msdb.dbo.restore_worker') IS NOT NULL + + BEGIN + + /* + + Make sure configuration table exists... + + */ + + IF OBJECT_ID('msdb.dbo.restore_configuration') IS NOT NULL + + BEGIN + + IF @Debug = 1 RAISERROR('Checking variables', 0, 1) WITH NOWAIT; + + /* + + These settings are configurable + + */ + + SELECT @rto = CONVERT(INT, configuration_setting) + FROM msdb.dbo.restore_configuration c + WHERE configuration_name = N'log restore frequency'; + + + IF @rto IS NULL + BEGIN + RAISERROR('@rto cannot be NULL. Please check the msdb.dbo.restore_configuration table', 0, 1) WITH NOWAIT; + RETURN; + END; + + + END; + + ELSE + + BEGIN + + RAISERROR('msdb.dbo.restore_configuration does not exist, please run setup script', 0, 1) WITH NOWAIT; + + RETURN; + + END; + + + WHILE @Restore = 1 + /* + + Start loop to restore log backups - IF OBJECT_ID('msdbCentral.dbo.backup_configuration') IS NOT NULL + */ - RAISERROR('Found backup config, checking variables...', 0, 1) WITH NOWAIT; - + BEGIN - + BEGIN TRY + + BEGIN TRAN; + + IF @Debug = 1 RAISERROR('Begin tran to grab a database to restore', 0, 1) WITH NOWAIT; - - IF @RPOSeconds IS NOT NULL - - - BEGIN - - RAISERROR('Attempting to update RPO setting', 0, 1) WITH NOWAIT; - - UPDATE c - SET c.configuration_setting = CONVERT(NVARCHAR(10), @RPOSeconds) - FROM msdbCentral.dbo.backup_configuration AS c - WHERE c.configuration_name = N'log backup frequency'; - - END; - - - IF @BackupPath IS NOT NULL - BEGIN + /* - RAISERROR('Attempting to update Backup Path setting', 0, 1) WITH NOWAIT; - - UPDATE c - SET c.configuration_setting = @BackupPath - FROM msdbCentral.dbo.backup_configuration AS c - WHERE c.configuration_name = N'log backup path'; - + This grabs a database for a worker to work on - END; + The locking hints hope to provide some isolation when 10+ workers are in action + + */ + + + SELECT TOP (1) + @database = rw.database_name, + @only_logs_after = REPLACE(REPLACE(REPLACE(CONVERT(NVARCHAR(30), rw.last_log_restore_start_time, 120), ' ', ''), '-', ''), ':', ''), + @restore_full = CASE WHEN rw.is_started = 0 + AND rw.is_completed = 0 + AND rw.last_log_restore_start_time = '1900-01-01 00:00:00.000' + AND rw.last_log_restore_finish_time = '9999-12-31 00:00:00.000' + THEN 1 + ELSE 0 + END + FROM msdb.dbo.restore_worker rw WITH (UPDLOCK, HOLDLOCK, ROWLOCK) + WHERE ( + ( /*This section works on databases already part of the backup cycle*/ + rw.is_started = 0 + AND rw.is_completed = 1 + AND rw.last_log_restore_start_time < DATEADD(SECOND, (@rto * -1), GETDATE()) + AND (rw.error_number IS NULL OR rw.error_number > 0) /* negative numbers indicate human attention required */ + ) + OR + ( /*This section picks up newly added databases by DiskPollster*/ + rw.is_started = 0 + AND rw.is_completed = 0 + AND rw.last_log_restore_start_time = '1900-01-01 00:00:00.000' + AND rw.last_log_restore_finish_time = '9999-12-31 00:00:00.000' + AND (rw.error_number IS NULL OR rw.error_number > 0) /* negative numbers indicate human attention required */ + ) + ) + AND rw.ignore_database = 0 + AND NOT EXISTS ( + /* Validation check to ensure the database either doesn't exist or is in a restoring/standby state */ + SELECT 1 + FROM sys.databases d + WHERE d.name = rw.database_name + AND state <> 1 /* Restoring */ + AND NOT (state=0 AND d.is_in_standby=1) /* standby mode */ + ) + ORDER BY rw.last_log_restore_start_time ASC, rw.last_log_restore_finish_time ASC, rw.database_name ASC; + + + IF @database IS NOT NULL + BEGIN + SET @msg = N'Updating restore_worker for database ' + ISNULL(@database, 'UH OH NULL @database'); + IF @Debug = 1 RAISERROR(@msg, 0, 1) WITH NOWAIT; + + /* + + Update the worker table so other workers know a database is being restored + + */ + + UPDATE rw + SET rw.is_started = 1, + rw.is_completed = 0, + rw.last_log_restore_start_time = GETDATE() + FROM msdb.dbo.restore_worker rw + WHERE rw.database_name = @database; + END + + COMMIT; + END TRY - - + BEGIN CATCH + + /* + + Do I need to build retry logic in here? Try to catch deadlocks? I don't know yet! + + */ - - SELECT @error_number = ERROR_NUMBER(), - @error_severity = ERROR_SEVERITY(), - @error_state = ERROR_STATE(); - - SELECT @msg = N'Error updating backup configuration setting, error number is ' + CONVERT(NVARCHAR(10), ERROR_NUMBER()) + ', error message is ' + ERROR_MESSAGE(), + SELECT @msg = N'Error securing a database to restore, error number is ' + CONVERT(NVARCHAR(10), ERROR_NUMBER()) + ', error message is ' + ERROR_MESSAGE(), @error_severity = ERROR_SEVERITY(), @error_state = ERROR_STATE(); - RAISERROR(@msg, @error_severity, @error_state) WITH NOWAIT; - + SET @database = NULL; + + WHILE @@TRANCOUNT > 0 + ROLLBACK; + END CATCH; - END; + /* If we don't find a database to work on, wait for a few seconds */ + IF @database IS NULL - IF OBJECT_ID('msdb.dbo.restore_configuration') IS NOT NULL + BEGIN + IF @Debug = 1 RAISERROR('No databases to restore up right now, starting 3 second throttle', 0, 1) WITH NOWAIT; + WAITFOR DELAY '00:00:03.000'; - RAISERROR('Found restore config, checking variables...', 0, 1) WITH NOWAIT; + /* Check to make sure backup jobs aren't enabled */ + IF EXISTS ( + SELECT * + FROM msdb.dbo.sysjobs + WHERE name LIKE 'sp_AllNightLog_Backup%' + AND enabled = 1 + ) + BEGIN + RAISERROR('sp_AllNightLog_Backup jobs are enabled, so gracefully exiting. You do not want to accidentally do restores over top of the databases you are backing up.', 0, 1) WITH NOWAIT; + RETURN; + END - BEGIN + /* Check to make sure job is still enabled */ + IF NOT EXISTS ( + SELECT * + FROM msdb.dbo.sysjobs + WHERE name LIKE 'sp_AllNightLog_Restore%' + AND enabled = 1 + ) + BEGIN + RAISERROR('sp_AllNightLog_Restore jobs are disabled, so gracefully exiting. It feels graceful to me, anyway.', 0, 1) WITH NOWAIT; + RETURN; + END + END + + BEGIN TRY + + BEGIN + + IF @database IS NOT NULL - EXEC msdb.dbo.sp_update_schedule @name = ten_seconds, @active_start_date = @active_start_date, @active_start_time = 000000; - - IF @EnableRestoreJobs IS NOT NULL - BEGIN - RAISERROR('Changing restore job status based on @EnableBackupJobs parameter...', 0, 1) WITH NOWAIT; - INSERT INTO @jobs_to_change(name) - SELECT name - FROM msdb.dbo.sysjobs - WHERE name LIKE 'sp_AllNightLog_Restore%' OR name = 'sp_AllNightLog_PollDiskForNewDatabases'; - DECLARE jobs_cursor CURSOR FOR - SELECT name - FROM @jobs_to_change + /* + + Make sure we have a database to work on -- I should make this more robust so we do something if it is NULL, maybe + + */ - OPEN jobs_cursor - FETCH NEXT FROM jobs_cursor INTO @current_job_name + + BEGIN + + SET @msg = CASE WHEN @restore_full = 0 + THEN N'Restoring logs for ' + ELSE N'Restoring full backup for ' + END + + ISNULL(@database, 'UH OH NULL @database'); - WHILE @@FETCH_STATUS = 0 - BEGIN - RAISERROR(@current_job_name, 0, 1) WITH NOWAIT; - EXEC msdb.dbo.sp_update_job @job_name=@current_job_name,@enabled = @EnableRestoreJobs; - FETCH NEXT FROM jobs_cursor INTO @current_job_name - END + IF @Debug = 1 RAISERROR(@msg, 0, 1) WITH NOWAIT; - CLOSE jobs_cursor - DEALLOCATE jobs_cursor - DELETE @jobs_to_change; - END; + /* + + Call sp_DatabaseRestore to backup the database + + */ - /* If they wanted to turn off restore jobs, wait to make sure that finishes before we start enabling the backup jobs */ - IF @EnableRestoreJobs = 0 - BEGIN - SET @started_waiting_for_jobs = GETDATE(); - SELECT @counter = COUNT(*) - FROM [msdb].[dbo].[sysjobactivity] [ja] - INNER JOIN [msdb].[dbo].[sysjobs] [j] - ON [ja].[job_id] = [j].[job_id] - WHERE [ja].[session_id] = ( - SELECT TOP 1 [session_id] - FROM [msdb].[dbo].[syssessions] - ORDER BY [agent_start_date] DESC - ) - AND [start_execution_date] IS NOT NULL - AND [stop_execution_date] IS NULL - AND [j].[name] LIKE 'sp_AllNightLog_Restore%'; + SET @restore_path_full = @restore_path_base + N'\' + @database + N'\' + N'FULL\' + + SET @msg = N'Path for FULL backups for ' + @database + N' is ' + @restore_path_full + IF @Debug = 1 RAISERROR(@msg, 0, 1) WITH NOWAIT; - WHILE @counter > 0 - BEGIN - IF DATEADD(SS, 120, @started_waiting_for_jobs) < GETDATE() - BEGIN - RAISERROR('OH NOES! We waited 2 minutes and restore jobs are still running. We are stopping here - get a meatbag involved to figure out if restore jobs need to be killed, and the backup jobs will need to be enabled manually.', 16, 1) WITH NOWAIT; - RETURN - END - SET @msg = N'Waiting for ' + CAST(@counter AS NVARCHAR(100)) + N' sp_AllNightLog_Restore job(s) to finish.' - RAISERROR(@msg, 0, 1) WITH NOWAIT; - WAITFOR DELAY '0:00:01'; -- Wait until the restore jobs are fully stopped - - SELECT @counter = COUNT(*) - FROM [msdb].[dbo].[sysjobactivity] [ja] - INNER JOIN [msdb].[dbo].[sysjobs] [j] - ON [ja].[job_id] = [j].[job_id] - WHERE [ja].[session_id] = ( - SELECT TOP 1 [session_id] - FROM [msdb].[dbo].[syssessions] - ORDER BY [agent_start_date] DESC - ) - AND [start_execution_date] IS NOT NULL - AND [stop_execution_date] IS NULL - AND [j].[name] LIKE 'sp_AllNightLog_Restore%'; - END - END /* IF @EnableRestoreJobs = 0 */ + SET @restore_path_log = @restore_path_base + N'\' + @database + N'\' + N'LOG\' + SET @msg = N'Path for LOG backups for ' + @database + N' is ' + @restore_path_log + IF @Debug = 1 RAISERROR(@msg, 0, 1) WITH NOWAIT; - IF @EnableBackupJobs IS NOT NULL - BEGIN - RAISERROR('Changing backup job status based on @EnableBackupJobs parameter...', 0, 1) WITH NOWAIT; - INSERT INTO @jobs_to_change(name) - SELECT name - FROM msdb.dbo.sysjobs - WHERE name LIKE 'sp_AllNightLog_Backup%' OR name = 'sp_AllNightLog_PollForNewDatabases'; - DECLARE jobs_cursor CURSOR FOR - SELECT name - FROM @jobs_to_change + IF @restore_full = 0 - OPEN jobs_cursor - FETCH NEXT FROM jobs_cursor INTO @current_job_name + BEGIN - WHILE @@FETCH_STATUS = 0 - BEGIN - RAISERROR(@current_job_name, 0, 1) WITH NOWAIT; - EXEC msdb.dbo.sp_update_job @job_name=@current_job_name,@enabled = @EnableBackupJobs; - FETCH NEXT FROM jobs_cursor INTO @current_job_name - END + IF @Debug = 1 RAISERROR('Starting Log only restores', 0, 1) WITH NOWAIT; - CLOSE jobs_cursor - DEALLOCATE jobs_cursor - DELETE @jobs_to_change; - END; + EXEC dbo.sp_DatabaseRestore @Database = @database, + @BackupPathFull = @restore_path_full, + @BackupPathLog = @restore_path_log, + @ContinueLogs = 1, + @RunRecovery = 0, + @OnlyLogsAfter = @only_logs_after, + @MoveFiles = @restore_move_files, + @Debug = @Debug + + END + IF @restore_full = 1 - - IF @RTOSeconds IS NOT NULL + BEGIN - BEGIN + IF @Debug = 1 RAISERROR('Starting first Full restore from: ', 0, 1) WITH NOWAIT; + IF @Debug = 1 RAISERROR(@restore_path_full, 0, 1) WITH NOWAIT; - RAISERROR('Attempting to update RTO setting', 0, 1) WITH NOWAIT; + EXEC dbo.sp_DatabaseRestore @Database = @database, + @BackupPathFull = @restore_path_full, + @BackupPathLog = @restore_path_log, + @ContinueLogs = 0, + @RunRecovery = 0, + @MoveFiles = @restore_move_files, + @Debug = @Debug + + END - UPDATE c - SET c.configuration_setting = CONVERT(NVARCHAR(10), @RTOSeconds) - FROM msdb.dbo.restore_configuration AS c - WHERE c.configuration_name = N'log restore frequency'; - END; + + /* + + Catch any erroneous zones + + */ + + SELECT @error_number = ERROR_NUMBER(), + @error_severity = ERROR_SEVERITY(), + @error_state = ERROR_STATE(); + + END; --End call to dbo.sp_DatabaseRestore + + END; --End successful check of @database (not NULL) + + END TRY + + BEGIN CATCH + + IF @error_number IS NOT NULL + /* - IF @RestorePath IS NOT NULL + If the ERROR() function returns a number, update the table with it and the last error date. + + Also update the last start time to 1900-01-01 so it gets picked back up immediately -- the query to find a log restore to take sorts by start time + */ + BEGIN + + SET @msg = N'Error number is ' + CONVERT(NVARCHAR(10), ERROR_NUMBER()); + RAISERROR(@msg, @error_severity, @error_state) WITH NOWAIT; - RAISERROR('Attempting to update Restore Path setting', 0, 1) WITH NOWAIT; + SET @msg = N'Updating restore_worker for database ' + ISNULL(@database, 'UH OH NULL @database') + ' for unsuccessful backup'; + RAISERROR(@msg, 0, 1) WITH NOWAIT; + + + UPDATE rw + SET rw.is_started = 0, + rw.is_completed = 1, + rw.last_log_restore_start_time = '19000101', + rw.error_number = @error_number, + rw.last_error_date = GETDATE() + FROM msdb.dbo.restore_worker rw + WHERE rw.database_name = @database; - UPDATE c - SET c.configuration_setting = @RestorePath - FROM msdb.dbo.restore_configuration AS c - WHERE c.configuration_name = N'log restore path'; + /* + + Set @database back to NULL to avoid variable assignment weirdness + + */ - END; + SET @database = NULL; - END TRY + + /* + + Wait around for a second so we're not just spinning wheels -- this only runs if the BEGIN CATCH is triggered by an error + */ + + IF @Debug = 1 RAISERROR('Starting 1 second throttle', 0, 1) WITH NOWAIT; + + WAITFOR DELAY '00:00:01.000'; - BEGIN CATCH + END; -- End update of unsuccessful restore + + END CATCH; - SELECT @error_number = ERROR_NUMBER(), - @error_severity = ERROR_SEVERITY(), - @error_state = ERROR_STATE(); + IF @database IS NOT NULL AND @error_number IS NULL - SELECT @msg = N'Error updating restore configuration setting, error number is ' + CONVERT(NVARCHAR(10), ERROR_NUMBER()) + ', error message is ' + ERROR_MESSAGE(), - @error_severity = ERROR_SEVERITY(), - @error_state = ERROR_STATE(); + /* - RAISERROR(@msg, @error_severity, @error_state) WITH NOWAIT; + If no error, update everything normally + + */ + + BEGIN + + IF @Debug = 1 RAISERROR('Error number IS NULL', 0, 1) WITH NOWAIT; - END CATCH; + /* Make sure database actually exists and is in the restoring state */ + IF EXISTS (SELECT * FROM sys.databases WHERE name = @database AND state = 1) /* Restoring */ + BEGIN + SET @msg = N'Updating backup_worker for database ' + ISNULL(@database, 'UH OH NULL @database') + ' for successful backup'; + IF @Debug = 1 RAISERROR(@msg, 0, 1) WITH NOWAIT; + + UPDATE rw + SET rw.is_started = 0, + rw.is_completed = 1, + rw.last_log_restore_finish_time = GETDATE() + FROM msdb.dbo.restore_worker rw + WHERE rw.database_name = @database; - END; + END + ELSE /* The database doesn't exist, or it's not in the restoring state */ + BEGIN + SET @msg = N'Updating backup_worker for database ' + ISNULL(@database, 'UH OH NULL @database') + ' for UNsuccessful backup'; + IF @Debug = 1 RAISERROR(@msg, 0, 1) WITH NOWAIT; + + UPDATE rw + SET rw.is_started = 0, + rw.is_completed = 1, + rw.error_number = -1, /* unknown, human attention required */ + rw.last_error_date = GETDATE() + /* rw.last_log_restore_finish_time = GETDATE() don't change this - the last log may still be successful */ + FROM msdb.dbo.restore_worker rw + WHERE rw.database_name = @database; + END + + + + /* + + Set @database back to NULL to avoid variable assignment weirdness + + */ + + SET @database = NULL; - RAISERROR('Update complete!', 0, 1) WITH NOWAIT; + END; -- End update for successful backup + + END; -- End @Restore WHILE loop + + + END; -- End successful check for restore_worker and subsequent code + + + ELSE + + BEGIN + + RAISERROR('msdb.dbo.restore_worker does not exist, please run setup script', 0, 1) WITH NOWAIT; + RETURN; + + END; +RETURN; - END; --End updates to configuration table END; -- Final END for stored proc -GO +GO IF OBJECT_ID('dbo.sp_Blitz') IS NULL EXEC ('CREATE PROCEDURE dbo.sp_Blitz AS RETURN 0;'); GO @@ -2900,7 +2900,7 @@ AS SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.12', @VersionDate = '20221213'; + SELECT @Version = '8.13', @VersionDate = '20230215'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -2955,9 +2955,9 @@ AS tigertoolbox and are provided under the MIT license: https://github.com/Microsoft/tigertoolbox - All other copyrights for sp_Blitz are held by Brent Ozar Unlimited, 2021. + All other copyrights for sp_Blitz are held by Brent Ozar Unlimited. - Copyright (c) 2021 Brent Ozar Unlimited + Copyright (c) Brent Ozar Unlimited Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -12599,7 +12599,7 @@ AS SET NOCOUNT ON; SET STATISTICS XML OFF; -SELECT @Version = '8.12', @VersionDate = '20221213'; +SELECT @Version = '8.13', @VersionDate = '20230215'; IF(@VersionCheckMode = 1) BEGIN @@ -13477,7 +13477,7 @@ AS SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.12', @VersionDate = '20221213'; + SELECT @Version = '8.13', @VersionDate = '20230215'; IF(@VersionCheckMode = 1) BEGIN @@ -13524,7 +13524,7 @@ AS MIT License - Copyright (c) 2021 Brent Ozar Unlimited + Copyright (c) Brent Ozar Unlimited Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -15211,7 +15211,8 @@ CREATE TABLE ##BlitzCacheProcs ( cached_execution_parameters XML, missing_indexes XML, SetOptions VARCHAR(MAX), - Warnings VARCHAR(MAX) + Warnings VARCHAR(MAX), + Pattern NVARCHAR(20) ); GO @@ -15258,7 +15259,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.12', @VersionDate = '20221213'; +SELECT @Version = '8.13', @VersionDate = '20230215'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -15282,7 +15283,6 @@ IF @Help = 1 the findings, contribute your own code, and more. Known limitations of this version: - - This query will not run on SQL Server 2005. - SQL Server 2008 and 2008R2 have a bug in trigger stats, so that output is excluded by default. - @IgnoreQueryHashes and @OnlyQueryHashes require a CSV list of hashes @@ -15298,7 +15298,7 @@ IF @Help = 1 MIT License - Copyright (c) 2021 Brent Ozar Unlimited + Copyright (c) Brent Ozar Unlimited Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -16034,7 +16034,8 @@ BEGIN cached_execution_parameters XML, missing_indexes XML, SetOptions VARCHAR(MAX), - Warnings VARCHAR(MAX) + Warnings VARCHAR(MAX), + Pattern NVARCHAR(20) ); END; @@ -16135,18 +16136,18 @@ IF @SkipAnalysis = 1 DECLARE @AllSortSql NVARCHAR(MAX) = N''; DECLARE @VersionShowsMemoryGrants BIT; -IF EXISTS(SELECT * FROM sys.all_columns WHERE OBJECT_ID = OBJECT_ID('sys.dm_exec_query_stats') AND name = 'max_grant_kb') +IF EXISTS(SELECT * FROM sys.all_columns WHERE object_id = OBJECT_ID('sys.dm_exec_query_stats') AND name = 'max_grant_kb') SET @VersionShowsMemoryGrants = 1; ELSE SET @VersionShowsMemoryGrants = 0; DECLARE @VersionShowsSpills BIT; -IF EXISTS(SELECT * FROM sys.all_columns WHERE OBJECT_ID = OBJECT_ID('sys.dm_exec_query_stats') AND name = 'max_spills') +IF EXISTS(SELECT * FROM sys.all_columns WHERE object_id = OBJECT_ID('sys.dm_exec_query_stats') AND name = 'max_spills') SET @VersionShowsSpills = 1; ELSE SET @VersionShowsSpills = 0; -IF EXISTS(SELECT * FROM sys.all_columns WHERE OBJECT_ID = OBJECT_ID('sys.dm_exec_query_plan_stats') AND name = 'query_plan') +IF EXISTS(SELECT * FROM sys.all_columns WHERE object_id = OBJECT_ID('sys.dm_exec_query_plan_stats') AND name = 'query_plan') SET @VersionShowsAirQuoteActualPlans = 1; ELSE SET @VersionShowsAirQuoteActualPlans = 0; @@ -16953,7 +16954,7 @@ INSERT INTO ##BlitzCacheProcs (SPID, QueryType, DatabaseName, AverageCPU, TotalC LastReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, MaxUsedGrantKB, PercentMemoryGrantUsed, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, QueryText, QueryPlan, TotalWorkerTimeForType, TotalElapsedTimeForType, TotalReadsForType, TotalExecutionCountForType, TotalWritesForType, SqlHandle, PlanHandle, QueryHash, QueryPlanHash, - min_worker_time, max_worker_time, is_parallel, min_elapsed_time, max_elapsed_time, age_minutes, age_minutes_lifetime) ' ; + min_worker_time, max_worker_time, is_parallel, min_elapsed_time, max_elapsed_time, age_minutes, age_minutes_lifetime, Pattern) ' ; SET @body += N' FROM (SELECT TOP (@Top) x.*, xpa.*, @@ -17217,7 +17218,8 @@ SELECT TOP (@Top) qs.min_elapsed_time / 1000.0, qs.max_elapsed_time / 1000.0, age_minutes, - age_minutes_lifetime '; + age_minutes_lifetime, + @SortOrder '; IF LEFT(@QueryFilter, 3) IN ('all', 'sta') @@ -17365,7 +17367,8 @@ BEGIN qs.min_elapsed_time / 1000.0, qs.max_worker_time / 1000.0, age_minutes, - age_minutes_lifetime '; + age_minutes_lifetime, + @SortOrder '; SET @sql += REPLACE(REPLACE(@body, '#view#', 'dm_exec_query_stats'), 'cached_time', 'creation_time') ; @@ -17600,7 +17603,7 @@ IF @Reanalyze = 0 BEGIN RAISERROR('Collecting execution plan information.', 0, 1) WITH NOWAIT; - EXEC sp_executesql @sql, N'@Top INT, @min_duration INT, @min_back INT', @Top, @DurationFilter_i, @MinutesBack; + EXEC sp_executesql @sql, N'@Top INT, @min_duration INT, @min_back INT, @SortOrder NVARCHAR(20)', @Top, @DurationFilter_i, @MinutesBack, @SortOrder; END; IF @SkipAnalysis = 1 @@ -22093,6 +22096,7 @@ ELSE TotalSpills BIGINT, AvgSpills MONEY, QueryPlanCost FLOAT, + Pattern NVARCHAR(20), JoinKey AS ServerName + Cast(CheckDate AS NVARCHAR(50)), CONSTRAINT [PK_' + REPLACE(REPLACE(@OutputTableName,N'[',N''),N']',N'') + N'] PRIMARY KEY CLUSTERED(ID ASC));' ); @@ -22194,6 +22198,22 @@ END '; EXEC(@StringToExecute); END; + /* If the table doesn't have the new Pattern column, add it */ + SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableName; + SET @StringToExecute = N'IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns + WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''Pattern'') + ALTER TABLE ' + @ObjectFullName + N' ADD Pattern NVARCHAR(20) NULL;'; + IF @ValidOutputServer = 1 + BEGIN + SET @StringToExecute = REPLACE(@StringToExecute,'''Pattern''','''''Pattern'''''); + SET @StringToExecute = REPLACE(@StringToExecute,'''' + @ObjectFullName + '''','''''' + @ObjectFullName + ''''''); + EXEC('EXEC('''+@StringToExecute+''') AT ' + @OutputServerName); + END; + ELSE + BEGIN + EXEC(@StringToExecute); + END + IF @CheckDateOverride IS NULL BEGIN SET @CheckDateOverride = SYSDATETIMEOFFSET(); @@ -22213,14 +22233,14 @@ END '; + ' (ServerName, CheckDate, Version, QueryType, DatabaseName, AverageCPU, TotalCPU, PercentCPUByType, CPUWeight, AverageDuration, TotalDuration, DurationWeight, PercentDurationByType, AverageReads, TotalReads, ReadWeight, PercentReadsByType, ' + ' AverageWrites, TotalWrites, WriteWeight, PercentWritesByType, ExecutionCount, ExecutionWeight, PercentExecutionsByType, ' + ' ExecutionsPerMinute, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, QueryHash, QueryPlanHash, StatementStartOffset, StatementEndOffset, PlanGenerationNum, MinReturnedRows, MaxReturnedRows, AverageReturnedRows, TotalReturnedRows, QueryText, QueryPlan, NumberOfPlans, NumberOfDistinctPlans, Warnings, ' - + ' SerialRequiredMemory, SerialDesiredMemory, MinGrantKB, MaxGrantKB, MinUsedGrantKB, MaxUsedGrantKB, PercentMemoryGrantUsed, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, QueryPlanCost ) ' + + ' SerialRequiredMemory, SerialDesiredMemory, MinGrantKB, MaxGrantKB, MinUsedGrantKB, MaxUsedGrantKB, PercentMemoryGrantUsed, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, QueryPlanCost, Pattern ) ' + 'SELECT TOP (@Top) ' + QUOTENAME(CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128)), '''') + ', @CheckDateOverride, ' + QUOTENAME(CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)), '''') + ', ' + ' QueryType, DatabaseName, AverageCPU, TotalCPU, PercentCPUByType, PercentCPU, AverageDuration, TotalDuration, PercentDuration, PercentDurationByType, AverageReads, TotalReads, PercentReads, PercentReadsByType, ' + ' AverageWrites, TotalWrites, PercentWrites, PercentWritesByType, ExecutionCount, PercentExecutions, PercentExecutionsByType, ' + ' ExecutionsPerMinute, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, QueryHash, QueryPlanHash, StatementStartOffset, StatementEndOffset, PlanGenerationNum, MinReturnedRows, MaxReturnedRows, AverageReturnedRows, TotalReturnedRows, QueryText, CAST(QueryPlan AS NVARCHAR(MAX)), NumberOfPlans, NumberOfDistinctPlans, Warnings, ' - + ' SerialRequiredMemory, SerialDesiredMemory, MinGrantKB, MaxGrantKB, MinUsedGrantKB, MaxUsedGrantKB, PercentMemoryGrantUsed, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, QueryPlanCost ' + + ' SerialRequiredMemory, SerialDesiredMemory, MinGrantKB, MaxGrantKB, MinUsedGrantKB, MaxUsedGrantKB, PercentMemoryGrantUsed, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, QueryPlanCost, Pattern ' + ' FROM ##BlitzCacheProcs ' + ' WHERE 1=1 '; @@ -22281,14 +22301,14 @@ END '; + ' (ServerName, CheckDate, Version, QueryType, DatabaseName, AverageCPU, TotalCPU, PercentCPUByType, CPUWeight, AverageDuration, TotalDuration, DurationWeight, PercentDurationByType, AverageReads, TotalReads, ReadWeight, PercentReadsByType, ' + ' AverageWrites, TotalWrites, WriteWeight, PercentWritesByType, ExecutionCount, ExecutionWeight, PercentExecutionsByType, ' + ' ExecutionsPerMinute, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, QueryHash, QueryPlanHash, StatementStartOffset, StatementEndOffset, PlanGenerationNum, MinReturnedRows, MaxReturnedRows, AverageReturnedRows, TotalReturnedRows, QueryText, QueryPlan, NumberOfPlans, NumberOfDistinctPlans, Warnings, ' - + ' SerialRequiredMemory, SerialDesiredMemory, MinGrantKB, MaxGrantKB, MinUsedGrantKB, MaxUsedGrantKB, PercentMemoryGrantUsed, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, QueryPlanCost ) ' + + ' SerialRequiredMemory, SerialDesiredMemory, MinGrantKB, MaxGrantKB, MinUsedGrantKB, MaxUsedGrantKB, PercentMemoryGrantUsed, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, QueryPlanCost, Pattern ) ' + 'SELECT TOP (@Top) ' + QUOTENAME(CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128)), '''') + ', @CheckDateOverride, ' + QUOTENAME(CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)), '''') + ', ' + ' QueryType, DatabaseName, AverageCPU, TotalCPU, PercentCPUByType, PercentCPU, AverageDuration, TotalDuration, PercentDuration, PercentDurationByType, AverageReads, TotalReads, PercentReads, PercentReadsByType, ' + ' AverageWrites, TotalWrites, PercentWrites, PercentWritesByType, ExecutionCount, PercentExecutions, PercentExecutionsByType, ' + ' ExecutionsPerMinute, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, QueryHash, QueryPlanHash, StatementStartOffset, StatementEndOffset, PlanGenerationNum, MinReturnedRows, MaxReturnedRows, AverageReturnedRows, TotalReturnedRows, QueryText, QueryPlan, NumberOfPlans, NumberOfDistinctPlans, Warnings, ' - + ' SerialRequiredMemory, SerialDesiredMemory, MinGrantKB, MaxGrantKB, MinUsedGrantKB, MaxUsedGrantKB, PercentMemoryGrantUsed, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, QueryPlanCost ' + + ' SerialRequiredMemory, SerialDesiredMemory, MinGrantKB, MaxGrantKB, MinUsedGrantKB, MaxUsedGrantKB, PercentMemoryGrantUsed, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, QueryPlanCost, Pattern ' + ' FROM ##BlitzCacheProcs ' + ' WHERE 1=1 '; @@ -22430,6 +22450,7 @@ END '; TotalSpills BIGINT, AvgSpills MONEY, QueryPlanCost FLOAT, + Pattern NVARCHAR(20), JoinKey AS ServerName + Cast(CheckDate AS NVARCHAR(50)), CONSTRAINT [PK_' + REPLACE(REPLACE(@OutputTableName,'[',''),']','') + '] PRIMARY KEY CLUSTERED(ID ASC));'; SET @StringToExecute += N' INSERT ' @@ -22437,14 +22458,14 @@ END '; + ' (ServerName, CheckDate, Version, QueryType, DatabaseName, AverageCPU, TotalCPU, PercentCPUByType, CPUWeight, AverageDuration, TotalDuration, DurationWeight, PercentDurationByType, AverageReads, TotalReads, ReadWeight, PercentReadsByType, ' + ' AverageWrites, TotalWrites, WriteWeight, PercentWritesByType, ExecutionCount, ExecutionWeight, PercentExecutionsByType, ' + ' ExecutionsPerMinute, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, QueryHash, QueryPlanHash, StatementStartOffset, StatementEndOffset, PlanGenerationNum, MinReturnedRows, MaxReturnedRows, AverageReturnedRows, TotalReturnedRows, QueryText, QueryPlan, NumberOfPlans, NumberOfDistinctPlans, Warnings, ' - + ' SerialRequiredMemory, SerialDesiredMemory, MinGrantKB, MaxGrantKB, MinUsedGrantKB, MaxUsedGrantKB, PercentMemoryGrantUsed, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, QueryPlanCost ) ' + + ' SerialRequiredMemory, SerialDesiredMemory, MinGrantKB, MaxGrantKB, MinUsedGrantKB, MaxUsedGrantKB, PercentMemoryGrantUsed, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, QueryPlanCost, Pattern ) ' + 'SELECT TOP (@Top) ' + QUOTENAME(CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128)), '''') + ', @CheckDateOverride, ' + QUOTENAME(CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)), '''') + ', ' + ' QueryType, DatabaseName, AverageCPU, TotalCPU, PercentCPUByType, PercentCPU, AverageDuration, TotalDuration, PercentDuration, PercentDurationByType, AverageReads, TotalReads, PercentReads, PercentReadsByType, ' + ' AverageWrites, TotalWrites, PercentWrites, PercentWritesByType, ExecutionCount, PercentExecutions, PercentExecutionsByType, ' + ' ExecutionsPerMinute, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, QueryHash, QueryPlanHash, StatementStartOffset, StatementEndOffset, PlanGenerationNum, MinReturnedRows, MaxReturnedRows, AverageReturnedRows, TotalReturnedRows, QueryText, QueryPlan, NumberOfPlans, NumberOfDistinctPlans, Warnings, ' - + ' SerialRequiredMemory, SerialDesiredMemory, MinGrantKB, MaxGrantKB, MinUsedGrantKB, MaxUsedGrantKB, PercentMemoryGrantUsed, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, QueryPlanCost ' + + ' SerialRequiredMemory, SerialDesiredMemory, MinGrantKB, MaxGrantKB, MinUsedGrantKB, MaxUsedGrantKB, PercentMemoryGrantUsed, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, QueryPlanCost, Pattern ' + ' FROM ##BlitzCacheProcs ' + ' WHERE 1=1 '; @@ -22556,7 +22577,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.12', @VersionDate = '20221213'; +SELECT @Version = '8.13', @VersionDate = '20230215'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -22593,7 +22614,7 @@ Unknown limitations of this version: MIT License -Copyright (c) 2021 Brent Ozar Unlimited +Copyright (c) Brent Ozar Unlimited Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -28733,7 +28754,7 @@ BEGIN SET NOCOUNT, XACT_ABORT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.12', @VersionDate = '20221213'; + SELECT @Version = '8.13', @VersionDate = '20230215'; IF @VersionCheckMode = 1 BEGIN @@ -28794,7 +28815,7 @@ BEGIN MIT License - Copyright (c) 2022 Brent Ozar Unlimited + Copyright (c) Brent Ozar Unlimited Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -29867,7 +29888,7 @@ BEGIN waiter_mode = w.l.value('@mode', 'nvarchar(256)'), owner_id = o.l.value('@id', 'nvarchar(256)'), owner_mode = o.l.value('@mode', 'nvarchar(256)'), - lock_type = N'OBJECT' + lock_type = CAST(N'OBJECT' AS NVARCHAR(100)) INTO #deadlock_owner_waiter FROM ( @@ -30492,7 +30513,7 @@ BEGIN N'S', N'IS' ) - AND (dow.database_id = @DatabaseName OR @DatabaseName IS NULL) + AND (dow.database_id = @DatabaseId OR @DatabaseName IS NULL) AND (dow.event_date >= @StartDate OR @StartDate IS NULL) AND (dow.event_date < @EndDate OR @EndDate IS NULL) AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) @@ -30535,7 +30556,7 @@ BEGIN N' deadlock(s).' FROM #deadlock_owner_waiter AS dow WHERE 1 = 1 - AND (dow.database_id = @DatabaseName OR @DatabaseName IS NULL) + AND (dow.database_id = @DatabaseId OR @DatabaseName IS NULL) AND (dow.event_date >= @StartDate OR @StartDate IS NULL) AND (dow.event_date < @EndDate OR @EndDate IS NULL) AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) @@ -30574,7 +30595,7 @@ BEGIN N' deadlock(s).' FROM #deadlock_owner_waiter AS dow WHERE 1 = 1 - AND (dow.database_id = @DatabaseName OR @DatabaseName IS NULL) + AND (dow.database_id = @DatabaseId OR @DatabaseName IS NULL) AND (dow.event_date >= @StartDate OR @StartDate IS NULL) AND (dow.event_date < @EndDate OR @EndDate IS NULL) AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) @@ -30619,7 +30640,7 @@ BEGIN N' deadlock(s).' FROM #deadlock_owner_waiter AS dow WHERE 1 = 1 - AND (dow.database_id = @DatabaseName OR @DatabaseName IS NULL) + AND (dow.database_id = @DatabaseId OR @DatabaseName IS NULL) AND (dow.event_date >= @StartDate OR @StartDate IS NULL) AND (dow.event_date < @EndDate OR @EndDate IS NULL) AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) @@ -30945,7 +30966,7 @@ BEGIN ON dow.owner_id = ds.id AND dow.event_date = ds.event_date WHERE 1 = 1 - AND (dow.database_id = @DatabaseName OR @DatabaseName IS NULL) + AND (dow.database_id = @DatabaseId OR @DatabaseName IS NULL) AND (dow.event_date >= @StartDate OR @StartDate IS NULL) AND (dow.event_date < @EndDate OR @EndDate IS NULL) AND (dow.object_name = @StoredProcName OR @StoredProcName IS NULL) @@ -31001,7 +31022,7 @@ BEGIN ON dow.owner_id = ds.id AND dow.event_date = ds.event_date WHERE ds.proc_name <> N'adhoc' - AND (dow.database_id = @DatabaseName OR @DatabaseName IS NULL) + AND (dow.database_id = @DatabaseId OR @DatabaseName IS NULL) AND (dow.event_date >= @StartDate OR @StartDate IS NULL) AND (dow.event_date < @EndDate OR @EndDate IS NULL) AND (dow.object_name = @StoredProcName OR @StoredProcName IS NULL) @@ -31077,7 +31098,7 @@ BEGIN ON s.database_id = dow.database_id AND s.partition_id = dow.associatedObjectId WHERE 1 = 1 - AND (dow.database_id = @DatabaseName OR @DatabaseName IS NULL) + AND (dow.database_id = @DatabaseId OR @DatabaseName IS NULL) AND (dow.event_date >= @StartDate OR @StartDate IS NULL) AND (dow.event_date < @EndDate OR @EndDate IS NULL) AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) @@ -31139,6 +31160,76 @@ BEGIN ) ), wait_time_hms = + /*the more wait time you rack up the less accurate this gets, + it's either that or erroring out*/ + CASE + WHEN + SUM + ( + CONVERT + ( + bigint, + dp.wait_time + ) + )/1000 > 2147483647 + THEN + CONVERT + ( + nvarchar(30), + DATEADD + ( + MINUTE, + ( + ( + SUM + ( + CONVERT + ( + bigint, + dp.wait_time + ) + ) + )/ + 60000 + ), + 0 + ), + 14 + ) + WHEN + SUM + ( + CONVERT + ( + bigint, + dp.wait_time + ) + ) BETWEEN 2147483648 AND 2147483647000 + THEN + CONVERT + ( + nvarchar(30), + DATEADD + ( + SECOND, + ( + ( + SUM + ( + CONVERT + ( + bigint, + dp.wait_time + ) + ) + )/ + 1000 + ), + 0 + ), + 14 + ) + ELSE CONVERT ( nvarchar(30), @@ -31159,6 +31250,7 @@ BEGIN ), 14 ) + END FROM #deadlock_owner_waiter AS dow JOIN #deadlock_process AS dp ON (dp.id = dow.owner_id @@ -31275,6 +31367,76 @@ BEGIN ) ) + N' ' + + /*the more wait time you rack up the less accurate this gets, + it's either that or erroring out*/ + CASE + WHEN + SUM + ( + CONVERT + ( + bigint, + wt.total_wait_time_ms + ) + )/1000 > 2147483647 + THEN + CONVERT + ( + nvarchar(30), + DATEADD + ( + MINUTE, + ( + ( + SUM + ( + CONVERT + ( + bigint, + wt.total_wait_time_ms + ) + ) + )/ + 60000 + ), + 0 + ), + 14 + ) + WHEN + SUM + ( + CONVERT + ( + bigint, + wt.total_wait_time_ms + ) + ) BETWEEN 2147483648 AND 2147483647000 + THEN + CONVERT + ( + nvarchar(30), + DATEADD + ( + SECOND, + ( + ( + SUM + ( + CONVERT + ( + bigint, + wt.total_wait_time_ms + ) + ) + )/ + 1000 + ), + 0 + ), + 14 + ) + ELSE CONVERT ( nvarchar(30), @@ -31294,7 +31456,7 @@ BEGIN 0 ), 14 - ) + + ) END + N' [dd hh:mm:ss:ms] of deadlock wait time.' FROM wait_time AS wt GROUP BY @@ -32240,7 +32402,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.12', @VersionDate = '20221213'; +SELECT @Version = '8.13', @VersionDate = '20230215'; IF(@VersionCheckMode = 1) BEGIN RETURN; @@ -32326,7 +32488,7 @@ IF @Help = 1 MIT License - Copyright (c) 2021 Brent Ozar Unlimited + Copyright (c) Brent Ozar Unlimited Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -37971,7 +38133,7 @@ BEGIN SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.12', @VersionDate = '20221213'; + SELECT @Version = '8.13', @VersionDate = '20230215'; IF(@VersionCheckMode = 1) BEGIN @@ -37999,7 +38161,7 @@ Known limitations of this version: MIT License -Copyright (c) 2021 Brent Ozar Unlimited +Copyright (c) Brent Ozar Unlimited Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -38628,11 +38790,11 @@ BEGIN END AS blocking_session_id, COALESCE(DB_NAME(r.database_id), DB_NAME(blocked.dbid), ''N/A'') AS database_name, ISNULL(SUBSTRING(dest.text, - ( query_stats.statement_start_offset / 2 ) + 1, - ( ( CASE query_stats.statement_end_offset + ( r.statement_start_offset / 2 ) + 1, + ( ( CASE r.statement_end_offset WHEN -1 THEN DATALENGTH(dest.text) - ELSE query_stats.statement_end_offset - END - query_stats.statement_start_offset ) + ELSE r.statement_end_offset + END - r.statement_start_offset ) / 2 ) + 1), dest.text) AS query_text , '+CASE WHEN @GetOuterCommand = 1 THEN N'CAST(event_info AS NVARCHAR(4000)) AS outer_command,' @@ -38797,7 +38959,7 @@ BEGIN OUTER APPLY sys.dm_exec_sql_text(COALESCE(r.sql_handle, blocked.sql_handle)) AS dest OUTER APPLY sys.dm_exec_query_plan(r.plan_handle) AS derp OUTER APPLY ( - SELECT CONVERT(DECIMAL(38,2), SUM( (((tsu.user_objects_alloc_page_count - user_objects_dealloc_page_count) * 8) / 1024.)) ) AS tempdb_allocations_mb + SELECT CONVERT(DECIMAL(38,2), SUM( ((((tsu.user_objects_alloc_page_count - user_objects_dealloc_page_count) + (tsu.internal_objects_alloc_page_count - internal_objects_dealloc_page_count)) * 8) / 1024.)) ) AS tempdb_allocations_mb FROM sys.dm_db_task_space_usage tsu WHERE tsu.request_id = r.request_id AND tsu.session_id = r.session_id @@ -38846,11 +39008,11 @@ IF @ProductVersionMajor >= 11 END AS blocking_session_id, COALESCE(DB_NAME(r.database_id), DB_NAME(blocked.dbid), ''N/A'') AS database_name, ISNULL(SUBSTRING(dest.text, - ( query_stats.statement_start_offset / 2 ) + 1, - ( ( CASE query_stats.statement_end_offset + ( r.statement_start_offset / 2 ) + 1, + ( ( CASE r.statement_end_offset WHEN -1 THEN DATALENGTH(dest.text) - ELSE query_stats.statement_end_offset - END - query_stats.statement_start_offset ) + ELSE r.statement_end_offset + END - r.statement_start_offset ) / 2 ) + 1), dest.text) AS query_text , '+CASE WHEN @GetOuterCommand = 1 THEN N'CAST(event_info AS NVARCHAR(4000)) AS outer_command,' @@ -39090,7 +39252,7 @@ IF @ProductVersionMajor >= 11 OUTER APPLY sys.dm_exec_sql_text(COALESCE(r.sql_handle, blocked.sql_handle)) AS dest OUTER APPLY sys.dm_exec_query_plan(r.plan_handle) AS derp OUTER APPLY ( - SELECT CONVERT(DECIMAL(38,2), SUM( (((tsu.user_objects_alloc_page_count - user_objects_dealloc_page_count) * 8) / 1024.)) ) AS tempdb_allocations_mb + SELECT CONVERT(DECIMAL(38,2), SUM( ((((tsu.user_objects_alloc_page_count - user_objects_dealloc_page_count) + (tsu.internal_objects_alloc_page_count - internal_objects_dealloc_page_count)) * 8) / 1024.)) ) AS tempdb_allocations_mb FROM sys.dm_db_task_space_usage tsu WHERE tsu.request_id = r.request_id AND tsu.session_id = r.session_id @@ -39368,7 +39530,7 @@ SET STATISTICS XML OFF; /*Versioning details*/ -SELECT @Version = '8.12', @VersionDate = '20221213'; +SELECT @Version = '8.13', @VersionDate = '20230215'; IF(@VersionCheckMode = 1) BEGIN @@ -39400,7 +39562,7 @@ BEGIN MIT License - Copyright (c) 2021 Brent Ozar Unlimited + Copyright (c) Brent Ozar Unlimited Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -40944,7 +41106,7 @@ BEGIN SET NOCOUNT ON; SET STATISTICS XML OFF; - SELECT @Version = '8.12', @VersionDate = '20221213'; + SELECT @Version = '8.13', @VersionDate = '20230215'; IF(@VersionCheckMode = 1) BEGIN @@ -40977,7 +41139,7 @@ BEGIN MIT License - Copyright (c) 2021 Brent Ozar Unlimited + Copyright (c) Brent Ozar Unlimited Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -41290,7 +41452,9 @@ DELETE FROM dbo.SqlServerVersions; INSERT INTO dbo.SqlServerVersions (MajorVersionNumber, MinorVersionNumber, Branch, [Url], ReleaseDate, MainstreamSupportEndDate, ExtendedSupportEndDate, MajorVersionName, MinorVersionName) 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'), + (15, 4280, 'CU18 GDR', 'https://support.microsoft.com/kb/5021124', '2023-02-14', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 18 GDR'), (15, 4261, 'CU18', 'https://support.microsoft.com/en-us/help/5017593', '2022-09-28', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 18'), (15, 4249, 'CU17', 'https://support.microsoft.com/en-us/help/5016394', '2022-08-11', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 17'), (15, 4236, 'CU16 GDR', 'https://support.microsoft.com/en-us/help/5014353', '2022-06-14', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 16 GDR'), @@ -41313,6 +41477,7 @@ VALUES (15, 4003, 'CU1', 'https://support.microsoft.com/en-us/help/4527376', '2020-01-07', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 1 '), (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 '), + (14, 3460, 'RTM CU31 GDR', 'https://support.microsoft.com/kb/5021126', '2023-02-14', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 31 GDR'), (14, 3456, 'RTM CU31', 'https://support.microsoft.com/en-us/help/5016884', '2022-09-20', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 31'), (14, 3451, 'RTM CU30', 'https://support.microsoft.com/en-us/help/5013756', '2022-07-13', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 30'), (14, 3445, 'RTM CU29 GDR', 'https://support.microsoft.com/en-us/help/5014553', '2022-06-14', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 29 GDR'), @@ -41349,6 +41514,7 @@ VALUES (14, 1000, 'RTM ', '', '2017-10-02', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM '), (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, 6430, 'SP3 GDR', 'https://support.microsoft.com/kb/5021129', '2023-02-14', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3 GDR'), (13, 6419, 'SP3 GDR', 'https://support.microsoft.com/en-us/help/5014355', '2022-06-14', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3 GDR'), (13, 6404, 'SP3 GDR', 'https://support.microsoft.com/en-us/help/5006943', '2021-10-27', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3 GDR'), (13, 6300, 'SP3 ', 'https://support.microsoft.com/en-us/help/5003279', '2021-09-15', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3'), @@ -41400,6 +41566,7 @@ VALUES (13, 2164, 'RTM CU2', 'https://support.microsoft.com/en-us/help/3182270 ', '2016-09-22', '2018-01-09', '2018-01-09', 'SQL Server 2016', 'RTM Cumulative Update 2'), (13, 2149, 'RTM CU1', 'https://support.microsoft.com/en-us/help/3164674 ', '2016-07-25', '2018-01-09', '2018-01-09', 'SQL Server 2016', 'RTM Cumulative Update 1'), (13, 1601, 'RTM ', '', '2016-06-01', '2019-01-09', '2019-01-09', 'SQL Server 2016', 'RTM '), + (12, 6444, 'SP3 CU4 GDR', 'https://support.microsoft.com/kb/5021045', '2023-02-14', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 4 GDR'), (12, 6439, 'SP3 CU4 GDR', 'https://support.microsoft.com/en-us/help/5014164', '2022-06-14', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 4 GDR'), (12, 6433, 'SP3 CU4 GDR', 'https://support.microsoft.com/en-us/help/4583462', '2021-01-12', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 4 GDR'), (12, 6372, 'SP3 CU4 GDR', 'https://support.microsoft.com/en-us/help/4535288', '2020-02-11', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 4 GDR'), @@ -41706,7 +41873,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.12', @VersionDate = '20221213'; +SELECT @Version = '8.13', @VersionDate = '20230215'; IF(@VersionCheckMode = 1) BEGIN @@ -41746,7 +41913,7 @@ https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/ MIT License -Copyright (c) 2021 Brent Ozar Unlimited +Copyright (c) Brent Ozar Unlimited Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -43953,8 +44120,9 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, CROSS APPLY sys.dm_exec_query_plan(r.plan_handle) AS qp '; IF EXISTS (SELECT * FROM sys.all_objects WHERE name = 'dm_exec_query_statistics_xml') - SET @StringToExecute = @StringToExecute + N' OUTER APPLY sys.dm_exec_query_statistics_xml(s.session_id) qs_live '; - + /* GitHub #3210 */ + SET @StringToExecute = N' + SET LOCK_TIMEOUT 1000 ' + @StringToExecute + N' OUTER APPLY sys.dm_exec_query_statistics_xml(s.session_id) qs_live '; SET @StringToExecute = @StringToExecute + N'; diff --git a/Install-Core-Blitz-No-Query-Store.sql b/Install-Core-Blitz-No-Query-Store.sql index 9529b2a44..448bde00e 100644 --- a/Install-Core-Blitz-No-Query-Store.sql +++ b/Install-Core-Blitz-No-Query-Store.sql @@ -38,7 +38,7 @@ AS SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.12', @VersionDate = '20221213'; + SELECT @Version = '8.13', @VersionDate = '20230215'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -93,9 +93,9 @@ AS tigertoolbox and are provided under the MIT license: https://github.com/Microsoft/tigertoolbox - All other copyrights for sp_Blitz are held by Brent Ozar Unlimited, 2021. + All other copyrights for sp_Blitz are held by Brent Ozar Unlimited. - Copyright (c) 2021 Brent Ozar Unlimited + Copyright (c) Brent Ozar Unlimited Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -9737,7 +9737,7 @@ AS SET NOCOUNT ON; SET STATISTICS XML OFF; -SELECT @Version = '8.12', @VersionDate = '20221213'; +SELECT @Version = '8.13', @VersionDate = '20230215'; IF(@VersionCheckMode = 1) BEGIN @@ -10615,7 +10615,7 @@ AS SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.12', @VersionDate = '20221213'; + SELECT @Version = '8.13', @VersionDate = '20230215'; IF(@VersionCheckMode = 1) BEGIN @@ -10662,7 +10662,7 @@ AS MIT License - Copyright (c) 2021 Brent Ozar Unlimited + Copyright (c) Brent Ozar Unlimited Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -12349,7 +12349,8 @@ CREATE TABLE ##BlitzCacheProcs ( cached_execution_parameters XML, missing_indexes XML, SetOptions VARCHAR(MAX), - Warnings VARCHAR(MAX) + Warnings VARCHAR(MAX), + Pattern NVARCHAR(20) ); GO @@ -12396,7 +12397,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.12', @VersionDate = '20221213'; +SELECT @Version = '8.13', @VersionDate = '20230215'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -12420,7 +12421,6 @@ IF @Help = 1 the findings, contribute your own code, and more. Known limitations of this version: - - This query will not run on SQL Server 2005. - SQL Server 2008 and 2008R2 have a bug in trigger stats, so that output is excluded by default. - @IgnoreQueryHashes and @OnlyQueryHashes require a CSV list of hashes @@ -12436,7 +12436,7 @@ IF @Help = 1 MIT License - Copyright (c) 2021 Brent Ozar Unlimited + Copyright (c) Brent Ozar Unlimited Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -13172,7 +13172,8 @@ BEGIN cached_execution_parameters XML, missing_indexes XML, SetOptions VARCHAR(MAX), - Warnings VARCHAR(MAX) + Warnings VARCHAR(MAX), + Pattern NVARCHAR(20) ); END; @@ -13273,18 +13274,18 @@ IF @SkipAnalysis = 1 DECLARE @AllSortSql NVARCHAR(MAX) = N''; DECLARE @VersionShowsMemoryGrants BIT; -IF EXISTS(SELECT * FROM sys.all_columns WHERE OBJECT_ID = OBJECT_ID('sys.dm_exec_query_stats') AND name = 'max_grant_kb') +IF EXISTS(SELECT * FROM sys.all_columns WHERE object_id = OBJECT_ID('sys.dm_exec_query_stats') AND name = 'max_grant_kb') SET @VersionShowsMemoryGrants = 1; ELSE SET @VersionShowsMemoryGrants = 0; DECLARE @VersionShowsSpills BIT; -IF EXISTS(SELECT * FROM sys.all_columns WHERE OBJECT_ID = OBJECT_ID('sys.dm_exec_query_stats') AND name = 'max_spills') +IF EXISTS(SELECT * FROM sys.all_columns WHERE object_id = OBJECT_ID('sys.dm_exec_query_stats') AND name = 'max_spills') SET @VersionShowsSpills = 1; ELSE SET @VersionShowsSpills = 0; -IF EXISTS(SELECT * FROM sys.all_columns WHERE OBJECT_ID = OBJECT_ID('sys.dm_exec_query_plan_stats') AND name = 'query_plan') +IF EXISTS(SELECT * FROM sys.all_columns WHERE object_id = OBJECT_ID('sys.dm_exec_query_plan_stats') AND name = 'query_plan') SET @VersionShowsAirQuoteActualPlans = 1; ELSE SET @VersionShowsAirQuoteActualPlans = 0; @@ -14091,7 +14092,7 @@ INSERT INTO ##BlitzCacheProcs (SPID, QueryType, DatabaseName, AverageCPU, TotalC LastReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, MaxUsedGrantKB, PercentMemoryGrantUsed, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, QueryText, QueryPlan, TotalWorkerTimeForType, TotalElapsedTimeForType, TotalReadsForType, TotalExecutionCountForType, TotalWritesForType, SqlHandle, PlanHandle, QueryHash, QueryPlanHash, - min_worker_time, max_worker_time, is_parallel, min_elapsed_time, max_elapsed_time, age_minutes, age_minutes_lifetime) ' ; + min_worker_time, max_worker_time, is_parallel, min_elapsed_time, max_elapsed_time, age_minutes, age_minutes_lifetime, Pattern) ' ; SET @body += N' FROM (SELECT TOP (@Top) x.*, xpa.*, @@ -14355,7 +14356,8 @@ SELECT TOP (@Top) qs.min_elapsed_time / 1000.0, qs.max_elapsed_time / 1000.0, age_minutes, - age_minutes_lifetime '; + age_minutes_lifetime, + @SortOrder '; IF LEFT(@QueryFilter, 3) IN ('all', 'sta') @@ -14503,7 +14505,8 @@ BEGIN qs.min_elapsed_time / 1000.0, qs.max_worker_time / 1000.0, age_minutes, - age_minutes_lifetime '; + age_minutes_lifetime, + @SortOrder '; SET @sql += REPLACE(REPLACE(@body, '#view#', 'dm_exec_query_stats'), 'cached_time', 'creation_time') ; @@ -14738,7 +14741,7 @@ IF @Reanalyze = 0 BEGIN RAISERROR('Collecting execution plan information.', 0, 1) WITH NOWAIT; - EXEC sp_executesql @sql, N'@Top INT, @min_duration INT, @min_back INT', @Top, @DurationFilter_i, @MinutesBack; + EXEC sp_executesql @sql, N'@Top INT, @min_duration INT, @min_back INT, @SortOrder NVARCHAR(20)', @Top, @DurationFilter_i, @MinutesBack, @SortOrder; END; IF @SkipAnalysis = 1 @@ -19231,6 +19234,7 @@ ELSE TotalSpills BIGINT, AvgSpills MONEY, QueryPlanCost FLOAT, + Pattern NVARCHAR(20), JoinKey AS ServerName + Cast(CheckDate AS NVARCHAR(50)), CONSTRAINT [PK_' + REPLACE(REPLACE(@OutputTableName,N'[',N''),N']',N'') + N'] PRIMARY KEY CLUSTERED(ID ASC));' ); @@ -19332,6 +19336,22 @@ END '; EXEC(@StringToExecute); END; + /* If the table doesn't have the new Pattern column, add it */ + SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableName; + SET @StringToExecute = N'IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns + WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''Pattern'') + ALTER TABLE ' + @ObjectFullName + N' ADD Pattern NVARCHAR(20) NULL;'; + IF @ValidOutputServer = 1 + BEGIN + SET @StringToExecute = REPLACE(@StringToExecute,'''Pattern''','''''Pattern'''''); + SET @StringToExecute = REPLACE(@StringToExecute,'''' + @ObjectFullName + '''','''''' + @ObjectFullName + ''''''); + EXEC('EXEC('''+@StringToExecute+''') AT ' + @OutputServerName); + END; + ELSE + BEGIN + EXEC(@StringToExecute); + END + IF @CheckDateOverride IS NULL BEGIN SET @CheckDateOverride = SYSDATETIMEOFFSET(); @@ -19351,14 +19371,14 @@ END '; + ' (ServerName, CheckDate, Version, QueryType, DatabaseName, AverageCPU, TotalCPU, PercentCPUByType, CPUWeight, AverageDuration, TotalDuration, DurationWeight, PercentDurationByType, AverageReads, TotalReads, ReadWeight, PercentReadsByType, ' + ' AverageWrites, TotalWrites, WriteWeight, PercentWritesByType, ExecutionCount, ExecutionWeight, PercentExecutionsByType, ' + ' ExecutionsPerMinute, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, QueryHash, QueryPlanHash, StatementStartOffset, StatementEndOffset, PlanGenerationNum, MinReturnedRows, MaxReturnedRows, AverageReturnedRows, TotalReturnedRows, QueryText, QueryPlan, NumberOfPlans, NumberOfDistinctPlans, Warnings, ' - + ' SerialRequiredMemory, SerialDesiredMemory, MinGrantKB, MaxGrantKB, MinUsedGrantKB, MaxUsedGrantKB, PercentMemoryGrantUsed, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, QueryPlanCost ) ' + + ' SerialRequiredMemory, SerialDesiredMemory, MinGrantKB, MaxGrantKB, MinUsedGrantKB, MaxUsedGrantKB, PercentMemoryGrantUsed, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, QueryPlanCost, Pattern ) ' + 'SELECT TOP (@Top) ' + QUOTENAME(CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128)), '''') + ', @CheckDateOverride, ' + QUOTENAME(CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)), '''') + ', ' + ' QueryType, DatabaseName, AverageCPU, TotalCPU, PercentCPUByType, PercentCPU, AverageDuration, TotalDuration, PercentDuration, PercentDurationByType, AverageReads, TotalReads, PercentReads, PercentReadsByType, ' + ' AverageWrites, TotalWrites, PercentWrites, PercentWritesByType, ExecutionCount, PercentExecutions, PercentExecutionsByType, ' + ' ExecutionsPerMinute, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, QueryHash, QueryPlanHash, StatementStartOffset, StatementEndOffset, PlanGenerationNum, MinReturnedRows, MaxReturnedRows, AverageReturnedRows, TotalReturnedRows, QueryText, CAST(QueryPlan AS NVARCHAR(MAX)), NumberOfPlans, NumberOfDistinctPlans, Warnings, ' - + ' SerialRequiredMemory, SerialDesiredMemory, MinGrantKB, MaxGrantKB, MinUsedGrantKB, MaxUsedGrantKB, PercentMemoryGrantUsed, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, QueryPlanCost ' + + ' SerialRequiredMemory, SerialDesiredMemory, MinGrantKB, MaxGrantKB, MinUsedGrantKB, MaxUsedGrantKB, PercentMemoryGrantUsed, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, QueryPlanCost, Pattern ' + ' FROM ##BlitzCacheProcs ' + ' WHERE 1=1 '; @@ -19419,14 +19439,14 @@ END '; + ' (ServerName, CheckDate, Version, QueryType, DatabaseName, AverageCPU, TotalCPU, PercentCPUByType, CPUWeight, AverageDuration, TotalDuration, DurationWeight, PercentDurationByType, AverageReads, TotalReads, ReadWeight, PercentReadsByType, ' + ' AverageWrites, TotalWrites, WriteWeight, PercentWritesByType, ExecutionCount, ExecutionWeight, PercentExecutionsByType, ' + ' ExecutionsPerMinute, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, QueryHash, QueryPlanHash, StatementStartOffset, StatementEndOffset, PlanGenerationNum, MinReturnedRows, MaxReturnedRows, AverageReturnedRows, TotalReturnedRows, QueryText, QueryPlan, NumberOfPlans, NumberOfDistinctPlans, Warnings, ' - + ' SerialRequiredMemory, SerialDesiredMemory, MinGrantKB, MaxGrantKB, MinUsedGrantKB, MaxUsedGrantKB, PercentMemoryGrantUsed, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, QueryPlanCost ) ' + + ' SerialRequiredMemory, SerialDesiredMemory, MinGrantKB, MaxGrantKB, MinUsedGrantKB, MaxUsedGrantKB, PercentMemoryGrantUsed, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, QueryPlanCost, Pattern ) ' + 'SELECT TOP (@Top) ' + QUOTENAME(CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128)), '''') + ', @CheckDateOverride, ' + QUOTENAME(CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)), '''') + ', ' + ' QueryType, DatabaseName, AverageCPU, TotalCPU, PercentCPUByType, PercentCPU, AverageDuration, TotalDuration, PercentDuration, PercentDurationByType, AverageReads, TotalReads, PercentReads, PercentReadsByType, ' + ' AverageWrites, TotalWrites, PercentWrites, PercentWritesByType, ExecutionCount, PercentExecutions, PercentExecutionsByType, ' + ' ExecutionsPerMinute, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, QueryHash, QueryPlanHash, StatementStartOffset, StatementEndOffset, PlanGenerationNum, MinReturnedRows, MaxReturnedRows, AverageReturnedRows, TotalReturnedRows, QueryText, QueryPlan, NumberOfPlans, NumberOfDistinctPlans, Warnings, ' - + ' SerialRequiredMemory, SerialDesiredMemory, MinGrantKB, MaxGrantKB, MinUsedGrantKB, MaxUsedGrantKB, PercentMemoryGrantUsed, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, QueryPlanCost ' + + ' SerialRequiredMemory, SerialDesiredMemory, MinGrantKB, MaxGrantKB, MinUsedGrantKB, MaxUsedGrantKB, PercentMemoryGrantUsed, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, QueryPlanCost, Pattern ' + ' FROM ##BlitzCacheProcs ' + ' WHERE 1=1 '; @@ -19568,6 +19588,7 @@ END '; TotalSpills BIGINT, AvgSpills MONEY, QueryPlanCost FLOAT, + Pattern NVARCHAR(20), JoinKey AS ServerName + Cast(CheckDate AS NVARCHAR(50)), CONSTRAINT [PK_' + REPLACE(REPLACE(@OutputTableName,'[',''),']','') + '] PRIMARY KEY CLUSTERED(ID ASC));'; SET @StringToExecute += N' INSERT ' @@ -19575,14 +19596,14 @@ END '; + ' (ServerName, CheckDate, Version, QueryType, DatabaseName, AverageCPU, TotalCPU, PercentCPUByType, CPUWeight, AverageDuration, TotalDuration, DurationWeight, PercentDurationByType, AverageReads, TotalReads, ReadWeight, PercentReadsByType, ' + ' AverageWrites, TotalWrites, WriteWeight, PercentWritesByType, ExecutionCount, ExecutionWeight, PercentExecutionsByType, ' + ' ExecutionsPerMinute, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, QueryHash, QueryPlanHash, StatementStartOffset, StatementEndOffset, PlanGenerationNum, MinReturnedRows, MaxReturnedRows, AverageReturnedRows, TotalReturnedRows, QueryText, QueryPlan, NumberOfPlans, NumberOfDistinctPlans, Warnings, ' - + ' SerialRequiredMemory, SerialDesiredMemory, MinGrantKB, MaxGrantKB, MinUsedGrantKB, MaxUsedGrantKB, PercentMemoryGrantUsed, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, QueryPlanCost ) ' + + ' SerialRequiredMemory, SerialDesiredMemory, MinGrantKB, MaxGrantKB, MinUsedGrantKB, MaxUsedGrantKB, PercentMemoryGrantUsed, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, QueryPlanCost, Pattern ) ' + 'SELECT TOP (@Top) ' + QUOTENAME(CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128)), '''') + ', @CheckDateOverride, ' + QUOTENAME(CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)), '''') + ', ' + ' QueryType, DatabaseName, AverageCPU, TotalCPU, PercentCPUByType, PercentCPU, AverageDuration, TotalDuration, PercentDuration, PercentDurationByType, AverageReads, TotalReads, PercentReads, PercentReadsByType, ' + ' AverageWrites, TotalWrites, PercentWrites, PercentWritesByType, ExecutionCount, PercentExecutions, PercentExecutionsByType, ' + ' ExecutionsPerMinute, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, QueryHash, QueryPlanHash, StatementStartOffset, StatementEndOffset, PlanGenerationNum, MinReturnedRows, MaxReturnedRows, AverageReturnedRows, TotalReturnedRows, QueryText, QueryPlan, NumberOfPlans, NumberOfDistinctPlans, Warnings, ' - + ' SerialRequiredMemory, SerialDesiredMemory, MinGrantKB, MaxGrantKB, MinUsedGrantKB, MaxUsedGrantKB, PercentMemoryGrantUsed, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, QueryPlanCost ' + + ' SerialRequiredMemory, SerialDesiredMemory, MinGrantKB, MaxGrantKB, MinUsedGrantKB, MaxUsedGrantKB, PercentMemoryGrantUsed, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, QueryPlanCost, Pattern ' + ' FROM ##BlitzCacheProcs ' + ' WHERE 1=1 '; @@ -19694,7 +19715,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.12', @VersionDate = '20221213'; +SELECT @Version = '8.13', @VersionDate = '20230215'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -19731,7 +19752,7 @@ Unknown limitations of this version: MIT License -Copyright (c) 2021 Brent Ozar Unlimited +Copyright (c) Brent Ozar Unlimited Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -25871,7 +25892,7 @@ BEGIN SET NOCOUNT, XACT_ABORT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.12', @VersionDate = '20221213'; + SELECT @Version = '8.13', @VersionDate = '20230215'; IF @VersionCheckMode = 1 BEGIN @@ -25932,7 +25953,7 @@ BEGIN MIT License - Copyright (c) 2022 Brent Ozar Unlimited + Copyright (c) Brent Ozar Unlimited Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -27005,7 +27026,7 @@ BEGIN waiter_mode = w.l.value('@mode', 'nvarchar(256)'), owner_id = o.l.value('@id', 'nvarchar(256)'), owner_mode = o.l.value('@mode', 'nvarchar(256)'), - lock_type = N'OBJECT' + lock_type = CAST(N'OBJECT' AS NVARCHAR(100)) INTO #deadlock_owner_waiter FROM ( @@ -27630,7 +27651,7 @@ BEGIN N'S', N'IS' ) - AND (dow.database_id = @DatabaseName OR @DatabaseName IS NULL) + AND (dow.database_id = @DatabaseId OR @DatabaseName IS NULL) AND (dow.event_date >= @StartDate OR @StartDate IS NULL) AND (dow.event_date < @EndDate OR @EndDate IS NULL) AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) @@ -27673,7 +27694,7 @@ BEGIN N' deadlock(s).' FROM #deadlock_owner_waiter AS dow WHERE 1 = 1 - AND (dow.database_id = @DatabaseName OR @DatabaseName IS NULL) + AND (dow.database_id = @DatabaseId OR @DatabaseName IS NULL) AND (dow.event_date >= @StartDate OR @StartDate IS NULL) AND (dow.event_date < @EndDate OR @EndDate IS NULL) AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) @@ -27712,7 +27733,7 @@ BEGIN N' deadlock(s).' FROM #deadlock_owner_waiter AS dow WHERE 1 = 1 - AND (dow.database_id = @DatabaseName OR @DatabaseName IS NULL) + AND (dow.database_id = @DatabaseId OR @DatabaseName IS NULL) AND (dow.event_date >= @StartDate OR @StartDate IS NULL) AND (dow.event_date < @EndDate OR @EndDate IS NULL) AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) @@ -27757,7 +27778,7 @@ BEGIN N' deadlock(s).' FROM #deadlock_owner_waiter AS dow WHERE 1 = 1 - AND (dow.database_id = @DatabaseName OR @DatabaseName IS NULL) + AND (dow.database_id = @DatabaseId OR @DatabaseName IS NULL) AND (dow.event_date >= @StartDate OR @StartDate IS NULL) AND (dow.event_date < @EndDate OR @EndDate IS NULL) AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) @@ -28083,7 +28104,7 @@ BEGIN ON dow.owner_id = ds.id AND dow.event_date = ds.event_date WHERE 1 = 1 - AND (dow.database_id = @DatabaseName OR @DatabaseName IS NULL) + AND (dow.database_id = @DatabaseId OR @DatabaseName IS NULL) AND (dow.event_date >= @StartDate OR @StartDate IS NULL) AND (dow.event_date < @EndDate OR @EndDate IS NULL) AND (dow.object_name = @StoredProcName OR @StoredProcName IS NULL) @@ -28139,7 +28160,7 @@ BEGIN ON dow.owner_id = ds.id AND dow.event_date = ds.event_date WHERE ds.proc_name <> N'adhoc' - AND (dow.database_id = @DatabaseName OR @DatabaseName IS NULL) + AND (dow.database_id = @DatabaseId OR @DatabaseName IS NULL) AND (dow.event_date >= @StartDate OR @StartDate IS NULL) AND (dow.event_date < @EndDate OR @EndDate IS NULL) AND (dow.object_name = @StoredProcName OR @StoredProcName IS NULL) @@ -28215,7 +28236,7 @@ BEGIN ON s.database_id = dow.database_id AND s.partition_id = dow.associatedObjectId WHERE 1 = 1 - AND (dow.database_id = @DatabaseName OR @DatabaseName IS NULL) + AND (dow.database_id = @DatabaseId OR @DatabaseName IS NULL) AND (dow.event_date >= @StartDate OR @StartDate IS NULL) AND (dow.event_date < @EndDate OR @EndDate IS NULL) AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) @@ -28277,6 +28298,76 @@ BEGIN ) ), wait_time_hms = + /*the more wait time you rack up the less accurate this gets, + it's either that or erroring out*/ + CASE + WHEN + SUM + ( + CONVERT + ( + bigint, + dp.wait_time + ) + )/1000 > 2147483647 + THEN + CONVERT + ( + nvarchar(30), + DATEADD + ( + MINUTE, + ( + ( + SUM + ( + CONVERT + ( + bigint, + dp.wait_time + ) + ) + )/ + 60000 + ), + 0 + ), + 14 + ) + WHEN + SUM + ( + CONVERT + ( + bigint, + dp.wait_time + ) + ) BETWEEN 2147483648 AND 2147483647000 + THEN + CONVERT + ( + nvarchar(30), + DATEADD + ( + SECOND, + ( + ( + SUM + ( + CONVERT + ( + bigint, + dp.wait_time + ) + ) + )/ + 1000 + ), + 0 + ), + 14 + ) + ELSE CONVERT ( nvarchar(30), @@ -28297,6 +28388,7 @@ BEGIN ), 14 ) + END FROM #deadlock_owner_waiter AS dow JOIN #deadlock_process AS dp ON (dp.id = dow.owner_id @@ -28413,6 +28505,76 @@ BEGIN ) ) + N' ' + + /*the more wait time you rack up the less accurate this gets, + it's either that or erroring out*/ + CASE + WHEN + SUM + ( + CONVERT + ( + bigint, + wt.total_wait_time_ms + ) + )/1000 > 2147483647 + THEN + CONVERT + ( + nvarchar(30), + DATEADD + ( + MINUTE, + ( + ( + SUM + ( + CONVERT + ( + bigint, + wt.total_wait_time_ms + ) + ) + )/ + 60000 + ), + 0 + ), + 14 + ) + WHEN + SUM + ( + CONVERT + ( + bigint, + wt.total_wait_time_ms + ) + ) BETWEEN 2147483648 AND 2147483647000 + THEN + CONVERT + ( + nvarchar(30), + DATEADD + ( + SECOND, + ( + ( + SUM + ( + CONVERT + ( + bigint, + wt.total_wait_time_ms + ) + ) + )/ + 1000 + ), + 0 + ), + 14 + ) + ELSE CONVERT ( nvarchar(30), @@ -28432,7 +28594,7 @@ BEGIN 0 ), 14 - ) + + ) END + N' [dd hh:mm:ss:ms] of deadlock wait time.' FROM wait_time AS wt GROUP BY @@ -29354,7 +29516,7 @@ BEGIN SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.12', @VersionDate = '20221213'; + SELECT @Version = '8.13', @VersionDate = '20230215'; IF(@VersionCheckMode = 1) BEGIN @@ -29382,7 +29544,7 @@ Known limitations of this version: MIT License -Copyright (c) 2021 Brent Ozar Unlimited +Copyright (c) Brent Ozar Unlimited Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -30011,11 +30173,11 @@ BEGIN END AS blocking_session_id, COALESCE(DB_NAME(r.database_id), DB_NAME(blocked.dbid), ''N/A'') AS database_name, ISNULL(SUBSTRING(dest.text, - ( query_stats.statement_start_offset / 2 ) + 1, - ( ( CASE query_stats.statement_end_offset + ( r.statement_start_offset / 2 ) + 1, + ( ( CASE r.statement_end_offset WHEN -1 THEN DATALENGTH(dest.text) - ELSE query_stats.statement_end_offset - END - query_stats.statement_start_offset ) + ELSE r.statement_end_offset + END - r.statement_start_offset ) / 2 ) + 1), dest.text) AS query_text , '+CASE WHEN @GetOuterCommand = 1 THEN N'CAST(event_info AS NVARCHAR(4000)) AS outer_command,' @@ -30180,7 +30342,7 @@ BEGIN OUTER APPLY sys.dm_exec_sql_text(COALESCE(r.sql_handle, blocked.sql_handle)) AS dest OUTER APPLY sys.dm_exec_query_plan(r.plan_handle) AS derp OUTER APPLY ( - SELECT CONVERT(DECIMAL(38,2), SUM( (((tsu.user_objects_alloc_page_count - user_objects_dealloc_page_count) * 8) / 1024.)) ) AS tempdb_allocations_mb + SELECT CONVERT(DECIMAL(38,2), SUM( ((((tsu.user_objects_alloc_page_count - user_objects_dealloc_page_count) + (tsu.internal_objects_alloc_page_count - internal_objects_dealloc_page_count)) * 8) / 1024.)) ) AS tempdb_allocations_mb FROM sys.dm_db_task_space_usage tsu WHERE tsu.request_id = r.request_id AND tsu.session_id = r.session_id @@ -30229,11 +30391,11 @@ IF @ProductVersionMajor >= 11 END AS blocking_session_id, COALESCE(DB_NAME(r.database_id), DB_NAME(blocked.dbid), ''N/A'') AS database_name, ISNULL(SUBSTRING(dest.text, - ( query_stats.statement_start_offset / 2 ) + 1, - ( ( CASE query_stats.statement_end_offset + ( r.statement_start_offset / 2 ) + 1, + ( ( CASE r.statement_end_offset WHEN -1 THEN DATALENGTH(dest.text) - ELSE query_stats.statement_end_offset - END - query_stats.statement_start_offset ) + ELSE r.statement_end_offset + END - r.statement_start_offset ) / 2 ) + 1), dest.text) AS query_text , '+CASE WHEN @GetOuterCommand = 1 THEN N'CAST(event_info AS NVARCHAR(4000)) AS outer_command,' @@ -30473,7 +30635,7 @@ IF @ProductVersionMajor >= 11 OUTER APPLY sys.dm_exec_sql_text(COALESCE(r.sql_handle, blocked.sql_handle)) AS dest OUTER APPLY sys.dm_exec_query_plan(r.plan_handle) AS derp OUTER APPLY ( - SELECT CONVERT(DECIMAL(38,2), SUM( (((tsu.user_objects_alloc_page_count - user_objects_dealloc_page_count) * 8) / 1024.)) ) AS tempdb_allocations_mb + SELECT CONVERT(DECIMAL(38,2), SUM( ((((tsu.user_objects_alloc_page_count - user_objects_dealloc_page_count) + (tsu.internal_objects_alloc_page_count - internal_objects_dealloc_page_count)) * 8) / 1024.)) ) AS tempdb_allocations_mb FROM sys.dm_db_task_space_usage tsu WHERE tsu.request_id = r.request_id AND tsu.session_id = r.session_id @@ -30750,7 +30912,9 @@ DELETE FROM dbo.SqlServerVersions; INSERT INTO dbo.SqlServerVersions (MajorVersionNumber, MinorVersionNumber, Branch, [Url], ReleaseDate, MainstreamSupportEndDate, ExtendedSupportEndDate, MajorVersionName, MinorVersionName) 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'), + (15, 4280, 'CU18 GDR', 'https://support.microsoft.com/kb/5021124', '2023-02-14', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 18 GDR'), (15, 4261, 'CU18', 'https://support.microsoft.com/en-us/help/5017593', '2022-09-28', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 18'), (15, 4249, 'CU17', 'https://support.microsoft.com/en-us/help/5016394', '2022-08-11', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 17'), (15, 4236, 'CU16 GDR', 'https://support.microsoft.com/en-us/help/5014353', '2022-06-14', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 16 GDR'), @@ -30773,6 +30937,7 @@ VALUES (15, 4003, 'CU1', 'https://support.microsoft.com/en-us/help/4527376', '2020-01-07', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 1 '), (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 '), + (14, 3460, 'RTM CU31 GDR', 'https://support.microsoft.com/kb/5021126', '2023-02-14', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 31 GDR'), (14, 3456, 'RTM CU31', 'https://support.microsoft.com/en-us/help/5016884', '2022-09-20', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 31'), (14, 3451, 'RTM CU30', 'https://support.microsoft.com/en-us/help/5013756', '2022-07-13', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 30'), (14, 3445, 'RTM CU29 GDR', 'https://support.microsoft.com/en-us/help/5014553', '2022-06-14', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 29 GDR'), @@ -30809,6 +30974,7 @@ VALUES (14, 1000, 'RTM ', '', '2017-10-02', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM '), (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, 6430, 'SP3 GDR', 'https://support.microsoft.com/kb/5021129', '2023-02-14', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3 GDR'), (13, 6419, 'SP3 GDR', 'https://support.microsoft.com/en-us/help/5014355', '2022-06-14', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3 GDR'), (13, 6404, 'SP3 GDR', 'https://support.microsoft.com/en-us/help/5006943', '2021-10-27', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3 GDR'), (13, 6300, 'SP3 ', 'https://support.microsoft.com/en-us/help/5003279', '2021-09-15', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3'), @@ -30860,6 +31026,7 @@ VALUES (13, 2164, 'RTM CU2', 'https://support.microsoft.com/en-us/help/3182270 ', '2016-09-22', '2018-01-09', '2018-01-09', 'SQL Server 2016', 'RTM Cumulative Update 2'), (13, 2149, 'RTM CU1', 'https://support.microsoft.com/en-us/help/3164674 ', '2016-07-25', '2018-01-09', '2018-01-09', 'SQL Server 2016', 'RTM Cumulative Update 1'), (13, 1601, 'RTM ', '', '2016-06-01', '2019-01-09', '2019-01-09', 'SQL Server 2016', 'RTM '), + (12, 6444, 'SP3 CU4 GDR', 'https://support.microsoft.com/kb/5021045', '2023-02-14', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 4 GDR'), (12, 6439, 'SP3 CU4 GDR', 'https://support.microsoft.com/en-us/help/5014164', '2022-06-14', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 4 GDR'), (12, 6433, 'SP3 CU4 GDR', 'https://support.microsoft.com/en-us/help/4583462', '2021-01-12', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 4 GDR'), (12, 6372, 'SP3 CU4 GDR', 'https://support.microsoft.com/en-us/help/4535288', '2020-02-11', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 4 GDR'), @@ -31166,7 +31333,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.12', @VersionDate = '20221213'; +SELECT @Version = '8.13', @VersionDate = '20230215'; IF(@VersionCheckMode = 1) BEGIN @@ -31206,7 +31373,7 @@ https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/ MIT License -Copyright (c) 2021 Brent Ozar Unlimited +Copyright (c) Brent Ozar Unlimited Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -33413,8 +33580,9 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, CROSS APPLY sys.dm_exec_query_plan(r.plan_handle) AS qp '; IF EXISTS (SELECT * FROM sys.all_objects WHERE name = 'dm_exec_query_statistics_xml') - SET @StringToExecute = @StringToExecute + N' OUTER APPLY sys.dm_exec_query_statistics_xml(s.session_id) qs_live '; - + /* GitHub #3210 */ + SET @StringToExecute = N' + SET LOCK_TIMEOUT 1000 ' + @StringToExecute + N' OUTER APPLY sys.dm_exec_query_statistics_xml(s.session_id) qs_live '; SET @StringToExecute = @StringToExecute + N'; diff --git a/Install-Core-Blitz-With-Query-Store.sql b/Install-Core-Blitz-With-Query-Store.sql index 84ff56849..03b39864c 100644 --- a/Install-Core-Blitz-With-Query-Store.sql +++ b/Install-Core-Blitz-With-Query-Store.sql @@ -38,7 +38,7 @@ AS SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.12', @VersionDate = '20221213'; + SELECT @Version = '8.13', @VersionDate = '20230215'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -93,9 +93,9 @@ AS tigertoolbox and are provided under the MIT license: https://github.com/Microsoft/tigertoolbox - All other copyrights for sp_Blitz are held by Brent Ozar Unlimited, 2021. + All other copyrights for sp_Blitz are held by Brent Ozar Unlimited. - Copyright (c) 2021 Brent Ozar Unlimited + Copyright (c) Brent Ozar Unlimited Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -9737,7 +9737,7 @@ AS SET NOCOUNT ON; SET STATISTICS XML OFF; -SELECT @Version = '8.12', @VersionDate = '20221213'; +SELECT @Version = '8.13', @VersionDate = '20230215'; IF(@VersionCheckMode = 1) BEGIN @@ -10615,7 +10615,7 @@ AS SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.12', @VersionDate = '20221213'; + SELECT @Version = '8.13', @VersionDate = '20230215'; IF(@VersionCheckMode = 1) BEGIN @@ -10662,7 +10662,7 @@ AS MIT License - Copyright (c) 2021 Brent Ozar Unlimited + Copyright (c) Brent Ozar Unlimited Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -12349,7 +12349,8 @@ CREATE TABLE ##BlitzCacheProcs ( cached_execution_parameters XML, missing_indexes XML, SetOptions VARCHAR(MAX), - Warnings VARCHAR(MAX) + Warnings VARCHAR(MAX), + Pattern NVARCHAR(20) ); GO @@ -12396,7 +12397,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.12', @VersionDate = '20221213'; +SELECT @Version = '8.13', @VersionDate = '20230215'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -12420,7 +12421,6 @@ IF @Help = 1 the findings, contribute your own code, and more. Known limitations of this version: - - This query will not run on SQL Server 2005. - SQL Server 2008 and 2008R2 have a bug in trigger stats, so that output is excluded by default. - @IgnoreQueryHashes and @OnlyQueryHashes require a CSV list of hashes @@ -12436,7 +12436,7 @@ IF @Help = 1 MIT License - Copyright (c) 2021 Brent Ozar Unlimited + Copyright (c) Brent Ozar Unlimited Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -13172,7 +13172,8 @@ BEGIN cached_execution_parameters XML, missing_indexes XML, SetOptions VARCHAR(MAX), - Warnings VARCHAR(MAX) + Warnings VARCHAR(MAX), + Pattern NVARCHAR(20) ); END; @@ -13273,18 +13274,18 @@ IF @SkipAnalysis = 1 DECLARE @AllSortSql NVARCHAR(MAX) = N''; DECLARE @VersionShowsMemoryGrants BIT; -IF EXISTS(SELECT * FROM sys.all_columns WHERE OBJECT_ID = OBJECT_ID('sys.dm_exec_query_stats') AND name = 'max_grant_kb') +IF EXISTS(SELECT * FROM sys.all_columns WHERE object_id = OBJECT_ID('sys.dm_exec_query_stats') AND name = 'max_grant_kb') SET @VersionShowsMemoryGrants = 1; ELSE SET @VersionShowsMemoryGrants = 0; DECLARE @VersionShowsSpills BIT; -IF EXISTS(SELECT * FROM sys.all_columns WHERE OBJECT_ID = OBJECT_ID('sys.dm_exec_query_stats') AND name = 'max_spills') +IF EXISTS(SELECT * FROM sys.all_columns WHERE object_id = OBJECT_ID('sys.dm_exec_query_stats') AND name = 'max_spills') SET @VersionShowsSpills = 1; ELSE SET @VersionShowsSpills = 0; -IF EXISTS(SELECT * FROM sys.all_columns WHERE OBJECT_ID = OBJECT_ID('sys.dm_exec_query_plan_stats') AND name = 'query_plan') +IF EXISTS(SELECT * FROM sys.all_columns WHERE object_id = OBJECT_ID('sys.dm_exec_query_plan_stats') AND name = 'query_plan') SET @VersionShowsAirQuoteActualPlans = 1; ELSE SET @VersionShowsAirQuoteActualPlans = 0; @@ -14091,7 +14092,7 @@ INSERT INTO ##BlitzCacheProcs (SPID, QueryType, DatabaseName, AverageCPU, TotalC LastReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, MaxUsedGrantKB, PercentMemoryGrantUsed, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, QueryText, QueryPlan, TotalWorkerTimeForType, TotalElapsedTimeForType, TotalReadsForType, TotalExecutionCountForType, TotalWritesForType, SqlHandle, PlanHandle, QueryHash, QueryPlanHash, - min_worker_time, max_worker_time, is_parallel, min_elapsed_time, max_elapsed_time, age_minutes, age_minutes_lifetime) ' ; + min_worker_time, max_worker_time, is_parallel, min_elapsed_time, max_elapsed_time, age_minutes, age_minutes_lifetime, Pattern) ' ; SET @body += N' FROM (SELECT TOP (@Top) x.*, xpa.*, @@ -14355,7 +14356,8 @@ SELECT TOP (@Top) qs.min_elapsed_time / 1000.0, qs.max_elapsed_time / 1000.0, age_minutes, - age_minutes_lifetime '; + age_minutes_lifetime, + @SortOrder '; IF LEFT(@QueryFilter, 3) IN ('all', 'sta') @@ -14503,7 +14505,8 @@ BEGIN qs.min_elapsed_time / 1000.0, qs.max_worker_time / 1000.0, age_minutes, - age_minutes_lifetime '; + age_minutes_lifetime, + @SortOrder '; SET @sql += REPLACE(REPLACE(@body, '#view#', 'dm_exec_query_stats'), 'cached_time', 'creation_time') ; @@ -14738,7 +14741,7 @@ IF @Reanalyze = 0 BEGIN RAISERROR('Collecting execution plan information.', 0, 1) WITH NOWAIT; - EXEC sp_executesql @sql, N'@Top INT, @min_duration INT, @min_back INT', @Top, @DurationFilter_i, @MinutesBack; + EXEC sp_executesql @sql, N'@Top INT, @min_duration INT, @min_back INT, @SortOrder NVARCHAR(20)', @Top, @DurationFilter_i, @MinutesBack, @SortOrder; END; IF @SkipAnalysis = 1 @@ -19231,6 +19234,7 @@ ELSE TotalSpills BIGINT, AvgSpills MONEY, QueryPlanCost FLOAT, + Pattern NVARCHAR(20), JoinKey AS ServerName + Cast(CheckDate AS NVARCHAR(50)), CONSTRAINT [PK_' + REPLACE(REPLACE(@OutputTableName,N'[',N''),N']',N'') + N'] PRIMARY KEY CLUSTERED(ID ASC));' ); @@ -19332,6 +19336,22 @@ END '; EXEC(@StringToExecute); END; + /* If the table doesn't have the new Pattern column, add it */ + SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableName; + SET @StringToExecute = N'IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns + WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''Pattern'') + ALTER TABLE ' + @ObjectFullName + N' ADD Pattern NVARCHAR(20) NULL;'; + IF @ValidOutputServer = 1 + BEGIN + SET @StringToExecute = REPLACE(@StringToExecute,'''Pattern''','''''Pattern'''''); + SET @StringToExecute = REPLACE(@StringToExecute,'''' + @ObjectFullName + '''','''''' + @ObjectFullName + ''''''); + EXEC('EXEC('''+@StringToExecute+''') AT ' + @OutputServerName); + END; + ELSE + BEGIN + EXEC(@StringToExecute); + END + IF @CheckDateOverride IS NULL BEGIN SET @CheckDateOverride = SYSDATETIMEOFFSET(); @@ -19351,14 +19371,14 @@ END '; + ' (ServerName, CheckDate, Version, QueryType, DatabaseName, AverageCPU, TotalCPU, PercentCPUByType, CPUWeight, AverageDuration, TotalDuration, DurationWeight, PercentDurationByType, AverageReads, TotalReads, ReadWeight, PercentReadsByType, ' + ' AverageWrites, TotalWrites, WriteWeight, PercentWritesByType, ExecutionCount, ExecutionWeight, PercentExecutionsByType, ' + ' ExecutionsPerMinute, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, QueryHash, QueryPlanHash, StatementStartOffset, StatementEndOffset, PlanGenerationNum, MinReturnedRows, MaxReturnedRows, AverageReturnedRows, TotalReturnedRows, QueryText, QueryPlan, NumberOfPlans, NumberOfDistinctPlans, Warnings, ' - + ' SerialRequiredMemory, SerialDesiredMemory, MinGrantKB, MaxGrantKB, MinUsedGrantKB, MaxUsedGrantKB, PercentMemoryGrantUsed, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, QueryPlanCost ) ' + + ' SerialRequiredMemory, SerialDesiredMemory, MinGrantKB, MaxGrantKB, MinUsedGrantKB, MaxUsedGrantKB, PercentMemoryGrantUsed, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, QueryPlanCost, Pattern ) ' + 'SELECT TOP (@Top) ' + QUOTENAME(CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128)), '''') + ', @CheckDateOverride, ' + QUOTENAME(CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)), '''') + ', ' + ' QueryType, DatabaseName, AverageCPU, TotalCPU, PercentCPUByType, PercentCPU, AverageDuration, TotalDuration, PercentDuration, PercentDurationByType, AverageReads, TotalReads, PercentReads, PercentReadsByType, ' + ' AverageWrites, TotalWrites, PercentWrites, PercentWritesByType, ExecutionCount, PercentExecutions, PercentExecutionsByType, ' + ' ExecutionsPerMinute, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, QueryHash, QueryPlanHash, StatementStartOffset, StatementEndOffset, PlanGenerationNum, MinReturnedRows, MaxReturnedRows, AverageReturnedRows, TotalReturnedRows, QueryText, CAST(QueryPlan AS NVARCHAR(MAX)), NumberOfPlans, NumberOfDistinctPlans, Warnings, ' - + ' SerialRequiredMemory, SerialDesiredMemory, MinGrantKB, MaxGrantKB, MinUsedGrantKB, MaxUsedGrantKB, PercentMemoryGrantUsed, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, QueryPlanCost ' + + ' SerialRequiredMemory, SerialDesiredMemory, MinGrantKB, MaxGrantKB, MinUsedGrantKB, MaxUsedGrantKB, PercentMemoryGrantUsed, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, QueryPlanCost, Pattern ' + ' FROM ##BlitzCacheProcs ' + ' WHERE 1=1 '; @@ -19419,14 +19439,14 @@ END '; + ' (ServerName, CheckDate, Version, QueryType, DatabaseName, AverageCPU, TotalCPU, PercentCPUByType, CPUWeight, AverageDuration, TotalDuration, DurationWeight, PercentDurationByType, AverageReads, TotalReads, ReadWeight, PercentReadsByType, ' + ' AverageWrites, TotalWrites, WriteWeight, PercentWritesByType, ExecutionCount, ExecutionWeight, PercentExecutionsByType, ' + ' ExecutionsPerMinute, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, QueryHash, QueryPlanHash, StatementStartOffset, StatementEndOffset, PlanGenerationNum, MinReturnedRows, MaxReturnedRows, AverageReturnedRows, TotalReturnedRows, QueryText, QueryPlan, NumberOfPlans, NumberOfDistinctPlans, Warnings, ' - + ' SerialRequiredMemory, SerialDesiredMemory, MinGrantKB, MaxGrantKB, MinUsedGrantKB, MaxUsedGrantKB, PercentMemoryGrantUsed, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, QueryPlanCost ) ' + + ' SerialRequiredMemory, SerialDesiredMemory, MinGrantKB, MaxGrantKB, MinUsedGrantKB, MaxUsedGrantKB, PercentMemoryGrantUsed, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, QueryPlanCost, Pattern ) ' + 'SELECT TOP (@Top) ' + QUOTENAME(CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128)), '''') + ', @CheckDateOverride, ' + QUOTENAME(CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)), '''') + ', ' + ' QueryType, DatabaseName, AverageCPU, TotalCPU, PercentCPUByType, PercentCPU, AverageDuration, TotalDuration, PercentDuration, PercentDurationByType, AverageReads, TotalReads, PercentReads, PercentReadsByType, ' + ' AverageWrites, TotalWrites, PercentWrites, PercentWritesByType, ExecutionCount, PercentExecutions, PercentExecutionsByType, ' + ' ExecutionsPerMinute, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, QueryHash, QueryPlanHash, StatementStartOffset, StatementEndOffset, PlanGenerationNum, MinReturnedRows, MaxReturnedRows, AverageReturnedRows, TotalReturnedRows, QueryText, QueryPlan, NumberOfPlans, NumberOfDistinctPlans, Warnings, ' - + ' SerialRequiredMemory, SerialDesiredMemory, MinGrantKB, MaxGrantKB, MinUsedGrantKB, MaxUsedGrantKB, PercentMemoryGrantUsed, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, QueryPlanCost ' + + ' SerialRequiredMemory, SerialDesiredMemory, MinGrantKB, MaxGrantKB, MinUsedGrantKB, MaxUsedGrantKB, PercentMemoryGrantUsed, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, QueryPlanCost, Pattern ' + ' FROM ##BlitzCacheProcs ' + ' WHERE 1=1 '; @@ -19568,6 +19588,7 @@ END '; TotalSpills BIGINT, AvgSpills MONEY, QueryPlanCost FLOAT, + Pattern NVARCHAR(20), JoinKey AS ServerName + Cast(CheckDate AS NVARCHAR(50)), CONSTRAINT [PK_' + REPLACE(REPLACE(@OutputTableName,'[',''),']','') + '] PRIMARY KEY CLUSTERED(ID ASC));'; SET @StringToExecute += N' INSERT ' @@ -19575,14 +19596,14 @@ END '; + ' (ServerName, CheckDate, Version, QueryType, DatabaseName, AverageCPU, TotalCPU, PercentCPUByType, CPUWeight, AverageDuration, TotalDuration, DurationWeight, PercentDurationByType, AverageReads, TotalReads, ReadWeight, PercentReadsByType, ' + ' AverageWrites, TotalWrites, WriteWeight, PercentWritesByType, ExecutionCount, ExecutionWeight, PercentExecutionsByType, ' + ' ExecutionsPerMinute, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, QueryHash, QueryPlanHash, StatementStartOffset, StatementEndOffset, PlanGenerationNum, MinReturnedRows, MaxReturnedRows, AverageReturnedRows, TotalReturnedRows, QueryText, QueryPlan, NumberOfPlans, NumberOfDistinctPlans, Warnings, ' - + ' SerialRequiredMemory, SerialDesiredMemory, MinGrantKB, MaxGrantKB, MinUsedGrantKB, MaxUsedGrantKB, PercentMemoryGrantUsed, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, QueryPlanCost ) ' + + ' SerialRequiredMemory, SerialDesiredMemory, MinGrantKB, MaxGrantKB, MinUsedGrantKB, MaxUsedGrantKB, PercentMemoryGrantUsed, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, QueryPlanCost, Pattern ) ' + 'SELECT TOP (@Top) ' + QUOTENAME(CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128)), '''') + ', @CheckDateOverride, ' + QUOTENAME(CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)), '''') + ', ' + ' QueryType, DatabaseName, AverageCPU, TotalCPU, PercentCPUByType, PercentCPU, AverageDuration, TotalDuration, PercentDuration, PercentDurationByType, AverageReads, TotalReads, PercentReads, PercentReadsByType, ' + ' AverageWrites, TotalWrites, PercentWrites, PercentWritesByType, ExecutionCount, PercentExecutions, PercentExecutionsByType, ' + ' ExecutionsPerMinute, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, QueryHash, QueryPlanHash, StatementStartOffset, StatementEndOffset, PlanGenerationNum, MinReturnedRows, MaxReturnedRows, AverageReturnedRows, TotalReturnedRows, QueryText, QueryPlan, NumberOfPlans, NumberOfDistinctPlans, Warnings, ' - + ' SerialRequiredMemory, SerialDesiredMemory, MinGrantKB, MaxGrantKB, MinUsedGrantKB, MaxUsedGrantKB, PercentMemoryGrantUsed, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, QueryPlanCost ' + + ' SerialRequiredMemory, SerialDesiredMemory, MinGrantKB, MaxGrantKB, MinUsedGrantKB, MaxUsedGrantKB, PercentMemoryGrantUsed, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, QueryPlanCost, Pattern ' + ' FROM ##BlitzCacheProcs ' + ' WHERE 1=1 '; @@ -19694,7 +19715,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.12', @VersionDate = '20221213'; +SELECT @Version = '8.13', @VersionDate = '20230215'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -19731,7 +19752,7 @@ Unknown limitations of this version: MIT License -Copyright (c) 2021 Brent Ozar Unlimited +Copyright (c) Brent Ozar Unlimited Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -25871,7 +25892,7 @@ BEGIN SET NOCOUNT, XACT_ABORT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.12', @VersionDate = '20221213'; + SELECT @Version = '8.13', @VersionDate = '20230215'; IF @VersionCheckMode = 1 BEGIN @@ -25932,7 +25953,7 @@ BEGIN MIT License - Copyright (c) 2022 Brent Ozar Unlimited + Copyright (c) Brent Ozar Unlimited Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -27005,7 +27026,7 @@ BEGIN waiter_mode = w.l.value('@mode', 'nvarchar(256)'), owner_id = o.l.value('@id', 'nvarchar(256)'), owner_mode = o.l.value('@mode', 'nvarchar(256)'), - lock_type = N'OBJECT' + lock_type = CAST(N'OBJECT' AS NVARCHAR(100)) INTO #deadlock_owner_waiter FROM ( @@ -27630,7 +27651,7 @@ BEGIN N'S', N'IS' ) - AND (dow.database_id = @DatabaseName OR @DatabaseName IS NULL) + AND (dow.database_id = @DatabaseId OR @DatabaseName IS NULL) AND (dow.event_date >= @StartDate OR @StartDate IS NULL) AND (dow.event_date < @EndDate OR @EndDate IS NULL) AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) @@ -27673,7 +27694,7 @@ BEGIN N' deadlock(s).' FROM #deadlock_owner_waiter AS dow WHERE 1 = 1 - AND (dow.database_id = @DatabaseName OR @DatabaseName IS NULL) + AND (dow.database_id = @DatabaseId OR @DatabaseName IS NULL) AND (dow.event_date >= @StartDate OR @StartDate IS NULL) AND (dow.event_date < @EndDate OR @EndDate IS NULL) AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) @@ -27712,7 +27733,7 @@ BEGIN N' deadlock(s).' FROM #deadlock_owner_waiter AS dow WHERE 1 = 1 - AND (dow.database_id = @DatabaseName OR @DatabaseName IS NULL) + AND (dow.database_id = @DatabaseId OR @DatabaseName IS NULL) AND (dow.event_date >= @StartDate OR @StartDate IS NULL) AND (dow.event_date < @EndDate OR @EndDate IS NULL) AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) @@ -27757,7 +27778,7 @@ BEGIN N' deadlock(s).' FROM #deadlock_owner_waiter AS dow WHERE 1 = 1 - AND (dow.database_id = @DatabaseName OR @DatabaseName IS NULL) + AND (dow.database_id = @DatabaseId OR @DatabaseName IS NULL) AND (dow.event_date >= @StartDate OR @StartDate IS NULL) AND (dow.event_date < @EndDate OR @EndDate IS NULL) AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) @@ -28083,7 +28104,7 @@ BEGIN ON dow.owner_id = ds.id AND dow.event_date = ds.event_date WHERE 1 = 1 - AND (dow.database_id = @DatabaseName OR @DatabaseName IS NULL) + AND (dow.database_id = @DatabaseId OR @DatabaseName IS NULL) AND (dow.event_date >= @StartDate OR @StartDate IS NULL) AND (dow.event_date < @EndDate OR @EndDate IS NULL) AND (dow.object_name = @StoredProcName OR @StoredProcName IS NULL) @@ -28139,7 +28160,7 @@ BEGIN ON dow.owner_id = ds.id AND dow.event_date = ds.event_date WHERE ds.proc_name <> N'adhoc' - AND (dow.database_id = @DatabaseName OR @DatabaseName IS NULL) + AND (dow.database_id = @DatabaseId OR @DatabaseName IS NULL) AND (dow.event_date >= @StartDate OR @StartDate IS NULL) AND (dow.event_date < @EndDate OR @EndDate IS NULL) AND (dow.object_name = @StoredProcName OR @StoredProcName IS NULL) @@ -28215,7 +28236,7 @@ BEGIN ON s.database_id = dow.database_id AND s.partition_id = dow.associatedObjectId WHERE 1 = 1 - AND (dow.database_id = @DatabaseName OR @DatabaseName IS NULL) + AND (dow.database_id = @DatabaseId OR @DatabaseName IS NULL) AND (dow.event_date >= @StartDate OR @StartDate IS NULL) AND (dow.event_date < @EndDate OR @EndDate IS NULL) AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) @@ -28277,6 +28298,76 @@ BEGIN ) ), wait_time_hms = + /*the more wait time you rack up the less accurate this gets, + it's either that or erroring out*/ + CASE + WHEN + SUM + ( + CONVERT + ( + bigint, + dp.wait_time + ) + )/1000 > 2147483647 + THEN + CONVERT + ( + nvarchar(30), + DATEADD + ( + MINUTE, + ( + ( + SUM + ( + CONVERT + ( + bigint, + dp.wait_time + ) + ) + )/ + 60000 + ), + 0 + ), + 14 + ) + WHEN + SUM + ( + CONVERT + ( + bigint, + dp.wait_time + ) + ) BETWEEN 2147483648 AND 2147483647000 + THEN + CONVERT + ( + nvarchar(30), + DATEADD + ( + SECOND, + ( + ( + SUM + ( + CONVERT + ( + bigint, + dp.wait_time + ) + ) + )/ + 1000 + ), + 0 + ), + 14 + ) + ELSE CONVERT ( nvarchar(30), @@ -28297,6 +28388,7 @@ BEGIN ), 14 ) + END FROM #deadlock_owner_waiter AS dow JOIN #deadlock_process AS dp ON (dp.id = dow.owner_id @@ -28413,6 +28505,76 @@ BEGIN ) ) + N' ' + + /*the more wait time you rack up the less accurate this gets, + it's either that or erroring out*/ + CASE + WHEN + SUM + ( + CONVERT + ( + bigint, + wt.total_wait_time_ms + ) + )/1000 > 2147483647 + THEN + CONVERT + ( + nvarchar(30), + DATEADD + ( + MINUTE, + ( + ( + SUM + ( + CONVERT + ( + bigint, + wt.total_wait_time_ms + ) + ) + )/ + 60000 + ), + 0 + ), + 14 + ) + WHEN + SUM + ( + CONVERT + ( + bigint, + wt.total_wait_time_ms + ) + ) BETWEEN 2147483648 AND 2147483647000 + THEN + CONVERT + ( + nvarchar(30), + DATEADD + ( + SECOND, + ( + ( + SUM + ( + CONVERT + ( + bigint, + wt.total_wait_time_ms + ) + ) + )/ + 1000 + ), + 0 + ), + 14 + ) + ELSE CONVERT ( nvarchar(30), @@ -28432,7 +28594,7 @@ BEGIN 0 ), 14 - ) + + ) END + N' [dd hh:mm:ss:ms] of deadlock wait time.' FROM wait_time AS wt GROUP BY @@ -29378,7 +29540,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.12', @VersionDate = '20221213'; +SELECT @Version = '8.13', @VersionDate = '20230215'; IF(@VersionCheckMode = 1) BEGIN RETURN; @@ -29464,7 +29626,7 @@ IF @Help = 1 MIT License - Copyright (c) 2021 Brent Ozar Unlimited + Copyright (c) Brent Ozar Unlimited Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -35109,7 +35271,7 @@ BEGIN SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.12', @VersionDate = '20221213'; + SELECT @Version = '8.13', @VersionDate = '20230215'; IF(@VersionCheckMode = 1) BEGIN @@ -35137,7 +35299,7 @@ Known limitations of this version: MIT License -Copyright (c) 2021 Brent Ozar Unlimited +Copyright (c) Brent Ozar Unlimited Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -35766,11 +35928,11 @@ BEGIN END AS blocking_session_id, COALESCE(DB_NAME(r.database_id), DB_NAME(blocked.dbid), ''N/A'') AS database_name, ISNULL(SUBSTRING(dest.text, - ( query_stats.statement_start_offset / 2 ) + 1, - ( ( CASE query_stats.statement_end_offset + ( r.statement_start_offset / 2 ) + 1, + ( ( CASE r.statement_end_offset WHEN -1 THEN DATALENGTH(dest.text) - ELSE query_stats.statement_end_offset - END - query_stats.statement_start_offset ) + ELSE r.statement_end_offset + END - r.statement_start_offset ) / 2 ) + 1), dest.text) AS query_text , '+CASE WHEN @GetOuterCommand = 1 THEN N'CAST(event_info AS NVARCHAR(4000)) AS outer_command,' @@ -35935,7 +36097,7 @@ BEGIN OUTER APPLY sys.dm_exec_sql_text(COALESCE(r.sql_handle, blocked.sql_handle)) AS dest OUTER APPLY sys.dm_exec_query_plan(r.plan_handle) AS derp OUTER APPLY ( - SELECT CONVERT(DECIMAL(38,2), SUM( (((tsu.user_objects_alloc_page_count - user_objects_dealloc_page_count) * 8) / 1024.)) ) AS tempdb_allocations_mb + SELECT CONVERT(DECIMAL(38,2), SUM( ((((tsu.user_objects_alloc_page_count - user_objects_dealloc_page_count) + (tsu.internal_objects_alloc_page_count - internal_objects_dealloc_page_count)) * 8) / 1024.)) ) AS tempdb_allocations_mb FROM sys.dm_db_task_space_usage tsu WHERE tsu.request_id = r.request_id AND tsu.session_id = r.session_id @@ -35984,11 +36146,11 @@ IF @ProductVersionMajor >= 11 END AS blocking_session_id, COALESCE(DB_NAME(r.database_id), DB_NAME(blocked.dbid), ''N/A'') AS database_name, ISNULL(SUBSTRING(dest.text, - ( query_stats.statement_start_offset / 2 ) + 1, - ( ( CASE query_stats.statement_end_offset + ( r.statement_start_offset / 2 ) + 1, + ( ( CASE r.statement_end_offset WHEN -1 THEN DATALENGTH(dest.text) - ELSE query_stats.statement_end_offset - END - query_stats.statement_start_offset ) + ELSE r.statement_end_offset + END - r.statement_start_offset ) / 2 ) + 1), dest.text) AS query_text , '+CASE WHEN @GetOuterCommand = 1 THEN N'CAST(event_info AS NVARCHAR(4000)) AS outer_command,' @@ -36228,7 +36390,7 @@ IF @ProductVersionMajor >= 11 OUTER APPLY sys.dm_exec_sql_text(COALESCE(r.sql_handle, blocked.sql_handle)) AS dest OUTER APPLY sys.dm_exec_query_plan(r.plan_handle) AS derp OUTER APPLY ( - SELECT CONVERT(DECIMAL(38,2), SUM( (((tsu.user_objects_alloc_page_count - user_objects_dealloc_page_count) * 8) / 1024.)) ) AS tempdb_allocations_mb + SELECT CONVERT(DECIMAL(38,2), SUM( ((((tsu.user_objects_alloc_page_count - user_objects_dealloc_page_count) + (tsu.internal_objects_alloc_page_count - internal_objects_dealloc_page_count)) * 8) / 1024.)) ) AS tempdb_allocations_mb FROM sys.dm_db_task_space_usage tsu WHERE tsu.request_id = r.request_id AND tsu.session_id = r.session_id @@ -36505,7 +36667,9 @@ DELETE FROM dbo.SqlServerVersions; INSERT INTO dbo.SqlServerVersions (MajorVersionNumber, MinorVersionNumber, Branch, [Url], ReleaseDate, MainstreamSupportEndDate, ExtendedSupportEndDate, MajorVersionName, MinorVersionName) 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'), + (15, 4280, 'CU18 GDR', 'https://support.microsoft.com/kb/5021124', '2023-02-14', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 18 GDR'), (15, 4261, 'CU18', 'https://support.microsoft.com/en-us/help/5017593', '2022-09-28', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 18'), (15, 4249, 'CU17', 'https://support.microsoft.com/en-us/help/5016394', '2022-08-11', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 17'), (15, 4236, 'CU16 GDR', 'https://support.microsoft.com/en-us/help/5014353', '2022-06-14', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 16 GDR'), @@ -36528,6 +36692,7 @@ VALUES (15, 4003, 'CU1', 'https://support.microsoft.com/en-us/help/4527376', '2020-01-07', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 1 '), (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 '), + (14, 3460, 'RTM CU31 GDR', 'https://support.microsoft.com/kb/5021126', '2023-02-14', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 31 GDR'), (14, 3456, 'RTM CU31', 'https://support.microsoft.com/en-us/help/5016884', '2022-09-20', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 31'), (14, 3451, 'RTM CU30', 'https://support.microsoft.com/en-us/help/5013756', '2022-07-13', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 30'), (14, 3445, 'RTM CU29 GDR', 'https://support.microsoft.com/en-us/help/5014553', '2022-06-14', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 29 GDR'), @@ -36564,6 +36729,7 @@ VALUES (14, 1000, 'RTM ', '', '2017-10-02', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM '), (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, 6430, 'SP3 GDR', 'https://support.microsoft.com/kb/5021129', '2023-02-14', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3 GDR'), (13, 6419, 'SP3 GDR', 'https://support.microsoft.com/en-us/help/5014355', '2022-06-14', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3 GDR'), (13, 6404, 'SP3 GDR', 'https://support.microsoft.com/en-us/help/5006943', '2021-10-27', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3 GDR'), (13, 6300, 'SP3 ', 'https://support.microsoft.com/en-us/help/5003279', '2021-09-15', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3'), @@ -36615,6 +36781,7 @@ VALUES (13, 2164, 'RTM CU2', 'https://support.microsoft.com/en-us/help/3182270 ', '2016-09-22', '2018-01-09', '2018-01-09', 'SQL Server 2016', 'RTM Cumulative Update 2'), (13, 2149, 'RTM CU1', 'https://support.microsoft.com/en-us/help/3164674 ', '2016-07-25', '2018-01-09', '2018-01-09', 'SQL Server 2016', 'RTM Cumulative Update 1'), (13, 1601, 'RTM ', '', '2016-06-01', '2019-01-09', '2019-01-09', 'SQL Server 2016', 'RTM '), + (12, 6444, 'SP3 CU4 GDR', 'https://support.microsoft.com/kb/5021045', '2023-02-14', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 4 GDR'), (12, 6439, 'SP3 CU4 GDR', 'https://support.microsoft.com/en-us/help/5014164', '2022-06-14', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 4 GDR'), (12, 6433, 'SP3 CU4 GDR', 'https://support.microsoft.com/en-us/help/4583462', '2021-01-12', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 4 GDR'), (12, 6372, 'SP3 CU4 GDR', 'https://support.microsoft.com/en-us/help/4535288', '2020-02-11', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 4 GDR'), @@ -36921,7 +37088,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.12', @VersionDate = '20221213'; +SELECT @Version = '8.13', @VersionDate = '20230215'; IF(@VersionCheckMode = 1) BEGIN @@ -36961,7 +37128,7 @@ https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/ MIT License -Copyright (c) 2021 Brent Ozar Unlimited +Copyright (c) Brent Ozar Unlimited Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -39168,8 +39335,9 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, CROSS APPLY sys.dm_exec_query_plan(r.plan_handle) AS qp '; IF EXISTS (SELECT * FROM sys.all_objects WHERE name = 'dm_exec_query_statistics_xml') - SET @StringToExecute = @StringToExecute + N' OUTER APPLY sys.dm_exec_query_statistics_xml(s.session_id) qs_live '; - + /* GitHub #3210 */ + SET @StringToExecute = N' + SET LOCK_TIMEOUT 1000 ' + @StringToExecute + N' OUTER APPLY sys.dm_exec_query_statistics_xml(s.session_id) qs_live '; SET @StringToExecute = @StringToExecute + N'; diff --git a/sp_AllNightLog.sql b/sp_AllNightLog.sql index 9e5df6eb6..77a410ae0 100644 --- a/sp_AllNightLog.sql +++ b/sp_AllNightLog.sql @@ -31,7 +31,7 @@ SET STATISTICS XML OFF; BEGIN; -SELECT @Version = '8.12', @VersionDate = '20221213'; +SELECT @Version = '8.13', @VersionDate = '20230215'; IF(@VersionCheckMode = 1) BEGIN @@ -83,7 +83,7 @@ BEGIN MIT License - Copyright (c) 2021 Brent Ozar Unlimited + Copyright (c) Brent Ozar Unlimited Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/sp_AllNightLog_Setup.sql b/sp_AllNightLog_Setup.sql index ba77dbfca..cbcd2924b 100644 --- a/sp_AllNightLog_Setup.sql +++ b/sp_AllNightLog_Setup.sql @@ -38,7 +38,7 @@ SET STATISTICS XML OFF; BEGIN; -SELECT @Version = '8.12', @VersionDate = '20221213'; +SELECT @Version = '8.13', @VersionDate = '20230215'; IF(@VersionCheckMode = 1) BEGIN @@ -119,7 +119,7 @@ BEGIN MIT License - Copyright (c) 2021 Brent Ozar Unlimited + Copyright (c) Brent Ozar Unlimited Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/sp_Blitz.sql b/sp_Blitz.sql index ab74b6ac3..d0f77cc32 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -38,7 +38,7 @@ AS SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.12', @VersionDate = '20221213'; + SELECT @Version = '8.13', @VersionDate = '20230215'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -93,9 +93,9 @@ AS tigertoolbox and are provided under the MIT license: https://github.com/Microsoft/tigertoolbox - All other copyrights for sp_Blitz are held by Brent Ozar Unlimited, 2021. + All other copyrights for sp_Blitz are held by Brent Ozar Unlimited. - Copyright (c) 2021 Brent Ozar Unlimited + Copyright (c) Brent Ozar Unlimited Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/sp_BlitzAnalysis.sql b/sp_BlitzAnalysis.sql index e11f351cd..8a9447349 100644 --- a/sp_BlitzAnalysis.sql +++ b/sp_BlitzAnalysis.sql @@ -37,7 +37,7 @@ AS SET NOCOUNT ON; SET STATISTICS XML OFF; -SELECT @Version = '8.12', @VersionDate = '20221213'; +SELECT @Version = '8.13', @VersionDate = '20230215'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_BlitzBackups.sql b/sp_BlitzBackups.sql index a2d353e44..15d47a7fd 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.12', @VersionDate = '20221213'; + SELECT @Version = '8.13', @VersionDate = '20230215'; IF(@VersionCheckMode = 1) BEGIN @@ -71,7 +71,7 @@ AS MIT License - Copyright (c) 2021 Brent Ozar Unlimited + Copyright (c) Brent Ozar Unlimited Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/sp_BlitzCache.sql b/sp_BlitzCache.sql index ad39383be..974dec391 100644 --- a/sp_BlitzCache.sql +++ b/sp_BlitzCache.sql @@ -281,7 +281,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.12', @VersionDate = '20221213'; +SELECT @Version = '8.13', @VersionDate = '20230215'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -305,7 +305,6 @@ IF @Help = 1 the findings, contribute your own code, and more. Known limitations of this version: - - This query will not run on SQL Server 2005. - SQL Server 2008 and 2008R2 have a bug in trigger stats, so that output is excluded by default. - @IgnoreQueryHashes and @OnlyQueryHashes require a CSV list of hashes @@ -321,7 +320,7 @@ IF @Help = 1 MIT License - Copyright (c) 2021 Brent Ozar Unlimited + Copyright (c) Brent Ozar Unlimited Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/sp_BlitzFirst.sql b/sp_BlitzFirst.sql index 7dbcfd5d1..5ce7ad5db 100644 --- a/sp_BlitzFirst.sql +++ b/sp_BlitzFirst.sql @@ -46,7 +46,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.12', @VersionDate = '20221213'; +SELECT @Version = '8.13', @VersionDate = '20230215'; IF(@VersionCheckMode = 1) BEGIN @@ -86,7 +86,7 @@ https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/ MIT License -Copyright (c) 2021 Brent Ozar Unlimited +Copyright (c) Brent Ozar Unlimited Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/sp_BlitzInMemoryOLTP.sql b/sp_BlitzInMemoryOLTP.sql index 07d0a10aa..1dda6aba1 100644 --- a/sp_BlitzInMemoryOLTP.sql +++ b/sp_BlitzInMemoryOLTP.sql @@ -82,7 +82,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI */ AS DECLARE @ScriptVersion VARCHAR(30); -SELECT @ScriptVersion = '1.8', @VersionDate = '20221213'; +SELECT @ScriptVersion = '1.8', @VersionDate = '20230215'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index 7d0d71f90..0c6dd6b00 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -48,7 +48,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.12', @VersionDate = '20221213'; +SELECT @Version = '8.13', @VersionDate = '20230215'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -85,7 +85,7 @@ Unknown limitations of this version: MIT License -Copyright (c) 2021 Brent Ozar Unlimited +Copyright (c) Brent Ozar Unlimited Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index 2d5f3b356..26e781850 100644 --- a/sp_BlitzLock.sql +++ b/sp_BlitzLock.sql @@ -35,7 +35,7 @@ BEGIN SET NOCOUNT, XACT_ABORT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.12', @VersionDate = '20221213'; + SELECT @Version = '8.13', @VersionDate = '20230215'; IF @VersionCheckMode = 1 BEGIN @@ -96,7 +96,7 @@ BEGIN MIT License - Copyright (c) 2022 Brent Ozar Unlimited + Copyright (c) Brent Ozar Unlimited Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/sp_BlitzQueryStore.sql b/sp_BlitzQueryStore.sql index 6a8ddd11f..3d8dd5cf1 100644 --- a/sp_BlitzQueryStore.sql +++ b/sp_BlitzQueryStore.sql @@ -57,7 +57,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.12', @VersionDate = '20221213'; +SELECT @Version = '8.13', @VersionDate = '20230215'; IF(@VersionCheckMode = 1) BEGIN RETURN; @@ -143,7 +143,7 @@ IF @Help = 1 MIT License - Copyright (c) 2021 Brent Ozar Unlimited + Copyright (c) Brent Ozar Unlimited Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/sp_BlitzWho.sql b/sp_BlitzWho.sql index 429ec5a49..da7565ec6 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.12', @VersionDate = '20221213'; + SELECT @Version = '8.13', @VersionDate = '20230215'; IF(@VersionCheckMode = 1) BEGIN @@ -61,7 +61,7 @@ Known limitations of this version: MIT License -Copyright (c) 2021 Brent Ozar Unlimited +Copyright (c) Brent Ozar Unlimited Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/sp_DatabaseRestore.sql b/sp_DatabaseRestore.sql index 8e5df5062..36109fbc8 100755 --- a/sp_DatabaseRestore.sql +++ b/sp_DatabaseRestore.sql @@ -42,7 +42,7 @@ SET STATISTICS XML OFF; /*Versioning details*/ -SELECT @Version = '8.12', @VersionDate = '20221213'; +SELECT @Version = '8.13', @VersionDate = '20230215'; IF(@VersionCheckMode = 1) BEGIN @@ -74,7 +74,7 @@ BEGIN MIT License - Copyright (c) 2021 Brent Ozar Unlimited + Copyright (c) Brent Ozar Unlimited Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/sp_ineachdb.sql b/sp_ineachdb.sql index 2b5c8fe45..6ec373ba6 100644 --- a/sp_ineachdb.sql +++ b/sp_ineachdb.sql @@ -35,7 +35,7 @@ BEGIN SET NOCOUNT ON; SET STATISTICS XML OFF; - SELECT @Version = '8.12', @VersionDate = '20221213'; + SELECT @Version = '8.13', @VersionDate = '20230215'; IF(@VersionCheckMode = 1) BEGIN @@ -68,7 +68,7 @@ BEGIN MIT License - Copyright (c) 2021 Brent Ozar Unlimited + Copyright (c) Brent Ozar Unlimited Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From a2f0a2e464d23ee310815a2dec25db78f505bd32 Mon Sep 17 00:00:00 2001 From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com> Date: Wed, 22 Feb 2023 07:55:21 -0500 Subject: [PATCH 383/662] Update sp_BlitzLock.sql Filter on the `object_name` from `fn_xe_file_target_read_file` --- sp_BlitzLock.sql | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index 26e781850..c25fa1772 100644 --- a/sp_BlitzLock.sql +++ b/sp_BlitzLock.sql @@ -905,12 +905,13 @@ BEGIN SELECT deadlock_xml = TRY_CAST(fx.event_data AS xml) - FROM sys.fn_xe_file_target_read_file('system_health*.xel', NULL, NULL, NULL) AS fx + FROM sys.fn_xe_file_target_read_file(N'system_health*.xel', NULL, NULL, NULL) AS fx LEFT JOIN #t AS t ON 1 = 1 + WHERE fx.object_name = N'xml_deadlock_report' ) AS xml CROSS APPLY xml.deadlock_xml.nodes('/event') AS e(x) - WHERE e.x.exist('@name[ . = "xml_deadlock_report"]') = 1 + WHERE 1 = 1 AND e.x.exist('@timestamp[. >= sql:variable("@StartDate")]') = 1 AND e.x.exist('@timestamp[. < sql:variable("@EndDate")]') = 1 OPTION(RECOMPILE); From 05ded7c20d16b8f9971dcbb03c3490b399c51b3f Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Thu, 2 Mar 2023 10:49:49 -0800 Subject: [PATCH 384/662] #3237 sp_BlitzFirst Remove @@ROWCOUNT To prevent startup problems with Hekaton. Closes #3237. --- sp_BlitzFirst.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sp_BlitzFirst.sql b/sp_BlitzFirst.sql index 5ce7ad5db..42ba693df 100644 --- a/sp_BlitzFirst.sql +++ b/sp_BlitzFirst.sql @@ -3146,7 +3146,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, /* Check for temp objects with high forwarded fetches. This has to be done as dynamic SQL because we have to execute OBJECT_NAME inside TempDB. */ - IF @@ROWCOUNT > 0 + IF EXISTS (SELECT * FROM #BlitzFirstResults WHERE CheckID = 29) BEGIN SET @StringToExecute = N' INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) From 04c9e6f7c48f383066382bc68b803597ec1ff744 Mon Sep 17 00:00:00 2001 From: spurdata Date: Sat, 4 Mar 2023 20:21:05 -0600 Subject: [PATCH 385/662] Added @FileExtensionDiff param. Fixes #3234. --- sp_DatabaseRestore.sql | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/sp_DatabaseRestore.sql b/sp_DatabaseRestore.sql index 36109fbc8..a66abe79c 100755 --- a/sp_DatabaseRestore.sql +++ b/sp_DatabaseRestore.sql @@ -31,6 +31,7 @@ ALTER PROCEDURE [dbo].[sp_DatabaseRestore] @DatabaseOwner sysname = NULL, @SetTrustworthyON BIT = 0, @Execute CHAR(1) = Y, + @FileExtensionDiff NVARCHAR(128) = NULL, @Debug INT = 0, @Help BIT = 0, @Version VARCHAR(30) = NULL OUTPUT, @@ -158,6 +159,16 @@ BEGIN @RunRecovery = 0, @Debug = 0; + --Restore just through the latest DIFF, ignoring logs, and using a custom ".dif" file extension + EXEC dbo.sp_DatabaseRestore + @Database = ''LogShipMe'', + @BackupPathFull = ''D:\Backup\SQL2016PROD1A\LogShipMe\FULL\'', + @BackupPathDiff = ''D:\Backup\SQL2016PROD1A\LogShipMe\DIFF\'', + @RestoreDiff = 1, + @FileExtensionDiff = ''dif'', + @ContinueLogs = 0, + @RunRecovery = 1; + -- Restore from stripped backup set when multiple paths are used. This example will restore stripped full backup set along with stripped transactional logs set from multiple backup paths EXEC dbo.sp_DatabaseRestore @Database = ''DBA'', @@ -500,6 +511,13 @@ BEGIN END END +--File Extension cleanup +IF @FileExtensionDiff LIKE '%.%' +BEGIN + IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Removing "." from @FileExtensionDiff', 0, 1) WITH NOWAIT; + SET @FileExtensionDiff = REPLACE(@FileExtensionDiff,'.',''); +END + SET @RestoreDatabaseID = DB_ID(@RestoreDatabaseName); SET @RestoreDatabaseName = QUOTENAME(@RestoreDatabaseName); SET @UnquotedRestoreDatabaseName = PARSENAME(@RestoreDatabaseName,1); @@ -1044,9 +1062,15 @@ BEGIN END /*End folder sanity check*/ -- Find latest diff backup + IF @FileExtensionDiff IS NULL + BEGIN + IF @Execute = 'Y' OR @Debug = 1 RAISERROR('No @FileExtensionDiff given, assuming "bak".', 0, 1) WITH NOWAIT; + SET @FileExtensionDiff = 'bak'; + END + SELECT TOP 1 @LastDiffBackup = BackupFile, @CurrentBackupPathDiff = BackupPath FROM @FileList - WHERE BackupFile LIKE N'%.bak' + WHERE BackupFile LIKE N'%.' + @FileExtensionDiff AND BackupFile LIKE N'%' + @Database + '%' AND From 7aca53b9a972f51ff77243dcc7fde9706c778d70 Mon Sep 17 00:00:00 2001 From: Paul Lambert Date: Mon, 6 Mar 2023 09:44:22 +0800 Subject: [PATCH 386/662] Fix dm_exec_query_profiles arithmetic overflow error in check 45 --- sp_BlitzFirst.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sp_BlitzFirst.sql b/sp_BlitzFirst.sql index 42ba693df..b6793e338 100644 --- a/sp_BlitzFirst.sql +++ b/sp_BlitzFirst.sql @@ -2163,7 +2163,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, FROM ( SELECT deqp.session_id, deqp.request_id, - CASE WHEN deqp.row_count > ( deqp.estimate_row_count * 10000 ) + CASE WHEN (deqp.row_count/10000) > deqp.estimate_row_count THEN 1 ELSE 0 END AS estimate_inaccuracy From 3f07bd295c27914676109bfec3953c253d342e79 Mon Sep 17 00:00:00 2001 From: Edgar Walther Date: Mon, 3 Apr 2023 18:38:47 +0000 Subject: [PATCH 387/662] Exclude Erik Darling's sp_PressureDetector from the "...has WITH RECOMPILE in the stored procedure code..."check. (Issue 3244) --- sp_Blitz.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index d0f77cc32..92bc1e08f 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -6870,7 +6870,7 @@ IF @ProductVersionMajor >= 10 Details = '[' + DBName + '].[' + SPSchema + '].[' + ProcName + '] has WITH RECOMPILE in the stored procedure code, which may cause increased CPU usage due to constant recompiles of the code.', CheckID = '78' FROM #Recompile AS TR - WHERE ProcName NOT LIKE 'sp_AllNightLog%' AND ProcName NOT LIKE 'sp_AskBrent%' AND ProcName NOT LIKE 'sp_Blitz%' + WHERE ProcName NOT LIKE 'sp_AllNightLog%' AND ProcName NOT LIKE 'sp_AskBrent%' AND ProcName NOT LIKE 'sp_Blitz%' AND ProcName NOT LIKE 'sp_PressureDetector' AND DBName NOT IN ('master', 'model', 'msdb', 'tempdb'); DROP TABLE #Recompile; END; From 3933fcd0e85b1dc0c02aefbe3799e8e2be647209 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Thu, 6 Apr 2023 09:39:02 -0700 Subject: [PATCH 388/662] Contributing.md - updating link to Rob Sewell's new domain Closes #3246. --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 77d099d60..ae729eeb3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -56,7 +56,7 @@ We know that's a pain, but that's the kind of thing we find out in the wild. Of Rather than give you step-by-step instructions here, we'd rather link you to the work of others: -* [How to fork a GitHub repository and contribute to an open source project](https://sqldbawithabeard.com/2019/11/29/how-to-fork-a-github-repository-and-contribute-to-an-open-source-project/) +* [How to fork a GitHub repository and contribute to an open source project](https://blog.robsewell.com/blog/how-to-fork-a-github-repository-and-contribute-to-an-open-source-project/) From bebeb4e6a870948db256a8b933a349e03d44424b Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Thu, 6 Apr 2023 09:47:51 -0700 Subject: [PATCH 389/662] sp_BlitzWho checking for 1900 For invalid query start times that cause overflows. Closes #3243. --- sp_BlitzWho.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sp_BlitzWho.sql b/sp_BlitzWho.sql index da7565ec6..8df031f37 100644 --- a/sp_BlitzWho.sql +++ b/sp_BlitzWho.sql @@ -676,7 +676,7 @@ BEGIN /* Think of the StringToExecute as starting with this, but we'll set this up later depending on whether we're doing an insert or a select: SELECT @StringToExecute = N'SELECT GETDATE() AS run_date , */ - SET @StringToExecute = N'COALESCE( RIGHT(''00'' + CONVERT(VARCHAR(20), (ABS(r.total_elapsed_time) / 1000) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), (DATEADD(SECOND, (r.total_elapsed_time / 1000), 0) + DATEADD(MILLISECOND, (r.total_elapsed_time % 1000), 0)), 114), RIGHT(''00'' + CONVERT(VARCHAR(20), DATEDIFF(SECOND, s.last_request_start_time, GETDATE()) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), DATEADD(SECOND, DATEDIFF(SECOND, s.last_request_start_time, GETDATE()), 0), 114) ) AS [elapsed_time] , + SET @StringToExecute = N' CASE WHEN YEAR(s.last_request_start_time) = 1900 THEN NULL ELSE COALESCE( RIGHT(''00'' + CONVERT(VARCHAR(20), (ABS(r.total_elapsed_time) / 1000) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), (DATEADD(SECOND, (r.total_elapsed_time / 1000), 0) + DATEADD(MILLISECOND, (r.total_elapsed_time % 1000), 0)), 114), RIGHT(''00'' + CONVERT(VARCHAR(20), DATEDIFF(SECOND, s.last_request_start_time, GETDATE()) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), DATEADD(SECOND, DATEDIFF(SECOND, s.last_request_start_time, GETDATE()), 0), 114) ) END AS [elapsed_time] , s.session_id , CASE WHEN r.blocking_session_id <> 0 AND blocked.session_id IS NULL THEN r.blocking_session_id @@ -894,7 +894,7 @@ IF @ProductVersionMajor >= 11 /* Think of the StringToExecute as starting with this, but we'll set this up later depending on whether we're doing an insert or a select: SELECT @StringToExecute = N'SELECT GETDATE() AS run_date , */ - SELECT @StringToExecute = N'COALESCE( RIGHT(''00'' + CONVERT(VARCHAR(20), (ABS(r.total_elapsed_time) / 1000) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), (DATEADD(SECOND, (r.total_elapsed_time / 1000), 0) + DATEADD(MILLISECOND, (r.total_elapsed_time % 1000), 0)), 114), RIGHT(''00'' + CONVERT(VARCHAR(20), DATEDIFF(SECOND, s.last_request_start_time, GETDATE()) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), DATEADD(SECOND, DATEDIFF(SECOND, s.last_request_start_time, GETDATE()), 0), 114) ) AS [elapsed_time] , + SELECT @StringToExecute = N' CASE WHEN YEAR(s.last_request_start_time) = 1900 THEN NULL ELSE COALESCE( RIGHT(''00'' + CONVERT(VARCHAR(20), (ABS(r.total_elapsed_time) / 1000) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), (DATEADD(SECOND, (r.total_elapsed_time / 1000), 0) + DATEADD(MILLISECOND, (r.total_elapsed_time % 1000), 0)), 114), RIGHT(''00'' + CONVERT(VARCHAR(20), DATEDIFF(SECOND, s.last_request_start_time, GETDATE()) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), DATEADD(SECOND, DATEDIFF(SECOND, s.last_request_start_time, GETDATE()), 0), 114) ) END AS [elapsed_time] , s.session_id , CASE WHEN r.blocking_session_id <> 0 AND blocked.session_id IS NULL THEN r.blocking_session_id From 70920deec2025c746827ea49afaaa0ee5f80fecb Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Sat, 15 Apr 2023 08:02:29 -0700 Subject: [PATCH 390/662] SqlServerVersions.sql Adding new builds. --- SqlServerVersions.sql | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/SqlServerVersions.sql b/SqlServerVersions.sql index d7bffc635..d1478c7bf 100644 --- a/SqlServerVersions.sql +++ b/SqlServerVersions.sql @@ -41,8 +41,13 @@ DELETE FROM dbo.SqlServerVersions; INSERT INTO dbo.SqlServerVersions (MajorVersionNumber, MinorVersionNumber, Branch, [Url], ReleaseDate, MainstreamSupportEndDate, ExtendedSupportEndDate, MajorVersionName, MinorVersionName) VALUES + (16, 4025, 'CU3', 'https://learn.microsoft.com/en-us/troubleshoot/sql/releases/sqlserver-2022/cumulativeupdate3', '2023-04-13', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 3'), + (16, 4015, 'CU2', 'https://learn.microsoft.com/en-us/troubleshoot/sql/releases/sqlserver-2022/cumulativeupdate2', '2023-03-15', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 2'), + (16, 4003, 'CU1', 'https://learn.microsoft.com/en-us/troubleshoot/sql/releases/sqlserver-2022/cumulativeupdate1', '2023-02-16', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 1'), (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'), + (15, 4312, 'CU20', 'https://support.microsoft.com/kb/5024276', '2023-04-13', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 20'), + (15, 4298, 'CU19', 'https://support.microsoft.com/kb/5023049', '2023-02-16', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 19'), (15, 4280, 'CU18 GDR', 'https://support.microsoft.com/kb/5021124', '2023-02-14', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 18 GDR'), (15, 4261, 'CU18', 'https://support.microsoft.com/en-us/help/5017593', '2022-09-28', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 18'), (15, 4249, 'CU17', 'https://support.microsoft.com/en-us/help/5016394', '2022-08-11', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 17'), From 84813711d6cb5060b83b1747a6f7f4e1cfc68814 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Tue, 18 Apr 2023 11:56:25 -0700 Subject: [PATCH 391/662] #3253 sp_BlitzFirst estimation filter Only alert on queries running longer than 5 seconds. Closes #3253. --- sp_BlitzFirst.sql | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sp_BlitzFirst.sql b/sp_BlitzFirst.sql index b6793e338..826b17ac3 100644 --- a/sp_BlitzFirst.sql +++ b/sp_BlitzFirst.sql @@ -2168,7 +2168,9 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, ELSE 0 END AS estimate_inaccuracy FROM sys.dm_exec_query_profiles AS deqp + INNER JOIN sys.dm_exec_requests r ON deqp.session_id = r.session_id AND deqp.request_id = r.request_id WHERE deqp.session_id <> @@SPID + AND r.total_elapsed_time > 5000 ) AS x WHERE x.estimate_inaccuracy = 1 GROUP BY x.session_id, From 63a03a77598056cfdc44cee827fc9b71fb19431e Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Tue, 18 Apr 2023 14:06:06 -0700 Subject: [PATCH 392/662] #3255 BlitzFirst optional output sets New OutputResultSets param lets you skip output sets. Closes #3255. --- sp_BlitzFirst.sql | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/sp_BlitzFirst.sql b/sp_BlitzFirst.sql index 826b17ac3..edf051b68 100644 --- a/sp_BlitzFirst.sql +++ b/sp_BlitzFirst.sql @@ -19,6 +19,7 @@ ALTER PROCEDURE [dbo].[sp_BlitzFirst] @OutputTableNameWaitStats NVARCHAR(256) = NULL , @OutputTableNameBlitzCache NVARCHAR(256) = NULL , @OutputTableNameBlitzWho NVARCHAR(256) = NULL , + @OutputResultSets NVARCHAR(500) = N'BlitzWho_Start|Findings|FileStats|PerfmonStats|WaitStats|BlitzCache|BlitzWho_End' , @OutputTableRetentionDays TINYINT = 7 , @OutputXMLasNVARCHAR TINYINT = 0 , @FilterPlansByDatabase VARCHAR(MAX) = NULL , @@ -262,7 +263,7 @@ END; /* IF @AsOf IS NOT NULL AND @OutputDatabaseName IS NOT NULL AND @OutputSche ELSE IF @LogMessage IS NULL /* IF @OutputType = 'SCHEMA' */ BEGIN /* What's running right now? This is the first and last result set. */ - IF @SinceStartup = 0 AND @Seconds > 0 AND @ExpertMode = 1 AND @OutputType <> 'NONE' + IF @SinceStartup = 0 AND @Seconds > 0 AND @ExpertMode = 1 AND @OutputType <> 'NONE' AND @OutputResultSets LIKE N'%BlitzWho_Start%' BEGIN IF OBJECT_ID('master.dbo.sp_BlitzWho') IS NULL AND OBJECT_ID('dbo.sp_BlitzWho') IS NULL BEGIN @@ -4508,7 +4509,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, FROM #BlitzFirstResults; END; ELSE - IF @OutputType = 'Opserver1' AND @SinceStartup = 0 + IF @OutputType = 'Opserver1' AND @SinceStartup = 0 AND @OutputResultSets LIKE N'%Findings%' BEGIN SELECT r.[Priority] , @@ -4565,7 +4566,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, r.Finding, r.ID; END; - ELSE IF @OutputType IN ( 'CSV', 'RSV' ) AND @SinceStartup = 0 + ELSE IF @OutputType IN ( 'CSV', 'RSV' ) AND @SinceStartup = 0 AND @OutputResultSets LIKE N'%Findings%' BEGIN SELECT Result = CAST([Priority] AS NVARCHAR(100)) @@ -4586,7 +4587,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, Finding, Details; END; - ELSE IF @OutputType = 'Top10' + ELSE IF @OutputType = 'Top10' AND @OutputResultSets LIKE N'%WaitStats%' BEGIN /* Measure waits in hours */ ;WITH max_batch AS ( @@ -4623,7 +4624,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, AND wd2.wait_time_ms-wd1.wait_time_ms > 0 ORDER BY [Wait Time (Seconds)] DESC; END; - ELSE IF @ExpertMode = 0 AND @OutputType <> 'NONE' AND @OutputXMLasNVARCHAR = 0 AND @SinceStartup = 0 + ELSE IF @ExpertMode = 0 AND @OutputType <> 'NONE' AND @OutputXMLasNVARCHAR = 0 AND @SinceStartup = 0 AND @OutputResultSets LIKE N'%Findings%' BEGIN SELECT [Priority] , [FindingsGroup] , @@ -4645,7 +4646,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, ID, CAST(Details AS NVARCHAR(4000)); END; - ELSE IF @OutputType <> 'NONE' AND @OutputXMLasNVARCHAR = 1 AND @SinceStartup = 0 + ELSE IF @OutputType <> 'NONE' AND @OutputXMLasNVARCHAR = 1 AND @SinceStartup = 0 AND @OutputResultSets LIKE N'%Findings%' BEGIN SELECT [Priority] , [FindingsGroup] , @@ -4667,7 +4668,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, ID, CAST(Details AS NVARCHAR(4000)); END; - ELSE IF @ExpertMode = 1 AND @OutputType <> 'NONE' + ELSE IF @ExpertMode = 1 AND @OutputType <> 'NONE' AND @OutputResultSets LIKE N'%Findings%' BEGIN IF @SinceStartup = 0 SELECT r.[Priority] , @@ -4729,7 +4730,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, ------------------------- --What happened: #WaitStats ------------------------- - IF @Seconds = 0 + IF @Seconds = 0 AND @OutputResultSets LIKE N'%WaitStats%' BEGIN /* Measure waits in hours */ ;WITH max_batch AS ( @@ -4773,7 +4774,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, AND wd2.wait_time_ms-wd1.wait_time_ms > 0 ORDER BY [Wait Time (Seconds)] DESC; END; - ELSE + ELSE IF @OutputResultSets LIKE N'%WaitStats%' BEGIN /* Measure waits in seconds */ ;WITH max_batch AS ( @@ -4821,6 +4822,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, ------------------------- --What happened: #FileStats ------------------------- + IF @OutputResultSets LIKE N'%FileStats%' WITH readstats AS ( SELECT 'PHYSICAL READS' AS Pattern, ROW_NUMBER() OVER (ORDER BY wd2.avg_stall_read_ms DESC) AS StallRank, @@ -4879,7 +4881,8 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, --What happened: #PerfmonStats ------------------------- - SELECT 'PERFMON' AS Pattern, pLast.[object_name], pLast.counter_name, pLast.instance_name, + IF @OutputResultSets LIKE N'%PerfmonStats%' + SELECT 'PERFMON' AS Pattern, pLast.[object_name], pLast.counter_name, pLast.instance_name, pFirst.SampleTime AS FirstSampleTime, pFirst.cntr_value AS FirstSampleValue, pLast.SampleTime AS LastSampleTime, pLast.cntr_value AS LastSampleValue, pLast.cntr_value - pFirst.cntr_value AS ValueDelta, @@ -4894,7 +4897,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, ------------------------- --What happened: #QueryStats ------------------------- - IF @CheckProcedureCache = 1 + IF @CheckProcedureCache = 1 AND @OutputResultSets LIKE N'%BlitzCache%' BEGIN SELECT qsNow.*, qsFirst.* @@ -4911,7 +4914,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, DROP TABLE #BlitzFirstResults; /* What's running right now? This is the first and last result set. */ - IF @SinceStartup = 0 AND @Seconds > 0 AND @ExpertMode = 1 AND @OutputType <> 'NONE' + IF @SinceStartup = 0 AND @Seconds > 0 AND @ExpertMode = 1 AND @OutputType <> 'NONE' AND @OutputResultSets LIKE N'%BlitzWho_End%' BEGIN IF OBJECT_ID('master.dbo.sp_BlitzWho') IS NULL AND OBJECT_ID('dbo.sp_BlitzWho') IS NULL BEGIN From 043139dd6840e05fdbdf595281a3c5796737ffdf Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Tue, 18 Apr 2023 15:25:30 -0700 Subject: [PATCH 393/662] #3257 sp_BlitzFirst performance tuning Doing early filtering to avoid getting all locks. Closes #3257. --- sp_BlitzFirst.sql | 336 +++++++++++++++++++++++----------------------- 1 file changed, 171 insertions(+), 165 deletions(-) diff --git a/sp_BlitzFirst.sql b/sp_BlitzFirst.sql index edf051b68..05e8cdc26 100644 --- a/sp_BlitzFirst.sql +++ b/sp_BlitzFirst.sql @@ -1511,44 +1511,45 @@ BEGIN RAISERROR('Running CheckID 1',10,1) WITH NOWAIT; END - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, QueryHash) - SELECT 1 AS CheckID, - 1 AS Priority, - 'Maintenance Tasks Running' AS FindingGroup, - 'Backup Running' AS Finding, - 'https://www.brentozar.com/askbrent/backups/' AS URL, - 'Backup of ' + DB_NAME(db.resource_database_id) + ' database (' + (SELECT CAST(CAST(SUM(size * 8.0 / 1024 / 1024) AS BIGINT) AS NVARCHAR) FROM #MasterFiles WHERE database_id = db.resource_database_id) + 'GB) ' + @LineFeed - + CAST(r.percent_complete AS NVARCHAR(100)) + '% complete, has been running since ' + CAST(r.start_time AS NVARCHAR(100)) + '. ' + @LineFeed - + CASE WHEN COALESCE(s.nt_username, s.loginame) IS NOT NULL THEN (' Login: ' + COALESCE(s.nt_username, s.loginame) + ' ') ELSE '' END AS Details, - 'KILL ' + CAST(r.session_id AS NVARCHAR(100)) + ';' AS HowToStopIt, - pl.query_plan AS QueryPlan, - r.start_time AS StartTime, - s.loginame AS LoginName, - s.nt_username AS NTUserName, - s.[program_name] AS ProgramName, - s.[hostname] AS HostName, - db.[resource_database_id] AS DatabaseID, - DB_NAME(db.resource_database_id) AS DatabaseName, - 0 AS OpenTransactionCount, - r.query_hash - FROM sys.dm_exec_requests r - INNER JOIN sys.dm_exec_connections c ON r.session_id = c.session_id - INNER JOIN sys.sysprocesses AS s ON r.session_id = s.spid AND s.ecid = 0 - INNER JOIN - ( - SELECT DISTINCT - t.request_session_id, - t.resource_database_id - FROM sys.dm_tran_locks AS t - WHERE t.resource_type = N'DATABASE' - AND t.request_mode = N'S' - AND t.request_status = N'GRANT' - AND t.request_owner_type = N'SHARED_TRANSACTION_WORKSPACE' - ) AS db ON s.spid = db.request_session_id AND s.dbid = db.resource_database_id - CROSS APPLY sys.dm_exec_query_plan(r.plan_handle) pl - WHERE r.command LIKE 'BACKUP%' - AND r.start_time <= DATEADD(minute, -5, GETDATE()) - AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs); + IF EXISTS(SELECT * FROM sys.dm_exec_requests WHERE total_elapsed_time > 300000 AND command LIKE 'BACKUP%') + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, QueryHash) + SELECT 1 AS CheckID, + 1 AS Priority, + 'Maintenance Tasks Running' AS FindingGroup, + 'Backup Running' AS Finding, + 'https://www.brentozar.com/askbrent/backups/' AS URL, + 'Backup of ' + DB_NAME(db.resource_database_id) + ' database (' + (SELECT CAST(CAST(SUM(size * 8.0 / 1024 / 1024) AS BIGINT) AS NVARCHAR) FROM #MasterFiles WHERE database_id = db.resource_database_id) + 'GB) ' + @LineFeed + + CAST(r.percent_complete AS NVARCHAR(100)) + '% complete, has been running since ' + CAST(r.start_time AS NVARCHAR(100)) + '. ' + @LineFeed + + CASE WHEN COALESCE(s.nt_username, s.loginame) IS NOT NULL THEN (' Login: ' + COALESCE(s.nt_username, s.loginame) + ' ') ELSE '' END AS Details, + 'KILL ' + CAST(r.session_id AS NVARCHAR(100)) + ';' AS HowToStopIt, + pl.query_plan AS QueryPlan, + r.start_time AS StartTime, + s.loginame AS LoginName, + s.nt_username AS NTUserName, + s.[program_name] AS ProgramName, + s.[hostname] AS HostName, + db.[resource_database_id] AS DatabaseID, + DB_NAME(db.resource_database_id) AS DatabaseName, + 0 AS OpenTransactionCount, + r.query_hash + FROM sys.dm_exec_requests r + INNER JOIN sys.dm_exec_connections c ON r.session_id = c.session_id + INNER JOIN sys.sysprocesses AS s ON r.session_id = s.spid AND s.ecid = 0 + INNER JOIN + ( + SELECT DISTINCT + t.request_session_id, + t.resource_database_id + FROM sys.dm_tran_locks AS t + WHERE t.resource_type = N'DATABASE' + AND t.request_mode = N'S' + AND t.request_status = N'GRANT' + AND t.request_owner_type = N'SHARED_TRANSACTION_WORKSPACE' + ) AS db ON s.spid = db.request_session_id AND s.dbid = db.resource_database_id + CROSS APPLY sys.dm_exec_query_plan(r.plan_handle) pl + WHERE r.command LIKE 'BACKUP%' + AND r.start_time <= DATEADD(minute, -5, GETDATE()) + AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs); END /* If there's a backup running, add details explaining how long full backup has been taking in the last month. */ @@ -1560,48 +1561,49 @@ BEGIN /* Maintenance Tasks Running - DBCC CHECK* Running - CheckID 2 */ - IF @Seconds > 0 AND EXISTS(SELECT * FROM sys.dm_exec_requests WHERE command LIKE 'DBCC%') + IF @Seconds > 0 BEGIN IF (@Debug = 1) BEGIN RAISERROR('Running CheckID 2',10,1) WITH NOWAIT; END - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, QueryHash) - SELECT 2 AS CheckID, - 1 AS Priority, - 'Maintenance Tasks Running' AS FindingGroup, - 'DBCC CHECK* Running' AS Finding, - 'https://www.brentozar.com/askbrent/dbcc/' AS URL, - 'Corruption check of ' + DB_NAME(db.resource_database_id) + ' database (' + (SELECT CAST(CAST(SUM(size * 8.0 / 1024 / 1024) AS BIGINT) AS NVARCHAR) FROM #MasterFiles WHERE database_id = db.resource_database_id) + 'GB) 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, - s.login_name AS LoginName, - s.nt_user_name AS NTUserName, - s.[program_name] AS ProgramName, - s.[host_name] AS HostName, - db.[resource_database_id] AS DatabaseID, - DB_NAME(db.resource_database_id) AS DatabaseName, - 0 AS OpenTransactionCount, - r.query_hash - FROM sys.dm_exec_requests r - INNER JOIN sys.dm_exec_connections c ON r.session_id = c.session_id - INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id - INNER JOIN (SELECT DISTINCT l.request_session_id, l.resource_database_id - FROM sys.dm_tran_locks l - INNER JOIN sys.databases d ON l.resource_database_id = d.database_id - WHERE l.resource_type = N'DATABASE' - AND l.request_mode = N'S' - AND l.request_status = N'GRANT' - AND l.request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.session_id = db.request_session_id - OUTER APPLY sys.dm_exec_query_plan(r.plan_handle) pl - OUTER APPLY sys.dm_exec_sql_text(r.sql_handle) AS t - WHERE r.command LIKE 'DBCC%' - AND CAST(t.text AS NVARCHAR(4000)) NOT LIKE '%dm_db_index_physical_stats%' - AND CAST(t.text AS NVARCHAR(4000)) NOT LIKE '%ALTER INDEX%' - AND CAST(t.text AS NVARCHAR(4000)) NOT LIKE '%fileproperty%' - AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs); + IF EXISTS (SELECT * FROM sys.dm_exec_requests WHERE command LIKE 'DBCC%' AND total_elapsed_time > 5000) + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, QueryHash) + SELECT 2 AS CheckID, + 1 AS Priority, + 'Maintenance Tasks Running' AS FindingGroup, + 'DBCC CHECK* Running' AS Finding, + 'https://www.brentozar.com/askbrent/dbcc/' AS URL, + 'Corruption check of ' + DB_NAME(db.resource_database_id) + ' database (' + (SELECT CAST(CAST(SUM(size * 8.0 / 1024 / 1024) AS BIGINT) AS NVARCHAR) FROM #MasterFiles WHERE database_id = db.resource_database_id) + 'GB) 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, + s.login_name AS LoginName, + s.nt_user_name AS NTUserName, + s.[program_name] AS ProgramName, + s.[host_name] AS HostName, + db.[resource_database_id] AS DatabaseID, + DB_NAME(db.resource_database_id) AS DatabaseName, + 0 AS OpenTransactionCount, + r.query_hash + FROM sys.dm_exec_requests r + INNER JOIN sys.dm_exec_connections c ON r.session_id = c.session_id + INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id + INNER JOIN (SELECT DISTINCT l.request_session_id, l.resource_database_id + FROM sys.dm_tran_locks l + INNER JOIN sys.databases d ON l.resource_database_id = d.database_id + WHERE l.resource_type = N'DATABASE' + AND l.request_mode = N'S' + AND l.request_status = N'GRANT' + AND l.request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.session_id = db.request_session_id + OUTER APPLY sys.dm_exec_query_plan(r.plan_handle) pl + OUTER APPLY sys.dm_exec_sql_text(r.sql_handle) AS t + WHERE r.command LIKE 'DBCC%' + AND CAST(t.text AS NVARCHAR(4000)) NOT LIKE '%dm_db_index_physical_stats%' + AND CAST(t.text AS NVARCHAR(4000)) NOT LIKE '%ALTER INDEX%' + AND CAST(t.text AS NVARCHAR(4000)) NOT LIKE '%fileproperty%' + AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs); END /* Maintenance Tasks Running - Restore Running - CheckID 3 */ @@ -1612,37 +1614,38 @@ BEGIN RAISERROR('Running CheckID 3',10,1) WITH NOWAIT; END - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, QueryHash) - SELECT 3 AS CheckID, - 1 AS Priority, - 'Maintenance Tasks Running' AS FindingGroup, - 'Restore Running' AS Finding, - 'https://www.brentozar.com/askbrent/backups/' AS URL, - 'Restore of ' + DB_NAME(db.resource_database_id) + ' database (' + (SELECT CAST(CAST(SUM(size * 8.0 / 1024 / 1024) AS BIGINT) AS NVARCHAR) FROM #MasterFiles WHERE database_id = db.resource_database_id) + '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, - s.login_name AS LoginName, - s.nt_user_name AS NTUserName, - s.[program_name] AS ProgramName, - s.[host_name] AS HostName, - db.[resource_database_id] AS DatabaseID, - DB_NAME(db.resource_database_id) AS DatabaseName, - 0 AS OpenTransactionCount, - r.query_hash - FROM sys.dm_exec_requests r - INNER JOIN sys.dm_exec_connections c ON r.session_id = c.session_id - INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id - INNER JOIN ( - SELECT DISTINCT request_session_id, resource_database_id - FROM sys.dm_tran_locks - WHERE resource_type = N'DATABASE' - AND request_mode = N'S' - AND request_status = N'GRANT') AS db ON s.session_id = db.request_session_id - CROSS APPLY sys.dm_exec_query_plan(r.plan_handle) pl - WHERE r.command LIKE 'RESTORE%' - AND s.program_name <> 'SQL Server Log Shipping' - AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs); + IF EXISTS (SELECT * FROM sys.dm_exec_requests WHERE command LIKE 'RESTORE%' AND total_elapsed_time > 5000) + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, QueryHash) + SELECT 3 AS CheckID, + 1 AS Priority, + 'Maintenance Tasks Running' AS FindingGroup, + 'Restore Running' AS Finding, + 'https://www.brentozar.com/askbrent/backups/' AS URL, + 'Restore of ' + DB_NAME(db.resource_database_id) + ' database (' + (SELECT CAST(CAST(SUM(size * 8.0 / 1024 / 1024) AS BIGINT) AS NVARCHAR) FROM #MasterFiles WHERE database_id = db.resource_database_id) + '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, + s.login_name AS LoginName, + s.nt_user_name AS NTUserName, + s.[program_name] AS ProgramName, + s.[host_name] AS HostName, + db.[resource_database_id] AS DatabaseID, + DB_NAME(db.resource_database_id) AS DatabaseName, + 0 AS OpenTransactionCount, + r.query_hash + FROM sys.dm_exec_requests r + INNER JOIN sys.dm_exec_connections c ON r.session_id = c.session_id + INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id + INNER JOIN ( + SELECT DISTINCT request_session_id, resource_database_id + FROM sys.dm_tran_locks + WHERE resource_type = N'DATABASE' + AND request_mode = N'S' + AND request_status = N'GRANT') AS db ON s.session_id = db.request_session_id + CROSS APPLY sys.dm_exec_query_plan(r.plan_handle) pl + WHERE r.command LIKE 'RESTORE%' + AND s.program_name <> 'SQL Server Log Shipping' + AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs); END /* SQL Server Internal Maintenance - Database File Growing - CheckID 4 */ @@ -1755,37 +1758,38 @@ BEGIN RAISERROR('Running CheckID 8',10,1) WITH NOWAIT; END - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, OpenTransactionCount) - SELECT 8 AS CheckID, - 50 AS Priority, - 'Query Problems' AS FindingGroup, - 'Sleeping Query with Open Transactions' AS Finding, - 'https://www.brentozar.com/askbrent/sleeping-query-with-open-transactions/' AS URL, - 'Database: ' + DB_NAME(db.resource_database_id) + @LineFeed + 'Host: ' + s.hostname + @LineFeed + 'Program: ' + s.[program_name] + @LineFeed + 'Asleep with open transactions and locks since ' + CAST(s.last_batch AS NVARCHAR(100)) + '. ' AS Details, - 'KILL ' + CAST(s.spid AS NVARCHAR(100)) + ';' AS HowToStopIt, - s.last_batch AS StartTime, - s.loginame AS LoginName, - s.nt_username AS NTUserName, - s.[program_name] AS ProgramName, - s.hostname AS HostName, - db.[resource_database_id] AS DatabaseID, - DB_NAME(db.resource_database_id) AS DatabaseName, - (SELECT TOP 1 [text] FROM sys.dm_exec_sql_text(c.most_recent_sql_handle)) AS QueryText, - s.open_tran AS OpenTransactionCount - FROM sys.sysprocesses s - INNER JOIN sys.dm_exec_connections c ON s.spid = c.session_id - INNER JOIN ( - SELECT DISTINCT request_session_id, resource_database_id - FROM sys.dm_tran_locks - WHERE resource_type = N'DATABASE' - AND request_mode = N'S' - AND request_status = N'GRANT' - AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.spid = db.request_session_id - WHERE s.status = 'sleeping' - AND s.open_tran > 0 - AND s.last_batch < DATEADD(ss, -10, SYSDATETIME()) - AND EXISTS(SELECT * FROM sys.dm_tran_locks WHERE request_session_id = s.spid - AND NOT (resource_type = N'DATABASE' AND request_mode = N'S' AND request_status = N'GRANT' AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE')); + IF EXISTS (SELECT * FROM sys.dm_exec_requests WHERE total_elapsed_time > 5000 AND request_id > 0) + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, OpenTransactionCount) + SELECT 8 AS CheckID, + 50 AS Priority, + 'Query Problems' AS FindingGroup, + 'Sleeping Query with Open Transactions' AS Finding, + 'https://www.brentozar.com/askbrent/sleeping-query-with-open-transactions/' AS URL, + 'Database: ' + DB_NAME(db.resource_database_id) + @LineFeed + 'Host: ' + s.hostname + @LineFeed + 'Program: ' + s.[program_name] + @LineFeed + 'Asleep with open transactions and locks since ' + CAST(s.last_batch AS NVARCHAR(100)) + '. ' AS Details, + 'KILL ' + CAST(s.spid AS NVARCHAR(100)) + ';' AS HowToStopIt, + s.last_batch AS StartTime, + s.loginame AS LoginName, + s.nt_username AS NTUserName, + s.[program_name] AS ProgramName, + s.hostname AS HostName, + db.[resource_database_id] AS DatabaseID, + DB_NAME(db.resource_database_id) AS DatabaseName, + (SELECT TOP 1 [text] FROM sys.dm_exec_sql_text(c.most_recent_sql_handle)) AS QueryText, + s.open_tran AS OpenTransactionCount + FROM sys.sysprocesses s + INNER JOIN sys.dm_exec_connections c ON s.spid = c.session_id + INNER JOIN ( + SELECT DISTINCT request_session_id, resource_database_id + FROM sys.dm_tran_locks + WHERE resource_type = N'DATABASE' + AND request_mode = N'S' + AND request_status = N'GRANT' + AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.spid = db.request_session_id + WHERE s.status = 'sleeping' + AND s.open_tran > 0 + AND s.last_batch < DATEADD(ss, -10, SYSDATETIME()) + AND EXISTS(SELECT * FROM sys.dm_tran_locks WHERE request_session_id = s.spid + AND NOT (resource_type = N'DATABASE' AND request_mode = N'S' AND request_status = N'GRANT' AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE')); END /*Query Problems - Clients using implicit transactions - CheckID 37 */ @@ -1841,34 +1845,35 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, RAISERROR('Running CheckID 9',10,1) WITH NOWAIT; END - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, QueryHash) - SELECT 9 AS CheckID, - 1 AS Priority, - 'Query Problems' AS FindingGroup, - 'Query Rolling Back' AS Finding, - 'https://www.brentozar.com/askbrent/rollback/' AS URL, - 'Rollback started at ' + CAST(r.start_time AS NVARCHAR(100)) + ', is ' + CAST(r.percent_complete AS NVARCHAR(100)) + '% complete.' AS Details, - 'Unfortunately, you can''t stop this. Whatever you do, don''t restart the server in an attempt to fix it - SQL Server will keep rolling back.' AS HowToStopIt, - r.start_time AS StartTime, - s.login_name AS LoginName, - s.nt_user_name AS NTUserName, - s.[program_name] AS ProgramName, - s.[host_name] AS HostName, - db.[resource_database_id] AS DatabaseID, - DB_NAME(db.resource_database_id) AS DatabaseName, - (SELECT TOP 1 [text] FROM sys.dm_exec_sql_text(c.most_recent_sql_handle)) AS QueryText, - r.query_hash - FROM sys.dm_exec_sessions s - INNER JOIN sys.dm_exec_connections c ON s.session_id = c.session_id - INNER JOIN sys.dm_exec_requests r ON s.session_id = r.session_id - LEFT OUTER JOIN ( - SELECT DISTINCT request_session_id, resource_database_id - FROM sys.dm_tran_locks - WHERE resource_type = N'DATABASE' - AND request_mode = N'S' - AND request_status = N'GRANT' - AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.session_id = db.request_session_id - WHERE r.status = 'rollback'; + IF EXISTS (SELECT * FROM sys.dm_exec_requests WHERE total_elapsed_time > 5000 AND request_id > 0) + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, QueryHash) + SELECT 9 AS CheckID, + 1 AS Priority, + 'Query Problems' AS FindingGroup, + 'Query Rolling Back' AS Finding, + 'https://www.brentozar.com/askbrent/rollback/' AS URL, + 'Rollback started at ' + CAST(r.start_time AS NVARCHAR(100)) + ', is ' + CAST(r.percent_complete AS NVARCHAR(100)) + '% complete.' AS Details, + 'Unfortunately, you can''t stop this. Whatever you do, don''t restart the server in an attempt to fix it - SQL Server will keep rolling back.' AS HowToStopIt, + r.start_time AS StartTime, + s.login_name AS LoginName, + s.nt_user_name AS NTUserName, + s.[program_name] AS ProgramName, + s.[host_name] AS HostName, + db.[resource_database_id] AS DatabaseID, + DB_NAME(db.resource_database_id) AS DatabaseName, + (SELECT TOP 1 [text] FROM sys.dm_exec_sql_text(c.most_recent_sql_handle)) AS QueryText, + r.query_hash + FROM sys.dm_exec_sessions s + INNER JOIN sys.dm_exec_connections c ON s.session_id = c.session_id + INNER JOIN sys.dm_exec_requests r ON s.session_id = r.session_id + LEFT OUTER JOIN ( + SELECT DISTINCT request_session_id, resource_database_id + FROM sys.dm_tran_locks + WHERE resource_type = N'DATABASE' + AND request_mode = N'S' + AND request_status = N'GRANT' + AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.session_id = db.request_session_id + WHERE r.status = 'rollback'; END IF @Seconds > 0 @@ -2146,7 +2151,8 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, JOIN sys.dm_exec_sessions AS s ON r.session_id = s.session_id WHERE s.host_name IS NOT NULL - AND r.total_elapsed_time > 5000 ) + AND r.total_elapsed_time > 5000 + AND r.request_id > 0 ) BEGIN SET @StringToExecute = N' From c4314025857b15d98d93b03d12266fa555d2f878 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Thu, 20 Apr 2023 07:26:14 -0700 Subject: [PATCH 394/662] 2023-04-20 release dates Bumping release dates and version numbers --- Install-All-Scripts.sql | 437 +++++++++++++----------- Install-Core-Blitz-No-Query-Store.sql | 401 +++++++++++----------- Install-Core-Blitz-With-Query-Store.sql | 403 +++++++++++----------- sp_AllNightLog.sql | 2 +- sp_AllNightLog_Setup.sql | 2 +- sp_Blitz.sql | 2 +- sp_BlitzAnalysis.sql | 2 +- sp_BlitzBackups.sql | 2 +- sp_BlitzCache.sql | 2 +- sp_BlitzFirst.sql | 2 +- sp_BlitzInMemoryOLTP.sql | 2 +- sp_BlitzIndex.sql | 2 +- sp_BlitzLock.sql | 2 +- sp_BlitzQueryStore.sql | 2 +- sp_BlitzWho.sql | 2 +- sp_DatabaseRestore.sql | 2 +- sp_ineachdb.sql | 2 +- 17 files changed, 672 insertions(+), 597 deletions(-) diff --git a/Install-All-Scripts.sql b/Install-All-Scripts.sql index cd6a0269d..85d3ba010 100644 --- a/Install-All-Scripts.sql +++ b/Install-All-Scripts.sql @@ -38,7 +38,7 @@ SET STATISTICS XML OFF; BEGIN; -SELECT @Version = '8.13', @VersionDate = '20230215'; +SELECT @Version = '8.14', @VersionDate = '20230420'; IF(@VersionCheckMode = 1) BEGIN @@ -1375,7 +1375,7 @@ SET STATISTICS XML OFF; BEGIN; -SELECT @Version = '8.13', @VersionDate = '20230215'; +SELECT @Version = '8.14', @VersionDate = '20230420'; IF(@VersionCheckMode = 1) BEGIN @@ -2900,7 +2900,7 @@ AS SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.13', @VersionDate = '20230215'; + SELECT @Version = '8.14', @VersionDate = '20230420'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -9732,7 +9732,7 @@ IF @ProductVersionMajor >= 10 Details = '[' + DBName + '].[' + SPSchema + '].[' + ProcName + '] has WITH RECOMPILE in the stored procedure code, which may cause increased CPU usage due to constant recompiles of the code.', CheckID = '78' FROM #Recompile AS TR - WHERE ProcName NOT LIKE 'sp_AllNightLog%' AND ProcName NOT LIKE 'sp_AskBrent%' AND ProcName NOT LIKE 'sp_Blitz%' + WHERE ProcName NOT LIKE 'sp_AllNightLog%' AND ProcName NOT LIKE 'sp_AskBrent%' AND ProcName NOT LIKE 'sp_Blitz%' AND ProcName NOT LIKE 'sp_PressureDetector' AND DBName NOT IN ('master', 'model', 'msdb', 'tempdb'); DROP TABLE #Recompile; END; @@ -12599,7 +12599,7 @@ AS SET NOCOUNT ON; SET STATISTICS XML OFF; -SELECT @Version = '8.13', @VersionDate = '20230215'; +SELECT @Version = '8.14', @VersionDate = '20230420'; IF(@VersionCheckMode = 1) BEGIN @@ -13477,7 +13477,7 @@ AS SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.13', @VersionDate = '20230215'; + SELECT @Version = '8.14', @VersionDate = '20230420'; IF(@VersionCheckMode = 1) BEGIN @@ -15259,7 +15259,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.13', @VersionDate = '20230215'; +SELECT @Version = '8.14', @VersionDate = '20230420'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -22577,7 +22577,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.13', @VersionDate = '20230215'; +SELECT @Version = '8.14', @VersionDate = '20230420'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -28754,7 +28754,7 @@ BEGIN SET NOCOUNT, XACT_ABORT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.13', @VersionDate = '20230215'; + SELECT @Version = '8.14', @VersionDate = '20230420'; IF @VersionCheckMode = 1 BEGIN @@ -29624,12 +29624,13 @@ BEGIN SELECT deadlock_xml = TRY_CAST(fx.event_data AS xml) - FROM sys.fn_xe_file_target_read_file('system_health*.xel', NULL, NULL, NULL) AS fx + FROM sys.fn_xe_file_target_read_file(N'system_health*.xel', NULL, NULL, NULL) AS fx LEFT JOIN #t AS t ON 1 = 1 + WHERE fx.object_name = N'xml_deadlock_report' ) AS xml CROSS APPLY xml.deadlock_xml.nodes('/event') AS e(x) - WHERE e.x.exist('@name[ . = "xml_deadlock_report"]') = 1 + WHERE 1 = 1 AND e.x.exist('@timestamp[. >= sql:variable("@StartDate")]') = 1 AND e.x.exist('@timestamp[. < sql:variable("@EndDate")]') = 1 OPTION(RECOMPILE); @@ -32402,7 +32403,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.13', @VersionDate = '20230215'; +SELECT @Version = '8.14', @VersionDate = '20230420'; IF(@VersionCheckMode = 1) BEGIN RETURN; @@ -38133,7 +38134,7 @@ BEGIN SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.13', @VersionDate = '20230215'; + SELECT @Version = '8.14', @VersionDate = '20230420'; IF(@VersionCheckMode = 1) BEGIN @@ -38776,7 +38777,7 @@ BEGIN /* Think of the StringToExecute as starting with this, but we'll set this up later depending on whether we're doing an insert or a select: SELECT @StringToExecute = N'SELECT GETDATE() AS run_date , */ - SET @StringToExecute = N'COALESCE( RIGHT(''00'' + CONVERT(VARCHAR(20), (ABS(r.total_elapsed_time) / 1000) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), (DATEADD(SECOND, (r.total_elapsed_time / 1000), 0) + DATEADD(MILLISECOND, (r.total_elapsed_time % 1000), 0)), 114), RIGHT(''00'' + CONVERT(VARCHAR(20), DATEDIFF(SECOND, s.last_request_start_time, GETDATE()) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), DATEADD(SECOND, DATEDIFF(SECOND, s.last_request_start_time, GETDATE()), 0), 114) ) AS [elapsed_time] , + SET @StringToExecute = N' CASE WHEN YEAR(s.last_request_start_time) = 1900 THEN NULL ELSE COALESCE( RIGHT(''00'' + CONVERT(VARCHAR(20), (ABS(r.total_elapsed_time) / 1000) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), (DATEADD(SECOND, (r.total_elapsed_time / 1000), 0) + DATEADD(MILLISECOND, (r.total_elapsed_time % 1000), 0)), 114), RIGHT(''00'' + CONVERT(VARCHAR(20), DATEDIFF(SECOND, s.last_request_start_time, GETDATE()) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), DATEADD(SECOND, DATEDIFF(SECOND, s.last_request_start_time, GETDATE()), 0), 114) ) END AS [elapsed_time] , s.session_id , CASE WHEN r.blocking_session_id <> 0 AND blocked.session_id IS NULL THEN r.blocking_session_id @@ -38994,7 +38995,7 @@ IF @ProductVersionMajor >= 11 /* Think of the StringToExecute as starting with this, but we'll set this up later depending on whether we're doing an insert or a select: SELECT @StringToExecute = N'SELECT GETDATE() AS run_date , */ - SELECT @StringToExecute = N'COALESCE( RIGHT(''00'' + CONVERT(VARCHAR(20), (ABS(r.total_elapsed_time) / 1000) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), (DATEADD(SECOND, (r.total_elapsed_time / 1000), 0) + DATEADD(MILLISECOND, (r.total_elapsed_time % 1000), 0)), 114), RIGHT(''00'' + CONVERT(VARCHAR(20), DATEDIFF(SECOND, s.last_request_start_time, GETDATE()) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), DATEADD(SECOND, DATEDIFF(SECOND, s.last_request_start_time, GETDATE()), 0), 114) ) AS [elapsed_time] , + SELECT @StringToExecute = N' CASE WHEN YEAR(s.last_request_start_time) = 1900 THEN NULL ELSE COALESCE( RIGHT(''00'' + CONVERT(VARCHAR(20), (ABS(r.total_elapsed_time) / 1000) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), (DATEADD(SECOND, (r.total_elapsed_time / 1000), 0) + DATEADD(MILLISECOND, (r.total_elapsed_time % 1000), 0)), 114), RIGHT(''00'' + CONVERT(VARCHAR(20), DATEDIFF(SECOND, s.last_request_start_time, GETDATE()) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), DATEADD(SECOND, DATEDIFF(SECOND, s.last_request_start_time, GETDATE()), 0), 114) ) END AS [elapsed_time] , s.session_id , CASE WHEN r.blocking_session_id <> 0 AND blocked.session_id IS NULL THEN r.blocking_session_id @@ -39519,6 +39520,7 @@ ALTER PROCEDURE [dbo].[sp_DatabaseRestore] @DatabaseOwner sysname = NULL, @SetTrustworthyON BIT = 0, @Execute CHAR(1) = Y, + @FileExtensionDiff NVARCHAR(128) = NULL, @Debug INT = 0, @Help BIT = 0, @Version VARCHAR(30) = NULL OUTPUT, @@ -39530,7 +39532,7 @@ SET STATISTICS XML OFF; /*Versioning details*/ -SELECT @Version = '8.13', @VersionDate = '20230215'; +SELECT @Version = '8.14', @VersionDate = '20230420'; IF(@VersionCheckMode = 1) BEGIN @@ -39646,6 +39648,16 @@ BEGIN @RunRecovery = 0, @Debug = 0; + --Restore just through the latest DIFF, ignoring logs, and using a custom ".dif" file extension + EXEC dbo.sp_DatabaseRestore + @Database = ''LogShipMe'', + @BackupPathFull = ''D:\Backup\SQL2016PROD1A\LogShipMe\FULL\'', + @BackupPathDiff = ''D:\Backup\SQL2016PROD1A\LogShipMe\DIFF\'', + @RestoreDiff = 1, + @FileExtensionDiff = ''dif'', + @ContinueLogs = 0, + @RunRecovery = 1; + -- Restore from stripped backup set when multiple paths are used. This example will restore stripped full backup set along with stripped transactional logs set from multiple backup paths EXEC dbo.sp_DatabaseRestore @Database = ''DBA'', @@ -39988,6 +40000,13 @@ BEGIN END END +--File Extension cleanup +IF @FileExtensionDiff LIKE '%.%' +BEGIN + IF @Execute = 'Y' OR @Debug = 1 RAISERROR('Removing "." from @FileExtensionDiff', 0, 1) WITH NOWAIT; + SET @FileExtensionDiff = REPLACE(@FileExtensionDiff,'.',''); +END + SET @RestoreDatabaseID = DB_ID(@RestoreDatabaseName); SET @RestoreDatabaseName = QUOTENAME(@RestoreDatabaseName); SET @UnquotedRestoreDatabaseName = PARSENAME(@RestoreDatabaseName,1); @@ -40532,9 +40551,15 @@ BEGIN END /*End folder sanity check*/ -- Find latest diff backup + IF @FileExtensionDiff IS NULL + BEGIN + IF @Execute = 'Y' OR @Debug = 1 RAISERROR('No @FileExtensionDiff given, assuming "bak".', 0, 1) WITH NOWAIT; + SET @FileExtensionDiff = 'bak'; + END + SELECT TOP 1 @LastDiffBackup = BackupFile, @CurrentBackupPathDiff = BackupPath FROM @FileList - WHERE BackupFile LIKE N'%.bak' + WHERE BackupFile LIKE N'%.' + @FileExtensionDiff AND BackupFile LIKE N'%' + @Database + '%' AND @@ -41106,7 +41131,7 @@ BEGIN SET NOCOUNT ON; SET STATISTICS XML OFF; - SELECT @Version = '8.13', @VersionDate = '20230215'; + SELECT @Version = '8.14', @VersionDate = '20230420'; IF(@VersionCheckMode = 1) BEGIN @@ -41452,8 +41477,13 @@ DELETE FROM dbo.SqlServerVersions; INSERT INTO dbo.SqlServerVersions (MajorVersionNumber, MinorVersionNumber, Branch, [Url], ReleaseDate, MainstreamSupportEndDate, ExtendedSupportEndDate, MajorVersionName, MinorVersionName) VALUES + (16, 4025, 'CU3', 'https://learn.microsoft.com/en-us/troubleshoot/sql/releases/sqlserver-2022/cumulativeupdate3', '2023-04-13', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 3'), + (16, 4015, 'CU2', 'https://learn.microsoft.com/en-us/troubleshoot/sql/releases/sqlserver-2022/cumulativeupdate2', '2023-03-15', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 2'), + (16, 4003, 'CU1', 'https://learn.microsoft.com/en-us/troubleshoot/sql/releases/sqlserver-2022/cumulativeupdate1', '2023-02-16', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 1'), (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'), + (15, 4312, 'CU20', 'https://support.microsoft.com/kb/5024276', '2023-04-13', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 20'), + (15, 4298, 'CU19', 'https://support.microsoft.com/kb/5023049', '2023-02-16', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 19'), (15, 4280, 'CU18 GDR', 'https://support.microsoft.com/kb/5021124', '2023-02-14', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 18 GDR'), (15, 4261, 'CU18', 'https://support.microsoft.com/en-us/help/5017593', '2022-09-28', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 18'), (15, 4249, 'CU17', 'https://support.microsoft.com/en-us/help/5016394', '2022-08-11', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 17'), @@ -41846,6 +41876,7 @@ ALTER PROCEDURE [dbo].[sp_BlitzFirst] @OutputTableNameWaitStats NVARCHAR(256) = NULL , @OutputTableNameBlitzCache NVARCHAR(256) = NULL , @OutputTableNameBlitzWho NVARCHAR(256) = NULL , + @OutputResultSets NVARCHAR(500) = N'BlitzWho_Start|Findings|FileStats|PerfmonStats|WaitStats|BlitzCache|BlitzWho_End' , @OutputTableRetentionDays TINYINT = 7 , @OutputXMLasNVARCHAR TINYINT = 0 , @FilterPlansByDatabase VARCHAR(MAX) = NULL , @@ -41873,7 +41904,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.13', @VersionDate = '20230215'; +SELECT @Version = '8.14', @VersionDate = '20230420'; IF(@VersionCheckMode = 1) BEGIN @@ -42089,7 +42120,7 @@ END; /* IF @AsOf IS NOT NULL AND @OutputDatabaseName IS NOT NULL AND @OutputSche ELSE IF @LogMessage IS NULL /* IF @OutputType = 'SCHEMA' */ BEGIN /* What's running right now? This is the first and last result set. */ - IF @SinceStartup = 0 AND @Seconds > 0 AND @ExpertMode = 1 AND @OutputType <> 'NONE' + IF @SinceStartup = 0 AND @Seconds > 0 AND @ExpertMode = 1 AND @OutputType <> 'NONE' AND @OutputResultSets LIKE N'%BlitzWho_Start%' BEGIN IF OBJECT_ID('master.dbo.sp_BlitzWho') IS NULL AND OBJECT_ID('dbo.sp_BlitzWho') IS NULL BEGIN @@ -43337,44 +43368,45 @@ BEGIN RAISERROR('Running CheckID 1',10,1) WITH NOWAIT; END - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, QueryHash) - SELECT 1 AS CheckID, - 1 AS Priority, - 'Maintenance Tasks Running' AS FindingGroup, - 'Backup Running' AS Finding, - 'https://www.brentozar.com/askbrent/backups/' AS URL, - 'Backup of ' + DB_NAME(db.resource_database_id) + ' database (' + (SELECT CAST(CAST(SUM(size * 8.0 / 1024 / 1024) AS BIGINT) AS NVARCHAR) FROM #MasterFiles WHERE database_id = db.resource_database_id) + 'GB) ' + @LineFeed - + CAST(r.percent_complete AS NVARCHAR(100)) + '% complete, has been running since ' + CAST(r.start_time AS NVARCHAR(100)) + '. ' + @LineFeed - + CASE WHEN COALESCE(s.nt_username, s.loginame) IS NOT NULL THEN (' Login: ' + COALESCE(s.nt_username, s.loginame) + ' ') ELSE '' END AS Details, - 'KILL ' + CAST(r.session_id AS NVARCHAR(100)) + ';' AS HowToStopIt, - pl.query_plan AS QueryPlan, - r.start_time AS StartTime, - s.loginame AS LoginName, - s.nt_username AS NTUserName, - s.[program_name] AS ProgramName, - s.[hostname] AS HostName, - db.[resource_database_id] AS DatabaseID, - DB_NAME(db.resource_database_id) AS DatabaseName, - 0 AS OpenTransactionCount, - r.query_hash - FROM sys.dm_exec_requests r - INNER JOIN sys.dm_exec_connections c ON r.session_id = c.session_id - INNER JOIN sys.sysprocesses AS s ON r.session_id = s.spid AND s.ecid = 0 - INNER JOIN - ( - SELECT DISTINCT - t.request_session_id, - t.resource_database_id - FROM sys.dm_tran_locks AS t - WHERE t.resource_type = N'DATABASE' - AND t.request_mode = N'S' - AND t.request_status = N'GRANT' - AND t.request_owner_type = N'SHARED_TRANSACTION_WORKSPACE' - ) AS db ON s.spid = db.request_session_id AND s.dbid = db.resource_database_id - CROSS APPLY sys.dm_exec_query_plan(r.plan_handle) pl - WHERE r.command LIKE 'BACKUP%' - AND r.start_time <= DATEADD(minute, -5, GETDATE()) - AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs); + IF EXISTS(SELECT * FROM sys.dm_exec_requests WHERE total_elapsed_time > 300000 AND command LIKE 'BACKUP%') + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, QueryHash) + SELECT 1 AS CheckID, + 1 AS Priority, + 'Maintenance Tasks Running' AS FindingGroup, + 'Backup Running' AS Finding, + 'https://www.brentozar.com/askbrent/backups/' AS URL, + 'Backup of ' + DB_NAME(db.resource_database_id) + ' database (' + (SELECT CAST(CAST(SUM(size * 8.0 / 1024 / 1024) AS BIGINT) AS NVARCHAR) FROM #MasterFiles WHERE database_id = db.resource_database_id) + 'GB) ' + @LineFeed + + CAST(r.percent_complete AS NVARCHAR(100)) + '% complete, has been running since ' + CAST(r.start_time AS NVARCHAR(100)) + '. ' + @LineFeed + + CASE WHEN COALESCE(s.nt_username, s.loginame) IS NOT NULL THEN (' Login: ' + COALESCE(s.nt_username, s.loginame) + ' ') ELSE '' END AS Details, + 'KILL ' + CAST(r.session_id AS NVARCHAR(100)) + ';' AS HowToStopIt, + pl.query_plan AS QueryPlan, + r.start_time AS StartTime, + s.loginame AS LoginName, + s.nt_username AS NTUserName, + s.[program_name] AS ProgramName, + s.[hostname] AS HostName, + db.[resource_database_id] AS DatabaseID, + DB_NAME(db.resource_database_id) AS DatabaseName, + 0 AS OpenTransactionCount, + r.query_hash + FROM sys.dm_exec_requests r + INNER JOIN sys.dm_exec_connections c ON r.session_id = c.session_id + INNER JOIN sys.sysprocesses AS s ON r.session_id = s.spid AND s.ecid = 0 + INNER JOIN + ( + SELECT DISTINCT + t.request_session_id, + t.resource_database_id + FROM sys.dm_tran_locks AS t + WHERE t.resource_type = N'DATABASE' + AND t.request_mode = N'S' + AND t.request_status = N'GRANT' + AND t.request_owner_type = N'SHARED_TRANSACTION_WORKSPACE' + ) AS db ON s.spid = db.request_session_id AND s.dbid = db.resource_database_id + CROSS APPLY sys.dm_exec_query_plan(r.plan_handle) pl + WHERE r.command LIKE 'BACKUP%' + AND r.start_time <= DATEADD(minute, -5, GETDATE()) + AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs); END /* If there's a backup running, add details explaining how long full backup has been taking in the last month. */ @@ -43386,48 +43418,49 @@ BEGIN /* Maintenance Tasks Running - DBCC CHECK* Running - CheckID 2 */ - IF @Seconds > 0 AND EXISTS(SELECT * FROM sys.dm_exec_requests WHERE command LIKE 'DBCC%') + IF @Seconds > 0 BEGIN IF (@Debug = 1) BEGIN RAISERROR('Running CheckID 2',10,1) WITH NOWAIT; END - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, QueryHash) - SELECT 2 AS CheckID, - 1 AS Priority, - 'Maintenance Tasks Running' AS FindingGroup, - 'DBCC CHECK* Running' AS Finding, - 'https://www.brentozar.com/askbrent/dbcc/' AS URL, - 'Corruption check of ' + DB_NAME(db.resource_database_id) + ' database (' + (SELECT CAST(CAST(SUM(size * 8.0 / 1024 / 1024) AS BIGINT) AS NVARCHAR) FROM #MasterFiles WHERE database_id = db.resource_database_id) + 'GB) 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, - s.login_name AS LoginName, - s.nt_user_name AS NTUserName, - s.[program_name] AS ProgramName, - s.[host_name] AS HostName, - db.[resource_database_id] AS DatabaseID, - DB_NAME(db.resource_database_id) AS DatabaseName, - 0 AS OpenTransactionCount, - r.query_hash - FROM sys.dm_exec_requests r - INNER JOIN sys.dm_exec_connections c ON r.session_id = c.session_id - INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id - INNER JOIN (SELECT DISTINCT l.request_session_id, l.resource_database_id - FROM sys.dm_tran_locks l - INNER JOIN sys.databases d ON l.resource_database_id = d.database_id - WHERE l.resource_type = N'DATABASE' - AND l.request_mode = N'S' - AND l.request_status = N'GRANT' - AND l.request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.session_id = db.request_session_id - OUTER APPLY sys.dm_exec_query_plan(r.plan_handle) pl - OUTER APPLY sys.dm_exec_sql_text(r.sql_handle) AS t - WHERE r.command LIKE 'DBCC%' - AND CAST(t.text AS NVARCHAR(4000)) NOT LIKE '%dm_db_index_physical_stats%' - AND CAST(t.text AS NVARCHAR(4000)) NOT LIKE '%ALTER INDEX%' - AND CAST(t.text AS NVARCHAR(4000)) NOT LIKE '%fileproperty%' - AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs); + IF EXISTS (SELECT * FROM sys.dm_exec_requests WHERE command LIKE 'DBCC%' AND total_elapsed_time > 5000) + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, QueryHash) + SELECT 2 AS CheckID, + 1 AS Priority, + 'Maintenance Tasks Running' AS FindingGroup, + 'DBCC CHECK* Running' AS Finding, + 'https://www.brentozar.com/askbrent/dbcc/' AS URL, + 'Corruption check of ' + DB_NAME(db.resource_database_id) + ' database (' + (SELECT CAST(CAST(SUM(size * 8.0 / 1024 / 1024) AS BIGINT) AS NVARCHAR) FROM #MasterFiles WHERE database_id = db.resource_database_id) + 'GB) 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, + s.login_name AS LoginName, + s.nt_user_name AS NTUserName, + s.[program_name] AS ProgramName, + s.[host_name] AS HostName, + db.[resource_database_id] AS DatabaseID, + DB_NAME(db.resource_database_id) AS DatabaseName, + 0 AS OpenTransactionCount, + r.query_hash + FROM sys.dm_exec_requests r + INNER JOIN sys.dm_exec_connections c ON r.session_id = c.session_id + INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id + INNER JOIN (SELECT DISTINCT l.request_session_id, l.resource_database_id + FROM sys.dm_tran_locks l + INNER JOIN sys.databases d ON l.resource_database_id = d.database_id + WHERE l.resource_type = N'DATABASE' + AND l.request_mode = N'S' + AND l.request_status = N'GRANT' + AND l.request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.session_id = db.request_session_id + OUTER APPLY sys.dm_exec_query_plan(r.plan_handle) pl + OUTER APPLY sys.dm_exec_sql_text(r.sql_handle) AS t + WHERE r.command LIKE 'DBCC%' + AND CAST(t.text AS NVARCHAR(4000)) NOT LIKE '%dm_db_index_physical_stats%' + AND CAST(t.text AS NVARCHAR(4000)) NOT LIKE '%ALTER INDEX%' + AND CAST(t.text AS NVARCHAR(4000)) NOT LIKE '%fileproperty%' + AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs); END /* Maintenance Tasks Running - Restore Running - CheckID 3 */ @@ -43438,37 +43471,38 @@ BEGIN RAISERROR('Running CheckID 3',10,1) WITH NOWAIT; END - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, QueryHash) - SELECT 3 AS CheckID, - 1 AS Priority, - 'Maintenance Tasks Running' AS FindingGroup, - 'Restore Running' AS Finding, - 'https://www.brentozar.com/askbrent/backups/' AS URL, - 'Restore of ' + DB_NAME(db.resource_database_id) + ' database (' + (SELECT CAST(CAST(SUM(size * 8.0 / 1024 / 1024) AS BIGINT) AS NVARCHAR) FROM #MasterFiles WHERE database_id = db.resource_database_id) + '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, - s.login_name AS LoginName, - s.nt_user_name AS NTUserName, - s.[program_name] AS ProgramName, - s.[host_name] AS HostName, - db.[resource_database_id] AS DatabaseID, - DB_NAME(db.resource_database_id) AS DatabaseName, - 0 AS OpenTransactionCount, - r.query_hash - FROM sys.dm_exec_requests r - INNER JOIN sys.dm_exec_connections c ON r.session_id = c.session_id - INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id - INNER JOIN ( - SELECT DISTINCT request_session_id, resource_database_id - FROM sys.dm_tran_locks - WHERE resource_type = N'DATABASE' - AND request_mode = N'S' - AND request_status = N'GRANT') AS db ON s.session_id = db.request_session_id - CROSS APPLY sys.dm_exec_query_plan(r.plan_handle) pl - WHERE r.command LIKE 'RESTORE%' - AND s.program_name <> 'SQL Server Log Shipping' - AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs); + IF EXISTS (SELECT * FROM sys.dm_exec_requests WHERE command LIKE 'RESTORE%' AND total_elapsed_time > 5000) + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, QueryHash) + SELECT 3 AS CheckID, + 1 AS Priority, + 'Maintenance Tasks Running' AS FindingGroup, + 'Restore Running' AS Finding, + 'https://www.brentozar.com/askbrent/backups/' AS URL, + 'Restore of ' + DB_NAME(db.resource_database_id) + ' database (' + (SELECT CAST(CAST(SUM(size * 8.0 / 1024 / 1024) AS BIGINT) AS NVARCHAR) FROM #MasterFiles WHERE database_id = db.resource_database_id) + '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, + s.login_name AS LoginName, + s.nt_user_name AS NTUserName, + s.[program_name] AS ProgramName, + s.[host_name] AS HostName, + db.[resource_database_id] AS DatabaseID, + DB_NAME(db.resource_database_id) AS DatabaseName, + 0 AS OpenTransactionCount, + r.query_hash + FROM sys.dm_exec_requests r + INNER JOIN sys.dm_exec_connections c ON r.session_id = c.session_id + INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id + INNER JOIN ( + SELECT DISTINCT request_session_id, resource_database_id + FROM sys.dm_tran_locks + WHERE resource_type = N'DATABASE' + AND request_mode = N'S' + AND request_status = N'GRANT') AS db ON s.session_id = db.request_session_id + CROSS APPLY sys.dm_exec_query_plan(r.plan_handle) pl + WHERE r.command LIKE 'RESTORE%' + AND s.program_name <> 'SQL Server Log Shipping' + AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs); END /* SQL Server Internal Maintenance - Database File Growing - CheckID 4 */ @@ -43581,37 +43615,38 @@ BEGIN RAISERROR('Running CheckID 8',10,1) WITH NOWAIT; END - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, OpenTransactionCount) - SELECT 8 AS CheckID, - 50 AS Priority, - 'Query Problems' AS FindingGroup, - 'Sleeping Query with Open Transactions' AS Finding, - 'https://www.brentozar.com/askbrent/sleeping-query-with-open-transactions/' AS URL, - 'Database: ' + DB_NAME(db.resource_database_id) + @LineFeed + 'Host: ' + s.hostname + @LineFeed + 'Program: ' + s.[program_name] + @LineFeed + 'Asleep with open transactions and locks since ' + CAST(s.last_batch AS NVARCHAR(100)) + '. ' AS Details, - 'KILL ' + CAST(s.spid AS NVARCHAR(100)) + ';' AS HowToStopIt, - s.last_batch AS StartTime, - s.loginame AS LoginName, - s.nt_username AS NTUserName, - s.[program_name] AS ProgramName, - s.hostname AS HostName, - db.[resource_database_id] AS DatabaseID, - DB_NAME(db.resource_database_id) AS DatabaseName, - (SELECT TOP 1 [text] FROM sys.dm_exec_sql_text(c.most_recent_sql_handle)) AS QueryText, - s.open_tran AS OpenTransactionCount - FROM sys.sysprocesses s - INNER JOIN sys.dm_exec_connections c ON s.spid = c.session_id - INNER JOIN ( - SELECT DISTINCT request_session_id, resource_database_id - FROM sys.dm_tran_locks - WHERE resource_type = N'DATABASE' - AND request_mode = N'S' - AND request_status = N'GRANT' - AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.spid = db.request_session_id - WHERE s.status = 'sleeping' - AND s.open_tran > 0 - AND s.last_batch < DATEADD(ss, -10, SYSDATETIME()) - AND EXISTS(SELECT * FROM sys.dm_tran_locks WHERE request_session_id = s.spid - AND NOT (resource_type = N'DATABASE' AND request_mode = N'S' AND request_status = N'GRANT' AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE')); + IF EXISTS (SELECT * FROM sys.dm_exec_requests WHERE total_elapsed_time > 5000 AND request_id > 0) + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, OpenTransactionCount) + SELECT 8 AS CheckID, + 50 AS Priority, + 'Query Problems' AS FindingGroup, + 'Sleeping Query with Open Transactions' AS Finding, + 'https://www.brentozar.com/askbrent/sleeping-query-with-open-transactions/' AS URL, + 'Database: ' + DB_NAME(db.resource_database_id) + @LineFeed + 'Host: ' + s.hostname + @LineFeed + 'Program: ' + s.[program_name] + @LineFeed + 'Asleep with open transactions and locks since ' + CAST(s.last_batch AS NVARCHAR(100)) + '. ' AS Details, + 'KILL ' + CAST(s.spid AS NVARCHAR(100)) + ';' AS HowToStopIt, + s.last_batch AS StartTime, + s.loginame AS LoginName, + s.nt_username AS NTUserName, + s.[program_name] AS ProgramName, + s.hostname AS HostName, + db.[resource_database_id] AS DatabaseID, + DB_NAME(db.resource_database_id) AS DatabaseName, + (SELECT TOP 1 [text] FROM sys.dm_exec_sql_text(c.most_recent_sql_handle)) AS QueryText, + s.open_tran AS OpenTransactionCount + FROM sys.sysprocesses s + INNER JOIN sys.dm_exec_connections c ON s.spid = c.session_id + INNER JOIN ( + SELECT DISTINCT request_session_id, resource_database_id + FROM sys.dm_tran_locks + WHERE resource_type = N'DATABASE' + AND request_mode = N'S' + AND request_status = N'GRANT' + AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.spid = db.request_session_id + WHERE s.status = 'sleeping' + AND s.open_tran > 0 + AND s.last_batch < DATEADD(ss, -10, SYSDATETIME()) + AND EXISTS(SELECT * FROM sys.dm_tran_locks WHERE request_session_id = s.spid + AND NOT (resource_type = N'DATABASE' AND request_mode = N'S' AND request_status = N'GRANT' AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE')); END /*Query Problems - Clients using implicit transactions - CheckID 37 */ @@ -43667,34 +43702,35 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, RAISERROR('Running CheckID 9',10,1) WITH NOWAIT; END - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, QueryHash) - SELECT 9 AS CheckID, - 1 AS Priority, - 'Query Problems' AS FindingGroup, - 'Query Rolling Back' AS Finding, - 'https://www.brentozar.com/askbrent/rollback/' AS URL, - 'Rollback started at ' + CAST(r.start_time AS NVARCHAR(100)) + ', is ' + CAST(r.percent_complete AS NVARCHAR(100)) + '% complete.' AS Details, - 'Unfortunately, you can''t stop this. Whatever you do, don''t restart the server in an attempt to fix it - SQL Server will keep rolling back.' AS HowToStopIt, - r.start_time AS StartTime, - s.login_name AS LoginName, - s.nt_user_name AS NTUserName, - s.[program_name] AS ProgramName, - s.[host_name] AS HostName, - db.[resource_database_id] AS DatabaseID, - DB_NAME(db.resource_database_id) AS DatabaseName, - (SELECT TOP 1 [text] FROM sys.dm_exec_sql_text(c.most_recent_sql_handle)) AS QueryText, - r.query_hash - FROM sys.dm_exec_sessions s - INNER JOIN sys.dm_exec_connections c ON s.session_id = c.session_id - INNER JOIN sys.dm_exec_requests r ON s.session_id = r.session_id - LEFT OUTER JOIN ( - SELECT DISTINCT request_session_id, resource_database_id - FROM sys.dm_tran_locks - WHERE resource_type = N'DATABASE' - AND request_mode = N'S' - AND request_status = N'GRANT' - AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.session_id = db.request_session_id - WHERE r.status = 'rollback'; + IF EXISTS (SELECT * FROM sys.dm_exec_requests WHERE total_elapsed_time > 5000 AND request_id > 0) + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, QueryHash) + SELECT 9 AS CheckID, + 1 AS Priority, + 'Query Problems' AS FindingGroup, + 'Query Rolling Back' AS Finding, + 'https://www.brentozar.com/askbrent/rollback/' AS URL, + 'Rollback started at ' + CAST(r.start_time AS NVARCHAR(100)) + ', is ' + CAST(r.percent_complete AS NVARCHAR(100)) + '% complete.' AS Details, + 'Unfortunately, you can''t stop this. Whatever you do, don''t restart the server in an attempt to fix it - SQL Server will keep rolling back.' AS HowToStopIt, + r.start_time AS StartTime, + s.login_name AS LoginName, + s.nt_user_name AS NTUserName, + s.[program_name] AS ProgramName, + s.[host_name] AS HostName, + db.[resource_database_id] AS DatabaseID, + DB_NAME(db.resource_database_id) AS DatabaseName, + (SELECT TOP 1 [text] FROM sys.dm_exec_sql_text(c.most_recent_sql_handle)) AS QueryText, + r.query_hash + FROM sys.dm_exec_sessions s + INNER JOIN sys.dm_exec_connections c ON s.session_id = c.session_id + INNER JOIN sys.dm_exec_requests r ON s.session_id = r.session_id + LEFT OUTER JOIN ( + SELECT DISTINCT request_session_id, resource_database_id + FROM sys.dm_tran_locks + WHERE resource_type = N'DATABASE' + AND request_mode = N'S' + AND request_status = N'GRANT' + AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.session_id = db.request_session_id + WHERE r.status = 'rollback'; END IF @Seconds > 0 @@ -43972,7 +44008,8 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, JOIN sys.dm_exec_sessions AS s ON r.session_id = s.session_id WHERE s.host_name IS NOT NULL - AND r.total_elapsed_time > 5000 ) + AND r.total_elapsed_time > 5000 + AND r.request_id > 0 ) BEGIN SET @StringToExecute = N' @@ -43990,12 +44027,14 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, FROM ( SELECT deqp.session_id, deqp.request_id, - CASE WHEN deqp.row_count > ( deqp.estimate_row_count * 10000 ) + CASE WHEN (deqp.row_count/10000) > deqp.estimate_row_count THEN 1 ELSE 0 END AS estimate_inaccuracy FROM sys.dm_exec_query_profiles AS deqp + INNER JOIN sys.dm_exec_requests r ON deqp.session_id = r.session_id AND deqp.request_id = r.request_id WHERE deqp.session_id <> @@SPID + AND r.total_elapsed_time > 5000 ) AS x WHERE x.estimate_inaccuracy = 1 GROUP BY x.session_id, @@ -44973,7 +45012,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, /* Check for temp objects with high forwarded fetches. This has to be done as dynamic SQL because we have to execute OBJECT_NAME inside TempDB. */ - IF @@ROWCOUNT > 0 + IF EXISTS (SELECT * FROM #BlitzFirstResults WHERE CheckID = 29) BEGIN SET @StringToExecute = N' INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) @@ -46333,7 +46372,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, FROM #BlitzFirstResults; END; ELSE - IF @OutputType = 'Opserver1' AND @SinceStartup = 0 + IF @OutputType = 'Opserver1' AND @SinceStartup = 0 AND @OutputResultSets LIKE N'%Findings%' BEGIN SELECT r.[Priority] , @@ -46390,7 +46429,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, r.Finding, r.ID; END; - ELSE IF @OutputType IN ( 'CSV', 'RSV' ) AND @SinceStartup = 0 + ELSE IF @OutputType IN ( 'CSV', 'RSV' ) AND @SinceStartup = 0 AND @OutputResultSets LIKE N'%Findings%' BEGIN SELECT Result = CAST([Priority] AS NVARCHAR(100)) @@ -46411,7 +46450,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, Finding, Details; END; - ELSE IF @OutputType = 'Top10' + ELSE IF @OutputType = 'Top10' AND @OutputResultSets LIKE N'%WaitStats%' BEGIN /* Measure waits in hours */ ;WITH max_batch AS ( @@ -46448,7 +46487,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, AND wd2.wait_time_ms-wd1.wait_time_ms > 0 ORDER BY [Wait Time (Seconds)] DESC; END; - ELSE IF @ExpertMode = 0 AND @OutputType <> 'NONE' AND @OutputXMLasNVARCHAR = 0 AND @SinceStartup = 0 + ELSE IF @ExpertMode = 0 AND @OutputType <> 'NONE' AND @OutputXMLasNVARCHAR = 0 AND @SinceStartup = 0 AND @OutputResultSets LIKE N'%Findings%' BEGIN SELECT [Priority] , [FindingsGroup] , @@ -46470,7 +46509,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, ID, CAST(Details AS NVARCHAR(4000)); END; - ELSE IF @OutputType <> 'NONE' AND @OutputXMLasNVARCHAR = 1 AND @SinceStartup = 0 + ELSE IF @OutputType <> 'NONE' AND @OutputXMLasNVARCHAR = 1 AND @SinceStartup = 0 AND @OutputResultSets LIKE N'%Findings%' BEGIN SELECT [Priority] , [FindingsGroup] , @@ -46492,7 +46531,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, ID, CAST(Details AS NVARCHAR(4000)); END; - ELSE IF @ExpertMode = 1 AND @OutputType <> 'NONE' + ELSE IF @ExpertMode = 1 AND @OutputType <> 'NONE' AND @OutputResultSets LIKE N'%Findings%' BEGIN IF @SinceStartup = 0 SELECT r.[Priority] , @@ -46554,7 +46593,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, ------------------------- --What happened: #WaitStats ------------------------- - IF @Seconds = 0 + IF @Seconds = 0 AND @OutputResultSets LIKE N'%WaitStats%' BEGIN /* Measure waits in hours */ ;WITH max_batch AS ( @@ -46598,7 +46637,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, AND wd2.wait_time_ms-wd1.wait_time_ms > 0 ORDER BY [Wait Time (Seconds)] DESC; END; - ELSE + ELSE IF @OutputResultSets LIKE N'%WaitStats%' BEGIN /* Measure waits in seconds */ ;WITH max_batch AS ( @@ -46646,6 +46685,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, ------------------------- --What happened: #FileStats ------------------------- + IF @OutputResultSets LIKE N'%FileStats%' WITH readstats AS ( SELECT 'PHYSICAL READS' AS Pattern, ROW_NUMBER() OVER (ORDER BY wd2.avg_stall_read_ms DESC) AS StallRank, @@ -46704,7 +46744,8 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, --What happened: #PerfmonStats ------------------------- - SELECT 'PERFMON' AS Pattern, pLast.[object_name], pLast.counter_name, pLast.instance_name, + IF @OutputResultSets LIKE N'%PerfmonStats%' + SELECT 'PERFMON' AS Pattern, pLast.[object_name], pLast.counter_name, pLast.instance_name, pFirst.SampleTime AS FirstSampleTime, pFirst.cntr_value AS FirstSampleValue, pLast.SampleTime AS LastSampleTime, pLast.cntr_value AS LastSampleValue, pLast.cntr_value - pFirst.cntr_value AS ValueDelta, @@ -46719,7 +46760,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, ------------------------- --What happened: #QueryStats ------------------------- - IF @CheckProcedureCache = 1 + IF @CheckProcedureCache = 1 AND @OutputResultSets LIKE N'%BlitzCache%' BEGIN SELECT qsNow.*, qsFirst.* @@ -46736,7 +46777,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, DROP TABLE #BlitzFirstResults; /* What's running right now? This is the first and last result set. */ - IF @SinceStartup = 0 AND @Seconds > 0 AND @ExpertMode = 1 AND @OutputType <> 'NONE' + IF @SinceStartup = 0 AND @Seconds > 0 AND @ExpertMode = 1 AND @OutputType <> 'NONE' AND @OutputResultSets LIKE N'%BlitzWho_End%' BEGIN IF OBJECT_ID('master.dbo.sp_BlitzWho') IS NULL AND OBJECT_ID('dbo.sp_BlitzWho') IS NULL BEGIN diff --git a/Install-Core-Blitz-No-Query-Store.sql b/Install-Core-Blitz-No-Query-Store.sql index 448bde00e..3ce39a617 100644 --- a/Install-Core-Blitz-No-Query-Store.sql +++ b/Install-Core-Blitz-No-Query-Store.sql @@ -38,7 +38,7 @@ AS SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.13', @VersionDate = '20230215'; + SELECT @Version = '8.14', @VersionDate = '20230420'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -6870,7 +6870,7 @@ IF @ProductVersionMajor >= 10 Details = '[' + DBName + '].[' + SPSchema + '].[' + ProcName + '] has WITH RECOMPILE in the stored procedure code, which may cause increased CPU usage due to constant recompiles of the code.', CheckID = '78' FROM #Recompile AS TR - WHERE ProcName NOT LIKE 'sp_AllNightLog%' AND ProcName NOT LIKE 'sp_AskBrent%' AND ProcName NOT LIKE 'sp_Blitz%' + WHERE ProcName NOT LIKE 'sp_AllNightLog%' AND ProcName NOT LIKE 'sp_AskBrent%' AND ProcName NOT LIKE 'sp_Blitz%' AND ProcName NOT LIKE 'sp_PressureDetector' AND DBName NOT IN ('master', 'model', 'msdb', 'tempdb'); DROP TABLE #Recompile; END; @@ -9737,7 +9737,7 @@ AS SET NOCOUNT ON; SET STATISTICS XML OFF; -SELECT @Version = '8.13', @VersionDate = '20230215'; +SELECT @Version = '8.14', @VersionDate = '20230420'; IF(@VersionCheckMode = 1) BEGIN @@ -10615,7 +10615,7 @@ AS SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.13', @VersionDate = '20230215'; + SELECT @Version = '8.14', @VersionDate = '20230420'; IF(@VersionCheckMode = 1) BEGIN @@ -12397,7 +12397,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.13', @VersionDate = '20230215'; +SELECT @Version = '8.14', @VersionDate = '20230420'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -19715,7 +19715,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.13', @VersionDate = '20230215'; +SELECT @Version = '8.14', @VersionDate = '20230420'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -25892,7 +25892,7 @@ BEGIN SET NOCOUNT, XACT_ABORT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.13', @VersionDate = '20230215'; + SELECT @Version = '8.14', @VersionDate = '20230420'; IF @VersionCheckMode = 1 BEGIN @@ -26762,12 +26762,13 @@ BEGIN SELECT deadlock_xml = TRY_CAST(fx.event_data AS xml) - FROM sys.fn_xe_file_target_read_file('system_health*.xel', NULL, NULL, NULL) AS fx + FROM sys.fn_xe_file_target_read_file(N'system_health*.xel', NULL, NULL, NULL) AS fx LEFT JOIN #t AS t ON 1 = 1 + WHERE fx.object_name = N'xml_deadlock_report' ) AS xml CROSS APPLY xml.deadlock_xml.nodes('/event') AS e(x) - WHERE e.x.exist('@name[ . = "xml_deadlock_report"]') = 1 + WHERE 1 = 1 AND e.x.exist('@timestamp[. >= sql:variable("@StartDate")]') = 1 AND e.x.exist('@timestamp[. < sql:variable("@EndDate")]') = 1 OPTION(RECOMPILE); @@ -29516,7 +29517,7 @@ BEGIN SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.13', @VersionDate = '20230215'; + SELECT @Version = '8.14', @VersionDate = '20230420'; IF(@VersionCheckMode = 1) BEGIN @@ -30159,7 +30160,7 @@ BEGIN /* Think of the StringToExecute as starting with this, but we'll set this up later depending on whether we're doing an insert or a select: SELECT @StringToExecute = N'SELECT GETDATE() AS run_date , */ - SET @StringToExecute = N'COALESCE( RIGHT(''00'' + CONVERT(VARCHAR(20), (ABS(r.total_elapsed_time) / 1000) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), (DATEADD(SECOND, (r.total_elapsed_time / 1000), 0) + DATEADD(MILLISECOND, (r.total_elapsed_time % 1000), 0)), 114), RIGHT(''00'' + CONVERT(VARCHAR(20), DATEDIFF(SECOND, s.last_request_start_time, GETDATE()) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), DATEADD(SECOND, DATEDIFF(SECOND, s.last_request_start_time, GETDATE()), 0), 114) ) AS [elapsed_time] , + SET @StringToExecute = N' CASE WHEN YEAR(s.last_request_start_time) = 1900 THEN NULL ELSE COALESCE( RIGHT(''00'' + CONVERT(VARCHAR(20), (ABS(r.total_elapsed_time) / 1000) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), (DATEADD(SECOND, (r.total_elapsed_time / 1000), 0) + DATEADD(MILLISECOND, (r.total_elapsed_time % 1000), 0)), 114), RIGHT(''00'' + CONVERT(VARCHAR(20), DATEDIFF(SECOND, s.last_request_start_time, GETDATE()) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), DATEADD(SECOND, DATEDIFF(SECOND, s.last_request_start_time, GETDATE()), 0), 114) ) END AS [elapsed_time] , s.session_id , CASE WHEN r.blocking_session_id <> 0 AND blocked.session_id IS NULL THEN r.blocking_session_id @@ -30377,7 +30378,7 @@ IF @ProductVersionMajor >= 11 /* Think of the StringToExecute as starting with this, but we'll set this up later depending on whether we're doing an insert or a select: SELECT @StringToExecute = N'SELECT GETDATE() AS run_date , */ - SELECT @StringToExecute = N'COALESCE( RIGHT(''00'' + CONVERT(VARCHAR(20), (ABS(r.total_elapsed_time) / 1000) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), (DATEADD(SECOND, (r.total_elapsed_time / 1000), 0) + DATEADD(MILLISECOND, (r.total_elapsed_time % 1000), 0)), 114), RIGHT(''00'' + CONVERT(VARCHAR(20), DATEDIFF(SECOND, s.last_request_start_time, GETDATE()) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), DATEADD(SECOND, DATEDIFF(SECOND, s.last_request_start_time, GETDATE()), 0), 114) ) AS [elapsed_time] , + SELECT @StringToExecute = N' CASE WHEN YEAR(s.last_request_start_time) = 1900 THEN NULL ELSE COALESCE( RIGHT(''00'' + CONVERT(VARCHAR(20), (ABS(r.total_elapsed_time) / 1000) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), (DATEADD(SECOND, (r.total_elapsed_time / 1000), 0) + DATEADD(MILLISECOND, (r.total_elapsed_time % 1000), 0)), 114), RIGHT(''00'' + CONVERT(VARCHAR(20), DATEDIFF(SECOND, s.last_request_start_time, GETDATE()) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), DATEADD(SECOND, DATEDIFF(SECOND, s.last_request_start_time, GETDATE()), 0), 114) ) END AS [elapsed_time] , s.session_id , CASE WHEN r.blocking_session_id <> 0 AND blocked.session_id IS NULL THEN r.blocking_session_id @@ -30912,8 +30913,13 @@ DELETE FROM dbo.SqlServerVersions; INSERT INTO dbo.SqlServerVersions (MajorVersionNumber, MinorVersionNumber, Branch, [Url], ReleaseDate, MainstreamSupportEndDate, ExtendedSupportEndDate, MajorVersionName, MinorVersionName) VALUES + (16, 4025, 'CU3', 'https://learn.microsoft.com/en-us/troubleshoot/sql/releases/sqlserver-2022/cumulativeupdate3', '2023-04-13', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 3'), + (16, 4015, 'CU2', 'https://learn.microsoft.com/en-us/troubleshoot/sql/releases/sqlserver-2022/cumulativeupdate2', '2023-03-15', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 2'), + (16, 4003, 'CU1', 'https://learn.microsoft.com/en-us/troubleshoot/sql/releases/sqlserver-2022/cumulativeupdate1', '2023-02-16', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 1'), (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'), + (15, 4312, 'CU20', 'https://support.microsoft.com/kb/5024276', '2023-04-13', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 20'), + (15, 4298, 'CU19', 'https://support.microsoft.com/kb/5023049', '2023-02-16', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 19'), (15, 4280, 'CU18 GDR', 'https://support.microsoft.com/kb/5021124', '2023-02-14', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 18 GDR'), (15, 4261, 'CU18', 'https://support.microsoft.com/en-us/help/5017593', '2022-09-28', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 18'), (15, 4249, 'CU17', 'https://support.microsoft.com/en-us/help/5016394', '2022-08-11', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 17'), @@ -31306,6 +31312,7 @@ ALTER PROCEDURE [dbo].[sp_BlitzFirst] @OutputTableNameWaitStats NVARCHAR(256) = NULL , @OutputTableNameBlitzCache NVARCHAR(256) = NULL , @OutputTableNameBlitzWho NVARCHAR(256) = NULL , + @OutputResultSets NVARCHAR(500) = N'BlitzWho_Start|Findings|FileStats|PerfmonStats|WaitStats|BlitzCache|BlitzWho_End' , @OutputTableRetentionDays TINYINT = 7 , @OutputXMLasNVARCHAR TINYINT = 0 , @FilterPlansByDatabase VARCHAR(MAX) = NULL , @@ -31333,7 +31340,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.13', @VersionDate = '20230215'; +SELECT @Version = '8.14', @VersionDate = '20230420'; IF(@VersionCheckMode = 1) BEGIN @@ -31549,7 +31556,7 @@ END; /* IF @AsOf IS NOT NULL AND @OutputDatabaseName IS NOT NULL AND @OutputSche ELSE IF @LogMessage IS NULL /* IF @OutputType = 'SCHEMA' */ BEGIN /* What's running right now? This is the first and last result set. */ - IF @SinceStartup = 0 AND @Seconds > 0 AND @ExpertMode = 1 AND @OutputType <> 'NONE' + IF @SinceStartup = 0 AND @Seconds > 0 AND @ExpertMode = 1 AND @OutputType <> 'NONE' AND @OutputResultSets LIKE N'%BlitzWho_Start%' BEGIN IF OBJECT_ID('master.dbo.sp_BlitzWho') IS NULL AND OBJECT_ID('dbo.sp_BlitzWho') IS NULL BEGIN @@ -32797,44 +32804,45 @@ BEGIN RAISERROR('Running CheckID 1',10,1) WITH NOWAIT; END - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, QueryHash) - SELECT 1 AS CheckID, - 1 AS Priority, - 'Maintenance Tasks Running' AS FindingGroup, - 'Backup Running' AS Finding, - 'https://www.brentozar.com/askbrent/backups/' AS URL, - 'Backup of ' + DB_NAME(db.resource_database_id) + ' database (' + (SELECT CAST(CAST(SUM(size * 8.0 / 1024 / 1024) AS BIGINT) AS NVARCHAR) FROM #MasterFiles WHERE database_id = db.resource_database_id) + 'GB) ' + @LineFeed - + CAST(r.percent_complete AS NVARCHAR(100)) + '% complete, has been running since ' + CAST(r.start_time AS NVARCHAR(100)) + '. ' + @LineFeed - + CASE WHEN COALESCE(s.nt_username, s.loginame) IS NOT NULL THEN (' Login: ' + COALESCE(s.nt_username, s.loginame) + ' ') ELSE '' END AS Details, - 'KILL ' + CAST(r.session_id AS NVARCHAR(100)) + ';' AS HowToStopIt, - pl.query_plan AS QueryPlan, - r.start_time AS StartTime, - s.loginame AS LoginName, - s.nt_username AS NTUserName, - s.[program_name] AS ProgramName, - s.[hostname] AS HostName, - db.[resource_database_id] AS DatabaseID, - DB_NAME(db.resource_database_id) AS DatabaseName, - 0 AS OpenTransactionCount, - r.query_hash - FROM sys.dm_exec_requests r - INNER JOIN sys.dm_exec_connections c ON r.session_id = c.session_id - INNER JOIN sys.sysprocesses AS s ON r.session_id = s.spid AND s.ecid = 0 - INNER JOIN - ( - SELECT DISTINCT - t.request_session_id, - t.resource_database_id - FROM sys.dm_tran_locks AS t - WHERE t.resource_type = N'DATABASE' - AND t.request_mode = N'S' - AND t.request_status = N'GRANT' - AND t.request_owner_type = N'SHARED_TRANSACTION_WORKSPACE' - ) AS db ON s.spid = db.request_session_id AND s.dbid = db.resource_database_id - CROSS APPLY sys.dm_exec_query_plan(r.plan_handle) pl - WHERE r.command LIKE 'BACKUP%' - AND r.start_time <= DATEADD(minute, -5, GETDATE()) - AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs); + IF EXISTS(SELECT * FROM sys.dm_exec_requests WHERE total_elapsed_time > 300000 AND command LIKE 'BACKUP%') + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, QueryHash) + SELECT 1 AS CheckID, + 1 AS Priority, + 'Maintenance Tasks Running' AS FindingGroup, + 'Backup Running' AS Finding, + 'https://www.brentozar.com/askbrent/backups/' AS URL, + 'Backup of ' + DB_NAME(db.resource_database_id) + ' database (' + (SELECT CAST(CAST(SUM(size * 8.0 / 1024 / 1024) AS BIGINT) AS NVARCHAR) FROM #MasterFiles WHERE database_id = db.resource_database_id) + 'GB) ' + @LineFeed + + CAST(r.percent_complete AS NVARCHAR(100)) + '% complete, has been running since ' + CAST(r.start_time AS NVARCHAR(100)) + '. ' + @LineFeed + + CASE WHEN COALESCE(s.nt_username, s.loginame) IS NOT NULL THEN (' Login: ' + COALESCE(s.nt_username, s.loginame) + ' ') ELSE '' END AS Details, + 'KILL ' + CAST(r.session_id AS NVARCHAR(100)) + ';' AS HowToStopIt, + pl.query_plan AS QueryPlan, + r.start_time AS StartTime, + s.loginame AS LoginName, + s.nt_username AS NTUserName, + s.[program_name] AS ProgramName, + s.[hostname] AS HostName, + db.[resource_database_id] AS DatabaseID, + DB_NAME(db.resource_database_id) AS DatabaseName, + 0 AS OpenTransactionCount, + r.query_hash + FROM sys.dm_exec_requests r + INNER JOIN sys.dm_exec_connections c ON r.session_id = c.session_id + INNER JOIN sys.sysprocesses AS s ON r.session_id = s.spid AND s.ecid = 0 + INNER JOIN + ( + SELECT DISTINCT + t.request_session_id, + t.resource_database_id + FROM sys.dm_tran_locks AS t + WHERE t.resource_type = N'DATABASE' + AND t.request_mode = N'S' + AND t.request_status = N'GRANT' + AND t.request_owner_type = N'SHARED_TRANSACTION_WORKSPACE' + ) AS db ON s.spid = db.request_session_id AND s.dbid = db.resource_database_id + CROSS APPLY sys.dm_exec_query_plan(r.plan_handle) pl + WHERE r.command LIKE 'BACKUP%' + AND r.start_time <= DATEADD(minute, -5, GETDATE()) + AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs); END /* If there's a backup running, add details explaining how long full backup has been taking in the last month. */ @@ -32846,48 +32854,49 @@ BEGIN /* Maintenance Tasks Running - DBCC CHECK* Running - CheckID 2 */ - IF @Seconds > 0 AND EXISTS(SELECT * FROM sys.dm_exec_requests WHERE command LIKE 'DBCC%') + IF @Seconds > 0 BEGIN IF (@Debug = 1) BEGIN RAISERROR('Running CheckID 2',10,1) WITH NOWAIT; END - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, QueryHash) - SELECT 2 AS CheckID, - 1 AS Priority, - 'Maintenance Tasks Running' AS FindingGroup, - 'DBCC CHECK* Running' AS Finding, - 'https://www.brentozar.com/askbrent/dbcc/' AS URL, - 'Corruption check of ' + DB_NAME(db.resource_database_id) + ' database (' + (SELECT CAST(CAST(SUM(size * 8.0 / 1024 / 1024) AS BIGINT) AS NVARCHAR) FROM #MasterFiles WHERE database_id = db.resource_database_id) + 'GB) 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, - s.login_name AS LoginName, - s.nt_user_name AS NTUserName, - s.[program_name] AS ProgramName, - s.[host_name] AS HostName, - db.[resource_database_id] AS DatabaseID, - DB_NAME(db.resource_database_id) AS DatabaseName, - 0 AS OpenTransactionCount, - r.query_hash - FROM sys.dm_exec_requests r - INNER JOIN sys.dm_exec_connections c ON r.session_id = c.session_id - INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id - INNER JOIN (SELECT DISTINCT l.request_session_id, l.resource_database_id - FROM sys.dm_tran_locks l - INNER JOIN sys.databases d ON l.resource_database_id = d.database_id - WHERE l.resource_type = N'DATABASE' - AND l.request_mode = N'S' - AND l.request_status = N'GRANT' - AND l.request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.session_id = db.request_session_id - OUTER APPLY sys.dm_exec_query_plan(r.plan_handle) pl - OUTER APPLY sys.dm_exec_sql_text(r.sql_handle) AS t - WHERE r.command LIKE 'DBCC%' - AND CAST(t.text AS NVARCHAR(4000)) NOT LIKE '%dm_db_index_physical_stats%' - AND CAST(t.text AS NVARCHAR(4000)) NOT LIKE '%ALTER INDEX%' - AND CAST(t.text AS NVARCHAR(4000)) NOT LIKE '%fileproperty%' - AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs); + IF EXISTS (SELECT * FROM sys.dm_exec_requests WHERE command LIKE 'DBCC%' AND total_elapsed_time > 5000) + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, QueryHash) + SELECT 2 AS CheckID, + 1 AS Priority, + 'Maintenance Tasks Running' AS FindingGroup, + 'DBCC CHECK* Running' AS Finding, + 'https://www.brentozar.com/askbrent/dbcc/' AS URL, + 'Corruption check of ' + DB_NAME(db.resource_database_id) + ' database (' + (SELECT CAST(CAST(SUM(size * 8.0 / 1024 / 1024) AS BIGINT) AS NVARCHAR) FROM #MasterFiles WHERE database_id = db.resource_database_id) + 'GB) 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, + s.login_name AS LoginName, + s.nt_user_name AS NTUserName, + s.[program_name] AS ProgramName, + s.[host_name] AS HostName, + db.[resource_database_id] AS DatabaseID, + DB_NAME(db.resource_database_id) AS DatabaseName, + 0 AS OpenTransactionCount, + r.query_hash + FROM sys.dm_exec_requests r + INNER JOIN sys.dm_exec_connections c ON r.session_id = c.session_id + INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id + INNER JOIN (SELECT DISTINCT l.request_session_id, l.resource_database_id + FROM sys.dm_tran_locks l + INNER JOIN sys.databases d ON l.resource_database_id = d.database_id + WHERE l.resource_type = N'DATABASE' + AND l.request_mode = N'S' + AND l.request_status = N'GRANT' + AND l.request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.session_id = db.request_session_id + OUTER APPLY sys.dm_exec_query_plan(r.plan_handle) pl + OUTER APPLY sys.dm_exec_sql_text(r.sql_handle) AS t + WHERE r.command LIKE 'DBCC%' + AND CAST(t.text AS NVARCHAR(4000)) NOT LIKE '%dm_db_index_physical_stats%' + AND CAST(t.text AS NVARCHAR(4000)) NOT LIKE '%ALTER INDEX%' + AND CAST(t.text AS NVARCHAR(4000)) NOT LIKE '%fileproperty%' + AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs); END /* Maintenance Tasks Running - Restore Running - CheckID 3 */ @@ -32898,37 +32907,38 @@ BEGIN RAISERROR('Running CheckID 3',10,1) WITH NOWAIT; END - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, QueryHash) - SELECT 3 AS CheckID, - 1 AS Priority, - 'Maintenance Tasks Running' AS FindingGroup, - 'Restore Running' AS Finding, - 'https://www.brentozar.com/askbrent/backups/' AS URL, - 'Restore of ' + DB_NAME(db.resource_database_id) + ' database (' + (SELECT CAST(CAST(SUM(size * 8.0 / 1024 / 1024) AS BIGINT) AS NVARCHAR) FROM #MasterFiles WHERE database_id = db.resource_database_id) + '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, - s.login_name AS LoginName, - s.nt_user_name AS NTUserName, - s.[program_name] AS ProgramName, - s.[host_name] AS HostName, - db.[resource_database_id] AS DatabaseID, - DB_NAME(db.resource_database_id) AS DatabaseName, - 0 AS OpenTransactionCount, - r.query_hash - FROM sys.dm_exec_requests r - INNER JOIN sys.dm_exec_connections c ON r.session_id = c.session_id - INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id - INNER JOIN ( - SELECT DISTINCT request_session_id, resource_database_id - FROM sys.dm_tran_locks - WHERE resource_type = N'DATABASE' - AND request_mode = N'S' - AND request_status = N'GRANT') AS db ON s.session_id = db.request_session_id - CROSS APPLY sys.dm_exec_query_plan(r.plan_handle) pl - WHERE r.command LIKE 'RESTORE%' - AND s.program_name <> 'SQL Server Log Shipping' - AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs); + IF EXISTS (SELECT * FROM sys.dm_exec_requests WHERE command LIKE 'RESTORE%' AND total_elapsed_time > 5000) + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, QueryHash) + SELECT 3 AS CheckID, + 1 AS Priority, + 'Maintenance Tasks Running' AS FindingGroup, + 'Restore Running' AS Finding, + 'https://www.brentozar.com/askbrent/backups/' AS URL, + 'Restore of ' + DB_NAME(db.resource_database_id) + ' database (' + (SELECT CAST(CAST(SUM(size * 8.0 / 1024 / 1024) AS BIGINT) AS NVARCHAR) FROM #MasterFiles WHERE database_id = db.resource_database_id) + '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, + s.login_name AS LoginName, + s.nt_user_name AS NTUserName, + s.[program_name] AS ProgramName, + s.[host_name] AS HostName, + db.[resource_database_id] AS DatabaseID, + DB_NAME(db.resource_database_id) AS DatabaseName, + 0 AS OpenTransactionCount, + r.query_hash + FROM sys.dm_exec_requests r + INNER JOIN sys.dm_exec_connections c ON r.session_id = c.session_id + INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id + INNER JOIN ( + SELECT DISTINCT request_session_id, resource_database_id + FROM sys.dm_tran_locks + WHERE resource_type = N'DATABASE' + AND request_mode = N'S' + AND request_status = N'GRANT') AS db ON s.session_id = db.request_session_id + CROSS APPLY sys.dm_exec_query_plan(r.plan_handle) pl + WHERE r.command LIKE 'RESTORE%' + AND s.program_name <> 'SQL Server Log Shipping' + AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs); END /* SQL Server Internal Maintenance - Database File Growing - CheckID 4 */ @@ -33041,37 +33051,38 @@ BEGIN RAISERROR('Running CheckID 8',10,1) WITH NOWAIT; END - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, OpenTransactionCount) - SELECT 8 AS CheckID, - 50 AS Priority, - 'Query Problems' AS FindingGroup, - 'Sleeping Query with Open Transactions' AS Finding, - 'https://www.brentozar.com/askbrent/sleeping-query-with-open-transactions/' AS URL, - 'Database: ' + DB_NAME(db.resource_database_id) + @LineFeed + 'Host: ' + s.hostname + @LineFeed + 'Program: ' + s.[program_name] + @LineFeed + 'Asleep with open transactions and locks since ' + CAST(s.last_batch AS NVARCHAR(100)) + '. ' AS Details, - 'KILL ' + CAST(s.spid AS NVARCHAR(100)) + ';' AS HowToStopIt, - s.last_batch AS StartTime, - s.loginame AS LoginName, - s.nt_username AS NTUserName, - s.[program_name] AS ProgramName, - s.hostname AS HostName, - db.[resource_database_id] AS DatabaseID, - DB_NAME(db.resource_database_id) AS DatabaseName, - (SELECT TOP 1 [text] FROM sys.dm_exec_sql_text(c.most_recent_sql_handle)) AS QueryText, - s.open_tran AS OpenTransactionCount - FROM sys.sysprocesses s - INNER JOIN sys.dm_exec_connections c ON s.spid = c.session_id - INNER JOIN ( - SELECT DISTINCT request_session_id, resource_database_id - FROM sys.dm_tran_locks - WHERE resource_type = N'DATABASE' - AND request_mode = N'S' - AND request_status = N'GRANT' - AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.spid = db.request_session_id - WHERE s.status = 'sleeping' - AND s.open_tran > 0 - AND s.last_batch < DATEADD(ss, -10, SYSDATETIME()) - AND EXISTS(SELECT * FROM sys.dm_tran_locks WHERE request_session_id = s.spid - AND NOT (resource_type = N'DATABASE' AND request_mode = N'S' AND request_status = N'GRANT' AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE')); + IF EXISTS (SELECT * FROM sys.dm_exec_requests WHERE total_elapsed_time > 5000 AND request_id > 0) + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, OpenTransactionCount) + SELECT 8 AS CheckID, + 50 AS Priority, + 'Query Problems' AS FindingGroup, + 'Sleeping Query with Open Transactions' AS Finding, + 'https://www.brentozar.com/askbrent/sleeping-query-with-open-transactions/' AS URL, + 'Database: ' + DB_NAME(db.resource_database_id) + @LineFeed + 'Host: ' + s.hostname + @LineFeed + 'Program: ' + s.[program_name] + @LineFeed + 'Asleep with open transactions and locks since ' + CAST(s.last_batch AS NVARCHAR(100)) + '. ' AS Details, + 'KILL ' + CAST(s.spid AS NVARCHAR(100)) + ';' AS HowToStopIt, + s.last_batch AS StartTime, + s.loginame AS LoginName, + s.nt_username AS NTUserName, + s.[program_name] AS ProgramName, + s.hostname AS HostName, + db.[resource_database_id] AS DatabaseID, + DB_NAME(db.resource_database_id) AS DatabaseName, + (SELECT TOP 1 [text] FROM sys.dm_exec_sql_text(c.most_recent_sql_handle)) AS QueryText, + s.open_tran AS OpenTransactionCount + FROM sys.sysprocesses s + INNER JOIN sys.dm_exec_connections c ON s.spid = c.session_id + INNER JOIN ( + SELECT DISTINCT request_session_id, resource_database_id + FROM sys.dm_tran_locks + WHERE resource_type = N'DATABASE' + AND request_mode = N'S' + AND request_status = N'GRANT' + AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.spid = db.request_session_id + WHERE s.status = 'sleeping' + AND s.open_tran > 0 + AND s.last_batch < DATEADD(ss, -10, SYSDATETIME()) + AND EXISTS(SELECT * FROM sys.dm_tran_locks WHERE request_session_id = s.spid + AND NOT (resource_type = N'DATABASE' AND request_mode = N'S' AND request_status = N'GRANT' AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE')); END /*Query Problems - Clients using implicit transactions - CheckID 37 */ @@ -33127,34 +33138,35 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, RAISERROR('Running CheckID 9',10,1) WITH NOWAIT; END - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, QueryHash) - SELECT 9 AS CheckID, - 1 AS Priority, - 'Query Problems' AS FindingGroup, - 'Query Rolling Back' AS Finding, - 'https://www.brentozar.com/askbrent/rollback/' AS URL, - 'Rollback started at ' + CAST(r.start_time AS NVARCHAR(100)) + ', is ' + CAST(r.percent_complete AS NVARCHAR(100)) + '% complete.' AS Details, - 'Unfortunately, you can''t stop this. Whatever you do, don''t restart the server in an attempt to fix it - SQL Server will keep rolling back.' AS HowToStopIt, - r.start_time AS StartTime, - s.login_name AS LoginName, - s.nt_user_name AS NTUserName, - s.[program_name] AS ProgramName, - s.[host_name] AS HostName, - db.[resource_database_id] AS DatabaseID, - DB_NAME(db.resource_database_id) AS DatabaseName, - (SELECT TOP 1 [text] FROM sys.dm_exec_sql_text(c.most_recent_sql_handle)) AS QueryText, - r.query_hash - FROM sys.dm_exec_sessions s - INNER JOIN sys.dm_exec_connections c ON s.session_id = c.session_id - INNER JOIN sys.dm_exec_requests r ON s.session_id = r.session_id - LEFT OUTER JOIN ( - SELECT DISTINCT request_session_id, resource_database_id - FROM sys.dm_tran_locks - WHERE resource_type = N'DATABASE' - AND request_mode = N'S' - AND request_status = N'GRANT' - AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.session_id = db.request_session_id - WHERE r.status = 'rollback'; + IF EXISTS (SELECT * FROM sys.dm_exec_requests WHERE total_elapsed_time > 5000 AND request_id > 0) + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, QueryHash) + SELECT 9 AS CheckID, + 1 AS Priority, + 'Query Problems' AS FindingGroup, + 'Query Rolling Back' AS Finding, + 'https://www.brentozar.com/askbrent/rollback/' AS URL, + 'Rollback started at ' + CAST(r.start_time AS NVARCHAR(100)) + ', is ' + CAST(r.percent_complete AS NVARCHAR(100)) + '% complete.' AS Details, + 'Unfortunately, you can''t stop this. Whatever you do, don''t restart the server in an attempt to fix it - SQL Server will keep rolling back.' AS HowToStopIt, + r.start_time AS StartTime, + s.login_name AS LoginName, + s.nt_user_name AS NTUserName, + s.[program_name] AS ProgramName, + s.[host_name] AS HostName, + db.[resource_database_id] AS DatabaseID, + DB_NAME(db.resource_database_id) AS DatabaseName, + (SELECT TOP 1 [text] FROM sys.dm_exec_sql_text(c.most_recent_sql_handle)) AS QueryText, + r.query_hash + FROM sys.dm_exec_sessions s + INNER JOIN sys.dm_exec_connections c ON s.session_id = c.session_id + INNER JOIN sys.dm_exec_requests r ON s.session_id = r.session_id + LEFT OUTER JOIN ( + SELECT DISTINCT request_session_id, resource_database_id + FROM sys.dm_tran_locks + WHERE resource_type = N'DATABASE' + AND request_mode = N'S' + AND request_status = N'GRANT' + AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.session_id = db.request_session_id + WHERE r.status = 'rollback'; END IF @Seconds > 0 @@ -33432,7 +33444,8 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, JOIN sys.dm_exec_sessions AS s ON r.session_id = s.session_id WHERE s.host_name IS NOT NULL - AND r.total_elapsed_time > 5000 ) + AND r.total_elapsed_time > 5000 + AND r.request_id > 0 ) BEGIN SET @StringToExecute = N' @@ -33450,12 +33463,14 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, FROM ( SELECT deqp.session_id, deqp.request_id, - CASE WHEN deqp.row_count > ( deqp.estimate_row_count * 10000 ) + CASE WHEN (deqp.row_count/10000) > deqp.estimate_row_count THEN 1 ELSE 0 END AS estimate_inaccuracy FROM sys.dm_exec_query_profiles AS deqp + INNER JOIN sys.dm_exec_requests r ON deqp.session_id = r.session_id AND deqp.request_id = r.request_id WHERE deqp.session_id <> @@SPID + AND r.total_elapsed_time > 5000 ) AS x WHERE x.estimate_inaccuracy = 1 GROUP BY x.session_id, @@ -34433,7 +34448,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, /* Check for temp objects with high forwarded fetches. This has to be done as dynamic SQL because we have to execute OBJECT_NAME inside TempDB. */ - IF @@ROWCOUNT > 0 + IF EXISTS (SELECT * FROM #BlitzFirstResults WHERE CheckID = 29) BEGIN SET @StringToExecute = N' INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) @@ -35793,7 +35808,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, FROM #BlitzFirstResults; END; ELSE - IF @OutputType = 'Opserver1' AND @SinceStartup = 0 + IF @OutputType = 'Opserver1' AND @SinceStartup = 0 AND @OutputResultSets LIKE N'%Findings%' BEGIN SELECT r.[Priority] , @@ -35850,7 +35865,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, r.Finding, r.ID; END; - ELSE IF @OutputType IN ( 'CSV', 'RSV' ) AND @SinceStartup = 0 + ELSE IF @OutputType IN ( 'CSV', 'RSV' ) AND @SinceStartup = 0 AND @OutputResultSets LIKE N'%Findings%' BEGIN SELECT Result = CAST([Priority] AS NVARCHAR(100)) @@ -35871,7 +35886,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, Finding, Details; END; - ELSE IF @OutputType = 'Top10' + ELSE IF @OutputType = 'Top10' AND @OutputResultSets LIKE N'%WaitStats%' BEGIN /* Measure waits in hours */ ;WITH max_batch AS ( @@ -35908,7 +35923,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, AND wd2.wait_time_ms-wd1.wait_time_ms > 0 ORDER BY [Wait Time (Seconds)] DESC; END; - ELSE IF @ExpertMode = 0 AND @OutputType <> 'NONE' AND @OutputXMLasNVARCHAR = 0 AND @SinceStartup = 0 + ELSE IF @ExpertMode = 0 AND @OutputType <> 'NONE' AND @OutputXMLasNVARCHAR = 0 AND @SinceStartup = 0 AND @OutputResultSets LIKE N'%Findings%' BEGIN SELECT [Priority] , [FindingsGroup] , @@ -35930,7 +35945,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, ID, CAST(Details AS NVARCHAR(4000)); END; - ELSE IF @OutputType <> 'NONE' AND @OutputXMLasNVARCHAR = 1 AND @SinceStartup = 0 + ELSE IF @OutputType <> 'NONE' AND @OutputXMLasNVARCHAR = 1 AND @SinceStartup = 0 AND @OutputResultSets LIKE N'%Findings%' BEGIN SELECT [Priority] , [FindingsGroup] , @@ -35952,7 +35967,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, ID, CAST(Details AS NVARCHAR(4000)); END; - ELSE IF @ExpertMode = 1 AND @OutputType <> 'NONE' + ELSE IF @ExpertMode = 1 AND @OutputType <> 'NONE' AND @OutputResultSets LIKE N'%Findings%' BEGIN IF @SinceStartup = 0 SELECT r.[Priority] , @@ -36014,7 +36029,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, ------------------------- --What happened: #WaitStats ------------------------- - IF @Seconds = 0 + IF @Seconds = 0 AND @OutputResultSets LIKE N'%WaitStats%' BEGIN /* Measure waits in hours */ ;WITH max_batch AS ( @@ -36058,7 +36073,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, AND wd2.wait_time_ms-wd1.wait_time_ms > 0 ORDER BY [Wait Time (Seconds)] DESC; END; - ELSE + ELSE IF @OutputResultSets LIKE N'%WaitStats%' BEGIN /* Measure waits in seconds */ ;WITH max_batch AS ( @@ -36106,6 +36121,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, ------------------------- --What happened: #FileStats ------------------------- + IF @OutputResultSets LIKE N'%FileStats%' WITH readstats AS ( SELECT 'PHYSICAL READS' AS Pattern, ROW_NUMBER() OVER (ORDER BY wd2.avg_stall_read_ms DESC) AS StallRank, @@ -36164,7 +36180,8 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, --What happened: #PerfmonStats ------------------------- - SELECT 'PERFMON' AS Pattern, pLast.[object_name], pLast.counter_name, pLast.instance_name, + IF @OutputResultSets LIKE N'%PerfmonStats%' + SELECT 'PERFMON' AS Pattern, pLast.[object_name], pLast.counter_name, pLast.instance_name, pFirst.SampleTime AS FirstSampleTime, pFirst.cntr_value AS FirstSampleValue, pLast.SampleTime AS LastSampleTime, pLast.cntr_value AS LastSampleValue, pLast.cntr_value - pFirst.cntr_value AS ValueDelta, @@ -36179,7 +36196,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, ------------------------- --What happened: #QueryStats ------------------------- - IF @CheckProcedureCache = 1 + IF @CheckProcedureCache = 1 AND @OutputResultSets LIKE N'%BlitzCache%' BEGIN SELECT qsNow.*, qsFirst.* @@ -36196,7 +36213,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, DROP TABLE #BlitzFirstResults; /* What's running right now? This is the first and last result set. */ - IF @SinceStartup = 0 AND @Seconds > 0 AND @ExpertMode = 1 AND @OutputType <> 'NONE' + IF @SinceStartup = 0 AND @Seconds > 0 AND @ExpertMode = 1 AND @OutputType <> 'NONE' AND @OutputResultSets LIKE N'%BlitzWho_End%' BEGIN IF OBJECT_ID('master.dbo.sp_BlitzWho') IS NULL AND OBJECT_ID('dbo.sp_BlitzWho') IS NULL BEGIN diff --git a/Install-Core-Blitz-With-Query-Store.sql b/Install-Core-Blitz-With-Query-Store.sql index 03b39864c..20db09239 100644 --- a/Install-Core-Blitz-With-Query-Store.sql +++ b/Install-Core-Blitz-With-Query-Store.sql @@ -38,7 +38,7 @@ AS SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.13', @VersionDate = '20230215'; + SELECT @Version = '8.14', @VersionDate = '20230420'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -6870,7 +6870,7 @@ IF @ProductVersionMajor >= 10 Details = '[' + DBName + '].[' + SPSchema + '].[' + ProcName + '] has WITH RECOMPILE in the stored procedure code, which may cause increased CPU usage due to constant recompiles of the code.', CheckID = '78' FROM #Recompile AS TR - WHERE ProcName NOT LIKE 'sp_AllNightLog%' AND ProcName NOT LIKE 'sp_AskBrent%' AND ProcName NOT LIKE 'sp_Blitz%' + WHERE ProcName NOT LIKE 'sp_AllNightLog%' AND ProcName NOT LIKE 'sp_AskBrent%' AND ProcName NOT LIKE 'sp_Blitz%' AND ProcName NOT LIKE 'sp_PressureDetector' AND DBName NOT IN ('master', 'model', 'msdb', 'tempdb'); DROP TABLE #Recompile; END; @@ -9737,7 +9737,7 @@ AS SET NOCOUNT ON; SET STATISTICS XML OFF; -SELECT @Version = '8.13', @VersionDate = '20230215'; +SELECT @Version = '8.14', @VersionDate = '20230420'; IF(@VersionCheckMode = 1) BEGIN @@ -10615,7 +10615,7 @@ AS SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.13', @VersionDate = '20230215'; + SELECT @Version = '8.14', @VersionDate = '20230420'; IF(@VersionCheckMode = 1) BEGIN @@ -12397,7 +12397,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.13', @VersionDate = '20230215'; +SELECT @Version = '8.14', @VersionDate = '20230420'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -19715,7 +19715,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.13', @VersionDate = '20230215'; +SELECT @Version = '8.14', @VersionDate = '20230420'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -25892,7 +25892,7 @@ BEGIN SET NOCOUNT, XACT_ABORT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.13', @VersionDate = '20230215'; + SELECT @Version = '8.14', @VersionDate = '20230420'; IF @VersionCheckMode = 1 BEGIN @@ -26762,12 +26762,13 @@ BEGIN SELECT deadlock_xml = TRY_CAST(fx.event_data AS xml) - FROM sys.fn_xe_file_target_read_file('system_health*.xel', NULL, NULL, NULL) AS fx + FROM sys.fn_xe_file_target_read_file(N'system_health*.xel', NULL, NULL, NULL) AS fx LEFT JOIN #t AS t ON 1 = 1 + WHERE fx.object_name = N'xml_deadlock_report' ) AS xml CROSS APPLY xml.deadlock_xml.nodes('/event') AS e(x) - WHERE e.x.exist('@name[ . = "xml_deadlock_report"]') = 1 + WHERE 1 = 1 AND e.x.exist('@timestamp[. >= sql:variable("@StartDate")]') = 1 AND e.x.exist('@timestamp[. < sql:variable("@EndDate")]') = 1 OPTION(RECOMPILE); @@ -29540,7 +29541,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.13', @VersionDate = '20230215'; +SELECT @Version = '8.14', @VersionDate = '20230420'; IF(@VersionCheckMode = 1) BEGIN RETURN; @@ -35271,7 +35272,7 @@ BEGIN SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.13', @VersionDate = '20230215'; + SELECT @Version = '8.14', @VersionDate = '20230420'; IF(@VersionCheckMode = 1) BEGIN @@ -35914,7 +35915,7 @@ BEGIN /* Think of the StringToExecute as starting with this, but we'll set this up later depending on whether we're doing an insert or a select: SELECT @StringToExecute = N'SELECT GETDATE() AS run_date , */ - SET @StringToExecute = N'COALESCE( RIGHT(''00'' + CONVERT(VARCHAR(20), (ABS(r.total_elapsed_time) / 1000) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), (DATEADD(SECOND, (r.total_elapsed_time / 1000), 0) + DATEADD(MILLISECOND, (r.total_elapsed_time % 1000), 0)), 114), RIGHT(''00'' + CONVERT(VARCHAR(20), DATEDIFF(SECOND, s.last_request_start_time, GETDATE()) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), DATEADD(SECOND, DATEDIFF(SECOND, s.last_request_start_time, GETDATE()), 0), 114) ) AS [elapsed_time] , + SET @StringToExecute = N' CASE WHEN YEAR(s.last_request_start_time) = 1900 THEN NULL ELSE COALESCE( RIGHT(''00'' + CONVERT(VARCHAR(20), (ABS(r.total_elapsed_time) / 1000) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), (DATEADD(SECOND, (r.total_elapsed_time / 1000), 0) + DATEADD(MILLISECOND, (r.total_elapsed_time % 1000), 0)), 114), RIGHT(''00'' + CONVERT(VARCHAR(20), DATEDIFF(SECOND, s.last_request_start_time, GETDATE()) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), DATEADD(SECOND, DATEDIFF(SECOND, s.last_request_start_time, GETDATE()), 0), 114) ) END AS [elapsed_time] , s.session_id , CASE WHEN r.blocking_session_id <> 0 AND blocked.session_id IS NULL THEN r.blocking_session_id @@ -36132,7 +36133,7 @@ IF @ProductVersionMajor >= 11 /* Think of the StringToExecute as starting with this, but we'll set this up later depending on whether we're doing an insert or a select: SELECT @StringToExecute = N'SELECT GETDATE() AS run_date , */ - SELECT @StringToExecute = N'COALESCE( RIGHT(''00'' + CONVERT(VARCHAR(20), (ABS(r.total_elapsed_time) / 1000) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), (DATEADD(SECOND, (r.total_elapsed_time / 1000), 0) + DATEADD(MILLISECOND, (r.total_elapsed_time % 1000), 0)), 114), RIGHT(''00'' + CONVERT(VARCHAR(20), DATEDIFF(SECOND, s.last_request_start_time, GETDATE()) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), DATEADD(SECOND, DATEDIFF(SECOND, s.last_request_start_time, GETDATE()), 0), 114) ) AS [elapsed_time] , + SELECT @StringToExecute = N' CASE WHEN YEAR(s.last_request_start_time) = 1900 THEN NULL ELSE COALESCE( RIGHT(''00'' + CONVERT(VARCHAR(20), (ABS(r.total_elapsed_time) / 1000) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), (DATEADD(SECOND, (r.total_elapsed_time / 1000), 0) + DATEADD(MILLISECOND, (r.total_elapsed_time % 1000), 0)), 114), RIGHT(''00'' + CONVERT(VARCHAR(20), DATEDIFF(SECOND, s.last_request_start_time, GETDATE()) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), DATEADD(SECOND, DATEDIFF(SECOND, s.last_request_start_time, GETDATE()), 0), 114) ) END AS [elapsed_time] , s.session_id , CASE WHEN r.blocking_session_id <> 0 AND blocked.session_id IS NULL THEN r.blocking_session_id @@ -36667,8 +36668,13 @@ DELETE FROM dbo.SqlServerVersions; INSERT INTO dbo.SqlServerVersions (MajorVersionNumber, MinorVersionNumber, Branch, [Url], ReleaseDate, MainstreamSupportEndDate, ExtendedSupportEndDate, MajorVersionName, MinorVersionName) VALUES + (16, 4025, 'CU3', 'https://learn.microsoft.com/en-us/troubleshoot/sql/releases/sqlserver-2022/cumulativeupdate3', '2023-04-13', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 3'), + (16, 4015, 'CU2', 'https://learn.microsoft.com/en-us/troubleshoot/sql/releases/sqlserver-2022/cumulativeupdate2', '2023-03-15', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 2'), + (16, 4003, 'CU1', 'https://learn.microsoft.com/en-us/troubleshoot/sql/releases/sqlserver-2022/cumulativeupdate1', '2023-02-16', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 1'), (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'), + (15, 4312, 'CU20', 'https://support.microsoft.com/kb/5024276', '2023-04-13', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 20'), + (15, 4298, 'CU19', 'https://support.microsoft.com/kb/5023049', '2023-02-16', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 19'), (15, 4280, 'CU18 GDR', 'https://support.microsoft.com/kb/5021124', '2023-02-14', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 18 GDR'), (15, 4261, 'CU18', 'https://support.microsoft.com/en-us/help/5017593', '2022-09-28', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 18'), (15, 4249, 'CU17', 'https://support.microsoft.com/en-us/help/5016394', '2022-08-11', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 17'), @@ -37061,6 +37067,7 @@ ALTER PROCEDURE [dbo].[sp_BlitzFirst] @OutputTableNameWaitStats NVARCHAR(256) = NULL , @OutputTableNameBlitzCache NVARCHAR(256) = NULL , @OutputTableNameBlitzWho NVARCHAR(256) = NULL , + @OutputResultSets NVARCHAR(500) = N'BlitzWho_Start|Findings|FileStats|PerfmonStats|WaitStats|BlitzCache|BlitzWho_End' , @OutputTableRetentionDays TINYINT = 7 , @OutputXMLasNVARCHAR TINYINT = 0 , @FilterPlansByDatabase VARCHAR(MAX) = NULL , @@ -37088,7 +37095,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.13', @VersionDate = '20230215'; +SELECT @Version = '8.14', @VersionDate = '20230420'; IF(@VersionCheckMode = 1) BEGIN @@ -37304,7 +37311,7 @@ END; /* IF @AsOf IS NOT NULL AND @OutputDatabaseName IS NOT NULL AND @OutputSche ELSE IF @LogMessage IS NULL /* IF @OutputType = 'SCHEMA' */ BEGIN /* What's running right now? This is the first and last result set. */ - IF @SinceStartup = 0 AND @Seconds > 0 AND @ExpertMode = 1 AND @OutputType <> 'NONE' + IF @SinceStartup = 0 AND @Seconds > 0 AND @ExpertMode = 1 AND @OutputType <> 'NONE' AND @OutputResultSets LIKE N'%BlitzWho_Start%' BEGIN IF OBJECT_ID('master.dbo.sp_BlitzWho') IS NULL AND OBJECT_ID('dbo.sp_BlitzWho') IS NULL BEGIN @@ -38552,44 +38559,45 @@ BEGIN RAISERROR('Running CheckID 1',10,1) WITH NOWAIT; END - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, QueryHash) - SELECT 1 AS CheckID, - 1 AS Priority, - 'Maintenance Tasks Running' AS FindingGroup, - 'Backup Running' AS Finding, - 'https://www.brentozar.com/askbrent/backups/' AS URL, - 'Backup of ' + DB_NAME(db.resource_database_id) + ' database (' + (SELECT CAST(CAST(SUM(size * 8.0 / 1024 / 1024) AS BIGINT) AS NVARCHAR) FROM #MasterFiles WHERE database_id = db.resource_database_id) + 'GB) ' + @LineFeed - + CAST(r.percent_complete AS NVARCHAR(100)) + '% complete, has been running since ' + CAST(r.start_time AS NVARCHAR(100)) + '. ' + @LineFeed - + CASE WHEN COALESCE(s.nt_username, s.loginame) IS NOT NULL THEN (' Login: ' + COALESCE(s.nt_username, s.loginame) + ' ') ELSE '' END AS Details, - 'KILL ' + CAST(r.session_id AS NVARCHAR(100)) + ';' AS HowToStopIt, - pl.query_plan AS QueryPlan, - r.start_time AS StartTime, - s.loginame AS LoginName, - s.nt_username AS NTUserName, - s.[program_name] AS ProgramName, - s.[hostname] AS HostName, - db.[resource_database_id] AS DatabaseID, - DB_NAME(db.resource_database_id) AS DatabaseName, - 0 AS OpenTransactionCount, - r.query_hash - FROM sys.dm_exec_requests r - INNER JOIN sys.dm_exec_connections c ON r.session_id = c.session_id - INNER JOIN sys.sysprocesses AS s ON r.session_id = s.spid AND s.ecid = 0 - INNER JOIN - ( - SELECT DISTINCT - t.request_session_id, - t.resource_database_id - FROM sys.dm_tran_locks AS t - WHERE t.resource_type = N'DATABASE' - AND t.request_mode = N'S' - AND t.request_status = N'GRANT' - AND t.request_owner_type = N'SHARED_TRANSACTION_WORKSPACE' - ) AS db ON s.spid = db.request_session_id AND s.dbid = db.resource_database_id - CROSS APPLY sys.dm_exec_query_plan(r.plan_handle) pl - WHERE r.command LIKE 'BACKUP%' - AND r.start_time <= DATEADD(minute, -5, GETDATE()) - AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs); + IF EXISTS(SELECT * FROM sys.dm_exec_requests WHERE total_elapsed_time > 300000 AND command LIKE 'BACKUP%') + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, QueryHash) + SELECT 1 AS CheckID, + 1 AS Priority, + 'Maintenance Tasks Running' AS FindingGroup, + 'Backup Running' AS Finding, + 'https://www.brentozar.com/askbrent/backups/' AS URL, + 'Backup of ' + DB_NAME(db.resource_database_id) + ' database (' + (SELECT CAST(CAST(SUM(size * 8.0 / 1024 / 1024) AS BIGINT) AS NVARCHAR) FROM #MasterFiles WHERE database_id = db.resource_database_id) + 'GB) ' + @LineFeed + + CAST(r.percent_complete AS NVARCHAR(100)) + '% complete, has been running since ' + CAST(r.start_time AS NVARCHAR(100)) + '. ' + @LineFeed + + CASE WHEN COALESCE(s.nt_username, s.loginame) IS NOT NULL THEN (' Login: ' + COALESCE(s.nt_username, s.loginame) + ' ') ELSE '' END AS Details, + 'KILL ' + CAST(r.session_id AS NVARCHAR(100)) + ';' AS HowToStopIt, + pl.query_plan AS QueryPlan, + r.start_time AS StartTime, + s.loginame AS LoginName, + s.nt_username AS NTUserName, + s.[program_name] AS ProgramName, + s.[hostname] AS HostName, + db.[resource_database_id] AS DatabaseID, + DB_NAME(db.resource_database_id) AS DatabaseName, + 0 AS OpenTransactionCount, + r.query_hash + FROM sys.dm_exec_requests r + INNER JOIN sys.dm_exec_connections c ON r.session_id = c.session_id + INNER JOIN sys.sysprocesses AS s ON r.session_id = s.spid AND s.ecid = 0 + INNER JOIN + ( + SELECT DISTINCT + t.request_session_id, + t.resource_database_id + FROM sys.dm_tran_locks AS t + WHERE t.resource_type = N'DATABASE' + AND t.request_mode = N'S' + AND t.request_status = N'GRANT' + AND t.request_owner_type = N'SHARED_TRANSACTION_WORKSPACE' + ) AS db ON s.spid = db.request_session_id AND s.dbid = db.resource_database_id + CROSS APPLY sys.dm_exec_query_plan(r.plan_handle) pl + WHERE r.command LIKE 'BACKUP%' + AND r.start_time <= DATEADD(minute, -5, GETDATE()) + AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs); END /* If there's a backup running, add details explaining how long full backup has been taking in the last month. */ @@ -38601,48 +38609,49 @@ BEGIN /* Maintenance Tasks Running - DBCC CHECK* Running - CheckID 2 */ - IF @Seconds > 0 AND EXISTS(SELECT * FROM sys.dm_exec_requests WHERE command LIKE 'DBCC%') + IF @Seconds > 0 BEGIN IF (@Debug = 1) BEGIN RAISERROR('Running CheckID 2',10,1) WITH NOWAIT; END - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, QueryHash) - SELECT 2 AS CheckID, - 1 AS Priority, - 'Maintenance Tasks Running' AS FindingGroup, - 'DBCC CHECK* Running' AS Finding, - 'https://www.brentozar.com/askbrent/dbcc/' AS URL, - 'Corruption check of ' + DB_NAME(db.resource_database_id) + ' database (' + (SELECT CAST(CAST(SUM(size * 8.0 / 1024 / 1024) AS BIGINT) AS NVARCHAR) FROM #MasterFiles WHERE database_id = db.resource_database_id) + 'GB) 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, - s.login_name AS LoginName, - s.nt_user_name AS NTUserName, - s.[program_name] AS ProgramName, - s.[host_name] AS HostName, - db.[resource_database_id] AS DatabaseID, - DB_NAME(db.resource_database_id) AS DatabaseName, - 0 AS OpenTransactionCount, - r.query_hash - FROM sys.dm_exec_requests r - INNER JOIN sys.dm_exec_connections c ON r.session_id = c.session_id - INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id - INNER JOIN (SELECT DISTINCT l.request_session_id, l.resource_database_id - FROM sys.dm_tran_locks l - INNER JOIN sys.databases d ON l.resource_database_id = d.database_id - WHERE l.resource_type = N'DATABASE' - AND l.request_mode = N'S' - AND l.request_status = N'GRANT' - AND l.request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.session_id = db.request_session_id - OUTER APPLY sys.dm_exec_query_plan(r.plan_handle) pl - OUTER APPLY sys.dm_exec_sql_text(r.sql_handle) AS t - WHERE r.command LIKE 'DBCC%' - AND CAST(t.text AS NVARCHAR(4000)) NOT LIKE '%dm_db_index_physical_stats%' - AND CAST(t.text AS NVARCHAR(4000)) NOT LIKE '%ALTER INDEX%' - AND CAST(t.text AS NVARCHAR(4000)) NOT LIKE '%fileproperty%' - AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs); + IF EXISTS (SELECT * FROM sys.dm_exec_requests WHERE command LIKE 'DBCC%' AND total_elapsed_time > 5000) + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, QueryHash) + SELECT 2 AS CheckID, + 1 AS Priority, + 'Maintenance Tasks Running' AS FindingGroup, + 'DBCC CHECK* Running' AS Finding, + 'https://www.brentozar.com/askbrent/dbcc/' AS URL, + 'Corruption check of ' + DB_NAME(db.resource_database_id) + ' database (' + (SELECT CAST(CAST(SUM(size * 8.0 / 1024 / 1024) AS BIGINT) AS NVARCHAR) FROM #MasterFiles WHERE database_id = db.resource_database_id) + 'GB) 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, + s.login_name AS LoginName, + s.nt_user_name AS NTUserName, + s.[program_name] AS ProgramName, + s.[host_name] AS HostName, + db.[resource_database_id] AS DatabaseID, + DB_NAME(db.resource_database_id) AS DatabaseName, + 0 AS OpenTransactionCount, + r.query_hash + FROM sys.dm_exec_requests r + INNER JOIN sys.dm_exec_connections c ON r.session_id = c.session_id + INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id + INNER JOIN (SELECT DISTINCT l.request_session_id, l.resource_database_id + FROM sys.dm_tran_locks l + INNER JOIN sys.databases d ON l.resource_database_id = d.database_id + WHERE l.resource_type = N'DATABASE' + AND l.request_mode = N'S' + AND l.request_status = N'GRANT' + AND l.request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.session_id = db.request_session_id + OUTER APPLY sys.dm_exec_query_plan(r.plan_handle) pl + OUTER APPLY sys.dm_exec_sql_text(r.sql_handle) AS t + WHERE r.command LIKE 'DBCC%' + AND CAST(t.text AS NVARCHAR(4000)) NOT LIKE '%dm_db_index_physical_stats%' + AND CAST(t.text AS NVARCHAR(4000)) NOT LIKE '%ALTER INDEX%' + AND CAST(t.text AS NVARCHAR(4000)) NOT LIKE '%fileproperty%' + AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs); END /* Maintenance Tasks Running - Restore Running - CheckID 3 */ @@ -38653,37 +38662,38 @@ BEGIN RAISERROR('Running CheckID 3',10,1) WITH NOWAIT; END - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, QueryHash) - SELECT 3 AS CheckID, - 1 AS Priority, - 'Maintenance Tasks Running' AS FindingGroup, - 'Restore Running' AS Finding, - 'https://www.brentozar.com/askbrent/backups/' AS URL, - 'Restore of ' + DB_NAME(db.resource_database_id) + ' database (' + (SELECT CAST(CAST(SUM(size * 8.0 / 1024 / 1024) AS BIGINT) AS NVARCHAR) FROM #MasterFiles WHERE database_id = db.resource_database_id) + '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, - s.login_name AS LoginName, - s.nt_user_name AS NTUserName, - s.[program_name] AS ProgramName, - s.[host_name] AS HostName, - db.[resource_database_id] AS DatabaseID, - DB_NAME(db.resource_database_id) AS DatabaseName, - 0 AS OpenTransactionCount, - r.query_hash - FROM sys.dm_exec_requests r - INNER JOIN sys.dm_exec_connections c ON r.session_id = c.session_id - INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id - INNER JOIN ( - SELECT DISTINCT request_session_id, resource_database_id - FROM sys.dm_tran_locks - WHERE resource_type = N'DATABASE' - AND request_mode = N'S' - AND request_status = N'GRANT') AS db ON s.session_id = db.request_session_id - CROSS APPLY sys.dm_exec_query_plan(r.plan_handle) pl - WHERE r.command LIKE 'RESTORE%' - AND s.program_name <> 'SQL Server Log Shipping' - AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs); + IF EXISTS (SELECT * FROM sys.dm_exec_requests WHERE command LIKE 'RESTORE%' AND total_elapsed_time > 5000) + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, QueryHash) + SELECT 3 AS CheckID, + 1 AS Priority, + 'Maintenance Tasks Running' AS FindingGroup, + 'Restore Running' AS Finding, + 'https://www.brentozar.com/askbrent/backups/' AS URL, + 'Restore of ' + DB_NAME(db.resource_database_id) + ' database (' + (SELECT CAST(CAST(SUM(size * 8.0 / 1024 / 1024) AS BIGINT) AS NVARCHAR) FROM #MasterFiles WHERE database_id = db.resource_database_id) + '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, + s.login_name AS LoginName, + s.nt_user_name AS NTUserName, + s.[program_name] AS ProgramName, + s.[host_name] AS HostName, + db.[resource_database_id] AS DatabaseID, + DB_NAME(db.resource_database_id) AS DatabaseName, + 0 AS OpenTransactionCount, + r.query_hash + FROM sys.dm_exec_requests r + INNER JOIN sys.dm_exec_connections c ON r.session_id = c.session_id + INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id + INNER JOIN ( + SELECT DISTINCT request_session_id, resource_database_id + FROM sys.dm_tran_locks + WHERE resource_type = N'DATABASE' + AND request_mode = N'S' + AND request_status = N'GRANT') AS db ON s.session_id = db.request_session_id + CROSS APPLY sys.dm_exec_query_plan(r.plan_handle) pl + WHERE r.command LIKE 'RESTORE%' + AND s.program_name <> 'SQL Server Log Shipping' + AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs); END /* SQL Server Internal Maintenance - Database File Growing - CheckID 4 */ @@ -38796,37 +38806,38 @@ BEGIN RAISERROR('Running CheckID 8',10,1) WITH NOWAIT; END - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, OpenTransactionCount) - SELECT 8 AS CheckID, - 50 AS Priority, - 'Query Problems' AS FindingGroup, - 'Sleeping Query with Open Transactions' AS Finding, - 'https://www.brentozar.com/askbrent/sleeping-query-with-open-transactions/' AS URL, - 'Database: ' + DB_NAME(db.resource_database_id) + @LineFeed + 'Host: ' + s.hostname + @LineFeed + 'Program: ' + s.[program_name] + @LineFeed + 'Asleep with open transactions and locks since ' + CAST(s.last_batch AS NVARCHAR(100)) + '. ' AS Details, - 'KILL ' + CAST(s.spid AS NVARCHAR(100)) + ';' AS HowToStopIt, - s.last_batch AS StartTime, - s.loginame AS LoginName, - s.nt_username AS NTUserName, - s.[program_name] AS ProgramName, - s.hostname AS HostName, - db.[resource_database_id] AS DatabaseID, - DB_NAME(db.resource_database_id) AS DatabaseName, - (SELECT TOP 1 [text] FROM sys.dm_exec_sql_text(c.most_recent_sql_handle)) AS QueryText, - s.open_tran AS OpenTransactionCount - FROM sys.sysprocesses s - INNER JOIN sys.dm_exec_connections c ON s.spid = c.session_id - INNER JOIN ( - SELECT DISTINCT request_session_id, resource_database_id - FROM sys.dm_tran_locks - WHERE resource_type = N'DATABASE' - AND request_mode = N'S' - AND request_status = N'GRANT' - AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.spid = db.request_session_id - WHERE s.status = 'sleeping' - AND s.open_tran > 0 - AND s.last_batch < DATEADD(ss, -10, SYSDATETIME()) - AND EXISTS(SELECT * FROM sys.dm_tran_locks WHERE request_session_id = s.spid - AND NOT (resource_type = N'DATABASE' AND request_mode = N'S' AND request_status = N'GRANT' AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE')); + IF EXISTS (SELECT * FROM sys.dm_exec_requests WHERE total_elapsed_time > 5000 AND request_id > 0) + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, OpenTransactionCount) + SELECT 8 AS CheckID, + 50 AS Priority, + 'Query Problems' AS FindingGroup, + 'Sleeping Query with Open Transactions' AS Finding, + 'https://www.brentozar.com/askbrent/sleeping-query-with-open-transactions/' AS URL, + 'Database: ' + DB_NAME(db.resource_database_id) + @LineFeed + 'Host: ' + s.hostname + @LineFeed + 'Program: ' + s.[program_name] + @LineFeed + 'Asleep with open transactions and locks since ' + CAST(s.last_batch AS NVARCHAR(100)) + '. ' AS Details, + 'KILL ' + CAST(s.spid AS NVARCHAR(100)) + ';' AS HowToStopIt, + s.last_batch AS StartTime, + s.loginame AS LoginName, + s.nt_username AS NTUserName, + s.[program_name] AS ProgramName, + s.hostname AS HostName, + db.[resource_database_id] AS DatabaseID, + DB_NAME(db.resource_database_id) AS DatabaseName, + (SELECT TOP 1 [text] FROM sys.dm_exec_sql_text(c.most_recent_sql_handle)) AS QueryText, + s.open_tran AS OpenTransactionCount + FROM sys.sysprocesses s + INNER JOIN sys.dm_exec_connections c ON s.spid = c.session_id + INNER JOIN ( + SELECT DISTINCT request_session_id, resource_database_id + FROM sys.dm_tran_locks + WHERE resource_type = N'DATABASE' + AND request_mode = N'S' + AND request_status = N'GRANT' + AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.spid = db.request_session_id + WHERE s.status = 'sleeping' + AND s.open_tran > 0 + AND s.last_batch < DATEADD(ss, -10, SYSDATETIME()) + AND EXISTS(SELECT * FROM sys.dm_tran_locks WHERE request_session_id = s.spid + AND NOT (resource_type = N'DATABASE' AND request_mode = N'S' AND request_status = N'GRANT' AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE')); END /*Query Problems - Clients using implicit transactions - CheckID 37 */ @@ -38882,34 +38893,35 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, RAISERROR('Running CheckID 9',10,1) WITH NOWAIT; END - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, QueryHash) - SELECT 9 AS CheckID, - 1 AS Priority, - 'Query Problems' AS FindingGroup, - 'Query Rolling Back' AS Finding, - 'https://www.brentozar.com/askbrent/rollback/' AS URL, - 'Rollback started at ' + CAST(r.start_time AS NVARCHAR(100)) + ', is ' + CAST(r.percent_complete AS NVARCHAR(100)) + '% complete.' AS Details, - 'Unfortunately, you can''t stop this. Whatever you do, don''t restart the server in an attempt to fix it - SQL Server will keep rolling back.' AS HowToStopIt, - r.start_time AS StartTime, - s.login_name AS LoginName, - s.nt_user_name AS NTUserName, - s.[program_name] AS ProgramName, - s.[host_name] AS HostName, - db.[resource_database_id] AS DatabaseID, - DB_NAME(db.resource_database_id) AS DatabaseName, - (SELECT TOP 1 [text] FROM sys.dm_exec_sql_text(c.most_recent_sql_handle)) AS QueryText, - r.query_hash - FROM sys.dm_exec_sessions s - INNER JOIN sys.dm_exec_connections c ON s.session_id = c.session_id - INNER JOIN sys.dm_exec_requests r ON s.session_id = r.session_id - LEFT OUTER JOIN ( - SELECT DISTINCT request_session_id, resource_database_id - FROM sys.dm_tran_locks - WHERE resource_type = N'DATABASE' - AND request_mode = N'S' - AND request_status = N'GRANT' - AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.session_id = db.request_session_id - WHERE r.status = 'rollback'; + IF EXISTS (SELECT * FROM sys.dm_exec_requests WHERE total_elapsed_time > 5000 AND request_id > 0) + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, QueryHash) + SELECT 9 AS CheckID, + 1 AS Priority, + 'Query Problems' AS FindingGroup, + 'Query Rolling Back' AS Finding, + 'https://www.brentozar.com/askbrent/rollback/' AS URL, + 'Rollback started at ' + CAST(r.start_time AS NVARCHAR(100)) + ', is ' + CAST(r.percent_complete AS NVARCHAR(100)) + '% complete.' AS Details, + 'Unfortunately, you can''t stop this. Whatever you do, don''t restart the server in an attempt to fix it - SQL Server will keep rolling back.' AS HowToStopIt, + r.start_time AS StartTime, + s.login_name AS LoginName, + s.nt_user_name AS NTUserName, + s.[program_name] AS ProgramName, + s.[host_name] AS HostName, + db.[resource_database_id] AS DatabaseID, + DB_NAME(db.resource_database_id) AS DatabaseName, + (SELECT TOP 1 [text] FROM sys.dm_exec_sql_text(c.most_recent_sql_handle)) AS QueryText, + r.query_hash + FROM sys.dm_exec_sessions s + INNER JOIN sys.dm_exec_connections c ON s.session_id = c.session_id + INNER JOIN sys.dm_exec_requests r ON s.session_id = r.session_id + LEFT OUTER JOIN ( + SELECT DISTINCT request_session_id, resource_database_id + FROM sys.dm_tran_locks + WHERE resource_type = N'DATABASE' + AND request_mode = N'S' + AND request_status = N'GRANT' + AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.session_id = db.request_session_id + WHERE r.status = 'rollback'; END IF @Seconds > 0 @@ -39187,7 +39199,8 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, JOIN sys.dm_exec_sessions AS s ON r.session_id = s.session_id WHERE s.host_name IS NOT NULL - AND r.total_elapsed_time > 5000 ) + AND r.total_elapsed_time > 5000 + AND r.request_id > 0 ) BEGIN SET @StringToExecute = N' @@ -39205,12 +39218,14 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, FROM ( SELECT deqp.session_id, deqp.request_id, - CASE WHEN deqp.row_count > ( deqp.estimate_row_count * 10000 ) + CASE WHEN (deqp.row_count/10000) > deqp.estimate_row_count THEN 1 ELSE 0 END AS estimate_inaccuracy FROM sys.dm_exec_query_profiles AS deqp + INNER JOIN sys.dm_exec_requests r ON deqp.session_id = r.session_id AND deqp.request_id = r.request_id WHERE deqp.session_id <> @@SPID + AND r.total_elapsed_time > 5000 ) AS x WHERE x.estimate_inaccuracy = 1 GROUP BY x.session_id, @@ -40188,7 +40203,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, /* Check for temp objects with high forwarded fetches. This has to be done as dynamic SQL because we have to execute OBJECT_NAME inside TempDB. */ - IF @@ROWCOUNT > 0 + IF EXISTS (SELECT * FROM #BlitzFirstResults WHERE CheckID = 29) BEGIN SET @StringToExecute = N' INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) @@ -41548,7 +41563,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, FROM #BlitzFirstResults; END; ELSE - IF @OutputType = 'Opserver1' AND @SinceStartup = 0 + IF @OutputType = 'Opserver1' AND @SinceStartup = 0 AND @OutputResultSets LIKE N'%Findings%' BEGIN SELECT r.[Priority] , @@ -41605,7 +41620,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, r.Finding, r.ID; END; - ELSE IF @OutputType IN ( 'CSV', 'RSV' ) AND @SinceStartup = 0 + ELSE IF @OutputType IN ( 'CSV', 'RSV' ) AND @SinceStartup = 0 AND @OutputResultSets LIKE N'%Findings%' BEGIN SELECT Result = CAST([Priority] AS NVARCHAR(100)) @@ -41626,7 +41641,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, Finding, Details; END; - ELSE IF @OutputType = 'Top10' + ELSE IF @OutputType = 'Top10' AND @OutputResultSets LIKE N'%WaitStats%' BEGIN /* Measure waits in hours */ ;WITH max_batch AS ( @@ -41663,7 +41678,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, AND wd2.wait_time_ms-wd1.wait_time_ms > 0 ORDER BY [Wait Time (Seconds)] DESC; END; - ELSE IF @ExpertMode = 0 AND @OutputType <> 'NONE' AND @OutputXMLasNVARCHAR = 0 AND @SinceStartup = 0 + ELSE IF @ExpertMode = 0 AND @OutputType <> 'NONE' AND @OutputXMLasNVARCHAR = 0 AND @SinceStartup = 0 AND @OutputResultSets LIKE N'%Findings%' BEGIN SELECT [Priority] , [FindingsGroup] , @@ -41685,7 +41700,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, ID, CAST(Details AS NVARCHAR(4000)); END; - ELSE IF @OutputType <> 'NONE' AND @OutputXMLasNVARCHAR = 1 AND @SinceStartup = 0 + ELSE IF @OutputType <> 'NONE' AND @OutputXMLasNVARCHAR = 1 AND @SinceStartup = 0 AND @OutputResultSets LIKE N'%Findings%' BEGIN SELECT [Priority] , [FindingsGroup] , @@ -41707,7 +41722,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, ID, CAST(Details AS NVARCHAR(4000)); END; - ELSE IF @ExpertMode = 1 AND @OutputType <> 'NONE' + ELSE IF @ExpertMode = 1 AND @OutputType <> 'NONE' AND @OutputResultSets LIKE N'%Findings%' BEGIN IF @SinceStartup = 0 SELECT r.[Priority] , @@ -41769,7 +41784,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, ------------------------- --What happened: #WaitStats ------------------------- - IF @Seconds = 0 + IF @Seconds = 0 AND @OutputResultSets LIKE N'%WaitStats%' BEGIN /* Measure waits in hours */ ;WITH max_batch AS ( @@ -41813,7 +41828,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, AND wd2.wait_time_ms-wd1.wait_time_ms > 0 ORDER BY [Wait Time (Seconds)] DESC; END; - ELSE + ELSE IF @OutputResultSets LIKE N'%WaitStats%' BEGIN /* Measure waits in seconds */ ;WITH max_batch AS ( @@ -41861,6 +41876,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, ------------------------- --What happened: #FileStats ------------------------- + IF @OutputResultSets LIKE N'%FileStats%' WITH readstats AS ( SELECT 'PHYSICAL READS' AS Pattern, ROW_NUMBER() OVER (ORDER BY wd2.avg_stall_read_ms DESC) AS StallRank, @@ -41919,7 +41935,8 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, --What happened: #PerfmonStats ------------------------- - SELECT 'PERFMON' AS Pattern, pLast.[object_name], pLast.counter_name, pLast.instance_name, + IF @OutputResultSets LIKE N'%PerfmonStats%' + SELECT 'PERFMON' AS Pattern, pLast.[object_name], pLast.counter_name, pLast.instance_name, pFirst.SampleTime AS FirstSampleTime, pFirst.cntr_value AS FirstSampleValue, pLast.SampleTime AS LastSampleTime, pLast.cntr_value AS LastSampleValue, pLast.cntr_value - pFirst.cntr_value AS ValueDelta, @@ -41934,7 +41951,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, ------------------------- --What happened: #QueryStats ------------------------- - IF @CheckProcedureCache = 1 + IF @CheckProcedureCache = 1 AND @OutputResultSets LIKE N'%BlitzCache%' BEGIN SELECT qsNow.*, qsFirst.* @@ -41951,7 +41968,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, DROP TABLE #BlitzFirstResults; /* What's running right now? This is the first and last result set. */ - IF @SinceStartup = 0 AND @Seconds > 0 AND @ExpertMode = 1 AND @OutputType <> 'NONE' + IF @SinceStartup = 0 AND @Seconds > 0 AND @ExpertMode = 1 AND @OutputType <> 'NONE' AND @OutputResultSets LIKE N'%BlitzWho_End%' BEGIN IF OBJECT_ID('master.dbo.sp_BlitzWho') IS NULL AND OBJECT_ID('dbo.sp_BlitzWho') IS NULL BEGIN diff --git a/sp_AllNightLog.sql b/sp_AllNightLog.sql index 77a410ae0..d788ab463 100644 --- a/sp_AllNightLog.sql +++ b/sp_AllNightLog.sql @@ -31,7 +31,7 @@ SET STATISTICS XML OFF; BEGIN; -SELECT @Version = '8.13', @VersionDate = '20230215'; +SELECT @Version = '8.14', @VersionDate = '20230420'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_AllNightLog_Setup.sql b/sp_AllNightLog_Setup.sql index cbcd2924b..61d0bf2fa 100644 --- a/sp_AllNightLog_Setup.sql +++ b/sp_AllNightLog_Setup.sql @@ -38,7 +38,7 @@ SET STATISTICS XML OFF; BEGIN; -SELECT @Version = '8.13', @VersionDate = '20230215'; +SELECT @Version = '8.14', @VersionDate = '20230420'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_Blitz.sql b/sp_Blitz.sql index 92bc1e08f..451bd1b3b 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -38,7 +38,7 @@ AS SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.13', @VersionDate = '20230215'; + SELECT @Version = '8.14', @VersionDate = '20230420'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) diff --git a/sp_BlitzAnalysis.sql b/sp_BlitzAnalysis.sql index 8a9447349..600a2e9c7 100644 --- a/sp_BlitzAnalysis.sql +++ b/sp_BlitzAnalysis.sql @@ -37,7 +37,7 @@ AS SET NOCOUNT ON; SET STATISTICS XML OFF; -SELECT @Version = '8.13', @VersionDate = '20230215'; +SELECT @Version = '8.14', @VersionDate = '20230420'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_BlitzBackups.sql b/sp_BlitzBackups.sql index 15d47a7fd..a554b0b52 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.13', @VersionDate = '20230215'; + SELECT @Version = '8.14', @VersionDate = '20230420'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_BlitzCache.sql b/sp_BlitzCache.sql index 974dec391..72a0b531f 100644 --- a/sp_BlitzCache.sql +++ b/sp_BlitzCache.sql @@ -281,7 +281,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.13', @VersionDate = '20230215'; +SELECT @Version = '8.14', @VersionDate = '20230420'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) diff --git a/sp_BlitzFirst.sql b/sp_BlitzFirst.sql index 05e8cdc26..4d397eff4 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.13', @VersionDate = '20230215'; +SELECT @Version = '8.14', @VersionDate = '20230420'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_BlitzInMemoryOLTP.sql b/sp_BlitzInMemoryOLTP.sql index 1dda6aba1..a768f5c0c 100644 --- a/sp_BlitzInMemoryOLTP.sql +++ b/sp_BlitzInMemoryOLTP.sql @@ -82,7 +82,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI */ AS DECLARE @ScriptVersion VARCHAR(30); -SELECT @ScriptVersion = '1.8', @VersionDate = '20230215'; +SELECT @ScriptVersion = '1.8', @VersionDate = '20230420'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index 0c6dd6b00..f8ecb5ac8 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -48,7 +48,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.13', @VersionDate = '20230215'; +SELECT @Version = '8.14', @VersionDate = '20230420'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index c25fa1772..4e0e261bf 100644 --- a/sp_BlitzLock.sql +++ b/sp_BlitzLock.sql @@ -35,7 +35,7 @@ BEGIN SET NOCOUNT, XACT_ABORT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.13', @VersionDate = '20230215'; + SELECT @Version = '8.14', @VersionDate = '20230420'; IF @VersionCheckMode = 1 BEGIN diff --git a/sp_BlitzQueryStore.sql b/sp_BlitzQueryStore.sql index 3d8dd5cf1..371e5ead6 100644 --- a/sp_BlitzQueryStore.sql +++ b/sp_BlitzQueryStore.sql @@ -57,7 +57,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.13', @VersionDate = '20230215'; +SELECT @Version = '8.14', @VersionDate = '20230420'; IF(@VersionCheckMode = 1) BEGIN RETURN; diff --git a/sp_BlitzWho.sql b/sp_BlitzWho.sql index 8df031f37..9931cc085 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.13', @VersionDate = '20230215'; + SELECT @Version = '8.14', @VersionDate = '20230420'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_DatabaseRestore.sql b/sp_DatabaseRestore.sql index a66abe79c..ce6f076a1 100755 --- a/sp_DatabaseRestore.sql +++ b/sp_DatabaseRestore.sql @@ -43,7 +43,7 @@ SET STATISTICS XML OFF; /*Versioning details*/ -SELECT @Version = '8.13', @VersionDate = '20230215'; +SELECT @Version = '8.14', @VersionDate = '20230420'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_ineachdb.sql b/sp_ineachdb.sql index 6ec373ba6..e9fcff593 100644 --- a/sp_ineachdb.sql +++ b/sp_ineachdb.sql @@ -35,7 +35,7 @@ BEGIN SET NOCOUNT ON; SET STATISTICS XML OFF; - SELECT @Version = '8.13', @VersionDate = '20230215'; + SELECT @Version = '8.14', @VersionDate = '20230420'; IF(@VersionCheckMode = 1) BEGIN From 149aabea6b3d70947c7b9dd8fcc25a8b9ba69922 Mon Sep 17 00:00:00 2001 From: sqlslinger <64641794+sqlslinger@users.noreply.github.com> Date: Wed, 26 Apr 2023 14:56:51 -0400 Subject: [PATCH 395/662] Fix last log file for split backups when using @StopAt --- sp_DatabaseRestore.sql | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/sp_DatabaseRestore.sql b/sp_DatabaseRestore.sql index ce6f076a1..1d6999e43 100755 --- a/sp_DatabaseRestore.sql +++ b/sp_DatabaseRestore.sql @@ -11,10 +11,10 @@ ALTER PROCEDURE [dbo].[sp_DatabaseRestore] @MoveDataDrive NVARCHAR(260) = NULL, @MoveLogDrive NVARCHAR(260) = NULL, @MoveFilestreamDrive NVARCHAR(260) = NULL, - @MoveFullTextCatalogDrive NVARCHAR(260) = NULL, - @BufferCount INT = NULL, - @MaxTransferSize INT = NULL, - @BlockSize INT = NULL, + @MoveFullTextCatalogDrive NVARCHAR(260) = NULL, + @BufferCount INT = NULL, + @MaxTransferSize INT = NULL, + @BlockSize INT = NULL, @TestRestore BIT = 0, @RunCheckDB BIT = 0, @RestoreDiff BIT = 0, @@ -27,15 +27,15 @@ ALTER PROCEDURE [dbo].[sp_DatabaseRestore] @StopAt NVARCHAR(14) = NULL, @OnlyLogsAfter NVARCHAR(14) = NULL, @SimpleFolderEnumeration BIT = 0, - @SkipBackupsAlreadyInMsdb BIT = 0, - @DatabaseOwner sysname = NULL, - @SetTrustworthyON BIT = 0, + @SkipBackupsAlreadyInMsdb BIT = 0, + @DatabaseOwner sysname = NULL, + @SetTrustworthyON BIT = 0, @Execute CHAR(1) = Y, - @FileExtensionDiff NVARCHAR(128) = NULL, + @FileExtensionDiff NVARCHAR(128) = NULL, @Debug INT = 0, @Help BIT = 0, @Version VARCHAR(30) = NULL OUTPUT, - @VersionDate DATETIME = NULL OUTPUT, + @VersionDate DATETIME = NULL OUTPUT, @VersionCheckMode BIT = 0 AS SET NOCOUNT ON; @@ -899,7 +899,7 @@ BEGIN /* now take split backups into account */ IF (SELECT COUNT(*) FROM #SplitFullBackups) > 0 BEGIN - RAISERROR('Split backups found', 0, 1) WITH NOWAIT; + IF @Debug = 1 RAISERROR('Split backups found', 0, 1) WITH NOWAIT; SET @sql = N'RESTORE DATABASE ' + @RestoreDatabaseName + N' FROM ' + STUFF( @@ -1092,7 +1092,7 @@ BEGIN IF (SELECT COUNT(*) FROM #SplitDiffBackups) > 0 BEGIN - RAISERROR ('Split backups found', 0, 1) WITH NOWAIT; + IF @Debug = 1 RAISERROR ('Split backups found', 0, 1) WITH NOWAIT; SET @sql = N'RESTORE DATABASE ' + @RestoreDatabaseName + N' FROM ' + STUFF( (SELECT CHAR( 10 ) + ',DISK=''' + BackupPath + BackupFile + '''' @@ -1374,7 +1374,7 @@ BEGIN AND REPLACE( RIGHT( REPLACE( fl.BackupFile, RIGHT( fl.BackupFile, PATINDEX( '%_[0-9][0-9]%', REVERSE( fl.BackupFile ) ) ), '' ), 16 ), '_', '' ) > @StopAt ORDER BY BackupFile; END - + IF @ExtraLogFile IS NULL BEGIN DELETE fl @@ -1385,6 +1385,10 @@ BEGIN END ELSE BEGIN + -- If this is a split backup, @ExtraLogFile contains only the first split backup file, either _1.trn or _01.trn + -- Change @ExtraLogFile to the max split backup file, then delete all log files greater than this + SET @ExtraLogFile = REPLACE(REPLACE(@ExtraLogFile, '_1.trn', '_9.trn'), '_01.trn', '_64.trn') + DELETE fl FROM @FileList AS fl WHERE BackupFile LIKE N'%.trn' @@ -1447,7 +1451,7 @@ WHERE BackupFile IS NOT NULL; IF (SELECT COUNT( * ) FROM #SplitLogBackups WHERE DenseRank = @LogRestoreRanking) > 1 BEGIN - RAISERROR ('Split backups found', 0, 1) WITH NOWAIT; + IF @Debug = 1 RAISERROR ('Split backups found', 0, 1) WITH NOWAIT; SET @sql = N'RESTORE LOG ' + @RestoreDatabaseName + N' FROM ' + STUFF( (SELECT CHAR( 10 ) + ',DISK=''' + BackupPath + BackupFile + '''' From 7e9756a083cfcae0ad8933626a526e6d7cd663eb Mon Sep 17 00:00:00 2001 From: sm8680 Date: Thu, 4 May 2023 13:49:42 -0400 Subject: [PATCH 396/662] Update sp_Blitz.sql Updated SQL Server 2016 from SP2 to SP3. --- sp_Blitz.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index 451bd1b3b..7c1b5f8c8 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -3879,7 +3879,7 @@ AS IF (@ProductVersionMajor = 15 AND @ProductVersionMinor < 2000) OR (@ProductVersionMajor = 14 AND @ProductVersionMinor < 1000) OR - (@ProductVersionMajor = 13 AND @ProductVersionMinor < 5026) OR + (@ProductVersionMajor = 13 AND @ProductVersionMinor < 6300) OR (@ProductVersionMajor = 12 AND @ProductVersionMinor < 6024) OR (@ProductVersionMajor = 11 AND @ProductVersionMinor < 7001) OR (@ProductVersionMajor = 10.5 /*AND @ProductVersionMinor < 6000*/) OR From 1ff6e68c345da32840bd257b4626f7056b65ce2f Mon Sep 17 00:00:00 2001 From: Michel Zehnder Date: Wed, 10 May 2023 08:43:15 +0200 Subject: [PATCH 397/662] Adjust check to include SQL 2012 as Out of Support --- sp_Blitz.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index 451bd1b3b..87d5208e9 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -3881,7 +3881,7 @@ AS (@ProductVersionMajor = 14 AND @ProductVersionMinor < 1000) OR (@ProductVersionMajor = 13 AND @ProductVersionMinor < 5026) OR (@ProductVersionMajor = 12 AND @ProductVersionMinor < 6024) OR - (@ProductVersionMajor = 11 AND @ProductVersionMinor < 7001) OR + (@ProductVersionMajor = 11 /*AND @ProductVersionMinor < 7001)*/ OR (@ProductVersionMajor = 10.5 /*AND @ProductVersionMinor < 6000*/) OR (@ProductVersionMajor = 10 /*AND @ProductVersionMinor < 6000*/) OR (@ProductVersionMajor = 9 /*AND @ProductVersionMinor <= 5000*/) @@ -3892,7 +3892,7 @@ AS INSERT INTO #BlitzResults(CheckID, Priority, FindingsGroup, Finding, URL, Details) VALUES(128, 20, 'Reliability', 'Unsupported Build of SQL Server', 'https://www.brentozar.com/go/unsupported', 'Version ' + CAST(@ProductVersionMajor AS VARCHAR(100)) + - CASE WHEN @ProductVersionMajor >= 11 THEN + CASE WHEN @ProductVersionMajor >= 12 THEN '.' + CAST(@ProductVersionMinor AS VARCHAR(100)) + ' is no longer supported by Microsoft. You need to apply a service pack.' ELSE ' is no longer supported by Microsoft. You should be making plans to upgrade to a modern version of SQL Server.' END); END; From e27946813a1a0a91d0280b444b588d790ccc17d9 Mon Sep 17 00:00:00 2001 From: Michel Zehnder Date: Wed, 10 May 2023 08:58:06 +0200 Subject: [PATCH 398/662] Update Unsupported SQL 2016 version to < SP3 (Build 6300) --- sp_Blitz.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index 87d5208e9..be7021e25 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -3879,7 +3879,7 @@ AS IF (@ProductVersionMajor = 15 AND @ProductVersionMinor < 2000) OR (@ProductVersionMajor = 14 AND @ProductVersionMinor < 1000) OR - (@ProductVersionMajor = 13 AND @ProductVersionMinor < 5026) OR + (@ProductVersionMajor = 13 AND @ProductVersionMinor < 6300) OR (@ProductVersionMajor = 12 AND @ProductVersionMinor < 6024) OR (@ProductVersionMajor = 11 /*AND @ProductVersionMinor < 7001)*/ OR (@ProductVersionMajor = 10.5 /*AND @ProductVersionMinor < 6000*/) OR From 4364d2cb29fbc169f12759be23329fae9b06b6df Mon Sep 17 00:00:00 2001 From: sqlslinger <64641794+sqlslinger@users.noreply.github.com> Date: Wed, 26 Apr 2023 14:56:51 -0400 Subject: [PATCH 399/662] sp_DatabaseRestore.sql: Add @FixOrphanUsers option Implements issue #3267 Fix database_principals that do not match server_principals after a cross-server restore. --- sp_DatabaseRestore.sql | 60 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 49 insertions(+), 11 deletions(-) diff --git a/sp_DatabaseRestore.sql b/sp_DatabaseRestore.sql index ce6f076a1..58b901f83 100755 --- a/sp_DatabaseRestore.sql +++ b/sp_DatabaseRestore.sql @@ -11,10 +11,10 @@ ALTER PROCEDURE [dbo].[sp_DatabaseRestore] @MoveDataDrive NVARCHAR(260) = NULL, @MoveLogDrive NVARCHAR(260) = NULL, @MoveFilestreamDrive NVARCHAR(260) = NULL, - @MoveFullTextCatalogDrive NVARCHAR(260) = NULL, - @BufferCount INT = NULL, - @MaxTransferSize INT = NULL, - @BlockSize INT = NULL, + @MoveFullTextCatalogDrive NVARCHAR(260) = NULL, + @BufferCount INT = NULL, + @MaxTransferSize INT = NULL, + @BlockSize INT = NULL, @TestRestore BIT = 0, @RunCheckDB BIT = 0, @RestoreDiff BIT = 0, @@ -27,15 +27,16 @@ ALTER PROCEDURE [dbo].[sp_DatabaseRestore] @StopAt NVARCHAR(14) = NULL, @OnlyLogsAfter NVARCHAR(14) = NULL, @SimpleFolderEnumeration BIT = 0, - @SkipBackupsAlreadyInMsdb BIT = 0, - @DatabaseOwner sysname = NULL, - @SetTrustworthyON BIT = 0, + @SkipBackupsAlreadyInMsdb BIT = 0, + @DatabaseOwner sysname = NULL, + @SetTrustworthyON BIT = 0, + @FixOrphanUsers BIT = 0, @Execute CHAR(1) = Y, - @FileExtensionDiff NVARCHAR(128) = NULL, + @FileExtensionDiff NVARCHAR(128) = NULL, @Debug INT = 0, @Help BIT = 0, @Version VARCHAR(30) = NULL OUTPUT, - @VersionDate DATETIME = NULL OUTPUT, + @VersionDate DATETIME = NULL OUTPUT, @VersionCheckMode BIT = 0 AS SET NOCOUNT ON; @@ -1374,7 +1375,6 @@ BEGIN AND REPLACE( RIGHT( REPLACE( fl.BackupFile, RIGHT( fl.BackupFile, PATINDEX( '%_[0-9][0-9]%', REVERSE( fl.BackupFile ) ) ), '' ), 16 ), '_', '' ) > @StopAt ORDER BY BackupFile; END - IF @ExtraLogFile IS NULL BEGIN DELETE fl @@ -1584,7 +1584,45 @@ IF @DatabaseOwner IS NOT NULL END END; - -- If test restore then blow the database away (be careful) +-- Link a user entry in the sys.database_principals system catalog view in the restored database to a SQL Server login of the same name +IF @FixOrphanUsers = 1 + BEGIN + SET @sql = N' +-- Fixup Orphan Users by setting database user sid to match login sid +DECLARE @FixOrphansSql NVARCHAR(MAX); +DECLARE @OrphanUsers TABLE (SqlToExecute NVARCHAR(MAX)); +USE ' + @RestoreDatabaseName + '; + +INSERT @OrphanUsers +SELECT ''ALTER USER ['' + d.name + ''] WITH LOGIN = ['' + d.name + '']; '' + FROM sys.database_principals d + INNER JOIN master.sys.server_principals s ON d.name COLLATE DATABASE_DEFAULT = s.name COLLATE DATABASE_DEFAULT + WHERE d.type_desc = ''SQL_USER'' + AND d.name NOT IN (''guest'',''dbo'') + AND d.sid <> s.sid + ORDER BY d.name; + +SELECT @FixOrphansSql = (SELECT SqlToExecute AS [text()] FROM @OrphanUsers FOR XML PATH (''''), TYPE).value(''text()[1]'',''NVARCHAR(MAX)''); + +IF @FixOrphansSql IS NULL + PRINT ''No orphan users require a sid fixup.''; +ELSE +BEGIN + PRINT ''Fix Orphan Users: '' + @FixOrphansSql; + EXECUTE(@FixOrphansSql); +END;' + + IF @Debug = 1 OR @Execute = 'N' + BEGIN + IF @sql IS NULL PRINT '@sql is NULL for Fix Orphan Users'; + PRINT @sql; + END; + + IF @Debug IN (0, 1) AND @Execute = 'Y' + EXECUTE [dbo].[CommandExecute] @DatabaseContext = 'master', @Command = @sql, @CommandType = 'UPDATE', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y'; + END; + +-- If test restore then blow the database away (be careful) IF @TestRestore = 1 BEGIN SET @sql = N'DROP DATABASE ' + @RestoreDatabaseName + NCHAR(13); From 64fe9bd07ef965b58dadb3c1f34a441856d6b177 Mon Sep 17 00:00:00 2001 From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com> Date: Wed, 17 May 2023 13:41:16 -0400 Subject: [PATCH 400/662] Issue 3279 Closes #3279 * Adds more explicit check for SQLDB vs MI * Standardize xml parsing for session * Allow for other xml deadlock reporting events --- sp_BlitzLock.sql | 38 ++++++++++++++++++++++++++++---------- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index 4e0e261bf..2593a0cf8 100644 --- a/sp_BlitzLock.sql +++ b/sp_BlitzLock.sql @@ -150,8 +150,12 @@ BEGIN WHEN ( SELECT - SERVERPROPERTY('EDITION') - ) = 'SQL Azure' + CONVERT + ( + integer, + SERVERPROPERTY('EngineEdition') + ) + ) = 5 THEN 1 ELSE 0 END, @@ -845,7 +849,12 @@ BEGIN LEFT JOIN #t AS t ON 1 = 1 CROSS APPLY x.x.nodes('/RingBufferTarget/event') AS e(x) - WHERE e.x.exist('@name[ .= "xml_deadlock_report"]') = 1 + WHERE + ( + e.x.exist('@name[ .= "xml_deadlock_report"]') = 1 + OR e.x.exist('@name[ .= "database_xml_deadlock_report"]') = 1 + OR e.x.exist('@name[ .= "xml_deadlock_report_filtered"]') = 1 + ) AND e.x.exist('@timestamp[. >= sql:variable("@StartDate")]') = 1 AND e.x.exist('@timestamp[. < sql:variable("@EndDate")]') = 1 OPTION(RECOMPILE); @@ -864,7 +873,9 @@ BEGIN SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Inserting to #deadlock_data for event file data', 0, 1) WITH NOWAIT; - INSERT + IF @Debug = 1 BEGIN SET STATISTICS XML ON; END; + + INSERT #deadlock_data WITH(TABLOCKX) ( deadlock_xml @@ -876,12 +887,19 @@ BEGIN LEFT JOIN #t AS t ON 1 = 1 CROSS APPLY x.x.nodes('/event') AS e(x) - WHERE e.x.exist('/event/@name[ .= "xml_deadlock_report"]') = 1 - AND e.x.exist('/event/@timestamp[. >= sql:variable("@StartDate")]') = 1 - AND e.x.exist('/event/@timestamp[. < sql:variable("@EndDate")]') = 1 + WHERE + ( + e.x.exist('@name[ .= "xml_deadlock_report"]') = 1 + OR e.x.exist('@name[ .= "database_xml_deadlock_report"]') = 1 + OR e.x.exist('@name[ .= "xml_deadlock_report_filtered"]') = 1 + ) + AND e.x.exist('@timestamp[. >= sql:variable("@StartDate")]') = 1 + AND e.x.exist('@timestamp[. < sql:variable("@EndDate")]') = 1 OPTION(RECOMPILE); - SET @d = CONVERT(varchar(40), GETDATE(), 109); + IF @Debug = 1 BEGIN SET STATISTICS XML OFF; END; + + SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; END; @@ -908,7 +926,7 @@ BEGIN FROM sys.fn_xe_file_target_read_file(N'system_health*.xel', NULL, NULL, NULL) AS fx LEFT JOIN #t AS t ON 1 = 1 - WHERE fx.object_name = N'xml_deadlock_report' + WHERE fx.object_name = N'xml_deadlock_report' ) AS xml CROSS APPLY xml.deadlock_xml.nodes('/event') AS e(x) WHERE 1 = 1 @@ -2532,7 +2550,7 @@ BEGIN ), 14 ) - END + END FROM #deadlock_owner_waiter AS dow JOIN #deadlock_process AS dp ON (dp.id = dow.owner_id From 904688ef2498e024c964f468efa58461593ca781 Mon Sep 17 00:00:00 2001 From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com> Date: Wed, 17 May 2023 13:55:12 -0400 Subject: [PATCH 401/662] Update sp_BlitzLock.sql Closes #3277 --- sp_BlitzLock.sql | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index 2593a0cf8..479a46211 100644 --- a/sp_BlitzLock.sql +++ b/sp_BlitzLock.sql @@ -383,7 +383,7 @@ BEGIN /*Add wait_resource column*/ ALTER TABLE ' + @ObjectFullName + - N' ADD client_option_1 nvarchar(8000) NULL;'; + N' ADD client_option_1 varchar(500) NULL;'; IF @Debug = 1 BEGIN PRINT @StringToExecute; END; EXEC sys.sp_executesql @@ -399,7 +399,7 @@ BEGIN /*Add wait_resource column*/ ALTER TABLE ' + @ObjectFullName + - N' ADD client_option_2 nvarchar(8000) NULL;'; + N' ADD client_option_2 varchar(500) NULL;'; IF @Debug = 1 BEGIN PRINT @StringToExecute; END; EXEC sys.sp_executesql @@ -462,8 +462,8 @@ BEGIN waiter_mode nvarchar(256), lock_mode nvarchar(256), transaction_count bigint, - client_option_1 varchar(2000), - client_option_2 varchar(2000), + client_option_1 varchar(500), + client_option_2 varchar(500), login_name nvarchar(256), host_name nvarchar(256), client_app nvarchar(1024), @@ -1024,7 +1024,7 @@ BEGIN CASE WHEN q.clientoption1 & 8192 = 8192 THEN ', NUMERIC_ROUNDABORT' ELSE '' END + CASE WHEN q.clientoption1 & 16384 = 16384 THEN ', XACT_ABORT' ELSE '' END, 3, - 8000 + 500 ), client_option_2 = SUBSTRING @@ -1046,7 +1046,7 @@ BEGIN CASE WHEN q.clientoption2 & 1073741824 = 1073741824 THEN ', AUTO UPDATE STATISTICS' ELSE '' END + CASE WHEN q.clientoption2 & 1469283328 = 1469283328 THEN ', ALL SETTABLE OPTIONS' ELSE '' END, 3, - 8000 + 500 ), q.process_xml INTO #deadlock_process From e7c2ecb85f5466e6df694c217460bfc9f5e5584f Mon Sep 17 00:00:00 2001 From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com> Date: Wed, 17 May 2023 14:04:47 -0400 Subject: [PATCH 402/662] Update sp_BlitzLock.sql Closes #3276 --- sp_BlitzLock.sql | 66 +++++++++++++++++++++++++----------------------- 1 file changed, 34 insertions(+), 32 deletions(-) diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index 479a46211..d631d1b66 100644 --- a/sp_BlitzLock.sql +++ b/sp_BlitzLock.sql @@ -3538,38 +3538,40 @@ BEGIN DROP SYNONYM DeadlockFindings; /*done with inserting.*/ END; ELSE /*Output to database is not set output to client app*/ - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Results to client %s', 0, 1, @d) WITH NOWAIT; - - IF @Debug = 1 BEGIN SET STATISTICS XML ON; END; - - EXEC sys.sp_executesql - @deadlock_result; - - IF @Debug = 1 - BEGIN - SET STATISTICS XML OFF; - PRINT @deadlock_result; - END; - - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Returning findings %s', 0, 1, @d) WITH NOWAIT; - - SELECT - df.check_id, - df.database_name, - df.object_name, - df.finding_group, - df.finding - FROM #deadlock_findings AS df - ORDER BY df.check_id - OPTION(RECOMPILE); - - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - END; /*done with output to client app.*/ + BEGIN + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Results to client %s', 0, 1, @d) WITH NOWAIT; + + IF @Debug = 1 BEGIN SET STATISTICS XML ON; END; + + EXEC sys.sp_executesql + @deadlock_result; + + IF @Debug = 1 + BEGIN + SET STATISTICS XML OFF; + PRINT @deadlock_result; + END; + + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Returning findings %s', 0, 1, @d) WITH NOWAIT; + + SELECT + df.check_id, + df.database_name, + df.object_name, + df.finding_group, + df.finding + FROM #deadlock_findings AS df + ORDER BY df.check_id + OPTION(RECOMPILE); + + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + END; /*done with output to client app.*/ + END; IF @Debug = 1 BEGIN From c7d14b1b930e81f3282091a645da9da2ee906b41 Mon Sep 17 00:00:00 2001 From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com> Date: Wed, 31 May 2023 15:13:23 -0400 Subject: [PATCH 403/662] Update sp_BlitzLock.sql Closes #3283 Adds checks for owner and waiter mode showing S locks. --- sp_BlitzLock.sql | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index d631d1b66..ee39544e9 100644 --- a/sp_BlitzLock.sql +++ b/sp_BlitzLock.sql @@ -1813,6 +1813,16 @@ BEGIN N'S', N'IS' ) + OR dow.owner_mode IN + ( + N'S', + N'IS' + ) + OR dow.waiter_mode IN + ( + N'S', + N'IS' + ) AND (dow.database_id = @DatabaseId OR @DatabaseName IS NULL) AND (dow.event_date >= @StartDate OR @StartDate IS NULL) AND (dow.event_date < @EndDate OR @EndDate IS NULL) From f0449f78bf750a924dcccf8385485c1dcdc2d7b7 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Tue, 13 Jun 2023 13:35:54 -0700 Subject: [PATCH 404/662] #3278 sp_Blitz TDE alerts Show alerts in all databases, and also fixed a bug in the unsupported version checks. Closes #3278. --- sp_Blitz.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index be7021e25..69d533408 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -1265,7 +1265,7 @@ AS db_name(dek.database_id) AS DatabaseName, ''https://www.brentozar.com/go/tde'' AS URL, ''The certificate '' + c.name + '' is used to encrypt database '' + db_name(dek.database_id) + ''. Last backup date: '' + COALESCE(CAST(c.pvt_key_last_backup_date AS VARCHAR(100)), ''Never'') AS Details - FROM sys.certificates c INNER JOIN sys.dm_database_encryption_keys dek ON c.thumbprint = dek.encryptor_thumbprint + FROM master.sys.certificates c INNER JOIN sys.dm_database_encryption_keys dek ON c.thumbprint = dek.encryptor_thumbprint WHERE pvt_key_last_backup_date IS NULL OR pvt_key_last_backup_date <= DATEADD(dd, -30, GETDATE()) OPTION (RECOMPILE);'; IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; @@ -3881,7 +3881,7 @@ AS (@ProductVersionMajor = 14 AND @ProductVersionMinor < 1000) OR (@ProductVersionMajor = 13 AND @ProductVersionMinor < 6300) OR (@ProductVersionMajor = 12 AND @ProductVersionMinor < 6024) OR - (@ProductVersionMajor = 11 /*AND @ProductVersionMinor < 7001)*/ OR + (@ProductVersionMajor = 11 /*AND @ProductVersionMinor < 7001)*/) OR (@ProductVersionMajor = 10.5 /*AND @ProductVersionMinor < 6000*/) OR (@ProductVersionMajor = 10 /*AND @ProductVersionMinor < 6000*/) OR (@ProductVersionMajor = 9 /*AND @ProductVersionMinor <= 5000*/) From 1bde9f8e0996d56461d86dabe31d08db97b0bcc9 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Tue, 13 Jun 2023 13:44:23 -0700 Subject: [PATCH 405/662] #3275 add 2022 CU4 And change CU1-3 to use the KB ID numbers. Closes #3275. --- SqlServerVersions.sql | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/SqlServerVersions.sql b/SqlServerVersions.sql index d1478c7bf..db33a7929 100644 --- a/SqlServerVersions.sql +++ b/SqlServerVersions.sql @@ -41,9 +41,10 @@ DELETE FROM dbo.SqlServerVersions; INSERT INTO dbo.SqlServerVersions (MajorVersionNumber, MinorVersionNumber, Branch, [Url], ReleaseDate, MainstreamSupportEndDate, ExtendedSupportEndDate, MajorVersionName, MinorVersionName) VALUES - (16, 4025, 'CU3', 'https://learn.microsoft.com/en-us/troubleshoot/sql/releases/sqlserver-2022/cumulativeupdate3', '2023-04-13', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 3'), - (16, 4015, 'CU2', 'https://learn.microsoft.com/en-us/troubleshoot/sql/releases/sqlserver-2022/cumulativeupdate2', '2023-03-15', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 2'), - (16, 4003, 'CU1', 'https://learn.microsoft.com/en-us/troubleshoot/sql/releases/sqlserver-2022/cumulativeupdate1', '2023-02-16', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 1'), + (16, 4035, 'CU4', 'https://support.microsoft.com/en-us/help/5026717', '2023-05-11', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 4'), + (16, 4025, 'CU3', 'https://support.microsoft.com/en-us/help/5024396', '2023-04-13', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 3'), + (16, 4015, 'CU2', 'https://support.microsoft.com/en-us/help/5023127', '2023-03-15', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 2'), + (16, 4003, 'CU1', 'https://support.microsoft.com/en-us/help/5022375', '2023-02-16', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 1'), (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'), (15, 4312, 'CU20', 'https://support.microsoft.com/kb/5024276', '2023-04-13', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 20'), From 464396c06dc6e6c5869d235d884379cc5062f36b Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Tue, 13 Jun 2023 14:17:43 -0700 Subject: [PATCH 406/662] 20230613 release Bumping version numbers and dates. --- Install-All-Scripts.sql | 238 ++++++++++++++++-------- Install-Core-Blitz-No-Query-Store.sql | 157 +++++++++------- Install-Core-Blitz-With-Query-Store.sql | 159 +++++++++------- sp_AllNightLog.sql | 2 +- sp_AllNightLog_Setup.sql | 2 +- sp_Blitz.sql | 2 +- sp_BlitzAnalysis.sql | 2 +- sp_BlitzBackups.sql | 2 +- sp_BlitzCache.sql | 2 +- sp_BlitzFirst.sql | 2 +- sp_BlitzInMemoryOLTP.sql | 2 +- sp_BlitzIndex.sql | 2 +- sp_BlitzLock.sql | 2 +- sp_BlitzQueryStore.sql | 2 +- sp_BlitzWho.sql | 2 +- sp_DatabaseRestore.sql | 2 +- sp_ineachdb.sql | 2 +- 17 files changed, 359 insertions(+), 223 deletions(-) diff --git a/Install-All-Scripts.sql b/Install-All-Scripts.sql index 85d3ba010..054ff3f13 100644 --- a/Install-All-Scripts.sql +++ b/Install-All-Scripts.sql @@ -38,7 +38,7 @@ SET STATISTICS XML OFF; BEGIN; -SELECT @Version = '8.14', @VersionDate = '20230420'; +SELECT @Version = '8.15', @VersionDate = '20230613'; IF(@VersionCheckMode = 1) BEGIN @@ -1375,7 +1375,7 @@ SET STATISTICS XML OFF; BEGIN; -SELECT @Version = '8.14', @VersionDate = '20230420'; +SELECT @Version = '8.15', @VersionDate = '20230613'; IF(@VersionCheckMode = 1) BEGIN @@ -2900,7 +2900,7 @@ AS SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.14', @VersionDate = '20230420'; + SELECT @Version = '8.15', @VersionDate = '20230613'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -4127,7 +4127,7 @@ AS db_name(dek.database_id) AS DatabaseName, ''https://www.brentozar.com/go/tde'' AS URL, ''The certificate '' + c.name + '' is used to encrypt database '' + db_name(dek.database_id) + ''. Last backup date: '' + COALESCE(CAST(c.pvt_key_last_backup_date AS VARCHAR(100)), ''Never'') AS Details - FROM sys.certificates c INNER JOIN sys.dm_database_encryption_keys dek ON c.thumbprint = dek.encryptor_thumbprint + FROM master.sys.certificates c INNER JOIN sys.dm_database_encryption_keys dek ON c.thumbprint = dek.encryptor_thumbprint WHERE pvt_key_last_backup_date IS NULL OR pvt_key_last_backup_date <= DATEADD(dd, -30, GETDATE()) OPTION (RECOMPILE);'; IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; @@ -6741,9 +6741,9 @@ AS IF (@ProductVersionMajor = 15 AND @ProductVersionMinor < 2000) OR (@ProductVersionMajor = 14 AND @ProductVersionMinor < 1000) OR - (@ProductVersionMajor = 13 AND @ProductVersionMinor < 5026) OR + (@ProductVersionMajor = 13 AND @ProductVersionMinor < 6300) OR (@ProductVersionMajor = 12 AND @ProductVersionMinor < 6024) OR - (@ProductVersionMajor = 11 AND @ProductVersionMinor < 7001) OR + (@ProductVersionMajor = 11 /*AND @ProductVersionMinor < 7001)*/) OR (@ProductVersionMajor = 10.5 /*AND @ProductVersionMinor < 6000*/) OR (@ProductVersionMajor = 10 /*AND @ProductVersionMinor < 6000*/) OR (@ProductVersionMajor = 9 /*AND @ProductVersionMinor <= 5000*/) @@ -6754,7 +6754,7 @@ AS INSERT INTO #BlitzResults(CheckID, Priority, FindingsGroup, Finding, URL, Details) VALUES(128, 20, 'Reliability', 'Unsupported Build of SQL Server', 'https://www.brentozar.com/go/unsupported', 'Version ' + CAST(@ProductVersionMajor AS VARCHAR(100)) + - CASE WHEN @ProductVersionMajor >= 11 THEN + CASE WHEN @ProductVersionMajor >= 12 THEN '.' + CAST(@ProductVersionMinor AS VARCHAR(100)) + ' is no longer supported by Microsoft. You need to apply a service pack.' ELSE ' is no longer supported by Microsoft. You should be making plans to upgrade to a modern version of SQL Server.' END); END; @@ -12599,7 +12599,7 @@ AS SET NOCOUNT ON; SET STATISTICS XML OFF; -SELECT @Version = '8.14', @VersionDate = '20230420'; +SELECT @Version = '8.15', @VersionDate = '20230613'; IF(@VersionCheckMode = 1) BEGIN @@ -13477,7 +13477,7 @@ AS SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.14', @VersionDate = '20230420'; + SELECT @Version = '8.15', @VersionDate = '20230613'; IF(@VersionCheckMode = 1) BEGIN @@ -15259,7 +15259,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.14', @VersionDate = '20230420'; +SELECT @Version = '8.15', @VersionDate = '20230613'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -22577,7 +22577,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.14', @VersionDate = '20230420'; +SELECT @Version = '8.15', @VersionDate = '20230613'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -28754,7 +28754,7 @@ BEGIN SET NOCOUNT, XACT_ABORT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.14', @VersionDate = '20230420'; + SELECT @Version = '8.15', @VersionDate = '20230613'; IF @VersionCheckMode = 1 BEGIN @@ -28869,8 +28869,12 @@ BEGIN WHEN ( SELECT - SERVERPROPERTY('EDITION') - ) = 'SQL Azure' + CONVERT + ( + integer, + SERVERPROPERTY('EngineEdition') + ) + ) = 5 THEN 1 ELSE 0 END, @@ -29098,7 +29102,7 @@ BEGIN /*Add wait_resource column*/ ALTER TABLE ' + @ObjectFullName + - N' ADD client_option_1 nvarchar(8000) NULL;'; + N' ADD client_option_1 varchar(500) NULL;'; IF @Debug = 1 BEGIN PRINT @StringToExecute; END; EXEC sys.sp_executesql @@ -29114,7 +29118,7 @@ BEGIN /*Add wait_resource column*/ ALTER TABLE ' + @ObjectFullName + - N' ADD client_option_2 nvarchar(8000) NULL;'; + N' ADD client_option_2 varchar(500) NULL;'; IF @Debug = 1 BEGIN PRINT @StringToExecute; END; EXEC sys.sp_executesql @@ -29177,8 +29181,8 @@ BEGIN waiter_mode nvarchar(256), lock_mode nvarchar(256), transaction_count bigint, - client_option_1 varchar(2000), - client_option_2 varchar(2000), + client_option_1 varchar(500), + client_option_2 varchar(500), login_name nvarchar(256), host_name nvarchar(256), client_app nvarchar(1024), @@ -29564,7 +29568,12 @@ BEGIN LEFT JOIN #t AS t ON 1 = 1 CROSS APPLY x.x.nodes('/RingBufferTarget/event') AS e(x) - WHERE e.x.exist('@name[ .= "xml_deadlock_report"]') = 1 + WHERE + ( + e.x.exist('@name[ .= "xml_deadlock_report"]') = 1 + OR e.x.exist('@name[ .= "database_xml_deadlock_report"]') = 1 + OR e.x.exist('@name[ .= "xml_deadlock_report_filtered"]') = 1 + ) AND e.x.exist('@timestamp[. >= sql:variable("@StartDate")]') = 1 AND e.x.exist('@timestamp[. < sql:variable("@EndDate")]') = 1 OPTION(RECOMPILE); @@ -29583,7 +29592,9 @@ BEGIN SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Inserting to #deadlock_data for event file data', 0, 1) WITH NOWAIT; - INSERT + IF @Debug = 1 BEGIN SET STATISTICS XML ON; END; + + INSERT #deadlock_data WITH(TABLOCKX) ( deadlock_xml @@ -29595,12 +29606,19 @@ BEGIN LEFT JOIN #t AS t ON 1 = 1 CROSS APPLY x.x.nodes('/event') AS e(x) - WHERE e.x.exist('/event/@name[ .= "xml_deadlock_report"]') = 1 - AND e.x.exist('/event/@timestamp[. >= sql:variable("@StartDate")]') = 1 - AND e.x.exist('/event/@timestamp[. < sql:variable("@EndDate")]') = 1 + WHERE + ( + e.x.exist('@name[ .= "xml_deadlock_report"]') = 1 + OR e.x.exist('@name[ .= "database_xml_deadlock_report"]') = 1 + OR e.x.exist('@name[ .= "xml_deadlock_report_filtered"]') = 1 + ) + AND e.x.exist('@timestamp[. >= sql:variable("@StartDate")]') = 1 + AND e.x.exist('@timestamp[. < sql:variable("@EndDate")]') = 1 OPTION(RECOMPILE); - SET @d = CONVERT(varchar(40), GETDATE(), 109); + IF @Debug = 1 BEGIN SET STATISTICS XML OFF; END; + + SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; END; @@ -29627,7 +29645,7 @@ BEGIN FROM sys.fn_xe_file_target_read_file(N'system_health*.xel', NULL, NULL, NULL) AS fx LEFT JOIN #t AS t ON 1 = 1 - WHERE fx.object_name = N'xml_deadlock_report' + WHERE fx.object_name = N'xml_deadlock_report' ) AS xml CROSS APPLY xml.deadlock_xml.nodes('/event') AS e(x) WHERE 1 = 1 @@ -29725,7 +29743,7 @@ BEGIN CASE WHEN q.clientoption1 & 8192 = 8192 THEN ', NUMERIC_ROUNDABORT' ELSE '' END + CASE WHEN q.clientoption1 & 16384 = 16384 THEN ', XACT_ABORT' ELSE '' END, 3, - 8000 + 500 ), client_option_2 = SUBSTRING @@ -29747,7 +29765,7 @@ BEGIN CASE WHEN q.clientoption2 & 1073741824 = 1073741824 THEN ', AUTO UPDATE STATISTICS' ELSE '' END + CASE WHEN q.clientoption2 & 1469283328 = 1469283328 THEN ', ALL SETTABLE OPTIONS' ELSE '' END, 3, - 8000 + 500 ), q.process_xml INTO #deadlock_process @@ -30514,6 +30532,16 @@ BEGIN N'S', N'IS' ) + OR dow.owner_mode IN + ( + N'S', + N'IS' + ) + OR dow.waiter_mode IN + ( + N'S', + N'IS' + ) AND (dow.database_id = @DatabaseId OR @DatabaseName IS NULL) AND (dow.event_date >= @StartDate OR @StartDate IS NULL) AND (dow.event_date < @EndDate OR @EndDate IS NULL) @@ -31251,7 +31279,7 @@ BEGIN ), 14 ) - END + END FROM #deadlock_owner_waiter AS dow JOIN #deadlock_process AS dp ON (dp.id = dow.owner_id @@ -32239,38 +32267,40 @@ BEGIN DROP SYNONYM DeadlockFindings; /*done with inserting.*/ END; ELSE /*Output to database is not set output to client app*/ - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Results to client %s', 0, 1, @d) WITH NOWAIT; - - IF @Debug = 1 BEGIN SET STATISTICS XML ON; END; - - EXEC sys.sp_executesql - @deadlock_result; - - IF @Debug = 1 - BEGIN - SET STATISTICS XML OFF; - PRINT @deadlock_result; - END; - - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Returning findings %s', 0, 1, @d) WITH NOWAIT; - - SELECT - df.check_id, - df.database_name, - df.object_name, - df.finding_group, - df.finding - FROM #deadlock_findings AS df - ORDER BY df.check_id - OPTION(RECOMPILE); - - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - END; /*done with output to client app.*/ + BEGIN + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Results to client %s', 0, 1, @d) WITH NOWAIT; + + IF @Debug = 1 BEGIN SET STATISTICS XML ON; END; + + EXEC sys.sp_executesql + @deadlock_result; + + IF @Debug = 1 + BEGIN + SET STATISTICS XML OFF; + PRINT @deadlock_result; + END; + + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Returning findings %s', 0, 1, @d) WITH NOWAIT; + + SELECT + df.check_id, + df.database_name, + df.object_name, + df.finding_group, + df.finding + FROM #deadlock_findings AS df + ORDER BY df.check_id + OPTION(RECOMPILE); + + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + END; /*done with output to client app.*/ + END; IF @Debug = 1 BEGIN @@ -32403,7 +32433,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.14', @VersionDate = '20230420'; +SELECT @Version = '8.15', @VersionDate = '20230613'; IF(@VersionCheckMode = 1) BEGIN RETURN; @@ -38134,7 +38164,7 @@ BEGIN SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.14', @VersionDate = '20230420'; + SELECT @Version = '8.15', @VersionDate = '20230613'; IF(@VersionCheckMode = 1) BEGIN @@ -39500,10 +39530,10 @@ ALTER PROCEDURE [dbo].[sp_DatabaseRestore] @MoveDataDrive NVARCHAR(260) = NULL, @MoveLogDrive NVARCHAR(260) = NULL, @MoveFilestreamDrive NVARCHAR(260) = NULL, - @MoveFullTextCatalogDrive NVARCHAR(260) = NULL, - @BufferCount INT = NULL, - @MaxTransferSize INT = NULL, - @BlockSize INT = NULL, + @MoveFullTextCatalogDrive NVARCHAR(260) = NULL, + @BufferCount INT = NULL, + @MaxTransferSize INT = NULL, + @BlockSize INT = NULL, @TestRestore BIT = 0, @RunCheckDB BIT = 0, @RestoreDiff BIT = 0, @@ -39516,15 +39546,16 @@ ALTER PROCEDURE [dbo].[sp_DatabaseRestore] @StopAt NVARCHAR(14) = NULL, @OnlyLogsAfter NVARCHAR(14) = NULL, @SimpleFolderEnumeration BIT = 0, - @SkipBackupsAlreadyInMsdb BIT = 0, - @DatabaseOwner sysname = NULL, - @SetTrustworthyON BIT = 0, + @SkipBackupsAlreadyInMsdb BIT = 0, + @DatabaseOwner sysname = NULL, + @SetTrustworthyON BIT = 0, + @FixOrphanUsers BIT = 0, @Execute CHAR(1) = Y, - @FileExtensionDiff NVARCHAR(128) = NULL, + @FileExtensionDiff NVARCHAR(128) = NULL, @Debug INT = 0, @Help BIT = 0, @Version VARCHAR(30) = NULL OUTPUT, - @VersionDate DATETIME = NULL OUTPUT, + @VersionDate DATETIME = NULL OUTPUT, @VersionCheckMode BIT = 0 AS SET NOCOUNT ON; @@ -39532,7 +39563,7 @@ SET STATISTICS XML OFF; /*Versioning details*/ -SELECT @Version = '8.14', @VersionDate = '20230420'; +SELECT @Version = '8.15', @VersionDate = '20230613'; IF(@VersionCheckMode = 1) BEGIN @@ -40388,7 +40419,7 @@ BEGIN /* now take split backups into account */ IF (SELECT COUNT(*) FROM #SplitFullBackups) > 0 BEGIN - RAISERROR('Split backups found', 0, 1) WITH NOWAIT; + IF @Debug = 1 RAISERROR('Split backups found', 0, 1) WITH NOWAIT; SET @sql = N'RESTORE DATABASE ' + @RestoreDatabaseName + N' FROM ' + STUFF( @@ -40581,7 +40612,7 @@ BEGIN IF (SELECT COUNT(*) FROM #SplitDiffBackups) > 0 BEGIN - RAISERROR ('Split backups found', 0, 1) WITH NOWAIT; + IF @Debug = 1 RAISERROR ('Split backups found', 0, 1) WITH NOWAIT; SET @sql = N'RESTORE DATABASE ' + @RestoreDatabaseName + N' FROM ' + STUFF( (SELECT CHAR( 10 ) + ',DISK=''' + BackupPath + BackupFile + '''' @@ -40863,7 +40894,7 @@ BEGIN AND REPLACE( RIGHT( REPLACE( fl.BackupFile, RIGHT( fl.BackupFile, PATINDEX( '%_[0-9][0-9]%', REVERSE( fl.BackupFile ) ) ), '' ), 16 ), '_', '' ) > @StopAt ORDER BY BackupFile; END - + IF @ExtraLogFile IS NULL BEGIN DELETE fl @@ -40874,6 +40905,10 @@ BEGIN END ELSE BEGIN + -- If this is a split backup, @ExtraLogFile contains only the first split backup file, either _1.trn or _01.trn + -- Change @ExtraLogFile to the max split backup file, then delete all log files greater than this + SET @ExtraLogFile = REPLACE(REPLACE(@ExtraLogFile, '_1.trn', '_9.trn'), '_01.trn', '_64.trn') + DELETE fl FROM @FileList AS fl WHERE BackupFile LIKE N'%.trn' @@ -40936,7 +40971,7 @@ WHERE BackupFile IS NOT NULL; IF (SELECT COUNT( * ) FROM #SplitLogBackups WHERE DenseRank = @LogRestoreRanking) > 1 BEGIN - RAISERROR ('Split backups found', 0, 1) WITH NOWAIT; + IF @Debug = 1 RAISERROR ('Split backups found', 0, 1) WITH NOWAIT; SET @sql = N'RESTORE LOG ' + @RestoreDatabaseName + N' FROM ' + STUFF( (SELECT CHAR( 10 ) + ',DISK=''' + BackupPath + BackupFile + '''' @@ -41073,7 +41108,45 @@ IF @DatabaseOwner IS NOT NULL END END; - -- If test restore then blow the database away (be careful) +-- Link a user entry in the sys.database_principals system catalog view in the restored database to a SQL Server login of the same name +IF @FixOrphanUsers = 1 + BEGIN + SET @sql = N' +-- Fixup Orphan Users by setting database user sid to match login sid +DECLARE @FixOrphansSql NVARCHAR(MAX); +DECLARE @OrphanUsers TABLE (SqlToExecute NVARCHAR(MAX)); +USE ' + @RestoreDatabaseName + '; + +INSERT @OrphanUsers +SELECT ''ALTER USER ['' + d.name + ''] WITH LOGIN = ['' + d.name + '']; '' + FROM sys.database_principals d + INNER JOIN master.sys.server_principals s ON d.name COLLATE DATABASE_DEFAULT = s.name COLLATE DATABASE_DEFAULT + WHERE d.type_desc = ''SQL_USER'' + AND d.name NOT IN (''guest'',''dbo'') + AND d.sid <> s.sid + ORDER BY d.name; + +SELECT @FixOrphansSql = (SELECT SqlToExecute AS [text()] FROM @OrphanUsers FOR XML PATH (''''), TYPE).value(''text()[1]'',''NVARCHAR(MAX)''); + +IF @FixOrphansSql IS NULL + PRINT ''No orphan users require a sid fixup.''; +ELSE +BEGIN + PRINT ''Fix Orphan Users: '' + @FixOrphansSql; + EXECUTE(@FixOrphansSql); +END;' + + IF @Debug = 1 OR @Execute = 'N' + BEGIN + IF @sql IS NULL PRINT '@sql is NULL for Fix Orphan Users'; + PRINT @sql; + END; + + IF @Debug IN (0, 1) AND @Execute = 'Y' + EXECUTE [dbo].[CommandExecute] @DatabaseContext = 'master', @Command = @sql, @CommandType = 'UPDATE', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y'; + END; + +-- If test restore then blow the database away (be careful) IF @TestRestore = 1 BEGIN SET @sql = N'DROP DATABASE ' + @RestoreDatabaseName + NCHAR(13); @@ -41131,7 +41204,7 @@ BEGIN SET NOCOUNT ON; SET STATISTICS XML OFF; - SELECT @Version = '8.14', @VersionDate = '20230420'; + SELECT @Version = '8.15', @VersionDate = '20230613'; IF(@VersionCheckMode = 1) BEGIN @@ -41477,9 +41550,10 @@ DELETE FROM dbo.SqlServerVersions; INSERT INTO dbo.SqlServerVersions (MajorVersionNumber, MinorVersionNumber, Branch, [Url], ReleaseDate, MainstreamSupportEndDate, ExtendedSupportEndDate, MajorVersionName, MinorVersionName) VALUES - (16, 4025, 'CU3', 'https://learn.microsoft.com/en-us/troubleshoot/sql/releases/sqlserver-2022/cumulativeupdate3', '2023-04-13', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 3'), - (16, 4015, 'CU2', 'https://learn.microsoft.com/en-us/troubleshoot/sql/releases/sqlserver-2022/cumulativeupdate2', '2023-03-15', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 2'), - (16, 4003, 'CU1', 'https://learn.microsoft.com/en-us/troubleshoot/sql/releases/sqlserver-2022/cumulativeupdate1', '2023-02-16', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 1'), + (16, 4035, 'CU4', 'https://support.microsoft.com/en-us/help/5026717', '2023-05-11', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 4'), + (16, 4025, 'CU3', 'https://support.microsoft.com/en-us/help/5024396', '2023-04-13', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 3'), + (16, 4015, 'CU2', 'https://support.microsoft.com/en-us/help/5023127', '2023-03-15', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 2'), + (16, 4003, 'CU1', 'https://support.microsoft.com/en-us/help/5022375', '2023-02-16', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 1'), (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'), (15, 4312, 'CU20', 'https://support.microsoft.com/kb/5024276', '2023-04-13', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 20'), @@ -41904,7 +41978,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.14', @VersionDate = '20230420'; +SELECT @Version = '8.15', @VersionDate = '20230613'; IF(@VersionCheckMode = 1) BEGIN diff --git a/Install-Core-Blitz-No-Query-Store.sql b/Install-Core-Blitz-No-Query-Store.sql index 3ce39a617..7acb71d2b 100644 --- a/Install-Core-Blitz-No-Query-Store.sql +++ b/Install-Core-Blitz-No-Query-Store.sql @@ -38,7 +38,7 @@ AS SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.14', @VersionDate = '20230420'; + SELECT @Version = '8.15', @VersionDate = '20230613'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -1265,7 +1265,7 @@ AS db_name(dek.database_id) AS DatabaseName, ''https://www.brentozar.com/go/tde'' AS URL, ''The certificate '' + c.name + '' is used to encrypt database '' + db_name(dek.database_id) + ''. Last backup date: '' + COALESCE(CAST(c.pvt_key_last_backup_date AS VARCHAR(100)), ''Never'') AS Details - FROM sys.certificates c INNER JOIN sys.dm_database_encryption_keys dek ON c.thumbprint = dek.encryptor_thumbprint + FROM master.sys.certificates c INNER JOIN sys.dm_database_encryption_keys dek ON c.thumbprint = dek.encryptor_thumbprint WHERE pvt_key_last_backup_date IS NULL OR pvt_key_last_backup_date <= DATEADD(dd, -30, GETDATE()) OPTION (RECOMPILE);'; IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; @@ -3879,9 +3879,9 @@ AS IF (@ProductVersionMajor = 15 AND @ProductVersionMinor < 2000) OR (@ProductVersionMajor = 14 AND @ProductVersionMinor < 1000) OR - (@ProductVersionMajor = 13 AND @ProductVersionMinor < 5026) OR + (@ProductVersionMajor = 13 AND @ProductVersionMinor < 6300) OR (@ProductVersionMajor = 12 AND @ProductVersionMinor < 6024) OR - (@ProductVersionMajor = 11 AND @ProductVersionMinor < 7001) OR + (@ProductVersionMajor = 11 /*AND @ProductVersionMinor < 7001)*/) OR (@ProductVersionMajor = 10.5 /*AND @ProductVersionMinor < 6000*/) OR (@ProductVersionMajor = 10 /*AND @ProductVersionMinor < 6000*/) OR (@ProductVersionMajor = 9 /*AND @ProductVersionMinor <= 5000*/) @@ -3892,7 +3892,7 @@ AS INSERT INTO #BlitzResults(CheckID, Priority, FindingsGroup, Finding, URL, Details) VALUES(128, 20, 'Reliability', 'Unsupported Build of SQL Server', 'https://www.brentozar.com/go/unsupported', 'Version ' + CAST(@ProductVersionMajor AS VARCHAR(100)) + - CASE WHEN @ProductVersionMajor >= 11 THEN + CASE WHEN @ProductVersionMajor >= 12 THEN '.' + CAST(@ProductVersionMinor AS VARCHAR(100)) + ' is no longer supported by Microsoft. You need to apply a service pack.' ELSE ' is no longer supported by Microsoft. You should be making plans to upgrade to a modern version of SQL Server.' END); END; @@ -9737,7 +9737,7 @@ AS SET NOCOUNT ON; SET STATISTICS XML OFF; -SELECT @Version = '8.14', @VersionDate = '20230420'; +SELECT @Version = '8.15', @VersionDate = '20230613'; IF(@VersionCheckMode = 1) BEGIN @@ -10615,7 +10615,7 @@ AS SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.14', @VersionDate = '20230420'; + SELECT @Version = '8.15', @VersionDate = '20230613'; IF(@VersionCheckMode = 1) BEGIN @@ -12397,7 +12397,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.14', @VersionDate = '20230420'; +SELECT @Version = '8.15', @VersionDate = '20230613'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -19715,7 +19715,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.14', @VersionDate = '20230420'; +SELECT @Version = '8.15', @VersionDate = '20230613'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -25892,7 +25892,7 @@ BEGIN SET NOCOUNT, XACT_ABORT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.14', @VersionDate = '20230420'; + SELECT @Version = '8.15', @VersionDate = '20230613'; IF @VersionCheckMode = 1 BEGIN @@ -26007,8 +26007,12 @@ BEGIN WHEN ( SELECT - SERVERPROPERTY('EDITION') - ) = 'SQL Azure' + CONVERT + ( + integer, + SERVERPROPERTY('EngineEdition') + ) + ) = 5 THEN 1 ELSE 0 END, @@ -26236,7 +26240,7 @@ BEGIN /*Add wait_resource column*/ ALTER TABLE ' + @ObjectFullName + - N' ADD client_option_1 nvarchar(8000) NULL;'; + N' ADD client_option_1 varchar(500) NULL;'; IF @Debug = 1 BEGIN PRINT @StringToExecute; END; EXEC sys.sp_executesql @@ -26252,7 +26256,7 @@ BEGIN /*Add wait_resource column*/ ALTER TABLE ' + @ObjectFullName + - N' ADD client_option_2 nvarchar(8000) NULL;'; + N' ADD client_option_2 varchar(500) NULL;'; IF @Debug = 1 BEGIN PRINT @StringToExecute; END; EXEC sys.sp_executesql @@ -26315,8 +26319,8 @@ BEGIN waiter_mode nvarchar(256), lock_mode nvarchar(256), transaction_count bigint, - client_option_1 varchar(2000), - client_option_2 varchar(2000), + client_option_1 varchar(500), + client_option_2 varchar(500), login_name nvarchar(256), host_name nvarchar(256), client_app nvarchar(1024), @@ -26702,7 +26706,12 @@ BEGIN LEFT JOIN #t AS t ON 1 = 1 CROSS APPLY x.x.nodes('/RingBufferTarget/event') AS e(x) - WHERE e.x.exist('@name[ .= "xml_deadlock_report"]') = 1 + WHERE + ( + e.x.exist('@name[ .= "xml_deadlock_report"]') = 1 + OR e.x.exist('@name[ .= "database_xml_deadlock_report"]') = 1 + OR e.x.exist('@name[ .= "xml_deadlock_report_filtered"]') = 1 + ) AND e.x.exist('@timestamp[. >= sql:variable("@StartDate")]') = 1 AND e.x.exist('@timestamp[. < sql:variable("@EndDate")]') = 1 OPTION(RECOMPILE); @@ -26721,7 +26730,9 @@ BEGIN SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Inserting to #deadlock_data for event file data', 0, 1) WITH NOWAIT; - INSERT + IF @Debug = 1 BEGIN SET STATISTICS XML ON; END; + + INSERT #deadlock_data WITH(TABLOCKX) ( deadlock_xml @@ -26733,12 +26744,19 @@ BEGIN LEFT JOIN #t AS t ON 1 = 1 CROSS APPLY x.x.nodes('/event') AS e(x) - WHERE e.x.exist('/event/@name[ .= "xml_deadlock_report"]') = 1 - AND e.x.exist('/event/@timestamp[. >= sql:variable("@StartDate")]') = 1 - AND e.x.exist('/event/@timestamp[. < sql:variable("@EndDate")]') = 1 + WHERE + ( + e.x.exist('@name[ .= "xml_deadlock_report"]') = 1 + OR e.x.exist('@name[ .= "database_xml_deadlock_report"]') = 1 + OR e.x.exist('@name[ .= "xml_deadlock_report_filtered"]') = 1 + ) + AND e.x.exist('@timestamp[. >= sql:variable("@StartDate")]') = 1 + AND e.x.exist('@timestamp[. < sql:variable("@EndDate")]') = 1 OPTION(RECOMPILE); - SET @d = CONVERT(varchar(40), GETDATE(), 109); + IF @Debug = 1 BEGIN SET STATISTICS XML OFF; END; + + SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; END; @@ -26765,7 +26783,7 @@ BEGIN FROM sys.fn_xe_file_target_read_file(N'system_health*.xel', NULL, NULL, NULL) AS fx LEFT JOIN #t AS t ON 1 = 1 - WHERE fx.object_name = N'xml_deadlock_report' + WHERE fx.object_name = N'xml_deadlock_report' ) AS xml CROSS APPLY xml.deadlock_xml.nodes('/event') AS e(x) WHERE 1 = 1 @@ -26863,7 +26881,7 @@ BEGIN CASE WHEN q.clientoption1 & 8192 = 8192 THEN ', NUMERIC_ROUNDABORT' ELSE '' END + CASE WHEN q.clientoption1 & 16384 = 16384 THEN ', XACT_ABORT' ELSE '' END, 3, - 8000 + 500 ), client_option_2 = SUBSTRING @@ -26885,7 +26903,7 @@ BEGIN CASE WHEN q.clientoption2 & 1073741824 = 1073741824 THEN ', AUTO UPDATE STATISTICS' ELSE '' END + CASE WHEN q.clientoption2 & 1469283328 = 1469283328 THEN ', ALL SETTABLE OPTIONS' ELSE '' END, 3, - 8000 + 500 ), q.process_xml INTO #deadlock_process @@ -27652,6 +27670,16 @@ BEGIN N'S', N'IS' ) + OR dow.owner_mode IN + ( + N'S', + N'IS' + ) + OR dow.waiter_mode IN + ( + N'S', + N'IS' + ) AND (dow.database_id = @DatabaseId OR @DatabaseName IS NULL) AND (dow.event_date >= @StartDate OR @StartDate IS NULL) AND (dow.event_date < @EndDate OR @EndDate IS NULL) @@ -28389,7 +28417,7 @@ BEGIN ), 14 ) - END + END FROM #deadlock_owner_waiter AS dow JOIN #deadlock_process AS dp ON (dp.id = dow.owner_id @@ -29377,38 +29405,40 @@ BEGIN DROP SYNONYM DeadlockFindings; /*done with inserting.*/ END; ELSE /*Output to database is not set output to client app*/ - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Results to client %s', 0, 1, @d) WITH NOWAIT; - - IF @Debug = 1 BEGIN SET STATISTICS XML ON; END; - - EXEC sys.sp_executesql - @deadlock_result; - - IF @Debug = 1 - BEGIN - SET STATISTICS XML OFF; - PRINT @deadlock_result; - END; - - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Returning findings %s', 0, 1, @d) WITH NOWAIT; - - SELECT - df.check_id, - df.database_name, - df.object_name, - df.finding_group, - df.finding - FROM #deadlock_findings AS df - ORDER BY df.check_id - OPTION(RECOMPILE); - - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - END; /*done with output to client app.*/ + BEGIN + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Results to client %s', 0, 1, @d) WITH NOWAIT; + + IF @Debug = 1 BEGIN SET STATISTICS XML ON; END; + + EXEC sys.sp_executesql + @deadlock_result; + + IF @Debug = 1 + BEGIN + SET STATISTICS XML OFF; + PRINT @deadlock_result; + END; + + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Returning findings %s', 0, 1, @d) WITH NOWAIT; + + SELECT + df.check_id, + df.database_name, + df.object_name, + df.finding_group, + df.finding + FROM #deadlock_findings AS df + ORDER BY df.check_id + OPTION(RECOMPILE); + + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + END; /*done with output to client app.*/ + END; IF @Debug = 1 BEGIN @@ -29517,7 +29547,7 @@ BEGIN SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.14', @VersionDate = '20230420'; + SELECT @Version = '8.15', @VersionDate = '20230613'; IF(@VersionCheckMode = 1) BEGIN @@ -30913,9 +30943,10 @@ DELETE FROM dbo.SqlServerVersions; INSERT INTO dbo.SqlServerVersions (MajorVersionNumber, MinorVersionNumber, Branch, [Url], ReleaseDate, MainstreamSupportEndDate, ExtendedSupportEndDate, MajorVersionName, MinorVersionName) VALUES - (16, 4025, 'CU3', 'https://learn.microsoft.com/en-us/troubleshoot/sql/releases/sqlserver-2022/cumulativeupdate3', '2023-04-13', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 3'), - (16, 4015, 'CU2', 'https://learn.microsoft.com/en-us/troubleshoot/sql/releases/sqlserver-2022/cumulativeupdate2', '2023-03-15', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 2'), - (16, 4003, 'CU1', 'https://learn.microsoft.com/en-us/troubleshoot/sql/releases/sqlserver-2022/cumulativeupdate1', '2023-02-16', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 1'), + (16, 4035, 'CU4', 'https://support.microsoft.com/en-us/help/5026717', '2023-05-11', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 4'), + (16, 4025, 'CU3', 'https://support.microsoft.com/en-us/help/5024396', '2023-04-13', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 3'), + (16, 4015, 'CU2', 'https://support.microsoft.com/en-us/help/5023127', '2023-03-15', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 2'), + (16, 4003, 'CU1', 'https://support.microsoft.com/en-us/help/5022375', '2023-02-16', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 1'), (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'), (15, 4312, 'CU20', 'https://support.microsoft.com/kb/5024276', '2023-04-13', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 20'), @@ -31340,7 +31371,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.14', @VersionDate = '20230420'; +SELECT @Version = '8.15', @VersionDate = '20230613'; IF(@VersionCheckMode = 1) BEGIN diff --git a/Install-Core-Blitz-With-Query-Store.sql b/Install-Core-Blitz-With-Query-Store.sql index 20db09239..b06e07543 100644 --- a/Install-Core-Blitz-With-Query-Store.sql +++ b/Install-Core-Blitz-With-Query-Store.sql @@ -38,7 +38,7 @@ AS SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.14', @VersionDate = '20230420'; + SELECT @Version = '8.15', @VersionDate = '20230613'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -1265,7 +1265,7 @@ AS db_name(dek.database_id) AS DatabaseName, ''https://www.brentozar.com/go/tde'' AS URL, ''The certificate '' + c.name + '' is used to encrypt database '' + db_name(dek.database_id) + ''. Last backup date: '' + COALESCE(CAST(c.pvt_key_last_backup_date AS VARCHAR(100)), ''Never'') AS Details - FROM sys.certificates c INNER JOIN sys.dm_database_encryption_keys dek ON c.thumbprint = dek.encryptor_thumbprint + FROM master.sys.certificates c INNER JOIN sys.dm_database_encryption_keys dek ON c.thumbprint = dek.encryptor_thumbprint WHERE pvt_key_last_backup_date IS NULL OR pvt_key_last_backup_date <= DATEADD(dd, -30, GETDATE()) OPTION (RECOMPILE);'; IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; @@ -3879,9 +3879,9 @@ AS IF (@ProductVersionMajor = 15 AND @ProductVersionMinor < 2000) OR (@ProductVersionMajor = 14 AND @ProductVersionMinor < 1000) OR - (@ProductVersionMajor = 13 AND @ProductVersionMinor < 5026) OR + (@ProductVersionMajor = 13 AND @ProductVersionMinor < 6300) OR (@ProductVersionMajor = 12 AND @ProductVersionMinor < 6024) OR - (@ProductVersionMajor = 11 AND @ProductVersionMinor < 7001) OR + (@ProductVersionMajor = 11 /*AND @ProductVersionMinor < 7001)*/) OR (@ProductVersionMajor = 10.5 /*AND @ProductVersionMinor < 6000*/) OR (@ProductVersionMajor = 10 /*AND @ProductVersionMinor < 6000*/) OR (@ProductVersionMajor = 9 /*AND @ProductVersionMinor <= 5000*/) @@ -3892,7 +3892,7 @@ AS INSERT INTO #BlitzResults(CheckID, Priority, FindingsGroup, Finding, URL, Details) VALUES(128, 20, 'Reliability', 'Unsupported Build of SQL Server', 'https://www.brentozar.com/go/unsupported', 'Version ' + CAST(@ProductVersionMajor AS VARCHAR(100)) + - CASE WHEN @ProductVersionMajor >= 11 THEN + CASE WHEN @ProductVersionMajor >= 12 THEN '.' + CAST(@ProductVersionMinor AS VARCHAR(100)) + ' is no longer supported by Microsoft. You need to apply a service pack.' ELSE ' is no longer supported by Microsoft. You should be making plans to upgrade to a modern version of SQL Server.' END); END; @@ -9737,7 +9737,7 @@ AS SET NOCOUNT ON; SET STATISTICS XML OFF; -SELECT @Version = '8.14', @VersionDate = '20230420'; +SELECT @Version = '8.15', @VersionDate = '20230613'; IF(@VersionCheckMode = 1) BEGIN @@ -10615,7 +10615,7 @@ AS SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.14', @VersionDate = '20230420'; + SELECT @Version = '8.15', @VersionDate = '20230613'; IF(@VersionCheckMode = 1) BEGIN @@ -12397,7 +12397,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.14', @VersionDate = '20230420'; +SELECT @Version = '8.15', @VersionDate = '20230613'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -19715,7 +19715,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.14', @VersionDate = '20230420'; +SELECT @Version = '8.15', @VersionDate = '20230613'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -25892,7 +25892,7 @@ BEGIN SET NOCOUNT, XACT_ABORT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.14', @VersionDate = '20230420'; + SELECT @Version = '8.15', @VersionDate = '20230613'; IF @VersionCheckMode = 1 BEGIN @@ -26007,8 +26007,12 @@ BEGIN WHEN ( SELECT - SERVERPROPERTY('EDITION') - ) = 'SQL Azure' + CONVERT + ( + integer, + SERVERPROPERTY('EngineEdition') + ) + ) = 5 THEN 1 ELSE 0 END, @@ -26236,7 +26240,7 @@ BEGIN /*Add wait_resource column*/ ALTER TABLE ' + @ObjectFullName + - N' ADD client_option_1 nvarchar(8000) NULL;'; + N' ADD client_option_1 varchar(500) NULL;'; IF @Debug = 1 BEGIN PRINT @StringToExecute; END; EXEC sys.sp_executesql @@ -26252,7 +26256,7 @@ BEGIN /*Add wait_resource column*/ ALTER TABLE ' + @ObjectFullName + - N' ADD client_option_2 nvarchar(8000) NULL;'; + N' ADD client_option_2 varchar(500) NULL;'; IF @Debug = 1 BEGIN PRINT @StringToExecute; END; EXEC sys.sp_executesql @@ -26315,8 +26319,8 @@ BEGIN waiter_mode nvarchar(256), lock_mode nvarchar(256), transaction_count bigint, - client_option_1 varchar(2000), - client_option_2 varchar(2000), + client_option_1 varchar(500), + client_option_2 varchar(500), login_name nvarchar(256), host_name nvarchar(256), client_app nvarchar(1024), @@ -26702,7 +26706,12 @@ BEGIN LEFT JOIN #t AS t ON 1 = 1 CROSS APPLY x.x.nodes('/RingBufferTarget/event') AS e(x) - WHERE e.x.exist('@name[ .= "xml_deadlock_report"]') = 1 + WHERE + ( + e.x.exist('@name[ .= "xml_deadlock_report"]') = 1 + OR e.x.exist('@name[ .= "database_xml_deadlock_report"]') = 1 + OR e.x.exist('@name[ .= "xml_deadlock_report_filtered"]') = 1 + ) AND e.x.exist('@timestamp[. >= sql:variable("@StartDate")]') = 1 AND e.x.exist('@timestamp[. < sql:variable("@EndDate")]') = 1 OPTION(RECOMPILE); @@ -26721,7 +26730,9 @@ BEGIN SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Inserting to #deadlock_data for event file data', 0, 1) WITH NOWAIT; - INSERT + IF @Debug = 1 BEGIN SET STATISTICS XML ON; END; + + INSERT #deadlock_data WITH(TABLOCKX) ( deadlock_xml @@ -26733,12 +26744,19 @@ BEGIN LEFT JOIN #t AS t ON 1 = 1 CROSS APPLY x.x.nodes('/event') AS e(x) - WHERE e.x.exist('/event/@name[ .= "xml_deadlock_report"]') = 1 - AND e.x.exist('/event/@timestamp[. >= sql:variable("@StartDate")]') = 1 - AND e.x.exist('/event/@timestamp[. < sql:variable("@EndDate")]') = 1 + WHERE + ( + e.x.exist('@name[ .= "xml_deadlock_report"]') = 1 + OR e.x.exist('@name[ .= "database_xml_deadlock_report"]') = 1 + OR e.x.exist('@name[ .= "xml_deadlock_report_filtered"]') = 1 + ) + AND e.x.exist('@timestamp[. >= sql:variable("@StartDate")]') = 1 + AND e.x.exist('@timestamp[. < sql:variable("@EndDate")]') = 1 OPTION(RECOMPILE); - SET @d = CONVERT(varchar(40), GETDATE(), 109); + IF @Debug = 1 BEGIN SET STATISTICS XML OFF; END; + + SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; END; @@ -26765,7 +26783,7 @@ BEGIN FROM sys.fn_xe_file_target_read_file(N'system_health*.xel', NULL, NULL, NULL) AS fx LEFT JOIN #t AS t ON 1 = 1 - WHERE fx.object_name = N'xml_deadlock_report' + WHERE fx.object_name = N'xml_deadlock_report' ) AS xml CROSS APPLY xml.deadlock_xml.nodes('/event') AS e(x) WHERE 1 = 1 @@ -26863,7 +26881,7 @@ BEGIN CASE WHEN q.clientoption1 & 8192 = 8192 THEN ', NUMERIC_ROUNDABORT' ELSE '' END + CASE WHEN q.clientoption1 & 16384 = 16384 THEN ', XACT_ABORT' ELSE '' END, 3, - 8000 + 500 ), client_option_2 = SUBSTRING @@ -26885,7 +26903,7 @@ BEGIN CASE WHEN q.clientoption2 & 1073741824 = 1073741824 THEN ', AUTO UPDATE STATISTICS' ELSE '' END + CASE WHEN q.clientoption2 & 1469283328 = 1469283328 THEN ', ALL SETTABLE OPTIONS' ELSE '' END, 3, - 8000 + 500 ), q.process_xml INTO #deadlock_process @@ -27652,6 +27670,16 @@ BEGIN N'S', N'IS' ) + OR dow.owner_mode IN + ( + N'S', + N'IS' + ) + OR dow.waiter_mode IN + ( + N'S', + N'IS' + ) AND (dow.database_id = @DatabaseId OR @DatabaseName IS NULL) AND (dow.event_date >= @StartDate OR @StartDate IS NULL) AND (dow.event_date < @EndDate OR @EndDate IS NULL) @@ -28389,7 +28417,7 @@ BEGIN ), 14 ) - END + END FROM #deadlock_owner_waiter AS dow JOIN #deadlock_process AS dp ON (dp.id = dow.owner_id @@ -29377,38 +29405,40 @@ BEGIN DROP SYNONYM DeadlockFindings; /*done with inserting.*/ END; ELSE /*Output to database is not set output to client app*/ - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Results to client %s', 0, 1, @d) WITH NOWAIT; - - IF @Debug = 1 BEGIN SET STATISTICS XML ON; END; - - EXEC sys.sp_executesql - @deadlock_result; - - IF @Debug = 1 - BEGIN - SET STATISTICS XML OFF; - PRINT @deadlock_result; - END; - - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Returning findings %s', 0, 1, @d) WITH NOWAIT; - - SELECT - df.check_id, - df.database_name, - df.object_name, - df.finding_group, - df.finding - FROM #deadlock_findings AS df - ORDER BY df.check_id - OPTION(RECOMPILE); - - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - END; /*done with output to client app.*/ + BEGIN + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Results to client %s', 0, 1, @d) WITH NOWAIT; + + IF @Debug = 1 BEGIN SET STATISTICS XML ON; END; + + EXEC sys.sp_executesql + @deadlock_result; + + IF @Debug = 1 + BEGIN + SET STATISTICS XML OFF; + PRINT @deadlock_result; + END; + + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Returning findings %s', 0, 1, @d) WITH NOWAIT; + + SELECT + df.check_id, + df.database_name, + df.object_name, + df.finding_group, + df.finding + FROM #deadlock_findings AS df + ORDER BY df.check_id + OPTION(RECOMPILE); + + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + END; /*done with output to client app.*/ + END; IF @Debug = 1 BEGIN @@ -29541,7 +29571,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.14', @VersionDate = '20230420'; +SELECT @Version = '8.15', @VersionDate = '20230613'; IF(@VersionCheckMode = 1) BEGIN RETURN; @@ -35272,7 +35302,7 @@ BEGIN SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.14', @VersionDate = '20230420'; + SELECT @Version = '8.15', @VersionDate = '20230613'; IF(@VersionCheckMode = 1) BEGIN @@ -36668,9 +36698,10 @@ DELETE FROM dbo.SqlServerVersions; INSERT INTO dbo.SqlServerVersions (MajorVersionNumber, MinorVersionNumber, Branch, [Url], ReleaseDate, MainstreamSupportEndDate, ExtendedSupportEndDate, MajorVersionName, MinorVersionName) VALUES - (16, 4025, 'CU3', 'https://learn.microsoft.com/en-us/troubleshoot/sql/releases/sqlserver-2022/cumulativeupdate3', '2023-04-13', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 3'), - (16, 4015, 'CU2', 'https://learn.microsoft.com/en-us/troubleshoot/sql/releases/sqlserver-2022/cumulativeupdate2', '2023-03-15', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 2'), - (16, 4003, 'CU1', 'https://learn.microsoft.com/en-us/troubleshoot/sql/releases/sqlserver-2022/cumulativeupdate1', '2023-02-16', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 1'), + (16, 4035, 'CU4', 'https://support.microsoft.com/en-us/help/5026717', '2023-05-11', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 4'), + (16, 4025, 'CU3', 'https://support.microsoft.com/en-us/help/5024396', '2023-04-13', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 3'), + (16, 4015, 'CU2', 'https://support.microsoft.com/en-us/help/5023127', '2023-03-15', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 2'), + (16, 4003, 'CU1', 'https://support.microsoft.com/en-us/help/5022375', '2023-02-16', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 1'), (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'), (15, 4312, 'CU20', 'https://support.microsoft.com/kb/5024276', '2023-04-13', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 20'), @@ -37095,7 +37126,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.14', @VersionDate = '20230420'; +SELECT @Version = '8.15', @VersionDate = '20230613'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_AllNightLog.sql b/sp_AllNightLog.sql index d788ab463..5c4e72190 100644 --- a/sp_AllNightLog.sql +++ b/sp_AllNightLog.sql @@ -31,7 +31,7 @@ SET STATISTICS XML OFF; BEGIN; -SELECT @Version = '8.14', @VersionDate = '20230420'; +SELECT @Version = '8.15', @VersionDate = '20230613'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_AllNightLog_Setup.sql b/sp_AllNightLog_Setup.sql index 61d0bf2fa..a1ddcec31 100644 --- a/sp_AllNightLog_Setup.sql +++ b/sp_AllNightLog_Setup.sql @@ -38,7 +38,7 @@ SET STATISTICS XML OFF; BEGIN; -SELECT @Version = '8.14', @VersionDate = '20230420'; +SELECT @Version = '8.15', @VersionDate = '20230613'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_Blitz.sql b/sp_Blitz.sql index 69d533408..436578173 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -38,7 +38,7 @@ AS SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.14', @VersionDate = '20230420'; + SELECT @Version = '8.15', @VersionDate = '20230613'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) diff --git a/sp_BlitzAnalysis.sql b/sp_BlitzAnalysis.sql index 600a2e9c7..5c686a696 100644 --- a/sp_BlitzAnalysis.sql +++ b/sp_BlitzAnalysis.sql @@ -37,7 +37,7 @@ AS SET NOCOUNT ON; SET STATISTICS XML OFF; -SELECT @Version = '8.14', @VersionDate = '20230420'; +SELECT @Version = '8.15', @VersionDate = '20230613'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_BlitzBackups.sql b/sp_BlitzBackups.sql index a554b0b52..c5d4932a3 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.14', @VersionDate = '20230420'; + SELECT @Version = '8.15', @VersionDate = '20230613'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_BlitzCache.sql b/sp_BlitzCache.sql index 72a0b531f..49ad85cdf 100644 --- a/sp_BlitzCache.sql +++ b/sp_BlitzCache.sql @@ -281,7 +281,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.14', @VersionDate = '20230420'; +SELECT @Version = '8.15', @VersionDate = '20230613'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) diff --git a/sp_BlitzFirst.sql b/sp_BlitzFirst.sql index 4d397eff4..5cfc95b03 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.14', @VersionDate = '20230420'; +SELECT @Version = '8.15', @VersionDate = '20230613'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_BlitzInMemoryOLTP.sql b/sp_BlitzInMemoryOLTP.sql index a768f5c0c..8dde49d89 100644 --- a/sp_BlitzInMemoryOLTP.sql +++ b/sp_BlitzInMemoryOLTP.sql @@ -82,7 +82,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI */ AS DECLARE @ScriptVersion VARCHAR(30); -SELECT @ScriptVersion = '1.8', @VersionDate = '20230420'; +SELECT @ScriptVersion = '1.8', @VersionDate = '20230613'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index f8ecb5ac8..6d7cd7b69 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -48,7 +48,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.14', @VersionDate = '20230420'; +SELECT @Version = '8.15', @VersionDate = '20230613'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index ee39544e9..9cfbc1e4b 100644 --- a/sp_BlitzLock.sql +++ b/sp_BlitzLock.sql @@ -35,7 +35,7 @@ BEGIN SET NOCOUNT, XACT_ABORT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.14', @VersionDate = '20230420'; + SELECT @Version = '8.15', @VersionDate = '20230613'; IF @VersionCheckMode = 1 BEGIN diff --git a/sp_BlitzQueryStore.sql b/sp_BlitzQueryStore.sql index 371e5ead6..431711405 100644 --- a/sp_BlitzQueryStore.sql +++ b/sp_BlitzQueryStore.sql @@ -57,7 +57,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.14', @VersionDate = '20230420'; +SELECT @Version = '8.15', @VersionDate = '20230613'; IF(@VersionCheckMode = 1) BEGIN RETURN; diff --git a/sp_BlitzWho.sql b/sp_BlitzWho.sql index 9931cc085..5a65a607c 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.14', @VersionDate = '20230420'; + SELECT @Version = '8.15', @VersionDate = '20230613'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_DatabaseRestore.sql b/sp_DatabaseRestore.sql index 597b3fd3e..0442cae9a 100755 --- a/sp_DatabaseRestore.sql +++ b/sp_DatabaseRestore.sql @@ -44,7 +44,7 @@ SET STATISTICS XML OFF; /*Versioning details*/ -SELECT @Version = '8.14', @VersionDate = '20230420'; +SELECT @Version = '8.15', @VersionDate = '20230613'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_ineachdb.sql b/sp_ineachdb.sql index e9fcff593..8ea9646e8 100644 --- a/sp_ineachdb.sql +++ b/sp_ineachdb.sql @@ -35,7 +35,7 @@ BEGIN SET NOCOUNT ON; SET STATISTICS XML OFF; - SELECT @Version = '8.14', @VersionDate = '20230420'; + SELECT @Version = '8.15', @VersionDate = '20230613'; IF(@VersionCheckMode = 1) BEGIN From 182995954716eb3e149548cb90402da69e7201c1 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Thu, 15 Jun 2023 14:40:52 -0700 Subject: [PATCH 407/662] 2019 CU21 and 2022 CU5 --- SqlServerVersions.sql | 2 ++ 1 file changed, 2 insertions(+) diff --git a/SqlServerVersions.sql b/SqlServerVersions.sql index db33a7929..60c0b31e2 100644 --- a/SqlServerVersions.sql +++ b/SqlServerVersions.sql @@ -41,12 +41,14 @@ DELETE FROM dbo.SqlServerVersions; INSERT INTO dbo.SqlServerVersions (MajorVersionNumber, MinorVersionNumber, Branch, [Url], ReleaseDate, MainstreamSupportEndDate, ExtendedSupportEndDate, MajorVersionName, MinorVersionName) VALUES + (16, 4045, 'CU5', 'https://support.microsoft.com/en-us/help/5026806', '2023-06-15', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 5'), (16, 4035, 'CU4', 'https://support.microsoft.com/en-us/help/5026717', '2023-05-11', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 4'), (16, 4025, 'CU3', 'https://support.microsoft.com/en-us/help/5024396', '2023-04-13', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 3'), (16, 4015, 'CU2', 'https://support.microsoft.com/en-us/help/5023127', '2023-03-15', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 2'), (16, 4003, 'CU1', 'https://support.microsoft.com/en-us/help/5022375', '2023-02-16', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 1'), (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'), + (15, 4316, 'CU21', 'https://support.microsoft.com/kb/5025808', '2023-06-15', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 21'), (15, 4312, 'CU20', 'https://support.microsoft.com/kb/5024276', '2023-04-13', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 20'), (15, 4298, 'CU19', 'https://support.microsoft.com/kb/5023049', '2023-02-16', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 19'), (15, 4280, 'CU18 GDR', 'https://support.microsoft.com/kb/5021124', '2023-02-14', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 18 GDR'), From 0e71711d735cae4b788ba67aaceffbf1ffbb06e3 Mon Sep 17 00:00:00 2001 From: sqlslinger Date: Mon, 19 Jun 2023 13:54:52 -0400 Subject: [PATCH 408/662] Add @KeepCdc parameter Add the KEEP_CDC option to the RESTORE DATABASE ... WITH RECOVERY command. --- sp_DatabaseRestore.sql | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/sp_DatabaseRestore.sql b/sp_DatabaseRestore.sql index 0442cae9a..068d61968 100755 --- a/sp_DatabaseRestore.sql +++ b/sp_DatabaseRestore.sql @@ -31,6 +31,7 @@ ALTER PROCEDURE [dbo].[sp_DatabaseRestore] @DatabaseOwner sysname = NULL, @SetTrustworthyON BIT = 0, @FixOrphanUsers BIT = 0, + @KeepCdc BIT = 0, @Execute CHAR(1) = Y, @FileExtensionDiff NVARCHAR(128) = NULL, @Debug INT = 0, @@ -1490,13 +1491,18 @@ END -- Put database in a useable state IF @RunRecovery = 1 BEGIN - SET @sql = N'RESTORE DATABASE ' + @RestoreDatabaseName + N' WITH RECOVERY' + NCHAR(13); + SET @sql = N'RESTORE DATABASE ' + @RestoreDatabaseName + N' WITH RECOVERY'; + + IF @KeepCdc = 1 + SET @sql = @sql + N', KEEP_CDC'; - IF @Debug = 1 OR @Execute = 'N' - BEGIN - IF @sql IS NULL PRINT '@sql is NULL for RESTORE DATABASE: @RestoreDatabaseName'; - PRINT @sql; - END; + SET @sql = @sql + NCHAR(13); + + IF @Debug = 1 OR @Execute = 'N' + BEGIN + IF @sql IS NULL PRINT '@sql is NULL for RESTORE DATABASE: @RestoreDatabaseName'; + PRINT @sql; + END; IF @Debug IN (0, 1) AND @Execute = 'Y' EXECUTE @sql = [dbo].[CommandExecute] @DatabaseContext=N'master', @Command = @sql, @CommandType = 'RECOVER DATABASE', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y'; From 8335aa48a45e52fa5db3b94291f25cc6e721d394 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Tue, 27 Jun 2023 12:08:32 -0700 Subject: [PATCH 409/662] #3294 sp_BlitzIndex columnstore Add columns from sys.dm_db_column_store_row_group_physical_stats. Working on #3294. --- sp_BlitzIndex.sql | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index 6d7cd7b69..1934f7944 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -2958,7 +2958,8 @@ BEGIN + CASE WHEN @ShowPartitionRanges = 1 THEN N' COALESCE(range_start_op + '' '' + range_start + '' '', '''') + COALESCE(range_end_op + '' '' + range_end, '''') AS partition_range, ' ELSE N' ' END + N' row_group_id, total_rows, deleted_rows, ' + @ColumnList - + CASE WHEN @ShowPartitionRanges = 1 THEN N' + + CASE WHEN @ShowPartitionRanges = 1 THEN N' , + state_desc, trim_reason_desc, transition_to_compressed_state_desc, has_vertipaq_optimization FROM ( SELECT column_name, partition_number, row_group_id, total_rows, deleted_rows, details, range_start_op, @@ -2968,9 +2969,11 @@ BEGIN range_end_op, CASE WHEN format_type IS NULL THEN CAST(range_end_value AS NVARCHAR(4000)) - ELSE CONVERT(NVARCHAR(4000), range_end_value, format_type) END range_end' ELSE N' ' END + N' + ELSE CONVERT(NVARCHAR(4000), range_end_value, format_type) END range_end' ELSE N' ' END + N', + state_desc, trim_reason_desc, transition_to_compressed_state_desc, has_vertipaq_optimization FROM ( SELECT c.name AS column_name, p.partition_number, rg.row_group_id, rg.total_rows, rg.deleted_rows, + phys.state_desc, phys.trim_reason_desc, phys.transition_to_compressed_state_desc, phys.has_vertipaq_optimization, details = CAST(seg.min_data_id AS VARCHAR(20)) + '' to '' + CAST(seg.max_data_id AS VARCHAR(20)) + '', '' + CAST(CAST((seg.on_disk_size / 1024.0 / 1024) AS DECIMAL(18,0)) AS VARCHAR(20)) + '' MB''' + CASE WHEN @ShowPartitionRanges = 1 THEN N', CASE @@ -2985,7 +2988,8 @@ BEGIN FROM ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_row_groups rg INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns c ON rg.object_id = c.object_id INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partitions p ON rg.object_id = p.object_id AND rg.partition_number = p.partition_number - INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.index_columns ic on ic.column_id = c.column_id AND ic.object_id = c.object_id AND ic.index_id = p.index_id ' + CASE WHEN @ShowPartitionRanges = 1 THEN N' + INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.index_columns ic on ic.column_id = c.column_id AND ic.object_id = c.object_id AND ic.index_id = p.index_id + LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_column_store_row_group_physical_stats phys ON rg.row_group_id = phys.row_group_id AND rg.object_id = phys.object_id AND rg.partition_number = phys.partition_number AND p.index_id = phys.index_id ' + CASE WHEN @ShowPartitionRanges = 1 THEN N' LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.indexes i ON i.object_id = rg.object_id AND i.index_id = rg.index_id LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partition_schemes ps ON ps.data_space_id = i.data_space_id LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partition_functions pf ON pf.function_id = ps.function_id From ff8a0f4869a3f040ea3741d134b877288a9b5ad7 Mon Sep 17 00:00:00 2001 From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com> Date: Wed, 28 Jun 2023 18:28:49 -0400 Subject: [PATCH 410/662] Get plans from cache Closes #3293 Adds a result set that goes out to the plan cache to look for query plans for queries involved in deadlocks. --- sp_BlitzLock.sql | 248 ++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 202 insertions(+), 46 deletions(-) diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index 9cfbc1e4b..51984cfa8 100644 --- a/sp_BlitzLock.sql +++ b/sp_BlitzLock.sql @@ -873,9 +873,9 @@ BEGIN SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Inserting to #deadlock_data for event file data', 0, 1) WITH NOWAIT; - IF @Debug = 1 BEGIN SET STATISTICS XML ON; END; + IF @Debug = 1 BEGIN SET STATISTICS XML ON; END; - INSERT + INSERT #deadlock_data WITH(TABLOCKX) ( deadlock_xml @@ -897,9 +897,9 @@ BEGIN AND e.x.exist('@timestamp[. < sql:variable("@EndDate")]') = 1 OPTION(RECOMPILE); - IF @Debug = 1 BEGIN SET STATISTICS XML OFF; END; + IF @Debug = 1 BEGIN SET STATISTICS XML OFF; END; - SET @d = CONVERT(varchar(40), GETDATE(), 109); + SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; END; @@ -1116,6 +1116,7 @@ BEGIN FROM #deadlock_process AS dp CROSS APPLY dp.process_xml.nodes('//executionStack/frame') AS ca(dp) WHERE (ca.dp.exist('@procname[. = sql:variable("@StoredProcName")]') = 1 OR @StoredProcName IS NULL) + AND ca.dp.exist('@sqlhandle[ .= "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"]') = 0 OPTION(RECOMPILE); SET @d = CONVERT(varchar(40), GETDATE(), 109); @@ -1584,13 +1585,19 @@ BEGIN 32 ), step_id = - SUBSTRING - ( - dp.client_app, - CHARINDEX(N': Step ', dp.client_app) + LEN(N': Step '), - CHARINDEX(N')', dp.client_app, CHARINDEX(N': Step ', dp.client_app)) - - (CHARINDEX(N': Step ', dp.client_app) + LEN(N': Step ')) - ) + CASE + WHEN CHARINDEX(N': Step ', dp.client_app) > 0 + AND CHARINDEX(N')', dp.client_app, CHARINDEX(N': Step ', dp.client_app)) > 0 + THEN + SUBSTRING + ( + dp.client_app, + CHARINDEX(N': Step ', dp.client_app) + LEN(N': Step '), + CHARINDEX(N')', dp.client_app, CHARINDEX(N': Step ', dp.client_app)) - + (CHARINDEX(N': Step ', dp.client_app) + LEN(N': Step ')) + ) + ELSE dp.client_app + END FROM #deadlock_process AS dp WHERE dp.client_app LIKE N'SQLAgent - %' AND dp.client_app <> N'SQLAgent - Initial Boot Probe' @@ -2211,9 +2218,9 @@ BEGIN deadlock_stack AS ( SELECT DISTINCT - ds.id, - ds.proc_name, - ds.event_date, + ds.id, + ds.event_date, + ds.proc_name, database_name = PARSENAME(ds.proc_name, 3), schema_name = @@ -2247,8 +2254,8 @@ BEGIN PARSENAME(ds.proc_name, 3), PARSENAME(ds.proc_name, 2), PARSENAME(ds.proc_name, 1), - ds.id, ds.proc_name, + ds.id, ds.event_date ) INSERT @@ -2280,6 +2287,7 @@ BEGIN AND (dow.event_date >= @StartDate OR @StartDate IS NULL) AND (dow.event_date < @EndDate OR @EndDate IS NULL) AND (dow.object_name = @StoredProcName OR @StoredProcName IS NULL) + AND ds.proc_name NOT LIKE 'Unknown%' OPTION(RECOMPILE); RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; @@ -2470,19 +2478,19 @@ BEGIN ) ), wait_time_hms = - /*the more wait time you rack up the less accurate this gets, + /*the more wait time you rack up the less accurate this gets, it's either that or erroring out*/ - CASE - WHEN + CASE + WHEN SUM ( CONVERT ( - bigint, + bigint, dp.wait_time ) )/1000 > 2147483647 - THEN + THEN CONVERT ( nvarchar(30), @@ -2495,7 +2503,7 @@ BEGIN ( CONVERT ( - bigint, + bigint, dp.wait_time ) ) @@ -2506,16 +2514,16 @@ BEGIN ), 14 ) - WHEN + WHEN SUM ( CONVERT ( - bigint, + bigint, dp.wait_time ) ) BETWEEN 2147483648 AND 2147483647000 - THEN + THEN CONVERT ( nvarchar(30), @@ -2528,7 +2536,7 @@ BEGIN ( CONVERT ( - bigint, + bigint, dp.wait_time ) ) @@ -2677,19 +2685,19 @@ BEGIN ) ) + N' ' + - /*the more wait time you rack up the less accurate this gets, + /*the more wait time you rack up the less accurate this gets, it's either that or erroring out*/ - CASE - WHEN + CASE + WHEN SUM ( CONVERT ( - bigint, + bigint, wt.total_wait_time_ms ) )/1000 > 2147483647 - THEN + THEN CONVERT ( nvarchar(30), @@ -2702,7 +2710,7 @@ BEGIN ( CONVERT ( - bigint, + bigint, wt.total_wait_time_ms ) ) @@ -2713,16 +2721,16 @@ BEGIN ), 14 ) - WHEN + WHEN SUM ( CONVERT ( - bigint, + bigint, wt.total_wait_time_ms ) ) BETWEEN 2147483648 AND 2147483647000 - THEN + THEN CONVERT ( nvarchar(30), @@ -2735,7 +2743,7 @@ BEGIN ( CONVERT ( - bigint, + bigint, wt.total_wait_time_ms ) ) @@ -3345,7 +3353,8 @@ BEGIN d.waiter_waiting_to_close, /*end parallel deadlock columns*/ d.deadlock_graph, - d.is_victim + d.is_victim, + d.id INTO #deadlock_results FROM #deadlocks AS d; @@ -3548,26 +3557,173 @@ BEGIN DROP SYNONYM DeadlockFindings; /*done with inserting.*/ END; ELSE /*Output to database is not set output to client app*/ - BEGIN - SET @d = CONVERT(varchar(40), GETDATE(), 109); + BEGIN + SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Results to client %s', 0, 1, @d) WITH NOWAIT; - + IF @Debug = 1 BEGIN SET STATISTICS XML ON; END; - + EXEC sys.sp_executesql @deadlock_result; - + IF @Debug = 1 BEGIN SET STATISTICS XML OFF; PRINT @deadlock_result; END; - + + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Getting available execution plans for deadlocks %s', 0, 1, @d) WITH NOWAIT; + + SELECT DISTINCT + ds.proc_name, + sql_handle = + CONVERT(varbinary(64), ds.sql_handle, 1), + dow.database_name, + dow.object_name, + query_xml = + CONVERT(nvarchar(MAX), dr.query_xml) + INTO #available_plans + FROM #deadlock_stack AS ds + JOIN #deadlock_owner_waiter AS dow + ON dow.owner_id = ds.id + AND dow.event_date = ds.event_date + JOIN #deadlock_results AS dr + ON dr.id = ds.id + AND dr.event_date = ds.event_date + OPTION(RECOMPILE); + + SELECT + ap.database_name, + query_text = + TRY_CAST(ap.query_xml AS xml), + ap.query_plan, + ap.creation_time, + ap.last_execution_time, + ap.execution_count, + ap.executions_per_second, + ap.total_worker_time_ms, + ap.avg_worker_time_ms, + ap.total_elapsed_time_ms, + ap.avg_elapsed_time, + ap.total_logical_reads_mb, + ap.total_physical_reads_mb, + ap.min_num_physical_reads_mb, + ap.max_num_physical_reads_mb, + ap.total_logical_writes_mb, + ap.min_grant_mb, + ap.max_grant_mb, + ap.min_used_grant_mb, + ap.max_used_grant_mb, + ap.min_spills_mb, + ap.max_spills_mb, + ap.min_reserved_threads, + ap.max_reserved_threads, + ap.min_used_threads, + ap.max_used_threads, + ap.total_rows, + ap.sql_handle, + ap.statement_start_offset, + ap.statement_end_offset + FROM + ( + SELECT + *, + n = + ROW_NUMBER() OVER + ( + PARTITION BY + ap.sql_handle + ORDER BY + ap.sql_handle + ) + FROM #available_plans AS ap + OUTER APPLY + ( + SELECT TOP (1) + deqs.statement_start_offset, + deqs.statement_end_offset, + deqs.creation_time, + deqs.last_execution_time, + deqs.execution_count, + total_worker_time_ms = + deqs.total_worker_time / 1000., + avg_worker_time_ms = + CONVERT(decimal(38, 6), deqs.total_worker_time / 1000. / deqs.execution_count), + total_elapsed_time_ms = + deqs.total_elapsed_time / 1000., + avg_elapsed_time = + CONVERT(decimal(38, 6), deqs.total_elapsed_time / 1000. / deqs.execution_count), + executions_per_second = + ISNULL + ( + execution_count / + NULLIF + ( + DATEDIFF + ( + SECOND, + deqs.creation_time, + deqs.last_execution_time + ), + 0 + ), + 0 + ), + total_physical_reads_mb = + deqs.total_physical_reads * 8. / 1024., + total_logical_writes_mb = + deqs.total_logical_writes * 8. / 1024., + total_logical_reads_mb = + deqs.total_logical_reads * 8. / 1024., + min_num_physical_reads_mb = + deqs.min_num_physical_reads * 8. / 1024., + max_num_physical_reads_mb = + deqs.max_num_physical_reads * 8. / 1024., + min_grant_mb = + deqs.min_grant_kb * 8. / 1024., + max_grant_mb = + deqs.max_grant_kb * 8. / 1024., + min_used_grant_mb = + deqs.min_used_grant_kb * 8. / 1024., + max_used_grant_mb = + deqs.max_used_grant_kb * 8. / 1024., + min_spills_mb = + deqs.min_spills * 8. / 1024., + max_spills_mb = + deqs.max_spills * 8. / 1024., + deqs.min_reserved_threads, + deqs.max_reserved_threads, + deqs.min_used_threads, + deqs.max_used_threads, + deqs.total_rows, + query_plan = + TRY_CAST(deps.query_plan AS xml) + FROM sys.dm_exec_query_stats AS deqs + OUTER APPLY sys.dm_exec_text_query_plan + ( + deqs.plan_handle, + deqs.statement_start_offset, + deqs.statement_end_offset + ) AS deps + WHERE deqs.sql_handle = ap.sql_handle + ORDER BY + deqs.last_execution_time DESC + ) AS c + ) AS ap + WHERE ap.query_plan IS NOT NULL + AND ap.n = 1 + ORDER BY + ap.last_execution_time DESC + OPTION(RECOMPILE); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - + SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Returning findings %s', 0, 1, @d) WITH NOWAIT; - + SELECT df.check_id, df.database_name, @@ -3577,11 +3733,11 @@ BEGIN FROM #deadlock_findings AS df ORDER BY df.check_id OPTION(RECOMPILE); - + SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; END; /*done with output to client app.*/ - END; + END; IF @Debug = 1 BEGIN From 91f4d92303e0f074fa61d657d28dfbc40697f474 Mon Sep 17 00:00:00 2001 From: "marlon.ambion" Date: Thu, 29 Jun 2023 14:31:55 +1200 Subject: [PATCH 411/662] #3291 FIX: sp_BlitzCache fails when executed on instances with readable secondary replicas Change: Add internal resource database "32767" in the ReadableDB list for exclusion --- sp_BlitzCache.sql | 1 + 1 file changed, 1 insertion(+) diff --git a/sp_BlitzCache.sql b/sp_BlitzCache.sql index 49ad85cdf..11600c64f 100644 --- a/sp_BlitzCache.sql +++ b/sp_BlitzCache.sql @@ -1579,6 +1579,7 @@ BEGIN RAISERROR('Checking for Read intent databases to exclude',0,0) WITH NOWAIT; EXEC('INSERT INTO #ReadableDBs (database_id) SELECT DBs.database_id FROM sys.databases DBs INNER JOIN sys.availability_replicas Replicas ON DBs.replica_id = Replicas.replica_id WHERE replica_server_name NOT IN (SELECT DISTINCT primary_replica FROM sys.dm_hadr_availability_group_states States) AND Replicas.secondary_role_allow_connections_desc = ''READ_ONLY'' AND replica_server_name = @@SERVERNAME OPTION (RECOMPILE);'); + EXEC('INSERT INTO #ReadableDBs VALUES (32767) ;'); -- Exclude internal resource database as well END RAISERROR(N'Checking plan cache age', 0, 1) WITH NOWAIT; From ac6bb26de09f3116a0d53aa260d32702c486d954 Mon Sep 17 00:00:00 2001 From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com> Date: Thu, 29 Jun 2023 21:29:52 -0400 Subject: [PATCH 412/662] Add better sorting Closes #3298 --- sp_BlitzLock.sql | 118 +++++++++++++++++++++++++++++++++++------------ 1 file changed, 88 insertions(+), 30 deletions(-) diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index 51984cfa8..c2c1480ee 100644 --- a/sp_BlitzLock.sql +++ b/sp_BlitzLock.sql @@ -219,7 +219,8 @@ BEGIN database_name nvarchar(256), object_name nvarchar(1000), finding_group nvarchar(100), - finding nvarchar(4000) + finding nvarchar(4000), + sort_order bigint ); /*Set these to some sane defaults if NULLs are passed in*/ @@ -1758,7 +1759,8 @@ BEGIN database_name, object_name, finding_group, - finding + finding, + sort_order ) SELECT check_id = 1, @@ -1772,7 +1774,10 @@ BEGIN nvarchar(20), COUNT_BIG(DISTINCT dp.event_date) ) + - N' deadlocks.' + N' deadlocks.', + sort_order = + ROW_NUMBER() + OVER (ORDER BY COUNT_BIG(DISTINCT dp.event_date) DESC) FROM #deadlock_process AS dp WHERE 1 = 1 AND (dp.database_name = @DatabaseName OR @DatabaseName IS NULL) @@ -1797,7 +1802,8 @@ BEGIN database_name, object_name, finding_group, - finding + finding, + sort_order ) SELECT check_id = 2, @@ -1812,7 +1818,10 @@ BEGIN nvarchar(20), COUNT_BIG(DISTINCT dow.event_date) ) + - N' deadlock(s) between read queries and modification queries.' + N' deadlock(s) between read queries and modification queries.', + sort_order = + ROW_NUMBER() + OVER (ORDER BY COUNT_BIG(DISTINCT dow.event_date) DESC) FROM #deadlock_owner_waiter AS dow WHERE 1 = 1 AND dow.lock_mode IN @@ -1851,7 +1860,8 @@ BEGIN database_name, object_name, finding_group, - finding + finding, + sort_order ) SELECT check_id = 3, @@ -1870,7 +1880,10 @@ BEGIN nvarchar(20), COUNT_BIG(DISTINCT dow.event_date) ) + - N' deadlock(s).' + N' deadlock(s).', + sort_order = + ROW_NUMBER() + OVER (ORDER BY COUNT_BIG(DISTINCT dow.event_date) DESC) FROM #deadlock_owner_waiter AS dow WHERE 1 = 1 AND (dow.database_id = @DatabaseId OR @DatabaseName IS NULL) @@ -1895,7 +1908,8 @@ BEGIN database_name, object_name, finding_group, - finding + finding, + sort_order ) SELECT check_id = 3, @@ -1909,7 +1923,10 @@ BEGIN nvarchar(20), COUNT_BIG(DISTINCT dow.event_date) ) + - N' deadlock(s).' + N' deadlock(s).', + sort_order = + ROW_NUMBER() + OVER (ORDER BY COUNT_BIG(DISTINCT dow.event_date) DESC) FROM #deadlock_owner_waiter AS dow WHERE 1 = 1 AND (dow.database_id = @DatabaseId OR @DatabaseName IS NULL) @@ -1940,7 +1957,8 @@ BEGIN database_name, object_name, finding_group, - finding + finding, + sort_order ) SELECT check_id = 3, @@ -1954,7 +1972,10 @@ BEGIN nvarchar(20), COUNT_BIG(DISTINCT dow.event_date) ) + - N' deadlock(s).' + N' deadlock(s).', + sort_order = + ROW_NUMBER() + OVER (ORDER BY COUNT_BIG(DISTINCT dow.event_date) DESC) FROM #deadlock_owner_waiter AS dow WHERE 1 = 1 AND (dow.database_id = @DatabaseId OR @DatabaseName IS NULL) @@ -1984,7 +2005,8 @@ BEGIN database_name, object_name, finding_group, - finding + finding, + sort_order ) SELECT check_id = 4, @@ -1999,7 +2021,10 @@ BEGIN nvarchar(20), COUNT_BIG(DISTINCT dp.event_date) ) + - N' instances of Serializable deadlocks.' + N' instances of Serializable deadlocks.', + sort_order = + ROW_NUMBER() + OVER (ORDER BY COUNT_BIG(DISTINCT dow.event_date) DESC) FROM #deadlock_process AS dp WHERE dp.isolation_level LIKE N'serializable%' AND (dp.database_name = @DatabaseName OR @DatabaseName IS NULL) @@ -2024,7 +2049,8 @@ BEGIN database_name, object_name, finding_group, - finding + finding, + sort_order ) SELECT check_id = 5, @@ -2038,7 +2064,10 @@ BEGIN nvarchar(20), COUNT_BIG(DISTINCT dp.event_date) ) + - N' instances of Repeatable Read deadlocks.' + N' instances of Repeatable Read deadlocks.', + sort_order = + ROW_NUMBER() + OVER (ORDER BY COUNT_BIG(DISTINCT dow.event_date) DESC) FROM #deadlock_process AS dp WHERE dp.isolation_level LIKE N'repeatable%' AND (dp.database_name = @DatabaseName OR @DatabaseName IS NULL) @@ -2063,7 +2092,8 @@ BEGIN database_name, object_name, finding_group, - finding + finding, + sort_order ) SELECT check_id = 6, @@ -2096,7 +2126,10 @@ BEGIN dp.host_name, N'UNKNOWN' ) + - N'.' + N'.', + sort_order = + ROW_NUMBER() + OVER (ORDER BY COUNT_BIG(DISTINCT dp.event_date) DESC) FROM #deadlock_process AS dp WHERE 1 = 1 AND (dp.database_name = @DatabaseName OR @DatabaseName IS NULL) @@ -2177,7 +2210,8 @@ BEGIN database_name, object_name, finding_group, - finding + finding, + sort_order ) SELECT check_id = 7, @@ -2204,7 +2238,10 @@ BEGIN 1, 1, N'' - ) + N' locks.' + ) + N' locks.', + sort_order = + ROW_NUMBER() + OVER (ORDER BY CONVERT(bigint, lt2.lock_count) DESC) FROM lock_types AS lt OPTION(RECOMPILE); @@ -2360,7 +2397,8 @@ BEGIN database_name, object_name, finding_group, - finding + finding, + sort_order ) SELECT check_id = 9, @@ -2379,7 +2417,10 @@ BEGIN nvarchar(10), COUNT_BIG(DISTINCT ds.id) ) + - N' deadlocks.' + N' deadlocks.', + sort_order = + ROW_NUMBER() + OVER (ORDER BY COUNT_BIG(DISTINCT ds.id) DESC) FROM #deadlock_stack AS ds JOIN #deadlock_process AS dp ON dp.id = ds.id @@ -2568,7 +2609,9 @@ BEGIN ), 14 ) - END + END, + total_waits = + SUM(CONVERT(bigint, dp.wait_time)) FROM #deadlock_owner_waiter AS dow JOIN #deadlock_process AS dp ON (dp.id = dow.owner_id @@ -2593,7 +2636,8 @@ BEGIN database_name, object_name, finding_group, - finding + finding, + sort_order ) SELECT check_id = 11, @@ -2614,7 +2658,10 @@ BEGIN cs.wait_time_hms, 14 ) + - N' [dd hh:mm:ss:ms] of deadlock wait time.' + N' [dd hh:mm:ss:ms] of deadlock wait time.', + sort_order = + ROW_NUMBER() + OVER (ORDER BY cs.total_waits DESC) FROM chopsuey AS cs WHERE cs.object_name IS NOT NULL OPTION(RECOMPILE); @@ -2662,7 +2709,8 @@ BEGIN database_name, object_name, finding_group, - finding + finding, + sort_order ) SELECT check_id = 12, @@ -2775,7 +2823,10 @@ BEGIN ), 14 ) END + - N' [dd hh:mm:ss:ms] of deadlock wait time.' + N' [dd hh:mm:ss:ms] of deadlock wait time.', + sort_order = + ROW_NUMBER() + OVER (ORDER BY wt.total_wait_time_ms DESC) FROM wait_time AS wt GROUP BY wt.database_name @@ -2794,7 +2845,8 @@ BEGIN database_name, object_name, finding_group, - finding + finding, + sort_order ) SELECT check_id = 13, @@ -2809,7 +2861,10 @@ BEGIN finding = N'There have been ' + RTRIM(COUNT_BIG(DISTINCT aj.event_date)) + - N' deadlocks from this Agent Job and Step.' + N' deadlocks from this Agent Job and Step.', + sort_order = + ROW_NUMBER() + OVER (ORDER BY COUNT_BIG(DISTINCT aj.event_date) DESC) FROM #agent_job AS aj GROUP BY DB_NAME(aj.database_id), @@ -2925,7 +2980,8 @@ BEGIN database_name, object_name, finding_group, - finding + finding, + sort_order ) VALUES ( @@ -3731,7 +3787,9 @@ BEGIN df.finding_group, df.finding FROM #deadlock_findings AS df - ORDER BY df.check_id + ORDER BY + df.check_id, + df.sort_order OPTION(RECOMPILE); SET @d = CONVERT(varchar(40), GETDATE(), 109); From c5405fd274c79fcba5307a4dadaee999db47715c Mon Sep 17 00:00:00 2001 From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com> Date: Thu, 29 Jun 2023 21:36:30 -0400 Subject: [PATCH 413/662] Quality assurance --- sp_BlitzLock.sql | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index c2c1480ee..dafba72a8 100644 --- a/sp_BlitzLock.sql +++ b/sp_BlitzLock.sql @@ -2024,7 +2024,7 @@ BEGIN N' instances of Serializable deadlocks.', sort_order = ROW_NUMBER() - OVER (ORDER BY COUNT_BIG(DISTINCT dow.event_date) DESC) + OVER (ORDER BY COUNT_BIG(DISTINCT dp.event_date) DESC) FROM #deadlock_process AS dp WHERE dp.isolation_level LIKE N'serializable%' AND (dp.database_name = @DatabaseName OR @DatabaseName IS NULL) @@ -2067,7 +2067,7 @@ BEGIN N' instances of Repeatable Read deadlocks.', sort_order = ROW_NUMBER() - OVER (ORDER BY COUNT_BIG(DISTINCT dow.event_date) DESC) + OVER (ORDER BY COUNT_BIG(DISTINCT dp.event_date) DESC) FROM #deadlock_process AS dp WHERE dp.isolation_level LIKE N'repeatable%' AND (dp.database_name = @DatabaseName OR @DatabaseName IS NULL) @@ -2241,7 +2241,7 @@ BEGIN ) + N' locks.', sort_order = ROW_NUMBER() - OVER (ORDER BY CONVERT(bigint, lt2.lock_count) DESC) + OVER (ORDER BY CONVERT(bigint, lt.lock_count) DESC) FROM lock_types AS lt OPTION(RECOMPILE); @@ -2826,7 +2826,7 @@ BEGIN N' [dd hh:mm:ss:ms] of deadlock wait time.', sort_order = ROW_NUMBER() - OVER (ORDER BY wt.total_wait_time_ms DESC) + OVER (ORDER BY SUM(CONVERT(bigint, wt.total_wait_time_ms)) DESC) FROM wait_time AS wt GROUP BY wt.database_name @@ -2980,8 +2980,7 @@ BEGIN database_name, object_name, finding_group, - finding, - sort_order + finding ) VALUES ( From 4bb9866786a47a8b04189c029ab8dadfac68f298 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Fri, 30 Jun 2023 09:06:41 -0700 Subject: [PATCH 414/662] #3294 sp_BlitzIndex: add dictionary sizes In the columnstore visualization, secondary dictionary only. Working on #3294. --- sp_BlitzIndex.sql | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index 1934f7944..5ad1e33a1 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -2974,7 +2974,7 @@ BEGIN FROM ( SELECT c.name AS column_name, p.partition_number, rg.row_group_id, rg.total_rows, rg.deleted_rows, phys.state_desc, phys.trim_reason_desc, phys.transition_to_compressed_state_desc, phys.has_vertipaq_optimization, - details = CAST(seg.min_data_id AS VARCHAR(20)) + '' to '' + CAST(seg.max_data_id AS VARCHAR(20)) + '', '' + CAST(CAST((seg.on_disk_size / 1024.0 / 1024) AS DECIMAL(18,0)) AS VARCHAR(20)) + '' MB''' + details = CAST(seg.min_data_id AS VARCHAR(20)) + '' to '' + CAST(seg.max_data_id AS VARCHAR(20)) + '', '' + CAST(CAST(((COALESCE(d.on_disk_size,0) + COALESCE(seg.on_disk_size,0)) / 1024.0 / 1024) AS DECIMAL(18,0)) AS VARCHAR(20)) + '' MB''' + CASE WHEN @ShowPartitionRanges = 1 THEN N', CASE WHEN pp.system_type_id IN (40, 41, 42, 43, 58, 61) THEN 126 @@ -2997,6 +2997,7 @@ BEGIN LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partition_range_values prvs ON prvs.function_id = pf.function_id AND prvs.boundary_id = p.partition_number - 1 LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partition_range_values prve ON prve.function_id = pf.function_id AND prve.boundary_id = p.partition_number ' ELSE N' ' END + N' LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_segments seg ON p.partition_id = seg.partition_id AND ic.index_column_id = seg.column_id AND rg.row_group_id = seg.segment_id + LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_dictionaries d ON p.hobt_id = d.hobt_id AND c.column_id = d.column_id AND seg.secondary_dictionary_id = d.dictionary_id WHERE rg.object_id = @ObjectID AND rg.state IN (1, 2, 3) AND c.name IN ( ' + @ColumnListWithApostrophes + N')' @@ -3034,6 +3035,9 @@ BEGIN RAISERROR(N'Done visualizing columnstore index contents.', 0,1) WITH NOWAIT; END + IF @ShowColumnstoreOnly = 1 + RETURN; + END; /* IF @TableName IS NOT NULL */ From 3900d6eed9d91246cddaccef10b716d6bd261920 Mon Sep 17 00:00:00 2001 From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com> Date: Sun, 2 Jul 2023 11:26:56 -0400 Subject: [PATCH 415/662] Add some permissions checks to sp_Blitz MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #3292 This feels like a somewhat naïve set of checks. I don't know a ton about security. It may clash when someone uses `EXECUTE AS` or signs the procedure for execution for lower-privileged users. It's also incomplete at the moment, because I need to round up commands that touch system databases we may not have read permissions in. I fully expect this to get rejected, but it got me error-free runs in a tightly locked down SQL Server environment. If anyone has feedback, I'm happy to take it. --- sp_Blitz.sql | 279 ++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 230 insertions(+), 49 deletions(-) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index 436578173..7e6eaae9e 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -186,11 +186,128 @@ AS ,@CurrentComponentVersionCheckModeOK BIT ,@canExitLoop BIT ,@frkIsConsistent BIT - ,@NeedToTurnNumericRoundabortBackOn BIT; + ,@NeedToTurnNumericRoundabortBackOn BIT + ,@sa bit = 1 + ,@SUSER_NAME sysname = SUSER_SNAME() + ,@SkipDBCC bit = 0 + ,@SkipTrace bit = 0 + ,@SkipXPRegRead bit = 0 + ,@SkipXPFixedDrives bit = 0 + ,@SkipXPCMDShell bit = 0 + ,@SkipMaster bit = 0 + ,@SkipMSDB bit = 0 + ,@SkipModel bit = 0 + ,@SkipTempDB bit = 0 + ,@SkipValidateLogins bit = 0; + + DECLARE + @db_perms table + ( + database_name sysname, + permission_name sysname + ); + /* End of declarations for First Responder Kit consistency check:*/ ; + /*Starting permissions checks here, but only if we're not a sysadmin*/ + IF + ( + SELECT + sa = + ISNULL + ( + IS_SRVROLEMEMBER(N'sysadmin'), + 0 + ) + ) = 0 + BEGIN + SET @sa = 0; /*Setting this to 0 to skip DBCC COMMANDS*/ + + IF NOT EXISTS + ( + SELECT + 1/0 + FROM sys.fn_my_permissions(NULL, NULL) AS fmp + WHERE fmp.permission_name = N'VIEW SERVER STATE' + ) + BEGIN + RAISERROR('The user %s does not have VIEW SERVER STATE permissions.', 0, 11, @SUSER_NAME) WITH NOWAIT; + RETURN; + END; /*If we don't have this, we can't do anything at all.*/ + + IF NOT EXISTS + ( + SELECT + 1/0 + FROM fn_my_permissions(N'sys.traces', N'OBJECT') AS fmp + WHERE fmp.permission_name = N'ALTER' + ) + BEGIN + SET @SkipTrace = 1; + END; /*We need this permission to execute trace stuff, apparently*/ + + IF NOT EXISTS + ( + SELECT + 1/0 + FROM fn_my_permissions(N'xp_regread', N'OBJECT') AS fmp + WHERE fmp.permission_name = N'EXECUTE' + ) + BEGIN + SET @SkipXPRegRead = 1; + END; /*Need execute on xp_regread*/ + + IF NOT EXISTS + ( + SELECT + 1/0 + FROM fn_my_permissions(N'xp_fixeddrives', N'OBJECT') AS fmp + WHERE fmp.permission_name = N'EXECUTE' + ) + BEGIN + SET @SkipXPFixedDrives = 1; + END; /*Need execute on xp_fixeddrives*/ + + IF NOT EXISTS + ( + SELECT + 1/0 + FROM fn_my_permissions(N'xp_cmdshell', N'OBJECT') AS fmp + WHERE fmp.permission_name = N'EXECUTE' + ) + BEGIN + SET @SkipXPCMDShell = 1; + END; /*Need execute on xp_cmdshell*/ + + IF NOT EXISTS + ( + SELECT + 1/0 + FROM fn_my_permissions(N'sp_validatelogins', N'OBJECT') AS fmp + WHERE fmp.permission_name = N'EXECUTE' + ) + BEGIN + SET @SkipValidateLogins = 1; + END; /*Need execute on sp_validatelogins*/ + + INSERT + @db_perms + ( + database_name, + permission_name + ) + SELECT + database_name = + DB_NAME(d.database_id), + fmp.permission_name + FROM sys.databases AS d + CROSS APPLY fn_my_permissions(d.name, 'DATABASE') AS fmp + WHERE fmp.permission_name = N'SELECT' + AND d.database_id < 5; /*Databases where we don't have read permissions*/ + END; + SET @crlf = NCHAR(13) + NCHAR(10); SET @ResultText = 'sp_Blitz Results: ' + @crlf; @@ -331,6 +448,66 @@ AS OR LOWER(d.name) IN ('dbatools', 'dbadmin', 'dbmaintenance')) OPTION(RECOMPILE); + /*Skip checks for database where we don't have read permissions*/ + INSERT INTO + #SkipChecks + ( + DatabaseName + ) + SELECT + DB_NAME(d.database_id) + FROM sys.databases AS d + WHERE NOT EXISTS + ( + SELECT + 1/0 + FROM @db_perms AS dp + WHERE dp.database_name = DB_NAME(d.database_id) + ); + + /*Skip individial checks where we don't have permissions*/ + INSERT #SkipChecks (DatabaseName, CheckID, ServerName) + SELECT + v.* + FROM (VALUES(NULL, NULL, 29)) AS v (DatabaseName, CheckID, ServerName) /*Looks for user tables in model*/ + WHERE NOT EXISTS (SELECT 1/0 FROM @db_perms AS dp WHERE dp.database_name = 'model'); + + INSERT #SkipChecks (DatabaseName, CheckID, ServerName) + SELECT + v.* + FROM (VALUES(NULL, NULL, 68)) AS v (DatabaseName, CheckID, ServerName) /*DBCC command*/ + WHERE @sa = 0; + + INSERT #SkipChecks (DatabaseName, CheckID, ServerName) + SELECT + v.* + FROM (VALUES(NULL, NULL, 69)) AS v (DatabaseName, CheckID, ServerName) /*DBCC command*/ + WHERE @sa = 0; + + INSERT #SkipChecks (DatabaseName, CheckID, ServerName) + SELECT + v.* + FROM (VALUES(NULL, NULL, 92)) AS v (DatabaseName, CheckID, ServerName) /*xp_fixeddrives*/ + WHERE @SkipXPFixedDrives = 1; + + INSERT #SkipChecks (DatabaseName, CheckID, ServerName) + SELECT + v.* + FROM (VALUES(NULL, NULL, 211)) AS v (DatabaseName, CheckID, ServerName) /*xp_regread*/ + WHERE @SkipXPRegRead = 1; + + INSERT #SkipChecks (DatabaseName, CheckID, ServerName) + SELECT + v.* + FROM (VALUES(NULL, NULL, 212)) AS v (DatabaseName, CheckID, ServerName) /*xp_regread*/ + WHERE @SkipXPCMDShell = 1; + + INSERT #SkipChecks (DatabaseName, CheckID, ServerName) + SELECT + v.* + FROM (VALUES(NULL, NULL, 2301)) AS v (DatabaseName, CheckID, ServerName) /*sp_validatelogins*/ + WHERE @SkipValidateLogins = 1 + IF(OBJECT_ID('tempdb..#InvalidLogins') IS NOT NULL) BEGIN EXEC sp_executesql N'DROP TABLE #InvalidLogins;'; @@ -372,7 +549,8 @@ AS SELECT @IsWindowsOperatingSystem = 1 ; END; - IF NOT EXISTS ( SELECT 1 + + IF NOT EXISTS ( SELECT 1 FROM #SkipChecks WHERE DatabaseName IS NULL AND CheckID = 106 ) AND (select convert(int,value_in_use) from sys.configurations where name = 'default trace enabled' ) = 1 @@ -4158,53 +4336,56 @@ AS /* First, let's check that there aren't any issues with the trace files */ BEGIN TRY - - INSERT INTO #fnTraceGettable - ( TextData , - DatabaseName , - EventClass , - Severity , - StartTime , - EndTime , - Duration , - NTUserName , - NTDomainName , - HostName , - ApplicationName , - LoginName , - DBUserName - ) - SELECT TOP 20000 - CONVERT(NVARCHAR(4000),t.TextData) , - t.DatabaseName , - t.EventClass , - t.Severity , - t.StartTime , - t.EndTime , - t.Duration , - t.NTUserName , - t.NTDomainName , - t.HostName , - t.ApplicationName , - t.LoginName , - t.DBUserName - FROM sys.fn_trace_gettable(@base_tracefilename, DEFAULT) t - WHERE - ( - t.EventClass = 22 - AND t.Severity >= 17 - AND t.StartTime > DATEADD(dd, -30, GETDATE()) - ) - OR - ( - t.EventClass IN (92, 93) - AND t.StartTime > DATEADD(dd, -30, GETDATE()) - AND t.Duration > 15000000 - ) - OR - ( - t.EventClass IN (94, 95, 116) - ) + + IF @SkipTrace = 0 + BEGIN + INSERT INTO #fnTraceGettable + ( TextData , + DatabaseName , + EventClass , + Severity , + StartTime , + EndTime , + Duration , + NTUserName , + NTDomainName , + HostName , + ApplicationName , + LoginName , + DBUserName + ) + SELECT TOP 20000 + CONVERT(NVARCHAR(4000),t.TextData) , + t.DatabaseName , + t.EventClass , + t.Severity , + t.StartTime , + t.EndTime , + t.Duration , + t.NTUserName , + t.NTDomainName , + t.HostName , + t.ApplicationName , + t.LoginName , + t.DBUserName + FROM sys.fn_trace_gettable(@base_tracefilename, DEFAULT) t + WHERE + ( + t.EventClass = 22 + AND t.Severity >= 17 + AND t.StartTime > DATEADD(dd, -30, GETDATE()) + ) + OR + ( + t.EventClass IN (92, 93) + AND t.StartTime > DATEADD(dd, -30, GETDATE()) + AND t.Duration > 15000000 + ) + OR + ( + t.EventClass IN (94, 95, 116) + ) + END; SET @TraceFileIssue = 0 From 0eab78ead50cbd29f8faada7fba11322a2895f76 Mon Sep 17 00:00:00 2001 From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com> Date: Thu, 6 Jul 2023 13:26:42 -0400 Subject: [PATCH 416/662] Update sp_BlitzLock.sql Removes the `_num` from the physical reads columns, which aren't available in SQL Server 2016. Replaces them with columns that are available with physical reads metrics. --- sp_BlitzLock.sql | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index dafba72a8..79f1693d9 100644 --- a/sp_BlitzLock.sql +++ b/sp_BlitzLock.sql @@ -3665,8 +3665,8 @@ BEGIN ap.avg_elapsed_time, ap.total_logical_reads_mb, ap.total_physical_reads_mb, - ap.min_num_physical_reads_mb, - ap.max_num_physical_reads_mb, + ap.min_physical_reads_mb, + ap.max_physical_reads_mb, ap.total_logical_writes_mb, ap.min_grant_mb, ap.max_grant_mb, @@ -3733,10 +3733,10 @@ BEGIN deqs.total_logical_writes * 8. / 1024., total_logical_reads_mb = deqs.total_logical_reads * 8. / 1024., - min_num_physical_reads_mb = - deqs.min_num_physical_reads * 8. / 1024., - max_num_physical_reads_mb = - deqs.max_num_physical_reads * 8. / 1024., + min_physical_reads_mb = + deqs.min_physical_reads * 8. / 1024., + max_physical_reads_mb = + deqs.max_physical_reads * 8. / 1024., min_grant_mb = deqs.min_grant_kb * 8. / 1024., max_grant_mb = From aea7771ab345f9686596b0bc1546bc9e3326f83e Mon Sep 17 00:00:00 2001 From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com> Date: Fri, 7 Jul 2023 11:14:07 -0400 Subject: [PATCH 417/662] Update sp_BlitzLock.sql This is a hot fix. I keep running into servers where the physical reads columns are inconsistently there. I'm going to pull them for now. I don't feel like making this query dynamic at the moment. --- sp_BlitzLock.sql | 6 ------ 1 file changed, 6 deletions(-) diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index 79f1693d9..18b6ea9f3 100644 --- a/sp_BlitzLock.sql +++ b/sp_BlitzLock.sql @@ -3665,8 +3665,6 @@ BEGIN ap.avg_elapsed_time, ap.total_logical_reads_mb, ap.total_physical_reads_mb, - ap.min_physical_reads_mb, - ap.max_physical_reads_mb, ap.total_logical_writes_mb, ap.min_grant_mb, ap.max_grant_mb, @@ -3733,10 +3731,6 @@ BEGIN deqs.total_logical_writes * 8. / 1024., total_logical_reads_mb = deqs.total_logical_reads * 8. / 1024., - min_physical_reads_mb = - deqs.min_physical_reads * 8. / 1024., - max_physical_reads_mb = - deqs.max_physical_reads * 8. / 1024., min_grant_mb = deqs.min_grant_kb * 8. / 1024., max_grant_mb = From 731451879fd9c0e47f0da1c5c4bcb3130ef0ada2 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Thu, 13 Jul 2023 02:56:01 -0700 Subject: [PATCH 418/662] #3190 sp_DatabaseRestore data types Matching Microsoft's documentation. #3190 --- sp_DatabaseRestore.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sp_DatabaseRestore.sql b/sp_DatabaseRestore.sql index 068d61968..06968cade 100755 --- a/sp_DatabaseRestore.sql +++ b/sp_DatabaseRestore.sql @@ -366,8 +366,8 @@ CREATE TABLE #Headers EncryptorThumbprint VARBINARY(20), EncryptorType NVARCHAR(32), LastValidRestoreTime DATETIME, - TimeZone NVARCHAR(256), - CompressionAlgorithm NVARCHAR(256), + TimeZone NVARCHAR(32), + CompressionAlgorithm NVARCHAR(32), -- -- Seq added to retain order by -- From ca7cffcc9f21ecba39f8ee589a3f8c46c199c611 Mon Sep 17 00:00:00 2001 From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com> Date: Sat, 15 Jul 2023 11:23:28 -0400 Subject: [PATCH 419/662] Enhanced! XML filtering Closes #3305 --- sp_BlitzLock.sql | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index 79f1693d9..1a85d612a 100644 --- a/sp_BlitzLock.sql +++ b/sp_BlitzLock.sql @@ -856,8 +856,7 @@ BEGIN OR e.x.exist('@name[ .= "database_xml_deadlock_report"]') = 1 OR e.x.exist('@name[ .= "xml_deadlock_report_filtered"]') = 1 ) - AND e.x.exist('@timestamp[. >= sql:variable("@StartDate")]') = 1 - AND e.x.exist('@timestamp[. < sql:variable("@EndDate")]') = 1 + AND e.x.exist('@timestamp[. >= sql:variable("@StartDate") and .< sql:variable("@EndDate")]') = 1 OPTION(RECOMPILE); SET @d = CONVERT(varchar(40), GETDATE(), 109); @@ -894,8 +893,7 @@ BEGIN OR e.x.exist('@name[ .= "database_xml_deadlock_report"]') = 1 OR e.x.exist('@name[ .= "xml_deadlock_report_filtered"]') = 1 ) - AND e.x.exist('@timestamp[. >= sql:variable("@StartDate")]') = 1 - AND e.x.exist('@timestamp[. < sql:variable("@EndDate")]') = 1 + AND e.x.exist('@timestamp[. >= sql:variable("@StartDate") and .< sql:variable("@EndDate")]') = 1 OPTION(RECOMPILE); IF @Debug = 1 BEGIN SET STATISTICS XML OFF; END; @@ -931,8 +929,7 @@ BEGIN ) AS xml CROSS APPLY xml.deadlock_xml.nodes('/event') AS e(x) WHERE 1 = 1 - AND e.x.exist('@timestamp[. >= sql:variable("@StartDate")]') = 1 - AND e.x.exist('@timestamp[. < sql:variable("@EndDate")]') = 1 + AND e.x.exist('@timestamp[. >= sql:variable("@StartDate") and .< sql:variable("@EndDate")]') = 1 OPTION(RECOMPILE); INSERT From 1ae5fc7bc3a50e4b29ee264394fef97c7ebcd9d0 Mon Sep 17 00:00:00 2001 From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com> Date: Sun, 16 Jul 2023 10:03:21 -0400 Subject: [PATCH 420/662] Adds check for session existence I should probably add a to-do that also checks to make sure that the event has an xml deadlock report of some variety assigned to it, but Nicht heute, Satan Closes #3306 --- sp_BlitzLock.sql | 40 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index 1a85d612a..12a023a8e 100644 --- a/sp_BlitzLock.sql +++ b/sp_BlitzLock.sql @@ -227,8 +227,8 @@ BEGIN /*Normally I'd hate this, but we RECOMPILE everything*/ SELECT @StartDate = - CASE - WHEN @StartDate IS NULL + CASE + WHEN @StartDate IS NULL THEN DATEADD ( @@ -274,6 +274,42 @@ BEGIN ELSE @EndDate END; + IF @Azure = 0 + BEGIN + IF NOT EXISTS + ( + SELECT + 1/0 + FROM sys.server_event_sessions AS ses + JOIN sys.dm_xe_sessions AS dxs + ON dxs.name = ses.name + WHERE ses.name = @EventSessionName + AND dxs.create_time IS NOT NULL + ) + BEGIN + RAISERROR('A session with the name %s does not exist or is not currently active.', 11, 1, @session_name) WITH NOWAIT; + RETURN; + END; + END; + + IF @Azure = 1 + BEGIN + IF NOT EXISTS + ( + SELECT + 1/0 + FROM sys.database_event_sessions AS ses + JOIN sys.dm_xe_database_sessions AS dxs + ON dxs.name = ses.name + WHERE ses.name = @EventSessionName + AND dxs.create_time IS NOT NULL + ) + BEGIN + RAISERROR('A session with the name %s does not exist or is not currently active.', 11, 1, @session_name) WITH NOWAIT; + RETURN; + END; + END; + IF @OutputDatabaseName IS NOT NULL BEGIN /*IF databaseName is set, do some sanity checks and put [] around def.*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); From b4a8c1082e4b8226c38beaa13777998669e9222d Mon Sep 17 00:00:00 2001 From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com> Date: Sun, 16 Jul 2023 10:11:08 -0400 Subject: [PATCH 421/662] Update sp_BlitzLock.sql Fix raiserror --- sp_BlitzLock.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index 12a023a8e..7a44d6763 100644 --- a/sp_BlitzLock.sql +++ b/sp_BlitzLock.sql @@ -287,7 +287,7 @@ BEGIN AND dxs.create_time IS NOT NULL ) BEGIN - RAISERROR('A session with the name %s does not exist or is not currently active.', 11, 1, @session_name) WITH NOWAIT; + RAISERROR('A session with the name %s does not exist or is not currently active.', 11, 1, @EventSessionName) WITH NOWAIT; RETURN; END; END; @@ -305,7 +305,7 @@ BEGIN AND dxs.create_time IS NOT NULL ) BEGIN - RAISERROR('A session with the name %s does not exist or is not currently active.', 11, 1, @session_name) WITH NOWAIT; + RAISERROR('A session with the name %s does not exist or is not currently active.', 11, 1, @EventSessionName) WITH NOWAIT; RETURN; END; END; From 238c70f893032b3dd477be256523682ca2c6e9d5 Mon Sep 17 00:00:00 2001 From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com> Date: Sun, 16 Jul 2023 11:09:24 -0400 Subject: [PATCH 422/662] Update sp_BlitzLock.sql Closes #3307 In this commit: * Adjust start and end dates to make them UTC-friendly for initial XML searches * Revert them back to the originals for subsequent searches, since the time gets converted to local after extraction from the event XML * Add parameter and variable output to the debug section for improved troubleshooting (boy did I need that!) * Minor formatting/whitespace changes --- sp_BlitzLock.sql | 291 +++++++++++++++++++++++++++++++++-------------- 1 file changed, 208 insertions(+), 83 deletions(-) diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index 7a44d6763..36f92b65d 100644 --- a/sp_BlitzLock.sql +++ b/sp_BlitzLock.sql @@ -179,7 +179,9 @@ BEGIN @TargetSessionId int = 0, @FileName nvarchar(4000) = N'', @inputbuf_bom nvarchar(1) = CONVERT(nvarchar(1), 0x0a00, 0), - @deadlock_result nvarchar(MAX) = N''; + @deadlock_result nvarchar(MAX) = N'', + @StartDateOriginal datetime = @StartDate, + @EndDateOriginal datetime = @EndDate; /*Temporary objects used in the procedure*/ DECLARE @@ -220,11 +222,12 @@ BEGIN object_name nvarchar(1000), finding_group nvarchar(100), finding nvarchar(4000), - sort_order bigint + sort_order bigint ); /*Set these to some sane defaults if NULLs are passed in*/ /*Normally I'd hate this, but we RECOMPILE everything*/ + SELECT @StartDate = CASE @@ -250,7 +253,18 @@ BEGIN ) ) ) - ELSE @StartDate + ELSE + DATEADD + ( + MINUTE, + DATEDIFF + ( + MINUTE, + SYSDATETIME(), + GETUTCDATE() + ), + @StartDate + ) END, @EndDate = CASE @@ -271,7 +285,18 @@ BEGIN SYSDATETIME() ) ) - ELSE @EndDate + ELSE + DATEADD + ( + MINUTE, + DATEDIFF + ( + MINUTE, + SYSDATETIME(), + GETUTCDATE() + ), + @EndDate + ) END; IF @Azure = 0 @@ -291,7 +316,7 @@ BEGIN RETURN; END; END; - + IF @Azure = 1 BEGIN IF NOT EXISTS @@ -1092,7 +1117,12 @@ BEGIN DATEADD ( MINUTE, - DATEDIFF(MINUTE, GETUTCDATE(), SYSDATETIME()), + DATEDIFF + ( + MINUTE, + GETUTCDATE(), + SYSDATETIME() + ), dd.event_date ), dd.victim_id, @@ -1223,7 +1253,7 @@ BEGIN waiter_mode = w.l.value('@mode', 'nvarchar(256)'), owner_id = o.l.value('@id', 'nvarchar(256)'), owner_mode = o.l.value('@mode', 'nvarchar(256)'), - lock_type = CAST(N'OBJECT' AS NVARCHAR(100)) + lock_type = CAST(N'OBJECT' AS nvarchar(100)) INTO #deadlock_owner_waiter FROM ( @@ -1781,6 +1811,14 @@ BEGIN /*Begin checks based on parsed values*/ + /* + First, revert these back since we already converted the event data to local time, + and searches will break if we use the times converted over to UTC for the event data + */ + SELECT + @StartDate = @StartDateOriginal, + @EndDate = @EndDateOriginal; + /*Check 1 is deadlocks by database*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Check 1 database deadlocks %s', 0, 1, @d) WITH NOWAIT; @@ -1793,7 +1831,7 @@ BEGIN object_name, finding_group, finding, - sort_order + sort_order ) SELECT check_id = 1, @@ -1808,9 +1846,9 @@ BEGIN COUNT_BIG(DISTINCT dp.event_date) ) + N' deadlocks.', - sort_order = + sort_order = ROW_NUMBER() - OVER (ORDER BY COUNT_BIG(DISTINCT dp.event_date) DESC) + OVER (ORDER BY COUNT_BIG(DISTINCT dp.event_date) DESC) FROM #deadlock_process AS dp WHERE 1 = 1 AND (dp.database_name = @DatabaseName OR @DatabaseName IS NULL) @@ -1836,7 +1874,7 @@ BEGIN object_name, finding_group, finding, - sort_order + sort_order ) SELECT check_id = 2, @@ -1852,9 +1890,9 @@ BEGIN COUNT_BIG(DISTINCT dow.event_date) ) + N' deadlock(s) between read queries and modification queries.', - sort_order = + sort_order = ROW_NUMBER() - OVER (ORDER BY COUNT_BIG(DISTINCT dow.event_date) DESC) + OVER (ORDER BY COUNT_BIG(DISTINCT dow.event_date) DESC) FROM #deadlock_owner_waiter AS dow WHERE 1 = 1 AND dow.lock_mode IN @@ -1894,7 +1932,7 @@ BEGIN object_name, finding_group, finding, - sort_order + sort_order ) SELECT check_id = 3, @@ -1914,9 +1952,9 @@ BEGIN COUNT_BIG(DISTINCT dow.event_date) ) + N' deadlock(s).', - sort_order = + sort_order = ROW_NUMBER() - OVER (ORDER BY COUNT_BIG(DISTINCT dow.event_date) DESC) + OVER (ORDER BY COUNT_BIG(DISTINCT dow.event_date) DESC) FROM #deadlock_owner_waiter AS dow WHERE 1 = 1 AND (dow.database_id = @DatabaseId OR @DatabaseName IS NULL) @@ -1942,7 +1980,7 @@ BEGIN object_name, finding_group, finding, - sort_order + sort_order ) SELECT check_id = 3, @@ -1957,9 +1995,9 @@ BEGIN COUNT_BIG(DISTINCT dow.event_date) ) + N' deadlock(s).', - sort_order = + sort_order = ROW_NUMBER() - OVER (ORDER BY COUNT_BIG(DISTINCT dow.event_date) DESC) + OVER (ORDER BY COUNT_BIG(DISTINCT dow.event_date) DESC) FROM #deadlock_owner_waiter AS dow WHERE 1 = 1 AND (dow.database_id = @DatabaseId OR @DatabaseName IS NULL) @@ -1991,7 +2029,7 @@ BEGIN object_name, finding_group, finding, - sort_order + sort_order ) SELECT check_id = 3, @@ -2006,9 +2044,9 @@ BEGIN COUNT_BIG(DISTINCT dow.event_date) ) + N' deadlock(s).', - sort_order = + sort_order = ROW_NUMBER() - OVER (ORDER BY COUNT_BIG(DISTINCT dow.event_date) DESC) + OVER (ORDER BY COUNT_BIG(DISTINCT dow.event_date) DESC) FROM #deadlock_owner_waiter AS dow WHERE 1 = 1 AND (dow.database_id = @DatabaseId OR @DatabaseName IS NULL) @@ -2039,7 +2077,7 @@ BEGIN object_name, finding_group, finding, - sort_order + sort_order ) SELECT check_id = 4, @@ -2055,9 +2093,9 @@ BEGIN COUNT_BIG(DISTINCT dp.event_date) ) + N' instances of Serializable deadlocks.', - sort_order = + sort_order = ROW_NUMBER() - OVER (ORDER BY COUNT_BIG(DISTINCT dp.event_date) DESC) + OVER (ORDER BY COUNT_BIG(DISTINCT dp.event_date) DESC) FROM #deadlock_process AS dp WHERE dp.isolation_level LIKE N'serializable%' AND (dp.database_name = @DatabaseName OR @DatabaseName IS NULL) @@ -2083,7 +2121,7 @@ BEGIN object_name, finding_group, finding, - sort_order + sort_order ) SELECT check_id = 5, @@ -2098,9 +2136,9 @@ BEGIN COUNT_BIG(DISTINCT dp.event_date) ) + N' instances of Repeatable Read deadlocks.', - sort_order = + sort_order = ROW_NUMBER() - OVER (ORDER BY COUNT_BIG(DISTINCT dp.event_date) DESC) + OVER (ORDER BY COUNT_BIG(DISTINCT dp.event_date) DESC) FROM #deadlock_process AS dp WHERE dp.isolation_level LIKE N'repeatable%' AND (dp.database_name = @DatabaseName OR @DatabaseName IS NULL) @@ -2126,7 +2164,7 @@ BEGIN object_name, finding_group, finding, - sort_order + sort_order ) SELECT check_id = 6, @@ -2160,9 +2198,9 @@ BEGIN N'UNKNOWN' ) + N'.', - sort_order = + sort_order = ROW_NUMBER() - OVER (ORDER BY COUNT_BIG(DISTINCT dp.event_date) DESC) + OVER (ORDER BY COUNT_BIG(DISTINCT dp.event_date) DESC) FROM #deadlock_process AS dp WHERE 1 = 1 AND (dp.database_name = @DatabaseName OR @DatabaseName IS NULL) @@ -2244,7 +2282,7 @@ BEGIN object_name, finding_group, finding, - sort_order + sort_order ) SELECT check_id = 7, @@ -2272,9 +2310,9 @@ BEGIN 1, N'' ) + N' locks.', - sort_order = + sort_order = ROW_NUMBER() - OVER (ORDER BY CONVERT(bigint, lt.lock_count) DESC) + OVER (ORDER BY CONVERT(bigint, lt.lock_count) DESC) FROM lock_types AS lt OPTION(RECOMPILE); @@ -2431,7 +2469,7 @@ BEGIN object_name, finding_group, finding, - sort_order + sort_order ) SELECT check_id = 9, @@ -2451,9 +2489,9 @@ BEGIN COUNT_BIG(DISTINCT ds.id) ) + N' deadlocks.', - sort_order = + sort_order = ROW_NUMBER() - OVER (ORDER BY COUNT_BIG(DISTINCT ds.id) DESC) + OVER (ORDER BY COUNT_BIG(DISTINCT ds.id) DESC) FROM #deadlock_stack AS ds JOIN #deadlock_process AS dp ON dp.id = ds.id @@ -2552,19 +2590,19 @@ BEGIN ) ), wait_time_hms = - /*the more wait time you rack up the less accurate this gets, + /*the more wait time you rack up the less accurate this gets, it's either that or erroring out*/ - CASE - WHEN + CASE + WHEN SUM ( CONVERT ( - bigint, + bigint, dp.wait_time ) )/1000 > 2147483647 - THEN + THEN CONVERT ( nvarchar(30), @@ -2577,7 +2615,7 @@ BEGIN ( CONVERT ( - bigint, + bigint, dp.wait_time ) ) @@ -2588,16 +2626,16 @@ BEGIN ), 14 ) - WHEN + WHEN SUM ( CONVERT ( - bigint, + bigint, dp.wait_time ) ) BETWEEN 2147483648 AND 2147483647000 - THEN + THEN CONVERT ( nvarchar(30), @@ -2610,7 +2648,7 @@ BEGIN ( CONVERT ( - bigint, + bigint, dp.wait_time ) ) @@ -2643,8 +2681,8 @@ BEGIN 14 ) END, - total_waits = - SUM(CONVERT(bigint, dp.wait_time)) + total_waits = + SUM(CONVERT(bigint, dp.wait_time)) FROM #deadlock_owner_waiter AS dow JOIN #deadlock_process AS dp ON (dp.id = dow.owner_id @@ -2670,7 +2708,7 @@ BEGIN object_name, finding_group, finding, - sort_order + sort_order ) SELECT check_id = 11, @@ -2692,9 +2730,9 @@ BEGIN 14 ) + N' [dd hh:mm:ss:ms] of deadlock wait time.', - sort_order = + sort_order = ROW_NUMBER() - OVER (ORDER BY cs.total_waits DESC) + OVER (ORDER BY cs.total_waits DESC) FROM chopsuey AS cs WHERE cs.object_name IS NOT NULL OPTION(RECOMPILE); @@ -2743,7 +2781,7 @@ BEGIN object_name, finding_group, finding, - sort_order + sort_order ) SELECT check_id = 12, @@ -2766,19 +2804,19 @@ BEGIN ) ) + N' ' + - /*the more wait time you rack up the less accurate this gets, + /*the more wait time you rack up the less accurate this gets, it's either that or erroring out*/ - CASE - WHEN + CASE + WHEN SUM ( CONVERT ( - bigint, + bigint, wt.total_wait_time_ms ) )/1000 > 2147483647 - THEN + THEN CONVERT ( nvarchar(30), @@ -2791,7 +2829,7 @@ BEGIN ( CONVERT ( - bigint, + bigint, wt.total_wait_time_ms ) ) @@ -2802,16 +2840,16 @@ BEGIN ), 14 ) - WHEN + WHEN SUM ( CONVERT ( - bigint, + bigint, wt.total_wait_time_ms ) ) BETWEEN 2147483648 AND 2147483647000 - THEN + THEN CONVERT ( nvarchar(30), @@ -2824,7 +2862,7 @@ BEGIN ( CONVERT ( - bigint, + bigint, wt.total_wait_time_ms ) ) @@ -2857,9 +2895,9 @@ BEGIN 14 ) END + N' [dd hh:mm:ss:ms] of deadlock wait time.', - sort_order = + sort_order = ROW_NUMBER() - OVER (ORDER BY SUM(CONVERT(bigint, wt.total_wait_time_ms)) DESC) + OVER (ORDER BY SUM(CONVERT(bigint, wt.total_wait_time_ms)) DESC) FROM wait_time AS wt GROUP BY wt.database_name @@ -2879,7 +2917,7 @@ BEGIN object_name, finding_group, finding, - sort_order + sort_order ) SELECT check_id = 13, @@ -2895,9 +2933,9 @@ BEGIN N'There have been ' + RTRIM(COUNT_BIG(DISTINCT aj.event_date)) + N' deadlocks from this Agent Job and Step.', - sort_order = + sort_order = ROW_NUMBER() - OVER (ORDER BY COUNT_BIG(DISTINCT aj.event_date) DESC) + OVER (ORDER BY COUNT_BIG(DISTINCT aj.event_date) DESC) FROM #agent_job AS aj GROUP BY DB_NAME(aj.database_id), @@ -3648,18 +3686,18 @@ BEGIN BEGIN SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Results to client %s', 0, 1, @d) WITH NOWAIT; - + IF @Debug = 1 BEGIN SET STATISTICS XML ON; END; - + EXEC sys.sp_executesql @deadlock_result; - + IF @Debug = 1 BEGIN SET STATISTICS XML OFF; PRINT @deadlock_result; END; - + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; SET @d = CONVERT(varchar(40), GETDATE(), 109); @@ -3747,7 +3785,7 @@ BEGIN executions_per_second = ISNULL ( - execution_count / + deqs.execution_count / NULLIF ( DATEDIFF @@ -3781,7 +3819,7 @@ BEGIN min_spills_mb = deqs.min_spills * 8. / 1024., max_spills_mb = - deqs.max_spills * 8. / 1024., + deqs.max_spills * 8. / 1024., deqs.min_reserved_threads, deqs.max_reserved_threads, deqs.min_used_threads, @@ -3808,10 +3846,10 @@ BEGIN OPTION(RECOMPILE); RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - + SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Returning findings %s', 0, 1, @d) WITH NOWAIT; - + SELECT df.check_id, df.database_name, @@ -3820,10 +3858,10 @@ BEGIN df.finding FROM #deadlock_findings AS df ORDER BY - df.check_id, - df.sort_order + df.check_id, + df.sort_order OPTION(RECOMPILE); - + SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; END; /*done with output to client app.*/ @@ -3832,15 +3870,15 @@ BEGIN IF @Debug = 1 BEGIN SELECT - table_name = N'#dd', + table_name = N'#deadlock_data', * - FROM #dd AS d + FROM #deadlock_data AS dd OPTION(RECOMPILE); SELECT - table_name = N'#deadlock_data', + table_name = N'#dd', * - FROM #deadlock_data AS dd + FROM #dd AS d OPTION(RECOMPILE); SELECT @@ -3897,6 +3935,93 @@ BEGIN FROM @sysAssObjId AS s OPTION(RECOMPILE); + SELECT + procedure_parameters = + 'procedure_parameters', + DatabaseName = + @DatabaseName, + StartDate = + @StartDate, + EndDate = + @EndDate, + ObjectName = + @ObjectName, + StoredProcName = + @StoredProcName, + AppName = + @AppName, + HostName = + @HostName, + LoginName = + @LoginName, + EventSessionName = + @EventSessionName, + TargetSessionType = + @TargetSessionType, + VictimsOnly = + @VictimsOnly, + Debug = + @Debug, + Help = + @Help, + Version = + @Version, + VersionDate = + @VersionDate, + VersionCheckMode = + @VersionCheckMode, + OutputDatabaseName = + @OutputDatabaseName, + OutputSchemaName = + @OutputSchemaName, + OutputTableName = + @OutputTableName, + ExportToExcel = + @ExportToExcel; + + SELECT + declared_variables = + 'declared_variables', + DatabaseId = + @DatabaseId, + ProductVersion = + @ProductVersion, + ProductVersionMajor = + @ProductVersionMajor, + ProductVersionMinor = + @ProductVersionMinor, + ObjectFullName = + @ObjectFullName, + Azure = + @Azure, + RDS = + @RDS, + d = + @d, + StringToExecute = + @StringToExecute, + StringToExecuteParams = + @StringToExecuteParams, + r = + @r, + OutputTableFindings = + @OutputTableFindings, + DeadlockCount = + @DeadlockCount, + ServerName = + @ServerName, + OutputDatabaseCheck = + @OutputDatabaseCheck, + SessionId = + @SessionId, + TargetSessionId = + @TargetSessionId, + FileName = + @FileName, + inputbuf_bom = + @inputbuf_bom, + deadlock_result = + @deadlock_result; END; /*End debug*/ END; /*Final End*/ From daac34090cca6f3172610275d1a601a12e37ea67 Mon Sep 17 00:00:00 2001 From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com> Date: Mon, 24 Jul 2023 12:55:22 -0400 Subject: [PATCH 423/662] Update sp_BlitzLock.sql Closes #3311 --- sp_BlitzLock.sql | 234 +++++++++++++++++++++++++++-------------------- 1 file changed, 136 insertions(+), 98 deletions(-) diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index 36f92b65d..54d554524 100644 --- a/sp_BlitzLock.sql +++ b/sp_BlitzLock.sql @@ -181,7 +181,9 @@ BEGIN @inputbuf_bom nvarchar(1) = CONVERT(nvarchar(1), 0x0a00, 0), @deadlock_result nvarchar(MAX) = N'', @StartDateOriginal datetime = @StartDate, - @EndDateOriginal datetime = @EndDate; + @EndDateOriginal datetime = @EndDate, + @StartDateUTC datetime, + @EndDateUTC datetime; /*Temporary objects used in the procedure*/ DECLARE @@ -242,15 +244,11 @@ BEGIN SYSDATETIME(), GETUTCDATE() ), - ISNULL + DATEADD ( - @StartDate, - DATEADD - ( - DAY, - -7, - SYSDATETIME() - ) + DAY, + -7, + SYSDATETIME() ) ) ELSE @@ -279,11 +277,7 @@ BEGIN SYSDATETIME(), GETUTCDATE() ), - ISNULL - ( - @EndDate, - SYSDATETIME() - ) + SYSDATETIME() ) ELSE DATEADD @@ -299,6 +293,10 @@ BEGIN ) END; + SELECT + @StartDateUTC = @StartDate, + @EndDateUTC = @EndDate; + IF @Azure = 0 BEGIN IF NOT EXISTS @@ -1812,9 +1810,9 @@ BEGIN /*Begin checks based on parsed values*/ /* - First, revert these back since we already converted the event data to local time, - and searches will break if we use the times converted over to UTC for the event data - */ + First, revert these back since we already converted the event data to local time, + and searches will break if we use the times converted over to UTC for the event data + */ SELECT @StartDate = @StartDateOriginal, @EndDate = @EndDateOriginal; @@ -3702,26 +3700,104 @@ BEGIN SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Getting available execution plans for deadlocks %s', 0, 1, @d) WITH NOWAIT; - + SELECT DISTINCT - ds.proc_name, + available_plans = + 'available_plans', + ds.proc_name, sql_handle = CONVERT(varbinary(64), ds.sql_handle, 1), dow.database_name, - dow.object_name, + dow.database_id, + dow.object_name, query_xml = - CONVERT(nvarchar(MAX), dr.query_xml) + TRY_CAST(dr.query_xml AS nvarchar(MAX)) INTO #available_plans FROM #deadlock_stack AS ds JOIN #deadlock_owner_waiter AS dow ON dow.owner_id = ds.id AND dow.event_date = ds.event_date JOIN #deadlock_results AS dr - ON dr.id = ds.id - AND dr.event_date = ds.event_date + ON dr.id = ds.id + AND dr.event_date = ds.event_date OPTION(RECOMPILE); SELECT + deqs.sql_handle, + deqs.plan_handle, + deqs.statement_start_offset, + deqs.statement_end_offset, + deqs.creation_time, + deqs.last_execution_time, + deqs.execution_count, + total_worker_time_ms = + deqs.total_worker_time / 1000., + avg_worker_time_ms = + CONVERT(decimal(38, 6), deqs.total_worker_time / 1000. / deqs.execution_count), + total_elapsed_time_ms = + deqs.total_elapsed_time / 1000., + avg_elapsed_time = + CONVERT(decimal(38, 6), deqs.total_elapsed_time / 1000. / deqs.execution_count), + executions_per_second = + ISNULL + ( + deqs.execution_count / + NULLIF + ( + DATEDIFF + ( + SECOND, + deqs.creation_time, + deqs.last_execution_time + ), + 0 + ), + 0 + ), + total_physical_reads_mb = + deqs.total_physical_reads * 8. / 1024., + total_logical_writes_mb = + deqs.total_logical_writes * 8. / 1024., + total_logical_reads_mb = + deqs.total_logical_reads * 8. / 1024., + min_grant_mb = + deqs.min_grant_kb * 8. / 1024., + max_grant_mb = + deqs.max_grant_kb * 8. / 1024., + min_used_grant_mb = + deqs.min_used_grant_kb * 8. / 1024., + max_used_grant_mb = + deqs.max_used_grant_kb * 8. / 1024., + min_spills_mb = + deqs.min_spills * 8. / 1024., + max_spills_mb = + deqs.max_spills * 8. / 1024., + deqs.min_reserved_threads, + deqs.max_reserved_threads, + deqs.min_used_threads, + deqs.max_used_threads, + deqs.total_rows + INTO #dm_exec_query_stats + FROM sys.dm_exec_query_stats AS deqs + WHERE EXISTS + ( + SELECT + 1/0 + FROM #available_plans AS ap + WHERE ap.sql_handle = deqs.sql_handle + ) + AND deqs.query_hash IS NOT NULL; + + CREATE CLUSTERED INDEX + deqs + ON #dm_exec_query_stats + ( + sql_handle, + plan_handle + ); + + SELECT + ap.available_plans, ap.database_name, query_text = TRY_CAST(ap.query_xml AS xml), @@ -3736,8 +3812,6 @@ BEGIN ap.avg_elapsed_time, ap.total_logical_reads_mb, ap.total_physical_reads_mb, - ap.min_physical_reads_mb, - ap.max_physical_reads_mb, ap.total_logical_writes_mb, ap.min_grant_mb, ap.max_grant_mb, @@ -3755,79 +3829,42 @@ BEGIN ap.statement_end_offset FROM ( + SELECT - *, - n = - ROW_NUMBER() OVER - ( - PARTITION BY - ap.sql_handle - ORDER BY - ap.sql_handle - ) + ap.*, + c.statement_start_offset, + c.statement_end_offset, + c.creation_time, + c.last_execution_time, + c.execution_count, + c.total_worker_time_ms, + c.avg_worker_time_ms, + c.total_elapsed_time_ms, + c.avg_elapsed_time, + c.executions_per_second, + c.total_physical_reads_mb, + c.total_logical_writes_mb, + c.total_logical_reads_mb, + c.min_grant_mb, + c.max_grant_mb, + c.min_used_grant_mb, + c.max_used_grant_mb, + c.min_spills_mb, + c.max_spills_mb, + c.min_reserved_threads, + c.max_reserved_threads, + c.min_used_threads, + c.max_used_threads, + c.total_rows, + c.query_plan FROM #available_plans AS ap OUTER APPLY ( - SELECT TOP (1) - deqs.statement_start_offset, - deqs.statement_end_offset, - deqs.creation_time, - deqs.last_execution_time, - deqs.execution_count, - total_worker_time_ms = - deqs.total_worker_time / 1000., - avg_worker_time_ms = - CONVERT(decimal(38, 6), deqs.total_worker_time / 1000. / deqs.execution_count), - total_elapsed_time_ms = - deqs.total_elapsed_time / 1000., - avg_elapsed_time = - CONVERT(decimal(38, 6), deqs.total_elapsed_time / 1000. / deqs.execution_count), - executions_per_second = - ISNULL - ( - deqs.execution_count / - NULLIF - ( - DATEDIFF - ( - SECOND, - deqs.creation_time, - deqs.last_execution_time - ), - 0 - ), - 0 - ), - total_physical_reads_mb = - deqs.total_physical_reads * 8. / 1024., - total_logical_writes_mb = - deqs.total_logical_writes * 8. / 1024., - total_logical_reads_mb = - deqs.total_logical_reads * 8. / 1024., - min_physical_reads_mb = - deqs.min_physical_reads * 8. / 1024., - max_physical_reads_mb = - deqs.max_physical_reads * 8. / 1024., - min_grant_mb = - deqs.min_grant_kb * 8. / 1024., - max_grant_mb = - deqs.max_grant_kb * 8. / 1024., - min_used_grant_mb = - deqs.min_used_grant_kb * 8. / 1024., - max_used_grant_mb = - deqs.max_used_grant_kb * 8. / 1024., - min_spills_mb = - deqs.min_spills * 8. / 1024., - max_spills_mb = - deqs.max_spills * 8. / 1024., - deqs.min_reserved_threads, - deqs.max_reserved_threads, - deqs.min_used_threads, - deqs.max_used_threads, - deqs.total_rows, + SELECT + deqs.*, query_plan = TRY_CAST(deps.query_plan AS xml) - FROM sys.dm_exec_query_stats AS deqs + FROM #dm_exec_query_stats deqs OUTER APPLY sys.dm_exec_text_query_plan ( deqs.plan_handle, @@ -3835,15 +3872,13 @@ BEGIN deqs.statement_end_offset ) AS deps WHERE deqs.sql_handle = ap.sql_handle - ORDER BY - deqs.last_execution_time DESC + AND deps.dbid = ap.database_id ) AS c ) AS ap WHERE ap.query_plan IS NOT NULL - AND ap.n = 1 ORDER BY - ap.last_execution_time DESC - OPTION(RECOMPILE); + ap.avg_worker_time_ms DESC + OPTION(RECOMPILE, LOOP JOIN, HASH JOIN); RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; @@ -3984,6 +4019,10 @@ BEGIN 'declared_variables', DatabaseId = @DatabaseId, + StartDateUTC = + @StartDateUTC, + EndDateUTC = + @EndDateUTC, ProductVersion = @ProductVersion, ProductVersionMajor = @@ -4024,5 +4063,4 @@ BEGIN @deadlock_result; END; /*End debug*/ END; /*Final End*/ - GO From 73600c9c3194922bd3ed92d88cc47b9899d90a46 Mon Sep 17 00:00:00 2001 From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com> Date: Wed, 26 Jul 2023 11:03:41 -0400 Subject: [PATCH 424/662] Update sp_BlitzLock.sql Pull spills columns since they break compat pre-2016 and I'm still too lazy to make this dynamic. --- sp_BlitzLock.sql | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index 54d554524..d9d3760dd 100644 --- a/sp_BlitzLock.sql +++ b/sp_BlitzLock.sql @@ -3767,11 +3767,7 @@ BEGIN min_used_grant_mb = deqs.min_used_grant_kb * 8. / 1024., max_used_grant_mb = - deqs.max_used_grant_kb * 8. / 1024., - min_spills_mb = - deqs.min_spills * 8. / 1024., - max_spills_mb = - deqs.max_spills * 8. / 1024., + deqs.max_used_grant_kb * 8. / 1024., deqs.min_reserved_threads, deqs.max_reserved_threads, deqs.min_used_threads, @@ -3817,8 +3813,6 @@ BEGIN ap.max_grant_mb, ap.min_used_grant_mb, ap.max_used_grant_mb, - ap.min_spills_mb, - ap.max_spills_mb, ap.min_reserved_threads, ap.max_reserved_threads, ap.min_used_threads, @@ -3849,8 +3843,6 @@ BEGIN c.max_grant_mb, c.min_used_grant_mb, c.max_used_grant_mb, - c.min_spills_mb, - c.max_spills_mb, c.min_reserved_threads, c.max_reserved_threads, c.min_used_threads, From 9d7f7ea6c787efd44c4adfc5884d37fd21cf7688 Mon Sep 17 00:00:00 2001 From: Walden Leverich Date: Wed, 26 Jul 2023 20:10:38 -0500 Subject: [PATCH 425/662] Omit Resource DB from duplicate query plan checks Addresses #3314 --- sp_BlitzCache.sql | 1 + 1 file changed, 1 insertion(+) diff --git a/sp_BlitzCache.sql b/sp_BlitzCache.sql index 11600c64f..fb41ff60b 100644 --- a/sp_BlitzCache.sql +++ b/sp_BlitzCache.sql @@ -1620,6 +1620,7 @@ WITH total_plans AS ON qs.sql_handle = ps.sql_handle CROSS APPLY sys.dm_exec_plan_attributes(qs.plan_handle) pa WHERE pa.attribute = N'dbid' + AND pa.value <> 32767 /*Omit Resource database-based queries, we're not going to "fix" them no matter what. Addresses #3314*/ AND qs.query_plan_hash <> 0x0000000000000000 GROUP BY /* qs.query_plan_hash, BGO 20210524 commenting this out to fix #2909 */ From 7320f75ecdc222ecfaadd660b9b17fb2a8a822f7 Mon Sep 17 00:00:00 2001 From: David Schanzer Date: Thu, 27 Jul 2023 17:12:23 +1000 Subject: [PATCH 426/662] Correct bracketing for Replication In Use check --- sp_Blitz.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index 436578173..0ef90c316 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -6579,10 +6579,10 @@ IF @ProductVersionMajor >= 10 DatabaseName FROM #SkipChecks WHERE CheckID IS NULL OR CheckID = 19) - AND is_published = 1 + AND (is_published = 1 OR is_subscribed = 1 OR is_merge_published = 1 - OR is_distributor = 1; + OR is_distributor = 1); /* Method B: check subscribers for MSreplication_objects tables */ EXEC dbo.sp_MSforeachdb 'USE [?]; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; From af157326f55548eea614c692cb3830409ae49b00 Mon Sep 17 00:00:00 2001 From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com> Date: Thu, 27 Jul 2023 11:15:44 -0400 Subject: [PATCH 427/662] Update sp_BlitzLock.sql Add the available plans and exec query stats temp tables to debug output --- sp_BlitzLock.sql | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index d9d3760dd..f12787503 100644 --- a/sp_BlitzLock.sql +++ b/sp_BlitzLock.sql @@ -293,9 +293,9 @@ BEGIN ) END; - SELECT - @StartDateUTC = @StartDate, - @EndDateUTC = @EndDate; + SELECT + @StartDateUTC = @StartDate, + @EndDateUTC = @EndDate; IF @Azure = 0 BEGIN @@ -3700,16 +3700,16 @@ BEGIN SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Getting available execution plans for deadlocks %s', 0, 1, @d) WITH NOWAIT; - + SELECT DISTINCT - available_plans = - 'available_plans', - ds.proc_name, + available_plans = + 'available_plans', + ds.proc_name, sql_handle = CONVERT(varbinary(64), ds.sql_handle, 1), dow.database_name, - dow.database_id, - dow.object_name, + dow.database_id, + dow.object_name, query_xml = TRY_CAST(dr.query_xml AS nvarchar(MAX)) INTO #available_plans @@ -3823,7 +3823,7 @@ BEGIN ap.statement_end_offset FROM ( - + SELECT ap.*, c.statement_start_offset, @@ -3962,6 +3962,18 @@ BEGIN FROM @sysAssObjId AS s OPTION(RECOMPILE); + SELECT + table_name = N'#available_plans', + * + FROM #available_plans AS ap + OPTION(RECOMPILE); + + SELECT + table_name = N'ava#dm_exec_query_statsilable_plans', + * + FROM #dm_exec_query_stats + OPTION(RECOMPILE); + SELECT procedure_parameters = 'procedure_parameters', From 717b5da58fa6273cf31fbb17bcf77d7791a2eceb Mon Sep 17 00:00:00 2001 From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com> Date: Thu, 27 Jul 2023 13:13:30 -0400 Subject: [PATCH 428/662] Update sp_BlitzLock.sql Well that was unfortunate. --- sp_BlitzLock.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index f12787503..b609731f5 100644 --- a/sp_BlitzLock.sql +++ b/sp_BlitzLock.sql @@ -3969,7 +3969,7 @@ BEGIN OPTION(RECOMPILE); SELECT - table_name = N'ava#dm_exec_query_statsilable_plans', + table_name = N'#dm_exec_query_stats', * FROM #dm_exec_query_stats OPTION(RECOMPILE); From b70b326e35d384a0b5b576612303fa111c3020cb Mon Sep 17 00:00:00 2001 From: PasswordIsMyPassword Date: Sat, 29 Jul 2023 17:10:00 -0500 Subject: [PATCH 429/662] #3314 sp_BlitzCache high percentage of duplicate plans Fixes #3314 --- sp_BlitzCache.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sp_BlitzCache.sql b/sp_BlitzCache.sql index 11600c64f..aa4d6450e 100644 --- a/sp_BlitzCache.sql +++ b/sp_BlitzCache.sql @@ -1617,7 +1617,7 @@ WITH total_plans AS COUNT_BIG(qs.query_plan_hash) AS duplicate_plan_hashes FROM sys.dm_exec_query_stats qs LEFT JOIN sys.dm_exec_procedure_stats ps - ON qs.sql_handle = ps.sql_handle + ON qs.plan_handle = ps.plan_handle CROSS APPLY sys.dm_exec_plan_attributes(qs.plan_handle) pa WHERE pa.attribute = N'dbid' AND qs.query_plan_hash <> 0x0000000000000000 From a79a9ddd46a68f055ef96e656e8676bc41ccf8e4 Mon Sep 17 00:00:00 2001 From: PasswordIsMyPassword Date: Sat, 29 Jul 2023 18:04:09 -0500 Subject: [PATCH 430/662] #3314 update to --- sp_BlitzCache.sql | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sp_BlitzCache.sql b/sp_BlitzCache.sql index aa4d6450e..162cfa993 100644 --- a/sp_BlitzCache.sql +++ b/sp_BlitzCache.sql @@ -1616,8 +1616,7 @@ WITH total_plans AS SELECT COUNT_BIG(qs.query_plan_hash) AS duplicate_plan_hashes FROM sys.dm_exec_query_stats qs - LEFT JOIN sys.dm_exec_procedure_stats ps - ON qs.plan_handle = ps.plan_handle + LEFT JOIN sys.dm_exec_procedure_stats ps ON qs.plan_handle = ps.plan_handle CROSS APPLY sys.dm_exec_plan_attributes(qs.plan_handle) pa WHERE pa.attribute = N'dbid' AND qs.query_plan_hash <> 0x0000000000000000 From 5252cdfcc60f22936fee10c731e89894c0bb9314 Mon Sep 17 00:00:00 2001 From: MDPenguin <122059580+mdpenguin@users.noreply.github.com> Date: Wed, 9 Aug 2023 12:57:18 -0400 Subject: [PATCH 431/662] Update PercentMemoryGrantUsed in sp_BlitzCache.sql Update to calculate as total_used_grant_kb / total_grant_kb. Fixes #3313. --- sp_BlitzCache.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sp_BlitzCache.sql b/sp_BlitzCache.sql index 11600c64f..0bf0920de 100644 --- a/sp_BlitzCache.sql +++ b/sp_BlitzCache.sql @@ -2324,7 +2324,7 @@ BEGIN max_grant_kb AS MaxGrantKB, min_used_grant_kb AS MinUsedGrantKB, max_used_grant_kb AS MaxUsedGrantKB, - CAST(ISNULL(NULLIF(( max_used_grant_kb * 1.00 ), 0) / NULLIF(min_grant_kb, 0), 0) * 100. AS MONEY) AS PercentMemoryGrantUsed, + CAST(ISNULL(NULLIF(( total_used_grant_kb * 1.00 ), 0) / NULLIF(total_grant_kb, 0), 0) * 100. AS MONEY) AS PercentMemoryGrantUsed, CAST(ISNULL(NULLIF(( total_grant_kb * 1. ), 0) / NULLIF(execution_count, 0), 0) AS MONEY) AS AvgMaxMemoryGrant, '; END; ELSE From 2f3e67365e2dae0a985f9ad6d6a73cf8138abf40 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Thu, 10 Aug 2023 14:48:37 -0700 Subject: [PATCH 432/662] Versions - add 2022 CU6, CU7 --- SqlServerVersions.sql | 2 ++ 1 file changed, 2 insertions(+) diff --git a/SqlServerVersions.sql b/SqlServerVersions.sql index 60c0b31e2..0b5f69ae5 100644 --- a/SqlServerVersions.sql +++ b/SqlServerVersions.sql @@ -41,6 +41,8 @@ DELETE FROM dbo.SqlServerVersions; INSERT INTO dbo.SqlServerVersions (MajorVersionNumber, MinorVersionNumber, Branch, [Url], ReleaseDate, MainstreamSupportEndDate, ExtendedSupportEndDate, MajorVersionName, MinorVersionName) VALUES + (16, 4065, 'CU7', 'https://support.microsoft.com/en-us/help/5028743', '2023-08-10', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 7'), + (16, 4055, 'CU6', 'https://support.microsoft.com/en-us/help/5027505', '2023-07-13', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 6'), (16, 4045, 'CU5', 'https://support.microsoft.com/en-us/help/5026806', '2023-06-15', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 5'), (16, 4035, 'CU4', 'https://support.microsoft.com/en-us/help/5026717', '2023-05-11', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 4'), (16, 4025, 'CU3', 'https://support.microsoft.com/en-us/help/5024396', '2023-04-13', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 3'), From d63983575f4efa4ed61775668dd78429beff0268 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Tue, 15 Aug 2023 10:38:30 -0700 Subject: [PATCH 433/662] Update sp_Blitz.sql Moved the db_perms table population out of the non-SA section, removed the filter for only system databases. --- sp_Blitz.sql | 45 +++++++++++++++++++++++---------------------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index 7e6eaae9e..548fde1d7 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -207,6 +207,19 @@ AS permission_name sysname ); + INSERT + @db_perms + ( + database_name, + permission_name + ) + SELECT + database_name = + DB_NAME(d.database_id), + fmp.permission_name + FROM sys.databases AS d + CROSS APPLY fn_my_permissions(d.name, 'DATABASE') AS fmp + WHERE fmp.permission_name = N'SELECT' /*Databases where we don't have read permissions*/ /* End of declarations for First Responder Kit consistency check:*/ ; @@ -223,7 +236,9 @@ AS ) ) = 0 BEGIN - SET @sa = 0; /*Setting this to 0 to skip DBCC COMMANDS*/ + IF @Debug IN (1, 2) RAISERROR('User not SA, checking permissions', 0, 1) WITH NOWAIT; + + SET @sa = 0; /*Setting this to 0 to skip DBCC COMMANDS*/ IF NOT EXISTS ( @@ -292,20 +307,6 @@ AS SET @SkipValidateLogins = 1; END; /*Need execute on sp_validatelogins*/ - INSERT - @db_perms - ( - database_name, - permission_name - ) - SELECT - database_name = - DB_NAME(d.database_id), - fmp.permission_name - FROM sys.databases AS d - CROSS APPLY fn_my_permissions(d.name, 'DATABASE') AS fmp - WHERE fmp.permission_name = N'SELECT' - AND d.database_id < 5; /*Databases where we don't have read permissions*/ END; SET @crlf = NCHAR(13) + NCHAR(10); @@ -469,43 +470,43 @@ AS INSERT #SkipChecks (DatabaseName, CheckID, ServerName) SELECT v.* - FROM (VALUES(NULL, NULL, 29)) AS v (DatabaseName, CheckID, ServerName) /*Looks for user tables in model*/ + FROM (VALUES(NULL, 29, NULL)) AS v (DatabaseName, CheckID, ServerName) /*Looks for user tables in model*/ WHERE NOT EXISTS (SELECT 1/0 FROM @db_perms AS dp WHERE dp.database_name = 'model'); INSERT #SkipChecks (DatabaseName, CheckID, ServerName) SELECT v.* - FROM (VALUES(NULL, NULL, 68)) AS v (DatabaseName, CheckID, ServerName) /*DBCC command*/ + FROM (VALUES(NULL, 68, NULL)) AS v (DatabaseName, CheckID, ServerName) /*DBCC command*/ WHERE @sa = 0; INSERT #SkipChecks (DatabaseName, CheckID, ServerName) SELECT v.* - FROM (VALUES(NULL, NULL, 69)) AS v (DatabaseName, CheckID, ServerName) /*DBCC command*/ + FROM (VALUES(NULL, 69, NULL)) AS v (DatabaseName, CheckID, ServerName) /*DBCC command*/ WHERE @sa = 0; INSERT #SkipChecks (DatabaseName, CheckID, ServerName) SELECT v.* - FROM (VALUES(NULL, NULL, 92)) AS v (DatabaseName, CheckID, ServerName) /*xp_fixeddrives*/ + FROM (VALUES(NULL, 92, NULL)) AS v (DatabaseName, CheckID, ServerName) /*xp_fixeddrives*/ WHERE @SkipXPFixedDrives = 1; INSERT #SkipChecks (DatabaseName, CheckID, ServerName) SELECT v.* - FROM (VALUES(NULL, NULL, 211)) AS v (DatabaseName, CheckID, ServerName) /*xp_regread*/ + FROM (VALUES(NULL, 211, NULL)) AS v (DatabaseName, CheckID, ServerName) /*xp_regread*/ WHERE @SkipXPRegRead = 1; INSERT #SkipChecks (DatabaseName, CheckID, ServerName) SELECT v.* - FROM (VALUES(NULL, NULL, 212)) AS v (DatabaseName, CheckID, ServerName) /*xp_regread*/ + FROM (VALUES(NULL, 212, NULL)) AS v (DatabaseName, CheckID, ServerName) /*xp_regread*/ WHERE @SkipXPCMDShell = 1; INSERT #SkipChecks (DatabaseName, CheckID, ServerName) SELECT v.* - FROM (VALUES(NULL, NULL, 2301)) AS v (DatabaseName, CheckID, ServerName) /*sp_validatelogins*/ + FROM (VALUES(NULL, 2301, NULL)) AS v (DatabaseName, CheckID, ServerName) /*sp_validatelogins*/ WHERE @SkipValidateLogins = 1 IF(OBJECT_ID('tempdb..#InvalidLogins') IS NOT NULL) From 1d86feedeedc93a06f2647b06edb01ddb79b6080 Mon Sep 17 00:00:00 2001 From: sqljared Date: Thu, 17 Aug 2023 21:19:21 -0400 Subject: [PATCH 434/662] PSPO update for sp_BlitzQueryStore Detects if the database_scoped_configurations for PSPO is present and enabled; adds logic to include variant queries when @StoredProcName filter is used. --- sp_BlitzQueryStore.sql | 53 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 49 insertions(+), 4 deletions(-) diff --git a/sp_BlitzQueryStore.sql b/sp_BlitzQueryStore.sql index 431711405..41c325e81 100644 --- a/sp_BlitzQueryStore.sql +++ b/sp_BlitzQueryStore.sql @@ -330,6 +330,28 @@ SET @msg = N'New query_store_runtime_stats columns ' + CASE @new_columns END; RAISERROR(@msg, 0, 1) WITH NOWAIT; +/* +This section determines if Parameter Sensitive Plan Optimization is enabled on SQL Server 2022+. +*/ + +RAISERROR('Checking for Parameter Sensitive Plan Optimization ', 0, 1) WITH NOWAIT; + +DECLARE @pspo_out BIT, + @pspo_enabled BIT, + @pspo_sql NVARCHAR(MAX) = N'SELECT @i_out = CONVERT(bit,dsc.value) + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.database_scoped_configurations dsc + WHERE dsc.name = ''PARAMETER_SENSITIVE_PLAN_OPTIMIZATION'';', + @pspo_params NVARCHAR(MAX) = N'@i_out INT OUTPUT'; + +EXEC sys.sp_executesql @pspo_sql, @pspo_params, @i_out = @pspo_out OUTPUT; + +SET @pspo_enabled = CASE WHEN @pspo_out = 1 THEN 1 ELSE 0 END; + +SET @msg = N'Parameter Sensitive Plan Optimization ' + CASE @pspo_enabled + WHEN 0 THEN N' not enabled, skipping.' + WHEN 1 THEN N' enabled, will analyze.' + END; +RAISERROR(@msg, 0, 1) WITH NOWAIT; /* These are the temp tables we use @@ -1033,10 +1055,33 @@ IF @MinimumExecutionCount IS NOT NULL --You care about stored proc names IF @StoredProcName IS NOT NULL - BEGIN - RAISERROR(N'Setting stored proc filter', 0, 1) WITH NOWAIT; - SET @sql_where += N' AND object_name(qsq.object_id, DB_ID(' + QUOTENAME(@DatabaseName, '''') + N')) = @sp_StoredProcName - '; + BEGIN + + IF (@pspo_enabled = 1) + BEGIN + RAISERROR(N'Setting stored proc filter, PSPO enabled', 0, 1) WITH NOWAIT; + /* If PSPO is enabled, the object_id for a variant query would be 0. To include it, we check whether the object_id = 0 query + is a variant query, and whether it's parent query belongs to @sp_StoredProcName. */ + SET @sql_where += N' AND (object_name(qsq.object_id, DB_ID(' + QUOTENAME(@DatabaseName, '''') + N')) = @sp_StoredProcName + OR (qsq.object_id = 0 + AND EXISTS( + SELECT 1 + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_variant vr + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query pqsq + ON pqsq.query_id = vr.parent_query_id + WHERE + vr.query_variant_query_id = qsq.query_id + AND object_name(pqsq.object_id, DB_ID(' + QUOTENAME(@DatabaseName, '''') + N')) = @sp_StoredProcName + ) + )) + '; + END + ELSE + BEGIN + RAISERROR(N'Setting stored proc filter', 0, 1) WITH NOWAIT; + SET @sql_where += N' AND object_name(qsq.object_id, DB_ID(' + QUOTENAME(@DatabaseName, '''') + N')) = @sp_StoredProcName + '; + END END; --I will always love you, but hopefully this query will eventually end From 3527872f27c59b4850f52a27b0750c6e8806ca49 Mon Sep 17 00:00:00 2001 From: Greg Dodd Date: Fri, 18 Aug 2023 12:19:51 +1000 Subject: [PATCH 435/662] Fixing output message to show correct parameter --- sp_DatabaseRestore.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sp_DatabaseRestore.sql b/sp_DatabaseRestore.sql index 06968cade..db32e4100 100755 --- a/sp_DatabaseRestore.sql +++ b/sp_DatabaseRestore.sql @@ -1342,7 +1342,7 @@ IF (@LogRecoveryOption = N'') IF (@StopAt IS NOT NULL) BEGIN - IF @Execute = 'Y' OR @Debug = 1 RAISERROR('@OnlyLogsAfter is NOT NULL, deleting from @FileList', 0, 1) WITH NOWAIT; + IF @Execute = 'Y' OR @Debug = 1 RAISERROR('@StopAt is NOT NULL, deleting from @FileList', 0, 1) WITH NOWAIT; IF LEN(@StopAt) <> 14 OR PATINDEX('%[^0-9]%', @StopAt) > 0 BEGIN From 6081f4d1451c86e7f61c325d7db8660258f92254 Mon Sep 17 00:00:00 2001 From: sqljared Date: Thu, 17 Aug 2023 22:20:59 -0400 Subject: [PATCH 436/662] Update proc_or_function_name for PSPO in sp_BlitzQueryStore If PSPO is enabled, attempt to populate proc_or_function_name based on a parent query. --- sp_BlitzQueryStore.sql | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/sp_BlitzQueryStore.sql b/sp_BlitzQueryStore.sql index 41c325e81..70993ce38 100644 --- a/sp_BlitzQueryStore.sql +++ b/sp_BlitzQueryStore.sql @@ -2315,6 +2315,30 @@ EXEC sys.sp_executesql @stmt = @sql_select, @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; +/*If PSPO is enabled, get procedure names for variant queries.*/ +IF (@pspo_enabled = 1) +BEGIN + DECLARE + @pspo_names NVARCHAR(MAX) = ''; + + SET @pspo_names = + 'UPDATE wm + SET + wm.proc_or_function_name = + QUOTENAME(object_schema_name(qsq.object_id, DB_ID(' + QUOTENAME(@DatabaseName, '''') + N'))) + ''.'' + + QUOTENAME(object_name(qsq.object_id, DB_ID(' + QUOTENAME(@DatabaseName, '''') + N'))) + FROM #working_metrics wm + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_variant AS vr + ON vr.query_variant_query_id = wm.query_id + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq + ON qsq.query_id = vr.parent_query_id + AND qsq.object_id > 0 + WHERE + wm.proc_or_function_name IS NULL;' + + EXEC sys.sp_executesql @pspo_names; +END; + /*This just helps us classify our queries*/ UPDATE #working_metrics From ef4670f0bc59b24c57db512b8e4ba8bfbf4455de Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Sun, 20 Aug 2023 08:08:34 -0700 Subject: [PATCH 437/662] 2023-08-20 release Bumping dates and version numbers, adding SQL 2019 CU22, generated install scripts. --- Install-All-Scripts.sql | 983 +++++++++++++++++++----- Install-Core-Blitz-No-Query-Store.sql | 951 ++++++++++++++++++----- Install-Core-Blitz-With-Query-Store.sql | 953 ++++++++++++++++++----- SqlServerVersions.sql | 1 + sp_AllNightLog.sql | 2 +- sp_AllNightLog_Setup.sql | 2 +- sp_Blitz.sql | 2 +- sp_BlitzAnalysis.sql | 2 +- sp_BlitzBackups.sql | 2 +- sp_BlitzCache.sql | 2 +- sp_BlitzFirst.sql | 2 +- sp_BlitzInMemoryOLTP.sql | 2 +- sp_BlitzIndex.sql | 2 +- sp_BlitzLock.sql | 2 +- sp_BlitzQueryStore.sql | 2 +- sp_BlitzWho.sql | 2 +- sp_DatabaseRestore.sql | 2 +- sp_ineachdb.sql | 2 +- 18 files changed, 2375 insertions(+), 541 deletions(-) diff --git a/Install-All-Scripts.sql b/Install-All-Scripts.sql index 054ff3f13..ca4313b15 100644 --- a/Install-All-Scripts.sql +++ b/Install-All-Scripts.sql @@ -38,7 +38,7 @@ SET STATISTICS XML OFF; BEGIN; -SELECT @Version = '8.15', @VersionDate = '20230613'; +SELECT @Version = '8.16', @VersionDate = '20230820'; IF(@VersionCheckMode = 1) BEGIN @@ -1375,7 +1375,7 @@ SET STATISTICS XML OFF; BEGIN; -SELECT @Version = '8.15', @VersionDate = '20230613'; +SELECT @Version = '8.16', @VersionDate = '20230820'; IF(@VersionCheckMode = 1) BEGIN @@ -2900,7 +2900,7 @@ AS SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.15', @VersionDate = '20230613'; + SELECT @Version = '8.16', @VersionDate = '20230820'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -3048,11 +3048,129 @@ AS ,@CurrentComponentVersionCheckModeOK BIT ,@canExitLoop BIT ,@frkIsConsistent BIT - ,@NeedToTurnNumericRoundabortBackOn BIT; + ,@NeedToTurnNumericRoundabortBackOn BIT + ,@sa bit = 1 + ,@SUSER_NAME sysname = SUSER_SNAME() + ,@SkipDBCC bit = 0 + ,@SkipTrace bit = 0 + ,@SkipXPRegRead bit = 0 + ,@SkipXPFixedDrives bit = 0 + ,@SkipXPCMDShell bit = 0 + ,@SkipMaster bit = 0 + ,@SkipMSDB bit = 0 + ,@SkipModel bit = 0 + ,@SkipTempDB bit = 0 + ,@SkipValidateLogins bit = 0; + + DECLARE + @db_perms table + ( + database_name sysname, + permission_name sysname + ); + + INSERT + @db_perms + ( + database_name, + permission_name + ) + SELECT + database_name = + DB_NAME(d.database_id), + fmp.permission_name + FROM sys.databases AS d + CROSS APPLY fn_my_permissions(d.name, 'DATABASE') AS fmp + WHERE fmp.permission_name = N'SELECT' /*Databases where we don't have read permissions*/ /* End of declarations for First Responder Kit consistency check:*/ ; + /*Starting permissions checks here, but only if we're not a sysadmin*/ + IF + ( + SELECT + sa = + ISNULL + ( + IS_SRVROLEMEMBER(N'sysadmin'), + 0 + ) + ) = 0 + BEGIN + IF @Debug IN (1, 2) RAISERROR('User not SA, checking permissions', 0, 1) WITH NOWAIT; + + SET @sa = 0; /*Setting this to 0 to skip DBCC COMMANDS*/ + + IF NOT EXISTS + ( + SELECT + 1/0 + FROM sys.fn_my_permissions(NULL, NULL) AS fmp + WHERE fmp.permission_name = N'VIEW SERVER STATE' + ) + BEGIN + RAISERROR('The user %s does not have VIEW SERVER STATE permissions.', 0, 11, @SUSER_NAME) WITH NOWAIT; + RETURN; + END; /*If we don't have this, we can't do anything at all.*/ + + IF NOT EXISTS + ( + SELECT + 1/0 + FROM fn_my_permissions(N'sys.traces', N'OBJECT') AS fmp + WHERE fmp.permission_name = N'ALTER' + ) + BEGIN + SET @SkipTrace = 1; + END; /*We need this permission to execute trace stuff, apparently*/ + + IF NOT EXISTS + ( + SELECT + 1/0 + FROM fn_my_permissions(N'xp_regread', N'OBJECT') AS fmp + WHERE fmp.permission_name = N'EXECUTE' + ) + BEGIN + SET @SkipXPRegRead = 1; + END; /*Need execute on xp_regread*/ + + IF NOT EXISTS + ( + SELECT + 1/0 + FROM fn_my_permissions(N'xp_fixeddrives', N'OBJECT') AS fmp + WHERE fmp.permission_name = N'EXECUTE' + ) + BEGIN + SET @SkipXPFixedDrives = 1; + END; /*Need execute on xp_fixeddrives*/ + + IF NOT EXISTS + ( + SELECT + 1/0 + FROM fn_my_permissions(N'xp_cmdshell', N'OBJECT') AS fmp + WHERE fmp.permission_name = N'EXECUTE' + ) + BEGIN + SET @SkipXPCMDShell = 1; + END; /*Need execute on xp_cmdshell*/ + + IF NOT EXISTS + ( + SELECT + 1/0 + FROM fn_my_permissions(N'sp_validatelogins', N'OBJECT') AS fmp + WHERE fmp.permission_name = N'EXECUTE' + ) + BEGIN + SET @SkipValidateLogins = 1; + END; /*Need execute on sp_validatelogins*/ + + END; + SET @crlf = NCHAR(13) + NCHAR(10); SET @ResultText = 'sp_Blitz Results: ' + @crlf; @@ -3193,6 +3311,66 @@ AS OR LOWER(d.name) IN ('dbatools', 'dbadmin', 'dbmaintenance')) OPTION(RECOMPILE); + /*Skip checks for database where we don't have read permissions*/ + INSERT INTO + #SkipChecks + ( + DatabaseName + ) + SELECT + DB_NAME(d.database_id) + FROM sys.databases AS d + WHERE NOT EXISTS + ( + SELECT + 1/0 + FROM @db_perms AS dp + WHERE dp.database_name = DB_NAME(d.database_id) + ); + + /*Skip individial checks where we don't have permissions*/ + INSERT #SkipChecks (DatabaseName, CheckID, ServerName) + SELECT + v.* + FROM (VALUES(NULL, 29, NULL)) AS v (DatabaseName, CheckID, ServerName) /*Looks for user tables in model*/ + WHERE NOT EXISTS (SELECT 1/0 FROM @db_perms AS dp WHERE dp.database_name = 'model'); + + INSERT #SkipChecks (DatabaseName, CheckID, ServerName) + SELECT + v.* + FROM (VALUES(NULL, 68, NULL)) AS v (DatabaseName, CheckID, ServerName) /*DBCC command*/ + WHERE @sa = 0; + + INSERT #SkipChecks (DatabaseName, CheckID, ServerName) + SELECT + v.* + FROM (VALUES(NULL, 69, NULL)) AS v (DatabaseName, CheckID, ServerName) /*DBCC command*/ + WHERE @sa = 0; + + INSERT #SkipChecks (DatabaseName, CheckID, ServerName) + SELECT + v.* + FROM (VALUES(NULL, 92, NULL)) AS v (DatabaseName, CheckID, ServerName) /*xp_fixeddrives*/ + WHERE @SkipXPFixedDrives = 1; + + INSERT #SkipChecks (DatabaseName, CheckID, ServerName) + SELECT + v.* + FROM (VALUES(NULL, 211, NULL)) AS v (DatabaseName, CheckID, ServerName) /*xp_regread*/ + WHERE @SkipXPRegRead = 1; + + INSERT #SkipChecks (DatabaseName, CheckID, ServerName) + SELECT + v.* + FROM (VALUES(NULL, 212, NULL)) AS v (DatabaseName, CheckID, ServerName) /*xp_regread*/ + WHERE @SkipXPCMDShell = 1; + + INSERT #SkipChecks (DatabaseName, CheckID, ServerName) + SELECT + v.* + FROM (VALUES(NULL, 2301, NULL)) AS v (DatabaseName, CheckID, ServerName) /*sp_validatelogins*/ + WHERE @SkipValidateLogins = 1 + IF(OBJECT_ID('tempdb..#InvalidLogins') IS NOT NULL) BEGIN EXEC sp_executesql N'DROP TABLE #InvalidLogins;'; @@ -3234,7 +3412,8 @@ AS SELECT @IsWindowsOperatingSystem = 1 ; END; - IF NOT EXISTS ( SELECT 1 + + IF NOT EXISTS ( SELECT 1 FROM #SkipChecks WHERE DatabaseName IS NULL AND CheckID = 106 ) AND (select convert(int,value_in_use) from sys.configurations where name = 'default trace enabled' ) = 1 @@ -7020,53 +7199,56 @@ AS /* First, let's check that there aren't any issues with the trace files */ BEGIN TRY - - INSERT INTO #fnTraceGettable - ( TextData , - DatabaseName , - EventClass , - Severity , - StartTime , - EndTime , - Duration , - NTUserName , - NTDomainName , - HostName , - ApplicationName , - LoginName , - DBUserName - ) - SELECT TOP 20000 - CONVERT(NVARCHAR(4000),t.TextData) , - t.DatabaseName , - t.EventClass , - t.Severity , - t.StartTime , - t.EndTime , - t.Duration , - t.NTUserName , - t.NTDomainName , - t.HostName , - t.ApplicationName , - t.LoginName , - t.DBUserName - FROM sys.fn_trace_gettable(@base_tracefilename, DEFAULT) t - WHERE - ( - t.EventClass = 22 - AND t.Severity >= 17 - AND t.StartTime > DATEADD(dd, -30, GETDATE()) - ) - OR - ( - t.EventClass IN (92, 93) - AND t.StartTime > DATEADD(dd, -30, GETDATE()) - AND t.Duration > 15000000 - ) - OR - ( - t.EventClass IN (94, 95, 116) - ) + + IF @SkipTrace = 0 + BEGIN + INSERT INTO #fnTraceGettable + ( TextData , + DatabaseName , + EventClass , + Severity , + StartTime , + EndTime , + Duration , + NTUserName , + NTDomainName , + HostName , + ApplicationName , + LoginName , + DBUserName + ) + SELECT TOP 20000 + CONVERT(NVARCHAR(4000),t.TextData) , + t.DatabaseName , + t.EventClass , + t.Severity , + t.StartTime , + t.EndTime , + t.Duration , + t.NTUserName , + t.NTDomainName , + t.HostName , + t.ApplicationName , + t.LoginName , + t.DBUserName + FROM sys.fn_trace_gettable(@base_tracefilename, DEFAULT) t + WHERE + ( + t.EventClass = 22 + AND t.Severity >= 17 + AND t.StartTime > DATEADD(dd, -30, GETDATE()) + ) + OR + ( + t.EventClass IN (92, 93) + AND t.StartTime > DATEADD(dd, -30, GETDATE()) + AND t.Duration > 15000000 + ) + OR + ( + t.EventClass IN (94, 95, 116) + ) + END; SET @TraceFileIssue = 0 @@ -9441,10 +9623,10 @@ IF @ProductVersionMajor >= 10 DatabaseName FROM #SkipChecks WHERE CheckID IS NULL OR CheckID = 19) - AND is_published = 1 + AND (is_published = 1 OR is_subscribed = 1 OR is_merge_published = 1 - OR is_distributor = 1; + OR is_distributor = 1); /* Method B: check subscribers for MSreplication_objects tables */ EXEC dbo.sp_MSforeachdb 'USE [?]; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; @@ -12599,7 +12781,7 @@ AS SET NOCOUNT ON; SET STATISTICS XML OFF; -SELECT @Version = '8.15', @VersionDate = '20230613'; +SELECT @Version = '8.16', @VersionDate = '20230820'; IF(@VersionCheckMode = 1) BEGIN @@ -13477,7 +13659,7 @@ AS SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.15', @VersionDate = '20230613'; + SELECT @Version = '8.16', @VersionDate = '20230820'; IF(@VersionCheckMode = 1) BEGIN @@ -15259,7 +15441,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.15', @VersionDate = '20230613'; +SELECT @Version = '8.16', @VersionDate = '20230820'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -16557,6 +16739,7 @@ BEGIN RAISERROR('Checking for Read intent databases to exclude',0,0) WITH NOWAIT; EXEC('INSERT INTO #ReadableDBs (database_id) SELECT DBs.database_id FROM sys.databases DBs INNER JOIN sys.availability_replicas Replicas ON DBs.replica_id = Replicas.replica_id WHERE replica_server_name NOT IN (SELECT DISTINCT primary_replica FROM sys.dm_hadr_availability_group_states States) AND Replicas.secondary_role_allow_connections_desc = ''READ_ONLY'' AND replica_server_name = @@SERVERNAME OPTION (RECOMPILE);'); + EXEC('INSERT INTO #ReadableDBs VALUES (32767) ;'); -- Exclude internal resource database as well END RAISERROR(N'Checking plan cache age', 0, 1) WITH NOWAIT; @@ -16593,10 +16776,10 @@ WITH total_plans AS SELECT COUNT_BIG(qs.query_plan_hash) AS duplicate_plan_hashes FROM sys.dm_exec_query_stats qs - LEFT JOIN sys.dm_exec_procedure_stats ps - ON qs.sql_handle = ps.sql_handle + LEFT JOIN sys.dm_exec_procedure_stats ps ON qs.plan_handle = ps.plan_handle CROSS APPLY sys.dm_exec_plan_attributes(qs.plan_handle) pa WHERE pa.attribute = N'dbid' + AND pa.value <> 32767 /*Omit Resource database-based queries, we're not going to "fix" them no matter what. Addresses #3314*/ AND qs.query_plan_hash <> 0x0000000000000000 GROUP BY /* qs.query_plan_hash, BGO 20210524 commenting this out to fix #2909 */ @@ -17301,7 +17484,7 @@ BEGIN max_grant_kb AS MaxGrantKB, min_used_grant_kb AS MinUsedGrantKB, max_used_grant_kb AS MaxUsedGrantKB, - CAST(ISNULL(NULLIF(( max_used_grant_kb * 1.00 ), 0) / NULLIF(min_grant_kb, 0), 0) * 100. AS MONEY) AS PercentMemoryGrantUsed, + CAST(ISNULL(NULLIF(( total_used_grant_kb * 1.00 ), 0) / NULLIF(total_grant_kb, 0), 0) * 100. AS MONEY) AS PercentMemoryGrantUsed, CAST(ISNULL(NULLIF(( total_grant_kb * 1. ), 0) / NULLIF(execution_count, 0), 0) AS MONEY) AS AvgMaxMemoryGrant, '; END; ELSE @@ -22577,7 +22760,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.15', @VersionDate = '20230613'; +SELECT @Version = '8.16', @VersionDate = '20230820'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -25487,7 +25670,8 @@ BEGIN + CASE WHEN @ShowPartitionRanges = 1 THEN N' COALESCE(range_start_op + '' '' + range_start + '' '', '''') + COALESCE(range_end_op + '' '' + range_end, '''') AS partition_range, ' ELSE N' ' END + N' row_group_id, total_rows, deleted_rows, ' + @ColumnList - + CASE WHEN @ShowPartitionRanges = 1 THEN N' + + CASE WHEN @ShowPartitionRanges = 1 THEN N' , + state_desc, trim_reason_desc, transition_to_compressed_state_desc, has_vertipaq_optimization FROM ( SELECT column_name, partition_number, row_group_id, total_rows, deleted_rows, details, range_start_op, @@ -25497,10 +25681,12 @@ BEGIN range_end_op, CASE WHEN format_type IS NULL THEN CAST(range_end_value AS NVARCHAR(4000)) - ELSE CONVERT(NVARCHAR(4000), range_end_value, format_type) END range_end' ELSE N' ' END + N' + ELSE CONVERT(NVARCHAR(4000), range_end_value, format_type) END range_end' ELSE N' ' END + N', + state_desc, trim_reason_desc, transition_to_compressed_state_desc, has_vertipaq_optimization FROM ( SELECT c.name AS column_name, p.partition_number, rg.row_group_id, rg.total_rows, rg.deleted_rows, - details = CAST(seg.min_data_id AS VARCHAR(20)) + '' to '' + CAST(seg.max_data_id AS VARCHAR(20)) + '', '' + CAST(CAST((seg.on_disk_size / 1024.0 / 1024) AS DECIMAL(18,0)) AS VARCHAR(20)) + '' MB''' + phys.state_desc, phys.trim_reason_desc, phys.transition_to_compressed_state_desc, phys.has_vertipaq_optimization, + details = CAST(seg.min_data_id AS VARCHAR(20)) + '' to '' + CAST(seg.max_data_id AS VARCHAR(20)) + '', '' + CAST(CAST(((COALESCE(d.on_disk_size,0) + COALESCE(seg.on_disk_size,0)) / 1024.0 / 1024) AS DECIMAL(18,0)) AS VARCHAR(20)) + '' MB''' + CASE WHEN @ShowPartitionRanges = 1 THEN N', CASE WHEN pp.system_type_id IN (40, 41, 42, 43, 58, 61) THEN 126 @@ -25514,7 +25700,8 @@ BEGIN FROM ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_row_groups rg INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns c ON rg.object_id = c.object_id INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partitions p ON rg.object_id = p.object_id AND rg.partition_number = p.partition_number - INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.index_columns ic on ic.column_id = c.column_id AND ic.object_id = c.object_id AND ic.index_id = p.index_id ' + CASE WHEN @ShowPartitionRanges = 1 THEN N' + INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.index_columns ic on ic.column_id = c.column_id AND ic.object_id = c.object_id AND ic.index_id = p.index_id + LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_column_store_row_group_physical_stats phys ON rg.row_group_id = phys.row_group_id AND rg.object_id = phys.object_id AND rg.partition_number = phys.partition_number AND p.index_id = phys.index_id ' + CASE WHEN @ShowPartitionRanges = 1 THEN N' LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.indexes i ON i.object_id = rg.object_id AND i.index_id = rg.index_id LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partition_schemes ps ON ps.data_space_id = i.data_space_id LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partition_functions pf ON pf.function_id = ps.function_id @@ -25522,6 +25709,7 @@ BEGIN LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partition_range_values prvs ON prvs.function_id = pf.function_id AND prvs.boundary_id = p.partition_number - 1 LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partition_range_values prve ON prve.function_id = pf.function_id AND prve.boundary_id = p.partition_number ' ELSE N' ' END + N' LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_segments seg ON p.partition_id = seg.partition_id AND ic.index_column_id = seg.column_id AND rg.row_group_id = seg.segment_id + LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_dictionaries d ON p.hobt_id = d.hobt_id AND c.column_id = d.column_id AND seg.secondary_dictionary_id = d.dictionary_id WHERE rg.object_id = @ObjectID AND rg.state IN (1, 2, 3) AND c.name IN ( ' + @ColumnListWithApostrophes + N')' @@ -25559,6 +25747,9 @@ BEGIN RAISERROR(N'Done visualizing columnstore index contents.', 0,1) WITH NOWAIT; END + IF @ShowColumnstoreOnly = 1 + RETURN; + END; /* IF @TableName IS NOT NULL */ @@ -28754,7 +28945,7 @@ BEGIN SET NOCOUNT, XACT_ABORT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.15', @VersionDate = '20230613'; + SELECT @Version = '8.16', @VersionDate = '20230820'; IF @VersionCheckMode = 1 BEGIN @@ -28898,7 +29089,11 @@ BEGIN @TargetSessionId int = 0, @FileName nvarchar(4000) = N'', @inputbuf_bom nvarchar(1) = CONVERT(nvarchar(1), 0x0a00, 0), - @deadlock_result nvarchar(MAX) = N''; + @deadlock_result nvarchar(MAX) = N'', + @StartDateOriginal datetime = @StartDate, + @EndDateOriginal datetime = @EndDate, + @StartDateUTC datetime, + @EndDateUTC datetime; /*Temporary objects used in the procedure*/ DECLARE @@ -28938,15 +29133,17 @@ BEGIN database_name nvarchar(256), object_name nvarchar(1000), finding_group nvarchar(100), - finding nvarchar(4000) + finding nvarchar(4000), + sort_order bigint ); /*Set these to some sane defaults if NULLs are passed in*/ /*Normally I'd hate this, but we RECOMPILE everything*/ + SELECT @StartDate = - CASE - WHEN @StartDate IS NULL + CASE + WHEN @StartDate IS NULL THEN DATEADD ( @@ -28957,18 +29154,25 @@ BEGIN SYSDATETIME(), GETUTCDATE() ), - ISNULL + DATEADD ( - @StartDate, - DATEADD - ( - DAY, - -7, - SYSDATETIME() - ) + DAY, + -7, + SYSDATETIME() ) ) - ELSE @StartDate + ELSE + DATEADD + ( + MINUTE, + DATEDIFF + ( + MINUTE, + SYSDATETIME(), + GETUTCDATE() + ), + @StartDate + ) END, @EndDate = CASE @@ -28983,15 +29187,62 @@ BEGIN SYSDATETIME(), GETUTCDATE() ), - ISNULL + SYSDATETIME() + ) + ELSE + DATEADD + ( + MINUTE, + DATEDIFF ( - @EndDate, - SYSDATETIME() - ) + MINUTE, + SYSDATETIME(), + GETUTCDATE() + ), + @EndDate ) - ELSE @EndDate END; + SELECT + @StartDateUTC = @StartDate, + @EndDateUTC = @EndDate; + + IF @Azure = 0 + BEGIN + IF NOT EXISTS + ( + SELECT + 1/0 + FROM sys.server_event_sessions AS ses + JOIN sys.dm_xe_sessions AS dxs + ON dxs.name = ses.name + WHERE ses.name = @EventSessionName + AND dxs.create_time IS NOT NULL + ) + BEGIN + RAISERROR('A session with the name %s does not exist or is not currently active.', 11, 1, @EventSessionName) WITH NOWAIT; + RETURN; + END; + END; + + IF @Azure = 1 + BEGIN + IF NOT EXISTS + ( + SELECT + 1/0 + FROM sys.database_event_sessions AS ses + JOIN sys.dm_xe_database_sessions AS dxs + ON dxs.name = ses.name + WHERE ses.name = @EventSessionName + AND dxs.create_time IS NOT NULL + ) + BEGIN + RAISERROR('A session with the name %s does not exist or is not currently active.', 11, 1, @EventSessionName) WITH NOWAIT; + RETURN; + END; + END; + IF @OutputDatabaseName IS NOT NULL BEGIN /*IF databaseName is set, do some sanity checks and put [] around def.*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); @@ -29574,8 +29825,7 @@ BEGIN OR e.x.exist('@name[ .= "database_xml_deadlock_report"]') = 1 OR e.x.exist('@name[ .= "xml_deadlock_report_filtered"]') = 1 ) - AND e.x.exist('@timestamp[. >= sql:variable("@StartDate")]') = 1 - AND e.x.exist('@timestamp[. < sql:variable("@EndDate")]') = 1 + AND e.x.exist('@timestamp[. >= sql:variable("@StartDate") and .< sql:variable("@EndDate")]') = 1 OPTION(RECOMPILE); SET @d = CONVERT(varchar(40), GETDATE(), 109); @@ -29592,9 +29842,9 @@ BEGIN SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Inserting to #deadlock_data for event file data', 0, 1) WITH NOWAIT; - IF @Debug = 1 BEGIN SET STATISTICS XML ON; END; + IF @Debug = 1 BEGIN SET STATISTICS XML ON; END; - INSERT + INSERT #deadlock_data WITH(TABLOCKX) ( deadlock_xml @@ -29612,13 +29862,12 @@ BEGIN OR e.x.exist('@name[ .= "database_xml_deadlock_report"]') = 1 OR e.x.exist('@name[ .= "xml_deadlock_report_filtered"]') = 1 ) - AND e.x.exist('@timestamp[. >= sql:variable("@StartDate")]') = 1 - AND e.x.exist('@timestamp[. < sql:variable("@EndDate")]') = 1 + AND e.x.exist('@timestamp[. >= sql:variable("@StartDate") and .< sql:variable("@EndDate")]') = 1 OPTION(RECOMPILE); - IF @Debug = 1 BEGIN SET STATISTICS XML OFF; END; + IF @Debug = 1 BEGIN SET STATISTICS XML OFF; END; - SET @d = CONVERT(varchar(40), GETDATE(), 109); + SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; END; @@ -29649,8 +29898,7 @@ BEGIN ) AS xml CROSS APPLY xml.deadlock_xml.nodes('/event') AS e(x) WHERE 1 = 1 - AND e.x.exist('@timestamp[. >= sql:variable("@StartDate")]') = 1 - AND e.x.exist('@timestamp[. < sql:variable("@EndDate")]') = 1 + AND e.x.exist('@timestamp[. >= sql:variable("@StartDate") and .< sql:variable("@EndDate")]') = 1 OPTION(RECOMPILE); INSERT @@ -29777,7 +30025,12 @@ BEGIN DATEADD ( MINUTE, - DATEDIFF(MINUTE, GETUTCDATE(), SYSDATETIME()), + DATEDIFF + ( + MINUTE, + GETUTCDATE(), + SYSDATETIME() + ), dd.event_date ), dd.victim_id, @@ -29835,6 +30088,7 @@ BEGIN FROM #deadlock_process AS dp CROSS APPLY dp.process_xml.nodes('//executionStack/frame') AS ca(dp) WHERE (ca.dp.exist('@procname[. = sql:variable("@StoredProcName")]') = 1 OR @StoredProcName IS NULL) + AND ca.dp.exist('@sqlhandle[ .= "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"]') = 0 OPTION(RECOMPILE); SET @d = CONVERT(varchar(40), GETDATE(), 109); @@ -29907,7 +30161,7 @@ BEGIN waiter_mode = w.l.value('@mode', 'nvarchar(256)'), owner_id = o.l.value('@id', 'nvarchar(256)'), owner_mode = o.l.value('@mode', 'nvarchar(256)'), - lock_type = CAST(N'OBJECT' AS NVARCHAR(100)) + lock_type = CAST(N'OBJECT' AS nvarchar(100)) INTO #deadlock_owner_waiter FROM ( @@ -30303,13 +30557,19 @@ BEGIN 32 ), step_id = - SUBSTRING - ( - dp.client_app, - CHARINDEX(N': Step ', dp.client_app) + LEN(N': Step '), - CHARINDEX(N')', dp.client_app, CHARINDEX(N': Step ', dp.client_app)) - - (CHARINDEX(N': Step ', dp.client_app) + LEN(N': Step ')) - ) + CASE + WHEN CHARINDEX(N': Step ', dp.client_app) > 0 + AND CHARINDEX(N')', dp.client_app, CHARINDEX(N': Step ', dp.client_app)) > 0 + THEN + SUBSTRING + ( + dp.client_app, + CHARINDEX(N': Step ', dp.client_app) + LEN(N': Step '), + CHARINDEX(N')', dp.client_app, CHARINDEX(N': Step ', dp.client_app)) - + (CHARINDEX(N': Step ', dp.client_app) + LEN(N': Step ')) + ) + ELSE dp.client_app + END FROM #deadlock_process AS dp WHERE dp.client_app LIKE N'SQLAgent - %' AND dp.client_app <> N'SQLAgent - Initial Boot Probe' @@ -30459,6 +30719,14 @@ BEGIN /*Begin checks based on parsed values*/ + /* + First, revert these back since we already converted the event data to local time, + and searches will break if we use the times converted over to UTC for the event data + */ + SELECT + @StartDate = @StartDateOriginal, + @EndDate = @EndDateOriginal; + /*Check 1 is deadlocks by database*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Check 1 database deadlocks %s', 0, 1, @d) WITH NOWAIT; @@ -30470,7 +30738,8 @@ BEGIN database_name, object_name, finding_group, - finding + finding, + sort_order ) SELECT check_id = 1, @@ -30484,7 +30753,10 @@ BEGIN nvarchar(20), COUNT_BIG(DISTINCT dp.event_date) ) + - N' deadlocks.' + N' deadlocks.', + sort_order = + ROW_NUMBER() + OVER (ORDER BY COUNT_BIG(DISTINCT dp.event_date) DESC) FROM #deadlock_process AS dp WHERE 1 = 1 AND (dp.database_name = @DatabaseName OR @DatabaseName IS NULL) @@ -30509,7 +30781,8 @@ BEGIN database_name, object_name, finding_group, - finding + finding, + sort_order ) SELECT check_id = 2, @@ -30524,7 +30797,10 @@ BEGIN nvarchar(20), COUNT_BIG(DISTINCT dow.event_date) ) + - N' deadlock(s) between read queries and modification queries.' + N' deadlock(s) between read queries and modification queries.', + sort_order = + ROW_NUMBER() + OVER (ORDER BY COUNT_BIG(DISTINCT dow.event_date) DESC) FROM #deadlock_owner_waiter AS dow WHERE 1 = 1 AND dow.lock_mode IN @@ -30563,7 +30839,8 @@ BEGIN database_name, object_name, finding_group, - finding + finding, + sort_order ) SELECT check_id = 3, @@ -30582,7 +30859,10 @@ BEGIN nvarchar(20), COUNT_BIG(DISTINCT dow.event_date) ) + - N' deadlock(s).' + N' deadlock(s).', + sort_order = + ROW_NUMBER() + OVER (ORDER BY COUNT_BIG(DISTINCT dow.event_date) DESC) FROM #deadlock_owner_waiter AS dow WHERE 1 = 1 AND (dow.database_id = @DatabaseId OR @DatabaseName IS NULL) @@ -30607,7 +30887,8 @@ BEGIN database_name, object_name, finding_group, - finding + finding, + sort_order ) SELECT check_id = 3, @@ -30621,7 +30902,10 @@ BEGIN nvarchar(20), COUNT_BIG(DISTINCT dow.event_date) ) + - N' deadlock(s).' + N' deadlock(s).', + sort_order = + ROW_NUMBER() + OVER (ORDER BY COUNT_BIG(DISTINCT dow.event_date) DESC) FROM #deadlock_owner_waiter AS dow WHERE 1 = 1 AND (dow.database_id = @DatabaseId OR @DatabaseName IS NULL) @@ -30652,7 +30936,8 @@ BEGIN database_name, object_name, finding_group, - finding + finding, + sort_order ) SELECT check_id = 3, @@ -30666,7 +30951,10 @@ BEGIN nvarchar(20), COUNT_BIG(DISTINCT dow.event_date) ) + - N' deadlock(s).' + N' deadlock(s).', + sort_order = + ROW_NUMBER() + OVER (ORDER BY COUNT_BIG(DISTINCT dow.event_date) DESC) FROM #deadlock_owner_waiter AS dow WHERE 1 = 1 AND (dow.database_id = @DatabaseId OR @DatabaseName IS NULL) @@ -30696,7 +30984,8 @@ BEGIN database_name, object_name, finding_group, - finding + finding, + sort_order ) SELECT check_id = 4, @@ -30711,7 +31000,10 @@ BEGIN nvarchar(20), COUNT_BIG(DISTINCT dp.event_date) ) + - N' instances of Serializable deadlocks.' + N' instances of Serializable deadlocks.', + sort_order = + ROW_NUMBER() + OVER (ORDER BY COUNT_BIG(DISTINCT dp.event_date) DESC) FROM #deadlock_process AS dp WHERE dp.isolation_level LIKE N'serializable%' AND (dp.database_name = @DatabaseName OR @DatabaseName IS NULL) @@ -30736,7 +31028,8 @@ BEGIN database_name, object_name, finding_group, - finding + finding, + sort_order ) SELECT check_id = 5, @@ -30750,7 +31043,10 @@ BEGIN nvarchar(20), COUNT_BIG(DISTINCT dp.event_date) ) + - N' instances of Repeatable Read deadlocks.' + N' instances of Repeatable Read deadlocks.', + sort_order = + ROW_NUMBER() + OVER (ORDER BY COUNT_BIG(DISTINCT dp.event_date) DESC) FROM #deadlock_process AS dp WHERE dp.isolation_level LIKE N'repeatable%' AND (dp.database_name = @DatabaseName OR @DatabaseName IS NULL) @@ -30775,7 +31071,8 @@ BEGIN database_name, object_name, finding_group, - finding + finding, + sort_order ) SELECT check_id = 6, @@ -30808,7 +31105,10 @@ BEGIN dp.host_name, N'UNKNOWN' ) + - N'.' + N'.', + sort_order = + ROW_NUMBER() + OVER (ORDER BY COUNT_BIG(DISTINCT dp.event_date) DESC) FROM #deadlock_process AS dp WHERE 1 = 1 AND (dp.database_name = @DatabaseName OR @DatabaseName IS NULL) @@ -30889,7 +31189,8 @@ BEGIN database_name, object_name, finding_group, - finding + finding, + sort_order ) SELECT check_id = 7, @@ -30916,7 +31217,10 @@ BEGIN 1, 1, N'' - ) + N' locks.' + ) + N' locks.', + sort_order = + ROW_NUMBER() + OVER (ORDER BY CONVERT(bigint, lt.lock_count) DESC) FROM lock_types AS lt OPTION(RECOMPILE); @@ -30930,9 +31234,9 @@ BEGIN deadlock_stack AS ( SELECT DISTINCT - ds.id, - ds.proc_name, - ds.event_date, + ds.id, + ds.event_date, + ds.proc_name, database_name = PARSENAME(ds.proc_name, 3), schema_name = @@ -30966,8 +31270,8 @@ BEGIN PARSENAME(ds.proc_name, 3), PARSENAME(ds.proc_name, 2), PARSENAME(ds.proc_name, 1), - ds.id, ds.proc_name, + ds.id, ds.event_date ) INSERT @@ -30999,6 +31303,7 @@ BEGIN AND (dow.event_date >= @StartDate OR @StartDate IS NULL) AND (dow.event_date < @EndDate OR @EndDate IS NULL) AND (dow.object_name = @StoredProcName OR @StoredProcName IS NULL) + AND ds.proc_name NOT LIKE 'Unknown%' OPTION(RECOMPILE); RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; @@ -31071,7 +31376,8 @@ BEGIN database_name, object_name, finding_group, - finding + finding, + sort_order ) SELECT check_id = 9, @@ -31090,7 +31396,10 @@ BEGIN nvarchar(10), COUNT_BIG(DISTINCT ds.id) ) + - N' deadlocks.' + N' deadlocks.', + sort_order = + ROW_NUMBER() + OVER (ORDER BY COUNT_BIG(DISTINCT ds.id) DESC) FROM #deadlock_stack AS ds JOIN #deadlock_process AS dp ON dp.id = ds.id @@ -31189,19 +31498,19 @@ BEGIN ) ), wait_time_hms = - /*the more wait time you rack up the less accurate this gets, + /*the more wait time you rack up the less accurate this gets, it's either that or erroring out*/ - CASE - WHEN + CASE + WHEN SUM ( CONVERT ( - bigint, + bigint, dp.wait_time ) )/1000 > 2147483647 - THEN + THEN CONVERT ( nvarchar(30), @@ -31214,7 +31523,7 @@ BEGIN ( CONVERT ( - bigint, + bigint, dp.wait_time ) ) @@ -31225,16 +31534,16 @@ BEGIN ), 14 ) - WHEN + WHEN SUM ( CONVERT ( - bigint, + bigint, dp.wait_time ) ) BETWEEN 2147483648 AND 2147483647000 - THEN + THEN CONVERT ( nvarchar(30), @@ -31247,7 +31556,7 @@ BEGIN ( CONVERT ( - bigint, + bigint, dp.wait_time ) ) @@ -31279,7 +31588,9 @@ BEGIN ), 14 ) - END + END, + total_waits = + SUM(CONVERT(bigint, dp.wait_time)) FROM #deadlock_owner_waiter AS dow JOIN #deadlock_process AS dp ON (dp.id = dow.owner_id @@ -31304,7 +31615,8 @@ BEGIN database_name, object_name, finding_group, - finding + finding, + sort_order ) SELECT check_id = 11, @@ -31325,7 +31637,10 @@ BEGIN cs.wait_time_hms, 14 ) + - N' [dd hh:mm:ss:ms] of deadlock wait time.' + N' [dd hh:mm:ss:ms] of deadlock wait time.', + sort_order = + ROW_NUMBER() + OVER (ORDER BY cs.total_waits DESC) FROM chopsuey AS cs WHERE cs.object_name IS NOT NULL OPTION(RECOMPILE); @@ -31373,7 +31688,8 @@ BEGIN database_name, object_name, finding_group, - finding + finding, + sort_order ) SELECT check_id = 12, @@ -31396,19 +31712,19 @@ BEGIN ) ) + N' ' + - /*the more wait time you rack up the less accurate this gets, + /*the more wait time you rack up the less accurate this gets, it's either that or erroring out*/ - CASE - WHEN + CASE + WHEN SUM ( CONVERT ( - bigint, + bigint, wt.total_wait_time_ms ) )/1000 > 2147483647 - THEN + THEN CONVERT ( nvarchar(30), @@ -31421,7 +31737,7 @@ BEGIN ( CONVERT ( - bigint, + bigint, wt.total_wait_time_ms ) ) @@ -31432,16 +31748,16 @@ BEGIN ), 14 ) - WHEN + WHEN SUM ( CONVERT ( - bigint, + bigint, wt.total_wait_time_ms ) ) BETWEEN 2147483648 AND 2147483647000 - THEN + THEN CONVERT ( nvarchar(30), @@ -31454,7 +31770,7 @@ BEGIN ( CONVERT ( - bigint, + bigint, wt.total_wait_time_ms ) ) @@ -31486,7 +31802,10 @@ BEGIN ), 14 ) END + - N' [dd hh:mm:ss:ms] of deadlock wait time.' + N' [dd hh:mm:ss:ms] of deadlock wait time.', + sort_order = + ROW_NUMBER() + OVER (ORDER BY SUM(CONVERT(bigint, wt.total_wait_time_ms)) DESC) FROM wait_time AS wt GROUP BY wt.database_name @@ -31505,7 +31824,8 @@ BEGIN database_name, object_name, finding_group, - finding + finding, + sort_order ) SELECT check_id = 13, @@ -31520,7 +31840,10 @@ BEGIN finding = N'There have been ' + RTRIM(COUNT_BIG(DISTINCT aj.event_date)) + - N' deadlocks from this Agent Job and Step.' + N' deadlocks from this Agent Job and Step.', + sort_order = + ROW_NUMBER() + OVER (ORDER BY COUNT_BIG(DISTINCT aj.event_date) DESC) FROM #agent_job AS aj GROUP BY DB_NAME(aj.database_id), @@ -32064,7 +32387,8 @@ BEGIN d.waiter_waiting_to_close, /*end parallel deadlock columns*/ d.deadlock_graph, - d.is_victim + d.is_victim, + d.id INTO #deadlock_results FROM #deadlocks AS d; @@ -32267,26 +32591,202 @@ BEGIN DROP SYNONYM DeadlockFindings; /*done with inserting.*/ END; ELSE /*Output to database is not set output to client app*/ - BEGIN - SET @d = CONVERT(varchar(40), GETDATE(), 109); + BEGIN + SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Results to client %s', 0, 1, @d) WITH NOWAIT; - + IF @Debug = 1 BEGIN SET STATISTICS XML ON; END; - + EXEC sys.sp_executesql @deadlock_result; - + IF @Debug = 1 BEGIN SET STATISTICS XML OFF; PRINT @deadlock_result; END; - + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - + + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Getting available execution plans for deadlocks %s', 0, 1, @d) WITH NOWAIT; + + SELECT DISTINCT + available_plans = + 'available_plans', + ds.proc_name, + sql_handle = + CONVERT(varbinary(64), ds.sql_handle, 1), + dow.database_name, + dow.database_id, + dow.object_name, + query_xml = + TRY_CAST(dr.query_xml AS nvarchar(MAX)) + INTO #available_plans + FROM #deadlock_stack AS ds + JOIN #deadlock_owner_waiter AS dow + ON dow.owner_id = ds.id + AND dow.event_date = ds.event_date + JOIN #deadlock_results AS dr + ON dr.id = ds.id + AND dr.event_date = ds.event_date + OPTION(RECOMPILE); + + SELECT + deqs.sql_handle, + deqs.plan_handle, + deqs.statement_start_offset, + deqs.statement_end_offset, + deqs.creation_time, + deqs.last_execution_time, + deqs.execution_count, + total_worker_time_ms = + deqs.total_worker_time / 1000., + avg_worker_time_ms = + CONVERT(decimal(38, 6), deqs.total_worker_time / 1000. / deqs.execution_count), + total_elapsed_time_ms = + deqs.total_elapsed_time / 1000., + avg_elapsed_time = + CONVERT(decimal(38, 6), deqs.total_elapsed_time / 1000. / deqs.execution_count), + executions_per_second = + ISNULL + ( + deqs.execution_count / + NULLIF + ( + DATEDIFF + ( + SECOND, + deqs.creation_time, + deqs.last_execution_time + ), + 0 + ), + 0 + ), + total_physical_reads_mb = + deqs.total_physical_reads * 8. / 1024., + total_logical_writes_mb = + deqs.total_logical_writes * 8. / 1024., + total_logical_reads_mb = + deqs.total_logical_reads * 8. / 1024., + min_grant_mb = + deqs.min_grant_kb * 8. / 1024., + max_grant_mb = + deqs.max_grant_kb * 8. / 1024., + min_used_grant_mb = + deqs.min_used_grant_kb * 8. / 1024., + max_used_grant_mb = + deqs.max_used_grant_kb * 8. / 1024., + deqs.min_reserved_threads, + deqs.max_reserved_threads, + deqs.min_used_threads, + deqs.max_used_threads, + deqs.total_rows + INTO #dm_exec_query_stats + FROM sys.dm_exec_query_stats AS deqs + WHERE EXISTS + ( + SELECT + 1/0 + FROM #available_plans AS ap + WHERE ap.sql_handle = deqs.sql_handle + ) + AND deqs.query_hash IS NOT NULL; + + CREATE CLUSTERED INDEX + deqs + ON #dm_exec_query_stats + ( + sql_handle, + plan_handle + ); + + SELECT + ap.available_plans, + ap.database_name, + query_text = + TRY_CAST(ap.query_xml AS xml), + ap.query_plan, + ap.creation_time, + ap.last_execution_time, + ap.execution_count, + ap.executions_per_second, + ap.total_worker_time_ms, + ap.avg_worker_time_ms, + ap.total_elapsed_time_ms, + ap.avg_elapsed_time, + ap.total_logical_reads_mb, + ap.total_physical_reads_mb, + ap.total_logical_writes_mb, + ap.min_grant_mb, + ap.max_grant_mb, + ap.min_used_grant_mb, + ap.max_used_grant_mb, + ap.min_reserved_threads, + ap.max_reserved_threads, + ap.min_used_threads, + ap.max_used_threads, + ap.total_rows, + ap.sql_handle, + ap.statement_start_offset, + ap.statement_end_offset + FROM + ( + + SELECT + ap.*, + c.statement_start_offset, + c.statement_end_offset, + c.creation_time, + c.last_execution_time, + c.execution_count, + c.total_worker_time_ms, + c.avg_worker_time_ms, + c.total_elapsed_time_ms, + c.avg_elapsed_time, + c.executions_per_second, + c.total_physical_reads_mb, + c.total_logical_writes_mb, + c.total_logical_reads_mb, + c.min_grant_mb, + c.max_grant_mb, + c.min_used_grant_mb, + c.max_used_grant_mb, + c.min_reserved_threads, + c.max_reserved_threads, + c.min_used_threads, + c.max_used_threads, + c.total_rows, + c.query_plan + FROM #available_plans AS ap + OUTER APPLY + ( + SELECT + deqs.*, + query_plan = + TRY_CAST(deps.query_plan AS xml) + FROM #dm_exec_query_stats deqs + OUTER APPLY sys.dm_exec_text_query_plan + ( + deqs.plan_handle, + deqs.statement_start_offset, + deqs.statement_end_offset + ) AS deps + WHERE deqs.sql_handle = ap.sql_handle + AND deps.dbid = ap.database_id + ) AS c + ) AS ap + WHERE ap.query_plan IS NOT NULL + ORDER BY + ap.avg_worker_time_ms DESC + OPTION(RECOMPILE, LOOP JOIN, HASH JOIN); + + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Returning findings %s', 0, 1, @d) WITH NOWAIT; - + SELECT df.check_id, df.database_name, @@ -32294,26 +32794,28 @@ BEGIN df.finding_group, df.finding FROM #deadlock_findings AS df - ORDER BY df.check_id + ORDER BY + df.check_id, + df.sort_order OPTION(RECOMPILE); - + SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; END; /*done with output to client app.*/ - END; + END; IF @Debug = 1 BEGIN SELECT - table_name = N'#dd', + table_name = N'#deadlock_data', * - FROM #dd AS d + FROM #deadlock_data AS dd OPTION(RECOMPILE); SELECT - table_name = N'#deadlock_data', + table_name = N'#dd', * - FROM #deadlock_data AS dd + FROM #dd AS d OPTION(RECOMPILE); SELECT @@ -32370,9 +32872,111 @@ BEGIN FROM @sysAssObjId AS s OPTION(RECOMPILE); + SELECT + table_name = N'#available_plans', + * + FROM #available_plans AS ap + OPTION(RECOMPILE); + + SELECT + table_name = N'#dm_exec_query_stats', + * + FROM #dm_exec_query_stats + OPTION(RECOMPILE); + + SELECT + procedure_parameters = + 'procedure_parameters', + DatabaseName = + @DatabaseName, + StartDate = + @StartDate, + EndDate = + @EndDate, + ObjectName = + @ObjectName, + StoredProcName = + @StoredProcName, + AppName = + @AppName, + HostName = + @HostName, + LoginName = + @LoginName, + EventSessionName = + @EventSessionName, + TargetSessionType = + @TargetSessionType, + VictimsOnly = + @VictimsOnly, + Debug = + @Debug, + Help = + @Help, + Version = + @Version, + VersionDate = + @VersionDate, + VersionCheckMode = + @VersionCheckMode, + OutputDatabaseName = + @OutputDatabaseName, + OutputSchemaName = + @OutputSchemaName, + OutputTableName = + @OutputTableName, + ExportToExcel = + @ExportToExcel; + + SELECT + declared_variables = + 'declared_variables', + DatabaseId = + @DatabaseId, + StartDateUTC = + @StartDateUTC, + EndDateUTC = + @EndDateUTC, + ProductVersion = + @ProductVersion, + ProductVersionMajor = + @ProductVersionMajor, + ProductVersionMinor = + @ProductVersionMinor, + ObjectFullName = + @ObjectFullName, + Azure = + @Azure, + RDS = + @RDS, + d = + @d, + StringToExecute = + @StringToExecute, + StringToExecuteParams = + @StringToExecuteParams, + r = + @r, + OutputTableFindings = + @OutputTableFindings, + DeadlockCount = + @DeadlockCount, + ServerName = + @ServerName, + OutputDatabaseCheck = + @OutputDatabaseCheck, + SessionId = + @SessionId, + TargetSessionId = + @TargetSessionId, + FileName = + @FileName, + inputbuf_bom = + @inputbuf_bom, + deadlock_result = + @deadlock_result; END; /*End debug*/ END; /*Final End*/ - GO SET ANSI_NULLS ON; SET ANSI_PADDING ON; @@ -32433,7 +33037,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.15', @VersionDate = '20230613'; +SELECT @Version = '8.16', @VersionDate = '20230820'; IF(@VersionCheckMode = 1) BEGIN RETURN; @@ -38164,7 +38768,7 @@ BEGIN SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.15', @VersionDate = '20230613'; + SELECT @Version = '8.16', @VersionDate = '20230820'; IF(@VersionCheckMode = 1) BEGIN @@ -39550,6 +40154,7 @@ ALTER PROCEDURE [dbo].[sp_DatabaseRestore] @DatabaseOwner sysname = NULL, @SetTrustworthyON BIT = 0, @FixOrphanUsers BIT = 0, + @KeepCdc BIT = 0, @Execute CHAR(1) = Y, @FileExtensionDiff NVARCHAR(128) = NULL, @Debug INT = 0, @@ -39563,7 +40168,7 @@ SET STATISTICS XML OFF; /*Versioning details*/ -SELECT @Version = '8.15', @VersionDate = '20230613'; +SELECT @Version = '8.16', @VersionDate = '20230820'; IF(@VersionCheckMode = 1) BEGIN @@ -39884,8 +40489,8 @@ CREATE TABLE #Headers EncryptorThumbprint VARBINARY(20), EncryptorType NVARCHAR(32), LastValidRestoreTime DATETIME, - TimeZone NVARCHAR(256), - CompressionAlgorithm NVARCHAR(256), + TimeZone NVARCHAR(32), + CompressionAlgorithm NVARCHAR(32), -- -- Seq added to retain order by -- @@ -41009,13 +41614,18 @@ END -- Put database in a useable state IF @RunRecovery = 1 BEGIN - SET @sql = N'RESTORE DATABASE ' + @RestoreDatabaseName + N' WITH RECOVERY' + NCHAR(13); + SET @sql = N'RESTORE DATABASE ' + @RestoreDatabaseName + N' WITH RECOVERY'; + + IF @KeepCdc = 1 + SET @sql = @sql + N', KEEP_CDC'; - IF @Debug = 1 OR @Execute = 'N' - BEGIN - IF @sql IS NULL PRINT '@sql is NULL for RESTORE DATABASE: @RestoreDatabaseName'; - PRINT @sql; - END; + SET @sql = @sql + NCHAR(13); + + IF @Debug = 1 OR @Execute = 'N' + BEGIN + IF @sql IS NULL PRINT '@sql is NULL for RESTORE DATABASE: @RestoreDatabaseName'; + PRINT @sql; + END; IF @Debug IN (0, 1) AND @Execute = 'Y' EXECUTE @sql = [dbo].[CommandExecute] @DatabaseContext=N'master', @Command = @sql, @CommandType = 'RECOVER DATABASE', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y'; @@ -41204,7 +41814,7 @@ BEGIN SET NOCOUNT ON; SET STATISTICS XML OFF; - SELECT @Version = '8.15', @VersionDate = '20230613'; + SELECT @Version = '8.16', @VersionDate = '20230820'; IF(@VersionCheckMode = 1) BEGIN @@ -41550,12 +42160,17 @@ DELETE FROM dbo.SqlServerVersions; INSERT INTO dbo.SqlServerVersions (MajorVersionNumber, MinorVersionNumber, Branch, [Url], ReleaseDate, MainstreamSupportEndDate, ExtendedSupportEndDate, MajorVersionName, MinorVersionName) VALUES + (16, 4065, 'CU7', 'https://support.microsoft.com/en-us/help/5028743', '2023-08-10', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 7'), + (16, 4055, 'CU6', 'https://support.microsoft.com/en-us/help/5027505', '2023-07-13', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 6'), + (16, 4045, 'CU5', 'https://support.microsoft.com/en-us/help/5026806', '2023-06-15', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 5'), (16, 4035, 'CU4', 'https://support.microsoft.com/en-us/help/5026717', '2023-05-11', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 4'), (16, 4025, 'CU3', 'https://support.microsoft.com/en-us/help/5024396', '2023-04-13', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 3'), (16, 4015, 'CU2', 'https://support.microsoft.com/en-us/help/5023127', '2023-03-15', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 2'), (16, 4003, 'CU1', 'https://support.microsoft.com/en-us/help/5022375', '2023-02-16', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 1'), (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'), + (15, 4322, 'CU22', 'https://support.microsoft.com/kb/5027702', '2023-08-14', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 22'), + (15, 4316, 'CU21', 'https://support.microsoft.com/kb/5025808', '2023-06-15', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 21'), (15, 4312, 'CU20', 'https://support.microsoft.com/kb/5024276', '2023-04-13', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 20'), (15, 4298, 'CU19', 'https://support.microsoft.com/kb/5023049', '2023-02-16', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 19'), (15, 4280, 'CU18 GDR', 'https://support.microsoft.com/kb/5021124', '2023-02-14', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 18 GDR'), @@ -41978,7 +42593,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.15', @VersionDate = '20230613'; +SELECT @Version = '8.16', @VersionDate = '20230820'; IF(@VersionCheckMode = 1) BEGIN diff --git a/Install-Core-Blitz-No-Query-Store.sql b/Install-Core-Blitz-No-Query-Store.sql index 7acb71d2b..e66bf69a9 100644 --- a/Install-Core-Blitz-No-Query-Store.sql +++ b/Install-Core-Blitz-No-Query-Store.sql @@ -38,7 +38,7 @@ AS SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.15', @VersionDate = '20230613'; + SELECT @Version = '8.16', @VersionDate = '20230820'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -186,11 +186,129 @@ AS ,@CurrentComponentVersionCheckModeOK BIT ,@canExitLoop BIT ,@frkIsConsistent BIT - ,@NeedToTurnNumericRoundabortBackOn BIT; + ,@NeedToTurnNumericRoundabortBackOn BIT + ,@sa bit = 1 + ,@SUSER_NAME sysname = SUSER_SNAME() + ,@SkipDBCC bit = 0 + ,@SkipTrace bit = 0 + ,@SkipXPRegRead bit = 0 + ,@SkipXPFixedDrives bit = 0 + ,@SkipXPCMDShell bit = 0 + ,@SkipMaster bit = 0 + ,@SkipMSDB bit = 0 + ,@SkipModel bit = 0 + ,@SkipTempDB bit = 0 + ,@SkipValidateLogins bit = 0; + + DECLARE + @db_perms table + ( + database_name sysname, + permission_name sysname + ); + + INSERT + @db_perms + ( + database_name, + permission_name + ) + SELECT + database_name = + DB_NAME(d.database_id), + fmp.permission_name + FROM sys.databases AS d + CROSS APPLY fn_my_permissions(d.name, 'DATABASE') AS fmp + WHERE fmp.permission_name = N'SELECT' /*Databases where we don't have read permissions*/ /* End of declarations for First Responder Kit consistency check:*/ ; + /*Starting permissions checks here, but only if we're not a sysadmin*/ + IF + ( + SELECT + sa = + ISNULL + ( + IS_SRVROLEMEMBER(N'sysadmin'), + 0 + ) + ) = 0 + BEGIN + IF @Debug IN (1, 2) RAISERROR('User not SA, checking permissions', 0, 1) WITH NOWAIT; + + SET @sa = 0; /*Setting this to 0 to skip DBCC COMMANDS*/ + + IF NOT EXISTS + ( + SELECT + 1/0 + FROM sys.fn_my_permissions(NULL, NULL) AS fmp + WHERE fmp.permission_name = N'VIEW SERVER STATE' + ) + BEGIN + RAISERROR('The user %s does not have VIEW SERVER STATE permissions.', 0, 11, @SUSER_NAME) WITH NOWAIT; + RETURN; + END; /*If we don't have this, we can't do anything at all.*/ + + IF NOT EXISTS + ( + SELECT + 1/0 + FROM fn_my_permissions(N'sys.traces', N'OBJECT') AS fmp + WHERE fmp.permission_name = N'ALTER' + ) + BEGIN + SET @SkipTrace = 1; + END; /*We need this permission to execute trace stuff, apparently*/ + + IF NOT EXISTS + ( + SELECT + 1/0 + FROM fn_my_permissions(N'xp_regread', N'OBJECT') AS fmp + WHERE fmp.permission_name = N'EXECUTE' + ) + BEGIN + SET @SkipXPRegRead = 1; + END; /*Need execute on xp_regread*/ + + IF NOT EXISTS + ( + SELECT + 1/0 + FROM fn_my_permissions(N'xp_fixeddrives', N'OBJECT') AS fmp + WHERE fmp.permission_name = N'EXECUTE' + ) + BEGIN + SET @SkipXPFixedDrives = 1; + END; /*Need execute on xp_fixeddrives*/ + + IF NOT EXISTS + ( + SELECT + 1/0 + FROM fn_my_permissions(N'xp_cmdshell', N'OBJECT') AS fmp + WHERE fmp.permission_name = N'EXECUTE' + ) + BEGIN + SET @SkipXPCMDShell = 1; + END; /*Need execute on xp_cmdshell*/ + + IF NOT EXISTS + ( + SELECT + 1/0 + FROM fn_my_permissions(N'sp_validatelogins', N'OBJECT') AS fmp + WHERE fmp.permission_name = N'EXECUTE' + ) + BEGIN + SET @SkipValidateLogins = 1; + END; /*Need execute on sp_validatelogins*/ + + END; + SET @crlf = NCHAR(13) + NCHAR(10); SET @ResultText = 'sp_Blitz Results: ' + @crlf; @@ -331,6 +449,66 @@ AS OR LOWER(d.name) IN ('dbatools', 'dbadmin', 'dbmaintenance')) OPTION(RECOMPILE); + /*Skip checks for database where we don't have read permissions*/ + INSERT INTO + #SkipChecks + ( + DatabaseName + ) + SELECT + DB_NAME(d.database_id) + FROM sys.databases AS d + WHERE NOT EXISTS + ( + SELECT + 1/0 + FROM @db_perms AS dp + WHERE dp.database_name = DB_NAME(d.database_id) + ); + + /*Skip individial checks where we don't have permissions*/ + INSERT #SkipChecks (DatabaseName, CheckID, ServerName) + SELECT + v.* + FROM (VALUES(NULL, 29, NULL)) AS v (DatabaseName, CheckID, ServerName) /*Looks for user tables in model*/ + WHERE NOT EXISTS (SELECT 1/0 FROM @db_perms AS dp WHERE dp.database_name = 'model'); + + INSERT #SkipChecks (DatabaseName, CheckID, ServerName) + SELECT + v.* + FROM (VALUES(NULL, 68, NULL)) AS v (DatabaseName, CheckID, ServerName) /*DBCC command*/ + WHERE @sa = 0; + + INSERT #SkipChecks (DatabaseName, CheckID, ServerName) + SELECT + v.* + FROM (VALUES(NULL, 69, NULL)) AS v (DatabaseName, CheckID, ServerName) /*DBCC command*/ + WHERE @sa = 0; + + INSERT #SkipChecks (DatabaseName, CheckID, ServerName) + SELECT + v.* + FROM (VALUES(NULL, 92, NULL)) AS v (DatabaseName, CheckID, ServerName) /*xp_fixeddrives*/ + WHERE @SkipXPFixedDrives = 1; + + INSERT #SkipChecks (DatabaseName, CheckID, ServerName) + SELECT + v.* + FROM (VALUES(NULL, 211, NULL)) AS v (DatabaseName, CheckID, ServerName) /*xp_regread*/ + WHERE @SkipXPRegRead = 1; + + INSERT #SkipChecks (DatabaseName, CheckID, ServerName) + SELECT + v.* + FROM (VALUES(NULL, 212, NULL)) AS v (DatabaseName, CheckID, ServerName) /*xp_regread*/ + WHERE @SkipXPCMDShell = 1; + + INSERT #SkipChecks (DatabaseName, CheckID, ServerName) + SELECT + v.* + FROM (VALUES(NULL, 2301, NULL)) AS v (DatabaseName, CheckID, ServerName) /*sp_validatelogins*/ + WHERE @SkipValidateLogins = 1 + IF(OBJECT_ID('tempdb..#InvalidLogins') IS NOT NULL) BEGIN EXEC sp_executesql N'DROP TABLE #InvalidLogins;'; @@ -372,7 +550,8 @@ AS SELECT @IsWindowsOperatingSystem = 1 ; END; - IF NOT EXISTS ( SELECT 1 + + IF NOT EXISTS ( SELECT 1 FROM #SkipChecks WHERE DatabaseName IS NULL AND CheckID = 106 ) AND (select convert(int,value_in_use) from sys.configurations where name = 'default trace enabled' ) = 1 @@ -4158,53 +4337,56 @@ AS /* First, let's check that there aren't any issues with the trace files */ BEGIN TRY - - INSERT INTO #fnTraceGettable - ( TextData , - DatabaseName , - EventClass , - Severity , - StartTime , - EndTime , - Duration , - NTUserName , - NTDomainName , - HostName , - ApplicationName , - LoginName , - DBUserName - ) - SELECT TOP 20000 - CONVERT(NVARCHAR(4000),t.TextData) , - t.DatabaseName , - t.EventClass , - t.Severity , - t.StartTime , - t.EndTime , - t.Duration , - t.NTUserName , - t.NTDomainName , - t.HostName , - t.ApplicationName , - t.LoginName , - t.DBUserName - FROM sys.fn_trace_gettable(@base_tracefilename, DEFAULT) t - WHERE - ( - t.EventClass = 22 - AND t.Severity >= 17 - AND t.StartTime > DATEADD(dd, -30, GETDATE()) - ) - OR - ( - t.EventClass IN (92, 93) - AND t.StartTime > DATEADD(dd, -30, GETDATE()) - AND t.Duration > 15000000 - ) - OR - ( - t.EventClass IN (94, 95, 116) - ) + + IF @SkipTrace = 0 + BEGIN + INSERT INTO #fnTraceGettable + ( TextData , + DatabaseName , + EventClass , + Severity , + StartTime , + EndTime , + Duration , + NTUserName , + NTDomainName , + HostName , + ApplicationName , + LoginName , + DBUserName + ) + SELECT TOP 20000 + CONVERT(NVARCHAR(4000),t.TextData) , + t.DatabaseName , + t.EventClass , + t.Severity , + t.StartTime , + t.EndTime , + t.Duration , + t.NTUserName , + t.NTDomainName , + t.HostName , + t.ApplicationName , + t.LoginName , + t.DBUserName + FROM sys.fn_trace_gettable(@base_tracefilename, DEFAULT) t + WHERE + ( + t.EventClass = 22 + AND t.Severity >= 17 + AND t.StartTime > DATEADD(dd, -30, GETDATE()) + ) + OR + ( + t.EventClass IN (92, 93) + AND t.StartTime > DATEADD(dd, -30, GETDATE()) + AND t.Duration > 15000000 + ) + OR + ( + t.EventClass IN (94, 95, 116) + ) + END; SET @TraceFileIssue = 0 @@ -6579,10 +6761,10 @@ IF @ProductVersionMajor >= 10 DatabaseName FROM #SkipChecks WHERE CheckID IS NULL OR CheckID = 19) - AND is_published = 1 + AND (is_published = 1 OR is_subscribed = 1 OR is_merge_published = 1 - OR is_distributor = 1; + OR is_distributor = 1); /* Method B: check subscribers for MSreplication_objects tables */ EXEC dbo.sp_MSforeachdb 'USE [?]; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; @@ -9737,7 +9919,7 @@ AS SET NOCOUNT ON; SET STATISTICS XML OFF; -SELECT @Version = '8.15', @VersionDate = '20230613'; +SELECT @Version = '8.16', @VersionDate = '20230820'; IF(@VersionCheckMode = 1) BEGIN @@ -10615,7 +10797,7 @@ AS SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.15', @VersionDate = '20230613'; + SELECT @Version = '8.16', @VersionDate = '20230820'; IF(@VersionCheckMode = 1) BEGIN @@ -12397,7 +12579,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.15', @VersionDate = '20230613'; +SELECT @Version = '8.16', @VersionDate = '20230820'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -13695,6 +13877,7 @@ BEGIN RAISERROR('Checking for Read intent databases to exclude',0,0) WITH NOWAIT; EXEC('INSERT INTO #ReadableDBs (database_id) SELECT DBs.database_id FROM sys.databases DBs INNER JOIN sys.availability_replicas Replicas ON DBs.replica_id = Replicas.replica_id WHERE replica_server_name NOT IN (SELECT DISTINCT primary_replica FROM sys.dm_hadr_availability_group_states States) AND Replicas.secondary_role_allow_connections_desc = ''READ_ONLY'' AND replica_server_name = @@SERVERNAME OPTION (RECOMPILE);'); + EXEC('INSERT INTO #ReadableDBs VALUES (32767) ;'); -- Exclude internal resource database as well END RAISERROR(N'Checking plan cache age', 0, 1) WITH NOWAIT; @@ -13731,10 +13914,10 @@ WITH total_plans AS SELECT COUNT_BIG(qs.query_plan_hash) AS duplicate_plan_hashes FROM sys.dm_exec_query_stats qs - LEFT JOIN sys.dm_exec_procedure_stats ps - ON qs.sql_handle = ps.sql_handle + LEFT JOIN sys.dm_exec_procedure_stats ps ON qs.plan_handle = ps.plan_handle CROSS APPLY sys.dm_exec_plan_attributes(qs.plan_handle) pa WHERE pa.attribute = N'dbid' + AND pa.value <> 32767 /*Omit Resource database-based queries, we're not going to "fix" them no matter what. Addresses #3314*/ AND qs.query_plan_hash <> 0x0000000000000000 GROUP BY /* qs.query_plan_hash, BGO 20210524 commenting this out to fix #2909 */ @@ -14439,7 +14622,7 @@ BEGIN max_grant_kb AS MaxGrantKB, min_used_grant_kb AS MinUsedGrantKB, max_used_grant_kb AS MaxUsedGrantKB, - CAST(ISNULL(NULLIF(( max_used_grant_kb * 1.00 ), 0) / NULLIF(min_grant_kb, 0), 0) * 100. AS MONEY) AS PercentMemoryGrantUsed, + CAST(ISNULL(NULLIF(( total_used_grant_kb * 1.00 ), 0) / NULLIF(total_grant_kb, 0), 0) * 100. AS MONEY) AS PercentMemoryGrantUsed, CAST(ISNULL(NULLIF(( total_grant_kb * 1. ), 0) / NULLIF(execution_count, 0), 0) AS MONEY) AS AvgMaxMemoryGrant, '; END; ELSE @@ -19715,7 +19898,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.15', @VersionDate = '20230613'; +SELECT @Version = '8.16', @VersionDate = '20230820'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -22625,7 +22808,8 @@ BEGIN + CASE WHEN @ShowPartitionRanges = 1 THEN N' COALESCE(range_start_op + '' '' + range_start + '' '', '''') + COALESCE(range_end_op + '' '' + range_end, '''') AS partition_range, ' ELSE N' ' END + N' row_group_id, total_rows, deleted_rows, ' + @ColumnList - + CASE WHEN @ShowPartitionRanges = 1 THEN N' + + CASE WHEN @ShowPartitionRanges = 1 THEN N' , + state_desc, trim_reason_desc, transition_to_compressed_state_desc, has_vertipaq_optimization FROM ( SELECT column_name, partition_number, row_group_id, total_rows, deleted_rows, details, range_start_op, @@ -22635,10 +22819,12 @@ BEGIN range_end_op, CASE WHEN format_type IS NULL THEN CAST(range_end_value AS NVARCHAR(4000)) - ELSE CONVERT(NVARCHAR(4000), range_end_value, format_type) END range_end' ELSE N' ' END + N' + ELSE CONVERT(NVARCHAR(4000), range_end_value, format_type) END range_end' ELSE N' ' END + N', + state_desc, trim_reason_desc, transition_to_compressed_state_desc, has_vertipaq_optimization FROM ( SELECT c.name AS column_name, p.partition_number, rg.row_group_id, rg.total_rows, rg.deleted_rows, - details = CAST(seg.min_data_id AS VARCHAR(20)) + '' to '' + CAST(seg.max_data_id AS VARCHAR(20)) + '', '' + CAST(CAST((seg.on_disk_size / 1024.0 / 1024) AS DECIMAL(18,0)) AS VARCHAR(20)) + '' MB''' + phys.state_desc, phys.trim_reason_desc, phys.transition_to_compressed_state_desc, phys.has_vertipaq_optimization, + details = CAST(seg.min_data_id AS VARCHAR(20)) + '' to '' + CAST(seg.max_data_id AS VARCHAR(20)) + '', '' + CAST(CAST(((COALESCE(d.on_disk_size,0) + COALESCE(seg.on_disk_size,0)) / 1024.0 / 1024) AS DECIMAL(18,0)) AS VARCHAR(20)) + '' MB''' + CASE WHEN @ShowPartitionRanges = 1 THEN N', CASE WHEN pp.system_type_id IN (40, 41, 42, 43, 58, 61) THEN 126 @@ -22652,7 +22838,8 @@ BEGIN FROM ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_row_groups rg INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns c ON rg.object_id = c.object_id INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partitions p ON rg.object_id = p.object_id AND rg.partition_number = p.partition_number - INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.index_columns ic on ic.column_id = c.column_id AND ic.object_id = c.object_id AND ic.index_id = p.index_id ' + CASE WHEN @ShowPartitionRanges = 1 THEN N' + INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.index_columns ic on ic.column_id = c.column_id AND ic.object_id = c.object_id AND ic.index_id = p.index_id + LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_column_store_row_group_physical_stats phys ON rg.row_group_id = phys.row_group_id AND rg.object_id = phys.object_id AND rg.partition_number = phys.partition_number AND p.index_id = phys.index_id ' + CASE WHEN @ShowPartitionRanges = 1 THEN N' LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.indexes i ON i.object_id = rg.object_id AND i.index_id = rg.index_id LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partition_schemes ps ON ps.data_space_id = i.data_space_id LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partition_functions pf ON pf.function_id = ps.function_id @@ -22660,6 +22847,7 @@ BEGIN LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partition_range_values prvs ON prvs.function_id = pf.function_id AND prvs.boundary_id = p.partition_number - 1 LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partition_range_values prve ON prve.function_id = pf.function_id AND prve.boundary_id = p.partition_number ' ELSE N' ' END + N' LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_segments seg ON p.partition_id = seg.partition_id AND ic.index_column_id = seg.column_id AND rg.row_group_id = seg.segment_id + LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_dictionaries d ON p.hobt_id = d.hobt_id AND c.column_id = d.column_id AND seg.secondary_dictionary_id = d.dictionary_id WHERE rg.object_id = @ObjectID AND rg.state IN (1, 2, 3) AND c.name IN ( ' + @ColumnListWithApostrophes + N')' @@ -22697,6 +22885,9 @@ BEGIN RAISERROR(N'Done visualizing columnstore index contents.', 0,1) WITH NOWAIT; END + IF @ShowColumnstoreOnly = 1 + RETURN; + END; /* IF @TableName IS NOT NULL */ @@ -25892,7 +26083,7 @@ BEGIN SET NOCOUNT, XACT_ABORT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.15', @VersionDate = '20230613'; + SELECT @Version = '8.16', @VersionDate = '20230820'; IF @VersionCheckMode = 1 BEGIN @@ -26036,7 +26227,11 @@ BEGIN @TargetSessionId int = 0, @FileName nvarchar(4000) = N'', @inputbuf_bom nvarchar(1) = CONVERT(nvarchar(1), 0x0a00, 0), - @deadlock_result nvarchar(MAX) = N''; + @deadlock_result nvarchar(MAX) = N'', + @StartDateOriginal datetime = @StartDate, + @EndDateOriginal datetime = @EndDate, + @StartDateUTC datetime, + @EndDateUTC datetime; /*Temporary objects used in the procedure*/ DECLARE @@ -26076,15 +26271,17 @@ BEGIN database_name nvarchar(256), object_name nvarchar(1000), finding_group nvarchar(100), - finding nvarchar(4000) + finding nvarchar(4000), + sort_order bigint ); /*Set these to some sane defaults if NULLs are passed in*/ /*Normally I'd hate this, but we RECOMPILE everything*/ + SELECT @StartDate = - CASE - WHEN @StartDate IS NULL + CASE + WHEN @StartDate IS NULL THEN DATEADD ( @@ -26095,18 +26292,25 @@ BEGIN SYSDATETIME(), GETUTCDATE() ), - ISNULL + DATEADD ( - @StartDate, - DATEADD - ( - DAY, - -7, - SYSDATETIME() - ) + DAY, + -7, + SYSDATETIME() ) ) - ELSE @StartDate + ELSE + DATEADD + ( + MINUTE, + DATEDIFF + ( + MINUTE, + SYSDATETIME(), + GETUTCDATE() + ), + @StartDate + ) END, @EndDate = CASE @@ -26121,15 +26325,62 @@ BEGIN SYSDATETIME(), GETUTCDATE() ), - ISNULL + SYSDATETIME() + ) + ELSE + DATEADD + ( + MINUTE, + DATEDIFF ( - @EndDate, - SYSDATETIME() - ) + MINUTE, + SYSDATETIME(), + GETUTCDATE() + ), + @EndDate ) - ELSE @EndDate END; + SELECT + @StartDateUTC = @StartDate, + @EndDateUTC = @EndDate; + + IF @Azure = 0 + BEGIN + IF NOT EXISTS + ( + SELECT + 1/0 + FROM sys.server_event_sessions AS ses + JOIN sys.dm_xe_sessions AS dxs + ON dxs.name = ses.name + WHERE ses.name = @EventSessionName + AND dxs.create_time IS NOT NULL + ) + BEGIN + RAISERROR('A session with the name %s does not exist or is not currently active.', 11, 1, @EventSessionName) WITH NOWAIT; + RETURN; + END; + END; + + IF @Azure = 1 + BEGIN + IF NOT EXISTS + ( + SELECT + 1/0 + FROM sys.database_event_sessions AS ses + JOIN sys.dm_xe_database_sessions AS dxs + ON dxs.name = ses.name + WHERE ses.name = @EventSessionName + AND dxs.create_time IS NOT NULL + ) + BEGIN + RAISERROR('A session with the name %s does not exist or is not currently active.', 11, 1, @EventSessionName) WITH NOWAIT; + RETURN; + END; + END; + IF @OutputDatabaseName IS NOT NULL BEGIN /*IF databaseName is set, do some sanity checks and put [] around def.*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); @@ -26712,8 +26963,7 @@ BEGIN OR e.x.exist('@name[ .= "database_xml_deadlock_report"]') = 1 OR e.x.exist('@name[ .= "xml_deadlock_report_filtered"]') = 1 ) - AND e.x.exist('@timestamp[. >= sql:variable("@StartDate")]') = 1 - AND e.x.exist('@timestamp[. < sql:variable("@EndDate")]') = 1 + AND e.x.exist('@timestamp[. >= sql:variable("@StartDate") and .< sql:variable("@EndDate")]') = 1 OPTION(RECOMPILE); SET @d = CONVERT(varchar(40), GETDATE(), 109); @@ -26730,9 +26980,9 @@ BEGIN SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Inserting to #deadlock_data for event file data', 0, 1) WITH NOWAIT; - IF @Debug = 1 BEGIN SET STATISTICS XML ON; END; + IF @Debug = 1 BEGIN SET STATISTICS XML ON; END; - INSERT + INSERT #deadlock_data WITH(TABLOCKX) ( deadlock_xml @@ -26750,13 +27000,12 @@ BEGIN OR e.x.exist('@name[ .= "database_xml_deadlock_report"]') = 1 OR e.x.exist('@name[ .= "xml_deadlock_report_filtered"]') = 1 ) - AND e.x.exist('@timestamp[. >= sql:variable("@StartDate")]') = 1 - AND e.x.exist('@timestamp[. < sql:variable("@EndDate")]') = 1 + AND e.x.exist('@timestamp[. >= sql:variable("@StartDate") and .< sql:variable("@EndDate")]') = 1 OPTION(RECOMPILE); - IF @Debug = 1 BEGIN SET STATISTICS XML OFF; END; + IF @Debug = 1 BEGIN SET STATISTICS XML OFF; END; - SET @d = CONVERT(varchar(40), GETDATE(), 109); + SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; END; @@ -26787,8 +27036,7 @@ BEGIN ) AS xml CROSS APPLY xml.deadlock_xml.nodes('/event') AS e(x) WHERE 1 = 1 - AND e.x.exist('@timestamp[. >= sql:variable("@StartDate")]') = 1 - AND e.x.exist('@timestamp[. < sql:variable("@EndDate")]') = 1 + AND e.x.exist('@timestamp[. >= sql:variable("@StartDate") and .< sql:variable("@EndDate")]') = 1 OPTION(RECOMPILE); INSERT @@ -26915,7 +27163,12 @@ BEGIN DATEADD ( MINUTE, - DATEDIFF(MINUTE, GETUTCDATE(), SYSDATETIME()), + DATEDIFF + ( + MINUTE, + GETUTCDATE(), + SYSDATETIME() + ), dd.event_date ), dd.victim_id, @@ -26973,6 +27226,7 @@ BEGIN FROM #deadlock_process AS dp CROSS APPLY dp.process_xml.nodes('//executionStack/frame') AS ca(dp) WHERE (ca.dp.exist('@procname[. = sql:variable("@StoredProcName")]') = 1 OR @StoredProcName IS NULL) + AND ca.dp.exist('@sqlhandle[ .= "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"]') = 0 OPTION(RECOMPILE); SET @d = CONVERT(varchar(40), GETDATE(), 109); @@ -27045,7 +27299,7 @@ BEGIN waiter_mode = w.l.value('@mode', 'nvarchar(256)'), owner_id = o.l.value('@id', 'nvarchar(256)'), owner_mode = o.l.value('@mode', 'nvarchar(256)'), - lock_type = CAST(N'OBJECT' AS NVARCHAR(100)) + lock_type = CAST(N'OBJECT' AS nvarchar(100)) INTO #deadlock_owner_waiter FROM ( @@ -27441,13 +27695,19 @@ BEGIN 32 ), step_id = - SUBSTRING - ( - dp.client_app, - CHARINDEX(N': Step ', dp.client_app) + LEN(N': Step '), - CHARINDEX(N')', dp.client_app, CHARINDEX(N': Step ', dp.client_app)) - - (CHARINDEX(N': Step ', dp.client_app) + LEN(N': Step ')) - ) + CASE + WHEN CHARINDEX(N': Step ', dp.client_app) > 0 + AND CHARINDEX(N')', dp.client_app, CHARINDEX(N': Step ', dp.client_app)) > 0 + THEN + SUBSTRING + ( + dp.client_app, + CHARINDEX(N': Step ', dp.client_app) + LEN(N': Step '), + CHARINDEX(N')', dp.client_app, CHARINDEX(N': Step ', dp.client_app)) - + (CHARINDEX(N': Step ', dp.client_app) + LEN(N': Step ')) + ) + ELSE dp.client_app + END FROM #deadlock_process AS dp WHERE dp.client_app LIKE N'SQLAgent - %' AND dp.client_app <> N'SQLAgent - Initial Boot Probe' @@ -27597,6 +27857,14 @@ BEGIN /*Begin checks based on parsed values*/ + /* + First, revert these back since we already converted the event data to local time, + and searches will break if we use the times converted over to UTC for the event data + */ + SELECT + @StartDate = @StartDateOriginal, + @EndDate = @EndDateOriginal; + /*Check 1 is deadlocks by database*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Check 1 database deadlocks %s', 0, 1, @d) WITH NOWAIT; @@ -27608,7 +27876,8 @@ BEGIN database_name, object_name, finding_group, - finding + finding, + sort_order ) SELECT check_id = 1, @@ -27622,7 +27891,10 @@ BEGIN nvarchar(20), COUNT_BIG(DISTINCT dp.event_date) ) + - N' deadlocks.' + N' deadlocks.', + sort_order = + ROW_NUMBER() + OVER (ORDER BY COUNT_BIG(DISTINCT dp.event_date) DESC) FROM #deadlock_process AS dp WHERE 1 = 1 AND (dp.database_name = @DatabaseName OR @DatabaseName IS NULL) @@ -27647,7 +27919,8 @@ BEGIN database_name, object_name, finding_group, - finding + finding, + sort_order ) SELECT check_id = 2, @@ -27662,7 +27935,10 @@ BEGIN nvarchar(20), COUNT_BIG(DISTINCT dow.event_date) ) + - N' deadlock(s) between read queries and modification queries.' + N' deadlock(s) between read queries and modification queries.', + sort_order = + ROW_NUMBER() + OVER (ORDER BY COUNT_BIG(DISTINCT dow.event_date) DESC) FROM #deadlock_owner_waiter AS dow WHERE 1 = 1 AND dow.lock_mode IN @@ -27701,7 +27977,8 @@ BEGIN database_name, object_name, finding_group, - finding + finding, + sort_order ) SELECT check_id = 3, @@ -27720,7 +27997,10 @@ BEGIN nvarchar(20), COUNT_BIG(DISTINCT dow.event_date) ) + - N' deadlock(s).' + N' deadlock(s).', + sort_order = + ROW_NUMBER() + OVER (ORDER BY COUNT_BIG(DISTINCT dow.event_date) DESC) FROM #deadlock_owner_waiter AS dow WHERE 1 = 1 AND (dow.database_id = @DatabaseId OR @DatabaseName IS NULL) @@ -27745,7 +28025,8 @@ BEGIN database_name, object_name, finding_group, - finding + finding, + sort_order ) SELECT check_id = 3, @@ -27759,7 +28040,10 @@ BEGIN nvarchar(20), COUNT_BIG(DISTINCT dow.event_date) ) + - N' deadlock(s).' + N' deadlock(s).', + sort_order = + ROW_NUMBER() + OVER (ORDER BY COUNT_BIG(DISTINCT dow.event_date) DESC) FROM #deadlock_owner_waiter AS dow WHERE 1 = 1 AND (dow.database_id = @DatabaseId OR @DatabaseName IS NULL) @@ -27790,7 +28074,8 @@ BEGIN database_name, object_name, finding_group, - finding + finding, + sort_order ) SELECT check_id = 3, @@ -27804,7 +28089,10 @@ BEGIN nvarchar(20), COUNT_BIG(DISTINCT dow.event_date) ) + - N' deadlock(s).' + N' deadlock(s).', + sort_order = + ROW_NUMBER() + OVER (ORDER BY COUNT_BIG(DISTINCT dow.event_date) DESC) FROM #deadlock_owner_waiter AS dow WHERE 1 = 1 AND (dow.database_id = @DatabaseId OR @DatabaseName IS NULL) @@ -27834,7 +28122,8 @@ BEGIN database_name, object_name, finding_group, - finding + finding, + sort_order ) SELECT check_id = 4, @@ -27849,7 +28138,10 @@ BEGIN nvarchar(20), COUNT_BIG(DISTINCT dp.event_date) ) + - N' instances of Serializable deadlocks.' + N' instances of Serializable deadlocks.', + sort_order = + ROW_NUMBER() + OVER (ORDER BY COUNT_BIG(DISTINCT dp.event_date) DESC) FROM #deadlock_process AS dp WHERE dp.isolation_level LIKE N'serializable%' AND (dp.database_name = @DatabaseName OR @DatabaseName IS NULL) @@ -27874,7 +28166,8 @@ BEGIN database_name, object_name, finding_group, - finding + finding, + sort_order ) SELECT check_id = 5, @@ -27888,7 +28181,10 @@ BEGIN nvarchar(20), COUNT_BIG(DISTINCT dp.event_date) ) + - N' instances of Repeatable Read deadlocks.' + N' instances of Repeatable Read deadlocks.', + sort_order = + ROW_NUMBER() + OVER (ORDER BY COUNT_BIG(DISTINCT dp.event_date) DESC) FROM #deadlock_process AS dp WHERE dp.isolation_level LIKE N'repeatable%' AND (dp.database_name = @DatabaseName OR @DatabaseName IS NULL) @@ -27913,7 +28209,8 @@ BEGIN database_name, object_name, finding_group, - finding + finding, + sort_order ) SELECT check_id = 6, @@ -27946,7 +28243,10 @@ BEGIN dp.host_name, N'UNKNOWN' ) + - N'.' + N'.', + sort_order = + ROW_NUMBER() + OVER (ORDER BY COUNT_BIG(DISTINCT dp.event_date) DESC) FROM #deadlock_process AS dp WHERE 1 = 1 AND (dp.database_name = @DatabaseName OR @DatabaseName IS NULL) @@ -28027,7 +28327,8 @@ BEGIN database_name, object_name, finding_group, - finding + finding, + sort_order ) SELECT check_id = 7, @@ -28054,7 +28355,10 @@ BEGIN 1, 1, N'' - ) + N' locks.' + ) + N' locks.', + sort_order = + ROW_NUMBER() + OVER (ORDER BY CONVERT(bigint, lt.lock_count) DESC) FROM lock_types AS lt OPTION(RECOMPILE); @@ -28068,9 +28372,9 @@ BEGIN deadlock_stack AS ( SELECT DISTINCT - ds.id, - ds.proc_name, - ds.event_date, + ds.id, + ds.event_date, + ds.proc_name, database_name = PARSENAME(ds.proc_name, 3), schema_name = @@ -28104,8 +28408,8 @@ BEGIN PARSENAME(ds.proc_name, 3), PARSENAME(ds.proc_name, 2), PARSENAME(ds.proc_name, 1), - ds.id, ds.proc_name, + ds.id, ds.event_date ) INSERT @@ -28137,6 +28441,7 @@ BEGIN AND (dow.event_date >= @StartDate OR @StartDate IS NULL) AND (dow.event_date < @EndDate OR @EndDate IS NULL) AND (dow.object_name = @StoredProcName OR @StoredProcName IS NULL) + AND ds.proc_name NOT LIKE 'Unknown%' OPTION(RECOMPILE); RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; @@ -28209,7 +28514,8 @@ BEGIN database_name, object_name, finding_group, - finding + finding, + sort_order ) SELECT check_id = 9, @@ -28228,7 +28534,10 @@ BEGIN nvarchar(10), COUNT_BIG(DISTINCT ds.id) ) + - N' deadlocks.' + N' deadlocks.', + sort_order = + ROW_NUMBER() + OVER (ORDER BY COUNT_BIG(DISTINCT ds.id) DESC) FROM #deadlock_stack AS ds JOIN #deadlock_process AS dp ON dp.id = ds.id @@ -28327,19 +28636,19 @@ BEGIN ) ), wait_time_hms = - /*the more wait time you rack up the less accurate this gets, + /*the more wait time you rack up the less accurate this gets, it's either that or erroring out*/ - CASE - WHEN + CASE + WHEN SUM ( CONVERT ( - bigint, + bigint, dp.wait_time ) )/1000 > 2147483647 - THEN + THEN CONVERT ( nvarchar(30), @@ -28352,7 +28661,7 @@ BEGIN ( CONVERT ( - bigint, + bigint, dp.wait_time ) ) @@ -28363,16 +28672,16 @@ BEGIN ), 14 ) - WHEN + WHEN SUM ( CONVERT ( - bigint, + bigint, dp.wait_time ) ) BETWEEN 2147483648 AND 2147483647000 - THEN + THEN CONVERT ( nvarchar(30), @@ -28385,7 +28694,7 @@ BEGIN ( CONVERT ( - bigint, + bigint, dp.wait_time ) ) @@ -28417,7 +28726,9 @@ BEGIN ), 14 ) - END + END, + total_waits = + SUM(CONVERT(bigint, dp.wait_time)) FROM #deadlock_owner_waiter AS dow JOIN #deadlock_process AS dp ON (dp.id = dow.owner_id @@ -28442,7 +28753,8 @@ BEGIN database_name, object_name, finding_group, - finding + finding, + sort_order ) SELECT check_id = 11, @@ -28463,7 +28775,10 @@ BEGIN cs.wait_time_hms, 14 ) + - N' [dd hh:mm:ss:ms] of deadlock wait time.' + N' [dd hh:mm:ss:ms] of deadlock wait time.', + sort_order = + ROW_NUMBER() + OVER (ORDER BY cs.total_waits DESC) FROM chopsuey AS cs WHERE cs.object_name IS NOT NULL OPTION(RECOMPILE); @@ -28511,7 +28826,8 @@ BEGIN database_name, object_name, finding_group, - finding + finding, + sort_order ) SELECT check_id = 12, @@ -28534,19 +28850,19 @@ BEGIN ) ) + N' ' + - /*the more wait time you rack up the less accurate this gets, + /*the more wait time you rack up the less accurate this gets, it's either that or erroring out*/ - CASE - WHEN + CASE + WHEN SUM ( CONVERT ( - bigint, + bigint, wt.total_wait_time_ms ) )/1000 > 2147483647 - THEN + THEN CONVERT ( nvarchar(30), @@ -28559,7 +28875,7 @@ BEGIN ( CONVERT ( - bigint, + bigint, wt.total_wait_time_ms ) ) @@ -28570,16 +28886,16 @@ BEGIN ), 14 ) - WHEN + WHEN SUM ( CONVERT ( - bigint, + bigint, wt.total_wait_time_ms ) ) BETWEEN 2147483648 AND 2147483647000 - THEN + THEN CONVERT ( nvarchar(30), @@ -28592,7 +28908,7 @@ BEGIN ( CONVERT ( - bigint, + bigint, wt.total_wait_time_ms ) ) @@ -28624,7 +28940,10 @@ BEGIN ), 14 ) END + - N' [dd hh:mm:ss:ms] of deadlock wait time.' + N' [dd hh:mm:ss:ms] of deadlock wait time.', + sort_order = + ROW_NUMBER() + OVER (ORDER BY SUM(CONVERT(bigint, wt.total_wait_time_ms)) DESC) FROM wait_time AS wt GROUP BY wt.database_name @@ -28643,7 +28962,8 @@ BEGIN database_name, object_name, finding_group, - finding + finding, + sort_order ) SELECT check_id = 13, @@ -28658,7 +28978,10 @@ BEGIN finding = N'There have been ' + RTRIM(COUNT_BIG(DISTINCT aj.event_date)) + - N' deadlocks from this Agent Job and Step.' + N' deadlocks from this Agent Job and Step.', + sort_order = + ROW_NUMBER() + OVER (ORDER BY COUNT_BIG(DISTINCT aj.event_date) DESC) FROM #agent_job AS aj GROUP BY DB_NAME(aj.database_id), @@ -29202,7 +29525,8 @@ BEGIN d.waiter_waiting_to_close, /*end parallel deadlock columns*/ d.deadlock_graph, - d.is_victim + d.is_victim, + d.id INTO #deadlock_results FROM #deadlocks AS d; @@ -29405,26 +29729,202 @@ BEGIN DROP SYNONYM DeadlockFindings; /*done with inserting.*/ END; ELSE /*Output to database is not set output to client app*/ - BEGIN - SET @d = CONVERT(varchar(40), GETDATE(), 109); + BEGIN + SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Results to client %s', 0, 1, @d) WITH NOWAIT; - + IF @Debug = 1 BEGIN SET STATISTICS XML ON; END; - + EXEC sys.sp_executesql @deadlock_result; - + IF @Debug = 1 BEGIN SET STATISTICS XML OFF; PRINT @deadlock_result; END; - + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - + + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Getting available execution plans for deadlocks %s', 0, 1, @d) WITH NOWAIT; + + SELECT DISTINCT + available_plans = + 'available_plans', + ds.proc_name, + sql_handle = + CONVERT(varbinary(64), ds.sql_handle, 1), + dow.database_name, + dow.database_id, + dow.object_name, + query_xml = + TRY_CAST(dr.query_xml AS nvarchar(MAX)) + INTO #available_plans + FROM #deadlock_stack AS ds + JOIN #deadlock_owner_waiter AS dow + ON dow.owner_id = ds.id + AND dow.event_date = ds.event_date + JOIN #deadlock_results AS dr + ON dr.id = ds.id + AND dr.event_date = ds.event_date + OPTION(RECOMPILE); + + SELECT + deqs.sql_handle, + deqs.plan_handle, + deqs.statement_start_offset, + deqs.statement_end_offset, + deqs.creation_time, + deqs.last_execution_time, + deqs.execution_count, + total_worker_time_ms = + deqs.total_worker_time / 1000., + avg_worker_time_ms = + CONVERT(decimal(38, 6), deqs.total_worker_time / 1000. / deqs.execution_count), + total_elapsed_time_ms = + deqs.total_elapsed_time / 1000., + avg_elapsed_time = + CONVERT(decimal(38, 6), deqs.total_elapsed_time / 1000. / deqs.execution_count), + executions_per_second = + ISNULL + ( + deqs.execution_count / + NULLIF + ( + DATEDIFF + ( + SECOND, + deqs.creation_time, + deqs.last_execution_time + ), + 0 + ), + 0 + ), + total_physical_reads_mb = + deqs.total_physical_reads * 8. / 1024., + total_logical_writes_mb = + deqs.total_logical_writes * 8. / 1024., + total_logical_reads_mb = + deqs.total_logical_reads * 8. / 1024., + min_grant_mb = + deqs.min_grant_kb * 8. / 1024., + max_grant_mb = + deqs.max_grant_kb * 8. / 1024., + min_used_grant_mb = + deqs.min_used_grant_kb * 8. / 1024., + max_used_grant_mb = + deqs.max_used_grant_kb * 8. / 1024., + deqs.min_reserved_threads, + deqs.max_reserved_threads, + deqs.min_used_threads, + deqs.max_used_threads, + deqs.total_rows + INTO #dm_exec_query_stats + FROM sys.dm_exec_query_stats AS deqs + WHERE EXISTS + ( + SELECT + 1/0 + FROM #available_plans AS ap + WHERE ap.sql_handle = deqs.sql_handle + ) + AND deqs.query_hash IS NOT NULL; + + CREATE CLUSTERED INDEX + deqs + ON #dm_exec_query_stats + ( + sql_handle, + plan_handle + ); + + SELECT + ap.available_plans, + ap.database_name, + query_text = + TRY_CAST(ap.query_xml AS xml), + ap.query_plan, + ap.creation_time, + ap.last_execution_time, + ap.execution_count, + ap.executions_per_second, + ap.total_worker_time_ms, + ap.avg_worker_time_ms, + ap.total_elapsed_time_ms, + ap.avg_elapsed_time, + ap.total_logical_reads_mb, + ap.total_physical_reads_mb, + ap.total_logical_writes_mb, + ap.min_grant_mb, + ap.max_grant_mb, + ap.min_used_grant_mb, + ap.max_used_grant_mb, + ap.min_reserved_threads, + ap.max_reserved_threads, + ap.min_used_threads, + ap.max_used_threads, + ap.total_rows, + ap.sql_handle, + ap.statement_start_offset, + ap.statement_end_offset + FROM + ( + + SELECT + ap.*, + c.statement_start_offset, + c.statement_end_offset, + c.creation_time, + c.last_execution_time, + c.execution_count, + c.total_worker_time_ms, + c.avg_worker_time_ms, + c.total_elapsed_time_ms, + c.avg_elapsed_time, + c.executions_per_second, + c.total_physical_reads_mb, + c.total_logical_writes_mb, + c.total_logical_reads_mb, + c.min_grant_mb, + c.max_grant_mb, + c.min_used_grant_mb, + c.max_used_grant_mb, + c.min_reserved_threads, + c.max_reserved_threads, + c.min_used_threads, + c.max_used_threads, + c.total_rows, + c.query_plan + FROM #available_plans AS ap + OUTER APPLY + ( + SELECT + deqs.*, + query_plan = + TRY_CAST(deps.query_plan AS xml) + FROM #dm_exec_query_stats deqs + OUTER APPLY sys.dm_exec_text_query_plan + ( + deqs.plan_handle, + deqs.statement_start_offset, + deqs.statement_end_offset + ) AS deps + WHERE deqs.sql_handle = ap.sql_handle + AND deps.dbid = ap.database_id + ) AS c + ) AS ap + WHERE ap.query_plan IS NOT NULL + ORDER BY + ap.avg_worker_time_ms DESC + OPTION(RECOMPILE, LOOP JOIN, HASH JOIN); + + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Returning findings %s', 0, 1, @d) WITH NOWAIT; - + SELECT df.check_id, df.database_name, @@ -29432,26 +29932,28 @@ BEGIN df.finding_group, df.finding FROM #deadlock_findings AS df - ORDER BY df.check_id + ORDER BY + df.check_id, + df.sort_order OPTION(RECOMPILE); - + SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; END; /*done with output to client app.*/ - END; + END; IF @Debug = 1 BEGIN SELECT - table_name = N'#dd', + table_name = N'#deadlock_data', * - FROM #dd AS d + FROM #deadlock_data AS dd OPTION(RECOMPILE); SELECT - table_name = N'#deadlock_data', + table_name = N'#dd', * - FROM #deadlock_data AS dd + FROM #dd AS d OPTION(RECOMPILE); SELECT @@ -29508,9 +30010,111 @@ BEGIN FROM @sysAssObjId AS s OPTION(RECOMPILE); + SELECT + table_name = N'#available_plans', + * + FROM #available_plans AS ap + OPTION(RECOMPILE); + + SELECT + table_name = N'#dm_exec_query_stats', + * + FROM #dm_exec_query_stats + OPTION(RECOMPILE); + + SELECT + procedure_parameters = + 'procedure_parameters', + DatabaseName = + @DatabaseName, + StartDate = + @StartDate, + EndDate = + @EndDate, + ObjectName = + @ObjectName, + StoredProcName = + @StoredProcName, + AppName = + @AppName, + HostName = + @HostName, + LoginName = + @LoginName, + EventSessionName = + @EventSessionName, + TargetSessionType = + @TargetSessionType, + VictimsOnly = + @VictimsOnly, + Debug = + @Debug, + Help = + @Help, + Version = + @Version, + VersionDate = + @VersionDate, + VersionCheckMode = + @VersionCheckMode, + OutputDatabaseName = + @OutputDatabaseName, + OutputSchemaName = + @OutputSchemaName, + OutputTableName = + @OutputTableName, + ExportToExcel = + @ExportToExcel; + + SELECT + declared_variables = + 'declared_variables', + DatabaseId = + @DatabaseId, + StartDateUTC = + @StartDateUTC, + EndDateUTC = + @EndDateUTC, + ProductVersion = + @ProductVersion, + ProductVersionMajor = + @ProductVersionMajor, + ProductVersionMinor = + @ProductVersionMinor, + ObjectFullName = + @ObjectFullName, + Azure = + @Azure, + RDS = + @RDS, + d = + @d, + StringToExecute = + @StringToExecute, + StringToExecuteParams = + @StringToExecuteParams, + r = + @r, + OutputTableFindings = + @OutputTableFindings, + DeadlockCount = + @DeadlockCount, + ServerName = + @ServerName, + OutputDatabaseCheck = + @OutputDatabaseCheck, + SessionId = + @SessionId, + TargetSessionId = + @TargetSessionId, + FileName = + @FileName, + inputbuf_bom = + @inputbuf_bom, + deadlock_result = + @deadlock_result; END; /*End debug*/ END; /*Final End*/ - GO IF OBJECT_ID('dbo.sp_BlitzWho') IS NULL EXEC ('CREATE PROCEDURE dbo.sp_BlitzWho AS RETURN 0;') @@ -29547,7 +30151,7 @@ BEGIN SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.15', @VersionDate = '20230613'; + SELECT @Version = '8.16', @VersionDate = '20230820'; IF(@VersionCheckMode = 1) BEGIN @@ -30943,12 +31547,17 @@ DELETE FROM dbo.SqlServerVersions; INSERT INTO dbo.SqlServerVersions (MajorVersionNumber, MinorVersionNumber, Branch, [Url], ReleaseDate, MainstreamSupportEndDate, ExtendedSupportEndDate, MajorVersionName, MinorVersionName) VALUES + (16, 4065, 'CU7', 'https://support.microsoft.com/en-us/help/5028743', '2023-08-10', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 7'), + (16, 4055, 'CU6', 'https://support.microsoft.com/en-us/help/5027505', '2023-07-13', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 6'), + (16, 4045, 'CU5', 'https://support.microsoft.com/en-us/help/5026806', '2023-06-15', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 5'), (16, 4035, 'CU4', 'https://support.microsoft.com/en-us/help/5026717', '2023-05-11', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 4'), (16, 4025, 'CU3', 'https://support.microsoft.com/en-us/help/5024396', '2023-04-13', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 3'), (16, 4015, 'CU2', 'https://support.microsoft.com/en-us/help/5023127', '2023-03-15', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 2'), (16, 4003, 'CU1', 'https://support.microsoft.com/en-us/help/5022375', '2023-02-16', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 1'), (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'), + (15, 4322, 'CU22', 'https://support.microsoft.com/kb/5027702', '2023-08-14', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 22'), + (15, 4316, 'CU21', 'https://support.microsoft.com/kb/5025808', '2023-06-15', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 21'), (15, 4312, 'CU20', 'https://support.microsoft.com/kb/5024276', '2023-04-13', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 20'), (15, 4298, 'CU19', 'https://support.microsoft.com/kb/5023049', '2023-02-16', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 19'), (15, 4280, 'CU18 GDR', 'https://support.microsoft.com/kb/5021124', '2023-02-14', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 18 GDR'), @@ -31371,7 +31980,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.15', @VersionDate = '20230613'; +SELECT @Version = '8.16', @VersionDate = '20230820'; IF(@VersionCheckMode = 1) BEGIN diff --git a/Install-Core-Blitz-With-Query-Store.sql b/Install-Core-Blitz-With-Query-Store.sql index b06e07543..bc39b6f6a 100644 --- a/Install-Core-Blitz-With-Query-Store.sql +++ b/Install-Core-Blitz-With-Query-Store.sql @@ -38,7 +38,7 @@ AS SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.15', @VersionDate = '20230613'; + SELECT @Version = '8.16', @VersionDate = '20230820'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -186,11 +186,129 @@ AS ,@CurrentComponentVersionCheckModeOK BIT ,@canExitLoop BIT ,@frkIsConsistent BIT - ,@NeedToTurnNumericRoundabortBackOn BIT; + ,@NeedToTurnNumericRoundabortBackOn BIT + ,@sa bit = 1 + ,@SUSER_NAME sysname = SUSER_SNAME() + ,@SkipDBCC bit = 0 + ,@SkipTrace bit = 0 + ,@SkipXPRegRead bit = 0 + ,@SkipXPFixedDrives bit = 0 + ,@SkipXPCMDShell bit = 0 + ,@SkipMaster bit = 0 + ,@SkipMSDB bit = 0 + ,@SkipModel bit = 0 + ,@SkipTempDB bit = 0 + ,@SkipValidateLogins bit = 0; + + DECLARE + @db_perms table + ( + database_name sysname, + permission_name sysname + ); + + INSERT + @db_perms + ( + database_name, + permission_name + ) + SELECT + database_name = + DB_NAME(d.database_id), + fmp.permission_name + FROM sys.databases AS d + CROSS APPLY fn_my_permissions(d.name, 'DATABASE') AS fmp + WHERE fmp.permission_name = N'SELECT' /*Databases where we don't have read permissions*/ /* End of declarations for First Responder Kit consistency check:*/ ; + /*Starting permissions checks here, but only if we're not a sysadmin*/ + IF + ( + SELECT + sa = + ISNULL + ( + IS_SRVROLEMEMBER(N'sysadmin'), + 0 + ) + ) = 0 + BEGIN + IF @Debug IN (1, 2) RAISERROR('User not SA, checking permissions', 0, 1) WITH NOWAIT; + + SET @sa = 0; /*Setting this to 0 to skip DBCC COMMANDS*/ + + IF NOT EXISTS + ( + SELECT + 1/0 + FROM sys.fn_my_permissions(NULL, NULL) AS fmp + WHERE fmp.permission_name = N'VIEW SERVER STATE' + ) + BEGIN + RAISERROR('The user %s does not have VIEW SERVER STATE permissions.', 0, 11, @SUSER_NAME) WITH NOWAIT; + RETURN; + END; /*If we don't have this, we can't do anything at all.*/ + + IF NOT EXISTS + ( + SELECT + 1/0 + FROM fn_my_permissions(N'sys.traces', N'OBJECT') AS fmp + WHERE fmp.permission_name = N'ALTER' + ) + BEGIN + SET @SkipTrace = 1; + END; /*We need this permission to execute trace stuff, apparently*/ + + IF NOT EXISTS + ( + SELECT + 1/0 + FROM fn_my_permissions(N'xp_regread', N'OBJECT') AS fmp + WHERE fmp.permission_name = N'EXECUTE' + ) + BEGIN + SET @SkipXPRegRead = 1; + END; /*Need execute on xp_regread*/ + + IF NOT EXISTS + ( + SELECT + 1/0 + FROM fn_my_permissions(N'xp_fixeddrives', N'OBJECT') AS fmp + WHERE fmp.permission_name = N'EXECUTE' + ) + BEGIN + SET @SkipXPFixedDrives = 1; + END; /*Need execute on xp_fixeddrives*/ + + IF NOT EXISTS + ( + SELECT + 1/0 + FROM fn_my_permissions(N'xp_cmdshell', N'OBJECT') AS fmp + WHERE fmp.permission_name = N'EXECUTE' + ) + BEGIN + SET @SkipXPCMDShell = 1; + END; /*Need execute on xp_cmdshell*/ + + IF NOT EXISTS + ( + SELECT + 1/0 + FROM fn_my_permissions(N'sp_validatelogins', N'OBJECT') AS fmp + WHERE fmp.permission_name = N'EXECUTE' + ) + BEGIN + SET @SkipValidateLogins = 1; + END; /*Need execute on sp_validatelogins*/ + + END; + SET @crlf = NCHAR(13) + NCHAR(10); SET @ResultText = 'sp_Blitz Results: ' + @crlf; @@ -331,6 +449,66 @@ AS OR LOWER(d.name) IN ('dbatools', 'dbadmin', 'dbmaintenance')) OPTION(RECOMPILE); + /*Skip checks for database where we don't have read permissions*/ + INSERT INTO + #SkipChecks + ( + DatabaseName + ) + SELECT + DB_NAME(d.database_id) + FROM sys.databases AS d + WHERE NOT EXISTS + ( + SELECT + 1/0 + FROM @db_perms AS dp + WHERE dp.database_name = DB_NAME(d.database_id) + ); + + /*Skip individial checks where we don't have permissions*/ + INSERT #SkipChecks (DatabaseName, CheckID, ServerName) + SELECT + v.* + FROM (VALUES(NULL, 29, NULL)) AS v (DatabaseName, CheckID, ServerName) /*Looks for user tables in model*/ + WHERE NOT EXISTS (SELECT 1/0 FROM @db_perms AS dp WHERE dp.database_name = 'model'); + + INSERT #SkipChecks (DatabaseName, CheckID, ServerName) + SELECT + v.* + FROM (VALUES(NULL, 68, NULL)) AS v (DatabaseName, CheckID, ServerName) /*DBCC command*/ + WHERE @sa = 0; + + INSERT #SkipChecks (DatabaseName, CheckID, ServerName) + SELECT + v.* + FROM (VALUES(NULL, 69, NULL)) AS v (DatabaseName, CheckID, ServerName) /*DBCC command*/ + WHERE @sa = 0; + + INSERT #SkipChecks (DatabaseName, CheckID, ServerName) + SELECT + v.* + FROM (VALUES(NULL, 92, NULL)) AS v (DatabaseName, CheckID, ServerName) /*xp_fixeddrives*/ + WHERE @SkipXPFixedDrives = 1; + + INSERT #SkipChecks (DatabaseName, CheckID, ServerName) + SELECT + v.* + FROM (VALUES(NULL, 211, NULL)) AS v (DatabaseName, CheckID, ServerName) /*xp_regread*/ + WHERE @SkipXPRegRead = 1; + + INSERT #SkipChecks (DatabaseName, CheckID, ServerName) + SELECT + v.* + FROM (VALUES(NULL, 212, NULL)) AS v (DatabaseName, CheckID, ServerName) /*xp_regread*/ + WHERE @SkipXPCMDShell = 1; + + INSERT #SkipChecks (DatabaseName, CheckID, ServerName) + SELECT + v.* + FROM (VALUES(NULL, 2301, NULL)) AS v (DatabaseName, CheckID, ServerName) /*sp_validatelogins*/ + WHERE @SkipValidateLogins = 1 + IF(OBJECT_ID('tempdb..#InvalidLogins') IS NOT NULL) BEGIN EXEC sp_executesql N'DROP TABLE #InvalidLogins;'; @@ -372,7 +550,8 @@ AS SELECT @IsWindowsOperatingSystem = 1 ; END; - IF NOT EXISTS ( SELECT 1 + + IF NOT EXISTS ( SELECT 1 FROM #SkipChecks WHERE DatabaseName IS NULL AND CheckID = 106 ) AND (select convert(int,value_in_use) from sys.configurations where name = 'default trace enabled' ) = 1 @@ -4158,53 +4337,56 @@ AS /* First, let's check that there aren't any issues with the trace files */ BEGIN TRY - - INSERT INTO #fnTraceGettable - ( TextData , - DatabaseName , - EventClass , - Severity , - StartTime , - EndTime , - Duration , - NTUserName , - NTDomainName , - HostName , - ApplicationName , - LoginName , - DBUserName - ) - SELECT TOP 20000 - CONVERT(NVARCHAR(4000),t.TextData) , - t.DatabaseName , - t.EventClass , - t.Severity , - t.StartTime , - t.EndTime , - t.Duration , - t.NTUserName , - t.NTDomainName , - t.HostName , - t.ApplicationName , - t.LoginName , - t.DBUserName - FROM sys.fn_trace_gettable(@base_tracefilename, DEFAULT) t - WHERE - ( - t.EventClass = 22 - AND t.Severity >= 17 - AND t.StartTime > DATEADD(dd, -30, GETDATE()) - ) - OR - ( - t.EventClass IN (92, 93) - AND t.StartTime > DATEADD(dd, -30, GETDATE()) - AND t.Duration > 15000000 - ) - OR - ( - t.EventClass IN (94, 95, 116) - ) + + IF @SkipTrace = 0 + BEGIN + INSERT INTO #fnTraceGettable + ( TextData , + DatabaseName , + EventClass , + Severity , + StartTime , + EndTime , + Duration , + NTUserName , + NTDomainName , + HostName , + ApplicationName , + LoginName , + DBUserName + ) + SELECT TOP 20000 + CONVERT(NVARCHAR(4000),t.TextData) , + t.DatabaseName , + t.EventClass , + t.Severity , + t.StartTime , + t.EndTime , + t.Duration , + t.NTUserName , + t.NTDomainName , + t.HostName , + t.ApplicationName , + t.LoginName , + t.DBUserName + FROM sys.fn_trace_gettable(@base_tracefilename, DEFAULT) t + WHERE + ( + t.EventClass = 22 + AND t.Severity >= 17 + AND t.StartTime > DATEADD(dd, -30, GETDATE()) + ) + OR + ( + t.EventClass IN (92, 93) + AND t.StartTime > DATEADD(dd, -30, GETDATE()) + AND t.Duration > 15000000 + ) + OR + ( + t.EventClass IN (94, 95, 116) + ) + END; SET @TraceFileIssue = 0 @@ -6579,10 +6761,10 @@ IF @ProductVersionMajor >= 10 DatabaseName FROM #SkipChecks WHERE CheckID IS NULL OR CheckID = 19) - AND is_published = 1 + AND (is_published = 1 OR is_subscribed = 1 OR is_merge_published = 1 - OR is_distributor = 1; + OR is_distributor = 1); /* Method B: check subscribers for MSreplication_objects tables */ EXEC dbo.sp_MSforeachdb 'USE [?]; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; @@ -9737,7 +9919,7 @@ AS SET NOCOUNT ON; SET STATISTICS XML OFF; -SELECT @Version = '8.15', @VersionDate = '20230613'; +SELECT @Version = '8.16', @VersionDate = '20230820'; IF(@VersionCheckMode = 1) BEGIN @@ -10615,7 +10797,7 @@ AS SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.15', @VersionDate = '20230613'; + SELECT @Version = '8.16', @VersionDate = '20230820'; IF(@VersionCheckMode = 1) BEGIN @@ -12397,7 +12579,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.15', @VersionDate = '20230613'; +SELECT @Version = '8.16', @VersionDate = '20230820'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -13695,6 +13877,7 @@ BEGIN RAISERROR('Checking for Read intent databases to exclude',0,0) WITH NOWAIT; EXEC('INSERT INTO #ReadableDBs (database_id) SELECT DBs.database_id FROM sys.databases DBs INNER JOIN sys.availability_replicas Replicas ON DBs.replica_id = Replicas.replica_id WHERE replica_server_name NOT IN (SELECT DISTINCT primary_replica FROM sys.dm_hadr_availability_group_states States) AND Replicas.secondary_role_allow_connections_desc = ''READ_ONLY'' AND replica_server_name = @@SERVERNAME OPTION (RECOMPILE);'); + EXEC('INSERT INTO #ReadableDBs VALUES (32767) ;'); -- Exclude internal resource database as well END RAISERROR(N'Checking plan cache age', 0, 1) WITH NOWAIT; @@ -13731,10 +13914,10 @@ WITH total_plans AS SELECT COUNT_BIG(qs.query_plan_hash) AS duplicate_plan_hashes FROM sys.dm_exec_query_stats qs - LEFT JOIN sys.dm_exec_procedure_stats ps - ON qs.sql_handle = ps.sql_handle + LEFT JOIN sys.dm_exec_procedure_stats ps ON qs.plan_handle = ps.plan_handle CROSS APPLY sys.dm_exec_plan_attributes(qs.plan_handle) pa WHERE pa.attribute = N'dbid' + AND pa.value <> 32767 /*Omit Resource database-based queries, we're not going to "fix" them no matter what. Addresses #3314*/ AND qs.query_plan_hash <> 0x0000000000000000 GROUP BY /* qs.query_plan_hash, BGO 20210524 commenting this out to fix #2909 */ @@ -14439,7 +14622,7 @@ BEGIN max_grant_kb AS MaxGrantKB, min_used_grant_kb AS MinUsedGrantKB, max_used_grant_kb AS MaxUsedGrantKB, - CAST(ISNULL(NULLIF(( max_used_grant_kb * 1.00 ), 0) / NULLIF(min_grant_kb, 0), 0) * 100. AS MONEY) AS PercentMemoryGrantUsed, + CAST(ISNULL(NULLIF(( total_used_grant_kb * 1.00 ), 0) / NULLIF(total_grant_kb, 0), 0) * 100. AS MONEY) AS PercentMemoryGrantUsed, CAST(ISNULL(NULLIF(( total_grant_kb * 1. ), 0) / NULLIF(execution_count, 0), 0) AS MONEY) AS AvgMaxMemoryGrant, '; END; ELSE @@ -19715,7 +19898,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.15', @VersionDate = '20230613'; +SELECT @Version = '8.16', @VersionDate = '20230820'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -22625,7 +22808,8 @@ BEGIN + CASE WHEN @ShowPartitionRanges = 1 THEN N' COALESCE(range_start_op + '' '' + range_start + '' '', '''') + COALESCE(range_end_op + '' '' + range_end, '''') AS partition_range, ' ELSE N' ' END + N' row_group_id, total_rows, deleted_rows, ' + @ColumnList - + CASE WHEN @ShowPartitionRanges = 1 THEN N' + + CASE WHEN @ShowPartitionRanges = 1 THEN N' , + state_desc, trim_reason_desc, transition_to_compressed_state_desc, has_vertipaq_optimization FROM ( SELECT column_name, partition_number, row_group_id, total_rows, deleted_rows, details, range_start_op, @@ -22635,10 +22819,12 @@ BEGIN range_end_op, CASE WHEN format_type IS NULL THEN CAST(range_end_value AS NVARCHAR(4000)) - ELSE CONVERT(NVARCHAR(4000), range_end_value, format_type) END range_end' ELSE N' ' END + N' + ELSE CONVERT(NVARCHAR(4000), range_end_value, format_type) END range_end' ELSE N' ' END + N', + state_desc, trim_reason_desc, transition_to_compressed_state_desc, has_vertipaq_optimization FROM ( SELECT c.name AS column_name, p.partition_number, rg.row_group_id, rg.total_rows, rg.deleted_rows, - details = CAST(seg.min_data_id AS VARCHAR(20)) + '' to '' + CAST(seg.max_data_id AS VARCHAR(20)) + '', '' + CAST(CAST((seg.on_disk_size / 1024.0 / 1024) AS DECIMAL(18,0)) AS VARCHAR(20)) + '' MB''' + phys.state_desc, phys.trim_reason_desc, phys.transition_to_compressed_state_desc, phys.has_vertipaq_optimization, + details = CAST(seg.min_data_id AS VARCHAR(20)) + '' to '' + CAST(seg.max_data_id AS VARCHAR(20)) + '', '' + CAST(CAST(((COALESCE(d.on_disk_size,0) + COALESCE(seg.on_disk_size,0)) / 1024.0 / 1024) AS DECIMAL(18,0)) AS VARCHAR(20)) + '' MB''' + CASE WHEN @ShowPartitionRanges = 1 THEN N', CASE WHEN pp.system_type_id IN (40, 41, 42, 43, 58, 61) THEN 126 @@ -22652,7 +22838,8 @@ BEGIN FROM ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_row_groups rg INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns c ON rg.object_id = c.object_id INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partitions p ON rg.object_id = p.object_id AND rg.partition_number = p.partition_number - INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.index_columns ic on ic.column_id = c.column_id AND ic.object_id = c.object_id AND ic.index_id = p.index_id ' + CASE WHEN @ShowPartitionRanges = 1 THEN N' + INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.index_columns ic on ic.column_id = c.column_id AND ic.object_id = c.object_id AND ic.index_id = p.index_id + LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_column_store_row_group_physical_stats phys ON rg.row_group_id = phys.row_group_id AND rg.object_id = phys.object_id AND rg.partition_number = phys.partition_number AND p.index_id = phys.index_id ' + CASE WHEN @ShowPartitionRanges = 1 THEN N' LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.indexes i ON i.object_id = rg.object_id AND i.index_id = rg.index_id LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partition_schemes ps ON ps.data_space_id = i.data_space_id LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partition_functions pf ON pf.function_id = ps.function_id @@ -22660,6 +22847,7 @@ BEGIN LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partition_range_values prvs ON prvs.function_id = pf.function_id AND prvs.boundary_id = p.partition_number - 1 LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partition_range_values prve ON prve.function_id = pf.function_id AND prve.boundary_id = p.partition_number ' ELSE N' ' END + N' LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_segments seg ON p.partition_id = seg.partition_id AND ic.index_column_id = seg.column_id AND rg.row_group_id = seg.segment_id + LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_dictionaries d ON p.hobt_id = d.hobt_id AND c.column_id = d.column_id AND seg.secondary_dictionary_id = d.dictionary_id WHERE rg.object_id = @ObjectID AND rg.state IN (1, 2, 3) AND c.name IN ( ' + @ColumnListWithApostrophes + N')' @@ -22697,6 +22885,9 @@ BEGIN RAISERROR(N'Done visualizing columnstore index contents.', 0,1) WITH NOWAIT; END + IF @ShowColumnstoreOnly = 1 + RETURN; + END; /* IF @TableName IS NOT NULL */ @@ -25892,7 +26083,7 @@ BEGIN SET NOCOUNT, XACT_ABORT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.15', @VersionDate = '20230613'; + SELECT @Version = '8.16', @VersionDate = '20230820'; IF @VersionCheckMode = 1 BEGIN @@ -26036,7 +26227,11 @@ BEGIN @TargetSessionId int = 0, @FileName nvarchar(4000) = N'', @inputbuf_bom nvarchar(1) = CONVERT(nvarchar(1), 0x0a00, 0), - @deadlock_result nvarchar(MAX) = N''; + @deadlock_result nvarchar(MAX) = N'', + @StartDateOriginal datetime = @StartDate, + @EndDateOriginal datetime = @EndDate, + @StartDateUTC datetime, + @EndDateUTC datetime; /*Temporary objects used in the procedure*/ DECLARE @@ -26076,15 +26271,17 @@ BEGIN database_name nvarchar(256), object_name nvarchar(1000), finding_group nvarchar(100), - finding nvarchar(4000) + finding nvarchar(4000), + sort_order bigint ); /*Set these to some sane defaults if NULLs are passed in*/ /*Normally I'd hate this, but we RECOMPILE everything*/ + SELECT @StartDate = - CASE - WHEN @StartDate IS NULL + CASE + WHEN @StartDate IS NULL THEN DATEADD ( @@ -26095,18 +26292,25 @@ BEGIN SYSDATETIME(), GETUTCDATE() ), - ISNULL + DATEADD ( - @StartDate, - DATEADD - ( - DAY, - -7, - SYSDATETIME() - ) + DAY, + -7, + SYSDATETIME() ) ) - ELSE @StartDate + ELSE + DATEADD + ( + MINUTE, + DATEDIFF + ( + MINUTE, + SYSDATETIME(), + GETUTCDATE() + ), + @StartDate + ) END, @EndDate = CASE @@ -26121,15 +26325,62 @@ BEGIN SYSDATETIME(), GETUTCDATE() ), - ISNULL + SYSDATETIME() + ) + ELSE + DATEADD + ( + MINUTE, + DATEDIFF ( - @EndDate, - SYSDATETIME() - ) + MINUTE, + SYSDATETIME(), + GETUTCDATE() + ), + @EndDate ) - ELSE @EndDate END; + SELECT + @StartDateUTC = @StartDate, + @EndDateUTC = @EndDate; + + IF @Azure = 0 + BEGIN + IF NOT EXISTS + ( + SELECT + 1/0 + FROM sys.server_event_sessions AS ses + JOIN sys.dm_xe_sessions AS dxs + ON dxs.name = ses.name + WHERE ses.name = @EventSessionName + AND dxs.create_time IS NOT NULL + ) + BEGIN + RAISERROR('A session with the name %s does not exist or is not currently active.', 11, 1, @EventSessionName) WITH NOWAIT; + RETURN; + END; + END; + + IF @Azure = 1 + BEGIN + IF NOT EXISTS + ( + SELECT + 1/0 + FROM sys.database_event_sessions AS ses + JOIN sys.dm_xe_database_sessions AS dxs + ON dxs.name = ses.name + WHERE ses.name = @EventSessionName + AND dxs.create_time IS NOT NULL + ) + BEGIN + RAISERROR('A session with the name %s does not exist or is not currently active.', 11, 1, @EventSessionName) WITH NOWAIT; + RETURN; + END; + END; + IF @OutputDatabaseName IS NOT NULL BEGIN /*IF databaseName is set, do some sanity checks and put [] around def.*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); @@ -26712,8 +26963,7 @@ BEGIN OR e.x.exist('@name[ .= "database_xml_deadlock_report"]') = 1 OR e.x.exist('@name[ .= "xml_deadlock_report_filtered"]') = 1 ) - AND e.x.exist('@timestamp[. >= sql:variable("@StartDate")]') = 1 - AND e.x.exist('@timestamp[. < sql:variable("@EndDate")]') = 1 + AND e.x.exist('@timestamp[. >= sql:variable("@StartDate") and .< sql:variable("@EndDate")]') = 1 OPTION(RECOMPILE); SET @d = CONVERT(varchar(40), GETDATE(), 109); @@ -26730,9 +26980,9 @@ BEGIN SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Inserting to #deadlock_data for event file data', 0, 1) WITH NOWAIT; - IF @Debug = 1 BEGIN SET STATISTICS XML ON; END; + IF @Debug = 1 BEGIN SET STATISTICS XML ON; END; - INSERT + INSERT #deadlock_data WITH(TABLOCKX) ( deadlock_xml @@ -26750,13 +27000,12 @@ BEGIN OR e.x.exist('@name[ .= "database_xml_deadlock_report"]') = 1 OR e.x.exist('@name[ .= "xml_deadlock_report_filtered"]') = 1 ) - AND e.x.exist('@timestamp[. >= sql:variable("@StartDate")]') = 1 - AND e.x.exist('@timestamp[. < sql:variable("@EndDate")]') = 1 + AND e.x.exist('@timestamp[. >= sql:variable("@StartDate") and .< sql:variable("@EndDate")]') = 1 OPTION(RECOMPILE); - IF @Debug = 1 BEGIN SET STATISTICS XML OFF; END; + IF @Debug = 1 BEGIN SET STATISTICS XML OFF; END; - SET @d = CONVERT(varchar(40), GETDATE(), 109); + SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; END; @@ -26787,8 +27036,7 @@ BEGIN ) AS xml CROSS APPLY xml.deadlock_xml.nodes('/event') AS e(x) WHERE 1 = 1 - AND e.x.exist('@timestamp[. >= sql:variable("@StartDate")]') = 1 - AND e.x.exist('@timestamp[. < sql:variable("@EndDate")]') = 1 + AND e.x.exist('@timestamp[. >= sql:variable("@StartDate") and .< sql:variable("@EndDate")]') = 1 OPTION(RECOMPILE); INSERT @@ -26915,7 +27163,12 @@ BEGIN DATEADD ( MINUTE, - DATEDIFF(MINUTE, GETUTCDATE(), SYSDATETIME()), + DATEDIFF + ( + MINUTE, + GETUTCDATE(), + SYSDATETIME() + ), dd.event_date ), dd.victim_id, @@ -26973,6 +27226,7 @@ BEGIN FROM #deadlock_process AS dp CROSS APPLY dp.process_xml.nodes('//executionStack/frame') AS ca(dp) WHERE (ca.dp.exist('@procname[. = sql:variable("@StoredProcName")]') = 1 OR @StoredProcName IS NULL) + AND ca.dp.exist('@sqlhandle[ .= "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"]') = 0 OPTION(RECOMPILE); SET @d = CONVERT(varchar(40), GETDATE(), 109); @@ -27045,7 +27299,7 @@ BEGIN waiter_mode = w.l.value('@mode', 'nvarchar(256)'), owner_id = o.l.value('@id', 'nvarchar(256)'), owner_mode = o.l.value('@mode', 'nvarchar(256)'), - lock_type = CAST(N'OBJECT' AS NVARCHAR(100)) + lock_type = CAST(N'OBJECT' AS nvarchar(100)) INTO #deadlock_owner_waiter FROM ( @@ -27441,13 +27695,19 @@ BEGIN 32 ), step_id = - SUBSTRING - ( - dp.client_app, - CHARINDEX(N': Step ', dp.client_app) + LEN(N': Step '), - CHARINDEX(N')', dp.client_app, CHARINDEX(N': Step ', dp.client_app)) - - (CHARINDEX(N': Step ', dp.client_app) + LEN(N': Step ')) - ) + CASE + WHEN CHARINDEX(N': Step ', dp.client_app) > 0 + AND CHARINDEX(N')', dp.client_app, CHARINDEX(N': Step ', dp.client_app)) > 0 + THEN + SUBSTRING + ( + dp.client_app, + CHARINDEX(N': Step ', dp.client_app) + LEN(N': Step '), + CHARINDEX(N')', dp.client_app, CHARINDEX(N': Step ', dp.client_app)) - + (CHARINDEX(N': Step ', dp.client_app) + LEN(N': Step ')) + ) + ELSE dp.client_app + END FROM #deadlock_process AS dp WHERE dp.client_app LIKE N'SQLAgent - %' AND dp.client_app <> N'SQLAgent - Initial Boot Probe' @@ -27597,6 +27857,14 @@ BEGIN /*Begin checks based on parsed values*/ + /* + First, revert these back since we already converted the event data to local time, + and searches will break if we use the times converted over to UTC for the event data + */ + SELECT + @StartDate = @StartDateOriginal, + @EndDate = @EndDateOriginal; + /*Check 1 is deadlocks by database*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Check 1 database deadlocks %s', 0, 1, @d) WITH NOWAIT; @@ -27608,7 +27876,8 @@ BEGIN database_name, object_name, finding_group, - finding + finding, + sort_order ) SELECT check_id = 1, @@ -27622,7 +27891,10 @@ BEGIN nvarchar(20), COUNT_BIG(DISTINCT dp.event_date) ) + - N' deadlocks.' + N' deadlocks.', + sort_order = + ROW_NUMBER() + OVER (ORDER BY COUNT_BIG(DISTINCT dp.event_date) DESC) FROM #deadlock_process AS dp WHERE 1 = 1 AND (dp.database_name = @DatabaseName OR @DatabaseName IS NULL) @@ -27647,7 +27919,8 @@ BEGIN database_name, object_name, finding_group, - finding + finding, + sort_order ) SELECT check_id = 2, @@ -27662,7 +27935,10 @@ BEGIN nvarchar(20), COUNT_BIG(DISTINCT dow.event_date) ) + - N' deadlock(s) between read queries and modification queries.' + N' deadlock(s) between read queries and modification queries.', + sort_order = + ROW_NUMBER() + OVER (ORDER BY COUNT_BIG(DISTINCT dow.event_date) DESC) FROM #deadlock_owner_waiter AS dow WHERE 1 = 1 AND dow.lock_mode IN @@ -27701,7 +27977,8 @@ BEGIN database_name, object_name, finding_group, - finding + finding, + sort_order ) SELECT check_id = 3, @@ -27720,7 +27997,10 @@ BEGIN nvarchar(20), COUNT_BIG(DISTINCT dow.event_date) ) + - N' deadlock(s).' + N' deadlock(s).', + sort_order = + ROW_NUMBER() + OVER (ORDER BY COUNT_BIG(DISTINCT dow.event_date) DESC) FROM #deadlock_owner_waiter AS dow WHERE 1 = 1 AND (dow.database_id = @DatabaseId OR @DatabaseName IS NULL) @@ -27745,7 +28025,8 @@ BEGIN database_name, object_name, finding_group, - finding + finding, + sort_order ) SELECT check_id = 3, @@ -27759,7 +28040,10 @@ BEGIN nvarchar(20), COUNT_BIG(DISTINCT dow.event_date) ) + - N' deadlock(s).' + N' deadlock(s).', + sort_order = + ROW_NUMBER() + OVER (ORDER BY COUNT_BIG(DISTINCT dow.event_date) DESC) FROM #deadlock_owner_waiter AS dow WHERE 1 = 1 AND (dow.database_id = @DatabaseId OR @DatabaseName IS NULL) @@ -27790,7 +28074,8 @@ BEGIN database_name, object_name, finding_group, - finding + finding, + sort_order ) SELECT check_id = 3, @@ -27804,7 +28089,10 @@ BEGIN nvarchar(20), COUNT_BIG(DISTINCT dow.event_date) ) + - N' deadlock(s).' + N' deadlock(s).', + sort_order = + ROW_NUMBER() + OVER (ORDER BY COUNT_BIG(DISTINCT dow.event_date) DESC) FROM #deadlock_owner_waiter AS dow WHERE 1 = 1 AND (dow.database_id = @DatabaseId OR @DatabaseName IS NULL) @@ -27834,7 +28122,8 @@ BEGIN database_name, object_name, finding_group, - finding + finding, + sort_order ) SELECT check_id = 4, @@ -27849,7 +28138,10 @@ BEGIN nvarchar(20), COUNT_BIG(DISTINCT dp.event_date) ) + - N' instances of Serializable deadlocks.' + N' instances of Serializable deadlocks.', + sort_order = + ROW_NUMBER() + OVER (ORDER BY COUNT_BIG(DISTINCT dp.event_date) DESC) FROM #deadlock_process AS dp WHERE dp.isolation_level LIKE N'serializable%' AND (dp.database_name = @DatabaseName OR @DatabaseName IS NULL) @@ -27874,7 +28166,8 @@ BEGIN database_name, object_name, finding_group, - finding + finding, + sort_order ) SELECT check_id = 5, @@ -27888,7 +28181,10 @@ BEGIN nvarchar(20), COUNT_BIG(DISTINCT dp.event_date) ) + - N' instances of Repeatable Read deadlocks.' + N' instances of Repeatable Read deadlocks.', + sort_order = + ROW_NUMBER() + OVER (ORDER BY COUNT_BIG(DISTINCT dp.event_date) DESC) FROM #deadlock_process AS dp WHERE dp.isolation_level LIKE N'repeatable%' AND (dp.database_name = @DatabaseName OR @DatabaseName IS NULL) @@ -27913,7 +28209,8 @@ BEGIN database_name, object_name, finding_group, - finding + finding, + sort_order ) SELECT check_id = 6, @@ -27946,7 +28243,10 @@ BEGIN dp.host_name, N'UNKNOWN' ) + - N'.' + N'.', + sort_order = + ROW_NUMBER() + OVER (ORDER BY COUNT_BIG(DISTINCT dp.event_date) DESC) FROM #deadlock_process AS dp WHERE 1 = 1 AND (dp.database_name = @DatabaseName OR @DatabaseName IS NULL) @@ -28027,7 +28327,8 @@ BEGIN database_name, object_name, finding_group, - finding + finding, + sort_order ) SELECT check_id = 7, @@ -28054,7 +28355,10 @@ BEGIN 1, 1, N'' - ) + N' locks.' + ) + N' locks.', + sort_order = + ROW_NUMBER() + OVER (ORDER BY CONVERT(bigint, lt.lock_count) DESC) FROM lock_types AS lt OPTION(RECOMPILE); @@ -28068,9 +28372,9 @@ BEGIN deadlock_stack AS ( SELECT DISTINCT - ds.id, - ds.proc_name, - ds.event_date, + ds.id, + ds.event_date, + ds.proc_name, database_name = PARSENAME(ds.proc_name, 3), schema_name = @@ -28104,8 +28408,8 @@ BEGIN PARSENAME(ds.proc_name, 3), PARSENAME(ds.proc_name, 2), PARSENAME(ds.proc_name, 1), - ds.id, ds.proc_name, + ds.id, ds.event_date ) INSERT @@ -28137,6 +28441,7 @@ BEGIN AND (dow.event_date >= @StartDate OR @StartDate IS NULL) AND (dow.event_date < @EndDate OR @EndDate IS NULL) AND (dow.object_name = @StoredProcName OR @StoredProcName IS NULL) + AND ds.proc_name NOT LIKE 'Unknown%' OPTION(RECOMPILE); RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; @@ -28209,7 +28514,8 @@ BEGIN database_name, object_name, finding_group, - finding + finding, + sort_order ) SELECT check_id = 9, @@ -28228,7 +28534,10 @@ BEGIN nvarchar(10), COUNT_BIG(DISTINCT ds.id) ) + - N' deadlocks.' + N' deadlocks.', + sort_order = + ROW_NUMBER() + OVER (ORDER BY COUNT_BIG(DISTINCT ds.id) DESC) FROM #deadlock_stack AS ds JOIN #deadlock_process AS dp ON dp.id = ds.id @@ -28327,19 +28636,19 @@ BEGIN ) ), wait_time_hms = - /*the more wait time you rack up the less accurate this gets, + /*the more wait time you rack up the less accurate this gets, it's either that or erroring out*/ - CASE - WHEN + CASE + WHEN SUM ( CONVERT ( - bigint, + bigint, dp.wait_time ) )/1000 > 2147483647 - THEN + THEN CONVERT ( nvarchar(30), @@ -28352,7 +28661,7 @@ BEGIN ( CONVERT ( - bigint, + bigint, dp.wait_time ) ) @@ -28363,16 +28672,16 @@ BEGIN ), 14 ) - WHEN + WHEN SUM ( CONVERT ( - bigint, + bigint, dp.wait_time ) ) BETWEEN 2147483648 AND 2147483647000 - THEN + THEN CONVERT ( nvarchar(30), @@ -28385,7 +28694,7 @@ BEGIN ( CONVERT ( - bigint, + bigint, dp.wait_time ) ) @@ -28417,7 +28726,9 @@ BEGIN ), 14 ) - END + END, + total_waits = + SUM(CONVERT(bigint, dp.wait_time)) FROM #deadlock_owner_waiter AS dow JOIN #deadlock_process AS dp ON (dp.id = dow.owner_id @@ -28442,7 +28753,8 @@ BEGIN database_name, object_name, finding_group, - finding + finding, + sort_order ) SELECT check_id = 11, @@ -28463,7 +28775,10 @@ BEGIN cs.wait_time_hms, 14 ) + - N' [dd hh:mm:ss:ms] of deadlock wait time.' + N' [dd hh:mm:ss:ms] of deadlock wait time.', + sort_order = + ROW_NUMBER() + OVER (ORDER BY cs.total_waits DESC) FROM chopsuey AS cs WHERE cs.object_name IS NOT NULL OPTION(RECOMPILE); @@ -28511,7 +28826,8 @@ BEGIN database_name, object_name, finding_group, - finding + finding, + sort_order ) SELECT check_id = 12, @@ -28534,19 +28850,19 @@ BEGIN ) ) + N' ' + - /*the more wait time you rack up the less accurate this gets, + /*the more wait time you rack up the less accurate this gets, it's either that or erroring out*/ - CASE - WHEN + CASE + WHEN SUM ( CONVERT ( - bigint, + bigint, wt.total_wait_time_ms ) )/1000 > 2147483647 - THEN + THEN CONVERT ( nvarchar(30), @@ -28559,7 +28875,7 @@ BEGIN ( CONVERT ( - bigint, + bigint, wt.total_wait_time_ms ) ) @@ -28570,16 +28886,16 @@ BEGIN ), 14 ) - WHEN + WHEN SUM ( CONVERT ( - bigint, + bigint, wt.total_wait_time_ms ) ) BETWEEN 2147483648 AND 2147483647000 - THEN + THEN CONVERT ( nvarchar(30), @@ -28592,7 +28908,7 @@ BEGIN ( CONVERT ( - bigint, + bigint, wt.total_wait_time_ms ) ) @@ -28624,7 +28940,10 @@ BEGIN ), 14 ) END + - N' [dd hh:mm:ss:ms] of deadlock wait time.' + N' [dd hh:mm:ss:ms] of deadlock wait time.', + sort_order = + ROW_NUMBER() + OVER (ORDER BY SUM(CONVERT(bigint, wt.total_wait_time_ms)) DESC) FROM wait_time AS wt GROUP BY wt.database_name @@ -28643,7 +28962,8 @@ BEGIN database_name, object_name, finding_group, - finding + finding, + sort_order ) SELECT check_id = 13, @@ -28658,7 +28978,10 @@ BEGIN finding = N'There have been ' + RTRIM(COUNT_BIG(DISTINCT aj.event_date)) + - N' deadlocks from this Agent Job and Step.' + N' deadlocks from this Agent Job and Step.', + sort_order = + ROW_NUMBER() + OVER (ORDER BY COUNT_BIG(DISTINCT aj.event_date) DESC) FROM #agent_job AS aj GROUP BY DB_NAME(aj.database_id), @@ -29202,7 +29525,8 @@ BEGIN d.waiter_waiting_to_close, /*end parallel deadlock columns*/ d.deadlock_graph, - d.is_victim + d.is_victim, + d.id INTO #deadlock_results FROM #deadlocks AS d; @@ -29405,26 +29729,202 @@ BEGIN DROP SYNONYM DeadlockFindings; /*done with inserting.*/ END; ELSE /*Output to database is not set output to client app*/ - BEGIN - SET @d = CONVERT(varchar(40), GETDATE(), 109); + BEGIN + SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Results to client %s', 0, 1, @d) WITH NOWAIT; - + IF @Debug = 1 BEGIN SET STATISTICS XML ON; END; - + EXEC sys.sp_executesql @deadlock_result; - + IF @Debug = 1 BEGIN SET STATISTICS XML OFF; PRINT @deadlock_result; END; - + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - + + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Getting available execution plans for deadlocks %s', 0, 1, @d) WITH NOWAIT; + + SELECT DISTINCT + available_plans = + 'available_plans', + ds.proc_name, + sql_handle = + CONVERT(varbinary(64), ds.sql_handle, 1), + dow.database_name, + dow.database_id, + dow.object_name, + query_xml = + TRY_CAST(dr.query_xml AS nvarchar(MAX)) + INTO #available_plans + FROM #deadlock_stack AS ds + JOIN #deadlock_owner_waiter AS dow + ON dow.owner_id = ds.id + AND dow.event_date = ds.event_date + JOIN #deadlock_results AS dr + ON dr.id = ds.id + AND dr.event_date = ds.event_date + OPTION(RECOMPILE); + + SELECT + deqs.sql_handle, + deqs.plan_handle, + deqs.statement_start_offset, + deqs.statement_end_offset, + deqs.creation_time, + deqs.last_execution_time, + deqs.execution_count, + total_worker_time_ms = + deqs.total_worker_time / 1000., + avg_worker_time_ms = + CONVERT(decimal(38, 6), deqs.total_worker_time / 1000. / deqs.execution_count), + total_elapsed_time_ms = + deqs.total_elapsed_time / 1000., + avg_elapsed_time = + CONVERT(decimal(38, 6), deqs.total_elapsed_time / 1000. / deqs.execution_count), + executions_per_second = + ISNULL + ( + deqs.execution_count / + NULLIF + ( + DATEDIFF + ( + SECOND, + deqs.creation_time, + deqs.last_execution_time + ), + 0 + ), + 0 + ), + total_physical_reads_mb = + deqs.total_physical_reads * 8. / 1024., + total_logical_writes_mb = + deqs.total_logical_writes * 8. / 1024., + total_logical_reads_mb = + deqs.total_logical_reads * 8. / 1024., + min_grant_mb = + deqs.min_grant_kb * 8. / 1024., + max_grant_mb = + deqs.max_grant_kb * 8. / 1024., + min_used_grant_mb = + deqs.min_used_grant_kb * 8. / 1024., + max_used_grant_mb = + deqs.max_used_grant_kb * 8. / 1024., + deqs.min_reserved_threads, + deqs.max_reserved_threads, + deqs.min_used_threads, + deqs.max_used_threads, + deqs.total_rows + INTO #dm_exec_query_stats + FROM sys.dm_exec_query_stats AS deqs + WHERE EXISTS + ( + SELECT + 1/0 + FROM #available_plans AS ap + WHERE ap.sql_handle = deqs.sql_handle + ) + AND deqs.query_hash IS NOT NULL; + + CREATE CLUSTERED INDEX + deqs + ON #dm_exec_query_stats + ( + sql_handle, + plan_handle + ); + + SELECT + ap.available_plans, + ap.database_name, + query_text = + TRY_CAST(ap.query_xml AS xml), + ap.query_plan, + ap.creation_time, + ap.last_execution_time, + ap.execution_count, + ap.executions_per_second, + ap.total_worker_time_ms, + ap.avg_worker_time_ms, + ap.total_elapsed_time_ms, + ap.avg_elapsed_time, + ap.total_logical_reads_mb, + ap.total_physical_reads_mb, + ap.total_logical_writes_mb, + ap.min_grant_mb, + ap.max_grant_mb, + ap.min_used_grant_mb, + ap.max_used_grant_mb, + ap.min_reserved_threads, + ap.max_reserved_threads, + ap.min_used_threads, + ap.max_used_threads, + ap.total_rows, + ap.sql_handle, + ap.statement_start_offset, + ap.statement_end_offset + FROM + ( + + SELECT + ap.*, + c.statement_start_offset, + c.statement_end_offset, + c.creation_time, + c.last_execution_time, + c.execution_count, + c.total_worker_time_ms, + c.avg_worker_time_ms, + c.total_elapsed_time_ms, + c.avg_elapsed_time, + c.executions_per_second, + c.total_physical_reads_mb, + c.total_logical_writes_mb, + c.total_logical_reads_mb, + c.min_grant_mb, + c.max_grant_mb, + c.min_used_grant_mb, + c.max_used_grant_mb, + c.min_reserved_threads, + c.max_reserved_threads, + c.min_used_threads, + c.max_used_threads, + c.total_rows, + c.query_plan + FROM #available_plans AS ap + OUTER APPLY + ( + SELECT + deqs.*, + query_plan = + TRY_CAST(deps.query_plan AS xml) + FROM #dm_exec_query_stats deqs + OUTER APPLY sys.dm_exec_text_query_plan + ( + deqs.plan_handle, + deqs.statement_start_offset, + deqs.statement_end_offset + ) AS deps + WHERE deqs.sql_handle = ap.sql_handle + AND deps.dbid = ap.database_id + ) AS c + ) AS ap + WHERE ap.query_plan IS NOT NULL + ORDER BY + ap.avg_worker_time_ms DESC + OPTION(RECOMPILE, LOOP JOIN, HASH JOIN); + + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Returning findings %s', 0, 1, @d) WITH NOWAIT; - + SELECT df.check_id, df.database_name, @@ -29432,26 +29932,28 @@ BEGIN df.finding_group, df.finding FROM #deadlock_findings AS df - ORDER BY df.check_id + ORDER BY + df.check_id, + df.sort_order OPTION(RECOMPILE); - + SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; END; /*done with output to client app.*/ - END; + END; IF @Debug = 1 BEGIN SELECT - table_name = N'#dd', + table_name = N'#deadlock_data', * - FROM #dd AS d + FROM #deadlock_data AS dd OPTION(RECOMPILE); SELECT - table_name = N'#deadlock_data', + table_name = N'#dd', * - FROM #deadlock_data AS dd + FROM #dd AS d OPTION(RECOMPILE); SELECT @@ -29508,9 +30010,111 @@ BEGIN FROM @sysAssObjId AS s OPTION(RECOMPILE); + SELECT + table_name = N'#available_plans', + * + FROM #available_plans AS ap + OPTION(RECOMPILE); + + SELECT + table_name = N'#dm_exec_query_stats', + * + FROM #dm_exec_query_stats + OPTION(RECOMPILE); + + SELECT + procedure_parameters = + 'procedure_parameters', + DatabaseName = + @DatabaseName, + StartDate = + @StartDate, + EndDate = + @EndDate, + ObjectName = + @ObjectName, + StoredProcName = + @StoredProcName, + AppName = + @AppName, + HostName = + @HostName, + LoginName = + @LoginName, + EventSessionName = + @EventSessionName, + TargetSessionType = + @TargetSessionType, + VictimsOnly = + @VictimsOnly, + Debug = + @Debug, + Help = + @Help, + Version = + @Version, + VersionDate = + @VersionDate, + VersionCheckMode = + @VersionCheckMode, + OutputDatabaseName = + @OutputDatabaseName, + OutputSchemaName = + @OutputSchemaName, + OutputTableName = + @OutputTableName, + ExportToExcel = + @ExportToExcel; + + SELECT + declared_variables = + 'declared_variables', + DatabaseId = + @DatabaseId, + StartDateUTC = + @StartDateUTC, + EndDateUTC = + @EndDateUTC, + ProductVersion = + @ProductVersion, + ProductVersionMajor = + @ProductVersionMajor, + ProductVersionMinor = + @ProductVersionMinor, + ObjectFullName = + @ObjectFullName, + Azure = + @Azure, + RDS = + @RDS, + d = + @d, + StringToExecute = + @StringToExecute, + StringToExecuteParams = + @StringToExecuteParams, + r = + @r, + OutputTableFindings = + @OutputTableFindings, + DeadlockCount = + @DeadlockCount, + ServerName = + @ServerName, + OutputDatabaseCheck = + @OutputDatabaseCheck, + SessionId = + @SessionId, + TargetSessionId = + @TargetSessionId, + FileName = + @FileName, + inputbuf_bom = + @inputbuf_bom, + deadlock_result = + @deadlock_result; END; /*End debug*/ END; /*Final End*/ - GO SET ANSI_NULLS ON; SET ANSI_PADDING ON; @@ -29571,7 +30175,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.15', @VersionDate = '20230613'; +SELECT @Version = '8.16', @VersionDate = '20230820'; IF(@VersionCheckMode = 1) BEGIN RETURN; @@ -35302,7 +35906,7 @@ BEGIN SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.15', @VersionDate = '20230613'; + SELECT @Version = '8.16', @VersionDate = '20230820'; IF(@VersionCheckMode = 1) BEGIN @@ -36698,12 +37302,17 @@ DELETE FROM dbo.SqlServerVersions; INSERT INTO dbo.SqlServerVersions (MajorVersionNumber, MinorVersionNumber, Branch, [Url], ReleaseDate, MainstreamSupportEndDate, ExtendedSupportEndDate, MajorVersionName, MinorVersionName) VALUES + (16, 4065, 'CU7', 'https://support.microsoft.com/en-us/help/5028743', '2023-08-10', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 7'), + (16, 4055, 'CU6', 'https://support.microsoft.com/en-us/help/5027505', '2023-07-13', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 6'), + (16, 4045, 'CU5', 'https://support.microsoft.com/en-us/help/5026806', '2023-06-15', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 5'), (16, 4035, 'CU4', 'https://support.microsoft.com/en-us/help/5026717', '2023-05-11', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 4'), (16, 4025, 'CU3', 'https://support.microsoft.com/en-us/help/5024396', '2023-04-13', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 3'), (16, 4015, 'CU2', 'https://support.microsoft.com/en-us/help/5023127', '2023-03-15', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 2'), (16, 4003, 'CU1', 'https://support.microsoft.com/en-us/help/5022375', '2023-02-16', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 1'), (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'), + (15, 4322, 'CU22', 'https://support.microsoft.com/kb/5027702', '2023-08-14', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 22'), + (15, 4316, 'CU21', 'https://support.microsoft.com/kb/5025808', '2023-06-15', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 21'), (15, 4312, 'CU20', 'https://support.microsoft.com/kb/5024276', '2023-04-13', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 20'), (15, 4298, 'CU19', 'https://support.microsoft.com/kb/5023049', '2023-02-16', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 19'), (15, 4280, 'CU18 GDR', 'https://support.microsoft.com/kb/5021124', '2023-02-14', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 18 GDR'), @@ -37126,7 +37735,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.15', @VersionDate = '20230613'; +SELECT @Version = '8.16', @VersionDate = '20230820'; IF(@VersionCheckMode = 1) BEGIN diff --git a/SqlServerVersions.sql b/SqlServerVersions.sql index 0b5f69ae5..cf6d224c5 100644 --- a/SqlServerVersions.sql +++ b/SqlServerVersions.sql @@ -50,6 +50,7 @@ VALUES (16, 4003, 'CU1', 'https://support.microsoft.com/en-us/help/5022375', '2023-02-16', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 1'), (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'), + (15, 4322, 'CU22', 'https://support.microsoft.com/kb/5027702', '2023-08-14', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 22'), (15, 4316, 'CU21', 'https://support.microsoft.com/kb/5025808', '2023-06-15', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 21'), (15, 4312, 'CU20', 'https://support.microsoft.com/kb/5024276', '2023-04-13', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 20'), (15, 4298, 'CU19', 'https://support.microsoft.com/kb/5023049', '2023-02-16', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 19'), diff --git a/sp_AllNightLog.sql b/sp_AllNightLog.sql index 5c4e72190..0a37f4f7a 100644 --- a/sp_AllNightLog.sql +++ b/sp_AllNightLog.sql @@ -31,7 +31,7 @@ SET STATISTICS XML OFF; BEGIN; -SELECT @Version = '8.15', @VersionDate = '20230613'; +SELECT @Version = '8.16', @VersionDate = '20230820'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_AllNightLog_Setup.sql b/sp_AllNightLog_Setup.sql index a1ddcec31..9df6a50cc 100644 --- a/sp_AllNightLog_Setup.sql +++ b/sp_AllNightLog_Setup.sql @@ -38,7 +38,7 @@ SET STATISTICS XML OFF; BEGIN; -SELECT @Version = '8.15', @VersionDate = '20230613'; +SELECT @Version = '8.16', @VersionDate = '20230820'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_Blitz.sql b/sp_Blitz.sql index c3190203c..552df1ee4 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -38,7 +38,7 @@ AS SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.15', @VersionDate = '20230613'; + SELECT @Version = '8.16', @VersionDate = '20230820'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) diff --git a/sp_BlitzAnalysis.sql b/sp_BlitzAnalysis.sql index 5c686a696..9cfb737cb 100644 --- a/sp_BlitzAnalysis.sql +++ b/sp_BlitzAnalysis.sql @@ -37,7 +37,7 @@ AS SET NOCOUNT ON; SET STATISTICS XML OFF; -SELECT @Version = '8.15', @VersionDate = '20230613'; +SELECT @Version = '8.16', @VersionDate = '20230820'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_BlitzBackups.sql b/sp_BlitzBackups.sql index c5d4932a3..17d081901 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.15', @VersionDate = '20230613'; + SELECT @Version = '8.16', @VersionDate = '20230820'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_BlitzCache.sql b/sp_BlitzCache.sql index 10d3013f6..ddd418e2a 100644 --- a/sp_BlitzCache.sql +++ b/sp_BlitzCache.sql @@ -281,7 +281,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.15', @VersionDate = '20230613'; +SELECT @Version = '8.16', @VersionDate = '20230820'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) diff --git a/sp_BlitzFirst.sql b/sp_BlitzFirst.sql index 5cfc95b03..0e822f1be 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.15', @VersionDate = '20230613'; +SELECT @Version = '8.16', @VersionDate = '20230820'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_BlitzInMemoryOLTP.sql b/sp_BlitzInMemoryOLTP.sql index 8dde49d89..919a72d6d 100644 --- a/sp_BlitzInMemoryOLTP.sql +++ b/sp_BlitzInMemoryOLTP.sql @@ -82,7 +82,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI */ AS DECLARE @ScriptVersion VARCHAR(30); -SELECT @ScriptVersion = '1.8', @VersionDate = '20230613'; +SELECT @ScriptVersion = '1.8', @VersionDate = '20230820'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index 5ad1e33a1..2c83589d1 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -48,7 +48,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.15', @VersionDate = '20230613'; +SELECT @Version = '8.16', @VersionDate = '20230820'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index b609731f5..6eb00b83c 100644 --- a/sp_BlitzLock.sql +++ b/sp_BlitzLock.sql @@ -35,7 +35,7 @@ BEGIN SET NOCOUNT, XACT_ABORT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.15', @VersionDate = '20230613'; + SELECT @Version = '8.16', @VersionDate = '20230820'; IF @VersionCheckMode = 1 BEGIN diff --git a/sp_BlitzQueryStore.sql b/sp_BlitzQueryStore.sql index 431711405..c600a1300 100644 --- a/sp_BlitzQueryStore.sql +++ b/sp_BlitzQueryStore.sql @@ -57,7 +57,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.15', @VersionDate = '20230613'; +SELECT @Version = '8.16', @VersionDate = '20230820'; IF(@VersionCheckMode = 1) BEGIN RETURN; diff --git a/sp_BlitzWho.sql b/sp_BlitzWho.sql index 5a65a607c..d433ddf5b 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.15', @VersionDate = '20230613'; + SELECT @Version = '8.16', @VersionDate = '20230820'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_DatabaseRestore.sql b/sp_DatabaseRestore.sql index 06968cade..646ed84e5 100755 --- a/sp_DatabaseRestore.sql +++ b/sp_DatabaseRestore.sql @@ -45,7 +45,7 @@ SET STATISTICS XML OFF; /*Versioning details*/ -SELECT @Version = '8.15', @VersionDate = '20230613'; +SELECT @Version = '8.16', @VersionDate = '20230820'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_ineachdb.sql b/sp_ineachdb.sql index 8ea9646e8..8e7890e85 100644 --- a/sp_ineachdb.sql +++ b/sp_ineachdb.sql @@ -35,7 +35,7 @@ BEGIN SET NOCOUNT ON; SET STATISTICS XML OFF; - SELECT @Version = '8.15', @VersionDate = '20230613'; + SELECT @Version = '8.16', @VersionDate = '20230820'; IF(@VersionCheckMode = 1) BEGIN From 513f73260ba840b18734f1d1306d59b7589fa7a7 Mon Sep 17 00:00:00 2001 From: Montro1981 Date: Tue, 5 Sep 2023 15:03:15 +0200 Subject: [PATCH 438/662] Issue #3326 Issue #3326 Added Check 106 to #SkipChecks when @SkipTrace = 1 --- sp_Blitz.sql | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index 552df1ee4..21ca3f646 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -491,6 +491,12 @@ AS FROM (VALUES(NULL, 92, NULL)) AS v (DatabaseName, CheckID, ServerName) /*xp_fixeddrives*/ WHERE @SkipXPFixedDrives = 1; + INSERT #SkipChecks (DatabaseName, CheckID, ServerName) + SELECT + v.* + FROM (VALUES(NULL, 106, NULL)) AS v (DatabaseName, CheckID, ServerName) /*alter trace*/ + WHERE @SkipTrace = 1; + INSERT #SkipChecks (DatabaseName, CheckID, ServerName) SELECT v.* From a015e051f1616c37bfc9a1ce3c07c7f8bfe8c7e5 Mon Sep 17 00:00:00 2001 From: Martin van der Boom Date: Thu, 14 Sep 2023 10:51:13 +0200 Subject: [PATCH 439/662] Issue #3334: sp_Blitz Fails because of permissions --- sp_Blitz.sql | 40 +++++++++++++++++++++++++++++++++------- 1 file changed, 33 insertions(+), 7 deletions(-) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index 21ca3f646..2e6ef6505 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -198,7 +198,8 @@ AS ,@SkipMSDB bit = 0 ,@SkipModel bit = 0 ,@SkipTempDB bit = 0 - ,@SkipValidateLogins bit = 0; + ,@SkipValidateLogins bit = 0 + ,@SkipModelCheck BIT = 0; DECLARE @db_perms table @@ -219,7 +220,7 @@ AS fmp.permission_name FROM sys.databases AS d CROSS APPLY fn_my_permissions(d.name, 'DATABASE') AS fmp - WHERE fmp.permission_name = N'SELECT' /*Databases where we don't have read permissions*/ + WHERE fmp.permission_name = N'SELECT'; /*Databases where we don't have read permissions*/ /* End of declarations for First Responder Kit consistency check:*/ ; @@ -307,6 +308,31 @@ AS SET @SkipValidateLogins = 1; END; /*Need execute on sp_validatelogins*/ + IF EXISTS + ( + SELECT 1/0 + FROM @db_perms + WHERE database_name = N'model' + ) + BEGIN + BEGIN TRY + IF EXISTS + ( + SELECT 1/0 + FROM model.sys.objects + ) + BEGIN + SET @SkipModelCheck = 0; /*We have read permissions in the model database, and can view the objects*/ + END; + END TRY + BEGIN CATCH + SET @SkipModelCheck = 1; /*We have read permissions in the model database ... oh wait we got tricked, we can't view the objects*/ + END CATCH; + END; + ELSE + BEGIN + SET @SkipModelCheck = 1; /*We don't have read permissions in the model database*/ + END; END; SET @crlf = NCHAR(13) + NCHAR(10); @@ -467,11 +493,11 @@ AS ); /*Skip individial checks where we don't have permissions*/ - INSERT #SkipChecks (DatabaseName, CheckID, ServerName) - SELECT - v.* - FROM (VALUES(NULL, 29, NULL)) AS v (DatabaseName, CheckID, ServerName) /*Looks for user tables in model*/ - WHERE NOT EXISTS (SELECT 1/0 FROM @db_perms AS dp WHERE dp.database_name = 'model'); + INSERT #SkipChecks (DatabaseName, CheckID, ServerName) + SELECT + v.* + FROM (VALUES(NULL, 29, NULL)) AS v (DatabaseName, CheckID, ServerName) /*Looks for user tables in model*/ + WHERE @SkipModelCheck = 1; INSERT #SkipChecks (DatabaseName, CheckID, ServerName) SELECT From aba25c67dbf649834efbbf382c56e406b2e01b92 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Mon, 18 Sep 2023 08:45:24 -0700 Subject: [PATCH 440/662] sp_BlitzCache all-sorts bug Was calling for spills, but saying the results were for memory grants. --- sp_BlitzCache.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sp_BlitzCache.sql b/sp_BlitzCache.sql index ddd418e2a..62e614379 100644 --- a/sp_BlitzCache.sql +++ b/sp_BlitzCache.sql @@ -6908,7 +6908,7 @@ SET @AllSortSql += N' EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''avg spills'', @IgnoreSqlHandles = @ISH, @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; - UPDATE #bou_allsort SET Pattern = ''avg memory grant'' WHERE Pattern IS NULL OPTION(RECOMPILE);'; + UPDATE #bou_allsort SET Pattern = ''avg spills'' WHERE Pattern IS NULL OPTION(RECOMPILE);'; IF @ExportToExcel = 1 BEGIN SET @AllSortSql += N' UPDATE #bou_allsort From 3aaa6f48ecda1a36a8ad6a25c4024c70750514d6 Mon Sep 17 00:00:00 2001 From: eschnepel Date: Wed, 20 Sep 2023 09:18:05 +0200 Subject: [PATCH 441/662] Update sp_BlitzCache.sql for double-spill fix after iterating over all sort sub procedure calls ... avoid going to OutputResultsToTable ... otherwise the last result (e.g. spills) would be recorded twice into the output table. --- sp_BlitzCache.sql | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/sp_BlitzCache.sql b/sp_BlitzCache.sql index 62e614379..97f79d58b 100644 --- a/sp_BlitzCache.sql +++ b/sp_BlitzCache.sql @@ -6955,6 +6955,11 @@ END; EXEC sys.sp_executesql @stmt = @AllSortSql, @params = N'@i_DatabaseName NVARCHAR(128), @i_Top INT, @i_SkipAnalysis BIT, @i_OutputDatabaseName NVARCHAR(258), @i_OutputSchemaName NVARCHAR(258), @i_OutputTableName NVARCHAR(258), @i_CheckDateOverride DATETIMEOFFSET, @i_MinutesBack INT ', @i_DatabaseName = @DatabaseName, @i_Top = @Top, @i_SkipAnalysis = @SkipAnalysis, @i_OutputDatabaseName = @OutputDatabaseName, @i_OutputSchemaName = @OutputSchemaName, @i_OutputTableName = @OutputTableName, @i_CheckDateOverride = @CheckDateOverride, @i_MinutesBack = @MinutesBack; +/* Avoid going into OutputResultsToTable + ... otherwise the last result (e.g. spills) would be recorded twice into the output table. +*/ +RETURN; + /*End of AllSort section*/ From ac79de5fa92523e88ac757202c0582d4423740b0 Mon Sep 17 00:00:00 2001 From: Martin van der Boom Date: Wed, 20 Sep 2023 11:50:26 +0200 Subject: [PATCH 442/662] Issue #3334 sp_Blitz Fails because of permissions --- sp_Blitz.sql | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index 2e6ef6505..1e057ae55 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -196,10 +196,9 @@ AS ,@SkipXPCMDShell bit = 0 ,@SkipMaster bit = 0 ,@SkipMSDB bit = 0 - ,@SkipModel bit = 0 + ,@SkipModel BIT = 0 ,@SkipTempDB bit = 0 - ,@SkipValidateLogins bit = 0 - ,@SkipModelCheck BIT = 0; + ,@SkipValidateLogins bit = 0; DECLARE @db_perms table @@ -322,16 +321,16 @@ AS FROM model.sys.objects ) BEGIN - SET @SkipModelCheck = 0; /*We have read permissions in the model database, and can view the objects*/ + SET @SkipModel = 0; /*We have read permissions in the model database, and can view the objects*/ END; END TRY BEGIN CATCH - SET @SkipModelCheck = 1; /*We have read permissions in the model database ... oh wait we got tricked, we can't view the objects*/ + SET @SkipModel = 1; /*We have read permissions in the model database ... oh wait we got tricked, we can't view the objects*/ END CATCH; END; ELSE BEGIN - SET @SkipModelCheck = 1; /*We don't have read permissions in the model database*/ + SET @SkipModel = 1; /*We don't have read permissions in the model database*/ END; END; @@ -497,7 +496,7 @@ AS SELECT v.* FROM (VALUES(NULL, 29, NULL)) AS v (DatabaseName, CheckID, ServerName) /*Looks for user tables in model*/ - WHERE @SkipModelCheck = 1; + WHERE @SkipModel = 1; INSERT #SkipChecks (DatabaseName, CheckID, ServerName) SELECT From 305e9c68dde155b5f97839499dbe780971e0d0d8 Mon Sep 17 00:00:00 2001 From: Martin van der Boom Date: Wed, 20 Sep 2023 12:00:08 +0200 Subject: [PATCH 443/662] Issue #3334: sp_Blitz Fails because of permissions --- sp_Blitz.sql | 55 +++++++++++++++++++++++++++------------------------- 1 file changed, 29 insertions(+), 26 deletions(-) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index 1e057ae55..9ff1ed8c0 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -306,32 +306,35 @@ AS BEGIN SET @SkipValidateLogins = 1; END; /*Need execute on sp_validatelogins*/ - - IF EXISTS - ( - SELECT 1/0 - FROM @db_perms - WHERE database_name = N'model' - ) - BEGIN - BEGIN TRY - IF EXISTS - ( - SELECT 1/0 - FROM model.sys.objects - ) - BEGIN - SET @SkipModel = 0; /*We have read permissions in the model database, and can view the objects*/ - END; - END TRY - BEGIN CATCH - SET @SkipModel = 1; /*We have read permissions in the model database ... oh wait we got tricked, we can't view the objects*/ - END CATCH; - END; - ELSE - BEGIN - SET @SkipModel = 1; /*We don't have read permissions in the model database*/ - END; + + IF ISNULL(@SkipModel, 0) != 1 /* If @SkipModel hasn't been set to 1 by the caller */ + BEGIN + IF EXISTS + ( + SELECT 1/0 + FROM @db_perms + WHERE database_name = N'model' + ) + BEGIN + BEGIN TRY + IF EXISTS + ( + SELECT 1/0 + FROM model.sys.objects + ) + BEGIN + SET @SkipModel = 0; /*We have read permissions in the model database, and can view the objects*/ + END; + END TRY + BEGIN CATCH + SET @SkipModel = 1; /*We have read permissions in the model database ... oh wait we got tricked, we can't view the objects*/ + END CATCH; + END; + ELSE + BEGIN + SET @SkipModel = 1; /*We don't have read permissions in the model database*/ + END; + END; END; SET @crlf = NCHAR(13) + NCHAR(10); From 842526e8b2c3d130575a514f8052e480079d7380 Mon Sep 17 00:00:00 2001 From: Martin van der Boom Date: Wed, 20 Sep 2023 12:02:31 +0200 Subject: [PATCH 444/662] Issue #3334: sp_Blitz Fails because of permissions --- sp_Blitz.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index 9ff1ed8c0..468e6f474 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -196,7 +196,7 @@ AS ,@SkipXPCMDShell bit = 0 ,@SkipMaster bit = 0 ,@SkipMSDB bit = 0 - ,@SkipModel BIT = 0 + ,@SkipModel bit = 0 ,@SkipTempDB bit = 0 ,@SkipValidateLogins bit = 0; @@ -307,7 +307,7 @@ AS SET @SkipValidateLogins = 1; END; /*Need execute on sp_validatelogins*/ - IF ISNULL(@SkipModel, 0) != 1 /* If @SkipModel hasn't been set to 1 by the caller */ + IF ISNULL(@SkipModel, 0) != 1 /*If @SkipModel hasn't been set to 1 by the caller*/ BEGIN IF EXISTS ( From aee2ada37e28d224882ad61b75829c323a78f04f Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Thu, 21 Sep 2023 04:52:38 -0700 Subject: [PATCH 445/662] #3342 sp_BlitzCache concurrency Create global temp tables later to reduce the likelihood that they'll disappear. Closes #3342. --- sp_BlitzCache.sql | 416 +++++++++++++++++++++++----------------------- 1 file changed, 209 insertions(+), 207 deletions(-) diff --git a/sp_BlitzCache.sql b/sp_BlitzCache.sql index 97f79d58b..993380021 100644 --- a/sp_BlitzCache.sql +++ b/sp_BlitzCache.sql @@ -853,213 +853,6 @@ IF @MinutesBack IS NOT NULL END; -RAISERROR(N'Creating temp tables for results and warnings.', 0, 1) WITH NOWAIT; - - -IF OBJECT_ID('tempdb.dbo.##BlitzCacheResults') IS NULL -BEGIN - CREATE TABLE ##BlitzCacheResults ( - SPID INT, - ID INT IDENTITY(1,1), - CheckID INT, - Priority TINYINT, - FindingsGroup VARCHAR(50), - Finding VARCHAR(500), - URL VARCHAR(200), - Details VARCHAR(4000) - ); -END; - -IF OBJECT_ID('tempdb.dbo.##BlitzCacheProcs') IS NULL -BEGIN - CREATE TABLE ##BlitzCacheProcs ( - SPID INT , - QueryType NVARCHAR(258), - DatabaseName sysname, - AverageCPU DECIMAL(38,4), - AverageCPUPerMinute DECIMAL(38,4), - TotalCPU DECIMAL(38,4), - PercentCPUByType MONEY, - PercentCPU MONEY, - AverageDuration DECIMAL(38,4), - TotalDuration DECIMAL(38,4), - PercentDuration MONEY, - PercentDurationByType MONEY, - AverageReads BIGINT, - TotalReads BIGINT, - PercentReads MONEY, - PercentReadsByType MONEY, - ExecutionCount BIGINT, - PercentExecutions MONEY, - PercentExecutionsByType MONEY, - ExecutionsPerMinute MONEY, - TotalWrites BIGINT, - AverageWrites MONEY, - PercentWrites MONEY, - PercentWritesByType MONEY, - WritesPerMinute MONEY, - PlanCreationTime DATETIME, - PlanCreationTimeHours AS DATEDIFF(HOUR, PlanCreationTime, SYSDATETIME()), - LastExecutionTime DATETIME, - LastCompletionTime DATETIME, - PlanHandle VARBINARY(64), - [Remove Plan Handle From Cache] AS - CASE WHEN [PlanHandle] IS NOT NULL - THEN 'DBCC FREEPROCCACHE (' + CONVERT(VARCHAR(128), [PlanHandle], 1) + ');' - ELSE 'N/A' END, - SqlHandle VARBINARY(64), - [Remove SQL Handle From Cache] AS - CASE WHEN [SqlHandle] IS NOT NULL - THEN 'DBCC FREEPROCCACHE (' + CONVERT(VARCHAR(128), [SqlHandle], 1) + ');' - ELSE 'N/A' END, - [SQL Handle More Info] AS - CASE WHEN [SqlHandle] IS NOT NULL - THEN 'EXEC sp_BlitzCache @OnlySqlHandles = ''' + CONVERT(VARCHAR(128), [SqlHandle], 1) + '''; ' - ELSE 'N/A' END, - QueryHash BINARY(8), - [Query Hash More Info] AS - CASE WHEN [QueryHash] IS NOT NULL - THEN 'EXEC sp_BlitzCache @OnlyQueryHashes = ''' + CONVERT(VARCHAR(32), [QueryHash], 1) + '''; ' - ELSE 'N/A' END, - QueryPlanHash BINARY(8), - StatementStartOffset INT, - StatementEndOffset INT, - PlanGenerationNum BIGINT, - MinReturnedRows BIGINT, - MaxReturnedRows BIGINT, - AverageReturnedRows MONEY, - TotalReturnedRows BIGINT, - LastReturnedRows BIGINT, - MinGrantKB BIGINT, - MaxGrantKB BIGINT, - MinUsedGrantKB BIGINT, - MaxUsedGrantKB BIGINT, - PercentMemoryGrantUsed MONEY, - AvgMaxMemoryGrant MONEY, - MinSpills BIGINT, - MaxSpills BIGINT, - TotalSpills BIGINT, - AvgSpills MONEY, - QueryText NVARCHAR(MAX), - QueryPlan XML, - /* these next four columns are the total for the type of query. - don't actually use them for anything apart from math by type. - */ - TotalWorkerTimeForType BIGINT, - TotalElapsedTimeForType BIGINT, - TotalReadsForType BIGINT, - TotalExecutionCountForType BIGINT, - TotalWritesForType BIGINT, - NumberOfPlans INT, - NumberOfDistinctPlans INT, - SerialDesiredMemory FLOAT, - SerialRequiredMemory FLOAT, - CachedPlanSize FLOAT, - CompileTime FLOAT, - CompileCPU FLOAT , - CompileMemory FLOAT , - MaxCompileMemory FLOAT , - min_worker_time BIGINT, - max_worker_time BIGINT, - is_forced_plan BIT, - is_forced_parameterized BIT, - is_cursor BIT, - is_optimistic_cursor BIT, - is_forward_only_cursor BIT, - is_fast_forward_cursor BIT, - is_cursor_dynamic BIT, - is_parallel BIT, - is_forced_serial BIT, - is_key_lookup_expensive BIT, - key_lookup_cost FLOAT, - is_remote_query_expensive BIT, - remote_query_cost FLOAT, - frequent_execution BIT, - parameter_sniffing BIT, - unparameterized_query BIT, - near_parallel BIT, - plan_warnings BIT, - plan_multiple_plans INT, - long_running BIT, - downlevel_estimator BIT, - implicit_conversions BIT, - busy_loops BIT, - tvf_join BIT, - tvf_estimate BIT, - compile_timeout BIT, - compile_memory_limit_exceeded BIT, - warning_no_join_predicate BIT, - QueryPlanCost FLOAT, - missing_index_count INT, - unmatched_index_count INT, - min_elapsed_time BIGINT, - max_elapsed_time BIGINT, - age_minutes MONEY, - age_minutes_lifetime MONEY, - is_trivial BIT, - trace_flags_session VARCHAR(1000), - is_unused_grant BIT, - function_count INT, - clr_function_count INT, - is_table_variable BIT, - no_stats_warning BIT, - relop_warnings BIT, - is_table_scan BIT, - backwards_scan BIT, - forced_index BIT, - forced_seek BIT, - forced_scan BIT, - columnstore_row_mode BIT, - is_computed_scalar BIT , - is_sort_expensive BIT, - sort_cost FLOAT, - is_computed_filter BIT, - op_name VARCHAR(100) NULL, - index_insert_count INT NULL, - index_update_count INT NULL, - index_delete_count INT NULL, - cx_insert_count INT NULL, - cx_update_count INT NULL, - cx_delete_count INT NULL, - table_insert_count INT NULL, - table_update_count INT NULL, - table_delete_count INT NULL, - index_ops AS (index_insert_count + index_update_count + index_delete_count + - cx_insert_count + cx_update_count + cx_delete_count + - table_insert_count + table_update_count + table_delete_count), - is_row_level BIT, - is_spatial BIT, - index_dml BIT, - table_dml BIT, - long_running_low_cpu BIT, - low_cost_high_cpu BIT, - stale_stats BIT, - is_adaptive BIT, - index_spool_cost FLOAT, - index_spool_rows FLOAT, - table_spool_cost FLOAT, - table_spool_rows FLOAT, - is_spool_expensive BIT, - is_spool_more_rows BIT, - is_table_spool_expensive BIT, - is_table_spool_more_rows BIT, - estimated_rows FLOAT, - is_bad_estimate BIT, - is_paul_white_electric BIT, - is_row_goal BIT, - is_big_spills BIT, - is_mstvf BIT, - is_mm_join BIT, - is_nonsargable BIT, - select_with_writes BIT, - implicit_conversion_info XML, - cached_execution_parameters XML, - missing_indexes XML, - SetOptions VARCHAR(MAX), - Warnings VARCHAR(MAX), - Pattern NVARCHAR(20) - ); -END; DECLARE @DurationFilter_i INT, @MinMemoryPerQuery INT, @@ -2622,6 +2415,215 @@ IF @Debug = 1 PRINT SUBSTRING(@sql, 36000, 40000); END; +RAISERROR(N'Creating temp tables for results and warnings.', 0, 1) WITH NOWAIT; + + +IF OBJECT_ID('tempdb.dbo.##BlitzCacheResults') IS NULL +BEGIN + CREATE TABLE ##BlitzCacheResults ( + SPID INT, + ID INT IDENTITY(1,1), + CheckID INT, + Priority TINYINT, + FindingsGroup VARCHAR(50), + Finding VARCHAR(500), + URL VARCHAR(200), + Details VARCHAR(4000) + ); +END; + +IF OBJECT_ID('tempdb.dbo.##BlitzCacheProcs') IS NULL +BEGIN + CREATE TABLE ##BlitzCacheProcs ( + SPID INT , + QueryType NVARCHAR(258), + DatabaseName sysname, + AverageCPU DECIMAL(38,4), + AverageCPUPerMinute DECIMAL(38,4), + TotalCPU DECIMAL(38,4), + PercentCPUByType MONEY, + PercentCPU MONEY, + AverageDuration DECIMAL(38,4), + TotalDuration DECIMAL(38,4), + PercentDuration MONEY, + PercentDurationByType MONEY, + AverageReads BIGINT, + TotalReads BIGINT, + PercentReads MONEY, + PercentReadsByType MONEY, + ExecutionCount BIGINT, + PercentExecutions MONEY, + PercentExecutionsByType MONEY, + ExecutionsPerMinute MONEY, + TotalWrites BIGINT, + AverageWrites MONEY, + PercentWrites MONEY, + PercentWritesByType MONEY, + WritesPerMinute MONEY, + PlanCreationTime DATETIME, + PlanCreationTimeHours AS DATEDIFF(HOUR, PlanCreationTime, SYSDATETIME()), + LastExecutionTime DATETIME, + LastCompletionTime DATETIME, + PlanHandle VARBINARY(64), + [Remove Plan Handle From Cache] AS + CASE WHEN [PlanHandle] IS NOT NULL + THEN 'DBCC FREEPROCCACHE (' + CONVERT(VARCHAR(128), [PlanHandle], 1) + ');' + ELSE 'N/A' END, + SqlHandle VARBINARY(64), + [Remove SQL Handle From Cache] AS + CASE WHEN [SqlHandle] IS NOT NULL + THEN 'DBCC FREEPROCCACHE (' + CONVERT(VARCHAR(128), [SqlHandle], 1) + ');' + ELSE 'N/A' END, + [SQL Handle More Info] AS + CASE WHEN [SqlHandle] IS NOT NULL + THEN 'EXEC sp_BlitzCache @OnlySqlHandles = ''' + CONVERT(VARCHAR(128), [SqlHandle], 1) + '''; ' + ELSE 'N/A' END, + QueryHash BINARY(8), + [Query Hash More Info] AS + CASE WHEN [QueryHash] IS NOT NULL + THEN 'EXEC sp_BlitzCache @OnlyQueryHashes = ''' + CONVERT(VARCHAR(32), [QueryHash], 1) + '''; ' + ELSE 'N/A' END, + QueryPlanHash BINARY(8), + StatementStartOffset INT, + StatementEndOffset INT, + PlanGenerationNum BIGINT, + MinReturnedRows BIGINT, + MaxReturnedRows BIGINT, + AverageReturnedRows MONEY, + TotalReturnedRows BIGINT, + LastReturnedRows BIGINT, + MinGrantKB BIGINT, + MaxGrantKB BIGINT, + MinUsedGrantKB BIGINT, + MaxUsedGrantKB BIGINT, + PercentMemoryGrantUsed MONEY, + AvgMaxMemoryGrant MONEY, + MinSpills BIGINT, + MaxSpills BIGINT, + TotalSpills BIGINT, + AvgSpills MONEY, + QueryText NVARCHAR(MAX), + QueryPlan XML, + /* these next four columns are the total for the type of query. + don't actually use them for anything apart from math by type. + */ + TotalWorkerTimeForType BIGINT, + TotalElapsedTimeForType BIGINT, + TotalReadsForType BIGINT, + TotalExecutionCountForType BIGINT, + TotalWritesForType BIGINT, + NumberOfPlans INT, + NumberOfDistinctPlans INT, + SerialDesiredMemory FLOAT, + SerialRequiredMemory FLOAT, + CachedPlanSize FLOAT, + CompileTime FLOAT, + CompileCPU FLOAT , + CompileMemory FLOAT , + MaxCompileMemory FLOAT , + min_worker_time BIGINT, + max_worker_time BIGINT, + is_forced_plan BIT, + is_forced_parameterized BIT, + is_cursor BIT, + is_optimistic_cursor BIT, + is_forward_only_cursor BIT, + is_fast_forward_cursor BIT, + is_cursor_dynamic BIT, + is_parallel BIT, + is_forced_serial BIT, + is_key_lookup_expensive BIT, + key_lookup_cost FLOAT, + is_remote_query_expensive BIT, + remote_query_cost FLOAT, + frequent_execution BIT, + parameter_sniffing BIT, + unparameterized_query BIT, + near_parallel BIT, + plan_warnings BIT, + plan_multiple_plans INT, + long_running BIT, + downlevel_estimator BIT, + implicit_conversions BIT, + busy_loops BIT, + tvf_join BIT, + tvf_estimate BIT, + compile_timeout BIT, + compile_memory_limit_exceeded BIT, + warning_no_join_predicate BIT, + QueryPlanCost FLOAT, + missing_index_count INT, + unmatched_index_count INT, + min_elapsed_time BIGINT, + max_elapsed_time BIGINT, + age_minutes MONEY, + age_minutes_lifetime MONEY, + is_trivial BIT, + trace_flags_session VARCHAR(1000), + is_unused_grant BIT, + function_count INT, + clr_function_count INT, + is_table_variable BIT, + no_stats_warning BIT, + relop_warnings BIT, + is_table_scan BIT, + backwards_scan BIT, + forced_index BIT, + forced_seek BIT, + forced_scan BIT, + columnstore_row_mode BIT, + is_computed_scalar BIT , + is_sort_expensive BIT, + sort_cost FLOAT, + is_computed_filter BIT, + op_name VARCHAR(100) NULL, + index_insert_count INT NULL, + index_update_count INT NULL, + index_delete_count INT NULL, + cx_insert_count INT NULL, + cx_update_count INT NULL, + cx_delete_count INT NULL, + table_insert_count INT NULL, + table_update_count INT NULL, + table_delete_count INT NULL, + index_ops AS (index_insert_count + index_update_count + index_delete_count + + cx_insert_count + cx_update_count + cx_delete_count + + table_insert_count + table_update_count + table_delete_count), + is_row_level BIT, + is_spatial BIT, + index_dml BIT, + table_dml BIT, + long_running_low_cpu BIT, + low_cost_high_cpu BIT, + stale_stats BIT, + is_adaptive BIT, + index_spool_cost FLOAT, + index_spool_rows FLOAT, + table_spool_cost FLOAT, + table_spool_rows FLOAT, + is_spool_expensive BIT, + is_spool_more_rows BIT, + is_table_spool_expensive BIT, + is_table_spool_more_rows BIT, + estimated_rows FLOAT, + is_bad_estimate BIT, + is_paul_white_electric BIT, + is_row_goal BIT, + is_big_spills BIT, + is_mstvf BIT, + is_mm_join BIT, + is_nonsargable BIT, + select_with_writes BIT, + implicit_conversion_info XML, + cached_execution_parameters XML, + missing_indexes XML, + SetOptions VARCHAR(MAX), + Warnings VARCHAR(MAX), + Pattern NVARCHAR(20) + ); +END; + + IF @Reanalyze = 0 BEGIN RAISERROR('Collecting execution plan information.', 0, 1) WITH NOWAIT; From 9d786d43056bd3767c9a32cdfad94d795be2401c Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Thu, 21 Sep 2023 05:18:50 -0700 Subject: [PATCH 446/662] #3342 sp_BlitzCache concurrency On testing #3342, it was failing with the Reanalyze param turned on, so moved those to different parts of the code to make sure the temp table exists. --- sp_BlitzCache.sql | 44 +++++++++++++++++++++++--------------------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/sp_BlitzCache.sql b/sp_BlitzCache.sql index 993380021..9655e2871 100644 --- a/sp_BlitzCache.sql +++ b/sp_BlitzCache.sql @@ -967,31 +967,19 @@ IF EXISTS(SELECT * FROM sys.all_columns WHERE object_id = OBJECT_ID('sys.dm_exec ELSE SET @VersionShowsAirQuoteActualPlans = 0; -IF @Reanalyze = 1 AND OBJECT_ID('tempdb..##BlitzCacheResults') IS NULL - BEGIN - RAISERROR(N'##BlitzCacheResults does not exist, can''t reanalyze', 0, 1) WITH NOWAIT; - SET @Reanalyze = 0; - END; - -IF @Reanalyze = 0 - BEGIN - RAISERROR(N'Cleaning up old warnings for your SPID', 0, 1) WITH NOWAIT; - DELETE ##BlitzCacheResults - WHERE SPID = @@SPID - OPTION (RECOMPILE) ; - RAISERROR(N'Cleaning up old plans for your SPID', 0, 1) WITH NOWAIT; - DELETE ##BlitzCacheProcs - WHERE SPID = @@SPID - OPTION (RECOMPILE) ; - END; - IF @Reanalyze = 1 + BEGIN + IF OBJECT_ID('tempdb..##BlitzCacheResults') IS NULL + BEGIN + RAISERROR(N'##BlitzCacheResults does not exist, can''t reanalyze', 0, 1) WITH NOWAIT; + SET @Reanalyze = 0; + END + ELSE BEGIN RAISERROR(N'Reanalyzing current data, skipping to results', 0, 1) WITH NOWAIT; GOTO Results; END; - - + END; IF @SortOrder IN ('all', 'all avg') @@ -2431,6 +2419,14 @@ BEGIN Details VARCHAR(4000) ); END; +ELSE +BEGIN + RAISERROR(N'Cleaning up old warnings for your SPID', 0, 1) WITH NOWAIT; + DELETE ##BlitzCacheResults + WHERE SPID = @@SPID + OPTION (RECOMPILE) ; +END + IF OBJECT_ID('tempdb.dbo.##BlitzCacheProcs') IS NULL BEGIN @@ -2622,7 +2618,13 @@ BEGIN Pattern NVARCHAR(20) ); END; - +ELSE +BEGIN + RAISERROR(N'Cleaning up old plans for your SPID', 0, 1) WITH NOWAIT; + DELETE ##BlitzCacheProcs + WHERE SPID = @@SPID + OPTION (RECOMPILE) ; +END IF @Reanalyze = 0 BEGIN From 418cc3edd196653defab40ae264e411eadbfd0f1 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Thu, 21 Sep 2023 07:22:51 -0700 Subject: [PATCH 447/662] #3345 sp_BlitzCache sort by duplicates Adds @SortOrder = 'duplicate' parameter. Closes #3345. --- sp_BlitzCache.sql | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/sp_BlitzCache.sql b/sp_BlitzCache.sql index 9655e2871..2e09bc7bf 100644 --- a/sp_BlitzCache.sql +++ b/sp_BlitzCache.sql @@ -354,7 +354,7 @@ IF @Help = 1 UNION ALL SELECT N'@SortOrder', N'VARCHAR(10)', - N'Data processing and display order. @SortOrder will still be used, even when preparing output for a table or for excel. Possible values are: "CPU", "Reads", "Writes", "Duration", "Executions", "Recent Compilations", "Memory Grant", "Unused Grant", "Spills", "Query Hash". Additionally, the word "Average" or "Avg" can be used to sort on averages rather than total. "Executions per minute" and "Executions / minute" can be used to sort by execution per minute. For the truly lazy, "xpm" can also be used. Note that when you use all or all avg, the only parameters you can use are @Top and @DatabaseName. All others will be ignored.' + N'Data processing and display order. @SortOrder will still be used, even when preparing output for a table or for excel. Possible values are: "CPU", "Reads", "Writes", "Duration", "Executions", "Recent Compilations", "Memory Grant", "Unused Grant", "Spills", "Query Hash", "Duplicates". Additionally, the word "Average" or "Avg" can be used to sort on averages rather than total. "Executions per minute" and "Executions / minute" can be used to sort by execution per minute. For the truly lazy, "xpm" can also be used. Note that when you use all or all avg, the only parameters you can use are @Top and @DatabaseName. All others will be ignored.' UNION ALL SELECT N'@UseTriggersAnyway', @@ -810,6 +810,35 @@ IF @SortOrder LIKE 'query hash%' END +/* If they want to sort by duplicates, populate the @OnlySqlHandles list for them */ +IF @SortOrder LIKE 'duplicate%' + BEGIN + RAISERROR('Beginning duplicate query hash sort', 0, 1) WITH NOWAIT; + + SELECT qs.query_hash, + MAX(qs.sql_handle) AS max_sql_handle, + COUNT_BIG(*) AS records + INTO #duplicate_grouped + FROM sys.dm_exec_query_stats AS qs + CROSS APPLY ( SELECT pa.value + FROM sys.dm_exec_plan_attributes(qs.plan_handle) AS pa + WHERE pa.attribute = 'dbid' ) AS ca + GROUP BY qs.query_hash, ca.value + HAVING COUNT_BIG(*) > 100 + ORDER BY records DESC; + + SELECT TOP (1) + @OnlySqlHandles = STUFF((SELECT DISTINCT N',' + CONVERT(NVARCHAR(MAX), qhg.max_sql_handle, 1) + FROM #duplicate_grouped AS qhg + WHERE qhg.max_sql_handle <> 0x00 + FOR XML PATH(N''), TYPE).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 1, N'') + OPTION(RECOMPILE); + + SET @SortOrder = 'cpu'; + + END + + /* Set @Top based on sort */ IF ( @Top IS NULL From 1b6d6682fddecb0a5607044ee484304ef51271fc Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Thu, 21 Sep 2023 07:28:05 -0700 Subject: [PATCH 448/662] #3345 sp_BlitzCache sort by duplicates Adds @SortOrder = 'duplicate' parameter. Closes #3345. --- sp_BlitzCache.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sp_BlitzCache.sql b/sp_BlitzCache.sql index 2e09bc7bf..902574b29 100644 --- a/sp_BlitzCache.sql +++ b/sp_BlitzCache.sql @@ -815,7 +815,7 @@ IF @SortOrder LIKE 'duplicate%' BEGIN RAISERROR('Beginning duplicate query hash sort', 0, 1) WITH NOWAIT; - SELECT qs.query_hash, + SELECT TOP(@Top) qs.query_hash, MAX(qs.sql_handle) AS max_sql_handle, COUNT_BIG(*) AS records INTO #duplicate_grouped From a733eb4666a822eb14f2720469d4e864630d85a8 Mon Sep 17 00:00:00 2001 From: Martin van der Boom Date: Fri, 22 Sep 2023 15:29:26 +0200 Subject: [PATCH 449/662] #3348 added explanation for @debug in help --- README.md | 2 ++ sp_Blitz.sql | 1 + 2 files changed, 3 insertions(+) diff --git a/README.md b/README.md index 9bc8bab63..b2446796e 100644 --- a/README.md +++ b/README.md @@ -100,6 +100,8 @@ Advanced tips: In addition to the [parameters common to many of the stored procedures](#parameters-common-to-many-of-the-stored-procedures), here are the ones specific to sp_Blitz: +* @Debug default 0. When 1, we print out messages of what we're doing. When 2, we print the dynamic queries as well + [*Back to top*](#header1) #### Writing sp_Blitz Output to a Table diff --git a/sp_Blitz.sql b/sp_Blitz.sql index 468e6f474..ada1500a2 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -85,6 +85,7 @@ AS @OutputType ''TABLE''=table | ''COUNT''=row with number found | ''MARKDOWN''=bulleted list (including server info, excluding security findings) | ''SCHEMA''=version and field list | ''XML'' =table output as XML | ''NONE'' = none @IgnorePrioritiesBelow 50=ignore priorities below 50 @IgnorePrioritiesAbove 50=ignore priorities above 50 + @Debug 0=silent (Default) | 1=messages per step | 2=outputs dynamic queries For the rest of the parameters, see https://www.BrentOzar.com/blitz/documentation for details. MIT License From ab595c23d164af8070b635e4a4971e9fdf0deeee Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Fri, 22 Sep 2023 10:24:40 -0700 Subject: [PATCH 450/662] #3345 sp_BlitzCache duplicate plans 2 Second attempt at adding sort order for duplicate plans in the cache. Working on #3345. --- sp_BlitzCache.sql | 110 +++++++++++++++++++++++++--------------------- 1 file changed, 61 insertions(+), 49 deletions(-) diff --git a/sp_BlitzCache.sql b/sp_BlitzCache.sql index 902574b29..55ceb45aa 100644 --- a/sp_BlitzCache.sql +++ b/sp_BlitzCache.sql @@ -354,7 +354,7 @@ IF @Help = 1 UNION ALL SELECT N'@SortOrder', N'VARCHAR(10)', - N'Data processing and display order. @SortOrder will still be used, even when preparing output for a table or for excel. Possible values are: "CPU", "Reads", "Writes", "Duration", "Executions", "Recent Compilations", "Memory Grant", "Unused Grant", "Spills", "Query Hash", "Duplicates". Additionally, the word "Average" or "Avg" can be used to sort on averages rather than total. "Executions per minute" and "Executions / minute" can be used to sort by execution per minute. For the truly lazy, "xpm" can also be used. Note that when you use all or all avg, the only parameters you can use are @Top and @DatabaseName. All others will be ignored.' + N'Data processing and display order. @SortOrder will still be used, even when preparing output for a table or for excel. Possible values are: "CPU", "Reads", "Writes", "Duration", "Executions", "Recent Compilations", "Memory Grant", "Unused Grant", "Spills", "Query Hash", "Duplicate". Additionally, the word "Average" or "Avg" can be used to sort on averages rather than total. "Executions per minute" and "Executions / minute" can be used to sort by execution per minute. For the truly lazy, "xpm" can also be used. Note that when you use all or all avg, the only parameters you can use are @Top and @DatabaseName. All others will be ignored.' UNION ALL SELECT N'@UseTriggersAnyway', @@ -776,12 +776,30 @@ END; /* Lets get @SortOrder set to lower case here for comparisons later */ SET @SortOrder = LOWER(@SortOrder); +/* Set @Top based on sort */ +IF ( + @Top IS NULL + AND @SortOrder IN ( 'all', 'all sort' ) + ) + BEGIN + SET @Top = 5; + END; + +IF ( + @Top IS NULL + AND @SortOrder NOT IN ( 'all', 'all sort' ) + ) + BEGIN + SET @Top = 10; + END; + + /* If they want to sort by query hash, populate the @OnlyQueryHashes list for them */ IF @SortOrder LIKE 'query hash%' BEGIN RAISERROR('Beginning query hash sort', 0, 1) WITH NOWAIT; - SELECT qs.query_hash, + SELECT TOP(@Top) qs.query_hash, MAX(qs.max_worker_time) AS max_worker_time, COUNT_BIG(*) AS records INTO #query_hash_grouped @@ -810,52 +828,33 @@ IF @SortOrder LIKE 'query hash%' END -/* If they want to sort by duplicates, populate the @OnlySqlHandles list for them */ +/* If they want to sort by duplicate, create a table with the worst offenders - issue #3345 */ IF @SortOrder LIKE 'duplicate%' BEGIN RAISERROR('Beginning duplicate query hash sort', 0, 1) WITH NOWAIT; - SELECT TOP(@Top) qs.query_hash, - MAX(qs.sql_handle) AS max_sql_handle, - COUNT_BIG(*) AS records - INTO #duplicate_grouped - FROM sys.dm_exec_query_stats AS qs - CROSS APPLY ( SELECT pa.value - FROM sys.dm_exec_plan_attributes(qs.plan_handle) AS pa - WHERE pa.attribute = 'dbid' ) AS ca - GROUP BY qs.query_hash, ca.value - HAVING COUNT_BIG(*) > 100 - ORDER BY records DESC; - - SELECT TOP (1) - @OnlySqlHandles = STUFF((SELECT DISTINCT N',' + CONVERT(NVARCHAR(MAX), qhg.max_sql_handle, 1) - FROM #duplicate_grouped AS qhg - WHERE qhg.max_sql_handle <> 0x00 - FOR XML PATH(N''), TYPE).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 1, N'') - OPTION(RECOMPILE); - - SET @SortOrder = 'cpu'; + /* Find the query hashes that are the most duplicated */ + WITH MostCommonQueries AS ( + SELECT TOP(@Top) qs.query_hash, + COUNT_BIG(*) AS plans + FROM sys.dm_exec_query_stats AS qs + GROUP BY qs.query_hash + HAVING COUNT_BIG(*) > 100 + ORDER BY COUNT_BIG(*) DESC + ) + SELECT mcq_recent.sql_handle, mcq_recent.plan_handle, mcq_recent.creation_time AS duplicate_creation_time, mcq.plans + INTO #duplicate_query_filter + FROM MostCommonQueries mcq + CROSS APPLY ( SELECT TOP 1 qs.sql_handle, qs.plan_handle, qs.creation_time + FROM sys.dm_exec_query_stats qs + WHERE qs.query_hash = mcq.query_hash + ORDER BY qs.creation_time DESC) AS mcq_recent + OPTION (RECOMPILE); + SET @minimumExecutionCount = 0; END -/* Set @Top based on sort */ -IF ( - @Top IS NULL - AND @SortOrder IN ( 'all', 'all sort' ) - ) - BEGIN - SET @Top = 5; - END; - -IF ( - @Top IS NULL - AND @SortOrder NOT IN ( 'all', 'all sort' ) - ) - BEGIN - SET @Top = 10; - END; - /* validate user inputs */ IF @Top IS NULL OR @SortOrder IS NULL @@ -951,6 +950,7 @@ SET @SortOrder = CASE WHEN @SortOrder IN ('spill') THEN 'spills' WHEN @SortOrder IN ('avg spill') THEN 'avg spills' WHEN @SortOrder IN ('execution') THEN 'executions' + WHEN @SortOrder IN ('duplicates') THEN 'duplicate' ELSE @SortOrder END RAISERROR(N'Checking sort order', 0, 1) WITH NOWAIT; @@ -958,7 +958,7 @@ IF @SortOrder NOT IN ('cpu', 'avg cpu', 'reads', 'avg reads', 'writes', 'avg wri 'duration', 'avg duration', 'executions', 'avg executions', 'compiles', 'memory grant', 'avg memory grant', 'unused grant', 'spills', 'avg spills', 'all', 'all avg', 'sp_BlitzIndex', - 'query hash') + 'query hash', 'duplicate') BEGIN RAISERROR(N'Invalid sort order chosen, reverting to cpu', 16, 1) WITH NOWAIT; SET @SortOrder = 'cpu'; @@ -1801,6 +1801,10 @@ FROM (SELECT TOP (@Top) x.*, xpa.*, CROSS APPLY (SELECT * FROM sys.dm_exec_plan_attributes(x.plan_handle) AS ixpa WHERE ixpa.attribute = ''dbid'') AS xpa ' + @nl ; +IF @SortOrder = 'duplicate' /* Issue #3345 */ + BEGIN + SET @body += N' INNER JOIN #duplicate_query_filter AS dqf ON x.sql_handle = dqf.sql_handle AND x.plan_handle = dqf.plan_handle AND x.creation_time = dqf.duplicate_creation_time ' + @nl ; + END IF @VersionShowsAirQuoteActualPlans = 1 BEGIN @@ -1859,7 +1863,6 @@ BEGIN END; /* end filtering for query hashes */ - IF @DurationFilter IS NOT NULL BEGIN RAISERROR(N'Setting duration filter', 0, 1) WITH NOWAIT; @@ -1895,6 +1898,7 @@ SELECT @body += N' ORDER BY ' + WHEN N'memory grant' THEN N'max_grant_kb' WHEN N'unused grant' THEN N'max_grant_kb - max_used_grant_kb' WHEN N'spills' THEN N'max_spills' + WHEN N'duplicate' THEN N'total_worker_time' /* Issue #3345 */ /* And now the averages */ WHEN N'avg cpu' THEN N'total_worker_time / execution_count' WHEN N'avg reads' THEN N'total_logical_reads / execution_count' @@ -1936,7 +1940,6 @@ IF @VersionShowsAirQuoteActualPlans = 1 SET @body_where += N' AND pa.attribute = ' + QUOTENAME('dbid', @q ) + @nl ; - IF @NoobSaibot = 1 BEGIN SET @body_where += N' AND qp.query_plan.exist(''declare namespace p="http://schemas.microsoft.com/sqlserver/2004/07/showplan";//p:StmtSimple//p:MissingIndex'') = 1' + @nl ; @@ -2245,7 +2248,7 @@ END; IF (@QueryFilter = 'all' AND (SELECT COUNT(*) FROM #only_query_hashes) = 0 AND (SELECT COUNT(*) FROM #ignore_query_hashes) = 0) - AND (@SortOrder NOT IN ('memory grant', 'avg memory grant', 'unused grant')) + AND (@SortOrder NOT IN ('memory grant', 'avg memory grant', 'unused grant', 'duplicate')) /* Issue #3345 added 'duplicate' */ OR (LEFT(@QueryFilter, 3) = 'pro') BEGIN SET @sql += @insert_list; @@ -2266,7 +2269,7 @@ IF (@v >= 13 AND @QueryFilter = 'all' AND (SELECT COUNT(*) FROM #only_query_hashes) = 0 AND (SELECT COUNT(*) FROM #ignore_query_hashes) = 0) - AND (@SortOrder NOT IN ('memory grant', 'avg memory grant', 'unused grant')) + AND (@SortOrder NOT IN ('memory grant', 'avg memory grant', 'unused grant', 'duplicate')) /* Issue #3345 added 'duplicate' */ AND (@SortOrder NOT IN ('spills', 'avg spills')) OR (LEFT(@QueryFilter, 3) = 'fun') BEGIN @@ -2309,7 +2312,7 @@ IF (@UseTriggersAnyway = 1 OR @v >= 11) AND (SELECT COUNT(*) FROM #only_query_hashes) = 0 AND (SELECT COUNT(*) FROM #ignore_query_hashes) = 0 AND (@QueryFilter = 'all') - AND (@SortOrder NOT IN ('memory grant', 'avg memory grant', 'unused grant')) + AND (@SortOrder NOT IN ('memory grant', 'avg memory grant', 'unused grant', 'duplicate')) /* Issue #3345 added 'duplicate' */ BEGIN RAISERROR (N'Adding SQL to collect trigger stats.',0,1) WITH NOWAIT; @@ -2341,6 +2344,7 @@ SELECT @sort = CASE @SortOrder WHEN N'cpu' THEN N'total_worker_time' WHEN N'memory grant' THEN N'max_grant_kb' WHEN N'unused grant' THEN N'max_grant_kb - max_used_grant_kb' WHEN N'spills' THEN N'max_spills' + WHEN N'duplicate' THEN N'total_worker_time' /* Issue #3345 */ /* And now the averages */ WHEN N'avg cpu' THEN N'total_worker_time / execution_count' WHEN N'avg reads' THEN N'total_logical_reads / execution_count' @@ -2402,6 +2406,7 @@ SELECT @sort = CASE @SortOrder WHEN N'cpu' THEN N'TotalCPU' WHEN N'memory grant' THEN N'MaxGrantKB' WHEN N'unused grant' THEN N'MaxGrantKB - MaxUsedGrantKB' WHEN N'spills' THEN N'MaxSpills' + WHEN N'duplicate' THEN N'TotalCPU' /* Issue #3345 */ /* And now the averages */ WHEN N'avg cpu' THEN N'TotalCPU / ExecutionCount' WHEN N'avg reads' THEN N'TotalReads / ExecutionCount' @@ -2420,6 +2425,7 @@ SELECT @sql = REPLACE(@sql, '#sortable#', @sort); IF @Debug = 1 BEGIN + PRINT N'Printing dynamic SQL stored in @sql: '; PRINT SUBSTRING(@sql, 0, 4000); PRINT SUBSTRING(@sql, 4000, 8000); PRINT SUBSTRING(@sql, 8000, 12000); @@ -4990,6 +4996,7 @@ BEGIN WHEN N'memory grant' THEN N' MaxGrantKB' WHEN N'unused grant' THEN N' MaxGrantKB - MaxUsedGrantKB' WHEN N'spills' THEN N' MaxSpills' + WHEN N'duplicate' THEN N' plan_multiple_plans ' /* Issue #3345 */ WHEN N'avg cpu' THEN N' AverageCPU' WHEN N'avg reads' THEN N' AverageReads' WHEN N'avg writes' THEN N' AverageWrites' @@ -5001,8 +5008,14 @@ BEGIN SET @sql += N' OPTION (RECOMPILE) ; '; + IF @sql IS NULL + BEGIN + RAISERROR('@sql is null, which means dynamic SQL generation went terribly wrong', 0, 1) WITH NOWAIT; + END + IF @Debug = 1 BEGIN + RAISERROR('Printing @sql, the dynamic SQL we generated:', 0, 1) WITH NOWAIT; PRINT SUBSTRING(@sql, 0, 4000); PRINT SUBSTRING(@sql, 4000, 8000); PRINT SUBSTRING(@sql, 8000, 12000); @@ -5214,8 +5227,6 @@ BEGIN [Remove SQL Handle From Cache]'; END; - - SET @sql = N' SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; SELECT TOP (@Top) ' + @columns + @nl + N' @@ -5240,7 +5251,8 @@ SELECT @sql += N' ORDER BY ' + CASE @SortOrder WHEN N'cpu' THEN N' TotalCPU ' WHEN N'compiles' THEN N' PlanCreationTime ' WHEN N'memory grant' THEN N' MaxGrantKB' WHEN N'unused grant' THEN N' MaxGrantKB - MaxUsedGrantKB ' - WHEN N'spills' THEN N' MaxSpills' + WHEN N'duplicate' THEN N' plan_multiple_plans ' + WHEN N'spills' THEN N' MaxSpills ' WHEN N'avg cpu' THEN N' AverageCPU' WHEN N'avg reads' THEN N' AverageReads' WHEN N'avg writes' THEN N' AverageWrites' From 64877a9542f213e2d5826f2cfee7add6912c7e9e Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Fri, 22 Sep 2023 10:55:01 -0700 Subject: [PATCH 451/662] #3353 sp_BlitzCache single use plans Changing the source from dm_exec_cached_plans to dm_exec_query_stats. Closes #3353. --- sp_BlitzCache.sql | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/sp_BlitzCache.sql b/sp_BlitzCache.sql index 55ceb45aa..9eff864d0 100644 --- a/sp_BlitzCache.sql +++ b/sp_BlitzCache.sql @@ -1443,18 +1443,8 @@ WITH total_plans AS ( SELECT COUNT_BIG(*) AS single_use_plan_count - FROM sys.dm_exec_cached_plans AS cp - WHERE cp.usecounts = 1 - AND cp.objtype = N'Adhoc' - AND EXISTS - ( - SELECT - 1/0 - FROM sys.configurations AS c - WHERE c.name = N'optimize for ad hoc workloads' - AND c.value_in_use = 0 - ) - HAVING COUNT_BIG(*) > 1 + FROM sys.dm_exec_query_stats AS s + WHERE s.execution_count = 1 ) INSERT #plan_usage From 7b976362f37575261dd415776ef6d802e6e9bd85 Mon Sep 17 00:00:00 2001 From: Martin van der Boom Date: Tue, 26 Sep 2023 10:16:24 +0200 Subject: [PATCH 452/662] #3350 DBCC DBinfo fires incorrectly --- sp_Blitz.sql | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index ada1500a2..ad6337193 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -1109,17 +1109,17 @@ AS least one of the relevant checks is not being skipped then we can extract the dbinfo information. */ - IF NOT EXISTS ( SELECT 1 - FROM #BlitzResults - WHERE CheckID = 223 AND URL = 'https://aws.amazon.com/rds/sqlserver/') - AND ( - NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 2 ) - OR NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 68 ) - ) + IF NOT EXISTS + ( + SELECT 1/0 + FROM #BlitzResults + WHERE CheckID = 223 AND URL = 'https://aws.amazon.com/rds/sqlserver/' + ) AND NOT EXISTS + ( + SELECT 1/0 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID IN (2, 68) + ) BEGIN IF @Debug IN (1, 2) RAISERROR('Extracting DBCC DBINFO data (used in checks 2 and 68).', 0, 1, 68) WITH NOWAIT; From 99b8058e73fe4c4e19c41189f2606e5fbed9a08f Mon Sep 17 00:00:00 2001 From: Martin van der Boom Date: Tue, 26 Sep 2023 10:37:06 +0200 Subject: [PATCH 453/662] #3355 Added permissions check for MSDB --- sp_Blitz.sql | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index ada1500a2..de46274c3 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -336,6 +336,35 @@ AS SET @SkipModel = 1; /*We don't have read permissions in the model database*/ END; END; + + IF ISNULL(@SkipMSDB, 0) != 1 /*If @SkipMSDB hasn't been set to 1 by the caller*/ + BEGIN + IF EXISTS + ( + SELECT 1/0 + FROM @db_perms + WHERE database_name = N'msdb' + ) + BEGIN + BEGIN TRY + IF EXISTS + ( + SELECT 1/0 + FROM msdb.sys.objects + ) + BEGIN + SET @SkipMSDB = 0; /*We have read permissions in the msdb database, and can view the objects*/ + END; + END TRY + BEGIN CATCH + SET @SkipMSDB = 1; /*We have read permissions in the msdb database ... oh wait we got tricked, we can't view the objects*/ + END CATCH; + END; + ELSE + BEGIN + SET @SkipMSDB = 1; /*We don't have read permissions in the msdb database*/ + END; + END; END; SET @crlf = NCHAR(13) + NCHAR(10); @@ -502,6 +531,21 @@ AS FROM (VALUES(NULL, 29, NULL)) AS v (DatabaseName, CheckID, ServerName) /*Looks for user tables in model*/ WHERE @SkipModel = 1; + INSERT #SkipChecks (DatabaseName, CheckID, ServerName) + SELECT + v.* + FROM (VALUES(NULL, 6, NULL), /*Jobs Owned By Users*/ + (NULL, 28, NULL), /*SQL Agent Job Runs at Startup*/ + (NULL, 57, NULL), /*Tables in the MSDB Database*/ + (NULL, 79, NULL), /*Shrink Database Job*/ + (NULL, 94, NULL), /*Agent Jobs Without Failure Emails*/ + (NULL, 123, NULL), /*Agent Jobs Starting Simultaneously*/ + (NULL, 180, NULL), /*Shrink Database Step In Maintenance Plan*/ + (NULL, 181, NULL), /*Repetitive Maintenance Tasks*/ + (NULL, 219, NULL) /*Alerts Without Event Descriptions*/ + ) AS v (DatabaseName, CheckID, ServerName) + WHERE @SkipMSDB = 1; + INSERT #SkipChecks (DatabaseName, CheckID, ServerName) SELECT v.* From e9d98e924d16e6b19058395260fa621e79d6154e Mon Sep 17 00:00:00 2001 From: Martin van der Boom Date: Tue, 26 Sep 2023 10:56:08 +0200 Subject: [PATCH 454/662] #3356 fix for sp_validatelogins --- sp_Blitz.sql | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index ada1500a2..68db64127 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -297,16 +297,29 @@ AS SET @SkipXPCMDShell = 1; END; /*Need execute on xp_cmdshell*/ - IF NOT EXISTS - ( - SELECT - 1/0 - FROM fn_my_permissions(N'sp_validatelogins', N'OBJECT') AS fmp - WHERE fmp.permission_name = N'EXECUTE' - ) - BEGIN - SET @SkipValidateLogins = 1; - END; /*Need execute on sp_validatelogins*/ + IF ISNULL(@SkipValidateLogins, 0) != 1 /*If @SkipValidateLogins hasn't been set to 1 by the caller*/ + BEGIN + IF EXISTS + ( + SELECT 1/0 + FROM fn_my_permissions(N'sp_validatelogins', N'OBJECT') AS fmp + WHERE fmp.permission_name = N'EXECUTE' + ) + BEGIN + BEGIN TRY + EXEC sp_validatelogins; + + SET @SkipValidateLogins = 0 /*We can execute sp_validatelogins*/ + END TRY + BEGIN CATCH + SET @SkipValidateLogins = 1 /*We have execute rights but sp_validatelogins throws an error so skip it*/ + END CATCH + END; + END; + ELSE + BEGIN + SET @SkipValidateLogins = 1; + END; /*Need execute on sp_validatelogins*/ IF ISNULL(@SkipModel, 0) != 1 /*If @SkipModel hasn't been set to 1 by the caller*/ BEGIN From 0b5071c86fce7f66e0a5c489ce580120aa5516d5 Mon Sep 17 00:00:00 2001 From: Martin van der Boom Date: Tue, 26 Sep 2023 11:13:51 +0200 Subject: [PATCH 455/662] #3356 xp_regread fix --- sp_Blitz.sql | 40 ++++++++++++++++++++++++++++++---------- 1 file changed, 30 insertions(+), 10 deletions(-) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index ada1500a2..94dd8f529 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -264,16 +264,36 @@ AS SET @SkipTrace = 1; END; /*We need this permission to execute trace stuff, apparently*/ - IF NOT EXISTS - ( - SELECT - 1/0 - FROM fn_my_permissions(N'xp_regread', N'OBJECT') AS fmp - WHERE fmp.permission_name = N'EXECUTE' - ) - BEGIN - SET @SkipXPRegRead = 1; - END; /*Need execute on xp_regread*/ + IF ISNULL(@SkipXPRegRead, 0) != 1 /*If @SkipXPRegRead hasn't been set to 1 by the caller*/ + BEGIN TRY + IF OBJECT_ID(N'tempdb..#XpRegReadTest') IS NULL + BEGIN + CREATE TABLE #XpRegReadTest + ( + [Value] varchar(20) + ,[Data] int + ); + END; + + INSERT INTO #XpRegReadTest + ( + [Value] + ,[Data] + ) + EXEC xp_regread @rootkey = N'HKEY_LOCAL_MACHINE', + @key = N'', + @value_name = N''; + + SET @SkipXPRegRead = 0; /*We can execute xp_regread*/ + + IF OBJECT_ID(N'tempdb..#XpRegReadTest') IS NOT NULL + BEGIN + DROP TABLE #XpRegReadTest; + END; + END TRY + BEGIN CATCH + SET @SkipXPRegRead = 1; /*We have don't have execute rights or xp_regread throws an error so skip it*/ + END CATCH; /*Need execute on xp_regread*/ IF NOT EXISTS ( From 5f337bd0031c098f2db5b0019a89df3bbec59b84 Mon Sep 17 00:00:00 2001 From: Martin van der Boom Date: Tue, 26 Sep 2023 11:28:09 +0200 Subject: [PATCH 456/662] #3356 fix for sp_validatelogins. Reworked to handle output --- sp_Blitz.sql | 47 +++++++++++++++++++++++++++-------------------- 1 file changed, 27 insertions(+), 20 deletions(-) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index 68db64127..cc12721fc 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -299,26 +299,33 @@ AS IF ISNULL(@SkipValidateLogins, 0) != 1 /*If @SkipValidateLogins hasn't been set to 1 by the caller*/ BEGIN - IF EXISTS - ( - SELECT 1/0 - FROM fn_my_permissions(N'sp_validatelogins', N'OBJECT') AS fmp - WHERE fmp.permission_name = N'EXECUTE' - ) - BEGIN - BEGIN TRY - EXEC sp_validatelogins; - - SET @SkipValidateLogins = 0 /*We can execute sp_validatelogins*/ - END TRY - BEGIN CATCH - SET @SkipValidateLogins = 1 /*We have execute rights but sp_validatelogins throws an error so skip it*/ - END CATCH - END; - END; - ELSE - BEGIN - SET @SkipValidateLogins = 1; + IF OBJECT_ID(N'tempdb..#ValidateLoginsTest') IS NULL + BEGIN + CREATE TABLE #ValidateLoginsTest + ( + [SID] varbinary(85) + ,[NT_Login] sysname + ); + END; + + BEGIN TRY + INSERT INTO #ValidateLoginsTest + ( + [SID] + ,[NT_Login] + ) + EXEC sp_validatelogins; + + SET @SkipValidateLogins = 0 /*We can execute sp_validatelogins*/ + END TRY + BEGIN CATCH + SET @SkipValidateLogins = 1 /*We have don't have execute rights or sp_validatelogins throws an error so skip it*/ + END CATCH; + + IF OBJECT_ID(N'tempdb..#ValidateLoginsTest') IS NOT NULL + BEGIN + DROP TABLE #ValidateLoginsTest; + END; END; /*Need execute on sp_validatelogins*/ IF ISNULL(@SkipModel, 0) != 1 /*If @SkipModel hasn't been set to 1 by the caller*/ From 3e1b16af29c12f1b0d1409a3994f3fa407a2f5a6 Mon Sep 17 00:00:00 2001 From: Martin van der Boom Date: Tue, 26 Sep 2023 11:31:36 +0200 Subject: [PATCH 457/662] #3356 fix for xp_regread. Moved temp table out of try catch --- sp_Blitz.sql | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index 94dd8f529..fd51fff30 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -265,7 +265,7 @@ AS END; /*We need this permission to execute trace stuff, apparently*/ IF ISNULL(@SkipXPRegRead, 0) != 1 /*If @SkipXPRegRead hasn't been set to 1 by the caller*/ - BEGIN TRY + BEGIN IF OBJECT_ID(N'tempdb..#XpRegReadTest') IS NULL BEGIN CREATE TABLE #XpRegReadTest @@ -275,25 +275,28 @@ AS ); END; - INSERT INTO #XpRegReadTest - ( - [Value] - ,[Data] - ) - EXEC xp_regread @rootkey = N'HKEY_LOCAL_MACHINE', - @key = N'', - @value_name = N''; - - SET @SkipXPRegRead = 0; /*We can execute xp_regread*/ + BEGIN TRY + INSERT INTO #XpRegReadTest + ( + [Value] + ,[Data] + ) + EXEC xp_regread @rootkey = N'HKEY_LOCAL_MACHINE', + @key = N'', + @value_name = N''; + + SET @SkipXPRegRead = 0; /*We can execute xp_regread*/ + END TRY + BEGIN CATCH + SET @SkipXPRegRead = 1; /*We have don't have execute rights or xp_regread throws an error so skip it*/ + END CATCH; IF OBJECT_ID(N'tempdb..#XpRegReadTest') IS NOT NULL BEGIN DROP TABLE #XpRegReadTest; END; - END TRY - BEGIN CATCH - SET @SkipXPRegRead = 1; /*We have don't have execute rights or xp_regread throws an error so skip it*/ - END CATCH; /*Need execute on xp_regread*/ + END; /*Need execute on xp_regread*/ + IF NOT EXISTS ( From 5a1dc9d311dd8f2b26639285f9e1bfcb6cdf137a Mon Sep 17 00:00:00 2001 From: Martin van der Boom Date: Tue, 26 Sep 2023 11:56:38 +0200 Subject: [PATCH 458/662] #3356 Small change to the temp table creation --- sp_Blitz.sql | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index cc12721fc..4084f4f73 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -299,14 +299,14 @@ AS IF ISNULL(@SkipValidateLogins, 0) != 1 /*If @SkipValidateLogins hasn't been set to 1 by the caller*/ BEGIN - IF OBJECT_ID(N'tempdb..#ValidateLoginsTest') IS NULL - BEGIN - CREATE TABLE #ValidateLoginsTest - ( - [SID] varbinary(85) - ,[NT_Login] sysname - ); - END; + IF OBJECT_ID(N'tempdb..#ValidateLoginsTest') IS NOT NULL + EXEC sp_executesql N'DROP TABLE #ValidateLoginsTest;'; + + CREATE TABLE #ValidateLoginsTest + ( + [SID] varbinary(85) + ,[NT_Login] sysname + ); BEGIN TRY INSERT INTO #ValidateLoginsTest @@ -316,16 +316,14 @@ AS ) EXEC sp_validatelogins; - SET @SkipValidateLogins = 0 /*We can execute sp_validatelogins*/ + SET @SkipValidateLogins = 0; /*We can execute sp_validatelogins*/ END TRY BEGIN CATCH - SET @SkipValidateLogins = 1 /*We have don't have execute rights or sp_validatelogins throws an error so skip it*/ + SET @SkipValidateLogins = 1; /*We have don't have execute rights or sp_validatelogins throws an error so skip it*/ END CATCH; IF OBJECT_ID(N'tempdb..#ValidateLoginsTest') IS NOT NULL - BEGIN - DROP TABLE #ValidateLoginsTest; - END; + EXEC sp_executesql N'DROP TABLE #ValidateLoginsTest;'; END; /*Need execute on sp_validatelogins*/ IF ISNULL(@SkipModel, 0) != 1 /*If @SkipModel hasn't been set to 1 by the caller*/ From 4be96d466e9c63eb3e1b7916b9ace64852cd89f8 Mon Sep 17 00:00:00 2001 From: Martin van der Boom Date: Tue, 26 Sep 2023 11:58:19 +0200 Subject: [PATCH 459/662] #3356 Small change to temp table creation --- sp_Blitz.sql | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index fd51fff30..a35e53317 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -266,14 +266,14 @@ AS IF ISNULL(@SkipXPRegRead, 0) != 1 /*If @SkipXPRegRead hasn't been set to 1 by the caller*/ BEGIN - IF OBJECT_ID(N'tempdb..#XpRegReadTest') IS NULL - BEGIN - CREATE TABLE #XpRegReadTest - ( - [Value] varchar(20) - ,[Data] int - ); - END; + IF OBJECT_ID(N'tempdb..#XpRegReadTest') IS NOT NULL + EXEC sp_executesql N'DROP TABLE #XpRegReadTest;'; + + CREATE TABLE #XpRegReadTest + ( + [Value] varchar(20) + ,[Data] int + ); BEGIN TRY INSERT INTO #XpRegReadTest @@ -292,12 +292,9 @@ AS END CATCH; IF OBJECT_ID(N'tempdb..#XpRegReadTest') IS NOT NULL - BEGIN - DROP TABLE #XpRegReadTest; - END; + EXEC sp_executesql N'DROP TABLE #XpRegReadTest;'; END; /*Need execute on xp_regread*/ - IF NOT EXISTS ( SELECT From 4f4cc3dbc09941dd9f17ddc1f50c365863742198 Mon Sep 17 00:00:00 2001 From: Martin van der Boom Date: Tue, 26 Sep 2023 12:08:09 +0200 Subject: [PATCH 460/662] #3356 Reworked the flow so no extra temp table is needed --- sp_Blitz.sql | 45 +++++++++++++++++---------------------------- 1 file changed, 17 insertions(+), 28 deletions(-) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index 4084f4f73..7d30b5e7a 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -225,6 +225,16 @@ AS /* End of declarations for First Responder Kit consistency check:*/ ; + /* Create temp table for check 2301 */ + IF OBJECT_ID('tempdb..#InvalidLogins') IS NOT NULL + EXEC sp_executesql N'DROP TABLE #InvalidLogins;'; + + CREATE TABLE #InvalidLogins + ( + LoginSID varbinary(85), + LoginName VARCHAR(256) + ); + /*Starting permissions checks here, but only if we're not a sysadmin*/ IF ( @@ -299,20 +309,12 @@ AS IF ISNULL(@SkipValidateLogins, 0) != 1 /*If @SkipValidateLogins hasn't been set to 1 by the caller*/ BEGIN - IF OBJECT_ID(N'tempdb..#ValidateLoginsTest') IS NOT NULL - EXEC sp_executesql N'DROP TABLE #ValidateLoginsTest;'; - - CREATE TABLE #ValidateLoginsTest - ( - [SID] varbinary(85) - ,[NT_Login] sysname - ); - BEGIN TRY - INSERT INTO #ValidateLoginsTest + /* Try to fill the table for check 2301 */ + INSERT INTO #InvalidLogins ( - [SID] - ,[NT_Login] + [LoginSID] + ,[LoginName] ) EXEC sp_validatelogins; @@ -321,9 +323,6 @@ AS BEGIN CATCH SET @SkipValidateLogins = 1; /*We have don't have execute rights or sp_validatelogins throws an error so skip it*/ END CATCH; - - IF OBJECT_ID(N'tempdb..#ValidateLoginsTest') IS NOT NULL - EXEC sp_executesql N'DROP TABLE #ValidateLoginsTest;'; END; /*Need execute on sp_validatelogins*/ IF ISNULL(@SkipModel, 0) != 1 /*If @SkipModel hasn't been set to 1 by the caller*/ @@ -562,16 +561,6 @@ AS FROM (VALUES(NULL, 2301, NULL)) AS v (DatabaseName, CheckID, ServerName) /*sp_validatelogins*/ WHERE @SkipValidateLogins = 1 - IF(OBJECT_ID('tempdb..#InvalidLogins') IS NOT NULL) - BEGIN - EXEC sp_executesql N'DROP TABLE #InvalidLogins;'; - END; - - CREATE TABLE #InvalidLogins ( - LoginSID varbinary(85), - LoginName VARCHAR(256) - ); - IF @SkipChecksTable IS NOT NULL AND @SkipChecksSchema IS NOT NULL AND @SkipChecksDatabase IS NOT NULL @@ -1704,9 +1693,9 @@ AS IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 2301) WITH NOWAIT; - INSERT INTO #InvalidLogins - EXEC sp_validatelogins - ; + /* + #InvalidLogins is filled at the start during the permissions check + */ INSERT INTO #BlitzResults ( CheckID , From 6c753e688d9ae4cd7a7b9c85482feaa0b8a80fc7 Mon Sep 17 00:00:00 2001 From: Martin van der Boom Date: Tue, 26 Sep 2023 12:41:03 +0200 Subject: [PATCH 461/662] #3356 fix for xp_regread without the extra temp table from the previous commit --- sp_Blitz.sql | 77 ++++++++++++++++++++-------------------------------- 1 file changed, 29 insertions(+), 48 deletions(-) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index a35e53317..f2ee047a6 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -199,7 +199,11 @@ AS ,@SkipMSDB bit = 0 ,@SkipModel bit = 0 ,@SkipTempDB bit = 0 - ,@SkipValidateLogins bit = 0; + ,@SkipValidateLogins bit = 0 + /* Variables for check 211: */ + ,@powerScheme varchar(36) + ,@cpu_speed_mhz int + ,@cpu_speed_ghz decimal(18,2); DECLARE @db_perms table @@ -266,33 +270,34 @@ AS IF ISNULL(@SkipXPRegRead, 0) != 1 /*If @SkipXPRegRead hasn't been set to 1 by the caller*/ BEGIN - IF OBJECT_ID(N'tempdb..#XpRegReadTest') IS NOT NULL - EXEC sp_executesql N'DROP TABLE #XpRegReadTest;'; - - CREATE TABLE #XpRegReadTest - ( - [Value] varchar(20) - ,[Data] int - ); - BEGIN TRY - INSERT INTO #XpRegReadTest - ( - [Value] - ,[Data] - ) - EXEC xp_regread @rootkey = N'HKEY_LOCAL_MACHINE', - @key = N'', - @value_name = N''; - - SET @SkipXPRegRead = 0; /*We can execute xp_regread*/ + /* Get power plan if set by group policy [Git Hub Issue #1620] */ + EXEC xp_regread @rootkey = N'HKEY_LOCAL_MACHINE', + @key = N'SOFTWARE\Policies\Microsoft\Power\PowerSettings', + @value_name = N'ActivePowerScheme', + @value = @powerScheme OUTPUT, + @no_output = N'no_output'; + + IF @powersaveSetting IS NULL /* If power plan was not set by group policy, get local value [Git Hub Issue #1620]*/ + EXEC xp_regread @rootkey = N'HKEY_LOCAL_MACHINE', + @key = N'SYSTEM\CurrentControlSet\Control\Power\User\PowerSchemes', + @value_name = N'ActivePowerScheme', + @value = @powerScheme OUTPUT; + + /* Get the cpu speed*/ + EXEC xp_regread @rootkey = N'HKEY_LOCAL_MACHINE', + @key = N'HARDWARE\DESCRIPTION\System\CentralProcessor\0', + @value_name = N'~MHz', + @value = @cpu_speed_mhz OUTPUT; + + /* Convert the Megahertz to Gigaherth */ + SET @cpu_speed_ghz = CAST(CAST(@cpu_speed_mhz AS decimal) / 1000 AS decimal(18,2)); + + SET @SkipXPRegRead = 0; /*We could execute xp_regread*/ END TRY BEGIN CATCH SET @SkipXPRegRead = 1; /*We have don't have execute rights or xp_regread throws an error so skip it*/ END CATCH; - - IF OBJECT_ID(N'tempdb..#XpRegReadTest') IS NOT NULL - EXEC sp_executesql N'DROP TABLE #XpRegReadTest;'; END; /*Need execute on xp_regread*/ IF NOT EXISTS @@ -9103,30 +9108,6 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 211) WITH NOWAIT; - DECLARE @outval VARCHAR(36); - /* Get power plan if set by group policy [Git Hub Issue #1620] */ - EXEC master.sys.xp_regread @rootkey = 'HKEY_LOCAL_MACHINE', - @key = 'SOFTWARE\Policies\Microsoft\Power\PowerSettings', - @value_name = 'ActivePowerScheme', - @value = @outval OUTPUT, - @no_output = 'no_output'; - - IF @outval IS NULL /* If power plan was not set by group policy, get local value [Git Hub Issue #1620]*/ - EXEC master.sys.xp_regread @rootkey = 'HKEY_LOCAL_MACHINE', - @key = 'SYSTEM\CurrentControlSet\Control\Power\User\PowerSchemes', - @value_name = 'ActivePowerScheme', - @value = @outval OUTPUT; - - DECLARE @cpu_speed_mhz int, - @cpu_speed_ghz decimal(18,2); - - EXEC master.sys.xp_regread @rootkey = 'HKEY_LOCAL_MACHINE', - @key = 'HARDWARE\DESCRIPTION\System\CentralProcessor\0', - @value_name = '~MHz', - @value = @cpu_speed_mhz OUTPUT; - - SELECT @cpu_speed_ghz = CAST(CAST(@cpu_speed_mhz AS DECIMAL) / 1000 AS DECIMAL(18,2)); - INSERT INTO #BlitzResults ( CheckID , Priority , @@ -9143,7 +9124,7 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 'Your server has ' + CAST(@cpu_speed_ghz as VARCHAR(4)) + 'GHz CPUs, and is in ' - + CASE @outval + + CASE @powerScheme WHEN 'a1841308-3541-4fab-bc81-f71556f20b4a' THEN 'power saving mode -- are you sure this is a production SQL Server?' WHEN '381b4222-f694-41f0-9685-ff5bb260df2e' From 16e178cb71e38128e84cec4ed3086f5e97e9eeaa Mon Sep 17 00:00:00 2001 From: Martin van der Boom Date: Tue, 26 Sep 2023 12:41:29 +0200 Subject: [PATCH 462/662] Typo --- sp_Blitz.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index f2ee047a6..295320aae 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -290,7 +290,7 @@ AS @value_name = N'~MHz', @value = @cpu_speed_mhz OUTPUT; - /* Convert the Megahertz to Gigaherth */ + /* Convert the Megahertz to Gigahertz */ SET @cpu_speed_ghz = CAST(CAST(@cpu_speed_mhz AS decimal) / 1000 AS decimal(18,2)); SET @SkipXPRegRead = 0; /*We could execute xp_regread*/ From fb0d8fda62f7928c602f17907acb50de91a1b572 Mon Sep 17 00:00:00 2001 From: Martin van der Boom Date: Tue, 26 Sep 2023 13:15:17 +0200 Subject: [PATCH 463/662] #3356 Fix for xp_readerrorlog --- sp_Blitz.sql | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index ada1500a2..d45f60f96 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -8621,12 +8621,22 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 END ELSE BEGIN - INSERT INTO #ErrorLog - EXEC sys.xp_readerrorlog 0, 1, N'Database Instant File Initialization: enabled'; + BEGIN TRY + INSERT INTO #ErrorLog + EXEC sys.xp_readerrorlog 0, 1, N'Database Instant File Initialization: enabled'; + END TRY + BEGIN CATCH + IF @Debug IN (1, 2) RAISERROR('No permissions to execute xp_readerrorlog.', 0, 1) WITH NOWAIT; + END CATCH END - IF @@ROWCOUNT > 0 - begin + IF EXISTS + ( + SELECT 1/0 + FROM #ErrorLog + WHERE LEFT([Text], 45) = N'Database Instant File Initialization: enabled' + ) + BEGIN INSERT INTO #BlitzResults ( CheckID , [Priority] , @@ -8642,7 +8652,7 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 'Instant File Initialization Enabled' AS [Finding] , 'https://www.brentozar.com/go/instant' AS [URL] , 'The service account has the Perform Volume Maintenance Tasks permission.'; - end + END; else -- if version of sql server has instant_file_initialization_enabled column in dm_server_services, check that too -- in the event the error log has been cycled and the startup messages are not in the current error log begin From 9bb31e0fb6e5a9ec6ba765aaf4cb0ef2b34f75fa Mon Sep 17 00:00:00 2001 From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com> Date: Thu, 28 Sep 2023 09:52:01 -0400 Subject: [PATCH 464/662] Update sp_BlitzLock.sql Something really weird is going on with my repo. I'm going to create a pull request for #3347 and #3329 to see what happens. --- sp_BlitzLock.sql | 126 ++++++++++++++++++++++++++++++----------------- 1 file changed, 82 insertions(+), 44 deletions(-) diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index 6eb00b83c..9d842a97f 100644 --- a/sp_BlitzLock.sql +++ b/sp_BlitzLock.sql @@ -314,7 +314,7 @@ BEGIN RETURN; END; END; - + IF @Azure = 1 BEGIN IF NOT EXISTS @@ -1844,7 +1844,7 @@ BEGIN COUNT_BIG(DISTINCT dp.event_date) ) + N' deadlocks.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT dp.event_date) DESC) FROM #deadlock_process AS dp @@ -1878,7 +1878,18 @@ BEGIN check_id = 2, dow.database_name, object_name = - N'You Might Need RCSI', + CASE + WHEN EXISTS + ( + SELECT + 1/0 + FROM sys.databases AS d + WHERE d.name = dow.database_name + AND d.is_read_committed_snapshot_on = 1 + ) + THEN N'You already enabled RCSI, but...' + ELSE N'You Might Need RCSI' + END, finding_group = N'Total Deadlocks Involving Selects', finding = N'There have been ' + @@ -1888,7 +1899,7 @@ BEGIN COUNT_BIG(DISTINCT dow.event_date) ) + N' deadlock(s) between read queries and modification queries.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT dow.event_date) DESC) FROM #deadlock_owner_waiter AS dow @@ -1950,7 +1961,7 @@ BEGIN COUNT_BIG(DISTINCT dow.event_date) ) + N' deadlock(s).', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT dow.event_date) DESC) FROM #deadlock_owner_waiter AS dow @@ -1993,7 +2004,7 @@ BEGIN COUNT_BIG(DISTINCT dow.event_date) ) + N' deadlock(s).', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT dow.event_date) DESC) FROM #deadlock_owner_waiter AS dow @@ -2042,7 +2053,7 @@ BEGIN COUNT_BIG(DISTINCT dow.event_date) ) + N' deadlock(s).', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT dow.event_date) DESC) FROM #deadlock_owner_waiter AS dow @@ -2091,7 +2102,7 @@ BEGIN COUNT_BIG(DISTINCT dp.event_date) ) + N' instances of Serializable deadlocks.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT dp.event_date) DESC) FROM #deadlock_process AS dp @@ -2134,7 +2145,7 @@ BEGIN COUNT_BIG(DISTINCT dp.event_date) ) + N' instances of Repeatable Read deadlocks.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT dp.event_date) DESC) FROM #deadlock_process AS dp @@ -2196,7 +2207,7 @@ BEGIN N'UNKNOWN' ) + N'.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT dp.event_date) DESC) FROM #deadlock_process AS dp @@ -2308,7 +2319,7 @@ BEGIN 1, N'' ) + N' locks.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY CONVERT(bigint, lt.lock_count) DESC) FROM lock_types AS lt @@ -2487,7 +2498,7 @@ BEGIN COUNT_BIG(DISTINCT ds.id) ) + N' deadlocks.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT ds.id) DESC) FROM #deadlock_stack AS ds @@ -2588,19 +2599,19 @@ BEGIN ) ), wait_time_hms = - /*the more wait time you rack up the less accurate this gets, + /*the more wait time you rack up the less accurate this gets, it's either that or erroring out*/ - CASE - WHEN + CASE + WHEN SUM ( CONVERT ( - bigint, + bigint, dp.wait_time ) )/1000 > 2147483647 - THEN + THEN CONVERT ( nvarchar(30), @@ -2613,7 +2624,7 @@ BEGIN ( CONVERT ( - bigint, + bigint, dp.wait_time ) ) @@ -2624,16 +2635,16 @@ BEGIN ), 14 ) - WHEN + WHEN SUM ( CONVERT ( - bigint, + bigint, dp.wait_time ) ) BETWEEN 2147483648 AND 2147483647000 - THEN + THEN CONVERT ( nvarchar(30), @@ -2646,7 +2657,7 @@ BEGIN ( CONVERT ( - bigint, + bigint, dp.wait_time ) ) @@ -2728,7 +2739,7 @@ BEGIN 14 ) + N' [dd hh:mm:ss:ms] of deadlock wait time.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY cs.total_waits DESC) FROM chopsuey AS cs @@ -2802,19 +2813,19 @@ BEGIN ) ) + N' ' + - /*the more wait time you rack up the less accurate this gets, + /*the more wait time you rack up the less accurate this gets, it's either that or erroring out*/ - CASE - WHEN + CASE + WHEN SUM ( CONVERT ( - bigint, + bigint, wt.total_wait_time_ms ) )/1000 > 2147483647 - THEN + THEN CONVERT ( nvarchar(30), @@ -2827,7 +2838,7 @@ BEGIN ( CONVERT ( - bigint, + bigint, wt.total_wait_time_ms ) ) @@ -2838,16 +2849,16 @@ BEGIN ), 14 ) - WHEN + WHEN SUM ( CONVERT ( - bigint, + bigint, wt.total_wait_time_ms ) ) BETWEEN 2147483648 AND 2147483647000 - THEN + THEN CONVERT ( nvarchar(30), @@ -2860,7 +2871,7 @@ BEGIN ( CONVERT ( - bigint, + bigint, wt.total_wait_time_ms ) ) @@ -2893,7 +2904,7 @@ BEGIN 14 ) END + N' [dd hh:mm:ss:ms] of deadlock wait time.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY SUM(CONVERT(bigint, wt.total_wait_time_ms)) DESC) FROM wait_time AS wt @@ -2931,7 +2942,7 @@ BEGIN N'There have been ' + RTRIM(COUNT_BIG(DISTINCT aj.event_date)) + N' deadlocks from this Agent Job and Step.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT aj.event_date) DESC) FROM #agent_job AS aj @@ -2977,7 +2988,7 @@ BEGIN /*Check 15 is total deadlocks involving sleeping sessions*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Check 15 sleeping deadlocks %s', 0, 1, @d) WITH NOWAIT; + RAISERROR('Check 15 sleeping and background deadlocks %s', 0, 1, @d) WITH NOWAIT; INSERT #deadlock_findings WITH(TABLOCKX) @@ -3006,6 +3017,33 @@ BEGIN HAVING COUNT_BIG(DISTINCT dp.event_date) > 0 OPTION(RECOMPILE); + INSERT + #deadlock_findings WITH(TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding + ) + SELECT + check_id = 15, + database_name = N'-', + object_name = N'-', + finding_group = N'Total deadlocks involving background processes', + finding = + N'There have been ' + + CONVERT + ( + nvarchar(20), + COUNT_BIG(DISTINCT dp.event_date) + ) + + N' deadlocks with background task.' + FROM #deadlock_process AS dp + WHERE dp.status = N'background' + HAVING COUNT_BIG(DISTINCT dp.event_date) > 0 + OPTION(RECOMPILE); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; /*Check 16 is total deadlocks involving implicit transactions*/ @@ -3684,18 +3722,18 @@ BEGIN BEGIN SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Results to client %s', 0, 1, @d) WITH NOWAIT; - + IF @Debug = 1 BEGIN SET STATISTICS XML ON; END; - + EXEC sys.sp_executesql @deadlock_result; - + IF @Debug = 1 BEGIN SET STATISTICS XML OFF; PRINT @deadlock_result; END; - + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; SET @d = CONVERT(varchar(40), GETDATE(), 109); @@ -3873,10 +3911,10 @@ BEGIN OPTION(RECOMPILE, LOOP JOIN, HASH JOIN); RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - + SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Returning findings %s', 0, 1, @d) WITH NOWAIT; - + SELECT df.check_id, df.database_name, @@ -3888,7 +3926,7 @@ BEGIN df.check_id, df.sort_order OPTION(RECOMPILE); - + SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; END; /*done with output to client app.*/ @@ -3972,7 +4010,7 @@ BEGIN table_name = N'#dm_exec_query_stats', * FROM #dm_exec_query_stats - OPTION(RECOMPILE); + OPTION(RECOMPILE); SELECT procedure_parameters = From b56d881b43b34e7ff880ae8ddea56e06f1cbfed6 Mon Sep 17 00:00:00 2001 From: Martin van der Boom Date: Thu, 5 Oct 2023 09:34:51 +0200 Subject: [PATCH 465/662] #3368 Case issue with @MinimumExecutionCount --- sp_BlitzCache.sql | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/sp_BlitzCache.sql b/sp_BlitzCache.sql index 9eff864d0..b6c3113fa 100644 --- a/sp_BlitzCache.sql +++ b/sp_BlitzCache.sql @@ -851,7 +851,7 @@ IF @SortOrder LIKE 'duplicate%' ORDER BY qs.creation_time DESC) AS mcq_recent OPTION (RECOMPILE); - SET @minimumExecutionCount = 0; + SET @MinimumExecutionCount = 0; END @@ -4969,7 +4969,7 @@ BEGIN IF @MinimumExecutionCount IS NOT NULL BEGIN - SET @sql += N' AND ExecutionCount >= @minimumExecutionCount '; + SET @sql += N' AND ExecutionCount >= @MinimumExecutionCount '; END; IF @MinutesBack IS NOT NULL @@ -5018,7 +5018,7 @@ BEGIN PRINT SUBSTRING(@sql, 36000, 40000); END; - EXEC sp_executesql @sql, N'@Top INT, @min_duration INT, @min_back INT, @minimumExecutionCount INT', @Top, @DurationFilter_i, @MinutesBack, @MinimumExecutionCount; + EXEC sp_executesql @sql, N'@Top INT, @min_duration INT, @min_back INT, @MinimumExecutionCount INT', @Top, @DurationFilter_i, @MinutesBack, @MinimumExecutionCount; END; @@ -5225,7 +5225,7 @@ WHERE SPID = @spid ' + @nl; IF @MinimumExecutionCount IS NOT NULL BEGIN - SET @sql += N' AND ExecutionCount >= @minimumExecutionCount ' + @nl; + SET @sql += N' AND ExecutionCount >= @MinimumExecutionCount ' + @nl; END; IF @MinutesBack IS NOT NULL @@ -5268,7 +5268,7 @@ IF @Debug = 1 END; IF(@OutputType <> 'NONE') BEGIN - EXEC sp_executesql @sql, N'@Top INT, @spid INT, @minimumExecutionCount INT, @min_back INT', @Top, @@SPID, @MinimumExecutionCount, @MinutesBack; + EXEC sp_executesql @sql, N'@Top INT, @spid INT, @MinimumExecutionCount INT, @min_back INT', @Top, @@SPID, @MinimumExecutionCount, @MinutesBack; END; /* From 240d357802946187a3244e05e818ccc52dfbb43d Mon Sep 17 00:00:00 2001 From: Martin van der Boom Date: Mon, 9 Oct 2023 09:12:53 +0200 Subject: [PATCH 466/662] #3370 Division by zero in check 20 --- sp_BlitzFirst.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sp_BlitzFirst.sql b/sp_BlitzFirst.sql index 0e822f1be..e1e2be7f3 100644 --- a/sp_BlitzFirst.sql +++ b/sp_BlitzFirst.sql @@ -3364,8 +3364,8 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 'Server Info' AS FindingGroup, 'Wait Time per Core per Sec' AS Finding, 'https://www.brentozar.com/go/measure' AS URL, - CAST((CAST(waits2.waits_ms - waits1.waits_ms AS MONEY)) / 1000 / i.cpu_count / DATEDIFF(ss, waits1.SampleTime, waits2.SampleTime) AS NVARCHAR(20)) AS Details, - (waits2.waits_ms - waits1.waits_ms) / 1000 / i.cpu_count / DATEDIFF(ss, waits1.SampleTime, waits2.SampleTime) AS DetailsInt + CAST((CAST(waits2.waits_ms - waits1.waits_ms AS MONEY)) / 1000 / i.cpu_count / ISNULL(NULLIF(DATEDIFF(ss, waits1.SampleTime, waits2.SampleTime), 0), 1) AS NVARCHAR(20)) AS Details, + (waits2.waits_ms - waits1.waits_ms) / 1000 / i.cpu_count / ISNULL(NULLIF(DATEDIFF(ss, waits1.SampleTime, waits2.SampleTime), 0), 1) AS DetailsInt FROM cores i CROSS JOIN waits1 CROSS JOIN waits2; From b88c0d689f81a1f6d96fe635c75aa4e0b223ba37 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Tue, 10 Oct 2023 06:54:09 -0700 Subject: [PATCH 467/662] 20231010 release prep Bumping version numbers and dates, building install scripts. --- Install-All-Scripts.sql | 1071 ++++++++++++++--------- Install-Core-Blitz-No-Query-Store.sql | 982 ++++++++++++--------- Install-Core-Blitz-With-Query-Store.sql | 1061 +++++++++++++--------- SqlServerVersions.sql | 1 + sp_AllNightLog.sql | 2 +- sp_AllNightLog_Setup.sql | 2 +- sp_Blitz.sql | 2 +- sp_BlitzAnalysis.sql | 2 +- sp_BlitzBackups.sql | 2 +- sp_BlitzCache.sql | 2 +- sp_BlitzFirst.sql | 2 +- sp_BlitzInMemoryOLTP.sql | 2 +- sp_BlitzIndex.sql | 2 +- sp_BlitzLock.sql | 2 +- sp_BlitzQueryStore.sql | 2 +- sp_BlitzWho.sql | 2 +- sp_DatabaseRestore.sql | 2 +- sp_ineachdb.sql | 2 +- 18 files changed, 1905 insertions(+), 1238 deletions(-) diff --git a/Install-All-Scripts.sql b/Install-All-Scripts.sql index ca4313b15..bdb41e6b6 100644 --- a/Install-All-Scripts.sql +++ b/Install-All-Scripts.sql @@ -38,7 +38,7 @@ SET STATISTICS XML OFF; BEGIN; -SELECT @Version = '8.16', @VersionDate = '20230820'; +SELECT @Version = '8.17', @VersionDate = '20231010'; IF(@VersionCheckMode = 1) BEGIN @@ -1375,7 +1375,7 @@ SET STATISTICS XML OFF; BEGIN; -SELECT @Version = '8.16', @VersionDate = '20230820'; +SELECT @Version = '8.17', @VersionDate = '20231010'; IF(@VersionCheckMode = 1) BEGIN @@ -2900,7 +2900,7 @@ AS SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.16', @VersionDate = '20230820'; + SELECT @Version = '8.17', @VersionDate = '20231010'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -2947,6 +2947,7 @@ AS @OutputType ''TABLE''=table | ''COUNT''=row with number found | ''MARKDOWN''=bulleted list (including server info, excluding security findings) | ''SCHEMA''=version and field list | ''XML'' =table output as XML | ''NONE'' = none @IgnorePrioritiesBelow 50=ignore priorities below 50 @IgnorePrioritiesAbove 50=ignore priorities above 50 + @Debug 0=silent (Default) | 1=messages per step | 2=outputs dynamic queries For the rest of the parameters, see https://www.BrentOzar.com/blitz/documentation for details. MIT License @@ -3060,7 +3061,11 @@ AS ,@SkipMSDB bit = 0 ,@SkipModel bit = 0 ,@SkipTempDB bit = 0 - ,@SkipValidateLogins bit = 0; + ,@SkipValidateLogins bit = 0 + /* Variables for check 211: */ + ,@powerScheme varchar(36) + ,@cpu_speed_mhz int + ,@cpu_speed_ghz decimal(18,2); DECLARE @db_perms table @@ -3081,11 +3086,21 @@ AS fmp.permission_name FROM sys.databases AS d CROSS APPLY fn_my_permissions(d.name, 'DATABASE') AS fmp - WHERE fmp.permission_name = N'SELECT' /*Databases where we don't have read permissions*/ + WHERE fmp.permission_name = N'SELECT'; /*Databases where we don't have read permissions*/ /* End of declarations for First Responder Kit consistency check:*/ ; + /* Create temp table for check 2301 */ + IF OBJECT_ID('tempdb..#InvalidLogins') IS NOT NULL + EXEC sp_executesql N'DROP TABLE #InvalidLogins;'; + + CREATE TABLE #InvalidLogins + ( + LoginSID varbinary(85), + LoginName VARCHAR(256) + ); + /*Starting permissions checks here, but only if we're not a sysadmin*/ IF ( @@ -3125,16 +3140,37 @@ AS SET @SkipTrace = 1; END; /*We need this permission to execute trace stuff, apparently*/ - IF NOT EXISTS - ( - SELECT - 1/0 - FROM fn_my_permissions(N'xp_regread', N'OBJECT') AS fmp - WHERE fmp.permission_name = N'EXECUTE' - ) - BEGIN - SET @SkipXPRegRead = 1; - END; /*Need execute on xp_regread*/ + IF ISNULL(@SkipXPRegRead, 0) != 1 /*If @SkipXPRegRead hasn't been set to 1 by the caller*/ + BEGIN + BEGIN TRY + /* Get power plan if set by group policy [Git Hub Issue #1620] */ + EXEC xp_regread @rootkey = N'HKEY_LOCAL_MACHINE', + @key = N'SOFTWARE\Policies\Microsoft\Power\PowerSettings', + @value_name = N'ActivePowerScheme', + @value = @powerScheme OUTPUT, + @no_output = N'no_output'; + + IF @powersaveSetting IS NULL /* If power plan was not set by group policy, get local value [Git Hub Issue #1620]*/ + EXEC xp_regread @rootkey = N'HKEY_LOCAL_MACHINE', + @key = N'SYSTEM\CurrentControlSet\Control\Power\User\PowerSchemes', + @value_name = N'ActivePowerScheme', + @value = @powerScheme OUTPUT; + + /* Get the cpu speed*/ + EXEC xp_regread @rootkey = N'HKEY_LOCAL_MACHINE', + @key = N'HARDWARE\DESCRIPTION\System\CentralProcessor\0', + @value_name = N'~MHz', + @value = @cpu_speed_mhz OUTPUT; + + /* Convert the Megahertz to Gigahertz */ + SET @cpu_speed_ghz = CAST(CAST(@cpu_speed_mhz AS decimal) / 1000 AS decimal(18,2)); + + SET @SkipXPRegRead = 0; /*We could execute xp_regread*/ + END TRY + BEGIN CATCH + SET @SkipXPRegRead = 1; /*We have don't have execute rights or xp_regread throws an error so skip it*/ + END CATCH; + END; /*Need execute on xp_regread*/ IF NOT EXISTS ( @@ -3158,17 +3194,81 @@ AS SET @SkipXPCMDShell = 1; END; /*Need execute on xp_cmdshell*/ - IF NOT EXISTS - ( - SELECT - 1/0 - FROM fn_my_permissions(N'sp_validatelogins', N'OBJECT') AS fmp - WHERE fmp.permission_name = N'EXECUTE' - ) - BEGIN - SET @SkipValidateLogins = 1; - END; /*Need execute on sp_validatelogins*/ + IF ISNULL(@SkipValidateLogins, 0) != 1 /*If @SkipValidateLogins hasn't been set to 1 by the caller*/ + BEGIN + BEGIN TRY + /* Try to fill the table for check 2301 */ + INSERT INTO #InvalidLogins + ( + [LoginSID] + ,[LoginName] + ) + EXEC sp_validatelogins; + + SET @SkipValidateLogins = 0; /*We can execute sp_validatelogins*/ + END TRY + BEGIN CATCH + SET @SkipValidateLogins = 1; /*We have don't have execute rights or sp_validatelogins throws an error so skip it*/ + END CATCH; + END; /*Need execute on sp_validatelogins*/ + + IF ISNULL(@SkipModel, 0) != 1 /*If @SkipModel hasn't been set to 1 by the caller*/ + BEGIN + IF EXISTS + ( + SELECT 1/0 + FROM @db_perms + WHERE database_name = N'model' + ) + BEGIN + BEGIN TRY + IF EXISTS + ( + SELECT 1/0 + FROM model.sys.objects + ) + BEGIN + SET @SkipModel = 0; /*We have read permissions in the model database, and can view the objects*/ + END; + END TRY + BEGIN CATCH + SET @SkipModel = 1; /*We have read permissions in the model database ... oh wait we got tricked, we can't view the objects*/ + END CATCH; + END; + ELSE + BEGIN + SET @SkipModel = 1; /*We don't have read permissions in the model database*/ + END; + END; + IF ISNULL(@SkipMSDB, 0) != 1 /*If @SkipMSDB hasn't been set to 1 by the caller*/ + BEGIN + IF EXISTS + ( + SELECT 1/0 + FROM @db_perms + WHERE database_name = N'msdb' + ) + BEGIN + BEGIN TRY + IF EXISTS + ( + SELECT 1/0 + FROM msdb.sys.objects + ) + BEGIN + SET @SkipMSDB = 0; /*We have read permissions in the msdb database, and can view the objects*/ + END; + END TRY + BEGIN CATCH + SET @SkipMSDB = 1; /*We have read permissions in the msdb database ... oh wait we got tricked, we can't view the objects*/ + END CATCH; + END; + ELSE + BEGIN + SET @SkipMSDB = 1; /*We don't have read permissions in the msdb database*/ + END; + END; END; SET @crlf = NCHAR(13) + NCHAR(10); @@ -3329,11 +3429,26 @@ AS ); /*Skip individial checks where we don't have permissions*/ + INSERT #SkipChecks (DatabaseName, CheckID, ServerName) + SELECT + v.* + FROM (VALUES(NULL, 29, NULL)) AS v (DatabaseName, CheckID, ServerName) /*Looks for user tables in model*/ + WHERE @SkipModel = 1; + INSERT #SkipChecks (DatabaseName, CheckID, ServerName) SELECT - v.* - FROM (VALUES(NULL, 29, NULL)) AS v (DatabaseName, CheckID, ServerName) /*Looks for user tables in model*/ - WHERE NOT EXISTS (SELECT 1/0 FROM @db_perms AS dp WHERE dp.database_name = 'model'); + v.* + FROM (VALUES(NULL, 6, NULL), /*Jobs Owned By Users*/ + (NULL, 28, NULL), /*SQL Agent Job Runs at Startup*/ + (NULL, 57, NULL), /*Tables in the MSDB Database*/ + (NULL, 79, NULL), /*Shrink Database Job*/ + (NULL, 94, NULL), /*Agent Jobs Without Failure Emails*/ + (NULL, 123, NULL), /*Agent Jobs Starting Simultaneously*/ + (NULL, 180, NULL), /*Shrink Database Step In Maintenance Plan*/ + (NULL, 181, NULL), /*Repetitive Maintenance Tasks*/ + (NULL, 219, NULL) /*Alerts Without Event Descriptions*/ + ) AS v (DatabaseName, CheckID, ServerName) + WHERE @SkipMSDB = 1; INSERT #SkipChecks (DatabaseName, CheckID, ServerName) SELECT @@ -3353,6 +3468,12 @@ AS FROM (VALUES(NULL, 92, NULL)) AS v (DatabaseName, CheckID, ServerName) /*xp_fixeddrives*/ WHERE @SkipXPFixedDrives = 1; + INSERT #SkipChecks (DatabaseName, CheckID, ServerName) + SELECT + v.* + FROM (VALUES(NULL, 106, NULL)) AS v (DatabaseName, CheckID, ServerName) /*alter trace*/ + WHERE @SkipTrace = 1; + INSERT #SkipChecks (DatabaseName, CheckID, ServerName) SELECT v.* @@ -3371,16 +3492,6 @@ AS FROM (VALUES(NULL, 2301, NULL)) AS v (DatabaseName, CheckID, ServerName) /*sp_validatelogins*/ WHERE @SkipValidateLogins = 1 - IF(OBJECT_ID('tempdb..#InvalidLogins') IS NOT NULL) - BEGIN - EXEC sp_executesql N'DROP TABLE #InvalidLogins;'; - END; - - CREATE TABLE #InvalidLogins ( - LoginSID varbinary(85), - LoginName VARCHAR(256) - ); - IF @SkipChecksTable IS NOT NULL AND @SkipChecksSchema IS NOT NULL AND @SkipChecksDatabase IS NOT NULL @@ -3936,17 +4047,17 @@ AS least one of the relevant checks is not being skipped then we can extract the dbinfo information. */ - IF NOT EXISTS ( SELECT 1 - FROM #BlitzResults - WHERE CheckID = 223 AND URL = 'https://aws.amazon.com/rds/sqlserver/') - AND ( - NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 2 ) - OR NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 68 ) - ) + IF NOT EXISTS + ( + SELECT 1/0 + FROM #BlitzResults + WHERE CheckID = 223 AND URL = 'https://aws.amazon.com/rds/sqlserver/' + ) AND NOT EXISTS + ( + SELECT 1/0 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID IN (2, 68) + ) BEGIN IF @Debug IN (1, 2) RAISERROR('Extracting DBCC DBINFO data (used in checks 2 and 68).', 0, 1, 68) WITH NOWAIT; @@ -4513,9 +4624,9 @@ AS IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 2301) WITH NOWAIT; - INSERT INTO #InvalidLogins - EXEC sp_validatelogins - ; + /* + #InvalidLogins is filled at the start during the permissions check + */ INSERT INTO #BlitzResults ( CheckID , @@ -11448,12 +11559,22 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 END ELSE BEGIN - INSERT INTO #ErrorLog - EXEC sys.xp_readerrorlog 0, 1, N'Database Instant File Initialization: enabled'; + BEGIN TRY + INSERT INTO #ErrorLog + EXEC sys.xp_readerrorlog 0, 1, N'Database Instant File Initialization: enabled'; + END TRY + BEGIN CATCH + IF @Debug IN (1, 2) RAISERROR('No permissions to execute xp_readerrorlog.', 0, 1) WITH NOWAIT; + END CATCH END - IF @@ROWCOUNT > 0 - begin + IF EXISTS + ( + SELECT 1/0 + FROM #ErrorLog + WHERE LEFT([Text], 45) = N'Database Instant File Initialization: enabled' + ) + BEGIN INSERT INTO #BlitzResults ( CheckID , [Priority] , @@ -11469,7 +11590,7 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 'Instant File Initialization Enabled' AS [Finding] , 'https://www.brentozar.com/go/instant' AS [URL] , 'The service account has the Perform Volume Maintenance Tasks permission.'; - end + END; else -- if version of sql server has instant_file_initialization_enabled column in dm_server_services, check that too -- in the event the error log has been cycled and the startup messages are not in the current error log begin @@ -11910,30 +12031,6 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 211) WITH NOWAIT; - DECLARE @outval VARCHAR(36); - /* Get power plan if set by group policy [Git Hub Issue #1620] */ - EXEC master.sys.xp_regread @rootkey = 'HKEY_LOCAL_MACHINE', - @key = 'SOFTWARE\Policies\Microsoft\Power\PowerSettings', - @value_name = 'ActivePowerScheme', - @value = @outval OUTPUT, - @no_output = 'no_output'; - - IF @outval IS NULL /* If power plan was not set by group policy, get local value [Git Hub Issue #1620]*/ - EXEC master.sys.xp_regread @rootkey = 'HKEY_LOCAL_MACHINE', - @key = 'SYSTEM\CurrentControlSet\Control\Power\User\PowerSchemes', - @value_name = 'ActivePowerScheme', - @value = @outval OUTPUT; - - DECLARE @cpu_speed_mhz int, - @cpu_speed_ghz decimal(18,2); - - EXEC master.sys.xp_regread @rootkey = 'HKEY_LOCAL_MACHINE', - @key = 'HARDWARE\DESCRIPTION\System\CentralProcessor\0', - @value_name = '~MHz', - @value = @cpu_speed_mhz OUTPUT; - - SELECT @cpu_speed_ghz = CAST(CAST(@cpu_speed_mhz AS DECIMAL) / 1000 AS DECIMAL(18,2)); - INSERT INTO #BlitzResults ( CheckID , Priority , @@ -11950,7 +12047,7 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 'Your server has ' + CAST(@cpu_speed_ghz as VARCHAR(4)) + 'GHz CPUs, and is in ' - + CASE @outval + + CASE @powerScheme WHEN 'a1841308-3541-4fab-bc81-f71556f20b4a' THEN 'power saving mode -- are you sure this is a production SQL Server?' WHEN '381b4222-f694-41f0-9685-ff5bb260df2e' @@ -12781,7 +12878,7 @@ AS SET NOCOUNT ON; SET STATISTICS XML OFF; -SELECT @Version = '8.16', @VersionDate = '20230820'; +SELECT @Version = '8.17', @VersionDate = '20231010'; IF(@VersionCheckMode = 1) BEGIN @@ -13659,7 +13756,7 @@ AS SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.16', @VersionDate = '20230820'; + SELECT @Version = '8.17', @VersionDate = '20231010'; IF(@VersionCheckMode = 1) BEGIN @@ -15441,7 +15538,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.16', @VersionDate = '20230820'; +SELECT @Version = '8.17', @VersionDate = '20231010'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -15514,7 +15611,7 @@ IF @Help = 1 UNION ALL SELECT N'@SortOrder', N'VARCHAR(10)', - N'Data processing and display order. @SortOrder will still be used, even when preparing output for a table or for excel. Possible values are: "CPU", "Reads", "Writes", "Duration", "Executions", "Recent Compilations", "Memory Grant", "Unused Grant", "Spills", "Query Hash". Additionally, the word "Average" or "Avg" can be used to sort on averages rather than total. "Executions per minute" and "Executions / minute" can be used to sort by execution per minute. For the truly lazy, "xpm" can also be used. Note that when you use all or all avg, the only parameters you can use are @Top and @DatabaseName. All others will be ignored.' + N'Data processing and display order. @SortOrder will still be used, even when preparing output for a table or for excel. Possible values are: "CPU", "Reads", "Writes", "Duration", "Executions", "Recent Compilations", "Memory Grant", "Unused Grant", "Spills", "Query Hash", "Duplicate". Additionally, the word "Average" or "Avg" can be used to sort on averages rather than total. "Executions per minute" and "Executions / minute" can be used to sort by execution per minute. For the truly lazy, "xpm" can also be used. Note that when you use all or all avg, the only parameters you can use are @Top and @DatabaseName. All others will be ignored.' UNION ALL SELECT N'@UseTriggersAnyway', @@ -15936,12 +16033,30 @@ END; /* Lets get @SortOrder set to lower case here for comparisons later */ SET @SortOrder = LOWER(@SortOrder); +/* Set @Top based on sort */ +IF ( + @Top IS NULL + AND @SortOrder IN ( 'all', 'all sort' ) + ) + BEGIN + SET @Top = 5; + END; + +IF ( + @Top IS NULL + AND @SortOrder NOT IN ( 'all', 'all sort' ) + ) + BEGIN + SET @Top = 10; + END; + + /* If they want to sort by query hash, populate the @OnlyQueryHashes list for them */ IF @SortOrder LIKE 'query hash%' BEGIN RAISERROR('Beginning query hash sort', 0, 1) WITH NOWAIT; - SELECT qs.query_hash, + SELECT TOP(@Top) qs.query_hash, MAX(qs.max_worker_time) AS max_worker_time, COUNT_BIG(*) AS records INTO #query_hash_grouped @@ -15970,22 +16085,32 @@ IF @SortOrder LIKE 'query hash%' END -/* Set @Top based on sort */ -IF ( - @Top IS NULL - AND @SortOrder IN ( 'all', 'all sort' ) - ) - BEGIN - SET @Top = 5; - END; +/* If they want to sort by duplicate, create a table with the worst offenders - issue #3345 */ +IF @SortOrder LIKE 'duplicate%' + BEGIN + RAISERROR('Beginning duplicate query hash sort', 0, 1) WITH NOWAIT; + + /* Find the query hashes that are the most duplicated */ + WITH MostCommonQueries AS ( + SELECT TOP(@Top) qs.query_hash, + COUNT_BIG(*) AS plans + FROM sys.dm_exec_query_stats AS qs + GROUP BY qs.query_hash + HAVING COUNT_BIG(*) > 100 + ORDER BY COUNT_BIG(*) DESC + ) + SELECT mcq_recent.sql_handle, mcq_recent.plan_handle, mcq_recent.creation_time AS duplicate_creation_time, mcq.plans + INTO #duplicate_query_filter + FROM MostCommonQueries mcq + CROSS APPLY ( SELECT TOP 1 qs.sql_handle, qs.plan_handle, qs.creation_time + FROM sys.dm_exec_query_stats qs + WHERE qs.query_hash = mcq.query_hash + ORDER BY qs.creation_time DESC) AS mcq_recent + OPTION (RECOMPILE); + + SET @MinimumExecutionCount = 0; + END -IF ( - @Top IS NULL - AND @SortOrder NOT IN ( 'all', 'all sort' ) - ) - BEGIN - SET @Top = 10; - END; /* validate user inputs */ IF @Top IS NULL @@ -16013,213 +16138,6 @@ IF @MinutesBack IS NOT NULL END; -RAISERROR(N'Creating temp tables for results and warnings.', 0, 1) WITH NOWAIT; - - -IF OBJECT_ID('tempdb.dbo.##BlitzCacheResults') IS NULL -BEGIN - CREATE TABLE ##BlitzCacheResults ( - SPID INT, - ID INT IDENTITY(1,1), - CheckID INT, - Priority TINYINT, - FindingsGroup VARCHAR(50), - Finding VARCHAR(500), - URL VARCHAR(200), - Details VARCHAR(4000) - ); -END; - -IF OBJECT_ID('tempdb.dbo.##BlitzCacheProcs') IS NULL -BEGIN - CREATE TABLE ##BlitzCacheProcs ( - SPID INT , - QueryType NVARCHAR(258), - DatabaseName sysname, - AverageCPU DECIMAL(38,4), - AverageCPUPerMinute DECIMAL(38,4), - TotalCPU DECIMAL(38,4), - PercentCPUByType MONEY, - PercentCPU MONEY, - AverageDuration DECIMAL(38,4), - TotalDuration DECIMAL(38,4), - PercentDuration MONEY, - PercentDurationByType MONEY, - AverageReads BIGINT, - TotalReads BIGINT, - PercentReads MONEY, - PercentReadsByType MONEY, - ExecutionCount BIGINT, - PercentExecutions MONEY, - PercentExecutionsByType MONEY, - ExecutionsPerMinute MONEY, - TotalWrites BIGINT, - AverageWrites MONEY, - PercentWrites MONEY, - PercentWritesByType MONEY, - WritesPerMinute MONEY, - PlanCreationTime DATETIME, - PlanCreationTimeHours AS DATEDIFF(HOUR, PlanCreationTime, SYSDATETIME()), - LastExecutionTime DATETIME, - LastCompletionTime DATETIME, - PlanHandle VARBINARY(64), - [Remove Plan Handle From Cache] AS - CASE WHEN [PlanHandle] IS NOT NULL - THEN 'DBCC FREEPROCCACHE (' + CONVERT(VARCHAR(128), [PlanHandle], 1) + ');' - ELSE 'N/A' END, - SqlHandle VARBINARY(64), - [Remove SQL Handle From Cache] AS - CASE WHEN [SqlHandle] IS NOT NULL - THEN 'DBCC FREEPROCCACHE (' + CONVERT(VARCHAR(128), [SqlHandle], 1) + ');' - ELSE 'N/A' END, - [SQL Handle More Info] AS - CASE WHEN [SqlHandle] IS NOT NULL - THEN 'EXEC sp_BlitzCache @OnlySqlHandles = ''' + CONVERT(VARCHAR(128), [SqlHandle], 1) + '''; ' - ELSE 'N/A' END, - QueryHash BINARY(8), - [Query Hash More Info] AS - CASE WHEN [QueryHash] IS NOT NULL - THEN 'EXEC sp_BlitzCache @OnlyQueryHashes = ''' + CONVERT(VARCHAR(32), [QueryHash], 1) + '''; ' - ELSE 'N/A' END, - QueryPlanHash BINARY(8), - StatementStartOffset INT, - StatementEndOffset INT, - PlanGenerationNum BIGINT, - MinReturnedRows BIGINT, - MaxReturnedRows BIGINT, - AverageReturnedRows MONEY, - TotalReturnedRows BIGINT, - LastReturnedRows BIGINT, - MinGrantKB BIGINT, - MaxGrantKB BIGINT, - MinUsedGrantKB BIGINT, - MaxUsedGrantKB BIGINT, - PercentMemoryGrantUsed MONEY, - AvgMaxMemoryGrant MONEY, - MinSpills BIGINT, - MaxSpills BIGINT, - TotalSpills BIGINT, - AvgSpills MONEY, - QueryText NVARCHAR(MAX), - QueryPlan XML, - /* these next four columns are the total for the type of query. - don't actually use them for anything apart from math by type. - */ - TotalWorkerTimeForType BIGINT, - TotalElapsedTimeForType BIGINT, - TotalReadsForType BIGINT, - TotalExecutionCountForType BIGINT, - TotalWritesForType BIGINT, - NumberOfPlans INT, - NumberOfDistinctPlans INT, - SerialDesiredMemory FLOAT, - SerialRequiredMemory FLOAT, - CachedPlanSize FLOAT, - CompileTime FLOAT, - CompileCPU FLOAT , - CompileMemory FLOAT , - MaxCompileMemory FLOAT , - min_worker_time BIGINT, - max_worker_time BIGINT, - is_forced_plan BIT, - is_forced_parameterized BIT, - is_cursor BIT, - is_optimistic_cursor BIT, - is_forward_only_cursor BIT, - is_fast_forward_cursor BIT, - is_cursor_dynamic BIT, - is_parallel BIT, - is_forced_serial BIT, - is_key_lookup_expensive BIT, - key_lookup_cost FLOAT, - is_remote_query_expensive BIT, - remote_query_cost FLOAT, - frequent_execution BIT, - parameter_sniffing BIT, - unparameterized_query BIT, - near_parallel BIT, - plan_warnings BIT, - plan_multiple_plans INT, - long_running BIT, - downlevel_estimator BIT, - implicit_conversions BIT, - busy_loops BIT, - tvf_join BIT, - tvf_estimate BIT, - compile_timeout BIT, - compile_memory_limit_exceeded BIT, - warning_no_join_predicate BIT, - QueryPlanCost FLOAT, - missing_index_count INT, - unmatched_index_count INT, - min_elapsed_time BIGINT, - max_elapsed_time BIGINT, - age_minutes MONEY, - age_minutes_lifetime MONEY, - is_trivial BIT, - trace_flags_session VARCHAR(1000), - is_unused_grant BIT, - function_count INT, - clr_function_count INT, - is_table_variable BIT, - no_stats_warning BIT, - relop_warnings BIT, - is_table_scan BIT, - backwards_scan BIT, - forced_index BIT, - forced_seek BIT, - forced_scan BIT, - columnstore_row_mode BIT, - is_computed_scalar BIT , - is_sort_expensive BIT, - sort_cost FLOAT, - is_computed_filter BIT, - op_name VARCHAR(100) NULL, - index_insert_count INT NULL, - index_update_count INT NULL, - index_delete_count INT NULL, - cx_insert_count INT NULL, - cx_update_count INT NULL, - cx_delete_count INT NULL, - table_insert_count INT NULL, - table_update_count INT NULL, - table_delete_count INT NULL, - index_ops AS (index_insert_count + index_update_count + index_delete_count + - cx_insert_count + cx_update_count + cx_delete_count + - table_insert_count + table_update_count + table_delete_count), - is_row_level BIT, - is_spatial BIT, - index_dml BIT, - table_dml BIT, - long_running_low_cpu BIT, - low_cost_high_cpu BIT, - stale_stats BIT, - is_adaptive BIT, - index_spool_cost FLOAT, - index_spool_rows FLOAT, - table_spool_cost FLOAT, - table_spool_rows FLOAT, - is_spool_expensive BIT, - is_spool_more_rows BIT, - is_table_spool_expensive BIT, - is_table_spool_more_rows BIT, - estimated_rows FLOAT, - is_bad_estimate BIT, - is_paul_white_electric BIT, - is_row_goal BIT, - is_big_spills BIT, - is_mstvf BIT, - is_mm_join BIT, - is_nonsargable BIT, - select_with_writes BIT, - implicit_conversion_info XML, - cached_execution_parameters XML, - missing_indexes XML, - SetOptions VARCHAR(MAX), - Warnings VARCHAR(MAX), - Pattern NVARCHAR(20) - ); -END; DECLARE @DurationFilter_i INT, @MinMemoryPerQuery INT, @@ -16289,6 +16207,7 @@ SET @SortOrder = CASE WHEN @SortOrder IN ('spill') THEN 'spills' WHEN @SortOrder IN ('avg spill') THEN 'avg spills' WHEN @SortOrder IN ('execution') THEN 'executions' + WHEN @SortOrder IN ('duplicates') THEN 'duplicate' ELSE @SortOrder END RAISERROR(N'Checking sort order', 0, 1) WITH NOWAIT; @@ -16296,7 +16215,7 @@ IF @SortOrder NOT IN ('cpu', 'avg cpu', 'reads', 'avg reads', 'writes', 'avg wri 'duration', 'avg duration', 'executions', 'avg executions', 'compiles', 'memory grant', 'avg memory grant', 'unused grant', 'spills', 'avg spills', 'all', 'all avg', 'sp_BlitzIndex', - 'query hash') + 'query hash', 'duplicate') BEGIN RAISERROR(N'Invalid sort order chosen, reverting to cpu', 16, 1) WITH NOWAIT; SET @SortOrder = 'cpu'; @@ -16334,31 +16253,19 @@ IF EXISTS(SELECT * FROM sys.all_columns WHERE object_id = OBJECT_ID('sys.dm_exec ELSE SET @VersionShowsAirQuoteActualPlans = 0; -IF @Reanalyze = 1 AND OBJECT_ID('tempdb..##BlitzCacheResults') IS NULL - BEGIN - RAISERROR(N'##BlitzCacheResults does not exist, can''t reanalyze', 0, 1) WITH NOWAIT; - SET @Reanalyze = 0; - END; - -IF @Reanalyze = 0 - BEGIN - RAISERROR(N'Cleaning up old warnings for your SPID', 0, 1) WITH NOWAIT; - DELETE ##BlitzCacheResults - WHERE SPID = @@SPID - OPTION (RECOMPILE) ; - RAISERROR(N'Cleaning up old plans for your SPID', 0, 1) WITH NOWAIT; - DELETE ##BlitzCacheProcs - WHERE SPID = @@SPID - OPTION (RECOMPILE) ; - END; - IF @Reanalyze = 1 + BEGIN + IF OBJECT_ID('tempdb..##BlitzCacheResults') IS NULL + BEGIN + RAISERROR(N'##BlitzCacheResults does not exist, can''t reanalyze', 0, 1) WITH NOWAIT; + SET @Reanalyze = 0; + END + ELSE BEGIN RAISERROR(N'Reanalyzing current data, skipping to results', 0, 1) WITH NOWAIT; GOTO Results; END; - - + END; IF @SortOrder IN ('all', 'all avg') @@ -16793,18 +16700,8 @@ WITH total_plans AS ( SELECT COUNT_BIG(*) AS single_use_plan_count - FROM sys.dm_exec_cached_plans AS cp - WHERE cp.usecounts = 1 - AND cp.objtype = N'Adhoc' - AND EXISTS - ( - SELECT - 1/0 - FROM sys.configurations AS c - WHERE c.name = N'optimize for ad hoc workloads' - AND c.value_in_use = 0 - ) - HAVING COUNT_BIG(*) > 1 + FROM sys.dm_exec_query_stats AS s + WHERE s.execution_count = 1 ) INSERT #plan_usage @@ -17151,6 +17048,10 @@ FROM (SELECT TOP (@Top) x.*, xpa.*, CROSS APPLY (SELECT * FROM sys.dm_exec_plan_attributes(x.plan_handle) AS ixpa WHERE ixpa.attribute = ''dbid'') AS xpa ' + @nl ; +IF @SortOrder = 'duplicate' /* Issue #3345 */ + BEGIN + SET @body += N' INNER JOIN #duplicate_query_filter AS dqf ON x.sql_handle = dqf.sql_handle AND x.plan_handle = dqf.plan_handle AND x.creation_time = dqf.duplicate_creation_time ' + @nl ; + END IF @VersionShowsAirQuoteActualPlans = 1 BEGIN @@ -17209,7 +17110,6 @@ BEGIN END; /* end filtering for query hashes */ - IF @DurationFilter IS NOT NULL BEGIN RAISERROR(N'Setting duration filter', 0, 1) WITH NOWAIT; @@ -17245,6 +17145,7 @@ SELECT @body += N' ORDER BY ' + WHEN N'memory grant' THEN N'max_grant_kb' WHEN N'unused grant' THEN N'max_grant_kb - max_used_grant_kb' WHEN N'spills' THEN N'max_spills' + WHEN N'duplicate' THEN N'total_worker_time' /* Issue #3345 */ /* And now the averages */ WHEN N'avg cpu' THEN N'total_worker_time / execution_count' WHEN N'avg reads' THEN N'total_logical_reads / execution_count' @@ -17286,7 +17187,6 @@ IF @VersionShowsAirQuoteActualPlans = 1 SET @body_where += N' AND pa.attribute = ' + QUOTENAME('dbid', @q ) + @nl ; - IF @NoobSaibot = 1 BEGIN SET @body_where += N' AND qp.query_plan.exist(''declare namespace p="http://schemas.microsoft.com/sqlserver/2004/07/showplan";//p:StmtSimple//p:MissingIndex'') = 1' + @nl ; @@ -17595,7 +17495,7 @@ END; IF (@QueryFilter = 'all' AND (SELECT COUNT(*) FROM #only_query_hashes) = 0 AND (SELECT COUNT(*) FROM #ignore_query_hashes) = 0) - AND (@SortOrder NOT IN ('memory grant', 'avg memory grant', 'unused grant')) + AND (@SortOrder NOT IN ('memory grant', 'avg memory grant', 'unused grant', 'duplicate')) /* Issue #3345 added 'duplicate' */ OR (LEFT(@QueryFilter, 3) = 'pro') BEGIN SET @sql += @insert_list; @@ -17616,7 +17516,7 @@ IF (@v >= 13 AND @QueryFilter = 'all' AND (SELECT COUNT(*) FROM #only_query_hashes) = 0 AND (SELECT COUNT(*) FROM #ignore_query_hashes) = 0) - AND (@SortOrder NOT IN ('memory grant', 'avg memory grant', 'unused grant')) + AND (@SortOrder NOT IN ('memory grant', 'avg memory grant', 'unused grant', 'duplicate')) /* Issue #3345 added 'duplicate' */ AND (@SortOrder NOT IN ('spills', 'avg spills')) OR (LEFT(@QueryFilter, 3) = 'fun') BEGIN @@ -17659,7 +17559,7 @@ IF (@UseTriggersAnyway = 1 OR @v >= 11) AND (SELECT COUNT(*) FROM #only_query_hashes) = 0 AND (SELECT COUNT(*) FROM #ignore_query_hashes) = 0 AND (@QueryFilter = 'all') - AND (@SortOrder NOT IN ('memory grant', 'avg memory grant', 'unused grant')) + AND (@SortOrder NOT IN ('memory grant', 'avg memory grant', 'unused grant', 'duplicate')) /* Issue #3345 added 'duplicate' */ BEGIN RAISERROR (N'Adding SQL to collect trigger stats.',0,1) WITH NOWAIT; @@ -17691,6 +17591,7 @@ SELECT @sort = CASE @SortOrder WHEN N'cpu' THEN N'total_worker_time' WHEN N'memory grant' THEN N'max_grant_kb' WHEN N'unused grant' THEN N'max_grant_kb - max_used_grant_kb' WHEN N'spills' THEN N'max_spills' + WHEN N'duplicate' THEN N'total_worker_time' /* Issue #3345 */ /* And now the averages */ WHEN N'avg cpu' THEN N'total_worker_time / execution_count' WHEN N'avg reads' THEN N'total_logical_reads / execution_count' @@ -17752,6 +17653,7 @@ SELECT @sort = CASE @SortOrder WHEN N'cpu' THEN N'TotalCPU' WHEN N'memory grant' THEN N'MaxGrantKB' WHEN N'unused grant' THEN N'MaxGrantKB - MaxUsedGrantKB' WHEN N'spills' THEN N'MaxSpills' + WHEN N'duplicate' THEN N'TotalCPU' /* Issue #3345 */ /* And now the averages */ WHEN N'avg cpu' THEN N'TotalCPU / ExecutionCount' WHEN N'avg reads' THEN N'TotalReads / ExecutionCount' @@ -17770,6 +17672,7 @@ SELECT @sql = REPLACE(@sql, '#sortable#', @sort); IF @Debug = 1 BEGIN + PRINT N'Printing dynamic SQL stored in @sql: '; PRINT SUBSTRING(@sql, 0, 4000); PRINT SUBSTRING(@sql, 4000, 8000); PRINT SUBSTRING(@sql, 8000, 12000); @@ -17782,6 +17685,229 @@ IF @Debug = 1 PRINT SUBSTRING(@sql, 36000, 40000); END; +RAISERROR(N'Creating temp tables for results and warnings.', 0, 1) WITH NOWAIT; + + +IF OBJECT_ID('tempdb.dbo.##BlitzCacheResults') IS NULL +BEGIN + CREATE TABLE ##BlitzCacheResults ( + SPID INT, + ID INT IDENTITY(1,1), + CheckID INT, + Priority TINYINT, + FindingsGroup VARCHAR(50), + Finding VARCHAR(500), + URL VARCHAR(200), + Details VARCHAR(4000) + ); +END; +ELSE +BEGIN + RAISERROR(N'Cleaning up old warnings for your SPID', 0, 1) WITH NOWAIT; + DELETE ##BlitzCacheResults + WHERE SPID = @@SPID + OPTION (RECOMPILE) ; +END + + +IF OBJECT_ID('tempdb.dbo.##BlitzCacheProcs') IS NULL +BEGIN + CREATE TABLE ##BlitzCacheProcs ( + SPID INT , + QueryType NVARCHAR(258), + DatabaseName sysname, + AverageCPU DECIMAL(38,4), + AverageCPUPerMinute DECIMAL(38,4), + TotalCPU DECIMAL(38,4), + PercentCPUByType MONEY, + PercentCPU MONEY, + AverageDuration DECIMAL(38,4), + TotalDuration DECIMAL(38,4), + PercentDuration MONEY, + PercentDurationByType MONEY, + AverageReads BIGINT, + TotalReads BIGINT, + PercentReads MONEY, + PercentReadsByType MONEY, + ExecutionCount BIGINT, + PercentExecutions MONEY, + PercentExecutionsByType MONEY, + ExecutionsPerMinute MONEY, + TotalWrites BIGINT, + AverageWrites MONEY, + PercentWrites MONEY, + PercentWritesByType MONEY, + WritesPerMinute MONEY, + PlanCreationTime DATETIME, + PlanCreationTimeHours AS DATEDIFF(HOUR, PlanCreationTime, SYSDATETIME()), + LastExecutionTime DATETIME, + LastCompletionTime DATETIME, + PlanHandle VARBINARY(64), + [Remove Plan Handle From Cache] AS + CASE WHEN [PlanHandle] IS NOT NULL + THEN 'DBCC FREEPROCCACHE (' + CONVERT(VARCHAR(128), [PlanHandle], 1) + ');' + ELSE 'N/A' END, + SqlHandle VARBINARY(64), + [Remove SQL Handle From Cache] AS + CASE WHEN [SqlHandle] IS NOT NULL + THEN 'DBCC FREEPROCCACHE (' + CONVERT(VARCHAR(128), [SqlHandle], 1) + ');' + ELSE 'N/A' END, + [SQL Handle More Info] AS + CASE WHEN [SqlHandle] IS NOT NULL + THEN 'EXEC sp_BlitzCache @OnlySqlHandles = ''' + CONVERT(VARCHAR(128), [SqlHandle], 1) + '''; ' + ELSE 'N/A' END, + QueryHash BINARY(8), + [Query Hash More Info] AS + CASE WHEN [QueryHash] IS NOT NULL + THEN 'EXEC sp_BlitzCache @OnlyQueryHashes = ''' + CONVERT(VARCHAR(32), [QueryHash], 1) + '''; ' + ELSE 'N/A' END, + QueryPlanHash BINARY(8), + StatementStartOffset INT, + StatementEndOffset INT, + PlanGenerationNum BIGINT, + MinReturnedRows BIGINT, + MaxReturnedRows BIGINT, + AverageReturnedRows MONEY, + TotalReturnedRows BIGINT, + LastReturnedRows BIGINT, + MinGrantKB BIGINT, + MaxGrantKB BIGINT, + MinUsedGrantKB BIGINT, + MaxUsedGrantKB BIGINT, + PercentMemoryGrantUsed MONEY, + AvgMaxMemoryGrant MONEY, + MinSpills BIGINT, + MaxSpills BIGINT, + TotalSpills BIGINT, + AvgSpills MONEY, + QueryText NVARCHAR(MAX), + QueryPlan XML, + /* these next four columns are the total for the type of query. + don't actually use them for anything apart from math by type. + */ + TotalWorkerTimeForType BIGINT, + TotalElapsedTimeForType BIGINT, + TotalReadsForType BIGINT, + TotalExecutionCountForType BIGINT, + TotalWritesForType BIGINT, + NumberOfPlans INT, + NumberOfDistinctPlans INT, + SerialDesiredMemory FLOAT, + SerialRequiredMemory FLOAT, + CachedPlanSize FLOAT, + CompileTime FLOAT, + CompileCPU FLOAT , + CompileMemory FLOAT , + MaxCompileMemory FLOAT , + min_worker_time BIGINT, + max_worker_time BIGINT, + is_forced_plan BIT, + is_forced_parameterized BIT, + is_cursor BIT, + is_optimistic_cursor BIT, + is_forward_only_cursor BIT, + is_fast_forward_cursor BIT, + is_cursor_dynamic BIT, + is_parallel BIT, + is_forced_serial BIT, + is_key_lookup_expensive BIT, + key_lookup_cost FLOAT, + is_remote_query_expensive BIT, + remote_query_cost FLOAT, + frequent_execution BIT, + parameter_sniffing BIT, + unparameterized_query BIT, + near_parallel BIT, + plan_warnings BIT, + plan_multiple_plans INT, + long_running BIT, + downlevel_estimator BIT, + implicit_conversions BIT, + busy_loops BIT, + tvf_join BIT, + tvf_estimate BIT, + compile_timeout BIT, + compile_memory_limit_exceeded BIT, + warning_no_join_predicate BIT, + QueryPlanCost FLOAT, + missing_index_count INT, + unmatched_index_count INT, + min_elapsed_time BIGINT, + max_elapsed_time BIGINT, + age_minutes MONEY, + age_minutes_lifetime MONEY, + is_trivial BIT, + trace_flags_session VARCHAR(1000), + is_unused_grant BIT, + function_count INT, + clr_function_count INT, + is_table_variable BIT, + no_stats_warning BIT, + relop_warnings BIT, + is_table_scan BIT, + backwards_scan BIT, + forced_index BIT, + forced_seek BIT, + forced_scan BIT, + columnstore_row_mode BIT, + is_computed_scalar BIT , + is_sort_expensive BIT, + sort_cost FLOAT, + is_computed_filter BIT, + op_name VARCHAR(100) NULL, + index_insert_count INT NULL, + index_update_count INT NULL, + index_delete_count INT NULL, + cx_insert_count INT NULL, + cx_update_count INT NULL, + cx_delete_count INT NULL, + table_insert_count INT NULL, + table_update_count INT NULL, + table_delete_count INT NULL, + index_ops AS (index_insert_count + index_update_count + index_delete_count + + cx_insert_count + cx_update_count + cx_delete_count + + table_insert_count + table_update_count + table_delete_count), + is_row_level BIT, + is_spatial BIT, + index_dml BIT, + table_dml BIT, + long_running_low_cpu BIT, + low_cost_high_cpu BIT, + stale_stats BIT, + is_adaptive BIT, + index_spool_cost FLOAT, + index_spool_rows FLOAT, + table_spool_cost FLOAT, + table_spool_rows FLOAT, + is_spool_expensive BIT, + is_spool_more_rows BIT, + is_table_spool_expensive BIT, + is_table_spool_more_rows BIT, + estimated_rows FLOAT, + is_bad_estimate BIT, + is_paul_white_electric BIT, + is_row_goal BIT, + is_big_spills BIT, + is_mstvf BIT, + is_mm_join BIT, + is_nonsargable BIT, + select_with_writes BIT, + implicit_conversion_info XML, + cached_execution_parameters XML, + missing_indexes XML, + SetOptions VARCHAR(MAX), + Warnings VARCHAR(MAX), + Pattern NVARCHAR(20) + ); +END; +ELSE +BEGIN + RAISERROR(N'Cleaning up old plans for your SPID', 0, 1) WITH NOWAIT; + DELETE ##BlitzCacheProcs + WHERE SPID = @@SPID + OPTION (RECOMPILE) ; +END + IF @Reanalyze = 0 BEGIN RAISERROR('Collecting execution plan information.', 0, 1) WITH NOWAIT; @@ -20100,7 +20226,7 @@ BEGIN IF @MinimumExecutionCount IS NOT NULL BEGIN - SET @sql += N' AND ExecutionCount >= @minimumExecutionCount '; + SET @sql += N' AND ExecutionCount >= @MinimumExecutionCount '; END; IF @MinutesBack IS NOT NULL @@ -20117,6 +20243,7 @@ BEGIN WHEN N'memory grant' THEN N' MaxGrantKB' WHEN N'unused grant' THEN N' MaxGrantKB - MaxUsedGrantKB' WHEN N'spills' THEN N' MaxSpills' + WHEN N'duplicate' THEN N' plan_multiple_plans ' /* Issue #3345 */ WHEN N'avg cpu' THEN N' AverageCPU' WHEN N'avg reads' THEN N' AverageReads' WHEN N'avg writes' THEN N' AverageWrites' @@ -20128,8 +20255,14 @@ BEGIN SET @sql += N' OPTION (RECOMPILE) ; '; + IF @sql IS NULL + BEGIN + RAISERROR('@sql is null, which means dynamic SQL generation went terribly wrong', 0, 1) WITH NOWAIT; + END + IF @Debug = 1 BEGIN + RAISERROR('Printing @sql, the dynamic SQL we generated:', 0, 1) WITH NOWAIT; PRINT SUBSTRING(@sql, 0, 4000); PRINT SUBSTRING(@sql, 4000, 8000); PRINT SUBSTRING(@sql, 8000, 12000); @@ -20142,7 +20275,7 @@ BEGIN PRINT SUBSTRING(@sql, 36000, 40000); END; - EXEC sp_executesql @sql, N'@Top INT, @min_duration INT, @min_back INT, @minimumExecutionCount INT', @Top, @DurationFilter_i, @MinutesBack, @MinimumExecutionCount; + EXEC sp_executesql @sql, N'@Top INT, @min_duration INT, @min_back INT, @MinimumExecutionCount INT', @Top, @DurationFilter_i, @MinutesBack, @MinimumExecutionCount; END; @@ -20341,8 +20474,6 @@ BEGIN [Remove SQL Handle From Cache]'; END; - - SET @sql = N' SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; SELECT TOP (@Top) ' + @columns + @nl + N' @@ -20351,7 +20482,7 @@ WHERE SPID = @spid ' + @nl; IF @MinimumExecutionCount IS NOT NULL BEGIN - SET @sql += N' AND ExecutionCount >= @minimumExecutionCount ' + @nl; + SET @sql += N' AND ExecutionCount >= @MinimumExecutionCount ' + @nl; END; IF @MinutesBack IS NOT NULL @@ -20367,7 +20498,8 @@ SELECT @sql += N' ORDER BY ' + CASE @SortOrder WHEN N'cpu' THEN N' TotalCPU ' WHEN N'compiles' THEN N' PlanCreationTime ' WHEN N'memory grant' THEN N' MaxGrantKB' WHEN N'unused grant' THEN N' MaxGrantKB - MaxUsedGrantKB ' - WHEN N'spills' THEN N' MaxSpills' + WHEN N'duplicate' THEN N' plan_multiple_plans ' + WHEN N'spills' THEN N' MaxSpills ' WHEN N'avg cpu' THEN N' AverageCPU' WHEN N'avg reads' THEN N' AverageReads' WHEN N'avg writes' THEN N' AverageWrites' @@ -20393,7 +20525,7 @@ IF @Debug = 1 END; IF(@OutputType <> 'NONE') BEGIN - EXEC sp_executesql @sql, N'@Top INT, @spid INT, @minimumExecutionCount INT, @min_back INT', @Top, @@SPID, @MinimumExecutionCount, @MinutesBack; + EXEC sp_executesql @sql, N'@Top INT, @spid INT, @MinimumExecutionCount INT, @min_back INT', @Top, @@SPID, @MinimumExecutionCount, @MinutesBack; END; /* @@ -22068,7 +22200,7 @@ SET @AllSortSql += N' EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''avg spills'', @IgnoreSqlHandles = @ISH, @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; - UPDATE #bou_allsort SET Pattern = ''avg memory grant'' WHERE Pattern IS NULL OPTION(RECOMPILE);'; + UPDATE #bou_allsort SET Pattern = ''avg spills'' WHERE Pattern IS NULL OPTION(RECOMPILE);'; IF @ExportToExcel = 1 BEGIN SET @AllSortSql += N' UPDATE #bou_allsort @@ -22115,6 +22247,11 @@ END; EXEC sys.sp_executesql @stmt = @AllSortSql, @params = N'@i_DatabaseName NVARCHAR(128), @i_Top INT, @i_SkipAnalysis BIT, @i_OutputDatabaseName NVARCHAR(258), @i_OutputSchemaName NVARCHAR(258), @i_OutputTableName NVARCHAR(258), @i_CheckDateOverride DATETIMEOFFSET, @i_MinutesBack INT ', @i_DatabaseName = @DatabaseName, @i_Top = @Top, @i_SkipAnalysis = @SkipAnalysis, @i_OutputDatabaseName = @OutputDatabaseName, @i_OutputSchemaName = @OutputSchemaName, @i_OutputTableName = @OutputTableName, @i_CheckDateOverride = @CheckDateOverride, @i_MinutesBack = @MinutesBack; +/* Avoid going into OutputResultsToTable + ... otherwise the last result (e.g. spills) would be recorded twice into the output table. +*/ +RETURN; + /*End of AllSort section*/ @@ -22760,7 +22897,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.16', @VersionDate = '20230820'; +SELECT @Version = '8.17', @VersionDate = '20231010'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -28945,7 +29082,7 @@ BEGIN SET NOCOUNT, XACT_ABORT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.16', @VersionDate = '20230820'; + SELECT @Version = '8.17', @VersionDate = '20231010'; IF @VersionCheckMode = 1 BEGIN @@ -29224,7 +29361,7 @@ BEGIN RETURN; END; END; - + IF @Azure = 1 BEGIN IF NOT EXISTS @@ -30754,7 +30891,7 @@ BEGIN COUNT_BIG(DISTINCT dp.event_date) ) + N' deadlocks.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT dp.event_date) DESC) FROM #deadlock_process AS dp @@ -30788,7 +30925,18 @@ BEGIN check_id = 2, dow.database_name, object_name = - N'You Might Need RCSI', + CASE + WHEN EXISTS + ( + SELECT + 1/0 + FROM sys.databases AS d + WHERE d.name = dow.database_name + AND d.is_read_committed_snapshot_on = 1 + ) + THEN N'You already enabled RCSI, but...' + ELSE N'You Might Need RCSI' + END, finding_group = N'Total Deadlocks Involving Selects', finding = N'There have been ' + @@ -30798,7 +30946,7 @@ BEGIN COUNT_BIG(DISTINCT dow.event_date) ) + N' deadlock(s) between read queries and modification queries.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT dow.event_date) DESC) FROM #deadlock_owner_waiter AS dow @@ -30860,7 +31008,7 @@ BEGIN COUNT_BIG(DISTINCT dow.event_date) ) + N' deadlock(s).', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT dow.event_date) DESC) FROM #deadlock_owner_waiter AS dow @@ -30903,7 +31051,7 @@ BEGIN COUNT_BIG(DISTINCT dow.event_date) ) + N' deadlock(s).', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT dow.event_date) DESC) FROM #deadlock_owner_waiter AS dow @@ -30952,7 +31100,7 @@ BEGIN COUNT_BIG(DISTINCT dow.event_date) ) + N' deadlock(s).', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT dow.event_date) DESC) FROM #deadlock_owner_waiter AS dow @@ -31001,7 +31149,7 @@ BEGIN COUNT_BIG(DISTINCT dp.event_date) ) + N' instances of Serializable deadlocks.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT dp.event_date) DESC) FROM #deadlock_process AS dp @@ -31044,7 +31192,7 @@ BEGIN COUNT_BIG(DISTINCT dp.event_date) ) + N' instances of Repeatable Read deadlocks.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT dp.event_date) DESC) FROM #deadlock_process AS dp @@ -31106,7 +31254,7 @@ BEGIN N'UNKNOWN' ) + N'.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT dp.event_date) DESC) FROM #deadlock_process AS dp @@ -31218,7 +31366,7 @@ BEGIN 1, N'' ) + N' locks.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY CONVERT(bigint, lt.lock_count) DESC) FROM lock_types AS lt @@ -31397,7 +31545,7 @@ BEGIN COUNT_BIG(DISTINCT ds.id) ) + N' deadlocks.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT ds.id) DESC) FROM #deadlock_stack AS ds @@ -31498,19 +31646,19 @@ BEGIN ) ), wait_time_hms = - /*the more wait time you rack up the less accurate this gets, + /*the more wait time you rack up the less accurate this gets, it's either that or erroring out*/ - CASE - WHEN + CASE + WHEN SUM ( CONVERT ( - bigint, + bigint, dp.wait_time ) )/1000 > 2147483647 - THEN + THEN CONVERT ( nvarchar(30), @@ -31523,7 +31671,7 @@ BEGIN ( CONVERT ( - bigint, + bigint, dp.wait_time ) ) @@ -31534,16 +31682,16 @@ BEGIN ), 14 ) - WHEN + WHEN SUM ( CONVERT ( - bigint, + bigint, dp.wait_time ) ) BETWEEN 2147483648 AND 2147483647000 - THEN + THEN CONVERT ( nvarchar(30), @@ -31556,7 +31704,7 @@ BEGIN ( CONVERT ( - bigint, + bigint, dp.wait_time ) ) @@ -31638,7 +31786,7 @@ BEGIN 14 ) + N' [dd hh:mm:ss:ms] of deadlock wait time.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY cs.total_waits DESC) FROM chopsuey AS cs @@ -31712,19 +31860,19 @@ BEGIN ) ) + N' ' + - /*the more wait time you rack up the less accurate this gets, + /*the more wait time you rack up the less accurate this gets, it's either that or erroring out*/ - CASE - WHEN + CASE + WHEN SUM ( CONVERT ( - bigint, + bigint, wt.total_wait_time_ms ) )/1000 > 2147483647 - THEN + THEN CONVERT ( nvarchar(30), @@ -31737,7 +31885,7 @@ BEGIN ( CONVERT ( - bigint, + bigint, wt.total_wait_time_ms ) ) @@ -31748,16 +31896,16 @@ BEGIN ), 14 ) - WHEN + WHEN SUM ( CONVERT ( - bigint, + bigint, wt.total_wait_time_ms ) ) BETWEEN 2147483648 AND 2147483647000 - THEN + THEN CONVERT ( nvarchar(30), @@ -31770,7 +31918,7 @@ BEGIN ( CONVERT ( - bigint, + bigint, wt.total_wait_time_ms ) ) @@ -31803,7 +31951,7 @@ BEGIN 14 ) END + N' [dd hh:mm:ss:ms] of deadlock wait time.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY SUM(CONVERT(bigint, wt.total_wait_time_ms)) DESC) FROM wait_time AS wt @@ -31841,7 +31989,7 @@ BEGIN N'There have been ' + RTRIM(COUNT_BIG(DISTINCT aj.event_date)) + N' deadlocks from this Agent Job and Step.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT aj.event_date) DESC) FROM #agent_job AS aj @@ -31887,7 +32035,7 @@ BEGIN /*Check 15 is total deadlocks involving sleeping sessions*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Check 15 sleeping deadlocks %s', 0, 1, @d) WITH NOWAIT; + RAISERROR('Check 15 sleeping and background deadlocks %s', 0, 1, @d) WITH NOWAIT; INSERT #deadlock_findings WITH(TABLOCKX) @@ -31916,6 +32064,33 @@ BEGIN HAVING COUNT_BIG(DISTINCT dp.event_date) > 0 OPTION(RECOMPILE); + INSERT + #deadlock_findings WITH(TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding + ) + SELECT + check_id = 15, + database_name = N'-', + object_name = N'-', + finding_group = N'Total deadlocks involving background processes', + finding = + N'There have been ' + + CONVERT + ( + nvarchar(20), + COUNT_BIG(DISTINCT dp.event_date) + ) + + N' deadlocks with background task.' + FROM #deadlock_process AS dp + WHERE dp.status = N'background' + HAVING COUNT_BIG(DISTINCT dp.event_date) > 0 + OPTION(RECOMPILE); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; /*Check 16 is total deadlocks involving implicit transactions*/ @@ -32594,18 +32769,18 @@ BEGIN BEGIN SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Results to client %s', 0, 1, @d) WITH NOWAIT; - + IF @Debug = 1 BEGIN SET STATISTICS XML ON; END; - + EXEC sys.sp_executesql @deadlock_result; - + IF @Debug = 1 BEGIN SET STATISTICS XML OFF; PRINT @deadlock_result; END; - + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; SET @d = CONVERT(varchar(40), GETDATE(), 109); @@ -32783,10 +32958,10 @@ BEGIN OPTION(RECOMPILE, LOOP JOIN, HASH JOIN); RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - + SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Returning findings %s', 0, 1, @d) WITH NOWAIT; - + SELECT df.check_id, df.database_name, @@ -32798,7 +32973,7 @@ BEGIN df.check_id, df.sort_order OPTION(RECOMPILE); - + SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; END; /*done with output to client app.*/ @@ -32882,7 +33057,7 @@ BEGIN table_name = N'#dm_exec_query_stats', * FROM #dm_exec_query_stats - OPTION(RECOMPILE); + OPTION(RECOMPILE); SELECT procedure_parameters = @@ -33037,7 +33212,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.16', @VersionDate = '20230820'; +SELECT @Version = '8.17', @VersionDate = '20231010'; IF(@VersionCheckMode = 1) BEGIN RETURN; @@ -33310,6 +33485,28 @@ SET @msg = N'New query_store_runtime_stats columns ' + CASE @new_columns END; RAISERROR(@msg, 0, 1) WITH NOWAIT; +/* +This section determines if Parameter Sensitive Plan Optimization is enabled on SQL Server 2022+. +*/ + +RAISERROR('Checking for Parameter Sensitive Plan Optimization ', 0, 1) WITH NOWAIT; + +DECLARE @pspo_out BIT, + @pspo_enabled BIT, + @pspo_sql NVARCHAR(MAX) = N'SELECT @i_out = CONVERT(bit,dsc.value) + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.database_scoped_configurations dsc + WHERE dsc.name = ''PARAMETER_SENSITIVE_PLAN_OPTIMIZATION'';', + @pspo_params NVARCHAR(MAX) = N'@i_out INT OUTPUT'; + +EXEC sys.sp_executesql @pspo_sql, @pspo_params, @i_out = @pspo_out OUTPUT; + +SET @pspo_enabled = CASE WHEN @pspo_out = 1 THEN 1 ELSE 0 END; + +SET @msg = N'Parameter Sensitive Plan Optimization ' + CASE @pspo_enabled + WHEN 0 THEN N' not enabled, skipping.' + WHEN 1 THEN N' enabled, will analyze.' + END; +RAISERROR(@msg, 0, 1) WITH NOWAIT; /* These are the temp tables we use @@ -34013,10 +34210,33 @@ IF @MinimumExecutionCount IS NOT NULL --You care about stored proc names IF @StoredProcName IS NOT NULL - BEGIN - RAISERROR(N'Setting stored proc filter', 0, 1) WITH NOWAIT; - SET @sql_where += N' AND object_name(qsq.object_id, DB_ID(' + QUOTENAME(@DatabaseName, '''') + N')) = @sp_StoredProcName - '; + BEGIN + + IF (@pspo_enabled = 1) + BEGIN + RAISERROR(N'Setting stored proc filter, PSPO enabled', 0, 1) WITH NOWAIT; + /* If PSPO is enabled, the object_id for a variant query would be 0. To include it, we check whether the object_id = 0 query + is a variant query, and whether it's parent query belongs to @sp_StoredProcName. */ + SET @sql_where += N' AND (object_name(qsq.object_id, DB_ID(' + QUOTENAME(@DatabaseName, '''') + N')) = @sp_StoredProcName + OR (qsq.object_id = 0 + AND EXISTS( + SELECT 1 + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_variant vr + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query pqsq + ON pqsq.query_id = vr.parent_query_id + WHERE + vr.query_variant_query_id = qsq.query_id + AND object_name(pqsq.object_id, DB_ID(' + QUOTENAME(@DatabaseName, '''') + N')) = @sp_StoredProcName + ) + )) + '; + END + ELSE + BEGIN + RAISERROR(N'Setting stored proc filter', 0, 1) WITH NOWAIT; + SET @sql_where += N' AND object_name(qsq.object_id, DB_ID(' + QUOTENAME(@DatabaseName, '''') + N')) = @sp_StoredProcName + '; + END END; --I will always love you, but hopefully this query will eventually end @@ -35250,6 +35470,30 @@ EXEC sys.sp_executesql @stmt = @sql_select, @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; +/*If PSPO is enabled, get procedure names for variant queries.*/ +IF (@pspo_enabled = 1) +BEGIN + DECLARE + @pspo_names NVARCHAR(MAX) = ''; + + SET @pspo_names = + 'UPDATE wm + SET + wm.proc_or_function_name = + QUOTENAME(object_schema_name(qsq.object_id, DB_ID(' + QUOTENAME(@DatabaseName, '''') + N'))) + ''.'' + + QUOTENAME(object_name(qsq.object_id, DB_ID(' + QUOTENAME(@DatabaseName, '''') + N'))) + FROM #working_metrics wm + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_variant AS vr + ON vr.query_variant_query_id = wm.query_id + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq + ON qsq.query_id = vr.parent_query_id + AND qsq.object_id > 0 + WHERE + wm.proc_or_function_name IS NULL;' + + EXEC sys.sp_executesql @pspo_names; +END; + /*This just helps us classify our queries*/ UPDATE #working_metrics @@ -38768,7 +39012,7 @@ BEGIN SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.16', @VersionDate = '20230820'; + SELECT @Version = '8.17', @VersionDate = '20231010'; IF(@VersionCheckMode = 1) BEGIN @@ -40168,7 +40412,7 @@ SET STATISTICS XML OFF; /*Versioning details*/ -SELECT @Version = '8.16', @VersionDate = '20230820'; +SELECT @Version = '8.17', @VersionDate = '20231010'; IF(@VersionCheckMode = 1) BEGIN @@ -41465,7 +41709,7 @@ IF (@LogRecoveryOption = N'') IF (@StopAt IS NOT NULL) BEGIN - IF @Execute = 'Y' OR @Debug = 1 RAISERROR('@OnlyLogsAfter is NOT NULL, deleting from @FileList', 0, 1) WITH NOWAIT; + IF @Execute = 'Y' OR @Debug = 1 RAISERROR('@StopAt is NOT NULL, deleting from @FileList', 0, 1) WITH NOWAIT; IF LEN(@StopAt) <> 14 OR PATINDEX('%[^0-9]%', @StopAt) > 0 BEGIN @@ -41814,7 +42058,7 @@ BEGIN SET NOCOUNT ON; SET STATISTICS XML OFF; - SELECT @Version = '8.16', @VersionDate = '20230820'; + SELECT @Version = '8.17', @VersionDate = '20231010'; IF(@VersionCheckMode = 1) BEGIN @@ -42160,6 +42404,7 @@ DELETE FROM dbo.SqlServerVersions; INSERT INTO dbo.SqlServerVersions (MajorVersionNumber, MinorVersionNumber, Branch, [Url], ReleaseDate, MainstreamSupportEndDate, ExtendedSupportEndDate, MajorVersionName, MinorVersionName) VALUES + (16, 4075, 'CU8', 'https://support.microsoft.com/en-us/help/5029666', '2023-09-14', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 8'), (16, 4065, 'CU7', 'https://support.microsoft.com/en-us/help/5028743', '2023-08-10', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 7'), (16, 4055, 'CU6', 'https://support.microsoft.com/en-us/help/5027505', '2023-07-13', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 6'), (16, 4045, 'CU5', 'https://support.microsoft.com/en-us/help/5026806', '2023-06-15', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 5'), @@ -42593,7 +42838,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.16', @VersionDate = '20230820'; +SELECT @Version = '8.17', @VersionDate = '20231010'; IF(@VersionCheckMode = 1) BEGIN @@ -45910,8 +46155,8 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 'Server Info' AS FindingGroup, 'Wait Time per Core per Sec' AS Finding, 'https://www.brentozar.com/go/measure' AS URL, - CAST((CAST(waits2.waits_ms - waits1.waits_ms AS MONEY)) / 1000 / i.cpu_count / DATEDIFF(ss, waits1.SampleTime, waits2.SampleTime) AS NVARCHAR(20)) AS Details, - (waits2.waits_ms - waits1.waits_ms) / 1000 / i.cpu_count / DATEDIFF(ss, waits1.SampleTime, waits2.SampleTime) AS DetailsInt + CAST((CAST(waits2.waits_ms - waits1.waits_ms AS MONEY)) / 1000 / i.cpu_count / ISNULL(NULLIF(DATEDIFF(ss, waits1.SampleTime, waits2.SampleTime), 0), 1) AS NVARCHAR(20)) AS Details, + (waits2.waits_ms - waits1.waits_ms) / 1000 / i.cpu_count / ISNULL(NULLIF(DATEDIFF(ss, waits1.SampleTime, waits2.SampleTime), 0), 1) AS DetailsInt FROM cores i CROSS JOIN waits1 CROSS JOIN waits2; diff --git a/Install-Core-Blitz-No-Query-Store.sql b/Install-Core-Blitz-No-Query-Store.sql index e66bf69a9..59ec534a9 100644 --- a/Install-Core-Blitz-No-Query-Store.sql +++ b/Install-Core-Blitz-No-Query-Store.sql @@ -38,7 +38,7 @@ AS SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.16', @VersionDate = '20230820'; + SELECT @Version = '8.17', @VersionDate = '20231010'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -85,6 +85,7 @@ AS @OutputType ''TABLE''=table | ''COUNT''=row with number found | ''MARKDOWN''=bulleted list (including server info, excluding security findings) | ''SCHEMA''=version and field list | ''XML'' =table output as XML | ''NONE'' = none @IgnorePrioritiesBelow 50=ignore priorities below 50 @IgnorePrioritiesAbove 50=ignore priorities above 50 + @Debug 0=silent (Default) | 1=messages per step | 2=outputs dynamic queries For the rest of the parameters, see https://www.BrentOzar.com/blitz/documentation for details. MIT License @@ -198,7 +199,11 @@ AS ,@SkipMSDB bit = 0 ,@SkipModel bit = 0 ,@SkipTempDB bit = 0 - ,@SkipValidateLogins bit = 0; + ,@SkipValidateLogins bit = 0 + /* Variables for check 211: */ + ,@powerScheme varchar(36) + ,@cpu_speed_mhz int + ,@cpu_speed_ghz decimal(18,2); DECLARE @db_perms table @@ -219,11 +224,21 @@ AS fmp.permission_name FROM sys.databases AS d CROSS APPLY fn_my_permissions(d.name, 'DATABASE') AS fmp - WHERE fmp.permission_name = N'SELECT' /*Databases where we don't have read permissions*/ + WHERE fmp.permission_name = N'SELECT'; /*Databases where we don't have read permissions*/ /* End of declarations for First Responder Kit consistency check:*/ ; + /* Create temp table for check 2301 */ + IF OBJECT_ID('tempdb..#InvalidLogins') IS NOT NULL + EXEC sp_executesql N'DROP TABLE #InvalidLogins;'; + + CREATE TABLE #InvalidLogins + ( + LoginSID varbinary(85), + LoginName VARCHAR(256) + ); + /*Starting permissions checks here, but only if we're not a sysadmin*/ IF ( @@ -263,16 +278,37 @@ AS SET @SkipTrace = 1; END; /*We need this permission to execute trace stuff, apparently*/ - IF NOT EXISTS - ( - SELECT - 1/0 - FROM fn_my_permissions(N'xp_regread', N'OBJECT') AS fmp - WHERE fmp.permission_name = N'EXECUTE' - ) - BEGIN - SET @SkipXPRegRead = 1; - END; /*Need execute on xp_regread*/ + IF ISNULL(@SkipXPRegRead, 0) != 1 /*If @SkipXPRegRead hasn't been set to 1 by the caller*/ + BEGIN + BEGIN TRY + /* Get power plan if set by group policy [Git Hub Issue #1620] */ + EXEC xp_regread @rootkey = N'HKEY_LOCAL_MACHINE', + @key = N'SOFTWARE\Policies\Microsoft\Power\PowerSettings', + @value_name = N'ActivePowerScheme', + @value = @powerScheme OUTPUT, + @no_output = N'no_output'; + + IF @powersaveSetting IS NULL /* If power plan was not set by group policy, get local value [Git Hub Issue #1620]*/ + EXEC xp_regread @rootkey = N'HKEY_LOCAL_MACHINE', + @key = N'SYSTEM\CurrentControlSet\Control\Power\User\PowerSchemes', + @value_name = N'ActivePowerScheme', + @value = @powerScheme OUTPUT; + + /* Get the cpu speed*/ + EXEC xp_regread @rootkey = N'HKEY_LOCAL_MACHINE', + @key = N'HARDWARE\DESCRIPTION\System\CentralProcessor\0', + @value_name = N'~MHz', + @value = @cpu_speed_mhz OUTPUT; + + /* Convert the Megahertz to Gigahertz */ + SET @cpu_speed_ghz = CAST(CAST(@cpu_speed_mhz AS decimal) / 1000 AS decimal(18,2)); + + SET @SkipXPRegRead = 0; /*We could execute xp_regread*/ + END TRY + BEGIN CATCH + SET @SkipXPRegRead = 1; /*We have don't have execute rights or xp_regread throws an error so skip it*/ + END CATCH; + END; /*Need execute on xp_regread*/ IF NOT EXISTS ( @@ -296,17 +332,81 @@ AS SET @SkipXPCMDShell = 1; END; /*Need execute on xp_cmdshell*/ - IF NOT EXISTS - ( - SELECT - 1/0 - FROM fn_my_permissions(N'sp_validatelogins', N'OBJECT') AS fmp - WHERE fmp.permission_name = N'EXECUTE' - ) - BEGIN - SET @SkipValidateLogins = 1; - END; /*Need execute on sp_validatelogins*/ + IF ISNULL(@SkipValidateLogins, 0) != 1 /*If @SkipValidateLogins hasn't been set to 1 by the caller*/ + BEGIN + BEGIN TRY + /* Try to fill the table for check 2301 */ + INSERT INTO #InvalidLogins + ( + [LoginSID] + ,[LoginName] + ) + EXEC sp_validatelogins; + + SET @SkipValidateLogins = 0; /*We can execute sp_validatelogins*/ + END TRY + BEGIN CATCH + SET @SkipValidateLogins = 1; /*We have don't have execute rights or sp_validatelogins throws an error so skip it*/ + END CATCH; + END; /*Need execute on sp_validatelogins*/ + + IF ISNULL(@SkipModel, 0) != 1 /*If @SkipModel hasn't been set to 1 by the caller*/ + BEGIN + IF EXISTS + ( + SELECT 1/0 + FROM @db_perms + WHERE database_name = N'model' + ) + BEGIN + BEGIN TRY + IF EXISTS + ( + SELECT 1/0 + FROM model.sys.objects + ) + BEGIN + SET @SkipModel = 0; /*We have read permissions in the model database, and can view the objects*/ + END; + END TRY + BEGIN CATCH + SET @SkipModel = 1; /*We have read permissions in the model database ... oh wait we got tricked, we can't view the objects*/ + END CATCH; + END; + ELSE + BEGIN + SET @SkipModel = 1; /*We don't have read permissions in the model database*/ + END; + END; + IF ISNULL(@SkipMSDB, 0) != 1 /*If @SkipMSDB hasn't been set to 1 by the caller*/ + BEGIN + IF EXISTS + ( + SELECT 1/0 + FROM @db_perms + WHERE database_name = N'msdb' + ) + BEGIN + BEGIN TRY + IF EXISTS + ( + SELECT 1/0 + FROM msdb.sys.objects + ) + BEGIN + SET @SkipMSDB = 0; /*We have read permissions in the msdb database, and can view the objects*/ + END; + END TRY + BEGIN CATCH + SET @SkipMSDB = 1; /*We have read permissions in the msdb database ... oh wait we got tricked, we can't view the objects*/ + END CATCH; + END; + ELSE + BEGIN + SET @SkipMSDB = 1; /*We don't have read permissions in the msdb database*/ + END; + END; END; SET @crlf = NCHAR(13) + NCHAR(10); @@ -467,11 +567,26 @@ AS ); /*Skip individial checks where we don't have permissions*/ + INSERT #SkipChecks (DatabaseName, CheckID, ServerName) + SELECT + v.* + FROM (VALUES(NULL, 29, NULL)) AS v (DatabaseName, CheckID, ServerName) /*Looks for user tables in model*/ + WHERE @SkipModel = 1; + INSERT #SkipChecks (DatabaseName, CheckID, ServerName) SELECT - v.* - FROM (VALUES(NULL, 29, NULL)) AS v (DatabaseName, CheckID, ServerName) /*Looks for user tables in model*/ - WHERE NOT EXISTS (SELECT 1/0 FROM @db_perms AS dp WHERE dp.database_name = 'model'); + v.* + FROM (VALUES(NULL, 6, NULL), /*Jobs Owned By Users*/ + (NULL, 28, NULL), /*SQL Agent Job Runs at Startup*/ + (NULL, 57, NULL), /*Tables in the MSDB Database*/ + (NULL, 79, NULL), /*Shrink Database Job*/ + (NULL, 94, NULL), /*Agent Jobs Without Failure Emails*/ + (NULL, 123, NULL), /*Agent Jobs Starting Simultaneously*/ + (NULL, 180, NULL), /*Shrink Database Step In Maintenance Plan*/ + (NULL, 181, NULL), /*Repetitive Maintenance Tasks*/ + (NULL, 219, NULL) /*Alerts Without Event Descriptions*/ + ) AS v (DatabaseName, CheckID, ServerName) + WHERE @SkipMSDB = 1; INSERT #SkipChecks (DatabaseName, CheckID, ServerName) SELECT @@ -491,6 +606,12 @@ AS FROM (VALUES(NULL, 92, NULL)) AS v (DatabaseName, CheckID, ServerName) /*xp_fixeddrives*/ WHERE @SkipXPFixedDrives = 1; + INSERT #SkipChecks (DatabaseName, CheckID, ServerName) + SELECT + v.* + FROM (VALUES(NULL, 106, NULL)) AS v (DatabaseName, CheckID, ServerName) /*alter trace*/ + WHERE @SkipTrace = 1; + INSERT #SkipChecks (DatabaseName, CheckID, ServerName) SELECT v.* @@ -509,16 +630,6 @@ AS FROM (VALUES(NULL, 2301, NULL)) AS v (DatabaseName, CheckID, ServerName) /*sp_validatelogins*/ WHERE @SkipValidateLogins = 1 - IF(OBJECT_ID('tempdb..#InvalidLogins') IS NOT NULL) - BEGIN - EXEC sp_executesql N'DROP TABLE #InvalidLogins;'; - END; - - CREATE TABLE #InvalidLogins ( - LoginSID varbinary(85), - LoginName VARCHAR(256) - ); - IF @SkipChecksTable IS NOT NULL AND @SkipChecksSchema IS NOT NULL AND @SkipChecksDatabase IS NOT NULL @@ -1074,17 +1185,17 @@ AS least one of the relevant checks is not being skipped then we can extract the dbinfo information. */ - IF NOT EXISTS ( SELECT 1 - FROM #BlitzResults - WHERE CheckID = 223 AND URL = 'https://aws.amazon.com/rds/sqlserver/') - AND ( - NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 2 ) - OR NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 68 ) - ) + IF NOT EXISTS + ( + SELECT 1/0 + FROM #BlitzResults + WHERE CheckID = 223 AND URL = 'https://aws.amazon.com/rds/sqlserver/' + ) AND NOT EXISTS + ( + SELECT 1/0 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID IN (2, 68) + ) BEGIN IF @Debug IN (1, 2) RAISERROR('Extracting DBCC DBINFO data (used in checks 2 and 68).', 0, 1, 68) WITH NOWAIT; @@ -1651,9 +1762,9 @@ AS IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 2301) WITH NOWAIT; - INSERT INTO #InvalidLogins - EXEC sp_validatelogins - ; + /* + #InvalidLogins is filled at the start during the permissions check + */ INSERT INTO #BlitzResults ( CheckID , @@ -8586,12 +8697,22 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 END ELSE BEGIN - INSERT INTO #ErrorLog - EXEC sys.xp_readerrorlog 0, 1, N'Database Instant File Initialization: enabled'; + BEGIN TRY + INSERT INTO #ErrorLog + EXEC sys.xp_readerrorlog 0, 1, N'Database Instant File Initialization: enabled'; + END TRY + BEGIN CATCH + IF @Debug IN (1, 2) RAISERROR('No permissions to execute xp_readerrorlog.', 0, 1) WITH NOWAIT; + END CATCH END - IF @@ROWCOUNT > 0 - begin + IF EXISTS + ( + SELECT 1/0 + FROM #ErrorLog + WHERE LEFT([Text], 45) = N'Database Instant File Initialization: enabled' + ) + BEGIN INSERT INTO #BlitzResults ( CheckID , [Priority] , @@ -8607,7 +8728,7 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 'Instant File Initialization Enabled' AS [Finding] , 'https://www.brentozar.com/go/instant' AS [URL] , 'The service account has the Perform Volume Maintenance Tasks permission.'; - end + END; else -- if version of sql server has instant_file_initialization_enabled column in dm_server_services, check that too -- in the event the error log has been cycled and the startup messages are not in the current error log begin @@ -9048,30 +9169,6 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 211) WITH NOWAIT; - DECLARE @outval VARCHAR(36); - /* Get power plan if set by group policy [Git Hub Issue #1620] */ - EXEC master.sys.xp_regread @rootkey = 'HKEY_LOCAL_MACHINE', - @key = 'SOFTWARE\Policies\Microsoft\Power\PowerSettings', - @value_name = 'ActivePowerScheme', - @value = @outval OUTPUT, - @no_output = 'no_output'; - - IF @outval IS NULL /* If power plan was not set by group policy, get local value [Git Hub Issue #1620]*/ - EXEC master.sys.xp_regread @rootkey = 'HKEY_LOCAL_MACHINE', - @key = 'SYSTEM\CurrentControlSet\Control\Power\User\PowerSchemes', - @value_name = 'ActivePowerScheme', - @value = @outval OUTPUT; - - DECLARE @cpu_speed_mhz int, - @cpu_speed_ghz decimal(18,2); - - EXEC master.sys.xp_regread @rootkey = 'HKEY_LOCAL_MACHINE', - @key = 'HARDWARE\DESCRIPTION\System\CentralProcessor\0', - @value_name = '~MHz', - @value = @cpu_speed_mhz OUTPUT; - - SELECT @cpu_speed_ghz = CAST(CAST(@cpu_speed_mhz AS DECIMAL) / 1000 AS DECIMAL(18,2)); - INSERT INTO #BlitzResults ( CheckID , Priority , @@ -9088,7 +9185,7 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 'Your server has ' + CAST(@cpu_speed_ghz as VARCHAR(4)) + 'GHz CPUs, and is in ' - + CASE @outval + + CASE @powerScheme WHEN 'a1841308-3541-4fab-bc81-f71556f20b4a' THEN 'power saving mode -- are you sure this is a production SQL Server?' WHEN '381b4222-f694-41f0-9685-ff5bb260df2e' @@ -9919,7 +10016,7 @@ AS SET NOCOUNT ON; SET STATISTICS XML OFF; -SELECT @Version = '8.16', @VersionDate = '20230820'; +SELECT @Version = '8.17', @VersionDate = '20231010'; IF(@VersionCheckMode = 1) BEGIN @@ -10797,7 +10894,7 @@ AS SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.16', @VersionDate = '20230820'; + SELECT @Version = '8.17', @VersionDate = '20231010'; IF(@VersionCheckMode = 1) BEGIN @@ -12579,7 +12676,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.16', @VersionDate = '20230820'; +SELECT @Version = '8.17', @VersionDate = '20231010'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -12652,7 +12749,7 @@ IF @Help = 1 UNION ALL SELECT N'@SortOrder', N'VARCHAR(10)', - N'Data processing and display order. @SortOrder will still be used, even when preparing output for a table or for excel. Possible values are: "CPU", "Reads", "Writes", "Duration", "Executions", "Recent Compilations", "Memory Grant", "Unused Grant", "Spills", "Query Hash". Additionally, the word "Average" or "Avg" can be used to sort on averages rather than total. "Executions per minute" and "Executions / minute" can be used to sort by execution per minute. For the truly lazy, "xpm" can also be used. Note that when you use all or all avg, the only parameters you can use are @Top and @DatabaseName. All others will be ignored.' + N'Data processing and display order. @SortOrder will still be used, even when preparing output for a table or for excel. Possible values are: "CPU", "Reads", "Writes", "Duration", "Executions", "Recent Compilations", "Memory Grant", "Unused Grant", "Spills", "Query Hash", "Duplicate". Additionally, the word "Average" or "Avg" can be used to sort on averages rather than total. "Executions per minute" and "Executions / minute" can be used to sort by execution per minute. For the truly lazy, "xpm" can also be used. Note that when you use all or all avg, the only parameters you can use are @Top and @DatabaseName. All others will be ignored.' UNION ALL SELECT N'@UseTriggersAnyway', @@ -13074,12 +13171,30 @@ END; /* Lets get @SortOrder set to lower case here for comparisons later */ SET @SortOrder = LOWER(@SortOrder); +/* Set @Top based on sort */ +IF ( + @Top IS NULL + AND @SortOrder IN ( 'all', 'all sort' ) + ) + BEGIN + SET @Top = 5; + END; + +IF ( + @Top IS NULL + AND @SortOrder NOT IN ( 'all', 'all sort' ) + ) + BEGIN + SET @Top = 10; + END; + + /* If they want to sort by query hash, populate the @OnlyQueryHashes list for them */ IF @SortOrder LIKE 'query hash%' BEGIN RAISERROR('Beginning query hash sort', 0, 1) WITH NOWAIT; - SELECT qs.query_hash, + SELECT TOP(@Top) qs.query_hash, MAX(qs.max_worker_time) AS max_worker_time, COUNT_BIG(*) AS records INTO #query_hash_grouped @@ -13108,22 +13223,32 @@ IF @SortOrder LIKE 'query hash%' END -/* Set @Top based on sort */ -IF ( - @Top IS NULL - AND @SortOrder IN ( 'all', 'all sort' ) - ) - BEGIN - SET @Top = 5; - END; +/* If they want to sort by duplicate, create a table with the worst offenders - issue #3345 */ +IF @SortOrder LIKE 'duplicate%' + BEGIN + RAISERROR('Beginning duplicate query hash sort', 0, 1) WITH NOWAIT; + + /* Find the query hashes that are the most duplicated */ + WITH MostCommonQueries AS ( + SELECT TOP(@Top) qs.query_hash, + COUNT_BIG(*) AS plans + FROM sys.dm_exec_query_stats AS qs + GROUP BY qs.query_hash + HAVING COUNT_BIG(*) > 100 + ORDER BY COUNT_BIG(*) DESC + ) + SELECT mcq_recent.sql_handle, mcq_recent.plan_handle, mcq_recent.creation_time AS duplicate_creation_time, mcq.plans + INTO #duplicate_query_filter + FROM MostCommonQueries mcq + CROSS APPLY ( SELECT TOP 1 qs.sql_handle, qs.plan_handle, qs.creation_time + FROM sys.dm_exec_query_stats qs + WHERE qs.query_hash = mcq.query_hash + ORDER BY qs.creation_time DESC) AS mcq_recent + OPTION (RECOMPILE); + + SET @MinimumExecutionCount = 0; + END -IF ( - @Top IS NULL - AND @SortOrder NOT IN ( 'all', 'all sort' ) - ) - BEGIN - SET @Top = 10; - END; /* validate user inputs */ IF @Top IS NULL @@ -13151,213 +13276,6 @@ IF @MinutesBack IS NOT NULL END; -RAISERROR(N'Creating temp tables for results and warnings.', 0, 1) WITH NOWAIT; - - -IF OBJECT_ID('tempdb.dbo.##BlitzCacheResults') IS NULL -BEGIN - CREATE TABLE ##BlitzCacheResults ( - SPID INT, - ID INT IDENTITY(1,1), - CheckID INT, - Priority TINYINT, - FindingsGroup VARCHAR(50), - Finding VARCHAR(500), - URL VARCHAR(200), - Details VARCHAR(4000) - ); -END; - -IF OBJECT_ID('tempdb.dbo.##BlitzCacheProcs') IS NULL -BEGIN - CREATE TABLE ##BlitzCacheProcs ( - SPID INT , - QueryType NVARCHAR(258), - DatabaseName sysname, - AverageCPU DECIMAL(38,4), - AverageCPUPerMinute DECIMAL(38,4), - TotalCPU DECIMAL(38,4), - PercentCPUByType MONEY, - PercentCPU MONEY, - AverageDuration DECIMAL(38,4), - TotalDuration DECIMAL(38,4), - PercentDuration MONEY, - PercentDurationByType MONEY, - AverageReads BIGINT, - TotalReads BIGINT, - PercentReads MONEY, - PercentReadsByType MONEY, - ExecutionCount BIGINT, - PercentExecutions MONEY, - PercentExecutionsByType MONEY, - ExecutionsPerMinute MONEY, - TotalWrites BIGINT, - AverageWrites MONEY, - PercentWrites MONEY, - PercentWritesByType MONEY, - WritesPerMinute MONEY, - PlanCreationTime DATETIME, - PlanCreationTimeHours AS DATEDIFF(HOUR, PlanCreationTime, SYSDATETIME()), - LastExecutionTime DATETIME, - LastCompletionTime DATETIME, - PlanHandle VARBINARY(64), - [Remove Plan Handle From Cache] AS - CASE WHEN [PlanHandle] IS NOT NULL - THEN 'DBCC FREEPROCCACHE (' + CONVERT(VARCHAR(128), [PlanHandle], 1) + ');' - ELSE 'N/A' END, - SqlHandle VARBINARY(64), - [Remove SQL Handle From Cache] AS - CASE WHEN [SqlHandle] IS NOT NULL - THEN 'DBCC FREEPROCCACHE (' + CONVERT(VARCHAR(128), [SqlHandle], 1) + ');' - ELSE 'N/A' END, - [SQL Handle More Info] AS - CASE WHEN [SqlHandle] IS NOT NULL - THEN 'EXEC sp_BlitzCache @OnlySqlHandles = ''' + CONVERT(VARCHAR(128), [SqlHandle], 1) + '''; ' - ELSE 'N/A' END, - QueryHash BINARY(8), - [Query Hash More Info] AS - CASE WHEN [QueryHash] IS NOT NULL - THEN 'EXEC sp_BlitzCache @OnlyQueryHashes = ''' + CONVERT(VARCHAR(32), [QueryHash], 1) + '''; ' - ELSE 'N/A' END, - QueryPlanHash BINARY(8), - StatementStartOffset INT, - StatementEndOffset INT, - PlanGenerationNum BIGINT, - MinReturnedRows BIGINT, - MaxReturnedRows BIGINT, - AverageReturnedRows MONEY, - TotalReturnedRows BIGINT, - LastReturnedRows BIGINT, - MinGrantKB BIGINT, - MaxGrantKB BIGINT, - MinUsedGrantKB BIGINT, - MaxUsedGrantKB BIGINT, - PercentMemoryGrantUsed MONEY, - AvgMaxMemoryGrant MONEY, - MinSpills BIGINT, - MaxSpills BIGINT, - TotalSpills BIGINT, - AvgSpills MONEY, - QueryText NVARCHAR(MAX), - QueryPlan XML, - /* these next four columns are the total for the type of query. - don't actually use them for anything apart from math by type. - */ - TotalWorkerTimeForType BIGINT, - TotalElapsedTimeForType BIGINT, - TotalReadsForType BIGINT, - TotalExecutionCountForType BIGINT, - TotalWritesForType BIGINT, - NumberOfPlans INT, - NumberOfDistinctPlans INT, - SerialDesiredMemory FLOAT, - SerialRequiredMemory FLOAT, - CachedPlanSize FLOAT, - CompileTime FLOAT, - CompileCPU FLOAT , - CompileMemory FLOAT , - MaxCompileMemory FLOAT , - min_worker_time BIGINT, - max_worker_time BIGINT, - is_forced_plan BIT, - is_forced_parameterized BIT, - is_cursor BIT, - is_optimistic_cursor BIT, - is_forward_only_cursor BIT, - is_fast_forward_cursor BIT, - is_cursor_dynamic BIT, - is_parallel BIT, - is_forced_serial BIT, - is_key_lookup_expensive BIT, - key_lookup_cost FLOAT, - is_remote_query_expensive BIT, - remote_query_cost FLOAT, - frequent_execution BIT, - parameter_sniffing BIT, - unparameterized_query BIT, - near_parallel BIT, - plan_warnings BIT, - plan_multiple_plans INT, - long_running BIT, - downlevel_estimator BIT, - implicit_conversions BIT, - busy_loops BIT, - tvf_join BIT, - tvf_estimate BIT, - compile_timeout BIT, - compile_memory_limit_exceeded BIT, - warning_no_join_predicate BIT, - QueryPlanCost FLOAT, - missing_index_count INT, - unmatched_index_count INT, - min_elapsed_time BIGINT, - max_elapsed_time BIGINT, - age_minutes MONEY, - age_minutes_lifetime MONEY, - is_trivial BIT, - trace_flags_session VARCHAR(1000), - is_unused_grant BIT, - function_count INT, - clr_function_count INT, - is_table_variable BIT, - no_stats_warning BIT, - relop_warnings BIT, - is_table_scan BIT, - backwards_scan BIT, - forced_index BIT, - forced_seek BIT, - forced_scan BIT, - columnstore_row_mode BIT, - is_computed_scalar BIT , - is_sort_expensive BIT, - sort_cost FLOAT, - is_computed_filter BIT, - op_name VARCHAR(100) NULL, - index_insert_count INT NULL, - index_update_count INT NULL, - index_delete_count INT NULL, - cx_insert_count INT NULL, - cx_update_count INT NULL, - cx_delete_count INT NULL, - table_insert_count INT NULL, - table_update_count INT NULL, - table_delete_count INT NULL, - index_ops AS (index_insert_count + index_update_count + index_delete_count + - cx_insert_count + cx_update_count + cx_delete_count + - table_insert_count + table_update_count + table_delete_count), - is_row_level BIT, - is_spatial BIT, - index_dml BIT, - table_dml BIT, - long_running_low_cpu BIT, - low_cost_high_cpu BIT, - stale_stats BIT, - is_adaptive BIT, - index_spool_cost FLOAT, - index_spool_rows FLOAT, - table_spool_cost FLOAT, - table_spool_rows FLOAT, - is_spool_expensive BIT, - is_spool_more_rows BIT, - is_table_spool_expensive BIT, - is_table_spool_more_rows BIT, - estimated_rows FLOAT, - is_bad_estimate BIT, - is_paul_white_electric BIT, - is_row_goal BIT, - is_big_spills BIT, - is_mstvf BIT, - is_mm_join BIT, - is_nonsargable BIT, - select_with_writes BIT, - implicit_conversion_info XML, - cached_execution_parameters XML, - missing_indexes XML, - SetOptions VARCHAR(MAX), - Warnings VARCHAR(MAX), - Pattern NVARCHAR(20) - ); -END; DECLARE @DurationFilter_i INT, @MinMemoryPerQuery INT, @@ -13427,6 +13345,7 @@ SET @SortOrder = CASE WHEN @SortOrder IN ('spill') THEN 'spills' WHEN @SortOrder IN ('avg spill') THEN 'avg spills' WHEN @SortOrder IN ('execution') THEN 'executions' + WHEN @SortOrder IN ('duplicates') THEN 'duplicate' ELSE @SortOrder END RAISERROR(N'Checking sort order', 0, 1) WITH NOWAIT; @@ -13434,7 +13353,7 @@ IF @SortOrder NOT IN ('cpu', 'avg cpu', 'reads', 'avg reads', 'writes', 'avg wri 'duration', 'avg duration', 'executions', 'avg executions', 'compiles', 'memory grant', 'avg memory grant', 'unused grant', 'spills', 'avg spills', 'all', 'all avg', 'sp_BlitzIndex', - 'query hash') + 'query hash', 'duplicate') BEGIN RAISERROR(N'Invalid sort order chosen, reverting to cpu', 16, 1) WITH NOWAIT; SET @SortOrder = 'cpu'; @@ -13472,31 +13391,19 @@ IF EXISTS(SELECT * FROM sys.all_columns WHERE object_id = OBJECT_ID('sys.dm_exec ELSE SET @VersionShowsAirQuoteActualPlans = 0; -IF @Reanalyze = 1 AND OBJECT_ID('tempdb..##BlitzCacheResults') IS NULL - BEGIN - RAISERROR(N'##BlitzCacheResults does not exist, can''t reanalyze', 0, 1) WITH NOWAIT; - SET @Reanalyze = 0; - END; - -IF @Reanalyze = 0 - BEGIN - RAISERROR(N'Cleaning up old warnings for your SPID', 0, 1) WITH NOWAIT; - DELETE ##BlitzCacheResults - WHERE SPID = @@SPID - OPTION (RECOMPILE) ; - RAISERROR(N'Cleaning up old plans for your SPID', 0, 1) WITH NOWAIT; - DELETE ##BlitzCacheProcs - WHERE SPID = @@SPID - OPTION (RECOMPILE) ; - END; - IF @Reanalyze = 1 + BEGIN + IF OBJECT_ID('tempdb..##BlitzCacheResults') IS NULL + BEGIN + RAISERROR(N'##BlitzCacheResults does not exist, can''t reanalyze', 0, 1) WITH NOWAIT; + SET @Reanalyze = 0; + END + ELSE BEGIN RAISERROR(N'Reanalyzing current data, skipping to results', 0, 1) WITH NOWAIT; GOTO Results; END; - - + END; IF @SortOrder IN ('all', 'all avg') @@ -13931,18 +13838,8 @@ WITH total_plans AS ( SELECT COUNT_BIG(*) AS single_use_plan_count - FROM sys.dm_exec_cached_plans AS cp - WHERE cp.usecounts = 1 - AND cp.objtype = N'Adhoc' - AND EXISTS - ( - SELECT - 1/0 - FROM sys.configurations AS c - WHERE c.name = N'optimize for ad hoc workloads' - AND c.value_in_use = 0 - ) - HAVING COUNT_BIG(*) > 1 + FROM sys.dm_exec_query_stats AS s + WHERE s.execution_count = 1 ) INSERT #plan_usage @@ -14289,6 +14186,10 @@ FROM (SELECT TOP (@Top) x.*, xpa.*, CROSS APPLY (SELECT * FROM sys.dm_exec_plan_attributes(x.plan_handle) AS ixpa WHERE ixpa.attribute = ''dbid'') AS xpa ' + @nl ; +IF @SortOrder = 'duplicate' /* Issue #3345 */ + BEGIN + SET @body += N' INNER JOIN #duplicate_query_filter AS dqf ON x.sql_handle = dqf.sql_handle AND x.plan_handle = dqf.plan_handle AND x.creation_time = dqf.duplicate_creation_time ' + @nl ; + END IF @VersionShowsAirQuoteActualPlans = 1 BEGIN @@ -14347,7 +14248,6 @@ BEGIN END; /* end filtering for query hashes */ - IF @DurationFilter IS NOT NULL BEGIN RAISERROR(N'Setting duration filter', 0, 1) WITH NOWAIT; @@ -14383,6 +14283,7 @@ SELECT @body += N' ORDER BY ' + WHEN N'memory grant' THEN N'max_grant_kb' WHEN N'unused grant' THEN N'max_grant_kb - max_used_grant_kb' WHEN N'spills' THEN N'max_spills' + WHEN N'duplicate' THEN N'total_worker_time' /* Issue #3345 */ /* And now the averages */ WHEN N'avg cpu' THEN N'total_worker_time / execution_count' WHEN N'avg reads' THEN N'total_logical_reads / execution_count' @@ -14424,7 +14325,6 @@ IF @VersionShowsAirQuoteActualPlans = 1 SET @body_where += N' AND pa.attribute = ' + QUOTENAME('dbid', @q ) + @nl ; - IF @NoobSaibot = 1 BEGIN SET @body_where += N' AND qp.query_plan.exist(''declare namespace p="http://schemas.microsoft.com/sqlserver/2004/07/showplan";//p:StmtSimple//p:MissingIndex'') = 1' + @nl ; @@ -14733,7 +14633,7 @@ END; IF (@QueryFilter = 'all' AND (SELECT COUNT(*) FROM #only_query_hashes) = 0 AND (SELECT COUNT(*) FROM #ignore_query_hashes) = 0) - AND (@SortOrder NOT IN ('memory grant', 'avg memory grant', 'unused grant')) + AND (@SortOrder NOT IN ('memory grant', 'avg memory grant', 'unused grant', 'duplicate')) /* Issue #3345 added 'duplicate' */ OR (LEFT(@QueryFilter, 3) = 'pro') BEGIN SET @sql += @insert_list; @@ -14754,7 +14654,7 @@ IF (@v >= 13 AND @QueryFilter = 'all' AND (SELECT COUNT(*) FROM #only_query_hashes) = 0 AND (SELECT COUNT(*) FROM #ignore_query_hashes) = 0) - AND (@SortOrder NOT IN ('memory grant', 'avg memory grant', 'unused grant')) + AND (@SortOrder NOT IN ('memory grant', 'avg memory grant', 'unused grant', 'duplicate')) /* Issue #3345 added 'duplicate' */ AND (@SortOrder NOT IN ('spills', 'avg spills')) OR (LEFT(@QueryFilter, 3) = 'fun') BEGIN @@ -14797,7 +14697,7 @@ IF (@UseTriggersAnyway = 1 OR @v >= 11) AND (SELECT COUNT(*) FROM #only_query_hashes) = 0 AND (SELECT COUNT(*) FROM #ignore_query_hashes) = 0 AND (@QueryFilter = 'all') - AND (@SortOrder NOT IN ('memory grant', 'avg memory grant', 'unused grant')) + AND (@SortOrder NOT IN ('memory grant', 'avg memory grant', 'unused grant', 'duplicate')) /* Issue #3345 added 'duplicate' */ BEGIN RAISERROR (N'Adding SQL to collect trigger stats.',0,1) WITH NOWAIT; @@ -14829,6 +14729,7 @@ SELECT @sort = CASE @SortOrder WHEN N'cpu' THEN N'total_worker_time' WHEN N'memory grant' THEN N'max_grant_kb' WHEN N'unused grant' THEN N'max_grant_kb - max_used_grant_kb' WHEN N'spills' THEN N'max_spills' + WHEN N'duplicate' THEN N'total_worker_time' /* Issue #3345 */ /* And now the averages */ WHEN N'avg cpu' THEN N'total_worker_time / execution_count' WHEN N'avg reads' THEN N'total_logical_reads / execution_count' @@ -14890,6 +14791,7 @@ SELECT @sort = CASE @SortOrder WHEN N'cpu' THEN N'TotalCPU' WHEN N'memory grant' THEN N'MaxGrantKB' WHEN N'unused grant' THEN N'MaxGrantKB - MaxUsedGrantKB' WHEN N'spills' THEN N'MaxSpills' + WHEN N'duplicate' THEN N'TotalCPU' /* Issue #3345 */ /* And now the averages */ WHEN N'avg cpu' THEN N'TotalCPU / ExecutionCount' WHEN N'avg reads' THEN N'TotalReads / ExecutionCount' @@ -14908,6 +14810,7 @@ SELECT @sql = REPLACE(@sql, '#sortable#', @sort); IF @Debug = 1 BEGIN + PRINT N'Printing dynamic SQL stored in @sql: '; PRINT SUBSTRING(@sql, 0, 4000); PRINT SUBSTRING(@sql, 4000, 8000); PRINT SUBSTRING(@sql, 8000, 12000); @@ -14920,6 +14823,229 @@ IF @Debug = 1 PRINT SUBSTRING(@sql, 36000, 40000); END; +RAISERROR(N'Creating temp tables for results and warnings.', 0, 1) WITH NOWAIT; + + +IF OBJECT_ID('tempdb.dbo.##BlitzCacheResults') IS NULL +BEGIN + CREATE TABLE ##BlitzCacheResults ( + SPID INT, + ID INT IDENTITY(1,1), + CheckID INT, + Priority TINYINT, + FindingsGroup VARCHAR(50), + Finding VARCHAR(500), + URL VARCHAR(200), + Details VARCHAR(4000) + ); +END; +ELSE +BEGIN + RAISERROR(N'Cleaning up old warnings for your SPID', 0, 1) WITH NOWAIT; + DELETE ##BlitzCacheResults + WHERE SPID = @@SPID + OPTION (RECOMPILE) ; +END + + +IF OBJECT_ID('tempdb.dbo.##BlitzCacheProcs') IS NULL +BEGIN + CREATE TABLE ##BlitzCacheProcs ( + SPID INT , + QueryType NVARCHAR(258), + DatabaseName sysname, + AverageCPU DECIMAL(38,4), + AverageCPUPerMinute DECIMAL(38,4), + TotalCPU DECIMAL(38,4), + PercentCPUByType MONEY, + PercentCPU MONEY, + AverageDuration DECIMAL(38,4), + TotalDuration DECIMAL(38,4), + PercentDuration MONEY, + PercentDurationByType MONEY, + AverageReads BIGINT, + TotalReads BIGINT, + PercentReads MONEY, + PercentReadsByType MONEY, + ExecutionCount BIGINT, + PercentExecutions MONEY, + PercentExecutionsByType MONEY, + ExecutionsPerMinute MONEY, + TotalWrites BIGINT, + AverageWrites MONEY, + PercentWrites MONEY, + PercentWritesByType MONEY, + WritesPerMinute MONEY, + PlanCreationTime DATETIME, + PlanCreationTimeHours AS DATEDIFF(HOUR, PlanCreationTime, SYSDATETIME()), + LastExecutionTime DATETIME, + LastCompletionTime DATETIME, + PlanHandle VARBINARY(64), + [Remove Plan Handle From Cache] AS + CASE WHEN [PlanHandle] IS NOT NULL + THEN 'DBCC FREEPROCCACHE (' + CONVERT(VARCHAR(128), [PlanHandle], 1) + ');' + ELSE 'N/A' END, + SqlHandle VARBINARY(64), + [Remove SQL Handle From Cache] AS + CASE WHEN [SqlHandle] IS NOT NULL + THEN 'DBCC FREEPROCCACHE (' + CONVERT(VARCHAR(128), [SqlHandle], 1) + ');' + ELSE 'N/A' END, + [SQL Handle More Info] AS + CASE WHEN [SqlHandle] IS NOT NULL + THEN 'EXEC sp_BlitzCache @OnlySqlHandles = ''' + CONVERT(VARCHAR(128), [SqlHandle], 1) + '''; ' + ELSE 'N/A' END, + QueryHash BINARY(8), + [Query Hash More Info] AS + CASE WHEN [QueryHash] IS NOT NULL + THEN 'EXEC sp_BlitzCache @OnlyQueryHashes = ''' + CONVERT(VARCHAR(32), [QueryHash], 1) + '''; ' + ELSE 'N/A' END, + QueryPlanHash BINARY(8), + StatementStartOffset INT, + StatementEndOffset INT, + PlanGenerationNum BIGINT, + MinReturnedRows BIGINT, + MaxReturnedRows BIGINT, + AverageReturnedRows MONEY, + TotalReturnedRows BIGINT, + LastReturnedRows BIGINT, + MinGrantKB BIGINT, + MaxGrantKB BIGINT, + MinUsedGrantKB BIGINT, + MaxUsedGrantKB BIGINT, + PercentMemoryGrantUsed MONEY, + AvgMaxMemoryGrant MONEY, + MinSpills BIGINT, + MaxSpills BIGINT, + TotalSpills BIGINT, + AvgSpills MONEY, + QueryText NVARCHAR(MAX), + QueryPlan XML, + /* these next four columns are the total for the type of query. + don't actually use them for anything apart from math by type. + */ + TotalWorkerTimeForType BIGINT, + TotalElapsedTimeForType BIGINT, + TotalReadsForType BIGINT, + TotalExecutionCountForType BIGINT, + TotalWritesForType BIGINT, + NumberOfPlans INT, + NumberOfDistinctPlans INT, + SerialDesiredMemory FLOAT, + SerialRequiredMemory FLOAT, + CachedPlanSize FLOAT, + CompileTime FLOAT, + CompileCPU FLOAT , + CompileMemory FLOAT , + MaxCompileMemory FLOAT , + min_worker_time BIGINT, + max_worker_time BIGINT, + is_forced_plan BIT, + is_forced_parameterized BIT, + is_cursor BIT, + is_optimistic_cursor BIT, + is_forward_only_cursor BIT, + is_fast_forward_cursor BIT, + is_cursor_dynamic BIT, + is_parallel BIT, + is_forced_serial BIT, + is_key_lookup_expensive BIT, + key_lookup_cost FLOAT, + is_remote_query_expensive BIT, + remote_query_cost FLOAT, + frequent_execution BIT, + parameter_sniffing BIT, + unparameterized_query BIT, + near_parallel BIT, + plan_warnings BIT, + plan_multiple_plans INT, + long_running BIT, + downlevel_estimator BIT, + implicit_conversions BIT, + busy_loops BIT, + tvf_join BIT, + tvf_estimate BIT, + compile_timeout BIT, + compile_memory_limit_exceeded BIT, + warning_no_join_predicate BIT, + QueryPlanCost FLOAT, + missing_index_count INT, + unmatched_index_count INT, + min_elapsed_time BIGINT, + max_elapsed_time BIGINT, + age_minutes MONEY, + age_minutes_lifetime MONEY, + is_trivial BIT, + trace_flags_session VARCHAR(1000), + is_unused_grant BIT, + function_count INT, + clr_function_count INT, + is_table_variable BIT, + no_stats_warning BIT, + relop_warnings BIT, + is_table_scan BIT, + backwards_scan BIT, + forced_index BIT, + forced_seek BIT, + forced_scan BIT, + columnstore_row_mode BIT, + is_computed_scalar BIT , + is_sort_expensive BIT, + sort_cost FLOAT, + is_computed_filter BIT, + op_name VARCHAR(100) NULL, + index_insert_count INT NULL, + index_update_count INT NULL, + index_delete_count INT NULL, + cx_insert_count INT NULL, + cx_update_count INT NULL, + cx_delete_count INT NULL, + table_insert_count INT NULL, + table_update_count INT NULL, + table_delete_count INT NULL, + index_ops AS (index_insert_count + index_update_count + index_delete_count + + cx_insert_count + cx_update_count + cx_delete_count + + table_insert_count + table_update_count + table_delete_count), + is_row_level BIT, + is_spatial BIT, + index_dml BIT, + table_dml BIT, + long_running_low_cpu BIT, + low_cost_high_cpu BIT, + stale_stats BIT, + is_adaptive BIT, + index_spool_cost FLOAT, + index_spool_rows FLOAT, + table_spool_cost FLOAT, + table_spool_rows FLOAT, + is_spool_expensive BIT, + is_spool_more_rows BIT, + is_table_spool_expensive BIT, + is_table_spool_more_rows BIT, + estimated_rows FLOAT, + is_bad_estimate BIT, + is_paul_white_electric BIT, + is_row_goal BIT, + is_big_spills BIT, + is_mstvf BIT, + is_mm_join BIT, + is_nonsargable BIT, + select_with_writes BIT, + implicit_conversion_info XML, + cached_execution_parameters XML, + missing_indexes XML, + SetOptions VARCHAR(MAX), + Warnings VARCHAR(MAX), + Pattern NVARCHAR(20) + ); +END; +ELSE +BEGIN + RAISERROR(N'Cleaning up old plans for your SPID', 0, 1) WITH NOWAIT; + DELETE ##BlitzCacheProcs + WHERE SPID = @@SPID + OPTION (RECOMPILE) ; +END + IF @Reanalyze = 0 BEGIN RAISERROR('Collecting execution plan information.', 0, 1) WITH NOWAIT; @@ -17238,7 +17364,7 @@ BEGIN IF @MinimumExecutionCount IS NOT NULL BEGIN - SET @sql += N' AND ExecutionCount >= @minimumExecutionCount '; + SET @sql += N' AND ExecutionCount >= @MinimumExecutionCount '; END; IF @MinutesBack IS NOT NULL @@ -17255,6 +17381,7 @@ BEGIN WHEN N'memory grant' THEN N' MaxGrantKB' WHEN N'unused grant' THEN N' MaxGrantKB - MaxUsedGrantKB' WHEN N'spills' THEN N' MaxSpills' + WHEN N'duplicate' THEN N' plan_multiple_plans ' /* Issue #3345 */ WHEN N'avg cpu' THEN N' AverageCPU' WHEN N'avg reads' THEN N' AverageReads' WHEN N'avg writes' THEN N' AverageWrites' @@ -17266,8 +17393,14 @@ BEGIN SET @sql += N' OPTION (RECOMPILE) ; '; + IF @sql IS NULL + BEGIN + RAISERROR('@sql is null, which means dynamic SQL generation went terribly wrong', 0, 1) WITH NOWAIT; + END + IF @Debug = 1 BEGIN + RAISERROR('Printing @sql, the dynamic SQL we generated:', 0, 1) WITH NOWAIT; PRINT SUBSTRING(@sql, 0, 4000); PRINT SUBSTRING(@sql, 4000, 8000); PRINT SUBSTRING(@sql, 8000, 12000); @@ -17280,7 +17413,7 @@ BEGIN PRINT SUBSTRING(@sql, 36000, 40000); END; - EXEC sp_executesql @sql, N'@Top INT, @min_duration INT, @min_back INT, @minimumExecutionCount INT', @Top, @DurationFilter_i, @MinutesBack, @MinimumExecutionCount; + EXEC sp_executesql @sql, N'@Top INT, @min_duration INT, @min_back INT, @MinimumExecutionCount INT', @Top, @DurationFilter_i, @MinutesBack, @MinimumExecutionCount; END; @@ -17479,8 +17612,6 @@ BEGIN [Remove SQL Handle From Cache]'; END; - - SET @sql = N' SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; SELECT TOP (@Top) ' + @columns + @nl + N' @@ -17489,7 +17620,7 @@ WHERE SPID = @spid ' + @nl; IF @MinimumExecutionCount IS NOT NULL BEGIN - SET @sql += N' AND ExecutionCount >= @minimumExecutionCount ' + @nl; + SET @sql += N' AND ExecutionCount >= @MinimumExecutionCount ' + @nl; END; IF @MinutesBack IS NOT NULL @@ -17505,7 +17636,8 @@ SELECT @sql += N' ORDER BY ' + CASE @SortOrder WHEN N'cpu' THEN N' TotalCPU ' WHEN N'compiles' THEN N' PlanCreationTime ' WHEN N'memory grant' THEN N' MaxGrantKB' WHEN N'unused grant' THEN N' MaxGrantKB - MaxUsedGrantKB ' - WHEN N'spills' THEN N' MaxSpills' + WHEN N'duplicate' THEN N' plan_multiple_plans ' + WHEN N'spills' THEN N' MaxSpills ' WHEN N'avg cpu' THEN N' AverageCPU' WHEN N'avg reads' THEN N' AverageReads' WHEN N'avg writes' THEN N' AverageWrites' @@ -17531,7 +17663,7 @@ IF @Debug = 1 END; IF(@OutputType <> 'NONE') BEGIN - EXEC sp_executesql @sql, N'@Top INT, @spid INT, @minimumExecutionCount INT, @min_back INT', @Top, @@SPID, @MinimumExecutionCount, @MinutesBack; + EXEC sp_executesql @sql, N'@Top INT, @spid INT, @MinimumExecutionCount INT, @min_back INT', @Top, @@SPID, @MinimumExecutionCount, @MinutesBack; END; /* @@ -19206,7 +19338,7 @@ SET @AllSortSql += N' EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''avg spills'', @IgnoreSqlHandles = @ISH, @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; - UPDATE #bou_allsort SET Pattern = ''avg memory grant'' WHERE Pattern IS NULL OPTION(RECOMPILE);'; + UPDATE #bou_allsort SET Pattern = ''avg spills'' WHERE Pattern IS NULL OPTION(RECOMPILE);'; IF @ExportToExcel = 1 BEGIN SET @AllSortSql += N' UPDATE #bou_allsort @@ -19253,6 +19385,11 @@ END; EXEC sys.sp_executesql @stmt = @AllSortSql, @params = N'@i_DatabaseName NVARCHAR(128), @i_Top INT, @i_SkipAnalysis BIT, @i_OutputDatabaseName NVARCHAR(258), @i_OutputSchemaName NVARCHAR(258), @i_OutputTableName NVARCHAR(258), @i_CheckDateOverride DATETIMEOFFSET, @i_MinutesBack INT ', @i_DatabaseName = @DatabaseName, @i_Top = @Top, @i_SkipAnalysis = @SkipAnalysis, @i_OutputDatabaseName = @OutputDatabaseName, @i_OutputSchemaName = @OutputSchemaName, @i_OutputTableName = @OutputTableName, @i_CheckDateOverride = @CheckDateOverride, @i_MinutesBack = @MinutesBack; +/* Avoid going into OutputResultsToTable + ... otherwise the last result (e.g. spills) would be recorded twice into the output table. +*/ +RETURN; + /*End of AllSort section*/ @@ -19898,7 +20035,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.16', @VersionDate = '20230820'; +SELECT @Version = '8.17', @VersionDate = '20231010'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -26083,7 +26220,7 @@ BEGIN SET NOCOUNT, XACT_ABORT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.16', @VersionDate = '20230820'; + SELECT @Version = '8.17', @VersionDate = '20231010'; IF @VersionCheckMode = 1 BEGIN @@ -26362,7 +26499,7 @@ BEGIN RETURN; END; END; - + IF @Azure = 1 BEGIN IF NOT EXISTS @@ -27892,7 +28029,7 @@ BEGIN COUNT_BIG(DISTINCT dp.event_date) ) + N' deadlocks.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT dp.event_date) DESC) FROM #deadlock_process AS dp @@ -27926,7 +28063,18 @@ BEGIN check_id = 2, dow.database_name, object_name = - N'You Might Need RCSI', + CASE + WHEN EXISTS + ( + SELECT + 1/0 + FROM sys.databases AS d + WHERE d.name = dow.database_name + AND d.is_read_committed_snapshot_on = 1 + ) + THEN N'You already enabled RCSI, but...' + ELSE N'You Might Need RCSI' + END, finding_group = N'Total Deadlocks Involving Selects', finding = N'There have been ' + @@ -27936,7 +28084,7 @@ BEGIN COUNT_BIG(DISTINCT dow.event_date) ) + N' deadlock(s) between read queries and modification queries.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT dow.event_date) DESC) FROM #deadlock_owner_waiter AS dow @@ -27998,7 +28146,7 @@ BEGIN COUNT_BIG(DISTINCT dow.event_date) ) + N' deadlock(s).', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT dow.event_date) DESC) FROM #deadlock_owner_waiter AS dow @@ -28041,7 +28189,7 @@ BEGIN COUNT_BIG(DISTINCT dow.event_date) ) + N' deadlock(s).', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT dow.event_date) DESC) FROM #deadlock_owner_waiter AS dow @@ -28090,7 +28238,7 @@ BEGIN COUNT_BIG(DISTINCT dow.event_date) ) + N' deadlock(s).', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT dow.event_date) DESC) FROM #deadlock_owner_waiter AS dow @@ -28139,7 +28287,7 @@ BEGIN COUNT_BIG(DISTINCT dp.event_date) ) + N' instances of Serializable deadlocks.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT dp.event_date) DESC) FROM #deadlock_process AS dp @@ -28182,7 +28330,7 @@ BEGIN COUNT_BIG(DISTINCT dp.event_date) ) + N' instances of Repeatable Read deadlocks.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT dp.event_date) DESC) FROM #deadlock_process AS dp @@ -28244,7 +28392,7 @@ BEGIN N'UNKNOWN' ) + N'.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT dp.event_date) DESC) FROM #deadlock_process AS dp @@ -28356,7 +28504,7 @@ BEGIN 1, N'' ) + N' locks.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY CONVERT(bigint, lt.lock_count) DESC) FROM lock_types AS lt @@ -28535,7 +28683,7 @@ BEGIN COUNT_BIG(DISTINCT ds.id) ) + N' deadlocks.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT ds.id) DESC) FROM #deadlock_stack AS ds @@ -28636,19 +28784,19 @@ BEGIN ) ), wait_time_hms = - /*the more wait time you rack up the less accurate this gets, + /*the more wait time you rack up the less accurate this gets, it's either that or erroring out*/ - CASE - WHEN + CASE + WHEN SUM ( CONVERT ( - bigint, + bigint, dp.wait_time ) )/1000 > 2147483647 - THEN + THEN CONVERT ( nvarchar(30), @@ -28661,7 +28809,7 @@ BEGIN ( CONVERT ( - bigint, + bigint, dp.wait_time ) ) @@ -28672,16 +28820,16 @@ BEGIN ), 14 ) - WHEN + WHEN SUM ( CONVERT ( - bigint, + bigint, dp.wait_time ) ) BETWEEN 2147483648 AND 2147483647000 - THEN + THEN CONVERT ( nvarchar(30), @@ -28694,7 +28842,7 @@ BEGIN ( CONVERT ( - bigint, + bigint, dp.wait_time ) ) @@ -28776,7 +28924,7 @@ BEGIN 14 ) + N' [dd hh:mm:ss:ms] of deadlock wait time.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY cs.total_waits DESC) FROM chopsuey AS cs @@ -28850,19 +28998,19 @@ BEGIN ) ) + N' ' + - /*the more wait time you rack up the less accurate this gets, + /*the more wait time you rack up the less accurate this gets, it's either that or erroring out*/ - CASE - WHEN + CASE + WHEN SUM ( CONVERT ( - bigint, + bigint, wt.total_wait_time_ms ) )/1000 > 2147483647 - THEN + THEN CONVERT ( nvarchar(30), @@ -28875,7 +29023,7 @@ BEGIN ( CONVERT ( - bigint, + bigint, wt.total_wait_time_ms ) ) @@ -28886,16 +29034,16 @@ BEGIN ), 14 ) - WHEN + WHEN SUM ( CONVERT ( - bigint, + bigint, wt.total_wait_time_ms ) ) BETWEEN 2147483648 AND 2147483647000 - THEN + THEN CONVERT ( nvarchar(30), @@ -28908,7 +29056,7 @@ BEGIN ( CONVERT ( - bigint, + bigint, wt.total_wait_time_ms ) ) @@ -28941,7 +29089,7 @@ BEGIN 14 ) END + N' [dd hh:mm:ss:ms] of deadlock wait time.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY SUM(CONVERT(bigint, wt.total_wait_time_ms)) DESC) FROM wait_time AS wt @@ -28979,7 +29127,7 @@ BEGIN N'There have been ' + RTRIM(COUNT_BIG(DISTINCT aj.event_date)) + N' deadlocks from this Agent Job and Step.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT aj.event_date) DESC) FROM #agent_job AS aj @@ -29025,7 +29173,7 @@ BEGIN /*Check 15 is total deadlocks involving sleeping sessions*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Check 15 sleeping deadlocks %s', 0, 1, @d) WITH NOWAIT; + RAISERROR('Check 15 sleeping and background deadlocks %s', 0, 1, @d) WITH NOWAIT; INSERT #deadlock_findings WITH(TABLOCKX) @@ -29054,6 +29202,33 @@ BEGIN HAVING COUNT_BIG(DISTINCT dp.event_date) > 0 OPTION(RECOMPILE); + INSERT + #deadlock_findings WITH(TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding + ) + SELECT + check_id = 15, + database_name = N'-', + object_name = N'-', + finding_group = N'Total deadlocks involving background processes', + finding = + N'There have been ' + + CONVERT + ( + nvarchar(20), + COUNT_BIG(DISTINCT dp.event_date) + ) + + N' deadlocks with background task.' + FROM #deadlock_process AS dp + WHERE dp.status = N'background' + HAVING COUNT_BIG(DISTINCT dp.event_date) > 0 + OPTION(RECOMPILE); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; /*Check 16 is total deadlocks involving implicit transactions*/ @@ -29732,18 +29907,18 @@ BEGIN BEGIN SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Results to client %s', 0, 1, @d) WITH NOWAIT; - + IF @Debug = 1 BEGIN SET STATISTICS XML ON; END; - + EXEC sys.sp_executesql @deadlock_result; - + IF @Debug = 1 BEGIN SET STATISTICS XML OFF; PRINT @deadlock_result; END; - + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; SET @d = CONVERT(varchar(40), GETDATE(), 109); @@ -29921,10 +30096,10 @@ BEGIN OPTION(RECOMPILE, LOOP JOIN, HASH JOIN); RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - + SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Returning findings %s', 0, 1, @d) WITH NOWAIT; - + SELECT df.check_id, df.database_name, @@ -29936,7 +30111,7 @@ BEGIN df.check_id, df.sort_order OPTION(RECOMPILE); - + SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; END; /*done with output to client app.*/ @@ -30020,7 +30195,7 @@ BEGIN table_name = N'#dm_exec_query_stats', * FROM #dm_exec_query_stats - OPTION(RECOMPILE); + OPTION(RECOMPILE); SELECT procedure_parameters = @@ -30151,7 +30326,7 @@ BEGIN SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.16', @VersionDate = '20230820'; + SELECT @Version = '8.17', @VersionDate = '20231010'; IF(@VersionCheckMode = 1) BEGIN @@ -31547,6 +31722,7 @@ DELETE FROM dbo.SqlServerVersions; INSERT INTO dbo.SqlServerVersions (MajorVersionNumber, MinorVersionNumber, Branch, [Url], ReleaseDate, MainstreamSupportEndDate, ExtendedSupportEndDate, MajorVersionName, MinorVersionName) VALUES + (16, 4075, 'CU8', 'https://support.microsoft.com/en-us/help/5029666', '2023-09-14', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 8'), (16, 4065, 'CU7', 'https://support.microsoft.com/en-us/help/5028743', '2023-08-10', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 7'), (16, 4055, 'CU6', 'https://support.microsoft.com/en-us/help/5027505', '2023-07-13', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 6'), (16, 4045, 'CU5', 'https://support.microsoft.com/en-us/help/5026806', '2023-06-15', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 5'), @@ -31980,7 +32156,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.16', @VersionDate = '20230820'; +SELECT @Version = '8.17', @VersionDate = '20231010'; IF(@VersionCheckMode = 1) BEGIN @@ -35297,8 +35473,8 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 'Server Info' AS FindingGroup, 'Wait Time per Core per Sec' AS Finding, 'https://www.brentozar.com/go/measure' AS URL, - CAST((CAST(waits2.waits_ms - waits1.waits_ms AS MONEY)) / 1000 / i.cpu_count / DATEDIFF(ss, waits1.SampleTime, waits2.SampleTime) AS NVARCHAR(20)) AS Details, - (waits2.waits_ms - waits1.waits_ms) / 1000 / i.cpu_count / DATEDIFF(ss, waits1.SampleTime, waits2.SampleTime) AS DetailsInt + CAST((CAST(waits2.waits_ms - waits1.waits_ms AS MONEY)) / 1000 / i.cpu_count / ISNULL(NULLIF(DATEDIFF(ss, waits1.SampleTime, waits2.SampleTime), 0), 1) AS NVARCHAR(20)) AS Details, + (waits2.waits_ms - waits1.waits_ms) / 1000 / i.cpu_count / ISNULL(NULLIF(DATEDIFF(ss, waits1.SampleTime, waits2.SampleTime), 0), 1) AS DetailsInt FROM cores i CROSS JOIN waits1 CROSS JOIN waits2; diff --git a/Install-Core-Blitz-With-Query-Store.sql b/Install-Core-Blitz-With-Query-Store.sql index bc39b6f6a..e7b9fd0bc 100644 --- a/Install-Core-Blitz-With-Query-Store.sql +++ b/Install-Core-Blitz-With-Query-Store.sql @@ -38,7 +38,7 @@ AS SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.16', @VersionDate = '20230820'; + SELECT @Version = '8.17', @VersionDate = '20231010'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -85,6 +85,7 @@ AS @OutputType ''TABLE''=table | ''COUNT''=row with number found | ''MARKDOWN''=bulleted list (including server info, excluding security findings) | ''SCHEMA''=version and field list | ''XML'' =table output as XML | ''NONE'' = none @IgnorePrioritiesBelow 50=ignore priorities below 50 @IgnorePrioritiesAbove 50=ignore priorities above 50 + @Debug 0=silent (Default) | 1=messages per step | 2=outputs dynamic queries For the rest of the parameters, see https://www.BrentOzar.com/blitz/documentation for details. MIT License @@ -198,7 +199,11 @@ AS ,@SkipMSDB bit = 0 ,@SkipModel bit = 0 ,@SkipTempDB bit = 0 - ,@SkipValidateLogins bit = 0; + ,@SkipValidateLogins bit = 0 + /* Variables for check 211: */ + ,@powerScheme varchar(36) + ,@cpu_speed_mhz int + ,@cpu_speed_ghz decimal(18,2); DECLARE @db_perms table @@ -219,11 +224,21 @@ AS fmp.permission_name FROM sys.databases AS d CROSS APPLY fn_my_permissions(d.name, 'DATABASE') AS fmp - WHERE fmp.permission_name = N'SELECT' /*Databases where we don't have read permissions*/ + WHERE fmp.permission_name = N'SELECT'; /*Databases where we don't have read permissions*/ /* End of declarations for First Responder Kit consistency check:*/ ; + /* Create temp table for check 2301 */ + IF OBJECT_ID('tempdb..#InvalidLogins') IS NOT NULL + EXEC sp_executesql N'DROP TABLE #InvalidLogins;'; + + CREATE TABLE #InvalidLogins + ( + LoginSID varbinary(85), + LoginName VARCHAR(256) + ); + /*Starting permissions checks here, but only if we're not a sysadmin*/ IF ( @@ -263,16 +278,37 @@ AS SET @SkipTrace = 1; END; /*We need this permission to execute trace stuff, apparently*/ - IF NOT EXISTS - ( - SELECT - 1/0 - FROM fn_my_permissions(N'xp_regread', N'OBJECT') AS fmp - WHERE fmp.permission_name = N'EXECUTE' - ) - BEGIN - SET @SkipXPRegRead = 1; - END; /*Need execute on xp_regread*/ + IF ISNULL(@SkipXPRegRead, 0) != 1 /*If @SkipXPRegRead hasn't been set to 1 by the caller*/ + BEGIN + BEGIN TRY + /* Get power plan if set by group policy [Git Hub Issue #1620] */ + EXEC xp_regread @rootkey = N'HKEY_LOCAL_MACHINE', + @key = N'SOFTWARE\Policies\Microsoft\Power\PowerSettings', + @value_name = N'ActivePowerScheme', + @value = @powerScheme OUTPUT, + @no_output = N'no_output'; + + IF @powersaveSetting IS NULL /* If power plan was not set by group policy, get local value [Git Hub Issue #1620]*/ + EXEC xp_regread @rootkey = N'HKEY_LOCAL_MACHINE', + @key = N'SYSTEM\CurrentControlSet\Control\Power\User\PowerSchemes', + @value_name = N'ActivePowerScheme', + @value = @powerScheme OUTPUT; + + /* Get the cpu speed*/ + EXEC xp_regread @rootkey = N'HKEY_LOCAL_MACHINE', + @key = N'HARDWARE\DESCRIPTION\System\CentralProcessor\0', + @value_name = N'~MHz', + @value = @cpu_speed_mhz OUTPUT; + + /* Convert the Megahertz to Gigahertz */ + SET @cpu_speed_ghz = CAST(CAST(@cpu_speed_mhz AS decimal) / 1000 AS decimal(18,2)); + + SET @SkipXPRegRead = 0; /*We could execute xp_regread*/ + END TRY + BEGIN CATCH + SET @SkipXPRegRead = 1; /*We have don't have execute rights or xp_regread throws an error so skip it*/ + END CATCH; + END; /*Need execute on xp_regread*/ IF NOT EXISTS ( @@ -296,17 +332,81 @@ AS SET @SkipXPCMDShell = 1; END; /*Need execute on xp_cmdshell*/ - IF NOT EXISTS - ( - SELECT - 1/0 - FROM fn_my_permissions(N'sp_validatelogins', N'OBJECT') AS fmp - WHERE fmp.permission_name = N'EXECUTE' - ) - BEGIN - SET @SkipValidateLogins = 1; - END; /*Need execute on sp_validatelogins*/ + IF ISNULL(@SkipValidateLogins, 0) != 1 /*If @SkipValidateLogins hasn't been set to 1 by the caller*/ + BEGIN + BEGIN TRY + /* Try to fill the table for check 2301 */ + INSERT INTO #InvalidLogins + ( + [LoginSID] + ,[LoginName] + ) + EXEC sp_validatelogins; + + SET @SkipValidateLogins = 0; /*We can execute sp_validatelogins*/ + END TRY + BEGIN CATCH + SET @SkipValidateLogins = 1; /*We have don't have execute rights or sp_validatelogins throws an error so skip it*/ + END CATCH; + END; /*Need execute on sp_validatelogins*/ + + IF ISNULL(@SkipModel, 0) != 1 /*If @SkipModel hasn't been set to 1 by the caller*/ + BEGIN + IF EXISTS + ( + SELECT 1/0 + FROM @db_perms + WHERE database_name = N'model' + ) + BEGIN + BEGIN TRY + IF EXISTS + ( + SELECT 1/0 + FROM model.sys.objects + ) + BEGIN + SET @SkipModel = 0; /*We have read permissions in the model database, and can view the objects*/ + END; + END TRY + BEGIN CATCH + SET @SkipModel = 1; /*We have read permissions in the model database ... oh wait we got tricked, we can't view the objects*/ + END CATCH; + END; + ELSE + BEGIN + SET @SkipModel = 1; /*We don't have read permissions in the model database*/ + END; + END; + IF ISNULL(@SkipMSDB, 0) != 1 /*If @SkipMSDB hasn't been set to 1 by the caller*/ + BEGIN + IF EXISTS + ( + SELECT 1/0 + FROM @db_perms + WHERE database_name = N'msdb' + ) + BEGIN + BEGIN TRY + IF EXISTS + ( + SELECT 1/0 + FROM msdb.sys.objects + ) + BEGIN + SET @SkipMSDB = 0; /*We have read permissions in the msdb database, and can view the objects*/ + END; + END TRY + BEGIN CATCH + SET @SkipMSDB = 1; /*We have read permissions in the msdb database ... oh wait we got tricked, we can't view the objects*/ + END CATCH; + END; + ELSE + BEGIN + SET @SkipMSDB = 1; /*We don't have read permissions in the msdb database*/ + END; + END; END; SET @crlf = NCHAR(13) + NCHAR(10); @@ -467,11 +567,26 @@ AS ); /*Skip individial checks where we don't have permissions*/ + INSERT #SkipChecks (DatabaseName, CheckID, ServerName) + SELECT + v.* + FROM (VALUES(NULL, 29, NULL)) AS v (DatabaseName, CheckID, ServerName) /*Looks for user tables in model*/ + WHERE @SkipModel = 1; + INSERT #SkipChecks (DatabaseName, CheckID, ServerName) SELECT - v.* - FROM (VALUES(NULL, 29, NULL)) AS v (DatabaseName, CheckID, ServerName) /*Looks for user tables in model*/ - WHERE NOT EXISTS (SELECT 1/0 FROM @db_perms AS dp WHERE dp.database_name = 'model'); + v.* + FROM (VALUES(NULL, 6, NULL), /*Jobs Owned By Users*/ + (NULL, 28, NULL), /*SQL Agent Job Runs at Startup*/ + (NULL, 57, NULL), /*Tables in the MSDB Database*/ + (NULL, 79, NULL), /*Shrink Database Job*/ + (NULL, 94, NULL), /*Agent Jobs Without Failure Emails*/ + (NULL, 123, NULL), /*Agent Jobs Starting Simultaneously*/ + (NULL, 180, NULL), /*Shrink Database Step In Maintenance Plan*/ + (NULL, 181, NULL), /*Repetitive Maintenance Tasks*/ + (NULL, 219, NULL) /*Alerts Without Event Descriptions*/ + ) AS v (DatabaseName, CheckID, ServerName) + WHERE @SkipMSDB = 1; INSERT #SkipChecks (DatabaseName, CheckID, ServerName) SELECT @@ -491,6 +606,12 @@ AS FROM (VALUES(NULL, 92, NULL)) AS v (DatabaseName, CheckID, ServerName) /*xp_fixeddrives*/ WHERE @SkipXPFixedDrives = 1; + INSERT #SkipChecks (DatabaseName, CheckID, ServerName) + SELECT + v.* + FROM (VALUES(NULL, 106, NULL)) AS v (DatabaseName, CheckID, ServerName) /*alter trace*/ + WHERE @SkipTrace = 1; + INSERT #SkipChecks (DatabaseName, CheckID, ServerName) SELECT v.* @@ -509,16 +630,6 @@ AS FROM (VALUES(NULL, 2301, NULL)) AS v (DatabaseName, CheckID, ServerName) /*sp_validatelogins*/ WHERE @SkipValidateLogins = 1 - IF(OBJECT_ID('tempdb..#InvalidLogins') IS NOT NULL) - BEGIN - EXEC sp_executesql N'DROP TABLE #InvalidLogins;'; - END; - - CREATE TABLE #InvalidLogins ( - LoginSID varbinary(85), - LoginName VARCHAR(256) - ); - IF @SkipChecksTable IS NOT NULL AND @SkipChecksSchema IS NOT NULL AND @SkipChecksDatabase IS NOT NULL @@ -1074,17 +1185,17 @@ AS least one of the relevant checks is not being skipped then we can extract the dbinfo information. */ - IF NOT EXISTS ( SELECT 1 - FROM #BlitzResults - WHERE CheckID = 223 AND URL = 'https://aws.amazon.com/rds/sqlserver/') - AND ( - NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 2 ) - OR NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 68 ) - ) + IF NOT EXISTS + ( + SELECT 1/0 + FROM #BlitzResults + WHERE CheckID = 223 AND URL = 'https://aws.amazon.com/rds/sqlserver/' + ) AND NOT EXISTS + ( + SELECT 1/0 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID IN (2, 68) + ) BEGIN IF @Debug IN (1, 2) RAISERROR('Extracting DBCC DBINFO data (used in checks 2 and 68).', 0, 1, 68) WITH NOWAIT; @@ -1651,9 +1762,9 @@ AS IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 2301) WITH NOWAIT; - INSERT INTO #InvalidLogins - EXEC sp_validatelogins - ; + /* + #InvalidLogins is filled at the start during the permissions check + */ INSERT INTO #BlitzResults ( CheckID , @@ -8586,12 +8697,22 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 END ELSE BEGIN - INSERT INTO #ErrorLog - EXEC sys.xp_readerrorlog 0, 1, N'Database Instant File Initialization: enabled'; + BEGIN TRY + INSERT INTO #ErrorLog + EXEC sys.xp_readerrorlog 0, 1, N'Database Instant File Initialization: enabled'; + END TRY + BEGIN CATCH + IF @Debug IN (1, 2) RAISERROR('No permissions to execute xp_readerrorlog.', 0, 1) WITH NOWAIT; + END CATCH END - IF @@ROWCOUNT > 0 - begin + IF EXISTS + ( + SELECT 1/0 + FROM #ErrorLog + WHERE LEFT([Text], 45) = N'Database Instant File Initialization: enabled' + ) + BEGIN INSERT INTO #BlitzResults ( CheckID , [Priority] , @@ -8607,7 +8728,7 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 'Instant File Initialization Enabled' AS [Finding] , 'https://www.brentozar.com/go/instant' AS [URL] , 'The service account has the Perform Volume Maintenance Tasks permission.'; - end + END; else -- if version of sql server has instant_file_initialization_enabled column in dm_server_services, check that too -- in the event the error log has been cycled and the startup messages are not in the current error log begin @@ -9048,30 +9169,6 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 211) WITH NOWAIT; - DECLARE @outval VARCHAR(36); - /* Get power plan if set by group policy [Git Hub Issue #1620] */ - EXEC master.sys.xp_regread @rootkey = 'HKEY_LOCAL_MACHINE', - @key = 'SOFTWARE\Policies\Microsoft\Power\PowerSettings', - @value_name = 'ActivePowerScheme', - @value = @outval OUTPUT, - @no_output = 'no_output'; - - IF @outval IS NULL /* If power plan was not set by group policy, get local value [Git Hub Issue #1620]*/ - EXEC master.sys.xp_regread @rootkey = 'HKEY_LOCAL_MACHINE', - @key = 'SYSTEM\CurrentControlSet\Control\Power\User\PowerSchemes', - @value_name = 'ActivePowerScheme', - @value = @outval OUTPUT; - - DECLARE @cpu_speed_mhz int, - @cpu_speed_ghz decimal(18,2); - - EXEC master.sys.xp_regread @rootkey = 'HKEY_LOCAL_MACHINE', - @key = 'HARDWARE\DESCRIPTION\System\CentralProcessor\0', - @value_name = '~MHz', - @value = @cpu_speed_mhz OUTPUT; - - SELECT @cpu_speed_ghz = CAST(CAST(@cpu_speed_mhz AS DECIMAL) / 1000 AS DECIMAL(18,2)); - INSERT INTO #BlitzResults ( CheckID , Priority , @@ -9088,7 +9185,7 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 'Your server has ' + CAST(@cpu_speed_ghz as VARCHAR(4)) + 'GHz CPUs, and is in ' - + CASE @outval + + CASE @powerScheme WHEN 'a1841308-3541-4fab-bc81-f71556f20b4a' THEN 'power saving mode -- are you sure this is a production SQL Server?' WHEN '381b4222-f694-41f0-9685-ff5bb260df2e' @@ -9919,7 +10016,7 @@ AS SET NOCOUNT ON; SET STATISTICS XML OFF; -SELECT @Version = '8.16', @VersionDate = '20230820'; +SELECT @Version = '8.17', @VersionDate = '20231010'; IF(@VersionCheckMode = 1) BEGIN @@ -10797,7 +10894,7 @@ AS SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.16', @VersionDate = '20230820'; + SELECT @Version = '8.17', @VersionDate = '20231010'; IF(@VersionCheckMode = 1) BEGIN @@ -12579,7 +12676,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.16', @VersionDate = '20230820'; +SELECT @Version = '8.17', @VersionDate = '20231010'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -12652,7 +12749,7 @@ IF @Help = 1 UNION ALL SELECT N'@SortOrder', N'VARCHAR(10)', - N'Data processing and display order. @SortOrder will still be used, even when preparing output for a table or for excel. Possible values are: "CPU", "Reads", "Writes", "Duration", "Executions", "Recent Compilations", "Memory Grant", "Unused Grant", "Spills", "Query Hash". Additionally, the word "Average" or "Avg" can be used to sort on averages rather than total. "Executions per minute" and "Executions / minute" can be used to sort by execution per minute. For the truly lazy, "xpm" can also be used. Note that when you use all or all avg, the only parameters you can use are @Top and @DatabaseName. All others will be ignored.' + N'Data processing and display order. @SortOrder will still be used, even when preparing output for a table or for excel. Possible values are: "CPU", "Reads", "Writes", "Duration", "Executions", "Recent Compilations", "Memory Grant", "Unused Grant", "Spills", "Query Hash", "Duplicate". Additionally, the word "Average" or "Avg" can be used to sort on averages rather than total. "Executions per minute" and "Executions / minute" can be used to sort by execution per minute. For the truly lazy, "xpm" can also be used. Note that when you use all or all avg, the only parameters you can use are @Top and @DatabaseName. All others will be ignored.' UNION ALL SELECT N'@UseTriggersAnyway', @@ -13074,12 +13171,30 @@ END; /* Lets get @SortOrder set to lower case here for comparisons later */ SET @SortOrder = LOWER(@SortOrder); +/* Set @Top based on sort */ +IF ( + @Top IS NULL + AND @SortOrder IN ( 'all', 'all sort' ) + ) + BEGIN + SET @Top = 5; + END; + +IF ( + @Top IS NULL + AND @SortOrder NOT IN ( 'all', 'all sort' ) + ) + BEGIN + SET @Top = 10; + END; + + /* If they want to sort by query hash, populate the @OnlyQueryHashes list for them */ IF @SortOrder LIKE 'query hash%' BEGIN RAISERROR('Beginning query hash sort', 0, 1) WITH NOWAIT; - SELECT qs.query_hash, + SELECT TOP(@Top) qs.query_hash, MAX(qs.max_worker_time) AS max_worker_time, COUNT_BIG(*) AS records INTO #query_hash_grouped @@ -13108,22 +13223,32 @@ IF @SortOrder LIKE 'query hash%' END -/* Set @Top based on sort */ -IF ( - @Top IS NULL - AND @SortOrder IN ( 'all', 'all sort' ) - ) - BEGIN - SET @Top = 5; - END; +/* If they want to sort by duplicate, create a table with the worst offenders - issue #3345 */ +IF @SortOrder LIKE 'duplicate%' + BEGIN + RAISERROR('Beginning duplicate query hash sort', 0, 1) WITH NOWAIT; + + /* Find the query hashes that are the most duplicated */ + WITH MostCommonQueries AS ( + SELECT TOP(@Top) qs.query_hash, + COUNT_BIG(*) AS plans + FROM sys.dm_exec_query_stats AS qs + GROUP BY qs.query_hash + HAVING COUNT_BIG(*) > 100 + ORDER BY COUNT_BIG(*) DESC + ) + SELECT mcq_recent.sql_handle, mcq_recent.plan_handle, mcq_recent.creation_time AS duplicate_creation_time, mcq.plans + INTO #duplicate_query_filter + FROM MostCommonQueries mcq + CROSS APPLY ( SELECT TOP 1 qs.sql_handle, qs.plan_handle, qs.creation_time + FROM sys.dm_exec_query_stats qs + WHERE qs.query_hash = mcq.query_hash + ORDER BY qs.creation_time DESC) AS mcq_recent + OPTION (RECOMPILE); + + SET @MinimumExecutionCount = 0; + END -IF ( - @Top IS NULL - AND @SortOrder NOT IN ( 'all', 'all sort' ) - ) - BEGIN - SET @Top = 10; - END; /* validate user inputs */ IF @Top IS NULL @@ -13151,213 +13276,6 @@ IF @MinutesBack IS NOT NULL END; -RAISERROR(N'Creating temp tables for results and warnings.', 0, 1) WITH NOWAIT; - - -IF OBJECT_ID('tempdb.dbo.##BlitzCacheResults') IS NULL -BEGIN - CREATE TABLE ##BlitzCacheResults ( - SPID INT, - ID INT IDENTITY(1,1), - CheckID INT, - Priority TINYINT, - FindingsGroup VARCHAR(50), - Finding VARCHAR(500), - URL VARCHAR(200), - Details VARCHAR(4000) - ); -END; - -IF OBJECT_ID('tempdb.dbo.##BlitzCacheProcs') IS NULL -BEGIN - CREATE TABLE ##BlitzCacheProcs ( - SPID INT , - QueryType NVARCHAR(258), - DatabaseName sysname, - AverageCPU DECIMAL(38,4), - AverageCPUPerMinute DECIMAL(38,4), - TotalCPU DECIMAL(38,4), - PercentCPUByType MONEY, - PercentCPU MONEY, - AverageDuration DECIMAL(38,4), - TotalDuration DECIMAL(38,4), - PercentDuration MONEY, - PercentDurationByType MONEY, - AverageReads BIGINT, - TotalReads BIGINT, - PercentReads MONEY, - PercentReadsByType MONEY, - ExecutionCount BIGINT, - PercentExecutions MONEY, - PercentExecutionsByType MONEY, - ExecutionsPerMinute MONEY, - TotalWrites BIGINT, - AverageWrites MONEY, - PercentWrites MONEY, - PercentWritesByType MONEY, - WritesPerMinute MONEY, - PlanCreationTime DATETIME, - PlanCreationTimeHours AS DATEDIFF(HOUR, PlanCreationTime, SYSDATETIME()), - LastExecutionTime DATETIME, - LastCompletionTime DATETIME, - PlanHandle VARBINARY(64), - [Remove Plan Handle From Cache] AS - CASE WHEN [PlanHandle] IS NOT NULL - THEN 'DBCC FREEPROCCACHE (' + CONVERT(VARCHAR(128), [PlanHandle], 1) + ');' - ELSE 'N/A' END, - SqlHandle VARBINARY(64), - [Remove SQL Handle From Cache] AS - CASE WHEN [SqlHandle] IS NOT NULL - THEN 'DBCC FREEPROCCACHE (' + CONVERT(VARCHAR(128), [SqlHandle], 1) + ');' - ELSE 'N/A' END, - [SQL Handle More Info] AS - CASE WHEN [SqlHandle] IS NOT NULL - THEN 'EXEC sp_BlitzCache @OnlySqlHandles = ''' + CONVERT(VARCHAR(128), [SqlHandle], 1) + '''; ' - ELSE 'N/A' END, - QueryHash BINARY(8), - [Query Hash More Info] AS - CASE WHEN [QueryHash] IS NOT NULL - THEN 'EXEC sp_BlitzCache @OnlyQueryHashes = ''' + CONVERT(VARCHAR(32), [QueryHash], 1) + '''; ' - ELSE 'N/A' END, - QueryPlanHash BINARY(8), - StatementStartOffset INT, - StatementEndOffset INT, - PlanGenerationNum BIGINT, - MinReturnedRows BIGINT, - MaxReturnedRows BIGINT, - AverageReturnedRows MONEY, - TotalReturnedRows BIGINT, - LastReturnedRows BIGINT, - MinGrantKB BIGINT, - MaxGrantKB BIGINT, - MinUsedGrantKB BIGINT, - MaxUsedGrantKB BIGINT, - PercentMemoryGrantUsed MONEY, - AvgMaxMemoryGrant MONEY, - MinSpills BIGINT, - MaxSpills BIGINT, - TotalSpills BIGINT, - AvgSpills MONEY, - QueryText NVARCHAR(MAX), - QueryPlan XML, - /* these next four columns are the total for the type of query. - don't actually use them for anything apart from math by type. - */ - TotalWorkerTimeForType BIGINT, - TotalElapsedTimeForType BIGINT, - TotalReadsForType BIGINT, - TotalExecutionCountForType BIGINT, - TotalWritesForType BIGINT, - NumberOfPlans INT, - NumberOfDistinctPlans INT, - SerialDesiredMemory FLOAT, - SerialRequiredMemory FLOAT, - CachedPlanSize FLOAT, - CompileTime FLOAT, - CompileCPU FLOAT , - CompileMemory FLOAT , - MaxCompileMemory FLOAT , - min_worker_time BIGINT, - max_worker_time BIGINT, - is_forced_plan BIT, - is_forced_parameterized BIT, - is_cursor BIT, - is_optimistic_cursor BIT, - is_forward_only_cursor BIT, - is_fast_forward_cursor BIT, - is_cursor_dynamic BIT, - is_parallel BIT, - is_forced_serial BIT, - is_key_lookup_expensive BIT, - key_lookup_cost FLOAT, - is_remote_query_expensive BIT, - remote_query_cost FLOAT, - frequent_execution BIT, - parameter_sniffing BIT, - unparameterized_query BIT, - near_parallel BIT, - plan_warnings BIT, - plan_multiple_plans INT, - long_running BIT, - downlevel_estimator BIT, - implicit_conversions BIT, - busy_loops BIT, - tvf_join BIT, - tvf_estimate BIT, - compile_timeout BIT, - compile_memory_limit_exceeded BIT, - warning_no_join_predicate BIT, - QueryPlanCost FLOAT, - missing_index_count INT, - unmatched_index_count INT, - min_elapsed_time BIGINT, - max_elapsed_time BIGINT, - age_minutes MONEY, - age_minutes_lifetime MONEY, - is_trivial BIT, - trace_flags_session VARCHAR(1000), - is_unused_grant BIT, - function_count INT, - clr_function_count INT, - is_table_variable BIT, - no_stats_warning BIT, - relop_warnings BIT, - is_table_scan BIT, - backwards_scan BIT, - forced_index BIT, - forced_seek BIT, - forced_scan BIT, - columnstore_row_mode BIT, - is_computed_scalar BIT , - is_sort_expensive BIT, - sort_cost FLOAT, - is_computed_filter BIT, - op_name VARCHAR(100) NULL, - index_insert_count INT NULL, - index_update_count INT NULL, - index_delete_count INT NULL, - cx_insert_count INT NULL, - cx_update_count INT NULL, - cx_delete_count INT NULL, - table_insert_count INT NULL, - table_update_count INT NULL, - table_delete_count INT NULL, - index_ops AS (index_insert_count + index_update_count + index_delete_count + - cx_insert_count + cx_update_count + cx_delete_count + - table_insert_count + table_update_count + table_delete_count), - is_row_level BIT, - is_spatial BIT, - index_dml BIT, - table_dml BIT, - long_running_low_cpu BIT, - low_cost_high_cpu BIT, - stale_stats BIT, - is_adaptive BIT, - index_spool_cost FLOAT, - index_spool_rows FLOAT, - table_spool_cost FLOAT, - table_spool_rows FLOAT, - is_spool_expensive BIT, - is_spool_more_rows BIT, - is_table_spool_expensive BIT, - is_table_spool_more_rows BIT, - estimated_rows FLOAT, - is_bad_estimate BIT, - is_paul_white_electric BIT, - is_row_goal BIT, - is_big_spills BIT, - is_mstvf BIT, - is_mm_join BIT, - is_nonsargable BIT, - select_with_writes BIT, - implicit_conversion_info XML, - cached_execution_parameters XML, - missing_indexes XML, - SetOptions VARCHAR(MAX), - Warnings VARCHAR(MAX), - Pattern NVARCHAR(20) - ); -END; DECLARE @DurationFilter_i INT, @MinMemoryPerQuery INT, @@ -13427,6 +13345,7 @@ SET @SortOrder = CASE WHEN @SortOrder IN ('spill') THEN 'spills' WHEN @SortOrder IN ('avg spill') THEN 'avg spills' WHEN @SortOrder IN ('execution') THEN 'executions' + WHEN @SortOrder IN ('duplicates') THEN 'duplicate' ELSE @SortOrder END RAISERROR(N'Checking sort order', 0, 1) WITH NOWAIT; @@ -13434,7 +13353,7 @@ IF @SortOrder NOT IN ('cpu', 'avg cpu', 'reads', 'avg reads', 'writes', 'avg wri 'duration', 'avg duration', 'executions', 'avg executions', 'compiles', 'memory grant', 'avg memory grant', 'unused grant', 'spills', 'avg spills', 'all', 'all avg', 'sp_BlitzIndex', - 'query hash') + 'query hash', 'duplicate') BEGIN RAISERROR(N'Invalid sort order chosen, reverting to cpu', 16, 1) WITH NOWAIT; SET @SortOrder = 'cpu'; @@ -13472,31 +13391,19 @@ IF EXISTS(SELECT * FROM sys.all_columns WHERE object_id = OBJECT_ID('sys.dm_exec ELSE SET @VersionShowsAirQuoteActualPlans = 0; -IF @Reanalyze = 1 AND OBJECT_ID('tempdb..##BlitzCacheResults') IS NULL - BEGIN - RAISERROR(N'##BlitzCacheResults does not exist, can''t reanalyze', 0, 1) WITH NOWAIT; - SET @Reanalyze = 0; - END; - -IF @Reanalyze = 0 - BEGIN - RAISERROR(N'Cleaning up old warnings for your SPID', 0, 1) WITH NOWAIT; - DELETE ##BlitzCacheResults - WHERE SPID = @@SPID - OPTION (RECOMPILE) ; - RAISERROR(N'Cleaning up old plans for your SPID', 0, 1) WITH NOWAIT; - DELETE ##BlitzCacheProcs - WHERE SPID = @@SPID - OPTION (RECOMPILE) ; - END; - IF @Reanalyze = 1 + BEGIN + IF OBJECT_ID('tempdb..##BlitzCacheResults') IS NULL + BEGIN + RAISERROR(N'##BlitzCacheResults does not exist, can''t reanalyze', 0, 1) WITH NOWAIT; + SET @Reanalyze = 0; + END + ELSE BEGIN RAISERROR(N'Reanalyzing current data, skipping to results', 0, 1) WITH NOWAIT; GOTO Results; END; - - + END; IF @SortOrder IN ('all', 'all avg') @@ -13931,18 +13838,8 @@ WITH total_plans AS ( SELECT COUNT_BIG(*) AS single_use_plan_count - FROM sys.dm_exec_cached_plans AS cp - WHERE cp.usecounts = 1 - AND cp.objtype = N'Adhoc' - AND EXISTS - ( - SELECT - 1/0 - FROM sys.configurations AS c - WHERE c.name = N'optimize for ad hoc workloads' - AND c.value_in_use = 0 - ) - HAVING COUNT_BIG(*) > 1 + FROM sys.dm_exec_query_stats AS s + WHERE s.execution_count = 1 ) INSERT #plan_usage @@ -14289,6 +14186,10 @@ FROM (SELECT TOP (@Top) x.*, xpa.*, CROSS APPLY (SELECT * FROM sys.dm_exec_plan_attributes(x.plan_handle) AS ixpa WHERE ixpa.attribute = ''dbid'') AS xpa ' + @nl ; +IF @SortOrder = 'duplicate' /* Issue #3345 */ + BEGIN + SET @body += N' INNER JOIN #duplicate_query_filter AS dqf ON x.sql_handle = dqf.sql_handle AND x.plan_handle = dqf.plan_handle AND x.creation_time = dqf.duplicate_creation_time ' + @nl ; + END IF @VersionShowsAirQuoteActualPlans = 1 BEGIN @@ -14347,7 +14248,6 @@ BEGIN END; /* end filtering for query hashes */ - IF @DurationFilter IS NOT NULL BEGIN RAISERROR(N'Setting duration filter', 0, 1) WITH NOWAIT; @@ -14383,6 +14283,7 @@ SELECT @body += N' ORDER BY ' + WHEN N'memory grant' THEN N'max_grant_kb' WHEN N'unused grant' THEN N'max_grant_kb - max_used_grant_kb' WHEN N'spills' THEN N'max_spills' + WHEN N'duplicate' THEN N'total_worker_time' /* Issue #3345 */ /* And now the averages */ WHEN N'avg cpu' THEN N'total_worker_time / execution_count' WHEN N'avg reads' THEN N'total_logical_reads / execution_count' @@ -14424,7 +14325,6 @@ IF @VersionShowsAirQuoteActualPlans = 1 SET @body_where += N' AND pa.attribute = ' + QUOTENAME('dbid', @q ) + @nl ; - IF @NoobSaibot = 1 BEGIN SET @body_where += N' AND qp.query_plan.exist(''declare namespace p="http://schemas.microsoft.com/sqlserver/2004/07/showplan";//p:StmtSimple//p:MissingIndex'') = 1' + @nl ; @@ -14733,7 +14633,7 @@ END; IF (@QueryFilter = 'all' AND (SELECT COUNT(*) FROM #only_query_hashes) = 0 AND (SELECT COUNT(*) FROM #ignore_query_hashes) = 0) - AND (@SortOrder NOT IN ('memory grant', 'avg memory grant', 'unused grant')) + AND (@SortOrder NOT IN ('memory grant', 'avg memory grant', 'unused grant', 'duplicate')) /* Issue #3345 added 'duplicate' */ OR (LEFT(@QueryFilter, 3) = 'pro') BEGIN SET @sql += @insert_list; @@ -14754,7 +14654,7 @@ IF (@v >= 13 AND @QueryFilter = 'all' AND (SELECT COUNT(*) FROM #only_query_hashes) = 0 AND (SELECT COUNT(*) FROM #ignore_query_hashes) = 0) - AND (@SortOrder NOT IN ('memory grant', 'avg memory grant', 'unused grant')) + AND (@SortOrder NOT IN ('memory grant', 'avg memory grant', 'unused grant', 'duplicate')) /* Issue #3345 added 'duplicate' */ AND (@SortOrder NOT IN ('spills', 'avg spills')) OR (LEFT(@QueryFilter, 3) = 'fun') BEGIN @@ -14797,7 +14697,7 @@ IF (@UseTriggersAnyway = 1 OR @v >= 11) AND (SELECT COUNT(*) FROM #only_query_hashes) = 0 AND (SELECT COUNT(*) FROM #ignore_query_hashes) = 0 AND (@QueryFilter = 'all') - AND (@SortOrder NOT IN ('memory grant', 'avg memory grant', 'unused grant')) + AND (@SortOrder NOT IN ('memory grant', 'avg memory grant', 'unused grant', 'duplicate')) /* Issue #3345 added 'duplicate' */ BEGIN RAISERROR (N'Adding SQL to collect trigger stats.',0,1) WITH NOWAIT; @@ -14829,6 +14729,7 @@ SELECT @sort = CASE @SortOrder WHEN N'cpu' THEN N'total_worker_time' WHEN N'memory grant' THEN N'max_grant_kb' WHEN N'unused grant' THEN N'max_grant_kb - max_used_grant_kb' WHEN N'spills' THEN N'max_spills' + WHEN N'duplicate' THEN N'total_worker_time' /* Issue #3345 */ /* And now the averages */ WHEN N'avg cpu' THEN N'total_worker_time / execution_count' WHEN N'avg reads' THEN N'total_logical_reads / execution_count' @@ -14890,6 +14791,7 @@ SELECT @sort = CASE @SortOrder WHEN N'cpu' THEN N'TotalCPU' WHEN N'memory grant' THEN N'MaxGrantKB' WHEN N'unused grant' THEN N'MaxGrantKB - MaxUsedGrantKB' WHEN N'spills' THEN N'MaxSpills' + WHEN N'duplicate' THEN N'TotalCPU' /* Issue #3345 */ /* And now the averages */ WHEN N'avg cpu' THEN N'TotalCPU / ExecutionCount' WHEN N'avg reads' THEN N'TotalReads / ExecutionCount' @@ -14908,6 +14810,7 @@ SELECT @sql = REPLACE(@sql, '#sortable#', @sort); IF @Debug = 1 BEGIN + PRINT N'Printing dynamic SQL stored in @sql: '; PRINT SUBSTRING(@sql, 0, 4000); PRINT SUBSTRING(@sql, 4000, 8000); PRINT SUBSTRING(@sql, 8000, 12000); @@ -14920,6 +14823,229 @@ IF @Debug = 1 PRINT SUBSTRING(@sql, 36000, 40000); END; +RAISERROR(N'Creating temp tables for results and warnings.', 0, 1) WITH NOWAIT; + + +IF OBJECT_ID('tempdb.dbo.##BlitzCacheResults') IS NULL +BEGIN + CREATE TABLE ##BlitzCacheResults ( + SPID INT, + ID INT IDENTITY(1,1), + CheckID INT, + Priority TINYINT, + FindingsGroup VARCHAR(50), + Finding VARCHAR(500), + URL VARCHAR(200), + Details VARCHAR(4000) + ); +END; +ELSE +BEGIN + RAISERROR(N'Cleaning up old warnings for your SPID', 0, 1) WITH NOWAIT; + DELETE ##BlitzCacheResults + WHERE SPID = @@SPID + OPTION (RECOMPILE) ; +END + + +IF OBJECT_ID('tempdb.dbo.##BlitzCacheProcs') IS NULL +BEGIN + CREATE TABLE ##BlitzCacheProcs ( + SPID INT , + QueryType NVARCHAR(258), + DatabaseName sysname, + AverageCPU DECIMAL(38,4), + AverageCPUPerMinute DECIMAL(38,4), + TotalCPU DECIMAL(38,4), + PercentCPUByType MONEY, + PercentCPU MONEY, + AverageDuration DECIMAL(38,4), + TotalDuration DECIMAL(38,4), + PercentDuration MONEY, + PercentDurationByType MONEY, + AverageReads BIGINT, + TotalReads BIGINT, + PercentReads MONEY, + PercentReadsByType MONEY, + ExecutionCount BIGINT, + PercentExecutions MONEY, + PercentExecutionsByType MONEY, + ExecutionsPerMinute MONEY, + TotalWrites BIGINT, + AverageWrites MONEY, + PercentWrites MONEY, + PercentWritesByType MONEY, + WritesPerMinute MONEY, + PlanCreationTime DATETIME, + PlanCreationTimeHours AS DATEDIFF(HOUR, PlanCreationTime, SYSDATETIME()), + LastExecutionTime DATETIME, + LastCompletionTime DATETIME, + PlanHandle VARBINARY(64), + [Remove Plan Handle From Cache] AS + CASE WHEN [PlanHandle] IS NOT NULL + THEN 'DBCC FREEPROCCACHE (' + CONVERT(VARCHAR(128), [PlanHandle], 1) + ');' + ELSE 'N/A' END, + SqlHandle VARBINARY(64), + [Remove SQL Handle From Cache] AS + CASE WHEN [SqlHandle] IS NOT NULL + THEN 'DBCC FREEPROCCACHE (' + CONVERT(VARCHAR(128), [SqlHandle], 1) + ');' + ELSE 'N/A' END, + [SQL Handle More Info] AS + CASE WHEN [SqlHandle] IS NOT NULL + THEN 'EXEC sp_BlitzCache @OnlySqlHandles = ''' + CONVERT(VARCHAR(128), [SqlHandle], 1) + '''; ' + ELSE 'N/A' END, + QueryHash BINARY(8), + [Query Hash More Info] AS + CASE WHEN [QueryHash] IS NOT NULL + THEN 'EXEC sp_BlitzCache @OnlyQueryHashes = ''' + CONVERT(VARCHAR(32), [QueryHash], 1) + '''; ' + ELSE 'N/A' END, + QueryPlanHash BINARY(8), + StatementStartOffset INT, + StatementEndOffset INT, + PlanGenerationNum BIGINT, + MinReturnedRows BIGINT, + MaxReturnedRows BIGINT, + AverageReturnedRows MONEY, + TotalReturnedRows BIGINT, + LastReturnedRows BIGINT, + MinGrantKB BIGINT, + MaxGrantKB BIGINT, + MinUsedGrantKB BIGINT, + MaxUsedGrantKB BIGINT, + PercentMemoryGrantUsed MONEY, + AvgMaxMemoryGrant MONEY, + MinSpills BIGINT, + MaxSpills BIGINT, + TotalSpills BIGINT, + AvgSpills MONEY, + QueryText NVARCHAR(MAX), + QueryPlan XML, + /* these next four columns are the total for the type of query. + don't actually use them for anything apart from math by type. + */ + TotalWorkerTimeForType BIGINT, + TotalElapsedTimeForType BIGINT, + TotalReadsForType BIGINT, + TotalExecutionCountForType BIGINT, + TotalWritesForType BIGINT, + NumberOfPlans INT, + NumberOfDistinctPlans INT, + SerialDesiredMemory FLOAT, + SerialRequiredMemory FLOAT, + CachedPlanSize FLOAT, + CompileTime FLOAT, + CompileCPU FLOAT , + CompileMemory FLOAT , + MaxCompileMemory FLOAT , + min_worker_time BIGINT, + max_worker_time BIGINT, + is_forced_plan BIT, + is_forced_parameterized BIT, + is_cursor BIT, + is_optimistic_cursor BIT, + is_forward_only_cursor BIT, + is_fast_forward_cursor BIT, + is_cursor_dynamic BIT, + is_parallel BIT, + is_forced_serial BIT, + is_key_lookup_expensive BIT, + key_lookup_cost FLOAT, + is_remote_query_expensive BIT, + remote_query_cost FLOAT, + frequent_execution BIT, + parameter_sniffing BIT, + unparameterized_query BIT, + near_parallel BIT, + plan_warnings BIT, + plan_multiple_plans INT, + long_running BIT, + downlevel_estimator BIT, + implicit_conversions BIT, + busy_loops BIT, + tvf_join BIT, + tvf_estimate BIT, + compile_timeout BIT, + compile_memory_limit_exceeded BIT, + warning_no_join_predicate BIT, + QueryPlanCost FLOAT, + missing_index_count INT, + unmatched_index_count INT, + min_elapsed_time BIGINT, + max_elapsed_time BIGINT, + age_minutes MONEY, + age_minutes_lifetime MONEY, + is_trivial BIT, + trace_flags_session VARCHAR(1000), + is_unused_grant BIT, + function_count INT, + clr_function_count INT, + is_table_variable BIT, + no_stats_warning BIT, + relop_warnings BIT, + is_table_scan BIT, + backwards_scan BIT, + forced_index BIT, + forced_seek BIT, + forced_scan BIT, + columnstore_row_mode BIT, + is_computed_scalar BIT , + is_sort_expensive BIT, + sort_cost FLOAT, + is_computed_filter BIT, + op_name VARCHAR(100) NULL, + index_insert_count INT NULL, + index_update_count INT NULL, + index_delete_count INT NULL, + cx_insert_count INT NULL, + cx_update_count INT NULL, + cx_delete_count INT NULL, + table_insert_count INT NULL, + table_update_count INT NULL, + table_delete_count INT NULL, + index_ops AS (index_insert_count + index_update_count + index_delete_count + + cx_insert_count + cx_update_count + cx_delete_count + + table_insert_count + table_update_count + table_delete_count), + is_row_level BIT, + is_spatial BIT, + index_dml BIT, + table_dml BIT, + long_running_low_cpu BIT, + low_cost_high_cpu BIT, + stale_stats BIT, + is_adaptive BIT, + index_spool_cost FLOAT, + index_spool_rows FLOAT, + table_spool_cost FLOAT, + table_spool_rows FLOAT, + is_spool_expensive BIT, + is_spool_more_rows BIT, + is_table_spool_expensive BIT, + is_table_spool_more_rows BIT, + estimated_rows FLOAT, + is_bad_estimate BIT, + is_paul_white_electric BIT, + is_row_goal BIT, + is_big_spills BIT, + is_mstvf BIT, + is_mm_join BIT, + is_nonsargable BIT, + select_with_writes BIT, + implicit_conversion_info XML, + cached_execution_parameters XML, + missing_indexes XML, + SetOptions VARCHAR(MAX), + Warnings VARCHAR(MAX), + Pattern NVARCHAR(20) + ); +END; +ELSE +BEGIN + RAISERROR(N'Cleaning up old plans for your SPID', 0, 1) WITH NOWAIT; + DELETE ##BlitzCacheProcs + WHERE SPID = @@SPID + OPTION (RECOMPILE) ; +END + IF @Reanalyze = 0 BEGIN RAISERROR('Collecting execution plan information.', 0, 1) WITH NOWAIT; @@ -17238,7 +17364,7 @@ BEGIN IF @MinimumExecutionCount IS NOT NULL BEGIN - SET @sql += N' AND ExecutionCount >= @minimumExecutionCount '; + SET @sql += N' AND ExecutionCount >= @MinimumExecutionCount '; END; IF @MinutesBack IS NOT NULL @@ -17255,6 +17381,7 @@ BEGIN WHEN N'memory grant' THEN N' MaxGrantKB' WHEN N'unused grant' THEN N' MaxGrantKB - MaxUsedGrantKB' WHEN N'spills' THEN N' MaxSpills' + WHEN N'duplicate' THEN N' plan_multiple_plans ' /* Issue #3345 */ WHEN N'avg cpu' THEN N' AverageCPU' WHEN N'avg reads' THEN N' AverageReads' WHEN N'avg writes' THEN N' AverageWrites' @@ -17266,8 +17393,14 @@ BEGIN SET @sql += N' OPTION (RECOMPILE) ; '; + IF @sql IS NULL + BEGIN + RAISERROR('@sql is null, which means dynamic SQL generation went terribly wrong', 0, 1) WITH NOWAIT; + END + IF @Debug = 1 BEGIN + RAISERROR('Printing @sql, the dynamic SQL we generated:', 0, 1) WITH NOWAIT; PRINT SUBSTRING(@sql, 0, 4000); PRINT SUBSTRING(@sql, 4000, 8000); PRINT SUBSTRING(@sql, 8000, 12000); @@ -17280,7 +17413,7 @@ BEGIN PRINT SUBSTRING(@sql, 36000, 40000); END; - EXEC sp_executesql @sql, N'@Top INT, @min_duration INT, @min_back INT, @minimumExecutionCount INT', @Top, @DurationFilter_i, @MinutesBack, @MinimumExecutionCount; + EXEC sp_executesql @sql, N'@Top INT, @min_duration INT, @min_back INT, @MinimumExecutionCount INT', @Top, @DurationFilter_i, @MinutesBack, @MinimumExecutionCount; END; @@ -17479,8 +17612,6 @@ BEGIN [Remove SQL Handle From Cache]'; END; - - SET @sql = N' SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; SELECT TOP (@Top) ' + @columns + @nl + N' @@ -17489,7 +17620,7 @@ WHERE SPID = @spid ' + @nl; IF @MinimumExecutionCount IS NOT NULL BEGIN - SET @sql += N' AND ExecutionCount >= @minimumExecutionCount ' + @nl; + SET @sql += N' AND ExecutionCount >= @MinimumExecutionCount ' + @nl; END; IF @MinutesBack IS NOT NULL @@ -17505,7 +17636,8 @@ SELECT @sql += N' ORDER BY ' + CASE @SortOrder WHEN N'cpu' THEN N' TotalCPU ' WHEN N'compiles' THEN N' PlanCreationTime ' WHEN N'memory grant' THEN N' MaxGrantKB' WHEN N'unused grant' THEN N' MaxGrantKB - MaxUsedGrantKB ' - WHEN N'spills' THEN N' MaxSpills' + WHEN N'duplicate' THEN N' plan_multiple_plans ' + WHEN N'spills' THEN N' MaxSpills ' WHEN N'avg cpu' THEN N' AverageCPU' WHEN N'avg reads' THEN N' AverageReads' WHEN N'avg writes' THEN N' AverageWrites' @@ -17531,7 +17663,7 @@ IF @Debug = 1 END; IF(@OutputType <> 'NONE') BEGIN - EXEC sp_executesql @sql, N'@Top INT, @spid INT, @minimumExecutionCount INT, @min_back INT', @Top, @@SPID, @MinimumExecutionCount, @MinutesBack; + EXEC sp_executesql @sql, N'@Top INT, @spid INT, @MinimumExecutionCount INT, @min_back INT', @Top, @@SPID, @MinimumExecutionCount, @MinutesBack; END; /* @@ -19206,7 +19338,7 @@ SET @AllSortSql += N' EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''avg spills'', @IgnoreSqlHandles = @ISH, @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; - UPDATE #bou_allsort SET Pattern = ''avg memory grant'' WHERE Pattern IS NULL OPTION(RECOMPILE);'; + UPDATE #bou_allsort SET Pattern = ''avg spills'' WHERE Pattern IS NULL OPTION(RECOMPILE);'; IF @ExportToExcel = 1 BEGIN SET @AllSortSql += N' UPDATE #bou_allsort @@ -19253,6 +19385,11 @@ END; EXEC sys.sp_executesql @stmt = @AllSortSql, @params = N'@i_DatabaseName NVARCHAR(128), @i_Top INT, @i_SkipAnalysis BIT, @i_OutputDatabaseName NVARCHAR(258), @i_OutputSchemaName NVARCHAR(258), @i_OutputTableName NVARCHAR(258), @i_CheckDateOverride DATETIMEOFFSET, @i_MinutesBack INT ', @i_DatabaseName = @DatabaseName, @i_Top = @Top, @i_SkipAnalysis = @SkipAnalysis, @i_OutputDatabaseName = @OutputDatabaseName, @i_OutputSchemaName = @OutputSchemaName, @i_OutputTableName = @OutputTableName, @i_CheckDateOverride = @CheckDateOverride, @i_MinutesBack = @MinutesBack; +/* Avoid going into OutputResultsToTable + ... otherwise the last result (e.g. spills) would be recorded twice into the output table. +*/ +RETURN; + /*End of AllSort section*/ @@ -19898,7 +20035,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.16', @VersionDate = '20230820'; +SELECT @Version = '8.17', @VersionDate = '20231010'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -26083,7 +26220,7 @@ BEGIN SET NOCOUNT, XACT_ABORT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.16', @VersionDate = '20230820'; + SELECT @Version = '8.17', @VersionDate = '20231010'; IF @VersionCheckMode = 1 BEGIN @@ -26362,7 +26499,7 @@ BEGIN RETURN; END; END; - + IF @Azure = 1 BEGIN IF NOT EXISTS @@ -27892,7 +28029,7 @@ BEGIN COUNT_BIG(DISTINCT dp.event_date) ) + N' deadlocks.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT dp.event_date) DESC) FROM #deadlock_process AS dp @@ -27926,7 +28063,18 @@ BEGIN check_id = 2, dow.database_name, object_name = - N'You Might Need RCSI', + CASE + WHEN EXISTS + ( + SELECT + 1/0 + FROM sys.databases AS d + WHERE d.name = dow.database_name + AND d.is_read_committed_snapshot_on = 1 + ) + THEN N'You already enabled RCSI, but...' + ELSE N'You Might Need RCSI' + END, finding_group = N'Total Deadlocks Involving Selects', finding = N'There have been ' + @@ -27936,7 +28084,7 @@ BEGIN COUNT_BIG(DISTINCT dow.event_date) ) + N' deadlock(s) between read queries and modification queries.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT dow.event_date) DESC) FROM #deadlock_owner_waiter AS dow @@ -27998,7 +28146,7 @@ BEGIN COUNT_BIG(DISTINCT dow.event_date) ) + N' deadlock(s).', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT dow.event_date) DESC) FROM #deadlock_owner_waiter AS dow @@ -28041,7 +28189,7 @@ BEGIN COUNT_BIG(DISTINCT dow.event_date) ) + N' deadlock(s).', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT dow.event_date) DESC) FROM #deadlock_owner_waiter AS dow @@ -28090,7 +28238,7 @@ BEGIN COUNT_BIG(DISTINCT dow.event_date) ) + N' deadlock(s).', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT dow.event_date) DESC) FROM #deadlock_owner_waiter AS dow @@ -28139,7 +28287,7 @@ BEGIN COUNT_BIG(DISTINCT dp.event_date) ) + N' instances of Serializable deadlocks.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT dp.event_date) DESC) FROM #deadlock_process AS dp @@ -28182,7 +28330,7 @@ BEGIN COUNT_BIG(DISTINCT dp.event_date) ) + N' instances of Repeatable Read deadlocks.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT dp.event_date) DESC) FROM #deadlock_process AS dp @@ -28244,7 +28392,7 @@ BEGIN N'UNKNOWN' ) + N'.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT dp.event_date) DESC) FROM #deadlock_process AS dp @@ -28356,7 +28504,7 @@ BEGIN 1, N'' ) + N' locks.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY CONVERT(bigint, lt.lock_count) DESC) FROM lock_types AS lt @@ -28535,7 +28683,7 @@ BEGIN COUNT_BIG(DISTINCT ds.id) ) + N' deadlocks.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT ds.id) DESC) FROM #deadlock_stack AS ds @@ -28636,19 +28784,19 @@ BEGIN ) ), wait_time_hms = - /*the more wait time you rack up the less accurate this gets, + /*the more wait time you rack up the less accurate this gets, it's either that or erroring out*/ - CASE - WHEN + CASE + WHEN SUM ( CONVERT ( - bigint, + bigint, dp.wait_time ) )/1000 > 2147483647 - THEN + THEN CONVERT ( nvarchar(30), @@ -28661,7 +28809,7 @@ BEGIN ( CONVERT ( - bigint, + bigint, dp.wait_time ) ) @@ -28672,16 +28820,16 @@ BEGIN ), 14 ) - WHEN + WHEN SUM ( CONVERT ( - bigint, + bigint, dp.wait_time ) ) BETWEEN 2147483648 AND 2147483647000 - THEN + THEN CONVERT ( nvarchar(30), @@ -28694,7 +28842,7 @@ BEGIN ( CONVERT ( - bigint, + bigint, dp.wait_time ) ) @@ -28776,7 +28924,7 @@ BEGIN 14 ) + N' [dd hh:mm:ss:ms] of deadlock wait time.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY cs.total_waits DESC) FROM chopsuey AS cs @@ -28850,19 +28998,19 @@ BEGIN ) ) + N' ' + - /*the more wait time you rack up the less accurate this gets, + /*the more wait time you rack up the less accurate this gets, it's either that or erroring out*/ - CASE - WHEN + CASE + WHEN SUM ( CONVERT ( - bigint, + bigint, wt.total_wait_time_ms ) )/1000 > 2147483647 - THEN + THEN CONVERT ( nvarchar(30), @@ -28875,7 +29023,7 @@ BEGIN ( CONVERT ( - bigint, + bigint, wt.total_wait_time_ms ) ) @@ -28886,16 +29034,16 @@ BEGIN ), 14 ) - WHEN + WHEN SUM ( CONVERT ( - bigint, + bigint, wt.total_wait_time_ms ) ) BETWEEN 2147483648 AND 2147483647000 - THEN + THEN CONVERT ( nvarchar(30), @@ -28908,7 +29056,7 @@ BEGIN ( CONVERT ( - bigint, + bigint, wt.total_wait_time_ms ) ) @@ -28941,7 +29089,7 @@ BEGIN 14 ) END + N' [dd hh:mm:ss:ms] of deadlock wait time.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY SUM(CONVERT(bigint, wt.total_wait_time_ms)) DESC) FROM wait_time AS wt @@ -28979,7 +29127,7 @@ BEGIN N'There have been ' + RTRIM(COUNT_BIG(DISTINCT aj.event_date)) + N' deadlocks from this Agent Job and Step.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT aj.event_date) DESC) FROM #agent_job AS aj @@ -29025,7 +29173,7 @@ BEGIN /*Check 15 is total deadlocks involving sleeping sessions*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Check 15 sleeping deadlocks %s', 0, 1, @d) WITH NOWAIT; + RAISERROR('Check 15 sleeping and background deadlocks %s', 0, 1, @d) WITH NOWAIT; INSERT #deadlock_findings WITH(TABLOCKX) @@ -29054,6 +29202,33 @@ BEGIN HAVING COUNT_BIG(DISTINCT dp.event_date) > 0 OPTION(RECOMPILE); + INSERT + #deadlock_findings WITH(TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding + ) + SELECT + check_id = 15, + database_name = N'-', + object_name = N'-', + finding_group = N'Total deadlocks involving background processes', + finding = + N'There have been ' + + CONVERT + ( + nvarchar(20), + COUNT_BIG(DISTINCT dp.event_date) + ) + + N' deadlocks with background task.' + FROM #deadlock_process AS dp + WHERE dp.status = N'background' + HAVING COUNT_BIG(DISTINCT dp.event_date) > 0 + OPTION(RECOMPILE); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; /*Check 16 is total deadlocks involving implicit transactions*/ @@ -29732,18 +29907,18 @@ BEGIN BEGIN SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Results to client %s', 0, 1, @d) WITH NOWAIT; - + IF @Debug = 1 BEGIN SET STATISTICS XML ON; END; - + EXEC sys.sp_executesql @deadlock_result; - + IF @Debug = 1 BEGIN SET STATISTICS XML OFF; PRINT @deadlock_result; END; - + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; SET @d = CONVERT(varchar(40), GETDATE(), 109); @@ -29921,10 +30096,10 @@ BEGIN OPTION(RECOMPILE, LOOP JOIN, HASH JOIN); RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - + SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Returning findings %s', 0, 1, @d) WITH NOWAIT; - + SELECT df.check_id, df.database_name, @@ -29936,7 +30111,7 @@ BEGIN df.check_id, df.sort_order OPTION(RECOMPILE); - + SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; END; /*done with output to client app.*/ @@ -30020,7 +30195,7 @@ BEGIN table_name = N'#dm_exec_query_stats', * FROM #dm_exec_query_stats - OPTION(RECOMPILE); + OPTION(RECOMPILE); SELECT procedure_parameters = @@ -30175,7 +30350,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.16', @VersionDate = '20230820'; +SELECT @Version = '8.17', @VersionDate = '20231010'; IF(@VersionCheckMode = 1) BEGIN RETURN; @@ -30448,6 +30623,28 @@ SET @msg = N'New query_store_runtime_stats columns ' + CASE @new_columns END; RAISERROR(@msg, 0, 1) WITH NOWAIT; +/* +This section determines if Parameter Sensitive Plan Optimization is enabled on SQL Server 2022+. +*/ + +RAISERROR('Checking for Parameter Sensitive Plan Optimization ', 0, 1) WITH NOWAIT; + +DECLARE @pspo_out BIT, + @pspo_enabled BIT, + @pspo_sql NVARCHAR(MAX) = N'SELECT @i_out = CONVERT(bit,dsc.value) + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.database_scoped_configurations dsc + WHERE dsc.name = ''PARAMETER_SENSITIVE_PLAN_OPTIMIZATION'';', + @pspo_params NVARCHAR(MAX) = N'@i_out INT OUTPUT'; + +EXEC sys.sp_executesql @pspo_sql, @pspo_params, @i_out = @pspo_out OUTPUT; + +SET @pspo_enabled = CASE WHEN @pspo_out = 1 THEN 1 ELSE 0 END; + +SET @msg = N'Parameter Sensitive Plan Optimization ' + CASE @pspo_enabled + WHEN 0 THEN N' not enabled, skipping.' + WHEN 1 THEN N' enabled, will analyze.' + END; +RAISERROR(@msg, 0, 1) WITH NOWAIT; /* These are the temp tables we use @@ -31151,10 +31348,33 @@ IF @MinimumExecutionCount IS NOT NULL --You care about stored proc names IF @StoredProcName IS NOT NULL - BEGIN - RAISERROR(N'Setting stored proc filter', 0, 1) WITH NOWAIT; - SET @sql_where += N' AND object_name(qsq.object_id, DB_ID(' + QUOTENAME(@DatabaseName, '''') + N')) = @sp_StoredProcName - '; + BEGIN + + IF (@pspo_enabled = 1) + BEGIN + RAISERROR(N'Setting stored proc filter, PSPO enabled', 0, 1) WITH NOWAIT; + /* If PSPO is enabled, the object_id for a variant query would be 0. To include it, we check whether the object_id = 0 query + is a variant query, and whether it's parent query belongs to @sp_StoredProcName. */ + SET @sql_where += N' AND (object_name(qsq.object_id, DB_ID(' + QUOTENAME(@DatabaseName, '''') + N')) = @sp_StoredProcName + OR (qsq.object_id = 0 + AND EXISTS( + SELECT 1 + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_variant vr + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query pqsq + ON pqsq.query_id = vr.parent_query_id + WHERE + vr.query_variant_query_id = qsq.query_id + AND object_name(pqsq.object_id, DB_ID(' + QUOTENAME(@DatabaseName, '''') + N')) = @sp_StoredProcName + ) + )) + '; + END + ELSE + BEGIN + RAISERROR(N'Setting stored proc filter', 0, 1) WITH NOWAIT; + SET @sql_where += N' AND object_name(qsq.object_id, DB_ID(' + QUOTENAME(@DatabaseName, '''') + N')) = @sp_StoredProcName + '; + END END; --I will always love you, but hopefully this query will eventually end @@ -32388,6 +32608,30 @@ EXEC sys.sp_executesql @stmt = @sql_select, @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; +/*If PSPO is enabled, get procedure names for variant queries.*/ +IF (@pspo_enabled = 1) +BEGIN + DECLARE + @pspo_names NVARCHAR(MAX) = ''; + + SET @pspo_names = + 'UPDATE wm + SET + wm.proc_or_function_name = + QUOTENAME(object_schema_name(qsq.object_id, DB_ID(' + QUOTENAME(@DatabaseName, '''') + N'))) + ''.'' + + QUOTENAME(object_name(qsq.object_id, DB_ID(' + QUOTENAME(@DatabaseName, '''') + N'))) + FROM #working_metrics wm + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_variant AS vr + ON vr.query_variant_query_id = wm.query_id + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq + ON qsq.query_id = vr.parent_query_id + AND qsq.object_id > 0 + WHERE + wm.proc_or_function_name IS NULL;' + + EXEC sys.sp_executesql @pspo_names; +END; + /*This just helps us classify our queries*/ UPDATE #working_metrics @@ -35906,7 +36150,7 @@ BEGIN SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.16', @VersionDate = '20230820'; + SELECT @Version = '8.17', @VersionDate = '20231010'; IF(@VersionCheckMode = 1) BEGIN @@ -37302,6 +37546,7 @@ DELETE FROM dbo.SqlServerVersions; INSERT INTO dbo.SqlServerVersions (MajorVersionNumber, MinorVersionNumber, Branch, [Url], ReleaseDate, MainstreamSupportEndDate, ExtendedSupportEndDate, MajorVersionName, MinorVersionName) VALUES + (16, 4075, 'CU8', 'https://support.microsoft.com/en-us/help/5029666', '2023-09-14', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 8'), (16, 4065, 'CU7', 'https://support.microsoft.com/en-us/help/5028743', '2023-08-10', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 7'), (16, 4055, 'CU6', 'https://support.microsoft.com/en-us/help/5027505', '2023-07-13', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 6'), (16, 4045, 'CU5', 'https://support.microsoft.com/en-us/help/5026806', '2023-06-15', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 5'), @@ -37735,7 +37980,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.16', @VersionDate = '20230820'; +SELECT @Version = '8.17', @VersionDate = '20231010'; IF(@VersionCheckMode = 1) BEGIN @@ -41052,8 +41297,8 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 'Server Info' AS FindingGroup, 'Wait Time per Core per Sec' AS Finding, 'https://www.brentozar.com/go/measure' AS URL, - CAST((CAST(waits2.waits_ms - waits1.waits_ms AS MONEY)) / 1000 / i.cpu_count / DATEDIFF(ss, waits1.SampleTime, waits2.SampleTime) AS NVARCHAR(20)) AS Details, - (waits2.waits_ms - waits1.waits_ms) / 1000 / i.cpu_count / DATEDIFF(ss, waits1.SampleTime, waits2.SampleTime) AS DetailsInt + CAST((CAST(waits2.waits_ms - waits1.waits_ms AS MONEY)) / 1000 / i.cpu_count / ISNULL(NULLIF(DATEDIFF(ss, waits1.SampleTime, waits2.SampleTime), 0), 1) AS NVARCHAR(20)) AS Details, + (waits2.waits_ms - waits1.waits_ms) / 1000 / i.cpu_count / ISNULL(NULLIF(DATEDIFF(ss, waits1.SampleTime, waits2.SampleTime), 0), 1) AS DetailsInt FROM cores i CROSS JOIN waits1 CROSS JOIN waits2; diff --git a/SqlServerVersions.sql b/SqlServerVersions.sql index cf6d224c5..ac7600d01 100644 --- a/SqlServerVersions.sql +++ b/SqlServerVersions.sql @@ -41,6 +41,7 @@ DELETE FROM dbo.SqlServerVersions; INSERT INTO dbo.SqlServerVersions (MajorVersionNumber, MinorVersionNumber, Branch, [Url], ReleaseDate, MainstreamSupportEndDate, ExtendedSupportEndDate, MajorVersionName, MinorVersionName) VALUES + (16, 4075, 'CU8', 'https://support.microsoft.com/en-us/help/5029666', '2023-09-14', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 8'), (16, 4065, 'CU7', 'https://support.microsoft.com/en-us/help/5028743', '2023-08-10', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 7'), (16, 4055, 'CU6', 'https://support.microsoft.com/en-us/help/5027505', '2023-07-13', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 6'), (16, 4045, 'CU5', 'https://support.microsoft.com/en-us/help/5026806', '2023-06-15', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 5'), diff --git a/sp_AllNightLog.sql b/sp_AllNightLog.sql index 0a37f4f7a..da4cf313f 100644 --- a/sp_AllNightLog.sql +++ b/sp_AllNightLog.sql @@ -31,7 +31,7 @@ SET STATISTICS XML OFF; BEGIN; -SELECT @Version = '8.16', @VersionDate = '20230820'; +SELECT @Version = '8.17', @VersionDate = '20231010'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_AllNightLog_Setup.sql b/sp_AllNightLog_Setup.sql index 9df6a50cc..cfc2042fd 100644 --- a/sp_AllNightLog_Setup.sql +++ b/sp_AllNightLog_Setup.sql @@ -38,7 +38,7 @@ SET STATISTICS XML OFF; BEGIN; -SELECT @Version = '8.16', @VersionDate = '20230820'; +SELECT @Version = '8.17', @VersionDate = '20231010'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_Blitz.sql b/sp_Blitz.sql index 0b7f32767..7e4039226 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -38,7 +38,7 @@ AS SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.16', @VersionDate = '20230820'; + SELECT @Version = '8.17', @VersionDate = '20231010'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) diff --git a/sp_BlitzAnalysis.sql b/sp_BlitzAnalysis.sql index 9cfb737cb..e7ebb1dfd 100644 --- a/sp_BlitzAnalysis.sql +++ b/sp_BlitzAnalysis.sql @@ -37,7 +37,7 @@ AS SET NOCOUNT ON; SET STATISTICS XML OFF; -SELECT @Version = '8.16', @VersionDate = '20230820'; +SELECT @Version = '8.17', @VersionDate = '20231010'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_BlitzBackups.sql b/sp_BlitzBackups.sql index 17d081901..1f31ddaa0 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.16', @VersionDate = '20230820'; + SELECT @Version = '8.17', @VersionDate = '20231010'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_BlitzCache.sql b/sp_BlitzCache.sql index b6c3113fa..5c7fb2dc0 100644 --- a/sp_BlitzCache.sql +++ b/sp_BlitzCache.sql @@ -281,7 +281,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.16', @VersionDate = '20230820'; +SELECT @Version = '8.17', @VersionDate = '20231010'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) diff --git a/sp_BlitzFirst.sql b/sp_BlitzFirst.sql index e1e2be7f3..a35db05ca 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.16', @VersionDate = '20230820'; +SELECT @Version = '8.17', @VersionDate = '20231010'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_BlitzInMemoryOLTP.sql b/sp_BlitzInMemoryOLTP.sql index 919a72d6d..f2294de10 100644 --- a/sp_BlitzInMemoryOLTP.sql +++ b/sp_BlitzInMemoryOLTP.sql @@ -82,7 +82,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI */ AS DECLARE @ScriptVersion VARCHAR(30); -SELECT @ScriptVersion = '1.8', @VersionDate = '20230820'; +SELECT @ScriptVersion = '1.8', @VersionDate = '20231010'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index 2c83589d1..0798e1d29 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -48,7 +48,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.16', @VersionDate = '20230820'; +SELECT @Version = '8.17', @VersionDate = '20231010'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index 9d842a97f..bb40b88c7 100644 --- a/sp_BlitzLock.sql +++ b/sp_BlitzLock.sql @@ -35,7 +35,7 @@ BEGIN SET NOCOUNT, XACT_ABORT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.16', @VersionDate = '20230820'; + SELECT @Version = '8.17', @VersionDate = '20231010'; IF @VersionCheckMode = 1 BEGIN diff --git a/sp_BlitzQueryStore.sql b/sp_BlitzQueryStore.sql index ea5b90247..6ff69f240 100644 --- a/sp_BlitzQueryStore.sql +++ b/sp_BlitzQueryStore.sql @@ -57,7 +57,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.16', @VersionDate = '20230820'; +SELECT @Version = '8.17', @VersionDate = '20231010'; IF(@VersionCheckMode = 1) BEGIN RETURN; diff --git a/sp_BlitzWho.sql b/sp_BlitzWho.sql index d433ddf5b..d3e9f1004 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.16', @VersionDate = '20230820'; + SELECT @Version = '8.17', @VersionDate = '20231010'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_DatabaseRestore.sql b/sp_DatabaseRestore.sql index e02a0d4b2..640ad8c89 100755 --- a/sp_DatabaseRestore.sql +++ b/sp_DatabaseRestore.sql @@ -45,7 +45,7 @@ SET STATISTICS XML OFF; /*Versioning details*/ -SELECT @Version = '8.16', @VersionDate = '20230820'; +SELECT @Version = '8.17', @VersionDate = '20231010'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_ineachdb.sql b/sp_ineachdb.sql index 8e7890e85..c7bddb69f 100644 --- a/sp_ineachdb.sql +++ b/sp_ineachdb.sql @@ -35,7 +35,7 @@ BEGIN SET NOCOUNT ON; SET STATISTICS XML OFF; - SELECT @Version = '8.16', @VersionDate = '20230820'; + SELECT @Version = '8.17', @VersionDate = '20231010'; IF(@VersionCheckMode = 1) BEGIN From f59b78f25a6e508560e9b5a1b5da6913f4bce32a Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Tue, 10 Oct 2023 08:14:21 -0700 Subject: [PATCH 468/662] sp_Blitz power savings Fixing wrong variable name introduced in recent changes. --- Install-All-Scripts.sql | 2 +- Install-Core-Blitz-No-Query-Store.sql | 2 +- Install-Core-Blitz-With-Query-Store.sql | 2 +- sp_Blitz.sql | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Install-All-Scripts.sql b/Install-All-Scripts.sql index bdb41e6b6..cf1bc0e84 100644 --- a/Install-All-Scripts.sql +++ b/Install-All-Scripts.sql @@ -3150,7 +3150,7 @@ AS @value = @powerScheme OUTPUT, @no_output = N'no_output'; - IF @powersaveSetting IS NULL /* If power plan was not set by group policy, get local value [Git Hub Issue #1620]*/ + IF @powerScheme IS NULL /* If power plan was not set by group policy, get local value [Git Hub Issue #1620]*/ EXEC xp_regread @rootkey = N'HKEY_LOCAL_MACHINE', @key = N'SYSTEM\CurrentControlSet\Control\Power\User\PowerSchemes', @value_name = N'ActivePowerScheme', diff --git a/Install-Core-Blitz-No-Query-Store.sql b/Install-Core-Blitz-No-Query-Store.sql index 59ec534a9..04060a6c6 100644 --- a/Install-Core-Blitz-No-Query-Store.sql +++ b/Install-Core-Blitz-No-Query-Store.sql @@ -288,7 +288,7 @@ AS @value = @powerScheme OUTPUT, @no_output = N'no_output'; - IF @powersaveSetting IS NULL /* If power plan was not set by group policy, get local value [Git Hub Issue #1620]*/ + IF @powerScheme IS NULL /* If power plan was not set by group policy, get local value [Git Hub Issue #1620]*/ EXEC xp_regread @rootkey = N'HKEY_LOCAL_MACHINE', @key = N'SYSTEM\CurrentControlSet\Control\Power\User\PowerSchemes', @value_name = N'ActivePowerScheme', diff --git a/Install-Core-Blitz-With-Query-Store.sql b/Install-Core-Blitz-With-Query-Store.sql index e7b9fd0bc..0075b6f10 100644 --- a/Install-Core-Blitz-With-Query-Store.sql +++ b/Install-Core-Blitz-With-Query-Store.sql @@ -288,7 +288,7 @@ AS @value = @powerScheme OUTPUT, @no_output = N'no_output'; - IF @powersaveSetting IS NULL /* If power plan was not set by group policy, get local value [Git Hub Issue #1620]*/ + IF @powerScheme IS NULL /* If power plan was not set by group policy, get local value [Git Hub Issue #1620]*/ EXEC xp_regread @rootkey = N'HKEY_LOCAL_MACHINE', @key = N'SYSTEM\CurrentControlSet\Control\Power\User\PowerSchemes', @value_name = N'ActivePowerScheme', diff --git a/sp_Blitz.sql b/sp_Blitz.sql index 7e4039226..08a6ca91c 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -288,7 +288,7 @@ AS @value = @powerScheme OUTPUT, @no_output = N'no_output'; - IF @powersaveSetting IS NULL /* If power plan was not set by group policy, get local value [Git Hub Issue #1620]*/ + IF @powerScheme IS NULL /* If power plan was not set by group policy, get local value [Git Hub Issue #1620]*/ EXEC xp_regread @rootkey = N'HKEY_LOCAL_MACHINE', @key = N'SYSTEM\CurrentControlSet\Control\Power\User\PowerSchemes', @value_name = N'ActivePowerScheme', From a6eadf0ad72373d78dd5e5430572a4857d1bef69 Mon Sep 17 00:00:00 2001 From: Martin van der Boom Date: Wed, 11 Oct 2023 13:14:56 +0200 Subject: [PATCH 469/662] #3377 Moved the xp_regread calls back to the check and add some messages to the user --- sp_Blitz.sql | 70 +++++++++++++++++++++++++--------------------------- 1 file changed, 33 insertions(+), 37 deletions(-) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index 08a6ca91c..e03300af4 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -200,10 +200,6 @@ AS ,@SkipModel bit = 0 ,@SkipTempDB bit = 0 ,@SkipValidateLogins bit = 0 - /* Variables for check 211: */ - ,@powerScheme varchar(36) - ,@cpu_speed_mhz int - ,@cpu_speed_ghz decimal(18,2); DECLARE @db_perms table @@ -278,38 +274,6 @@ AS SET @SkipTrace = 1; END; /*We need this permission to execute trace stuff, apparently*/ - IF ISNULL(@SkipXPRegRead, 0) != 1 /*If @SkipXPRegRead hasn't been set to 1 by the caller*/ - BEGIN - BEGIN TRY - /* Get power plan if set by group policy [Git Hub Issue #1620] */ - EXEC xp_regread @rootkey = N'HKEY_LOCAL_MACHINE', - @key = N'SOFTWARE\Policies\Microsoft\Power\PowerSettings', - @value_name = N'ActivePowerScheme', - @value = @powerScheme OUTPUT, - @no_output = N'no_output'; - - IF @powerScheme IS NULL /* If power plan was not set by group policy, get local value [Git Hub Issue #1620]*/ - EXEC xp_regread @rootkey = N'HKEY_LOCAL_MACHINE', - @key = N'SYSTEM\CurrentControlSet\Control\Power\User\PowerSchemes', - @value_name = N'ActivePowerScheme', - @value = @powerScheme OUTPUT; - - /* Get the cpu speed*/ - EXEC xp_regread @rootkey = N'HKEY_LOCAL_MACHINE', - @key = N'HARDWARE\DESCRIPTION\System\CentralProcessor\0', - @value_name = N'~MHz', - @value = @cpu_speed_mhz OUTPUT; - - /* Convert the Megahertz to Gigahertz */ - SET @cpu_speed_ghz = CAST(CAST(@cpu_speed_mhz AS decimal) / 1000 AS decimal(18,2)); - - SET @SkipXPRegRead = 0; /*We could execute xp_regread*/ - END TRY - BEGIN CATCH - SET @SkipXPRegRead = 1; /*We have don't have execute rights or xp_regread throws an error so skip it*/ - END CATCH; - END; /*Need execute on xp_regread*/ - IF NOT EXISTS ( SELECT @@ -9167,7 +9131,39 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 WHERE DatabaseName IS NULL AND CheckID = 211 ) BEGIN + /* Variables for check 211: */ + DECLARE + @powerScheme varchar(36) + ,@cpu_speed_mhz int + ,@cpu_speed_ghz decimal(18,2) + ,@ExecResult int; + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 211) WITH NOWAIT; + IF @sa = 0 RAISERROR('The errors: ''xp_regread() returned error 5, ''Access is denied.'''' can be safely ignored', 0, 1) WITH NOWAIT; + + /* Get power plan if set by group policy [Git Hub Issue #1620] */ + EXEC xp_regread @rootkey = N'HKEY_LOCAL_MACHINE', + @key = N'SOFTWARE\Policies\Microsoft\Power\PowerSettings', + @value_name = N'ActivePowerScheme', + @value = @powerScheme OUTPUT, + @no_output = N'no_output'; + + IF @powerScheme IS NULL /* If power plan was not set by group policy, get local value [Git Hub Issue #1620]*/ + EXEC xp_regread @rootkey = N'HKEY_LOCAL_MACHINE', + @key = N'SYSTEM\CurrentControlSet\Control\Power\User\PowerSchemes', + @value_name = N'ActivePowerScheme', + @value = @powerScheme OUTPUT; + + /* Get the cpu speed*/ + EXEC @ExecResult = xp_regread @rootkey = N'HKEY_LOCAL_MACHINE', + @key = N'HARDWARE\DESCRIPTION\System\CentralProcessor\0', + @value_name = N'~MHz', + @value = @cpu_speed_mhz OUTPUT; + + /* Convert the Megahertz to Gigahertz */ + IF @ExecResult != 0 RAISERROR('We couldn''t the CPU speed, you will see Unknown in the results', 0, 1) + + SET @cpu_speed_ghz = CAST(CAST(@cpu_speed_mhz AS decimal) / 1000 AS decimal(18,2)); INSERT INTO #BlitzResults ( CheckID , @@ -9183,7 +9179,7 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 'Power Plan' AS Finding, 'https://www.brentozar.com/blitz/power-mode/' AS URL, 'Your server has ' - + CAST(@cpu_speed_ghz as VARCHAR(4)) + + ISNULL(CAST(@cpu_speed_ghz as VARCHAR(8)), 'Unknown ') + 'GHz CPUs, and is in ' + CASE @powerScheme WHEN 'a1841308-3541-4fab-bc81-f71556f20b4a' From f00c58a90da93cd8eea2992dd6c646a9ce4483dd Mon Sep 17 00:00:00 2001 From: Martin van der Boom Date: Wed, 11 Oct 2023 13:59:12 +0200 Subject: [PATCH 470/662] #3376 Added Informational message about skipping checks --- sp_Blitz.sql | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index 08a6ca91c..48d907559 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -630,6 +630,25 @@ AS FROM (VALUES(NULL, 2301, NULL)) AS v (DatabaseName, CheckID, ServerName) /*sp_validatelogins*/ WHERE @SkipValidateLogins = 1 + IF @sa = 0 + BEGIN + INSERT INTO #BlitzResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT 223 AS CheckID , + 0 AS Priority , + 'Informational' AS FindingsGroup , + 'Some Checks Skipped' AS Finding , + '' AS URL , + 'User ''' + @SUSER_NAME + ''' is not part of the sysadmin role, so we skipped some checks that are not possible due to lack of permissions.' AS Details; + END; + /*End of SkipsChecks added due to permissions*/ + IF @SkipChecksTable IS NOT NULL AND @SkipChecksSchema IS NOT NULL AND @SkipChecksDatabase IS NOT NULL From 0ec23578f20d0170222b3b37405be901ff9549ea Mon Sep 17 00:00:00 2001 From: Martin van der Boom Date: Wed, 11 Oct 2023 14:06:43 +0200 Subject: [PATCH 471/662] #3377 xp_readreg completed the sentence --- sp_Blitz.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index e03300af4..31d4824a5 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -9161,7 +9161,7 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 @value = @cpu_speed_mhz OUTPUT; /* Convert the Megahertz to Gigahertz */ - IF @ExecResult != 0 RAISERROR('We couldn''t the CPU speed, you will see Unknown in the results', 0, 1) + IF @ExecResult != 0 RAISERROR('We couldn''t retrieve the CPU speed, you will see Unknown in the results', 0, 1) SET @cpu_speed_ghz = CAST(CAST(@cpu_speed_mhz AS decimal) / 1000 AS decimal(18,2)); From 6487f338d51ef5ac4b377bcca0a5bba6c1308af1 Mon Sep 17 00:00:00 2001 From: Martin van der Boom Date: Wed, 11 Oct 2023 14:17:13 +0200 Subject: [PATCH 472/662] #3377 Expanded msdb permission checks --- sp_Blitz.sql | 134 ++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 123 insertions(+), 11 deletions(-) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index 08a6ca91c..3d921a2f3 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -196,7 +196,10 @@ AS ,@SkipXPFixedDrives bit = 0 ,@SkipXPCMDShell bit = 0 ,@SkipMaster bit = 0 - ,@SkipMSDB bit = 0 + ,@SkipMSDB_objs bit = 0 + ,@SkipMSDB_jobs bit = 0 + ,@SkipMSDB_alerts bit = 0 + ,@SkipMSDB_operators bit = 0 ,@SkipModel bit = 0 ,@SkipTempDB bit = 0 ,@SkipValidateLogins bit = 0 @@ -379,7 +382,7 @@ AS END; END; - IF ISNULL(@SkipMSDB, 0) != 1 /*If @SkipMSDB hasn't been set to 1 by the caller*/ + IF ISNULL(@SkipMSDB_objs, 0) != 1 /*If @SkipMSDB_objs hasn't been set to 1 by the caller*/ BEGIN IF EXISTS ( @@ -395,16 +398,103 @@ AS FROM msdb.sys.objects ) BEGIN - SET @SkipMSDB = 0; /*We have read permissions in the msdb database, and can view the objects*/ + SET @SkipMSDB_objs = 0; /*We have read permissions in the msdb database, and can view the objects*/ END; END TRY BEGIN CATCH - SET @SkipMSDB = 1; /*We have read permissions in the msdb database ... oh wait we got tricked, we can't view the objects*/ + SET @SkipMSDB_objs = 1; /*We have read permissions in the msdb database ... oh wait we got tricked, we can't view the objects*/ END CATCH; END; ELSE BEGIN - SET @SkipMSDB = 1; /*We don't have read permissions in the msdb database*/ + SET @SkipMSDB_objs = 1; /*We don't have read permissions in the msdb database*/ + END; + END; + + IF ISNULL(@SkipMSDB_jobs, 0) != 1 /*If @SkipMSDB_jobs hasn't been set to 1 by the caller*/ + BEGIN + IF EXISTS + ( + SELECT 1/0 + FROM @db_perms + WHERE database_name = N'msdb' + ) + BEGIN + BEGIN TRY + IF EXISTS + ( + SELECT 1/0 + FROM msdb.dbo.sysjobs + ) + BEGIN + SET @SkipMSDB_jobs = 0; /*We have read permissions in the msdb database, and can view the objects*/ + END; + END TRY + BEGIN CATCH + SET @SkipMSDB_jobs = 1; /*We have read permissions in the msdb database ... oh wait we got tricked, we can't view the objects*/ + END CATCH; + END; + ELSE + BEGIN + SET @SkipMSDB_jobs = 1; /*We don't have read permissions in the msdb database*/ + END; + END; + + IF ISNULL(@SkipMSDB_alerts, 0) != 1 /*If @SkipMSDB_alerts hasn't been set to 1 by the caller*/ + BEGIN + IF EXISTS + ( + SELECT 1/0 + FROM @db_perms + WHERE database_name = N'msdb' + ) + BEGIN + BEGIN TRY + IF EXISTS + ( + SELECT 1/0 + FROM msdb.dbo.sysalerts + ) + BEGIN + SET @SkipMSDB_alerts = 0; /*We have read permissions in the msdb database, and can view the objects*/ + END; + END TRY + BEGIN CATCH + SET @SkipMSDB_alerts = 1; /*We have read permissions in the msdb database ... oh wait we got tricked, we can't view the objects*/ + END CATCH; + END; + ELSE + BEGIN + SET @SkipMSDB_alerts = 1; /*We don't have read permissions in the msdb database*/ + END; + END; + + IF ISNULL(@SkipMSDB_operators, 0) != 1 /*If @SkipMSDB_operators hasn't been set to 1 by the caller*/ + BEGIN + IF EXISTS + ( + SELECT 1/0 + FROM @db_perms + WHERE database_name = N'msdb' + ) + BEGIN + BEGIN TRY + IF EXISTS + ( + SELECT 1/0 + FROM msdb.dbo.sysoperators + ) + BEGIN + SET @SkipMSDB_operators = 0; /*We have read permissions in the msdb database, and can view the objects*/ + END; + END TRY + BEGIN CATCH + SET @SkipMSDB_operators = 1; /*We have read permissions in the msdb database ... oh wait we got tricked, we can't view the objects*/ + END CATCH; + END; + ELSE + BEGIN + SET @SkipMSDB_operators = 1; /*We don't have read permissions in the msdb database*/ END; END; END; @@ -574,19 +664,41 @@ AS WHERE @SkipModel = 1; INSERT #SkipChecks (DatabaseName, CheckID, ServerName) + SELECT + v.* + FROM (VALUES(NULL, 28, NULL)) AS v (DatabaseName, CheckID, ServerName) /*Tables in the MSDB Database*/ + WHERE @SkipMSDB_objs = 1; + + INSERT #SkipChecks (DatabaseName, CheckID, ServerName) SELECT v.* FROM (VALUES(NULL, 6, NULL), /*Jobs Owned By Users*/ - (NULL, 28, NULL), /*SQL Agent Job Runs at Startup*/ - (NULL, 57, NULL), /*Tables in the MSDB Database*/ + (NULL, 57, NULL), /*SQL Agent Job Runs at Startup*/ (NULL, 79, NULL), /*Shrink Database Job*/ (NULL, 94, NULL), /*Agent Jobs Without Failure Emails*/ (NULL, 123, NULL), /*Agent Jobs Starting Simultaneously*/ (NULL, 180, NULL), /*Shrink Database Step In Maintenance Plan*/ - (NULL, 181, NULL), /*Repetitive Maintenance Tasks*/ + (NULL, 181, NULL) /*Repetitive Maintenance Tasks*/ + ) AS v (DatabaseName, CheckID, ServerName) + WHERE @SkipMSDB_jobs = 1; + + INSERT #SkipChecks (DatabaseName, CheckID, ServerName) + SELECT + v.* + FROM (VALUES(NULL, 30, NULL), /*Not All Alerts Configured*/ + (NULL, 59, NULL), /*Alerts Configured without Follow Up*/ + (NULL, 61, NULL), /*No Alerts for Sev 19-25*/ + (NULL, 96, NULL), /*No Alerts for Corruption*/ + (NULL, 98, NULL), /*Alerts Disabled*/ (NULL, 219, NULL) /*Alerts Without Event Descriptions*/ - ) AS v (DatabaseName, CheckID, ServerName) - WHERE @SkipMSDB = 1; + ) AS v (DatabaseName, CheckID, ServerName) + WHERE @SkipMSDB_alerts = 1; + + INSERT #SkipChecks (DatabaseName, CheckID, ServerName) + SELECT + v.* + FROM (VALUES(NULL, 31, NULL)) AS v (DatabaseName, CheckID, ServerName) /*No Operators Configured/Enabled*/ + WHERE @SkipMSDB_operators = 1; INSERT #SkipChecks (DatabaseName, CheckID, ServerName) SELECT @@ -9976,4 +10088,4 @@ EXEC [dbo].[sp_Blitz] @OutputProcedureCache = 0 , @CheckProcedureCacheFilter = NULL, @CheckServerInfo = 1 -*/ +*/ \ No newline at end of file From 26f701c83253307d3d74fb16034d566fc23d7203 Mon Sep 17 00:00:00 2001 From: Martin van der Boom Date: Wed, 11 Oct 2023 14:42:27 +0200 Subject: [PATCH 473/662] #3377 Added a check for sp_MSgetalertinfo --- sp_Blitz.sql | 61 ++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 45 insertions(+), 16 deletions(-) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index 08a6ca91c..26d90bfc6 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -200,6 +200,7 @@ AS ,@SkipModel bit = 0 ,@SkipTempDB bit = 0 ,@SkipValidateLogins bit = 0 + ,@SkipGetAlertInfo bit = 0 /* Variables for check 211: */ ,@powerScheme varchar(36) ,@cpu_speed_mhz int @@ -229,6 +230,23 @@ AS /* End of declarations for First Responder Kit consistency check:*/ ; + /* Create temp table for check 73 */ + IF OBJECT_ID('tempdb..#AlertInfo') IS NOT NULL + EXEC sp_executesql N'DROP TABLE #AlertInfo;'; + + CREATE TABLE #AlertInfo + ( + FailSafeOperator NVARCHAR(255) , + NotificationMethod INT , + ForwardingServer NVARCHAR(255) , + ForwardingSeverity INT , + PagerToTemplate NVARCHAR(255) , + PagerCCTemplate NVARCHAR(255) , + PagerSubjectTemplate NVARCHAR(255) , + PagerSendSubjectOnly NVARCHAR(255) , + ForwardAlways INT + ); + /* Create temp table for check 2301 */ IF OBJECT_ID('tempdb..#InvalidLogins') IS NOT NULL EXEC sp_executesql N'DROP TABLE #InvalidLogins;'; @@ -350,6 +368,20 @@ AS END CATCH; END; /*Need execute on sp_validatelogins*/ + IF ISNULL(@SkipGetAlertInfo, 0) != 1 /*If @SkipGetAlertInfo hasn't been set to 1 by the caller*/ + BEGIN + BEGIN TRY + /* Try to fill the table for check 73 */ + INSERT INTO #AlertInfo + EXEC [master].[dbo].[sp_MSgetalertinfo] @includeaddresses = 0; + + SET @SkipGetAlertInfo = 0; /*We can execute sp_MSgetalertinfo*/ + END TRY + BEGIN CATCH + SET @SkipGetAlertInfo = 1; /*We have don't have execute rights or sp_MSgetalertinfo throws an error so skip it*/ + END CATCH; + END; /*Need execute on sp_MSgetalertinfo*/ + IF ISNULL(@SkipModel, 0) != 1 /*If @SkipModel hasn't been set to 1 by the caller*/ BEGIN IF EXISTS @@ -628,7 +660,13 @@ AS SELECT v.* FROM (VALUES(NULL, 2301, NULL)) AS v (DatabaseName, CheckID, ServerName) /*sp_validatelogins*/ - WHERE @SkipValidateLogins = 1 + WHERE @SkipValidateLogins = 1; + + INSERT #SkipChecks (DatabaseName, CheckID, ServerName) + SELECT + v.* + FROM (VALUES(NULL, 73, NULL)) AS v (DatabaseName, CheckID, ServerName) /*sp_validatelogins*/ + WHERE @SkipGetAlertInfo = 1; IF @SkipChecksTable IS NOT NULL AND @SkipChecksSchema IS NOT NULL @@ -8181,20 +8219,6 @@ IF @ProductVersionMajor >= 10 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 73) WITH NOWAIT; - DECLARE @AlertInfo TABLE - ( - FailSafeOperator NVARCHAR(255) , - NotificationMethod INT , - ForwardingServer NVARCHAR(255) , - ForwardingSeverity INT , - PagerToTemplate NVARCHAR(255) , - PagerCCTemplate NVARCHAR(255) , - PagerSubjectTemplate NVARCHAR(255) , - PagerSendSubjectOnly NVARCHAR(255) , - ForwardAlways INT - ); - INSERT INTO @AlertInfo - EXEC [master].[dbo].[sp_MSgetalertinfo] @includeaddresses = 0; INSERT INTO #BlitzResults ( CheckID , Priority , @@ -8209,7 +8233,7 @@ IF @ProductVersionMajor >= 10 'No Failsafe Operator Configured' AS Finding , 'https://www.brentozar.com/go/failsafe' AS URL , ( 'No failsafe operator is configured on this server. This is a good idea just in-case there are issues with the [msdb] database that prevents alerting.' ) AS Details - FROM @AlertInfo + FROM #AlertInfo WHERE FailSafeOperator IS NULL; END; @@ -9957,6 +9981,11 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 EXEC sp_executesql N'DROP TABLE #InvalidLogins;'; END; + IF OBJECT_ID('tempdb..#AlertInfo') IS NOT NULL + BEGIN + EXEC sp_executesql N'DROP TABLE #AlertInfo;'; + END; + /* Reset the Nmumeric_RoundAbort session state back to enabled if it was disabled earlier. See Github issue #2302 for more info. From 4512041d17bffa4fa411ab9bb183988600864f47 Mon Sep 17 00:00:00 2001 From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com> Date: Wed, 11 Oct 2023 11:27:46 -0400 Subject: [PATCH 474/662] Update sp_BlitzLock.sql Missing _ms in the available plans output. Not worth an issue. --- sp_BlitzLock.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index 4130fe3ef..a7a0c4ac5 100644 --- a/sp_BlitzLock.sql +++ b/sp_BlitzLock.sql @@ -3736,7 +3736,7 @@ BEGIN CONVERT(decimal(38, 6), deqs.total_worker_time / 1000. / deqs.execution_count), total_elapsed_time_ms = deqs.total_elapsed_time / 1000., - avg_elapsed_time = + avg_elapsed_time_ms = CONVERT(decimal(38, 6), deqs.total_elapsed_time / 1000. / deqs.execution_count), executions_per_second = ISNULL From a2bea2fff41aa9138c0b273524321c6a06dcac3b Mon Sep 17 00:00:00 2001 From: Martin van der Boom Date: Thu, 12 Oct 2023 14:27:43 +0200 Subject: [PATCH 475/662] #3362 Rewrite of check 192 and 193 --- sp_Blitz.sql | 249 +++++++++++++++++++++++++++------------------------ 1 file changed, 134 insertions(+), 115 deletions(-) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index 08a6ca91c..e5a4c0a28 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -8674,122 +8674,141 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 EXECUTE(@StringToExecute); END; - /* - Starting with SQL Server 2014 SP2, Instant File Initialization - is logged in the SQL Server Error Log. - */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 193 ) - AND ((@ProductVersionMajor >= 13) OR (@ProductVersionMajor = 12 AND @ProductVersionMinor >= 5000)) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 193) WITH NOWAIT; - - -- If this is Amazon RDS, use rdsadmin.dbo.rds_read_error_log - IF LEFT(CAST(SERVERPROPERTY('ComputerNamePhysicalNetBIOS') AS VARCHAR(8000)), 8) = 'EC2AMAZ-' - AND LEFT(CAST(SERVERPROPERTY('MachineName') AS VARCHAR(8000)), 8) = 'EC2AMAZ-' - AND db_id('rdsadmin') IS NOT NULL - AND EXISTS(SELECT * FROM master.sys.all_objects WHERE name IN ('rds_startup_tasks', 'rds_help_revlogin', 'rds_hexadecimal', 'rds_failover_tracking', 'rds_database_tracking', 'rds_track_change')) - BEGIN - INSERT INTO #ErrorLog - EXEC rdsadmin.dbo.rds_read_error_log 0, 1, N'Database Instant File Initialization: enabled'; - END - ELSE - BEGIN - BEGIN TRY - INSERT INTO #ErrorLog - EXEC sys.xp_readerrorlog 0, 1, N'Database Instant File Initialization: enabled'; - END TRY - BEGIN CATCH - IF @Debug IN (1, 2) RAISERROR('No permissions to execute xp_readerrorlog.', 0, 1) WITH NOWAIT; - END CATCH - END - - IF EXISTS - ( - SELECT 1/0 - FROM #ErrorLog - WHERE LEFT([Text], 45) = N'Database Instant File Initialization: enabled' - ) - BEGIN - INSERT INTO #BlitzResults - ( CheckID , - [Priority] , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT - 193 AS [CheckID] , - 250 AS [Priority] , - 'Server Info' AS [FindingsGroup] , - 'Instant File Initialization Enabled' AS [Finding] , - 'https://www.brentozar.com/go/instant' AS [URL] , - 'The service account has the Perform Volume Maintenance Tasks permission.'; - END; - else -- if version of sql server has instant_file_initialization_enabled column in dm_server_services, check that too - -- in the event the error log has been cycled and the startup messages are not in the current error log - begin - if EXISTS ( SELECT * - FROM sys.all_objects o - INNER JOIN sys.all_columns c ON o.object_id = c.object_id - WHERE o.name = 'dm_server_services' - AND c.name = 'instant_file_initialization_enabled' ) - begin - SET @StringToExecute = N' - INSERT INTO #BlitzResults - ( CheckID , - [Priority] , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT - 193 AS [CheckID] , - 250 AS [Priority] , - ''Server Info'' AS [FindingsGroup] , - ''Instant File Initialization Enabled'' AS [Finding] , - ''https://www.brentozar.com/go/instant'' AS [URL] , - ''The service account has the Perform Volume Maintenance Tasks permission.'' - where exists (select 1 FROM sys.dm_server_services - WHERE instant_file_initialization_enabled = ''Y'' - AND filename LIKE ''%sqlservr.exe%'') - OPTION (RECOMPILE);'; - EXEC(@StringToExecute); - end; - end; - END; + /* Performance - Instant File Initialization Not Enabled - Check 192 */ + /* Server Info - Instant File Initialization Enabled - Check 193 */ + IF NOT EXISTS ( SELECT 1/0 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 192 /* IFI disabled check disabled */ + ) OR NOT EXISTS + ( SELECT 1/0 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 193 /* IFI enabled check disabled */ + ) + BEGIN + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d] and CheckId [%d].', 0, 1, 192, 193) WITH NOWAIT; + + DECLARE @IFISetting varchar(1) = N'N' + ,@IFIReadDMVFailed bit = 0 + ,@IFIAllFailed bit = 0; + + BEGIN TRY + /* See if we can get the instant_file_initialization_enabled column from sys.dm_server_services */ + SET @StringToExecute = N' + SELECT @IFISetting = instant_file_initialization_enabled + FROM sys.dm_server_services + WHERE filename LIKE ''%sqlservr.exe%'' + OPTION (RECOMPILE);'; + + IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; + IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; + + EXEC dbo.sp_executesql + @StringToExecute + ,N'@IFISetting varchar(1) OUTPUT' + ,@IFISetting = @IFISetting OUTPUT + END TRY + BEGIN CATCH + /* We couldn't get the instant_file_initialization_enabled column from sys.dm_server_services, fall back to read error log */ + SET @IFIReadDMVFailed = 1; + END CATCH; + + IF @IFIReadDMVFailed = 1 + BEGIN + /* If this is Amazon RDS, we'll use the rdsadmin.dbo.rds_read_error_log */ + IF LEFT(CAST(SERVERPROPERTY('ComputerNamePhysicalNetBIOS') AS VARCHAR(8000)), 8) = 'EC2AMAZ-' + AND LEFT(CAST(SERVERPROPERTY('MachineName') AS VARCHAR(8000)), 8) = 'EC2AMAZ-' + AND db_id('rdsadmin') IS NOT NULL + AND EXISTS ( SELECT 1/0 + FROM master.sys.all_objects + WHERE name IN ('rds_startup_tasks', 'rds_help_revlogin', 'rds_hexadecimal', 'rds_failover_tracking', 'rds_database_tracking', 'rds_track_change') + ) + BEGIN + /* Amazon RDS detected, read rdsadmin.dbo.rds_read_error_log */ + INSERT INTO #ErrorLog + EXEC rdsadmin.dbo.rds_read_error_log 0, 1, N'Database Instant File Initialization: enabled'; + END + ELSE + BEGIN + /* Try to read the error log, this might fail due to permissions */ + BEGIN TRY + INSERT INTO #ErrorLog + EXEC sys.xp_readerrorlog 0, 1, N'Database Instant File Initialization: enabled'; + END TRY + BEGIN CATCH + IF @Debug IN (1, 2) RAISERROR('No permissions to execute xp_readerrorlog.', 0, 1) WITH NOWAIT; + SET @IFIAllFailed = 1; + END CATCH + END; + END; + + IF @IFIAllFailed = 0 + BEGIN + IF @IFIReadDMVFailed = 1 + /* We couldn't read the DMV so set the @IFISetting variable using the error log */ + BEGIN + IF EXISTS ( SELECT 1/0 + FROM #ErrorLog + WHERE LEFT([Text], 45) = N'Database Instant File Initialization: enabled' + ) + BEGIN + SET @IFISetting = 'Y'; + END + ELSE + BEGIN + SET @IFISetting = 'N'; + END; + END; + + IF NOT EXISTS ( SELECT 1/0 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 192 /* IFI disabled check disabled */ + ) AND @IFISetting = 'N' + BEGIN + INSERT INTO #BlitzResults + ( + CheckID , + [Priority] , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT + 192 AS [CheckID] , + 50 AS [Priority] , + 'Performance' AS [FindingsGroup] , + 'Instant File Initialization Not Enabled' AS [Finding] , + 'https://www.brentozar.com/go/instant' AS [URL] , + 'Consider enabling IFI for faster restores and data file growths.' AS [Details] + END; + + IF NOT EXISTS ( SELECT 1/0 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 193 /* IFI enabled check disabled */ + ) AND @IFISetting = 'Y' + BEGIN + INSERT INTO #BlitzResults + ( + CheckID , + [Priority] , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT + 193 AS [CheckID] , + 250 AS [Priority] , + 'Server Info' AS [FindingsGroup] , + 'Instant File Initialization Enabled' AS [Finding] , + 'https://www.brentozar.com/go/instant' AS [URL] , + 'The service account has the Perform Volume Maintenance Tasks permission.' AS [Details] + END; + END; + END; - /* Server Info - Instant File Initialization Not Enabled - Check 192 - SQL Server 2016 SP1 and newer */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 192 ) - AND EXISTS ( SELECT * - FROM sys.all_objects o - INNER JOIN sys.all_columns c ON o.object_id = c.object_id - WHERE o.name = 'dm_server_services' - AND c.name = 'instant_file_initialization_enabled' ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 192) WITH NOWAIT; - - SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) - SELECT 192 AS CheckID , - 50 AS Priority , - ''Server Info'' AS FindingsGroup , - ''Instant File Initialization Not Enabled'' AS Finding , - ''https://www.brentozar.com/go/instant'' AS URL , - ''Consider enabling IFI for faster restores and data file growths.'' - FROM sys.dm_server_services WHERE instant_file_initialization_enabled <> ''Y'' AND filename LIKE ''%sqlservr.exe%'' OPTION (RECOMPILE);'; - - IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; - IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; - - EXECUTE(@StringToExecute); - END; + /* End of checkId 192 */ + /* End of checkId 193 */ IF NOT EXISTS ( SELECT 1 FROM #SkipChecks From 87880bb4aacd1fd86ac68e6e24264645fa701ad7 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Thu, 12 Oct 2023 15:47:04 -0400 Subject: [PATCH 476/662] SqlServerVersions Adding new CUs for 2022, 2019, 2017. --- SqlServerVersions.sql | 3 +++ 1 file changed, 3 insertions(+) diff --git a/SqlServerVersions.sql b/SqlServerVersions.sql index ac7600d01..fdac63763 100644 --- a/SqlServerVersions.sql +++ b/SqlServerVersions.sql @@ -41,6 +41,7 @@ DELETE FROM dbo.SqlServerVersions; INSERT INTO dbo.SqlServerVersions (MajorVersionNumber, MinorVersionNumber, Branch, [Url], ReleaseDate, MainstreamSupportEndDate, ExtendedSupportEndDate, MajorVersionName, MinorVersionName) VALUES + (16, 4085, 'CU9', 'https://support.microsoft.com/en-us/help/5030731', '2023-10-12', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 9'), (16, 4075, 'CU8', 'https://support.microsoft.com/en-us/help/5029666', '2023-09-14', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 8'), (16, 4065, 'CU7', 'https://support.microsoft.com/en-us/help/5028743', '2023-08-10', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 7'), (16, 4055, 'CU6', 'https://support.microsoft.com/en-us/help/5027505', '2023-07-13', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 6'), @@ -51,6 +52,7 @@ VALUES (16, 4003, 'CU1', 'https://support.microsoft.com/en-us/help/5022375', '2023-02-16', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 1'), (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'), + (15, 4335, 'CU23', 'https://support.microsoft.com/kb/5030333', '2023-10-12', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 23'), (15, 4322, 'CU22', 'https://support.microsoft.com/kb/5027702', '2023-08-14', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 22'), (15, 4316, 'CU21', 'https://support.microsoft.com/kb/5025808', '2023-06-15', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 21'), (15, 4312, 'CU20', 'https://support.microsoft.com/kb/5024276', '2023-04-13', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 20'), @@ -78,6 +80,7 @@ VALUES (15, 4003, 'CU1', 'https://support.microsoft.com/en-us/help/4527376', '2020-01-07', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 1 '), (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 '), + (14, 3465, 'RTM CU31 GDR', 'https://support.microsoft.com/kb/5029376', '2023-10-12', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 31 GDR'), (14, 3460, 'RTM CU31 GDR', 'https://support.microsoft.com/kb/5021126', '2023-02-14', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 31 GDR'), (14, 3456, 'RTM CU31', 'https://support.microsoft.com/en-us/help/5016884', '2022-09-20', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 31'), (14, 3451, 'RTM CU30', 'https://support.microsoft.com/en-us/help/5013756', '2022-07-13', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 30'), From fe645e77974a103345b49b2cb1096a46947d743c Mon Sep 17 00:00:00 2001 From: Chad Baldwin Date: Sat, 14 Oct 2023 13:18:25 -0400 Subject: [PATCH 477/662] Add missing drop if exists statements for temp tables Added drop if exists statements for two temp tables in order to make the contents of sp_blitzindex easier to run ad-hoc. --- sp_BlitzIndex.sql | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index 0798e1d29..bb23c2ee1 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -2591,6 +2591,7 @@ SELECT FROM #IndexSanity; RAISERROR (N'Populate #PartitionCompressionInfo.',0,1) WITH NOWAIT; +IF OBJECT_ID('tempdb..#maps') IS NOT NULL DROP TABLE #maps; WITH maps AS ( @@ -2605,6 +2606,7 @@ SELECT * INTO #maps FROM maps; +IF OBJECT_ID('tempdb..#grps') IS NOT NULL DROP TABLE #grps; WITH grps AS ( From 1028ba6c3b563c673fb5d6163affe1a2edbf1475 Mon Sep 17 00:00:00 2001 From: Vlad Drumea <48413726+VladDBA@users.noreply.github.com> Date: Tue, 31 Oct 2023 19:47:07 +0200 Subject: [PATCH 478/662] Add files via upload Added help message --- sp_BlitzQueryStore.sql | 253 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 251 insertions(+), 2 deletions(-) diff --git a/sp_BlitzQueryStore.sql b/sp_BlitzQueryStore.sql index 6ff69f240..a5fa4abe0 100644 --- a/sp_BlitzQueryStore.sql +++ b/sp_BlitzQueryStore.sql @@ -118,8 +118,6 @@ IF (SELECT CONVERT(NVARCHAR(128), SERVERPROPERTY ('EDITION'))) <> 'SQL Azure' IF @Help = 1 BEGIN - - SELECT N'You have requested assistance. It will arrive as soon as humanly possible.' AS [Take four red capsules, help is on the way]; PRINT N' sp_BlitzQueryStore from http://FirstResponderKit.org @@ -163,6 +161,257 @@ IF @Help = 1 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. '; + /*Parameter info*/ + SELECT N'@Help' AS [Parameter Name] , + N'BIT' AS [Data Type] , + N'0' AS [Default Value], + N'Displays this help message.' AS [Parameter Description] + UNION ALL + SELECT N'@DatabaseName', + N'NVARCHAR(128)', + N'NULL', + N'The name of the database you want to check the query store for.' + UNION ALL + SELECT N'@Top', + N'INT', + N'3', + N'The number of records to retrieve and analyze from the query store. The following system views are used: query_store_query, query_context_settings, query_store_wait_stats, query_store_runtime_stats,query_store_plan.' + + UNION ALL + SELECT N'@StartDate', + N'DATETIME2(7)', + N'NULL', + N'Get query store info starting from this date. When not specified, sp_BlitzQueryStore gets info from the last 7 days' + UNION ALL + SELECT N'@EndDate', + N'DATETIME2(7)', + N'NULL', + N'Get query store info until this date. When not specified, sp_BlitzQueryStore gets info from the last 7 days' + UNION ALL + SELECT N'@MinimumExecutionCount', + N'INT', + N'NULL', + N'When a value is specified, sp_BlitzQueryStore gets info for queries where count_executions >= @MinimumExecutionCount' + UNION ALL + SELECT N'@DurationFilter', + N'DECIMAL(38,4)', + N'NULL', + N'Time unit - seconds. When a value is specified, sp_BlitzQueryStore gets info for queries where the average duration >= @DurationFilter' + UNION ALL + SELECT N'@StoredProcName', + N'NVARCHAR(128)', + N'NULL', + N'Get information for this specific stored procedure.' + UNION ALL + SELECT N'@Failed', + N'BIT', + N'0', + N'When set to 1, only information about failed queries is returned.' + UNION ALL + SELECT N'@PlanIdFilter', + N'INT', + N'NULL', + N'The ID of the plan you want to check for.' + UNION ALL + SELECT N'@QueryIdFilter', + N'INT', + N'NULL', + N'The ID of the query you want to check for.' + UNION ALL + SELECT N'@ExportToExcel', + N'BIT', + N'0', + N'When set to 1, prepares output for exporting to Excel. Newlines and additional whitespace are removed from query text and the execution plan is not displayed.' + UNION ALL + SELECT N'@HideSummary', + N'BIT', + N'0', + N'When set to 1, hides the findings summary result set.' + UNION ALL + SELECT N'@SkipXML', + N'BIT', + N'0', + N'When set to 1, missing_indexes, implicit_conversion_info, cached_execution_parameters, are not returned. Does not affect query_plan_xml' + UNION ALL + SELECT N'@Debug', + N'BIT', + N'0', + N'Setting this to 1 will print dynamic SQL and select data from all tables used.' + UNION ALL + SELECT N'@ExpertMode', + N'BIT', + N'0', + N'When set to 1, more checks are done. Examples: many to many merge joins, row goals, adaptive joins, stats info, bad scans and plan forcing, computed columns that reference scalar UDFs.' + UNION ALL + SELECT N'@VersionCheckMode', + N'BIT', + N'0', + N'Outputs the version number and date.' + + /* Column definitions */ + SELECT 'database_name' AS [Column Name], + 'NVARCHAR(258)' AS [Data Type], + 'The name of the database where the plan was encountered.' AS [Column Description] + UNION ALL + SELECT 'query_cost', + 'FLOAT', + 'The cost of the execution plan in query bucks.' + UNION ALL + SELECT 'plan_id', + 'BIGINT', + 'The ID of the plan from sys.query_store_plan.' + UNION ALL + SELECT 'query_id', + 'BIGINT', + 'The ID of the query from sys.query_store_query.' + UNION ALL + SELECT 'query_id_all_plan_ids', + 'VARCHAR(8000)', + 'Comma-separated list of all query plan IDs associated with this query.' + UNION ALL + SELECT 'query_sql_text', + 'NVARCHAR(MAX)', + 'The text of the query, as provided by the user/app. Includes whitespaces, hints and comments. Comments and spaces before and after the query text are ignored.' + UNION ALL + SELECT 'proc_or_function_name', + 'NVARCHAR(258)', + 'If the query is part of a function/stored procedure, you''ll see here the name of its parent object.' + UNION ALL + SELECT 'query_plan_xml', + ' XML', + 'The query plan. Click to display a graphical plan.' + UNION ALL + SELECT 'warnings', + 'VARCHAR(MAX)', + 'A list of individual warnings generated by this query.' + UNION ALL + SELECT 'pattern', + 'NVARCHAR(512)', + 'A list of performance related patterns identified for this query.' + UNION ALL + SELECT 'parameter_sniffing_symptoms', + 'NVARCHAR(4000)', + 'A list of all the identified symptoms that are usually indicators of parameter sniffing.' + UNION ALL + SELECT 'last_force_failure_reason_desc', + 'NVARCHAR(258)', + 'Reason why plan forcing failed. NONE if plan isn''t forced.' + UNION ALL + SELECT 'top_three_waits', + 'NVARCHAR(MAX)', + 'The top 3 wait types, and their times in milliseconds, recorded for this query.' + UNION ALL + SELECT 'missing_indexes', + 'XML', + 'Missing index recommendations retrieved from the query plan.' + UNION ALL + SELECT 'implicit_conversion_info', + 'XML', + 'Information about the implicit conversion warnings,if any, retrieved from the query plan.' + UNION ALL + SELECT 'cached_execution_parameters', + 'XML', + 'Names, data types, and values for the parameters used when the query plan was compiled.' + UNION ALL + SELECT 'count_executions ', + 'BIGINT', + 'The number of executions of this particular query.' + UNION ALL + SELECT 'count_compiles', + 'BIGINT', + 'The number of plan compilations for this particular query.' + UNION ALL + SELECT 'total_cpu_time', + 'BIGINT', + 'Total CPU time, reported in milliseconds, that was consumed by all executions of this query.' + UNION ALL + SELECT 'avg_cpu_time ', + 'BIGINT', + 'Average CPU time, reported in milliseconds, consumed by each execution of this query.' + UNION ALL + SELECT 'total_duration', + 'BIGINT', + 'Total elapsed time, reported in milliseconds, consumed by all executions of this query.' + UNION ALL + SELECT 'avg_duration', + 'BIGINT', + 'Average elapsed time, reported in milliseconds, consumed by each execution of this query.' + UNION ALL + SELECT 'total_logical_io_reads', + 'BIGINT', + 'Total logical reads, reported in MB, performed by this query.' + UNION ALL + SELECT 'avg_logical_io_reads', + 'BIGINT', + 'Average logical reads, reported in MB, performed by each execution of this query.' + UNION ALL + SELECT 'total_physical_io_reads', + 'BIGINT', + 'Total physical reads, reported in MB, performed by this query.' + UNION ALL + SELECT 'avg_physical_io_reads', + 'BIGINT', + 'Average physical reads, reported in MB, performed by each execution of this query.' + UNION ALL + SELECT 'total_logical_io_writes', + 'BIGINT', + 'Total logical writes, reported in MB, performed by this query.' + UNION ALL + SELECT 'avg_logical_io_writes', + 'BIGINT', + 'Average logical writes, reported in MB, performed by each execution of this query.' + UNION ALL + SELECT 'total_rowcount', + 'BIGINT', + 'Total number of rows returned for all executions of this query.' + UNION ALL + SELECT 'avg_rowcount', + 'BIGINT', + 'Average number of rows returned by each execution of this query.' + UNION ALL + SELECT 'total_query_max_used_memory', + 'DECIMAL(38,2)', + 'Total max memory grant, reported in MB, used by this query.' + UNION ALL + SELECT 'avg_query_max_used_memory', + 'DECIMAL(38,2)', + 'Average max memory grant, reported in MB, used by each execution of this query.' + UNION ALL + SELECT 'total_tempdb_space_used', + 'DECIMAL(38,2)', + 'Total tempdb space, reported in MB, used by this query.' + UNION ALL + SELECT 'avg_tempdb_space_used', + 'DECIMAL(38,2)', + 'Average tempdb space, reported in MB, used by each execution of this query.' + UNION ALL + SELECT 'total_log_bytes_used', + 'DECIMAL(38,2)', + 'Total number of bytes in the database log used by this query.' + UNION ALL + SELECT 'avg_log_bytes_used', + 'DECIMAL(38,2)', + 'Average number of bytes in the database log used by each execution of this query.' + UNION ALL + SELECT 'total_num_physical_io_reads', + 'DECIMAL(38,2)', + 'Total number of physical I/O reads performed by this query (expressed as a number of read I/O operations).' + UNION ALL + SELECT 'avg_num_physical_io_reads', + 'DECIMAL(38,2)', + 'Average number of physical I/O reads performed by each execution of this query (expressed as a number of read I/O operations).' + UNION ALL + SELECT 'first_execution_time', + 'DATETIME2', + 'First execution time for this query within the aggregation interval. This is the end time of the query execution.' + UNION ALL + SELECT 'last_execution_time', + 'DATETIME2', + 'Last execution time for this query within the aggregation interval. This is the end time of the query execution.' + UNION ALL + SELECT 'context_settings', + 'NVARCHAR(512)', + 'Contains information about context settings associated with this query.'; RETURN; END; From 103ea3f43ff8463b2c0e3bf0bcfa44ff6d3fb48b Mon Sep 17 00:00:00 2001 From: Henrik Staun Poulsen Date: Mon, 13 Nov 2023 13:20:56 +0000 Subject: [PATCH 479/662] added #h and #os tables --- sp_BlitzIndex.sql | 282 +++++++++++++++++++++++++++++++++++++++------- 1 file changed, 244 insertions(+), 38 deletions(-) diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index bb23c2ee1..26dd93b4e 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -38,7 +38,7 @@ ALTER PROCEDURE dbo.sp_BlitzIndex @SortOrder NVARCHAR(50) = NULL, /* Only affects @Mode = 2. */ @SortDirection NVARCHAR(4) = 'DESC', /* Only affects @Mode = 2. */ @Help TINYINT = 0, - @Debug BIT = 0, + @Debug BIT =0, @Version VARCHAR(30) = NULL OUTPUT, @VersionDate DATETIME = NULL OUTPUT, @VersionCheckMode BIT = 0 @@ -105,7 +105,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. '; -RETURN; +return; END; /* @Help = 1 */ DECLARE @ScriptVersionName NVARCHAR(50); @@ -253,9 +253,14 @@ IF OBJECT_ID('tempdb..#CheckConstraints') IS NOT NULL IF OBJECT_ID('tempdb..#FilteredIndexes') IS NOT NULL DROP TABLE #FilteredIndexes; - + IF OBJECT_ID('tempdb..#Ignore_Databases') IS NOT NULL DROP TABLE #Ignore_Databases + +IF OBJECT_ID('tempdb..#H') IS NOT NULL + DROP TABLE #H +IF OBJECT_ID('tempdb..#OS') IS NOT NULL + DROP TABLE #OS RAISERROR (N'Create temp tables.',0,1) WITH NOWAIT; CREATE TABLE #BlitzIndexResults @@ -1065,6 +1070,8 @@ FROM sys.databases ---------------------------------------- BEGIN TRY BEGIN + declare @d varchar(19) = convert(varchar(19), getdate(), 121); + RAISERROR (N'starting at %s',0,1, @d) WITH NOWAIT; --Validate SQL Server Version @@ -1160,7 +1167,7 @@ BEGIN TRY --insert columns for clustered indexes and heaps --collect info on identity columns for this one SET @dsql = N'/* sp_BlitzIndex */ - SET LOCK_TIMEOUT 1000; /* To fix locking bug in sys.identity_columns. See Github issue #2176. */ + --SET LOCK_TIMEOUT 10000; /* To fix locking bug in sys.identity_columns. See Github issue #2176. */ SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; SELECT ' + CAST(@DatabaseID AS NVARCHAR(16)) + ', s.name, @@ -1435,40 +1442,100 @@ BEGIN TRY --NOTE: If you want to use the newer syntax for 2012+, you'll have to change 2147483647 to 11 on line ~819 --This change was made because on a table with lots of paritions, the OUTER APPLY was crazy slow. - SET @dsql = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; +DROP TABLE if exists #h +create table #h +( + database_id smallint not null + , object_id int not null + , sname sysname NULL + , index_id int + , partition_number int + , partition_id bigint + , row_count bigint + , reserved_MB bigint + , reserved_LOB_MB bigint + , reserved_row_overflow_MB bigint + , lock_escalation_desc varchar(1000)/*?*/ + , data_compression_desc varchar(100)/*?*/ + , reserved_dictionary_MB bigint +) +drop TABLE if exists #os +create table #os +( + database_id smallint not null + , object_id int not null + , index_id int + , partition_number int + , hobt_id bigint + , leaf_insert_count bigint + , leaf_delete_count bigint + , leaf_update_count bigint + , leaf_ghost_count bigint + , nonleaf_insert_count bigint + , nonleaf_delete_count bigint + , nonleaf_update_count bigint + , leaf_allocation_count bigint + , nonleaf_allocation_count bigint + , leaf_page_merge_count bigint + , nonleaf_page_merge_count bigint + , range_scan_count bigint + , singleton_lookup_count bigint + , forwarded_fetch_count bigint + , lob_fetch_in_pages bigint + , lob_fetch_in_bytes bigint + , lob_orphan_create_count bigint + , lob_orphan_insert_count bigint + , row_overflow_fetch_in_pages bigint + , row_overflow_fetch_in_bytes bigint + , column_value_push_off_row_count bigint + , column_value_pull_in_row_count bigint + , row_lock_count bigint + , row_lock_wait_count bigint + , row_lock_wait_in_ms bigint + , page_lock_count bigint + , page_lock_wait_count bigint + , page_lock_wait_in_ms bigint + , index_lock_promotion_attempt_count bigint + , index_lock_promotion_count bigint + , page_latch_wait_count bigint + , page_latch_wait_in_ms bigint + , page_io_latch_wait_count bigint + , page_io_latch_wait_in_ms bigint + , tree_page_latch_wait_count bigint + , tree_page_latch_wait_in_ms bigint + , tree_page_io_latch_wait_count bigint + , tree_page_io_latch_wait_in_ms bigint + , page_compression_attempt_count bigint + , page_compression_success_count bigint + , version_generated_inrow bigint + , version_generated_offrow bigint + , ghost_version_inrow bigint + , ghost_version_offrow bigint + , insert_over_ghost_version_inrow bigint + , insert_over_ghost_version_offrow bigint + ) + + SET @dsql = N' + declare @d varchar(19) = convert(varchar(19), getdate(), 121) + RAISERROR (N''start getting data into #h at %s'',0,1, @d) WITH NOWAIT; + SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + insert into #h + ( + database_id, object_id, sname, index_id, partition_number, partition_id, row_count, reserved_MB, reserved_LOB_MB, reserved_row_overflow_MB, lock_escalation_desc, data_compression_desc, reserved_dictionary_MB + ) SELECT ' + CAST(@DatabaseID AS NVARCHAR(10)) + N' AS database_id, ps.object_id, - s.name, + s.name as sname, ps.index_id, ps.partition_number, + ps.partition_id, ps.row_count, ps.reserved_page_count * 8. / 1024. AS reserved_MB, ps.lob_reserved_page_count * 8. / 1024. AS reserved_LOB_MB, ps.row_overflow_reserved_page_count * 8. / 1024. AS reserved_row_overflow_MB, le.lock_escalation_desc, ' + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N'par.data_compression_desc ' ELSE N'null as data_compression_desc ' END + N', - SUM(os.leaf_insert_count), - SUM(os.leaf_delete_count), - SUM(os.leaf_update_count), - SUM(os.range_scan_count), - SUM(os.singleton_lookup_count), - SUM(os.forwarded_fetch_count), - SUM(os.lob_fetch_in_pages), - SUM(os.lob_fetch_in_bytes), - SUM(os.row_overflow_fetch_in_pages), - SUM(os.row_overflow_fetch_in_bytes), - SUM(os.row_lock_count), - SUM(os.row_lock_wait_count), - SUM(os.row_lock_wait_in_ms), - SUM(os.page_lock_count), - SUM(os.page_lock_wait_count), - SUM(os.page_lock_wait_in_ms), - SUM(os.index_lock_promotion_attempt_count), - SUM(os.index_lock_promotion_count), - SUM(os.page_latch_wait_count), - SUM(os.page_latch_wait_in_ms), - SUM(os.page_io_latch_wait_count), - SUM(os.page_io_latch_wait_in_ms), '; +'; /* Get columnstore dictionary size - more info: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2585 */ IF EXISTS (SELECT * FROM sys.all_objects WHERE name = 'column_store_dictionaries') @@ -1484,9 +1551,6 @@ BEGIN TRY AND so.is_ms_shipped = 0 /*Exclude objects shipped by Microsoft*/ AND so.type <> ''TF'' /*Exclude table valued functions*/ JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s ON s.schema_id = so.schema_id - LEFT JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_index_operational_stats(' - + CAST(@DatabaseID AS NVARCHAR(10)) + N', NULL, NULL,NULL) AS os ON - ps.object_id=os.object_id and ps.index_id=os.index_id and ps.partition_number=os.partition_number OUTER APPLY (SELECT st.lock_escalation_desc FROM ' + QUOTENAME(@DatabaseName) + N'.sys.tables st WHERE st.object_id = ps.object_id @@ -1506,7 +1570,123 @@ BEGIN TRY le.lock_escalation_desc, ' + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N'par.data_compression_desc ' ELSE N'null as data_compression_desc ' END + N' ORDER BY ps.object_id, ps.index_id, ps.partition_number - OPTION ( RECOMPILE ); + /*OPTION ( RECOMPILE );*/ + OPTION ( RECOMPILE , min_grant_percent = 1); + + set @d = convert(varchar(19), getdate(), 121) + RAISERROR (N''start getting data into #os at %s.'',0,1, @d) WITH NOWAIT; + + insert into #os + ( + database_id + , object_id + , index_id + , partition_number + , hobt_id + , leaf_insert_count + , leaf_delete_count + , leaf_update_count + , leaf_ghost_count + , nonleaf_insert_count + , nonleaf_delete_count + , nonleaf_update_count + , leaf_allocation_count + , nonleaf_allocation_count + , leaf_page_merge_count + , nonleaf_page_merge_count + , range_scan_count + , singleton_lookup_count + , forwarded_fetch_count + , lob_fetch_in_pages + , lob_fetch_in_bytes + , lob_orphan_create_count + , lob_orphan_insert_count + , row_overflow_fetch_in_pages + , row_overflow_fetch_in_bytes + , column_value_push_off_row_count + , column_value_pull_in_row_count + , row_lock_count + , row_lock_wait_count + , row_lock_wait_in_ms + , page_lock_count + , page_lock_wait_count + , page_lock_wait_in_ms + , index_lock_promotion_attempt_count + , index_lock_promotion_count + , page_latch_wait_count + , page_latch_wait_in_ms + , page_io_latch_wait_count + , page_io_latch_wait_in_ms + , tree_page_latch_wait_count + , tree_page_latch_wait_in_ms + , tree_page_io_latch_wait_count + , tree_page_io_latch_wait_in_ms + , page_compression_attempt_count + , page_compression_success_count + , version_generated_inrow + , version_generated_offrow + , ghost_version_inrow + , ghost_version_offrow + , insert_over_ghost_version_inrow + , insert_over_ghost_version_offrow + ) + + select os.database_id + , os.object_id + , os.index_id + , os.partition_number + , os.hobt_id + , os.leaf_insert_count + , os.leaf_delete_count + , os.leaf_update_count + , os.leaf_ghost_count + , os.nonleaf_insert_count + , os.nonleaf_delete_count + , os.nonleaf_update_count + , os.leaf_allocation_count + , os.nonleaf_allocation_count + , os.leaf_page_merge_count + , os.nonleaf_page_merge_count + , os.range_scan_count + , os.singleton_lookup_count + , os.forwarded_fetch_count + , os.lob_fetch_in_pages + , os.lob_fetch_in_bytes + , os.lob_orphan_create_count + , os.lob_orphan_insert_count + , os.row_overflow_fetch_in_pages + , os.row_overflow_fetch_in_bytes + , os.column_value_push_off_row_count + , os.column_value_pull_in_row_count + , os.row_lock_count + , os.row_lock_wait_count + , os.row_lock_wait_in_ms + , os.page_lock_count + , os.page_lock_wait_count + , os.page_lock_wait_in_ms + , os.index_lock_promotion_attempt_count + , os.index_lock_promotion_count + , os.page_latch_wait_count + , os.page_latch_wait_in_ms + , os.page_io_latch_wait_count + , os.page_io_latch_wait_in_ms + , os.tree_page_latch_wait_count + , os.tree_page_latch_wait_in_ms + , os.tree_page_io_latch_wait_count + , os.tree_page_io_latch_wait_in_ms + , os.page_compression_attempt_count + , os.page_compression_success_count + , os.version_generated_inrow + , os.version_generated_offrow + , os.ghost_version_inrow + , os.ghost_version_offrow + , os.insert_over_ghost_version_inrow + , os.insert_over_ghost_version_offrow + from ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_index_operational_stats('+ CAST(@DatabaseID AS NVARCHAR(10)) +', NULL, NULL,NULL) AS os + OPTION ( RECOMPILE , min_grant_percent = 1); + + set @d = convert(varchar(19), getdate(), 121) + RAISERROR (N''finished getting data into #os at %s.'',0,1, @d) WITH NOWAIT; '; END; ELSE @@ -1605,6 +1785,7 @@ BEGIN TRY PRINT SUBSTRING(@dsql, 32000, 36000); PRINT SUBSTRING(@dsql, 36000, 40000); END; + EXEC sp_executesql @dsql; INSERT #IndexPartitionSanity ( [database_id], [object_id], [schema_name], @@ -1639,8 +1820,35 @@ BEGIN TRY page_io_latch_wait_count, page_io_latch_wait_in_ms, reserved_dictionary_MB) - EXEC sp_executesql @dsql; - + select h.database_id, h.object_id, h.sname, h.index_id, h.partition_number, h.row_count, h.reserved_MB, h.reserved_LOB_MB, h.reserved_row_overflow_MB, h.lock_escalation_desc, h.data_compression_desc, + SUM(os.leaf_insert_count), + SUM(os.leaf_delete_count), + SUM(os.leaf_update_count), + SUM(os.range_scan_count), + SUM(os.singleton_lookup_count), + SUM(os.forwarded_fetch_count), + SUM(os.lob_fetch_in_pages), + SUM(os.lob_fetch_in_bytes), + SUM(os.row_overflow_fetch_in_pages), + SUM(os.row_overflow_fetch_in_bytes), + SUM(os.row_lock_count), + SUM(os.row_lock_wait_count), + SUM(os.row_lock_wait_in_ms), + SUM(os.page_lock_count), + SUM(os.page_lock_wait_count), + SUM(os.page_lock_wait_in_ms), + SUM(os.index_lock_promotion_attempt_count), + SUM(os.index_lock_promotion_count), + SUM(os.page_latch_wait_count), + SUM(os.page_latch_wait_in_ms), + SUM(os.page_io_latch_wait_count), + SUM(os.page_io_latch_wait_in_ms) + ,COALESCE((SELECT SUM (dict.on_disk_size / 1024.0 / 1024) FROM [DataWarehouse].sys.column_store_dictionaries dict WHERE dict.partition_id = h.partition_id),0) AS reserved_dictionary_MB + from #h h + left JOIN #os as os ON + h.object_id=os.object_id and h.index_id=os.index_id and h.partition_number=os.partition_number + group by h.database_id, h.object_id, h.sname, h.index_id, h.partition_number, h.partition_id, h.row_count, h.reserved_MB, h.reserved_LOB_MB, h.reserved_row_overflow_MB, h.lock_escalation_desc, h.data_compression_desc + END; --End Check For @SkipPartitions = 0 @@ -2591,7 +2799,6 @@ SELECT FROM #IndexSanity; RAISERROR (N'Populate #PartitionCompressionInfo.',0,1) WITH NOWAIT; -IF OBJECT_ID('tempdb..#maps') IS NOT NULL DROP TABLE #maps; WITH maps AS ( @@ -2606,7 +2813,6 @@ SELECT * INTO #maps FROM maps; -IF OBJECT_ID('tempdb..#grps') IS NOT NULL DROP TABLE #grps; WITH grps AS ( @@ -6178,7 +6384,8 @@ BEGIN END; /* End @Mode=3 (index detail)*/ - + set @d = convert(varchar(19), getdate(), 121); + RAISERROR (N'finishing at %s',0,1, @d) WITH NOWAIT; END /* End @TableName IS NULL (mode 0/1/2/3/4) */ END TRY @@ -6197,4 +6404,3 @@ BEGIN CATCH RETURN; END CATCH; -GO From 3eee3ff94778741e0bd7adeabcdc26edfbc3b838 Mon Sep 17 00:00:00 2001 From: Henrik Staun Poulsen Date: Mon, 13 Nov 2023 14:55:02 +0100 Subject: [PATCH 480/662] Update sp_BlitzIndex.sql with #h and #os Update sp_BlitzIndex.sql with #h and #os to improve speed --- sp_BlitzIndex.sql | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index 26dd93b4e..90528e8c9 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -38,7 +38,7 @@ ALTER PROCEDURE dbo.sp_BlitzIndex @SortOrder NVARCHAR(50) = NULL, /* Only affects @Mode = 2. */ @SortDirection NVARCHAR(4) = 'DESC', /* Only affects @Mode = 2. */ @Help TINYINT = 0, - @Debug BIT =0, + @Debug BIT = 0, @Version VARCHAR(30) = NULL OUTPUT, @VersionDate DATETIME = NULL OUTPUT, @VersionCheckMode BIT = 0 @@ -105,7 +105,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. '; -return; +RETURN; END; /* @Help = 1 */ DECLARE @ScriptVersionName NVARCHAR(50); @@ -1070,7 +1070,7 @@ FROM sys.databases ---------------------------------------- BEGIN TRY BEGIN - declare @d varchar(19) = convert(varchar(19), getdate(), 121); + DECLARE @d VARCHAR(19) = CONVERT(VARCHAR(19), GETDATE(), 121); RAISERROR (N'starting at %s',0,1, @d) WITH NOWAIT; --Validate SQL Server Version @@ -1167,7 +1167,7 @@ BEGIN TRY --insert columns for clustered indexes and heaps --collect info on identity columns for this one SET @dsql = N'/* sp_BlitzIndex */ - --SET LOCK_TIMEOUT 10000; /* To fix locking bug in sys.identity_columns. See Github issue #2176. */ + SET LOCK_TIMEOUT 1000; /* To fix locking bug in sys.identity_columns. See Github issue #2176. */ SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; SELECT ' + CAST(@DatabaseID AS NVARCHAR(16)) + ', s.name, @@ -1516,10 +1516,10 @@ create table #os ) SET @dsql = N' - declare @d varchar(19) = convert(varchar(19), getdate(), 121) + DECLARE @d VARCHAR(19) = CONVERT(VARCHAR(19), GETDATE(), 121) RAISERROR (N''start getting data into #h at %s'',0,1, @d) WITH NOWAIT; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - insert into #h + INSERT INTO #h ( database_id, object_id, sname, index_id, partition_number, partition_id, row_count, reserved_MB, reserved_LOB_MB, reserved_row_overflow_MB, lock_escalation_desc, data_compression_desc, reserved_dictionary_MB ) @@ -1573,7 +1573,7 @@ create table #os /*OPTION ( RECOMPILE );*/ OPTION ( RECOMPILE , min_grant_percent = 1); - set @d = convert(varchar(19), getdate(), 121) + SET @d = CONVERT(VARCHAR(19), GETDATE(), 121) RAISERROR (N''start getting data into #os at %s.'',0,1, @d) WITH NOWAIT; insert into #os @@ -1685,7 +1685,7 @@ create table #os from ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_index_operational_stats('+ CAST(@DatabaseID AS NVARCHAR(10)) +', NULL, NULL,NULL) AS os OPTION ( RECOMPILE , min_grant_percent = 1); - set @d = convert(varchar(19), getdate(), 121) + SET @d = CONVERT(VARCHAR(19), GETDATE(), 121) RAISERROR (N''finished getting data into #os at %s.'',0,1, @d) WITH NOWAIT; '; END; @@ -2799,6 +2799,7 @@ SELECT FROM #IndexSanity; RAISERROR (N'Populate #PartitionCompressionInfo.',0,1) WITH NOWAIT; +IF OBJECT_ID('tempdb..#maps') IS NOT NULL DROP TABLE #maps; WITH maps AS ( @@ -2813,6 +2814,7 @@ SELECT * INTO #maps FROM maps; +IF OBJECT_ID('tempdb..#grps') IS NOT NULL DROP TABLE #grps; WITH grps AS ( @@ -6384,7 +6386,7 @@ BEGIN END; /* End @Mode=3 (index detail)*/ - set @d = convert(varchar(19), getdate(), 121); + SET @d = CONVERT(VARCHAR(19), GETDATE(), 121); RAISERROR (N'finishing at %s',0,1, @d) WITH NOWAIT; END /* End @TableName IS NULL (mode 0/1/2/3/4) */ END TRY @@ -6404,3 +6406,4 @@ BEGIN CATCH RETURN; END CATCH; +GO From e9887f5cd5ec7d4e8c7501bb11d84f7107dddeaf Mon Sep 17 00:00:00 2001 From: Henrik Staun Poulsen Date: Mon, 13 Nov 2023 21:49:29 +0100 Subject: [PATCH 481/662] removed hardcoded db name --- sp_BlitzIndex.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index 90528e8c9..e84f3f46a 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -1843,11 +1843,11 @@ create table #os SUM(os.page_latch_wait_in_ms), SUM(os.page_io_latch_wait_count), SUM(os.page_io_latch_wait_in_ms) - ,COALESCE((SELECT SUM (dict.on_disk_size / 1024.0 / 1024) FROM [DataWarehouse].sys.column_store_dictionaries dict WHERE dict.partition_id = h.partition_id),0) AS reserved_dictionary_MB + , h.reserved_dictionary_MB from #h h left JOIN #os as os ON h.object_id=os.object_id and h.index_id=os.index_id and h.partition_number=os.partition_number - group by h.database_id, h.object_id, h.sname, h.index_id, h.partition_number, h.partition_id, h.row_count, h.reserved_MB, h.reserved_LOB_MB, h.reserved_row_overflow_MB, h.lock_escalation_desc, h.data_compression_desc + group by h.database_id, h.object_id, h.sname, h.index_id, h.partition_number, h.partition_id, h.row_count, h.reserved_MB, h.reserved_LOB_MB, h.reserved_row_overflow_MB, h.lock_escalation_desc, h.data_compression_desc, h.reserved_dictionary_MB END; --End Check For @SkipPartitions = 0 From 78596fce43034bf52010060d77ebfb406de1fc1a Mon Sep 17 00:00:00 2001 From: Martin van der Boom Date: Tue, 14 Nov 2023 09:22:02 +0100 Subject: [PATCH 482/662] #3377 simplified the msdb jobs check --- sp_Blitz.sql | 89 +++++++--------------------------------------------- 1 file changed, 12 insertions(+), 77 deletions(-) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index 3d921a2f3..7a1d6f5aa 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -198,8 +198,6 @@ AS ,@SkipMaster bit = 0 ,@SkipMSDB_objs bit = 0 ,@SkipMSDB_jobs bit = 0 - ,@SkipMSDB_alerts bit = 0 - ,@SkipMSDB_operators bit = 0 ,@SkipModel bit = 0 ,@SkipTempDB bit = 0 ,@SkipValidateLogins bit = 0 @@ -439,64 +437,6 @@ AS SET @SkipMSDB_jobs = 1; /*We don't have read permissions in the msdb database*/ END; END; - - IF ISNULL(@SkipMSDB_alerts, 0) != 1 /*If @SkipMSDB_alerts hasn't been set to 1 by the caller*/ - BEGIN - IF EXISTS - ( - SELECT 1/0 - FROM @db_perms - WHERE database_name = N'msdb' - ) - BEGIN - BEGIN TRY - IF EXISTS - ( - SELECT 1/0 - FROM msdb.dbo.sysalerts - ) - BEGIN - SET @SkipMSDB_alerts = 0; /*We have read permissions in the msdb database, and can view the objects*/ - END; - END TRY - BEGIN CATCH - SET @SkipMSDB_alerts = 1; /*We have read permissions in the msdb database ... oh wait we got tricked, we can't view the objects*/ - END CATCH; - END; - ELSE - BEGIN - SET @SkipMSDB_alerts = 1; /*We don't have read permissions in the msdb database*/ - END; - END; - - IF ISNULL(@SkipMSDB_operators, 0) != 1 /*If @SkipMSDB_operators hasn't been set to 1 by the caller*/ - BEGIN - IF EXISTS - ( - SELECT 1/0 - FROM @db_perms - WHERE database_name = N'msdb' - ) - BEGIN - BEGIN TRY - IF EXISTS - ( - SELECT 1/0 - FROM msdb.dbo.sysoperators - ) - BEGIN - SET @SkipMSDB_operators = 0; /*We have read permissions in the msdb database, and can view the objects*/ - END; - END TRY - BEGIN CATCH - SET @SkipMSDB_operators = 1; /*We have read permissions in the msdb database ... oh wait we got tricked, we can't view the objects*/ - END CATCH; - END; - ELSE - BEGIN - SET @SkipMSDB_operators = 1; /*We don't have read permissions in the msdb database*/ - END; - END; END; SET @crlf = NCHAR(13) + NCHAR(10); @@ -672,33 +612,28 @@ AS INSERT #SkipChecks (DatabaseName, CheckID, ServerName) SELECT v.* - FROM (VALUES(NULL, 6, NULL), /*Jobs Owned By Users*/ + FROM (VALUES + /*sysjobs checks*/ + (NULL, 6, NULL), /*Jobs Owned By Users*/ (NULL, 57, NULL), /*SQL Agent Job Runs at Startup*/ (NULL, 79, NULL), /*Shrink Database Job*/ (NULL, 94, NULL), /*Agent Jobs Without Failure Emails*/ (NULL, 123, NULL), /*Agent Jobs Starting Simultaneously*/ (NULL, 180, NULL), /*Shrink Database Step In Maintenance Plan*/ - (NULL, 181, NULL) /*Repetitive Maintenance Tasks*/ - ) AS v (DatabaseName, CheckID, ServerName) - WHERE @SkipMSDB_jobs = 1; - - INSERT #SkipChecks (DatabaseName, CheckID, ServerName) - SELECT - v.* - FROM (VALUES(NULL, 30, NULL), /*Not All Alerts Configured*/ + (NULL, 181, NULL), /*Repetitive Maintenance Tasks*/ + + /*sysalerts checks*/ + (NULL, 30, NULL), /*Not All Alerts Configured*/ (NULL, 59, NULL), /*Alerts Configured without Follow Up*/ (NULL, 61, NULL), /*No Alerts for Sev 19-25*/ (NULL, 96, NULL), /*No Alerts for Corruption*/ (NULL, 98, NULL), /*Alerts Disabled*/ - (NULL, 219, NULL) /*Alerts Without Event Descriptions*/ - ) AS v (DatabaseName, CheckID, ServerName) - WHERE @SkipMSDB_alerts = 1; + (NULL, 219, NULL), /*Alerts Without Event Descriptions*/ - INSERT #SkipChecks (DatabaseName, CheckID, ServerName) - SELECT - v.* - FROM (VALUES(NULL, 31, NULL)) AS v (DatabaseName, CheckID, ServerName) /*No Operators Configured/Enabled*/ - WHERE @SkipMSDB_operators = 1; + /*sysoperators*/ + (NULL, 31, NULL) /*No Operators Configured/Enabled*/ + ) AS v (DatabaseName, CheckID, ServerName) + WHERE @SkipMSDB_jobs = 1; INSERT #SkipChecks (DatabaseName, CheckID, ServerName) SELECT From 019d4e4f450a55774c83afae047ebc5119b7c0d3 Mon Sep 17 00:00:00 2001 From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com> Date: Mon, 20 Nov 2023 11:53:29 -0500 Subject: [PATCH 483/662] Fix date math and improve MI compatibility This pull request looks a little funny, because when I re-synced my fork, the available plans query was all beat up for some reason, and I had to paste in the version from the current master branch. Anyway, the two actual changes in this PR: * Closes #3385 * Closes #3392 Since I can't repro #3385, I'm making the smallest change possible that fixes the issue with mocked-up test data. It will need some community testing to validate. For #3392 I added a local variable to detect MI, and then set the TargetSessionType to ring_buffer if it's NULL, and we're looking at the system_health extended event. It might be a good idea to have this happen for any session on MI, but I'm going to keep it limited at first. --- sp_BlitzLock.sql | 137 ++++++++++++++++++++++++++++------------------- 1 file changed, 81 insertions(+), 56 deletions(-) diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index bb40b88c7..0420e757b 100644 --- a/sp_BlitzLock.sql +++ b/sp_BlitzLock.sql @@ -159,6 +159,20 @@ BEGIN THEN 1 ELSE 0 END, + @MI bit = + CASE + WHEN + ( + SELECT + CONVERT + ( + integer, + SERVERPROPERTY('EngineEdition') + ) + ) = 8 + THEN 1 + ELSE 0 + END, @RDS bit = CASE WHEN LEFT(CAST(SERVERPROPERTY('ComputerNamePhysicalNetBIOS') AS varchar(8000)), 8) <> 'EC2AMAZ-' @@ -297,6 +311,17 @@ BEGIN @StartDateUTC = @StartDate, @EndDateUTC = @EndDate; + IF + ( + @MI = 1 + AND @EventSessionName = N'system_health' + AND @TargetSessionType IS NULL + ) + BEGIN + SET + @TargetSessionType = N'ring_buffer'; + END; + IF @Azure = 0 BEGIN IF NOT EXISTS @@ -314,7 +339,7 @@ BEGIN RETURN; END; END; - + IF @Azure = 1 BEGIN IF NOT EXISTS @@ -1844,7 +1869,7 @@ BEGIN COUNT_BIG(DISTINCT dp.event_date) ) + N' deadlocks.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT dp.event_date) DESC) FROM #deadlock_process AS dp @@ -1899,7 +1924,7 @@ BEGIN COUNT_BIG(DISTINCT dow.event_date) ) + N' deadlock(s) between read queries and modification queries.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT dow.event_date) DESC) FROM #deadlock_owner_waiter AS dow @@ -1961,7 +1986,7 @@ BEGIN COUNT_BIG(DISTINCT dow.event_date) ) + N' deadlock(s).', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT dow.event_date) DESC) FROM #deadlock_owner_waiter AS dow @@ -2004,7 +2029,7 @@ BEGIN COUNT_BIG(DISTINCT dow.event_date) ) + N' deadlock(s).', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT dow.event_date) DESC) FROM #deadlock_owner_waiter AS dow @@ -2053,7 +2078,7 @@ BEGIN COUNT_BIG(DISTINCT dow.event_date) ) + N' deadlock(s).', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT dow.event_date) DESC) FROM #deadlock_owner_waiter AS dow @@ -2102,7 +2127,7 @@ BEGIN COUNT_BIG(DISTINCT dp.event_date) ) + N' instances of Serializable deadlocks.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT dp.event_date) DESC) FROM #deadlock_process AS dp @@ -2145,7 +2170,7 @@ BEGIN COUNT_BIG(DISTINCT dp.event_date) ) + N' instances of Repeatable Read deadlocks.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT dp.event_date) DESC) FROM #deadlock_process AS dp @@ -2207,7 +2232,7 @@ BEGIN N'UNKNOWN' ) + N'.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT dp.event_date) DESC) FROM #deadlock_process AS dp @@ -2319,7 +2344,7 @@ BEGIN 1, N'' ) + N' locks.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY CONVERT(bigint, lt.lock_count) DESC) FROM lock_types AS lt @@ -2498,7 +2523,7 @@ BEGIN COUNT_BIG(DISTINCT ds.id) ) + N' deadlocks.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT ds.id) DESC) FROM #deadlock_stack AS ds @@ -2599,19 +2624,19 @@ BEGIN ) ), wait_time_hms = - /*the more wait time you rack up the less accurate this gets, + /*the more wait time you rack up the less accurate this gets, it's either that or erroring out*/ - CASE - WHEN + CASE + WHEN SUM ( CONVERT ( - bigint, + bigint, dp.wait_time ) )/1000 > 2147483647 - THEN + THEN CONVERT ( nvarchar(30), @@ -2624,7 +2649,7 @@ BEGIN ( CONVERT ( - bigint, + bigint, dp.wait_time ) ) @@ -2635,16 +2660,16 @@ BEGIN ), 14 ) - WHEN + WHEN SUM ( CONVERT ( - bigint, + bigint, dp.wait_time ) ) BETWEEN 2147483648 AND 2147483647000 - THEN + THEN CONVERT ( nvarchar(30), @@ -2657,7 +2682,7 @@ BEGIN ( CONVERT ( - bigint, + bigint, dp.wait_time ) ) @@ -2739,7 +2764,7 @@ BEGIN 14 ) + N' [dd hh:mm:ss:ms] of deadlock wait time.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY cs.total_waits DESC) FROM chopsuey AS cs @@ -2813,19 +2838,19 @@ BEGIN ) ) + N' ' + - /*the more wait time you rack up the less accurate this gets, + /*the more wait time you rack up the less accurate this gets, it's either that or erroring out*/ - CASE - WHEN + CASE + WHEN SUM ( CONVERT ( - bigint, + bigint, wt.total_wait_time_ms ) )/1000 > 2147483647 - THEN + THEN CONVERT ( nvarchar(30), @@ -2838,7 +2863,7 @@ BEGIN ( CONVERT ( - bigint, + bigint, wt.total_wait_time_ms ) ) @@ -2849,16 +2874,16 @@ BEGIN ), 14 ) - WHEN + WHEN SUM ( CONVERT ( - bigint, + bigint, wt.total_wait_time_ms ) ) BETWEEN 2147483648 AND 2147483647000 - THEN + THEN CONVERT ( nvarchar(30), @@ -2871,7 +2896,7 @@ BEGIN ( CONVERT ( - bigint, + bigint, wt.total_wait_time_ms ) ) @@ -2904,7 +2929,7 @@ BEGIN 14 ) END + N' [dd hh:mm:ss:ms] of deadlock wait time.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY SUM(CONVERT(bigint, wt.total_wait_time_ms)) DESC) FROM wait_time AS wt @@ -2942,7 +2967,7 @@ BEGIN N'There have been ' + RTRIM(COUNT_BIG(DISTINCT aj.event_date)) + N' deadlocks from this Agent Job and Step.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT aj.event_date) DESC) FROM #agent_job AS aj @@ -3722,23 +3747,23 @@ BEGIN BEGIN SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Results to client %s', 0, 1, @d) WITH NOWAIT; - + IF @Debug = 1 BEGIN SET STATISTICS XML ON; END; - + EXEC sys.sp_executesql @deadlock_result; - + IF @Debug = 1 BEGIN SET STATISTICS XML OFF; PRINT @deadlock_result; END; - + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Getting available execution plans for deadlocks %s', 0, 1, @d) WITH NOWAIT; - + SELECT DISTINCT available_plans = 'available_plans', @@ -3786,7 +3811,7 @@ BEGIN ( SECOND, deqs.creation_time, - deqs.last_execution_time + NULLIF(deqs.last_execution_time, '1900-01-01 00:00:00.000') ), 0 ), @@ -3805,7 +3830,7 @@ BEGIN min_used_grant_mb = deqs.min_used_grant_kb * 8. / 1024., max_used_grant_mb = - deqs.max_used_grant_kb * 8. / 1024., + deqs.max_used_grant_kb * 8. / 1024., deqs.min_reserved_threads, deqs.max_reserved_threads, deqs.min_used_threads, @@ -3815,21 +3840,21 @@ BEGIN FROM sys.dm_exec_query_stats AS deqs WHERE EXISTS ( - SELECT - 1/0 - FROM #available_plans AS ap - WHERE ap.sql_handle = deqs.sql_handle + SELECT + 1/0 + FROM #available_plans AS ap + WHERE ap.sql_handle = deqs.sql_handle ) AND deqs.query_hash IS NOT NULL; - - CREATE CLUSTERED INDEX - deqs + + CREATE CLUSTERED INDEX + deqs ON #dm_exec_query_stats ( - sql_handle, + sql_handle, plan_handle ); - + SELECT ap.available_plans, ap.database_name, @@ -3843,7 +3868,7 @@ BEGIN ap.total_worker_time_ms, ap.avg_worker_time_ms, ap.total_elapsed_time_ms, - ap.avg_elapsed_time, + ap.avg_elapsed_time_ms, ap.total_logical_reads_mb, ap.total_physical_reads_mb, ap.total_logical_writes_mb, @@ -3861,7 +3886,7 @@ BEGIN ap.statement_end_offset FROM ( - + SELECT ap.*, c.statement_start_offset, @@ -3872,7 +3897,7 @@ BEGIN c.total_worker_time_ms, c.avg_worker_time_ms, c.total_elapsed_time_ms, - c.avg_elapsed_time, + c.avg_elapsed_time_ms, c.executions_per_second, c.total_physical_reads_mb, c.total_logical_writes_mb, @@ -3911,10 +3936,10 @@ BEGIN OPTION(RECOMPILE, LOOP JOIN, HASH JOIN); RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - + SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Returning findings %s', 0, 1, @d) WITH NOWAIT; - + SELECT df.check_id, df.database_name, @@ -3926,7 +3951,7 @@ BEGIN df.check_id, df.sort_order OPTION(RECOMPILE); - + SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; END; /*done with output to client app.*/ From cb0b70002088498912047de0b51c81029a020bd1 Mon Sep 17 00:00:00 2001 From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com> Date: Mon, 27 Nov 2023 12:43:17 -0500 Subject: [PATCH 484/662] Update sp_BlitzLock.sql Closes #3398 --- sp_BlitzLock.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index 0420e757b..4b2bc67ec 100644 --- a/sp_BlitzLock.sql +++ b/sp_BlitzLock.sql @@ -1909,7 +1909,7 @@ BEGIN SELECT 1/0 FROM sys.databases AS d - WHERE d.name = dow.database_name + WHERE d.name COLLATE DATABASE_DEFAULT = dow.database_name COLLATE DATABASE_DEFAULT AND d.is_read_committed_snapshot_on = 1 ) THEN N'You already enabled RCSI, but...' From 099f9f6bbbdbfa4c19aec14da7a15c419e574192 Mon Sep 17 00:00:00 2001 From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com> Date: Mon, 27 Nov 2023 12:45:48 -0500 Subject: [PATCH 485/662] Update sp_BlitzLock.sql For some reason the `_ms` I added before disappeared. Thanks, GitHub. --- sp_BlitzLock.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index 4b2bc67ec..342e708c4 100644 --- a/sp_BlitzLock.sql +++ b/sp_BlitzLock.sql @@ -3799,7 +3799,7 @@ BEGIN CONVERT(decimal(38, 6), deqs.total_worker_time / 1000. / deqs.execution_count), total_elapsed_time_ms = deqs.total_elapsed_time / 1000., - avg_elapsed_time = + avg_elapsed_time_ms = CONVERT(decimal(38, 6), deqs.total_elapsed_time / 1000. / deqs.execution_count), executions_per_second = ISNULL From 81af402e53fcb724eccf29e7a67f32128717b304 Mon Sep 17 00:00:00 2001 From: "MIKE-PC\\mikes" Date: Mon, 4 Dec 2023 08:33:51 -0500 Subject: [PATCH 486/662] #3401 sp_Blitz: Improve Markdown output alias and spacing --- sp_Blitz.sql | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index 08a6ca91c..bb80296cf 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -9839,20 +9839,22 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 FROM #BlitzResults WHERE Priority > 0 AND Priority < 255 AND FindingsGroup IS NOT NULL AND Finding IS NOT NULL AND FindingsGroup <> 'Security' /* Specifically excluding security checks for public exports */) - SELECT - CASE - WHEN r.Priority <> COALESCE(rPrior.Priority, 0) OR r.FindingsGroup <> rPrior.FindingsGroup THEN @crlf + N'**Priority ' + CAST(COALESCE(r.Priority,N'') AS NVARCHAR(5)) + N': ' + COALESCE(r.FindingsGroup,N'') + N'**:' + @crlf + @crlf - ELSE N'' - END - + CASE WHEN r.Finding <> COALESCE(rPrior.Finding,N'') AND r.Finding <> COALESCE(rNext.Finding,N'') THEN N'- ' + COALESCE(r.Finding,N'') + N' ' + COALESCE(r.DatabaseName, N'') + N' - ' + COALESCE(r.Details,N'') + @crlf - WHEN r.Finding <> COALESCE(rPrior.Finding,N'') AND r.Finding = rNext.Finding AND r.Details = rNext.Details THEN N'- ' + COALESCE(r.Finding,N'') + N' - ' + COALESCE(r.Details,N'') + @crlf + @crlf + N' * ' + COALESCE(r.DatabaseName, N'') + @crlf - WHEN r.Finding <> COALESCE(rPrior.Finding,N'') AND r.Finding = rNext.Finding THEN N'- ' + COALESCE(r.Finding,N'') + @crlf + CASE WHEN r.DatabaseName IS NULL THEN N'' ELSE N' * ' + COALESCE(r.DatabaseName,N'') END + CASE WHEN r.Details <> rPrior.Details THEN N' - ' + COALESCE(r.Details,N'') + @crlf ELSE '' END - ELSE CASE WHEN r.DatabaseName IS NULL THEN N'' ELSE N' * ' + COALESCE(r.DatabaseName,N'') END + CASE WHEN r.Details <> rPrior.Details THEN N' - ' + COALESCE(r.Details,N'') + @crlf ELSE N'' + @crlf END - END + @crlf - FROM Results r - LEFT OUTER JOIN Results rPrior ON r.rownum = rPrior.rownum + 1 - LEFT OUTER JOIN Results rNext ON r.rownum = rNext.rownum - 1 - ORDER BY r.rownum FOR XML PATH(N''); + SELECT + Markdown = CONVERT(XML, STUFF((SELECT + CASE + WHEN r.Priority <> COALESCE(rPrior.Priority, 0) OR r.FindingsGroup <> rPrior.FindingsGroup THEN @crlf + N'**Priority ' + CAST(COALESCE(r.Priority,N'') AS NVARCHAR(5)) + N': ' + COALESCE(r.FindingsGroup,N'') + N'**:' + @crlf + @crlf + ELSE N'' + END + + CASE WHEN r.Finding <> COALESCE(rPrior.Finding,N'') AND r.Finding <> COALESCE(rNext.Finding,N'') THEN N'- ' + COALESCE(r.Finding,N'') + N' ' + COALESCE(r.DatabaseName, N'') + N' - ' + COALESCE(r.Details,N'') + @crlf + WHEN r.Finding <> COALESCE(rPrior.Finding,N'') AND r.Finding = rNext.Finding AND r.Details = rNext.Details THEN N'- ' + COALESCE(r.Finding,N'') + N' - ' + COALESCE(r.Details,N'') + @crlf + @crlf + N' * ' + COALESCE(r.DatabaseName, N'') + @crlf + WHEN r.Finding <> COALESCE(rPrior.Finding,N'') AND r.Finding = rNext.Finding THEN N'- ' + COALESCE(r.Finding,N'') + @crlf + @crlf + CASE WHEN r.DatabaseName IS NULL THEN N'' ELSE N' * ' + COALESCE(r.DatabaseName,N'') END + CASE WHEN r.Details <> rPrior.Details THEN N' - ' + COALESCE(r.Details,N'') + @crlf ELSE '' END + ELSE CASE WHEN r.DatabaseName IS NULL THEN N'' ELSE N' * ' + COALESCE(r.DatabaseName,N'') END + CASE WHEN r.Details <> rPrior.Details THEN N' - ' + COALESCE(r.Details,N'') + @crlf ELSE N'' + @crlf END + END + @crlf + FROM Results r + LEFT OUTER JOIN Results rPrior ON r.rownum = rPrior.rownum + 1 + LEFT OUTER JOIN Results rNext ON r.rownum = rNext.rownum - 1 + ORDER BY r.rownum FOR XML PATH(N''), ROOT('Markdown'), TYPE).value('/Markdown[1]','VARCHAR(MAX)'), 1, 2, '') + + ''); END; ELSE IF @OutputType = 'XML' BEGIN From f2faab1289d67a58aed386431c3c622c2d845af3 Mon Sep 17 00:00:00 2001 From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com> Date: Fri, 8 Dec 2023 09:53:19 -0500 Subject: [PATCH 487/662] Fixes #3400 Fixes #3400 --- sp_BlitzLock.sql | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index 342e708c4..4885ad3ce 100644 --- a/sp_BlitzLock.sql +++ b/sp_BlitzLock.sql @@ -32,7 +32,8 @@ WITH RECOMPILE AS BEGIN SET STATISTICS XML OFF; - SET NOCOUNT, XACT_ABORT ON; + SET NOCOUNT ON; + SET XACT_ABORT OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; SELECT @Version = '8.17', @VersionDate = '20231010'; From 2ceb77d1b3eb8f19801f559e1faf5a81c993c4a5 Mon Sep 17 00:00:00 2001 From: Martin van der Boom Date: Thu, 14 Dec 2023 09:40:51 +0100 Subject: [PATCH 488/662] #3406 Fixed the Substring command to respect the command syntax --- sp_BlitzCache.sql | 20 ++++++++++---------- sp_BlitzIndex.sql | 20 ++++++++++---------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/sp_BlitzCache.sql b/sp_BlitzCache.sql index 5c7fb2dc0..6e092262f 100644 --- a/sp_BlitzCache.sql +++ b/sp_BlitzCache.sql @@ -7338,16 +7338,16 @@ END '; IF @Debug = 1 BEGIN - PRINT SUBSTRING(@StringToExecute, 0, 4000); - PRINT SUBSTRING(@StringToExecute, 4000, 8000); - PRINT SUBSTRING(@StringToExecute, 8000, 12000); - PRINT SUBSTRING(@StringToExecute, 12000, 16000); - PRINT SUBSTRING(@StringToExecute, 16000, 20000); - PRINT SUBSTRING(@StringToExecute, 20000, 24000); - PRINT SUBSTRING(@StringToExecute, 24000, 28000); - PRINT SUBSTRING(@StringToExecute, 28000, 32000); - PRINT SUBSTRING(@StringToExecute, 32000, 36000); - PRINT SUBSTRING(@StringToExecute, 36000, 40000); + PRINT SUBSTRING(@StringToExecute, 1, 4000); + PRINT SUBSTRING(@StringToExecute, 4001, 4000); + PRINT SUBSTRING(@StringToExecute, 8001, 4000); + PRINT SUBSTRING(@StringToExecute, 12001, 4000); + PRINT SUBSTRING(@StringToExecute, 16001, 4000); + PRINT SUBSTRING(@StringToExecute, 20001, 4000); + PRINT SUBSTRING(@StringToExecute, 24001, 4000); + PRINT SUBSTRING(@StringToExecute, 28001, 4000); + PRINT SUBSTRING(@StringToExecute, 32001, 4000); + PRINT SUBSTRING(@StringToExecute, 36001, 4000); END; EXEC sp_executesql @StringToExecute, N'@Top INT, @min_duration INT, @min_back INT, @CheckDateOverride DATETIMEOFFSET, @MinimumExecutionCount INT', @Top, @DurationFilter_i, @MinutesBack, @CheckDateOverride, @MinimumExecutionCount; diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index bb23c2ee1..2faaa3afa 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -1214,16 +1214,16 @@ BEGIN TRY RAISERROR (N'Inserting data into #IndexColumns for clustered indexes and heaps',0,1) WITH NOWAIT; IF @Debug = 1 BEGIN - PRINT SUBSTRING(@dsql, 0, 4000); - PRINT SUBSTRING(@dsql, 4000, 8000); - PRINT SUBSTRING(@dsql, 8000, 12000); - PRINT SUBSTRING(@dsql, 12000, 16000); - PRINT SUBSTRING(@dsql, 16000, 20000); - PRINT SUBSTRING(@dsql, 20000, 24000); - PRINT SUBSTRING(@dsql, 24000, 28000); - PRINT SUBSTRING(@dsql, 28000, 32000); - PRINT SUBSTRING(@dsql, 32000, 36000); - PRINT SUBSTRING(@dsql, 36000, 40000); + PRINT SUBSTRING(@dsql, 1, 4000); + PRINT SUBSTRING(@dsql, 4001, 4000); + PRINT SUBSTRING(@dsql, 8001, 4000); + PRINT SUBSTRING(@dsql, 12001, 4000); + PRINT SUBSTRING(@dsql, 16001, 4000); + PRINT SUBSTRING(@dsql, 20001, 4000); + PRINT SUBSTRING(@dsql, 24001, 4000); + PRINT SUBSTRING(@dsql, 28001, 4000); + PRINT SUBSTRING(@dsql, 32001, 4000); + PRINT SUBSTRING(@dsql, 36001, 4000); END; BEGIN TRY INSERT #IndexColumns ( database_id, [schema_name], [object_id], index_id, key_ordinal, is_included_column, is_descending_key, partition_ordinal, From ccd0873530df86f8312f4199f6218b03cd8487c5 Mon Sep 17 00:00:00 2001 From: DougTaft Date: Thu, 14 Dec 2023 13:34:59 -0700 Subject: [PATCH 489/662] Issue 3399 sp_ineachdb run only for writable copy of an AG database To achieve this I added a param, @is_ag_writeable_copy, bit with default value of 0. In the existing section, from Andy Mallon, for testing if an AG is present, I added an IF block to remove database that are not the writable copies in an AG if the above param is set to 1. modified: sp_ineachdb.sql --- sp_ineachdb.sql | 67 +++++++++++++++++++++++++++++++------------------ 1 file changed, 42 insertions(+), 25 deletions(-) diff --git a/sp_ineachdb.sql b/sp_ineachdb.sql index c7bddb69f..cc9707de6 100644 --- a/sp_ineachdb.sql +++ b/sp_ineachdb.sql @@ -4,31 +4,32 @@ GO ALTER PROCEDURE [dbo].[sp_ineachdb] -- mssqltips.com/sqlservertip/5694/execute-a-command-in-the-context-of-each-database-in-sql-server--part-2/ - @command nvarchar(max) = NULL, - @replace_character nchar(1) = N'?', - @print_dbname bit = 0, - @select_dbname bit = 0, - @print_command bit = 0, - @print_command_only bit = 0, - @suppress_quotename bit = 0, -- use with caution - @system_only bit = 0, - @user_only bit = 0, - @name_pattern nvarchar(300) = N'%', - @database_list nvarchar(max) = NULL, - @exclude_pattern nvarchar(300) = NULL, - @exclude_list nvarchar(max) = NULL, - @recovery_model_desc nvarchar(120) = NULL, - @compatibility_level tinyint = NULL, - @state_desc nvarchar(120) = N'ONLINE', - @is_read_only bit = 0, - @is_auto_close_on bit = NULL, - @is_auto_shrink_on bit = NULL, - @is_broker_enabled bit = NULL, - @user_access nvarchar(128) = NULL, - @Help BIT = 0, - @Version VARCHAR(30) = NULL OUTPUT, - @VersionDate DATETIME = NULL OUTPUT, - @VersionCheckMode BIT = 0 + @command nvarchar(max) = NULL, + @replace_character nchar(1) = N'?', + @print_dbname bit = 0, + @select_dbname bit = 0, + @print_command bit = 0, + @print_command_only bit = 0, + @suppress_quotename bit = 0, -- use with caution + @system_only bit = 0, + @user_only bit = 0, + @name_pattern nvarchar(300) = N'%', + @database_list nvarchar(max) = NULL, + @exclude_pattern nvarchar(300) = NULL, + @exclude_list nvarchar(max) = NULL, + @recovery_model_desc nvarchar(120) = NULL, + @compatibility_level tinyint = NULL, + @state_desc nvarchar(120) = N'ONLINE', + @is_read_only bit = 0, + @is_auto_close_on bit = NULL, + @is_auto_shrink_on bit = NULL, + @is_broker_enabled bit = NULL, + @user_access nvarchar(128) = NULL, + @Help bit = 0, + @Version varchar(30) = NULL OUTPUT, + @VersionDate datetime = NULL OUTPUT, + @VersionCheckMode bit = 0, + @is_ag_writeable_copy bit = 0 -- WITH EXECUTE AS OWNER – maybe not a great idea, depending on the security of your system AS BEGIN @@ -277,6 +278,22 @@ OPTION (MAXRECURSION 0); AND ar.secondary_role_allow_connections = 0 AND ags.primary_replica <> @ServerName ); + /* Remove databases which are not the writeable copies in an AG. */ + IF @is_ag_writeable_copy = 1 + BEGIN + DELETE dbs FROM #ineachdb AS dbs + WHERE EXISTS + ( + SELECT 1 FROM sys.dm_hadr_database_replica_states AS drs + INNER JOIN sys.availability_replicas AS ar + ON ar.replica_id = drs.replica_id + INNER JOIN sys.dm_hadr_availability_group_states AS ags + ON ags.group_id = ar.group_id + WHERE drs.database_id = dbs.id + AND drs.is_primary_replica <> 1 + AND ags.primary_replica <> @ServerName + ); + END END -- Well, if we deleted them all... From 1cc978d61471332bfbc679f8f93c8bd4d91e425c Mon Sep 17 00:00:00 2001 From: Martin van der Boom Date: Fri, 15 Dec 2023 12:06:57 +0100 Subject: [PATCH 490/662] #3409 Implemented a better check for the 'instant_file_initialization_enabled' column --- sp_Blitz.sql | 34 ++++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index 1752ae787..89494db4f 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -8721,13 +8721,20 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 ,@IFIReadDMVFailed bit = 0 ,@IFIAllFailed bit = 0; - BEGIN TRY - /* See if we can get the instant_file_initialization_enabled column from sys.dm_server_services */ - SET @StringToExecute = N' - SELECT @IFISetting = instant_file_initialization_enabled - FROM sys.dm_server_services - WHERE filename LIKE ''%sqlservr.exe%'' - OPTION (RECOMPILE);'; + /* See if we can get the instant_file_initialization_enabled column from sys.dm_server_services */ + IF EXISTS + ( + SELECT 1/0 + FROM sys.all_columns + WHERE [object_id] = OBJECT_ID(N'[sys].[dm_server_services]') + AND [name] = N'instant_file_initialization_enabled' + ) + BEGIN + /* This needs to be a "dynamic" SQL statement because if the 'instant_file_initialization_enabled' column doesn't exist the procedure might fail on a bind error */ + SET @StringToExecute = N'SELECT @IFISetting = instant_file_initialization_enabled' + + N'FROM sys.dm_server_services' + + N'WHERE filename LIKE ''%sqlservr.exe%''' + + N'OPTION (RECOMPILE);'; IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; @@ -8736,14 +8743,13 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 @StringToExecute ,N'@IFISetting varchar(1) OUTPUT' ,@IFISetting = @IFISetting OUTPUT - END TRY - BEGIN CATCH - /* We couldn't get the instant_file_initialization_enabled column from sys.dm_server_services, fall back to read error log */ - SET @IFIReadDMVFailed = 1; - END CATCH; - - IF @IFIReadDMVFailed = 1 + + SET @IFIReadDMVFailed = 0; + END + ELSE + /* We couldn't get the instant_file_initialization_enabled column from sys.dm_server_services, fall back to read error log */ BEGIN + SET @IFIReadDMVFailed = 1; /* If this is Amazon RDS, we'll use the rdsadmin.dbo.rds_read_error_log */ IF LEFT(CAST(SERVERPROPERTY('ComputerNamePhysicalNetBIOS') AS VARCHAR(8000)), 8) = 'EC2AMAZ-' AND LEFT(CAST(SERVERPROPERTY('MachineName') AS VARCHAR(8000)), 8) = 'EC2AMAZ-' From abdd7beba257e78c5e1ff9c613fdcc4668bfef4e Mon Sep 17 00:00:00 2001 From: Martin van der Boom Date: Fri, 15 Dec 2023 12:15:35 +0100 Subject: [PATCH 491/662] Cough bug cough --- sp_Blitz.sql | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index 89494db4f..473f4c65d 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -8731,9 +8731,9 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 ) BEGIN /* This needs to be a "dynamic" SQL statement because if the 'instant_file_initialization_enabled' column doesn't exist the procedure might fail on a bind error */ - SET @StringToExecute = N'SELECT @IFISetting = instant_file_initialization_enabled' + - N'FROM sys.dm_server_services' + - N'WHERE filename LIKE ''%sqlservr.exe%''' + + SET @StringToExecute = N'SELECT @IFISetting = instant_file_initialization_enabled' + @crlf + + N'FROM sys.dm_server_services' + @crlf + + N'WHERE filename LIKE ''%sqlservr.exe%''' + @crlf + N'OPTION (RECOMPILE);'; IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; From 4ca5f976e44b1394dd78a6b3865d435eca07a5d2 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Fri, 22 Dec 2023 09:59:05 -0800 Subject: [PATCH 492/662] 2023-12-22 release Bumping version numbers and dates, adding latest SQL Server CUs. --- Install-All-Scripts.sql | 976 +++++++++++++++++------- Install-Core-Blitz-No-Query-Store.sql | 646 +++++++++------- Install-Core-Blitz-With-Query-Store.sql | 901 +++++++++++++++------- SqlServerVersions.sql | 2 + sp_AllNightLog.sql | 2 +- sp_AllNightLog_Setup.sql | 2 +- sp_Blitz.sql | 2 +- sp_BlitzAnalysis.sql | 2 +- sp_BlitzBackups.sql | 2 +- sp_BlitzCache.sql | 2 +- sp_BlitzFirst.sql | 2 +- sp_BlitzInMemoryOLTP.sql | 2 +- sp_BlitzIndex.sql | 2 +- sp_BlitzLock.sql | 2 +- sp_BlitzQueryStore.sql | 2 +- sp_BlitzWho.sql | 2 +- sp_DatabaseRestore.sql | 2 +- sp_ineachdb.sql | 2 +- 18 files changed, 1718 insertions(+), 835 deletions(-) diff --git a/Install-All-Scripts.sql b/Install-All-Scripts.sql index cf1bc0e84..547300b1b 100644 --- a/Install-All-Scripts.sql +++ b/Install-All-Scripts.sql @@ -38,7 +38,7 @@ SET STATISTICS XML OFF; BEGIN; -SELECT @Version = '8.17', @VersionDate = '20231010'; +SELECT @Version = '8.18', @VersionDate = '20231222'; IF(@VersionCheckMode = 1) BEGIN @@ -1375,7 +1375,7 @@ SET STATISTICS XML OFF; BEGIN; -SELECT @Version = '8.17', @VersionDate = '20231010'; +SELECT @Version = '8.18', @VersionDate = '20231222'; IF(@VersionCheckMode = 1) BEGIN @@ -2900,7 +2900,7 @@ AS SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.17', @VersionDate = '20231010'; + SELECT @Version = '8.18', @VersionDate = '20231222'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -3058,14 +3058,11 @@ AS ,@SkipXPFixedDrives bit = 0 ,@SkipXPCMDShell bit = 0 ,@SkipMaster bit = 0 - ,@SkipMSDB bit = 0 + ,@SkipMSDB_objs bit = 0 + ,@SkipMSDB_jobs bit = 0 ,@SkipModel bit = 0 ,@SkipTempDB bit = 0 ,@SkipValidateLogins bit = 0 - /* Variables for check 211: */ - ,@powerScheme varchar(36) - ,@cpu_speed_mhz int - ,@cpu_speed_ghz decimal(18,2); DECLARE @db_perms table @@ -3140,38 +3137,6 @@ AS SET @SkipTrace = 1; END; /*We need this permission to execute trace stuff, apparently*/ - IF ISNULL(@SkipXPRegRead, 0) != 1 /*If @SkipXPRegRead hasn't been set to 1 by the caller*/ - BEGIN - BEGIN TRY - /* Get power plan if set by group policy [Git Hub Issue #1620] */ - EXEC xp_regread @rootkey = N'HKEY_LOCAL_MACHINE', - @key = N'SOFTWARE\Policies\Microsoft\Power\PowerSettings', - @value_name = N'ActivePowerScheme', - @value = @powerScheme OUTPUT, - @no_output = N'no_output'; - - IF @powerScheme IS NULL /* If power plan was not set by group policy, get local value [Git Hub Issue #1620]*/ - EXEC xp_regread @rootkey = N'HKEY_LOCAL_MACHINE', - @key = N'SYSTEM\CurrentControlSet\Control\Power\User\PowerSchemes', - @value_name = N'ActivePowerScheme', - @value = @powerScheme OUTPUT; - - /* Get the cpu speed*/ - EXEC xp_regread @rootkey = N'HKEY_LOCAL_MACHINE', - @key = N'HARDWARE\DESCRIPTION\System\CentralProcessor\0', - @value_name = N'~MHz', - @value = @cpu_speed_mhz OUTPUT; - - /* Convert the Megahertz to Gigahertz */ - SET @cpu_speed_ghz = CAST(CAST(@cpu_speed_mhz AS decimal) / 1000 AS decimal(18,2)); - - SET @SkipXPRegRead = 0; /*We could execute xp_regread*/ - END TRY - BEGIN CATCH - SET @SkipXPRegRead = 1; /*We have don't have execute rights or xp_regread throws an error so skip it*/ - END CATCH; - END; /*Need execute on xp_regread*/ - IF NOT EXISTS ( SELECT @@ -3241,7 +3206,7 @@ AS END; END; - IF ISNULL(@SkipMSDB, 0) != 1 /*If @SkipMSDB hasn't been set to 1 by the caller*/ + IF ISNULL(@SkipMSDB_objs, 0) != 1 /*If @SkipMSDB_objs hasn't been set to 1 by the caller*/ BEGIN IF EXISTS ( @@ -3257,16 +3222,45 @@ AS FROM msdb.sys.objects ) BEGIN - SET @SkipMSDB = 0; /*We have read permissions in the msdb database, and can view the objects*/ + SET @SkipMSDB_objs = 0; /*We have read permissions in the msdb database, and can view the objects*/ + END; + END TRY + BEGIN CATCH + SET @SkipMSDB_objs = 1; /*We have read permissions in the msdb database ... oh wait we got tricked, we can't view the objects*/ + END CATCH; + END; + ELSE + BEGIN + SET @SkipMSDB_objs = 1; /*We don't have read permissions in the msdb database*/ + END; + END; + + IF ISNULL(@SkipMSDB_jobs, 0) != 1 /*If @SkipMSDB_jobs hasn't been set to 1 by the caller*/ + BEGIN + IF EXISTS + ( + SELECT 1/0 + FROM @db_perms + WHERE database_name = N'msdb' + ) + BEGIN + BEGIN TRY + IF EXISTS + ( + SELECT 1/0 + FROM msdb.dbo.sysjobs + ) + BEGIN + SET @SkipMSDB_jobs = 0; /*We have read permissions in the msdb database, and can view the objects*/ END; END TRY BEGIN CATCH - SET @SkipMSDB = 1; /*We have read permissions in the msdb database ... oh wait we got tricked, we can't view the objects*/ + SET @SkipMSDB_jobs = 1; /*We have read permissions in the msdb database ... oh wait we got tricked, we can't view the objects*/ END CATCH; END; ELSE BEGIN - SET @SkipMSDB = 1; /*We don't have read permissions in the msdb database*/ + SET @SkipMSDB_jobs = 1; /*We don't have read permissions in the msdb database*/ END; END; END; @@ -3438,17 +3432,34 @@ AS INSERT #SkipChecks (DatabaseName, CheckID, ServerName) SELECT v.* - FROM (VALUES(NULL, 6, NULL), /*Jobs Owned By Users*/ - (NULL, 28, NULL), /*SQL Agent Job Runs at Startup*/ - (NULL, 57, NULL), /*Tables in the MSDB Database*/ + FROM (VALUES(NULL, 28, NULL)) AS v (DatabaseName, CheckID, ServerName) /*Tables in the MSDB Database*/ + WHERE @SkipMSDB_objs = 1; + + INSERT #SkipChecks (DatabaseName, CheckID, ServerName) + SELECT + v.* + FROM (VALUES + /*sysjobs checks*/ + (NULL, 6, NULL), /*Jobs Owned By Users*/ + (NULL, 57, NULL), /*SQL Agent Job Runs at Startup*/ (NULL, 79, NULL), /*Shrink Database Job*/ (NULL, 94, NULL), /*Agent Jobs Without Failure Emails*/ (NULL, 123, NULL), /*Agent Jobs Starting Simultaneously*/ (NULL, 180, NULL), /*Shrink Database Step In Maintenance Plan*/ (NULL, 181, NULL), /*Repetitive Maintenance Tasks*/ - (NULL, 219, NULL) /*Alerts Without Event Descriptions*/ - ) AS v (DatabaseName, CheckID, ServerName) - WHERE @SkipMSDB = 1; + + /*sysalerts checks*/ + (NULL, 30, NULL), /*Not All Alerts Configured*/ + (NULL, 59, NULL), /*Alerts Configured without Follow Up*/ + (NULL, 61, NULL), /*No Alerts for Sev 19-25*/ + (NULL, 96, NULL), /*No Alerts for Corruption*/ + (NULL, 98, NULL), /*Alerts Disabled*/ + (NULL, 219, NULL), /*Alerts Without Event Descriptions*/ + + /*sysoperators*/ + (NULL, 31, NULL) /*No Operators Configured/Enabled*/ + ) AS v (DatabaseName, CheckID, ServerName) + WHERE @SkipMSDB_jobs = 1; INSERT #SkipChecks (DatabaseName, CheckID, ServerName) SELECT @@ -3492,6 +3503,25 @@ AS FROM (VALUES(NULL, 2301, NULL)) AS v (DatabaseName, CheckID, ServerName) /*sp_validatelogins*/ WHERE @SkipValidateLogins = 1 + IF @sa = 0 + BEGIN + INSERT INTO #BlitzResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT 223 AS CheckID , + 0 AS Priority , + 'Informational' AS FindingsGroup , + 'Some Checks Skipped' AS Finding , + '' AS URL , + 'User ''' + @SUSER_NAME + ''' is not part of the sysadmin role, so we skipped some checks that are not possible due to lack of permissions.' AS Details; + END; + /*End of SkipsChecks added due to permissions*/ + IF @SkipChecksTable IS NOT NULL AND @SkipChecksSchema IS NOT NULL AND @SkipChecksDatabase IS NOT NULL @@ -11536,122 +11566,147 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 EXECUTE(@StringToExecute); END; - /* - Starting with SQL Server 2014 SP2, Instant File Initialization - is logged in the SQL Server Error Log. - */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 193 ) - AND ((@ProductVersionMajor >= 13) OR (@ProductVersionMajor = 12 AND @ProductVersionMinor >= 5000)) + /* Performance - Instant File Initialization Not Enabled - Check 192 */ + /* Server Info - Instant File Initialization Enabled - Check 193 */ + IF NOT EXISTS ( SELECT 1/0 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 192 /* IFI disabled check disabled */ + ) OR NOT EXISTS + ( SELECT 1/0 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 193 /* IFI enabled check disabled */ + ) + BEGIN + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d] and CheckId [%d].', 0, 1, 192, 193) WITH NOWAIT; + + DECLARE @IFISetting varchar(1) = N'N' + ,@IFIReadDMVFailed bit = 0 + ,@IFIAllFailed bit = 0; + + /* See if we can get the instant_file_initialization_enabled column from sys.dm_server_services */ + IF EXISTS + ( + SELECT 1/0 + FROM sys.all_columns + WHERE [object_id] = OBJECT_ID(N'[sys].[dm_server_services]') + AND [name] = N'instant_file_initialization_enabled' + ) BEGIN + /* This needs to be a "dynamic" SQL statement because if the 'instant_file_initialization_enabled' column doesn't exist the procedure might fail on a bind error */ + SET @StringToExecute = N'SELECT @IFISetting = instant_file_initialization_enabled' + @crlf + + N'FROM sys.dm_server_services' + @crlf + + N'WHERE filename LIKE ''%sqlservr.exe%''' + @crlf + + N'OPTION (RECOMPILE);'; + + IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; + IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; + + EXEC dbo.sp_executesql + @StringToExecute + ,N'@IFISetting varchar(1) OUTPUT' + ,@IFISetting = @IFISetting OUTPUT - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 193) WITH NOWAIT; - - -- If this is Amazon RDS, use rdsadmin.dbo.rds_read_error_log - IF LEFT(CAST(SERVERPROPERTY('ComputerNamePhysicalNetBIOS') AS VARCHAR(8000)), 8) = 'EC2AMAZ-' - AND LEFT(CAST(SERVERPROPERTY('MachineName') AS VARCHAR(8000)), 8) = 'EC2AMAZ-' - AND db_id('rdsadmin') IS NOT NULL - AND EXISTS(SELECT * FROM master.sys.all_objects WHERE name IN ('rds_startup_tasks', 'rds_help_revlogin', 'rds_hexadecimal', 'rds_failover_tracking', 'rds_database_tracking', 'rds_track_change')) - BEGIN - INSERT INTO #ErrorLog - EXEC rdsadmin.dbo.rds_read_error_log 0, 1, N'Database Instant File Initialization: enabled'; - END - ELSE - BEGIN - BEGIN TRY - INSERT INTO #ErrorLog - EXEC sys.xp_readerrorlog 0, 1, N'Database Instant File Initialization: enabled'; - END TRY - BEGIN CATCH - IF @Debug IN (1, 2) RAISERROR('No permissions to execute xp_readerrorlog.', 0, 1) WITH NOWAIT; - END CATCH - END - - IF EXISTS - ( - SELECT 1/0 - FROM #ErrorLog - WHERE LEFT([Text], 45) = N'Database Instant File Initialization: enabled' - ) - BEGIN - INSERT INTO #BlitzResults - ( CheckID , - [Priority] , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT - 193 AS [CheckID] , - 250 AS [Priority] , - 'Server Info' AS [FindingsGroup] , - 'Instant File Initialization Enabled' AS [Finding] , - 'https://www.brentozar.com/go/instant' AS [URL] , - 'The service account has the Perform Volume Maintenance Tasks permission.'; - END; - else -- if version of sql server has instant_file_initialization_enabled column in dm_server_services, check that too - -- in the event the error log has been cycled and the startup messages are not in the current error log - begin - if EXISTS ( SELECT * - FROM sys.all_objects o - INNER JOIN sys.all_columns c ON o.object_id = c.object_id - WHERE o.name = 'dm_server_services' - AND c.name = 'instant_file_initialization_enabled' ) - begin - SET @StringToExecute = N' - INSERT INTO #BlitzResults - ( CheckID , - [Priority] , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT - 193 AS [CheckID] , - 250 AS [Priority] , - ''Server Info'' AS [FindingsGroup] , - ''Instant File Initialization Enabled'' AS [Finding] , - ''https://www.brentozar.com/go/instant'' AS [URL] , - ''The service account has the Perform Volume Maintenance Tasks permission.'' - where exists (select 1 FROM sys.dm_server_services - WHERE instant_file_initialization_enabled = ''Y'' - AND filename LIKE ''%sqlservr.exe%'') - OPTION (RECOMPILE);'; - EXEC(@StringToExecute); - end; - end; - END; + SET @IFIReadDMVFailed = 0; + END + ELSE + /* We couldn't get the instant_file_initialization_enabled column from sys.dm_server_services, fall back to read error log */ + BEGIN + SET @IFIReadDMVFailed = 1; + /* If this is Amazon RDS, we'll use the rdsadmin.dbo.rds_read_error_log */ + IF LEFT(CAST(SERVERPROPERTY('ComputerNamePhysicalNetBIOS') AS VARCHAR(8000)), 8) = 'EC2AMAZ-' + AND LEFT(CAST(SERVERPROPERTY('MachineName') AS VARCHAR(8000)), 8) = 'EC2AMAZ-' + AND db_id('rdsadmin') IS NOT NULL + AND EXISTS ( SELECT 1/0 + FROM master.sys.all_objects + WHERE name IN ('rds_startup_tasks', 'rds_help_revlogin', 'rds_hexadecimal', 'rds_failover_tracking', 'rds_database_tracking', 'rds_track_change') + ) + BEGIN + /* Amazon RDS detected, read rdsadmin.dbo.rds_read_error_log */ + INSERT INTO #ErrorLog + EXEC rdsadmin.dbo.rds_read_error_log 0, 1, N'Database Instant File Initialization: enabled'; + END + ELSE + BEGIN + /* Try to read the error log, this might fail due to permissions */ + BEGIN TRY + INSERT INTO #ErrorLog + EXEC sys.xp_readerrorlog 0, 1, N'Database Instant File Initialization: enabled'; + END TRY + BEGIN CATCH + IF @Debug IN (1, 2) RAISERROR('No permissions to execute xp_readerrorlog.', 0, 1) WITH NOWAIT; + SET @IFIAllFailed = 1; + END CATCH + END; + END; + + IF @IFIAllFailed = 0 + BEGIN + IF @IFIReadDMVFailed = 1 + /* We couldn't read the DMV so set the @IFISetting variable using the error log */ + BEGIN + IF EXISTS ( SELECT 1/0 + FROM #ErrorLog + WHERE LEFT([Text], 45) = N'Database Instant File Initialization: enabled' + ) + BEGIN + SET @IFISetting = 'Y'; + END + ELSE + BEGIN + SET @IFISetting = 'N'; + END; + END; + + IF NOT EXISTS ( SELECT 1/0 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 192 /* IFI disabled check disabled */ + ) AND @IFISetting = 'N' + BEGIN + INSERT INTO #BlitzResults + ( + CheckID , + [Priority] , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT + 192 AS [CheckID] , + 50 AS [Priority] , + 'Performance' AS [FindingsGroup] , + 'Instant File Initialization Not Enabled' AS [Finding] , + 'https://www.brentozar.com/go/instant' AS [URL] , + 'Consider enabling IFI for faster restores and data file growths.' AS [Details] + END; + + IF NOT EXISTS ( SELECT 1/0 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 193 /* IFI enabled check disabled */ + ) AND @IFISetting = 'Y' + BEGIN + INSERT INTO #BlitzResults + ( + CheckID , + [Priority] , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT + 193 AS [CheckID] , + 250 AS [Priority] , + 'Server Info' AS [FindingsGroup] , + 'Instant File Initialization Enabled' AS [Finding] , + 'https://www.brentozar.com/go/instant' AS [URL] , + 'The service account has the Perform Volume Maintenance Tasks permission.' AS [Details] + END; + END; + END; - /* Server Info - Instant File Initialization Not Enabled - Check 192 - SQL Server 2016 SP1 and newer */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 192 ) - AND EXISTS ( SELECT * - FROM sys.all_objects o - INNER JOIN sys.all_columns c ON o.object_id = c.object_id - WHERE o.name = 'dm_server_services' - AND c.name = 'instant_file_initialization_enabled' ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 192) WITH NOWAIT; - - SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) - SELECT 192 AS CheckID , - 50 AS Priority , - ''Server Info'' AS FindingsGroup , - ''Instant File Initialization Not Enabled'' AS Finding , - ''https://www.brentozar.com/go/instant'' AS URL , - ''Consider enabling IFI for faster restores and data file growths.'' - FROM sys.dm_server_services WHERE instant_file_initialization_enabled <> ''Y'' AND filename LIKE ''%sqlservr.exe%'' OPTION (RECOMPILE);'; - - IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; - IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; - - EXECUTE(@StringToExecute); - END; + /* End of checkId 192 */ + /* End of checkId 193 */ IF NOT EXISTS ( SELECT 1 FROM #SkipChecks @@ -12029,7 +12084,39 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 WHERE DatabaseName IS NULL AND CheckID = 211 ) BEGIN + /* Variables for check 211: */ + DECLARE + @powerScheme varchar(36) + ,@cpu_speed_mhz int + ,@cpu_speed_ghz decimal(18,2) + ,@ExecResult int; + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 211) WITH NOWAIT; + IF @sa = 0 RAISERROR('The errors: ''xp_regread() returned error 5, ''Access is denied.'''' can be safely ignored', 0, 1) WITH NOWAIT; + + /* Get power plan if set by group policy [Git Hub Issue #1620] */ + EXEC xp_regread @rootkey = N'HKEY_LOCAL_MACHINE', + @key = N'SOFTWARE\Policies\Microsoft\Power\PowerSettings', + @value_name = N'ActivePowerScheme', + @value = @powerScheme OUTPUT, + @no_output = N'no_output'; + + IF @powerScheme IS NULL /* If power plan was not set by group policy, get local value [Git Hub Issue #1620]*/ + EXEC xp_regread @rootkey = N'HKEY_LOCAL_MACHINE', + @key = N'SYSTEM\CurrentControlSet\Control\Power\User\PowerSchemes', + @value_name = N'ActivePowerScheme', + @value = @powerScheme OUTPUT; + + /* Get the cpu speed*/ + EXEC @ExecResult = xp_regread @rootkey = N'HKEY_LOCAL_MACHINE', + @key = N'HARDWARE\DESCRIPTION\System\CentralProcessor\0', + @value_name = N'~MHz', + @value = @cpu_speed_mhz OUTPUT; + + /* Convert the Megahertz to Gigahertz */ + IF @ExecResult != 0 RAISERROR('We couldn''t retrieve the CPU speed, you will see Unknown in the results', 0, 1) + + SET @cpu_speed_ghz = CAST(CAST(@cpu_speed_mhz AS decimal) / 1000 AS decimal(18,2)); INSERT INTO #BlitzResults ( CheckID , @@ -12045,7 +12132,7 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 'Power Plan' AS Finding, 'https://www.brentozar.com/blitz/power-mode/' AS URL, 'Your server has ' - + CAST(@cpu_speed_ghz as VARCHAR(4)) + + ISNULL(CAST(@cpu_speed_ghz as VARCHAR(8)), 'Unknown ') + 'GHz CPUs, and is in ' + CASE @powerScheme WHEN 'a1841308-3541-4fab-bc81-f71556f20b4a' @@ -12701,20 +12788,22 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 FROM #BlitzResults WHERE Priority > 0 AND Priority < 255 AND FindingsGroup IS NOT NULL AND Finding IS NOT NULL AND FindingsGroup <> 'Security' /* Specifically excluding security checks for public exports */) - SELECT - CASE - WHEN r.Priority <> COALESCE(rPrior.Priority, 0) OR r.FindingsGroup <> rPrior.FindingsGroup THEN @crlf + N'**Priority ' + CAST(COALESCE(r.Priority,N'') AS NVARCHAR(5)) + N': ' + COALESCE(r.FindingsGroup,N'') + N'**:' + @crlf + @crlf - ELSE N'' - END - + CASE WHEN r.Finding <> COALESCE(rPrior.Finding,N'') AND r.Finding <> COALESCE(rNext.Finding,N'') THEN N'- ' + COALESCE(r.Finding,N'') + N' ' + COALESCE(r.DatabaseName, N'') + N' - ' + COALESCE(r.Details,N'') + @crlf - WHEN r.Finding <> COALESCE(rPrior.Finding,N'') AND r.Finding = rNext.Finding AND r.Details = rNext.Details THEN N'- ' + COALESCE(r.Finding,N'') + N' - ' + COALESCE(r.Details,N'') + @crlf + @crlf + N' * ' + COALESCE(r.DatabaseName, N'') + @crlf - WHEN r.Finding <> COALESCE(rPrior.Finding,N'') AND r.Finding = rNext.Finding THEN N'- ' + COALESCE(r.Finding,N'') + @crlf + CASE WHEN r.DatabaseName IS NULL THEN N'' ELSE N' * ' + COALESCE(r.DatabaseName,N'') END + CASE WHEN r.Details <> rPrior.Details THEN N' - ' + COALESCE(r.Details,N'') + @crlf ELSE '' END - ELSE CASE WHEN r.DatabaseName IS NULL THEN N'' ELSE N' * ' + COALESCE(r.DatabaseName,N'') END + CASE WHEN r.Details <> rPrior.Details THEN N' - ' + COALESCE(r.Details,N'') + @crlf ELSE N'' + @crlf END - END + @crlf - FROM Results r - LEFT OUTER JOIN Results rPrior ON r.rownum = rPrior.rownum + 1 - LEFT OUTER JOIN Results rNext ON r.rownum = rNext.rownum - 1 - ORDER BY r.rownum FOR XML PATH(N''); + SELECT + Markdown = CONVERT(XML, STUFF((SELECT + CASE + WHEN r.Priority <> COALESCE(rPrior.Priority, 0) OR r.FindingsGroup <> rPrior.FindingsGroup THEN @crlf + N'**Priority ' + CAST(COALESCE(r.Priority,N'') AS NVARCHAR(5)) + N': ' + COALESCE(r.FindingsGroup,N'') + N'**:' + @crlf + @crlf + ELSE N'' + END + + CASE WHEN r.Finding <> COALESCE(rPrior.Finding,N'') AND r.Finding <> COALESCE(rNext.Finding,N'') THEN N'- ' + COALESCE(r.Finding,N'') + N' ' + COALESCE(r.DatabaseName, N'') + N' - ' + COALESCE(r.Details,N'') + @crlf + WHEN r.Finding <> COALESCE(rPrior.Finding,N'') AND r.Finding = rNext.Finding AND r.Details = rNext.Details THEN N'- ' + COALESCE(r.Finding,N'') + N' - ' + COALESCE(r.Details,N'') + @crlf + @crlf + N' * ' + COALESCE(r.DatabaseName, N'') + @crlf + WHEN r.Finding <> COALESCE(rPrior.Finding,N'') AND r.Finding = rNext.Finding THEN N'- ' + COALESCE(r.Finding,N'') + @crlf + @crlf + CASE WHEN r.DatabaseName IS NULL THEN N'' ELSE N' * ' + COALESCE(r.DatabaseName,N'') END + CASE WHEN r.Details <> rPrior.Details THEN N' - ' + COALESCE(r.Details,N'') + @crlf ELSE '' END + ELSE CASE WHEN r.DatabaseName IS NULL THEN N'' ELSE N' * ' + COALESCE(r.DatabaseName,N'') END + CASE WHEN r.Details <> rPrior.Details THEN N' - ' + COALESCE(r.Details,N'') + @crlf ELSE N'' + @crlf END + END + @crlf + FROM Results r + LEFT OUTER JOIN Results rPrior ON r.rownum = rPrior.rownum + 1 + LEFT OUTER JOIN Results rNext ON r.rownum = rNext.rownum - 1 + ORDER BY r.rownum FOR XML PATH(N''), ROOT('Markdown'), TYPE).value('/Markdown[1]','VARCHAR(MAX)'), 1, 2, '') + + ''); END; ELSE IF @OutputType = 'XML' BEGIN @@ -12878,7 +12967,7 @@ AS SET NOCOUNT ON; SET STATISTICS XML OFF; -SELECT @Version = '8.17', @VersionDate = '20231010'; +SELECT @Version = '8.18', @VersionDate = '20231222'; IF(@VersionCheckMode = 1) BEGIN @@ -13756,7 +13845,7 @@ AS SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.17', @VersionDate = '20231010'; + SELECT @Version = '8.18', @VersionDate = '20231222'; IF(@VersionCheckMode = 1) BEGIN @@ -15538,7 +15627,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.17', @VersionDate = '20231010'; +SELECT @Version = '8.18', @VersionDate = '20231222'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -22595,16 +22684,16 @@ END '; IF @Debug = 1 BEGIN - PRINT SUBSTRING(@StringToExecute, 0, 4000); - PRINT SUBSTRING(@StringToExecute, 4000, 8000); - PRINT SUBSTRING(@StringToExecute, 8000, 12000); - PRINT SUBSTRING(@StringToExecute, 12000, 16000); - PRINT SUBSTRING(@StringToExecute, 16000, 20000); - PRINT SUBSTRING(@StringToExecute, 20000, 24000); - PRINT SUBSTRING(@StringToExecute, 24000, 28000); - PRINT SUBSTRING(@StringToExecute, 28000, 32000); - PRINT SUBSTRING(@StringToExecute, 32000, 36000); - PRINT SUBSTRING(@StringToExecute, 36000, 40000); + PRINT SUBSTRING(@StringToExecute, 1, 4000); + PRINT SUBSTRING(@StringToExecute, 4001, 4000); + PRINT SUBSTRING(@StringToExecute, 8001, 4000); + PRINT SUBSTRING(@StringToExecute, 12001, 4000); + PRINT SUBSTRING(@StringToExecute, 16001, 4000); + PRINT SUBSTRING(@StringToExecute, 20001, 4000); + PRINT SUBSTRING(@StringToExecute, 24001, 4000); + PRINT SUBSTRING(@StringToExecute, 28001, 4000); + PRINT SUBSTRING(@StringToExecute, 32001, 4000); + PRINT SUBSTRING(@StringToExecute, 36001, 4000); END; EXEC sp_executesql @StringToExecute, N'@Top INT, @min_duration INT, @min_back INT, @CheckDateOverride DATETIMEOFFSET, @MinimumExecutionCount INT', @Top, @DurationFilter_i, @MinutesBack, @CheckDateOverride, @MinimumExecutionCount; @@ -22897,7 +22986,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.17', @VersionDate = '20231010'; +SELECT @Version = '8.18', @VersionDate = '20231222'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -24063,16 +24152,16 @@ BEGIN TRY RAISERROR (N'Inserting data into #IndexColumns for clustered indexes and heaps',0,1) WITH NOWAIT; IF @Debug = 1 BEGIN - PRINT SUBSTRING(@dsql, 0, 4000); - PRINT SUBSTRING(@dsql, 4000, 8000); - PRINT SUBSTRING(@dsql, 8000, 12000); - PRINT SUBSTRING(@dsql, 12000, 16000); - PRINT SUBSTRING(@dsql, 16000, 20000); - PRINT SUBSTRING(@dsql, 20000, 24000); - PRINT SUBSTRING(@dsql, 24000, 28000); - PRINT SUBSTRING(@dsql, 28000, 32000); - PRINT SUBSTRING(@dsql, 32000, 36000); - PRINT SUBSTRING(@dsql, 36000, 40000); + PRINT SUBSTRING(@dsql, 1, 4000); + PRINT SUBSTRING(@dsql, 4001, 4000); + PRINT SUBSTRING(@dsql, 8001, 4000); + PRINT SUBSTRING(@dsql, 12001, 4000); + PRINT SUBSTRING(@dsql, 16001, 4000); + PRINT SUBSTRING(@dsql, 20001, 4000); + PRINT SUBSTRING(@dsql, 24001, 4000); + PRINT SUBSTRING(@dsql, 28001, 4000); + PRINT SUBSTRING(@dsql, 32001, 4000); + PRINT SUBSTRING(@dsql, 36001, 4000); END; BEGIN TRY INSERT #IndexColumns ( database_id, [schema_name], [object_id], index_id, key_ordinal, is_included_column, is_descending_key, partition_ordinal, @@ -25440,6 +25529,7 @@ SELECT FROM #IndexSanity; RAISERROR (N'Populate #PartitionCompressionInfo.',0,1) WITH NOWAIT; +IF OBJECT_ID('tempdb..#maps') IS NOT NULL DROP TABLE #maps; WITH maps AS ( @@ -25454,6 +25544,7 @@ SELECT * INTO #maps FROM maps; +IF OBJECT_ID('tempdb..#grps') IS NOT NULL DROP TABLE #grps; WITH grps AS ( @@ -29079,10 +29170,11 @@ WITH RECOMPILE AS BEGIN SET STATISTICS XML OFF; - SET NOCOUNT, XACT_ABORT ON; + SET NOCOUNT ON; + SET XACT_ABORT OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.17', @VersionDate = '20231010'; + SELECT @Version = '8.18', @VersionDate = '20231222'; IF @VersionCheckMode = 1 BEGIN @@ -29206,6 +29298,20 @@ BEGIN THEN 1 ELSE 0 END, + @MI bit = + CASE + WHEN + ( + SELECT + CONVERT + ( + integer, + SERVERPROPERTY('EngineEdition') + ) + ) = 8 + THEN 1 + ELSE 0 + END, @RDS bit = CASE WHEN LEFT(CAST(SERVERPROPERTY('ComputerNamePhysicalNetBIOS') AS varchar(8000)), 8) <> 'EC2AMAZ-' @@ -29344,6 +29450,17 @@ BEGIN @StartDateUTC = @StartDate, @EndDateUTC = @EndDate; + IF + ( + @MI = 1 + AND @EventSessionName = N'system_health' + AND @TargetSessionType IS NULL + ) + BEGIN + SET + @TargetSessionType = N'ring_buffer'; + END; + IF @Azure = 0 BEGIN IF NOT EXISTS @@ -29361,7 +29478,7 @@ BEGIN RETURN; END; END; - + IF @Azure = 1 BEGIN IF NOT EXISTS @@ -30891,7 +31008,7 @@ BEGIN COUNT_BIG(DISTINCT dp.event_date) ) + N' deadlocks.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT dp.event_date) DESC) FROM #deadlock_process AS dp @@ -30931,7 +31048,7 @@ BEGIN SELECT 1/0 FROM sys.databases AS d - WHERE d.name = dow.database_name + WHERE d.name COLLATE DATABASE_DEFAULT = dow.database_name COLLATE DATABASE_DEFAULT AND d.is_read_committed_snapshot_on = 1 ) THEN N'You already enabled RCSI, but...' @@ -30946,7 +31063,7 @@ BEGIN COUNT_BIG(DISTINCT dow.event_date) ) + N' deadlock(s) between read queries and modification queries.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT dow.event_date) DESC) FROM #deadlock_owner_waiter AS dow @@ -31008,7 +31125,7 @@ BEGIN COUNT_BIG(DISTINCT dow.event_date) ) + N' deadlock(s).', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT dow.event_date) DESC) FROM #deadlock_owner_waiter AS dow @@ -31051,7 +31168,7 @@ BEGIN COUNT_BIG(DISTINCT dow.event_date) ) + N' deadlock(s).', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT dow.event_date) DESC) FROM #deadlock_owner_waiter AS dow @@ -31100,7 +31217,7 @@ BEGIN COUNT_BIG(DISTINCT dow.event_date) ) + N' deadlock(s).', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT dow.event_date) DESC) FROM #deadlock_owner_waiter AS dow @@ -31149,7 +31266,7 @@ BEGIN COUNT_BIG(DISTINCT dp.event_date) ) + N' instances of Serializable deadlocks.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT dp.event_date) DESC) FROM #deadlock_process AS dp @@ -31192,7 +31309,7 @@ BEGIN COUNT_BIG(DISTINCT dp.event_date) ) + N' instances of Repeatable Read deadlocks.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT dp.event_date) DESC) FROM #deadlock_process AS dp @@ -31254,7 +31371,7 @@ BEGIN N'UNKNOWN' ) + N'.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT dp.event_date) DESC) FROM #deadlock_process AS dp @@ -31366,7 +31483,7 @@ BEGIN 1, N'' ) + N' locks.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY CONVERT(bigint, lt.lock_count) DESC) FROM lock_types AS lt @@ -31545,7 +31662,7 @@ BEGIN COUNT_BIG(DISTINCT ds.id) ) + N' deadlocks.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT ds.id) DESC) FROM #deadlock_stack AS ds @@ -31646,19 +31763,19 @@ BEGIN ) ), wait_time_hms = - /*the more wait time you rack up the less accurate this gets, + /*the more wait time you rack up the less accurate this gets, it's either that or erroring out*/ - CASE - WHEN + CASE + WHEN SUM ( CONVERT ( - bigint, + bigint, dp.wait_time ) )/1000 > 2147483647 - THEN + THEN CONVERT ( nvarchar(30), @@ -31671,7 +31788,7 @@ BEGIN ( CONVERT ( - bigint, + bigint, dp.wait_time ) ) @@ -31682,16 +31799,16 @@ BEGIN ), 14 ) - WHEN + WHEN SUM ( CONVERT ( - bigint, + bigint, dp.wait_time ) ) BETWEEN 2147483648 AND 2147483647000 - THEN + THEN CONVERT ( nvarchar(30), @@ -31704,7 +31821,7 @@ BEGIN ( CONVERT ( - bigint, + bigint, dp.wait_time ) ) @@ -31786,7 +31903,7 @@ BEGIN 14 ) + N' [dd hh:mm:ss:ms] of deadlock wait time.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY cs.total_waits DESC) FROM chopsuey AS cs @@ -31860,19 +31977,19 @@ BEGIN ) ) + N' ' + - /*the more wait time you rack up the less accurate this gets, + /*the more wait time you rack up the less accurate this gets, it's either that or erroring out*/ - CASE - WHEN + CASE + WHEN SUM ( CONVERT ( - bigint, + bigint, wt.total_wait_time_ms ) )/1000 > 2147483647 - THEN + THEN CONVERT ( nvarchar(30), @@ -31885,7 +32002,7 @@ BEGIN ( CONVERT ( - bigint, + bigint, wt.total_wait_time_ms ) ) @@ -31896,16 +32013,16 @@ BEGIN ), 14 ) - WHEN + WHEN SUM ( CONVERT ( - bigint, + bigint, wt.total_wait_time_ms ) ) BETWEEN 2147483648 AND 2147483647000 - THEN + THEN CONVERT ( nvarchar(30), @@ -31918,7 +32035,7 @@ BEGIN ( CONVERT ( - bigint, + bigint, wt.total_wait_time_ms ) ) @@ -31951,7 +32068,7 @@ BEGIN 14 ) END + N' [dd hh:mm:ss:ms] of deadlock wait time.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY SUM(CONVERT(bigint, wt.total_wait_time_ms)) DESC) FROM wait_time AS wt @@ -31989,7 +32106,7 @@ BEGIN N'There have been ' + RTRIM(COUNT_BIG(DISTINCT aj.event_date)) + N' deadlocks from this Agent Job and Step.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT aj.event_date) DESC) FROM #agent_job AS aj @@ -32769,23 +32886,23 @@ BEGIN BEGIN SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Results to client %s', 0, 1, @d) WITH NOWAIT; - + IF @Debug = 1 BEGIN SET STATISTICS XML ON; END; - + EXEC sys.sp_executesql @deadlock_result; - + IF @Debug = 1 BEGIN SET STATISTICS XML OFF; PRINT @deadlock_result; END; - + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Getting available execution plans for deadlocks %s', 0, 1, @d) WITH NOWAIT; - + SELECT DISTINCT available_plans = 'available_plans', @@ -32821,7 +32938,7 @@ BEGIN CONVERT(decimal(38, 6), deqs.total_worker_time / 1000. / deqs.execution_count), total_elapsed_time_ms = deqs.total_elapsed_time / 1000., - avg_elapsed_time = + avg_elapsed_time_ms = CONVERT(decimal(38, 6), deqs.total_elapsed_time / 1000. / deqs.execution_count), executions_per_second = ISNULL @@ -32833,7 +32950,7 @@ BEGIN ( SECOND, deqs.creation_time, - deqs.last_execution_time + NULLIF(deqs.last_execution_time, '1900-01-01 00:00:00.000') ), 0 ), @@ -32852,7 +32969,7 @@ BEGIN min_used_grant_mb = deqs.min_used_grant_kb * 8. / 1024., max_used_grant_mb = - deqs.max_used_grant_kb * 8. / 1024., + deqs.max_used_grant_kb * 8. / 1024., deqs.min_reserved_threads, deqs.max_reserved_threads, deqs.min_used_threads, @@ -32862,21 +32979,21 @@ BEGIN FROM sys.dm_exec_query_stats AS deqs WHERE EXISTS ( - SELECT - 1/0 - FROM #available_plans AS ap - WHERE ap.sql_handle = deqs.sql_handle + SELECT + 1/0 + FROM #available_plans AS ap + WHERE ap.sql_handle = deqs.sql_handle ) AND deqs.query_hash IS NOT NULL; - - CREATE CLUSTERED INDEX - deqs + + CREATE CLUSTERED INDEX + deqs ON #dm_exec_query_stats ( - sql_handle, + sql_handle, plan_handle ); - + SELECT ap.available_plans, ap.database_name, @@ -32890,7 +33007,7 @@ BEGIN ap.total_worker_time_ms, ap.avg_worker_time_ms, ap.total_elapsed_time_ms, - ap.avg_elapsed_time, + ap.avg_elapsed_time_ms, ap.total_logical_reads_mb, ap.total_physical_reads_mb, ap.total_logical_writes_mb, @@ -32908,7 +33025,7 @@ BEGIN ap.statement_end_offset FROM ( - + SELECT ap.*, c.statement_start_offset, @@ -32919,7 +33036,7 @@ BEGIN c.total_worker_time_ms, c.avg_worker_time_ms, c.total_elapsed_time_ms, - c.avg_elapsed_time, + c.avg_elapsed_time_ms, c.executions_per_second, c.total_physical_reads_mb, c.total_logical_writes_mb, @@ -32958,10 +33075,10 @@ BEGIN OPTION(RECOMPILE, LOOP JOIN, HASH JOIN); RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - + SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Returning findings %s', 0, 1, @d) WITH NOWAIT; - + SELECT df.check_id, df.database_name, @@ -32973,7 +33090,7 @@ BEGIN df.check_id, df.sort_order OPTION(RECOMPILE); - + SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; END; /*done with output to client app.*/ @@ -33212,7 +33329,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.17', @VersionDate = '20231010'; +SELECT @Version = '8.18', @VersionDate = '20231222'; IF(@VersionCheckMode = 1) BEGIN RETURN; @@ -33273,8 +33390,6 @@ IF (SELECT CONVERT(NVARCHAR(128), SERVERPROPERTY ('EDITION'))) <> 'SQL Azure' IF @Help = 1 BEGIN - - SELECT N'You have requested assistance. It will arrive as soon as humanly possible.' AS [Take four red capsules, help is on the way]; PRINT N' sp_BlitzQueryStore from http://FirstResponderKit.org @@ -33318,6 +33433,257 @@ IF @Help = 1 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. '; + /*Parameter info*/ + SELECT N'@Help' AS [Parameter Name] , + N'BIT' AS [Data Type] , + N'0' AS [Default Value], + N'Displays this help message.' AS [Parameter Description] + UNION ALL + SELECT N'@DatabaseName', + N'NVARCHAR(128)', + N'NULL', + N'The name of the database you want to check the query store for.' + UNION ALL + SELECT N'@Top', + N'INT', + N'3', + N'The number of records to retrieve and analyze from the query store. The following system views are used: query_store_query, query_context_settings, query_store_wait_stats, query_store_runtime_stats,query_store_plan.' + + UNION ALL + SELECT N'@StartDate', + N'DATETIME2(7)', + N'NULL', + N'Get query store info starting from this date. When not specified, sp_BlitzQueryStore gets info from the last 7 days' + UNION ALL + SELECT N'@EndDate', + N'DATETIME2(7)', + N'NULL', + N'Get query store info until this date. When not specified, sp_BlitzQueryStore gets info from the last 7 days' + UNION ALL + SELECT N'@MinimumExecutionCount', + N'INT', + N'NULL', + N'When a value is specified, sp_BlitzQueryStore gets info for queries where count_executions >= @MinimumExecutionCount' + UNION ALL + SELECT N'@DurationFilter', + N'DECIMAL(38,4)', + N'NULL', + N'Time unit - seconds. When a value is specified, sp_BlitzQueryStore gets info for queries where the average duration >= @DurationFilter' + UNION ALL + SELECT N'@StoredProcName', + N'NVARCHAR(128)', + N'NULL', + N'Get information for this specific stored procedure.' + UNION ALL + SELECT N'@Failed', + N'BIT', + N'0', + N'When set to 1, only information about failed queries is returned.' + UNION ALL + SELECT N'@PlanIdFilter', + N'INT', + N'NULL', + N'The ID of the plan you want to check for.' + UNION ALL + SELECT N'@QueryIdFilter', + N'INT', + N'NULL', + N'The ID of the query you want to check for.' + UNION ALL + SELECT N'@ExportToExcel', + N'BIT', + N'0', + N'When set to 1, prepares output for exporting to Excel. Newlines and additional whitespace are removed from query text and the execution plan is not displayed.' + UNION ALL + SELECT N'@HideSummary', + N'BIT', + N'0', + N'When set to 1, hides the findings summary result set.' + UNION ALL + SELECT N'@SkipXML', + N'BIT', + N'0', + N'When set to 1, missing_indexes, implicit_conversion_info, cached_execution_parameters, are not returned. Does not affect query_plan_xml' + UNION ALL + SELECT N'@Debug', + N'BIT', + N'0', + N'Setting this to 1 will print dynamic SQL and select data from all tables used.' + UNION ALL + SELECT N'@ExpertMode', + N'BIT', + N'0', + N'When set to 1, more checks are done. Examples: many to many merge joins, row goals, adaptive joins, stats info, bad scans and plan forcing, computed columns that reference scalar UDFs.' + UNION ALL + SELECT N'@VersionCheckMode', + N'BIT', + N'0', + N'Outputs the version number and date.' + + /* Column definitions */ + SELECT 'database_name' AS [Column Name], + 'NVARCHAR(258)' AS [Data Type], + 'The name of the database where the plan was encountered.' AS [Column Description] + UNION ALL + SELECT 'query_cost', + 'FLOAT', + 'The cost of the execution plan in query bucks.' + UNION ALL + SELECT 'plan_id', + 'BIGINT', + 'The ID of the plan from sys.query_store_plan.' + UNION ALL + SELECT 'query_id', + 'BIGINT', + 'The ID of the query from sys.query_store_query.' + UNION ALL + SELECT 'query_id_all_plan_ids', + 'VARCHAR(8000)', + 'Comma-separated list of all query plan IDs associated with this query.' + UNION ALL + SELECT 'query_sql_text', + 'NVARCHAR(MAX)', + 'The text of the query, as provided by the user/app. Includes whitespaces, hints and comments. Comments and spaces before and after the query text are ignored.' + UNION ALL + SELECT 'proc_or_function_name', + 'NVARCHAR(258)', + 'If the query is part of a function/stored procedure, you''ll see here the name of its parent object.' + UNION ALL + SELECT 'query_plan_xml', + ' XML', + 'The query plan. Click to display a graphical plan.' + UNION ALL + SELECT 'warnings', + 'VARCHAR(MAX)', + 'A list of individual warnings generated by this query.' + UNION ALL + SELECT 'pattern', + 'NVARCHAR(512)', + 'A list of performance related patterns identified for this query.' + UNION ALL + SELECT 'parameter_sniffing_symptoms', + 'NVARCHAR(4000)', + 'A list of all the identified symptoms that are usually indicators of parameter sniffing.' + UNION ALL + SELECT 'last_force_failure_reason_desc', + 'NVARCHAR(258)', + 'Reason why plan forcing failed. NONE if plan isn''t forced.' + UNION ALL + SELECT 'top_three_waits', + 'NVARCHAR(MAX)', + 'The top 3 wait types, and their times in milliseconds, recorded for this query.' + UNION ALL + SELECT 'missing_indexes', + 'XML', + 'Missing index recommendations retrieved from the query plan.' + UNION ALL + SELECT 'implicit_conversion_info', + 'XML', + 'Information about the implicit conversion warnings,if any, retrieved from the query plan.' + UNION ALL + SELECT 'cached_execution_parameters', + 'XML', + 'Names, data types, and values for the parameters used when the query plan was compiled.' + UNION ALL + SELECT 'count_executions ', + 'BIGINT', + 'The number of executions of this particular query.' + UNION ALL + SELECT 'count_compiles', + 'BIGINT', + 'The number of plan compilations for this particular query.' + UNION ALL + SELECT 'total_cpu_time', + 'BIGINT', + 'Total CPU time, reported in milliseconds, that was consumed by all executions of this query.' + UNION ALL + SELECT 'avg_cpu_time ', + 'BIGINT', + 'Average CPU time, reported in milliseconds, consumed by each execution of this query.' + UNION ALL + SELECT 'total_duration', + 'BIGINT', + 'Total elapsed time, reported in milliseconds, consumed by all executions of this query.' + UNION ALL + SELECT 'avg_duration', + 'BIGINT', + 'Average elapsed time, reported in milliseconds, consumed by each execution of this query.' + UNION ALL + SELECT 'total_logical_io_reads', + 'BIGINT', + 'Total logical reads, reported in MB, performed by this query.' + UNION ALL + SELECT 'avg_logical_io_reads', + 'BIGINT', + 'Average logical reads, reported in MB, performed by each execution of this query.' + UNION ALL + SELECT 'total_physical_io_reads', + 'BIGINT', + 'Total physical reads, reported in MB, performed by this query.' + UNION ALL + SELECT 'avg_physical_io_reads', + 'BIGINT', + 'Average physical reads, reported in MB, performed by each execution of this query.' + UNION ALL + SELECT 'total_logical_io_writes', + 'BIGINT', + 'Total logical writes, reported in MB, performed by this query.' + UNION ALL + SELECT 'avg_logical_io_writes', + 'BIGINT', + 'Average logical writes, reported in MB, performed by each execution of this query.' + UNION ALL + SELECT 'total_rowcount', + 'BIGINT', + 'Total number of rows returned for all executions of this query.' + UNION ALL + SELECT 'avg_rowcount', + 'BIGINT', + 'Average number of rows returned by each execution of this query.' + UNION ALL + SELECT 'total_query_max_used_memory', + 'DECIMAL(38,2)', + 'Total max memory grant, reported in MB, used by this query.' + UNION ALL + SELECT 'avg_query_max_used_memory', + 'DECIMAL(38,2)', + 'Average max memory grant, reported in MB, used by each execution of this query.' + UNION ALL + SELECT 'total_tempdb_space_used', + 'DECIMAL(38,2)', + 'Total tempdb space, reported in MB, used by this query.' + UNION ALL + SELECT 'avg_tempdb_space_used', + 'DECIMAL(38,2)', + 'Average tempdb space, reported in MB, used by each execution of this query.' + UNION ALL + SELECT 'total_log_bytes_used', + 'DECIMAL(38,2)', + 'Total number of bytes in the database log used by this query.' + UNION ALL + SELECT 'avg_log_bytes_used', + 'DECIMAL(38,2)', + 'Average number of bytes in the database log used by each execution of this query.' + UNION ALL + SELECT 'total_num_physical_io_reads', + 'DECIMAL(38,2)', + 'Total number of physical I/O reads performed by this query (expressed as a number of read I/O operations).' + UNION ALL + SELECT 'avg_num_physical_io_reads', + 'DECIMAL(38,2)', + 'Average number of physical I/O reads performed by each execution of this query (expressed as a number of read I/O operations).' + UNION ALL + SELECT 'first_execution_time', + 'DATETIME2', + 'First execution time for this query within the aggregation interval. This is the end time of the query execution.' + UNION ALL + SELECT 'last_execution_time', + 'DATETIME2', + 'Last execution time for this query within the aggregation interval. This is the end time of the query execution.' + UNION ALL + SELECT 'context_settings', + 'NVARCHAR(512)', + 'Contains information about context settings associated with this query.'; RETURN; END; @@ -39012,7 +39378,7 @@ BEGIN SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.17', @VersionDate = '20231010'; + SELECT @Version = '8.18', @VersionDate = '20231222'; IF(@VersionCheckMode = 1) BEGIN @@ -40412,7 +40778,7 @@ SET STATISTICS XML OFF; /*Versioning details*/ -SELECT @Version = '8.17', @VersionDate = '20231010'; +SELECT @Version = '8.18', @VersionDate = '20231222'; IF(@VersionCheckMode = 1) BEGIN @@ -42027,38 +42393,39 @@ GO ALTER PROCEDURE [dbo].[sp_ineachdb] -- mssqltips.com/sqlservertip/5694/execute-a-command-in-the-context-of-each-database-in-sql-server--part-2/ - @command nvarchar(max) = NULL, - @replace_character nchar(1) = N'?', - @print_dbname bit = 0, - @select_dbname bit = 0, - @print_command bit = 0, - @print_command_only bit = 0, - @suppress_quotename bit = 0, -- use with caution - @system_only bit = 0, - @user_only bit = 0, - @name_pattern nvarchar(300) = N'%', - @database_list nvarchar(max) = NULL, - @exclude_pattern nvarchar(300) = NULL, - @exclude_list nvarchar(max) = NULL, - @recovery_model_desc nvarchar(120) = NULL, - @compatibility_level tinyint = NULL, - @state_desc nvarchar(120) = N'ONLINE', - @is_read_only bit = 0, - @is_auto_close_on bit = NULL, - @is_auto_shrink_on bit = NULL, - @is_broker_enabled bit = NULL, - @user_access nvarchar(128) = NULL, - @Help BIT = 0, - @Version VARCHAR(30) = NULL OUTPUT, - @VersionDate DATETIME = NULL OUTPUT, - @VersionCheckMode BIT = 0 + @command nvarchar(max) = NULL, + @replace_character nchar(1) = N'?', + @print_dbname bit = 0, + @select_dbname bit = 0, + @print_command bit = 0, + @print_command_only bit = 0, + @suppress_quotename bit = 0, -- use with caution + @system_only bit = 0, + @user_only bit = 0, + @name_pattern nvarchar(300) = N'%', + @database_list nvarchar(max) = NULL, + @exclude_pattern nvarchar(300) = NULL, + @exclude_list nvarchar(max) = NULL, + @recovery_model_desc nvarchar(120) = NULL, + @compatibility_level tinyint = NULL, + @state_desc nvarchar(120) = N'ONLINE', + @is_read_only bit = 0, + @is_auto_close_on bit = NULL, + @is_auto_shrink_on bit = NULL, + @is_broker_enabled bit = NULL, + @user_access nvarchar(128) = NULL, + @Help bit = 0, + @Version varchar(30) = NULL OUTPUT, + @VersionDate datetime = NULL OUTPUT, + @VersionCheckMode bit = 0, + @is_ag_writeable_copy bit = 0 -- 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.17', @VersionDate = '20231010'; + SELECT @Version = '8.18', @VersionDate = '20231222'; IF(@VersionCheckMode = 1) BEGIN @@ -42300,6 +42667,22 @@ OPTION (MAXRECURSION 0); AND ar.secondary_role_allow_connections = 0 AND ags.primary_replica <> @ServerName ); + /* Remove databases which are not the writeable copies in an AG. */ + IF @is_ag_writeable_copy = 1 + BEGIN + DELETE dbs FROM #ineachdb AS dbs + WHERE EXISTS + ( + SELECT 1 FROM sys.dm_hadr_database_replica_states AS drs + INNER JOIN sys.availability_replicas AS ar + ON ar.replica_id = drs.replica_id + INNER JOIN sys.dm_hadr_availability_group_states AS ags + ON ags.group_id = ar.group_id + WHERE drs.database_id = dbs.id + AND drs.is_primary_replica <> 1 + AND ags.primary_replica <> @ServerName + ); + END END -- Well, if we deleted them all... @@ -42404,6 +42787,8 @@ DELETE FROM dbo.SqlServerVersions; INSERT INTO dbo.SqlServerVersions (MajorVersionNumber, MinorVersionNumber, Branch, [Url], ReleaseDate, MainstreamSupportEndDate, ExtendedSupportEndDate, MajorVersionName, MinorVersionName) VALUES + (16, 4095, 'CU10', 'https://support.microsoft.com/en-us/help/5031778', '2023-11-16', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 10'), + (16, 4085, 'CU9', 'https://support.microsoft.com/en-us/help/5030731', '2023-10-12', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 9'), (16, 4075, 'CU8', 'https://support.microsoft.com/en-us/help/5029666', '2023-09-14', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 8'), (16, 4065, 'CU7', 'https://support.microsoft.com/en-us/help/5028743', '2023-08-10', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 7'), (16, 4055, 'CU6', 'https://support.microsoft.com/en-us/help/5027505', '2023-07-13', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 6'), @@ -42414,6 +42799,8 @@ VALUES (16, 4003, 'CU1', 'https://support.microsoft.com/en-us/help/5022375', '2023-02-16', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 1'), (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'), + (15, 4345, 'CU24', 'https://support.microsoft.com/kb/5031908', '2023-12-14', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 24'), + (15, 4335, 'CU23', 'https://support.microsoft.com/kb/5030333', '2023-10-12', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 23'), (15, 4322, 'CU22', 'https://support.microsoft.com/kb/5027702', '2023-08-14', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 22'), (15, 4316, 'CU21', 'https://support.microsoft.com/kb/5025808', '2023-06-15', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 21'), (15, 4312, 'CU20', 'https://support.microsoft.com/kb/5024276', '2023-04-13', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 20'), @@ -42441,6 +42828,7 @@ VALUES (15, 4003, 'CU1', 'https://support.microsoft.com/en-us/help/4527376', '2020-01-07', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 1 '), (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 '), + (14, 3465, 'RTM CU31 GDR', 'https://support.microsoft.com/kb/5029376', '2023-10-12', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 31 GDR'), (14, 3460, 'RTM CU31 GDR', 'https://support.microsoft.com/kb/5021126', '2023-02-14', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 31 GDR'), (14, 3456, 'RTM CU31', 'https://support.microsoft.com/en-us/help/5016884', '2022-09-20', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 31'), (14, 3451, 'RTM CU30', 'https://support.microsoft.com/en-us/help/5013756', '2022-07-13', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 30'), @@ -42838,7 +43226,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.17', @VersionDate = '20231010'; +SELECT @Version = '8.18', @VersionDate = '20231222'; IF(@VersionCheckMode = 1) BEGIN diff --git a/Install-Core-Blitz-No-Query-Store.sql b/Install-Core-Blitz-No-Query-Store.sql index 04060a6c6..58cd05196 100644 --- a/Install-Core-Blitz-No-Query-Store.sql +++ b/Install-Core-Blitz-No-Query-Store.sql @@ -38,7 +38,7 @@ AS SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.17', @VersionDate = '20231010'; + SELECT @Version = '8.18', @VersionDate = '20231222'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -196,14 +196,11 @@ AS ,@SkipXPFixedDrives bit = 0 ,@SkipXPCMDShell bit = 0 ,@SkipMaster bit = 0 - ,@SkipMSDB bit = 0 + ,@SkipMSDB_objs bit = 0 + ,@SkipMSDB_jobs bit = 0 ,@SkipModel bit = 0 ,@SkipTempDB bit = 0 ,@SkipValidateLogins bit = 0 - /* Variables for check 211: */ - ,@powerScheme varchar(36) - ,@cpu_speed_mhz int - ,@cpu_speed_ghz decimal(18,2); DECLARE @db_perms table @@ -278,38 +275,6 @@ AS SET @SkipTrace = 1; END; /*We need this permission to execute trace stuff, apparently*/ - IF ISNULL(@SkipXPRegRead, 0) != 1 /*If @SkipXPRegRead hasn't been set to 1 by the caller*/ - BEGIN - BEGIN TRY - /* Get power plan if set by group policy [Git Hub Issue #1620] */ - EXEC xp_regread @rootkey = N'HKEY_LOCAL_MACHINE', - @key = N'SOFTWARE\Policies\Microsoft\Power\PowerSettings', - @value_name = N'ActivePowerScheme', - @value = @powerScheme OUTPUT, - @no_output = N'no_output'; - - IF @powerScheme IS NULL /* If power plan was not set by group policy, get local value [Git Hub Issue #1620]*/ - EXEC xp_regread @rootkey = N'HKEY_LOCAL_MACHINE', - @key = N'SYSTEM\CurrentControlSet\Control\Power\User\PowerSchemes', - @value_name = N'ActivePowerScheme', - @value = @powerScheme OUTPUT; - - /* Get the cpu speed*/ - EXEC xp_regread @rootkey = N'HKEY_LOCAL_MACHINE', - @key = N'HARDWARE\DESCRIPTION\System\CentralProcessor\0', - @value_name = N'~MHz', - @value = @cpu_speed_mhz OUTPUT; - - /* Convert the Megahertz to Gigahertz */ - SET @cpu_speed_ghz = CAST(CAST(@cpu_speed_mhz AS decimal) / 1000 AS decimal(18,2)); - - SET @SkipXPRegRead = 0; /*We could execute xp_regread*/ - END TRY - BEGIN CATCH - SET @SkipXPRegRead = 1; /*We have don't have execute rights or xp_regread throws an error so skip it*/ - END CATCH; - END; /*Need execute on xp_regread*/ - IF NOT EXISTS ( SELECT @@ -379,7 +344,7 @@ AS END; END; - IF ISNULL(@SkipMSDB, 0) != 1 /*If @SkipMSDB hasn't been set to 1 by the caller*/ + IF ISNULL(@SkipMSDB_objs, 0) != 1 /*If @SkipMSDB_objs hasn't been set to 1 by the caller*/ BEGIN IF EXISTS ( @@ -395,16 +360,45 @@ AS FROM msdb.sys.objects ) BEGIN - SET @SkipMSDB = 0; /*We have read permissions in the msdb database, and can view the objects*/ + SET @SkipMSDB_objs = 0; /*We have read permissions in the msdb database, and can view the objects*/ END; END TRY BEGIN CATCH - SET @SkipMSDB = 1; /*We have read permissions in the msdb database ... oh wait we got tricked, we can't view the objects*/ + SET @SkipMSDB_objs = 1; /*We have read permissions in the msdb database ... oh wait we got tricked, we can't view the objects*/ END CATCH; END; ELSE BEGIN - SET @SkipMSDB = 1; /*We don't have read permissions in the msdb database*/ + SET @SkipMSDB_objs = 1; /*We don't have read permissions in the msdb database*/ + END; + END; + + IF ISNULL(@SkipMSDB_jobs, 0) != 1 /*If @SkipMSDB_jobs hasn't been set to 1 by the caller*/ + BEGIN + IF EXISTS + ( + SELECT 1/0 + FROM @db_perms + WHERE database_name = N'msdb' + ) + BEGIN + BEGIN TRY + IF EXISTS + ( + SELECT 1/0 + FROM msdb.dbo.sysjobs + ) + BEGIN + SET @SkipMSDB_jobs = 0; /*We have read permissions in the msdb database, and can view the objects*/ + END; + END TRY + BEGIN CATCH + SET @SkipMSDB_jobs = 1; /*We have read permissions in the msdb database ... oh wait we got tricked, we can't view the objects*/ + END CATCH; + END; + ELSE + BEGIN + SET @SkipMSDB_jobs = 1; /*We don't have read permissions in the msdb database*/ END; END; END; @@ -576,17 +570,34 @@ AS INSERT #SkipChecks (DatabaseName, CheckID, ServerName) SELECT v.* - FROM (VALUES(NULL, 6, NULL), /*Jobs Owned By Users*/ - (NULL, 28, NULL), /*SQL Agent Job Runs at Startup*/ - (NULL, 57, NULL), /*Tables in the MSDB Database*/ + FROM (VALUES(NULL, 28, NULL)) AS v (DatabaseName, CheckID, ServerName) /*Tables in the MSDB Database*/ + WHERE @SkipMSDB_objs = 1; + + INSERT #SkipChecks (DatabaseName, CheckID, ServerName) + SELECT + v.* + FROM (VALUES + /*sysjobs checks*/ + (NULL, 6, NULL), /*Jobs Owned By Users*/ + (NULL, 57, NULL), /*SQL Agent Job Runs at Startup*/ (NULL, 79, NULL), /*Shrink Database Job*/ (NULL, 94, NULL), /*Agent Jobs Without Failure Emails*/ (NULL, 123, NULL), /*Agent Jobs Starting Simultaneously*/ (NULL, 180, NULL), /*Shrink Database Step In Maintenance Plan*/ (NULL, 181, NULL), /*Repetitive Maintenance Tasks*/ - (NULL, 219, NULL) /*Alerts Without Event Descriptions*/ - ) AS v (DatabaseName, CheckID, ServerName) - WHERE @SkipMSDB = 1; + + /*sysalerts checks*/ + (NULL, 30, NULL), /*Not All Alerts Configured*/ + (NULL, 59, NULL), /*Alerts Configured without Follow Up*/ + (NULL, 61, NULL), /*No Alerts for Sev 19-25*/ + (NULL, 96, NULL), /*No Alerts for Corruption*/ + (NULL, 98, NULL), /*Alerts Disabled*/ + (NULL, 219, NULL), /*Alerts Without Event Descriptions*/ + + /*sysoperators*/ + (NULL, 31, NULL) /*No Operators Configured/Enabled*/ + ) AS v (DatabaseName, CheckID, ServerName) + WHERE @SkipMSDB_jobs = 1; INSERT #SkipChecks (DatabaseName, CheckID, ServerName) SELECT @@ -630,6 +641,25 @@ AS FROM (VALUES(NULL, 2301, NULL)) AS v (DatabaseName, CheckID, ServerName) /*sp_validatelogins*/ WHERE @SkipValidateLogins = 1 + IF @sa = 0 + BEGIN + INSERT INTO #BlitzResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT 223 AS CheckID , + 0 AS Priority , + 'Informational' AS FindingsGroup , + 'Some Checks Skipped' AS Finding , + '' AS URL , + 'User ''' + @SUSER_NAME + ''' is not part of the sysadmin role, so we skipped some checks that are not possible due to lack of permissions.' AS Details; + END; + /*End of SkipsChecks added due to permissions*/ + IF @SkipChecksTable IS NOT NULL AND @SkipChecksSchema IS NOT NULL AND @SkipChecksDatabase IS NOT NULL @@ -8674,122 +8704,147 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 EXECUTE(@StringToExecute); END; - /* - Starting with SQL Server 2014 SP2, Instant File Initialization - is logged in the SQL Server Error Log. - */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 193 ) - AND ((@ProductVersionMajor >= 13) OR (@ProductVersionMajor = 12 AND @ProductVersionMinor >= 5000)) + /* Performance - Instant File Initialization Not Enabled - Check 192 */ + /* Server Info - Instant File Initialization Enabled - Check 193 */ + IF NOT EXISTS ( SELECT 1/0 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 192 /* IFI disabled check disabled */ + ) OR NOT EXISTS + ( SELECT 1/0 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 193 /* IFI enabled check disabled */ + ) + BEGIN + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d] and CheckId [%d].', 0, 1, 192, 193) WITH NOWAIT; + + DECLARE @IFISetting varchar(1) = N'N' + ,@IFIReadDMVFailed bit = 0 + ,@IFIAllFailed bit = 0; + + /* See if we can get the instant_file_initialization_enabled column from sys.dm_server_services */ + IF EXISTS + ( + SELECT 1/0 + FROM sys.all_columns + WHERE [object_id] = OBJECT_ID(N'[sys].[dm_server_services]') + AND [name] = N'instant_file_initialization_enabled' + ) BEGIN + /* This needs to be a "dynamic" SQL statement because if the 'instant_file_initialization_enabled' column doesn't exist the procedure might fail on a bind error */ + SET @StringToExecute = N'SELECT @IFISetting = instant_file_initialization_enabled' + @crlf + + N'FROM sys.dm_server_services' + @crlf + + N'WHERE filename LIKE ''%sqlservr.exe%''' + @crlf + + N'OPTION (RECOMPILE);'; + + IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; + IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; + + EXEC dbo.sp_executesql + @StringToExecute + ,N'@IFISetting varchar(1) OUTPUT' + ,@IFISetting = @IFISetting OUTPUT - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 193) WITH NOWAIT; - - -- If this is Amazon RDS, use rdsadmin.dbo.rds_read_error_log - IF LEFT(CAST(SERVERPROPERTY('ComputerNamePhysicalNetBIOS') AS VARCHAR(8000)), 8) = 'EC2AMAZ-' - AND LEFT(CAST(SERVERPROPERTY('MachineName') AS VARCHAR(8000)), 8) = 'EC2AMAZ-' - AND db_id('rdsadmin') IS NOT NULL - AND EXISTS(SELECT * FROM master.sys.all_objects WHERE name IN ('rds_startup_tasks', 'rds_help_revlogin', 'rds_hexadecimal', 'rds_failover_tracking', 'rds_database_tracking', 'rds_track_change')) - BEGIN - INSERT INTO #ErrorLog - EXEC rdsadmin.dbo.rds_read_error_log 0, 1, N'Database Instant File Initialization: enabled'; - END - ELSE - BEGIN - BEGIN TRY - INSERT INTO #ErrorLog - EXEC sys.xp_readerrorlog 0, 1, N'Database Instant File Initialization: enabled'; - END TRY - BEGIN CATCH - IF @Debug IN (1, 2) RAISERROR('No permissions to execute xp_readerrorlog.', 0, 1) WITH NOWAIT; - END CATCH - END + SET @IFIReadDMVFailed = 0; + END + ELSE + /* We couldn't get the instant_file_initialization_enabled column from sys.dm_server_services, fall back to read error log */ + BEGIN + SET @IFIReadDMVFailed = 1; + /* If this is Amazon RDS, we'll use the rdsadmin.dbo.rds_read_error_log */ + IF LEFT(CAST(SERVERPROPERTY('ComputerNamePhysicalNetBIOS') AS VARCHAR(8000)), 8) = 'EC2AMAZ-' + AND LEFT(CAST(SERVERPROPERTY('MachineName') AS VARCHAR(8000)), 8) = 'EC2AMAZ-' + AND db_id('rdsadmin') IS NOT NULL + AND EXISTS ( SELECT 1/0 + FROM master.sys.all_objects + WHERE name IN ('rds_startup_tasks', 'rds_help_revlogin', 'rds_hexadecimal', 'rds_failover_tracking', 'rds_database_tracking', 'rds_track_change') + ) + BEGIN + /* Amazon RDS detected, read rdsadmin.dbo.rds_read_error_log */ + INSERT INTO #ErrorLog + EXEC rdsadmin.dbo.rds_read_error_log 0, 1, N'Database Instant File Initialization: enabled'; + END + ELSE + BEGIN + /* Try to read the error log, this might fail due to permissions */ + BEGIN TRY + INSERT INTO #ErrorLog + EXEC sys.xp_readerrorlog 0, 1, N'Database Instant File Initialization: enabled'; + END TRY + BEGIN CATCH + IF @Debug IN (1, 2) RAISERROR('No permissions to execute xp_readerrorlog.', 0, 1) WITH NOWAIT; + SET @IFIAllFailed = 1; + END CATCH + END; + END; + + IF @IFIAllFailed = 0 + BEGIN + IF @IFIReadDMVFailed = 1 + /* We couldn't read the DMV so set the @IFISetting variable using the error log */ + BEGIN + IF EXISTS ( SELECT 1/0 + FROM #ErrorLog + WHERE LEFT([Text], 45) = N'Database Instant File Initialization: enabled' + ) + BEGIN + SET @IFISetting = 'Y'; + END + ELSE + BEGIN + SET @IFISetting = 'N'; + END; + END; + + IF NOT EXISTS ( SELECT 1/0 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 192 /* IFI disabled check disabled */ + ) AND @IFISetting = 'N' + BEGIN + INSERT INTO #BlitzResults + ( + CheckID , + [Priority] , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT + 192 AS [CheckID] , + 50 AS [Priority] , + 'Performance' AS [FindingsGroup] , + 'Instant File Initialization Not Enabled' AS [Finding] , + 'https://www.brentozar.com/go/instant' AS [URL] , + 'Consider enabling IFI for faster restores and data file growths.' AS [Details] + END; + + IF NOT EXISTS ( SELECT 1/0 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 193 /* IFI enabled check disabled */ + ) AND @IFISetting = 'Y' + BEGIN + INSERT INTO #BlitzResults + ( + CheckID , + [Priority] , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT + 193 AS [CheckID] , + 250 AS [Priority] , + 'Server Info' AS [FindingsGroup] , + 'Instant File Initialization Enabled' AS [Finding] , + 'https://www.brentozar.com/go/instant' AS [URL] , + 'The service account has the Perform Volume Maintenance Tasks permission.' AS [Details] + END; + END; + END; - IF EXISTS - ( - SELECT 1/0 - FROM #ErrorLog - WHERE LEFT([Text], 45) = N'Database Instant File Initialization: enabled' - ) - BEGIN - INSERT INTO #BlitzResults - ( CheckID , - [Priority] , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT - 193 AS [CheckID] , - 250 AS [Priority] , - 'Server Info' AS [FindingsGroup] , - 'Instant File Initialization Enabled' AS [Finding] , - 'https://www.brentozar.com/go/instant' AS [URL] , - 'The service account has the Perform Volume Maintenance Tasks permission.'; - END; - else -- if version of sql server has instant_file_initialization_enabled column in dm_server_services, check that too - -- in the event the error log has been cycled and the startup messages are not in the current error log - begin - if EXISTS ( SELECT * - FROM sys.all_objects o - INNER JOIN sys.all_columns c ON o.object_id = c.object_id - WHERE o.name = 'dm_server_services' - AND c.name = 'instant_file_initialization_enabled' ) - begin - SET @StringToExecute = N' - INSERT INTO #BlitzResults - ( CheckID , - [Priority] , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT - 193 AS [CheckID] , - 250 AS [Priority] , - ''Server Info'' AS [FindingsGroup] , - ''Instant File Initialization Enabled'' AS [Finding] , - ''https://www.brentozar.com/go/instant'' AS [URL] , - ''The service account has the Perform Volume Maintenance Tasks permission.'' - where exists (select 1 FROM sys.dm_server_services - WHERE instant_file_initialization_enabled = ''Y'' - AND filename LIKE ''%sqlservr.exe%'') - OPTION (RECOMPILE);'; - EXEC(@StringToExecute); - end; - end; - END; - - /* Server Info - Instant File Initialization Not Enabled - Check 192 - SQL Server 2016 SP1 and newer */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 192 ) - AND EXISTS ( SELECT * - FROM sys.all_objects o - INNER JOIN sys.all_columns c ON o.object_id = c.object_id - WHERE o.name = 'dm_server_services' - AND c.name = 'instant_file_initialization_enabled' ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 192) WITH NOWAIT; - - SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) - SELECT 192 AS CheckID , - 50 AS Priority , - ''Server Info'' AS FindingsGroup , - ''Instant File Initialization Not Enabled'' AS Finding , - ''https://www.brentozar.com/go/instant'' AS URL , - ''Consider enabling IFI for faster restores and data file growths.'' - FROM sys.dm_server_services WHERE instant_file_initialization_enabled <> ''Y'' AND filename LIKE ''%sqlservr.exe%'' OPTION (RECOMPILE);'; - - IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; - IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; - - EXECUTE(@StringToExecute); - END; + /* End of checkId 192 */ + /* End of checkId 193 */ IF NOT EXISTS ( SELECT 1 FROM #SkipChecks @@ -9167,7 +9222,39 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 WHERE DatabaseName IS NULL AND CheckID = 211 ) BEGIN + /* Variables for check 211: */ + DECLARE + @powerScheme varchar(36) + ,@cpu_speed_mhz int + ,@cpu_speed_ghz decimal(18,2) + ,@ExecResult int; + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 211) WITH NOWAIT; + IF @sa = 0 RAISERROR('The errors: ''xp_regread() returned error 5, ''Access is denied.'''' can be safely ignored', 0, 1) WITH NOWAIT; + + /* Get power plan if set by group policy [Git Hub Issue #1620] */ + EXEC xp_regread @rootkey = N'HKEY_LOCAL_MACHINE', + @key = N'SOFTWARE\Policies\Microsoft\Power\PowerSettings', + @value_name = N'ActivePowerScheme', + @value = @powerScheme OUTPUT, + @no_output = N'no_output'; + + IF @powerScheme IS NULL /* If power plan was not set by group policy, get local value [Git Hub Issue #1620]*/ + EXEC xp_regread @rootkey = N'HKEY_LOCAL_MACHINE', + @key = N'SYSTEM\CurrentControlSet\Control\Power\User\PowerSchemes', + @value_name = N'ActivePowerScheme', + @value = @powerScheme OUTPUT; + + /* Get the cpu speed*/ + EXEC @ExecResult = xp_regread @rootkey = N'HKEY_LOCAL_MACHINE', + @key = N'HARDWARE\DESCRIPTION\System\CentralProcessor\0', + @value_name = N'~MHz', + @value = @cpu_speed_mhz OUTPUT; + + /* Convert the Megahertz to Gigahertz */ + IF @ExecResult != 0 RAISERROR('We couldn''t retrieve the CPU speed, you will see Unknown in the results', 0, 1) + + SET @cpu_speed_ghz = CAST(CAST(@cpu_speed_mhz AS decimal) / 1000 AS decimal(18,2)); INSERT INTO #BlitzResults ( CheckID , @@ -9183,7 +9270,7 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 'Power Plan' AS Finding, 'https://www.brentozar.com/blitz/power-mode/' AS URL, 'Your server has ' - + CAST(@cpu_speed_ghz as VARCHAR(4)) + + ISNULL(CAST(@cpu_speed_ghz as VARCHAR(8)), 'Unknown ') + 'GHz CPUs, and is in ' + CASE @powerScheme WHEN 'a1841308-3541-4fab-bc81-f71556f20b4a' @@ -9839,20 +9926,22 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 FROM #BlitzResults WHERE Priority > 0 AND Priority < 255 AND FindingsGroup IS NOT NULL AND Finding IS NOT NULL AND FindingsGroup <> 'Security' /* Specifically excluding security checks for public exports */) - SELECT - CASE - WHEN r.Priority <> COALESCE(rPrior.Priority, 0) OR r.FindingsGroup <> rPrior.FindingsGroup THEN @crlf + N'**Priority ' + CAST(COALESCE(r.Priority,N'') AS NVARCHAR(5)) + N': ' + COALESCE(r.FindingsGroup,N'') + N'**:' + @crlf + @crlf - ELSE N'' - END - + CASE WHEN r.Finding <> COALESCE(rPrior.Finding,N'') AND r.Finding <> COALESCE(rNext.Finding,N'') THEN N'- ' + COALESCE(r.Finding,N'') + N' ' + COALESCE(r.DatabaseName, N'') + N' - ' + COALESCE(r.Details,N'') + @crlf - WHEN r.Finding <> COALESCE(rPrior.Finding,N'') AND r.Finding = rNext.Finding AND r.Details = rNext.Details THEN N'- ' + COALESCE(r.Finding,N'') + N' - ' + COALESCE(r.Details,N'') + @crlf + @crlf + N' * ' + COALESCE(r.DatabaseName, N'') + @crlf - WHEN r.Finding <> COALESCE(rPrior.Finding,N'') AND r.Finding = rNext.Finding THEN N'- ' + COALESCE(r.Finding,N'') + @crlf + CASE WHEN r.DatabaseName IS NULL THEN N'' ELSE N' * ' + COALESCE(r.DatabaseName,N'') END + CASE WHEN r.Details <> rPrior.Details THEN N' - ' + COALESCE(r.Details,N'') + @crlf ELSE '' END - ELSE CASE WHEN r.DatabaseName IS NULL THEN N'' ELSE N' * ' + COALESCE(r.DatabaseName,N'') END + CASE WHEN r.Details <> rPrior.Details THEN N' - ' + COALESCE(r.Details,N'') + @crlf ELSE N'' + @crlf END - END + @crlf - FROM Results r - LEFT OUTER JOIN Results rPrior ON r.rownum = rPrior.rownum + 1 - LEFT OUTER JOIN Results rNext ON r.rownum = rNext.rownum - 1 - ORDER BY r.rownum FOR XML PATH(N''); + SELECT + Markdown = CONVERT(XML, STUFF((SELECT + CASE + WHEN r.Priority <> COALESCE(rPrior.Priority, 0) OR r.FindingsGroup <> rPrior.FindingsGroup THEN @crlf + N'**Priority ' + CAST(COALESCE(r.Priority,N'') AS NVARCHAR(5)) + N': ' + COALESCE(r.FindingsGroup,N'') + N'**:' + @crlf + @crlf + ELSE N'' + END + + CASE WHEN r.Finding <> COALESCE(rPrior.Finding,N'') AND r.Finding <> COALESCE(rNext.Finding,N'') THEN N'- ' + COALESCE(r.Finding,N'') + N' ' + COALESCE(r.DatabaseName, N'') + N' - ' + COALESCE(r.Details,N'') + @crlf + WHEN r.Finding <> COALESCE(rPrior.Finding,N'') AND r.Finding = rNext.Finding AND r.Details = rNext.Details THEN N'- ' + COALESCE(r.Finding,N'') + N' - ' + COALESCE(r.Details,N'') + @crlf + @crlf + N' * ' + COALESCE(r.DatabaseName, N'') + @crlf + WHEN r.Finding <> COALESCE(rPrior.Finding,N'') AND r.Finding = rNext.Finding THEN N'- ' + COALESCE(r.Finding,N'') + @crlf + @crlf + CASE WHEN r.DatabaseName IS NULL THEN N'' ELSE N' * ' + COALESCE(r.DatabaseName,N'') END + CASE WHEN r.Details <> rPrior.Details THEN N' - ' + COALESCE(r.Details,N'') + @crlf ELSE '' END + ELSE CASE WHEN r.DatabaseName IS NULL THEN N'' ELSE N' * ' + COALESCE(r.DatabaseName,N'') END + CASE WHEN r.Details <> rPrior.Details THEN N' - ' + COALESCE(r.Details,N'') + @crlf ELSE N'' + @crlf END + END + @crlf + FROM Results r + LEFT OUTER JOIN Results rPrior ON r.rownum = rPrior.rownum + 1 + LEFT OUTER JOIN Results rNext ON r.rownum = rNext.rownum - 1 + ORDER BY r.rownum FOR XML PATH(N''), ROOT('Markdown'), TYPE).value('/Markdown[1]','VARCHAR(MAX)'), 1, 2, '') + + ''); END; ELSE IF @OutputType = 'XML' BEGIN @@ -10016,7 +10105,7 @@ AS SET NOCOUNT ON; SET STATISTICS XML OFF; -SELECT @Version = '8.17', @VersionDate = '20231010'; +SELECT @Version = '8.18', @VersionDate = '20231222'; IF(@VersionCheckMode = 1) BEGIN @@ -10894,7 +10983,7 @@ AS SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.17', @VersionDate = '20231010'; + SELECT @Version = '8.18', @VersionDate = '20231222'; IF(@VersionCheckMode = 1) BEGIN @@ -12676,7 +12765,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.17', @VersionDate = '20231010'; +SELECT @Version = '8.18', @VersionDate = '20231222'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -19733,16 +19822,16 @@ END '; IF @Debug = 1 BEGIN - PRINT SUBSTRING(@StringToExecute, 0, 4000); - PRINT SUBSTRING(@StringToExecute, 4000, 8000); - PRINT SUBSTRING(@StringToExecute, 8000, 12000); - PRINT SUBSTRING(@StringToExecute, 12000, 16000); - PRINT SUBSTRING(@StringToExecute, 16000, 20000); - PRINT SUBSTRING(@StringToExecute, 20000, 24000); - PRINT SUBSTRING(@StringToExecute, 24000, 28000); - PRINT SUBSTRING(@StringToExecute, 28000, 32000); - PRINT SUBSTRING(@StringToExecute, 32000, 36000); - PRINT SUBSTRING(@StringToExecute, 36000, 40000); + PRINT SUBSTRING(@StringToExecute, 1, 4000); + PRINT SUBSTRING(@StringToExecute, 4001, 4000); + PRINT SUBSTRING(@StringToExecute, 8001, 4000); + PRINT SUBSTRING(@StringToExecute, 12001, 4000); + PRINT SUBSTRING(@StringToExecute, 16001, 4000); + PRINT SUBSTRING(@StringToExecute, 20001, 4000); + PRINT SUBSTRING(@StringToExecute, 24001, 4000); + PRINT SUBSTRING(@StringToExecute, 28001, 4000); + PRINT SUBSTRING(@StringToExecute, 32001, 4000); + PRINT SUBSTRING(@StringToExecute, 36001, 4000); END; EXEC sp_executesql @StringToExecute, N'@Top INT, @min_duration INT, @min_back INT, @CheckDateOverride DATETIMEOFFSET, @MinimumExecutionCount INT', @Top, @DurationFilter_i, @MinutesBack, @CheckDateOverride, @MinimumExecutionCount; @@ -20035,7 +20124,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.17', @VersionDate = '20231010'; +SELECT @Version = '8.18', @VersionDate = '20231222'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -21201,16 +21290,16 @@ BEGIN TRY RAISERROR (N'Inserting data into #IndexColumns for clustered indexes and heaps',0,1) WITH NOWAIT; IF @Debug = 1 BEGIN - PRINT SUBSTRING(@dsql, 0, 4000); - PRINT SUBSTRING(@dsql, 4000, 8000); - PRINT SUBSTRING(@dsql, 8000, 12000); - PRINT SUBSTRING(@dsql, 12000, 16000); - PRINT SUBSTRING(@dsql, 16000, 20000); - PRINT SUBSTRING(@dsql, 20000, 24000); - PRINT SUBSTRING(@dsql, 24000, 28000); - PRINT SUBSTRING(@dsql, 28000, 32000); - PRINT SUBSTRING(@dsql, 32000, 36000); - PRINT SUBSTRING(@dsql, 36000, 40000); + PRINT SUBSTRING(@dsql, 1, 4000); + PRINT SUBSTRING(@dsql, 4001, 4000); + PRINT SUBSTRING(@dsql, 8001, 4000); + PRINT SUBSTRING(@dsql, 12001, 4000); + PRINT SUBSTRING(@dsql, 16001, 4000); + PRINT SUBSTRING(@dsql, 20001, 4000); + PRINT SUBSTRING(@dsql, 24001, 4000); + PRINT SUBSTRING(@dsql, 28001, 4000); + PRINT SUBSTRING(@dsql, 32001, 4000); + PRINT SUBSTRING(@dsql, 36001, 4000); END; BEGIN TRY INSERT #IndexColumns ( database_id, [schema_name], [object_id], index_id, key_ordinal, is_included_column, is_descending_key, partition_ordinal, @@ -22578,6 +22667,7 @@ SELECT FROM #IndexSanity; RAISERROR (N'Populate #PartitionCompressionInfo.',0,1) WITH NOWAIT; +IF OBJECT_ID('tempdb..#maps') IS NOT NULL DROP TABLE #maps; WITH maps AS ( @@ -22592,6 +22682,7 @@ SELECT * INTO #maps FROM maps; +IF OBJECT_ID('tempdb..#grps') IS NOT NULL DROP TABLE #grps; WITH grps AS ( @@ -26217,10 +26308,11 @@ WITH RECOMPILE AS BEGIN SET STATISTICS XML OFF; - SET NOCOUNT, XACT_ABORT ON; + SET NOCOUNT ON; + SET XACT_ABORT OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.17', @VersionDate = '20231010'; + SELECT @Version = '8.18', @VersionDate = '20231222'; IF @VersionCheckMode = 1 BEGIN @@ -26344,6 +26436,20 @@ BEGIN THEN 1 ELSE 0 END, + @MI bit = + CASE + WHEN + ( + SELECT + CONVERT + ( + integer, + SERVERPROPERTY('EngineEdition') + ) + ) = 8 + THEN 1 + ELSE 0 + END, @RDS bit = CASE WHEN LEFT(CAST(SERVERPROPERTY('ComputerNamePhysicalNetBIOS') AS varchar(8000)), 8) <> 'EC2AMAZ-' @@ -26482,6 +26588,17 @@ BEGIN @StartDateUTC = @StartDate, @EndDateUTC = @EndDate; + IF + ( + @MI = 1 + AND @EventSessionName = N'system_health' + AND @TargetSessionType IS NULL + ) + BEGIN + SET + @TargetSessionType = N'ring_buffer'; + END; + IF @Azure = 0 BEGIN IF NOT EXISTS @@ -26499,7 +26616,7 @@ BEGIN RETURN; END; END; - + IF @Azure = 1 BEGIN IF NOT EXISTS @@ -28029,7 +28146,7 @@ BEGIN COUNT_BIG(DISTINCT dp.event_date) ) + N' deadlocks.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT dp.event_date) DESC) FROM #deadlock_process AS dp @@ -28069,7 +28186,7 @@ BEGIN SELECT 1/0 FROM sys.databases AS d - WHERE d.name = dow.database_name + WHERE d.name COLLATE DATABASE_DEFAULT = dow.database_name COLLATE DATABASE_DEFAULT AND d.is_read_committed_snapshot_on = 1 ) THEN N'You already enabled RCSI, but...' @@ -28084,7 +28201,7 @@ BEGIN COUNT_BIG(DISTINCT dow.event_date) ) + N' deadlock(s) between read queries and modification queries.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT dow.event_date) DESC) FROM #deadlock_owner_waiter AS dow @@ -28146,7 +28263,7 @@ BEGIN COUNT_BIG(DISTINCT dow.event_date) ) + N' deadlock(s).', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT dow.event_date) DESC) FROM #deadlock_owner_waiter AS dow @@ -28189,7 +28306,7 @@ BEGIN COUNT_BIG(DISTINCT dow.event_date) ) + N' deadlock(s).', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT dow.event_date) DESC) FROM #deadlock_owner_waiter AS dow @@ -28238,7 +28355,7 @@ BEGIN COUNT_BIG(DISTINCT dow.event_date) ) + N' deadlock(s).', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT dow.event_date) DESC) FROM #deadlock_owner_waiter AS dow @@ -28287,7 +28404,7 @@ BEGIN COUNT_BIG(DISTINCT dp.event_date) ) + N' instances of Serializable deadlocks.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT dp.event_date) DESC) FROM #deadlock_process AS dp @@ -28330,7 +28447,7 @@ BEGIN COUNT_BIG(DISTINCT dp.event_date) ) + N' instances of Repeatable Read deadlocks.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT dp.event_date) DESC) FROM #deadlock_process AS dp @@ -28392,7 +28509,7 @@ BEGIN N'UNKNOWN' ) + N'.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT dp.event_date) DESC) FROM #deadlock_process AS dp @@ -28504,7 +28621,7 @@ BEGIN 1, N'' ) + N' locks.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY CONVERT(bigint, lt.lock_count) DESC) FROM lock_types AS lt @@ -28683,7 +28800,7 @@ BEGIN COUNT_BIG(DISTINCT ds.id) ) + N' deadlocks.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT ds.id) DESC) FROM #deadlock_stack AS ds @@ -28784,19 +28901,19 @@ BEGIN ) ), wait_time_hms = - /*the more wait time you rack up the less accurate this gets, + /*the more wait time you rack up the less accurate this gets, it's either that or erroring out*/ - CASE - WHEN + CASE + WHEN SUM ( CONVERT ( - bigint, + bigint, dp.wait_time ) )/1000 > 2147483647 - THEN + THEN CONVERT ( nvarchar(30), @@ -28809,7 +28926,7 @@ BEGIN ( CONVERT ( - bigint, + bigint, dp.wait_time ) ) @@ -28820,16 +28937,16 @@ BEGIN ), 14 ) - WHEN + WHEN SUM ( CONVERT ( - bigint, + bigint, dp.wait_time ) ) BETWEEN 2147483648 AND 2147483647000 - THEN + THEN CONVERT ( nvarchar(30), @@ -28842,7 +28959,7 @@ BEGIN ( CONVERT ( - bigint, + bigint, dp.wait_time ) ) @@ -28924,7 +29041,7 @@ BEGIN 14 ) + N' [dd hh:mm:ss:ms] of deadlock wait time.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY cs.total_waits DESC) FROM chopsuey AS cs @@ -28998,19 +29115,19 @@ BEGIN ) ) + N' ' + - /*the more wait time you rack up the less accurate this gets, + /*the more wait time you rack up the less accurate this gets, it's either that or erroring out*/ - CASE - WHEN + CASE + WHEN SUM ( CONVERT ( - bigint, + bigint, wt.total_wait_time_ms ) )/1000 > 2147483647 - THEN + THEN CONVERT ( nvarchar(30), @@ -29023,7 +29140,7 @@ BEGIN ( CONVERT ( - bigint, + bigint, wt.total_wait_time_ms ) ) @@ -29034,16 +29151,16 @@ BEGIN ), 14 ) - WHEN + WHEN SUM ( CONVERT ( - bigint, + bigint, wt.total_wait_time_ms ) ) BETWEEN 2147483648 AND 2147483647000 - THEN + THEN CONVERT ( nvarchar(30), @@ -29056,7 +29173,7 @@ BEGIN ( CONVERT ( - bigint, + bigint, wt.total_wait_time_ms ) ) @@ -29089,7 +29206,7 @@ BEGIN 14 ) END + N' [dd hh:mm:ss:ms] of deadlock wait time.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY SUM(CONVERT(bigint, wt.total_wait_time_ms)) DESC) FROM wait_time AS wt @@ -29127,7 +29244,7 @@ BEGIN N'There have been ' + RTRIM(COUNT_BIG(DISTINCT aj.event_date)) + N' deadlocks from this Agent Job and Step.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT aj.event_date) DESC) FROM #agent_job AS aj @@ -29907,23 +30024,23 @@ BEGIN BEGIN SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Results to client %s', 0, 1, @d) WITH NOWAIT; - + IF @Debug = 1 BEGIN SET STATISTICS XML ON; END; - + EXEC sys.sp_executesql @deadlock_result; - + IF @Debug = 1 BEGIN SET STATISTICS XML OFF; PRINT @deadlock_result; END; - + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Getting available execution plans for deadlocks %s', 0, 1, @d) WITH NOWAIT; - + SELECT DISTINCT available_plans = 'available_plans', @@ -29959,7 +30076,7 @@ BEGIN CONVERT(decimal(38, 6), deqs.total_worker_time / 1000. / deqs.execution_count), total_elapsed_time_ms = deqs.total_elapsed_time / 1000., - avg_elapsed_time = + avg_elapsed_time_ms = CONVERT(decimal(38, 6), deqs.total_elapsed_time / 1000. / deqs.execution_count), executions_per_second = ISNULL @@ -29971,7 +30088,7 @@ BEGIN ( SECOND, deqs.creation_time, - deqs.last_execution_time + NULLIF(deqs.last_execution_time, '1900-01-01 00:00:00.000') ), 0 ), @@ -29990,7 +30107,7 @@ BEGIN min_used_grant_mb = deqs.min_used_grant_kb * 8. / 1024., max_used_grant_mb = - deqs.max_used_grant_kb * 8. / 1024., + deqs.max_used_grant_kb * 8. / 1024., deqs.min_reserved_threads, deqs.max_reserved_threads, deqs.min_used_threads, @@ -30000,21 +30117,21 @@ BEGIN FROM sys.dm_exec_query_stats AS deqs WHERE EXISTS ( - SELECT - 1/0 - FROM #available_plans AS ap - WHERE ap.sql_handle = deqs.sql_handle + SELECT + 1/0 + FROM #available_plans AS ap + WHERE ap.sql_handle = deqs.sql_handle ) AND deqs.query_hash IS NOT NULL; - - CREATE CLUSTERED INDEX - deqs + + CREATE CLUSTERED INDEX + deqs ON #dm_exec_query_stats ( - sql_handle, + sql_handle, plan_handle ); - + SELECT ap.available_plans, ap.database_name, @@ -30028,7 +30145,7 @@ BEGIN ap.total_worker_time_ms, ap.avg_worker_time_ms, ap.total_elapsed_time_ms, - ap.avg_elapsed_time, + ap.avg_elapsed_time_ms, ap.total_logical_reads_mb, ap.total_physical_reads_mb, ap.total_logical_writes_mb, @@ -30046,7 +30163,7 @@ BEGIN ap.statement_end_offset FROM ( - + SELECT ap.*, c.statement_start_offset, @@ -30057,7 +30174,7 @@ BEGIN c.total_worker_time_ms, c.avg_worker_time_ms, c.total_elapsed_time_ms, - c.avg_elapsed_time, + c.avg_elapsed_time_ms, c.executions_per_second, c.total_physical_reads_mb, c.total_logical_writes_mb, @@ -30096,10 +30213,10 @@ BEGIN OPTION(RECOMPILE, LOOP JOIN, HASH JOIN); RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - + SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Returning findings %s', 0, 1, @d) WITH NOWAIT; - + SELECT df.check_id, df.database_name, @@ -30111,7 +30228,7 @@ BEGIN df.check_id, df.sort_order OPTION(RECOMPILE); - + SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; END; /*done with output to client app.*/ @@ -30326,7 +30443,7 @@ BEGIN SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.17', @VersionDate = '20231010'; + SELECT @Version = '8.18', @VersionDate = '20231222'; IF(@VersionCheckMode = 1) BEGIN @@ -31722,6 +31839,8 @@ DELETE FROM dbo.SqlServerVersions; INSERT INTO dbo.SqlServerVersions (MajorVersionNumber, MinorVersionNumber, Branch, [Url], ReleaseDate, MainstreamSupportEndDate, ExtendedSupportEndDate, MajorVersionName, MinorVersionName) VALUES + (16, 4095, 'CU10', 'https://support.microsoft.com/en-us/help/5031778', '2023-11-16', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 10'), + (16, 4085, 'CU9', 'https://support.microsoft.com/en-us/help/5030731', '2023-10-12', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 9'), (16, 4075, 'CU8', 'https://support.microsoft.com/en-us/help/5029666', '2023-09-14', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 8'), (16, 4065, 'CU7', 'https://support.microsoft.com/en-us/help/5028743', '2023-08-10', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 7'), (16, 4055, 'CU6', 'https://support.microsoft.com/en-us/help/5027505', '2023-07-13', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 6'), @@ -31732,6 +31851,8 @@ VALUES (16, 4003, 'CU1', 'https://support.microsoft.com/en-us/help/5022375', '2023-02-16', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 1'), (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'), + (15, 4345, 'CU24', 'https://support.microsoft.com/kb/5031908', '2023-12-14', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 24'), + (15, 4335, 'CU23', 'https://support.microsoft.com/kb/5030333', '2023-10-12', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 23'), (15, 4322, 'CU22', 'https://support.microsoft.com/kb/5027702', '2023-08-14', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 22'), (15, 4316, 'CU21', 'https://support.microsoft.com/kb/5025808', '2023-06-15', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 21'), (15, 4312, 'CU20', 'https://support.microsoft.com/kb/5024276', '2023-04-13', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 20'), @@ -31759,6 +31880,7 @@ VALUES (15, 4003, 'CU1', 'https://support.microsoft.com/en-us/help/4527376', '2020-01-07', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 1 '), (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 '), + (14, 3465, 'RTM CU31 GDR', 'https://support.microsoft.com/kb/5029376', '2023-10-12', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 31 GDR'), (14, 3460, 'RTM CU31 GDR', 'https://support.microsoft.com/kb/5021126', '2023-02-14', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 31 GDR'), (14, 3456, 'RTM CU31', 'https://support.microsoft.com/en-us/help/5016884', '2022-09-20', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 31'), (14, 3451, 'RTM CU30', 'https://support.microsoft.com/en-us/help/5013756', '2022-07-13', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 30'), @@ -32156,7 +32278,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.17', @VersionDate = '20231010'; +SELECT @Version = '8.18', @VersionDate = '20231222'; IF(@VersionCheckMode = 1) BEGIN diff --git a/Install-Core-Blitz-With-Query-Store.sql b/Install-Core-Blitz-With-Query-Store.sql index 0075b6f10..2724cc926 100644 --- a/Install-Core-Blitz-With-Query-Store.sql +++ b/Install-Core-Blitz-With-Query-Store.sql @@ -38,7 +38,7 @@ AS SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.17', @VersionDate = '20231010'; + SELECT @Version = '8.18', @VersionDate = '20231222'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -196,14 +196,11 @@ AS ,@SkipXPFixedDrives bit = 0 ,@SkipXPCMDShell bit = 0 ,@SkipMaster bit = 0 - ,@SkipMSDB bit = 0 + ,@SkipMSDB_objs bit = 0 + ,@SkipMSDB_jobs bit = 0 ,@SkipModel bit = 0 ,@SkipTempDB bit = 0 ,@SkipValidateLogins bit = 0 - /* Variables for check 211: */ - ,@powerScheme varchar(36) - ,@cpu_speed_mhz int - ,@cpu_speed_ghz decimal(18,2); DECLARE @db_perms table @@ -278,38 +275,6 @@ AS SET @SkipTrace = 1; END; /*We need this permission to execute trace stuff, apparently*/ - IF ISNULL(@SkipXPRegRead, 0) != 1 /*If @SkipXPRegRead hasn't been set to 1 by the caller*/ - BEGIN - BEGIN TRY - /* Get power plan if set by group policy [Git Hub Issue #1620] */ - EXEC xp_regread @rootkey = N'HKEY_LOCAL_MACHINE', - @key = N'SOFTWARE\Policies\Microsoft\Power\PowerSettings', - @value_name = N'ActivePowerScheme', - @value = @powerScheme OUTPUT, - @no_output = N'no_output'; - - IF @powerScheme IS NULL /* If power plan was not set by group policy, get local value [Git Hub Issue #1620]*/ - EXEC xp_regread @rootkey = N'HKEY_LOCAL_MACHINE', - @key = N'SYSTEM\CurrentControlSet\Control\Power\User\PowerSchemes', - @value_name = N'ActivePowerScheme', - @value = @powerScheme OUTPUT; - - /* Get the cpu speed*/ - EXEC xp_regread @rootkey = N'HKEY_LOCAL_MACHINE', - @key = N'HARDWARE\DESCRIPTION\System\CentralProcessor\0', - @value_name = N'~MHz', - @value = @cpu_speed_mhz OUTPUT; - - /* Convert the Megahertz to Gigahertz */ - SET @cpu_speed_ghz = CAST(CAST(@cpu_speed_mhz AS decimal) / 1000 AS decimal(18,2)); - - SET @SkipXPRegRead = 0; /*We could execute xp_regread*/ - END TRY - BEGIN CATCH - SET @SkipXPRegRead = 1; /*We have don't have execute rights or xp_regread throws an error so skip it*/ - END CATCH; - END; /*Need execute on xp_regread*/ - IF NOT EXISTS ( SELECT @@ -379,7 +344,7 @@ AS END; END; - IF ISNULL(@SkipMSDB, 0) != 1 /*If @SkipMSDB hasn't been set to 1 by the caller*/ + IF ISNULL(@SkipMSDB_objs, 0) != 1 /*If @SkipMSDB_objs hasn't been set to 1 by the caller*/ BEGIN IF EXISTS ( @@ -395,16 +360,45 @@ AS FROM msdb.sys.objects ) BEGIN - SET @SkipMSDB = 0; /*We have read permissions in the msdb database, and can view the objects*/ + SET @SkipMSDB_objs = 0; /*We have read permissions in the msdb database, and can view the objects*/ END; END TRY BEGIN CATCH - SET @SkipMSDB = 1; /*We have read permissions in the msdb database ... oh wait we got tricked, we can't view the objects*/ + SET @SkipMSDB_objs = 1; /*We have read permissions in the msdb database ... oh wait we got tricked, we can't view the objects*/ END CATCH; END; ELSE BEGIN - SET @SkipMSDB = 1; /*We don't have read permissions in the msdb database*/ + SET @SkipMSDB_objs = 1; /*We don't have read permissions in the msdb database*/ + END; + END; + + IF ISNULL(@SkipMSDB_jobs, 0) != 1 /*If @SkipMSDB_jobs hasn't been set to 1 by the caller*/ + BEGIN + IF EXISTS + ( + SELECT 1/0 + FROM @db_perms + WHERE database_name = N'msdb' + ) + BEGIN + BEGIN TRY + IF EXISTS + ( + SELECT 1/0 + FROM msdb.dbo.sysjobs + ) + BEGIN + SET @SkipMSDB_jobs = 0; /*We have read permissions in the msdb database, and can view the objects*/ + END; + END TRY + BEGIN CATCH + SET @SkipMSDB_jobs = 1; /*We have read permissions in the msdb database ... oh wait we got tricked, we can't view the objects*/ + END CATCH; + END; + ELSE + BEGIN + SET @SkipMSDB_jobs = 1; /*We don't have read permissions in the msdb database*/ END; END; END; @@ -576,17 +570,34 @@ AS INSERT #SkipChecks (DatabaseName, CheckID, ServerName) SELECT v.* - FROM (VALUES(NULL, 6, NULL), /*Jobs Owned By Users*/ - (NULL, 28, NULL), /*SQL Agent Job Runs at Startup*/ - (NULL, 57, NULL), /*Tables in the MSDB Database*/ + FROM (VALUES(NULL, 28, NULL)) AS v (DatabaseName, CheckID, ServerName) /*Tables in the MSDB Database*/ + WHERE @SkipMSDB_objs = 1; + + INSERT #SkipChecks (DatabaseName, CheckID, ServerName) + SELECT + v.* + FROM (VALUES + /*sysjobs checks*/ + (NULL, 6, NULL), /*Jobs Owned By Users*/ + (NULL, 57, NULL), /*SQL Agent Job Runs at Startup*/ (NULL, 79, NULL), /*Shrink Database Job*/ (NULL, 94, NULL), /*Agent Jobs Without Failure Emails*/ (NULL, 123, NULL), /*Agent Jobs Starting Simultaneously*/ (NULL, 180, NULL), /*Shrink Database Step In Maintenance Plan*/ (NULL, 181, NULL), /*Repetitive Maintenance Tasks*/ - (NULL, 219, NULL) /*Alerts Without Event Descriptions*/ - ) AS v (DatabaseName, CheckID, ServerName) - WHERE @SkipMSDB = 1; + + /*sysalerts checks*/ + (NULL, 30, NULL), /*Not All Alerts Configured*/ + (NULL, 59, NULL), /*Alerts Configured without Follow Up*/ + (NULL, 61, NULL), /*No Alerts for Sev 19-25*/ + (NULL, 96, NULL), /*No Alerts for Corruption*/ + (NULL, 98, NULL), /*Alerts Disabled*/ + (NULL, 219, NULL), /*Alerts Without Event Descriptions*/ + + /*sysoperators*/ + (NULL, 31, NULL) /*No Operators Configured/Enabled*/ + ) AS v (DatabaseName, CheckID, ServerName) + WHERE @SkipMSDB_jobs = 1; INSERT #SkipChecks (DatabaseName, CheckID, ServerName) SELECT @@ -630,6 +641,25 @@ AS FROM (VALUES(NULL, 2301, NULL)) AS v (DatabaseName, CheckID, ServerName) /*sp_validatelogins*/ WHERE @SkipValidateLogins = 1 + IF @sa = 0 + BEGIN + INSERT INTO #BlitzResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT 223 AS CheckID , + 0 AS Priority , + 'Informational' AS FindingsGroup , + 'Some Checks Skipped' AS Finding , + '' AS URL , + 'User ''' + @SUSER_NAME + ''' is not part of the sysadmin role, so we skipped some checks that are not possible due to lack of permissions.' AS Details; + END; + /*End of SkipsChecks added due to permissions*/ + IF @SkipChecksTable IS NOT NULL AND @SkipChecksSchema IS NOT NULL AND @SkipChecksDatabase IS NOT NULL @@ -8674,122 +8704,147 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 EXECUTE(@StringToExecute); END; - /* - Starting with SQL Server 2014 SP2, Instant File Initialization - is logged in the SQL Server Error Log. - */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 193 ) - AND ((@ProductVersionMajor >= 13) OR (@ProductVersionMajor = 12 AND @ProductVersionMinor >= 5000)) + /* Performance - Instant File Initialization Not Enabled - Check 192 */ + /* Server Info - Instant File Initialization Enabled - Check 193 */ + IF NOT EXISTS ( SELECT 1/0 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 192 /* IFI disabled check disabled */ + ) OR NOT EXISTS + ( SELECT 1/0 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 193 /* IFI enabled check disabled */ + ) + BEGIN + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d] and CheckId [%d].', 0, 1, 192, 193) WITH NOWAIT; + + DECLARE @IFISetting varchar(1) = N'N' + ,@IFIReadDMVFailed bit = 0 + ,@IFIAllFailed bit = 0; + + /* See if we can get the instant_file_initialization_enabled column from sys.dm_server_services */ + IF EXISTS + ( + SELECT 1/0 + FROM sys.all_columns + WHERE [object_id] = OBJECT_ID(N'[sys].[dm_server_services]') + AND [name] = N'instant_file_initialization_enabled' + ) BEGIN + /* This needs to be a "dynamic" SQL statement because if the 'instant_file_initialization_enabled' column doesn't exist the procedure might fail on a bind error */ + SET @StringToExecute = N'SELECT @IFISetting = instant_file_initialization_enabled' + @crlf + + N'FROM sys.dm_server_services' + @crlf + + N'WHERE filename LIKE ''%sqlservr.exe%''' + @crlf + + N'OPTION (RECOMPILE);'; + + IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; + IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; + + EXEC dbo.sp_executesql + @StringToExecute + ,N'@IFISetting varchar(1) OUTPUT' + ,@IFISetting = @IFISetting OUTPUT - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 193) WITH NOWAIT; - - -- If this is Amazon RDS, use rdsadmin.dbo.rds_read_error_log - IF LEFT(CAST(SERVERPROPERTY('ComputerNamePhysicalNetBIOS') AS VARCHAR(8000)), 8) = 'EC2AMAZ-' - AND LEFT(CAST(SERVERPROPERTY('MachineName') AS VARCHAR(8000)), 8) = 'EC2AMAZ-' - AND db_id('rdsadmin') IS NOT NULL - AND EXISTS(SELECT * FROM master.sys.all_objects WHERE name IN ('rds_startup_tasks', 'rds_help_revlogin', 'rds_hexadecimal', 'rds_failover_tracking', 'rds_database_tracking', 'rds_track_change')) - BEGIN - INSERT INTO #ErrorLog - EXEC rdsadmin.dbo.rds_read_error_log 0, 1, N'Database Instant File Initialization: enabled'; - END - ELSE - BEGIN - BEGIN TRY - INSERT INTO #ErrorLog - EXEC sys.xp_readerrorlog 0, 1, N'Database Instant File Initialization: enabled'; - END TRY - BEGIN CATCH - IF @Debug IN (1, 2) RAISERROR('No permissions to execute xp_readerrorlog.', 0, 1) WITH NOWAIT; - END CATCH - END - - IF EXISTS - ( - SELECT 1/0 - FROM #ErrorLog - WHERE LEFT([Text], 45) = N'Database Instant File Initialization: enabled' - ) - BEGIN - INSERT INTO #BlitzResults - ( CheckID , - [Priority] , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT - 193 AS [CheckID] , - 250 AS [Priority] , - 'Server Info' AS [FindingsGroup] , - 'Instant File Initialization Enabled' AS [Finding] , - 'https://www.brentozar.com/go/instant' AS [URL] , - 'The service account has the Perform Volume Maintenance Tasks permission.'; - END; - else -- if version of sql server has instant_file_initialization_enabled column in dm_server_services, check that too - -- in the event the error log has been cycled and the startup messages are not in the current error log - begin - if EXISTS ( SELECT * - FROM sys.all_objects o - INNER JOIN sys.all_columns c ON o.object_id = c.object_id - WHERE o.name = 'dm_server_services' - AND c.name = 'instant_file_initialization_enabled' ) - begin - SET @StringToExecute = N' - INSERT INTO #BlitzResults - ( CheckID , - [Priority] , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT - 193 AS [CheckID] , - 250 AS [Priority] , - ''Server Info'' AS [FindingsGroup] , - ''Instant File Initialization Enabled'' AS [Finding] , - ''https://www.brentozar.com/go/instant'' AS [URL] , - ''The service account has the Perform Volume Maintenance Tasks permission.'' - where exists (select 1 FROM sys.dm_server_services - WHERE instant_file_initialization_enabled = ''Y'' - AND filename LIKE ''%sqlservr.exe%'') - OPTION (RECOMPILE);'; - EXEC(@StringToExecute); - end; - end; - END; + SET @IFIReadDMVFailed = 0; + END + ELSE + /* We couldn't get the instant_file_initialization_enabled column from sys.dm_server_services, fall back to read error log */ + BEGIN + SET @IFIReadDMVFailed = 1; + /* If this is Amazon RDS, we'll use the rdsadmin.dbo.rds_read_error_log */ + IF LEFT(CAST(SERVERPROPERTY('ComputerNamePhysicalNetBIOS') AS VARCHAR(8000)), 8) = 'EC2AMAZ-' + AND LEFT(CAST(SERVERPROPERTY('MachineName') AS VARCHAR(8000)), 8) = 'EC2AMAZ-' + AND db_id('rdsadmin') IS NOT NULL + AND EXISTS ( SELECT 1/0 + FROM master.sys.all_objects + WHERE name IN ('rds_startup_tasks', 'rds_help_revlogin', 'rds_hexadecimal', 'rds_failover_tracking', 'rds_database_tracking', 'rds_track_change') + ) + BEGIN + /* Amazon RDS detected, read rdsadmin.dbo.rds_read_error_log */ + INSERT INTO #ErrorLog + EXEC rdsadmin.dbo.rds_read_error_log 0, 1, N'Database Instant File Initialization: enabled'; + END + ELSE + BEGIN + /* Try to read the error log, this might fail due to permissions */ + BEGIN TRY + INSERT INTO #ErrorLog + EXEC sys.xp_readerrorlog 0, 1, N'Database Instant File Initialization: enabled'; + END TRY + BEGIN CATCH + IF @Debug IN (1, 2) RAISERROR('No permissions to execute xp_readerrorlog.', 0, 1) WITH NOWAIT; + SET @IFIAllFailed = 1; + END CATCH + END; + END; + + IF @IFIAllFailed = 0 + BEGIN + IF @IFIReadDMVFailed = 1 + /* We couldn't read the DMV so set the @IFISetting variable using the error log */ + BEGIN + IF EXISTS ( SELECT 1/0 + FROM #ErrorLog + WHERE LEFT([Text], 45) = N'Database Instant File Initialization: enabled' + ) + BEGIN + SET @IFISetting = 'Y'; + END + ELSE + BEGIN + SET @IFISetting = 'N'; + END; + END; + + IF NOT EXISTS ( SELECT 1/0 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 192 /* IFI disabled check disabled */ + ) AND @IFISetting = 'N' + BEGIN + INSERT INTO #BlitzResults + ( + CheckID , + [Priority] , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT + 192 AS [CheckID] , + 50 AS [Priority] , + 'Performance' AS [FindingsGroup] , + 'Instant File Initialization Not Enabled' AS [Finding] , + 'https://www.brentozar.com/go/instant' AS [URL] , + 'Consider enabling IFI for faster restores and data file growths.' AS [Details] + END; + + IF NOT EXISTS ( SELECT 1/0 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 193 /* IFI enabled check disabled */ + ) AND @IFISetting = 'Y' + BEGIN + INSERT INTO #BlitzResults + ( + CheckID , + [Priority] , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT + 193 AS [CheckID] , + 250 AS [Priority] , + 'Server Info' AS [FindingsGroup] , + 'Instant File Initialization Enabled' AS [Finding] , + 'https://www.brentozar.com/go/instant' AS [URL] , + 'The service account has the Perform Volume Maintenance Tasks permission.' AS [Details] + END; + END; + END; - /* Server Info - Instant File Initialization Not Enabled - Check 192 - SQL Server 2016 SP1 and newer */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 192 ) - AND EXISTS ( SELECT * - FROM sys.all_objects o - INNER JOIN sys.all_columns c ON o.object_id = c.object_id - WHERE o.name = 'dm_server_services' - AND c.name = 'instant_file_initialization_enabled' ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 192) WITH NOWAIT; - - SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) - SELECT 192 AS CheckID , - 50 AS Priority , - ''Server Info'' AS FindingsGroup , - ''Instant File Initialization Not Enabled'' AS Finding , - ''https://www.brentozar.com/go/instant'' AS URL , - ''Consider enabling IFI for faster restores and data file growths.'' - FROM sys.dm_server_services WHERE instant_file_initialization_enabled <> ''Y'' AND filename LIKE ''%sqlservr.exe%'' OPTION (RECOMPILE);'; - - IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; - IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; - - EXECUTE(@StringToExecute); - END; + /* End of checkId 192 */ + /* End of checkId 193 */ IF NOT EXISTS ( SELECT 1 FROM #SkipChecks @@ -9167,7 +9222,39 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 WHERE DatabaseName IS NULL AND CheckID = 211 ) BEGIN + /* Variables for check 211: */ + DECLARE + @powerScheme varchar(36) + ,@cpu_speed_mhz int + ,@cpu_speed_ghz decimal(18,2) + ,@ExecResult int; + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 211) WITH NOWAIT; + IF @sa = 0 RAISERROR('The errors: ''xp_regread() returned error 5, ''Access is denied.'''' can be safely ignored', 0, 1) WITH NOWAIT; + + /* Get power plan if set by group policy [Git Hub Issue #1620] */ + EXEC xp_regread @rootkey = N'HKEY_LOCAL_MACHINE', + @key = N'SOFTWARE\Policies\Microsoft\Power\PowerSettings', + @value_name = N'ActivePowerScheme', + @value = @powerScheme OUTPUT, + @no_output = N'no_output'; + + IF @powerScheme IS NULL /* If power plan was not set by group policy, get local value [Git Hub Issue #1620]*/ + EXEC xp_regread @rootkey = N'HKEY_LOCAL_MACHINE', + @key = N'SYSTEM\CurrentControlSet\Control\Power\User\PowerSchemes', + @value_name = N'ActivePowerScheme', + @value = @powerScheme OUTPUT; + + /* Get the cpu speed*/ + EXEC @ExecResult = xp_regread @rootkey = N'HKEY_LOCAL_MACHINE', + @key = N'HARDWARE\DESCRIPTION\System\CentralProcessor\0', + @value_name = N'~MHz', + @value = @cpu_speed_mhz OUTPUT; + + /* Convert the Megahertz to Gigahertz */ + IF @ExecResult != 0 RAISERROR('We couldn''t retrieve the CPU speed, you will see Unknown in the results', 0, 1) + + SET @cpu_speed_ghz = CAST(CAST(@cpu_speed_mhz AS decimal) / 1000 AS decimal(18,2)); INSERT INTO #BlitzResults ( CheckID , @@ -9183,7 +9270,7 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 'Power Plan' AS Finding, 'https://www.brentozar.com/blitz/power-mode/' AS URL, 'Your server has ' - + CAST(@cpu_speed_ghz as VARCHAR(4)) + + ISNULL(CAST(@cpu_speed_ghz as VARCHAR(8)), 'Unknown ') + 'GHz CPUs, and is in ' + CASE @powerScheme WHEN 'a1841308-3541-4fab-bc81-f71556f20b4a' @@ -9839,20 +9926,22 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 FROM #BlitzResults WHERE Priority > 0 AND Priority < 255 AND FindingsGroup IS NOT NULL AND Finding IS NOT NULL AND FindingsGroup <> 'Security' /* Specifically excluding security checks for public exports */) - SELECT - CASE - WHEN r.Priority <> COALESCE(rPrior.Priority, 0) OR r.FindingsGroup <> rPrior.FindingsGroup THEN @crlf + N'**Priority ' + CAST(COALESCE(r.Priority,N'') AS NVARCHAR(5)) + N': ' + COALESCE(r.FindingsGroup,N'') + N'**:' + @crlf + @crlf - ELSE N'' - END - + CASE WHEN r.Finding <> COALESCE(rPrior.Finding,N'') AND r.Finding <> COALESCE(rNext.Finding,N'') THEN N'- ' + COALESCE(r.Finding,N'') + N' ' + COALESCE(r.DatabaseName, N'') + N' - ' + COALESCE(r.Details,N'') + @crlf - WHEN r.Finding <> COALESCE(rPrior.Finding,N'') AND r.Finding = rNext.Finding AND r.Details = rNext.Details THEN N'- ' + COALESCE(r.Finding,N'') + N' - ' + COALESCE(r.Details,N'') + @crlf + @crlf + N' * ' + COALESCE(r.DatabaseName, N'') + @crlf - WHEN r.Finding <> COALESCE(rPrior.Finding,N'') AND r.Finding = rNext.Finding THEN N'- ' + COALESCE(r.Finding,N'') + @crlf + CASE WHEN r.DatabaseName IS NULL THEN N'' ELSE N' * ' + COALESCE(r.DatabaseName,N'') END + CASE WHEN r.Details <> rPrior.Details THEN N' - ' + COALESCE(r.Details,N'') + @crlf ELSE '' END - ELSE CASE WHEN r.DatabaseName IS NULL THEN N'' ELSE N' * ' + COALESCE(r.DatabaseName,N'') END + CASE WHEN r.Details <> rPrior.Details THEN N' - ' + COALESCE(r.Details,N'') + @crlf ELSE N'' + @crlf END - END + @crlf - FROM Results r - LEFT OUTER JOIN Results rPrior ON r.rownum = rPrior.rownum + 1 - LEFT OUTER JOIN Results rNext ON r.rownum = rNext.rownum - 1 - ORDER BY r.rownum FOR XML PATH(N''); + SELECT + Markdown = CONVERT(XML, STUFF((SELECT + CASE + WHEN r.Priority <> COALESCE(rPrior.Priority, 0) OR r.FindingsGroup <> rPrior.FindingsGroup THEN @crlf + N'**Priority ' + CAST(COALESCE(r.Priority,N'') AS NVARCHAR(5)) + N': ' + COALESCE(r.FindingsGroup,N'') + N'**:' + @crlf + @crlf + ELSE N'' + END + + CASE WHEN r.Finding <> COALESCE(rPrior.Finding,N'') AND r.Finding <> COALESCE(rNext.Finding,N'') THEN N'- ' + COALESCE(r.Finding,N'') + N' ' + COALESCE(r.DatabaseName, N'') + N' - ' + COALESCE(r.Details,N'') + @crlf + WHEN r.Finding <> COALESCE(rPrior.Finding,N'') AND r.Finding = rNext.Finding AND r.Details = rNext.Details THEN N'- ' + COALESCE(r.Finding,N'') + N' - ' + COALESCE(r.Details,N'') + @crlf + @crlf + N' * ' + COALESCE(r.DatabaseName, N'') + @crlf + WHEN r.Finding <> COALESCE(rPrior.Finding,N'') AND r.Finding = rNext.Finding THEN N'- ' + COALESCE(r.Finding,N'') + @crlf + @crlf + CASE WHEN r.DatabaseName IS NULL THEN N'' ELSE N' * ' + COALESCE(r.DatabaseName,N'') END + CASE WHEN r.Details <> rPrior.Details THEN N' - ' + COALESCE(r.Details,N'') + @crlf ELSE '' END + ELSE CASE WHEN r.DatabaseName IS NULL THEN N'' ELSE N' * ' + COALESCE(r.DatabaseName,N'') END + CASE WHEN r.Details <> rPrior.Details THEN N' - ' + COALESCE(r.Details,N'') + @crlf ELSE N'' + @crlf END + END + @crlf + FROM Results r + LEFT OUTER JOIN Results rPrior ON r.rownum = rPrior.rownum + 1 + LEFT OUTER JOIN Results rNext ON r.rownum = rNext.rownum - 1 + ORDER BY r.rownum FOR XML PATH(N''), ROOT('Markdown'), TYPE).value('/Markdown[1]','VARCHAR(MAX)'), 1, 2, '') + + ''); END; ELSE IF @OutputType = 'XML' BEGIN @@ -10016,7 +10105,7 @@ AS SET NOCOUNT ON; SET STATISTICS XML OFF; -SELECT @Version = '8.17', @VersionDate = '20231010'; +SELECT @Version = '8.18', @VersionDate = '20231222'; IF(@VersionCheckMode = 1) BEGIN @@ -10894,7 +10983,7 @@ AS SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.17', @VersionDate = '20231010'; + SELECT @Version = '8.18', @VersionDate = '20231222'; IF(@VersionCheckMode = 1) BEGIN @@ -12676,7 +12765,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.17', @VersionDate = '20231010'; +SELECT @Version = '8.18', @VersionDate = '20231222'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -19733,16 +19822,16 @@ END '; IF @Debug = 1 BEGIN - PRINT SUBSTRING(@StringToExecute, 0, 4000); - PRINT SUBSTRING(@StringToExecute, 4000, 8000); - PRINT SUBSTRING(@StringToExecute, 8000, 12000); - PRINT SUBSTRING(@StringToExecute, 12000, 16000); - PRINT SUBSTRING(@StringToExecute, 16000, 20000); - PRINT SUBSTRING(@StringToExecute, 20000, 24000); - PRINT SUBSTRING(@StringToExecute, 24000, 28000); - PRINT SUBSTRING(@StringToExecute, 28000, 32000); - PRINT SUBSTRING(@StringToExecute, 32000, 36000); - PRINT SUBSTRING(@StringToExecute, 36000, 40000); + PRINT SUBSTRING(@StringToExecute, 1, 4000); + PRINT SUBSTRING(@StringToExecute, 4001, 4000); + PRINT SUBSTRING(@StringToExecute, 8001, 4000); + PRINT SUBSTRING(@StringToExecute, 12001, 4000); + PRINT SUBSTRING(@StringToExecute, 16001, 4000); + PRINT SUBSTRING(@StringToExecute, 20001, 4000); + PRINT SUBSTRING(@StringToExecute, 24001, 4000); + PRINT SUBSTRING(@StringToExecute, 28001, 4000); + PRINT SUBSTRING(@StringToExecute, 32001, 4000); + PRINT SUBSTRING(@StringToExecute, 36001, 4000); END; EXEC sp_executesql @StringToExecute, N'@Top INT, @min_duration INT, @min_back INT, @CheckDateOverride DATETIMEOFFSET, @MinimumExecutionCount INT', @Top, @DurationFilter_i, @MinutesBack, @CheckDateOverride, @MinimumExecutionCount; @@ -20035,7 +20124,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.17', @VersionDate = '20231010'; +SELECT @Version = '8.18', @VersionDate = '20231222'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -21201,16 +21290,16 @@ BEGIN TRY RAISERROR (N'Inserting data into #IndexColumns for clustered indexes and heaps',0,1) WITH NOWAIT; IF @Debug = 1 BEGIN - PRINT SUBSTRING(@dsql, 0, 4000); - PRINT SUBSTRING(@dsql, 4000, 8000); - PRINT SUBSTRING(@dsql, 8000, 12000); - PRINT SUBSTRING(@dsql, 12000, 16000); - PRINT SUBSTRING(@dsql, 16000, 20000); - PRINT SUBSTRING(@dsql, 20000, 24000); - PRINT SUBSTRING(@dsql, 24000, 28000); - PRINT SUBSTRING(@dsql, 28000, 32000); - PRINT SUBSTRING(@dsql, 32000, 36000); - PRINT SUBSTRING(@dsql, 36000, 40000); + PRINT SUBSTRING(@dsql, 1, 4000); + PRINT SUBSTRING(@dsql, 4001, 4000); + PRINT SUBSTRING(@dsql, 8001, 4000); + PRINT SUBSTRING(@dsql, 12001, 4000); + PRINT SUBSTRING(@dsql, 16001, 4000); + PRINT SUBSTRING(@dsql, 20001, 4000); + PRINT SUBSTRING(@dsql, 24001, 4000); + PRINT SUBSTRING(@dsql, 28001, 4000); + PRINT SUBSTRING(@dsql, 32001, 4000); + PRINT SUBSTRING(@dsql, 36001, 4000); END; BEGIN TRY INSERT #IndexColumns ( database_id, [schema_name], [object_id], index_id, key_ordinal, is_included_column, is_descending_key, partition_ordinal, @@ -22578,6 +22667,7 @@ SELECT FROM #IndexSanity; RAISERROR (N'Populate #PartitionCompressionInfo.',0,1) WITH NOWAIT; +IF OBJECT_ID('tempdb..#maps') IS NOT NULL DROP TABLE #maps; WITH maps AS ( @@ -22592,6 +22682,7 @@ SELECT * INTO #maps FROM maps; +IF OBJECT_ID('tempdb..#grps') IS NOT NULL DROP TABLE #grps; WITH grps AS ( @@ -26217,10 +26308,11 @@ WITH RECOMPILE AS BEGIN SET STATISTICS XML OFF; - SET NOCOUNT, XACT_ABORT ON; + SET NOCOUNT ON; + SET XACT_ABORT OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.17', @VersionDate = '20231010'; + SELECT @Version = '8.18', @VersionDate = '20231222'; IF @VersionCheckMode = 1 BEGIN @@ -26344,6 +26436,20 @@ BEGIN THEN 1 ELSE 0 END, + @MI bit = + CASE + WHEN + ( + SELECT + CONVERT + ( + integer, + SERVERPROPERTY('EngineEdition') + ) + ) = 8 + THEN 1 + ELSE 0 + END, @RDS bit = CASE WHEN LEFT(CAST(SERVERPROPERTY('ComputerNamePhysicalNetBIOS') AS varchar(8000)), 8) <> 'EC2AMAZ-' @@ -26482,6 +26588,17 @@ BEGIN @StartDateUTC = @StartDate, @EndDateUTC = @EndDate; + IF + ( + @MI = 1 + AND @EventSessionName = N'system_health' + AND @TargetSessionType IS NULL + ) + BEGIN + SET + @TargetSessionType = N'ring_buffer'; + END; + IF @Azure = 0 BEGIN IF NOT EXISTS @@ -26499,7 +26616,7 @@ BEGIN RETURN; END; END; - + IF @Azure = 1 BEGIN IF NOT EXISTS @@ -28029,7 +28146,7 @@ BEGIN COUNT_BIG(DISTINCT dp.event_date) ) + N' deadlocks.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT dp.event_date) DESC) FROM #deadlock_process AS dp @@ -28069,7 +28186,7 @@ BEGIN SELECT 1/0 FROM sys.databases AS d - WHERE d.name = dow.database_name + WHERE d.name COLLATE DATABASE_DEFAULT = dow.database_name COLLATE DATABASE_DEFAULT AND d.is_read_committed_snapshot_on = 1 ) THEN N'You already enabled RCSI, but...' @@ -28084,7 +28201,7 @@ BEGIN COUNT_BIG(DISTINCT dow.event_date) ) + N' deadlock(s) between read queries and modification queries.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT dow.event_date) DESC) FROM #deadlock_owner_waiter AS dow @@ -28146,7 +28263,7 @@ BEGIN COUNT_BIG(DISTINCT dow.event_date) ) + N' deadlock(s).', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT dow.event_date) DESC) FROM #deadlock_owner_waiter AS dow @@ -28189,7 +28306,7 @@ BEGIN COUNT_BIG(DISTINCT dow.event_date) ) + N' deadlock(s).', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT dow.event_date) DESC) FROM #deadlock_owner_waiter AS dow @@ -28238,7 +28355,7 @@ BEGIN COUNT_BIG(DISTINCT dow.event_date) ) + N' deadlock(s).', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT dow.event_date) DESC) FROM #deadlock_owner_waiter AS dow @@ -28287,7 +28404,7 @@ BEGIN COUNT_BIG(DISTINCT dp.event_date) ) + N' instances of Serializable deadlocks.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT dp.event_date) DESC) FROM #deadlock_process AS dp @@ -28330,7 +28447,7 @@ BEGIN COUNT_BIG(DISTINCT dp.event_date) ) + N' instances of Repeatable Read deadlocks.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT dp.event_date) DESC) FROM #deadlock_process AS dp @@ -28392,7 +28509,7 @@ BEGIN N'UNKNOWN' ) + N'.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT dp.event_date) DESC) FROM #deadlock_process AS dp @@ -28504,7 +28621,7 @@ BEGIN 1, N'' ) + N' locks.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY CONVERT(bigint, lt.lock_count) DESC) FROM lock_types AS lt @@ -28683,7 +28800,7 @@ BEGIN COUNT_BIG(DISTINCT ds.id) ) + N' deadlocks.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT ds.id) DESC) FROM #deadlock_stack AS ds @@ -28784,19 +28901,19 @@ BEGIN ) ), wait_time_hms = - /*the more wait time you rack up the less accurate this gets, + /*the more wait time you rack up the less accurate this gets, it's either that or erroring out*/ - CASE - WHEN + CASE + WHEN SUM ( CONVERT ( - bigint, + bigint, dp.wait_time ) )/1000 > 2147483647 - THEN + THEN CONVERT ( nvarchar(30), @@ -28809,7 +28926,7 @@ BEGIN ( CONVERT ( - bigint, + bigint, dp.wait_time ) ) @@ -28820,16 +28937,16 @@ BEGIN ), 14 ) - WHEN + WHEN SUM ( CONVERT ( - bigint, + bigint, dp.wait_time ) ) BETWEEN 2147483648 AND 2147483647000 - THEN + THEN CONVERT ( nvarchar(30), @@ -28842,7 +28959,7 @@ BEGIN ( CONVERT ( - bigint, + bigint, dp.wait_time ) ) @@ -28924,7 +29041,7 @@ BEGIN 14 ) + N' [dd hh:mm:ss:ms] of deadlock wait time.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY cs.total_waits DESC) FROM chopsuey AS cs @@ -28998,19 +29115,19 @@ BEGIN ) ) + N' ' + - /*the more wait time you rack up the less accurate this gets, + /*the more wait time you rack up the less accurate this gets, it's either that or erroring out*/ - CASE - WHEN + CASE + WHEN SUM ( CONVERT ( - bigint, + bigint, wt.total_wait_time_ms ) )/1000 > 2147483647 - THEN + THEN CONVERT ( nvarchar(30), @@ -29023,7 +29140,7 @@ BEGIN ( CONVERT ( - bigint, + bigint, wt.total_wait_time_ms ) ) @@ -29034,16 +29151,16 @@ BEGIN ), 14 ) - WHEN + WHEN SUM ( CONVERT ( - bigint, + bigint, wt.total_wait_time_ms ) ) BETWEEN 2147483648 AND 2147483647000 - THEN + THEN CONVERT ( nvarchar(30), @@ -29056,7 +29173,7 @@ BEGIN ( CONVERT ( - bigint, + bigint, wt.total_wait_time_ms ) ) @@ -29089,7 +29206,7 @@ BEGIN 14 ) END + N' [dd hh:mm:ss:ms] of deadlock wait time.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY SUM(CONVERT(bigint, wt.total_wait_time_ms)) DESC) FROM wait_time AS wt @@ -29127,7 +29244,7 @@ BEGIN N'There have been ' + RTRIM(COUNT_BIG(DISTINCT aj.event_date)) + N' deadlocks from this Agent Job and Step.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT aj.event_date) DESC) FROM #agent_job AS aj @@ -29907,23 +30024,23 @@ BEGIN BEGIN SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Results to client %s', 0, 1, @d) WITH NOWAIT; - + IF @Debug = 1 BEGIN SET STATISTICS XML ON; END; - + EXEC sys.sp_executesql @deadlock_result; - + IF @Debug = 1 BEGIN SET STATISTICS XML OFF; PRINT @deadlock_result; END; - + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Getting available execution plans for deadlocks %s', 0, 1, @d) WITH NOWAIT; - + SELECT DISTINCT available_plans = 'available_plans', @@ -29959,7 +30076,7 @@ BEGIN CONVERT(decimal(38, 6), deqs.total_worker_time / 1000. / deqs.execution_count), total_elapsed_time_ms = deqs.total_elapsed_time / 1000., - avg_elapsed_time = + avg_elapsed_time_ms = CONVERT(decimal(38, 6), deqs.total_elapsed_time / 1000. / deqs.execution_count), executions_per_second = ISNULL @@ -29971,7 +30088,7 @@ BEGIN ( SECOND, deqs.creation_time, - deqs.last_execution_time + NULLIF(deqs.last_execution_time, '1900-01-01 00:00:00.000') ), 0 ), @@ -29990,7 +30107,7 @@ BEGIN min_used_grant_mb = deqs.min_used_grant_kb * 8. / 1024., max_used_grant_mb = - deqs.max_used_grant_kb * 8. / 1024., + deqs.max_used_grant_kb * 8. / 1024., deqs.min_reserved_threads, deqs.max_reserved_threads, deqs.min_used_threads, @@ -30000,21 +30117,21 @@ BEGIN FROM sys.dm_exec_query_stats AS deqs WHERE EXISTS ( - SELECT - 1/0 - FROM #available_plans AS ap - WHERE ap.sql_handle = deqs.sql_handle + SELECT + 1/0 + FROM #available_plans AS ap + WHERE ap.sql_handle = deqs.sql_handle ) AND deqs.query_hash IS NOT NULL; - - CREATE CLUSTERED INDEX - deqs + + CREATE CLUSTERED INDEX + deqs ON #dm_exec_query_stats ( - sql_handle, + sql_handle, plan_handle ); - + SELECT ap.available_plans, ap.database_name, @@ -30028,7 +30145,7 @@ BEGIN ap.total_worker_time_ms, ap.avg_worker_time_ms, ap.total_elapsed_time_ms, - ap.avg_elapsed_time, + ap.avg_elapsed_time_ms, ap.total_logical_reads_mb, ap.total_physical_reads_mb, ap.total_logical_writes_mb, @@ -30046,7 +30163,7 @@ BEGIN ap.statement_end_offset FROM ( - + SELECT ap.*, c.statement_start_offset, @@ -30057,7 +30174,7 @@ BEGIN c.total_worker_time_ms, c.avg_worker_time_ms, c.total_elapsed_time_ms, - c.avg_elapsed_time, + c.avg_elapsed_time_ms, c.executions_per_second, c.total_physical_reads_mb, c.total_logical_writes_mb, @@ -30096,10 +30213,10 @@ BEGIN OPTION(RECOMPILE, LOOP JOIN, HASH JOIN); RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - + SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Returning findings %s', 0, 1, @d) WITH NOWAIT; - + SELECT df.check_id, df.database_name, @@ -30111,7 +30228,7 @@ BEGIN df.check_id, df.sort_order OPTION(RECOMPILE); - + SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; END; /*done with output to client app.*/ @@ -30350,7 +30467,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.17', @VersionDate = '20231010'; +SELECT @Version = '8.18', @VersionDate = '20231222'; IF(@VersionCheckMode = 1) BEGIN RETURN; @@ -30411,8 +30528,6 @@ IF (SELECT CONVERT(NVARCHAR(128), SERVERPROPERTY ('EDITION'))) <> 'SQL Azure' IF @Help = 1 BEGIN - - SELECT N'You have requested assistance. It will arrive as soon as humanly possible.' AS [Take four red capsules, help is on the way]; PRINT N' sp_BlitzQueryStore from http://FirstResponderKit.org @@ -30456,6 +30571,257 @@ IF @Help = 1 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. '; + /*Parameter info*/ + SELECT N'@Help' AS [Parameter Name] , + N'BIT' AS [Data Type] , + N'0' AS [Default Value], + N'Displays this help message.' AS [Parameter Description] + UNION ALL + SELECT N'@DatabaseName', + N'NVARCHAR(128)', + N'NULL', + N'The name of the database you want to check the query store for.' + UNION ALL + SELECT N'@Top', + N'INT', + N'3', + N'The number of records to retrieve and analyze from the query store. The following system views are used: query_store_query, query_context_settings, query_store_wait_stats, query_store_runtime_stats,query_store_plan.' + + UNION ALL + SELECT N'@StartDate', + N'DATETIME2(7)', + N'NULL', + N'Get query store info starting from this date. When not specified, sp_BlitzQueryStore gets info from the last 7 days' + UNION ALL + SELECT N'@EndDate', + N'DATETIME2(7)', + N'NULL', + N'Get query store info until this date. When not specified, sp_BlitzQueryStore gets info from the last 7 days' + UNION ALL + SELECT N'@MinimumExecutionCount', + N'INT', + N'NULL', + N'When a value is specified, sp_BlitzQueryStore gets info for queries where count_executions >= @MinimumExecutionCount' + UNION ALL + SELECT N'@DurationFilter', + N'DECIMAL(38,4)', + N'NULL', + N'Time unit - seconds. When a value is specified, sp_BlitzQueryStore gets info for queries where the average duration >= @DurationFilter' + UNION ALL + SELECT N'@StoredProcName', + N'NVARCHAR(128)', + N'NULL', + N'Get information for this specific stored procedure.' + UNION ALL + SELECT N'@Failed', + N'BIT', + N'0', + N'When set to 1, only information about failed queries is returned.' + UNION ALL + SELECT N'@PlanIdFilter', + N'INT', + N'NULL', + N'The ID of the plan you want to check for.' + UNION ALL + SELECT N'@QueryIdFilter', + N'INT', + N'NULL', + N'The ID of the query you want to check for.' + UNION ALL + SELECT N'@ExportToExcel', + N'BIT', + N'0', + N'When set to 1, prepares output for exporting to Excel. Newlines and additional whitespace are removed from query text and the execution plan is not displayed.' + UNION ALL + SELECT N'@HideSummary', + N'BIT', + N'0', + N'When set to 1, hides the findings summary result set.' + UNION ALL + SELECT N'@SkipXML', + N'BIT', + N'0', + N'When set to 1, missing_indexes, implicit_conversion_info, cached_execution_parameters, are not returned. Does not affect query_plan_xml' + UNION ALL + SELECT N'@Debug', + N'BIT', + N'0', + N'Setting this to 1 will print dynamic SQL and select data from all tables used.' + UNION ALL + SELECT N'@ExpertMode', + N'BIT', + N'0', + N'When set to 1, more checks are done. Examples: many to many merge joins, row goals, adaptive joins, stats info, bad scans and plan forcing, computed columns that reference scalar UDFs.' + UNION ALL + SELECT N'@VersionCheckMode', + N'BIT', + N'0', + N'Outputs the version number and date.' + + /* Column definitions */ + SELECT 'database_name' AS [Column Name], + 'NVARCHAR(258)' AS [Data Type], + 'The name of the database where the plan was encountered.' AS [Column Description] + UNION ALL + SELECT 'query_cost', + 'FLOAT', + 'The cost of the execution plan in query bucks.' + UNION ALL + SELECT 'plan_id', + 'BIGINT', + 'The ID of the plan from sys.query_store_plan.' + UNION ALL + SELECT 'query_id', + 'BIGINT', + 'The ID of the query from sys.query_store_query.' + UNION ALL + SELECT 'query_id_all_plan_ids', + 'VARCHAR(8000)', + 'Comma-separated list of all query plan IDs associated with this query.' + UNION ALL + SELECT 'query_sql_text', + 'NVARCHAR(MAX)', + 'The text of the query, as provided by the user/app. Includes whitespaces, hints and comments. Comments and spaces before and after the query text are ignored.' + UNION ALL + SELECT 'proc_or_function_name', + 'NVARCHAR(258)', + 'If the query is part of a function/stored procedure, you''ll see here the name of its parent object.' + UNION ALL + SELECT 'query_plan_xml', + ' XML', + 'The query plan. Click to display a graphical plan.' + UNION ALL + SELECT 'warnings', + 'VARCHAR(MAX)', + 'A list of individual warnings generated by this query.' + UNION ALL + SELECT 'pattern', + 'NVARCHAR(512)', + 'A list of performance related patterns identified for this query.' + UNION ALL + SELECT 'parameter_sniffing_symptoms', + 'NVARCHAR(4000)', + 'A list of all the identified symptoms that are usually indicators of parameter sniffing.' + UNION ALL + SELECT 'last_force_failure_reason_desc', + 'NVARCHAR(258)', + 'Reason why plan forcing failed. NONE if plan isn''t forced.' + UNION ALL + SELECT 'top_three_waits', + 'NVARCHAR(MAX)', + 'The top 3 wait types, and their times in milliseconds, recorded for this query.' + UNION ALL + SELECT 'missing_indexes', + 'XML', + 'Missing index recommendations retrieved from the query plan.' + UNION ALL + SELECT 'implicit_conversion_info', + 'XML', + 'Information about the implicit conversion warnings,if any, retrieved from the query plan.' + UNION ALL + SELECT 'cached_execution_parameters', + 'XML', + 'Names, data types, and values for the parameters used when the query plan was compiled.' + UNION ALL + SELECT 'count_executions ', + 'BIGINT', + 'The number of executions of this particular query.' + UNION ALL + SELECT 'count_compiles', + 'BIGINT', + 'The number of plan compilations for this particular query.' + UNION ALL + SELECT 'total_cpu_time', + 'BIGINT', + 'Total CPU time, reported in milliseconds, that was consumed by all executions of this query.' + UNION ALL + SELECT 'avg_cpu_time ', + 'BIGINT', + 'Average CPU time, reported in milliseconds, consumed by each execution of this query.' + UNION ALL + SELECT 'total_duration', + 'BIGINT', + 'Total elapsed time, reported in milliseconds, consumed by all executions of this query.' + UNION ALL + SELECT 'avg_duration', + 'BIGINT', + 'Average elapsed time, reported in milliseconds, consumed by each execution of this query.' + UNION ALL + SELECT 'total_logical_io_reads', + 'BIGINT', + 'Total logical reads, reported in MB, performed by this query.' + UNION ALL + SELECT 'avg_logical_io_reads', + 'BIGINT', + 'Average logical reads, reported in MB, performed by each execution of this query.' + UNION ALL + SELECT 'total_physical_io_reads', + 'BIGINT', + 'Total physical reads, reported in MB, performed by this query.' + UNION ALL + SELECT 'avg_physical_io_reads', + 'BIGINT', + 'Average physical reads, reported in MB, performed by each execution of this query.' + UNION ALL + SELECT 'total_logical_io_writes', + 'BIGINT', + 'Total logical writes, reported in MB, performed by this query.' + UNION ALL + SELECT 'avg_logical_io_writes', + 'BIGINT', + 'Average logical writes, reported in MB, performed by each execution of this query.' + UNION ALL + SELECT 'total_rowcount', + 'BIGINT', + 'Total number of rows returned for all executions of this query.' + UNION ALL + SELECT 'avg_rowcount', + 'BIGINT', + 'Average number of rows returned by each execution of this query.' + UNION ALL + SELECT 'total_query_max_used_memory', + 'DECIMAL(38,2)', + 'Total max memory grant, reported in MB, used by this query.' + UNION ALL + SELECT 'avg_query_max_used_memory', + 'DECIMAL(38,2)', + 'Average max memory grant, reported in MB, used by each execution of this query.' + UNION ALL + SELECT 'total_tempdb_space_used', + 'DECIMAL(38,2)', + 'Total tempdb space, reported in MB, used by this query.' + UNION ALL + SELECT 'avg_tempdb_space_used', + 'DECIMAL(38,2)', + 'Average tempdb space, reported in MB, used by each execution of this query.' + UNION ALL + SELECT 'total_log_bytes_used', + 'DECIMAL(38,2)', + 'Total number of bytes in the database log used by this query.' + UNION ALL + SELECT 'avg_log_bytes_used', + 'DECIMAL(38,2)', + 'Average number of bytes in the database log used by each execution of this query.' + UNION ALL + SELECT 'total_num_physical_io_reads', + 'DECIMAL(38,2)', + 'Total number of physical I/O reads performed by this query (expressed as a number of read I/O operations).' + UNION ALL + SELECT 'avg_num_physical_io_reads', + 'DECIMAL(38,2)', + 'Average number of physical I/O reads performed by each execution of this query (expressed as a number of read I/O operations).' + UNION ALL + SELECT 'first_execution_time', + 'DATETIME2', + 'First execution time for this query within the aggregation interval. This is the end time of the query execution.' + UNION ALL + SELECT 'last_execution_time', + 'DATETIME2', + 'Last execution time for this query within the aggregation interval. This is the end time of the query execution.' + UNION ALL + SELECT 'context_settings', + 'NVARCHAR(512)', + 'Contains information about context settings associated with this query.'; RETURN; END; @@ -36150,7 +36516,7 @@ BEGIN SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.17', @VersionDate = '20231010'; + SELECT @Version = '8.18', @VersionDate = '20231222'; IF(@VersionCheckMode = 1) BEGIN @@ -37546,6 +37912,8 @@ DELETE FROM dbo.SqlServerVersions; INSERT INTO dbo.SqlServerVersions (MajorVersionNumber, MinorVersionNumber, Branch, [Url], ReleaseDate, MainstreamSupportEndDate, ExtendedSupportEndDate, MajorVersionName, MinorVersionName) VALUES + (16, 4095, 'CU10', 'https://support.microsoft.com/en-us/help/5031778', '2023-11-16', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 10'), + (16, 4085, 'CU9', 'https://support.microsoft.com/en-us/help/5030731', '2023-10-12', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 9'), (16, 4075, 'CU8', 'https://support.microsoft.com/en-us/help/5029666', '2023-09-14', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 8'), (16, 4065, 'CU7', 'https://support.microsoft.com/en-us/help/5028743', '2023-08-10', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 7'), (16, 4055, 'CU6', 'https://support.microsoft.com/en-us/help/5027505', '2023-07-13', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 6'), @@ -37556,6 +37924,8 @@ VALUES (16, 4003, 'CU1', 'https://support.microsoft.com/en-us/help/5022375', '2023-02-16', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 1'), (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'), + (15, 4345, 'CU24', 'https://support.microsoft.com/kb/5031908', '2023-12-14', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 24'), + (15, 4335, 'CU23', 'https://support.microsoft.com/kb/5030333', '2023-10-12', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 23'), (15, 4322, 'CU22', 'https://support.microsoft.com/kb/5027702', '2023-08-14', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 22'), (15, 4316, 'CU21', 'https://support.microsoft.com/kb/5025808', '2023-06-15', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 21'), (15, 4312, 'CU20', 'https://support.microsoft.com/kb/5024276', '2023-04-13', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 20'), @@ -37583,6 +37953,7 @@ VALUES (15, 4003, 'CU1', 'https://support.microsoft.com/en-us/help/4527376', '2020-01-07', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 1 '), (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 '), + (14, 3465, 'RTM CU31 GDR', 'https://support.microsoft.com/kb/5029376', '2023-10-12', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 31 GDR'), (14, 3460, 'RTM CU31 GDR', 'https://support.microsoft.com/kb/5021126', '2023-02-14', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 31 GDR'), (14, 3456, 'RTM CU31', 'https://support.microsoft.com/en-us/help/5016884', '2022-09-20', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 31'), (14, 3451, 'RTM CU30', 'https://support.microsoft.com/en-us/help/5013756', '2022-07-13', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 30'), @@ -37980,7 +38351,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.17', @VersionDate = '20231010'; +SELECT @Version = '8.18', @VersionDate = '20231222'; IF(@VersionCheckMode = 1) BEGIN diff --git a/SqlServerVersions.sql b/SqlServerVersions.sql index fdac63763..40aea38ce 100644 --- a/SqlServerVersions.sql +++ b/SqlServerVersions.sql @@ -41,6 +41,7 @@ DELETE FROM dbo.SqlServerVersions; INSERT INTO dbo.SqlServerVersions (MajorVersionNumber, MinorVersionNumber, Branch, [Url], ReleaseDate, MainstreamSupportEndDate, ExtendedSupportEndDate, MajorVersionName, MinorVersionName) VALUES + (16, 4095, 'CU10', 'https://support.microsoft.com/en-us/help/5031778', '2023-11-16', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 10'), (16, 4085, 'CU9', 'https://support.microsoft.com/en-us/help/5030731', '2023-10-12', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 9'), (16, 4075, 'CU8', 'https://support.microsoft.com/en-us/help/5029666', '2023-09-14', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 8'), (16, 4065, 'CU7', 'https://support.microsoft.com/en-us/help/5028743', '2023-08-10', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 7'), @@ -52,6 +53,7 @@ VALUES (16, 4003, 'CU1', 'https://support.microsoft.com/en-us/help/5022375', '2023-02-16', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 1'), (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'), + (15, 4345, 'CU24', 'https://support.microsoft.com/kb/5031908', '2023-12-14', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 24'), (15, 4335, 'CU23', 'https://support.microsoft.com/kb/5030333', '2023-10-12', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 23'), (15, 4322, 'CU22', 'https://support.microsoft.com/kb/5027702', '2023-08-14', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 22'), (15, 4316, 'CU21', 'https://support.microsoft.com/kb/5025808', '2023-06-15', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 21'), diff --git a/sp_AllNightLog.sql b/sp_AllNightLog.sql index da4cf313f..1e7b1dddd 100644 --- a/sp_AllNightLog.sql +++ b/sp_AllNightLog.sql @@ -31,7 +31,7 @@ SET STATISTICS XML OFF; BEGIN; -SELECT @Version = '8.17', @VersionDate = '20231010'; +SELECT @Version = '8.18', @VersionDate = '20231222'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_AllNightLog_Setup.sql b/sp_AllNightLog_Setup.sql index cfc2042fd..8238c897e 100644 --- a/sp_AllNightLog_Setup.sql +++ b/sp_AllNightLog_Setup.sql @@ -38,7 +38,7 @@ SET STATISTICS XML OFF; BEGIN; -SELECT @Version = '8.17', @VersionDate = '20231010'; +SELECT @Version = '8.18', @VersionDate = '20231222'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_Blitz.sql b/sp_Blitz.sql index 473f4c65d..9804f45d8 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -38,7 +38,7 @@ AS SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.17', @VersionDate = '20231010'; + SELECT @Version = '8.18', @VersionDate = '20231222'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) diff --git a/sp_BlitzAnalysis.sql b/sp_BlitzAnalysis.sql index e7ebb1dfd..f96c56b9a 100644 --- a/sp_BlitzAnalysis.sql +++ b/sp_BlitzAnalysis.sql @@ -37,7 +37,7 @@ AS SET NOCOUNT ON; SET STATISTICS XML OFF; -SELECT @Version = '8.17', @VersionDate = '20231010'; +SELECT @Version = '8.18', @VersionDate = '20231222'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_BlitzBackups.sql b/sp_BlitzBackups.sql index 1f31ddaa0..b11b48d20 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.17', @VersionDate = '20231010'; + SELECT @Version = '8.18', @VersionDate = '20231222'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_BlitzCache.sql b/sp_BlitzCache.sql index 6e092262f..07b3e1ab4 100644 --- a/sp_BlitzCache.sql +++ b/sp_BlitzCache.sql @@ -281,7 +281,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.17', @VersionDate = '20231010'; +SELECT @Version = '8.18', @VersionDate = '20231222'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) diff --git a/sp_BlitzFirst.sql b/sp_BlitzFirst.sql index a35db05ca..3b1624e6e 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.17', @VersionDate = '20231010'; +SELECT @Version = '8.18', @VersionDate = '20231222'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_BlitzInMemoryOLTP.sql b/sp_BlitzInMemoryOLTP.sql index f2294de10..fc56a3cc6 100644 --- a/sp_BlitzInMemoryOLTP.sql +++ b/sp_BlitzInMemoryOLTP.sql @@ -82,7 +82,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI */ AS DECLARE @ScriptVersion VARCHAR(30); -SELECT @ScriptVersion = '1.8', @VersionDate = '20231010'; +SELECT @ScriptVersion = '1.8', @VersionDate = '20231222'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index 2faaa3afa..0e23b24fc 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -48,7 +48,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.17', @VersionDate = '20231010'; +SELECT @Version = '8.18', @VersionDate = '20231222'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index 4885ad3ce..848a324c2 100644 --- a/sp_BlitzLock.sql +++ b/sp_BlitzLock.sql @@ -36,7 +36,7 @@ BEGIN SET XACT_ABORT OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.17', @VersionDate = '20231010'; + SELECT @Version = '8.18', @VersionDate = '20231222'; IF @VersionCheckMode = 1 BEGIN diff --git a/sp_BlitzQueryStore.sql b/sp_BlitzQueryStore.sql index a5fa4abe0..084e988f4 100644 --- a/sp_BlitzQueryStore.sql +++ b/sp_BlitzQueryStore.sql @@ -57,7 +57,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.17', @VersionDate = '20231010'; +SELECT @Version = '8.18', @VersionDate = '20231222'; IF(@VersionCheckMode = 1) BEGIN RETURN; diff --git a/sp_BlitzWho.sql b/sp_BlitzWho.sql index d3e9f1004..d623706c8 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.17', @VersionDate = '20231010'; + SELECT @Version = '8.18', @VersionDate = '20231222'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_DatabaseRestore.sql b/sp_DatabaseRestore.sql index 640ad8c89..07273a2b8 100755 --- a/sp_DatabaseRestore.sql +++ b/sp_DatabaseRestore.sql @@ -45,7 +45,7 @@ SET STATISTICS XML OFF; /*Versioning details*/ -SELECT @Version = '8.17', @VersionDate = '20231010'; +SELECT @Version = '8.18', @VersionDate = '20231222'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_ineachdb.sql b/sp_ineachdb.sql index cc9707de6..9cf874bac 100644 --- a/sp_ineachdb.sql +++ b/sp_ineachdb.sql @@ -36,7 +36,7 @@ BEGIN SET NOCOUNT ON; SET STATISTICS XML OFF; - SELECT @Version = '8.17', @VersionDate = '20231010'; + SELECT @Version = '8.18', @VersionDate = '20231222'; IF(@VersionCheckMode = 1) BEGIN From bf57b8d23410a4ef1e39460bc3d56e59b64ec926 Mon Sep 17 00:00:00 2001 From: Henrik Staun Poulsen Date: Mon, 25 Dec 2023 20:09:17 +0100 Subject: [PATCH 493/662] Update sp_BlitzIndex.sql with better #tmp table names Named the new tables #dm_db_partition_stats_etc and #dm_db_index_operational_stats Tested on a smaller db (27TB) where the new version runs in 3 seconds, and the old 11 seconds. Same output. I'll see if I can get a result set from the old version on the larger database, now that there are a couple of holidays. Previously I have given up after 11 hours. My new version ran in 3 hours. --- sp_BlitzIndex.sql | 184 +++++++++++++++++++++++----------------------- 1 file changed, 90 insertions(+), 94 deletions(-) diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index e84f3f46a..7f7fa13e8 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -257,10 +257,10 @@ IF OBJECT_ID('tempdb..#FilteredIndexes') IS NOT NULL IF OBJECT_ID('tempdb..#Ignore_Databases') IS NOT NULL DROP TABLE #Ignore_Databases -IF OBJECT_ID('tempdb..#H') IS NOT NULL - DROP TABLE #H -IF OBJECT_ID('tempdb..#OS') IS NOT NULL - DROP TABLE #OS +IF OBJECT_ID('tempdb..#dm_db_partition_stats_etc') IS NOT NULL + DROP TABLE #dm_db_partition_stats_etc +IF OBJECT_ID('tempdb..#dm_db_index_operational_stats') IS NOT NULL + DROP TABLE #dm_db_index_operational_stats RAISERROR (N'Create temp tables.',0,1) WITH NOWAIT; CREATE TABLE #BlitzIndexResults @@ -1442,86 +1442,89 @@ BEGIN TRY --NOTE: If you want to use the newer syntax for 2012+, you'll have to change 2147483647 to 11 on line ~819 --This change was made because on a table with lots of paritions, the OUTER APPLY was crazy slow. -DROP TABLE if exists #h -create table #h -( - database_id smallint not null - , object_id int not null - , sname sysname NULL - , index_id int - , partition_number int - , partition_id bigint - , row_count bigint - , reserved_MB bigint - , reserved_LOB_MB bigint - , reserved_row_overflow_MB bigint - , lock_escalation_desc varchar(1000)/*?*/ - , data_compression_desc varchar(100)/*?*/ - , reserved_dictionary_MB bigint -) -drop TABLE if exists #os -create table #os -( - database_id smallint not null - , object_id int not null - , index_id int - , partition_number int - , hobt_id bigint - , leaf_insert_count bigint - , leaf_delete_count bigint - , leaf_update_count bigint - , leaf_ghost_count bigint - , nonleaf_insert_count bigint - , nonleaf_delete_count bigint - , nonleaf_update_count bigint - , leaf_allocation_count bigint - , nonleaf_allocation_count bigint - , leaf_page_merge_count bigint - , nonleaf_page_merge_count bigint - , range_scan_count bigint - , singleton_lookup_count bigint - , forwarded_fetch_count bigint - , lob_fetch_in_pages bigint - , lob_fetch_in_bytes bigint - , lob_orphan_create_count bigint - , lob_orphan_insert_count bigint - , row_overflow_fetch_in_pages bigint - , row_overflow_fetch_in_bytes bigint - , column_value_push_off_row_count bigint - , column_value_pull_in_row_count bigint - , row_lock_count bigint - , row_lock_wait_count bigint - , row_lock_wait_in_ms bigint - , page_lock_count bigint - , page_lock_wait_count bigint - , page_lock_wait_in_ms bigint - , index_lock_promotion_attempt_count bigint - , index_lock_promotion_count bigint - , page_latch_wait_count bigint - , page_latch_wait_in_ms bigint - , page_io_latch_wait_count bigint - , page_io_latch_wait_in_ms bigint - , tree_page_latch_wait_count bigint - , tree_page_latch_wait_in_ms bigint - , tree_page_io_latch_wait_count bigint - , tree_page_io_latch_wait_in_ms bigint - , page_compression_attempt_count bigint - , page_compression_success_count bigint - , version_generated_inrow bigint - , version_generated_offrow bigint - , ghost_version_inrow bigint - , ghost_version_offrow bigint - , insert_over_ghost_version_inrow bigint - , insert_over_ghost_version_offrow bigint - ) + + -- get relevant columns from sys.dm_db_partition_stats, sys.partitions and sys.objects + DROP TABLE if exists #dm_db_partition_stats_etc + create table #dm_db_partition_stats_etc + ( + database_id smallint not null + , object_id int not null + , sname sysname NULL + , index_id int + , partition_number int + , partition_id bigint + , row_count bigint + , reserved_MB bigint + , reserved_LOB_MB bigint + , reserved_row_overflow_MB bigint + , lock_escalation_desc nvarchar(60) + , data_compression_desc nvarchar(60) + ) + + -- get relevant info from sys.dm_db_index_operational_stats + drop TABLE if exists #dm_db_index_operational_stats + create table #dm_db_index_operational_stats + ( + database_id smallint not null + , object_id int not null + , index_id int + , partition_number int + , hobt_id bigint + , leaf_insert_count bigint + , leaf_delete_count bigint + , leaf_update_count bigint + , leaf_ghost_count bigint + , nonleaf_insert_count bigint + , nonleaf_delete_count bigint + , nonleaf_update_count bigint + , leaf_allocation_count bigint + , nonleaf_allocation_count bigint + , leaf_page_merge_count bigint + , nonleaf_page_merge_count bigint + , range_scan_count bigint + , singleton_lookup_count bigint + , forwarded_fetch_count bigint + , lob_fetch_in_pages bigint + , lob_fetch_in_bytes bigint + , lob_orphan_create_count bigint + , lob_orphan_insert_count bigint + , row_overflow_fetch_in_pages bigint + , row_overflow_fetch_in_bytes bigint + , column_value_push_off_row_count bigint + , column_value_pull_in_row_count bigint + , row_lock_count bigint + , row_lock_wait_count bigint + , row_lock_wait_in_ms bigint + , page_lock_count bigint + , page_lock_wait_count bigint + , page_lock_wait_in_ms bigint + , index_lock_promotion_attempt_count bigint + , index_lock_promotion_count bigint + , page_latch_wait_count bigint + , page_latch_wait_in_ms bigint + , page_io_latch_wait_count bigint + , page_io_latch_wait_in_ms bigint + , tree_page_latch_wait_count bigint + , tree_page_latch_wait_in_ms bigint + , tree_page_io_latch_wait_count bigint + , tree_page_io_latch_wait_in_ms bigint + , page_compression_attempt_count bigint + , page_compression_success_count bigint + , version_generated_inrow bigint + , version_generated_offrow bigint + , ghost_version_inrow bigint + , ghost_version_offrow bigint + , insert_over_ghost_version_inrow bigint + , insert_over_ghost_version_offrow bigint + ) SET @dsql = N' DECLARE @d VARCHAR(19) = CONVERT(VARCHAR(19), GETDATE(), 121) - RAISERROR (N''start getting data into #h at %s'',0,1, @d) WITH NOWAIT; + RAISERROR (N''start getting data into #dm_db_partition_stats_etc at %s'',0,1, @d) WITH NOWAIT; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - INSERT INTO #h + INSERT INTO #dm_db_partition_stats_etc ( - database_id, object_id, sname, index_id, partition_number, partition_id, row_count, reserved_MB, reserved_LOB_MB, reserved_row_overflow_MB, lock_escalation_desc, data_compression_desc, reserved_dictionary_MB + database_id, object_id, sname, index_id, partition_number, partition_id, row_count, reserved_MB, reserved_LOB_MB, reserved_row_overflow_MB, lock_escalation_desc, data_compression_desc ) SELECT ' + CAST(@DatabaseID AS NVARCHAR(10)) + N' AS database_id, ps.object_id, @@ -1534,16 +1537,9 @@ create table #os ps.lob_reserved_page_count * 8. / 1024. AS reserved_LOB_MB, ps.row_overflow_reserved_page_count * 8. / 1024. AS reserved_row_overflow_MB, le.lock_escalation_desc, - ' + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N'par.data_compression_desc ' ELSE N'null as data_compression_desc ' END + N', + ' + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N'par.data_compression_desc ' ELSE N'null as data_compression_desc ' END + N' '; - /* Get columnstore dictionary size - more info: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2585 */ - IF EXISTS (SELECT * FROM sys.all_objects WHERE name = 'column_store_dictionaries') - SET @dsql = @dsql + N' COALESCE((SELECT SUM (on_disk_size / 1024.0 / 1024) FROM ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_dictionaries dict WHERE dict.partition_id = ps.partition_id),0) AS reserved_dictionary_MB '; - ELSE - SET @dsql = @dsql + N' 0 AS reserved_dictionary_MB '; - - SET @dsql = @dsql + N' FROM ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_partition_stats AS ps JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partitions AS par on ps.partition_id=par.partition_id @@ -1574,9 +1570,9 @@ create table #os OPTION ( RECOMPILE , min_grant_percent = 1); SET @d = CONVERT(VARCHAR(19), GETDATE(), 121) - RAISERROR (N''start getting data into #os at %s.'',0,1, @d) WITH NOWAIT; + RAISERROR (N''start getting data into #dm_db_index_operational_stats at %s.'',0,1, @d) WITH NOWAIT; - insert into #os + insert into #dm_db_index_operational_stats ( database_id , object_id @@ -1686,7 +1682,7 @@ create table #os OPTION ( RECOMPILE , min_grant_percent = 1); SET @d = CONVERT(VARCHAR(19), GETDATE(), 121) - RAISERROR (N''finished getting data into #os at %s.'',0,1, @d) WITH NOWAIT; + RAISERROR (N''finished getting data into #dm_db_index_operational_stats at %s.'',0,1, @d) WITH NOWAIT; '; END; ELSE @@ -1843,11 +1839,11 @@ create table #os SUM(os.page_latch_wait_in_ms), SUM(os.page_io_latch_wait_count), SUM(os.page_io_latch_wait_in_ms) - , h.reserved_dictionary_MB - from #h h - left JOIN #os as os ON + ,COALESCE((SELECT SUM (dict.on_disk_size / 1024.0 / 1024) FROM sys.column_store_dictionaries dict WHERE dict.partition_id = h.partition_id),0) AS reserved_dictionary_MB + from #dm_db_partition_stats_etc h + left JOIN #dm_db_index_operational_stats as os ON h.object_id=os.object_id and h.index_id=os.index_id and h.partition_number=os.partition_number - group by h.database_id, h.object_id, h.sname, h.index_id, h.partition_number, h.partition_id, h.row_count, h.reserved_MB, h.reserved_LOB_MB, h.reserved_row_overflow_MB, h.lock_escalation_desc, h.data_compression_desc, h.reserved_dictionary_MB + group by h.database_id, h.object_id, h.sname, h.index_id, h.partition_number, h.partition_id, h.row_count, h.reserved_MB, h.reserved_LOB_MB, h.reserved_row_overflow_MB, h.lock_escalation_desc, h.data_compression_desc END; --End Check For @SkipPartitions = 0 From 42a7590be0765d069dbb81dcc53a119b98772006 Mon Sep 17 00:00:00 2001 From: Chris May <99744523+ChrisMayIVCE@users.noreply.github.com> Date: Wed, 27 Dec 2023 12:53:12 +0000 Subject: [PATCH 494/662] Update sp_BlitzIndex.sql Remove extra junk output for columnstore visualisation on a partitioned table with at least one other rowstore index --- sp_BlitzIndex.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index 0e23b24fc..02e4386b6 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -2991,7 +2991,7 @@ BEGIN INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns c ON rg.object_id = c.object_id INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partitions p ON rg.object_id = p.object_id AND rg.partition_number = p.partition_number INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.index_columns ic on ic.column_id = c.column_id AND ic.object_id = c.object_id AND ic.index_id = p.index_id - LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_column_store_row_group_physical_stats phys ON rg.row_group_id = phys.row_group_id AND rg.object_id = phys.object_id AND rg.partition_number = phys.partition_number AND p.index_id = phys.index_id ' + CASE WHEN @ShowPartitionRanges = 1 THEN N' + LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_column_store_row_group_physical_stats phys ON rg.row_group_id = phys.row_group_id AND rg.object_id = phys.object_id AND rg.partition_number = phys.partition_number AND rg.index_id = phys.index_id ' + CASE WHEN @ShowPartitionRanges = 1 THEN N' LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.indexes i ON i.object_id = rg.object_id AND i.index_id = rg.index_id LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partition_schemes ps ON ps.data_space_id = i.data_space_id LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partition_functions pf ON pf.function_id = ps.function_id From 9a026def6bfdead374839f41e6e14f8e7f1f1711 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Thu, 28 Dec 2023 09:15:20 -0800 Subject: [PATCH 495/662] Update sp_BlitzIndex.sql Removed columns we don't need from prior versions. --- sp_BlitzIndex.sql | 72 ----------------------------------------------- 1 file changed, 72 deletions(-) diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index 180874170..15f81c6a2 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -1473,25 +1473,13 @@ BEGIN TRY , leaf_insert_count bigint , leaf_delete_count bigint , leaf_update_count bigint - , leaf_ghost_count bigint - , nonleaf_insert_count bigint - , nonleaf_delete_count bigint - , nonleaf_update_count bigint - , leaf_allocation_count bigint - , nonleaf_allocation_count bigint - , leaf_page_merge_count bigint - , nonleaf_page_merge_count bigint , range_scan_count bigint , singleton_lookup_count bigint , forwarded_fetch_count bigint , lob_fetch_in_pages bigint , lob_fetch_in_bytes bigint - , lob_orphan_create_count bigint - , lob_orphan_insert_count bigint , row_overflow_fetch_in_pages bigint , row_overflow_fetch_in_bytes bigint - , column_value_push_off_row_count bigint - , column_value_pull_in_row_count bigint , row_lock_count bigint , row_lock_wait_count bigint , row_lock_wait_in_ms bigint @@ -1504,18 +1492,6 @@ BEGIN TRY , page_latch_wait_in_ms bigint , page_io_latch_wait_count bigint , page_io_latch_wait_in_ms bigint - , tree_page_latch_wait_count bigint - , tree_page_latch_wait_in_ms bigint - , tree_page_io_latch_wait_count bigint - , tree_page_io_latch_wait_in_ms bigint - , page_compression_attempt_count bigint - , page_compression_success_count bigint - , version_generated_inrow bigint - , version_generated_offrow bigint - , ghost_version_inrow bigint - , ghost_version_offrow bigint - , insert_over_ghost_version_inrow bigint - , insert_over_ghost_version_offrow bigint ) SET @dsql = N' @@ -1582,25 +1558,13 @@ BEGIN TRY , leaf_insert_count , leaf_delete_count , leaf_update_count - , leaf_ghost_count - , nonleaf_insert_count - , nonleaf_delete_count - , nonleaf_update_count - , leaf_allocation_count - , nonleaf_allocation_count - , leaf_page_merge_count - , nonleaf_page_merge_count , range_scan_count , singleton_lookup_count , forwarded_fetch_count , lob_fetch_in_pages , lob_fetch_in_bytes - , lob_orphan_create_count - , lob_orphan_insert_count , row_overflow_fetch_in_pages , row_overflow_fetch_in_bytes - , column_value_push_off_row_count - , column_value_pull_in_row_count , row_lock_count , row_lock_wait_count , row_lock_wait_in_ms @@ -1613,18 +1577,6 @@ BEGIN TRY , page_latch_wait_in_ms , page_io_latch_wait_count , page_io_latch_wait_in_ms - , tree_page_latch_wait_count - , tree_page_latch_wait_in_ms - , tree_page_io_latch_wait_count - , tree_page_io_latch_wait_in_ms - , page_compression_attempt_count - , page_compression_success_count - , version_generated_inrow - , version_generated_offrow - , ghost_version_inrow - , ghost_version_offrow - , insert_over_ghost_version_inrow - , insert_over_ghost_version_offrow ) select os.database_id @@ -1635,25 +1587,13 @@ BEGIN TRY , os.leaf_insert_count , os.leaf_delete_count , os.leaf_update_count - , os.leaf_ghost_count - , os.nonleaf_insert_count - , os.nonleaf_delete_count - , os.nonleaf_update_count - , os.leaf_allocation_count - , os.nonleaf_allocation_count - , os.leaf_page_merge_count - , os.nonleaf_page_merge_count , os.range_scan_count , os.singleton_lookup_count , os.forwarded_fetch_count , os.lob_fetch_in_pages , os.lob_fetch_in_bytes - , os.lob_orphan_create_count - , os.lob_orphan_insert_count , os.row_overflow_fetch_in_pages , os.row_overflow_fetch_in_bytes - , os.column_value_push_off_row_count - , os.column_value_pull_in_row_count , os.row_lock_count , os.row_lock_wait_count , os.row_lock_wait_in_ms @@ -1666,18 +1606,6 @@ BEGIN TRY , os.page_latch_wait_in_ms , os.page_io_latch_wait_count , os.page_io_latch_wait_in_ms - , os.tree_page_latch_wait_count - , os.tree_page_latch_wait_in_ms - , os.tree_page_io_latch_wait_count - , os.tree_page_io_latch_wait_in_ms - , os.page_compression_attempt_count - , os.page_compression_success_count - , os.version_generated_inrow - , os.version_generated_offrow - , os.ghost_version_inrow - , os.ghost_version_offrow - , os.insert_over_ghost_version_inrow - , os.insert_over_ghost_version_offrow from ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_index_operational_stats('+ CAST(@DatabaseID AS NVARCHAR(10)) +', NULL, NULL,NULL) AS os OPTION ( RECOMPILE , min_grant_percent = 1); From 31799ee32521d1d430f9b2ad9e78f9927caae9ac Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Thu, 28 Dec 2023 09:31:02 -0800 Subject: [PATCH 496/662] #3402 sp_Blitz AG duplicate rows Fixed with every DBA's favorite T-SQL component, DISTINCT. Closes #3402. --- sp_Blitz.sql | 25 ++++--------------------- 1 file changed, 4 insertions(+), 21 deletions(-) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index 9804f45d8..424926272 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -3394,23 +3394,6 @@ AS IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 53) WITH NOWAIT; - --INSERT INTO #BlitzResults - -- ( CheckID , - -- Priority , - -- FindingsGroup , - -- Finding , - -- URL , - -- Details - -- ) - -- SELECT TOP 1 - -- 53 AS CheckID , - -- 200 AS Priority , - -- 'Informational' AS FindingsGroup , - -- 'Cluster Node' AS Finding , - -- 'https://BrentOzar.com/go/node' AS URL , - -- 'This is a node in a cluster.' AS Details - -- FROM sys.dm_os_cluster_nodes; - DECLARE @AOFCI AS INT, @AOAG AS INT, @HAType AS VARCHAR(10), @errmsg AS VARCHAR(200) SELECT @AOAG = CAST(SERVERPROPERTY('IsHadrEnabled') AS INT) @@ -3441,7 +3424,7 @@ AS Details ) - SELECT 53 AS CheckID , + SELECT DISTINCT 53 AS CheckID , 200 AS Priority , 'Informational' AS FindingsGroup , 'Cluster Node' AS Finding , @@ -3458,7 +3441,7 @@ AS URL , Details ) - SELECT 53 AS CheckID , + SELECT DISTINCT 53 AS CheckID , 200 AS Priority , 'Informational' AS FindingsGroup , 'Cluster Node Info' AS Finding , @@ -3484,7 +3467,7 @@ AS URL , Details ) - SELECT 53 AS CheckID , + SELECT DISTINCT 53 AS CheckID , 200 AS Priority , 'Informational' AS FindingsGroup , 'Cluster Node Info' AS Finding , @@ -3509,7 +3492,7 @@ AS URL , Details ) - SELECT 53 AS CheckID , + SELECT DISTINCT 53 AS CheckID , 200 AS Priority , 'Informational' AS FindingsGroup , 'Cluster Node Info' AS Finding , From c3c9a7cdd2a4ac2a46c56d9ac4225e82caef3734 Mon Sep 17 00:00:00 2001 From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com> Date: Fri, 5 Jan 2024 14:44:38 -0500 Subject: [PATCH 497/662] Add ADR Check Closes #3411 Adds a check if accelerated database recovery is enabled, It's skipped for MI and SQLDB because it is on by default there. --- sp_Blitz.sql | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index 424926272..d0772a77a 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -4803,6 +4803,10 @@ AS FROM sys.all_columns WHERE name = 'is_memory_optimized_elevate_to_snapshot_on' AND object_id = OBJECT_ID('sys.databases') AND SERVERPROPERTY('EngineEdition') <> 8; /* Hekaton is always enabled in Managed Instances per https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/1919 */ + INSERT INTO #DatabaseDefaults + SELECT 'is_accelerated_database_recovery_on', 0, 145, 210, 'Acclerated Database Recovery Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL + FROM sys.all_columns + WHERE name = 'is_accelerated_database_recovery_on' AND object_id = OBJECT_ID('sys.databases') AND SERVERPROPERTY('EngineEdition') NOT IN (5, 8) ; DECLARE DatabaseDefaultsLoop CURSOR FOR SELECT name, DefaultValue, CheckID, Priority, Finding, URL, Details @@ -10048,4 +10052,4 @@ EXEC [dbo].[sp_Blitz] @OutputProcedureCache = 0 , @CheckProcedureCacheFilter = NULL, @CheckServerInfo = 1 -*/ \ No newline at end of file +*/ From d6121bef43b88a4a9751e62d6479b8d39afa9d35 Mon Sep 17 00:00:00 2001 From: David Wiseman <33157668+DavidWiseman@users.noreply.github.com> Date: Mon, 8 Jan 2024 09:21:25 +0000 Subject: [PATCH 498/662] Add Automated Testing Use GitHub Actions to provide automated testing for first responder kit stored procedures. #3418 --- .github/workflows/integration-tests.yml | 52 +++++++++++++++++++++++++ tests/run-tests.ps1 | 14 +++++++ tests/sp_Blitz.tests.ps1 | 10 +++++ tests/sp_BlitzAnalysis.tests.ps1 | 15 +++++++ tests/sp_BlitzBackups.tests.ps1 | 20 ++++++++++ tests/sp_BlitzCache.tests.ps1 | 13 +++++++ tests/sp_BlitzFirst.tests.ps1 | 40 +++++++++++++++++++ tests/sp_BlitzInMemoryOLTP.tests.ps1 | 13 +++++++ tests/sp_BlitzIndex.tests.ps1 | 10 +++++ tests/sp_BlitzLock.tests.ps1 | 11 ++++++ tests/sp_BlitzQueryStore.tests.ps1 | 12 ++++++ tests/sp_BlitzWho.tests.ps1 | 15 +++++++ 12 files changed, 225 insertions(+) create mode 100644 .github/workflows/integration-tests.yml create mode 100644 tests/run-tests.ps1 create mode 100644 tests/sp_Blitz.tests.ps1 create mode 100644 tests/sp_BlitzAnalysis.tests.ps1 create mode 100644 tests/sp_BlitzBackups.tests.ps1 create mode 100644 tests/sp_BlitzCache.tests.ps1 create mode 100644 tests/sp_BlitzFirst.tests.ps1 create mode 100644 tests/sp_BlitzInMemoryOLTP.tests.ps1 create mode 100644 tests/sp_BlitzIndex.tests.ps1 create mode 100644 tests/sp_BlitzLock.tests.ps1 create mode 100644 tests/sp_BlitzQueryStore.tests.ps1 create mode 100644 tests/sp_BlitzWho.tests.ps1 diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml new file mode 100644 index 000000000..250e8fbc4 --- /dev/null +++ b/.github/workflows/integration-tests.yml @@ -0,0 +1,52 @@ +name: First Responder Kit Integration Tests + +on: + push: + workflow_dispatch: + +jobs: + build: + name: Build + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install SqlServer Module + shell: pwsh + run: | + Set-PSRepository -Name PSGallery -InstallationPolicy Trusted + Install-Module SqlServer + + - name: Install SQL Server + uses: potatoqualitee/mssqlsuite@v1.7 + with: + install: sqlengine + version: 2017 + collation: SQL_Latin1_General_CP1_CS_AS + + - name: Check SQL Install + run: | + sqlcmd -S localhost -U sa -P dbatools.I0 -d tempdb -Q "SELECT @@version as Version;" -I -b -t 60 + sqlcmd -S localhost -U sa -P dbatools.I0 -d tempdb -Q "SELECT SERVERPROPERTY('Collation') AS Collation;" -I -b -t 60 + + - name: Deploy FRK + run: | + sqlcmd -S localhost -U sa -P dbatools.I0 -d master -i "sp_BlitzCache.sql" -I -b -t 60 + sqlcmd -S localhost -U sa -P dbatools.I0 -d master -i "sp_BlitzWho.sql" -I -b -t 60 + sqlcmd -S localhost -U sa -P dbatools.I0 -d master -i "sp_Blitz.sql" -I -b -t 60 + sqlcmd -S localhost -U sa -P dbatools.I0 -d master -i "sp_BlitzFirst.sql" -I -b -t 60 + sqlcmd -S localhost -U sa -P dbatools.I0 -d master -i "sp_BlitzAnalysis.sql" -I -b -t 60 + sqlcmd -S localhost -U sa -P dbatools.I0 -d master -i "sp_BlitzBackups.sql" -I -b -t 60 + sqlcmd -S localhost -U sa -P dbatools.I0 -d master -i "sp_BlitzIndex.sql" -I -b -t 60 + sqlcmd -S localhost -U sa -P dbatools.I0 -d master -i "sp_BlitzInMemoryOLTP.sql" -I -b -t 60 + sqlcmd -S localhost -U sa -P dbatools.I0 -d master -i "sp_BlitzLock.sql" -I -b -t 60 + sqlcmd -S localhost -U sa -P dbatools.I0 -d master -i "sp_BlitzQueryStore.sql" -I -b -t 60 + sqlcmd -S localhost -U sa -P dbatools.I0 -d master -Q "SELECT * FROM sys.procedures WHERE name LIKE 'sp_Blitz%';" -I -b -t 60 + + - name: Run Pester Tests + shell: pwsh + run: | + cd tests + ./run-tests.ps1 \ No newline at end of file diff --git a/tests/run-tests.ps1 b/tests/run-tests.ps1 new file mode 100644 index 000000000..c16ea15f5 --- /dev/null +++ b/tests/run-tests.ps1 @@ -0,0 +1,14 @@ +# Assign default values if script-scoped variables are not set +$ServerInstance = if ($null -ne $script:ServerInstance) { $script:ServerInstance } else { "localhost" } +$UserName = if ($null -ne $script:UserName) { $script:UserName } else { "sa" } +$Password = if ($null -ne $script:Password) { $script:Password } else { "dbatools.I0" } +$TrustServerCertificate = if ($null -ne $script:TrustServerCertificate) { $script:TrustServerCertificate } else { $true } + +$PSDefaultParameterValues = @{ + "*:ServerInstance" = $ServerInstance + "*:UserName" = $UserName + "*:Password" = $Password + "*:TrustServerCertificate" = $TrustServerCertificate +} + +Invoke-Pester -PassThru \ No newline at end of file diff --git a/tests/sp_Blitz.tests.ps1 b/tests/sp_Blitz.tests.ps1 new file mode 100644 index 000000000..341b9c348 --- /dev/null +++ b/tests/sp_Blitz.tests.ps1 @@ -0,0 +1,10 @@ +Describe "sp_Blitz Tests" { + + It "sp_Blitz Check" { + $results = Invoke-SqlCmd -Query "EXEC dbo.sp_Blitz" -OutputAs DataSet + $results.Tables.Count | Should -Be 1 + $results.Tables[0].Columns.Count | Should -Be 9 + $results.Tables[0].Rows.Count | Should -BeGreaterThan 0 + } + +} diff --git a/tests/sp_BlitzAnalysis.tests.ps1 b/tests/sp_BlitzAnalysis.tests.ps1 new file mode 100644 index 000000000..f346b6197 --- /dev/null +++ b/tests/sp_BlitzAnalysis.tests.ps1 @@ -0,0 +1,15 @@ +Describe "sp_BlitzAnalysis Tests" { + + It "sp_BlitzAnalysis Check" { + + # Run sp_BlitzFirst to populate the tables used by sp_BlitzAnalysis + Invoke-SqlCmd -Query "EXEC dbo.sp_BlitzFirst @OutputDatabaseName = 'tempdb', @OutputSchemaName = N'dbo', @OutputTableName = N'BlitzFirst', @OutputTableNameFileStats = N'BlitzFirst_FileStats',@OutputTableNamePerfmonStats = N'BlitzFirst_PerfmonStats', + @OutputTableNameWaitStats = N'BlitzFirst_WaitStats', + @OutputTableNameBlitzCache = N'BlitzCache', + @OutputTableNameBlitzWho= N'BlitzWho'" + + $results = Invoke-SqlCmd -Query "EXEC dbo.sp_BlitzAnalysis @OutputDatabaseName = 'tempdb'" -OutputAs DataSet + $results.Tables.Count | Should -BeGreaterThan 6 + } + +} diff --git a/tests/sp_BlitzBackups.tests.ps1 b/tests/sp_BlitzBackups.tests.ps1 new file mode 100644 index 000000000..162e869fb --- /dev/null +++ b/tests/sp_BlitzBackups.tests.ps1 @@ -0,0 +1,20 @@ +Describe "sp_BlitzBackups Tests" { + + It "sp_BlitzBackups Check" { + # Give sp_BlitzBackups something to capture by performing a dummy backup of model DB + # Test to be run in GitHub action but backing up model to NUL should be safe on most systems + Invoke-SqlCmd -Query "BACKUP DATABASE model TO DISK='NUL'" + $results = Invoke-SqlCmd -Query "EXEC dbo.sp_BlitzBackups" -OutputAs DataSet + $results.Tables.Count | Should -Be 3 + + $results.Tables[0].Columns.Count | Should -Be 39 + $results.Tables[0].Rows.Count | Should -BeGreaterThan 0 + + $results.Tables[1].Columns.Count | Should -Be 32 + $results.Tables[1].Rows.Count | Should -BeGreaterThan 0 + + $results.Tables[2].Columns.Count | Should -Be 5 + $results.Tables[2].Rows.Count | Should -BeGreaterThan 0 + } + +} diff --git a/tests/sp_BlitzCache.tests.ps1 b/tests/sp_BlitzCache.tests.ps1 new file mode 100644 index 000000000..4483e090b --- /dev/null +++ b/tests/sp_BlitzCache.tests.ps1 @@ -0,0 +1,13 @@ +Describe "sp_BlitzCache Tests" { + + It "sp_BlitzCache Check" { + # Note: Added 'SELECT 1 AS A' as an empty first resultset causes issues returning the full DataSet + $results = Invoke-SqlCmd -Query "SELECT 1 AS A;EXEC dbo.sp_BlitzCache" -OutputAs DataSet + # Adjust table count to get the actual tables returned from sp_BlitzCache (So reporting isn't confusing) + $tableCount = $results.Tables.Count -1 + $tableCount | Should -Be 2 + $results.Tables[1].Columns.Count | Should -Be 43 + $results.Tables[2].Columns.Count | Should -Be 6 + $results.Tables[2].Rows.Count | Should -BeGreaterThan 0 + } +} \ No newline at end of file diff --git a/tests/sp_BlitzFirst.tests.ps1 b/tests/sp_BlitzFirst.tests.ps1 new file mode 100644 index 000000000..d2bb42d3f --- /dev/null +++ b/tests/sp_BlitzFirst.tests.ps1 @@ -0,0 +1,40 @@ +Describe "sp_BlitzFirst Tests" { + + It "sp_BlitzFirst Check" { + # Give sp_BlitzFirst something to capture + Start-Job -ScriptBlock { + Invoke-SqlCmd -Query "WAITFOR DELAY '00:00:15'" -ServerInstance $using:ServerInstance -Username $using:UserName -Password $using:Password -TrustServerCertificate:$using:TrustServerCertificate + } + Start-Sleep -Milliseconds 1000 + $results = Invoke-SqlCmd -Query "EXEC dbo.sp_BlitzFirst" -OutputAs DataSet + $results.Tables.Count | Should -Be 1 + $results.Tables[0].Columns.Count | Should -Be 8 + $results.Tables[0].Rows.Count | Should -BeGreaterThan 0 + + $results = Invoke-SqlCmd -Query "EXEC dbo.sp_BlitzFirst @ExpertMode=1" -OutputAs DataSet + $results.Tables.Count | Should -Be 7 + + $results.Tables[0].Columns.Count | Should -Be 21 + $results.Tables[0].Rows.Count | Should -BeGreaterThan 0 + + $results.Tables[1].Columns.Count | Should -Be 40 + $results.Tables[1].Rows.Count | Should -BeGreaterThan 0 + + $results.Tables[2].Columns.Count | Should -Be 13 + $results.Tables[2].Rows.Count | Should -BeGreaterThan 0 + + $results.Tables[3].Columns.Count | Should -Be 11 + $results.Tables[3].Rows.Count | Should -BeGreaterThan 0 + + $results.Tables[4].Columns.Count | Should -Be 10 + $results.Tables[4].Rows.Count | Should -BeGreaterThan 0 + + $results.Tables[5].Columns.Count | Should -Be 4 + $results.Tables[5].Rows.Count | Should -BeGreaterThan 0 + + $results.Tables[6].Columns.Count | Should -Be 21 + $results.Tables[6].Rows.Count | Should -BeGreaterThan 0 + + } + +} \ No newline at end of file diff --git a/tests/sp_BlitzInMemoryOLTP.tests.ps1 b/tests/sp_BlitzInMemoryOLTP.tests.ps1 new file mode 100644 index 000000000..710f0bc5e --- /dev/null +++ b/tests/sp_BlitzInMemoryOLTP.tests.ps1 @@ -0,0 +1,13 @@ +Describe "sp_BlitzInMemoryOLTP Tests" { + + It "sp_BlitzInMemoryOLTP Check" { + # Create InMemory OLTP Database + Invoke-SqlCmd -Query "CREATE DATABASE sp_BlitzInMemoryOLTPTest;ALTER DATABASE sp_BlitzInMemoryOLTPTest SET MEMORY_OPTIMIZED_ELEVATE_TO_SNAPSHOT = ON;ALTER DATABASE sp_BlitzInMemoryOLTPTest ADD FILEGROUP sp_BlitzInMemoryOLTPTest CONTAINS MEMORY_OPTIMIZED_DATA;" + # Note: Added 'SELECT 1 AS A' as an empty first resultset causes issues returning the full DataSet + $results = Invoke-SqlCmd -Query "SELECT 1 AS A;EXEC dbo.sp_BlitzInMemoryOLTP" -OutputAs DataSet + # Adjust table count to get the actual tables returned from sp_BlitzInMemoryOLTP (So reporting isn't confusing) + $tableCount = $results.Tables.Count -1 + $tableCount | Should -BeGreaterThan 0 + } + +} \ No newline at end of file diff --git a/tests/sp_BlitzIndex.tests.ps1 b/tests/sp_BlitzIndex.tests.ps1 new file mode 100644 index 000000000..63c479ad3 --- /dev/null +++ b/tests/sp_BlitzIndex.tests.ps1 @@ -0,0 +1,10 @@ +Describe "sp_BlitzIndex Tests" { + + It "sp_BlitzIndex Check" { + $results = Invoke-SqlCmd -Query "EXEC dbo.sp_BlitzIndex" -OutputAs DataSet + $results.Tables.Count | Should -Be 1 + $results.Tables[0].Columns.Count | Should -Be 12 + $results.Tables[0].Rows.Count | Should -BeGreaterThan 0 + } + +} \ No newline at end of file diff --git a/tests/sp_BlitzLock.tests.ps1 b/tests/sp_BlitzLock.tests.ps1 new file mode 100644 index 000000000..5368e3283 --- /dev/null +++ b/tests/sp_BlitzLock.tests.ps1 @@ -0,0 +1,11 @@ +Describe "sp_BlitzLock Tests" { + + It "sp_BlitzLock Check" { + # Note: Added 'SELECT 1 AS A' as an empty first resultset causes issues returning the full DataSet + $results = Invoke-SqlCmd -Query "SELECT 1 AS A;EXEC dbo.sp_BlitzLock" -OutputAs DataSet + # Adjust table count to get the actual tables returned from sp_BlitzLock (So reporting isn't confusing) + $tableCount = $results.Tables.Count - 1 + $tableCount | Should -Be 3 + } + +} \ No newline at end of file diff --git a/tests/sp_BlitzQueryStore.tests.ps1 b/tests/sp_BlitzQueryStore.tests.ps1 new file mode 100644 index 000000000..079ff1081 --- /dev/null +++ b/tests/sp_BlitzQueryStore.tests.ps1 @@ -0,0 +1,12 @@ +Describe "sp_BlitzQueryStore Tests" { + + It "sp_BlitzQueryStore Check" { + # Note: Added 'SELECT 1 AS A' as an empty first resultset causes issues returning the full DataSet + $results = Invoke-SqlCmd -Query "SELECT 1 AS A;EXEC dbo.sp_BlitzQueryStore" -OutputAs DataSet + # Adjust table count to get the actual tables returned from sp_BlitzQueryStore (So reporting isn't confusing) + $tableCount = $results.Tables.Count - 1 + ## We haven't specified @DatabaseName and don't have DBs with Query Store enabled so table count is 0 + $tableCount | Should -Be 0 + } + +} \ No newline at end of file diff --git a/tests/sp_BlitzWho.tests.ps1 b/tests/sp_BlitzWho.tests.ps1 new file mode 100644 index 000000000..3e9febd56 --- /dev/null +++ b/tests/sp_BlitzWho.tests.ps1 @@ -0,0 +1,15 @@ +Describe "sp_BlitzWho Tests" { + + It "sp_BlitzWho Check" { + # Give sp_BlitzWho something to capture + Start-Job -ScriptBlock { + Invoke-SqlCmd -Query "WAITFOR DELAY '00:00:15'" -ServerInstance $using:ServerInstance -Username $using:UserName -Password $using:Password -TrustServerCertificate:$using:TrustServerCertificate + } + Start-Sleep -Milliseconds 1000 + $results = Invoke-SqlCmd -Query "EXEC dbo.sp_BlitzWho" -OutputAs DataSet + $results.Tables.Count | Should -Be 1 + $results.Tables[0].Columns.Count | Should -Be 21 + $results.Tables[0].Rows.Count | Should -BeGreaterThan 0 + } + +} \ No newline at end of file From 08c94850799a7fb5e3f81483b42a4cbc153a22ac Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Wed, 10 Jan 2024 03:57:17 -0800 Subject: [PATCH 499/662] Update integration-tests.yml --- .github/workflows/integration-tests.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index 250e8fbc4..d5a09391c 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -3,6 +3,8 @@ name: First Responder Kit Integration Tests on: push: workflow_dispatch: + pull_request: + types: [opened, review_requested, synchronize] jobs: build: @@ -49,4 +51,4 @@ jobs: shell: pwsh run: | cd tests - ./run-tests.ps1 \ No newline at end of file + ./run-tests.ps1 From 63129ceba213f35c8bcdf63043b1052869231524 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Wed, 10 Jan 2024 04:00:22 -0800 Subject: [PATCH 500/662] Update SqlServerVersions.sql Add SQL 2022 CU10 GDR. --- SqlServerVersions.sql | 1 + 1 file changed, 1 insertion(+) diff --git a/SqlServerVersions.sql b/SqlServerVersions.sql index 40aea38ce..51384c199 100644 --- a/SqlServerVersions.sql +++ b/SqlServerVersions.sql @@ -41,6 +41,7 @@ DELETE FROM dbo.SqlServerVersions; INSERT INTO dbo.SqlServerVersions (MajorVersionNumber, MinorVersionNumber, Branch, [Url], ReleaseDate, MainstreamSupportEndDate, ExtendedSupportEndDate, MajorVersionName, MinorVersionName) VALUES + (16, 4100, 'CU10 GDR', 'https://support.microsoft.com/en-us/help/5033592', '2024-01-09', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 10 GDR'), (16, 4095, 'CU10', 'https://support.microsoft.com/en-us/help/5031778', '2023-11-16', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 10'), (16, 4085, 'CU9', 'https://support.microsoft.com/en-us/help/5030731', '2023-10-12', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 9'), (16, 4075, 'CU8', 'https://support.microsoft.com/en-us/help/5029666', '2023-09-14', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 8'), From fca5bf918137ccb572674fcbf5b102492d63b23a Mon Sep 17 00:00:00 2001 From: twitthoeft-gls <132710946+twitthoeft-gls@users.noreply.github.com> Date: Wed, 10 Jan 2024 17:48:15 -0500 Subject: [PATCH 501/662] Update sp_BlitzCache.sql - Typo fix It's a typo fix. Thanks for making my job easier. Whiskey and Yoga --- sp_BlitzCache.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sp_BlitzCache.sql b/sp_BlitzCache.sql index 07b3e1ab4..3279e178b 100644 --- a/sp_BlitzCache.sql +++ b/sp_BlitzCache.sql @@ -468,7 +468,7 @@ IF @Help = 1 UNION ALL SELECT N'@MinutesBack', N'INT', - N'How many minutes back to begin plan cache analysis. If you put in a positive number, we''ll flip it to negtive.'; + N'How many minutes back to begin plan cache analysis. If you put in a positive number, we''ll flip it to negative.'; /* Column definitions */ From 464b2292fb0fbfd9184fda63a268c351f8c6d4eb Mon Sep 17 00:00:00 2001 From: Sean Killeen Date: Tue, 16 Jan 2024 10:40:49 -0500 Subject: [PATCH 502/662] Add Matrix for SQL Server Versions --- .github/workflows/integration-tests.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index d5a09391c..7d43b4c95 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -10,6 +10,10 @@ jobs: build: name: Build runs-on: ubuntu-latest + strategy: + matrix: + SQL_ENGINE_VERSION: [2017,2019] + steps: - name: Checkout repository @@ -25,7 +29,7 @@ jobs: uses: potatoqualitee/mssqlsuite@v1.7 with: install: sqlengine - version: 2017 + version: ${{ matrix.SQL_ENGINE_VERSION }} collation: SQL_Latin1_General_CP1_CS_AS - name: Check SQL Install From a6b27ce9dae726d6786007a465896a342acb9801 Mon Sep 17 00:00:00 2001 From: Sean Killeen Date: Tue, 16 Jan 2024 10:53:56 -0500 Subject: [PATCH 503/662] Add matrix capability for collation too --- .github/workflows/integration-tests.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index 7d43b4c95..1a9405e62 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -13,6 +13,7 @@ jobs: strategy: matrix: SQL_ENGINE_VERSION: [2017,2019] + COLLATION: [SQL_Latin1_General_CP1_CS_AS] steps: @@ -30,7 +31,7 @@ jobs: with: install: sqlengine version: ${{ matrix.SQL_ENGINE_VERSION }} - collation: SQL_Latin1_General_CP1_CS_AS + collation: ${{ matrix.COLLATION }} - name: Check SQL Install run: | From cabcea4e1df83ee77ed847d508f7eaa24ae4516b Mon Sep 17 00:00:00 2001 From: Sean Killeen Date: Tue, 16 Jan 2024 11:32:21 -0500 Subject: [PATCH 504/662] Add SQL 2022 as well --- .github/workflows/integration-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index 1a9405e62..eccb5901e 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - SQL_ENGINE_VERSION: [2017,2019] + SQL_ENGINE_VERSION: [2017,2019,2022] COLLATION: [SQL_Latin1_General_CP1_CS_AS] From d9d16a0867164c9413fe71daa26e03d963253594 Mon Sep 17 00:00:00 2001 From: Sean Killeen Date: Tue, 16 Jan 2024 11:40:00 -0500 Subject: [PATCH 505/662] Language: Insanity -> Problems --- Deprecated/sp_BlitzIndex_SQL_Server_2005.sql | 2 +- Install-All-Scripts.sql | 8 ++++---- Install-Core-Blitz-No-Query-Store.sql | 8 ++++---- Install-Core-Blitz-With-Query-Store.sql | 8 ++++---- sp_Blitz.sql | 2 +- sp_BlitzIndex.sql | 6 +++--- 6 files changed, 17 insertions(+), 17 deletions(-) diff --git a/Deprecated/sp_BlitzIndex_SQL_Server_2005.sql b/Deprecated/sp_BlitzIndex_SQL_Server_2005.sql index 22ec91870..86d0f5845 100644 --- a/Deprecated/sp_BlitzIndex_SQL_Server_2005.sql +++ b/Deprecated/sp_BlitzIndex_SQL_Server_2005.sql @@ -43,7 +43,7 @@ Known limitations of this version: -- Example 1: index creates use ONLINE=? instead of ONLINE=ON / ONLINE=OFF. This is because it's important for the user to understand if it's going to be offline and not just run a script. -- Example 2: they do not include all the options the index may have been created with (padding, compression filegroup/partition scheme etc.) -- (The compression and filegroup index create syntax isn't trivial because it's set at the partition level and isn't trivial to code. Two people have voted for wanting it so far.) - - Doesn't advise you about data modeling for clustered indexes and primary keys (primarily looks for signs of insanity.) + - Doesn't advise you about data modeling for clustered indexes and primary keys (primarily looks for signs of problems.) - Found something? Let us know at help@brentozar.com. Thanks for using sp_BlitzIndex(TM)! diff --git a/Install-All-Scripts.sql b/Install-All-Scripts.sql index 547300b1b..9fa75fdd4 100644 --- a/Install-All-Scripts.sql +++ b/Install-All-Scripts.sql @@ -4022,7 +4022,7 @@ AS IF @BringThePain = 0 AND 50 <= (SELECT COUNT(*) FROM sys.databases) AND @CheckUserDatabaseObjects = 1 BEGIN SET @CheckUserDatabaseObjects = 0; - PRINT 'Running sp_Blitz @CheckUserDatabaseObjects = 1 on a server with 50+ databases may cause temporary insanity for the server and/or user.'; + PRINT 'Running sp_Blitz @CheckUserDatabaseObjects = 1 on a server with 50+ databases may cause temporary problems for the server and/or user.'; PRINT 'If you''re sure you want to do this, run again with the parameter @BringThePain = 1.'; INSERT INTO #BlitzResults ( CheckID , @@ -23015,7 +23015,7 @@ Known limitations of this version: filegroup/partition scheme etc.) -- (The compression and filegroup index create syntax is not trivial because it is set at the partition level and is not trivial to code.) - - Does not advise you about data modeling for clustered indexes and primary keys (primarily looks for signs of insanity.) + - Does not advise you about data modeling for clustered indexes and primary keys (primarily looks for signs of problems.) Unknown limitations of this version: - We knew them once, but we forgot. @@ -23857,7 +23857,7 @@ BEGIN TRY VALUES ( 1, 0, N'You''re trying to run sp_BlitzIndex on a server with ' + CAST(@NumDatabases AS NVARCHAR(8)) + N' databases. ', - N'Running sp_BlitzIndex on a server with 50+ databases may cause temporary insanity for the server and/or user.', + N'Running sp_BlitzIndex on a server with 50+ databases may cause temporary problems for the server and/or user.', N'If you''re sure you want to do this, run again with the parameter @BringThePain = 1.', 'http://FirstResponderKit.org', '', @@ -23884,7 +23884,7 @@ BEGIN TRY bir.create_tsql, bir.more_info FROM #BlitzIndexResults AS bir; - RAISERROR('Running sp_BlitzIndex on a server with 50+ databases may cause temporary insanity for the server', 12, 1); + RAISERROR('Running sp_BlitzIndex on a server with 50+ databases may cause temporary problems for the server', 12, 1); END; RETURN; diff --git a/Install-Core-Blitz-No-Query-Store.sql b/Install-Core-Blitz-No-Query-Store.sql index 58cd05196..2b2dcc93d 100644 --- a/Install-Core-Blitz-No-Query-Store.sql +++ b/Install-Core-Blitz-No-Query-Store.sql @@ -1160,7 +1160,7 @@ AS IF @BringThePain = 0 AND 50 <= (SELECT COUNT(*) FROM sys.databases) AND @CheckUserDatabaseObjects = 1 BEGIN SET @CheckUserDatabaseObjects = 0; - PRINT 'Running sp_Blitz @CheckUserDatabaseObjects = 1 on a server with 50+ databases may cause temporary insanity for the server and/or user.'; + PRINT 'Running sp_Blitz @CheckUserDatabaseObjects = 1 on a server with 50+ databases may cause temporary problems for the server and/or user.'; PRINT 'If you''re sure you want to do this, run again with the parameter @BringThePain = 1.'; INSERT INTO #BlitzResults ( CheckID , @@ -20153,7 +20153,7 @@ Known limitations of this version: filegroup/partition scheme etc.) -- (The compression and filegroup index create syntax is not trivial because it is set at the partition level and is not trivial to code.) - - Does not advise you about data modeling for clustered indexes and primary keys (primarily looks for signs of insanity.) + - Does not advise you about data modeling for clustered indexes and primary keys (primarily looks for signs of problems.) Unknown limitations of this version: - We knew them once, but we forgot. @@ -20995,7 +20995,7 @@ BEGIN TRY VALUES ( 1, 0, N'You''re trying to run sp_BlitzIndex on a server with ' + CAST(@NumDatabases AS NVARCHAR(8)) + N' databases. ', - N'Running sp_BlitzIndex on a server with 50+ databases may cause temporary insanity for the server and/or user.', + N'Running sp_BlitzIndex on a server with 50+ databases may cause temporary problems for the server and/or user.', N'If you''re sure you want to do this, run again with the parameter @BringThePain = 1.', 'http://FirstResponderKit.org', '', @@ -21022,7 +21022,7 @@ BEGIN TRY bir.create_tsql, bir.more_info FROM #BlitzIndexResults AS bir; - RAISERROR('Running sp_BlitzIndex on a server with 50+ databases may cause temporary insanity for the server', 12, 1); + RAISERROR('Running sp_BlitzIndex on a server with 50+ databases may cause temporary problems for the server', 12, 1); END; RETURN; diff --git a/Install-Core-Blitz-With-Query-Store.sql b/Install-Core-Blitz-With-Query-Store.sql index 2724cc926..f62410924 100644 --- a/Install-Core-Blitz-With-Query-Store.sql +++ b/Install-Core-Blitz-With-Query-Store.sql @@ -1160,7 +1160,7 @@ AS IF @BringThePain = 0 AND 50 <= (SELECT COUNT(*) FROM sys.databases) AND @CheckUserDatabaseObjects = 1 BEGIN SET @CheckUserDatabaseObjects = 0; - PRINT 'Running sp_Blitz @CheckUserDatabaseObjects = 1 on a server with 50+ databases may cause temporary insanity for the server and/or user.'; + PRINT 'Running sp_Blitz @CheckUserDatabaseObjects = 1 on a server with 50+ databases may cause temporary problems for the server and/or user.'; PRINT 'If you''re sure you want to do this, run again with the parameter @BringThePain = 1.'; INSERT INTO #BlitzResults ( CheckID , @@ -20153,7 +20153,7 @@ Known limitations of this version: filegroup/partition scheme etc.) -- (The compression and filegroup index create syntax is not trivial because it is set at the partition level and is not trivial to code.) - - Does not advise you about data modeling for clustered indexes and primary keys (primarily looks for signs of insanity.) + - Does not advise you about data modeling for clustered indexes and primary keys (primarily looks for signs of problems.) Unknown limitations of this version: - We knew them once, but we forgot. @@ -20995,7 +20995,7 @@ BEGIN TRY VALUES ( 1, 0, N'You''re trying to run sp_BlitzIndex on a server with ' + CAST(@NumDatabases AS NVARCHAR(8)) + N' databases. ', - N'Running sp_BlitzIndex on a server with 50+ databases may cause temporary insanity for the server and/or user.', + N'Running sp_BlitzIndex on a server with 50+ databases may cause temporary problems for the server and/or user.', N'If you''re sure you want to do this, run again with the parameter @BringThePain = 1.', 'http://FirstResponderKit.org', '', @@ -21022,7 +21022,7 @@ BEGIN TRY bir.create_tsql, bir.more_info FROM #BlitzIndexResults AS bir; - RAISERROR('Running sp_BlitzIndex on a server with 50+ databases may cause temporary insanity for the server', 12, 1); + RAISERROR('Running sp_BlitzIndex on a server with 50+ databases may cause temporary problems for the server', 12, 1); END; RETURN; diff --git a/sp_Blitz.sql b/sp_Blitz.sql index b7792b21f..52b79967c 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -1198,7 +1198,7 @@ AS IF @BringThePain = 0 AND 50 <= (SELECT COUNT(*) FROM sys.databases) AND @CheckUserDatabaseObjects = 1 BEGIN SET @CheckUserDatabaseObjects = 0; - PRINT 'Running sp_Blitz @CheckUserDatabaseObjects = 1 on a server with 50+ databases may cause temporary insanity for the server and/or user.'; + PRINT 'Running sp_Blitz @CheckUserDatabaseObjects = 1 on a server with 50+ databases may cause temporary problems for the server and/or user.'; PRINT 'If you''re sure you want to do this, run again with the parameter @BringThePain = 1.'; INSERT INTO #BlitzResults ( CheckID , diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index 400713fea..a34095275 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -77,7 +77,7 @@ Known limitations of this version: filegroup/partition scheme etc.) -- (The compression and filegroup index create syntax is not trivial because it is set at the partition level and is not trivial to code.) - - Does not advise you about data modeling for clustered indexes and primary keys (primarily looks for signs of insanity.) + - Does not advise you about data modeling for clustered indexes and primary keys (primarily looks for signs of problems.) Unknown limitations of this version: - We knew them once, but we forgot. @@ -924,7 +924,7 @@ BEGIN TRY VALUES ( 1, 0, N'You''re trying to run sp_BlitzIndex on a server with ' + CAST(@NumDatabases AS NVARCHAR(8)) + N' databases. ', - N'Running sp_BlitzIndex on a server with 50+ databases may cause temporary insanity for the server and/or user.', + N'Running sp_BlitzIndex on a server with 50+ databases may cause temporary problems for the server and/or user.', N'If you''re sure you want to do this, run again with the parameter @BringThePain = 1.', 'http://FirstResponderKit.org', '', @@ -951,7 +951,7 @@ BEGIN TRY bir.create_tsql, bir.more_info FROM #BlitzIndexResults AS bir; - RAISERROR('Running sp_BlitzIndex on a server with 50+ databases may cause temporary insanity for the server', 12, 1); + RAISERROR('Running sp_BlitzIndex on a server with 50+ databases may cause temporary problems for the server', 12, 1); END; RETURN; From cfbf261e20f777f32e1315039ed0c026d309992f Mon Sep 17 00:00:00 2001 From: Sean Killeen Date: Tue, 16 Jan 2024 12:05:20 -0500 Subject: [PATCH 506/662] Revert changes to install scripts --- Install-All-Scripts.sql | 112 ++++++++++++------------ Install-Core-Blitz-No-Query-Store.sql | 102 ++++++++++----------- Install-Core-Blitz-With-Query-Store.sql | 112 ++++++++++++------------ 3 files changed, 163 insertions(+), 163 deletions(-) diff --git a/Install-All-Scripts.sql b/Install-All-Scripts.sql index 9fa75fdd4..0cf3afc91 100644 --- a/Install-All-Scripts.sql +++ b/Install-All-Scripts.sql @@ -3585,7 +3585,7 @@ AS SET @CheckUserDatabaseObjects = 0; PRINT 'Databases with compatibility level < 90 found, so setting @CheckUserDatabaseObjects = 0.'; PRINT 'The database-level checks rely on CTEs, which are not supported in SQL 2000 compat level databases.'; - PRINT 'Get with the cool kids and switch to a current compatibility level, Grandpa. To find the problems, run:'; + PRINT 'Get with the cool kids and switch to a current compatibility level, Grandpa. To find the insanity, run:'; PRINT 'SELECT * FROM sys.databases WHERE compatibility_level < 90;'; INSERT INTO #BlitzResults ( CheckID , @@ -4022,7 +4022,7 @@ AS IF @BringThePain = 0 AND 50 <= (SELECT COUNT(*) FROM sys.databases) AND @CheckUserDatabaseObjects = 1 BEGIN SET @CheckUserDatabaseObjects = 0; - PRINT 'Running sp_Blitz @CheckUserDatabaseObjects = 1 on a server with 50+ databases may cause temporary problems for the server and/or user.'; + PRINT 'Running sp_Blitz @CheckUserDatabaseObjects = 1 on a server with 50+ databases may cause temporary insanity for the server and/or user.'; PRINT 'If you''re sure you want to do this, run again with the parameter @BringThePain = 1.'; INSERT INTO #BlitzResults ( CheckID , @@ -4117,9 +4117,9 @@ AS Below, we check master.sys.databases looking for databases that haven't had a backup in the last week. If we find any, we insert them into #BlitzResults, the temp table that - tracks our server's problems. Note that if the check does - NOT find any problems, we don't save that. We're only - saving the problems, not the successful checks. + tracks our server's insanity. Note that if the check does + NOT find any insanity, we don't save that. We're only + saving the insanity, not the successful checks. */ IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 1) WITH NOWAIT; @@ -4209,7 +4209,7 @@ AS And there you have it. The rest of this stored procedure works the same way: it asks: - Should I skip this check? - - If not, do I find problems? + - If not, do I find insanity? - Insert the results into #BlitzResults */ @@ -4757,7 +4757,7 @@ AS 'https://www.brentozar.com/go/owners' AS URL , ( 'Job [' + j.name + '] is owned by [' + SUSER_SNAME(j.owner_sid) - + '] - meaning if their login is disabled or not available due to Active Directory problems, the job will stop working.' ) AS Details + + '] - meaning if their login is disabled or not available due to Active Directory insanity, 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); @@ -6665,7 +6665,7 @@ AS 'Performance' AS FindingGroup , 'CPU Schedulers Offline' AS Finding , 'https://www.brentozar.com/go/schedulers' AS URL , - 'Some CPU cores are not accessible to SQL Server due to affinity masking or licensing problems.'; + 'Some CPU cores are not accessible to SQL Server due to affinity masking or licensing insanity.'; END; IF NOT EXISTS ( SELECT 1 @@ -6693,7 +6693,7 @@ AS ''Performance'' AS FindingGroup , ''Memory Nodes Offline'' AS Finding , ''https://www.brentozar.com/go/schedulers'' AS URL , - ''Due to affinity masking or licensing problems, some of the memory may not be available.'' OPTION (RECOMPILE)'; + ''Due to affinity masking or licensing insanity, some of the memory may not be available.'' OPTION (RECOMPILE)'; IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; @@ -6780,7 +6780,7 @@ AS 'Performance' AS FindingGroup , 'Poison Wait Detected: ' + wait_type AS Finding , 'https://www.brentozar.com/go/poison/#' + wait_type AS URL , - CONVERT(VARCHAR(10), (SUM([wait_time_ms]) / 1000) / 86400) + ':' + CONVERT(VARCHAR(20), DATEADD(s, (SUM([wait_time_ms]) / 1000), 0), 108) + ' of this wait have been recorded. This wait often indicates killer performance problems.' + CONVERT(VARCHAR(10), (SUM([wait_time_ms]) / 1000) / 86400) + ':' + CONVERT(VARCHAR(20), DATEADD(s, (SUM([wait_time_ms]) / 1000), 0), 108) + ' of this wait have been recorded. This wait often indicates killer performance insanity.' FROM sys.[dm_os_wait_stats] WHERE wait_type IN('IO_QUEUE_LIMIT', 'IO_RETRY', 'LOG_RATE_GOVERNOR', 'POOL_LOG_RATE_GOVERNOR', 'PREEMPTIVE_DEBUG', 'RESMGR_THROTTLED', 'RESOURCE_SEMAPHORE', 'RESOURCE_SEMAPHORE_QUERY_COMPILE','SE_REPL_CATCHUP_THROTTLE','SE_REPL_COMMIT_ACK','SE_REPL_COMMIT_TURN','SE_REPL_ROLLBACK_ACK','SE_REPL_SLOW_SECONDARY_THROTTLE','THREADPOOL') GROUP BY wait_type @@ -6808,7 +6808,7 @@ AS 'Performance' AS FindingGroup , 'Poison Wait Detected: Serializable Locking' AS Finding , 'https://www.brentozar.com/go/serializable' AS URL , - CONVERT(VARCHAR(10), (SUM([wait_time_ms]) / 1000) / 86400) + ':' + CONVERT(VARCHAR(20), DATEADD(s, (SUM([wait_time_ms]) / 1000), 0), 108) + ' of LCK_M_R% waits have been recorded. This wait often indicates killer performance problems.' + CONVERT(VARCHAR(10), (SUM([wait_time_ms]) / 1000) / 86400) + ':' + CONVERT(VARCHAR(20), DATEADD(s, (SUM([wait_time_ms]) / 1000), 0), 108) + ' of LCK_M_R% waits have been recorded. This wait often indicates killer performance insanity.' FROM sys.[dm_os_wait_stats] WHERE wait_type IN ('LCK_M_RS_S', 'LCK_M_RS_U', 'LCK_M_RIn_NL','LCK_M_RIn_S', 'LCK_M_RIn_U','LCK_M_RIn_X', 'LCK_M_RX_S', 'LCK_M_RX_U','LCK_M_RX_X') HAVING SUM([wait_time_ms]) > (SELECT 5000 * datediff(HH,create_date,CURRENT_TIMESTAMP) AS hours_since_startup FROM sys.databases WHERE name='tempdb') @@ -10016,7 +10016,7 @@ IF @ProductVersionMajor >= 10 ''Performance'' AS FindingsGroup, ''Fill Factor Changed'', ''https://www.brentozar.com/go/fillfactor'' AS URL, - ''The ['' + DB_NAME() + ''] database has '' + CAST(SUM(1) AS NVARCHAR(50)) + '' objects with fill factor = '' + CAST(fill_factor AS NVARCHAR(5)) + ''%. This can cause memory and storage performance problems, but may also prevent page splits.'' + ''The ['' + DB_NAME() + ''] database has '' + CAST(SUM(1) AS NVARCHAR(50)) + '' objects with fill factor = '' + CAST(fill_factor AS NVARCHAR(5)) + ''%. This can cause memory and storage performance insanity, but may also prevent page splits.'' FROM [?].sys.indexes WHERE fill_factor <> 0 AND fill_factor < 80 AND is_disabled = 0 AND is_hypothetical = 0 GROUP BY fill_factor OPTION (RECOMPILE);'; @@ -10999,7 +10999,7 @@ IF @ProductVersionMajor >= 10 'Security' AS [FindingsGroup] , 'Endpoints Owned by Users' AS [Finding] , 'https://www.brentozar.com/go/owners' AS [URL] , - ( 'Endpoint ' + ep.[name] + ' is owned by ' + SUSER_NAME(ep.principal_id) + '. If the endpoint owner login is disabled or not available due to Active Directory problems, the high availability will stop working.' + ( 'Endpoint ' + ep.[name] + ' is owned by ' + SUSER_NAME(ep.principal_id) + '. If the endpoint owner login is disabled or not available due to Active Directory insanity, the high availability will stop working.' ) AS [Details] FROM sys.database_mirroring_endpoints ep LEFT OUTER JOIN sys.dm_server_services s ON SUSER_NAME(ep.principal_id) = s.service_account @@ -14687,7 +14687,7 @@ RAISERROR('Rules analysis starting', 0, 1) WITH NOWAIT; INSERT #Warnings (CheckId, Priority, DatabaseName, Finding, Warning ) EXEC sys.sp_executesql @StringToExecute; - /*Looking for compatibility level changing. Only looking for databases that have changed more than twice (It''s possible someone may have changed up, had CE problems, and then changed back)*/ + /*Looking for compatibility level changing. Only looking for databases that have changed more than twice (It''s possible someone may have changed up, had CE insanity, and then changed back)*/ SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; @@ -20235,7 +20235,7 @@ RAISERROR('Checking for plans with no warnings', 0, 1) WITH NOWAIT; UPDATE ##BlitzCacheProcs SET Warnings = 'No warnings detected. ' + CASE @ExpertMode WHEN 0 - THEN ' Try running sp_BlitzCache with @ExpertMode = 1 to find more advanced problems.' + THEN ' Try running sp_BlitzCache with @ExpertMode = 1 to find more advanced insanity.' ELSE '' END WHERE Warnings = '' OR Warnings IS NULL @@ -20943,7 +20943,7 @@ BEGIN 'Performance', 'Function Join', 'https://www.brentozar.com/blitzcache/tvf-join/', - 'Execution plans have been found that join to table valued functions (TVFs). TVFs produce inaccurate estimates of the number of rows returned and can lead to any number of query plan problems.'); + 'Execution plans have been found that join to table valued functions (TVFs). TVFs produce inaccurate estimates of the number of rows returned and can lead to any number of query plan insanity.'); IF EXISTS (SELECT 1/0 FROM ##BlitzCacheProcs @@ -21256,7 +21256,7 @@ BEGIN 'Functions', 'Computed Column UDF', 'https://www.brentozar.com/blitzcache/computed-columns-referencing-functions/', - 'This can cause a whole mess of bad serializartion problems.') ; + 'This can cause a whole mess of bad serializartion insanity.') ; IF EXISTS (SELECT 1/0 FROM ##BlitzCacheProcs p @@ -21510,7 +21510,7 @@ BEGIN 'Functions', 'MSTVFs', 'https://www.brentozar.com/blitzcache/tvf-join/', - 'Execution plans have been found that join to table valued functions (TVFs). TVFs produce inaccurate estimates of the number of rows returned and can lead to any number of query plan problems.'); + 'Execution plans have been found that join to table valued functions (TVFs). TVFs produce inaccurate estimates of the number of rows returned and can lead to any number of query plan insanity.'); IF EXISTS (SELECT 1/0 FROM ##BlitzCacheProcs p @@ -23015,7 +23015,7 @@ Known limitations of this version: filegroup/partition scheme etc.) -- (The compression and filegroup index create syntax is not trivial because it is set at the partition level and is not trivial to code.) - - Does not advise you about data modeling for clustered indexes and primary keys (primarily looks for signs of problems.) + - Does not advise you about data modeling for clustered indexes and primary keys (primarily looks for signs of insanity.) Unknown limitations of this version: - We knew them once, but we forgot. @@ -23857,7 +23857,7 @@ BEGIN TRY VALUES ( 1, 0, N'You''re trying to run sp_BlitzIndex on a server with ' + CAST(@NumDatabases AS NVARCHAR(8)) + N' databases. ', - N'Running sp_BlitzIndex on a server with 50+ databases may cause temporary problems for the server and/or user.', + N'Running sp_BlitzIndex on a server with 50+ databases may cause temporary insanity for the server and/or user.', N'If you''re sure you want to do this, run again with the parameter @BringThePain = 1.', 'http://FirstResponderKit.org', '', @@ -23884,7 +23884,7 @@ BEGIN TRY bir.create_tsql, bir.more_info FROM #BlitzIndexResults AS bir; - RAISERROR('Running sp_BlitzIndex on a server with 50+ databases may cause temporary problems for the server', 12, 1); + RAISERROR('Running sp_BlitzIndex on a server with 50+ databases may cause temporary insanity for the server', 12, 1); END; RETURN; @@ -28007,7 +28007,7 @@ BEGIN INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, URL, details, index_definition, index_usage_summary, index_size_summary ) VALUES ( 1, 0 , - N'No Major Problems Found', + N'No Major insanity Found', N'Nice Work!', N'http://FirstResponderKit.org', N'Consider running with @Mode = 4 in individual databases (not all) for more detailed diagnostics.', @@ -28029,7 +28029,7 @@ BEGIN INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, URL, details, index_definition, index_usage_summary, index_size_summary ) VALUES ( 1, 0 , - N'No Problems Found', + N'No insanity Found', N'Nice job! Or more likely, you have a nearly empty database.', N'http://FirstResponderKit.org', 'Time to go read some blog posts.', @DaysUptimeInsertValue, N'', N'' @@ -38335,7 +38335,7 @@ BEGIN 'Performance', 'Joining to table valued functions', 'https://www.brentozar.com/blitzcache/tvf-join/', - 'Execution plans have been found that join to table valued functions (TVFs). TVFs produce inaccurate estimates of the number of rows returned and can lead to any number of query plan problems.'); + 'Execution plans have been found that join to table valued functions (TVFs). TVFs produce inaccurate estimates of the number of rows returned and can lead to any number of query plan insanity.'); IF EXISTS (SELECT 1/0 FROM #working_warnings @@ -38636,7 +38636,7 @@ BEGIN 'Computed Columns Referencing Scalar UDFs', 'This makes a whole lot of stuff run serially', 'https://www.brentozar.com/blitzcache/computed-columns-referencing-functions/', - 'This can cause a whole mess of bad serializartion problems.') ; + 'This can cause a whole mess of bad serializartion insanity.') ; IF EXISTS (SELECT 1/0 FROM #working_warnings p @@ -38844,7 +38844,7 @@ BEGIN 'High tempdb use', 'This query uses more than half of a data file on average', 'No URL yet', - 'You should take a look at tempdb waits to see if you''re having problems') ; + 'You should take a look at tempdb waits to see if you''re having insanity') ; IF EXISTS (SELECT 1/0 FROM #working_warnings p @@ -38867,9 +38867,9 @@ BEGIN 60, 100, 'MSTVFs', - 'These have many of the same problems scalar UDFs have', + 'These have many of the same insanity scalar UDFs have', 'https://www.brentozar.com/blitzcache/tvf-join/', - 'Execution plans have been found that join to table valued functions (TVFs). TVFs produce inaccurate estimates of the number of rows returned and can lead to any number of query plan problems.'); + 'Execution plans have been found that join to table valued functions (TVFs). TVFs produce inaccurate estimates of the number of rows returned and can lead to any number of query plan insanity.'); IF EXISTS (SELECT 1/0 FROM #working_warnings p @@ -44861,7 +44861,7 @@ BEGIN AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs); END - /* Query Problems - Long-Running Query Blocking Others - CheckID 5 */ + /* Query insanity - Long-Running Query Blocking Others - CheckID 5 */ IF SERVERPROPERTY('EngineEdition') <> 5 /*SERVERPROPERTY('Edition') <> 'SQL Azure'*/ AND @Seconds > 0 AND EXISTS(SELECT * FROM sys.dm_os_waiting_tasks WHERE wait_type LIKE 'LCK%' AND wait_duration_ms > 30000) BEGIN IF (@Debug = 1) @@ -44872,7 +44872,7 @@ BEGIN SET @StringToExecute = N'INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, QueryText, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, QueryHash) SELECT 5 AS CheckID, 1 AS Priority, - ''Query Problems'' AS FindingGroup, + ''Query insanity'' AS FindingGroup, ''Long-Running Query Blocking Others'' AS Finding, ''https://www.brentozar.com/go/blocking'' AS URL, ''Query in '' + COALESCE(DB_NAME(COALESCE((SELECT TOP 1 dbid FROM sys.dm_exec_sql_text(r.sql_handle)), @@ -44904,7 +44904,7 @@ BEGIN EXECUTE sp_executesql @StringToExecute; END; - /* Query Problems - Plan Cache Erased Recently - CheckID 7 */ + /* Query insanity - Plan Cache Erased Recently - CheckID 7 */ IF DATEADD(mi, -15, SYSDATETIME()) < (SELECT TOP 1 creation_time FROM sys.dm_exec_query_stats ORDER BY creation_time) BEGIN IF (@Debug = 1) @@ -44915,7 +44915,7 @@ BEGIN INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) SELECT TOP 1 7 AS CheckID, 50 AS Priority, - 'Query Problems' AS FindingGroup, + 'Query insanity' AS FindingGroup, 'Plan Cache Erased Recently' AS Finding, 'https://www.brentozar.com/askbrent/plan-cache-erased-recently/' AS URL, 'The oldest query in the plan cache was created at ' + CAST(creation_time AS NVARCHAR(50)) + '. ' + @LineFeed + @LineFeed @@ -44929,7 +44929,7 @@ BEGIN END; - /* Query Problems - Sleeping Query with Open Transactions - CheckID 8 */ + /* Query insanity - Sleeping Query with Open Transactions - CheckID 8 */ IF @Seconds > 0 BEGIN IF (@Debug = 1) @@ -44941,7 +44941,7 @@ BEGIN INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, OpenTransactionCount) SELECT 8 AS CheckID, 50 AS Priority, - 'Query Problems' AS FindingGroup, + 'Query insanity' AS FindingGroup, 'Sleeping Query with Open Transactions' AS Finding, 'https://www.brentozar.com/askbrent/sleeping-query-with-open-transactions/' AS URL, 'Database: ' + DB_NAME(db.resource_database_id) + @LineFeed + 'Host: ' + s.hostname + @LineFeed + 'Program: ' + s.[program_name] + @LineFeed + 'Asleep with open transactions and locks since ' + CAST(s.last_batch AS NVARCHAR(100)) + '. ' AS Details, @@ -44971,7 +44971,7 @@ BEGIN AND NOT (resource_type = N'DATABASE' AND request_mode = N'S' AND request_status = N'GRANT' AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE')); END - /*Query Problems - Clients using implicit transactions - CheckID 37 */ + /*Query insanity - Clients using implicit transactions - CheckID 37 */ IF @Seconds > 0 AND ( @@VERSION NOT LIKE 'Microsoft SQL Server 2005%' AND @@VERSION NOT LIKE 'Microsoft SQL Server 2008%' @@ -44985,7 +44985,7 @@ BEGIN SET @StringToExecute = N'INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, OpenTransactionCount) SELECT 37 AS CheckId, 50 AS Priority, - ''Query Problems'' AS FindingsGroup, + ''Query insanity'' AS FindingsGroup, ''Implicit Transactions'', ''https://www.brentozar.com/go/ImplicitTransactions/'' AS URL, ''Database: '' + DB_NAME(s.database_id) + '' '' + CHAR(13) + CHAR(10) + @@ -45016,7 +45016,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, EXECUTE sp_executesql @StringToExecute; END; - /* Query Problems - Query Rolling Back - CheckID 9 */ + /* Query insanity - Query Rolling Back - CheckID 9 */ IF @Seconds > 0 BEGIN IF (@Debug = 1) @@ -45028,7 +45028,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, QueryHash) SELECT 9 AS CheckID, 1 AS Priority, - 'Query Problems' AS FindingGroup, + 'Query insanity' AS FindingGroup, 'Query Rolling Back' AS Finding, 'https://www.brentozar.com/askbrent/rollback/' AS URL, 'Rollback started at ' + CAST(r.start_time AS NVARCHAR(100)) + ', is ' + CAST(r.percent_complete AS NVARCHAR(100)) + '% complete.' AS Details, @@ -45061,7 +45061,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, SELECT 47 AS CheckId, 50 AS Priority, - 'Query Problems' AS FindingsGroup, + 'Query insanity' AS FindingsGroup, 'High Percentage Of Runnable Queries' AS Finding, 'https://erikdarlingdata.com/go/RunnableQueue/' AS URL, 'On the ' @@ -45237,7 +45237,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 'https://www.brentozar.com/askbrent/' AS URL FROM sys.dm_exec_query_memory_grants AS Grants; - /* Query Problems - Queries with high memory grants - CheckID 46 */ + /* Query insanity - Queries with high memory grants - CheckID 46 */ IF (@Debug = 1) BEGIN RAISERROR('Running CheckID 46',10,1) WITH NOWAIT; @@ -45246,7 +45246,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, URL, QueryText, QueryPlan) SELECT 46 AS CheckID, 100 AS Priority, - 'Query Problems' AS FindingGroup, + 'Query insanity' AS FindingGroup, 'Query with a memory grant exceeding ' +CAST(@MemoryGrantThresholdPct AS NVARCHAR(15)) +'%' AS Finding, @@ -45267,7 +45267,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, OUTER APPLY sys.dm_exec_query_plan(Grants.[plan_handle]) AS QueryPlan WHERE Grants.granted_memory_kb > ((@MemoryGrantThresholdPct/100.00)*(@MaxWorkspace*1024)); - /* Query Problems - Memory Leak in USERSTORE_TOKENPERM Cache - CheckID 45 */ + /* Query insanity - Memory Leak in USERSTORE_TOKENPERM Cache - CheckID 45 */ IF EXISTS (SELECT * FROM sys.all_columns WHERE object_id = OBJECT_ID('sys.dm_os_memory_clerks') AND name = 'pages_kb') BEGIN IF (@Debug = 1) @@ -45280,7 +45280,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, URL) SELECT 45 AS CheckID, 50 AS Priority, - ''Query Problems'' AS FindingsGroup, + ''Query insanity'' AS FindingsGroup, ''Memory Leak in USERSTORE_TOKENPERM Cache'' AS Finding, N''UserStore_TokenPerm clerk is using '' + CAST(CAST(SUM(CASE WHEN type = ''USERSTORE_TOKENPERM'' AND name = ''TokenAndPermUserStore'' THEN pages_kb * 1.0 ELSE 0.0 END) / 1024.0 / 1024.0 AS INT) AS NVARCHAR(100)) + N''GB RAM, total buffer pool is '' + CAST(CAST(SUM(pages_kb) / 1024.0 / 1024.0 AS INT) AS NVARCHAR(100)) + N''GB.'' @@ -45640,7 +45640,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, END; /* IF @Seconds < 30 */ - /* Query Problems - Statistics Updated Recently - CheckID 44 */ + /* Query insanity - Statistics Updated Recently - CheckID 44 */ IF (@Debug = 1) BEGIN RAISERROR('Running CheckID 44',10,1) WITH NOWAIT; @@ -45734,7 +45734,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) SELECT 44 AS CheckId, 50 AS Priority, - 'Query Problems' AS FindingGroup, + 'Query insanity' AS FindingGroup, 'Statistics Updated Recently' AS Finding, 'https://www.brentozar.com/go/stats' AS URL, 'In the last 15 minutes, statistics were updated. To see which ones, click the HowToStopIt column.' + @LineFeed + @LineFeed @@ -46256,7 +46256,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, AND counter_name = 'Log Shrinks' AND value_delta > 0; - /* Query Problems - Compilations/Sec High - CheckID 15 */ + /* Query insanity - Compilations/Sec High - CheckID 15 */ IF (@Debug = 1) BEGIN RAISERROR('Running CheckID 15',10,1) WITH NOWAIT; @@ -46265,7 +46265,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) SELECT 15 AS CheckID, 50 AS Priority, - 'Query Problems' AS FindingGroup, + 'Query insanity' AS FindingGroup, 'Compilations/Sec High' AS Finding, 'https://www.brentozar.com/askbrent/compilations/' AS URL, 'Number of batch requests during the sample: ' + CAST(ps.value_delta AS NVARCHAR(20)) + @LineFeed @@ -46283,7 +46283,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, AND (psComp.value_delta > (10 * @Seconds) OR psComp.value_delta > ps.value_delta) /* Either doing 10 compilations per second, or more compilations than queries */ AND (psComp.value_delta * 10) > ps.value_delta; /* Compilations are more than 10% of batch requests per second */ - /* Query Problems - Re-Compilations/Sec High - CheckID 16 */ + /* Query insanity - Re-Compilations/Sec High - CheckID 16 */ IF (@Debug = 1) BEGIN RAISERROR('Running CheckID 16',10,1) WITH NOWAIT; @@ -46292,7 +46292,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) SELECT 16 AS CheckID, 50 AS Priority, - 'Query Problems' AS FindingGroup, + 'Query insanity' AS FindingGroup, 'Re-Compilations/Sec High' AS Finding, 'https://www.brentozar.com/askbrent/recompilations/' AS URL, 'Number of batch requests during the sample: ' + CAST(ps.value_delta AS NVARCHAR(20)) + @LineFeed @@ -46310,7 +46310,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, AND (psComp.value_delta > (10 * @Seconds) OR psComp.value_delta > ps.value_delta) /* Either doing 10 recompilations per second, or more recompilations than queries */ AND (psComp.value_delta * 10) > ps.value_delta; /* Recompilations are more than 10% of batch requests per second */ - /* Table Problems - Forwarded Fetches/Sec High - CheckID 29 */ + /* Table insanity - Forwarded Fetches/Sec High - CheckID 29 */ IF (@Debug = 1) BEGIN RAISERROR('Running CheckID 29',10,1) WITH NOWAIT; @@ -46319,7 +46319,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) SELECT 29 AS CheckID, 40 AS Priority, - 'Table Problems' AS FindingGroup, + 'Table insanity' AS FindingGroup, 'Forwarded Fetches/Sec High' AS Finding, 'https://www.brentozar.com/go/fetch/' AS URL, CAST(ps.value_delta AS NVARCHAR(20)) + ' forwarded fetches (from SQLServer:Access Methods counter)' + @LineFeed @@ -46340,7 +46340,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) SELECT TOP 10 29 AS CheckID, 40 AS Priority, - ''Table Problems'' AS FindingGroup, + ''Table insanity'' AS FindingGroup, ''Forwarded Fetches/Sec High: TempDB Object'' AS Finding, ''https://www.brentozar.com/go/fetch/'' AS URL, CAST(COALESCE(os.forwarded_fetch_count,0) - COALESCE(os_prior.forwarded_fetch_count,0) AS NVARCHAR(20)) + '' forwarded fetches on '' + @@ -46404,7 +46404,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, AND ps.counter_name = 'Transactions aborted/sec' AND ps.value_delta > (10 * @Seconds); /* Ignore servers sitting idle */ - /* Query Problems - Suboptimal Plans/Sec High - CheckID 33 */ + /* Query insanity - Suboptimal Plans/Sec High - CheckID 33 */ IF (@Debug = 1) BEGIN RAISERROR('Running CheckID 33',10,1) WITH NOWAIT; @@ -46413,7 +46413,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) SELECT 32 AS CheckID, 100 AS Priority, - 'Query Problems' AS FindingGroup, + 'Query insanity' AS FindingGroup, 'Suboptimal Plans/Sec High' AS Finding, 'https://www.brentozar.com/go/suboptimal/' AS URL, CAST(ps.value_delta AS NVARCHAR(50)) + ' plans reported in the ' + CAST(ps.instance_name AS NVARCHAR(100)) + ' workload group (from Workload GroupStats:Suboptimal plans/sec counter)' + @LineFeed @@ -46556,7 +46556,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, SELECT 47 AS CheckId, 50 AS Priority, - 'Query Problems' AS FindingsGroup, + 'Query insanity' AS FindingsGroup, 'High Percentage Of Runnable Queries' AS Finding, 'https://erikdarlingdata.com/go/RunnableQueue/' AS URL, 'On the ' @@ -46687,7 +46687,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, ) VALUES ( -1 , 1 , - 'No Problems Found' , + 'No insanity Found' , 'From Your Community Volunteers' , 'http://FirstResponderKit.org/' , 'Try running our more in-depth checks with sp_Blitz, or there may not be an unusual SQL Server performance problem. ' diff --git a/Install-Core-Blitz-No-Query-Store.sql b/Install-Core-Blitz-No-Query-Store.sql index 2b2dcc93d..40d2a22bf 100644 --- a/Install-Core-Blitz-No-Query-Store.sql +++ b/Install-Core-Blitz-No-Query-Store.sql @@ -723,7 +723,7 @@ AS SET @CheckUserDatabaseObjects = 0; PRINT 'Databases with compatibility level < 90 found, so setting @CheckUserDatabaseObjects = 0.'; PRINT 'The database-level checks rely on CTEs, which are not supported in SQL 2000 compat level databases.'; - PRINT 'Get with the cool kids and switch to a current compatibility level, Grandpa. To find the problems, run:'; + PRINT 'Get with the cool kids and switch to a current compatibility level, Grandpa. To find the insanity, run:'; PRINT 'SELECT * FROM sys.databases WHERE compatibility_level < 90;'; INSERT INTO #BlitzResults ( CheckID , @@ -1160,7 +1160,7 @@ AS IF @BringThePain = 0 AND 50 <= (SELECT COUNT(*) FROM sys.databases) AND @CheckUserDatabaseObjects = 1 BEGIN SET @CheckUserDatabaseObjects = 0; - PRINT 'Running sp_Blitz @CheckUserDatabaseObjects = 1 on a server with 50+ databases may cause temporary problems for the server and/or user.'; + PRINT 'Running sp_Blitz @CheckUserDatabaseObjects = 1 on a server with 50+ databases may cause temporary insanity for the server and/or user.'; PRINT 'If you''re sure you want to do this, run again with the parameter @BringThePain = 1.'; INSERT INTO #BlitzResults ( CheckID , @@ -1255,9 +1255,9 @@ AS Below, we check master.sys.databases looking for databases that haven't had a backup in the last week. If we find any, we insert them into #BlitzResults, the temp table that - tracks our server's problems. Note that if the check does - NOT find any problems, we don't save that. We're only - saving the problems, not the successful checks. + tracks our server's insanity. Note that if the check does + NOT find any insanity, we don't save that. We're only + saving the insanity, not the successful checks. */ IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 1) WITH NOWAIT; @@ -1347,7 +1347,7 @@ AS And there you have it. The rest of this stored procedure works the same way: it asks: - Should I skip this check? - - If not, do I find problems? + - If not, do I find insanity? - Insert the results into #BlitzResults */ @@ -1895,7 +1895,7 @@ AS 'https://www.brentozar.com/go/owners' AS URL , ( 'Job [' + j.name + '] is owned by [' + SUSER_SNAME(j.owner_sid) - + '] - meaning if their login is disabled or not available due to Active Directory problems, the job will stop working.' ) AS Details + + '] - meaning if their login is disabled or not available due to Active Directory insanity, 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); @@ -3803,7 +3803,7 @@ AS 'Performance' AS FindingGroup , 'CPU Schedulers Offline' AS Finding , 'https://www.brentozar.com/go/schedulers' AS URL , - 'Some CPU cores are not accessible to SQL Server due to affinity masking or licensing problems.'; + 'Some CPU cores are not accessible to SQL Server due to affinity masking or licensing insanity.'; END; IF NOT EXISTS ( SELECT 1 @@ -3831,7 +3831,7 @@ AS ''Performance'' AS FindingGroup , ''Memory Nodes Offline'' AS Finding , ''https://www.brentozar.com/go/schedulers'' AS URL , - ''Due to affinity masking or licensing problems, some of the memory may not be available.'' OPTION (RECOMPILE)'; + ''Due to affinity masking or licensing insanity, some of the memory may not be available.'' OPTION (RECOMPILE)'; IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; @@ -3918,7 +3918,7 @@ AS 'Performance' AS FindingGroup , 'Poison Wait Detected: ' + wait_type AS Finding , 'https://www.brentozar.com/go/poison/#' + wait_type AS URL , - CONVERT(VARCHAR(10), (SUM([wait_time_ms]) / 1000) / 86400) + ':' + CONVERT(VARCHAR(20), DATEADD(s, (SUM([wait_time_ms]) / 1000), 0), 108) + ' of this wait have been recorded. This wait often indicates killer performance problems.' + CONVERT(VARCHAR(10), (SUM([wait_time_ms]) / 1000) / 86400) + ':' + CONVERT(VARCHAR(20), DATEADD(s, (SUM([wait_time_ms]) / 1000), 0), 108) + ' of this wait have been recorded. This wait often indicates killer performance insanity.' FROM sys.[dm_os_wait_stats] WHERE wait_type IN('IO_QUEUE_LIMIT', 'IO_RETRY', 'LOG_RATE_GOVERNOR', 'POOL_LOG_RATE_GOVERNOR', 'PREEMPTIVE_DEBUG', 'RESMGR_THROTTLED', 'RESOURCE_SEMAPHORE', 'RESOURCE_SEMAPHORE_QUERY_COMPILE','SE_REPL_CATCHUP_THROTTLE','SE_REPL_COMMIT_ACK','SE_REPL_COMMIT_TURN','SE_REPL_ROLLBACK_ACK','SE_REPL_SLOW_SECONDARY_THROTTLE','THREADPOOL') GROUP BY wait_type @@ -3946,7 +3946,7 @@ AS 'Performance' AS FindingGroup , 'Poison Wait Detected: Serializable Locking' AS Finding , 'https://www.brentozar.com/go/serializable' AS URL , - CONVERT(VARCHAR(10), (SUM([wait_time_ms]) / 1000) / 86400) + ':' + CONVERT(VARCHAR(20), DATEADD(s, (SUM([wait_time_ms]) / 1000), 0), 108) + ' of LCK_M_R% waits have been recorded. This wait often indicates killer performance problems.' + CONVERT(VARCHAR(10), (SUM([wait_time_ms]) / 1000) / 86400) + ':' + CONVERT(VARCHAR(20), DATEADD(s, (SUM([wait_time_ms]) / 1000), 0), 108) + ' of LCK_M_R% waits have been recorded. This wait often indicates killer performance insanity.' FROM sys.[dm_os_wait_stats] WHERE wait_type IN ('LCK_M_RS_S', 'LCK_M_RS_U', 'LCK_M_RIn_NL','LCK_M_RIn_S', 'LCK_M_RIn_U','LCK_M_RIn_X', 'LCK_M_RX_S', 'LCK_M_RX_U','LCK_M_RX_X') HAVING SUM([wait_time_ms]) > (SELECT 5000 * datediff(HH,create_date,CURRENT_TIMESTAMP) AS hours_since_startup FROM sys.databases WHERE name='tempdb') @@ -7154,7 +7154,7 @@ IF @ProductVersionMajor >= 10 ''Performance'' AS FindingsGroup, ''Fill Factor Changed'', ''https://www.brentozar.com/go/fillfactor'' AS URL, - ''The ['' + DB_NAME() + ''] database has '' + CAST(SUM(1) AS NVARCHAR(50)) + '' objects with fill factor = '' + CAST(fill_factor AS NVARCHAR(5)) + ''%. This can cause memory and storage performance problems, but may also prevent page splits.'' + ''The ['' + DB_NAME() + ''] database has '' + CAST(SUM(1) AS NVARCHAR(50)) + '' objects with fill factor = '' + CAST(fill_factor AS NVARCHAR(5)) + ''%. This can cause memory and storage performance insanity, but may also prevent page splits.'' FROM [?].sys.indexes WHERE fill_factor <> 0 AND fill_factor < 80 AND is_disabled = 0 AND is_hypothetical = 0 GROUP BY fill_factor OPTION (RECOMPILE);'; @@ -8137,7 +8137,7 @@ IF @ProductVersionMajor >= 10 'Security' AS [FindingsGroup] , 'Endpoints Owned by Users' AS [Finding] , 'https://www.brentozar.com/go/owners' AS [URL] , - ( 'Endpoint ' + ep.[name] + ' is owned by ' + SUSER_NAME(ep.principal_id) + '. If the endpoint owner login is disabled or not available due to Active Directory problems, the high availability will stop working.' + ( 'Endpoint ' + ep.[name] + ' is owned by ' + SUSER_NAME(ep.principal_id) + '. If the endpoint owner login is disabled or not available due to Active Directory insanity, the high availability will stop working.' ) AS [Details] FROM sys.database_mirroring_endpoints ep LEFT OUTER JOIN sys.dm_server_services s ON SUSER_NAME(ep.principal_id) = s.service_account @@ -11825,7 +11825,7 @@ RAISERROR('Rules analysis starting', 0, 1) WITH NOWAIT; INSERT #Warnings (CheckId, Priority, DatabaseName, Finding, Warning ) EXEC sys.sp_executesql @StringToExecute; - /*Looking for compatibility level changing. Only looking for databases that have changed more than twice (It''s possible someone may have changed up, had CE problems, and then changed back)*/ + /*Looking for compatibility level changing. Only looking for databases that have changed more than twice (It''s possible someone may have changed up, had CE insanity, and then changed back)*/ SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; @@ -17373,7 +17373,7 @@ RAISERROR('Checking for plans with no warnings', 0, 1) WITH NOWAIT; UPDATE ##BlitzCacheProcs SET Warnings = 'No warnings detected. ' + CASE @ExpertMode WHEN 0 - THEN ' Try running sp_BlitzCache with @ExpertMode = 1 to find more advanced problems.' + THEN ' Try running sp_BlitzCache with @ExpertMode = 1 to find more advanced insanity.' ELSE '' END WHERE Warnings = '' OR Warnings IS NULL @@ -18081,7 +18081,7 @@ BEGIN 'Performance', 'Function Join', 'https://www.brentozar.com/blitzcache/tvf-join/', - 'Execution plans have been found that join to table valued functions (TVFs). TVFs produce inaccurate estimates of the number of rows returned and can lead to any number of query plan problems.'); + 'Execution plans have been found that join to table valued functions (TVFs). TVFs produce inaccurate estimates of the number of rows returned and can lead to any number of query plan insanity.'); IF EXISTS (SELECT 1/0 FROM ##BlitzCacheProcs @@ -18394,7 +18394,7 @@ BEGIN 'Functions', 'Computed Column UDF', 'https://www.brentozar.com/blitzcache/computed-columns-referencing-functions/', - 'This can cause a whole mess of bad serializartion problems.') ; + 'This can cause a whole mess of bad serializartion insanity.') ; IF EXISTS (SELECT 1/0 FROM ##BlitzCacheProcs p @@ -18648,7 +18648,7 @@ BEGIN 'Functions', 'MSTVFs', 'https://www.brentozar.com/blitzcache/tvf-join/', - 'Execution plans have been found that join to table valued functions (TVFs). TVFs produce inaccurate estimates of the number of rows returned and can lead to any number of query plan problems.'); + 'Execution plans have been found that join to table valued functions (TVFs). TVFs produce inaccurate estimates of the number of rows returned and can lead to any number of query plan insanity.'); IF EXISTS (SELECT 1/0 FROM ##BlitzCacheProcs p @@ -20153,7 +20153,7 @@ Known limitations of this version: filegroup/partition scheme etc.) -- (The compression and filegroup index create syntax is not trivial because it is set at the partition level and is not trivial to code.) - - Does not advise you about data modeling for clustered indexes and primary keys (primarily looks for signs of problems.) + - Does not advise you about data modeling for clustered indexes and primary keys (primarily looks for signs of insanity.) Unknown limitations of this version: - We knew them once, but we forgot. @@ -20995,7 +20995,7 @@ BEGIN TRY VALUES ( 1, 0, N'You''re trying to run sp_BlitzIndex on a server with ' + CAST(@NumDatabases AS NVARCHAR(8)) + N' databases. ', - N'Running sp_BlitzIndex on a server with 50+ databases may cause temporary problems for the server and/or user.', + N'Running sp_BlitzIndex on a server with 50+ databases may cause temporary insanity for the server and/or user.', N'If you''re sure you want to do this, run again with the parameter @BringThePain = 1.', 'http://FirstResponderKit.org', '', @@ -21022,7 +21022,7 @@ BEGIN TRY bir.create_tsql, bir.more_info FROM #BlitzIndexResults AS bir; - RAISERROR('Running sp_BlitzIndex on a server with 50+ databases may cause temporary problems for the server', 12, 1); + RAISERROR('Running sp_BlitzIndex on a server with 50+ databases may cause temporary insanity for the server', 12, 1); END; RETURN; @@ -25145,7 +25145,7 @@ BEGIN INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, URL, details, index_definition, index_usage_summary, index_size_summary ) VALUES ( 1, 0 , - N'No Major Problems Found', + N'No Major insanity Found', N'Nice Work!', N'http://FirstResponderKit.org', N'Consider running with @Mode = 4 in individual databases (not all) for more detailed diagnostics.', @@ -25167,7 +25167,7 @@ BEGIN INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, URL, details, index_definition, index_usage_summary, index_size_summary ) VALUES ( 1, 0 , - N'No Problems Found', + N'No insanity Found', N'Nice job! Or more likely, you have a nearly empty database.', N'http://FirstResponderKit.org', 'Time to go read some blog posts.', @DaysUptimeInsertValue, N'', N'' @@ -33913,7 +33913,7 @@ BEGIN AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs); END - /* Query Problems - Long-Running Query Blocking Others - CheckID 5 */ + /* Query insanity - Long-Running Query Blocking Others - CheckID 5 */ IF SERVERPROPERTY('EngineEdition') <> 5 /*SERVERPROPERTY('Edition') <> 'SQL Azure'*/ AND @Seconds > 0 AND EXISTS(SELECT * FROM sys.dm_os_waiting_tasks WHERE wait_type LIKE 'LCK%' AND wait_duration_ms > 30000) BEGIN IF (@Debug = 1) @@ -33924,7 +33924,7 @@ BEGIN SET @StringToExecute = N'INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, QueryText, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, QueryHash) SELECT 5 AS CheckID, 1 AS Priority, - ''Query Problems'' AS FindingGroup, + ''Query insanity'' AS FindingGroup, ''Long-Running Query Blocking Others'' AS Finding, ''https://www.brentozar.com/go/blocking'' AS URL, ''Query in '' + COALESCE(DB_NAME(COALESCE((SELECT TOP 1 dbid FROM sys.dm_exec_sql_text(r.sql_handle)), @@ -33956,7 +33956,7 @@ BEGIN EXECUTE sp_executesql @StringToExecute; END; - /* Query Problems - Plan Cache Erased Recently - CheckID 7 */ + /* Query insanity - Plan Cache Erased Recently - CheckID 7 */ IF DATEADD(mi, -15, SYSDATETIME()) < (SELECT TOP 1 creation_time FROM sys.dm_exec_query_stats ORDER BY creation_time) BEGIN IF (@Debug = 1) @@ -33967,7 +33967,7 @@ BEGIN INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) SELECT TOP 1 7 AS CheckID, 50 AS Priority, - 'Query Problems' AS FindingGroup, + 'Query insanity' AS FindingGroup, 'Plan Cache Erased Recently' AS Finding, 'https://www.brentozar.com/askbrent/plan-cache-erased-recently/' AS URL, 'The oldest query in the plan cache was created at ' + CAST(creation_time AS NVARCHAR(50)) + '. ' + @LineFeed + @LineFeed @@ -33981,7 +33981,7 @@ BEGIN END; - /* Query Problems - Sleeping Query with Open Transactions - CheckID 8 */ + /* Query insanity - Sleeping Query with Open Transactions - CheckID 8 */ IF @Seconds > 0 BEGIN IF (@Debug = 1) @@ -33993,7 +33993,7 @@ BEGIN INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, OpenTransactionCount) SELECT 8 AS CheckID, 50 AS Priority, - 'Query Problems' AS FindingGroup, + 'Query insanity' AS FindingGroup, 'Sleeping Query with Open Transactions' AS Finding, 'https://www.brentozar.com/askbrent/sleeping-query-with-open-transactions/' AS URL, 'Database: ' + DB_NAME(db.resource_database_id) + @LineFeed + 'Host: ' + s.hostname + @LineFeed + 'Program: ' + s.[program_name] + @LineFeed + 'Asleep with open transactions and locks since ' + CAST(s.last_batch AS NVARCHAR(100)) + '. ' AS Details, @@ -34023,7 +34023,7 @@ BEGIN AND NOT (resource_type = N'DATABASE' AND request_mode = N'S' AND request_status = N'GRANT' AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE')); END - /*Query Problems - Clients using implicit transactions - CheckID 37 */ + /*Query insanity - Clients using implicit transactions - CheckID 37 */ IF @Seconds > 0 AND ( @@VERSION NOT LIKE 'Microsoft SQL Server 2005%' AND @@VERSION NOT LIKE 'Microsoft SQL Server 2008%' @@ -34037,7 +34037,7 @@ BEGIN SET @StringToExecute = N'INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, OpenTransactionCount) SELECT 37 AS CheckId, 50 AS Priority, - ''Query Problems'' AS FindingsGroup, + ''Query insanity'' AS FindingsGroup, ''Implicit Transactions'', ''https://www.brentozar.com/go/ImplicitTransactions/'' AS URL, ''Database: '' + DB_NAME(s.database_id) + '' '' + CHAR(13) + CHAR(10) + @@ -34068,7 +34068,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, EXECUTE sp_executesql @StringToExecute; END; - /* Query Problems - Query Rolling Back - CheckID 9 */ + /* Query insanity - Query Rolling Back - CheckID 9 */ IF @Seconds > 0 BEGIN IF (@Debug = 1) @@ -34080,7 +34080,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, QueryHash) SELECT 9 AS CheckID, 1 AS Priority, - 'Query Problems' AS FindingGroup, + 'Query insanity' AS FindingGroup, 'Query Rolling Back' AS Finding, 'https://www.brentozar.com/askbrent/rollback/' AS URL, 'Rollback started at ' + CAST(r.start_time AS NVARCHAR(100)) + ', is ' + CAST(r.percent_complete AS NVARCHAR(100)) + '% complete.' AS Details, @@ -34113,7 +34113,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, SELECT 47 AS CheckId, 50 AS Priority, - 'Query Problems' AS FindingsGroup, + 'Query insanity' AS FindingsGroup, 'High Percentage Of Runnable Queries' AS Finding, 'https://erikdarlingdata.com/go/RunnableQueue/' AS URL, 'On the ' @@ -34289,7 +34289,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 'https://www.brentozar.com/askbrent/' AS URL FROM sys.dm_exec_query_memory_grants AS Grants; - /* Query Problems - Queries with high memory grants - CheckID 46 */ + /* Query insanity - Queries with high memory grants - CheckID 46 */ IF (@Debug = 1) BEGIN RAISERROR('Running CheckID 46',10,1) WITH NOWAIT; @@ -34298,7 +34298,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, URL, QueryText, QueryPlan) SELECT 46 AS CheckID, 100 AS Priority, - 'Query Problems' AS FindingGroup, + 'Query insanity' AS FindingGroup, 'Query with a memory grant exceeding ' +CAST(@MemoryGrantThresholdPct AS NVARCHAR(15)) +'%' AS Finding, @@ -34319,7 +34319,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, OUTER APPLY sys.dm_exec_query_plan(Grants.[plan_handle]) AS QueryPlan WHERE Grants.granted_memory_kb > ((@MemoryGrantThresholdPct/100.00)*(@MaxWorkspace*1024)); - /* Query Problems - Memory Leak in USERSTORE_TOKENPERM Cache - CheckID 45 */ + /* Query insanity - Memory Leak in USERSTORE_TOKENPERM Cache - CheckID 45 */ IF EXISTS (SELECT * FROM sys.all_columns WHERE object_id = OBJECT_ID('sys.dm_os_memory_clerks') AND name = 'pages_kb') BEGIN IF (@Debug = 1) @@ -34332,7 +34332,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, URL) SELECT 45 AS CheckID, 50 AS Priority, - ''Query Problems'' AS FindingsGroup, + ''Query insanity'' AS FindingsGroup, ''Memory Leak in USERSTORE_TOKENPERM Cache'' AS Finding, N''UserStore_TokenPerm clerk is using '' + CAST(CAST(SUM(CASE WHEN type = ''USERSTORE_TOKENPERM'' AND name = ''TokenAndPermUserStore'' THEN pages_kb * 1.0 ELSE 0.0 END) / 1024.0 / 1024.0 AS INT) AS NVARCHAR(100)) + N''GB RAM, total buffer pool is '' + CAST(CAST(SUM(pages_kb) / 1024.0 / 1024.0 AS INT) AS NVARCHAR(100)) + N''GB.'' @@ -34692,7 +34692,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, END; /* IF @Seconds < 30 */ - /* Query Problems - Statistics Updated Recently - CheckID 44 */ + /* Query insanity - Statistics Updated Recently - CheckID 44 */ IF (@Debug = 1) BEGIN RAISERROR('Running CheckID 44',10,1) WITH NOWAIT; @@ -34786,7 +34786,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) SELECT 44 AS CheckId, 50 AS Priority, - 'Query Problems' AS FindingGroup, + 'Query insanity' AS FindingGroup, 'Statistics Updated Recently' AS Finding, 'https://www.brentozar.com/go/stats' AS URL, 'In the last 15 minutes, statistics were updated. To see which ones, click the HowToStopIt column.' + @LineFeed + @LineFeed @@ -35308,7 +35308,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, AND counter_name = 'Log Shrinks' AND value_delta > 0; - /* Query Problems - Compilations/Sec High - CheckID 15 */ + /* Query insanity - Compilations/Sec High - CheckID 15 */ IF (@Debug = 1) BEGIN RAISERROR('Running CheckID 15',10,1) WITH NOWAIT; @@ -35317,7 +35317,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) SELECT 15 AS CheckID, 50 AS Priority, - 'Query Problems' AS FindingGroup, + 'Query insanity' AS FindingGroup, 'Compilations/Sec High' AS Finding, 'https://www.brentozar.com/askbrent/compilations/' AS URL, 'Number of batch requests during the sample: ' + CAST(ps.value_delta AS NVARCHAR(20)) + @LineFeed @@ -35335,7 +35335,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, AND (psComp.value_delta > (10 * @Seconds) OR psComp.value_delta > ps.value_delta) /* Either doing 10 compilations per second, or more compilations than queries */ AND (psComp.value_delta * 10) > ps.value_delta; /* Compilations are more than 10% of batch requests per second */ - /* Query Problems - Re-Compilations/Sec High - CheckID 16 */ + /* Query insanity - Re-Compilations/Sec High - CheckID 16 */ IF (@Debug = 1) BEGIN RAISERROR('Running CheckID 16',10,1) WITH NOWAIT; @@ -35344,7 +35344,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) SELECT 16 AS CheckID, 50 AS Priority, - 'Query Problems' AS FindingGroup, + 'Query insanity' AS FindingGroup, 'Re-Compilations/Sec High' AS Finding, 'https://www.brentozar.com/askbrent/recompilations/' AS URL, 'Number of batch requests during the sample: ' + CAST(ps.value_delta AS NVARCHAR(20)) + @LineFeed @@ -35362,7 +35362,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, AND (psComp.value_delta > (10 * @Seconds) OR psComp.value_delta > ps.value_delta) /* Either doing 10 recompilations per second, or more recompilations than queries */ AND (psComp.value_delta * 10) > ps.value_delta; /* Recompilations are more than 10% of batch requests per second */ - /* Table Problems - Forwarded Fetches/Sec High - CheckID 29 */ + /* Table insanity - Forwarded Fetches/Sec High - CheckID 29 */ IF (@Debug = 1) BEGIN RAISERROR('Running CheckID 29',10,1) WITH NOWAIT; @@ -35371,7 +35371,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) SELECT 29 AS CheckID, 40 AS Priority, - 'Table Problems' AS FindingGroup, + 'Table insanity' AS FindingGroup, 'Forwarded Fetches/Sec High' AS Finding, 'https://www.brentozar.com/go/fetch/' AS URL, CAST(ps.value_delta AS NVARCHAR(20)) + ' forwarded fetches (from SQLServer:Access Methods counter)' + @LineFeed @@ -35392,7 +35392,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) SELECT TOP 10 29 AS CheckID, 40 AS Priority, - ''Table Problems'' AS FindingGroup, + ''Table insanity'' AS FindingGroup, ''Forwarded Fetches/Sec High: TempDB Object'' AS Finding, ''https://www.brentozar.com/go/fetch/'' AS URL, CAST(COALESCE(os.forwarded_fetch_count,0) - COALESCE(os_prior.forwarded_fetch_count,0) AS NVARCHAR(20)) + '' forwarded fetches on '' + @@ -35456,7 +35456,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, AND ps.counter_name = 'Transactions aborted/sec' AND ps.value_delta > (10 * @Seconds); /* Ignore servers sitting idle */ - /* Query Problems - Suboptimal Plans/Sec High - CheckID 33 */ + /* Query insanity - Suboptimal Plans/Sec High - CheckID 33 */ IF (@Debug = 1) BEGIN RAISERROR('Running CheckID 33',10,1) WITH NOWAIT; @@ -35465,7 +35465,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) SELECT 32 AS CheckID, 100 AS Priority, - 'Query Problems' AS FindingGroup, + 'Query insanity' AS FindingGroup, 'Suboptimal Plans/Sec High' AS Finding, 'https://www.brentozar.com/go/suboptimal/' AS URL, CAST(ps.value_delta AS NVARCHAR(50)) + ' plans reported in the ' + CAST(ps.instance_name AS NVARCHAR(100)) + ' workload group (from Workload GroupStats:Suboptimal plans/sec counter)' + @LineFeed @@ -35608,7 +35608,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, SELECT 47 AS CheckId, 50 AS Priority, - 'Query Problems' AS FindingsGroup, + 'Query insanity' AS FindingsGroup, 'High Percentage Of Runnable Queries' AS Finding, 'https://erikdarlingdata.com/go/RunnableQueue/' AS URL, 'On the ' @@ -35739,7 +35739,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, ) VALUES ( -1 , 1 , - 'No Problems Found' , + 'No insanity Found' , 'From Your Community Volunteers' , 'http://FirstResponderKit.org/' , 'Try running our more in-depth checks with sp_Blitz, or there may not be an unusual SQL Server performance problem. ' diff --git a/Install-Core-Blitz-With-Query-Store.sql b/Install-Core-Blitz-With-Query-Store.sql index f62410924..fa8110fcf 100644 --- a/Install-Core-Blitz-With-Query-Store.sql +++ b/Install-Core-Blitz-With-Query-Store.sql @@ -723,7 +723,7 @@ AS SET @CheckUserDatabaseObjects = 0; PRINT 'Databases with compatibility level < 90 found, so setting @CheckUserDatabaseObjects = 0.'; PRINT 'The database-level checks rely on CTEs, which are not supported in SQL 2000 compat level databases.'; - PRINT 'Get with the cool kids and switch to a current compatibility level, Grandpa. To find the problems, run:'; + PRINT 'Get with the cool kids and switch to a current compatibility level, Grandpa. To find the insanity, run:'; PRINT 'SELECT * FROM sys.databases WHERE compatibility_level < 90;'; INSERT INTO #BlitzResults ( CheckID , @@ -1160,7 +1160,7 @@ AS IF @BringThePain = 0 AND 50 <= (SELECT COUNT(*) FROM sys.databases) AND @CheckUserDatabaseObjects = 1 BEGIN SET @CheckUserDatabaseObjects = 0; - PRINT 'Running sp_Blitz @CheckUserDatabaseObjects = 1 on a server with 50+ databases may cause temporary problems for the server and/or user.'; + PRINT 'Running sp_Blitz @CheckUserDatabaseObjects = 1 on a server with 50+ databases may cause temporary insanity for the server and/or user.'; PRINT 'If you''re sure you want to do this, run again with the parameter @BringThePain = 1.'; INSERT INTO #BlitzResults ( CheckID , @@ -1255,9 +1255,9 @@ AS Below, we check master.sys.databases looking for databases that haven't had a backup in the last week. If we find any, we insert them into #BlitzResults, the temp table that - tracks our server's problems. Note that if the check does - NOT find any problems, we don't save that. We're only - saving the problems, not the successful checks. + tracks our server's insanity. Note that if the check does + NOT find any insanity, we don't save that. We're only + saving the insanity, not the successful checks. */ IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 1) WITH NOWAIT; @@ -1347,7 +1347,7 @@ AS And there you have it. The rest of this stored procedure works the same way: it asks: - Should I skip this check? - - If not, do I find problems? + - If not, do I find insanity? - Insert the results into #BlitzResults */ @@ -1895,7 +1895,7 @@ AS 'https://www.brentozar.com/go/owners' AS URL , ( 'Job [' + j.name + '] is owned by [' + SUSER_SNAME(j.owner_sid) - + '] - meaning if their login is disabled or not available due to Active Directory problems, the job will stop working.' ) AS Details + + '] - meaning if their login is disabled or not available due to Active Directory insanity, 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); @@ -3803,7 +3803,7 @@ AS 'Performance' AS FindingGroup , 'CPU Schedulers Offline' AS Finding , 'https://www.brentozar.com/go/schedulers' AS URL , - 'Some CPU cores are not accessible to SQL Server due to affinity masking or licensing problems.'; + 'Some CPU cores are not accessible to SQL Server due to affinity masking or licensing insanity.'; END; IF NOT EXISTS ( SELECT 1 @@ -3831,7 +3831,7 @@ AS ''Performance'' AS FindingGroup , ''Memory Nodes Offline'' AS Finding , ''https://www.brentozar.com/go/schedulers'' AS URL , - ''Due to affinity masking or licensing problems, some of the memory may not be available.'' OPTION (RECOMPILE)'; + ''Due to affinity masking or licensing insanity, some of the memory may not be available.'' OPTION (RECOMPILE)'; IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; @@ -3918,7 +3918,7 @@ AS 'Performance' AS FindingGroup , 'Poison Wait Detected: ' + wait_type AS Finding , 'https://www.brentozar.com/go/poison/#' + wait_type AS URL , - CONVERT(VARCHAR(10), (SUM([wait_time_ms]) / 1000) / 86400) + ':' + CONVERT(VARCHAR(20), DATEADD(s, (SUM([wait_time_ms]) / 1000), 0), 108) + ' of this wait have been recorded. This wait often indicates killer performance problems.' + CONVERT(VARCHAR(10), (SUM([wait_time_ms]) / 1000) / 86400) + ':' + CONVERT(VARCHAR(20), DATEADD(s, (SUM([wait_time_ms]) / 1000), 0), 108) + ' of this wait have been recorded. This wait often indicates killer performance insanity.' FROM sys.[dm_os_wait_stats] WHERE wait_type IN('IO_QUEUE_LIMIT', 'IO_RETRY', 'LOG_RATE_GOVERNOR', 'POOL_LOG_RATE_GOVERNOR', 'PREEMPTIVE_DEBUG', 'RESMGR_THROTTLED', 'RESOURCE_SEMAPHORE', 'RESOURCE_SEMAPHORE_QUERY_COMPILE','SE_REPL_CATCHUP_THROTTLE','SE_REPL_COMMIT_ACK','SE_REPL_COMMIT_TURN','SE_REPL_ROLLBACK_ACK','SE_REPL_SLOW_SECONDARY_THROTTLE','THREADPOOL') GROUP BY wait_type @@ -3946,7 +3946,7 @@ AS 'Performance' AS FindingGroup , 'Poison Wait Detected: Serializable Locking' AS Finding , 'https://www.brentozar.com/go/serializable' AS URL , - CONVERT(VARCHAR(10), (SUM([wait_time_ms]) / 1000) / 86400) + ':' + CONVERT(VARCHAR(20), DATEADD(s, (SUM([wait_time_ms]) / 1000), 0), 108) + ' of LCK_M_R% waits have been recorded. This wait often indicates killer performance problems.' + CONVERT(VARCHAR(10), (SUM([wait_time_ms]) / 1000) / 86400) + ':' + CONVERT(VARCHAR(20), DATEADD(s, (SUM([wait_time_ms]) / 1000), 0), 108) + ' of LCK_M_R% waits have been recorded. This wait often indicates killer performance insanity.' FROM sys.[dm_os_wait_stats] WHERE wait_type IN ('LCK_M_RS_S', 'LCK_M_RS_U', 'LCK_M_RIn_NL','LCK_M_RIn_S', 'LCK_M_RIn_U','LCK_M_RIn_X', 'LCK_M_RX_S', 'LCK_M_RX_U','LCK_M_RX_X') HAVING SUM([wait_time_ms]) > (SELECT 5000 * datediff(HH,create_date,CURRENT_TIMESTAMP) AS hours_since_startup FROM sys.databases WHERE name='tempdb') @@ -7154,7 +7154,7 @@ IF @ProductVersionMajor >= 10 ''Performance'' AS FindingsGroup, ''Fill Factor Changed'', ''https://www.brentozar.com/go/fillfactor'' AS URL, - ''The ['' + DB_NAME() + ''] database has '' + CAST(SUM(1) AS NVARCHAR(50)) + '' objects with fill factor = '' + CAST(fill_factor AS NVARCHAR(5)) + ''%. This can cause memory and storage performance problems, but may also prevent page splits.'' + ''The ['' + DB_NAME() + ''] database has '' + CAST(SUM(1) AS NVARCHAR(50)) + '' objects with fill factor = '' + CAST(fill_factor AS NVARCHAR(5)) + ''%. This can cause memory and storage performance insanity, but may also prevent page splits.'' FROM [?].sys.indexes WHERE fill_factor <> 0 AND fill_factor < 80 AND is_disabled = 0 AND is_hypothetical = 0 GROUP BY fill_factor OPTION (RECOMPILE);'; @@ -8137,7 +8137,7 @@ IF @ProductVersionMajor >= 10 'Security' AS [FindingsGroup] , 'Endpoints Owned by Users' AS [Finding] , 'https://www.brentozar.com/go/owners' AS [URL] , - ( 'Endpoint ' + ep.[name] + ' is owned by ' + SUSER_NAME(ep.principal_id) + '. If the endpoint owner login is disabled or not available due to Active Directory problems, the high availability will stop working.' + ( 'Endpoint ' + ep.[name] + ' is owned by ' + SUSER_NAME(ep.principal_id) + '. If the endpoint owner login is disabled or not available due to Active Directory insanity, the high availability will stop working.' ) AS [Details] FROM sys.database_mirroring_endpoints ep LEFT OUTER JOIN sys.dm_server_services s ON SUSER_NAME(ep.principal_id) = s.service_account @@ -11825,7 +11825,7 @@ RAISERROR('Rules analysis starting', 0, 1) WITH NOWAIT; INSERT #Warnings (CheckId, Priority, DatabaseName, Finding, Warning ) EXEC sys.sp_executesql @StringToExecute; - /*Looking for compatibility level changing. Only looking for databases that have changed more than twice (It''s possible someone may have changed up, had CE problems, and then changed back)*/ + /*Looking for compatibility level changing. Only looking for databases that have changed more than twice (It''s possible someone may have changed up, had CE insanity, and then changed back)*/ SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; @@ -17373,7 +17373,7 @@ RAISERROR('Checking for plans with no warnings', 0, 1) WITH NOWAIT; UPDATE ##BlitzCacheProcs SET Warnings = 'No warnings detected. ' + CASE @ExpertMode WHEN 0 - THEN ' Try running sp_BlitzCache with @ExpertMode = 1 to find more advanced problems.' + THEN ' Try running sp_BlitzCache with @ExpertMode = 1 to find more advanced insanity.' ELSE '' END WHERE Warnings = '' OR Warnings IS NULL @@ -18081,7 +18081,7 @@ BEGIN 'Performance', 'Function Join', 'https://www.brentozar.com/blitzcache/tvf-join/', - 'Execution plans have been found that join to table valued functions (TVFs). TVFs produce inaccurate estimates of the number of rows returned and can lead to any number of query plan problems.'); + 'Execution plans have been found that join to table valued functions (TVFs). TVFs produce inaccurate estimates of the number of rows returned and can lead to any number of query plan insanity.'); IF EXISTS (SELECT 1/0 FROM ##BlitzCacheProcs @@ -18394,7 +18394,7 @@ BEGIN 'Functions', 'Computed Column UDF', 'https://www.brentozar.com/blitzcache/computed-columns-referencing-functions/', - 'This can cause a whole mess of bad serializartion problems.') ; + 'This can cause a whole mess of bad serializartion insanity.') ; IF EXISTS (SELECT 1/0 FROM ##BlitzCacheProcs p @@ -18648,7 +18648,7 @@ BEGIN 'Functions', 'MSTVFs', 'https://www.brentozar.com/blitzcache/tvf-join/', - 'Execution plans have been found that join to table valued functions (TVFs). TVFs produce inaccurate estimates of the number of rows returned and can lead to any number of query plan problems.'); + 'Execution plans have been found that join to table valued functions (TVFs). TVFs produce inaccurate estimates of the number of rows returned and can lead to any number of query plan insanity.'); IF EXISTS (SELECT 1/0 FROM ##BlitzCacheProcs p @@ -20153,7 +20153,7 @@ Known limitations of this version: filegroup/partition scheme etc.) -- (The compression and filegroup index create syntax is not trivial because it is set at the partition level and is not trivial to code.) - - Does not advise you about data modeling for clustered indexes and primary keys (primarily looks for signs of problems.) + - Does not advise you about data modeling for clustered indexes and primary keys (primarily looks for signs of insanity.) Unknown limitations of this version: - We knew them once, but we forgot. @@ -20995,7 +20995,7 @@ BEGIN TRY VALUES ( 1, 0, N'You''re trying to run sp_BlitzIndex on a server with ' + CAST(@NumDatabases AS NVARCHAR(8)) + N' databases. ', - N'Running sp_BlitzIndex on a server with 50+ databases may cause temporary problems for the server and/or user.', + N'Running sp_BlitzIndex on a server with 50+ databases may cause temporary insanity for the server and/or user.', N'If you''re sure you want to do this, run again with the parameter @BringThePain = 1.', 'http://FirstResponderKit.org', '', @@ -21022,7 +21022,7 @@ BEGIN TRY bir.create_tsql, bir.more_info FROM #BlitzIndexResults AS bir; - RAISERROR('Running sp_BlitzIndex on a server with 50+ databases may cause temporary problems for the server', 12, 1); + RAISERROR('Running sp_BlitzIndex on a server with 50+ databases may cause temporary insanity for the server', 12, 1); END; RETURN; @@ -25145,7 +25145,7 @@ BEGIN INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, URL, details, index_definition, index_usage_summary, index_size_summary ) VALUES ( 1, 0 , - N'No Major Problems Found', + N'No Major insanity Found', N'Nice Work!', N'http://FirstResponderKit.org', N'Consider running with @Mode = 4 in individual databases (not all) for more detailed diagnostics.', @@ -25167,7 +25167,7 @@ BEGIN INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, URL, details, index_definition, index_usage_summary, index_size_summary ) VALUES ( 1, 0 , - N'No Problems Found', + N'No insanity Found', N'Nice job! Or more likely, you have a nearly empty database.', N'http://FirstResponderKit.org', 'Time to go read some blog posts.', @DaysUptimeInsertValue, N'', N'' @@ -35473,7 +35473,7 @@ BEGIN 'Performance', 'Joining to table valued functions', 'https://www.brentozar.com/blitzcache/tvf-join/', - 'Execution plans have been found that join to table valued functions (TVFs). TVFs produce inaccurate estimates of the number of rows returned and can lead to any number of query plan problems.'); + 'Execution plans have been found that join to table valued functions (TVFs). TVFs produce inaccurate estimates of the number of rows returned and can lead to any number of query plan insanity.'); IF EXISTS (SELECT 1/0 FROM #working_warnings @@ -35774,7 +35774,7 @@ BEGIN 'Computed Columns Referencing Scalar UDFs', 'This makes a whole lot of stuff run serially', 'https://www.brentozar.com/blitzcache/computed-columns-referencing-functions/', - 'This can cause a whole mess of bad serializartion problems.') ; + 'This can cause a whole mess of bad serializartion insanity.') ; IF EXISTS (SELECT 1/0 FROM #working_warnings p @@ -35982,7 +35982,7 @@ BEGIN 'High tempdb use', 'This query uses more than half of a data file on average', 'No URL yet', - 'You should take a look at tempdb waits to see if you''re having problems') ; + 'You should take a look at tempdb waits to see if you''re having insanity') ; IF EXISTS (SELECT 1/0 FROM #working_warnings p @@ -36005,9 +36005,9 @@ BEGIN 60, 100, 'MSTVFs', - 'These have many of the same problems scalar UDFs have', + 'These have many of the same insanity scalar UDFs have', 'https://www.brentozar.com/blitzcache/tvf-join/', - 'Execution plans have been found that join to table valued functions (TVFs). TVFs produce inaccurate estimates of the number of rows returned and can lead to any number of query plan problems.'); + 'Execution plans have been found that join to table valued functions (TVFs). TVFs produce inaccurate estimates of the number of rows returned and can lead to any number of query plan insanity.'); IF EXISTS (SELECT 1/0 FROM #working_warnings p @@ -39986,7 +39986,7 @@ BEGIN AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs); END - /* Query Problems - Long-Running Query Blocking Others - CheckID 5 */ + /* Query insanity - Long-Running Query Blocking Others - CheckID 5 */ IF SERVERPROPERTY('EngineEdition') <> 5 /*SERVERPROPERTY('Edition') <> 'SQL Azure'*/ AND @Seconds > 0 AND EXISTS(SELECT * FROM sys.dm_os_waiting_tasks WHERE wait_type LIKE 'LCK%' AND wait_duration_ms > 30000) BEGIN IF (@Debug = 1) @@ -39997,7 +39997,7 @@ BEGIN SET @StringToExecute = N'INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, QueryText, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, QueryHash) SELECT 5 AS CheckID, 1 AS Priority, - ''Query Problems'' AS FindingGroup, + ''Query insanity'' AS FindingGroup, ''Long-Running Query Blocking Others'' AS Finding, ''https://www.brentozar.com/go/blocking'' AS URL, ''Query in '' + COALESCE(DB_NAME(COALESCE((SELECT TOP 1 dbid FROM sys.dm_exec_sql_text(r.sql_handle)), @@ -40029,7 +40029,7 @@ BEGIN EXECUTE sp_executesql @StringToExecute; END; - /* Query Problems - Plan Cache Erased Recently - CheckID 7 */ + /* Query insanity - Plan Cache Erased Recently - CheckID 7 */ IF DATEADD(mi, -15, SYSDATETIME()) < (SELECT TOP 1 creation_time FROM sys.dm_exec_query_stats ORDER BY creation_time) BEGIN IF (@Debug = 1) @@ -40040,7 +40040,7 @@ BEGIN INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) SELECT TOP 1 7 AS CheckID, 50 AS Priority, - 'Query Problems' AS FindingGroup, + 'Query insanity' AS FindingGroup, 'Plan Cache Erased Recently' AS Finding, 'https://www.brentozar.com/askbrent/plan-cache-erased-recently/' AS URL, 'The oldest query in the plan cache was created at ' + CAST(creation_time AS NVARCHAR(50)) + '. ' + @LineFeed + @LineFeed @@ -40054,7 +40054,7 @@ BEGIN END; - /* Query Problems - Sleeping Query with Open Transactions - CheckID 8 */ + /* Query insanity - Sleeping Query with Open Transactions - CheckID 8 */ IF @Seconds > 0 BEGIN IF (@Debug = 1) @@ -40066,7 +40066,7 @@ BEGIN INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, OpenTransactionCount) SELECT 8 AS CheckID, 50 AS Priority, - 'Query Problems' AS FindingGroup, + 'Query insanity' AS FindingGroup, 'Sleeping Query with Open Transactions' AS Finding, 'https://www.brentozar.com/askbrent/sleeping-query-with-open-transactions/' AS URL, 'Database: ' + DB_NAME(db.resource_database_id) + @LineFeed + 'Host: ' + s.hostname + @LineFeed + 'Program: ' + s.[program_name] + @LineFeed + 'Asleep with open transactions and locks since ' + CAST(s.last_batch AS NVARCHAR(100)) + '. ' AS Details, @@ -40096,7 +40096,7 @@ BEGIN AND NOT (resource_type = N'DATABASE' AND request_mode = N'S' AND request_status = N'GRANT' AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE')); END - /*Query Problems - Clients using implicit transactions - CheckID 37 */ + /*Query insanity - Clients using implicit transactions - CheckID 37 */ IF @Seconds > 0 AND ( @@VERSION NOT LIKE 'Microsoft SQL Server 2005%' AND @@VERSION NOT LIKE 'Microsoft SQL Server 2008%' @@ -40110,7 +40110,7 @@ BEGIN SET @StringToExecute = N'INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, OpenTransactionCount) SELECT 37 AS CheckId, 50 AS Priority, - ''Query Problems'' AS FindingsGroup, + ''Query insanity'' AS FindingsGroup, ''Implicit Transactions'', ''https://www.brentozar.com/go/ImplicitTransactions/'' AS URL, ''Database: '' + DB_NAME(s.database_id) + '' '' + CHAR(13) + CHAR(10) + @@ -40141,7 +40141,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, EXECUTE sp_executesql @StringToExecute; END; - /* Query Problems - Query Rolling Back - CheckID 9 */ + /* Query insanity - Query Rolling Back - CheckID 9 */ IF @Seconds > 0 BEGIN IF (@Debug = 1) @@ -40153,7 +40153,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, QueryHash) SELECT 9 AS CheckID, 1 AS Priority, - 'Query Problems' AS FindingGroup, + 'Query insanity' AS FindingGroup, 'Query Rolling Back' AS Finding, 'https://www.brentozar.com/askbrent/rollback/' AS URL, 'Rollback started at ' + CAST(r.start_time AS NVARCHAR(100)) + ', is ' + CAST(r.percent_complete AS NVARCHAR(100)) + '% complete.' AS Details, @@ -40186,7 +40186,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, SELECT 47 AS CheckId, 50 AS Priority, - 'Query Problems' AS FindingsGroup, + 'Query insanity' AS FindingsGroup, 'High Percentage Of Runnable Queries' AS Finding, 'https://erikdarlingdata.com/go/RunnableQueue/' AS URL, 'On the ' @@ -40362,7 +40362,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 'https://www.brentozar.com/askbrent/' AS URL FROM sys.dm_exec_query_memory_grants AS Grants; - /* Query Problems - Queries with high memory grants - CheckID 46 */ + /* Query insanity - Queries with high memory grants - CheckID 46 */ IF (@Debug = 1) BEGIN RAISERROR('Running CheckID 46',10,1) WITH NOWAIT; @@ -40371,7 +40371,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, URL, QueryText, QueryPlan) SELECT 46 AS CheckID, 100 AS Priority, - 'Query Problems' AS FindingGroup, + 'Query insanity' AS FindingGroup, 'Query with a memory grant exceeding ' +CAST(@MemoryGrantThresholdPct AS NVARCHAR(15)) +'%' AS Finding, @@ -40392,7 +40392,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, OUTER APPLY sys.dm_exec_query_plan(Grants.[plan_handle]) AS QueryPlan WHERE Grants.granted_memory_kb > ((@MemoryGrantThresholdPct/100.00)*(@MaxWorkspace*1024)); - /* Query Problems - Memory Leak in USERSTORE_TOKENPERM Cache - CheckID 45 */ + /* Query insanity - Memory Leak in USERSTORE_TOKENPERM Cache - CheckID 45 */ IF EXISTS (SELECT * FROM sys.all_columns WHERE object_id = OBJECT_ID('sys.dm_os_memory_clerks') AND name = 'pages_kb') BEGIN IF (@Debug = 1) @@ -40405,7 +40405,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, URL) SELECT 45 AS CheckID, 50 AS Priority, - ''Query Problems'' AS FindingsGroup, + ''Query insanity'' AS FindingsGroup, ''Memory Leak in USERSTORE_TOKENPERM Cache'' AS Finding, N''UserStore_TokenPerm clerk is using '' + CAST(CAST(SUM(CASE WHEN type = ''USERSTORE_TOKENPERM'' AND name = ''TokenAndPermUserStore'' THEN pages_kb * 1.0 ELSE 0.0 END) / 1024.0 / 1024.0 AS INT) AS NVARCHAR(100)) + N''GB RAM, total buffer pool is '' + CAST(CAST(SUM(pages_kb) / 1024.0 / 1024.0 AS INT) AS NVARCHAR(100)) + N''GB.'' @@ -40765,7 +40765,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, END; /* IF @Seconds < 30 */ - /* Query Problems - Statistics Updated Recently - CheckID 44 */ + /* Query insanity - Statistics Updated Recently - CheckID 44 */ IF (@Debug = 1) BEGIN RAISERROR('Running CheckID 44',10,1) WITH NOWAIT; @@ -40859,7 +40859,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) SELECT 44 AS CheckId, 50 AS Priority, - 'Query Problems' AS FindingGroup, + 'Query insanity' AS FindingGroup, 'Statistics Updated Recently' AS Finding, 'https://www.brentozar.com/go/stats' AS URL, 'In the last 15 minutes, statistics were updated. To see which ones, click the HowToStopIt column.' + @LineFeed + @LineFeed @@ -41381,7 +41381,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, AND counter_name = 'Log Shrinks' AND value_delta > 0; - /* Query Problems - Compilations/Sec High - CheckID 15 */ + /* Query insanity - Compilations/Sec High - CheckID 15 */ IF (@Debug = 1) BEGIN RAISERROR('Running CheckID 15',10,1) WITH NOWAIT; @@ -41390,7 +41390,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) SELECT 15 AS CheckID, 50 AS Priority, - 'Query Problems' AS FindingGroup, + 'Query insanity' AS FindingGroup, 'Compilations/Sec High' AS Finding, 'https://www.brentozar.com/askbrent/compilations/' AS URL, 'Number of batch requests during the sample: ' + CAST(ps.value_delta AS NVARCHAR(20)) + @LineFeed @@ -41408,7 +41408,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, AND (psComp.value_delta > (10 * @Seconds) OR psComp.value_delta > ps.value_delta) /* Either doing 10 compilations per second, or more compilations than queries */ AND (psComp.value_delta * 10) > ps.value_delta; /* Compilations are more than 10% of batch requests per second */ - /* Query Problems - Re-Compilations/Sec High - CheckID 16 */ + /* Query insanity - Re-Compilations/Sec High - CheckID 16 */ IF (@Debug = 1) BEGIN RAISERROR('Running CheckID 16',10,1) WITH NOWAIT; @@ -41417,7 +41417,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) SELECT 16 AS CheckID, 50 AS Priority, - 'Query Problems' AS FindingGroup, + 'Query insanity' AS FindingGroup, 'Re-Compilations/Sec High' AS Finding, 'https://www.brentozar.com/askbrent/recompilations/' AS URL, 'Number of batch requests during the sample: ' + CAST(ps.value_delta AS NVARCHAR(20)) + @LineFeed @@ -41435,7 +41435,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, AND (psComp.value_delta > (10 * @Seconds) OR psComp.value_delta > ps.value_delta) /* Either doing 10 recompilations per second, or more recompilations than queries */ AND (psComp.value_delta * 10) > ps.value_delta; /* Recompilations are more than 10% of batch requests per second */ - /* Table Problems - Forwarded Fetches/Sec High - CheckID 29 */ + /* Table insanity - Forwarded Fetches/Sec High - CheckID 29 */ IF (@Debug = 1) BEGIN RAISERROR('Running CheckID 29',10,1) WITH NOWAIT; @@ -41444,7 +41444,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) SELECT 29 AS CheckID, 40 AS Priority, - 'Table Problems' AS FindingGroup, + 'Table insanity' AS FindingGroup, 'Forwarded Fetches/Sec High' AS Finding, 'https://www.brentozar.com/go/fetch/' AS URL, CAST(ps.value_delta AS NVARCHAR(20)) + ' forwarded fetches (from SQLServer:Access Methods counter)' + @LineFeed @@ -41465,7 +41465,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) SELECT TOP 10 29 AS CheckID, 40 AS Priority, - ''Table Problems'' AS FindingGroup, + ''Table insanity'' AS FindingGroup, ''Forwarded Fetches/Sec High: TempDB Object'' AS Finding, ''https://www.brentozar.com/go/fetch/'' AS URL, CAST(COALESCE(os.forwarded_fetch_count,0) - COALESCE(os_prior.forwarded_fetch_count,0) AS NVARCHAR(20)) + '' forwarded fetches on '' + @@ -41529,7 +41529,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, AND ps.counter_name = 'Transactions aborted/sec' AND ps.value_delta > (10 * @Seconds); /* Ignore servers sitting idle */ - /* Query Problems - Suboptimal Plans/Sec High - CheckID 33 */ + /* Query insanity - Suboptimal Plans/Sec High - CheckID 33 */ IF (@Debug = 1) BEGIN RAISERROR('Running CheckID 33',10,1) WITH NOWAIT; @@ -41538,7 +41538,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) SELECT 32 AS CheckID, 100 AS Priority, - 'Query Problems' AS FindingGroup, + 'Query insanity' AS FindingGroup, 'Suboptimal Plans/Sec High' AS Finding, 'https://www.brentozar.com/go/suboptimal/' AS URL, CAST(ps.value_delta AS NVARCHAR(50)) + ' plans reported in the ' + CAST(ps.instance_name AS NVARCHAR(100)) + ' workload group (from Workload GroupStats:Suboptimal plans/sec counter)' + @LineFeed @@ -41681,7 +41681,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, SELECT 47 AS CheckId, 50 AS Priority, - 'Query Problems' AS FindingsGroup, + 'Query insanity' AS FindingsGroup, 'High Percentage Of Runnable Queries' AS Finding, 'https://erikdarlingdata.com/go/RunnableQueue/' AS URL, 'On the ' @@ -41812,7 +41812,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, ) VALUES ( -1 , 1 , - 'No Problems Found' , + 'No insanity Found' , 'From Your Community Volunteers' , 'http://FirstResponderKit.org/' , 'Try running our more in-depth checks with sp_Blitz, or there may not be an unusual SQL Server performance problem. ' From 377a8c614ea0144e94880ebcf5e1fb4c6a565b45 Mon Sep 17 00:00:00 2001 From: Sean Killeen Date: Tue, 16 Jan 2024 12:06:11 -0500 Subject: [PATCH 507/662] Welp, that didn't do the right thing at all. This reverts commit cfbf261e20f777f32e1315039ed0c026d309992f. --- Install-All-Scripts.sql | 112 ++++++++++++------------ Install-Core-Blitz-No-Query-Store.sql | 102 ++++++++++----------- Install-Core-Blitz-With-Query-Store.sql | 112 ++++++++++++------------ 3 files changed, 163 insertions(+), 163 deletions(-) diff --git a/Install-All-Scripts.sql b/Install-All-Scripts.sql index 0cf3afc91..9fa75fdd4 100644 --- a/Install-All-Scripts.sql +++ b/Install-All-Scripts.sql @@ -3585,7 +3585,7 @@ AS SET @CheckUserDatabaseObjects = 0; PRINT 'Databases with compatibility level < 90 found, so setting @CheckUserDatabaseObjects = 0.'; PRINT 'The database-level checks rely on CTEs, which are not supported in SQL 2000 compat level databases.'; - PRINT 'Get with the cool kids and switch to a current compatibility level, Grandpa. To find the insanity, run:'; + PRINT 'Get with the cool kids and switch to a current compatibility level, Grandpa. To find the problems, run:'; PRINT 'SELECT * FROM sys.databases WHERE compatibility_level < 90;'; INSERT INTO #BlitzResults ( CheckID , @@ -4022,7 +4022,7 @@ AS IF @BringThePain = 0 AND 50 <= (SELECT COUNT(*) FROM sys.databases) AND @CheckUserDatabaseObjects = 1 BEGIN SET @CheckUserDatabaseObjects = 0; - PRINT 'Running sp_Blitz @CheckUserDatabaseObjects = 1 on a server with 50+ databases may cause temporary insanity for the server and/or user.'; + PRINT 'Running sp_Blitz @CheckUserDatabaseObjects = 1 on a server with 50+ databases may cause temporary problems for the server and/or user.'; PRINT 'If you''re sure you want to do this, run again with the parameter @BringThePain = 1.'; INSERT INTO #BlitzResults ( CheckID , @@ -4117,9 +4117,9 @@ AS Below, we check master.sys.databases looking for databases that haven't had a backup in the last week. If we find any, we insert them into #BlitzResults, the temp table that - tracks our server's insanity. Note that if the check does - NOT find any insanity, we don't save that. We're only - saving the insanity, not the successful checks. + tracks our server's problems. Note that if the check does + NOT find any problems, we don't save that. We're only + saving the problems, not the successful checks. */ IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 1) WITH NOWAIT; @@ -4209,7 +4209,7 @@ AS And there you have it. The rest of this stored procedure works the same way: it asks: - Should I skip this check? - - If not, do I find insanity? + - If not, do I find problems? - Insert the results into #BlitzResults */ @@ -4757,7 +4757,7 @@ AS 'https://www.brentozar.com/go/owners' AS URL , ( 'Job [' + j.name + '] is owned by [' + SUSER_SNAME(j.owner_sid) - + '] - meaning if their login is disabled or not available due to Active Directory insanity, the job will stop working.' ) AS Details + + '] - 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); @@ -6665,7 +6665,7 @@ AS 'Performance' AS FindingGroup , 'CPU Schedulers Offline' AS Finding , 'https://www.brentozar.com/go/schedulers' AS URL , - 'Some CPU cores are not accessible to SQL Server due to affinity masking or licensing insanity.'; + 'Some CPU cores are not accessible to SQL Server due to affinity masking or licensing problems.'; END; IF NOT EXISTS ( SELECT 1 @@ -6693,7 +6693,7 @@ AS ''Performance'' AS FindingGroup , ''Memory Nodes Offline'' AS Finding , ''https://www.brentozar.com/go/schedulers'' AS URL , - ''Due to affinity masking or licensing insanity, some of the memory may not be available.'' OPTION (RECOMPILE)'; + ''Due to affinity masking or licensing problems, some of the memory may not be available.'' OPTION (RECOMPILE)'; IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; @@ -6780,7 +6780,7 @@ AS 'Performance' AS FindingGroup , 'Poison Wait Detected: ' + wait_type AS Finding , 'https://www.brentozar.com/go/poison/#' + wait_type AS URL , - CONVERT(VARCHAR(10), (SUM([wait_time_ms]) / 1000) / 86400) + ':' + CONVERT(VARCHAR(20), DATEADD(s, (SUM([wait_time_ms]) / 1000), 0), 108) + ' of this wait have been recorded. This wait often indicates killer performance insanity.' + CONVERT(VARCHAR(10), (SUM([wait_time_ms]) / 1000) / 86400) + ':' + CONVERT(VARCHAR(20), DATEADD(s, (SUM([wait_time_ms]) / 1000), 0), 108) + ' of this wait have been recorded. This wait often indicates killer performance problems.' FROM sys.[dm_os_wait_stats] WHERE wait_type IN('IO_QUEUE_LIMIT', 'IO_RETRY', 'LOG_RATE_GOVERNOR', 'POOL_LOG_RATE_GOVERNOR', 'PREEMPTIVE_DEBUG', 'RESMGR_THROTTLED', 'RESOURCE_SEMAPHORE', 'RESOURCE_SEMAPHORE_QUERY_COMPILE','SE_REPL_CATCHUP_THROTTLE','SE_REPL_COMMIT_ACK','SE_REPL_COMMIT_TURN','SE_REPL_ROLLBACK_ACK','SE_REPL_SLOW_SECONDARY_THROTTLE','THREADPOOL') GROUP BY wait_type @@ -6808,7 +6808,7 @@ AS 'Performance' AS FindingGroup , 'Poison Wait Detected: Serializable Locking' AS Finding , 'https://www.brentozar.com/go/serializable' AS URL , - CONVERT(VARCHAR(10), (SUM([wait_time_ms]) / 1000) / 86400) + ':' + CONVERT(VARCHAR(20), DATEADD(s, (SUM([wait_time_ms]) / 1000), 0), 108) + ' of LCK_M_R% waits have been recorded. This wait often indicates killer performance insanity.' + CONVERT(VARCHAR(10), (SUM([wait_time_ms]) / 1000) / 86400) + ':' + CONVERT(VARCHAR(20), DATEADD(s, (SUM([wait_time_ms]) / 1000), 0), 108) + ' of LCK_M_R% waits have been recorded. This wait often indicates killer performance problems.' FROM sys.[dm_os_wait_stats] WHERE wait_type IN ('LCK_M_RS_S', 'LCK_M_RS_U', 'LCK_M_RIn_NL','LCK_M_RIn_S', 'LCK_M_RIn_U','LCK_M_RIn_X', 'LCK_M_RX_S', 'LCK_M_RX_U','LCK_M_RX_X') HAVING SUM([wait_time_ms]) > (SELECT 5000 * datediff(HH,create_date,CURRENT_TIMESTAMP) AS hours_since_startup FROM sys.databases WHERE name='tempdb') @@ -10016,7 +10016,7 @@ IF @ProductVersionMajor >= 10 ''Performance'' AS FindingsGroup, ''Fill Factor Changed'', ''https://www.brentozar.com/go/fillfactor'' AS URL, - ''The ['' + DB_NAME() + ''] database has '' + CAST(SUM(1) AS NVARCHAR(50)) + '' objects with fill factor = '' + CAST(fill_factor AS NVARCHAR(5)) + ''%. This can cause memory and storage performance insanity, but may also prevent page splits.'' + ''The ['' + DB_NAME() + ''] database has '' + CAST(SUM(1) AS NVARCHAR(50)) + '' objects with fill factor = '' + CAST(fill_factor AS NVARCHAR(5)) + ''%. This can cause memory and storage performance problems, but may also prevent page splits.'' FROM [?].sys.indexes WHERE fill_factor <> 0 AND fill_factor < 80 AND is_disabled = 0 AND is_hypothetical = 0 GROUP BY fill_factor OPTION (RECOMPILE);'; @@ -10999,7 +10999,7 @@ IF @ProductVersionMajor >= 10 'Security' AS [FindingsGroup] , 'Endpoints Owned by Users' AS [Finding] , 'https://www.brentozar.com/go/owners' AS [URL] , - ( 'Endpoint ' + ep.[name] + ' is owned by ' + SUSER_NAME(ep.principal_id) + '. If the endpoint owner login is disabled or not available due to Active Directory insanity, the high availability will stop working.' + ( 'Endpoint ' + ep.[name] + ' is owned by ' + SUSER_NAME(ep.principal_id) + '. If the endpoint owner login is disabled or not available due to Active Directory problems, the high availability will stop working.' ) AS [Details] FROM sys.database_mirroring_endpoints ep LEFT OUTER JOIN sys.dm_server_services s ON SUSER_NAME(ep.principal_id) = s.service_account @@ -14687,7 +14687,7 @@ RAISERROR('Rules analysis starting', 0, 1) WITH NOWAIT; INSERT #Warnings (CheckId, Priority, DatabaseName, Finding, Warning ) EXEC sys.sp_executesql @StringToExecute; - /*Looking for compatibility level changing. Only looking for databases that have changed more than twice (It''s possible someone may have changed up, had CE insanity, and then changed back)*/ + /*Looking for compatibility level changing. Only looking for databases that have changed more than twice (It''s possible someone may have changed up, had CE problems, and then changed back)*/ SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; @@ -20235,7 +20235,7 @@ RAISERROR('Checking for plans with no warnings', 0, 1) WITH NOWAIT; UPDATE ##BlitzCacheProcs SET Warnings = 'No warnings detected. ' + CASE @ExpertMode WHEN 0 - THEN ' Try running sp_BlitzCache with @ExpertMode = 1 to find more advanced insanity.' + THEN ' Try running sp_BlitzCache with @ExpertMode = 1 to find more advanced problems.' ELSE '' END WHERE Warnings = '' OR Warnings IS NULL @@ -20943,7 +20943,7 @@ BEGIN 'Performance', 'Function Join', 'https://www.brentozar.com/blitzcache/tvf-join/', - 'Execution plans have been found that join to table valued functions (TVFs). TVFs produce inaccurate estimates of the number of rows returned and can lead to any number of query plan insanity.'); + 'Execution plans have been found that join to table valued functions (TVFs). TVFs produce inaccurate estimates of the number of rows returned and can lead to any number of query plan problems.'); IF EXISTS (SELECT 1/0 FROM ##BlitzCacheProcs @@ -21256,7 +21256,7 @@ BEGIN 'Functions', 'Computed Column UDF', 'https://www.brentozar.com/blitzcache/computed-columns-referencing-functions/', - 'This can cause a whole mess of bad serializartion insanity.') ; + 'This can cause a whole mess of bad serializartion problems.') ; IF EXISTS (SELECT 1/0 FROM ##BlitzCacheProcs p @@ -21510,7 +21510,7 @@ BEGIN 'Functions', 'MSTVFs', 'https://www.brentozar.com/blitzcache/tvf-join/', - 'Execution plans have been found that join to table valued functions (TVFs). TVFs produce inaccurate estimates of the number of rows returned and can lead to any number of query plan insanity.'); + 'Execution plans have been found that join to table valued functions (TVFs). TVFs produce inaccurate estimates of the number of rows returned and can lead to any number of query plan problems.'); IF EXISTS (SELECT 1/0 FROM ##BlitzCacheProcs p @@ -23015,7 +23015,7 @@ Known limitations of this version: filegroup/partition scheme etc.) -- (The compression and filegroup index create syntax is not trivial because it is set at the partition level and is not trivial to code.) - - Does not advise you about data modeling for clustered indexes and primary keys (primarily looks for signs of insanity.) + - Does not advise you about data modeling for clustered indexes and primary keys (primarily looks for signs of problems.) Unknown limitations of this version: - We knew them once, but we forgot. @@ -23857,7 +23857,7 @@ BEGIN TRY VALUES ( 1, 0, N'You''re trying to run sp_BlitzIndex on a server with ' + CAST(@NumDatabases AS NVARCHAR(8)) + N' databases. ', - N'Running sp_BlitzIndex on a server with 50+ databases may cause temporary insanity for the server and/or user.', + N'Running sp_BlitzIndex on a server with 50+ databases may cause temporary problems for the server and/or user.', N'If you''re sure you want to do this, run again with the parameter @BringThePain = 1.', 'http://FirstResponderKit.org', '', @@ -23884,7 +23884,7 @@ BEGIN TRY bir.create_tsql, bir.more_info FROM #BlitzIndexResults AS bir; - RAISERROR('Running sp_BlitzIndex on a server with 50+ databases may cause temporary insanity for the server', 12, 1); + RAISERROR('Running sp_BlitzIndex on a server with 50+ databases may cause temporary problems for the server', 12, 1); END; RETURN; @@ -28007,7 +28007,7 @@ BEGIN INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, URL, details, index_definition, index_usage_summary, index_size_summary ) VALUES ( 1, 0 , - N'No Major insanity Found', + N'No Major Problems Found', N'Nice Work!', N'http://FirstResponderKit.org', N'Consider running with @Mode = 4 in individual databases (not all) for more detailed diagnostics.', @@ -28029,7 +28029,7 @@ BEGIN INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, URL, details, index_definition, index_usage_summary, index_size_summary ) VALUES ( 1, 0 , - N'No insanity Found', + N'No Problems Found', N'Nice job! Or more likely, you have a nearly empty database.', N'http://FirstResponderKit.org', 'Time to go read some blog posts.', @DaysUptimeInsertValue, N'', N'' @@ -38335,7 +38335,7 @@ BEGIN 'Performance', 'Joining to table valued functions', 'https://www.brentozar.com/blitzcache/tvf-join/', - 'Execution plans have been found that join to table valued functions (TVFs). TVFs produce inaccurate estimates of the number of rows returned and can lead to any number of query plan insanity.'); + 'Execution plans have been found that join to table valued functions (TVFs). TVFs produce inaccurate estimates of the number of rows returned and can lead to any number of query plan problems.'); IF EXISTS (SELECT 1/0 FROM #working_warnings @@ -38636,7 +38636,7 @@ BEGIN 'Computed Columns Referencing Scalar UDFs', 'This makes a whole lot of stuff run serially', 'https://www.brentozar.com/blitzcache/computed-columns-referencing-functions/', - 'This can cause a whole mess of bad serializartion insanity.') ; + 'This can cause a whole mess of bad serializartion problems.') ; IF EXISTS (SELECT 1/0 FROM #working_warnings p @@ -38844,7 +38844,7 @@ BEGIN 'High tempdb use', 'This query uses more than half of a data file on average', 'No URL yet', - 'You should take a look at tempdb waits to see if you''re having insanity') ; + 'You should take a look at tempdb waits to see if you''re having problems') ; IF EXISTS (SELECT 1/0 FROM #working_warnings p @@ -38867,9 +38867,9 @@ BEGIN 60, 100, 'MSTVFs', - 'These have many of the same insanity scalar UDFs have', + 'These have many of the same problems scalar UDFs have', 'https://www.brentozar.com/blitzcache/tvf-join/', - 'Execution plans have been found that join to table valued functions (TVFs). TVFs produce inaccurate estimates of the number of rows returned and can lead to any number of query plan insanity.'); + 'Execution plans have been found that join to table valued functions (TVFs). TVFs produce inaccurate estimates of the number of rows returned and can lead to any number of query plan problems.'); IF EXISTS (SELECT 1/0 FROM #working_warnings p @@ -44861,7 +44861,7 @@ BEGIN AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs); END - /* Query insanity - Long-Running Query Blocking Others - CheckID 5 */ + /* Query Problems - Long-Running Query Blocking Others - CheckID 5 */ IF SERVERPROPERTY('EngineEdition') <> 5 /*SERVERPROPERTY('Edition') <> 'SQL Azure'*/ AND @Seconds > 0 AND EXISTS(SELECT * FROM sys.dm_os_waiting_tasks WHERE wait_type LIKE 'LCK%' AND wait_duration_ms > 30000) BEGIN IF (@Debug = 1) @@ -44872,7 +44872,7 @@ BEGIN SET @StringToExecute = N'INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, QueryText, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, QueryHash) SELECT 5 AS CheckID, 1 AS Priority, - ''Query insanity'' AS FindingGroup, + ''Query Problems'' AS FindingGroup, ''Long-Running Query Blocking Others'' AS Finding, ''https://www.brentozar.com/go/blocking'' AS URL, ''Query in '' + COALESCE(DB_NAME(COALESCE((SELECT TOP 1 dbid FROM sys.dm_exec_sql_text(r.sql_handle)), @@ -44904,7 +44904,7 @@ BEGIN EXECUTE sp_executesql @StringToExecute; END; - /* Query insanity - Plan Cache Erased Recently - CheckID 7 */ + /* Query Problems - Plan Cache Erased Recently - CheckID 7 */ IF DATEADD(mi, -15, SYSDATETIME()) < (SELECT TOP 1 creation_time FROM sys.dm_exec_query_stats ORDER BY creation_time) BEGIN IF (@Debug = 1) @@ -44915,7 +44915,7 @@ BEGIN INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) SELECT TOP 1 7 AS CheckID, 50 AS Priority, - 'Query insanity' AS FindingGroup, + 'Query Problems' AS FindingGroup, 'Plan Cache Erased Recently' AS Finding, 'https://www.brentozar.com/askbrent/plan-cache-erased-recently/' AS URL, 'The oldest query in the plan cache was created at ' + CAST(creation_time AS NVARCHAR(50)) + '. ' + @LineFeed + @LineFeed @@ -44929,7 +44929,7 @@ BEGIN END; - /* Query insanity - Sleeping Query with Open Transactions - CheckID 8 */ + /* Query Problems - Sleeping Query with Open Transactions - CheckID 8 */ IF @Seconds > 0 BEGIN IF (@Debug = 1) @@ -44941,7 +44941,7 @@ BEGIN INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, OpenTransactionCount) SELECT 8 AS CheckID, 50 AS Priority, - 'Query insanity' AS FindingGroup, + 'Query Problems' AS FindingGroup, 'Sleeping Query with Open Transactions' AS Finding, 'https://www.brentozar.com/askbrent/sleeping-query-with-open-transactions/' AS URL, 'Database: ' + DB_NAME(db.resource_database_id) + @LineFeed + 'Host: ' + s.hostname + @LineFeed + 'Program: ' + s.[program_name] + @LineFeed + 'Asleep with open transactions and locks since ' + CAST(s.last_batch AS NVARCHAR(100)) + '. ' AS Details, @@ -44971,7 +44971,7 @@ BEGIN AND NOT (resource_type = N'DATABASE' AND request_mode = N'S' AND request_status = N'GRANT' AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE')); END - /*Query insanity - Clients using implicit transactions - CheckID 37 */ + /*Query Problems - Clients using implicit transactions - CheckID 37 */ IF @Seconds > 0 AND ( @@VERSION NOT LIKE 'Microsoft SQL Server 2005%' AND @@VERSION NOT LIKE 'Microsoft SQL Server 2008%' @@ -44985,7 +44985,7 @@ BEGIN SET @StringToExecute = N'INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, OpenTransactionCount) SELECT 37 AS CheckId, 50 AS Priority, - ''Query insanity'' AS FindingsGroup, + ''Query Problems'' AS FindingsGroup, ''Implicit Transactions'', ''https://www.brentozar.com/go/ImplicitTransactions/'' AS URL, ''Database: '' + DB_NAME(s.database_id) + '' '' + CHAR(13) + CHAR(10) + @@ -45016,7 +45016,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, EXECUTE sp_executesql @StringToExecute; END; - /* Query insanity - Query Rolling Back - CheckID 9 */ + /* Query Problems - Query Rolling Back - CheckID 9 */ IF @Seconds > 0 BEGIN IF (@Debug = 1) @@ -45028,7 +45028,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, QueryHash) SELECT 9 AS CheckID, 1 AS Priority, - 'Query insanity' AS FindingGroup, + 'Query Problems' AS FindingGroup, 'Query Rolling Back' AS Finding, 'https://www.brentozar.com/askbrent/rollback/' AS URL, 'Rollback started at ' + CAST(r.start_time AS NVARCHAR(100)) + ', is ' + CAST(r.percent_complete AS NVARCHAR(100)) + '% complete.' AS Details, @@ -45061,7 +45061,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, SELECT 47 AS CheckId, 50 AS Priority, - 'Query insanity' AS FindingsGroup, + 'Query Problems' AS FindingsGroup, 'High Percentage Of Runnable Queries' AS Finding, 'https://erikdarlingdata.com/go/RunnableQueue/' AS URL, 'On the ' @@ -45237,7 +45237,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 'https://www.brentozar.com/askbrent/' AS URL FROM sys.dm_exec_query_memory_grants AS Grants; - /* Query insanity - Queries with high memory grants - CheckID 46 */ + /* Query Problems - Queries with high memory grants - CheckID 46 */ IF (@Debug = 1) BEGIN RAISERROR('Running CheckID 46',10,1) WITH NOWAIT; @@ -45246,7 +45246,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, URL, QueryText, QueryPlan) SELECT 46 AS CheckID, 100 AS Priority, - 'Query insanity' AS FindingGroup, + 'Query Problems' AS FindingGroup, 'Query with a memory grant exceeding ' +CAST(@MemoryGrantThresholdPct AS NVARCHAR(15)) +'%' AS Finding, @@ -45267,7 +45267,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, OUTER APPLY sys.dm_exec_query_plan(Grants.[plan_handle]) AS QueryPlan WHERE Grants.granted_memory_kb > ((@MemoryGrantThresholdPct/100.00)*(@MaxWorkspace*1024)); - /* Query insanity - Memory Leak in USERSTORE_TOKENPERM Cache - CheckID 45 */ + /* Query Problems - Memory Leak in USERSTORE_TOKENPERM Cache - CheckID 45 */ IF EXISTS (SELECT * FROM sys.all_columns WHERE object_id = OBJECT_ID('sys.dm_os_memory_clerks') AND name = 'pages_kb') BEGIN IF (@Debug = 1) @@ -45280,7 +45280,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, URL) SELECT 45 AS CheckID, 50 AS Priority, - ''Query insanity'' AS FindingsGroup, + ''Query Problems'' AS FindingsGroup, ''Memory Leak in USERSTORE_TOKENPERM Cache'' AS Finding, N''UserStore_TokenPerm clerk is using '' + CAST(CAST(SUM(CASE WHEN type = ''USERSTORE_TOKENPERM'' AND name = ''TokenAndPermUserStore'' THEN pages_kb * 1.0 ELSE 0.0 END) / 1024.0 / 1024.0 AS INT) AS NVARCHAR(100)) + N''GB RAM, total buffer pool is '' + CAST(CAST(SUM(pages_kb) / 1024.0 / 1024.0 AS INT) AS NVARCHAR(100)) + N''GB.'' @@ -45640,7 +45640,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, END; /* IF @Seconds < 30 */ - /* Query insanity - Statistics Updated Recently - CheckID 44 */ + /* Query Problems - Statistics Updated Recently - CheckID 44 */ IF (@Debug = 1) BEGIN RAISERROR('Running CheckID 44',10,1) WITH NOWAIT; @@ -45734,7 +45734,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) SELECT 44 AS CheckId, 50 AS Priority, - 'Query insanity' AS FindingGroup, + 'Query Problems' AS FindingGroup, 'Statistics Updated Recently' AS Finding, 'https://www.brentozar.com/go/stats' AS URL, 'In the last 15 minutes, statistics were updated. To see which ones, click the HowToStopIt column.' + @LineFeed + @LineFeed @@ -46256,7 +46256,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, AND counter_name = 'Log Shrinks' AND value_delta > 0; - /* Query insanity - Compilations/Sec High - CheckID 15 */ + /* Query Problems - Compilations/Sec High - CheckID 15 */ IF (@Debug = 1) BEGIN RAISERROR('Running CheckID 15',10,1) WITH NOWAIT; @@ -46265,7 +46265,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) SELECT 15 AS CheckID, 50 AS Priority, - 'Query insanity' AS FindingGroup, + 'Query Problems' AS FindingGroup, 'Compilations/Sec High' AS Finding, 'https://www.brentozar.com/askbrent/compilations/' AS URL, 'Number of batch requests during the sample: ' + CAST(ps.value_delta AS NVARCHAR(20)) + @LineFeed @@ -46283,7 +46283,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, AND (psComp.value_delta > (10 * @Seconds) OR psComp.value_delta > ps.value_delta) /* Either doing 10 compilations per second, or more compilations than queries */ AND (psComp.value_delta * 10) > ps.value_delta; /* Compilations are more than 10% of batch requests per second */ - /* Query insanity - Re-Compilations/Sec High - CheckID 16 */ + /* Query Problems - Re-Compilations/Sec High - CheckID 16 */ IF (@Debug = 1) BEGIN RAISERROR('Running CheckID 16',10,1) WITH NOWAIT; @@ -46292,7 +46292,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) SELECT 16 AS CheckID, 50 AS Priority, - 'Query insanity' AS FindingGroup, + 'Query Problems' AS FindingGroup, 'Re-Compilations/Sec High' AS Finding, 'https://www.brentozar.com/askbrent/recompilations/' AS URL, 'Number of batch requests during the sample: ' + CAST(ps.value_delta AS NVARCHAR(20)) + @LineFeed @@ -46310,7 +46310,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, AND (psComp.value_delta > (10 * @Seconds) OR psComp.value_delta > ps.value_delta) /* Either doing 10 recompilations per second, or more recompilations than queries */ AND (psComp.value_delta * 10) > ps.value_delta; /* Recompilations are more than 10% of batch requests per second */ - /* Table insanity - Forwarded Fetches/Sec High - CheckID 29 */ + /* Table Problems - Forwarded Fetches/Sec High - CheckID 29 */ IF (@Debug = 1) BEGIN RAISERROR('Running CheckID 29',10,1) WITH NOWAIT; @@ -46319,7 +46319,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) SELECT 29 AS CheckID, 40 AS Priority, - 'Table insanity' AS FindingGroup, + 'Table Problems' AS FindingGroup, 'Forwarded Fetches/Sec High' AS Finding, 'https://www.brentozar.com/go/fetch/' AS URL, CAST(ps.value_delta AS NVARCHAR(20)) + ' forwarded fetches (from SQLServer:Access Methods counter)' + @LineFeed @@ -46340,7 +46340,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) SELECT TOP 10 29 AS CheckID, 40 AS Priority, - ''Table insanity'' AS FindingGroup, + ''Table Problems'' AS FindingGroup, ''Forwarded Fetches/Sec High: TempDB Object'' AS Finding, ''https://www.brentozar.com/go/fetch/'' AS URL, CAST(COALESCE(os.forwarded_fetch_count,0) - COALESCE(os_prior.forwarded_fetch_count,0) AS NVARCHAR(20)) + '' forwarded fetches on '' + @@ -46404,7 +46404,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, AND ps.counter_name = 'Transactions aborted/sec' AND ps.value_delta > (10 * @Seconds); /* Ignore servers sitting idle */ - /* Query insanity - Suboptimal Plans/Sec High - CheckID 33 */ + /* Query Problems - Suboptimal Plans/Sec High - CheckID 33 */ IF (@Debug = 1) BEGIN RAISERROR('Running CheckID 33',10,1) WITH NOWAIT; @@ -46413,7 +46413,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) SELECT 32 AS CheckID, 100 AS Priority, - 'Query insanity' AS FindingGroup, + 'Query Problems' AS FindingGroup, 'Suboptimal Plans/Sec High' AS Finding, 'https://www.brentozar.com/go/suboptimal/' AS URL, CAST(ps.value_delta AS NVARCHAR(50)) + ' plans reported in the ' + CAST(ps.instance_name AS NVARCHAR(100)) + ' workload group (from Workload GroupStats:Suboptimal plans/sec counter)' + @LineFeed @@ -46556,7 +46556,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, SELECT 47 AS CheckId, 50 AS Priority, - 'Query insanity' AS FindingsGroup, + 'Query Problems' AS FindingsGroup, 'High Percentage Of Runnable Queries' AS Finding, 'https://erikdarlingdata.com/go/RunnableQueue/' AS URL, 'On the ' @@ -46687,7 +46687,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, ) VALUES ( -1 , 1 , - 'No insanity Found' , + 'No Problems Found' , 'From Your Community Volunteers' , 'http://FirstResponderKit.org/' , 'Try running our more in-depth checks with sp_Blitz, or there may not be an unusual SQL Server performance problem. ' diff --git a/Install-Core-Blitz-No-Query-Store.sql b/Install-Core-Blitz-No-Query-Store.sql index 40d2a22bf..2b2dcc93d 100644 --- a/Install-Core-Blitz-No-Query-Store.sql +++ b/Install-Core-Blitz-No-Query-Store.sql @@ -723,7 +723,7 @@ AS SET @CheckUserDatabaseObjects = 0; PRINT 'Databases with compatibility level < 90 found, so setting @CheckUserDatabaseObjects = 0.'; PRINT 'The database-level checks rely on CTEs, which are not supported in SQL 2000 compat level databases.'; - PRINT 'Get with the cool kids and switch to a current compatibility level, Grandpa. To find the insanity, run:'; + PRINT 'Get with the cool kids and switch to a current compatibility level, Grandpa. To find the problems, run:'; PRINT 'SELECT * FROM sys.databases WHERE compatibility_level < 90;'; INSERT INTO #BlitzResults ( CheckID , @@ -1160,7 +1160,7 @@ AS IF @BringThePain = 0 AND 50 <= (SELECT COUNT(*) FROM sys.databases) AND @CheckUserDatabaseObjects = 1 BEGIN SET @CheckUserDatabaseObjects = 0; - PRINT 'Running sp_Blitz @CheckUserDatabaseObjects = 1 on a server with 50+ databases may cause temporary insanity for the server and/or user.'; + PRINT 'Running sp_Blitz @CheckUserDatabaseObjects = 1 on a server with 50+ databases may cause temporary problems for the server and/or user.'; PRINT 'If you''re sure you want to do this, run again with the parameter @BringThePain = 1.'; INSERT INTO #BlitzResults ( CheckID , @@ -1255,9 +1255,9 @@ AS Below, we check master.sys.databases looking for databases that haven't had a backup in the last week. If we find any, we insert them into #BlitzResults, the temp table that - tracks our server's insanity. Note that if the check does - NOT find any insanity, we don't save that. We're only - saving the insanity, not the successful checks. + tracks our server's problems. Note that if the check does + NOT find any problems, we don't save that. We're only + saving the problems, not the successful checks. */ IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 1) WITH NOWAIT; @@ -1347,7 +1347,7 @@ AS And there you have it. The rest of this stored procedure works the same way: it asks: - Should I skip this check? - - If not, do I find insanity? + - If not, do I find problems? - Insert the results into #BlitzResults */ @@ -1895,7 +1895,7 @@ AS 'https://www.brentozar.com/go/owners' AS URL , ( 'Job [' + j.name + '] is owned by [' + SUSER_SNAME(j.owner_sid) - + '] - meaning if their login is disabled or not available due to Active Directory insanity, the job will stop working.' ) AS Details + + '] - 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); @@ -3803,7 +3803,7 @@ AS 'Performance' AS FindingGroup , 'CPU Schedulers Offline' AS Finding , 'https://www.brentozar.com/go/schedulers' AS URL , - 'Some CPU cores are not accessible to SQL Server due to affinity masking or licensing insanity.'; + 'Some CPU cores are not accessible to SQL Server due to affinity masking or licensing problems.'; END; IF NOT EXISTS ( SELECT 1 @@ -3831,7 +3831,7 @@ AS ''Performance'' AS FindingGroup , ''Memory Nodes Offline'' AS Finding , ''https://www.brentozar.com/go/schedulers'' AS URL , - ''Due to affinity masking or licensing insanity, some of the memory may not be available.'' OPTION (RECOMPILE)'; + ''Due to affinity masking or licensing problems, some of the memory may not be available.'' OPTION (RECOMPILE)'; IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; @@ -3918,7 +3918,7 @@ AS 'Performance' AS FindingGroup , 'Poison Wait Detected: ' + wait_type AS Finding , 'https://www.brentozar.com/go/poison/#' + wait_type AS URL , - CONVERT(VARCHAR(10), (SUM([wait_time_ms]) / 1000) / 86400) + ':' + CONVERT(VARCHAR(20), DATEADD(s, (SUM([wait_time_ms]) / 1000), 0), 108) + ' of this wait have been recorded. This wait often indicates killer performance insanity.' + CONVERT(VARCHAR(10), (SUM([wait_time_ms]) / 1000) / 86400) + ':' + CONVERT(VARCHAR(20), DATEADD(s, (SUM([wait_time_ms]) / 1000), 0), 108) + ' of this wait have been recorded. This wait often indicates killer performance problems.' FROM sys.[dm_os_wait_stats] WHERE wait_type IN('IO_QUEUE_LIMIT', 'IO_RETRY', 'LOG_RATE_GOVERNOR', 'POOL_LOG_RATE_GOVERNOR', 'PREEMPTIVE_DEBUG', 'RESMGR_THROTTLED', 'RESOURCE_SEMAPHORE', 'RESOURCE_SEMAPHORE_QUERY_COMPILE','SE_REPL_CATCHUP_THROTTLE','SE_REPL_COMMIT_ACK','SE_REPL_COMMIT_TURN','SE_REPL_ROLLBACK_ACK','SE_REPL_SLOW_SECONDARY_THROTTLE','THREADPOOL') GROUP BY wait_type @@ -3946,7 +3946,7 @@ AS 'Performance' AS FindingGroup , 'Poison Wait Detected: Serializable Locking' AS Finding , 'https://www.brentozar.com/go/serializable' AS URL , - CONVERT(VARCHAR(10), (SUM([wait_time_ms]) / 1000) / 86400) + ':' + CONVERT(VARCHAR(20), DATEADD(s, (SUM([wait_time_ms]) / 1000), 0), 108) + ' of LCK_M_R% waits have been recorded. This wait often indicates killer performance insanity.' + CONVERT(VARCHAR(10), (SUM([wait_time_ms]) / 1000) / 86400) + ':' + CONVERT(VARCHAR(20), DATEADD(s, (SUM([wait_time_ms]) / 1000), 0), 108) + ' of LCK_M_R% waits have been recorded. This wait often indicates killer performance problems.' FROM sys.[dm_os_wait_stats] WHERE wait_type IN ('LCK_M_RS_S', 'LCK_M_RS_U', 'LCK_M_RIn_NL','LCK_M_RIn_S', 'LCK_M_RIn_U','LCK_M_RIn_X', 'LCK_M_RX_S', 'LCK_M_RX_U','LCK_M_RX_X') HAVING SUM([wait_time_ms]) > (SELECT 5000 * datediff(HH,create_date,CURRENT_TIMESTAMP) AS hours_since_startup FROM sys.databases WHERE name='tempdb') @@ -7154,7 +7154,7 @@ IF @ProductVersionMajor >= 10 ''Performance'' AS FindingsGroup, ''Fill Factor Changed'', ''https://www.brentozar.com/go/fillfactor'' AS URL, - ''The ['' + DB_NAME() + ''] database has '' + CAST(SUM(1) AS NVARCHAR(50)) + '' objects with fill factor = '' + CAST(fill_factor AS NVARCHAR(5)) + ''%. This can cause memory and storage performance insanity, but may also prevent page splits.'' + ''The ['' + DB_NAME() + ''] database has '' + CAST(SUM(1) AS NVARCHAR(50)) + '' objects with fill factor = '' + CAST(fill_factor AS NVARCHAR(5)) + ''%. This can cause memory and storage performance problems, but may also prevent page splits.'' FROM [?].sys.indexes WHERE fill_factor <> 0 AND fill_factor < 80 AND is_disabled = 0 AND is_hypothetical = 0 GROUP BY fill_factor OPTION (RECOMPILE);'; @@ -8137,7 +8137,7 @@ IF @ProductVersionMajor >= 10 'Security' AS [FindingsGroup] , 'Endpoints Owned by Users' AS [Finding] , 'https://www.brentozar.com/go/owners' AS [URL] , - ( 'Endpoint ' + ep.[name] + ' is owned by ' + SUSER_NAME(ep.principal_id) + '. If the endpoint owner login is disabled or not available due to Active Directory insanity, the high availability will stop working.' + ( 'Endpoint ' + ep.[name] + ' is owned by ' + SUSER_NAME(ep.principal_id) + '. If the endpoint owner login is disabled or not available due to Active Directory problems, the high availability will stop working.' ) AS [Details] FROM sys.database_mirroring_endpoints ep LEFT OUTER JOIN sys.dm_server_services s ON SUSER_NAME(ep.principal_id) = s.service_account @@ -11825,7 +11825,7 @@ RAISERROR('Rules analysis starting', 0, 1) WITH NOWAIT; INSERT #Warnings (CheckId, Priority, DatabaseName, Finding, Warning ) EXEC sys.sp_executesql @StringToExecute; - /*Looking for compatibility level changing. Only looking for databases that have changed more than twice (It''s possible someone may have changed up, had CE insanity, and then changed back)*/ + /*Looking for compatibility level changing. Only looking for databases that have changed more than twice (It''s possible someone may have changed up, had CE problems, and then changed back)*/ SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; @@ -17373,7 +17373,7 @@ RAISERROR('Checking for plans with no warnings', 0, 1) WITH NOWAIT; UPDATE ##BlitzCacheProcs SET Warnings = 'No warnings detected. ' + CASE @ExpertMode WHEN 0 - THEN ' Try running sp_BlitzCache with @ExpertMode = 1 to find more advanced insanity.' + THEN ' Try running sp_BlitzCache with @ExpertMode = 1 to find more advanced problems.' ELSE '' END WHERE Warnings = '' OR Warnings IS NULL @@ -18081,7 +18081,7 @@ BEGIN 'Performance', 'Function Join', 'https://www.brentozar.com/blitzcache/tvf-join/', - 'Execution plans have been found that join to table valued functions (TVFs). TVFs produce inaccurate estimates of the number of rows returned and can lead to any number of query plan insanity.'); + 'Execution plans have been found that join to table valued functions (TVFs). TVFs produce inaccurate estimates of the number of rows returned and can lead to any number of query plan problems.'); IF EXISTS (SELECT 1/0 FROM ##BlitzCacheProcs @@ -18394,7 +18394,7 @@ BEGIN 'Functions', 'Computed Column UDF', 'https://www.brentozar.com/blitzcache/computed-columns-referencing-functions/', - 'This can cause a whole mess of bad serializartion insanity.') ; + 'This can cause a whole mess of bad serializartion problems.') ; IF EXISTS (SELECT 1/0 FROM ##BlitzCacheProcs p @@ -18648,7 +18648,7 @@ BEGIN 'Functions', 'MSTVFs', 'https://www.brentozar.com/blitzcache/tvf-join/', - 'Execution plans have been found that join to table valued functions (TVFs). TVFs produce inaccurate estimates of the number of rows returned and can lead to any number of query plan insanity.'); + 'Execution plans have been found that join to table valued functions (TVFs). TVFs produce inaccurate estimates of the number of rows returned and can lead to any number of query plan problems.'); IF EXISTS (SELECT 1/0 FROM ##BlitzCacheProcs p @@ -20153,7 +20153,7 @@ Known limitations of this version: filegroup/partition scheme etc.) -- (The compression and filegroup index create syntax is not trivial because it is set at the partition level and is not trivial to code.) - - Does not advise you about data modeling for clustered indexes and primary keys (primarily looks for signs of insanity.) + - Does not advise you about data modeling for clustered indexes and primary keys (primarily looks for signs of problems.) Unknown limitations of this version: - We knew them once, but we forgot. @@ -20995,7 +20995,7 @@ BEGIN TRY VALUES ( 1, 0, N'You''re trying to run sp_BlitzIndex on a server with ' + CAST(@NumDatabases AS NVARCHAR(8)) + N' databases. ', - N'Running sp_BlitzIndex on a server with 50+ databases may cause temporary insanity for the server and/or user.', + N'Running sp_BlitzIndex on a server with 50+ databases may cause temporary problems for the server and/or user.', N'If you''re sure you want to do this, run again with the parameter @BringThePain = 1.', 'http://FirstResponderKit.org', '', @@ -21022,7 +21022,7 @@ BEGIN TRY bir.create_tsql, bir.more_info FROM #BlitzIndexResults AS bir; - RAISERROR('Running sp_BlitzIndex on a server with 50+ databases may cause temporary insanity for the server', 12, 1); + RAISERROR('Running sp_BlitzIndex on a server with 50+ databases may cause temporary problems for the server', 12, 1); END; RETURN; @@ -25145,7 +25145,7 @@ BEGIN INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, URL, details, index_definition, index_usage_summary, index_size_summary ) VALUES ( 1, 0 , - N'No Major insanity Found', + N'No Major Problems Found', N'Nice Work!', N'http://FirstResponderKit.org', N'Consider running with @Mode = 4 in individual databases (not all) for more detailed diagnostics.', @@ -25167,7 +25167,7 @@ BEGIN INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, URL, details, index_definition, index_usage_summary, index_size_summary ) VALUES ( 1, 0 , - N'No insanity Found', + N'No Problems Found', N'Nice job! Or more likely, you have a nearly empty database.', N'http://FirstResponderKit.org', 'Time to go read some blog posts.', @DaysUptimeInsertValue, N'', N'' @@ -33913,7 +33913,7 @@ BEGIN AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs); END - /* Query insanity - Long-Running Query Blocking Others - CheckID 5 */ + /* Query Problems - Long-Running Query Blocking Others - CheckID 5 */ IF SERVERPROPERTY('EngineEdition') <> 5 /*SERVERPROPERTY('Edition') <> 'SQL Azure'*/ AND @Seconds > 0 AND EXISTS(SELECT * FROM sys.dm_os_waiting_tasks WHERE wait_type LIKE 'LCK%' AND wait_duration_ms > 30000) BEGIN IF (@Debug = 1) @@ -33924,7 +33924,7 @@ BEGIN SET @StringToExecute = N'INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, QueryText, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, QueryHash) SELECT 5 AS CheckID, 1 AS Priority, - ''Query insanity'' AS FindingGroup, + ''Query Problems'' AS FindingGroup, ''Long-Running Query Blocking Others'' AS Finding, ''https://www.brentozar.com/go/blocking'' AS URL, ''Query in '' + COALESCE(DB_NAME(COALESCE((SELECT TOP 1 dbid FROM sys.dm_exec_sql_text(r.sql_handle)), @@ -33956,7 +33956,7 @@ BEGIN EXECUTE sp_executesql @StringToExecute; END; - /* Query insanity - Plan Cache Erased Recently - CheckID 7 */ + /* Query Problems - Plan Cache Erased Recently - CheckID 7 */ IF DATEADD(mi, -15, SYSDATETIME()) < (SELECT TOP 1 creation_time FROM sys.dm_exec_query_stats ORDER BY creation_time) BEGIN IF (@Debug = 1) @@ -33967,7 +33967,7 @@ BEGIN INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) SELECT TOP 1 7 AS CheckID, 50 AS Priority, - 'Query insanity' AS FindingGroup, + 'Query Problems' AS FindingGroup, 'Plan Cache Erased Recently' AS Finding, 'https://www.brentozar.com/askbrent/plan-cache-erased-recently/' AS URL, 'The oldest query in the plan cache was created at ' + CAST(creation_time AS NVARCHAR(50)) + '. ' + @LineFeed + @LineFeed @@ -33981,7 +33981,7 @@ BEGIN END; - /* Query insanity - Sleeping Query with Open Transactions - CheckID 8 */ + /* Query Problems - Sleeping Query with Open Transactions - CheckID 8 */ IF @Seconds > 0 BEGIN IF (@Debug = 1) @@ -33993,7 +33993,7 @@ BEGIN INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, OpenTransactionCount) SELECT 8 AS CheckID, 50 AS Priority, - 'Query insanity' AS FindingGroup, + 'Query Problems' AS FindingGroup, 'Sleeping Query with Open Transactions' AS Finding, 'https://www.brentozar.com/askbrent/sleeping-query-with-open-transactions/' AS URL, 'Database: ' + DB_NAME(db.resource_database_id) + @LineFeed + 'Host: ' + s.hostname + @LineFeed + 'Program: ' + s.[program_name] + @LineFeed + 'Asleep with open transactions and locks since ' + CAST(s.last_batch AS NVARCHAR(100)) + '. ' AS Details, @@ -34023,7 +34023,7 @@ BEGIN AND NOT (resource_type = N'DATABASE' AND request_mode = N'S' AND request_status = N'GRANT' AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE')); END - /*Query insanity - Clients using implicit transactions - CheckID 37 */ + /*Query Problems - Clients using implicit transactions - CheckID 37 */ IF @Seconds > 0 AND ( @@VERSION NOT LIKE 'Microsoft SQL Server 2005%' AND @@VERSION NOT LIKE 'Microsoft SQL Server 2008%' @@ -34037,7 +34037,7 @@ BEGIN SET @StringToExecute = N'INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, OpenTransactionCount) SELECT 37 AS CheckId, 50 AS Priority, - ''Query insanity'' AS FindingsGroup, + ''Query Problems'' AS FindingsGroup, ''Implicit Transactions'', ''https://www.brentozar.com/go/ImplicitTransactions/'' AS URL, ''Database: '' + DB_NAME(s.database_id) + '' '' + CHAR(13) + CHAR(10) + @@ -34068,7 +34068,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, EXECUTE sp_executesql @StringToExecute; END; - /* Query insanity - Query Rolling Back - CheckID 9 */ + /* Query Problems - Query Rolling Back - CheckID 9 */ IF @Seconds > 0 BEGIN IF (@Debug = 1) @@ -34080,7 +34080,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, QueryHash) SELECT 9 AS CheckID, 1 AS Priority, - 'Query insanity' AS FindingGroup, + 'Query Problems' AS FindingGroup, 'Query Rolling Back' AS Finding, 'https://www.brentozar.com/askbrent/rollback/' AS URL, 'Rollback started at ' + CAST(r.start_time AS NVARCHAR(100)) + ', is ' + CAST(r.percent_complete AS NVARCHAR(100)) + '% complete.' AS Details, @@ -34113,7 +34113,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, SELECT 47 AS CheckId, 50 AS Priority, - 'Query insanity' AS FindingsGroup, + 'Query Problems' AS FindingsGroup, 'High Percentage Of Runnable Queries' AS Finding, 'https://erikdarlingdata.com/go/RunnableQueue/' AS URL, 'On the ' @@ -34289,7 +34289,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 'https://www.brentozar.com/askbrent/' AS URL FROM sys.dm_exec_query_memory_grants AS Grants; - /* Query insanity - Queries with high memory grants - CheckID 46 */ + /* Query Problems - Queries with high memory grants - CheckID 46 */ IF (@Debug = 1) BEGIN RAISERROR('Running CheckID 46',10,1) WITH NOWAIT; @@ -34298,7 +34298,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, URL, QueryText, QueryPlan) SELECT 46 AS CheckID, 100 AS Priority, - 'Query insanity' AS FindingGroup, + 'Query Problems' AS FindingGroup, 'Query with a memory grant exceeding ' +CAST(@MemoryGrantThresholdPct AS NVARCHAR(15)) +'%' AS Finding, @@ -34319,7 +34319,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, OUTER APPLY sys.dm_exec_query_plan(Grants.[plan_handle]) AS QueryPlan WHERE Grants.granted_memory_kb > ((@MemoryGrantThresholdPct/100.00)*(@MaxWorkspace*1024)); - /* Query insanity - Memory Leak in USERSTORE_TOKENPERM Cache - CheckID 45 */ + /* Query Problems - Memory Leak in USERSTORE_TOKENPERM Cache - CheckID 45 */ IF EXISTS (SELECT * FROM sys.all_columns WHERE object_id = OBJECT_ID('sys.dm_os_memory_clerks') AND name = 'pages_kb') BEGIN IF (@Debug = 1) @@ -34332,7 +34332,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, URL) SELECT 45 AS CheckID, 50 AS Priority, - ''Query insanity'' AS FindingsGroup, + ''Query Problems'' AS FindingsGroup, ''Memory Leak in USERSTORE_TOKENPERM Cache'' AS Finding, N''UserStore_TokenPerm clerk is using '' + CAST(CAST(SUM(CASE WHEN type = ''USERSTORE_TOKENPERM'' AND name = ''TokenAndPermUserStore'' THEN pages_kb * 1.0 ELSE 0.0 END) / 1024.0 / 1024.0 AS INT) AS NVARCHAR(100)) + N''GB RAM, total buffer pool is '' + CAST(CAST(SUM(pages_kb) / 1024.0 / 1024.0 AS INT) AS NVARCHAR(100)) + N''GB.'' @@ -34692,7 +34692,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, END; /* IF @Seconds < 30 */ - /* Query insanity - Statistics Updated Recently - CheckID 44 */ + /* Query Problems - Statistics Updated Recently - CheckID 44 */ IF (@Debug = 1) BEGIN RAISERROR('Running CheckID 44',10,1) WITH NOWAIT; @@ -34786,7 +34786,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) SELECT 44 AS CheckId, 50 AS Priority, - 'Query insanity' AS FindingGroup, + 'Query Problems' AS FindingGroup, 'Statistics Updated Recently' AS Finding, 'https://www.brentozar.com/go/stats' AS URL, 'In the last 15 minutes, statistics were updated. To see which ones, click the HowToStopIt column.' + @LineFeed + @LineFeed @@ -35308,7 +35308,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, AND counter_name = 'Log Shrinks' AND value_delta > 0; - /* Query insanity - Compilations/Sec High - CheckID 15 */ + /* Query Problems - Compilations/Sec High - CheckID 15 */ IF (@Debug = 1) BEGIN RAISERROR('Running CheckID 15',10,1) WITH NOWAIT; @@ -35317,7 +35317,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) SELECT 15 AS CheckID, 50 AS Priority, - 'Query insanity' AS FindingGroup, + 'Query Problems' AS FindingGroup, 'Compilations/Sec High' AS Finding, 'https://www.brentozar.com/askbrent/compilations/' AS URL, 'Number of batch requests during the sample: ' + CAST(ps.value_delta AS NVARCHAR(20)) + @LineFeed @@ -35335,7 +35335,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, AND (psComp.value_delta > (10 * @Seconds) OR psComp.value_delta > ps.value_delta) /* Either doing 10 compilations per second, or more compilations than queries */ AND (psComp.value_delta * 10) > ps.value_delta; /* Compilations are more than 10% of batch requests per second */ - /* Query insanity - Re-Compilations/Sec High - CheckID 16 */ + /* Query Problems - Re-Compilations/Sec High - CheckID 16 */ IF (@Debug = 1) BEGIN RAISERROR('Running CheckID 16',10,1) WITH NOWAIT; @@ -35344,7 +35344,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) SELECT 16 AS CheckID, 50 AS Priority, - 'Query insanity' AS FindingGroup, + 'Query Problems' AS FindingGroup, 'Re-Compilations/Sec High' AS Finding, 'https://www.brentozar.com/askbrent/recompilations/' AS URL, 'Number of batch requests during the sample: ' + CAST(ps.value_delta AS NVARCHAR(20)) + @LineFeed @@ -35362,7 +35362,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, AND (psComp.value_delta > (10 * @Seconds) OR psComp.value_delta > ps.value_delta) /* Either doing 10 recompilations per second, or more recompilations than queries */ AND (psComp.value_delta * 10) > ps.value_delta; /* Recompilations are more than 10% of batch requests per second */ - /* Table insanity - Forwarded Fetches/Sec High - CheckID 29 */ + /* Table Problems - Forwarded Fetches/Sec High - CheckID 29 */ IF (@Debug = 1) BEGIN RAISERROR('Running CheckID 29',10,1) WITH NOWAIT; @@ -35371,7 +35371,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) SELECT 29 AS CheckID, 40 AS Priority, - 'Table insanity' AS FindingGroup, + 'Table Problems' AS FindingGroup, 'Forwarded Fetches/Sec High' AS Finding, 'https://www.brentozar.com/go/fetch/' AS URL, CAST(ps.value_delta AS NVARCHAR(20)) + ' forwarded fetches (from SQLServer:Access Methods counter)' + @LineFeed @@ -35392,7 +35392,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) SELECT TOP 10 29 AS CheckID, 40 AS Priority, - ''Table insanity'' AS FindingGroup, + ''Table Problems'' AS FindingGroup, ''Forwarded Fetches/Sec High: TempDB Object'' AS Finding, ''https://www.brentozar.com/go/fetch/'' AS URL, CAST(COALESCE(os.forwarded_fetch_count,0) - COALESCE(os_prior.forwarded_fetch_count,0) AS NVARCHAR(20)) + '' forwarded fetches on '' + @@ -35456,7 +35456,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, AND ps.counter_name = 'Transactions aborted/sec' AND ps.value_delta > (10 * @Seconds); /* Ignore servers sitting idle */ - /* Query insanity - Suboptimal Plans/Sec High - CheckID 33 */ + /* Query Problems - Suboptimal Plans/Sec High - CheckID 33 */ IF (@Debug = 1) BEGIN RAISERROR('Running CheckID 33',10,1) WITH NOWAIT; @@ -35465,7 +35465,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) SELECT 32 AS CheckID, 100 AS Priority, - 'Query insanity' AS FindingGroup, + 'Query Problems' AS FindingGroup, 'Suboptimal Plans/Sec High' AS Finding, 'https://www.brentozar.com/go/suboptimal/' AS URL, CAST(ps.value_delta AS NVARCHAR(50)) + ' plans reported in the ' + CAST(ps.instance_name AS NVARCHAR(100)) + ' workload group (from Workload GroupStats:Suboptimal plans/sec counter)' + @LineFeed @@ -35608,7 +35608,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, SELECT 47 AS CheckId, 50 AS Priority, - 'Query insanity' AS FindingsGroup, + 'Query Problems' AS FindingsGroup, 'High Percentage Of Runnable Queries' AS Finding, 'https://erikdarlingdata.com/go/RunnableQueue/' AS URL, 'On the ' @@ -35739,7 +35739,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, ) VALUES ( -1 , 1 , - 'No insanity Found' , + 'No Problems Found' , 'From Your Community Volunteers' , 'http://FirstResponderKit.org/' , 'Try running our more in-depth checks with sp_Blitz, or there may not be an unusual SQL Server performance problem. ' diff --git a/Install-Core-Blitz-With-Query-Store.sql b/Install-Core-Blitz-With-Query-Store.sql index fa8110fcf..f62410924 100644 --- a/Install-Core-Blitz-With-Query-Store.sql +++ b/Install-Core-Blitz-With-Query-Store.sql @@ -723,7 +723,7 @@ AS SET @CheckUserDatabaseObjects = 0; PRINT 'Databases with compatibility level < 90 found, so setting @CheckUserDatabaseObjects = 0.'; PRINT 'The database-level checks rely on CTEs, which are not supported in SQL 2000 compat level databases.'; - PRINT 'Get with the cool kids and switch to a current compatibility level, Grandpa. To find the insanity, run:'; + PRINT 'Get with the cool kids and switch to a current compatibility level, Grandpa. To find the problems, run:'; PRINT 'SELECT * FROM sys.databases WHERE compatibility_level < 90;'; INSERT INTO #BlitzResults ( CheckID , @@ -1160,7 +1160,7 @@ AS IF @BringThePain = 0 AND 50 <= (SELECT COUNT(*) FROM sys.databases) AND @CheckUserDatabaseObjects = 1 BEGIN SET @CheckUserDatabaseObjects = 0; - PRINT 'Running sp_Blitz @CheckUserDatabaseObjects = 1 on a server with 50+ databases may cause temporary insanity for the server and/or user.'; + PRINT 'Running sp_Blitz @CheckUserDatabaseObjects = 1 on a server with 50+ databases may cause temporary problems for the server and/or user.'; PRINT 'If you''re sure you want to do this, run again with the parameter @BringThePain = 1.'; INSERT INTO #BlitzResults ( CheckID , @@ -1255,9 +1255,9 @@ AS Below, we check master.sys.databases looking for databases that haven't had a backup in the last week. If we find any, we insert them into #BlitzResults, the temp table that - tracks our server's insanity. Note that if the check does - NOT find any insanity, we don't save that. We're only - saving the insanity, not the successful checks. + tracks our server's problems. Note that if the check does + NOT find any problems, we don't save that. We're only + saving the problems, not the successful checks. */ IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 1) WITH NOWAIT; @@ -1347,7 +1347,7 @@ AS And there you have it. The rest of this stored procedure works the same way: it asks: - Should I skip this check? - - If not, do I find insanity? + - If not, do I find problems? - Insert the results into #BlitzResults */ @@ -1895,7 +1895,7 @@ AS 'https://www.brentozar.com/go/owners' AS URL , ( 'Job [' + j.name + '] is owned by [' + SUSER_SNAME(j.owner_sid) - + '] - meaning if their login is disabled or not available due to Active Directory insanity, the job will stop working.' ) AS Details + + '] - 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); @@ -3803,7 +3803,7 @@ AS 'Performance' AS FindingGroup , 'CPU Schedulers Offline' AS Finding , 'https://www.brentozar.com/go/schedulers' AS URL , - 'Some CPU cores are not accessible to SQL Server due to affinity masking or licensing insanity.'; + 'Some CPU cores are not accessible to SQL Server due to affinity masking or licensing problems.'; END; IF NOT EXISTS ( SELECT 1 @@ -3831,7 +3831,7 @@ AS ''Performance'' AS FindingGroup , ''Memory Nodes Offline'' AS Finding , ''https://www.brentozar.com/go/schedulers'' AS URL , - ''Due to affinity masking or licensing insanity, some of the memory may not be available.'' OPTION (RECOMPILE)'; + ''Due to affinity masking or licensing problems, some of the memory may not be available.'' OPTION (RECOMPILE)'; IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; @@ -3918,7 +3918,7 @@ AS 'Performance' AS FindingGroup , 'Poison Wait Detected: ' + wait_type AS Finding , 'https://www.brentozar.com/go/poison/#' + wait_type AS URL , - CONVERT(VARCHAR(10), (SUM([wait_time_ms]) / 1000) / 86400) + ':' + CONVERT(VARCHAR(20), DATEADD(s, (SUM([wait_time_ms]) / 1000), 0), 108) + ' of this wait have been recorded. This wait often indicates killer performance insanity.' + CONVERT(VARCHAR(10), (SUM([wait_time_ms]) / 1000) / 86400) + ':' + CONVERT(VARCHAR(20), DATEADD(s, (SUM([wait_time_ms]) / 1000), 0), 108) + ' of this wait have been recorded. This wait often indicates killer performance problems.' FROM sys.[dm_os_wait_stats] WHERE wait_type IN('IO_QUEUE_LIMIT', 'IO_RETRY', 'LOG_RATE_GOVERNOR', 'POOL_LOG_RATE_GOVERNOR', 'PREEMPTIVE_DEBUG', 'RESMGR_THROTTLED', 'RESOURCE_SEMAPHORE', 'RESOURCE_SEMAPHORE_QUERY_COMPILE','SE_REPL_CATCHUP_THROTTLE','SE_REPL_COMMIT_ACK','SE_REPL_COMMIT_TURN','SE_REPL_ROLLBACK_ACK','SE_REPL_SLOW_SECONDARY_THROTTLE','THREADPOOL') GROUP BY wait_type @@ -3946,7 +3946,7 @@ AS 'Performance' AS FindingGroup , 'Poison Wait Detected: Serializable Locking' AS Finding , 'https://www.brentozar.com/go/serializable' AS URL , - CONVERT(VARCHAR(10), (SUM([wait_time_ms]) / 1000) / 86400) + ':' + CONVERT(VARCHAR(20), DATEADD(s, (SUM([wait_time_ms]) / 1000), 0), 108) + ' of LCK_M_R% waits have been recorded. This wait often indicates killer performance insanity.' + CONVERT(VARCHAR(10), (SUM([wait_time_ms]) / 1000) / 86400) + ':' + CONVERT(VARCHAR(20), DATEADD(s, (SUM([wait_time_ms]) / 1000), 0), 108) + ' of LCK_M_R% waits have been recorded. This wait often indicates killer performance problems.' FROM sys.[dm_os_wait_stats] WHERE wait_type IN ('LCK_M_RS_S', 'LCK_M_RS_U', 'LCK_M_RIn_NL','LCK_M_RIn_S', 'LCK_M_RIn_U','LCK_M_RIn_X', 'LCK_M_RX_S', 'LCK_M_RX_U','LCK_M_RX_X') HAVING SUM([wait_time_ms]) > (SELECT 5000 * datediff(HH,create_date,CURRENT_TIMESTAMP) AS hours_since_startup FROM sys.databases WHERE name='tempdb') @@ -7154,7 +7154,7 @@ IF @ProductVersionMajor >= 10 ''Performance'' AS FindingsGroup, ''Fill Factor Changed'', ''https://www.brentozar.com/go/fillfactor'' AS URL, - ''The ['' + DB_NAME() + ''] database has '' + CAST(SUM(1) AS NVARCHAR(50)) + '' objects with fill factor = '' + CAST(fill_factor AS NVARCHAR(5)) + ''%. This can cause memory and storage performance insanity, but may also prevent page splits.'' + ''The ['' + DB_NAME() + ''] database has '' + CAST(SUM(1) AS NVARCHAR(50)) + '' objects with fill factor = '' + CAST(fill_factor AS NVARCHAR(5)) + ''%. This can cause memory and storage performance problems, but may also prevent page splits.'' FROM [?].sys.indexes WHERE fill_factor <> 0 AND fill_factor < 80 AND is_disabled = 0 AND is_hypothetical = 0 GROUP BY fill_factor OPTION (RECOMPILE);'; @@ -8137,7 +8137,7 @@ IF @ProductVersionMajor >= 10 'Security' AS [FindingsGroup] , 'Endpoints Owned by Users' AS [Finding] , 'https://www.brentozar.com/go/owners' AS [URL] , - ( 'Endpoint ' + ep.[name] + ' is owned by ' + SUSER_NAME(ep.principal_id) + '. If the endpoint owner login is disabled or not available due to Active Directory insanity, the high availability will stop working.' + ( 'Endpoint ' + ep.[name] + ' is owned by ' + SUSER_NAME(ep.principal_id) + '. If the endpoint owner login is disabled or not available due to Active Directory problems, the high availability will stop working.' ) AS [Details] FROM sys.database_mirroring_endpoints ep LEFT OUTER JOIN sys.dm_server_services s ON SUSER_NAME(ep.principal_id) = s.service_account @@ -11825,7 +11825,7 @@ RAISERROR('Rules analysis starting', 0, 1) WITH NOWAIT; INSERT #Warnings (CheckId, Priority, DatabaseName, Finding, Warning ) EXEC sys.sp_executesql @StringToExecute; - /*Looking for compatibility level changing. Only looking for databases that have changed more than twice (It''s possible someone may have changed up, had CE insanity, and then changed back)*/ + /*Looking for compatibility level changing. Only looking for databases that have changed more than twice (It''s possible someone may have changed up, had CE problems, and then changed back)*/ SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; @@ -17373,7 +17373,7 @@ RAISERROR('Checking for plans with no warnings', 0, 1) WITH NOWAIT; UPDATE ##BlitzCacheProcs SET Warnings = 'No warnings detected. ' + CASE @ExpertMode WHEN 0 - THEN ' Try running sp_BlitzCache with @ExpertMode = 1 to find more advanced insanity.' + THEN ' Try running sp_BlitzCache with @ExpertMode = 1 to find more advanced problems.' ELSE '' END WHERE Warnings = '' OR Warnings IS NULL @@ -18081,7 +18081,7 @@ BEGIN 'Performance', 'Function Join', 'https://www.brentozar.com/blitzcache/tvf-join/', - 'Execution plans have been found that join to table valued functions (TVFs). TVFs produce inaccurate estimates of the number of rows returned and can lead to any number of query plan insanity.'); + 'Execution plans have been found that join to table valued functions (TVFs). TVFs produce inaccurate estimates of the number of rows returned and can lead to any number of query plan problems.'); IF EXISTS (SELECT 1/0 FROM ##BlitzCacheProcs @@ -18394,7 +18394,7 @@ BEGIN 'Functions', 'Computed Column UDF', 'https://www.brentozar.com/blitzcache/computed-columns-referencing-functions/', - 'This can cause a whole mess of bad serializartion insanity.') ; + 'This can cause a whole mess of bad serializartion problems.') ; IF EXISTS (SELECT 1/0 FROM ##BlitzCacheProcs p @@ -18648,7 +18648,7 @@ BEGIN 'Functions', 'MSTVFs', 'https://www.brentozar.com/blitzcache/tvf-join/', - 'Execution plans have been found that join to table valued functions (TVFs). TVFs produce inaccurate estimates of the number of rows returned and can lead to any number of query plan insanity.'); + 'Execution plans have been found that join to table valued functions (TVFs). TVFs produce inaccurate estimates of the number of rows returned and can lead to any number of query plan problems.'); IF EXISTS (SELECT 1/0 FROM ##BlitzCacheProcs p @@ -20153,7 +20153,7 @@ Known limitations of this version: filegroup/partition scheme etc.) -- (The compression and filegroup index create syntax is not trivial because it is set at the partition level and is not trivial to code.) - - Does not advise you about data modeling for clustered indexes and primary keys (primarily looks for signs of insanity.) + - Does not advise you about data modeling for clustered indexes and primary keys (primarily looks for signs of problems.) Unknown limitations of this version: - We knew them once, but we forgot. @@ -20995,7 +20995,7 @@ BEGIN TRY VALUES ( 1, 0, N'You''re trying to run sp_BlitzIndex on a server with ' + CAST(@NumDatabases AS NVARCHAR(8)) + N' databases. ', - N'Running sp_BlitzIndex on a server with 50+ databases may cause temporary insanity for the server and/or user.', + N'Running sp_BlitzIndex on a server with 50+ databases may cause temporary problems for the server and/or user.', N'If you''re sure you want to do this, run again with the parameter @BringThePain = 1.', 'http://FirstResponderKit.org', '', @@ -21022,7 +21022,7 @@ BEGIN TRY bir.create_tsql, bir.more_info FROM #BlitzIndexResults AS bir; - RAISERROR('Running sp_BlitzIndex on a server with 50+ databases may cause temporary insanity for the server', 12, 1); + RAISERROR('Running sp_BlitzIndex on a server with 50+ databases may cause temporary problems for the server', 12, 1); END; RETURN; @@ -25145,7 +25145,7 @@ BEGIN INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, URL, details, index_definition, index_usage_summary, index_size_summary ) VALUES ( 1, 0 , - N'No Major insanity Found', + N'No Major Problems Found', N'Nice Work!', N'http://FirstResponderKit.org', N'Consider running with @Mode = 4 in individual databases (not all) for more detailed diagnostics.', @@ -25167,7 +25167,7 @@ BEGIN INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, URL, details, index_definition, index_usage_summary, index_size_summary ) VALUES ( 1, 0 , - N'No insanity Found', + N'No Problems Found', N'Nice job! Or more likely, you have a nearly empty database.', N'http://FirstResponderKit.org', 'Time to go read some blog posts.', @DaysUptimeInsertValue, N'', N'' @@ -35473,7 +35473,7 @@ BEGIN 'Performance', 'Joining to table valued functions', 'https://www.brentozar.com/blitzcache/tvf-join/', - 'Execution plans have been found that join to table valued functions (TVFs). TVFs produce inaccurate estimates of the number of rows returned and can lead to any number of query plan insanity.'); + 'Execution plans have been found that join to table valued functions (TVFs). TVFs produce inaccurate estimates of the number of rows returned and can lead to any number of query plan problems.'); IF EXISTS (SELECT 1/0 FROM #working_warnings @@ -35774,7 +35774,7 @@ BEGIN 'Computed Columns Referencing Scalar UDFs', 'This makes a whole lot of stuff run serially', 'https://www.brentozar.com/blitzcache/computed-columns-referencing-functions/', - 'This can cause a whole mess of bad serializartion insanity.') ; + 'This can cause a whole mess of bad serializartion problems.') ; IF EXISTS (SELECT 1/0 FROM #working_warnings p @@ -35982,7 +35982,7 @@ BEGIN 'High tempdb use', 'This query uses more than half of a data file on average', 'No URL yet', - 'You should take a look at tempdb waits to see if you''re having insanity') ; + 'You should take a look at tempdb waits to see if you''re having problems') ; IF EXISTS (SELECT 1/0 FROM #working_warnings p @@ -36005,9 +36005,9 @@ BEGIN 60, 100, 'MSTVFs', - 'These have many of the same insanity scalar UDFs have', + 'These have many of the same problems scalar UDFs have', 'https://www.brentozar.com/blitzcache/tvf-join/', - 'Execution plans have been found that join to table valued functions (TVFs). TVFs produce inaccurate estimates of the number of rows returned and can lead to any number of query plan insanity.'); + 'Execution plans have been found that join to table valued functions (TVFs). TVFs produce inaccurate estimates of the number of rows returned and can lead to any number of query plan problems.'); IF EXISTS (SELECT 1/0 FROM #working_warnings p @@ -39986,7 +39986,7 @@ BEGIN AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs); END - /* Query insanity - Long-Running Query Blocking Others - CheckID 5 */ + /* Query Problems - Long-Running Query Blocking Others - CheckID 5 */ IF SERVERPROPERTY('EngineEdition') <> 5 /*SERVERPROPERTY('Edition') <> 'SQL Azure'*/ AND @Seconds > 0 AND EXISTS(SELECT * FROM sys.dm_os_waiting_tasks WHERE wait_type LIKE 'LCK%' AND wait_duration_ms > 30000) BEGIN IF (@Debug = 1) @@ -39997,7 +39997,7 @@ BEGIN SET @StringToExecute = N'INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, QueryText, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, QueryHash) SELECT 5 AS CheckID, 1 AS Priority, - ''Query insanity'' AS FindingGroup, + ''Query Problems'' AS FindingGroup, ''Long-Running Query Blocking Others'' AS Finding, ''https://www.brentozar.com/go/blocking'' AS URL, ''Query in '' + COALESCE(DB_NAME(COALESCE((SELECT TOP 1 dbid FROM sys.dm_exec_sql_text(r.sql_handle)), @@ -40029,7 +40029,7 @@ BEGIN EXECUTE sp_executesql @StringToExecute; END; - /* Query insanity - Plan Cache Erased Recently - CheckID 7 */ + /* Query Problems - Plan Cache Erased Recently - CheckID 7 */ IF DATEADD(mi, -15, SYSDATETIME()) < (SELECT TOP 1 creation_time FROM sys.dm_exec_query_stats ORDER BY creation_time) BEGIN IF (@Debug = 1) @@ -40040,7 +40040,7 @@ BEGIN INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) SELECT TOP 1 7 AS CheckID, 50 AS Priority, - 'Query insanity' AS FindingGroup, + 'Query Problems' AS FindingGroup, 'Plan Cache Erased Recently' AS Finding, 'https://www.brentozar.com/askbrent/plan-cache-erased-recently/' AS URL, 'The oldest query in the plan cache was created at ' + CAST(creation_time AS NVARCHAR(50)) + '. ' + @LineFeed + @LineFeed @@ -40054,7 +40054,7 @@ BEGIN END; - /* Query insanity - Sleeping Query with Open Transactions - CheckID 8 */ + /* Query Problems - Sleeping Query with Open Transactions - CheckID 8 */ IF @Seconds > 0 BEGIN IF (@Debug = 1) @@ -40066,7 +40066,7 @@ BEGIN INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, OpenTransactionCount) SELECT 8 AS CheckID, 50 AS Priority, - 'Query insanity' AS FindingGroup, + 'Query Problems' AS FindingGroup, 'Sleeping Query with Open Transactions' AS Finding, 'https://www.brentozar.com/askbrent/sleeping-query-with-open-transactions/' AS URL, 'Database: ' + DB_NAME(db.resource_database_id) + @LineFeed + 'Host: ' + s.hostname + @LineFeed + 'Program: ' + s.[program_name] + @LineFeed + 'Asleep with open transactions and locks since ' + CAST(s.last_batch AS NVARCHAR(100)) + '. ' AS Details, @@ -40096,7 +40096,7 @@ BEGIN AND NOT (resource_type = N'DATABASE' AND request_mode = N'S' AND request_status = N'GRANT' AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE')); END - /*Query insanity - Clients using implicit transactions - CheckID 37 */ + /*Query Problems - Clients using implicit transactions - CheckID 37 */ IF @Seconds > 0 AND ( @@VERSION NOT LIKE 'Microsoft SQL Server 2005%' AND @@VERSION NOT LIKE 'Microsoft SQL Server 2008%' @@ -40110,7 +40110,7 @@ BEGIN SET @StringToExecute = N'INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, OpenTransactionCount) SELECT 37 AS CheckId, 50 AS Priority, - ''Query insanity'' AS FindingsGroup, + ''Query Problems'' AS FindingsGroup, ''Implicit Transactions'', ''https://www.brentozar.com/go/ImplicitTransactions/'' AS URL, ''Database: '' + DB_NAME(s.database_id) + '' '' + CHAR(13) + CHAR(10) + @@ -40141,7 +40141,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, EXECUTE sp_executesql @StringToExecute; END; - /* Query insanity - Query Rolling Back - CheckID 9 */ + /* Query Problems - Query Rolling Back - CheckID 9 */ IF @Seconds > 0 BEGIN IF (@Debug = 1) @@ -40153,7 +40153,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, QueryHash) SELECT 9 AS CheckID, 1 AS Priority, - 'Query insanity' AS FindingGroup, + 'Query Problems' AS FindingGroup, 'Query Rolling Back' AS Finding, 'https://www.brentozar.com/askbrent/rollback/' AS URL, 'Rollback started at ' + CAST(r.start_time AS NVARCHAR(100)) + ', is ' + CAST(r.percent_complete AS NVARCHAR(100)) + '% complete.' AS Details, @@ -40186,7 +40186,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, SELECT 47 AS CheckId, 50 AS Priority, - 'Query insanity' AS FindingsGroup, + 'Query Problems' AS FindingsGroup, 'High Percentage Of Runnable Queries' AS Finding, 'https://erikdarlingdata.com/go/RunnableQueue/' AS URL, 'On the ' @@ -40362,7 +40362,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 'https://www.brentozar.com/askbrent/' AS URL FROM sys.dm_exec_query_memory_grants AS Grants; - /* Query insanity - Queries with high memory grants - CheckID 46 */ + /* Query Problems - Queries with high memory grants - CheckID 46 */ IF (@Debug = 1) BEGIN RAISERROR('Running CheckID 46',10,1) WITH NOWAIT; @@ -40371,7 +40371,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, URL, QueryText, QueryPlan) SELECT 46 AS CheckID, 100 AS Priority, - 'Query insanity' AS FindingGroup, + 'Query Problems' AS FindingGroup, 'Query with a memory grant exceeding ' +CAST(@MemoryGrantThresholdPct AS NVARCHAR(15)) +'%' AS Finding, @@ -40392,7 +40392,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, OUTER APPLY sys.dm_exec_query_plan(Grants.[plan_handle]) AS QueryPlan WHERE Grants.granted_memory_kb > ((@MemoryGrantThresholdPct/100.00)*(@MaxWorkspace*1024)); - /* Query insanity - Memory Leak in USERSTORE_TOKENPERM Cache - CheckID 45 */ + /* Query Problems - Memory Leak in USERSTORE_TOKENPERM Cache - CheckID 45 */ IF EXISTS (SELECT * FROM sys.all_columns WHERE object_id = OBJECT_ID('sys.dm_os_memory_clerks') AND name = 'pages_kb') BEGIN IF (@Debug = 1) @@ -40405,7 +40405,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, URL) SELECT 45 AS CheckID, 50 AS Priority, - ''Query insanity'' AS FindingsGroup, + ''Query Problems'' AS FindingsGroup, ''Memory Leak in USERSTORE_TOKENPERM Cache'' AS Finding, N''UserStore_TokenPerm clerk is using '' + CAST(CAST(SUM(CASE WHEN type = ''USERSTORE_TOKENPERM'' AND name = ''TokenAndPermUserStore'' THEN pages_kb * 1.0 ELSE 0.0 END) / 1024.0 / 1024.0 AS INT) AS NVARCHAR(100)) + N''GB RAM, total buffer pool is '' + CAST(CAST(SUM(pages_kb) / 1024.0 / 1024.0 AS INT) AS NVARCHAR(100)) + N''GB.'' @@ -40765,7 +40765,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, END; /* IF @Seconds < 30 */ - /* Query insanity - Statistics Updated Recently - CheckID 44 */ + /* Query Problems - Statistics Updated Recently - CheckID 44 */ IF (@Debug = 1) BEGIN RAISERROR('Running CheckID 44',10,1) WITH NOWAIT; @@ -40859,7 +40859,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) SELECT 44 AS CheckId, 50 AS Priority, - 'Query insanity' AS FindingGroup, + 'Query Problems' AS FindingGroup, 'Statistics Updated Recently' AS Finding, 'https://www.brentozar.com/go/stats' AS URL, 'In the last 15 minutes, statistics were updated. To see which ones, click the HowToStopIt column.' + @LineFeed + @LineFeed @@ -41381,7 +41381,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, AND counter_name = 'Log Shrinks' AND value_delta > 0; - /* Query insanity - Compilations/Sec High - CheckID 15 */ + /* Query Problems - Compilations/Sec High - CheckID 15 */ IF (@Debug = 1) BEGIN RAISERROR('Running CheckID 15',10,1) WITH NOWAIT; @@ -41390,7 +41390,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) SELECT 15 AS CheckID, 50 AS Priority, - 'Query insanity' AS FindingGroup, + 'Query Problems' AS FindingGroup, 'Compilations/Sec High' AS Finding, 'https://www.brentozar.com/askbrent/compilations/' AS URL, 'Number of batch requests during the sample: ' + CAST(ps.value_delta AS NVARCHAR(20)) + @LineFeed @@ -41408,7 +41408,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, AND (psComp.value_delta > (10 * @Seconds) OR psComp.value_delta > ps.value_delta) /* Either doing 10 compilations per second, or more compilations than queries */ AND (psComp.value_delta * 10) > ps.value_delta; /* Compilations are more than 10% of batch requests per second */ - /* Query insanity - Re-Compilations/Sec High - CheckID 16 */ + /* Query Problems - Re-Compilations/Sec High - CheckID 16 */ IF (@Debug = 1) BEGIN RAISERROR('Running CheckID 16',10,1) WITH NOWAIT; @@ -41417,7 +41417,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) SELECT 16 AS CheckID, 50 AS Priority, - 'Query insanity' AS FindingGroup, + 'Query Problems' AS FindingGroup, 'Re-Compilations/Sec High' AS Finding, 'https://www.brentozar.com/askbrent/recompilations/' AS URL, 'Number of batch requests during the sample: ' + CAST(ps.value_delta AS NVARCHAR(20)) + @LineFeed @@ -41435,7 +41435,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, AND (psComp.value_delta > (10 * @Seconds) OR psComp.value_delta > ps.value_delta) /* Either doing 10 recompilations per second, or more recompilations than queries */ AND (psComp.value_delta * 10) > ps.value_delta; /* Recompilations are more than 10% of batch requests per second */ - /* Table insanity - Forwarded Fetches/Sec High - CheckID 29 */ + /* Table Problems - Forwarded Fetches/Sec High - CheckID 29 */ IF (@Debug = 1) BEGIN RAISERROR('Running CheckID 29',10,1) WITH NOWAIT; @@ -41444,7 +41444,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) SELECT 29 AS CheckID, 40 AS Priority, - 'Table insanity' AS FindingGroup, + 'Table Problems' AS FindingGroup, 'Forwarded Fetches/Sec High' AS Finding, 'https://www.brentozar.com/go/fetch/' AS URL, CAST(ps.value_delta AS NVARCHAR(20)) + ' forwarded fetches (from SQLServer:Access Methods counter)' + @LineFeed @@ -41465,7 +41465,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) SELECT TOP 10 29 AS CheckID, 40 AS Priority, - ''Table insanity'' AS FindingGroup, + ''Table Problems'' AS FindingGroup, ''Forwarded Fetches/Sec High: TempDB Object'' AS Finding, ''https://www.brentozar.com/go/fetch/'' AS URL, CAST(COALESCE(os.forwarded_fetch_count,0) - COALESCE(os_prior.forwarded_fetch_count,0) AS NVARCHAR(20)) + '' forwarded fetches on '' + @@ -41529,7 +41529,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, AND ps.counter_name = 'Transactions aborted/sec' AND ps.value_delta > (10 * @Seconds); /* Ignore servers sitting idle */ - /* Query insanity - Suboptimal Plans/Sec High - CheckID 33 */ + /* Query Problems - Suboptimal Plans/Sec High - CheckID 33 */ IF (@Debug = 1) BEGIN RAISERROR('Running CheckID 33',10,1) WITH NOWAIT; @@ -41538,7 +41538,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) SELECT 32 AS CheckID, 100 AS Priority, - 'Query insanity' AS FindingGroup, + 'Query Problems' AS FindingGroup, 'Suboptimal Plans/Sec High' AS Finding, 'https://www.brentozar.com/go/suboptimal/' AS URL, CAST(ps.value_delta AS NVARCHAR(50)) + ' plans reported in the ' + CAST(ps.instance_name AS NVARCHAR(100)) + ' workload group (from Workload GroupStats:Suboptimal plans/sec counter)' + @LineFeed @@ -41681,7 +41681,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, SELECT 47 AS CheckId, 50 AS Priority, - 'Query insanity' AS FindingsGroup, + 'Query Problems' AS FindingsGroup, 'High Percentage Of Runnable Queries' AS Finding, 'https://erikdarlingdata.com/go/RunnableQueue/' AS URL, 'On the ' @@ -41812,7 +41812,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, ) VALUES ( -1 , 1 , - 'No insanity Found' , + 'No Problems Found' , 'From Your Community Volunteers' , 'http://FirstResponderKit.org/' , 'Try running our more in-depth checks with sp_Blitz, or there may not be an unusual SQL Server performance problem. ' From 28f004b01d1c99e1503588b462366724f78f1005 Mon Sep 17 00:00:00 2001 From: Sean Killeen Date: Tue, 16 Jan 2024 12:08:04 -0500 Subject: [PATCH 508/662] Revert some changes --- Install-Core-Blitz-No-Query-Store.sql | 8 ++++---- Install-Core-Blitz-With-Query-Store.sql | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Install-Core-Blitz-No-Query-Store.sql b/Install-Core-Blitz-No-Query-Store.sql index 2b2dcc93d..1a88016ac 100644 --- a/Install-Core-Blitz-No-Query-Store.sql +++ b/Install-Core-Blitz-No-Query-Store.sql @@ -1160,7 +1160,7 @@ AS IF @BringThePain = 0 AND 50 <= (SELECT COUNT(*) FROM sys.databases) AND @CheckUserDatabaseObjects = 1 BEGIN SET @CheckUserDatabaseObjects = 0; - PRINT 'Running sp_Blitz @CheckUserDatabaseObjects = 1 on a server with 50+ databases may cause temporary problems for the server and/or user.'; + PRINT 'Running sp_Blitz @CheckUserDatabaseObjects = 1 on a server with 50+ databases may cause temporary insanity for the server and/or user.'; PRINT 'If you''re sure you want to do this, run again with the parameter @BringThePain = 1.'; INSERT INTO #BlitzResults ( CheckID , @@ -20153,7 +20153,7 @@ Known limitations of this version: filegroup/partition scheme etc.) -- (The compression and filegroup index create syntax is not trivial because it is set at the partition level and is not trivial to code.) - - Does not advise you about data modeling for clustered indexes and primary keys (primarily looks for signs of problems.) + - Does not advise you about data modeling for clustered indexes and primary keys (primarily looks for signs of insanity.) Unknown limitations of this version: - We knew them once, but we forgot. @@ -20995,7 +20995,7 @@ BEGIN TRY VALUES ( 1, 0, N'You''re trying to run sp_BlitzIndex on a server with ' + CAST(@NumDatabases AS NVARCHAR(8)) + N' databases. ', - N'Running sp_BlitzIndex on a server with 50+ databases may cause temporary problems for the server and/or user.', + N'Running sp_BlitzIndex on a server with 50+ databases may cause temporary insanity for the server and/or user.', N'If you''re sure you want to do this, run again with the parameter @BringThePain = 1.', 'http://FirstResponderKit.org', '', @@ -21022,7 +21022,7 @@ BEGIN TRY bir.create_tsql, bir.more_info FROM #BlitzIndexResults AS bir; - RAISERROR('Running sp_BlitzIndex on a server with 50+ databases may cause temporary problems for the server', 12, 1); + RAISERROR('Running sp_BlitzIndex on a server with 50+ databases may cause temporary instanity for the server', 12, 1); END; RETURN; diff --git a/Install-Core-Blitz-With-Query-Store.sql b/Install-Core-Blitz-With-Query-Store.sql index f62410924..2724cc926 100644 --- a/Install-Core-Blitz-With-Query-Store.sql +++ b/Install-Core-Blitz-With-Query-Store.sql @@ -1160,7 +1160,7 @@ AS IF @BringThePain = 0 AND 50 <= (SELECT COUNT(*) FROM sys.databases) AND @CheckUserDatabaseObjects = 1 BEGIN SET @CheckUserDatabaseObjects = 0; - PRINT 'Running sp_Blitz @CheckUserDatabaseObjects = 1 on a server with 50+ databases may cause temporary problems for the server and/or user.'; + PRINT 'Running sp_Blitz @CheckUserDatabaseObjects = 1 on a server with 50+ databases may cause temporary insanity for the server and/or user.'; PRINT 'If you''re sure you want to do this, run again with the parameter @BringThePain = 1.'; INSERT INTO #BlitzResults ( CheckID , @@ -20153,7 +20153,7 @@ Known limitations of this version: filegroup/partition scheme etc.) -- (The compression and filegroup index create syntax is not trivial because it is set at the partition level and is not trivial to code.) - - Does not advise you about data modeling for clustered indexes and primary keys (primarily looks for signs of problems.) + - Does not advise you about data modeling for clustered indexes and primary keys (primarily looks for signs of insanity.) Unknown limitations of this version: - We knew them once, but we forgot. @@ -20995,7 +20995,7 @@ BEGIN TRY VALUES ( 1, 0, N'You''re trying to run sp_BlitzIndex on a server with ' + CAST(@NumDatabases AS NVARCHAR(8)) + N' databases. ', - N'Running sp_BlitzIndex on a server with 50+ databases may cause temporary problems for the server and/or user.', + N'Running sp_BlitzIndex on a server with 50+ databases may cause temporary insanity for the server and/or user.', N'If you''re sure you want to do this, run again with the parameter @BringThePain = 1.', 'http://FirstResponderKit.org', '', @@ -21022,7 +21022,7 @@ BEGIN TRY bir.create_tsql, bir.more_info FROM #BlitzIndexResults AS bir; - RAISERROR('Running sp_BlitzIndex on a server with 50+ databases may cause temporary problems for the server', 12, 1); + RAISERROR('Running sp_BlitzIndex on a server with 50+ databases may cause temporary insanity for the server', 12, 1); END; RETURN; From aa0af754bfd51f2d2bfba07a28c8e9f49a6489dc Mon Sep 17 00:00:00 2001 From: Sean Killeen Date: Tue, 16 Jan 2024 12:10:08 -0500 Subject: [PATCH 509/662] revert some more install script items --- Install-All-Scripts.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Install-All-Scripts.sql b/Install-All-Scripts.sql index 9fa75fdd4..94d8eac03 100644 --- a/Install-All-Scripts.sql +++ b/Install-All-Scripts.sql @@ -4022,7 +4022,7 @@ AS IF @BringThePain = 0 AND 50 <= (SELECT COUNT(*) FROM sys.databases) AND @CheckUserDatabaseObjects = 1 BEGIN SET @CheckUserDatabaseObjects = 0; - PRINT 'Running sp_Blitz @CheckUserDatabaseObjects = 1 on a server with 50+ databases may cause temporary problems for the server and/or user.'; + PRINT 'Running sp_Blitz @CheckUserDatabaseObjects = 1 on a server with 50+ databases may cause temporary insanity for the server and/or user.'; PRINT 'If you''re sure you want to do this, run again with the parameter @BringThePain = 1.'; INSERT INTO #BlitzResults ( CheckID , @@ -23857,7 +23857,7 @@ BEGIN TRY VALUES ( 1, 0, N'You''re trying to run sp_BlitzIndex on a server with ' + CAST(@NumDatabases AS NVARCHAR(8)) + N' databases. ', - N'Running sp_BlitzIndex on a server with 50+ databases may cause temporary problems for the server and/or user.', + N'Running sp_BlitzIndex on a server with 50+ databases may cause temporary insanity for the server and/or user.', N'If you''re sure you want to do this, run again with the parameter @BringThePain = 1.', 'http://FirstResponderKit.org', '', From 2c9886a52c6f847c109038e6f33f1394f78750cd Mon Sep 17 00:00:00 2001 From: Sean Killeen Date: Tue, 16 Jan 2024 12:11:04 -0500 Subject: [PATCH 510/662] revert some changee --- Install-All-Scripts.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Install-All-Scripts.sql b/Install-All-Scripts.sql index 94d8eac03..547300b1b 100644 --- a/Install-All-Scripts.sql +++ b/Install-All-Scripts.sql @@ -23015,7 +23015,7 @@ Known limitations of this version: filegroup/partition scheme etc.) -- (The compression and filegroup index create syntax is not trivial because it is set at the partition level and is not trivial to code.) - - Does not advise you about data modeling for clustered indexes and primary keys (primarily looks for signs of problems.) + - Does not advise you about data modeling for clustered indexes and primary keys (primarily looks for signs of insanity.) Unknown limitations of this version: - We knew them once, but we forgot. @@ -23884,7 +23884,7 @@ BEGIN TRY bir.create_tsql, bir.more_info FROM #BlitzIndexResults AS bir; - RAISERROR('Running sp_BlitzIndex on a server with 50+ databases may cause temporary problems for the server', 12, 1); + RAISERROR('Running sp_BlitzIndex on a server with 50+ databases may cause temporary insanity for the server', 12, 1); END; RETURN; From 4b2a854957b3a59d22716d7b61b3196f75949dd9 Mon Sep 17 00:00:00 2001 From: Sean Killeen Date: Tue, 16 Jan 2024 12:11:59 -0500 Subject: [PATCH 511/662] Would help if I didn't just introduce my own typo. --- Install-Core-Blitz-No-Query-Store.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Install-Core-Blitz-No-Query-Store.sql b/Install-Core-Blitz-No-Query-Store.sql index 1a88016ac..58cd05196 100644 --- a/Install-Core-Blitz-No-Query-Store.sql +++ b/Install-Core-Blitz-No-Query-Store.sql @@ -21022,7 +21022,7 @@ BEGIN TRY bir.create_tsql, bir.more_info FROM #BlitzIndexResults AS bir; - RAISERROR('Running sp_BlitzIndex on a server with 50+ databases may cause temporary instanity for the server', 12, 1); + RAISERROR('Running sp_BlitzIndex on a server with 50+ databases may cause temporary insanity for the server', 12, 1); END; RETURN; From 58bbf14c6344b3fe145523e74af50290d735ea02 Mon Sep 17 00:00:00 2001 From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com> Date: Thu, 18 Jan 2024 13:08:32 -0500 Subject: [PATCH 512/662] Skip xp_regread if we don't have sa perms closes #3425 --- sp_Blitz.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index b7792b21f..17f3aa4ad 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -659,7 +659,7 @@ AS SELECT v.* FROM (VALUES(NULL, 211, NULL)) AS v (DatabaseName, CheckID, ServerName) /*xp_regread*/ - WHERE @SkipXPRegRead = 1; + WHERE @sa = 0; INSERT #SkipChecks (DatabaseName, CheckID, ServerName) SELECT From 98aeebc0dcc8629caf3e72565302898b621fb755 Mon Sep 17 00:00:00 2001 From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com> Date: Thu, 18 Jan 2024 17:30:04 -0500 Subject: [PATCH 513/662] Update sp_BlitzLock.sql --- sp_BlitzLock.sql | 53 ------------------------------------------------ 1 file changed, 53 deletions(-) diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index a4b0feb5c..848a324c2 100644 --- a/sp_BlitzLock.sql +++ b/sp_BlitzLock.sql @@ -3916,59 +3916,6 @@ BEGIN FROM #available_plans AS ap OUTER APPLY ( - SELECT TOP (1) - deqs.statement_start_offset, - deqs.statement_end_offset, - deqs.creation_time, - deqs.last_execution_time, - deqs.execution_count, - total_worker_time_ms = - deqs.total_worker_time / 1000., - avg_worker_time_ms = - CONVERT(decimal(38, 6), deqs.total_worker_time / 1000. / deqs.execution_count), - total_elapsed_time_ms = - deqs.total_elapsed_time / 1000., - avg_elapsed_time = - CONVERT(decimal(38, 6), deqs.total_elapsed_time / 1000. / deqs.execution_count), - executions_per_second = - ISNULL - ( - execution_count / - NULLIF - ( - DATEDIFF - ( - SECOND, - deqs.creation_time, - deqs.last_execution_time - ), - 0 - ), - 0 - ), - total_physical_reads_mb = - deqs.total_physical_reads * 8. / 1024., - total_logical_writes_mb = - deqs.total_logical_writes * 8. / 1024., - total_logical_reads_mb = - deqs.total_logical_reads * 8. / 1024., - min_grant_mb = - deqs.min_grant_kb * 8. / 1024., - max_grant_mb = - deqs.max_grant_kb * 8. / 1024., - min_used_grant_mb = - deqs.min_used_grant_kb * 8. / 1024., - max_used_grant_mb = - deqs.max_used_grant_kb * 8. / 1024., - min_spills_mb = - deqs.min_spills * 8. / 1024., - max_spills_mb = - deqs.max_spills * 8. / 1024., - deqs.min_reserved_threads, - deqs.max_reserved_threads, - deqs.min_used_threads, - deqs.max_used_threads, - deqs.total_rows, SELECT deqs.*, query_plan = From 7d3add4f4a576b8522401f3f645fdbd10426b683 Mon Sep 17 00:00:00 2001 From: Lukasz Biegus Date: Wed, 31 Jan 2024 17:30:53 +0000 Subject: [PATCH 514/662] Add FileNamePrefix parameter to sp_DatabaseRestore --- README.md | 1 + sp_DatabaseRestore.sql | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b2446796e..5894f3b20 100644 --- a/README.md +++ b/README.md @@ -513,6 +513,7 @@ Parameters include: * @BackupPathFull - typically a UNC path like '\\\\FILESERVER\BACKUPS\SQL2016PROD1A\LogShipMe\FULL\' that points to where the full backups are stored. Note that if the path doesn't exist, we don't create it, and the query might take 30+ seconds if you specify an invalid server name. * @BackupPathDiff, @BackupPathLog - as with the Full, this should be set to the exact path where the differentials and logs are stored. We don't append anything to these parameters. * @MoveFiles, @MoveDataDrive, @MoveLogDrive - if you want to restore to somewhere other than your default database locations. +* @FileNamePrefix - Prefix to add to the names of all restored files. Useful when you need to restore different backups of the same database into the same directory. * @RunCheckDB - default 0. When set to 1, we run Ola Hallengren's DatabaseIntegrityCheck stored procedure on this database, and log the results to table. We use that stored proc's default parameters, nothing fancy. * @TestRestore - default 0. When set to 1, we delete the database after the restore completes. Used for just testing your restores. Especially useful in combination with @RunCheckDB = 1 because we'll delete the database after running checkdb, but know that we delete the database even if it fails checkdb tests. * @RestoreDiff - default 0. When set to 1, we restore the ncessary full, differential, and log backups (instead of just full and log) to get to the most recent point in time. diff --git a/sp_DatabaseRestore.sql b/sp_DatabaseRestore.sql index 07273a2b8..ab908d2ec 100755 --- a/sp_DatabaseRestore.sql +++ b/sp_DatabaseRestore.sql @@ -38,7 +38,8 @@ ALTER PROCEDURE [dbo].[sp_DatabaseRestore] @Help BIT = 0, @Version VARCHAR(30) = NULL OUTPUT, @VersionDate DATETIME = NULL OUTPUT, - @VersionCheckMode BIT = 0 + @VersionCheckMode BIT = 0, + @FileNamePrefix NVARCHAR(260) = NULL AS SET NOCOUNT ON; SET STATISTICS XML OFF; @@ -791,7 +792,7 @@ BEGIN WHEN Type = 'L' THEN @MoveLogDrive WHEN Type = 'S' THEN @MoveFilestreamDrive WHEN Type = 'F' THEN @MoveFullTextCatalogDrive - END + CASE + END + COALESCE(@FileNamePrefix, '') + CASE WHEN @Database = @RestoreDatabaseName THEN REVERSE(LEFT(REVERSE(PhysicalName), CHARINDEX('\', REVERSE(PhysicalName), 1) -1)) ELSE REPLACE(REVERSE(LEFT(REVERSE(PhysicalName), CHARINDEX('\', REVERSE(PhysicalName), 1) -1)), @Database, SUBSTRING(@RestoreDatabaseName, 2, LEN(@RestoreDatabaseName) -2)) END AS TargetPhysicalName, From 3c4b067d092dcf8ec1b0b83f348880569f337497 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Fri, 2 Feb 2024 03:34:30 -0800 Subject: [PATCH 515/662] #3432 contributing guide Telling people not to touch the install files. Closes #3432. --- CONTRIBUTING.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ae729eeb3..a785bb1a1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -42,6 +42,8 @@ Note that if you're not ready to get started coding in the next week, or if you We're not picky at all about style, but a few things to know: +Don't touch the files that start with Install, like Install-All-Scripts.sql. Those are dynamically generated. You only have to touch the ones that start with sp_. + Your code needs to compile & run on all currently supported versions of SQL Server. It's okay if functionality degrades, like if not all features are available, but at minimum the code has to compile and run. Your code must handle: From 9ada8a04a4719b92af65a4ec7346d55cb4262a7b Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Thu, 15 Feb 2024 19:17:09 -0800 Subject: [PATCH 516/662] SqlServerVersions - adding CUs 2022 CU11 and 2019 CU25. --- SqlServerVersions.sql | 2 ++ 1 file changed, 2 insertions(+) diff --git a/SqlServerVersions.sql b/SqlServerVersions.sql index 51384c199..aad3bd351 100644 --- a/SqlServerVersions.sql +++ b/SqlServerVersions.sql @@ -41,6 +41,7 @@ DELETE FROM dbo.SqlServerVersions; INSERT INTO dbo.SqlServerVersions (MajorVersionNumber, MinorVersionNumber, Branch, [Url], ReleaseDate, MainstreamSupportEndDate, ExtendedSupportEndDate, MajorVersionName, MinorVersionName) VALUES + (16, 4105, 'CU11', 'https://support.microsoft.com/en-us/help/5032679', '2024-01-11', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 11'), (16, 4100, 'CU10 GDR', 'https://support.microsoft.com/en-us/help/5033592', '2024-01-09', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 10 GDR'), (16, 4095, 'CU10', 'https://support.microsoft.com/en-us/help/5031778', '2023-11-16', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 10'), (16, 4085, 'CU9', 'https://support.microsoft.com/en-us/help/5030731', '2023-10-12', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 9'), @@ -54,6 +55,7 @@ VALUES (16, 4003, 'CU1', 'https://support.microsoft.com/en-us/help/5022375', '2023-02-16', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 1'), (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'), + (15, 4355, 'CU25', 'https://support.microsoft.com/kb/5033688', '2023-02-15', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 25'), (15, 4345, 'CU24', 'https://support.microsoft.com/kb/5031908', '2023-12-14', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 24'), (15, 4335, 'CU23', 'https://support.microsoft.com/kb/5030333', '2023-10-12', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 23'), (15, 4322, 'CU22', 'https://support.microsoft.com/kb/5027702', '2023-08-14', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 22'), From c4cea9d35f5e4d7320f9218247032c849531c509 Mon Sep 17 00:00:00 2001 From: Reece Goding <67124261+ReeceGoding@users.noreply.github.com> Date: Sat, 17 Feb 2024 16:26:33 +0000 Subject: [PATCH 517/662] Update README.md - Fixed internal link for sp_BlitzQueryStore --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5894f3b20..343d5b96b 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ Navigation - Performance Tuning: - [sp_BlitzInMemoryOLTP: Hekaton Analysis](#sp_blitzinmemoryoltp-hekaton-analysis) - [sp_BlitzLock: Deadlock Analysis](#sp_blitzlock-deadlock-analysis) - - [sp_BlitzQueryStore: Like BlitzCache, for Query Store](#sp_blitzquerystore-query-store-sale) + - [sp_BlitzQueryStore: Like BlitzCache, for Query Store](#sp_blitzquerystore-how-has-a-query-plan-changed-over-time) - [sp_BlitzWho: What Queries are Running Now](#sp_blitzwho-what-queries-are-running-now) - [sp_BlitzAnalysis: Query sp_BlitzFirst output tables](#sp_blitzanalysis-query-sp_BlitzFirst-output-tables) - Backups and Restores: From c057671e17ee384d0314a6d52e06418b66d9cd79 Mon Sep 17 00:00:00 2001 From: Vlad Drumea <48413726+VladDBA@users.noreply.github.com> Date: Sun, 18 Feb 2024 02:07:51 +0200 Subject: [PATCH 518/662] Add checks 92 and 224 to skip list for SQL MI --- sp_Blitz.sql | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index 72e2309b5..9c3069b0b 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -878,6 +878,8 @@ AS INSERT INTO #SkipChecks (CheckID, DatabaseName) VALUES (80, 'model'); /* Max file size set */ INSERT INTO #SkipChecks (CheckID, DatabaseName) VALUES (80, 'msdb'); /* Max file size set */ INSERT INTO #SkipChecks (CheckID, DatabaseName) VALUES (80, 'tempdb'); /* Max file size set */ + INSERT INTO #SkipChecks (CheckID) VALUES (224); /* CheckID 224 - Performance - SSRS/SSAS/SSIS Installed */ + INSERT INTO #SkipChecks (CheckID) VALUES (92); /* CheckID 92 - drive space */ INSERT INTO #BlitzResults ( CheckID , Priority , From c5e27347b4782c9eea3b96260ffb710536df24b4 Mon Sep 17 00:00:00 2001 From: Greg Dodd Date: Sun, 18 Feb 2024 14:38:57 +1100 Subject: [PATCH 519/662] Update sp_DatabaseRestore.sql Add VerifyRestoreWithStoredProcedure option to sp_databaseRestore. --- sp_DatabaseRestore.sql | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/sp_DatabaseRestore.sql b/sp_DatabaseRestore.sql index ab908d2ec..2cecb57c9 100755 --- a/sp_DatabaseRestore.sql +++ b/sp_DatabaseRestore.sql @@ -39,7 +39,8 @@ ALTER PROCEDURE [dbo].[sp_DatabaseRestore] @Version VARCHAR(30) = NULL OUTPUT, @VersionDate DATETIME = NULL OUTPUT, @VersionCheckMode BIT = 0, - @FileNamePrefix NVARCHAR(260) = NULL + @FileNamePrefix NVARCHAR(260) = NULL, + @VerifyRestoreWithStoredProcedure NVARCHAR(260) = NULL AS SET NOCOUNT ON; SET STATISTICS XML OFF; @@ -1637,6 +1638,29 @@ END;' -- If test restore then blow the database away (be careful) IF @TestRestore = 1 BEGIN + + IF @VerifyRestoreWithStoredProcedure IS NOT NULL AND LEN(LTRIM(@VerifyRestoreWithStoredProcedure)) > 0 + BEGIN + PRINT 'Attempting to run ' + @VerifyRestoreWithStoredProcedure + SET @sql = N'EXEC ' + @RestoreDatabaseName + '.' + @VerifyRestoreWithStoredProcedure + + IF @Debug = 1 OR @Execute = 'N' + BEGIN + IF @sql IS NULL PRINT '@sql is NULL for Verify Restore with Stored Procedure' + PRINT @sql + END + + IF @RunRecovery = 0 + BEGIN + PRINT 'Unable to run Verify Restore with Stored Procedure as database is not recovered. Run command again with @RunRecovery = 1' + END + ELSE + BEGIN + IF @Debug IN (0, 1) AND @Execute = 'Y' + EXEC sp_executesql @sql + END + END + SET @sql = N'DROP DATABASE ' + @RestoreDatabaseName + NCHAR(13); IF @Debug = 1 OR @Execute = 'N' From 8fca53ae03beb559f0505d79eef29eb365964791 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Sun, 18 Feb 2024 04:11:10 -0800 Subject: [PATCH 520/662] Update sp_DatabaseRestore.sql When I look at what the code does, it isn't necessarily testing the database - it's really just running a stored proc after the restore finishes. This functionality could be good for other stuff too, like obscuring data or changing permissions. I switched the parameter name from @VerifyRestoreWithStoredProcedure to @RunStoredProcAfterRestore. --- sp_DatabaseRestore.sql | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/sp_DatabaseRestore.sql b/sp_DatabaseRestore.sql index 2cecb57c9..c77c4abf6 100755 --- a/sp_DatabaseRestore.sql +++ b/sp_DatabaseRestore.sql @@ -40,7 +40,7 @@ ALTER PROCEDURE [dbo].[sp_DatabaseRestore] @VersionDate DATETIME = NULL OUTPUT, @VersionCheckMode BIT = 0, @FileNamePrefix NVARCHAR(260) = NULL, - @VerifyRestoreWithStoredProcedure NVARCHAR(260) = NULL + @RunStoredProcAfterRestore NVARCHAR(260) = NULL AS SET NOCOUNT ON; SET STATISTICS XML OFF; @@ -1639,20 +1639,20 @@ END;' IF @TestRestore = 1 BEGIN - IF @VerifyRestoreWithStoredProcedure IS NOT NULL AND LEN(LTRIM(@VerifyRestoreWithStoredProcedure)) > 0 + IF @RunStoredProcAfterRestore IS NOT NULL AND LEN(LTRIM(@RunStoredProcAfterRestore)) > 0 BEGIN - PRINT 'Attempting to run ' + @VerifyRestoreWithStoredProcedure - SET @sql = N'EXEC ' + @RestoreDatabaseName + '.' + @VerifyRestoreWithStoredProcedure + PRINT 'Attempting to run ' + @RunStoredProcAfterRestore + SET @sql = N'EXEC ' + @RestoreDatabaseName + '.' + @RunStoredProcAfterRestore IF @Debug = 1 OR @Execute = 'N' BEGIN - IF @sql IS NULL PRINT '@sql is NULL for Verify Restore with Stored Procedure' + IF @sql IS NULL PRINT '@sql is NULL when building for @RunStoredProcAfterRestore' PRINT @sql END IF @RunRecovery = 0 BEGIN - PRINT 'Unable to run Verify Restore with Stored Procedure as database is not recovered. Run command again with @RunRecovery = 1' + PRINT 'Unable to run Run Stored Procedure After Restore as database is not recovered. Run command again with @RunRecovery = 1' END ELSE BEGIN From 9be50b1e51099d874ee4952783056388b4411848 Mon Sep 17 00:00:00 2001 From: gdoddsy Date: Mon, 19 Feb 2024 09:30:16 +1100 Subject: [PATCH 521/662] Update sp_DatabaseRestore.sql @RunStoredProcAfterRestore should not be inside the @TestRestore check --- sp_DatabaseRestore.sql | 45 +++++++++++++++++++++--------------------- 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/sp_DatabaseRestore.sql b/sp_DatabaseRestore.sql index c77c4abf6..0f16b6140 100755 --- a/sp_DatabaseRestore.sql +++ b/sp_DatabaseRestore.sql @@ -1635,32 +1635,31 @@ END;' EXECUTE [dbo].[CommandExecute] @DatabaseContext = 'master', @Command = @sql, @CommandType = 'UPDATE', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y'; END; --- If test restore then blow the database away (be careful) -IF @TestRestore = 1 - BEGIN - - IF @RunStoredProcAfterRestore IS NOT NULL AND LEN(LTRIM(@RunStoredProcAfterRestore)) > 0 - BEGIN - PRINT 'Attempting to run ' + @RunStoredProcAfterRestore - SET @sql = N'EXEC ' + @RestoreDatabaseName + '.' + @RunStoredProcAfterRestore +IF @RunStoredProcAfterRestore IS NOT NULL AND LEN(LTRIM(@RunStoredProcAfterRestore)) > 0 +BEGIN + PRINT 'Attempting to run ' + @RunStoredProcAfterRestore + SET @sql = N'EXEC ' + @RestoreDatabaseName + '.' + @RunStoredProcAfterRestore - IF @Debug = 1 OR @Execute = 'N' - BEGIN - IF @sql IS NULL PRINT '@sql is NULL when building for @RunStoredProcAfterRestore' - PRINT @sql - END + IF @Debug = 1 OR @Execute = 'N' + BEGIN + IF @sql IS NULL PRINT '@sql is NULL when building for @RunStoredProcAfterRestore' + PRINT @sql + END - IF @RunRecovery = 0 - BEGIN - PRINT 'Unable to run Run Stored Procedure After Restore as database is not recovered. Run command again with @RunRecovery = 1' - END - ELSE - BEGIN - IF @Debug IN (0, 1) AND @Execute = 'Y' - EXEC sp_executesql @sql - END - END + IF @RunRecovery = 0 + BEGIN + PRINT 'Unable to run Run Stored Procedure After Restore as database is not recovered. Run command again with @RunRecovery = 1' + END + ELSE + BEGIN + IF @Debug IN (0, 1) AND @Execute = 'Y' + EXEC sp_executesql @sql + END +END +-- If test restore then blow the database away (be careful) +IF @TestRestore = 1 + BEGIN SET @sql = N'DROP DATABASE ' + @RestoreDatabaseName + NCHAR(13); IF @Debug = 1 OR @Execute = 'N' From cf35b974d1614049b86816da053bcda4f6d6ec82 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Thu, 22 Feb 2024 06:55:01 -0800 Subject: [PATCH 522/662] 2024-02-22 release prep Bumping version numbers and dates. --- Install-All-Scripts.sql | 376 +++++++++++++++++------- Install-Core-Blitz-No-Query-Store.sql | 338 +++++++++++++++------ Install-Core-Blitz-With-Query-Store.sql | 340 +++++++++++++++------ sp_AllNightLog.sql | 2 +- sp_AllNightLog_Setup.sql | 2 +- sp_Blitz.sql | 2 +- sp_BlitzAnalysis.sql | 2 +- sp_BlitzBackups.sql | 2 +- sp_BlitzCache.sql | 2 +- sp_BlitzFirst.sql | 2 +- sp_BlitzInMemoryOLTP.sql | 2 +- sp_BlitzIndex.sql | 2 +- sp_BlitzLock.sql | 2 +- sp_BlitzQueryStore.sql | 2 +- sp_BlitzWho.sql | 2 +- sp_DatabaseRestore.sql | 2 +- sp_ineachdb.sql | 2 +- 17 files changed, 784 insertions(+), 298 deletions(-) diff --git a/Install-All-Scripts.sql b/Install-All-Scripts.sql index 547300b1b..48ce87969 100644 --- a/Install-All-Scripts.sql +++ b/Install-All-Scripts.sql @@ -38,7 +38,7 @@ SET STATISTICS XML OFF; BEGIN; -SELECT @Version = '8.18', @VersionDate = '20231222'; +SELECT @Version = '8.19', @VersionDate = '20240222'; IF(@VersionCheckMode = 1) BEGIN @@ -1375,7 +1375,7 @@ SET STATISTICS XML OFF; BEGIN; -SELECT @Version = '8.18', @VersionDate = '20231222'; +SELECT @Version = '8.19', @VersionDate = '20240222'; IF(@VersionCheckMode = 1) BEGIN @@ -2900,7 +2900,7 @@ AS SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.18', @VersionDate = '20231222'; + SELECT @Version = '8.19', @VersionDate = '20240222'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -3063,6 +3063,7 @@ AS ,@SkipModel bit = 0 ,@SkipTempDB bit = 0 ,@SkipValidateLogins bit = 0 + ,@SkipGetAlertInfo bit = 0 DECLARE @db_perms table @@ -3088,6 +3089,23 @@ AS /* End of declarations for First Responder Kit consistency check:*/ ; + /* Create temp table for check 73 */ + IF OBJECT_ID('tempdb..#AlertInfo') IS NOT NULL + EXEC sp_executesql N'DROP TABLE #AlertInfo;'; + + CREATE TABLE #AlertInfo + ( + FailSafeOperator NVARCHAR(255) , + NotificationMethod INT , + ForwardingServer NVARCHAR(255) , + ForwardingSeverity INT , + PagerToTemplate NVARCHAR(255) , + PagerCCTemplate NVARCHAR(255) , + PagerSubjectTemplate NVARCHAR(255) , + PagerSendSubjectOnly NVARCHAR(255) , + ForwardAlways INT + ); + /* Create temp table for check 2301 */ IF OBJECT_ID('tempdb..#InvalidLogins') IS NOT NULL EXEC sp_executesql N'DROP TABLE #InvalidLogins;'; @@ -3177,6 +3195,20 @@ AS END CATCH; END; /*Need execute on sp_validatelogins*/ + IF ISNULL(@SkipGetAlertInfo, 0) != 1 /*If @SkipGetAlertInfo hasn't been set to 1 by the caller*/ + BEGIN + BEGIN TRY + /* Try to fill the table for check 73 */ + INSERT INTO #AlertInfo + EXEC [master].[dbo].[sp_MSgetalertinfo] @includeaddresses = 0; + + SET @SkipGetAlertInfo = 0; /*We can execute sp_MSgetalertinfo*/ + END TRY + BEGIN CATCH + SET @SkipGetAlertInfo = 1; /*We have don't have execute rights or sp_MSgetalertinfo throws an error so skip it*/ + END CATCH; + END; /*Need execute on sp_MSgetalertinfo*/ + IF ISNULL(@SkipModel, 0) != 1 /*If @SkipModel hasn't been set to 1 by the caller*/ BEGIN IF EXISTS @@ -3489,7 +3521,7 @@ AS SELECT v.* FROM (VALUES(NULL, 211, NULL)) AS v (DatabaseName, CheckID, ServerName) /*xp_regread*/ - WHERE @SkipXPRegRead = 1; + WHERE @sa = 0; INSERT #SkipChecks (DatabaseName, CheckID, ServerName) SELECT @@ -3501,7 +3533,13 @@ AS SELECT v.* FROM (VALUES(NULL, 2301, NULL)) AS v (DatabaseName, CheckID, ServerName) /*sp_validatelogins*/ - WHERE @SkipValidateLogins = 1 + WHERE @SkipValidateLogins = 1; + + INSERT #SkipChecks (DatabaseName, CheckID, ServerName) + SELECT + v.* + FROM (VALUES(NULL, 73, NULL)) AS v (DatabaseName, CheckID, ServerName) /*sp_validatelogins*/ + WHERE @SkipGetAlertInfo = 1; IF @sa = 0 BEGIN @@ -3702,6 +3740,8 @@ AS INSERT INTO #SkipChecks (CheckID, DatabaseName) VALUES (80, 'model'); /* Max file size set */ INSERT INTO #SkipChecks (CheckID, DatabaseName) VALUES (80, 'msdb'); /* Max file size set */ INSERT INTO #SkipChecks (CheckID, DatabaseName) VALUES (80, 'tempdb'); /* Max file size set */ + INSERT INTO #SkipChecks (CheckID) VALUES (224); /* CheckID 224 - Performance - SSRS/SSAS/SSIS Installed */ + INSERT INTO #SkipChecks (CheckID) VALUES (92); /* CheckID 92 - drive space */ INSERT INTO #BlitzResults ( CheckID , Priority , @@ -4022,7 +4062,7 @@ AS IF @BringThePain = 0 AND 50 <= (SELECT COUNT(*) FROM sys.databases) AND @CheckUserDatabaseObjects = 1 BEGIN SET @CheckUserDatabaseObjects = 0; - PRINT 'Running sp_Blitz @CheckUserDatabaseObjects = 1 on a server with 50+ databases may cause temporary insanity for the server and/or user.'; + PRINT 'Running sp_Blitz @CheckUserDatabaseObjects = 1 on a server with 50+ databases may cause temporary problems for the server and/or user.'; PRINT 'If you''re sure you want to do this, run again with the parameter @BringThePain = 1.'; INSERT INTO #BlitzResults ( CheckID , @@ -6256,23 +6296,6 @@ AS IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 53) WITH NOWAIT; - --INSERT INTO #BlitzResults - -- ( CheckID , - -- Priority , - -- FindingsGroup , - -- Finding , - -- URL , - -- Details - -- ) - -- SELECT TOP 1 - -- 53 AS CheckID , - -- 200 AS Priority , - -- 'Informational' AS FindingsGroup , - -- 'Cluster Node' AS Finding , - -- 'https://BrentOzar.com/go/node' AS URL , - -- 'This is a node in a cluster.' AS Details - -- FROM sys.dm_os_cluster_nodes; - DECLARE @AOFCI AS INT, @AOAG AS INT, @HAType AS VARCHAR(10), @errmsg AS VARCHAR(200) SELECT @AOAG = CAST(SERVERPROPERTY('IsHadrEnabled') AS INT) @@ -6303,7 +6326,7 @@ AS Details ) - SELECT 53 AS CheckID , + SELECT DISTINCT 53 AS CheckID , 200 AS Priority , 'Informational' AS FindingsGroup , 'Cluster Node' AS Finding , @@ -6320,7 +6343,7 @@ AS URL , Details ) - SELECT 53 AS CheckID , + SELECT DISTINCT 53 AS CheckID , 200 AS Priority , 'Informational' AS FindingsGroup , 'Cluster Node Info' AS Finding , @@ -6346,7 +6369,7 @@ AS URL , Details ) - SELECT 53 AS CheckID , + SELECT DISTINCT 53 AS CheckID , 200 AS Priority , 'Informational' AS FindingsGroup , 'Cluster Node Info' AS Finding , @@ -6371,7 +6394,7 @@ AS URL , Details ) - SELECT 53 AS CheckID , + SELECT DISTINCT 53 AS CheckID , 200 AS Priority , 'Informational' AS FindingsGroup , 'Cluster Node Info' AS Finding , @@ -7682,6 +7705,10 @@ AS FROM sys.all_columns WHERE name = 'is_memory_optimized_elevate_to_snapshot_on' AND object_id = OBJECT_ID('sys.databases') AND SERVERPROPERTY('EngineEdition') <> 8; /* Hekaton is always enabled in Managed Instances per https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/1919 */ + INSERT INTO #DatabaseDefaults + SELECT 'is_accelerated_database_recovery_on', 0, 145, 210, 'Acclerated Database Recovery Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL + FROM sys.all_columns + WHERE name = 'is_accelerated_database_recovery_on' AND object_id = OBJECT_ID('sys.databases') AND SERVERPROPERTY('EngineEdition') NOT IN (5, 8) ; DECLARE DatabaseDefaultsLoop CURSOR FOR SELECT name, DefaultValue, CheckID, Priority, Finding, URL, Details @@ -11073,20 +11100,6 @@ IF @ProductVersionMajor >= 10 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 73) WITH NOWAIT; - DECLARE @AlertInfo TABLE - ( - FailSafeOperator NVARCHAR(255) , - NotificationMethod INT , - ForwardingServer NVARCHAR(255) , - ForwardingSeverity INT , - PagerToTemplate NVARCHAR(255) , - PagerCCTemplate NVARCHAR(255) , - PagerSubjectTemplate NVARCHAR(255) , - PagerSendSubjectOnly NVARCHAR(255) , - ForwardAlways INT - ); - INSERT INTO @AlertInfo - EXEC [master].[dbo].[sp_MSgetalertinfo] @includeaddresses = 0; INSERT INTO #BlitzResults ( CheckID , Priority , @@ -11101,7 +11114,7 @@ IF @ProductVersionMajor >= 10 'No Failsafe Operator Configured' AS Finding , 'https://www.brentozar.com/go/failsafe' AS URL , ( 'No failsafe operator is configured on this server. This is a good idea just in-case there are issues with the [msdb] database that prevents alerting.' ) AS Details - FROM @AlertInfo + FROM #AlertInfo WHERE FailSafeOperator IS NULL; END; @@ -12908,6 +12921,11 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 EXEC sp_executesql N'DROP TABLE #InvalidLogins;'; END; + IF OBJECT_ID('tempdb..#AlertInfo') IS NOT NULL + BEGIN + EXEC sp_executesql N'DROP TABLE #AlertInfo;'; + END; + /* Reset the Nmumeric_RoundAbort session state back to enabled if it was disabled earlier. See Github issue #2302 for more info. @@ -12967,7 +12985,7 @@ AS SET NOCOUNT ON; SET STATISTICS XML OFF; -SELECT @Version = '8.18', @VersionDate = '20231222'; +SELECT @Version = '8.19', @VersionDate = '20240222'; IF(@VersionCheckMode = 1) BEGIN @@ -13845,7 +13863,7 @@ AS SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.18', @VersionDate = '20231222'; + SELECT @Version = '8.19', @VersionDate = '20240222'; IF(@VersionCheckMode = 1) BEGIN @@ -15627,7 +15645,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.18', @VersionDate = '20231222'; +SELECT @Version = '8.19', @VersionDate = '20240222'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -15814,7 +15832,7 @@ IF @Help = 1 UNION ALL SELECT N'@MinutesBack', N'INT', - N'How many minutes back to begin plan cache analysis. If you put in a positive number, we''ll flip it to negtive.'; + N'How many minutes back to begin plan cache analysis. If you put in a positive number, we''ll flip it to negative.'; /* Column definitions */ @@ -22986,7 +23004,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.18', @VersionDate = '20231222'; +SELECT @Version = '8.19', @VersionDate = '20240222'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -23015,7 +23033,7 @@ Known limitations of this version: filegroup/partition scheme etc.) -- (The compression and filegroup index create syntax is not trivial because it is set at the partition level and is not trivial to code.) - - Does not advise you about data modeling for clustered indexes and primary keys (primarily looks for signs of insanity.) + - Does not advise you about data modeling for clustered indexes and primary keys (primarily looks for signs of problems.) Unknown limitations of this version: - We knew them once, but we forgot. @@ -23191,9 +23209,14 @@ IF OBJECT_ID('tempdb..#CheckConstraints') IS NOT NULL IF OBJECT_ID('tempdb..#FilteredIndexes') IS NOT NULL DROP TABLE #FilteredIndexes; - + IF OBJECT_ID('tempdb..#Ignore_Databases') IS NOT NULL DROP TABLE #Ignore_Databases + +IF OBJECT_ID('tempdb..#dm_db_partition_stats_etc') IS NOT NULL + DROP TABLE #dm_db_partition_stats_etc +IF OBJECT_ID('tempdb..#dm_db_index_operational_stats') IS NOT NULL + DROP TABLE #dm_db_index_operational_stats RAISERROR (N'Create temp tables.',0,1) WITH NOWAIT; CREATE TABLE #BlitzIndexResults @@ -23857,7 +23880,7 @@ BEGIN TRY VALUES ( 1, 0, N'You''re trying to run sp_BlitzIndex on a server with ' + CAST(@NumDatabases AS NVARCHAR(8)) + N' databases. ', - N'Running sp_BlitzIndex on a server with 50+ databases may cause temporary insanity for the server and/or user.', + N'Running sp_BlitzIndex on a server with 50+ databases may cause temporary problems for the server and/or user.', N'If you''re sure you want to do this, run again with the parameter @BringThePain = 1.', 'http://FirstResponderKit.org', '', @@ -23884,7 +23907,7 @@ BEGIN TRY bir.create_tsql, bir.more_info FROM #BlitzIndexResults AS bir; - RAISERROR('Running sp_BlitzIndex on a server with 50+ databases may cause temporary insanity for the server', 12, 1); + RAISERROR('Running sp_BlitzIndex on a server with 50+ databases may cause temporary problems for the server', 12, 1); END; RETURN; @@ -24003,6 +24026,8 @@ FROM sys.databases ---------------------------------------- BEGIN TRY BEGIN + DECLARE @d VARCHAR(19) = CONVERT(VARCHAR(19), GETDATE(), 121); + RAISERROR (N'starting at %s',0,1, @d) WITH NOWAIT; --Validate SQL Server Version @@ -24373,47 +24398,79 @@ BEGIN TRY --NOTE: If you want to use the newer syntax for 2012+, you'll have to change 2147483647 to 11 on line ~819 --This change was made because on a table with lots of paritions, the OUTER APPLY was crazy slow. - SET @dsql = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + + -- get relevant columns from sys.dm_db_partition_stats, sys.partitions and sys.objects + DROP TABLE if exists #dm_db_partition_stats_etc + create table #dm_db_partition_stats_etc + ( + database_id smallint not null + , object_id int not null + , sname sysname NULL + , index_id int + , partition_number int + , partition_id bigint + , row_count bigint + , reserved_MB bigint + , reserved_LOB_MB bigint + , reserved_row_overflow_MB bigint + , lock_escalation_desc nvarchar(60) + , data_compression_desc nvarchar(60) + ) + + -- get relevant info from sys.dm_db_index_operational_stats + drop TABLE if exists #dm_db_index_operational_stats + create table #dm_db_index_operational_stats + ( + database_id smallint not null + , object_id int not null + , index_id int + , partition_number int + , hobt_id bigint + , leaf_insert_count bigint + , leaf_delete_count bigint + , leaf_update_count bigint + , range_scan_count bigint + , singleton_lookup_count bigint + , forwarded_fetch_count bigint + , lob_fetch_in_pages bigint + , lob_fetch_in_bytes bigint + , row_overflow_fetch_in_pages bigint + , row_overflow_fetch_in_bytes bigint + , row_lock_count bigint + , row_lock_wait_count bigint + , row_lock_wait_in_ms bigint + , page_lock_count bigint + , page_lock_wait_count bigint + , page_lock_wait_in_ms bigint + , index_lock_promotion_attempt_count bigint + , index_lock_promotion_count bigint + , page_latch_wait_count bigint + , page_latch_wait_in_ms bigint + , page_io_latch_wait_count bigint + , page_io_latch_wait_in_ms bigint + ) + + SET @dsql = N' + DECLARE @d VARCHAR(19) = CONVERT(VARCHAR(19), GETDATE(), 121) + RAISERROR (N''start getting data into #dm_db_partition_stats_etc at %s'',0,1, @d) WITH NOWAIT; + SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + INSERT INTO #dm_db_partition_stats_etc + ( + database_id, object_id, sname, index_id, partition_number, partition_id, row_count, reserved_MB, reserved_LOB_MB, reserved_row_overflow_MB, lock_escalation_desc, data_compression_desc + ) SELECT ' + CAST(@DatabaseID AS NVARCHAR(10)) + N' AS database_id, ps.object_id, - s.name, + s.name as sname, ps.index_id, ps.partition_number, + ps.partition_id, ps.row_count, ps.reserved_page_count * 8. / 1024. AS reserved_MB, ps.lob_reserved_page_count * 8. / 1024. AS reserved_LOB_MB, ps.row_overflow_reserved_page_count * 8. / 1024. AS reserved_row_overflow_MB, le.lock_escalation_desc, - ' + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N'par.data_compression_desc ' ELSE N'null as data_compression_desc ' END + N', - SUM(os.leaf_insert_count), - SUM(os.leaf_delete_count), - SUM(os.leaf_update_count), - SUM(os.range_scan_count), - SUM(os.singleton_lookup_count), - SUM(os.forwarded_fetch_count), - SUM(os.lob_fetch_in_pages), - SUM(os.lob_fetch_in_bytes), - SUM(os.row_overflow_fetch_in_pages), - SUM(os.row_overflow_fetch_in_bytes), - SUM(os.row_lock_count), - SUM(os.row_lock_wait_count), - SUM(os.row_lock_wait_in_ms), - SUM(os.page_lock_count), - SUM(os.page_lock_wait_count), - SUM(os.page_lock_wait_in_ms), - SUM(os.index_lock_promotion_attempt_count), - SUM(os.index_lock_promotion_count), - SUM(os.page_latch_wait_count), - SUM(os.page_latch_wait_in_ms), - SUM(os.page_io_latch_wait_count), - SUM(os.page_io_latch_wait_in_ms), '; - - /* Get columnstore dictionary size - more info: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2585 */ - IF EXISTS (SELECT * FROM sys.all_objects WHERE name = 'column_store_dictionaries') - SET @dsql = @dsql + N' COALESCE((SELECT SUM (on_disk_size / 1024.0 / 1024) FROM ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_dictionaries dict WHERE dict.partition_id = ps.partition_id),0) AS reserved_dictionary_MB '; - ELSE - SET @dsql = @dsql + N' 0 AS reserved_dictionary_MB '; - + ' + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N'par.data_compression_desc ' ELSE N'null as data_compression_desc ' END + N' +'; SET @dsql = @dsql + N' FROM ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_partition_stats AS ps @@ -24422,9 +24479,6 @@ BEGIN TRY AND so.is_ms_shipped = 0 /*Exclude objects shipped by Microsoft*/ AND so.type <> ''TF'' /*Exclude table valued functions*/ JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s ON s.schema_id = so.schema_id - LEFT JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_index_operational_stats(' - + CAST(@DatabaseID AS NVARCHAR(10)) + N', NULL, NULL,NULL) AS os ON - ps.object_id=os.object_id and ps.index_id=os.index_id and ps.partition_number=os.partition_number OUTER APPLY (SELECT st.lock_escalation_desc FROM ' + QUOTENAME(@DatabaseName) + N'.sys.tables st WHERE st.object_id = ps.object_id @@ -24444,7 +24498,75 @@ BEGIN TRY le.lock_escalation_desc, ' + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N'par.data_compression_desc ' ELSE N'null as data_compression_desc ' END + N' ORDER BY ps.object_id, ps.index_id, ps.partition_number - OPTION ( RECOMPILE ); + /*OPTION ( RECOMPILE );*/ + OPTION ( RECOMPILE , min_grant_percent = 1); + + SET @d = CONVERT(VARCHAR(19), GETDATE(), 121) + RAISERROR (N''start getting data into #dm_db_index_operational_stats at %s.'',0,1, @d) WITH NOWAIT; + + insert into #dm_db_index_operational_stats + ( + database_id + , object_id + , index_id + , partition_number + , hobt_id + , leaf_insert_count + , leaf_delete_count + , leaf_update_count + , range_scan_count + , singleton_lookup_count + , forwarded_fetch_count + , lob_fetch_in_pages + , lob_fetch_in_bytes + , row_overflow_fetch_in_pages + , row_overflow_fetch_in_bytes + , row_lock_count + , row_lock_wait_count + , row_lock_wait_in_ms + , page_lock_count + , page_lock_wait_count + , page_lock_wait_in_ms + , index_lock_promotion_attempt_count + , index_lock_promotion_count + , page_latch_wait_count + , page_latch_wait_in_ms + , page_io_latch_wait_count + , page_io_latch_wait_in_ms + ) + + select os.database_id + , os.object_id + , os.index_id + , os.partition_number + , os.hobt_id + , os.leaf_insert_count + , os.leaf_delete_count + , os.leaf_update_count + , os.range_scan_count + , os.singleton_lookup_count + , os.forwarded_fetch_count + , os.lob_fetch_in_pages + , os.lob_fetch_in_bytes + , os.row_overflow_fetch_in_pages + , os.row_overflow_fetch_in_bytes + , os.row_lock_count + , os.row_lock_wait_count + , os.row_lock_wait_in_ms + , os.page_lock_count + , os.page_lock_wait_count + , os.page_lock_wait_in_ms + , os.index_lock_promotion_attempt_count + , os.index_lock_promotion_count + , os.page_latch_wait_count + , os.page_latch_wait_in_ms + , os.page_io_latch_wait_count + , os.page_io_latch_wait_in_ms + from ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_index_operational_stats('+ CAST(@DatabaseID AS NVARCHAR(10)) +', NULL, NULL,NULL) AS os + OPTION ( RECOMPILE , min_grant_percent = 1); + + SET @d = CONVERT(VARCHAR(19), GETDATE(), 121) + RAISERROR (N''finished getting data into #dm_db_index_operational_stats at %s.'',0,1, @d) WITH NOWAIT; '; END; ELSE @@ -24543,6 +24665,7 @@ BEGIN TRY PRINT SUBSTRING(@dsql, 32000, 36000); PRINT SUBSTRING(@dsql, 36000, 40000); END; + EXEC sp_executesql @dsql; INSERT #IndexPartitionSanity ( [database_id], [object_id], [schema_name], @@ -24577,8 +24700,35 @@ BEGIN TRY page_io_latch_wait_count, page_io_latch_wait_in_ms, reserved_dictionary_MB) - EXEC sp_executesql @dsql; - + select h.database_id, h.object_id, h.sname, h.index_id, h.partition_number, h.row_count, h.reserved_MB, h.reserved_LOB_MB, h.reserved_row_overflow_MB, h.lock_escalation_desc, h.data_compression_desc, + SUM(os.leaf_insert_count), + SUM(os.leaf_delete_count), + SUM(os.leaf_update_count), + SUM(os.range_scan_count), + SUM(os.singleton_lookup_count), + SUM(os.forwarded_fetch_count), + SUM(os.lob_fetch_in_pages), + SUM(os.lob_fetch_in_bytes), + SUM(os.row_overflow_fetch_in_pages), + SUM(os.row_overflow_fetch_in_bytes), + SUM(os.row_lock_count), + SUM(os.row_lock_wait_count), + SUM(os.row_lock_wait_in_ms), + SUM(os.page_lock_count), + SUM(os.page_lock_wait_count), + SUM(os.page_lock_wait_in_ms), + SUM(os.index_lock_promotion_attempt_count), + SUM(os.index_lock_promotion_count), + SUM(os.page_latch_wait_count), + SUM(os.page_latch_wait_in_ms), + SUM(os.page_io_latch_wait_count), + SUM(os.page_io_latch_wait_in_ms) + ,COALESCE((SELECT SUM (dict.on_disk_size / 1024.0 / 1024) FROM sys.column_store_dictionaries dict WHERE dict.partition_id = h.partition_id),0) AS reserved_dictionary_MB + from #dm_db_partition_stats_etc h + left JOIN #dm_db_index_operational_stats as os ON + h.object_id=os.object_id and h.index_id=os.index_id and h.partition_number=os.partition_number + group by h.database_id, h.object_id, h.sname, h.index_id, h.partition_number, h.partition_id, h.row_count, h.reserved_MB, h.reserved_LOB_MB, h.reserved_row_overflow_MB, h.lock_escalation_desc, h.data_compression_desc + END; --End Check For @SkipPartitions = 0 @@ -25929,7 +26079,7 @@ BEGIN INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns c ON rg.object_id = c.object_id INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partitions p ON rg.object_id = p.object_id AND rg.partition_number = p.partition_number INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.index_columns ic on ic.column_id = c.column_id AND ic.object_id = c.object_id AND ic.index_id = p.index_id - LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_column_store_row_group_physical_stats phys ON rg.row_group_id = phys.row_group_id AND rg.object_id = phys.object_id AND rg.partition_number = phys.partition_number AND p.index_id = phys.index_id ' + CASE WHEN @ShowPartitionRanges = 1 THEN N' + LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_column_store_row_group_physical_stats phys ON rg.row_group_id = phys.row_group_id AND rg.object_id = phys.object_id AND rg.partition_number = phys.partition_number AND rg.index_id = phys.index_id ' + CASE WHEN @ShowPartitionRanges = 1 THEN N' LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.indexes i ON i.object_id = rg.object_id AND i.index_id = rg.index_id LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partition_schemes ps ON ps.data_space_id = i.data_space_id LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partition_functions pf ON pf.function_id = ps.function_id @@ -29116,7 +29266,8 @@ BEGIN END; /* End @Mode=3 (index detail)*/ - + SET @d = CONVERT(VARCHAR(19), GETDATE(), 121); + RAISERROR (N'finishing at %s',0,1, @d) WITH NOWAIT; END /* End @TableName IS NULL (mode 0/1/2/3/4) */ END TRY @@ -29174,7 +29325,7 @@ BEGIN SET XACT_ABORT OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.18', @VersionDate = '20231222'; + SELECT @Version = '8.19', @VersionDate = '20240222'; IF @VersionCheckMode = 1 BEGIN @@ -33329,7 +33480,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.18', @VersionDate = '20231222'; +SELECT @Version = '8.19', @VersionDate = '20240222'; IF(@VersionCheckMode = 1) BEGIN RETURN; @@ -39378,7 +39529,7 @@ BEGIN SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.18', @VersionDate = '20231222'; + SELECT @Version = '8.19', @VersionDate = '20240222'; IF(@VersionCheckMode = 1) BEGIN @@ -40771,14 +40922,16 @@ ALTER PROCEDURE [dbo].[sp_DatabaseRestore] @Help BIT = 0, @Version VARCHAR(30) = NULL OUTPUT, @VersionDate DATETIME = NULL OUTPUT, - @VersionCheckMode BIT = 0 + @VersionCheckMode BIT = 0, + @FileNamePrefix NVARCHAR(260) = NULL, + @RunStoredProcAfterRestore NVARCHAR(260) = NULL AS SET NOCOUNT ON; SET STATISTICS XML OFF; /*Versioning details*/ -SELECT @Version = '8.18', @VersionDate = '20231222'; +SELECT @Version = '8.19', @VersionDate = '20240222'; IF(@VersionCheckMode = 1) BEGIN @@ -41524,7 +41677,7 @@ BEGIN WHEN Type = 'L' THEN @MoveLogDrive WHEN Type = 'S' THEN @MoveFilestreamDrive WHEN Type = 'F' THEN @MoveFullTextCatalogDrive - END + CASE + END + COALESCE(@FileNamePrefix, '') + CASE WHEN @Database = @RestoreDatabaseName THEN REVERSE(LEFT(REVERSE(PhysicalName), CHARINDEX('\', REVERSE(PhysicalName), 1) -1)) ELSE REPLACE(REVERSE(LEFT(REVERSE(PhysicalName), CHARINDEX('\', REVERSE(PhysicalName), 1) -1)), @Database, SUBSTRING(@RestoreDatabaseName, 2, LEN(@RestoreDatabaseName) -2)) END AS TargetPhysicalName, @@ -42366,6 +42519,28 @@ END;' EXECUTE [dbo].[CommandExecute] @DatabaseContext = 'master', @Command = @sql, @CommandType = 'UPDATE', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y'; END; +IF @RunStoredProcAfterRestore IS NOT NULL AND LEN(LTRIM(@RunStoredProcAfterRestore)) > 0 +BEGIN + PRINT 'Attempting to run ' + @RunStoredProcAfterRestore + SET @sql = N'EXEC ' + @RestoreDatabaseName + '.' + @RunStoredProcAfterRestore + + IF @Debug = 1 OR @Execute = 'N' + BEGIN + IF @sql IS NULL PRINT '@sql is NULL when building for @RunStoredProcAfterRestore' + PRINT @sql + END + + IF @RunRecovery = 0 + BEGIN + PRINT 'Unable to run Run Stored Procedure After Restore as database is not recovered. Run command again with @RunRecovery = 1' + END + ELSE + BEGIN + IF @Debug IN (0, 1) AND @Execute = 'Y' + EXEC sp_executesql @sql + END +END + -- If test restore then blow the database away (be careful) IF @TestRestore = 1 BEGIN @@ -42425,7 +42600,7 @@ BEGIN SET NOCOUNT ON; SET STATISTICS XML OFF; - SELECT @Version = '8.18', @VersionDate = '20231222'; + SELECT @Version = '8.19', @VersionDate = '20240222'; IF(@VersionCheckMode = 1) BEGIN @@ -42787,6 +42962,8 @@ DELETE FROM dbo.SqlServerVersions; INSERT INTO dbo.SqlServerVersions (MajorVersionNumber, MinorVersionNumber, Branch, [Url], ReleaseDate, MainstreamSupportEndDate, ExtendedSupportEndDate, MajorVersionName, MinorVersionName) VALUES + (16, 4105, 'CU11', 'https://support.microsoft.com/en-us/help/5032679', '2024-01-11', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 11'), + (16, 4100, 'CU10 GDR', 'https://support.microsoft.com/en-us/help/5033592', '2024-01-09', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 10 GDR'), (16, 4095, 'CU10', 'https://support.microsoft.com/en-us/help/5031778', '2023-11-16', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 10'), (16, 4085, 'CU9', 'https://support.microsoft.com/en-us/help/5030731', '2023-10-12', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 9'), (16, 4075, 'CU8', 'https://support.microsoft.com/en-us/help/5029666', '2023-09-14', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 8'), @@ -42799,6 +42976,7 @@ VALUES (16, 4003, 'CU1', 'https://support.microsoft.com/en-us/help/5022375', '2023-02-16', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 1'), (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'), + (15, 4355, 'CU25', 'https://support.microsoft.com/kb/5033688', '2023-02-15', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 25'), (15, 4345, 'CU24', 'https://support.microsoft.com/kb/5031908', '2023-12-14', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 24'), (15, 4335, 'CU23', 'https://support.microsoft.com/kb/5030333', '2023-10-12', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 23'), (15, 4322, 'CU22', 'https://support.microsoft.com/kb/5027702', '2023-08-14', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 22'), @@ -43226,7 +43404,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.18', @VersionDate = '20231222'; +SELECT @Version = '8.19', @VersionDate = '20240222'; IF(@VersionCheckMode = 1) BEGIN diff --git a/Install-Core-Blitz-No-Query-Store.sql b/Install-Core-Blitz-No-Query-Store.sql index 58cd05196..eb2b1df80 100644 --- a/Install-Core-Blitz-No-Query-Store.sql +++ b/Install-Core-Blitz-No-Query-Store.sql @@ -38,7 +38,7 @@ AS SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.18', @VersionDate = '20231222'; + SELECT @Version = '8.19', @VersionDate = '20240222'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -201,6 +201,7 @@ AS ,@SkipModel bit = 0 ,@SkipTempDB bit = 0 ,@SkipValidateLogins bit = 0 + ,@SkipGetAlertInfo bit = 0 DECLARE @db_perms table @@ -226,6 +227,23 @@ AS /* End of declarations for First Responder Kit consistency check:*/ ; + /* Create temp table for check 73 */ + IF OBJECT_ID('tempdb..#AlertInfo') IS NOT NULL + EXEC sp_executesql N'DROP TABLE #AlertInfo;'; + + CREATE TABLE #AlertInfo + ( + FailSafeOperator NVARCHAR(255) , + NotificationMethod INT , + ForwardingServer NVARCHAR(255) , + ForwardingSeverity INT , + PagerToTemplate NVARCHAR(255) , + PagerCCTemplate NVARCHAR(255) , + PagerSubjectTemplate NVARCHAR(255) , + PagerSendSubjectOnly NVARCHAR(255) , + ForwardAlways INT + ); + /* Create temp table for check 2301 */ IF OBJECT_ID('tempdb..#InvalidLogins') IS NOT NULL EXEC sp_executesql N'DROP TABLE #InvalidLogins;'; @@ -315,6 +333,20 @@ AS END CATCH; END; /*Need execute on sp_validatelogins*/ + IF ISNULL(@SkipGetAlertInfo, 0) != 1 /*If @SkipGetAlertInfo hasn't been set to 1 by the caller*/ + BEGIN + BEGIN TRY + /* Try to fill the table for check 73 */ + INSERT INTO #AlertInfo + EXEC [master].[dbo].[sp_MSgetalertinfo] @includeaddresses = 0; + + SET @SkipGetAlertInfo = 0; /*We can execute sp_MSgetalertinfo*/ + END TRY + BEGIN CATCH + SET @SkipGetAlertInfo = 1; /*We have don't have execute rights or sp_MSgetalertinfo throws an error so skip it*/ + END CATCH; + END; /*Need execute on sp_MSgetalertinfo*/ + IF ISNULL(@SkipModel, 0) != 1 /*If @SkipModel hasn't been set to 1 by the caller*/ BEGIN IF EXISTS @@ -627,7 +659,7 @@ AS SELECT v.* FROM (VALUES(NULL, 211, NULL)) AS v (DatabaseName, CheckID, ServerName) /*xp_regread*/ - WHERE @SkipXPRegRead = 1; + WHERE @sa = 0; INSERT #SkipChecks (DatabaseName, CheckID, ServerName) SELECT @@ -639,7 +671,13 @@ AS SELECT v.* FROM (VALUES(NULL, 2301, NULL)) AS v (DatabaseName, CheckID, ServerName) /*sp_validatelogins*/ - WHERE @SkipValidateLogins = 1 + WHERE @SkipValidateLogins = 1; + + INSERT #SkipChecks (DatabaseName, CheckID, ServerName) + SELECT + v.* + FROM (VALUES(NULL, 73, NULL)) AS v (DatabaseName, CheckID, ServerName) /*sp_validatelogins*/ + WHERE @SkipGetAlertInfo = 1; IF @sa = 0 BEGIN @@ -840,6 +878,8 @@ AS INSERT INTO #SkipChecks (CheckID, DatabaseName) VALUES (80, 'model'); /* Max file size set */ INSERT INTO #SkipChecks (CheckID, DatabaseName) VALUES (80, 'msdb'); /* Max file size set */ INSERT INTO #SkipChecks (CheckID, DatabaseName) VALUES (80, 'tempdb'); /* Max file size set */ + INSERT INTO #SkipChecks (CheckID) VALUES (224); /* CheckID 224 - Performance - SSRS/SSAS/SSIS Installed */ + INSERT INTO #SkipChecks (CheckID) VALUES (92); /* CheckID 92 - drive space */ INSERT INTO #BlitzResults ( CheckID , Priority , @@ -1160,7 +1200,7 @@ AS IF @BringThePain = 0 AND 50 <= (SELECT COUNT(*) FROM sys.databases) AND @CheckUserDatabaseObjects = 1 BEGIN SET @CheckUserDatabaseObjects = 0; - PRINT 'Running sp_Blitz @CheckUserDatabaseObjects = 1 on a server with 50+ databases may cause temporary insanity for the server and/or user.'; + PRINT 'Running sp_Blitz @CheckUserDatabaseObjects = 1 on a server with 50+ databases may cause temporary problems for the server and/or user.'; PRINT 'If you''re sure you want to do this, run again with the parameter @BringThePain = 1.'; INSERT INTO #BlitzResults ( CheckID , @@ -3394,23 +3434,6 @@ AS IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 53) WITH NOWAIT; - --INSERT INTO #BlitzResults - -- ( CheckID , - -- Priority , - -- FindingsGroup , - -- Finding , - -- URL , - -- Details - -- ) - -- SELECT TOP 1 - -- 53 AS CheckID , - -- 200 AS Priority , - -- 'Informational' AS FindingsGroup , - -- 'Cluster Node' AS Finding , - -- 'https://BrentOzar.com/go/node' AS URL , - -- 'This is a node in a cluster.' AS Details - -- FROM sys.dm_os_cluster_nodes; - DECLARE @AOFCI AS INT, @AOAG AS INT, @HAType AS VARCHAR(10), @errmsg AS VARCHAR(200) SELECT @AOAG = CAST(SERVERPROPERTY('IsHadrEnabled') AS INT) @@ -3441,7 +3464,7 @@ AS Details ) - SELECT 53 AS CheckID , + SELECT DISTINCT 53 AS CheckID , 200 AS Priority , 'Informational' AS FindingsGroup , 'Cluster Node' AS Finding , @@ -3458,7 +3481,7 @@ AS URL , Details ) - SELECT 53 AS CheckID , + SELECT DISTINCT 53 AS CheckID , 200 AS Priority , 'Informational' AS FindingsGroup , 'Cluster Node Info' AS Finding , @@ -3484,7 +3507,7 @@ AS URL , Details ) - SELECT 53 AS CheckID , + SELECT DISTINCT 53 AS CheckID , 200 AS Priority , 'Informational' AS FindingsGroup , 'Cluster Node Info' AS Finding , @@ -3509,7 +3532,7 @@ AS URL , Details ) - SELECT 53 AS CheckID , + SELECT DISTINCT 53 AS CheckID , 200 AS Priority , 'Informational' AS FindingsGroup , 'Cluster Node Info' AS Finding , @@ -4820,6 +4843,10 @@ AS FROM sys.all_columns WHERE name = 'is_memory_optimized_elevate_to_snapshot_on' AND object_id = OBJECT_ID('sys.databases') AND SERVERPROPERTY('EngineEdition') <> 8; /* Hekaton is always enabled in Managed Instances per https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/1919 */ + INSERT INTO #DatabaseDefaults + SELECT 'is_accelerated_database_recovery_on', 0, 145, 210, 'Acclerated Database Recovery Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL + FROM sys.all_columns + WHERE name = 'is_accelerated_database_recovery_on' AND object_id = OBJECT_ID('sys.databases') AND SERVERPROPERTY('EngineEdition') NOT IN (5, 8) ; DECLARE DatabaseDefaultsLoop CURSOR FOR SELECT name, DefaultValue, CheckID, Priority, Finding, URL, Details @@ -8211,20 +8238,6 @@ IF @ProductVersionMajor >= 10 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 73) WITH NOWAIT; - DECLARE @AlertInfo TABLE - ( - FailSafeOperator NVARCHAR(255) , - NotificationMethod INT , - ForwardingServer NVARCHAR(255) , - ForwardingSeverity INT , - PagerToTemplate NVARCHAR(255) , - PagerCCTemplate NVARCHAR(255) , - PagerSubjectTemplate NVARCHAR(255) , - PagerSendSubjectOnly NVARCHAR(255) , - ForwardAlways INT - ); - INSERT INTO @AlertInfo - EXEC [master].[dbo].[sp_MSgetalertinfo] @includeaddresses = 0; INSERT INTO #BlitzResults ( CheckID , Priority , @@ -8239,7 +8252,7 @@ IF @ProductVersionMajor >= 10 'No Failsafe Operator Configured' AS Finding , 'https://www.brentozar.com/go/failsafe' AS URL , ( 'No failsafe operator is configured on this server. This is a good idea just in-case there are issues with the [msdb] database that prevents alerting.' ) AS Details - FROM @AlertInfo + FROM #AlertInfo WHERE FailSafeOperator IS NULL; END; @@ -10046,6 +10059,11 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 EXEC sp_executesql N'DROP TABLE #InvalidLogins;'; END; + IF OBJECT_ID('tempdb..#AlertInfo') IS NOT NULL + BEGIN + EXEC sp_executesql N'DROP TABLE #AlertInfo;'; + END; + /* Reset the Nmumeric_RoundAbort session state back to enabled if it was disabled earlier. See Github issue #2302 for more info. @@ -10105,7 +10123,7 @@ AS SET NOCOUNT ON; SET STATISTICS XML OFF; -SELECT @Version = '8.18', @VersionDate = '20231222'; +SELECT @Version = '8.19', @VersionDate = '20240222'; IF(@VersionCheckMode = 1) BEGIN @@ -10983,7 +11001,7 @@ AS SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.18', @VersionDate = '20231222'; + SELECT @Version = '8.19', @VersionDate = '20240222'; IF(@VersionCheckMode = 1) BEGIN @@ -12765,7 +12783,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.18', @VersionDate = '20231222'; +SELECT @Version = '8.19', @VersionDate = '20240222'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -12952,7 +12970,7 @@ IF @Help = 1 UNION ALL SELECT N'@MinutesBack', N'INT', - N'How many minutes back to begin plan cache analysis. If you put in a positive number, we''ll flip it to negtive.'; + N'How many minutes back to begin plan cache analysis. If you put in a positive number, we''ll flip it to negative.'; /* Column definitions */ @@ -20124,7 +20142,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.18', @VersionDate = '20231222'; +SELECT @Version = '8.19', @VersionDate = '20240222'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -20153,7 +20171,7 @@ Known limitations of this version: filegroup/partition scheme etc.) -- (The compression and filegroup index create syntax is not trivial because it is set at the partition level and is not trivial to code.) - - Does not advise you about data modeling for clustered indexes and primary keys (primarily looks for signs of insanity.) + - Does not advise you about data modeling for clustered indexes and primary keys (primarily looks for signs of problems.) Unknown limitations of this version: - We knew them once, but we forgot. @@ -20329,9 +20347,14 @@ IF OBJECT_ID('tempdb..#CheckConstraints') IS NOT NULL IF OBJECT_ID('tempdb..#FilteredIndexes') IS NOT NULL DROP TABLE #FilteredIndexes; - + IF OBJECT_ID('tempdb..#Ignore_Databases') IS NOT NULL DROP TABLE #Ignore_Databases + +IF OBJECT_ID('tempdb..#dm_db_partition_stats_etc') IS NOT NULL + DROP TABLE #dm_db_partition_stats_etc +IF OBJECT_ID('tempdb..#dm_db_index_operational_stats') IS NOT NULL + DROP TABLE #dm_db_index_operational_stats RAISERROR (N'Create temp tables.',0,1) WITH NOWAIT; CREATE TABLE #BlitzIndexResults @@ -20995,7 +21018,7 @@ BEGIN TRY VALUES ( 1, 0, N'You''re trying to run sp_BlitzIndex on a server with ' + CAST(@NumDatabases AS NVARCHAR(8)) + N' databases. ', - N'Running sp_BlitzIndex on a server with 50+ databases may cause temporary insanity for the server and/or user.', + N'Running sp_BlitzIndex on a server with 50+ databases may cause temporary problems for the server and/or user.', N'If you''re sure you want to do this, run again with the parameter @BringThePain = 1.', 'http://FirstResponderKit.org', '', @@ -21022,7 +21045,7 @@ BEGIN TRY bir.create_tsql, bir.more_info FROM #BlitzIndexResults AS bir; - RAISERROR('Running sp_BlitzIndex on a server with 50+ databases may cause temporary insanity for the server', 12, 1); + RAISERROR('Running sp_BlitzIndex on a server with 50+ databases may cause temporary problems for the server', 12, 1); END; RETURN; @@ -21141,6 +21164,8 @@ FROM sys.databases ---------------------------------------- BEGIN TRY BEGIN + DECLARE @d VARCHAR(19) = CONVERT(VARCHAR(19), GETDATE(), 121); + RAISERROR (N'starting at %s',0,1, @d) WITH NOWAIT; --Validate SQL Server Version @@ -21511,47 +21536,79 @@ BEGIN TRY --NOTE: If you want to use the newer syntax for 2012+, you'll have to change 2147483647 to 11 on line ~819 --This change was made because on a table with lots of paritions, the OUTER APPLY was crazy slow. - SET @dsql = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + + -- get relevant columns from sys.dm_db_partition_stats, sys.partitions and sys.objects + DROP TABLE if exists #dm_db_partition_stats_etc + create table #dm_db_partition_stats_etc + ( + database_id smallint not null + , object_id int not null + , sname sysname NULL + , index_id int + , partition_number int + , partition_id bigint + , row_count bigint + , reserved_MB bigint + , reserved_LOB_MB bigint + , reserved_row_overflow_MB bigint + , lock_escalation_desc nvarchar(60) + , data_compression_desc nvarchar(60) + ) + + -- get relevant info from sys.dm_db_index_operational_stats + drop TABLE if exists #dm_db_index_operational_stats + create table #dm_db_index_operational_stats + ( + database_id smallint not null + , object_id int not null + , index_id int + , partition_number int + , hobt_id bigint + , leaf_insert_count bigint + , leaf_delete_count bigint + , leaf_update_count bigint + , range_scan_count bigint + , singleton_lookup_count bigint + , forwarded_fetch_count bigint + , lob_fetch_in_pages bigint + , lob_fetch_in_bytes bigint + , row_overflow_fetch_in_pages bigint + , row_overflow_fetch_in_bytes bigint + , row_lock_count bigint + , row_lock_wait_count bigint + , row_lock_wait_in_ms bigint + , page_lock_count bigint + , page_lock_wait_count bigint + , page_lock_wait_in_ms bigint + , index_lock_promotion_attempt_count bigint + , index_lock_promotion_count bigint + , page_latch_wait_count bigint + , page_latch_wait_in_ms bigint + , page_io_latch_wait_count bigint + , page_io_latch_wait_in_ms bigint + ) + + SET @dsql = N' + DECLARE @d VARCHAR(19) = CONVERT(VARCHAR(19), GETDATE(), 121) + RAISERROR (N''start getting data into #dm_db_partition_stats_etc at %s'',0,1, @d) WITH NOWAIT; + SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + INSERT INTO #dm_db_partition_stats_etc + ( + database_id, object_id, sname, index_id, partition_number, partition_id, row_count, reserved_MB, reserved_LOB_MB, reserved_row_overflow_MB, lock_escalation_desc, data_compression_desc + ) SELECT ' + CAST(@DatabaseID AS NVARCHAR(10)) + N' AS database_id, ps.object_id, - s.name, + s.name as sname, ps.index_id, ps.partition_number, + ps.partition_id, ps.row_count, ps.reserved_page_count * 8. / 1024. AS reserved_MB, ps.lob_reserved_page_count * 8. / 1024. AS reserved_LOB_MB, ps.row_overflow_reserved_page_count * 8. / 1024. AS reserved_row_overflow_MB, le.lock_escalation_desc, - ' + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N'par.data_compression_desc ' ELSE N'null as data_compression_desc ' END + N', - SUM(os.leaf_insert_count), - SUM(os.leaf_delete_count), - SUM(os.leaf_update_count), - SUM(os.range_scan_count), - SUM(os.singleton_lookup_count), - SUM(os.forwarded_fetch_count), - SUM(os.lob_fetch_in_pages), - SUM(os.lob_fetch_in_bytes), - SUM(os.row_overflow_fetch_in_pages), - SUM(os.row_overflow_fetch_in_bytes), - SUM(os.row_lock_count), - SUM(os.row_lock_wait_count), - SUM(os.row_lock_wait_in_ms), - SUM(os.page_lock_count), - SUM(os.page_lock_wait_count), - SUM(os.page_lock_wait_in_ms), - SUM(os.index_lock_promotion_attempt_count), - SUM(os.index_lock_promotion_count), - SUM(os.page_latch_wait_count), - SUM(os.page_latch_wait_in_ms), - SUM(os.page_io_latch_wait_count), - SUM(os.page_io_latch_wait_in_ms), '; - - /* Get columnstore dictionary size - more info: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2585 */ - IF EXISTS (SELECT * FROM sys.all_objects WHERE name = 'column_store_dictionaries') - SET @dsql = @dsql + N' COALESCE((SELECT SUM (on_disk_size / 1024.0 / 1024) FROM ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_dictionaries dict WHERE dict.partition_id = ps.partition_id),0) AS reserved_dictionary_MB '; - ELSE - SET @dsql = @dsql + N' 0 AS reserved_dictionary_MB '; - + ' + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N'par.data_compression_desc ' ELSE N'null as data_compression_desc ' END + N' +'; SET @dsql = @dsql + N' FROM ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_partition_stats AS ps @@ -21560,9 +21617,6 @@ BEGIN TRY AND so.is_ms_shipped = 0 /*Exclude objects shipped by Microsoft*/ AND so.type <> ''TF'' /*Exclude table valued functions*/ JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s ON s.schema_id = so.schema_id - LEFT JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_index_operational_stats(' - + CAST(@DatabaseID AS NVARCHAR(10)) + N', NULL, NULL,NULL) AS os ON - ps.object_id=os.object_id and ps.index_id=os.index_id and ps.partition_number=os.partition_number OUTER APPLY (SELECT st.lock_escalation_desc FROM ' + QUOTENAME(@DatabaseName) + N'.sys.tables st WHERE st.object_id = ps.object_id @@ -21582,7 +21636,75 @@ BEGIN TRY le.lock_escalation_desc, ' + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N'par.data_compression_desc ' ELSE N'null as data_compression_desc ' END + N' ORDER BY ps.object_id, ps.index_id, ps.partition_number - OPTION ( RECOMPILE ); + /*OPTION ( RECOMPILE );*/ + OPTION ( RECOMPILE , min_grant_percent = 1); + + SET @d = CONVERT(VARCHAR(19), GETDATE(), 121) + RAISERROR (N''start getting data into #dm_db_index_operational_stats at %s.'',0,1, @d) WITH NOWAIT; + + insert into #dm_db_index_operational_stats + ( + database_id + , object_id + , index_id + , partition_number + , hobt_id + , leaf_insert_count + , leaf_delete_count + , leaf_update_count + , range_scan_count + , singleton_lookup_count + , forwarded_fetch_count + , lob_fetch_in_pages + , lob_fetch_in_bytes + , row_overflow_fetch_in_pages + , row_overflow_fetch_in_bytes + , row_lock_count + , row_lock_wait_count + , row_lock_wait_in_ms + , page_lock_count + , page_lock_wait_count + , page_lock_wait_in_ms + , index_lock_promotion_attempt_count + , index_lock_promotion_count + , page_latch_wait_count + , page_latch_wait_in_ms + , page_io_latch_wait_count + , page_io_latch_wait_in_ms + ) + + select os.database_id + , os.object_id + , os.index_id + , os.partition_number + , os.hobt_id + , os.leaf_insert_count + , os.leaf_delete_count + , os.leaf_update_count + , os.range_scan_count + , os.singleton_lookup_count + , os.forwarded_fetch_count + , os.lob_fetch_in_pages + , os.lob_fetch_in_bytes + , os.row_overflow_fetch_in_pages + , os.row_overflow_fetch_in_bytes + , os.row_lock_count + , os.row_lock_wait_count + , os.row_lock_wait_in_ms + , os.page_lock_count + , os.page_lock_wait_count + , os.page_lock_wait_in_ms + , os.index_lock_promotion_attempt_count + , os.index_lock_promotion_count + , os.page_latch_wait_count + , os.page_latch_wait_in_ms + , os.page_io_latch_wait_count + , os.page_io_latch_wait_in_ms + from ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_index_operational_stats('+ CAST(@DatabaseID AS NVARCHAR(10)) +', NULL, NULL,NULL) AS os + OPTION ( RECOMPILE , min_grant_percent = 1); + + SET @d = CONVERT(VARCHAR(19), GETDATE(), 121) + RAISERROR (N''finished getting data into #dm_db_index_operational_stats at %s.'',0,1, @d) WITH NOWAIT; '; END; ELSE @@ -21681,6 +21803,7 @@ BEGIN TRY PRINT SUBSTRING(@dsql, 32000, 36000); PRINT SUBSTRING(@dsql, 36000, 40000); END; + EXEC sp_executesql @dsql; INSERT #IndexPartitionSanity ( [database_id], [object_id], [schema_name], @@ -21715,8 +21838,35 @@ BEGIN TRY page_io_latch_wait_count, page_io_latch_wait_in_ms, reserved_dictionary_MB) - EXEC sp_executesql @dsql; - + select h.database_id, h.object_id, h.sname, h.index_id, h.partition_number, h.row_count, h.reserved_MB, h.reserved_LOB_MB, h.reserved_row_overflow_MB, h.lock_escalation_desc, h.data_compression_desc, + SUM(os.leaf_insert_count), + SUM(os.leaf_delete_count), + SUM(os.leaf_update_count), + SUM(os.range_scan_count), + SUM(os.singleton_lookup_count), + SUM(os.forwarded_fetch_count), + SUM(os.lob_fetch_in_pages), + SUM(os.lob_fetch_in_bytes), + SUM(os.row_overflow_fetch_in_pages), + SUM(os.row_overflow_fetch_in_bytes), + SUM(os.row_lock_count), + SUM(os.row_lock_wait_count), + SUM(os.row_lock_wait_in_ms), + SUM(os.page_lock_count), + SUM(os.page_lock_wait_count), + SUM(os.page_lock_wait_in_ms), + SUM(os.index_lock_promotion_attempt_count), + SUM(os.index_lock_promotion_count), + SUM(os.page_latch_wait_count), + SUM(os.page_latch_wait_in_ms), + SUM(os.page_io_latch_wait_count), + SUM(os.page_io_latch_wait_in_ms) + ,COALESCE((SELECT SUM (dict.on_disk_size / 1024.0 / 1024) FROM sys.column_store_dictionaries dict WHERE dict.partition_id = h.partition_id),0) AS reserved_dictionary_MB + from #dm_db_partition_stats_etc h + left JOIN #dm_db_index_operational_stats as os ON + h.object_id=os.object_id and h.index_id=os.index_id and h.partition_number=os.partition_number + group by h.database_id, h.object_id, h.sname, h.index_id, h.partition_number, h.partition_id, h.row_count, h.reserved_MB, h.reserved_LOB_MB, h.reserved_row_overflow_MB, h.lock_escalation_desc, h.data_compression_desc + END; --End Check For @SkipPartitions = 0 @@ -23067,7 +23217,7 @@ BEGIN INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns c ON rg.object_id = c.object_id INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partitions p ON rg.object_id = p.object_id AND rg.partition_number = p.partition_number INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.index_columns ic on ic.column_id = c.column_id AND ic.object_id = c.object_id AND ic.index_id = p.index_id - LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_column_store_row_group_physical_stats phys ON rg.row_group_id = phys.row_group_id AND rg.object_id = phys.object_id AND rg.partition_number = phys.partition_number AND p.index_id = phys.index_id ' + CASE WHEN @ShowPartitionRanges = 1 THEN N' + LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_column_store_row_group_physical_stats phys ON rg.row_group_id = phys.row_group_id AND rg.object_id = phys.object_id AND rg.partition_number = phys.partition_number AND rg.index_id = phys.index_id ' + CASE WHEN @ShowPartitionRanges = 1 THEN N' LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.indexes i ON i.object_id = rg.object_id AND i.index_id = rg.index_id LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partition_schemes ps ON ps.data_space_id = i.data_space_id LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partition_functions pf ON pf.function_id = ps.function_id @@ -26254,7 +26404,8 @@ BEGIN END; /* End @Mode=3 (index detail)*/ - + SET @d = CONVERT(VARCHAR(19), GETDATE(), 121); + RAISERROR (N'finishing at %s',0,1, @d) WITH NOWAIT; END /* End @TableName IS NULL (mode 0/1/2/3/4) */ END TRY @@ -26312,7 +26463,7 @@ BEGIN SET XACT_ABORT OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.18', @VersionDate = '20231222'; + SELECT @Version = '8.19', @VersionDate = '20240222'; IF @VersionCheckMode = 1 BEGIN @@ -30443,7 +30594,7 @@ BEGIN SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.18', @VersionDate = '20231222'; + SELECT @Version = '8.19', @VersionDate = '20240222'; IF(@VersionCheckMode = 1) BEGIN @@ -31839,6 +31990,8 @@ DELETE FROM dbo.SqlServerVersions; INSERT INTO dbo.SqlServerVersions (MajorVersionNumber, MinorVersionNumber, Branch, [Url], ReleaseDate, MainstreamSupportEndDate, ExtendedSupportEndDate, MajorVersionName, MinorVersionName) VALUES + (16, 4105, 'CU11', 'https://support.microsoft.com/en-us/help/5032679', '2024-01-11', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 11'), + (16, 4100, 'CU10 GDR', 'https://support.microsoft.com/en-us/help/5033592', '2024-01-09', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 10 GDR'), (16, 4095, 'CU10', 'https://support.microsoft.com/en-us/help/5031778', '2023-11-16', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 10'), (16, 4085, 'CU9', 'https://support.microsoft.com/en-us/help/5030731', '2023-10-12', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 9'), (16, 4075, 'CU8', 'https://support.microsoft.com/en-us/help/5029666', '2023-09-14', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 8'), @@ -31851,6 +32004,7 @@ VALUES (16, 4003, 'CU1', 'https://support.microsoft.com/en-us/help/5022375', '2023-02-16', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 1'), (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'), + (15, 4355, 'CU25', 'https://support.microsoft.com/kb/5033688', '2023-02-15', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 25'), (15, 4345, 'CU24', 'https://support.microsoft.com/kb/5031908', '2023-12-14', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 24'), (15, 4335, 'CU23', 'https://support.microsoft.com/kb/5030333', '2023-10-12', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 23'), (15, 4322, 'CU22', 'https://support.microsoft.com/kb/5027702', '2023-08-14', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 22'), @@ -32278,7 +32432,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.18', @VersionDate = '20231222'; +SELECT @Version = '8.19', @VersionDate = '20240222'; IF(@VersionCheckMode = 1) BEGIN diff --git a/Install-Core-Blitz-With-Query-Store.sql b/Install-Core-Blitz-With-Query-Store.sql index 2724cc926..44c08d66c 100644 --- a/Install-Core-Blitz-With-Query-Store.sql +++ b/Install-Core-Blitz-With-Query-Store.sql @@ -38,7 +38,7 @@ AS SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.18', @VersionDate = '20231222'; + SELECT @Version = '8.19', @VersionDate = '20240222'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -201,6 +201,7 @@ AS ,@SkipModel bit = 0 ,@SkipTempDB bit = 0 ,@SkipValidateLogins bit = 0 + ,@SkipGetAlertInfo bit = 0 DECLARE @db_perms table @@ -226,6 +227,23 @@ AS /* End of declarations for First Responder Kit consistency check:*/ ; + /* Create temp table for check 73 */ + IF OBJECT_ID('tempdb..#AlertInfo') IS NOT NULL + EXEC sp_executesql N'DROP TABLE #AlertInfo;'; + + CREATE TABLE #AlertInfo + ( + FailSafeOperator NVARCHAR(255) , + NotificationMethod INT , + ForwardingServer NVARCHAR(255) , + ForwardingSeverity INT , + PagerToTemplate NVARCHAR(255) , + PagerCCTemplate NVARCHAR(255) , + PagerSubjectTemplate NVARCHAR(255) , + PagerSendSubjectOnly NVARCHAR(255) , + ForwardAlways INT + ); + /* Create temp table for check 2301 */ IF OBJECT_ID('tempdb..#InvalidLogins') IS NOT NULL EXEC sp_executesql N'DROP TABLE #InvalidLogins;'; @@ -315,6 +333,20 @@ AS END CATCH; END; /*Need execute on sp_validatelogins*/ + IF ISNULL(@SkipGetAlertInfo, 0) != 1 /*If @SkipGetAlertInfo hasn't been set to 1 by the caller*/ + BEGIN + BEGIN TRY + /* Try to fill the table for check 73 */ + INSERT INTO #AlertInfo + EXEC [master].[dbo].[sp_MSgetalertinfo] @includeaddresses = 0; + + SET @SkipGetAlertInfo = 0; /*We can execute sp_MSgetalertinfo*/ + END TRY + BEGIN CATCH + SET @SkipGetAlertInfo = 1; /*We have don't have execute rights or sp_MSgetalertinfo throws an error so skip it*/ + END CATCH; + END; /*Need execute on sp_MSgetalertinfo*/ + IF ISNULL(@SkipModel, 0) != 1 /*If @SkipModel hasn't been set to 1 by the caller*/ BEGIN IF EXISTS @@ -627,7 +659,7 @@ AS SELECT v.* FROM (VALUES(NULL, 211, NULL)) AS v (DatabaseName, CheckID, ServerName) /*xp_regread*/ - WHERE @SkipXPRegRead = 1; + WHERE @sa = 0; INSERT #SkipChecks (DatabaseName, CheckID, ServerName) SELECT @@ -639,7 +671,13 @@ AS SELECT v.* FROM (VALUES(NULL, 2301, NULL)) AS v (DatabaseName, CheckID, ServerName) /*sp_validatelogins*/ - WHERE @SkipValidateLogins = 1 + WHERE @SkipValidateLogins = 1; + + INSERT #SkipChecks (DatabaseName, CheckID, ServerName) + SELECT + v.* + FROM (VALUES(NULL, 73, NULL)) AS v (DatabaseName, CheckID, ServerName) /*sp_validatelogins*/ + WHERE @SkipGetAlertInfo = 1; IF @sa = 0 BEGIN @@ -840,6 +878,8 @@ AS INSERT INTO #SkipChecks (CheckID, DatabaseName) VALUES (80, 'model'); /* Max file size set */ INSERT INTO #SkipChecks (CheckID, DatabaseName) VALUES (80, 'msdb'); /* Max file size set */ INSERT INTO #SkipChecks (CheckID, DatabaseName) VALUES (80, 'tempdb'); /* Max file size set */ + INSERT INTO #SkipChecks (CheckID) VALUES (224); /* CheckID 224 - Performance - SSRS/SSAS/SSIS Installed */ + INSERT INTO #SkipChecks (CheckID) VALUES (92); /* CheckID 92 - drive space */ INSERT INTO #BlitzResults ( CheckID , Priority , @@ -1160,7 +1200,7 @@ AS IF @BringThePain = 0 AND 50 <= (SELECT COUNT(*) FROM sys.databases) AND @CheckUserDatabaseObjects = 1 BEGIN SET @CheckUserDatabaseObjects = 0; - PRINT 'Running sp_Blitz @CheckUserDatabaseObjects = 1 on a server with 50+ databases may cause temporary insanity for the server and/or user.'; + PRINT 'Running sp_Blitz @CheckUserDatabaseObjects = 1 on a server with 50+ databases may cause temporary problems for the server and/or user.'; PRINT 'If you''re sure you want to do this, run again with the parameter @BringThePain = 1.'; INSERT INTO #BlitzResults ( CheckID , @@ -3394,23 +3434,6 @@ AS IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 53) WITH NOWAIT; - --INSERT INTO #BlitzResults - -- ( CheckID , - -- Priority , - -- FindingsGroup , - -- Finding , - -- URL , - -- Details - -- ) - -- SELECT TOP 1 - -- 53 AS CheckID , - -- 200 AS Priority , - -- 'Informational' AS FindingsGroup , - -- 'Cluster Node' AS Finding , - -- 'https://BrentOzar.com/go/node' AS URL , - -- 'This is a node in a cluster.' AS Details - -- FROM sys.dm_os_cluster_nodes; - DECLARE @AOFCI AS INT, @AOAG AS INT, @HAType AS VARCHAR(10), @errmsg AS VARCHAR(200) SELECT @AOAG = CAST(SERVERPROPERTY('IsHadrEnabled') AS INT) @@ -3441,7 +3464,7 @@ AS Details ) - SELECT 53 AS CheckID , + SELECT DISTINCT 53 AS CheckID , 200 AS Priority , 'Informational' AS FindingsGroup , 'Cluster Node' AS Finding , @@ -3458,7 +3481,7 @@ AS URL , Details ) - SELECT 53 AS CheckID , + SELECT DISTINCT 53 AS CheckID , 200 AS Priority , 'Informational' AS FindingsGroup , 'Cluster Node Info' AS Finding , @@ -3484,7 +3507,7 @@ AS URL , Details ) - SELECT 53 AS CheckID , + SELECT DISTINCT 53 AS CheckID , 200 AS Priority , 'Informational' AS FindingsGroup , 'Cluster Node Info' AS Finding , @@ -3509,7 +3532,7 @@ AS URL , Details ) - SELECT 53 AS CheckID , + SELECT DISTINCT 53 AS CheckID , 200 AS Priority , 'Informational' AS FindingsGroup , 'Cluster Node Info' AS Finding , @@ -4820,6 +4843,10 @@ AS FROM sys.all_columns WHERE name = 'is_memory_optimized_elevate_to_snapshot_on' AND object_id = OBJECT_ID('sys.databases') AND SERVERPROPERTY('EngineEdition') <> 8; /* Hekaton is always enabled in Managed Instances per https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/1919 */ + INSERT INTO #DatabaseDefaults + SELECT 'is_accelerated_database_recovery_on', 0, 145, 210, 'Acclerated Database Recovery Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL + FROM sys.all_columns + WHERE name = 'is_accelerated_database_recovery_on' AND object_id = OBJECT_ID('sys.databases') AND SERVERPROPERTY('EngineEdition') NOT IN (5, 8) ; DECLARE DatabaseDefaultsLoop CURSOR FOR SELECT name, DefaultValue, CheckID, Priority, Finding, URL, Details @@ -8211,20 +8238,6 @@ IF @ProductVersionMajor >= 10 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 73) WITH NOWAIT; - DECLARE @AlertInfo TABLE - ( - FailSafeOperator NVARCHAR(255) , - NotificationMethod INT , - ForwardingServer NVARCHAR(255) , - ForwardingSeverity INT , - PagerToTemplate NVARCHAR(255) , - PagerCCTemplate NVARCHAR(255) , - PagerSubjectTemplate NVARCHAR(255) , - PagerSendSubjectOnly NVARCHAR(255) , - ForwardAlways INT - ); - INSERT INTO @AlertInfo - EXEC [master].[dbo].[sp_MSgetalertinfo] @includeaddresses = 0; INSERT INTO #BlitzResults ( CheckID , Priority , @@ -8239,7 +8252,7 @@ IF @ProductVersionMajor >= 10 'No Failsafe Operator Configured' AS Finding , 'https://www.brentozar.com/go/failsafe' AS URL , ( 'No failsafe operator is configured on this server. This is a good idea just in-case there are issues with the [msdb] database that prevents alerting.' ) AS Details - FROM @AlertInfo + FROM #AlertInfo WHERE FailSafeOperator IS NULL; END; @@ -10046,6 +10059,11 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 EXEC sp_executesql N'DROP TABLE #InvalidLogins;'; END; + IF OBJECT_ID('tempdb..#AlertInfo') IS NOT NULL + BEGIN + EXEC sp_executesql N'DROP TABLE #AlertInfo;'; + END; + /* Reset the Nmumeric_RoundAbort session state back to enabled if it was disabled earlier. See Github issue #2302 for more info. @@ -10105,7 +10123,7 @@ AS SET NOCOUNT ON; SET STATISTICS XML OFF; -SELECT @Version = '8.18', @VersionDate = '20231222'; +SELECT @Version = '8.19', @VersionDate = '20240222'; IF(@VersionCheckMode = 1) BEGIN @@ -10983,7 +11001,7 @@ AS SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.18', @VersionDate = '20231222'; + SELECT @Version = '8.19', @VersionDate = '20240222'; IF(@VersionCheckMode = 1) BEGIN @@ -12765,7 +12783,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.18', @VersionDate = '20231222'; +SELECT @Version = '8.19', @VersionDate = '20240222'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -12952,7 +12970,7 @@ IF @Help = 1 UNION ALL SELECT N'@MinutesBack', N'INT', - N'How many minutes back to begin plan cache analysis. If you put in a positive number, we''ll flip it to negtive.'; + N'How many minutes back to begin plan cache analysis. If you put in a positive number, we''ll flip it to negative.'; /* Column definitions */ @@ -20124,7 +20142,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.18', @VersionDate = '20231222'; +SELECT @Version = '8.19', @VersionDate = '20240222'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -20153,7 +20171,7 @@ Known limitations of this version: filegroup/partition scheme etc.) -- (The compression and filegroup index create syntax is not trivial because it is set at the partition level and is not trivial to code.) - - Does not advise you about data modeling for clustered indexes and primary keys (primarily looks for signs of insanity.) + - Does not advise you about data modeling for clustered indexes and primary keys (primarily looks for signs of problems.) Unknown limitations of this version: - We knew them once, but we forgot. @@ -20329,9 +20347,14 @@ IF OBJECT_ID('tempdb..#CheckConstraints') IS NOT NULL IF OBJECT_ID('tempdb..#FilteredIndexes') IS NOT NULL DROP TABLE #FilteredIndexes; - + IF OBJECT_ID('tempdb..#Ignore_Databases') IS NOT NULL DROP TABLE #Ignore_Databases + +IF OBJECT_ID('tempdb..#dm_db_partition_stats_etc') IS NOT NULL + DROP TABLE #dm_db_partition_stats_etc +IF OBJECT_ID('tempdb..#dm_db_index_operational_stats') IS NOT NULL + DROP TABLE #dm_db_index_operational_stats RAISERROR (N'Create temp tables.',0,1) WITH NOWAIT; CREATE TABLE #BlitzIndexResults @@ -20995,7 +21018,7 @@ BEGIN TRY VALUES ( 1, 0, N'You''re trying to run sp_BlitzIndex on a server with ' + CAST(@NumDatabases AS NVARCHAR(8)) + N' databases. ', - N'Running sp_BlitzIndex on a server with 50+ databases may cause temporary insanity for the server and/or user.', + N'Running sp_BlitzIndex on a server with 50+ databases may cause temporary problems for the server and/or user.', N'If you''re sure you want to do this, run again with the parameter @BringThePain = 1.', 'http://FirstResponderKit.org', '', @@ -21022,7 +21045,7 @@ BEGIN TRY bir.create_tsql, bir.more_info FROM #BlitzIndexResults AS bir; - RAISERROR('Running sp_BlitzIndex on a server with 50+ databases may cause temporary insanity for the server', 12, 1); + RAISERROR('Running sp_BlitzIndex on a server with 50+ databases may cause temporary problems for the server', 12, 1); END; RETURN; @@ -21141,6 +21164,8 @@ FROM sys.databases ---------------------------------------- BEGIN TRY BEGIN + DECLARE @d VARCHAR(19) = CONVERT(VARCHAR(19), GETDATE(), 121); + RAISERROR (N'starting at %s',0,1, @d) WITH NOWAIT; --Validate SQL Server Version @@ -21511,47 +21536,79 @@ BEGIN TRY --NOTE: If you want to use the newer syntax for 2012+, you'll have to change 2147483647 to 11 on line ~819 --This change was made because on a table with lots of paritions, the OUTER APPLY was crazy slow. - SET @dsql = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + + -- get relevant columns from sys.dm_db_partition_stats, sys.partitions and sys.objects + DROP TABLE if exists #dm_db_partition_stats_etc + create table #dm_db_partition_stats_etc + ( + database_id smallint not null + , object_id int not null + , sname sysname NULL + , index_id int + , partition_number int + , partition_id bigint + , row_count bigint + , reserved_MB bigint + , reserved_LOB_MB bigint + , reserved_row_overflow_MB bigint + , lock_escalation_desc nvarchar(60) + , data_compression_desc nvarchar(60) + ) + + -- get relevant info from sys.dm_db_index_operational_stats + drop TABLE if exists #dm_db_index_operational_stats + create table #dm_db_index_operational_stats + ( + database_id smallint not null + , object_id int not null + , index_id int + , partition_number int + , hobt_id bigint + , leaf_insert_count bigint + , leaf_delete_count bigint + , leaf_update_count bigint + , range_scan_count bigint + , singleton_lookup_count bigint + , forwarded_fetch_count bigint + , lob_fetch_in_pages bigint + , lob_fetch_in_bytes bigint + , row_overflow_fetch_in_pages bigint + , row_overflow_fetch_in_bytes bigint + , row_lock_count bigint + , row_lock_wait_count bigint + , row_lock_wait_in_ms bigint + , page_lock_count bigint + , page_lock_wait_count bigint + , page_lock_wait_in_ms bigint + , index_lock_promotion_attempt_count bigint + , index_lock_promotion_count bigint + , page_latch_wait_count bigint + , page_latch_wait_in_ms bigint + , page_io_latch_wait_count bigint + , page_io_latch_wait_in_ms bigint + ) + + SET @dsql = N' + DECLARE @d VARCHAR(19) = CONVERT(VARCHAR(19), GETDATE(), 121) + RAISERROR (N''start getting data into #dm_db_partition_stats_etc at %s'',0,1, @d) WITH NOWAIT; + SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + INSERT INTO #dm_db_partition_stats_etc + ( + database_id, object_id, sname, index_id, partition_number, partition_id, row_count, reserved_MB, reserved_LOB_MB, reserved_row_overflow_MB, lock_escalation_desc, data_compression_desc + ) SELECT ' + CAST(@DatabaseID AS NVARCHAR(10)) + N' AS database_id, ps.object_id, - s.name, + s.name as sname, ps.index_id, ps.partition_number, + ps.partition_id, ps.row_count, ps.reserved_page_count * 8. / 1024. AS reserved_MB, ps.lob_reserved_page_count * 8. / 1024. AS reserved_LOB_MB, ps.row_overflow_reserved_page_count * 8. / 1024. AS reserved_row_overflow_MB, le.lock_escalation_desc, - ' + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N'par.data_compression_desc ' ELSE N'null as data_compression_desc ' END + N', - SUM(os.leaf_insert_count), - SUM(os.leaf_delete_count), - SUM(os.leaf_update_count), - SUM(os.range_scan_count), - SUM(os.singleton_lookup_count), - SUM(os.forwarded_fetch_count), - SUM(os.lob_fetch_in_pages), - SUM(os.lob_fetch_in_bytes), - SUM(os.row_overflow_fetch_in_pages), - SUM(os.row_overflow_fetch_in_bytes), - SUM(os.row_lock_count), - SUM(os.row_lock_wait_count), - SUM(os.row_lock_wait_in_ms), - SUM(os.page_lock_count), - SUM(os.page_lock_wait_count), - SUM(os.page_lock_wait_in_ms), - SUM(os.index_lock_promotion_attempt_count), - SUM(os.index_lock_promotion_count), - SUM(os.page_latch_wait_count), - SUM(os.page_latch_wait_in_ms), - SUM(os.page_io_latch_wait_count), - SUM(os.page_io_latch_wait_in_ms), '; - - /* Get columnstore dictionary size - more info: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2585 */ - IF EXISTS (SELECT * FROM sys.all_objects WHERE name = 'column_store_dictionaries') - SET @dsql = @dsql + N' COALESCE((SELECT SUM (on_disk_size / 1024.0 / 1024) FROM ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_dictionaries dict WHERE dict.partition_id = ps.partition_id),0) AS reserved_dictionary_MB '; - ELSE - SET @dsql = @dsql + N' 0 AS reserved_dictionary_MB '; - + ' + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N'par.data_compression_desc ' ELSE N'null as data_compression_desc ' END + N' +'; SET @dsql = @dsql + N' FROM ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_partition_stats AS ps @@ -21560,9 +21617,6 @@ BEGIN TRY AND so.is_ms_shipped = 0 /*Exclude objects shipped by Microsoft*/ AND so.type <> ''TF'' /*Exclude table valued functions*/ JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s ON s.schema_id = so.schema_id - LEFT JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_index_operational_stats(' - + CAST(@DatabaseID AS NVARCHAR(10)) + N', NULL, NULL,NULL) AS os ON - ps.object_id=os.object_id and ps.index_id=os.index_id and ps.partition_number=os.partition_number OUTER APPLY (SELECT st.lock_escalation_desc FROM ' + QUOTENAME(@DatabaseName) + N'.sys.tables st WHERE st.object_id = ps.object_id @@ -21582,7 +21636,75 @@ BEGIN TRY le.lock_escalation_desc, ' + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N'par.data_compression_desc ' ELSE N'null as data_compression_desc ' END + N' ORDER BY ps.object_id, ps.index_id, ps.partition_number - OPTION ( RECOMPILE ); + /*OPTION ( RECOMPILE );*/ + OPTION ( RECOMPILE , min_grant_percent = 1); + + SET @d = CONVERT(VARCHAR(19), GETDATE(), 121) + RAISERROR (N''start getting data into #dm_db_index_operational_stats at %s.'',0,1, @d) WITH NOWAIT; + + insert into #dm_db_index_operational_stats + ( + database_id + , object_id + , index_id + , partition_number + , hobt_id + , leaf_insert_count + , leaf_delete_count + , leaf_update_count + , range_scan_count + , singleton_lookup_count + , forwarded_fetch_count + , lob_fetch_in_pages + , lob_fetch_in_bytes + , row_overflow_fetch_in_pages + , row_overflow_fetch_in_bytes + , row_lock_count + , row_lock_wait_count + , row_lock_wait_in_ms + , page_lock_count + , page_lock_wait_count + , page_lock_wait_in_ms + , index_lock_promotion_attempt_count + , index_lock_promotion_count + , page_latch_wait_count + , page_latch_wait_in_ms + , page_io_latch_wait_count + , page_io_latch_wait_in_ms + ) + + select os.database_id + , os.object_id + , os.index_id + , os.partition_number + , os.hobt_id + , os.leaf_insert_count + , os.leaf_delete_count + , os.leaf_update_count + , os.range_scan_count + , os.singleton_lookup_count + , os.forwarded_fetch_count + , os.lob_fetch_in_pages + , os.lob_fetch_in_bytes + , os.row_overflow_fetch_in_pages + , os.row_overflow_fetch_in_bytes + , os.row_lock_count + , os.row_lock_wait_count + , os.row_lock_wait_in_ms + , os.page_lock_count + , os.page_lock_wait_count + , os.page_lock_wait_in_ms + , os.index_lock_promotion_attempt_count + , os.index_lock_promotion_count + , os.page_latch_wait_count + , os.page_latch_wait_in_ms + , os.page_io_latch_wait_count + , os.page_io_latch_wait_in_ms + from ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_index_operational_stats('+ CAST(@DatabaseID AS NVARCHAR(10)) +', NULL, NULL,NULL) AS os + OPTION ( RECOMPILE , min_grant_percent = 1); + + SET @d = CONVERT(VARCHAR(19), GETDATE(), 121) + RAISERROR (N''finished getting data into #dm_db_index_operational_stats at %s.'',0,1, @d) WITH NOWAIT; '; END; ELSE @@ -21681,6 +21803,7 @@ BEGIN TRY PRINT SUBSTRING(@dsql, 32000, 36000); PRINT SUBSTRING(@dsql, 36000, 40000); END; + EXEC sp_executesql @dsql; INSERT #IndexPartitionSanity ( [database_id], [object_id], [schema_name], @@ -21715,8 +21838,35 @@ BEGIN TRY page_io_latch_wait_count, page_io_latch_wait_in_ms, reserved_dictionary_MB) - EXEC sp_executesql @dsql; - + select h.database_id, h.object_id, h.sname, h.index_id, h.partition_number, h.row_count, h.reserved_MB, h.reserved_LOB_MB, h.reserved_row_overflow_MB, h.lock_escalation_desc, h.data_compression_desc, + SUM(os.leaf_insert_count), + SUM(os.leaf_delete_count), + SUM(os.leaf_update_count), + SUM(os.range_scan_count), + SUM(os.singleton_lookup_count), + SUM(os.forwarded_fetch_count), + SUM(os.lob_fetch_in_pages), + SUM(os.lob_fetch_in_bytes), + SUM(os.row_overflow_fetch_in_pages), + SUM(os.row_overflow_fetch_in_bytes), + SUM(os.row_lock_count), + SUM(os.row_lock_wait_count), + SUM(os.row_lock_wait_in_ms), + SUM(os.page_lock_count), + SUM(os.page_lock_wait_count), + SUM(os.page_lock_wait_in_ms), + SUM(os.index_lock_promotion_attempt_count), + SUM(os.index_lock_promotion_count), + SUM(os.page_latch_wait_count), + SUM(os.page_latch_wait_in_ms), + SUM(os.page_io_latch_wait_count), + SUM(os.page_io_latch_wait_in_ms) + ,COALESCE((SELECT SUM (dict.on_disk_size / 1024.0 / 1024) FROM sys.column_store_dictionaries dict WHERE dict.partition_id = h.partition_id),0) AS reserved_dictionary_MB + from #dm_db_partition_stats_etc h + left JOIN #dm_db_index_operational_stats as os ON + h.object_id=os.object_id and h.index_id=os.index_id and h.partition_number=os.partition_number + group by h.database_id, h.object_id, h.sname, h.index_id, h.partition_number, h.partition_id, h.row_count, h.reserved_MB, h.reserved_LOB_MB, h.reserved_row_overflow_MB, h.lock_escalation_desc, h.data_compression_desc + END; --End Check For @SkipPartitions = 0 @@ -23067,7 +23217,7 @@ BEGIN INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns c ON rg.object_id = c.object_id INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partitions p ON rg.object_id = p.object_id AND rg.partition_number = p.partition_number INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.index_columns ic on ic.column_id = c.column_id AND ic.object_id = c.object_id AND ic.index_id = p.index_id - LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_column_store_row_group_physical_stats phys ON rg.row_group_id = phys.row_group_id AND rg.object_id = phys.object_id AND rg.partition_number = phys.partition_number AND p.index_id = phys.index_id ' + CASE WHEN @ShowPartitionRanges = 1 THEN N' + LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_column_store_row_group_physical_stats phys ON rg.row_group_id = phys.row_group_id AND rg.object_id = phys.object_id AND rg.partition_number = phys.partition_number AND rg.index_id = phys.index_id ' + CASE WHEN @ShowPartitionRanges = 1 THEN N' LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.indexes i ON i.object_id = rg.object_id AND i.index_id = rg.index_id LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partition_schemes ps ON ps.data_space_id = i.data_space_id LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partition_functions pf ON pf.function_id = ps.function_id @@ -26254,7 +26404,8 @@ BEGIN END; /* End @Mode=3 (index detail)*/ - + SET @d = CONVERT(VARCHAR(19), GETDATE(), 121); + RAISERROR (N'finishing at %s',0,1, @d) WITH NOWAIT; END /* End @TableName IS NULL (mode 0/1/2/3/4) */ END TRY @@ -26312,7 +26463,7 @@ BEGIN SET XACT_ABORT OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.18', @VersionDate = '20231222'; + SELECT @Version = '8.19', @VersionDate = '20240222'; IF @VersionCheckMode = 1 BEGIN @@ -30467,7 +30618,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.18', @VersionDate = '20231222'; +SELECT @Version = '8.19', @VersionDate = '20240222'; IF(@VersionCheckMode = 1) BEGIN RETURN; @@ -36516,7 +36667,7 @@ BEGIN SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.18', @VersionDate = '20231222'; + SELECT @Version = '8.19', @VersionDate = '20240222'; IF(@VersionCheckMode = 1) BEGIN @@ -37912,6 +38063,8 @@ DELETE FROM dbo.SqlServerVersions; INSERT INTO dbo.SqlServerVersions (MajorVersionNumber, MinorVersionNumber, Branch, [Url], ReleaseDate, MainstreamSupportEndDate, ExtendedSupportEndDate, MajorVersionName, MinorVersionName) VALUES + (16, 4105, 'CU11', 'https://support.microsoft.com/en-us/help/5032679', '2024-01-11', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 11'), + (16, 4100, 'CU10 GDR', 'https://support.microsoft.com/en-us/help/5033592', '2024-01-09', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 10 GDR'), (16, 4095, 'CU10', 'https://support.microsoft.com/en-us/help/5031778', '2023-11-16', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 10'), (16, 4085, 'CU9', 'https://support.microsoft.com/en-us/help/5030731', '2023-10-12', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 9'), (16, 4075, 'CU8', 'https://support.microsoft.com/en-us/help/5029666', '2023-09-14', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 8'), @@ -37924,6 +38077,7 @@ VALUES (16, 4003, 'CU1', 'https://support.microsoft.com/en-us/help/5022375', '2023-02-16', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 1'), (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'), + (15, 4355, 'CU25', 'https://support.microsoft.com/kb/5033688', '2023-02-15', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 25'), (15, 4345, 'CU24', 'https://support.microsoft.com/kb/5031908', '2023-12-14', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 24'), (15, 4335, 'CU23', 'https://support.microsoft.com/kb/5030333', '2023-10-12', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 23'), (15, 4322, 'CU22', 'https://support.microsoft.com/kb/5027702', '2023-08-14', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 22'), @@ -38351,7 +38505,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.18', @VersionDate = '20231222'; +SELECT @Version = '8.19', @VersionDate = '20240222'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_AllNightLog.sql b/sp_AllNightLog.sql index 1e7b1dddd..e886de70d 100644 --- a/sp_AllNightLog.sql +++ b/sp_AllNightLog.sql @@ -31,7 +31,7 @@ SET STATISTICS XML OFF; BEGIN; -SELECT @Version = '8.18', @VersionDate = '20231222'; +SELECT @Version = '8.19', @VersionDate = '20240222'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_AllNightLog_Setup.sql b/sp_AllNightLog_Setup.sql index 8238c897e..0925414d2 100644 --- a/sp_AllNightLog_Setup.sql +++ b/sp_AllNightLog_Setup.sql @@ -38,7 +38,7 @@ SET STATISTICS XML OFF; BEGIN; -SELECT @Version = '8.18', @VersionDate = '20231222'; +SELECT @Version = '8.19', @VersionDate = '20240222'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_Blitz.sql b/sp_Blitz.sql index 9c3069b0b..b1304b841 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -38,7 +38,7 @@ AS SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.18', @VersionDate = '20231222'; + SELECT @Version = '8.19', @VersionDate = '20240222'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) diff --git a/sp_BlitzAnalysis.sql b/sp_BlitzAnalysis.sql index f96c56b9a..52256e652 100644 --- a/sp_BlitzAnalysis.sql +++ b/sp_BlitzAnalysis.sql @@ -37,7 +37,7 @@ AS SET NOCOUNT ON; SET STATISTICS XML OFF; -SELECT @Version = '8.18', @VersionDate = '20231222'; +SELECT @Version = '8.19', @VersionDate = '20240222'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_BlitzBackups.sql b/sp_BlitzBackups.sql index b11b48d20..8fc7fa297 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.18', @VersionDate = '20231222'; + SELECT @Version = '8.19', @VersionDate = '20240222'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_BlitzCache.sql b/sp_BlitzCache.sql index 3279e178b..4b5ec1a45 100644 --- a/sp_BlitzCache.sql +++ b/sp_BlitzCache.sql @@ -281,7 +281,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.18', @VersionDate = '20231222'; +SELECT @Version = '8.19', @VersionDate = '20240222'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) diff --git a/sp_BlitzFirst.sql b/sp_BlitzFirst.sql index 3b1624e6e..fed15f318 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.18', @VersionDate = '20231222'; +SELECT @Version = '8.19', @VersionDate = '20240222'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_BlitzInMemoryOLTP.sql b/sp_BlitzInMemoryOLTP.sql index fc56a3cc6..fd622d9fb 100644 --- a/sp_BlitzInMemoryOLTP.sql +++ b/sp_BlitzInMemoryOLTP.sql @@ -82,7 +82,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI */ AS DECLARE @ScriptVersion VARCHAR(30); -SELECT @ScriptVersion = '1.8', @VersionDate = '20231222'; +SELECT @ScriptVersion = '1.8', @VersionDate = '20240222'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index a34095275..131c3155f 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -48,7 +48,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.18', @VersionDate = '20231222'; +SELECT @Version = '8.19', @VersionDate = '20240222'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index 848a324c2..313887b95 100644 --- a/sp_BlitzLock.sql +++ b/sp_BlitzLock.sql @@ -36,7 +36,7 @@ BEGIN SET XACT_ABORT OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.18', @VersionDate = '20231222'; + SELECT @Version = '8.19', @VersionDate = '20240222'; IF @VersionCheckMode = 1 BEGIN diff --git a/sp_BlitzQueryStore.sql b/sp_BlitzQueryStore.sql index 084e988f4..991a7deca 100644 --- a/sp_BlitzQueryStore.sql +++ b/sp_BlitzQueryStore.sql @@ -57,7 +57,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.18', @VersionDate = '20231222'; +SELECT @Version = '8.19', @VersionDate = '20240222'; IF(@VersionCheckMode = 1) BEGIN RETURN; diff --git a/sp_BlitzWho.sql b/sp_BlitzWho.sql index d623706c8..a6728be9d 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.18', @VersionDate = '20231222'; + SELECT @Version = '8.19', @VersionDate = '20240222'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_DatabaseRestore.sql b/sp_DatabaseRestore.sql index 0f16b6140..43805aee8 100755 --- a/sp_DatabaseRestore.sql +++ b/sp_DatabaseRestore.sql @@ -47,7 +47,7 @@ SET STATISTICS XML OFF; /*Versioning details*/ -SELECT @Version = '8.18', @VersionDate = '20231222'; +SELECT @Version = '8.19', @VersionDate = '20240222'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_ineachdb.sql b/sp_ineachdb.sql index 9cf874bac..06c84e070 100644 --- a/sp_ineachdb.sql +++ b/sp_ineachdb.sql @@ -36,7 +36,7 @@ BEGIN SET NOCOUNT ON; SET STATISTICS XML OFF; - SELECT @Version = '8.18', @VersionDate = '20231222'; + SELECT @Version = '8.19', @VersionDate = '20240222'; IF(@VersionCheckMode = 1) BEGIN From 854cc4cf5208664feeada47456ba0cfb298d00c7 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Sun, 25 Feb 2024 05:27:22 -0800 Subject: [PATCH 523/662] #3446 contribution instructions Fixing outdated instructions in sp_Blitz. Closes #3446. --- sp_Blitz.sql | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index 9c3069b0b..f0d1da562 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -1559,10 +1559,8 @@ AS end of the stored proc, where we start doing things like checking the plan cache, but those aren't as cleanly commented. - If you'd like to contribute your own check, use one of the check - formats shown above and email it to Help@BrentOzar.com. You don't - have to pick a CheckID or a link - we'll take care of that when we - test and publish the code. Thanks! + To contribute your own checks or fix bugs, learn more here: + https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/blob/main/CONTRIBUTING.md */ IF NOT EXISTS ( SELECT 1 From f1e1761894b207aec3c050d84f6e51d9f9c6e5ab Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Tue, 27 Feb 2024 04:43:28 -0800 Subject: [PATCH 524/662] SqlServerVersions 2019 CU25 Had 2023 instead of 2024 on release date. --- SqlServerVersions.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SqlServerVersions.sql b/SqlServerVersions.sql index aad3bd351..795ecd52e 100644 --- a/SqlServerVersions.sql +++ b/SqlServerVersions.sql @@ -55,7 +55,7 @@ VALUES (16, 4003, 'CU1', 'https://support.microsoft.com/en-us/help/5022375', '2023-02-16', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 1'), (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'), - (15, 4355, 'CU25', 'https://support.microsoft.com/kb/5033688', '2023-02-15', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 25'), + (15, 4355, 'CU25', 'https://support.microsoft.com/kb/5033688', '2024-02-15', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 25'), (15, 4345, 'CU24', 'https://support.microsoft.com/kb/5031908', '2023-12-14', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 24'), (15, 4335, 'CU23', 'https://support.microsoft.com/kb/5030333', '2023-10-12', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 23'), (15, 4322, 'CU22', 'https://support.microsoft.com/kb/5027702', '2023-08-14', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 22'), From e846e54d254c778491bcd9b2b49939a0d36dc7f0 Mon Sep 17 00:00:00 2001 From: Reece Goding <67124261+ReeceGoding@users.noreply.github.com> Date: Mon, 4 Mar 2024 16:08:28 +0000 Subject: [PATCH 525/662] Update sp_BlitzCache.sql Documented the @Version parameters. --- sp_BlitzCache.sql | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/sp_BlitzCache.sql b/sp_BlitzCache.sql index 4b5ec1a45..8d1b84b00 100644 --- a/sp_BlitzCache.sql +++ b/sp_BlitzCache.sql @@ -468,7 +468,22 @@ IF @Help = 1 UNION ALL SELECT N'@MinutesBack', N'INT', - N'How many minutes back to begin plan cache analysis. If you put in a positive number, we''ll flip it to negative.'; + N'How many minutes back to begin plan cache analysis. If you put in a positive number, we''ll flip it to negative.' + + UNION ALL + SELECT N'@Version', + N'VARCHAR(30)', + N'OUTPUT parameter holding version number' + + UNION ALL + SELECT N'@VersionDate', + N'DATETIME', + N'OUTPUT parameter holding version date.' + + UNION ALL + SELECT N'@VersionCheckMode', + N'BIT', + N'Setting this to 1 will make the procedure stop after setting @Version and @VersionDate.'; /* Column definitions */ From 5e97ed89c431ee3139b3bdd6c9caf72d0453c072 Mon Sep 17 00:00:00 2001 From: Reece Goding <67124261+ReeceGoding@users.noreply.github.com> Date: Mon, 4 Mar 2024 16:12:02 +0000 Subject: [PATCH 526/662] Documented @Version Parameters. --- sp_BlitzCache.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sp_BlitzCache.sql b/sp_BlitzCache.sql index 8d1b84b00..51216d6ae 100644 --- a/sp_BlitzCache.sql +++ b/sp_BlitzCache.sql @@ -473,7 +473,7 @@ IF @Help = 1 UNION ALL SELECT N'@Version', N'VARCHAR(30)', - N'OUTPUT parameter holding version number' + N'OUTPUT parameter holding version number.' UNION ALL SELECT N'@VersionDate', From d1543c8751955de77760d06c4cb4bce186b09180 Mon Sep 17 00:00:00 2001 From: Reece Goding <67124261+ReeceGoding@users.noreply.github.com> Date: Sat, 9 Mar 2024 20:01:00 +0000 Subject: [PATCH 527/662] Removed skipping of model and msdb in check for QS-enabled DBs Although it's sometimes considered a bad idea to turn on Query Store for these databases, that doesn't justify telling people that they haven't done so. --- sp_BlitzQueryStore.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sp_BlitzQueryStore.sql b/sp_BlitzQueryStore.sql index 991a7deca..07f281c31 100644 --- a/sp_BlitzQueryStore.sql +++ b/sp_BlitzQueryStore.sql @@ -443,7 +443,7 @@ IF ( SELECT COUNT(*) WHERE d.is_query_store_on = 1 AND d.user_access_desc='MULTI_USER' AND d.state_desc = 'ONLINE' - AND d.name NOT IN ('master', 'model', 'msdb', 'tempdb', '32767') + AND d.name NOT IN ('master', 'tempdb', '32767') AND d.is_distributor = 0 ) = 0 BEGIN SELECT @msg = N'You don''t currently have any databases with Query Store enabled.' + REPLICATE(CHAR(13), 7933); From 93edbc2b3bafe6b7375f4c912c72bbe741d5c919 Mon Sep 17 00:00:00 2001 From: Reece Goding <67124261+ReeceGoding@users.noreply.github.com> Date: Fri, 15 Mar 2024 22:59:08 +0000 Subject: [PATCH 528/662] Updated warning text for "Non-SARGable queries" The old one seemed like an obvious copy and paste error. I have replaced it with my best guess at a useful summary. --- sp_BlitzQueryStore.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sp_BlitzQueryStore.sql b/sp_BlitzQueryStore.sql index 991a7deca..5f23a5225 100644 --- a/sp_BlitzQueryStore.sql +++ b/sp_BlitzQueryStore.sql @@ -5621,7 +5621,7 @@ BEGIN 'Non-SARGable queries', 'Queries may be using', 'https://www.brentozar.com/blitzcache/non-sargable-predicates/', - 'Occurs when join inputs aren''t known to be unique. Can be really bad when parallel.'); + 'Occurs when predicates cannot be used for index seeks. Causes all kinds of bad side effects.'); IF EXISTS (SELECT 1/0 FROM #working_warnings p From 555a3495369090c82b4953b568c89c2a4e3725c9 Mon Sep 17 00:00:00 2001 From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com> Date: Fri, 29 Mar 2024 10:58:16 -0400 Subject: [PATCH 529/662] Closes #3461 Closes #3461 --- sp_BlitzIndex.sql | 113 ++++++++++++++++++++++++++++------------------ 1 file changed, 69 insertions(+), 44 deletions(-) diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index 131c3155f..04a5bb548 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -6036,52 +6036,77 @@ BEGIN WHEN i.index_definition = '[HEAP]' THEN N'' ELSE N'--' + ict.create_tsql END AS [Create TSQL], 1 AS [Display Order] + INTO #Mode2Temp FROM #IndexSanity AS i --left join here so we don't lose disabled nc indexes - LEFT JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id - LEFT JOIN #IndexCreateTsql AS ict ON i.index_sanity_id = ict.index_sanity_id - ORDER BY /* Shout out to DHutmacher */ - /*DESC*/ - CASE WHEN @SortOrder = N'rows' AND @SortDirection = N'desc' THEN sz.total_rows ELSE NULL END DESC, - CASE WHEN @SortOrder = N'reserved_mb' AND @SortDirection = N'desc' THEN sz.total_reserved_MB ELSE NULL END DESC, - CASE WHEN @SortOrder = N'size' AND @SortDirection = N'desc' THEN sz.total_reserved_MB ELSE NULL END DESC, - CASE WHEN @SortOrder = N'reserved_lob_mb' AND @SortDirection = N'desc' THEN sz.total_reserved_LOB_MB ELSE NULL END DESC, - CASE WHEN @SortOrder = N'lob' AND @SortDirection = N'desc' THEN sz.total_reserved_LOB_MB ELSE NULL END DESC, - CASE WHEN @SortOrder = N'total_row_lock_wait_in_ms' AND @SortDirection = N'desc' THEN COALESCE(sz.total_row_lock_wait_in_ms,0) ELSE NULL END DESC, - CASE WHEN @SortOrder = N'total_page_lock_wait_in_ms' AND @SortDirection = N'desc' THEN COALESCE(sz.total_page_lock_wait_in_ms,0) ELSE NULL END DESC, - CASE WHEN @SortOrder = N'lock_time' AND @SortDirection = N'desc' THEN (COALESCE(sz.total_row_lock_wait_in_ms,0) + COALESCE(sz.total_page_lock_wait_in_ms,0)) ELSE NULL END DESC, - CASE WHEN @SortOrder = N'total_reads' AND @SortDirection = N'desc' THEN total_reads ELSE NULL END DESC, - CASE WHEN @SortOrder = N'reads' AND @SortDirection = N'desc' THEN total_reads ELSE NULL END DESC, - CASE WHEN @SortOrder = N'user_updates' AND @SortDirection = N'desc' THEN user_updates ELSE NULL END DESC, - CASE WHEN @SortOrder = N'writes' AND @SortDirection = N'desc' THEN user_updates ELSE NULL END DESC, - CASE WHEN @SortOrder = N'reads_per_write' AND @SortDirection = N'desc' THEN reads_per_write ELSE NULL END DESC, - CASE WHEN @SortOrder = N'ratio' AND @SortDirection = N'desc' THEN reads_per_write ELSE NULL END DESC, - CASE WHEN @SortOrder = N'forward_fetches' AND @SortDirection = N'desc' THEN sz.total_forwarded_fetch_count ELSE NULL END DESC, - CASE WHEN @SortOrder = N'fetches' AND @SortDirection = N'desc' THEN sz.total_forwarded_fetch_count ELSE NULL END DESC, - CASE WHEN @SortOrder = N'create_date' AND @SortDirection = N'desc' THEN CONVERT(DATETIME, i.create_date) ELSE NULL END DESC, - CASE WHEN @SortOrder = N'modify_date' AND @SortDirection = N'desc' THEN CONVERT(DATETIME, i.modify_date) ELSE NULL END DESC, - /*ASC*/ - CASE WHEN @SortOrder = N'rows' AND @SortDirection = N'asc' THEN sz.total_rows ELSE NULL END ASC, - CASE WHEN @SortOrder = N'reserved_mb' AND @SortDirection = N'asc' THEN sz.total_reserved_MB ELSE NULL END ASC, - CASE WHEN @SortOrder = N'size' AND @SortDirection = N'asc' THEN sz.total_reserved_MB ELSE NULL END ASC, - CASE WHEN @SortOrder = N'reserved_lob_mb' AND @SortDirection = N'asc' THEN sz.total_reserved_LOB_MB ELSE NULL END ASC, - CASE WHEN @SortOrder = N'lob' AND @SortDirection = N'asc' THEN sz.total_reserved_LOB_MB ELSE NULL END ASC, - CASE WHEN @SortOrder = N'total_row_lock_wait_in_ms' AND @SortDirection = N'asc' THEN COALESCE(sz.total_row_lock_wait_in_ms,0) ELSE NULL END ASC, - CASE WHEN @SortOrder = N'total_page_lock_wait_in_ms' AND @SortDirection = N'asc' THEN COALESCE(sz.total_page_lock_wait_in_ms,0) ELSE NULL END ASC, - CASE WHEN @SortOrder = N'lock_time' AND @SortDirection = N'asc' THEN (COALESCE(sz.total_row_lock_wait_in_ms,0) + COALESCE(sz.total_page_lock_wait_in_ms,0)) ELSE NULL END ASC, - CASE WHEN @SortOrder = N'total_reads' AND @SortDirection = N'asc' THEN total_reads ELSE NULL END ASC, - CASE WHEN @SortOrder = N'reads' AND @SortDirection = N'asc' THEN total_reads ELSE NULL END ASC, - CASE WHEN @SortOrder = N'user_updates' AND @SortDirection = N'asc' THEN user_updates ELSE NULL END ASC, - CASE WHEN @SortOrder = N'writes' AND @SortDirection = N'asc' THEN user_updates ELSE NULL END ASC, - CASE WHEN @SortOrder = N'reads_per_write' AND @SortDirection = N'asc' THEN reads_per_write ELSE NULL END ASC, - CASE WHEN @SortOrder = N'ratio' AND @SortDirection = N'asc' THEN reads_per_write ELSE NULL END ASC, - CASE WHEN @SortOrder = N'forward_fetches' AND @SortDirection = N'asc' THEN sz.total_forwarded_fetch_count ELSE NULL END ASC, - CASE WHEN @SortOrder = N'fetches' AND @SortDirection = N'asc' THEN sz.total_forwarded_fetch_count ELSE NULL END ASC, - CASE WHEN @SortOrder = N'create_date' AND @SortDirection = N'asc' THEN CONVERT(DATETIME, i.create_date) ELSE NULL END ASC, - CASE WHEN @SortOrder = N'modify_date' AND @SortDirection = N'asc' THEN CONVERT(DATETIME, i.modify_date) ELSE NULL END ASC, - i.[database_name], [Schema Name], [Object Name], [Index ID] - OPTION (RECOMPILE); - END; + LEFT JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id + LEFT JOIN #IndexCreateTsql AS ict ON i.index_sanity_id = ict.index_sanity_id + OPTION(RECOMPILE); + IF @@ROWCOUNT > 0 + BEGIN + SELECT + sz.* + FROM #Mode2Temp AS sz + ORDER BY /* Shout out to DHutmacher */ + /*DESC*/ + CASE WHEN @SortOrder = N'rows' AND @SortDirection = N'desc' THEN sz.[Rows] ELSE NULL END DESC, + CASE WHEN @SortOrder = N'reserved_mb' AND @SortDirection = N'desc' THEN sz.[Reserved MB] ELSE NULL END DESC, + CASE WHEN @SortOrder = N'size' AND @SortDirection = N'desc' THEN sz.[Reserved MB] ELSE NULL END DESC, + CASE WHEN @SortOrder = N'reserved_lob_mb' AND @SortDirection = N'desc' THEN sz.[Reserved LOB MB] ELSE NULL END DESC, + CASE WHEN @SortOrder = N'lob' AND @SortDirection = N'desc' THEN sz.[Reserved LOB MB] ELSE NULL END DESC, + CASE WHEN @SortOrder = N'total_row_lock_wait_in_ms' AND @SortDirection = N'desc' THEN COALESCE(sz.[Row Lock Wait ms],0) ELSE NULL END DESC, + CASE WHEN @SortOrder = N'total_page_lock_wait_in_ms' AND @SortDirection = N'desc' THEN COALESCE(sz.[Page Lock Wait ms],0) ELSE NULL END DESC, + CASE WHEN @SortOrder = N'lock_time' AND @SortDirection = N'desc' THEN (COALESCE(sz.[Row Lock Wait ms],0) + COALESCE(sz.[Page Lock Wait ms],0)) ELSE NULL END DESC, + CASE WHEN @SortOrder = N'total_reads' AND @SortDirection = N'desc' THEN [Total Reads] ELSE NULL END DESC, + CASE WHEN @SortOrder = N'reads' AND @SortDirection = N'desc' THEN [Total Reads] ELSE NULL END DESC, + CASE WHEN @SortOrder = N'user_updates' AND @SortDirection = N'desc' THEN [User Updates] ELSE NULL END DESC, + CASE WHEN @SortOrder = N'writes' AND @SortDirection = N'desc' THEN [User Updates] ELSE NULL END DESC, + CASE WHEN @SortOrder = N'reads_per_write' AND @SortDirection = N'desc' THEN [Reads Per Write] ELSE NULL END DESC, + CASE WHEN @SortOrder = N'ratio' AND @SortDirection = N'desc' THEN [Reads Per Write] ELSE NULL END DESC, + CASE WHEN @SortOrder = N'forward_fetches' AND @SortDirection = N'desc' THEN sz.[Forwarded Fetches] ELSE NULL END DESC, + CASE WHEN @SortOrder = N'fetches' AND @SortDirection = N'desc' THEN sz.[Forwarded Fetches] ELSE NULL END DESC, + CASE WHEN @SortOrder = N'create_date' AND @SortDirection = N'desc' THEN CONVERT(DATETIME, sz.[Create Date]) ELSE NULL END DESC, + CASE WHEN @SortOrder = N'modify_date' AND @SortDirection = N'desc' THEN CONVERT(DATETIME, sz.[Modify Date]) ELSE NULL END DESC, + /*ASC*/ + CASE WHEN @SortOrder = N'rows' AND @SortDirection = N'asc' THEN sz.[Rows] ELSE NULL END ASC, + CASE WHEN @SortOrder = N'reserved_mb' AND @SortDirection = N'asc' THEN sz.[Reserved MB] ELSE NULL END ASC, + CASE WHEN @SortOrder = N'size' AND @SortDirection = N'asc' THEN sz.[Reserved MB] ELSE NULL END ASC, + CASE WHEN @SortOrder = N'reserved_lob_mb' AND @SortDirection = N'asc' THEN sz.[Reserved LOB MB] ELSE NULL END ASC, + CASE WHEN @SortOrder = N'lob' AND @SortDirection = N'asc' THEN sz.[Reserved LOB MB] ELSE NULL END ASC, + CASE WHEN @SortOrder = N'total_row_lock_wait_in_ms' AND @SortDirection = N'asc' THEN COALESCE(sz.[Row Lock Wait ms],0) ELSE NULL END ASC, + CASE WHEN @SortOrder = N'total_page_lock_wait_in_ms' AND @SortDirection = N'asc' THEN COALESCE(sz.[Page Lock Wait ms],0) ELSE NULL END ASC, + CASE WHEN @SortOrder = N'lock_time' AND @SortDirection = N'asc' THEN (COALESCE(sz.[Row Lock Wait ms],0) + COALESCE(sz.[Page Lock Wait ms],0)) ELSE NULL END ASC, + CASE WHEN @SortOrder = N'total_reads' AND @SortDirection = N'asc' THEN [Total Reads] ELSE NULL END ASC, + CASE WHEN @SortOrder = N'reads' AND @SortDirection = N'asc' THEN [Total Reads] ELSE NULL END ASC, + CASE WHEN @SortOrder = N'user_updates' AND @SortDirection = N'asc' THEN [User Updates] ELSE NULL END ASC, + CASE WHEN @SortOrder = N'writes' AND @SortDirection = N'asc' THEN [User Updates] ELSE NULL END ASC, + CASE WHEN @SortOrder = N'reads_per_write' AND @SortDirection = N'asc' THEN [Reads Per Write] ELSE NULL END ASC, + CASE WHEN @SortOrder = N'ratio' AND @SortDirection = N'asc' THEN [Reads Per Write] ELSE NULL END ASC, + CASE WHEN @SortOrder = N'forward_fetches' AND @SortDirection = N'asc' THEN sz.[Forwarded Fetches] ELSE NULL END ASC, + CASE WHEN @SortOrder = N'fetches' AND @SortDirection = N'asc' THEN sz.[Forwarded Fetches] ELSE NULL END ASC, + CASE WHEN @SortOrder = N'create_date' AND @SortDirection = N'asc' THEN CONVERT(DATETIME, sz.[Create Date]) ELSE NULL END ASC, + CASE WHEN @SortOrder = N'modify_date' AND @SortDirection = N'asc' THEN CONVERT(DATETIME, sz.[Modify Date]) ELSE NULL END ASC, + sz.[Database Name], [Schema Name], [Object Name], [Index ID] + OPTION (RECOMPILE); + END + ELSE + BEGIN + SELECT + DatabaseDetails = + N'Database ' + + ISNULL(@DatabaseName, DB_NAME()) + + N' has ' + + ISNULL(RTRIM(@Rowcount), 0) + + N' partitions.', + BringThePain = + CASE + WHEN @BringThePain IN (0, 1) AND ISNULL(@Rowcount, 0) = 0 + THEN N'Check the database name, it looks like nothing is here.' + WHEN @BringThePain = 0 AND ISNULL(@Rowcount, 0) > 0 + THEN N'Please re-run with @BringThePain = 1' + END; + END + END; END; /* End @Mode=2 (index detail)*/ From f52f00287fbdece359929ab9010d88c810cfbb8f Mon Sep 17 00:00:00 2001 From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com> Date: Fri, 29 Mar 2024 19:31:10 -0400 Subject: [PATCH 530/662] Update sp_BlitzIndex.sql Closes #3462 --- sp_BlitzIndex.sql | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index 131c3155f..4f0280c12 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -1776,7 +1776,8 @@ BEGIN TRY END; --End Check For @SkipPartitions = 0 - + IF @Mode NOT IN(1, 2) + BEGIN RAISERROR (N'Inserting data into #MissingIndexes',0,1) WITH NOWAIT; SET @dsql=N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' @@ -1927,6 +1928,7 @@ BEGIN TRY inequality_columns, included_columns, equality_columns_with_data_type, inequality_columns_with_data_type, included_columns_with_data_type, sample_query_plan) EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; + END; SET @dsql = N' SELECT DB_ID(N' + QUOTENAME(@DatabaseName,'''') + N') AS [database_id], @@ -1994,7 +1996,8 @@ BEGIN TRY [update_referential_action_desc], [delete_referential_action_desc] ) EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; - + IF @Mode NOT IN(1, 2) + BEGIN SET @dsql = N' SELECT DB_ID(N' + QUOTENAME(@DatabaseName,'''') + N') AS [database_id], @@ -2033,7 +2036,7 @@ BEGIN TRY IF @dsql IS NULL RAISERROR('@dsql is null',16,1); - RAISERROR (N'Inserting data into #ForeignKeys',0,1) WITH NOWAIT; + RAISERROR (N'Inserting data into #UnindexedForeignKeys',0,1) WITH NOWAIT; IF @Debug = 1 BEGIN PRINT SUBSTRING(@dsql, 0, 4000); @@ -2064,8 +2067,11 @@ BEGIN TRY @dsql, N'@i_DatabaseName sysname', @DatabaseName; + END; + IF @Mode NOT IN(1, 2) + BEGIN IF @SkipStatistics = 0 /* AND DB_NAME() = @DatabaseName /* Can only get stats in the current database - see https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/1947 */ */ BEGIN IF ((PARSENAME(@SQLServerProductVersion, 4) >= 12) @@ -2223,9 +2229,11 @@ BEGIN TRY EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; END; - + END; END; + IF @Mode NOT IN(1, 2) + BEGIN IF (PARSENAME(@SQLServerProductVersion, 4) >= 10) BEGIN RAISERROR (N'Gathering Computed Column Info.',0,1) WITH NOWAIT; @@ -2259,9 +2267,11 @@ BEGIN TRY ( database_id, [database_name], table_name, schema_name, column_name, is_nullable, definition, uses_database_collation, is_persisted, is_computed, is_function, column_definition ) EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; + END; + END; - END; - + IF @Mode NOT IN(1, 2) + BEGIN RAISERROR (N'Gathering Trace Flag Information',0,1) WITH NOWAIT; INSERT #TraceStatus EXEC ('DBCC TRACESTATUS(-1) WITH NO_INFOMSGS'); @@ -2306,6 +2316,7 @@ BEGIN TRY history_table_name, start_column_name, end_column_name, period_name ) EXEC sp_executesql @dsql; + END; SET @dsql=N'SELECT DB_ID(@i_DatabaseName) AS [database_id], @i_DatabaseName AS database_name, @@ -2332,6 +2343,8 @@ BEGIN TRY EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; + IF @Mode NOT IN(1, 2) + BEGIN SET @dsql=N'SELECT DB_ID(@i_DatabaseName) AS [database_id], @i_DatabaseName AS database_name, s.name AS missing_schema_name, @@ -2361,9 +2374,8 @@ BEGIN TRY INSERT #FilteredIndexes ( database_id, database_name, schema_name, table_name, index_name, column_name ) EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; - - END; + END; END; END TRY From 23de247f87855e250ab073c3c2b09663e5148ded Mon Sep 17 00:00:00 2001 From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com> Date: Fri, 29 Mar 2024 19:55:40 -0400 Subject: [PATCH 531/662] Update sp_BlitzIndex.sql Closes #3463 --- sp_BlitzIndex.sql | 312 ++++++++++++++++++++++++++++++++-------------- 1 file changed, 216 insertions(+), 96 deletions(-) diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index 131c3155f..6243b9f1f 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -1781,70 +1781,179 @@ BEGIN TRY SET @dsql=N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' - SET @dsql = @dsql + 'WITH ColumnNamesWithDataTypes AS(SELECT id.index_handle,id.object_id,cn.IndexColumnType,STUFF((SELECT '', '' + cn_inner.ColumnName + '' '' + - N'' {'' + CASE WHEN ty.name IN ( ''varchar'', ''char'' ) THEN ty.name + ''('' + CASE WHEN co.max_length = -1 THEN ''max'' ELSE CAST(co.max_length AS VARCHAR(25)) END + '')'' - WHEN ty.name IN ( ''nvarchar'', ''nchar'' ) THEN ty.name + ''('' + CASE WHEN co.max_length = -1 THEN ''max'' ELSE CAST(co.max_length / 2 AS VARCHAR(25)) END + '')'' - WHEN ty.name IN ( ''decimal'', ''numeric'' ) THEN ty.name + ''('' + CAST(co.precision AS VARCHAR(25)) + '', '' + CAST(co.scale AS VARCHAR(25)) + '')'' - WHEN ty.name IN ( ''datetime2'' ) THEN ty.name + ''('' + CAST(co.scale AS VARCHAR(25)) + '')'' - ELSE ty.name END + ''}'' - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_details AS id_inner - CROSS APPLY( - SELECT LTRIM(RTRIM(v.value(''(./text())[1]'', ''varchar(max)''))) AS ColumnName, ''Equality'' AS IndexColumnType - FROM (VALUES (CONVERT(XML, N'''' + REPLACE((SELECT CAST(id_inner.equality_columns AS nvarchar(max)) FOR XML PATH('''')), N'','', N'''') + N''''))) x(n) - CROSS APPLY n.nodes(''x'') node(v) - UNION ALL - SELECT LTRIM(RTRIM(v.value(N''(./text())[1]'', ''varchar(max)''))) AS ColumnName, ''Inequality'' AS IndexColumnType - FROM (VALUES (CONVERT(XML, N'''' + REPLACE((SELECT CAST(id_inner.inequality_columns AS nvarchar(max)) FOR XML PATH('''')), N'','', N'''') + N''''))) x(n) - CROSS APPLY n.nodes(''x'') node(v) - UNION ALL - SELECT LTRIM(RTRIM(v.value(''(./text())[1]'', ''varchar(max)''))) AS ColumnName, ''Included'' AS IndexColumnType - FROM (VALUES (CONVERT(XML, N'''' + REPLACE((SELECT CAST(id_inner.included_columns AS nvarchar(max)) FOR XML PATH('''')), N'','', N'''') + N''''))) x(n) - CROSS APPLY n.nodes(''x'') node(v) - )AS cn_inner' - + /*split the string otherwise dsql cuts some of it out*/ - ' JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns AS co ON co.object_id = id_inner.object_id AND ''['' + co.name + '']'' = cn_inner.ColumnName - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.types AS ty ON ty.user_type_id = co.user_type_id + SET @dsql = @dsql + ' +WITH + ColumnNamesWithDataTypes AS +( + SELECT + id.index_handle, + id.object_id, + cn.IndexColumnType, + STUFF + ( + ( + SELECT + '', '' + + cn_inner.ColumnName + + '' '' + + N'' {'' + + CASE + WHEN ty.name IN (''varchar'', ''char'') + THEN ty.name + + ''('' + + CASE + WHEN co.max_length = -1 + THEN ''max'' + ELSE CAST(co.max_length AS VARCHAR(25)) + END + + '')'' + WHEN ty.name IN (''nvarchar'', ''nchar'') + THEN ty.name + + ''('' + + CASE + WHEN co.max_length = -1 + THEN ''max'' + ELSE CAST(co.max_length / 2 AS VARCHAR(25)) + END + + '')'' + WHEN ty.name IN (''decimal'', ''numeric'') + THEN ty.name + + ''('' + + CAST(co.precision AS VARCHAR(25)) + + '', '' + + CAST(co.scale AS VARCHAR(25)) + + '')'' + WHEN ty.name IN (''datetime2'') + THEN ty.name + + ''('' + + CAST(co.scale AS VARCHAR(25)) + + '')'' + ELSE ty.name END + ''}'' + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_details AS id_inner + CROSS APPLY + ( + SELECT + LTRIM(RTRIM(v.value(''(./text())[1]'', ''varchar(max)''))) AS ColumnName, + ''Equality'' AS IndexColumnType + FROM + ( + VALUES + (CONVERT(XML, N'''' + REPLACE((SELECT CAST(id_inner.equality_columns AS nvarchar(max)) FOR XML PATH('''')), N'','', N'''') + N'''')) + ) x (n) + CROSS APPLY n.nodes(''x'') node(v) + UNION ALL + SELECT + LTRIM(RTRIM(v.value(N''(./text())[1]'', ''varchar(max)''))) AS ColumnName, + ''Inequality'' AS IndexColumnType + FROM + ( + VALUES + (CONVERT(XML, N'''' + REPLACE((SELECT CAST(id_inner.inequality_columns AS nvarchar(max)) FOR XML PATH('''')), N'','', N'''') + N'''')) + ) x (n) + CROSS APPLY n.nodes(''x'') node(v) + UNION ALL + SELECT + LTRIM(RTRIM(v.value(''(./text())[1]'', ''varchar(max)''))) AS ColumnName, + ''Included'' AS IndexColumnType + FROM + ( + VALUES (CONVERT(XML, N'''' + REPLACE((SELECT CAST(id_inner.included_columns AS nvarchar(max)) FOR XML PATH('''')), N'','', N'''') + N'''')) + ) x (n) + CROSS APPLY n.nodes(''x'') node(v) + ) AS cn_inner + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns AS co + ON co.object_id = id_inner.object_id + AND ''['' + co.name + '']'' = cn_inner.ColumnName + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.types AS ty + ON ty.user_type_id = co.user_type_id WHERE id_inner.index_handle = id.index_handle - AND id_inner.object_id = id.object_id - AND cn_inner.IndexColumnType = cn.IndexColumnType - FOR XML PATH('''') - ),1,1,'''') AS ReplaceColumnNames - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_details AS id - CROSS APPLY( - SELECT LTRIM(RTRIM(v.value(''(./text())[1]'', ''varchar(max)''))) AS ColumnName, ''Equality'' AS IndexColumnType - FROM (VALUES (CONVERT(XML, N'''' + REPLACE((SELECT CAST(id.equality_columns AS nvarchar(max)) FOR XML PATH('''')), N'','', N'''') + N''''))) x(n) - CROSS APPLY n.nodes(''x'') node(v) - UNION ALL - SELECT LTRIM(RTRIM(v.value(''(./text())[1]'', ''varchar(max)''))) AS ColumnName, ''Inequality'' AS IndexColumnType - FROM (VALUES (CONVERT(XML, N'''' + REPLACE((SELECT CAST(id.inequality_columns AS nvarchar(max)) FOR XML PATH('''')), N'','', N'''') + N''''))) x(n) - CROSS APPLY n.nodes(''x'') node(v) - UNION ALL - SELECT LTRIM(RTRIM(v.value(''(./text())[1]'', ''varchar(max)''))) AS ColumnName, ''Included'' AS IndexColumnType - FROM (VALUES (CONVERT(XML, N'''' + REPLACE((SELECT CAST(id.included_columns AS nvarchar(max)) FOR XML PATH('''')), N'','', N'''') + N''''))) x(n) - CROSS APPLY n.nodes(''x'') node(v) - )AS cn - GROUP BY id.index_handle,id.object_id,cn.IndexColumnType - ) - SELECT id.database_id, id.object_id, @i_DatabaseName, sc.[name], so.[name], id.statement , gs.avg_total_user_cost, - gs.avg_user_impact, gs.user_seeks, gs.user_scans, gs.unique_compiles, id.equality_columns, id.inequality_columns, id.included_columns, - ( - SELECT ColumnNamesWithDataTypes.ReplaceColumnNames - FROM ColumnNamesWithDataTypes WHERE ColumnNamesWithDataTypes.index_handle = id.index_handle - AND ColumnNamesWithDataTypes.object_id = id.object_id - AND ColumnNamesWithDataTypes.IndexColumnType = ''Equality'' - ) AS equality_columns_with_data_type - ,( - SELECT ColumnNamesWithDataTypes.ReplaceColumnNames - FROM ColumnNamesWithDataTypes WHERE ColumnNamesWithDataTypes.index_handle = id.index_handle - AND ColumnNamesWithDataTypes.object_id = id.object_id - AND ColumnNamesWithDataTypes.IndexColumnType = ''Inequality'' - ) AS inequality_columns_with_data_type - ,( - SELECT ColumnNamesWithDataTypes.ReplaceColumnNames - FROM ColumnNamesWithDataTypes WHERE ColumnNamesWithDataTypes.index_handle = id.index_handle - AND ColumnNamesWithDataTypes.object_id = id.object_id - AND ColumnNamesWithDataTypes.IndexColumnType = ''Included'' - ) AS included_columns_with_data_type ' + AND id_inner.object_id = id.object_id + AND cn_inner.IndexColumnType = cn.IndexColumnType + FOR XML PATH('''') + ), + 1, + 1, + '''' + ) AS ReplaceColumnNames + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_details AS id + CROSS APPLY + ( + SELECT + LTRIM(RTRIM(v.value(''(./text())[1]'', ''varchar(max)''))) AS ColumnName, + ''Equality'' AS IndexColumnType + FROM + ( + VALUES (CONVERT(XML, N'''' + REPLACE((SELECT CAST(id.equality_columns AS nvarchar(max)) FOR XML PATH('''')), N'','', N'''') + N'''')) + ) x (n) + CROSS APPLY n.nodes(''x'') node(v) + UNION ALL + SELECT + LTRIM(RTRIM(v.value(''(./text())[1]'', ''varchar(max)''))) AS ColumnName, + ''Inequality'' AS IndexColumnType + FROM + ( + VALUES (CONVERT(XML, N'''' + REPLACE((SELECT CAST(id.inequality_columns AS nvarchar(max)) FOR XML PATH('''')), N'','', N'''') + N'''')) + ) x (n) + CROSS APPLY n.nodes(''x'') node(v) + UNION ALL + SELECT + LTRIM(RTRIM(v.value(''(./text())[1]'', ''varchar(max)''))) AS ColumnName, + ''Included'' AS IndexColumnType + FROM + ( + VALUES (CONVERT(XML, N'''' + REPLACE((SELECT CAST(id.included_columns AS nvarchar(max)) FOR XML PATH('''')), N'','', N'''') + N'''')) + ) x (n) + CROSS APPLY n.nodes(''x'') node(v) + )AS cn + GROUP BY + id.index_handle, + id.object_id, + cn.IndexColumnType +) +SELECT + * +INTO #ColumnNamesWithDataTypes +FROM ColumnNamesWithDataTypes +OPTION(RECOMPILE); + +SELECT + id.database_id, + id.object_id, + @i_DatabaseName, + sc.[name], + so.[name], + id.statement, + gs.avg_total_user_cost, + gs.avg_user_impact, + gs.user_seeks, + gs.user_scans, + gs.unique_compiles, + id.equality_columns, + id.inequality_columns, + id.included_columns, + ( + SELECT + ColumnNamesWithDataTypes.ReplaceColumnNames + FROM #ColumnNamesWithDataTypes ColumnNamesWithDataTypes + WHERE ColumnNamesWithDataTypes.index_handle = id.index_handle + AND ColumnNamesWithDataTypes.object_id = id.object_id + AND ColumnNamesWithDataTypes.IndexColumnType = ''Equality'' + ) AS equality_columns_with_data_type, + ( + SELECT + ColumnNamesWithDataTypes.ReplaceColumnNames + FROM #ColumnNamesWithDataTypes ColumnNamesWithDataTypes + WHERE ColumnNamesWithDataTypes.index_handle = id.index_handle + AND ColumnNamesWithDataTypes.object_id = id.object_id + AND ColumnNamesWithDataTypes.IndexColumnType = ''Inequality'' + ) AS inequality_columns_with_data_type, + ( + SELECT ColumnNamesWithDataTypes.ReplaceColumnNames + FROM #ColumnNamesWithDataTypes ColumnNamesWithDataTypes + WHERE ColumnNamesWithDataTypes.index_handle = id.index_handle + AND ColumnNamesWithDataTypes.object_id = id.object_id + AND ColumnNamesWithDataTypes.IndexColumnType = ''Included'' + ) AS included_columns_with_data_type,'; /* Get the sample query plan if it's available, and if there are less than 1,000 rows in the DMV: */ IF NOT EXISTS @@ -1854,8 +1963,9 @@ BEGIN TRY FROM sys.all_objects AS o WHERE o.name = 'dm_db_missing_index_group_stats_query' ) - SELECT - @dsql += N' , NULL AS sample_query_plan ' + SELECT + @dsql += N' + NULL AS sample_query_plan' ELSE BEGIN /* The DMV is only supposed to have 600 rows in it. If it's got more, @@ -1866,46 +1976,56 @@ BEGIN TRY IF @MissingIndexPlans > 1000 BEGIN - SELECT @dsql += N' , NULL AS sample_query_plan /* Over 1000 plans found, skipping */ '; + SELECT @dsql += N' + NULL AS sample_query_plan /* Over 1000 plans found, skipping */'; RAISERROR (N'Over 1000 plans found in sys.dm_db_missing_index_group_stats_query - your SQL Server is hitting a bug: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/3085',0,1) WITH NOWAIT; END ELSE SELECT @dsql += N' - , sample_query_plan = - ( - SELECT TOP (1) - p.query_plan - FROM sys.dm_db_missing_index_group_stats gs - CROSS APPLY - ( - SELECT TOP (1) - s.plan_handle - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_group_stats_query q - INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.dm_exec_query_stats s - ON q.query_plan_hash = s.query_plan_hash - WHERE gs.group_handle = q.group_handle - ORDER BY (q.user_seeks + q.user_scans) DESC, s.total_logical_reads DESC - ) q2 - CROSS APPLY sys.dm_exec_query_plan(q2.plan_handle) p - WHERE ig.index_group_handle = gs.group_handle - ) ' + sample_query_plan = + ( + SELECT TOP (1) + p.query_plan + FROM sys.dm_db_missing_index_group_stats gs + CROSS APPLY + ( + SELECT TOP (1) + s.plan_handle + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_group_stats_query q + INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.dm_exec_query_stats s + ON q.query_plan_hash = s.query_plan_hash + WHERE gs.group_handle = q.group_handle + ORDER BY + (q.user_seeks + q.user_scans) DESC, + s.total_logical_reads DESC + ) q2 + CROSS APPLY sys.dm_exec_query_plan(q2.plan_handle) p + WHERE ig.index_group_handle = gs.group_handle + )' END - SET @dsql = @dsql + N'FROM ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_groups ig - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_details id ON ig.index_handle = id.index_handle - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_group_stats gs ON ig.index_group_handle = gs.group_handle - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects so on - id.object_id=so.object_id - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas sc on - so.schema_id=sc.schema_id - WHERE id.database_id = ' + CAST(@DatabaseID AS NVARCHAR(30)) + ' - ' + CASE WHEN @ObjectID IS NULL THEN N'' - ELSE N'and id.object_id=' + CAST(@ObjectID AS NVARCHAR(30)) - END + - N'OPTION (RECOMPILE);'; + SET @dsql = @dsql + N' +FROM ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_groups ig +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_details id + ON ig.index_handle = id.index_handle +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_group_stats gs + ON ig.index_group_handle = gs.group_handle +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects so + ON id.object_id=so.object_id +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas sc + ON so.schema_id=sc.schema_id +WHERE id.database_id = ' + CAST(@DatabaseID AS NVARCHAR(30)) + +CASE + WHEN @ObjectID IS NULL + THEN N'' + ELSE N' +AND id.object_id = ' + CAST(@ObjectID AS NVARCHAR(30)) +END + +N' +OPTION (RECOMPILE);'; IF @dsql IS NULL RAISERROR('@dsql is null',16,1); From e928927d138de7440b416dd7d25cd36e0cad3c62 Mon Sep 17 00:00:00 2001 From: Reece Goding <67124261+ReeceGoding@users.noreply.github.com> Date: Sat, 30 Mar 2024 00:11:38 +0000 Subject: [PATCH 532/662] Remove "rn" column in sp_BlitzQueryStore output --- sp_BlitzQueryStore.sql | 33 +++++++++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/sp_BlitzQueryStore.sql b/sp_BlitzQueryStore.sql index 5f23a5225..6536b85e2 100644 --- a/sp_BlitzQueryStore.sql +++ b/sp_BlitzQueryStore.sql @@ -4698,7 +4698,13 @@ JOIN #working_metrics AS wm ON wpt.plan_id = wm.plan_id AND wpt.query_id = wm.query_id ) -SELECT * +SELECT x.database_name, x.query_cost, x.plan_id, x.query_id, x.query_id_all_plan_ids, x.query_sql_text, x.proc_or_function_name, x.query_plan_xml, x.warnings, x.pattern, + x.parameter_sniffing_symptoms, x.top_three_waits, x.missing_indexes, x.implicit_conversion_info, x.cached_execution_parameters, x.count_executions, x.count_compiles, x.total_cpu_time, x.avg_cpu_time, + x.total_duration, x.avg_duration, x.total_logical_io_reads, x.avg_logical_io_reads, + x.total_physical_io_reads, x.avg_physical_io_reads, x.total_logical_io_writes, x.avg_logical_io_writes, x.total_rowcount, x.avg_rowcount, + x.total_query_max_used_memory, x.avg_query_max_used_memory, x.total_tempdb_space_used, x.avg_tempdb_space_used, + x.total_log_bytes_used, x.avg_log_bytes_used, x.total_num_physical_io_reads, x.avg_num_physical_io_reads, + x.first_execution_time, x.last_execution_time, x.last_force_failure_reason_desc, x.context_settings FROM x WHERE x.rn = 1 ORDER BY x.last_execution_time @@ -4728,7 +4734,14 @@ JOIN #working_metrics AS wm ON wpt.plan_id = wm.plan_id AND wpt.query_id = wm.query_id ) -SELECT * +SELECT x.database_name, x.query_cost, x.plan_id, x.query_id, x.query_id_all_plan_ids, x.query_sql_text, x.proc_or_function_name, x.query_plan_xml, x.warnings, x.pattern, + x.parameter_sniffing_symptoms, x.last_force_failure_reason_desc, x.top_three_waits, x.missing_indexes, x.implicit_conversion_info, x.cached_execution_parameters, + x.count_executions, x.count_compiles, x.total_cpu_time, x.avg_cpu_time, + x.total_duration, x.avg_duration, x.total_logical_io_reads, x.avg_logical_io_reads, + x.total_physical_io_reads, x.avg_physical_io_reads, x.total_logical_io_writes, x.avg_logical_io_writes, x.total_rowcount, x.avg_rowcount, + x.total_query_max_used_memory, x.avg_query_max_used_memory, x.total_tempdb_space_used, x.avg_tempdb_space_used, + x.total_log_bytes_used, x.avg_log_bytes_used, x.total_num_physical_io_reads, x.avg_num_physical_io_reads, + x.first_execution_time, x.last_execution_time, x.context_settings FROM x WHERE x.rn = 1 ORDER BY x.last_execution_time @@ -4761,7 +4774,13 @@ JOIN #working_metrics AS wm ON wpt.plan_id = wm.plan_id AND wpt.query_id = wm.query_id ) -SELECT * +SELECT x.database_name, x.query_cost, x.plan_id, x.query_id, x.query_id_all_plan_ids, x.query_sql_text, x.proc_or_function_name, x.warnings, x.pattern, + x.parameter_sniffing_symptoms, x.last_force_failure_reason_desc, x.top_three_waits, x.count_executions, x.count_compiles, x.total_cpu_time, x.avg_cpu_time, + x.total_duration, x.avg_duration, x.total_logical_io_reads, x.avg_logical_io_reads, + x.total_physical_io_reads, x.avg_physical_io_reads, x.total_logical_io_writes, x.avg_logical_io_writes, x.total_rowcount, x.avg_rowcount, + x.total_query_max_used_memory, x.avg_query_max_used_memory, x.total_tempdb_space_used, x.avg_tempdb_space_used, + x.total_log_bytes_used, x.avg_log_bytes_used, x.total_num_physical_io_reads, x.avg_num_physical_io_reads, + x.first_execution_time, x.last_execution_time, x.context_settings FROM x WHERE x.rn = 1 ORDER BY x.last_execution_time @@ -4787,7 +4806,13 @@ JOIN #working_metrics AS wm ON wpt.plan_id = wm.plan_id AND wpt.query_id = wm.query_id ) -SELECT * +SELECT x.database_name, x.plan_id, x.query_id, x.query_id_all_plan_ids, x.query_sql_text, x.query_plan_xml, x.pattern, + x.parameter_sniffing_symptoms, x.top_three_waits, x.count_executions, x.count_compiles, x.total_cpu_time, x.avg_cpu_time, + x.total_duration, x.avg_duration, x.total_logical_io_reads, x.avg_logical_io_reads, + x.total_physical_io_reads, x.avg_physical_io_reads, x.total_logical_io_writes, x.avg_logical_io_writes, x.total_rowcount, x.avg_rowcount, + x.total_query_max_used_memory, x.avg_query_max_used_memory, x.total_tempdb_space_used, x.avg_tempdb_space_used, + x.total_log_bytes_used, x.avg_log_bytes_used, x.total_num_physical_io_reads, x.avg_num_physical_io_reads, + x.first_execution_time, x.last_execution_time, x.last_force_failure_reason_desc, x.context_settings FROM x WHERE x.rn = 1 ORDER BY x.last_execution_time From 089e64722b42399aa4c653e50933d4657598f82a Mon Sep 17 00:00:00 2001 From: Reece Goding <67124261+ReeceGoding@users.noreply.github.com> Date: Sat, 30 Mar 2024 01:04:09 +0000 Subject: [PATCH 533/662] Adding missing RAISERROR to the check parallel plans This is the only warning that was lacking a RAISERROR, so added it in for consistency. --- sp_BlitzQueryStore.sql | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sp_BlitzQueryStore.sql b/sp_BlitzQueryStore.sql index 5f23a5225..9f1c61902 100644 --- a/sp_BlitzQueryStore.sql +++ b/sp_BlitzQueryStore.sql @@ -3022,6 +3022,9 @@ OPTION (RECOMPILE); /* This looks for parallel plans */ + +RAISERROR(N'Checking for parallel plans', 0, 1) WITH NOWAIT; + UPDATE ww SET ww.is_parallel = 1 FROM #working_warnings AS ww From 748f42846a0bfe596cd00c1ac62ee8af95281884 Mon Sep 17 00:00:00 2001 From: Reece Goding <67124261+ReeceGoding@users.noreply.github.com> Date: Sat, 30 Mar 2024 16:31:25 +0000 Subject: [PATCH 534/662] sp_BlitzQueryStore.sql: Fixed various spelling/grammar issues, mostly with spaces. Closes #3472 --- sp_BlitzQueryStore.sql | 48 +++++++++++++++++++++--------------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/sp_BlitzQueryStore.sql b/sp_BlitzQueryStore.sql index 5f23a5225..0e177b324 100644 --- a/sp_BlitzQueryStore.sql +++ b/sp_BlitzQueryStore.sql @@ -31,18 +31,18 @@ GO ALTER PROCEDURE dbo.sp_BlitzQueryStore @Help BIT = 0, - @DatabaseName NVARCHAR(128) = NULL , + @DatabaseName NVARCHAR(128) = NULL, @Top INT = 3, @StartDate DATETIME2 = NULL, @EndDate DATETIME2 = NULL, @MinimumExecutionCount INT = NULL, - @DurationFilter DECIMAL(38,4) = NULL , + @DurationFilter DECIMAL(38,4) = NULL, @StoredProcName NVARCHAR(128) = NULL, @Failed BIT = 0, @PlanIdFilter INT = NULL, @QueryIdFilter INT = NULL, @ExportToExcel BIT = 0, - @HideSummary BIT = 0 , + @HideSummary BIT = 0, @SkipXML BIT = 0, @Debug BIT = 0, @ExpertMode BIT = 0, @@ -307,7 +307,7 @@ IF @Help = 1 UNION ALL SELECT 'implicit_conversion_info', 'XML', - 'Information about the implicit conversion warnings,if any, retrieved from the query plan.' + 'Information about the implicit conversion warnings, if any, retrieved from the query plan.' UNION ALL SELECT 'cached_execution_parameters', 'XML', @@ -323,7 +323,7 @@ IF @Help = 1 UNION ALL SELECT 'total_cpu_time', 'BIGINT', - 'Total CPU time, reported in milliseconds, that was consumed by all executions of this query.' + 'Total CPU time, reported in milliseconds, consumed by all executions of this query.' UNION ALL SELECT 'avg_cpu_time ', 'BIGINT', @@ -416,7 +416,7 @@ IF @Help = 1 END; -/*Making sure your version is copasetic*/ +/*Making sure your version is copacetic*/ IF ( (SELECT CONVERT(NVARCHAR(128), SERVERPROPERTY ('EDITION'))) = 'SQL Azure' ) BEGIN SET @is_azure_db = 1; @@ -512,7 +512,7 @@ SELECT @compatibility_level = d.compatibility_level FROM sys.databases AS d WHERE d.name = @DatabaseName; -RAISERROR('The @DatabaseName you specified ([%s])is running in compatibility level ([%d]).', 0, 1, @DatabaseName, @compatibility_level) WITH NOWAIT; +RAISERROR('The @DatabaseName you specified ([%s]) is running in compatibility level ([%d]).', 0, 1, @DatabaseName, @compatibility_level) WITH NOWAIT; /*Making sure top is set to something if NULL*/ @@ -537,8 +537,8 @@ EXEC sys.sp_executesql @ws_sql, @ws_params, @i_out = @ws_out OUTPUT; SELECT @waitstats = CASE @ws_out WHEN 0 THEN 0 ELSE 1 END; SET @msg = N'Wait stats DMV ' + CASE @waitstats - WHEN 0 THEN N' does not exist, skipping.' - WHEN 1 THEN N' exists, will analyze.' + WHEN 0 THEN N'does not exist, skipping.' + WHEN 1 THEN N'exists, will analyze.' END; RAISERROR(@msg, 0, 1) WITH NOWAIT; @@ -574,8 +574,8 @@ EXEC sys.sp_executesql @nc_sql, @ws_params, @i_out = @nc_out OUTPUT; SELECT @new_columns = CASE @nc_out WHEN 12 THEN 1 ELSE 0 END; SET @msg = N'New query_store_runtime_stats columns ' + CASE @new_columns - WHEN 0 THEN N' do not exist, skipping.' - WHEN 1 THEN N' exist, will analyze.' + WHEN 0 THEN N'do not exist, skipping.' + WHEN 1 THEN N'exist, will analyze.' END; RAISERROR(@msg, 0, 1) WITH NOWAIT; @@ -597,8 +597,8 @@ EXEC sys.sp_executesql @pspo_sql, @pspo_params, @i_out = @pspo_out OUTPUT; SET @pspo_enabled = CASE WHEN @pspo_out = 1 THEN 1 ELSE 0 END; SET @msg = N'Parameter Sensitive Plan Optimization ' + CASE @pspo_enabled - WHEN 0 THEN N' not enabled, skipping.' - WHEN 1 THEN N' enabled, will analyze.' + WHEN 0 THEN N'not enabled, skipping.' + WHEN 1 THEN N'enabled, will analyze.' END; RAISERROR(@msg, 0, 1) WITH NOWAIT; @@ -950,7 +950,7 @@ CREATE TABLE #working_wait_stats WHEN 22 THEN N'SE_REPL_%, REPL_%, HADR_% (but not HADR_THROTTLE_LOG_RATE_GOVERNOR), PWAIT_HADR_%, REPLICA_WRITES, FCB_REPLICA_WRITE, FCB_REPLICA_READ, PWAIT_HADRSIM' WHEN 23 THEN N'LOG_RATE_GOVERNOR, POOL_LOG_RATE_GOVERNOR, HADR_THROTTLE_LOG_RATE_GOVERNOR, INSTANCE_LOG_RATE_GOVERNOR' END, - INDEX wws_ix_ids CLUSTERED ( plan_id) + INDEX wws_ix_ids CLUSTERED (plan_id) ); @@ -3718,7 +3718,7 @@ OR ((PARSENAME(CONVERT(VARCHAR(128), SERVERPROPERTY ('PRODUCTVERSION')), 4)) = 1 BEGIN -RAISERROR(N'Beginning 2017 and 2016 SP2 specfic checks', 0, 1) WITH NOWAIT; +RAISERROR(N'Beginning 2017 and 2016 SP2 specific checks', 0, 1) WITH NOWAIT; IF @ExpertMode > 0 BEGIN @@ -4918,7 +4918,7 @@ BEGIN 'Cursors', 'Dynamic Cursors', 'https://www.brentozar.com/blitzcache/cursors-found-slow-queries/', - 'Dynamic Cursors inhibit parallelism!.'); + 'Dynamic Cursors inhibit parallelism!'); IF EXISTS (SELECT 1/0 FROM #working_warnings @@ -4931,7 +4931,7 @@ BEGIN 'Cursors', 'Fast Forward Cursors', 'https://www.brentozar.com/blitzcache/cursors-found-slow-queries/', - 'Fast forward cursors inhibit parallelism!.'); + 'Fast forward cursors inhibit parallelism!'); IF EXISTS (SELECT 1/0 FROM #working_warnings @@ -5245,7 +5245,7 @@ BEGIN 'Compute Scalar That References A CLR Function', 'This could be trouble if your CLR functions perform data access', 'https://www.brentozar.com/blitzcache/compute-scalar-functions/', - 'May force queries to run serially, run at least once per row, and may result in poor cardinlity estimates.') ; + 'May force queries to run serially, run at least once per row, and may result in poor cardinality estimates.') ; IF EXISTS (SELECT 1/0 @@ -5691,13 +5691,13 @@ BEGIN gi.total_max_log_bytes_mb, gi.total_max_tempdb_space, CONVERT(NVARCHAR(20), gi.flat_date) AS worst_date, - CASE WHEN DATEPART(HOUR, gi.start_range) = 0 THEN ' midnight ' - WHEN DATEPART(HOUR, gi.start_range) <= 12 THEN CONVERT(NVARCHAR(3), DATEPART(HOUR, gi.start_range)) + 'am ' - WHEN DATEPART(HOUR, gi.start_range) > 12 THEN CONVERT(NVARCHAR(3), DATEPART(HOUR, gi.start_range) -12) + 'pm ' + CASE WHEN DATEPART(HOUR, gi.start_range) = 0 THEN 'midnight' + WHEN DATEPART(HOUR, gi.start_range) <= 12 THEN CONVERT(NVARCHAR(3), DATEPART(HOUR, gi.start_range)) + 'am' + WHEN DATEPART(HOUR, gi.start_range) > 12 THEN CONVERT(NVARCHAR(3), DATEPART(HOUR, gi.start_range) -12) + 'pm' END AS worst_start_time, - CASE WHEN DATEPART(HOUR, gi.end_range) = 0 THEN ' midnight ' - WHEN DATEPART(HOUR, gi.end_range) <= 12 THEN CONVERT(NVARCHAR(3), DATEPART(HOUR, gi.end_range)) + 'am ' - WHEN DATEPART(HOUR, gi.end_range) > 12 THEN CONVERT(NVARCHAR(3), DATEPART(HOUR, gi.end_range) -12) + 'pm ' + CASE WHEN DATEPART(HOUR, gi.end_range) = 0 THEN 'midnight' + WHEN DATEPART(HOUR, gi.end_range) <= 12 THEN CONVERT(NVARCHAR(3), DATEPART(HOUR, gi.end_range)) + 'am' + WHEN DATEPART(HOUR, gi.end_range) > 12 THEN CONVERT(NVARCHAR(3), DATEPART(HOUR, gi.end_range) -12) + 'pm' END AS worst_end_time FROM #grouped_interval AS gi ), /*averages*/ From c5c040c45c8fdc792cd19f5c389be91f311f435f Mon Sep 17 00:00:00 2001 From: Reece Goding <67124261+ReeceGoding@users.noreply.github.com> Date: Sat, 30 Mar 2024 17:29:46 +0000 Subject: [PATCH 535/662] sp_BlitzQueryStore.sql: Switch TOP X with TOP (X) Closes #3476 --- sp_BlitzQueryStore.sql | 70 +++++++++++++++++++++--------------------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/sp_BlitzQueryStore.sql b/sp_BlitzQueryStore.sql index 5f23a5225..e1b2c8ed1 100644 --- a/sp_BlitzQueryStore.sql +++ b/sp_BlitzQueryStore.sql @@ -1545,7 +1545,7 @@ RAISERROR(N'Gathering longest duration plans', 0, 1) WITH NOWAIT; SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; SET @sql_select += N' WITH duration_max -AS ( SELECT TOP 1 +AS ( SELECT TOP (1) gi.start_range, gi.end_range FROM #grouped_interval AS gi @@ -1592,7 +1592,7 @@ EXEC sys.sp_executesql @stmt = @sql_select, SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; SET @sql_select += N' WITH duration_max -AS ( SELECT TOP 1 +AS ( SELECT TOP (1) gi.start_range, gi.end_range FROM #grouped_interval AS gi @@ -1643,7 +1643,7 @@ RAISERROR(N'Gathering highest cpu plans', 0, 1) WITH NOWAIT; SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; SET @sql_select += N' WITH cpu_max -AS ( SELECT TOP 1 +AS ( SELECT TOP (1) gi.start_range, gi.end_range FROM #grouped_interval AS gi @@ -1690,7 +1690,7 @@ EXEC sys.sp_executesql @stmt = @sql_select, SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; SET @sql_select += N' WITH cpu_max -AS ( SELECT TOP 1 +AS ( SELECT TOP (1) gi.start_range, gi.end_range FROM #grouped_interval AS gi @@ -1741,7 +1741,7 @@ RAISERROR(N'Gathering highest logical read plans', 0, 1) WITH NOWAIT; SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; SET @sql_select += N' WITH logical_reads_max -AS ( SELECT TOP 1 +AS ( SELECT TOP (1) gi.start_range, gi.end_range FROM #grouped_interval AS gi @@ -1788,7 +1788,7 @@ EXEC sys.sp_executesql @stmt = @sql_select, SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; SET @sql_select += N' WITH logical_reads_max -AS ( SELECT TOP 1 +AS ( SELECT TOP (1) gi.start_range, gi.end_range FROM #grouped_interval AS gi @@ -1839,7 +1839,7 @@ RAISERROR(N'Gathering highest physical read plans', 0, 1) WITH NOWAIT; SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; SET @sql_select += N' WITH physical_read_max -AS ( SELECT TOP 1 +AS ( SELECT TOP (1) gi.start_range, gi.end_range FROM #grouped_interval AS gi @@ -1886,7 +1886,7 @@ EXEC sys.sp_executesql @stmt = @sql_select, SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; SET @sql_select += N' WITH physical_read_max -AS ( SELECT TOP 1 +AS ( SELECT TOP (1) gi.start_range, gi.end_range FROM #grouped_interval AS gi @@ -1937,7 +1937,7 @@ RAISERROR(N'Gathering highest write plans', 0, 1) WITH NOWAIT; SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; SET @sql_select += N' WITH logical_writes_max -AS ( SELECT TOP 1 +AS ( SELECT TOP (1) gi.start_range, gi.end_range FROM #grouped_interval AS gi @@ -1984,7 +1984,7 @@ EXEC sys.sp_executesql @stmt = @sql_select, SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; SET @sql_select += N' WITH logical_writes_max -AS ( SELECT TOP 1 +AS ( SELECT TOP (1) gi.start_range, gi.end_range FROM #grouped_interval AS gi @@ -2035,7 +2035,7 @@ RAISERROR(N'Gathering highest memory use plans', 0, 1) WITH NOWAIT; SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; SET @sql_select += N' WITH memory_max -AS ( SELECT TOP 1 +AS ( SELECT TOP (1) gi.start_range, gi.end_range FROM #grouped_interval AS gi @@ -2081,7 +2081,7 @@ EXEC sys.sp_executesql @stmt = @sql_select, SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; SET @sql_select += N' WITH memory_max -AS ( SELECT TOP 1 +AS ( SELECT TOP (1) gi.start_range, gi.end_range FROM #grouped_interval AS gi @@ -2132,7 +2132,7 @@ RAISERROR(N'Gathering highest row count plans', 0, 1) WITH NOWAIT; SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; SET @sql_select += N' WITH rowcount_max -AS ( SELECT TOP 1 +AS ( SELECT TOP (1) gi.start_range, gi.end_range FROM #grouped_interval AS gi @@ -2188,7 +2188,7 @@ RAISERROR(N'Gathering highest log byte use plans', 0, 1) WITH NOWAIT; SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; SET @sql_select += N' WITH rowcount_max -AS ( SELECT TOP 1 +AS ( SELECT TOP (1) gi.start_range, gi.end_range FROM #grouped_interval AS gi @@ -2236,7 +2236,7 @@ RAISERROR(N'Gathering highest log byte use plans', 0, 1) WITH NOWAIT; SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; SET @sql_select += N' WITH rowcount_max -AS ( SELECT TOP 1 +AS ( SELECT TOP (1) gi.start_range, gi.end_range FROM #grouped_interval AS gi @@ -2287,7 +2287,7 @@ RAISERROR(N'Gathering highest tempdb use plans', 0, 1) WITH NOWAIT; SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; SET @sql_select += N' WITH rowcount_max -AS ( SELECT TOP 1 +AS ( SELECT TOP (1) gi.start_range, gi.end_range FROM #grouped_interval AS gi @@ -2333,7 +2333,7 @@ EXEC sys.sp_executesql @stmt = @sql_select, SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; SET @sql_select += N' WITH rowcount_max -AS ( SELECT TOP 1 +AS ( SELECT TOP (1) gi.start_range, gi.end_range FROM #grouped_interval AS gi @@ -2784,7 +2784,7 @@ IF @waitstats = 1 FROM #working_plan_text AS wpt JOIN ( SELECT wws.plan_id, - top_three_waits = STUFF((SELECT TOP 3 N', ' + wws2.wait_category_desc + N' (' + CONVERT(NVARCHAR(20), SUM(CONVERT(BIGINT, wws2.avg_query_wait_time_ms))) + N' ms) ' + top_three_waits = STUFF((SELECT TOP (3) N', ' + wws2.wait_category_desc + N' (' + CONVERT(NVARCHAR(20), SUM(CONVERT(BIGINT, wws2.avg_query_wait_time_ms))) + N' ms) ' FROM #working_wait_stats AS wws2 WHERE wws.plan_id = wws2.plan_id GROUP BY wws2.wait_category_desc @@ -5702,87 +5702,87 @@ BEGIN FROM #grouped_interval AS gi ), /*averages*/ duration_worst AS ( - SELECT TOP 1 'Your worst avg duration range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg + SELECT TOP (1) 'Your worst avg duration range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg FROM worsts ORDER BY worsts.total_avg_duration_ms DESC ), cpu_worst AS ( - SELECT TOP 1 'Your worst avg cpu range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg + SELECT TOP (1) 'Your worst avg cpu range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg FROM worsts ORDER BY worsts.total_avg_cpu_time_ms DESC ), logical_reads_worst AS ( - SELECT TOP 1 'Your worst avg logical read range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg + SELECT TOP (1) 'Your worst avg logical read range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg FROM worsts ORDER BY worsts.total_avg_logical_io_reads_mb DESC ), physical_reads_worst AS ( - SELECT TOP 1 'Your worst avg physical read range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg + SELECT TOP (1) 'Your worst avg physical read range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg FROM worsts ORDER BY worsts.total_avg_physical_io_reads_mb DESC ), logical_writes_worst AS ( - SELECT TOP 1 'Your worst avg logical write range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg + SELECT TOP (1) 'Your worst avg logical write range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg FROM worsts ORDER BY worsts.total_avg_logical_io_writes_mb DESC ), memory_worst AS ( - SELECT TOP 1 'Your worst avg memory range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg + SELECT TOP (1) 'Your worst avg memory range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg FROM worsts ORDER BY worsts.total_avg_query_max_used_memory_mb DESC ), rowcount_worst AS ( - SELECT TOP 1 'Your worst avg row count range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg + SELECT TOP (1) 'Your worst avg row count range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg FROM worsts ORDER BY worsts.total_rowcount DESC ), logbytes_worst AS ( - SELECT TOP 1 'Your worst avg log bytes range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg + SELECT TOP (1) 'Your worst avg log bytes range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg FROM worsts ORDER BY worsts.total_avg_log_bytes_mb DESC ), tempdb_worst AS ( - SELECT TOP 1 'Your worst avg tempdb range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg + SELECT TOP (1) 'Your worst avg tempdb range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg FROM worsts ORDER BY worsts.total_avg_tempdb_space DESC )/*maxes*/, max_duration_worst AS ( - SELECT TOP 1 'Your worst max duration range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg + SELECT TOP (1) 'Your worst max duration range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg FROM worsts ORDER BY worsts.total_max_duration_ms DESC ), max_cpu_worst AS ( - SELECT TOP 1 'Your worst max cpu range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg + SELECT TOP (1) 'Your worst max cpu range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg FROM worsts ORDER BY worsts.total_max_cpu_time_ms DESC ), max_logical_reads_worst AS ( - SELECT TOP 1 'Your worst max logical read range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg + SELECT TOP (1) 'Your worst max logical read range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg FROM worsts ORDER BY worsts.total_max_logical_io_reads_mb DESC ), max_physical_reads_worst AS ( - SELECT TOP 1 'Your worst max physical read range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg + SELECT TOP (1) 'Your worst max physical read range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg FROM worsts ORDER BY worsts.total_max_physical_io_reads_mb DESC ), max_logical_writes_worst AS ( - SELECT TOP 1 'Your worst max logical write range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg + SELECT TOP (1) 'Your worst max logical write range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg FROM worsts ORDER BY worsts.total_max_logical_io_writes_mb DESC ), max_memory_worst AS ( - SELECT TOP 1 'Your worst max memory range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg + SELECT TOP (1) 'Your worst max memory range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg FROM worsts ORDER BY worsts.total_max_query_max_used_memory_mb DESC ), max_logbytes_worst AS ( - SELECT TOP 1 'Your worst max log bytes range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg + SELECT TOP (1) 'Your worst max log bytes range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg FROM worsts ORDER BY worsts.total_max_log_bytes_mb DESC ), max_tempdb_worst AS ( - SELECT TOP 1 'Your worst max tempdb range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg + SELECT TOP (1) 'Your worst max tempdb range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg FROM worsts ORDER BY worsts.total_max_tempdb_space DESC ) From 1206f83d24c1370e445e09b3c2be1b2faa6504c3 Mon Sep 17 00:00:00 2001 From: Reece Goding <67124261+ReeceGoding@users.noreply.github.com> Date: Sat, 30 Mar 2024 18:19:58 +0000 Subject: [PATCH 536/662] Document the @Version parameters in sp_BlitzQueryStore's @Help = 1 output --- sp_BlitzQueryStore.sql | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/sp_BlitzQueryStore.sql b/sp_BlitzQueryStore.sql index 991a7deca..fec82566e 100644 --- a/sp_BlitzQueryStore.sql +++ b/sp_BlitzQueryStore.sql @@ -243,6 +243,16 @@ IF @Help = 1 N'0', N'When set to 1, more checks are done. Examples: many to many merge joins, row goals, adaptive joins, stats info, bad scans and plan forcing, computed columns that reference scalar UDFs.' UNION ALL + SELECT N'@Version', + N'VARCHAR(30)', + N'NULL', + N'OUTPUT parameter holding version number.' + UNION ALL + SELECT N'@VersionDate', + N'DATETIME', + N'NULL', + N'OUTPUT parameter holding version date.' + UNION ALL SELECT N'@VersionCheckMode', N'BIT', N'0', From 37ff7d597377a13ff89b46f8ab2bbef17d13d9a6 Mon Sep 17 00:00:00 2001 From: Reece Goding <67124261+ReeceGoding@users.noreply.github.com> Date: Sat, 30 Mar 2024 18:58:09 +0000 Subject: [PATCH 537/662] sp_BlitzQueryStore.sql: Add comments for uncommented temp tables. Closes #3478 --- sp_BlitzQueryStore.sql | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sp_BlitzQueryStore.sql b/sp_BlitzQueryStore.sql index 5f23a5225..ecffb62e9 100644 --- a/sp_BlitzQueryStore.sql +++ b/sp_BlitzQueryStore.sql @@ -912,6 +912,7 @@ CREATE TABLE #working_warnings ); +/*This is where we store some wait metrics*/ DROP TABLE IF EXISTS #working_wait_stats; CREATE TABLE #working_wait_stats @@ -954,9 +955,7 @@ CREATE TABLE #working_wait_stats ); -/* -The next three tables hold plan XML parsed out to different degrees -*/ +/*The next seven tables hold plan XML parsed out to different degrees*/ DROP TABLE IF EXISTS #statements; CREATE TABLE #statements @@ -1045,6 +1044,7 @@ CREATE TABLE #trace_flags ); +/*This is where we store the user-facing meaning of each check*/ DROP TABLE IF EXISTS #warning_results; CREATE TABLE #warning_results From e1a736c68aeb035054ee29761f61fe66e3d18fa1 Mon Sep 17 00:00:00 2001 From: Reece Goding <67124261+ReeceGoding@users.noreply.github.com> Date: Sat, 30 Mar 2024 19:14:02 +0000 Subject: [PATCH 538/662] Changed "support" to "make" to make a comment make sense. --- sp_BlitzQueryStore.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sp_BlitzQueryStore.sql b/sp_BlitzQueryStore.sql index ecffb62e9..df7eded5a 100644 --- a/sp_BlitzQueryStore.sql +++ b/sp_BlitzQueryStore.sql @@ -1110,7 +1110,7 @@ CREATE TABLE #conversion_info INDEX cif_ix_ids CLUSTERED (sql_handle, query_hash) ); -/* These tables support the Missing Index details clickable*/ +/* These tables make the Missing Index details clickable*/ DROP TABLE IF EXISTS #missing_index_xml; From 06bcdd1aed9ffadb990bcd3d2e30ac8ede3fd22b Mon Sep 17 00:00:00 2001 From: Reece Goding <67124261+ReeceGoding@users.noreply.github.com> Date: Sat, 30 Mar 2024 19:20:49 +0000 Subject: [PATCH 539/662] TOP 1 -> TOP (1) --- sp_BlitzQueryStore.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sp_BlitzQueryStore.sql b/sp_BlitzQueryStore.sql index e1b2c8ed1..a402c550d 100644 --- a/sp_BlitzQueryStore.sql +++ b/sp_BlitzQueryStore.sql @@ -5663,7 +5663,7 @@ BEGIN 'Global Trace Flags Enabled', 'You have Global Trace Flags enabled on your server', 'https://www.brentozar.com/blitz/trace-flags-enabled-globally/', - 'You have the following Global Trace Flags enabled: ' + (SELECT TOP 1 tf.global_trace_flags FROM #trace_flags AS tf WHERE tf.global_trace_flags IS NOT NULL)) ; + 'You have the following Global Trace Flags enabled: ' + (SELECT TOP (1) tf.global_trace_flags FROM #trace_flags AS tf WHERE tf.global_trace_flags IS NOT NULL)) ; /* From 1e5b058e9e39f5d404cf0418fe8c16a8b8454eaa Mon Sep 17 00:00:00 2001 From: Vlad Drumea <48413726+VladDBA@users.noreply.github.com> Date: Sun, 31 Mar 2024 00:17:34 +0200 Subject: [PATCH 540/662] Added new security related checks - CheckId 258 Check if SQL Server is running as `Local System` or `NT AUTHORITY\SYSTEM` - CheckId 259 Check if SQL Server Agent is running as `Local System` or `NT AUTHORITY\SYSTEM` - CheckID 260 Check if SQL Server service account is a member of the local Administrators group - only done when sp_Blitz is executed with `@CheckServerInfo = 1` - CheckID 261 Check if SQL Server Agent service account is a member of the local Administrators group - only done when sp_Blitz is executed with `@CheckServerInfo = 1` --- sp_Blitz.sql | 200 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 200 insertions(+) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index c5ab74044..1c6e3909f 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -880,6 +880,10 @@ AS INSERT INTO #SkipChecks (CheckID, DatabaseName) VALUES (80, 'tempdb'); /* Max file size set */ INSERT INTO #SkipChecks (CheckID) VALUES (224); /* CheckID 224 - Performance - SSRS/SSAS/SSIS Installed */ INSERT INTO #SkipChecks (CheckID) VALUES (92); /* CheckID 92 - drive space */ + INSERT INTO #SkipChecks (CheckID) VALUES (258);/* CheckID 258 - Security - SQL Server service is running as LocalSystem or NT AUTHORITY\SYSTEM */ + INSERT INTO #SkipChecks (CheckID) VALUES (259);/* CheckID 259 - Security - SQL Server Agent service is running as LocalSystem or NT AUTHORITY\SYSTEM */ + INSERT INTO #SkipChecks (CheckID) VALUES (260); /* CheckID 260 - Security - SQL Server service account is member of Administrators */ + INSERT INTO #SkipChecks (CheckID) VALUES (261); /*CheckID 261 - Security - SQL Server Agent service account is member of Administrators */ INSERT INTO #BlitzResults ( CheckID , Priority , @@ -4991,6 +4995,78 @@ IF @ProductVersionMajor >= 10 END; END; +/* CheckID 258 - Security - SQL Server Service is running as LocalSystem or NT AUTHORITY\SYSTEM */ +IF @ProductVersionMajor >= 10 + AND NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 258 ) + BEGIN + IF EXISTS ( SELECT 1 + FROM sys.all_objects + WHERE [name] = 'dm_server_services' ) + BEGIN + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 258) WITH NOWAIT; + + INSERT INTO [#BlitzResults] + ( [CheckID] , + [Priority] , + [FindingsGroup] , + [Finding] , + [URL] , + [Details] ) + + SELECT + 258 AS [CheckID] , + 1 AS [Priority] , + 'Security' AS [FindingsGroup] , + 'SQL Server is running under the '+ [service_account] +' account' AS [Finding] , + 'https://www.brentozar.com/go/setup' AS [URL] , + 'SQL Server''s service account is '+ [service_account] + +' - meaning that anyone who can use xp_cmdshell can do absolutely anything on the host.' AS [Details] + FROM + [sys].[dm_server_services] + WHERE ([service_account] = 'LocalSystem' + OR LOWER([service_account]) = 'nt authority\system') + AND [servicename] LIKE 'SQL Server%' + AND [servicename] NOT LIKE 'SQL Server Agent%'; + END; + END; + +/* CheckID 259 - Security - SQL Server Agent Service is running as LocalSystem or NT AUTHORITY\SYSTEM */ +IF @ProductVersionMajor >= 10 + AND NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 259 ) + BEGIN + IF EXISTS ( SELECT 1 + FROM sys.all_objects + WHERE [name] = 'dm_server_services' ) + BEGIN + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 259) WITH NOWAIT; + + INSERT INTO [#BlitzResults] + ( [CheckID] , + [Priority] , + [FindingsGroup] , + [Finding] , + [URL] , + [Details] ) + + SELECT + 259 AS [CheckID] , + 1 AS [Priority] , + 'Security' AS [FindingsGroup] , + 'SQL Server Agent is running under the '+ [service_account] +' account' AS [Finding] , + 'https://www.brentozar.com/go/setup' AS [URL] , + 'SQL Server Agent''s service account is '+ [service_account] + +' - meaning that anyone who can create and run jobs can do absolutely anything on the host.' AS [Details] + FROM + [sys].[dm_server_services] + WHERE ([service_account] = 'LocalSystem' + OR LOWER([service_account]) = 'nt authority\system') + AND [servicename] LIKE 'SQL Server Agent%'; + END; + END; /*This checks to see if the Full Text thingy is offline*/ IF @ProductVersionMajor >= 10 @@ -9555,6 +9631,130 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 END; + /* CheckID 260 - Security - SQL Server service account is member of Administrators */ + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 260 ) AND @ProductVersionMajor >= 10 + BEGIN + + IF (SELECT value_in_use FROM sys.configurations WHERE [name] = 'xp_cmdshell') = 1 + AND EXISTS ( SELECT 1 FROM sys.all_objects WHERE [name] = 'dm_server_services' ) + BEGIN + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 260) WITH NOWAIT; + IF OBJECT_ID('tempdb..#localadmins') IS NOT NULL DROP TABLE #localadmins; + CREATE TABLE #localadmins (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 */ + + IF EXISTS (SELECT 1 + FROM #localadmins + 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 Launchpad%')) + BEGIN + INSERT INTO #BlitzResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT + 260 AS CheckID + ,1 AS Priority + ,'Security' AS FindingsGroup + ,'SQL Server''s service account is a local admin' AS Finding + ,'https://www.brentozar.com/go/setup' AS URL + ,'SQL Server''s service account is a member of the local Administrators group - meaning that anyone who can use xp_cmdshell can do anything on the host.' as Details + + END; + + END; + END; + + /* CheckID 261 - Security - SQL Server Agent service account is member of Administrators */ + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 261 ) AND @ProductVersionMajor >= 10 + BEGIN + + IF (SELECT value_in_use FROM sys.configurations WHERE [name] = 'xp_cmdshell') = 1 + AND EXISTS ( SELECT 1 FROM sys.all_objects WHERE [name] = 'dm_server_services' ) + BEGIN + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 261) WITH NOWAIT; + /*If this table exists and CheckId 260 was not skipped, then we're piggybacking off of 260's results */ + IF OBJECT_ID('tempdb..#localadmins') IS NOT NULL + AND NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 260 ) + BEGIN + IF @Debug IN (1, 2) RAISERROR('CheckId [%d] - found #localadmins table from CheckID 260 - no need to call xp_cmdshell again', 0, 1, 261) WITH NOWAIT; + + IF EXISTS (SELECT 1 + FROM #localadmins + WHERE LOWER(cmdshell_output) = ( SELECT LOWER([service_account]) + FROM [sys].[dm_server_services] + WHERE [servicename] LIKE 'SQL Server Agent%' + AND [servicename] NOT LIKE 'SQL Server Launchpad%')) + BEGIN + INSERT INTO #BlitzResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT + 261 AS CheckID + ,1 AS Priority + ,'Security' AS FindingsGroup + ,'SQL Server Agent''s service account is a local admin' AS Finding + ,'https://www.brentozar.com/go/setup' AS URL + ,'SQL Server Agent''s service account is a member of the local Administrators group - meaning that anyone who can create and run jobs can do anything on the host.' as Details + + END; + END; /*piggyback*/ + ELSE /*can't piggyback*/ + BEGIN + /*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 */ + + IF EXISTS (SELECT 1 + FROM #localadmins + WHERE LOWER(cmdshell_output) = ( SELECT LOWER([service_account]) + FROM [sys].[dm_server_services] + WHERE [servicename] LIKE 'SQL Server Agent%' + AND [servicename] NOT LIKE 'SQL Server Launchpad%')) + BEGIN + INSERT INTO #BlitzResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT + 261 AS CheckID + ,1 AS Priority + ,'Security' AS FindingsGroup + ,'SQL Server Agent''s service account is a local admin' AS Finding + ,'https://www.brentozar.com/go/setup' AS URL + ,'SQL Server Agent''s service account is a member of the local Administrators group - meaning that anyone who can create and run jobs can do anything on the host.' as Details + + END; + + END;/*can't piggyback*/ + END; + END; /* CheckID 261 */ END; /* IF @CheckServerInfo = 1 */ END; /* IF ( ( SERVERPROPERTY('ServerName') NOT IN ( SELECT ServerName */ From 90b520ab71eea6bf780d8fc9359cffef39a9bfa7 Mon Sep 17 00:00:00 2001 From: Matt <88049403+MMollart@users.noreply.github.com> Date: Tue, 2 Apr 2024 16:06:51 +0100 Subject: [PATCH 541/662] Update sp_BlitzFirst.sql Corrected URL for Slow Data File Reads event finding --- sp_BlitzFirst.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sp_BlitzFirst.sql b/sp_BlitzFirst.sql index fed15f318..abe5e8cd4 100644 --- a/sp_BlitzFirst.sql +++ b/sp_BlitzFirst.sql @@ -2986,7 +2986,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 50 AS Priority, 'Server Performance' AS FindingGroup, 'Slow Data File Reads' AS Finding, - 'https://www.brentozar.com/go/slow/' AS URL, + 'https://www.brentozar.com/blitz/slow-storage-reads-writes/' AS URL, 'Your server is experiencing PAGEIOLATCH% waits due to slow data file reads. This file is one of the reasons why.' + @LineFeed + 'File: ' + fNow.PhysicalName + @LineFeed + 'Number of reads during the sample: ' + CAST((fNow.num_of_reads - fBase.num_of_reads) AS NVARCHAR(20)) + @LineFeed @@ -3016,7 +3016,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 50 AS Priority, 'Server Performance' AS FindingGroup, 'Slow Log File Writes' AS Finding, - 'https://www.brentozar.com/go/slow/' AS URL, + 'https://www.brentozar.com/blitz/slow-storage-reads-writes/' AS URL, 'Your server is experiencing WRITELOG waits due to slow log file writes. This file is one of the reasons why.' + @LineFeed + 'File: ' + fNow.PhysicalName + @LineFeed + 'Number of writes during the sample: ' + CAST((fNow.num_of_writes - fBase.num_of_writes) AS NVARCHAR(20)) + @LineFeed From 385b984fcac2edeb5703e81c0132bb997ca4939c Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Mon, 15 Apr 2024 10:19:00 -0400 Subject: [PATCH 542/662] #3486 sp_BlitzFirst add ExpertMode = 2 @ExpertMode = 2 skips calling sp_BlitzFirst. Closes #3486. --- sp_BlitzFirst.sql | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/sp_BlitzFirst.sql b/sp_BlitzFirst.sql index fed15f318..a3169793a 100644 --- a/sp_BlitzFirst.sql +++ b/sp_BlitzFirst.sql @@ -228,7 +228,11 @@ IF @LogMessage IS NOT NULL END; IF @SinceStartup = 1 - SELECT @Seconds = 0, @ExpertMode = 1; + BEGIN + SET @Seconds = 0 + IF @ExpertMode = 0 + SET @ExpertMode = 1 + END; IF @OutputType = 'SCHEMA' @@ -3302,7 +3306,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','SQL Re-Compilations/sec', NULL); /* Server Info - SQL Compilations/sec - CheckID 25 */ - IF @ExpertMode = 1 + IF @ExpertMode >= 1 BEGIN IF (@Debug = 1) BEGIN @@ -3325,7 +3329,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, END /* Server Info - SQL Re-Compilations/sec - CheckID 26 */ - IF @ExpertMode = 1 + IF @ExpertMode >= 1 BEGIN IF (@Debug = 1) BEGIN @@ -4674,7 +4678,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, ID, CAST(Details AS NVARCHAR(4000)); END; - ELSE IF @ExpertMode = 1 AND @OutputType <> 'NONE' AND @OutputResultSets LIKE N'%Findings%' + ELSE IF @ExpertMode >= 1 AND @OutputType <> 'NONE' AND @OutputResultSets LIKE N'%Findings%' BEGIN IF @SinceStartup = 0 SELECT r.[Priority] , From 74dc40444c78b10d798bfddfebf50bd8ad6f40c4 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Mon, 15 Apr 2024 10:32:08 -0400 Subject: [PATCH 543/662] #3488 sp_BlitzFirst garbage collection Clarifying warning that can also be caused by memory-optimized TempDB. Closes #3488. --- sp_BlitzFirst.sql | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/sp_BlitzFirst.sql b/sp_BlitzFirst.sql index a3169793a..5eca4facc 100644 --- a/sp_BlitzFirst.sql +++ b/sp_BlitzFirst.sql @@ -3197,8 +3197,10 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 'Garbage Collection in Progress' AS Finding, 'https://www.brentozar.com/go/garbage/' AS URL, CAST(ps.value_delta AS NVARCHAR(50)) + ' rows processed (from SQL Server YYYY XTP Garbage Collection:Rows processed/sec counter)' + @LineFeed - + 'This can happen due to memory pressure (causing In-Memory OLTP to shrink its footprint) or' + @LineFeed - + 'due to transactional workloads that constantly insert/delete data.' AS Details, + + 'This can happen for a few reasons: ' + @LineFeed + + 'Memory-Optimized TempDB, or ' + @LineFeed + + 'transactional workloads that constantly insert/delete data in In-Memory OLTP tables, or ' + @LineFeed + + 'memory pressure (causing In-Memory OLTP to shrink its footprint) or' AS Details, 'Sadly, you cannot choose when garbage collection occurs. This is one of the many gotchas of Hekaton. Learn more: http://nedotter.com/archive/2016/04/row-version-lifecycle-for-in-memory-oltp/' AS HowToStopIt FROM #PerfmonStats ps INNER JOIN #PerfmonStats psComp ON psComp.Pass = 2 AND psComp.object_name LIKE '%XTP Garbage Collection' AND psComp.counter_name = 'Rows processed/sec' AND psComp.value_delta > 100 From 3d37e1c6b9fc26f984b70597d6bb0722246d4c86 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Mon, 15 Apr 2024 13:03:24 -0400 Subject: [PATCH 544/662] #3492 sp_BlitzFirst hide plan cache If OutputResultSets didn't ask for BlitzCache. Closes #3492. --- sp_BlitzFirst.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sp_BlitzFirst.sql b/sp_BlitzFirst.sql index 5eca4facc..dac1f3493 100644 --- a/sp_BlitzFirst.sql +++ b/sp_BlitzFirst.sql @@ -4917,7 +4917,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, INNER JOIN #QueryStats qsFirst ON qsNow.[sql_handle] = qsFirst.[sql_handle] AND qsNow.statement_start_offset = qsFirst.statement_start_offset AND qsNow.statement_end_offset = qsFirst.statement_end_offset AND qsNow.plan_generation_num = qsFirst.plan_generation_num AND qsNow.plan_handle = qsFirst.plan_handle AND qsFirst.Pass = 1 WHERE qsNow.Pass = 2; END; - ELSE + ELSE IF @OutputResultSets LIKE N'%BlitzCache%' BEGIN SELECT 'Plan Cache' AS [Pattern], 'Plan cache not analyzed' AS [Finding], 'Use @CheckProcedureCache = 1 or run sp_BlitzCache for more analysis' AS [More Info], CONVERT(XML, @StockDetailsHeader + 'firstresponderkit.org' + @StockDetailsFooter) AS [Details]; END; From 56baf4ae37648be16e7a68c536c304c155992bf4 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Fri, 19 Apr 2024 07:25:57 -0700 Subject: [PATCH 545/662] #3452 sp_BlitzIndex 2014 compat Revert a few performance tuning changes for SQL Server 2014 RTM compatibility. Closes #3452. --- sp_BlitzIndex.sql | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index f01abb6ca..ac6bd2450 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -1444,7 +1444,9 @@ BEGIN TRY --This change was made because on a table with lots of paritions, the OUTER APPLY was crazy slow. -- get relevant columns from sys.dm_db_partition_stats, sys.partitions and sys.objects - DROP TABLE if exists #dm_db_partition_stats_etc + IF OBJECT_ID('tempdb..#dm_db_partition_stats_etc') IS NOT NULL + DROP TABLE #dm_db_partition_stats_etc; + create table #dm_db_partition_stats_etc ( database_id smallint not null @@ -1462,7 +1464,8 @@ BEGIN TRY ) -- get relevant info from sys.dm_db_index_operational_stats - drop TABLE if exists #dm_db_index_operational_stats + IF OBJECT_ID('tempdb..#dm_db_index_operational_stats') IS NOT NULL + DROP TABLE #dm_db_index_operational_stats; create table #dm_db_index_operational_stats ( database_id smallint not null @@ -1542,8 +1545,10 @@ BEGIN TRY le.lock_escalation_desc, ' + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N'par.data_compression_desc ' ELSE N'null as data_compression_desc ' END + N' ORDER BY ps.object_id, ps.index_id, ps.partition_number - /*OPTION ( RECOMPILE );*/ - OPTION ( RECOMPILE , min_grant_percent = 1); + OPTION ( RECOMPILE ' + + CASE WHEN (PARSENAME(@SQLServerProductVersion, 4) ) > 12 THEN N', min_grant_percent = 1 ' + ELSE N' ' + END + N'); SET @d = CONVERT(VARCHAR(19), GETDATE(), 121) RAISERROR (N''start getting data into #dm_db_index_operational_stats at %s.'',0,1, @d) WITH NOWAIT; @@ -1582,8 +1587,10 @@ BEGIN TRY select os.database_id , os.object_id , os.index_id - , os.partition_number - , os.hobt_id + , os.partition_number ' + + CASE WHEN (PARSENAME(@SQLServerProductVersion, 4) ) > 12 THEN N', os.hobt_id ' + ELSE N', NULL AS hobt_id ' + END + N' , os.leaf_insert_count , os.leaf_delete_count , os.leaf_update_count @@ -1607,7 +1614,10 @@ BEGIN TRY , os.page_io_latch_wait_count , os.page_io_latch_wait_in_ms from ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_index_operational_stats('+ CAST(@DatabaseID AS NVARCHAR(10)) +', NULL, NULL,NULL) AS os - OPTION ( RECOMPILE , min_grant_percent = 1); + OPTION ( RECOMPILE ' + + CASE WHEN (PARSENAME(@SQLServerProductVersion, 4) ) > 12 THEN N', min_grant_percent = 1 ' + ELSE N' ' + END + N'); SET @d = CONVERT(VARCHAR(19), GETDATE(), 121) RAISERROR (N''finished getting data into #dm_db_index_operational_stats at %s.'',0,1, @d) WITH NOWAIT; From e2fc1219a99439a8fdea721105da868f9d6be825 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Fri, 19 Apr 2024 08:31:12 -0700 Subject: [PATCH 546/662] #3481 sp_Blitz security docs Updated links on new alerts, added documentation. Closes #3481. --- Documentation/sp_Blitz_Checks_by_Priority.md | 8 ++++++-- sp_Blitz.sql | 20 ++++++++++---------- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/Documentation/sp_Blitz_Checks_by_Priority.md b/Documentation/sp_Blitz_Checks_by_Priority.md index f870b3fea..0600e82e3 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: 257. -If you want to add a new one, start at 258. +CURRENT HIGH CHECKID: 261. +If you want to add a new one, start at 262. | Priority | FindingsGroup | Finding | URL | CheckID | |----------|-----------------------------|---------------------------------------------------------|------------------------------------------------------------------------|----------| @@ -28,6 +28,10 @@ If you want to add a new one, start at 258. | 1 | Performance | Memory Dangerously Low in NUMA Nodes | https://www.BrentOzar.com/go/max | 159 | | 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 | +| 1 | Security | Dangerous Service Account | https://vladdba.com/SQLServerSvcAccount | 259 | +| 1 | Security | Dangerous Service Account | https://vladdba.com/SQLServerSvcAccount | 260 | +| 1 | Security | Dangerous Service Account | https://vladdba.com/SQLServerSvcAccount | 261 | | 5 | Monitoring | Disabled Internal Monitoring Features | https://msdn.microsoft.com/en-us/library/ms190737.aspx | 177 | | 5 | Reliability | Dangerous Third Party Modules | https://support.microsoft.com/en-us/kb/2033238 | 179 | | 5 | Reliability | Priority Boost Enabled | https://www.BrentOzar.com/go/priorityboost | 126 | diff --git a/sp_Blitz.sql b/sp_Blitz.sql index 1c6e3909f..5353df50e 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -5019,8 +5019,8 @@ IF @ProductVersionMajor >= 10 258 AS [CheckID] , 1 AS [Priority] , 'Security' AS [FindingsGroup] , - 'SQL Server is running under the '+ [service_account] +' account' AS [Finding] , - 'https://www.brentozar.com/go/setup' AS [URL] , + 'Dangerous Service Account' AS [Finding] , + 'https://vladdba.com/SQLServerSvcAccount' AS [URL] , 'SQL Server''s service account is '+ [service_account] +' - meaning that anyone who can use xp_cmdshell can do absolutely anything on the host.' AS [Details] FROM @@ -5056,8 +5056,8 @@ IF @ProductVersionMajor >= 10 259 AS [CheckID] , 1 AS [Priority] , 'Security' AS [FindingsGroup] , - 'SQL Server Agent is running under the '+ [service_account] +' account' AS [Finding] , - 'https://www.brentozar.com/go/setup' AS [URL] , + 'Dangerous Service Account' AS [Finding] , + 'https://vladdba.com/SQLServerSvcAccount' AS [URL] , 'SQL Server Agent''s service account is '+ [service_account] +' - meaning that anyone who can create and run jobs can do absolutely anything on the host.' AS [Details] FROM @@ -9667,8 +9667,8 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 260 AS CheckID ,1 AS Priority ,'Security' AS FindingsGroup - ,'SQL Server''s service account is a local admin' AS Finding - ,'https://www.brentozar.com/go/setup' AS URL + ,'Dangerous Service Account' AS Finding + ,'https://vladdba.com/SQLServerSvcAccount' AS URL ,'SQL Server''s service account is a member of the local Administrators group - meaning that anyone who can use xp_cmdshell can do anything on the host.' as Details END; @@ -9713,8 +9713,8 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 261 AS CheckID ,1 AS Priority ,'Security' AS FindingsGroup - ,'SQL Server Agent''s service account is a local admin' AS Finding - ,'https://www.brentozar.com/go/setup' AS URL + ,'Dangerous Service Account' AS Finding + ,'https://vladdba.com/SQLServerSvcAccount' AS URL ,'SQL Server Agent''s service account is a member of the local Administrators group - meaning that anyone who can create and run jobs can do anything on the host.' as Details END; @@ -9746,8 +9746,8 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 261 AS CheckID ,1 AS Priority ,'Security' AS FindingsGroup - ,'SQL Server Agent''s service account is a local admin' AS Finding - ,'https://www.brentozar.com/go/setup' AS URL + ,'Dangerous Service Account' AS Finding + ,'https://vladdba.com/SQLServerSvcAccount' AS URL ,'SQL Server Agent''s service account is a member of the local Administrators group - meaning that anyone who can create and run jobs can do anything on the host.' as Details END; From b272e89171bc29e4a88e24ec3a4ae3e486293f4b Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Fri, 19 Apr 2024 08:38:45 -0700 Subject: [PATCH 547/662] #3468 updating supported versions Clarifying that we only support versions that Microsoft currently supports. Closes #3468. --- CONTRIBUTING.md | 2 +- README.md | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a785bb1a1..8ed84478c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -22,7 +22,7 @@ If you can't find a similar issue, go ahead and open your own. Include as much d Open source is community-built software. Anyone is welcome to build things that would help make their job easier. -Open source isn't free development, though. Working on these scripts is hard work: they have to work on case-sensitive instances, and on all supported versions of SQL Server (currently 2008 through 2017.) If you just waltz in and say, "Someone please bake me a cake," you're probably not going to get a cake. +Open source isn't free development, though. Working on these scripts is hard work: they have to work on case-sensitive instances, and on all versions of SQL Server that Microsoft currently supports. If you just waltz in and say, "Someone please bake me a cake," you're probably not going to get a cake. If you want something, you're going to either need to build it yourself, or convince someone else to devote their free time to your feature request. You can do that by sponsoring development (offering to hire a developer to build it for you), or getting people excited enough that they volunteer to build it for you. diff --git a/README.md b/README.md index 343d5b96b..1ff8d59bb 100644 --- a/README.md +++ b/README.md @@ -43,12 +43,13 @@ To install, [download the latest release ZIP](https://github.com/BrentOzarULTD/S The First Responder Kit runs on: -* SQL Server 2012, 2014, 2016, 2017, 2019, 2022 on Windows - fully supported. +* SQL Server on Windows - all versions that Microsoft supports. For end of support dates, check out the "Support Ends" column at https://sqlserverupdates.com. * SQL Server on Linux - yes, fully supported except sp_AllNightLog and sp_DatabaseRestore, which require xp_cmdshell, which Microsoft doesn't provide on Linux. -* SQL Server 2008 R2 and earlier - not supported since it's out of Microsoft support, but check the Deprecated folder for older versions of the scripts which may work, depending on your versions and compatibility levels. * Amazon RDS SQL Server - fully supported. * Azure SQL DB - not supported. Some of the procedures work, but some don't, and Microsoft has a tendency to change DMVs in Azure without warning, so we don't put any effort into supporting it. If it works, great! If not, any changes to make it work would be on you. [See the contributing.md file](CONTRIBUTING.md) for how to do that. +If you're stuck with versions of SQL Server that Microsoft no longer supports, like SQL Server 2008, check the Deprecated folder for older versions of the scripts which may work, depending on your versions and compatibility levels. + ## How to Install the Scripts There are three installation scripts. Choose the one that most suits your needs: From e8cedfafaf8ffee352be6ea278e85555e0f3b13f Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Fri, 19 Apr 2024 08:43:40 -0700 Subject: [PATCH 548/662] #3449 sp_BlitzQueryStore debug message Making it more clear what each query is doing. Closes #3449. --- sp_BlitzQueryStore.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sp_BlitzQueryStore.sql b/sp_BlitzQueryStore.sql index b7f7f940a..45405097f 100644 --- a/sp_BlitzQueryStore.sql +++ b/sp_BlitzQueryStore.sql @@ -2193,7 +2193,7 @@ RAISERROR(N'Gathering new 2017 new column info...', 0, 1) WITH NOWAIT; /*Get highest log byte count plans*/ -RAISERROR(N'Gathering highest log byte use plans', 0, 1) WITH NOWAIT; +RAISERROR(N'Gathering highest log byte use plans by average log bytes mb', 0, 1) WITH NOWAIT; SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; SET @sql_select += N' @@ -2241,7 +2241,7 @@ EXEC sys.sp_executesql @stmt = @sql_select, @params = @sp_params, @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; -RAISERROR(N'Gathering highest log byte use plans', 0, 1) WITH NOWAIT; +RAISERROR(N'Gathering highest log byte use plans by max log bytes mb', 0, 1) WITH NOWAIT; SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; SET @sql_select += N' From 57c0c6ebb70035435a677d9e1f38bf6b8b81c423 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Fri, 19 Apr 2024 09:41:24 -0700 Subject: [PATCH 549/662] #3500 deprecating procs Moving sp_AllNightLog, sp_BlitzInMemoryOLTP, and sp_BlitzQueryStore into the deprecated folder. --- .../sp_AllNightLog.sql | 0 .../sp_AllNightLog_Setup.sql | 0 .../sp_BlitzInMemoryOLTP.sql | 0 .../sp_BlitzQueryStore.sql | 0 Documentation/Development/Merge Blitz.ps1 | 25 +- Install-All-Scripts.sql | 65045 +++++++--------- ...tz-No-Query-Store.sql => Install-Azure.sql | 60118 ++++++-------- Install-Core-Blitz-With-Query-Store.sql | 43418 ----------- README.md | 74 +- 9 files changed, 53152 insertions(+), 115528 deletions(-) rename sp_AllNightLog.sql => Deprecated/sp_AllNightLog.sql (100%) rename sp_AllNightLog_Setup.sql => Deprecated/sp_AllNightLog_Setup.sql (100%) rename sp_BlitzInMemoryOLTP.sql => Deprecated/sp_BlitzInMemoryOLTP.sql (100%) rename sp_BlitzQueryStore.sql => Deprecated/sp_BlitzQueryStore.sql (100%) rename Install-Core-Blitz-No-Query-Store.sql => Install-Azure.sql (70%) delete mode 100644 Install-Core-Blitz-With-Query-Store.sql diff --git a/sp_AllNightLog.sql b/Deprecated/sp_AllNightLog.sql similarity index 100% rename from sp_AllNightLog.sql rename to Deprecated/sp_AllNightLog.sql diff --git a/sp_AllNightLog_Setup.sql b/Deprecated/sp_AllNightLog_Setup.sql similarity index 100% rename from sp_AllNightLog_Setup.sql rename to Deprecated/sp_AllNightLog_Setup.sql diff --git a/sp_BlitzInMemoryOLTP.sql b/Deprecated/sp_BlitzInMemoryOLTP.sql similarity index 100% rename from sp_BlitzInMemoryOLTP.sql rename to Deprecated/sp_BlitzInMemoryOLTP.sql diff --git a/sp_BlitzQueryStore.sql b/Deprecated/sp_BlitzQueryStore.sql similarity index 100% rename from sp_BlitzQueryStore.sql rename to Deprecated/sp_BlitzQueryStore.sql diff --git a/Documentation/Development/Merge Blitz.ps1 b/Documentation/Development/Merge Blitz.ps1 index 14ab2ec06..735a0903b 100644 --- a/Documentation/Development/Merge Blitz.ps1 +++ b/Documentation/Development/Merge Blitz.ps1 @@ -3,29 +3,14 @@ $FilePath = "/Users/brentozar/LocalOnly/Github/SQL-Server-First-Responder-Kit" $SqlVersionsPath = "$FilePath/SqlServerVersions.sql" $BlitzFirstPath = "$FilePath/sp_BlitzFirst.sql" -#All Core Blitz Without sp_BlitzQueryStore +#Azure - skip sp_Blitz, sp_BlitzBackups, sp_DatabaseRestore, sp_ineachdb Get-ChildItem -Path "$FilePath" -Filter "sp_Blitz*.sql" | -Where-Object { $_.FullName -notlike "*sp_BlitzQueryStore.sql*" -and $_.FullName -notlike "*sp_BlitzInMemoryOLTP*" -and $_.FullName -notlike "*sp_BlitzFirst*"} | +Where-Object { $_.FullName -notlike "*sp_Blitz.sql*" -and $_.FullName -notlike "*sp_BlitzBackups*" -and $_.FullName -notlike "*sp_DatabaseRestore*"} | ForEach-Object { Get-Content $_.FullName } | -Set-Content -Path "$FilePath/Install-Core-Blitz-No-Query-Store.sql" -Force -#append script to (re-)create SqlServerVersions Table (https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2429) -if ( test-path "$SqlVersionsPath") - { Add-Content -Path "$FilePath/Install-Core-Blitz-No-Query-Store.sql" -Value (Get-Content -Path "$SqlVersionsPath")} -if ( test-path "$BlitzFirstPath") - { Add-Content -Path "$FilePath/Install-Core-Blitz-No-Query-Store.sql" -Value (Get-Content -Path "$BlitzFirstPath")} - - -#All Core Blitz With sp_BlitzQueryStore -Get-ChildItem -Path "$FilePath" -Filter "sp_Blitz*.sql" | -Where-Object { $_.FullName -notlike "*sp_BlitzInMemoryOLTP*" -and $_.FullName -notlike "*sp_BlitzFirst*"} | -ForEach-Object { Get-Content $_.FullName } | -Set-Content -Path "$FilePath/Install-Core-Blitz-With-Query-Store.sql" -Force -#append script to (re-)create SqlServerVersions Table (https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2429) -if ( test-path "$SqlVersionsPath") - { Add-Content -Path "$FilePath/Install-Core-Blitz-With-Query-Store.sql" -Value (Get-Content -Path "$SqlVersionsPath")} +Set-Content -Path "$FilePath/Install-Azure.sql" -Force if ( test-path "$BlitzFirstPath") - { Add-Content -Path "$FilePath/Install-Core-Blitz-With-Query-Store.sql" -Value (Get-Content -Path "$BlitzFirstPath")} - + { Add-Content -Path "$FilePath/Install-All-Scripts.sql" -Value (Get-Content -Path "$BlitzFirstPath")} + #All Scripts Get-ChildItem -Path "$FilePath" -Filter "sp_*.sql" | diff --git a/Install-All-Scripts.sql b/Install-All-Scripts.sql index 48ce87969..bb75555d7 100644 --- a/Install-All-Scripts.sql +++ b/Install-All-Scripts.sql @@ -1,4275 +1,5817 @@ -SET ANSI_NULLS ON; -SET ANSI_PADDING ON; -SET ANSI_WARNINGS ON; -SET ARITHABORT ON; -SET CONCAT_NULL_YIELDS_NULL ON; -SET QUOTED_IDENTIFIER ON; -SET STATISTICS IO OFF; -SET STATISTICS TIME OFF; -GO - -IF OBJECT_ID('dbo.sp_AllNightLog_Setup') IS NULL - EXEC ('CREATE PROCEDURE dbo.sp_AllNightLog_Setup AS RETURN 0;'); +IF OBJECT_ID('dbo.sp_Blitz') IS NULL + EXEC ('CREATE PROCEDURE dbo.sp_Blitz AS RETURN 0;'); GO - -ALTER PROCEDURE dbo.sp_AllNightLog_Setup - @RPOSeconds BIGINT = 30, - @RTOSeconds BIGINT = 30, - @BackupPath NVARCHAR(MAX) = NULL, - @RestorePath NVARCHAR(MAX) = NULL, - @Jobs TINYINT = 10, - @RunSetup BIT = 0, - @UpdateSetup BIT = 0, - @EnableBackupJobs INT = NULL, - @EnableRestoreJobs INT = NULL, - @Debug BIT = 0, - @FirstFullBackup BIT = 0, - @FirstDiffBackup BIT = 0, - @MoveFiles BIT = 1, - @Help BIT = 0, - @Version VARCHAR(30) = NULL OUTPUT, - @VersionDate DATETIME = NULL OUTPUT, - @VersionCheckMode BIT = 0 +ALTER PROCEDURE [dbo].[sp_Blitz] + @Help TINYINT = 0 , + @CheckUserDatabaseObjects TINYINT = 1 , + @CheckProcedureCache TINYINT = 0 , + @OutputType VARCHAR(20) = 'TABLE' , + @OutputProcedureCache TINYINT = 0 , + @CheckProcedureCacheFilter VARCHAR(10) = NULL , + @CheckServerInfo TINYINT = 0 , + @SkipChecksServer NVARCHAR(256) = NULL , + @SkipChecksDatabase NVARCHAR(256) = NULL , + @SkipChecksSchema NVARCHAR(256) = NULL , + @SkipChecksTable NVARCHAR(256) = NULL , + @IgnorePrioritiesBelow INT = NULL , + @IgnorePrioritiesAbove INT = NULL , + @OutputServerName NVARCHAR(256) = NULL , + @OutputDatabaseName NVARCHAR(256) = NULL , + @OutputSchemaName NVARCHAR(256) = NULL , + @OutputTableName NVARCHAR(256) = NULL , + @OutputXMLasNVARCHAR TINYINT = 0 , + @EmailRecipients VARCHAR(MAX) = NULL , + @EmailProfile sysname = NULL , + @SummaryMode TINYINT = 0 , + @BringThePain TINYINT = 0 , + @UsualDBOwner sysname = NULL , + @SkipBlockingChecks TINYINT = 1 , + @Debug TINYINT = 0 , + @Version VARCHAR(30) = NULL OUTPUT, + @VersionDate DATETIME = NULL OUTPUT, + @VersionCheckMode BIT = 0 WITH RECOMPILE AS -SET NOCOUNT ON; -SET STATISTICS XML OFF; - -BEGIN; + SET NOCOUNT ON; + SET STATISTICS XML OFF; + SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + -SELECT @Version = '8.19', @VersionDate = '20240222'; + SELECT @Version = '8.19', @VersionDate = '20240222'; + SET @OutputType = UPPER(@OutputType); -IF(@VersionCheckMode = 1) -BEGIN - RETURN; -END; + IF(@VersionCheckMode = 1) + BEGIN + RETURN; + END; -IF @Help = 1 + IF @Help = 1 + BEGIN + PRINT ' + /* + sp_Blitz from http://FirstResponderKit.org + + This script checks the health of your SQL Server and gives you a prioritized + to-do list of the most urgent things you should consider fixing. -BEGIN + To learn more, visit http://FirstResponderKit.org where you can download new + versions for free, watch training videos on how it works, get more info on + the findings, contribute your own code, and more. - PRINT ' - /* + Known limitations of this version: + - Only Microsoft-supported versions of SQL Server. Sorry, 2005 and 2000. + - If a database name has a question mark in it, some tests will fail. Gotta + love that unsupported sp_MSforeachdb. + - If you have offline databases, sp_Blitz fails the first time you run it, + but does work the second time. (Hoo, boy, this will be fun to debug.) + - @OutputServerName will output QueryPlans as NVARCHAR(MAX) since Microsoft + has refused to support XML columns in Linked Server queries. The bug is now + 16 years old! *~ \o/ ~* + Unknown limitations of this version: + - None. (If we knew them, they would be known. Duh.) - sp_AllNightLog_Setup from http://FirstResponderKit.org - - This script sets up a database, tables, rows, and jobs for sp_AllNightLog, including: + Changes - for the full list of improvements and fixes in this version, see: + https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/ - * Creates a database - * Right now it''s hard-coded to use msdbCentral, that might change later - - * Creates tables in that database! - * dbo.backup_configuration - * Hold variables used by stored proc to make runtime decisions - * RPO: Seconds, how often we look for databases that need log backups - * Backup Path: The path we feed to Ola H''s backup proc - * dbo.backup_worker - * Holds list of databases and some information that helps our Agent jobs figure out if they need to take another log backup - - * Creates tables in msdb - * dbo.restore_configuration - * Holds variables used by stored proc to make runtime decisions - * RTO: Seconds, how often to look for log backups to restore - * Restore Path: The path we feed to sp_DatabaseRestore - * Move Files: Whether to move files to default data/log directories. - * dbo.restore_worker - * Holds list of databases and some information that helps our Agent jobs figure out if they need to look for files to restore - - * Creates agent jobs - * 1 job that polls sys.databases for new entries - * 10 jobs that run to take log backups - * Based on a queue table - * Requires Ola Hallengren''s Database Backup stored proc - - To learn more, visit http://FirstResponderKit.org where you can download new - versions for free, watch training videos on how it works, get more info on - the findings, contribute your own code, and more. - - Known limitations of this version: - - Only Microsoft-supported versions of SQL Server. Sorry, 2005 and 2000! And really, maybe not even anything less than 2016. Heh. - - The repository database name is hard-coded to msdbCentral. - - Unknown limitations of this version: - - None. (If we knew them, they would be known. Duh.) - - Changes - for the full list of improvements and fixes in this version, see: - https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/ - - - Parameter explanations: - - @RunSetup BIT, defaults to 0. When this is set to 1, it will run the setup portion to create database, tables, and worker jobs. - @UpdateSetup BIT, defaults to 0. When set to 1, will update existing configs for RPO/RTO and database backup/restore paths. - @RPOSeconds BIGINT, defaults to 30. Value in seconds you want to use to determine if a new log backup needs to be taken. - @BackupPath NVARCHAR(MAX), This is REQUIRED if @Runsetup=1. This tells Ola''s job where to put backups. - @MoveFiles BIT, defaults to 1. When this is set to 1, it will move files to default data/log directories - @Debug BIT, defaults to 0. Whent this is set to 1, it prints out dynamic SQL commands - - Sample call: - EXEC dbo.sp_AllNightLog_Setup - @RunSetup = 1, - @RPOSeconds = 30, - @BackupPath = N''M:\MSSQL\Backup'', - @Debug = 1 + Parameter explanations: + @CheckUserDatabaseObjects 1=review user databases for triggers, heaps, etc. Takes more time for more databases and objects. + @CheckServerInfo 1=show server info like CPUs, memory, virtualization + @CheckProcedureCache 1=top 20-50 resource-intensive cache plans and analyze them for common performance issues. + @OutputProcedureCache 1=output the top 20-50 resource-intensive plans even if they did not trigger an alarm + @CheckProcedureCacheFilter ''CPU'' | ''Reads'' | ''Duration'' | ''ExecCount'' + @OutputType ''TABLE''=table | ''COUNT''=row with number found | ''MARKDOWN''=bulleted list (including server info, excluding security findings) | ''SCHEMA''=version and field list | ''XML'' =table output as XML | ''NONE'' = none + @IgnorePrioritiesBelow 50=ignore priorities below 50 + @IgnorePrioritiesAbove 50=ignore priorities above 50 + @Debug 0=silent (Default) | 1=messages per step | 2=outputs dynamic queries + For the rest of the parameters, see https://www.BrentOzar.com/blitz/documentation for details. - For more documentation: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/ - - MIT License - - Copyright (c) Brent Ozar Unlimited - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: + MIT License - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. + Copyright for portions of sp_Blitz are held by Microsoft as part of project + tigertoolbox and are provided under the MIT license: + https://github.com/Microsoft/tigertoolbox - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE. - - - */'; - -RETURN; -END; /* IF @Help = 1 */ - -DECLARE @database NVARCHAR(128) = NULL; --Holds the database that's currently being processed -DECLARE @error_number INT = NULL; --Used for TRY/CATCH -DECLARE @error_severity INT; --Used for TRY/CATCH -DECLARE @error_state INT; --Used for TRY/CATCH -DECLARE @msg NVARCHAR(4000) = N''; --Used for RAISERROR -DECLARE @rpo INT; --Used to hold the RPO value in our configuration table -DECLARE @backup_path NVARCHAR(MAX); --Used to hold the backup path in our configuration table -DECLARE @db_sql NVARCHAR(MAX) = N''; --Used to hold the dynamic SQL to create msdbCentral -DECLARE @tbl_sql NVARCHAR(MAX) = N''; --Used to hold the dynamic SQL that creates tables in msdbCentral -DECLARE @database_name NVARCHAR(256) = N'msdbCentral'; --Used to hold the name of the database we create to centralize data - --Right now it's hardcoded to msdbCentral, but I made it dynamic in case that changes down the line - - -/*These variables control the loop to create/modify jobs*/ -DECLARE @job_sql NVARCHAR(MAX) = N''; --Used to hold the dynamic SQL that creates Agent jobs -DECLARE @counter INT = 0; --For looping to create 10 Agent jobs -DECLARE @job_category NVARCHAR(MAX) = N'''Database Maintenance'''; --Job category -DECLARE @job_owner NVARCHAR(128) = QUOTENAME(SUSER_SNAME(0x01), ''''); -- Admin user/owner -DECLARE @jobs_to_change TABLE(name SYSNAME); -- list of jobs we need to enable or disable -DECLARE @current_job_name SYSNAME; -- While looping through Agent jobs to enable or disable -DECLARE @active_start_date INT = (CONVERT(INT, CONVERT(VARCHAR(10), GETDATE(), 112))); -DECLARE @started_waiting_for_jobs DATETIME; --We need to wait for a while when disabling jobs - -/*Specifically for Backups*/ -DECLARE @job_name_backups NVARCHAR(MAX) = N'''sp_AllNightLog_Backup_Job_'''; --Name of log backup job -DECLARE @job_description_backups NVARCHAR(MAX) = N'''This is a worker for the purposes of taking log backups from msdbCentral.dbo.backup_worker queue table.'''; --Job description -DECLARE @job_command_backups NVARCHAR(MAX) = N'''EXEC sp_AllNightLog @Backup = 1'''; --Command the Agent job will run - -/*Specifically for Restores*/ -DECLARE @job_name_restores NVARCHAR(MAX) = N'''sp_AllNightLog_Restore_Job_'''; --Name of log backup job -DECLARE @job_description_restores NVARCHAR(MAX) = N'''This is a worker for the purposes of restoring log backups from msdb.dbo.restore_worker queue table.'''; --Job description -DECLARE @job_command_restores NVARCHAR(MAX) = N'''EXEC sp_AllNightLog @Restore = 1'''; --Command the Agent job will run - - -/* + All other copyrights for sp_Blitz are held by Brent Ozar Unlimited. -Sanity check some variables + Copyright (c) Brent Ozar Unlimited -*/ + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. -IF ((@RunSetup = 0 OR @RunSetup IS NULL) AND (@UpdateSetup = 0 OR @UpdateSetup IS NULL)) + */'; + RETURN; + END; /* @Help = 1 */ + ELSE IF @OutputType = 'SCHEMA' BEGIN + SELECT FieldList = '[Priority] TINYINT, [FindingsGroup] VARCHAR(50), [Finding] VARCHAR(200), [DatabaseName] NVARCHAR(128), [URL] VARCHAR(200), [Details] NVARCHAR(4000), [QueryPlan] NVARCHAR(MAX), [QueryPlanFiltered] NVARCHAR(MAX), [CheckID] INT'; - RAISERROR('You have to either run setup or update setup. You can''t not do neither nor, if you follow. Or not.', 0, 1) WITH NOWAIT; - - RETURN; + END;/* IF @OutputType = 'SCHEMA' */ + ELSE + BEGIN - END; + DECLARE @StringToExecute NVARCHAR(4000) + ,@curr_tracefilename NVARCHAR(500) + ,@base_tracefilename NVARCHAR(500) + ,@indx int + ,@query_result_separator CHAR(1) + ,@EmailSubject NVARCHAR(255) + ,@EmailBody NVARCHAR(MAX) + ,@EmailAttachmentFilename NVARCHAR(255) + ,@ProductVersion NVARCHAR(128) + ,@ProductVersionMajor DECIMAL(10,2) + ,@ProductVersionMinor DECIMAL(10,2) + ,@CurrentName NVARCHAR(128) + ,@CurrentDefaultValue NVARCHAR(200) + ,@CurrentCheckID INT + ,@CurrentPriority INT + ,@CurrentFinding VARCHAR(200) + ,@CurrentURL VARCHAR(200) + ,@CurrentDetails NVARCHAR(4000) + ,@MsSinceWaitsCleared DECIMAL(38,0) + ,@CpuMsSinceWaitsCleared DECIMAL(38,0) + ,@ResultText NVARCHAR(MAX) + ,@crlf NVARCHAR(2) + ,@Processors int + ,@NUMANodes int + ,@MinServerMemory bigint + ,@MaxServerMemory bigint + ,@ColumnStoreIndexesInUse bit + ,@TraceFileIssue bit + -- Flag for Windows OS to help with Linux support + ,@IsWindowsOperatingSystem BIT + ,@DaysUptime NUMERIC(23,2) + /* For First Responder Kit consistency check:*/ + ,@spBlitzFullName VARCHAR(1024) + ,@BlitzIsOutdatedComparedToOthers BIT + ,@tsql NVARCHAR(MAX) + ,@VersionCheckModeExistsTSQL NVARCHAR(MAX) + ,@BlitzProcDbName VARCHAR(256) + ,@ExecRet INT + ,@InnerExecRet INT + ,@TmpCnt INT + ,@PreviousComponentName VARCHAR(256) + ,@PreviousComponentFullPath VARCHAR(1024) + ,@CurrentStatementId INT + ,@CurrentComponentSchema VARCHAR(256) + ,@CurrentComponentName VARCHAR(256) + ,@CurrentComponentType VARCHAR(256) + ,@CurrentComponentVersionDate DATETIME2 + ,@CurrentComponentFullName VARCHAR(1024) + ,@CurrentComponentMandatory BIT + ,@MaximumVersionDate DATETIME + ,@StatementCheckName VARCHAR(256) + ,@StatementOutputsCounter BIT + ,@OutputCounterExpectedValue INT + ,@StatementOutputsExecRet BIT + ,@StatementOutputsDateTime BIT + ,@CurrentComponentMandatoryCheckOK BIT + ,@CurrentComponentVersionCheckModeOK BIT + ,@canExitLoop BIT + ,@frkIsConsistent BIT + ,@NeedToTurnNumericRoundabortBackOn BIT + ,@sa bit = 1 + ,@SUSER_NAME sysname = SUSER_SNAME() + ,@SkipDBCC bit = 0 + ,@SkipTrace bit = 0 + ,@SkipXPRegRead bit = 0 + ,@SkipXPFixedDrives bit = 0 + ,@SkipXPCMDShell bit = 0 + ,@SkipMaster bit = 0 + ,@SkipMSDB_objs bit = 0 + ,@SkipMSDB_jobs bit = 0 + ,@SkipModel bit = 0 + ,@SkipTempDB bit = 0 + ,@SkipValidateLogins bit = 0 + ,@SkipGetAlertInfo bit = 0 + DECLARE + @db_perms table + ( + database_name sysname, + permission_name sysname + ); -/* + INSERT + @db_perms + ( + database_name, + permission_name + ) + SELECT + database_name = + DB_NAME(d.database_id), + fmp.permission_name + FROM sys.databases AS d + CROSS APPLY fn_my_permissions(d.name, 'DATABASE') AS fmp + WHERE fmp.permission_name = N'SELECT'; /*Databases where we don't have read permissions*/ + + /* End of declarations for First Responder Kit consistency check:*/ + ; -Should be a positive number + /* Create temp table for check 73 */ + IF OBJECT_ID('tempdb..#AlertInfo') IS NOT NULL + EXEC sp_executesql N'DROP TABLE #AlertInfo;'; -*/ + CREATE TABLE #AlertInfo + ( + FailSafeOperator NVARCHAR(255) , + NotificationMethod INT , + ForwardingServer NVARCHAR(255) , + ForwardingSeverity INT , + PagerToTemplate NVARCHAR(255) , + PagerCCTemplate NVARCHAR(255) , + PagerSubjectTemplate NVARCHAR(255) , + PagerSendSubjectOnly NVARCHAR(255) , + ForwardAlways INT + ); -IF (@RPOSeconds < 0) + /* Create temp table for check 2301 */ + IF OBJECT_ID('tempdb..#InvalidLogins') IS NOT NULL + EXEC sp_executesql N'DROP TABLE #InvalidLogins;'; + + CREATE TABLE #InvalidLogins + ( + LoginSID varbinary(85), + LoginName VARCHAR(256) + ); - BEGIN - RAISERROR('Please choose a positive number for @RPOSeconds', 0, 1) WITH NOWAIT; + /*Starting permissions checks here, but only if we're not a sysadmin*/ + IF + ( + SELECT + sa = + ISNULL + ( + IS_SRVROLEMEMBER(N'sysadmin'), + 0 + ) + ) = 0 + BEGIN + IF @Debug IN (1, 2) RAISERROR('User not SA, checking permissions', 0, 1) WITH NOWAIT; + + SET @sa = 0; /*Setting this to 0 to skip DBCC COMMANDS*/ - RETURN; - END; + IF NOT EXISTS + ( + SELECT + 1/0 + FROM sys.fn_my_permissions(NULL, NULL) AS fmp + WHERE fmp.permission_name = N'VIEW SERVER STATE' + ) + BEGIN + RAISERROR('The user %s does not have VIEW SERVER STATE permissions.', 0, 11, @SUSER_NAME) WITH NOWAIT; + RETURN; + END; /*If we don't have this, we can't do anything at all.*/ + IF NOT EXISTS + ( + SELECT + 1/0 + FROM fn_my_permissions(N'sys.traces', N'OBJECT') AS fmp + WHERE fmp.permission_name = N'ALTER' + ) + BEGIN + SET @SkipTrace = 1; + END; /*We need this permission to execute trace stuff, apparently*/ -/* + IF NOT EXISTS + ( + SELECT + 1/0 + FROM fn_my_permissions(N'xp_fixeddrives', N'OBJECT') AS fmp + WHERE fmp.permission_name = N'EXECUTE' + ) + BEGIN + SET @SkipXPFixedDrives = 1; + END; /*Need execute on xp_fixeddrives*/ -Probably shouldn't be more than 20 + IF NOT EXISTS + ( + SELECT + 1/0 + FROM fn_my_permissions(N'xp_cmdshell', N'OBJECT') AS fmp + WHERE fmp.permission_name = N'EXECUTE' + ) + BEGIN + SET @SkipXPCMDShell = 1; + END; /*Need execute on xp_cmdshell*/ -*/ + IF ISNULL(@SkipValidateLogins, 0) != 1 /*If @SkipValidateLogins hasn't been set to 1 by the caller*/ + BEGIN + BEGIN TRY + /* Try to fill the table for check 2301 */ + INSERT INTO #InvalidLogins + ( + [LoginSID] + ,[LoginName] + ) + EXEC sp_validatelogins; + + SET @SkipValidateLogins = 0; /*We can execute sp_validatelogins*/ + END TRY + BEGIN CATCH + SET @SkipValidateLogins = 1; /*We have don't have execute rights or sp_validatelogins throws an error so skip it*/ + END CATCH; + END; /*Need execute on sp_validatelogins*/ + + IF ISNULL(@SkipGetAlertInfo, 0) != 1 /*If @SkipGetAlertInfo hasn't been set to 1 by the caller*/ + BEGIN + BEGIN TRY + /* Try to fill the table for check 73 */ + INSERT INTO #AlertInfo + EXEC [master].[dbo].[sp_MSgetalertinfo] @includeaddresses = 0; + + SET @SkipGetAlertInfo = 0; /*We can execute sp_MSgetalertinfo*/ + END TRY + BEGIN CATCH + SET @SkipGetAlertInfo = 1; /*We have don't have execute rights or sp_MSgetalertinfo throws an error so skip it*/ + END CATCH; + END; /*Need execute on sp_MSgetalertinfo*/ -IF (@Jobs > 20) OR (@Jobs < 1) + IF ISNULL(@SkipModel, 0) != 1 /*If @SkipModel hasn't been set to 1 by the caller*/ + BEGIN + IF EXISTS + ( + SELECT 1/0 + FROM @db_perms + WHERE database_name = N'model' + ) + BEGIN + BEGIN TRY + IF EXISTS + ( + SELECT 1/0 + FROM model.sys.objects + ) + BEGIN + SET @SkipModel = 0; /*We have read permissions in the model database, and can view the objects*/ + END; + END TRY + BEGIN CATCH + SET @SkipModel = 1; /*We have read permissions in the model database ... oh wait we got tricked, we can't view the objects*/ + END CATCH; + END; + ELSE + BEGIN + SET @SkipModel = 1; /*We don't have read permissions in the model database*/ + END; + END; - BEGIN - RAISERROR('We advise sticking with 1-20 jobs.', 0, 1) WITH NOWAIT; + IF ISNULL(@SkipMSDB_objs, 0) != 1 /*If @SkipMSDB_objs hasn't been set to 1 by the caller*/ + BEGIN + IF EXISTS + ( + SELECT 1/0 + FROM @db_perms + WHERE database_name = N'msdb' + ) + BEGIN + BEGIN TRY + IF EXISTS + ( + SELECT 1/0 + FROM msdb.sys.objects + ) + BEGIN + SET @SkipMSDB_objs = 0; /*We have read permissions in the msdb database, and can view the objects*/ + END; + END TRY + BEGIN CATCH + SET @SkipMSDB_objs = 1; /*We have read permissions in the msdb database ... oh wait we got tricked, we can't view the objects*/ + END CATCH; + END; + ELSE + BEGIN + SET @SkipMSDB_objs = 1; /*We don't have read permissions in the msdb database*/ + END; + END; - RETURN; + IF ISNULL(@SkipMSDB_jobs, 0) != 1 /*If @SkipMSDB_jobs hasn't been set to 1 by the caller*/ + BEGIN + IF EXISTS + ( + SELECT 1/0 + FROM @db_perms + WHERE database_name = N'msdb' + ) + BEGIN + BEGIN TRY + IF EXISTS + ( + SELECT 1/0 + FROM msdb.dbo.sysjobs + ) + BEGIN + SET @SkipMSDB_jobs = 0; /*We have read permissions in the msdb database, and can view the objects*/ + END; + END TRY + BEGIN CATCH + SET @SkipMSDB_jobs = 1; /*We have read permissions in the msdb database ... oh wait we got tricked, we can't view the objects*/ + END CATCH; + END; + ELSE + BEGIN + SET @SkipMSDB_jobs = 1; /*We don't have read permissions in the msdb database*/ + END; + END; END; -/* + SET @crlf = NCHAR(13) + NCHAR(10); + SET @ResultText = 'sp_Blitz Results: ' + @crlf; -Probably shouldn't be more than 4 hours + /* Last startup */ + SELECT @DaysUptime = CAST(DATEDIFF(HOUR, create_date, GETDATE()) / 24. AS NUMERIC(23, 2)) + FROM sys.databases + WHERE database_id = 2; + + IF @DaysUptime = 0 + SET @DaysUptime = .01; -*/ + /* + Set the session state of Numeric_RoundAbort to off if any databases have Numeric Round-Abort enabled. + Stops arithmetic overflow errors during data conversion. See Github issue #2302 for more info. + */ + IF ( (8192 & @@OPTIONS) = 8192 ) /* Numeric RoundAbort is currently on, so we may need to turn it off temporarily */ + BEGIN + IF EXISTS (SELECT 1 + FROM sys.databases + WHERE is_numeric_roundabort_on = 1) /* A database has it turned on */ + BEGIN + SET @NeedToTurnNumericRoundabortBackOn = 1; + SET NUMERIC_ROUNDABORT OFF; + END; + END; + -IF (@RPOSeconds >= 14400) - BEGIN - RAISERROR('If your RPO is really 4 hours, perhaps you''d be interested in a more modest recovery model, like SIMPLE?', 0, 1) WITH NOWAIT; - RETURN; - END; + /* + --TOURSTOP01-- + See https://www.BrentOzar.com/go/blitztour for a guided tour. + We start by creating #BlitzResults. It's a temp table that will store all of + the results from our checks. Throughout the rest of this stored procedure, + we're running a series of checks looking for dangerous things inside the SQL + Server. When we find a problem, we insert rows into #BlitzResults. At the + end, we return these results to the end user. -/* - -Can't enable both the backup and restore jobs at the same time - -*/ - -IF @EnableBackupJobs = 1 AND @EnableRestoreJobs = 1 - BEGIN + #BlitzResults has a CheckID field, but there's no Check table. As we do + checks, we insert data into this table, and we manually put in the CheckID. + For a list of checks, visit http://FirstResponderKit.org. + */ + IF OBJECT_ID('tempdb..#BlitzResults') IS NOT NULL + DROP TABLE #BlitzResults; + CREATE TABLE #BlitzResults + ( + ID INT IDENTITY(1, 1) , + CheckID INT , + DatabaseName NVARCHAR(128) , + Priority TINYINT , + FindingsGroup VARCHAR(50) , + Finding VARCHAR(200) , + URL VARCHAR(200) , + Details NVARCHAR(4000) , + QueryPlan [XML] NULL , + QueryPlanFiltered [NVARCHAR](MAX) NULL + ); - RAISERROR('You are not allowed to enable both the backup and restore jobs at the same time. Pick one, bucko.', 0, 1) WITH NOWAIT; + IF OBJECT_ID('tempdb..#TemporaryDatabaseResults') IS NOT NULL + DROP TABLE #TemporaryDatabaseResults; + CREATE TABLE #TemporaryDatabaseResults + ( + DatabaseName NVARCHAR(128) , + Finding NVARCHAR(128) + ); - RETURN; - END; + /* First Responder Kit consistency (temporary tables) */ + + IF(OBJECT_ID('tempdb..#FRKObjects') IS NOT NULL) + BEGIN + EXEC sp_executesql N'DROP TABLE #FRKObjects;'; + END; -/* -Make sure xp_cmdshell is enabled -*/ -IF NOT EXISTS (SELECT * FROM sys.configurations WHERE name = 'xp_cmdshell' AND value_in_use = 1) - BEGIN - RAISERROR('xp_cmdshell must be enabled so we can get directory contents to check for new databases to restore.', 0, 1) WITH NOWAIT - - RETURN; - END + -- this one represents FRK objects + CREATE TABLE #FRKObjects ( + DatabaseName VARCHAR(256) NOT NULL, + ObjectSchemaName VARCHAR(256) NULL, + ObjectName VARCHAR(256) NOT NULL, + ObjectType VARCHAR(256) NOT NULL, + MandatoryComponent BIT NOT NULL + ); + + + IF(OBJECT_ID('tempdb..#StatementsToRun4FRKVersionCheck') IS NOT NULL) + BEGIN + EXEC sp_executesql N'DROP TABLE #StatementsToRun4FRKVersionCheck;'; + END; -/* -Make sure Ola Hallengren's scripts are installed in same database -*/ -DECLARE @CurrentDatabaseContext nvarchar(128) = DB_NAME(); -IF NOT EXISTS (SELECT * FROM sys.procedures WHERE name = 'CommandExecute') - BEGIN - RAISERROR('Ola Hallengren''s CommandExecute must be installed in the same database (%s) as SQL Server First Responder Kit. More info: http://ola.hallengren.com', 0, 1, @CurrentDatabaseContext) WITH NOWAIT; - - RETURN; - END -IF NOT EXISTS (SELECT * FROM sys.procedures WHERE name = 'DatabaseBackup') - BEGIN - RAISERROR('Ola Hallengren''s DatabaseBackup must be installed in the same database (%s) as SQL Server First Responder Kit. More info: http://ola.hallengren.com', 0, 1, @CurrentDatabaseContext) WITH NOWAIT; - - RETURN; - END -/* -Make sure sp_DatabaseRestore is installed in same database -*/ -IF NOT EXISTS (SELECT * FROM sys.procedures WHERE name = 'sp_DatabaseRestore') - BEGIN - RAISERROR('sp_DatabaseRestore must be installed in the same database (%s) as SQL Server First Responder Kit. To get it: http://FirstResponderKit.org', 0, 1, @CurrentDatabaseContext) WITH NOWAIT; - - RETURN; - END + -- This one will contain the statements to be executed + -- order: 1- Mandatory, 2- VersionCheckMode, 3- VersionCheck -/* + CREATE TABLE #StatementsToRun4FRKVersionCheck ( + StatementId INT IDENTITY(1,1), + CheckName VARCHAR(256), + SubjectName VARCHAR(256), + SubjectFullPath VARCHAR(1024), + StatementText NVARCHAR(MAX), + StatementOutputsCounter BIT, + OutputCounterExpectedValue INT, + StatementOutputsExecRet BIT, + StatementOutputsDateTime BIT + ); -Basic path sanity checks + /* End of First Responder Kit consistency (temporary tables) */ + + + /* + You can build your own table with a list of checks to skip. For example, you + might have some databases that you don't care about, or some checks you don't + want to run. Then, when you run sp_Blitz, you can specify these parameters: + @SkipChecksDatabase = 'DBAtools', + @SkipChecksSchema = 'dbo', + @SkipChecksTable = 'BlitzChecksToSkip' + Pass in the database, schema, and table that contains the list of checks you + want to skip. This part of the code checks those parameters, gets the list, + and then saves those in a temp table. As we run each check, we'll see if we + need to skip it. + */ + /* --TOURSTOP07-- */ + IF OBJECT_ID('tempdb..#SkipChecks') IS NOT NULL + DROP TABLE #SkipChecks; + CREATE TABLE #SkipChecks + ( + DatabaseName NVARCHAR(128) , + CheckID INT , + ServerName NVARCHAR(128) + ); + CREATE CLUSTERED INDEX IX_CheckID_DatabaseName ON #SkipChecks(CheckID, DatabaseName); -*/ + INSERT INTO #SkipChecks + (DatabaseName) + SELECT + DB_NAME(d.database_id) + FROM sys.databases AS d + WHERE (DB_NAME(d.database_id) LIKE 'rdsadmin%' + OR LOWER(d.name) IN ('dbatools', 'dbadmin', 'dbmaintenance')) + OPTION(RECOMPILE); -IF @RunSetup = 1 and @BackupPath is NULL - BEGIN - - RAISERROR('@BackupPath is required during setup', 0, 1) WITH NOWAIT; - - RETURN; - END + /*Skip checks for database where we don't have read permissions*/ + INSERT INTO + #SkipChecks + ( + DatabaseName + ) + SELECT + DB_NAME(d.database_id) + FROM sys.databases AS d + WHERE NOT EXISTS + ( + SELECT + 1/0 + FROM @db_perms AS dp + WHERE dp.database_name = DB_NAME(d.database_id) + ); -IF (@BackupPath NOT LIKE '[c-zC-Z]:\%') --Local path, don't think anyone has A or B drives -AND (@BackupPath NOT LIKE '\\[a-zA-Z0-9]%\%') --UNC path - - BEGIN - RAISERROR('Are you sure that''s a real path?', 0, 1) WITH NOWAIT; - - RETURN; - END; + /*Skip individial checks where we don't have permissions*/ + INSERT #SkipChecks (DatabaseName, CheckID, ServerName) + SELECT + v.* + FROM (VALUES(NULL, 29, NULL)) AS v (DatabaseName, CheckID, ServerName) /*Looks for user tables in model*/ + WHERE @SkipModel = 1; -/* + INSERT #SkipChecks (DatabaseName, CheckID, ServerName) + SELECT + v.* + FROM (VALUES(NULL, 28, NULL)) AS v (DatabaseName, CheckID, ServerName) /*Tables in the MSDB Database*/ + WHERE @SkipMSDB_objs = 1; -If you want to update the table, one of these has to not be NULL + INSERT #SkipChecks (DatabaseName, CheckID, ServerName) + SELECT + v.* + FROM (VALUES + /*sysjobs checks*/ + (NULL, 6, NULL), /*Jobs Owned By Users*/ + (NULL, 57, NULL), /*SQL Agent Job Runs at Startup*/ + (NULL, 79, NULL), /*Shrink Database Job*/ + (NULL, 94, NULL), /*Agent Jobs Without Failure Emails*/ + (NULL, 123, NULL), /*Agent Jobs Starting Simultaneously*/ + (NULL, 180, NULL), /*Shrink Database Step In Maintenance Plan*/ + (NULL, 181, NULL), /*Repetitive Maintenance Tasks*/ + + /*sysalerts checks*/ + (NULL, 30, NULL), /*Not All Alerts Configured*/ + (NULL, 59, NULL), /*Alerts Configured without Follow Up*/ + (NULL, 61, NULL), /*No Alerts for Sev 19-25*/ + (NULL, 96, NULL), /*No Alerts for Corruption*/ + (NULL, 98, NULL), /*Alerts Disabled*/ + (NULL, 219, NULL), /*Alerts Without Event Descriptions*/ -*/ + /*sysoperators*/ + (NULL, 31, NULL) /*No Operators Configured/Enabled*/ + ) AS v (DatabaseName, CheckID, ServerName) + WHERE @SkipMSDB_jobs = 1; -IF @UpdateSetup = 1 - AND ( @RPOSeconds IS NULL - AND @BackupPath IS NULL - AND @RPOSeconds IS NULL - AND @RestorePath IS NULL - AND @EnableBackupJobs IS NULL - AND @EnableRestoreJobs IS NULL - ) + INSERT #SkipChecks (DatabaseName, CheckID, ServerName) + SELECT + v.* + FROM (VALUES(NULL, 68, NULL)) AS v (DatabaseName, CheckID, ServerName) /*DBCC command*/ + WHERE @sa = 0; - BEGIN + INSERT #SkipChecks (DatabaseName, CheckID, ServerName) + SELECT + v.* + FROM (VALUES(NULL, 69, NULL)) AS v (DatabaseName, CheckID, ServerName) /*DBCC command*/ + WHERE @sa = 0; - RAISERROR('If you want to update configuration settings, they can''t be NULL. Please Make sure @RPOSeconds / @RTOSeconds or @BackupPath / @RestorePath has a value', 0, 1) WITH NOWAIT; + INSERT #SkipChecks (DatabaseName, CheckID, ServerName) + SELECT + v.* + FROM (VALUES(NULL, 92, NULL)) AS v (DatabaseName, CheckID, ServerName) /*xp_fixeddrives*/ + WHERE @SkipXPFixedDrives = 1; - RETURN; + INSERT #SkipChecks (DatabaseName, CheckID, ServerName) + SELECT + v.* + FROM (VALUES(NULL, 106, NULL)) AS v (DatabaseName, CheckID, ServerName) /*alter trace*/ + WHERE @SkipTrace = 1; - END; + INSERT #SkipChecks (DatabaseName, CheckID, ServerName) + SELECT + v.* + FROM (VALUES(NULL, 211, NULL)) AS v (DatabaseName, CheckID, ServerName) /*xp_regread*/ + WHERE @sa = 0; + INSERT #SkipChecks (DatabaseName, CheckID, ServerName) + SELECT + v.* + FROM (VALUES(NULL, 212, NULL)) AS v (DatabaseName, CheckID, ServerName) /*xp_regread*/ + WHERE @SkipXPCMDShell = 1; -IF @UpdateSetup = 1 - GOTO UpdateConfigs; + INSERT #SkipChecks (DatabaseName, CheckID, ServerName) + SELECT + v.* + FROM (VALUES(NULL, 2301, NULL)) AS v (DatabaseName, CheckID, ServerName) /*sp_validatelogins*/ + WHERE @SkipValidateLogins = 1; -IF @RunSetup = 1 -BEGIN - BEGIN TRY + INSERT #SkipChecks (DatabaseName, CheckID, ServerName) + SELECT + v.* + FROM (VALUES(NULL, 73, NULL)) AS v (DatabaseName, CheckID, ServerName) /*sp_validatelogins*/ + WHERE @SkipGetAlertInfo = 1; - BEGIN - + IF @sa = 0 + BEGIN + INSERT INTO #BlitzResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT 223 AS CheckID , + 0 AS Priority , + 'Informational' AS FindingsGroup , + 'Some Checks Skipped' AS Finding , + '' AS URL , + 'User ''' + @SUSER_NAME + ''' is not part of the sysadmin role, so we skipped some checks that are not possible due to lack of permissions.' AS Details; + END; + /*End of SkipsChecks added due to permissions*/ - /* - - First check to see if Agent is running -- we'll get errors if it's not - - */ + IF @SkipChecksTable IS NOT NULL + AND @SkipChecksSchema IS NOT NULL + AND @SkipChecksDatabase IS NOT NULL + BEGIN + IF @Debug IN (1, 2) RAISERROR('Inserting SkipChecks', 0, 1) WITH NOWAIT; - IF ( SELECT 1 - FROM sys.all_objects - WHERE name = 'dm_server_services' ) IS NOT NULL - - BEGIN - - IF EXISTS ( - SELECT 1 - FROM sys.dm_server_services - WHERE servicename LIKE 'SQL Server Agent%' - AND status_desc = 'Stopped' - ) - + SET @StringToExecute = N'INSERT INTO #SkipChecks(DatabaseName, CheckID, ServerName ) + SELECT DISTINCT DatabaseName, CheckID, ServerName + FROM ' + IF LTRIM(RTRIM(@SkipChecksServer)) <> '' BEGIN - - RAISERROR('SQL Server Agent is not currently running -- it needs to be enabled to add backup worker jobs and the new database polling job', 0, 1) WITH NOWAIT; - - RETURN; - - END; - - END - - - BEGIN - + SET @StringToExecute += QUOTENAME(@SkipChecksServer) + N'.'; + END + SET @StringToExecute += QUOTENAME(@SkipChecksDatabase) + N'.' + QUOTENAME(@SkipChecksSchema) + N'.' + QUOTENAME(@SkipChecksTable) + + N' WHERE ServerName IS NULL OR ServerName = CAST(SERVERPROPERTY(''ServerName'') AS NVARCHAR(128)) OPTION (RECOMPILE);'; + EXEC(@StringToExecute); + END; - /* - - Check to see if the database exists + -- Flag for Windows OS to help with Linux support + IF EXISTS ( SELECT 1 + FROM sys.all_objects + WHERE name = 'dm_os_host_info' ) + BEGIN + SELECT @IsWindowsOperatingSystem = CASE WHEN host_platform = 'Windows' THEN 1 ELSE 0 END FROM sys.dm_os_host_info ; + END; + ELSE + BEGIN + SELECT @IsWindowsOperatingSystem = 1 ; + END; - */ - - RAISERROR('Checking for msdbCentral', 0, 1) WITH NOWAIT; - SET @db_sql += N' + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 106 ) + AND (select convert(int,value_in_use) from sys.configurations where name = 'default trace enabled' ) = 1 + BEGIN - IF DATABASEPROPERTYEX(' + QUOTENAME(@database_name, '''') + ', ''Status'') IS NULL + select @curr_tracefilename = [path] from sys.traces where is_default = 1 ; + set @curr_tracefilename = reverse(@curr_tracefilename); - BEGIN + -- Set the trace file path separator based on underlying OS + IF (@IsWindowsOperatingSystem = 1) AND @curr_tracefilename IS NOT NULL + BEGIN + select @indx = patindex('%\%', @curr_tracefilename) ; + set @curr_tracefilename = reverse(@curr_tracefilename) ; + set @base_tracefilename = left( @curr_tracefilename,len(@curr_tracefilename) - @indx) + '\log.trc' ; + END; + ELSE + BEGIN + select @indx = patindex('%/%', @curr_tracefilename) ; + set @curr_tracefilename = reverse(@curr_tracefilename) ; + set @base_tracefilename = left( @curr_tracefilename,len(@curr_tracefilename) - @indx) + '/log.trc' ; + END; - RAISERROR(''Creating msdbCentral'', 0, 1) WITH NOWAIT; + END; - CREATE DATABASE ' + QUOTENAME(@database_name) + '; - - ALTER DATABASE ' + QUOTENAME(@database_name) + ' SET RECOVERY FULL; - - END + /* If the server has any databases on Antiques Roadshow, skip the checks that would break due to CTEs. */ + IF @CheckUserDatabaseObjects = 1 AND EXISTS(SELECT * FROM sys.databases WHERE compatibility_level < 90) + BEGIN + SET @CheckUserDatabaseObjects = 0; + PRINT 'Databases with compatibility level < 90 found, so setting @CheckUserDatabaseObjects = 0.'; + PRINT 'The database-level checks rely on CTEs, which are not supported in SQL 2000 compat level databases.'; + PRINT 'Get with the cool kids and switch to a current compatibility level, Grandpa. To find the problems, run:'; + PRINT 'SELECT * FROM sys.databases WHERE compatibility_level < 90;'; + INSERT INTO #BlitzResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT 204 AS CheckID , + 0 AS Priority , + 'Informational' AS FindingsGroup , + '@CheckUserDatabaseObjects Disabled' AS Finding , + 'https://www.BrentOzar.com/blitz/' AS URL , + 'Since you have databases with compatibility_level < 90, we can''t run @CheckUserDatabaseObjects = 1. To find them: SELECT * FROM sys.databases WHERE compatibility_level < 90' AS Details; + END; - '; + /* --TOURSTOP08-- */ + /* If the server is Amazon RDS, skip checks that it doesn't allow */ + IF LEFT(CAST(SERVERPROPERTY('ComputerNamePhysicalNetBIOS') AS VARCHAR(8000)), 8) = 'EC2AMAZ-' + AND LEFT(CAST(SERVERPROPERTY('MachineName') AS VARCHAR(8000)), 8) = 'EC2AMAZ-' + AND db_id('rdsadmin') IS NOT NULL + AND EXISTS(SELECT * FROM master.sys.all_objects WHERE name IN ('rds_startup_tasks', 'rds_help_revlogin', 'rds_hexadecimal', 'rds_failover_tracking', 'rds_database_tracking', 'rds_track_change')) + BEGIN + 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 (29); /* tables in model database created by users - not allowed */ + INSERT INTO #SkipChecks (CheckID) VALUES (40); /* TempDB only has one data file in RDS */ + INSERT INTO #SkipChecks (CheckID) VALUES (62); /* Database compatibility level - cannot change in RDS */ + INSERT INTO #SkipChecks (CheckID) VALUES (68); /*Check for the last good DBCC CHECKDB date - can't run DBCC DBINFO() */ + INSERT INTO #SkipChecks (CheckID) VALUES (69); /* High VLF check - requires DBCC LOGINFO permission */ + INSERT INTO #SkipChecks (CheckID) VALUES (73); /* No Failsafe Operator Configured check */ + INSERT INTO #SkipChecks (CheckID) VALUES (92); /* Drive info check - requires xp_Fixeddrives permission */ + INSERT INTO #SkipChecks (CheckID) VALUES (100); /* Remote DAC disabled */ + INSERT INTO #SkipChecks (CheckID) VALUES (177); /* Disabled Internal Monitoring Features check - requires dm_server_registry access */ + INSERT INTO #SkipChecks (CheckID) VALUES (180); /* 180/181 are maintenance plans checks - Maint plans not available in RDS*/ + INSERT INTO #SkipChecks (CheckID) VALUES (181); /*Find repetitive maintenance tasks*/ + -- can check errorlog using rdsadmin.dbo.rds_read_error_log, so allow this check + --INSERT INTO #SkipChecks (CheckID) VALUES (193); /* xp_readerrorlog checking for IFI */ - IF @Debug = 1 - BEGIN - RAISERROR(@db_sql, 0, 1) WITH NOWAIT; - END; + INSERT INTO #SkipChecks (CheckID) VALUES (211); /* xp_regread not allowed - checking for power saving */ + INSERT INTO #SkipChecks (CheckID) VALUES (212); /* xp_regread not allowed - checking for additional instances */ + INSERT INTO #SkipChecks (CheckID) VALUES (2301); /* sp_validatelogins called by Invalid login defined with Windows Authentication */ + -- Following are skipped due to limited permissions in msdb/SQLAgent in RDS + INSERT INTO #SkipChecks (CheckID) VALUES (30); /* SQL Server Agent alerts not configured */ + INSERT INTO #SkipChecks (CheckID) VALUES (31); /* check whether we have NO ENABLED operators */ + INSERT INTO #SkipChecks (CheckID) VALUES (57); /* SQL Agent Job Runs at Startup */ + INSERT INTO #SkipChecks (CheckID) VALUES (59); /* Alerts Configured without Follow Up */ + INSERT INTO #SkipChecks (CheckID) VALUES (61); /*SQL Server Agent alerts do not exist for severity levels 19 through 25*/ + INSERT INTO #SkipChecks (CheckID) VALUES (79); /* Shrink Database Job check */ + INSERT INTO #SkipChecks (CheckID) VALUES (94); /* job failure without operator notification check */ + INSERT INTO #SkipChecks (CheckID) VALUES (96); /* Agent alerts for corruption */ + INSERT INTO #SkipChecks (CheckID) VALUES (98); /* check for disabled alerts */ + INSERT INTO #SkipChecks (CheckID) VALUES (123); /* Agent Jobs Starting Simultaneously */ + INSERT INTO #SkipChecks (CheckID) VALUES (219); /* check for alerts that do NOT include event descriptions in their outputs via email/pager/net-send */ + INSERT INTO #BlitzResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT 223 AS CheckID , + 0 AS Priority , + 'Informational' AS FindingsGroup , + 'Some Checks Skipped' AS Finding , + 'https://aws.amazon.com/rds/sqlserver/' AS URL , + 'Amazon RDS detected, so we skipped some checks that are not currently possible, relevant, or practical there.' AS Details; + END; /* Amazon RDS skipped checks */ - IF @db_sql IS NULL - BEGIN - RAISERROR('@db_sql is NULL for some reason', 0, 1) WITH NOWAIT; - END; + /* If the server is ExpressEdition, skip checks that it doesn't allow */ + IF CAST(SERVERPROPERTY('Edition') AS NVARCHAR(1000)) LIKE N'%Express%' + BEGIN + INSERT INTO #SkipChecks (CheckID) VALUES (30); /* Alerts not configured */ + INSERT INTO #SkipChecks (CheckID) VALUES (31); /* Operators not configured */ + INSERT INTO #SkipChecks (CheckID) VALUES (61); /* Agent alerts 19-25 */ + INSERT INTO #SkipChecks (CheckID) VALUES (73); /* Failsafe operator */ + INSERT INTO #SkipChecks (CheckID) VALUES (96); /* Agent alerts for corruption */ + INSERT INTO #BlitzResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT 223 AS CheckID , + 0 AS Priority , + 'Informational' AS FindingsGroup , + 'Some Checks Skipped' AS Finding , + 'https://stackoverflow.com/questions/1169634/limitations-of-sql-server-express' AS URL , + 'Express Edition detected, so we skipped some checks that are not currently possible, relevant, or practical there.' AS Details; + END; /* Express Edition skipped checks */ + /* If the server is an Azure Managed Instance, skip checks that it doesn't allow */ + IF SERVERPROPERTY('EngineEdition') = 8 + BEGIN + INSERT INTO #SkipChecks (CheckID) VALUES (1); /* Full backups - because of the MI GUID name bug mentioned here: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/1481 */ + INSERT INTO #SkipChecks (CheckID) VALUES (2); /* Log backups - because of the MI GUID name bug mentioned here: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/1481 */ + 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 (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 (74); /* TraceFlag On - because Azure Managed Instances go wild and crazy with the trace flags */ + 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 (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 */ + INSERT INTO #SkipChecks (CheckID, DatabaseName) VALUES (80, 'model'); /* Max file size set */ + INSERT INTO #SkipChecks (CheckID, DatabaseName) VALUES (80, 'msdb'); /* Max file size set */ + INSERT INTO #SkipChecks (CheckID, DatabaseName) VALUES (80, 'tempdb'); /* Max file size set */ + INSERT INTO #SkipChecks (CheckID) VALUES (224); /* CheckID 224 - Performance - SSRS/SSAS/SSIS Installed */ + INSERT INTO #SkipChecks (CheckID) VALUES (92); /* CheckID 92 - drive space */ + INSERT INTO #SkipChecks (CheckID) VALUES (258);/* CheckID 258 - Security - SQL Server service is running as LocalSystem or NT AUTHORITY\SYSTEM */ + INSERT INTO #SkipChecks (CheckID) VALUES (259);/* CheckID 259 - Security - SQL Server Agent service is running as LocalSystem or NT AUTHORITY\SYSTEM */ + INSERT INTO #SkipChecks (CheckID) VALUES (260); /* CheckID 260 - Security - SQL Server service account is member of Administrators */ + INSERT INTO #SkipChecks (CheckID) VALUES (261); /*CheckID 261 - Security - SQL Server Agent service account is member of Administrators */ + INSERT INTO #BlitzResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT 223 AS CheckID , + 0 AS Priority , + 'Informational' AS FindingsGroup , + 'Some Checks Skipped' AS Finding , + 'https://docs.microsoft.com/en-us/azure/sql-database/sql-database-managed-instance-index' AS URL , + 'Managed Instance detected, so we skipped some checks that are not currently possible, relevant, or practical there.' AS Details; + END; /* Azure Managed Instance skipped checks */ - EXEC sp_executesql @db_sql; + /* + That's the end of the SkipChecks stuff. + The next several tables are used by various checks later. + */ + IF OBJECT_ID('tempdb..#ConfigurationDefaults') IS NOT NULL + DROP TABLE #ConfigurationDefaults; + CREATE TABLE #ConfigurationDefaults + ( + name NVARCHAR(128) , + DefaultValue BIGINT, + CheckID INT + ); + IF OBJECT_ID ('tempdb..#Recompile') IS NOT NULL + DROP TABLE #Recompile; + CREATE TABLE #Recompile( + DBName varchar(200), + ProcName varchar(300), + RecompileFlag varchar(1), + SPSchema varchar(50) + ); - /* - - Check for tables and stuff + IF OBJECT_ID('tempdb..#DatabaseDefaults') IS NOT NULL + DROP TABLE #DatabaseDefaults; + CREATE TABLE #DatabaseDefaults + ( + name NVARCHAR(128) , + DefaultValue NVARCHAR(200), + CheckID INT, + Priority INT, + Finding VARCHAR(200), + URL VARCHAR(200), + Details NVARCHAR(4000) + ); - */ + IF OBJECT_ID('tempdb..#DatabaseScopedConfigurationDefaults') IS NOT NULL + DROP TABLE #DatabaseScopedConfigurationDefaults; + CREATE TABLE #DatabaseScopedConfigurationDefaults + (ID INT IDENTITY(1,1), configuration_id INT, [name] NVARCHAR(60), default_value sql_variant, default_value_for_secondary sql_variant, CheckID INT, ); - - RAISERROR('Checking for tables in msdbCentral', 0, 1) WITH NOWAIT; + IF OBJECT_ID('tempdb..#DBCCs') IS NOT NULL + DROP TABLE #DBCCs; + CREATE TABLE #DBCCs + ( + ID INT IDENTITY(1, 1) + PRIMARY KEY , + ParentObject VARCHAR(255) , + Object VARCHAR(255) , + Field VARCHAR(255) , + Value VARCHAR(255) , + DbName NVARCHAR(128) NULL + ); - SET @tbl_sql += N' - - USE ' + QUOTENAME(@database_name) + ' - - - IF OBJECT_ID(''' + QUOTENAME(@database_name) + '.dbo.backup_configuration'') IS NULL - - BEGIN - - RAISERROR(''Creating table dbo.backup_configuration'', 0, 1) WITH NOWAIT; - - CREATE TABLE dbo.backup_configuration ( - database_name NVARCHAR(256), - configuration_name NVARCHAR(512), - configuration_description NVARCHAR(512), - configuration_setting NVARCHAR(MAX) - ); - - END - - ELSE - - BEGIN - - - RAISERROR(''Backup configuration table exists, truncating'', 0, 1) WITH NOWAIT; - - - TRUNCATE TABLE dbo.backup_configuration - - - END - - - RAISERROR(''Inserting configuration values'', 0, 1) WITH NOWAIT; - - - INSERT dbo.backup_configuration (database_name, configuration_name, configuration_description, configuration_setting) - VALUES (''all'', ''log backup frequency'', ''The length of time in second between Log Backups.'', ''' + CONVERT(NVARCHAR(10), @RPOSeconds) + '''); - - INSERT dbo.backup_configuration (database_name, configuration_name, configuration_description, configuration_setting) - VALUES (''all'', ''log backup path'', ''The path to which Log Backups should go.'', ''' + @BackupPath + '''); - - INSERT dbo.backup_configuration (database_name, configuration_name, configuration_description, configuration_setting) - VALUES (''all'', ''change backup type'', ''For Ola Hallengren DatabaseBackup @ChangeBackupType param: Y = escalate to fulls, MSDB = escalate by checking msdb backup history.'', ''MSDB''); - - INSERT dbo.backup_configuration (database_name, configuration_name, configuration_description, configuration_setting) - VALUES (''all'', ''encrypt'', ''For Ola Hallengren DatabaseBackup: Y = encrypt the backup. N (default) = do not encrypt.'', NULL); - - INSERT dbo.backup_configuration (database_name, configuration_name, configuration_description, configuration_setting) - VALUES (''all'', ''encryptionalgorithm'', ''For Ola Hallengren DatabaseBackup: native 2014 choices include TRIPLE_DES_3KEY, AES_128, AES_192, AES_256.'', NULL); - - INSERT dbo.backup_configuration (database_name, configuration_name, configuration_description, configuration_setting) - VALUES (''all'', ''servercertificate'', ''For Ola Hallengren DatabaseBackup: server certificate that is used to encrypt the backup.'', NULL); - - - IF OBJECT_ID(''' + QUOTENAME(@database_name) + '.dbo.backup_worker'') IS NULL - - BEGIN - - - RAISERROR(''Creating table dbo.backup_worker'', 0, 1) WITH NOWAIT; - - CREATE TABLE dbo.backup_worker ( - id INT IDENTITY(1, 1) PRIMARY KEY CLUSTERED, - database_name NVARCHAR(256), - last_log_backup_start_time DATETIME DEFAULT ''19000101'', - last_log_backup_finish_time DATETIME DEFAULT ''99991231'', - is_started BIT DEFAULT 0, - is_completed BIT DEFAULT 0, - error_number INT DEFAULT NULL, - last_error_date DATETIME DEFAULT NULL, - ignore_database BIT DEFAULT 0, - full_backup_required BIT DEFAULT ' + CASE WHEN @FirstFullBackup = 0 THEN N'0,' ELSE N'1,' END + CHAR(10) + - N'diff_backup_required BIT DEFAULT ' + CASE WHEN @FirstDiffBackup = 0 THEN N'0' ELSE N'1' END + CHAR(10) + - N'); - - END; - - ELSE - - BEGIN - - - RAISERROR(''Backup worker table exists, truncating'', 0, 1) WITH NOWAIT; - - - TRUNCATE TABLE dbo.backup_worker + IF OBJECT_ID('tempdb..#LogInfo2012') IS NOT NULL + DROP TABLE #LogInfo2012; + CREATE TABLE #LogInfo2012 + ( + recoveryunitid INT , + FileID SMALLINT , + FileSize BIGINT , + StartOffset BIGINT , + FSeqNo BIGINT , + [Status] TINYINT , + Parity TINYINT , + CreateLSN NUMERIC(38) + ); + IF OBJECT_ID('tempdb..#LogInfo') IS NOT NULL + DROP TABLE #LogInfo; + CREATE TABLE #LogInfo + ( + FileID SMALLINT , + FileSize BIGINT , + StartOffset BIGINT , + FSeqNo BIGINT , + [Status] TINYINT , + Parity TINYINT , + CreateLSN NUMERIC(38) + ); - END + IF OBJECT_ID('tempdb..#partdb') IS NOT NULL + DROP TABLE #partdb; + CREATE TABLE #partdb + ( + dbname NVARCHAR(128) , + objectname NVARCHAR(200) , + type_desc NVARCHAR(128) + ); - - RAISERROR(''Inserting databases for backups'', 0, 1) WITH NOWAIT; - - INSERT ' + QUOTENAME(@database_name) + '.dbo.backup_worker (database_name) - SELECT d.name - FROM sys.databases d - WHERE NOT EXISTS ( - SELECT * - FROM msdbCentral.dbo.backup_worker bw - WHERE bw.database_name = d.name - ) - AND d.database_id > 4; - - '; + IF OBJECT_ID('tempdb..#TraceStatus') IS NOT NULL + DROP TABLE #TraceStatus; + CREATE TABLE #TraceStatus + ( + TraceFlag VARCHAR(10) , + status BIT , + Global BIT , + Session BIT + ); - - IF @Debug = 1 - BEGIN - SET @msg = SUBSTRING(@tbl_sql, 0, 2044) - RAISERROR(@msg, 0, 1) WITH NOWAIT; - SET @msg = SUBSTRING(@tbl_sql, 2044, 4088) - RAISERROR(@msg, 0, 1) WITH NOWAIT; - SET @msg = SUBSTRING(@tbl_sql, 4088, 6132) - RAISERROR(@msg, 0, 1) WITH NOWAIT; - SET @msg = SUBSTRING(@tbl_sql, 6132, 8176) - RAISERROR(@msg, 0, 1) WITH NOWAIT; - END; + IF OBJECT_ID('tempdb..#driveInfo') IS NOT NULL + DROP TABLE #driveInfo; + CREATE TABLE #driveInfo + ( + drive NVARCHAR(2), + logical_volume_name NVARCHAR(36), --Limit is 32 for NTFS, 11 for FAT + available_MB DECIMAL(18, 0), + total_MB DECIMAL(18, 0), + used_percent DECIMAL(18, 2) + ); - - IF @tbl_sql IS NULL - BEGIN - RAISERROR('@tbl_sql is NULL for some reason', 0, 1) WITH NOWAIT; - END; + IF OBJECT_ID('tempdb..#dm_exec_query_stats') IS NOT NULL + DROP TABLE #dm_exec_query_stats; + CREATE TABLE #dm_exec_query_stats + ( + [id] [int] NOT NULL + IDENTITY(1, 1) , + [sql_handle] [varbinary](64) NOT NULL , + [statement_start_offset] [int] NOT NULL , + [statement_end_offset] [int] NOT NULL , + [plan_generation_num] [bigint] NOT NULL , + [plan_handle] [varbinary](64) NOT NULL , + [creation_time] [datetime] NOT NULL , + [last_execution_time] [datetime] NOT NULL , + [execution_count] [bigint] NOT NULL , + [total_worker_time] [bigint] NOT NULL , + [last_worker_time] [bigint] NOT NULL , + [min_worker_time] [bigint] NOT NULL , + [max_worker_time] [bigint] NOT NULL , + [total_physical_reads] [bigint] NOT NULL , + [last_physical_reads] [bigint] NOT NULL , + [min_physical_reads] [bigint] NOT NULL , + [max_physical_reads] [bigint] NOT NULL , + [total_logical_writes] [bigint] NOT NULL , + [last_logical_writes] [bigint] NOT NULL , + [min_logical_writes] [bigint] NOT NULL , + [max_logical_writes] [bigint] NOT NULL , + [total_logical_reads] [bigint] NOT NULL , + [last_logical_reads] [bigint] NOT NULL , + [min_logical_reads] [bigint] NOT NULL , + [max_logical_reads] [bigint] NOT NULL , + [total_clr_time] [bigint] NOT NULL , + [last_clr_time] [bigint] NOT NULL , + [min_clr_time] [bigint] NOT NULL , + [max_clr_time] [bigint] NOT NULL , + [total_elapsed_time] [bigint] NOT NULL , + [last_elapsed_time] [bigint] NOT NULL , + [min_elapsed_time] [bigint] NOT NULL , + [max_elapsed_time] [bigint] NOT NULL , + [query_hash] [binary](8) NULL , + [query_plan_hash] [binary](8) NULL , + [query_plan] [xml] NULL , + [query_plan_filtered] [nvarchar](MAX) NULL , + [text] [nvarchar](MAX) COLLATE SQL_Latin1_General_CP1_CI_AS + NULL , + [text_filtered] [nvarchar](MAX) COLLATE SQL_Latin1_General_CP1_CI_AS + NULL + ); + IF OBJECT_ID('tempdb..#ErrorLog') IS NOT NULL + DROP TABLE #ErrorLog; + CREATE TABLE #ErrorLog + ( + LogDate DATETIME , + ProcessInfo NVARCHAR(20) , + [Text] NVARCHAR(1000) + ); - EXEC sp_executesql @tbl_sql; + IF OBJECT_ID('tempdb..#fnTraceGettable') IS NOT NULL + DROP TABLE #fnTraceGettable; + CREATE TABLE #fnTraceGettable + ( + TextData NVARCHAR(4000) , + DatabaseName NVARCHAR(256) , + EventClass INT , + Severity INT , + StartTime DATETIME , + EndTime DATETIME , + Duration BIGINT , + NTUserName NVARCHAR(256) , + NTDomainName NVARCHAR(256) , + HostName NVARCHAR(256) , + ApplicationName NVARCHAR(256) , + LoginName NVARCHAR(256) , + DBUserName NVARCHAR(256) + ); - - /* - - This section creates tables for restore workers to work off of - - */ + IF OBJECT_ID('tempdb..#Instances') IS NOT NULL + DROP TABLE #Instances; + CREATE TABLE #Instances + ( + Instance_Number NVARCHAR(MAX) , + Instance_Name NVARCHAR(MAX) , + Data_Field NVARCHAR(MAX) + ); - - /* - - In search of msdb - - */ - - RAISERROR('Checking for msdb. Yeah, I know...', 0, 1) WITH NOWAIT; - - IF DATABASEPROPERTYEX('msdb', 'Status') IS NULL + IF OBJECT_ID('tempdb..#IgnorableWaits') IS NOT NULL + DROP TABLE #IgnorableWaits; + CREATE TABLE #IgnorableWaits (wait_type NVARCHAR(60)); + INSERT INTO #IgnorableWaits VALUES ('BROKER_EVENTHANDLER'); + INSERT INTO #IgnorableWaits VALUES ('BROKER_RECEIVE_WAITFOR'); + INSERT INTO #IgnorableWaits VALUES ('BROKER_TASK_STOP'); + INSERT INTO #IgnorableWaits VALUES ('BROKER_TO_FLUSH'); + INSERT INTO #IgnorableWaits VALUES ('BROKER_TRANSMITTER'); + INSERT INTO #IgnorableWaits VALUES ('CHECKPOINT_QUEUE'); + INSERT INTO #IgnorableWaits VALUES ('CLR_AUTO_EVENT'); + INSERT INTO #IgnorableWaits VALUES ('CLR_MANUAL_EVENT'); + INSERT INTO #IgnorableWaits VALUES ('CLR_SEMAPHORE'); + INSERT INTO #IgnorableWaits VALUES ('DBMIRROR_DBM_EVENT'); + INSERT INTO #IgnorableWaits VALUES ('DBMIRROR_DBM_MUTEX'); + INSERT INTO #IgnorableWaits VALUES ('DBMIRROR_EVENTS_QUEUE'); + INSERT INTO #IgnorableWaits VALUES ('DBMIRROR_WORKER_QUEUE'); + INSERT INTO #IgnorableWaits VALUES ('DBMIRRORING_CMD'); + INSERT INTO #IgnorableWaits VALUES ('DIRTY_PAGE_POLL'); + INSERT INTO #IgnorableWaits VALUES ('DISPATCHER_QUEUE_SEMAPHORE'); + INSERT INTO #IgnorableWaits VALUES ('FT_IFTS_SCHEDULER_IDLE_WAIT'); + INSERT INTO #IgnorableWaits VALUES ('FT_IFTSHC_MUTEX'); + INSERT INTO #IgnorableWaits VALUES ('HADR_CLUSAPI_CALL'); + INSERT INTO #IgnorableWaits VALUES ('HADR_FABRIC_CALLBACK'); + INSERT INTO #IgnorableWaits VALUES ('HADR_FILESTREAM_IOMGR_IOCOMPLETION'); + INSERT INTO #IgnorableWaits VALUES ('HADR_LOGCAPTURE_WAIT'); + INSERT INTO #IgnorableWaits VALUES ('HADR_NOTIFICATION_DEQUEUE'); + INSERT INTO #IgnorableWaits VALUES ('HADR_TIMER_TASK'); + INSERT INTO #IgnorableWaits VALUES ('HADR_WORK_QUEUE'); + INSERT INTO #IgnorableWaits VALUES ('LAZYWRITER_SLEEP'); + INSERT INTO #IgnorableWaits VALUES ('LOGMGR_QUEUE'); + INSERT INTO #IgnorableWaits VALUES ('ONDEMAND_TASK_QUEUE'); + INSERT INTO #IgnorableWaits VALUES ('PARALLEL_REDO_DRAIN_WORKER'); + INSERT INTO #IgnorableWaits VALUES ('PARALLEL_REDO_LOG_CACHE'); + INSERT INTO #IgnorableWaits VALUES ('PARALLEL_REDO_TRAN_LIST'); + INSERT INTO #IgnorableWaits VALUES ('PARALLEL_REDO_WORKER_SYNC'); + INSERT INTO #IgnorableWaits VALUES ('PARALLEL_REDO_WORKER_WAIT_WORK'); + INSERT INTO #IgnorableWaits VALUES ('POPULATE_LOCK_ORDINALS'); + INSERT INTO #IgnorableWaits VALUES ('PREEMPTIVE_HADR_LEASE_MECHANISM'); + INSERT INTO #IgnorableWaits VALUES ('PREEMPTIVE_SP_SERVER_DIAGNOSTICS'); + INSERT INTO #IgnorableWaits VALUES ('QDS_ASYNC_QUEUE'); + INSERT INTO #IgnorableWaits VALUES ('QDS_CLEANUP_STALE_QUERIES_TASK_MAIN_LOOP_SLEEP'); + INSERT INTO #IgnorableWaits VALUES ('QDS_PERSIST_TASK_MAIN_LOOP_SLEEP'); + INSERT INTO #IgnorableWaits VALUES ('QDS_SHUTDOWN_QUEUE'); + INSERT INTO #IgnorableWaits VALUES ('REDO_THREAD_PENDING_WORK'); + INSERT INTO #IgnorableWaits VALUES ('REQUEST_FOR_DEADLOCK_SEARCH'); + INSERT INTO #IgnorableWaits VALUES ('SLEEP_SYSTEMTASK'); + INSERT INTO #IgnorableWaits VALUES ('SLEEP_TASK'); + INSERT INTO #IgnorableWaits VALUES ('SOS_WORK_DISPATCHER'); + INSERT INTO #IgnorableWaits VALUES ('SP_SERVER_DIAGNOSTICS_SLEEP'); + INSERT INTO #IgnorableWaits VALUES ('SQLTRACE_BUFFER_FLUSH'); + INSERT INTO #IgnorableWaits VALUES ('SQLTRACE_INCREMENTAL_FLUSH_SLEEP'); + INSERT INTO #IgnorableWaits VALUES ('UCS_SESSION_REGISTRATION'); + INSERT INTO #IgnorableWaits VALUES ('WAIT_XTP_OFFLINE_CKPT_NEW_LOG'); + INSERT INTO #IgnorableWaits VALUES ('WAITFOR'); + INSERT INTO #IgnorableWaits VALUES ('XE_DISPATCHER_WAIT'); + INSERT INTO #IgnorableWaits VALUES ('XE_LIVE_TARGET_TVF'); + INSERT INTO #IgnorableWaits VALUES ('XE_TIMER_EVENT'); - BEGIN + IF @Debug IN (1, 2) RAISERROR('Setting @MsSinceWaitsCleared', 0, 1) WITH NOWAIT; - RAISERROR('YOU HAVE NO MSDB WHY?!', 0, 1) WITH NOWAIT; + SELECT @MsSinceWaitsCleared = DATEDIFF(MINUTE, create_date, CURRENT_TIMESTAMP) * 60000.0 + FROM sys.databases + WHERE name = 'tempdb'; - RETURN; + /* Have they cleared wait stats? Using a 10% fudge factor */ + IF @MsSinceWaitsCleared * .9 > (SELECT MAX(wait_time_ms) FROM sys.dm_os_wait_stats WHERE wait_type IN ('SP_SERVER_DIAGNOSTICS_SLEEP', 'QDS_PERSIST_TASK_MAIN_LOOP_SLEEP', 'REQUEST_FOR_DEADLOCK_SEARCH', 'HADR_FILESTREAM_IOMGR_IOCOMPLETION', 'LAZYWRITER_SLEEP', 'SQLTRACE_INCREMENTAL_FLUSH_SLEEP', 'DIRTY_PAGE_POLL', 'LOGMGR_QUEUE')) + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 185) WITH NOWAIT; - END; + SET @MsSinceWaitsCleared = (SELECT MAX(wait_time_ms) FROM sys.dm_os_wait_stats WHERE wait_type IN ('SP_SERVER_DIAGNOSTICS_SLEEP', 'QDS_PERSIST_TASK_MAIN_LOOP_SLEEP', 'REQUEST_FOR_DEADLOCK_SEARCH', 'HADR_FILESTREAM_IOMGR_IOCOMPLETION', 'LAZYWRITER_SLEEP', 'SQLTRACE_INCREMENTAL_FLUSH_SLEEP', 'DIRTY_PAGE_POLL', 'LOGMGR_QUEUE')); + IF @MsSinceWaitsCleared = 0 SET @MsSinceWaitsCleared = 1; + INSERT INTO #BlitzResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + VALUES( 185, + 240, + 'Wait Stats', + 'Wait Stats Have Been Cleared', + 'https://www.brentozar.com/go/waits', + 'Someone ran DBCC SQLPERF to clear sys.dm_os_wait_stats at approximately: ' + + CONVERT(NVARCHAR(100), + DATEADD(MINUTE, (-1. * (@MsSinceWaitsCleared) / 1000. / 60.), GETDATE()), 120)); + END; - - /* In search of restore_configuration */ + /* @CpuMsSinceWaitsCleared is used for waits stats calculations */ + + IF @Debug IN (1, 2) RAISERROR('Setting @CpuMsSinceWaitsCleared', 0, 1) WITH NOWAIT; + + SELECT @CpuMsSinceWaitsCleared = @MsSinceWaitsCleared * scheduler_count + FROM sys.dm_os_sys_info; - RAISERROR('Checking for Restore Worker tables in msdb', 0, 1) WITH NOWAIT; + /* If we're outputting CSV or Markdown, don't bother checking the plan cache because we cannot export plans. */ + IF @OutputType = 'CSV' OR @OutputType = 'MARKDOWN' + SET @CheckProcedureCache = 0; - IF OBJECT_ID('msdb.dbo.restore_configuration') IS NULL + /* If we're posting a question on Stack, include background info on the server */ + IF @OutputType = 'MARKDOWN' + SET @CheckServerInfo = 1; - BEGIN + /* Only run CheckUserDatabaseObjects if there are less than 50 databases. */ + IF @BringThePain = 0 AND 50 <= (SELECT COUNT(*) FROM sys.databases) AND @CheckUserDatabaseObjects = 1 + BEGIN + SET @CheckUserDatabaseObjects = 0; + PRINT 'Running sp_Blitz @CheckUserDatabaseObjects = 1 on a server with 50+ databases may cause temporary problems for the server and/or user.'; + PRINT 'If you''re sure you want to do this, run again with the parameter @BringThePain = 1.'; + INSERT INTO #BlitzResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT 201 AS CheckID , + 0 AS Priority , + 'Informational' AS FindingsGroup , + '@CheckUserDatabaseObjects Disabled' AS Finding , + 'https://www.BrentOzar.com/blitz/' AS URL , + 'If you want to check 50+ databases, you have to also use @BringThePain = 1.' AS Details; + END; - RAISERROR('Creating restore_configuration table in msdb', 0, 1) WITH NOWAIT; + /* Sanitize our inputs */ + SELECT + @OutputServerName = QUOTENAME(@OutputServerName), + @OutputDatabaseName = QUOTENAME(@OutputDatabaseName), + @OutputSchemaName = QUOTENAME(@OutputSchemaName), + @OutputTableName = QUOTENAME(@OutputTableName); - CREATE TABLE msdb.dbo.restore_configuration ( - database_name NVARCHAR(256), - configuration_name NVARCHAR(512), - configuration_description NVARCHAR(512), - configuration_setting NVARCHAR(MAX) - ); + /* Get the major and minor build numbers */ + + IF @Debug IN (1, 2) RAISERROR('Getting version information.', 0, 1) WITH NOWAIT; + + SET @ProductVersion = CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)); + SELECT @ProductVersionMajor = SUBSTRING(@ProductVersion, 1,CHARINDEX('.', @ProductVersion) + 1 ), + @ProductVersionMinor = PARSENAME(CONVERT(varchar(32), @ProductVersion), 2); + + /* + Whew! we're finally done with the setup, and we can start doing checks. + First, let's make sure we're actually supposed to do checks on this server. + The user could have passed in a SkipChecks table that specified to skip ALL + checks on this server, so let's check for that: + */ + IF ( ( SERVERPROPERTY('ServerName') NOT IN ( SELECT ServerName + FROM #SkipChecks + WHERE DatabaseName IS NULL + AND CheckID IS NULL ) ) + OR ( @SkipChecksTable IS NULL ) + ) + BEGIN - END; + /* + Extract DBCC DBINFO data from the server. This data is used for check 2 using + the dbi_LastLogBackupTime field and check 68 using the dbi_LastKnownGood field. + NB: DBCC DBINFO is not available on AWS RDS databases so if the server is RDS + (which will have previously triggered inserting a checkID 223 record) and at + least one of the relevant checks is not being skipped then we can extract the + dbinfo information. + */ + IF NOT EXISTS + ( + SELECT 1/0 + FROM #BlitzResults + WHERE CheckID = 223 AND URL = 'https://aws.amazon.com/rds/sqlserver/' + ) AND NOT EXISTS + ( + SELECT 1/0 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID IN (2, 68) + ) + BEGIN + IF @Debug IN (1, 2) RAISERROR('Extracting DBCC DBINFO data (used in checks 2 and 68).', 0, 1, 68) WITH NOWAIT; - ELSE + EXEC sp_MSforeachdb N'USE [?]; + SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + INSERT #DBCCs + (ParentObject, + Object, + Field, + Value) + EXEC (''DBCC DBInfo() With TableResults, NO_INFOMSGS''); + UPDATE #DBCCs SET DbName = N''?'' WHERE DbName IS NULL OPTION (RECOMPILE);'; + END + /* + Our very first check! We'll put more comments in this one just to + explain exactly how it works. First, we check to see if we're + supposed to skip CheckID 1 (that's the check we're working on.) + */ + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 1 ) + BEGIN - BEGIN + /* + Below, we check master.sys.databases looking for databases + that haven't had a backup in the last week. If we find any, + we insert them into #BlitzResults, the temp table that + tracks our server's problems. Note that if the check does + NOT find any problems, we don't save that. We're only + saving the problems, not the successful checks. + */ - RAISERROR('Restore configuration table exists, truncating', 0, 1) WITH NOWAIT; + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 1) WITH NOWAIT; - TRUNCATE TABLE msdb.dbo.restore_configuration; - - END; + IF SERVERPROPERTY('EngineEdition') <> 8 /* Azure Managed Instances need a special query */ + BEGIN + INSERT INTO #BlitzResults + ( CheckID , + DatabaseName , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT 1 AS CheckID , + d.[name] AS DatabaseName , + 1 AS Priority , + 'Backup' AS FindingsGroup , + 'Backups Not Performed Recently' AS Finding , + 'https://www.brentozar.com/go/nobak' AS URL , + 'Last backed up: ' + + COALESCE(CAST(MAX(b.backup_finish_date) AS VARCHAR(25)),'never') AS Details + FROM master.sys.databases d + LEFT OUTER JOIN msdb.dbo.backupset b ON d.name COLLATE SQL_Latin1_General_CP1_CI_AS = b.database_name COLLATE SQL_Latin1_General_CP1_CI_AS + AND b.type = 'D' + AND b.server_name = SERVERPROPERTY('ServerName') /*Backupset ran on current server */ + WHERE d.database_id <> 2 /* Bonus points if you know what that means */ + AND d.state NOT IN(1, 6, 10) /* Not currently offline or restoring, like log shipping databases */ + AND d.is_in_standby = 0 /* Not a log shipping target database */ + AND d.source_database_id IS NULL /* Excludes database snapshots */ + AND d.name NOT IN ( SELECT DISTINCT + DatabaseName + FROM #SkipChecks + WHERE CheckID IS NULL OR CheckID = 1) + /* + The above NOT IN filters out the databases we're not supposed to check. + */ + GROUP BY d.name + HAVING MAX(b.backup_finish_date) <= DATEADD(dd, + -7, GETDATE()) + OR MAX(b.backup_finish_date) IS NULL; + END; + ELSE /* SERVERPROPERTY('EngineName') must be 8, Azure Managed Instances */ + BEGIN + INSERT INTO #BlitzResults + ( CheckID , + DatabaseName , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT 1 AS CheckID , + d.[name] AS DatabaseName , + 1 AS Priority , + 'Backup' AS FindingsGroup , + 'Backups Not Performed Recently' AS Finding , + 'https://www.brentozar.com/go/nobak' AS URL , + 'Last backed up: ' + + COALESCE(CAST(MAX(b.backup_finish_date) AS VARCHAR(25)),'never') AS Details + FROM master.sys.databases d + LEFT OUTER JOIN msdb.dbo.backupset b ON d.name COLLATE SQL_Latin1_General_CP1_CI_AS = b.database_name COLLATE SQL_Latin1_General_CP1_CI_AS + AND b.type = 'D' + WHERE d.database_id <> 2 /* Bonus points if you know what that means */ + AND d.state NOT IN(1, 6, 10) /* Not currently offline or restoring, like log shipping databases */ + AND d.is_in_standby = 0 /* Not a log shipping target database */ + AND d.source_database_id IS NULL /* Excludes database snapshots */ + AND d.name NOT IN ( SELECT DISTINCT + DatabaseName + FROM #SkipChecks + WHERE CheckID IS NULL OR CheckID = 1) + /* + The above NOT IN filters out the databases we're not supposed to check. + */ + GROUP BY d.name + HAVING MAX(b.backup_finish_date) <= DATEADD(dd, + -7, GETDATE()) + OR MAX(b.backup_finish_date) IS NULL; + END; - RAISERROR('Inserting configuration values to msdb.dbo.restore_configuration', 0, 1) WITH NOWAIT; - - INSERT msdb.dbo.restore_configuration (database_name, configuration_name, configuration_description, configuration_setting) - VALUES ('all', 'log restore frequency', 'The length of time in second between Log Restores.', @RTOSeconds); - - INSERT msdb.dbo.restore_configuration (database_name, configuration_name, configuration_description, configuration_setting) - VALUES ('all', 'log restore path', 'The path to which Log Restores come from.', @RestorePath); - INSERT msdb.dbo.restore_configuration (database_name, configuration_name, configuration_description, configuration_setting) - VALUES ('all', 'move files', 'Determines if we move database files to default data/log directories.', @MoveFiles); - IF OBJECT_ID('msdb.dbo.restore_worker') IS NULL - - BEGIN - - - RAISERROR('Creating table msdb.dbo.restore_worker', 0, 1) WITH NOWAIT; - - CREATE TABLE msdb.dbo.restore_worker ( - id INT IDENTITY(1, 1) PRIMARY KEY CLUSTERED, - database_name NVARCHAR(256), - last_log_restore_start_time DATETIME DEFAULT '19000101', - last_log_restore_finish_time DATETIME DEFAULT '99991231', - is_started BIT DEFAULT 0, - is_completed BIT DEFAULT 0, - error_number INT DEFAULT NULL, - last_error_date DATETIME DEFAULT NULL, - ignore_database BIT DEFAULT 0, - full_backup_required BIT DEFAULT 0, - diff_backup_required BIT DEFAULT 0 - ); + /* + And there you have it. The rest of this stored procedure works the same + way: it asks: + - Should I skip this check? + - If not, do I find problems? + - Insert the results into #BlitzResults + */ - - RAISERROR('Inserting databases for restores', 0, 1) WITH NOWAIT; - - INSERT msdb.dbo.restore_worker (database_name) - SELECT d.name - FROM sys.databases d - WHERE NOT EXISTS ( - SELECT * - FROM msdb.dbo.restore_worker bw - WHERE bw.database_name = d.name - ) - AND d.database_id > 4; - - - END; + END; + /* + And that's the end of CheckID #1. - - /* - - Add Jobs - - */ - + CheckID #2 is a little simpler because it only involves one query, and it's + more typical for queries that people contribute. But keep reading, because + the next check gets more complex again. + */ - - /* - - Look for our ten second schedule -- all jobs use this to restart themselves if they fail + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 2 ) + BEGIN - Fun fact: you can add the same schedule name multiple times, so we don't want to just stick it in there - - */ - - - RAISERROR('Checking for ten second schedule', 0, 1) WITH NOWAIT; - - IF NOT EXISTS ( - SELECT 1 - FROM msdb.dbo.sysschedules - WHERE name = 'ten_seconds' - ) - - BEGIN - - - RAISERROR('Creating ten second schedule', 0, 1) WITH NOWAIT; - - - EXEC msdb.dbo.sp_add_schedule @schedule_name= ten_seconds, - @enabled = 1, - @freq_type = 4, - @freq_interval = 1, - @freq_subday_type = 2, - @freq_subday_interval = 10, - @freq_relative_interval = 0, - @freq_recurrence_factor = 0, - @active_start_date = @active_start_date, - @active_end_date = 99991231, - @active_start_time = 0, - @active_end_time = 235959; - - END; - - - /* - - Look for Backup Pollster job -- this job sets up our watcher for new databases to back up - - */ - - - RAISERROR('Checking for pollster job', 0, 1) WITH NOWAIT; - - - IF NOT EXISTS ( - SELECT 1 - FROM msdb.dbo.sysjobs - WHERE name = 'sp_AllNightLog_PollForNewDatabases' - ) - - - BEGIN - - - RAISERROR('Creating pollster job', 0, 1) WITH NOWAIT; - - IF @EnableBackupJobs = 1 - BEGIN - EXEC msdb.dbo.sp_add_job @job_name = sp_AllNightLog_PollForNewDatabases, - @description = 'This is a worker for the purposes of polling sys.databases for new entries to insert to the worker queue table.', - @category_name = 'Database Maintenance', - @owner_login_name = 'sa', - @enabled = 1; - END - ELSE - BEGIN - EXEC msdb.dbo.sp_add_job @job_name = sp_AllNightLog_PollForNewDatabases, - @description = 'This is a worker for the purposes of polling sys.databases for new entries to insert to the worker queue table.', - @category_name = 'Database Maintenance', - @owner_login_name = 'sa', - @enabled = 0; - END - - - RAISERROR('Adding job step', 0, 1) WITH NOWAIT; - - - EXEC msdb.dbo.sp_add_jobstep @job_name = sp_AllNightLog_PollForNewDatabases, - @step_name = sp_AllNightLog_PollForNewDatabases, - @subsystem = 'TSQL', - @command = 'EXEC sp_AllNightLog @PollForNewDatabases = 1'; - - - - RAISERROR('Adding job server', 0, 1) WITH NOWAIT; - - - EXEC msdb.dbo.sp_add_jobserver @job_name = sp_AllNightLog_PollForNewDatabases; - - - - RAISERROR('Attaching schedule', 0, 1) WITH NOWAIT; - - - EXEC msdb.dbo.sp_attach_schedule @job_name = sp_AllNightLog_PollForNewDatabases, - @schedule_name = ten_seconds; - - - END; - - - - /* - - Look for Restore Pollster job -- this job sets up our watcher for new databases to back up - - */ - - - RAISERROR('Checking for restore pollster job', 0, 1) WITH NOWAIT; - - - IF NOT EXISTS ( - SELECT 1 - FROM msdb.dbo.sysjobs - WHERE name = 'sp_AllNightLog_PollDiskForNewDatabases' - ) - - - BEGIN - - - RAISERROR('Creating restore pollster job', 0, 1) WITH NOWAIT; - - - IF @EnableRestoreJobs = 1 - BEGIN - EXEC msdb.dbo.sp_add_job @job_name = sp_AllNightLog_PollDiskForNewDatabases, - @description = 'This is a worker for the purposes of polling your restore path for new entries to insert to the worker queue table.', - @category_name = 'Database Maintenance', - @owner_login_name = 'sa', - @enabled = 1; - END - ELSE - BEGIN - EXEC msdb.dbo.sp_add_job @job_name = sp_AllNightLog_PollDiskForNewDatabases, - @description = 'This is a worker for the purposes of polling your restore path for new entries to insert to the worker queue table.', - @category_name = 'Database Maintenance', - @owner_login_name = 'sa', - @enabled = 0; - END - - - - RAISERROR('Adding restore job step', 0, 1) WITH NOWAIT; - - - EXEC msdb.dbo.sp_add_jobstep @job_name = sp_AllNightLog_PollDiskForNewDatabases, - @step_name = sp_AllNightLog_PollDiskForNewDatabases, - @subsystem = 'TSQL', - @command = 'EXEC sp_AllNightLog @PollDiskForNewDatabases = 1'; - - - - RAISERROR('Adding restore job server', 0, 1) WITH NOWAIT; - - - EXEC msdb.dbo.sp_add_jobserver @job_name = sp_AllNightLog_PollDiskForNewDatabases; - - - - RAISERROR('Attaching schedule', 0, 1) WITH NOWAIT; - - - EXEC msdb.dbo.sp_attach_schedule @job_name = sp_AllNightLog_PollDiskForNewDatabases, - @schedule_name = ten_seconds; - - - END; + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 2) WITH NOWAIT; + INSERT INTO #BlitzResults + ( CheckID , + DatabaseName , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT DISTINCT + 2 AS CheckID , + d.name AS DatabaseName , + 1 AS Priority , + 'Backup' AS FindingsGroup , + 'Full Recovery Model w/o Log Backups' AS Finding , + 'https://www.brentozar.com/go/biglogs' AS URL , + ( 'The ' + CAST(CAST((SELECT ((SUM([mf].[size]) * 8.) / 1024.) FROM sys.[master_files] AS [mf] WHERE [mf].[database_id] = d.[database_id] AND [mf].[type_desc] = 'LOG') AS DECIMAL(18,2)) AS VARCHAR(30)) + 'MB log file has not been backed up in the last week.' ) AS Details + FROM master.sys.databases d + LEFT JOIN #DBCCs ll On ll.DbName = d.name And ll.Field = 'dbi_LastLogBackupTime' + WHERE d.recovery_model IN ( 1, 2 ) + AND d.database_id NOT IN ( 2, 3 ) + AND d.source_database_id IS NULL + AND d.state NOT IN(1, 6, 10) /* Not currently offline or restoring, like log shipping databases */ + AND d.is_in_standby = 0 /* Not a log shipping target database */ + AND d.source_database_id IS NULL /* Excludes database snapshots */ + AND d.name NOT IN ( SELECT DISTINCT + DatabaseName + FROM #SkipChecks + WHERE CheckID IS NULL OR CheckID = 2) + AND ( + ( + /* We couldn't get a value from the DBCC DBINFO data so let's check the msdb backup history information */ + [ll].[Value] Is Null + AND NOT EXISTS ( SELECT * + FROM msdb.dbo.backupset b + WHERE d.name COLLATE SQL_Latin1_General_CP1_CI_AS = b.database_name COLLATE SQL_Latin1_General_CP1_CI_AS + AND b.type = 'L' + AND b.backup_finish_date >= DATEADD(dd,-7, GETDATE()) + ) + ) + OR + ( + Convert(datetime,ll.Value,21) < DATEADD(dd,-7, GETDATE()) + ) + ); + END; /* - - This section creates @Jobs (quantity) of worker jobs to take log backups with - - They work in a queue - - It's queuete - + CheckID #256 is searching for backups to NUL. */ + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 256 ) + BEGIN - RAISERROR('Checking for sp_AllNightLog backup jobs', 0, 1) WITH NOWAIT; - - - SELECT @counter = COUNT(*) + 1 - FROM msdb.dbo.sysjobs - WHERE name LIKE 'sp[_]AllNightLog[_]Backup[_]%'; - - SET @msg = 'Found ' + CONVERT(NVARCHAR(10), (@counter - 1)) + ' backup jobs -- ' + CASE WHEN @counter < @Jobs THEN + 'starting loop!' - WHEN @counter >= @Jobs THEN 'skipping loop!' - ELSE 'Oh woah something weird happened!' - END; - - RAISERROR(@msg, 0, 1) WITH NOWAIT; - - - WHILE @counter <= @Jobs - - - BEGIN - - - RAISERROR('Setting job name', 0, 1) WITH NOWAIT; - - SET @job_name_backups = N'sp_AllNightLog_Backup_' + CASE WHEN @counter < 10 THEN N'0' + CONVERT(NVARCHAR(10), @counter) - WHEN @counter >= 10 THEN CONVERT(NVARCHAR(10), @counter) - END; - - - RAISERROR('Setting @job_sql', 0, 1) WITH NOWAIT; - - - SET @job_sql = N' - - EXEC msdb.dbo.sp_add_job @job_name = ' + @job_name_backups + ', - @description = ' + @job_description_backups + ', - @category_name = ' + @job_category + ', - @owner_login_name = ' + @job_owner + ','; - IF @EnableBackupJobs = 1 - BEGIN - SET @job_sql = @job_sql + ' @enabled = 1; '; - END - ELSE - BEGIN - SET @job_sql = @job_sql + ' @enabled = 0; '; - END - - - SET @job_sql = @job_sql + ' - EXEC msdb.dbo.sp_add_jobstep @job_name = ' + @job_name_backups + ', - @step_name = ' + @job_name_backups + ', - @subsystem = ''TSQL'', - @command = ' + @job_command_backups + '; - - - EXEC msdb.dbo.sp_add_jobserver @job_name = ' + @job_name_backups + '; - - - EXEC msdb.dbo.sp_attach_schedule @job_name = ' + @job_name_backups + ', - @schedule_name = ten_seconds; - - '; - - - SET @counter += 1; - - - IF @Debug = 1 - BEGIN - RAISERROR(@job_sql, 0, 1) WITH NOWAIT; - END; - - - IF @job_sql IS NULL - BEGIN - RAISERROR('@job_sql is NULL for some reason', 0, 1) WITH NOWAIT; - END; + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 2) WITH NOWAIT; + INSERT INTO #BlitzResults + ( CheckID , + DatabaseName , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT DISTINCT + 256 AS CheckID , + d.name AS DatabaseName, + 1 AS Priority , + 'Backup' AS FindingsGroup , + 'Log Backups to NUL' AS Finding , + 'https://www.brentozar.com/go/nul' AS URL , + N'The transaction log file has been backed up ' + CAST((SELECT count(*) + FROM msdb.dbo.backupset AS b INNER JOIN + msdb.dbo.backupmediafamily AS bmf + ON b.media_set_id = bmf.media_set_id + WHERE b.database_name COLLATE SQL_Latin1_General_CP1_CI_AS = d.name COLLATE SQL_Latin1_General_CP1_CI_AS + AND bmf.physical_device_name = 'NUL' + AND b.type = 'L' + AND b.backup_finish_date >= DATEADD(dd, + -7, GETDATE())) AS NVARCHAR(8)) + ' time(s) to ''NUL'' in the last week, which means the backup does not exist. This breaks point-in-time recovery.' AS Details + FROM master.sys.databases AS d + WHERE d.recovery_model IN ( 1, 2 ) + AND d.database_id NOT IN ( 2, 3 ) + AND d.source_database_id IS NULL + AND d.state NOT IN(1, 6, 10) /* Not currently offline or restoring, like log shipping databases */ + AND d.is_in_standby = 0 /* Not a log shipping target database */ + AND d.source_database_id IS NULL /* Excludes database snapshots */ + --AND d.name NOT IN ( SELECT DISTINCT + -- DatabaseName + -- FROM #SkipChecks + -- WHERE CheckID IS NULL OR CheckID = 2) + AND EXISTS ( SELECT * + FROM msdb.dbo.backupset AS b INNER JOIN + msdb.dbo.backupmediafamily AS bmf + ON b.media_set_id = bmf.media_set_id + WHERE d.name COLLATE SQL_Latin1_General_CP1_CI_AS = b.database_name COLLATE SQL_Latin1_General_CP1_CI_AS + AND bmf.physical_device_name = 'NUL' + AND b.type = 'L' + AND b.backup_finish_date >= DATEADD(dd, + -7, GETDATE()) ); + END; - EXEC sp_executesql @job_sql; + /* + Next up, we've got CheckID 8. (These don't have to go in order.) This one + won't work on SQL Server 2005 because it relies on a new DMV that didn't + exist prior to SQL Server 2008. This means we have to check the SQL Server + version first, then build a dynamic string with the query we want to run: + */ - - END; + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 8 ) + BEGIN + IF @@VERSION NOT LIKE '%Microsoft SQL Server 2000%' + AND @@VERSION NOT LIKE '%Microsoft SQL Server 2005%' + BEGIN + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 8) WITH NOWAIT; + SET @StringToExecute = 'INSERT INTO #BlitzResults + (CheckID, Priority, + FindingsGroup, + Finding, URL, + Details) + SELECT 8 AS CheckID, + 230 AS Priority, + ''Security'' AS FindingsGroup, + ''Server Audits Running'' AS Finding, + ''https://www.brentozar.com/go/audits'' AS URL, + (''SQL Server built-in audit functionality is being used by server audit: '' + [name]) AS Details FROM sys.dm_server_audit_status OPTION (RECOMPILE);'; + + IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; + IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; + + EXECUTE(@StringToExecute); + END; + END; /* - - This section creates @Jobs (quantity) of worker jobs to restore logs with + But what if you need to run a query in every individual database? + Hop down to the @CheckUserDatabaseObjects section. - They too work in a queue + And that's the basic idea! You can read through the rest of the + checks if you like - some more exciting stuff happens closer to the + end of the stored proc, where we start doing things like checking + the plan cache, but those aren't as cleanly commented. - Like a queue-t 3.14 - + To contribute your own checks or fix bugs, learn more here: + https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/blob/main/CONTRIBUTING.md */ + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 93 ) + BEGIN - RAISERROR('Checking for sp_AllNightLog Restore jobs', 0, 1) WITH NOWAIT; - - - SELECT @counter = COUNT(*) + 1 - FROM msdb.dbo.sysjobs - WHERE name LIKE 'sp[_]AllNightLog[_]Restore[_]%'; - - SET @msg = 'Found ' + CONVERT(NVARCHAR(10), (@counter - 1)) + ' restore jobs -- ' + CASE WHEN @counter < @Jobs THEN + 'starting loop!' - WHEN @counter >= @Jobs THEN 'skipping loop!' - ELSE 'Oh woah something weird happened!' - END; - - RAISERROR(@msg, 0, 1) WITH NOWAIT; - - - WHILE @counter <= @Jobs + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 93) WITH NOWAIT; - - BEGIN + INSERT INTO #BlitzResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT + 93 AS CheckID , + 1 AS Priority , + 'Backup' AS FindingsGroup , + 'Backing Up to Same Drive Where Databases Reside' AS Finding , + 'https://www.brentozar.com/go/backup' AS URL , + CAST(COUNT(1) AS VARCHAR(50)) + ' backups done on drive ' + + UPPER(LEFT(bmf.physical_device_name, 3)) + + ' in the last two weeks, where database files also live. This represents a serious risk if that array fails.' Details + FROM msdb.dbo.backupmediafamily AS bmf + INNER JOIN msdb.dbo.backupset AS bs ON bmf.media_set_id = bs.media_set_id + AND bs.backup_start_date >= ( DATEADD(dd, + -14, GETDATE()) ) + /* Filter out databases that were recently restored: */ + LEFT OUTER JOIN msdb.dbo.restorehistory rh ON bs.database_name = rh.destination_database_name AND rh.restore_date > DATEADD(dd, -14, GETDATE()) + WHERE UPPER(LEFT(bmf.physical_device_name, 3)) <> 'HTT' AND + bmf.physical_device_name NOT LIKE '\\%' AND -- GitHub Issue #2141 + @IsWindowsOperatingSystem = 1 AND -- GitHub Issue #1995 + UPPER(LEFT(bmf.physical_device_name COLLATE SQL_Latin1_General_CP1_CI_AS, 3)) IN ( + SELECT DISTINCT + UPPER(LEFT(mf.physical_name COLLATE SQL_Latin1_General_CP1_CI_AS, 3)) + FROM sys.master_files AS mf + WHERE mf.database_id <> 2 ) + AND rh.destination_database_name IS NULL + GROUP BY UPPER(LEFT(bmf.physical_device_name, 3)); + END; - - RAISERROR('Setting job name', 0, 1) WITH NOWAIT; + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 119 ) + AND EXISTS ( SELECT * + FROM sys.all_objects o + WHERE o.name = 'dm_database_encryption_keys' ) + BEGIN - SET @job_name_restores = N'sp_AllNightLog_Restore_' + CASE WHEN @counter < 10 THEN N'0' + CONVERT(NVARCHAR(10), @counter) - WHEN @counter >= 10 THEN CONVERT(NVARCHAR(10), @counter) - END; - - - RAISERROR('Setting @job_sql', 0, 1) WITH NOWAIT; + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 119) WITH NOWAIT; - - SET @job_sql = N' + SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, DatabaseName, URL, Details) + SELECT 119 AS CheckID, + 1 AS Priority, + ''Backup'' AS FindingsGroup, + ''TDE Certificate Not Backed Up Recently'' AS Finding, + db_name(dek.database_id) AS DatabaseName, + ''https://www.brentozar.com/go/tde'' AS URL, + ''The certificate '' + c.name + '' is used to encrypt database '' + db_name(dek.database_id) + ''. Last backup date: '' + COALESCE(CAST(c.pvt_key_last_backup_date AS VARCHAR(100)), ''Never'') AS Details + FROM master.sys.certificates c INNER JOIN sys.dm_database_encryption_keys dek ON c.thumbprint = dek.encryptor_thumbprint + WHERE pvt_key_last_backup_date IS NULL OR pvt_key_last_backup_date <= DATEADD(dd, -30, GETDATE()) OPTION (RECOMPILE);'; - EXEC msdb.dbo.sp_add_job @job_name = ' + @job_name_restores + ', - @description = ' + @job_description_restores + ', - @category_name = ' + @job_category + ', - @owner_login_name = ' + @job_owner + ','; - IF @EnableRestoreJobs = 1 - BEGIN - SET @job_sql = @job_sql + ' @enabled = 1; '; - END - ELSE - BEGIN - SET @job_sql = @job_sql + ' @enabled = 0; '; - END - - - SET @job_sql = @job_sql + ' - - EXEC msdb.dbo.sp_add_jobstep @job_name = ' + @job_name_restores + ', - @step_name = ' + @job_name_restores + ', - @subsystem = ''TSQL'', - @command = ' + @job_command_restores + '; - - - EXEC msdb.dbo.sp_add_jobserver @job_name = ' + @job_name_restores + '; - - - EXEC msdb.dbo.sp_attach_schedule @job_name = ' + @job_name_restores + ', - @schedule_name = ten_seconds; - - '; + IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; + IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; - - SET @counter += 1; - - - IF @Debug = 1 - BEGIN - RAISERROR(@job_sql, 0, 1) WITH NOWAIT; - END; - - - IF @job_sql IS NULL - BEGIN - RAISERROR('@job_sql is NULL for some reason', 0, 1) WITH NOWAIT; - END; + EXECUTE(@StringToExecute); + END; + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 202 ) + AND EXISTS ( SELECT * + FROM sys.all_columns c + WHERE c.name = 'pvt_key_last_backup_date' ) + AND EXISTS ( SELECT * + FROM msdb.INFORMATION_SCHEMA.COLUMNS c + WHERE c.TABLE_NAME = 'backupset' AND c.COLUMN_NAME = 'encryptor_thumbprint' ) + BEGIN - EXEC sp_executesql @job_sql; + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 202) WITH NOWAIT; + SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) + SELECT DISTINCT 202 AS CheckID, + 1 AS Priority, + ''Backup'' AS FindingsGroup, + ''Encryption Certificate Not Backed Up Recently'' AS Finding, + ''https://www.brentozar.com/go/tde'' AS URL, + ''The certificate '' + c.name + '' is used to encrypt database backups. Last backup date: '' + COALESCE(CAST(c.pvt_key_last_backup_date AS VARCHAR(100)), ''Never'') AS Details + FROM sys.certificates c + INNER JOIN msdb.dbo.backupset bs ON c.thumbprint = bs.encryptor_thumbprint + WHERE pvt_key_last_backup_date IS NULL OR pvt_key_last_backup_date <= DATEADD(dd, -30, GETDATE()) OPTION (RECOMPILE);'; - END; - - - RAISERROR('Setup complete!', 0, 1) WITH NOWAIT; - - END; --End for the Agent job creation - - END;--End for Database and Table creation - - END TRY - - BEGIN CATCH - - - SELECT @msg = N'Error occurred during setup: ' + CONVERT(NVARCHAR(10), ERROR_NUMBER()) + ', error message is ' + ERROR_MESSAGE(), - @error_severity = ERROR_SEVERITY(), - @error_state = ERROR_STATE(); - - RAISERROR(@msg, @error_severity, @error_state) WITH NOWAIT; - - - WHILE @@TRANCOUNT > 0 - ROLLBACK; - - END CATCH; - -END; /* IF @RunSetup = 1 */ - -RETURN; - + IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; + IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; + + EXECUTE(@StringToExecute); + END; -UpdateConfigs: + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 3 ) + BEGIN + IF DATEADD(dd, -60, GETDATE()) > (SELECT TOP 1 backup_start_date FROM msdb.dbo.backupset ORDER BY backup_start_date) -IF @UpdateSetup = 1 - - BEGIN + BEGIN - /* If we're enabling backup jobs, we may need to run restore with recovery on msdbCentral to bring it online: */ - IF @EnableBackupJobs = 1 AND EXISTS (SELECT * FROM sys.databases WHERE name = 'msdbCentral' AND state = 1) - BEGIN - RAISERROR('msdbCentral exists, but is in restoring state. Running restore with recovery...', 0, 1) WITH NOWAIT; + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 3) WITH NOWAIT; - BEGIN TRY - RESTORE DATABASE [msdbCentral] WITH RECOVERY; - END TRY + INSERT INTO #BlitzResults + ( CheckID , + DatabaseName , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT TOP 1 + 3 AS CheckID , + 'msdb' , + 200 AS Priority , + 'Backup' AS FindingsGroup , + 'MSDB Backup History Not Purged' AS Finding , + 'https://www.brentozar.com/go/history' AS URL , + ( 'Database backup history retained back to ' + + CAST(bs.backup_start_date AS VARCHAR(20)) ) AS Details + FROM msdb.dbo.backupset bs + LEFT OUTER JOIN msdb.dbo.restorehistory rh ON bs.database_name = rh.destination_database_name + WHERE rh.destination_database_name IS NULL + ORDER BY bs.backup_start_date ASC; + END; + END; - BEGIN CATCH + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 186 ) + BEGIN + IF DATEADD(dd, -2, GETDATE()) < (SELECT TOP 1 backup_start_date FROM msdb.dbo.backupset ORDER BY backup_start_date) - SELECT @error_number = ERROR_NUMBER(), - @error_severity = ERROR_SEVERITY(), - @error_state = ERROR_STATE(); + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 186) WITH NOWAIT; + + INSERT INTO #BlitzResults + ( CheckID , + DatabaseName , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT TOP 1 + 186 AS CheckID , + 'msdb' , + 200 AS Priority , + 'Backup' AS FindingsGroup , + 'MSDB Backup History Purged Too Frequently' AS Finding , + 'https://www.brentozar.com/go/history' AS URL , + ( 'Database backup history only retained back to ' + + CAST(bs.backup_start_date AS VARCHAR(20)) ) AS Details + FROM msdb.dbo.backupset bs + ORDER BY backup_start_date ASC; + END; + END; - SELECT @msg = N'Error running restore with recovery on msdbCentral, error number is ' + CONVERT(NVARCHAR(10), ERROR_NUMBER()) + ', error message is ' + ERROR_MESSAGE(), - @error_severity = ERROR_SEVERITY(), - @error_state = ERROR_STATE(); + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 178 ) + AND EXISTS (SELECT * + FROM msdb.dbo.backupset bs + WHERE bs.type = 'D' + AND bs.backup_size >= 50000000000 /* At least 50GB */ + AND DATEDIFF(SECOND, bs.backup_start_date, bs.backup_finish_date) <= 60 /* Backup took less than 60 seconds */ + AND bs.backup_finish_date >= DATEADD(DAY, -14, GETDATE()) /* In the last 2 weeks */) + BEGIN - RAISERROR(@msg, @error_severity, @error_state) WITH NOWAIT; - - END CATCH; - - END - - /* Only check for this after trying to restore msdbCentral: */ - IF @EnableBackupJobs = 1 AND NOT EXISTS (SELECT * FROM sys.databases WHERE name = 'msdbCentral' AND state = 0) - BEGIN - RAISERROR('msdbCentral is not online. Repair that first, then try to enable backup jobs.', 0, 1) WITH NOWAIT; - RETURN - END - - - IF OBJECT_ID('msdbCentral.dbo.backup_configuration') IS NOT NULL - - RAISERROR('Found backup config, checking variables...', 0, 1) WITH NOWAIT; - - BEGIN - - BEGIN TRY - + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 178) WITH NOWAIT; - IF @RPOSeconds IS NOT NULL - - - BEGIN - - RAISERROR('Attempting to update RPO setting', 0, 1) WITH NOWAIT; - - UPDATE c - SET c.configuration_setting = CONVERT(NVARCHAR(10), @RPOSeconds) - FROM msdbCentral.dbo.backup_configuration AS c - WHERE c.configuration_name = N'log backup frequency'; - - END; + INSERT INTO #BlitzResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT 178 AS CheckID , + 200 AS Priority , + 'Performance' AS FindingsGroup , + 'Snapshot Backups Occurring' AS Finding , + 'https://www.brentozar.com/go/snaps' AS URL , + ( CAST(COUNT(*) AS VARCHAR(20)) + ' snapshot-looking backups have occurred in the last two weeks, indicating that IO may be freezing up.') AS Details + FROM msdb.dbo.backupset bs + WHERE bs.type = 'D' + AND bs.backup_size >= 50000000000 /* At least 50GB */ + AND DATEDIFF(SECOND, bs.backup_start_date, bs.backup_finish_date) <= 60 /* Backup took less than 60 seconds */ + AND bs.backup_finish_date >= DATEADD(DAY, -14, GETDATE()); /* In the last 2 weeks */ + END; + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 236 ) + BEGIN - IF @BackupPath IS NOT NULL - - BEGIN - - RAISERROR('Attempting to update Backup Path setting', 0, 1) WITH NOWAIT; - - UPDATE c - SET c.configuration_setting = @BackupPath - FROM msdbCentral.dbo.backup_configuration AS c - WHERE c.configuration_name = N'log backup path'; - - - END; - - END TRY - - - BEGIN CATCH - - - SELECT @error_number = ERROR_NUMBER(), - @error_severity = ERROR_SEVERITY(), - @error_state = ERROR_STATE(); - - SELECT @msg = N'Error updating backup configuration setting, error number is ' + CONVERT(NVARCHAR(10), ERROR_NUMBER()) + ', error message is ' + ERROR_MESSAGE(), - @error_severity = ERROR_SEVERITY(), - @error_state = ERROR_STATE(); + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 236) WITH NOWAIT; - RAISERROR(@msg, @error_severity, @error_state) WITH NOWAIT; - - - END CATCH; - - END; - - - IF OBJECT_ID('msdb.dbo.restore_configuration') IS NOT NULL - - RAISERROR('Found restore config, checking variables...', 0, 1) WITH NOWAIT; - - BEGIN - - BEGIN TRY - - EXEC msdb.dbo.sp_update_schedule @name = ten_seconds, @active_start_date = @active_start_date, @active_start_time = 000000; - - IF @EnableRestoreJobs IS NOT NULL - BEGIN - RAISERROR('Changing restore job status based on @EnableBackupJobs parameter...', 0, 1) WITH NOWAIT; - INSERT INTO @jobs_to_change(name) - SELECT name - FROM msdb.dbo.sysjobs - WHERE name LIKE 'sp_AllNightLog_Restore%' OR name = 'sp_AllNightLog_PollDiskForNewDatabases'; - DECLARE jobs_cursor CURSOR FOR - SELECT name - FROM @jobs_to_change - - OPEN jobs_cursor - FETCH NEXT FROM jobs_cursor INTO @current_job_name - - WHILE @@FETCH_STATUS = 0 - BEGIN - RAISERROR(@current_job_name, 0, 1) WITH NOWAIT; - EXEC msdb.dbo.sp_update_job @job_name=@current_job_name,@enabled = @EnableRestoreJobs; - FETCH NEXT FROM jobs_cursor INTO @current_job_name - END - - CLOSE jobs_cursor - DEALLOCATE jobs_cursor - DELETE @jobs_to_change; - END; + INSERT INTO #BlitzResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT TOP 1 236 AS CheckID , + 50 AS Priority , + 'Performance' AS FindingsGroup , + 'Snapshotting Too Many Databases' AS Finding , + 'https://www.brentozar.com/go/toomanysnaps' AS URL , + ( CAST(SUM(1) AS VARCHAR(20)) + ' databases snapshotted at once in the last two weeks, indicating that IO may be freezing up. Microsoft does not recommend VSS snaps for 35 or more databases.') AS Details + FROM msdb.dbo.backupset bs + WHERE bs.type = 'D' + AND bs.backup_finish_date >= DATEADD(DAY, -14, GETDATE()) /* In the last 2 weeks */ + GROUP BY bs.backup_finish_date + HAVING SUM(1) >= 35 + ORDER BY SUM(1) DESC; + END; - /* If they wanted to turn off restore jobs, wait to make sure that finishes before we start enabling the backup jobs */ - IF @EnableRestoreJobs = 0 - BEGIN - SET @started_waiting_for_jobs = GETDATE(); - SELECT @counter = COUNT(*) - FROM [msdb].[dbo].[sysjobactivity] [ja] - INNER JOIN [msdb].[dbo].[sysjobs] [j] - ON [ja].[job_id] = [j].[job_id] - WHERE [ja].[session_id] = ( - SELECT TOP 1 [session_id] - FROM [msdb].[dbo].[syssessions] - ORDER BY [agent_start_date] DESC - ) - AND [start_execution_date] IS NOT NULL - AND [stop_execution_date] IS NULL - AND [j].[name] LIKE 'sp_AllNightLog_Restore%'; - - WHILE @counter > 0 - BEGIN - IF DATEADD(SS, 120, @started_waiting_for_jobs) < GETDATE() - BEGIN - RAISERROR('OH NOES! We waited 2 minutes and restore jobs are still running. We are stopping here - get a meatbag involved to figure out if restore jobs need to be killed, and the backup jobs will need to be enabled manually.', 16, 1) WITH NOWAIT; - RETURN - END - SET @msg = N'Waiting for ' + CAST(@counter AS NVARCHAR(100)) + N' sp_AllNightLog_Restore job(s) to finish.' - RAISERROR(@msg, 0, 1) WITH NOWAIT; - WAITFOR DELAY '0:00:01'; -- Wait until the restore jobs are fully stopped - - SELECT @counter = COUNT(*) - FROM [msdb].[dbo].[sysjobactivity] [ja] - INNER JOIN [msdb].[dbo].[sysjobs] [j] - ON [ja].[job_id] = [j].[job_id] - WHERE [ja].[session_id] = ( - SELECT TOP 1 [session_id] - FROM [msdb].[dbo].[syssessions] - ORDER BY [agent_start_date] DESC - ) - AND [start_execution_date] IS NOT NULL - AND [stop_execution_date] IS NULL - AND [j].[name] LIKE 'sp_AllNightLog_Restore%'; - END - END /* IF @EnableRestoreJobs = 0 */ + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 4 ) + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 4) WITH NOWAIT; + + INSERT INTO #BlitzResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT 4 AS CheckID , + 230 AS Priority , + 'Security' AS FindingsGroup , + 'Sysadmins' AS Finding , + 'https://www.brentozar.com/go/sa' AS URL , + ( 'Login [' + l.name + + '] is a sysadmin - meaning they can do absolutely anything in SQL Server, including dropping databases or hiding their tracks.' ) AS Details + FROM master.sys.syslogins l + WHERE l.sysadmin = 1 + AND l.name <> SUSER_SNAME(0x01) + AND l.denylogin = 0 + AND l.name NOT LIKE 'NT SERVICE\%' + AND l.name <> 'l_certSignSmDetach'; /* Added in SQL 2016 */ + END; + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE CheckID = 2301 ) + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 2301) WITH NOWAIT; + + /* + #InvalidLogins is filled at the start during the permissions check + */ + + INSERT INTO #BlitzResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT 2301 AS CheckID , + 230 AS Priority , + 'Security' AS FindingsGroup , + 'Invalid login defined with Windows Authentication' AS Finding , + 'https://docs.microsoft.com/en-us/sql/relational-databases/system-stored-procedures/sp-validatelogins-transact-sql' AS URL , + ( 'Windows user or group ' + QUOTENAME(LoginName) + ' is mapped to a SQL Server principal but no longer exists in the Windows environment.') AS Details + FROM #InvalidLogins + ; + END; + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 5 ) + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 5) WITH NOWAIT; + + INSERT INTO #BlitzResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT 5 AS CheckID , + 230 AS Priority , + 'Security' AS FindingsGroup , + 'Security Admins' AS Finding , + 'https://www.brentozar.com/go/sa' AS URL , + ( 'Login [' + l.name + + '] is a security admin - meaning they can give themselves permission to do absolutely anything in SQL Server, including dropping databases or hiding their tracks.' ) AS Details + FROM master.sys.syslogins l + WHERE l.securityadmin = 1 + AND l.name <> SUSER_SNAME(0x01) + AND l.denylogin = 0; + END; - IF @EnableBackupJobs IS NOT NULL - BEGIN - RAISERROR('Changing backup job status based on @EnableBackupJobs parameter...', 0, 1) WITH NOWAIT; - INSERT INTO @jobs_to_change(name) - SELECT name - FROM msdb.dbo.sysjobs - WHERE name LIKE 'sp_AllNightLog_Backup%' OR name = 'sp_AllNightLog_PollForNewDatabases'; - DECLARE jobs_cursor CURSOR FOR - SELECT name - FROM @jobs_to_change - - OPEN jobs_cursor - FETCH NEXT FROM jobs_cursor INTO @current_job_name - - WHILE @@FETCH_STATUS = 0 - BEGIN - RAISERROR(@current_job_name, 0, 1) WITH NOWAIT; - EXEC msdb.dbo.sp_update_job @job_name=@current_job_name,@enabled = @EnableBackupJobs; - FETCH NEXT FROM jobs_cursor INTO @current_job_name - END - - CLOSE jobs_cursor - DEALLOCATE jobs_cursor - DELETE @jobs_to_change; - END; + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 104 ) + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 104) WITH NOWAIT; + + INSERT INTO #BlitzResults + ( [CheckID] , + [Priority] , + [FindingsGroup] , + [Finding] , + [URL] , + [Details] + ) + SELECT 104 AS [CheckID] , + 230 AS [Priority] , + 'Security' AS [FindingsGroup] , + 'Login Can Control Server' AS [Finding] , + 'https://www.brentozar.com/go/sa' AS [URL] , + 'Login [' + pri.[name] + + '] has the CONTROL SERVER permission - meaning they can do absolutely anything in SQL Server, including dropping databases or hiding their tracks.' AS [Details] + FROM sys.server_principals AS pri + WHERE pri.[principal_id] IN ( + SELECT p.[grantee_principal_id] + FROM sys.server_permissions AS p + WHERE p.[state] IN ( 'G', 'W' ) + AND p.[class] = 100 + AND p.[type] = 'CL' ) + AND pri.[name] NOT LIKE '##%##'; + END; + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 6 ) + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 6) WITH NOWAIT; + + INSERT INTO #BlitzResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT 6 AS CheckID , + 230 AS Priority , + 'Security' AS FindingsGroup , + 'Jobs Owned By Users' AS Finding , + 'https://www.brentozar.com/go/owners' AS URL , + ( 'Job [' + j.name + '] is owned by [' + + SUSER_SNAME(j.owner_sid) + + '] - 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); + END; + /* --TOURSTOP06-- */ + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 7 ) + BEGIN + /* --TOURSTOP02-- */ - IF @RTOSeconds IS NOT NULL + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 7) WITH NOWAIT; + + INSERT INTO #BlitzResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT 7 AS CheckID , + 230 AS Priority , + 'Security' AS FindingsGroup , + 'Stored Procedure Runs at Startup' AS Finding , + 'https://www.brentozar.com/go/startup' AS URL , + ( 'Stored procedure [master].[' + + r.SPECIFIC_SCHEMA + '].[' + + r.SPECIFIC_NAME + + '] runs automatically when SQL Server starts up. Make sure you know exactly what this stored procedure is doing, because it could pose a security risk.' ) AS Details + FROM master.INFORMATION_SCHEMA.ROUTINES r + WHERE OBJECTPROPERTY(OBJECT_ID(ROUTINE_NAME), + 'ExecIsStartup') = 1; + END; + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 10 ) + BEGIN + IF @@VERSION NOT LIKE '%Microsoft SQL Server 2000%' + AND @@VERSION NOT LIKE '%Microsoft SQL Server 2005%' BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 10) WITH NOWAIT; + + SET @StringToExecute = 'INSERT INTO #BlitzResults + (CheckID, + Priority, + FindingsGroup, + Finding, + URL, + Details) + SELECT 10 AS CheckID, + 100 AS Priority, + ''Performance'' AS FindingsGroup, + ''Resource Governor Enabled'' AS Finding, + ''https://www.brentozar.com/go/rg'' AS URL, + (''Resource Governor is enabled. Queries may be throttled. Make sure you understand how the Classifier Function is configured.'') AS Details FROM sys.resource_governor_configuration WHERE is_enabled = 1 OPTION (RECOMPILE);'; - RAISERROR('Attempting to update RTO setting', 0, 1) WITH NOWAIT; - - UPDATE c - SET c.configuration_setting = CONVERT(NVARCHAR(10), @RTOSeconds) - FROM msdb.dbo.restore_configuration AS c - WHERE c.configuration_name = N'log restore frequency'; + IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; + IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; + EXECUTE(@StringToExecute); END; + END; - - IF @RestorePath IS NOT NULL - + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 11 ) + BEGIN + IF @@VERSION NOT LIKE '%Microsoft SQL Server 2000%' BEGIN - RAISERROR('Attempting to update Restore Path setting', 0, 1) WITH NOWAIT; - - UPDATE c - SET c.configuration_setting = @RestorePath - FROM msdb.dbo.restore_configuration AS c - WHERE c.configuration_name = N'log restore path'; + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 11) WITH NOWAIT; + + SET @StringToExecute = 'INSERT INTO #BlitzResults + (CheckID, + Priority, + FindingsGroup, + Finding, + URL, + Details) + SELECT 11 AS CheckID, + 100 AS Priority, + ''Performance'' AS FindingsGroup, + ''Server Triggers Enabled'' AS Finding, + ''https://www.brentozar.com/go/logontriggers/'' AS URL, + (''Server Trigger ['' + [name] ++ ''] is enabled. Make sure you understand what that trigger is doing - the less work it does, the better.'') AS Details FROM sys.server_triggers WHERE is_disabled = 0 AND is_ms_shipped = 0 OPTION (RECOMPILE);'; + IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; + IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; + EXECUTE(@StringToExecute); END; + END; - END TRY - - - BEGIN CATCH + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 12 ) + BEGIN + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 12) WITH NOWAIT; - SELECT @error_number = ERROR_NUMBER(), - @error_severity = ERROR_SEVERITY(), - @error_state = ERROR_STATE(); + INSERT INTO #BlitzResults + ( CheckID , + DatabaseName , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT 12 AS CheckID , + [name] AS DatabaseName , + 10 AS Priority , + 'Performance' AS FindingsGroup , + 'Auto-Close Enabled' AS Finding , + 'https://www.brentozar.com/go/autoclose' AS URL , + ( 'Database [' + [name] + + '] has auto-close enabled. This setting can dramatically decrease performance.' ) AS Details + FROM sys.databases + WHERE is_auto_close_on = 1 + AND name NOT IN ( SELECT DISTINCT + DatabaseName + FROM #SkipChecks + WHERE CheckID IS NULL OR CheckID = 12); + END; - SELECT @msg = N'Error updating restore configuration setting, error number is ' + CONVERT(NVARCHAR(10), ERROR_NUMBER()) + ', error message is ' + ERROR_MESSAGE(), - @error_severity = ERROR_SEVERITY(), - @error_state = ERROR_STATE(); + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 13 ) + BEGIN - RAISERROR(@msg, @error_severity, @error_state) WITH NOWAIT; - + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 13) WITH NOWAIT; + + INSERT INTO #BlitzResults + ( CheckID , + DatabaseName , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT 13 AS CheckID , + [name] AS DatabaseName , + 10 AS Priority , + 'Performance' AS FindingsGroup , + 'Auto-Shrink Enabled' AS Finding , + 'https://www.brentozar.com/go/autoshrink' AS URL , + ( 'Database [' + [name] + + '] has auto-shrink enabled. This setting can dramatically decrease performance.' ) AS Details + FROM sys.databases + WHERE is_auto_shrink_on = 1 + AND state <> 6 /* Offline */ + AND name NOT IN ( SELECT DISTINCT + DatabaseName + FROM #SkipChecks + WHERE CheckID IS NULL OR CheckID = 13); + END; - END CATCH; + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 14 ) + BEGIN + IF @@VERSION NOT LIKE '%Microsoft SQL Server 2000%' + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 14) WITH NOWAIT; - END; + SET @StringToExecute = 'INSERT INTO #BlitzResults + (CheckID, + DatabaseName, + Priority, + FindingsGroup, + Finding, + URL, + Details) + SELECT 14 AS CheckID, + [name] as DatabaseName, + 50 AS Priority, + ''Reliability'' AS FindingsGroup, + ''Page Verification Not Optimal'' AS Finding, + ''https://www.brentozar.com/go/torn'' AS URL, + (''Database ['' + [name] + ''] has '' + [page_verify_option_desc] + '' for page verification. SQL Server may have a harder time recognizing and recovering from storage corruption. Consider using CHECKSUM instead.'') COLLATE database_default AS Details + FROM sys.databases + WHERE page_verify_option < 2 + AND name <> ''tempdb'' + AND state NOT IN (1, 6) /* Restoring, Offline */ + and name not in (select distinct DatabaseName from #SkipChecks WHERE CheckID IS NULL OR CheckID = 14) OPTION (RECOMPILE);'; + + IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; + IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; + + EXECUTE(@StringToExecute); + END; + END; - RAISERROR('Update complete!', 0, 1) WITH NOWAIT; + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 15 ) + BEGIN - RETURN; + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 15) WITH NOWAIT; + + INSERT INTO #BlitzResults + ( CheckID , + DatabaseName , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT 15 AS CheckID , + [name] AS DatabaseName , + 110 AS Priority , + 'Performance' AS FindingsGroup , + 'Auto-Create Stats Disabled' AS Finding , + 'https://www.brentozar.com/go/acs' AS URL , + ( 'Database [' + [name] + + '] has auto-create-stats disabled. SQL Server uses statistics to build better execution plans, and without the ability to automatically create more, performance may suffer.' ) AS Details + FROM sys.databases + WHERE is_auto_create_stats_on = 0 + AND name NOT IN ( SELECT DISTINCT + DatabaseName + FROM #SkipChecks + WHERE CheckID IS NULL OR CheckID = 15); + END; - END; --End updates to configuration table + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 16 ) + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 16) WITH NOWAIT; + + INSERT INTO #BlitzResults + ( CheckID , + DatabaseName , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT 16 AS CheckID , + [name] AS DatabaseName , + 110 AS Priority , + 'Performance' AS FindingsGroup , + 'Auto-Update Stats Disabled' AS Finding , + 'https://www.brentozar.com/go/aus' AS URL , + ( 'Database [' + [name] + + '] has auto-update-stats disabled. SQL Server uses statistics to build better execution plans, and without the ability to automatically update them, performance may suffer.' ) AS Details + FROM sys.databases + WHERE is_auto_update_stats_on = 0 + AND name NOT IN ( SELECT DISTINCT + DatabaseName + FROM #SkipChecks + WHERE CheckID IS NULL OR CheckID = 16); + END; + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 17 ) + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 17) WITH NOWAIT; + + INSERT INTO #BlitzResults + ( CheckID , + DatabaseName , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT 17 AS CheckID , + [name] AS DatabaseName , + 150 AS Priority , + 'Performance' AS FindingsGroup , + 'Stats Updated Asynchronously' AS Finding , + 'https://www.brentozar.com/go/asyncstats' AS URL , + ( 'Database [' + [name] + + '] has auto-update-stats-async enabled. When SQL Server gets a query for a table with out-of-date statistics, it will run the query with the stats it has - while updating stats to make later queries better. The initial run of the query may suffer, though.' ) AS Details + FROM sys.databases + WHERE is_auto_update_stats_async_on = 1 + AND name NOT IN ( SELECT DISTINCT + DatabaseName + FROM #SkipChecks + WHERE CheckID IS NULL OR CheckID = 17); + END; -END; -- Final END for stored proc -GO + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 20 ) + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 20) WITH NOWAIT; + + INSERT INTO #BlitzResults + ( CheckID , + DatabaseName , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT 20 AS CheckID , + [name] AS DatabaseName , + 200 AS Priority , + 'Informational' AS FindingsGroup , + 'Date Correlation On' AS Finding , + 'https://www.brentozar.com/go/corr' AS URL , + ( 'Database [' + [name] + + '] has date correlation enabled. This is not a default setting, and it has some performance overhead. It tells SQL Server that date fields in two tables are related, and SQL Server maintains statistics showing that relation.' ) AS Details + FROM sys.databases + WHERE is_date_correlation_on = 1 + AND name NOT IN ( SELECT DISTINCT + DatabaseName + FROM #SkipChecks + WHERE CheckID IS NULL OR CheckID = 20); + END; -SET ANSI_NULLS ON; -SET ANSI_PADDING ON; -SET ANSI_WARNINGS ON; -SET ARITHABORT ON; -SET CONCAT_NULL_YIELDS_NULL ON; -SET QUOTED_IDENTIFIER ON; -SET STATISTICS IO OFF; -SET STATISTICS TIME OFF; -GO - -IF OBJECT_ID('dbo.sp_AllNightLog') IS NULL - EXEC ('CREATE PROCEDURE dbo.sp_AllNightLog AS RETURN 0;') -GO - - -ALTER PROCEDURE dbo.sp_AllNightLog - @PollForNewDatabases BIT = 0, /* Formerly Pollster */ - @Backup BIT = 0, /* Formerly LogShaming */ - @PollDiskForNewDatabases BIT = 0, - @Restore BIT = 0, - @Debug BIT = 0, - @Help BIT = 0, - @Version VARCHAR(30) = NULL OUTPUT, - @VersionDate DATETIME = NULL OUTPUT, - @VersionCheckMode BIT = 0 -WITH RECOMPILE -AS -SET NOCOUNT ON; -SET STATISTICS XML OFF; - -BEGIN; - - -SELECT @Version = '8.19', @VersionDate = '20240222'; + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 21 ) + BEGIN + /* --TOURSTOP04-- */ + IF @@VERSION NOT LIKE '%Microsoft SQL Server 2000%' + AND @@VERSION NOT LIKE '%Microsoft SQL Server 2005%' + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 21) WITH NOWAIT; + + SET @StringToExecute = 'INSERT INTO #BlitzResults + (CheckID, + DatabaseName, + Priority, + FindingsGroup, + Finding, + URL, + Details) + SELECT 21 AS CheckID, + [name] as DatabaseName, + 200 AS Priority, + ''Informational'' AS FindingsGroup, + ''Database Encrypted'' AS Finding, + ''https://www.brentozar.com/go/tde'' AS URL, + (''Database ['' + [name] + ''] has Transparent Data Encryption enabled. Make absolutely sure you have backed up the certificate and private key, or else you will not be able to restore this database.'') AS Details + FROM sys.databases + WHERE is_encrypted = 1 + and name not in (select distinct DatabaseName from #SkipChecks WHERE CheckID IS NULL OR CheckID = 21) OPTION (RECOMPILE);'; + + IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; + IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; + + EXECUTE(@StringToExecute); + END; + END; -IF(@VersionCheckMode = 1) -BEGIN - RETURN; -END; + /* + Believe it or not, SQL Server doesn't track the default values + for sp_configure options! We'll make our own list here. + */ -IF @Help = 1 + IF @Debug IN (1, 2) RAISERROR('Generating default configuration values', 0, 1) WITH NOWAIT; -BEGIN + INSERT INTO #ConfigurationDefaults + VALUES ( 'access check cache bucket count', 0, 1001 ); + INSERT INTO #ConfigurationDefaults + VALUES ( 'access check cache quota', 0, 1002 ); + INSERT INTO #ConfigurationDefaults + VALUES ( 'Ad Hoc Distributed Queries', 0, 1003 ); + INSERT INTO #ConfigurationDefaults + VALUES ( 'affinity I/O mask', 0, 1004 ); + INSERT INTO #ConfigurationDefaults + VALUES ( 'affinity mask', 0, 1005 ); + INSERT INTO #ConfigurationDefaults + VALUES ( 'affinity64 mask', 0, 1066 ); + INSERT INTO #ConfigurationDefaults + VALUES ( 'affinity64 I/O mask', 0, 1067 ); + INSERT INTO #ConfigurationDefaults + VALUES ( 'Agent XPs', 0, 1071 ); + INSERT INTO #ConfigurationDefaults + VALUES ( 'allow updates', 0, 1007 ); + INSERT INTO #ConfigurationDefaults + VALUES ( 'awe enabled', 0, 1008 ); + INSERT INTO #ConfigurationDefaults + VALUES ( 'backup checksum default', 0, 1070 ); + INSERT INTO #ConfigurationDefaults + VALUES ( 'backup compression default', 0, 1073 ); + INSERT INTO #ConfigurationDefaults + VALUES ( 'blocked process threshold', 0, 1009 ); + INSERT INTO #ConfigurationDefaults + VALUES ( 'blocked process threshold (s)', 0, 1009 ); + INSERT INTO #ConfigurationDefaults + VALUES ( 'c2 audit mode', 0, 1010 ); + INSERT INTO #ConfigurationDefaults + VALUES ( 'clr enabled', 0, 1011 ); + INSERT INTO #ConfigurationDefaults + VALUES ( 'common criteria compliance enabled', 0, 1074 ); + INSERT INTO #ConfigurationDefaults + VALUES ( 'contained database authentication', 0, 1068 ); + INSERT INTO #ConfigurationDefaults + VALUES ( 'cost threshold for parallelism', 5, 1012 ); + INSERT INTO #ConfigurationDefaults + VALUES ( 'cross db ownership chaining', 0, 1013 ); + INSERT INTO #ConfigurationDefaults + VALUES ( 'cursor threshold', -1, 1014 ); + INSERT INTO #ConfigurationDefaults + VALUES ( 'Database Mail XPs', 0, 1072 ); + INSERT INTO #ConfigurationDefaults + VALUES ( 'default full-text language', 1033, 1016 ); + INSERT INTO #ConfigurationDefaults + VALUES ( 'default language', 0, 1017 ); + INSERT INTO #ConfigurationDefaults + VALUES ( 'default trace enabled', 1, 1018 ); + INSERT INTO #ConfigurationDefaults + VALUES ( 'disallow results from triggers', 0, 1019 ); + INSERT INTO #ConfigurationDefaults + VALUES ( 'EKM provider enabled', 0, 1075 ); + INSERT INTO #ConfigurationDefaults + VALUES ( 'filestream access level', 0, 1076 ); + INSERT INTO #ConfigurationDefaults + VALUES ( 'fill factor (%)', 0, 1020 ); + INSERT INTO #ConfigurationDefaults + VALUES ( 'ft crawl bandwidth (max)', 100, 1021 ); + INSERT INTO #ConfigurationDefaults + VALUES ( 'ft crawl bandwidth (min)', 0, 1022 ); + INSERT INTO #ConfigurationDefaults + VALUES ( 'ft notify bandwidth (max)', 100, 1023 ); + INSERT INTO #ConfigurationDefaults + VALUES ( 'ft notify bandwidth (min)', 0, 1024 ); + INSERT INTO #ConfigurationDefaults + VALUES ( 'index create memory (KB)', 0, 1025 ); + INSERT INTO #ConfigurationDefaults + VALUES ( 'in-doubt xact resolution', 0, 1026 ); + INSERT INTO #ConfigurationDefaults + VALUES ( 'lightweight pooling', 0, 1027 ); + INSERT INTO #ConfigurationDefaults + VALUES ( 'locks', 0, 1028 ); + INSERT INTO #ConfigurationDefaults + VALUES ( 'max degree of parallelism', 0, 1029 ); + INSERT INTO #ConfigurationDefaults + VALUES ( 'max full-text crawl range', 4, 1030 ); + INSERT INTO #ConfigurationDefaults + VALUES ( 'max server memory (MB)', 2147483647, 1031 ); + INSERT INTO #ConfigurationDefaults + VALUES ( 'max text repl size (B)', 65536, 1032 ); + INSERT INTO #ConfigurationDefaults + VALUES ( 'max worker threads', 0, 1033 ); + INSERT INTO #ConfigurationDefaults + VALUES ( 'media retention', 0, 1034 ); + INSERT INTO #ConfigurationDefaults + VALUES ( 'min memory per query (KB)', 1024, 1035 ); + /* Accepting both 0 and 16 below because both have been seen in the wild as defaults. */ + IF EXISTS ( SELECT * + FROM sys.configurations + WHERE name = 'min server memory (MB)' + AND value_in_use IN ( 0, 16 ) ) + INSERT INTO #ConfigurationDefaults + SELECT 'min server memory (MB)' , + CAST(value_in_use AS BIGINT), 1036 + FROM sys.configurations + WHERE name = 'min server memory (MB)'; + ELSE + INSERT INTO #ConfigurationDefaults + VALUES ( 'min server memory (MB)', 0, 1036 ); + INSERT INTO #ConfigurationDefaults + VALUES ( 'nested triggers', 1, 1037 ); + INSERT INTO #ConfigurationDefaults + VALUES ( 'network packet size (B)', 4096, 1038 ); + INSERT INTO #ConfigurationDefaults + VALUES ( 'Ole Automation Procedures', 0, 1039 ); + INSERT INTO #ConfigurationDefaults + VALUES ( 'open objects', 0, 1040 ); + INSERT INTO #ConfigurationDefaults + VALUES ( 'optimize for ad hoc workloads', 0, 1041 ); + INSERT INTO #ConfigurationDefaults + VALUES ( 'PH timeout (s)', 60, 1042 ); + INSERT INTO #ConfigurationDefaults + VALUES ( 'precompute rank', 0, 1043 ); + INSERT INTO #ConfigurationDefaults + VALUES ( 'priority boost', 0, 1044 ); + INSERT INTO #ConfigurationDefaults + VALUES ( 'query governor cost limit', 0, 1045 ); + INSERT INTO #ConfigurationDefaults + VALUES ( 'query wait (s)', -1, 1046 ); + INSERT INTO #ConfigurationDefaults + VALUES ( 'recovery interval (min)', 0, 1047 ); + INSERT INTO #ConfigurationDefaults + VALUES ( 'remote access', 1, 1048 ); + INSERT INTO #ConfigurationDefaults + VALUES ( 'remote admin connections', 0, 1049 ); + /* SQL Server 2012 changes a configuration default */ + IF @@VERSION LIKE '%Microsoft SQL Server 2005%' + OR @@VERSION LIKE '%Microsoft SQL Server 2008%' + BEGIN + INSERT INTO #ConfigurationDefaults + VALUES ( 'remote login timeout (s)', 20, 1069 ); + END; + ELSE + BEGIN + INSERT INTO #ConfigurationDefaults + VALUES ( 'remote login timeout (s)', 10, 1069 ); + END; + INSERT INTO #ConfigurationDefaults + VALUES ( 'remote proc trans', 0, 1050 ); + INSERT INTO #ConfigurationDefaults + VALUES ( 'remote query timeout (s)', 600, 1051 ); + INSERT INTO #ConfigurationDefaults + VALUES ( 'Replication XPs', 0, 1052 ); + INSERT INTO #ConfigurationDefaults + VALUES ( 'RPC parameter data validation', 0, 1053 ); + INSERT INTO #ConfigurationDefaults + VALUES ( 'scan for startup procs', 0, 1054 ); + INSERT INTO #ConfigurationDefaults + VALUES ( 'server trigger recursion', 1, 1055 ); + INSERT INTO #ConfigurationDefaults + VALUES ( 'set working set size', 0, 1056 ); + INSERT INTO #ConfigurationDefaults + VALUES ( 'show advanced options', 0, 1057 ); + INSERT INTO #ConfigurationDefaults + VALUES ( 'SMO and DMO XPs', 1, 1058 ); + INSERT INTO #ConfigurationDefaults + VALUES ( 'SQL Mail XPs', 0, 1059 ); + INSERT INTO #ConfigurationDefaults + VALUES ( 'transform noise words', 0, 1060 ); + INSERT INTO #ConfigurationDefaults + VALUES ( 'two digit year cutoff', 2049, 1061 ); + INSERT INTO #ConfigurationDefaults + VALUES ( 'user connections', 0, 1062 ); + INSERT INTO #ConfigurationDefaults + VALUES ( 'user options', 0, 1063 ); + INSERT INTO #ConfigurationDefaults + VALUES ( 'Web Assistant Procedures', 0, 1064 ); + INSERT INTO #ConfigurationDefaults + VALUES ( 'xp_cmdshell', 0, 1065 ); - PRINT ' - /* + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 22 ) + BEGIN + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 22) WITH NOWAIT; - sp_AllNightLog from http://FirstResponderKit.org - - * @PollForNewDatabases = 1 polls sys.databases for new entries - * Unfortunately no other way currently to automate new database additions when restored from backups - * No triggers or extended events that easily do this - - * @Backup = 1 polls msdbCentral.dbo.backup_worker for databases not backed up in [RPO], takes LOG backups - * Will switch to a full backup if none exists - - - To learn more, visit http://FirstResponderKit.org where you can download new - versions for free, watch training videos on how it works, get more info on - the findings, contribute your own code, and more. - - Known limitations of this version: - - Only Microsoft-supported versions of SQL Server. Sorry, 2005 and 2000! And really, maybe not even anything less than 2016. Heh. - - When restoring encrypted backups, the encryption certificate must already be installed. - - Unknown limitations of this version: - - None. (If we knew them, they would be known. Duh.) - - Changes - for the full list of improvements and fixes in this version, see: - https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/ - - - Parameter explanations: - - @PollForNewDatabases BIT, defaults to 0. When this is set to 1, runs in a perma-loop to find new entries in sys.databases - @Backup BIT, defaults to 0. When this is set to 1, runs in a perma-loop checking the backup_worker table for databases that need to be backed up - @Debug BIT, defaults to 0. Whent this is set to 1, it prints out dynamic SQL commands - @RPOSeconds BIGINT, defaults to 30. Value in seconds you want to use to determine if a new log backup needs to be taken. - @BackupPath NVARCHAR(MAX), defaults to = ''D:\Backup''. You 99.99999% will need to change this path to something else. This tells Ola''s job where to put backups. - - For more documentation: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/ - - MIT License - - Copyright (c) Brent Ozar Unlimited - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE. + INSERT INTO #BlitzResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT cd.CheckID , + 200 AS Priority , + 'Non-Default Server Config' AS FindingsGroup , + cr.name AS Finding , + 'https://www.brentozar.com/go/conf' AS URL , + ( 'This sp_configure option has been changed. Its default value is ' + + COALESCE(CAST(cd.[DefaultValue] AS VARCHAR(100)), + '(unknown)') + + ' and it has been set to ' + + CAST(cr.value_in_use AS VARCHAR(100)) + + '.' ) AS Details + FROM sys.configurations cr + INNER JOIN #ConfigurationDefaults cd ON cd.name = cr.name + LEFT OUTER JOIN #ConfigurationDefaults cdUsed ON cdUsed.name = cr.name + AND cdUsed.DefaultValue = cr.value_in_use + WHERE cdUsed.name IS NULL; + END; + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 190 ) + BEGIN - */'; + IF @Debug IN (1, 2) RAISERROR('Setting @MinServerMemory and @MaxServerMemory', 0, 1) WITH NOWAIT; -RETURN -END + SELECT @MinServerMemory = CAST(value_in_use as BIGINT) FROM sys.configurations WHERE name = 'min server memory (MB)'; + SELECT @MaxServerMemory = CAST(value_in_use as BIGINT) FROM sys.configurations WHERE name = 'max server memory (MB)'; + + IF (@MinServerMemory = @MaxServerMemory) + BEGIN -DECLARE @database NVARCHAR(128) = NULL; --Holds the database that's currently being processed -DECLARE @error_number INT = NULL; --Used for TRY/CATCH -DECLARE @error_severity INT; --Used for TRY/CATCH -DECLARE @error_state INT; --Used for TRY/CATCH -DECLARE @msg NVARCHAR(4000) = N''; --Used for RAISERROR -DECLARE @rpo INT; --Used to hold the RPO value in our configuration table -DECLARE @rto INT; --Used to hold the RPO value in our configuration table -DECLARE @backup_path NVARCHAR(MAX); --Used to hold the backup path in our configuration table -DECLARE @changebackuptype NVARCHAR(MAX); --Config table: Y = escalate to full backup, MSDB = escalate if MSDB history doesn't show a recent full. -DECLARE @encrypt NVARCHAR(MAX); --Config table: Y = encrypt the backup. N (default) = do not encrypt. -DECLARE @encryptionalgorithm NVARCHAR(MAX); --Config table: native 2014 choices include TRIPLE_DES_3KEY, AES_128, AES_192, AES_256 -DECLARE @servercertificate NVARCHAR(MAX); --Config table: server certificate that is used to encrypt the backup -DECLARE @restore_path_base NVARCHAR(MAX); --Used to hold the base backup path in our configuration table -DECLARE @restore_path_full NVARCHAR(MAX); --Used to hold the full backup path in our configuration table -DECLARE @restore_path_log NVARCHAR(MAX); --Used to hold the log backup path in our configuration table -DECLARE @restore_move_files INT; -- used to hold the move files bit in our configuration table -DECLARE @db_sql NVARCHAR(MAX) = N''; --Used to hold the dynamic SQL to create msdbCentral -DECLARE @tbl_sql NVARCHAR(MAX) = N''; --Used to hold the dynamic SQL that creates tables in msdbCentral -DECLARE @database_name NVARCHAR(256) = N'msdbCentral'; --Used to hold the name of the database we create to centralize data - --Right now it's hardcoded to msdbCentral, but I made it dynamic in case that changes down the line -DECLARE @cmd NVARCHAR(4000) = N'' --Holds dir cmd -DECLARE @FileList TABLE ( BackupFile NVARCHAR(255) ); --Where we dump @cmd -DECLARE @restore_full BIT = 0 --We use this one -DECLARE @only_logs_after NVARCHAR(30) = N'' + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 190) WITH NOWAIT; + INSERT INTO #BlitzResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + VALUES + ( 190, + 200, + 'Performance', + 'Non-Dynamic Memory', + 'https://www.brentozar.com/go/memory', + 'Minimum Server Memory setting is the same as the Maximum (both set to ' + CAST(@MinServerMemory AS NVARCHAR(50)) + '). This will not allow dynamic memory. Please revise memory settings' + ); + END; + END; + + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 188 ) + BEGIN + /* Let's set variables so that our query is still SARGable */ + + IF @Debug IN (1, 2) RAISERROR('Setting @Processors.', 0, 1) WITH NOWAIT; + + SET @Processors = (SELECT cpu_count FROM sys.dm_os_sys_info); + + IF @Debug IN (1, 2) RAISERROR('Setting @NUMANodes', 0, 1) WITH NOWAIT; + + SET @NUMANodes = (SELECT COUNT(1) + FROM sys.dm_os_performance_counters pc + WHERE pc.object_name LIKE '%Buffer Node%' + AND counter_name = 'Page life expectancy'); + /* If Cost Threshold for Parallelism is default then flag as a potential issue */ + /* If MAXDOP is default and processors > 8 or NUMA nodes > 1 then flag as potential issue */ + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 188) WITH NOWAIT; + + INSERT INTO #BlitzResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT 188 AS CheckID , + 200 AS Priority , + 'Performance' AS FindingsGroup , + cr.name AS Finding , + 'https://www.brentozar.com/go/cxpacket' AS URL , + ( 'Set to ' + CAST(cr.value_in_use AS NVARCHAR(50)) + ', its default value. Changing this sp_configure setting may reduce CXPACKET waits.') + FROM sys.configurations cr + INNER JOIN #ConfigurationDefaults cd ON cd.name = cr.name + AND cr.value_in_use = cd.DefaultValue + WHERE cr.name = 'cost threshold for parallelism' + OR (cr.name = 'max degree of parallelism' AND (@NUMANodes > 1 OR @Processors > 8)); + END; -/* + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 24 ) + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 24) WITH NOWAIT; + + INSERT INTO #BlitzResults + ( CheckID , + DatabaseName , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT DISTINCT + 24 AS CheckID , + DB_NAME(database_id) AS DatabaseName , + 170 AS Priority , + 'File Configuration' AS FindingsGroup , + 'System Database on C Drive' AS Finding , + 'https://www.brentozar.com/go/cdrive' AS URL , + ( 'The ' + DB_NAME(database_id) + + ' database has a file on the C drive. Putting system databases on the C drive runs the risk of crashing the server when it runs out of space.' ) AS Details + FROM sys.master_files + WHERE UPPER(LEFT(physical_name, 1)) = 'C' + AND DB_NAME(database_id) IN ( 'master', + 'model', 'msdb' ); + END; -Make sure we're doing something + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 25 ) + AND SERVERPROPERTY('EngineEdition') <> 8 /* Azure Managed Instances */ + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 25) WITH NOWAIT; + + INSERT INTO #BlitzResults + ( CheckID , + DatabaseName , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT TOP 1 + 25 AS CheckID , + 'tempdb' , + 20 AS Priority , + 'File Configuration' AS FindingsGroup , + 'TempDB on C Drive' AS Finding , + 'https://www.brentozar.com/go/cdrive' AS URL , + CASE WHEN growth > 0 + THEN ( 'The tempdb database has files on the C drive. TempDB frequently grows unpredictably, putting your server at risk of running out of C drive space and crashing hard. C is also often much slower than other drives, so performance may be suffering.' ) + ELSE ( 'The tempdb database has files on the C drive. TempDB is not set to Autogrow, hopefully it is big enough. C is also often much slower than other drives, so performance may be suffering.' ) + END AS Details + FROM sys.master_files + WHERE UPPER(LEFT(physical_name, 1)) = 'C' + AND DB_NAME(database_id) = 'tempdb'; + END; -*/ + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 26 ) + AND SERVERPROPERTY('EngineEdition') <> 8 /* Azure Managed Instances */ + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 26) WITH NOWAIT; + + INSERT INTO #BlitzResults + ( CheckID , + DatabaseName , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT DISTINCT + 26 AS CheckID , + DB_NAME(database_id) AS DatabaseName , + 20 AS Priority , + 'Reliability' AS FindingsGroup , + 'User Databases on C Drive' AS Finding , + 'https://www.brentozar.com/go/cdrive' AS URL , + ( 'The ' + DB_NAME(database_id) + + ' database has a file on the C drive. Putting databases on the C drive runs the risk of crashing the server when it runs out of space.' ) AS Details + FROM sys.master_files + WHERE UPPER(LEFT(physical_name, 1)) = 'C' + AND DB_NAME(database_id) NOT IN ( 'master', + 'model', 'msdb', + 'tempdb' ) + AND DB_NAME(database_id) NOT IN ( + SELECT DISTINCT + DatabaseName + FROM #SkipChecks + WHERE CheckID IS NULL OR CheckID = 26 ); + END; -IF ( - @PollForNewDatabases = 0 - AND @PollDiskForNewDatabases = 0 - AND @Backup = 0 - AND @Restore = 0 - AND @Help = 0 -) - BEGIN - RAISERROR('You don''t seem to have picked an action for this stored procedure to take.', 0, 1) WITH NOWAIT - - RETURN; - END + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 27 ) + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 27) WITH NOWAIT; + + INSERT INTO #BlitzResults + ( CheckID , + DatabaseName , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT 27 AS CheckID , + 'master' AS DatabaseName , + 200 AS Priority , + 'Informational' AS FindingsGroup , + 'Tables in the Master Database' AS Finding , + 'https://www.brentozar.com/go/mastuser' AS URL , + ( 'The ' + name + + ' table in the master database was created by end users on ' + + CAST(create_date AS VARCHAR(20)) + + '. Tables in the master database may not be restored in the event of a disaster.' ) AS Details + FROM master.sys.tables + WHERE is_ms_shipped = 0 + AND name NOT IN ('CommandLog','SqlServerVersions','$ndo$srvproperty'); + /* That last one is the Dynamics NAV licensing table: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2426 */ + END; -/* -Make sure xp_cmdshell is enabled -*/ -IF NOT EXISTS (SELECT * FROM sys.configurations WHERE name = 'xp_cmdshell' AND value_in_use = 1) - BEGIN - RAISERROR('xp_cmdshell must be enabled so we can get directory contents to check for new databases to restore.', 0, 1) WITH NOWAIT - - RETURN; - END - -/* -Make sure Ola Hallengren's scripts are installed in same database -*/ -DECLARE @CurrentDatabaseContext nvarchar(128) = DB_NAME(); -IF NOT EXISTS (SELECT * FROM sys.procedures WHERE name = 'CommandExecute') - BEGIN - RAISERROR('Ola Hallengren''s CommandExecute must be installed in the same database (%s) as SQL Server First Responder Kit. More info: http://ola.hallengren.com', 0, 1, @CurrentDatabaseContext) WITH NOWAIT; - - RETURN; - END -IF NOT EXISTS (SELECT * FROM sys.procedures WHERE name = 'DatabaseBackup') - BEGIN - RAISERROR('Ola Hallengren''s DatabaseBackup must be installed in the same database (%s) as SQL Server First Responder Kit. More info: http://ola.hallengren.com', 0, 1, @CurrentDatabaseContext) WITH NOWAIT; - - RETURN; - END - -/* -Make sure sp_DatabaseRestore is installed in same database -*/ -IF NOT EXISTS (SELECT * FROM sys.procedures WHERE name = 'sp_DatabaseRestore') - BEGIN - RAISERROR('sp_DatabaseRestore must be installed in the same database (%s) as SQL Server First Responder Kit. To get it: http://FirstResponderKit.org', 0, 1, @CurrentDatabaseContext) WITH NOWAIT; - - RETURN; - END - - -IF (@PollDiskForNewDatabases = 1 OR @Restore = 1) AND OBJECT_ID('msdb.dbo.restore_configuration') IS NOT NULL - BEGIN + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 28 ) + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 28) WITH NOWAIT; + + INSERT INTO #BlitzResults + ( CheckID , + DatabaseName , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT 28 AS CheckID , + 'msdb' AS DatabaseName , + 200 AS Priority , + 'Informational' AS FindingsGroup , + 'Tables in the MSDB Database' AS Finding , + 'https://www.brentozar.com/go/msdbuser' AS URL , + ( 'The ' + name + + ' table in the msdb database was created by end users on ' + + CAST(create_date AS VARCHAR(20)) + + '. Tables in the msdb database may not be restored in the event of a disaster.' ) AS Details + FROM msdb.sys.tables + WHERE is_ms_shipped = 0 AND name NOT LIKE '%DTA_%'; + END; - IF @Debug = 1 RAISERROR('Checking restore path', 0, 1) WITH NOWAIT; + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 29 ) + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 29) WITH NOWAIT; + + INSERT INTO #BlitzResults + ( CheckID , + DatabaseName , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT 29 AS CheckID , + 'model' AS DatabaseName , + 200 AS Priority , + 'Informational' AS FindingsGroup , + 'Tables in the Model Database' AS Finding , + 'https://www.brentozar.com/go/model' AS URL , + ( 'The ' + name + + ' table in the model database was created by end users on ' + + CAST(create_date AS VARCHAR(20)) + + '. Tables in the model database are automatically copied into all new databases.' ) AS Details + FROM model.sys.tables + WHERE is_ms_shipped = 0; + END; - SELECT @restore_path_base = CONVERT(NVARCHAR(512), configuration_setting) - FROM msdb.dbo.restore_configuration c - WHERE configuration_name = N'log restore path'; + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 30 ) + BEGIN + IF ( SELECT COUNT(*) + FROM msdb.dbo.sysalerts + WHERE severity BETWEEN 19 AND 25 + ) < 7 + BEGIN - IF @restore_path_base IS NULL - BEGIN - RAISERROR('@restore_path cannot be NULL. Please check the msdb.dbo.restore_configuration table', 0, 1) WITH NOWAIT; - RETURN; - END; + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 30) WITH NOWAIT; - IF CHARINDEX('**', @restore_path_base) <> 0 - BEGIN + INSERT INTO #BlitzResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT 30 AS CheckID , + 200 AS Priority , + 'Monitoring' AS FindingsGroup , + 'Not All Alerts Configured' AS Finding , + 'https://www.brentozar.com/go/alert' AS URL , + ( 'Not all SQL Server Agent alerts have been configured. This is a free, easy way to get notified of corruption, job failures, or major outages even before monitoring systems pick it up.' ) AS Details; + END; + END; - /* If they passed in a dynamic **DATABASENAME**, stop at that folder looking for databases. More info: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/993 */ - IF CHARINDEX('**DATABASENAME**', @restore_path_base) <> 0 - BEGIN - SET @restore_path_base = SUBSTRING(@restore_path_base, 1, CHARINDEX('**DATABASENAME**',@restore_path_base) - 2); - END; + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 59 ) + BEGIN + IF EXISTS ( SELECT * + FROM msdb.dbo.sysalerts + WHERE enabled = 1 + AND COALESCE(has_notification, 0) = 0 + AND (job_id IS NULL OR job_id = 0x)) - SET @restore_path_base = REPLACE(@restore_path_base, '**AVAILABILITYGROUP**', ''); - SET @restore_path_base = REPLACE(@restore_path_base, '**BACKUPTYPE**', 'FULL'); - SET @restore_path_base = REPLACE(@restore_path_base, '**SERVERNAME**', REPLACE(CAST(SERVERPROPERTY('servername') AS nvarchar(max)),'\','$')); + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 59) WITH NOWAIT; - IF CHARINDEX('\',CAST(SERVERPROPERTY('servername') AS nvarchar(max))) > 0 - BEGIN - SET @restore_path_base = REPLACE(@restore_path_base, '**SERVERNAMEWITHOUTINSTANCE**', SUBSTRING(CAST(SERVERPROPERTY('servername') AS nvarchar(max)), 1, (CHARINDEX('\',CAST(SERVERPROPERTY('servername') AS nvarchar(max))) - 1))); - SET @restore_path_base = REPLACE(@restore_path_base, '**INSTANCENAME**', SUBSTRING(CAST(SERVERPROPERTY('servername') AS nvarchar(max)), CHARINDEX('\',CAST(SERVERPROPERTY('servername') AS nvarchar(max))), (LEN(CAST(SERVERPROPERTY('servername') AS nvarchar(max))) - CHARINDEX('\',CAST(SERVERPROPERTY('servername') AS nvarchar(max)))) + 1)); - END - ELSE /* No instance installed */ - BEGIN - SET @restore_path_base = REPLACE(@restore_path_base, '**SERVERNAMEWITHOUTINSTANCE**', CAST(SERVERPROPERTY('servername') AS nvarchar(max))); - SET @restore_path_base = REPLACE(@restore_path_base, '**INSTANCENAME**', 'DEFAULT'); - END + INSERT INTO #BlitzResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT 59 AS CheckID , + 200 AS Priority , + 'Monitoring' AS FindingsGroup , + 'Alerts Configured without Follow Up' AS Finding , + 'https://www.brentozar.com/go/alert' AS URL , + ( 'SQL Server Agent alerts have been configured but they either do not notify anyone or else they do not take any action. This is a free, easy way to get notified of corruption, job failures, or major outages even before monitoring systems pick it up.' ) AS Details; + + END; + END; - IF CHARINDEX('**CLUSTER**', @restore_path_base) <> 0 - BEGIN - DECLARE @ClusterName NVARCHAR(128); - IF EXISTS(SELECT * FROM sys.all_objects WHERE name = 'dm_hadr_cluster') - BEGIN - SELECT @ClusterName = cluster_name FROM sys.dm_hadr_cluster; - END - SET @restore_path_base = REPLACE(@restore_path_base, '**CLUSTER**', COALESCE(@ClusterName,'')); - END; + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 96 ) + BEGIN + IF NOT EXISTS ( SELECT * + FROM msdb.dbo.sysalerts + WHERE message_id IN ( 823, 824, 825 ) ) + + BEGIN; + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 96) WITH NOWAIT; + + INSERT INTO #BlitzResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT 96 AS CheckID , + 200 AS Priority , + 'Monitoring' AS FindingsGroup , + 'No Alerts for Corruption' AS Finding , + 'https://www.brentozar.com/go/alert' AS URL , + ( 'SQL Server Agent alerts do not exist for errors 823, 824, and 825. These three errors can give you notification about early hardware failure. Enabling them can prevent you a lot of heartbreak.' ) AS Details; + + END; + END; - END /* IF CHARINDEX('**', @restore_path_base) <> 0 */ - - SELECT @restore_move_files = CONVERT(BIT, configuration_setting) - FROM msdb.dbo.restore_configuration c - WHERE configuration_name = N'move files'; - - IF @restore_move_files is NULL - BEGIN - -- Set to default value of 1 - SET @restore_move_files = 1 - END + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 61 ) + BEGIN + IF NOT EXISTS ( SELECT * + FROM msdb.dbo.sysalerts + WHERE severity BETWEEN 19 AND 25 ) + + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 61) WITH NOWAIT; + + INSERT INTO #BlitzResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT 61 AS CheckID , + 200 AS Priority , + 'Monitoring' AS FindingsGroup , + 'No Alerts for Sev 19-25' AS Finding , + 'https://www.brentozar.com/go/alert' AS URL , + ( 'SQL Server Agent alerts do not exist for severity levels 19 through 25. These are some very severe SQL Server errors. Knowing that these are happening may let you recover from errors faster.' ) AS Details; + + END; - END /* IF @PollDiskForNewDatabases = 1 OR @Restore = 1 */ + END; + --check for disabled alerts + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 98 ) + BEGIN + IF EXISTS ( SELECT name + FROM msdb.dbo.sysalerts + WHERE enabled = 0 ) + + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 98) WITH NOWAIT; + + INSERT INTO #BlitzResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT 98 AS CheckID , + 200 AS Priority , + 'Monitoring' AS FindingsGroup , + 'Alerts Disabled' AS Finding , + 'https://www.brentozar.com/go/alert' AS URL , + ( 'The following Alert is disabled, please review and enable if desired: ' + + name ) AS Details + FROM msdb.dbo.sysalerts + WHERE enabled = 0; + END; + END; -/* + --check for alerts that do NOT include event descriptions in their outputs via email/pager/net-send + IF NOT EXISTS ( + SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL + AND CheckID = 219 + ) + BEGIN; + IF @Debug IN (1, 2) + BEGIN; + RAISERROR ('Running CheckId [%d].', 0, 1, 219) WITH NOWAIT; + END; -Certain variables necessarily skip to parts of this script that are irrelevant -in both directions to each other. They are used for other stuff. + INSERT INTO #BlitzResults ( + CheckID + ,[Priority] + ,FindingsGroup + ,Finding + ,[URL] + ,Details + ) + SELECT 219 AS CheckID + ,200 AS [Priority] + ,'Monitoring' AS FindingsGroup + ,'Alerts Without Event Descriptions' AS Finding + ,'https://www.brentozar.com/go/alert' AS [URL] + ,('The following Alert is not including detailed event descriptions in its output messages: ' + QUOTENAME([name]) + + '. You can fix it by ticking the relevant boxes in its Properties --> Options page.') AS Details + FROM msdb.dbo.sysalerts + WHERE [enabled] = 1 + AND include_event_description = 0 --bitmask: 1 = email, 2 = pager, 4 = net send + ; + END; -*/ + --check whether we have NO ENABLED operators! + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 31 ) + BEGIN; + IF NOT EXISTS ( SELECT * + FROM msdb.dbo.sysoperators + WHERE enabled = 1 ) + + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 31) WITH NOWAIT; + + INSERT INTO #BlitzResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT 31 AS CheckID , + 200 AS Priority , + 'Monitoring' AS FindingsGroup , + 'No Operators Configured/Enabled' AS Finding , + 'https://www.brentozar.com/go/op' AS URL , + ( 'No SQL Server Agent operators (emails) have been configured. This is a free, easy way to get notified of corruption, job failures, or major outages even before monitoring systems pick it up.' ) AS Details; + + END; + END; + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 34 ) + BEGIN + IF EXISTS ( SELECT * + FROM sys.all_objects + WHERE name = 'dm_db_mirroring_auto_page_repair' ) + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 34) WITH NOWAIT; + + SET @StringToExecute = 'INSERT INTO #BlitzResults + (CheckID, + DatabaseName, + Priority, + FindingsGroup, + Finding, + URL, + Details) + SELECT DISTINCT + 34 AS CheckID , + db.name , + 1 AS Priority , + ''Corruption'' AS FindingsGroup , + ''Database Corruption Detected'' AS Finding , + ''https://www.brentozar.com/go/repair'' AS URL , + ( ''Database mirroring has automatically repaired at least one corrupt page in the last 30 days. For more information, query the DMV sys.dm_db_mirroring_auto_page_repair.'' ) AS Details + FROM (SELECT rp2.database_id, rp2.modification_time + FROM sys.dm_db_mirroring_auto_page_repair rp2 + WHERE rp2.[database_id] not in ( + SELECT db2.[database_id] + FROM sys.databases as db2 + WHERE db2.[state] = 1 + ) ) as rp + INNER JOIN master.sys.databases db ON rp.database_id = db.database_id + WHERE rp.modification_time >= DATEADD(dd, -30, GETDATE()) OPTION (RECOMPILE);'; -/* + IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; + IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; -Pollster use happens strictly to check for new databases in sys.databases to place them in a worker queue + EXECUTE(@StringToExecute); + END; + END; -*/ + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 89 ) + BEGIN + IF EXISTS ( SELECT * + FROM sys.all_objects + WHERE name = 'dm_hadr_auto_page_repair' ) + BEGIN -IF @PollForNewDatabases = 1 - GOTO Pollster; + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 89) WITH NOWAIT; -/* + SET @StringToExecute = 'INSERT INTO #BlitzResults + (CheckID, + DatabaseName, + Priority, + FindingsGroup, + Finding, + URL, + Details) + SELECT DISTINCT + 89 AS CheckID , + db.name , + 1 AS Priority , + ''Corruption'' AS FindingsGroup , + ''Database Corruption Detected'' AS Finding , + ''https://www.brentozar.com/go/repair'' AS URL , + ( ''Availability Groups has automatically repaired at least one corrupt page in the last 30 days. For more information, query the DMV sys.dm_hadr_auto_page_repair.'' ) AS Details + FROM sys.dm_hadr_auto_page_repair rp + INNER JOIN master.sys.databases db ON rp.database_id = db.database_id + WHERE rp.modification_time >= DATEADD(dd, -30, GETDATE()) OPTION (RECOMPILE) ;'; + + IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; + IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; + + EXECUTE(@StringToExecute); + END; + END; -LogShamer happens when we need to find and assign work to a worker job for backups + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 90 ) + BEGIN + IF EXISTS ( SELECT * + FROM msdb.sys.all_objects + WHERE name = 'suspect_pages' ) + BEGIN -*/ + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 90) WITH NOWAIT; -IF @Backup = 1 - GOTO LogShamer; + SET @StringToExecute = 'INSERT INTO #BlitzResults + (CheckID, + DatabaseName, + Priority, + FindingsGroup, + Finding, + URL, + Details) + SELECT DISTINCT + 90 AS CheckID , + db.name , + 1 AS Priority , + ''Corruption'' AS FindingsGroup , + ''Database Corruption Detected'' AS Finding , + ''https://www.brentozar.com/go/repair'' AS URL , + ( ''SQL Server has detected at least one corrupt page in the last 30 days. For more information, query the system table msdb.dbo.suspect_pages.'' ) AS Details + FROM msdb.dbo.suspect_pages sp + INNER JOIN master.sys.databases db ON sp.database_id = db.database_id + WHERE sp.last_update_date >= DATEADD(dd, -30, GETDATE()) OPTION (RECOMPILE);'; -/* + IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; + IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; -Pollster use happens strictly to check for new databases in sys.databases to place them in a worker queue + EXECUTE(@StringToExecute); + END; + END; -*/ + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 36 ) + BEGIN -IF @PollDiskForNewDatabases = 1 - GOTO DiskPollster; + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 36) WITH NOWAIT; + INSERT INTO #BlitzResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT DISTINCT + 36 AS CheckID , + 150 AS Priority , + 'Performance' AS FindingsGroup , + 'Slow Storage Reads on Drive ' + + UPPER(LEFT(mf.physical_name, 1)) AS Finding , + 'https://www.brentozar.com/go/slow' AS URL , + 'Reads are averaging longer than 200ms for at least one database on this drive. For specific database file speeds, run the query from the information link.' AS Details + FROM sys.dm_io_virtual_file_stats(NULL, NULL) + AS fs + INNER JOIN sys.master_files AS mf ON fs.database_id = mf.database_id + AND fs.[file_id] = mf.[file_id] + WHERE ( io_stall_read_ms / ( 1.0 + num_of_reads ) ) > 200 + AND num_of_reads > 100000; + END; -/* + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 37 ) + BEGIN -Restoregasm Addict happens when we need to find and assign work to a worker job for restores + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 37) WITH NOWAIT; -*/ + INSERT INTO #BlitzResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT DISTINCT + 37 AS CheckID , + 150 AS Priority , + 'Performance' AS FindingsGroup , + 'Slow Storage Writes on Drive ' + + UPPER(LEFT(mf.physical_name, 1)) AS Finding , + 'https://www.brentozar.com/go/slow' AS URL , + 'Writes are averaging longer than 100ms for at least one database on this drive. For specific database file speeds, run the query from the information link.' AS Details + FROM sys.dm_io_virtual_file_stats(NULL, NULL) + AS fs + INNER JOIN sys.master_files AS mf ON fs.database_id = mf.database_id + AND fs.[file_id] = mf.[file_id] + WHERE ( io_stall_write_ms / ( 1.0 + + num_of_writes ) ) > 100 + AND num_of_writes > 100000; + END; -IF @Restore = 1 - GOTO Restoregasm_Addict; + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 40 ) + BEGIN + IF ( SELECT COUNT(*) + FROM tempdb.sys.database_files + WHERE type_desc = 'ROWS' + ) = 1 + BEGIN + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 40) WITH NOWAIT; + INSERT INTO #BlitzResults + ( CheckID , + DatabaseName , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + VALUES ( 40 , + 'tempdb' , + 170 , + 'File Configuration' , + 'TempDB Only Has 1 Data File' , + 'https://www.brentozar.com/go/tempdb' , + 'TempDB is only configured with one data file. More data files are usually required to alleviate SGAM contention.' + ); + END; + END; -/* + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 183 ) -Begin Polling section + BEGIN -*/ - - - -/* - -This section runs in a loop checking for new databases added to the server, or broken backups - -*/ - - -Pollster: - - IF @Debug = 1 RAISERROR('Beginning Pollster', 0, 1) WITH NOWAIT; - - IF OBJECT_ID('msdbCentral.dbo.backup_worker') IS NOT NULL - - BEGIN - - WHILE @PollForNewDatabases = 1 - - BEGIN - - BEGIN TRY - - IF @Debug = 1 RAISERROR('Checking for new databases...', 0, 1) WITH NOWAIT; + IF ( SELECT COUNT (distinct [size]) + FROM tempdb.sys.database_files + WHERE type_desc = 'ROWS' + HAVING MAX((size * 8) / (1024. * 1024)) - MIN((size * 8) / (1024. * 1024)) > 1. + ) <> 1 + BEGIN - /* - - Look for new non-system databases -- there should probably be additional filters here for accessibility, etc. + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 183) WITH NOWAIT; - */ - - INSERT msdbCentral.dbo.backup_worker (database_name) - SELECT d.name - FROM sys.databases d - WHERE NOT EXISTS ( - SELECT 1 - FROM msdbCentral.dbo.backup_worker bw - WHERE bw.database_name = d.name + INSERT INTO #BlitzResults + ( CheckID , + DatabaseName , + Priority , + FindingsGroup , + Finding , + URL , + Details ) - AND d.database_id > 4; + VALUES ( 183 , + 'tempdb' , + 170 , + 'File Configuration' , + 'TempDB Unevenly Sized Data Files' , + 'https://www.brentozar.com/go/tempdb' , + 'TempDB data files are not configured with the same size. Unevenly sized tempdb data files will result in unevenly sized workloads.' + ); + END; + END; - IF @Debug = 1 RAISERROR('Checking for wayward databases', 0, 1) WITH NOWAIT; + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 44 ) + BEGIN - /* - - This section aims to find databases that have - * Had a log backup ever (the default for finish time is 9999-12-31, so anything with a more recent finish time has had a log backup) - * Not had a log backup start in the last 5 minutes (this could be trouble! or a really big log backup) - * Also checks msdb.dbo.backupset to make sure the database has a full backup associated with it (otherwise it's the first full, and we don't need to start taking log backups yet) + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 44) WITH NOWAIT; - */ - - IF EXISTS ( - - SELECT 1 - FROM msdbCentral.dbo.backup_worker bw WITH (READPAST) - WHERE bw.last_log_backup_finish_time < '99991231' - AND bw.last_log_backup_start_time < DATEADD(SECOND, (@rpo * -1), GETDATE()) - AND EXISTS ( - SELECT 1 - FROM msdb.dbo.backupset b - WHERE b.database_name = bw.database_name - AND b.type = 'D' - ) + INSERT INTO #BlitzResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details ) - - BEGIN - - IF @Debug = 1 RAISERROR('Resetting databases with a log backup and no log backup in the last 5 minutes', 0, 1) WITH NOWAIT; - - - UPDATE bw - SET bw.is_started = 0, - bw.is_completed = 1, - bw.last_log_backup_start_time = '19000101' - FROM msdbCentral.dbo.backup_worker bw - WHERE bw.last_log_backup_finish_time < '99991231' - AND bw.last_log_backup_start_time < DATEADD(SECOND, (@rpo * -1), GETDATE()) - AND EXISTS ( - SELECT 1 - FROM msdb.dbo.backupset b - WHERE b.database_name = bw.database_name - AND b.type = 'D' - ); - - - END; --End check for wayward databases - - /* - - Wait 1 minute between runs, we don't need to be checking this constantly - - */ + SELECT 44 AS CheckID , + 150 AS Priority , + 'Performance' AS FindingsGroup , + 'Queries Forcing Order Hints' AS Finding , + 'https://www.brentozar.com/go/hints' AS URL , + CAST(occurrence AS VARCHAR(10)) + + ' instances of order hinting have been recorded since restart. This means queries are bossing the SQL Server optimizer around, and if they don''t know what they''re doing, this can cause more harm than good. This can also explain why DBA tuning efforts aren''t working.' AS Details + FROM sys.dm_exec_query_optimizer_info + WHERE counter = 'order hint' + AND occurrence > 1000; + END; - - IF @Debug = 1 RAISERROR('Waiting for 1 minute', 0, 1) WITH NOWAIT; - - WAITFOR DELAY '00:01:00.000'; + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 45 ) + BEGIN - END TRY + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 45) WITH NOWAIT; - BEGIN CATCH + INSERT INTO #BlitzResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT 45 AS CheckID , + 150 AS Priority , + 'Performance' AS FindingsGroup , + 'Queries Forcing Join Hints' AS Finding , + 'https://www.brentozar.com/go/hints' AS URL , + CAST(occurrence AS VARCHAR(10)) + + ' instances of join hinting have been recorded since restart. This means queries are bossing the SQL Server optimizer around, and if they don''t know what they''re doing, this can cause more harm than good. This can also explain why DBA tuning efforts aren''t working.' AS Details + FROM sys.dm_exec_query_optimizer_info + WHERE counter = 'join hint' + AND occurrence > 1000; + END; + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 49 ) + BEGIN - SELECT @msg = N'Error inserting databases to msdbCentral.dbo.backup_worker, error number is ' + CONVERT(NVARCHAR(10), ERROR_NUMBER()) + ', error message is ' + ERROR_MESSAGE(), - @error_severity = ERROR_SEVERITY(), - @error_state = ERROR_STATE(); - - RAISERROR(@msg, @error_severity, @error_state) WITH NOWAIT; + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 49) WITH NOWAIT; - - WHILE @@TRANCOUNT > 0 - ROLLBACK; + INSERT INTO #BlitzResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT DISTINCT + 49 AS CheckID , + 200 AS Priority , + 'Informational' AS FindingsGroup , + 'Linked Server Configured' AS Finding , + 'https://www.brentozar.com/go/link' AS URL , + +CASE WHEN l.remote_name = 'sa' + THEN COALESCE(s.data_source, s.provider) + + ' is configured as a linked server. Check its security configuration as it is connecting with sa, because any user who queries it will get admin-level permissions.' + ELSE COALESCE(s.data_source, s.provider) + + ' is configured as a linked server. Check its security configuration to make sure it isn''t connecting with SA or some other bone-headed administrative login, because any user who queries it might get admin-level permissions.' + END AS Details + FROM sys.servers s + INNER JOIN sys.linked_logins l ON s.server_id = l.server_id + WHERE s.is_linked = 1; + END; + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 50 ) + BEGIN + IF @@VERSION NOT LIKE '%Microsoft SQL Server 2000%' + AND @@VERSION NOT LIKE '%Microsoft SQL Server 2005%' + BEGIN - END CATCH; - - - END; + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 50) WITH NOWAIT; - /* Check to make sure job is still enabled */ - IF NOT EXISTS ( - SELECT * - FROM msdb.dbo.sysjobs - WHERE name = 'sp_AllNightLog_PollForNewDatabases' - AND enabled = 1 - ) - BEGIN - RAISERROR('sp_AllNightLog_PollForNewDatabases job is disabled, so gracefully exiting. It feels graceful to me, anyway.', 0, 1) WITH NOWAIT; - RETURN; - END - - END;-- End Pollster loop - - ELSE - - BEGIN - - RAISERROR('msdbCentral.dbo.backup_worker does not exist, please create it.', 0, 1) WITH NOWAIT; - RETURN; - - END; - RETURN; + SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) + SELECT 50 AS CheckID , + 100 AS Priority , + ''Performance'' AS FindingsGroup , + ''Max Memory Set Too High'' AS Finding , + ''https://www.brentozar.com/go/max'' AS URL , + ''SQL Server max memory is set to '' + + CAST(c.value_in_use AS VARCHAR(20)) + + '' megabytes, but the server only has '' + + CAST(( CAST(m.total_physical_memory_kb AS BIGINT) / 1024 ) AS VARCHAR(20)) + + '' megabytes. SQL Server may drain the system dry of memory, and under certain conditions, this can cause Windows to swap to disk.'' AS Details + FROM sys.dm_os_sys_memory m + INNER JOIN sys.configurations c ON c.name = ''max server memory (MB)'' + WHERE CAST(m.total_physical_memory_kb AS BIGINT) < ( CAST(c.value_in_use AS BIGINT) * 1024 ) OPTION (RECOMPILE);'; + IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; + IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; -/* + EXECUTE(@StringToExecute); + END; + END; -End of Pollster + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 51 ) + BEGIN + IF @@VERSION NOT LIKE '%Microsoft SQL Server 2000%' + AND @@VERSION NOT LIKE '%Microsoft SQL Server 2005%' + BEGIN -*/ + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 51) WITH NOWAIT + SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) + SELECT 51 AS CheckID , + 1 AS Priority , + ''Performance'' AS FindingsGroup , + ''Memory Dangerously Low'' AS Finding , + ''https://www.brentozar.com/go/max'' AS URL , + ''The server has '' + CAST(( CAST(m.total_physical_memory_kb AS BIGINT) / 1024 ) AS VARCHAR(20)) + '' megabytes of physical memory, but only '' + CAST(( CAST(m.available_physical_memory_kb AS BIGINT) / 1024 ) AS VARCHAR(20)) + + '' megabytes are available. As the server runs out of memory, there is danger of swapping to disk, which will kill performance.'' AS Details + FROM sys.dm_os_sys_memory m + WHERE CAST(m.available_physical_memory_kb AS BIGINT) < 262144 OPTION (RECOMPILE);'; -/* + IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; + IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; -Begin DiskPollster + EXECUTE(@StringToExecute); + END; + END; -*/ + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 159 ) + BEGIN + IF @@VERSION NOT LIKE '%Microsoft SQL Server 2000%' + AND @@VERSION NOT LIKE '%Microsoft SQL Server 2005%' + BEGIN + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 159) WITH NOWAIT; -/* + SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) + SELECT DISTINCT 159 AS CheckID , + 1 AS Priority , + ''Performance'' AS FindingsGroup , + ''Memory Dangerously Low in NUMA Nodes'' AS Finding , + ''https://www.brentozar.com/go/max'' AS URL , + ''At least one NUMA node is reporting THREAD_RESOURCES_LOW in sys.dm_os_nodes and can no longer create threads.'' AS Details + FROM sys.dm_os_nodes m + WHERE node_state_desc LIKE ''%THREAD_RESOURCES_LOW%'' OPTION (RECOMPILE);'; -This section runs in a loop checking restore path for new databases added to the server, or broken restores + IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; + IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; -*/ + EXECUTE(@StringToExecute); + END; + END; -DiskPollster: + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 53 ) + BEGIN - IF @Debug = 1 RAISERROR('Beginning DiskPollster', 0, 1) WITH NOWAIT; - - IF OBJECT_ID('msdb.dbo.restore_configuration') IS NOT NULL - - BEGIN - - WHILE @PollDiskForNewDatabases = 1 - - BEGIN - - BEGIN TRY - - IF @Debug = 1 RAISERROR('Checking for new databases in: ', 0, 1) WITH NOWAIT; - IF @Debug = 1 RAISERROR(@restore_path_base, 0, 1) WITH NOWAIT; + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 53) WITH NOWAIT; - /* - - Look for new non-system databases -- there should probably be additional filters here for accessibility, etc. + DECLARE @AOFCI AS INT, @AOAG AS INT, @HAType AS VARCHAR(10), @errmsg AS VARCHAR(200) - */ - - /* - - This setups up the @cmd variable to check the restore path for new folders - - In our case, a new folder means a new database, because we assume a pristine path + SELECT @AOAG = CAST(SERVERPROPERTY('IsHadrEnabled') AS INT) + SELECT @AOFCI = CAST(SERVERPROPERTY('IsClustered') AS INT) - */ + IF (SELECT COUNT(DISTINCT join_state_desc) FROM sys.dm_hadr_availability_replica_cluster_states) = 2 + BEGIN --if count is 2 both JOINED_STANDALONE and JOINED_FCI is configured + SET @AOFCI = 1 + END - SET @cmd = N'DIR /b "' + @restore_path_base + N'"'; - - IF @Debug = 1 - BEGIN - PRINT @cmd; - END - - - DELETE @FileList; - INSERT INTO @FileList (BackupFile) - EXEC master.sys.xp_cmdshell @cmd; - - IF ( - SELECT COUNT(*) - FROM @FileList AS fl - WHERE fl.BackupFile = 'The system cannot find the path specified.' - OR fl.BackupFile = 'File Not Found' - ) = 1 + SELECT @HAType = + CASE + WHEN @AOFCI = 1 AND @AOAG =1 THEN 'FCIAG' + WHEN @AOFCI = 1 AND @AOAG =0 THEN 'FCI' + WHEN @AOFCI = 0 AND @AOAG =1 THEN 'AG' + ELSE 'STANDALONE' + END - BEGIN - - RAISERROR('No rows were returned for that database\path', 0, 1) WITH NOWAIT; + IF (@HAType IN ('FCIAG','FCI','AG')) + BEGIN - END; + INSERT INTO #BlitzResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) - IF ( - SELECT COUNT(*) - FROM @FileList AS fl - WHERE fl.BackupFile = 'Access is denied.' - ) = 1 + SELECT DISTINCT 53 AS CheckID , + 200 AS Priority , + 'Informational' AS FindingsGroup , + 'Cluster Node' AS Finding , + 'https://BrentOzar.com/go/node' AS URL , + 'This is a node in a cluster.' AS Details - BEGIN - - RAISERROR('Access is denied to %s', 16, 1, @restore_path_base) WITH NOWAIT; + IF @HAType = 'AG' + BEGIN + INSERT INTO #BlitzResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT DISTINCT 53 AS CheckID , + 200 AS Priority , + 'Informational' AS FindingsGroup , + 'Cluster Node Info' AS Finding , + 'https://BrentOzar.com/go/node' AS URL, + 'The cluster nodes are: ' + STUFF((SELECT ', ' + CASE ar.replica_server_name WHEN dhags.primary_replica THEN 'PRIMARY' + ELSE 'SECONDARY' + END + '=' + UPPER(ar.replica_server_name) + FROM sys.availability_groups AS ag + LEFT OUTER JOIN sys.availability_replicas AS ar ON ag.group_id = ar.group_id + LEFT OUTER JOIN sys.dm_hadr_availability_group_states as dhags ON ag.group_id = dhags.group_id + ORDER BY 1 + FOR XML PATH ('') + ), 1, 1, '') + ' - High Availability solution used is AlwaysOn Availability Group (AG)' As Details + END - END; + IF @HAType = 'FCI' + BEGIN + INSERT INTO #BlitzResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT DISTINCT 53 AS CheckID , + 200 AS Priority , + 'Informational' AS FindingsGroup , + 'Cluster Node Info' AS Finding , + 'https://BrentOzar.com/go/node' AS URL, + 'The cluster nodes are: ' + STUFF((SELECT ', ' + CASE is_current_owner + WHEN 1 THEN 'PRIMARY' + ELSE 'SECONDARY' + END + '=' + UPPER(NodeName) + FROM sys.dm_os_cluster_nodes + ORDER BY 1 + FOR XML PATH ('') + ), 1, 1, '') + ' - High Availability solution used is AlwaysOn Failover Cluster Instance (FCI)' As Details + END - IF ( - SELECT COUNT(*) - FROM @FileList AS fl - ) = 1 - AND ( - SELECT COUNT(*) - FROM @FileList AS fl - WHERE fl.BackupFile IS NULL - ) = 1 + IF @HAType = 'FCIAG' + BEGIN + INSERT INTO #BlitzResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT DISTINCT 53 AS CheckID , + 200 AS Priority , + 'Informational' AS FindingsGroup , + 'Cluster Node Info' AS Finding , + 'https://BrentOzar.com/go/node' AS URL, + 'The cluster nodes are: ' + STUFF((SELECT ', ' + HighAvailabilityRoleDesc + '=' + ServerName + FROM (SELECT UPPER(ar.replica_server_name) AS ServerName + ,CASE ar.replica_server_name WHEN dhags.primary_replica THEN 'PRIMARY' + ELSE 'SECONDARY' + END AS HighAvailabilityRoleDesc + FROM sys.availability_groups AS ag + LEFT OUTER JOIN sys.availability_replicas AS ar ON ag.group_id = ar.group_id + LEFT OUTER JOIN sys.dm_hadr_availability_group_states as dhags ON ag.group_id = dhags.group_id + WHERE UPPER(CAST(SERVERPROPERTY('ServerName') AS VARCHAR)) <> ar.replica_server_name + UNION ALL + SELECT UPPER(NodeName) AS ServerName + ,CASE is_current_owner + WHEN 1 THEN 'PRIMARY' + ELSE 'SECONDARY' + END AS HighAvailabilityRoleDesc + FROM sys.dm_os_cluster_nodes) AS Z + ORDER BY 1 + FOR XML PATH ('') + ), 1, 1, '') + ' - High Availability solution used is AlwaysOn FCI with AG (Failover Cluster Instance with Availability Group).'As Details + END + END - BEGIN - - RAISERROR('That directory appears to be empty', 0, 1) WITH NOWAIT; - - RETURN; - - END + END; - IF ( - SELECT COUNT(*) - FROM @FileList AS fl - WHERE fl.BackupFile = 'The user name or password is incorrect.' - ) = 1 + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 55 ) + BEGIN - BEGIN - - RAISERROR('Incorrect user name or password for %s', 16, 1, @restore_path_base) WITH NOWAIT; + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 55) WITH NOWAIT; - END; + IF @UsualDBOwner IS NULL + SET @UsualDBOwner = SUSER_SNAME(0x01); - INSERT msdb.dbo.restore_worker (database_name) - SELECT fl.BackupFile - FROM @FileList AS fl - WHERE fl.BackupFile IS NOT NULL - AND fl.BackupFile COLLATE DATABASE_DEFAULT NOT IN (SELECT name from sys.databases where database_id < 5) - AND NOT EXISTS - ( - SELECT 1 - FROM msdb.dbo.restore_worker rw - WHERE rw.database_name = fl.BackupFile - ) + INSERT INTO #BlitzResults + ( CheckID , + DatabaseName , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT 55 AS CheckID , + [name] AS DatabaseName , + 230 AS Priority , + 'Security' AS FindingsGroup , + 'Database Owner <> ' + @UsualDBOwner AS Finding , + 'https://www.brentozar.com/go/owndb' AS URL , + ( 'Database name: ' + [name] + ' ' + + 'Owner name: ' + SUSER_SNAME(owner_sid) ) AS Details + FROM sys.databases + WHERE (((SUSER_SNAME(owner_sid) <> SUSER_SNAME(0x01)) AND (name IN (N'master', N'model', N'msdb', N'tempdb'))) + OR ((SUSER_SNAME(owner_sid) <> @UsualDBOwner) AND (name NOT IN (N'master', N'model', N'msdb', N'tempdb'))) + ) + AND name NOT IN ( SELECT DISTINCT DatabaseName + FROM #SkipChecks + WHERE CheckID IS NULL OR CheckID = 55); + END; - IF @Debug = 1 RAISERROR('Checking for wayward databases', 0, 1) WITH NOWAIT; + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 213 ) + BEGIN - /* - - This section aims to find databases that have - * Had a log restore ever (the default for finish time is 9999-12-31, so anything with a more recent finish time has had a log restore) - * Not had a log restore start in the last 5 minutes (this could be trouble! or a really big log restore) - * Also checks msdb.dbo.backupset to make sure the database has a full backup associated with it (otherwise it's the first full, and we don't need to start adding log restores yet) + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 213) WITH NOWAIT; - */ - - IF EXISTS ( - - SELECT 1 - FROM msdb.dbo.restore_worker rw WITH (READPAST) - WHERE rw.last_log_restore_finish_time < '99991231' - AND rw.last_log_restore_start_time < DATEADD(SECOND, (@rto * -1), GETDATE()) - AND EXISTS ( - SELECT 1 - FROM msdb.dbo.restorehistory r - WHERE r.destination_database_name = rw.database_name - AND r.restore_type = 'D' - ) + INSERT INTO #BlitzResults + ( CheckID , + DatabaseName , + Priority , + FindingsGroup , + Finding , + URL , + Details ) - - BEGIN - - IF @Debug = 1 RAISERROR('Resetting databases with a log restore and no log restore in the last 5 minutes', 0, 1) WITH NOWAIT; + SELECT 213 AS CheckID , + [name] AS DatabaseName , + 230 AS Priority , + 'Security' AS FindingsGroup , + 'Database Owner is Unknown' AS Finding , + '' AS URL , + ( 'Database name: ' + [name] + ' ' + + 'Owner name: ' + ISNULL(SUSER_SNAME(owner_sid),'~~ UNKNOWN ~~') ) AS Details + FROM sys.databases + WHERE SUSER_SNAME(owner_sid) is NULL + AND name NOT IN ( SELECT DISTINCT DatabaseName + FROM #SkipChecks + WHERE CheckID IS NULL OR CheckID = 213); + END; - - UPDATE rw - SET rw.is_started = 0, - rw.is_completed = 1, - rw.last_log_restore_start_time = '19000101' - FROM msdb.dbo.restore_worker rw - WHERE rw.last_log_restore_finish_time < '99991231' - AND rw.last_log_restore_start_time < DATEADD(SECOND, (@rto * -1), GETDATE()) - AND EXISTS ( - SELECT 1 - FROM msdb.dbo.restorehistory r - WHERE r.destination_database_name = rw.database_name - AND r.restore_type = 'D' - ); + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 57 ) + BEGIN - - END; --End check for wayward databases + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 57) WITH NOWAIT; - /* - - Wait 1 minute between runs, we don't need to be checking this constantly - - */ + INSERT INTO #BlitzResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT 57 AS CheckID , + 230 AS Priority , + 'Security' AS FindingsGroup , + 'SQL Agent Job Runs at Startup' AS Finding , + 'https://www.brentozar.com/go/startup' AS URL , + ( 'Job [' + j.name + + '] runs automatically when SQL Server Agent starts up. Make sure you know exactly what this job is doing, because it could pose a security risk.' ) AS Details + FROM msdb.dbo.sysschedules sched + JOIN msdb.dbo.sysjobschedules jsched ON sched.schedule_id = jsched.schedule_id + JOIN msdb.dbo.sysjobs j ON jsched.job_id = j.job_id + WHERE sched.freq_type = 64 + AND sched.enabled = 1; + END; - /* Check to make sure job is still enabled */ - IF NOT EXISTS ( - SELECT * - FROM msdb.dbo.sysjobs - WHERE name = 'sp_AllNightLog_PollDiskForNewDatabases' - AND enabled = 1 - ) - BEGIN - RAISERROR('sp_AllNightLog_PollDiskForNewDatabases job is disabled, so gracefully exiting. It feels graceful to me, anyway.', 0, 1) WITH NOWAIT; - RETURN; - END - - IF @Debug = 1 RAISERROR('Waiting for 1 minute', 0, 1) WITH NOWAIT; - - WAITFOR DELAY '00:01:00.000'; + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 97 ) + BEGIN - END TRY - - BEGIN CATCH - - - SELECT @msg = N'Error inserting databases to msdb.dbo.restore_worker, error number is ' + CONVERT(NVARCHAR(10), ERROR_NUMBER()) + ', error message is ' + ERROR_MESSAGE(), - @error_severity = ERROR_SEVERITY(), - @error_state = ERROR_STATE(); - - RAISERROR(@msg, @error_severity, @error_state) WITH NOWAIT; - - - WHILE @@TRANCOUNT > 0 - ROLLBACK; + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 97) WITH NOWAIT; + INSERT INTO #BlitzResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT 97 AS CheckID , + 100 AS Priority , + 'Performance' AS FindingsGroup , + 'Unusual SQL Server Edition' AS Finding , + 'https://www.brentozar.com/go/workgroup' AS URL , + ( 'This server is using ' + + CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) + + ', which is capped at low amounts of CPU and memory.' ) AS Details + WHERE CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) NOT LIKE '%Standard%' + AND CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) NOT LIKE '%Enterprise%' + AND CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) NOT LIKE '%Data Center%' + AND CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) NOT LIKE '%Developer%' + AND CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) NOT LIKE '%Business Intelligence%'; + END; - END CATCH; - - - END; - - END;-- End Pollster loop - - ELSE - - BEGIN - - RAISERROR('msdb.dbo.restore_worker does not exist, please create it.', 0, 1) WITH NOWAIT; - RETURN; - - END; - RETURN; + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 154 ) + AND SERVERPROPERTY('EngineEdition') <> 8 + BEGIN + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 154) WITH NOWAIT; + INSERT INTO #BlitzResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT 154 AS CheckID , + 10 AS Priority , + 'Performance' AS FindingsGroup , + '32-bit SQL Server Installed' AS Finding , + 'https://www.brentozar.com/go/32bit' AS URL , + ( 'This server uses the 32-bit x86 binaries for SQL Server instead of the 64-bit x64 binaries. The amount of memory available for query workspace and execution plans is heavily limited.' ) AS Details + WHERE CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) NOT LIKE '%64%'; + END; -/* + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 62 ) + BEGIN -Begin LogShamer + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 62) WITH NOWAIT; -*/ + INSERT INTO #BlitzResults + ( CheckID , + DatabaseName , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT 62 AS CheckID , + [name] AS DatabaseName , + 200 AS Priority , + 'Performance' AS FindingsGroup , + 'Old Compatibility Level' AS Finding , + 'https://www.brentozar.com/go/compatlevel' AS URL , + ( 'Database ' + [name] + + ' is compatibility level ' + + CAST(compatibility_level AS VARCHAR(20)) + + ', which may cause unwanted results when trying to run queries that have newer T-SQL features.' ) AS Details + FROM sys.databases + WHERE name NOT IN ( SELECT DISTINCT + DatabaseName + FROM #SkipChecks + WHERE CheckID IS NULL OR CheckID = 62) + AND compatibility_level <= 90; + END; -LogShamer: + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 94 ) + BEGIN - IF @Debug = 1 RAISERROR('Beginning Backups', 0, 1) WITH NOWAIT; - - IF OBJECT_ID('msdbCentral.dbo.backup_worker') IS NOT NULL - - BEGIN - - /* - - Make sure configuration table exists... - - */ - - IF OBJECT_ID('msdbCentral.dbo.backup_configuration') IS NOT NULL - - BEGIN - - IF @Debug = 1 RAISERROR('Checking variables', 0, 1) WITH NOWAIT; - - /* - - These settings are configurable - - I haven't found a good way to find the default backup path that doesn't involve xp_regread - - */ - - SELECT @rpo = CONVERT(INT, configuration_setting) - FROM msdbCentral.dbo.backup_configuration c - WHERE configuration_name = N'log backup frequency' - AND database_name = N'all'; - - - IF @rpo IS NULL - BEGIN - RAISERROR('@rpo cannot be NULL. Please check the msdbCentral.dbo.backup_configuration table', 0, 1) WITH NOWAIT; - RETURN; - END; - - - SELECT @backup_path = CONVERT(NVARCHAR(512), configuration_setting) - FROM msdbCentral.dbo.backup_configuration c - WHERE configuration_name = N'log backup path' - AND database_name = N'all'; - - - IF @backup_path IS NULL - BEGIN - RAISERROR('@backup_path cannot be NULL. Please check the msdbCentral.dbo.backup_configuration table', 0, 1) WITH NOWAIT; - RETURN; - END; + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 94) WITH NOWAIT; - SELECT @changebackuptype = configuration_setting - FROM msdbCentral.dbo.backup_configuration c - WHERE configuration_name = N'change backup type' - AND database_name = N'all'; + INSERT INTO #BlitzResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT 94 AS CheckID , + 200 AS [Priority] , + 'Monitoring' AS FindingsGroup , + 'Agent Jobs Without Failure Emails' AS Finding , + 'https://www.brentozar.com/go/alerts' AS URL , + 'The job ' + [name] + + ' has not been set up to notify an operator if it fails.' AS Details + FROM msdb.[dbo].[sysjobs] j + WHERE j.enabled = 1 + AND j.notify_email_operator_id = 0 + AND j.notify_netsend_operator_id = 0 + AND j.notify_page_operator_id = 0 + AND j.category_id <> 100; /* Exclude SSRS category */ + END; - SELECT @encrypt = configuration_setting - FROM msdbCentral.dbo.backup_configuration c - WHERE configuration_name = N'encrypt' - AND database_name = N'all'; + IF EXISTS ( SELECT 1 + FROM sys.configurations + WHERE name = 'remote admin connections' + AND value_in_use = 0 ) + AND NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 100 ) + BEGIN - SELECT @encryptionalgorithm = configuration_setting - FROM msdbCentral.dbo.backup_configuration c - WHERE configuration_name = N'encryptionalgorithm' - AND database_name = N'all'; + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 100) WITH NOWAIT; - SELECT @servercertificate = configuration_setting - FROM msdbCentral.dbo.backup_configuration c - WHERE configuration_name = N'servercertificate' - AND database_name = N'all'; + INSERT INTO #BlitzResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT 100 AS CheckID , + 170 AS Priority , + 'Reliability' AS FindingGroup , + 'Remote DAC Disabled' AS Finding , + 'https://www.brentozar.com/go/dac' AS URL , + 'Remote access to the Dedicated Admin Connection (DAC) is not enabled. The DAC can make remote troubleshooting much easier when SQL Server is unresponsive.'; + END; - IF @encrypt = N'Y' AND (@encryptionalgorithm IS NULL OR @servercertificate IS NULL) - BEGIN - RAISERROR('If encryption is Y, then both the encryptionalgorithm and servercertificate must be set. Please check the msdbCentral.dbo.backup_configuration table', 0, 1) WITH NOWAIT; - RETURN; - END; - - END; - - ELSE - - BEGIN - - RAISERROR('msdbCentral.dbo.backup_configuration does not exist, please run setup script', 0, 1) WITH NOWAIT; - RETURN; - - END; - - - WHILE @Backup = 1 + IF EXISTS ( SELECT * + FROM sys.dm_os_schedulers + WHERE is_online = 0 ) + AND NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 101 ) + BEGIN - /* - - Start loop to take log backups + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 101) WITH NOWAIT; - */ + INSERT INTO #BlitzResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT 101 AS CheckID , + 50 AS Priority , + 'Performance' AS FindingGroup , + 'CPU Schedulers Offline' AS Finding , + 'https://www.brentozar.com/go/schedulers' AS URL , + 'Some CPU cores are not accessible to SQL Server due to affinity masking or licensing problems.'; + END; - - BEGIN - - BEGIN TRY - - BEGIN TRAN; - - IF @Debug = 1 RAISERROR('Begin tran to grab a database to back up', 0, 1) WITH NOWAIT; + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 110 ) + AND EXISTS (SELECT * FROM master.sys.all_objects WHERE name = 'dm_os_memory_nodes') + BEGIN + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 110) WITH NOWAIT; - /* - - This grabs a database for a worker to work on + SET @StringToExecute = 'IF EXISTS (SELECT * + FROM sys.dm_os_nodes n + INNER JOIN sys.dm_os_memory_nodes m ON n.memory_node_id = m.memory_node_id + WHERE n.node_state_desc = ''OFFLINE'') + INSERT INTO #BlitzResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT 110 AS CheckID , + 50 AS Priority , + ''Performance'' AS FindingGroup , + ''Memory Nodes Offline'' AS Finding , + ''https://www.brentozar.com/go/schedulers'' AS URL , + ''Due to affinity masking or licensing problems, some of the memory may not be available.'' OPTION (RECOMPILE)'; + + IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; + IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; + + EXECUTE(@StringToExecute); + END; - The locking hints hope to provide some isolation when 10+ workers are in action - - */ - - - SELECT TOP (1) - @database = bw.database_name - FROM msdbCentral.dbo.backup_worker bw WITH (UPDLOCK, HOLDLOCK, ROWLOCK) - WHERE - ( /*This section works on databases already part of the backup cycle*/ - bw.is_started = 0 - AND bw.is_completed = 1 - AND bw.last_log_backup_start_time < DATEADD(SECOND, (@rpo * -1), GETDATE()) - AND (bw.error_number IS NULL OR bw.error_number > 0) /* negative numbers indicate human attention required */ - AND bw.ignore_database = 0 - ) - OR - ( /*This section picks up newly added databases by Pollster*/ - bw.is_started = 0 - AND bw.is_completed = 0 - AND bw.last_log_backup_start_time = '1900-01-01 00:00:00.000' - AND bw.last_log_backup_finish_time = '9999-12-31 00:00:00.000' - AND (bw.error_number IS NULL OR bw.error_number > 0) /* negative numbers indicate human attention required */ - AND bw.ignore_database = 0 - ) - ORDER BY bw.last_log_backup_start_time ASC, bw.last_log_backup_finish_time ASC, bw.database_name ASC; - - - IF @database IS NOT NULL - BEGIN - SET @msg = N'Updating backup_worker for database ' + ISNULL(@database, 'UH OH NULL @database'); - IF @Debug = 1 RAISERROR(@msg, 0, 1) WITH NOWAIT; - - /* - - Update the worker table so other workers know a database is being backed up - - */ + IF EXISTS ( SELECT * + FROM sys.databases + WHERE state > 1 ) + AND NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 102 ) + BEGIN - - UPDATE bw - SET bw.is_started = 1, - bw.is_completed = 0, - bw.last_log_backup_start_time = GETDATE() - FROM msdbCentral.dbo.backup_worker bw - WHERE bw.database_name = @database; - END - - COMMIT; - - END TRY - - BEGIN CATCH - - /* - - Do I need to build retry logic in here? Try to catch deadlocks? I don't know yet! - - */ + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 102) WITH NOWAIT; - SELECT @msg = N'Error securing a database to backup, error number is ' + CONVERT(NVARCHAR(10), ERROR_NUMBER()) + ', error message is ' + ERROR_MESSAGE(), - @error_severity = ERROR_SEVERITY(), - @error_state = ERROR_STATE(); - RAISERROR(@msg, @error_severity, @error_state) WITH NOWAIT; + INSERT INTO #BlitzResults + ( CheckID , + DatabaseName , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT 102 AS CheckID , + [name] , + 20 AS Priority , + 'Reliability' AS FindingGroup , + 'Unusual Database State: ' + [state_desc] AS Finding , + 'https://www.brentozar.com/go/repair' AS URL , + 'This database may not be online.' + FROM sys.databases + WHERE state > 1; + END; - SET @database = NULL; - - WHILE @@TRANCOUNT > 0 - ROLLBACK; - - END CATCH; + IF EXISTS ( SELECT * + FROM master.sys.extended_procedures ) + AND NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 105 ) + BEGIN + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 105) WITH NOWAIT; - /* If we don't find a database to work on, wait for a few seconds */ - IF @database IS NULL + INSERT INTO #BlitzResults + ( CheckID , + DatabaseName , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT 105 AS CheckID , + 'master' , + 200 AS Priority , + 'Reliability' AS FindingGroup , + 'Extended Stored Procedures in Master' AS Finding , + 'https://www.brentozar.com/go/clr' AS URL , + 'The [' + name + + '] extended stored procedure is in the master database. CLR may be in use, and the master database now needs to be part of your backup/recovery planning.' + FROM master.sys.extended_procedures; + END; + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 107 ) BEGIN - IF @Debug = 1 RAISERROR('No databases to back up right now, starting 3 second throttle', 0, 1) WITH NOWAIT; - WAITFOR DELAY '00:00:03.000'; - - /* Check to make sure job is still enabled */ - IF NOT EXISTS ( - SELECT * - FROM msdb.dbo.sysjobs - WHERE name LIKE 'sp_AllNightLog_Backup%' - AND enabled = 1 - ) - BEGIN - RAISERROR('sp_AllNightLog_Backup jobs are disabled, so gracefully exiting. It feels graceful to me, anyway.', 0, 1) WITH NOWAIT; - RETURN; - END + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 107) WITH NOWAIT; + INSERT INTO #BlitzResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT 107 AS CheckID , + 50 AS Priority , + 'Performance' AS FindingGroup , + 'Poison Wait Detected: ' + wait_type AS Finding , + 'https://www.brentozar.com/go/poison/#' + wait_type AS URL , + CONVERT(VARCHAR(10), (SUM([wait_time_ms]) / 1000) / 86400) + ':' + CONVERT(VARCHAR(20), DATEADD(s, (SUM([wait_time_ms]) / 1000), 0), 108) + ' of this wait have been recorded. This wait often indicates killer performance problems.' + FROM sys.[dm_os_wait_stats] + WHERE wait_type IN('IO_QUEUE_LIMIT', 'IO_RETRY', 'LOG_RATE_GOVERNOR', 'POOL_LOG_RATE_GOVERNOR', 'PREEMPTIVE_DEBUG', 'RESMGR_THROTTLED', 'RESOURCE_SEMAPHORE', 'RESOURCE_SEMAPHORE_QUERY_COMPILE','SE_REPL_CATCHUP_THROTTLE','SE_REPL_COMMIT_ACK','SE_REPL_COMMIT_TURN','SE_REPL_ROLLBACK_ACK','SE_REPL_SLOW_SECONDARY_THROTTLE','THREADPOOL') + GROUP BY wait_type + HAVING SUM([wait_time_ms]) > (SELECT 5000 * datediff(HH,create_date,CURRENT_TIMESTAMP) AS hours_since_startup FROM sys.databases WHERE name='tempdb') + AND SUM([wait_time_ms]) > 60000; + END; - END - - - BEGIN TRY - + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 121 ) BEGIN - - IF @database IS NOT NULL - - /* - - Make sure we have a database to work on -- I should make this more robust so we do something if it is NULL, maybe - - */ - - BEGIN - - SET @msg = N'Taking backup of ' + ISNULL(@database, 'UH OH NULL @database'); - IF @Debug = 1 RAISERROR(@msg, 0, 1) WITH NOWAIT; + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 121) WITH NOWAIT; - /* - - Call Ola's proc to backup the database - - */ - - IF @encrypt = 'Y' - EXEC dbo.DatabaseBackup @Databases = @database, --Database we're working on - @BackupType = 'LOG', --Going for the LOGs - @Directory = @backup_path, --The path we need to back up to - @Verify = 'N', --We don't want to verify these, it eats into job time - @ChangeBackupType = @changebackuptype, --If we need to switch to a FULL because one hasn't been taken - @CheckSum = 'Y', --These are a good idea - @Compress = 'Y', --This is usually a good idea - @LogToTable = 'Y', --We should do this for posterity - @Encrypt = @encrypt, - @EncryptionAlgorithm = @encryptionalgorithm, - @ServerCertificate = @servercertificate; - - ELSE - EXEC dbo.DatabaseBackup @Databases = @database, --Database we're working on - @BackupType = 'LOG', --Going for the LOGs - @Directory = @backup_path, --The path we need to back up to - @Verify = 'N', --We don't want to verify these, it eats into job time - @ChangeBackupType = @changebackuptype, --If we need to switch to a FULL because one hasn't been taken - @CheckSum = 'Y', --These are a good idea - @Compress = 'Y', --This is usually a good idea - @LogToTable = 'Y'; --We should do this for posterity - - - /* - - Catch any erroneous zones - - */ - - SELECT @error_number = ERROR_NUMBER(), - @error_severity = ERROR_SEVERITY(), - @error_state = ERROR_STATE(); - - END; --End call to dbo.DatabaseBackup - - END; --End successful check of @database (not NULL) - - END TRY - - BEGIN CATCH - - IF @error_number IS NOT NULL + INSERT INTO #BlitzResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT 121 AS CheckID , + 50 AS Priority , + 'Performance' AS FindingGroup , + 'Poison Wait Detected: Serializable Locking' AS Finding , + 'https://www.brentozar.com/go/serializable' AS URL , + CONVERT(VARCHAR(10), (SUM([wait_time_ms]) / 1000) / 86400) + ':' + CONVERT(VARCHAR(20), DATEADD(s, (SUM([wait_time_ms]) / 1000), 0), 108) + ' of LCK_M_R% waits have been recorded. This wait often indicates killer performance problems.' + FROM sys.[dm_os_wait_stats] + WHERE wait_type IN ('LCK_M_RS_S', 'LCK_M_RS_U', 'LCK_M_RIn_NL','LCK_M_RIn_S', 'LCK_M_RIn_U','LCK_M_RIn_X', 'LCK_M_RX_S', 'LCK_M_RX_U','LCK_M_RX_X') + HAVING SUM([wait_time_ms]) > (SELECT 5000 * datediff(HH,create_date,CURRENT_TIMESTAMP) AS hours_since_startup FROM sys.databases WHERE name='tempdb') + AND SUM([wait_time_ms]) > 60000; + END; - /* - - If the ERROR() function returns a number, update the table with it and the last error date. - Also update the last start time to 1900-01-01 so it gets picked back up immediately -- the query to find a log backup to take sorts by start time + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 111 ) + BEGIN - */ - - BEGIN - - SET @msg = N'Error number is ' + CONVERT(NVARCHAR(10), ERROR_NUMBER()); - RAISERROR(@msg, @error_severity, @error_state) WITH NOWAIT; - - SET @msg = N'Updating backup_worker for database ' + ISNULL(@database, 'UH OH NULL @database') + ' for unsuccessful backup'; - RAISERROR(@msg, 0, 1) WITH NOWAIT; - - - UPDATE bw - SET bw.is_started = 0, - bw.is_completed = 1, - bw.last_log_backup_start_time = '19000101', - bw.error_number = @error_number, - bw.last_error_date = GETDATE() - FROM msdbCentral.dbo.backup_worker bw - WHERE bw.database_name = @database; + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 111) WITH NOWAIT; + INSERT INTO #BlitzResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + DatabaseName , + URL , + Details + ) + SELECT 111 AS CheckID , + 50 AS Priority , + 'Reliability' AS FindingGroup , + 'Possibly Broken Log Shipping' AS Finding , + d.[name] , + 'https://www.brentozar.com/go/shipping' AS URL , + d.[name] + ' is in a restoring state, but has not had a backup applied in the last two days. This is a possible indication of a broken transaction log shipping setup.' + FROM [master].sys.databases d + INNER JOIN [master].sys.database_mirroring dm ON d.database_id = dm.database_id + AND dm.mirroring_role IS NULL + WHERE ( d.[state] = 1 + OR (d.[state] = 0 AND d.[is_in_standby] = 1) ) + AND NOT EXISTS(SELECT * FROM msdb.dbo.restorehistory rh + INNER JOIN msdb.dbo.backupset bs ON rh.backup_set_id = bs.backup_set_id + WHERE d.[name] COLLATE SQL_Latin1_General_CP1_CI_AS = rh.destination_database_name COLLATE SQL_Latin1_General_CP1_CI_AS + AND rh.restore_date >= DATEADD(dd, -2, GETDATE())); - /* - - Set @database back to NULL to avoid variable assignment weirdness - - */ + END; - SET @database = NULL; + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 112 ) + AND EXISTS (SELECT * FROM master.sys.all_objects WHERE name = 'change_tracking_databases') + BEGIN - - /* - - Wait around for a second so we're not just spinning wheels -- this only runs if the BEGIN CATCH is triggered by an error + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 112) WITH NOWAIT; - */ + SET @StringToExecute = 'INSERT INTO #BlitzResults + (CheckID, + Priority, + FindingsGroup, + Finding, + DatabaseName, + URL, + Details) + SELECT 112 AS CheckID, + 100 AS Priority, + ''Performance'' AS FindingsGroup, + ''Change Tracking Enabled'' AS Finding, + d.[name], + ''https://www.brentozar.com/go/tracking'' AS URL, + ( d.[name] + '' has change tracking enabled. This is not a default setting, and it has some performance overhead. It keeps track of changes to rows in tables that have change tracking turned on.'' ) AS Details FROM sys.change_tracking_databases AS ctd INNER JOIN sys.databases AS d ON ctd.database_id = d.database_id OPTION (RECOMPILE)'; - IF @Debug = 1 RAISERROR('Starting 1 second throttle', 0, 1) WITH NOWAIT; + IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; + IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; - WAITFOR DELAY '00:00:01.000'; - - END; -- End update of unsuccessful backup - - END CATCH; - - IF @database IS NOT NULL AND @error_number IS NULL - - /* - - If no error, update everything normally - - */ + EXECUTE(@StringToExecute); + END; - + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 116 ) + AND EXISTS (SELECT * FROM msdb.sys.all_columns WHERE name = 'compressed_backup_size') BEGIN - - IF @Debug = 1 RAISERROR('Error number IS NULL', 0, 1) WITH NOWAIT; - - SET @msg = N'Updating backup_worker for database ' + ISNULL(@database, 'UH OH NULL @database') + ' for successful backup'; - IF @Debug = 1 RAISERROR(@msg, 0, 1) WITH NOWAIT; - - - UPDATE bw - SET bw.is_started = 0, - bw.is_completed = 1, - bw.last_log_backup_finish_time = GETDATE() - FROM msdbCentral.dbo.backup_worker bw - WHERE bw.database_name = @database; - - /* - - Set @database back to NULL to avoid variable assignment weirdness - - */ - - SET @database = NULL; - - - END; -- End update for successful backup + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 116) WITH NOWAIT + SET @StringToExecute = 'INSERT INTO #BlitzResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT 116 AS CheckID , + 200 AS Priority , + ''Informational'' AS FindingGroup , + ''Backup Compression Default Off'' AS Finding , + ''https://www.brentozar.com/go/backup'' AS URL , + ''Uncompressed full backups have happened recently, and backup compression is not turned on at the server level. Backup compression is included with SQL Server 2008R2 & newer, even in Standard Edition. We recommend turning backup compression on by default so that ad-hoc backups will get compressed.'' + FROM sys.configurations + WHERE configuration_id = 1579 AND CAST(value_in_use AS INT) = 0 + AND EXISTS (SELECT * FROM msdb.dbo.backupset WHERE backup_size = compressed_backup_size AND type = ''D'' AND backup_finish_date >= DATEADD(DD, -14, GETDATE())) OPTION (RECOMPILE);'; - END; -- End @Backup WHILE loop - - - END; -- End successful check for backup_worker and subsequent code - - - ELSE - - BEGIN - - RAISERROR('msdbCentral.dbo.backup_worker does not exist, please run setup script', 0, 1) WITH NOWAIT; - - RETURN; - - END; -RETURN; - - -/* - -Begin Restoregasm_Addict section - -*/ + IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; + IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; + + EXECUTE(@StringToExecute); + END; -Restoregasm_Addict: + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 117 ) + AND EXISTS (SELECT * FROM master.sys.all_objects WHERE name = 'dm_exec_query_resource_semaphores') + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 117) WITH NOWAIT; + + SET @StringToExecute = 'IF 0 < (SELECT SUM([forced_grant_count]) FROM sys.dm_exec_query_resource_semaphores WHERE [forced_grant_count] IS NOT NULL) + INSERT INTO #BlitzResults + (CheckID, + Priority, + FindingsGroup, + Finding, + URL, + Details) + SELECT 117 AS CheckID, + 100 AS Priority, + ''Performance'' AS FindingsGroup, + ''Memory Pressure Affecting Queries'' AS Finding, + ''https://www.brentozar.com/go/grants'' AS URL, + CAST(SUM(forced_grant_count) AS NVARCHAR(100)) + '' forced grants reported in the DMV sys.dm_exec_query_resource_semaphores, indicating memory pressure has affected query runtimes.'' + FROM sys.dm_exec_query_resource_semaphores WHERE [forced_grant_count] IS NOT NULL OPTION (RECOMPILE);'; + + IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; + IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; + + EXECUTE(@StringToExecute); + END; -IF @Restore = 1 - IF @Debug = 1 RAISERROR('Beginning Restores', 0, 1) WITH NOWAIT; - - /* Check to make sure backup jobs aren't enabled */ - IF EXISTS ( - SELECT * - FROM msdb.dbo.sysjobs - WHERE name LIKE 'sp_AllNightLog_Backup%' - AND enabled = 1 - ) - BEGIN - RAISERROR('sp_AllNightLog_Backup jobs are enabled, so gracefully exiting. You do not want to accidentally do restores over top of the databases you are backing up.', 0, 1) WITH NOWAIT; - RETURN; - END + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 124 ) + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 124) WITH NOWAIT; + + INSERT INTO #BlitzResults + (CheckID, + Priority, + FindingsGroup, + Finding, + URL, + Details) + SELECT 124, + 150, + 'Performance', + 'Deadlocks Happening Daily', + 'https://www.brentozar.com/go/deadlocks', + CAST(CAST(p.cntr_value / @DaysUptime AS BIGINT) AS NVARCHAR(100)) + ' average deadlocks per day. To find them, run sp_BlitzLock.' AS Details + FROM sys.dm_os_performance_counters p + INNER JOIN sys.databases d ON d.name = 'tempdb' + WHERE RTRIM(p.counter_name) = 'Number of Deadlocks/sec' + AND RTRIM(p.instance_name) = '_Total' + AND p.cntr_value > 0 + AND (1.0 * p.cntr_value / NULLIF(datediff(DD,create_date,CURRENT_TIMESTAMP),0)) > 10; + END; - IF OBJECT_ID('msdb.dbo.restore_worker') IS NOT NULL - - BEGIN - - /* - - Make sure configuration table exists... - - */ - - IF OBJECT_ID('msdb.dbo.restore_configuration') IS NOT NULL - - BEGIN - - IF @Debug = 1 RAISERROR('Checking variables', 0, 1) WITH NOWAIT; - - /* - - These settings are configurable - - */ - - SELECT @rto = CONVERT(INT, configuration_setting) - FROM msdb.dbo.restore_configuration c - WHERE configuration_name = N'log restore frequency'; - + IF DATEADD(mi, -15, GETDATE()) < (SELECT TOP 1 creation_time FROM sys.dm_exec_query_stats ORDER BY creation_time) + AND NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 125 ) + BEGIN - IF @rto IS NULL - BEGIN - RAISERROR('@rto cannot be NULL. Please check the msdb.dbo.restore_configuration table', 0, 1) WITH NOWAIT; - RETURN; - END; - - - END; - - ELSE - - BEGIN - - RAISERROR('msdb.dbo.restore_configuration does not exist, please run setup script', 0, 1) WITH NOWAIT; - - RETURN; - - END; - - - WHILE @Restore = 1 - - /* - - Start loop to restore log backups - - */ - - - BEGIN - - BEGIN TRY + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 125) WITH NOWAIT; - BEGIN TRAN; - - IF @Debug = 1 RAISERROR('Begin tran to grab a database to restore', 0, 1) WITH NOWAIT; - - - /* + DECLARE @user_perm_sql NVARCHAR(MAX) = N''; + DECLARE @user_perm_gb_out DECIMAL(38,2); - This grabs a database for a worker to work on - - The locking hints hope to provide some isolation when 10+ workers are in action + IF @ProductVersionMajor >= 11 - */ - - - SELECT TOP (1) - @database = rw.database_name, - @only_logs_after = REPLACE(REPLACE(REPLACE(CONVERT(NVARCHAR(30), rw.last_log_restore_start_time, 120), ' ', ''), '-', ''), ':', ''), - @restore_full = CASE WHEN rw.is_started = 0 - AND rw.is_completed = 0 - AND rw.last_log_restore_start_time = '1900-01-01 00:00:00.000' - AND rw.last_log_restore_finish_time = '9999-12-31 00:00:00.000' - THEN 1 - ELSE 0 - END - FROM msdb.dbo.restore_worker rw WITH (UPDLOCK, HOLDLOCK, ROWLOCK) - WHERE ( - ( /*This section works on databases already part of the backup cycle*/ - rw.is_started = 0 - AND rw.is_completed = 1 - AND rw.last_log_restore_start_time < DATEADD(SECOND, (@rto * -1), GETDATE()) - AND (rw.error_number IS NULL OR rw.error_number > 0) /* negative numbers indicate human attention required */ - ) - OR - ( /*This section picks up newly added databases by DiskPollster*/ - rw.is_started = 0 - AND rw.is_completed = 0 - AND rw.last_log_restore_start_time = '1900-01-01 00:00:00.000' - AND rw.last_log_restore_finish_time = '9999-12-31 00:00:00.000' - AND (rw.error_number IS NULL OR rw.error_number > 0) /* negative numbers indicate human attention required */ - ) - ) - AND rw.ignore_database = 0 - AND NOT EXISTS ( - /* Validation check to ensure the database either doesn't exist or is in a restoring/standby state */ - SELECT 1 - FROM sys.databases d - WHERE d.name = rw.database_name - AND state <> 1 /* Restoring */ - AND NOT (state=0 AND d.is_in_standby=1) /* standby mode */ - ) - ORDER BY rw.last_log_restore_start_time ASC, rw.last_log_restore_finish_time ASC, rw.database_name ASC; - + BEGIN - IF @database IS NOT NULL - BEGIN - SET @msg = N'Updating restore_worker for database ' + ISNULL(@database, 'UH OH NULL @database'); - IF @Debug = 1 RAISERROR(@msg, 0, 1) WITH NOWAIT; + SET @user_perm_sql += N' + SELECT @user_perm_gb = CASE WHEN (pages_kb / 1024. / 1024.) >= 2. + THEN CONVERT(DECIMAL(38, 2), (pages_kb / 1024. / 1024.)) + ELSE NULL + END + FROM sys.dm_os_memory_clerks + WHERE type = ''USERSTORE_TOKENPERM'' + AND name = ''TokenAndPermUserStore'' + '; - /* + END - Update the worker table so other workers know a database is being restored + IF @ProductVersionMajor < 11 - */ - + BEGIN + SET @user_perm_sql += N' + SELECT @user_perm_gb = CASE WHEN ((single_pages_kb + multi_pages_kb) / 1024.0 / 1024.) >= 2. + THEN CONVERT(DECIMAL(38, 2), ((single_pages_kb + multi_pages_kb) / 1024.0 / 1024.)) + ELSE NULL + END + FROM sys.dm_os_memory_clerks + WHERE type = ''USERSTORE_TOKENPERM'' + AND name = ''TokenAndPermUserStore'' + '; - UPDATE rw - SET rw.is_started = 1, - rw.is_completed = 0, - rw.last_log_restore_start_time = GETDATE() - FROM msdb.dbo.restore_worker rw - WHERE rw.database_name = @database; - END - - COMMIT; - - END TRY - - BEGIN CATCH - - /* - - Do I need to build retry logic in here? Try to catch deadlocks? I don't know yet! - - */ - - SELECT @msg = N'Error securing a database to restore, error number is ' + CONVERT(NVARCHAR(10), ERROR_NUMBER()) + ', error message is ' + ERROR_MESSAGE(), - @error_severity = ERROR_SEVERITY(), - @error_state = ERROR_STATE(); - RAISERROR(@msg, @error_severity, @error_state) WITH NOWAIT; - - SET @database = NULL; - - WHILE @@TRANCOUNT > 0 - ROLLBACK; - - END CATCH; - + END - /* If we don't find a database to work on, wait for a few seconds */ - IF @database IS NULL + EXEC sys.sp_executesql @user_perm_sql, + N'@user_perm_gb DECIMAL(38,2) OUTPUT', + @user_perm_gb = @user_perm_gb_out OUTPUT - BEGIN - IF @Debug = 1 RAISERROR('No databases to restore up right now, starting 3 second throttle', 0, 1) WITH NOWAIT; - WAITFOR DELAY '00:00:03.000'; - - /* Check to make sure backup jobs aren't enabled */ - IF EXISTS ( - SELECT * - FROM msdb.dbo.sysjobs - WHERE name LIKE 'sp_AllNightLog_Backup%' - AND enabled = 1 - ) - BEGIN - RAISERROR('sp_AllNightLog_Backup jobs are enabled, so gracefully exiting. You do not want to accidentally do restores over top of the databases you are backing up.', 0, 1) WITH NOWAIT; - RETURN; - END - - /* Check to make sure job is still enabled */ - IF NOT EXISTS ( - SELECT * - FROM msdb.dbo.sysjobs - WHERE name LIKE 'sp_AllNightLog_Restore%' - AND enabled = 1 - ) - BEGIN - RAISERROR('sp_AllNightLog_Restore jobs are disabled, so gracefully exiting. It feels graceful to me, anyway.', 0, 1) WITH NOWAIT; - RETURN; - END + INSERT INTO #BlitzResults + (CheckID, + Priority, + FindingsGroup, + Finding, + URL, + Details) + SELECT TOP 1 125, 10, 'Performance', 'Plan Cache Erased Recently', 'https://www.brentozar.com/askbrent/plan-cache-erased-recently/', + 'The oldest query in the plan cache was created at ' + CAST(creation_time AS NVARCHAR(50)) + + CASE WHEN @user_perm_gb_out IS NULL + THEN '. Someone ran DBCC FREEPROCCACHE, restarted SQL Server, or it is under horrific memory pressure.' + ELSE '. You also have ' + CONVERT(NVARCHAR(20), @user_perm_gb_out) + ' GB of USERSTORE_TOKENPERM, which could indicate unusual memory consumption.' + END + FROM sys.dm_exec_query_stats WITH (NOLOCK) + ORDER BY creation_time; + END; - END - - - BEGIN TRY - + IF EXISTS (SELECT * FROM sys.configurations WHERE name = 'priority boost' AND (value = 1 OR value_in_use = 1)) + AND NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 126 ) BEGIN - - IF @database IS NOT NULL - - /* - Make sure we have a database to work on -- I should make this more robust so we do something if it is NULL, maybe + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 126) WITH NOWAIT; - */ - - - BEGIN - - SET @msg = CASE WHEN @restore_full = 0 - THEN N'Restoring logs for ' - ELSE N'Restoring full backup for ' - END - + ISNULL(@database, 'UH OH NULL @database'); - - IF @Debug = 1 RAISERROR(@msg, 0, 1) WITH NOWAIT; - - /* - - Call sp_DatabaseRestore to backup the database - - */ - - SET @restore_path_full = @restore_path_base + N'\' + @database + N'\' + N'FULL\' - - SET @msg = N'Path for FULL backups for ' + @database + N' is ' + @restore_path_full - IF @Debug = 1 RAISERROR(@msg, 0, 1) WITH NOWAIT; - - SET @restore_path_log = @restore_path_base + N'\' + @database + N'\' + N'LOG\' - - SET @msg = N'Path for LOG backups for ' + @database + N' is ' + @restore_path_log - IF @Debug = 1 RAISERROR(@msg, 0, 1) WITH NOWAIT; - - IF @restore_full = 0 - - BEGIN - - IF @Debug = 1 RAISERROR('Starting Log only restores', 0, 1) WITH NOWAIT; - - EXEC dbo.sp_DatabaseRestore @Database = @database, - @BackupPathFull = @restore_path_full, - @BackupPathLog = @restore_path_log, - @ContinueLogs = 1, - @RunRecovery = 0, - @OnlyLogsAfter = @only_logs_after, - @MoveFiles = @restore_move_files, - @Debug = @Debug - - END - - IF @restore_full = 1 - - BEGIN - - IF @Debug = 1 RAISERROR('Starting first Full restore from: ', 0, 1) WITH NOWAIT; - IF @Debug = 1 RAISERROR(@restore_path_full, 0, 1) WITH NOWAIT; - - EXEC dbo.sp_DatabaseRestore @Database = @database, - @BackupPathFull = @restore_path_full, - @BackupPathLog = @restore_path_log, - @ContinueLogs = 0, - @RunRecovery = 0, - @MoveFiles = @restore_move_files, - @Debug = @Debug - - END - - - - /* - - Catch any erroneous zones - - */ - - SELECT @error_number = ERROR_NUMBER(), - @error_severity = ERROR_SEVERITY(), - @error_state = ERROR_STATE(); - - END; --End call to dbo.sp_DatabaseRestore - - END; --End successful check of @database (not NULL) - - END TRY - - BEGIN CATCH - - IF @error_number IS NOT NULL + INSERT INTO #BlitzResults + (CheckID, + Priority, + FindingsGroup, + Finding, + URL, + Details) + VALUES(126, 5, 'Reliability', 'Priority Boost Enabled', 'https://www.brentozar.com/go/priorityboost/', + 'Priority Boost sounds awesome, but it can actually cause your SQL Server to crash.'); + END; - /* - - If the ERROR() function returns a number, update the table with it and the last error date. + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 128 ) + AND SERVERPROPERTY('EngineEdition') <> 8 /* Azure Managed Instances */ + BEGIN - Also update the last start time to 1900-01-01 so it gets picked back up immediately -- the query to find a log restore to take sorts by start time + IF (@ProductVersionMajor = 15 AND @ProductVersionMinor < 2000) OR + (@ProductVersionMajor = 14 AND @ProductVersionMinor < 1000) OR + (@ProductVersionMajor = 13 AND @ProductVersionMinor < 6300) OR + (@ProductVersionMajor = 12 AND @ProductVersionMinor < 6024) OR + (@ProductVersionMajor = 11 /*AND @ProductVersionMinor < 7001)*/) OR + (@ProductVersionMajor = 10.5 /*AND @ProductVersionMinor < 6000*/) OR + (@ProductVersionMajor = 10 /*AND @ProductVersionMinor < 6000*/) OR + (@ProductVersionMajor = 9 /*AND @ProductVersionMinor <= 5000*/) + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 128) WITH NOWAIT; + + INSERT INTO #BlitzResults(CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES(128, 20, 'Reliability', 'Unsupported Build of SQL Server', 'https://www.brentozar.com/go/unsupported', + 'Version ' + CAST(@ProductVersionMajor AS VARCHAR(100)) + + CASE WHEN @ProductVersionMajor >= 12 THEN + '.' + CAST(@ProductVersionMinor AS VARCHAR(100)) + ' is no longer supported by Microsoft. You need to apply a service pack.' + ELSE ' is no longer supported by Microsoft. You should be making plans to upgrade to a modern version of SQL Server.' END); + END; - */ - + END; + + /* Reliability - Dangerous Build of SQL Server (Corruption) */ + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 129 ) + AND SERVERPROPERTY('EngineEdition') <> 8 /* Azure Managed Instances */ BEGIN - - SET @msg = N'Error number is ' + CONVERT(NVARCHAR(10), ERROR_NUMBER()); - RAISERROR(@msg, @error_severity, @error_state) WITH NOWAIT; + IF (@ProductVersionMajor = 11 AND @ProductVersionMinor >= 3000 AND @ProductVersionMinor <= 3436) OR + (@ProductVersionMajor = 11 AND @ProductVersionMinor = 5058) OR + (@ProductVersionMajor = 12 AND @ProductVersionMinor >= 2000 AND @ProductVersionMinor <= 2342) + BEGIN - SET @msg = N'Updating restore_worker for database ' + ISNULL(@database, 'UH OH NULL @database') + ' for unsuccessful backup'; - RAISERROR(@msg, 0, 1) WITH NOWAIT; - + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 129) WITH NOWAIT; - UPDATE rw - SET rw.is_started = 0, - rw.is_completed = 1, - rw.last_log_restore_start_time = '19000101', - rw.error_number = @error_number, - rw.last_error_date = GETDATE() - FROM msdb.dbo.restore_worker rw - WHERE rw.database_name = @database; + INSERT INTO #BlitzResults(CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES(129, 20, 'Reliability', 'Dangerous Build of SQL Server (Corruption)', 'http://sqlperformance.com/2014/06/sql-indexes/hotfix-sql-2012-rebuilds', + 'There are dangerous known bugs with version ' + CAST(@ProductVersionMajor AS VARCHAR(100)) + '.' + CAST(@ProductVersionMinor AS VARCHAR(100)) + '. Check the URL for details and apply the right service pack or hotfix.'); + END; + END; - /* + /* Reliability - Dangerous Build of SQL Server (Security) */ + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 157 ) + AND SERVERPROPERTY('EngineEdition') <> 8 /* Azure Managed Instances */ + BEGIN + IF (@ProductVersionMajor = 10 AND @ProductVersionMinor >= 5500 AND @ProductVersionMinor <= 5512) OR + (@ProductVersionMajor = 10 AND @ProductVersionMinor >= 5750 AND @ProductVersionMinor <= 5867) OR + (@ProductVersionMajor = 10.5 AND @ProductVersionMinor >= 4000 AND @ProductVersionMinor <= 4017) OR + (@ProductVersionMajor = 10.5 AND @ProductVersionMinor >= 4251 AND @ProductVersionMinor <= 4319) OR + (@ProductVersionMajor = 11 AND @ProductVersionMinor >= 3000 AND @ProductVersionMinor <= 3129) OR + (@ProductVersionMajor = 11 AND @ProductVersionMinor >= 3300 AND @ProductVersionMinor <= 3447) OR + (@ProductVersionMajor = 12 AND @ProductVersionMinor >= 2000 AND @ProductVersionMinor <= 2253) OR + (@ProductVersionMajor = 12 AND @ProductVersionMinor >= 2300 AND @ProductVersionMinor <= 2370) + BEGIN - Set @database back to NULL to avoid variable assignment weirdness + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 157) WITH NOWAIT; - */ - - SET @database = NULL; - - - /* - - Wait around for a second so we're not just spinning wheels -- this only runs if the BEGIN CATCH is triggered by an error - - */ - - IF @Debug = 1 RAISERROR('Starting 1 second throttle', 0, 1) WITH NOWAIT; - - WAITFOR DELAY '00:00:01.000'; - - END; -- End update of unsuccessful restore - - END CATCH; - - - IF @database IS NOT NULL AND @error_number IS NULL + INSERT INTO #BlitzResults(CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES(157, 20, 'Reliability', 'Dangerous Build of SQL Server (Security)', 'https://technet.microsoft.com/en-us/library/security/MS14-044', + 'There are dangerous known bugs with version ' + CAST(@ProductVersionMajor AS VARCHAR(100)) + '.' + CAST(@ProductVersionMinor AS VARCHAR(100)) + '. Check the URL for details and apply the right service pack or hotfix.'); + END; - /* - - If no error, update everything normally + END; - */ - - - BEGIN - - IF @Debug = 1 RAISERROR('Error number IS NULL', 0, 1) WITH NOWAIT; - - /* Make sure database actually exists and is in the restoring state */ - IF EXISTS (SELECT * FROM sys.databases WHERE name = @database AND state = 1) /* Restoring */ + /* Check if SQL 2016 Standard Edition but not SP1 */ + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 189 ) + AND SERVERPROPERTY('EngineEdition') <> 8 /* Azure Managed Instances */ + BEGIN + IF (@ProductVersionMajor = 13 AND @ProductVersionMinor < 4001 AND @@VERSION LIKE '%Standard Edition%') BEGIN - SET @msg = N'Updating backup_worker for database ' + ISNULL(@database, 'UH OH NULL @database') + ' for successful backup'; - IF @Debug = 1 RAISERROR(@msg, 0, 1) WITH NOWAIT; - UPDATE rw - SET rw.is_started = 0, - rw.is_completed = 1, - rw.last_log_restore_finish_time = GETDATE() - FROM msdb.dbo.restore_worker rw - WHERE rw.database_name = @database; - - END - ELSE /* The database doesn't exist, or it's not in the restoring state */ - BEGIN - SET @msg = N'Updating backup_worker for database ' + ISNULL(@database, 'UH OH NULL @database') + ' for UNsuccessful backup'; - IF @Debug = 1 RAISERROR(@msg, 0, 1) WITH NOWAIT; + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 189) WITH NOWAIT; - UPDATE rw - SET rw.is_started = 0, - rw.is_completed = 1, - rw.error_number = -1, /* unknown, human attention required */ - rw.last_error_date = GETDATE() - /* rw.last_log_restore_finish_time = GETDATE() don't change this - the last log may still be successful */ - FROM msdb.dbo.restore_worker rw - WHERE rw.database_name = @database; - END - + INSERT INTO #BlitzResults(CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES(189, 100, 'Features', 'Missing Features', 'https://blogs.msdn.microsoft.com/sqlreleaseservices/sql-server-2016-service-pack-1-sp1-released/', + 'SQL 2016 Standard Edition is being used but not Service Pack 1. Check the URL for a list of Enterprise Features that are included in Standard Edition as of SP1.'); + END; + END; + + /* Check if SQL 2017 but not CU3 */ + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 216 ) + AND SERVERPROPERTY('EngineEdition') <> 8 /* Azure Managed Instances */ + BEGIN + IF (@ProductVersionMajor = 14 AND @ProductVersionMinor < 3015) + BEGIN - /* - - Set @database back to NULL to avoid variable assignment weirdness + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 216) WITH NOWAIT; - */ - - SET @database = NULL; - + INSERT INTO #BlitzResults(CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES(216, 100, 'Features', 'Missing Features', 'https://support.microsoft.com/en-us/help/4041814', + 'SQL 2017 is being used but not Cumulative Update 3. We''d recommend patching to take advantage of increased analytics when running BlitzCache.'); + END; - END; -- End update for successful backup - - END; -- End @Restore WHILE loop + END; - - END; -- End successful check for restore_worker and subsequent code + /* Cumulative Update Available */ + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 217 ) + AND SERVERPROPERTY('EngineEdition') NOT IN (5,8) /* Azure Managed Instances and Azure SQL DB*/ + AND EXISTS (SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 'SqlServerVersions' AND TABLE_TYPE = 'BASE TABLE') + AND NOT EXISTS (SELECT * FROM #BlitzResults WHERE CheckID IN (128, 129, 157, 189, 216)) /* Other version checks */ + BEGIN + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 217) WITH NOWAIT; + + INSERT INTO #BlitzResults(CheckID, Priority, FindingsGroup, Finding, URL, Details) + SELECT TOP 1 217, 100, 'Reliability', 'Cumulative Update Available', COALESCE(v.Url, 'https://SQLServerUpdates.com/'), + v.MinorVersionName + ' was released on ' + CAST(CONVERT(DATETIME, v.ReleaseDate, 112) AS VARCHAR(100)) + FROM dbo.SqlServerVersions v + WHERE v.MajorVersionNumber = @ProductVersionMajor + AND v.MinorVersionNumber > @ProductVersionMinor + ORDER BY v.MinorVersionNumber DESC; + END; - - ELSE - - BEGIN - - RAISERROR('msdb.dbo.restore_worker does not exist, please run setup script', 0, 1) WITH NOWAIT; - - RETURN; + /* Performance - High Memory Use for In-Memory OLTP (Hekaton) */ + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 145 ) + AND EXISTS ( SELECT * + FROM sys.all_objects o + WHERE o.name = 'dm_db_xtp_table_memory_stats' ) + BEGIN - END; -RETURN; - - + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 145) WITH NOWAIT; + + SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) + SELECT 145 AS CheckID, + 10 AS Priority, + ''Performance'' AS FindingsGroup, + ''High Memory Use for In-Memory OLTP (Hekaton)'' AS Finding, + ''https://www.brentozar.com/go/hekaton'' AS URL, + CAST(CAST((SUM(mem.pages_kb / 1024.0) / CAST(value_in_use AS INT) * 100) AS INT) AS NVARCHAR(100)) + ''% of your '' + CAST(CAST((CAST(value_in_use AS DECIMAL(38,1)) / 1024) AS MONEY) AS NVARCHAR(100)) + ''GB of your max server memory is being used for in-memory OLTP tables (Hekaton). Microsoft recommends having 2X your Hekaton table space available in memory just for Hekaton, with a max of 250GB of in-memory data regardless of your server memory capacity.'' AS Details + FROM sys.configurations c INNER JOIN sys.dm_os_memory_clerks mem ON mem.type = ''MEMORYCLERK_XTP'' + WHERE c.name = ''max server memory (MB)'' + GROUP BY c.value_in_use + HAVING CAST(value_in_use AS DECIMAL(38,2)) * .25 < SUM(mem.pages_kb / 1024.0) + OR SUM(mem.pages_kb / 1024.0) > 250000 OPTION (RECOMPILE)'; + + IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; + IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; + + EXECUTE(@StringToExecute); + END; -END; -- Final END for stored proc + /* Performance - In-Memory OLTP (Hekaton) In Use */ + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 146 ) + AND EXISTS ( SELECT * + FROM sys.all_objects o + WHERE o.name = 'dm_db_xtp_table_memory_stats' ) + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 146) WITH NOWAIT; + + SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) + SELECT 146 AS CheckID, + 200 AS Priority, + ''Performance'' AS FindingsGroup, + ''In-Memory OLTP (Hekaton) In Use'' AS Finding, + ''https://www.brentozar.com/go/hekaton'' AS URL, + CAST(CAST((SUM(mem.pages_kb / 1024.0) / CAST(value_in_use AS INT) * 100) AS DECIMAL(6,1)) AS NVARCHAR(100)) + ''% of your '' + CAST(CAST((CAST(value_in_use AS DECIMAL(38,1)) / 1024) AS MONEY) AS NVARCHAR(100)) + ''GB of your max server memory is being used for in-memory OLTP tables (Hekaton).'' AS Details + FROM sys.configurations c INNER JOIN sys.dm_os_memory_clerks mem ON mem.type = ''MEMORYCLERK_XTP'' + WHERE c.name = ''max server memory (MB)'' + GROUP BY c.value_in_use + HAVING SUM(mem.pages_kb / 1024.0) > 1000 OPTION (RECOMPILE)'; + + IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; + IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; + + EXECUTE(@StringToExecute); + END; -GO -IF OBJECT_ID('dbo.sp_Blitz') IS NULL - EXEC ('CREATE PROCEDURE dbo.sp_Blitz AS RETURN 0;'); -GO + /* In-Memory OLTP (Hekaton) - Transaction Errors */ + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 147 ) + AND EXISTS ( SELECT * + FROM sys.all_objects o + WHERE o.name = 'dm_xtp_transaction_stats' ) + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 147) WITH NOWAIT + + SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) + SELECT 147 AS CheckID, + 100 AS Priority, + ''In-Memory OLTP (Hekaton)'' AS FindingsGroup, + ''Transaction Errors'' AS Finding, + ''https://www.brentozar.com/go/hekaton'' AS URL, + ''Since restart: '' + CAST(validation_failures AS NVARCHAR(100)) + '' validation failures, '' + CAST(dependencies_failed AS NVARCHAR(100)) + '' dependency failures, '' + CAST(write_conflicts AS NVARCHAR(100)) + '' write conflicts, '' + CAST(unique_constraint_violations AS NVARCHAR(100)) + '' unique constraint violations.'' AS Details + FROM sys.dm_xtp_transaction_stats + WHERE validation_failures <> 0 + OR dependencies_failed <> 0 + OR write_conflicts <> 0 + OR unique_constraint_violations <> 0 OPTION (RECOMPILE);'; + + IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; + IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; + + EXECUTE(@StringToExecute); + END; -ALTER PROCEDURE [dbo].[sp_Blitz] - @Help TINYINT = 0 , - @CheckUserDatabaseObjects TINYINT = 1 , - @CheckProcedureCache TINYINT = 0 , - @OutputType VARCHAR(20) = 'TABLE' , - @OutputProcedureCache TINYINT = 0 , - @CheckProcedureCacheFilter VARCHAR(10) = NULL , - @CheckServerInfo TINYINT = 0 , - @SkipChecksServer NVARCHAR(256) = NULL , - @SkipChecksDatabase NVARCHAR(256) = NULL , - @SkipChecksSchema NVARCHAR(256) = NULL , - @SkipChecksTable NVARCHAR(256) = NULL , - @IgnorePrioritiesBelow INT = NULL , - @IgnorePrioritiesAbove INT = NULL , - @OutputServerName NVARCHAR(256) = NULL , - @OutputDatabaseName NVARCHAR(256) = NULL , - @OutputSchemaName NVARCHAR(256) = NULL , - @OutputTableName NVARCHAR(256) = NULL , - @OutputXMLasNVARCHAR TINYINT = 0 , - @EmailRecipients VARCHAR(MAX) = NULL , - @EmailProfile sysname = NULL , - @SummaryMode TINYINT = 0 , - @BringThePain TINYINT = 0 , - @UsualDBOwner sysname = NULL , - @SkipBlockingChecks TINYINT = 1 , - @Debug TINYINT = 0 , - @Version VARCHAR(30) = NULL OUTPUT, - @VersionDate DATETIME = NULL OUTPUT, - @VersionCheckMode BIT = 0 -WITH RECOMPILE -AS - SET NOCOUNT ON; - SET STATISTICS XML OFF; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - + /* Reliability - Database Files on Network File Shares */ + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 148 ) + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 148) WITH NOWAIT; + + INSERT INTO #BlitzResults + ( CheckID , + DatabaseName , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT DISTINCT 148 AS CheckID , + d.[name] AS DatabaseName , + 170 AS Priority , + 'Reliability' AS FindingsGroup , + 'Database Files on Network File Shares' AS Finding , + 'https://www.brentozar.com/go/nas' AS URL , + ( 'Files for this database are on: ' + LEFT(mf.physical_name, 30)) AS Details + FROM sys.databases d + INNER JOIN sys.master_files mf ON d.database_id = mf.database_id + WHERE mf.physical_name LIKE '\\%' + AND d.name NOT IN ( SELECT DISTINCT + DatabaseName + FROM #SkipChecks + WHERE CheckID IS NULL OR CheckID = 148); + END; - SELECT @Version = '8.19', @VersionDate = '20240222'; - SET @OutputType = UPPER(@OutputType); + /* Reliability - Database Files Stored in Azure */ + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 149 ) + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 149) WITH NOWAIT; + + INSERT INTO #BlitzResults + ( CheckID , + DatabaseName , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT DISTINCT 149 AS CheckID , + d.[name] AS DatabaseName , + 170 AS Priority , + 'Reliability' AS FindingsGroup , + 'Database Files Stored in Azure' AS Finding , + 'https://www.brentozar.com/go/azurefiles' AS URL , + ( 'Files for this database are on: ' + LEFT(mf.physical_name, 30)) AS Details + FROM sys.databases d + INNER JOIN sys.master_files mf ON d.database_id = mf.database_id + WHERE mf.physical_name LIKE 'http://%' + AND d.name NOT IN ( SELECT DISTINCT + DatabaseName + FROM #SkipChecks + WHERE CheckID IS NULL OR CheckID = 149); + END; - IF(@VersionCheckMode = 1) - BEGIN - RETURN; - END; + /* Reliability - Errors Logged Recently in the Default Trace */ - IF @Help = 1 - BEGIN - PRINT ' - /* - sp_Blitz from http://FirstResponderKit.org - - This script checks the health of your SQL Server and gives you a prioritized - to-do list of the most urgent things you should consider fixing. + /* First, let's check that there aren't any issues with the trace files */ + BEGIN TRY - To learn more, visit http://FirstResponderKit.org where you can download new - versions for free, watch training videos on how it works, get more info on - the findings, contribute your own code, and more. + IF @SkipTrace = 0 + BEGIN + INSERT INTO #fnTraceGettable + ( TextData , + DatabaseName , + EventClass , + Severity , + StartTime , + EndTime , + Duration , + NTUserName , + NTDomainName , + HostName , + ApplicationName , + LoginName , + DBUserName + ) + SELECT TOP 20000 + CONVERT(NVARCHAR(4000),t.TextData) , + t.DatabaseName , + t.EventClass , + t.Severity , + t.StartTime , + t.EndTime , + t.Duration , + t.NTUserName , + t.NTDomainName , + t.HostName , + t.ApplicationName , + t.LoginName , + t.DBUserName + FROM sys.fn_trace_gettable(@base_tracefilename, DEFAULT) t + WHERE + ( + t.EventClass = 22 + AND t.Severity >= 17 + AND t.StartTime > DATEADD(dd, -30, GETDATE()) + ) + OR + ( + t.EventClass IN (92, 93) + AND t.StartTime > DATEADD(dd, -30, GETDATE()) + AND t.Duration > 15000000 + ) + OR + ( + t.EventClass IN (94, 95, 116) + ) + END; - Known limitations of this version: - - Only Microsoft-supported versions of SQL Server. Sorry, 2005 and 2000. - - If a database name has a question mark in it, some tests will fail. Gotta - love that unsupported sp_MSforeachdb. - - If you have offline databases, sp_Blitz fails the first time you run it, - but does work the second time. (Hoo, boy, this will be fun to debug.) - - @OutputServerName will output QueryPlans as NVARCHAR(MAX) since Microsoft - has refused to support XML columns in Linked Server queries. The bug is now - 16 years old! *~ \o/ ~* + SET @TraceFileIssue = 0 - Unknown limitations of this version: - - None. (If we knew them, they would be known. Duh.) + END TRY + BEGIN CATCH - Changes - for the full list of improvements and fixes in this version, see: - https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/ + SET @TraceFileIssue = 1 + + END CATCH + + IF @TraceFileIssue = 1 + BEGIN + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 199 ) + + INSERT INTO #BlitzResults + ( CheckID , + DatabaseName , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT + '199' AS CheckID , + '' AS DatabaseName , + 50 AS Priority , + 'Reliability' AS FindingsGroup , + 'There Is An Error With The Default Trace' AS Finding , + 'https://www.brentozar.com/go/defaulttrace' AS URL , + 'Somebody has been messing with your trace files. Check the files are present at ' + @base_tracefilename AS Details + END + + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 150 ) + AND @base_tracefilename IS NOT NULL + AND @TraceFileIssue = 0 + BEGIN - Parameter explanations: + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 150) WITH NOWAIT; + + INSERT INTO #BlitzResults + ( CheckID , + DatabaseName , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT DISTINCT 150 AS CheckID , + t.DatabaseName, + 170 AS Priority , + 'Reliability' AS FindingsGroup , + 'Errors Logged Recently in the Default Trace' AS Finding , + 'https://www.brentozar.com/go/defaulttrace' AS URL , + CAST(t.TextData AS NVARCHAR(4000)) AS Details + FROM #fnTraceGettable t + WHERE t.EventClass = 22 + /* Removed these as they're unnecessary, we filter this when inserting data into #fnTraceGettable */ + --AND t.Severity >= 17 + --AND t.StartTime > DATEADD(dd, -30, GETDATE()); + END; - @CheckUserDatabaseObjects 1=review user databases for triggers, heaps, etc. Takes more time for more databases and objects. - @CheckServerInfo 1=show server info like CPUs, memory, virtualization - @CheckProcedureCache 1=top 20-50 resource-intensive cache plans and analyze them for common performance issues. - @OutputProcedureCache 1=output the top 20-50 resource-intensive plans even if they did not trigger an alarm - @CheckProcedureCacheFilter ''CPU'' | ''Reads'' | ''Duration'' | ''ExecCount'' - @OutputType ''TABLE''=table | ''COUNT''=row with number found | ''MARKDOWN''=bulleted list (including server info, excluding security findings) | ''SCHEMA''=version and field list | ''XML'' =table output as XML | ''NONE'' = none - @IgnorePrioritiesBelow 50=ignore priorities below 50 - @IgnorePrioritiesAbove 50=ignore priorities above 50 - @Debug 0=silent (Default) | 1=messages per step | 2=outputs dynamic queries - For the rest of the parameters, see https://www.BrentOzar.com/blitz/documentation for details. + /* Performance - File Growths Slow */ + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 151 ) + AND @base_tracefilename IS NOT NULL + AND @TraceFileIssue = 0 + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 151) WITH NOWAIT; + + INSERT INTO #BlitzResults + ( CheckID , + DatabaseName , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT DISTINCT 151 AS CheckID , + t.DatabaseName, + 50 AS Priority , + 'Performance' AS FindingsGroup , + 'File Growths Slow' AS Finding , + 'https://www.brentozar.com/go/filegrowth' AS URL , + CAST(COUNT(*) AS NVARCHAR(100)) + ' growths took more than 15 seconds each. Consider setting file autogrowth to a smaller increment.' AS Details + FROM #fnTraceGettable t + WHERE t.EventClass IN (92, 93) + /* Removed these as they're unnecessary, we filter this when inserting data into #fnTraceGettable */ + --AND t.StartTime > DATEADD(dd, -30, GETDATE()) + --AND t.Duration > 15000000 + GROUP BY t.DatabaseName + HAVING COUNT(*) > 1; + END; - MIT License - - Copyright for portions of sp_Blitz are held by Microsoft as part of project - tigertoolbox and are provided under the MIT license: - https://github.com/Microsoft/tigertoolbox - - All other copyrights for sp_Blitz are held by Brent Ozar Unlimited. + /* Performance - Many Plans for One Query */ + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 160 ) + AND EXISTS (SELECT * FROM sys.all_columns WHERE name = 'query_hash') + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 160) WITH NOWAIT; + + SET @StringToExecute = N'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) + SELECT TOP 1 160 AS CheckID, + 100 AS Priority, + ''Performance'' AS FindingsGroup, + ''Many Plans for One Query'' AS Finding, + ''https://www.brentozar.com/go/parameterization'' AS URL, + CAST(COUNT(DISTINCT plan_handle) AS NVARCHAR(50)) + '' plans are present for a single query in the plan cache - meaning we probably have parameterization issues.'' AS Details + FROM sys.dm_exec_query_stats qs + CROSS APPLY sys.dm_exec_plan_attributes(qs.plan_handle) pa + WHERE pa.attribute = ''dbid'' + GROUP BY qs.query_hash, pa.value + HAVING COUNT(DISTINCT plan_handle) > '; - Copyright (c) Brent Ozar Unlimited + IF 50 > (SELECT COUNT(*) FROM sys.databases) + SET @StringToExecute = @StringToExecute + N' 50 '; + ELSE + SELECT @StringToExecute = @StringToExecute + CAST(COUNT(*) * 2 AS NVARCHAR(50)) FROM sys.databases; - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: + SET @StringToExecute = @StringToExecute + N' ORDER BY COUNT(DISTINCT plan_handle) DESC OPTION (RECOMPILE);'; + + IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; + IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; + + EXECUTE(@StringToExecute); + END; - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. + /* Performance - High Number of Cached Plans */ + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 161 ) + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 161) WITH NOWAIT; + + SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) + SELECT TOP 1 161 AS CheckID, + 100 AS Priority, + ''Performance'' AS FindingsGroup, + ''High Number of Cached Plans'' AS Finding, + ''https://www.brentozar.com/go/planlimits'' AS URL, + ''Your server configuration is limited to '' + CAST(ht.buckets_count * 4 AS VARCHAR(20)) + '' '' + ht.name + '', and you are currently caching '' + CAST(cc.entries_count AS VARCHAR(20)) + ''.'' AS Details + FROM sys.dm_os_memory_cache_hash_tables ht + INNER JOIN sys.dm_os_memory_cache_counters cc ON ht.name = cc.name AND ht.type = cc.type + where ht.name IN ( ''SQL Plans'' , ''Object Plans'' , ''Bound Trees'' ) + AND cc.entries_count >= (3 * ht.buckets_count) OPTION (RECOMPILE)'; + + IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; + IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; + + EXECUTE(@StringToExecute); + END; - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE. + /* Performance - Too Much Free Memory */ + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 165 ) + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 165) WITH NOWAIT; + + INSERT INTO #BlitzResults + (CheckID, + Priority, + FindingsGroup, + Finding, + URL, + Details) + SELECT 165, 50, 'Performance', 'Too Much Free Memory', 'https://www.brentozar.com/go/freememory', + CAST((CAST(cFree.cntr_value AS BIGINT) / 1024 / 1024 ) AS NVARCHAR(100)) + N'GB of free memory inside SQL Server''s buffer pool, which is ' + CAST((CAST(cTotal.cntr_value AS BIGINT) / 1024 / 1024) AS NVARCHAR(100)) + N'GB. You would think lots of free memory would be good, but check out the URL for more information.' AS Details + FROM sys.dm_os_performance_counters cFree + INNER JOIN sys.dm_os_performance_counters cTotal ON cTotal.object_name LIKE N'%Memory Manager%' + AND cTotal.counter_name = N'Total Server Memory (KB) ' + WHERE cFree.object_name LIKE N'%Memory Manager%' + AND cFree.counter_name = N'Free Memory (KB) ' + AND CAST(cTotal.cntr_value AS BIGINT) > 20480000000 + AND CAST(cTotal.cntr_value AS BIGINT) * .3 <= CAST(cFree.cntr_value AS BIGINT) + AND CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) NOT LIKE '%Standard%'; - */'; - RETURN; - END; /* @Help = 1 */ + END; - ELSE IF @OutputType = 'SCHEMA' - BEGIN - SELECT FieldList = '[Priority] TINYINT, [FindingsGroup] VARCHAR(50), [Finding] VARCHAR(200), [DatabaseName] NVARCHAR(128), [URL] VARCHAR(200), [Details] NVARCHAR(4000), [QueryPlan] NVARCHAR(MAX), [QueryPlanFiltered] NVARCHAR(MAX), [CheckID] INT'; + /* Outdated sp_Blitz - sp_Blitz is Over 6 Months Old */ + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 155 ) + AND DATEDIFF(MM, @VersionDate, GETDATE()) > 6 + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 155) WITH NOWAIT; + + INSERT INTO #BlitzResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT 155 AS CheckID , + 0 AS Priority , + 'Outdated sp_Blitz' AS FindingsGroup , + 'sp_Blitz is Over 6 Months Old' AS Finding , + 'http://FirstResponderKit.org/' AS URL , + 'Some things get better with age, like fine wine and your T-SQL. However, sp_Blitz is not one of those things - time to go download the current one.' AS Details; + END; - END;/* IF @OutputType = 'SCHEMA' */ - ELSE - BEGIN + /* Populate a list of database defaults. I'm doing this kind of oddly - + it reads like a lot of work, but this way it compiles & runs on all + versions of SQL Server. + */ + + IF @Debug IN (1, 2) RAISERROR('Generating database defaults.', 0, 1) WITH NOWAIT; + + INSERT INTO #DatabaseDefaults + SELECT 'is_supplemental_logging_enabled', 0, 131, 210, 'Supplemental Logging Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL + FROM sys.all_columns + WHERE name = 'is_supplemental_logging_enabled' AND object_id = OBJECT_ID('sys.databases'); + INSERT INTO #DatabaseDefaults + SELECT 'snapshot_isolation_state', 0, 132, 210, 'Snapshot Isolation Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL + FROM sys.all_columns + WHERE name = 'snapshot_isolation_state' AND object_id = OBJECT_ID('sys.databases'); + INSERT INTO #DatabaseDefaults + SELECT 'is_read_committed_snapshot_on', + CASE WHEN SERVERPROPERTY('EngineEdition') = 5 THEN 1 ELSE 0 END, /* RCSI is always enabled in Azure SQL DB per https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/1919 */ + 133, 210, CASE WHEN SERVERPROPERTY('EngineEdition') = 5 THEN 'Read Committed Snapshot Isolation Disabled' ELSE 'Read Committed Snapshot Isolation Enabled' END, 'https://www.brentozar.com/go/dbdefaults', NULL + FROM sys.all_columns + WHERE name = 'is_read_committed_snapshot_on' AND object_id = OBJECT_ID('sys.databases'); + INSERT INTO #DatabaseDefaults + SELECT 'is_auto_create_stats_incremental_on', 0, 134, 210, 'Auto Create Stats Incremental Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL + FROM sys.all_columns + WHERE name = 'is_auto_create_stats_incremental_on' AND object_id = OBJECT_ID('sys.databases'); + INSERT INTO #DatabaseDefaults + SELECT 'is_ansi_null_default_on', 0, 135, 210, 'ANSI NULL Default Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL + FROM sys.all_columns + WHERE name = 'is_ansi_null_default_on' AND object_id = OBJECT_ID('sys.databases'); + INSERT INTO #DatabaseDefaults + SELECT 'is_recursive_triggers_on', 0, 136, 210, 'Recursive Triggers Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL + FROM sys.all_columns + WHERE name = 'is_recursive_triggers_on' AND object_id = OBJECT_ID('sys.databases'); + INSERT INTO #DatabaseDefaults + SELECT 'is_trustworthy_on', 0, 137, 210, 'Trustworthy Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL + FROM sys.all_columns + WHERE name = 'is_trustworthy_on' AND object_id = OBJECT_ID('sys.databases'); + INSERT INTO #DatabaseDefaults + SELECT 'is_broker_enabled', 0, 230, 210, 'Broker Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL + FROM sys.all_columns + WHERE name = 'is_broker_enabled' AND object_id = OBJECT_ID('sys.databases'); + INSERT INTO #DatabaseDefaults + SELECT 'is_honor_broker_priority_on', 0, 231, 210, 'Honor Broker Priority Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL + FROM sys.all_columns + WHERE name = 'is_honor_broker_priority_on' AND object_id = OBJECT_ID('sys.databases'); + INSERT INTO #DatabaseDefaults + SELECT 'is_parameterization_forced', 0, 138, 210, 'Forced Parameterization Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL + FROM sys.all_columns + WHERE name = 'is_parameterization_forced' AND object_id = OBJECT_ID('sys.databases'); + /* Not alerting for this since we actually want it and we have a separate check for it: + INSERT INTO #DatabaseDefaults + SELECT 'is_query_store_on', 0, 139, 210, 'Query Store Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL + FROM sys.all_columns + WHERE name = 'is_query_store_on' AND object_id = OBJECT_ID('sys.databases'); + */ + INSERT INTO #DatabaseDefaults + SELECT 'is_cdc_enabled', 0, 140, 210, 'Change Data Capture Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL + FROM sys.all_columns + WHERE name = 'is_cdc_enabled' AND object_id = OBJECT_ID('sys.databases'); + INSERT INTO #DatabaseDefaults + SELECT 'containment', 0, 141, 210, 'Containment Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL + FROM sys.all_columns + WHERE name = 'containment' AND object_id = OBJECT_ID('sys.databases'); + --INSERT INTO #DatabaseDefaults + -- SELECT 'target_recovery_time_in_seconds', 0, 142, 210, 'Target Recovery Time Changed', 'https://www.brentozar.com/go/dbdefaults', NULL + -- FROM sys.all_columns + -- WHERE name = 'target_recovery_time_in_seconds' AND object_id = OBJECT_ID('sys.databases'); + INSERT INTO #DatabaseDefaults + SELECT 'delayed_durability', 0, 143, 210, 'Delayed Durability Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL + FROM sys.all_columns + WHERE name = 'delayed_durability' AND object_id = OBJECT_ID('sys.databases'); + INSERT INTO #DatabaseDefaults + SELECT 'is_memory_optimized_elevate_to_snapshot_on', 0, 144, 210, 'Memory Optimized Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL + FROM sys.all_columns + WHERE name = 'is_memory_optimized_elevate_to_snapshot_on' AND object_id = OBJECT_ID('sys.databases') + AND SERVERPROPERTY('EngineEdition') <> 8; /* Hekaton is always enabled in Managed Instances per https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/1919 */ + INSERT INTO #DatabaseDefaults + SELECT 'is_accelerated_database_recovery_on', 0, 145, 210, 'Acclerated Database Recovery Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL + FROM sys.all_columns + WHERE name = 'is_accelerated_database_recovery_on' AND object_id = OBJECT_ID('sys.databases') AND SERVERPROPERTY('EngineEdition') NOT IN (5, 8) ; - DECLARE @StringToExecute NVARCHAR(4000) - ,@curr_tracefilename NVARCHAR(500) - ,@base_tracefilename NVARCHAR(500) - ,@indx int - ,@query_result_separator CHAR(1) - ,@EmailSubject NVARCHAR(255) - ,@EmailBody NVARCHAR(MAX) - ,@EmailAttachmentFilename NVARCHAR(255) - ,@ProductVersion NVARCHAR(128) - ,@ProductVersionMajor DECIMAL(10,2) - ,@ProductVersionMinor DECIMAL(10,2) - ,@CurrentName NVARCHAR(128) - ,@CurrentDefaultValue NVARCHAR(200) - ,@CurrentCheckID INT - ,@CurrentPriority INT - ,@CurrentFinding VARCHAR(200) - ,@CurrentURL VARCHAR(200) - ,@CurrentDetails NVARCHAR(4000) - ,@MsSinceWaitsCleared DECIMAL(38,0) - ,@CpuMsSinceWaitsCleared DECIMAL(38,0) - ,@ResultText NVARCHAR(MAX) - ,@crlf NVARCHAR(2) - ,@Processors int - ,@NUMANodes int - ,@MinServerMemory bigint - ,@MaxServerMemory bigint - ,@ColumnStoreIndexesInUse bit - ,@TraceFileIssue bit - -- Flag for Windows OS to help with Linux support - ,@IsWindowsOperatingSystem BIT - ,@DaysUptime NUMERIC(23,2) - /* For First Responder Kit consistency check:*/ - ,@spBlitzFullName VARCHAR(1024) - ,@BlitzIsOutdatedComparedToOthers BIT - ,@tsql NVARCHAR(MAX) - ,@VersionCheckModeExistsTSQL NVARCHAR(MAX) - ,@BlitzProcDbName VARCHAR(256) - ,@ExecRet INT - ,@InnerExecRet INT - ,@TmpCnt INT - ,@PreviousComponentName VARCHAR(256) - ,@PreviousComponentFullPath VARCHAR(1024) - ,@CurrentStatementId INT - ,@CurrentComponentSchema VARCHAR(256) - ,@CurrentComponentName VARCHAR(256) - ,@CurrentComponentType VARCHAR(256) - ,@CurrentComponentVersionDate DATETIME2 - ,@CurrentComponentFullName VARCHAR(1024) - ,@CurrentComponentMandatory BIT - ,@MaximumVersionDate DATETIME - ,@StatementCheckName VARCHAR(256) - ,@StatementOutputsCounter BIT - ,@OutputCounterExpectedValue INT - ,@StatementOutputsExecRet BIT - ,@StatementOutputsDateTime BIT - ,@CurrentComponentMandatoryCheckOK BIT - ,@CurrentComponentVersionCheckModeOK BIT - ,@canExitLoop BIT - ,@frkIsConsistent BIT - ,@NeedToTurnNumericRoundabortBackOn BIT - ,@sa bit = 1 - ,@SUSER_NAME sysname = SUSER_SNAME() - ,@SkipDBCC bit = 0 - ,@SkipTrace bit = 0 - ,@SkipXPRegRead bit = 0 - ,@SkipXPFixedDrives bit = 0 - ,@SkipXPCMDShell bit = 0 - ,@SkipMaster bit = 0 - ,@SkipMSDB_objs bit = 0 - ,@SkipMSDB_jobs bit = 0 - ,@SkipModel bit = 0 - ,@SkipTempDB bit = 0 - ,@SkipValidateLogins bit = 0 - ,@SkipGetAlertInfo bit = 0 + DECLARE DatabaseDefaultsLoop CURSOR FOR + SELECT name, DefaultValue, CheckID, Priority, Finding, URL, Details + FROM #DatabaseDefaults; - DECLARE - @db_perms table - ( - database_name sysname, - permission_name sysname - ); + OPEN DatabaseDefaultsLoop; + FETCH NEXT FROM DatabaseDefaultsLoop into @CurrentName, @CurrentDefaultValue, @CurrentCheckID, @CurrentPriority, @CurrentFinding, @CurrentURL, @CurrentDetails; + WHILE @@FETCH_STATUS = 0 + BEGIN - INSERT - @db_perms - ( - database_name, - permission_name - ) - SELECT - database_name = - DB_NAME(d.database_id), - fmp.permission_name - FROM sys.databases AS d - CROSS APPLY fn_my_permissions(d.name, 'DATABASE') AS fmp - WHERE fmp.permission_name = N'SELECT'; /*Databases where we don't have read permissions*/ - - /* End of declarations for First Responder Kit consistency check:*/ - ; + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, @CurrentCheckID) WITH NOWAIT; - /* Create temp table for check 73 */ - IF OBJECT_ID('tempdb..#AlertInfo') IS NOT NULL - EXEC sp_executesql N'DROP TABLE #AlertInfo;'; + /* Target Recovery Time (142) can be either 0 or 60 due to a number of bugs */ + IF @CurrentCheckID = 142 + SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details) + SELECT ' + CAST(@CurrentCheckID AS NVARCHAR(200)) + ', d.[name], ' + CAST(@CurrentPriority AS NVARCHAR(200)) + ', ''Non-Default Database Config'', ''' + @CurrentFinding + ''',''' + @CurrentURL + ''',''' + COALESCE(@CurrentDetails, 'This database setting is not the default.') + ''' + FROM sys.databases d + WHERE d.database_id > 4 AND d.state = 0 AND (d.[' + @CurrentName + '] NOT IN (0, 60) OR d.[' + @CurrentName + '] IS NULL) OPTION (RECOMPILE);'; + ELSE + SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details) + SELECT ' + CAST(@CurrentCheckID AS NVARCHAR(200)) + ', d.[name], ' + CAST(@CurrentPriority AS NVARCHAR(200)) + ', ''Non-Default Database Config'', ''' + @CurrentFinding + ''',''' + @CurrentURL + ''',''' + COALESCE(@CurrentDetails, 'This database setting is not the default.') + ''' + FROM sys.databases d + WHERE d.database_id > 4 AND d.state = 0 AND (d.[' + @CurrentName + '] <> ' + @CurrentDefaultValue + ' OR d.[' + @CurrentName + '] IS NULL) OPTION (RECOMPILE);'; + + IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; + IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; + + EXEC (@StringToExecute); - CREATE TABLE #AlertInfo - ( - FailSafeOperator NVARCHAR(255) , - NotificationMethod INT , - ForwardingServer NVARCHAR(255) , - ForwardingSeverity INT , - PagerToTemplate NVARCHAR(255) , - PagerCCTemplate NVARCHAR(255) , - PagerSubjectTemplate NVARCHAR(255) , - PagerSendSubjectOnly NVARCHAR(255) , - ForwardAlways INT - ); + FETCH NEXT FROM DatabaseDefaultsLoop into @CurrentName, @CurrentDefaultValue, @CurrentCheckID, @CurrentPriority, @CurrentFinding, @CurrentURL, @CurrentDetails; + END; - /* Create temp table for check 2301 */ - IF OBJECT_ID('tempdb..#InvalidLogins') IS NOT NULL - EXEC sp_executesql N'DROP TABLE #InvalidLogins;'; - - CREATE TABLE #InvalidLogins - ( - LoginSID varbinary(85), - LoginName VARCHAR(256) - ); + CLOSE DatabaseDefaultsLoop; + DEALLOCATE DatabaseDefaultsLoop; - /*Starting permissions checks here, but only if we're not a sysadmin*/ - IF +/* Check if target recovery interval <> 60 */ +IF + @ProductVersionMajor >= 10 + AND NOT EXISTS ( - SELECT - sa = - ISNULL - ( - IS_SRVROLEMEMBER(N'sysadmin'), - 0 - ) - ) = 0 - BEGIN - IF @Debug IN (1, 2) RAISERROR('User not SA, checking permissions', 0, 1) WITH NOWAIT; - - SET @sa = 0; /*Setting this to 0 to skip DBCC COMMANDS*/ - - IF NOT EXISTS - ( - SELECT - 1/0 - FROM sys.fn_my_permissions(NULL, NULL) AS fmp - WHERE fmp.permission_name = N'VIEW SERVER STATE' - ) - BEGIN - RAISERROR('The user %s does not have VIEW SERVER STATE permissions.', 0, 11, @SUSER_NAME) WITH NOWAIT; - RETURN; - END; /*If we don't have this, we can't do anything at all.*/ - - IF NOT EXISTS - ( - SELECT - 1/0 - FROM fn_my_permissions(N'sys.traces', N'OBJECT') AS fmp - WHERE fmp.permission_name = N'ALTER' - ) - BEGIN - SET @SkipTrace = 1; - END; /*We need this permission to execute trace stuff, apparently*/ + SELECT + 1/0 + FROM #SkipChecks AS sc + WHERE sc.DatabaseName IS NULL + AND sc.CheckID = 257 + ) + BEGIN + IF EXISTS + ( + SELECT + 1/0 + FROM sys.all_columns AS ac + WHERE ac.name = 'target_recovery_time_in_seconds' + AND ac.object_id = OBJECT_ID('sys.databases') + ) + BEGIN + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 257) WITH NOWAIT; + + DECLARE + @tri nvarchar(max) = N' + SELECT + DatabaseName = + d.name, + CheckId = + 257, + Priority = + 50, + FindingsGroup = + N''Performance'', + Finding = + N''Recovery Interval Not Optimal'', + URL = + N''https://sqlperformance.com/2020/05/system-configuration/0-to-60-switching-to-indirect-checkpoints'', + Details = + N''The database '' + + QUOTENAME(d.name) + + N'' has a target recovery interval of '' + + RTRIM(d.target_recovery_time_in_seconds) + + CASE + WHEN d.target_recovery_time_in_seconds = 0 + THEN N'', which is a legacy default, and should be changed to 60.'' + WHEN d.target_recovery_time_in_seconds <> 0 + THEN N'', which is probably a mistake, and should be changed to 60.'' + END + FROM sys.databases AS d + WHERE d.database_id > 4 + AND d.is_read_only = 0 + AND d.is_in_standby = 0 + AND d.target_recovery_time_in_seconds <> 60; + '; - IF NOT EXISTS - ( - SELECT - 1/0 - FROM fn_my_permissions(N'xp_fixeddrives', N'OBJECT') AS fmp - WHERE fmp.permission_name = N'EXECUTE' - ) - BEGIN - SET @SkipXPFixedDrives = 1; - END; /*Need execute on xp_fixeddrives*/ + INSERT INTO + #BlitzResults + ( + DatabaseName, + CheckID, + Priority, + FindingsGroup, + Finding, + URL, + Details + ) + EXEC sys.sp_executesql + @tri; - IF NOT EXISTS - ( - SELECT - 1/0 - FROM fn_my_permissions(N'xp_cmdshell', N'OBJECT') AS fmp - WHERE fmp.permission_name = N'EXECUTE' - ) - BEGIN - SET @SkipXPCMDShell = 1; - END; /*Need execute on xp_cmdshell*/ + END; + END; + - IF ISNULL(@SkipValidateLogins, 0) != 1 /*If @SkipValidateLogins hasn't been set to 1 by the caller*/ - BEGIN - BEGIN TRY - /* Try to fill the table for check 2301 */ - INSERT INTO #InvalidLogins - ( - [LoginSID] - ,[LoginName] - ) - EXEC sp_validatelogins; - - SET @SkipValidateLogins = 0; /*We can execute sp_validatelogins*/ - END TRY - BEGIN CATCH - SET @SkipValidateLogins = 1; /*We have don't have execute rights or sp_validatelogins throws an error so skip it*/ - END CATCH; - END; /*Need execute on sp_validatelogins*/ - - IF ISNULL(@SkipGetAlertInfo, 0) != 1 /*If @SkipGetAlertInfo hasn't been set to 1 by the caller*/ - BEGIN - BEGIN TRY - /* Try to fill the table for check 73 */ - INSERT INTO #AlertInfo - EXEC [master].[dbo].[sp_MSgetalertinfo] @includeaddresses = 0; - - SET @SkipGetAlertInfo = 0; /*We can execute sp_MSgetalertinfo*/ - END TRY - BEGIN CATCH - SET @SkipGetAlertInfo = 1; /*We have don't have execute rights or sp_MSgetalertinfo throws an error so skip it*/ - END CATCH; - END; /*Need execute on sp_MSgetalertinfo*/ +/*This checks to see if Agent is Offline*/ +IF @ProductVersionMajor >= 10 + AND NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 167 ) + BEGIN + IF EXISTS ( SELECT 1 + FROM sys.all_objects + WHERE name = 'dm_server_services' ) + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 167) WITH NOWAIT; + + INSERT INTO [#BlitzResults] + ( [CheckID] , + [Priority] , + [FindingsGroup] , + [Finding] , + [URL] , + [Details] ) - IF ISNULL(@SkipModel, 0) != 1 /*If @SkipModel hasn't been set to 1 by the caller*/ - BEGIN - IF EXISTS - ( - SELECT 1/0 - FROM @db_perms - WHERE database_name = N'model' - ) - BEGIN - BEGIN TRY - IF EXISTS - ( - SELECT 1/0 - FROM model.sys.objects - ) - BEGIN - SET @SkipModel = 0; /*We have read permissions in the model database, and can view the objects*/ - END; - END TRY - BEGIN CATCH - SET @SkipModel = 1; /*We have read permissions in the model database ... oh wait we got tricked, we can't view the objects*/ - END CATCH; - END; - ELSE - BEGIN - SET @SkipModel = 1; /*We don't have read permissions in the model database*/ - END; - END; + SELECT + 167 AS [CheckID] , + 250 AS [Priority] , + 'Server Info' AS [FindingsGroup] , + 'Agent is Currently Offline' AS [Finding] , + '' AS [URL] , + ( 'Oops! It looks like the ' + [servicename] + ' service is ' + [status_desc] + '. The startup type is ' + [startup_type_desc] + '.' + ) AS [Details] + FROM + [sys].[dm_server_services] + WHERE [status_desc] <> 'Running' + AND [servicename] LIKE 'SQL Server Agent%' + AND CAST(SERVERPROPERTY('Edition') AS VARCHAR(1000)) NOT LIKE '%xpress%'; - IF ISNULL(@SkipMSDB_objs, 0) != 1 /*If @SkipMSDB_objs hasn't been set to 1 by the caller*/ - BEGIN - IF EXISTS - ( - SELECT 1/0 - FROM @db_perms - WHERE database_name = N'msdb' - ) - BEGIN - BEGIN TRY - IF EXISTS - ( - SELECT 1/0 - FROM msdb.sys.objects - ) - BEGIN - SET @SkipMSDB_objs = 0; /*We have read permissions in the msdb database, and can view the objects*/ - END; - END TRY - BEGIN CATCH - SET @SkipMSDB_objs = 1; /*We have read permissions in the msdb database ... oh wait we got tricked, we can't view the objects*/ - END CATCH; + END; END; - ELSE +/* CheckID 258 - Security - SQL Server Service is running as LocalSystem or NT AUTHORITY\SYSTEM */ +IF @ProductVersionMajor >= 10 + AND NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 258 ) BEGIN - SET @SkipMSDB_objs = 1; /*We don't have read permissions in the msdb database*/ - END; - END; + IF EXISTS ( SELECT 1 + FROM sys.all_objects + WHERE [name] = 'dm_server_services' ) + BEGIN + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 258) WITH NOWAIT; + + INSERT INTO [#BlitzResults] + ( [CheckID] , + [Priority] , + [FindingsGroup] , + [Finding] , + [URL] , + [Details] ) - IF ISNULL(@SkipMSDB_jobs, 0) != 1 /*If @SkipMSDB_jobs hasn't been set to 1 by the caller*/ - BEGIN - IF EXISTS - ( - SELECT 1/0 - FROM @db_perms - WHERE database_name = N'msdb' - ) - BEGIN - BEGIN TRY - IF EXISTS - ( - SELECT 1/0 - FROM msdb.dbo.sysjobs - ) - BEGIN - SET @SkipMSDB_jobs = 0; /*We have read permissions in the msdb database, and can view the objects*/ - END; - END TRY - BEGIN CATCH - SET @SkipMSDB_jobs = 1; /*We have read permissions in the msdb database ... oh wait we got tricked, we can't view the objects*/ - END CATCH; - END; - ELSE - BEGIN - SET @SkipMSDB_jobs = 1; /*We don't have read permissions in the msdb database*/ + SELECT + 258 AS [CheckID] , + 1 AS [Priority] , + 'Security' AS [FindingsGroup] , + 'Dangerous Service Account' AS [Finding] , + 'https://vladdba.com/SQLServerSvcAccount' AS [URL] , + 'SQL Server''s service account is '+ [service_account] + +' - meaning that anyone who can use xp_cmdshell can do absolutely anything on the host.' AS [Details] + FROM + [sys].[dm_server_services] + WHERE ([service_account] = 'LocalSystem' + OR LOWER([service_account]) = 'nt authority\system') + AND [servicename] LIKE 'SQL Server%' + AND [servicename] NOT LIKE 'SQL Server Agent%'; + END; END; - END; - END; - SET @crlf = NCHAR(13) + NCHAR(10); - SET @ResultText = 'sp_Blitz Results: ' + @crlf; - - /* Last startup */ - SELECT @DaysUptime = CAST(DATEDIFF(HOUR, create_date, GETDATE()) / 24. AS NUMERIC(23, 2)) - FROM sys.databases - WHERE database_id = 2; - - IF @DaysUptime = 0 - SET @DaysUptime = .01; - - /* - Set the session state of Numeric_RoundAbort to off if any databases have Numeric Round-Abort enabled. - Stops arithmetic overflow errors during data conversion. See Github issue #2302 for more info. - */ - IF ( (8192 & @@OPTIONS) = 8192 ) /* Numeric RoundAbort is currently on, so we may need to turn it off temporarily */ - BEGIN - IF EXISTS (SELECT 1 - FROM sys.databases - WHERE is_numeric_roundabort_on = 1) /* A database has it turned on */ +/* CheckID 259 - Security - SQL Server Agent Service is running as LocalSystem or NT AUTHORITY\SYSTEM */ +IF @ProductVersionMajor >= 10 + AND NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 259 ) BEGIN - SET @NeedToTurnNumericRoundabortBackOn = 1; - SET NUMERIC_ROUNDABORT OFF; + IF EXISTS ( SELECT 1 + FROM sys.all_objects + WHERE [name] = 'dm_server_services' ) + BEGIN + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 259) WITH NOWAIT; + + INSERT INTO [#BlitzResults] + ( [CheckID] , + [Priority] , + [FindingsGroup] , + [Finding] , + [URL] , + [Details] ) + + SELECT + 259 AS [CheckID] , + 1 AS [Priority] , + 'Security' AS [FindingsGroup] , + 'Dangerous Service Account' AS [Finding] , + 'https://vladdba.com/SQLServerSvcAccount' AS [URL] , + 'SQL Server Agent''s service account is '+ [service_account] + +' - meaning that anyone who can create and run jobs can do absolutely anything on the host.' AS [Details] + FROM + [sys].[dm_server_services] + WHERE ([service_account] = 'LocalSystem' + OR LOWER([service_account]) = 'nt authority\system') + AND [servicename] LIKE 'SQL Server Agent%'; + END; END; - END; - +/*This checks to see if the Full Text thingy is offline*/ +IF @ProductVersionMajor >= 10 + AND NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 168 ) + BEGIN + IF EXISTS ( SELECT 1 + FROM sys.all_objects + WHERE name = 'dm_server_services' ) + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 168) WITH NOWAIT; + + INSERT INTO [#BlitzResults] + ( [CheckID] , + [Priority] , + [FindingsGroup] , + [Finding] , + [URL] , + [Details] ) + SELECT + 168 AS [CheckID] , + 250 AS [Priority] , + 'Server Info' AS [FindingsGroup] , + 'Full-text Filter Daemon Launcher is Currently Offline' AS [Finding] , + '' AS [URL] , + ( 'Oops! It looks like the ' + [servicename] + ' service is ' + [status_desc] + '. The startup type is ' + [startup_type_desc] + '.' + ) AS [Details] + FROM + [sys].[dm_server_services] + WHERE [status_desc] <> 'Running' + AND [servicename] LIKE 'SQL Full-text Filter Daemon Launcher%'; - /* - --TOURSTOP01-- - See https://www.BrentOzar.com/go/blitztour for a guided tour. + END; + END; - We start by creating #BlitzResults. It's a temp table that will store all of - the results from our checks. Throughout the rest of this stored procedure, - we're running a series of checks looking for dangerous things inside the SQL - Server. When we find a problem, we insert rows into #BlitzResults. At the - end, we return these results to the end user. - - #BlitzResults has a CheckID field, but there's no Check table. As we do - checks, we insert data into this table, and we manually put in the CheckID. - For a list of checks, visit http://FirstResponderKit.org. - */ - IF OBJECT_ID('tempdb..#BlitzResults') IS NOT NULL - DROP TABLE #BlitzResults; - CREATE TABLE #BlitzResults - ( - ID INT IDENTITY(1, 1) , - CheckID INT , - DatabaseName NVARCHAR(128) , - Priority TINYINT , - FindingsGroup VARCHAR(50) , - Finding VARCHAR(200) , - URL VARCHAR(200) , - Details NVARCHAR(4000) , - QueryPlan [XML] NULL , - QueryPlanFiltered [NVARCHAR](MAX) NULL - ); +/*This checks which service account SQL Server is running as.*/ +IF @ProductVersionMajor >= 10 + AND NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 169 ) - IF OBJECT_ID('tempdb..#TemporaryDatabaseResults') IS NOT NULL - DROP TABLE #TemporaryDatabaseResults; - CREATE TABLE #TemporaryDatabaseResults - ( - DatabaseName NVARCHAR(128) , - Finding NVARCHAR(128) - ); + BEGIN + IF EXISTS ( SELECT 1 + FROM sys.all_objects + WHERE name = 'dm_server_services' ) + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 169) WITH NOWAIT; + + INSERT INTO [#BlitzResults] + ( [CheckID] , + [Priority] , + [FindingsGroup] , + [Finding] , + [URL] , + [Details] ) - /* First Responder Kit consistency (temporary tables) */ - - IF(OBJECT_ID('tempdb..#FRKObjects') IS NOT NULL) - BEGIN - EXEC sp_executesql N'DROP TABLE #FRKObjects;'; - END; + SELECT + 169 AS [CheckID] , + 250 AS [Priority] , + 'Informational' AS [FindingsGroup] , + 'SQL Server is running under an NT Service account' AS [Finding] , + 'https://www.brentozar.com/go/setup' AS [URL] , + ( 'I''m running as ' + [service_account] + '.' + ) AS [Details] + FROM + [sys].[dm_server_services] + WHERE [service_account] LIKE 'NT Service%' + AND [servicename] LIKE 'SQL Server%' + AND [servicename] NOT LIKE 'SQL Server Agent%' + AND [servicename] NOT LIKE 'SQL Server Launchpad%'; - -- this one represents FRK objects - CREATE TABLE #FRKObjects ( - DatabaseName VARCHAR(256) NOT NULL, - ObjectSchemaName VARCHAR(256) NULL, - ObjectName VARCHAR(256) NOT NULL, - ObjectType VARCHAR(256) NOT NULL, - MandatoryComponent BIT NOT NULL - ); - - - IF(OBJECT_ID('tempdb..#StatementsToRun4FRKVersionCheck') IS NOT NULL) - BEGIN - EXEC sp_executesql N'DROP TABLE #StatementsToRun4FRKVersionCheck;'; - END; + END; + END; +/*This checks which service account SQL Agent is running as.*/ +IF @ProductVersionMajor >= 10 + AND NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 170 ) - -- This one will contain the statements to be executed - -- order: 1- Mandatory, 2- VersionCheckMode, 3- VersionCheck + BEGIN + IF EXISTS ( SELECT 1 + FROM sys.all_objects + WHERE name = 'dm_server_services' ) + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 170) WITH NOWAIT; + + INSERT INTO [#BlitzResults] + ( [CheckID] , + [Priority] , + [FindingsGroup] , + [Finding] , + [URL] , + [Details] ) - CREATE TABLE #StatementsToRun4FRKVersionCheck ( - StatementId INT IDENTITY(1,1), - CheckName VARCHAR(256), - SubjectName VARCHAR(256), - SubjectFullPath VARCHAR(1024), - StatementText NVARCHAR(MAX), - StatementOutputsCounter BIT, - OutputCounterExpectedValue INT, - StatementOutputsExecRet BIT, - StatementOutputsDateTime BIT - ); + SELECT + 170 AS [CheckID] , + 250 AS [Priority] , + 'Informational' AS [FindingsGroup] , + 'SQL Server Agent is running under an NT Service account' AS [Finding] , + 'https://www.brentozar.com/go/setup' AS [URL] , + ( 'I''m running as ' + [service_account] + '.' + ) AS [Details] + FROM + [sys].[dm_server_services] + WHERE [service_account] LIKE 'NT Service%' + AND [servicename] LIKE 'SQL Server Agent%'; - /* End of First Responder Kit consistency (temporary tables) */ - - - /* - You can build your own table with a list of checks to skip. For example, you - might have some databases that you don't care about, or some checks you don't - want to run. Then, when you run sp_Blitz, you can specify these parameters: - @SkipChecksDatabase = 'DBAtools', - @SkipChecksSchema = 'dbo', - @SkipChecksTable = 'BlitzChecksToSkip' - Pass in the database, schema, and table that contains the list of checks you - want to skip. This part of the code checks those parameters, gets the list, - and then saves those in a temp table. As we run each check, we'll see if we - need to skip it. - */ - /* --TOURSTOP07-- */ - IF OBJECT_ID('tempdb..#SkipChecks') IS NOT NULL - DROP TABLE #SkipChecks; - CREATE TABLE #SkipChecks - ( - DatabaseName NVARCHAR(128) , - CheckID INT , - ServerName NVARCHAR(128) - ); - CREATE CLUSTERED INDEX IX_CheckID_DatabaseName ON #SkipChecks(CheckID, DatabaseName); + END; + END; + +/*This checks that First Responder Kit is consistent. +It assumes that all the objects of the kit resides in the same database, the one in which this SP is stored +It also is ready to check for installation in another schema. +*/ +IF( + NOT EXISTS ( + SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 226 + ) +) +BEGIN - INSERT INTO #SkipChecks - (DatabaseName) - SELECT - DB_NAME(d.database_id) - FROM sys.databases AS d - WHERE (DB_NAME(d.database_id) LIKE 'rdsadmin%' - OR LOWER(d.name) IN ('dbatools', 'dbadmin', 'dbmaintenance')) - OPTION(RECOMPILE); + IF @Debug IN (1, 2) RAISERROR('Running check with id %d',0,1,2000); - /*Skip checks for database where we don't have read permissions*/ - INSERT INTO - #SkipChecks - ( - DatabaseName - ) - SELECT - DB_NAME(d.database_id) - FROM sys.databases AS d - WHERE NOT EXISTS - ( - SELECT - 1/0 - FROM @db_perms AS dp - WHERE dp.database_name = DB_NAME(d.database_id) - ); + SET @spBlitzFullName = QUOTENAME(DB_NAME()) + '.' +QUOTENAME(OBJECT_SCHEMA_NAME(@@PROCID)) + '.' + QUOTENAME(OBJECT_NAME(@@PROCID)); + SET @BlitzIsOutdatedComparedToOthers = 0; + SET @tsql = NULL; + SET @VersionCheckModeExistsTSQL = NULL; + SET @BlitzProcDbName = DB_NAME(); + SET @ExecRet = NULL; + SET @InnerExecRet = NULL; + SET @TmpCnt = NULL; - /*Skip individial checks where we don't have permissions*/ - INSERT #SkipChecks (DatabaseName, CheckID, ServerName) - SELECT - v.* - FROM (VALUES(NULL, 29, NULL)) AS v (DatabaseName, CheckID, ServerName) /*Looks for user tables in model*/ - WHERE @SkipModel = 1; + SET @PreviousComponentName = NULL; + SET @PreviousComponentFullPath = NULL; + SET @CurrentStatementId = NULL; + SET @CurrentComponentSchema = NULL; + SET @CurrentComponentName = NULL; + SET @CurrentComponentType = NULL; + SET @CurrentComponentVersionDate = NULL; + SET @CurrentComponentFullName = NULL; + SET @CurrentComponentMandatory = NULL; + SET @MaximumVersionDate = NULL; - INSERT #SkipChecks (DatabaseName, CheckID, ServerName) - SELECT - v.* - FROM (VALUES(NULL, 28, NULL)) AS v (DatabaseName, CheckID, ServerName) /*Tables in the MSDB Database*/ - WHERE @SkipMSDB_objs = 1; + SET @StatementCheckName = NULL; + SET @StatementOutputsCounter = NULL; + SET @OutputCounterExpectedValue = NULL; + SET @StatementOutputsExecRet = NULL; + SET @StatementOutputsDateTime = NULL; - INSERT #SkipChecks (DatabaseName, CheckID, ServerName) - SELECT - v.* - FROM (VALUES - /*sysjobs checks*/ - (NULL, 6, NULL), /*Jobs Owned By Users*/ - (NULL, 57, NULL), /*SQL Agent Job Runs at Startup*/ - (NULL, 79, NULL), /*Shrink Database Job*/ - (NULL, 94, NULL), /*Agent Jobs Without Failure Emails*/ - (NULL, 123, NULL), /*Agent Jobs Starting Simultaneously*/ - (NULL, 180, NULL), /*Shrink Database Step In Maintenance Plan*/ - (NULL, 181, NULL), /*Repetitive Maintenance Tasks*/ - - /*sysalerts checks*/ - (NULL, 30, NULL), /*Not All Alerts Configured*/ - (NULL, 59, NULL), /*Alerts Configured without Follow Up*/ - (NULL, 61, NULL), /*No Alerts for Sev 19-25*/ - (NULL, 96, NULL), /*No Alerts for Corruption*/ - (NULL, 98, NULL), /*Alerts Disabled*/ - (NULL, 219, NULL), /*Alerts Without Event Descriptions*/ + SET @CurrentComponentMandatoryCheckOK = NULL; + SET @CurrentComponentVersionCheckModeOK = NULL; - /*sysoperators*/ - (NULL, 31, NULL) /*No Operators Configured/Enabled*/ - ) AS v (DatabaseName, CheckID, ServerName) - WHERE @SkipMSDB_jobs = 1; + SET @canExitLoop = 0; + SET @frkIsConsistent = 0; - INSERT #SkipChecks (DatabaseName, CheckID, ServerName) - SELECT - v.* - FROM (VALUES(NULL, 68, NULL)) AS v (DatabaseName, CheckID, ServerName) /*DBCC command*/ - WHERE @sa = 0; - INSERT #SkipChecks (DatabaseName, CheckID, ServerName) - SELECT - v.* - FROM (VALUES(NULL, 69, NULL)) AS v (DatabaseName, CheckID, ServerName) /*DBCC command*/ - WHERE @sa = 0; + SET @tsql = 'USE ' + QUOTENAME(@BlitzProcDbName) + ';' + @crlf + + 'WITH FRKComponents (' + @crlf + + ' ObjectName,' + @crlf + + ' ObjectType,' + @crlf + + ' MandatoryComponent' + @crlf + + ')' + @crlf + + 'AS (' + @crlf + + ' SELECT ''sp_AllNightLog'',''P'' ,0' + @crlf + + ' UNION ALL' + @crlf + + ' SELECT ''sp_AllNightLog_Setup'', ''P'',0' + @crlf + + ' UNION ALL ' + @crlf + + ' SELECT ''sp_Blitz'',''P'',0' + @crlf + + ' UNION ALL ' + @crlf + + ' SELECT ''sp_BlitzBackups'',''P'',0' + @crlf + + ' UNION ALL ' + @crlf + + ' SELECT ''sp_BlitzCache'',''P'',0' + @crlf + + ' UNION ALL ' + @crlf + + ' SELECT ''sp_BlitzFirst'',''P'',0' + @crlf + + ' UNION ALL' + @crlf + + ' SELECT ''sp_BlitzIndex'',''P'',0' + @crlf + + ' UNION ALL ' + @crlf + + ' SELECT ''sp_BlitzLock'',''P'',0' + @crlf + + ' UNION ALL ' + @crlf + + ' SELECT ''sp_BlitzQueryStore'',''P'',0' + @crlf + + ' UNION ALL ' + @crlf + + ' SELECT ''sp_BlitzWho'',''P'',0' + @crlf + + ' UNION ALL ' + @crlf + + ' SELECT ''sp_DatabaseRestore'',''P'',0' + @crlf + + ' UNION ALL ' + @crlf + + ' SELECT ''sp_ineachdb'',''P'',0' + @crlf + + ' UNION ALL' + @crlf + + ' SELECT ''SqlServerVersions'',''U'',0' + @crlf + + ')' + @crlf + + 'INSERT INTO #FRKObjects (' + @crlf + + ' DatabaseName,ObjectSchemaName,ObjectName, ObjectType,MandatoryComponent' + @crlf + + ')' + @crlf + + 'SELECT DB_NAME(),SCHEMA_NAME(o.schema_id), c.ObjectName,c.ObjectType,c.MandatoryComponent' + @crlf + + 'FROM ' + @crlf + + ' FRKComponents c' + @crlf + + 'LEFT JOIN ' + @crlf + + ' sys.objects o' + @crlf + + 'ON c.ObjectName = o.[name]' + @crlf + + 'AND c.ObjectType = o.[type]' + @crlf + + --'WHERE o.schema_id IS NOT NULL' + @crlf + + ';' + ; - INSERT #SkipChecks (DatabaseName, CheckID, ServerName) - SELECT - v.* - FROM (VALUES(NULL, 92, NULL)) AS v (DatabaseName, CheckID, ServerName) /*xp_fixeddrives*/ - WHERE @SkipXPFixedDrives = 1; + EXEC @ExecRet = sp_executesql @tsql ; - INSERT #SkipChecks (DatabaseName, CheckID, ServerName) - SELECT - v.* - FROM (VALUES(NULL, 106, NULL)) AS v (DatabaseName, CheckID, ServerName) /*alter trace*/ - WHERE @SkipTrace = 1; + -- TODO: add check for statement success - INSERT #SkipChecks (DatabaseName, CheckID, ServerName) - SELECT - v.* - FROM (VALUES(NULL, 211, NULL)) AS v (DatabaseName, CheckID, ServerName) /*xp_regread*/ - WHERE @sa = 0; + -- TODO: based on SP requirements and presence (SchemaName is not null) ==> update MandatoryComponent column - INSERT #SkipChecks (DatabaseName, CheckID, ServerName) - SELECT - v.* - FROM (VALUES(NULL, 212, NULL)) AS v (DatabaseName, CheckID, ServerName) /*xp_regread*/ - WHERE @SkipXPCMDShell = 1; + -- Filling #StatementsToRun4FRKVersionCheck + INSERT INTO #StatementsToRun4FRKVersionCheck ( + CheckName,StatementText,SubjectName,SubjectFullPath, StatementOutputsCounter,OutputCounterExpectedValue,StatementOutputsExecRet,StatementOutputsDateTime + ) + SELECT + 'Mandatory', + 'SELECT @cnt = COUNT(*) FROM #FRKObjects WHERE ObjectSchemaName IS NULL AND ObjectName = ''' + ObjectName + ''' AND MandatoryComponent = 1;', + ObjectName, + QUOTENAME(DatabaseName) + '.' + QUOTENAME(ObjectSchemaName) + '.' + QUOTENAME(ObjectName), + 1, + 0, + 0, + 0 + FROM #FRKObjects + UNION ALL + SELECT + 'VersionCheckMode', + 'SELECT @cnt = COUNT(*) FROM ' + + QUOTENAME(DatabaseName) + '.sys.all_parameters ' + + 'where object_id = OBJECT_ID(''' + QUOTENAME(DatabaseName) + '.' + QUOTENAME(ObjectSchemaName) + '.' + QUOTENAME(ObjectName) + ''') AND [name] = ''@VersionCheckMode'';', + ObjectName, + QUOTENAME(DatabaseName) + '.' + QUOTENAME(ObjectSchemaName) + '.' + QUOTENAME(ObjectName), + 1, + 1, + 0, + 0 + FROM #FRKObjects + WHERE ObjectType = 'P' + AND ObjectSchemaName IS NOT NULL + UNION ALL + SELECT + 'VersionCheck', + 'EXEC @ExecRet = ' + QUOTENAME(DatabaseName) + '.' + QUOTENAME(ObjectSchemaName) + '.' + QUOTENAME(ObjectName) + ' @VersionCheckMode = 1 , @VersionDate = @ObjDate OUTPUT;', + ObjectName, + QUOTENAME(DatabaseName) + '.' + QUOTENAME(ObjectSchemaName) + '.' + QUOTENAME(ObjectName), + 0, + 0, + 1, + 1 + FROM #FRKObjects + WHERE ObjectType = 'P' + AND ObjectSchemaName IS NOT NULL + ; + IF(@Debug in (1,2)) + BEGIN + SELECT * + FROM #StatementsToRun4FRKVersionCheck ORDER BY SubjectName,SubjectFullPath,StatementId -- in case of schema change ; + END; + + + -- loop on queries... + WHILE(@canExitLoop = 0) + BEGIN + SET @CurrentStatementId = NULL; - INSERT #SkipChecks (DatabaseName, CheckID, ServerName) - SELECT - v.* - FROM (VALUES(NULL, 2301, NULL)) AS v (DatabaseName, CheckID, ServerName) /*sp_validatelogins*/ - WHERE @SkipValidateLogins = 1; + SELECT TOP 1 + @StatementCheckName = CheckName, + @CurrentStatementId = StatementId , + @CurrentComponentName = SubjectName, + @CurrentComponentFullName = SubjectFullPath, + @tsql = StatementText, + @StatementOutputsCounter = StatementOutputsCounter, + @OutputCounterExpectedValue = OutputCounterExpectedValue , + @StatementOutputsExecRet = StatementOutputsExecRet, + @StatementOutputsDateTime = StatementOutputsDateTime + FROM #StatementsToRun4FRKVersionCheck + ORDER BY SubjectName, SubjectFullPath,StatementId /* in case of schema change */ + ; - INSERT #SkipChecks (DatabaseName, CheckID, ServerName) - SELECT - v.* - FROM (VALUES(NULL, 73, NULL)) AS v (DatabaseName, CheckID, ServerName) /*sp_validatelogins*/ - WHERE @SkipGetAlertInfo = 1; + -- loop exit condition + IF(@CurrentStatementId IS NULL) + BEGIN + BREAK; + END; - IF @sa = 0 - BEGIN - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 223 AS CheckID , - 0 AS Priority , - 'Informational' AS FindingsGroup , - 'Some Checks Skipped' AS Finding , - '' AS URL , - 'User ''' + @SUSER_NAME + ''' is not part of the sysadmin role, so we skipped some checks that are not possible due to lack of permissions.' AS Details; - END; - /*End of SkipsChecks added due to permissions*/ + IF @Debug IN (1, 2) RAISERROR(' Statement: %s',0,1,@tsql); - IF @SkipChecksTable IS NOT NULL - AND @SkipChecksSchema IS NOT NULL - AND @SkipChecksDatabase IS NOT NULL - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Inserting SkipChecks', 0, 1) WITH NOWAIT; - - SET @StringToExecute = N'INSERT INTO #SkipChecks(DatabaseName, CheckID, ServerName ) - SELECT DISTINCT DatabaseName, CheckID, ServerName - FROM ' - IF LTRIM(RTRIM(@SkipChecksServer)) <> '' - BEGIN - SET @StringToExecute += QUOTENAME(@SkipChecksServer) + N'.'; - END - SET @StringToExecute += QUOTENAME(@SkipChecksDatabase) + N'.' + QUOTENAME(@SkipChecksSchema) + N'.' + QUOTENAME(@SkipChecksTable) - + N' WHERE ServerName IS NULL OR ServerName = CAST(SERVERPROPERTY(''ServerName'') AS NVARCHAR(128)) OPTION (RECOMPILE);'; - EXEC(@StringToExecute); - END; + -- we start a new component + IF(@PreviousComponentName IS NULL OR + (@PreviousComponentName IS NOT NULL AND @PreviousComponentName <> @CurrentComponentName) OR + (@PreviousComponentName IS NOT NULL AND @PreviousComponentName = @CurrentComponentName AND @PreviousComponentFullPath <> @CurrentComponentFullName) + ) + BEGIN + -- reset variables + SET @CurrentComponentMandatoryCheckOK = 0; + SET @CurrentComponentVersionCheckModeOK = 0; + SET @PreviousComponentName = @CurrentComponentName; + SET @PreviousComponentFullPath = @CurrentComponentFullName ; + END; - -- Flag for Windows OS to help with Linux support - IF EXISTS ( SELECT 1 - FROM sys.all_objects - WHERE name = 'dm_os_host_info' ) - BEGIN - SELECT @IsWindowsOperatingSystem = CASE WHEN host_platform = 'Windows' THEN 1 ELSE 0 END FROM sys.dm_os_host_info ; - END; - ELSE - BEGIN - SELECT @IsWindowsOperatingSystem = 1 ; - END; + IF(@StatementCheckName NOT IN ('Mandatory','VersionCheckMode','VersionCheck')) + BEGIN + INSERT INTO #BlitzResults( + CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT + 226 AS CheckID , + 253 AS Priority , + 'First Responder Kit' AS FindingsGroup , + 'Version Check Failed (code generator changed)' AS Finding , + 'http://FirstResponderKit.org' AS URL , + 'Download an updated First Responder Kit. Your version check failed because a change has been made to the version check code generator.' + @crlf + + 'Error: No handler for check with name "' + ISNULL(@StatementCheckName,'') + '"' AS Details + ; + + -- we will stop the test because it's possible to get the same message for other components + SET @canExitLoop = 1; + CONTINUE; + END; + IF(@StatementCheckName = 'Mandatory') + BEGIN + -- outputs counter + EXEC @ExecRet = sp_executesql @tsql, N'@cnt INT OUTPUT',@cnt = @TmpCnt OUTPUT; - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 106 ) - AND (select convert(int,value_in_use) from sys.configurations where name = 'default trace enabled' ) = 1 - BEGIN - - select @curr_tracefilename = [path] from sys.traces where is_default = 1 ; - set @curr_tracefilename = reverse(@curr_tracefilename); + IF(@ExecRet <> 0) + BEGIN + + INSERT INTO #BlitzResults( + CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT + 226 AS CheckID , + 253 AS Priority , + 'First Responder Kit' AS FindingsGroup , + 'Version Check Failed (dynamic query failure)' AS Finding , + 'http://FirstResponderKit.org' AS URL , + 'Download an updated First Responder Kit. Your version check failed due to dynamic query failure.' + @crlf + + 'Error: following query failed at execution (check if component [' + ISNULL(@CurrentComponentName,@CurrentComponentName) + '] is mandatory and missing)' + @crlf + + @tsql AS Details + ; + + -- we will stop the test because it's possible to get the same message for other components + SET @canExitLoop = 1; + CONTINUE; + END; - -- Set the trace file path separator based on underlying OS - IF (@IsWindowsOperatingSystem = 1) AND @curr_tracefilename IS NOT NULL - BEGIN - select @indx = patindex('%\%', @curr_tracefilename) ; - set @curr_tracefilename = reverse(@curr_tracefilename) ; - set @base_tracefilename = left( @curr_tracefilename,len(@curr_tracefilename) - @indx) + '\log.trc' ; - END; - ELSE - BEGIN - select @indx = patindex('%/%', @curr_tracefilename) ; - set @curr_tracefilename = reverse(@curr_tracefilename) ; - set @base_tracefilename = left( @curr_tracefilename,len(@curr_tracefilename) - @indx) + '/log.trc' ; - END; + IF(@TmpCnt <> @OutputCounterExpectedValue) + BEGIN + INSERT INTO #BlitzResults( + CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT + 227 AS CheckID , + 253 AS Priority , + 'First Responder Kit' AS FindingsGroup , + 'Component Missing: ' + @CurrentComponentName AS Finding , + 'http://FirstResponderKit.org' AS URL , + 'Download an updated version of the First Responder Kit to install it.' AS Details + ; + + -- as it's missing, no value for SubjectFullPath + DELETE FROM #StatementsToRun4FRKVersionCheck WHERE SubjectName = @CurrentComponentName ; + CONTINUE; + END; - END; + SET @CurrentComponentMandatoryCheckOK = 1; + END; + + IF(@StatementCheckName = 'VersionCheckMode') + BEGIN + IF(@CurrentComponentMandatoryCheckOK = 0) + BEGIN + INSERT INTO #BlitzResults( + CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT + 226 AS CheckID , + 253 AS Priority , + 'First Responder Kit' AS FindingsGroup , + 'Version Check Failed (unexpectedly modified checks ordering)' AS Finding , + 'http://FirstResponderKit.org' AS URL , + 'Download an updated First Responder Kit. Version check failed because "Mandatory" check has not been completed before for current component' + @crlf + + 'Error: version check mode happenned before "Mandatory" check for component called "' + @CurrentComponentFullName + '"' + ; + + -- we will stop the test because it's possible to get the same message for other components + SET @canExitLoop = 1; + CONTINUE; + END; - /* If the server has any databases on Antiques Roadshow, skip the checks that would break due to CTEs. */ - IF @CheckUserDatabaseObjects = 1 AND EXISTS(SELECT * FROM sys.databases WHERE compatibility_level < 90) - BEGIN - SET @CheckUserDatabaseObjects = 0; - PRINT 'Databases with compatibility level < 90 found, so setting @CheckUserDatabaseObjects = 0.'; - PRINT 'The database-level checks rely on CTEs, which are not supported in SQL 2000 compat level databases.'; - PRINT 'Get with the cool kids and switch to a current compatibility level, Grandpa. To find the problems, run:'; - PRINT 'SELECT * FROM sys.databases WHERE compatibility_level < 90;'; - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 204 AS CheckID , - 0 AS Priority , - 'Informational' AS FindingsGroup , - '@CheckUserDatabaseObjects Disabled' AS Finding , - 'https://www.BrentOzar.com/blitz/' AS URL , - 'Since you have databases with compatibility_level < 90, we can''t run @CheckUserDatabaseObjects = 1. To find them: SELECT * FROM sys.databases WHERE compatibility_level < 90' AS Details; - END; + -- outputs counter + EXEC @ExecRet = sp_executesql @tsql, N'@cnt INT OUTPUT',@cnt = @TmpCnt OUTPUT; - /* --TOURSTOP08-- */ - /* If the server is Amazon RDS, skip checks that it doesn't allow */ - IF LEFT(CAST(SERVERPROPERTY('ComputerNamePhysicalNetBIOS') AS VARCHAR(8000)), 8) = 'EC2AMAZ-' - AND LEFT(CAST(SERVERPROPERTY('MachineName') AS VARCHAR(8000)), 8) = 'EC2AMAZ-' - AND db_id('rdsadmin') IS NOT NULL - AND EXISTS(SELECT * FROM master.sys.all_objects WHERE name IN ('rds_startup_tasks', 'rds_help_revlogin', 'rds_hexadecimal', 'rds_failover_tracking', 'rds_database_tracking', 'rds_track_change')) - BEGIN - 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 (29); /* tables in model database created by users - not allowed */ - INSERT INTO #SkipChecks (CheckID) VALUES (40); /* TempDB only has one data file in RDS */ - INSERT INTO #SkipChecks (CheckID) VALUES (62); /* Database compatibility level - cannot change in RDS */ - INSERT INTO #SkipChecks (CheckID) VALUES (68); /*Check for the last good DBCC CHECKDB date - can't run DBCC DBINFO() */ - INSERT INTO #SkipChecks (CheckID) VALUES (69); /* High VLF check - requires DBCC LOGINFO permission */ - INSERT INTO #SkipChecks (CheckID) VALUES (73); /* No Failsafe Operator Configured check */ - INSERT INTO #SkipChecks (CheckID) VALUES (92); /* Drive info check - requires xp_Fixeddrives permission */ - INSERT INTO #SkipChecks (CheckID) VALUES (100); /* Remote DAC disabled */ - INSERT INTO #SkipChecks (CheckID) VALUES (177); /* Disabled Internal Monitoring Features check - requires dm_server_registry access */ - INSERT INTO #SkipChecks (CheckID) VALUES (180); /* 180/181 are maintenance plans checks - Maint plans not available in RDS*/ - INSERT INTO #SkipChecks (CheckID) VALUES (181); /*Find repetitive maintenance tasks*/ + IF(@ExecRet <> 0) + BEGIN + INSERT INTO #BlitzResults( + CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT + 226 AS CheckID , + 253 AS Priority , + 'First Responder Kit' AS FindingsGroup , + 'Version Check Failed (dynamic query failure)' AS Finding , + 'http://FirstResponderKit.org' AS URL , + 'Download an updated First Responder Kit. Version check failed because a change has been made to the code generator.' + @crlf + + 'Error: following query failed at execution (check if component [' + @CurrentComponentFullName + '] can run in VersionCheckMode)' + @crlf + + @tsql AS Details + ; + + -- we will stop the test because it's possible to get the same message for other components + SET @canExitLoop = 1; + CONTINUE; + END; - -- can check errorlog using rdsadmin.dbo.rds_read_error_log, so allow this check - --INSERT INTO #SkipChecks (CheckID) VALUES (193); /* xp_readerrorlog checking for IFI */ + IF(@TmpCnt <> @OutputCounterExpectedValue) + BEGIN + INSERT INTO #BlitzResults( + CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT + 228 AS CheckID , + 253 AS Priority , + 'First Responder Kit' AS FindingsGroup , + 'Component Outdated: ' + @CurrentComponentFullName AS Finding , + 'http://FirstResponderKit.org' AS URL , + 'Download an updated First Responder Kit. Component ' + @CurrentComponentFullName + ' is not at the minimum version required to run this procedure' + @crlf + + 'VersionCheckMode has been introduced in component version date after "20190320". This means its version is lower than or equal to that date.' AS Details; + ; + + DELETE FROM #StatementsToRun4FRKVersionCheck WHERE SubjectFullPath = @CurrentComponentFullName ; + CONTINUE; + END; - INSERT INTO #SkipChecks (CheckID) VALUES (211); /* xp_regread not allowed - checking for power saving */ - INSERT INTO #SkipChecks (CheckID) VALUES (212); /* xp_regread not allowed - checking for additional instances */ - INSERT INTO #SkipChecks (CheckID) VALUES (2301); /* sp_validatelogins called by Invalid login defined with Windows Authentication */ + SET @CurrentComponentVersionCheckModeOK = 1; + END; - -- Following are skipped due to limited permissions in msdb/SQLAgent in RDS - INSERT INTO #SkipChecks (CheckID) VALUES (30); /* SQL Server Agent alerts not configured */ - INSERT INTO #SkipChecks (CheckID) VALUES (31); /* check whether we have NO ENABLED operators */ - INSERT INTO #SkipChecks (CheckID) VALUES (57); /* SQL Agent Job Runs at Startup */ - INSERT INTO #SkipChecks (CheckID) VALUES (59); /* Alerts Configured without Follow Up */ - INSERT INTO #SkipChecks (CheckID) VALUES (61); /*SQL Server Agent alerts do not exist for severity levels 19 through 25*/ - INSERT INTO #SkipChecks (CheckID) VALUES (79); /* Shrink Database Job check */ - INSERT INTO #SkipChecks (CheckID) VALUES (94); /* job failure without operator notification check */ - INSERT INTO #SkipChecks (CheckID) VALUES (96); /* Agent alerts for corruption */ - INSERT INTO #SkipChecks (CheckID) VALUES (98); /* check for disabled alerts */ - INSERT INTO #SkipChecks (CheckID) VALUES (123); /* Agent Jobs Starting Simultaneously */ - INSERT INTO #SkipChecks (CheckID) VALUES (219); /* check for alerts that do NOT include event descriptions in their outputs via email/pager/net-send */ - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 223 AS CheckID , - 0 AS Priority , - 'Informational' AS FindingsGroup , - 'Some Checks Skipped' AS Finding , - 'https://aws.amazon.com/rds/sqlserver/' AS URL , - 'Amazon RDS detected, so we skipped some checks that are not currently possible, relevant, or practical there.' AS Details; - END; /* Amazon RDS skipped checks */ + IF(@StatementCheckName = 'VersionCheck') + BEGIN + IF(@CurrentComponentMandatoryCheckOK = 0 OR @CurrentComponentVersionCheckModeOK = 0) + BEGIN + INSERT INTO #BlitzResults( + CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT + 226 AS CheckID , + 253 AS Priority , + 'First Responder Kit' AS FindingsGroup , + 'Version Check Failed (unexpectedly modified checks ordering)' AS Finding , + 'http://FirstResponderKit.org' AS URL , + 'Download an updated First Responder Kit. Version check failed because "VersionCheckMode" check has not been completed before for component called "' + @CurrentComponentFullName + '"' + @crlf + + 'Error: VersionCheck happenned before "VersionCheckMode" check for component called "' + @CurrentComponentFullName + '"' + ; + + -- we will stop the test because it's possible to get the same message for other components + SET @canExitLoop = 1; + CONTINUE; + END; - /* If the server is ExpressEdition, skip checks that it doesn't allow */ - IF CAST(SERVERPROPERTY('Edition') AS NVARCHAR(1000)) LIKE N'%Express%' - BEGIN - INSERT INTO #SkipChecks (CheckID) VALUES (30); /* Alerts not configured */ - INSERT INTO #SkipChecks (CheckID) VALUES (31); /* Operators not configured */ - INSERT INTO #SkipChecks (CheckID) VALUES (61); /* Agent alerts 19-25 */ - INSERT INTO #SkipChecks (CheckID) VALUES (73); /* Failsafe operator */ - INSERT INTO #SkipChecks (CheckID) VALUES (96); /* Agent alerts for corruption */ - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 223 AS CheckID , - 0 AS Priority , - 'Informational' AS FindingsGroup , - 'Some Checks Skipped' AS Finding , - 'https://stackoverflow.com/questions/1169634/limitations-of-sql-server-express' AS URL , - 'Express Edition detected, so we skipped some checks that are not currently possible, relevant, or practical there.' AS Details; - END; /* Express Edition skipped checks */ + EXEC @ExecRet = sp_executesql @tsql , N'@ExecRet INT OUTPUT, @ObjDate DATETIME OUTPUT', @ExecRet = @InnerExecRet OUTPUT, @ObjDate = @CurrentComponentVersionDate OUTPUT; - /* If the server is an Azure Managed Instance, skip checks that it doesn't allow */ - IF SERVERPROPERTY('EngineEdition') = 8 - BEGIN - INSERT INTO #SkipChecks (CheckID) VALUES (1); /* Full backups - because of the MI GUID name bug mentioned here: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/1481 */ - INSERT INTO #SkipChecks (CheckID) VALUES (2); /* Log backups - because of the MI GUID name bug mentioned here: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/1481 */ - 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 (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 (74); /* TraceFlag On - because Azure Managed Instances go wild and crazy with the trace flags */ - 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 (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 */ - INSERT INTO #SkipChecks (CheckID, DatabaseName) VALUES (80, 'model'); /* Max file size set */ - INSERT INTO #SkipChecks (CheckID, DatabaseName) VALUES (80, 'msdb'); /* Max file size set */ - INSERT INTO #SkipChecks (CheckID, DatabaseName) VALUES (80, 'tempdb'); /* Max file size set */ - INSERT INTO #SkipChecks (CheckID) VALUES (224); /* CheckID 224 - Performance - SSRS/SSAS/SSIS Installed */ - INSERT INTO #SkipChecks (CheckID) VALUES (92); /* CheckID 92 - drive space */ - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 223 AS CheckID , - 0 AS Priority , - 'Informational' AS FindingsGroup , - 'Some Checks Skipped' AS Finding , - 'https://docs.microsoft.com/en-us/azure/sql-database/sql-database-managed-instance-index' AS URL , - 'Managed Instance detected, so we skipped some checks that are not currently possible, relevant, or practical there.' AS Details; - END; /* Azure Managed Instance skipped checks */ + IF(@ExecRet <> 0) + BEGIN + INSERT INTO #BlitzResults( + CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT + 226 AS CheckID , + 253 AS Priority , + 'First Responder Kit' AS FindingsGroup , + 'Version Check Failed (dynamic query failure)' AS Finding , + 'http://FirstResponderKit.org' AS URL , + 'Download an updated First Responder Kit. The version check failed because a change has been made to the code generator.' + @crlf + + 'Error: following query failed at execution (check if component [' + @CurrentComponentFullName + '] is at the expected version)' + @crlf + + @tsql AS Details + ; + + -- we will stop the test because it's possible to get the same message for other components + SET @canExitLoop = 1; + CONTINUE; + END; + + + IF(@InnerExecRet <> 0) + BEGIN + INSERT INTO #BlitzResults( + CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT + 226 AS CheckID , + 253 AS Priority , + 'First Responder Kit' AS FindingsGroup , + 'Version Check Failed (Failed dynamic SP call to ' + @CurrentComponentFullName + ')' AS Finding , + 'http://FirstResponderKit.org' AS URL , + 'Download an updated First Responder Kit. Error: following query failed at execution (check if component [' + @CurrentComponentFullName + '] is at the expected version)' + @crlf + + 'Return code: ' + CONVERT(VARCHAR(10),@InnerExecRet) + @crlf + + 'T-SQL Query: ' + @crlf + + @tsql AS Details + ; + + -- advance to next component + DELETE FROM #StatementsToRun4FRKVersionCheck WHERE SubjectFullPath = @CurrentComponentFullName ; + CONTINUE; + END; - /* - That's the end of the SkipChecks stuff. - The next several tables are used by various checks later. - */ - IF OBJECT_ID('tempdb..#ConfigurationDefaults') IS NOT NULL - DROP TABLE #ConfigurationDefaults; - CREATE TABLE #ConfigurationDefaults - ( - name NVARCHAR(128) , - DefaultValue BIGINT, - CheckID INT - ); + IF(@CurrentComponentVersionDate < @VersionDate) + BEGIN + + INSERT INTO #BlitzResults( + CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT + 228 AS CheckID , + 253 AS Priority , + 'First Responder Kit' AS FindingsGroup , + 'Component Outdated: ' + @CurrentComponentFullName AS Finding , + 'http://FirstResponderKit.org' AS URL , + 'Download and install the latest First Responder Kit - you''re running some older code, and it doesn''t get better with age.' AS Details + ; + + RAISERROR('Component %s is outdated',10,1,@CurrentComponentFullName); + -- advance to next component + DELETE FROM #StatementsToRun4FRKVersionCheck WHERE SubjectFullPath = @CurrentComponentFullName ; + CONTINUE; + END; - IF OBJECT_ID ('tempdb..#Recompile') IS NOT NULL - DROP TABLE #Recompile; - CREATE TABLE #Recompile( - DBName varchar(200), - ProcName varchar(300), - RecompileFlag varchar(1), - SPSchema varchar(50) - ); + ELSE IF(@CurrentComponentVersionDate > @VersionDate AND @BlitzIsOutdatedComparedToOthers = 0) + BEGIN + SET @BlitzIsOutdatedComparedToOthers = 1; + RAISERROR('Procedure %s is outdated',10,1,@spBlitzFullName); + IF(@MaximumVersionDate IS NULL OR @MaximumVersionDate < @CurrentComponentVersionDate) + BEGIN + SET @MaximumVersionDate = @CurrentComponentVersionDate; + END; + END; + /* Kept for debug purpose: + ELSE + BEGIN + INSERT INTO #BlitzResults( + CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT + 2000 AS CheckID , + 250 AS Priority , + 'Informational' AS FindingsGroup , + 'First Responder kit component ' + @CurrentComponentFullName + ' is at the expected version' AS Finding , + 'https://www.BrentOzar.com/blitz/' AS URL , + 'Version date is: ' + CONVERT(VARCHAR(32),@CurrentComponentVersionDate,121) AS Details + ; + END; + */ + END; - IF OBJECT_ID('tempdb..#DatabaseDefaults') IS NOT NULL - DROP TABLE #DatabaseDefaults; - CREATE TABLE #DatabaseDefaults - ( - name NVARCHAR(128) , - DefaultValue NVARCHAR(200), - CheckID INT, - Priority INT, - Finding VARCHAR(200), - URL VARCHAR(200), - Details NVARCHAR(4000) - ); + -- could be performed differently to minimize computation + DELETE FROM #StatementsToRun4FRKVersionCheck WHERE StatementId = @CurrentStatementId ; + END; +END; - IF OBJECT_ID('tempdb..#DatabaseScopedConfigurationDefaults') IS NOT NULL - DROP TABLE #DatabaseScopedConfigurationDefaults; - CREATE TABLE #DatabaseScopedConfigurationDefaults - (ID INT IDENTITY(1,1), configuration_id INT, [name] NVARCHAR(60), default_value sql_variant, default_value_for_secondary sql_variant, CheckID INT, ); - IF OBJECT_ID('tempdb..#DBCCs') IS NOT NULL - DROP TABLE #DBCCs; - CREATE TABLE #DBCCs - ( - ID INT IDENTITY(1, 1) - PRIMARY KEY , - ParentObject VARCHAR(255) , - Object VARCHAR(255) , - Field VARCHAR(255) , - Value VARCHAR(255) , - DbName NVARCHAR(128) NULL - ); +/*This counts memory dumps and gives min and max date of in view*/ +IF @ProductVersionMajor >= 10 + AND NOT (@ProductVersionMajor = 10.5 AND @ProductVersionMinor < 4297) /* Skip due to crash bug: https://support.microsoft.com/en-us/help/2908087 */ + AND NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 171 ) + BEGIN + IF EXISTS ( SELECT 1 + FROM sys.all_objects + WHERE name = 'dm_server_memory_dumps' ) + BEGIN + IF EXISTS (SELECT * FROM [sys].[dm_server_memory_dumps] WHERE [creation_time] >= DATEADD(YEAR, -1, GETDATE())) - IF OBJECT_ID('tempdb..#LogInfo2012') IS NOT NULL - DROP TABLE #LogInfo2012; - CREATE TABLE #LogInfo2012 - ( - recoveryunitid INT , - FileID SMALLINT , - FileSize BIGINT , - StartOffset BIGINT , - FSeqNo BIGINT , - [Status] TINYINT , - Parity TINYINT , - CreateLSN NUMERIC(38) - ); + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 171) WITH NOWAIT; + + INSERT INTO [#BlitzResults] + ( [CheckID] , + [Priority] , + [FindingsGroup] , + [Finding] , + [URL] , + [Details] ) - IF OBJECT_ID('tempdb..#LogInfo') IS NOT NULL - DROP TABLE #LogInfo; - CREATE TABLE #LogInfo - ( - FileID SMALLINT , - FileSize BIGINT , - StartOffset BIGINT , - FSeqNo BIGINT , - [Status] TINYINT , - Parity TINYINT , - CreateLSN NUMERIC(38) - ); - - IF OBJECT_ID('tempdb..#partdb') IS NOT NULL - DROP TABLE #partdb; - CREATE TABLE #partdb - ( - dbname NVARCHAR(128) , - objectname NVARCHAR(200) , - type_desc NVARCHAR(128) - ); - - IF OBJECT_ID('tempdb..#TraceStatus') IS NOT NULL - DROP TABLE #TraceStatus; - CREATE TABLE #TraceStatus - ( - TraceFlag VARCHAR(10) , - status BIT , - Global BIT , - Session BIT - ); - - IF OBJECT_ID('tempdb..#driveInfo') IS NOT NULL - DROP TABLE #driveInfo; - CREATE TABLE #driveInfo - ( - drive NVARCHAR(2), - logical_volume_name NVARCHAR(36), --Limit is 32 for NTFS, 11 for FAT - available_MB DECIMAL(18, 0), - total_MB DECIMAL(18, 0), - used_percent DECIMAL(18, 2) - ); - - IF OBJECT_ID('tempdb..#dm_exec_query_stats') IS NOT NULL - DROP TABLE #dm_exec_query_stats; - CREATE TABLE #dm_exec_query_stats - ( - [id] [int] NOT NULL - IDENTITY(1, 1) , - [sql_handle] [varbinary](64) NOT NULL , - [statement_start_offset] [int] NOT NULL , - [statement_end_offset] [int] NOT NULL , - [plan_generation_num] [bigint] NOT NULL , - [plan_handle] [varbinary](64) NOT NULL , - [creation_time] [datetime] NOT NULL , - [last_execution_time] [datetime] NOT NULL , - [execution_count] [bigint] NOT NULL , - [total_worker_time] [bigint] NOT NULL , - [last_worker_time] [bigint] NOT NULL , - [min_worker_time] [bigint] NOT NULL , - [max_worker_time] [bigint] NOT NULL , - [total_physical_reads] [bigint] NOT NULL , - [last_physical_reads] [bigint] NOT NULL , - [min_physical_reads] [bigint] NOT NULL , - [max_physical_reads] [bigint] NOT NULL , - [total_logical_writes] [bigint] NOT NULL , - [last_logical_writes] [bigint] NOT NULL , - [min_logical_writes] [bigint] NOT NULL , - [max_logical_writes] [bigint] NOT NULL , - [total_logical_reads] [bigint] NOT NULL , - [last_logical_reads] [bigint] NOT NULL , - [min_logical_reads] [bigint] NOT NULL , - [max_logical_reads] [bigint] NOT NULL , - [total_clr_time] [bigint] NOT NULL , - [last_clr_time] [bigint] NOT NULL , - [min_clr_time] [bigint] NOT NULL , - [max_clr_time] [bigint] NOT NULL , - [total_elapsed_time] [bigint] NOT NULL , - [last_elapsed_time] [bigint] NOT NULL , - [min_elapsed_time] [bigint] NOT NULL , - [max_elapsed_time] [bigint] NOT NULL , - [query_hash] [binary](8) NULL , - [query_plan_hash] [binary](8) NULL , - [query_plan] [xml] NULL , - [query_plan_filtered] [nvarchar](MAX) NULL , - [text] [nvarchar](MAX) COLLATE SQL_Latin1_General_CP1_CI_AS - NULL , - [text_filtered] [nvarchar](MAX) COLLATE SQL_Latin1_General_CP1_CI_AS - NULL - ); - - IF OBJECT_ID('tempdb..#ErrorLog') IS NOT NULL - DROP TABLE #ErrorLog; - CREATE TABLE #ErrorLog - ( - LogDate DATETIME , - ProcessInfo NVARCHAR(20) , - [Text] NVARCHAR(1000) - ); - - IF OBJECT_ID('tempdb..#fnTraceGettable') IS NOT NULL - DROP TABLE #fnTraceGettable; - CREATE TABLE #fnTraceGettable - ( - TextData NVARCHAR(4000) , - DatabaseName NVARCHAR(256) , - EventClass INT , - Severity INT , - StartTime DATETIME , - EndTime DATETIME , - Duration BIGINT , - NTUserName NVARCHAR(256) , - NTDomainName NVARCHAR(256) , - HostName NVARCHAR(256) , - ApplicationName NVARCHAR(256) , - LoginName NVARCHAR(256) , - DBUserName NVARCHAR(256) - ); - - IF OBJECT_ID('tempdb..#Instances') IS NOT NULL - DROP TABLE #Instances; - CREATE TABLE #Instances - ( - Instance_Number NVARCHAR(MAX) , - Instance_Name NVARCHAR(MAX) , - Data_Field NVARCHAR(MAX) - ); - - IF OBJECT_ID('tempdb..#IgnorableWaits') IS NOT NULL - DROP TABLE #IgnorableWaits; - CREATE TABLE #IgnorableWaits (wait_type NVARCHAR(60)); - INSERT INTO #IgnorableWaits VALUES ('BROKER_EVENTHANDLER'); - INSERT INTO #IgnorableWaits VALUES ('BROKER_RECEIVE_WAITFOR'); - INSERT INTO #IgnorableWaits VALUES ('BROKER_TASK_STOP'); - INSERT INTO #IgnorableWaits VALUES ('BROKER_TO_FLUSH'); - INSERT INTO #IgnorableWaits VALUES ('BROKER_TRANSMITTER'); - INSERT INTO #IgnorableWaits VALUES ('CHECKPOINT_QUEUE'); - INSERT INTO #IgnorableWaits VALUES ('CLR_AUTO_EVENT'); - INSERT INTO #IgnorableWaits VALUES ('CLR_MANUAL_EVENT'); - INSERT INTO #IgnorableWaits VALUES ('CLR_SEMAPHORE'); - INSERT INTO #IgnorableWaits VALUES ('DBMIRROR_DBM_EVENT'); - INSERT INTO #IgnorableWaits VALUES ('DBMIRROR_DBM_MUTEX'); - INSERT INTO #IgnorableWaits VALUES ('DBMIRROR_EVENTS_QUEUE'); - INSERT INTO #IgnorableWaits VALUES ('DBMIRROR_WORKER_QUEUE'); - INSERT INTO #IgnorableWaits VALUES ('DBMIRRORING_CMD'); - INSERT INTO #IgnorableWaits VALUES ('DIRTY_PAGE_POLL'); - INSERT INTO #IgnorableWaits VALUES ('DISPATCHER_QUEUE_SEMAPHORE'); - INSERT INTO #IgnorableWaits VALUES ('FT_IFTS_SCHEDULER_IDLE_WAIT'); - INSERT INTO #IgnorableWaits VALUES ('FT_IFTSHC_MUTEX'); - INSERT INTO #IgnorableWaits VALUES ('HADR_CLUSAPI_CALL'); - INSERT INTO #IgnorableWaits VALUES ('HADR_FABRIC_CALLBACK'); - INSERT INTO #IgnorableWaits VALUES ('HADR_FILESTREAM_IOMGR_IOCOMPLETION'); - INSERT INTO #IgnorableWaits VALUES ('HADR_LOGCAPTURE_WAIT'); - INSERT INTO #IgnorableWaits VALUES ('HADR_NOTIFICATION_DEQUEUE'); - INSERT INTO #IgnorableWaits VALUES ('HADR_TIMER_TASK'); - INSERT INTO #IgnorableWaits VALUES ('HADR_WORK_QUEUE'); - INSERT INTO #IgnorableWaits VALUES ('LAZYWRITER_SLEEP'); - INSERT INTO #IgnorableWaits VALUES ('LOGMGR_QUEUE'); - INSERT INTO #IgnorableWaits VALUES ('ONDEMAND_TASK_QUEUE'); - INSERT INTO #IgnorableWaits VALUES ('PARALLEL_REDO_DRAIN_WORKER'); - INSERT INTO #IgnorableWaits VALUES ('PARALLEL_REDO_LOG_CACHE'); - INSERT INTO #IgnorableWaits VALUES ('PARALLEL_REDO_TRAN_LIST'); - INSERT INTO #IgnorableWaits VALUES ('PARALLEL_REDO_WORKER_SYNC'); - INSERT INTO #IgnorableWaits VALUES ('PARALLEL_REDO_WORKER_WAIT_WORK'); - INSERT INTO #IgnorableWaits VALUES ('POPULATE_LOCK_ORDINALS'); - INSERT INTO #IgnorableWaits VALUES ('PREEMPTIVE_HADR_LEASE_MECHANISM'); - INSERT INTO #IgnorableWaits VALUES ('PREEMPTIVE_SP_SERVER_DIAGNOSTICS'); - INSERT INTO #IgnorableWaits VALUES ('QDS_ASYNC_QUEUE'); - INSERT INTO #IgnorableWaits VALUES ('QDS_CLEANUP_STALE_QUERIES_TASK_MAIN_LOOP_SLEEP'); - INSERT INTO #IgnorableWaits VALUES ('QDS_PERSIST_TASK_MAIN_LOOP_SLEEP'); - INSERT INTO #IgnorableWaits VALUES ('QDS_SHUTDOWN_QUEUE'); - INSERT INTO #IgnorableWaits VALUES ('REDO_THREAD_PENDING_WORK'); - INSERT INTO #IgnorableWaits VALUES ('REQUEST_FOR_DEADLOCK_SEARCH'); - INSERT INTO #IgnorableWaits VALUES ('SLEEP_SYSTEMTASK'); - INSERT INTO #IgnorableWaits VALUES ('SLEEP_TASK'); - INSERT INTO #IgnorableWaits VALUES ('SOS_WORK_DISPATCHER'); - INSERT INTO #IgnorableWaits VALUES ('SP_SERVER_DIAGNOSTICS_SLEEP'); - INSERT INTO #IgnorableWaits VALUES ('SQLTRACE_BUFFER_FLUSH'); - INSERT INTO #IgnorableWaits VALUES ('SQLTRACE_INCREMENTAL_FLUSH_SLEEP'); - INSERT INTO #IgnorableWaits VALUES ('UCS_SESSION_REGISTRATION'); - INSERT INTO #IgnorableWaits VALUES ('WAIT_XTP_OFFLINE_CKPT_NEW_LOG'); - INSERT INTO #IgnorableWaits VALUES ('WAITFOR'); - INSERT INTO #IgnorableWaits VALUES ('XE_DISPATCHER_WAIT'); - INSERT INTO #IgnorableWaits VALUES ('XE_LIVE_TARGET_TVF'); - INSERT INTO #IgnorableWaits VALUES ('XE_TIMER_EVENT'); - - IF @Debug IN (1, 2) RAISERROR('Setting @MsSinceWaitsCleared', 0, 1) WITH NOWAIT; - - SELECT @MsSinceWaitsCleared = DATEDIFF(MINUTE, create_date, CURRENT_TIMESTAMP) * 60000.0 - FROM sys.databases - WHERE name = 'tempdb'; - - /* Have they cleared wait stats? Using a 10% fudge factor */ - IF @MsSinceWaitsCleared * .9 > (SELECT MAX(wait_time_ms) FROM sys.dm_os_wait_stats WHERE wait_type IN ('SP_SERVER_DIAGNOSTICS_SLEEP', 'QDS_PERSIST_TASK_MAIN_LOOP_SLEEP', 'REQUEST_FOR_DEADLOCK_SEARCH', 'HADR_FILESTREAM_IOMGR_IOCOMPLETION', 'LAZYWRITER_SLEEP', 'SQLTRACE_INCREMENTAL_FLUSH_SLEEP', 'DIRTY_PAGE_POLL', 'LOGMGR_QUEUE')) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 185) WITH NOWAIT; - - SET @MsSinceWaitsCleared = (SELECT MAX(wait_time_ms) FROM sys.dm_os_wait_stats WHERE wait_type IN ('SP_SERVER_DIAGNOSTICS_SLEEP', 'QDS_PERSIST_TASK_MAIN_LOOP_SLEEP', 'REQUEST_FOR_DEADLOCK_SEARCH', 'HADR_FILESTREAM_IOMGR_IOCOMPLETION', 'LAZYWRITER_SLEEP', 'SQLTRACE_INCREMENTAL_FLUSH_SLEEP', 'DIRTY_PAGE_POLL', 'LOGMGR_QUEUE')); - IF @MsSinceWaitsCleared = 0 SET @MsSinceWaitsCleared = 1; - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - VALUES( 185, - 240, - 'Wait Stats', - 'Wait Stats Have Been Cleared', - 'https://www.brentozar.com/go/waits', - 'Someone ran DBCC SQLPERF to clear sys.dm_os_wait_stats at approximately: ' - + CONVERT(NVARCHAR(100), - DATEADD(MINUTE, (-1. * (@MsSinceWaitsCleared) / 1000. / 60.), GETDATE()), 120)); - END; - - /* @CpuMsSinceWaitsCleared is used for waits stats calculations */ - - IF @Debug IN (1, 2) RAISERROR('Setting @CpuMsSinceWaitsCleared', 0, 1) WITH NOWAIT; - - SELECT @CpuMsSinceWaitsCleared = @MsSinceWaitsCleared * scheduler_count - FROM sys.dm_os_sys_info; - - /* If we're outputting CSV or Markdown, don't bother checking the plan cache because we cannot export plans. */ - IF @OutputType = 'CSV' OR @OutputType = 'MARKDOWN' - SET @CheckProcedureCache = 0; - - /* If we're posting a question on Stack, include background info on the server */ - IF @OutputType = 'MARKDOWN' - SET @CheckServerInfo = 1; - - /* Only run CheckUserDatabaseObjects if there are less than 50 databases. */ - IF @BringThePain = 0 AND 50 <= (SELECT COUNT(*) FROM sys.databases) AND @CheckUserDatabaseObjects = 1 - BEGIN - SET @CheckUserDatabaseObjects = 0; - PRINT 'Running sp_Blitz @CheckUserDatabaseObjects = 1 on a server with 50+ databases may cause temporary problems for the server and/or user.'; - PRINT 'If you''re sure you want to do this, run again with the parameter @BringThePain = 1.'; - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 201 AS CheckID , - 0 AS Priority , - 'Informational' AS FindingsGroup , - '@CheckUserDatabaseObjects Disabled' AS Finding , - 'https://www.BrentOzar.com/blitz/' AS URL , - 'If you want to check 50+ databases, you have to also use @BringThePain = 1.' AS Details; - END; - - /* Sanitize our inputs */ - SELECT - @OutputServerName = QUOTENAME(@OutputServerName), - @OutputDatabaseName = QUOTENAME(@OutputDatabaseName), - @OutputSchemaName = QUOTENAME(@OutputSchemaName), - @OutputTableName = QUOTENAME(@OutputTableName); + SELECT + 171 AS [CheckID] , + 20 AS [Priority] , + 'Reliability' AS [FindingsGroup] , + 'Memory Dumps Have Occurred' AS [Finding] , + 'https://www.brentozar.com/go/dump' AS [URL] , + ( 'That ain''t good. I''ve had ' + + CAST(COUNT(*) AS VARCHAR(100)) + ' memory dumps between ' + + CAST(CAST(MIN([creation_time]) AS DATETIME) AS VARCHAR(100)) + + ' and ' + + CAST(CAST(MAX([creation_time]) AS DATETIME) AS VARCHAR(100)) + + '!' + ) AS [Details] + FROM + [sys].[dm_server_memory_dumps] + WHERE [creation_time] >= DATEADD(year, -1, GETDATE()); - /* Get the major and minor build numbers */ - - IF @Debug IN (1, 2) RAISERROR('Getting version information.', 0, 1) WITH NOWAIT; - - SET @ProductVersion = CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)); - SELECT @ProductVersionMajor = SUBSTRING(@ProductVersion, 1,CHARINDEX('.', @ProductVersion) + 1 ), - @ProductVersionMinor = PARSENAME(CONVERT(varchar(32), @ProductVersion), 2); - - /* - Whew! we're finally done with the setup, and we can start doing checks. - First, let's make sure we're actually supposed to do checks on this server. - The user could have passed in a SkipChecks table that specified to skip ALL - checks on this server, so let's check for that: - */ - IF ( ( SERVERPROPERTY('ServerName') NOT IN ( SELECT ServerName - FROM #SkipChecks - WHERE DatabaseName IS NULL - AND CheckID IS NULL ) ) - OR ( @SkipChecksTable IS NULL ) - ) - BEGIN + END; + END; + END; - /* - Extract DBCC DBINFO data from the server. This data is used for check 2 using - the dbi_LastLogBackupTime field and check 68 using the dbi_LastKnownGood field. - NB: DBCC DBINFO is not available on AWS RDS databases so if the server is RDS - (which will have previously triggered inserting a checkID 223 record) and at - least one of the relevant checks is not being skipped then we can extract the - dbinfo information. - */ - IF NOT EXISTS - ( - SELECT 1/0 - FROM #BlitzResults - WHERE CheckID = 223 AND URL = 'https://aws.amazon.com/rds/sqlserver/' - ) AND NOT EXISTS - ( - SELECT 1/0 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID IN (2, 68) - ) +/*Checks to see if you're on Developer or Evaluation*/ + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 173 ) BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 173) WITH NOWAIT; + + INSERT INTO [#BlitzResults] + ( [CheckID] , + [Priority] , + [FindingsGroup] , + [Finding] , + [URL] , + [Details] ) - IF @Debug IN (1, 2) RAISERROR('Extracting DBCC DBINFO data (used in checks 2 and 68).', 0, 1, 68) WITH NOWAIT; + SELECT + 173 AS [CheckID] , + 200 AS [Priority] , + 'Licensing' AS [FindingsGroup] , + 'Non-Production License' AS [Finding] , + 'https://www.brentozar.com/go/licensing' AS [URL] , + ( 'We''re not the licensing police, but if this is supposed to be a production server, and you''re running ' + + CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) + + ' the good folks at Microsoft might get upset with you. Better start counting those cores.' + ) AS [Details] + WHERE CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) LIKE '%Developer%' + OR CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) LIKE '%Evaluation%'; - EXEC sp_MSforeachdb N'USE [?]; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - INSERT #DBCCs - (ParentObject, - Object, - Field, - Value) - EXEC (''DBCC DBInfo() With TableResults, NO_INFOMSGS''); - UPDATE #DBCCs SET DbName = N''?'' WHERE DbName IS NULL OPTION (RECOMPILE);'; - END + END; - /* - Our very first check! We'll put more comments in this one just to - explain exactly how it works. First, we check to see if we're - supposed to skip CheckID 1 (that's the check we're working on.) - */ - IF NOT EXISTS ( SELECT 1 +/*Checks to see if Buffer Pool Extensions are in use*/ + IF @ProductVersionMajor >= 12 + AND NOT EXISTS ( SELECT 1 FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 1 ) + WHERE DatabaseName IS NULL AND CheckID = 174 ) BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 174) WITH NOWAIT; + + INSERT INTO [#BlitzResults] + ( [CheckID] , + [Priority] , + [FindingsGroup] , + [Finding] , + [URL] , + [Details] ) - /* - Below, we check master.sys.databases looking for databases - that haven't had a backup in the last week. If we find any, - we insert them into #BlitzResults, the temp table that - tracks our server's problems. Note that if the check does - NOT find any problems, we don't save that. We're only - saving the problems, not the successful checks. - */ - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 1) WITH NOWAIT; - - IF SERVERPROPERTY('EngineEdition') <> 8 /* Azure Managed Instances need a special query */ - BEGIN - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 1 AS CheckID , - d.[name] AS DatabaseName , - 1 AS Priority , - 'Backup' AS FindingsGroup , - 'Backups Not Performed Recently' AS Finding , - 'https://www.brentozar.com/go/nobak' AS URL , - 'Last backed up: ' - + COALESCE(CAST(MAX(b.backup_finish_date) AS VARCHAR(25)),'never') AS Details - FROM master.sys.databases d - LEFT OUTER JOIN msdb.dbo.backupset b ON d.name COLLATE SQL_Latin1_General_CP1_CI_AS = b.database_name COLLATE SQL_Latin1_General_CP1_CI_AS - AND b.type = 'D' - AND b.server_name = SERVERPROPERTY('ServerName') /*Backupset ran on current server */ - WHERE d.database_id <> 2 /* Bonus points if you know what that means */ - AND d.state NOT IN(1, 6, 10) /* Not currently offline or restoring, like log shipping databases */ - AND d.is_in_standby = 0 /* Not a log shipping target database */ - AND d.source_database_id IS NULL /* Excludes database snapshots */ - AND d.name NOT IN ( SELECT DISTINCT - DatabaseName - FROM #SkipChecks - WHERE CheckID IS NULL OR CheckID = 1) - /* - The above NOT IN filters out the databases we're not supposed to check. - */ - GROUP BY d.name - HAVING MAX(b.backup_finish_date) <= DATEADD(dd, - -7, GETDATE()) - OR MAX(b.backup_finish_date) IS NULL; - END; - - ELSE /* SERVERPROPERTY('EngineName') must be 8, Azure Managed Instances */ - BEGIN - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 1 AS CheckID , - d.[name] AS DatabaseName , - 1 AS Priority , - 'Backup' AS FindingsGroup , - 'Backups Not Performed Recently' AS Finding , - 'https://www.brentozar.com/go/nobak' AS URL , - 'Last backed up: ' - + COALESCE(CAST(MAX(b.backup_finish_date) AS VARCHAR(25)),'never') AS Details - FROM master.sys.databases d - LEFT OUTER JOIN msdb.dbo.backupset b ON d.name COLLATE SQL_Latin1_General_CP1_CI_AS = b.database_name COLLATE SQL_Latin1_General_CP1_CI_AS - AND b.type = 'D' - WHERE d.database_id <> 2 /* Bonus points if you know what that means */ - AND d.state NOT IN(1, 6, 10) /* Not currently offline or restoring, like log shipping databases */ - AND d.is_in_standby = 0 /* Not a log shipping target database */ - AND d.source_database_id IS NULL /* Excludes database snapshots */ - AND d.name NOT IN ( SELECT DISTINCT - DatabaseName - FROM #SkipChecks - WHERE CheckID IS NULL OR CheckID = 1) - /* - The above NOT IN filters out the databases we're not supposed to check. - */ - GROUP BY d.name - HAVING MAX(b.backup_finish_date) <= DATEADD(dd, - -7, GETDATE()) - OR MAX(b.backup_finish_date) IS NULL; - END; - - - - /* - And there you have it. The rest of this stored procedure works the same - way: it asks: - - Should I skip this check? - - If not, do I find problems? - - Insert the results into #BlitzResults - */ + SELECT + 174 AS [CheckID] , + 200 AS [Priority] , + 'Performance' AS [FindingsGroup] , + 'Buffer Pool Extensions Enabled' AS [Finding] , + 'https://www.brentozar.com/go/bpe' AS [URL] , + ( 'You have Buffer Pool Extensions enabled, and one lives here: ' + + [path] + + '. It''s currently ' + + CASE WHEN [current_size_in_kb] / 1024. / 1024. > 0 + THEN CAST([current_size_in_kb] / 1024. / 1024. AS VARCHAR(100)) + + ' GB' + ELSE CAST([current_size_in_kb] / 1024. AS VARCHAR(100)) + + ' MB' + END + + '. Did you know that BPEs only provide single threaded access 8KB (one page) at a time?' + ) AS [Details] + FROM sys.dm_os_buffer_pool_extension_configuration + WHERE [state_description] <> 'BUFFER POOL EXTENSION DISABLED'; END; - /* - And that's the end of CheckID #1. - - CheckID #2 is a little simpler because it only involves one query, and it's - more typical for queries that people contribute. But keep reading, because - the next check gets more complex again. - */ - - IF NOT EXISTS ( SELECT 1 +/*Check for too many tempdb files*/ + IF NOT EXISTS ( SELECT 1 FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 2 ) + WHERE DatabaseName IS NULL AND CheckID = 175 ) BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 2) WITH NOWAIT; - + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 175) WITH NOWAIT; + INSERT INTO #BlitzResults ( CheckID , DatabaseName , @@ -4280,1876 +5822,2496 @@ AS Details ) SELECT DISTINCT - 2 AS CheckID , - d.name AS DatabaseName , - 1 AS Priority , - 'Backup' AS FindingsGroup , - 'Full Recovery Model w/o Log Backups' AS Finding , - 'https://www.brentozar.com/go/biglogs' AS URL , - ( 'The ' + CAST(CAST((SELECT ((SUM([mf].[size]) * 8.) / 1024.) FROM sys.[master_files] AS [mf] WHERE [mf].[database_id] = d.[database_id] AND [mf].[type_desc] = 'LOG') AS DECIMAL(18,2)) AS VARCHAR(30)) + 'MB log file has not been backed up in the last week.' ) AS Details - FROM master.sys.databases d - LEFT JOIN #DBCCs ll On ll.DbName = d.name And ll.Field = 'dbi_LastLogBackupTime' - WHERE d.recovery_model IN ( 1, 2 ) - AND d.database_id NOT IN ( 2, 3 ) - AND d.source_database_id IS NULL - AND d.state NOT IN(1, 6, 10) /* Not currently offline or restoring, like log shipping databases */ - AND d.is_in_standby = 0 /* Not a log shipping target database */ - AND d.source_database_id IS NULL /* Excludes database snapshots */ - AND d.name NOT IN ( SELECT DISTINCT - DatabaseName - FROM #SkipChecks - WHERE CheckID IS NULL OR CheckID = 2) - AND ( - ( - /* We couldn't get a value from the DBCC DBINFO data so let's check the msdb backup history information */ - [ll].[Value] Is Null - AND NOT EXISTS ( SELECT * - FROM msdb.dbo.backupset b - WHERE d.name COLLATE SQL_Latin1_General_CP1_CI_AS = b.database_name COLLATE SQL_Latin1_General_CP1_CI_AS - AND b.type = 'L' - AND b.backup_finish_date >= DATEADD(dd,-7, GETDATE()) - ) - ) - OR - ( - Convert(datetime,ll.Value,21) < DATEADD(dd,-7, GETDATE()) - ) + 175 AS CheckID , + 'TempDB' AS DatabaseName , + 170 AS Priority , + 'File Configuration' AS FindingsGroup , + 'TempDB Has >16 Data Files' AS Finding , + 'https://www.brentozar.com/go/tempdb' AS URL , + 'Woah, Nelly! TempDB has ' + CAST(COUNT_BIG(*) AS VARCHAR(30)) + '. Did you forget to terminate a loop somewhere?' AS Details + FROM sys.[master_files] AS [mf] + WHERE [mf].[database_id] = 2 AND [mf].[type] = 0 + HAVING COUNT_BIG(*) > 16; + END; - ); + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 176 ) + BEGIN + + IF EXISTS ( SELECT 1 + FROM sys.all_objects + WHERE name = 'dm_xe_sessions' ) + + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 176) WITH NOWAIT; + + INSERT INTO #BlitzResults + ( CheckID , + DatabaseName , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT DISTINCT + 176 AS CheckID , + '' AS DatabaseName , + 200 AS Priority , + 'Monitoring' AS FindingsGroup , + 'Extended Events Hyperextension' AS Finding , + 'https://www.brentozar.com/go/xe' AS URL , + 'Hey big spender, you have ' + CAST(COUNT_BIG(*) AS VARCHAR(30)) + ' Extended Events sessions running. You sure you meant to do that?' AS Details + FROM sys.dm_xe_sessions + WHERE [name] NOT IN + ( 'AlwaysOn_health', + 'system_health', + 'telemetry_xevents', + 'sp_server_diagnostics', + 'sp_server_diagnostics session', + 'hkenginexesession' ) + AND name NOT LIKE '%$A%' + HAVING COUNT_BIG(*) >= 2; + END; + END; + + /*Harmful startup parameter*/ + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 177 ) + BEGIN + + IF EXISTS ( SELECT 1 + FROM sys.all_objects + WHERE name = 'dm_server_registry' ) + + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 177) WITH NOWAIT; + + INSERT INTO #BlitzResults + ( CheckID , + DatabaseName , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT DISTINCT + 177 AS CheckID , + '' AS DatabaseName , + 5 AS Priority , + 'Monitoring' AS FindingsGroup , + 'Disabled Internal Monitoring Features' AS Finding , + 'https://msdn.microsoft.com/en-us/library/ms190737.aspx' AS URL , + 'You have -x as a startup parameter. You should head to the URL and read more about what it does to your system.' AS Details + FROM + [sys].[dm_server_registry] AS [dsr] + WHERE + [dsr].[registry_key] LIKE N'%MSSQLServer\Parameters' + AND [dsr].[value_data] = '-x';; + END; + END; + + + /* Reliability - Dangerous Third Party Modules - 179 */ + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 179 ) + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 179) WITH NOWAIT; + + INSERT INTO [#BlitzResults] + ( [CheckID] , + [Priority] , + [FindingsGroup] , + [Finding] , + [URL] , + [Details] ) + + SELECT + 179 AS [CheckID] , + 5 AS [Priority] , + 'Reliability' AS [FindingsGroup] , + 'Dangerous Third Party Modules' AS [Finding] , + 'https://support.microsoft.com/en-us/kb/2033238' AS [URL] , + ( COALESCE(company, '') + ' - ' + COALESCE(description, '') + ' - ' + COALESCE(name, '') + ' - suspected dangerous third party module is installed.') AS [Details] + FROM sys.dm_os_loaded_modules + WHERE UPPER(name) LIKE UPPER('%\ENTAPI.DLL') OR UPPER(name) LIKE '%MFEBOPK.SYS' /* McAfee VirusScan Enterprise */ + OR UPPER(name) LIKE UPPER('%\HIPI.DLL') OR UPPER(name) LIKE UPPER('%\HcSQL.dll') OR UPPER(name) LIKE UPPER('%\HcApi.dll') OR UPPER(name) LIKE UPPER('%\HcThe.dll') /* McAfee Host Intrusion */ + OR UPPER(name) LIKE UPPER('%\SOPHOS_DETOURED.DLL') OR UPPER(name) LIKE UPPER('%\SOPHOS_DETOURED_x64.DLL') OR UPPER(name) LIKE UPPER('%\SWI_IFSLSP_64.dll') OR UPPER(name) LIKE UPPER('%\SOPHOS~%.dll') /* Sophos AV */ + OR UPPER(name) LIKE UPPER('%\PIOLEDB.DLL') OR UPPER(name) LIKE UPPER('%\PISDK.DLL') /* OSISoft PI data access */ + OR UPPER(name) LIKE UPPER('%ScriptControl%.dll') OR UPPER(name) LIKE UPPER('%umppc%.dll') /* CrowdStrike */ + OR UPPER(name) LIKE UPPER('%perfiCrcPerfMonMgr.DLL') /* Trend Micro OfficeScan */ + OR UPPER(name) LIKE UPPER('%NLEMSQL.SYS') /* NetLib Encryptionizer-Software. */ + OR UPPER(name) LIKE UPPER('%MFETDIK.SYS') /* McAfee Anti-Virus Mini-Firewall */ + OR UPPER(name) LIKE UPPER('%ANTIVIRUS%'); /* To pick up sqlmaggieAntiVirus_64.dll (malware) or anything else labelled AntiVirus */ + /* MS docs link for blacklisted modules: https://learn.microsoft.com/en-us/troubleshoot/sql/performance/performance-consistency-issues-filter-drivers-modules */ END; - /* - CheckID #256 is searching for backups to NUL. - */ + /*Find shrink database tasks*/ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 256 ) + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 180 ) + AND CONVERT(VARCHAR(128), SERVERPROPERTY ('productversion')) LIKE '1%' /* Only run on 2008+ */ BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 180) WITH NOWAIT; + + WITH XMLNAMESPACES ('www.microsoft.com/SqlServer/Dts' AS [dts]) + ,[maintenance_plan_steps] AS ( + SELECT [name] + , [id] -- ID required to link maintenace plan with jobs and jobhistory (sp_Blitz Issue #776) + , CAST(CAST([packagedata] AS VARBINARY(MAX)) AS XML) AS [maintenance_plan_xml] + FROM [msdb].[dbo].[sysssispackages] + WHERE [packagetype] = 6 + ) + INSERT INTO [#BlitzResults] + ( [CheckID] , + [Priority] , + [FindingsGroup] , + [Finding] , + [URL] , + [Details] ) + SELECT + 180 AS [CheckID] , + -- sp_Blitz Issue #776 + -- Job has history and was executed in the last 30 days + CASE WHEN (cast(datediff(dd, substring(cast(sjh.run_date as nvarchar(10)), 1, 4) + '-' + substring(cast(sjh.run_date as nvarchar(10)), 5, 2) + '-' + substring(cast(sjh.run_date as nvarchar(10)), 7, 2), GETDATE()) AS INT) < 30) OR (j.[enabled] = 1 AND ssc.[enabled] = 1 )THEN + 100 + ELSE -- no job history (implicit) AND job not run in the past 30 days AND (Job disabled OR Job Schedule disabled) + 200 + END AS Priority, + 'Performance' AS [FindingsGroup] , + 'Shrink Database Step In Maintenance Plan' AS [Finding] , + 'https://www.brentozar.com/go/autoshrink' AS [URL] , + 'The maintenance plan ' + [mps].[name] + ' has a step to shrink databases in it. Shrinking databases is as outdated as maintenance plans.' + + CASE WHEN COALESCE(ssc.name,'0') != '0' THEN + ' (Schedule: [' + ssc.name + '])' ELSE + '' END AS [Details] + FROM [maintenance_plan_steps] [mps] + CROSS APPLY [maintenance_plan_xml].[nodes]('//dts:Executables/dts:Executable') [t]([c]) + join msdb.dbo.sysmaintplan_subplans as sms + on mps.id = sms.plan_id + JOIN msdb.dbo.sysjobs j + on sms.job_id = j.job_id + LEFT OUTER JOIN msdb.dbo.sysjobsteps AS step + ON j.job_id = step.job_id + LEFT OUTER JOIN msdb.dbo.sysjobschedules AS sjsc + ON j.job_id = sjsc.job_id + LEFT OUTER JOIN msdb.dbo.sysschedules AS ssc + ON sjsc.schedule_id = ssc.schedule_id + AND sjsc.job_id = j.job_id + LEFT OUTER JOIN msdb.dbo.sysjobhistory AS sjh + ON j.job_id = sjh.job_id + AND step.step_id = sjh.step_id + AND sjh.run_date IN (SELECT max(sjh2.run_date) FROM msdb.dbo.sysjobhistory AS sjh2 WHERE sjh2.job_id = j.job_id) -- get the latest entry date + AND sjh.run_time IN (SELECT max(sjh3.run_time) FROM msdb.dbo.sysjobhistory AS sjh3 WHERE sjh3.job_id = j.job_id AND sjh3.run_date = sjh.run_date) -- get the latest entry time + WHERE [c].[value]('(@dts:ObjectName)', 'VARCHAR(128)') = 'Shrink Database Task'; - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 2) WITH NOWAIT; + END; - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT DISTINCT - 256 AS CheckID , - d.name AS DatabaseName, - 1 AS Priority , - 'Backup' AS FindingsGroup , - 'Log Backups to NUL' AS Finding , - 'https://www.brentozar.com/go/nul' AS URL , - N'The transaction log file has been backed up ' + CAST((SELECT count(*) - FROM msdb.dbo.backupset AS b INNER JOIN - msdb.dbo.backupmediafamily AS bmf - ON b.media_set_id = bmf.media_set_id - WHERE b.database_name COLLATE SQL_Latin1_General_CP1_CI_AS = d.name COLLATE SQL_Latin1_General_CP1_CI_AS - AND bmf.physical_device_name = 'NUL' - AND b.type = 'L' - AND b.backup_finish_date >= DATEADD(dd, - -7, GETDATE())) AS NVARCHAR(8)) + ' time(s) to ''NUL'' in the last week, which means the backup does not exist. This breaks point-in-time recovery.' AS Details - FROM master.sys.databases AS d - WHERE d.recovery_model IN ( 1, 2 ) - AND d.database_id NOT IN ( 2, 3 ) - AND d.source_database_id IS NULL - AND d.state NOT IN(1, 6, 10) /* Not currently offline or restoring, like log shipping databases */ - AND d.is_in_standby = 0 /* Not a log shipping target database */ - AND d.source_database_id IS NULL /* Excludes database snapshots */ - --AND d.name NOT IN ( SELECT DISTINCT - -- DatabaseName - -- FROM #SkipChecks - -- WHERE CheckID IS NULL OR CheckID = 2) - AND EXISTS ( SELECT * - FROM msdb.dbo.backupset AS b INNER JOIN - msdb.dbo.backupmediafamily AS bmf - ON b.media_set_id = bmf.media_set_id - WHERE d.name COLLATE SQL_Latin1_General_CP1_CI_AS = b.database_name COLLATE SQL_Latin1_General_CP1_CI_AS - AND bmf.physical_device_name = 'NUL' - AND b.type = 'L' - AND b.backup_finish_date >= DATEADD(dd, - -7, GETDATE()) ); - END; + /*Find repetitive maintenance tasks*/ + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 181 ) + AND CONVERT(VARCHAR(128), SERVERPROPERTY ('productversion')) LIKE '1%' /* Only run on 2008+ */ + BEGIN + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 181) WITH NOWAIT; - /* - Next up, we've got CheckID 8. (These don't have to go in order.) This one - won't work on SQL Server 2005 because it relies on a new DMV that didn't - exist prior to SQL Server 2008. This means we have to check the SQL Server - version first, then build a dynamic string with the query we want to run: - */ + WITH XMLNAMESPACES ('www.microsoft.com/SqlServer/Dts' AS [dts]) + ,[maintenance_plan_steps] AS ( + SELECT [name] + , CAST(CAST([packagedata] AS VARBINARY(MAX)) AS XML) AS [maintenance_plan_xml] + FROM [msdb].[dbo].[sysssispackages] + WHERE [packagetype] = 6 + ), [maintenance_plan_table] AS ( + SELECT [mps].[name] + ,[c].[value]('(@dts:ObjectName)', 'NVARCHAR(128)') AS [step_name] + FROM [maintenance_plan_steps] [mps] + CROSS APPLY [maintenance_plan_xml].[nodes]('//dts:Executables/dts:Executable') [t]([c]) + ), [mp_steps_pretty] AS (SELECT DISTINCT [m1].[name] , + STUFF((SELECT N', ' + [m2].[step_name] FROM [maintenance_plan_table] AS [m2] WHERE [m1].[name] = [m2].[name] + FOR XML PATH(N'')), 1, 2, N'') AS [maintenance_plan_steps] + FROM [maintenance_plan_table] AS [m1]) + + INSERT INTO [#BlitzResults] + ( [CheckID] , + [Priority] , + [FindingsGroup] , + [Finding] , + [URL] , + [Details] ) + + SELECT + 181 AS [CheckID] , + 100 AS [Priority] , + 'Performance' AS [FindingsGroup] , + 'Repetitive Steps In Maintenance Plans' AS [Finding] , + 'https://ola.hallengren.com/' AS [URL] , + 'The maintenance plan ' + [m].[name] + ' is doing repetitive work on indexes and statistics. Perhaps it''s time to try something more modern?' AS [Details] + FROM [mp_steps_pretty] m + WHERE m.[maintenance_plan_steps] LIKE '%Rebuild%Reorganize%' + OR m.[maintenance_plan_steps] LIKE '%Rebuild%Update%'; - IF NOT EXISTS ( SELECT 1 + END; + + + /* Reliability - No Failover Cluster Nodes Available - 184 */ + IF NOT EXISTS ( SELECT 1 FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 8 ) + WHERE DatabaseName IS NULL AND CheckID = 184 ) + AND CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)) NOT LIKE '10%' + AND CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)) NOT LIKE '9%' BEGIN - IF @@VERSION NOT LIKE '%Microsoft SQL Server 2000%' - AND @@VERSION NOT LIKE '%Microsoft SQL Server 2005%' - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 8) WITH NOWAIT; - - SET @StringToExecute = 'INSERT INTO #BlitzResults - (CheckID, Priority, - FindingsGroup, - Finding, URL, - Details) - SELECT 8 AS CheckID, - 230 AS Priority, - ''Security'' AS FindingsGroup, - ''Server Audits Running'' AS Finding, - ''https://www.brentozar.com/go/audits'' AS URL, - (''SQL Server built-in audit functionality is being used by server audit: '' + [name]) AS Details FROM sys.dm_server_audit_status OPTION (RECOMPILE);'; + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 184) WITH NOWAIT; + SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) + SELECT TOP 1 + 184 AS CheckID , + 20 AS Priority , + ''Reliability'' AS FindingsGroup , + ''No Failover Cluster Nodes Available'' AS Finding , + ''https://www.brentozar.com/go/node'' AS URL , + ''There are no failover cluster nodes available if the active node fails'' AS Details + FROM ( + SELECT SUM(CASE WHEN [status] = 0 AND [is_current_owner] = 0 THEN 1 ELSE 0 END) AS [available_nodes] + FROM sys.dm_os_cluster_nodes + ) a + WHERE [available_nodes] < 1 OPTION (RECOMPILE)'; + IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; EXECUTE(@StringToExecute); - END; END; - /* - But what if you need to run a query in every individual database? - Hop down to the @CheckUserDatabaseObjects section. + /* Reliability - TempDB File Error */ + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 191 ) + AND (SELECT COUNT(*) FROM sys.master_files WHERE database_id = 2) <> (SELECT COUNT(*) FROM tempdb.sys.database_files) + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 191) WITH NOWAIT + + INSERT INTO [#BlitzResults] + ( [CheckID] , + [Priority] , + [FindingsGroup] , + [Finding] , + [URL] , + [Details] ) + + SELECT + 191 AS [CheckID] , + 50 AS [Priority] , + 'Reliability' AS [FindingsGroup] , + 'TempDB File Error' AS [Finding] , + 'https://www.brentozar.com/go/tempdboops' AS [URL] , + 'Mismatch between the number of TempDB files in sys.master_files versus tempdb.sys.database_files' AS [Details]; + END; - And that's the basic idea! You can read through the rest of the - checks if you like - some more exciting stuff happens closer to the - end of the stored proc, where we start doing things like checking - the plan cache, but those aren't as cleanly commented. +/*Perf - Odd number of cores in a socket*/ + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL + AND CheckID = 198 ) + AND EXISTS ( SELECT 1 + FROM sys.dm_os_schedulers + WHERE is_online = 1 + AND scheduler_id < 255 + AND parent_node_id < 64 + GROUP BY parent_node_id, + is_online + HAVING ( COUNT(cpu_id) + 2 ) % 2 = 1 ) + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 198) WITH NOWAIT + + INSERT INTO #BlitzResults + ( + CheckID, + DatabaseName, + Priority, + FindingsGroup, + Finding, + URL, + Details + ) + SELECT 198 AS CheckID, + NULL AS DatabaseName, + 10 AS Priority, + 'Performance' AS FindingsGroup, + 'CPU w/Odd Number of Cores' AS Finding, + 'https://www.brentozar.com/go/oddity' AS URL, + 'Node ' + CONVERT(VARCHAR(10), parent_node_id) + ' has ' + CONVERT(VARCHAR(10), COUNT(cpu_id)) + + CASE WHEN COUNT(cpu_id) = 1 THEN ' core assigned to it. This is a really bad NUMA configuration.' + ELSE ' cores assigned to it. This is a really bad NUMA configuration.' + END AS Details + FROM sys.dm_os_schedulers + WHERE is_online = 1 + AND scheduler_id < 255 + AND parent_node_id < 64 + AND EXISTS ( + SELECT 1 + FROM ( SELECT memory_node_id, SUM(online_scheduler_count) AS schedulers + FROM sys.dm_os_nodes + WHERE memory_node_id < 64 + GROUP BY memory_node_id ) AS nodes + HAVING MIN(nodes.schedulers) <> MAX(nodes.schedulers) + ) + GROUP BY parent_node_id, + is_online + HAVING ( COUNT(cpu_id) + 2 ) % 2 = 1; + + END; - If you'd like to contribute your own check, use one of the check - formats shown above and email it to Help@BrentOzar.com. You don't - have to pick a CheckID or a link - we'll take care of that when we - test and publish the code. Thanks! - */ +/*Begin: checking default trace for odd DBCC activity*/ + + --Grab relevant event data + IF @TraceFileIssue = 0 + BEGIN + SELECT UPPER( + REPLACE( + SUBSTRING(CONVERT(NVARCHAR(MAX), t.TextData), 0, + ISNULL( + NULLIF( + CHARINDEX('(', CONVERT(NVARCHAR(MAX), t.TextData)), + 0), + LEN(CONVERT(NVARCHAR(MAX), t.TextData)) + 1 )) --This replaces everything up to an open paren, if one exists. + , SUBSTRING(CONVERT(NVARCHAR(MAX), t.TextData), + ISNULL( + NULLIF( + CHARINDEX(' WITH ',CONVERT(NVARCHAR(MAX), t.TextData)) + , 0), + LEN(CONVERT(NVARCHAR(MAX), t.TextData)) + 1), + LEN(CONVERT(NVARCHAR(MAX), t.TextData)) + 1 ) + , '') --This replaces any optional WITH clause to a DBCC command, like tableresults. + ) AS [dbcc_event_trunc_upper], + UPPER( + REPLACE( + CONVERT(NVARCHAR(MAX), t.TextData), SUBSTRING(CONVERT(NVARCHAR(MAX), t.TextData), + ISNULL( + NULLIF( + CHARINDEX(' WITH ',CONVERT(NVARCHAR(MAX), t.TextData)) + , 0), + LEN(CONVERT(NVARCHAR(MAX), t.TextData)) + 1), + LEN(CONVERT(NVARCHAR(MAX), t.TextData)) + 1 ), '')) AS [dbcc_event_full_upper], + MIN(t.StartTime) OVER (PARTITION BY CONVERT(NVARCHAR(128), t.TextData)) AS min_start_time, + MAX(t.StartTime) OVER (PARTITION BY CONVERT(NVARCHAR(128), t.TextData)) AS max_start_time, + t.NTUserName AS [nt_user_name], + t.NTDomainName AS [nt_domain_name], + t.HostName AS [host_name], + t.ApplicationName AS [application_name], + t.LoginName [login_name], + t.DBUserName AS [db_user_name] + INTO #dbcc_events_from_trace + FROM #fnTraceGettable AS t + WHERE t.EventClass = 116 + OPTION(RECOMPILE) + END; - IF NOT EXISTS ( SELECT 1 + /*Overall count of DBCC events excluding silly stuff*/ + IF NOT EXISTS ( SELECT 1 FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 93 ) + WHERE DatabaseName IS NULL AND CheckID = 203 ) + AND @TraceFileIssue = 0 BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 93) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT - 93 AS CheckID , - 1 AS Priority , - 'Backup' AS FindingsGroup , - 'Backing Up to Same Drive Where Databases Reside' AS Finding , - 'https://www.brentozar.com/go/backup' AS URL , - CAST(COUNT(1) AS VARCHAR(50)) + ' backups done on drive ' - + UPPER(LEFT(bmf.physical_device_name, 3)) - + ' in the last two weeks, where database files also live. This represents a serious risk if that array fails.' Details - FROM msdb.dbo.backupmediafamily AS bmf - INNER JOIN msdb.dbo.backupset AS bs ON bmf.media_set_id = bs.media_set_id - AND bs.backup_start_date >= ( DATEADD(dd, - -14, GETDATE()) ) - /* Filter out databases that were recently restored: */ - LEFT OUTER JOIN msdb.dbo.restorehistory rh ON bs.database_name = rh.destination_database_name AND rh.restore_date > DATEADD(dd, -14, GETDATE()) - WHERE UPPER(LEFT(bmf.physical_device_name, 3)) <> 'HTT' AND - bmf.physical_device_name NOT LIKE '\\%' AND -- GitHub Issue #2141 - @IsWindowsOperatingSystem = 1 AND -- GitHub Issue #1995 - UPPER(LEFT(bmf.physical_device_name COLLATE SQL_Latin1_General_CP1_CI_AS, 3)) IN ( - SELECT DISTINCT - UPPER(LEFT(mf.physical_name COLLATE SQL_Latin1_General_CP1_CI_AS, 3)) - FROM sys.master_files AS mf - WHERE mf.database_id <> 2 ) - AND rh.destination_database_name IS NULL - GROUP BY UPPER(LEFT(bmf.physical_device_name, 3)); + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 203) WITH NOWAIT + + INSERT INTO [#BlitzResults] + ( [CheckID] , + [Priority] , + [FindingsGroup] , + [Finding] , + [URL] , + [Details] ) + SELECT 203 AS CheckID , + 50 AS Priority , + 'DBCC Events' AS FindingsGroup , + 'Overall Events' AS Finding , + 'https://www.BrentOzar.com/go/dbcc' AS URL , + CAST(COUNT(*) AS NVARCHAR(100)) + ' DBCC events have taken place between ' + CONVERT(NVARCHAR(30), MIN(d.min_start_time)) + ' and ' + CONVERT(NVARCHAR(30), MAX(d.max_start_time)) + + '. This does not include CHECKDB and other usually benign DBCC events.' + AS Details + FROM #dbcc_events_from_trace d + /* This WHERE clause below looks horrible, but it's because users can run stuff like + DBCC LOGINFO + with lots of spaces (or carriage returns, or comments) in between the DBCC and the + command they're trying to run. See Github issues 1062, 1074, 1075. + */ + WHERE d.dbcc_event_full_upper NOT LIKE '%DBCC%ADDINSTANCE%' + AND d.dbcc_event_full_upper NOT LIKE '%DBCC%AUTOPILOT%' + AND d.dbcc_event_full_upper NOT LIKE '%DBCC%CHECKALLOC%' + AND d.dbcc_event_full_upper NOT LIKE '%DBCC%CHECKCATALOG%' + AND d.dbcc_event_full_upper NOT LIKE '%DBCC%CHECKCONSTRAINTS%' + AND d.dbcc_event_full_upper NOT LIKE '%DBCC%CHECKDB%' + AND d.dbcc_event_full_upper NOT LIKE '%DBCC%CHECKFILEGROUP%' + AND d.dbcc_event_full_upper NOT LIKE '%DBCC%CHECKIDENT%' + AND d.dbcc_event_full_upper NOT LIKE '%DBCC%CHECKPRIMARYFILE%' + AND d.dbcc_event_full_upper NOT LIKE '%DBCC%CHECKTABLE%' + AND d.dbcc_event_full_upper NOT LIKE '%DBCC%CLEANTABLE%' + AND d.dbcc_event_full_upper NOT LIKE '%DBCC%DBINFO%' + AND d.dbcc_event_full_upper NOT LIKE '%DBCC%ERRORLOG%' + AND d.dbcc_event_full_upper NOT LIKE '%DBCC%INCREMENTINSTANCE%' + AND d.dbcc_event_full_upper NOT LIKE '%DBCC%INPUTBUFFER%' + AND d.dbcc_event_full_upper NOT LIKE '%DBCC%LOGINFO%' + AND d.dbcc_event_full_upper NOT LIKE '%DBCC%OPENTRAN%' + AND d.dbcc_event_full_upper NOT LIKE '%DBCC%SETINSTANCE%' + AND d.dbcc_event_full_upper NOT LIKE '%DBCC%SHOWFILESTATS%' + AND d.dbcc_event_full_upper NOT LIKE '%DBCC%SHOW_STATISTICS%' + AND d.dbcc_event_full_upper NOT LIKE '%DBCC%SQLPERF%NETSTATS%' + AND d.dbcc_event_full_upper NOT LIKE '%DBCC%SQLPERF%LOGSPACE%' + AND d.dbcc_event_full_upper NOT LIKE '%DBCC%TRACEON%' + AND d.dbcc_event_full_upper NOT LIKE '%DBCC%TRACEOFF%' + AND d.dbcc_event_full_upper NOT LIKE '%DBCC%TRACESTATUS%' + AND d.dbcc_event_full_upper NOT LIKE '%DBCC%USEROPTIONS%' + AND d.application_name NOT LIKE 'Critical Care(R) Collector' + AND d.application_name NOT LIKE '%Red Gate Software Ltd SQL Prompt%' + AND d.application_name NOT LIKE '%Spotlight Diagnostic Server%' + AND d.application_name NOT LIKE '%SQL Diagnostic Manager%' + AND d.application_name NOT LIKE 'SQL Server Checkup%' + AND d.application_name NOT LIKE '%Sentry%' + AND d.application_name NOT LIKE '%LiteSpeed%' + AND d.application_name NOT LIKE '%SQL Monitor - Monitoring%' + + + HAVING COUNT(*) > 0; + + END; + + /*Check for someone running drop clean buffers*/ + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 207 ) + AND @TraceFileIssue = 0 + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 207) WITH NOWAIT + + INSERT INTO [#BlitzResults] + ( [CheckID] , + [Priority] , + [FindingsGroup] , + [Finding] , + [URL] , + [Details] ) + SELECT 207 AS CheckID , + 10 AS Priority , + 'Performance' AS FindingsGroup , + 'DBCC DROPCLEANBUFFERS Ran Recently' AS Finding , + 'https://www.BrentOzar.com/go/dbcc' AS URL , + 'The user ' + COALESCE(d.nt_user_name, d.login_name) + ' has run DBCC DROPCLEANBUFFERS ' + CAST(COUNT(*) AS NVARCHAR(100)) + ' times between ' + CONVERT(NVARCHAR(30), MIN(d.min_start_time)) + ' and ' + CONVERT(NVARCHAR(30), MAX(d.max_start_time)) + + '. If this is a production box, know that you''re clearing all data out of memory when this happens. What kind of monster would do that?' + AS Details + FROM #dbcc_events_from_trace d + WHERE d.dbcc_event_full_upper = N'DBCC DROPCLEANBUFFERS' + GROUP BY COALESCE(d.nt_user_name, d.login_name) + HAVING COUNT(*) > 0; + END; - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 119 ) - AND EXISTS ( SELECT * - FROM sys.all_objects o - WHERE o.name = 'dm_database_encryption_keys' ) - BEGIN + /*Check for someone running free proc cache*/ + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 208 ) + AND @TraceFileIssue = 0 + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 208) WITH NOWAIT + + INSERT INTO [#BlitzResults] + ( [CheckID] , + [Priority] , + [FindingsGroup] , + [Finding] , + [URL] , + [Details] ) + SELECT 208 AS CheckID , + 10 AS Priority , + 'DBCC Events' AS FindingsGroup , + 'DBCC FREEPROCCACHE Ran Recently' AS Finding , + 'https://www.BrentOzar.com/go/dbcc' AS URL , + 'The user ' + COALESCE(d.nt_user_name, d.login_name) + ' has run DBCC FREEPROCCACHE ' + CAST(COUNT(*) AS NVARCHAR(100)) + ' times between ' + CONVERT(NVARCHAR(30), MIN(d.min_start_time)) + ' and ' + CONVERT(NVARCHAR(30), MAX(d.max_start_time)) + + '. This has bad idea jeans written all over its butt, like most other bad idea jeans.' + AS Details + FROM #dbcc_events_from_trace d + WHERE d.dbcc_event_full_upper = N'DBCC FREEPROCCACHE' + GROUP BY COALESCE(d.nt_user_name, d.login_name) + HAVING COUNT(*) > 0; - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 119) WITH NOWAIT; + END; + + /*Check for someone clearing wait stats*/ + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 205 ) + AND @TraceFileIssue = 0 + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 205) WITH NOWAIT + + INSERT INTO [#BlitzResults] + ( [CheckID] , + [Priority] , + [FindingsGroup] , + [Finding] , + [URL] , + [Details] ) + SELECT 205 AS CheckID , + 50 AS Priority , + 'Performance' AS FindingsGroup , + 'Wait Stats Cleared Recently' AS Finding , + 'https://www.BrentOzar.com/go/dbcc' AS URL , + 'The user ' + COALESCE(d.nt_user_name, d.login_name) + ' has run DBCC SQLPERF(''SYS.DM_OS_WAIT_STATS'',CLEAR) ' + CAST(COUNT(*) AS NVARCHAR(100)) + ' times between ' + CONVERT(NVARCHAR(30), MIN(d.min_start_time)) + ' and ' + CONVERT(NVARCHAR(30), MAX(d.max_start_time)) + + '. Why are you clearing wait stats? What are you hiding?' + AS Details + FROM #dbcc_events_from_trace d + WHERE d.dbcc_event_full_upper = N'DBCC SQLPERF(''SYS.DM_OS_WAIT_STATS'',CLEAR)' + GROUP BY COALESCE(d.nt_user_name, d.login_name) + HAVING COUNT(*) > 0; + + END; + + /*Check for someone writing to pages. Yeah, right?*/ + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 209 ) + AND @TraceFileIssue = 0 + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 209) WITH NOWAIT + + INSERT INTO [#BlitzResults] + ( [CheckID] , + [Priority] , + [FindingsGroup] , + [Finding] , + [URL] , + [Details] ) + SELECT 209 AS CheckID , + 10 AS Priority , + 'Reliability' AS FindingsGroup , + 'DBCC WRITEPAGE Used Recently' AS Finding , + 'https://www.BrentOzar.com/go/dbcc' AS URL , + 'The user ' + COALESCE(d.nt_user_name, d.login_name) + ' has run DBCC WRITEPAGE ' + CAST(COUNT(*) AS NVARCHAR(100)) + ' times between ' + CONVERT(NVARCHAR(30), MIN(d.min_start_time)) + ' and ' + CONVERT(NVARCHAR(30), MAX(d.max_start_time)) + + '. So, uh, are they trying to fix corruption, or cause corruption?' + AS Details + FROM #dbcc_events_from_trace d + WHERE d.dbcc_event_trunc_upper = N'DBCC WRITEPAGE' + GROUP BY COALESCE(d.nt_user_name, d.login_name) + HAVING COUNT(*) > 0; - SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, DatabaseName, URL, Details) - SELECT 119 AS CheckID, - 1 AS Priority, - ''Backup'' AS FindingsGroup, - ''TDE Certificate Not Backed Up Recently'' AS Finding, - db_name(dek.database_id) AS DatabaseName, - ''https://www.brentozar.com/go/tde'' AS URL, - ''The certificate '' + c.name + '' is used to encrypt database '' + db_name(dek.database_id) + ''. Last backup date: '' + COALESCE(CAST(c.pvt_key_last_backup_date AS VARCHAR(100)), ''Never'') AS Details - FROM master.sys.certificates c INNER JOIN sys.dm_database_encryption_keys dek ON c.thumbprint = dek.encryptor_thumbprint - WHERE pvt_key_last_backup_date IS NULL OR pvt_key_last_backup_date <= DATEADD(dd, -30, GETDATE()) OPTION (RECOMPILE);'; - - IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; - IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; - - EXECUTE(@StringToExecute); END; - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 202 ) - AND EXISTS ( SELECT * - FROM sys.all_columns c - WHERE c.name = 'pvt_key_last_backup_date' ) - AND EXISTS ( SELECT * - FROM msdb.INFORMATION_SCHEMA.COLUMNS c - WHERE c.TABLE_NAME = 'backupset' AND c.COLUMN_NAME = 'encryptor_thumbprint' ) - BEGIN + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 210 ) + AND @TraceFileIssue = 0 + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 210) WITH NOWAIT + + INSERT INTO [#BlitzResults] + ( [CheckID] , + [Priority] , + [FindingsGroup] , + [Finding] , + [URL] , + [Details] ) - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 202) WITH NOWAIT; + SELECT 210 AS CheckID , + 10 AS Priority , + 'Performance' AS FindingsGroup , + 'DBCC SHRINK% Ran Recently' AS Finding , + 'https://www.BrentOzar.com/go/dbcc' AS URL , + 'The user ' + COALESCE(d.nt_user_name, d.login_name) + ' has run file shrinks ' + CAST(COUNT(*) AS NVARCHAR(100)) + ' times between ' + CONVERT(NVARCHAR(30), MIN(d.min_start_time)) + ' and ' + CONVERT(NVARCHAR(30), MAX(d.max_start_time)) + + '. So, uh, are they trying to cause bad performance on purpose?' + AS Details + FROM #dbcc_events_from_trace d + WHERE d.dbcc_event_trunc_upper LIKE N'DBCC SHRINK%' + GROUP BY COALESCE(d.nt_user_name, d.login_name) + HAVING COUNT(*) > 0; - SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) - SELECT DISTINCT 202 AS CheckID, - 1 AS Priority, - ''Backup'' AS FindingsGroup, - ''Encryption Certificate Not Backed Up Recently'' AS Finding, - ''https://www.brentozar.com/go/tde'' AS URL, - ''The certificate '' + c.name + '' is used to encrypt database backups. Last backup date: '' + COALESCE(CAST(c.pvt_key_last_backup_date AS VARCHAR(100)), ''Never'') AS Details - FROM sys.certificates c - INNER JOIN msdb.dbo.backupset bs ON c.thumbprint = bs.encryptor_thumbprint - WHERE pvt_key_last_backup_date IS NULL OR pvt_key_last_backup_date <= DATEADD(dd, -30, GETDATE()) OPTION (RECOMPILE);'; - - IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; - IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; - - EXECUTE(@StringToExecute); END; - IF NOT EXISTS ( SELECT 1 +/*End: checking default trace for odd DBCC activity*/ + + /*Begin check for autoshrink events*/ + + IF NOT EXISTS ( SELECT 1 FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 3 ) + WHERE DatabaseName IS NULL AND CheckID = 206 ) + AND @TraceFileIssue = 0 BEGIN - IF DATEADD(dd, -60, GETDATE()) > (SELECT TOP 1 backup_start_date FROM msdb.dbo.backupset ORDER BY backup_start_date) + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 206) WITH NOWAIT + + INSERT INTO [#BlitzResults] + ( [CheckID] , + [Priority] , + [FindingsGroup] , + [Finding] , + [URL] , + [Details] ) - BEGIN + SELECT 206 AS CheckID , + 10 AS Priority , + 'Performance' AS FindingsGroup , + 'Auto-Shrink Ran Recently' AS Finding , + '' AS URL , + N'The database ' + QUOTENAME(t.DatabaseName) + N' has had ' + + CONVERT(NVARCHAR(10), COUNT(*)) + + N' auto shrink events between ' + + CONVERT(NVARCHAR(30), MIN(t.StartTime)) + ' and ' + CONVERT(NVARCHAR(30), MAX(t.StartTime)) + + ' that lasted on average ' + + CONVERT(NVARCHAR(10), AVG(DATEDIFF(SECOND, t.StartTime, t.EndTime))) + + ' seconds.' AS Details + FROM #fnTraceGettable AS t + WHERE t.EventClass IN (94, 95) + GROUP BY t.DatabaseName + HAVING AVG(DATEDIFF(SECOND, t.StartTime, t.EndTime)) > 5; + + END; - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 3) WITH NOWAIT; + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 215 ) + AND @TraceFileIssue = 0 + AND EXISTS (SELECT * FROM sys.all_columns WHERE name = 'database_id' AND object_id = OBJECT_ID('sys.dm_exec_sessions')) + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 215) WITH NOWAIT + + SET @StringToExecute = 'INSERT INTO [#BlitzResults] + ( [CheckID] , + [Priority] , + [FindingsGroup] , + [Finding] , + [DatabaseName] , + [URL] , + [Details] ) - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT TOP 1 - 3 AS CheckID , - 'msdb' , - 200 AS Priority , - 'Backup' AS FindingsGroup , - 'MSDB Backup History Not Purged' AS Finding , - 'https://www.brentozar.com/go/history' AS URL , - ( 'Database backup history retained back to ' - + CAST(bs.backup_start_date AS VARCHAR(20)) ) AS Details - FROM msdb.dbo.backupset bs - LEFT OUTER JOIN msdb.dbo.restorehistory rh ON bs.database_name = rh.destination_database_name - WHERE rh.destination_database_name IS NULL - ORDER BY bs.backup_start_date ASC; - END; - END; + SELECT 215 AS CheckID , + 100 AS Priority , + ''Performance'' AS FindingsGroup , + ''Implicit Transactions'' AS Finding , + DB_NAME(s.database_id) AS DatabaseName, + ''https://www.brentozar.com/go/ImplicitTransactions/'' AS URL , + N''The database '' + + DB_NAME(s.database_id) + + '' has '' + + CONVERT(NVARCHAR(20), COUNT_BIG(*)) + + '' open implicit transactions with an oldest begin time of '' + + CONVERT(NVARCHAR(30), MIN(tat.transaction_begin_time)) + + '' Run sp_BlitzWho and check the is_implicit_transaction column to see the culprits.'' AS details + FROM sys.dm_tran_active_transactions AS tat + LEFT JOIN sys.dm_tran_session_transactions AS tst + ON tst.transaction_id = tat.transaction_id + LEFT JOIN sys.dm_exec_sessions AS s + ON s.session_id = tst.session_id + WHERE tat.name = ''implicit_transaction'' + GROUP BY DB_NAME(s.database_id), transaction_type, transaction_state;'; + + + IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; + IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; + + EXECUTE(@StringToExecute); + + + + END; + IF NOT EXISTS ( SELECT 1 FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 186 ) + WHERE DatabaseName IS NULL AND CheckID = 221 ) BEGIN - IF DATEADD(dd, -2, GETDATE()) < (SELECT TOP 1 backup_start_date FROM msdb.dbo.backupset ORDER BY backup_start_date) - - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 186) WITH NOWAIT; - + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 221) WITH NOWAIT; + + WITH reboot_airhorn + AS + ( + SELECT create_date + FROM sys.databases + WHERE database_id = 2 + UNION ALL + SELECT CAST(DATEADD(SECOND, ( ms_ticks / 1000 ) * ( -1 ), GETDATE()) AS DATETIME) + FROM sys.dm_os_sys_info + ) INSERT INTO #BlitzResults ( CheckID , - DatabaseName , Priority , FindingsGroup , Finding , URL , Details - ) - SELECT TOP 1 - 186 AS CheckID , - 'msdb' , - 200 AS Priority , - 'Backup' AS FindingsGroup , - 'MSDB Backup History Purged Too Frequently' AS Finding , - 'https://www.brentozar.com/go/history' AS URL , - ( 'Database backup history only retained back to ' - + CAST(bs.backup_start_date AS VARCHAR(20)) ) AS Details - FROM msdb.dbo.backupset bs - ORDER BY backup_start_date ASC; - END; + ) + SELECT 221 AS CheckID, + 10 AS Priority, + 'Reliability' AS FindingsGroup, + 'Server restarted in last 24 hours' AS Finding, + '' AS URL, + 'Surprise! Your server was last restarted on: ' + CONVERT(VARCHAR(30), MAX(reboot_airhorn.create_date)) AS details + FROM reboot_airhorn + HAVING MAX(reboot_airhorn.create_date) >= DATEADD(HOUR, -24, GETDATE()); + + END; IF NOT EXISTS ( SELECT 1 FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 178 ) - AND EXISTS (SELECT * - FROM msdb.dbo.backupset bs - WHERE bs.type = 'D' - AND bs.backup_size >= 50000000000 /* At least 50GB */ - AND DATEDIFF(SECOND, bs.backup_start_date, bs.backup_finish_date) <= 60 /* Backup took less than 60 seconds */ - AND bs.backup_finish_date >= DATEADD(DAY, -14, GETDATE()) /* In the last 2 weeks */) + WHERE DatabaseName IS NULL AND CheckID = 229 ) + AND CAST(SERVERPROPERTY('Edition') AS NVARCHAR(4000)) LIKE '%Evaluation%' BEGIN - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 178) WITH NOWAIT; + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 229) WITH NOWAIT; + + INSERT INTO #BlitzResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT 229 AS CheckID, + 1 AS Priority, + 'Reliability' AS FindingsGroup, + 'Evaluation Edition' AS Finding, + 'https://www.BrentOzar.com/go/workgroup' AS URL, + 'This server will stop working on: ' + CAST(CONVERT(DATETIME, DATEADD(DD, 180, create_date), 102) AS VARCHAR(100)) AS details + FROM sys.server_principals + WHERE sid = 0x010100000000000512000000; - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 178 AS CheckID , - 200 AS Priority , - 'Performance' AS FindingsGroup , - 'Snapshot Backups Occurring' AS Finding , - 'https://www.brentozar.com/go/snaps' AS URL , - ( CAST(COUNT(*) AS VARCHAR(20)) + ' snapshot-looking backups have occurred in the last two weeks, indicating that IO may be freezing up.') AS Details - FROM msdb.dbo.backupset bs - WHERE bs.type = 'D' - AND bs.backup_size >= 50000000000 /* At least 50GB */ - AND DATEDIFF(SECOND, bs.backup_start_date, bs.backup_finish_date) <= 60 /* Backup took less than 60 seconds */ - AND bs.backup_finish_date >= DATEADD(DAY, -14, GETDATE()); /* In the last 2 weeks */ END; + + + + IF NOT EXISTS ( SELECT 1 FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 236 ) + WHERE DatabaseName IS NULL AND CheckID = 233 ) BEGIN - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 236) WITH NOWAIT; + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 233) WITH NOWAIT; - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT TOP 1 236 AS CheckID , - 50 AS Priority , - 'Performance' AS FindingsGroup , - 'Snapshotting Too Many Databases' AS Finding , - 'https://www.brentozar.com/go/toomanysnaps' AS URL , - ( CAST(SUM(1) AS VARCHAR(20)) + ' databases snapshotted at once in the last two weeks, indicating that IO may be freezing up. Microsoft does not recommend VSS snaps for 35 or more databases.') AS Details - FROM msdb.dbo.backupset bs - WHERE bs.type = 'D' - AND bs.backup_finish_date >= DATEADD(DAY, -14, GETDATE()) /* In the last 2 weeks */ - GROUP BY bs.backup_finish_date - HAVING SUM(1) >= 35 - ORDER BY SUM(1) DESC; - END; - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 4 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 4) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 4 AS CheckID , - 230 AS Priority , - 'Security' AS FindingsGroup , - 'Sysadmins' AS Finding , - 'https://www.brentozar.com/go/sa' AS URL , - ( 'Login [' + l.name - + '] is a sysadmin - meaning they can do absolutely anything in SQL Server, including dropping databases or hiding their tracks.' ) AS Details - FROM master.sys.syslogins l - WHERE l.sysadmin = 1 - AND l.name <> SUSER_SNAME(0x01) - AND l.denylogin = 0 - AND l.name NOT LIKE 'NT SERVICE\%' - AND l.name <> 'l_certSignSmDetach'; /* Added in SQL 2016 */ - END; + IF EXISTS (SELECT * FROM sys.all_columns WHERE object_id = OBJECT_ID('sys.dm_os_memory_clerks') AND name = 'pages_kb') + BEGIN + /* SQL 2012+ version */ + SET @StringToExecute = N' + INSERT INTO #BlitzResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT 233 AS CheckID, + 50 AS Priority, + ''Performance'' AS FindingsGroup, + ''Memory Leak in USERSTORE_TOKENPERM Cache'' AS Finding, + ''https://www.BrentOzar.com/go/userstore'' AS URL, + N''UserStore_TokenPerm clerk is using '' + CAST(CAST(SUM(CASE WHEN type = ''USERSTORE_TOKENPERM'' AND name = ''TokenAndPermUserStore'' THEN pages_kb * 1.0 ELSE 0.0 END) / 1024.0 / 1024.0 AS INT) AS NVARCHAR(100)) + + N''GB RAM, total buffer pool is '' + CAST(CAST(SUM(pages_kb) / 1024.0 / 1024.0 AS INT) AS NVARCHAR(100)) + N''GB.'' + AS details + FROM sys.dm_os_memory_clerks + HAVING SUM(CASE WHEN type = ''USERSTORE_TOKENPERM'' AND name = ''TokenAndPermUserStore'' THEN pages_kb * 1.0 ELSE 0.0 END) / SUM(pages_kb) >= 0.1 + AND SUM(pages_kb) / 1024.0 / 1024.0 >= 1; /* At least 1GB RAM overall */'; + EXEC sp_executesql @StringToExecute; + END + ELSE + BEGIN + /* Antiques Roadshow SQL 2008R2 - version */ + SET @StringToExecute = N' + INSERT INTO #BlitzResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT 233 AS CheckID, + 50 AS Priority, + ''Performance'' AS FindingsGroup, + ''Memory Leak in USERSTORE_TOKENPERM Cache'' AS Finding, + ''https://www.BrentOzar.com/go/userstore'' AS URL, + N''UserStore_TokenPerm clerk is using '' + CAST(CAST(SUM(CASE WHEN type = ''USERSTORE_TOKENPERM'' AND name = ''TokenAndPermUserStore'' THEN single_pages_kb + multi_pages_kb * 1.0 ELSE 0.0 END) / 1024.0 / 1024.0 AS INT) AS NVARCHAR(100)) + + N''GB RAM, total buffer pool is '' + CAST(CAST(SUM(single_pages_kb + multi_pages_kb) / 1024.0 / 1024.0 AS INT) AS NVARCHAR(100)) + N''GB.'' + AS details + FROM sys.dm_os_memory_clerks + HAVING SUM(CASE WHEN type = ''USERSTORE_TOKENPERM'' AND name = ''TokenAndPermUserStore'' THEN single_pages_kb + multi_pages_kb * 1.0 ELSE 0.0 END) / SUM(single_pages_kb + multi_pages_kb) >= 0.1 + AND SUM(single_pages_kb + multi_pages_kb) / 1024.0 / 1024.0 >= 1; /* At least 1GB RAM overall */'; + EXEC sp_executesql @StringToExecute; + END - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE CheckID = 2301 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 2301) WITH NOWAIT; - - /* - #InvalidLogins is filled at the start during the permissions check - */ - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 2301 AS CheckID , - 230 AS Priority , - 'Security' AS FindingsGroup , - 'Invalid login defined with Windows Authentication' AS Finding , - 'https://docs.microsoft.com/en-us/sql/relational-databases/system-stored-procedures/sp-validatelogins-transact-sql' AS URL , - ( 'Windows user or group ' + QUOTENAME(LoginName) + ' is mapped to a SQL Server principal but no longer exists in the Windows environment.') AS Details - FROM #InvalidLogins - ; - END; - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 5 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 5) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 5 AS CheckID , - 230 AS Priority , - 'Security' AS FindingsGroup , - 'Security Admins' AS Finding , - 'https://www.brentozar.com/go/sa' AS URL , - ( 'Login [' + l.name - + '] is a security admin - meaning they can give themselves permission to do absolutely anything in SQL Server, including dropping databases or hiding their tracks.' ) AS Details - FROM master.sys.syslogins l - WHERE l.securityadmin = 1 - AND l.name <> SUSER_SNAME(0x01) - AND l.denylogin = 0; END; - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 104 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 104) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( [CheckID] , - [Priority] , - [FindingsGroup] , - [Finding] , - [URL] , - [Details] - ) - SELECT 104 AS [CheckID] , - 230 AS [Priority] , - 'Security' AS [FindingsGroup] , - 'Login Can Control Server' AS [Finding] , - 'https://www.brentozar.com/go/sa' AS [URL] , - 'Login [' + pri.[name] - + '] has the CONTROL SERVER permission - meaning they can do absolutely anything in SQL Server, including dropping databases or hiding their tracks.' AS [Details] - FROM sys.server_principals AS pri - WHERE pri.[principal_id] IN ( - SELECT p.[grantee_principal_id] - FROM sys.server_permissions AS p - WHERE p.[state] IN ( 'G', 'W' ) - AND p.[class] = 100 - AND p.[type] = 'CL' ) - AND pri.[name] NOT LIKE '##%##'; - END; - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 6 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 6) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 6 AS CheckID , - 230 AS Priority , - 'Security' AS FindingsGroup , - 'Jobs Owned By Users' AS Finding , - 'https://www.brentozar.com/go/owners' AS URL , - ( 'Job [' + j.name + '] is owned by [' - + SUSER_SNAME(j.owner_sid) - + '] - 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); - END; - /* --TOURSTOP06-- */ + + IF NOT EXISTS ( SELECT 1 FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 7 ) + WHERE DatabaseName IS NULL AND CheckID = 234 ) BEGIN - /* --TOURSTOP02-- */ - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 7) WITH NOWAIT; + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 234) WITH NOWAIT; - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 7 AS CheckID , - 230 AS Priority , - 'Security' AS FindingsGroup , - 'Stored Procedure Runs at Startup' AS Finding , - 'https://www.brentozar.com/go/startup' AS URL , - ( 'Stored procedure [master].[' - + r.SPECIFIC_SCHEMA + '].[' - + r.SPECIFIC_NAME - + '] runs automatically when SQL Server starts up. Make sure you know exactly what this stored procedure is doing, because it could pose a security risk.' ) AS Details - FROM master.INFORMATION_SCHEMA.ROUTINES r - WHERE OBJECTPROPERTY(OBJECT_ID(ROUTINE_NAME), - 'ExecIsStartup') = 1; + INSERT INTO #BlitzResults + ( CheckID , + Priority , + DatabaseName , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT 234 AS CheckID, + 100 AS Priority, + db_name(f.database_id) AS DatabaseName, + 'Reliability' AS FindingsGroup, + 'SQL Server Update May Fail' AS Finding, + 'https://desertdba.com/failovers-cant-serve-two-masters/' AS URL, + 'This database has a file with a logical name of ''master'', which can break SQL Server updates. Rename it in SSMS by right-clicking on the database, go into Properties, and rename the file. Takes effect instantly.' AS details + FROM master.sys.master_files f + WHERE (f.name = N'master') + AND f.database_id > 4 + AND db_name(f.database_id) <> 'master'; /* Thanks Michaels3 for catching this */ END; - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 10 ) - BEGIN - IF @@VERSION NOT LIKE '%Microsoft SQL Server 2000%' - AND @@VERSION NOT LIKE '%Microsoft SQL Server 2005%' - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 10) WITH NOWAIT; - - SET @StringToExecute = 'INSERT INTO #BlitzResults - (CheckID, - Priority, - FindingsGroup, - Finding, - URL, - Details) - SELECT 10 AS CheckID, - 100 AS Priority, - ''Performance'' AS FindingsGroup, - ''Resource Governor Enabled'' AS Finding, - ''https://www.brentozar.com/go/rg'' AS URL, - (''Resource Governor is enabled. Queries may be throttled. Make sure you understand how the Classifier Function is configured.'') AS Details FROM sys.resource_governor_configuration WHERE is_enabled = 1 OPTION (RECOMPILE);'; - IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; - IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; - EXECUTE(@StringToExecute); - END; - END; - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 11 ) + IF @CheckUserDatabaseObjects = 1 BEGIN - IF @@VERSION NOT LIKE '%Microsoft SQL Server 2000%' - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 11) WITH NOWAIT; - - SET @StringToExecute = 'INSERT INTO #BlitzResults - (CheckID, - Priority, - FindingsGroup, - Finding, - URL, - Details) - SELECT 11 AS CheckID, - 100 AS Priority, - ''Performance'' AS FindingsGroup, - ''Server Triggers Enabled'' AS Finding, - ''https://www.brentozar.com/go/logontriggers/'' AS URL, - (''Server Trigger ['' + [name] ++ ''] is enabled. Make sure you understand what that trigger is doing - the less work it does, the better.'') AS Details FROM sys.server_triggers WHERE is_disabled = 0 AND is_ms_shipped = 0 OPTION (RECOMPILE);'; - IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; - IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; + IF @Debug IN (1, 2) RAISERROR('Starting @CheckUserDatabaseObjects section.', 0, 1) WITH NOWAIT - EXECUTE(@StringToExecute); - END; - END; + /* + But what if you need to run a query in every individual database? + Check out CheckID 99 below. Yes, it uses sp_MSforeachdb, and no, + we're not happy about that. sp_MSforeachdb is known to have a lot + of issues, like skipping databases sometimes. However, this is the + only built-in option that we have. If you're writing your own code + for database maintenance, consider Aaron Bertrand's alternative: + http://www.mssqltips.com/sqlservertip/2201/making-a-more-reliable-and-flexible-spmsforeachdb/ + We don't include that as part of sp_Blitz, of course, because + copying and distributing copyrighted code from others without their + written permission isn't a good idea. + */ + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 99 ) + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 99) WITH NOWAIT; + + EXEC dbo.sp_MSforeachdb 'USE [?]; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; IF EXISTS (SELECT * FROM sys.tables WITH (NOLOCK) WHERE name = ''sysmergepublications'' ) IF EXISTS ( SELECT * FROM sysmergepublications WITH (NOLOCK) WHERE retention = 0) INSERT INTO #BlitzResults (CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details) SELECT DISTINCT 99, DB_NAME(), 110, ''Performance'', ''Infinite merge replication metadata retention period'', ''https://www.brentozar.com/go/merge'', (''The ['' + DB_NAME() + ''] database has merge replication metadata retention period set to infinite - this can be the case of significant performance issues.'')'; + END; + /* + Note that by using sp_MSforeachdb, we're running the query in all + databases. We're not checking #SkipChecks here for each database to + see if we should run the check in this database. That means we may + still run a skipped check if it involves sp_MSforeachdb. We just + don't output those results in the last step. + */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 12 ) - BEGIN + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 163 ) + AND EXISTS(SELECT * FROM sys.all_objects WHERE name = 'database_query_store_options') + BEGIN + /* --TOURSTOP03-- */ - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 12) WITH NOWAIT; + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 163) WITH NOWAIT; - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 12 AS CheckID , - [name] AS DatabaseName , - 10 AS Priority , - 'Performance' AS FindingsGroup , - 'Auto-Close Enabled' AS Finding , - 'https://www.brentozar.com/go/autoclose' AS URL , - ( 'Database [' + [name] - + '] has auto-close enabled. This setting can dramatically decrease performance.' ) AS Details - FROM sys.databases - WHERE is_auto_close_on = 1 - AND name NOT IN ( SELECT DISTINCT - DatabaseName - FROM #SkipChecks - WHERE CheckID IS NULL OR CheckID = 12); - END; + EXEC dbo.sp_MSforeachdb 'USE [?]; + SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + INSERT INTO #BlitzResults + (CheckID, + DatabaseName, + Priority, + FindingsGroup, + Finding, + URL, + Details) + SELECT TOP 1 163, + N''?'', + 200, + ''Performance'', + ''Query Store Disabled'', + ''https://www.brentozar.com/go/querystore'', + (''The new SQL Server 2016 Query Store feature has not been enabled on this database.'') + FROM [?].sys.database_query_store_options WHERE desired_state = 0 + AND N''?'' NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''DWConfiguration'', ''DWDiagnostics'', ''DWQueue'', ''ReportServer'', ''ReportServerTempDB'') OPTION (RECOMPILE)'; + END; - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 13 ) - BEGIN - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 13) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 13 AS CheckID , - [name] AS DatabaseName , - 10 AS Priority , - 'Performance' AS FindingsGroup , - 'Auto-Shrink Enabled' AS Finding , - 'https://www.brentozar.com/go/autoshrink' AS URL , - ( 'Database [' + [name] - + '] has auto-shrink enabled. This setting can dramatically decrease performance.' ) AS Details - FROM sys.databases - WHERE is_auto_shrink_on = 1 - AND state <> 6 /* Offline */ - AND name NOT IN ( SELECT DISTINCT - DatabaseName - FROM #SkipChecks - WHERE CheckID IS NULL OR CheckID = 13); - END; + IF @ProductVersionMajor = 13 AND @ProductVersionMinor < 2149 --2016 CU1 has the fix in it + AND NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 182 ) + AND CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) NOT LIKE '%Enterprise%' + AND CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) NOT LIKE '%Developer%' + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 182) WITH NOWAIT; + + SET @StringToExecute = 'INSERT INTO #BlitzResults + (CheckID, + DatabaseName, + Priority, + FindingsGroup, + Finding, + URL, + Details) + SELECT TOP 1 + 182, + ''Server'', + 20, + ''Reliability'', + ''Query Store Cleanup Disabled'', + ''https://www.brentozar.com/go/cleanup'', + (''SQL 2016 RTM has a bug involving dumps that happen every time Query Store cleanup jobs run. This is fixed in CU1 and later: https://sqlserverupdates.com/sql-server-2016-updates/'') + FROM sys.databases AS d + WHERE d.is_query_store_on = 1 OPTION (RECOMPILE);'; + + IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; + IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; + + EXECUTE(@StringToExecute); + END; - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 14 ) - BEGIN - IF @@VERSION NOT LIKE '%Microsoft SQL Server 2000%' + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 235 ) + AND EXISTS(SELECT * FROM sys.all_objects WHERE name = 'database_query_store_options') BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 14) WITH NOWAIT; - SET @StringToExecute = 'INSERT INTO #BlitzResults - (CheckID, - DatabaseName, - Priority, - FindingsGroup, - Finding, - URL, - Details) - SELECT 14 AS CheckID, - [name] as DatabaseName, - 50 AS Priority, - ''Reliability'' AS FindingsGroup, - ''Page Verification Not Optimal'' AS Finding, - ''https://www.brentozar.com/go/torn'' AS URL, - (''Database ['' + [name] + ''] has '' + [page_verify_option_desc] + '' for page verification. SQL Server may have a harder time recognizing and recovering from storage corruption. Consider using CHECKSUM instead.'') COLLATE database_default AS Details - FROM sys.databases - WHERE page_verify_option < 2 - AND name <> ''tempdb'' - AND state NOT IN (1, 6) /* Restoring, Offline */ - and name not in (select distinct DatabaseName from #SkipChecks WHERE CheckID IS NULL OR CheckID = 14) OPTION (RECOMPILE);'; - - IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; - IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; - - EXECUTE(@StringToExecute); - END; - END; + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 235) WITH NOWAIT; - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 15 ) - BEGIN + EXEC dbo.sp_MSforeachdb 'USE [?]; + SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + INSERT INTO #BlitzResults + (CheckID, + DatabaseName, + Priority, + FindingsGroup, + Finding, + URL, + Details) + SELECT TOP 1 235, + N''?'', + 150, + ''Performance'', + ''Inconsistent Query Store metadata'', + '''', + (''Query store state in master metadata and database specific metadata not in sync.'') + FROM [?].sys.database_query_store_options dqso + join master.sys.databases D on D.name = N''?'' + WHERE ((dqso.actual_state = 0 AND D.is_query_store_on = 1) OR (dqso.actual_state <> 0 AND D.is_query_store_on = 0)) + AND N''?'' NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''DWConfiguration'', ''DWDiagnostics'', ''DWQueue'', ''ReportServer'', ''ReportServerTempDB'') OPTION (RECOMPILE)'; + END; - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 15) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 15 AS CheckID , - [name] AS DatabaseName , - 110 AS Priority , - 'Performance' AS FindingsGroup , - 'Auto-Create Stats Disabled' AS Finding , - 'https://www.brentozar.com/go/acs' AS URL , - ( 'Database [' + [name] - + '] has auto-create-stats disabled. SQL Server uses statistics to build better execution plans, and without the ability to automatically create more, performance may suffer.' ) AS Details - FROM sys.databases - WHERE is_auto_create_stats_on = 0 - AND name NOT IN ( SELECT DISTINCT - DatabaseName - FROM #SkipChecks - WHERE CheckID IS NULL OR CheckID = 15); - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 16 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 16) WITH NOWAIT; + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 41 ) + BEGIN - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 16 AS CheckID , - [name] AS DatabaseName , - 110 AS Priority , - 'Performance' AS FindingsGroup , - 'Auto-Update Stats Disabled' AS Finding , - 'https://www.brentozar.com/go/aus' AS URL , - ( 'Database [' + [name] - + '] has auto-update-stats disabled. SQL Server uses statistics to build better execution plans, and without the ability to automatically update them, performance may suffer.' ) AS Details - FROM sys.databases - WHERE is_auto_update_stats_on = 0 - AND name NOT IN ( SELECT DISTINCT - DatabaseName - FROM #SkipChecks - WHERE CheckID IS NULL OR CheckID = 16); - END; + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 41) WITH NOWAIT; + + EXEC dbo.sp_MSforeachdb 'use [?]; + SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + INSERT INTO #BlitzResults + (CheckID, + DatabaseName, + Priority, + FindingsGroup, + Finding, + URL, + Details) + SELECT 41, + N''?'', + 170, + ''File Configuration'', + ''Multiple Log Files on One Drive'', + ''https://www.brentozar.com/go/manylogs'', + (''The ['' + DB_NAME() + ''] database has multiple log files on the '' + LEFT(physical_name, 1) + '' drive. This is not a performance booster because log file access is sequential, not parallel.'') + FROM [?].sys.database_files WHERE type_desc = ''LOG'' + AND N''?'' <> ''[tempdb]'' + GROUP BY LEFT(physical_name, 1) + HAVING COUNT(*) > 1 + AND SUM(size) < 268435456 OPTION (RECOMPILE);'; + END; - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 17 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 17) WITH NOWAIT; + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 42 ) + BEGIN - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 17 AS CheckID , - [name] AS DatabaseName , - 150 AS Priority , - 'Performance' AS FindingsGroup , - 'Stats Updated Asynchronously' AS Finding , - 'https://www.brentozar.com/go/asyncstats' AS URL , - ( 'Database [' + [name] - + '] has auto-update-stats-async enabled. When SQL Server gets a query for a table with out-of-date statistics, it will run the query with the stats it has - while updating stats to make later queries better. The initial run of the query may suffer, though.' ) AS Details - FROM sys.databases - WHERE is_auto_update_stats_async_on = 1 - AND name NOT IN ( SELECT DISTINCT - DatabaseName - FROM #SkipChecks - WHERE CheckID IS NULL OR CheckID = 17); - END; + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 42) WITH NOWAIT; + + EXEC dbo.sp_MSforeachdb 'use [?]; + SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + INSERT INTO #BlitzResults + (CheckID, + DatabaseName, + Priority, + FindingsGroup, + Finding, + URL, + Details) + SELECT DISTINCT 42, + N''?'', + 170, + ''File Configuration'', + ''Uneven File Growth Settings in One Filegroup'', + ''https://www.brentozar.com/go/grow'', + (''The ['' + DB_NAME() + ''] database has multiple data files in one filegroup, but they are not all set up to grow in identical amounts. This can lead to uneven file activity inside the filegroup.'') + FROM [?].sys.database_files + WHERE type_desc = ''ROWS'' + GROUP BY data_space_id + HAVING COUNT(DISTINCT growth) > 1 OR COUNT(DISTINCT is_percent_growth) > 1 OPTION (RECOMPILE);'; + END; - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 20 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 20) WITH NOWAIT; + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 82 ) + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 82) WITH NOWAIT; + + EXEC sp_MSforeachdb 'use [?]; + SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + INSERT INTO #BlitzResults + (CheckID, + DatabaseName, + Priority, + FindingsGroup, + Finding, + URL, Details) + SELECT DISTINCT 82 AS CheckID, + N''?'' as DatabaseName, + 170 AS Priority, + ''File Configuration'' AS FindingsGroup, + ''File growth set to percent'', + ''https://www.brentozar.com/go/percentgrowth'' AS URL, + ''The ['' + DB_NAME() + ''] database file '' + f.physical_name + '' has grown to '' + CONVERT(NVARCHAR(10), CONVERT(NUMERIC(38, 2), (f.size / 128.) / 1024.)) + '' GB, and is using percent filegrowth settings. This can lead to slow performance during growths if Instant File Initialization is not enabled.'' + FROM [?].sys.database_files f + WHERE is_percent_growth = 1 and size > 128000 OPTION (RECOMPILE);'; + END; + + /* addition by Henrik Staun Poulsen, Stovi Software */ + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 158 ) + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 158) WITH NOWAIT; + + EXEC sp_MSforeachdb 'use [?]; + SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + INSERT INTO #BlitzResults + (CheckID, + DatabaseName, + Priority, + FindingsGroup, + Finding, + URL, Details) + SELECT DISTINCT 158 AS CheckID, + N''?'' as DatabaseName, + 170 AS Priority, + ''File Configuration'' AS FindingsGroup, + ''File growth set to 1MB'', + ''https://www.brentozar.com/go/percentgrowth'' AS URL, + ''The ['' + DB_NAME() + ''] database file '' + f.physical_name + '' is using 1MB filegrowth settings, but it has grown to '' + CAST((CAST(f.size AS BIGINT) * 8 / 1000000) AS NVARCHAR(10)) + '' GB. Time to up the growth amount.'' + FROM [?].sys.database_files f + WHERE is_percent_growth = 0 and growth=128 and size > 128000 OPTION (RECOMPILE);'; + END; + + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 33 ) + BEGIN + IF @@VERSION NOT LIKE '%Microsoft SQL Server 2000%' + AND @@VERSION NOT LIKE '%Microsoft SQL Server 2005%' + AND @SkipBlockingChecks = 0 + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 33) WITH NOWAIT; + + EXEC dbo.sp_MSforeachdb 'USE [?]; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + INSERT INTO #BlitzResults + (CheckID, + DatabaseName, + Priority, + FindingsGroup, + Finding, + URL, + Details) + SELECT DISTINCT 33, + db_name(), + 200, + ''Licensing'', + ''Enterprise Edition Features In Use'', + ''https://www.brentozar.com/go/ee'', + (''The ['' + DB_NAME() + ''] database is using '' + feature_name + ''. If this database is restored onto a Standard Edition server, the restore will fail on versions prior to 2016 SP1.'') + FROM [?].sys.dm_db_persisted_sku_features OPTION (RECOMPILE);'; + END; + END; + + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 19 ) + BEGIN + /* Method 1: Check sys.databases parameters */ - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 20 AS CheckID , - [name] AS DatabaseName , - 200 AS Priority , - 'Informational' AS FindingsGroup , - 'Date Correlation On' AS Finding , - 'https://www.brentozar.com/go/corr' AS URL , - ( 'Database [' + [name] - + '] has date correlation enabled. This is not a default setting, and it has some performance overhead. It tells SQL Server that date fields in two tables are related, and SQL Server maintains statistics showing that relation.' ) AS Details - FROM sys.databases - WHERE is_date_correlation_on = 1 - AND name NOT IN ( SELECT DISTINCT - DatabaseName - FROM #SkipChecks - WHERE CheckID IS NULL OR CheckID = 20); - END; + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 19) WITH NOWAIT; + + INSERT INTO #BlitzResults + ( CheckID , + DatabaseName , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 21 ) - BEGIN - /* --TOURSTOP04-- */ - IF @@VERSION NOT LIKE '%Microsoft SQL Server 2000%' - AND @@VERSION NOT LIKE '%Microsoft SQL Server 2005%' + SELECT 19 AS CheckID , + [name] AS DatabaseName , + 200 AS Priority , + 'Informational' AS FindingsGroup , + 'Replication In Use' AS Finding , + 'https://www.brentozar.com/go/repl' AS URL , + ( 'Database [' + [name] + + '] is a replication publisher, subscriber, or distributor.' ) AS Details + FROM sys.databases + WHERE name NOT IN ( SELECT DISTINCT + DatabaseName + FROM #SkipChecks + WHERE CheckID IS NULL OR CheckID = 19) + AND (is_published = 1 + OR is_subscribed = 1 + OR is_merge_published = 1 + OR is_distributor = 1); + + /* Method B: check subscribers for MSreplication_objects tables */ + EXEC dbo.sp_MSforeachdb 'USE [?]; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + INSERT INTO #BlitzResults + (CheckID, + DatabaseName, + Priority, + FindingsGroup, + Finding, + URL, + Details) + SELECT DISTINCT 19, + db_name(), + 200, + ''Informational'', + ''Replication In Use'', + ''https://www.brentozar.com/go/repl'', + (''['' + DB_NAME() + ''] has MSreplication_objects tables in it, indicating it is a replication subscriber.'') + FROM [?].sys.tables + WHERE name = ''dbo.MSreplication_objects'' AND ''?'' <> ''master'' OPTION (RECOMPILE)'; + + END; + + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 32 ) BEGIN - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 21) WITH NOWAIT; + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 32) WITH NOWAIT; - SET @StringToExecute = 'INSERT INTO #BlitzResults - (CheckID, - DatabaseName, - Priority, - FindingsGroup, - Finding, - URL, - Details) - SELECT 21 AS CheckID, - [name] as DatabaseName, - 200 AS Priority, - ''Informational'' AS FindingsGroup, - ''Database Encrypted'' AS Finding, - ''https://www.brentozar.com/go/tde'' AS URL, - (''Database ['' + [name] + ''] has Transparent Data Encryption enabled. Make absolutely sure you have backed up the certificate and private key, or else you will not be able to restore this database.'') AS Details - FROM sys.databases - WHERE is_encrypted = 1 - and name not in (select distinct DatabaseName from #SkipChecks WHERE CheckID IS NULL OR CheckID = 21) OPTION (RECOMPILE);'; + EXEC dbo.sp_MSforeachdb 'USE [?]; + SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + INSERT INTO #BlitzResults + (CheckID, + DatabaseName, + Priority, + FindingsGroup, + Finding, + URL, + Details) + SELECT 32, + N''?'', + 150, + ''Performance'', + ''Triggers on Tables'', + ''https://www.brentozar.com/go/trig'', + (''The ['' + DB_NAME() + ''] database has '' + CAST(SUM(1) AS NVARCHAR(50)) + '' triggers.'') + FROM [?].sys.triggers t INNER JOIN [?].sys.objects o ON t.parent_id = o.object_id + INNER JOIN [?].sys.schemas s ON o.schema_id = s.schema_id WHERE t.is_ms_shipped = 0 AND DB_NAME() != ''ReportServer'' + HAVING SUM(1) > 0 OPTION (RECOMPILE)'; + END; + + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 164 ) + AND EXISTS(SELECT * FROM sys.all_objects WHERE name = 'fn_validate_plan_guide') + BEGIN - IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; - IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 164) WITH NOWAIT; - EXECUTE(@StringToExecute); + EXEC dbo.sp_MSforeachdb 'USE [?]; + SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + SET QUOTED_IDENTIFIER ON; + INSERT INTO #BlitzResults + (CheckID, + DatabaseName, + Priority, + FindingsGroup, + Finding, + URL, + Details) + SELECT DISTINCT 164, + N''?'', + 100, + ''Reliability'', + ''Plan Guides Failing'', + ''https://www.brentozar.com/go/misguided'', + (''The ['' + DB_NAME() + ''] database has plan guides that are no longer valid, so the queries involved may be failing silently.'') + FROM [?].sys.plan_guides g CROSS APPLY fn_validate_plan_guide(g.plan_guide_id) OPTION (RECOMPILE)'; END; - END; - /* - Believe it or not, SQL Server doesn't track the default values - for sp_configure options! We'll make our own list here. - */ + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 46 ) + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 46) WITH NOWAIT; + + EXEC dbo.sp_MSforeachdb 'USE [?]; + SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + INSERT INTO #BlitzResults + (CheckID, + DatabaseName, + Priority, + FindingsGroup, + Finding, + URL, + Details) + SELECT 46, + N''?'', + 150, + ''Performance'', + ''Leftover Fake Indexes From Wizards'', + ''https://www.brentozar.com/go/hypo'', + (''The index ['' + DB_NAME() + ''].['' + s.name + ''].['' + o.name + ''].['' + i.name + ''] is a leftover hypothetical index from the Index Tuning Wizard or Database Tuning Advisor. This index is not actually helping performance and should be removed.'') + from [?].sys.indexes i INNER JOIN [?].sys.objects o ON i.object_id = o.object_id INNER JOIN [?].sys.schemas s ON o.schema_id = s.schema_id + WHERE i.is_hypothetical = 1 OPTION (RECOMPILE);'; + END; - IF @Debug IN (1, 2) RAISERROR('Generating default configuration values', 0, 1) WITH NOWAIT; + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 47 ) + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 47) WITH NOWAIT; + + EXEC dbo.sp_MSforeachdb 'USE [?]; + SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + INSERT INTO #BlitzResults + (CheckID, + DatabaseName, + Priority, + FindingsGroup, + Finding, + URL, + Details) + SELECT 47, + N''?'', + 100, + ''Performance'', + ''Indexes Disabled'', + ''https://www.brentozar.com/go/ixoff'', + (''The index ['' + DB_NAME() + ''].['' + s.name + ''].['' + o.name + ''].['' + i.name + ''] is disabled. This index is not actually helping performance and should either be enabled or removed.'') + from [?].sys.indexes i INNER JOIN [?].sys.objects o ON i.object_id = o.object_id INNER JOIN [?].sys.schemas s ON o.schema_id = s.schema_id + WHERE i.is_disabled = 1 OPTION (RECOMPILE);'; + END; - INSERT INTO #ConfigurationDefaults - VALUES ( 'access check cache bucket count', 0, 1001 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'access check cache quota', 0, 1002 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'Ad Hoc Distributed Queries', 0, 1003 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'affinity I/O mask', 0, 1004 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'affinity mask', 0, 1005 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'affinity64 mask', 0, 1066 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'affinity64 I/O mask', 0, 1067 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'Agent XPs', 0, 1071 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'allow updates', 0, 1007 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'awe enabled', 0, 1008 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'backup checksum default', 0, 1070 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'backup compression default', 0, 1073 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'blocked process threshold', 0, 1009 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'blocked process threshold (s)', 0, 1009 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'c2 audit mode', 0, 1010 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'clr enabled', 0, 1011 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'common criteria compliance enabled', 0, 1074 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'contained database authentication', 0, 1068 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'cost threshold for parallelism', 5, 1012 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'cross db ownership chaining', 0, 1013 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'cursor threshold', -1, 1014 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'Database Mail XPs', 0, 1072 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'default full-text language', 1033, 1016 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'default language', 0, 1017 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'default trace enabled', 1, 1018 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'disallow results from triggers', 0, 1019 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'EKM provider enabled', 0, 1075 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'filestream access level', 0, 1076 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'fill factor (%)', 0, 1020 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'ft crawl bandwidth (max)', 100, 1021 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'ft crawl bandwidth (min)', 0, 1022 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'ft notify bandwidth (max)', 100, 1023 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'ft notify bandwidth (min)', 0, 1024 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'index create memory (KB)', 0, 1025 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'in-doubt xact resolution', 0, 1026 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'lightweight pooling', 0, 1027 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'locks', 0, 1028 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'max degree of parallelism', 0, 1029 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'max full-text crawl range', 4, 1030 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'max server memory (MB)', 2147483647, 1031 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'max text repl size (B)', 65536, 1032 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'max worker threads', 0, 1033 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'media retention', 0, 1034 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'min memory per query (KB)', 1024, 1035 ); - /* Accepting both 0 and 16 below because both have been seen in the wild as defaults. */ - IF EXISTS ( SELECT * - FROM sys.configurations - WHERE name = 'min server memory (MB)' - AND value_in_use IN ( 0, 16 ) ) - INSERT INTO #ConfigurationDefaults - SELECT 'min server memory (MB)' , - CAST(value_in_use AS BIGINT), 1036 - FROM sys.configurations - WHERE name = 'min server memory (MB)'; - ELSE - INSERT INTO #ConfigurationDefaults - VALUES ( 'min server memory (MB)', 0, 1036 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'nested triggers', 1, 1037 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'network packet size (B)', 4096, 1038 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'Ole Automation Procedures', 0, 1039 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'open objects', 0, 1040 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'optimize for ad hoc workloads', 0, 1041 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'PH timeout (s)', 60, 1042 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'precompute rank', 0, 1043 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'priority boost', 0, 1044 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'query governor cost limit', 0, 1045 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'query wait (s)', -1, 1046 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'recovery interval (min)', 0, 1047 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'remote access', 1, 1048 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'remote admin connections', 0, 1049 ); - /* SQL Server 2012 changes a configuration default */ - IF @@VERSION LIKE '%Microsoft SQL Server 2005%' - OR @@VERSION LIKE '%Microsoft SQL Server 2008%' - BEGIN - INSERT INTO #ConfigurationDefaults - VALUES ( 'remote login timeout (s)', 20, 1069 ); - END; - ELSE - BEGIN - INSERT INTO #ConfigurationDefaults - VALUES ( 'remote login timeout (s)', 10, 1069 ); - END; - INSERT INTO #ConfigurationDefaults - VALUES ( 'remote proc trans', 0, 1050 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'remote query timeout (s)', 600, 1051 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'Replication XPs', 0, 1052 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'RPC parameter data validation', 0, 1053 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'scan for startup procs', 0, 1054 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'server trigger recursion', 1, 1055 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'set working set size', 0, 1056 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'show advanced options', 0, 1057 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'SMO and DMO XPs', 1, 1058 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'SQL Mail XPs', 0, 1059 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'transform noise words', 0, 1060 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'two digit year cutoff', 2049, 1061 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'user connections', 0, 1062 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'user options', 0, 1063 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'Web Assistant Procedures', 0, 1064 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'xp_cmdshell', 0, 1065 ); + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 48 ) + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 48) WITH NOWAIT; + + EXEC dbo.sp_MSforeachdb 'USE [?]; + SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + INSERT INTO #BlitzResults + (CheckID, + DatabaseName, + Priority, + FindingsGroup, + Finding, + URL, + Details) + SELECT DISTINCT 48, + N''?'', + 150, + ''Performance'', + ''Foreign Keys Not Trusted'', + ''https://www.brentozar.com/go/trust'', + (''The ['' + DB_NAME() + ''] database has foreign keys that were probably disabled, data was changed, and then the key was enabled again. Simply enabling the key is not enough for the optimizer to use this key - we have to alter the table using the WITH CHECK CHECK CONSTRAINT parameter.'') + from [?].sys.foreign_keys i INNER JOIN [?].sys.objects o ON i.parent_object_id = o.object_id INNER JOIN [?].sys.schemas s ON o.schema_id = s.schema_id + WHERE i.is_not_trusted = 1 AND i.is_not_for_replication = 0 AND i.is_disabled = 0 AND N''?'' NOT IN (''master'', ''model'', ''msdb'', ''ReportServer'', ''ReportServerTempDB'') OPTION (RECOMPILE);'; + END; - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 22 ) - BEGIN + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 56 ) + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 56) WITH NOWAIT; + + EXEC dbo.sp_MSforeachdb 'USE [?]; + SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + INSERT INTO #BlitzResults + (CheckID, + DatabaseName, + Priority, + FindingsGroup, + Finding, + URL, + Details) + SELECT 56, + N''?'', + 150, + ''Performance'', + ''Check Constraint Not Trusted'', + ''https://www.brentozar.com/go/trust'', + (''The check constraint ['' + DB_NAME() + ''].['' + s.name + ''].['' + o.name + ''].['' + i.name + ''] is not trusted - meaning, it was disabled, data was changed, and then the constraint was enabled again. Simply enabling the constraint is not enough for the optimizer to use this constraint - we have to alter the table using the WITH CHECK CHECK CONSTRAINT parameter.'') + from [?].sys.check_constraints i INNER JOIN [?].sys.objects o ON i.parent_object_id = o.object_id + INNER JOIN [?].sys.schemas s ON o.schema_id = s.schema_id + WHERE i.is_not_trusted = 1 AND i.is_not_for_replication = 0 AND i.is_disabled = 0 OPTION (RECOMPILE);'; + END; - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 22) WITH NOWAIT; + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 95 ) + BEGIN + IF @@VERSION NOT LIKE '%Microsoft SQL Server 2000%' + AND @@VERSION NOT LIKE '%Microsoft SQL Server 2005%' + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 95) WITH NOWAIT; + + EXEC dbo.sp_MSforeachdb 'USE [?]; + SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + INSERT INTO #BlitzResults + (CheckID, + DatabaseName, + Priority, + FindingsGroup, + Finding, + URL, + Details) + SELECT TOP 1 95 AS CheckID, + N''?'' as DatabaseName, + 110 AS Priority, + ''Performance'' AS FindingsGroup, + ''Plan Guides Enabled'' AS Finding, + ''https://www.brentozar.com/go/guides'' AS URL, + (''Database ['' + DB_NAME() + ''] has query plan guides so a query will always get a specific execution plan. If you are having trouble getting query performance to improve, it might be due to a frozen plan. Review the DMV sys.plan_guides to learn more about the plan guides in place on this server.'') AS Details + FROM [?].sys.plan_guides WHERE is_disabled = 0 OPTION (RECOMPILE);'; + END; + END; - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT cd.CheckID , - 200 AS Priority , - 'Non-Default Server Config' AS FindingsGroup , - cr.name AS Finding , - 'https://www.brentozar.com/go/conf' AS URL , - ( 'This sp_configure option has been changed. Its default value is ' - + COALESCE(CAST(cd.[DefaultValue] AS VARCHAR(100)), - '(unknown)') - + ' and it has been set to ' - + CAST(cr.value_in_use AS VARCHAR(100)) - + '.' ) AS Details - FROM sys.configurations cr - INNER JOIN #ConfigurationDefaults cd ON cd.name = cr.name - LEFT OUTER JOIN #ConfigurationDefaults cdUsed ON cdUsed.name = cr.name - AND cdUsed.DefaultValue = cr.value_in_use - WHERE cdUsed.name IS NULL; - END; + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 60 ) + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 60) WITH NOWAIT; + + EXEC sp_MSforeachdb 'USE [?]; + SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + INSERT INTO #BlitzResults + (CheckID, + DatabaseName, + Priority, + FindingsGroup, + Finding, + URL, + Details) + SELECT 60 AS CheckID, + N''?'' as DatabaseName, + 100 AS Priority, + ''Performance'' AS FindingsGroup, + ''Fill Factor Changed'', + ''https://www.brentozar.com/go/fillfactor'' AS URL, + ''The ['' + DB_NAME() + ''] database has '' + CAST(SUM(1) AS NVARCHAR(50)) + '' objects with fill factor = '' + CAST(fill_factor AS NVARCHAR(5)) + ''%. This can cause memory and storage performance problems, but may also prevent page splits.'' + FROM [?].sys.indexes + WHERE fill_factor <> 0 AND fill_factor < 80 AND is_disabled = 0 AND is_hypothetical = 0 + GROUP BY fill_factor OPTION (RECOMPILE);'; + END; - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 190 ) - BEGIN + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 78 ) + BEGIN - IF @Debug IN (1, 2) RAISERROR('Setting @MinServerMemory and @MaxServerMemory', 0, 1) WITH NOWAIT; + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 78) WITH NOWAIT; + + EXECUTE master.sys.sp_MSforeachdb 'USE [?]; + SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + INSERT INTO #Recompile + SELECT DISTINCT DBName = DB_Name(), SPName = SO.name, SM.is_recompiled, ISR.SPECIFIC_SCHEMA + FROM sys.sql_modules AS SM + LEFT OUTER JOIN master.sys.databases AS sDB ON SM.object_id = DB_id() + LEFT OUTER JOIN dbo.sysobjects AS SO ON SM.object_id = SO.id and type = ''P'' + LEFT OUTER JOIN INFORMATION_SCHEMA.ROUTINES AS ISR on ISR.Routine_Name = SO.name AND ISR.SPECIFIC_CATALOG = DB_Name() + WHERE SM.is_recompiled=1 OPTION (RECOMPILE); /* oh the rich irony of recompile here */ + '; + INSERT INTO #BlitzResults + (Priority, + FindingsGroup, + Finding, + DatabaseName, + URL, + Details, + CheckID) + SELECT [Priority] = '100', + FindingsGroup = 'Performance', + Finding = 'Stored Procedure WITH RECOMPILE', + DatabaseName = DBName, + URL = 'https://www.brentozar.com/go/recompile', + Details = '[' + DBName + '].[' + SPSchema + '].[' + ProcName + '] has WITH RECOMPILE in the stored procedure code, which may cause increased CPU usage due to constant recompiles of the code.', + CheckID = '78' + FROM #Recompile AS TR + WHERE ProcName NOT LIKE 'sp_AllNightLog%' AND ProcName NOT LIKE 'sp_AskBrent%' AND ProcName NOT LIKE 'sp_Blitz%' AND ProcName NOT LIKE 'sp_PressureDetector' + AND DBName NOT IN ('master', 'model', 'msdb', 'tempdb'); + DROP TABLE #Recompile; + END; - SELECT @MinServerMemory = CAST(value_in_use as BIGINT) FROM sys.configurations WHERE name = 'min server memory (MB)'; - SELECT @MaxServerMemory = CAST(value_in_use as BIGINT) FROM sys.configurations WHERE name = 'max server memory (MB)'; - - IF (@MinServerMemory = @MaxServerMemory) - BEGIN + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 86 ) + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 86) WITH NOWAIT; + + EXEC dbo.sp_MSforeachdb 'USE [?]; INSERT INTO #BlitzResults (CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details) SELECT DISTINCT 86, DB_NAME(), 230, ''Security'', ''Elevated Permissions on a Database'', ''https://www.brentozar.com/go/elevated'', (''In ['' + DB_NAME() + ''], user ['' + u.name + ''] has the role ['' + g.name + '']. This user can perform tasks beyond just reading and writing data.'') FROM (SELECT memberuid = convert(int, member_principal_id), groupuid = convert(int, role_principal_id) FROM [?].sys.database_role_members) m inner join [?].dbo.sysusers u on m.memberuid = u.uid inner join sysusers g on m.groupuid = g.uid where u.name <> ''dbo'' and g.name in (''db_owner'' , ''db_accessadmin'' , ''db_securityadmin'' , ''db_ddladmin'') OPTION (RECOMPILE);'; + END; - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 190) WITH NOWAIT; + /*Check for non-aligned indexes in partioned databases*/ - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - VALUES - ( 190, - 200, - 'Performance', - 'Non-Dynamic Memory', - 'https://www.brentozar.com/go/memory', - 'Minimum Server Memory setting is the same as the Maximum (both set to ' + CAST(@MinServerMemory AS NVARCHAR(50)) + '). This will not allow dynamic memory. Please revise memory settings' - ); - END; - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 188 ) - BEGIN + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 72 ) + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 72) WITH NOWAIT; + + EXEC dbo.sp_MSforeachdb 'USE [?]; + SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + insert into #partdb(dbname, objectname, type_desc) + SELECT distinct db_name(DB_ID()) as DBName,o.name Object_Name,ds.type_desc + FROM sys.objects AS o JOIN sys.indexes AS i ON o.object_id = i.object_id + JOIN sys.data_spaces ds on ds.data_space_id = i.data_space_id + LEFT OUTER JOIN sys.dm_db_index_usage_stats AS s ON i.object_id = s.object_id AND i.index_id = s.index_id AND s.database_id = DB_ID() + WHERE o.type = ''u'' + -- Clustered and Non-Clustered indexes + AND i.type IN (1, 2) + AND o.object_id in + ( + SELECT a.object_id from + (SELECT ob.object_id, ds.type_desc from sys.objects ob JOIN sys.indexes ind on ind.object_id = ob.object_id join sys.data_spaces ds on ds.data_space_id = ind.data_space_id + GROUP BY ob.object_id, ds.type_desc ) a group by a.object_id having COUNT (*) > 1 + ) OPTION (RECOMPILE);'; + INSERT INTO #BlitzResults + ( CheckID , + DatabaseName , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT DISTINCT + 72 AS CheckID , + dbname AS DatabaseName , + 100 AS Priority , + 'Performance' AS FindingsGroup , + 'The partitioned database ' + dbname + + ' may have non-aligned indexes' AS Finding , + 'https://www.brentozar.com/go/aligned' AS URL , + 'Having non-aligned indexes on partitioned tables may cause inefficient query plans and CPU pressure' AS Details + FROM #partdb + WHERE dbname IS NOT NULL + AND dbname NOT IN ( SELECT DISTINCT + DatabaseName + FROM #SkipChecks + WHERE CheckID IS NULL OR CheckID = 72); + DROP TABLE #partdb; + END; - /* Let's set variables so that our query is still SARGable */ - - IF @Debug IN (1, 2) RAISERROR('Setting @Processors.', 0, 1) WITH NOWAIT; - - SET @Processors = (SELECT cpu_count FROM sys.dm_os_sys_info); - - IF @Debug IN (1, 2) RAISERROR('Setting @NUMANodes', 0, 1) WITH NOWAIT; - - SET @NUMANodes = (SELECT COUNT(1) - FROM sys.dm_os_performance_counters pc - WHERE pc.object_name LIKE '%Buffer Node%' - AND counter_name = 'Page life expectancy'); - /* If Cost Threshold for Parallelism is default then flag as a potential issue */ - /* If MAXDOP is default and processors > 8 or NUMA nodes > 1 then flag as potential issue */ - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 188) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 188 AS CheckID , - 200 AS Priority , - 'Performance' AS FindingsGroup , - cr.name AS Finding , - 'https://www.brentozar.com/go/cxpacket' AS URL , - ( 'Set to ' + CAST(cr.value_in_use AS NVARCHAR(50)) + ', its default value. Changing this sp_configure setting may reduce CXPACKET waits.') - FROM sys.configurations cr - INNER JOIN #ConfigurationDefaults cd ON cd.name = cr.name - AND cr.value_in_use = cd.DefaultValue - WHERE cr.name = 'cost threshold for parallelism' - OR (cr.name = 'max degree of parallelism' AND (@NUMANodes > 1 OR @Processors > 8)); - END; + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 113 ) + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 113) WITH NOWAIT; + + EXEC dbo.sp_MSforeachdb 'USE [?]; + SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + INSERT INTO #BlitzResults + (CheckID, + DatabaseName, + Priority, + FindingsGroup, + Finding, + URL, + Details) + SELECT DISTINCT 113, + N''?'', + 50, + ''Reliability'', + ''Full Text Indexes Not Updating'', + ''https://www.brentozar.com/go/fulltext'', + (''At least one full text index in this database has not been crawled in the last week.'') + from [?].sys.fulltext_indexes i WHERE change_tracking_state_desc <> ''AUTO'' AND i.is_enabled = 1 AND i.crawl_end_date < DATEADD(dd, -7, GETDATE()) OPTION (RECOMPILE);'; + END; - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 24 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 24) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT DISTINCT - 24 AS CheckID , - DB_NAME(database_id) AS DatabaseName , - 170 AS Priority , - 'File Configuration' AS FindingsGroup , - 'System Database on C Drive' AS Finding , - 'https://www.brentozar.com/go/cdrive' AS URL , - ( 'The ' + DB_NAME(database_id) - + ' database has a file on the C drive. Putting system databases on the C drive runs the risk of crashing the server when it runs out of space.' ) AS Details - FROM sys.master_files - WHERE UPPER(LEFT(physical_name, 1)) = 'C' - AND DB_NAME(database_id) IN ( 'master', - 'model', 'msdb' ); - END; + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 115 ) + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 115) WITH NOWAIT; + + EXEC dbo.sp_MSforeachdb 'USE [?]; + SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + INSERT INTO #BlitzResults + (CheckID, + DatabaseName, + Priority, + FindingsGroup, + Finding, + URL, + Details) + SELECT 115, + N''?'', + 110, + ''Performance'', + ''Parallelism Rocket Surgery'', + ''https://www.brentozar.com/go/makeparallel'', + (''['' + DB_NAME() + ''] has a make_parallel function, indicating that an advanced developer may be manhandling SQL Server into forcing queries to go parallel.'') + from [?].INFORMATION_SCHEMA.ROUTINES WHERE ROUTINE_NAME = ''make_parallel'' AND ROUTINE_TYPE = ''FUNCTION'' OPTION (RECOMPILE);'; + END; - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 25 ) - AND SERVERPROPERTY('EngineEdition') <> 8 /* Azure Managed Instances */ - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 25) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT TOP 1 - 25 AS CheckID , - 'tempdb' , - 20 AS Priority , - 'File Configuration' AS FindingsGroup , - 'TempDB on C Drive' AS Finding , - 'https://www.brentozar.com/go/cdrive' AS URL , - CASE WHEN growth > 0 - THEN ( 'The tempdb database has files on the C drive. TempDB frequently grows unpredictably, putting your server at risk of running out of C drive space and crashing hard. C is also often much slower than other drives, so performance may be suffering.' ) - ELSE ( 'The tempdb database has files on the C drive. TempDB is not set to Autogrow, hopefully it is big enough. C is also often much slower than other drives, so performance may be suffering.' ) - END AS Details - FROM sys.master_files - WHERE UPPER(LEFT(physical_name, 1)) = 'C' - AND DB_NAME(database_id) = 'tempdb'; - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 26 ) - AND SERVERPROPERTY('EngineEdition') <> 8 /* Azure Managed Instances */ - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 26) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT DISTINCT - 26 AS CheckID , - DB_NAME(database_id) AS DatabaseName , - 20 AS Priority , - 'Reliability' AS FindingsGroup , - 'User Databases on C Drive' AS Finding , - 'https://www.brentozar.com/go/cdrive' AS URL , - ( 'The ' + DB_NAME(database_id) - + ' database has a file on the C drive. Putting databases on the C drive runs the risk of crashing the server when it runs out of space.' ) AS Details - FROM sys.master_files - WHERE UPPER(LEFT(physical_name, 1)) = 'C' - AND DB_NAME(database_id) NOT IN ( 'master', - 'model', 'msdb', - 'tempdb' ) - AND DB_NAME(database_id) NOT IN ( - SELECT DISTINCT - DatabaseName + IF NOT EXISTS ( SELECT 1 FROM #SkipChecks - WHERE CheckID IS NULL OR CheckID = 26 ); - END; + WHERE DatabaseName IS NULL AND CheckID = 122 ) + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 122) WITH NOWAIT; + + /* SQL Server 2012 and newer uses temporary stats for Availability Groups, and those show up as user-created */ + IF EXISTS (SELECT * + FROM sys.all_columns c + INNER JOIN sys.all_objects o ON c.object_id = o.object_id + WHERE c.name = 'is_temporary' AND o.name = 'stats') + + EXEC dbo.sp_MSforeachdb 'USE [?]; + SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + INSERT INTO #BlitzResults + (CheckID, + DatabaseName, + Priority, + FindingsGroup, + Finding, + URL, + Details) + SELECT TOP 1 122, + N''?'', + 200, + ''Performance'', + ''User-Created Statistics In Place'', + ''https://www.brentozar.com/go/userstats'', + (''['' + DB_NAME() + ''] has '' + CAST(SUM(1) AS NVARCHAR(10)) + '' user-created statistics. This indicates that someone is being a rocket scientist with the stats, and might actually be slowing things down, especially during stats updates.'') + from [?].sys.stats WHERE user_created = 1 AND is_temporary = 0 + HAVING SUM(1) > 0 OPTION (RECOMPILE);'; - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 27 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 27) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 27 AS CheckID , - 'master' AS DatabaseName , - 200 AS Priority , - 'Informational' AS FindingsGroup , - 'Tables in the Master Database' AS Finding , - 'https://www.brentozar.com/go/mastuser' AS URL , - ( 'The ' + name - + ' table in the master database was created by end users on ' - + CAST(create_date AS VARCHAR(20)) - + '. Tables in the master database may not be restored in the event of a disaster.' ) AS Details - FROM master.sys.tables - WHERE is_ms_shipped = 0 - AND name NOT IN ('CommandLog','SqlServerVersions','$ndo$srvproperty'); - /* That last one is the Dynamics NAV licensing table: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2426 */ - END; + ELSE + EXEC dbo.sp_MSforeachdb 'USE [?]; + SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + INSERT INTO #BlitzResults + (CheckID, + DatabaseName, + Priority, + FindingsGroup, + Finding, + URL, + Details) + SELECT 122, + N''?'', + 200, + ''Performance'', + ''User-Created Statistics In Place'', + ''https://www.brentozar.com/go/userstats'', + (''['' + DB_NAME() + ''] has '' + CAST(SUM(1) AS NVARCHAR(10)) + '' user-created statistics. This indicates that someone is being a rocket scientist with the stats, and might actually be slowing things down, especially during stats updates.'') + from [?].sys.stats WHERE user_created = 1 + HAVING SUM(1) > 0 OPTION (RECOMPILE);'; - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 28 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 28) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 28 AS CheckID , - 'msdb' AS DatabaseName , - 200 AS Priority , - 'Informational' AS FindingsGroup , - 'Tables in the MSDB Database' AS Finding , - 'https://www.brentozar.com/go/msdbuser' AS URL , - ( 'The ' + name - + ' table in the msdb database was created by end users on ' - + CAST(create_date AS VARCHAR(20)) - + '. Tables in the msdb database may not be restored in the event of a disaster.' ) AS Details - FROM msdb.sys.tables - WHERE is_ms_shipped = 0 AND name NOT LIKE '%DTA_%'; - END; + END; /* IF NOT EXISTS ( SELECT 1 */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 29 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 29) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 29 AS CheckID , - 'model' AS DatabaseName , - 200 AS Priority , - 'Informational' AS FindingsGroup , - 'Tables in the Model Database' AS Finding , - 'https://www.brentozar.com/go/model' AS URL , - ( 'The ' + name - + ' table in the model database was created by end users on ' - + CAST(create_date AS VARCHAR(20)) - + '. Tables in the model database are automatically copied into all new databases.' ) AS Details - FROM model.sys.tables - WHERE is_ms_shipped = 0; - END; + /*Check for high VLF count: this will omit any database snapshots*/ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 30 ) - BEGIN - IF ( SELECT COUNT(*) - FROM msdb.dbo.sysalerts - WHERE severity BETWEEN 19 AND 25 - ) < 7 + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 69 ) + BEGIN + IF @ProductVersionMajor >= 11 - BEGIN + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d] (2012 version of Log Info).', 0, 1, 69) WITH NOWAIT; + + EXEC sp_MSforeachdb N'USE [?]; + SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + INSERT INTO #LogInfo2012 + EXEC sp_executesql N''DBCC LogInfo() WITH NO_INFOMSGS''; + IF @@ROWCOUNT > 999 + BEGIN + INSERT INTO #BlitzResults + ( CheckID + ,DatabaseName + ,Priority + ,FindingsGroup + ,Finding + ,URL + ,Details) + SELECT 69 + ,DB_NAME() + ,170 + ,''File Configuration'' + ,''High VLF Count'' + ,''https://www.brentozar.com/go/vlf'' + ,''The ['' + DB_NAME() + ''] database has '' + CAST(COUNT(*) as VARCHAR(20)) + '' virtual log files (VLFs). This may be slowing down startup, restores, and even inserts/updates/deletes.'' + FROM #LogInfo2012 + WHERE EXISTS (SELECT name FROM master.sys.databases + WHERE source_database_id is null) OPTION (RECOMPILE); + END + TRUNCATE TABLE #LogInfo2012;'; + DROP TABLE #LogInfo2012; + END; + ELSE + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d] (pre-2012 version of Log Info).', 0, 1, 69) WITH NOWAIT; + + EXEC sp_MSforeachdb N'USE [?]; + SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + INSERT INTO #LogInfo + EXEC sp_executesql N''DBCC LogInfo() WITH NO_INFOMSGS''; + IF @@ROWCOUNT > 999 + BEGIN + INSERT INTO #BlitzResults + ( CheckID + ,DatabaseName + ,Priority + ,FindingsGroup + ,Finding + ,URL + ,Details) + SELECT 69 + ,DB_NAME() + ,170 + ,''File Configuration'' + ,''High VLF Count'' + ,''https://www.brentozar.com/go/vlf'' + ,''The ['' + DB_NAME() + ''] database has '' + CAST(COUNT(*) as VARCHAR(20)) + '' virtual log files (VLFs). This may be slowing down startup, restores, and even inserts/updates/deletes.'' + FROM #LogInfo + WHERE EXISTS (SELECT name FROM master.sys.databases + WHERE source_database_id is null) OPTION (RECOMPILE); + END + TRUNCATE TABLE #LogInfo;'; + DROP TABLE #LogInfo; + END; + END; - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 30) WITH NOWAIT; + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 80 ) + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 80) WITH NOWAIT; + + EXEC dbo.sp_MSforeachdb 'USE [?]; + SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + INSERT INTO #BlitzResults (CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details) + SELECT DISTINCT 80, DB_NAME(), 170, ''Reliability'', ''Max File Size Set'', ''https://www.brentozar.com/go/maxsize'', + (''The ['' + DB_NAME() + ''] database file '' + df.name + '' has a max file size set to '' + + CAST(CAST(df.max_size AS BIGINT) * 8 / 1024 AS VARCHAR(100)) + + ''MB. If it runs out of space, the database will stop working even though there may be drive space available.'') + FROM sys.database_files df + WHERE 0 = (SELECT is_read_only FROM sys.databases WHERE name = ''?'') + AND df.max_size <> 268435456 + AND df.max_size <> -1 + AND df.type <> 2 + AND df.growth > 0 + AND df.name <> ''DWDiagnostics'' OPTION (RECOMPILE);'; - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 30 AS CheckID , - 200 AS Priority , - 'Monitoring' AS FindingsGroup , - 'Not All Alerts Configured' AS Finding , - 'https://www.brentozar.com/go/alert' AS URL , - ( 'Not all SQL Server Agent alerts have been configured. This is a free, easy way to get notified of corruption, job failures, or major outages even before monitoring systems pick it up.' ) AS Details; - END; - END; + DELETE br + FROM #BlitzResults br + INNER JOIN #SkipChecks sc ON sc.CheckID = 80 AND br.DatabaseName = sc.DatabaseName; + END; + + + /* Check if columnstore indexes are in use - for Github issue #615 */ + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 74 ) /* Trace flags */ + BEGIN + TRUNCATE TABLE #TemporaryDatabaseResults; + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 74) WITH NOWAIT; + + EXEC dbo.sp_MSforeachdb 'USE [?]; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; IF EXISTS(SELECT * FROM sys.indexes WHERE type IN (5,6)) INSERT INTO #TemporaryDatabaseResults (DatabaseName, Finding) VALUES (DB_NAME(), ''Yup'') OPTION (RECOMPILE);'; + IF EXISTS (SELECT * FROM #TemporaryDatabaseResults) SET @ColumnStoreIndexesInUse = 1; + END; - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 59 ) - BEGIN - IF EXISTS ( SELECT * - FROM msdb.dbo.sysalerts - WHERE enabled = 1 - AND COALESCE(has_notification, 0) = 0 - AND (job_id IS NULL OR job_id = 0x)) + /* Non-Default Database Scoped Config - Github issue #598 */ + IF EXISTS ( SELECT * FROM sys.all_objects WHERE [name] = 'database_scoped_configurations' ) + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d] through [%d] and [%d] through [%d].', 0, 1, 194, 197, 237, 255) WITH NOWAIT; + + INSERT INTO #DatabaseScopedConfigurationDefaults (configuration_id, [name], default_value, default_value_for_secondary, CheckID) + SELECT 1, 'MAXDOP', '0', NULL, 194 + UNION ALL + SELECT 2, 'LEGACY_CARDINALITY_ESTIMATION', '0', NULL, 195 + UNION ALL + SELECT 3, 'PARAMETER_SNIFFING', '1', NULL, 196 + UNION ALL + SELECT 4, 'QUERY_OPTIMIZER_HOTFIXES', '0', NULL, 197 + UNION ALL + SELECT 6, 'IDENTITY_CACHE', '1', NULL, 237 + UNION ALL + SELECT 7, 'INTERLEAVED_EXECUTION_TVF', '1', NULL, 238 + UNION ALL + SELECT 8, 'BATCH_MODE_MEMORY_GRANT_FEEDBACK', '1', NULL, 239 + UNION ALL + SELECT 9, 'BATCH_MODE_ADAPTIVE_JOINS', '1', NULL, 240 + UNION ALL + SELECT 10, 'TSQL_SCALAR_UDF_INLINING', '1', NULL, 241 + UNION ALL + SELECT 11, 'ELEVATE_ONLINE', 'OFF', NULL, 242 + UNION ALL + SELECT 12, 'ELEVATE_RESUMABLE', 'OFF', NULL, 243 + UNION ALL + SELECT 13, 'OPTIMIZE_FOR_AD_HOC_WORKLOADS', '0', NULL, 244 + UNION ALL + SELECT 14, 'XTP_PROCEDURE_EXECUTION_STATISTICS', '0', NULL, 245 + UNION ALL + SELECT 15, 'XTP_QUERY_EXECUTION_STATISTICS', '0', NULL, 246 + UNION ALL + SELECT 16, 'ROW_MODE_MEMORY_GRANT_FEEDBACK', '1', NULL, 247 + UNION ALL + SELECT 17, 'ISOLATE_SECURITY_POLICY_CARDINALITY', '0', NULL, 248 + UNION ALL + SELECT 18, 'BATCH_MODE_ON_ROWSTORE', '1', NULL, 249 + UNION ALL + SELECT 19, 'DEFERRED_COMPILATION_TV', '1', NULL, 250 + UNION ALL + SELECT 20, 'ACCELERATED_PLAN_FORCING', '1', NULL, 251 + UNION ALL + SELECT 21, 'GLOBAL_TEMPORARY_TABLE_AUTO_DROP', '1', NULL, 252 + UNION ALL + SELECT 22, 'LIGHTWEIGHT_QUERY_PROFILING', '1', NULL, 253 + UNION ALL + SELECT 23, 'VERBOSE_TRUNCATION_WARNINGS', '1', NULL, 254 + UNION ALL + SELECT 24, 'LAST_QUERY_PLAN_STATS', '0', NULL, 255; + EXEC dbo.sp_MSforeachdb 'USE [?]; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; INSERT INTO #BlitzResults (CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details) + SELECT def1.CheckID, DB_NAME(), 210, ''Non-Default Database Scoped Config'', dsc.[name], ''https://www.brentozar.com/go/dbscope'', (''Set value: '' + COALESCE(CAST(dsc.value AS NVARCHAR(100)),''Empty'') + '' Default: '' + COALESCE(CAST(def1.default_value AS NVARCHAR(100)),''Empty'') + '' Set value for secondary: '' + COALESCE(CAST(dsc.value_for_secondary AS NVARCHAR(100)),''Empty'') + '' Default value for secondary: '' + COALESCE(CAST(def1.default_value_for_secondary AS NVARCHAR(100)),''Empty'')) + FROM [?].sys.database_scoped_configurations dsc + INNER JOIN #DatabaseScopedConfigurationDefaults def1 ON dsc.configuration_id = def1.configuration_id + LEFT OUTER JOIN #DatabaseScopedConfigurationDefaults def ON dsc.configuration_id = def.configuration_id AND (cast(dsc.value as nvarchar(100)) = cast(def.default_value as nvarchar(100)) OR dsc.value IS NULL) AND (dsc.value_for_secondary = def.default_value_for_secondary OR dsc.value_for_secondary IS NULL) + LEFT OUTER JOIN #SkipChecks sk ON (sk.CheckID IS NULL OR def.CheckID = sk.CheckID) AND (sk.DatabaseName IS NULL OR sk.DatabaseName = DB_NAME()) + WHERE def.configuration_id IS NULL AND sk.CheckID IS NULL ORDER BY 1 + OPTION (RECOMPILE);'; + END; - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 59) WITH NOWAIT; + /* Check 218 - Show me the dodgy SET Options */ + IF NOT EXISTS ( + SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL + AND CheckID = 218 + ) + BEGIN + IF @Debug IN (1,2) + BEGIN + RAISERROR ('Running CheckId [%d].',0,1,218) WITH NOWAIT; + END - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 59 AS CheckID , - 200 AS Priority , - 'Monitoring' AS FindingsGroup , - 'Alerts Configured without Follow Up' AS Finding , - 'https://www.brentozar.com/go/alert' AS URL , - ( 'SQL Server Agent alerts have been configured but they either do not notify anyone or else they do not take any action. This is a free, easy way to get notified of corruption, job failures, or major outages even before monitoring systems pick it up.' ) AS Details; - - END; - END; + EXECUTE sp_MSforeachdb 'USE [?]; + SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + INSERT INTO #BlitzResults (CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details) + SELECT 218 AS CheckID + ,''?'' AS DatabaseName + ,150 AS Priority + ,''Performance'' AS FindingsGroup + ,''Objects created with dangerous SET Options'' AS Finding + ,''https://www.brentozar.com/go/badset'' AS URL + ,''The '' + QUOTENAME(DB_NAME()) + + '' database has '' + CONVERT(VARCHAR(20),COUNT(1)) + + '' objects that were created with dangerous ANSI_NULL or QUOTED_IDENTIFIER options.'' + + '' These objects can break when using filtered indexes, indexed views'' + + '' and other advanced SQL features.'' AS Details + FROM sys.sql_modules sm + JOIN sys.objects o ON o.[object_id] = sm.[object_id] + AND ( + sm.uses_ansi_nulls <> 1 + OR sm.uses_quoted_identifier <> 1 + ) + AND o.is_ms_shipped = 0 + HAVING COUNT(1) > 0;'; + END; --of Check 218. - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 96 ) - BEGIN - IF NOT EXISTS ( SELECT * - FROM msdb.dbo.sysalerts - WHERE message_id IN ( 823, 824, 825 ) ) - - BEGIN; - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 96) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 96 AS CheckID , - 200 AS Priority , - 'Monitoring' AS FindingsGroup , - 'No Alerts for Corruption' AS Finding , - 'https://www.brentozar.com/go/alert' AS URL , - ( 'SQL Server Agent alerts do not exist for errors 823, 824, and 825. These three errors can give you notification about early hardware failure. Enabling them can prevent you a lot of heartbreak.' ) AS Details; - - END; - END; + /* Check 225 - Reliability - Resumable Index Operation Paused */ + IF NOT EXISTS ( + SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL + AND CheckID = 225 + ) + AND EXISTS (SELECT * FROM sys.all_objects WHERE name = 'index_resumable_operations') + BEGIN + IF @Debug IN (1,2) + BEGIN + RAISERROR ('Running CheckId [%d].',0,1,218) WITH NOWAIT; + END - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 61 ) - BEGIN - IF NOT EXISTS ( SELECT * - FROM msdb.dbo.sysalerts - WHERE severity BETWEEN 19 AND 25 ) - - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 61) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 61 AS CheckID , - 200 AS Priority , - 'Monitoring' AS FindingsGroup , - 'No Alerts for Sev 19-25' AS Finding , - 'https://www.brentozar.com/go/alert' AS URL , - ( 'SQL Server Agent alerts do not exist for severity levels 19 through 25. These are some very severe SQL Server errors. Knowing that these are happening may let you recover from errors faster.' ) AS Details; - - END; - - END; - - --check for disabled alerts - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 98 ) - BEGIN - IF EXISTS ( SELECT name - FROM msdb.dbo.sysalerts - WHERE enabled = 0 ) - - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 98) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 98 AS CheckID , - 200 AS Priority , - 'Monitoring' AS FindingsGroup , - 'Alerts Disabled' AS Finding , - 'https://www.brentozar.com/go/alert' AS URL , - ( 'The following Alert is disabled, please review and enable if desired: ' - + name ) AS Details - FROM msdb.dbo.sysalerts - WHERE enabled = 0; - END; - END; + EXECUTE sp_MSforeachdb 'USE [?]; + SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + INSERT INTO #BlitzResults (CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details) + SELECT 225 AS CheckID + ,''?'' AS DatabaseName + ,200 AS Priority + ,''Reliability'' AS FindingsGroup + ,''Resumable Index Operation Paused'' AS Finding + ,''https://www.brentozar.com/go/resumable'' AS URL + ,iro.state_desc + N'' since '' + CONVERT(NVARCHAR(50), last_pause_time, 120) + '', '' + + CAST(iro.percent_complete AS NVARCHAR(20)) + ''% complete: '' + + CAST(iro.sql_text AS NVARCHAR(1000)) AS Details + FROM sys.index_resumable_operations iro + JOIN sys.objects o ON iro.[object_id] = o.[object_id] + WHERE iro.state <> 0;'; + END; --of Check 225. - --check for alerts that do NOT include event descriptions in their outputs via email/pager/net-send - IF NOT EXISTS ( - SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL - AND CheckID = 219 - ) - BEGIN; - IF @Debug IN (1, 2) - BEGIN; - RAISERROR ('Running CheckId [%d].', 0, 1, 219) WITH NOWAIT; - END; + --/* Check 220 - Statistics Without Histograms */ + --IF NOT EXISTS ( + -- SELECT 1 + -- FROM #SkipChecks + -- WHERE DatabaseName IS NULL + -- AND CheckID = 220 + -- ) + -- AND EXISTS (SELECT * FROM sys.all_objects WHERE name = 'dm_db_stats_histogram') + --BEGIN + -- IF @Debug IN (1,2) + -- BEGIN + -- RAISERROR ('Running CheckId [%d].',0,1,220) WITH NOWAIT; + -- END - INSERT INTO #BlitzResults ( - CheckID - ,[Priority] - ,FindingsGroup - ,Finding - ,[URL] - ,Details - ) - SELECT 219 AS CheckID - ,200 AS [Priority] - ,'Monitoring' AS FindingsGroup - ,'Alerts Without Event Descriptions' AS Finding - ,'https://www.brentozar.com/go/alert' AS [URL] - ,('The following Alert is not including detailed event descriptions in its output messages: ' + QUOTENAME([name]) - + '. You can fix it by ticking the relevant boxes in its Properties --> Options page.') AS Details - FROM msdb.dbo.sysalerts - WHERE [enabled] = 1 - AND include_event_description = 0 --bitmask: 1 = email, 2 = pager, 4 = net send - ; - END; + -- EXECUTE sp_MSforeachdb 'USE [?]; + -- SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + -- INSERT INTO #BlitzResults (CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details) + -- SELECT 220 AS CheckID + -- ,DB_NAME() AS DatabaseName + -- ,110 AS Priority + -- ,''Performance'' AS FindingsGroup + -- ,''Statistics Without Histograms'' AS Finding + -- ,''https://www.brentozar.com/go/brokenstats'' AS URL + -- ,CAST(COUNT(DISTINCT o.object_id) AS VARCHAR(100)) + '' tables have statistics that have not been updated since the database was restored or upgraded,'' + -- + '' and have no data in their histogram. See the More Info URL for a script to update them. '' AS Details + -- FROM sys.all_objects o + -- INNER JOIN sys.stats s ON o.object_id = s.object_id AND s.has_filter = 0 + -- OUTER APPLY sys.dm_db_stats_histogram(o.object_id, s.stats_id) h + -- WHERE o.is_ms_shipped = 0 AND o.type_desc = ''USER_TABLE'' + -- AND h.object_id IS NULL + -- AND 0 < (SELECT SUM(row_count) FROM sys.dm_db_partition_stats ps WHERE ps.object_id = o.object_id) + -- AND ''?'' NOT IN (''master'', ''model'', ''msdb'', ''tempdb'') + -- HAVING COUNT(DISTINCT o.object_id) > 0;'; + --END; --of Check 220. - --check whether we have NO ENABLED operators! + /*Check for the last good DBCC CHECKDB date */ IF NOT EXISTS ( SELECT 1 FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 31 ) - BEGIN; - IF NOT EXISTS ( SELECT * - FROM msdb.dbo.sysoperators - WHERE enabled = 1 ) - - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 31) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 31 AS CheckID , - 200 AS Priority , - 'Monitoring' AS FindingsGroup , - 'No Operators Configured/Enabled' AS Finding , - 'https://www.brentozar.com/go/op' AS URL , - ( 'No SQL Server Agent operators (emails) have been configured. This is a free, easy way to get notified of corruption, job failures, or major outages even before monitoring systems pick it up.' ) AS Details; - - END; - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 34 ) - BEGIN - IF EXISTS ( SELECT * - FROM sys.all_objects - WHERE name = 'dm_db_mirroring_auto_page_repair' ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 34) WITH NOWAIT; - - SET @StringToExecute = 'INSERT INTO #BlitzResults - (CheckID, - DatabaseName, - Priority, - FindingsGroup, - Finding, - URL, - Details) - SELECT DISTINCT - 34 AS CheckID , - db.name , - 1 AS Priority , - ''Corruption'' AS FindingsGroup , - ''Database Corruption Detected'' AS Finding , - ''https://www.brentozar.com/go/repair'' AS URL , - ( ''Database mirroring has automatically repaired at least one corrupt page in the last 30 days. For more information, query the DMV sys.dm_db_mirroring_auto_page_repair.'' ) AS Details - FROM (SELECT rp2.database_id, rp2.modification_time - FROM sys.dm_db_mirroring_auto_page_repair rp2 - WHERE rp2.[database_id] not in ( - SELECT db2.[database_id] - FROM sys.databases as db2 - WHERE db2.[state] = 1 - ) ) as rp - INNER JOIN master.sys.databases db ON rp.database_id = db.database_id - WHERE rp.modification_time >= DATEADD(dd, -30, GETDATE()) OPTION (RECOMPILE);'; - - IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; - IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; - - EXECUTE(@StringToExecute); - END; - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 89 ) - BEGIN - IF EXISTS ( SELECT * - FROM sys.all_objects - WHERE name = 'dm_hadr_auto_page_repair' ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 89) WITH NOWAIT; - - SET @StringToExecute = 'INSERT INTO #BlitzResults - (CheckID, - DatabaseName, - Priority, - FindingsGroup, - Finding, - URL, - Details) - SELECT DISTINCT - 89 AS CheckID , - db.name , - 1 AS Priority , - ''Corruption'' AS FindingsGroup , - ''Database Corruption Detected'' AS Finding , - ''https://www.brentozar.com/go/repair'' AS URL , - ( ''Availability Groups has automatically repaired at least one corrupt page in the last 30 days. For more information, query the DMV sys.dm_hadr_auto_page_repair.'' ) AS Details - FROM sys.dm_hadr_auto_page_repair rp - INNER JOIN master.sys.databases db ON rp.database_id = db.database_id - WHERE rp.modification_time >= DATEADD(dd, -30, GETDATE()) OPTION (RECOMPILE) ;'; - - IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; - IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; - - EXECUTE(@StringToExecute); - END; - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 90 ) - BEGIN - IF EXISTS ( SELECT * - FROM msdb.sys.all_objects - WHERE name = 'suspect_pages' ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 90) WITH NOWAIT; - - SET @StringToExecute = 'INSERT INTO #BlitzResults - (CheckID, - DatabaseName, - Priority, - FindingsGroup, - Finding, - URL, - Details) - SELECT DISTINCT - 90 AS CheckID , - db.name , - 1 AS Priority , - ''Corruption'' AS FindingsGroup , - ''Database Corruption Detected'' AS Finding , - ''https://www.brentozar.com/go/repair'' AS URL , - ( ''SQL Server has detected at least one corrupt page in the last 30 days. For more information, query the system table msdb.dbo.suspect_pages.'' ) AS Details - FROM msdb.dbo.suspect_pages sp - INNER JOIN master.sys.databases db ON sp.database_id = db.database_id - WHERE sp.last_update_date >= DATEADD(dd, -30, GETDATE()) OPTION (RECOMPILE);'; + WHERE DatabaseName IS NULL AND CheckID = 68 ) + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 68) WITH NOWAIT; + + /* Removed as populating the #DBCCs table now done in advance as data is uses for multiple checks*/ + --EXEC sp_MSforeachdb N'USE [?]; + --SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + --INSERT #DBCCs + -- (ParentObject, + -- Object, + -- Field, + -- Value) + --EXEC (''DBCC DBInfo() With TableResults, NO_INFOMSGS''); + --UPDATE #DBCCs SET DbName = N''?'' WHERE DbName IS NULL OPTION (RECOMPILE);'; - IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; - IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; + WITH DB2 + AS ( SELECT DISTINCT + Field , + Value , + DbName + FROM #DBCCs + INNER JOIN sys.databases d ON #DBCCs.DbName = d.name + WHERE Field = 'dbi_dbccLastKnownGood' + AND d.create_date < DATEADD(dd, -14, GETDATE()) + ) + INSERT INTO #BlitzResults + ( CheckID , + DatabaseName , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT 68 AS CheckID , + DB2.DbName AS DatabaseName , + 1 AS PRIORITY , + 'Reliability' AS FindingsGroup , + 'Last good DBCC CHECKDB over 2 weeks old' AS Finding , + 'https://www.brentozar.com/go/checkdb' AS URL , + 'Last successful CHECKDB: ' + + CASE DB2.Value + WHEN '1900-01-01 00:00:00.000' + THEN ' never.' + ELSE DB2.Value + END AS Details + FROM DB2 + WHERE DB2.DbName <> 'tempdb' + AND DB2.DbName NOT IN ( SELECT DISTINCT + DatabaseName + FROM + #SkipChecks + WHERE CheckID IS NULL OR CheckID = 68) + AND DB2.DbName NOT IN ( SELECT name + FROM sys.databases + WHERE is_read_only = 1) + AND CONVERT(DATETIME, DB2.Value, 121) < DATEADD(DD, + -14, + CURRENT_TIMESTAMP); + END; - EXECUTE(@StringToExecute); - END; - END; + END; /* IF @CheckUserDatabaseObjects = 1 */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 36 ) + IF @CheckProcedureCache = 1 + BEGIN - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 36) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT DISTINCT - 36 AS CheckID , - 150 AS Priority , - 'Performance' AS FindingsGroup , - 'Slow Storage Reads on Drive ' - + UPPER(LEFT(mf.physical_name, 1)) AS Finding , - 'https://www.brentozar.com/go/slow' AS URL , - 'Reads are averaging longer than 200ms for at least one database on this drive. For specific database file speeds, run the query from the information link.' AS Details - FROM sys.dm_io_virtual_file_stats(NULL, NULL) - AS fs - INNER JOIN sys.master_files AS mf ON fs.database_id = mf.database_id - AND fs.[file_id] = mf.[file_id] - WHERE ( io_stall_read_ms / ( 1.0 + num_of_reads ) ) > 200 - AND num_of_reads > 100000; - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 37 ) + IF @Debug IN (1, 2) RAISERROR('Begin checking procedure cache', 0, 1) WITH NOWAIT; + BEGIN - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 37) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT DISTINCT - 37 AS CheckID , - 150 AS Priority , - 'Performance' AS FindingsGroup , - 'Slow Storage Writes on Drive ' - + UPPER(LEFT(mf.physical_name, 1)) AS Finding , - 'https://www.brentozar.com/go/slow' AS URL , - 'Writes are averaging longer than 100ms for at least one database on this drive. For specific database file speeds, run the query from the information link.' AS Details - FROM sys.dm_io_virtual_file_stats(NULL, NULL) - AS fs - INNER JOIN sys.master_files AS mf ON fs.database_id = mf.database_id - AND fs.[file_id] = mf.[file_id] - WHERE ( io_stall_write_ms / ( 1.0 - + num_of_writes ) ) > 100 - AND num_of_writes > 100000; - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 40 ) - BEGIN - IF ( SELECT COUNT(*) - FROM tempdb.sys.database_files - WHERE type_desc = 'ROWS' - ) = 1 + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 35 ) BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 40) WITH NOWAIT; - + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 35) WITH NOWAIT; + INSERT INTO #BlitzResults ( CheckID , - DatabaseName , Priority , FindingsGroup , Finding , URL , Details ) - VALUES ( 40 , - 'tempdb' , - 170 , - 'File Configuration' , - 'TempDB Only Has 1 Data File' , - 'https://www.brentozar.com/go/tempdb' , - 'TempDB is only configured with one data file. More data files are usually required to alleviate SGAM contention.' - ); + SELECT 35 AS CheckID , + 100 AS Priority , + 'Performance' AS FindingsGroup , + 'Single-Use Plans in Procedure Cache' AS Finding , + 'https://www.brentozar.com/go/single' AS URL , + ( CAST(COUNT(*) AS VARCHAR(10)) + + ' query plans are taking up memory in the procedure cache. This may be wasted memory if we cache plans for queries that never get called again. This may be a good use case for SQL Server 2008''s Optimize for Ad Hoc or for Forced Parameterization.' ) AS Details + FROM sys.dm_exec_cached_plans AS cp + WHERE cp.usecounts = 1 + AND cp.objtype = 'Adhoc' + AND EXISTS ( SELECT + 1 + FROM sys.configurations + WHERE + name = 'optimize for ad hoc workloads' + AND value_in_use = 0 ) + HAVING COUNT(*) > 1; END; - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 183 ) - - BEGIN - IF ( SELECT COUNT (distinct [size]) - FROM tempdb.sys.database_files - WHERE type_desc = 'ROWS' - HAVING MAX((size * 8) / (1024. * 1024)) - MIN((size * 8) / (1024. * 1024)) > 1. - ) <> 1 + /* Set up the cache tables. Different on 2005 since it doesn't support query_hash, query_plan_hash. */ + IF @@VERSION LIKE '%Microsoft SQL Server 2005%' BEGIN + IF @CheckProcedureCacheFilter = 'CPU' + OR @CheckProcedureCacheFilter IS NULL + BEGIN + SET @StringToExecute = 'WITH queries ([sql_handle],[statement_start_offset],[statement_end_offset],[plan_generation_num],[plan_handle],[creation_time],[last_execution_time],[execution_count],[total_worker_time],[last_worker_time],[min_worker_time],[max_worker_time],[total_physical_reads],[last_physical_reads],[min_physical_reads],[max_physical_reads],[total_logical_writes],[last_logical_writes],[min_logical_writes],[max_logical_writes],[total_logical_reads],[last_logical_reads],[min_logical_reads],[max_logical_reads],[total_clr_time],[last_clr_time],[min_clr_time],[max_clr_time],[total_elapsed_time],[last_elapsed_time],[min_elapsed_time],[max_elapsed_time]) + AS (SELECT TOP 20 qs.[sql_handle],qs.[statement_start_offset],qs.[statement_end_offset],qs.[plan_generation_num],qs.[plan_handle],qs.[creation_time],qs.[last_execution_time],qs.[execution_count],qs.[total_worker_time],qs.[last_worker_time],qs.[min_worker_time],qs.[max_worker_time],qs.[total_physical_reads],qs.[last_physical_reads],qs.[min_physical_reads],qs.[max_physical_reads],qs.[total_logical_writes],qs.[last_logical_writes],qs.[min_logical_writes],qs.[max_logical_writes],qs.[total_logical_reads],qs.[last_logical_reads],qs.[min_logical_reads],qs.[max_logical_reads],qs.[total_clr_time],qs.[last_clr_time],qs.[min_clr_time],qs.[max_clr_time],qs.[total_elapsed_time],qs.[last_elapsed_time],qs.[min_elapsed_time],qs.[max_elapsed_time] + FROM sys.dm_exec_query_stats qs + ORDER BY qs.total_worker_time DESC) + INSERT INTO #dm_exec_query_stats ([sql_handle],[statement_start_offset],[statement_end_offset],[plan_generation_num],[plan_handle],[creation_time],[last_execution_time],[execution_count],[total_worker_time],[last_worker_time],[min_worker_time],[max_worker_time],[total_physical_reads],[last_physical_reads],[min_physical_reads],[max_physical_reads],[total_logical_writes],[last_logical_writes],[min_logical_writes],[max_logical_writes],[total_logical_reads],[last_logical_reads],[min_logical_reads],[max_logical_reads],[total_clr_time],[last_clr_time],[min_clr_time],[max_clr_time],[total_elapsed_time],[last_elapsed_time],[min_elapsed_time],[max_elapsed_time]) + SELECT qs.[sql_handle],qs.[statement_start_offset],qs.[statement_end_offset],qs.[plan_generation_num],qs.[plan_handle],qs.[creation_time],qs.[last_execution_time],qs.[execution_count],qs.[total_worker_time],qs.[last_worker_time],qs.[min_worker_time],qs.[max_worker_time],qs.[total_physical_reads],qs.[last_physical_reads],qs.[min_physical_reads],qs.[max_physical_reads],qs.[total_logical_writes],qs.[last_logical_writes],qs.[min_logical_writes],qs.[max_logical_writes],qs.[total_logical_reads],qs.[last_logical_reads],qs.[min_logical_reads],qs.[max_logical_reads],qs.[total_clr_time],qs.[last_clr_time],qs.[min_clr_time],qs.[max_clr_time],qs.[total_elapsed_time],qs.[last_elapsed_time],qs.[min_elapsed_time],qs.[max_elapsed_time] + FROM queries qs + LEFT OUTER JOIN #dm_exec_query_stats qsCaught ON qs.sql_handle = qsCaught.sql_handle AND qs.plan_handle = qsCaught.plan_handle AND qs.statement_start_offset = qsCaught.statement_start_offset + WHERE qsCaught.sql_handle IS NULL OPTION (RECOMPILE);'; + EXECUTE(@StringToExecute); + END; - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 183) WITH NOWAIT; + IF @CheckProcedureCacheFilter = 'Reads' + OR @CheckProcedureCacheFilter IS NULL + BEGIN + SET @StringToExecute = 'WITH queries ([sql_handle],[statement_start_offset],[statement_end_offset],[plan_generation_num],[plan_handle],[creation_time],[last_execution_time],[execution_count],[total_worker_time],[last_worker_time],[min_worker_time],[max_worker_time],[total_physical_reads],[last_physical_reads],[min_physical_reads],[max_physical_reads],[total_logical_writes],[last_logical_writes],[min_logical_writes],[max_logical_writes],[total_logical_reads],[last_logical_reads],[min_logical_reads],[max_logical_reads],[total_clr_time],[last_clr_time],[min_clr_time],[max_clr_time],[total_elapsed_time],[last_elapsed_time],[min_elapsed_time],[max_elapsed_time]) + AS (SELECT TOP 20 qs.[sql_handle],qs.[statement_start_offset],qs.[statement_end_offset],qs.[plan_generation_num],qs.[plan_handle],qs.[creation_time],qs.[last_execution_time],qs.[execution_count],qs.[total_worker_time],qs.[last_worker_time],qs.[min_worker_time],qs.[max_worker_time],qs.[total_physical_reads],qs.[last_physical_reads],qs.[min_physical_reads],qs.[max_physical_reads],qs.[total_logical_writes],qs.[last_logical_writes],qs.[min_logical_writes],qs.[max_logical_writes],qs.[total_logical_reads],qs.[last_logical_reads],qs.[min_logical_reads],qs.[max_logical_reads],qs.[total_clr_time],qs.[last_clr_time],qs.[min_clr_time],qs.[max_clr_time],qs.[total_elapsed_time],qs.[last_elapsed_time],qs.[min_elapsed_time],qs.[max_elapsed_time] + FROM sys.dm_exec_query_stats qs + ORDER BY qs.total_logical_reads DESC) + INSERT INTO #dm_exec_query_stats ([sql_handle],[statement_start_offset],[statement_end_offset],[plan_generation_num],[plan_handle],[creation_time],[last_execution_time],[execution_count],[total_worker_time],[last_worker_time],[min_worker_time],[max_worker_time],[total_physical_reads],[last_physical_reads],[min_physical_reads],[max_physical_reads],[total_logical_writes],[last_logical_writes],[min_logical_writes],[max_logical_writes],[total_logical_reads],[last_logical_reads],[min_logical_reads],[max_logical_reads],[total_clr_time],[last_clr_time],[min_clr_time],[max_clr_time],[total_elapsed_time],[last_elapsed_time],[min_elapsed_time],[max_elapsed_time]) + SELECT qs.[sql_handle],qs.[statement_start_offset],qs.[statement_end_offset],qs.[plan_generation_num],qs.[plan_handle],qs.[creation_time],qs.[last_execution_time],qs.[execution_count],qs.[total_worker_time],qs.[last_worker_time],qs.[min_worker_time],qs.[max_worker_time],qs.[total_physical_reads],qs.[last_physical_reads],qs.[min_physical_reads],qs.[max_physical_reads],qs.[total_logical_writes],qs.[last_logical_writes],qs.[min_logical_writes],qs.[max_logical_writes],qs.[total_logical_reads],qs.[last_logical_reads],qs.[min_logical_reads],qs.[max_logical_reads],qs.[total_clr_time],qs.[last_clr_time],qs.[min_clr_time],qs.[max_clr_time],qs.[total_elapsed_time],qs.[last_elapsed_time],qs.[min_elapsed_time],qs.[max_elapsed_time] + FROM queries qs + LEFT OUTER JOIN #dm_exec_query_stats qsCaught ON qs.sql_handle = qsCaught.sql_handle AND qs.plan_handle = qsCaught.plan_handle AND qs.statement_start_offset = qsCaught.statement_start_offset + WHERE qsCaught.sql_handle IS NULL OPTION (RECOMPILE);'; + EXECUTE(@StringToExecute); + END; - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - VALUES ( 183 , - 'tempdb' , - 170 , - 'File Configuration' , - 'TempDB Unevenly Sized Data Files' , - 'https://www.brentozar.com/go/tempdb' , - 'TempDB data files are not configured with the same size. Unevenly sized tempdb data files will result in unevenly sized workloads.' - ); + IF @CheckProcedureCacheFilter = 'ExecCount' + OR @CheckProcedureCacheFilter IS NULL + BEGIN + SET @StringToExecute = 'WITH queries ([sql_handle],[statement_start_offset],[statement_end_offset],[plan_generation_num],[plan_handle],[creation_time],[last_execution_time],[execution_count],[total_worker_time],[last_worker_time],[min_worker_time],[max_worker_time],[total_physical_reads],[last_physical_reads],[min_physical_reads],[max_physical_reads],[total_logical_writes],[last_logical_writes],[min_logical_writes],[max_logical_writes],[total_logical_reads],[last_logical_reads],[min_logical_reads],[max_logical_reads],[total_clr_time],[last_clr_time],[min_clr_time],[max_clr_time],[total_elapsed_time],[last_elapsed_time],[min_elapsed_time],[max_elapsed_time]) + AS (SELECT TOP 20 qs.[sql_handle],qs.[statement_start_offset],qs.[statement_end_offset],qs.[plan_generation_num],qs.[plan_handle],qs.[creation_time],qs.[last_execution_time],qs.[execution_count],qs.[total_worker_time],qs.[last_worker_time],qs.[min_worker_time],qs.[max_worker_time],qs.[total_physical_reads],qs.[last_physical_reads],qs.[min_physical_reads],qs.[max_physical_reads],qs.[total_logical_writes],qs.[last_logical_writes],qs.[min_logical_writes],qs.[max_logical_writes],qs.[total_logical_reads],qs.[last_logical_reads],qs.[min_logical_reads],qs.[max_logical_reads],qs.[total_clr_time],qs.[last_clr_time],qs.[min_clr_time],qs.[max_clr_time],qs.[total_elapsed_time],qs.[last_elapsed_time],qs.[min_elapsed_time],qs.[max_elapsed_time] + FROM sys.dm_exec_query_stats qs + ORDER BY qs.execution_count DESC) + INSERT INTO #dm_exec_query_stats ([sql_handle],[statement_start_offset],[statement_end_offset],[plan_generation_num],[plan_handle],[creation_time],[last_execution_time],[execution_count],[total_worker_time],[last_worker_time],[min_worker_time],[max_worker_time],[total_physical_reads],[last_physical_reads],[min_physical_reads],[max_physical_reads],[total_logical_writes],[last_logical_writes],[min_logical_writes],[max_logical_writes],[total_logical_reads],[last_logical_reads],[min_logical_reads],[max_logical_reads],[total_clr_time],[last_clr_time],[min_clr_time],[max_clr_time],[total_elapsed_time],[last_elapsed_time],[min_elapsed_time],[max_elapsed_time]) + SELECT qs.[sql_handle],qs.[statement_start_offset],qs.[statement_end_offset],qs.[plan_generation_num],qs.[plan_handle],qs.[creation_time],qs.[last_execution_time],qs.[execution_count],qs.[total_worker_time],qs.[last_worker_time],qs.[min_worker_time],qs.[max_worker_time],qs.[total_physical_reads],qs.[last_physical_reads],qs.[min_physical_reads],qs.[max_physical_reads],qs.[total_logical_writes],qs.[last_logical_writes],qs.[min_logical_writes],qs.[max_logical_writes],qs.[total_logical_reads],qs.[last_logical_reads],qs.[min_logical_reads],qs.[max_logical_reads],qs.[total_clr_time],qs.[last_clr_time],qs.[min_clr_time],qs.[max_clr_time],qs.[total_elapsed_time],qs.[last_elapsed_time],qs.[min_elapsed_time],qs.[max_elapsed_time] + FROM queries qs + LEFT OUTER JOIN #dm_exec_query_stats qsCaught ON qs.sql_handle = qsCaught.sql_handle AND qs.plan_handle = qsCaught.plan_handle AND qs.statement_start_offset = qsCaught.statement_start_offset + WHERE qsCaught.sql_handle IS NULL OPTION (RECOMPILE);'; + EXECUTE(@StringToExecute); + END; + + IF @CheckProcedureCacheFilter = 'Duration' + OR @CheckProcedureCacheFilter IS NULL + BEGIN + SET @StringToExecute = 'WITH queries ([sql_handle],[statement_start_offset],[statement_end_offset],[plan_generation_num],[plan_handle],[creation_time],[last_execution_time],[execution_count],[total_worker_time],[last_worker_time],[min_worker_time],[max_worker_time],[total_physical_reads],[last_physical_reads],[min_physical_reads],[max_physical_reads],[total_logical_writes],[last_logical_writes],[min_logical_writes],[max_logical_writes],[total_logical_reads],[last_logical_reads],[min_logical_reads],[max_logical_reads],[total_clr_time],[last_clr_time],[min_clr_time],[max_clr_time],[total_elapsed_time],[last_elapsed_time],[min_elapsed_time],[max_elapsed_time]) + AS (SELECT TOP 20 qs.[sql_handle],qs.[statement_start_offset],qs.[statement_end_offset],qs.[plan_generation_num],qs.[plan_handle],qs.[creation_time],qs.[last_execution_time],qs.[execution_count],qs.[total_worker_time],qs.[last_worker_time],qs.[min_worker_time],qs.[max_worker_time],qs.[total_physical_reads],qs.[last_physical_reads],qs.[min_physical_reads],qs.[max_physical_reads],qs.[total_logical_writes],qs.[last_logical_writes],qs.[min_logical_writes],qs.[max_logical_writes],qs.[total_logical_reads],qs.[last_logical_reads],qs.[min_logical_reads],qs.[max_logical_reads],qs.[total_clr_time],qs.[last_clr_time],qs.[min_clr_time],qs.[max_clr_time],qs.[total_elapsed_time],qs.[last_elapsed_time],qs.[min_elapsed_time],qs.[max_elapsed_time] + FROM sys.dm_exec_query_stats qs + ORDER BY qs.total_elapsed_time DESC) + INSERT INTO #dm_exec_query_stats ([sql_handle],[statement_start_offset],[statement_end_offset],[plan_generation_num],[plan_handle],[creation_time],[last_execution_time],[execution_count],[total_worker_time],[last_worker_time],[min_worker_time],[max_worker_time],[total_physical_reads],[last_physical_reads],[min_physical_reads],[max_physical_reads],[total_logical_writes],[last_logical_writes],[min_logical_writes],[max_logical_writes],[total_logical_reads],[last_logical_reads],[min_logical_reads],[max_logical_reads],[total_clr_time],[last_clr_time],[min_clr_time],[max_clr_time],[total_elapsed_time],[last_elapsed_time],[min_elapsed_time],[max_elapsed_time]) + SELECT qs.[sql_handle],qs.[statement_start_offset],qs.[statement_end_offset],qs.[plan_generation_num],qs.[plan_handle],qs.[creation_time],qs.[last_execution_time],qs.[execution_count],qs.[total_worker_time],qs.[last_worker_time],qs.[min_worker_time],qs.[max_worker_time],qs.[total_physical_reads],qs.[last_physical_reads],qs.[min_physical_reads],qs.[max_physical_reads],qs.[total_logical_writes],qs.[last_logical_writes],qs.[min_logical_writes],qs.[max_logical_writes],qs.[total_logical_reads],qs.[last_logical_reads],qs.[min_logical_reads],qs.[max_logical_reads],qs.[total_clr_time],qs.[last_clr_time],qs.[min_clr_time],qs.[max_clr_time],qs.[total_elapsed_time],qs.[last_elapsed_time],qs.[min_elapsed_time],qs.[max_elapsed_time] + FROM queries qs + LEFT OUTER JOIN #dm_exec_query_stats qsCaught ON qs.sql_handle = qsCaught.sql_handle AND qs.plan_handle = qsCaught.plan_handle AND qs.statement_start_offset = qsCaught.statement_start_offset + WHERE qsCaught.sql_handle IS NULL OPTION (RECOMPILE);'; + EXECUTE(@StringToExecute); + END; + + END; + IF @ProductVersionMajor >= 10 + BEGIN + IF @CheckProcedureCacheFilter = 'CPU' + OR @CheckProcedureCacheFilter IS NULL + BEGIN + SET @StringToExecute = 'WITH queries ([sql_handle],[statement_start_offset],[statement_end_offset],[plan_generation_num],[plan_handle],[creation_time],[last_execution_time],[execution_count],[total_worker_time],[last_worker_time],[min_worker_time],[max_worker_time],[total_physical_reads],[last_physical_reads],[min_physical_reads],[max_physical_reads],[total_logical_writes],[last_logical_writes],[min_logical_writes],[max_logical_writes],[total_logical_reads],[last_logical_reads],[min_logical_reads],[max_logical_reads],[total_clr_time],[last_clr_time],[min_clr_time],[max_clr_time],[total_elapsed_time],[last_elapsed_time],[min_elapsed_time],[max_elapsed_time],[query_hash],[query_plan_hash]) + AS (SELECT TOP 20 qs.[sql_handle],qs.[statement_start_offset],qs.[statement_end_offset],qs.[plan_generation_num],qs.[plan_handle],qs.[creation_time],qs.[last_execution_time],qs.[execution_count],qs.[total_worker_time],qs.[last_worker_time],qs.[min_worker_time],qs.[max_worker_time],qs.[total_physical_reads],qs.[last_physical_reads],qs.[min_physical_reads],qs.[max_physical_reads],qs.[total_logical_writes],qs.[last_logical_writes],qs.[min_logical_writes],qs.[max_logical_writes],qs.[total_logical_reads],qs.[last_logical_reads],qs.[min_logical_reads],qs.[max_logical_reads],qs.[total_clr_time],qs.[last_clr_time],qs.[min_clr_time],qs.[max_clr_time],qs.[total_elapsed_time],qs.[last_elapsed_time],qs.[min_elapsed_time],qs.[max_elapsed_time],qs.[query_hash],qs.[query_plan_hash] + FROM sys.dm_exec_query_stats qs + ORDER BY qs.total_worker_time DESC) + INSERT INTO #dm_exec_query_stats ([sql_handle],[statement_start_offset],[statement_end_offset],[plan_generation_num],[plan_handle],[creation_time],[last_execution_time],[execution_count],[total_worker_time],[last_worker_time],[min_worker_time],[max_worker_time],[total_physical_reads],[last_physical_reads],[min_physical_reads],[max_physical_reads],[total_logical_writes],[last_logical_writes],[min_logical_writes],[max_logical_writes],[total_logical_reads],[last_logical_reads],[min_logical_reads],[max_logical_reads],[total_clr_time],[last_clr_time],[min_clr_time],[max_clr_time],[total_elapsed_time],[last_elapsed_time],[min_elapsed_time],[max_elapsed_time],[query_hash],[query_plan_hash]) + SELECT qs.[sql_handle],qs.[statement_start_offset],qs.[statement_end_offset],qs.[plan_generation_num],qs.[plan_handle],qs.[creation_time],qs.[last_execution_time],qs.[execution_count],qs.[total_worker_time],qs.[last_worker_time],qs.[min_worker_time],qs.[max_worker_time],qs.[total_physical_reads],qs.[last_physical_reads],qs.[min_physical_reads],qs.[max_physical_reads],qs.[total_logical_writes],qs.[last_logical_writes],qs.[min_logical_writes],qs.[max_logical_writes],qs.[total_logical_reads],qs.[last_logical_reads],qs.[min_logical_reads],qs.[max_logical_reads],qs.[total_clr_time],qs.[last_clr_time],qs.[min_clr_time],qs.[max_clr_time],qs.[total_elapsed_time],qs.[last_elapsed_time],qs.[min_elapsed_time],qs.[max_elapsed_time],qs.[query_hash],qs.[query_plan_hash] + FROM queries qs + LEFT OUTER JOIN #dm_exec_query_stats qsCaught ON qs.sql_handle = qsCaught.sql_handle AND qs.plan_handle = qsCaught.plan_handle AND qs.statement_start_offset = qsCaught.statement_start_offset + WHERE qsCaught.sql_handle IS NULL OPTION (RECOMPILE);'; + EXECUTE(@StringToExecute); + END; + + IF @CheckProcedureCacheFilter = 'Reads' + OR @CheckProcedureCacheFilter IS NULL + BEGIN + SET @StringToExecute = 'WITH queries ([sql_handle],[statement_start_offset],[statement_end_offset],[plan_generation_num],[plan_handle],[creation_time],[last_execution_time],[execution_count],[total_worker_time],[last_worker_time],[min_worker_time],[max_worker_time],[total_physical_reads],[last_physical_reads],[min_physical_reads],[max_physical_reads],[total_logical_writes],[last_logical_writes],[min_logical_writes],[max_logical_writes],[total_logical_reads],[last_logical_reads],[min_logical_reads],[max_logical_reads],[total_clr_time],[last_clr_time],[min_clr_time],[max_clr_time],[total_elapsed_time],[last_elapsed_time],[min_elapsed_time],[max_elapsed_time],[query_hash],[query_plan_hash]) + AS (SELECT TOP 20 qs.[sql_handle],qs.[statement_start_offset],qs.[statement_end_offset],qs.[plan_generation_num],qs.[plan_handle],qs.[creation_time],qs.[last_execution_time],qs.[execution_count],qs.[total_worker_time],qs.[last_worker_time],qs.[min_worker_time],qs.[max_worker_time],qs.[total_physical_reads],qs.[last_physical_reads],qs.[min_physical_reads],qs.[max_physical_reads],qs.[total_logical_writes],qs.[last_logical_writes],qs.[min_logical_writes],qs.[max_logical_writes],qs.[total_logical_reads],qs.[last_logical_reads],qs.[min_logical_reads],qs.[max_logical_reads],qs.[total_clr_time],qs.[last_clr_time],qs.[min_clr_time],qs.[max_clr_time],qs.[total_elapsed_time],qs.[last_elapsed_time],qs.[min_elapsed_time],qs.[max_elapsed_time],qs.[query_hash],qs.[query_plan_hash] + FROM sys.dm_exec_query_stats qs + ORDER BY qs.total_logical_reads DESC) + INSERT INTO #dm_exec_query_stats ([sql_handle],[statement_start_offset],[statement_end_offset],[plan_generation_num],[plan_handle],[creation_time],[last_execution_time],[execution_count],[total_worker_time],[last_worker_time],[min_worker_time],[max_worker_time],[total_physical_reads],[last_physical_reads],[min_physical_reads],[max_physical_reads],[total_logical_writes],[last_logical_writes],[min_logical_writes],[max_logical_writes],[total_logical_reads],[last_logical_reads],[min_logical_reads],[max_logical_reads],[total_clr_time],[last_clr_time],[min_clr_time],[max_clr_time],[total_elapsed_time],[last_elapsed_time],[min_elapsed_time],[max_elapsed_time],[query_hash],[query_plan_hash]) + SELECT qs.[sql_handle],qs.[statement_start_offset],qs.[statement_end_offset],qs.[plan_generation_num],qs.[plan_handle],qs.[creation_time],qs.[last_execution_time],qs.[execution_count],qs.[total_worker_time],qs.[last_worker_time],qs.[min_worker_time],qs.[max_worker_time],qs.[total_physical_reads],qs.[last_physical_reads],qs.[min_physical_reads],qs.[max_physical_reads],qs.[total_logical_writes],qs.[last_logical_writes],qs.[min_logical_writes],qs.[max_logical_writes],qs.[total_logical_reads],qs.[last_logical_reads],qs.[min_logical_reads],qs.[max_logical_reads],qs.[total_clr_time],qs.[last_clr_time],qs.[min_clr_time],qs.[max_clr_time],qs.[total_elapsed_time],qs.[last_elapsed_time],qs.[min_elapsed_time],qs.[max_elapsed_time],qs.[query_hash],qs.[query_plan_hash] + FROM queries qs + LEFT OUTER JOIN #dm_exec_query_stats qsCaught ON qs.sql_handle = qsCaught.sql_handle AND qs.plan_handle = qsCaught.plan_handle AND qs.statement_start_offset = qsCaught.statement_start_offset + WHERE qsCaught.sql_handle IS NULL OPTION (RECOMPILE);'; + EXECUTE(@StringToExecute); + END; + + IF @CheckProcedureCacheFilter = 'ExecCount' + OR @CheckProcedureCacheFilter IS NULL + BEGIN + SET @StringToExecute = 'WITH queries ([sql_handle],[statement_start_offset],[statement_end_offset],[plan_generation_num],[plan_handle],[creation_time],[last_execution_time],[execution_count],[total_worker_time],[last_worker_time],[min_worker_time],[max_worker_time],[total_physical_reads],[last_physical_reads],[min_physical_reads],[max_physical_reads],[total_logical_writes],[last_logical_writes],[min_logical_writes],[max_logical_writes],[total_logical_reads],[last_logical_reads],[min_logical_reads],[max_logical_reads],[total_clr_time],[last_clr_time],[min_clr_time],[max_clr_time],[total_elapsed_time],[last_elapsed_time],[min_elapsed_time],[max_elapsed_time],[query_hash],[query_plan_hash]) + AS (SELECT TOP 20 qs.[sql_handle],qs.[statement_start_offset],qs.[statement_end_offset],qs.[plan_generation_num],qs.[plan_handle],qs.[creation_time],qs.[last_execution_time],qs.[execution_count],qs.[total_worker_time],qs.[last_worker_time],qs.[min_worker_time],qs.[max_worker_time],qs.[total_physical_reads],qs.[last_physical_reads],qs.[min_physical_reads],qs.[max_physical_reads],qs.[total_logical_writes],qs.[last_logical_writes],qs.[min_logical_writes],qs.[max_logical_writes],qs.[total_logical_reads],qs.[last_logical_reads],qs.[min_logical_reads],qs.[max_logical_reads],qs.[total_clr_time],qs.[last_clr_time],qs.[min_clr_time],qs.[max_clr_time],qs.[total_elapsed_time],qs.[last_elapsed_time],qs.[min_elapsed_time],qs.[max_elapsed_time],qs.[query_hash],qs.[query_plan_hash] + FROM sys.dm_exec_query_stats qs + ORDER BY qs.execution_count DESC) + INSERT INTO #dm_exec_query_stats ([sql_handle],[statement_start_offset],[statement_end_offset],[plan_generation_num],[plan_handle],[creation_time],[last_execution_time],[execution_count],[total_worker_time],[last_worker_time],[min_worker_time],[max_worker_time],[total_physical_reads],[last_physical_reads],[min_physical_reads],[max_physical_reads],[total_logical_writes],[last_logical_writes],[min_logical_writes],[max_logical_writes],[total_logical_reads],[last_logical_reads],[min_logical_reads],[max_logical_reads],[total_clr_time],[last_clr_time],[min_clr_time],[max_clr_time],[total_elapsed_time],[last_elapsed_time],[min_elapsed_time],[max_elapsed_time],[query_hash],[query_plan_hash]) + SELECT qs.[sql_handle],qs.[statement_start_offset],qs.[statement_end_offset],qs.[plan_generation_num],qs.[plan_handle],qs.[creation_time],qs.[last_execution_time],qs.[execution_count],qs.[total_worker_time],qs.[last_worker_time],qs.[min_worker_time],qs.[max_worker_time],qs.[total_physical_reads],qs.[last_physical_reads],qs.[min_physical_reads],qs.[max_physical_reads],qs.[total_logical_writes],qs.[last_logical_writes],qs.[min_logical_writes],qs.[max_logical_writes],qs.[total_logical_reads],qs.[last_logical_reads],qs.[min_logical_reads],qs.[max_logical_reads],qs.[total_clr_time],qs.[last_clr_time],qs.[min_clr_time],qs.[max_clr_time],qs.[total_elapsed_time],qs.[last_elapsed_time],qs.[min_elapsed_time],qs.[max_elapsed_time],qs.[query_hash],qs.[query_plan_hash] + FROM queries qs + LEFT OUTER JOIN #dm_exec_query_stats qsCaught ON qs.sql_handle = qsCaught.sql_handle AND qs.plan_handle = qsCaught.plan_handle AND qs.statement_start_offset = qsCaught.statement_start_offset + WHERE qsCaught.sql_handle IS NULL OPTION (RECOMPILE);'; + EXECUTE(@StringToExecute); + END; + + IF @CheckProcedureCacheFilter = 'Duration' + OR @CheckProcedureCacheFilter IS NULL + BEGIN + SET @StringToExecute = 'WITH queries ([sql_handle],[statement_start_offset],[statement_end_offset],[plan_generation_num],[plan_handle],[creation_time],[last_execution_time],[execution_count],[total_worker_time],[last_worker_time],[min_worker_time],[max_worker_time],[total_physical_reads],[last_physical_reads],[min_physical_reads],[max_physical_reads],[total_logical_writes],[last_logical_writes],[min_logical_writes],[max_logical_writes],[total_logical_reads],[last_logical_reads],[min_logical_reads],[max_logical_reads],[total_clr_time],[last_clr_time],[min_clr_time],[max_clr_time],[total_elapsed_time],[last_elapsed_time],[min_elapsed_time],[max_elapsed_time],[query_hash],[query_plan_hash]) + AS (SELECT TOP 20 qs.[sql_handle],qs.[statement_start_offset],qs.[statement_end_offset],qs.[plan_generation_num],qs.[plan_handle],qs.[creation_time],qs.[last_execution_time],qs.[execution_count],qs.[total_worker_time],qs.[last_worker_time],qs.[min_worker_time],qs.[max_worker_time],qs.[total_physical_reads],qs.[last_physical_reads],qs.[min_physical_reads],qs.[max_physical_reads],qs.[total_logical_writes],qs.[last_logical_writes],qs.[min_logical_writes],qs.[max_logical_writes],qs.[total_logical_reads],qs.[last_logical_reads],qs.[min_logical_reads],qs.[max_logical_reads],qs.[total_clr_time],qs.[last_clr_time],qs.[min_clr_time],qs.[max_clr_time],qs.[total_elapsed_time],qs.[last_elapsed_time],qs.[min_elapsed_time],qs.[max_elapsed_time],qs.[query_hash],qs.[query_plan_hash] + FROM sys.dm_exec_query_stats qs + ORDER BY qs.total_elapsed_time DESC) + INSERT INTO #dm_exec_query_stats ([sql_handle],[statement_start_offset],[statement_end_offset],[plan_generation_num],[plan_handle],[creation_time],[last_execution_time],[execution_count],[total_worker_time],[last_worker_time],[min_worker_time],[max_worker_time],[total_physical_reads],[last_physical_reads],[min_physical_reads],[max_physical_reads],[total_logical_writes],[last_logical_writes],[min_logical_writes],[max_logical_writes],[total_logical_reads],[last_logical_reads],[min_logical_reads],[max_logical_reads],[total_clr_time],[last_clr_time],[min_clr_time],[max_clr_time],[total_elapsed_time],[last_elapsed_time],[min_elapsed_time],[max_elapsed_time],[query_hash],[query_plan_hash]) + SELECT qs.[sql_handle],qs.[statement_start_offset],qs.[statement_end_offset],qs.[plan_generation_num],qs.[plan_handle],qs.[creation_time],qs.[last_execution_time],qs.[execution_count],qs.[total_worker_time],qs.[last_worker_time],qs.[min_worker_time],qs.[max_worker_time],qs.[total_physical_reads],qs.[last_physical_reads],qs.[min_physical_reads],qs.[max_physical_reads],qs.[total_logical_writes],qs.[last_logical_writes],qs.[min_logical_writes],qs.[max_logical_writes],qs.[total_logical_reads],qs.[last_logical_reads],qs.[min_logical_reads],qs.[max_logical_reads],qs.[total_clr_time],qs.[last_clr_time],qs.[min_clr_time],qs.[max_clr_time],qs.[total_elapsed_time],qs.[last_elapsed_time],qs.[min_elapsed_time],qs.[max_elapsed_time],qs.[query_hash],qs.[query_plan_hash] + FROM queries qs + LEFT OUTER JOIN #dm_exec_query_stats qsCaught ON qs.sql_handle = qsCaught.sql_handle AND qs.plan_handle = qsCaught.plan_handle AND qs.statement_start_offset = qsCaught.statement_start_offset + WHERE qsCaught.sql_handle IS NULL OPTION (RECOMPILE);'; + EXECUTE(@StringToExecute); + END; + + /* Populate the query_plan_filtered field. Only works in 2005SP2+, but we're just doing it in 2008 to be safe. */ + UPDATE #dm_exec_query_stats + SET query_plan_filtered = qp.query_plan + FROM #dm_exec_query_stats qs + CROSS APPLY sys.dm_exec_text_query_plan(qs.plan_handle, + qs.statement_start_offset, + qs.statement_end_offset) + AS qp; + + END; + + /* Populate the additional query_plan, text, and text_filtered fields */ + UPDATE #dm_exec_query_stats + SET query_plan = qp.query_plan , + [text] = st.[text] , + text_filtered = SUBSTRING(st.text, + ( qs.statement_start_offset + / 2 ) + 1, + ( ( CASE qs.statement_end_offset + WHEN -1 + THEN DATALENGTH(st.text) + ELSE qs.statement_end_offset + END + - qs.statement_start_offset ) + / 2 ) + 1) + FROM #dm_exec_query_stats qs + CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) AS st + CROSS APPLY sys.dm_exec_query_plan(qs.plan_handle) + AS qp; + + /* Dump instances of our own script. We're not trying to tune ourselves. */ + DELETE #dm_exec_query_stats + WHERE text LIKE '%sp_Blitz%' + OR text LIKE '%#BlitzResults%'; + + /* Look for implicit conversions */ + + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 63 ) + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 63) WITH NOWAIT; + + INSERT INTO #BlitzResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details , + QueryPlan , + QueryPlanFiltered + ) + SELECT 63 AS CheckID , + 120 AS Priority , + 'Query Plans' AS FindingsGroup , + 'Implicit Conversion' AS Finding , + 'https://www.brentozar.com/go/implicit' AS URL , + ( 'One of the top resource-intensive queries is comparing two fields that are not the same datatype.' ) AS Details , + qs.query_plan , + qs.query_plan_filtered + FROM #dm_exec_query_stats qs + WHERE COALESCE(qs.query_plan_filtered, + CAST(qs.query_plan AS NVARCHAR(MAX))) LIKE '%CONVERT_IMPLICIT%' + AND COALESCE(qs.query_plan_filtered, + CAST(qs.query_plan AS NVARCHAR(MAX))) LIKE '%PhysicalOp="Index Scan"%'; + END; + + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 64 ) + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 64) WITH NOWAIT; + + INSERT INTO #BlitzResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details , + QueryPlan , + QueryPlanFiltered + ) + SELECT 64 AS CheckID , + 120 AS Priority , + 'Query Plans' AS FindingsGroup , + 'Implicit Conversion Affecting Cardinality' AS Finding , + 'https://www.brentozar.com/go/implicit' AS URL , + ( 'One of the top resource-intensive queries has an implicit conversion that is affecting cardinality estimation.' ) AS Details , + qs.query_plan , + qs.query_plan_filtered + FROM #dm_exec_query_stats qs + WHERE COALESCE(qs.query_plan_filtered, + CAST(qs.query_plan AS NVARCHAR(MAX))) LIKE '%= 10 + AND NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 187 ) - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 44 ) - BEGIN + IF SERVERPROPERTY('IsHadrEnabled') = 1 + BEGIN - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 44) WITH NOWAIT; + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 187) WITH NOWAIT; + + INSERT INTO [#BlitzResults] + ( [CheckID] , + [Priority] , + [FindingsGroup] , + [Finding] , + [URL] , + [Details] ) + SELECT + 187 AS [CheckID] , + 230 AS [Priority] , + 'Security' AS [FindingsGroup] , + 'Endpoints Owned by Users' AS [Finding] , + 'https://www.brentozar.com/go/owners' AS [URL] , + ( 'Endpoint ' + ep.[name] + ' is owned by ' + SUSER_NAME(ep.principal_id) + '. If the endpoint owner login is disabled or not available due to Active Directory problems, the high availability will stop working.' + ) AS [Details] + FROM sys.database_mirroring_endpoints ep + LEFT OUTER JOIN sys.dm_server_services s ON SUSER_NAME(ep.principal_id) = s.service_account + WHERE s.service_account IS NULL AND ep.principal_id <> 1; + END; - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 44 AS CheckID , - 150 AS Priority , - 'Performance' AS FindingsGroup , - 'Queries Forcing Order Hints' AS Finding , - 'https://www.brentozar.com/go/hints' AS URL , - CAST(occurrence AS VARCHAR(10)) - + ' instances of order hinting have been recorded since restart. This means queries are bossing the SQL Server optimizer around, and if they don''t know what they''re doing, this can cause more harm than good. This can also explain why DBA tuning efforts aren''t working.' AS Details - FROM sys.dm_exec_query_optimizer_info - WHERE counter = 'order hint' - AND occurrence > 1000; - END; + /*Verify that the servername is set */ + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 70 ) + BEGIN + IF @@SERVERNAME IS NULL + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 70) WITH NOWAIT; + + INSERT INTO #BlitzResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT 70 AS CheckID , + 200 AS Priority , + 'Informational' AS FindingsGroup , + '@@Servername Not Set' AS Finding , + 'https://www.brentozar.com/go/servername' AS URL , + '@@Servername variable is null. You can fix it by executing: "sp_addserver '''', local"' AS Details; + END; + + IF /* @@SERVERNAME IS set */ + (@@SERVERNAME IS NOT NULL + AND + /* not a named instance */ + CHARINDEX(CHAR(92),CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128))) = 0 + AND + /* not clustered, when computername may be different than the servername */ + SERVERPROPERTY('IsClustered') = 0 + AND + /* @@SERVERNAME is different than the computer name */ + @@SERVERNAME <> CAST(ISNULL(SERVERPROPERTY('ComputerNamePhysicalNetBIOS'),@@SERVERNAME) AS NVARCHAR(128)) ) + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 70) WITH NOWAIT; + + INSERT INTO #BlitzResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT 70 AS CheckID , + 200 AS Priority , + 'Configuration' AS FindingsGroup , + '@@Servername Not Correct' AS Finding , + 'https://www.brentozar.com/go/servername' AS URL , + 'The @@Servername is different than the computer name, which may trigger certificate errors.' AS Details; + END; + + END; + /*Check to see if a failsafe operator has been configured*/ IF NOT EXISTS ( SELECT 1 FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 45 ) + WHERE DatabaseName IS NULL AND CheckID = 73 ) BEGIN - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 45) WITH NOWAIT; - + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 73) WITH NOWAIT; + INSERT INTO #BlitzResults ( CheckID , Priority , @@ -6158,25 +8320,27 @@ AS URL , Details ) - SELECT 45 AS CheckID , - 150 AS Priority , - 'Performance' AS FindingsGroup , - 'Queries Forcing Join Hints' AS Finding , - 'https://www.brentozar.com/go/hints' AS URL , - CAST(occurrence AS VARCHAR(10)) - + ' instances of join hinting have been recorded since restart. This means queries are bossing the SQL Server optimizer around, and if they don''t know what they''re doing, this can cause more harm than good. This can also explain why DBA tuning efforts aren''t working.' AS Details - FROM sys.dm_exec_query_optimizer_info - WHERE counter = 'join hint' - AND occurrence > 1000; + SELECT 73 AS CheckID , + 200 AS Priority , + 'Monitoring' AS FindingsGroup , + 'No Failsafe Operator Configured' AS Finding , + 'https://www.brentozar.com/go/failsafe' AS URL , + ( 'No failsafe operator is configured on this server. This is a good idea just in-case there are issues with the [msdb] database that prevents alerting.' ) AS Details + FROM #AlertInfo + WHERE FailSafeOperator IS NULL; END; + /*Identify globally enabled trace flags*/ IF NOT EXISTS ( SELECT 1 FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 49 ) + WHERE DatabaseName IS NULL AND CheckID = 74 ) BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 49) WITH NOWAIT; - + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 74) WITH NOWAIT; + + INSERT INTO #TraceStatus + EXEC ( ' DBCC TRACESTATUS(-1) WITH NO_INFOMSGS' + ); INSERT INTO #BlitzResults ( CheckID , Priority , @@ -6185,254 +8349,85 @@ AS URL , Details ) - SELECT DISTINCT - 49 AS CheckID , + SELECT 74 AS CheckID , 200 AS Priority , 'Informational' AS FindingsGroup , - 'Linked Server Configured' AS Finding , - 'https://www.brentozar.com/go/link' AS URL , - +CASE WHEN l.remote_name = 'sa' - THEN COALESCE(s.data_source, s.provider) - + ' is configured as a linked server. Check its security configuration as it is connecting with sa, because any user who queries it will get admin-level permissions.' - ELSE COALESCE(s.data_source, s.provider) - + ' is configured as a linked server. Check its security configuration to make sure it isn''t connecting with SA or some other bone-headed administrative login, because any user who queries it might get admin-level permissions.' - END AS Details - FROM sys.servers s - INNER JOIN sys.linked_logins l ON s.server_id = l.server_id - WHERE s.is_linked = 1; + 'Trace Flag On' AS Finding , + CASE WHEN [T].[TraceFlag] = '834' AND @ColumnStoreIndexesInUse = 1 THEN 'https://support.microsoft.com/en-us/kb/3210239' + ELSE'https://www.BrentOzar.com/go/traceflags/' END AS URL , + 'Trace flag ' + + CASE WHEN [T].[TraceFlag] = '652' THEN '652 enabled globally, which disables pre-fetching during index scans. This is usually a very bad idea.' + WHEN [T].[TraceFlag] = '661' THEN '661 enabled globally, which disables ghost record removal, causing the database to grow in size. This is usually a very bad idea.' + WHEN [T].[TraceFlag] = '834' AND @ColumnStoreIndexesInUse = 1 THEN '834 is enabled globally, but you also have columnstore indexes. That combination is not recommended by Microsoft.' + WHEN [T].[TraceFlag] = '1117' THEN '1117 enabled globally, which grows all files in a filegroup at the same time.' + WHEN [T].[TraceFlag] = '1118' THEN '1118 enabled globally, which tries to reduce SGAM waits.' + WHEN [T].[TraceFlag] = '1211' THEN '1211 enabled globally, which disables lock escalation when you least expect it. This is usually a very bad idea.' + WHEN [T].[TraceFlag] = '1204' THEN '1204 enabled globally, which captures deadlock graphs in the error log.' + WHEN [T].[TraceFlag] = '1222' THEN '1222 enabled globally, which captures deadlock graphs in the error log.' + WHEN [T].[TraceFlag] = '1224' THEN '1224 enabled globally, which disables lock escalation until the server has memory pressure. This is usually a very bad idea.' + WHEN [T].[TraceFlag] = '1806' THEN '1806 enabled globally, which disables Instant File Initialization, causing restores and file growths to take longer. This is usually a very bad idea.' + WHEN [T].[TraceFlag] = '2330' THEN '2330 enabled globally, which disables missing index requests. This is usually a very bad idea.' + WHEN [T].[TraceFlag] = '2371' THEN '2371 enabled globally, which changes the auto update stats threshold.' + WHEN [T].[TraceFlag] = '3023' THEN '3023 enabled globally, which performs checksums by default when doing database backups.' + WHEN [T].[TraceFlag] = '3226' THEN '3226 enabled globally, which keeps the event log clean by not reporting successful backups.' + WHEN [T].[TraceFlag] = '3505' THEN '3505 enabled globally, which disables Checkpoints. This is usually a very bad idea.' + WHEN [T].[TraceFlag] = '4199' THEN '4199 enabled globally, which enables non-default Query Optimizer fixes, changing query plans from the default behaviors.' + WHEN [T].[TraceFlag] = '8048' THEN '8048 enabled globally, which tries to reduce CMEMTHREAD waits on servers with a lot of logical processors.' + WHEN [T].[TraceFlag] = '8017' AND (CAST(SERVERPROPERTY('Edition') AS NVARCHAR(1000)) LIKE N'%Express%') THEN '8017 is enabled globally, but this is the default for Express Edition.' + WHEN [T].[TraceFlag] = '8017' AND (CAST(SERVERPROPERTY('Edition') AS NVARCHAR(1000)) NOT LIKE N'%Express%') THEN '8017 is enabled globally, which disables the creation of schedulers for all logical processors.' + WHEN [T].[TraceFlag] = '8649' THEN '8649 enabled globally, which cost threshold for parallelism down to 0. This is usually a very bad idea.' + ELSE [T].[TraceFlag] + ' is enabled globally.' END + AS Details + FROM #TraceStatus T; END; - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 50 ) - BEGIN - IF @@VERSION NOT LIKE '%Microsoft SQL Server 2000%' - AND @@VERSION NOT LIKE '%Microsoft SQL Server 2005%' - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 50) WITH NOWAIT; - - SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) - SELECT 50 AS CheckID , - 100 AS Priority , - ''Performance'' AS FindingsGroup , - ''Max Memory Set Too High'' AS Finding , - ''https://www.brentozar.com/go/max'' AS URL , - ''SQL Server max memory is set to '' - + CAST(c.value_in_use AS VARCHAR(20)) - + '' megabytes, but the server only has '' - + CAST(( CAST(m.total_physical_memory_kb AS BIGINT) / 1024 ) AS VARCHAR(20)) - + '' megabytes. SQL Server may drain the system dry of memory, and under certain conditions, this can cause Windows to swap to disk.'' AS Details - FROM sys.dm_os_sys_memory m - INNER JOIN sys.configurations c ON c.name = ''max server memory (MB)'' - WHERE CAST(m.total_physical_memory_kb AS BIGINT) < ( CAST(c.value_in_use AS BIGINT) * 1024 ) OPTION (RECOMPILE);'; - - IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; - IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; - - EXECUTE(@StringToExecute); - END; - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 51 ) - BEGIN - IF @@VERSION NOT LIKE '%Microsoft SQL Server 2000%' - AND @@VERSION NOT LIKE '%Microsoft SQL Server 2005%' - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 51) WITH NOWAIT - - SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) - SELECT 51 AS CheckID , - 1 AS Priority , - ''Performance'' AS FindingsGroup , - ''Memory Dangerously Low'' AS Finding , - ''https://www.brentozar.com/go/max'' AS URL , - ''The server has '' + CAST(( CAST(m.total_physical_memory_kb AS BIGINT) / 1024 ) AS VARCHAR(20)) + '' megabytes of physical memory, but only '' + CAST(( CAST(m.available_physical_memory_kb AS BIGINT) / 1024 ) AS VARCHAR(20)) - + '' megabytes are available. As the server runs out of memory, there is danger of swapping to disk, which will kill performance.'' AS Details - FROM sys.dm_os_sys_memory m - WHERE CAST(m.available_physical_memory_kb AS BIGINT) < 262144 OPTION (RECOMPILE);'; - - IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; - IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; - - EXECUTE(@StringToExecute); - END; - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 159 ) - BEGIN - IF @@VERSION NOT LIKE '%Microsoft SQL Server 2000%' - AND @@VERSION NOT LIKE '%Microsoft SQL Server 2005%' - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 159) WITH NOWAIT; - - SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) - SELECT DISTINCT 159 AS CheckID , - 1 AS Priority , - ''Performance'' AS FindingsGroup , - ''Memory Dangerously Low in NUMA Nodes'' AS Finding , - ''https://www.brentozar.com/go/max'' AS URL , - ''At least one NUMA node is reporting THREAD_RESOURCES_LOW in sys.dm_os_nodes and can no longer create threads.'' AS Details - FROM sys.dm_os_nodes m - WHERE node_state_desc LIKE ''%THREAD_RESOURCES_LOW%'' OPTION (RECOMPILE);'; - - IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; - IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; - - EXECUTE(@StringToExecute); - END; - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 53 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 53) WITH NOWAIT; - - DECLARE @AOFCI AS INT, @AOAG AS INT, @HAType AS VARCHAR(10), @errmsg AS VARCHAR(200) - - SELECT @AOAG = CAST(SERVERPROPERTY('IsHadrEnabled') AS INT) - SELECT @AOFCI = CAST(SERVERPROPERTY('IsClustered') AS INT) - - IF (SELECT COUNT(DISTINCT join_state_desc) FROM sys.dm_hadr_availability_replica_cluster_states) = 2 - BEGIN --if count is 2 both JOINED_STANDALONE and JOINED_FCI is configured - SET @AOFCI = 1 - END - - SELECT @HAType = - CASE - WHEN @AOFCI = 1 AND @AOAG =1 THEN 'FCIAG' - WHEN @AOFCI = 1 AND @AOAG =0 THEN 'FCI' - WHEN @AOFCI = 0 AND @AOAG =1 THEN 'AG' - ELSE 'STANDALONE' - END - - IF (@HAType IN ('FCIAG','FCI','AG')) - BEGIN - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - - SELECT DISTINCT 53 AS CheckID , - 200 AS Priority , - 'Informational' AS FindingsGroup , - 'Cluster Node' AS Finding , - 'https://BrentOzar.com/go/node' AS URL , - 'This is a node in a cluster.' AS Details - - IF @HAType = 'AG' - BEGIN - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT DISTINCT 53 AS CheckID , - 200 AS Priority , - 'Informational' AS FindingsGroup , - 'Cluster Node Info' AS Finding , - 'https://BrentOzar.com/go/node' AS URL, - 'The cluster nodes are: ' + STUFF((SELECT ', ' + CASE ar.replica_server_name WHEN dhags.primary_replica THEN 'PRIMARY' - ELSE 'SECONDARY' - END + '=' + UPPER(ar.replica_server_name) - FROM sys.availability_groups AS ag - LEFT OUTER JOIN sys.availability_replicas AS ar ON ag.group_id = ar.group_id - LEFT OUTER JOIN sys.dm_hadr_availability_group_states as dhags ON ag.group_id = dhags.group_id - ORDER BY 1 - FOR XML PATH ('') - ), 1, 1, '') + ' - High Availability solution used is AlwaysOn Availability Group (AG)' As Details - END - - IF @HAType = 'FCI' - BEGIN - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT DISTINCT 53 AS CheckID , - 200 AS Priority , - 'Informational' AS FindingsGroup , - 'Cluster Node Info' AS Finding , - 'https://BrentOzar.com/go/node' AS URL, - 'The cluster nodes are: ' + STUFF((SELECT ', ' + CASE is_current_owner - WHEN 1 THEN 'PRIMARY' - ELSE 'SECONDARY' - END + '=' + UPPER(NodeName) - FROM sys.dm_os_cluster_nodes - ORDER BY 1 - FOR XML PATH ('') - ), 1, 1, '') + ' - High Availability solution used is AlwaysOn Failover Cluster Instance (FCI)' As Details - END + /* High CMEMTHREAD waits that could need trace flag 8048. + This check has to be run AFTER the globally enabled trace flag check, + since it uses the #TraceStatus table to know if flags are enabled. + */ + IF @ProductVersionMajor >= 11 AND NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 162 ) + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 162) WITH NOWAIT; - IF @HAType = 'FCIAG' - BEGIN - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT DISTINCT 53 AS CheckID , - 200 AS Priority , - 'Informational' AS FindingsGroup , - 'Cluster Node Info' AS Finding , - 'https://BrentOzar.com/go/node' AS URL, - 'The cluster nodes are: ' + STUFF((SELECT ', ' + HighAvailabilityRoleDesc + '=' + ServerName - FROM (SELECT UPPER(ar.replica_server_name) AS ServerName - ,CASE ar.replica_server_name WHEN dhags.primary_replica THEN 'PRIMARY' - ELSE 'SECONDARY' - END AS HighAvailabilityRoleDesc - FROM sys.availability_groups AS ag - LEFT OUTER JOIN sys.availability_replicas AS ar ON ag.group_id = ar.group_id - LEFT OUTER JOIN sys.dm_hadr_availability_group_states as dhags ON ag.group_id = dhags.group_id - WHERE UPPER(CAST(SERVERPROPERTY('ServerName') AS VARCHAR)) <> ar.replica_server_name - UNION ALL - SELECT UPPER(NodeName) AS ServerName - ,CASE is_current_owner - WHEN 1 THEN 'PRIMARY' - ELSE 'SECONDARY' - END AS HighAvailabilityRoleDesc - FROM sys.dm_os_cluster_nodes) AS Z - ORDER BY 1 - FOR XML PATH ('') - ), 1, 1, '') + ' - High Availability solution used is AlwaysOn FCI with AG (Failover Cluster Instance with Availability Group).'As Details - END - END + INSERT INTO #BlitzResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT 162 AS CheckID , + 50 AS Priority , + 'Performance' AS FindingGroup , + 'Poison Wait Detected: CMEMTHREAD & NUMA' AS Finding , + 'https://www.brentozar.com/go/poison' AS URL , + CONVERT(VARCHAR(10), (MAX([wait_time_ms]) / 1000) / 86400) + ':' + CONVERT(VARCHAR(20), DATEADD(s, (MAX([wait_time_ms]) / 1000), 0), 108) + ' of this wait have been recorded' + + CASE WHEN ts.status = 1 THEN ' despite enabling trace flag 8048 already.' + ELSE '. In servers with over 8 cores per NUMA node, when CMEMTHREAD waits are a bottleneck, trace flag 8048 may be needed.' + END + FROM sys.dm_os_nodes n + INNER JOIN sys.[dm_os_wait_stats] w ON w.wait_type = 'CMEMTHREAD' + LEFT OUTER JOIN #TraceStatus ts ON ts.TraceFlag = 8048 AND ts.status = 1 + WHERE n.node_id = 0 AND n.online_scheduler_count >= 8 + AND EXISTS (SELECT * FROM sys.dm_os_nodes WHERE node_id > 0 AND node_state_desc NOT LIKE '%DAC') + GROUP BY w.wait_type, ts.status + HAVING SUM([wait_time_ms]) > (SELECT 5000 * datediff(HH,create_date,CURRENT_TIMESTAMP) AS hours_since_startup FROM sys.databases WHERE name='tempdb') + AND SUM([wait_time_ms]) > 60000; + END; - END; + /*Check for transaction log file larger than data file */ IF NOT EXISTS ( SELECT 1 FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 55 ) + WHERE DatabaseName IS NULL AND CheckID = 75 ) BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 55) WITH NOWAIT; - - IF @UsualDBOwner IS NULL - SET @UsualDBOwner = SUSER_SNAME(0x01); - + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 75) WITH NOWAIT; + INSERT INTO #BlitzResults ( CheckID , DatabaseName , @@ -6442,30 +8437,41 @@ AS URL , Details ) - SELECT 55 AS CheckID , - [name] AS DatabaseName , - 230 AS Priority , - 'Security' AS FindingsGroup , - 'Database Owner <> ' + @UsualDBOwner AS Finding , - 'https://www.brentozar.com/go/owndb' AS URL , - ( 'Database name: ' + [name] + ' ' - + 'Owner name: ' + SUSER_SNAME(owner_sid) ) AS Details - FROM sys.databases - WHERE (((SUSER_SNAME(owner_sid) <> SUSER_SNAME(0x01)) AND (name IN (N'master', N'model', N'msdb', N'tempdb'))) - OR ((SUSER_SNAME(owner_sid) <> @UsualDBOwner) AND (name NOT IN (N'master', N'model', N'msdb', N'tempdb'))) - ) - AND name NOT IN ( SELECT DISTINCT DatabaseName - FROM #SkipChecks - WHERE CheckID IS NULL OR CheckID = 55); + SELECT 75 AS CheckID , + DB_NAME(a.database_id) , + 50 AS Priority , + 'Reliability' AS FindingsGroup , + 'Transaction Log Larger than Data File' AS Finding , + 'https://www.brentozar.com/go/biglog' AS URL , + 'The database [' + DB_NAME(a.database_id) + + '] has a ' + CAST((CAST(a.size AS BIGINT) * 8 / 1000000) AS NVARCHAR(20)) + ' GB transaction log file, larger than the total data file sizes. This may indicate that transaction log backups are not being performed or not performed often enough.' AS Details + FROM sys.master_files a + WHERE a.type = 1 + AND DB_NAME(a.database_id) NOT IN ( + SELECT DISTINCT + DatabaseName + FROM #SkipChecks + WHERE CheckID = 75 OR CheckID IS NULL) + AND a.size > 125000 /* Size is measured in pages here, so this gets us log files over 1GB. */ + AND a.size > ( SELECT SUM(CAST(b.size AS BIGINT)) + FROM sys.master_files b + WHERE a.database_id = b.database_id + AND b.type = 0 + ) + AND a.database_id IN ( + SELECT database_id + FROM sys.databases + WHERE source_database_id IS NULL ); END; + /*Check for collation conflicts between user databases and tempdb */ IF NOT EXISTS ( SELECT 1 FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 213 ) + WHERE DatabaseName IS NULL AND CheckID = 76 ) BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 213) WITH NOWAIT; - + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 76) WITH NOWAIT; + INSERT INTO #BlitzResults ( CheckID , DatabaseName , @@ -6475,57 +8481,70 @@ AS URL , Details ) - SELECT 213 AS CheckID , - [name] AS DatabaseName , - 230 AS Priority , - 'Security' AS FindingsGroup , - 'Database Owner is Unknown' AS Finding , - '' AS URL , - ( 'Database name: ' + [name] + ' ' - + 'Owner name: ' + ISNULL(SUSER_SNAME(owner_sid),'~~ UNKNOWN ~~') ) AS Details + SELECT 76 AS CheckID , + name AS DatabaseName , + 200 AS Priority , + 'Informational' AS FindingsGroup , + 'Collation is ' + collation_name AS Finding , + 'https://www.brentozar.com/go/collate' AS URL , + 'Collation differences between user databases and tempdb can cause conflicts especially when comparing string values' AS Details FROM sys.databases - WHERE SUSER_SNAME(owner_sid) is NULL - AND name NOT IN ( SELECT DISTINCT DatabaseName - FROM #SkipChecks - WHERE CheckID IS NULL OR CheckID = 213); + WHERE name NOT IN ( 'master', 'model', 'msdb') + AND name NOT LIKE 'ReportServer%' + AND name NOT IN ( SELECT DISTINCT + DatabaseName + FROM #SkipChecks + WHERE CheckID IS NULL OR CheckID = 76) + AND collation_name <> ( SELECT + collation_name + FROM + sys.databases + WHERE + name = 'tempdb' + ); END; IF NOT EXISTS ( SELECT 1 FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 57 ) + WHERE DatabaseName IS NULL AND CheckID = 77 ) BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 57) WITH NOWAIT; - + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 77) WITH NOWAIT; + INSERT INTO #BlitzResults ( CheckID , + DatabaseName , Priority , FindingsGroup , Finding , URL , Details ) - SELECT 57 AS CheckID , - 230 AS Priority , - 'Security' AS FindingsGroup , - 'SQL Agent Job Runs at Startup' AS Finding , - 'https://www.brentozar.com/go/startup' AS URL , - ( 'Job [' + j.name - + '] runs automatically when SQL Server Agent starts up. Make sure you know exactly what this job is doing, because it could pose a security risk.' ) AS Details - FROM msdb.dbo.sysschedules sched - JOIN msdb.dbo.sysjobschedules jsched ON sched.schedule_id = jsched.schedule_id - JOIN msdb.dbo.sysjobs j ON jsched.job_id = j.job_id - WHERE sched.freq_type = 64 - AND sched.enabled = 1; + SELECT 77 AS CheckID , + dSnap.[name] AS DatabaseName , + 170 AS Priority , + 'Reliability' AS FindingsGroup , + 'Database Snapshot Online' AS Finding , + 'https://www.brentozar.com/go/snapshot' AS URL , + 'Database [' + dSnap.[name] + + '] is a snapshot of [' + + dOriginal.[name] + + ']. Make sure you have enough drive space to maintain the snapshot as the original database grows.' AS Details + FROM sys.databases dSnap + INNER JOIN sys.databases dOriginal ON dSnap.source_database_id = dOriginal.database_id + AND dSnap.name NOT IN ( + SELECT DISTINCT DatabaseName + FROM #SkipChecks + WHERE CheckID = 77 OR CheckID IS NULL); END; IF NOT EXISTS ( SELECT 1 FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 97 ) + WHERE DatabaseName IS NULL AND CheckID = 79 ) BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 97) WITH NOWAIT; - + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 79) WITH NOWAIT; + INSERT INTO #BlitzResults ( CheckID , Priority , @@ -6534,29 +8553,44 @@ AS URL , Details ) - SELECT 97 AS CheckID , - 100 AS Priority , + SELECT 79 AS CheckID , + -- sp_Blitz Issue #776 + -- Job has history and was executed in the last 30 days OR Job is enabled AND Job Schedule is enabled + CASE WHEN (cast(datediff(dd, substring(cast(sjh.run_date as nvarchar(10)), 1, 4) + '-' + substring(cast(sjh.run_date as nvarchar(10)), 5, 2) + '-' + substring(cast(sjh.run_date as nvarchar(10)), 7, 2), GETDATE()) AS INT) < 30) OR (j.[enabled] = 1 AND ssc.[enabled] = 1 )THEN + 100 + ELSE -- no job history (implicit) AND job not run in the past 30 days AND (Job disabled OR Job Schedule disabled) + 200 + END AS Priority, 'Performance' AS FindingsGroup , - 'Unusual SQL Server Edition' AS Finding , - 'https://www.brentozar.com/go/workgroup' AS URL , - ( 'This server is using ' - + CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) - + ', which is capped at low amounts of CPU and memory.' ) AS Details - WHERE CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) NOT LIKE '%Standard%' - AND CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) NOT LIKE '%Enterprise%' - AND CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) NOT LIKE '%Data Center%' - AND CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) NOT LIKE '%Developer%' - AND CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) NOT LIKE '%Business Intelligence%'; + 'Shrink Database Job' AS Finding , + 'https://www.brentozar.com/go/autoshrink' AS URL , + 'In the [' + j.[name] + '] job, step [' + + step.[step_name] + + '] has SHRINKDATABASE or SHRINKFILE, which may be causing database fragmentation.' + + CASE WHEN COALESCE(ssc.name,'0') != '0' THEN + ' (Schedule: [' + ssc.name + '])' ELSE + '' END AS Details + FROM msdb.dbo.sysjobs j + INNER JOIN msdb.dbo.sysjobsteps step ON j.job_id = step.job_id + LEFT OUTER JOIN msdb.dbo.sysjobschedules AS sjsc + ON j.job_id = sjsc.job_id + LEFT OUTER JOIN msdb.dbo.sysschedules AS ssc + ON sjsc.schedule_id = ssc.schedule_id + AND sjsc.job_id = j.job_id + LEFT OUTER JOIN msdb.dbo.sysjobhistory AS sjh + ON j.job_id = sjh.job_id + AND step.step_id = sjh.step_id + AND sjh.run_date IN (SELECT max(sjh2.run_date) FROM msdb.dbo.sysjobhistory AS sjh2 WHERE sjh2.job_id = j.job_id) -- get the latest entry date + AND sjh.run_time IN (SELECT max(sjh3.run_time) FROM msdb.dbo.sysjobhistory AS sjh3 WHERE sjh3.job_id = j.job_id AND sjh3.run_date = sjh.run_date) -- get the latest entry time + WHERE step.command LIKE N'%SHRINKDATABASE%' + OR step.command LIKE N'%SHRINKFILE%'; END; IF NOT EXISTS ( SELECT 1 FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 154 ) - AND SERVERPROPERTY('EngineEdition') <> 8 + WHERE DatabaseName IS NULL AND CheckID = 81 ) BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 154) WITH NOWAIT; - + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 81) WITH NOWAIT; + INSERT INTO #BlitzResults ( CheckID , Priority , @@ -6565,32935 +8599,22346 @@ AS URL , Details ) - SELECT 154 AS CheckID , - 10 AS Priority , - 'Performance' AS FindingsGroup , - '32-bit SQL Server Installed' AS Finding , - 'https://www.brentozar.com/go/32bit' AS URL , - ( 'This server uses the 32-bit x86 binaries for SQL Server instead of the 64-bit x64 binaries. The amount of memory available for query workspace and execution plans is heavily limited.' ) AS Details - WHERE CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) NOT LIKE '%64%'; + SELECT 81 AS CheckID , + 200 AS Priority , + 'Non-Active Server Config' AS FindingsGroup , + cr.name AS Finding , + 'https://www.BrentOzar.com/blitz/sp_configure/' AS URL , + ( 'This sp_configure option isn''t running under its set value. Its set value is ' + + CAST(cr.[value] AS VARCHAR(100)) + + ' and its running value is ' + + CAST(cr.value_in_use AS VARCHAR(100)) + + '. When someone does a RECONFIGURE or restarts the instance, this setting will start taking effect.' ) AS Details + FROM sys.configurations cr + WHERE cr.value <> cr.value_in_use + AND NOT (cr.name = 'min server memory (MB)' AND cr.value IN (0,16) AND cr.value_in_use IN (0,16)); END; IF NOT EXISTS ( SELECT 1 FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 62 ) + WHERE DatabaseName IS NULL AND CheckID = 123 ) BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 62) WITH NOWAIT; - + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 123) WITH NOWAIT; + INSERT INTO #BlitzResults ( CheckID , - DatabaseName , Priority , FindingsGroup , Finding , URL , Details ) - SELECT 62 AS CheckID , - [name] AS DatabaseName , + SELECT TOP 1 123 AS CheckID , 200 AS Priority , - 'Performance' AS FindingsGroup , - 'Old Compatibility Level' AS Finding , - 'https://www.brentozar.com/go/compatlevel' AS URL , - ( 'Database ' + [name] - + ' is compatibility level ' - + CAST(compatibility_level AS VARCHAR(20)) - + ', which may cause unwanted results when trying to run queries that have newer T-SQL features.' ) AS Details - FROM sys.databases - WHERE name NOT IN ( SELECT DISTINCT - DatabaseName - FROM #SkipChecks - WHERE CheckID IS NULL OR CheckID = 62) - AND compatibility_level <= 90; + 'Informational' AS FindingsGroup , + 'Agent Jobs Starting Simultaneously' AS Finding , + 'https://www.brentozar.com/go/busyagent/' AS URL , + ( 'Multiple SQL Server Agent jobs are configured to start simultaneously. For detailed schedule listings, see the query in the URL.' ) AS Details + FROM msdb.dbo.sysjobactivity + WHERE start_execution_date > DATEADD(dd, -14, GETDATE()) + GROUP BY start_execution_date HAVING COUNT(*) > 1; END; - IF NOT EXISTS ( SELECT 1 + IF @CheckServerInfo = 1 + BEGIN + +/*This checks Windows version. It would be better if Microsoft gave everything a separate build number, but whatever.*/ +IF @ProductVersionMajor >= 10 + AND NOT EXISTS ( SELECT 1 FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 94 ) + WHERE DatabaseName IS NULL AND CheckID = 172 ) + BEGIN + -- sys.dm_os_host_info includes both Windows and Linux info + IF EXISTS (SELECT 1 + FROM sys.all_objects + WHERE name = 'dm_os_host_info' ) BEGIN - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 94) WITH NOWAIT; + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 172) WITH NOWAIT; + + INSERT INTO [#BlitzResults] + ( [CheckID] , + [Priority] , + [FindingsGroup] , + [Finding] , + [URL] , + [Details] ) - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 94 AS CheckID , - 200 AS [Priority] , - 'Monitoring' AS FindingsGroup , - 'Agent Jobs Without Failure Emails' AS Finding , - 'https://www.brentozar.com/go/alerts' AS URL , - 'The job ' + [name] - + ' has not been set up to notify an operator if it fails.' AS Details - FROM msdb.[dbo].[sysjobs] j - WHERE j.enabled = 1 - AND j.notify_email_operator_id = 0 - AND j.notify_netsend_operator_id = 0 - AND j.notify_page_operator_id = 0 - AND j.category_id <> 100; /* Exclude SSRS category */ - END; - - IF EXISTS ( SELECT 1 - FROM sys.configurations - WHERE name = 'remote admin connections' - AND value_in_use = 0 ) - AND NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 100 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 100) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 100 AS CheckID , - 170 AS Priority , - 'Reliability' AS FindingGroup , - 'Remote DAC Disabled' AS Finding , - 'https://www.brentozar.com/go/dac' AS URL , - 'Remote access to the Dedicated Admin Connection (DAC) is not enabled. The DAC can make remote troubleshooting much easier when SQL Server is unresponsive.'; + SELECT + 172 AS [CheckID] , + 250 AS [Priority] , + 'Server Info' AS [FindingsGroup] , + 'Operating System Version' AS [Finding] , + ( CASE WHEN @IsWindowsOperatingSystem = 1 + THEN 'https://en.wikipedia.org/wiki/List_of_Microsoft_Windows_versions' + ELSE 'https://en.wikipedia.org/wiki/List_of_Linux_distributions' + END + ) AS [URL] , + ( CASE + WHEN [ohi].[host_platform] = 'Linux' THEN 'You''re running the ' + CAST([ohi].[host_distribution] AS VARCHAR(35)) + ' distribution of ' + CAST([ohi].[host_platform] AS VARCHAR(35)) + ', version ' + CAST([ohi].[host_release] AS VARCHAR(5)) + WHEN [ohi].[host_platform] = 'Windows' AND [ohi].[host_release] = '5' THEN 'You''re running Windows 2000, version ' + CAST([ohi].[host_release] AS VARCHAR(5)) + WHEN [ohi].[host_platform] = 'Windows' AND [ohi].[host_release] > '5' THEN 'You''re running ' + CAST([ohi].[host_distribution] AS VARCHAR(50)) + ', version ' + CAST([ohi].[host_release] AS VARCHAR(5)) + ELSE 'You''re running ' + CAST([ohi].[host_distribution] AS VARCHAR(35)) + ', version ' + CAST([ohi].[host_release] AS VARCHAR(5)) + END + ) AS [Details] + FROM [sys].[dm_os_host_info] [ohi]; END; - - IF EXISTS ( SELECT * - FROM sys.dm_os_schedulers - WHERE is_online = 0 ) - AND NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 101 ) + ELSE BEGIN + -- Otherwise, stick with Windows-only detection - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 101) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 101 AS CheckID , - 50 AS Priority , - 'Performance' AS FindingGroup , - 'CPU Schedulers Offline' AS Finding , - 'https://www.brentozar.com/go/schedulers' AS URL , - 'Some CPU cores are not accessible to SQL Server due to affinity masking or licensing problems.'; - END; + IF EXISTS ( SELECT 1 + FROM sys.all_objects + WHERE name = 'dm_os_windows_info' ) - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 110 ) - AND EXISTS (SELECT * FROM master.sys.all_objects WHERE name = 'dm_os_memory_nodes') - BEGIN + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 172) WITH NOWAIT; + + INSERT INTO [#BlitzResults] + ( [CheckID] , + [Priority] , + [FindingsGroup] , + [Finding] , + [URL] , + [Details] ) - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 110) WITH NOWAIT; + SELECT + 172 AS [CheckID] , + 250 AS [Priority] , + 'Server Info' AS [FindingsGroup] , + 'Windows Version' AS [Finding] , + 'https://en.wikipedia.org/wiki/List_of_Microsoft_Windows_versions' AS [URL] , + ( CASE + WHEN [owi].[windows_release] = '5' THEN 'You''re running Windows 2000, version ' + CAST([owi].[windows_release] AS VARCHAR(5)) + WHEN [owi].[windows_release] > '5' AND [owi].[windows_release] < '6' THEN 'You''re running Windows Server 2003/2003R2 era, version ' + CAST([owi].[windows_release] AS VARCHAR(5)) + WHEN [owi].[windows_release] >= '6' AND [owi].[windows_release] <= '6.1' THEN 'You''re running Windows Server 2008/2008R2 era, version ' + CAST([owi].[windows_release] AS VARCHAR(5)) + WHEN [owi].[windows_release] >= '6.2' AND [owi].[windows_release] <= '6.3' THEN 'You''re running Windows Server 2012/2012R2 era, version ' + CAST([owi].[windows_release] AS VARCHAR(5)) + WHEN [owi].[windows_release] = '10.0' THEN 'You''re running Windows Server 2016/2019 era, version ' + CAST([owi].[windows_release] AS VARCHAR(5)) + ELSE 'You''re running Windows Server, version ' + CAST([owi].[windows_release] AS VARCHAR(5)) + END + ) AS [Details] + FROM [sys].[dm_os_windows_info] [owi]; - SET @StringToExecute = 'IF EXISTS (SELECT * - FROM sys.dm_os_nodes n - INNER JOIN sys.dm_os_memory_nodes m ON n.memory_node_id = m.memory_node_id - WHERE n.node_state_desc = ''OFFLINE'') - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 110 AS CheckID , - 50 AS Priority , - ''Performance'' AS FindingGroup , - ''Memory Nodes Offline'' AS Finding , - ''https://www.brentozar.com/go/schedulers'' AS URL , - ''Due to affinity masking or licensing problems, some of the memory may not be available.'' OPTION (RECOMPILE)'; - - IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; - IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; - - EXECUTE(@StringToExecute); + END; END; + END; - IF EXISTS ( SELECT * - FROM sys.databases - WHERE state > 1 ) - AND NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 102 ) +/* +This check hits the dm_os_process_memory system view +to see if locked_page_allocations_kb is > 0, +which could indicate that locked pages in memory is enabled. +*/ +IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 166 ) BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 166) WITH NOWAIT; + + INSERT INTO [#BlitzResults] + ( [CheckID] , + [Priority] , + [FindingsGroup] , + [Finding] , + [URL] , + [Details] ) + SELECT + 166 AS [CheckID] , + 250 AS [Priority] , + 'Server Info' AS [FindingsGroup] , + 'Locked Pages In Memory Enabled' AS [Finding] , + 'https://www.brentozar.com/go/lpim' AS [URL] , + ( 'You currently have ' + + CASE WHEN [dopm].[locked_page_allocations_kb] / 1024. / 1024. > 0 + THEN CAST([dopm].[locked_page_allocations_kb] / 1024 / 1024 AS VARCHAR(100)) + + ' GB' + ELSE CAST([dopm].[locked_page_allocations_kb] / 1024 AS VARCHAR(100)) + + ' MB' + END + ' of pages locked in memory.' ) AS [Details] + FROM + [sys].[dm_os_process_memory] AS [dopm] + WHERE + [dopm].[locked_page_allocations_kb] > 0; + END; - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 102) WITH NOWAIT; + /* Server Info - Locked Pages In Memory Enabled - Check 166 - SQL Server 2016 SP1 and newer */ + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 166 ) + AND EXISTS ( SELECT * + FROM sys.all_objects o + INNER JOIN sys.all_columns c ON o.object_id = c.object_id + WHERE o.name = 'dm_os_sys_info' + AND c.name = 'sql_memory_model' ) + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 166) WITH NOWAIT; + + SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) + SELECT 166 AS CheckID , + 250 AS Priority , + ''Server Info'' AS FindingsGroup , + ''Memory Model Unconventional'' AS Finding , + ''https://www.brentozar.com/go/lpim'' AS URL , + ''Memory Model: '' + CAST(sql_memory_model_desc AS NVARCHAR(100)) + FROM sys.dm_os_sys_info WHERE sql_memory_model <> 1 OPTION (RECOMPILE);'; + + IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; + IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 102 AS CheckID , - [name] , - 20 AS Priority , - 'Reliability' AS FindingGroup , - 'Unusual Database State: ' + [state_desc] AS Finding , - 'https://www.brentozar.com/go/repair' AS URL , - 'This database may not be online.' - FROM sys.databases - WHERE state > 1; - END; + EXECUTE(@StringToExecute); + END; - IF EXISTS ( SELECT * - FROM master.sys.extended_procedures ) - AND NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 105 ) + /* Performance - Instant File Initialization Not Enabled - Check 192 */ + /* Server Info - Instant File Initialization Enabled - Check 193 */ + IF NOT EXISTS ( SELECT 1/0 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 192 /* IFI disabled check disabled */ + ) OR NOT EXISTS + ( SELECT 1/0 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 193 /* IFI enabled check disabled */ + ) BEGIN + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d] and CheckId [%d].', 0, 1, 192, 193) WITH NOWAIT; - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 105) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 105 AS CheckID , - 'master' , - 200 AS Priority , - 'Reliability' AS FindingGroup , - 'Extended Stored Procedures in Master' AS Finding , - 'https://www.brentozar.com/go/clr' AS URL , - 'The [' + name - + '] extended stored procedure is in the master database. CLR may be in use, and the master database now needs to be part of your backup/recovery planning.' - FROM master.sys.extended_procedures; - END; + DECLARE @IFISetting varchar(1) = N'N' + ,@IFIReadDMVFailed bit = 0 + ,@IFIAllFailed bit = 0; - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 107 ) + /* See if we can get the instant_file_initialization_enabled column from sys.dm_server_services */ + IF EXISTS + ( + SELECT 1/0 + FROM sys.all_columns + WHERE [object_id] = OBJECT_ID(N'[sys].[dm_server_services]') + AND [name] = N'instant_file_initialization_enabled' + ) BEGIN + /* This needs to be a "dynamic" SQL statement because if the 'instant_file_initialization_enabled' column doesn't exist the procedure might fail on a bind error */ + SET @StringToExecute = N'SELECT @IFISetting = instant_file_initialization_enabled' + @crlf + + N'FROM sys.dm_server_services' + @crlf + + N'WHERE filename LIKE ''%sqlservr.exe%''' + @crlf + + N'OPTION (RECOMPILE);'; + + IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; + IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; + + EXEC dbo.sp_executesql + @StringToExecute + ,N'@IFISetting varchar(1) OUTPUT' + ,@IFISetting = @IFISetting OUTPUT - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 107) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 107 AS CheckID , - 50 AS Priority , - 'Performance' AS FindingGroup , - 'Poison Wait Detected: ' + wait_type AS Finding , - 'https://www.brentozar.com/go/poison/#' + wait_type AS URL , - CONVERT(VARCHAR(10), (SUM([wait_time_ms]) / 1000) / 86400) + ':' + CONVERT(VARCHAR(20), DATEADD(s, (SUM([wait_time_ms]) / 1000), 0), 108) + ' of this wait have been recorded. This wait often indicates killer performance problems.' - FROM sys.[dm_os_wait_stats] - WHERE wait_type IN('IO_QUEUE_LIMIT', 'IO_RETRY', 'LOG_RATE_GOVERNOR', 'POOL_LOG_RATE_GOVERNOR', 'PREEMPTIVE_DEBUG', 'RESMGR_THROTTLED', 'RESOURCE_SEMAPHORE', 'RESOURCE_SEMAPHORE_QUERY_COMPILE','SE_REPL_CATCHUP_THROTTLE','SE_REPL_COMMIT_ACK','SE_REPL_COMMIT_TURN','SE_REPL_ROLLBACK_ACK','SE_REPL_SLOW_SECONDARY_THROTTLE','THREADPOOL') - GROUP BY wait_type - HAVING SUM([wait_time_ms]) > (SELECT 5000 * datediff(HH,create_date,CURRENT_TIMESTAMP) AS hours_since_startup FROM sys.databases WHERE name='tempdb') - AND SUM([wait_time_ms]) > 60000; - END; + SET @IFIReadDMVFailed = 0; + END + ELSE + /* We couldn't get the instant_file_initialization_enabled column from sys.dm_server_services, fall back to read error log */ + BEGIN + SET @IFIReadDMVFailed = 1; + /* If this is Amazon RDS, we'll use the rdsadmin.dbo.rds_read_error_log */ + IF LEFT(CAST(SERVERPROPERTY('ComputerNamePhysicalNetBIOS') AS VARCHAR(8000)), 8) = 'EC2AMAZ-' + AND LEFT(CAST(SERVERPROPERTY('MachineName') AS VARCHAR(8000)), 8) = 'EC2AMAZ-' + AND db_id('rdsadmin') IS NOT NULL + AND EXISTS ( SELECT 1/0 + FROM master.sys.all_objects + WHERE name IN ('rds_startup_tasks', 'rds_help_revlogin', 'rds_hexadecimal', 'rds_failover_tracking', 'rds_database_tracking', 'rds_track_change') + ) + BEGIN + /* Amazon RDS detected, read rdsadmin.dbo.rds_read_error_log */ + INSERT INTO #ErrorLog + EXEC rdsadmin.dbo.rds_read_error_log 0, 1, N'Database Instant File Initialization: enabled'; + END + ELSE + BEGIN + /* Try to read the error log, this might fail due to permissions */ + BEGIN TRY + INSERT INTO #ErrorLog + EXEC sys.xp_readerrorlog 0, 1, N'Database Instant File Initialization: enabled'; + END TRY + BEGIN CATCH + IF @Debug IN (1, 2) RAISERROR('No permissions to execute xp_readerrorlog.', 0, 1) WITH NOWAIT; + SET @IFIAllFailed = 1; + END CATCH + END; + END; - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 121 ) - BEGIN + IF @IFIAllFailed = 0 + BEGIN + IF @IFIReadDMVFailed = 1 + /* We couldn't read the DMV so set the @IFISetting variable using the error log */ + BEGIN + IF EXISTS ( SELECT 1/0 + FROM #ErrorLog + WHERE LEFT([Text], 45) = N'Database Instant File Initialization: enabled' + ) + BEGIN + SET @IFISetting = 'Y'; + END + ELSE + BEGIN + SET @IFISetting = 'N'; + END; + END; - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 121) WITH NOWAIT; + IF NOT EXISTS ( SELECT 1/0 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 192 /* IFI disabled check disabled */ + ) AND @IFISetting = 'N' + BEGIN + INSERT INTO #BlitzResults + ( + CheckID , + [Priority] , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT + 192 AS [CheckID] , + 50 AS [Priority] , + 'Performance' AS [FindingsGroup] , + 'Instant File Initialization Not Enabled' AS [Finding] , + 'https://www.brentozar.com/go/instant' AS [URL] , + 'Consider enabling IFI for faster restores and data file growths.' AS [Details] + END; - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 121 AS CheckID , - 50 AS Priority , - 'Performance' AS FindingGroup , - 'Poison Wait Detected: Serializable Locking' AS Finding , - 'https://www.brentozar.com/go/serializable' AS URL , - CONVERT(VARCHAR(10), (SUM([wait_time_ms]) / 1000) / 86400) + ':' + CONVERT(VARCHAR(20), DATEADD(s, (SUM([wait_time_ms]) / 1000), 0), 108) + ' of LCK_M_R% waits have been recorded. This wait often indicates killer performance problems.' - FROM sys.[dm_os_wait_stats] - WHERE wait_type IN ('LCK_M_RS_S', 'LCK_M_RS_U', 'LCK_M_RIn_NL','LCK_M_RIn_S', 'LCK_M_RIn_U','LCK_M_RIn_X', 'LCK_M_RX_S', 'LCK_M_RX_U','LCK_M_RX_X') - HAVING SUM([wait_time_ms]) > (SELECT 5000 * datediff(HH,create_date,CURRENT_TIMESTAMP) AS hours_since_startup FROM sys.databases WHERE name='tempdb') - AND SUM([wait_time_ms]) > 60000; - END; + IF NOT EXISTS ( SELECT 1/0 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 193 /* IFI enabled check disabled */ + ) AND @IFISetting = 'Y' + BEGIN + INSERT INTO #BlitzResults + ( + CheckID , + [Priority] , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT + 193 AS [CheckID] , + 250 AS [Priority] , + 'Server Info' AS [FindingsGroup] , + 'Instant File Initialization Enabled' AS [Finding] , + 'https://www.brentozar.com/go/instant' AS [URL] , + 'The service account has the Perform Volume Maintenance Tasks permission.' AS [Details] + END; + END; + END; + /* End of checkId 192 */ + /* End of checkId 193 */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 111 ) + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 130 ) BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 111) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - DatabaseName , - URL , - Details - ) - SELECT 111 AS CheckID , - 50 AS Priority , - 'Reliability' AS FindingGroup , - 'Possibly Broken Log Shipping' AS Finding , - d.[name] , - 'https://www.brentozar.com/go/shipping' AS URL , - d.[name] + ' is in a restoring state, but has not had a backup applied in the last two days. This is a possible indication of a broken transaction log shipping setup.' - FROM [master].sys.databases d - INNER JOIN [master].sys.database_mirroring dm ON d.database_id = dm.database_id - AND dm.mirroring_role IS NULL - WHERE ( d.[state] = 1 - OR (d.[state] = 0 AND d.[is_in_standby] = 1) ) - AND NOT EXISTS(SELECT * FROM msdb.dbo.restorehistory rh - INNER JOIN msdb.dbo.backupset bs ON rh.backup_set_id = bs.backup_set_id - WHERE d.[name] COLLATE SQL_Latin1_General_CP1_CI_AS = rh.destination_database_name COLLATE SQL_Latin1_General_CP1_CI_AS - AND rh.restore_date >= DATEADD(dd, -2, GETDATE())); - - END; + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 130) WITH NOWAIT; + + INSERT INTO #BlitzResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT 130 AS CheckID , + 250 AS Priority , + 'Server Info' AS FindingsGroup , + 'Server Name' AS Finding , + 'https://www.brentozar.com/go/servername' AS URL , + @@SERVERNAME AS Details + WHERE @@SERVERNAME IS NOT NULL; + END; IF NOT EXISTS ( SELECT 1 FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 112 ) - AND EXISTS (SELECT * FROM master.sys.all_objects WHERE name = 'change_tracking_databases') + WHERE DatabaseName IS NULL AND CheckID = 83 ) BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 112) WITH NOWAIT; - - SET @StringToExecute = 'INSERT INTO #BlitzResults - (CheckID, - Priority, - FindingsGroup, - Finding, - DatabaseName, - URL, - Details) - SELECT 112 AS CheckID, - 100 AS Priority, - ''Performance'' AS FindingsGroup, - ''Change Tracking Enabled'' AS Finding, - d.[name], - ''https://www.brentozar.com/go/tracking'' AS URL, - ( d.[name] + '' has change tracking enabled. This is not a default setting, and it has some performance overhead. It keeps track of changes to rows in tables that have change tracking turned on.'' ) AS Details FROM sys.change_tracking_databases AS ctd INNER JOIN sys.databases AS d ON ctd.database_id = d.database_id OPTION (RECOMPILE)'; + IF EXISTS ( SELECT * + FROM sys.all_objects + WHERE name = 'dm_server_services' ) + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 83) WITH NOWAIT; + + -- DATETIMEOFFSET and DATETIME have different minimum values, so there's + -- a small workaround here to force 1753-01-01 if the minimum is detected + SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) + SELECT 83 AS CheckID , + 250 AS Priority , + ''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 + ''.'' + FROM sys.dm_server_services OPTION (RECOMPILE);'; IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; EXECUTE(@StringToExecute); + END; END; - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 116 ) - AND EXISTS (SELECT * FROM msdb.sys.all_columns WHERE name = 'compressed_backup_size') - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 116) WITH NOWAIT - - SET @StringToExecute = 'INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 116 AS CheckID , - 200 AS Priority , - ''Informational'' AS FindingGroup , - ''Backup Compression Default Off'' AS Finding , - ''https://www.brentozar.com/go/backup'' AS URL , - ''Uncompressed full backups have happened recently, and backup compression is not turned on at the server level. Backup compression is included with SQL Server 2008R2 & newer, even in Standard Edition. We recommend turning backup compression on by default so that ad-hoc backups will get compressed.'' - FROM sys.configurations - WHERE configuration_id = 1579 AND CAST(value_in_use AS INT) = 0 - AND EXISTS (SELECT * FROM msdb.dbo.backupset WHERE backup_size = compressed_backup_size AND type = ''D'' AND backup_finish_date >= DATEADD(DD, -14, GETDATE())) OPTION (RECOMPILE);'; + /* Check 84 - SQL Server 2012 */ + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 84 ) + BEGIN + IF EXISTS ( SELECT * + FROM sys.all_objects o + INNER JOIN sys.all_columns c ON o.object_id = c.object_id + WHERE o.name = 'dm_os_sys_info' + AND c.name = 'physical_memory_kb' ) + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 84) WITH NOWAIT; + + SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) + SELECT 84 AS CheckID , + 250 AS Priority , + ''Server Info'' AS FindingsGroup , + ''Hardware'' AS Finding , + '''' AS URL , + ''Logical processors: '' + CAST(cpu_count AS VARCHAR(50)) + ''. Physical memory: '' + CAST( CAST(ROUND((physical_memory_kb / 1024.0 / 1024), 1) AS INT) AS VARCHAR(50)) + ''GB.'' + FROM sys.dm_os_sys_info OPTION (RECOMPILE);'; IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; EXECUTE(@StringToExecute); - END; + END; - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 117 ) - AND EXISTS (SELECT * FROM master.sys.all_objects WHERE name = 'dm_exec_query_resource_semaphores') - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 117) WITH NOWAIT; - - SET @StringToExecute = 'IF 0 < (SELECT SUM([forced_grant_count]) FROM sys.dm_exec_query_resource_semaphores WHERE [forced_grant_count] IS NOT NULL) - INSERT INTO #BlitzResults - (CheckID, - Priority, - FindingsGroup, - Finding, - URL, - Details) - SELECT 117 AS CheckID, - 100 AS Priority, - ''Performance'' AS FindingsGroup, - ''Memory Pressure Affecting Queries'' AS Finding, - ''https://www.brentozar.com/go/grants'' AS URL, - CAST(SUM(forced_grant_count) AS NVARCHAR(100)) + '' forced grants reported in the DMV sys.dm_exec_query_resource_semaphores, indicating memory pressure has affected query runtimes.'' - FROM sys.dm_exec_query_resource_semaphores WHERE [forced_grant_count] IS NOT NULL OPTION (RECOMPILE);'; + /* Check 84 - SQL Server 2008 */ + IF EXISTS ( SELECT * + FROM sys.all_objects o + INNER JOIN sys.all_columns c ON o.object_id = c.object_id + WHERE o.name = 'dm_os_sys_info' + AND c.name = 'physical_memory_in_bytes' ) + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 84) WITH NOWAIT; + + SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) + SELECT 84 AS CheckID , + 250 AS Priority , + ''Server Info'' AS FindingsGroup , + ''Hardware'' AS Finding , + '''' AS URL , + ''Logical processors: '' + CAST(cpu_count AS VARCHAR(50)) + ''. Physical memory: '' + CAST( CAST(ROUND((physical_memory_in_bytes / 1024.0 / 1024 / 1024), 1) AS INT) AS VARCHAR(50)) + ''GB.'' + FROM sys.dm_os_sys_info OPTION (RECOMPILE);'; IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; EXECUTE(@StringToExecute); + END; END; IF NOT EXISTS ( SELECT 1 FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 124 ) + WHERE DatabaseName IS NULL AND CheckID = 85 ) BEGIN - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 124) WITH NOWAIT; + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 85) WITH NOWAIT; - INSERT INTO #BlitzResults - (CheckID, - Priority, - FindingsGroup, - Finding, - URL, - Details) - SELECT 124, - 150, - 'Performance', - 'Deadlocks Happening Daily', - 'https://www.brentozar.com/go/deadlocks', - CAST(CAST(p.cntr_value / @DaysUptime AS BIGINT) AS NVARCHAR(100)) + ' average deadlocks per day. To find them, run sp_BlitzLock.' AS Details - FROM sys.dm_os_performance_counters p - INNER JOIN sys.databases d ON d.name = 'tempdb' - WHERE RTRIM(p.counter_name) = 'Number of Deadlocks/sec' - AND RTRIM(p.instance_name) = '_Total' - AND p.cntr_value > 0 - AND (1.0 * p.cntr_value / NULLIF(datediff(DD,create_date,CURRENT_TIMESTAMP),0)) > 10; + INSERT INTO #BlitzResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT 85 AS CheckID , + 250 AS Priority , + 'Server Info' AS FindingsGroup , + 'SQL Server Service' AS Finding , + '' AS URL , + N'Version: ' + + CAST(SERVERPROPERTY('productversion') AS NVARCHAR(100)) + + N'. Patch Level: ' + + CAST(SERVERPROPERTY('productlevel') AS NVARCHAR(100)) + + CASE WHEN SERVERPROPERTY('ProductUpdateLevel') IS NULL + THEN N'' + ELSE N'. Cumulative Update: ' + + CAST(SERVERPROPERTY('ProductUpdateLevel') AS NVARCHAR(100)) + END + + N'. Edition: ' + + CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) + + N'. Availability Groups Enabled: ' + + CAST(COALESCE(SERVERPROPERTY('IsHadrEnabled'), + 0) AS VARCHAR(100)) + + N'. Availability Groups Manager Status: ' + + CAST(COALESCE(SERVERPROPERTY('HadrManagerStatus'), + 0) AS VARCHAR(100)); END; - IF DATEADD(mi, -15, GETDATE()) < (SELECT TOP 1 creation_time FROM sys.dm_exec_query_stats ORDER BY creation_time) - AND NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 125 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 125) WITH NOWAIT; - - DECLARE @user_perm_sql NVARCHAR(MAX) = N''; - DECLARE @user_perm_gb_out DECIMAL(38,2); - - IF @ProductVersionMajor >= 11 - - BEGIN - - SET @user_perm_sql += N' - SELECT @user_perm_gb = CASE WHEN (pages_kb / 1024. / 1024.) >= 2. - THEN CONVERT(DECIMAL(38, 2), (pages_kb / 1024. / 1024.)) - ELSE NULL - END - FROM sys.dm_os_memory_clerks - WHERE type = ''USERSTORE_TOKENPERM'' - AND name = ''TokenAndPermUserStore'' - '; - - END - - IF @ProductVersionMajor < 11 - - BEGIN - SET @user_perm_sql += N' - SELECT @user_perm_gb = CASE WHEN ((single_pages_kb + multi_pages_kb) / 1024.0 / 1024.) >= 2. - THEN CONVERT(DECIMAL(38, 2), ((single_pages_kb + multi_pages_kb) / 1024.0 / 1024.)) - ELSE NULL - END - FROM sys.dm_os_memory_clerks - WHERE type = ''USERSTORE_TOKENPERM'' - AND name = ''TokenAndPermUserStore'' - '; - - END - - EXEC sys.sp_executesql @user_perm_sql, - N'@user_perm_gb DECIMAL(38,2) OUTPUT', - @user_perm_gb = @user_perm_gb_out OUTPUT - - INSERT INTO #BlitzResults - (CheckID, - Priority, - FindingsGroup, - Finding, - URL, - Details) - SELECT TOP 1 125, 10, 'Performance', 'Plan Cache Erased Recently', 'https://www.brentozar.com/askbrent/plan-cache-erased-recently/', - 'The oldest query in the plan cache was created at ' + CAST(creation_time AS NVARCHAR(50)) - + CASE WHEN @user_perm_gb_out IS NULL - THEN '. Someone ran DBCC FREEPROCCACHE, restarted SQL Server, or it is under horrific memory pressure.' - ELSE '. You also have ' + CONVERT(NVARCHAR(20), @user_perm_gb_out) + ' GB of USERSTORE_TOKENPERM, which could indicate unusual memory consumption.' - END - FROM sys.dm_exec_query_stats WITH (NOLOCK) - ORDER BY creation_time; - END; - - IF EXISTS (SELECT * FROM sys.configurations WHERE name = 'priority boost' AND (value = 1 OR value_in_use = 1)) - AND NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 126 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 126) WITH NOWAIT; - - INSERT INTO #BlitzResults - (CheckID, - Priority, - FindingsGroup, - Finding, - URL, - Details) - VALUES(126, 5, 'Reliability', 'Priority Boost Enabled', 'https://www.brentozar.com/go/priorityboost/', - 'Priority Boost sounds awesome, but it can actually cause your SQL Server to crash.'); - END; - IF NOT EXISTS ( SELECT 1 FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 128 ) - AND SERVERPROPERTY('EngineEdition') <> 8 /* Azure Managed Instances */ + WHERE DatabaseName IS NULL AND CheckID = 88 ) BEGIN - - IF (@ProductVersionMajor = 15 AND @ProductVersionMinor < 2000) OR - (@ProductVersionMajor = 14 AND @ProductVersionMinor < 1000) OR - (@ProductVersionMajor = 13 AND @ProductVersionMinor < 6300) OR - (@ProductVersionMajor = 12 AND @ProductVersionMinor < 6024) OR - (@ProductVersionMajor = 11 /*AND @ProductVersionMinor < 7001)*/) OR - (@ProductVersionMajor = 10.5 /*AND @ProductVersionMinor < 6000*/) OR - (@ProductVersionMajor = 10 /*AND @ProductVersionMinor < 6000*/) OR - (@ProductVersionMajor = 9 /*AND @ProductVersionMinor <= 5000*/) - BEGIN - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 128) WITH NOWAIT; + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 88) WITH NOWAIT; - INSERT INTO #BlitzResults(CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES(128, 20, 'Reliability', 'Unsupported Build of SQL Server', 'https://www.brentozar.com/go/unsupported', - 'Version ' + CAST(@ProductVersionMajor AS VARCHAR(100)) + - CASE WHEN @ProductVersionMajor >= 12 THEN - '.' + CAST(@ProductVersionMinor AS VARCHAR(100)) + ' is no longer supported by Microsoft. You need to apply a service pack.' - ELSE ' is no longer supported by Microsoft. You should be making plans to upgrade to a modern version of SQL Server.' END); - END; - + INSERT INTO #BlitzResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT 88 AS CheckID , + 250 AS Priority , + 'Server Info' AS FindingsGroup , + 'SQL Server Last Restart' AS Finding , + '' AS URL , + CAST(create_date AS VARCHAR(100)) + FROM sys.databases + WHERE database_id = 2; END; - - /* Reliability - Dangerous Build of SQL Server (Corruption) */ + IF NOT EXISTS ( SELECT 1 FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 129 ) - AND SERVERPROPERTY('EngineEdition') <> 8 /* Azure Managed Instances */ + WHERE DatabaseName IS NULL AND CheckID = 91 ) BEGIN - IF (@ProductVersionMajor = 11 AND @ProductVersionMinor >= 3000 AND @ProductVersionMinor <= 3436) OR - (@ProductVersionMajor = 11 AND @ProductVersionMinor = 5058) OR - (@ProductVersionMajor = 12 AND @ProductVersionMinor >= 2000 AND @ProductVersionMinor <= 2342) - BEGIN - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 129) WITH NOWAIT; + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 91) WITH NOWAIT; - INSERT INTO #BlitzResults(CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES(129, 20, 'Reliability', 'Dangerous Build of SQL Server (Corruption)', 'http://sqlperformance.com/2014/06/sql-indexes/hotfix-sql-2012-rebuilds', - 'There are dangerous known bugs with version ' + CAST(@ProductVersionMajor AS VARCHAR(100)) + '.' + CAST(@ProductVersionMinor AS VARCHAR(100)) + '. Check the URL for details and apply the right service pack or hotfix.'); - END; - + INSERT INTO #BlitzResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT 91 AS CheckID , + 250 AS Priority , + 'Server Info' AS FindingsGroup , + 'Server Last Restart' AS Finding , + '' AS URL , + CAST(DATEADD(SECOND, (ms_ticks/1000)*(-1), GETDATE()) AS nvarchar(25)) + FROM sys.dm_os_sys_info; END; - /* Reliability - Dangerous Build of SQL Server (Security) */ IF NOT EXISTS ( SELECT 1 FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 157 ) - AND SERVERPROPERTY('EngineEdition') <> 8 /* Azure Managed Instances */ + WHERE DatabaseName IS NULL AND CheckID = 92 ) BEGIN - IF (@ProductVersionMajor = 10 AND @ProductVersionMinor >= 5500 AND @ProductVersionMinor <= 5512) OR - (@ProductVersionMajor = 10 AND @ProductVersionMinor >= 5750 AND @ProductVersionMinor <= 5867) OR - (@ProductVersionMajor = 10.5 AND @ProductVersionMinor >= 4000 AND @ProductVersionMinor <= 4017) OR - (@ProductVersionMajor = 10.5 AND @ProductVersionMinor >= 4251 AND @ProductVersionMinor <= 4319) OR - (@ProductVersionMajor = 11 AND @ProductVersionMinor >= 3000 AND @ProductVersionMinor <= 3129) OR - (@ProductVersionMajor = 11 AND @ProductVersionMinor >= 3300 AND @ProductVersionMinor <= 3447) OR - (@ProductVersionMajor = 12 AND @ProductVersionMinor >= 2000 AND @ProductVersionMinor <= 2253) OR - (@ProductVersionMajor = 12 AND @ProductVersionMinor >= 2300 AND @ProductVersionMinor <= 2370) - BEGIN - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 157) WITH NOWAIT; + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 92) WITH NOWAIT; - INSERT INTO #BlitzResults(CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES(157, 20, 'Reliability', 'Dangerous Build of SQL Server (Security)', 'https://technet.microsoft.com/en-us/library/security/MS14-044', - 'There are dangerous known bugs with version ' + CAST(@ProductVersionMajor AS VARCHAR(100)) + '.' + CAST(@ProductVersionMinor AS VARCHAR(100)) + '. Check the URL for details and apply the right service pack or hotfix.'); + INSERT INTO #driveInfo + ( drive, available_MB ) + EXEC master..xp_fixeddrives; + + IF EXISTS (SELECT * FROM sys.all_objects WHERE name = 'dm_os_volume_stats') + BEGIN + SET @StringToExecute = 'Update #driveInfo + SET + logical_volume_name = v.logical_volume_name, + total_MB = v.total_MB, + used_percent = v.used_percent + FROM + #driveInfo + inner join ( + SELECT DISTINCT + SUBSTRING(volume_mount_point, 1, 1) AS volume_mount_point + ,CASE WHEN ISNULL(logical_volume_name,'''') = '''' THEN '''' ELSE ''('' + logical_volume_name + '')'' END AS logical_volume_name + ,total_bytes/1024/1024 AS total_MB + ,available_bytes/1024/1024 AS available_MB + ,(CONVERT(DECIMAL(5,2),(total_bytes/1.0 - available_bytes)/total_bytes * 100)) AS used_percent + FROM + (SELECT TOP 1 WITH TIES + database_id + ,file_id + ,SUBSTRING(physical_name,1,1) AS Drive + FROM sys.master_files + ORDER BY ROW_NUMBER() OVER(PARTITION BY SUBSTRING(physical_name,1,1) ORDER BY database_id) + ) f + CROSS APPLY + sys.dm_os_volume_stats(f.database_id, f.file_id) + ) as v on #driveInfo.drive = v.volume_mount_point;'; + EXECUTE(@StringToExecute); END; + SET @StringToExecute ='INSERT INTO #BlitzResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT 92 AS CheckID , + 250 AS Priority , + ''Server Info'' AS FindingsGroup , + ''Drive '' + i.drive + '' Space'' AS Finding , + '''' AS URL , + CASE WHEN i.total_MB IS NULL THEN + CAST(CAST(i.available_MB/1024 AS NUMERIC(18,2)) AS VARCHAR(30)) + + '' GB free on '' + i.drive + + '' drive'' + ELSE CAST(CAST(i.available_MB/1024 AS NUMERIC(18,2)) AS VARCHAR(30)) + + '' GB free on '' + i.drive + + '' drive '' + i.logical_volume_name + + '' out of '' + CAST(CAST(i.total_MB/1024 AS NUMERIC(18,2)) AS VARCHAR(30)) + + '' GB total ('' + CAST(i.used_percent AS VARCHAR(30)) + ''% used)'' END + AS Details + FROM #driveInfo AS i;' + + IF (@ProductVersionMajor >= 11) + BEGIN + SET @StringToExecute=REPLACE(@StringToExecute,'CAST(i.available_MB/1024 AS NUMERIC(18,2))','FORMAT(i.available_MB/1024,''N2'')'); + SET @StringToExecute=REPLACE(@StringToExecute,'CAST(i.total_MB/1024 AS NUMERIC(18,2))','FORMAT(i.total_MB/1024,''N2'')'); + END; + + EXECUTE(@StringToExecute); + + DROP TABLE #driveInfo; END; - - /* Check if SQL 2016 Standard Edition but not SP1 */ + IF NOT EXISTS ( SELECT 1 FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 189 ) - AND SERVERPROPERTY('EngineEdition') <> 8 /* Azure Managed Instances */ + WHERE DatabaseName IS NULL AND CheckID = 103 ) + AND EXISTS ( SELECT * + FROM sys.all_objects o + INNER JOIN sys.all_columns c ON o.object_id = c.object_id + WHERE o.name = 'dm_os_sys_info' + AND c.name = 'virtual_machine_type_desc' ) BEGIN - IF (@ProductVersionMajor = 13 AND @ProductVersionMinor < 4001 AND @@VERSION LIKE '%Standard Edition%') - BEGIN - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 189) WITH NOWAIT; + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 103) WITH NOWAIT; - INSERT INTO #BlitzResults(CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES(189, 100, 'Features', 'Missing Features', 'https://blogs.msdn.microsoft.com/sqlreleaseservices/sql-server-2016-service-pack-1-sp1-released/', - 'SQL 2016 Standard Edition is being used but not Service Pack 1. Check the URL for a list of Enterprise Features that are included in Standard Edition as of SP1.'); - END; + SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) + SELECT 103 AS CheckID, + 250 AS Priority, + ''Server Info'' AS FindingsGroup, + ''Virtual Server'' AS Finding, + ''https://www.brentozar.com/go/virtual'' AS URL, + ''Type: ('' + virtual_machine_type_desc + '')'' AS Details + FROM sys.dm_os_sys_info + WHERE virtual_machine_type <> 0 OPTION (RECOMPILE);'; + + IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; + IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; + + EXECUTE(@StringToExecute); + END; - END; - - /* Check if SQL 2017 but not CU3 */ IF NOT EXISTS ( SELECT 1 FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 216 ) - AND SERVERPROPERTY('EngineEdition') <> 8 /* Azure Managed Instances */ + WHERE DatabaseName IS NULL AND CheckID = 214 ) + AND EXISTS ( SELECT * + FROM sys.all_objects o + INNER JOIN sys.all_columns c ON o.object_id = c.object_id + WHERE o.name = 'dm_os_sys_info' + AND c.name = 'container_type_desc' ) BEGIN - IF (@ProductVersionMajor = 14 AND @ProductVersionMinor < 3015) - BEGIN - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 216) WITH NOWAIT; + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 214) WITH NOWAIT; - INSERT INTO #BlitzResults(CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES(216, 100, 'Features', 'Missing Features', 'https://support.microsoft.com/en-us/help/4041814', - 'SQL 2017 is being used but not Cumulative Update 3. We''d recommend patching to take advantage of increased analytics when running BlitzCache.'); - END; - - END; + SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) + SELECT 214 AS CheckID, + 250 AS Priority, + ''Server Info'' AS FindingsGroup, + ''Container'' AS Finding, + ''https://www.brentozar.com/go/virtual'' AS URL, + ''Type: ('' + container_type_desc + '')'' AS Details + FROM sys.dm_os_sys_info + WHERE container_type_desc <> ''NONE'' OPTION (RECOMPILE);'; + + IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; + IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; + + EXECUTE(@StringToExecute); + END; - /* Cumulative Update Available */ IF NOT EXISTS ( SELECT 1 FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 217 ) - AND SERVERPROPERTY('EngineEdition') NOT IN (5,8) /* Azure Managed Instances and Azure SQL DB*/ - AND EXISTS (SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 'SqlServerVersions' AND TABLE_TYPE = 'BASE TABLE') - AND NOT EXISTS (SELECT * FROM #BlitzResults WHERE CheckID IN (128, 129, 157, 189, 216)) /* Other version checks */ + WHERE DatabaseName IS NULL AND CheckID = 114 ) + AND EXISTS ( SELECT * + FROM sys.all_objects o + WHERE o.name = 'dm_os_memory_nodes' ) + AND EXISTS ( SELECT * + FROM sys.all_objects o + INNER JOIN sys.all_columns c ON o.object_id = c.object_id + WHERE o.name = 'dm_os_nodes' + AND c.name = 'processor_group' ) BEGIN - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 217) WITH NOWAIT; - INSERT INTO #BlitzResults(CheckID, Priority, FindingsGroup, Finding, URL, Details) - SELECT TOP 1 217, 100, 'Reliability', 'Cumulative Update Available', COALESCE(v.Url, 'https://SQLServerUpdates.com/'), - v.MinorVersionName + ' was released on ' + CAST(CONVERT(DATETIME, v.ReleaseDate, 112) AS VARCHAR(100)) - FROM dbo.SqlServerVersions v - WHERE v.MajorVersionNumber = @ProductVersionMajor - AND v.MinorVersionNumber > @ProductVersionMinor - ORDER BY v.MinorVersionNumber DESC; - END; - - /* Performance - High Memory Use for In-Memory OLTP (Hekaton) */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 145 ) - AND EXISTS ( SELECT * - FROM sys.all_objects o - WHERE o.name = 'dm_db_xtp_table_memory_stats' ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 145) WITH NOWAIT; - - SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) - SELECT 145 AS CheckID, - 10 AS Priority, - ''Performance'' AS FindingsGroup, - ''High Memory Use for In-Memory OLTP (Hekaton)'' AS Finding, - ''https://www.brentozar.com/go/hekaton'' AS URL, - CAST(CAST((SUM(mem.pages_kb / 1024.0) / CAST(value_in_use AS INT) * 100) AS INT) AS NVARCHAR(100)) + ''% of your '' + CAST(CAST((CAST(value_in_use AS DECIMAL(38,1)) / 1024) AS MONEY) AS NVARCHAR(100)) + ''GB of your max server memory is being used for in-memory OLTP tables (Hekaton). Microsoft recommends having 2X your Hekaton table space available in memory just for Hekaton, with a max of 250GB of in-memory data regardless of your server memory capacity.'' AS Details - FROM sys.configurations c INNER JOIN sys.dm_os_memory_clerks mem ON mem.type = ''MEMORYCLERK_XTP'' - WHERE c.name = ''max server memory (MB)'' - GROUP BY c.value_in_use - HAVING CAST(value_in_use AS DECIMAL(38,2)) * .25 < SUM(mem.pages_kb / 1024.0) - OR SUM(mem.pages_kb / 1024.0) > 250000 OPTION (RECOMPILE)'; - - IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; - IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; - - EXECUTE(@StringToExecute); - END; - - /* Performance - In-Memory OLTP (Hekaton) In Use */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 146 ) - AND EXISTS ( SELECT * - FROM sys.all_objects o - WHERE o.name = 'dm_db_xtp_table_memory_stats' ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 146) WITH NOWAIT; + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 114) WITH NOWAIT; SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) - SELECT 146 AS CheckID, - 200 AS Priority, - ''Performance'' AS FindingsGroup, - ''In-Memory OLTP (Hekaton) In Use'' AS Finding, - ''https://www.brentozar.com/go/hekaton'' AS URL, - CAST(CAST((SUM(mem.pages_kb / 1024.0) / CAST(value_in_use AS INT) * 100) AS DECIMAL(6,1)) AS NVARCHAR(100)) + ''% of your '' + CAST(CAST((CAST(value_in_use AS DECIMAL(38,1)) / 1024) AS MONEY) AS NVARCHAR(100)) + ''GB of your max server memory is being used for in-memory OLTP tables (Hekaton).'' AS Details - FROM sys.configurations c INNER JOIN sys.dm_os_memory_clerks mem ON mem.type = ''MEMORYCLERK_XTP'' - WHERE c.name = ''max server memory (MB)'' - GROUP BY c.value_in_use - HAVING SUM(mem.pages_kb / 1024.0) > 1000 OPTION (RECOMPILE)'; - - IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; - IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; - - EXECUTE(@StringToExecute); - END; - - /* In-Memory OLTP (Hekaton) - Transaction Errors */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 147 ) - AND EXISTS ( SELECT * - FROM sys.all_objects o - WHERE o.name = 'dm_xtp_transaction_stats' ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 147) WITH NOWAIT + SELECT 114 AS CheckID , + 250 AS Priority , + ''Server Info'' AS FindingsGroup , + ''Hardware - NUMA Config'' AS Finding , + '''' AS URL , + ''Node: '' + CAST(n.node_id AS NVARCHAR(10)) + '' State: '' + node_state_desc + + '' Online schedulers: '' + CAST(n.online_scheduler_count AS NVARCHAR(10)) + '' Offline schedulers: '' + CAST(oac.offline_schedulers AS VARCHAR(100)) + '' Processor Group: '' + CAST(n.processor_group AS NVARCHAR(10)) + + '' Memory node: '' + CAST(n.memory_node_id AS NVARCHAR(10)) + '' Memory VAS Reserved GB: '' + CAST(CAST((m.virtual_address_space_reserved_kb / 1024.0 / 1024) AS INT) AS NVARCHAR(100)) + FROM sys.dm_os_nodes n + INNER JOIN sys.dm_os_memory_nodes m ON n.memory_node_id = m.memory_node_id + OUTER APPLY (SELECT + COUNT(*) AS [offline_schedulers] + FROM sys.dm_os_schedulers dos + WHERE n.node_id = dos.parent_node_id + AND dos.status = ''VISIBLE OFFLINE'' + ) oac + WHERE n.node_state_desc NOT LIKE ''%DAC%'' + ORDER BY n.node_id OPTION (RECOMPILE);'; - SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) - SELECT 147 AS CheckID, - 100 AS Priority, - ''In-Memory OLTP (Hekaton)'' AS FindingsGroup, - ''Transaction Errors'' AS Finding, - ''https://www.brentozar.com/go/hekaton'' AS URL, - ''Since restart: '' + CAST(validation_failures AS NVARCHAR(100)) + '' validation failures, '' + CAST(dependencies_failed AS NVARCHAR(100)) + '' dependency failures, '' + CAST(write_conflicts AS NVARCHAR(100)) + '' write conflicts, '' + CAST(unique_constraint_violations AS NVARCHAR(100)) + '' unique constraint violations.'' AS Details - FROM sys.dm_xtp_transaction_stats - WHERE validation_failures <> 0 - OR dependencies_failed <> 0 - OR write_conflicts <> 0 - OR unique_constraint_violations <> 0 OPTION (RECOMPILE);'; - IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; EXECUTE(@StringToExecute); - END; - - /* Reliability - Database Files on Network File Shares */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 148 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 148) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT DISTINCT 148 AS CheckID , - d.[name] AS DatabaseName , - 170 AS Priority , - 'Reliability' AS FindingsGroup , - 'Database Files on Network File Shares' AS Finding , - 'https://www.brentozar.com/go/nas' AS URL , - ( 'Files for this database are on: ' + LEFT(mf.physical_name, 30)) AS Details - FROM sys.databases d - INNER JOIN sys.master_files mf ON d.database_id = mf.database_id - WHERE mf.physical_name LIKE '\\%' - AND d.name NOT IN ( SELECT DISTINCT - DatabaseName - FROM #SkipChecks - WHERE CheckID IS NULL OR CheckID = 148); - END; - - /* Reliability - Database Files Stored in Azure */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 149 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 149) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT DISTINCT 149 AS CheckID , - d.[name] AS DatabaseName , - 170 AS Priority , - 'Reliability' AS FindingsGroup , - 'Database Files Stored in Azure' AS Finding , - 'https://www.brentozar.com/go/azurefiles' AS URL , - ( 'Files for this database are on: ' + LEFT(mf.physical_name, 30)) AS Details - FROM sys.databases d - INNER JOIN sys.master_files mf ON d.database_id = mf.database_id - WHERE mf.physical_name LIKE 'http://%' - AND d.name NOT IN ( SELECT DISTINCT - DatabaseName - FROM #SkipChecks - WHERE CheckID IS NULL OR CheckID = 149); - END; - - /* Reliability - Errors Logged Recently in the Default Trace */ - - /* First, let's check that there aren't any issues with the trace files */ - BEGIN TRY - - IF @SkipTrace = 0 - BEGIN - INSERT INTO #fnTraceGettable - ( TextData , - DatabaseName , - EventClass , - Severity , - StartTime , - EndTime , - Duration , - NTUserName , - NTDomainName , - HostName , - ApplicationName , - LoginName , - DBUserName - ) - SELECT TOP 20000 - CONVERT(NVARCHAR(4000),t.TextData) , - t.DatabaseName , - t.EventClass , - t.Severity , - t.StartTime , - t.EndTime , - t.Duration , - t.NTUserName , - t.NTDomainName , - t.HostName , - t.ApplicationName , - t.LoginName , - t.DBUserName - FROM sys.fn_trace_gettable(@base_tracefilename, DEFAULT) t - WHERE - ( - t.EventClass = 22 - AND t.Severity >= 17 - AND t.StartTime > DATEADD(dd, -30, GETDATE()) - ) - OR - ( - t.EventClass IN (92, 93) - AND t.StartTime > DATEADD(dd, -30, GETDATE()) - AND t.Duration > 15000000 - ) - OR - ( - t.EventClass IN (94, 95, 116) - ) END; + - SET @TraceFileIssue = 0 - - END TRY - BEGIN CATCH - - SET @TraceFileIssue = 1 - - END CATCH - - IF @TraceFileIssue = 1 - BEGIN IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 199 ) + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 211 ) + BEGIN - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT - '199' AS CheckID , - '' AS DatabaseName , - 50 AS Priority , - 'Reliability' AS FindingsGroup , - 'There Is An Error With The Default Trace' AS Finding , - 'https://www.brentozar.com/go/defaulttrace' AS URL , - 'Somebody has been messing with your trace files. Check the files are present at ' + @base_tracefilename AS Details - END - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 150 ) - AND @base_tracefilename IS NOT NULL - AND @TraceFileIssue = 0 - BEGIN + /* Variables for check 211: */ + DECLARE + @powerScheme varchar(36) + ,@cpu_speed_mhz int + ,@cpu_speed_ghz decimal(18,2) + ,@ExecResult int; - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 150) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT DISTINCT 150 AS CheckID , - t.DatabaseName, - 170 AS Priority , - 'Reliability' AS FindingsGroup , - 'Errors Logged Recently in the Default Trace' AS Finding , - 'https://www.brentozar.com/go/defaulttrace' AS URL , - CAST(t.TextData AS NVARCHAR(4000)) AS Details - FROM #fnTraceGettable t - WHERE t.EventClass = 22 - /* Removed these as they're unnecessary, we filter this when inserting data into #fnTraceGettable */ - --AND t.Severity >= 17 - --AND t.StartTime > DATEADD(dd, -30, GETDATE()); - END; + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 211) WITH NOWAIT; + IF @sa = 0 RAISERROR('The errors: ''xp_regread() returned error 5, ''Access is denied.'''' can be safely ignored', 0, 1) WITH NOWAIT; - /* Performance - File Growths Slow */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 151 ) - AND @base_tracefilename IS NOT NULL - AND @TraceFileIssue = 0 - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 151) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT DISTINCT 151 AS CheckID , - t.DatabaseName, - 50 AS Priority , - 'Performance' AS FindingsGroup , - 'File Growths Slow' AS Finding , - 'https://www.brentozar.com/go/filegrowth' AS URL , - CAST(COUNT(*) AS NVARCHAR(100)) + ' growths took more than 15 seconds each. Consider setting file autogrowth to a smaller increment.' AS Details - FROM #fnTraceGettable t - WHERE t.EventClass IN (92, 93) - /* Removed these as they're unnecessary, we filter this when inserting data into #fnTraceGettable */ - --AND t.StartTime > DATEADD(dd, -30, GETDATE()) - --AND t.Duration > 15000000 - GROUP BY t.DatabaseName - HAVING COUNT(*) > 1; - END; + /* Get power plan if set by group policy [Git Hub Issue #1620] */ + EXEC xp_regread @rootkey = N'HKEY_LOCAL_MACHINE', + @key = N'SOFTWARE\Policies\Microsoft\Power\PowerSettings', + @value_name = N'ActivePowerScheme', + @value = @powerScheme OUTPUT, + @no_output = N'no_output'; - /* Performance - Many Plans for One Query */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 160 ) - AND EXISTS (SELECT * FROM sys.all_columns WHERE name = 'query_hash') - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 160) WITH NOWAIT; + IF @powerScheme IS NULL /* If power plan was not set by group policy, get local value [Git Hub Issue #1620]*/ + EXEC xp_regread @rootkey = N'HKEY_LOCAL_MACHINE', + @key = N'SYSTEM\CurrentControlSet\Control\Power\User\PowerSchemes', + @value_name = N'ActivePowerScheme', + @value = @powerScheme OUTPUT; - SET @StringToExecute = N'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) - SELECT TOP 1 160 AS CheckID, - 100 AS Priority, - ''Performance'' AS FindingsGroup, - ''Many Plans for One Query'' AS Finding, - ''https://www.brentozar.com/go/parameterization'' AS URL, - CAST(COUNT(DISTINCT plan_handle) AS NVARCHAR(50)) + '' plans are present for a single query in the plan cache - meaning we probably have parameterization issues.'' AS Details - FROM sys.dm_exec_query_stats qs - CROSS APPLY sys.dm_exec_plan_attributes(qs.plan_handle) pa - WHERE pa.attribute = ''dbid'' - GROUP BY qs.query_hash, pa.value - HAVING COUNT(DISTINCT plan_handle) > '; + /* Get the cpu speed*/ + EXEC @ExecResult = xp_regread @rootkey = N'HKEY_LOCAL_MACHINE', + @key = N'HARDWARE\DESCRIPTION\System\CentralProcessor\0', + @value_name = N'~MHz', + @value = @cpu_speed_mhz OUTPUT; - IF 50 > (SELECT COUNT(*) FROM sys.databases) - SET @StringToExecute = @StringToExecute + N' 50 '; - ELSE - SELECT @StringToExecute = @StringToExecute + CAST(COUNT(*) * 2 AS NVARCHAR(50)) FROM sys.databases; + /* Convert the Megahertz to Gigahertz */ + IF @ExecResult != 0 RAISERROR('We couldn''t retrieve the CPU speed, you will see Unknown in the results', 0, 1) - SET @StringToExecute = @StringToExecute + N' ORDER BY COUNT(DISTINCT plan_handle) DESC OPTION (RECOMPILE);'; - - IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; - IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; - - EXECUTE(@StringToExecute); - END; + SET @cpu_speed_ghz = CAST(CAST(@cpu_speed_mhz AS decimal) / 1000 AS decimal(18,2)); - /* Performance - High Number of Cached Plans */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 161 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 161) WITH NOWAIT; - - SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) - SELECT TOP 1 161 AS CheckID, - 100 AS Priority, - ''Performance'' AS FindingsGroup, - ''High Number of Cached Plans'' AS Finding, - ''https://www.brentozar.com/go/planlimits'' AS URL, - ''Your server configuration is limited to '' + CAST(ht.buckets_count * 4 AS VARCHAR(20)) + '' '' + ht.name + '', and you are currently caching '' + CAST(cc.entries_count AS VARCHAR(20)) + ''.'' AS Details - FROM sys.dm_os_memory_cache_hash_tables ht - INNER JOIN sys.dm_os_memory_cache_counters cc ON ht.name = cc.name AND ht.type = cc.type - where ht.name IN ( ''SQL Plans'' , ''Object Plans'' , ''Bound Trees'' ) - AND cc.entries_count >= (3 * ht.buckets_count) OPTION (RECOMPILE)'; - - IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; - IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; + INSERT INTO #BlitzResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT 211 AS CheckId, + 250 AS Priority, + 'Server Info' AS FindingsGroup, + 'Power Plan' AS Finding, + 'https://www.brentozar.com/blitz/power-mode/' AS URL, + 'Your server has ' + + ISNULL(CAST(@cpu_speed_ghz as VARCHAR(8)), 'Unknown ') + + 'GHz CPUs, and is in ' + + CASE @powerScheme + WHEN 'a1841308-3541-4fab-bc81-f71556f20b4a' + THEN 'power saving mode -- are you sure this is a production SQL Server?' + WHEN '381b4222-f694-41f0-9685-ff5bb260df2e' + THEN 'balanced power mode -- Uh... you want your CPUs to run at full speed, right?' + WHEN '8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c' + THEN 'high performance power mode' + WHEN 'e9a42b02-d5df-448d-aa00-03f14749eb61' + THEN 'ultimate performance power mode' + ELSE 'an unknown power mode.' + END AS Details - EXECUTE(@StringToExecute); - END; + END; - /* Performance - Too Much Free Memory */ IF NOT EXISTS ( SELECT 1 FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 165 ) - BEGIN + WHERE DatabaseName IS NULL AND CheckID = 212 ) + BEGIN - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 165) WITH NOWAIT; + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 212) WITH NOWAIT; + + INSERT INTO #Instances (Instance_Number, Instance_Name, Data_Field) + EXEC master.sys.xp_regread @rootkey = 'HKEY_LOCAL_MACHINE', + @key = 'SOFTWARE\Microsoft\Microsoft SQL Server', + @value_name = 'InstalledInstances' - INSERT INTO #BlitzResults - (CheckID, - Priority, - FindingsGroup, - Finding, - URL, - Details) - SELECT 165, 50, 'Performance', 'Too Much Free Memory', 'https://www.brentozar.com/go/freememory', - CAST((CAST(cFree.cntr_value AS BIGINT) / 1024 / 1024 ) AS NVARCHAR(100)) + N'GB of free memory inside SQL Server''s buffer pool, which is ' + CAST((CAST(cTotal.cntr_value AS BIGINT) / 1024 / 1024) AS NVARCHAR(100)) + N'GB. You would think lots of free memory would be good, but check out the URL for more information.' AS Details - FROM sys.dm_os_performance_counters cFree - INNER JOIN sys.dm_os_performance_counters cTotal ON cTotal.object_name LIKE N'%Memory Manager%' - AND cTotal.counter_name = N'Total Server Memory (KB) ' - WHERE cFree.object_name LIKE N'%Memory Manager%' - AND cFree.counter_name = N'Free Memory (KB) ' - AND CAST(cTotal.cntr_value AS BIGINT) > 20480000000 - AND CAST(cTotal.cntr_value AS BIGINT) * .3 <= CAST(cFree.cntr_value AS BIGINT) - AND CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) NOT LIKE '%Standard%'; + IF (SELECT COUNT(*) FROM #Instances) > 1 + BEGIN - END; + DECLARE @InstanceCount NVARCHAR(MAX) + SELECT @InstanceCount = COUNT(*) FROM #Instances - /* Outdated sp_Blitz - sp_Blitz is Over 6 Months Old */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 155 ) - AND DATEDIFF(MM, @VersionDate, GETDATE()) > 6 - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 155) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 155 AS CheckID , - 0 AS Priority , - 'Outdated sp_Blitz' AS FindingsGroup , - 'sp_Blitz is Over 6 Months Old' AS Finding , - 'http://FirstResponderKit.org/' AS URL , - 'Some things get better with age, like fine wine and your T-SQL. However, sp_Blitz is not one of those things - time to go download the current one.' AS Details; + INSERT INTO #BlitzResults + ( + CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT + 212 AS CheckId , + 250 AS Priority , + 'Server Info' AS FindingsGroup , + 'Instance Stacking' AS Finding , + 'https://www.brentozar.com/go/babygotstacked/' AS URL , + 'Your Server has ' + @InstanceCount + ' Instances of SQL Server installed. More than one is usually a bad idea. Read the URL for more info.' + END; END; + + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 106 ) + AND (select convert(int,value_in_use) from sys.configurations where name = 'default trace enabled' ) = 1 + AND DATALENGTH( COALESCE( @base_tracefilename, '' ) ) > DATALENGTH('.TRC') + AND @TraceFileIssue = 0 + BEGIN - /* Populate a list of database defaults. I'm doing this kind of oddly - - it reads like a lot of work, but this way it compiles & runs on all - versions of SQL Server. - */ - - IF @Debug IN (1, 2) RAISERROR('Generating database defaults.', 0, 1) WITH NOWAIT; - - INSERT INTO #DatabaseDefaults - SELECT 'is_supplemental_logging_enabled', 0, 131, 210, 'Supplemental Logging Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL - FROM sys.all_columns - WHERE name = 'is_supplemental_logging_enabled' AND object_id = OBJECT_ID('sys.databases'); - INSERT INTO #DatabaseDefaults - SELECT 'snapshot_isolation_state', 0, 132, 210, 'Snapshot Isolation Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL - FROM sys.all_columns - WHERE name = 'snapshot_isolation_state' AND object_id = OBJECT_ID('sys.databases'); - INSERT INTO #DatabaseDefaults - SELECT 'is_read_committed_snapshot_on', - CASE WHEN SERVERPROPERTY('EngineEdition') = 5 THEN 1 ELSE 0 END, /* RCSI is always enabled in Azure SQL DB per https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/1919 */ - 133, 210, CASE WHEN SERVERPROPERTY('EngineEdition') = 5 THEN 'Read Committed Snapshot Isolation Disabled' ELSE 'Read Committed Snapshot Isolation Enabled' END, 'https://www.brentozar.com/go/dbdefaults', NULL - FROM sys.all_columns - WHERE name = 'is_read_committed_snapshot_on' AND object_id = OBJECT_ID('sys.databases'); - INSERT INTO #DatabaseDefaults - SELECT 'is_auto_create_stats_incremental_on', 0, 134, 210, 'Auto Create Stats Incremental Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL - FROM sys.all_columns - WHERE name = 'is_auto_create_stats_incremental_on' AND object_id = OBJECT_ID('sys.databases'); - INSERT INTO #DatabaseDefaults - SELECT 'is_ansi_null_default_on', 0, 135, 210, 'ANSI NULL Default Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL - FROM sys.all_columns - WHERE name = 'is_ansi_null_default_on' AND object_id = OBJECT_ID('sys.databases'); - INSERT INTO #DatabaseDefaults - SELECT 'is_recursive_triggers_on', 0, 136, 210, 'Recursive Triggers Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL - FROM sys.all_columns - WHERE name = 'is_recursive_triggers_on' AND object_id = OBJECT_ID('sys.databases'); - INSERT INTO #DatabaseDefaults - SELECT 'is_trustworthy_on', 0, 137, 210, 'Trustworthy Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL - FROM sys.all_columns - WHERE name = 'is_trustworthy_on' AND object_id = OBJECT_ID('sys.databases'); - INSERT INTO #DatabaseDefaults - SELECT 'is_broker_enabled', 0, 230, 210, 'Broker Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL - FROM sys.all_columns - WHERE name = 'is_broker_enabled' AND object_id = OBJECT_ID('sys.databases'); - INSERT INTO #DatabaseDefaults - SELECT 'is_honor_broker_priority_on', 0, 231, 210, 'Honor Broker Priority Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL - FROM sys.all_columns - WHERE name = 'is_honor_broker_priority_on' AND object_id = OBJECT_ID('sys.databases'); - INSERT INTO #DatabaseDefaults - SELECT 'is_parameterization_forced', 0, 138, 210, 'Forced Parameterization Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL - FROM sys.all_columns - WHERE name = 'is_parameterization_forced' AND object_id = OBJECT_ID('sys.databases'); - /* Not alerting for this since we actually want it and we have a separate check for it: - INSERT INTO #DatabaseDefaults - SELECT 'is_query_store_on', 0, 139, 210, 'Query Store Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL - FROM sys.all_columns - WHERE name = 'is_query_store_on' AND object_id = OBJECT_ID('sys.databases'); - */ - INSERT INTO #DatabaseDefaults - SELECT 'is_cdc_enabled', 0, 140, 210, 'Change Data Capture Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL - FROM sys.all_columns - WHERE name = 'is_cdc_enabled' AND object_id = OBJECT_ID('sys.databases'); - INSERT INTO #DatabaseDefaults - SELECT 'containment', 0, 141, 210, 'Containment Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL - FROM sys.all_columns - WHERE name = 'containment' AND object_id = OBJECT_ID('sys.databases'); - --INSERT INTO #DatabaseDefaults - -- SELECT 'target_recovery_time_in_seconds', 0, 142, 210, 'Target Recovery Time Changed', 'https://www.brentozar.com/go/dbdefaults', NULL - -- FROM sys.all_columns - -- WHERE name = 'target_recovery_time_in_seconds' AND object_id = OBJECT_ID('sys.databases'); - INSERT INTO #DatabaseDefaults - SELECT 'delayed_durability', 0, 143, 210, 'Delayed Durability Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL - FROM sys.all_columns - WHERE name = 'delayed_durability' AND object_id = OBJECT_ID('sys.databases'); - INSERT INTO #DatabaseDefaults - SELECT 'is_memory_optimized_elevate_to_snapshot_on', 0, 144, 210, 'Memory Optimized Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL - FROM sys.all_columns - WHERE name = 'is_memory_optimized_elevate_to_snapshot_on' AND object_id = OBJECT_ID('sys.databases') - AND SERVERPROPERTY('EngineEdition') <> 8; /* Hekaton is always enabled in Managed Instances per https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/1919 */ - INSERT INTO #DatabaseDefaults - SELECT 'is_accelerated_database_recovery_on', 0, 145, 210, 'Acclerated Database Recovery Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL - FROM sys.all_columns - WHERE name = 'is_accelerated_database_recovery_on' AND object_id = OBJECT_ID('sys.databases') AND SERVERPROPERTY('EngineEdition') NOT IN (5, 8) ; + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 106) WITH NOWAIT; + + INSERT INTO #BlitzResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT + 106 AS CheckID + ,250 AS Priority + ,'Server Info' AS FindingsGroup + ,'Default Trace Contents' AS Finding + ,'https://www.brentozar.com/go/trace' AS URL + ,'The default trace holds '+cast(DATEDIFF(hour,MIN(StartTime),GETDATE())as VARCHAR(30))+' hours of data' + +' between '+cast(Min(StartTime) as VARCHAR(30))+' and '+cast(GETDATE()as VARCHAR(30)) + +('. The default trace files are located in: '+left( @curr_tracefilename,len(@curr_tracefilename) - @indx) + ) as Details + FROM ::fn_trace_gettable( @base_tracefilename, default ) + WHERE EventClass BETWEEN 65500 and 65600; + END; /* CheckID 106 */ - DECLARE DatabaseDefaultsLoop CURSOR FOR - SELECT name, DefaultValue, CheckID, Priority, Finding, URL, Details - FROM #DatabaseDefaults; + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 152 ) + BEGIN + IF EXISTS (SELECT * FROM sys.dm_os_wait_stats ws + LEFT OUTER JOIN #IgnorableWaits i ON ws.wait_type = i.wait_type + WHERE wait_time_ms > .1 * @CpuMsSinceWaitsCleared AND waiting_tasks_count > 0 + AND i.wait_type IS NULL) + BEGIN + /* Check for waits that have had more than 10% of the server's wait time */ + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 152) WITH NOWAIT; + + WITH os(wait_type, waiting_tasks_count, wait_time_ms, max_wait_time_ms, signal_wait_time_ms) + AS + (SELECT ws.wait_type, waiting_tasks_count, wait_time_ms, max_wait_time_ms, signal_wait_time_ms + FROM sys.dm_os_wait_stats ws + LEFT OUTER JOIN #IgnorableWaits i ON ws.wait_type = i.wait_type + WHERE i.wait_type IS NULL + AND wait_time_ms > .1 * @CpuMsSinceWaitsCleared + AND waiting_tasks_count > 0) + INSERT INTO #BlitzResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT TOP 9 + 152 AS CheckID + ,240 AS Priority + ,'Wait Stats' AS FindingsGroup + , CAST(ROW_NUMBER() OVER(ORDER BY os.wait_time_ms DESC) AS NVARCHAR(10)) + N' - ' + os.wait_type AS Finding + ,'https://www.sqlskills.com/help/waits/' + LOWER(os.wait_type) + '/' AS URL + , Details = CAST(CAST(SUM(os.wait_time_ms / 1000.0 / 60 / 60) OVER (PARTITION BY os.wait_type) AS NUMERIC(18,1)) AS NVARCHAR(20)) + N' hours of waits, ' + + CAST(CAST((SUM(60.0 * os.wait_time_ms) OVER (PARTITION BY os.wait_type) ) / @MsSinceWaitsCleared AS NUMERIC(18,1)) AS NVARCHAR(20)) + N' minutes average wait time per hour, ' + + /* CAST(CAST( + 100.* SUM(os.wait_time_ms) OVER (PARTITION BY os.wait_type) + / (1. * SUM(os.wait_time_ms) OVER () ) + AS NUMERIC(18,1)) AS NVARCHAR(40)) + N'% of waits, ' + */ + CAST(CAST( + 100. * SUM(os.signal_wait_time_ms) OVER (PARTITION BY os.wait_type) + / (1. * SUM(os.wait_time_ms) OVER ()) + AS NUMERIC(18,1)) AS NVARCHAR(40)) + N'% signal wait, ' + + CAST(SUM(os.waiting_tasks_count) OVER (PARTITION BY os.wait_type) AS NVARCHAR(40)) + N' waiting tasks, ' + + CAST(CASE WHEN SUM(os.waiting_tasks_count) OVER (PARTITION BY os.wait_type) > 0 + THEN + CAST( + SUM(os.wait_time_ms) OVER (PARTITION BY os.wait_type) + / (1. * SUM(os.waiting_tasks_count) OVER (PARTITION BY os.wait_type)) + AS NUMERIC(18,1)) + ELSE 0 END AS NVARCHAR(40)) + N' ms average wait time.' + FROM os + ORDER BY SUM(os.wait_time_ms / 1000.0 / 60 / 60) OVER (PARTITION BY os.wait_type) DESC; + END; /* IF EXISTS (SELECT * FROM sys.dm_os_wait_stats WHERE wait_time_ms > 0 AND waiting_tasks_count > 0) */ - OPEN DatabaseDefaultsLoop; - FETCH NEXT FROM DatabaseDefaultsLoop into @CurrentName, @CurrentDefaultValue, @CurrentCheckID, @CurrentPriority, @CurrentFinding, @CurrentURL, @CurrentDetails; - WHILE @@FETCH_STATUS = 0 - BEGIN + /* If no waits were found, add a note about that */ + IF NOT EXISTS (SELECT * FROM #BlitzResults WHERE CheckID IN (107, 108, 109, 121, 152, 162)) + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 153) WITH NOWAIT; + + INSERT INTO #BlitzResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + VALUES (153, 240, 'Wait Stats', 'No Significant Waits Detected', 'https://www.brentozar.com/go/waits', 'This server might be just sitting around idle, or someone may have cleared wait stats recently.'); + END; + END; /* CheckID 152 */ - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, @CurrentCheckID) WITH NOWAIT; + /* CheckID 222 - Server Info - Azure Managed Instance */ + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 222 ) + AND 4 = ( SELECT COUNT(*) + FROM sys.all_objects o + INNER JOIN sys.all_columns c ON o.object_id = c.object_id + WHERE o.name = 'dm_os_job_object' + AND c.name IN ('cpu_rate', 'memory_limit_mb', 'process_memory_limit_mb', 'workingset_limit_mb' )) + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 222) WITH NOWAIT; + + SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) + SELECT 222 AS CheckID , + 250 AS Priority , + ''Server Info'' AS FindingsGroup , + ''Azure Managed Instance'' AS Finding , + ''https://www.BrentOzar.com/go/azurevm'' AS URL , + ''cpu_rate: '' + CAST(COALESCE(cpu_rate, 0) AS VARCHAR(20)) + + '', memory_limit_mb: '' + CAST(COALESCE(memory_limit_mb, 0) AS NVARCHAR(20)) + + '', process_memory_limit_mb: '' + CAST(COALESCE(process_memory_limit_mb, 0) AS NVARCHAR(20)) + + '', workingset_limit_mb: '' + CAST(COALESCE(workingset_limit_mb, 0) AS NVARCHAR(20)) + FROM sys.dm_os_job_object OPTION (RECOMPILE);'; + + IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; + IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; + + EXECUTE(@StringToExecute); + END; - /* Target Recovery Time (142) can be either 0 or 60 due to a number of bugs */ - IF @CurrentCheckID = 142 - SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details) - SELECT ' + CAST(@CurrentCheckID AS NVARCHAR(200)) + ', d.[name], ' + CAST(@CurrentPriority AS NVARCHAR(200)) + ', ''Non-Default Database Config'', ''' + @CurrentFinding + ''',''' + @CurrentURL + ''',''' + COALESCE(@CurrentDetails, 'This database setting is not the default.') + ''' - FROM sys.databases d - WHERE d.database_id > 4 AND d.state = 0 AND (d.[' + @CurrentName + '] NOT IN (0, 60) OR d.[' + @CurrentName + '] IS NULL) OPTION (RECOMPILE);'; - ELSE - SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details) - SELECT ' + CAST(@CurrentCheckID AS NVARCHAR(200)) + ', d.[name], ' + CAST(@CurrentPriority AS NVARCHAR(200)) + ', ''Non-Default Database Config'', ''' + @CurrentFinding + ''',''' + @CurrentURL + ''',''' + COALESCE(@CurrentDetails, 'This database setting is not the default.') + ''' - FROM sys.databases d - WHERE d.database_id > 4 AND d.state = 0 AND (d.[' + @CurrentName + '] <> ' + @CurrentDefaultValue + ' OR d.[' + @CurrentName + '] IS NULL) OPTION (RECOMPILE);'; - - IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; - IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; - - EXEC (@StringToExecute); + /* CheckID 224 - Performance - SSRS/SSAS/SSIS Installed */ + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 224 ) + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 224) WITH NOWAIT; + + IF (SELECT value_in_use FROM sys.configurations WHERE [name] = 'xp_cmdshell') = 1 + BEGIN + + IF OBJECT_ID('tempdb..#services') IS NOT NULL DROP TABLE #services; + CREATE TABLE #services (cmdshell_output varchar(max)); + + INSERT INTO #services + EXEC /**/xp_cmdshell/**/ 'net start' /* added comments around command since some firewalls block this string TL 20210221 */ + + IF EXISTS (SELECT 1 + FROM #services + WHERE cmdshell_output LIKE '%SQL Server Reporting Services%' + OR cmdshell_output LIKE '%SQL Server Integration Services%' + OR cmdshell_output LIKE '%SQL Server Analysis Services%') + BEGIN + INSERT INTO #BlitzResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT + 224 AS CheckID + ,200 AS Priority + ,'Performance' AS FindingsGroup + ,'SSAS/SSIS/SSRS Installed' AS Finding + ,'https://www.BrentOzar.com/go/services' AS URL + ,'Did you know you have other SQL Server services installed on this box other than the engine? It can be a real performance pain.' as Details + + END; + + END; + END; - FETCH NEXT FROM DatabaseDefaultsLoop into @CurrentName, @CurrentDefaultValue, @CurrentCheckID, @CurrentPriority, @CurrentFinding, @CurrentURL, @CurrentDetails; - END; + /* CheckID 232 - Server Info - Data Size */ + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 232 ) + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 232) WITH NOWAIT; + + IF OBJECT_ID('tempdb..#MasterFiles') IS NOT NULL + DROP TABLE #MasterFiles; + CREATE TABLE #MasterFiles (database_id INT, file_id INT, type_desc NVARCHAR(50), name NVARCHAR(255), physical_name NVARCHAR(255), size BIGINT); + /* Azure SQL Database doesn't have sys.master_files, so we have to build our own. */ + IF ((SERVERPROPERTY('Edition')) = 'SQL Azure' + AND (OBJECT_ID('sys.master_files') IS NULL)) + SET @StringToExecute = 'INSERT INTO #MasterFiles (database_id, file_id, type_desc, name, physical_name, size) SELECT DB_ID(), file_id, type_desc, name, physical_name, size FROM sys.database_files;'; + ELSE + SET @StringToExecute = 'INSERT INTO #MasterFiles (database_id, file_id, type_desc, name, physical_name, size) SELECT database_id, file_id, type_desc, name, physical_name, size FROM sys.master_files;'; + EXEC(@StringToExecute); - CLOSE DatabaseDefaultsLoop; - DEALLOCATE DatabaseDefaultsLoop; -/* Check if target recovery interval <> 60 */ -IF - @ProductVersionMajor >= 10 - AND NOT EXISTS - ( - SELECT - 1/0 - FROM #SkipChecks AS sc - WHERE sc.DatabaseName IS NULL - AND sc.CheckID = 257 - ) - BEGIN - IF EXISTS - ( - SELECT - 1/0 - FROM sys.all_columns AS ac - WHERE ac.name = 'target_recovery_time_in_seconds' - AND ac.object_id = OBJECT_ID('sys.databases') - ) - BEGIN - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 257) WITH NOWAIT; - - DECLARE - @tri nvarchar(max) = N' - SELECT - DatabaseName = - d.name, - CheckId = - 257, - Priority = - 50, - FindingsGroup = - N''Performance'', - Finding = - N''Recovery Interval Not Optimal'', - URL = - N''https://sqlperformance.com/2020/05/system-configuration/0-to-60-switching-to-indirect-checkpoints'', - Details = - N''The database '' + - QUOTENAME(d.name) + - N'' has a target recovery interval of '' + - RTRIM(d.target_recovery_time_in_seconds) + - CASE - WHEN d.target_recovery_time_in_seconds = 0 - THEN N'', which is a legacy default, and should be changed to 60.'' - WHEN d.target_recovery_time_in_seconds <> 0 - THEN N'', which is probably a mistake, and should be changed to 60.'' - END - FROM sys.databases AS d - WHERE d.database_id > 4 - AND d.is_read_only = 0 - AND d.is_in_standby = 0 - AND d.target_recovery_time_in_seconds <> 60; - '; + INSERT INTO #BlitzResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT 232 AS CheckID + ,250 AS Priority + ,'Server Info' AS FindingsGroup + ,'Data Size' AS Finding + ,'' AS URL + ,CAST(COUNT(DISTINCT database_id) AS NVARCHAR(100)) + N' databases, ' + CAST(CAST(SUM (CAST(size AS BIGINT)*8./1024./1024.) AS MONEY) AS VARCHAR(100)) + ' GB total file size' as Details + FROM #MasterFiles + WHERE database_id > 4; - INSERT INTO - #BlitzResults - ( - DatabaseName, - CheckID, - Priority, - FindingsGroup, - Finding, - URL, - Details - ) - EXEC sys.sp_executesql - @tri; + END; - END; - END; - + /* CheckID 260 - Security - SQL Server service account is member of Administrators */ + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 260 ) AND @ProductVersionMajor >= 10 + BEGIN + + IF (SELECT value_in_use FROM sys.configurations WHERE [name] = 'xp_cmdshell') = 1 + AND EXISTS ( SELECT 1 FROM sys.all_objects WHERE [name] = 'dm_server_services' ) + BEGIN + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 260) WITH NOWAIT; + IF OBJECT_ID('tempdb..#localadmins') IS NOT NULL DROP TABLE #localadmins; + CREATE TABLE #localadmins (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 */ + + IF EXISTS (SELECT 1 + FROM #localadmins + 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 Launchpad%')) + BEGIN + INSERT INTO #BlitzResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT + 260 AS CheckID + ,1 AS Priority + ,'Security' AS FindingsGroup + ,'Dangerous Service Account' AS Finding + ,'https://vladdba.com/SQLServerSvcAccount' AS URL + ,'SQL Server''s service account is a member of the local Administrators group - meaning that anyone who can use xp_cmdshell can do anything on the host.' as Details + + END; + + END; + END; -/*This checks to see if Agent is Offline*/ -IF @ProductVersionMajor >= 10 - AND NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 167 ) - BEGIN - IF EXISTS ( SELECT 1 - FROM sys.all_objects - WHERE name = 'dm_server_services' ) + /* CheckID 261 - Security - SQL Server Agent service account is member of Administrators */ + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 261 ) AND @ProductVersionMajor >= 10 + BEGIN + + IF (SELECT value_in_use FROM sys.configurations WHERE [name] = 'xp_cmdshell') = 1 + AND EXISTS ( SELECT 1 FROM sys.all_objects WHERE [name] = 'dm_server_services' ) + BEGIN + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 261) WITH NOWAIT; + /*If this table exists and CheckId 260 was not skipped, then we're piggybacking off of 260's results */ + IF OBJECT_ID('tempdb..#localadmins') IS NOT NULL + AND NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 260 ) + BEGIN + IF @Debug IN (1, 2) RAISERROR('CheckId [%d] - found #localadmins table from CheckID 260 - no need to call xp_cmdshell again', 0, 1, 261) WITH NOWAIT; + + IF EXISTS (SELECT 1 + FROM #localadmins + WHERE LOWER(cmdshell_output) = ( SELECT LOWER([service_account]) + FROM [sys].[dm_server_services] + WHERE [servicename] LIKE 'SQL Server Agent%' + AND [servicename] NOT LIKE 'SQL Server Launchpad%')) + BEGIN + INSERT INTO #BlitzResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT + 261 AS CheckID + ,1 AS Priority + ,'Security' AS FindingsGroup + ,'Dangerous Service Account' AS Finding + ,'https://vladdba.com/SQLServerSvcAccount' AS URL + ,'SQL Server Agent''s service account is a member of the local Administrators group - meaning that anyone who can create and run jobs can do anything on the host.' as Details + + END; + END; /*piggyback*/ + ELSE /*can't piggyback*/ BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 167) WITH NOWAIT; - - INSERT INTO [#BlitzResults] - ( [CheckID] , - [Priority] , - [FindingsGroup] , - [Finding] , - [URL] , - [Details] ) + /*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 */ + + IF EXISTS (SELECT 1 + FROM #localadmins + WHERE LOWER(cmdshell_output) = ( SELECT LOWER([service_account]) + FROM [sys].[dm_server_services] + WHERE [servicename] LIKE 'SQL Server Agent%' + AND [servicename] NOT LIKE 'SQL Server Launchpad%')) + BEGIN + INSERT INTO #BlitzResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT + 261 AS CheckID + ,1 AS Priority + ,'Security' AS FindingsGroup + ,'Dangerous Service Account' AS Finding + ,'https://vladdba.com/SQLServerSvcAccount' AS URL + ,'SQL Server Agent''s service account is a member of the local Administrators group - meaning that anyone who can create and run jobs can do anything on the host.' as Details + + END; - SELECT - 167 AS [CheckID] , - 250 AS [Priority] , - 'Server Info' AS [FindingsGroup] , - 'Agent is Currently Offline' AS [Finding] , - '' AS [URL] , - ( 'Oops! It looks like the ' + [servicename] + ' service is ' + [status_desc] + '. The startup type is ' + [startup_type_desc] + '.' - ) AS [Details] - FROM - [sys].[dm_server_services] - WHERE [status_desc] <> 'Running' - AND [servicename] LIKE 'SQL Server Agent%' - AND CAST(SERVERPROPERTY('Edition') AS VARCHAR(1000)) NOT LIKE '%xpress%'; + END;/*can't piggyback*/ + END; + END; /* CheckID 261 */ - END; - END; + END; /* IF @CheckServerInfo = 1 */ + END; /* IF ( ( SERVERPROPERTY('ServerName') NOT IN ( SELECT ServerName */ -/*This checks to see if the Full Text thingy is offline*/ -IF @ProductVersionMajor >= 10 - AND NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 168 ) - BEGIN - IF EXISTS ( SELECT 1 - FROM sys.all_objects - WHERE name = 'dm_server_services' ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 168) WITH NOWAIT; - - INSERT INTO [#BlitzResults] - ( [CheckID] , - [Priority] , - [FindingsGroup] , - [Finding] , - [URL] , - [Details] ) + /* Delete priorites they wanted to skip. */ + IF @IgnorePrioritiesAbove IS NOT NULL + DELETE #BlitzResults + WHERE [Priority] > @IgnorePrioritiesAbove AND CheckID <> -1; - SELECT - 168 AS [CheckID] , - 250 AS [Priority] , - 'Server Info' AS [FindingsGroup] , - 'Full-text Filter Daemon Launcher is Currently Offline' AS [Finding] , - '' AS [URL] , - ( 'Oops! It looks like the ' + [servicename] + ' service is ' + [status_desc] + '. The startup type is ' + [startup_type_desc] + '.' - ) AS [Details] - FROM - [sys].[dm_server_services] - WHERE [status_desc] <> 'Running' - AND [servicename] LIKE 'SQL Full-text Filter Daemon Launcher%'; + IF @IgnorePrioritiesBelow IS NOT NULL + DELETE #BlitzResults + WHERE [Priority] < @IgnorePrioritiesBelow AND CheckID <> -1; - END; + /* Delete checks they wanted to skip. */ + IF @SkipChecksTable IS NOT NULL + BEGIN + DELETE FROM #BlitzResults + WHERE DatabaseName IN ( SELECT DatabaseName + FROM #SkipChecks + WHERE CheckID IS NULL + AND (ServerName IS NULL OR ServerName = SERVERPROPERTY('ServerName'))); + DELETE FROM #BlitzResults + WHERE CheckID IN ( SELECT CheckID + FROM #SkipChecks + WHERE DatabaseName IS NULL + AND (ServerName IS NULL OR ServerName = SERVERPROPERTY('ServerName'))); + DELETE r FROM #BlitzResults r + INNER JOIN #SkipChecks c ON r.DatabaseName = c.DatabaseName and r.CheckID = c.CheckID + AND (ServerName IS NULL OR ServerName = SERVERPROPERTY('ServerName')); END; -/*This checks which service account SQL Server is running as.*/ -IF @ProductVersionMajor >= 10 - AND NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 169 ) - + /* Add summary mode */ + IF @SummaryMode > 0 BEGIN - IF EXISTS ( SELECT 1 - FROM sys.all_objects - WHERE name = 'dm_server_services' ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 169) WITH NOWAIT; - - INSERT INTO [#BlitzResults] - ( [CheckID] , - [Priority] , - [FindingsGroup] , - [Finding] , - [URL] , - [Details] ) + UPDATE #BlitzResults + SET Finding = br.Finding + ' (' + CAST(brTotals.recs AS NVARCHAR(20)) + ')' + FROM #BlitzResults br + INNER JOIN (SELECT FindingsGroup, Finding, Priority, COUNT(*) AS recs FROM #BlitzResults GROUP BY FindingsGroup, Finding, Priority) brTotals ON br.FindingsGroup = brTotals.FindingsGroup AND br.Finding = brTotals.Finding AND br.Priority = brTotals.Priority + WHERE brTotals.recs > 1; - SELECT - 169 AS [CheckID] , - 250 AS [Priority] , - 'Informational' AS [FindingsGroup] , - 'SQL Server is running under an NT Service account' AS [Finding] , - 'https://www.brentozar.com/go/setup' AS [URL] , - ( 'I''m running as ' + [service_account] + '.' - ) AS [Details] - FROM - [sys].[dm_server_services] - WHERE [service_account] LIKE 'NT Service%' - AND [servicename] LIKE 'SQL Server%' - AND [servicename] NOT LIKE 'SQL Server Agent%' - AND [servicename] NOT LIKE 'SQL Server Launchpad%'; + DELETE br + FROM #BlitzResults br + WHERE EXISTS (SELECT * FROM #BlitzResults brLower WHERE br.FindingsGroup = brLower.FindingsGroup AND br.Finding = brLower.Finding AND br.Priority = brLower.Priority AND br.ID > brLower.ID); END; - END; -/*This checks which service account SQL Agent is running as.*/ -IF @ProductVersionMajor >= 10 - AND NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 170 ) + /* Add credits for the nice folks who put so much time into building and maintaining this for free: */ + + INSERT INTO #BlitzResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + VALUES ( -1 , + 255 , + 'Thanks!' , + 'From Your Community Volunteers' , + 'http://FirstResponderKit.org' , + 'We hope you found this tool useful.' + ); + + INSERT INTO #BlitzResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + + ) + VALUES ( -1 , + 0 , + 'sp_Blitz ' + CAST(CONVERT(DATETIME, @VersionDate, 102) AS VARCHAR(100)), + 'SQL Server First Responder Kit' , + 'http://FirstResponderKit.org/' , + 'To get help or add your own contributions, join us at http://FirstResponderKit.org.' + + ); + + INSERT INTO #BlitzResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT 156 , + 254 , + 'Rundate' , + GETDATE() , + 'http://FirstResponderKit.org/' , + 'Captain''s log: stardate something and something...'; + + IF @EmailRecipients IS NOT NULL BEGIN - IF EXISTS ( SELECT 1 - FROM sys.all_objects - WHERE name = 'dm_server_services' ) + + IF @Debug IN (1, 2) RAISERROR('Sending an email.', 0, 1) WITH NOWAIT; + + /* Database mail won't work off a local temp table. I'm not happy about this hacky workaround either. */ + IF (OBJECT_ID('tempdb..##BlitzResults', 'U') IS NOT NULL) DROP TABLE ##BlitzResults; + SELECT * INTO ##BlitzResults FROM #BlitzResults; + SET @query_result_separator = char(9); + SET @StringToExecute = 'SET NOCOUNT ON;SELECT [Priority] , [FindingsGroup] , [Finding] , [DatabaseName] , [URL] , [Details] , CheckID FROM ##BlitzResults ORDER BY Priority , FindingsGroup , Finding , DatabaseName , Details; SET NOCOUNT OFF;'; + SET @EmailSubject = 'sp_Blitz Results for ' + @@SERVERNAME; + SET @EmailBody = 'sp_Blitz ' + CAST(CONVERT(DATETIME, @VersionDate, 102) AS VARCHAR(100)) + '. http://FirstResponderKit.org'; + IF @EmailProfile IS NULL + EXEC msdb.dbo.sp_send_dbmail + @recipients = @EmailRecipients, + @subject = @EmailSubject, + @body = @EmailBody, + @query_attachment_filename = 'sp_Blitz-Results.csv', + @attach_query_result_as_file = 1, + @query_result_header = 1, + @query_result_width = 32767, + @append_query_error = 1, + @query_result_no_padding = 1, + @query_result_separator = @query_result_separator, + @query = @StringToExecute; + ELSE + EXEC msdb.dbo.sp_send_dbmail + @profile_name = @EmailProfile, + @recipients = @EmailRecipients, + @subject = @EmailSubject, + @body = @EmailBody, + @query_attachment_filename = 'sp_Blitz-Results.csv', + @attach_query_result_as_file = 1, + @query_result_header = 1, + @query_result_width = 32767, + @append_query_error = 1, + @query_result_no_padding = 1, + @query_result_separator = @query_result_separator, + @query = @StringToExecute; + IF (OBJECT_ID('tempdb..##BlitzResults', 'U') IS NOT NULL) DROP TABLE ##BlitzResults; + END; + + /* Checks if @OutputServerName is populated with a valid linked server, and that the database name specified is valid */ + DECLARE @ValidOutputServer BIT; + DECLARE @ValidOutputLocation BIT; + DECLARE @LinkedServerDBCheck NVARCHAR(2000); + DECLARE @ValidLinkedServerDB INT; + DECLARE @tmpdbchk table (cnt int); + IF @OutputServerName IS NOT NULL BEGIN - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 170) WITH NOWAIT; + IF @Debug IN (1, 2) RAISERROR('Outputting to a remote server.', 0, 1) WITH NOWAIT; - INSERT INTO [#BlitzResults] - ( [CheckID] , - [Priority] , - [FindingsGroup] , - [Finding] , - [URL] , - [Details] ) - - SELECT - 170 AS [CheckID] , - 250 AS [Priority] , - 'Informational' AS [FindingsGroup] , - 'SQL Server Agent is running under an NT Service account' AS [Finding] , - 'https://www.brentozar.com/go/setup' AS [URL] , - ( 'I''m running as ' + [service_account] + '.' - ) AS [Details] - FROM - [sys].[dm_server_services] - WHERE [service_account] LIKE 'NT Service%' - AND [servicename] LIKE 'SQL Server Agent%'; - + IF EXISTS (SELECT server_id FROM sys.servers WHERE QUOTENAME([name]) = @OutputServerName) + BEGIN + SET @LinkedServerDBCheck = 'SELECT 1 WHERE EXISTS (SELECT * FROM '+@OutputServerName+'.master.sys.databases WHERE QUOTENAME([name]) = '''+@OutputDatabaseName+''')'; + INSERT INTO @tmpdbchk EXEC sys.sp_executesql @LinkedServerDBCheck; + SET @ValidLinkedServerDB = (SELECT COUNT(*) FROM @tmpdbchk); + IF (@ValidLinkedServerDB > 0) + BEGIN + SET @ValidOutputServer = 1; + SET @ValidOutputLocation = 1; + END; + ELSE + RAISERROR('The specified database was not found on the output server', 16, 0); + END; + ELSE + BEGIN + RAISERROR('The specified output server was not found', 16, 0); + END; END; + ELSE + BEGIN + IF @OutputDatabaseName IS NOT NULL + AND @OutputSchemaName IS NOT NULL + AND @OutputTableName IS NOT NULL + AND EXISTS ( SELECT * + FROM sys.databases + WHERE QUOTENAME([name]) = @OutputDatabaseName) + BEGIN + SET @ValidOutputLocation = 1; + END; + ELSE IF @OutputDatabaseName IS NOT NULL + AND @OutputSchemaName IS NOT NULL + AND @OutputTableName IS NOT NULL + AND NOT EXISTS ( SELECT * + FROM sys.databases + WHERE QUOTENAME([name]) = @OutputDatabaseName) + BEGIN + RAISERROR('The specified output database was not found on this server', 16, 0); + END; + ELSE + BEGIN + SET @ValidOutputLocation = 0; + END; END; - -/*This checks that First Responder Kit is consistent. -It assumes that all the objects of the kit resides in the same database, the one in which this SP is stored -It also is ready to check for installation in another schema. -*/ -IF( - NOT EXISTS ( - SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 226 - ) -) -BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running check with id %d',0,1,2000); - SET @spBlitzFullName = QUOTENAME(DB_NAME()) + '.' +QUOTENAME(OBJECT_SCHEMA_NAME(@@PROCID)) + '.' + QUOTENAME(OBJECT_NAME(@@PROCID)); - SET @BlitzIsOutdatedComparedToOthers = 0; - SET @tsql = NULL; - SET @VersionCheckModeExistsTSQL = NULL; - SET @BlitzProcDbName = DB_NAME(); - SET @ExecRet = NULL; - SET @InnerExecRet = NULL; - SET @TmpCnt = NULL; + /* @OutputTableName lets us export the results to a permanent table */ + IF @ValidOutputLocation = 1 + BEGIN + SET @StringToExecute = 'USE ' + + @OutputDatabaseName + + '; IF EXISTS(SELECT * FROM ' + + @OutputDatabaseName + + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' + + @OutputSchemaName + + ''') AND NOT EXISTS (SELECT * FROM ' + + @OutputDatabaseName + + '.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''' + + @OutputSchemaName + ''' AND QUOTENAME(TABLE_NAME) = ''' + + @OutputTableName + ''') CREATE TABLE ' + + @OutputSchemaName + '.' + + @OutputTableName + + ' (ID INT IDENTITY(1,1) NOT NULL, + ServerName NVARCHAR(128), + CheckDate DATETIMEOFFSET, + Priority TINYINT , + FindingsGroup VARCHAR(50) , + Finding VARCHAR(200) , + DatabaseName NVARCHAR(128), + URL VARCHAR(200) , + Details NVARCHAR(4000) , + QueryPlan [XML] NULL , + QueryPlanFiltered [NVARCHAR](MAX) NULL, + CheckID INT , + CONSTRAINT [PK_' + CAST(NEWID() AS CHAR(36)) + '] PRIMARY KEY CLUSTERED (ID ASC));'; + IF @ValidOutputServer = 1 + BEGIN + SET @StringToExecute = REPLACE(@StringToExecute,''''+@OutputSchemaName+'''',''''''+@OutputSchemaName+''''''); + SET @StringToExecute = REPLACE(@StringToExecute,''''+@OutputTableName+'''',''''''+@OutputTableName+''''''); + SET @StringToExecute = REPLACE(@StringToExecute,'[XML]','[NVARCHAR](MAX)'); + EXEC('EXEC('''+@StringToExecute+''') AT ' + @OutputServerName); + END; + ELSE + BEGIN + IF @OutputXMLasNVARCHAR = 1 + BEGIN + SET @StringToExecute = REPLACE(@StringToExecute,'[XML]','[NVARCHAR](MAX)'); + END; + EXEC(@StringToExecute); + END; + IF @ValidOutputServer = 1 + BEGIN + SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' + + @OutputServerName + '.' + + @OutputDatabaseName + + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' + + @OutputSchemaName + ''') INSERT ' + + @OutputServerName + '.' + + @OutputDatabaseName + '.' + + @OutputSchemaName + '.' + + @OutputTableName + + ' (ServerName, CheckDate, CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details, QueryPlan, QueryPlanFiltered) SELECT ''' + + CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128)) + + ''', SYSDATETIMEOFFSET(), CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details, CAST(QueryPlan AS NVARCHAR(MAX)), QueryPlanFiltered FROM #BlitzResults ORDER BY Priority , FindingsGroup , Finding , DatabaseName , Details'; - SET @PreviousComponentName = NULL; - SET @PreviousComponentFullPath = NULL; - SET @CurrentStatementId = NULL; - SET @CurrentComponentSchema = NULL; - SET @CurrentComponentName = NULL; - SET @CurrentComponentType = NULL; - SET @CurrentComponentVersionDate = NULL; - SET @CurrentComponentFullName = NULL; - SET @CurrentComponentMandatory = NULL; - SET @MaximumVersionDate = NULL; + EXEC(@StringToExecute); + END; + ELSE + BEGIN + IF @OutputXMLasNVARCHAR = 1 + BEGIN + SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' + + @OutputDatabaseName + + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' + + @OutputSchemaName + ''') INSERT ' + + @OutputDatabaseName + '.' + + @OutputSchemaName + '.' + + @OutputTableName + + ' (ServerName, CheckDate, CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details, QueryPlan, QueryPlanFiltered) SELECT ''' + + CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128)) + + ''', SYSDATETIMEOFFSET(), CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details, CAST(QueryPlan AS NVARCHAR(MAX)), QueryPlanFiltered FROM #BlitzResults ORDER BY Priority , FindingsGroup , Finding , DatabaseName , Details'; + END; + ELSE + begin + SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' + + @OutputDatabaseName + + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' + + @OutputSchemaName + ''') INSERT ' + + @OutputDatabaseName + '.' + + @OutputSchemaName + '.' + + @OutputTableName + + ' (ServerName, CheckDate, CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details, QueryPlan, QueryPlanFiltered) SELECT ''' + + CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128)) + + ''', SYSDATETIMEOFFSET(), CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details, QueryPlan, QueryPlanFiltered FROM #BlitzResults ORDER BY Priority , FindingsGroup , Finding , DatabaseName , Details'; + END; + EXEC(@StringToExecute); + + END; + END; + ELSE IF (SUBSTRING(@OutputTableName, 2, 2) = '##') + BEGIN + IF @ValidOutputServer = 1 + BEGIN + RAISERROR('Due to the nature of temporary tables, outputting to a linked server requires a permanent table.', 16, 0); + END; + ELSE + BEGIN + SET @StringToExecute = N' IF (OBJECT_ID(''tempdb..' + + @OutputTableName + + ''') IS NOT NULL) DROP TABLE ' + @OutputTableName + ';' + + 'CREATE TABLE ' + + @OutputTableName + + ' (ID INT IDENTITY(1,1) NOT NULL, + ServerName NVARCHAR(128), + CheckDate DATETIMEOFFSET, + Priority TINYINT , + FindingsGroup VARCHAR(50) , + Finding VARCHAR(200) , + DatabaseName NVARCHAR(128), + URL VARCHAR(200) , + Details NVARCHAR(4000) , + QueryPlan [XML] NULL , + QueryPlanFiltered [NVARCHAR](MAX) NULL, + CheckID INT , + CONSTRAINT [PK_' + CAST(NEWID() AS CHAR(36)) + '] PRIMARY KEY CLUSTERED (ID ASC));' + + ' INSERT ' + + @OutputTableName + + ' (ServerName, CheckDate, CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details, QueryPlan, QueryPlanFiltered) SELECT ''' + + CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128)) + + ''', SYSDATETIMEOFFSET(), CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details, QueryPlan, QueryPlanFiltered FROM #BlitzResults ORDER BY Priority , FindingsGroup , Finding , DatabaseName , Details'; + + EXEC(@StringToExecute); + END; + END; + ELSE IF (SUBSTRING(@OutputTableName, 2, 1) = '#') + BEGIN + RAISERROR('Due to the nature of Dymamic SQL, only global (i.e. double pound (##)) temp tables are supported for @OutputTableName', 16, 0); + END; - SET @StatementCheckName = NULL; - SET @StatementOutputsCounter = NULL; - SET @OutputCounterExpectedValue = NULL; - SET @StatementOutputsExecRet = NULL; - SET @StatementOutputsDateTime = NULL; + DECLARE @separator AS VARCHAR(1); + IF @OutputType = 'RSV' + SET @separator = CHAR(31); + ELSE + SET @separator = ','; - SET @CurrentComponentMandatoryCheckOK = NULL; - SET @CurrentComponentVersionCheckModeOK = NULL; + IF @OutputType = 'COUNT' + BEGIN + SELECT COUNT(*) AS Warnings + FROM #BlitzResults; + END; + ELSE + IF @OutputType IN ( 'CSV', 'RSV' ) + BEGIN - SET @canExitLoop = 0; - SET @frkIsConsistent = 0; + SELECT Result = CAST([Priority] AS NVARCHAR(100)) + + @separator + CAST(CheckID AS NVARCHAR(100)) + + @separator + COALESCE([FindingsGroup], + '(N/A)') + @separator + + COALESCE([Finding], '(N/A)') + @separator + + COALESCE(DatabaseName, '(N/A)') + @separator + + COALESCE([URL], '(N/A)') + @separator + + COALESCE([Details], '(N/A)') + FROM #BlitzResults + ORDER BY Priority , + FindingsGroup , + Finding , + DatabaseName , + Details; + END; + ELSE IF @OutputXMLasNVARCHAR = 1 AND @OutputType <> 'NONE' + BEGIN + SELECT [Priority] , + [FindingsGroup] , + [Finding] , + [DatabaseName] , + [URL] , + [Details] , + CAST([QueryPlan] AS NVARCHAR(MAX)) AS QueryPlan, + [QueryPlanFiltered] , + CheckID + FROM #BlitzResults + ORDER BY Priority , + FindingsGroup , + Finding , + DatabaseName , + Details; + END; + ELSE IF @OutputType = 'MARKDOWN' + BEGIN + WITH Results AS (SELECT row_number() OVER (ORDER BY Priority, FindingsGroup, Finding, DatabaseName, Details) AS rownum, * + FROM #BlitzResults + WHERE Priority > 0 AND Priority < 255 AND FindingsGroup IS NOT NULL AND Finding IS NOT NULL + AND FindingsGroup <> 'Security' /* Specifically excluding security checks for public exports */) + SELECT + Markdown = CONVERT(XML, STUFF((SELECT + CASE + WHEN r.Priority <> COALESCE(rPrior.Priority, 0) OR r.FindingsGroup <> rPrior.FindingsGroup THEN @crlf + N'**Priority ' + CAST(COALESCE(r.Priority,N'') AS NVARCHAR(5)) + N': ' + COALESCE(r.FindingsGroup,N'') + N'**:' + @crlf + @crlf + ELSE N'' + END + + CASE WHEN r.Finding <> COALESCE(rPrior.Finding,N'') AND r.Finding <> COALESCE(rNext.Finding,N'') THEN N'- ' + COALESCE(r.Finding,N'') + N' ' + COALESCE(r.DatabaseName, N'') + N' - ' + COALESCE(r.Details,N'') + @crlf + WHEN r.Finding <> COALESCE(rPrior.Finding,N'') AND r.Finding = rNext.Finding AND r.Details = rNext.Details THEN N'- ' + COALESCE(r.Finding,N'') + N' - ' + COALESCE(r.Details,N'') + @crlf + @crlf + N' * ' + COALESCE(r.DatabaseName, N'') + @crlf + WHEN r.Finding <> COALESCE(rPrior.Finding,N'') AND r.Finding = rNext.Finding THEN N'- ' + COALESCE(r.Finding,N'') + @crlf + @crlf + CASE WHEN r.DatabaseName IS NULL THEN N'' ELSE N' * ' + COALESCE(r.DatabaseName,N'') END + CASE WHEN r.Details <> rPrior.Details THEN N' - ' + COALESCE(r.Details,N'') + @crlf ELSE '' END + ELSE CASE WHEN r.DatabaseName IS NULL THEN N'' ELSE N' * ' + COALESCE(r.DatabaseName,N'') END + CASE WHEN r.Details <> rPrior.Details THEN N' - ' + COALESCE(r.Details,N'') + @crlf ELSE N'' + @crlf END + END + @crlf + FROM Results r + LEFT OUTER JOIN Results rPrior ON r.rownum = rPrior.rownum + 1 + LEFT OUTER JOIN Results rNext ON r.rownum = rNext.rownum - 1 + ORDER BY r.rownum FOR XML PATH(N''), ROOT('Markdown'), TYPE).value('/Markdown[1]','VARCHAR(MAX)'), 1, 2, '') + + ''); + END; + ELSE IF @OutputType = 'XML' + BEGIN + /* --TOURSTOP05-- */ + SELECT [Priority] , + [FindingsGroup] , + [Finding] , + [DatabaseName] , + [URL] , + [Details] , + [QueryPlanFiltered] , + CheckID + FROM #BlitzResults + ORDER BY Priority , + FindingsGroup , + Finding , + DatabaseName , + Details + FOR XML PATH('Result'), ROOT('sp_Blitz_Output'); + END; + ELSE IF @OutputType <> 'NONE' + BEGIN + /* --TOURSTOP05-- */ + SELECT [Priority] , + [FindingsGroup] , + [Finding] , + [DatabaseName] , + [URL] , + [Details] , + [QueryPlan] , + [QueryPlanFiltered] , + CheckID + FROM #BlitzResults + ORDER BY Priority , + FindingsGroup , + Finding , + DatabaseName , + Details; + END; + DROP TABLE #BlitzResults; - SET @tsql = 'USE ' + QUOTENAME(@BlitzProcDbName) + ';' + @crlf + - 'WITH FRKComponents (' + @crlf + - ' ObjectName,' + @crlf + - ' ObjectType,' + @crlf + - ' MandatoryComponent' + @crlf + - ')' + @crlf + - 'AS (' + @crlf + - ' SELECT ''sp_AllNightLog'',''P'' ,0' + @crlf + - ' UNION ALL' + @crlf + - ' SELECT ''sp_AllNightLog_Setup'', ''P'',0' + @crlf + - ' UNION ALL ' + @crlf + - ' SELECT ''sp_Blitz'',''P'',0' + @crlf + - ' UNION ALL ' + @crlf + - ' SELECT ''sp_BlitzBackups'',''P'',0' + @crlf + - ' UNION ALL ' + @crlf + - ' SELECT ''sp_BlitzCache'',''P'',0' + @crlf + - ' UNION ALL ' + @crlf + - ' SELECT ''sp_BlitzFirst'',''P'',0' + @crlf + - ' UNION ALL' + @crlf + - ' SELECT ''sp_BlitzIndex'',''P'',0' + @crlf + - ' UNION ALL ' + @crlf + - ' SELECT ''sp_BlitzLock'',''P'',0' + @crlf + - ' UNION ALL ' + @crlf + - ' SELECT ''sp_BlitzQueryStore'',''P'',0' + @crlf + - ' UNION ALL ' + @crlf + - ' SELECT ''sp_BlitzWho'',''P'',0' + @crlf + - ' UNION ALL ' + @crlf + - ' SELECT ''sp_DatabaseRestore'',''P'',0' + @crlf + - ' UNION ALL ' + @crlf + - ' SELECT ''sp_ineachdb'',''P'',0' + @crlf + - ' UNION ALL' + @crlf + - ' SELECT ''SqlServerVersions'',''U'',0' + @crlf + - ')' + @crlf + - 'INSERT INTO #FRKObjects (' + @crlf + - ' DatabaseName,ObjectSchemaName,ObjectName, ObjectType,MandatoryComponent' + @crlf + - ')' + @crlf + - 'SELECT DB_NAME(),SCHEMA_NAME(o.schema_id), c.ObjectName,c.ObjectType,c.MandatoryComponent' + @crlf + - 'FROM ' + @crlf + - ' FRKComponents c' + @crlf + - 'LEFT JOIN ' + @crlf + - ' sys.objects o' + @crlf + - 'ON c.ObjectName = o.[name]' + @crlf + - 'AND c.ObjectType = o.[type]' + @crlf + - --'WHERE o.schema_id IS NOT NULL' + @crlf + - ';' - ; + IF @OutputProcedureCache = 1 + AND @CheckProcedureCache = 1 + SELECT TOP 20 + total_worker_time / execution_count AS AvgCPU , + total_worker_time AS TotalCPU , + CAST(ROUND(100.00 * total_worker_time + / ( SELECT SUM(total_worker_time) + FROM sys.dm_exec_query_stats + ), 2) AS MONEY) AS PercentCPU , + total_elapsed_time / execution_count AS AvgDuration , + total_elapsed_time AS TotalDuration , + CAST(ROUND(100.00 * total_elapsed_time + / ( SELECT SUM(total_elapsed_time) + FROM sys.dm_exec_query_stats + ), 2) AS MONEY) AS PercentDuration , + total_logical_reads / execution_count AS AvgReads , + total_logical_reads AS TotalReads , + CAST(ROUND(100.00 * total_logical_reads + / ( SELECT SUM(total_logical_reads) + FROM sys.dm_exec_query_stats + ), 2) AS MONEY) AS PercentReads , + execution_count , + CAST(ROUND(100.00 * execution_count + / ( SELECT SUM(execution_count) + FROM sys.dm_exec_query_stats + ), 2) AS MONEY) AS PercentExecutions , + CASE WHEN DATEDIFF(mi, creation_time, + qs.last_execution_time) = 0 THEN 0 + ELSE CAST(( 1.00 * execution_count / DATEDIFF(mi, + creation_time, + qs.last_execution_time) ) AS MONEY) + END AS executions_per_minute , + qs.creation_time AS plan_creation_time , + qs.last_execution_time , + text , + text_filtered , + query_plan , + query_plan_filtered , + sql_handle , + query_hash , + plan_handle , + query_plan_hash + FROM #dm_exec_query_stats qs + ORDER BY CASE UPPER(@CheckProcedureCacheFilter) + WHEN 'CPU' THEN total_worker_time + WHEN 'READS' THEN total_logical_reads + WHEN 'EXECCOUNT' THEN execution_count + WHEN 'DURATION' THEN total_elapsed_time + ELSE total_worker_time + END DESC; - EXEC @ExecRet = sp_executesql @tsql ; + END; /* ELSE -- IF @OutputType = 'SCHEMA' */ - -- TODO: add check for statement success + /* + Cleanups - drop temporary tables that have been created by this SP. + */ + + IF(OBJECT_ID('tempdb..#InvalidLogins') IS NOT NULL) + BEGIN + EXEC sp_executesql N'DROP TABLE #InvalidLogins;'; + END; - -- TODO: based on SP requirements and presence (SchemaName is not null) ==> update MandatoryComponent column + IF OBJECT_ID('tempdb..#AlertInfo') IS NOT NULL + BEGIN + EXEC sp_executesql N'DROP TABLE #AlertInfo;'; + END; - -- Filling #StatementsToRun4FRKVersionCheck - INSERT INTO #StatementsToRun4FRKVersionCheck ( - CheckName,StatementText,SubjectName,SubjectFullPath, StatementOutputsCounter,OutputCounterExpectedValue,StatementOutputsExecRet,StatementOutputsDateTime - ) - SELECT - 'Mandatory', - 'SELECT @cnt = COUNT(*) FROM #FRKObjects WHERE ObjectSchemaName IS NULL AND ObjectName = ''' + ObjectName + ''' AND MandatoryComponent = 1;', - ObjectName, - QUOTENAME(DatabaseName) + '.' + QUOTENAME(ObjectSchemaName) + '.' + QUOTENAME(ObjectName), - 1, - 0, - 0, - 0 - FROM #FRKObjects - UNION ALL - SELECT - 'VersionCheckMode', - 'SELECT @cnt = COUNT(*) FROM ' + - QUOTENAME(DatabaseName) + '.sys.all_parameters ' + - 'where object_id = OBJECT_ID(''' + QUOTENAME(DatabaseName) + '.' + QUOTENAME(ObjectSchemaName) + '.' + QUOTENAME(ObjectName) + ''') AND [name] = ''@VersionCheckMode'';', - ObjectName, - QUOTENAME(DatabaseName) + '.' + QUOTENAME(ObjectSchemaName) + '.' + QUOTENAME(ObjectName), - 1, - 1, - 0, - 0 - FROM #FRKObjects - WHERE ObjectType = 'P' - AND ObjectSchemaName IS NOT NULL - UNION ALL - SELECT - 'VersionCheck', - 'EXEC @ExecRet = ' + QUOTENAME(DatabaseName) + '.' + QUOTENAME(ObjectSchemaName) + '.' + QUOTENAME(ObjectName) + ' @VersionCheckMode = 1 , @VersionDate = @ObjDate OUTPUT;', - ObjectName, - QUOTENAME(DatabaseName) + '.' + QUOTENAME(ObjectSchemaName) + '.' + QUOTENAME(ObjectName), - 0, - 0, - 1, - 1 - FROM #FRKObjects - WHERE ObjectType = 'P' - AND ObjectSchemaName IS NOT NULL - ; - IF(@Debug in (1,2)) - BEGIN - SELECT * - FROM #StatementsToRun4FRKVersionCheck ORDER BY SubjectName,SubjectFullPath,StatementId -- in case of schema change ; - END; - - - -- loop on queries... - WHILE(@canExitLoop = 0) - BEGIN - SET @CurrentStatementId = NULL; + /* + Reset the Nmumeric_RoundAbort session state back to enabled if it was disabled earlier. + See Github issue #2302 for more info. + */ + IF @NeedToTurnNumericRoundabortBackOn = 1 + SET NUMERIC_ROUNDABORT ON; - SELECT TOP 1 - @StatementCheckName = CheckName, - @CurrentStatementId = StatementId , - @CurrentComponentName = SubjectName, - @CurrentComponentFullName = SubjectFullPath, - @tsql = StatementText, - @StatementOutputsCounter = StatementOutputsCounter, - @OutputCounterExpectedValue = OutputCounterExpectedValue , - @StatementOutputsExecRet = StatementOutputsExecRet, - @StatementOutputsDateTime = StatementOutputsDateTime - FROM #StatementsToRun4FRKVersionCheck - ORDER BY SubjectName, SubjectFullPath,StatementId /* in case of schema change */ - ; + SET NOCOUNT OFF; +GO - -- loop exit condition - IF(@CurrentStatementId IS NULL) - BEGIN - BREAK; - END; +/* +--Sample execution call with the most common parameters: +EXEC [dbo].[sp_Blitz] + @CheckUserDatabaseObjects = 1 , + @CheckProcedureCache = 0 , + @OutputType = 'TABLE' , + @OutputProcedureCache = 0 , + @CheckProcedureCacheFilter = NULL, + @CheckServerInfo = 1 +*/ +SET ANSI_NULLS ON; +SET QUOTED_IDENTIFIER ON - IF @Debug IN (1, 2) RAISERROR(' Statement: %s',0,1,@tsql); +IF NOT EXISTS (SELECT * FROM sys.objects WHERE [object_id] = OBJECT_ID(N'[dbo].[sp_BlitzAnalysis]') AND [type] in (N'P', N'PC')) +BEGIN +EXEC dbo.sp_executesql @statement = N'CREATE PROCEDURE [dbo].[sp_BlitzAnalysis] AS' +END +GO - -- we start a new component - IF(@PreviousComponentName IS NULL OR - (@PreviousComponentName IS NOT NULL AND @PreviousComponentName <> @CurrentComponentName) OR - (@PreviousComponentName IS NOT NULL AND @PreviousComponentName = @CurrentComponentName AND @PreviousComponentFullPath <> @CurrentComponentFullName) - ) - BEGIN - -- reset variables - SET @CurrentComponentMandatoryCheckOK = 0; - SET @CurrentComponentVersionCheckModeOK = 0; - SET @PreviousComponentName = @CurrentComponentName; - SET @PreviousComponentFullPath = @CurrentComponentFullName ; - END; +ALTER PROCEDURE [dbo].[sp_BlitzAnalysis] ( +@Help TINYINT = 0, +@StartDate DATETIMEOFFSET(7) = NULL, +@EndDate DATETIMEOFFSET(7) = NULL, +@OutputDatabaseName NVARCHAR(256) = 'DBAtools', +@OutputSchemaName NVARCHAR(256) = N'dbo', +@OutputTableNameBlitzFirst NVARCHAR(256) = N'BlitzFirst', +@OutputTableNameFileStats NVARCHAR(256) = N'BlitzFirst_FileStats', +@OutputTableNamePerfmonStats NVARCHAR(256) = N'BlitzFirst_PerfmonStats', +@OutputTableNameWaitStats NVARCHAR(256) = N'BlitzFirst_WaitStats', +@OutputTableNameBlitzCache NVARCHAR(256) = N'BlitzCache', +@OutputTableNameBlitzWho NVARCHAR(256) = N'BlitzWho', +@Servername NVARCHAR(128) = @@SERVERNAME, +@Databasename NVARCHAR(128) = NULL, +@BlitzCacheSortorder NVARCHAR(20) = N'cpu', +@MaxBlitzFirstPriority INT = 249, +@ReadLatencyThreshold INT = 100, +@WriteLatencyThreshold INT = 100, +@WaitStatsTop TINYINT = 10, +@Version VARCHAR(30) = NULL OUTPUT, +@VersionDate DATETIME = NULL OUTPUT, +@VersionCheckMode BIT = 0, +@BringThePain BIT = 0, +@Maxdop INT = 1, +@Debug BIT = 0 +) +AS +SET NOCOUNT ON; +SET STATISTICS XML OFF; - IF(@StatementCheckName NOT IN ('Mandatory','VersionCheckMode','VersionCheck')) - BEGIN - INSERT INTO #BlitzResults( - CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT - 226 AS CheckID , - 253 AS Priority , - 'First Responder Kit' AS FindingsGroup , - 'Version Check Failed (code generator changed)' AS Finding , - 'http://FirstResponderKit.org' AS URL , - 'Download an updated First Responder Kit. Your version check failed because a change has been made to the version check code generator.' + @crlf + - 'Error: No handler for check with name "' + ISNULL(@StatementCheckName,'') + '"' AS Details - ; - - -- we will stop the test because it's possible to get the same message for other components - SET @canExitLoop = 1; - CONTINUE; - END; +SELECT @Version = '8.19', @VersionDate = '20240222'; - IF(@StatementCheckName = 'Mandatory') - BEGIN - -- outputs counter - EXEC @ExecRet = sp_executesql @tsql, N'@cnt INT OUTPUT',@cnt = @TmpCnt OUTPUT; +IF(@VersionCheckMode = 1) +BEGIN + RETURN; +END; - IF(@ExecRet <> 0) - BEGIN - - INSERT INTO #BlitzResults( - CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT - 226 AS CheckID , - 253 AS Priority , - 'First Responder Kit' AS FindingsGroup , - 'Version Check Failed (dynamic query failure)' AS Finding , - 'http://FirstResponderKit.org' AS URL , - 'Download an updated First Responder Kit. Your version check failed due to dynamic query failure.' + @crlf + - 'Error: following query failed at execution (check if component [' + ISNULL(@CurrentComponentName,@CurrentComponentName) + '] is mandatory and missing)' + @crlf + - @tsql AS Details - ; - - -- we will stop the test because it's possible to get the same message for other components - SET @canExitLoop = 1; - CONTINUE; - END; +IF (@Help = 1) +BEGIN + PRINT 'EXEC sp_BlitzAnalysis +@StartDate = NULL, /* Specify a datetime or NULL will get an hour ago */ +@EndDate = NULL, /* Specify a datetime or NULL will get an hour of data since @StartDate */ +@OutputDatabaseName = N''DBA'', /* Specify the database name where where we can find your logged blitz data */ +@OutputSchemaName = N''dbo'', /* Specify the schema */ +@OutputTableNameBlitzFirst = N''BlitzFirst'', /* Table name where you are storing sp_BlitzFirst output, Set to NULL to ignore */ +@OutputTableNameFileStats = N''BlitzFirst_FileStats'', /* Table name where you are storing sp_BlitzFirst filestats output, Set to NULL to ignore */ +@OutputTableNamePerfmonStats = N''BlitzFirst_PerfmonStats'', /* Table name where you are storing sp_BlitzFirst Perfmon output, Set to NULL to ignore */ +@OutputTableNameWaitStats = N''BlitzFirst_WaitStats'', /* Table name where you are storing sp_BlitzFirst Wait stats output, Set to NULL to ignore */ +@OutputTableNameBlitzCache = N''BlitzCache'', /* Table name where you are storing sp_BlitzCache output, Set to NULL to ignore */ +@OutputTableNameBlitzWho = N''BlitzWho'', /* Table name where you are storing sp_BlitzWho output, Set to NULL to ignore */ +@Databasename = NULL, /* Filters results for BlitzCache, FileStats (will also include tempdb), BlitzWho. Leave as NULL for all databases */ +@MaxBlitzFirstPriority = 249, /* Max priority to include in the results */ +@BlitzCacheSortorder = ''cpu'', /* Accepted values ''all'' ''cpu'' ''reads'' ''writes'' ''duration'' ''executions'' ''memory grant'' ''spills'' */ +@WaitStatsTop = 3, /* Controls the top for wait stats only */ +@Maxdop = 1, /* Control the degree of parallelism that the queries within this proc can use if they want to*/ +@Debug = 0; /* Show sp_BlitzAnalysis SQL commands in the messages tab as they execute */ - IF(@TmpCnt <> @OutputCounterExpectedValue) - BEGIN - INSERT INTO #BlitzResults( - CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT - 227 AS CheckID , - 253 AS Priority , - 'First Responder Kit' AS FindingsGroup , - 'Component Missing: ' + @CurrentComponentName AS Finding , - 'http://FirstResponderKit.org' AS URL , - 'Download an updated version of the First Responder Kit to install it.' AS Details - ; - - -- as it's missing, no value for SubjectFullPath - DELETE FROM #StatementsToRun4FRKVersionCheck WHERE SubjectName = @CurrentComponentName ; - CONTINUE; - END; +/* +Additional parameters: +@ReadLatencyThreshold INT /* Default: 100 - Sets the threshold in ms to compare against io_stall_read_average_ms in your filestats table */ +@WriteLatencyThreshold INT /* Default: 100 - Sets the threshold in ms to compare against io_stall_write_average_ms in your filestats table */ +@BringThePain BIT /* Default: 0 - If you are getting more than 4 hours of data with blitzcachesortorder set to ''all'' you will need to set BringThePain to 1 */ +*/'; + RETURN; +END - SET @CurrentComponentMandatoryCheckOK = 1; - END; - - IF(@StatementCheckName = 'VersionCheckMode') - BEGIN - IF(@CurrentComponentMandatoryCheckOK = 0) - BEGIN - INSERT INTO #BlitzResults( - CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT - 226 AS CheckID , - 253 AS Priority , - 'First Responder Kit' AS FindingsGroup , - 'Version Check Failed (unexpectedly modified checks ordering)' AS Finding , - 'http://FirstResponderKit.org' AS URL , - 'Download an updated First Responder Kit. Version check failed because "Mandatory" check has not been completed before for current component' + @crlf + - 'Error: version check mode happenned before "Mandatory" check for component called "' + @CurrentComponentFullName + '"' - ; - - -- we will stop the test because it's possible to get the same message for other components - SET @canExitLoop = 1; - CONTINUE; - END; +/* Declare all local variables required */ +DECLARE @FullOutputTableNameBlitzFirst NVARCHAR(1000); +DECLARE @FullOutputTableNameFileStats NVARCHAR(1000); +DECLARE @FullOutputTableNamePerfmonStats NVARCHAR(1000); +DECLARE @FullOutputTableNameWaitStats NVARCHAR(1000); +DECLARE @FullOutputTableNameBlitzCache NVARCHAR(1000); +DECLARE @FullOutputTableNameBlitzWho NVARCHAR(1000); +DECLARE @Sql NVARCHAR(MAX); +DECLARE @NewLine NVARCHAR(2) = CHAR(13); +DECLARE @IncludeMemoryGrants BIT; +DECLARE @IncludeSpills BIT; - -- outputs counter - EXEC @ExecRet = sp_executesql @tsql, N'@cnt INT OUTPUT',@cnt = @TmpCnt OUTPUT; +/* Validate the database name */ +IF (DB_ID(@OutputDatabaseName) IS NULL) +BEGIN + RAISERROR('Invalid database name provided for parameter @OutputDatabaseName: %s',11,0,@OutputDatabaseName); + RETURN; +END - IF(@ExecRet <> 0) - BEGIN - INSERT INTO #BlitzResults( - CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT - 226 AS CheckID , - 253 AS Priority , - 'First Responder Kit' AS FindingsGroup , - 'Version Check Failed (dynamic query failure)' AS Finding , - 'http://FirstResponderKit.org' AS URL , - 'Download an updated First Responder Kit. Version check failed because a change has been made to the code generator.' + @crlf + - 'Error: following query failed at execution (check if component [' + @CurrentComponentFullName + '] can run in VersionCheckMode)' + @crlf + - @tsql AS Details - ; - - -- we will stop the test because it's possible to get the same message for other components - SET @canExitLoop = 1; - CONTINUE; - END; +/* Set fully qualified table names */ +SET @FullOutputTableNameBlitzFirst = QUOTENAME(@OutputDatabaseName)+N'.'+QUOTENAME(@OutputSchemaName)+N'.'+QUOTENAME(@OutputTableNameBlitzFirst); +SET @FullOutputTableNameFileStats = QUOTENAME(@OutputDatabaseName)+N'.'+QUOTENAME(@OutputSchemaName)+N'.'+QUOTENAME(@OutputTableNameFileStats+N'_Deltas'); +SET @FullOutputTableNamePerfmonStats = QUOTENAME(@OutputDatabaseName)+N'.'+QUOTENAME(@OutputSchemaName)+N'.'+QUOTENAME(@OutputTableNamePerfmonStats+N'_Actuals'); +SET @FullOutputTableNameWaitStats = QUOTENAME(@OutputDatabaseName)+N'.'+QUOTENAME(@OutputSchemaName)+N'.'+QUOTENAME(@OutputTableNameWaitStats+N'_Deltas'); +SET @FullOutputTableNameBlitzCache = QUOTENAME(@OutputDatabaseName)+N'.'+QUOTENAME(@OutputSchemaName)+N'.'+QUOTENAME(@OutputTableNameBlitzCache); +SET @FullOutputTableNameBlitzWho = QUOTENAME(@OutputDatabaseName)+N'.'+QUOTENAME(@OutputSchemaName)+N'.'+QUOTENAME(@OutputTableNameBlitzWho+N'_Deltas'); - IF(@TmpCnt <> @OutputCounterExpectedValue) - BEGIN - INSERT INTO #BlitzResults( - CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT - 228 AS CheckID , - 253 AS Priority , - 'First Responder Kit' AS FindingsGroup , - 'Component Outdated: ' + @CurrentComponentFullName AS Finding , - 'http://FirstResponderKit.org' AS URL , - 'Download an updated First Responder Kit. Component ' + @CurrentComponentFullName + ' is not at the minimum version required to run this procedure' + @crlf + - 'VersionCheckMode has been introduced in component version date after "20190320". This means its version is lower than or equal to that date.' AS Details; - ; - - DELETE FROM #StatementsToRun4FRKVersionCheck WHERE SubjectFullPath = @CurrentComponentFullName ; - CONTINUE; - END; +IF OBJECT_ID('tempdb.dbo.#BlitzFirstCounts') IS NOT NULL +BEGIN + DROP TABLE #BlitzFirstCounts; +END - SET @CurrentComponentVersionCheckModeOK = 1; - END; +CREATE TABLE #BlitzFirstCounts ( + [Priority] TINYINT NOT NULL, + [FindingsGroup] VARCHAR(50) NOT NULL, + [Finding] VARCHAR(200) NOT NULL, + [TotalOccurrences] INT NULL, + [FirstOccurrence] DATETIMEOFFSET(7) NULL, + [LastOccurrence] DATETIMEOFFSET(7) NULL +); - IF(@StatementCheckName = 'VersionCheck') - BEGIN - IF(@CurrentComponentMandatoryCheckOK = 0 OR @CurrentComponentVersionCheckModeOK = 0) - BEGIN - INSERT INTO #BlitzResults( - CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT - 226 AS CheckID , - 253 AS Priority , - 'First Responder Kit' AS FindingsGroup , - 'Version Check Failed (unexpectedly modified checks ordering)' AS Finding , - 'http://FirstResponderKit.org' AS URL , - 'Download an updated First Responder Kit. Version check failed because "VersionCheckMode" check has not been completed before for component called "' + @CurrentComponentFullName + '"' + @crlf + - 'Error: VersionCheck happenned before "VersionCheckMode" check for component called "' + @CurrentComponentFullName + '"' - ; - - -- we will stop the test because it's possible to get the same message for other components - SET @canExitLoop = 1; - CONTINUE; - END; +/* Validate variables and set defaults as required */ +IF (@BlitzCacheSortorder IS NULL) +BEGIN + SET @BlitzCacheSortorder = N'cpu'; +END - EXEC @ExecRet = sp_executesql @tsql , N'@ExecRet INT OUTPUT, @ObjDate DATETIME OUTPUT', @ExecRet = @InnerExecRet OUTPUT, @ObjDate = @CurrentComponentVersionDate OUTPUT; +SET @BlitzCacheSortorder = LOWER(@BlitzCacheSortorder); - IF(@ExecRet <> 0) - BEGIN - INSERT INTO #BlitzResults( - CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT - 226 AS CheckID , - 253 AS Priority , - 'First Responder Kit' AS FindingsGroup , - 'Version Check Failed (dynamic query failure)' AS Finding , - 'http://FirstResponderKit.org' AS URL , - 'Download an updated First Responder Kit. The version check failed because a change has been made to the code generator.' + @crlf + - 'Error: following query failed at execution (check if component [' + @CurrentComponentFullName + '] is at the expected version)' + @crlf + - @tsql AS Details - ; - - -- we will stop the test because it's possible to get the same message for other components - SET @canExitLoop = 1; - CONTINUE; - END; - - - IF(@InnerExecRet <> 0) - BEGIN - INSERT INTO #BlitzResults( - CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT - 226 AS CheckID , - 253 AS Priority , - 'First Responder Kit' AS FindingsGroup , - 'Version Check Failed (Failed dynamic SP call to ' + @CurrentComponentFullName + ')' AS Finding , - 'http://FirstResponderKit.org' AS URL , - 'Download an updated First Responder Kit. Error: following query failed at execution (check if component [' + @CurrentComponentFullName + '] is at the expected version)' + @crlf + - 'Return code: ' + CONVERT(VARCHAR(10),@InnerExecRet) + @crlf + - 'T-SQL Query: ' + @crlf + - @tsql AS Details - ; - - -- advance to next component - DELETE FROM #StatementsToRun4FRKVersionCheck WHERE SubjectFullPath = @CurrentComponentFullName ; - CONTINUE; - END; +IF (@OutputTableNameBlitzCache IS NOT NULL AND @BlitzCacheSortorder NOT IN (N'all',N'cpu',N'reads',N'writes',N'duration',N'executions',N'memory grant',N'spills')) +BEGIN + RAISERROR('Invalid sort option specified for @BlitzCacheSortorder, supported values are ''all'', ''cpu'', ''reads'', ''writes'', ''duration'', ''executions'', ''memory grant'', ''spills''',11,0) WITH NOWAIT; + RETURN; +END - IF(@CurrentComponentVersionDate < @VersionDate) - BEGIN - - INSERT INTO #BlitzResults( - CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT - 228 AS CheckID , - 253 AS Priority , - 'First Responder Kit' AS FindingsGroup , - 'Component Outdated: ' + @CurrentComponentFullName AS Finding , - 'http://FirstResponderKit.org' AS URL , - 'Download and install the latest First Responder Kit - you''re running some older code, and it doesn''t get better with age.' AS Details - ; - - RAISERROR('Component %s is outdated',10,1,@CurrentComponentFullName); - -- advance to next component - DELETE FROM #StatementsToRun4FRKVersionCheck WHERE SubjectFullPath = @CurrentComponentFullName ; - CONTINUE; - END; +/* Set @Maxdop to 1 if NULL was passed in */ +IF (@Maxdop IS NULL) +BEGIN + SET @Maxdop = 1; +END - ELSE IF(@CurrentComponentVersionDate > @VersionDate AND @BlitzIsOutdatedComparedToOthers = 0) - BEGIN - SET @BlitzIsOutdatedComparedToOthers = 1; - RAISERROR('Procedure %s is outdated',10,1,@spBlitzFullName); - IF(@MaximumVersionDate IS NULL OR @MaximumVersionDate < @CurrentComponentVersionDate) - BEGIN - SET @MaximumVersionDate = @CurrentComponentVersionDate; - END; - END; - /* Kept for debug purpose: - ELSE - BEGIN - INSERT INTO #BlitzResults( - CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT - 2000 AS CheckID , - 250 AS Priority , - 'Informational' AS FindingsGroup , - 'First Responder kit component ' + @CurrentComponentFullName + ' is at the expected version' AS Finding , - 'https://www.BrentOzar.com/blitz/' AS URL , - 'Version date is: ' + CONVERT(VARCHAR(32),@CurrentComponentVersionDate,121) AS Details - ; - END; - */ - END; +/* iF @Maxdop is set higher than the core count just set it to 0 */ +IF (@Maxdop > (SELECT CAST(cpu_count AS INT) FROM sys.dm_os_sys_info)) +BEGIN + SET @Maxdop = 0; +END - -- could be performed differently to minimize computation - DELETE FROM #StatementsToRun4FRKVersionCheck WHERE StatementId = @CurrentStatementId ; - END; -END; +/* We need to check if your SQL version has memory grant and spills columns in sys.dm_exec_query_stats */ +SELECT @IncludeMemoryGrants = + CASE + WHEN (EXISTS(SELECT * FROM sys.all_columns WHERE [object_id] = OBJECT_ID('sys.dm_exec_query_stats') AND name = 'max_grant_kb')) THEN 1 + ELSE 0 + END; +SELECT @IncludeSpills = + CASE + WHEN (EXISTS(SELECT * FROM sys.all_columns WHERE [object_id] = OBJECT_ID('sys.dm_exec_query_stats') AND name = 'max_spills')) THEN 1 + ELSE 0 + END; -/*This counts memory dumps and gives min and max date of in view*/ -IF @ProductVersionMajor >= 10 - AND NOT (@ProductVersionMajor = 10.5 AND @ProductVersionMinor < 4297) /* Skip due to crash bug: https://support.microsoft.com/en-us/help/2908087 */ - AND NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 171 ) - BEGIN - IF EXISTS ( SELECT 1 - FROM sys.all_objects - WHERE name = 'dm_server_memory_dumps' ) - BEGIN - IF EXISTS (SELECT * FROM [sys].[dm_server_memory_dumps] WHERE [creation_time] >= DATEADD(YEAR, -1, GETDATE())) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 171) WITH NOWAIT; - - INSERT INTO [#BlitzResults] - ( [CheckID] , - [Priority] , - [FindingsGroup] , - [Finding] , - [URL] , - [Details] ) +IF (@StartDate IS NULL) +BEGIN + RAISERROR('Setting @StartDate to: 1 hour ago',0,0) WITH NOWAIT; + /* Set StartDate to be an hour ago */ + SET @StartDate = DATEADD(HOUR,-1,SYSDATETIMEOFFSET()); - SELECT - 171 AS [CheckID] , - 20 AS [Priority] , - 'Reliability' AS [FindingsGroup] , - 'Memory Dumps Have Occurred' AS [Finding] , - 'https://www.brentozar.com/go/dump' AS [URL] , - ( 'That ain''t good. I''ve had ' + - CAST(COUNT(*) AS VARCHAR(100)) + ' memory dumps between ' + - CAST(CAST(MIN([creation_time]) AS DATETIME) AS VARCHAR(100)) + - ' and ' + - CAST(CAST(MAX([creation_time]) AS DATETIME) AS VARCHAR(100)) + - '!' - ) AS [Details] - FROM - [sys].[dm_server_memory_dumps] - WHERE [creation_time] >= DATEADD(year, -1, GETDATE()); + IF (@EndDate IS NULL) + BEGIN + RAISERROR('Setting @EndDate to: Now',0,0) WITH NOWAIT; + /* Get data right up to now */ + SET @EndDate = SYSDATETIMEOFFSET(); + END +END - END; - END; - END; +IF (@EndDate IS NULL) +BEGIN + /* Default to an hour of data or SYSDATETIMEOFFSET() if now is earlier than the hour added to @StartDate */ + IF(DATEADD(HOUR,1,@StartDate) < SYSDATETIMEOFFSET()) + BEGIN + RAISERROR('@EndDate was NULL - Setting to return 1 hour of information, if you want more then set @EndDate aswell',0,0) WITH NOWAIT; + SET @EndDate = DATEADD(HOUR,1,@StartDate); + END + ELSE + BEGIN + RAISERROR('@EndDate was NULL - Setting to SYSDATETIMEOFFSET()',0,0) WITH NOWAIT; + SET @EndDate = SYSDATETIMEOFFSET(); + END +END -/*Checks to see if you're on Developer or Evaluation*/ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 173 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 173) WITH NOWAIT; - - INSERT INTO [#BlitzResults] - ( [CheckID] , - [Priority] , - [FindingsGroup] , - [Finding] , - [URL] , - [Details] ) +/* Default to dbo schema if NULL is passed in */ +IF (@OutputSchemaName IS NULL) +BEGIN + SET @OutputSchemaName = 'dbo'; +END - SELECT - 173 AS [CheckID] , - 200 AS [Priority] , - 'Licensing' AS [FindingsGroup] , - 'Non-Production License' AS [Finding] , - 'https://www.brentozar.com/go/licensing' AS [URL] , - ( 'We''re not the licensing police, but if this is supposed to be a production server, and you''re running ' + - CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) + - ' the good folks at Microsoft might get upset with you. Better start counting those cores.' - ) AS [Details] - WHERE CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) LIKE '%Developer%' - OR CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) LIKE '%Evaluation%'; +/* Prompt the user for @BringThePain = 1 if they are searching a timeframe greater than 4 hours and they are using BlitzCacheSortorder = 'all' */ +IF(@BlitzCacheSortorder = 'all' AND DATEDIFF(HOUR,@StartDate,@EndDate) > 4 AND @BringThePain = 0) +BEGIN + RAISERROR('Wow! hold up now, are you sure you wanna do this? Are sure you want to query over 4 hours of data with @BlitzCacheSortorder set to ''all''? IF you do then set @BringThePain = 1 but I gotta warn you this might hurt a bit!',11,1) WITH NOWAIT; + RETURN; +END - END; +/* Output report window information */ +SELECT + @Servername AS [ServerToReportOn], + CAST(1 AS NVARCHAR(20)) + N' - '+ CAST(@MaxBlitzFirstPriority AS NVARCHAR(20)) AS [PrioritesToInclude], + @StartDate AS [StartDatetime], + @EndDate AS [EndDatetime];; -/*Checks to see if Buffer Pool Extensions are in use*/ - IF @ProductVersionMajor >= 12 - AND NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 174 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 174) WITH NOWAIT; - - INSERT INTO [#BlitzResults] - ( [CheckID] , - [Priority] , - [FindingsGroup] , - [Finding] , - [URL] , - [Details] ) - SELECT - 174 AS [CheckID] , - 200 AS [Priority] , - 'Performance' AS [FindingsGroup] , - 'Buffer Pool Extensions Enabled' AS [Finding] , - 'https://www.brentozar.com/go/bpe' AS [URL] , - ( 'You have Buffer Pool Extensions enabled, and one lives here: ' + - [path] + - '. It''s currently ' + - CASE WHEN [current_size_in_kb] / 1024. / 1024. > 0 - THEN CAST([current_size_in_kb] / 1024. / 1024. AS VARCHAR(100)) - + ' GB' - ELSE CAST([current_size_in_kb] / 1024. AS VARCHAR(100)) - + ' MB' - END + - '. Did you know that BPEs only provide single threaded access 8KB (one page) at a time?' - ) AS [Details] - FROM sys.dm_os_buffer_pool_extension_configuration - WHERE [state_description] <> 'BUFFER POOL EXTENSION DISABLED'; +/* BlitzFirst data */ +SET @Sql = N' +INSERT INTO #BlitzFirstCounts ([Priority],[FindingsGroup],[Finding],[TotalOccurrences],[FirstOccurrence],[LastOccurrence]) +SELECT +[Priority], +[FindingsGroup], +[Finding], +COUNT(*) AS [TotalOccurrences], +MIN(CheckDate) AS [FirstOccurrence], +MAX(CheckDate) AS [LastOccurrence] +FROM '+@FullOutputTableNameBlitzFirst+N' +WHERE [ServerName] = @Servername +AND [Priority] BETWEEN 1 AND @MaxBlitzFirstPriority +AND CheckDate BETWEEN @StartDate AND @EndDate +AND [CheckID] > -1 +GROUP BY [Priority],[FindingsGroup],[Finding]; - END; +IF EXISTS(SELECT 1 FROM #BlitzFirstCounts) +BEGIN + SELECT + [Priority], + [FindingsGroup], + [Finding], + [TotalOccurrences], + [FirstOccurrence], + [LastOccurrence] + FROM #BlitzFirstCounts + ORDER BY [Priority] ASC,[TotalOccurrences] DESC; +END +ELSE +BEGIN + SELECT N''No findings with a priority between 1 and ''+CAST(@MaxBlitzFirstPriority AS NVARCHAR(10))+N'' found for this period''; +END +SELECT + [ServerName] +,[CheckDate] +,[CheckID] +,[Priority] +,[Finding] +,[URL] +,[Details] +,[HowToStopIt] +,[QueryPlan] +,[QueryText] +FROM '+@FullOutputTableNameBlitzFirst+N' Findings +WHERE [ServerName] = @Servername +AND [Priority] BETWEEN 1 AND @MaxBlitzFirstPriority +AND [CheckDate] BETWEEN @StartDate AND @EndDate +AND [CheckID] > -1 +ORDER BY CheckDate ASC,[Priority] ASC +OPTION (RECOMPILE, MAXDOP '+CAST(@Maxdop AS NVARCHAR(2))+N');'; -/*Check for too many tempdb files*/ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 175 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 175) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT DISTINCT - 175 AS CheckID , - 'TempDB' AS DatabaseName , - 170 AS Priority , - 'File Configuration' AS FindingsGroup , - 'TempDB Has >16 Data Files' AS Finding , - 'https://www.brentozar.com/go/tempdb' AS URL , - 'Woah, Nelly! TempDB has ' + CAST(COUNT_BIG(*) AS VARCHAR(30)) + '. Did you forget to terminate a loop somewhere?' AS Details - FROM sys.[master_files] AS [mf] - WHERE [mf].[database_id] = 2 AND [mf].[type] = 0 - HAVING COUNT_BIG(*) > 16; - END; - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 176 ) - BEGIN - - IF EXISTS ( SELECT 1 - FROM sys.all_objects - WHERE name = 'dm_xe_sessions' ) - - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 176) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT DISTINCT - 176 AS CheckID , - '' AS DatabaseName , - 200 AS Priority , - 'Monitoring' AS FindingsGroup , - 'Extended Events Hyperextension' AS Finding , - 'https://www.brentozar.com/go/xe' AS URL , - 'Hey big spender, you have ' + CAST(COUNT_BIG(*) AS VARCHAR(30)) + ' Extended Events sessions running. You sure you meant to do that?' AS Details - FROM sys.dm_xe_sessions - WHERE [name] NOT IN - ( 'AlwaysOn_health', - 'system_health', - 'telemetry_xevents', - 'sp_server_diagnostics', - 'sp_server_diagnostics session', - 'hkenginexesession' ) - AND name NOT LIKE '%$A%' - HAVING COUNT_BIG(*) >= 2; - END; - END; - - /*Harmful startup parameter*/ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 177 ) - BEGIN - - IF EXISTS ( SELECT 1 - FROM sys.all_objects - WHERE name = 'dm_server_registry' ) - - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 177) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT DISTINCT - 177 AS CheckID , - '' AS DatabaseName , - 5 AS Priority , - 'Monitoring' AS FindingsGroup , - 'Disabled Internal Monitoring Features' AS Finding , - 'https://msdn.microsoft.com/en-us/library/ms190737.aspx' AS URL , - 'You have -x as a startup parameter. You should head to the URL and read more about what it does to your system.' AS Details - FROM - [sys].[dm_server_registry] AS [dsr] - WHERE - [dsr].[registry_key] LIKE N'%MSSQLServer\Parameters' - AND [dsr].[value_data] = '-x';; - END; - END; - - - /* Reliability - Dangerous Third Party Modules - 179 */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 179 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 179) WITH NOWAIT; - - INSERT INTO [#BlitzResults] - ( [CheckID] , - [Priority] , - [FindingsGroup] , - [Finding] , - [URL] , - [Details] ) +RAISERROR('Getting BlitzFirst info from %s',0,0,@FullOutputTableNameBlitzFirst) WITH NOWAIT; - SELECT - 179 AS [CheckID] , - 5 AS [Priority] , - 'Reliability' AS [FindingsGroup] , - 'Dangerous Third Party Modules' AS [Finding] , - 'https://support.microsoft.com/en-us/kb/2033238' AS [URL] , - ( COALESCE(company, '') + ' - ' + COALESCE(description, '') + ' - ' + COALESCE(name, '') + ' - suspected dangerous third party module is installed.') AS [Details] - FROM sys.dm_os_loaded_modules - WHERE UPPER(name) LIKE UPPER('%\ENTAPI.DLL') OR UPPER(name) LIKE '%MFEBOPK.SYS' /* McAfee VirusScan Enterprise */ - OR UPPER(name) LIKE UPPER('%\HIPI.DLL') OR UPPER(name) LIKE UPPER('%\HcSQL.dll') OR UPPER(name) LIKE UPPER('%\HcApi.dll') OR UPPER(name) LIKE UPPER('%\HcThe.dll') /* McAfee Host Intrusion */ - OR UPPER(name) LIKE UPPER('%\SOPHOS_DETOURED.DLL') OR UPPER(name) LIKE UPPER('%\SOPHOS_DETOURED_x64.DLL') OR UPPER(name) LIKE UPPER('%\SWI_IFSLSP_64.dll') OR UPPER(name) LIKE UPPER('%\SOPHOS~%.dll') /* Sophos AV */ - OR UPPER(name) LIKE UPPER('%\PIOLEDB.DLL') OR UPPER(name) LIKE UPPER('%\PISDK.DLL') /* OSISoft PI data access */ - OR UPPER(name) LIKE UPPER('%ScriptControl%.dll') OR UPPER(name) LIKE UPPER('%umppc%.dll') /* CrowdStrike */ - OR UPPER(name) LIKE UPPER('%perfiCrcPerfMonMgr.DLL') /* Trend Micro OfficeScan */ - OR UPPER(name) LIKE UPPER('%NLEMSQL.SYS') /* NetLib Encryptionizer-Software. */ - OR UPPER(name) LIKE UPPER('%MFETDIK.SYS') /* McAfee Anti-Virus Mini-Firewall */ - OR UPPER(name) LIKE UPPER('%ANTIVIRUS%'); /* To pick up sqlmaggieAntiVirus_64.dll (malware) or anything else labelled AntiVirus */ - /* MS docs link for blacklisted modules: https://learn.microsoft.com/en-us/troubleshoot/sql/performance/performance-consistency-issues-filter-drivers-modules */ - END; +IF (@Debug = 1) +BEGIN + PRINT @Sql; +END - /*Find shrink database tasks*/ +IF (OBJECT_ID(@FullOutputTableNameBlitzFirst) IS NULL) +BEGIN + IF (@OutputTableNameBlitzFirst IS NULL) + BEGIN + RAISERROR('BlitzFirst data skipped',10,0); + SELECT N'Skipped logged BlitzFirst data as NULL was passed to parameter @OutputTableNameBlitzFirst'; + END + ELSE + BEGIN + RAISERROR('Table provided for BlitzFirst data: %s does not exist',10,0,@FullOutputTableNameBlitzFirst); + SELECT N'No BlitzFirst data available as the table cannot be found'; + END - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 180 ) - AND CONVERT(VARCHAR(128), SERVERPROPERTY ('productversion')) LIKE '1%' /* Only run on 2008+ */ - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 180) WITH NOWAIT; - - WITH XMLNAMESPACES ('www.microsoft.com/SqlServer/Dts' AS [dts]) - ,[maintenance_plan_steps] AS ( - SELECT [name] - , [id] -- ID required to link maintenace plan with jobs and jobhistory (sp_Blitz Issue #776) - , CAST(CAST([packagedata] AS VARBINARY(MAX)) AS XML) AS [maintenance_plan_xml] - FROM [msdb].[dbo].[sysssispackages] - WHERE [packagetype] = 6 - ) - INSERT INTO [#BlitzResults] - ( [CheckID] , - [Priority] , - [FindingsGroup] , - [Finding] , - [URL] , - [Details] ) - SELECT - 180 AS [CheckID] , - -- sp_Blitz Issue #776 - -- Job has history and was executed in the last 30 days - CASE WHEN (cast(datediff(dd, substring(cast(sjh.run_date as nvarchar(10)), 1, 4) + '-' + substring(cast(sjh.run_date as nvarchar(10)), 5, 2) + '-' + substring(cast(sjh.run_date as nvarchar(10)), 7, 2), GETDATE()) AS INT) < 30) OR (j.[enabled] = 1 AND ssc.[enabled] = 1 )THEN - 100 - ELSE -- no job history (implicit) AND job not run in the past 30 days AND (Job disabled OR Job Schedule disabled) - 200 - END AS Priority, - 'Performance' AS [FindingsGroup] , - 'Shrink Database Step In Maintenance Plan' AS [Finding] , - 'https://www.brentozar.com/go/autoshrink' AS [URL] , - 'The maintenance plan ' + [mps].[name] + ' has a step to shrink databases in it. Shrinking databases is as outdated as maintenance plans.' - + CASE WHEN COALESCE(ssc.name,'0') != '0' THEN + ' (Schedule: [' + ssc.name + '])' ELSE + '' END AS [Details] - FROM [maintenance_plan_steps] [mps] - CROSS APPLY [maintenance_plan_xml].[nodes]('//dts:Executables/dts:Executable') [t]([c]) - join msdb.dbo.sysmaintplan_subplans as sms - on mps.id = sms.plan_id - JOIN msdb.dbo.sysjobs j - on sms.job_id = j.job_id - LEFT OUTER JOIN msdb.dbo.sysjobsteps AS step - ON j.job_id = step.job_id - LEFT OUTER JOIN msdb.dbo.sysjobschedules AS sjsc - ON j.job_id = sjsc.job_id - LEFT OUTER JOIN msdb.dbo.sysschedules AS ssc - ON sjsc.schedule_id = ssc.schedule_id - AND sjsc.job_id = j.job_id - LEFT OUTER JOIN msdb.dbo.sysjobhistory AS sjh - ON j.job_id = sjh.job_id - AND step.step_id = sjh.step_id - AND sjh.run_date IN (SELECT max(sjh2.run_date) FROM msdb.dbo.sysjobhistory AS sjh2 WHERE sjh2.job_id = j.job_id) -- get the latest entry date - AND sjh.run_time IN (SELECT max(sjh3.run_time) FROM msdb.dbo.sysjobhistory AS sjh3 WHERE sjh3.job_id = j.job_id AND sjh3.run_date = sjh.run_date) -- get the latest entry time - WHERE [c].[value]('(@dts:ObjectName)', 'VARCHAR(128)') = 'Shrink Database Task'; +END +ELSE /* Table exists then run the query */ +BEGIN + EXEC sp_executesql @Sql, + N'@StartDate DATETIMEOFFSET(7), + @EndDate DATETIMEOFFSET(7), + @Servername NVARCHAR(128), + @MaxBlitzFirstPriority INT', + @StartDate=@StartDate, + @EndDate=@EndDate, + @Servername=@Servername, + @MaxBlitzFirstPriority = @MaxBlitzFirstPriority; +END - END; +/* Blitz WaitStats data */ +SET @Sql = N'SELECT +[ServerName], +[CheckDate], +[wait_type], +[WaitsRank], +[WaitCategory], +[Ignorable], +[ElapsedSeconds], +[wait_time_ms_delta], +[wait_time_minutes_delta], +[wait_time_minutes_per_minute], +[signal_wait_time_ms_delta], +[waiting_tasks_count_delta], +ISNULL((CAST([wait_time_ms_delta] AS DECIMAL(38,2))/NULLIF(CAST([waiting_tasks_count_delta] AS DECIMAL(38,2)),0)),0) AS [wait_time_ms_per_wait] +FROM +( + SELECT + [ServerName], + [CheckDate], + [wait_type], + [WaitCategory], + [Ignorable], + [ElapsedSeconds], + [wait_time_ms_delta], + [wait_time_minutes_delta], + [wait_time_minutes_per_minute], + [signal_wait_time_ms_delta], + [waiting_tasks_count_delta], + ROW_NUMBER() OVER(PARTITION BY [CheckDate] ORDER BY [CheckDate] ASC,[wait_time_ms_delta] DESC) AS [WaitsRank] + FROM '+@FullOutputTableNameWaitStats+N' AS [Waits] + WHERE [ServerName] = @Servername + AND [CheckDate] BETWEEN @StartDate AND @EndDate +) TopWaits +WHERE [WaitsRank] <= @WaitStatsTop +ORDER BY +[CheckDate] ASC, +[wait_time_ms_delta] DESC +OPTION(RECOMPILE, MAXDOP '+CAST(@Maxdop AS NVARCHAR(2))+N');' - /*Find repetitive maintenance tasks*/ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 181 ) - AND CONVERT(VARCHAR(128), SERVERPROPERTY ('productversion')) LIKE '1%' /* Only run on 2008+ */ - BEGIN - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 181) WITH NOWAIT; +RAISERROR('Getting wait stats info from %s',0,0,@FullOutputTableNameWaitStats) WITH NOWAIT; - WITH XMLNAMESPACES ('www.microsoft.com/SqlServer/Dts' AS [dts]) - ,[maintenance_plan_steps] AS ( - SELECT [name] - , CAST(CAST([packagedata] AS VARBINARY(MAX)) AS XML) AS [maintenance_plan_xml] - FROM [msdb].[dbo].[sysssispackages] - WHERE [packagetype] = 6 - ), [maintenance_plan_table] AS ( - SELECT [mps].[name] - ,[c].[value]('(@dts:ObjectName)', 'NVARCHAR(128)') AS [step_name] - FROM [maintenance_plan_steps] [mps] - CROSS APPLY [maintenance_plan_xml].[nodes]('//dts:Executables/dts:Executable') [t]([c]) - ), [mp_steps_pretty] AS (SELECT DISTINCT [m1].[name] , - STUFF((SELECT N', ' + [m2].[step_name] FROM [maintenance_plan_table] AS [m2] WHERE [m1].[name] = [m2].[name] - FOR XML PATH(N'')), 1, 2, N'') AS [maintenance_plan_steps] - FROM [maintenance_plan_table] AS [m1]) - - INSERT INTO [#BlitzResults] - ( [CheckID] , - [Priority] , - [FindingsGroup] , - [Finding] , - [URL] , - [Details] ) - - SELECT - 181 AS [CheckID] , - 100 AS [Priority] , - 'Performance' AS [FindingsGroup] , - 'Repetitive Steps In Maintenance Plans' AS [Finding] , - 'https://ola.hallengren.com/' AS [URL] , - 'The maintenance plan ' + [m].[name] + ' is doing repetitive work on indexes and statistics. Perhaps it''s time to try something more modern?' AS [Details] - FROM [mp_steps_pretty] m - WHERE m.[maintenance_plan_steps] LIKE '%Rebuild%Reorganize%' - OR m.[maintenance_plan_steps] LIKE '%Rebuild%Update%'; +IF (@Debug = 1) +BEGIN + PRINT @Sql; +END - END; - +IF (OBJECT_ID(@FullOutputTableNameWaitStats) IS NULL) +BEGIN + IF (@OutputTableNameWaitStats IS NULL) + BEGIN + RAISERROR('Wait stats data skipped',10,0); + SELECT N'Skipped logged wait stats data as NULL was passed to parameter @OutputTableNameWaitStats'; + END + ELSE + BEGIN + RAISERROR('Table provided for wait stats data: %s does not exist',10,0,@FullOutputTableNameWaitStats); + SELECT N'No wait stats data available as the table cannot be found'; + END +END +ELSE /* Table exists then run the query */ +BEGIN + EXEC sp_executesql @Sql, + N'@StartDate DATETIMEOFFSET(7), + @EndDate DATETIMEOFFSET(7), + @Servername NVARCHAR(128), + @WaitStatsTop TINYINT', + @StartDate=@StartDate, + @EndDate=@EndDate, + @Servername=@Servername, + @WaitStatsTop=@WaitStatsTop; +END - /* Reliability - No Failover Cluster Nodes Available - 184 */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 184 ) - AND CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)) NOT LIKE '10%' - AND CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)) NOT LIKE '9%' - BEGIN - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 184) WITH NOWAIT; - - SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) - SELECT TOP 1 - 184 AS CheckID , - 20 AS Priority , - ''Reliability'' AS FindingsGroup , - ''No Failover Cluster Nodes Available'' AS Finding , - ''https://www.brentozar.com/go/node'' AS URL , - ''There are no failover cluster nodes available if the active node fails'' AS Details - FROM ( - SELECT SUM(CASE WHEN [status] = 0 AND [is_current_owner] = 0 THEN 1 ELSE 0 END) AS [available_nodes] - FROM sys.dm_os_cluster_nodes - ) a - WHERE [available_nodes] < 1 OPTION (RECOMPILE)'; - - IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; - IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; - - EXECUTE(@StringToExecute); - END; +/* BlitzFileStats info */ +SET @Sql = N' +SELECT +[ServerName], +[CheckDate], +CASE + WHEN MAX([io_stall_read_ms_average]) > @ReadLatencyThreshold THEN ''Yes'' + WHEN MAX([io_stall_write_ms_average]) > @WriteLatencyThreshold THEN ''Yes'' + ELSE ''No'' +END AS [io_stall_ms_breached], +LEFT([PhysicalName],LEN([PhysicalName])-CHARINDEX(''\'',REVERSE([PhysicalName]))+1) AS [PhysicalPath], +SUM([SizeOnDiskMB]) AS [SizeOnDiskMB], +SUM([SizeOnDiskMBgrowth]) AS [SizeOnDiskMBgrowth], +MAX([io_stall_read_ms]) AS [max_io_stall_read_ms], +MAX([io_stall_read_ms_average]) AS [max_io_stall_read_ms_average], +@ReadLatencyThreshold AS [is_stall_read_ms_threshold], +SUM([num_of_reads]) AS [num_of_reads], +SUM([megabytes_read]) AS [megabytes_read], +MAX([io_stall_write_ms]) AS [max_io_stall_write_ms], +MAX([io_stall_write_ms_average]) AS [max_io_stall_write_ms_average], +@WriteLatencyThreshold AS [io_stall_write_ms_average], +SUM([num_of_writes]) AS [num_of_writes], +SUM([megabytes_written]) AS [megabytes_written] +FROM '+@FullOutputTableNameFileStats+N' +WHERE [ServerName] = @Servername +AND [CheckDate] BETWEEN @StartDate AND @EndDate +' ++CASE + WHEN @Databasename IS NOT NULL THEN N'AND [DatabaseName] IN (N''tempdb'',@Databasename) +' + ELSE N'' +END ++N'GROUP BY +[ServerName], +[CheckDate], +LEFT([PhysicalName],LEN([PhysicalName])-CHARINDEX(''\'',REVERSE([PhysicalName]))+1) +ORDER BY +[CheckDate] ASC +OPTION (RECOMPILE, MAXDOP '+CAST(@Maxdop AS NVARCHAR(2))+N');' - /* Reliability - TempDB File Error */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 191 ) - AND (SELECT COUNT(*) FROM sys.master_files WHERE database_id = 2) <> (SELECT COUNT(*) FROM tempdb.sys.database_files) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 191) WITH NOWAIT - - INSERT INTO [#BlitzResults] - ( [CheckID] , - [Priority] , - [FindingsGroup] , - [Finding] , - [URL] , - [Details] ) - - SELECT - 191 AS [CheckID] , - 50 AS [Priority] , - 'Reliability' AS [FindingsGroup] , - 'TempDB File Error' AS [Finding] , - 'https://www.brentozar.com/go/tempdboops' AS [URL] , - 'Mismatch between the number of TempDB files in sys.master_files versus tempdb.sys.database_files' AS [Details]; - END; +RAISERROR('Getting FileStats info from %s',0,0,@FullOutputTableNameFileStats) WITH NOWAIT; -/*Perf - Odd number of cores in a socket*/ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL - AND CheckID = 198 ) - AND EXISTS ( SELECT 1 - FROM sys.dm_os_schedulers - WHERE is_online = 1 - AND scheduler_id < 255 - AND parent_node_id < 64 - GROUP BY parent_node_id, - is_online - HAVING ( COUNT(cpu_id) + 2 ) % 2 = 1 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 198) WITH NOWAIT - - INSERT INTO #BlitzResults - ( - CheckID, - DatabaseName, - Priority, - FindingsGroup, - Finding, - URL, - Details - ) - SELECT 198 AS CheckID, - NULL AS DatabaseName, - 10 AS Priority, - 'Performance' AS FindingsGroup, - 'CPU w/Odd Number of Cores' AS Finding, - 'https://www.brentozar.com/go/oddity' AS URL, - 'Node ' + CONVERT(VARCHAR(10), parent_node_id) + ' has ' + CONVERT(VARCHAR(10), COUNT(cpu_id)) - + CASE WHEN COUNT(cpu_id) = 1 THEN ' core assigned to it. This is a really bad NUMA configuration.' - ELSE ' cores assigned to it. This is a really bad NUMA configuration.' - END AS Details - FROM sys.dm_os_schedulers - WHERE is_online = 1 - AND scheduler_id < 255 - AND parent_node_id < 64 - AND EXISTS ( - SELECT 1 - FROM ( SELECT memory_node_id, SUM(online_scheduler_count) AS schedulers - FROM sys.dm_os_nodes - WHERE memory_node_id < 64 - GROUP BY memory_node_id ) AS nodes - HAVING MIN(nodes.schedulers) <> MAX(nodes.schedulers) - ) - GROUP BY parent_node_id, - is_online - HAVING ( COUNT(cpu_id) + 2 ) % 2 = 1; - - END; +IF (@Debug = 1) +BEGIN + PRINT @Sql; +END -/*Begin: checking default trace for odd DBCC activity*/ - - --Grab relevant event data - IF @TraceFileIssue = 0 - BEGIN - SELECT UPPER( - REPLACE( - SUBSTRING(CONVERT(NVARCHAR(MAX), t.TextData), 0, - ISNULL( - NULLIF( - CHARINDEX('(', CONVERT(NVARCHAR(MAX), t.TextData)), - 0), - LEN(CONVERT(NVARCHAR(MAX), t.TextData)) + 1 )) --This replaces everything up to an open paren, if one exists. - , SUBSTRING(CONVERT(NVARCHAR(MAX), t.TextData), - ISNULL( - NULLIF( - CHARINDEX(' WITH ',CONVERT(NVARCHAR(MAX), t.TextData)) - , 0), - LEN(CONVERT(NVARCHAR(MAX), t.TextData)) + 1), - LEN(CONVERT(NVARCHAR(MAX), t.TextData)) + 1 ) - , '') --This replaces any optional WITH clause to a DBCC command, like tableresults. - ) AS [dbcc_event_trunc_upper], - UPPER( - REPLACE( - CONVERT(NVARCHAR(MAX), t.TextData), SUBSTRING(CONVERT(NVARCHAR(MAX), t.TextData), - ISNULL( - NULLIF( - CHARINDEX(' WITH ',CONVERT(NVARCHAR(MAX), t.TextData)) - , 0), - LEN(CONVERT(NVARCHAR(MAX), t.TextData)) + 1), - LEN(CONVERT(NVARCHAR(MAX), t.TextData)) + 1 ), '')) AS [dbcc_event_full_upper], - MIN(t.StartTime) OVER (PARTITION BY CONVERT(NVARCHAR(128), t.TextData)) AS min_start_time, - MAX(t.StartTime) OVER (PARTITION BY CONVERT(NVARCHAR(128), t.TextData)) AS max_start_time, - t.NTUserName AS [nt_user_name], - t.NTDomainName AS [nt_domain_name], - t.HostName AS [host_name], - t.ApplicationName AS [application_name], - t.LoginName [login_name], - t.DBUserName AS [db_user_name] - INTO #dbcc_events_from_trace - FROM #fnTraceGettable AS t - WHERE t.EventClass = 116 - OPTION(RECOMPILE) - END; +IF (OBJECT_ID(@FullOutputTableNameFileStats) IS NULL) +BEGIN + IF (@OutputTableNameFileStats IS NULL) + BEGIN + RAISERROR('File stats data skipped',10,0); + SELECT N'Skipped logged File stats data as NULL was passed to parameter @OutputTableNameFileStats'; + END + ELSE + BEGIN + RAISERROR('Table provided for FileStats data: %s does not exist',10,0,@FullOutputTableNameFileStats); + SELECT N'No File stats data available as the table cannot be found'; + END +END +ELSE /* Table exists then run the query */ +BEGIN + EXEC sp_executesql @Sql, + N'@StartDate DATETIMEOFFSET(7), + @EndDate DATETIMEOFFSET(7), + @Servername NVARCHAR(128), + @Databasename NVARCHAR(128), + @ReadLatencyThreshold INT, + @WriteLatencyThreshold INT', + @StartDate=@StartDate, + @EndDate=@EndDate, + @Servername=@Servername, + @Databasename = @Databasename, + @ReadLatencyThreshold = @ReadLatencyThreshold, + @WriteLatencyThreshold = @WriteLatencyThreshold; +END - /*Overall count of DBCC events excluding silly stuff*/ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 203 ) - AND @TraceFileIssue = 0 - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 203) WITH NOWAIT - - INSERT INTO [#BlitzResults] - ( [CheckID] , - [Priority] , - [FindingsGroup] , - [Finding] , - [URL] , - [Details] ) - SELECT 203 AS CheckID , - 50 AS Priority , - 'DBCC Events' AS FindingsGroup , - 'Overall Events' AS Finding , - 'https://www.BrentOzar.com/go/dbcc' AS URL , - CAST(COUNT(*) AS NVARCHAR(100)) + ' DBCC events have taken place between ' + CONVERT(NVARCHAR(30), MIN(d.min_start_time)) + ' and ' + CONVERT(NVARCHAR(30), MAX(d.max_start_time)) + - '. This does not include CHECKDB and other usually benign DBCC events.' - AS Details - FROM #dbcc_events_from_trace d - /* This WHERE clause below looks horrible, but it's because users can run stuff like - DBCC LOGINFO - with lots of spaces (or carriage returns, or comments) in between the DBCC and the - command they're trying to run. See Github issues 1062, 1074, 1075. - */ - WHERE d.dbcc_event_full_upper NOT LIKE '%DBCC%ADDINSTANCE%' - AND d.dbcc_event_full_upper NOT LIKE '%DBCC%AUTOPILOT%' - AND d.dbcc_event_full_upper NOT LIKE '%DBCC%CHECKALLOC%' - AND d.dbcc_event_full_upper NOT LIKE '%DBCC%CHECKCATALOG%' - AND d.dbcc_event_full_upper NOT LIKE '%DBCC%CHECKCONSTRAINTS%' - AND d.dbcc_event_full_upper NOT LIKE '%DBCC%CHECKDB%' - AND d.dbcc_event_full_upper NOT LIKE '%DBCC%CHECKFILEGROUP%' - AND d.dbcc_event_full_upper NOT LIKE '%DBCC%CHECKIDENT%' - AND d.dbcc_event_full_upper NOT LIKE '%DBCC%CHECKPRIMARYFILE%' - AND d.dbcc_event_full_upper NOT LIKE '%DBCC%CHECKTABLE%' - AND d.dbcc_event_full_upper NOT LIKE '%DBCC%CLEANTABLE%' - AND d.dbcc_event_full_upper NOT LIKE '%DBCC%DBINFO%' - AND d.dbcc_event_full_upper NOT LIKE '%DBCC%ERRORLOG%' - AND d.dbcc_event_full_upper NOT LIKE '%DBCC%INCREMENTINSTANCE%' - AND d.dbcc_event_full_upper NOT LIKE '%DBCC%INPUTBUFFER%' - AND d.dbcc_event_full_upper NOT LIKE '%DBCC%LOGINFO%' - AND d.dbcc_event_full_upper NOT LIKE '%DBCC%OPENTRAN%' - AND d.dbcc_event_full_upper NOT LIKE '%DBCC%SETINSTANCE%' - AND d.dbcc_event_full_upper NOT LIKE '%DBCC%SHOWFILESTATS%' - AND d.dbcc_event_full_upper NOT LIKE '%DBCC%SHOW_STATISTICS%' - AND d.dbcc_event_full_upper NOT LIKE '%DBCC%SQLPERF%NETSTATS%' - AND d.dbcc_event_full_upper NOT LIKE '%DBCC%SQLPERF%LOGSPACE%' - AND d.dbcc_event_full_upper NOT LIKE '%DBCC%TRACEON%' - AND d.dbcc_event_full_upper NOT LIKE '%DBCC%TRACEOFF%' - AND d.dbcc_event_full_upper NOT LIKE '%DBCC%TRACESTATUS%' - AND d.dbcc_event_full_upper NOT LIKE '%DBCC%USEROPTIONS%' - AND d.application_name NOT LIKE 'Critical Care(R) Collector' - AND d.application_name NOT LIKE '%Red Gate Software Ltd SQL Prompt%' - AND d.application_name NOT LIKE '%Spotlight Diagnostic Server%' - AND d.application_name NOT LIKE '%SQL Diagnostic Manager%' - AND d.application_name NOT LIKE 'SQL Server Checkup%' - AND d.application_name NOT LIKE '%Sentry%' - AND d.application_name NOT LIKE '%LiteSpeed%' - AND d.application_name NOT LIKE '%SQL Monitor - Monitoring%' - +/* Blitz Perfmon stats*/ +SET @Sql = N' +SELECT + [ServerName] + ,[CheckDate] + ,[counter_name] + ,[object_name] + ,[instance_name] + ,[cntr_value] +FROM '+@FullOutputTableNamePerfmonStats+N' +WHERE [ServerName] = @Servername +AND CheckDate BETWEEN @StartDate AND @EndDate +ORDER BY + [CheckDate] ASC, + [counter_name] ASC +OPTION (RECOMPILE, MAXDOP '+CAST(@Maxdop AS NVARCHAR(2))+N');' - HAVING COUNT(*) > 0; - - END; +RAISERROR('Getting Perfmon info from %s',0,0,@FullOutputTableNamePerfmonStats) WITH NOWAIT; - /*Check for someone running drop clean buffers*/ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 207 ) - AND @TraceFileIssue = 0 - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 207) WITH NOWAIT - - INSERT INTO [#BlitzResults] - ( [CheckID] , - [Priority] , - [FindingsGroup] , - [Finding] , - [URL] , - [Details] ) - SELECT 207 AS CheckID , - 10 AS Priority , - 'Performance' AS FindingsGroup , - 'DBCC DROPCLEANBUFFERS Ran Recently' AS Finding , - 'https://www.BrentOzar.com/go/dbcc' AS URL , - 'The user ' + COALESCE(d.nt_user_name, d.login_name) + ' has run DBCC DROPCLEANBUFFERS ' + CAST(COUNT(*) AS NVARCHAR(100)) + ' times between ' + CONVERT(NVARCHAR(30), MIN(d.min_start_time)) + ' and ' + CONVERT(NVARCHAR(30), MAX(d.max_start_time)) + - '. If this is a production box, know that you''re clearing all data out of memory when this happens. What kind of monster would do that?' - AS Details - FROM #dbcc_events_from_trace d - WHERE d.dbcc_event_full_upper = N'DBCC DROPCLEANBUFFERS' - GROUP BY COALESCE(d.nt_user_name, d.login_name) - HAVING COUNT(*) > 0; +IF (@Debug = 1) +BEGIN + PRINT @Sql; +END - END; +IF (OBJECT_ID(@FullOutputTableNamePerfmonStats) IS NULL) +BEGIN + IF (@OutputTableNamePerfmonStats IS NULL) + BEGIN + RAISERROR('Perfmon stats data skipped',10,0); + SELECT N'Skipped logged Perfmon stats data as NULL was passed to parameter @OutputTableNamePerfmonStats'; + END + ELSE + BEGIN + RAISERROR('Table provided for Perfmon stats data: %s does not exist',10,0,@FullOutputTableNamePerfmonStats); + SELECT N'No Perfmon data available as the table cannot be found'; + END +END +ELSE /* Table exists then run the query */ +BEGIN + EXEC sp_executesql @Sql, + N'@StartDate DATETIMEOFFSET(7), + @EndDate DATETIMEOFFSET(7), + @Servername NVARCHAR(128)', + @StartDate=@StartDate, + @EndDate=@EndDate, + @Servername=@Servername; +END - /*Check for someone running free proc cache*/ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 208 ) - AND @TraceFileIssue = 0 - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 208) WITH NOWAIT - - INSERT INTO [#BlitzResults] - ( [CheckID] , - [Priority] , - [FindingsGroup] , - [Finding] , - [URL] , - [Details] ) - SELECT 208 AS CheckID , - 10 AS Priority , - 'DBCC Events' AS FindingsGroup , - 'DBCC FREEPROCCACHE Ran Recently' AS Finding , - 'https://www.BrentOzar.com/go/dbcc' AS URL , - 'The user ' + COALESCE(d.nt_user_name, d.login_name) + ' has run DBCC FREEPROCCACHE ' + CAST(COUNT(*) AS NVARCHAR(100)) + ' times between ' + CONVERT(NVARCHAR(30), MIN(d.min_start_time)) + ' and ' + CONVERT(NVARCHAR(30), MAX(d.max_start_time)) + - '. This has bad idea jeans written all over its butt, like most other bad idea jeans.' - AS Details - FROM #dbcc_events_from_trace d - WHERE d.dbcc_event_full_upper = N'DBCC FREEPROCCACHE' - GROUP BY COALESCE(d.nt_user_name, d.login_name) - HAVING COUNT(*) > 0; - - END; - - /*Check for someone clearing wait stats*/ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 205 ) - AND @TraceFileIssue = 0 - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 205) WITH NOWAIT - - INSERT INTO [#BlitzResults] - ( [CheckID] , - [Priority] , - [FindingsGroup] , - [Finding] , - [URL] , - [Details] ) - SELECT 205 AS CheckID , - 50 AS Priority , - 'Performance' AS FindingsGroup , - 'Wait Stats Cleared Recently' AS Finding , - 'https://www.BrentOzar.com/go/dbcc' AS URL , - 'The user ' + COALESCE(d.nt_user_name, d.login_name) + ' has run DBCC SQLPERF(''SYS.DM_OS_WAIT_STATS'',CLEAR) ' + CAST(COUNT(*) AS NVARCHAR(100)) + ' times between ' + CONVERT(NVARCHAR(30), MIN(d.min_start_time)) + ' and ' + CONVERT(NVARCHAR(30), MAX(d.max_start_time)) + - '. Why are you clearing wait stats? What are you hiding?' - AS Details - FROM #dbcc_events_from_trace d - WHERE d.dbcc_event_full_upper = N'DBCC SQLPERF(''SYS.DM_OS_WAIT_STATS'',CLEAR)' - GROUP BY COALESCE(d.nt_user_name, d.login_name) - HAVING COUNT(*) > 0; - - END; - - /*Check for someone writing to pages. Yeah, right?*/ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 209 ) - AND @TraceFileIssue = 0 - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 209) WITH NOWAIT - - INSERT INTO [#BlitzResults] - ( [CheckID] , - [Priority] , - [FindingsGroup] , - [Finding] , - [URL] , - [Details] ) - SELECT 209 AS CheckID , - 10 AS Priority , - 'Reliability' AS FindingsGroup , - 'DBCC WRITEPAGE Used Recently' AS Finding , - 'https://www.BrentOzar.com/go/dbcc' AS URL , - 'The user ' + COALESCE(d.nt_user_name, d.login_name) + ' has run DBCC WRITEPAGE ' + CAST(COUNT(*) AS NVARCHAR(100)) + ' times between ' + CONVERT(NVARCHAR(30), MIN(d.min_start_time)) + ' and ' + CONVERT(NVARCHAR(30), MAX(d.max_start_time)) + - '. So, uh, are they trying to fix corruption, or cause corruption?' - AS Details - FROM #dbcc_events_from_trace d - WHERE d.dbcc_event_trunc_upper = N'DBCC WRITEPAGE' - GROUP BY COALESCE(d.nt_user_name, d.login_name) - HAVING COUNT(*) > 0; - - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 210 ) - AND @TraceFileIssue = 0 - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 210) WITH NOWAIT - - INSERT INTO [#BlitzResults] - ( [CheckID] , - [Priority] , - [FindingsGroup] , - [Finding] , - [URL] , - [Details] ) - - SELECT 210 AS CheckID , - 10 AS Priority , - 'Performance' AS FindingsGroup , - 'DBCC SHRINK% Ran Recently' AS Finding , - 'https://www.BrentOzar.com/go/dbcc' AS URL , - 'The user ' + COALESCE(d.nt_user_name, d.login_name) + ' has run file shrinks ' + CAST(COUNT(*) AS NVARCHAR(100)) + ' times between ' + CONVERT(NVARCHAR(30), MIN(d.min_start_time)) + ' and ' + CONVERT(NVARCHAR(30), MAX(d.max_start_time)) + - '. So, uh, are they trying to cause bad performance on purpose?' - AS Details - FROM #dbcc_events_from_trace d - WHERE d.dbcc_event_trunc_upper LIKE N'DBCC SHRINK%' - GROUP BY COALESCE(d.nt_user_name, d.login_name) - HAVING COUNT(*) > 0; - - END; - -/*End: checking default trace for odd DBCC activity*/ - - /*Begin check for autoshrink events*/ - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 206 ) - AND @TraceFileIssue = 0 - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 206) WITH NOWAIT - - INSERT INTO [#BlitzResults] - ( [CheckID] , - [Priority] , - [FindingsGroup] , - [Finding] , - [URL] , - [Details] ) - - SELECT 206 AS CheckID , - 10 AS Priority , - 'Performance' AS FindingsGroup , - 'Auto-Shrink Ran Recently' AS Finding , - '' AS URL , - N'The database ' + QUOTENAME(t.DatabaseName) + N' has had ' - + CONVERT(NVARCHAR(10), COUNT(*)) - + N' auto shrink events between ' - + CONVERT(NVARCHAR(30), MIN(t.StartTime)) + ' and ' + CONVERT(NVARCHAR(30), MAX(t.StartTime)) - + ' that lasted on average ' - + CONVERT(NVARCHAR(10), AVG(DATEDIFF(SECOND, t.StartTime, t.EndTime))) - + ' seconds.' AS Details - FROM #fnTraceGettable AS t - WHERE t.EventClass IN (94, 95) - GROUP BY t.DatabaseName - HAVING AVG(DATEDIFF(SECOND, t.StartTime, t.EndTime)) > 5; - - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 215 ) - AND @TraceFileIssue = 0 - AND EXISTS (SELECT * FROM sys.all_columns WHERE name = 'database_id' AND object_id = OBJECT_ID('sys.dm_exec_sessions')) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 215) WITH NOWAIT - - SET @StringToExecute = 'INSERT INTO [#BlitzResults] - ( [CheckID] , - [Priority] , - [FindingsGroup] , - [Finding] , - [DatabaseName] , - [URL] , - [Details] ) +/* Blitz cache data */ +RAISERROR('Sortorder for BlitzCache data: %s',0,0,@BlitzCacheSortorder) WITH NOWAIT; - SELECT 215 AS CheckID , - 100 AS Priority , - ''Performance'' AS FindingsGroup , - ''Implicit Transactions'' AS Finding , - DB_NAME(s.database_id) AS DatabaseName, - ''https://www.brentozar.com/go/ImplicitTransactions/'' AS URL , - N''The database '' + - DB_NAME(s.database_id) - + '' has '' - + CONVERT(NVARCHAR(20), COUNT_BIG(*)) - + '' open implicit transactions with an oldest begin time of '' - + CONVERT(NVARCHAR(30), MIN(tat.transaction_begin_time)) - + '' Run sp_BlitzWho and check the is_implicit_transaction column to see the culprits.'' AS details - FROM sys.dm_tran_active_transactions AS tat - LEFT JOIN sys.dm_tran_session_transactions AS tst - ON tst.transaction_id = tat.transaction_id - LEFT JOIN sys.dm_exec_sessions AS s - ON s.session_id = tst.session_id - WHERE tat.name = ''implicit_transaction'' - GROUP BY DB_NAME(s.database_id), transaction_type, transaction_state;'; +/* Set intial CTE */ +SET @Sql = N'WITH CheckDates AS ( +SELECT DISTINCT CheckDate +FROM ' ++@FullOutputTableNameBlitzCache ++N' +WHERE [ServerName] = @Servername +AND [CheckDate] BETWEEN @StartDate AND @EndDate' ++@NewLine ++CASE + WHEN @Databasename IS NOT NULL THEN N'AND [DatabaseName] = @Databasename'+@NewLine + ELSE N'' +END ++N')' +; +SET @Sql += @NewLine; - IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; - IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; - - EXECUTE(@StringToExecute); +/* Append additional CTEs based on sortorder */ +SET @Sql += ( +SELECT CAST(N',' AS NVARCHAR(MAX)) ++[SortOptions].[Aliasname]+N' AS ( +SELECT + [ServerName] + ,'+[SortOptions].[Aliasname]+N'.[CheckDate] + ,[Sortorder] + ,[TimeFrameRank] + ,ROW_NUMBER() OVER(ORDER BY ['+[SortOptions].[Columnname]+N'] DESC) AS [OverallRank] + ,'+[SortOptions].[Aliasname]+N'.[QueryType] + ,'+[SortOptions].[Aliasname]+N'.[QueryText] + ,'+[SortOptions].[Aliasname]+N'.[DatabaseName] + ,'+[SortOptions].[Aliasname]+N'.[AverageCPU] + ,'+[SortOptions].[Aliasname]+N'.[TotalCPU] + ,'+[SortOptions].[Aliasname]+N'.[PercentCPUByType] + ,'+[SortOptions].[Aliasname]+N'.[AverageDuration] + ,'+[SortOptions].[Aliasname]+N'.[TotalDuration] + ,'+[SortOptions].[Aliasname]+N'.[PercentDurationByType] + ,'+[SortOptions].[Aliasname]+N'.[AverageReads] + ,'+[SortOptions].[Aliasname]+N'.[TotalReads] + ,'+[SortOptions].[Aliasname]+N'.[PercentReadsByType] + ,'+[SortOptions].[Aliasname]+N'.[AverageWrites] + ,'+[SortOptions].[Aliasname]+N'.[TotalWrites] + ,'+[SortOptions].[Aliasname]+N'.[PercentWritesByType] + ,'+[SortOptions].[Aliasname]+N'.[ExecutionCount] + ,'+[SortOptions].[Aliasname]+N'.[ExecutionWeight] + ,'+[SortOptions].[Aliasname]+N'.[PercentExecutionsByType] + ,'+[SortOptions].[Aliasname]+N'.[ExecutionsPerMinute] + ,'+[SortOptions].[Aliasname]+N'.[PlanCreationTime] + ,'+[SortOptions].[Aliasname]+N'.[PlanCreationTimeHours] + ,'+[SortOptions].[Aliasname]+N'.[LastExecutionTime] + ,'+[SortOptions].[Aliasname]+N'.[PlanHandle] + ,'+[SortOptions].[Aliasname]+N'.[SqlHandle] + ,'+[SortOptions].[Aliasname]+N'.[SQL Handle More Info] + ,'+[SortOptions].[Aliasname]+N'.[QueryHash] + ,'+[SortOptions].[Aliasname]+N'.[Query Hash More Info] + ,'+[SortOptions].[Aliasname]+N'.[QueryPlanHash] + ,'+[SortOptions].[Aliasname]+N'.[StatementStartOffset] + ,'+[SortOptions].[Aliasname]+N'.[StatementEndOffset] + ,'+[SortOptions].[Aliasname]+N'.[MinReturnedRows] + ,'+[SortOptions].[Aliasname]+N'.[MaxReturnedRows] + ,'+[SortOptions].[Aliasname]+N'.[AverageReturnedRows] + ,'+[SortOptions].[Aliasname]+N'.[TotalReturnedRows] + ,'+[SortOptions].[Aliasname]+N'.[QueryPlan] + ,'+[SortOptions].[Aliasname]+N'.[NumberOfPlans] + ,'+[SortOptions].[Aliasname]+N'.[NumberOfDistinctPlans] + ,'+[SortOptions].[Aliasname]+N'.[MinGrantKB] + ,'+[SortOptions].[Aliasname]+N'.[MaxGrantKB] + ,'+[SortOptions].[Aliasname]+N'.[MinUsedGrantKB] + ,'+[SortOptions].[Aliasname]+N'.[MaxUsedGrantKB] + ,'+[SortOptions].[Aliasname]+N'.[PercentMemoryGrantUsed] + ,'+[SortOptions].[Aliasname]+N'.[AvgMaxMemoryGrant] + ,'+[SortOptions].[Aliasname]+N'.[MinSpills] + ,'+[SortOptions].[Aliasname]+N'.[MaxSpills] + ,'+[SortOptions].[Aliasname]+N'.[TotalSpills] + ,'+[SortOptions].[Aliasname]+N'.[AvgSpills] + ,'+[SortOptions].[Aliasname]+N'.[QueryPlanCost] +FROM CheckDates +CROSS APPLY ( + SELECT TOP (5) + [ServerName] + ,'+[SortOptions].[Aliasname]+N'.[CheckDate] + ,'+QUOTENAME(UPPER([SortOptions].[Sortorder]),N'''')+N' AS [Sortorder] + ,ROW_NUMBER() OVER(ORDER BY ['+[SortOptions].[Columnname]+N'] DESC) AS [TimeFrameRank] + ,'+[SortOptions].[Aliasname]+N'.[QueryType] + ,'+[SortOptions].[Aliasname]+N'.[QueryText] + ,'+[SortOptions].[Aliasname]+N'.[DatabaseName] + ,'+[SortOptions].[Aliasname]+N'.[AverageCPU] + ,'+[SortOptions].[Aliasname]+N'.[TotalCPU] + ,'+[SortOptions].[Aliasname]+N'.[PercentCPUByType] + ,'+[SortOptions].[Aliasname]+N'.[AverageDuration] + ,'+[SortOptions].[Aliasname]+N'.[TotalDuration] + ,'+[SortOptions].[Aliasname]+N'.[PercentDurationByType] + ,'+[SortOptions].[Aliasname]+N'.[AverageReads] + ,'+[SortOptions].[Aliasname]+N'.[TotalReads] + ,'+[SortOptions].[Aliasname]+N'.[PercentReadsByType] + ,'+[SortOptions].[Aliasname]+N'.[AverageWrites] + ,'+[SortOptions].[Aliasname]+N'.[TotalWrites] + ,'+[SortOptions].[Aliasname]+N'.[PercentWritesByType] + ,'+[SortOptions].[Aliasname]+N'.[ExecutionCount] + ,'+[SortOptions].[Aliasname]+N'.[ExecutionWeight] + ,'+[SortOptions].[Aliasname]+N'.[PercentExecutionsByType] + ,'+[SortOptions].[Aliasname]+N'.[ExecutionsPerMinute] + ,'+[SortOptions].[Aliasname]+N'.[PlanCreationTime] + ,'+[SortOptions].[Aliasname]+N'.[PlanCreationTimeHours] + ,'+[SortOptions].[Aliasname]+N'.[LastExecutionTime] + ,'+[SortOptions].[Aliasname]+N'.[PlanHandle] + ,'+[SortOptions].[Aliasname]+N'.[SqlHandle] + ,'+[SortOptions].[Aliasname]+N'.[SQL Handle More Info] + ,'+[SortOptions].[Aliasname]+N'.[QueryHash] + ,'+[SortOptions].[Aliasname]+N'.[Query Hash More Info] + ,'+[SortOptions].[Aliasname]+N'.[QueryPlanHash] + ,'+[SortOptions].[Aliasname]+N'.[StatementStartOffset] + ,'+[SortOptions].[Aliasname]+N'.[StatementEndOffset] + ,'+[SortOptions].[Aliasname]+N'.[MinReturnedRows] + ,'+[SortOptions].[Aliasname]+N'.[MaxReturnedRows] + ,'+[SortOptions].[Aliasname]+N'.[AverageReturnedRows] + ,'+[SortOptions].[Aliasname]+N'.[TotalReturnedRows] + ,'+[SortOptions].[Aliasname]+N'.[QueryPlan] + ,'+[SortOptions].[Aliasname]+N'.[NumberOfPlans] + ,'+[SortOptions].[Aliasname]+N'.[NumberOfDistinctPlans] + ,'+[SortOptions].[Aliasname]+N'.[MinGrantKB] + ,'+[SortOptions].[Aliasname]+N'.[MaxGrantKB] + ,'+[SortOptions].[Aliasname]+N'.[MinUsedGrantKB] + ,'+[SortOptions].[Aliasname]+N'.[MaxUsedGrantKB] + ,'+[SortOptions].[Aliasname]+N'.[PercentMemoryGrantUsed] + ,'+[SortOptions].[Aliasname]+N'.[AvgMaxMemoryGrant] + ,'+[SortOptions].[Aliasname]+N'.[MinSpills] + ,'+[SortOptions].[Aliasname]+N'.[MaxSpills] + ,'+[SortOptions].[Aliasname]+N'.[TotalSpills] + ,'+[SortOptions].[Aliasname]+N'.[AvgSpills] + ,'+[SortOptions].[Aliasname]+N'.[QueryPlanCost] + FROM '+@FullOutputTableNameBlitzCache+N' AS '+[SortOptions].[Aliasname]+N' + WHERE [ServerName] = @Servername + AND [CheckDate] BETWEEN @StartDate AND @EndDate + AND ['+[SortOptions].[Aliasname]+N'].[CheckDate] = [CheckDates].[CheckDate]' + +@NewLine + +CASE + WHEN @Databasename IS NOT NULL THEN N'AND ['+[SortOptions].[Aliasname]+N'].[DatabaseName] = @Databasename'+@NewLine + ELSE N'' + END + +CASE + WHEN [Sortorder] = N'cpu' THEN N'AND [TotalCPU] > 0' + WHEN [Sortorder] = N'reads' THEN N'AND [TotalReads] > 0' + WHEN [Sortorder] = N'writes' THEN N'AND [TotalWrites] > 0' + WHEN [Sortorder] = N'duration' THEN N'AND [TotalDuration] > 0' + WHEN [Sortorder] = N'executions' THEN N'AND [ExecutionCount] > 0' + WHEN [Sortorder] = N'memory grant' THEN N'AND [MaxGrantKB] > 0' + WHEN [Sortorder] = N'spills' THEN N'AND [MaxSpills] > 0' + ELSE N'' + END + +N' + ORDER BY ['+[SortOptions].[Columnname]+N'] DESC) '+[SortOptions].[Aliasname]+N' +)' +FROM (VALUES + (N'cpu',N'TopCPU',N'TotalCPU'), + (N'reads',N'TopReads',N'TotalReads'), + (N'writes',N'TopWrites',N'TotalWrites'), + (N'duration',N'TopDuration',N'TotalDuration'), + (N'executions',N'TopExecutions',N'ExecutionCount'), + (N'memory grant',N'TopMemoryGrants',N'MaxGrantKB'), + (N'spills',N'TopSpills',N'MaxSpills') + ) SortOptions(Sortorder,Aliasname,Columnname) +WHERE + CASE /* for spills and memory grant sorts make sure the underlying columns exist in the DMV otherwise do not include them */ + WHEN (@IncludeMemoryGrants = 0 OR @IncludeMemoryGrants IS NULL) AND ([SortOptions].[Sortorder] = N'memory grant' OR [SortOptions].[Sortorder] = N'all') THEN NULL + WHEN (@IncludeSpills = 0 OR @IncludeSpills IS NULL) AND ([SortOptions].[Sortorder] = N'spills' OR [SortOptions].[Sortorder] = N'all') THEN NULL + ELSE [SortOptions].[Sortorder] + END = ISNULL(NULLIF(@BlitzCacheSortorder,N'all'),[SortOptions].[Sortorder]) +FOR XML PATH(N''), TYPE).value(N'.[1]', N'NVARCHAR(MAX)'); +SET @Sql += @NewLine; - - END; +/* Build the select statements to return the data after CTE declarations */ +SET @Sql += ( +SELECT STUFF(( +SELECT @NewLine ++N'UNION ALL' ++@NewLine ++N'SELECT * +FROM '+[SortOptions].[Aliasname] +FROM (VALUES + (N'cpu',N'TopCPU',N'TotalCPU'), + (N'reads',N'TopReads',N'TotalReads'), + (N'writes',N'TopWrites',N'TotalWrites'), + (N'duration',N'TopDuration',N'TotalDuration'), + (N'executions',N'TopExecutions',N'ExecutionCount'), + (N'memory grant',N'TopMemoryGrants',N'MaxGrantKB'), + (N'spills',N'TopSpills',N'MaxSpills') + ) SortOptions(Sortorder,Aliasname,Columnname) +WHERE + CASE /* for spills and memory grant sorts make sure the underlying columns exist in the DMV otherwise do not include them */ + WHEN (@IncludeMemoryGrants = 0 OR @IncludeMemoryGrants IS NULL) AND ([SortOptions].[Sortorder] = N'memory grant' OR [SortOptions].[Sortorder] = N'all') THEN NULL + WHEN (@IncludeSpills = 0 OR @IncludeSpills IS NULL) AND ([SortOptions].[Sortorder] = N'spills' OR [SortOptions].[Sortorder] = N'all') THEN NULL + ELSE [SortOptions].[Sortorder] + END = ISNULL(NULLIF(@BlitzCacheSortorder,N'all'),[SortOptions].[Sortorder]) +FOR XML PATH(N''), TYPE).value(N'.[1]', N'NVARCHAR(MAX)'),1,11,N'') +); - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 221 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 221) WITH NOWAIT; - - WITH reboot_airhorn - AS - ( - SELECT create_date - FROM sys.databases - WHERE database_id = 2 - UNION ALL - SELECT CAST(DATEADD(SECOND, ( ms_ticks / 1000 ) * ( -1 ), GETDATE()) AS DATETIME) - FROM sys.dm_os_sys_info - ) - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 221 AS CheckID, - 10 AS Priority, - 'Reliability' AS FindingsGroup, - 'Server restarted in last 24 hours' AS Finding, - '' AS URL, - 'Surprise! Your server was last restarted on: ' + CONVERT(VARCHAR(30), MAX(reboot_airhorn.create_date)) AS details - FROM reboot_airhorn - HAVING MAX(reboot_airhorn.create_date) >= DATEADD(HOUR, -24, GETDATE()); - +/* Append Order By */ +SET @Sql += @NewLine ++N'ORDER BY + [Sortorder] ASC, + [CheckDate] ASC, + [TimeFrameRank] ASC'; - END; +/* Append OPTION(RECOMPILE, MAXDOP) to complete the statement */ +SET @Sql += @NewLine ++N'OPTION(RECOMPILE, MAXDOP '+CAST(@Maxdop AS NVARCHAR(2))+N');'; - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 229 ) - AND CAST(SERVERPROPERTY('Edition') AS NVARCHAR(4000)) LIKE '%Evaluation%' - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 229) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 229 AS CheckID, - 1 AS Priority, - 'Reliability' AS FindingsGroup, - 'Evaluation Edition' AS Finding, - 'https://www.BrentOzar.com/go/workgroup' AS URL, - 'This server will stop working on: ' + CAST(CONVERT(DATETIME, DATEADD(DD, 180, create_date), 102) AS VARCHAR(100)) AS details - FROM sys.server_principals - WHERE sid = 0x010100000000000512000000; - - END; +RAISERROR('Getting BlitzCache info from %s',0,0,@FullOutputTableNameBlitzCache) WITH NOWAIT; +IF (@Debug = 1) +BEGIN + PRINT SUBSTRING(@Sql, 0, 4000); + PRINT SUBSTRING(@Sql, 4000, 8000); + PRINT SUBSTRING(@Sql, 8000, 12000); + PRINT SUBSTRING(@Sql, 12000, 16000); + PRINT SUBSTRING(@Sql, 16000, 20000); + PRINT SUBSTRING(@Sql, 20000, 24000); + PRINT SUBSTRING(@Sql, 24000, 28000); +END +IF (OBJECT_ID(@FullOutputTableNameBlitzCache) IS NULL) +BEGIN + IF (@OutputTableNameBlitzCache IS NULL) + BEGIN + RAISERROR('BlitzCache data skipped',10,0); + SELECT N'Skipped logged BlitzCache data as NULL was passed to parameter @OutputTableNameBlitzCache'; + END + ELSE + BEGIN + RAISERROR('Table provided for BlitzCache data: %s does not exist',10,0,@FullOutputTableNameBlitzCache); + SELECT N'No BlitzCache data available as the table cannot be found'; + END +END +ELSE /* Table exists then run the query */ +BEGIN + EXEC sp_executesql @Sql, + N'@Servername NVARCHAR(128), + @Databasename NVARCHAR(128), + @BlitzCacheSortorder NVARCHAR(20), + @StartDate DATETIMEOFFSET(7), + @EndDate DATETIMEOFFSET(7)', + @Servername = @Servername, + @Databasename = @Databasename, + @BlitzCacheSortorder = @BlitzCacheSortorder, + @StartDate = @StartDate, + @EndDate = @EndDate; +END - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 233 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 233) WITH NOWAIT; - +/* BlitzWho data */ +SET @Sql = N' +SELECT [ServerName] + ,[CheckDate] + ,[elapsed_time] + ,[session_id] + ,[database_name] + ,[query_text_snippet] + ,[query_plan] + ,[live_query_plan] + ,[query_cost] + ,[status] + ,[wait_info] + ,[top_session_waits] + ,[blocking_session_id] + ,[open_transaction_count] + ,[is_implicit_transaction] + ,[nt_domain] + ,[host_name] + ,[login_name] + ,[nt_user_name] + ,[program_name] + ,[fix_parameter_sniffing] + ,[client_interface_name] + ,[login_time] + ,[start_time] + ,[request_time] + ,[request_cpu_time] + ,[degree_of_parallelism] + ,[request_logical_reads] + ,[Logical_Reads_MB] + ,[request_writes] + ,[Logical_Writes_MB] + ,[request_physical_reads] + ,[Physical_reads_MB] + ,[session_cpu] + ,[session_logical_reads] + ,[session_logical_reads_MB] + ,[session_physical_reads] + ,[session_physical_reads_MB] + ,[session_writes] + ,[session_writes_MB] + ,[tempdb_allocations_mb] + ,[memory_usage] + ,[estimated_completion_time] + ,[percent_complete] + ,[deadlock_priority] + ,[transaction_isolation_level] + ,[last_dop] + ,[min_dop] + ,[max_dop] + ,[last_grant_kb] + ,[min_grant_kb] + ,[max_grant_kb] + ,[last_used_grant_kb] + ,[min_used_grant_kb] + ,[max_used_grant_kb] + ,[last_ideal_grant_kb] + ,[min_ideal_grant_kb] + ,[max_ideal_grant_kb] + ,[last_reserved_threads] + ,[min_reserved_threads] + ,[max_reserved_threads] + ,[last_used_threads] + ,[min_used_threads] + ,[max_used_threads] + ,[grant_time] + ,[requested_memory_kb] + ,[grant_memory_kb] + ,[is_request_granted] + ,[required_memory_kb] + ,[query_memory_grant_used_memory_kb] + ,[ideal_memory_kb] + ,[is_small] + ,[timeout_sec] + ,[resource_semaphore_id] + ,[wait_order] + ,[wait_time_ms] + ,[next_candidate_for_memory_grant] + ,[target_memory_kb] + ,[max_target_memory_kb] + ,[total_memory_kb] + ,[available_memory_kb] + ,[granted_memory_kb] + ,[query_resource_semaphore_used_memory_kb] + ,[grantee_count] + ,[waiter_count] + ,[timeout_error_count] + ,[forced_grant_count] + ,[workload_group_name] + ,[resource_pool_name] + ,[context_info] + ,[query_hash] + ,[query_plan_hash] + ,[sql_handle] + ,[plan_handle] + ,[statement_start_offset] + ,[statement_end_offset] + FROM '+@FullOutputTableNameBlitzWho+N' + WHERE [ServerName] = @Servername + AND ([CheckDate] BETWEEN @StartDate AND @EndDate OR [start_time] BETWEEN CAST(@StartDate AS DATETIME) AND CAST(@EndDate AS DATETIME)) + ' + +CASE + WHEN @Databasename IS NOT NULL THEN N'AND [database_name] = @Databasename + ' + ELSE N'' + END ++N'ORDER BY [CheckDate] ASC + OPTION (RECOMPILE, MAXDOP '+CAST(@Maxdop AS NVARCHAR(2))+N');'; - IF EXISTS (SELECT * FROM sys.all_columns WHERE object_id = OBJECT_ID('sys.dm_os_memory_clerks') AND name = 'pages_kb') - BEGIN - /* SQL 2012+ version */ - SET @StringToExecute = N' - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 233 AS CheckID, - 50 AS Priority, - ''Performance'' AS FindingsGroup, - ''Memory Leak in USERSTORE_TOKENPERM Cache'' AS Finding, - ''https://www.BrentOzar.com/go/userstore'' AS URL, - N''UserStore_TokenPerm clerk is using '' + CAST(CAST(SUM(CASE WHEN type = ''USERSTORE_TOKENPERM'' AND name = ''TokenAndPermUserStore'' THEN pages_kb * 1.0 ELSE 0.0 END) / 1024.0 / 1024.0 AS INT) AS NVARCHAR(100)) - + N''GB RAM, total buffer pool is '' + CAST(CAST(SUM(pages_kb) / 1024.0 / 1024.0 AS INT) AS NVARCHAR(100)) + N''GB.'' - AS details - FROM sys.dm_os_memory_clerks - HAVING SUM(CASE WHEN type = ''USERSTORE_TOKENPERM'' AND name = ''TokenAndPermUserStore'' THEN pages_kb * 1.0 ELSE 0.0 END) / SUM(pages_kb) >= 0.1 - AND SUM(pages_kb) / 1024.0 / 1024.0 >= 1; /* At least 1GB RAM overall */'; - EXEC sp_executesql @StringToExecute; - END - ELSE - BEGIN - /* Antiques Roadshow SQL 2008R2 - version */ - SET @StringToExecute = N' - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 233 AS CheckID, - 50 AS Priority, - ''Performance'' AS FindingsGroup, - ''Memory Leak in USERSTORE_TOKENPERM Cache'' AS Finding, - ''https://www.BrentOzar.com/go/userstore'' AS URL, - N''UserStore_TokenPerm clerk is using '' + CAST(CAST(SUM(CASE WHEN type = ''USERSTORE_TOKENPERM'' AND name = ''TokenAndPermUserStore'' THEN single_pages_kb + multi_pages_kb * 1.0 ELSE 0.0 END) / 1024.0 / 1024.0 AS INT) AS NVARCHAR(100)) - + N''GB RAM, total buffer pool is '' + CAST(CAST(SUM(single_pages_kb + multi_pages_kb) / 1024.0 / 1024.0 AS INT) AS NVARCHAR(100)) + N''GB.'' - AS details - FROM sys.dm_os_memory_clerks - HAVING SUM(CASE WHEN type = ''USERSTORE_TOKENPERM'' AND name = ''TokenAndPermUserStore'' THEN single_pages_kb + multi_pages_kb * 1.0 ELSE 0.0 END) / SUM(single_pages_kb + multi_pages_kb) >= 0.1 - AND SUM(single_pages_kb + multi_pages_kb) / 1024.0 / 1024.0 >= 1; /* At least 1GB RAM overall */'; - EXEC sp_executesql @StringToExecute; - END +RAISERROR('Getting BlitzWho info from %s',0,0,@FullOutputTableNameBlitzWho) WITH NOWAIT; - END; +IF (@Debug = 1) +BEGIN + PRINT @Sql; +END +IF (OBJECT_ID(@FullOutputTableNameBlitzWho) IS NULL) +BEGIN + IF (@OutputTableNameBlitzWho IS NULL) + BEGIN + RAISERROR('BlitzWho data skipped',10,0); + SELECT N'Skipped logged BlitzWho data as NULL was passed to parameter @OutputTableNameBlitzWho'; + END + ELSE + BEGIN + RAISERROR('Table provided for BlitzWho data: %s does not exist',10,0,@FullOutputTableNameBlitzWho); + SELECT N'No BlitzWho data available as the table cannot be found'; + END +END +ELSE +BEGIN + EXEC sp_executesql @Sql, + N'@StartDate DATETIMEOFFSET(7), + @EndDate DATETIMEOFFSET(7), + @Servername NVARCHAR(128), + @Databasename NVARCHAR(128)', + @StartDate=@StartDate, + @EndDate=@EndDate, + @Servername=@Servername, + @Databasename = @Databasename; +END +GO +IF OBJECT_ID('dbo.sp_BlitzBackups') IS NULL + EXEC ('CREATE PROCEDURE dbo.sp_BlitzBackups AS RETURN 0;'); +GO +ALTER PROCEDURE [dbo].[sp_BlitzBackups] + @Help TINYINT = 0 , + @HoursBack INT = 168, + @MSDBName NVARCHAR(256) = 'msdb', + @AGName NVARCHAR(256) = NULL, + @RestoreSpeedFullMBps INT = NULL, + @RestoreSpeedDiffMBps INT = NULL, + @RestoreSpeedLogMBps INT = NULL, + @Debug TINYINT = 0, + @PushBackupHistoryToListener BIT = 0, + @WriteBackupsToListenerName NVARCHAR(256) = NULL, + @WriteBackupsToDatabaseName NVARCHAR(256) = NULL, + @WriteBackupsLastHours INT = 168, + @Version VARCHAR(30) = NULL OUTPUT, + @VersionDate DATETIME = NULL OUTPUT, + @VersionCheckMode BIT = 0 +WITH RECOMPILE +AS + BEGIN + SET NOCOUNT ON; + SET STATISTICS XML OFF; + SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + + SELECT @Version = '8.19', @VersionDate = '20240222'; + + IF(@VersionCheckMode = 1) + BEGIN + RETURN; + END; + IF @Help = 1 PRINT ' + /* + sp_BlitzBackups from http://FirstResponderKit.org + + This script checks your backups to see how much data you might lose when + this server fails, and how long it might take to recover. - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 234 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 234) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - DatabaseName , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 234 AS CheckID, - 100 AS Priority, - db_name(f.database_id) AS DatabaseName, - 'Reliability' AS FindingsGroup, - 'SQL Server Update May Fail' AS Finding, - 'https://desertdba.com/failovers-cant-serve-two-masters/' AS URL, - 'This database has a file with a logical name of ''master'', which can break SQL Server updates. Rename it in SSMS by right-clicking on the database, go into Properties, and rename the file. Takes effect instantly.' AS details - FROM master.sys.master_files f - WHERE (f.name = N'master') - AND f.database_id > 4 - AND db_name(f.database_id) <> 'master'; /* Thanks Michaels3 for catching this */ - END; + To learn more, visit http://FirstResponderKit.org where you can download new + versions for free, watch training videos on how it works, get more info on + the findings, contribute your own code, and more. + Known limitations of this version: + - Only Microsoft-supported versions of SQL Server. Sorry, 2005 and 2000. + Unknown limitations of this version: + - None. (If we knew them, they would be known. Duh.) + Changes - for the full list of improvements and fixes in this version, see: + https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/ - IF @CheckUserDatabaseObjects = 1 - BEGIN - IF @Debug IN (1, 2) RAISERROR('Starting @CheckUserDatabaseObjects section.', 0, 1) WITH NOWAIT + Parameter explanations: - /* - But what if you need to run a query in every individual database? - Check out CheckID 99 below. Yes, it uses sp_MSforeachdb, and no, - we're not happy about that. sp_MSforeachdb is known to have a lot - of issues, like skipping databases sometimes. However, this is the - only built-in option that we have. If you're writing your own code - for database maintenance, consider Aaron Bertrand's alternative: - http://www.mssqltips.com/sqlservertip/2201/making-a-more-reliable-and-flexible-spmsforeachdb/ - We don't include that as part of sp_Blitz, of course, because - copying and distributing copyrighted code from others without their - written permission isn't a good idea. - */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 99 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 99) WITH NOWAIT; - - EXEC dbo.sp_MSforeachdb 'USE [?]; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; IF EXISTS (SELECT * FROM sys.tables WITH (NOLOCK) WHERE name = ''sysmergepublications'' ) IF EXISTS ( SELECT * FROM sysmergepublications WITH (NOLOCK) WHERE retention = 0) INSERT INTO #BlitzResults (CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details) SELECT DISTINCT 99, DB_NAME(), 110, ''Performance'', ''Infinite merge replication metadata retention period'', ''https://www.brentozar.com/go/merge'', (''The ['' + DB_NAME() + ''] database has merge replication metadata retention period set to infinite - this can be the case of significant performance issues.'')'; - END; - /* - Note that by using sp_MSforeachdb, we're running the query in all - databases. We're not checking #SkipChecks here for each database to - see if we should run the check in this database. That means we may - still run a skipped check if it involves sp_MSforeachdb. We just - don't output those results in the last step. - */ + @HoursBack INT = 168 How many hours of history to examine, back from now. + You can check just the last 24 hours of backups, for example. + @MSDBName NVARCHAR(255) You can restore MSDB from different servers and check them + centrally. Also useful if you create a DBA utility database + and merge data from several servers in an AG into one DB. + @RestoreSpeedFullMBps INT By default, we use the backup speed from MSDB to guesstimate + how fast your restores will go. If you have done performance + tuning and testing of your backups (or if they horribly go even + slower in your DR environment, and you want to account for + that), then you can pass in different numbers here. + @RestoreSpeedDiffMBps INT See above. + @RestoreSpeedLogMBps INT See above. - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 163 ) - AND EXISTS(SELECT * FROM sys.all_objects WHERE name = 'database_query_store_options') - BEGIN - /* --TOURSTOP03-- */ + For more documentation: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/ - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 163) WITH NOWAIT; + MIT License + + Copyright (c) Brent Ozar Unlimited - EXEC dbo.sp_MSforeachdb 'USE [?]; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - INSERT INTO #BlitzResults - (CheckID, - DatabaseName, - Priority, - FindingsGroup, - Finding, - URL, - Details) - SELECT TOP 1 163, - N''?'', - 200, - ''Performance'', - ''Query Store Disabled'', - ''https://www.brentozar.com/go/querystore'', - (''The new SQL Server 2016 Query Store feature has not been enabled on this database.'') - FROM [?].sys.database_query_store_options WHERE desired_state = 0 - AND N''?'' NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''DWConfiguration'', ''DWDiagnostics'', ''DWQueue'', ''ReportServer'', ''ReportServerTempDB'') OPTION (RECOMPILE)'; - END; + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: - - IF @ProductVersionMajor = 13 AND @ProductVersionMinor < 2149 --2016 CU1 has the fix in it - AND NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 182 ) - AND CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) NOT LIKE '%Enterprise%' - AND CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) NOT LIKE '%Developer%' - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 182) WITH NOWAIT; - - SET @StringToExecute = 'INSERT INTO #BlitzResults - (CheckID, - DatabaseName, - Priority, - FindingsGroup, - Finding, - URL, - Details) - SELECT TOP 1 - 182, - ''Server'', - 20, - ''Reliability'', - ''Query Store Cleanup Disabled'', - ''https://www.brentozar.com/go/cleanup'', - (''SQL 2016 RTM has a bug involving dumps that happen every time Query Store cleanup jobs run. This is fixed in CU1 and later: https://sqlserverupdates.com/sql-server-2016-updates/'') - FROM sys.databases AS d - WHERE d.is_query_store_on = 1 OPTION (RECOMPILE);'; - - IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; - IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; - - EXECUTE(@StringToExecute); - END; + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 235 ) - AND EXISTS(SELECT * FROM sys.all_objects WHERE name = 'database_query_store_options') - BEGIN + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 235) WITH NOWAIT; - EXEC dbo.sp_MSforeachdb 'USE [?]; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - INSERT INTO #BlitzResults - (CheckID, - DatabaseName, - Priority, - FindingsGroup, - Finding, - URL, - Details) - SELECT TOP 1 235, - N''?'', - 150, - ''Performance'', - ''Inconsistent Query Store metadata'', - '''', - (''Query store state in master metadata and database specific metadata not in sync.'') - FROM [?].sys.database_query_store_options dqso - join master.sys.databases D on D.name = N''?'' - WHERE ((dqso.actual_state = 0 AND D.is_query_store_on = 1) OR (dqso.actual_state <> 0 AND D.is_query_store_on = 0)) - AND N''?'' NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''DWConfiguration'', ''DWDiagnostics'', ''DWQueue'', ''ReportServer'', ''ReportServerTempDB'') OPTION (RECOMPILE)'; - END; - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 41 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 41) WITH NOWAIT; - - EXEC dbo.sp_MSforeachdb 'use [?]; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - INSERT INTO #BlitzResults - (CheckID, - DatabaseName, - Priority, - FindingsGroup, - Finding, - URL, - Details) - SELECT 41, - N''?'', - 170, - ''File Configuration'', - ''Multiple Log Files on One Drive'', - ''https://www.brentozar.com/go/manylogs'', - (''The ['' + DB_NAME() + ''] database has multiple log files on the '' + LEFT(physical_name, 1) + '' drive. This is not a performance booster because log file access is sequential, not parallel.'') - FROM [?].sys.database_files WHERE type_desc = ''LOG'' - AND N''?'' <> ''[tempdb]'' - GROUP BY LEFT(physical_name, 1) - HAVING COUNT(*) > 1 - AND SUM(size) < 268435456 OPTION (RECOMPILE);'; - END; - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 42 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 42) WITH NOWAIT; - - EXEC dbo.sp_MSforeachdb 'use [?]; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - INSERT INTO #BlitzResults - (CheckID, - DatabaseName, - Priority, - FindingsGroup, - Finding, - URL, - Details) - SELECT DISTINCT 42, - N''?'', - 170, - ''File Configuration'', - ''Uneven File Growth Settings in One Filegroup'', - ''https://www.brentozar.com/go/grow'', - (''The ['' + DB_NAME() + ''] database has multiple data files in one filegroup, but they are not all set up to grow in identical amounts. This can lead to uneven file activity inside the filegroup.'') - FROM [?].sys.database_files - WHERE type_desc = ''ROWS'' - GROUP BY data_space_id - HAVING COUNT(DISTINCT growth) > 1 OR COUNT(DISTINCT is_percent_growth) > 1 OPTION (RECOMPILE);'; - END; + */'; +ELSE +BEGIN +DECLARE @StringToExecute NVARCHAR(MAX) = N'', + @InnerStringToExecute NVARCHAR(MAX) = N'', + @ProductVersion NVARCHAR(128), + @ProductVersionMajor DECIMAL(10, 2), + @ProductVersionMinor DECIMAL(10, 2), + @StartTime DATETIME2, @ResultText NVARCHAR(MAX), + @crlf NVARCHAR(2), + @MoreInfoHeader NVARCHAR(100), + @MoreInfoFooter NVARCHAR(100); - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 82 ) - BEGIN +IF @HoursBack > 0 + SET @HoursBack = @HoursBack * -1; - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 82) WITH NOWAIT; +IF @WriteBackupsLastHours > 0 + SET @WriteBackupsLastHours = @WriteBackupsLastHours * -1; - EXEC sp_MSforeachdb 'use [?]; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - INSERT INTO #BlitzResults - (CheckID, - DatabaseName, - Priority, - FindingsGroup, - Finding, - URL, Details) - SELECT DISTINCT 82 AS CheckID, - N''?'' as DatabaseName, - 170 AS Priority, - ''File Configuration'' AS FindingsGroup, - ''File growth set to percent'', - ''https://www.brentozar.com/go/percentgrowth'' AS URL, - ''The ['' + DB_NAME() + ''] database file '' + f.physical_name + '' has grown to '' + CONVERT(NVARCHAR(10), CONVERT(NUMERIC(38, 2), (f.size / 128.) / 1024.)) + '' GB, and is using percent filegrowth settings. This can lead to slow performance during growths if Instant File Initialization is not enabled.'' - FROM [?].sys.database_files f - WHERE is_percent_growth = 1 and size > 128000 OPTION (RECOMPILE);'; - END; - - /* addition by Henrik Staun Poulsen, Stovi Software */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 158 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 158) WITH NOWAIT; +SELECT @crlf = NCHAR(13) + NCHAR(10), + @StartTime = DATEADD(hh, @HoursBack, GETDATE()), + @MoreInfoHeader = N''; - EXEC sp_MSforeachdb 'use [?]; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - INSERT INTO #BlitzResults - (CheckID, - DatabaseName, - Priority, - FindingsGroup, - Finding, - URL, Details) - SELECT DISTINCT 158 AS CheckID, - N''?'' as DatabaseName, - 170 AS Priority, - ''File Configuration'' AS FindingsGroup, - ''File growth set to 1MB'', - ''https://www.brentozar.com/go/percentgrowth'' AS URL, - ''The ['' + DB_NAME() + ''] database file '' + f.physical_name + '' is using 1MB filegrowth settings, but it has grown to '' + CAST((CAST(f.size AS BIGINT) * 8 / 1000000) AS NVARCHAR(10)) + '' GB. Time to up the growth amount.'' - FROM [?].sys.database_files f - WHERE is_percent_growth = 0 and growth=128 and size > 128000 OPTION (RECOMPILE);'; - END; +SET @ProductVersion = CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)); +SELECT @ProductVersionMajor = SUBSTRING(@ProductVersion, 1, CHARINDEX('.', @ProductVersion) + 1), + @ProductVersionMinor = PARSENAME(CONVERT(VARCHAR(32), @ProductVersion), 2); - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 33 ) - BEGIN - IF @@VERSION NOT LIKE '%Microsoft SQL Server 2000%' - AND @@VERSION NOT LIKE '%Microsoft SQL Server 2005%' - AND @SkipBlockingChecks = 0 - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 33) WITH NOWAIT; - - EXEC dbo.sp_MSforeachdb 'USE [?]; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - INSERT INTO #BlitzResults - (CheckID, - DatabaseName, - Priority, - FindingsGroup, - Finding, - URL, - Details) - SELECT DISTINCT 33, - db_name(), - 200, - ''Licensing'', - ''Enterprise Edition Features In Use'', - ''https://www.brentozar.com/go/ee'', - (''The ['' + DB_NAME() + ''] database is using '' + feature_name + ''. If this database is restored onto a Standard Edition server, the restore will fail on versions prior to 2016 SP1.'') - FROM [?].sys.dm_db_persisted_sku_features OPTION (RECOMPILE);'; - END; - END; +CREATE TABLE #Backups +( + id INT IDENTITY(1, 1), + database_name NVARCHAR(128), + database_guid UNIQUEIDENTIFIER, + RPOWorstCaseMinutes DECIMAL(18, 1), + RTOWorstCaseMinutes DECIMAL(18, 1), + RPOWorstCaseBackupSetID INT, + RPOWorstCaseBackupSetFinishTime DATETIME, + RPOWorstCaseBackupSetIDPrior INT, + RPOWorstCaseBackupSetPriorFinishTime DATETIME, + RPOWorstCaseMoreInfoQuery XML, + RTOWorstCaseBackupFileSizeMB DECIMAL(18, 2), + RTOWorstCaseMoreInfoQuery XML, + FullMBpsAvg DECIMAL(18, 2), + FullMBpsMin DECIMAL(18, 2), + FullMBpsMax DECIMAL(18, 2), + FullSizeMBAvg DECIMAL(18, 2), + FullSizeMBMin DECIMAL(18, 2), + FullSizeMBMax DECIMAL(18, 2), + FullCompressedSizeMBAvg DECIMAL(18, 2), + FullCompressedSizeMBMin DECIMAL(18, 2), + FullCompressedSizeMBMax DECIMAL(18, 2), + DiffMBpsAvg DECIMAL(18, 2), + DiffMBpsMin DECIMAL(18, 2), + DiffMBpsMax DECIMAL(18, 2), + DiffSizeMBAvg DECIMAL(18, 2), + DiffSizeMBMin DECIMAL(18, 2), + DiffSizeMBMax DECIMAL(18, 2), + DiffCompressedSizeMBAvg DECIMAL(18, 2), + DiffCompressedSizeMBMin DECIMAL(18, 2), + DiffCompressedSizeMBMax DECIMAL(18, 2), + LogMBpsAvg DECIMAL(18, 2), + LogMBpsMin DECIMAL(18, 2), + LogMBpsMax DECIMAL(18, 2), + LogSizeMBAvg DECIMAL(18, 2), + LogSizeMBMin DECIMAL(18, 2), + LogSizeMBMax DECIMAL(18, 2), + LogCompressedSizeMBAvg DECIMAL(18, 2), + LogCompressedSizeMBMin DECIMAL(18, 2), + LogCompressedSizeMBMax DECIMAL(18, 2) +); - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 19 ) - BEGIN - /* Method 1: Check sys.databases parameters */ - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 19) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) +CREATE TABLE #RTORecoveryPoints +( + id INT IDENTITY(1, 1), + database_name NVARCHAR(128), + database_guid UNIQUEIDENTIFIER, + rto_worst_case_size_mb AS + ( COALESCE(log_file_size_mb, 0) + COALESCE(diff_file_size_mb, 0) + COALESCE(full_file_size_mb, 0)), + rto_worst_case_time_seconds AS + ( COALESCE(log_time_seconds, 0) + COALESCE(diff_time_seconds, 0) + COALESCE(full_time_seconds, 0)), + full_backup_set_id INT, + full_last_lsn NUMERIC(25, 0), + full_backup_set_uuid UNIQUEIDENTIFIER, + full_time_seconds BIGINT, + full_file_size_mb DECIMAL(18, 2), + diff_backup_set_id INT, + diff_last_lsn NUMERIC(25, 0), + diff_time_seconds BIGINT, + diff_file_size_mb DECIMAL(18, 2), + log_backup_set_id INT, + log_last_lsn NUMERIC(25, 0), + log_time_seconds BIGINT, + log_file_size_mb DECIMAL(18, 2), + log_backups INT +); - SELECT 19 AS CheckID , - [name] AS DatabaseName , - 200 AS Priority , - 'Informational' AS FindingsGroup , - 'Replication In Use' AS Finding , - 'https://www.brentozar.com/go/repl' AS URL , - ( 'Database [' + [name] - + '] is a replication publisher, subscriber, or distributor.' ) AS Details - FROM sys.databases - WHERE name NOT IN ( SELECT DISTINCT - DatabaseName - FROM #SkipChecks - WHERE CheckID IS NULL OR CheckID = 19) - AND (is_published = 1 - OR is_subscribed = 1 - OR is_merge_published = 1 - OR is_distributor = 1); +CREATE TABLE #Recoverability + ( + Id INT IDENTITY , + DatabaseName NVARCHAR(128), + DatabaseGUID UNIQUEIDENTIFIER, + LastBackupRecoveryModel NVARCHAR(60), + FirstFullBackupSizeMB DECIMAL (18,2), + FirstFullBackupDate DATETIME, + LastFullBackupSizeMB DECIMAL (18,2), + LastFullBackupDate DATETIME, + AvgFullBackupThroughputMB DECIMAL (18,2), + AvgFullBackupDurationSeconds INT, + AvgDiffBackupThroughputMB DECIMAL (18,2), + AvgDiffBackupDurationSeconds INT, + AvgLogBackupThroughputMB DECIMAL (18,2), + AvgLogBackupDurationSeconds INT, + AvgFullSizeMB DECIMAL (18,2), + AvgDiffSizeMB DECIMAL (18,2), + AvgLogSizeMB DECIMAL (18,2), + IsBigDiff AS CASE WHEN (AvgFullSizeMB > 10240. AND ((AvgDiffSizeMB * 100.) / AvgFullSizeMB >= 40.)) THEN 1 ELSE 0 END, + IsBigLog AS CASE WHEN (AvgFullSizeMB > 10240. AND ((AvgLogSizeMB * 100.) / AvgFullSizeMB >= 20.)) THEN 1 ELSE 0 END + ); - /* Method B: check subscribers for MSreplication_objects tables */ - EXEC dbo.sp_MSforeachdb 'USE [?]; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - INSERT INTO #BlitzResults - (CheckID, - DatabaseName, - Priority, - FindingsGroup, - Finding, - URL, - Details) - SELECT DISTINCT 19, - db_name(), - 200, - ''Informational'', - ''Replication In Use'', - ''https://www.brentozar.com/go/repl'', - (''['' + DB_NAME() + ''] has MSreplication_objects tables in it, indicating it is a replication subscriber.'') - FROM [?].sys.tables - WHERE name = ''dbo.MSreplication_objects'' AND ''?'' <> ''master'' OPTION (RECOMPILE)'; +CREATE TABLE #Trending +( + DatabaseName NVARCHAR(128), + DatabaseGUID UNIQUEIDENTIFIER, + [0] DECIMAL(18, 2), + [-1] DECIMAL(18, 2), + [-2] DECIMAL(18, 2), + [-3] DECIMAL(18, 2), + [-4] DECIMAL(18, 2), + [-5] DECIMAL(18, 2), + [-6] DECIMAL(18, 2), + [-7] DECIMAL(18, 2), + [-8] DECIMAL(18, 2), + [-9] DECIMAL(18, 2), + [-10] DECIMAL(18, 2), + [-11] DECIMAL(18, 2), + [-12] DECIMAL(18, 2) +); - END; - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 32 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 32) WITH NOWAIT; - - EXEC dbo.sp_MSforeachdb 'USE [?]; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - INSERT INTO #BlitzResults - (CheckID, - DatabaseName, - Priority, - FindingsGroup, - Finding, - URL, - Details) - SELECT 32, - N''?'', - 150, - ''Performance'', - ''Triggers on Tables'', - ''https://www.brentozar.com/go/trig'', - (''The ['' + DB_NAME() + ''] database has '' + CAST(SUM(1) AS NVARCHAR(50)) + '' triggers.'') - FROM [?].sys.triggers t INNER JOIN [?].sys.objects o ON t.parent_id = o.object_id - INNER JOIN [?].sys.schemas s ON o.schema_id = s.schema_id WHERE t.is_ms_shipped = 0 AND DB_NAME() != ''ReportServer'' - HAVING SUM(1) > 0 OPTION (RECOMPILE)'; - END; +CREATE TABLE #Warnings +( + Id INT IDENTITY(1, 1) PRIMARY KEY CLUSTERED, + CheckId INT, + Priority INT, + DatabaseName VARCHAR(128), + Finding VARCHAR(256), + Warning VARCHAR(8000) +); - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 164 ) - AND EXISTS(SELECT * FROM sys.all_objects WHERE name = 'fn_validate_plan_guide') - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 164) WITH NOWAIT; - - EXEC dbo.sp_MSforeachdb 'USE [?]; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SET QUOTED_IDENTIFIER ON; - INSERT INTO #BlitzResults - (CheckID, - DatabaseName, - Priority, - FindingsGroup, - Finding, - URL, - Details) - SELECT DISTINCT 164, - N''?'', - 100, - ''Reliability'', - ''Plan Guides Failing'', - ''https://www.brentozar.com/go/misguided'', - (''The ['' + DB_NAME() + ''] database has plan guides that are no longer valid, so the queries involved may be failing silently.'') - FROM [?].sys.plan_guides g CROSS APPLY fn_validate_plan_guide(g.plan_guide_id) OPTION (RECOMPILE)'; - END; +IF NOT EXISTS(SELECT * FROM sys.databases WHERE name = @MSDBName) + BEGIN + RAISERROR('@MSDBName was specified, but the database does not exist.', 16, 1) WITH NOWAIT; + RETURN; + END - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 46 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 46) WITH NOWAIT; - - EXEC dbo.sp_MSforeachdb 'USE [?]; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - INSERT INTO #BlitzResults - (CheckID, - DatabaseName, - Priority, - FindingsGroup, - Finding, - URL, - Details) - SELECT 46, - N''?'', - 150, - ''Performance'', - ''Leftover Fake Indexes From Wizards'', - ''https://www.brentozar.com/go/hypo'', - (''The index ['' + DB_NAME() + ''].['' + s.name + ''].['' + o.name + ''].['' + i.name + ''] is a leftover hypothetical index from the Index Tuning Wizard or Database Tuning Advisor. This index is not actually helping performance and should be removed.'') - from [?].sys.indexes i INNER JOIN [?].sys.objects o ON i.object_id = o.object_id INNER JOIN [?].sys.schemas s ON o.schema_id = s.schema_id - WHERE i.is_hypothetical = 1 OPTION (RECOMPILE);'; - END; +IF @PushBackupHistoryToListener = 1 +GOTO PushBackupHistoryToListener - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 47 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 47) WITH NOWAIT; - - EXEC dbo.sp_MSforeachdb 'USE [?]; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - INSERT INTO #BlitzResults - (CheckID, - DatabaseName, - Priority, - FindingsGroup, - Finding, - URL, - Details) - SELECT 47, - N''?'', - 100, - ''Performance'', - ''Indexes Disabled'', - ''https://www.brentozar.com/go/ixoff'', - (''The index ['' + DB_NAME() + ''].['' + s.name + ''].['' + o.name + ''].['' + i.name + ''] is disabled. This index is not actually helping performance and should either be enabled or removed.'') - from [?].sys.indexes i INNER JOIN [?].sys.objects o ON i.object_id = o.object_id INNER JOIN [?].sys.schemas s ON o.schema_id = s.schema_id - WHERE i.is_disabled = 1 OPTION (RECOMPILE);'; - END; - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 48 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 48) WITH NOWAIT; - - EXEC dbo.sp_MSforeachdb 'USE [?]; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - INSERT INTO #BlitzResults - (CheckID, - DatabaseName, - Priority, - FindingsGroup, - Finding, - URL, - Details) - SELECT DISTINCT 48, - N''?'', - 150, - ''Performance'', - ''Foreign Keys Not Trusted'', - ''https://www.brentozar.com/go/trust'', - (''The ['' + DB_NAME() + ''] database has foreign keys that were probably disabled, data was changed, and then the key was enabled again. Simply enabling the key is not enough for the optimizer to use this key - we have to alter the table using the WITH CHECK CHECK CONSTRAINT parameter.'') - from [?].sys.foreign_keys i INNER JOIN [?].sys.objects o ON i.parent_object_id = o.object_id INNER JOIN [?].sys.schemas s ON o.schema_id = s.schema_id - WHERE i.is_not_trusted = 1 AND i.is_not_for_replication = 0 AND i.is_disabled = 0 AND N''?'' NOT IN (''master'', ''model'', ''msdb'', ''ReportServer'', ''ReportServerTempDB'') OPTION (RECOMPILE);'; - END; + RAISERROR('Inserting to #Backups', 0, 1) WITH NOWAIT; - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 56 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 56) WITH NOWAIT; - - EXEC dbo.sp_MSforeachdb 'USE [?]; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - INSERT INTO #BlitzResults - (CheckID, - DatabaseName, - Priority, - FindingsGroup, - Finding, - URL, - Details) - SELECT 56, - N''?'', - 150, - ''Performance'', - ''Check Constraint Not Trusted'', - ''https://www.brentozar.com/go/trust'', - (''The check constraint ['' + DB_NAME() + ''].['' + s.name + ''].['' + o.name + ''].['' + i.name + ''] is not trusted - meaning, it was disabled, data was changed, and then the constraint was enabled again. Simply enabling the constraint is not enough for the optimizer to use this constraint - we have to alter the table using the WITH CHECK CHECK CONSTRAINT parameter.'') - from [?].sys.check_constraints i INNER JOIN [?].sys.objects o ON i.parent_object_id = o.object_id - INNER JOIN [?].sys.schemas s ON o.schema_id = s.schema_id - WHERE i.is_not_trusted = 1 AND i.is_not_for_replication = 0 AND i.is_disabled = 0 OPTION (RECOMPILE);'; - END; + SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 95 ) - BEGIN - IF @@VERSION NOT LIKE '%Microsoft SQL Server 2000%' - AND @@VERSION NOT LIKE '%Microsoft SQL Server 2005%' - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 95) WITH NOWAIT; - - EXEC dbo.sp_MSforeachdb 'USE [?]; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - INSERT INTO #BlitzResults - (CheckID, - DatabaseName, - Priority, - FindingsGroup, - Finding, - URL, - Details) - SELECT TOP 1 95 AS CheckID, - N''?'' as DatabaseName, - 110 AS Priority, - ''Performance'' AS FindingsGroup, - ''Plan Guides Enabled'' AS Finding, - ''https://www.brentozar.com/go/guides'' AS URL, - (''Database ['' + DB_NAME() + ''] has query plan guides so a query will always get a specific execution plan. If you are having trouble getting query performance to improve, it might be due to a frozen plan. Review the DMV sys.plan_guides to learn more about the plan guides in place on this server.'') AS Details - FROM [?].sys.plan_guides WHERE is_disabled = 0 OPTION (RECOMPILE);'; - END; - END; + SET @StringToExecute += N'WITH Backups AS (SELECT bs.database_name, bs.database_guid, bs.type AS backup_type ' + @crlf + + ' , MBpsAvg = CAST(AVG(( bs.backup_size / ( CASE WHEN DATEDIFF(ss, bs.backup_start_date, bs.backup_finish_date) = 0 THEN 1 ELSE DATEDIFF(ss, bs.backup_start_date, bs.backup_finish_date) END ) / 1048576 )) AS INT) ' + @crlf + + ' , MBpsMin = CAST(MIN(( bs.backup_size / ( CASE WHEN DATEDIFF(ss, bs.backup_start_date, bs.backup_finish_date) = 0 THEN 1 ELSE DATEDIFF(ss, bs.backup_start_date, bs.backup_finish_date) END ) / 1048576 )) AS INT) ' + @crlf + + ' , MBpsMax = CAST(MAX(( bs.backup_size / ( CASE WHEN DATEDIFF(ss, bs.backup_start_date, bs.backup_finish_date) = 0 THEN 1 ELSE DATEDIFF(ss, bs.backup_start_date, bs.backup_finish_date) END ) / 1048576 )) AS INT) ' + @crlf + + ' , SizeMBAvg = AVG(backup_size / 1048576.0) ' + @crlf + + ' , SizeMBMin = MIN(backup_size / 1048576.0) ' + @crlf + + ' , SizeMBMax = MAX(backup_size / 1048576.0) ' + @crlf + + ' , CompressedSizeMBAvg = AVG(compressed_backup_size / 1048576.0) ' + @crlf + + ' , CompressedSizeMBMin = MIN(compressed_backup_size / 1048576.0) ' + @crlf + + ' , CompressedSizeMBMax = MAX(compressed_backup_size / 1048576.0) ' + @crlf; - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 60 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 60) WITH NOWAIT; - - EXEC sp_MSforeachdb 'USE [?]; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - INSERT INTO #BlitzResults - (CheckID, - DatabaseName, - Priority, - FindingsGroup, - Finding, - URL, - Details) - SELECT 60 AS CheckID, - N''?'' as DatabaseName, - 100 AS Priority, - ''Performance'' AS FindingsGroup, - ''Fill Factor Changed'', - ''https://www.brentozar.com/go/fillfactor'' AS URL, - ''The ['' + DB_NAME() + ''] database has '' + CAST(SUM(1) AS NVARCHAR(50)) + '' objects with fill factor = '' + CAST(fill_factor AS NVARCHAR(5)) + ''%. This can cause memory and storage performance problems, but may also prevent page splits.'' - FROM [?].sys.indexes - WHERE fill_factor <> 0 AND fill_factor < 80 AND is_disabled = 0 AND is_hypothetical = 0 - GROUP BY fill_factor OPTION (RECOMPILE);'; - END; - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 78 ) - BEGIN + SET @StringToExecute += N' FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset bs ' + @crlf + + N' WHERE bs.backup_finish_date >= @StartTime AND bs.is_damaged = 0 ' + @crlf + + N' GROUP BY bs.database_name, bs.database_guid, bs.type)' + @crlf; - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 78) WITH NOWAIT; - - EXECUTE master.sys.sp_MSforeachdb 'USE [?]; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - INSERT INTO #Recompile - SELECT DISTINCT DBName = DB_Name(), SPName = SO.name, SM.is_recompiled, ISR.SPECIFIC_SCHEMA - FROM sys.sql_modules AS SM - LEFT OUTER JOIN master.sys.databases AS sDB ON SM.object_id = DB_id() - LEFT OUTER JOIN dbo.sysobjects AS SO ON SM.object_id = SO.id and type = ''P'' - LEFT OUTER JOIN INFORMATION_SCHEMA.ROUTINES AS ISR on ISR.Routine_Name = SO.name AND ISR.SPECIFIC_CATALOG = DB_Name() - WHERE SM.is_recompiled=1 OPTION (RECOMPILE); /* oh the rich irony of recompile here */ - '; - INSERT INTO #BlitzResults - (Priority, - FindingsGroup, - Finding, - DatabaseName, - URL, - Details, - CheckID) - SELECT [Priority] = '100', - FindingsGroup = 'Performance', - Finding = 'Stored Procedure WITH RECOMPILE', - DatabaseName = DBName, - URL = 'https://www.brentozar.com/go/recompile', - Details = '[' + DBName + '].[' + SPSchema + '].[' + ProcName + '] has WITH RECOMPILE in the stored procedure code, which may cause increased CPU usage due to constant recompiles of the code.', - CheckID = '78' - FROM #Recompile AS TR - WHERE ProcName NOT LIKE 'sp_AllNightLog%' AND ProcName NOT LIKE 'sp_AskBrent%' AND ProcName NOT LIKE 'sp_Blitz%' AND ProcName NOT LIKE 'sp_PressureDetector' - AND DBName NOT IN ('master', 'model', 'msdb', 'tempdb'); - DROP TABLE #Recompile; - END; + SET @StringToExecute += + N'INSERT INTO #Backups(database_name, database_guid, ' + @crlf + + N' FullMBpsAvg, FullMBpsMin, FullMBpsMax, FullSizeMBAvg, FullSizeMBMin, FullSizeMBMax, FullCompressedSizeMBAvg, FullCompressedSizeMBMin, FullCompressedSizeMBMax, ' + @crlf + + N' DiffMBpsAvg, DiffMBpsMin, DiffMBpsMax, DiffSizeMBAvg, DiffSizeMBMin, DiffSizeMBMax, DiffCompressedSizeMBAvg, DiffCompressedSizeMBMin, DiffCompressedSizeMBMax, ' + @crlf + + N' LogMBpsAvg, LogMBpsMin, LogMBpsMax, LogSizeMBAvg, LogSizeMBMin, LogSizeMBMax, LogCompressedSizeMBAvg, LogCompressedSizeMBMin, LogCompressedSizeMBMax ) ' + @crlf + + N'SELECT bF.database_name, bF.database_guid ' + @crlf + + N' , bF.MBpsAvg AS FullMBpsAvg ' + @crlf + + N' , bF.MBpsMin AS FullMBpsMin ' + @crlf + + N' , bF.MBpsMax AS FullMBpsMax ' + @crlf + + N' , bF.SizeMBAvg AS FullSizeMBAvg ' + @crlf + + N' , bF.SizeMBMin AS FullSizeMBMin ' + @crlf + + N' , bF.SizeMBMax AS FullSizeMBMax ' + @crlf + + N' , bF.CompressedSizeMBAvg AS FullCompressedSizeMBAvg ' + @crlf + + N' , bF.CompressedSizeMBMin AS FullCompressedSizeMBMin ' + @crlf + + N' , bF.CompressedSizeMBMax AS FullCompressedSizeMBMax ' + @crlf + + N' , bD.MBpsAvg AS DiffMBpsAvg ' + @crlf + + N' , bD.MBpsMin AS DiffMBpsMin ' + @crlf + + N' , bD.MBpsMax AS DiffMBpsMax ' + @crlf + + N' , bD.SizeMBAvg AS DiffSizeMBAvg ' + @crlf + + N' , bD.SizeMBMin AS DiffSizeMBMin ' + @crlf + + N' , bD.SizeMBMax AS DiffSizeMBMax ' + @crlf + + N' , bD.CompressedSizeMBAvg AS DiffCompressedSizeMBAvg ' + @crlf + + N' , bD.CompressedSizeMBMin AS DiffCompressedSizeMBMin ' + @crlf + + N' , bD.CompressedSizeMBMax AS DiffCompressedSizeMBMax ' + @crlf + + N' , bL.MBpsAvg AS LogMBpsAvg ' + @crlf + + N' , bL.MBpsMin AS LogMBpsMin ' + @crlf + + N' , bL.MBpsMax AS LogMBpsMax ' + @crlf + + N' , bL.SizeMBAvg AS LogSizeMBAvg ' + @crlf + + N' , bL.SizeMBMin AS LogSizeMBMin ' + @crlf + + N' , bL.SizeMBMax AS LogSizeMBMax ' + @crlf + + N' , bL.CompressedSizeMBAvg AS LogCompressedSizeMBAvg ' + @crlf + + N' , bL.CompressedSizeMBMin AS LogCompressedSizeMBMin ' + @crlf + + N' , bL.CompressedSizeMBMax AS LogCompressedSizeMBMax ' + @crlf + + N' FROM Backups bF ' + @crlf + + N' LEFT OUTER JOIN Backups bD ON bF.database_name = bD.database_name AND bF.database_guid = bD.database_guid AND bD.backup_type = ''I''' + @crlf + + N' LEFT OUTER JOIN Backups bL ON bF.database_name = bL.database_name AND bF.database_guid = bL.database_guid AND bL.backup_type = ''L''' + @crlf + + N' WHERE bF.backup_type = ''D''; ' + @crlf; - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 86 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 86) WITH NOWAIT; - - EXEC dbo.sp_MSforeachdb 'USE [?]; INSERT INTO #BlitzResults (CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details) SELECT DISTINCT 86, DB_NAME(), 230, ''Security'', ''Elevated Permissions on a Database'', ''https://www.brentozar.com/go/elevated'', (''In ['' + DB_NAME() + ''], user ['' + u.name + ''] has the role ['' + g.name + '']. This user can perform tasks beyond just reading and writing data.'') FROM (SELECT memberuid = convert(int, member_principal_id), groupuid = convert(int, role_principal_id) FROM [?].sys.database_role_members) m inner join [?].dbo.sysusers u on m.memberuid = u.uid inner join sysusers g on m.groupuid = g.uid where u.name <> ''dbo'' and g.name in (''db_owner'' , ''db_accessadmin'' , ''db_securityadmin'' , ''db_ddladmin'') OPTION (RECOMPILE);'; - END; + IF @Debug = 1 + PRINT @StringToExecute; - /*Check for non-aligned indexes in partioned databases*/ + EXEC sys.sp_executesql @StringToExecute, N'@StartTime DATETIME2', @StartTime; - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 72 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 72) WITH NOWAIT; - - EXEC dbo.sp_MSforeachdb 'USE [?]; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - insert into #partdb(dbname, objectname, type_desc) - SELECT distinct db_name(DB_ID()) as DBName,o.name Object_Name,ds.type_desc - FROM sys.objects AS o JOIN sys.indexes AS i ON o.object_id = i.object_id - JOIN sys.data_spaces ds on ds.data_space_id = i.data_space_id - LEFT OUTER JOIN sys.dm_db_index_usage_stats AS s ON i.object_id = s.object_id AND i.index_id = s.index_id AND s.database_id = DB_ID() - WHERE o.type = ''u'' - -- Clustered and Non-Clustered indexes - AND i.type IN (1, 2) - AND o.object_id in - ( - SELECT a.object_id from - (SELECT ob.object_id, ds.type_desc from sys.objects ob JOIN sys.indexes ind on ind.object_id = ob.object_id join sys.data_spaces ds on ds.data_space_id = ind.data_space_id - GROUP BY ob.object_id, ds.type_desc ) a group by a.object_id having COUNT (*) > 1 - ) OPTION (RECOMPILE);'; - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT DISTINCT - 72 AS CheckID , - dbname AS DatabaseName , - 100 AS Priority , - 'Performance' AS FindingsGroup , - 'The partitioned database ' + dbname - + ' may have non-aligned indexes' AS Finding , - 'https://www.brentozar.com/go/aligned' AS URL , - 'Having non-aligned indexes on partitioned tables may cause inefficient query plans and CPU pressure' AS Details - FROM #partdb - WHERE dbname IS NOT NULL - AND dbname NOT IN ( SELECT DISTINCT - DatabaseName - FROM #SkipChecks - WHERE CheckID IS NULL OR CheckID = 72); - DROP TABLE #partdb; - END; - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 113 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 113) WITH NOWAIT; - - EXEC dbo.sp_MSforeachdb 'USE [?]; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - INSERT INTO #BlitzResults - (CheckID, - DatabaseName, - Priority, - FindingsGroup, - Finding, - URL, - Details) - SELECT DISTINCT 113, - N''?'', - 50, - ''Reliability'', - ''Full Text Indexes Not Updating'', - ''https://www.brentozar.com/go/fulltext'', - (''At least one full text index in this database has not been crawled in the last week.'') - from [?].sys.fulltext_indexes i WHERE change_tracking_state_desc <> ''AUTO'' AND i.is_enabled = 1 AND i.crawl_end_date < DATEADD(dd, -7, GETDATE()) OPTION (RECOMPILE);'; - END; + RAISERROR('Updating #Backups with worst RPO case', 0, 1) WITH NOWAIT; - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 115 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 115) WITH NOWAIT; - - EXEC dbo.sp_MSforeachdb 'USE [?]; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - INSERT INTO #BlitzResults - (CheckID, - DatabaseName, - Priority, - FindingsGroup, - Finding, - URL, - Details) - SELECT 115, - N''?'', - 110, - ''Performance'', - ''Parallelism Rocket Surgery'', - ''https://www.brentozar.com/go/makeparallel'', - (''['' + DB_NAME() + ''] has a make_parallel function, indicating that an advanced developer may be manhandling SQL Server into forcing queries to go parallel.'') - from [?].INFORMATION_SCHEMA.ROUTINES WHERE ROUTINE_NAME = ''make_parallel'' AND ROUTINE_TYPE = ''FUNCTION'' OPTION (RECOMPILE);'; - END; + SET @StringToExecute =N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 122 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 122) WITH NOWAIT; - - /* SQL Server 2012 and newer uses temporary stats for Availability Groups, and those show up as user-created */ - IF EXISTS (SELECT * - FROM sys.all_columns c - INNER JOIN sys.all_objects o ON c.object_id = o.object_id - WHERE c.name = 'is_temporary' AND o.name = 'stats') - - EXEC dbo.sp_MSforeachdb 'USE [?]; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - INSERT INTO #BlitzResults - (CheckID, - DatabaseName, - Priority, - FindingsGroup, - Finding, - URL, - Details) - SELECT TOP 1 122, - N''?'', - 200, - ''Performance'', - ''User-Created Statistics In Place'', - ''https://www.brentozar.com/go/userstats'', - (''['' + DB_NAME() + ''] has '' + CAST(SUM(1) AS NVARCHAR(10)) + '' user-created statistics. This indicates that someone is being a rocket scientist with the stats, and might actually be slowing things down, especially during stats updates.'') - from [?].sys.stats WHERE user_created = 1 AND is_temporary = 0 - HAVING SUM(1) > 0 OPTION (RECOMPILE);'; + SET @StringToExecute += N' + SELECT bs.database_name, bs.database_guid, bs.backup_set_id, bsPrior.backup_set_id AS backup_set_id_prior, + bs.backup_finish_date, bsPrior.backup_finish_date AS backup_finish_date_prior, + DATEDIFF(ss, bsPrior.backup_finish_date, bs.backup_finish_date) AS backup_gap_seconds + INTO #backup_gaps + FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS bs + CROSS APPLY ( + SELECT TOP 1 bs1.backup_set_id, bs1.backup_finish_date + FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS bs1 + WHERE bs.database_name = bs1.database_name + AND bs.database_guid = bs1.database_guid + AND bs.backup_finish_date > bs1.backup_finish_date + AND bs.backup_set_id > bs1.backup_set_id + ORDER BY bs1.backup_finish_date DESC, bs1.backup_set_id DESC + ) bsPrior + WHERE bs.backup_finish_date > @StartTime + + CREATE CLUSTERED INDEX cx_backup_gaps ON #backup_gaps (database_name, database_guid, backup_set_id, backup_finish_date, backup_gap_seconds); + + WITH max_gaps AS ( + SELECT g.database_name, g.database_guid, g.backup_set_id, g.backup_set_id_prior, g.backup_finish_date_prior, + g.backup_finish_date, MAX(g.backup_gap_seconds) AS max_backup_gap_seconds + FROM #backup_gaps AS g + GROUP BY g.database_name, g.database_guid, g.backup_set_id, g.backup_set_id_prior, g.backup_finish_date_prior, g.backup_finish_date + ) + UPDATE #Backups + SET RPOWorstCaseMinutes = bg.max_backup_gap_seconds / 60.0 + , RPOWorstCaseBackupSetID = bg.backup_set_id + , RPOWorstCaseBackupSetFinishTime = bg.backup_finish_date + , RPOWorstCaseBackupSetIDPrior = bg.backup_set_id_prior + , RPOWorstCaseBackupSetPriorFinishTime = bg.backup_finish_date_prior + FROM #Backups b + INNER HASH JOIN max_gaps bg ON b.database_name = bg.database_name AND b.database_guid = bg.database_guid + LEFT OUTER HASH JOIN max_gaps bgBigger ON bg.database_name = bgBigger.database_name AND bg.database_guid = bgBigger.database_guid AND bg.max_backup_gap_seconds < bgBigger.max_backup_gap_seconds + WHERE bgBigger.backup_set_id IS NULL; + '; + IF @Debug = 1 + PRINT @StringToExecute; - ELSE - EXEC dbo.sp_MSforeachdb 'USE [?]; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - INSERT INTO #BlitzResults - (CheckID, - DatabaseName, - Priority, - FindingsGroup, - Finding, - URL, - Details) - SELECT 122, - N''?'', - 200, - ''Performance'', - ''User-Created Statistics In Place'', - ''https://www.brentozar.com/go/userstats'', - (''['' + DB_NAME() + ''] has '' + CAST(SUM(1) AS NVARCHAR(10)) + '' user-created statistics. This indicates that someone is being a rocket scientist with the stats, and might actually be slowing things down, especially during stats updates.'') - from [?].sys.stats WHERE user_created = 1 - HAVING SUM(1) > 0 OPTION (RECOMPILE);'; + EXEC sys.sp_executesql @StringToExecute, N'@StartTime DATETIME2', @StartTime; - END; /* IF NOT EXISTS ( SELECT 1 */ + RAISERROR('Updating #Backups with worst RPO case queries', 0, 1) WITH NOWAIT; - /*Check for high VLF count: this will omit any database snapshots*/ + UPDATE #Backups + SET RPOWorstCaseMoreInfoQuery = @MoreInfoHeader + N'SELECT * ' + @crlf + + N' FROM ' + QUOTENAME(@MSDBName) + '.dbo.backupset ' + @crlf + + N' WHERE database_name = ''' + database_name + ''' ' + @crlf + + N' AND database_guid = ''' + CAST(database_guid AS NVARCHAR(50)) + ''' ' + @crlf + + N' AND backup_finish_date >= DATEADD(hh, -2, ''' + CAST(CONVERT(DATETIME, RPOWorstCaseBackupSetPriorFinishTime, 102) AS NVARCHAR(100)) + ''') ' + @crlf + + N' AND backup_finish_date <= DATEADD(hh, 2, ''' + CAST(CONVERT(DATETIME, RPOWorstCaseBackupSetPriorFinishTime, 102) AS NVARCHAR(100)) + ''') ' + @crlf + + N' ORDER BY backup_finish_date;' + + @MoreInfoFooter; - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 69 ) - BEGIN - IF @ProductVersionMajor >= 11 - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d] (2012 version of Log Info).', 0, 1, 69) WITH NOWAIT; - - EXEC sp_MSforeachdb N'USE [?]; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - INSERT INTO #LogInfo2012 - EXEC sp_executesql N''DBCC LogInfo() WITH NO_INFOMSGS''; - IF @@ROWCOUNT > 999 - BEGIN - INSERT INTO #BlitzResults - ( CheckID - ,DatabaseName - ,Priority - ,FindingsGroup - ,Finding - ,URL - ,Details) - SELECT 69 - ,DB_NAME() - ,170 - ,''File Configuration'' - ,''High VLF Count'' - ,''https://www.brentozar.com/go/vlf'' - ,''The ['' + DB_NAME() + ''] database has '' + CAST(COUNT(*) as VARCHAR(20)) + '' virtual log files (VLFs). This may be slowing down startup, restores, and even inserts/updates/deletes.'' - FROM #LogInfo2012 - WHERE EXISTS (SELECT name FROM master.sys.databases - WHERE source_database_id is null) OPTION (RECOMPILE); - END - TRUNCATE TABLE #LogInfo2012;'; - DROP TABLE #LogInfo2012; - END; - ELSE - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d] (pre-2012 version of Log Info).', 0, 1, 69) WITH NOWAIT; - - EXEC sp_MSforeachdb N'USE [?]; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - INSERT INTO #LogInfo - EXEC sp_executesql N''DBCC LogInfo() WITH NO_INFOMSGS''; - IF @@ROWCOUNT > 999 - BEGIN - INSERT INTO #BlitzResults - ( CheckID - ,DatabaseName - ,Priority - ,FindingsGroup - ,Finding - ,URL - ,Details) - SELECT 69 - ,DB_NAME() - ,170 - ,''File Configuration'' - ,''High VLF Count'' - ,''https://www.brentozar.com/go/vlf'' - ,''The ['' + DB_NAME() + ''] database has '' + CAST(COUNT(*) as VARCHAR(20)) + '' virtual log files (VLFs). This may be slowing down startup, restores, and even inserts/updates/deletes.'' - FROM #LogInfo - WHERE EXISTS (SELECT name FROM master.sys.databases - WHERE source_database_id is null) OPTION (RECOMPILE); - END - TRUNCATE TABLE #LogInfo;'; - DROP TABLE #LogInfo; - END; - END; +/* RTO */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 80 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 80) WITH NOWAIT; - - EXEC dbo.sp_MSforeachdb 'USE [?]; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - INSERT INTO #BlitzResults (CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details) - SELECT DISTINCT 80, DB_NAME(), 170, ''Reliability'', ''Max File Size Set'', ''https://www.brentozar.com/go/maxsize'', - (''The ['' + DB_NAME() + ''] database file '' + df.name + '' has a max file size set to '' - + CAST(CAST(df.max_size AS BIGINT) * 8 / 1024 AS VARCHAR(100)) - + ''MB. If it runs out of space, the database will stop working even though there may be drive space available.'') - FROM sys.database_files df - WHERE 0 = (SELECT is_read_only FROM sys.databases WHERE name = ''?'') - AND df.max_size <> 268435456 - AND df.max_size <> -1 - AND df.type <> 2 - AND df.growth > 0 - AND df.name <> ''DWDiagnostics'' OPTION (RECOMPILE);'; +RAISERROR('Gathering RTO information', 0, 1) WITH NOWAIT; - DELETE br - FROM #BlitzResults br - INNER JOIN #SkipChecks sc ON sc.CheckID = 80 AND br.DatabaseName = sc.DatabaseName; - END; - - - /* Check if columnstore indexes are in use - for Github issue #615 */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 74 ) /* Trace flags */ - BEGIN - TRUNCATE TABLE #TemporaryDatabaseResults; - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 74) WITH NOWAIT; - - EXEC dbo.sp_MSforeachdb 'USE [?]; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; IF EXISTS(SELECT * FROM sys.indexes WHERE type IN (5,6)) INSERT INTO #TemporaryDatabaseResults (DatabaseName, Finding) VALUES (DB_NAME(), ''Yup'') OPTION (RECOMPILE);'; - IF EXISTS (SELECT * FROM #TemporaryDatabaseResults) SET @ColumnStoreIndexesInUse = 1; - END; - /* Non-Default Database Scoped Config - Github issue #598 */ - IF EXISTS ( SELECT * FROM sys.all_objects WHERE [name] = 'database_scoped_configurations' ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d] through [%d] and [%d] through [%d].', 0, 1, 194, 197, 237, 255) WITH NOWAIT; - - INSERT INTO #DatabaseScopedConfigurationDefaults (configuration_id, [name], default_value, default_value_for_secondary, CheckID) - SELECT 1, 'MAXDOP', '0', NULL, 194 - UNION ALL - SELECT 2, 'LEGACY_CARDINALITY_ESTIMATION', '0', NULL, 195 - UNION ALL - SELECT 3, 'PARAMETER_SNIFFING', '1', NULL, 196 - UNION ALL - SELECT 4, 'QUERY_OPTIMIZER_HOTFIXES', '0', NULL, 197 - UNION ALL - SELECT 6, 'IDENTITY_CACHE', '1', NULL, 237 - UNION ALL - SELECT 7, 'INTERLEAVED_EXECUTION_TVF', '1', NULL, 238 - UNION ALL - SELECT 8, 'BATCH_MODE_MEMORY_GRANT_FEEDBACK', '1', NULL, 239 - UNION ALL - SELECT 9, 'BATCH_MODE_ADAPTIVE_JOINS', '1', NULL, 240 - UNION ALL - SELECT 10, 'TSQL_SCALAR_UDF_INLINING', '1', NULL, 241 - UNION ALL - SELECT 11, 'ELEVATE_ONLINE', 'OFF', NULL, 242 - UNION ALL - SELECT 12, 'ELEVATE_RESUMABLE', 'OFF', NULL, 243 - UNION ALL - SELECT 13, 'OPTIMIZE_FOR_AD_HOC_WORKLOADS', '0', NULL, 244 - UNION ALL - SELECT 14, 'XTP_PROCEDURE_EXECUTION_STATISTICS', '0', NULL, 245 - UNION ALL - SELECT 15, 'XTP_QUERY_EXECUTION_STATISTICS', '0', NULL, 246 - UNION ALL - SELECT 16, 'ROW_MODE_MEMORY_GRANT_FEEDBACK', '1', NULL, 247 - UNION ALL - SELECT 17, 'ISOLATE_SECURITY_POLICY_CARDINALITY', '0', NULL, 248 - UNION ALL - SELECT 18, 'BATCH_MODE_ON_ROWSTORE', '1', NULL, 249 - UNION ALL - SELECT 19, 'DEFERRED_COMPILATION_TV', '1', NULL, 250 - UNION ALL - SELECT 20, 'ACCELERATED_PLAN_FORCING', '1', NULL, 251 - UNION ALL - SELECT 21, 'GLOBAL_TEMPORARY_TABLE_AUTO_DROP', '1', NULL, 252 - UNION ALL - SELECT 22, 'LIGHTWEIGHT_QUERY_PROFILING', '1', NULL, 253 - UNION ALL - SELECT 23, 'VERBOSE_TRUNCATION_WARNINGS', '1', NULL, 254 - UNION ALL - SELECT 24, 'LAST_QUERY_PLAN_STATS', '0', NULL, 255; - EXEC dbo.sp_MSforeachdb 'USE [?]; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; INSERT INTO #BlitzResults (CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details) - SELECT def1.CheckID, DB_NAME(), 210, ''Non-Default Database Scoped Config'', dsc.[name], ''https://www.brentozar.com/go/dbscope'', (''Set value: '' + COALESCE(CAST(dsc.value AS NVARCHAR(100)),''Empty'') + '' Default: '' + COALESCE(CAST(def1.default_value AS NVARCHAR(100)),''Empty'') + '' Set value for secondary: '' + COALESCE(CAST(dsc.value_for_secondary AS NVARCHAR(100)),''Empty'') + '' Default value for secondary: '' + COALESCE(CAST(def1.default_value_for_secondary AS NVARCHAR(100)),''Empty'')) - FROM [?].sys.database_scoped_configurations dsc - INNER JOIN #DatabaseScopedConfigurationDefaults def1 ON dsc.configuration_id = def1.configuration_id - LEFT OUTER JOIN #DatabaseScopedConfigurationDefaults def ON dsc.configuration_id = def.configuration_id AND (cast(dsc.value as nvarchar(100)) = cast(def.default_value as nvarchar(100)) OR dsc.value IS NULL) AND (dsc.value_for_secondary = def.default_value_for_secondary OR dsc.value_for_secondary IS NULL) - LEFT OUTER JOIN #SkipChecks sk ON (sk.CheckID IS NULL OR def.CheckID = sk.CheckID) AND (sk.DatabaseName IS NULL OR sk.DatabaseName = DB_NAME()) - WHERE def.configuration_id IS NULL AND sk.CheckID IS NULL ORDER BY 1 - OPTION (RECOMPILE);'; - END; + SET @StringToExecute =N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - /* Check 218 - Show me the dodgy SET Options */ - IF NOT EXISTS ( - SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL - AND CheckID = 218 - ) - BEGIN - IF @Debug IN (1,2) - BEGIN - RAISERROR ('Running CheckId [%d].',0,1,218) WITH NOWAIT; - END + SET @StringToExecute += N' + INSERT INTO #RTORecoveryPoints(database_name, database_guid, log_last_lsn) + SELECT database_name, database_guid, MAX(last_lsn) AS log_last_lsn + FROM ' + QUOTENAME(@MSDBName) + '.dbo.backupset bLastLog + WHERE type = ''L'' + AND bLastLog.backup_finish_date >= @StartTime + GROUP BY database_name, database_guid; + '; - EXECUTE sp_MSforeachdb 'USE [?]; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - INSERT INTO #BlitzResults (CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details) - SELECT 218 AS CheckID - ,''?'' AS DatabaseName - ,150 AS Priority - ,''Performance'' AS FindingsGroup - ,''Objects created with dangerous SET Options'' AS Finding - ,''https://www.brentozar.com/go/badset'' AS URL - ,''The '' + QUOTENAME(DB_NAME()) - + '' database has '' + CONVERT(VARCHAR(20),COUNT(1)) - + '' objects that were created with dangerous ANSI_NULL or QUOTED_IDENTIFIER options.'' - + '' These objects can break when using filtered indexes, indexed views'' - + '' and other advanced SQL features.'' AS Details - FROM sys.sql_modules sm - JOIN sys.objects o ON o.[object_id] = sm.[object_id] - AND ( - sm.uses_ansi_nulls <> 1 - OR sm.uses_quoted_identifier <> 1 - ) - AND o.is_ms_shipped = 0 - HAVING COUNT(1) > 0;'; - END; --of Check 218. + IF @Debug = 1 + PRINT @StringToExecute; - /* Check 225 - Reliability - Resumable Index Operation Paused */ - IF NOT EXISTS ( - SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL - AND CheckID = 225 - ) - AND EXISTS (SELECT * FROM sys.all_objects WHERE name = 'index_resumable_operations') - BEGIN - IF @Debug IN (1,2) - BEGIN - RAISERROR ('Running CheckId [%d].',0,1,218) WITH NOWAIT; - END + EXEC sys.sp_executesql @StringToExecute, N'@StartTime DATETIME2', @StartTime; - EXECUTE sp_MSforeachdb 'USE [?]; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - INSERT INTO #BlitzResults (CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details) - SELECT 225 AS CheckID - ,''?'' AS DatabaseName - ,200 AS Priority - ,''Reliability'' AS FindingsGroup - ,''Resumable Index Operation Paused'' AS Finding - ,''https://www.brentozar.com/go/resumable'' AS URL - ,iro.state_desc + N'' since '' + CONVERT(NVARCHAR(50), last_pause_time, 120) + '', '' - + CAST(iro.percent_complete AS NVARCHAR(20)) + ''% complete: '' - + CAST(iro.sql_text AS NVARCHAR(1000)) AS Details - FROM sys.index_resumable_operations iro - JOIN sys.objects o ON iro.[object_id] = o.[object_id] - WHERE iro.state <> 0;'; - END; --of Check 225. +/* Find the most recent full backups for those logs */ - --/* Check 220 - Statistics Without Histograms */ - --IF NOT EXISTS ( - -- SELECT 1 - -- FROM #SkipChecks - -- WHERE DatabaseName IS NULL - -- AND CheckID = 220 - -- ) - -- AND EXISTS (SELECT * FROM sys.all_objects WHERE name = 'dm_db_stats_histogram') - --BEGIN - -- IF @Debug IN (1,2) - -- BEGIN - -- RAISERROR ('Running CheckId [%d].',0,1,220) WITH NOWAIT; - -- END +RAISERROR('Updating #RTORecoveryPoints', 0, 1) WITH NOWAIT; - -- EXECUTE sp_MSforeachdb 'USE [?]; - -- SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - -- INSERT INTO #BlitzResults (CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details) - -- SELECT 220 AS CheckID - -- ,DB_NAME() AS DatabaseName - -- ,110 AS Priority - -- ,''Performance'' AS FindingsGroup - -- ,''Statistics Without Histograms'' AS Finding - -- ,''https://www.brentozar.com/go/brokenstats'' AS URL - -- ,CAST(COUNT(DISTINCT o.object_id) AS VARCHAR(100)) + '' tables have statistics that have not been updated since the database was restored or upgraded,'' - -- + '' and have no data in their histogram. See the More Info URL for a script to update them. '' AS Details - -- FROM sys.all_objects o - -- INNER JOIN sys.stats s ON o.object_id = s.object_id AND s.has_filter = 0 - -- OUTER APPLY sys.dm_db_stats_histogram(o.object_id, s.stats_id) h - -- WHERE o.is_ms_shipped = 0 AND o.type_desc = ''USER_TABLE'' - -- AND h.object_id IS NULL - -- AND 0 < (SELECT SUM(row_count) FROM sys.dm_db_partition_stats ps WHERE ps.object_id = o.object_id) - -- AND ''?'' NOT IN (''master'', ''model'', ''msdb'', ''tempdb'') - -- HAVING COUNT(DISTINCT o.object_id) > 0;'; - --END; --of Check 220. + SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - /*Check for the last good DBCC CHECKDB date */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 68 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 68) WITH NOWAIT; - - /* Removed as populating the #DBCCs table now done in advance as data is uses for multiple checks*/ - --EXEC sp_MSforeachdb N'USE [?]; - --SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - --INSERT #DBCCs - -- (ParentObject, - -- Object, - -- Field, - -- Value) - --EXEC (''DBCC DBInfo() With TableResults, NO_INFOMSGS''); - --UPDATE #DBCCs SET DbName = N''?'' WHERE DbName IS NULL OPTION (RECOMPILE);'; + SET @StringToExecute += N' + UPDATE #RTORecoveryPoints + SET log_backup_set_id = bLasted.backup_set_id + ,full_backup_set_id = bLasted.backup_set_id + ,full_last_lsn = bLasted.last_lsn + ,full_backup_set_uuid = bLasted.backup_set_uuid + FROM #RTORecoveryPoints rp + CROSS APPLY ( + SELECT TOP 1 bLog.backup_set_id AS backup_set_id_log, bLastFull.backup_set_id, bLastFull.last_lsn, bLastFull.backup_set_uuid, bLastFull.database_guid, bLastFull.database_name + FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset bLog + INNER JOIN ' + QUOTENAME(@MSDBName) + N'.dbo.backupset bLastFull + ON bLog.database_guid = bLastFull.database_guid + AND bLog.database_name = bLastFull.database_name + AND bLog.first_lsn > bLastFull.last_lsn + AND bLastFull.type = ''D'' + WHERE rp.database_guid = bLog.database_guid + AND rp.database_name = bLog.database_name + ) bLasted + LEFT OUTER JOIN ' + QUOTENAME(@MSDBName) + N'.dbo.backupset bLaterFulls ON bLasted.database_guid = bLaterFulls.database_guid AND bLasted.database_name = bLaterFulls.database_name + AND bLasted.last_lsn < bLaterFulls.last_lsn + AND bLaterFulls.first_lsn < bLasted.last_lsn + AND bLaterFulls.type = ''D'' + WHERE bLaterFulls.backup_set_id IS NULL; + '; - WITH DB2 - AS ( SELECT DISTINCT - Field , - Value , - DbName - FROM #DBCCs - INNER JOIN sys.databases d ON #DBCCs.DbName = d.name - WHERE Field = 'dbi_dbccLastKnownGood' - AND d.create_date < DATEADD(dd, -14, GETDATE()) - ) - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 68 AS CheckID , - DB2.DbName AS DatabaseName , - 1 AS PRIORITY , - 'Reliability' AS FindingsGroup , - 'Last good DBCC CHECKDB over 2 weeks old' AS Finding , - 'https://www.brentozar.com/go/checkdb' AS URL , - 'Last successful CHECKDB: ' - + CASE DB2.Value - WHEN '1900-01-01 00:00:00.000' - THEN ' never.' - ELSE DB2.Value - END AS Details - FROM DB2 - WHERE DB2.DbName <> 'tempdb' - AND DB2.DbName NOT IN ( SELECT DISTINCT - DatabaseName - FROM - #SkipChecks - WHERE CheckID IS NULL OR CheckID = 68) - AND DB2.DbName NOT IN ( SELECT name - FROM sys.databases - WHERE is_read_only = 1) - AND CONVERT(DATETIME, DB2.Value, 121) < DATEADD(DD, - -14, - CURRENT_TIMESTAMP); - END; + IF @Debug = 1 + PRINT @StringToExecute; - END; /* IF @CheckUserDatabaseObjects = 1 */ + EXEC sys.sp_executesql @StringToExecute; - IF @CheckProcedureCache = 1 - - BEGIN +/* Add any full backups in the StartDate range that weren't part of the above log backup chain */ - IF @Debug IN (1, 2) RAISERROR('Begin checking procedure cache', 0, 1) WITH NOWAIT; - - BEGIN +RAISERROR('Add any full backups in the StartDate range that weren''t part of the above log backup chain', 0, 1) WITH NOWAIT; - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 35 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 35) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 35 AS CheckID , - 100 AS Priority , - 'Performance' AS FindingsGroup , - 'Single-Use Plans in Procedure Cache' AS Finding , - 'https://www.brentozar.com/go/single' AS URL , - ( CAST(COUNT(*) AS VARCHAR(10)) - + ' query plans are taking up memory in the procedure cache. This may be wasted memory if we cache plans for queries that never get called again. This may be a good use case for SQL Server 2008''s Optimize for Ad Hoc or for Forced Parameterization.' ) AS Details - FROM sys.dm_exec_cached_plans AS cp - WHERE cp.usecounts = 1 - AND cp.objtype = 'Adhoc' - AND EXISTS ( SELECT - 1 - FROM sys.configurations - WHERE - name = 'optimize for ad hoc workloads' - AND value_in_use = 0 ) - HAVING COUNT(*) > 1; - END; + SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - /* Set up the cache tables. Different on 2005 since it doesn't support query_hash, query_plan_hash. */ - IF @@VERSION LIKE '%Microsoft SQL Server 2005%' - BEGIN - IF @CheckProcedureCacheFilter = 'CPU' - OR @CheckProcedureCacheFilter IS NULL - BEGIN - SET @StringToExecute = 'WITH queries ([sql_handle],[statement_start_offset],[statement_end_offset],[plan_generation_num],[plan_handle],[creation_time],[last_execution_time],[execution_count],[total_worker_time],[last_worker_time],[min_worker_time],[max_worker_time],[total_physical_reads],[last_physical_reads],[min_physical_reads],[max_physical_reads],[total_logical_writes],[last_logical_writes],[min_logical_writes],[max_logical_writes],[total_logical_reads],[last_logical_reads],[min_logical_reads],[max_logical_reads],[total_clr_time],[last_clr_time],[min_clr_time],[max_clr_time],[total_elapsed_time],[last_elapsed_time],[min_elapsed_time],[max_elapsed_time]) - AS (SELECT TOP 20 qs.[sql_handle],qs.[statement_start_offset],qs.[statement_end_offset],qs.[plan_generation_num],qs.[plan_handle],qs.[creation_time],qs.[last_execution_time],qs.[execution_count],qs.[total_worker_time],qs.[last_worker_time],qs.[min_worker_time],qs.[max_worker_time],qs.[total_physical_reads],qs.[last_physical_reads],qs.[min_physical_reads],qs.[max_physical_reads],qs.[total_logical_writes],qs.[last_logical_writes],qs.[min_logical_writes],qs.[max_logical_writes],qs.[total_logical_reads],qs.[last_logical_reads],qs.[min_logical_reads],qs.[max_logical_reads],qs.[total_clr_time],qs.[last_clr_time],qs.[min_clr_time],qs.[max_clr_time],qs.[total_elapsed_time],qs.[last_elapsed_time],qs.[min_elapsed_time],qs.[max_elapsed_time] - FROM sys.dm_exec_query_stats qs - ORDER BY qs.total_worker_time DESC) - INSERT INTO #dm_exec_query_stats ([sql_handle],[statement_start_offset],[statement_end_offset],[plan_generation_num],[plan_handle],[creation_time],[last_execution_time],[execution_count],[total_worker_time],[last_worker_time],[min_worker_time],[max_worker_time],[total_physical_reads],[last_physical_reads],[min_physical_reads],[max_physical_reads],[total_logical_writes],[last_logical_writes],[min_logical_writes],[max_logical_writes],[total_logical_reads],[last_logical_reads],[min_logical_reads],[max_logical_reads],[total_clr_time],[last_clr_time],[min_clr_time],[max_clr_time],[total_elapsed_time],[last_elapsed_time],[min_elapsed_time],[max_elapsed_time]) - SELECT qs.[sql_handle],qs.[statement_start_offset],qs.[statement_end_offset],qs.[plan_generation_num],qs.[plan_handle],qs.[creation_time],qs.[last_execution_time],qs.[execution_count],qs.[total_worker_time],qs.[last_worker_time],qs.[min_worker_time],qs.[max_worker_time],qs.[total_physical_reads],qs.[last_physical_reads],qs.[min_physical_reads],qs.[max_physical_reads],qs.[total_logical_writes],qs.[last_logical_writes],qs.[min_logical_writes],qs.[max_logical_writes],qs.[total_logical_reads],qs.[last_logical_reads],qs.[min_logical_reads],qs.[max_logical_reads],qs.[total_clr_time],qs.[last_clr_time],qs.[min_clr_time],qs.[max_clr_time],qs.[total_elapsed_time],qs.[last_elapsed_time],qs.[min_elapsed_time],qs.[max_elapsed_time] - FROM queries qs - LEFT OUTER JOIN #dm_exec_query_stats qsCaught ON qs.sql_handle = qsCaught.sql_handle AND qs.plan_handle = qsCaught.plan_handle AND qs.statement_start_offset = qsCaught.statement_start_offset - WHERE qsCaught.sql_handle IS NULL OPTION (RECOMPILE);'; - EXECUTE(@StringToExecute); - END; + SET @StringToExecute += N' + INSERT INTO #RTORecoveryPoints(database_name, database_guid, full_backup_set_id, full_last_lsn, full_backup_set_uuid) + SELECT bFull.database_name, bFull.database_guid, bFull.backup_set_id, bFull.last_lsn, bFull.backup_set_uuid + FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset bFull + LEFT OUTER JOIN #RTORecoveryPoints rp ON bFull.backup_set_uuid = rp.full_backup_set_uuid + WHERE bFull.type = ''D'' + AND bFull.backup_finish_date IS NOT NULL + AND rp.full_backup_set_uuid IS NULL + AND bFull.backup_finish_date >= @StartTime; + '; - IF @CheckProcedureCacheFilter = 'Reads' - OR @CheckProcedureCacheFilter IS NULL - BEGIN - SET @StringToExecute = 'WITH queries ([sql_handle],[statement_start_offset],[statement_end_offset],[plan_generation_num],[plan_handle],[creation_time],[last_execution_time],[execution_count],[total_worker_time],[last_worker_time],[min_worker_time],[max_worker_time],[total_physical_reads],[last_physical_reads],[min_physical_reads],[max_physical_reads],[total_logical_writes],[last_logical_writes],[min_logical_writes],[max_logical_writes],[total_logical_reads],[last_logical_reads],[min_logical_reads],[max_logical_reads],[total_clr_time],[last_clr_time],[min_clr_time],[max_clr_time],[total_elapsed_time],[last_elapsed_time],[min_elapsed_time],[max_elapsed_time]) - AS (SELECT TOP 20 qs.[sql_handle],qs.[statement_start_offset],qs.[statement_end_offset],qs.[plan_generation_num],qs.[plan_handle],qs.[creation_time],qs.[last_execution_time],qs.[execution_count],qs.[total_worker_time],qs.[last_worker_time],qs.[min_worker_time],qs.[max_worker_time],qs.[total_physical_reads],qs.[last_physical_reads],qs.[min_physical_reads],qs.[max_physical_reads],qs.[total_logical_writes],qs.[last_logical_writes],qs.[min_logical_writes],qs.[max_logical_writes],qs.[total_logical_reads],qs.[last_logical_reads],qs.[min_logical_reads],qs.[max_logical_reads],qs.[total_clr_time],qs.[last_clr_time],qs.[min_clr_time],qs.[max_clr_time],qs.[total_elapsed_time],qs.[last_elapsed_time],qs.[min_elapsed_time],qs.[max_elapsed_time] - FROM sys.dm_exec_query_stats qs - ORDER BY qs.total_logical_reads DESC) - INSERT INTO #dm_exec_query_stats ([sql_handle],[statement_start_offset],[statement_end_offset],[plan_generation_num],[plan_handle],[creation_time],[last_execution_time],[execution_count],[total_worker_time],[last_worker_time],[min_worker_time],[max_worker_time],[total_physical_reads],[last_physical_reads],[min_physical_reads],[max_physical_reads],[total_logical_writes],[last_logical_writes],[min_logical_writes],[max_logical_writes],[total_logical_reads],[last_logical_reads],[min_logical_reads],[max_logical_reads],[total_clr_time],[last_clr_time],[min_clr_time],[max_clr_time],[total_elapsed_time],[last_elapsed_time],[min_elapsed_time],[max_elapsed_time]) - SELECT qs.[sql_handle],qs.[statement_start_offset],qs.[statement_end_offset],qs.[plan_generation_num],qs.[plan_handle],qs.[creation_time],qs.[last_execution_time],qs.[execution_count],qs.[total_worker_time],qs.[last_worker_time],qs.[min_worker_time],qs.[max_worker_time],qs.[total_physical_reads],qs.[last_physical_reads],qs.[min_physical_reads],qs.[max_physical_reads],qs.[total_logical_writes],qs.[last_logical_writes],qs.[min_logical_writes],qs.[max_logical_writes],qs.[total_logical_reads],qs.[last_logical_reads],qs.[min_logical_reads],qs.[max_logical_reads],qs.[total_clr_time],qs.[last_clr_time],qs.[min_clr_time],qs.[max_clr_time],qs.[total_elapsed_time],qs.[last_elapsed_time],qs.[min_elapsed_time],qs.[max_elapsed_time] - FROM queries qs - LEFT OUTER JOIN #dm_exec_query_stats qsCaught ON qs.sql_handle = qsCaught.sql_handle AND qs.plan_handle = qsCaught.plan_handle AND qs.statement_start_offset = qsCaught.statement_start_offset - WHERE qsCaught.sql_handle IS NULL OPTION (RECOMPILE);'; - EXECUTE(@StringToExecute); - END; + IF @Debug = 1 + PRINT @StringToExecute; - IF @CheckProcedureCacheFilter = 'ExecCount' - OR @CheckProcedureCacheFilter IS NULL - BEGIN - SET @StringToExecute = 'WITH queries ([sql_handle],[statement_start_offset],[statement_end_offset],[plan_generation_num],[plan_handle],[creation_time],[last_execution_time],[execution_count],[total_worker_time],[last_worker_time],[min_worker_time],[max_worker_time],[total_physical_reads],[last_physical_reads],[min_physical_reads],[max_physical_reads],[total_logical_writes],[last_logical_writes],[min_logical_writes],[max_logical_writes],[total_logical_reads],[last_logical_reads],[min_logical_reads],[max_logical_reads],[total_clr_time],[last_clr_time],[min_clr_time],[max_clr_time],[total_elapsed_time],[last_elapsed_time],[min_elapsed_time],[max_elapsed_time]) - AS (SELECT TOP 20 qs.[sql_handle],qs.[statement_start_offset],qs.[statement_end_offset],qs.[plan_generation_num],qs.[plan_handle],qs.[creation_time],qs.[last_execution_time],qs.[execution_count],qs.[total_worker_time],qs.[last_worker_time],qs.[min_worker_time],qs.[max_worker_time],qs.[total_physical_reads],qs.[last_physical_reads],qs.[min_physical_reads],qs.[max_physical_reads],qs.[total_logical_writes],qs.[last_logical_writes],qs.[min_logical_writes],qs.[max_logical_writes],qs.[total_logical_reads],qs.[last_logical_reads],qs.[min_logical_reads],qs.[max_logical_reads],qs.[total_clr_time],qs.[last_clr_time],qs.[min_clr_time],qs.[max_clr_time],qs.[total_elapsed_time],qs.[last_elapsed_time],qs.[min_elapsed_time],qs.[max_elapsed_time] - FROM sys.dm_exec_query_stats qs - ORDER BY qs.execution_count DESC) - INSERT INTO #dm_exec_query_stats ([sql_handle],[statement_start_offset],[statement_end_offset],[plan_generation_num],[plan_handle],[creation_time],[last_execution_time],[execution_count],[total_worker_time],[last_worker_time],[min_worker_time],[max_worker_time],[total_physical_reads],[last_physical_reads],[min_physical_reads],[max_physical_reads],[total_logical_writes],[last_logical_writes],[min_logical_writes],[max_logical_writes],[total_logical_reads],[last_logical_reads],[min_logical_reads],[max_logical_reads],[total_clr_time],[last_clr_time],[min_clr_time],[max_clr_time],[total_elapsed_time],[last_elapsed_time],[min_elapsed_time],[max_elapsed_time]) - SELECT qs.[sql_handle],qs.[statement_start_offset],qs.[statement_end_offset],qs.[plan_generation_num],qs.[plan_handle],qs.[creation_time],qs.[last_execution_time],qs.[execution_count],qs.[total_worker_time],qs.[last_worker_time],qs.[min_worker_time],qs.[max_worker_time],qs.[total_physical_reads],qs.[last_physical_reads],qs.[min_physical_reads],qs.[max_physical_reads],qs.[total_logical_writes],qs.[last_logical_writes],qs.[min_logical_writes],qs.[max_logical_writes],qs.[total_logical_reads],qs.[last_logical_reads],qs.[min_logical_reads],qs.[max_logical_reads],qs.[total_clr_time],qs.[last_clr_time],qs.[min_clr_time],qs.[max_clr_time],qs.[total_elapsed_time],qs.[last_elapsed_time],qs.[min_elapsed_time],qs.[max_elapsed_time] - FROM queries qs - LEFT OUTER JOIN #dm_exec_query_stats qsCaught ON qs.sql_handle = qsCaught.sql_handle AND qs.plan_handle = qsCaught.plan_handle AND qs.statement_start_offset = qsCaught.statement_start_offset - WHERE qsCaught.sql_handle IS NULL OPTION (RECOMPILE);'; - EXECUTE(@StringToExecute); - END; + EXEC sys.sp_executesql @StringToExecute, N'@StartTime DATETIME2', @StartTime; - IF @CheckProcedureCacheFilter = 'Duration' - OR @CheckProcedureCacheFilter IS NULL - BEGIN - SET @StringToExecute = 'WITH queries ([sql_handle],[statement_start_offset],[statement_end_offset],[plan_generation_num],[plan_handle],[creation_time],[last_execution_time],[execution_count],[total_worker_time],[last_worker_time],[min_worker_time],[max_worker_time],[total_physical_reads],[last_physical_reads],[min_physical_reads],[max_physical_reads],[total_logical_writes],[last_logical_writes],[min_logical_writes],[max_logical_writes],[total_logical_reads],[last_logical_reads],[min_logical_reads],[max_logical_reads],[total_clr_time],[last_clr_time],[min_clr_time],[max_clr_time],[total_elapsed_time],[last_elapsed_time],[min_elapsed_time],[max_elapsed_time]) - AS (SELECT TOP 20 qs.[sql_handle],qs.[statement_start_offset],qs.[statement_end_offset],qs.[plan_generation_num],qs.[plan_handle],qs.[creation_time],qs.[last_execution_time],qs.[execution_count],qs.[total_worker_time],qs.[last_worker_time],qs.[min_worker_time],qs.[max_worker_time],qs.[total_physical_reads],qs.[last_physical_reads],qs.[min_physical_reads],qs.[max_physical_reads],qs.[total_logical_writes],qs.[last_logical_writes],qs.[min_logical_writes],qs.[max_logical_writes],qs.[total_logical_reads],qs.[last_logical_reads],qs.[min_logical_reads],qs.[max_logical_reads],qs.[total_clr_time],qs.[last_clr_time],qs.[min_clr_time],qs.[max_clr_time],qs.[total_elapsed_time],qs.[last_elapsed_time],qs.[min_elapsed_time],qs.[max_elapsed_time] - FROM sys.dm_exec_query_stats qs - ORDER BY qs.total_elapsed_time DESC) - INSERT INTO #dm_exec_query_stats ([sql_handle],[statement_start_offset],[statement_end_offset],[plan_generation_num],[plan_handle],[creation_time],[last_execution_time],[execution_count],[total_worker_time],[last_worker_time],[min_worker_time],[max_worker_time],[total_physical_reads],[last_physical_reads],[min_physical_reads],[max_physical_reads],[total_logical_writes],[last_logical_writes],[min_logical_writes],[max_logical_writes],[total_logical_reads],[last_logical_reads],[min_logical_reads],[max_logical_reads],[total_clr_time],[last_clr_time],[min_clr_time],[max_clr_time],[total_elapsed_time],[last_elapsed_time],[min_elapsed_time],[max_elapsed_time]) - SELECT qs.[sql_handle],qs.[statement_start_offset],qs.[statement_end_offset],qs.[plan_generation_num],qs.[plan_handle],qs.[creation_time],qs.[last_execution_time],qs.[execution_count],qs.[total_worker_time],qs.[last_worker_time],qs.[min_worker_time],qs.[max_worker_time],qs.[total_physical_reads],qs.[last_physical_reads],qs.[min_physical_reads],qs.[max_physical_reads],qs.[total_logical_writes],qs.[last_logical_writes],qs.[min_logical_writes],qs.[max_logical_writes],qs.[total_logical_reads],qs.[last_logical_reads],qs.[min_logical_reads],qs.[max_logical_reads],qs.[total_clr_time],qs.[last_clr_time],qs.[min_clr_time],qs.[max_clr_time],qs.[total_elapsed_time],qs.[last_elapsed_time],qs.[min_elapsed_time],qs.[max_elapsed_time] - FROM queries qs - LEFT OUTER JOIN #dm_exec_query_stats qsCaught ON qs.sql_handle = qsCaught.sql_handle AND qs.plan_handle = qsCaught.plan_handle AND qs.statement_start_offset = qsCaught.statement_start_offset - WHERE qsCaught.sql_handle IS NULL OPTION (RECOMPILE);'; - EXECUTE(@StringToExecute); - END; +/* Fill out the most recent log for that full, but before the next full */ - END; - IF @ProductVersionMajor >= 10 - BEGIN - IF @CheckProcedureCacheFilter = 'CPU' - OR @CheckProcedureCacheFilter IS NULL - BEGIN - SET @StringToExecute = 'WITH queries ([sql_handle],[statement_start_offset],[statement_end_offset],[plan_generation_num],[plan_handle],[creation_time],[last_execution_time],[execution_count],[total_worker_time],[last_worker_time],[min_worker_time],[max_worker_time],[total_physical_reads],[last_physical_reads],[min_physical_reads],[max_physical_reads],[total_logical_writes],[last_logical_writes],[min_logical_writes],[max_logical_writes],[total_logical_reads],[last_logical_reads],[min_logical_reads],[max_logical_reads],[total_clr_time],[last_clr_time],[min_clr_time],[max_clr_time],[total_elapsed_time],[last_elapsed_time],[min_elapsed_time],[max_elapsed_time],[query_hash],[query_plan_hash]) - AS (SELECT TOP 20 qs.[sql_handle],qs.[statement_start_offset],qs.[statement_end_offset],qs.[plan_generation_num],qs.[plan_handle],qs.[creation_time],qs.[last_execution_time],qs.[execution_count],qs.[total_worker_time],qs.[last_worker_time],qs.[min_worker_time],qs.[max_worker_time],qs.[total_physical_reads],qs.[last_physical_reads],qs.[min_physical_reads],qs.[max_physical_reads],qs.[total_logical_writes],qs.[last_logical_writes],qs.[min_logical_writes],qs.[max_logical_writes],qs.[total_logical_reads],qs.[last_logical_reads],qs.[min_logical_reads],qs.[max_logical_reads],qs.[total_clr_time],qs.[last_clr_time],qs.[min_clr_time],qs.[max_clr_time],qs.[total_elapsed_time],qs.[last_elapsed_time],qs.[min_elapsed_time],qs.[max_elapsed_time],qs.[query_hash],qs.[query_plan_hash] - FROM sys.dm_exec_query_stats qs - ORDER BY qs.total_worker_time DESC) - INSERT INTO #dm_exec_query_stats ([sql_handle],[statement_start_offset],[statement_end_offset],[plan_generation_num],[plan_handle],[creation_time],[last_execution_time],[execution_count],[total_worker_time],[last_worker_time],[min_worker_time],[max_worker_time],[total_physical_reads],[last_physical_reads],[min_physical_reads],[max_physical_reads],[total_logical_writes],[last_logical_writes],[min_logical_writes],[max_logical_writes],[total_logical_reads],[last_logical_reads],[min_logical_reads],[max_logical_reads],[total_clr_time],[last_clr_time],[min_clr_time],[max_clr_time],[total_elapsed_time],[last_elapsed_time],[min_elapsed_time],[max_elapsed_time],[query_hash],[query_plan_hash]) - SELECT qs.[sql_handle],qs.[statement_start_offset],qs.[statement_end_offset],qs.[plan_generation_num],qs.[plan_handle],qs.[creation_time],qs.[last_execution_time],qs.[execution_count],qs.[total_worker_time],qs.[last_worker_time],qs.[min_worker_time],qs.[max_worker_time],qs.[total_physical_reads],qs.[last_physical_reads],qs.[min_physical_reads],qs.[max_physical_reads],qs.[total_logical_writes],qs.[last_logical_writes],qs.[min_logical_writes],qs.[max_logical_writes],qs.[total_logical_reads],qs.[last_logical_reads],qs.[min_logical_reads],qs.[max_logical_reads],qs.[total_clr_time],qs.[last_clr_time],qs.[min_clr_time],qs.[max_clr_time],qs.[total_elapsed_time],qs.[last_elapsed_time],qs.[min_elapsed_time],qs.[max_elapsed_time],qs.[query_hash],qs.[query_plan_hash] - FROM queries qs - LEFT OUTER JOIN #dm_exec_query_stats qsCaught ON qs.sql_handle = qsCaught.sql_handle AND qs.plan_handle = qsCaught.plan_handle AND qs.statement_start_offset = qsCaught.statement_start_offset - WHERE qsCaught.sql_handle IS NULL OPTION (RECOMPILE);'; - EXECUTE(@StringToExecute); - END; +RAISERROR('Fill out the most recent log for that full, but before the next full', 0, 1) WITH NOWAIT; - IF @CheckProcedureCacheFilter = 'Reads' - OR @CheckProcedureCacheFilter IS NULL - BEGIN - SET @StringToExecute = 'WITH queries ([sql_handle],[statement_start_offset],[statement_end_offset],[plan_generation_num],[plan_handle],[creation_time],[last_execution_time],[execution_count],[total_worker_time],[last_worker_time],[min_worker_time],[max_worker_time],[total_physical_reads],[last_physical_reads],[min_physical_reads],[max_physical_reads],[total_logical_writes],[last_logical_writes],[min_logical_writes],[max_logical_writes],[total_logical_reads],[last_logical_reads],[min_logical_reads],[max_logical_reads],[total_clr_time],[last_clr_time],[min_clr_time],[max_clr_time],[total_elapsed_time],[last_elapsed_time],[min_elapsed_time],[max_elapsed_time],[query_hash],[query_plan_hash]) - AS (SELECT TOP 20 qs.[sql_handle],qs.[statement_start_offset],qs.[statement_end_offset],qs.[plan_generation_num],qs.[plan_handle],qs.[creation_time],qs.[last_execution_time],qs.[execution_count],qs.[total_worker_time],qs.[last_worker_time],qs.[min_worker_time],qs.[max_worker_time],qs.[total_physical_reads],qs.[last_physical_reads],qs.[min_physical_reads],qs.[max_physical_reads],qs.[total_logical_writes],qs.[last_logical_writes],qs.[min_logical_writes],qs.[max_logical_writes],qs.[total_logical_reads],qs.[last_logical_reads],qs.[min_logical_reads],qs.[max_logical_reads],qs.[total_clr_time],qs.[last_clr_time],qs.[min_clr_time],qs.[max_clr_time],qs.[total_elapsed_time],qs.[last_elapsed_time],qs.[min_elapsed_time],qs.[max_elapsed_time],qs.[query_hash],qs.[query_plan_hash] - FROM sys.dm_exec_query_stats qs - ORDER BY qs.total_logical_reads DESC) - INSERT INTO #dm_exec_query_stats ([sql_handle],[statement_start_offset],[statement_end_offset],[plan_generation_num],[plan_handle],[creation_time],[last_execution_time],[execution_count],[total_worker_time],[last_worker_time],[min_worker_time],[max_worker_time],[total_physical_reads],[last_physical_reads],[min_physical_reads],[max_physical_reads],[total_logical_writes],[last_logical_writes],[min_logical_writes],[max_logical_writes],[total_logical_reads],[last_logical_reads],[min_logical_reads],[max_logical_reads],[total_clr_time],[last_clr_time],[min_clr_time],[max_clr_time],[total_elapsed_time],[last_elapsed_time],[min_elapsed_time],[max_elapsed_time],[query_hash],[query_plan_hash]) - SELECT qs.[sql_handle],qs.[statement_start_offset],qs.[statement_end_offset],qs.[plan_generation_num],qs.[plan_handle],qs.[creation_time],qs.[last_execution_time],qs.[execution_count],qs.[total_worker_time],qs.[last_worker_time],qs.[min_worker_time],qs.[max_worker_time],qs.[total_physical_reads],qs.[last_physical_reads],qs.[min_physical_reads],qs.[max_physical_reads],qs.[total_logical_writes],qs.[last_logical_writes],qs.[min_logical_writes],qs.[max_logical_writes],qs.[total_logical_reads],qs.[last_logical_reads],qs.[min_logical_reads],qs.[max_logical_reads],qs.[total_clr_time],qs.[last_clr_time],qs.[min_clr_time],qs.[max_clr_time],qs.[total_elapsed_time],qs.[last_elapsed_time],qs.[min_elapsed_time],qs.[max_elapsed_time],qs.[query_hash],qs.[query_plan_hash] - FROM queries qs - LEFT OUTER JOIN #dm_exec_query_stats qsCaught ON qs.sql_handle = qsCaught.sql_handle AND qs.plan_handle = qsCaught.plan_handle AND qs.statement_start_offset = qsCaught.statement_start_offset - WHERE qsCaught.sql_handle IS NULL OPTION (RECOMPILE);'; - EXECUTE(@StringToExecute); - END; + SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - IF @CheckProcedureCacheFilter = 'ExecCount' - OR @CheckProcedureCacheFilter IS NULL - BEGIN - SET @StringToExecute = 'WITH queries ([sql_handle],[statement_start_offset],[statement_end_offset],[plan_generation_num],[plan_handle],[creation_time],[last_execution_time],[execution_count],[total_worker_time],[last_worker_time],[min_worker_time],[max_worker_time],[total_physical_reads],[last_physical_reads],[min_physical_reads],[max_physical_reads],[total_logical_writes],[last_logical_writes],[min_logical_writes],[max_logical_writes],[total_logical_reads],[last_logical_reads],[min_logical_reads],[max_logical_reads],[total_clr_time],[last_clr_time],[min_clr_time],[max_clr_time],[total_elapsed_time],[last_elapsed_time],[min_elapsed_time],[max_elapsed_time],[query_hash],[query_plan_hash]) - AS (SELECT TOP 20 qs.[sql_handle],qs.[statement_start_offset],qs.[statement_end_offset],qs.[plan_generation_num],qs.[plan_handle],qs.[creation_time],qs.[last_execution_time],qs.[execution_count],qs.[total_worker_time],qs.[last_worker_time],qs.[min_worker_time],qs.[max_worker_time],qs.[total_physical_reads],qs.[last_physical_reads],qs.[min_physical_reads],qs.[max_physical_reads],qs.[total_logical_writes],qs.[last_logical_writes],qs.[min_logical_writes],qs.[max_logical_writes],qs.[total_logical_reads],qs.[last_logical_reads],qs.[min_logical_reads],qs.[max_logical_reads],qs.[total_clr_time],qs.[last_clr_time],qs.[min_clr_time],qs.[max_clr_time],qs.[total_elapsed_time],qs.[last_elapsed_time],qs.[min_elapsed_time],qs.[max_elapsed_time],qs.[query_hash],qs.[query_plan_hash] - FROM sys.dm_exec_query_stats qs - ORDER BY qs.execution_count DESC) - INSERT INTO #dm_exec_query_stats ([sql_handle],[statement_start_offset],[statement_end_offset],[plan_generation_num],[plan_handle],[creation_time],[last_execution_time],[execution_count],[total_worker_time],[last_worker_time],[min_worker_time],[max_worker_time],[total_physical_reads],[last_physical_reads],[min_physical_reads],[max_physical_reads],[total_logical_writes],[last_logical_writes],[min_logical_writes],[max_logical_writes],[total_logical_reads],[last_logical_reads],[min_logical_reads],[max_logical_reads],[total_clr_time],[last_clr_time],[min_clr_time],[max_clr_time],[total_elapsed_time],[last_elapsed_time],[min_elapsed_time],[max_elapsed_time],[query_hash],[query_plan_hash]) - SELECT qs.[sql_handle],qs.[statement_start_offset],qs.[statement_end_offset],qs.[plan_generation_num],qs.[plan_handle],qs.[creation_time],qs.[last_execution_time],qs.[execution_count],qs.[total_worker_time],qs.[last_worker_time],qs.[min_worker_time],qs.[max_worker_time],qs.[total_physical_reads],qs.[last_physical_reads],qs.[min_physical_reads],qs.[max_physical_reads],qs.[total_logical_writes],qs.[last_logical_writes],qs.[min_logical_writes],qs.[max_logical_writes],qs.[total_logical_reads],qs.[last_logical_reads],qs.[min_logical_reads],qs.[max_logical_reads],qs.[total_clr_time],qs.[last_clr_time],qs.[min_clr_time],qs.[max_clr_time],qs.[total_elapsed_time],qs.[last_elapsed_time],qs.[min_elapsed_time],qs.[max_elapsed_time],qs.[query_hash],qs.[query_plan_hash] - FROM queries qs - LEFT OUTER JOIN #dm_exec_query_stats qsCaught ON qs.sql_handle = qsCaught.sql_handle AND qs.plan_handle = qsCaught.plan_handle AND qs.statement_start_offset = qsCaught.statement_start_offset - WHERE qsCaught.sql_handle IS NULL OPTION (RECOMPILE);'; - EXECUTE(@StringToExecute); - END; + SET @StringToExecute += N' + UPDATE rp + SET log_last_lsn = (SELECT MAX(last_lsn) FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset bLog WHERE bLog.first_lsn >= rp.full_last_lsn AND bLog.first_lsn <= rpNextFull.full_last_lsn AND bLog.type = ''L'') + FROM #RTORecoveryPoints rp + INNER JOIN #RTORecoveryPoints rpNextFull ON rp.database_guid = rpNextFull.database_guid AND rp.database_name = rpNextFull.database_name + AND rp.full_last_lsn < rpNextFull.full_last_lsn + LEFT OUTER JOIN #RTORecoveryPoints rpEarlierFull ON rp.database_guid = rpEarlierFull.database_guid AND rp.database_name = rpEarlierFull.database_name + AND rp.full_last_lsn < rpEarlierFull.full_last_lsn + AND rpNextFull.full_last_lsn > rpEarlierFull.full_last_lsn + WHERE rpEarlierFull.full_backup_set_id IS NULL; + '; - IF @CheckProcedureCacheFilter = 'Duration' - OR @CheckProcedureCacheFilter IS NULL - BEGIN - SET @StringToExecute = 'WITH queries ([sql_handle],[statement_start_offset],[statement_end_offset],[plan_generation_num],[plan_handle],[creation_time],[last_execution_time],[execution_count],[total_worker_time],[last_worker_time],[min_worker_time],[max_worker_time],[total_physical_reads],[last_physical_reads],[min_physical_reads],[max_physical_reads],[total_logical_writes],[last_logical_writes],[min_logical_writes],[max_logical_writes],[total_logical_reads],[last_logical_reads],[min_logical_reads],[max_logical_reads],[total_clr_time],[last_clr_time],[min_clr_time],[max_clr_time],[total_elapsed_time],[last_elapsed_time],[min_elapsed_time],[max_elapsed_time],[query_hash],[query_plan_hash]) - AS (SELECT TOP 20 qs.[sql_handle],qs.[statement_start_offset],qs.[statement_end_offset],qs.[plan_generation_num],qs.[plan_handle],qs.[creation_time],qs.[last_execution_time],qs.[execution_count],qs.[total_worker_time],qs.[last_worker_time],qs.[min_worker_time],qs.[max_worker_time],qs.[total_physical_reads],qs.[last_physical_reads],qs.[min_physical_reads],qs.[max_physical_reads],qs.[total_logical_writes],qs.[last_logical_writes],qs.[min_logical_writes],qs.[max_logical_writes],qs.[total_logical_reads],qs.[last_logical_reads],qs.[min_logical_reads],qs.[max_logical_reads],qs.[total_clr_time],qs.[last_clr_time],qs.[min_clr_time],qs.[max_clr_time],qs.[total_elapsed_time],qs.[last_elapsed_time],qs.[min_elapsed_time],qs.[max_elapsed_time],qs.[query_hash],qs.[query_plan_hash] - FROM sys.dm_exec_query_stats qs - ORDER BY qs.total_elapsed_time DESC) - INSERT INTO #dm_exec_query_stats ([sql_handle],[statement_start_offset],[statement_end_offset],[plan_generation_num],[plan_handle],[creation_time],[last_execution_time],[execution_count],[total_worker_time],[last_worker_time],[min_worker_time],[max_worker_time],[total_physical_reads],[last_physical_reads],[min_physical_reads],[max_physical_reads],[total_logical_writes],[last_logical_writes],[min_logical_writes],[max_logical_writes],[total_logical_reads],[last_logical_reads],[min_logical_reads],[max_logical_reads],[total_clr_time],[last_clr_time],[min_clr_time],[max_clr_time],[total_elapsed_time],[last_elapsed_time],[min_elapsed_time],[max_elapsed_time],[query_hash],[query_plan_hash]) - SELECT qs.[sql_handle],qs.[statement_start_offset],qs.[statement_end_offset],qs.[plan_generation_num],qs.[plan_handle],qs.[creation_time],qs.[last_execution_time],qs.[execution_count],qs.[total_worker_time],qs.[last_worker_time],qs.[min_worker_time],qs.[max_worker_time],qs.[total_physical_reads],qs.[last_physical_reads],qs.[min_physical_reads],qs.[max_physical_reads],qs.[total_logical_writes],qs.[last_logical_writes],qs.[min_logical_writes],qs.[max_logical_writes],qs.[total_logical_reads],qs.[last_logical_reads],qs.[min_logical_reads],qs.[max_logical_reads],qs.[total_clr_time],qs.[last_clr_time],qs.[min_clr_time],qs.[max_clr_time],qs.[total_elapsed_time],qs.[last_elapsed_time],qs.[min_elapsed_time],qs.[max_elapsed_time],qs.[query_hash],qs.[query_plan_hash] - FROM queries qs - LEFT OUTER JOIN #dm_exec_query_stats qsCaught ON qs.sql_handle = qsCaught.sql_handle AND qs.plan_handle = qsCaught.plan_handle AND qs.statement_start_offset = qsCaught.statement_start_offset - WHERE qsCaught.sql_handle IS NULL OPTION (RECOMPILE);'; - EXECUTE(@StringToExecute); - END; + IF @Debug = 1 + PRINT @StringToExecute; - /* Populate the query_plan_filtered field. Only works in 2005SP2+, but we're just doing it in 2008 to be safe. */ - UPDATE #dm_exec_query_stats - SET query_plan_filtered = qp.query_plan - FROM #dm_exec_query_stats qs - CROSS APPLY sys.dm_exec_text_query_plan(qs.plan_handle, - qs.statement_start_offset, - qs.statement_end_offset) - AS qp; + EXEC sys.sp_executesql @StringToExecute; - END; +/* Fill out a diff in that range */ - /* Populate the additional query_plan, text, and text_filtered fields */ - UPDATE #dm_exec_query_stats - SET query_plan = qp.query_plan , - [text] = st.[text] , - text_filtered = SUBSTRING(st.text, - ( qs.statement_start_offset - / 2 ) + 1, - ( ( CASE qs.statement_end_offset - WHEN -1 - THEN DATALENGTH(st.text) - ELSE qs.statement_end_offset - END - - qs.statement_start_offset ) - / 2 ) + 1) - FROM #dm_exec_query_stats qs - CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) AS st - CROSS APPLY sys.dm_exec_query_plan(qs.plan_handle) - AS qp; +RAISERROR('Fill out a diff in that range', 0, 1) WITH NOWAIT; - /* Dump instances of our own script. We're not trying to tune ourselves. */ - DELETE #dm_exec_query_stats - WHERE text LIKE '%sp_Blitz%' - OR text LIKE '%#BlitzResults%'; + SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - /* Look for implicit conversions */ + SET @StringToExecute += N' + UPDATE #RTORecoveryPoints + SET diff_last_lsn = (SELECT TOP 1 bDiff.last_lsn FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset bDiff + WHERE rp.database_guid = bDiff.database_guid AND rp.database_name = bDiff.database_name + AND bDiff.type = ''I'' + AND bDiff.last_lsn < rp.log_last_lsn + AND rp.full_backup_set_uuid = bDiff.differential_base_guid + ORDER BY bDiff.last_lsn DESC) + FROM #RTORecoveryPoints rp + WHERE diff_last_lsn IS NULL; + '; - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 63 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 63) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details , - QueryPlan , - QueryPlanFiltered - ) - SELECT 63 AS CheckID , - 120 AS Priority , - 'Query Plans' AS FindingsGroup , - 'Implicit Conversion' AS Finding , - 'https://www.brentozar.com/go/implicit' AS URL , - ( 'One of the top resource-intensive queries is comparing two fields that are not the same datatype.' ) AS Details , - qs.query_plan , - qs.query_plan_filtered - FROM #dm_exec_query_stats qs - WHERE COALESCE(qs.query_plan_filtered, - CAST(qs.query_plan AS NVARCHAR(MAX))) LIKE '%CONVERT_IMPLICIT%' - AND COALESCE(qs.query_plan_filtered, - CAST(qs.query_plan AS NVARCHAR(MAX))) LIKE '%PhysicalOp="Index Scan"%'; - END; + IF @Debug = 1 + PRINT @StringToExecute; - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 64 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 64) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details , - QueryPlan , - QueryPlanFiltered - ) - SELECT 64 AS CheckID , - 120 AS Priority , - 'Query Plans' AS FindingsGroup , - 'Implicit Conversion Affecting Cardinality' AS Finding , - 'https://www.brentozar.com/go/implicit' AS URL , - ( 'One of the top resource-intensive queries has an implicit conversion that is affecting cardinality estimation.' ) AS Details , - qs.query_plan , - qs.query_plan_filtered - FROM #dm_exec_query_stats qs - WHERE COALESCE(qs.query_plan_filtered, - CAST(qs.query_plan AS NVARCHAR(MAX))) LIKE '%= 10 - AND NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 187 ) - IF SERVERPROPERTY('IsHadrEnabled') = 1 - BEGIN +/* Get time & size totals for logs */ - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 187) WITH NOWAIT; - - INSERT INTO [#BlitzResults] - ( [CheckID] , - [Priority] , - [FindingsGroup] , - [Finding] , - [URL] , - [Details] ) - SELECT - 187 AS [CheckID] , - 230 AS [Priority] , - 'Security' AS [FindingsGroup] , - 'Endpoints Owned by Users' AS [Finding] , - 'https://www.brentozar.com/go/owners' AS [URL] , - ( 'Endpoint ' + ep.[name] + ' is owned by ' + SUSER_NAME(ep.principal_id) + '. If the endpoint owner login is disabled or not available due to Active Directory problems, the high availability will stop working.' - ) AS [Details] - FROM sys.database_mirroring_endpoints ep - LEFT OUTER JOIN sys.dm_server_services s ON SUSER_NAME(ep.principal_id) = s.service_account - WHERE s.service_account IS NULL AND ep.principal_id <> 1; - END; +RAISERROR('Get time & size totals for logs', 0, 1) WITH NOWAIT; + SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - /*Verify that the servername is set */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 70 ) - BEGIN - IF @@SERVERNAME IS NULL - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 70) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 70 AS CheckID , - 200 AS Priority , - 'Informational' AS FindingsGroup , - '@@Servername Not Set' AS Finding , - 'https://www.brentozar.com/go/servername' AS URL , - '@@Servername variable is null. You can fix it by executing: "sp_addserver '''', local"' AS Details; - END; + SET @StringToExecute += N' + WITH LogTotals AS ( + SELECT rp.id, log_time_seconds = SUM(DATEDIFF(ss,bLog.backup_start_date, bLog.backup_finish_date)) + , log_file_size = SUM(bLog.backup_size) + , SUM(1) AS log_backups + FROM #RTORecoveryPoints rp + INNER JOIN ' + QUOTENAME(@MSDBName) + N'.dbo.backupset bLog ON rp.database_guid = bLog.database_guid AND rp.database_name = bLog.database_name AND bLog.type = ''L'' + AND bLog.first_lsn > COALESCE(rp.diff_last_lsn, rp.full_last_lsn) + AND bLog.first_lsn <= rp.log_last_lsn + GROUP BY rp.id + ) + UPDATE #RTORecoveryPoints + SET log_time_seconds = lt.log_time_seconds + , log_file_size_mb = lt.log_file_size / 1048576.0 + , log_backups = lt.log_backups + FROM #RTORecoveryPoints rp + INNER JOIN LogTotals lt ON rp.id = lt.id; + '; - IF /* @@SERVERNAME IS set */ - (@@SERVERNAME IS NOT NULL - AND - /* not a named instance */ - CHARINDEX(CHAR(92),CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128))) = 0 - AND - /* not clustered, when computername may be different than the servername */ - SERVERPROPERTY('IsClustered') = 0 - AND - /* @@SERVERNAME is different than the computer name */ - @@SERVERNAME <> CAST(ISNULL(SERVERPROPERTY('ComputerNamePhysicalNetBIOS'),@@SERVERNAME) AS NVARCHAR(128)) ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 70) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 70 AS CheckID , - 200 AS Priority , - 'Configuration' AS FindingsGroup , - '@@Servername Not Correct' AS Finding , - 'https://www.brentozar.com/go/servername' AS URL , - 'The @@Servername is different than the computer name, which may trigger certificate errors.' AS Details; - END; + IF @Debug = 1 + PRINT @StringToExecute; - END; - /*Check to see if a failsafe operator has been configured*/ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 73 ) - BEGIN + EXEC sys.sp_executesql @StringToExecute; - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 73) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 73 AS CheckID , - 200 AS Priority , - 'Monitoring' AS FindingsGroup , - 'No Failsafe Operator Configured' AS Finding , - 'https://www.brentozar.com/go/failsafe' AS URL , - ( 'No failsafe operator is configured on this server. This is a good idea just in-case there are issues with the [msdb] database that prevents alerting.' ) AS Details - FROM #AlertInfo - WHERE FailSafeOperator IS NULL; - END; +RAISERROR('Gathering RTO worst cases', 0, 1) WITH NOWAIT; - /*Identify globally enabled trace flags*/ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 74 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 74) WITH NOWAIT; - - INSERT INTO #TraceStatus - EXEC ( ' DBCC TRACESTATUS(-1) WITH NO_INFOMSGS' - ); - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details + SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; + + SET @StringToExecute += N' + WITH WorstCases AS ( + SELECT rp.* + FROM #RTORecoveryPoints rp + LEFT OUTER JOIN #RTORecoveryPoints rpNewer + ON rp.database_guid = rpNewer.database_guid + AND rp.database_name = rpNewer.database_name + AND rp.full_last_lsn < rpNewer.full_last_lsn + AND rpNewer.rto_worst_case_size_mb = (SELECT TOP 1 rto_worst_case_size_mb FROM #RTORecoveryPoints s WHERE rp.database_guid = s.database_guid AND rp.database_name = s.database_name ORDER BY rto_worst_case_size_mb DESC) + WHERE rp.rto_worst_case_size_mb = (SELECT TOP 1 rto_worst_case_size_mb FROM #RTORecoveryPoints s WHERE rp.database_guid = s.database_guid AND rp.database_name = s.database_name ORDER BY rto_worst_case_size_mb DESC) + /* OR rp.rto_worst_case_time_seconds = (SELECT TOP 1 rto_worst_case_time_seconds FROM #RTORecoveryPoints s WHERE rp.database_guid = s.database_guid AND rp.database_name = s.database_name ORDER BY rto_worst_case_time_seconds DESC) */ + AND rpNewer.database_guid IS NULL ) - SELECT 74 AS CheckID , - 200 AS Priority , - 'Informational' AS FindingsGroup , - 'Trace Flag On' AS Finding , - CASE WHEN [T].[TraceFlag] = '834' AND @ColumnStoreIndexesInUse = 1 THEN 'https://support.microsoft.com/en-us/kb/3210239' - ELSE'https://www.BrentOzar.com/go/traceflags/' END AS URL , - 'Trace flag ' + - CASE WHEN [T].[TraceFlag] = '652' THEN '652 enabled globally, which disables pre-fetching during index scans. This is usually a very bad idea.' - WHEN [T].[TraceFlag] = '661' THEN '661 enabled globally, which disables ghost record removal, causing the database to grow in size. This is usually a very bad idea.' - WHEN [T].[TraceFlag] = '834' AND @ColumnStoreIndexesInUse = 1 THEN '834 is enabled globally, but you also have columnstore indexes. That combination is not recommended by Microsoft.' - WHEN [T].[TraceFlag] = '1117' THEN '1117 enabled globally, which grows all files in a filegroup at the same time.' - WHEN [T].[TraceFlag] = '1118' THEN '1118 enabled globally, which tries to reduce SGAM waits.' - WHEN [T].[TraceFlag] = '1211' THEN '1211 enabled globally, which disables lock escalation when you least expect it. This is usually a very bad idea.' - WHEN [T].[TraceFlag] = '1204' THEN '1204 enabled globally, which captures deadlock graphs in the error log.' - WHEN [T].[TraceFlag] = '1222' THEN '1222 enabled globally, which captures deadlock graphs in the error log.' - WHEN [T].[TraceFlag] = '1224' THEN '1224 enabled globally, which disables lock escalation until the server has memory pressure. This is usually a very bad idea.' - WHEN [T].[TraceFlag] = '1806' THEN '1806 enabled globally, which disables Instant File Initialization, causing restores and file growths to take longer. This is usually a very bad idea.' - WHEN [T].[TraceFlag] = '2330' THEN '2330 enabled globally, which disables missing index requests. This is usually a very bad idea.' - WHEN [T].[TraceFlag] = '2371' THEN '2371 enabled globally, which changes the auto update stats threshold.' - WHEN [T].[TraceFlag] = '3023' THEN '3023 enabled globally, which performs checksums by default when doing database backups.' - WHEN [T].[TraceFlag] = '3226' THEN '3226 enabled globally, which keeps the event log clean by not reporting successful backups.' - WHEN [T].[TraceFlag] = '3505' THEN '3505 enabled globally, which disables Checkpoints. This is usually a very bad idea.' - WHEN [T].[TraceFlag] = '4199' THEN '4199 enabled globally, which enables non-default Query Optimizer fixes, changing query plans from the default behaviors.' - WHEN [T].[TraceFlag] = '8048' THEN '8048 enabled globally, which tries to reduce CMEMTHREAD waits on servers with a lot of logical processors.' - WHEN [T].[TraceFlag] = '8017' AND (CAST(SERVERPROPERTY('Edition') AS NVARCHAR(1000)) LIKE N'%Express%') THEN '8017 is enabled globally, but this is the default for Express Edition.' - WHEN [T].[TraceFlag] = '8017' AND (CAST(SERVERPROPERTY('Edition') AS NVARCHAR(1000)) NOT LIKE N'%Express%') THEN '8017 is enabled globally, which disables the creation of schedulers for all logical processors.' - WHEN [T].[TraceFlag] = '8649' THEN '8649 enabled globally, which cost threshold for parallelism down to 0. This is usually a very bad idea.' - ELSE [T].[TraceFlag] + ' is enabled globally.' END - AS Details - FROM #TraceStatus T; - END; + UPDATE #Backups + SET RTOWorstCaseMinutes = + /* Fulls */ + (CASE WHEN @RestoreSpeedFullMBps IS NULL + THEN wc.full_time_seconds / 60.0 + ELSE @RestoreSpeedFullMBps / wc.full_file_size_mb + END) - /* High CMEMTHREAD waits that could need trace flag 8048. - This check has to be run AFTER the globally enabled trace flag check, - since it uses the #TraceStatus table to know if flags are enabled. - */ - IF @ProductVersionMajor >= 11 AND NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 162 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 162) WITH NOWAIT; + /* Diffs, which might not have been taken */ + + (CASE WHEN @RestoreSpeedDiffMBps IS NOT NULL AND wc.diff_file_size_mb IS NOT NULL + THEN @RestoreSpeedDiffMBps / wc.diff_file_size_mb + ELSE COALESCE(wc.diff_time_seconds,0) / 60.0 + END) - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 162 AS CheckID , - 50 AS Priority , - 'Performance' AS FindingGroup , - 'Poison Wait Detected: CMEMTHREAD & NUMA' AS Finding , - 'https://www.brentozar.com/go/poison' AS URL , - CONVERT(VARCHAR(10), (MAX([wait_time_ms]) / 1000) / 86400) + ':' + CONVERT(VARCHAR(20), DATEADD(s, (MAX([wait_time_ms]) / 1000), 0), 108) + ' of this wait have been recorded' - + CASE WHEN ts.status = 1 THEN ' despite enabling trace flag 8048 already.' - ELSE '. In servers with over 8 cores per NUMA node, when CMEMTHREAD waits are a bottleneck, trace flag 8048 may be needed.' - END - FROM sys.dm_os_nodes n - INNER JOIN sys.[dm_os_wait_stats] w ON w.wait_type = 'CMEMTHREAD' - LEFT OUTER JOIN #TraceStatus ts ON ts.TraceFlag = 8048 AND ts.status = 1 - WHERE n.node_id = 0 AND n.online_scheduler_count >= 8 - AND EXISTS (SELECT * FROM sys.dm_os_nodes WHERE node_id > 0 AND node_state_desc NOT LIKE '%DAC') - GROUP BY w.wait_type, ts.status - HAVING SUM([wait_time_ms]) > (SELECT 5000 * datediff(HH,create_date,CURRENT_TIMESTAMP) AS hours_since_startup FROM sys.databases WHERE name='tempdb') - AND SUM([wait_time_ms]) > 60000; - END; + /* Logs, which might not have been taken */ + + (CASE WHEN @RestoreSpeedLogMBps IS NOT NULL AND wc.log_file_size_mb IS NOT NULL + THEN @RestoreSpeedLogMBps / wc.log_file_size_mb + ELSE COALESCE(wc.log_time_seconds,0) / 60.0 + END) + , RTOWorstCaseBackupFileSizeMB = wc.rto_worst_case_size_mb + FROM #Backups b + INNER JOIN WorstCases wc + ON b.database_guid = wc.database_guid + AND b.database_name = wc.database_name; + '; + IF @Debug = 1 + PRINT @StringToExecute; - /*Check for transaction log file larger than data file */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 75 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 75) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 75 AS CheckID , - DB_NAME(a.database_id) , - 50 AS Priority , - 'Reliability' AS FindingsGroup , - 'Transaction Log Larger than Data File' AS Finding , - 'https://www.brentozar.com/go/biglog' AS URL , - 'The database [' + DB_NAME(a.database_id) - + '] has a ' + CAST((CAST(a.size AS BIGINT) * 8 / 1000000) AS NVARCHAR(20)) + ' GB transaction log file, larger than the total data file sizes. This may indicate that transaction log backups are not being performed or not performed often enough.' AS Details - FROM sys.master_files a - WHERE a.type = 1 - AND DB_NAME(a.database_id) NOT IN ( - SELECT DISTINCT - DatabaseName - FROM #SkipChecks - WHERE CheckID = 75 OR CheckID IS NULL) - AND a.size > 125000 /* Size is measured in pages here, so this gets us log files over 1GB. */ - AND a.size > ( SELECT SUM(CAST(b.size AS BIGINT)) - FROM sys.master_files b - WHERE a.database_id = b.database_id - AND b.type = 0 - ) - AND a.database_id IN ( - SELECT database_id - FROM sys.databases - WHERE source_database_id IS NULL ); - END; + EXEC sys.sp_executesql @StringToExecute, N'@RestoreSpeedFullMBps INT, @RestoreSpeedDiffMBps INT, @RestoreSpeedLogMBps INT', @RestoreSpeedFullMBps, @RestoreSpeedDiffMBps, @RestoreSpeedLogMBps; - /*Check for collation conflicts between user databases and tempdb */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 76 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 76) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 76 AS CheckID , - name AS DatabaseName , - 200 AS Priority , - 'Informational' AS FindingsGroup , - 'Collation is ' + collation_name AS Finding , - 'https://www.brentozar.com/go/collate' AS URL , - 'Collation differences between user databases and tempdb can cause conflicts especially when comparing string values' AS Details - FROM sys.databases - WHERE name NOT IN ( 'master', 'model', 'msdb') - AND name NOT LIKE 'ReportServer%' - AND name NOT IN ( SELECT DISTINCT - DatabaseName - FROM #SkipChecks - WHERE CheckID IS NULL OR CheckID = 76) - AND collation_name <> ( SELECT - collation_name - FROM - sys.databases - WHERE - name = 'tempdb' - ); - END; - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 77 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 77) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 77 AS CheckID , - dSnap.[name] AS DatabaseName , - 170 AS Priority , - 'Reliability' AS FindingsGroup , - 'Database Snapshot Online' AS Finding , - 'https://www.brentozar.com/go/snapshot' AS URL , - 'Database [' + dSnap.[name] - + '] is a snapshot of [' - + dOriginal.[name] - + ']. Make sure you have enough drive space to maintain the snapshot as the original database grows.' AS Details - FROM sys.databases dSnap - INNER JOIN sys.databases dOriginal ON dSnap.source_database_id = dOriginal.database_id - AND dSnap.name NOT IN ( - SELECT DISTINCT DatabaseName - FROM #SkipChecks - WHERE CheckID = 77 OR CheckID IS NULL); - END; - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 79 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 79) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 79 AS CheckID , - -- sp_Blitz Issue #776 - -- Job has history and was executed in the last 30 days OR Job is enabled AND Job Schedule is enabled - CASE WHEN (cast(datediff(dd, substring(cast(sjh.run_date as nvarchar(10)), 1, 4) + '-' + substring(cast(sjh.run_date as nvarchar(10)), 5, 2) + '-' + substring(cast(sjh.run_date as nvarchar(10)), 7, 2), GETDATE()) AS INT) < 30) OR (j.[enabled] = 1 AND ssc.[enabled] = 1 )THEN - 100 - ELSE -- no job history (implicit) AND job not run in the past 30 days AND (Job disabled OR Job Schedule disabled) - 200 - END AS Priority, - 'Performance' AS FindingsGroup , - 'Shrink Database Job' AS Finding , - 'https://www.brentozar.com/go/autoshrink' AS URL , - 'In the [' + j.[name] + '] job, step [' - + step.[step_name] - + '] has SHRINKDATABASE or SHRINKFILE, which may be causing database fragmentation.' - + CASE WHEN COALESCE(ssc.name,'0') != '0' THEN + ' (Schedule: [' + ssc.name + '])' ELSE + '' END AS Details - FROM msdb.dbo.sysjobs j - INNER JOIN msdb.dbo.sysjobsteps step ON j.job_id = step.job_id - LEFT OUTER JOIN msdb.dbo.sysjobschedules AS sjsc - ON j.job_id = sjsc.job_id - LEFT OUTER JOIN msdb.dbo.sysschedules AS ssc - ON sjsc.schedule_id = ssc.schedule_id - AND sjsc.job_id = j.job_id - LEFT OUTER JOIN msdb.dbo.sysjobhistory AS sjh - ON j.job_id = sjh.job_id - AND step.step_id = sjh.step_id - AND sjh.run_date IN (SELECT max(sjh2.run_date) FROM msdb.dbo.sysjobhistory AS sjh2 WHERE sjh2.job_id = j.job_id) -- get the latest entry date - AND sjh.run_time IN (SELECT max(sjh3.run_time) FROM msdb.dbo.sysjobhistory AS sjh3 WHERE sjh3.job_id = j.job_id AND sjh3.run_date = sjh.run_date) -- get the latest entry time - WHERE step.command LIKE N'%SHRINKDATABASE%' - OR step.command LIKE N'%SHRINKFILE%'; - END; +/*Populating Recoverability*/ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 81 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 81) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 81 AS CheckID , - 200 AS Priority , - 'Non-Active Server Config' AS FindingsGroup , - cr.name AS Finding , - 'https://www.BrentOzar.com/blitz/sp_configure/' AS URL , - ( 'This sp_configure option isn''t running under its set value. Its set value is ' - + CAST(cr.[value] AS VARCHAR(100)) - + ' and its running value is ' - + CAST(cr.value_in_use AS VARCHAR(100)) - + '. When someone does a RECONFIGURE or restarts the instance, this setting will start taking effect.' ) AS Details - FROM sys.configurations cr - WHERE cr.value <> cr.value_in_use - AND NOT (cr.name = 'min server memory (MB)' AND cr.value IN (0,16) AND cr.value_in_use IN (0,16)); - END; - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 123 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 123) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT TOP 1 123 AS CheckID , - 200 AS Priority , - 'Informational' AS FindingsGroup , - 'Agent Jobs Starting Simultaneously' AS Finding , - 'https://www.brentozar.com/go/busyagent/' AS URL , - ( 'Multiple SQL Server Agent jobs are configured to start simultaneously. For detailed schedule listings, see the query in the URL.' ) AS Details - FROM msdb.dbo.sysjobactivity - WHERE start_execution_date > DATEADD(dd, -14, GETDATE()) - GROUP BY start_execution_date HAVING COUNT(*) > 1; - END; + /*Get distinct list of databases*/ + SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - IF @CheckServerInfo = 1 - BEGIN + SET @StringToExecute += N' + SELECT DISTINCT b.database_name, database_guid + FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b;' -/*This checks Windows version. It would be better if Microsoft gave everything a separate build number, but whatever.*/ -IF @ProductVersionMajor >= 10 - AND NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 172 ) - BEGIN - -- sys.dm_os_host_info includes both Windows and Linux info - IF EXISTS (SELECT 1 - FROM sys.all_objects - WHERE name = 'dm_os_host_info' ) - BEGIN + IF @Debug = 1 + PRINT @StringToExecute; - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 172) WITH NOWAIT; - - INSERT INTO [#BlitzResults] - ( [CheckID] , - [Priority] , - [FindingsGroup] , - [Finding] , - [URL] , - [Details] ) + INSERT #Recoverability ( DatabaseName, DatabaseGUID ) + EXEC sys.sp_executesql @StringToExecute; - SELECT - 172 AS [CheckID] , - 250 AS [Priority] , - 'Server Info' AS [FindingsGroup] , - 'Operating System Version' AS [Finding] , - ( CASE WHEN @IsWindowsOperatingSystem = 1 - THEN 'https://en.wikipedia.org/wiki/List_of_Microsoft_Windows_versions' - ELSE 'https://en.wikipedia.org/wiki/List_of_Linux_distributions' - END - ) AS [URL] , - ( CASE - WHEN [ohi].[host_platform] = 'Linux' THEN 'You''re running the ' + CAST([ohi].[host_distribution] AS VARCHAR(35)) + ' distribution of ' + CAST([ohi].[host_platform] AS VARCHAR(35)) + ', version ' + CAST([ohi].[host_release] AS VARCHAR(5)) - WHEN [ohi].[host_platform] = 'Windows' AND [ohi].[host_release] = '5' THEN 'You''re running Windows 2000, version ' + CAST([ohi].[host_release] AS VARCHAR(5)) - WHEN [ohi].[host_platform] = 'Windows' AND [ohi].[host_release] > '5' THEN 'You''re running ' + CAST([ohi].[host_distribution] AS VARCHAR(50)) + ', version ' + CAST([ohi].[host_release] AS VARCHAR(5)) - ELSE 'You''re running ' + CAST([ohi].[host_distribution] AS VARCHAR(35)) + ', version ' + CAST([ohi].[host_release] AS VARCHAR(5)) - END - ) AS [Details] - FROM [sys].[dm_os_host_info] [ohi]; - END; - ELSE - BEGIN - -- Otherwise, stick with Windows-only detection - IF EXISTS ( SELECT 1 - FROM sys.all_objects - WHERE name = 'dm_os_windows_info' ) + /*Find most recent recovery model, backup size, and backup date*/ + SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 172) WITH NOWAIT; - - INSERT INTO [#BlitzResults] - ( [CheckID] , - [Priority] , - [FindingsGroup] , - [Finding] , - [URL] , - [Details] ) + SET @StringToExecute += N' + UPDATE r + SET r.LastBackupRecoveryModel = ca.recovery_model, + r.LastFullBackupSizeMB = ca.compressed_backup_size, + r.LastFullBackupDate = ca.backup_finish_date + FROM #Recoverability r + CROSS APPLY ( + SELECT TOP 1 b.recovery_model, (b.compressed_backup_size / 1048576.0) AS compressed_backup_size, b.backup_finish_date + FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b + WHERE r.DatabaseName = b.database_name + AND r.DatabaseGUID = b.database_guid + AND b.type = ''D'' + AND b.backup_finish_date > @StartTime + ORDER BY b.backup_finish_date DESC + ) ca;' - SELECT - 172 AS [CheckID] , - 250 AS [Priority] , - 'Server Info' AS [FindingsGroup] , - 'Windows Version' AS [Finding] , - 'https://en.wikipedia.org/wiki/List_of_Microsoft_Windows_versions' AS [URL] , - ( CASE - WHEN [owi].[windows_release] = '5' THEN 'You''re running Windows 2000, version ' + CAST([owi].[windows_release] AS VARCHAR(5)) - WHEN [owi].[windows_release] > '5' AND [owi].[windows_release] < '6' THEN 'You''re running Windows Server 2003/2003R2 era, version ' + CAST([owi].[windows_release] AS VARCHAR(5)) - WHEN [owi].[windows_release] >= '6' AND [owi].[windows_release] <= '6.1' THEN 'You''re running Windows Server 2008/2008R2 era, version ' + CAST([owi].[windows_release] AS VARCHAR(5)) - WHEN [owi].[windows_release] >= '6.2' AND [owi].[windows_release] <= '6.3' THEN 'You''re running Windows Server 2012/2012R2 era, version ' + CAST([owi].[windows_release] AS VARCHAR(5)) - WHEN [owi].[windows_release] = '10.0' THEN 'You''re running Windows Server 2016/2019 era, version ' + CAST([owi].[windows_release] AS VARCHAR(5)) - ELSE 'You''re running Windows Server, version ' + CAST([owi].[windows_release] AS VARCHAR(5)) - END - ) AS [Details] - FROM [sys].[dm_os_windows_info] [owi]; + IF @Debug = 1 + PRINT @StringToExecute; - END; - END; - END; + EXEC sys.sp_executesql @StringToExecute, N'@StartTime DATETIME2', @StartTime; -/* -This check hits the dm_os_process_memory system view -to see if locked_page_allocations_kb is > 0, -which could indicate that locked pages in memory is enabled. -*/ -IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 166 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 166) WITH NOWAIT; - - INSERT INTO [#BlitzResults] - ( [CheckID] , - [Priority] , - [FindingsGroup] , - [Finding] , - [URL] , - [Details] ) - SELECT - 166 AS [CheckID] , - 250 AS [Priority] , - 'Server Info' AS [FindingsGroup] , - 'Locked Pages In Memory Enabled' AS [Finding] , - 'https://www.brentozar.com/go/lpim' AS [URL] , - ( 'You currently have ' - + CASE WHEN [dopm].[locked_page_allocations_kb] / 1024. / 1024. > 0 - THEN CAST([dopm].[locked_page_allocations_kb] / 1024 / 1024 AS VARCHAR(100)) - + ' GB' - ELSE CAST([dopm].[locked_page_allocations_kb] / 1024 AS VARCHAR(100)) - + ' MB' - END + ' of pages locked in memory.' ) AS [Details] - FROM - [sys].[dm_os_process_memory] AS [dopm] - WHERE - [dopm].[locked_page_allocations_kb] > 0; - END; + /*Find first backup size and date*/ + SET @StringToExecute =N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - /* Server Info - Locked Pages In Memory Enabled - Check 166 - SQL Server 2016 SP1 and newer */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 166 ) - AND EXISTS ( SELECT * - FROM sys.all_objects o - INNER JOIN sys.all_columns c ON o.object_id = c.object_id - WHERE o.name = 'dm_os_sys_info' - AND c.name = 'sql_memory_model' ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 166) WITH NOWAIT; - - SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) - SELECT 166 AS CheckID , - 250 AS Priority , - ''Server Info'' AS FindingsGroup , - ''Memory Model Unconventional'' AS Finding , - ''https://www.brentozar.com/go/lpim'' AS URL , - ''Memory Model: '' + CAST(sql_memory_model_desc AS NVARCHAR(100)) - FROM sys.dm_os_sys_info WHERE sql_memory_model <> 1 OPTION (RECOMPILE);'; - - IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; - IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; + SET @StringToExecute += N' + UPDATE r + SET r.FirstFullBackupSizeMB = ca.compressed_backup_size, + r.FirstFullBackupDate = ca.backup_finish_date + FROM #Recoverability r + CROSS APPLY ( + SELECT TOP 1 (b.compressed_backup_size / 1048576.0) AS compressed_backup_size, b.backup_finish_date + FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b + WHERE r.DatabaseName = b.database_name + AND r.DatabaseGUID = b.database_guid + AND b.type = ''D'' + AND b.backup_finish_date > @StartTime + ORDER BY b.backup_finish_date ASC + ) ca;' - EXECUTE(@StringToExecute); - END; + IF @Debug = 1 + PRINT @StringToExecute; - /* Performance - Instant File Initialization Not Enabled - Check 192 */ - /* Server Info - Instant File Initialization Enabled - Check 193 */ - IF NOT EXISTS ( SELECT 1/0 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 192 /* IFI disabled check disabled */ - ) OR NOT EXISTS - ( SELECT 1/0 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 193 /* IFI enabled check disabled */ - ) - BEGIN - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d] and CheckId [%d].', 0, 1, 192, 193) WITH NOWAIT; + EXEC sys.sp_executesql @StringToExecute, N'@StartTime DATETIME2', @StartTime; - DECLARE @IFISetting varchar(1) = N'N' - ,@IFIReadDMVFailed bit = 0 - ,@IFIAllFailed bit = 0; - /* See if we can get the instant_file_initialization_enabled column from sys.dm_server_services */ - IF EXISTS - ( - SELECT 1/0 - FROM sys.all_columns - WHERE [object_id] = OBJECT_ID(N'[sys].[dm_server_services]') - AND [name] = N'instant_file_initialization_enabled' - ) - BEGIN - /* This needs to be a "dynamic" SQL statement because if the 'instant_file_initialization_enabled' column doesn't exist the procedure might fail on a bind error */ - SET @StringToExecute = N'SELECT @IFISetting = instant_file_initialization_enabled' + @crlf + - N'FROM sys.dm_server_services' + @crlf + - N'WHERE filename LIKE ''%sqlservr.exe%''' + @crlf + - N'OPTION (RECOMPILE);'; - - IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; - IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; - - EXEC dbo.sp_executesql - @StringToExecute - ,N'@IFISetting varchar(1) OUTPUT' - ,@IFISetting = @IFISetting OUTPUT - - SET @IFIReadDMVFailed = 0; - END - ELSE - /* We couldn't get the instant_file_initialization_enabled column from sys.dm_server_services, fall back to read error log */ - BEGIN - SET @IFIReadDMVFailed = 1; - /* If this is Amazon RDS, we'll use the rdsadmin.dbo.rds_read_error_log */ - IF LEFT(CAST(SERVERPROPERTY('ComputerNamePhysicalNetBIOS') AS VARCHAR(8000)), 8) = 'EC2AMAZ-' - AND LEFT(CAST(SERVERPROPERTY('MachineName') AS VARCHAR(8000)), 8) = 'EC2AMAZ-' - AND db_id('rdsadmin') IS NOT NULL - AND EXISTS ( SELECT 1/0 - FROM master.sys.all_objects - WHERE name IN ('rds_startup_tasks', 'rds_help_revlogin', 'rds_hexadecimal', 'rds_failover_tracking', 'rds_database_tracking', 'rds_track_change') - ) - BEGIN - /* Amazon RDS detected, read rdsadmin.dbo.rds_read_error_log */ - INSERT INTO #ErrorLog - EXEC rdsadmin.dbo.rds_read_error_log 0, 1, N'Database Instant File Initialization: enabled'; - END - ELSE - BEGIN - /* Try to read the error log, this might fail due to permissions */ - BEGIN TRY - INSERT INTO #ErrorLog - EXEC sys.xp_readerrorlog 0, 1, N'Database Instant File Initialization: enabled'; - END TRY - BEGIN CATCH - IF @Debug IN (1, 2) RAISERROR('No permissions to execute xp_readerrorlog.', 0, 1) WITH NOWAIT; - SET @IFIAllFailed = 1; - END CATCH - END; - END; + /*Find average backup throughputs for full, diff, and log*/ + SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - IF @IFIAllFailed = 0 - BEGIN - IF @IFIReadDMVFailed = 1 - /* We couldn't read the DMV so set the @IFISetting variable using the error log */ - BEGIN - IF EXISTS ( SELECT 1/0 - FROM #ErrorLog - WHERE LEFT([Text], 45) = N'Database Instant File Initialization: enabled' - ) - BEGIN - SET @IFISetting = 'Y'; - END - ELSE - BEGIN - SET @IFISetting = 'N'; - END; - END; + SET @StringToExecute += N' + UPDATE r + SET r.AvgFullBackupThroughputMB = ca_full.AvgFullSpeed, + r.AvgDiffBackupThroughputMB = ca_diff.AvgDiffSpeed, + r.AvgLogBackupThroughputMB = ca_log.AvgLogSpeed, + r.AvgFullBackupDurationSeconds = AvgFullDuration, + r.AvgDiffBackupDurationSeconds = AvgDiffDuration, + r.AvgLogBackupDurationSeconds = AvgLogDuration + FROM #Recoverability AS r + OUTER APPLY ( + SELECT b.database_name, + AVG( b.compressed_backup_size / ( DATEDIFF(ss, b.backup_start_date, b.backup_finish_date) ) / 1048576.0 ) AS AvgFullSpeed, + AVG( DATEDIFF(ss, b.backup_start_date, b.backup_finish_date) ) AS AvgFullDuration + FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset b + WHERE r.DatabaseName = b.database_name + AND r.DatabaseGUID = b.database_guid + AND b.type = ''D'' + AND DATEDIFF(SECOND, b.backup_start_date, b.backup_finish_date) > 0 + AND b.backup_finish_date > @StartTime + GROUP BY b.database_name + ) ca_full + OUTER APPLY ( + SELECT b.database_name, + AVG( b.compressed_backup_size / ( DATEDIFF(ss, b.backup_start_date, b.backup_finish_date) ) / 1048576.0 ) AS AvgDiffSpeed, + AVG( DATEDIFF(ss, b.backup_start_date, b.backup_finish_date) ) AS AvgDiffDuration + FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset b + WHERE r.DatabaseName = b.database_name + AND r.DatabaseGUID = b.database_guid + AND b.type = ''I'' + AND DATEDIFF(SECOND, b.backup_start_date, b.backup_finish_date) > 0 + AND b.backup_finish_date > @StartTime + GROUP BY b.database_name + ) ca_diff + OUTER APPLY ( + SELECT b.database_name, + AVG( b.compressed_backup_size / ( DATEDIFF(ss, b.backup_start_date, b.backup_finish_date) ) / 1048576.0 ) AS AvgLogSpeed, + AVG( DATEDIFF(ss, b.backup_start_date, b.backup_finish_date) ) AS AvgLogDuration + FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset b + WHERE r.DatabaseName = b.database_name + AND r.DatabaseGUID = b.database_guid + AND b.type = ''L'' + AND DATEDIFF(SECOND, b.backup_start_date, b.backup_finish_date) > 0 + AND b.backup_finish_date > @StartTime + GROUP BY b.database_name + ) ca_log;' - IF NOT EXISTS ( SELECT 1/0 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 192 /* IFI disabled check disabled */ - ) AND @IFISetting = 'N' - BEGIN - INSERT INTO #BlitzResults - ( - CheckID , - [Priority] , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT - 192 AS [CheckID] , - 50 AS [Priority] , - 'Performance' AS [FindingsGroup] , - 'Instant File Initialization Not Enabled' AS [Finding] , - 'https://www.brentozar.com/go/instant' AS [URL] , - 'Consider enabling IFI for faster restores and data file growths.' AS [Details] - END; + IF @Debug = 1 + PRINT @StringToExecute; - IF NOT EXISTS ( SELECT 1/0 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 193 /* IFI enabled check disabled */ - ) AND @IFISetting = 'Y' - BEGIN - INSERT INTO #BlitzResults - ( - CheckID , - [Priority] , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT - 193 AS [CheckID] , - 250 AS [Priority] , - 'Server Info' AS [FindingsGroup] , - 'Instant File Initialization Enabled' AS [Finding] , - 'https://www.brentozar.com/go/instant' AS [URL] , - 'The service account has the Perform Volume Maintenance Tasks permission.' AS [Details] - END; - END; - END; + EXEC sys.sp_executesql @StringToExecute, N'@StartTime DATETIME2', @StartTime; - /* End of checkId 192 */ - /* End of checkId 193 */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 130 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 130) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 130 AS CheckID , - 250 AS Priority , - 'Server Info' AS FindingsGroup , - 'Server Name' AS Finding , - 'https://www.brentozar.com/go/servername' AS URL , - @@SERVERNAME AS Details - WHERE @@SERVERNAME IS NOT NULL; - END; + /*Find max and avg diff and log sizes*/ + SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 83 ) - BEGIN - IF EXISTS ( SELECT * - FROM sys.all_objects - WHERE name = 'dm_server_services' ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 83) WITH NOWAIT; - - -- DATETIMEOFFSET and DATETIME have different minimum values, so there's - -- a small workaround here to force 1753-01-01 if the minimum is detected - SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) - SELECT 83 AS CheckID , - 250 AS Priority , - ''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 + ''.'' - FROM sys.dm_server_services OPTION (RECOMPILE);'; - - IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; - IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; - - EXECUTE(@StringToExecute); - END; - END; + SET @StringToExecute += N' + UPDATE r + SET r.AvgFullSizeMB = fulls.avg_full_size, + r.AvgDiffSizeMB = diffs.avg_diff_size, + r.AvgLogSizeMB = logs.avg_log_size + FROM #Recoverability AS r + OUTER APPLY ( + SELECT b.database_name, AVG(b.compressed_backup_size / 1048576.0) AS avg_full_size + FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b + WHERE r.DatabaseName = b.database_name + AND r.DatabaseGUID = b.database_guid + AND b.type = ''D'' + AND b.backup_finish_date > @StartTime + GROUP BY b.database_name + ) AS fulls + OUTER APPLY ( + SELECT b.database_name, AVG(b.compressed_backup_size / 1048576.0) AS avg_diff_size + FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b + WHERE r.DatabaseName = b.database_name + AND r.DatabaseGUID = b.database_guid + AND b.type = ''I'' + AND b.backup_finish_date > @StartTime + GROUP BY b.database_name + ) AS diffs + OUTER APPLY ( + SELECT b.database_name, AVG(b.compressed_backup_size / 1048576.0) AS avg_log_size + FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b + WHERE r.DatabaseName = b.database_name + AND r.DatabaseGUID = b.database_guid + AND b.type = ''L'' + AND b.backup_finish_date > @StartTime + GROUP BY b.database_name + ) AS logs;' - /* Check 84 - SQL Server 2012 */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 84 ) - BEGIN - IF EXISTS ( SELECT * - FROM sys.all_objects o - INNER JOIN sys.all_columns c ON o.object_id = c.object_id - WHERE o.name = 'dm_os_sys_info' - AND c.name = 'physical_memory_kb' ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 84) WITH NOWAIT; - - SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) - SELECT 84 AS CheckID , - 250 AS Priority , - ''Server Info'' AS FindingsGroup , - ''Hardware'' AS Finding , - '''' AS URL , - ''Logical processors: '' + CAST(cpu_count AS VARCHAR(50)) + ''. Physical memory: '' + CAST( CAST(ROUND((physical_memory_kb / 1024.0 / 1024), 1) AS INT) AS VARCHAR(50)) + ''GB.'' - FROM sys.dm_os_sys_info OPTION (RECOMPILE);'; - - IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; - IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; - - EXECUTE(@StringToExecute); - END; + IF @Debug = 1 + PRINT @StringToExecute; - /* Check 84 - SQL Server 2008 */ - IF EXISTS ( SELECT * - FROM sys.all_objects o - INNER JOIN sys.all_columns c ON o.object_id = c.object_id - WHERE o.name = 'dm_os_sys_info' - AND c.name = 'physical_memory_in_bytes' ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 84) WITH NOWAIT; - - SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) - SELECT 84 AS CheckID , - 250 AS Priority , - ''Server Info'' AS FindingsGroup , - ''Hardware'' AS Finding , - '''' AS URL , - ''Logical processors: '' + CAST(cpu_count AS VARCHAR(50)) + ''. Physical memory: '' + CAST( CAST(ROUND((physical_memory_in_bytes / 1024.0 / 1024 / 1024), 1) AS INT) AS VARCHAR(50)) + ''GB.'' - FROM sys.dm_os_sys_info OPTION (RECOMPILE);'; - - IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; - IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; - - EXECUTE(@StringToExecute); - END; - END; + EXEC sys.sp_executesql @StringToExecute, N'@StartTime DATETIME2', @StartTime; + +/*Trending - only works if backupfile is populated, which means in msdb */ +IF @MSDBName = N'msdb' +BEGIN + SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' --+ @crlf; - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 85 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 85) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 85 AS CheckID , - 250 AS Priority , - 'Server Info' AS FindingsGroup , - 'SQL Server Service' AS Finding , - '' AS URL , - N'Version: ' - + CAST(SERVERPROPERTY('productversion') AS NVARCHAR(100)) - + N'. Patch Level: ' - + CAST(SERVERPROPERTY('productlevel') AS NVARCHAR(100)) - + CASE WHEN SERVERPROPERTY('ProductUpdateLevel') IS NULL - THEN N'' - ELSE N'. Cumulative Update: ' - + CAST(SERVERPROPERTY('ProductUpdateLevel') AS NVARCHAR(100)) - END - + N'. Edition: ' - + CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) - + N'. Availability Groups Enabled: ' - + CAST(COALESCE(SERVERPROPERTY('IsHadrEnabled'), - 0) AS VARCHAR(100)) - + N'. Availability Groups Manager Status: ' - + CAST(COALESCE(SERVERPROPERTY('HadrManagerStatus'), - 0) AS VARCHAR(100)); - END; + SET @StringToExecute += N' + SELECT p.DatabaseName, + p.DatabaseGUID, + p.[0], + p.[-1], + p.[-2], + p.[-3], + p.[-4], + p.[-5], + p.[-6], + p.[-7], + p.[-8], + p.[-9], + p.[-10], + p.[-11], + p.[-12] + FROM ( SELECT b.database_name AS DatabaseName, + b.database_guid AS DatabaseGUID, + DATEDIFF(MONTH, @StartTime, b.backup_start_date) AS MonthsAgo , + CONVERT(DECIMAL(18, 2), AVG(bf.file_size / 1048576.0)) AS AvgSizeMB + FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b + INNER JOIN ' + QUOTENAME(@MSDBName) + N'.dbo.backupfile AS bf + ON b.backup_set_id = bf.backup_set_id + WHERE b.database_name NOT IN ( ''master'', ''msdb'', ''model'', ''tempdb'' ) + AND bf.file_type = ''D'' + AND b.backup_start_date >= DATEADD(YEAR, -1, @StartTime) + AND b.backup_start_date <= SYSDATETIME() + GROUP BY b.database_name, + b.database_guid, + DATEDIFF(mm, @StartTime, b.backup_start_date) + ) AS bckstat PIVOT ( SUM(bckstat.AvgSizeMB) FOR bckstat.MonthsAgo IN ( [0], [-1], [-2], [-3], [-4], [-5], [-6], [-7], [-8], [-9], [-10], [-11], [-12] ) ) AS p + ORDER BY p.DatabaseName; + ' - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 88 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 88) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 88 AS CheckID , - 250 AS Priority , - 'Server Info' AS FindingsGroup , - 'SQL Server Last Restart' AS Finding , - '' AS URL , - CAST(create_date AS VARCHAR(100)) - FROM sys.databases - WHERE database_id = 2; - END; + IF @Debug = 1 + PRINT @StringToExecute; - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 91 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 91) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 91 AS CheckID , - 250 AS Priority , - 'Server Info' AS FindingsGroup , - 'Server Last Restart' AS Finding , - '' AS URL , - CAST(DATEADD(SECOND, (ms_ticks/1000)*(-1), GETDATE()) AS nvarchar(25)) - FROM sys.dm_os_sys_info; - END; + INSERT #Trending ( DatabaseName, DatabaseGUID, [0], [-1], [-2], [-3], [-4], [-5], [-6], [-7], [-8], [-9], [-10], [-11], [-12] ) + EXEC sys.sp_executesql @StringToExecute, N'@StartTime DATETIME2', @StartTime; - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 92 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 92) WITH NOWAIT; - - INSERT INTO #driveInfo - ( drive, available_MB ) - EXEC master..xp_fixeddrives; - - IF EXISTS (SELECT * FROM sys.all_objects WHERE name = 'dm_os_volume_stats') - BEGIN - SET @StringToExecute = 'Update #driveInfo - SET - logical_volume_name = v.logical_volume_name, - total_MB = v.total_MB, - used_percent = v.used_percent - FROM - #driveInfo - inner join ( - SELECT DISTINCT - SUBSTRING(volume_mount_point, 1, 1) AS volume_mount_point - ,CASE WHEN ISNULL(logical_volume_name,'''') = '''' THEN '''' ELSE ''('' + logical_volume_name + '')'' END AS logical_volume_name - ,total_bytes/1024/1024 AS total_MB - ,available_bytes/1024/1024 AS available_MB - ,(CONVERT(DECIMAL(5,2),(total_bytes/1.0 - available_bytes)/total_bytes * 100)) AS used_percent - FROM - (SELECT TOP 1 WITH TIES - database_id - ,file_id - ,SUBSTRING(physical_name,1,1) AS Drive - FROM sys.master_files - ORDER BY ROW_NUMBER() OVER(PARTITION BY SUBSTRING(physical_name,1,1) ORDER BY database_id) - ) f - CROSS APPLY - sys.dm_os_volume_stats(f.database_id, f.file_id) - ) as v on #driveInfo.drive = v.volume_mount_point;'; - EXECUTE(@StringToExecute); - END; +END - SET @StringToExecute ='INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 92 AS CheckID , - 250 AS Priority , - ''Server Info'' AS FindingsGroup , - ''Drive '' + i.drive + '' Space'' AS Finding , - '''' AS URL , - CASE WHEN i.total_MB IS NULL THEN - CAST(CAST(i.available_MB/1024 AS NUMERIC(18,2)) AS VARCHAR(30)) - + '' GB free on '' + i.drive - + '' drive'' - ELSE CAST(CAST(i.available_MB/1024 AS NUMERIC(18,2)) AS VARCHAR(30)) - + '' GB free on '' + i.drive - + '' drive '' + i.logical_volume_name - + '' out of '' + CAST(CAST(i.total_MB/1024 AS NUMERIC(18,2)) AS VARCHAR(30)) - + '' GB total ('' + CAST(i.used_percent AS VARCHAR(30)) + ''% used)'' END - AS Details - FROM #driveInfo AS i;' +/*End Trending*/ - IF (@ProductVersionMajor >= 11) - BEGIN - SET @StringToExecute=REPLACE(@StringToExecute,'CAST(i.available_MB/1024 AS NUMERIC(18,2))','FORMAT(i.available_MB/1024,''N2'')'); - SET @StringToExecute=REPLACE(@StringToExecute,'CAST(i.total_MB/1024 AS NUMERIC(18,2))','FORMAT(i.total_MB/1024,''N2'')'); - END; +/*End populating Recoverability*/ - EXECUTE(@StringToExecute); +RAISERROR('Returning data', 0, 1) WITH NOWAIT; - DROP TABLE #driveInfo; - END; + SELECT b.* + FROM #Backups AS b + ORDER BY b.database_name; - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 103 ) - AND EXISTS ( SELECT * - FROM sys.all_objects o - INNER JOIN sys.all_columns c ON o.object_id = c.object_id - WHERE o.name = 'dm_os_sys_info' - AND c.name = 'virtual_machine_type_desc' ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 103) WITH NOWAIT; - - SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) - SELECT 103 AS CheckID, - 250 AS Priority, - ''Server Info'' AS FindingsGroup, - ''Virtual Server'' AS Finding, - ''https://www.brentozar.com/go/virtual'' AS URL, - ''Type: ('' + virtual_machine_type_desc + '')'' AS Details - FROM sys.dm_os_sys_info - WHERE virtual_machine_type <> 0 OPTION (RECOMPILE);'; - - IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; - IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; - - EXECUTE(@StringToExecute); - END; + SELECT r.*, + t.[0], t.[-1], t.[-2], t.[-3], t.[-4], t.[-5], t.[-6], t.[-7], t.[-8], t.[-9], t.[-10], t.[-11], t.[-12] + FROM #Recoverability AS r + LEFT JOIN #Trending t + ON r.DatabaseName = t.DatabaseName + AND r.DatabaseGUID = t.DatabaseGUID + WHERE r.LastBackupRecoveryModel IS NOT NULL + ORDER BY r.DatabaseName - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 214 ) - AND EXISTS ( SELECT * - FROM sys.all_objects o - INNER JOIN sys.all_columns c ON o.object_id = c.object_id - WHERE o.name = 'dm_os_sys_info' - AND c.name = 'container_type_desc' ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 214) WITH NOWAIT; - - SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) - SELECT 214 AS CheckID, - 250 AS Priority, - ''Server Info'' AS FindingsGroup, - ''Container'' AS Finding, - ''https://www.brentozar.com/go/virtual'' AS URL, - ''Type: ('' + container_type_desc + '')'' AS Details - FROM sys.dm_os_sys_info - WHERE container_type_desc <> ''NONE'' OPTION (RECOMPILE);'; - - IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; - IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; - - EXECUTE(@StringToExecute); - END; - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 114 ) - AND EXISTS ( SELECT * - FROM sys.all_objects o - WHERE o.name = 'dm_os_memory_nodes' ) - AND EXISTS ( SELECT * - FROM sys.all_objects o - INNER JOIN sys.all_columns c ON o.object_id = c.object_id - WHERE o.name = 'dm_os_nodes' - AND c.name = 'processor_group' ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 114) WITH NOWAIT; - - SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) - SELECT 114 AS CheckID , - 250 AS Priority , - ''Server Info'' AS FindingsGroup , - ''Hardware - NUMA Config'' AS Finding , - '''' AS URL , - ''Node: '' + CAST(n.node_id AS NVARCHAR(10)) + '' State: '' + node_state_desc - + '' Online schedulers: '' + CAST(n.online_scheduler_count AS NVARCHAR(10)) + '' Offline schedulers: '' + CAST(oac.offline_schedulers AS VARCHAR(100)) + '' Processor Group: '' + CAST(n.processor_group AS NVARCHAR(10)) - + '' Memory node: '' + CAST(n.memory_node_id AS NVARCHAR(10)) + '' Memory VAS Reserved GB: '' + CAST(CAST((m.virtual_address_space_reserved_kb / 1024.0 / 1024) AS INT) AS NVARCHAR(100)) - FROM sys.dm_os_nodes n - INNER JOIN sys.dm_os_memory_nodes m ON n.memory_node_id = m.memory_node_id - OUTER APPLY (SELECT - COUNT(*) AS [offline_schedulers] - FROM sys.dm_os_schedulers dos - WHERE n.node_id = dos.parent_node_id - AND dos.status = ''VISIBLE OFFLINE'' - ) oac - WHERE n.node_state_desc NOT LIKE ''%DAC%'' - ORDER BY n.node_id OPTION (RECOMPILE);'; - - IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; - IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; - - EXECUTE(@StringToExecute); - END; - +RAISERROR('Rules analysis starting', 0, 1) WITH NOWAIT; - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 211 ) - BEGIN - - /* Variables for check 211: */ - DECLARE - @powerScheme varchar(36) - ,@cpu_speed_mhz int - ,@cpu_speed_ghz decimal(18,2) - ,@ExecResult int; +/*Looking for out of band backups by finding most common backup operator user_name and noting backups taken by other user_names*/ - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 211) WITH NOWAIT; - IF @sa = 0 RAISERROR('The errors: ''xp_regread() returned error 5, ''Access is denied.'''' can be safely ignored', 0, 1) WITH NOWAIT; + SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - /* Get power plan if set by group policy [Git Hub Issue #1620] */ - EXEC xp_regread @rootkey = N'HKEY_LOCAL_MACHINE', - @key = N'SOFTWARE\Policies\Microsoft\Power\PowerSettings', - @value_name = N'ActivePowerScheme', - @value = @powerScheme OUTPUT, - @no_output = N'no_output'; + SET @StringToExecute += N' + WITH common_people AS ( + SELECT TOP 1 b.user_name, COUNT_BIG(*) AS Records + FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b + GROUP BY b.user_name + ORDER BY Records DESC + ) + SELECT + 1 AS CheckId, + 100 AS [Priority], + b.database_name AS [Database Name], + ''Non-Agent backups taken'' AS [Finding], + ''The database '' + QUOTENAME(b.database_name) + '' has been backed up by '' + QUOTENAME(b.user_name) + '' '' + CONVERT(VARCHAR(10), COUNT(*)) + '' times.'' AS [Warning] + FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b + WHERE b.user_name NOT LIKE ''%Agent%'' AND b.user_name NOT LIKE ''%AGENT%'' + AND NOT EXISTS ( + SELECT 1 + FROM common_people AS cp + WHERE cp.user_name = b.user_name + ) + GROUP BY b.database_name, b.user_name + HAVING COUNT(*) > 1;' + @crlf; - IF @powerScheme IS NULL /* If power plan was not set by group policy, get local value [Git Hub Issue #1620]*/ - EXEC xp_regread @rootkey = N'HKEY_LOCAL_MACHINE', - @key = N'SYSTEM\CurrentControlSet\Control\Power\User\PowerSchemes', - @value_name = N'ActivePowerScheme', - @value = @powerScheme OUTPUT; - - /* Get the cpu speed*/ - EXEC @ExecResult = xp_regread @rootkey = N'HKEY_LOCAL_MACHINE', - @key = N'HARDWARE\DESCRIPTION\System\CentralProcessor\0', - @value_name = N'~MHz', - @value = @cpu_speed_mhz OUTPUT; + IF @Debug = 1 + PRINT @StringToExecute; - /* Convert the Megahertz to Gigahertz */ - IF @ExecResult != 0 RAISERROR('We couldn''t retrieve the CPU speed, you will see Unknown in the results', 0, 1) + INSERT #Warnings (CheckId, Priority, DatabaseName, Finding, Warning ) + EXEC sys.sp_executesql @StringToExecute; + + /*Looking for compatibility level changing. Only looking for databases that have changed more than twice (It''s possible someone may have changed up, had CE problems, and then changed back)*/ - SET @cpu_speed_ghz = CAST(CAST(@cpu_speed_mhz AS decimal) / 1000 AS decimal(18,2)); + SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 211 AS CheckId, - 250 AS Priority, - 'Server Info' AS FindingsGroup, - 'Power Plan' AS Finding, - 'https://www.brentozar.com/blitz/power-mode/' AS URL, - 'Your server has ' - + ISNULL(CAST(@cpu_speed_ghz as VARCHAR(8)), 'Unknown ') - + 'GHz CPUs, and is in ' - + CASE @powerScheme - WHEN 'a1841308-3541-4fab-bc81-f71556f20b4a' - THEN 'power saving mode -- are you sure this is a production SQL Server?' - WHEN '381b4222-f694-41f0-9685-ff5bb260df2e' - THEN 'balanced power mode -- Uh... you want your CPUs to run at full speed, right?' - WHEN '8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c' - THEN 'high performance power mode' - WHEN 'e9a42b02-d5df-448d-aa00-03f14749eb61' - THEN 'ultimate performance power mode' - ELSE 'an unknown power mode.' - END AS Details - - END; + SET @StringToExecute += N'SELECT + 2 AS CheckId, + 100 AS [Priority], + b.database_name AS [Database Name], + ''Compatibility level changing'' AS [Finding], + ''The database '' + QUOTENAME(b.database_name) + '' has changed compatibility levels '' + CONVERT(VARCHAR(10), COUNT(DISTINCT b.compatibility_level)) + '' times.'' AS [Warning] + FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b + GROUP BY b.database_name + HAVING COUNT(DISTINCT b.compatibility_level) > 2;' + @crlf; - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 212 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 212) WITH NOWAIT; + IF @Debug = 1 + PRINT @StringToExecute; - INSERT INTO #Instances (Instance_Number, Instance_Name, Data_Field) - EXEC master.sys.xp_regread @rootkey = 'HKEY_LOCAL_MACHINE', - @key = 'SOFTWARE\Microsoft\Microsoft SQL Server', - @value_name = 'InstalledInstances' - - IF (SELECT COUNT(*) FROM #Instances) > 1 - BEGIN + INSERT #Warnings ( CheckId, Priority, DatabaseName, Finding, Warning ) + EXEC sys.sp_executesql @StringToExecute; + + /*Looking for password protected backups. This hasn''t been a popular option ever, and was largely replaced by encrypted backups, but it''s simple to check for.*/ - DECLARE @InstanceCount NVARCHAR(MAX) - SELECT @InstanceCount = COUNT(*) FROM #Instances + SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - INSERT INTO #BlitzResults - ( - CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT - 212 AS CheckId , - 250 AS Priority , - 'Server Info' AS FindingsGroup , - 'Instance Stacking' AS Finding , - 'https://www.brentozar.com/go/babygotstacked/' AS URL , - 'Your Server has ' + @InstanceCount + ' Instances of SQL Server installed. More than one is usually a bad idea. Read the URL for more info.' - END; - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 106 ) - AND (select convert(int,value_in_use) from sys.configurations where name = 'default trace enabled' ) = 1 - AND DATALENGTH( COALESCE( @base_tracefilename, '' ) ) > DATALENGTH('.TRC') - AND @TraceFileIssue = 0 - BEGIN + SET @StringToExecute += N'SELECT + 3 AS CheckId, + 100 AS [Priority], + b.database_name AS [Database Name], + ''Password backups'' AS [Finding], + ''The database '' + QUOTENAME(b.database_name) + '' has been backed up with a password '' + CONVERT(VARCHAR(10), COUNT(*)) + '' times. Who has the password?'' AS [Warning] + FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b + WHERE b.is_password_protected = 1 + GROUP BY b.database_name;' + @crlf; - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 106) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT - 106 AS CheckID - ,250 AS Priority - ,'Server Info' AS FindingsGroup - ,'Default Trace Contents' AS Finding - ,'https://www.brentozar.com/go/trace' AS URL - ,'The default trace holds '+cast(DATEDIFF(hour,MIN(StartTime),GETDATE())as VARCHAR(30))+' hours of data' - +' between '+cast(Min(StartTime) as VARCHAR(30))+' and '+cast(GETDATE()as VARCHAR(30)) - +('. The default trace files are located in: '+left( @curr_tracefilename,len(@curr_tracefilename) - @indx) - ) as Details - FROM ::fn_trace_gettable( @base_tracefilename, default ) - WHERE EventClass BETWEEN 65500 and 65600; - END; /* CheckID 106 */ + IF @Debug = 1 + PRINT @StringToExecute; - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 152 ) - BEGIN - IF EXISTS (SELECT * FROM sys.dm_os_wait_stats ws - LEFT OUTER JOIN #IgnorableWaits i ON ws.wait_type = i.wait_type - WHERE wait_time_ms > .1 * @CpuMsSinceWaitsCleared AND waiting_tasks_count > 0 - AND i.wait_type IS NULL) - BEGIN - /* Check for waits that have had more than 10% of the server's wait time */ - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 152) WITH NOWAIT; - - WITH os(wait_type, waiting_tasks_count, wait_time_ms, max_wait_time_ms, signal_wait_time_ms) - AS - (SELECT ws.wait_type, waiting_tasks_count, wait_time_ms, max_wait_time_ms, signal_wait_time_ms - FROM sys.dm_os_wait_stats ws - LEFT OUTER JOIN #IgnorableWaits i ON ws.wait_type = i.wait_type - WHERE i.wait_type IS NULL - AND wait_time_ms > .1 * @CpuMsSinceWaitsCleared - AND waiting_tasks_count > 0) - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT TOP 9 - 152 AS CheckID - ,240 AS Priority - ,'Wait Stats' AS FindingsGroup - , CAST(ROW_NUMBER() OVER(ORDER BY os.wait_time_ms DESC) AS NVARCHAR(10)) + N' - ' + os.wait_type AS Finding - ,'https://www.sqlskills.com/help/waits/' + LOWER(os.wait_type) + '/' AS URL - , Details = CAST(CAST(SUM(os.wait_time_ms / 1000.0 / 60 / 60) OVER (PARTITION BY os.wait_type) AS NUMERIC(18,1)) AS NVARCHAR(20)) + N' hours of waits, ' + - CAST(CAST((SUM(60.0 * os.wait_time_ms) OVER (PARTITION BY os.wait_type) ) / @MsSinceWaitsCleared AS NUMERIC(18,1)) AS NVARCHAR(20)) + N' minutes average wait time per hour, ' + - /* CAST(CAST( - 100.* SUM(os.wait_time_ms) OVER (PARTITION BY os.wait_type) - / (1. * SUM(os.wait_time_ms) OVER () ) - AS NUMERIC(18,1)) AS NVARCHAR(40)) + N'% of waits, ' + */ - CAST(CAST( - 100. * SUM(os.signal_wait_time_ms) OVER (PARTITION BY os.wait_type) - / (1. * SUM(os.wait_time_ms) OVER ()) - AS NUMERIC(18,1)) AS NVARCHAR(40)) + N'% signal wait, ' + - CAST(SUM(os.waiting_tasks_count) OVER (PARTITION BY os.wait_type) AS NVARCHAR(40)) + N' waiting tasks, ' + - CAST(CASE WHEN SUM(os.waiting_tasks_count) OVER (PARTITION BY os.wait_type) > 0 - THEN - CAST( - SUM(os.wait_time_ms) OVER (PARTITION BY os.wait_type) - / (1. * SUM(os.waiting_tasks_count) OVER (PARTITION BY os.wait_type)) - AS NUMERIC(18,1)) - ELSE 0 END AS NVARCHAR(40)) + N' ms average wait time.' - FROM os - ORDER BY SUM(os.wait_time_ms / 1000.0 / 60 / 60) OVER (PARTITION BY os.wait_type) DESC; - END; /* IF EXISTS (SELECT * FROM sys.dm_os_wait_stats WHERE wait_time_ms > 0 AND waiting_tasks_count > 0) */ + INSERT #Warnings ( CheckId, Priority, DatabaseName, Finding, Warning ) + EXEC sys.sp_executesql @StringToExecute; + + /*Looking for snapshot backups. There are legit reasons for these, but we should flag them so the questions get asked. What questions? Good question.*/ - /* If no waits were found, add a note about that */ - IF NOT EXISTS (SELECT * FROM #BlitzResults WHERE CheckID IN (107, 108, 109, 121, 152, 162)) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 153) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - VALUES (153, 240, 'Wait Stats', 'No Significant Waits Detected', 'https://www.brentozar.com/go/waits', 'This server might be just sitting around idle, or someone may have cleared wait stats recently.'); - END; - END; /* CheckID 152 */ + SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - /* CheckID 222 - Server Info - Azure Managed Instance */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 222 ) - AND 4 = ( SELECT COUNT(*) - FROM sys.all_objects o - INNER JOIN sys.all_columns c ON o.object_id = c.object_id - WHERE o.name = 'dm_os_job_object' - AND c.name IN ('cpu_rate', 'memory_limit_mb', 'process_memory_limit_mb', 'workingset_limit_mb' )) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 222) WITH NOWAIT; - - SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) - SELECT 222 AS CheckID , - 250 AS Priority , - ''Server Info'' AS FindingsGroup , - ''Azure Managed Instance'' AS Finding , - ''https://www.BrentOzar.com/go/azurevm'' AS URL , - ''cpu_rate: '' + CAST(COALESCE(cpu_rate, 0) AS VARCHAR(20)) + - '', memory_limit_mb: '' + CAST(COALESCE(memory_limit_mb, 0) AS NVARCHAR(20)) + - '', process_memory_limit_mb: '' + CAST(COALESCE(process_memory_limit_mb, 0) AS NVARCHAR(20)) + - '', workingset_limit_mb: '' + CAST(COALESCE(workingset_limit_mb, 0) AS NVARCHAR(20)) - FROM sys.dm_os_job_object OPTION (RECOMPILE);'; - - IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; - IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; - - EXECUTE(@StringToExecute); - END; + SET @StringToExecute += N'SELECT + 4 AS CheckId, + 100 AS [Priority], + b.database_name AS [Database Name], + ''Snapshot backups'' AS [Finding], + ''The database '' + QUOTENAME(b.database_name) + '' has had '' + CONVERT(VARCHAR(10), COUNT(*)) + '' snapshot backups. This message is purely informational.'' AS [Warning] + FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b + WHERE b.is_snapshot = 1 + GROUP BY b.database_name;' + @crlf; - /* CheckID 224 - Performance - SSRS/SSAS/SSIS Installed */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 224 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 224) WITH NOWAIT; - - IF (SELECT value_in_use FROM sys.configurations WHERE [name] = 'xp_cmdshell') = 1 - BEGIN - - IF OBJECT_ID('tempdb..#services') IS NOT NULL DROP TABLE #services; - CREATE TABLE #services (cmdshell_output varchar(max)); - - INSERT INTO #services - EXEC /**/xp_cmdshell/**/ 'net start' /* added comments around command since some firewalls block this string TL 20210221 */ - - IF EXISTS (SELECT 1 - FROM #services - WHERE cmdshell_output LIKE '%SQL Server Reporting Services%' - OR cmdshell_output LIKE '%SQL Server Integration Services%' - OR cmdshell_output LIKE '%SQL Server Analysis Services%') - BEGIN - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT - 224 AS CheckID - ,200 AS Priority - ,'Performance' AS FindingsGroup - ,'SSAS/SSIS/SSRS Installed' AS Finding - ,'https://www.BrentOzar.com/go/services' AS URL - ,'Did you know you have other SQL Server services installed on this box other than the engine? It can be a real performance pain.' as Details - - END; - - END; - END; + IF @Debug = 1 + PRINT @StringToExecute; - /* CheckID 232 - Server Info - Data Size */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 232 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 232) WITH NOWAIT; - - IF OBJECT_ID('tempdb..#MasterFiles') IS NOT NULL - DROP TABLE #MasterFiles; - CREATE TABLE #MasterFiles (database_id INT, file_id INT, type_desc NVARCHAR(50), name NVARCHAR(255), physical_name NVARCHAR(255), size BIGINT); - /* Azure SQL Database doesn't have sys.master_files, so we have to build our own. */ - IF ((SERVERPROPERTY('Edition')) = 'SQL Azure' - AND (OBJECT_ID('sys.master_files') IS NULL)) - SET @StringToExecute = 'INSERT INTO #MasterFiles (database_id, file_id, type_desc, name, physical_name, size) SELECT DB_ID(), file_id, type_desc, name, physical_name, size FROM sys.database_files;'; - ELSE - SET @StringToExecute = 'INSERT INTO #MasterFiles (database_id, file_id, type_desc, name, physical_name, size) SELECT database_id, file_id, type_desc, name, physical_name, size FROM sys.master_files;'; - EXEC(@StringToExecute); + INSERT #Warnings ( CheckId, Priority, DatabaseName, Finding, Warning ) + EXEC sys.sp_executesql @StringToExecute; + + /*It''s fine to take backups of read only databases, but it''s not always necessary (there''s no new data, after all).*/ + SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 232 AS CheckID - ,250 AS Priority - ,'Server Info' AS FindingsGroup - ,'Data Size' AS Finding - ,'' AS URL - ,CAST(COUNT(DISTINCT database_id) AS NVARCHAR(100)) + N' databases, ' + CAST(CAST(SUM (CAST(size AS BIGINT)*8./1024./1024.) AS MONEY) AS VARCHAR(100)) + ' GB total file size' as Details - FROM #MasterFiles - WHERE database_id > 4; + SET @StringToExecute += N'SELECT + 5 AS CheckId, + 100 AS [Priority], + b.database_name AS [Database Name], + ''Read only state backups'' AS [Finding], + ''The database '' + QUOTENAME(b.database_name) + '' has been backed up '' + CONVERT(VARCHAR(10), COUNT(*)) + '' times while in a read-only state. This can be normal if it''''s a secondary, but a bit odd otherwise.'' AS [Warning] + FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b + WHERE b.is_readonly = 1 + GROUP BY b.database_name;' + @crlf; - END; + IF @Debug = 1 + PRINT @StringToExecute; + INSERT #Warnings ( CheckId, Priority, DatabaseName, Finding, Warning ) + EXEC sys.sp_executesql @StringToExecute; + + /*So, I''ve come across people who think they need to change their database to single user mode to take a backup. Or that doing that will help something. I just need to know, here.*/ - END; /* IF @CheckServerInfo = 1 */ - END; /* IF ( ( SERVERPROPERTY('ServerName') NOT IN ( SELECT ServerName */ + SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - /* Delete priorites they wanted to skip. */ - IF @IgnorePrioritiesAbove IS NOT NULL - DELETE #BlitzResults - WHERE [Priority] > @IgnorePrioritiesAbove AND CheckID <> -1; + SET @StringToExecute += N'SELECT + 6 AS CheckId, + 100 AS [Priority], + b.database_name AS [Database Name], + ''Single user mode backups'' AS [Finding], + ''The database '' + QUOTENAME(b.database_name) + '' has been backed up '' + CONVERT(VARCHAR(10), COUNT(*)) + '' times while in single-user mode. This is really weird! Make sure your backup process doesn''''t include a mode change anywhere.'' AS [Warning] + FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b + WHERE b.is_single_user = 1 + GROUP BY b.database_name;' + @crlf; - IF @IgnorePrioritiesBelow IS NOT NULL - DELETE #BlitzResults - WHERE [Priority] < @IgnorePrioritiesBelow AND CheckID <> -1; + IF @Debug = 1 + PRINT @StringToExecute; - /* Delete checks they wanted to skip. */ - IF @SkipChecksTable IS NOT NULL - BEGIN - DELETE FROM #BlitzResults - WHERE DatabaseName IN ( SELECT DatabaseName - FROM #SkipChecks - WHERE CheckID IS NULL - AND (ServerName IS NULL OR ServerName = SERVERPROPERTY('ServerName'))); - DELETE FROM #BlitzResults - WHERE CheckID IN ( SELECT CheckID - FROM #SkipChecks - WHERE DatabaseName IS NULL - AND (ServerName IS NULL OR ServerName = SERVERPROPERTY('ServerName'))); - DELETE r FROM #BlitzResults r - INNER JOIN #SkipChecks c ON r.DatabaseName = c.DatabaseName and r.CheckID = c.CheckID - AND (ServerName IS NULL OR ServerName = SERVERPROPERTY('ServerName')); - END; + INSERT #Warnings ( CheckId, Priority, DatabaseName, Finding, Warning ) + EXEC sys.sp_executesql @StringToExecute; + + /*C''mon, it''s 2017. Take your backups with CHECKSUMS, people.*/ - /* Add summary mode */ - IF @SummaryMode > 0 - BEGIN - UPDATE #BlitzResults - SET Finding = br.Finding + ' (' + CAST(brTotals.recs AS NVARCHAR(20)) + ')' - FROM #BlitzResults br - INNER JOIN (SELECT FindingsGroup, Finding, Priority, COUNT(*) AS recs FROM #BlitzResults GROUP BY FindingsGroup, Finding, Priority) brTotals ON br.FindingsGroup = brTotals.FindingsGroup AND br.Finding = brTotals.Finding AND br.Priority = brTotals.Priority - WHERE brTotals.recs > 1; + SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - DELETE br - FROM #BlitzResults br - WHERE EXISTS (SELECT * FROM #BlitzResults brLower WHERE br.FindingsGroup = brLower.FindingsGroup AND br.Finding = brLower.Finding AND br.Priority = brLower.Priority AND br.ID > brLower.ID); + SET @StringToExecute += N'SELECT + 7 AS CheckId, + 100 AS [Priority], + b.database_name AS [Database Name], + ''No CHECKSUMS'' AS [Finding], + ''The database '' + QUOTENAME(b.database_name) + '' has been backed up '' + CONVERT(VARCHAR(10), COUNT(*)) + '' times without CHECKSUMS in the past 30 days. CHECKSUMS can help alert you to corruption errors.'' AS [Warning] + FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b + WHERE b.has_backup_checksums = 0 + AND b.backup_finish_date >= DATEADD(DAY, -30, SYSDATETIME()) + GROUP BY b.database_name;' + @crlf; - END; + IF @Debug = 1 + PRINT @StringToExecute; - /* Add credits for the nice folks who put so much time into building and maintaining this for free: */ - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - VALUES ( -1 , - 255 , - 'Thanks!' , - 'From Your Community Volunteers' , - 'http://FirstResponderKit.org' , - 'We hope you found this tool useful.' - ); + INSERT #Warnings ( CheckId, Priority, DatabaseName, Finding, Warning ) + EXEC sys.sp_executesql @StringToExecute; + + /*Damaged is a Black Flag album. You don''t want your backups to be like a Black Flag album. */ - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details + SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - ) - VALUES ( -1 , - 0 , - 'sp_Blitz ' + CAST(CONVERT(DATETIME, @VersionDate, 102) AS VARCHAR(100)), - 'SQL Server First Responder Kit' , - 'http://FirstResponderKit.org/' , - 'To get help or add your own contributions, join us at http://FirstResponderKit.org.' + SET @StringToExecute += N'SELECT + 8 AS CheckId, + 100 AS [Priority], + b.database_name AS [Database Name], + ''Damaged backups'' AS [Finding], + ''The database '' + QUOTENAME(b.database_name) + '' has had '' + CONVERT(VARCHAR(10), COUNT(*)) + '' damaged backups taken without stopping to throw an error. This is done by specifying CONTINUE_AFTER_ERROR in your BACKUP commands.'' AS [Warning] + FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b + WHERE b.is_damaged = 1 + GROUP BY b.database_name;' + @crlf; - ); + IF @Debug = 1 + PRINT @StringToExecute; - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details + INSERT #Warnings ( CheckId, Priority, DatabaseName, Finding, Warning ) + EXEC sys.sp_executesql @StringToExecute; + + /*Checking for encrypted backups and the last backup of the encryption key.*/ - ) - SELECT 156 , - 254 , - 'Rundate' , - GETDATE() , - 'http://FirstResponderKit.org/' , - 'Captain''s log: stardate something and something...'; - - IF @EmailRecipients IS NOT NULL - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Sending an email.', 0, 1) WITH NOWAIT; - - /* Database mail won't work off a local temp table. I'm not happy about this hacky workaround either. */ - IF (OBJECT_ID('tempdb..##BlitzResults', 'U') IS NOT NULL) DROP TABLE ##BlitzResults; - SELECT * INTO ##BlitzResults FROM #BlitzResults; - SET @query_result_separator = char(9); - SET @StringToExecute = 'SET NOCOUNT ON;SELECT [Priority] , [FindingsGroup] , [Finding] , [DatabaseName] , [URL] , [Details] , CheckID FROM ##BlitzResults ORDER BY Priority , FindingsGroup , Finding , DatabaseName , Details; SET NOCOUNT OFF;'; - SET @EmailSubject = 'sp_Blitz Results for ' + @@SERVERNAME; - SET @EmailBody = 'sp_Blitz ' + CAST(CONVERT(DATETIME, @VersionDate, 102) AS VARCHAR(100)) + '. http://FirstResponderKit.org'; - IF @EmailProfile IS NULL - EXEC msdb.dbo.sp_send_dbmail - @recipients = @EmailRecipients, - @subject = @EmailSubject, - @body = @EmailBody, - @query_attachment_filename = 'sp_Blitz-Results.csv', - @attach_query_result_as_file = 1, - @query_result_header = 1, - @query_result_width = 32767, - @append_query_error = 1, - @query_result_no_padding = 1, - @query_result_separator = @query_result_separator, - @query = @StringToExecute; - ELSE - EXEC msdb.dbo.sp_send_dbmail - @profile_name = @EmailProfile, - @recipients = @EmailRecipients, - @subject = @EmailSubject, - @body = @EmailBody, - @query_attachment_filename = 'sp_Blitz-Results.csv', - @attach_query_result_as_file = 1, - @query_result_header = 1, - @query_result_width = 32767, - @append_query_error = 1, - @query_result_no_padding = 1, - @query_result_separator = @query_result_separator, - @query = @StringToExecute; - IF (OBJECT_ID('tempdb..##BlitzResults', 'U') IS NOT NULL) DROP TABLE ##BlitzResults; - END; + /*2014 ONLY*/ - /* Checks if @OutputServerName is populated with a valid linked server, and that the database name specified is valid */ - DECLARE @ValidOutputServer BIT; - DECLARE @ValidOutputLocation BIT; - DECLARE @LinkedServerDBCheck NVARCHAR(2000); - DECLARE @ValidLinkedServerDB INT; - DECLARE @tmpdbchk table (cnt int); - IF @OutputServerName IS NOT NULL - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Outputting to a remote server.', 0, 1) WITH NOWAIT; - - IF EXISTS (SELECT server_id FROM sys.servers WHERE QUOTENAME([name]) = @OutputServerName) - BEGIN - SET @LinkedServerDBCheck = 'SELECT 1 WHERE EXISTS (SELECT * FROM '+@OutputServerName+'.master.sys.databases WHERE QUOTENAME([name]) = '''+@OutputDatabaseName+''')'; - INSERT INTO @tmpdbchk EXEC sys.sp_executesql @LinkedServerDBCheck; - SET @ValidLinkedServerDB = (SELECT COUNT(*) FROM @tmpdbchk); - IF (@ValidLinkedServerDB > 0) - BEGIN - SET @ValidOutputServer = 1; - SET @ValidOutputLocation = 1; - END; - ELSE - RAISERROR('The specified database was not found on the output server', 16, 0); - END; - ELSE - BEGIN - RAISERROR('The specified output server was not found', 16, 0); - END; - END; - ELSE - BEGIN - IF @OutputDatabaseName IS NOT NULL - AND @OutputSchemaName IS NOT NULL - AND @OutputTableName IS NOT NULL - AND EXISTS ( SELECT * - FROM sys.databases - WHERE QUOTENAME([name]) = @OutputDatabaseName) - BEGIN - SET @ValidOutputLocation = 1; - END; - ELSE IF @OutputDatabaseName IS NOT NULL - AND @OutputSchemaName IS NOT NULL - AND @OutputTableName IS NOT NULL - AND NOT EXISTS ( SELECT * - FROM sys.databases - WHERE QUOTENAME([name]) = @OutputDatabaseName) - BEGIN - RAISERROR('The specified output database was not found on this server', 16, 0); - END; - ELSE - BEGIN - SET @ValidOutputLocation = 0; - END; - END; +IF @ProductVersionMajor >= 12 + BEGIN - /* @OutputTableName lets us export the results to a permanent table */ - IF @ValidOutputLocation = 1 - BEGIN - SET @StringToExecute = 'USE ' - + @OutputDatabaseName - + '; IF EXISTS(SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' - + @OutputSchemaName - + ''') AND NOT EXISTS (SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''' - + @OutputSchemaName + ''' AND QUOTENAME(TABLE_NAME) = ''' - + @OutputTableName + ''') CREATE TABLE ' - + @OutputSchemaName + '.' - + @OutputTableName - + ' (ID INT IDENTITY(1,1) NOT NULL, - ServerName NVARCHAR(128), - CheckDate DATETIMEOFFSET, - Priority TINYINT , - FindingsGroup VARCHAR(50) , - Finding VARCHAR(200) , - DatabaseName NVARCHAR(128), - URL VARCHAR(200) , - Details NVARCHAR(4000) , - QueryPlan [XML] NULL , - QueryPlanFiltered [NVARCHAR](MAX) NULL, - CheckID INT , - CONSTRAINT [PK_' + CAST(NEWID() AS CHAR(36)) + '] PRIMARY KEY CLUSTERED (ID ASC));'; - IF @ValidOutputServer = 1 - BEGIN - SET @StringToExecute = REPLACE(@StringToExecute,''''+@OutputSchemaName+'''',''''''+@OutputSchemaName+''''''); - SET @StringToExecute = REPLACE(@StringToExecute,''''+@OutputTableName+'''',''''''+@OutputTableName+''''''); - SET @StringToExecute = REPLACE(@StringToExecute,'[XML]','[NVARCHAR](MAX)'); - EXEC('EXEC('''+@StringToExecute+''') AT ' + @OutputServerName); - END; - ELSE - BEGIN - IF @OutputXMLasNVARCHAR = 1 - BEGIN - SET @StringToExecute = REPLACE(@StringToExecute,'[XML]','[NVARCHAR](MAX)'); - END; - EXEC(@StringToExecute); - END; - IF @ValidOutputServer = 1 - BEGIN - SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' - + @OutputServerName + '.' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' - + @OutputSchemaName + ''') INSERT ' - + @OutputServerName + '.' - + @OutputDatabaseName + '.' - + @OutputSchemaName + '.' - + @OutputTableName - + ' (ServerName, CheckDate, CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details, QueryPlan, QueryPlanFiltered) SELECT ''' - + CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128)) - + ''', SYSDATETIMEOFFSET(), CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details, CAST(QueryPlan AS NVARCHAR(MAX)), QueryPlanFiltered FROM #BlitzResults ORDER BY Priority , FindingsGroup , Finding , DatabaseName , Details'; + SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - EXEC(@StringToExecute); - END; - ELSE - BEGIN - IF @OutputXMLasNVARCHAR = 1 - BEGIN - SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' - + @OutputSchemaName + ''') INSERT ' - + @OutputDatabaseName + '.' - + @OutputSchemaName + '.' - + @OutputTableName - + ' (ServerName, CheckDate, CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details, QueryPlan, QueryPlanFiltered) SELECT ''' - + CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128)) - + ''', SYSDATETIMEOFFSET(), CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details, CAST(QueryPlan AS NVARCHAR(MAX)), QueryPlanFiltered FROM #BlitzResults ORDER BY Priority , FindingsGroup , Finding , DatabaseName , Details'; - END; - ELSE - begin - SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' - + @OutputSchemaName + ''') INSERT ' - + @OutputDatabaseName + '.' - + @OutputSchemaName + '.' - + @OutputTableName - + ' (ServerName, CheckDate, CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details, QueryPlan, QueryPlanFiltered) SELECT ''' - + CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128)) - + ''', SYSDATETIMEOFFSET(), CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details, QueryPlan, QueryPlanFiltered FROM #BlitzResults ORDER BY Priority , FindingsGroup , Finding , DatabaseName , Details'; - END; - EXEC(@StringToExecute); - - END; - END; - ELSE IF (SUBSTRING(@OutputTableName, 2, 2) = '##') - BEGIN - IF @ValidOutputServer = 1 - BEGIN - RAISERROR('Due to the nature of temporary tables, outputting to a linked server requires a permanent table.', 16, 0); - END; - ELSE - BEGIN - SET @StringToExecute = N' IF (OBJECT_ID(''tempdb..' - + @OutputTableName - + ''') IS NOT NULL) DROP TABLE ' + @OutputTableName + ';' - + 'CREATE TABLE ' - + @OutputTableName - + ' (ID INT IDENTITY(1,1) NOT NULL, - ServerName NVARCHAR(128), - CheckDate DATETIMEOFFSET, - Priority TINYINT , - FindingsGroup VARCHAR(50) , - Finding VARCHAR(200) , - DatabaseName NVARCHAR(128), - URL VARCHAR(200) , - Details NVARCHAR(4000) , - QueryPlan [XML] NULL , - QueryPlanFiltered [NVARCHAR](MAX) NULL, - CheckID INT , - CONSTRAINT [PK_' + CAST(NEWID() AS CHAR(36)) + '] PRIMARY KEY CLUSTERED (ID ASC));' - + ' INSERT ' - + @OutputTableName - + ' (ServerName, CheckDate, CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details, QueryPlan, QueryPlanFiltered) SELECT ''' - + CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128)) - + ''', SYSDATETIMEOFFSET(), CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details, QueryPlan, QueryPlanFiltered FROM #BlitzResults ORDER BY Priority , FindingsGroup , Finding , DatabaseName , Details'; - - EXEC(@StringToExecute); - END; - END; - ELSE IF (SUBSTRING(@OutputTableName, 2, 1) = '#') - BEGIN - RAISERROR('Due to the nature of Dymamic SQL, only global (i.e. double pound (##)) temp tables are supported for @OutputTableName', 16, 0); - END; + SET @StringToExecute += N'SELECT + 9 AS CheckId, + 100 AS [Priority], + b.database_name AS [Database Name], + ''Encrypted backups'' AS [Finding], + ''The database '' + QUOTENAME(b.database_name) + '' has had '' + CONVERT(VARCHAR(10), COUNT(*)) + '' '' + b.encryptor_type + '' backups, and the last time a certificate was backed up is ' + + CASE WHEN LOWER(@MSDBName) <> N'msdb' + THEN + N'...well, that information is on another server, anyway.'' AS [Warning]' + ELSE + CONVERT(VARCHAR(30), (SELECT MAX(c.pvt_key_last_backup_date) FROM sys.certificates AS c WHERE c.name NOT LIKE '##%')) + N'.'' AS [Warning]' + END + + N' + FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b + WHERE b.encryptor_type IS NOT NULL + GROUP BY b.database_name, b.encryptor_type;' + @crlf; - DECLARE @separator AS VARCHAR(1); - IF @OutputType = 'RSV' - SET @separator = CHAR(31); - ELSE - SET @separator = ','; + IF @Debug = 1 + PRINT @StringToExecute; - IF @OutputType = 'COUNT' - BEGIN - SELECT COUNT(*) AS Warnings - FROM #BlitzResults; - END; - ELSE - IF @OutputType IN ( 'CSV', 'RSV' ) - BEGIN + INSERT #Warnings ( CheckId, Priority, DatabaseName, Finding, Warning ) + EXEC sys.sp_executesql @StringToExecute; + + END + + /*Looking for backups that have BULK LOGGED data in them -- this can screw up point in time LOG recovery.*/ - SELECT Result = CAST([Priority] AS NVARCHAR(100)) - + @separator + CAST(CheckID AS NVARCHAR(100)) - + @separator + COALESCE([FindingsGroup], - '(N/A)') + @separator - + COALESCE([Finding], '(N/A)') + @separator - + COALESCE(DatabaseName, '(N/A)') + @separator - + COALESCE([URL], '(N/A)') + @separator - + COALESCE([Details], '(N/A)') - FROM #BlitzResults - ORDER BY Priority , - FindingsGroup , - Finding , - DatabaseName , - Details; - END; - ELSE IF @OutputXMLasNVARCHAR = 1 AND @OutputType <> 'NONE' - BEGIN - SELECT [Priority] , - [FindingsGroup] , - [Finding] , - [DatabaseName] , - [URL] , - [Details] , - CAST([QueryPlan] AS NVARCHAR(MAX)) AS QueryPlan, - [QueryPlanFiltered] , - CheckID - FROM #BlitzResults - ORDER BY Priority , - FindingsGroup , - Finding , - DatabaseName , - Details; - END; - ELSE IF @OutputType = 'MARKDOWN' - BEGIN - WITH Results AS (SELECT row_number() OVER (ORDER BY Priority, FindingsGroup, Finding, DatabaseName, Details) AS rownum, * - FROM #BlitzResults - WHERE Priority > 0 AND Priority < 255 AND FindingsGroup IS NOT NULL AND Finding IS NOT NULL - AND FindingsGroup <> 'Security' /* Specifically excluding security checks for public exports */) - SELECT - Markdown = CONVERT(XML, STUFF((SELECT - CASE - WHEN r.Priority <> COALESCE(rPrior.Priority, 0) OR r.FindingsGroup <> rPrior.FindingsGroup THEN @crlf + N'**Priority ' + CAST(COALESCE(r.Priority,N'') AS NVARCHAR(5)) + N': ' + COALESCE(r.FindingsGroup,N'') + N'**:' + @crlf + @crlf - ELSE N'' - END - + CASE WHEN r.Finding <> COALESCE(rPrior.Finding,N'') AND r.Finding <> COALESCE(rNext.Finding,N'') THEN N'- ' + COALESCE(r.Finding,N'') + N' ' + COALESCE(r.DatabaseName, N'') + N' - ' + COALESCE(r.Details,N'') + @crlf - WHEN r.Finding <> COALESCE(rPrior.Finding,N'') AND r.Finding = rNext.Finding AND r.Details = rNext.Details THEN N'- ' + COALESCE(r.Finding,N'') + N' - ' + COALESCE(r.Details,N'') + @crlf + @crlf + N' * ' + COALESCE(r.DatabaseName, N'') + @crlf - WHEN r.Finding <> COALESCE(rPrior.Finding,N'') AND r.Finding = rNext.Finding THEN N'- ' + COALESCE(r.Finding,N'') + @crlf + @crlf + CASE WHEN r.DatabaseName IS NULL THEN N'' ELSE N' * ' + COALESCE(r.DatabaseName,N'') END + CASE WHEN r.Details <> rPrior.Details THEN N' - ' + COALESCE(r.Details,N'') + @crlf ELSE '' END - ELSE CASE WHEN r.DatabaseName IS NULL THEN N'' ELSE N' * ' + COALESCE(r.DatabaseName,N'') END + CASE WHEN r.Details <> rPrior.Details THEN N' - ' + COALESCE(r.Details,N'') + @crlf ELSE N'' + @crlf END - END + @crlf - FROM Results r - LEFT OUTER JOIN Results rPrior ON r.rownum = rPrior.rownum + 1 - LEFT OUTER JOIN Results rNext ON r.rownum = rNext.rownum - 1 - ORDER BY r.rownum FOR XML PATH(N''), ROOT('Markdown'), TYPE).value('/Markdown[1]','VARCHAR(MAX)'), 1, 2, '') - + ''); - END; - ELSE IF @OutputType = 'XML' - BEGIN - /* --TOURSTOP05-- */ - SELECT [Priority] , - [FindingsGroup] , - [Finding] , - [DatabaseName] , - [URL] , - [Details] , - [QueryPlanFiltered] , - CheckID - FROM #BlitzResults - ORDER BY Priority , - FindingsGroup , - Finding , - DatabaseName , - Details - FOR XML PATH('Result'), ROOT('sp_Blitz_Output'); - END; - ELSE IF @OutputType <> 'NONE' - BEGIN - /* --TOURSTOP05-- */ - SELECT [Priority] , - [FindingsGroup] , - [Finding] , - [DatabaseName] , - [URL] , - [Details] , - [QueryPlan] , - [QueryPlanFiltered] , - CheckID - FROM #BlitzResults - ORDER BY Priority , - FindingsGroup , - Finding , - DatabaseName , - Details; - END; + SET @StringToExecute =N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - DROP TABLE #BlitzResults; + SET @StringToExecute += N'SELECT + 10 AS CheckId, + 100 AS [Priority], + b.database_name AS [Database Name], + ''Bulk logged backups'' AS [Finding], + ''The database '' + QUOTENAME(b.database_name) + '' has had '' + CONVERT(VARCHAR(10), COUNT(*)) + '' backups with bulk logged data. This can make point in time recovery awkward. '' AS [Warning] + FROM ' + QUOTENAME(@MSDBName) + '.dbo.backupset AS b + WHERE b.has_bulk_logged_data = 1 + GROUP BY b.database_name;' + @crlf; - IF @OutputProcedureCache = 1 - AND @CheckProcedureCache = 1 - SELECT TOP 20 - total_worker_time / execution_count AS AvgCPU , - total_worker_time AS TotalCPU , - CAST(ROUND(100.00 * total_worker_time - / ( SELECT SUM(total_worker_time) - FROM sys.dm_exec_query_stats - ), 2) AS MONEY) AS PercentCPU , - total_elapsed_time / execution_count AS AvgDuration , - total_elapsed_time AS TotalDuration , - CAST(ROUND(100.00 * total_elapsed_time - / ( SELECT SUM(total_elapsed_time) - FROM sys.dm_exec_query_stats - ), 2) AS MONEY) AS PercentDuration , - total_logical_reads / execution_count AS AvgReads , - total_logical_reads AS TotalReads , - CAST(ROUND(100.00 * total_logical_reads - / ( SELECT SUM(total_logical_reads) - FROM sys.dm_exec_query_stats - ), 2) AS MONEY) AS PercentReads , - execution_count , - CAST(ROUND(100.00 * execution_count - / ( SELECT SUM(execution_count) - FROM sys.dm_exec_query_stats - ), 2) AS MONEY) AS PercentExecutions , - CASE WHEN DATEDIFF(mi, creation_time, - qs.last_execution_time) = 0 THEN 0 - ELSE CAST(( 1.00 * execution_count / DATEDIFF(mi, - creation_time, - qs.last_execution_time) ) AS MONEY) - END AS executions_per_minute , - qs.creation_time AS plan_creation_time , - qs.last_execution_time , - text , - text_filtered , - query_plan , - query_plan_filtered , - sql_handle , - query_hash , - plan_handle , - query_plan_hash - FROM #dm_exec_query_stats qs - ORDER BY CASE UPPER(@CheckProcedureCacheFilter) - WHEN 'CPU' THEN total_worker_time - WHEN 'READS' THEN total_logical_reads - WHEN 'EXECCOUNT' THEN execution_count - WHEN 'DURATION' THEN total_elapsed_time - ELSE total_worker_time - END DESC; + IF @Debug = 1 + PRINT @StringToExecute; - END; /* ELSE -- IF @OutputType = 'SCHEMA' */ + INSERT #Warnings ( CheckId, Priority, DatabaseName, Finding, Warning ) + EXEC sys.sp_executesql @StringToExecute; + + /*Looking for recovery model being switched between FULL and SIMPLE, because it''s a bad practice.*/ - /* - Cleanups - drop temporary tables that have been created by this SP. - */ - - IF(OBJECT_ID('tempdb..#InvalidLogins') IS NOT NULL) - BEGIN - EXEC sp_executesql N'DROP TABLE #InvalidLogins;'; - END; + SET @StringToExecute =N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - IF OBJECT_ID('tempdb..#AlertInfo') IS NOT NULL - BEGIN - EXEC sp_executesql N'DROP TABLE #AlertInfo;'; - END; + SET @StringToExecute += N'SELECT + 11 AS CheckId, + 100 AS [Priority], + b.database_name AS [Database Name], + ''Recovery model switched'' AS [Finding], + ''The database '' + QUOTENAME(b.database_name) + '' has changed recovery models from between FULL and SIMPLE '' + CONVERT(VARCHAR(10), COUNT(DISTINCT b.recovery_model)) + '' times. This breaks the log chain and is generally a bad idea.'' AS [Warning] + FROM ' + QUOTENAME(@MSDBName) + '.dbo.backupset AS b + WHERE b.recovery_model <> ''BULK-LOGGED'' + GROUP BY b.database_name + HAVING COUNT(DISTINCT b.recovery_model) > 4;' + @crlf; - /* - Reset the Nmumeric_RoundAbort session state back to enabled if it was disabled earlier. - See Github issue #2302 for more info. - */ - IF @NeedToTurnNumericRoundabortBackOn = 1 - SET NUMERIC_ROUNDABORT ON; + IF @Debug = 1 + PRINT @StringToExecute; - SET NOCOUNT OFF; -GO + INSERT #Warnings ( CheckId, Priority, DatabaseName, Finding, Warning ) + EXEC sys.sp_executesql @StringToExecute; -/* ---Sample execution call with the most common parameters: -EXEC [dbo].[sp_Blitz] - @CheckUserDatabaseObjects = 1 , - @CheckProcedureCache = 0 , - @OutputType = 'TABLE' , - @OutputProcedureCache = 0 , - @CheckProcedureCacheFilter = NULL, - @CheckServerInfo = 1 -*/ -SET ANSI_NULLS ON; -SET QUOTED_IDENTIFIER ON + /*Looking for uncompressed backups.*/ -IF NOT EXISTS (SELECT * FROM sys.objects WHERE [object_id] = OBJECT_ID(N'[dbo].[sp_BlitzAnalysis]') AND [type] in (N'P', N'PC')) -BEGIN -EXEC dbo.sp_executesql @statement = N'CREATE PROCEDURE [dbo].[sp_BlitzAnalysis] AS' -END -GO + SET @StringToExecute =N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; -ALTER PROCEDURE [dbo].[sp_BlitzAnalysis] ( -@Help TINYINT = 0, -@StartDate DATETIMEOFFSET(7) = NULL, -@EndDate DATETIMEOFFSET(7) = NULL, -@OutputDatabaseName NVARCHAR(256) = 'DBAtools', -@OutputSchemaName NVARCHAR(256) = N'dbo', -@OutputTableNameBlitzFirst NVARCHAR(256) = N'BlitzFirst', -@OutputTableNameFileStats NVARCHAR(256) = N'BlitzFirst_FileStats', -@OutputTableNamePerfmonStats NVARCHAR(256) = N'BlitzFirst_PerfmonStats', -@OutputTableNameWaitStats NVARCHAR(256) = N'BlitzFirst_WaitStats', -@OutputTableNameBlitzCache NVARCHAR(256) = N'BlitzCache', -@OutputTableNameBlitzWho NVARCHAR(256) = N'BlitzWho', -@Servername NVARCHAR(128) = @@SERVERNAME, -@Databasename NVARCHAR(128) = NULL, -@BlitzCacheSortorder NVARCHAR(20) = N'cpu', -@MaxBlitzFirstPriority INT = 249, -@ReadLatencyThreshold INT = 100, -@WriteLatencyThreshold INT = 100, -@WaitStatsTop TINYINT = 10, -@Version VARCHAR(30) = NULL OUTPUT, -@VersionDate DATETIME = NULL OUTPUT, -@VersionCheckMode BIT = 0, -@BringThePain BIT = 0, -@Maxdop INT = 1, -@Debug BIT = 0 -) -AS -SET NOCOUNT ON; -SET STATISTICS XML OFF; + SET @StringToExecute += N'SELECT + 12 AS CheckId, + 100 AS [Priority], + b.database_name AS [Database Name], + ''Uncompressed backups'' AS [Finding], + ''The database '' + QUOTENAME(b.database_name) + '' has had '' + CONVERT(VARCHAR(10), COUNT(*)) + '' uncompressed backups in the last 30 days. This is a free way to save time and space. And SPACETIME. If your version of SQL supports it.'' AS [Warning] + FROM ' + QUOTENAME(@MSDBName) + '.dbo.backupset AS b + WHERE backup_size = compressed_backup_size AND type = ''D'' + AND b.backup_finish_date >= DATEADD(DAY, -30, SYSDATETIME()) + GROUP BY b.database_name;' + @crlf; -SELECT @Version = '8.19', @VersionDate = '20240222'; + IF @Debug = 1 + PRINT @StringToExecute; -IF(@VersionCheckMode = 1) -BEGIN - RETURN; -END; + INSERT #Warnings ( CheckId, Priority, DatabaseName, Finding, Warning ) + EXEC sys.sp_executesql @StringToExecute; -IF (@Help = 1) -BEGIN - PRINT 'EXEC sp_BlitzAnalysis -@StartDate = NULL, /* Specify a datetime or NULL will get an hour ago */ -@EndDate = NULL, /* Specify a datetime or NULL will get an hour of data since @StartDate */ -@OutputDatabaseName = N''DBA'', /* Specify the database name where where we can find your logged blitz data */ -@OutputSchemaName = N''dbo'', /* Specify the schema */ -@OutputTableNameBlitzFirst = N''BlitzFirst'', /* Table name where you are storing sp_BlitzFirst output, Set to NULL to ignore */ -@OutputTableNameFileStats = N''BlitzFirst_FileStats'', /* Table name where you are storing sp_BlitzFirst filestats output, Set to NULL to ignore */ -@OutputTableNamePerfmonStats = N''BlitzFirst_PerfmonStats'', /* Table name where you are storing sp_BlitzFirst Perfmon output, Set to NULL to ignore */ -@OutputTableNameWaitStats = N''BlitzFirst_WaitStats'', /* Table name where you are storing sp_BlitzFirst Wait stats output, Set to NULL to ignore */ -@OutputTableNameBlitzCache = N''BlitzCache'', /* Table name where you are storing sp_BlitzCache output, Set to NULL to ignore */ -@OutputTableNameBlitzWho = N''BlitzWho'', /* Table name where you are storing sp_BlitzWho output, Set to NULL to ignore */ -@Databasename = NULL, /* Filters results for BlitzCache, FileStats (will also include tempdb), BlitzWho. Leave as NULL for all databases */ -@MaxBlitzFirstPriority = 249, /* Max priority to include in the results */ -@BlitzCacheSortorder = ''cpu'', /* Accepted values ''all'' ''cpu'' ''reads'' ''writes'' ''duration'' ''executions'' ''memory grant'' ''spills'' */ -@WaitStatsTop = 3, /* Controls the top for wait stats only */ -@Maxdop = 1, /* Control the degree of parallelism that the queries within this proc can use if they want to*/ -@Debug = 0; /* Show sp_BlitzAnalysis SQL commands in the messages tab as they execute */ +RAISERROR('Rules analysis starting on temp tables', 0, 1) WITH NOWAIT; -/* -Additional parameters: -@ReadLatencyThreshold INT /* Default: 100 - Sets the threshold in ms to compare against io_stall_read_average_ms in your filestats table */ -@WriteLatencyThreshold INT /* Default: 100 - Sets the threshold in ms to compare against io_stall_write_average_ms in your filestats table */ -@BringThePain BIT /* Default: 0 - If you are getting more than 4 hours of data with blitzcachesortorder set to ''all'' you will need to set BringThePain to 1 */ -*/'; - RETURN; -END + INSERT #Warnings ( CheckId, Priority, DatabaseName, Finding, Warning ) + SELECT + 13 AS CheckId, + 100 AS Priority, + r.DatabaseName as [DatabaseName], + 'Big Diffs' AS [Finding], + 'On average, Differential backups for this database are >=40% of the size of the average Full backup.' AS [Warning] + FROM #Recoverability AS r + WHERE r.IsBigDiff = 1 -/* Declare all local variables required */ -DECLARE @FullOutputTableNameBlitzFirst NVARCHAR(1000); -DECLARE @FullOutputTableNameFileStats NVARCHAR(1000); -DECLARE @FullOutputTableNamePerfmonStats NVARCHAR(1000); -DECLARE @FullOutputTableNameWaitStats NVARCHAR(1000); -DECLARE @FullOutputTableNameBlitzCache NVARCHAR(1000); -DECLARE @FullOutputTableNameBlitzWho NVARCHAR(1000); -DECLARE @Sql NVARCHAR(MAX); -DECLARE @NewLine NVARCHAR(2) = CHAR(13); -DECLARE @IncludeMemoryGrants BIT; -DECLARE @IncludeSpills BIT; + INSERT #Warnings ( CheckId, Priority, DatabaseName, Finding, Warning ) + SELECT + 13 AS CheckId, + 100 AS Priority, + r.DatabaseName as [DatabaseName], + 'Big Logs' AS [Finding], + 'On average, Log backups for this database are >=20% of the size of the average Full backup.' AS [Warning] + FROM #Recoverability AS r + WHERE r.IsBigLog = 1 -/* Validate the database name */ -IF (DB_ID(@OutputDatabaseName) IS NULL) -BEGIN - RAISERROR('Invalid database name provided for parameter @OutputDatabaseName: %s',11,0,@OutputDatabaseName); - RETURN; -END -/* Set fully qualified table names */ -SET @FullOutputTableNameBlitzFirst = QUOTENAME(@OutputDatabaseName)+N'.'+QUOTENAME(@OutputSchemaName)+N'.'+QUOTENAME(@OutputTableNameBlitzFirst); -SET @FullOutputTableNameFileStats = QUOTENAME(@OutputDatabaseName)+N'.'+QUOTENAME(@OutputSchemaName)+N'.'+QUOTENAME(@OutputTableNameFileStats+N'_Deltas'); -SET @FullOutputTableNamePerfmonStats = QUOTENAME(@OutputDatabaseName)+N'.'+QUOTENAME(@OutputSchemaName)+N'.'+QUOTENAME(@OutputTableNamePerfmonStats+N'_Actuals'); -SET @FullOutputTableNameWaitStats = QUOTENAME(@OutputDatabaseName)+N'.'+QUOTENAME(@OutputSchemaName)+N'.'+QUOTENAME(@OutputTableNameWaitStats+N'_Deltas'); -SET @FullOutputTableNameBlitzCache = QUOTENAME(@OutputDatabaseName)+N'.'+QUOTENAME(@OutputSchemaName)+N'.'+QUOTENAME(@OutputTableNameBlitzCache); -SET @FullOutputTableNameBlitzWho = QUOTENAME(@OutputDatabaseName)+N'.'+QUOTENAME(@OutputSchemaName)+N'.'+QUOTENAME(@OutputTableNameBlitzWho+N'_Deltas'); -IF OBJECT_ID('tempdb.dbo.#BlitzFirstCounts') IS NOT NULL -BEGIN - DROP TABLE #BlitzFirstCounts; -END +/*Insert thank you stuff last*/ + INSERT #Warnings ( CheckId, Priority, DatabaseName, Finding, Warning ) -CREATE TABLE #BlitzFirstCounts ( - [Priority] TINYINT NOT NULL, - [FindingsGroup] VARCHAR(50) NOT NULL, - [Finding] VARCHAR(200) NOT NULL, - [TotalOccurrences] INT NULL, - [FirstOccurrence] DATETIMEOFFSET(7) NULL, - [LastOccurrence] DATETIMEOFFSET(7) NULL -); + SELECT + 2147483647 AS [CheckId], + 2147483647 AS [Priority], + 'From Your Community Volunteers' AS [DatabaseName], + 'sp_BlitzBackups Version: ' + @Version + ', Version Date: ' + CONVERT(VARCHAR(30), @VersionDate) + '.' AS [Finding], + 'Thanks for using our stored procedure. We hope you find it useful! Check out our other free SQL Server scripts at firstresponderkit.org!' AS [Warning]; -/* Validate variables and set defaults as required */ -IF (@BlitzCacheSortorder IS NULL) -BEGIN - SET @BlitzCacheSortorder = N'cpu'; -END +RAISERROR('Rules analysis finished', 0, 1) WITH NOWAIT; -SET @BlitzCacheSortorder = LOWER(@BlitzCacheSortorder); +SELECT w.CheckId, w.Priority, w.DatabaseName, w.Finding, w.Warning +FROM #Warnings AS w +ORDER BY w.Priority, w.CheckId; -IF (@OutputTableNameBlitzCache IS NOT NULL AND @BlitzCacheSortorder NOT IN (N'all',N'cpu',N'reads',N'writes',N'duration',N'executions',N'memory grant',N'spills')) -BEGIN - RAISERROR('Invalid sort option specified for @BlitzCacheSortorder, supported values are ''all'', ''cpu'', ''reads'', ''writes'', ''duration'', ''executions'', ''memory grant'', ''spills''',11,0) WITH NOWAIT; - RETURN; -END +DROP TABLE #Backups, #Warnings, #Recoverability, #RTORecoveryPoints -/* Set @Maxdop to 1 if NULL was passed in */ -IF (@Maxdop IS NULL) -BEGIN - SET @Maxdop = 1; -END -/* iF @Maxdop is set higher than the core count just set it to 0 */ -IF (@Maxdop > (SELECT CAST(cpu_count AS INT) FROM sys.dm_os_sys_info)) -BEGIN - SET @Maxdop = 0; -END +RETURN; -/* We need to check if your SQL version has memory grant and spills columns in sys.dm_exec_query_stats */ -SELECT @IncludeMemoryGrants = - CASE - WHEN (EXISTS(SELECT * FROM sys.all_columns WHERE [object_id] = OBJECT_ID('sys.dm_exec_query_stats') AND name = 'max_grant_kb')) THEN 1 - ELSE 0 - END; +PushBackupHistoryToListener: -SELECT @IncludeSpills = - CASE - WHEN (EXISTS(SELECT * FROM sys.all_columns WHERE [object_id] = OBJECT_ID('sys.dm_exec_query_stats') AND name = 'max_spills')) THEN 1 - ELSE 0 - END; +RAISERROR('Pushing backup history to listener', 0, 1) WITH NOWAIT; +DECLARE @msg NVARCHAR(4000) = N''; +DECLARE @RemoteCheck TABLE (c INT NULL); -IF (@StartDate IS NULL) -BEGIN - RAISERROR('Setting @StartDate to: 1 hour ago',0,0) WITH NOWAIT; - /* Set StartDate to be an hour ago */ - SET @StartDate = DATEADD(HOUR,-1,SYSDATETIMEOFFSET()); - IF (@EndDate IS NULL) - BEGIN - RAISERROR('Setting @EndDate to: Now',0,0) WITH NOWAIT; - /* Get data right up to now */ - SET @EndDate = SYSDATETIMEOFFSET(); +IF @WriteBackupsToDatabaseName IS NULL + BEGIN + RAISERROR('@WriteBackupsToDatabaseName can''t be NULL.', 16, 1) WITH NOWAIT + RETURN; END -END -IF (@EndDate IS NULL) -BEGIN - /* Default to an hour of data or SYSDATETIMEOFFSET() if now is earlier than the hour added to @StartDate */ - IF(DATEADD(HOUR,1,@StartDate) < SYSDATETIMEOFFSET()) - BEGIN - RAISERROR('@EndDate was NULL - Setting to return 1 hour of information, if you want more then set @EndDate aswell',0,0) WITH NOWAIT; - SET @EndDate = DATEADD(HOUR,1,@StartDate); - END - ELSE - BEGIN - RAISERROR('@EndDate was NULL - Setting to SYSDATETIMEOFFSET()',0,0) WITH NOWAIT; - SET @EndDate = SYSDATETIMEOFFSET(); +IF LOWER(@WriteBackupsToDatabaseName) = N'msdb' + BEGIN + RAISERROR('We can''t write to the real msdb, we have to write to a fake msdb.', 16, 1) WITH NOWAIT + RETURN; END -END -/* Default to dbo schema if NULL is passed in */ -IF (@OutputSchemaName IS NULL) -BEGIN - SET @OutputSchemaName = 'dbo'; +IF @WriteBackupsToListenerName IS NULL +BEGIN + IF @AGName IS NULL + BEGIN + RAISERROR('@WriteBackupsToListenerName and @AGName can''t both be NULL.', 16, 1) WITH NOWAIT; + RETURN; + END + ELSE + BEGIN + SELECT @WriteBackupsToListenerName = dns_name + FROM sys.availability_groups AS ag + JOIN sys.availability_group_listeners AS agl + ON ag.group_id = agl.group_id + WHERE name = @AGName; + END + END -/* Prompt the user for @BringThePain = 1 if they are searching a timeframe greater than 4 hours and they are using BlitzCacheSortorder = 'all' */ -IF(@BlitzCacheSortorder = 'all' AND DATEDIFF(HOUR,@StartDate,@EndDate) > 4 AND @BringThePain = 0) +IF @WriteBackupsToListenerName IS NOT NULL BEGIN - RAISERROR('Wow! hold up now, are you sure you wanna do this? Are sure you want to query over 4 hours of data with @BlitzCacheSortorder set to ''all''? IF you do then set @BringThePain = 1 but I gotta warn you this might hurt a bit!',11,1) WITH NOWAIT; - RETURN; + IF NOT EXISTS + ( + SELECT * + FROM sys.servers s + WHERE name = @WriteBackupsToListenerName + ) + BEGIN + SET @msg = N'We need a linked server to write data across. Please set one up for ' + @WriteBackupsToListenerName + N'.'; + RAISERROR(@msg, 16, 1) WITH NOWAIT; + RETURN; + END END -/* Output report window information */ -SELECT - @Servername AS [ServerToReportOn], - CAST(1 AS NVARCHAR(20)) + N' - '+ CAST(@MaxBlitzFirstPriority AS NVARCHAR(20)) AS [PrioritesToInclude], - @StartDate AS [StartDatetime], - @EndDate AS [EndDatetime];; + SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; + SET @StringToExecute += N'SELECT TOP 1 1 FROM ' + + QUOTENAME(@WriteBackupsToListenerName) + N'.master.sys.databases d WHERE d.name = @i_WriteBackupsToDatabaseName;' -/* BlitzFirst data */ -SET @Sql = N' -INSERT INTO #BlitzFirstCounts ([Priority],[FindingsGroup],[Finding],[TotalOccurrences],[FirstOccurrence],[LastOccurrence]) -SELECT -[Priority], -[FindingsGroup], -[Finding], -COUNT(*) AS [TotalOccurrences], -MIN(CheckDate) AS [FirstOccurrence], -MAX(CheckDate) AS [LastOccurrence] -FROM '+@FullOutputTableNameBlitzFirst+N' -WHERE [ServerName] = @Servername -AND [Priority] BETWEEN 1 AND @MaxBlitzFirstPriority -AND CheckDate BETWEEN @StartDate AND @EndDate -AND [CheckID] > -1 -GROUP BY [Priority],[FindingsGroup],[Finding]; + IF @Debug = 1 + PRINT @StringToExecute; -IF EXISTS(SELECT 1 FROM #BlitzFirstCounts) -BEGIN - SELECT - [Priority], - [FindingsGroup], - [Finding], - [TotalOccurrences], - [FirstOccurrence], - [LastOccurrence] - FROM #BlitzFirstCounts - ORDER BY [Priority] ASC,[TotalOccurrences] DESC; -END -ELSE -BEGIN - SELECT N''No findings with a priority between 1 and ''+CAST(@MaxBlitzFirstPriority AS NVARCHAR(10))+N'' found for this period''; -END -SELECT - [ServerName] -,[CheckDate] -,[CheckID] -,[Priority] -,[Finding] -,[URL] -,[Details] -,[HowToStopIt] -,[QueryPlan] -,[QueryText] -FROM '+@FullOutputTableNameBlitzFirst+N' Findings -WHERE [ServerName] = @Servername -AND [Priority] BETWEEN 1 AND @MaxBlitzFirstPriority -AND [CheckDate] BETWEEN @StartDate AND @EndDate -AND [CheckID] > -1 -ORDER BY CheckDate ASC,[Priority] ASC -OPTION (RECOMPILE, MAXDOP '+CAST(@Maxdop AS NVARCHAR(2))+N');'; + INSERT @RemoteCheck (c) + EXEC sp_executesql @StringToExecute, N'@i_WriteBackupsToDatabaseName NVARCHAR(256)', @i_WriteBackupsToDatabaseName = @WriteBackupsToDatabaseName; + IF @@ROWCOUNT = 0 + BEGIN + SET @msg = N'The database ' + @WriteBackupsToDatabaseName + N' doesn''t appear to exist on that server.' + RAISERROR(@msg, 16, 1) WITH NOWAIT + RETURN; + END -RAISERROR('Getting BlitzFirst info from %s',0,0,@FullOutputTableNameBlitzFirst) WITH NOWAIT; -IF (@Debug = 1) -BEGIN - PRINT @Sql; -END + SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; -IF (OBJECT_ID(@FullOutputTableNameBlitzFirst) IS NULL) -BEGIN - IF (@OutputTableNameBlitzFirst IS NULL) - BEGIN - RAISERROR('BlitzFirst data skipped',10,0); - SELECT N'Skipped logged BlitzFirst data as NULL was passed to parameter @OutputTableNameBlitzFirst'; - END - ELSE - BEGIN - RAISERROR('Table provided for BlitzFirst data: %s does not exist',10,0,@FullOutputTableNameBlitzFirst); - SELECT N'No BlitzFirst data available as the table cannot be found'; - END + SET @StringToExecute += N'SELECT TOP 1 1 FROM ' + + QUOTENAME(@WriteBackupsToListenerName) + '.' + QUOTENAME(@WriteBackupsToDatabaseName) + '.sys.tables WHERE name = ''backupset'' AND SCHEMA_NAME(schema_id) = ''dbo''; + ' + @crlf; -END -ELSE /* Table exists then run the query */ -BEGIN - EXEC sp_executesql @Sql, - N'@StartDate DATETIMEOFFSET(7), - @EndDate DATETIMEOFFSET(7), - @Servername NVARCHAR(128), - @MaxBlitzFirstPriority INT', - @StartDate=@StartDate, - @EndDate=@EndDate, - @Servername=@Servername, - @MaxBlitzFirstPriority = @MaxBlitzFirstPriority; -END + IF @Debug = 1 + PRINT @StringToExecute; -/* Blitz WaitStats data */ -SET @Sql = N'SELECT -[ServerName], -[CheckDate], -[wait_type], -[WaitsRank], -[WaitCategory], -[Ignorable], -[ElapsedSeconds], -[wait_time_ms_delta], -[wait_time_minutes_delta], -[wait_time_minutes_per_minute], -[signal_wait_time_ms_delta], -[waiting_tasks_count_delta], -ISNULL((CAST([wait_time_ms_delta] AS DECIMAL(38,2))/NULLIF(CAST([waiting_tasks_count_delta] AS DECIMAL(38,2)),0)),0) AS [wait_time_ms_per_wait] -FROM -( - SELECT - [ServerName], - [CheckDate], - [wait_type], - [WaitCategory], - [Ignorable], - [ElapsedSeconds], - [wait_time_ms_delta], - [wait_time_minutes_delta], - [wait_time_minutes_per_minute], - [signal_wait_time_ms_delta], - [waiting_tasks_count_delta], - ROW_NUMBER() OVER(PARTITION BY [CheckDate] ORDER BY [CheckDate] ASC,[wait_time_ms_delta] DESC) AS [WaitsRank] - FROM '+@FullOutputTableNameWaitStats+N' AS [Waits] - WHERE [ServerName] = @Servername - AND [CheckDate] BETWEEN @StartDate AND @EndDate -) TopWaits -WHERE [WaitsRank] <= @WaitStatsTop -ORDER BY -[CheckDate] ASC, -[wait_time_ms_delta] DESC -OPTION(RECOMPILE, MAXDOP '+CAST(@Maxdop AS NVARCHAR(2))+N');' + INSERT @RemoteCheck (c) + EXEC sp_executesql @StringToExecute; -RAISERROR('Getting wait stats info from %s',0,0,@FullOutputTableNameWaitStats) WITH NOWAIT; + IF @@ROWCOUNT = 0 + BEGIN -IF (@Debug = 1) -BEGIN - PRINT @Sql; -END + SET @msg = N'The database ' + @WriteBackupsToDatabaseName + N' doesn''t appear to have a table called dbo.backupset in it.' + RAISERROR(@msg, 0, 1) WITH NOWAIT + RAISERROR('Don''t worry, we''ll create it for you!', 0, 1) WITH NOWAIT + + SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; -IF (OBJECT_ID(@FullOutputTableNameWaitStats) IS NULL) -BEGIN - IF (@OutputTableNameWaitStats IS NULL) - BEGIN - RAISERROR('Wait stats data skipped',10,0); - SELECT N'Skipped logged wait stats data as NULL was passed to parameter @OutputTableNameWaitStats'; - END - ELSE - BEGIN - RAISERROR('Table provided for wait stats data: %s does not exist',10,0,@FullOutputTableNameWaitStats); - SELECT N'No wait stats data available as the table cannot be found'; - END -END -ELSE /* Table exists then run the query */ -BEGIN - EXEC sp_executesql @Sql, - N'@StartDate DATETIMEOFFSET(7), - @EndDate DATETIMEOFFSET(7), - @Servername NVARCHAR(128), - @WaitStatsTop TINYINT', - @StartDate=@StartDate, - @EndDate=@EndDate, - @Servername=@Servername, - @WaitStatsTop=@WaitStatsTop; -END + SET @StringToExecute += N'CREATE TABLE ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.dbo.backupset + ( backup_set_id INT IDENTITY(1, 1), backup_set_uuid UNIQUEIDENTIFIER, media_set_id INT, first_family_number TINYINT, first_media_number SMALLINT, + last_family_number TINYINT, last_media_number SMALLINT, catalog_family_number TINYINT, catalog_media_number SMALLINT, position INT, expiration_date DATETIME, + software_vendor_id INT, name NVARCHAR(128), description NVARCHAR(255), user_name NVARCHAR(128), software_major_version TINYINT, software_minor_version TINYINT, + software_build_version SMALLINT, time_zone SMALLINT, mtf_minor_version TINYINT, first_lsn NUMERIC(25, 0), last_lsn NUMERIC(25, 0), checkpoint_lsn NUMERIC(25, 0), + database_backup_lsn NUMERIC(25, 0), database_creation_date DATETIME, backup_start_date DATETIME, backup_finish_date DATETIME, type CHAR(1), sort_order SMALLINT, + code_page SMALLINT, compatibility_level TINYINT, database_version INT, backup_size NUMERIC(20, 0), database_name NVARCHAR(128), server_name NVARCHAR(128), + machine_name NVARCHAR(128), flags INT, unicode_locale INT, unicode_compare_style INT, collation_name NVARCHAR(128), is_password_protected BIT, recovery_model NVARCHAR(60), + has_bulk_logged_data BIT, is_snapshot BIT, is_readonly BIT, is_single_user BIT, has_backup_checksums BIT, is_damaged BIT, begins_log_chain BIT, has_incomplete_metadata BIT, + is_force_offline BIT, is_copy_only BIT, first_recovery_fork_guid UNIQUEIDENTIFIER, last_recovery_fork_guid UNIQUEIDENTIFIER, fork_point_lsn NUMERIC(25, 0), database_guid UNIQUEIDENTIFIER, + family_guid UNIQUEIDENTIFIER, differential_base_lsn NUMERIC(25, 0), differential_base_guid UNIQUEIDENTIFIER, compressed_backup_size NUMERIC(20, 0), key_algorithm NVARCHAR(32), + encryptor_thumbprint VARBINARY(20) , encryptor_type NVARCHAR(32) + ); + ' + @crlf; + + SET @InnerStringToExecute = N'EXEC( ''' + @StringToExecute + ''' ) AT ' + QUOTENAME(@WriteBackupsToListenerName) + N';' + + IF @Debug = 1 + PRINT @InnerStringToExecute; + + EXEC sp_executesql @InnerStringToExecute -/* BlitzFileStats info */ -SET @Sql = N' -SELECT -[ServerName], -[CheckDate], -CASE - WHEN MAX([io_stall_read_ms_average]) > @ReadLatencyThreshold THEN ''Yes'' - WHEN MAX([io_stall_write_ms_average]) > @WriteLatencyThreshold THEN ''Yes'' - ELSE ''No'' -END AS [io_stall_ms_breached], -LEFT([PhysicalName],LEN([PhysicalName])-CHARINDEX(''\'',REVERSE([PhysicalName]))+1) AS [PhysicalPath], -SUM([SizeOnDiskMB]) AS [SizeOnDiskMB], -SUM([SizeOnDiskMBgrowth]) AS [SizeOnDiskMBgrowth], -MAX([io_stall_read_ms]) AS [max_io_stall_read_ms], -MAX([io_stall_read_ms_average]) AS [max_io_stall_read_ms_average], -@ReadLatencyThreshold AS [is_stall_read_ms_threshold], -SUM([num_of_reads]) AS [num_of_reads], -SUM([megabytes_read]) AS [megabytes_read], -MAX([io_stall_write_ms]) AS [max_io_stall_write_ms], -MAX([io_stall_write_ms_average]) AS [max_io_stall_write_ms_average], -@WriteLatencyThreshold AS [io_stall_write_ms_average], -SUM([num_of_writes]) AS [num_of_writes], -SUM([megabytes_written]) AS [megabytes_written] -FROM '+@FullOutputTableNameFileStats+N' -WHERE [ServerName] = @Servername -AND [CheckDate] BETWEEN @StartDate AND @EndDate -' -+CASE - WHEN @Databasename IS NOT NULL THEN N'AND [DatabaseName] IN (N''tempdb'',@Databasename) -' - ELSE N'' -END -+N'GROUP BY -[ServerName], -[CheckDate], -LEFT([PhysicalName],LEN([PhysicalName])-CHARINDEX(''\'',REVERSE([PhysicalName]))+1) -ORDER BY -[CheckDate] ASC -OPTION (RECOMPILE, MAXDOP '+CAST(@Maxdop AS NVARCHAR(2))+N');' -RAISERROR('Getting FileStats info from %s',0,0,@FullOutputTableNameFileStats) WITH NOWAIT; + RAISERROR('We''ll even make the indexes!', 0, 1) WITH NOWAIT -IF (@Debug = 1) -BEGIN - PRINT @Sql; -END + /*Checking for and creating the PK/CX*/ -IF (OBJECT_ID(@FullOutputTableNameFileStats) IS NULL) -BEGIN - IF (@OutputTableNameFileStats IS NULL) - BEGIN - RAISERROR('File stats data skipped',10,0); - SELECT N'Skipped logged File stats data as NULL was passed to parameter @OutputTableNameFileStats'; - END - ELSE - BEGIN - RAISERROR('Table provided for FileStats data: %s does not exist',10,0,@FullOutputTableNameFileStats); - SELECT N'No File stats data available as the table cannot be found'; - END -END -ELSE /* Table exists then run the query */ -BEGIN - EXEC sp_executesql @Sql, - N'@StartDate DATETIMEOFFSET(7), - @EndDate DATETIMEOFFSET(7), - @Servername NVARCHAR(128), - @Databasename NVARCHAR(128), - @ReadLatencyThreshold INT, - @WriteLatencyThreshold INT', - @StartDate=@StartDate, - @EndDate=@EndDate, - @Servername=@Servername, - @Databasename = @Databasename, - @ReadLatencyThreshold = @ReadLatencyThreshold, - @WriteLatencyThreshold = @WriteLatencyThreshold; -END + SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; + + SET @StringToExecute += N' + + IF NOT EXISTS ( + SELECT t.name, i.name + FROM ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.sys.tables AS t + JOIN ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.sys.indexes AS i + ON t.object_id = i.object_id + WHERE t.name = ? + AND i.name LIKE ? + ) -/* Blitz Perfmon stats*/ -SET @Sql = N' -SELECT - [ServerName] - ,[CheckDate] - ,[counter_name] - ,[object_name] - ,[instance_name] - ,[cntr_value] -FROM '+@FullOutputTableNamePerfmonStats+N' -WHERE [ServerName] = @Servername -AND CheckDate BETWEEN @StartDate AND @EndDate -ORDER BY - [CheckDate] ASC, - [counter_name] ASC -OPTION (RECOMPILE, MAXDOP '+CAST(@Maxdop AS NVARCHAR(2))+N');' + BEGIN + ALTER TABLE ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.[dbo].[backupset] ADD PRIMARY KEY CLUSTERED ([backup_set_id] ASC) + END + ' -RAISERROR('Getting Perfmon info from %s',0,0,@FullOutputTableNamePerfmonStats) WITH NOWAIT; + SET @InnerStringToExecute = N'EXEC( ''' + @StringToExecute + ''', ''backupset'', ''PK[_][_]%'' ) AT ' + QUOTENAME(@WriteBackupsToListenerName) + N';' + + IF @Debug = 1 + PRINT @InnerStringToExecute; + + EXEC sp_executesql @InnerStringToExecute -IF (@Debug = 1) -BEGIN - PRINT @Sql; -END -IF (OBJECT_ID(@FullOutputTableNamePerfmonStats) IS NULL) -BEGIN - IF (@OutputTableNamePerfmonStats IS NULL) - BEGIN - RAISERROR('Perfmon stats data skipped',10,0); - SELECT N'Skipped logged Perfmon stats data as NULL was passed to parameter @OutputTableNamePerfmonStats'; - END - ELSE - BEGIN - RAISERROR('Table provided for Perfmon stats data: %s does not exist',10,0,@FullOutputTableNamePerfmonStats); - SELECT N'No Perfmon data available as the table cannot be found'; - END -END -ELSE /* Table exists then run the query */ -BEGIN - EXEC sp_executesql @Sql, - N'@StartDate DATETIMEOFFSET(7), - @EndDate DATETIMEOFFSET(7), - @Servername NVARCHAR(128)', - @StartDate=@StartDate, - @EndDate=@EndDate, - @Servername=@Servername; -END -/* Blitz cache data */ -RAISERROR('Sortorder for BlitzCache data: %s',0,0,@BlitzCacheSortorder) WITH NOWAIT; + /*Checking for and creating index on backup_set_uuid*/ -/* Set intial CTE */ -SET @Sql = N'WITH CheckDates AS ( -SELECT DISTINCT CheckDate -FROM ' -+@FullOutputTableNameBlitzCache -+N' -WHERE [ServerName] = @Servername -AND [CheckDate] BETWEEN @StartDate AND @EndDate' -+@NewLine -+CASE - WHEN @Databasename IS NOT NULL THEN N'AND [DatabaseName] = @Databasename'+@NewLine - ELSE N'' -END -+N')' -; + SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; + + SET @StringToExecute += N'IF NOT EXISTS ( + SELECT t.name, i.name + FROM ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.sys.tables AS t + JOIN ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.sys.indexes AS i + ON t.object_id = i.object_id + WHERE t.name = ? + AND i.name = ? + ) -SET @Sql += @NewLine; + BEGIN + CREATE NONCLUSTERED INDEX [backupsetuuid] ON ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.[dbo].[backupset] ([backup_set_uuid] ASC) + END + ' -/* Append additional CTEs based on sortorder */ -SET @Sql += ( -SELECT CAST(N',' AS NVARCHAR(MAX)) -+[SortOptions].[Aliasname]+N' AS ( -SELECT - [ServerName] - ,'+[SortOptions].[Aliasname]+N'.[CheckDate] - ,[Sortorder] - ,[TimeFrameRank] - ,ROW_NUMBER() OVER(ORDER BY ['+[SortOptions].[Columnname]+N'] DESC) AS [OverallRank] - ,'+[SortOptions].[Aliasname]+N'.[QueryType] - ,'+[SortOptions].[Aliasname]+N'.[QueryText] - ,'+[SortOptions].[Aliasname]+N'.[DatabaseName] - ,'+[SortOptions].[Aliasname]+N'.[AverageCPU] - ,'+[SortOptions].[Aliasname]+N'.[TotalCPU] - ,'+[SortOptions].[Aliasname]+N'.[PercentCPUByType] - ,'+[SortOptions].[Aliasname]+N'.[AverageDuration] - ,'+[SortOptions].[Aliasname]+N'.[TotalDuration] - ,'+[SortOptions].[Aliasname]+N'.[PercentDurationByType] - ,'+[SortOptions].[Aliasname]+N'.[AverageReads] - ,'+[SortOptions].[Aliasname]+N'.[TotalReads] - ,'+[SortOptions].[Aliasname]+N'.[PercentReadsByType] - ,'+[SortOptions].[Aliasname]+N'.[AverageWrites] - ,'+[SortOptions].[Aliasname]+N'.[TotalWrites] - ,'+[SortOptions].[Aliasname]+N'.[PercentWritesByType] - ,'+[SortOptions].[Aliasname]+N'.[ExecutionCount] - ,'+[SortOptions].[Aliasname]+N'.[ExecutionWeight] - ,'+[SortOptions].[Aliasname]+N'.[PercentExecutionsByType] - ,'+[SortOptions].[Aliasname]+N'.[ExecutionsPerMinute] - ,'+[SortOptions].[Aliasname]+N'.[PlanCreationTime] - ,'+[SortOptions].[Aliasname]+N'.[PlanCreationTimeHours] - ,'+[SortOptions].[Aliasname]+N'.[LastExecutionTime] - ,'+[SortOptions].[Aliasname]+N'.[PlanHandle] - ,'+[SortOptions].[Aliasname]+N'.[SqlHandle] - ,'+[SortOptions].[Aliasname]+N'.[SQL Handle More Info] - ,'+[SortOptions].[Aliasname]+N'.[QueryHash] - ,'+[SortOptions].[Aliasname]+N'.[Query Hash More Info] - ,'+[SortOptions].[Aliasname]+N'.[QueryPlanHash] - ,'+[SortOptions].[Aliasname]+N'.[StatementStartOffset] - ,'+[SortOptions].[Aliasname]+N'.[StatementEndOffset] - ,'+[SortOptions].[Aliasname]+N'.[MinReturnedRows] - ,'+[SortOptions].[Aliasname]+N'.[MaxReturnedRows] - ,'+[SortOptions].[Aliasname]+N'.[AverageReturnedRows] - ,'+[SortOptions].[Aliasname]+N'.[TotalReturnedRows] - ,'+[SortOptions].[Aliasname]+N'.[QueryPlan] - ,'+[SortOptions].[Aliasname]+N'.[NumberOfPlans] - ,'+[SortOptions].[Aliasname]+N'.[NumberOfDistinctPlans] - ,'+[SortOptions].[Aliasname]+N'.[MinGrantKB] - ,'+[SortOptions].[Aliasname]+N'.[MaxGrantKB] - ,'+[SortOptions].[Aliasname]+N'.[MinUsedGrantKB] - ,'+[SortOptions].[Aliasname]+N'.[MaxUsedGrantKB] - ,'+[SortOptions].[Aliasname]+N'.[PercentMemoryGrantUsed] - ,'+[SortOptions].[Aliasname]+N'.[AvgMaxMemoryGrant] - ,'+[SortOptions].[Aliasname]+N'.[MinSpills] - ,'+[SortOptions].[Aliasname]+N'.[MaxSpills] - ,'+[SortOptions].[Aliasname]+N'.[TotalSpills] - ,'+[SortOptions].[Aliasname]+N'.[AvgSpills] - ,'+[SortOptions].[Aliasname]+N'.[QueryPlanCost] -FROM CheckDates -CROSS APPLY ( - SELECT TOP (5) - [ServerName] - ,'+[SortOptions].[Aliasname]+N'.[CheckDate] - ,'+QUOTENAME(UPPER([SortOptions].[Sortorder]),N'''')+N' AS [Sortorder] - ,ROW_NUMBER() OVER(ORDER BY ['+[SortOptions].[Columnname]+N'] DESC) AS [TimeFrameRank] - ,'+[SortOptions].[Aliasname]+N'.[QueryType] - ,'+[SortOptions].[Aliasname]+N'.[QueryText] - ,'+[SortOptions].[Aliasname]+N'.[DatabaseName] - ,'+[SortOptions].[Aliasname]+N'.[AverageCPU] - ,'+[SortOptions].[Aliasname]+N'.[TotalCPU] - ,'+[SortOptions].[Aliasname]+N'.[PercentCPUByType] - ,'+[SortOptions].[Aliasname]+N'.[AverageDuration] - ,'+[SortOptions].[Aliasname]+N'.[TotalDuration] - ,'+[SortOptions].[Aliasname]+N'.[PercentDurationByType] - ,'+[SortOptions].[Aliasname]+N'.[AverageReads] - ,'+[SortOptions].[Aliasname]+N'.[TotalReads] - ,'+[SortOptions].[Aliasname]+N'.[PercentReadsByType] - ,'+[SortOptions].[Aliasname]+N'.[AverageWrites] - ,'+[SortOptions].[Aliasname]+N'.[TotalWrites] - ,'+[SortOptions].[Aliasname]+N'.[PercentWritesByType] - ,'+[SortOptions].[Aliasname]+N'.[ExecutionCount] - ,'+[SortOptions].[Aliasname]+N'.[ExecutionWeight] - ,'+[SortOptions].[Aliasname]+N'.[PercentExecutionsByType] - ,'+[SortOptions].[Aliasname]+N'.[ExecutionsPerMinute] - ,'+[SortOptions].[Aliasname]+N'.[PlanCreationTime] - ,'+[SortOptions].[Aliasname]+N'.[PlanCreationTimeHours] - ,'+[SortOptions].[Aliasname]+N'.[LastExecutionTime] - ,'+[SortOptions].[Aliasname]+N'.[PlanHandle] - ,'+[SortOptions].[Aliasname]+N'.[SqlHandle] - ,'+[SortOptions].[Aliasname]+N'.[SQL Handle More Info] - ,'+[SortOptions].[Aliasname]+N'.[QueryHash] - ,'+[SortOptions].[Aliasname]+N'.[Query Hash More Info] - ,'+[SortOptions].[Aliasname]+N'.[QueryPlanHash] - ,'+[SortOptions].[Aliasname]+N'.[StatementStartOffset] - ,'+[SortOptions].[Aliasname]+N'.[StatementEndOffset] - ,'+[SortOptions].[Aliasname]+N'.[MinReturnedRows] - ,'+[SortOptions].[Aliasname]+N'.[MaxReturnedRows] - ,'+[SortOptions].[Aliasname]+N'.[AverageReturnedRows] - ,'+[SortOptions].[Aliasname]+N'.[TotalReturnedRows] - ,'+[SortOptions].[Aliasname]+N'.[QueryPlan] - ,'+[SortOptions].[Aliasname]+N'.[NumberOfPlans] - ,'+[SortOptions].[Aliasname]+N'.[NumberOfDistinctPlans] - ,'+[SortOptions].[Aliasname]+N'.[MinGrantKB] - ,'+[SortOptions].[Aliasname]+N'.[MaxGrantKB] - ,'+[SortOptions].[Aliasname]+N'.[MinUsedGrantKB] - ,'+[SortOptions].[Aliasname]+N'.[MaxUsedGrantKB] - ,'+[SortOptions].[Aliasname]+N'.[PercentMemoryGrantUsed] - ,'+[SortOptions].[Aliasname]+N'.[AvgMaxMemoryGrant] - ,'+[SortOptions].[Aliasname]+N'.[MinSpills] - ,'+[SortOptions].[Aliasname]+N'.[MaxSpills] - ,'+[SortOptions].[Aliasname]+N'.[TotalSpills] - ,'+[SortOptions].[Aliasname]+N'.[AvgSpills] - ,'+[SortOptions].[Aliasname]+N'.[QueryPlanCost] - FROM '+@FullOutputTableNameBlitzCache+N' AS '+[SortOptions].[Aliasname]+N' - WHERE [ServerName] = @Servername - AND [CheckDate] BETWEEN @StartDate AND @EndDate - AND ['+[SortOptions].[Aliasname]+N'].[CheckDate] = [CheckDates].[CheckDate]' - +@NewLine - +CASE - WHEN @Databasename IS NOT NULL THEN N'AND ['+[SortOptions].[Aliasname]+N'].[DatabaseName] = @Databasename'+@NewLine - ELSE N'' - END - +CASE - WHEN [Sortorder] = N'cpu' THEN N'AND [TotalCPU] > 0' - WHEN [Sortorder] = N'reads' THEN N'AND [TotalReads] > 0' - WHEN [Sortorder] = N'writes' THEN N'AND [TotalWrites] > 0' - WHEN [Sortorder] = N'duration' THEN N'AND [TotalDuration] > 0' - WHEN [Sortorder] = N'executions' THEN N'AND [ExecutionCount] > 0' - WHEN [Sortorder] = N'memory grant' THEN N'AND [MaxGrantKB] > 0' - WHEN [Sortorder] = N'spills' THEN N'AND [MaxSpills] > 0' - ELSE N'' - END - +N' - ORDER BY ['+[SortOptions].[Columnname]+N'] DESC) '+[SortOptions].[Aliasname]+N' -)' -FROM (VALUES - (N'cpu',N'TopCPU',N'TotalCPU'), - (N'reads',N'TopReads',N'TotalReads'), - (N'writes',N'TopWrites',N'TotalWrites'), - (N'duration',N'TopDuration',N'TotalDuration'), - (N'executions',N'TopExecutions',N'ExecutionCount'), - (N'memory grant',N'TopMemoryGrants',N'MaxGrantKB'), - (N'spills',N'TopSpills',N'MaxSpills') - ) SortOptions(Sortorder,Aliasname,Columnname) -WHERE - CASE /* for spills and memory grant sorts make sure the underlying columns exist in the DMV otherwise do not include them */ - WHEN (@IncludeMemoryGrants = 0 OR @IncludeMemoryGrants IS NULL) AND ([SortOptions].[Sortorder] = N'memory grant' OR [SortOptions].[Sortorder] = N'all') THEN NULL - WHEN (@IncludeSpills = 0 OR @IncludeSpills IS NULL) AND ([SortOptions].[Sortorder] = N'spills' OR [SortOptions].[Sortorder] = N'all') THEN NULL - ELSE [SortOptions].[Sortorder] - END = ISNULL(NULLIF(@BlitzCacheSortorder,N'all'),[SortOptions].[Sortorder]) -FOR XML PATH(N''), TYPE).value(N'.[1]', N'NVARCHAR(MAX)'); - -SET @Sql += @NewLine; - -/* Build the select statements to return the data after CTE declarations */ -SET @Sql += ( -SELECT STUFF(( -SELECT @NewLine -+N'UNION ALL' -+@NewLine -+N'SELECT * -FROM '+[SortOptions].[Aliasname] -FROM (VALUES - (N'cpu',N'TopCPU',N'TotalCPU'), - (N'reads',N'TopReads',N'TotalReads'), - (N'writes',N'TopWrites',N'TotalWrites'), - (N'duration',N'TopDuration',N'TotalDuration'), - (N'executions',N'TopExecutions',N'ExecutionCount'), - (N'memory grant',N'TopMemoryGrants',N'MaxGrantKB'), - (N'spills',N'TopSpills',N'MaxSpills') - ) SortOptions(Sortorder,Aliasname,Columnname) -WHERE - CASE /* for spills and memory grant sorts make sure the underlying columns exist in the DMV otherwise do not include them */ - WHEN (@IncludeMemoryGrants = 0 OR @IncludeMemoryGrants IS NULL) AND ([SortOptions].[Sortorder] = N'memory grant' OR [SortOptions].[Sortorder] = N'all') THEN NULL - WHEN (@IncludeSpills = 0 OR @IncludeSpills IS NULL) AND ([SortOptions].[Sortorder] = N'spills' OR [SortOptions].[Sortorder] = N'all') THEN NULL - ELSE [SortOptions].[Sortorder] - END = ISNULL(NULLIF(@BlitzCacheSortorder,N'all'),[SortOptions].[Sortorder]) -FOR XML PATH(N''), TYPE).value(N'.[1]', N'NVARCHAR(MAX)'),1,11,N'') -); - -/* Append Order By */ -SET @Sql += @NewLine -+N'ORDER BY - [Sortorder] ASC, - [CheckDate] ASC, - [TimeFrameRank] ASC'; - -/* Append OPTION(RECOMPILE, MAXDOP) to complete the statement */ -SET @Sql += @NewLine -+N'OPTION(RECOMPILE, MAXDOP '+CAST(@Maxdop AS NVARCHAR(2))+N');'; - -RAISERROR('Getting BlitzCache info from %s',0,0,@FullOutputTableNameBlitzCache) WITH NOWAIT; - -IF (@Debug = 1) -BEGIN - PRINT SUBSTRING(@Sql, 0, 4000); - PRINT SUBSTRING(@Sql, 4000, 8000); - PRINT SUBSTRING(@Sql, 8000, 12000); - PRINT SUBSTRING(@Sql, 12000, 16000); - PRINT SUBSTRING(@Sql, 16000, 20000); - PRINT SUBSTRING(@Sql, 20000, 24000); - PRINT SUBSTRING(@Sql, 24000, 28000); -END - -IF (OBJECT_ID(@FullOutputTableNameBlitzCache) IS NULL) -BEGIN - IF (@OutputTableNameBlitzCache IS NULL) - BEGIN - RAISERROR('BlitzCache data skipped',10,0); - SELECT N'Skipped logged BlitzCache data as NULL was passed to parameter @OutputTableNameBlitzCache'; - END - ELSE - BEGIN - RAISERROR('Table provided for BlitzCache data: %s does not exist',10,0,@FullOutputTableNameBlitzCache); - SELECT N'No BlitzCache data available as the table cannot be found'; - END -END -ELSE /* Table exists then run the query */ -BEGIN - EXEC sp_executesql @Sql, - N'@Servername NVARCHAR(128), - @Databasename NVARCHAR(128), - @BlitzCacheSortorder NVARCHAR(20), - @StartDate DATETIMEOFFSET(7), - @EndDate DATETIMEOFFSET(7)', - @Servername = @Servername, - @Databasename = @Databasename, - @BlitzCacheSortorder = @BlitzCacheSortorder, - @StartDate = @StartDate, - @EndDate = @EndDate; -END - - - -/* BlitzWho data */ -SET @Sql = N' -SELECT [ServerName] - ,[CheckDate] - ,[elapsed_time] - ,[session_id] - ,[database_name] - ,[query_text_snippet] - ,[query_plan] - ,[live_query_plan] - ,[query_cost] - ,[status] - ,[wait_info] - ,[top_session_waits] - ,[blocking_session_id] - ,[open_transaction_count] - ,[is_implicit_transaction] - ,[nt_domain] - ,[host_name] - ,[login_name] - ,[nt_user_name] - ,[program_name] - ,[fix_parameter_sniffing] - ,[client_interface_name] - ,[login_time] - ,[start_time] - ,[request_time] - ,[request_cpu_time] - ,[degree_of_parallelism] - ,[request_logical_reads] - ,[Logical_Reads_MB] - ,[request_writes] - ,[Logical_Writes_MB] - ,[request_physical_reads] - ,[Physical_reads_MB] - ,[session_cpu] - ,[session_logical_reads] - ,[session_logical_reads_MB] - ,[session_physical_reads] - ,[session_physical_reads_MB] - ,[session_writes] - ,[session_writes_MB] - ,[tempdb_allocations_mb] - ,[memory_usage] - ,[estimated_completion_time] - ,[percent_complete] - ,[deadlock_priority] - ,[transaction_isolation_level] - ,[last_dop] - ,[min_dop] - ,[max_dop] - ,[last_grant_kb] - ,[min_grant_kb] - ,[max_grant_kb] - ,[last_used_grant_kb] - ,[min_used_grant_kb] - ,[max_used_grant_kb] - ,[last_ideal_grant_kb] - ,[min_ideal_grant_kb] - ,[max_ideal_grant_kb] - ,[last_reserved_threads] - ,[min_reserved_threads] - ,[max_reserved_threads] - ,[last_used_threads] - ,[min_used_threads] - ,[max_used_threads] - ,[grant_time] - ,[requested_memory_kb] - ,[grant_memory_kb] - ,[is_request_granted] - ,[required_memory_kb] - ,[query_memory_grant_used_memory_kb] - ,[ideal_memory_kb] - ,[is_small] - ,[timeout_sec] - ,[resource_semaphore_id] - ,[wait_order] - ,[wait_time_ms] - ,[next_candidate_for_memory_grant] - ,[target_memory_kb] - ,[max_target_memory_kb] - ,[total_memory_kb] - ,[available_memory_kb] - ,[granted_memory_kb] - ,[query_resource_semaphore_used_memory_kb] - ,[grantee_count] - ,[waiter_count] - ,[timeout_error_count] - ,[forced_grant_count] - ,[workload_group_name] - ,[resource_pool_name] - ,[context_info] - ,[query_hash] - ,[query_plan_hash] - ,[sql_handle] - ,[plan_handle] - ,[statement_start_offset] - ,[statement_end_offset] - FROM '+@FullOutputTableNameBlitzWho+N' - WHERE [ServerName] = @Servername - AND ([CheckDate] BETWEEN @StartDate AND @EndDate OR [start_time] BETWEEN CAST(@StartDate AS DATETIME) AND CAST(@EndDate AS DATETIME)) - ' - +CASE - WHEN @Databasename IS NOT NULL THEN N'AND [database_name] = @Databasename - ' - ELSE N'' - END -+N'ORDER BY [CheckDate] ASC - OPTION (RECOMPILE, MAXDOP '+CAST(@Maxdop AS NVARCHAR(2))+N');'; - -RAISERROR('Getting BlitzWho info from %s',0,0,@FullOutputTableNameBlitzWho) WITH NOWAIT; - -IF (@Debug = 1) -BEGIN - PRINT @Sql; -END - -IF (OBJECT_ID(@FullOutputTableNameBlitzWho) IS NULL) -BEGIN - IF (@OutputTableNameBlitzWho IS NULL) - BEGIN - RAISERROR('BlitzWho data skipped',10,0); - SELECT N'Skipped logged BlitzWho data as NULL was passed to parameter @OutputTableNameBlitzWho'; - END - ELSE - BEGIN - RAISERROR('Table provided for BlitzWho data: %s does not exist',10,0,@FullOutputTableNameBlitzWho); - SELECT N'No BlitzWho data available as the table cannot be found'; - END -END -ELSE -BEGIN - EXEC sp_executesql @Sql, - N'@StartDate DATETIMEOFFSET(7), - @EndDate DATETIMEOFFSET(7), - @Servername NVARCHAR(128), - @Databasename NVARCHAR(128)', - @StartDate=@StartDate, - @EndDate=@EndDate, - @Servername=@Servername, - @Databasename = @Databasename; -END + SET @InnerStringToExecute = N'EXEC( ''' + @StringToExecute + ''', ''backupset'', ''backupsetuuid'' ) AT ' + QUOTENAME(@WriteBackupsToListenerName) + N';' + + IF @Debug = 1 + PRINT @InnerStringToExecute; + + EXEC sp_executesql @InnerStringToExecute + + + + /*Checking for and creating index on media_set_id*/ + SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; + + SET @StringToExecute += 'IF NOT EXISTS ( + SELECT t.name, i.name + FROM ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.sys.tables AS t + JOIN ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.sys.indexes AS i + ON t.object_id = i.object_id + WHERE t.name = ? + AND i.name = ? + ) -GO -IF OBJECT_ID('dbo.sp_BlitzBackups') IS NULL - EXEC ('CREATE PROCEDURE dbo.sp_BlitzBackups AS RETURN 0;'); -GO -ALTER PROCEDURE [dbo].[sp_BlitzBackups] - @Help TINYINT = 0 , - @HoursBack INT = 168, - @MSDBName NVARCHAR(256) = 'msdb', - @AGName NVARCHAR(256) = NULL, - @RestoreSpeedFullMBps INT = NULL, - @RestoreSpeedDiffMBps INT = NULL, - @RestoreSpeedLogMBps INT = NULL, - @Debug TINYINT = 0, - @PushBackupHistoryToListener BIT = 0, - @WriteBackupsToListenerName NVARCHAR(256) = NULL, - @WriteBackupsToDatabaseName NVARCHAR(256) = NULL, - @WriteBackupsLastHours INT = 168, - @Version VARCHAR(30) = NULL OUTPUT, - @VersionDate DATETIME = NULL OUTPUT, - @VersionCheckMode BIT = 0 -WITH RECOMPILE -AS - BEGIN - SET NOCOUNT ON; - SET STATISTICS XML OFF; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - - SELECT @Version = '8.19', @VersionDate = '20240222'; - - IF(@VersionCheckMode = 1) - BEGIN - RETURN; - END; + BEGIN + CREATE NONCLUSTERED INDEX [backupsetMediaSetId] ON ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.[dbo].[backupset] ([media_set_id] ASC) + END + ' - IF @Help = 1 PRINT ' - /* - sp_BlitzBackups from http://FirstResponderKit.org + SET @InnerStringToExecute = N'EXEC( ''' + @StringToExecute + ''', ''backupset'', ''backupsetMediaSetId'' ) AT ' + QUOTENAME(@WriteBackupsToListenerName) + N';' - This script checks your backups to see how much data you might lose when - this server fails, and how long it might take to recover. + IF @Debug = 1 + PRINT @InnerStringToExecute; + + EXEC sp_executesql @InnerStringToExecute + + + + /*Checking for and creating index on backup_finish_date*/ - To learn more, visit http://FirstResponderKit.org where you can download new - versions for free, watch training videos on how it works, get more info on - the findings, contribute your own code, and more. + SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; + + SET @StringToExecute += N'IF NOT EXISTS ( + SELECT t.name, i.name + FROM ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.sys.tables AS t + JOIN ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.sys.indexes AS i + ON t.object_id = i.object_id + WHERE t.name = ? + AND i.name = ? + ) - Known limitations of this version: - - Only Microsoft-supported versions of SQL Server. Sorry, 2005 and 2000. + BEGIN + CREATE NONCLUSTERED INDEX [backupsetDate] ON ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.[dbo].[backupset] ([backup_finish_date] ASC) + END + ' - Unknown limitations of this version: - - None. (If we knew them, they would be known. Duh.) + SET @InnerStringToExecute = N'EXEC( ''' + @StringToExecute + ''', ''backupset'', ''backupsetDate'' ) AT ' + QUOTENAME(@WriteBackupsToListenerName) + N';' + + IF @Debug = 1 + PRINT @InnerStringToExecute; + + EXEC sp_executesql @InnerStringToExecute - Changes - for the full list of improvements and fixes in this version, see: - https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/ + + /*Checking for and creating index on database_name*/ - Parameter explanations: + SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; + + SET @StringToExecute += N'IF NOT EXISTS ( + SELECT t.name, i.name + FROM ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.sys.tables AS t + JOIN ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.sys.indexes AS i + ON t.object_id = i.object_id + WHERE t.name = ? + AND i.name = ? + ) - @HoursBack INT = 168 How many hours of history to examine, back from now. - You can check just the last 24 hours of backups, for example. - @MSDBName NVARCHAR(255) You can restore MSDB from different servers and check them - centrally. Also useful if you create a DBA utility database - and merge data from several servers in an AG into one DB. - @RestoreSpeedFullMBps INT By default, we use the backup speed from MSDB to guesstimate - how fast your restores will go. If you have done performance - tuning and testing of your backups (or if they horribly go even - slower in your DR environment, and you want to account for - that), then you can pass in different numbers here. - @RestoreSpeedDiffMBps INT See above. - @RestoreSpeedLogMBps INT See above. + BEGIN + CREATE NONCLUSTERED INDEX [backupsetDatabaseName] ON ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.[dbo].[backupset] ([database_name] ASC ) INCLUDE ([backup_set_id], [media_set_id]) + END - For more documentation: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/ + ' - MIT License + SET @InnerStringToExecute = N'EXEC( ''' + @StringToExecute + ''', ''backupset'', ''backupsetDatabaseName'' ) AT ' + QUOTENAME(@WriteBackupsToListenerName) + N';' - Copyright (c) Brent Ozar Unlimited + IF @Debug = 1 + PRINT @InnerStringToExecute; + + EXEC sp_executesql @InnerStringToExecute - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: + RAISERROR('Table and indexes created! You''re welcome!', 0, 1) WITH NOWAIT + END - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE. + RAISERROR('Beginning inserts', 0, 1) WITH NOWAIT; + RAISERROR(@crlf, 0, 1) WITH NOWAIT; + /* + Batching code comes from the lovely and talented Michael J. Swart + http://michaeljswart.com/2014/09/take-care-when-scripting-batches/ + If you're ever in Canada, he says you can stay at his house, too. + */ + SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - */'; -ELSE -BEGIN -DECLARE @StringToExecute NVARCHAR(MAX) = N'', - @InnerStringToExecute NVARCHAR(MAX) = N'', - @ProductVersion NVARCHAR(128), - @ProductVersionMajor DECIMAL(10, 2), - @ProductVersionMinor DECIMAL(10, 2), - @StartTime DATETIME2, @ResultText NVARCHAR(MAX), - @crlf NVARCHAR(2), - @MoreInfoHeader NVARCHAR(100), - @MoreInfoFooter NVARCHAR(100); + SET @StringToExecute += N' + DECLARE + @StartDate DATETIME = DATEADD(HOUR, @i_WriteBackupsLastHours, SYSDATETIME()), + @StartDateNext DATETIME, + @RC INT = 1, + @msg NVARCHAR(4000) = N''''; + + SELECT @StartDate = MIN(b.backup_start_date) + FROM msdb.dbo.backupset b + WHERE b.backup_start_date >= @StartDate + AND NOT EXISTS ( + SELECT 1 + FROM ' + QUOTENAME(@WriteBackupsToListenerName) + N'.' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.dbo.backupset b2 + WHERE b.backup_set_uuid = b2.backup_set_uuid + AND b2.backup_start_date >= @StartDate + ) -IF @HoursBack > 0 - SET @HoursBack = @HoursBack * -1; + SET @StartDateNext = DATEADD(MINUTE, 10, @StartDate); -IF @WriteBackupsLastHours > 0 - SET @WriteBackupsLastHours = @WriteBackupsLastHours * -1; + IF + ( @StartDate IS NULL ) + BEGIN + SET @msg = N''No data to move, exiting.'' + RAISERROR(@msg, 0, 1) WITH NOWAIT -SELECT @crlf = NCHAR(13) + NCHAR(10), - @StartTime = DATEADD(hh, @HoursBack, GETDATE()), - @MoreInfoHeader = N''; + RETURN; + END -SET @ProductVersion = CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)); -SELECT @ProductVersionMajor = SUBSTRING(@ProductVersion, 1, CHARINDEX('.', @ProductVersion) + 1), - @ProductVersionMinor = PARSENAME(CONVERT(VARCHAR(32), @ProductVersion), 2); + RAISERROR(''Starting insert loop'', 0, 1) WITH NOWAIT; -CREATE TABLE #Backups -( - id INT IDENTITY(1, 1), - database_name NVARCHAR(128), - database_guid UNIQUEIDENTIFIER, - RPOWorstCaseMinutes DECIMAL(18, 1), - RTOWorstCaseMinutes DECIMAL(18, 1), - RPOWorstCaseBackupSetID INT, - RPOWorstCaseBackupSetFinishTime DATETIME, - RPOWorstCaseBackupSetIDPrior INT, - RPOWorstCaseBackupSetPriorFinishTime DATETIME, - RPOWorstCaseMoreInfoQuery XML, - RTOWorstCaseBackupFileSizeMB DECIMAL(18, 2), - RTOWorstCaseMoreInfoQuery XML, - FullMBpsAvg DECIMAL(18, 2), - FullMBpsMin DECIMAL(18, 2), - FullMBpsMax DECIMAL(18, 2), - FullSizeMBAvg DECIMAL(18, 2), - FullSizeMBMin DECIMAL(18, 2), - FullSizeMBMax DECIMAL(18, 2), - FullCompressedSizeMBAvg DECIMAL(18, 2), - FullCompressedSizeMBMin DECIMAL(18, 2), - FullCompressedSizeMBMax DECIMAL(18, 2), - DiffMBpsAvg DECIMAL(18, 2), - DiffMBpsMin DECIMAL(18, 2), - DiffMBpsMax DECIMAL(18, 2), - DiffSizeMBAvg DECIMAL(18, 2), - DiffSizeMBMin DECIMAL(18, 2), - DiffSizeMBMax DECIMAL(18, 2), - DiffCompressedSizeMBAvg DECIMAL(18, 2), - DiffCompressedSizeMBMin DECIMAL(18, 2), - DiffCompressedSizeMBMax DECIMAL(18, 2), - LogMBpsAvg DECIMAL(18, 2), - LogMBpsMin DECIMAL(18, 2), - LogMBpsMax DECIMAL(18, 2), - LogSizeMBAvg DECIMAL(18, 2), - LogSizeMBMin DECIMAL(18, 2), - LogSizeMBMax DECIMAL(18, 2), - LogCompressedSizeMBAvg DECIMAL(18, 2), - LogCompressedSizeMBMin DECIMAL(18, 2), - LogCompressedSizeMBMax DECIMAL(18, 2) -); - -CREATE TABLE #RTORecoveryPoints -( - id INT IDENTITY(1, 1), - database_name NVARCHAR(128), - database_guid UNIQUEIDENTIFIER, - rto_worst_case_size_mb AS - ( COALESCE(log_file_size_mb, 0) + COALESCE(diff_file_size_mb, 0) + COALESCE(full_file_size_mb, 0)), - rto_worst_case_time_seconds AS - ( COALESCE(log_time_seconds, 0) + COALESCE(diff_time_seconds, 0) + COALESCE(full_time_seconds, 0)), - full_backup_set_id INT, - full_last_lsn NUMERIC(25, 0), - full_backup_set_uuid UNIQUEIDENTIFIER, - full_time_seconds BIGINT, - full_file_size_mb DECIMAL(18, 2), - diff_backup_set_id INT, - diff_last_lsn NUMERIC(25, 0), - diff_time_seconds BIGINT, - diff_file_size_mb DECIMAL(18, 2), - log_backup_set_id INT, - log_last_lsn NUMERIC(25, 0), - log_time_seconds BIGINT, - log_file_size_mb DECIMAL(18, 2), - log_backups INT -); - -CREATE TABLE #Recoverability - ( - Id INT IDENTITY , - DatabaseName NVARCHAR(128), - DatabaseGUID UNIQUEIDENTIFIER, - LastBackupRecoveryModel NVARCHAR(60), - FirstFullBackupSizeMB DECIMAL (18,2), - FirstFullBackupDate DATETIME, - LastFullBackupSizeMB DECIMAL (18,2), - LastFullBackupDate DATETIME, - AvgFullBackupThroughputMB DECIMAL (18,2), - AvgFullBackupDurationSeconds INT, - AvgDiffBackupThroughputMB DECIMAL (18,2), - AvgDiffBackupDurationSeconds INT, - AvgLogBackupThroughputMB DECIMAL (18,2), - AvgLogBackupDurationSeconds INT, - AvgFullSizeMB DECIMAL (18,2), - AvgDiffSizeMB DECIMAL (18,2), - AvgLogSizeMB DECIMAL (18,2), - IsBigDiff AS CASE WHEN (AvgFullSizeMB > 10240. AND ((AvgDiffSizeMB * 100.) / AvgFullSizeMB >= 40.)) THEN 1 ELSE 0 END, - IsBigLog AS CASE WHEN (AvgFullSizeMB > 10240. AND ((AvgLogSizeMB * 100.) / AvgFullSizeMB >= 20.)) THEN 1 ELSE 0 END - ); - -CREATE TABLE #Trending -( - DatabaseName NVARCHAR(128), - DatabaseGUID UNIQUEIDENTIFIER, - [0] DECIMAL(18, 2), - [-1] DECIMAL(18, 2), - [-2] DECIMAL(18, 2), - [-3] DECIMAL(18, 2), - [-4] DECIMAL(18, 2), - [-5] DECIMAL(18, 2), - [-6] DECIMAL(18, 2), - [-7] DECIMAL(18, 2), - [-8] DECIMAL(18, 2), - [-9] DECIMAL(18, 2), - [-10] DECIMAL(18, 2), - [-11] DECIMAL(18, 2), - [-12] DECIMAL(18, 2) -); - - -CREATE TABLE #Warnings -( - Id INT IDENTITY(1, 1) PRIMARY KEY CLUSTERED, - CheckId INT, - Priority INT, - DatabaseName VARCHAR(128), - Finding VARCHAR(256), - Warning VARCHAR(8000) -); - -IF NOT EXISTS(SELECT * FROM sys.databases WHERE name = @MSDBName) - BEGIN - RAISERROR('@MSDBName was specified, but the database does not exist.', 16, 1) WITH NOWAIT; - RETURN; - END - -IF @PushBackupHistoryToListener = 1 -GOTO PushBackupHistoryToListener - - - RAISERROR('Inserting to #Backups', 0, 1) WITH NOWAIT; - - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - - SET @StringToExecute += N'WITH Backups AS (SELECT bs.database_name, bs.database_guid, bs.type AS backup_type ' + @crlf - + ' , MBpsAvg = CAST(AVG(( bs.backup_size / ( CASE WHEN DATEDIFF(ss, bs.backup_start_date, bs.backup_finish_date) = 0 THEN 1 ELSE DATEDIFF(ss, bs.backup_start_date, bs.backup_finish_date) END ) / 1048576 )) AS INT) ' + @crlf - + ' , MBpsMin = CAST(MIN(( bs.backup_size / ( CASE WHEN DATEDIFF(ss, bs.backup_start_date, bs.backup_finish_date) = 0 THEN 1 ELSE DATEDIFF(ss, bs.backup_start_date, bs.backup_finish_date) END ) / 1048576 )) AS INT) ' + @crlf - + ' , MBpsMax = CAST(MAX(( bs.backup_size / ( CASE WHEN DATEDIFF(ss, bs.backup_start_date, bs.backup_finish_date) = 0 THEN 1 ELSE DATEDIFF(ss, bs.backup_start_date, bs.backup_finish_date) END ) / 1048576 )) AS INT) ' + @crlf - + ' , SizeMBAvg = AVG(backup_size / 1048576.0) ' + @crlf - + ' , SizeMBMin = MIN(backup_size / 1048576.0) ' + @crlf - + ' , SizeMBMax = MAX(backup_size / 1048576.0) ' + @crlf - + ' , CompressedSizeMBAvg = AVG(compressed_backup_size / 1048576.0) ' + @crlf - + ' , CompressedSizeMBMin = MIN(compressed_backup_size / 1048576.0) ' + @crlf - + ' , CompressedSizeMBMax = MAX(compressed_backup_size / 1048576.0) ' + @crlf; - - - SET @StringToExecute += N' FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset bs ' + @crlf - + N' WHERE bs.backup_finish_date >= @StartTime AND bs.is_damaged = 0 ' + @crlf - + N' GROUP BY bs.database_name, bs.database_guid, bs.type)' + @crlf; - - SET @StringToExecute += + N'INSERT INTO #Backups(database_name, database_guid, ' + @crlf - + N' FullMBpsAvg, FullMBpsMin, FullMBpsMax, FullSizeMBAvg, FullSizeMBMin, FullSizeMBMax, FullCompressedSizeMBAvg, FullCompressedSizeMBMin, FullCompressedSizeMBMax, ' + @crlf - + N' DiffMBpsAvg, DiffMBpsMin, DiffMBpsMax, DiffSizeMBAvg, DiffSizeMBMin, DiffSizeMBMax, DiffCompressedSizeMBAvg, DiffCompressedSizeMBMin, DiffCompressedSizeMBMax, ' + @crlf - + N' LogMBpsAvg, LogMBpsMin, LogMBpsMax, LogSizeMBAvg, LogSizeMBMin, LogSizeMBMax, LogCompressedSizeMBAvg, LogCompressedSizeMBMin, LogCompressedSizeMBMax ) ' + @crlf - + N'SELECT bF.database_name, bF.database_guid ' + @crlf - + N' , bF.MBpsAvg AS FullMBpsAvg ' + @crlf - + N' , bF.MBpsMin AS FullMBpsMin ' + @crlf - + N' , bF.MBpsMax AS FullMBpsMax ' + @crlf - + N' , bF.SizeMBAvg AS FullSizeMBAvg ' + @crlf - + N' , bF.SizeMBMin AS FullSizeMBMin ' + @crlf - + N' , bF.SizeMBMax AS FullSizeMBMax ' + @crlf - + N' , bF.CompressedSizeMBAvg AS FullCompressedSizeMBAvg ' + @crlf - + N' , bF.CompressedSizeMBMin AS FullCompressedSizeMBMin ' + @crlf - + N' , bF.CompressedSizeMBMax AS FullCompressedSizeMBMax ' + @crlf - + N' , bD.MBpsAvg AS DiffMBpsAvg ' + @crlf - + N' , bD.MBpsMin AS DiffMBpsMin ' + @crlf - + N' , bD.MBpsMax AS DiffMBpsMax ' + @crlf - + N' , bD.SizeMBAvg AS DiffSizeMBAvg ' + @crlf - + N' , bD.SizeMBMin AS DiffSizeMBMin ' + @crlf - + N' , bD.SizeMBMax AS DiffSizeMBMax ' + @crlf - + N' , bD.CompressedSizeMBAvg AS DiffCompressedSizeMBAvg ' + @crlf - + N' , bD.CompressedSizeMBMin AS DiffCompressedSizeMBMin ' + @crlf - + N' , bD.CompressedSizeMBMax AS DiffCompressedSizeMBMax ' + @crlf - + N' , bL.MBpsAvg AS LogMBpsAvg ' + @crlf - + N' , bL.MBpsMin AS LogMBpsMin ' + @crlf - + N' , bL.MBpsMax AS LogMBpsMax ' + @crlf - + N' , bL.SizeMBAvg AS LogSizeMBAvg ' + @crlf - + N' , bL.SizeMBMin AS LogSizeMBMin ' + @crlf - + N' , bL.SizeMBMax AS LogSizeMBMax ' + @crlf - + N' , bL.CompressedSizeMBAvg AS LogCompressedSizeMBAvg ' + @crlf - + N' , bL.CompressedSizeMBMin AS LogCompressedSizeMBMin ' + @crlf - + N' , bL.CompressedSizeMBMax AS LogCompressedSizeMBMax ' + @crlf - + N' FROM Backups bF ' + @crlf - + N' LEFT OUTER JOIN Backups bD ON bF.database_name = bD.database_name AND bF.database_guid = bD.database_guid AND bD.backup_type = ''I''' + @crlf - + N' LEFT OUTER JOIN Backups bL ON bF.database_name = bL.database_name AND bF.database_guid = bL.database_guid AND bL.backup_type = ''L''' + @crlf - + N' WHERE bF.backup_type = ''D''; ' + @crlf; - - IF @Debug = 1 - PRINT @StringToExecute; - - EXEC sys.sp_executesql @StringToExecute, N'@StartTime DATETIME2', @StartTime; - - - RAISERROR('Updating #Backups with worst RPO case', 0, 1) WITH NOWAIT; - - SET @StringToExecute =N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - - SET @StringToExecute += N' - SELECT bs.database_name, bs.database_guid, bs.backup_set_id, bsPrior.backup_set_id AS backup_set_id_prior, - bs.backup_finish_date, bsPrior.backup_finish_date AS backup_finish_date_prior, - DATEDIFF(ss, bsPrior.backup_finish_date, bs.backup_finish_date) AS backup_gap_seconds - INTO #backup_gaps - FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS bs - CROSS APPLY ( - SELECT TOP 1 bs1.backup_set_id, bs1.backup_finish_date - FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS bs1 - WHERE bs.database_name = bs1.database_name - AND bs.database_guid = bs1.database_guid - AND bs.backup_finish_date > bs1.backup_finish_date - AND bs.backup_set_id > bs1.backup_set_id - ORDER BY bs1.backup_finish_date DESC, bs1.backup_set_id DESC - ) bsPrior - WHERE bs.backup_finish_date > @StartTime - - CREATE CLUSTERED INDEX cx_backup_gaps ON #backup_gaps (database_name, database_guid, backup_set_id, backup_finish_date, backup_gap_seconds); - - WITH max_gaps AS ( - SELECT g.database_name, g.database_guid, g.backup_set_id, g.backup_set_id_prior, g.backup_finish_date_prior, - g.backup_finish_date, MAX(g.backup_gap_seconds) AS max_backup_gap_seconds - FROM #backup_gaps AS g - GROUP BY g.database_name, g.database_guid, g.backup_set_id, g.backup_set_id_prior, g.backup_finish_date_prior, g.backup_finish_date + WHILE EXISTS ( + SELECT 1 + FROM msdb.dbo.backupset b + WHERE NOT EXISTS ( + SELECT 1 + FROM ' + QUOTENAME(@WriteBackupsToListenerName) + N'.' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.dbo.backupset b2 + WHERE b.backup_set_uuid = b2.backup_set_uuid + AND b2.backup_start_date >= @StartDate + ) ) - UPDATE #Backups - SET RPOWorstCaseMinutes = bg.max_backup_gap_seconds / 60.0 - , RPOWorstCaseBackupSetID = bg.backup_set_id - , RPOWorstCaseBackupSetFinishTime = bg.backup_finish_date - , RPOWorstCaseBackupSetIDPrior = bg.backup_set_id_prior - , RPOWorstCaseBackupSetPriorFinishTime = bg.backup_finish_date_prior - FROM #Backups b - INNER HASH JOIN max_gaps bg ON b.database_name = bg.database_name AND b.database_guid = bg.database_guid - LEFT OUTER HASH JOIN max_gaps bgBigger ON bg.database_name = bgBigger.database_name AND bg.database_guid = bgBigger.database_guid AND bg.max_backup_gap_seconds < bgBigger.max_backup_gap_seconds - WHERE bgBigger.backup_set_id IS NULL; - '; - IF @Debug = 1 - PRINT @StringToExecute; - - EXEC sys.sp_executesql @StringToExecute, N'@StartTime DATETIME2', @StartTime; - - RAISERROR('Updating #Backups with worst RPO case queries', 0, 1) WITH NOWAIT; - - UPDATE #Backups - SET RPOWorstCaseMoreInfoQuery = @MoreInfoHeader + N'SELECT * ' + @crlf - + N' FROM ' + QUOTENAME(@MSDBName) + '.dbo.backupset ' + @crlf - + N' WHERE database_name = ''' + database_name + ''' ' + @crlf - + N' AND database_guid = ''' + CAST(database_guid AS NVARCHAR(50)) + ''' ' + @crlf - + N' AND backup_finish_date >= DATEADD(hh, -2, ''' + CAST(CONVERT(DATETIME, RPOWorstCaseBackupSetPriorFinishTime, 102) AS NVARCHAR(100)) + ''') ' + @crlf - + N' AND backup_finish_date <= DATEADD(hh, 2, ''' + CAST(CONVERT(DATETIME, RPOWorstCaseBackupSetPriorFinishTime, 102) AS NVARCHAR(100)) + ''') ' + @crlf - + N' ORDER BY backup_finish_date;' - + @MoreInfoFooter; - - -/* RTO */ - -RAISERROR('Gathering RTO information', 0, 1) WITH NOWAIT; - - - SET @StringToExecute =N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - - SET @StringToExecute += N' - INSERT INTO #RTORecoveryPoints(database_name, database_guid, log_last_lsn) - SELECT database_name, database_guid, MAX(last_lsn) AS log_last_lsn - FROM ' + QUOTENAME(@MSDBName) + '.dbo.backupset bLastLog - WHERE type = ''L'' - AND bLastLog.backup_finish_date >= @StartTime - GROUP BY database_name, database_guid; - '; - - IF @Debug = 1 - PRINT @StringToExecute; - - EXEC sys.sp_executesql @StringToExecute, N'@StartTime DATETIME2', @StartTime; - -/* Find the most recent full backups for those logs */ - -RAISERROR('Updating #RTORecoveryPoints', 0, 1) WITH NOWAIT; - - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - - SET @StringToExecute += N' - UPDATE #RTORecoveryPoints - SET log_backup_set_id = bLasted.backup_set_id - ,full_backup_set_id = bLasted.backup_set_id - ,full_last_lsn = bLasted.last_lsn - ,full_backup_set_uuid = bLasted.backup_set_uuid - FROM #RTORecoveryPoints rp - CROSS APPLY ( - SELECT TOP 1 bLog.backup_set_id AS backup_set_id_log, bLastFull.backup_set_id, bLastFull.last_lsn, bLastFull.backup_set_uuid, bLastFull.database_guid, bLastFull.database_name - FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset bLog - INNER JOIN ' + QUOTENAME(@MSDBName) + N'.dbo.backupset bLastFull - ON bLog.database_guid = bLastFull.database_guid - AND bLog.database_name = bLastFull.database_name - AND bLog.first_lsn > bLastFull.last_lsn - AND bLastFull.type = ''D'' - WHERE rp.database_guid = bLog.database_guid - AND rp.database_name = bLog.database_name - ) bLasted - LEFT OUTER JOIN ' + QUOTENAME(@MSDBName) + N'.dbo.backupset bLaterFulls ON bLasted.database_guid = bLaterFulls.database_guid AND bLasted.database_name = bLaterFulls.database_name - AND bLasted.last_lsn < bLaterFulls.last_lsn - AND bLaterFulls.first_lsn < bLasted.last_lsn - AND bLaterFulls.type = ''D'' - WHERE bLaterFulls.backup_set_id IS NULL; - '; - - IF @Debug = 1 - PRINT @StringToExecute; - - EXEC sys.sp_executesql @StringToExecute; - -/* Add any full backups in the StartDate range that weren't part of the above log backup chain */ - -RAISERROR('Add any full backups in the StartDate range that weren''t part of the above log backup chain', 0, 1) WITH NOWAIT; - - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - - SET @StringToExecute += N' - INSERT INTO #RTORecoveryPoints(database_name, database_guid, full_backup_set_id, full_last_lsn, full_backup_set_uuid) - SELECT bFull.database_name, bFull.database_guid, bFull.backup_set_id, bFull.last_lsn, bFull.backup_set_uuid - FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset bFull - LEFT OUTER JOIN #RTORecoveryPoints rp ON bFull.backup_set_uuid = rp.full_backup_set_uuid - WHERE bFull.type = ''D'' - AND bFull.backup_finish_date IS NOT NULL - AND rp.full_backup_set_uuid IS NULL - AND bFull.backup_finish_date >= @StartTime; - '; - - IF @Debug = 1 - PRINT @StringToExecute; - - EXEC sys.sp_executesql @StringToExecute, N'@StartTime DATETIME2', @StartTime; - -/* Fill out the most recent log for that full, but before the next full */ - -RAISERROR('Fill out the most recent log for that full, but before the next full', 0, 1) WITH NOWAIT; - - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - - SET @StringToExecute += N' - UPDATE rp - SET log_last_lsn = (SELECT MAX(last_lsn) FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset bLog WHERE bLog.first_lsn >= rp.full_last_lsn AND bLog.first_lsn <= rpNextFull.full_last_lsn AND bLog.type = ''L'') - FROM #RTORecoveryPoints rp - INNER JOIN #RTORecoveryPoints rpNextFull ON rp.database_guid = rpNextFull.database_guid AND rp.database_name = rpNextFull.database_name - AND rp.full_last_lsn < rpNextFull.full_last_lsn - LEFT OUTER JOIN #RTORecoveryPoints rpEarlierFull ON rp.database_guid = rpEarlierFull.database_guid AND rp.database_name = rpEarlierFull.database_name - AND rp.full_last_lsn < rpEarlierFull.full_last_lsn - AND rpNextFull.full_last_lsn > rpEarlierFull.full_last_lsn - WHERE rpEarlierFull.full_backup_set_id IS NULL; - '; - - IF @Debug = 1 - PRINT @StringToExecute; + BEGIN + + SET @msg = N''Inserting data for '' + CONVERT(NVARCHAR(30), @StartDate) + '' through '' + + CONVERT(NVARCHAR(30), @StartDateNext) + ''.'' + RAISERROR(@msg, 0, 1) WITH NOWAIT + + ' - EXEC sys.sp_executesql @StringToExecute; + SET @StringToExecute += N'INSERT ' + QUOTENAME(@WriteBackupsToListenerName) + N'.' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.dbo.backupset + ' + SET @StringToExecute += N' (database_name, database_guid, backup_set_uuid, type, backup_size, backup_start_date, backup_finish_date, media_set_id, time_zone, + compressed_backup_size, recovery_model, server_name, machine_name, first_lsn, last_lsn, user_name, compatibility_level, + is_password_protected, is_snapshot, is_readonly, is_single_user, has_backup_checksums, is_damaged, ' + CASE WHEN @ProductVersionMajor >= 12 + THEN + N'encryptor_type, has_bulk_logged_data)' + @crlf + ELSE + N'has_bulk_logged_data)' + @crlf + END + + SET @StringToExecute +=N' + SELECT database_name, database_guid, backup_set_uuid, type, backup_size, backup_start_date, backup_finish_date, media_set_id, time_zone, + compressed_backup_size, recovery_model, server_name, machine_name, first_lsn, last_lsn, user_name, compatibility_level, + is_password_protected, is_snapshot, is_readonly, is_single_user, has_backup_checksums, is_damaged, ' + CASE WHEN @ProductVersionMajor >= 12 + THEN + N'encryptor_type, has_bulk_logged_data' + @crlf + ELSE + N'has_bulk_logged_data' + @crlf + END + SET @StringToExecute +=N' + FROM msdb.dbo.backupset b + WHERE 1=1 + AND b.backup_start_date >= @StartDate + AND b.backup_start_date < @StartDateNext + AND NOT EXISTS ( + SELECT 1 + FROM ' + QUOTENAME(@WriteBackupsToListenerName) + N'.' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.dbo.backupset b2 + WHERE b.backup_set_uuid = b2.backup_set_uuid + AND b2.backup_start_date >= @StartDate + )' + @crlf; -/* Fill out a diff in that range */ -RAISERROR('Fill out a diff in that range', 0, 1) WITH NOWAIT; + SET @StringToExecute +=N' + SET @RC = @@ROWCOUNT; + + SET @msg = N''Inserted '' + CONVERT(NVARCHAR(30), @RC) + '' rows for ''+ CONVERT(NVARCHAR(30), @StartDate) + '' through '' + CONVERT(NVARCHAR(30), @StartDateNext) + ''.'' + RAISERROR(@msg, 0, 1) WITH NOWAIT + + SET @StartDate = @StartDateNext; + SET @StartDateNext = DATEADD(MINUTE, 10, @StartDate); - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; + IF + ( @StartDate > SYSDATETIME() ) + BEGIN + + SET @msg = N''No more data to move, exiting.'' + RAISERROR(@msg, 0, 1) WITH NOWAIT + + BREAK; - SET @StringToExecute += N' - UPDATE #RTORecoveryPoints - SET diff_last_lsn = (SELECT TOP 1 bDiff.last_lsn FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset bDiff - WHERE rp.database_guid = bDiff.database_guid AND rp.database_name = bDiff.database_name - AND bDiff.type = ''I'' - AND bDiff.last_lsn < rp.log_last_lsn - AND rp.full_backup_set_uuid = bDiff.differential_base_guid - ORDER BY bDiff.last_lsn DESC) - FROM #RTORecoveryPoints rp - WHERE diff_last_lsn IS NULL; - '; + END + END' + @crlf; IF @Debug = 1 PRINT @StringToExecute; - EXEC sys.sp_executesql @StringToExecute; - -/* Get time & size totals for full & diff */ + EXEC sp_executesql @StringToExecute, N'@i_WriteBackupsLastHours INT', @i_WriteBackupsLastHours = @WriteBackupsLastHours; -RAISERROR('Get time & size totals for full & diff', 0, 1) WITH NOWAIT; +END; - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; +END; - SET @StringToExecute += N' - UPDATE #RTORecoveryPoints - SET full_time_seconds = DATEDIFF(ss,bFull.backup_start_date, bFull.backup_finish_date) - , full_file_size_mb = bFull.backup_size / 1048576.0 - , diff_backup_set_id = bDiff.backup_set_id - , diff_time_seconds = DATEDIFF(ss,bDiff.backup_start_date, bDiff.backup_finish_date) - , diff_file_size_mb = bDiff.backup_size / 1048576.0 - FROM #RTORecoveryPoints rp - INNER JOIN ' + QUOTENAME(@MSDBName) + N'.dbo.backupset bFull ON rp.database_guid = bFull.database_guid AND rp.database_name = bFull.database_name AND rp.full_last_lsn = bFull.last_lsn - LEFT OUTER JOIN ' + QUOTENAME(@MSDBName) + N'.dbo.backupset bDiff ON rp.database_guid = bDiff.database_guid AND rp.database_name = bDiff.database_name AND rp.diff_last_lsn = bDiff.last_lsn AND bDiff.last_lsn IS NOT NULL; - '; +GO +SET ANSI_NULLS ON; +SET ANSI_PADDING ON; +SET ANSI_WARNINGS ON; +SET ARITHABORT ON; +SET CONCAT_NULL_YIELDS_NULL ON; +SET QUOTED_IDENTIFIER ON; +SET STATISTICS IO OFF; +SET STATISTICS TIME OFF; +GO - IF @Debug = 1 - PRINT @StringToExecute; - - EXEC sys.sp_executesql @StringToExecute; +IF ( +SELECT + CASE + WHEN CONVERT(NVARCHAR(128), SERVERPROPERTY ('PRODUCTVERSION')) LIKE '8%' THEN 0 + WHEN CONVERT(NVARCHAR(128), SERVERPROPERTY ('PRODUCTVERSION')) LIKE '9%' THEN 0 + ELSE 1 + END +) = 0 +BEGIN + DECLARE @msg VARCHAR(8000); + SELECT @msg = 'Sorry, sp_BlitzCache doesn''t work on versions of SQL prior to 2008.' + REPLICATE(CHAR(13), 7933); + PRINT @msg; + RETURN; +END; +IF OBJECT_ID('dbo.sp_BlitzCache') IS NULL + EXEC ('CREATE PROCEDURE dbo.sp_BlitzCache AS RETURN 0;'); +GO -/* Get time & size totals for logs */ +IF OBJECT_ID('dbo.sp_BlitzCache') IS NOT NULL AND OBJECT_ID('tempdb.dbo.##BlitzCacheProcs', 'U') IS NOT NULL + EXEC ('DROP TABLE ##BlitzCacheProcs;'); +GO -RAISERROR('Get time & size totals for logs', 0, 1) WITH NOWAIT; +IF OBJECT_ID('dbo.sp_BlitzCache') IS NOT NULL AND OBJECT_ID('tempdb.dbo.##BlitzCacheResults', 'U') IS NOT NULL + EXEC ('DROP TABLE ##BlitzCacheResults;'); +GO - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; +CREATE TABLE ##BlitzCacheResults ( + SPID INT, + ID INT IDENTITY(1,1), + CheckID INT, + Priority TINYINT, + FindingsGroup VARCHAR(50), + Finding VARCHAR(500), + URL VARCHAR(200), + Details VARCHAR(4000) +); - SET @StringToExecute += N' - WITH LogTotals AS ( - SELECT rp.id, log_time_seconds = SUM(DATEDIFF(ss,bLog.backup_start_date, bLog.backup_finish_date)) - , log_file_size = SUM(bLog.backup_size) - , SUM(1) AS log_backups - FROM #RTORecoveryPoints rp - INNER JOIN ' + QUOTENAME(@MSDBName) + N'.dbo.backupset bLog ON rp.database_guid = bLog.database_guid AND rp.database_name = bLog.database_name AND bLog.type = ''L'' - AND bLog.first_lsn > COALESCE(rp.diff_last_lsn, rp.full_last_lsn) - AND bLog.first_lsn <= rp.log_last_lsn - GROUP BY rp.id - ) - UPDATE #RTORecoveryPoints - SET log_time_seconds = lt.log_time_seconds - , log_file_size_mb = lt.log_file_size / 1048576.0 - , log_backups = lt.log_backups - FROM #RTORecoveryPoints rp - INNER JOIN LogTotals lt ON rp.id = lt.id; - '; +CREATE TABLE ##BlitzCacheProcs ( + SPID INT , + QueryType NVARCHAR(258), + DatabaseName sysname, + AverageCPU DECIMAL(38,4), + AverageCPUPerMinute DECIMAL(38,4), + TotalCPU DECIMAL(38,4), + PercentCPUByType MONEY, + PercentCPU MONEY, + AverageDuration DECIMAL(38,4), + TotalDuration DECIMAL(38,4), + PercentDuration MONEY, + PercentDurationByType MONEY, + AverageReads BIGINT, + TotalReads BIGINT, + PercentReads MONEY, + PercentReadsByType MONEY, + ExecutionCount BIGINT, + PercentExecutions MONEY, + PercentExecutionsByType MONEY, + ExecutionsPerMinute MONEY, + TotalWrites BIGINT, + AverageWrites MONEY, + PercentWrites MONEY, + PercentWritesByType MONEY, + WritesPerMinute MONEY, + PlanCreationTime DATETIME, + PlanCreationTimeHours AS DATEDIFF(HOUR, PlanCreationTime, SYSDATETIME()), + LastExecutionTime DATETIME, + LastCompletionTime DATETIME, + PlanHandle VARBINARY(64), + [Remove Plan Handle From Cache] AS + CASE WHEN [PlanHandle] IS NOT NULL + THEN 'DBCC FREEPROCCACHE (' + CONVERT(VARCHAR(128), [PlanHandle], 1) + ');' + ELSE 'N/A' END, + SqlHandle VARBINARY(64), + [Remove SQL Handle From Cache] AS + CASE WHEN [SqlHandle] IS NOT NULL + THEN 'DBCC FREEPROCCACHE (' + CONVERT(VARCHAR(128), [SqlHandle], 1) + ');' + ELSE 'N/A' END, + [SQL Handle More Info] AS + CASE WHEN [SqlHandle] IS NOT NULL + THEN 'EXEC sp_BlitzCache @OnlySqlHandles = ''' + CONVERT(VARCHAR(128), [SqlHandle], 1) + '''; ' + ELSE 'N/A' END, + QueryHash BINARY(8), + [Query Hash More Info] AS + CASE WHEN [QueryHash] IS NOT NULL + THEN 'EXEC sp_BlitzCache @OnlyQueryHashes = ''' + CONVERT(VARCHAR(32), [QueryHash], 1) + '''; ' + ELSE 'N/A' END, + QueryPlanHash BINARY(8), + StatementStartOffset INT, + StatementEndOffset INT, + PlanGenerationNum BIGINT, + MinReturnedRows BIGINT, + MaxReturnedRows BIGINT, + AverageReturnedRows MONEY, + TotalReturnedRows BIGINT, + LastReturnedRows BIGINT, + /*The Memory Grant columns are only supported + in certain versions, giggle giggle. + */ + MinGrantKB BIGINT, + MaxGrantKB BIGINT, + MinUsedGrantKB BIGINT, + MaxUsedGrantKB BIGINT, + PercentMemoryGrantUsed MONEY, + AvgMaxMemoryGrant MONEY, + MinSpills BIGINT, + MaxSpills BIGINT, + TotalSpills BIGINT, + AvgSpills MONEY, + QueryText NVARCHAR(MAX), + QueryPlan XML, + /* these next four columns are the total for the type of query. + don't actually use them for anything apart from math by type. + */ + TotalWorkerTimeForType BIGINT, + TotalElapsedTimeForType BIGINT, + TotalReadsForType DECIMAL(30), + TotalExecutionCountForType BIGINT, + TotalWritesForType DECIMAL(30), + NumberOfPlans INT, + NumberOfDistinctPlans INT, + SerialDesiredMemory FLOAT, + SerialRequiredMemory FLOAT, + CachedPlanSize FLOAT, + CompileTime FLOAT, + CompileCPU FLOAT , + CompileMemory FLOAT , + MaxCompileMemory FLOAT , + min_worker_time BIGINT, + max_worker_time BIGINT, + is_forced_plan BIT, + is_forced_parameterized BIT, + is_cursor BIT, + is_optimistic_cursor BIT, + is_forward_only_cursor BIT, + is_fast_forward_cursor BIT, + is_cursor_dynamic BIT, + is_parallel BIT, + is_forced_serial BIT, + is_key_lookup_expensive BIT, + key_lookup_cost FLOAT, + is_remote_query_expensive BIT, + remote_query_cost FLOAT, + frequent_execution BIT, + parameter_sniffing BIT, + unparameterized_query BIT, + near_parallel BIT, + plan_warnings BIT, + plan_multiple_plans INT, + long_running BIT, + downlevel_estimator BIT, + implicit_conversions BIT, + busy_loops BIT, + tvf_join BIT, + tvf_estimate BIT, + compile_timeout BIT, + compile_memory_limit_exceeded BIT, + warning_no_join_predicate BIT, + QueryPlanCost FLOAT, + missing_index_count INT, + unmatched_index_count INT, + min_elapsed_time BIGINT, + max_elapsed_time BIGINT, + age_minutes MONEY, + age_minutes_lifetime MONEY, + is_trivial BIT, + trace_flags_session VARCHAR(1000), + is_unused_grant BIT, + function_count INT, + clr_function_count INT, + is_table_variable BIT, + no_stats_warning BIT, + relop_warnings BIT, + is_table_scan BIT, + backwards_scan BIT, + forced_index BIT, + forced_seek BIT, + forced_scan BIT, + columnstore_row_mode BIT, + is_computed_scalar BIT , + is_sort_expensive BIT, + sort_cost FLOAT, + is_computed_filter BIT, + op_name VARCHAR(100) NULL, + index_insert_count INT NULL, + index_update_count INT NULL, + index_delete_count INT NULL, + cx_insert_count INT NULL, + cx_update_count INT NULL, + cx_delete_count INT NULL, + table_insert_count INT NULL, + table_update_count INT NULL, + table_delete_count INT NULL, + index_ops AS (index_insert_count + index_update_count + index_delete_count + + cx_insert_count + cx_update_count + cx_delete_count + + table_insert_count + table_update_count + table_delete_count), + is_row_level BIT, + is_spatial BIT, + index_dml BIT, + table_dml BIT, + long_running_low_cpu BIT, + low_cost_high_cpu BIT, + stale_stats BIT, + is_adaptive BIT, + index_spool_cost FLOAT, + index_spool_rows FLOAT, + table_spool_cost FLOAT, + table_spool_rows FLOAT, + is_spool_expensive BIT, + is_spool_more_rows BIT, + is_table_spool_expensive BIT, + is_table_spool_more_rows BIT, + estimated_rows FLOAT, + is_bad_estimate BIT, + is_paul_white_electric BIT, + is_row_goal BIT, + is_big_spills BIT, + is_mstvf BIT, + is_mm_join BIT, + is_nonsargable BIT, + select_with_writes BIT, + implicit_conversion_info XML, + cached_execution_parameters XML, + missing_indexes XML, + SetOptions VARCHAR(MAX), + Warnings VARCHAR(MAX), + Pattern NVARCHAR(20) + ); +GO - IF @Debug = 1 - PRINT @StringToExecute; +ALTER PROCEDURE dbo.sp_BlitzCache + @Help BIT = 0, + @Top INT = NULL, + @SortOrder VARCHAR(50) = 'CPU', + @UseTriggersAnyway BIT = NULL, + @ExportToExcel BIT = 0, + @ExpertMode TINYINT = 0, + @OutputType VARCHAR(20) = 'TABLE' , + @OutputServerName NVARCHAR(258) = NULL , + @OutputDatabaseName NVARCHAR(258) = NULL , + @OutputSchemaName NVARCHAR(258) = NULL , + @OutputTableName NVARCHAR(258) = NULL , -- do NOT use ##BlitzCacheResults or ##BlitzCacheProcs as they are used as work tables in this procedure + @ConfigurationDatabaseName NVARCHAR(128) = NULL , + @ConfigurationSchemaName NVARCHAR(258) = NULL , + @ConfigurationTableName NVARCHAR(258) = NULL , + @DurationFilter DECIMAL(38,4) = NULL , + @HideSummary BIT = 0 , + @IgnoreSystemDBs BIT = 1 , + @OnlyQueryHashes VARCHAR(MAX) = NULL , + @IgnoreQueryHashes VARCHAR(MAX) = NULL , + @OnlySqlHandles VARCHAR(MAX) = NULL , + @IgnoreSqlHandles VARCHAR(MAX) = NULL , + @QueryFilter VARCHAR(10) = 'ALL' , + @DatabaseName NVARCHAR(128) = NULL , + @StoredProcName NVARCHAR(128) = NULL, + @SlowlySearchPlansFor NVARCHAR(4000) = NULL, + @Reanalyze BIT = 0 , + @SkipAnalysis BIT = 0 , + @BringThePain BIT = 0 , + @MinimumExecutionCount INT = 0, + @Debug BIT = 0, + @CheckDateOverride DATETIMEOFFSET = NULL, + @MinutesBack INT = NULL, + @Version VARCHAR(30) = NULL OUTPUT, + @VersionDate DATETIME = NULL OUTPUT, + @VersionCheckMode BIT = 0 +WITH RECOMPILE +AS +BEGIN +SET NOCOUNT ON; +SET STATISTICS XML OFF; +SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - EXEC sys.sp_executesql @StringToExecute; +SELECT @Version = '8.19', @VersionDate = '20240222'; +SET @OutputType = UPPER(@OutputType); -RAISERROR('Gathering RTO worst cases', 0, 1) WITH NOWAIT; +IF(@VersionCheckMode = 1) +BEGIN + RETURN; +END; - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; +DECLARE @nl NVARCHAR(2) = NCHAR(13) + NCHAR(10) ; + +IF @Help = 1 + BEGIN + PRINT ' + sp_BlitzCache from http://FirstResponderKit.org + + This script displays your most resource-intensive queries from the plan cache, + and points to ways you can tune these queries to make them faster. - SET @StringToExecute += N' - WITH WorstCases AS ( - SELECT rp.* - FROM #RTORecoveryPoints rp - LEFT OUTER JOIN #RTORecoveryPoints rpNewer - ON rp.database_guid = rpNewer.database_guid - AND rp.database_name = rpNewer.database_name - AND rp.full_last_lsn < rpNewer.full_last_lsn - AND rpNewer.rto_worst_case_size_mb = (SELECT TOP 1 rto_worst_case_size_mb FROM #RTORecoveryPoints s WHERE rp.database_guid = s.database_guid AND rp.database_name = s.database_name ORDER BY rto_worst_case_size_mb DESC) - WHERE rp.rto_worst_case_size_mb = (SELECT TOP 1 rto_worst_case_size_mb FROM #RTORecoveryPoints s WHERE rp.database_guid = s.database_guid AND rp.database_name = s.database_name ORDER BY rto_worst_case_size_mb DESC) - /* OR rp.rto_worst_case_time_seconds = (SELECT TOP 1 rto_worst_case_time_seconds FROM #RTORecoveryPoints s WHERE rp.database_guid = s.database_guid AND rp.database_name = s.database_name ORDER BY rto_worst_case_time_seconds DESC) */ - AND rpNewer.database_guid IS NULL - ) - UPDATE #Backups - SET RTOWorstCaseMinutes = - /* Fulls */ - (CASE WHEN @RestoreSpeedFullMBps IS NULL - THEN wc.full_time_seconds / 60.0 - ELSE @RestoreSpeedFullMBps / wc.full_file_size_mb - END) - /* Diffs, which might not have been taken */ - + (CASE WHEN @RestoreSpeedDiffMBps IS NOT NULL AND wc.diff_file_size_mb IS NOT NULL - THEN @RestoreSpeedDiffMBps / wc.diff_file_size_mb - ELSE COALESCE(wc.diff_time_seconds,0) / 60.0 - END) + To learn more, visit http://FirstResponderKit.org where you can download new + versions for free, watch training videos on how it works, get more info on + the findings, contribute your own code, and more. - /* Logs, which might not have been taken */ - + (CASE WHEN @RestoreSpeedLogMBps IS NOT NULL AND wc.log_file_size_mb IS NOT NULL - THEN @RestoreSpeedLogMBps / wc.log_file_size_mb - ELSE COALESCE(wc.log_time_seconds,0) / 60.0 - END) - , RTOWorstCaseBackupFileSizeMB = wc.rto_worst_case_size_mb - FROM #Backups b - INNER JOIN WorstCases wc - ON b.database_guid = wc.database_guid - AND b.database_name = wc.database_name; - '; + Known limitations of this version: + - SQL Server 2008 and 2008R2 have a bug in trigger stats, so that output is + excluded by default. + - @IgnoreQueryHashes and @OnlyQueryHashes require a CSV list of hashes + with no spaces between the hash values. - IF @Debug = 1 - PRINT @StringToExecute; + Unknown limitations of this version: + - May or may not be vulnerable to the wick effect. - EXEC sys.sp_executesql @StringToExecute, N'@RestoreSpeedFullMBps INT, @RestoreSpeedDiffMBps INT, @RestoreSpeedLogMBps INT', @RestoreSpeedFullMBps, @RestoreSpeedDiffMBps, @RestoreSpeedLogMBps; + Changes - for the full list of improvements and fixes in this version, see: + https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/ -/*Populating Recoverability*/ + MIT License + Copyright (c) Brent Ozar Unlimited - /*Get distinct list of databases*/ - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: - SET @StringToExecute += N' - SELECT DISTINCT b.database_name, database_guid - FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b;' + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. - IF @Debug = 1 - PRINT @StringToExecute; + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + '; - INSERT #Recoverability ( DatabaseName, DatabaseGUID ) - EXEC sys.sp_executesql @StringToExecute; + + SELECT N'@Help' AS [Parameter Name] , + N'BIT' AS [Data Type] , + N'Displays this help message.' AS [Parameter Description] + UNION ALL + SELECT N'@Top', + N'INT', + N'The number of records to retrieve and analyze from the plan cache. The following DMVs are used as the plan cache: dm_exec_query_stats, dm_exec_procedure_stats, dm_exec_trigger_stats.' - /*Find most recent recovery model, backup size, and backup date*/ - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; + UNION ALL + SELECT N'@SortOrder', + N'VARCHAR(10)', + N'Data processing and display order. @SortOrder will still be used, even when preparing output for a table or for excel. Possible values are: "CPU", "Reads", "Writes", "Duration", "Executions", "Recent Compilations", "Memory Grant", "Unused Grant", "Spills", "Query Hash", "Duplicate". Additionally, the word "Average" or "Avg" can be used to sort on averages rather than total. "Executions per minute" and "Executions / minute" can be used to sort by execution per minute. For the truly lazy, "xpm" can also be used. Note that when you use all or all avg, the only parameters you can use are @Top and @DatabaseName. All others will be ignored.' - SET @StringToExecute += N' - UPDATE r - SET r.LastBackupRecoveryModel = ca.recovery_model, - r.LastFullBackupSizeMB = ca.compressed_backup_size, - r.LastFullBackupDate = ca.backup_finish_date - FROM #Recoverability r - CROSS APPLY ( - SELECT TOP 1 b.recovery_model, (b.compressed_backup_size / 1048576.0) AS compressed_backup_size, b.backup_finish_date - FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b - WHERE r.DatabaseName = b.database_name - AND r.DatabaseGUID = b.database_guid - AND b.type = ''D'' - AND b.backup_finish_date > @StartTime - ORDER BY b.backup_finish_date DESC - ) ca;' + UNION ALL + SELECT N'@UseTriggersAnyway', + N'BIT', + N'On SQL Server 2008R2 and earlier, trigger execution count is incorrect - trigger execution count is incremented once per execution of a SQL agent job. If you still want to see relative execution count of triggers, then you can force sp_BlitzCache to include this information.' - IF @Debug = 1 - PRINT @StringToExecute; + UNION ALL + SELECT N'@ExportToExcel', + N'BIT', + N'Prepare output for exporting to Excel. Newlines and additional whitespace are removed from query text and the execution plan is not displayed.' - EXEC sys.sp_executesql @StringToExecute, N'@StartTime DATETIME2', @StartTime; + UNION ALL + SELECT N'@ExpertMode', + N'TINYINT', + N'Default 0. When set to 1, results include more columns. When 2, mode is optimized for Opserver, the open source dashboard.' + UNION ALL + SELECT N'@OutputType', + N'NVARCHAR(258)', + N'If set to "NONE", this will tell this procedure not to run any query leading to a results set thrown to caller.' + + UNION ALL + SELECT N'@OutputDatabaseName', + N'NVARCHAR(128)', + N'The output database. If this does not exist SQL Server will divide by zero and everything will fall apart.' - /*Find first backup size and date*/ - SET @StringToExecute =N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; + UNION ALL + SELECT N'@OutputSchemaName', + N'NVARCHAR(258)', + N'The output schema. If this does not exist SQL Server will divide by zero and everything will fall apart.' - SET @StringToExecute += N' - UPDATE r - SET r.FirstFullBackupSizeMB = ca.compressed_backup_size, - r.FirstFullBackupDate = ca.backup_finish_date - FROM #Recoverability r - CROSS APPLY ( - SELECT TOP 1 (b.compressed_backup_size / 1048576.0) AS compressed_backup_size, b.backup_finish_date - FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b - WHERE r.DatabaseName = b.database_name - AND r.DatabaseGUID = b.database_guid - AND b.type = ''D'' - AND b.backup_finish_date > @StartTime - ORDER BY b.backup_finish_date ASC - ) ca;' + UNION ALL + SELECT N'@OutputTableName', + N'NVARCHAR(258)', + N'The output table. If this does not exist, it will be created for you.' - IF @Debug = 1 - PRINT @StringToExecute; + UNION ALL + SELECT N'@DurationFilter', + N'DECIMAL(38,4)', + N'Excludes queries with an average duration (in seconds) less than @DurationFilter.' - EXEC sys.sp_executesql @StringToExecute, N'@StartTime DATETIME2', @StartTime; + UNION ALL + SELECT N'@HideSummary', + N'BIT', + N'Hides the findings summary result set.' + UNION ALL + SELECT N'@IgnoreSystemDBs', + N'BIT', + N'Ignores plans found in the system databases (master, model, msdb, tempdb, dbmaintenance, dbadmin, dbatools and resourcedb)' - /*Find average backup throughputs for full, diff, and log*/ - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; + UNION ALL + SELECT N'@OnlyQueryHashes', + N'VARCHAR(MAX)', + N'A list of query hashes to query. All other query hashes will be ignored. Stored procedures and triggers will be ignored.' - SET @StringToExecute += N' - UPDATE r - SET r.AvgFullBackupThroughputMB = ca_full.AvgFullSpeed, - r.AvgDiffBackupThroughputMB = ca_diff.AvgDiffSpeed, - r.AvgLogBackupThroughputMB = ca_log.AvgLogSpeed, - r.AvgFullBackupDurationSeconds = AvgFullDuration, - r.AvgDiffBackupDurationSeconds = AvgDiffDuration, - r.AvgLogBackupDurationSeconds = AvgLogDuration - FROM #Recoverability AS r - OUTER APPLY ( - SELECT b.database_name, - AVG( b.compressed_backup_size / ( DATEDIFF(ss, b.backup_start_date, b.backup_finish_date) ) / 1048576.0 ) AS AvgFullSpeed, - AVG( DATEDIFF(ss, b.backup_start_date, b.backup_finish_date) ) AS AvgFullDuration - FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset b - WHERE r.DatabaseName = b.database_name - AND r.DatabaseGUID = b.database_guid - AND b.type = ''D'' - AND DATEDIFF(SECOND, b.backup_start_date, b.backup_finish_date) > 0 - AND b.backup_finish_date > @StartTime - GROUP BY b.database_name - ) ca_full - OUTER APPLY ( - SELECT b.database_name, - AVG( b.compressed_backup_size / ( DATEDIFF(ss, b.backup_start_date, b.backup_finish_date) ) / 1048576.0 ) AS AvgDiffSpeed, - AVG( DATEDIFF(ss, b.backup_start_date, b.backup_finish_date) ) AS AvgDiffDuration - FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset b - WHERE r.DatabaseName = b.database_name - AND r.DatabaseGUID = b.database_guid - AND b.type = ''I'' - AND DATEDIFF(SECOND, b.backup_start_date, b.backup_finish_date) > 0 - AND b.backup_finish_date > @StartTime - GROUP BY b.database_name - ) ca_diff - OUTER APPLY ( - SELECT b.database_name, - AVG( b.compressed_backup_size / ( DATEDIFF(ss, b.backup_start_date, b.backup_finish_date) ) / 1048576.0 ) AS AvgLogSpeed, - AVG( DATEDIFF(ss, b.backup_start_date, b.backup_finish_date) ) AS AvgLogDuration - FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset b - WHERE r.DatabaseName = b.database_name - AND r.DatabaseGUID = b.database_guid - AND b.type = ''L'' - AND DATEDIFF(SECOND, b.backup_start_date, b.backup_finish_date) > 0 - AND b.backup_finish_date > @StartTime - GROUP BY b.database_name - ) ca_log;' + UNION ALL + SELECT N'@IgnoreQueryHashes', + N'VARCHAR(MAX)', + N'A list of query hashes to ignore.' + + UNION ALL + SELECT N'@OnlySqlHandles', + N'VARCHAR(MAX)', + N'One or more sql_handles to use for filtering results.' + + UNION ALL + SELECT N'@IgnoreSqlHandles', + N'VARCHAR(MAX)', + N'One or more sql_handles to ignore.' - IF @Debug = 1 - PRINT @StringToExecute; + UNION ALL + SELECT N'@DatabaseName', + N'NVARCHAR(128)', + N'A database name which is used for filtering results.' - EXEC sys.sp_executesql @StringToExecute, N'@StartTime DATETIME2', @StartTime; + UNION ALL + SELECT N'@StoredProcName', + N'NVARCHAR(128)', + N'Name of stored procedure you want to find plans for.' + UNION ALL + SELECT N'@SlowlySearchPlansFor', + N'NVARCHAR(4000)', + N'String to search for in plan text. % wildcards allowed.' - /*Find max and avg diff and log sizes*/ - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; + UNION ALL + SELECT N'@BringThePain', + N'BIT', + N'When using @SortOrder = ''all'' and @Top > 10, we require you to set @BringThePain = 1 so you understand that sp_BlitzCache will take a while to run.' - SET @StringToExecute += N' - UPDATE r - SET r.AvgFullSizeMB = fulls.avg_full_size, - r.AvgDiffSizeMB = diffs.avg_diff_size, - r.AvgLogSizeMB = logs.avg_log_size - FROM #Recoverability AS r - OUTER APPLY ( - SELECT b.database_name, AVG(b.compressed_backup_size / 1048576.0) AS avg_full_size - FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b - WHERE r.DatabaseName = b.database_name - AND r.DatabaseGUID = b.database_guid - AND b.type = ''D'' - AND b.backup_finish_date > @StartTime - GROUP BY b.database_name - ) AS fulls - OUTER APPLY ( - SELECT b.database_name, AVG(b.compressed_backup_size / 1048576.0) AS avg_diff_size - FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b - WHERE r.DatabaseName = b.database_name - AND r.DatabaseGUID = b.database_guid - AND b.type = ''I'' - AND b.backup_finish_date > @StartTime - GROUP BY b.database_name - ) AS diffs - OUTER APPLY ( - SELECT b.database_name, AVG(b.compressed_backup_size / 1048576.0) AS avg_log_size - FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b - WHERE r.DatabaseName = b.database_name - AND r.DatabaseGUID = b.database_guid - AND b.type = ''L'' - AND b.backup_finish_date > @StartTime - GROUP BY b.database_name - ) AS logs;' + UNION ALL + SELECT N'@QueryFilter', + N'VARCHAR(10)', + N'Filter out stored procedures or statements. The default value is ''ALL''. Allowed values are ''procedures'', ''statements'', ''functions'', or ''all'' (any variation in capitalization is acceptable).' - IF @Debug = 1 - PRINT @StringToExecute; + UNION ALL + SELECT N'@Reanalyze', + N'BIT', + N'The default is 0. When set to 0, sp_BlitzCache will re-evalute the plan cache. Set this to 1 to reanalyze existing results' + + UNION ALL + SELECT N'@MinimumExecutionCount', + N'INT', + N'Queries with fewer than this number of executions will be omitted from results.' + + UNION ALL + SELECT N'@Debug', + N'BIT', + N'Setting this to 1 will print dynamic SQL and select data from all tables used.' - EXEC sys.sp_executesql @StringToExecute, N'@StartTime DATETIME2', @StartTime; - -/*Trending - only works if backupfile is populated, which means in msdb */ -IF @MSDBName = N'msdb' -BEGIN - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' --+ @crlf; + UNION ALL + SELECT N'@MinutesBack', + N'INT', + N'How many minutes back to begin plan cache analysis. If you put in a positive number, we''ll flip it to negative.' - SET @StringToExecute += N' - SELECT p.DatabaseName, - p.DatabaseGUID, - p.[0], - p.[-1], - p.[-2], - p.[-3], - p.[-4], - p.[-5], - p.[-6], - p.[-7], - p.[-8], - p.[-9], - p.[-10], - p.[-11], - p.[-12] - FROM ( SELECT b.database_name AS DatabaseName, - b.database_guid AS DatabaseGUID, - DATEDIFF(MONTH, @StartTime, b.backup_start_date) AS MonthsAgo , - CONVERT(DECIMAL(18, 2), AVG(bf.file_size / 1048576.0)) AS AvgSizeMB - FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b - INNER JOIN ' + QUOTENAME(@MSDBName) + N'.dbo.backupfile AS bf - ON b.backup_set_id = bf.backup_set_id - WHERE b.database_name NOT IN ( ''master'', ''msdb'', ''model'', ''tempdb'' ) - AND bf.file_type = ''D'' - AND b.backup_start_date >= DATEADD(YEAR, -1, @StartTime) - AND b.backup_start_date <= SYSDATETIME() - GROUP BY b.database_name, - b.database_guid, - DATEDIFF(mm, @StartTime, b.backup_start_date) - ) AS bckstat PIVOT ( SUM(bckstat.AvgSizeMB) FOR bckstat.MonthsAgo IN ( [0], [-1], [-2], [-3], [-4], [-5], [-6], [-7], [-8], [-9], [-10], [-11], [-12] ) ) AS p - ORDER BY p.DatabaseName; - ' + UNION ALL + SELECT N'@Version', + N'VARCHAR(30)', + N'OUTPUT parameter holding version number.' + + UNION ALL + SELECT N'@VersionDate', + N'DATETIME', + N'OUTPUT parameter holding version date.' - IF @Debug = 1 - PRINT @StringToExecute; + UNION ALL + SELECT N'@VersionCheckMode', + N'BIT', + N'Setting this to 1 will make the procedure stop after setting @Version and @VersionDate.'; - INSERT #Trending ( DatabaseName, DatabaseGUID, [0], [-1], [-2], [-3], [-4], [-5], [-6], [-7], [-8], [-9], [-10], [-11], [-12] ) - EXEC sys.sp_executesql @StringToExecute, N'@StartTime DATETIME2', @StartTime; -END + /* Column definitions */ + SELECT N'# Executions' AS [Column Name], + N'BIGINT' AS [Data Type], + N'The number of executions of this particular query. This is computed across statements, procedures, and triggers and aggregated by the SQL handle.' AS [Column Description] -/*End Trending*/ + UNION ALL + SELECT N'Executions / Minute', + N'MONEY', + N'Number of executions per minute - calculated for the life of the current plan. Plan life is the last execution time minus the plan creation time.' -/*End populating Recoverability*/ + UNION ALL + SELECT N'Execution Weight', + N'MONEY', + N'An arbitrary metric of total "execution-ness". A weight of 2 is "one more" than a weight of 1.' -RAISERROR('Returning data', 0, 1) WITH NOWAIT; + UNION ALL + SELECT N'Database', + N'sysname', + N'The name of the database where the plan was encountered. If the database name cannot be determined for some reason, a value of NA will be substituted. A value of 32767 indicates the plan comes from ResourceDB.' - SELECT b.* - FROM #Backups AS b - ORDER BY b.database_name; + UNION ALL + SELECT N'Total CPU', + N'BIGINT', + N'Total CPU time, reported in milliseconds, that was consumed by all executions of this query since the last compilation.' - SELECT r.*, - t.[0], t.[-1], t.[-2], t.[-3], t.[-4], t.[-5], t.[-6], t.[-7], t.[-8], t.[-9], t.[-10], t.[-11], t.[-12] - FROM #Recoverability AS r - LEFT JOIN #Trending t - ON r.DatabaseName = t.DatabaseName - AND r.DatabaseGUID = t.DatabaseGUID - WHERE r.LastBackupRecoveryModel IS NOT NULL - ORDER BY r.DatabaseName + UNION ALL + SELECT N'Avg CPU', + N'BIGINT', + N'Average CPU time, reported in milliseconds, consumed by each execution of this query since the last compilation.' + UNION ALL + SELECT N'CPU Weight', + N'MONEY', + N'An arbitrary metric of total "CPU-ness". A weight of 2 is "one more" than a weight of 1.' -RAISERROR('Rules analysis starting', 0, 1) WITH NOWAIT; + UNION ALL + SELECT N'Total Duration', + N'BIGINT', + N'Total elapsed time, reported in milliseconds, consumed by all executions of this query since last compilation.' -/*Looking for out of band backups by finding most common backup operator user_name and noting backups taken by other user_names*/ + UNION ALL + SELECT N'Avg Duration', + N'BIGINT', + N'Average elapsed time, reported in milliseconds, consumed by each execution of this query since the last compilation.' - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; + UNION ALL + SELECT N'Duration Weight', + N'MONEY', + N'An arbitrary metric of total "Duration-ness". A weight of 2 is "one more" than a weight of 1.' - SET @StringToExecute += N' - WITH common_people AS ( - SELECT TOP 1 b.user_name, COUNT_BIG(*) AS Records - FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b - GROUP BY b.user_name - ORDER BY Records DESC - ) - SELECT - 1 AS CheckId, - 100 AS [Priority], - b.database_name AS [Database Name], - ''Non-Agent backups taken'' AS [Finding], - ''The database '' + QUOTENAME(b.database_name) + '' has been backed up by '' + QUOTENAME(b.user_name) + '' '' + CONVERT(VARCHAR(10), COUNT(*)) + '' times.'' AS [Warning] - FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b - WHERE b.user_name NOT LIKE ''%Agent%'' AND b.user_name NOT LIKE ''%AGENT%'' - AND NOT EXISTS ( - SELECT 1 - FROM common_people AS cp - WHERE cp.user_name = b.user_name - ) - GROUP BY b.database_name, b.user_name - HAVING COUNT(*) > 1;' + @crlf; + UNION ALL + SELECT N'Total Reads', + N'BIGINT', + N'Total logical reads performed by this query since last compilation.' - IF @Debug = 1 - PRINT @StringToExecute; + UNION ALL + SELECT N'Average Reads', + N'BIGINT', + N'Average logical reads performed by each execution of this query since the last compilation.' - INSERT #Warnings (CheckId, Priority, DatabaseName, Finding, Warning ) - EXEC sys.sp_executesql @StringToExecute; - - /*Looking for compatibility level changing. Only looking for databases that have changed more than twice (It''s possible someone may have changed up, had CE problems, and then changed back)*/ + UNION ALL + SELECT N'Read Weight', + N'MONEY', + N'An arbitrary metric of "Read-ness". A weight of 2 is "one more" than a weight of 1.' - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; + UNION ALL + SELECT N'Total Writes', + N'BIGINT', + N'Total logical writes performed by this query since last compilation.' - SET @StringToExecute += N'SELECT - 2 AS CheckId, - 100 AS [Priority], - b.database_name AS [Database Name], - ''Compatibility level changing'' AS [Finding], - ''The database '' + QUOTENAME(b.database_name) + '' has changed compatibility levels '' + CONVERT(VARCHAR(10), COUNT(DISTINCT b.compatibility_level)) + '' times.'' AS [Warning] - FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b - GROUP BY b.database_name - HAVING COUNT(DISTINCT b.compatibility_level) > 2;' + @crlf; + UNION ALL + SELECT N'Average Writes', + N'BIGINT', + N'Average logical writes performed by each execution this query since last compilation.' - IF @Debug = 1 - PRINT @StringToExecute; + UNION ALL + SELECT N'Write Weight', + N'MONEY', + N'An arbitrary metric of "Write-ness". A weight of 2 is "one more" than a weight of 1.' - INSERT #Warnings ( CheckId, Priority, DatabaseName, Finding, Warning ) - EXEC sys.sp_executesql @StringToExecute; - - /*Looking for password protected backups. This hasn''t been a popular option ever, and was largely replaced by encrypted backups, but it''s simple to check for.*/ + UNION ALL + SELECT N'Query Type', + N'NVARCHAR(258)', + N'The type of query being examined. This can be "Procedure", "Statement", or "Trigger".' - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; + UNION ALL + SELECT N'Query Text', + N'NVARCHAR(4000)', + N'The text of the query. This may be truncated by either SQL Server or by sp_BlitzCache(tm) for display purposes.' - SET @StringToExecute += N'SELECT - 3 AS CheckId, - 100 AS [Priority], - b.database_name AS [Database Name], - ''Password backups'' AS [Finding], - ''The database '' + QUOTENAME(b.database_name) + '' has been backed up with a password '' + CONVERT(VARCHAR(10), COUNT(*)) + '' times. Who has the password?'' AS [Warning] - FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b - WHERE b.is_password_protected = 1 - GROUP BY b.database_name;' + @crlf; + UNION ALL + SELECT N'% Executions (Type)', + N'MONEY', + N'Percent of executions relative to the type of query - e.g. 17.2% of all stored procedure executions.' - IF @Debug = 1 - PRINT @StringToExecute; + UNION ALL + SELECT N'% CPU (Type)', + N'MONEY', + N'Percent of CPU time consumed by this query for a given type of query - e.g. 22% of CPU of all stored procedures executed.' - INSERT #Warnings ( CheckId, Priority, DatabaseName, Finding, Warning ) - EXEC sys.sp_executesql @StringToExecute; - - /*Looking for snapshot backups. There are legit reasons for these, but we should flag them so the questions get asked. What questions? Good question.*/ + UNION ALL + SELECT N'% Duration (Type)', + N'MONEY', + N'Percent of elapsed time consumed by this query for a given type of query - e.g. 12% of all statements executed.' - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; + UNION ALL + SELECT N'% Reads (Type)', + N'MONEY', + N'Percent of reads consumed by this query for a given type of query - e.g. 34.2% of all stored procedures executed.' - SET @StringToExecute += N'SELECT - 4 AS CheckId, - 100 AS [Priority], - b.database_name AS [Database Name], - ''Snapshot backups'' AS [Finding], - ''The database '' + QUOTENAME(b.database_name) + '' has had '' + CONVERT(VARCHAR(10), COUNT(*)) + '' snapshot backups. This message is purely informational.'' AS [Warning] - FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b - WHERE b.is_snapshot = 1 - GROUP BY b.database_name;' + @crlf; + UNION ALL + SELECT N'% Writes (Type)', + N'MONEY', + N'Percent of writes performed by this query for a given type of query - e.g. 43.2% of all statements executed.' - IF @Debug = 1 - PRINT @StringToExecute; + UNION ALL + SELECT N'Total Rows', + N'BIGINT', + N'Total number of rows returned for all executions of this query. This only applies to query level stats, not stored procedures or triggers.' - INSERT #Warnings ( CheckId, Priority, DatabaseName, Finding, Warning ) - EXEC sys.sp_executesql @StringToExecute; - - /*It''s fine to take backups of read only databases, but it''s not always necessary (there''s no new data, after all).*/ + UNION ALL + SELECT N'Average Rows', + N'MONEY', + N'Average number of rows returned by each execution of the query.' - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; + UNION ALL + SELECT N'Min Rows', + N'BIGINT', + N'The minimum number of rows returned by any execution of this query.' - SET @StringToExecute += N'SELECT - 5 AS CheckId, - 100 AS [Priority], - b.database_name AS [Database Name], - ''Read only state backups'' AS [Finding], - ''The database '' + QUOTENAME(b.database_name) + '' has been backed up '' + CONVERT(VARCHAR(10), COUNT(*)) + '' times while in a read-only state. This can be normal if it''''s a secondary, but a bit odd otherwise.'' AS [Warning] - FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b - WHERE b.is_readonly = 1 - GROUP BY b.database_name;' + @crlf; + UNION ALL + SELECT N'Max Rows', + N'BIGINT', + N'The maximum number of rows returned by any execution of this query.' - IF @Debug = 1 - PRINT @StringToExecute; + UNION ALL + SELECT N'MinGrantKB', + N'BIGINT', + N'The minimum memory grant the query received in kb.' - INSERT #Warnings ( CheckId, Priority, DatabaseName, Finding, Warning ) - EXEC sys.sp_executesql @StringToExecute; - - /*So, I''ve come across people who think they need to change their database to single user mode to take a backup. Or that doing that will help something. I just need to know, here.*/ + UNION ALL + SELECT N'MaxGrantKB', + N'BIGINT', + N'The maximum memory grant the query received in kb.' - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; + UNION ALL + SELECT N'MinUsedGrantKB', + N'BIGINT', + N'The minimum used memory grant the query received in kb.' - SET @StringToExecute += N'SELECT - 6 AS CheckId, - 100 AS [Priority], - b.database_name AS [Database Name], - ''Single user mode backups'' AS [Finding], - ''The database '' + QUOTENAME(b.database_name) + '' has been backed up '' + CONVERT(VARCHAR(10), COUNT(*)) + '' times while in single-user mode. This is really weird! Make sure your backup process doesn''''t include a mode change anywhere.'' AS [Warning] - FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b - WHERE b.is_single_user = 1 - GROUP BY b.database_name;' + @crlf; + UNION ALL + SELECT N'MaxUsedGrantKB', + N'BIGINT', + N'The maximum used memory grant the query received in kb.' - IF @Debug = 1 - PRINT @StringToExecute; + UNION ALL + SELECT N'MinSpills', + N'BIGINT', + N'The minimum amount this query has spilled to tempdb in 8k pages.' - INSERT #Warnings ( CheckId, Priority, DatabaseName, Finding, Warning ) - EXEC sys.sp_executesql @StringToExecute; - - /*C''mon, it''s 2017. Take your backups with CHECKSUMS, people.*/ + UNION ALL + SELECT N'MaxSpills', + N'BIGINT', + N'The maximum amount this query has spilled to tempdb in 8k pages.' - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; + UNION ALL + SELECT N'TotalSpills', + N'BIGINT', + N'The total amount this query has spilled to tempdb in 8k pages.' - SET @StringToExecute += N'SELECT - 7 AS CheckId, - 100 AS [Priority], - b.database_name AS [Database Name], - ''No CHECKSUMS'' AS [Finding], - ''The database '' + QUOTENAME(b.database_name) + '' has been backed up '' + CONVERT(VARCHAR(10), COUNT(*)) + '' times without CHECKSUMS in the past 30 days. CHECKSUMS can help alert you to corruption errors.'' AS [Warning] - FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b - WHERE b.has_backup_checksums = 0 - AND b.backup_finish_date >= DATEADD(DAY, -30, SYSDATETIME()) - GROUP BY b.database_name;' + @crlf; + UNION ALL + SELECT N'AvgSpills', + N'BIGINT', + N'The average amount this query has spilled to tempdb in 8k pages.' - IF @Debug = 1 - PRINT @StringToExecute; + UNION ALL + SELECT N'PercentMemoryGrantUsed', + N'MONEY', + N'Result of dividing the maximum grant used by the minimum granted.' - INSERT #Warnings ( CheckId, Priority, DatabaseName, Finding, Warning ) - EXEC sys.sp_executesql @StringToExecute; - - /*Damaged is a Black Flag album. You don''t want your backups to be like a Black Flag album. */ + UNION ALL + SELECT N'AvgMaxMemoryGrant', + N'MONEY', + N'The average maximum memory grant for a query.' - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; + UNION ALL + SELECT N'# Plans', + N'INT', + N'The total number of execution plans found that match a given query.' - SET @StringToExecute += N'SELECT - 8 AS CheckId, - 100 AS [Priority], - b.database_name AS [Database Name], - ''Damaged backups'' AS [Finding], - ''The database '' + QUOTENAME(b.database_name) + '' has had '' + CONVERT(VARCHAR(10), COUNT(*)) + '' damaged backups taken without stopping to throw an error. This is done by specifying CONTINUE_AFTER_ERROR in your BACKUP commands.'' AS [Warning] - FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b - WHERE b.is_damaged = 1 - GROUP BY b.database_name;' + @crlf; + UNION ALL + SELECT N'# Distinct Plans', + N'INT', + N'The number of distinct execution plans that match a given query. ' + + NCHAR(13) + NCHAR(10) + + N'This may be caused by running the same query across multiple databases or because of a lack of proper parameterization in the database.' - IF @Debug = 1 - PRINT @StringToExecute; + UNION ALL + SELECT N'Created At', + N'DATETIME', + N'Time that the execution plan was last compiled.' - INSERT #Warnings ( CheckId, Priority, DatabaseName, Finding, Warning ) - EXEC sys.sp_executesql @StringToExecute; - - /*Checking for encrypted backups and the last backup of the encryption key.*/ + UNION ALL + SELECT N'Last Execution', + N'DATETIME', + N'The last time that this query was executed.' - /*2014 ONLY*/ + UNION ALL + SELECT N'Query Plan', + N'XML', + N'The query plan. Click to display a graphical plan or, if you need to patch SSMS, a pile of XML.' -IF @ProductVersionMajor >= 12 - BEGIN + UNION ALL + SELECT N'Plan Handle', + N'VARBINARY(64)', + N'An arbitrary identifier referring to the compiled plan this query is a part of.' - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; + UNION ALL + SELECT N'SQL Handle', + N'VARBINARY(64)', + N'An arbitrary identifier referring to a batch or stored procedure that this query is a part of.' - SET @StringToExecute += N'SELECT - 9 AS CheckId, - 100 AS [Priority], - b.database_name AS [Database Name], - ''Encrypted backups'' AS [Finding], - ''The database '' + QUOTENAME(b.database_name) + '' has had '' + CONVERT(VARCHAR(10), COUNT(*)) + '' '' + b.encryptor_type + '' backups, and the last time a certificate was backed up is ' - + CASE WHEN LOWER(@MSDBName) <> N'msdb' - THEN + N'...well, that information is on another server, anyway.'' AS [Warning]' - ELSE + CONVERT(VARCHAR(30), (SELECT MAX(c.pvt_key_last_backup_date) FROM sys.certificates AS c WHERE c.name NOT LIKE '##%')) + N'.'' AS [Warning]' - END + - N' - FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b - WHERE b.encryptor_type IS NOT NULL - GROUP BY b.database_name, b.encryptor_type;' + @crlf; + UNION ALL + SELECT N'Query Hash', + N'BINARY(8)', + N'A hash of the query. Queries with the same query hash have similar logic but only differ by literal values or database.' - IF @Debug = 1 - PRINT @StringToExecute; + UNION ALL + SELECT N'Warnings', + N'VARCHAR(MAX)', + N'A list of individual warnings generated by this query.' ; - INSERT #Warnings ( CheckId, Priority, DatabaseName, Finding, Warning ) - EXEC sys.sp_executesql @StringToExecute; - - END - - /*Looking for backups that have BULK LOGGED data in them -- this can screw up point in time LOG recovery.*/ - SET @StringToExecute =N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; + + /* Configuration table description */ + SELECT N'Frequent Execution Threshold' AS [Configuration Parameter] , + N'100' AS [Default Value] , + N'Executions / Minute' AS [Unit of Measure] , + N'Executions / Minute before a "Frequent Execution Threshold" warning is triggered.' AS [Description] - SET @StringToExecute += N'SELECT - 10 AS CheckId, - 100 AS [Priority], - b.database_name AS [Database Name], - ''Bulk logged backups'' AS [Finding], - ''The database '' + QUOTENAME(b.database_name) + '' has had '' + CONVERT(VARCHAR(10), COUNT(*)) + '' backups with bulk logged data. This can make point in time recovery awkward. '' AS [Warning] - FROM ' + QUOTENAME(@MSDBName) + '.dbo.backupset AS b - WHERE b.has_bulk_logged_data = 1 - GROUP BY b.database_name;' + @crlf; + UNION ALL + SELECT N'Parameter Sniffing Variance Percent' , + N'30' , + N'Percent' , + N'Variance required between min/max values and average values before a "Parameter Sniffing" warning is triggered. Applies to worker time and returned rows.' - IF @Debug = 1 - PRINT @StringToExecute; + UNION ALL + SELECT N'Parameter Sniffing IO Threshold' , + N'100,000' , + N'Logical reads' , + N'Minimum number of average logical reads before parameter sniffing checks are evaluated.' - INSERT #Warnings ( CheckId, Priority, DatabaseName, Finding, Warning ) - EXEC sys.sp_executesql @StringToExecute; - - /*Looking for recovery model being switched between FULL and SIMPLE, because it''s a bad practice.*/ + UNION ALL + SELECT N'Cost Threshold for Parallelism Warning' AS [Configuration Parameter] , + N'10' , + N'Percent' , + N'Trigger a "Nearly Parallel" warning when a query''s cost is within X percent of the cost threshold for parallelism.' - SET @StringToExecute =N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; + UNION ALL + SELECT N'Long Running Query Warning' AS [Configuration Parameter] , + N'300' , + N'Seconds' , + N'Triggers a "Long Running Query Warning" when average duration, max CPU time, or max clock time is higher than this number.' - SET @StringToExecute += N'SELECT - 11 AS CheckId, - 100 AS [Priority], - b.database_name AS [Database Name], - ''Recovery model switched'' AS [Finding], - ''The database '' + QUOTENAME(b.database_name) + '' has changed recovery models from between FULL and SIMPLE '' + CONVERT(VARCHAR(10), COUNT(DISTINCT b.recovery_model)) + '' times. This breaks the log chain and is generally a bad idea.'' AS [Warning] - FROM ' + QUOTENAME(@MSDBName) + '.dbo.backupset AS b - WHERE b.recovery_model <> ''BULK-LOGGED'' - GROUP BY b.database_name - HAVING COUNT(DISTINCT b.recovery_model) > 4;' + @crlf; + UNION ALL + SELECT N'Unused Memory Grant Warning' AS [Configuration Parameter] , + N'10' , + N'Percent' , + N'Triggers an "Unused Memory Grant Warning" when a query uses >= X percent of its memory grant.'; + RETURN; + END; /* IF @Help = 1 */ - IF @Debug = 1 - PRINT @StringToExecute; - INSERT #Warnings ( CheckId, Priority, DatabaseName, Finding, Warning ) - EXEC sys.sp_executesql @StringToExecute; - /*Looking for uncompressed backups.*/ +/*Validate version*/ +IF ( +SELECT + CASE + WHEN CONVERT(NVARCHAR(128), SERVERPROPERTY ('PRODUCTVERSION')) LIKE '8%' THEN 0 + WHEN CONVERT(NVARCHAR(128), SERVERPROPERTY ('PRODUCTVERSION')) LIKE '9%' THEN 0 + ELSE 1 + END +) = 0 +BEGIN + DECLARE @version_msg VARCHAR(8000); + SELECT @version_msg = 'Sorry, sp_BlitzCache doesn''t work on versions of SQL prior to 2008.' + REPLICATE(CHAR(13), 7933); + PRINT @version_msg; + RETURN; +END; - SET @StringToExecute =N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; +IF(@OutputType = 'NONE' AND (@OutputTableName IS NULL OR @OutputSchemaName IS NULL OR @OutputDatabaseName IS NULL)) +BEGIN + RAISERROR('This procedure should be called with a value for all @Output* parameters, as @OutputType is set to NONE',12,1); + RETURN; +END; - SET @StringToExecute += N'SELECT - 12 AS CheckId, - 100 AS [Priority], - b.database_name AS [Database Name], - ''Uncompressed backups'' AS [Finding], - ''The database '' + QUOTENAME(b.database_name) + '' has had '' + CONVERT(VARCHAR(10), COUNT(*)) + '' uncompressed backups in the last 30 days. This is a free way to save time and space. And SPACETIME. If your version of SQL supports it.'' AS [Warning] - FROM ' + QUOTENAME(@MSDBName) + '.dbo.backupset AS b - WHERE backup_size = compressed_backup_size AND type = ''D'' - AND b.backup_finish_date >= DATEADD(DAY, -30, SYSDATETIME()) - GROUP BY b.database_name;' + @crlf; +IF(@OutputType = 'NONE') +BEGIN + SET @HideSummary = 1; +END; - IF @Debug = 1 - PRINT @StringToExecute; - INSERT #Warnings ( CheckId, Priority, DatabaseName, Finding, Warning ) - EXEC sys.sp_executesql @StringToExecute; +/* Lets get @SortOrder set to lower case here for comparisons later */ +SET @SortOrder = LOWER(@SortOrder); -RAISERROR('Rules analysis starting on temp tables', 0, 1) WITH NOWAIT; +/* Set @Top based on sort */ +IF ( + @Top IS NULL + AND @SortOrder IN ( 'all', 'all sort' ) + ) + BEGIN + SET @Top = 5; + END; - INSERT #Warnings ( CheckId, Priority, DatabaseName, Finding, Warning ) - SELECT - 13 AS CheckId, - 100 AS Priority, - r.DatabaseName as [DatabaseName], - 'Big Diffs' AS [Finding], - 'On average, Differential backups for this database are >=40% of the size of the average Full backup.' AS [Warning] - FROM #Recoverability AS r - WHERE r.IsBigDiff = 1 +IF ( + @Top IS NULL + AND @SortOrder NOT IN ( 'all', 'all sort' ) + ) + BEGIN + SET @Top = 10; + END; - INSERT #Warnings ( CheckId, Priority, DatabaseName, Finding, Warning ) - SELECT - 13 AS CheckId, - 100 AS Priority, - r.DatabaseName as [DatabaseName], - 'Big Logs' AS [Finding], - 'On average, Log backups for this database are >=20% of the size of the average Full backup.' AS [Warning] - FROM #Recoverability AS r - WHERE r.IsBigLog = 1 +/* If they want to sort by query hash, populate the @OnlyQueryHashes list for them */ +IF @SortOrder LIKE 'query hash%' + BEGIN + RAISERROR('Beginning query hash sort', 0, 1) WITH NOWAIT; + SELECT TOP(@Top) qs.query_hash, + MAX(qs.max_worker_time) AS max_worker_time, + COUNT_BIG(*) AS records + INTO #query_hash_grouped + FROM sys.dm_exec_query_stats AS qs + CROSS APPLY ( SELECT pa.value + FROM sys.dm_exec_plan_attributes(qs.plan_handle) AS pa + WHERE pa.attribute = 'dbid' ) AS ca + GROUP BY qs.query_hash, ca.value + HAVING COUNT_BIG(*) > 1 + ORDER BY max_worker_time DESC, + records DESC; + + SELECT TOP (1) + @OnlyQueryHashes = STUFF((SELECT DISTINCT N',' + CONVERT(NVARCHAR(MAX), qhg.query_hash, 1) + FROM #query_hash_grouped AS qhg + WHERE qhg.query_hash <> 0x00 + FOR XML PATH(N''), TYPE).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 1, N'') + OPTION(RECOMPILE); -/*Insert thank you stuff last*/ - INSERT #Warnings ( CheckId, Priority, DatabaseName, Finding, Warning ) + /* When they ran it, @SortOrder probably looked like 'query hash, cpu', so strip the first sort order out: */ + SELECT @SortOrder = LTRIM(REPLACE(REPLACE(@SortOrder,'query hash', ''), ',', '')); + + /* If they just called it with @SortOrder = 'query hash', set it to 'cpu' for backwards compatibility: */ + IF @SortOrder = '' SET @SortOrder = 'cpu'; - SELECT - 2147483647 AS [CheckId], - 2147483647 AS [Priority], - 'From Your Community Volunteers' AS [DatabaseName], - 'sp_BlitzBackups Version: ' + @Version + ', Version Date: ' + CONVERT(VARCHAR(30), @VersionDate) + '.' AS [Finding], - 'Thanks for using our stored procedure. We hope you find it useful! Check out our other free SQL Server scripts at firstresponderkit.org!' AS [Warning]; + END -RAISERROR('Rules analysis finished', 0, 1) WITH NOWAIT; -SELECT w.CheckId, w.Priority, w.DatabaseName, w.Finding, w.Warning -FROM #Warnings AS w -ORDER BY w.Priority, w.CheckId; +/* If they want to sort by duplicate, create a table with the worst offenders - issue #3345 */ +IF @SortOrder LIKE 'duplicate%' + BEGIN + RAISERROR('Beginning duplicate query hash sort', 0, 1) WITH NOWAIT; -DROP TABLE #Backups, #Warnings, #Recoverability, #RTORecoveryPoints + /* Find the query hashes that are the most duplicated */ + WITH MostCommonQueries AS ( + SELECT TOP(@Top) qs.query_hash, + COUNT_BIG(*) AS plans + FROM sys.dm_exec_query_stats AS qs + GROUP BY qs.query_hash + HAVING COUNT_BIG(*) > 100 + ORDER BY COUNT_BIG(*) DESC + ) + SELECT mcq_recent.sql_handle, mcq_recent.plan_handle, mcq_recent.creation_time AS duplicate_creation_time, mcq.plans + INTO #duplicate_query_filter + FROM MostCommonQueries mcq + CROSS APPLY ( SELECT TOP 1 qs.sql_handle, qs.plan_handle, qs.creation_time + FROM sys.dm_exec_query_stats qs + WHERE qs.query_hash = mcq.query_hash + ORDER BY qs.creation_time DESC) AS mcq_recent + OPTION (RECOMPILE); + SET @MinimumExecutionCount = 0; + END -RETURN; -PushBackupHistoryToListener: +/* validate user inputs */ +IF @Top IS NULL + OR @SortOrder IS NULL + OR @QueryFilter IS NULL + OR @Reanalyze IS NULL +BEGIN + RAISERROR(N'Several parameters (@Top, @SortOrder, @QueryFilter, @renalyze) are required. Do not set them to NULL. Please try again.', 16, 1) WITH NOWAIT; + RETURN; +END; -RAISERROR('Pushing backup history to listener', 0, 1) WITH NOWAIT; +RAISERROR(N'Checking @MinutesBack validity.', 0, 1) WITH NOWAIT; +IF @MinutesBack IS NOT NULL + BEGIN + IF @MinutesBack > 0 + BEGIN + RAISERROR(N'Setting @MinutesBack to a negative number', 0, 1) WITH NOWAIT; + SET @MinutesBack *=-1; + END; + IF @MinutesBack = 0 + BEGIN + RAISERROR(N'@MinutesBack can''t be 0, setting to -1', 0, 1) WITH NOWAIT; + SET @MinutesBack = -1; + END; + END; -DECLARE @msg NVARCHAR(4000) = N''; -DECLARE @RemoteCheck TABLE (c INT NULL); -IF @WriteBackupsToDatabaseName IS NULL - BEGIN - RAISERROR('@WriteBackupsToDatabaseName can''t be NULL.', 16, 1) WITH NOWAIT - RETURN; - END +DECLARE @DurationFilter_i INT, + @MinMemoryPerQuery INT, + @msg NVARCHAR(4000), + @NoobSaibot BIT = 0, + @VersionShowsAirQuoteActualPlans BIT, + @ObjectFullName NVARCHAR(2000), + @user_perm_sql NVARCHAR(MAX) = N'', + @user_perm_gb_out DECIMAL(10,2), + @common_version DECIMAL(10,2), + @buffer_pool_memory_gb DECIMAL(10,2), + @user_perm_percent DECIMAL(10,2), + @is_tokenstore_big BIT = 0, + @sort NVARCHAR(MAX) = N'', + @sort_filter NVARCHAR(MAX) = N''; -IF LOWER(@WriteBackupsToDatabaseName) = N'msdb' - BEGIN - RAISERROR('We can''t write to the real msdb, we have to write to a fake msdb.', 16, 1) WITH NOWAIT - RETURN; - END -IF @WriteBackupsToListenerName IS NULL +IF @SortOrder = 'sp_BlitzIndex' BEGIN - IF @AGName IS NULL - BEGIN - RAISERROR('@WriteBackupsToListenerName and @AGName can''t both be NULL.', 16, 1) WITH NOWAIT; - RETURN; - END - ELSE - BEGIN - SELECT @WriteBackupsToListenerName = dns_name - FROM sys.availability_groups AS ag - JOIN sys.availability_group_listeners AS agl - ON ag.group_id = agl.group_id - WHERE name = @AGName; - END + RAISERROR(N'OUTSTANDING!', 0, 1) WITH NOWAIT; + SET @SortOrder = 'reads'; + SET @NoobSaibot = 1; END -IF @WriteBackupsToListenerName IS NOT NULL -BEGIN - IF NOT EXISTS - ( - SELECT * - FROM sys.servers s - WHERE name = @WriteBackupsToListenerName - ) - BEGIN - SET @msg = N'We need a linked server to write data across. Please set one up for ' + @WriteBackupsToListenerName + N'.'; - RAISERROR(@msg, 16, 1) WITH NOWAIT; - RETURN; - END -END - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; +/* Change duration from seconds to milliseconds */ +IF @DurationFilter IS NOT NULL + BEGIN + RAISERROR(N'Converting Duration Filter to milliseconds', 0, 1) WITH NOWAIT; + SET @DurationFilter_i = CAST((@DurationFilter * 1000.0) AS INT); + END; - SET @StringToExecute += N'SELECT TOP 1 1 FROM ' - + QUOTENAME(@WriteBackupsToListenerName) + N'.master.sys.databases d WHERE d.name = @i_WriteBackupsToDatabaseName;' +RAISERROR(N'Checking database validity', 0, 1) WITH NOWAIT; +SET @DatabaseName = LTRIM(RTRIM(@DatabaseName)) ; - IF @Debug = 1 - PRINT @StringToExecute; +IF SERVERPROPERTY('EngineEdition') IN (5, 6) AND DB_NAME() <> @DatabaseName +BEGIN + RAISERROR('You specified a database name other than the current database, but Azure SQL DB does not allow you to change databases. Execute sp_BlitzCache from the database you want to analyze.', 16, 1); + RETURN; +END; +IF (DB_ID(@DatabaseName)) IS NULL AND @DatabaseName <> N'' +BEGIN + RAISERROR('The database you specified does not exist. Please check the name and try again.', 16, 1); + RETURN; +END; +IF (SELECT DATABASEPROPERTYEX(ISNULL(@DatabaseName, 'master'), 'Collation')) IS NULL AND SERVERPROPERTY('EngineEdition') NOT IN (5, 6, 8) +BEGIN + RAISERROR('The database you specified is not readable. Please check the name and try again. Better yet, check your server.', 16, 1); + RETURN; +END; - INSERT @RemoteCheck (c) - EXEC sp_executesql @StringToExecute, N'@i_WriteBackupsToDatabaseName NVARCHAR(256)', @i_WriteBackupsToDatabaseName = @WriteBackupsToDatabaseName; +SELECT @MinMemoryPerQuery = CONVERT(INT, c.value) FROM sys.configurations AS c WHERE c.name = 'min memory per query (KB)'; - IF @@ROWCOUNT = 0 - BEGIN - SET @msg = N'The database ' + @WriteBackupsToDatabaseName + N' doesn''t appear to exist on that server.' - RAISERROR(@msg, 16, 1) WITH NOWAIT - RETURN; - END +SET @SortOrder = REPLACE(REPLACE(@SortOrder, 'average', 'avg'), '.', ''); +SET @SortOrder = CASE + WHEN @SortOrder IN ('executions per minute','execution per minute','executions / minute','execution / minute','xpm') THEN 'avg executions' + WHEN @SortOrder IN ('recent compilations','recent compilation','compile') THEN 'compiles' + WHEN @SortOrder IN ('read') THEN 'reads' + WHEN @SortOrder IN ('avg read') THEN 'avg reads' + WHEN @SortOrder IN ('write') THEN 'writes' + WHEN @SortOrder IN ('avg write') THEN 'avg writes' + WHEN @SortOrder IN ('memory grants') THEN 'memory grant' + WHEN @SortOrder IN ('avg memory grants') THEN 'avg memory grant' + WHEN @SortOrder IN ('unused grants','unused memory', 'unused memory grant', 'unused memory grants') THEN 'unused grant' + WHEN @SortOrder IN ('spill') THEN 'spills' + WHEN @SortOrder IN ('avg spill') THEN 'avg spills' + WHEN @SortOrder IN ('execution') THEN 'executions' + WHEN @SortOrder IN ('duplicates') THEN 'duplicate' + ELSE @SortOrder END + +RAISERROR(N'Checking sort order', 0, 1) WITH NOWAIT; +IF @SortOrder NOT IN ('cpu', 'avg cpu', 'reads', 'avg reads', 'writes', 'avg writes', + 'duration', 'avg duration', 'executions', 'avg executions', + 'compiles', 'memory grant', 'avg memory grant', 'unused grant', + 'spills', 'avg spills', 'all', 'all avg', 'sp_BlitzIndex', + 'query hash', 'duplicate') + BEGIN + RAISERROR(N'Invalid sort order chosen, reverting to cpu', 16, 1) WITH NOWAIT; + SET @SortOrder = 'cpu'; + END; - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; +SET @QueryFilter = LOWER(@QueryFilter); - SET @StringToExecute += N'SELECT TOP 1 1 FROM ' - + QUOTENAME(@WriteBackupsToListenerName) + '.' + QUOTENAME(@WriteBackupsToDatabaseName) + '.sys.tables WHERE name = ''backupset'' AND SCHEMA_NAME(schema_id) = ''dbo''; - ' + @crlf; +IF LEFT(@QueryFilter, 3) NOT IN ('all', 'sta', 'pro', 'fun') + BEGIN + RAISERROR(N'Invalid query filter chosen. Reverting to all.', 0, 1) WITH NOWAIT; + SET @QueryFilter = 'all'; + END; - IF @Debug = 1 - PRINT @StringToExecute; +IF @SkipAnalysis = 1 + BEGIN + RAISERROR(N'Skip Analysis set to 1, hiding Summary', 0, 1) WITH NOWAIT; + SET @HideSummary = 1; + END; - INSERT @RemoteCheck (c) - EXEC sp_executesql @StringToExecute; +DECLARE @AllSortSql NVARCHAR(MAX) = N''; +DECLARE @VersionShowsMemoryGrants BIT; +IF EXISTS(SELECT * FROM sys.all_columns WHERE object_id = OBJECT_ID('sys.dm_exec_query_stats') AND name = 'max_grant_kb') + SET @VersionShowsMemoryGrants = 1; +ELSE + SET @VersionShowsMemoryGrants = 0; - IF @@ROWCOUNT = 0 - BEGIN +DECLARE @VersionShowsSpills BIT; +IF EXISTS(SELECT * FROM sys.all_columns WHERE object_id = OBJECT_ID('sys.dm_exec_query_stats') AND name = 'max_spills') + SET @VersionShowsSpills = 1; +ELSE + SET @VersionShowsSpills = 0; - SET @msg = N'The database ' + @WriteBackupsToDatabaseName + N' doesn''t appear to have a table called dbo.backupset in it.' - RAISERROR(@msg, 0, 1) WITH NOWAIT - RAISERROR('Don''t worry, we''ll create it for you!', 0, 1) WITH NOWAIT - - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; +IF EXISTS(SELECT * FROM sys.all_columns WHERE object_id = OBJECT_ID('sys.dm_exec_query_plan_stats') AND name = 'query_plan') + SET @VersionShowsAirQuoteActualPlans = 1; +ELSE + SET @VersionShowsAirQuoteActualPlans = 0; - SET @StringToExecute += N'CREATE TABLE ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.dbo.backupset - ( backup_set_id INT IDENTITY(1, 1), backup_set_uuid UNIQUEIDENTIFIER, media_set_id INT, first_family_number TINYINT, first_media_number SMALLINT, - last_family_number TINYINT, last_media_number SMALLINT, catalog_family_number TINYINT, catalog_media_number SMALLINT, position INT, expiration_date DATETIME, - software_vendor_id INT, name NVARCHAR(128), description NVARCHAR(255), user_name NVARCHAR(128), software_major_version TINYINT, software_minor_version TINYINT, - software_build_version SMALLINT, time_zone SMALLINT, mtf_minor_version TINYINT, first_lsn NUMERIC(25, 0), last_lsn NUMERIC(25, 0), checkpoint_lsn NUMERIC(25, 0), - database_backup_lsn NUMERIC(25, 0), database_creation_date DATETIME, backup_start_date DATETIME, backup_finish_date DATETIME, type CHAR(1), sort_order SMALLINT, - code_page SMALLINT, compatibility_level TINYINT, database_version INT, backup_size NUMERIC(20, 0), database_name NVARCHAR(128), server_name NVARCHAR(128), - machine_name NVARCHAR(128), flags INT, unicode_locale INT, unicode_compare_style INT, collation_name NVARCHAR(128), is_password_protected BIT, recovery_model NVARCHAR(60), - has_bulk_logged_data BIT, is_snapshot BIT, is_readonly BIT, is_single_user BIT, has_backup_checksums BIT, is_damaged BIT, begins_log_chain BIT, has_incomplete_metadata BIT, - is_force_offline BIT, is_copy_only BIT, first_recovery_fork_guid UNIQUEIDENTIFIER, last_recovery_fork_guid UNIQUEIDENTIFIER, fork_point_lsn NUMERIC(25, 0), database_guid UNIQUEIDENTIFIER, - family_guid UNIQUEIDENTIFIER, differential_base_lsn NUMERIC(25, 0), differential_base_guid UNIQUEIDENTIFIER, compressed_backup_size NUMERIC(20, 0), key_algorithm NVARCHAR(32), - encryptor_thumbprint VARBINARY(20) , encryptor_type NVARCHAR(32) - ); - ' + @crlf; - - SET @InnerStringToExecute = N'EXEC( ''' + @StringToExecute + ''' ) AT ' + QUOTENAME(@WriteBackupsToListenerName) + N';' - - IF @Debug = 1 - PRINT @InnerStringToExecute; - - EXEC sp_executesql @InnerStringToExecute +IF @Reanalyze = 1 + BEGIN + IF OBJECT_ID('tempdb..##BlitzCacheResults') IS NULL + BEGIN + RAISERROR(N'##BlitzCacheResults does not exist, can''t reanalyze', 0, 1) WITH NOWAIT; + SET @Reanalyze = 0; + END + ELSE + BEGIN + RAISERROR(N'Reanalyzing current data, skipping to results', 0, 1) WITH NOWAIT; + GOTO Results; + END; + END; - RAISERROR('We''ll even make the indexes!', 0, 1) WITH NOWAIT +IF @SortOrder IN ('all', 'all avg') + BEGIN + RAISERROR(N'Checking all sort orders, please be patient', 0, 1) WITH NOWAIT; + GOTO AllSorts; + END; - /*Checking for and creating the PK/CX*/ +RAISERROR(N'Creating temp tables for internal processing', 0, 1) WITH NOWAIT; +IF OBJECT_ID('tempdb..#only_query_hashes') IS NOT NULL + DROP TABLE #only_query_hashes ; - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - - SET @StringToExecute += N' - - IF NOT EXISTS ( - SELECT t.name, i.name - FROM ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.sys.tables AS t - JOIN ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.sys.indexes AS i - ON t.object_id = i.object_id - WHERE t.name = ? - AND i.name LIKE ? - ) +IF OBJECT_ID('tempdb..#ignore_query_hashes') IS NOT NULL + DROP TABLE #ignore_query_hashes ; - BEGIN - ALTER TABLE ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.[dbo].[backupset] ADD PRIMARY KEY CLUSTERED ([backup_set_id] ASC) - END - ' +IF OBJECT_ID('tempdb..#only_sql_handles') IS NOT NULL + DROP TABLE #only_sql_handles ; - SET @InnerStringToExecute = N'EXEC( ''' + @StringToExecute + ''', ''backupset'', ''PK[_][_]%'' ) AT ' + QUOTENAME(@WriteBackupsToListenerName) + N';' - - IF @Debug = 1 - PRINT @InnerStringToExecute; - - EXEC sp_executesql @InnerStringToExecute +IF OBJECT_ID('tempdb..#ignore_sql_handles') IS NOT NULL + DROP TABLE #ignore_sql_handles ; + +IF OBJECT_ID('tempdb..#p') IS NOT NULL + DROP TABLE #p; +IF OBJECT_ID ('tempdb..#checkversion') IS NOT NULL + DROP TABLE #checkversion; +IF OBJECT_ID ('tempdb..#configuration') IS NOT NULL + DROP TABLE #configuration; - /*Checking for and creating index on backup_set_uuid*/ +IF OBJECT_ID ('tempdb..#stored_proc_info') IS NOT NULL + DROP TABLE #stored_proc_info; - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - - SET @StringToExecute += N'IF NOT EXISTS ( - SELECT t.name, i.name - FROM ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.sys.tables AS t - JOIN ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.sys.indexes AS i - ON t.object_id = i.object_id - WHERE t.name = ? - AND i.name = ? - ) +IF OBJECT_ID ('tempdb..#plan_creation') IS NOT NULL + DROP TABLE #plan_creation; - BEGIN - CREATE NONCLUSTERED INDEX [backupsetuuid] ON ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.[dbo].[backupset] ([backup_set_uuid] ASC) - END - ' +IF OBJECT_ID ('tempdb..#est_rows') IS NOT NULL + DROP TABLE #est_rows; - SET @InnerStringToExecute = N'EXEC( ''' + @StringToExecute + ''', ''backupset'', ''backupsetuuid'' ) AT ' + QUOTENAME(@WriteBackupsToListenerName) + N';' - - IF @Debug = 1 - PRINT @InnerStringToExecute; - - EXEC sp_executesql @InnerStringToExecute - - - - /*Checking for and creating index on media_set_id*/ +IF OBJECT_ID ('tempdb..#plan_cost') IS NOT NULL + DROP TABLE #plan_cost; - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - - SET @StringToExecute += 'IF NOT EXISTS ( - SELECT t.name, i.name - FROM ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.sys.tables AS t - JOIN ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.sys.indexes AS i - ON t.object_id = i.object_id - WHERE t.name = ? - AND i.name = ? - ) +IF OBJECT_ID ('tempdb..#proc_costs') IS NOT NULL + DROP TABLE #proc_costs; - BEGIN - CREATE NONCLUSTERED INDEX [backupsetMediaSetId] ON ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.[dbo].[backupset] ([media_set_id] ASC) - END - ' +IF OBJECT_ID ('tempdb..#stats_agg') IS NOT NULL + DROP TABLE #stats_agg; - SET @InnerStringToExecute = N'EXEC( ''' + @StringToExecute + ''', ''backupset'', ''backupsetMediaSetId'' ) AT ' + QUOTENAME(@WriteBackupsToListenerName) + N';' - - IF @Debug = 1 - PRINT @InnerStringToExecute; - - EXEC sp_executesql @InnerStringToExecute - - - - /*Checking for and creating index on backup_finish_date*/ +IF OBJECT_ID ('tempdb..#trace_flags') IS NOT NULL + DROP TABLE #trace_flags; - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - - SET @StringToExecute += N'IF NOT EXISTS ( - SELECT t.name, i.name - FROM ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.sys.tables AS t - JOIN ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.sys.indexes AS i - ON t.object_id = i.object_id - WHERE t.name = ? - AND i.name = ? - ) +IF OBJECT_ID('tempdb..#variable_info') IS NOT NULL + DROP TABLE #variable_info; - BEGIN - CREATE NONCLUSTERED INDEX [backupsetDate] ON ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.[dbo].[backupset] ([backup_finish_date] ASC) - END - ' +IF OBJECT_ID('tempdb..#conversion_info') IS NOT NULL + DROP TABLE #conversion_info; - SET @InnerStringToExecute = N'EXEC( ''' + @StringToExecute + ''', ''backupset'', ''backupsetDate'' ) AT ' + QUOTENAME(@WriteBackupsToListenerName) + N';' +IF OBJECT_ID('tempdb..#missing_index_xml') IS NOT NULL + DROP TABLE #missing_index_xml; + +IF OBJECT_ID('tempdb..#missing_index_schema') IS NOT NULL + DROP TABLE #missing_index_schema; + +IF OBJECT_ID('tempdb..#missing_index_usage') IS NOT NULL + DROP TABLE #missing_index_usage; + +IF OBJECT_ID('tempdb..#missing_index_detail') IS NOT NULL + DROP TABLE #missing_index_detail; + +IF OBJECT_ID('tempdb..#missing_index_pretty') IS NOT NULL + DROP TABLE #missing_index_pretty; + +IF OBJECT_ID('tempdb..#index_spool_ugly') IS NOT NULL + DROP TABLE #index_spool_ugly; - IF @Debug = 1 - PRINT @InnerStringToExecute; - - EXEC sp_executesql @InnerStringToExecute +IF OBJECT_ID('tempdb..#ReadableDBs') IS NOT NULL + DROP TABLE #ReadableDBs; +IF OBJECT_ID('tempdb..#plan_usage') IS NOT NULL + DROP TABLE #plan_usage; - - /*Checking for and creating index on database_name*/ +CREATE TABLE #only_query_hashes ( + query_hash BINARY(8) +); - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - - SET @StringToExecute += N'IF NOT EXISTS ( - SELECT t.name, i.name - FROM ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.sys.tables AS t - JOIN ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.sys.indexes AS i - ON t.object_id = i.object_id - WHERE t.name = ? - AND i.name = ? - ) +CREATE TABLE #ignore_query_hashes ( + query_hash BINARY(8) +); - BEGIN - CREATE NONCLUSTERED INDEX [backupsetDatabaseName] ON ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.[dbo].[backupset] ([database_name] ASC ) INCLUDE ([backup_set_id], [media_set_id]) - END +CREATE TABLE #only_sql_handles ( + sql_handle VARBINARY(64) +); - ' +CREATE TABLE #ignore_sql_handles ( + sql_handle VARBINARY(64) +); - SET @InnerStringToExecute = N'EXEC( ''' + @StringToExecute + ''', ''backupset'', ''backupsetDatabaseName'' ) AT ' + QUOTENAME(@WriteBackupsToListenerName) + N';' - - IF @Debug = 1 - PRINT @InnerStringToExecute; - - EXEC sp_executesql @InnerStringToExecute +CREATE TABLE #p ( + SqlHandle VARBINARY(64), + TotalCPU BIGINT, + TotalDuration BIGINT, + TotalReads BIGINT, + TotalWrites BIGINT, + ExecutionCount BIGINT +); - RAISERROR('Table and indexes created! You''re welcome!', 0, 1) WITH NOWAIT - END +CREATE TABLE #checkversion ( + version NVARCHAR(128), + common_version AS SUBSTRING(version, 1, CHARINDEX('.', version) + 1 ), + major AS PARSENAME(CONVERT(VARCHAR(32), version), 4), + minor AS PARSENAME(CONVERT(VARCHAR(32), version), 3), + build AS PARSENAME(CONVERT(VARCHAR(32), version), 2), + revision AS PARSENAME(CONVERT(VARCHAR(32), version), 1) +); +CREATE TABLE #configuration ( + parameter_name VARCHAR(100), + value DECIMAL(38,0) +); - RAISERROR('Beginning inserts', 0, 1) WITH NOWAIT; - RAISERROR(@crlf, 0, 1) WITH NOWAIT; +CREATE TABLE #plan_creation +( + percent_24 DECIMAL(5, 2), + percent_4 DECIMAL(5, 2), + percent_1 DECIMAL(5, 2), + total_plans INT, + SPID INT +); - /* - Batching code comes from the lovely and talented Michael J. Swart - http://michaeljswart.com/2014/09/take-care-when-scripting-batches/ - If you're ever in Canada, he says you can stay at his house, too. - */ +CREATE TABLE #est_rows +( + QueryHash BINARY(8), + estimated_rows FLOAT +); +CREATE TABLE #plan_cost +( + QueryPlanCost FLOAT, + SqlHandle VARBINARY(64), + PlanHandle VARBINARY(64), + QueryHash BINARY(8), + QueryPlanHash BINARY(8) +); - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; +CREATE TABLE #proc_costs +( + PlanTotalQuery FLOAT, + PlanHandle VARBINARY(64), + SqlHandle VARBINARY(64) +); - SET @StringToExecute += N' - DECLARE - @StartDate DATETIME = DATEADD(HOUR, @i_WriteBackupsLastHours, SYSDATETIME()), - @StartDateNext DATETIME, - @RC INT = 1, - @msg NVARCHAR(4000) = N''''; - - SELECT @StartDate = MIN(b.backup_start_date) - FROM msdb.dbo.backupset b - WHERE b.backup_start_date >= @StartDate - AND NOT EXISTS ( - SELECT 1 - FROM ' + QUOTENAME(@WriteBackupsToListenerName) + N'.' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.dbo.backupset b2 - WHERE b.backup_set_uuid = b2.backup_set_uuid - AND b2.backup_start_date >= @StartDate - ) +CREATE TABLE #stats_agg +( + SqlHandle VARBINARY(64), + LastUpdate DATETIME2(7), + ModificationCount BIGINT, + SamplingPercent FLOAT, + [Statistics] NVARCHAR(258), + [Table] NVARCHAR(258), + [Schema] NVARCHAR(258), + [Database] NVARCHAR(258), +); - SET @StartDateNext = DATEADD(MINUTE, 10, @StartDate); +CREATE TABLE #trace_flags +( + SqlHandle VARBINARY(64), + QueryHash BINARY(8), + global_trace_flags VARCHAR(1000), + session_trace_flags VARCHAR(1000) +); - IF - ( @StartDate IS NULL ) - BEGIN - SET @msg = N''No data to move, exiting.'' - RAISERROR(@msg, 0, 1) WITH NOWAIT +CREATE TABLE #stored_proc_info +( + SPID INT, + SqlHandle VARBINARY(64), + QueryHash BINARY(8), + variable_name NVARCHAR(258), + variable_datatype NVARCHAR(258), + converted_column_name NVARCHAR(258), + compile_time_value NVARCHAR(258), + proc_name NVARCHAR(1000), + column_name NVARCHAR(4000), + converted_to NVARCHAR(258), + set_options NVARCHAR(1000) +); - RETURN; - END +CREATE TABLE #variable_info +( + SPID INT, + QueryHash BINARY(8), + SqlHandle VARBINARY(64), + proc_name NVARCHAR(1000), + variable_name NVARCHAR(258), + variable_datatype NVARCHAR(258), + compile_time_value NVARCHAR(258) +); - RAISERROR(''Starting insert loop'', 0, 1) WITH NOWAIT; +CREATE TABLE #conversion_info +( + SPID INT, + QueryHash BINARY(8), + SqlHandle VARBINARY(64), + proc_name NVARCHAR(258), + expression NVARCHAR(4000), + at_charindex AS CHARINDEX('@', expression), + bracket_charindex AS CHARINDEX(']', expression, CHARINDEX('@', expression)) - CHARINDEX('@', expression), + comma_charindex AS CHARINDEX(',', expression) + 1, + second_comma_charindex AS + CHARINDEX(',', expression, CHARINDEX(',', expression) + 1) - CHARINDEX(',', expression) - 1, + equal_charindex AS CHARINDEX('=', expression) + 1, + paren_charindex AS CHARINDEX('(', expression) + 1, + comma_paren_charindex AS + CHARINDEX(',', expression, CHARINDEX('(', expression) + 1) - CHARINDEX('(', expression) - 1, + convert_implicit_charindex AS CHARINDEX('=CONVERT_IMPLICIT', expression) +); - WHILE EXISTS ( - SELECT 1 - FROM msdb.dbo.backupset b - WHERE NOT EXISTS ( - SELECT 1 - FROM ' + QUOTENAME(@WriteBackupsToListenerName) + N'.' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.dbo.backupset b2 - WHERE b.backup_set_uuid = b2.backup_set_uuid - AND b2.backup_start_date >= @StartDate - ) - ) - BEGIN - - SET @msg = N''Inserting data for '' + CONVERT(NVARCHAR(30), @StartDate) + '' through '' + + CONVERT(NVARCHAR(30), @StartDateNext) + ''.'' - RAISERROR(@msg, 0, 1) WITH NOWAIT - - ' - SET @StringToExecute += N'INSERT ' + QUOTENAME(@WriteBackupsToListenerName) + N'.' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.dbo.backupset - ' - SET @StringToExecute += N' (database_name, database_guid, backup_set_uuid, type, backup_size, backup_start_date, backup_finish_date, media_set_id, time_zone, - compressed_backup_size, recovery_model, server_name, machine_name, first_lsn, last_lsn, user_name, compatibility_level, - is_password_protected, is_snapshot, is_readonly, is_single_user, has_backup_checksums, is_damaged, ' + CASE WHEN @ProductVersionMajor >= 12 - THEN + N'encryptor_type, has_bulk_logged_data)' + @crlf - ELSE + N'has_bulk_logged_data)' + @crlf - END - - SET @StringToExecute +=N' - SELECT database_name, database_guid, backup_set_uuid, type, backup_size, backup_start_date, backup_finish_date, media_set_id, time_zone, - compressed_backup_size, recovery_model, server_name, machine_name, first_lsn, last_lsn, user_name, compatibility_level, - is_password_protected, is_snapshot, is_readonly, is_single_user, has_backup_checksums, is_damaged, ' + CASE WHEN @ProductVersionMajor >= 12 - THEN + N'encryptor_type, has_bulk_logged_data' + @crlf - ELSE + N'has_bulk_logged_data' + @crlf - END - SET @StringToExecute +=N' - FROM msdb.dbo.backupset b - WHERE 1=1 - AND b.backup_start_date >= @StartDate - AND b.backup_start_date < @StartDateNext - AND NOT EXISTS ( - SELECT 1 - FROM ' + QUOTENAME(@WriteBackupsToListenerName) + N'.' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.dbo.backupset b2 - WHERE b.backup_set_uuid = b2.backup_set_uuid - AND b2.backup_start_date >= @StartDate - )' + @crlf; +CREATE TABLE #missing_index_xml +( + QueryHash BINARY(8), + SqlHandle VARBINARY(64), + impact FLOAT, + index_xml XML +); - SET @StringToExecute +=N' - SET @RC = @@ROWCOUNT; - - SET @msg = N''Inserted '' + CONVERT(NVARCHAR(30), @RC) + '' rows for ''+ CONVERT(NVARCHAR(30), @StartDate) + '' through '' + CONVERT(NVARCHAR(30), @StartDateNext) + ''.'' - RAISERROR(@msg, 0, 1) WITH NOWAIT - - SET @StartDate = @StartDateNext; - SET @StartDateNext = DATEADD(MINUTE, 10, @StartDate); +CREATE TABLE #missing_index_schema +( + QueryHash BINARY(8), + SqlHandle VARBINARY(64), + impact FLOAT, + database_name NVARCHAR(128), + schema_name NVARCHAR(128), + table_name NVARCHAR(128), + index_xml XML +); - IF - ( @StartDate > SYSDATETIME() ) - BEGIN - - SET @msg = N''No more data to move, exiting.'' - RAISERROR(@msg, 0, 1) WITH NOWAIT - - BREAK; - END - END' + @crlf; +CREATE TABLE #missing_index_usage +( + QueryHash BINARY(8), + SqlHandle VARBINARY(64), + impact FLOAT, + database_name NVARCHAR(128), + schema_name NVARCHAR(128), + table_name NVARCHAR(128), + usage NVARCHAR(128), + index_xml XML +); - IF @Debug = 1 - PRINT @StringToExecute; - EXEC sp_executesql @StringToExecute, N'@i_WriteBackupsLastHours INT', @i_WriteBackupsLastHours = @WriteBackupsLastHours; +CREATE TABLE #missing_index_detail +( + QueryHash BINARY(8), + SqlHandle VARBINARY(64), + impact FLOAT, + database_name NVARCHAR(128), + schema_name NVARCHAR(128), + table_name NVARCHAR(128), + usage NVARCHAR(128), + column_name NVARCHAR(128) +); -END; -END; +CREATE TABLE #missing_index_pretty +( + QueryHash BINARY(8), + SqlHandle VARBINARY(64), + impact FLOAT, + database_name NVARCHAR(128), + schema_name NVARCHAR(128), + table_name NVARCHAR(128), + equality NVARCHAR(MAX), + inequality NVARCHAR(MAX), + [include] NVARCHAR(MAX), + executions NVARCHAR(128), + query_cost NVARCHAR(128), + creation_hours NVARCHAR(128), + is_spool BIT, + details AS N'/* ' + + CHAR(10) + + CASE is_spool + WHEN 0 + THEN N'The Query Processor estimates that implementing the ' + ELSE N'We estimate that implementing the ' + END + + N'following index could improve query cost (' + query_cost + N')' + + CHAR(10) + + N'by ' + + CONVERT(NVARCHAR(30), impact) + + N'% for ' + executions + N' executions of the query' + + N' over the last ' + + CASE WHEN creation_hours < 24 + THEN creation_hours + N' hours.' + WHEN creation_hours = 24 + THEN ' 1 day.' + WHEN creation_hours > 24 + THEN (CONVERT(NVARCHAR(128), creation_hours / 24)) + N' days.' + ELSE N'' + END + + CHAR(10) + + N'*/' + + CHAR(10) + CHAR(13) + + N'/* ' + + CHAR(10) + + N'USE ' + + database_name + + CHAR(10) + + N'GO' + + CHAR(10) + CHAR(13) + + N'CREATE NONCLUSTERED INDEX ix_' + + ISNULL(REPLACE(REPLACE(REPLACE(equality,'[', ''), ']', ''), ', ', '_'), '') + + ISNULL(REPLACE(REPLACE(REPLACE(inequality,'[', ''), ']', ''), ', ', '_'), '') + + CASE WHEN [include] IS NOT NULL THEN + N'_Includes' ELSE N'' END + + CHAR(10) + + N' ON ' + + schema_name + + N'.' + + table_name + + N' (' + + + CASE WHEN equality IS NOT NULL + THEN equality + + CASE WHEN inequality IS NOT NULL + THEN N', ' + inequality + ELSE N'' + END + ELSE inequality + END + + N')' + + CHAR(10) + + CASE WHEN include IS NOT NULL + THEN N'INCLUDE (' + include + N') WITH (FILLFACTOR=100, ONLINE=?, SORT_IN_TEMPDB=?, DATA_COMPRESSION=?);' + ELSE N' WITH (FILLFACTOR=100, ONLINE=?, SORT_IN_TEMPDB=?, DATA_COMPRESSION=?);' + END + + CHAR(10) + + N'GO' + + CHAR(10) + + N'*/' +); -GO -SET ANSI_NULLS ON; -SET ANSI_PADDING ON; -SET ANSI_WARNINGS ON; -SET ARITHABORT ON; -SET CONCAT_NULL_YIELDS_NULL ON; -SET QUOTED_IDENTIFIER ON; -SET STATISTICS IO OFF; -SET STATISTICS TIME OFF; -GO -IF ( -SELECT - CASE - WHEN CONVERT(NVARCHAR(128), SERVERPROPERTY ('PRODUCTVERSION')) LIKE '8%' THEN 0 - WHEN CONVERT(NVARCHAR(128), SERVERPROPERTY ('PRODUCTVERSION')) LIKE '9%' THEN 0 - ELSE 1 - END -) = 0 -BEGIN - DECLARE @msg VARCHAR(8000); - SELECT @msg = 'Sorry, sp_BlitzCache doesn''t work on versions of SQL prior to 2008.' + REPLICATE(CHAR(13), 7933); - PRINT @msg; - RETURN; -END; +CREATE TABLE #index_spool_ugly +( + QueryHash BINARY(8), + SqlHandle VARBINARY(64), + impact FLOAT, + database_name NVARCHAR(128), + schema_name NVARCHAR(128), + table_name NVARCHAR(128), + equality NVARCHAR(MAX), + inequality NVARCHAR(MAX), + [include] NVARCHAR(MAX), + executions NVARCHAR(128), + query_cost NVARCHAR(128), + creation_hours NVARCHAR(128) +); -IF OBJECT_ID('dbo.sp_BlitzCache') IS NULL - EXEC ('CREATE PROCEDURE dbo.sp_BlitzCache AS RETURN 0;'); -GO -IF OBJECT_ID('dbo.sp_BlitzCache') IS NOT NULL AND OBJECT_ID('tempdb.dbo.##BlitzCacheProcs', 'U') IS NOT NULL - EXEC ('DROP TABLE ##BlitzCacheProcs;'); -GO +CREATE TABLE #ReadableDBs +( +database_id INT +); -IF OBJECT_ID('dbo.sp_BlitzCache') IS NOT NULL AND OBJECT_ID('tempdb.dbo.##BlitzCacheResults', 'U') IS NOT NULL - EXEC ('DROP TABLE ##BlitzCacheResults;'); -GO -CREATE TABLE ##BlitzCacheResults ( - SPID INT, - ID INT IDENTITY(1,1), - CheckID INT, - Priority TINYINT, - FindingsGroup VARCHAR(50), - Finding VARCHAR(500), - URL VARCHAR(200), - Details VARCHAR(4000) +CREATE TABLE #plan_usage +( + duplicate_plan_hashes BIGINT NULL, + percent_duplicate DECIMAL(9, 2) NULL, + single_use_plan_count BIGINT NULL, + percent_single DECIMAL(9, 2) NULL, + total_plans BIGINT NULL, + spid INT ); -CREATE TABLE ##BlitzCacheProcs ( - SPID INT , - QueryType NVARCHAR(258), - DatabaseName sysname, - AverageCPU DECIMAL(38,4), - AverageCPUPerMinute DECIMAL(38,4), - TotalCPU DECIMAL(38,4), - PercentCPUByType MONEY, - PercentCPU MONEY, - AverageDuration DECIMAL(38,4), - TotalDuration DECIMAL(38,4), - PercentDuration MONEY, - PercentDurationByType MONEY, - AverageReads BIGINT, - TotalReads BIGINT, - PercentReads MONEY, - PercentReadsByType MONEY, - ExecutionCount BIGINT, - PercentExecutions MONEY, - PercentExecutionsByType MONEY, - ExecutionsPerMinute MONEY, - TotalWrites BIGINT, - AverageWrites MONEY, - PercentWrites MONEY, - PercentWritesByType MONEY, - WritesPerMinute MONEY, - PlanCreationTime DATETIME, - PlanCreationTimeHours AS DATEDIFF(HOUR, PlanCreationTime, SYSDATETIME()), - LastExecutionTime DATETIME, - LastCompletionTime DATETIME, - PlanHandle VARBINARY(64), - [Remove Plan Handle From Cache] AS - CASE WHEN [PlanHandle] IS NOT NULL - THEN 'DBCC FREEPROCCACHE (' + CONVERT(VARCHAR(128), [PlanHandle], 1) + ');' - ELSE 'N/A' END, - SqlHandle VARBINARY(64), - [Remove SQL Handle From Cache] AS - CASE WHEN [SqlHandle] IS NOT NULL - THEN 'DBCC FREEPROCCACHE (' + CONVERT(VARCHAR(128), [SqlHandle], 1) + ');' - ELSE 'N/A' END, - [SQL Handle More Info] AS - CASE WHEN [SqlHandle] IS NOT NULL - THEN 'EXEC sp_BlitzCache @OnlySqlHandles = ''' + CONVERT(VARCHAR(128), [SqlHandle], 1) + '''; ' - ELSE 'N/A' END, - QueryHash BINARY(8), - [Query Hash More Info] AS - CASE WHEN [QueryHash] IS NOT NULL - THEN 'EXEC sp_BlitzCache @OnlyQueryHashes = ''' + CONVERT(VARCHAR(32), [QueryHash], 1) + '''; ' - ELSE 'N/A' END, - QueryPlanHash BINARY(8), - StatementStartOffset INT, - StatementEndOffset INT, - PlanGenerationNum BIGINT, - MinReturnedRows BIGINT, - MaxReturnedRows BIGINT, - AverageReturnedRows MONEY, - TotalReturnedRows BIGINT, - LastReturnedRows BIGINT, - /*The Memory Grant columns are only supported - in certain versions, giggle giggle. - */ - MinGrantKB BIGINT, - MaxGrantKB BIGINT, - MinUsedGrantKB BIGINT, - MaxUsedGrantKB BIGINT, - PercentMemoryGrantUsed MONEY, - AvgMaxMemoryGrant MONEY, - MinSpills BIGINT, - MaxSpills BIGINT, - TotalSpills BIGINT, - AvgSpills MONEY, - QueryText NVARCHAR(MAX), - QueryPlan XML, - /* these next four columns are the total for the type of query. - don't actually use them for anything apart from math by type. - */ - TotalWorkerTimeForType BIGINT, - TotalElapsedTimeForType BIGINT, - TotalReadsForType DECIMAL(30), - TotalExecutionCountForType BIGINT, - TotalWritesForType DECIMAL(30), - NumberOfPlans INT, - NumberOfDistinctPlans INT, - SerialDesiredMemory FLOAT, - SerialRequiredMemory FLOAT, - CachedPlanSize FLOAT, - CompileTime FLOAT, - CompileCPU FLOAT , - CompileMemory FLOAT , - MaxCompileMemory FLOAT , - min_worker_time BIGINT, - max_worker_time BIGINT, - is_forced_plan BIT, - is_forced_parameterized BIT, - is_cursor BIT, - is_optimistic_cursor BIT, - is_forward_only_cursor BIT, - is_fast_forward_cursor BIT, - is_cursor_dynamic BIT, - is_parallel BIT, - is_forced_serial BIT, - is_key_lookup_expensive BIT, - key_lookup_cost FLOAT, - is_remote_query_expensive BIT, - remote_query_cost FLOAT, - frequent_execution BIT, - parameter_sniffing BIT, - unparameterized_query BIT, - near_parallel BIT, - plan_warnings BIT, - plan_multiple_plans INT, - long_running BIT, - downlevel_estimator BIT, - implicit_conversions BIT, - busy_loops BIT, - tvf_join BIT, - tvf_estimate BIT, - compile_timeout BIT, - compile_memory_limit_exceeded BIT, - warning_no_join_predicate BIT, - QueryPlanCost FLOAT, - missing_index_count INT, - unmatched_index_count INT, - min_elapsed_time BIGINT, - max_elapsed_time BIGINT, - age_minutes MONEY, - age_minutes_lifetime MONEY, - is_trivial BIT, - trace_flags_session VARCHAR(1000), - is_unused_grant BIT, - function_count INT, - clr_function_count INT, - is_table_variable BIT, - no_stats_warning BIT, - relop_warnings BIT, - is_table_scan BIT, - backwards_scan BIT, - forced_index BIT, - forced_seek BIT, - forced_scan BIT, - columnstore_row_mode BIT, - is_computed_scalar BIT , - is_sort_expensive BIT, - sort_cost FLOAT, - is_computed_filter BIT, - op_name VARCHAR(100) NULL, - index_insert_count INT NULL, - index_update_count INT NULL, - index_delete_count INT NULL, - cx_insert_count INT NULL, - cx_update_count INT NULL, - cx_delete_count INT NULL, - table_insert_count INT NULL, - table_update_count INT NULL, - table_delete_count INT NULL, - index_ops AS (index_insert_count + index_update_count + index_delete_count + - cx_insert_count + cx_update_count + cx_delete_count + - table_insert_count + table_update_count + table_delete_count), - is_row_level BIT, - is_spatial BIT, - index_dml BIT, - table_dml BIT, - long_running_low_cpu BIT, - low_cost_high_cpu BIT, - stale_stats BIT, - is_adaptive BIT, - index_spool_cost FLOAT, - index_spool_rows FLOAT, - table_spool_cost FLOAT, - table_spool_rows FLOAT, - is_spool_expensive BIT, - is_spool_more_rows BIT, - is_table_spool_expensive BIT, - is_table_spool_more_rows BIT, - estimated_rows FLOAT, - is_bad_estimate BIT, - is_paul_white_electric BIT, - is_row_goal BIT, - is_big_spills BIT, - is_mstvf BIT, - is_mm_join BIT, - is_nonsargable BIT, - select_with_writes BIT, - implicit_conversion_info XML, - cached_execution_parameters XML, - missing_indexes XML, - SetOptions VARCHAR(MAX), - Warnings VARCHAR(MAX), - Pattern NVARCHAR(20) - ); -GO -ALTER PROCEDURE dbo.sp_BlitzCache - @Help BIT = 0, - @Top INT = NULL, - @SortOrder VARCHAR(50) = 'CPU', - @UseTriggersAnyway BIT = NULL, - @ExportToExcel BIT = 0, - @ExpertMode TINYINT = 0, - @OutputType VARCHAR(20) = 'TABLE' , - @OutputServerName NVARCHAR(258) = NULL , - @OutputDatabaseName NVARCHAR(258) = NULL , - @OutputSchemaName NVARCHAR(258) = NULL , - @OutputTableName NVARCHAR(258) = NULL , -- do NOT use ##BlitzCacheResults or ##BlitzCacheProcs as they are used as work tables in this procedure - @ConfigurationDatabaseName NVARCHAR(128) = NULL , - @ConfigurationSchemaName NVARCHAR(258) = NULL , - @ConfigurationTableName NVARCHAR(258) = NULL , - @DurationFilter DECIMAL(38,4) = NULL , - @HideSummary BIT = 0 , - @IgnoreSystemDBs BIT = 1 , - @OnlyQueryHashes VARCHAR(MAX) = NULL , - @IgnoreQueryHashes VARCHAR(MAX) = NULL , - @OnlySqlHandles VARCHAR(MAX) = NULL , - @IgnoreSqlHandles VARCHAR(MAX) = NULL , - @QueryFilter VARCHAR(10) = 'ALL' , - @DatabaseName NVARCHAR(128) = NULL , - @StoredProcName NVARCHAR(128) = NULL, - @SlowlySearchPlansFor NVARCHAR(4000) = NULL, - @Reanalyze BIT = 0 , - @SkipAnalysis BIT = 0 , - @BringThePain BIT = 0 , - @MinimumExecutionCount INT = 0, - @Debug BIT = 0, - @CheckDateOverride DATETIMEOFFSET = NULL, - @MinutesBack INT = NULL, - @Version VARCHAR(30) = NULL OUTPUT, - @VersionDate DATETIME = NULL OUTPUT, - @VersionCheckMode BIT = 0 -WITH RECOMPILE -AS +IF EXISTS (SELECT * FROM sys.all_objects o WHERE o.name = 'dm_hadr_database_replica_states') BEGIN -SET NOCOUNT ON; -SET STATISTICS XML OFF; -SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + RAISERROR('Checking for Read intent databases to exclude',0,0) WITH NOWAIT; -SELECT @Version = '8.19', @VersionDate = '20240222'; -SET @OutputType = UPPER(@OutputType); + EXEC('INSERT INTO #ReadableDBs (database_id) SELECT DBs.database_id FROM sys.databases DBs INNER JOIN sys.availability_replicas Replicas ON DBs.replica_id = Replicas.replica_id WHERE replica_server_name NOT IN (SELECT DISTINCT primary_replica FROM sys.dm_hadr_availability_group_states States) AND Replicas.secondary_role_allow_connections_desc = ''READ_ONLY'' AND replica_server_name = @@SERVERNAME OPTION (RECOMPILE);'); + EXEC('INSERT INTO #ReadableDBs VALUES (32767) ;'); -- Exclude internal resource database as well +END -IF(@VersionCheckMode = 1) -BEGIN - RETURN; -END; +RAISERROR(N'Checking plan cache age', 0, 1) WITH NOWAIT; +WITH x AS ( +SELECT SUM(CASE WHEN DATEDIFF(HOUR, deqs.creation_time, SYSDATETIME()) <= 24 THEN 1 ELSE 0 END) AS [plans_24], + SUM(CASE WHEN DATEDIFF(HOUR, deqs.creation_time, SYSDATETIME()) <= 4 THEN 1 ELSE 0 END) AS [plans_4], + SUM(CASE WHEN DATEDIFF(HOUR, deqs.creation_time, SYSDATETIME()) <= 1 THEN 1 ELSE 0 END) AS [plans_1], + COUNT(deqs.creation_time) AS [total_plans] +FROM sys.dm_exec_query_stats AS deqs +) +INSERT INTO #plan_creation ( percent_24, percent_4, percent_1, total_plans, SPID ) +SELECT CONVERT(DECIMAL(5,2), NULLIF(x.plans_24, 0) / (1. * NULLIF(x.total_plans, 0))) * 100 AS [percent_24], + CONVERT(DECIMAL(5,2), NULLIF(x.plans_4 , 0) / (1. * NULLIF(x.total_plans, 0))) * 100 AS [percent_4], + CONVERT(DECIMAL(5,2), NULLIF(x.plans_1 , 0) / (1. * NULLIF(x.total_plans, 0))) * 100 AS [percent_1], + x.total_plans, + @@SPID AS SPID +FROM x +OPTION (RECOMPILE); -DECLARE @nl NVARCHAR(2) = NCHAR(13) + NCHAR(10) ; - -IF @Help = 1 - BEGIN - PRINT ' - sp_BlitzCache from http://FirstResponderKit.org - - This script displays your most resource-intensive queries from the plan cache, - and points to ways you can tune these queries to make them faster. +RAISERROR(N'Checking for single use plans and plans with many queries', 0, 1) WITH NOWAIT; +WITH total_plans AS +( + SELECT + COUNT_BIG(deqs.query_plan_hash) AS total_plans + FROM sys.dm_exec_query_stats AS deqs +), + many_plans AS +( + SELECT + SUM(x.duplicate_plan_hashes) AS duplicate_plan_hashes + FROM + ( + SELECT + COUNT_BIG(qs.query_plan_hash) AS duplicate_plan_hashes + FROM sys.dm_exec_query_stats qs + LEFT JOIN sys.dm_exec_procedure_stats ps ON qs.plan_handle = ps.plan_handle + CROSS APPLY sys.dm_exec_plan_attributes(qs.plan_handle) pa + WHERE pa.attribute = N'dbid' + AND pa.value <> 32767 /*Omit Resource database-based queries, we're not going to "fix" them no matter what. Addresses #3314*/ + AND qs.query_plan_hash <> 0x0000000000000000 + GROUP BY + /* qs.query_plan_hash, BGO 20210524 commenting this out to fix #2909 */ + qs.query_hash, + ps.object_id, + pa.value + HAVING COUNT_BIG(qs.query_plan_hash) > 5 + ) AS x +), + single_use_plans AS +( + SELECT + COUNT_BIG(*) AS single_use_plan_count + FROM sys.dm_exec_query_stats AS s + WHERE s.execution_count = 1 +) +INSERT + #plan_usage +( + duplicate_plan_hashes, + percent_duplicate, + single_use_plan_count, + percent_single, + total_plans, + spid +) +SELECT + m.duplicate_plan_hashes, + CONVERT + ( + decimal(5,2), + m.duplicate_plan_hashes + / (1. * NULLIF(t.total_plans, 0)) + ) * 100. AS percent_duplicate, + s.single_use_plan_count, + CONVERT + ( + decimal(5,2), + s.single_use_plan_count + / (1. * NULLIF(t.total_plans, 0)) + ) * 100. AS percent_single, + t.total_plans, + @@SPID +FROM many_plans AS m +CROSS JOIN single_use_plans AS s +CROSS JOIN total_plans AS t; - To learn more, visit http://FirstResponderKit.org where you can download new - versions for free, watch training videos on how it works, get more info on - the findings, contribute your own code, and more. - Known limitations of this version: - - SQL Server 2008 and 2008R2 have a bug in trigger stats, so that output is - excluded by default. - - @IgnoreQueryHashes and @OnlyQueryHashes require a CSV list of hashes - with no spaces between the hash values. +/* +Erik Darling: + Quoting this out to see if the above query fixes the issue + 2021-05-17, Issue #2909 - Unknown limitations of this version: - - May or may not be vulnerable to the wick effect. +UPDATE #plan_usage + SET percent_duplicate = CASE WHEN percent_duplicate > 100 THEN 100 ELSE percent_duplicate END, + percent_single = CASE WHEN percent_duplicate > 100 THEN 100 ELSE percent_duplicate END; +*/ - Changes - for the full list of improvements and fixes in this version, see: - https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/ +SET @OnlySqlHandles = LTRIM(RTRIM(@OnlySqlHandles)) ; +SET @OnlyQueryHashes = LTRIM(RTRIM(@OnlyQueryHashes)) ; +SET @IgnoreQueryHashes = LTRIM(RTRIM(@IgnoreQueryHashes)) ; +DECLARE @individual VARCHAR(100) ; +IF (@OnlySqlHandles IS NOT NULL AND @IgnoreSqlHandles IS NOT NULL) +BEGIN +RAISERROR('You shouldn''t need to ignore and filter on SqlHandle at the same time.', 0, 1) WITH NOWAIT; +RETURN; +END; - MIT License +IF (@StoredProcName IS NOT NULL AND (@OnlySqlHandles IS NOT NULL OR @IgnoreSqlHandles IS NOT NULL)) +BEGIN +RAISERROR('You can''t filter on stored procedure name and SQL Handle.', 0, 1) WITH NOWAIT; +RETURN; +END; - Copyright (c) Brent Ozar Unlimited +IF @OnlySqlHandles IS NOT NULL + AND LEN(@OnlySqlHandles) > 0 +BEGIN + RAISERROR(N'Processing SQL Handles', 0, 1) WITH NOWAIT; + SET @individual = ''; - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: + WHILE LEN(@OnlySqlHandles) > 0 + BEGIN + IF PATINDEX('%,%', @OnlySqlHandles) > 0 + BEGIN + SET @individual = SUBSTRING(@OnlySqlHandles, 0, PATINDEX('%,%',@OnlySqlHandles)) ; + + INSERT INTO #only_sql_handles + SELECT CAST('' AS XML).value('xs:hexBinary( substring(sql:variable("@individual"), sql:column("t.pos")) )', 'varbinary(max)') + FROM (SELECT CASE SUBSTRING(@individual, 1, 2) WHEN '0x' THEN 3 ELSE 0 END) AS t(pos) + OPTION (RECOMPILE) ; + + --SELECT CAST(SUBSTRING(@individual, 1, 2) AS BINARY(8)); - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. + SET @OnlySqlHandles = SUBSTRING(@OnlySqlHandles, LEN(@individual + ',') + 1, LEN(@OnlySqlHandles)) ; + END; + ELSE + BEGIN + SET @individual = @OnlySqlHandles; + SET @OnlySqlHandles = NULL; - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE. - '; + INSERT INTO #only_sql_handles + SELECT CAST('' AS XML).value('xs:hexBinary( substring(sql:variable("@individual"), sql:column("t.pos")) )', 'varbinary(max)') + FROM (SELECT CASE SUBSTRING(@individual, 1, 2) WHEN '0x' THEN 3 ELSE 0 END) AS t(pos) + OPTION (RECOMPILE) ; - - SELECT N'@Help' AS [Parameter Name] , - N'BIT' AS [Data Type] , - N'Displays this help message.' AS [Parameter Description] + --SELECT CAST(SUBSTRING(@individual, 1, 2) AS VARBINARY(MAX)) ; + END; + END; +END; - UNION ALL - SELECT N'@Top', - N'INT', - N'The number of records to retrieve and analyze from the plan cache. The following DMVs are used as the plan cache: dm_exec_query_stats, dm_exec_procedure_stats, dm_exec_trigger_stats.' +IF @IgnoreSqlHandles IS NOT NULL + AND LEN(@IgnoreSqlHandles) > 0 +BEGIN + RAISERROR(N'Processing SQL Handles To Ignore', 0, 1) WITH NOWAIT; + SET @individual = ''; - UNION ALL - SELECT N'@SortOrder', - N'VARCHAR(10)', - N'Data processing and display order. @SortOrder will still be used, even when preparing output for a table or for excel. Possible values are: "CPU", "Reads", "Writes", "Duration", "Executions", "Recent Compilations", "Memory Grant", "Unused Grant", "Spills", "Query Hash", "Duplicate". Additionally, the word "Average" or "Avg" can be used to sort on averages rather than total. "Executions per minute" and "Executions / minute" can be used to sort by execution per minute. For the truly lazy, "xpm" can also be used. Note that when you use all or all avg, the only parameters you can use are @Top and @DatabaseName. All others will be ignored.' + WHILE LEN(@IgnoreSqlHandles) > 0 + BEGIN + IF PATINDEX('%,%', @IgnoreSqlHandles) > 0 + BEGIN + SET @individual = SUBSTRING(@IgnoreSqlHandles, 0, PATINDEX('%,%',@IgnoreSqlHandles)) ; + + INSERT INTO #ignore_sql_handles + SELECT CAST('' AS XML).value('xs:hexBinary( substring(sql:variable("@individual"), sql:column("t.pos")) )', 'varbinary(max)') + FROM (SELECT CASE SUBSTRING(@individual, 1, 2) WHEN '0x' THEN 3 ELSE 0 END) AS t(pos) + OPTION (RECOMPILE) ; + + --SELECT CAST(SUBSTRING(@individual, 1, 2) AS BINARY(8)); - UNION ALL - SELECT N'@UseTriggersAnyway', - N'BIT', - N'On SQL Server 2008R2 and earlier, trigger execution count is incorrect - trigger execution count is incremented once per execution of a SQL agent job. If you still want to see relative execution count of triggers, then you can force sp_BlitzCache to include this information.' + SET @IgnoreSqlHandles = SUBSTRING(@IgnoreSqlHandles, LEN(@individual + ',') + 1, LEN(@IgnoreSqlHandles)) ; + END; + ELSE + BEGIN + SET @individual = @IgnoreSqlHandles; + SET @IgnoreSqlHandles = NULL; - UNION ALL - SELECT N'@ExportToExcel', - N'BIT', - N'Prepare output for exporting to Excel. Newlines and additional whitespace are removed from query text and the execution plan is not displayed.' + INSERT INTO #ignore_sql_handles + SELECT CAST('' AS XML).value('xs:hexBinary( substring(sql:variable("@individual"), sql:column("t.pos")) )', 'varbinary(max)') + FROM (SELECT CASE SUBSTRING(@individual, 1, 2) WHEN '0x' THEN 3 ELSE 0 END) AS t(pos) + OPTION (RECOMPILE) ; - UNION ALL - SELECT N'@ExpertMode', - N'TINYINT', - N'Default 0. When set to 1, results include more columns. When 2, mode is optimized for Opserver, the open source dashboard.' - UNION ALL - SELECT N'@OutputType', - N'NVARCHAR(258)', - N'If set to "NONE", this will tell this procedure not to run any query leading to a results set thrown to caller.' - - UNION ALL - SELECT N'@OutputDatabaseName', - N'NVARCHAR(128)', - N'The output database. If this does not exist SQL Server will divide by zero and everything will fall apart.' + --SELECT CAST(SUBSTRING(@individual, 1, 2) AS VARBINARY(MAX)) ; + END; + END; +END; - UNION ALL - SELECT N'@OutputSchemaName', - N'NVARCHAR(258)', - N'The output schema. If this does not exist SQL Server will divide by zero and everything will fall apart.' +IF @StoredProcName IS NOT NULL AND @StoredProcName <> N'' - UNION ALL - SELECT N'@OutputTableName', - N'NVARCHAR(258)', - N'The output table. If this does not exist, it will be created for you.' +BEGIN + RAISERROR(N'Setting up filter for stored procedure name', 0, 1) WITH NOWAIT; + + DECLARE @function_search_sql NVARCHAR(MAX) = N'' + + INSERT #only_sql_handles + ( sql_handle ) + SELECT ISNULL(deps.sql_handle, CONVERT(VARBINARY(64),'0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000')) + FROM sys.dm_exec_procedure_stats AS deps + WHERE OBJECT_NAME(deps.object_id, deps.database_id) = @StoredProcName - UNION ALL - SELECT N'@DurationFilter', - N'DECIMAL(38,4)', - N'Excludes queries with an average duration (in seconds) less than @DurationFilter.' + UNION ALL + + SELECT ISNULL(dets.sql_handle, CONVERT(VARBINARY(64),'0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000')) + FROM sys.dm_exec_trigger_stats AS dets + WHERE OBJECT_NAME(dets.object_id, dets.database_id) = @StoredProcName + OPTION (RECOMPILE); - UNION ALL - SELECT N'@HideSummary', - N'BIT', - N'Hides the findings summary result set.' + IF EXISTS (SELECT 1/0 FROM sys.all_objects AS o WHERE o.name = 'dm_exec_function_stats') + BEGIN + SET @function_search_sql = @function_search_sql + N' + SELECT ISNULL(defs.sql_handle, CONVERT(VARBINARY(64),''0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'')) + FROM sys.dm_exec_function_stats AS defs + WHERE OBJECT_NAME(defs.object_id, defs.database_id) = @i_StoredProcName + OPTION (RECOMPILE); + ' + INSERT #only_sql_handles ( sql_handle ) + EXEC sys.sp_executesql @function_search_sql, N'@i_StoredProcName NVARCHAR(128)', @StoredProcName + END + + IF (SELECT COUNT(*) FROM #only_sql_handles) = 0 + BEGIN + RAISERROR(N'No information for that stored procedure was found.', 0, 1) WITH NOWAIT; + RETURN; + END; - UNION ALL - SELECT N'@IgnoreSystemDBs', - N'BIT', - N'Ignores plans found in the system databases (master, model, msdb, tempdb, dbmaintenance, dbadmin, dbatools and resourcedb)' +END; - UNION ALL - SELECT N'@OnlyQueryHashes', - N'VARCHAR(MAX)', - N'A list of query hashes to query. All other query hashes will be ignored. Stored procedures and triggers will be ignored.' - UNION ALL - SELECT N'@IgnoreQueryHashes', - N'VARCHAR(MAX)', - N'A list of query hashes to ignore.' - - UNION ALL - SELECT N'@OnlySqlHandles', - N'VARCHAR(MAX)', - N'One or more sql_handles to use for filtering results.' - - UNION ALL - SELECT N'@IgnoreSqlHandles', - N'VARCHAR(MAX)', - N'One or more sql_handles to ignore.' - UNION ALL - SELECT N'@DatabaseName', - N'NVARCHAR(128)', - N'A database name which is used for filtering results.' +IF ((@OnlyQueryHashes IS NOT NULL AND LEN(@OnlyQueryHashes) > 0) + OR (@IgnoreQueryHashes IS NOT NULL AND LEN(@IgnoreQueryHashes) > 0)) + AND LEFT(@QueryFilter, 3) IN ('pro', 'fun') +BEGIN + RAISERROR('You cannot limit by query hash and filter by stored procedure', 16, 1); + RETURN; +END; - UNION ALL - SELECT N'@StoredProcName', - N'NVARCHAR(128)', - N'Name of stored procedure you want to find plans for.' - - UNION ALL - SELECT N'@SlowlySearchPlansFor', - N'NVARCHAR(4000)', - N'String to search for in plan text. % wildcards allowed.' +/* If the user is attempting to limit by query hash, set up the + #only_query_hashes temp table. This will be used to narrow down + results. - UNION ALL - SELECT N'@BringThePain', - N'BIT', - N'When using @SortOrder = ''all'' and @Top > 10, we require you to set @BringThePain = 1 so you understand that sp_BlitzCache will take a while to run.' + Just a reminder: Using @OnlyQueryHashes will ignore stored + procedures and triggers. + */ +IF @OnlyQueryHashes IS NOT NULL + AND LEN(@OnlyQueryHashes) > 0 +BEGIN + RAISERROR(N'Setting up filter for Query Hashes', 0, 1) WITH NOWAIT; + SET @individual = ''; - UNION ALL - SELECT N'@QueryFilter', - N'VARCHAR(10)', - N'Filter out stored procedures or statements. The default value is ''ALL''. Allowed values are ''procedures'', ''statements'', ''functions'', or ''all'' (any variation in capitalization is acceptable).' + WHILE LEN(@OnlyQueryHashes) > 0 + BEGIN + IF PATINDEX('%,%', @OnlyQueryHashes) > 0 + BEGIN + SET @individual = SUBSTRING(@OnlyQueryHashes, 0, PATINDEX('%,%',@OnlyQueryHashes)) ; + + INSERT INTO #only_query_hashes + SELECT CAST('' AS XML).value('xs:hexBinary( substring(sql:variable("@individual"), sql:column("t.pos")) )', 'varbinary(max)') + FROM (SELECT CASE SUBSTRING(@individual, 1, 2) WHEN '0x' THEN 3 ELSE 0 END) AS t(pos) + OPTION (RECOMPILE) ; + + --SELECT CAST(SUBSTRING(@individual, 1, 2) AS BINARY(8)); - UNION ALL - SELECT N'@Reanalyze', - N'BIT', - N'The default is 0. When set to 0, sp_BlitzCache will re-evalute the plan cache. Set this to 1 to reanalyze existing results' - - UNION ALL - SELECT N'@MinimumExecutionCount', - N'INT', - N'Queries with fewer than this number of executions will be omitted from results.' - - UNION ALL - SELECT N'@Debug', - N'BIT', - N'Setting this to 1 will print dynamic SQL and select data from all tables used.' + SET @OnlyQueryHashes = SUBSTRING(@OnlyQueryHashes, LEN(@individual + ',') + 1, LEN(@OnlyQueryHashes)) ; + END; + ELSE + BEGIN + SET @individual = @OnlyQueryHashes; + SET @OnlyQueryHashes = NULL; - UNION ALL - SELECT N'@MinutesBack', - N'INT', - N'How many minutes back to begin plan cache analysis. If you put in a positive number, we''ll flip it to negative.'; + INSERT INTO #only_query_hashes + SELECT CAST('' AS XML).value('xs:hexBinary( substring(sql:variable("@individual"), sql:column("t.pos")) )', 'varbinary(max)') + FROM (SELECT CASE SUBSTRING(@individual, 1, 2) WHEN '0x' THEN 3 ELSE 0 END) AS t(pos) + OPTION (RECOMPILE) ; + --SELECT CAST(SUBSTRING(@individual, 1, 2) AS VARBINARY(MAX)) ; + END; + END; +END; - /* Column definitions */ - SELECT N'# Executions' AS [Column Name], - N'BIGINT' AS [Data Type], - N'The number of executions of this particular query. This is computed across statements, procedures, and triggers and aggregated by the SQL handle.' AS [Column Description] +/* If the user is setting up a list of query hashes to ignore, those + values will be inserted into #ignore_query_hashes. This is used to + exclude values from query results. - UNION ALL - SELECT N'Executions / Minute', - N'MONEY', - N'Number of executions per minute - calculated for the life of the current plan. Plan life is the last execution time minus the plan creation time.' + Just a reminder: Using @IgnoreQueryHashes will ignore stored + procedures and triggers. + */ +IF @IgnoreQueryHashes IS NOT NULL + AND LEN(@IgnoreQueryHashes) > 0 +BEGIN + RAISERROR(N'Setting up filter to ignore query hashes', 0, 1) WITH NOWAIT; + SET @individual = '' ; - UNION ALL - SELECT N'Execution Weight', - N'MONEY', - N'An arbitrary metric of total "execution-ness". A weight of 2 is "one more" than a weight of 1.' + WHILE LEN(@IgnoreQueryHashes) > 0 + BEGIN + IF PATINDEX('%,%', @IgnoreQueryHashes) > 0 + BEGIN + SET @individual = SUBSTRING(@IgnoreQueryHashes, 0, PATINDEX('%,%',@IgnoreQueryHashes)) ; + + INSERT INTO #ignore_query_hashes + SELECT CAST('' AS XML).value('xs:hexBinary( substring(sql:variable("@individual"), sql:column("t.pos")) )', 'varbinary(max)') + FROM (SELECT CASE SUBSTRING(@individual, 1, 2) WHEN '0x' THEN 3 ELSE 0 END) AS t(pos) + OPTION (RECOMPILE) ; + + SET @IgnoreQueryHashes = SUBSTRING(@IgnoreQueryHashes, LEN(@individual + ',') + 1, LEN(@IgnoreQueryHashes)) ; + END; + ELSE + BEGIN + SET @individual = @IgnoreQueryHashes ; + SET @IgnoreQueryHashes = NULL ; - UNION ALL - SELECT N'Database', - N'sysname', - N'The name of the database where the plan was encountered. If the database name cannot be determined for some reason, a value of NA will be substituted. A value of 32767 indicates the plan comes from ResourceDB.' + INSERT INTO #ignore_query_hashes + SELECT CAST('' AS XML).value('xs:hexBinary( substring(sql:variable("@individual"), sql:column("t.pos")) )', 'varbinary(max)') + FROM (SELECT CASE SUBSTRING(@individual, 1, 2) WHEN '0x' THEN 3 ELSE 0 END) AS t(pos) + OPTION (RECOMPILE) ; + END; + END; +END; - UNION ALL - SELECT N'Total CPU', - N'BIGINT', - N'Total CPU time, reported in milliseconds, that was consumed by all executions of this query since the last compilation.' +IF @ConfigurationDatabaseName IS NOT NULL +BEGIN + RAISERROR(N'Reading values from Configuration Database', 0, 1) WITH NOWAIT; + DECLARE @config_sql NVARCHAR(MAX) = N'INSERT INTO #configuration SELECT parameter_name, value FROM ' + + QUOTENAME(@ConfigurationDatabaseName) + + '.' + QUOTENAME(@ConfigurationSchemaName) + + '.' + QUOTENAME(@ConfigurationTableName) + + ' ; ' ; + EXEC(@config_sql); +END; - UNION ALL - SELECT N'Avg CPU', - N'BIGINT', - N'Average CPU time, reported in milliseconds, consumed by each execution of this query since the last compilation.' +RAISERROR(N'Setting up variables', 0, 1) WITH NOWAIT; +DECLARE @sql NVARCHAR(MAX) = N'', + @insert_list NVARCHAR(MAX) = N'', + @plans_triggers_select_list NVARCHAR(MAX) = N'', + @body NVARCHAR(MAX) = N'', + @body_where NVARCHAR(MAX) = N'WHERE 1 = 1 ' + @nl, + @body_order NVARCHAR(MAX) = N'ORDER BY #sortable# DESC OPTION (RECOMPILE) ', + + @q NVARCHAR(1) = N'''', + @pv VARCHAR(20), + @pos TINYINT, + @v DECIMAL(6,2), + @build INT; - UNION ALL - SELECT N'CPU Weight', - N'MONEY', - N'An arbitrary metric of total "CPU-ness". A weight of 2 is "one more" than a weight of 1.' - UNION ALL - SELECT N'Total Duration', - N'BIGINT', - N'Total elapsed time, reported in milliseconds, consumed by all executions of this query since last compilation.' +RAISERROR (N'Determining SQL Server version.',0,1) WITH NOWAIT; - UNION ALL - SELECT N'Avg Duration', - N'BIGINT', - N'Average elapsed time, reported in milliseconds, consumed by each execution of this query since the last compilation.' +INSERT INTO #checkversion (version) +SELECT CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)) +OPTION (RECOMPILE); - UNION ALL - SELECT N'Duration Weight', - N'MONEY', - N'An arbitrary metric of total "Duration-ness". A weight of 2 is "one more" than a weight of 1.' - UNION ALL - SELECT N'Total Reads', - N'BIGINT', - N'Total logical reads performed by this query since last compilation.' +SELECT @v = common_version , + @build = build +FROM #checkversion +OPTION (RECOMPILE); - UNION ALL - SELECT N'Average Reads', - N'BIGINT', - N'Average logical reads performed by each execution of this query since the last compilation.' +IF (@SortOrder IN ('memory grant', 'avg memory grant')) AND @VersionShowsMemoryGrants = 0 +BEGIN + RAISERROR('Your version of SQL does not support sorting by memory grant or average memory grant. Please use another sort order.', 16, 1); + RETURN; +END; - UNION ALL - SELECT N'Read Weight', - N'MONEY', - N'An arbitrary metric of "Read-ness". A weight of 2 is "one more" than a weight of 1.' +IF (@SortOrder IN ('spills', 'avg spills') AND @VersionShowsSpills = 0) +BEGIN + RAISERROR('Your version of SQL does not support sorting by spills. Please use another sort order.', 16, 1); + RETURN; +END; - UNION ALL - SELECT N'Total Writes', - N'BIGINT', - N'Total logical writes performed by this query since last compilation.' +IF ((LEFT(@QueryFilter, 3) = 'fun') AND (@v < 13)) +BEGIN + RAISERROR('Your version of SQL does not support filtering by functions. Please use another filter.', 16, 1); + RETURN; +END; - UNION ALL - SELECT N'Average Writes', - N'BIGINT', - N'Average logical writes performed by each execution this query since last compilation.' +RAISERROR (N'Creating dynamic SQL based on SQL Server version.',0,1) WITH NOWAIT; - UNION ALL - SELECT N'Write Weight', - N'MONEY', - N'An arbitrary metric of "Write-ness". A weight of 2 is "one more" than a weight of 1.' +SET @insert_list += N' +SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; +INSERT INTO ##BlitzCacheProcs (SPID, QueryType, DatabaseName, AverageCPU, TotalCPU, AverageCPUPerMinute, PercentCPUByType, PercentDurationByType, + PercentReadsByType, PercentExecutionsByType, AverageDuration, TotalDuration, AverageReads, TotalReads, ExecutionCount, + ExecutionsPerMinute, TotalWrites, AverageWrites, PercentWritesByType, WritesPerMinute, PlanCreationTime, + LastExecutionTime, LastCompletionTime, StatementStartOffset, StatementEndOffset, PlanGenerationNum, MinReturnedRows, MaxReturnedRows, AverageReturnedRows, TotalReturnedRows, + LastReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, MaxUsedGrantKB, PercentMemoryGrantUsed, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, + QueryText, QueryPlan, TotalWorkerTimeForType, TotalElapsedTimeForType, TotalReadsForType, + TotalExecutionCountForType, TotalWritesForType, SqlHandle, PlanHandle, QueryHash, QueryPlanHash, + min_worker_time, max_worker_time, is_parallel, min_elapsed_time, max_elapsed_time, age_minutes, age_minutes_lifetime, Pattern) ' ; - UNION ALL - SELECT N'Query Type', - N'NVARCHAR(258)', - N'The type of query being examined. This can be "Procedure", "Statement", or "Trigger".' +SET @body += N' +FROM (SELECT TOP (@Top) x.*, xpa.*, + CAST((CASE WHEN DATEDIFF(mi, cached_time, GETDATE()) > 0 AND execution_count > 1 + THEN DATEDIFF(mi, cached_time, GETDATE()) + ELSE NULL END) as MONEY) as age_minutes, + CAST((CASE WHEN DATEDIFF(mi, cached_time, last_execution_time) > 0 AND execution_count > 1 + THEN DATEDIFF(mi, cached_time, last_execution_time) + ELSE Null END) as MONEY) as age_minutes_lifetime + FROM sys.#view# x + CROSS APPLY (SELECT * FROM sys.dm_exec_plan_attributes(x.plan_handle) AS ixpa + WHERE ixpa.attribute = ''dbid'') AS xpa ' + @nl ; - UNION ALL - SELECT N'Query Text', - N'NVARCHAR(4000)', - N'The text of the query. This may be truncated by either SQL Server or by sp_BlitzCache(tm) for display purposes.' +IF @SortOrder = 'duplicate' /* Issue #3345 */ + BEGIN + SET @body += N' INNER JOIN #duplicate_query_filter AS dqf ON x.sql_handle = dqf.sql_handle AND x.plan_handle = dqf.plan_handle AND x.creation_time = dqf.duplicate_creation_time ' + @nl ; + END - UNION ALL - SELECT N'% Executions (Type)', - N'MONEY', - N'Percent of executions relative to the type of query - e.g. 17.2% of all stored procedure executions.' +IF @VersionShowsAirQuoteActualPlans = 1 + BEGIN + SET @body += N' CROSS APPLY sys.dm_exec_query_plan_stats(x.plan_handle) AS deqps ' + @nl ; + END - UNION ALL - SELECT N'% CPU (Type)', - N'MONEY', - N'Percent of CPU time consumed by this query for a given type of query - e.g. 22% of CPU of all stored procedures executed.' +SET @body += N' WHERE 1 = 1 ' + @nl ; - UNION ALL - SELECT N'% Duration (Type)', - N'MONEY', - N'Percent of elapsed time consumed by this query for a given type of query - e.g. 12% of all statements executed.' + IF EXISTS (SELECT * FROM sys.all_objects o WHERE o.name = 'dm_hadr_database_replica_states') + BEGIN + RAISERROR(N'Ignoring readable secondaries databases by default', 0, 1) WITH NOWAIT; + SET @body += N' AND CAST(xpa.value AS INT) NOT IN (SELECT database_id FROM #ReadableDBs)' + @nl ; + END - UNION ALL - SELECT N'% Reads (Type)', - N'MONEY', - N'Percent of reads consumed by this query for a given type of query - e.g. 34.2% of all stored procedures executed.' +IF @IgnoreSystemDBs = 1 + BEGIN + RAISERROR(N'Ignoring system databases by default', 0, 1) WITH NOWAIT; + SET @body += N' AND COALESCE(LOWER(DB_NAME(CAST(xpa.value AS INT))), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'', ''dbmaintenance'', ''dbadmin'', ''dbatools'') AND COALESCE(DB_NAME(CAST(xpa.value AS INT)), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ; + END; - UNION ALL - SELECT N'% Writes (Type)', - N'MONEY', - N'Percent of writes performed by this query for a given type of query - e.g. 43.2% of all statements executed.' +IF @DatabaseName IS NOT NULL OR @DatabaseName <> N'' + BEGIN + RAISERROR(N'Filtering database name chosen', 0, 1) WITH NOWAIT; + SET @body += N' AND CAST(xpa.value AS BIGINT) = DB_ID(N' + + QUOTENAME(@DatabaseName, N'''') + + N') ' + @nl; + END; - UNION ALL - SELECT N'Total Rows', - N'BIGINT', - N'Total number of rows returned for all executions of this query. This only applies to query level stats, not stored procedures or triggers.' +IF (SELECT COUNT(*) FROM #only_sql_handles) > 0 +BEGIN + RAISERROR(N'Including only chosen SQL Handles', 0, 1) WITH NOWAIT; + SET @body += N' AND EXISTS(SELECT 1/0 FROM #only_sql_handles q WHERE q.sql_handle = x.sql_handle) ' + @nl ; +END; - UNION ALL - SELECT N'Average Rows', - N'MONEY', - N'Average number of rows returned by each execution of the query.' +IF (SELECT COUNT(*) FROM #ignore_sql_handles) > 0 +BEGIN + RAISERROR(N'Including only chosen SQL Handles', 0, 1) WITH NOWAIT; + SET @body += N' AND NOT EXISTS(SELECT 1/0 FROM #ignore_sql_handles q WHERE q.sql_handle = x.sql_handle) ' + @nl ; +END; - UNION ALL - SELECT N'Min Rows', - N'BIGINT', - N'The minimum number of rows returned by any execution of this query.' +IF (SELECT COUNT(*) FROM #only_query_hashes) > 0 + AND (SELECT COUNT(*) FROM #ignore_query_hashes) = 0 + AND (SELECT COUNT(*) FROM #only_sql_handles) = 0 + AND (SELECT COUNT(*) FROM #ignore_sql_handles) = 0 +BEGIN + RAISERROR(N'Including only chosen Query Hashes', 0, 1) WITH NOWAIT; + SET @body += N' AND EXISTS(SELECT 1/0 FROM #only_query_hashes q WHERE q.query_hash = x.query_hash) ' + @nl ; +END; - UNION ALL - SELECT N'Max Rows', - N'BIGINT', - N'The maximum number of rows returned by any execution of this query.' +/* filtering for query hashes */ +IF (SELECT COUNT(*) FROM #ignore_query_hashes) > 0 + AND (SELECT COUNT(*) FROM #only_query_hashes) = 0 +BEGIN + RAISERROR(N'Excluding chosen Query Hashes', 0, 1) WITH NOWAIT; + SET @body += N' AND NOT EXISTS(SELECT 1/0 FROM #ignore_query_hashes iq WHERE iq.query_hash = x.query_hash) ' + @nl ; +END; +/* end filtering for query hashes */ - UNION ALL - SELECT N'MinGrantKB', - N'BIGINT', - N'The minimum memory grant the query received in kb.' +IF @DurationFilter IS NOT NULL + BEGIN + RAISERROR(N'Setting duration filter', 0, 1) WITH NOWAIT; + SET @body += N' AND (total_elapsed_time / 1000.0) / execution_count > @min_duration ' + @nl ; + END; - UNION ALL - SELECT N'MaxGrantKB', - N'BIGINT', - N'The maximum memory grant the query received in kb.' +IF @MinutesBack IS NOT NULL + BEGIN + RAISERROR(N'Setting minutes back filter', 0, 1) WITH NOWAIT; + SET @body += N' AND DATEADD(SECOND, (x.last_elapsed_time / 1000000.), x.last_execution_time) >= DATEADD(MINUTE, @min_back, GETDATE()) ' + @nl ; + END; - UNION ALL - SELECT N'MinUsedGrantKB', - N'BIGINT', - N'The minimum used memory grant the query received in kb.' +IF @SlowlySearchPlansFor IS NOT NULL + BEGIN + RAISERROR(N'Setting string search for @SlowlySearchPlansFor, so remember, this is gonna be slow', 0, 1) WITH NOWAIT; + SET @SlowlySearchPlansFor = REPLACE((REPLACE((REPLACE((REPLACE(@SlowlySearchPlansFor, N'[', N'_')), N']', N'_')), N'^', N'_')), N'''', N''''''); + SET @body_where += N' AND CAST(qp.query_plan AS NVARCHAR(MAX)) LIKE N''%' + @SlowlySearchPlansFor + N'%'' ' + @nl; + END - UNION ALL - SELECT N'MaxUsedGrantKB', - N'BIGINT', - N'The maximum used memory grant the query received in kb.' - UNION ALL - SELECT N'MinSpills', - N'BIGINT', - N'The minimum amount this query has spilled to tempdb in 8k pages.' +/* Apply the sort order here to only grab relevant plans. + This should make it faster to process since we'll be pulling back fewer + plans for processing. + */ +RAISERROR(N'Applying chosen sort order', 0, 1) WITH NOWAIT; +SELECT @body += N' ORDER BY ' + + CASE @SortOrder WHEN N'cpu' THEN N'total_worker_time' + WHEN N'reads' THEN N'total_logical_reads' + WHEN N'writes' THEN N'total_logical_writes' + WHEN N'duration' THEN N'total_elapsed_time' + WHEN N'executions' THEN N'execution_count' + WHEN N'compiles' THEN N'cached_time' + WHEN N'memory grant' THEN N'max_grant_kb' + WHEN N'unused grant' THEN N'max_grant_kb - max_used_grant_kb' + WHEN N'spills' THEN N'max_spills' + WHEN N'duplicate' THEN N'total_worker_time' /* Issue #3345 */ + /* And now the averages */ + WHEN N'avg cpu' THEN N'total_worker_time / execution_count' + WHEN N'avg reads' THEN N'total_logical_reads / execution_count' + WHEN N'avg writes' THEN N'total_logical_writes / execution_count' + WHEN N'avg duration' THEN N'total_elapsed_time / execution_count' + WHEN N'avg memory grant' THEN N'CASE WHEN max_grant_kb = 0 THEN 0 ELSE max_grant_kb / execution_count END' + WHEN N'avg spills' THEN N'CASE WHEN total_spills = 0 THEN 0 ELSE total_spills / execution_count END' + WHEN N'avg executions' THEN 'CASE WHEN execution_count = 0 THEN 0 + WHEN COALESCE(CAST((CASE WHEN DATEDIFF(mi, cached_time, GETDATE()) > 0 AND execution_count > 1 + THEN DATEDIFF(mi, cached_time, GETDATE()) + ELSE NULL END) as MONEY), CAST((CASE WHEN DATEDIFF(mi, cached_time, last_execution_time) > 0 AND execution_count > 1 + THEN DATEDIFF(mi, cached_time, last_execution_time) + ELSE Null END) as MONEY), 0) = 0 THEN 0 + ELSE CAST((1.00 * execution_count / COALESCE(CAST((CASE WHEN DATEDIFF(mi, cached_time, GETDATE()) > 0 AND execution_count > 1 + THEN DATEDIFF(mi, cached_time, GETDATE()) + ELSE NULL END) as MONEY), CAST((CASE WHEN DATEDIFF(mi, cached_time, last_execution_time) > 0 AND execution_count > 1 + THEN DATEDIFF(mi, cached_time, last_execution_time) + ELSE Null END) as MONEY))) AS money) + END ' + END + N' DESC ' + @nl ; - UNION ALL - SELECT N'MaxSpills', - N'BIGINT', - N'The maximum amount this query has spilled to tempdb in 8k pages.' - UNION ALL - SELECT N'TotalSpills', - N'BIGINT', - N'The total amount this query has spilled to tempdb in 8k pages.' + +SET @body += N') AS qs + CROSS JOIN(SELECT SUM(execution_count) AS t_TotalExecs, + SUM(CAST(total_elapsed_time AS BIGINT) / 1000.0) AS t_TotalElapsed, + SUM(CAST(total_worker_time AS BIGINT) / 1000.0) AS t_TotalWorker, + SUM(CAST(total_logical_reads AS DECIMAL(30))) AS t_TotalReads, + SUM(CAST(total_logical_writes AS DECIMAL(30))) AS t_TotalWrites + FROM sys.#view#) AS t + CROSS APPLY sys.dm_exec_plan_attributes(qs.plan_handle) AS pa + CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) AS st + CROSS APPLY sys.dm_exec_query_plan(qs.plan_handle) AS qp ' + @nl ; - UNION ALL - SELECT N'AvgSpills', - N'BIGINT', - N'The average amount this query has spilled to tempdb in 8k pages.' +IF @VersionShowsAirQuoteActualPlans = 1 + BEGIN + SET @body += N' CROSS APPLY sys.dm_exec_query_plan_stats(qs.plan_handle) AS deqps ' + @nl ; + END - UNION ALL - SELECT N'PercentMemoryGrantUsed', - N'MONEY', - N'Result of dividing the maximum grant used by the minimum granted.' +SET @body_where += N' AND pa.attribute = ' + QUOTENAME('dbid', @q ) + @nl ; - UNION ALL - SELECT N'AvgMaxMemoryGrant', - N'MONEY', - N'The average maximum memory grant for a query.' +IF @NoobSaibot = 1 +BEGIN + SET @body_where += N' AND qp.query_plan.exist(''declare namespace p="http://schemas.microsoft.com/sqlserver/2004/07/showplan";//p:StmtSimple//p:MissingIndex'') = 1' + @nl ; +END - UNION ALL - SELECT N'# Plans', - N'INT', - N'The total number of execution plans found that match a given query.' +SET @plans_triggers_select_list += N' +SELECT TOP (@Top) + @@SPID , + ''Procedure or Function: '' + + QUOTENAME(COALESCE(OBJECT_SCHEMA_NAME(qs.object_id, qs.database_id),'''')) + + ''.'' + + QUOTENAME(COALESCE(OBJECT_NAME(qs.object_id, qs.database_id),'''')) AS QueryType, + COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), N''-- N/A --'') AS DatabaseName, + (total_worker_time / 1000.0) / execution_count AS AvgCPU , + (total_worker_time / 1000.0) AS TotalCPU , + CASE WHEN total_worker_time = 0 THEN 0 + WHEN COALESCE(age_minutes, DATEDIFF(mi, qs.cached_time, qs.last_execution_time), 0) = 0 THEN 0 + ELSE CAST((total_worker_time / 1000.0) / COALESCE(age_minutes, DATEDIFF(mi, qs.cached_time, qs.last_execution_time)) AS MONEY) + END AS AverageCPUPerMinute , + CASE WHEN t.t_TotalWorker = 0 THEN 0 + ELSE CAST(ROUND(100.00 * (total_worker_time / 1000.0) / t.t_TotalWorker, 2) AS MONEY) + END AS PercentCPUByType, + CASE WHEN t.t_TotalElapsed = 0 THEN 0 + ELSE CAST(ROUND(100.00 * (total_elapsed_time / 1000.0) / t.t_TotalElapsed, 2) AS MONEY) + END AS PercentDurationByType, + CASE WHEN t.t_TotalReads = 0 THEN 0 + ELSE CAST(ROUND(100.00 * total_logical_reads / t.t_TotalReads, 2) AS MONEY) + END AS PercentReadsByType, + CASE WHEN t.t_TotalExecs = 0 THEN 0 + ELSE CAST(ROUND(100.00 * execution_count / t.t_TotalExecs, 2) AS MONEY) + END AS PercentExecutionsByType, + (total_elapsed_time / 1000.0) / execution_count AS AvgDuration , + (total_elapsed_time / 1000.0) AS TotalDuration , + total_logical_reads / execution_count AS AvgReads , + total_logical_reads AS TotalReads , + execution_count AS ExecutionCount , + CASE WHEN execution_count = 0 THEN 0 + WHEN COALESCE(age_minutes, DATEDIFF(mi, qs.cached_time, qs.last_execution_time), 0) = 0 THEN 0 + ELSE CAST((1.00 * execution_count / COALESCE(age_minutes, DATEDIFF(mi, qs.cached_time, qs.last_execution_time))) AS money) + END AS ExecutionsPerMinute , + total_logical_writes AS TotalWrites , + total_logical_writes / execution_count AS AverageWrites , + CASE WHEN t.t_TotalWrites = 0 THEN 0 + ELSE CAST(ROUND(100.00 * total_logical_writes / t.t_TotalWrites, 2) AS MONEY) + END AS PercentWritesByType, + CASE WHEN total_logical_writes = 0 THEN 0 + WHEN COALESCE(age_minutes, DATEDIFF(mi, qs.cached_time, qs.last_execution_time), 0) = 0 THEN 0 + ELSE CAST((1.00 * total_logical_writes / COALESCE(age_minutes, DATEDIFF(mi, qs.cached_time, qs.last_execution_time), 0)) AS money) + END AS WritesPerMinute, + qs.cached_time AS PlanCreationTime, + qs.last_execution_time AS LastExecutionTime, + DATEADD(SECOND, (qs.last_elapsed_time / 1000000.), qs.last_execution_time) AS LastCompletionTime, + NULL AS StatementStartOffset, + NULL AS StatementEndOffset, + NULL AS PlanGenerationNum, + NULL AS MinReturnedRows, + NULL AS MaxReturnedRows, + NULL AS AvgReturnedRows, + NULL AS TotalReturnedRows, + NULL AS LastReturnedRows, + NULL AS MinGrantKB, + NULL AS MaxGrantKB, + NULL AS MinUsedGrantKB, + NULL AS MaxUsedGrantKB, + NULL AS PercentMemoryGrantUsed, + NULL AS AvgMaxMemoryGrant,'; - UNION ALL - SELECT N'# Distinct Plans', - N'INT', - N'The number of distinct execution plans that match a given query. ' - + NCHAR(13) + NCHAR(10) - + N'This may be caused by running the same query across multiple databases or because of a lack of proper parameterization in the database.' - - UNION ALL - SELECT N'Created At', - N'DATETIME', - N'Time that the execution plan was last compiled.' - - UNION ALL - SELECT N'Last Execution', - N'DATETIME', - N'The last time that this query was executed.' + IF @VersionShowsSpills = 1 + BEGIN + RAISERROR(N'Getting spill information for newer versions of SQL', 0, 1) WITH NOWAIT; + SET @plans_triggers_select_list += N' + min_spills AS MinSpills, + max_spills AS MaxSpills, + total_spills AS TotalSpills, + CAST(ISNULL(NULLIF(( total_spills * 1. ), 0) / NULLIF(execution_count, 0), 0) AS MONEY) AS AvgSpills, '; + END; + ELSE + BEGIN + RAISERROR(N'Substituting NULLs for spill columns in older versions of SQL', 0, 1) WITH NOWAIT; + SET @plans_triggers_select_list += N' + NULL AS MinSpills, + NULL AS MaxSpills, + NULL AS TotalSpills, + NULL AS AvgSpills, ' ; + END; + + SET @plans_triggers_select_list += + N'st.text AS QueryText ,'; - UNION ALL - SELECT N'Query Plan', - N'XML', - N'The query plan. Click to display a graphical plan or, if you need to patch SSMS, a pile of XML.' + IF @VersionShowsAirQuoteActualPlans = 1 + BEGIN + SET @plans_triggers_select_list += N' CASE WHEN DATALENGTH(COALESCE(deqps.query_plan,'''')) > DATALENGTH(COALESCE(qp.query_plan,'''')) THEN deqps.query_plan ELSE qp.query_plan END AS QueryPlan, ' + @nl ; + END; + ELSE + BEGIN + SET @plans_triggers_select_list += N' qp.query_plan AS QueryPlan, ' + @nl ; + END; - UNION ALL - SELECT N'Plan Handle', - N'VARBINARY(64)', - N'An arbitrary identifier referring to the compiled plan this query is a part of.' + SET @plans_triggers_select_list += + N't.t_TotalWorker, + t.t_TotalElapsed, + t.t_TotalReads, + t.t_TotalExecs, + t.t_TotalWrites, + qs.sql_handle AS SqlHandle, + qs.plan_handle AS PlanHandle, + NULL AS QueryHash, + NULL AS QueryPlanHash, + qs.min_worker_time / 1000.0, + qs.max_worker_time / 1000.0, + CASE WHEN qp.query_plan.value(''declare namespace p="http://schemas.microsoft.com/sqlserver/2004/07/showplan";max(//p:RelOp/@Parallel)'', ''float'') > 0 THEN 1 ELSE 0 END, + qs.min_elapsed_time / 1000.0, + qs.max_elapsed_time / 1000.0, + age_minutes, + age_minutes_lifetime, + @SortOrder '; - UNION ALL - SELECT N'SQL Handle', - N'VARBINARY(64)', - N'An arbitrary identifier referring to a batch or stored procedure that this query is a part of.' - UNION ALL - SELECT N'Query Hash', - N'BINARY(8)', - N'A hash of the query. Queries with the same query hash have similar logic but only differ by literal values or database.' +IF LEFT(@QueryFilter, 3) IN ('all', 'sta') +BEGIN + SET @sql += @insert_list; + + SET @sql += N' + SELECT TOP (@Top) + @@SPID , + ''Statement'' AS QueryType, + COALESCE(DB_NAME(CAST(pa.value AS INT)), N''-- N/A --'') AS DatabaseName, + (total_worker_time / 1000.0) / execution_count AS AvgCPU , + (total_worker_time / 1000.0) AS TotalCPU , + CASE WHEN total_worker_time = 0 THEN 0 + WHEN COALESCE(age_minutes, DATEDIFF(mi, qs.creation_time, qs.last_execution_time), 0) = 0 THEN 0 + ELSE CAST((total_worker_time / 1000.0) / COALESCE(age_minutes, DATEDIFF(mi, qs.creation_time, qs.last_execution_time)) AS MONEY) + END AS AverageCPUPerMinute , + CASE WHEN t.t_TotalWorker = 0 THEN 0 + ELSE CAST(ROUND(100.00 * total_worker_time / t.t_TotalWorker, 2) AS MONEY) + END AS PercentCPUByType, + CASE WHEN t.t_TotalElapsed = 0 THEN 0 + ELSE CAST(ROUND(100.00 * total_elapsed_time / t.t_TotalElapsed, 2) AS MONEY) + END AS PercentDurationByType, + CASE WHEN t.t_TotalReads = 0 THEN 0 + ELSE CAST(ROUND(100.00 * total_logical_reads / t.t_TotalReads, 2) AS MONEY) + END AS PercentReadsByType, + CAST(ROUND(100.00 * execution_count / t.t_TotalExecs, 2) AS MONEY) AS PercentExecutionsByType, + (total_elapsed_time / 1000.0) / execution_count AS AvgDuration , + (total_elapsed_time / 1000.0) AS TotalDuration , + total_logical_reads / execution_count AS AvgReads , + total_logical_reads AS TotalReads , + execution_count AS ExecutionCount , + CASE WHEN execution_count = 0 THEN 0 + WHEN COALESCE(age_minutes, DATEDIFF(mi, qs.creation_time, qs.last_execution_time), 0) = 0 THEN 0 + ELSE CAST((1.00 * execution_count / COALESCE(age_minutes, DATEDIFF(mi, qs.creation_time, qs.last_execution_time))) AS money) + END AS ExecutionsPerMinute , + total_logical_writes AS TotalWrites , + total_logical_writes / execution_count AS AverageWrites , + CASE WHEN t.t_TotalWrites = 0 THEN 0 + ELSE CAST(ROUND(100.00 * total_logical_writes / t.t_TotalWrites, 2) AS MONEY) + END AS PercentWritesByType, + CASE WHEN total_logical_writes = 0 THEN 0 + WHEN COALESCE(age_minutes, DATEDIFF(mi, qs.creation_time, qs.last_execution_time), 0) = 0 THEN 0 + ELSE CAST((1.00 * total_logical_writes / COALESCE(age_minutes, DATEDIFF(mi, qs.creation_time, qs.last_execution_time), 0)) AS money) + END AS WritesPerMinute, + qs.creation_time AS PlanCreationTime, + qs.last_execution_time AS LastExecutionTime, + DATEADD(SECOND, (qs.last_elapsed_time / 1000000.), qs.last_execution_time) AS LastCompletionTime, + qs.statement_start_offset AS StatementStartOffset, + qs.statement_end_offset AS StatementEndOffset, + qs.plan_generation_num AS PlanGenerationNum, '; + + IF (@v >= 11) OR (@v >= 10.5 AND @build >= 2500) + BEGIN + RAISERROR(N'Adding additional info columns for newer versions of SQL', 0, 1) WITH NOWAIT; + SET @sql += N' + qs.min_rows AS MinReturnedRows, + qs.max_rows AS MaxReturnedRows, + CAST(qs.total_rows as MONEY) / execution_count AS AvgReturnedRows, + qs.total_rows AS TotalReturnedRows, + qs.last_rows AS LastReturnedRows, ' ; + END; + ELSE + BEGIN + RAISERROR(N'Substituting NULLs for more info columns in older versions of SQL', 0, 1) WITH NOWAIT; + SET @sql += N' + NULL AS MinReturnedRows, + NULL AS MaxReturnedRows, + NULL AS AvgReturnedRows, + NULL AS TotalReturnedRows, + NULL AS LastReturnedRows, ' ; + END; - UNION ALL - SELECT N'Warnings', - N'VARCHAR(MAX)', - N'A list of individual warnings generated by this query.' ; + IF @VersionShowsMemoryGrants = 1 + BEGIN + RAISERROR(N'Getting memory grant information for newer versions of SQL', 0, 1) WITH NOWAIT; + SET @sql += N' + min_grant_kb AS MinGrantKB, + max_grant_kb AS MaxGrantKB, + min_used_grant_kb AS MinUsedGrantKB, + max_used_grant_kb AS MaxUsedGrantKB, + CAST(ISNULL(NULLIF(( total_used_grant_kb * 1.00 ), 0) / NULLIF(total_grant_kb, 0), 0) * 100. AS MONEY) AS PercentMemoryGrantUsed, + CAST(ISNULL(NULLIF(( total_grant_kb * 1. ), 0) / NULLIF(execution_count, 0), 0) AS MONEY) AS AvgMaxMemoryGrant, '; + END; + ELSE + BEGIN + RAISERROR(N'Substituting NULLs for memory grant columns in older versions of SQL', 0, 1) WITH NOWAIT; + SET @sql += N' + NULL AS MinGrantKB, + NULL AS MaxGrantKB, + NULL AS MinUsedGrantKB, + NULL AS MaxUsedGrantKB, + NULL AS PercentMemoryGrantUsed, + NULL AS AvgMaxMemoryGrant, ' ; + END; + IF @VersionShowsSpills = 1 + BEGIN + RAISERROR(N'Getting spill information for newer versions of SQL', 0, 1) WITH NOWAIT; + SET @sql += N' + min_spills AS MinSpills, + max_spills AS MaxSpills, + total_spills AS TotalSpills, + CAST(ISNULL(NULLIF(( total_spills * 1. ), 0) / NULLIF(execution_count, 0), 0) AS MONEY) AS AvgSpills,'; + END; + ELSE + BEGIN + RAISERROR(N'Substituting NULLs for spill columns in older versions of SQL', 0, 1) WITH NOWAIT; + SET @sql += N' + NULL AS MinSpills, + NULL AS MaxSpills, + NULL AS TotalSpills, + NULL AS AvgSpills, ' ; + END; + + SET @sql += N' + SUBSTRING(st.text, ( qs.statement_start_offset / 2 ) + 1, ( ( CASE qs.statement_end_offset + WHEN -1 THEN DATALENGTH(st.text) + ELSE qs.statement_end_offset + END - qs.statement_start_offset ) / 2 ) + 1) AS QueryText , ' + @nl ; - - /* Configuration table description */ - SELECT N'Frequent Execution Threshold' AS [Configuration Parameter] , - N'100' AS [Default Value] , - N'Executions / Minute' AS [Unit of Measure] , - N'Executions / Minute before a "Frequent Execution Threshold" warning is triggered.' AS [Description] - UNION ALL - SELECT N'Parameter Sniffing Variance Percent' , - N'30' , - N'Percent' , - N'Variance required between min/max values and average values before a "Parameter Sniffing" warning is triggered. Applies to worker time and returned rows.' + IF @VersionShowsAirQuoteActualPlans = 1 + BEGIN + SET @sql += N' CASE WHEN DATALENGTH(COALESCE(deqps.query_plan,'''')) > DATALENGTH(COALESCE(qp.query_plan,'''')) THEN deqps.query_plan ELSE qp.query_plan END AS QueryPlan, ' + @nl ; + END + ELSE + BEGIN + SET @sql += N' query_plan AS QueryPlan, ' + @nl ; + END - UNION ALL - SELECT N'Parameter Sniffing IO Threshold' , - N'100,000' , - N'Logical reads' , - N'Minimum number of average logical reads before parameter sniffing checks are evaluated.' + SET @sql += N' + t.t_TotalWorker, + t.t_TotalElapsed, + t.t_TotalReads, + t.t_TotalExecs, + t.t_TotalWrites, + qs.sql_handle AS SqlHandle, + qs.plan_handle AS PlanHandle, + qs.query_hash AS QueryHash, + qs.query_plan_hash AS QueryPlanHash, + qs.min_worker_time / 1000.0, + qs.max_worker_time / 1000.0, + CASE WHEN qp.query_plan.value(''declare namespace p="http://schemas.microsoft.com/sqlserver/2004/07/showplan";max(//p:RelOp/@Parallel)'', ''float'') > 0 THEN 1 ELSE 0 END, + qs.min_elapsed_time / 1000.0, + qs.max_worker_time / 1000.0, + age_minutes, + age_minutes_lifetime, + @SortOrder '; + + SET @sql += REPLACE(REPLACE(@body, '#view#', 'dm_exec_query_stats'), 'cached_time', 'creation_time') ; - UNION ALL - SELECT N'Cost Threshold for Parallelism Warning' AS [Configuration Parameter] , - N'10' , - N'Percent' , - N'Trigger a "Nearly Parallel" warning when a query''s cost is within X percent of the cost threshold for parallelism.' + SET @sort_filter += CASE @SortOrder WHEN N'cpu' THEN N'AND total_worker_time > 0' + WHEN N'reads' THEN N'AND total_logical_reads > 0' + WHEN N'writes' THEN N'AND total_logical_writes > 0' + WHEN N'duration' THEN N'AND total_elapsed_time > 0' + WHEN N'executions' THEN N'AND execution_count > 0' + /* WHEN N'compiles' THEN N'AND (age_minutes + age_minutes_lifetime) > 0' BGO 2021-01-24 commenting out for https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2772 */ + WHEN N'memory grant' THEN N'AND max_grant_kb > 0' + WHEN N'unused grant' THEN N'AND max_grant_kb > 0' + WHEN N'spills' THEN N'AND max_spills > 0' + /* And now the averages */ + WHEN N'avg cpu' THEN N'AND (total_worker_time / execution_count) > 0' + WHEN N'avg reads' THEN N'AND (total_logical_reads / execution_count) > 0' + WHEN N'avg writes' THEN N'AND (total_logical_writes / execution_count) > 0' + WHEN N'avg duration' THEN N'AND (total_elapsed_time / execution_count) > 0' + WHEN N'avg memory grant' THEN N'AND CASE WHEN max_grant_kb = 0 THEN 0 ELSE (max_grant_kb / execution_count) END > 0' + WHEN N'avg spills' THEN N'AND CASE WHEN total_spills = 0 THEN 0 ELSE (total_spills / execution_count) END > 0' + WHEN N'avg executions' THEN N'AND CASE WHEN execution_count = 0 THEN 0 + WHEN COALESCE(age_minutes, age_minutes_lifetime, 0) = 0 THEN 0 + ELSE CAST((1.00 * execution_count / COALESCE(age_minutes, age_minutes_lifetime)) AS money) + END > 0' + ELSE N' /* No minimum threshold set */ ' + END; - UNION ALL - SELECT N'Long Running Query Warning' AS [Configuration Parameter] , - N'300' , - N'Seconds' , - N'Triggers a "Long Running Query Warning" when average duration, max CPU time, or max clock time is higher than this number.' + SET @sql += REPLACE(@body_where, 'cached_time', 'creation_time') ; - UNION ALL - SELECT N'Unused Memory Grant Warning' AS [Configuration Parameter] , - N'10' , - N'Percent' , - N'Triggers an "Unused Memory Grant Warning" when a query uses >= X percent of its memory grant.'; - RETURN; - END; /* IF @Help = 1 */ + SET @sql += @sort_filter + @nl; + + SET @sql += @body_order + @nl + @nl + @nl; + IF @SortOrder = 'compiles' + BEGIN + RAISERROR(N'Sorting by compiles', 0, 1) WITH NOWAIT; + SET @sql = REPLACE(@sql, '#sortable#', 'creation_time'); + END; +END; -/*Validate version*/ -IF ( -SELECT - CASE - WHEN CONVERT(NVARCHAR(128), SERVERPROPERTY ('PRODUCTVERSION')) LIKE '8%' THEN 0 - WHEN CONVERT(NVARCHAR(128), SERVERPROPERTY ('PRODUCTVERSION')) LIKE '9%' THEN 0 - ELSE 1 - END -) = 0 +IF (@QueryFilter = 'all' + AND (SELECT COUNT(*) FROM #only_query_hashes) = 0 + AND (SELECT COUNT(*) FROM #ignore_query_hashes) = 0) + AND (@SortOrder NOT IN ('memory grant', 'avg memory grant', 'unused grant', 'duplicate')) /* Issue #3345 added 'duplicate' */ + OR (LEFT(@QueryFilter, 3) = 'pro') BEGIN - DECLARE @version_msg VARCHAR(8000); - SELECT @version_msg = 'Sorry, sp_BlitzCache doesn''t work on versions of SQL prior to 2008.' + REPLICATE(CHAR(13), 7933); - PRINT @version_msg; - RETURN; -END; + SET @sql += @insert_list; + SET @sql += REPLACE(@plans_triggers_select_list, '#query_type#', 'Stored Procedure') ; -IF(@OutputType = 'NONE' AND (@OutputTableName IS NULL OR @OutputSchemaName IS NULL OR @OutputDatabaseName IS NULL)) -BEGIN - RAISERROR('This procedure should be called with a value for all @Output* parameters, as @OutputType is set to NONE',12,1); - RETURN; -END; + SET @sql += REPLACE(@body, '#view#', 'dm_exec_procedure_stats') ; + SET @sql += @body_where ; -IF(@OutputType = 'NONE') -BEGIN - SET @HideSummary = 1; -END; + IF @IgnoreSystemDBs = 1 + SET @sql += N' AND COALESCE(LOWER(DB_NAME(database_id)), LOWER(CAST(pa.value AS sysname)), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'', ''dbmaintenance'', ''dbadmin'', ''dbatools'') AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ; + SET @sql += @sort_filter + @nl; -/* Lets get @SortOrder set to lower case here for comparisons later */ -SET @SortOrder = LOWER(@SortOrder); + SET @sql += @body_order + @nl + @nl + @nl ; +END; -/* Set @Top based on sort */ -IF ( - @Top IS NULL - AND @SortOrder IN ( 'all', 'all sort' ) - ) - BEGIN - SET @Top = 5; - END; +IF (@v >= 13 + AND @QueryFilter = 'all' + AND (SELECT COUNT(*) FROM #only_query_hashes) = 0 + AND (SELECT COUNT(*) FROM #ignore_query_hashes) = 0) + AND (@SortOrder NOT IN ('memory grant', 'avg memory grant', 'unused grant', 'duplicate')) /* Issue #3345 added 'duplicate' */ + AND (@SortOrder NOT IN ('spills', 'avg spills')) + OR (LEFT(@QueryFilter, 3) = 'fun') +BEGIN + SET @sql += @insert_list; + SET @sql += REPLACE(REPLACE(@plans_triggers_select_list, '#query_type#', 'Function') + , N' + min_spills AS MinSpills, + max_spills AS MaxSpills, + total_spills AS TotalSpills, + CAST(ISNULL(NULLIF(( total_spills * 1. ), 0) / NULLIF(execution_count, 0), 0) AS MONEY) AS AvgSpills, ', + N' + NULL AS MinSpills, + NULL AS MaxSpills, + NULL AS TotalSpills, + NULL AS AvgSpills, ') ; -IF ( - @Top IS NULL - AND @SortOrder NOT IN ( 'all', 'all sort' ) - ) - BEGIN - SET @Top = 10; - END; + SET @sql += REPLACE(@body, '#view#', 'dm_exec_function_stats') ; + SET @sql += @body_where ; + IF @IgnoreSystemDBs = 1 + SET @sql += N' AND COALESCE(LOWER(DB_NAME(database_id)), LOWER(CAST(pa.value AS sysname)), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'', ''dbmaintenance'', ''dbadmin'', ''dbatools'') AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ; -/* If they want to sort by query hash, populate the @OnlyQueryHashes list for them */ -IF @SortOrder LIKE 'query hash%' - BEGIN - RAISERROR('Beginning query hash sort', 0, 1) WITH NOWAIT; + SET @sql += @sort_filter + @nl; - SELECT TOP(@Top) qs.query_hash, - MAX(qs.max_worker_time) AS max_worker_time, - COUNT_BIG(*) AS records - INTO #query_hash_grouped - FROM sys.dm_exec_query_stats AS qs - CROSS APPLY ( SELECT pa.value - FROM sys.dm_exec_plan_attributes(qs.plan_handle) AS pa - WHERE pa.attribute = 'dbid' ) AS ca - GROUP BY qs.query_hash, ca.value - HAVING COUNT_BIG(*) > 1 - ORDER BY max_worker_time DESC, - records DESC; - - SELECT TOP (1) - @OnlyQueryHashes = STUFF((SELECT DISTINCT N',' + CONVERT(NVARCHAR(MAX), qhg.query_hash, 1) - FROM #query_hash_grouped AS qhg - WHERE qhg.query_hash <> 0x00 - FOR XML PATH(N''), TYPE).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 1, N'') - OPTION(RECOMPILE); + SET @sql += @body_order + @nl + @nl + @nl ; +END; - /* When they ran it, @SortOrder probably looked like 'query hash, cpu', so strip the first sort order out: */ - SELECT @SortOrder = LTRIM(REPLACE(REPLACE(@SortOrder,'query hash', ''), ',', '')); - - /* If they just called it with @SortOrder = 'query hash', set it to 'cpu' for backwards compatibility: */ - IF @SortOrder = '' SET @SortOrder = 'cpu'; +/******************************************************************************* + * + * Because the trigger execution count in SQL Server 2008R2 and earlier is not + * correct, we ignore triggers for these versions of SQL Server. If you'd like + * to include trigger numbers, just know that the ExecutionCount, + * PercentExecutions, and ExecutionsPerMinute are wildly inaccurate for + * triggers on these versions of SQL Server. + * + * This is why we can't have nice things. + * + ******************************************************************************/ +IF (@UseTriggersAnyway = 1 OR @v >= 11) + AND (SELECT COUNT(*) FROM #only_query_hashes) = 0 + AND (SELECT COUNT(*) FROM #ignore_query_hashes) = 0 + AND (@QueryFilter = 'all') + AND (@SortOrder NOT IN ('memory grant', 'avg memory grant', 'unused grant', 'duplicate')) /* Issue #3345 added 'duplicate' */ +BEGIN + RAISERROR (N'Adding SQL to collect trigger stats.',0,1) WITH NOWAIT; - END + /* Trigger level information from the plan cache */ + SET @sql += @insert_list ; + SET @sql += REPLACE(@plans_triggers_select_list, '#query_type#', 'Trigger') ; -/* If they want to sort by duplicate, create a table with the worst offenders - issue #3345 */ -IF @SortOrder LIKE 'duplicate%' - BEGIN - RAISERROR('Beginning duplicate query hash sort', 0, 1) WITH NOWAIT; + SET @sql += REPLACE(@body, '#view#', 'dm_exec_trigger_stats') ; - /* Find the query hashes that are the most duplicated */ - WITH MostCommonQueries AS ( - SELECT TOP(@Top) qs.query_hash, - COUNT_BIG(*) AS plans - FROM sys.dm_exec_query_stats AS qs - GROUP BY qs.query_hash - HAVING COUNT_BIG(*) > 100 - ORDER BY COUNT_BIG(*) DESC - ) - SELECT mcq_recent.sql_handle, mcq_recent.plan_handle, mcq_recent.creation_time AS duplicate_creation_time, mcq.plans - INTO #duplicate_query_filter - FROM MostCommonQueries mcq - CROSS APPLY ( SELECT TOP 1 qs.sql_handle, qs.plan_handle, qs.creation_time - FROM sys.dm_exec_query_stats qs - WHERE qs.query_hash = mcq.query_hash - ORDER BY qs.creation_time DESC) AS mcq_recent - OPTION (RECOMPILE); + SET @sql += @body_where ; - SET @MinimumExecutionCount = 0; - END + IF @IgnoreSystemDBs = 1 + SET @sql += N' AND COALESCE(LOWER(DB_NAME(database_id)), LOWER(CAST(pa.value AS sysname)), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'', ''dbmaintenance'', ''dbadmin'', ''dbatools'') AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ; + SET @sql += @sort_filter + @nl; -/* validate user inputs */ -IF @Top IS NULL - OR @SortOrder IS NULL - OR @QueryFilter IS NULL - OR @Reanalyze IS NULL -BEGIN - RAISERROR(N'Several parameters (@Top, @SortOrder, @QueryFilter, @renalyze) are required. Do not set them to NULL. Please try again.', 16, 1) WITH NOWAIT; - RETURN; + SET @sql += @body_order + @nl + @nl + @nl ; END; -RAISERROR(N'Checking @MinutesBack validity.', 0, 1) WITH NOWAIT; -IF @MinutesBack IS NOT NULL - BEGIN - IF @MinutesBack > 0 - BEGIN - RAISERROR(N'Setting @MinutesBack to a negative number', 0, 1) WITH NOWAIT; - SET @MinutesBack *=-1; - END; - IF @MinutesBack = 0 - BEGIN - RAISERROR(N'@MinutesBack can''t be 0, setting to -1', 0, 1) WITH NOWAIT; - SET @MinutesBack = -1; - END; - END; - -DECLARE @DurationFilter_i INT, - @MinMemoryPerQuery INT, - @msg NVARCHAR(4000), - @NoobSaibot BIT = 0, - @VersionShowsAirQuoteActualPlans BIT, - @ObjectFullName NVARCHAR(2000), - @user_perm_sql NVARCHAR(MAX) = N'', - @user_perm_gb_out DECIMAL(10,2), - @common_version DECIMAL(10,2), - @buffer_pool_memory_gb DECIMAL(10,2), - @user_perm_percent DECIMAL(10,2), - @is_tokenstore_big BIT = 0, - @sort NVARCHAR(MAX) = N'', - @sort_filter NVARCHAR(MAX) = N''; +SELECT @sort = CASE @SortOrder WHEN N'cpu' THEN N'total_worker_time' + WHEN N'reads' THEN N'total_logical_reads' + WHEN N'writes' THEN N'total_logical_writes' + WHEN N'duration' THEN N'total_elapsed_time' + WHEN N'executions' THEN N'execution_count' + WHEN N'compiles' THEN N'cached_time' + WHEN N'memory grant' THEN N'max_grant_kb' + WHEN N'unused grant' THEN N'max_grant_kb - max_used_grant_kb' + WHEN N'spills' THEN N'max_spills' + WHEN N'duplicate' THEN N'total_worker_time' /* Issue #3345 */ + /* And now the averages */ + WHEN N'avg cpu' THEN N'total_worker_time / execution_count' + WHEN N'avg reads' THEN N'total_logical_reads / execution_count' + WHEN N'avg writes' THEN N'total_logical_writes / execution_count' + WHEN N'avg duration' THEN N'total_elapsed_time / execution_count' + WHEN N'avg memory grant' THEN N'CASE WHEN max_grant_kb = 0 THEN 0 ELSE max_grant_kb / execution_count END' + WHEN N'avg spills' THEN N'CASE WHEN total_spills = 0 THEN 0 ELSE total_spills / execution_count END' + WHEN N'avg executions' THEN N'CASE WHEN execution_count = 0 THEN 0 + WHEN COALESCE(age_minutes, age_minutes_lifetime, 0) = 0 THEN 0 + ELSE CAST((1.00 * execution_count / COALESCE(age_minutes, age_minutes_lifetime)) AS money) + END' + END ; +SELECT @sql = REPLACE(@sql, '#sortable#', @sort); -IF @SortOrder = 'sp_BlitzIndex' -BEGIN - RAISERROR(N'OUTSTANDING!', 0, 1) WITH NOWAIT; - SET @SortOrder = 'reads'; - SET @NoobSaibot = 1; - -END - - -/* Change duration from seconds to milliseconds */ -IF @DurationFilter IS NOT NULL - BEGIN - RAISERROR(N'Converting Duration Filter to milliseconds', 0, 1) WITH NOWAIT; - SET @DurationFilter_i = CAST((@DurationFilter * 1000.0) AS INT); - END; - -RAISERROR(N'Checking database validity', 0, 1) WITH NOWAIT; -SET @DatabaseName = LTRIM(RTRIM(@DatabaseName)) ; +SET @sql += N' +SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; +INSERT INTO #p (SqlHandle, TotalCPU, TotalReads, TotalDuration, TotalWrites, ExecutionCount) +SELECT SqlHandle, + TotalCPU, + TotalReads, + TotalDuration, + TotalWrites, + ExecutionCount +FROM (SELECT SqlHandle, + TotalCPU, + TotalReads, + TotalDuration, + TotalWrites, + ExecutionCount, + ROW_NUMBER() OVER (PARTITION BY SqlHandle ORDER BY #sortable# DESC) AS rn + FROM ##BlitzCacheProcs + WHERE SPID = @@SPID) AS x +WHERE x.rn = 1 +OPTION (RECOMPILE); -IF SERVERPROPERTY('EngineEdition') IN (5, 6) AND DB_NAME() <> @DatabaseName -BEGIN - RAISERROR('You specified a database name other than the current database, but Azure SQL DB does not allow you to change databases. Execute sp_BlitzCache from the database you want to analyze.', 16, 1); - RETURN; -END; -IF (DB_ID(@DatabaseName)) IS NULL AND @DatabaseName <> N'' -BEGIN - RAISERROR('The database you specified does not exist. Please check the name and try again.', 16, 1); - RETURN; -END; -IF (SELECT DATABASEPROPERTYEX(ISNULL(@DatabaseName, 'master'), 'Collation')) IS NULL AND SERVERPROPERTY('EngineEdition') NOT IN (5, 6, 8) -BEGIN - RAISERROR('The database you specified is not readable. Please check the name and try again. Better yet, check your server.', 16, 1); - RETURN; -END; +/* + This block was used to delete duplicate queries, but has been removed. + For more info: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2026 +WITH d AS ( +SELECT SPID, + ROW_NUMBER() OVER (PARTITION BY SqlHandle, QueryHash ORDER BY #sortable# DESC) AS rn +FROM ##BlitzCacheProcs +WHERE SPID = @@SPID +) +DELETE d +WHERE d.rn > 1 +AND SPID = @@SPID +OPTION (RECOMPILE); +*/ +'; -SELECT @MinMemoryPerQuery = CONVERT(INT, c.value) FROM sys.configurations AS c WHERE c.name = 'min memory per query (KB)'; +SELECT @sort = CASE @SortOrder WHEN N'cpu' THEN N'TotalCPU' + WHEN N'reads' THEN N'TotalReads' + WHEN N'writes' THEN N'TotalWrites' + WHEN N'duration' THEN N'TotalDuration' + WHEN N'executions' THEN N'ExecutionCount' + WHEN N'compiles' THEN N'PlanCreationTime' + WHEN N'memory grant' THEN N'MaxGrantKB' + WHEN N'unused grant' THEN N'MaxGrantKB - MaxUsedGrantKB' + WHEN N'spills' THEN N'MaxSpills' + WHEN N'duplicate' THEN N'TotalCPU' /* Issue #3345 */ + /* And now the averages */ + WHEN N'avg cpu' THEN N'TotalCPU / ExecutionCount' + WHEN N'avg reads' THEN N'TotalReads / ExecutionCount' + WHEN N'avg writes' THEN N'TotalWrites / ExecutionCount' + WHEN N'avg duration' THEN N'TotalDuration / ExecutionCount' + WHEN N'avg memory grant' THEN N'AvgMaxMemoryGrant' + WHEN N'avg spills' THEN N'AvgSpills' + WHEN N'avg executions' THEN N'CASE WHEN ExecutionCount = 0 THEN 0 + WHEN COALESCE(age_minutes, age_minutes_lifetime, 0) = 0 THEN 0 + ELSE CAST((1.00 * ExecutionCount / COALESCE(age_minutes, age_minutes_lifetime)) AS money) + END' + END ; -SET @SortOrder = REPLACE(REPLACE(@SortOrder, 'average', 'avg'), '.', ''); +SELECT @sql = REPLACE(@sql, '#sortable#', @sort); -SET @SortOrder = CASE - WHEN @SortOrder IN ('executions per minute','execution per minute','executions / minute','execution / minute','xpm') THEN 'avg executions' - WHEN @SortOrder IN ('recent compilations','recent compilation','compile') THEN 'compiles' - WHEN @SortOrder IN ('read') THEN 'reads' - WHEN @SortOrder IN ('avg read') THEN 'avg reads' - WHEN @SortOrder IN ('write') THEN 'writes' - WHEN @SortOrder IN ('avg write') THEN 'avg writes' - WHEN @SortOrder IN ('memory grants') THEN 'memory grant' - WHEN @SortOrder IN ('avg memory grants') THEN 'avg memory grant' - WHEN @SortOrder IN ('unused grants','unused memory', 'unused memory grant', 'unused memory grants') THEN 'unused grant' - WHEN @SortOrder IN ('spill') THEN 'spills' - WHEN @SortOrder IN ('avg spill') THEN 'avg spills' - WHEN @SortOrder IN ('execution') THEN 'executions' - WHEN @SortOrder IN ('duplicates') THEN 'duplicate' - ELSE @SortOrder END - -RAISERROR(N'Checking sort order', 0, 1) WITH NOWAIT; -IF @SortOrder NOT IN ('cpu', 'avg cpu', 'reads', 'avg reads', 'writes', 'avg writes', - 'duration', 'avg duration', 'executions', 'avg executions', - 'compiles', 'memory grant', 'avg memory grant', 'unused grant', - 'spills', 'avg spills', 'all', 'all avg', 'sp_BlitzIndex', - 'query hash', 'duplicate') - BEGIN - RAISERROR(N'Invalid sort order chosen, reverting to cpu', 16, 1) WITH NOWAIT; - SET @SortOrder = 'cpu'; - END; -SET @QueryFilter = LOWER(@QueryFilter); +IF @Debug = 1 + BEGIN + PRINT N'Printing dynamic SQL stored in @sql: '; + PRINT SUBSTRING(@sql, 0, 4000); + PRINT SUBSTRING(@sql, 4000, 8000); + PRINT SUBSTRING(@sql, 8000, 12000); + PRINT SUBSTRING(@sql, 12000, 16000); + PRINT SUBSTRING(@sql, 16000, 20000); + PRINT SUBSTRING(@sql, 20000, 24000); + PRINT SUBSTRING(@sql, 24000, 28000); + PRINT SUBSTRING(@sql, 28000, 32000); + PRINT SUBSTRING(@sql, 32000, 36000); + PRINT SUBSTRING(@sql, 36000, 40000); + END; -IF LEFT(@QueryFilter, 3) NOT IN ('all', 'sta', 'pro', 'fun') - BEGIN - RAISERROR(N'Invalid query filter chosen. Reverting to all.', 0, 1) WITH NOWAIT; - SET @QueryFilter = 'all'; - END; +RAISERROR(N'Creating temp tables for results and warnings.', 0, 1) WITH NOWAIT; -IF @SkipAnalysis = 1 - BEGIN - RAISERROR(N'Skip Analysis set to 1, hiding Summary', 0, 1) WITH NOWAIT; - SET @HideSummary = 1; - END; -DECLARE @AllSortSql NVARCHAR(MAX) = N''; -DECLARE @VersionShowsMemoryGrants BIT; -IF EXISTS(SELECT * FROM sys.all_columns WHERE object_id = OBJECT_ID('sys.dm_exec_query_stats') AND name = 'max_grant_kb') - SET @VersionShowsMemoryGrants = 1; +IF OBJECT_ID('tempdb.dbo.##BlitzCacheResults') IS NULL +BEGIN + CREATE TABLE ##BlitzCacheResults ( + SPID INT, + ID INT IDENTITY(1,1), + CheckID INT, + Priority TINYINT, + FindingsGroup VARCHAR(50), + Finding VARCHAR(500), + URL VARCHAR(200), + Details VARCHAR(4000) + ); +END; ELSE - SET @VersionShowsMemoryGrants = 0; +BEGIN + RAISERROR(N'Cleaning up old warnings for your SPID', 0, 1) WITH NOWAIT; + DELETE ##BlitzCacheResults + WHERE SPID = @@SPID + OPTION (RECOMPILE) ; +END -DECLARE @VersionShowsSpills BIT; -IF EXISTS(SELECT * FROM sys.all_columns WHERE object_id = OBJECT_ID('sys.dm_exec_query_stats') AND name = 'max_spills') - SET @VersionShowsSpills = 1; -ELSE - SET @VersionShowsSpills = 0; -IF EXISTS(SELECT * FROM sys.all_columns WHERE object_id = OBJECT_ID('sys.dm_exec_query_plan_stats') AND name = 'query_plan') - SET @VersionShowsAirQuoteActualPlans = 1; +IF OBJECT_ID('tempdb.dbo.##BlitzCacheProcs') IS NULL +BEGIN + CREATE TABLE ##BlitzCacheProcs ( + SPID INT , + QueryType NVARCHAR(258), + DatabaseName sysname, + AverageCPU DECIMAL(38,4), + AverageCPUPerMinute DECIMAL(38,4), + TotalCPU DECIMAL(38,4), + PercentCPUByType MONEY, + PercentCPU MONEY, + AverageDuration DECIMAL(38,4), + TotalDuration DECIMAL(38,4), + PercentDuration MONEY, + PercentDurationByType MONEY, + AverageReads BIGINT, + TotalReads BIGINT, + PercentReads MONEY, + PercentReadsByType MONEY, + ExecutionCount BIGINT, + PercentExecutions MONEY, + PercentExecutionsByType MONEY, + ExecutionsPerMinute MONEY, + TotalWrites BIGINT, + AverageWrites MONEY, + PercentWrites MONEY, + PercentWritesByType MONEY, + WritesPerMinute MONEY, + PlanCreationTime DATETIME, + PlanCreationTimeHours AS DATEDIFF(HOUR, PlanCreationTime, SYSDATETIME()), + LastExecutionTime DATETIME, + LastCompletionTime DATETIME, + PlanHandle VARBINARY(64), + [Remove Plan Handle From Cache] AS + CASE WHEN [PlanHandle] IS NOT NULL + THEN 'DBCC FREEPROCCACHE (' + CONVERT(VARCHAR(128), [PlanHandle], 1) + ');' + ELSE 'N/A' END, + SqlHandle VARBINARY(64), + [Remove SQL Handle From Cache] AS + CASE WHEN [SqlHandle] IS NOT NULL + THEN 'DBCC FREEPROCCACHE (' + CONVERT(VARCHAR(128), [SqlHandle], 1) + ');' + ELSE 'N/A' END, + [SQL Handle More Info] AS + CASE WHEN [SqlHandle] IS NOT NULL + THEN 'EXEC sp_BlitzCache @OnlySqlHandles = ''' + CONVERT(VARCHAR(128), [SqlHandle], 1) + '''; ' + ELSE 'N/A' END, + QueryHash BINARY(8), + [Query Hash More Info] AS + CASE WHEN [QueryHash] IS NOT NULL + THEN 'EXEC sp_BlitzCache @OnlyQueryHashes = ''' + CONVERT(VARCHAR(32), [QueryHash], 1) + '''; ' + ELSE 'N/A' END, + QueryPlanHash BINARY(8), + StatementStartOffset INT, + StatementEndOffset INT, + PlanGenerationNum BIGINT, + MinReturnedRows BIGINT, + MaxReturnedRows BIGINT, + AverageReturnedRows MONEY, + TotalReturnedRows BIGINT, + LastReturnedRows BIGINT, + MinGrantKB BIGINT, + MaxGrantKB BIGINT, + MinUsedGrantKB BIGINT, + MaxUsedGrantKB BIGINT, + PercentMemoryGrantUsed MONEY, + AvgMaxMemoryGrant MONEY, + MinSpills BIGINT, + MaxSpills BIGINT, + TotalSpills BIGINT, + AvgSpills MONEY, + QueryText NVARCHAR(MAX), + QueryPlan XML, + /* these next four columns are the total for the type of query. + don't actually use them for anything apart from math by type. + */ + TotalWorkerTimeForType BIGINT, + TotalElapsedTimeForType BIGINT, + TotalReadsForType BIGINT, + TotalExecutionCountForType BIGINT, + TotalWritesForType BIGINT, + NumberOfPlans INT, + NumberOfDistinctPlans INT, + SerialDesiredMemory FLOAT, + SerialRequiredMemory FLOAT, + CachedPlanSize FLOAT, + CompileTime FLOAT, + CompileCPU FLOAT , + CompileMemory FLOAT , + MaxCompileMemory FLOAT , + min_worker_time BIGINT, + max_worker_time BIGINT, + is_forced_plan BIT, + is_forced_parameterized BIT, + is_cursor BIT, + is_optimistic_cursor BIT, + is_forward_only_cursor BIT, + is_fast_forward_cursor BIT, + is_cursor_dynamic BIT, + is_parallel BIT, + is_forced_serial BIT, + is_key_lookup_expensive BIT, + key_lookup_cost FLOAT, + is_remote_query_expensive BIT, + remote_query_cost FLOAT, + frequent_execution BIT, + parameter_sniffing BIT, + unparameterized_query BIT, + near_parallel BIT, + plan_warnings BIT, + plan_multiple_plans INT, + long_running BIT, + downlevel_estimator BIT, + implicit_conversions BIT, + busy_loops BIT, + tvf_join BIT, + tvf_estimate BIT, + compile_timeout BIT, + compile_memory_limit_exceeded BIT, + warning_no_join_predicate BIT, + QueryPlanCost FLOAT, + missing_index_count INT, + unmatched_index_count INT, + min_elapsed_time BIGINT, + max_elapsed_time BIGINT, + age_minutes MONEY, + age_minutes_lifetime MONEY, + is_trivial BIT, + trace_flags_session VARCHAR(1000), + is_unused_grant BIT, + function_count INT, + clr_function_count INT, + is_table_variable BIT, + no_stats_warning BIT, + relop_warnings BIT, + is_table_scan BIT, + backwards_scan BIT, + forced_index BIT, + forced_seek BIT, + forced_scan BIT, + columnstore_row_mode BIT, + is_computed_scalar BIT , + is_sort_expensive BIT, + sort_cost FLOAT, + is_computed_filter BIT, + op_name VARCHAR(100) NULL, + index_insert_count INT NULL, + index_update_count INT NULL, + index_delete_count INT NULL, + cx_insert_count INT NULL, + cx_update_count INT NULL, + cx_delete_count INT NULL, + table_insert_count INT NULL, + table_update_count INT NULL, + table_delete_count INT NULL, + index_ops AS (index_insert_count + index_update_count + index_delete_count + + cx_insert_count + cx_update_count + cx_delete_count + + table_insert_count + table_update_count + table_delete_count), + is_row_level BIT, + is_spatial BIT, + index_dml BIT, + table_dml BIT, + long_running_low_cpu BIT, + low_cost_high_cpu BIT, + stale_stats BIT, + is_adaptive BIT, + index_spool_cost FLOAT, + index_spool_rows FLOAT, + table_spool_cost FLOAT, + table_spool_rows FLOAT, + is_spool_expensive BIT, + is_spool_more_rows BIT, + is_table_spool_expensive BIT, + is_table_spool_more_rows BIT, + estimated_rows FLOAT, + is_bad_estimate BIT, + is_paul_white_electric BIT, + is_row_goal BIT, + is_big_spills BIT, + is_mstvf BIT, + is_mm_join BIT, + is_nonsargable BIT, + select_with_writes BIT, + implicit_conversion_info XML, + cached_execution_parameters XML, + missing_indexes XML, + SetOptions VARCHAR(MAX), + Warnings VARCHAR(MAX), + Pattern NVARCHAR(20) + ); +END; ELSE - SET @VersionShowsAirQuoteActualPlans = 0; - -IF @Reanalyze = 1 - BEGIN - IF OBJECT_ID('tempdb..##BlitzCacheResults') IS NULL - BEGIN - RAISERROR(N'##BlitzCacheResults does not exist, can''t reanalyze', 0, 1) WITH NOWAIT; - SET @Reanalyze = 0; - END - ELSE - BEGIN - RAISERROR(N'Reanalyzing current data, skipping to results', 0, 1) WITH NOWAIT; - GOTO Results; - END; - END; - - -IF @SortOrder IN ('all', 'all avg') - BEGIN - RAISERROR(N'Checking all sort orders, please be patient', 0, 1) WITH NOWAIT; - GOTO AllSorts; - END; - -RAISERROR(N'Creating temp tables for internal processing', 0, 1) WITH NOWAIT; -IF OBJECT_ID('tempdb..#only_query_hashes') IS NOT NULL - DROP TABLE #only_query_hashes ; - -IF OBJECT_ID('tempdb..#ignore_query_hashes') IS NOT NULL - DROP TABLE #ignore_query_hashes ; - -IF OBJECT_ID('tempdb..#only_sql_handles') IS NOT NULL - DROP TABLE #only_sql_handles ; - -IF OBJECT_ID('tempdb..#ignore_sql_handles') IS NOT NULL - DROP TABLE #ignore_sql_handles ; - -IF OBJECT_ID('tempdb..#p') IS NOT NULL - DROP TABLE #p; - -IF OBJECT_ID ('tempdb..#checkversion') IS NOT NULL - DROP TABLE #checkversion; - -IF OBJECT_ID ('tempdb..#configuration') IS NOT NULL - DROP TABLE #configuration; - -IF OBJECT_ID ('tempdb..#stored_proc_info') IS NOT NULL - DROP TABLE #stored_proc_info; - -IF OBJECT_ID ('tempdb..#plan_creation') IS NOT NULL - DROP TABLE #plan_creation; - -IF OBJECT_ID ('tempdb..#est_rows') IS NOT NULL - DROP TABLE #est_rows; +BEGIN + RAISERROR(N'Cleaning up old plans for your SPID', 0, 1) WITH NOWAIT; + DELETE ##BlitzCacheProcs + WHERE SPID = @@SPID + OPTION (RECOMPILE) ; +END -IF OBJECT_ID ('tempdb..#plan_cost') IS NOT NULL - DROP TABLE #plan_cost; +IF @Reanalyze = 0 +BEGIN + RAISERROR('Collecting execution plan information.', 0, 1) WITH NOWAIT; -IF OBJECT_ID ('tempdb..#proc_costs') IS NOT NULL - DROP TABLE #proc_costs; + EXEC sp_executesql @sql, N'@Top INT, @min_duration INT, @min_back INT, @SortOrder NVARCHAR(20)', @Top, @DurationFilter_i, @MinutesBack, @SortOrder; +END; -IF OBJECT_ID ('tempdb..#stats_agg') IS NOT NULL - DROP TABLE #stats_agg; +IF @SkipAnalysis = 1 + BEGIN + RAISERROR(N'Skipping analysis, going to results', 0, 1) WITH NOWAIT; + GOTO Results ; + END; -IF OBJECT_ID ('tempdb..#trace_flags') IS NOT NULL - DROP TABLE #trace_flags; -IF OBJECT_ID('tempdb..#variable_info') IS NOT NULL - DROP TABLE #variable_info; +/* Update ##BlitzCacheProcs to get Stored Proc info + * This should get totals for all statements in a Stored Proc + */ +RAISERROR(N'Attempting to aggregate stored proc info from separate statements', 0, 1) WITH NOWAIT; +;WITH agg AS ( + SELECT + b.SqlHandle, + SUM(b.MinReturnedRows) AS MinReturnedRows, + SUM(b.MaxReturnedRows) AS MaxReturnedRows, + SUM(b.AverageReturnedRows) AS AverageReturnedRows, + SUM(b.TotalReturnedRows) AS TotalReturnedRows, + SUM(b.LastReturnedRows) AS LastReturnedRows, + SUM(b.MinGrantKB) AS MinGrantKB, + SUM(b.MaxGrantKB) AS MaxGrantKB, + SUM(b.MinUsedGrantKB) AS MinUsedGrantKB, + SUM(b.MaxUsedGrantKB) AS MaxUsedGrantKB, + SUM(b.MinSpills) AS MinSpills, + SUM(b.MaxSpills) AS MaxSpills, + SUM(b.TotalSpills) AS TotalSpills + FROM ##BlitzCacheProcs b + WHERE b.SPID = @@SPID + AND b.QueryHash IS NOT NULL + GROUP BY b.SqlHandle +) +UPDATE b + SET + b.MinReturnedRows = b2.MinReturnedRows, + b.MaxReturnedRows = b2.MaxReturnedRows, + b.AverageReturnedRows = b2.AverageReturnedRows, + b.TotalReturnedRows = b2.TotalReturnedRows, + b.LastReturnedRows = b2.LastReturnedRows, + b.MinGrantKB = b2.MinGrantKB, + b.MaxGrantKB = b2.MaxGrantKB, + b.MinUsedGrantKB = b2.MinUsedGrantKB, + b.MaxUsedGrantKB = b2.MaxUsedGrantKB, + b.MinSpills = b2.MinSpills, + b.MaxSpills = b2.MaxSpills, + b.TotalSpills = b2.TotalSpills +FROM ##BlitzCacheProcs b +JOIN agg b2 +ON b2.SqlHandle = b.SqlHandle +WHERE b.QueryHash IS NULL +AND b.SPID = @@SPID +OPTION (RECOMPILE) ; -IF OBJECT_ID('tempdb..#conversion_info') IS NOT NULL - DROP TABLE #conversion_info; +/* Compute the total CPU, etc across our active set of the plan cache. + * Yes, there's a flaw - this doesn't include anything outside of our @Top + * metric. + */ +RAISERROR('Computing CPU, duration, read, and write metrics', 0, 1) WITH NOWAIT; +DECLARE @total_duration BIGINT, + @total_cpu BIGINT, + @total_reads BIGINT, + @total_writes BIGINT, + @total_execution_count BIGINT; -IF OBJECT_ID('tempdb..#missing_index_xml') IS NOT NULL - DROP TABLE #missing_index_xml; +SELECT @total_cpu = SUM(TotalCPU), + @total_duration = SUM(TotalDuration), + @total_reads = SUM(TotalReads), + @total_writes = SUM(TotalWrites), + @total_execution_count = SUM(ExecutionCount) +FROM #p +OPTION (RECOMPILE) ; -IF OBJECT_ID('tempdb..#missing_index_schema') IS NOT NULL - DROP TABLE #missing_index_schema; +DECLARE @cr NVARCHAR(1) = NCHAR(13); +DECLARE @lf NVARCHAR(1) = NCHAR(10); +DECLARE @tab NVARCHAR(1) = NCHAR(9); -IF OBJECT_ID('tempdb..#missing_index_usage') IS NOT NULL - DROP TABLE #missing_index_usage; +/* Update CPU percentage for stored procedures */ +RAISERROR(N'Update CPU percentage for stored procedures', 0, 1) WITH NOWAIT; +UPDATE ##BlitzCacheProcs +SET PercentCPU = y.PercentCPU, + PercentDuration = y.PercentDuration, + PercentReads = y.PercentReads, + PercentWrites = y.PercentWrites, + PercentExecutions = y.PercentExecutions, + ExecutionsPerMinute = y.ExecutionsPerMinute, + /* Strip newlines and tabs. Tabs are replaced with multiple spaces + so that the later whitespace trim will completely eliminate them + */ + QueryText = REPLACE(REPLACE(REPLACE(QueryText, @cr, ' '), @lf, ' '), @tab, ' ') +FROM ( + SELECT PlanHandle, + CASE @total_cpu WHEN 0 THEN 0 + ELSE CAST((100. * TotalCPU) / @total_cpu AS MONEY) END AS PercentCPU, + CASE @total_duration WHEN 0 THEN 0 + ELSE CAST((100. * TotalDuration) / @total_duration AS MONEY) END AS PercentDuration, + CASE @total_reads WHEN 0 THEN 0 + ELSE CAST((100. * TotalReads) / @total_reads AS MONEY) END AS PercentReads, + CASE @total_writes WHEN 0 THEN 0 + ELSE CAST((100. * TotalWrites) / @total_writes AS MONEY) END AS PercentWrites, + CASE @total_execution_count WHEN 0 THEN 0 + ELSE CAST((100. * ExecutionCount) / @total_execution_count AS MONEY) END AS PercentExecutions, + CASE DATEDIFF(mi, PlanCreationTime, LastExecutionTime) + WHEN 0 THEN 0 + ELSE CAST((1.00 * ExecutionCount / DATEDIFF(mi, PlanCreationTime, LastExecutionTime)) AS MONEY) + END AS ExecutionsPerMinute + FROM ( + SELECT PlanHandle, + TotalCPU, + TotalDuration, + TotalReads, + TotalWrites, + ExecutionCount, + PlanCreationTime, + LastExecutionTime + FROM ##BlitzCacheProcs + WHERE PlanHandle IS NOT NULL + AND SPID = @@SPID + GROUP BY PlanHandle, + TotalCPU, + TotalDuration, + TotalReads, + TotalWrites, + ExecutionCount, + PlanCreationTime, + LastExecutionTime + ) AS x +) AS y +WHERE ##BlitzCacheProcs.PlanHandle = y.PlanHandle + AND ##BlitzCacheProcs.PlanHandle IS NOT NULL + AND ##BlitzCacheProcs.SPID = @@SPID +OPTION (RECOMPILE) ; -IF OBJECT_ID('tempdb..#missing_index_detail') IS NOT NULL - DROP TABLE #missing_index_detail; -IF OBJECT_ID('tempdb..#missing_index_pretty') IS NOT NULL - DROP TABLE #missing_index_pretty; +RAISERROR(N'Gather percentage information from grouped results', 0, 1) WITH NOWAIT; +UPDATE ##BlitzCacheProcs +SET PercentCPU = y.PercentCPU, + PercentDuration = y.PercentDuration, + PercentReads = y.PercentReads, + PercentWrites = y.PercentWrites, + PercentExecutions = y.PercentExecutions, + ExecutionsPerMinute = y.ExecutionsPerMinute, + /* Strip newlines and tabs. Tabs are replaced with multiple spaces + so that the later whitespace trim will completely eliminate them + */ + QueryText = REPLACE(REPLACE(REPLACE(QueryText, @cr, ' '), @lf, ' '), @tab, ' ') +FROM ( + SELECT DatabaseName, + SqlHandle, + QueryHash, + CASE @total_cpu WHEN 0 THEN 0 + ELSE CAST((100. * TotalCPU) / @total_cpu AS MONEY) END AS PercentCPU, + CASE @total_duration WHEN 0 THEN 0 + ELSE CAST((100. * TotalDuration) / @total_duration AS MONEY) END AS PercentDuration, + CASE @total_reads WHEN 0 THEN 0 + ELSE CAST((100. * TotalReads) / @total_reads AS MONEY) END AS PercentReads, + CASE @total_writes WHEN 0 THEN 0 + ELSE CAST((100. * TotalWrites) / @total_writes AS MONEY) END AS PercentWrites, + CASE @total_execution_count WHEN 0 THEN 0 + ELSE CAST((100. * ExecutionCount) / @total_execution_count AS MONEY) END AS PercentExecutions, + CASE DATEDIFF(mi, PlanCreationTime, LastExecutionTime) + WHEN 0 THEN 0 + ELSE CAST((1.00 * ExecutionCount / DATEDIFF(mi, PlanCreationTime, LastExecutionTime)) AS MONEY) + END AS ExecutionsPerMinute + FROM ( + SELECT DatabaseName, + SqlHandle, + QueryHash, + TotalCPU, + TotalDuration, + TotalReads, + TotalWrites, + ExecutionCount, + PlanCreationTime, + LastExecutionTime + FROM ##BlitzCacheProcs + WHERE SPID = @@SPID + GROUP BY DatabaseName, + SqlHandle, + QueryHash, + TotalCPU, + TotalDuration, + TotalReads, + TotalWrites, + ExecutionCount, + PlanCreationTime, + LastExecutionTime + ) AS x +) AS y +WHERE ##BlitzCacheProcs.SqlHandle = y.SqlHandle + AND ##BlitzCacheProcs.QueryHash = y.QueryHash + AND ##BlitzCacheProcs.DatabaseName = y.DatabaseName + AND ##BlitzCacheProcs.PlanHandle IS NULL +OPTION (RECOMPILE) ; -IF OBJECT_ID('tempdb..#index_spool_ugly') IS NOT NULL - DROP TABLE #index_spool_ugly; - -IF OBJECT_ID('tempdb..#ReadableDBs') IS NOT NULL - DROP TABLE #ReadableDBs; -IF OBJECT_ID('tempdb..#plan_usage') IS NOT NULL - DROP TABLE #plan_usage; -CREATE TABLE #only_query_hashes ( - query_hash BINARY(8) -); +/* Testing using XML nodes to speed up processing */ +RAISERROR(N'Begin XML nodes processing', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +SELECT QueryHash , + SqlHandle , + PlanHandle, + q.n.query('.') AS statement, + 0 AS is_cursor +INTO #statements +FROM ##BlitzCacheProcs p + CROSS APPLY p.QueryPlan.nodes('//p:StmtSimple') AS q(n) +WHERE p.SPID = @@SPID +OPTION (RECOMPILE) ; -CREATE TABLE #ignore_query_hashes ( - query_hash BINARY(8) -); +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +INSERT #statements +SELECT QueryHash , + SqlHandle , + PlanHandle, + q.n.query('.') AS statement, + 1 AS is_cursor +FROM ##BlitzCacheProcs p + CROSS APPLY p.QueryPlan.nodes('//p:StmtCursor') AS q(n) +WHERE p.SPID = @@SPID +OPTION (RECOMPILE) ; -CREATE TABLE #only_sql_handles ( - sql_handle VARBINARY(64) -); +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +SELECT QueryHash , + SqlHandle , + q.n.query('.') AS query_plan +INTO #query_plan +FROM #statements p + CROSS APPLY p.statement.nodes('//p:QueryPlan') AS q(n) +OPTION (RECOMPILE) ; -CREATE TABLE #ignore_sql_handles ( - sql_handle VARBINARY(64) -); +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +SELECT QueryHash , + SqlHandle , + q.n.query('.') AS relop +INTO #relop +FROM #query_plan p + CROSS APPLY p.query_plan.nodes('//p:RelOp') AS q(n) +OPTION (RECOMPILE) ; -CREATE TABLE #p ( - SqlHandle VARBINARY(64), - TotalCPU BIGINT, - TotalDuration BIGINT, - TotalReads BIGINT, - TotalWrites BIGINT, - ExecutionCount BIGINT -); +-- high level plan stuff +RAISERROR(N'Gathering high level plan information', 0, 1) WITH NOWAIT; +UPDATE ##BlitzCacheProcs +SET NumberOfDistinctPlans = distinct_plan_count, + NumberOfPlans = number_of_plans , + plan_multiple_plans = CASE WHEN distinct_plan_count < number_of_plans THEN number_of_plans END +FROM + ( + SELECT + DatabaseName = + DB_NAME(CONVERT(int, pa.value)), + QueryHash = + qs.query_hash, + number_of_plans = + COUNT_BIG(qs.query_plan_hash), + distinct_plan_count = + COUNT_BIG(DISTINCT qs.query_plan_hash) + FROM sys.dm_exec_query_stats AS qs + CROSS APPLY sys.dm_exec_plan_attributes(qs.plan_handle) pa + WHERE pa.attribute = 'dbid' + GROUP BY + DB_NAME(CONVERT(int, pa.value)), + qs.query_hash +) AS x +WHERE ##BlitzCacheProcs.QueryHash = x.QueryHash +AND ##BlitzCacheProcs.DatabaseName = x.DatabaseName +OPTION (RECOMPILE) ; -CREATE TABLE #checkversion ( - version NVARCHAR(128), - common_version AS SUBSTRING(version, 1, CHARINDEX('.', version) + 1 ), - major AS PARSENAME(CONVERT(VARCHAR(32), version), 4), - minor AS PARSENAME(CONVERT(VARCHAR(32), version), 3), - build AS PARSENAME(CONVERT(VARCHAR(32), version), 2), - revision AS PARSENAME(CONVERT(VARCHAR(32), version), 1) -); +-- query level checks +RAISERROR(N'Performing query level checks', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +UPDATE ##BlitzCacheProcs +SET missing_index_count = query_plan.value('count(//p:QueryPlan/p:MissingIndexes/p:MissingIndexGroup)', 'int') , + unmatched_index_count = CASE WHEN is_trivial <> 1 THEN query_plan.value('count(//p:QueryPlan/p:UnmatchedIndexes/p:Parameterization/p:Object)', 'int') END , + SerialDesiredMemory = query_plan.value('sum(//p:QueryPlan/p:MemoryGrantInfo/@SerialDesiredMemory)', 'float') , + SerialRequiredMemory = query_plan.value('sum(//p:QueryPlan/p:MemoryGrantInfo/@SerialRequiredMemory)', 'float'), + CachedPlanSize = query_plan.value('sum(//p:QueryPlan/@CachedPlanSize)', 'float') , + CompileTime = query_plan.value('sum(//p:QueryPlan/@CompileTime)', 'float') , + CompileCPU = query_plan.value('sum(//p:QueryPlan/@CompileCPU)', 'float') , + CompileMemory = query_plan.value('sum(//p:QueryPlan/@CompileMemory)', 'float'), + MaxCompileMemory = query_plan.value('sum(//p:QueryPlan/p:OptimizerHardwareDependentProperties/@MaxCompileMemory)', 'float') +FROM #query_plan qp +WHERE qp.QueryHash = ##BlitzCacheProcs.QueryHash +AND qp.SqlHandle = ##BlitzCacheProcs.SqlHandle +AND SPID = @@SPID +OPTION (RECOMPILE); -CREATE TABLE #configuration ( - parameter_name VARCHAR(100), - value DECIMAL(38,0) -); +-- statement level checks +RAISERROR(N'Performing compile timeout checks', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +UPDATE b +SET compile_timeout = 1 +FROM #statements s +JOIN ##BlitzCacheProcs b +ON s.QueryHash = b.QueryHash +AND SPID = @@SPID +WHERE statement.exist('/p:StmtSimple/@StatementOptmEarlyAbortReason[.="TimeOut"]') = 1 +OPTION (RECOMPILE); -CREATE TABLE #plan_creation -( - percent_24 DECIMAL(5, 2), - percent_4 DECIMAL(5, 2), - percent_1 DECIMAL(5, 2), - total_plans INT, - SPID INT -); +RAISERROR(N'Performing compile memory limit exceeded checks', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +UPDATE b +SET compile_memory_limit_exceeded = 1 +FROM #statements s +JOIN ##BlitzCacheProcs b +ON s.QueryHash = b.QueryHash +AND SPID = @@SPID +WHERE statement.exist('/p:StmtSimple/@StatementOptmEarlyAbortReason[.="MemoryLimitExceeded"]') = 1 +OPTION (RECOMPILE); -CREATE TABLE #est_rows -( - QueryHash BINARY(8), - estimated_rows FLOAT -); +IF @ExpertMode > 0 +BEGIN +RAISERROR(N'Performing unparameterized query checks', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p), +unparameterized_query AS ( + SELECT s.QueryHash, + unparameterized_query = CASE WHEN statement.exist('//p:StmtSimple[@StatementOptmLevel[.="FULL"]]/p:QueryPlan/p:ParameterList') = 1 AND + statement.exist('//p:StmtSimple[@StatementOptmLevel[.="FULL"]]/p:QueryPlan/p:ParameterList/p:ColumnReference') = 0 THEN 1 + WHEN statement.exist('//p:StmtSimple[@StatementOptmLevel[.="FULL"]]/p:QueryPlan/p:ParameterList') = 0 AND + statement.exist('//p:StmtSimple[@StatementOptmLevel[.="FULL"]]/*/p:RelOp/descendant::p:ScalarOperator/p:Identifier/p:ColumnReference[contains(@Column, "@")]') = 1 THEN 1 + END + FROM #statements AS s + ) +UPDATE b +SET b.unparameterized_query = u.unparameterized_query +FROM ##BlitzCacheProcs b +JOIN unparameterized_query u +ON u.QueryHash = b.QueryHash +AND SPID = @@SPID +WHERE u.unparameterized_query = 1 +OPTION (RECOMPILE); +END; -CREATE TABLE #plan_cost -( - QueryPlanCost FLOAT, - SqlHandle VARBINARY(64), - PlanHandle VARBINARY(64), - QueryHash BINARY(8), - QueryPlanHash BINARY(8) -); -CREATE TABLE #proc_costs -( - PlanTotalQuery FLOAT, - PlanHandle VARBINARY(64), - SqlHandle VARBINARY(64) -); +IF @ExpertMode > 0 +BEGIN +RAISERROR(N'Performing index DML checks', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p), +index_dml AS ( + SELECT s.QueryHash, + index_dml = CASE WHEN statement.exist('//p:StmtSimple/@StatementType[.="CREATE INDEX"]') = 1 THEN 1 + WHEN statement.exist('//p:StmtSimple/@StatementType[.="DROP INDEX"]') = 1 THEN 1 + END + FROM #statements s + ) + UPDATE b + SET b.index_dml = i.index_dml + FROM ##BlitzCacheProcs AS b + JOIN index_dml i + ON i.QueryHash = b.QueryHash + WHERE i.index_dml = 1 + AND b.SPID = @@SPID + OPTION (RECOMPILE); +END; -CREATE TABLE #stats_agg -( - SqlHandle VARBINARY(64), - LastUpdate DATETIME2(7), - ModificationCount BIGINT, - SamplingPercent FLOAT, - [Statistics] NVARCHAR(258), - [Table] NVARCHAR(258), - [Schema] NVARCHAR(258), - [Database] NVARCHAR(258), -); -CREATE TABLE #trace_flags -( - SqlHandle VARBINARY(64), - QueryHash BINARY(8), - global_trace_flags VARCHAR(1000), - session_trace_flags VARCHAR(1000) -); +IF @ExpertMode > 0 +BEGIN +RAISERROR(N'Performing table DML checks', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p), +table_dml AS ( + SELECT s.QueryHash, + table_dml = CASE WHEN statement.exist('//p:StmtSimple/@StatementType[.="CREATE TABLE"]') = 1 THEN 1 + WHEN statement.exist('//p:StmtSimple/@StatementType[.="DROP OBJECT"]') = 1 THEN 1 + END + FROM #statements AS s + ) + UPDATE b + SET b.table_dml = t.table_dml + FROM ##BlitzCacheProcs AS b + JOIN table_dml t + ON t.QueryHash = b.QueryHash + WHERE t.table_dml = 1 + AND b.SPID = @@SPID + OPTION (RECOMPILE); +END; -CREATE TABLE #stored_proc_info -( - SPID INT, - SqlHandle VARBINARY(64), - QueryHash BINARY(8), - variable_name NVARCHAR(258), - variable_datatype NVARCHAR(258), - converted_column_name NVARCHAR(258), - compile_time_value NVARCHAR(258), - proc_name NVARCHAR(1000), - column_name NVARCHAR(4000), - converted_to NVARCHAR(258), - set_options NVARCHAR(1000) -); -CREATE TABLE #variable_info -( - SPID INT, - QueryHash BINARY(8), - SqlHandle VARBINARY(64), - proc_name NVARCHAR(1000), - variable_name NVARCHAR(258), - variable_datatype NVARCHAR(258), - compile_time_value NVARCHAR(258) -); +IF @ExpertMode > 0 +BEGIN +RAISERROR(N'Gathering row estimates', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES ('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) +INSERT INTO #est_rows +SELECT DISTINCT + CONVERT(BINARY(8), RIGHT('0000000000000000' + SUBSTRING(c.n.value('@QueryHash', 'VARCHAR(18)'), 3, 18), 16), 2) AS QueryHash, + c.n.value('(/p:StmtSimple/@StatementEstRows)[1]', 'FLOAT') AS estimated_rows +FROM #statements AS s +CROSS APPLY s.statement.nodes('/p:StmtSimple') AS c(n) +WHERE c.n.exist('/p:StmtSimple[@StatementEstRows > 0]') = 1; -CREATE TABLE #conversion_info -( - SPID INT, - QueryHash BINARY(8), - SqlHandle VARBINARY(64), - proc_name NVARCHAR(258), - expression NVARCHAR(4000), - at_charindex AS CHARINDEX('@', expression), - bracket_charindex AS CHARINDEX(']', expression, CHARINDEX('@', expression)) - CHARINDEX('@', expression), - comma_charindex AS CHARINDEX(',', expression) + 1, - second_comma_charindex AS - CHARINDEX(',', expression, CHARINDEX(',', expression) + 1) - CHARINDEX(',', expression) - 1, - equal_charindex AS CHARINDEX('=', expression) + 1, - paren_charindex AS CHARINDEX('(', expression) + 1, - comma_paren_charindex AS - CHARINDEX(',', expression, CHARINDEX('(', expression) + 1) - CHARINDEX('(', expression) - 1, - convert_implicit_charindex AS CHARINDEX('=CONVERT_IMPLICIT', expression) -); + UPDATE b + SET b.estimated_rows = er.estimated_rows + FROM ##BlitzCacheProcs AS b + JOIN #est_rows er + ON er.QueryHash = b.QueryHash + WHERE b.SPID = @@SPID + AND b.QueryType = 'Statement' + OPTION (RECOMPILE); +END; +RAISERROR(N'Gathering trivial plans', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES ( 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) +UPDATE b +SET b.is_trivial = 1 +FROM ##BlitzCacheProcs AS b +JOIN ( +SELECT s.SqlHandle +FROM #statements AS s +JOIN ( SELECT r.SqlHandle + FROM #relop AS r + WHERE r.relop.exist('//p:RelOp[contains(@LogicalOp, "Scan")]') = 1 ) AS r + ON r.SqlHandle = s.SqlHandle +WHERE s.statement.exist('//p:StmtSimple[@StatementOptmLevel[.="TRIVIAL"]]/p:QueryPlan/p:ParameterList') = 1 +) AS s +ON b.SqlHandle = s.SqlHandle +OPTION (RECOMPILE); -CREATE TABLE #missing_index_xml -( - QueryHash BINARY(8), - SqlHandle VARBINARY(64), - impact FLOAT, - index_xml XML -); +--Gather costs +RAISERROR(N'Gathering statement costs', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +INSERT INTO #plan_cost ( QueryPlanCost, SqlHandle, PlanHandle, QueryHash, QueryPlanHash ) +SELECT DISTINCT + statement.value('sum(/p:StmtSimple/@StatementSubTreeCost)', 'float') QueryPlanCost, + s.SqlHandle, + s.PlanHandle, + CONVERT(BINARY(8), RIGHT('0000000000000000' + SUBSTRING(q.n.value('@QueryHash', 'VARCHAR(18)'), 3, 18), 16), 2) AS QueryHash, + CONVERT(BINARY(8), RIGHT('0000000000000000' + SUBSTRING(q.n.value('@QueryPlanHash', 'VARCHAR(18)'), 3, 18), 16), 2) AS QueryPlanHash +FROM #statements s +CROSS APPLY s.statement.nodes('/p:StmtSimple') AS q(n) +WHERE statement.value('sum(/p:StmtSimple/@StatementSubTreeCost)', 'float') > 0 +OPTION (RECOMPILE); -CREATE TABLE #missing_index_schema -( - QueryHash BINARY(8), - SqlHandle VARBINARY(64), - impact FLOAT, - database_name NVARCHAR(128), - schema_name NVARCHAR(128), - table_name NVARCHAR(128), - index_xml XML -); +RAISERROR(N'Updating statement costs', 0, 1) WITH NOWAIT; +WITH pc AS ( + SELECT SUM(DISTINCT pc.QueryPlanCost) AS QueryPlanCostSum, pc.QueryHash, pc.QueryPlanHash, pc.SqlHandle, pc.PlanHandle + FROM #plan_cost AS pc + GROUP BY pc.QueryHash, pc.QueryPlanHash, pc.SqlHandle, pc.PlanHandle +) + UPDATE b + SET b.QueryPlanCost = ISNULL(pc.QueryPlanCostSum, 0) + FROM pc + JOIN ##BlitzCacheProcs b + ON b.SqlHandle = pc.SqlHandle + AND b.QueryHash = pc.QueryHash + WHERE b.QueryType NOT LIKE '%Procedure%' + OPTION (RECOMPILE); +IF EXISTS ( +SELECT 1 +FROM ##BlitzCacheProcs AS b +WHERE b.QueryType LIKE 'Procedure%' +) -CREATE TABLE #missing_index_usage -( - QueryHash BINARY(8), - SqlHandle VARBINARY(64), - impact FLOAT, - database_name NVARCHAR(128), - schema_name NVARCHAR(128), - table_name NVARCHAR(128), - usage NVARCHAR(128), - index_xml XML -); +BEGIN - -CREATE TABLE #missing_index_detail -( - QueryHash BINARY(8), - SqlHandle VARBINARY(64), - impact FLOAT, - database_name NVARCHAR(128), - schema_name NVARCHAR(128), - table_name NVARCHAR(128), - usage NVARCHAR(128), - column_name NVARCHAR(128) -); +RAISERROR(N'Gathering stored procedure costs', 0, 1) WITH NOWAIT; +;WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +, QueryCost AS ( + SELECT + DISTINCT + statement.value('sum(/p:StmtSimple/@StatementSubTreeCost)', 'float') AS SubTreeCost, + s.PlanHandle, + s.SqlHandle + FROM #statements AS s + WHERE PlanHandle IS NOT NULL +) +, QueryCostUpdate AS ( + SELECT + SUM(qc.SubTreeCost) OVER (PARTITION BY SqlHandle, PlanHandle) PlanTotalQuery, + qc.PlanHandle, + qc.SqlHandle + FROM QueryCost qc +) +INSERT INTO #proc_costs +SELECT qcu.PlanTotalQuery, PlanHandle, SqlHandle +FROM QueryCostUpdate AS qcu +OPTION (RECOMPILE); -CREATE TABLE #missing_index_pretty -( - QueryHash BINARY(8), - SqlHandle VARBINARY(64), - impact FLOAT, - database_name NVARCHAR(128), - schema_name NVARCHAR(128), - table_name NVARCHAR(128), - equality NVARCHAR(MAX), - inequality NVARCHAR(MAX), - [include] NVARCHAR(MAX), - executions NVARCHAR(128), - query_cost NVARCHAR(128), - creation_hours NVARCHAR(128), - is_spool BIT, - details AS N'/* ' - + CHAR(10) - + CASE is_spool - WHEN 0 - THEN N'The Query Processor estimates that implementing the ' - ELSE N'We estimate that implementing the ' - END - + N'following index could improve query cost (' + query_cost + N')' - + CHAR(10) - + N'by ' - + CONVERT(NVARCHAR(30), impact) - + N'% for ' + executions + N' executions of the query' - + N' over the last ' + - CASE WHEN creation_hours < 24 - THEN creation_hours + N' hours.' - WHEN creation_hours = 24 - THEN ' 1 day.' - WHEN creation_hours > 24 - THEN (CONVERT(NVARCHAR(128), creation_hours / 24)) + N' days.' - ELSE N'' - END - + CHAR(10) - + N'*/' - + CHAR(10) + CHAR(13) - + N'/* ' - + CHAR(10) - + N'USE ' - + database_name - + CHAR(10) - + N'GO' - + CHAR(10) + CHAR(13) - + N'CREATE NONCLUSTERED INDEX ix_' - + ISNULL(REPLACE(REPLACE(REPLACE(equality,'[', ''), ']', ''), ', ', '_'), '') - + ISNULL(REPLACE(REPLACE(REPLACE(inequality,'[', ''), ']', ''), ', ', '_'), '') - + CASE WHEN [include] IS NOT NULL THEN + N'_Includes' ELSE N'' END - + CHAR(10) - + N' ON ' - + schema_name - + N'.' - + table_name - + N' (' + - + CASE WHEN equality IS NOT NULL - THEN equality - + CASE WHEN inequality IS NOT NULL - THEN N', ' + inequality - ELSE N'' - END - ELSE inequality - END - + N')' - + CHAR(10) - + CASE WHEN include IS NOT NULL - THEN N'INCLUDE (' + include + N') WITH (FILLFACTOR=100, ONLINE=?, SORT_IN_TEMPDB=?, DATA_COMPRESSION=?);' - ELSE N' WITH (FILLFACTOR=100, ONLINE=?, SORT_IN_TEMPDB=?, DATA_COMPRESSION=?);' - END - + CHAR(10) - + N'GO' - + CHAR(10) - + N'*/' -); +UPDATE b + SET b.QueryPlanCost = ca.PlanTotalQuery +FROM ##BlitzCacheProcs AS b +CROSS APPLY ( + SELECT TOP 1 PlanTotalQuery + FROM #proc_costs qcu + WHERE qcu.PlanHandle = b.PlanHandle + ORDER BY PlanTotalQuery DESC +) ca +WHERE b.QueryType LIKE 'Procedure%' +AND b.SPID = @@SPID +OPTION (RECOMPILE); +END; -CREATE TABLE #index_spool_ugly -( - QueryHash BINARY(8), - SqlHandle VARBINARY(64), - impact FLOAT, - database_name NVARCHAR(128), - schema_name NVARCHAR(128), - table_name NVARCHAR(128), - equality NVARCHAR(MAX), - inequality NVARCHAR(MAX), - [include] NVARCHAR(MAX), - executions NVARCHAR(128), - query_cost NVARCHAR(128), - creation_hours NVARCHAR(128) -); +UPDATE b +SET b.QueryPlanCost = 0.0 +FROM ##BlitzCacheProcs b +WHERE b.QueryPlanCost IS NULL +AND b.SPID = @@SPID +OPTION (RECOMPILE); +RAISERROR(N'Checking for plan warnings', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +UPDATE ##BlitzCacheProcs +SET plan_warnings = 1 +FROM #query_plan qp +WHERE qp.SqlHandle = ##BlitzCacheProcs.SqlHandle +AND SPID = @@SPID +AND query_plan.exist('/p:QueryPlan/p:Warnings') = 1 +OPTION (RECOMPILE); -CREATE TABLE #ReadableDBs -( -database_id INT -); +RAISERROR(N'Checking for implicit conversion', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +UPDATE ##BlitzCacheProcs +SET implicit_conversions = 1 +FROM #query_plan qp +WHERE qp.SqlHandle = ##BlitzCacheProcs.SqlHandle +AND SPID = @@SPID +AND query_plan.exist('/p:QueryPlan/p:Warnings/p:PlanAffectingConvert/@Expression[contains(., "CONVERT_IMPLICIT")]') = 1 +OPTION (RECOMPILE); +-- operator level checks +IF @ExpertMode > 0 +BEGIN +RAISERROR(N'Performing busy loops checks', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +UPDATE p +SET busy_loops = CASE WHEN (x.estimated_executions / 100.0) > x.estimated_rows THEN 1 END +FROM ##BlitzCacheProcs p + JOIN ( + SELECT qs.SqlHandle, + relop.value('sum(/p:RelOp/@EstimateRows)', 'float') AS estimated_rows , + relop.value('sum(/p:RelOp/@EstimateRewinds)', 'float') + relop.value('sum(/p:RelOp/@EstimateRebinds)', 'float') + 1.0 AS estimated_executions + FROM #relop qs + ) AS x ON p.SqlHandle = x.SqlHandle +WHERE SPID = @@SPID +OPTION (RECOMPILE); +END; -CREATE TABLE #plan_usage -( - duplicate_plan_hashes BIGINT NULL, - percent_duplicate DECIMAL(9, 2) NULL, - single_use_plan_count BIGINT NULL, - percent_single DECIMAL(9, 2) NULL, - total_plans BIGINT NULL, - spid INT -); +RAISERROR(N'Performing TVF join check', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +UPDATE p +SET p.tvf_join = CASE WHEN x.tvf_join = 1 THEN 1 END +FROM ##BlitzCacheProcs p + JOIN ( + SELECT r.SqlHandle, + 1 AS tvf_join + FROM #relop AS r + WHERE r.relop.exist('//p:RelOp[(@LogicalOp[.="Table-valued function"])]') = 1 + AND r.relop.exist('//p:RelOp[contains(@LogicalOp, "Join")]') = 1 + ) AS x ON p.SqlHandle = x.SqlHandle +WHERE SPID = @@SPID +OPTION (RECOMPILE); -IF EXISTS (SELECT * FROM sys.all_objects o WHERE o.name = 'dm_hadr_database_replica_states') +IF @ExpertMode > 0 BEGIN - RAISERROR('Checking for Read intent databases to exclude',0,0) WITH NOWAIT; - - EXEC('INSERT INTO #ReadableDBs (database_id) SELECT DBs.database_id FROM sys.databases DBs INNER JOIN sys.availability_replicas Replicas ON DBs.replica_id = Replicas.replica_id WHERE replica_server_name NOT IN (SELECT DISTINCT primary_replica FROM sys.dm_hadr_availability_group_states States) AND Replicas.secondary_role_allow_connections_desc = ''READ_ONLY'' AND replica_server_name = @@SERVERNAME OPTION (RECOMPILE);'); - EXEC('INSERT INTO #ReadableDBs VALUES (32767) ;'); -- Exclude internal resource database as well -END - -RAISERROR(N'Checking plan cache age', 0, 1) WITH NOWAIT; -WITH x AS ( -SELECT SUM(CASE WHEN DATEDIFF(HOUR, deqs.creation_time, SYSDATETIME()) <= 24 THEN 1 ELSE 0 END) AS [plans_24], - SUM(CASE WHEN DATEDIFF(HOUR, deqs.creation_time, SYSDATETIME()) <= 4 THEN 1 ELSE 0 END) AS [plans_4], - SUM(CASE WHEN DATEDIFF(HOUR, deqs.creation_time, SYSDATETIME()) <= 1 THEN 1 ELSE 0 END) AS [plans_1], - COUNT(deqs.creation_time) AS [total_plans] -FROM sys.dm_exec_query_stats AS deqs +RAISERROR(N'Checking for operator warnings', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +, x AS ( +SELECT r.SqlHandle, + c.n.exist('//p:Warnings[(@NoJoinPredicate[.="1"])]') AS warning_no_join_predicate, + c.n.exist('//p:ColumnsWithNoStatistics') AS no_stats_warning , + c.n.exist('//p:Warnings') AS relop_warnings +FROM #relop AS r +CROSS APPLY r.relop.nodes('/p:RelOp/p:Warnings') AS c(n) ) -INSERT INTO #plan_creation ( percent_24, percent_4, percent_1, total_plans, SPID ) -SELECT CONVERT(DECIMAL(5,2), NULLIF(x.plans_24, 0) / (1. * NULLIF(x.total_plans, 0))) * 100 AS [percent_24], - CONVERT(DECIMAL(5,2), NULLIF(x.plans_4 , 0) / (1. * NULLIF(x.total_plans, 0))) * 100 AS [percent_4], - CONVERT(DECIMAL(5,2), NULLIF(x.plans_1 , 0) / (1. * NULLIF(x.total_plans, 0))) * 100 AS [percent_1], - x.total_plans, - @@SPID AS SPID -FROM x +UPDATE p +SET p.warning_no_join_predicate = x.warning_no_join_predicate, + p.no_stats_warning = x.no_stats_warning, + p.relop_warnings = x.relop_warnings +FROM ##BlitzCacheProcs AS p +JOIN x ON x.SqlHandle = p.SqlHandle +AND SPID = @@SPID OPTION (RECOMPILE); +END; -RAISERROR(N'Checking for single use plans and plans with many queries', 0, 1) WITH NOWAIT; -WITH total_plans AS -( - SELECT - COUNT_BIG(deqs.query_plan_hash) AS total_plans - FROM sys.dm_exec_query_stats AS deqs -), - many_plans AS -( - SELECT - SUM(x.duplicate_plan_hashes) AS duplicate_plan_hashes - FROM - ( - SELECT - COUNT_BIG(qs.query_plan_hash) AS duplicate_plan_hashes - FROM sys.dm_exec_query_stats qs - LEFT JOIN sys.dm_exec_procedure_stats ps ON qs.plan_handle = ps.plan_handle - CROSS APPLY sys.dm_exec_plan_attributes(qs.plan_handle) pa - WHERE pa.attribute = N'dbid' - AND pa.value <> 32767 /*Omit Resource database-based queries, we're not going to "fix" them no matter what. Addresses #3314*/ - AND qs.query_plan_hash <> 0x0000000000000000 - GROUP BY - /* qs.query_plan_hash, BGO 20210524 commenting this out to fix #2909 */ - qs.query_hash, - ps.object_id, - pa.value - HAVING COUNT_BIG(qs.query_plan_hash) > 5 - ) AS x -), - single_use_plans AS -( - SELECT - COUNT_BIG(*) AS single_use_plan_count - FROM sys.dm_exec_query_stats AS s - WHERE s.execution_count = 1 -) -INSERT - #plan_usage -( - duplicate_plan_hashes, - percent_duplicate, - single_use_plan_count, - percent_single, - total_plans, - spid +RAISERROR(N'Checking for table variables', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +, x AS ( +SELECT r.SqlHandle, + c.n.value('substring(@Table, 2, 1)','VARCHAR(100)') AS first_char +FROM #relop r +CROSS APPLY r.relop.nodes('//p:Object') AS c(n) ) -SELECT - m.duplicate_plan_hashes, - CONVERT - ( - decimal(5,2), - m.duplicate_plan_hashes - / (1. * NULLIF(t.total_plans, 0)) - ) * 100. AS percent_duplicate, - s.single_use_plan_count, - CONVERT - ( - decimal(5,2), - s.single_use_plan_count - / (1. * NULLIF(t.total_plans, 0)) - ) * 100. AS percent_single, - t.total_plans, - @@SPID -FROM many_plans AS m -CROSS JOIN single_use_plans AS s -CROSS JOIN total_plans AS t; +UPDATE p +SET is_table_variable = 1 +FROM ##BlitzCacheProcs AS p +JOIN x ON x.SqlHandle = p.SqlHandle +AND SPID = @@SPID +WHERE x.first_char = '@' +OPTION (RECOMPILE); +IF @ExpertMode > 0 +BEGIN +RAISERROR(N'Checking for functions', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +, x AS ( +SELECT qs.SqlHandle, + n.fn.value('count(distinct-values(//p:UserDefinedFunction[not(@IsClrFunction)]))', 'INT') AS function_count, + n.fn.value('count(distinct-values(//p:UserDefinedFunction[@IsClrFunction = "1"]))', 'INT') AS clr_function_count +FROM #relop qs +CROSS APPLY relop.nodes('/p:RelOp/p:ComputeScalar/p:DefinedValues/p:DefinedValue/p:ScalarOperator') n(fn) +) +UPDATE p +SET p.function_count = x.function_count, + p.clr_function_count = x.clr_function_count +FROM ##BlitzCacheProcs AS p +JOIN x ON x.SqlHandle = p.SqlHandle +AND SPID = @@SPID +OPTION (RECOMPILE); +END; -/* -Erik Darling: - Quoting this out to see if the above query fixes the issue - 2021-05-17, Issue #2909 -UPDATE #plan_usage - SET percent_duplicate = CASE WHEN percent_duplicate > 100 THEN 100 ELSE percent_duplicate END, - percent_single = CASE WHEN percent_duplicate > 100 THEN 100 ELSE percent_duplicate END; -*/ +RAISERROR(N'Checking for expensive key lookups', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +UPDATE ##BlitzCacheProcs +SET key_lookup_cost = x.key_lookup_cost +FROM ( +SELECT + qs.SqlHandle, + MAX(relop.value('sum(/p:RelOp/@EstimatedTotalSubtreeCost)', 'float')) AS key_lookup_cost +FROM #relop qs +WHERE [relop].exist('/p:RelOp/p:IndexScan[(@Lookup[.="1"])]') = 1 +GROUP BY qs.SqlHandle +) AS x +WHERE ##BlitzCacheProcs.SqlHandle = x.SqlHandle +AND SPID = @@SPID +OPTION (RECOMPILE); -SET @OnlySqlHandles = LTRIM(RTRIM(@OnlySqlHandles)) ; -SET @OnlyQueryHashes = LTRIM(RTRIM(@OnlyQueryHashes)) ; -SET @IgnoreQueryHashes = LTRIM(RTRIM(@IgnoreQueryHashes)) ; -DECLARE @individual VARCHAR(100) ; +RAISERROR(N'Checking for expensive remote queries', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +UPDATE ##BlitzCacheProcs +SET remote_query_cost = x.remote_query_cost +FROM ( +SELECT + qs.SqlHandle, + MAX(relop.value('sum(/p:RelOp/@EstimatedTotalSubtreeCost)', 'float')) AS remote_query_cost +FROM #relop qs +WHERE [relop].exist('/p:RelOp[(@PhysicalOp[contains(., "Remote")])]') = 1 +GROUP BY qs.SqlHandle +) AS x +WHERE ##BlitzCacheProcs.SqlHandle = x.SqlHandle +AND SPID = @@SPID +OPTION (RECOMPILE); -IF (@OnlySqlHandles IS NOT NULL AND @IgnoreSqlHandles IS NOT NULL) -BEGIN -RAISERROR('You shouldn''t need to ignore and filter on SqlHandle at the same time.', 0, 1) WITH NOWAIT; -RETURN; -END; +RAISERROR(N'Checking for expensive sorts', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +UPDATE ##BlitzCacheProcs +SET sort_cost = y.max_sort_cost +FROM ( + SELECT x.SqlHandle, MAX((x.sort_io + x.sort_cpu)) AS max_sort_cost + FROM ( + SELECT + qs.SqlHandle, + relop.value('sum(/p:RelOp/@EstimateIO)', 'float') AS sort_io, + relop.value('sum(/p:RelOp/@EstimateCPU)', 'float') AS sort_cpu + FROM #relop qs + WHERE [relop].exist('/p:RelOp[(@PhysicalOp[.="Sort"])]') = 1 + ) AS x + GROUP BY x.SqlHandle + ) AS y +WHERE ##BlitzCacheProcs.SqlHandle = y.SqlHandle +AND SPID = @@SPID +OPTION (RECOMPILE); -IF (@StoredProcName IS NOT NULL AND (@OnlySqlHandles IS NOT NULL OR @IgnoreSqlHandles IS NOT NULL)) +IF NOT EXISTS(SELECT 1/0 FROM #statements AS s WHERE s.is_cursor = 1) BEGIN -RAISERROR('You can''t filter on stored procedure name and SQL Handle.', 0, 1) WITH NOWAIT; -RETURN; -END; -IF @OnlySqlHandles IS NOT NULL - AND LEN(@OnlySqlHandles) > 0 -BEGIN - RAISERROR(N'Processing SQL Handles', 0, 1) WITH NOWAIT; - SET @individual = ''; +RAISERROR(N'No cursor plans found, skipping', 0, 1) WITH NOWAIT; - WHILE LEN(@OnlySqlHandles) > 0 - BEGIN - IF PATINDEX('%,%', @OnlySqlHandles) > 0 - BEGIN - SET @individual = SUBSTRING(@OnlySqlHandles, 0, PATINDEX('%,%',@OnlySqlHandles)) ; - - INSERT INTO #only_sql_handles - SELECT CAST('' AS XML).value('xs:hexBinary( substring(sql:variable("@individual"), sql:column("t.pos")) )', 'varbinary(max)') - FROM (SELECT CASE SUBSTRING(@individual, 1, 2) WHEN '0x' THEN 3 ELSE 0 END) AS t(pos) - OPTION (RECOMPILE) ; - - --SELECT CAST(SUBSTRING(@individual, 1, 2) AS BINARY(8)); +END - SET @OnlySqlHandles = SUBSTRING(@OnlySqlHandles, LEN(@individual + ',') + 1, LEN(@OnlySqlHandles)) ; - END; - ELSE - BEGIN - SET @individual = @OnlySqlHandles; - SET @OnlySqlHandles = NULL; +IF EXISTS(SELECT 1/0 FROM #statements AS s WHERE s.is_cursor = 1) +BEGIN - INSERT INTO #only_sql_handles - SELECT CAST('' AS XML).value('xs:hexBinary( substring(sql:variable("@individual"), sql:column("t.pos")) )', 'varbinary(max)') - FROM (SELECT CASE SUBSTRING(@individual, 1, 2) WHEN '0x' THEN 3 ELSE 0 END) AS t(pos) - OPTION (RECOMPILE) ; +RAISERROR(N'Cursor plans found, investigating', 0, 1) WITH NOWAIT; - --SELECT CAST(SUBSTRING(@individual, 1, 2) AS VARBINARY(MAX)) ; - END; - END; -END; +RAISERROR(N'Checking for Optimistic cursors', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +UPDATE b +SET b.is_optimistic_cursor = 1 +FROM ##BlitzCacheProcs b +JOIN #statements AS qs +ON b.SqlHandle = qs.SqlHandle +CROSS APPLY qs.statement.nodes('/p:StmtCursor') AS n1(fn) +WHERE SPID = @@SPID +AND n1.fn.exist('//p:CursorPlan/@CursorConcurrency[.="Optimistic"]') = 1 +AND qs.is_cursor = 1 +OPTION (RECOMPILE); -IF @IgnoreSqlHandles IS NOT NULL - AND LEN(@IgnoreSqlHandles) > 0 -BEGIN - RAISERROR(N'Processing SQL Handles To Ignore', 0, 1) WITH NOWAIT; - SET @individual = ''; - WHILE LEN(@IgnoreSqlHandles) > 0 - BEGIN - IF PATINDEX('%,%', @IgnoreSqlHandles) > 0 - BEGIN - SET @individual = SUBSTRING(@IgnoreSqlHandles, 0, PATINDEX('%,%',@IgnoreSqlHandles)) ; - - INSERT INTO #ignore_sql_handles - SELECT CAST('' AS XML).value('xs:hexBinary( substring(sql:variable("@individual"), sql:column("t.pos")) )', 'varbinary(max)') - FROM (SELECT CASE SUBSTRING(@individual, 1, 2) WHEN '0x' THEN 3 ELSE 0 END) AS t(pos) - OPTION (RECOMPILE) ; - - --SELECT CAST(SUBSTRING(@individual, 1, 2) AS BINARY(8)); +RAISERROR(N'Checking if cursor is Forward Only', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +UPDATE b +SET b.is_forward_only_cursor = 1 +FROM ##BlitzCacheProcs b +JOIN #statements AS qs +ON b.SqlHandle = qs.SqlHandle +CROSS APPLY qs.statement.nodes('/p:StmtCursor') AS n1(fn) +WHERE SPID = @@SPID +AND n1.fn.exist('//p:CursorPlan/@ForwardOnly[.="true"]') = 1 +AND qs.is_cursor = 1 +OPTION (RECOMPILE); - SET @IgnoreSqlHandles = SUBSTRING(@IgnoreSqlHandles, LEN(@individual + ',') + 1, LEN(@IgnoreSqlHandles)) ; - END; - ELSE - BEGIN - SET @individual = @IgnoreSqlHandles; - SET @IgnoreSqlHandles = NULL; +RAISERROR(N'Checking if cursor is Fast Forward', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +UPDATE b +SET b.is_fast_forward_cursor = 1 +FROM ##BlitzCacheProcs b +JOIN #statements AS qs +ON b.SqlHandle = qs.SqlHandle +CROSS APPLY qs.statement.nodes('/p:StmtCursor') AS n1(fn) +WHERE SPID = @@SPID +AND n1.fn.exist('//p:CursorPlan/@CursorActualType[.="FastForward"]') = 1 +AND qs.is_cursor = 1 +OPTION (RECOMPILE); - INSERT INTO #ignore_sql_handles - SELECT CAST('' AS XML).value('xs:hexBinary( substring(sql:variable("@individual"), sql:column("t.pos")) )', 'varbinary(max)') - FROM (SELECT CASE SUBSTRING(@individual, 1, 2) WHEN '0x' THEN 3 ELSE 0 END) AS t(pos) - OPTION (RECOMPILE) ; - --SELECT CAST(SUBSTRING(@individual, 1, 2) AS VARBINARY(MAX)) ; - END; - END; -END; +RAISERROR(N'Checking for Dynamic cursors', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +UPDATE b +SET b.is_cursor_dynamic = 1 +FROM ##BlitzCacheProcs b +JOIN #statements AS qs +ON b.SqlHandle = qs.SqlHandle +CROSS APPLY qs.statement.nodes('/p:StmtCursor') AS n1(fn) +WHERE SPID = @@SPID +AND n1.fn.exist('//p:CursorPlan/@CursorActualType[.="Dynamic"]') = 1 +AND qs.is_cursor = 1 +OPTION (RECOMPILE); -IF @StoredProcName IS NOT NULL AND @StoredProcName <> N'' +END +IF @ExpertMode > 0 BEGIN - RAISERROR(N'Setting up filter for stored procedure name', 0, 1) WITH NOWAIT; - - DECLARE @function_search_sql NVARCHAR(MAX) = N'' - - INSERT #only_sql_handles - ( sql_handle ) - SELECT ISNULL(deps.sql_handle, CONVERT(VARBINARY(64),'0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000')) - FROM sys.dm_exec_procedure_stats AS deps - WHERE OBJECT_NAME(deps.object_id, deps.database_id) = @StoredProcName - - UNION ALL - - SELECT ISNULL(dets.sql_handle, CONVERT(VARBINARY(64),'0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000')) - FROM sys.dm_exec_trigger_stats AS dets - WHERE OBJECT_NAME(dets.object_id, dets.database_id) = @StoredProcName - OPTION (RECOMPILE); +RAISERROR(N'Checking for bad scans and plan forcing', 0, 1) WITH NOWAIT; +;WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +UPDATE b +SET +b.is_table_scan = x.is_table_scan, +b.backwards_scan = x.backwards_scan, +b.forced_index = x.forced_index, +b.forced_seek = x.forced_seek, +b.forced_scan = x.forced_scan +FROM ##BlitzCacheProcs b +JOIN ( +SELECT + qs.SqlHandle, + 0 AS is_table_scan, + q.n.exist('@ScanDirection[.="BACKWARD"]') AS backwards_scan, + q.n.value('@ForcedIndex', 'bit') AS forced_index, + q.n.value('@ForceSeek', 'bit') AS forced_seek, + q.n.value('@ForceScan', 'bit') AS forced_scan +FROM #relop qs +CROSS APPLY qs.relop.nodes('//p:IndexScan') AS q(n) +UNION ALL +SELECT + qs.SqlHandle, + 1 AS is_table_scan, + q.n.exist('@ScanDirection[.="BACKWARD"]') AS backwards_scan, + q.n.value('@ForcedIndex', 'bit') AS forced_index, + q.n.value('@ForceSeek', 'bit') AS forced_seek, + q.n.value('@ForceScan', 'bit') AS forced_scan +FROM #relop qs +CROSS APPLY qs.relop.nodes('//p:TableScan') AS q(n) +) AS x ON b.SqlHandle = x.SqlHandle +WHERE SPID = @@SPID +OPTION (RECOMPILE); +END; - IF EXISTS (SELECT 1/0 FROM sys.all_objects AS o WHERE o.name = 'dm_exec_function_stats') - BEGIN - SET @function_search_sql = @function_search_sql + N' - SELECT ISNULL(defs.sql_handle, CONVERT(VARBINARY(64),''0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'')) - FROM sys.dm_exec_function_stats AS defs - WHERE OBJECT_NAME(defs.object_id, defs.database_id) = @i_StoredProcName - OPTION (RECOMPILE); - ' - INSERT #only_sql_handles ( sql_handle ) - EXEC sys.sp_executesql @function_search_sql, N'@i_StoredProcName NVARCHAR(128)', @StoredProcName - END - - IF (SELECT COUNT(*) FROM #only_sql_handles) = 0 - BEGIN - RAISERROR(N'No information for that stored procedure was found.', 0, 1) WITH NOWAIT; - RETURN; - END; -END; +IF @ExpertMode > 0 +BEGIN +RAISERROR(N'Checking for computed columns that reference scalar UDFs', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +UPDATE ##BlitzCacheProcs +SET is_computed_scalar = x.computed_column_function +FROM ( +SELECT qs.SqlHandle, + n.fn.value('count(distinct-values(//p:UserDefinedFunction[not(@IsClrFunction)]))', 'INT') AS computed_column_function +FROM #relop qs +CROSS APPLY relop.nodes('/p:RelOp/p:ComputeScalar/p:DefinedValues/p:DefinedValue/p:ScalarOperator') n(fn) +WHERE n.fn.exist('/p:RelOp/p:ComputeScalar/p:DefinedValues/p:DefinedValue/p:ColumnReference[(@ComputedColumn[.="1"])]') = 1 +) AS x +WHERE ##BlitzCacheProcs.SqlHandle = x.SqlHandle +AND SPID = @@SPID +OPTION (RECOMPILE); +END; +RAISERROR(N'Checking for filters that reference scalar UDFs', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +UPDATE ##BlitzCacheProcs +SET is_computed_filter = x.filter_function +FROM ( +SELECT +r.SqlHandle, +c.n.value('count(distinct-values(//p:UserDefinedFunction[not(@IsClrFunction)]))', 'INT') AS filter_function +FROM #relop AS r +CROSS APPLY r.relop.nodes('/p:RelOp/p:Filter/p:Predicate/p:ScalarOperator/p:Compare/p:ScalarOperator/p:UserDefinedFunction') c(n) +) x +WHERE ##BlitzCacheProcs.SqlHandle = x.SqlHandle +AND SPID = @@SPID +OPTION (RECOMPILE); -IF ((@OnlyQueryHashes IS NOT NULL AND LEN(@OnlyQueryHashes) > 0) - OR (@IgnoreQueryHashes IS NOT NULL AND LEN(@IgnoreQueryHashes) > 0)) - AND LEFT(@QueryFilter, 3) IN ('pro', 'fun') +IF @ExpertMode > 0 BEGIN - RAISERROR('You cannot limit by query hash and filter by stored procedure', 16, 1); - RETURN; -END; +RAISERROR(N'Checking modification queries that hit lots of indexes', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p), +IndexOps AS +( + SELECT + r.QueryHash, + c.n.value('@PhysicalOp', 'VARCHAR(100)') AS op_name, + c.n.exist('@PhysicalOp[.="Index Insert"]') AS ii, + c.n.exist('@PhysicalOp[.="Index Update"]') AS iu, + c.n.exist('@PhysicalOp[.="Index Delete"]') AS id, + c.n.exist('@PhysicalOp[.="Clustered Index Insert"]') AS cii, + c.n.exist('@PhysicalOp[.="Clustered Index Update"]') AS ciu, + c.n.exist('@PhysicalOp[.="Clustered Index Delete"]') AS cid, + c.n.exist('@PhysicalOp[.="Table Insert"]') AS ti, + c.n.exist('@PhysicalOp[.="Table Update"]') AS tu, + c.n.exist('@PhysicalOp[.="Table Delete"]') AS td + FROM #relop AS r + CROSS APPLY r.relop.nodes('/p:RelOp') c(n) + OUTER APPLY r.relop.nodes('/p:RelOp/p:ScalarInsert/p:Object') q(n) + OUTER APPLY r.relop.nodes('/p:RelOp/p:Update/p:Object') o2(n) + OUTER APPLY r.relop.nodes('/p:RelOp/p:SimpleUpdate/p:Object') o3(n) +), iops AS +( + SELECT ios.QueryHash, + SUM(CONVERT(TINYINT, ios.ii)) AS index_insert_count, + SUM(CONVERT(TINYINT, ios.iu)) AS index_update_count, + SUM(CONVERT(TINYINT, ios.id)) AS index_delete_count, + SUM(CONVERT(TINYINT, ios.cii)) AS cx_insert_count, + SUM(CONVERT(TINYINT, ios.ciu)) AS cx_update_count, + SUM(CONVERT(TINYINT, ios.cid)) AS cx_delete_count, + SUM(CONVERT(TINYINT, ios.ti)) AS table_insert_count, + SUM(CONVERT(TINYINT, ios.tu)) AS table_update_count, + SUM(CONVERT(TINYINT, ios.td)) AS table_delete_count + FROM IndexOps AS ios + WHERE ios.op_name IN ('Index Insert', 'Index Delete', 'Index Update', + 'Clustered Index Insert', 'Clustered Index Delete', 'Clustered Index Update', + 'Table Insert', 'Table Delete', 'Table Update') + GROUP BY ios.QueryHash) +UPDATE b +SET b.index_insert_count = iops.index_insert_count, + b.index_update_count = iops.index_update_count, + b.index_delete_count = iops.index_delete_count, + b.cx_insert_count = iops.cx_insert_count, + b.cx_update_count = iops.cx_update_count, + b.cx_delete_count = iops.cx_delete_count, + b.table_insert_count = iops.table_insert_count, + b.table_update_count = iops.table_update_count, + b.table_delete_count = iops.table_delete_count +FROM ##BlitzCacheProcs AS b +JOIN iops ON iops.QueryHash = b.QueryHash +WHERE SPID = @@SPID +OPTION (RECOMPILE); +END; -/* If the user is attempting to limit by query hash, set up the - #only_query_hashes temp table. This will be used to narrow down - results. - Just a reminder: Using @OnlyQueryHashes will ignore stored - procedures and triggers. - */ -IF @OnlyQueryHashes IS NOT NULL - AND LEN(@OnlyQueryHashes) > 0 +IF @ExpertMode > 0 BEGIN - RAISERROR(N'Setting up filter for Query Hashes', 0, 1) WITH NOWAIT; - SET @individual = ''; +RAISERROR(N'Checking for Spatial index use', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +UPDATE ##BlitzCacheProcs +SET is_spatial = x.is_spatial +FROM ( +SELECT qs.SqlHandle, + 1 AS is_spatial +FROM #relop qs +CROSS APPLY relop.nodes('/p:RelOp//p:Object') n(fn) +WHERE n.fn.exist('(@IndexKind[.="Spatial"])') = 1 +) AS x +WHERE ##BlitzCacheProcs.SqlHandle = x.SqlHandle +AND SPID = @@SPID +OPTION (RECOMPILE); +END; - WHILE LEN(@OnlyQueryHashes) > 0 - BEGIN - IF PATINDEX('%,%', @OnlyQueryHashes) > 0 - BEGIN - SET @individual = SUBSTRING(@OnlyQueryHashes, 0, PATINDEX('%,%',@OnlyQueryHashes)) ; - - INSERT INTO #only_query_hashes - SELECT CAST('' AS XML).value('xs:hexBinary( substring(sql:variable("@individual"), sql:column("t.pos")) )', 'varbinary(max)') - FROM (SELECT CASE SUBSTRING(@individual, 1, 2) WHEN '0x' THEN 3 ELSE 0 END) AS t(pos) - OPTION (RECOMPILE) ; - - --SELECT CAST(SUBSTRING(@individual, 1, 2) AS BINARY(8)); - SET @OnlyQueryHashes = SUBSTRING(@OnlyQueryHashes, LEN(@individual + ',') + 1, LEN(@OnlyQueryHashes)) ; - END; - ELSE - BEGIN - SET @individual = @OnlyQueryHashes; - SET @OnlyQueryHashes = NULL; +RAISERROR('Checking for wonky Index Spools', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES ( + 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) +, selects +AS ( SELECT s.QueryHash + FROM #statements AS s + WHERE s.statement.exist('/p:StmtSimple/@StatementType[.="SELECT"]') = 1 ) +, spools +AS ( SELECT DISTINCT r.QueryHash, + c.n.value('@EstimateRows', 'FLOAT') AS estimated_rows, + c.n.value('@EstimateIO', 'FLOAT') AS estimated_io, + c.n.value('@EstimateCPU', 'FLOAT') AS estimated_cpu, + c.n.value('@EstimateRebinds', 'FLOAT') AS estimated_rebinds +FROM #relop AS r +JOIN selects AS s +ON s.QueryHash = r.QueryHash +CROSS APPLY r.relop.nodes('/p:RelOp') AS c(n) +WHERE r.relop.exist('/p:RelOp[@PhysicalOp="Index Spool" and @LogicalOp="Eager Spool"]') = 1 +) +UPDATE b + SET b.index_spool_rows = sp.estimated_rows, + b.index_spool_cost = ((sp.estimated_io * sp.estimated_cpu) * CASE WHEN sp.estimated_rebinds < 1 THEN 1 ELSE sp.estimated_rebinds END) +FROM ##BlitzCacheProcs b +JOIN spools sp +ON sp.QueryHash = b.QueryHash +OPTION (RECOMPILE); - INSERT INTO #only_query_hashes - SELECT CAST('' AS XML).value('xs:hexBinary( substring(sql:variable("@individual"), sql:column("t.pos")) )', 'varbinary(max)') - FROM (SELECT CASE SUBSTRING(@individual, 1, 2) WHEN '0x' THEN 3 ELSE 0 END) AS t(pos) - OPTION (RECOMPILE) ; +RAISERROR('Checking for wonky Table Spools', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES ( + 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) +, selects +AS ( SELECT s.QueryHash + FROM #statements AS s + WHERE s.statement.exist('/p:StmtSimple/@StatementType[.="SELECT"]') = 1 ) +, spools +AS ( SELECT DISTINCT r.QueryHash, + c.n.value('@EstimateRows', 'FLOAT') AS estimated_rows, + c.n.value('@EstimateIO', 'FLOAT') AS estimated_io, + c.n.value('@EstimateCPU', 'FLOAT') AS estimated_cpu, + c.n.value('@EstimateRebinds', 'FLOAT') AS estimated_rebinds +FROM #relop AS r +JOIN selects AS s +ON s.QueryHash = r.QueryHash +CROSS APPLY r.relop.nodes('/p:RelOp') AS c(n) +WHERE r.relop.exist('/p:RelOp[@PhysicalOp="Table Spool" and @LogicalOp="Lazy Spool"]') = 1 +) +UPDATE b + SET b.table_spool_rows = (sp.estimated_rows * sp.estimated_rebinds), + b.table_spool_cost = ((sp.estimated_io * sp.estimated_cpu * sp.estimated_rows) * CASE WHEN sp.estimated_rebinds < 1 THEN 1 ELSE sp.estimated_rebinds END) +FROM ##BlitzCacheProcs b +JOIN spools sp +ON sp.QueryHash = b.QueryHash +OPTION (RECOMPILE); - --SELECT CAST(SUBSTRING(@individual, 1, 2) AS VARBINARY(MAX)) ; - END; - END; -END; -/* If the user is setting up a list of query hashes to ignore, those - values will be inserted into #ignore_query_hashes. This is used to - exclude values from query results. +RAISERROR('Checking for selects that cause non-spill and index spool writes', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES ( + 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) +, selects +AS ( SELECT CONVERT(BINARY(8), + RIGHT('0000000000000000' + + SUBSTRING(s.statement.value('(/p:StmtSimple/@QueryHash)[1]', 'VARCHAR(18)'), + 3, 18), 16), 2) AS QueryHash + FROM #statements AS s + JOIN ##BlitzCacheProcs b + ON s.QueryHash = b.QueryHash + WHERE b.index_spool_rows IS NULL + AND b.index_spool_cost IS NULL + AND b.table_spool_cost IS NULL + AND b.table_spool_rows IS NULL + AND b.is_big_spills IS NULL + AND b.AverageWrites > 1024. + AND s.statement.exist('/p:StmtSimple/@StatementType[.="SELECT"]') = 1 +) +UPDATE b + SET b.select_with_writes = 1 +FROM ##BlitzCacheProcs b +JOIN selects AS s +ON s.QueryHash = b.QueryHash +AND b.AverageWrites > 1024.; - Just a reminder: Using @IgnoreQueryHashes will ignore stored - procedures and triggers. - */ -IF @IgnoreQueryHashes IS NOT NULL - AND LEN(@IgnoreQueryHashes) > 0 +/* 2012+ only */ +IF @v >= 11 BEGIN - RAISERROR(N'Setting up filter to ignore query hashes', 0, 1) WITH NOWAIT; - SET @individual = '' ; - WHILE LEN(@IgnoreQueryHashes) > 0 - BEGIN - IF PATINDEX('%,%', @IgnoreQueryHashes) > 0 - BEGIN - SET @individual = SUBSTRING(@IgnoreQueryHashes, 0, PATINDEX('%,%',@IgnoreQueryHashes)) ; - - INSERT INTO #ignore_query_hashes - SELECT CAST('' AS XML).value('xs:hexBinary( substring(sql:variable("@individual"), sql:column("t.pos")) )', 'varbinary(max)') - FROM (SELECT CASE SUBSTRING(@individual, 1, 2) WHEN '0x' THEN 3 ELSE 0 END) AS t(pos) - OPTION (RECOMPILE) ; - - SET @IgnoreQueryHashes = SUBSTRING(@IgnoreQueryHashes, LEN(@individual + ',') + 1, LEN(@IgnoreQueryHashes)) ; - END; - ELSE - BEGIN - SET @individual = @IgnoreQueryHashes ; - SET @IgnoreQueryHashes = NULL ; + RAISERROR(N'Checking for forced serialization', 0, 1) WITH NOWAIT; + WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) + UPDATE ##BlitzCacheProcs + SET is_forced_serial = 1 + FROM #query_plan qp + WHERE qp.SqlHandle = ##BlitzCacheProcs.SqlHandle + AND SPID = @@SPID + AND query_plan.exist('/p:QueryPlan/@NonParallelPlanReason') = 1 + AND (##BlitzCacheProcs.is_parallel = 0 OR ##BlitzCacheProcs.is_parallel IS NULL) + OPTION (RECOMPILE); + + IF @ExpertMode > 0 + BEGIN + RAISERROR(N'Checking for ColumnStore queries operating in Row Mode instead of Batch Mode', 0, 1) WITH NOWAIT; + WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) + UPDATE ##BlitzCacheProcs + SET columnstore_row_mode = x.is_row_mode + FROM ( + SELECT + qs.SqlHandle, + relop.exist('/p:RelOp[(@EstimatedExecutionMode[.="Row"])]') AS is_row_mode + FROM #relop qs + WHERE [relop].exist('/p:RelOp/p:IndexScan[(@Storage[.="ColumnStore"])]') = 1 + ) AS x + WHERE ##BlitzCacheProcs.SqlHandle = x.SqlHandle + AND SPID = @@SPID + OPTION (RECOMPILE); + END; - INSERT INTO #ignore_query_hashes - SELECT CAST('' AS XML).value('xs:hexBinary( substring(sql:variable("@individual"), sql:column("t.pos")) )', 'varbinary(max)') - FROM (SELECT CASE SUBSTRING(@individual, 1, 2) WHEN '0x' THEN 3 ELSE 0 END) AS t(pos) - OPTION (RECOMPILE) ; - END; - END; END; -IF @ConfigurationDatabaseName IS NOT NULL +/* 2014+ only */ +IF @v >= 12 BEGIN - RAISERROR(N'Reading values from Configuration Database', 0, 1) WITH NOWAIT; - DECLARE @config_sql NVARCHAR(MAX) = N'INSERT INTO #configuration SELECT parameter_name, value FROM ' - + QUOTENAME(@ConfigurationDatabaseName) - + '.' + QUOTENAME(@ConfigurationSchemaName) - + '.' + QUOTENAME(@ConfigurationTableName) - + ' ; ' ; - EXEC(@config_sql); -END; + RAISERROR('Checking for downlevel cardinality estimators being used on SQL Server 2014.', 0, 1) WITH NOWAIT; -RAISERROR(N'Setting up variables', 0, 1) WITH NOWAIT; -DECLARE @sql NVARCHAR(MAX) = N'', - @insert_list NVARCHAR(MAX) = N'', - @plans_triggers_select_list NVARCHAR(MAX) = N'', - @body NVARCHAR(MAX) = N'', - @body_where NVARCHAR(MAX) = N'WHERE 1 = 1 ' + @nl, - @body_order NVARCHAR(MAX) = N'ORDER BY #sortable# DESC OPTION (RECOMPILE) ', - - @q NVARCHAR(1) = N'''', - @pv VARCHAR(20), - @pos TINYINT, - @v DECIMAL(6,2), - @build INT; + WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) + UPDATE p + SET downlevel_estimator = CASE WHEN statement.value('min(//p:StmtSimple/@CardinalityEstimationModelVersion)', 'int') < (@v * 10) THEN 1 END + FROM ##BlitzCacheProcs p + JOIN #statements s ON p.QueryHash = s.QueryHash + WHERE SPID = @@SPID + OPTION (RECOMPILE); +END ; +/* 2016+ only */ +IF @v >= 13 AND @ExpertMode > 0 +BEGIN + RAISERROR('Checking for row level security in 2016 only', 0, 1) WITH NOWAIT; -RAISERROR (N'Determining SQL Server version.',0,1) WITH NOWAIT; + WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) + UPDATE p + SET p.is_row_level = 1 + FROM ##BlitzCacheProcs p + JOIN #statements s ON p.QueryHash = s.QueryHash + WHERE SPID = @@SPID + AND statement.exist('/p:StmtSimple/@SecurityPolicyApplied[.="true"]') = 1 + OPTION (RECOMPILE); +END ; -INSERT INTO #checkversion (version) -SELECT CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)) +/* 2017+ only */ +IF @v >= 14 OR (@v = 13 AND @build >= 5026) +BEGIN + +IF @ExpertMode > 0 +BEGIN +RAISERROR('Gathering stats information', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +INSERT INTO #stats_agg +SELECT qp.SqlHandle, + x.c.value('@LastUpdate', 'DATETIME2(7)') AS LastUpdate, + x.c.value('@ModificationCount', 'BIGINT') AS ModificationCount, + x.c.value('@SamplingPercent', 'FLOAT') AS SamplingPercent, + x.c.value('@Statistics', 'NVARCHAR(258)') AS [Statistics], + x.c.value('@Table', 'NVARCHAR(258)') AS [Table], + x.c.value('@Schema', 'NVARCHAR(258)') AS [Schema], + x.c.value('@Database', 'NVARCHAR(258)') AS [Database] +FROM #query_plan AS qp +CROSS APPLY qp.query_plan.nodes('//p:OptimizerStatsUsage/p:StatisticsInfo') x (c) OPTION (RECOMPILE); -SELECT @v = common_version , - @build = build -FROM #checkversion +RAISERROR('Checking for stale stats', 0, 1) WITH NOWAIT; +WITH stale_stats AS ( + SELECT sa.SqlHandle + FROM #stats_agg AS sa + GROUP BY sa.SqlHandle + HAVING MAX(sa.LastUpdate) <= DATEADD(DAY, -7, SYSDATETIME()) + AND AVG(sa.ModificationCount) >= 100000 +) +UPDATE b +SET stale_stats = 1 +FROM ##BlitzCacheProcs b +JOIN stale_stats os +ON b.SqlHandle = os.SqlHandle +AND b.SPID = @@SPID OPTION (RECOMPILE); - -IF (@SortOrder IN ('memory grant', 'avg memory grant')) AND @VersionShowsMemoryGrants = 0 -BEGIN - RAISERROR('Your version of SQL does not support sorting by memory grant or average memory grant. Please use another sort order.', 16, 1); - RETURN; END; -IF (@SortOrder IN ('spills', 'avg spills') AND @VersionShowsSpills = 0) +IF @v >= 14 AND @ExpertMode > 0 BEGIN - RAISERROR('Your version of SQL does not support sorting by spills. Please use another sort order.', 16, 1); - RETURN; -END; +RAISERROR('Checking for adaptive joins', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p), +aj AS ( + SELECT + SqlHandle + FROM #relop AS r + CROSS APPLY r.relop.nodes('//p:RelOp') x(c) + WHERE x.c.exist('@IsAdaptive[.=1]') = 1 +) +UPDATE b +SET b.is_adaptive = 1 +FROM ##BlitzCacheProcs b +JOIN aj +ON b.SqlHandle = aj.SqlHandle +AND b.SPID = @@SPID +OPTION (RECOMPILE); +END; + +IF ((@v >= 14 + OR (@v = 13 AND @build >= 5026) + OR (@v = 12 AND @build >= 6024)) + AND @ExpertMode > 0) + +BEGIN; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p), +row_goals AS( +SELECT qs.QueryHash +FROM #relop qs +WHERE relop.value('sum(/p:RelOp/@EstimateRowsWithoutRowGoal)', 'float') > 0 +) +UPDATE b +SET b.is_row_goal = 1 +FROM ##BlitzCacheProcs b +JOIN row_goals +ON b.QueryHash = row_goals.QueryHash +AND b.SPID = @@SPID +OPTION (RECOMPILE); +END ; -IF ((LEFT(@QueryFilter, 3) = 'fun') AND (@v < 13)) -BEGIN - RAISERROR('Your version of SQL does not support filtering by functions. Please use another filter.', 16, 1); - RETURN; END; -RAISERROR (N'Creating dynamic SQL based on SQL Server version.',0,1) WITH NOWAIT; -SET @insert_list += N' -SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -INSERT INTO ##BlitzCacheProcs (SPID, QueryType, DatabaseName, AverageCPU, TotalCPU, AverageCPUPerMinute, PercentCPUByType, PercentDurationByType, - PercentReadsByType, PercentExecutionsByType, AverageDuration, TotalDuration, AverageReads, TotalReads, ExecutionCount, - ExecutionsPerMinute, TotalWrites, AverageWrites, PercentWritesByType, WritesPerMinute, PlanCreationTime, - LastExecutionTime, LastCompletionTime, StatementStartOffset, StatementEndOffset, PlanGenerationNum, MinReturnedRows, MaxReturnedRows, AverageReturnedRows, TotalReturnedRows, - LastReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, MaxUsedGrantKB, PercentMemoryGrantUsed, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, - QueryText, QueryPlan, TotalWorkerTimeForType, TotalElapsedTimeForType, TotalReadsForType, - TotalExecutionCountForType, TotalWritesForType, SqlHandle, PlanHandle, QueryHash, QueryPlanHash, - min_worker_time, max_worker_time, is_parallel, min_elapsed_time, max_elapsed_time, age_minutes, age_minutes_lifetime, Pattern) ' ; - -SET @body += N' -FROM (SELECT TOP (@Top) x.*, xpa.*, - CAST((CASE WHEN DATEDIFF(mi, cached_time, GETDATE()) > 0 AND execution_count > 1 - THEN DATEDIFF(mi, cached_time, GETDATE()) - ELSE NULL END) as MONEY) as age_minutes, - CAST((CASE WHEN DATEDIFF(mi, cached_time, last_execution_time) > 0 AND execution_count > 1 - THEN DATEDIFF(mi, cached_time, last_execution_time) - ELSE Null END) as MONEY) as age_minutes_lifetime - FROM sys.#view# x - CROSS APPLY (SELECT * FROM sys.dm_exec_plan_attributes(x.plan_handle) AS ixpa - WHERE ixpa.attribute = ''dbid'') AS xpa ' + @nl ; - -IF @SortOrder = 'duplicate' /* Issue #3345 */ - BEGIN - SET @body += N' INNER JOIN #duplicate_query_filter AS dqf ON x.sql_handle = dqf.sql_handle AND x.plan_handle = dqf.plan_handle AND x.creation_time = dqf.duplicate_creation_time ' + @nl ; - END +/* END Testing using XML nodes to speed up processing */ -IF @VersionShowsAirQuoteActualPlans = 1 - BEGIN - SET @body += N' CROSS APPLY sys.dm_exec_query_plan_stats(x.plan_handle) AS deqps ' + @nl ; - END -SET @body += N' WHERE 1 = 1 ' + @nl ; +/* Update to grab stored procedure name for individual statements */ +RAISERROR(N'Attempting to get stored procedure name for individual statements', 0, 1) WITH NOWAIT; +UPDATE p +SET QueryType = QueryType + ' (parent ' + + + QUOTENAME(OBJECT_SCHEMA_NAME(s.object_id, s.database_id)) + + '.' + + QUOTENAME(OBJECT_NAME(s.object_id, s.database_id)) + ')' +FROM ##BlitzCacheProcs p + JOIN sys.dm_exec_procedure_stats s ON p.SqlHandle = s.sql_handle +WHERE QueryType = 'Statement' +AND SPID = @@SPID +OPTION (RECOMPILE); - IF EXISTS (SELECT * FROM sys.all_objects o WHERE o.name = 'dm_hadr_database_replica_states') - BEGIN - RAISERROR(N'Ignoring readable secondaries databases by default', 0, 1) WITH NOWAIT; - SET @body += N' AND CAST(xpa.value AS INT) NOT IN (SELECT database_id FROM #ReadableDBs)' + @nl ; - END +/* Update to grab stored procedure name for individual statements when PSPO is detected */ +UPDATE p +SET QueryType = QueryType + ' (parent ' + + + QUOTENAME(OBJECT_SCHEMA_NAME(s.object_id, s.database_id)) + + '.' + + QUOTENAME(OBJECT_NAME(s.object_id, s.database_id)) + ')' +FROM ##BlitzCacheProcs p + OUTER APPLY ( + SELECT REPLACE(REPLACE(REPLACE(REPLACE(p.QueryText, ' (', '('), '( ', '('), ' =', '='), '= ', '=') AS NormalizedQueryText + ) a + OUTER APPLY ( + SELECT CHARINDEX('option(PLAN PER VALUE(ObjectID=', a.NormalizedQueryText) AS OptionStart + ) b + OUTER APPLY ( + SELECT SUBSTRING(a.NormalizedQueryText, b.OptionStart + 31, LEN(a.NormalizedQueryText) - b.OptionStart - 30) AS OptionSubstring + WHERE b.OptionStart > 0 + ) c + OUTER APPLY ( + SELECT PATINDEX('%[^0-9]%', c.OptionSubstring) AS ObjectLength + ) d + OUTER APPLY ( + SELECT TRY_CAST(SUBSTRING(OptionSubstring, 1, d.ObjectLength - 1) AS INT) AS ObjectId + ) e + JOIN sys.dm_exec_procedure_stats s ON DB_ID(p.DatabaseName) = s.database_id AND e.ObjectId = s.object_id +WHERE p.QueryType = 'Statement' +AND p.SPID = @@SPID +AND s.object_id IS NOT NULL +OPTION (RECOMPILE); -IF @IgnoreSystemDBs = 1 +RAISERROR(N'Attempting to get function name for individual statements', 0, 1) WITH NOWAIT; +DECLARE @function_update_sql NVARCHAR(MAX) = N'' +IF EXISTS (SELECT 1/0 FROM sys.all_objects AS o WHERE o.name = 'dm_exec_function_stats') BEGIN - RAISERROR(N'Ignoring system databases by default', 0, 1) WITH NOWAIT; - SET @body += N' AND COALESCE(LOWER(DB_NAME(CAST(xpa.value AS INT))), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'', ''dbmaintenance'', ''dbadmin'', ''dbatools'') AND COALESCE(DB_NAME(CAST(xpa.value AS INT)), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ; - END; + SET @function_update_sql = @function_update_sql + N' + UPDATE p + SET QueryType = QueryType + '' (parent '' + + + QUOTENAME(OBJECT_SCHEMA_NAME(s.object_id, s.database_id)) + + ''.'' + + QUOTENAME(OBJECT_NAME(s.object_id, s.database_id)) + '')'' + FROM ##BlitzCacheProcs p + JOIN sys.dm_exec_function_stats s ON p.SqlHandle = s.sql_handle + WHERE QueryType = ''Statement'' + AND SPID = @@SPID + OPTION (RECOMPILE); + ' + EXEC sys.sp_executesql @function_update_sql + END -IF @DatabaseName IS NOT NULL OR @DatabaseName <> N'' - BEGIN - RAISERROR(N'Filtering database name chosen', 0, 1) WITH NOWAIT; - SET @body += N' AND CAST(xpa.value AS BIGINT) = DB_ID(N' - + QUOTENAME(@DatabaseName, N'''') - + N') ' + @nl; - END; -IF (SELECT COUNT(*) FROM #only_sql_handles) > 0 +/* Trace Flag Checks 2012 SP3, 2014 SP2 and 2016 SP1 only)*/ +IF @v >= 11 BEGIN - RAISERROR(N'Including only chosen SQL Handles', 0, 1) WITH NOWAIT; - SET @body += N' AND EXISTS(SELECT 1/0 FROM #only_sql_handles q WHERE q.sql_handle = x.sql_handle) ' + @nl ; -END; -IF (SELECT COUNT(*) FROM #ignore_sql_handles) > 0 -BEGIN - RAISERROR(N'Including only chosen SQL Handles', 0, 1) WITH NOWAIT; - SET @body += N' AND NOT EXISTS(SELECT 1/0 FROM #ignore_sql_handles q WHERE q.sql_handle = x.sql_handle) ' + @nl ; -END; +RAISERROR(N'Trace flag checks', 0, 1) WITH NOWAIT; +;WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +, tf_pretty AS ( +SELECT qp.QueryHash, + qp.SqlHandle, + q.n.value('@Value', 'INT') AS trace_flag, + q.n.value('@Scope', 'VARCHAR(10)') AS scope +FROM #query_plan qp +CROSS APPLY qp.query_plan.nodes('/p:QueryPlan/p:TraceFlags/p:TraceFlag') AS q(n) +) +INSERT INTO #trace_flags +SELECT DISTINCT tf1.SqlHandle , tf1.QueryHash, + STUFF(( + SELECT DISTINCT ', ' + CONVERT(VARCHAR(5), tf2.trace_flag) + FROM tf_pretty AS tf2 + WHERE tf1.SqlHandle = tf2.SqlHandle + AND tf1.QueryHash = tf2.QueryHash + AND tf2.scope = 'Global' + FOR XML PATH(N'')), 1, 2, N'' + ) AS global_trace_flags, + STUFF(( + SELECT DISTINCT ', ' + CONVERT(VARCHAR(5), tf2.trace_flag) + FROM tf_pretty AS tf2 + WHERE tf1.SqlHandle = tf2.SqlHandle + AND tf1.QueryHash = tf2.QueryHash + AND tf2.scope = 'Session' + FOR XML PATH(N'')), 1, 2, N'' + ) AS session_trace_flags +FROM tf_pretty AS tf1 +OPTION (RECOMPILE); -IF (SELECT COUNT(*) FROM #only_query_hashes) > 0 - AND (SELECT COUNT(*) FROM #ignore_query_hashes) = 0 - AND (SELECT COUNT(*) FROM #only_sql_handles) = 0 - AND (SELECT COUNT(*) FROM #ignore_sql_handles) = 0 -BEGIN - RAISERROR(N'Including only chosen Query Hashes', 0, 1) WITH NOWAIT; - SET @body += N' AND EXISTS(SELECT 1/0 FROM #only_query_hashes q WHERE q.query_hash = x.query_hash) ' + @nl ; -END; +UPDATE p +SET p.trace_flags_session = tf.session_trace_flags +FROM ##BlitzCacheProcs p +JOIN #trace_flags tf ON tf.QueryHash = p.QueryHash +WHERE SPID = @@SPID +OPTION (RECOMPILE); -/* filtering for query hashes */ -IF (SELECT COUNT(*) FROM #ignore_query_hashes) > 0 - AND (SELECT COUNT(*) FROM #only_query_hashes) = 0 -BEGIN - RAISERROR(N'Excluding chosen Query Hashes', 0, 1) WITH NOWAIT; - SET @body += N' AND NOT EXISTS(SELECT 1/0 FROM #ignore_query_hashes iq WHERE iq.query_hash = x.query_hash) ' + @nl ; END; -/* end filtering for query hashes */ -IF @DurationFilter IS NOT NULL - BEGIN - RAISERROR(N'Setting duration filter', 0, 1) WITH NOWAIT; - SET @body += N' AND (total_elapsed_time / 1000.0) / execution_count > @min_duration ' + @nl ; - END; -IF @MinutesBack IS NOT NULL - BEGIN - RAISERROR(N'Setting minutes back filter', 0, 1) WITH NOWAIT; - SET @body += N' AND DATEADD(SECOND, (x.last_elapsed_time / 1000000.), x.last_execution_time) >= DATEADD(MINUTE, @min_back, GETDATE()) ' + @nl ; - END; +RAISERROR(N'Checking for MSTVFs', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +UPDATE b +SET b.is_mstvf = 1 +FROM #relop AS r +JOIN ##BlitzCacheProcs AS b +ON b.SqlHandle = r.SqlHandle +WHERE r.relop.exist('/p:RelOp[(@EstimateRows="100" or @EstimateRows="1") and @LogicalOp="Table-valued function"]') = 1 +OPTION (RECOMPILE); -IF @SlowlySearchPlansFor IS NOT NULL - BEGIN - RAISERROR(N'Setting string search for @SlowlySearchPlansFor, so remember, this is gonna be slow', 0, 1) WITH NOWAIT; - SET @SlowlySearchPlansFor = REPLACE((REPLACE((REPLACE((REPLACE(@SlowlySearchPlansFor, N'[', N'_')), N']', N'_')), N'^', N'_')), N'''', N''''''); - SET @body_where += N' AND CAST(qp.query_plan AS NVARCHAR(MAX)) LIKE N''%' + @SlowlySearchPlansFor + N'%'' ' + @nl; - END +IF @ExpertMode > 0 +BEGIN +RAISERROR(N'Checking for many to many merge joins', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +UPDATE b +SET b.is_mm_join = 1 +FROM #relop AS r +JOIN ##BlitzCacheProcs AS b +ON b.SqlHandle = r.SqlHandle +WHERE r.relop.exist('/p:RelOp/p:Merge/@ManyToMany[.="1"]') = 1 +OPTION (RECOMPILE); +END ; -/* Apply the sort order here to only grab relevant plans. - This should make it faster to process since we'll be pulling back fewer - plans for processing. - */ -RAISERROR(N'Applying chosen sort order', 0, 1) WITH NOWAIT; -SELECT @body += N' ORDER BY ' + - CASE @SortOrder WHEN N'cpu' THEN N'total_worker_time' - WHEN N'reads' THEN N'total_logical_reads' - WHEN N'writes' THEN N'total_logical_writes' - WHEN N'duration' THEN N'total_elapsed_time' - WHEN N'executions' THEN N'execution_count' - WHEN N'compiles' THEN N'cached_time' - WHEN N'memory grant' THEN N'max_grant_kb' - WHEN N'unused grant' THEN N'max_grant_kb - max_used_grant_kb' - WHEN N'spills' THEN N'max_spills' - WHEN N'duplicate' THEN N'total_worker_time' /* Issue #3345 */ - /* And now the averages */ - WHEN N'avg cpu' THEN N'total_worker_time / execution_count' - WHEN N'avg reads' THEN N'total_logical_reads / execution_count' - WHEN N'avg writes' THEN N'total_logical_writes / execution_count' - WHEN N'avg duration' THEN N'total_elapsed_time / execution_count' - WHEN N'avg memory grant' THEN N'CASE WHEN max_grant_kb = 0 THEN 0 ELSE max_grant_kb / execution_count END' - WHEN N'avg spills' THEN N'CASE WHEN total_spills = 0 THEN 0 ELSE total_spills / execution_count END' - WHEN N'avg executions' THEN 'CASE WHEN execution_count = 0 THEN 0 - WHEN COALESCE(CAST((CASE WHEN DATEDIFF(mi, cached_time, GETDATE()) > 0 AND execution_count > 1 - THEN DATEDIFF(mi, cached_time, GETDATE()) - ELSE NULL END) as MONEY), CAST((CASE WHEN DATEDIFF(mi, cached_time, last_execution_time) > 0 AND execution_count > 1 - THEN DATEDIFF(mi, cached_time, last_execution_time) - ELSE Null END) as MONEY), 0) = 0 THEN 0 - ELSE CAST((1.00 * execution_count / COALESCE(CAST((CASE WHEN DATEDIFF(mi, cached_time, GETDATE()) > 0 AND execution_count > 1 - THEN DATEDIFF(mi, cached_time, GETDATE()) - ELSE NULL END) as MONEY), CAST((CASE WHEN DATEDIFF(mi, cached_time, last_execution_time) > 0 AND execution_count > 1 - THEN DATEDIFF(mi, cached_time, last_execution_time) - ELSE Null END) as MONEY))) AS money) - END ' - END + N' DESC ' + @nl ; +IF @ExpertMode > 0 +BEGIN +RAISERROR(N'Is Paul White Electric?', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p), +is_paul_white_electric AS ( +SELECT 1 AS [is_paul_white_electric], +r.SqlHandle +FROM #relop AS r +CROSS APPLY r.relop.nodes('//p:RelOp') c(n) +WHERE c.n.exist('@PhysicalOp[.="Switch"]') = 1 +) +UPDATE b +SET b.is_paul_white_electric = ipwe.is_paul_white_electric +FROM ##BlitzCacheProcs AS b +JOIN is_paul_white_electric ipwe +ON ipwe.SqlHandle = b.SqlHandle +WHERE b.SPID = @@SPID +OPTION (RECOMPILE); +END ; - -SET @body += N') AS qs - CROSS JOIN(SELECT SUM(execution_count) AS t_TotalExecs, - SUM(CAST(total_elapsed_time AS BIGINT) / 1000.0) AS t_TotalElapsed, - SUM(CAST(total_worker_time AS BIGINT) / 1000.0) AS t_TotalWorker, - SUM(CAST(total_logical_reads AS DECIMAL(30))) AS t_TotalReads, - SUM(CAST(total_logical_writes AS DECIMAL(30))) AS t_TotalWrites - FROM sys.#view#) AS t - CROSS APPLY sys.dm_exec_plan_attributes(qs.plan_handle) AS pa - CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) AS st - CROSS APPLY sys.dm_exec_query_plan(qs.plan_handle) AS qp ' + @nl ; -IF @VersionShowsAirQuoteActualPlans = 1 - BEGIN - SET @body += N' CROSS APPLY sys.dm_exec_query_plan_stats(qs.plan_handle) AS deqps ' + @nl ; - END +RAISERROR(N'Checking for non-sargable predicates', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES ( 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) +, nsarg + AS ( SELECT r.QueryHash, 1 AS fn, 0 AS jo, 0 AS lk + FROM #relop AS r + CROSS APPLY r.relop.nodes('/p:RelOp/p:IndexScan/p:Predicate/p:ScalarOperator/p:Compare/p:ScalarOperator') AS ca(x) + WHERE ( ca.x.exist('//p:ScalarOperator/p:Intrinsic/@FunctionName') = 1 + OR ca.x.exist('//p:ScalarOperator/p:IF') = 1 ) + UNION ALL + SELECT r.QueryHash, 0 AS fn, 1 AS jo, 0 AS lk + FROM #relop AS r + CROSS APPLY r.relop.nodes('/p:RelOp//p:ScalarOperator') AS ca(x) + WHERE r.relop.exist('/p:RelOp[contains(@LogicalOp, "Join")]') = 1 + AND ca.x.exist('//p:ScalarOperator[contains(@ScalarString, "Expr")]') = 1 + UNION ALL + SELECT r.QueryHash, 0 AS fn, 0 AS jo, 1 AS lk + FROM #relop AS r + CROSS APPLY r.relop.nodes('/p:RelOp/p:IndexScan/p:Predicate/p:ScalarOperator') AS ca(x) + CROSS APPLY ca.x.nodes('//p:Const') AS co(x) + WHERE ca.x.exist('//p:ScalarOperator/p:Intrinsic/@FunctionName[.="like"]') = 1 + AND ( ( co.x.value('substring(@ConstValue, 1, 1)', 'VARCHAR(100)') <> 'N' + AND co.x.value('substring(@ConstValue, 2, 1)', 'VARCHAR(100)') = '%' ) + OR ( co.x.value('substring(@ConstValue, 1, 1)', 'VARCHAR(100)') = 'N' + AND co.x.value('substring(@ConstValue, 3, 1)', 'VARCHAR(100)') = '%' ))), + d_nsarg + AS ( SELECT DISTINCT + nsarg.QueryHash + FROM nsarg + WHERE nsarg.fn = 1 + OR nsarg.jo = 1 + OR nsarg.lk = 1 ) +UPDATE b +SET b.is_nonsargable = 1 +FROM d_nsarg AS d +JOIN ##BlitzCacheProcs AS b + ON b.QueryHash = d.QueryHash +WHERE b.SPID = @@SPID +OPTION ( RECOMPILE ); -SET @body_where += N' AND pa.attribute = ' + QUOTENAME('dbid', @q ) + @nl ; +/*Begin implicit conversion and parameter info */ -IF @NoobSaibot = 1 -BEGIN - SET @body_where += N' AND qp.query_plan.exist(''declare namespace p="http://schemas.microsoft.com/sqlserver/2004/07/showplan";//p:StmtSimple//p:MissingIndex'') = 1' + @nl ; -END +RAISERROR(N'Getting information about implicit conversions and stored proc parameters', 0, 1) WITH NOWAIT; -SET @plans_triggers_select_list += N' -SELECT TOP (@Top) - @@SPID , - ''Procedure or Function: '' - + QUOTENAME(COALESCE(OBJECT_SCHEMA_NAME(qs.object_id, qs.database_id),'''')) - + ''.'' - + QUOTENAME(COALESCE(OBJECT_NAME(qs.object_id, qs.database_id),'''')) AS QueryType, - COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), N''-- N/A --'') AS DatabaseName, - (total_worker_time / 1000.0) / execution_count AS AvgCPU , - (total_worker_time / 1000.0) AS TotalCPU , - CASE WHEN total_worker_time = 0 THEN 0 - WHEN COALESCE(age_minutes, DATEDIFF(mi, qs.cached_time, qs.last_execution_time), 0) = 0 THEN 0 - ELSE CAST((total_worker_time / 1000.0) / COALESCE(age_minutes, DATEDIFF(mi, qs.cached_time, qs.last_execution_time)) AS MONEY) - END AS AverageCPUPerMinute , - CASE WHEN t.t_TotalWorker = 0 THEN 0 - ELSE CAST(ROUND(100.00 * (total_worker_time / 1000.0) / t.t_TotalWorker, 2) AS MONEY) - END AS PercentCPUByType, - CASE WHEN t.t_TotalElapsed = 0 THEN 0 - ELSE CAST(ROUND(100.00 * (total_elapsed_time / 1000.0) / t.t_TotalElapsed, 2) AS MONEY) - END AS PercentDurationByType, - CASE WHEN t.t_TotalReads = 0 THEN 0 - ELSE CAST(ROUND(100.00 * total_logical_reads / t.t_TotalReads, 2) AS MONEY) - END AS PercentReadsByType, - CASE WHEN t.t_TotalExecs = 0 THEN 0 - ELSE CAST(ROUND(100.00 * execution_count / t.t_TotalExecs, 2) AS MONEY) - END AS PercentExecutionsByType, - (total_elapsed_time / 1000.0) / execution_count AS AvgDuration , - (total_elapsed_time / 1000.0) AS TotalDuration , - total_logical_reads / execution_count AS AvgReads , - total_logical_reads AS TotalReads , - execution_count AS ExecutionCount , - CASE WHEN execution_count = 0 THEN 0 - WHEN COALESCE(age_minutes, DATEDIFF(mi, qs.cached_time, qs.last_execution_time), 0) = 0 THEN 0 - ELSE CAST((1.00 * execution_count / COALESCE(age_minutes, DATEDIFF(mi, qs.cached_time, qs.last_execution_time))) AS money) - END AS ExecutionsPerMinute , - total_logical_writes AS TotalWrites , - total_logical_writes / execution_count AS AverageWrites , - CASE WHEN t.t_TotalWrites = 0 THEN 0 - ELSE CAST(ROUND(100.00 * total_logical_writes / t.t_TotalWrites, 2) AS MONEY) - END AS PercentWritesByType, - CASE WHEN total_logical_writes = 0 THEN 0 - WHEN COALESCE(age_minutes, DATEDIFF(mi, qs.cached_time, qs.last_execution_time), 0) = 0 THEN 0 - ELSE CAST((1.00 * total_logical_writes / COALESCE(age_minutes, DATEDIFF(mi, qs.cached_time, qs.last_execution_time), 0)) AS money) - END AS WritesPerMinute, - qs.cached_time AS PlanCreationTime, - qs.last_execution_time AS LastExecutionTime, - DATEADD(SECOND, (qs.last_elapsed_time / 1000000.), qs.last_execution_time) AS LastCompletionTime, - NULL AS StatementStartOffset, - NULL AS StatementEndOffset, - NULL AS PlanGenerationNum, - NULL AS MinReturnedRows, - NULL AS MaxReturnedRows, - NULL AS AvgReturnedRows, - NULL AS TotalReturnedRows, - NULL AS LastReturnedRows, - NULL AS MinGrantKB, - NULL AS MaxGrantKB, - NULL AS MinUsedGrantKB, - NULL AS MaxUsedGrantKB, - NULL AS PercentMemoryGrantUsed, - NULL AS AvgMaxMemoryGrant,'; +RAISERROR(N'Getting variable info', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES ( 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) +INSERT #variable_info ( SPID, QueryHash, SqlHandle, proc_name, variable_name, variable_datatype, compile_time_value ) +SELECT DISTINCT @@SPID, + qp.QueryHash, + qp.SqlHandle, + b.QueryType AS proc_name, + q.n.value('@Column', 'NVARCHAR(258)') AS variable_name, + q.n.value('@ParameterDataType', 'NVARCHAR(258)') AS variable_datatype, + q.n.value('@ParameterCompiledValue', 'NVARCHAR(258)') AS compile_time_value +FROM #query_plan AS qp +JOIN ##BlitzCacheProcs AS b +ON (b.QueryType = 'adhoc' AND b.QueryHash = qp.QueryHash) +OR (b.QueryType <> 'adhoc' AND b.SqlHandle = qp.SqlHandle) +CROSS APPLY qp.query_plan.nodes('//p:QueryPlan/p:ParameterList/p:ColumnReference') AS q(n) +WHERE b.SPID = @@SPID +OPTION (RECOMPILE); - IF @VersionShowsSpills = 1 - BEGIN - RAISERROR(N'Getting spill information for newer versions of SQL', 0, 1) WITH NOWAIT; - SET @plans_triggers_select_list += N' - min_spills AS MinSpills, - max_spills AS MaxSpills, - total_spills AS TotalSpills, - CAST(ISNULL(NULLIF(( total_spills * 1. ), 0) / NULLIF(execution_count, 0), 0) AS MONEY) AS AvgSpills, '; - END; - ELSE - BEGIN - RAISERROR(N'Substituting NULLs for spill columns in older versions of SQL', 0, 1) WITH NOWAIT; - SET @plans_triggers_select_list += N' - NULL AS MinSpills, - NULL AS MaxSpills, - NULL AS TotalSpills, - NULL AS AvgSpills, ' ; - END; - - SET @plans_triggers_select_list += - N'st.text AS QueryText ,'; - IF @VersionShowsAirQuoteActualPlans = 1 - BEGIN - SET @plans_triggers_select_list += N' CASE WHEN DATALENGTH(COALESCE(deqps.query_plan,'''')) > DATALENGTH(COALESCE(qp.query_plan,'''')) THEN deqps.query_plan ELSE qp.query_plan END AS QueryPlan, ' + @nl ; - END; - ELSE - BEGIN - SET @plans_triggers_select_list += N' qp.query_plan AS QueryPlan, ' + @nl ; - END; +RAISERROR(N'Getting conversion info', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES ( 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) +INSERT #conversion_info ( SPID, QueryHash, SqlHandle, proc_name, expression ) +SELECT DISTINCT @@SPID, + qp.QueryHash, + qp.SqlHandle, + b.QueryType AS proc_name, + qq.c.value('@Expression', 'NVARCHAR(4000)') AS expression +FROM #query_plan AS qp +JOIN ##BlitzCacheProcs AS b +ON (b.QueryType = 'adhoc' AND b.QueryHash = qp.QueryHash) +OR (b.QueryType <> 'adhoc' AND b.SqlHandle = qp.SqlHandle) +CROSS APPLY qp.query_plan.nodes('//p:QueryPlan/p:Warnings/p:PlanAffectingConvert') AS qq(c) +WHERE qq.c.exist('@ConvertIssue[.="Seek Plan"]') = 1 + AND qp.QueryHash IS NOT NULL + AND b.implicit_conversions = 1 +AND b.SPID = @@SPID +OPTION (RECOMPILE); - SET @plans_triggers_select_list += - N't.t_TotalWorker, - t.t_TotalElapsed, - t.t_TotalReads, - t.t_TotalExecs, - t.t_TotalWrites, - qs.sql_handle AS SqlHandle, - qs.plan_handle AS PlanHandle, - NULL AS QueryHash, - NULL AS QueryPlanHash, - qs.min_worker_time / 1000.0, - qs.max_worker_time / 1000.0, - CASE WHEN qp.query_plan.value(''declare namespace p="http://schemas.microsoft.com/sqlserver/2004/07/showplan";max(//p:RelOp/@Parallel)'', ''float'') > 0 THEN 1 ELSE 0 END, - qs.min_elapsed_time / 1000.0, - qs.max_elapsed_time / 1000.0, - age_minutes, - age_minutes_lifetime, - @SortOrder '; +RAISERROR(N'Parsing conversion info', 0, 1) WITH NOWAIT; +INSERT #stored_proc_info ( SPID, SqlHandle, QueryHash, proc_name, variable_name, variable_datatype, converted_column_name, column_name, converted_to, compile_time_value ) +SELECT @@SPID AS SPID, + ci.SqlHandle, + ci.QueryHash, + REPLACE(REPLACE(REPLACE(ci.proc_name, ')', ''), 'Statement (parent ', ''), 'Procedure or Function: ', '') AS proc_name, + CASE WHEN ci.at_charindex > 0 + AND ci.bracket_charindex > 0 + THEN SUBSTRING(ci.expression, ci.at_charindex, ci.bracket_charindex) + ELSE N'**no_variable**' + END AS variable_name, + N'**no_variable**' AS variable_datatype, + CASE WHEN ci.at_charindex = 0 + AND ci.comma_charindex > 0 + AND ci.second_comma_charindex > 0 + THEN SUBSTRING(ci.expression, ci.comma_charindex, ci.second_comma_charindex) + ELSE N'**no_column**' + END AS converted_column_name, + CASE WHEN ci.at_charindex = 0 + AND ci.equal_charindex > 0 + AND ci.convert_implicit_charindex = 0 + THEN SUBSTRING(ci.expression, ci.equal_charindex, 4000) + WHEN ci.at_charindex = 0 + AND (ci.equal_charindex -1) > 0 + AND ci.convert_implicit_charindex > 0 + THEN SUBSTRING(ci.expression, 0, ci.equal_charindex -1) + WHEN ci.at_charindex > 0 + AND ci.comma_charindex > 0 + AND ci.second_comma_charindex > 0 + THEN SUBSTRING(ci.expression, ci.comma_charindex, ci.second_comma_charindex) + ELSE N'**no_column **' + END AS column_name, + CASE WHEN ci.paren_charindex > 0 + 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 + 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 +FROM #conversion_info AS ci +OPTION (RECOMPILE); -IF LEFT(@QueryFilter, 3) IN ('all', 'sta') -BEGIN - SET @sql += @insert_list; - - SET @sql += N' - SELECT TOP (@Top) - @@SPID , - ''Statement'' AS QueryType, - COALESCE(DB_NAME(CAST(pa.value AS INT)), N''-- N/A --'') AS DatabaseName, - (total_worker_time / 1000.0) / execution_count AS AvgCPU , - (total_worker_time / 1000.0) AS TotalCPU , - CASE WHEN total_worker_time = 0 THEN 0 - WHEN COALESCE(age_minutes, DATEDIFF(mi, qs.creation_time, qs.last_execution_time), 0) = 0 THEN 0 - ELSE CAST((total_worker_time / 1000.0) / COALESCE(age_minutes, DATEDIFF(mi, qs.creation_time, qs.last_execution_time)) AS MONEY) - END AS AverageCPUPerMinute , - CASE WHEN t.t_TotalWorker = 0 THEN 0 - ELSE CAST(ROUND(100.00 * total_worker_time / t.t_TotalWorker, 2) AS MONEY) - END AS PercentCPUByType, - CASE WHEN t.t_TotalElapsed = 0 THEN 0 - ELSE CAST(ROUND(100.00 * total_elapsed_time / t.t_TotalElapsed, 2) AS MONEY) - END AS PercentDurationByType, - CASE WHEN t.t_TotalReads = 0 THEN 0 - ELSE CAST(ROUND(100.00 * total_logical_reads / t.t_TotalReads, 2) AS MONEY) - END AS PercentReadsByType, - CAST(ROUND(100.00 * execution_count / t.t_TotalExecs, 2) AS MONEY) AS PercentExecutionsByType, - (total_elapsed_time / 1000.0) / execution_count AS AvgDuration , - (total_elapsed_time / 1000.0) AS TotalDuration , - total_logical_reads / execution_count AS AvgReads , - total_logical_reads AS TotalReads , - execution_count AS ExecutionCount , - CASE WHEN execution_count = 0 THEN 0 - WHEN COALESCE(age_minutes, DATEDIFF(mi, qs.creation_time, qs.last_execution_time), 0) = 0 THEN 0 - ELSE CAST((1.00 * execution_count / COALESCE(age_minutes, DATEDIFF(mi, qs.creation_time, qs.last_execution_time))) AS money) - END AS ExecutionsPerMinute , - total_logical_writes AS TotalWrites , - total_logical_writes / execution_count AS AverageWrites , - CASE WHEN t.t_TotalWrites = 0 THEN 0 - ELSE CAST(ROUND(100.00 * total_logical_writes / t.t_TotalWrites, 2) AS MONEY) - END AS PercentWritesByType, - CASE WHEN total_logical_writes = 0 THEN 0 - WHEN COALESCE(age_minutes, DATEDIFF(mi, qs.creation_time, qs.last_execution_time), 0) = 0 THEN 0 - ELSE CAST((1.00 * total_logical_writes / COALESCE(age_minutes, DATEDIFF(mi, qs.creation_time, qs.last_execution_time), 0)) AS money) - END AS WritesPerMinute, - qs.creation_time AS PlanCreationTime, - qs.last_execution_time AS LastExecutionTime, - DATEADD(SECOND, (qs.last_elapsed_time / 1000000.), qs.last_execution_time) AS LastCompletionTime, - qs.statement_start_offset AS StatementStartOffset, - qs.statement_end_offset AS StatementEndOffset, - qs.plan_generation_num AS PlanGenerationNum, '; - - IF (@v >= 11) OR (@v >= 10.5 AND @build >= 2500) - BEGIN - RAISERROR(N'Adding additional info columns for newer versions of SQL', 0, 1) WITH NOWAIT; - SET @sql += N' - qs.min_rows AS MinReturnedRows, - qs.max_rows AS MaxReturnedRows, - CAST(qs.total_rows as MONEY) / execution_count AS AvgReturnedRows, - qs.total_rows AS TotalReturnedRows, - qs.last_rows AS LastReturnedRows, ' ; - END; - ELSE - BEGIN - RAISERROR(N'Substituting NULLs for more info columns in older versions of SQL', 0, 1) WITH NOWAIT; - SET @sql += N' - NULL AS MinReturnedRows, - NULL AS MaxReturnedRows, - NULL AS AvgReturnedRows, - NULL AS TotalReturnedRows, - NULL AS LastReturnedRows, ' ; - END; - - IF @VersionShowsMemoryGrants = 1 - BEGIN - RAISERROR(N'Getting memory grant information for newer versions of SQL', 0, 1) WITH NOWAIT; - SET @sql += N' - min_grant_kb AS MinGrantKB, - max_grant_kb AS MaxGrantKB, - min_used_grant_kb AS MinUsedGrantKB, - max_used_grant_kb AS MaxUsedGrantKB, - CAST(ISNULL(NULLIF(( total_used_grant_kb * 1.00 ), 0) / NULLIF(total_grant_kb, 0), 0) * 100. AS MONEY) AS PercentMemoryGrantUsed, - CAST(ISNULL(NULLIF(( total_grant_kb * 1. ), 0) / NULLIF(execution_count, 0), 0) AS MONEY) AS AvgMaxMemoryGrant, '; - END; - ELSE - BEGIN - RAISERROR(N'Substituting NULLs for memory grant columns in older versions of SQL', 0, 1) WITH NOWAIT; - SET @sql += N' - NULL AS MinGrantKB, - NULL AS MaxGrantKB, - NULL AS MinUsedGrantKB, - NULL AS MaxUsedGrantKB, - NULL AS PercentMemoryGrantUsed, - NULL AS AvgMaxMemoryGrant, ' ; - END; - - IF @VersionShowsSpills = 1 - BEGIN - RAISERROR(N'Getting spill information for newer versions of SQL', 0, 1) WITH NOWAIT; - SET @sql += N' - min_spills AS MinSpills, - max_spills AS MaxSpills, - total_spills AS TotalSpills, - CAST(ISNULL(NULLIF(( total_spills * 1. ), 0) / NULLIF(execution_count, 0), 0) AS MONEY) AS AvgSpills,'; - END; - ELSE - BEGIN - RAISERROR(N'Substituting NULLs for spill columns in older versions of SQL', 0, 1) WITH NOWAIT; - SET @sql += N' - NULL AS MinSpills, - NULL AS MaxSpills, - NULL AS TotalSpills, - NULL AS AvgSpills, ' ; - END; - - SET @sql += N' - SUBSTRING(st.text, ( qs.statement_start_offset / 2 ) + 1, ( ( CASE qs.statement_end_offset - WHEN -1 THEN DATALENGTH(st.text) - ELSE qs.statement_end_offset - END - qs.statement_start_offset ) / 2 ) + 1) AS QueryText , ' + @nl ; - - - IF @VersionShowsAirQuoteActualPlans = 1 - BEGIN - SET @sql += N' CASE WHEN DATALENGTH(COALESCE(deqps.query_plan,'''')) > DATALENGTH(COALESCE(qp.query_plan,'''')) THEN deqps.query_plan ELSE qp.query_plan END AS QueryPlan, ' + @nl ; - END - ELSE - BEGIN - SET @sql += N' query_plan AS QueryPlan, ' + @nl ; - END - - SET @sql += N' - t.t_TotalWorker, - t.t_TotalElapsed, - t.t_TotalReads, - t.t_TotalExecs, - t.t_TotalWrites, - qs.sql_handle AS SqlHandle, - qs.plan_handle AS PlanHandle, - qs.query_hash AS QueryHash, - qs.query_plan_hash AS QueryPlanHash, - qs.min_worker_time / 1000.0, - qs.max_worker_time / 1000.0, - CASE WHEN qp.query_plan.value(''declare namespace p="http://schemas.microsoft.com/sqlserver/2004/07/showplan";max(//p:RelOp/@Parallel)'', ''float'') > 0 THEN 1 ELSE 0 END, - qs.min_elapsed_time / 1000.0, - qs.max_worker_time / 1000.0, - age_minutes, - age_minutes_lifetime, - @SortOrder '; - - SET @sql += REPLACE(REPLACE(@body, '#view#', 'dm_exec_query_stats'), 'cached_time', 'creation_time') ; - - SET @sort_filter += CASE @SortOrder WHEN N'cpu' THEN N'AND total_worker_time > 0' - WHEN N'reads' THEN N'AND total_logical_reads > 0' - WHEN N'writes' THEN N'AND total_logical_writes > 0' - WHEN N'duration' THEN N'AND total_elapsed_time > 0' - WHEN N'executions' THEN N'AND execution_count > 0' - /* WHEN N'compiles' THEN N'AND (age_minutes + age_minutes_lifetime) > 0' BGO 2021-01-24 commenting out for https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2772 */ - WHEN N'memory grant' THEN N'AND max_grant_kb > 0' - WHEN N'unused grant' THEN N'AND max_grant_kb > 0' - WHEN N'spills' THEN N'AND max_spills > 0' - /* And now the averages */ - WHEN N'avg cpu' THEN N'AND (total_worker_time / execution_count) > 0' - WHEN N'avg reads' THEN N'AND (total_logical_reads / execution_count) > 0' - WHEN N'avg writes' THEN N'AND (total_logical_writes / execution_count) > 0' - WHEN N'avg duration' THEN N'AND (total_elapsed_time / execution_count) > 0' - WHEN N'avg memory grant' THEN N'AND CASE WHEN max_grant_kb = 0 THEN 0 ELSE (max_grant_kb / execution_count) END > 0' - WHEN N'avg spills' THEN N'AND CASE WHEN total_spills = 0 THEN 0 ELSE (total_spills / execution_count) END > 0' - WHEN N'avg executions' THEN N'AND CASE WHEN execution_count = 0 THEN 0 - WHEN COALESCE(age_minutes, age_minutes_lifetime, 0) = 0 THEN 0 - ELSE CAST((1.00 * execution_count / COALESCE(age_minutes, age_minutes_lifetime)) AS money) - END > 0' - ELSE N' /* No minimum threshold set */ ' - END; - - SET @sql += REPLACE(@body_where, 'cached_time', 'creation_time') ; - - SET @sql += @sort_filter + @nl; - - SET @sql += @body_order + @nl + @nl + @nl; - - IF @SortOrder = 'compiles' - BEGIN - RAISERROR(N'Sorting by compiles', 0, 1) WITH NOWAIT; - SET @sql = REPLACE(@sql, '#sortable#', 'creation_time'); - END; -END; - - -IF (@QueryFilter = 'all' - AND (SELECT COUNT(*) FROM #only_query_hashes) = 0 - AND (SELECT COUNT(*) FROM #ignore_query_hashes) = 0) - AND (@SortOrder NOT IN ('memory grant', 'avg memory grant', 'unused grant', 'duplicate')) /* Issue #3345 added 'duplicate' */ - OR (LEFT(@QueryFilter, 3) = 'pro') -BEGIN - SET @sql += @insert_list; - SET @sql += REPLACE(@plans_triggers_select_list, '#query_type#', 'Stored Procedure') ; - - SET @sql += REPLACE(@body, '#view#', 'dm_exec_procedure_stats') ; - SET @sql += @body_where ; - - IF @IgnoreSystemDBs = 1 - SET @sql += N' AND COALESCE(LOWER(DB_NAME(database_id)), LOWER(CAST(pa.value AS sysname)), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'', ''dbmaintenance'', ''dbadmin'', ''dbatools'') AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ; - SET @sql += @sort_filter + @nl; - - SET @sql += @body_order + @nl + @nl + @nl ; -END; - -IF (@v >= 13 - AND @QueryFilter = 'all' - AND (SELECT COUNT(*) FROM #only_query_hashes) = 0 - AND (SELECT COUNT(*) FROM #ignore_query_hashes) = 0) - AND (@SortOrder NOT IN ('memory grant', 'avg memory grant', 'unused grant', 'duplicate')) /* Issue #3345 added 'duplicate' */ - AND (@SortOrder NOT IN ('spills', 'avg spills')) - OR (LEFT(@QueryFilter, 3) = 'fun') -BEGIN - SET @sql += @insert_list; - SET @sql += REPLACE(REPLACE(@plans_triggers_select_list, '#query_type#', 'Function') - , N' - min_spills AS MinSpills, - max_spills AS MaxSpills, - total_spills AS TotalSpills, - CAST(ISNULL(NULLIF(( total_spills * 1. ), 0) / NULLIF(execution_count, 0), 0) AS MONEY) AS AvgSpills, ', - N' - NULL AS MinSpills, - NULL AS MaxSpills, - NULL AS TotalSpills, - NULL AS AvgSpills, ') ; +RAISERROR(N'Updating variables for inserted procs', 0, 1) WITH NOWAIT; +UPDATE sp +SET sp.variable_datatype = vi.variable_datatype, + sp.compile_time_value = vi.compile_time_value +FROM #stored_proc_info AS sp +JOIN #variable_info AS vi +ON (sp.proc_name = 'adhoc' AND sp.QueryHash = vi.QueryHash) +OR (sp.proc_name <> 'adhoc' AND sp.SqlHandle = vi.SqlHandle) +AND sp.variable_name = vi.variable_name +OPTION (RECOMPILE); - SET @sql += REPLACE(@body, '#view#', 'dm_exec_function_stats') ; - SET @sql += @body_where ; - IF @IgnoreSystemDBs = 1 - SET @sql += N' AND COALESCE(LOWER(DB_NAME(database_id)), LOWER(CAST(pa.value AS sysname)), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'', ''dbmaintenance'', ''dbadmin'', ''dbatools'') AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ; +RAISERROR(N'Inserting variables for other procs', 0, 1) WITH NOWAIT; +INSERT #stored_proc_info + ( SPID, SqlHandle, QueryHash, variable_name, variable_datatype, compile_time_value, proc_name ) +SELECT vi.SPID, vi.SqlHandle, vi.QueryHash, vi.variable_name, vi.variable_datatype, vi.compile_time_value, REPLACE(REPLACE(REPLACE(vi.proc_name, ')', ''), 'Statement (parent ', ''), 'Procedure or Function: ', '') AS proc_name +FROM #variable_info AS vi +WHERE NOT EXISTS +( + SELECT * + FROM #stored_proc_info AS sp + WHERE (sp.proc_name = 'adhoc' AND sp.QueryHash = vi.QueryHash) + OR (sp.proc_name <> 'adhoc' AND sp.SqlHandle = vi.SqlHandle) +) +OPTION (RECOMPILE); - SET @sql += @sort_filter + @nl; - SET @sql += @body_order + @nl + @nl + @nl ; -END; +RAISERROR(N'Updating procs', 0, 1) WITH NOWAIT; +UPDATE s +SET s.variable_datatype = CASE WHEN s.variable_datatype LIKE '%(%)%' + THEN LEFT(s.variable_datatype, CHARINDEX('(', s.variable_datatype) - 1) + ELSE s.variable_datatype + END, + s.converted_to = CASE WHEN s.converted_to LIKE '%(%)%' + THEN LEFT(s.converted_to, CHARINDEX('(', s.converted_to) - 1) + ELSE s.converted_to + END, + s.compile_time_value = CASE WHEN s.compile_time_value LIKE '%(%)%' + THEN SUBSTRING(s.compile_time_value, + CHARINDEX('(', s.compile_time_value) + 1, + CHARINDEX(')', s.compile_time_value, CHARINDEX('(', s.compile_time_value) + 1) - 1 - CHARINDEX('(', s.compile_time_value) + ) + WHEN variable_datatype NOT IN ('bit', 'tinyint', 'smallint', 'int', 'bigint') + AND s.variable_datatype NOT LIKE '%binary%' + AND s.compile_time_value NOT LIKE 'N''%''' + AND s.compile_time_value NOT LIKE '''%''' + AND s.compile_time_value <> s.column_name + AND s.compile_time_value <> '**idk_man**' + THEN QUOTENAME(compile_time_value, '''') + ELSE s.compile_time_value + END +FROM #stored_proc_info AS s +OPTION (RECOMPILE); -/******************************************************************************* - * - * Because the trigger execution count in SQL Server 2008R2 and earlier is not - * correct, we ignore triggers for these versions of SQL Server. If you'd like - * to include trigger numbers, just know that the ExecutionCount, - * PercentExecutions, and ExecutionsPerMinute are wildly inaccurate for - * triggers on these versions of SQL Server. - * - * This is why we can't have nice things. - * - ******************************************************************************/ -IF (@UseTriggersAnyway = 1 OR @v >= 11) - AND (SELECT COUNT(*) FROM #only_query_hashes) = 0 - AND (SELECT COUNT(*) FROM #ignore_query_hashes) = 0 - AND (@QueryFilter = 'all') - AND (@SortOrder NOT IN ('memory grant', 'avg memory grant', 'unused grant', 'duplicate')) /* Issue #3345 added 'duplicate' */ -BEGIN - RAISERROR (N'Adding SQL to collect trigger stats.',0,1) WITH NOWAIT; - /* Trigger level information from the plan cache */ - SET @sql += @insert_list ; +RAISERROR(N'Updating SET options', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +UPDATE s +SET set_options = set_options.ansi_set_options +FROM #stored_proc_info AS s +JOIN ( + SELECT x.SqlHandle, + N'SET ANSI_NULLS ' + CASE WHEN [ANSI_NULLS] = 'true' THEN N'ON ' ELSE N'OFF ' END + NCHAR(10) + + N'SET ANSI_PADDING ' + CASE WHEN [ANSI_PADDING] = 'true' THEN N'ON ' ELSE N'OFF ' END + NCHAR(10) + + N'SET ANSI_WARNINGS ' + CASE WHEN [ANSI_WARNINGS] = 'true' THEN N'ON ' ELSE N'OFF ' END + NCHAR(10) + + N'SET ARITHABORT ' + CASE WHEN [ARITHABORT] = 'true' THEN N'ON ' ELSE N' OFF ' END + NCHAR(10) + + N'SET CONCAT_NULL_YIELDS_NULL ' + CASE WHEN [CONCAT_NULL_YIELDS_NULL] = 'true' THEN N'ON ' ELSE N'OFF ' END + NCHAR(10) + + N'SET NUMERIC_ROUNDABORT ' + CASE WHEN [NUMERIC_ROUNDABORT] = 'true' THEN N'ON ' ELSE N'OFF ' END + NCHAR(10) + + N'SET QUOTED_IDENTIFIER ' + CASE WHEN [QUOTED_IDENTIFIER] = 'true' THEN N'ON ' ELSE N'OFF ' + NCHAR(10) END AS [ansi_set_options] + FROM ( + SELECT + s.SqlHandle, + so.o.value('@ANSI_NULLS', 'NVARCHAR(20)') AS [ANSI_NULLS], + so.o.value('@ANSI_PADDING', 'NVARCHAR(20)') AS [ANSI_PADDING], + so.o.value('@ANSI_WARNINGS', 'NVARCHAR(20)') AS [ANSI_WARNINGS], + so.o.value('@ARITHABORT', 'NVARCHAR(20)') AS [ARITHABORT], + so.o.value('@CONCAT_NULL_YIELDS_NULL', 'NVARCHAR(20)') AS [CONCAT_NULL_YIELDS_NULL], + so.o.value('@NUMERIC_ROUNDABORT', 'NVARCHAR(20)') AS [NUMERIC_ROUNDABORT], + so.o.value('@QUOTED_IDENTIFIER', 'NVARCHAR(20)') AS [QUOTED_IDENTIFIER] + FROM #statements AS s + CROSS APPLY s.statement.nodes('//p:StatementSetOptions') AS so(o) + ) AS x +) AS set_options ON set_options.SqlHandle = s.SqlHandle +OPTION(RECOMPILE); - SET @sql += REPLACE(@plans_triggers_select_list, '#query_type#', 'Trigger') ; - SET @sql += REPLACE(@body, '#view#', 'dm_exec_trigger_stats') ; +RAISERROR(N'Updating conversion XML', 0, 1) WITH NOWAIT; +WITH precheck AS ( +SELECT spi.SPID, + spi.SqlHandle, + spi.proc_name, + (SELECT + CASE WHEN spi.proc_name <> 'Statement' + THEN N'The stored procedure ' + spi.proc_name + ELSE N'This ad hoc statement' + END + + N' had the following implicit conversions: ' + + CHAR(10) + + STUFF(( + SELECT DISTINCT + @nl + + CASE WHEN spi2.variable_name <> N'**no_variable**' + THEN N'The variable ' + WHEN spi2.variable_name = N'**no_variable**' AND (spi2.column_name = spi2.converted_column_name OR spi2.column_name LIKE '%CONVERT_IMPLICIT%') + THEN N'The compiled value ' + WHEN spi2.column_name LIKE '%Expr%' + THEN 'The expression ' + ELSE N'The column ' + END + + CASE WHEN spi2.variable_name <> N'**no_variable**' + THEN spi2.variable_name + WHEN spi2.variable_name = N'**no_variable**' AND (spi2.column_name = spi2.converted_column_name OR spi2.column_name LIKE '%CONVERT_IMPLICIT%') + THEN spi2.compile_time_value + ELSE spi2.column_name + END + + N' has a data type of ' + + CASE WHEN spi2.variable_datatype = N'**no_variable**' THEN spi2.converted_to + ELSE spi2.variable_datatype + END + + N' which caused implicit conversion on the column ' + + CASE WHEN spi2.column_name LIKE N'%CONVERT_IMPLICIT%' + THEN spi2.converted_column_name + WHEN spi2.column_name = N'**no_column**' + THEN spi2.converted_column_name + WHEN spi2.converted_column_name = N'**no_column**' + THEN spi2.column_name + WHEN spi2.column_name <> spi2.converted_column_name + THEN spi2.converted_column_name + ELSE spi2.column_name + END + + CASE WHEN spi2.variable_name = N'**no_variable**' AND (spi2.column_name = spi2.converted_column_name OR spi2.column_name LIKE '%CONVERT_IMPLICIT%') + THEN N'' + WHEN spi2.column_name LIKE '%Expr%' + THEN N'' + WHEN spi2.compile_time_value NOT IN ('**declared in proc**', '**idk_man**') + AND spi2.compile_time_value <> spi2.column_name + THEN ' with the value ' + RTRIM(spi2.compile_time_value) + ELSE N'' + END + + '.' + FROM #stored_proc_info AS spi2 + WHERE spi.SqlHandle = spi2.SqlHandle + FOR XML PATH(N''), TYPE).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 1, N'') + AS [processing-instruction(ClickMe)] FOR XML PATH(''), TYPE ) + AS implicit_conversion_info +FROM #stored_proc_info AS spi +GROUP BY spi.SPID, spi.SqlHandle, spi.proc_name +) +UPDATE b +SET b.implicit_conversion_info = pk.implicit_conversion_info +FROM ##BlitzCacheProcs AS b +JOIN precheck pk +ON pk.SqlHandle = b.SqlHandle +AND pk.SPID = b.SPID +OPTION (RECOMPILE); - SET @sql += @body_where ; - IF @IgnoreSystemDBs = 1 - SET @sql += N' AND COALESCE(LOWER(DB_NAME(database_id)), LOWER(CAST(pa.value AS sysname)), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'', ''dbmaintenance'', ''dbadmin'', ''dbatools'') AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ; +RAISERROR(N'Updating cached parameter XML for stored procs', 0, 1) WITH NOWAIT; +WITH precheck AS ( +SELECT spi.SPID, + spi.SqlHandle, + spi.proc_name, + (SELECT + set_options + + @nl + + @nl + + N'EXEC ' + + spi.proc_name + + N' ' + + STUFF(( + SELECT DISTINCT N', ' + + CASE WHEN spi2.variable_name <> N'**no_variable**' AND spi2.compile_time_value <> N'**idk_man**' + THEN spi2.variable_name + N' = ' + ELSE @nl + N' We could not find any cached parameter values for this stored proc. ' + END + + CASE WHEN spi2.variable_name = N'**no_variable**' OR spi2.compile_time_value = N'**idk_man**' + THEN @nl + N'More info on possible reasons: https://www.brentozar.com/go/noplans ' + WHEN spi2.compile_time_value = N'NULL' + THEN spi2.compile_time_value + ELSE RTRIM(spi2.compile_time_value) + END + FROM #stored_proc_info AS spi2 + WHERE spi.SqlHandle = spi2.SqlHandle + AND spi2.proc_name <> N'Statement' + FOR XML PATH(N''), TYPE).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 1, N'') + AS [processing-instruction(ClickMe)] FOR XML PATH(''), TYPE ) + AS cached_execution_parameters +FROM #stored_proc_info AS spi +GROUP BY spi.SPID, spi.SqlHandle, spi.proc_name, spi.set_options +) +UPDATE b +SET b.cached_execution_parameters = pk.cached_execution_parameters +FROM ##BlitzCacheProcs AS b +JOIN precheck pk +ON pk.SqlHandle = b.SqlHandle +AND pk.SPID = b.SPID +WHERE b.QueryType <> N'Statement' +OPTION (RECOMPILE); - SET @sql += @sort_filter + @nl; - SET @sql += @body_order + @nl + @nl + @nl ; -END; +RAISERROR(N'Updating cached parameter XML for statements', 0, 1) WITH NOWAIT; +WITH precheck AS ( +SELECT spi.SPID, + spi.SqlHandle, + spi.proc_name, + (SELECT + set_options + + @nl + + @nl + + N' See QueryText column for full query text' + + @nl + + @nl + + STUFF(( + SELECT DISTINCT N', ' + + CASE WHEN spi2.variable_name <> N'**no_variable**' AND spi2.compile_time_value <> N'**idk_man**' + THEN spi2.variable_name + N' = ' + ELSE @nl + N' We could not find any cached parameter values for this stored proc. ' + END + + CASE WHEN spi2.variable_name = N'**no_variable**' OR spi2.compile_time_value = N'**idk_man**' + THEN @nl + N' More info on possible reasons: https://www.brentozar.com/go/noplans ' + WHEN spi2.compile_time_value = N'NULL' + THEN spi2.compile_time_value + ELSE RTRIM(spi2.compile_time_value) + END + FROM #stored_proc_info AS spi2 + WHERE spi.SqlHandle = spi2.SqlHandle + AND spi2.proc_name = N'Statement' + AND spi2.variable_name NOT LIKE N'%msparam%' + FOR XML PATH(N''), TYPE).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 1, N'') + AS [processing-instruction(ClickMe)] FOR XML PATH(''), TYPE ) + AS cached_execution_parameters +FROM #stored_proc_info AS spi +GROUP BY spi.SPID, spi.SqlHandle, spi.proc_name, spi.set_options +) +UPDATE b +SET b.cached_execution_parameters = pk.cached_execution_parameters +FROM ##BlitzCacheProcs AS b +JOIN precheck pk +ON pk.SqlHandle = b.SqlHandle +AND pk.SPID = b.SPID +WHERE b.QueryType = N'Statement' +OPTION (RECOMPILE); +RAISERROR(N'Filling in implicit conversion and cached plan parameter info', 0, 1) WITH NOWAIT; +UPDATE b +SET b.implicit_conversion_info = CASE WHEN b.implicit_conversion_info IS NULL + OR CONVERT(NVARCHAR(MAX), b.implicit_conversion_info) = N'' + THEN '' + ELSE b.implicit_conversion_info END, + b.cached_execution_parameters = CASE WHEN b.cached_execution_parameters IS NULL + OR CONVERT(NVARCHAR(MAX), b.cached_execution_parameters) = N'' + THEN '' + ELSE b.cached_execution_parameters END +FROM ##BlitzCacheProcs AS b +WHERE b.SPID = @@SPID +OPTION (RECOMPILE); +/*End implicit conversion and parameter info*/ -SELECT @sort = CASE @SortOrder WHEN N'cpu' THEN N'total_worker_time' - WHEN N'reads' THEN N'total_logical_reads' - WHEN N'writes' THEN N'total_logical_writes' - WHEN N'duration' THEN N'total_elapsed_time' - WHEN N'executions' THEN N'execution_count' - WHEN N'compiles' THEN N'cached_time' - WHEN N'memory grant' THEN N'max_grant_kb' - WHEN N'unused grant' THEN N'max_grant_kb - max_used_grant_kb' - WHEN N'spills' THEN N'max_spills' - WHEN N'duplicate' THEN N'total_worker_time' /* Issue #3345 */ - /* And now the averages */ - WHEN N'avg cpu' THEN N'total_worker_time / execution_count' - WHEN N'avg reads' THEN N'total_logical_reads / execution_count' - WHEN N'avg writes' THEN N'total_logical_writes / execution_count' - WHEN N'avg duration' THEN N'total_elapsed_time / execution_count' - WHEN N'avg memory grant' THEN N'CASE WHEN max_grant_kb = 0 THEN 0 ELSE max_grant_kb / execution_count END' - WHEN N'avg spills' THEN N'CASE WHEN total_spills = 0 THEN 0 ELSE total_spills / execution_count END' - WHEN N'avg executions' THEN N'CASE WHEN execution_count = 0 THEN 0 - WHEN COALESCE(age_minutes, age_minutes_lifetime, 0) = 0 THEN 0 - ELSE CAST((1.00 * execution_count / COALESCE(age_minutes, age_minutes_lifetime)) AS money) - END' - END ; +/*Begin Missing Index*/ +IF EXISTS ( SELECT 1/0 + FROM ##BlitzCacheProcs AS bbcp + WHERE bbcp.missing_index_count > 0 + OR bbcp.index_spool_cost > 0 + OR bbcp.index_spool_rows > 0 + AND bbcp.SPID = @@SPID ) + + BEGIN + RAISERROR(N'Inserting to #missing_index_xml', 0, 1) WITH NOWAIT; + WITH XMLNAMESPACES ( 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) + INSERT #missing_index_xml + SELECT qp.QueryHash, + qp.SqlHandle, + c.mg.value('@Impact', 'FLOAT') AS Impact, + c.mg.query('.') AS cmg + FROM #query_plan AS qp + CROSS APPLY qp.query_plan.nodes('//p:MissingIndexes/p:MissingIndexGroup') AS c(mg) + WHERE qp.QueryHash IS NOT NULL + OPTION(RECOMPILE); + + RAISERROR(N'Inserting to #missing_index_schema', 0, 1) WITH NOWAIT; + WITH XMLNAMESPACES ( 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) + INSERT #missing_index_schema + SELECT mix.QueryHash, mix.SqlHandle, mix.impact, + c.mi.value('@Database', 'NVARCHAR(128)'), + c.mi.value('@Schema', 'NVARCHAR(128)'), + c.mi.value('@Table', 'NVARCHAR(128)'), + c.mi.query('.') + FROM #missing_index_xml AS mix + CROSS APPLY mix.index_xml.nodes('//p:MissingIndex') AS c(mi) + OPTION(RECOMPILE); + + RAISERROR(N'Inserting to #missing_index_usage', 0, 1) WITH NOWAIT; + WITH XMLNAMESPACES ( 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) + INSERT #missing_index_usage + SELECT ms.QueryHash, ms.SqlHandle, ms.impact, ms.database_name, ms.schema_name, ms.table_name, + c.cg.value('@Usage', 'NVARCHAR(128)'), + c.cg.query('.') + FROM #missing_index_schema ms + CROSS APPLY ms.index_xml.nodes('//p:ColumnGroup') AS c(cg) + OPTION(RECOMPILE); + + RAISERROR(N'Inserting to #missing_index_detail', 0, 1) WITH NOWAIT; + WITH XMLNAMESPACES ( 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) + INSERT #missing_index_detail + SELECT miu.QueryHash, + miu.SqlHandle, + miu.impact, + miu.database_name, + miu.schema_name, + miu.table_name, + miu.usage, + c.c.value('@Name', 'NVARCHAR(128)') + FROM #missing_index_usage AS miu + CROSS APPLY miu.index_xml.nodes('//p:Column') AS c(c) + OPTION (RECOMPILE); + + RAISERROR(N'Inserting to missing indexes to #missing_index_pretty', 0, 1) WITH NOWAIT; + INSERT #missing_index_pretty + ( QueryHash, SqlHandle, impact, database_name, schema_name, table_name, equality, inequality, include, executions, query_cost, creation_hours, is_spool ) + SELECT DISTINCT m.QueryHash, m.SqlHandle, m.impact, m.database_name, m.schema_name, m.table_name + , STUFF(( SELECT DISTINCT N', ' + ISNULL(m2.column_name, '') AS column_name + FROM #missing_index_detail AS m2 + WHERE m2.usage = 'EQUALITY' + AND m.QueryHash = m2.QueryHash + AND m.SqlHandle = m2.SqlHandle + AND m.impact = m2.impact + AND m.database_name = m2.database_name + AND m.schema_name = m2.schema_name + AND m.table_name = m2.table_name + FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 2, N'') AS equality + , STUFF(( SELECT DISTINCT N', ' + ISNULL(m2.column_name, '') AS column_name + FROM #missing_index_detail AS m2 + WHERE m2.usage = 'INEQUALITY' + AND m.QueryHash = m2.QueryHash + AND m.SqlHandle = m2.SqlHandle + AND m.impact = m2.impact + AND m.database_name = m2.database_name + AND m.schema_name = m2.schema_name + AND m.table_name = m2.table_name + FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 2, N'') AS inequality + , STUFF(( SELECT DISTINCT N', ' + ISNULL(m2.column_name, '') AS column_name + FROM #missing_index_detail AS m2 + WHERE m2.usage = 'INCLUDE' + AND m.QueryHash = m2.QueryHash + AND m.SqlHandle = m2.SqlHandle + AND m.impact = m2.impact + AND m.database_name = m2.database_name + AND m.schema_name = m2.schema_name + AND m.table_name = m2.table_name + FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 2, N'') AS [include], + bbcp.ExecutionCount, + bbcp.QueryPlanCost, + bbcp.PlanCreationTimeHours, + 0 as is_spool + FROM #missing_index_detail AS m + JOIN ##BlitzCacheProcs AS bbcp + ON m.SqlHandle = bbcp.SqlHandle + AND m.QueryHash = bbcp.QueryHash + OPTION (RECOMPILE); + + RAISERROR(N'Inserting to #index_spool_ugly', 0, 1) WITH NOWAIT; + WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) + INSERT #index_spool_ugly + (QueryHash, SqlHandle, impact, database_name, schema_name, table_name, equality, inequality, include, executions, query_cost, creation_hours) + SELECT p.QueryHash, + p.SqlHandle, + (c.n.value('@EstimateIO', 'FLOAT') + (c.n.value('@EstimateCPU', 'FLOAT'))) + / ( 1 * NULLIF(p.QueryPlanCost, 0)) * 100 AS impact, + o.n.value('@Database', 'NVARCHAR(128)') AS output_database, + o.n.value('@Schema', 'NVARCHAR(128)') AS output_schema, + o.n.value('@Table', 'NVARCHAR(128)') AS output_table, + k.n.value('@Column', 'NVARCHAR(128)') AS range_column, + e.n.value('@Column', 'NVARCHAR(128)') AS expression_column, + o.n.value('@Column', 'NVARCHAR(128)') AS output_column, + p.ExecutionCount, + p.QueryPlanCost, + p.PlanCreationTimeHours + FROM #relop AS r + JOIN ##BlitzCacheProcs p + ON p.QueryHash = r.QueryHash + CROSS APPLY r.relop.nodes('/p:RelOp') AS c(n) + CROSS APPLY r.relop.nodes('/p:RelOp/p:OutputList/p:ColumnReference') AS o(n) + OUTER APPLY r.relop.nodes('/p:RelOp/p:Spool/p:SeekPredicateNew/p:SeekKeys/p:Prefix/p:RangeColumns/p:ColumnReference') AS k(n) + OUTER APPLY r.relop.nodes('/p:RelOp/p:Spool/p:SeekPredicateNew/p:SeekKeys/p:Prefix/p:RangeExpressions/p:ColumnReference') AS e(n) + WHERE r.relop.exist('/p:RelOp[@PhysicalOp="Index Spool" and @LogicalOp="Eager Spool"]') = 1 -SELECT @sql = REPLACE(@sql, '#sortable#', @sort); + RAISERROR(N'Inserting to spools to #missing_index_pretty', 0, 1) WITH NOWAIT; + INSERT #missing_index_pretty + (QueryHash, SqlHandle, impact, database_name, schema_name, table_name, equality, inequality, include, executions, query_cost, creation_hours, is_spool) + SELECT DISTINCT + isu.QueryHash, + isu.SqlHandle, + isu.impact, + isu.database_name, + isu.schema_name, + isu.table_name + , STUFF(( SELECT DISTINCT N', ' + ISNULL(isu2.equality, '') AS column_name + FROM #index_spool_ugly AS isu2 + WHERE isu2.equality IS NOT NULL + AND isu.QueryHash = isu2.QueryHash + AND isu.SqlHandle = isu2.SqlHandle + AND isu.impact = isu2.impact + AND isu.database_name = isu2.database_name + AND isu.schema_name = isu2.schema_name + AND isu.table_name = isu2.table_name + FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 2, N'') AS equality + , STUFF(( SELECT DISTINCT N', ' + ISNULL(isu2.inequality, '') AS column_name + FROM #index_spool_ugly AS isu2 + WHERE isu2.inequality IS NOT NULL + AND isu.QueryHash = isu2.QueryHash + AND isu.SqlHandle = isu2.SqlHandle + AND isu.impact = isu2.impact + AND isu.database_name = isu2.database_name + AND isu.schema_name = isu2.schema_name + AND isu.table_name = isu2.table_name + FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 2, N'') AS inequality + , STUFF(( SELECT DISTINCT N', ' + ISNULL(isu2.include, '') AS column_name + FROM #index_spool_ugly AS isu2 + WHERE isu2.include IS NOT NULL + AND isu.QueryHash = isu2.QueryHash + AND isu.SqlHandle = isu2.SqlHandle + AND isu.impact = isu2.impact + AND isu.database_name = isu2.database_name + AND isu.schema_name = isu2.schema_name + AND isu.table_name = isu2.table_name + FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 2, N'') AS include, + isu.executions, + isu.query_cost, + isu.creation_hours, + 1 AS is_spool + FROM #index_spool_ugly AS isu -SET @sql += N' -SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -INSERT INTO #p (SqlHandle, TotalCPU, TotalReads, TotalDuration, TotalWrites, ExecutionCount) -SELECT SqlHandle, - TotalCPU, - TotalReads, - TotalDuration, - TotalWrites, - ExecutionCount -FROM (SELECT SqlHandle, - TotalCPU, - TotalReads, - TotalDuration, - TotalWrites, - ExecutionCount, - ROW_NUMBER() OVER (PARTITION BY SqlHandle ORDER BY #sortable# DESC) AS rn - FROM ##BlitzCacheProcs - WHERE SPID = @@SPID) AS x -WHERE x.rn = 1 -OPTION (RECOMPILE); -/* - This block was used to delete duplicate queries, but has been removed. - For more info: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2026 -WITH d AS ( -SELECT SPID, - ROW_NUMBER() OVER (PARTITION BY SqlHandle, QueryHash ORDER BY #sortable# DESC) AS rn -FROM ##BlitzCacheProcs -WHERE SPID = @@SPID -) -DELETE d -WHERE d.rn > 1 -AND SPID = @@SPID -OPTION (RECOMPILE); -*/ -'; + RAISERROR(N'Updating missing index information', 0, 1) WITH NOWAIT; + WITH missing AS ( + SELECT DISTINCT + mip.QueryHash, + mip.SqlHandle, + mip.executions, + N'' + AS full_details + FROM #missing_index_pretty AS mip + ) + UPDATE bbcp + SET bbcp.missing_indexes = m.full_details + FROM ##BlitzCacheProcs AS bbcp + JOIN missing AS m + ON m.SqlHandle = bbcp.SqlHandle + AND m.QueryHash = bbcp.QueryHash + AND m.executions = bbcp.ExecutionCount + AND SPID = @@SPID + OPTION (RECOMPILE); -SELECT @sort = CASE @SortOrder WHEN N'cpu' THEN N'TotalCPU' - WHEN N'reads' THEN N'TotalReads' - WHEN N'writes' THEN N'TotalWrites' - WHEN N'duration' THEN N'TotalDuration' - WHEN N'executions' THEN N'ExecutionCount' - WHEN N'compiles' THEN N'PlanCreationTime' - WHEN N'memory grant' THEN N'MaxGrantKB' - WHEN N'unused grant' THEN N'MaxGrantKB - MaxUsedGrantKB' - WHEN N'spills' THEN N'MaxSpills' - WHEN N'duplicate' THEN N'TotalCPU' /* Issue #3345 */ - /* And now the averages */ - WHEN N'avg cpu' THEN N'TotalCPU / ExecutionCount' - WHEN N'avg reads' THEN N'TotalReads / ExecutionCount' - WHEN N'avg writes' THEN N'TotalWrites / ExecutionCount' - WHEN N'avg duration' THEN N'TotalDuration / ExecutionCount' - WHEN N'avg memory grant' THEN N'AvgMaxMemoryGrant' - WHEN N'avg spills' THEN N'AvgSpills' - WHEN N'avg executions' THEN N'CASE WHEN ExecutionCount = 0 THEN 0 - WHEN COALESCE(age_minutes, age_minutes_lifetime, 0) = 0 THEN 0 - ELSE CAST((1.00 * ExecutionCount / COALESCE(age_minutes, age_minutes_lifetime)) AS money) - END' - END ; + END; -SELECT @sql = REPLACE(@sql, '#sortable#', @sort); + RAISERROR(N'Filling in missing index blanks', 0, 1) WITH NOWAIT; + UPDATE b + SET b.missing_indexes = + CASE WHEN b.missing_indexes IS NULL + THEN '' + ELSE b.missing_indexes + END + FROM ##BlitzCacheProcs AS b + WHERE b.SPID = @@SPID + OPTION (RECOMPILE); +/*End Missing Index*/ -IF @Debug = 1 - BEGIN - PRINT N'Printing dynamic SQL stored in @sql: '; - PRINT SUBSTRING(@sql, 0, 4000); - PRINT SUBSTRING(@sql, 4000, 8000); - PRINT SUBSTRING(@sql, 8000, 12000); - PRINT SUBSTRING(@sql, 12000, 16000); - PRINT SUBSTRING(@sql, 16000, 20000); - PRINT SUBSTRING(@sql, 20000, 24000); - PRINT SUBSTRING(@sql, 24000, 28000); - PRINT SUBSTRING(@sql, 28000, 32000); - PRINT SUBSTRING(@sql, 32000, 36000); - PRINT SUBSTRING(@sql, 36000, 40000); - END; -RAISERROR(N'Creating temp tables for results and warnings.', 0, 1) WITH NOWAIT; +/* Set configuration values */ +RAISERROR(N'Setting configuration values', 0, 1) WITH NOWAIT; +DECLARE @execution_threshold INT = 1000 , + @parameter_sniffing_warning_pct TINYINT = 30, + /* This is in average reads */ + @parameter_sniffing_io_threshold BIGINT = 100000 , + @ctp_threshold_pct TINYINT = 10, + @long_running_query_warning_seconds BIGINT = 300 * 1000 , + @memory_grant_warning_percent INT = 10; + +IF EXISTS (SELECT 1/0 FROM #configuration WHERE 'frequent execution threshold' = LOWER(parameter_name)) +BEGIN + SELECT @execution_threshold = CAST(value AS INT) + FROM #configuration + WHERE 'frequent execution threshold' = LOWER(parameter_name) ; + SET @msg = ' Setting "frequent execution threshold" to ' + CAST(@execution_threshold AS VARCHAR(10)) ; -IF OBJECT_ID('tempdb.dbo.##BlitzCacheResults') IS NULL + RAISERROR(@msg, 0, 1) WITH NOWAIT; +END; + +IF EXISTS (SELECT 1/0 FROM #configuration WHERE 'parameter sniffing variance percent' = LOWER(parameter_name)) BEGIN - CREATE TABLE ##BlitzCacheResults ( - SPID INT, - ID INT IDENTITY(1,1), - CheckID INT, - Priority TINYINT, - FindingsGroup VARCHAR(50), - Finding VARCHAR(500), - URL VARCHAR(200), - Details VARCHAR(4000) - ); + SELECT @parameter_sniffing_warning_pct = CAST(value AS TINYINT) + FROM #configuration + WHERE 'parameter sniffing variance percent' = LOWER(parameter_name) ; + + SET @msg = ' Setting "parameter sniffing variance percent" to ' + CAST(@parameter_sniffing_warning_pct AS VARCHAR(3)) ; + + RAISERROR(@msg, 0, 1) WITH NOWAIT; END; -ELSE + +IF EXISTS (SELECT 1/0 FROM #configuration WHERE 'parameter sniffing io threshold' = LOWER(parameter_name)) BEGIN - RAISERROR(N'Cleaning up old warnings for your SPID', 0, 1) WITH NOWAIT; - DELETE ##BlitzCacheResults - WHERE SPID = @@SPID - OPTION (RECOMPILE) ; -END + SELECT @parameter_sniffing_io_threshold = CAST(value AS BIGINT) + FROM #configuration + WHERE 'parameter sniffing io threshold' = LOWER(parameter_name) ; + + SET @msg = ' Setting "parameter sniffing io threshold" to ' + CAST(@parameter_sniffing_io_threshold AS VARCHAR(10)); + RAISERROR(@msg, 0, 1) WITH NOWAIT; +END; -IF OBJECT_ID('tempdb.dbo.##BlitzCacheProcs') IS NULL +IF EXISTS (SELECT 1/0 FROM #configuration WHERE 'cost threshold for parallelism warning' = LOWER(parameter_name)) BEGIN - CREATE TABLE ##BlitzCacheProcs ( - SPID INT , - QueryType NVARCHAR(258), - DatabaseName sysname, - AverageCPU DECIMAL(38,4), - AverageCPUPerMinute DECIMAL(38,4), - TotalCPU DECIMAL(38,4), - PercentCPUByType MONEY, - PercentCPU MONEY, - AverageDuration DECIMAL(38,4), - TotalDuration DECIMAL(38,4), - PercentDuration MONEY, - PercentDurationByType MONEY, - AverageReads BIGINT, - TotalReads BIGINT, - PercentReads MONEY, - PercentReadsByType MONEY, - ExecutionCount BIGINT, - PercentExecutions MONEY, - PercentExecutionsByType MONEY, - ExecutionsPerMinute MONEY, - TotalWrites BIGINT, - AverageWrites MONEY, - PercentWrites MONEY, - PercentWritesByType MONEY, - WritesPerMinute MONEY, - PlanCreationTime DATETIME, - PlanCreationTimeHours AS DATEDIFF(HOUR, PlanCreationTime, SYSDATETIME()), - LastExecutionTime DATETIME, - LastCompletionTime DATETIME, - PlanHandle VARBINARY(64), - [Remove Plan Handle From Cache] AS - CASE WHEN [PlanHandle] IS NOT NULL - THEN 'DBCC FREEPROCCACHE (' + CONVERT(VARCHAR(128), [PlanHandle], 1) + ');' - ELSE 'N/A' END, - SqlHandle VARBINARY(64), - [Remove SQL Handle From Cache] AS - CASE WHEN [SqlHandle] IS NOT NULL - THEN 'DBCC FREEPROCCACHE (' + CONVERT(VARCHAR(128), [SqlHandle], 1) + ');' - ELSE 'N/A' END, - [SQL Handle More Info] AS - CASE WHEN [SqlHandle] IS NOT NULL - THEN 'EXEC sp_BlitzCache @OnlySqlHandles = ''' + CONVERT(VARCHAR(128), [SqlHandle], 1) + '''; ' - ELSE 'N/A' END, - QueryHash BINARY(8), - [Query Hash More Info] AS - CASE WHEN [QueryHash] IS NOT NULL - THEN 'EXEC sp_BlitzCache @OnlyQueryHashes = ''' + CONVERT(VARCHAR(32), [QueryHash], 1) + '''; ' - ELSE 'N/A' END, - QueryPlanHash BINARY(8), - StatementStartOffset INT, - StatementEndOffset INT, - PlanGenerationNum BIGINT, - MinReturnedRows BIGINT, - MaxReturnedRows BIGINT, - AverageReturnedRows MONEY, - TotalReturnedRows BIGINT, - LastReturnedRows BIGINT, - MinGrantKB BIGINT, - MaxGrantKB BIGINT, - MinUsedGrantKB BIGINT, - MaxUsedGrantKB BIGINT, - PercentMemoryGrantUsed MONEY, - AvgMaxMemoryGrant MONEY, - MinSpills BIGINT, - MaxSpills BIGINT, - TotalSpills BIGINT, - AvgSpills MONEY, - QueryText NVARCHAR(MAX), - QueryPlan XML, - /* these next four columns are the total for the type of query. - don't actually use them for anything apart from math by type. - */ - TotalWorkerTimeForType BIGINT, - TotalElapsedTimeForType BIGINT, - TotalReadsForType BIGINT, - TotalExecutionCountForType BIGINT, - TotalWritesForType BIGINT, - NumberOfPlans INT, - NumberOfDistinctPlans INT, - SerialDesiredMemory FLOAT, - SerialRequiredMemory FLOAT, - CachedPlanSize FLOAT, - CompileTime FLOAT, - CompileCPU FLOAT , - CompileMemory FLOAT , - MaxCompileMemory FLOAT , - min_worker_time BIGINT, - max_worker_time BIGINT, - is_forced_plan BIT, - is_forced_parameterized BIT, - is_cursor BIT, - is_optimistic_cursor BIT, - is_forward_only_cursor BIT, - is_fast_forward_cursor BIT, - is_cursor_dynamic BIT, - is_parallel BIT, - is_forced_serial BIT, - is_key_lookup_expensive BIT, - key_lookup_cost FLOAT, - is_remote_query_expensive BIT, - remote_query_cost FLOAT, - frequent_execution BIT, - parameter_sniffing BIT, - unparameterized_query BIT, - near_parallel BIT, - plan_warnings BIT, - plan_multiple_plans INT, - long_running BIT, - downlevel_estimator BIT, - implicit_conversions BIT, - busy_loops BIT, - tvf_join BIT, - tvf_estimate BIT, - compile_timeout BIT, - compile_memory_limit_exceeded BIT, - warning_no_join_predicate BIT, - QueryPlanCost FLOAT, - missing_index_count INT, - unmatched_index_count INT, - min_elapsed_time BIGINT, - max_elapsed_time BIGINT, - age_minutes MONEY, - age_minutes_lifetime MONEY, - is_trivial BIT, - trace_flags_session VARCHAR(1000), - is_unused_grant BIT, - function_count INT, - clr_function_count INT, - is_table_variable BIT, - no_stats_warning BIT, - relop_warnings BIT, - is_table_scan BIT, - backwards_scan BIT, - forced_index BIT, - forced_seek BIT, - forced_scan BIT, - columnstore_row_mode BIT, - is_computed_scalar BIT , - is_sort_expensive BIT, - sort_cost FLOAT, - is_computed_filter BIT, - op_name VARCHAR(100) NULL, - index_insert_count INT NULL, - index_update_count INT NULL, - index_delete_count INT NULL, - cx_insert_count INT NULL, - cx_update_count INT NULL, - cx_delete_count INT NULL, - table_insert_count INT NULL, - table_update_count INT NULL, - table_delete_count INT NULL, - index_ops AS (index_insert_count + index_update_count + index_delete_count + - cx_insert_count + cx_update_count + cx_delete_count + - table_insert_count + table_update_count + table_delete_count), - is_row_level BIT, - is_spatial BIT, - index_dml BIT, - table_dml BIT, - long_running_low_cpu BIT, - low_cost_high_cpu BIT, - stale_stats BIT, - is_adaptive BIT, - index_spool_cost FLOAT, - index_spool_rows FLOAT, - table_spool_cost FLOAT, - table_spool_rows FLOAT, - is_spool_expensive BIT, - is_spool_more_rows BIT, - is_table_spool_expensive BIT, - is_table_spool_more_rows BIT, - estimated_rows FLOAT, - is_bad_estimate BIT, - is_paul_white_electric BIT, - is_row_goal BIT, - is_big_spills BIT, - is_mstvf BIT, - is_mm_join BIT, - is_nonsargable BIT, - select_with_writes BIT, - implicit_conversion_info XML, - cached_execution_parameters XML, - missing_indexes XML, - SetOptions VARCHAR(MAX), - Warnings VARCHAR(MAX), - Pattern NVARCHAR(20) - ); -END; -ELSE -BEGIN - RAISERROR(N'Cleaning up old plans for your SPID', 0, 1) WITH NOWAIT; - DELETE ##BlitzCacheProcs - WHERE SPID = @@SPID - OPTION (RECOMPILE) ; -END + SELECT @ctp_threshold_pct = CAST(value AS TINYINT) + FROM #configuration + WHERE 'cost threshold for parallelism warning' = LOWER(parameter_name) ; -IF @Reanalyze = 0 -BEGIN - RAISERROR('Collecting execution plan information.', 0, 1) WITH NOWAIT; + SET @msg = ' Setting "cost threshold for parallelism warning" to ' + CAST(@ctp_threshold_pct AS VARCHAR(3)); - EXEC sp_executesql @sql, N'@Top INT, @min_duration INT, @min_back INT, @SortOrder NVARCHAR(20)', @Top, @DurationFilter_i, @MinutesBack, @SortOrder; + RAISERROR(@msg, 0, 1) WITH NOWAIT; END; -IF @SkipAnalysis = 1 - BEGIN - RAISERROR(N'Skipping analysis, going to results', 0, 1) WITH NOWAIT; - GOTO Results ; - END; - - -/* Update ##BlitzCacheProcs to get Stored Proc info - * This should get totals for all statements in a Stored Proc - */ -RAISERROR(N'Attempting to aggregate stored proc info from separate statements', 0, 1) WITH NOWAIT; -;WITH agg AS ( - SELECT - b.SqlHandle, - SUM(b.MinReturnedRows) AS MinReturnedRows, - SUM(b.MaxReturnedRows) AS MaxReturnedRows, - SUM(b.AverageReturnedRows) AS AverageReturnedRows, - SUM(b.TotalReturnedRows) AS TotalReturnedRows, - SUM(b.LastReturnedRows) AS LastReturnedRows, - SUM(b.MinGrantKB) AS MinGrantKB, - SUM(b.MaxGrantKB) AS MaxGrantKB, - SUM(b.MinUsedGrantKB) AS MinUsedGrantKB, - SUM(b.MaxUsedGrantKB) AS MaxUsedGrantKB, - SUM(b.MinSpills) AS MinSpills, - SUM(b.MaxSpills) AS MaxSpills, - SUM(b.TotalSpills) AS TotalSpills - FROM ##BlitzCacheProcs b - WHERE b.SPID = @@SPID - AND b.QueryHash IS NOT NULL - GROUP BY b.SqlHandle -) -UPDATE b - SET - b.MinReturnedRows = b2.MinReturnedRows, - b.MaxReturnedRows = b2.MaxReturnedRows, - b.AverageReturnedRows = b2.AverageReturnedRows, - b.TotalReturnedRows = b2.TotalReturnedRows, - b.LastReturnedRows = b2.LastReturnedRows, - b.MinGrantKB = b2.MinGrantKB, - b.MaxGrantKB = b2.MaxGrantKB, - b.MinUsedGrantKB = b2.MinUsedGrantKB, - b.MaxUsedGrantKB = b2.MaxUsedGrantKB, - b.MinSpills = b2.MinSpills, - b.MaxSpills = b2.MaxSpills, - b.TotalSpills = b2.TotalSpills -FROM ##BlitzCacheProcs b -JOIN agg b2 -ON b2.SqlHandle = b.SqlHandle -WHERE b.QueryHash IS NULL -AND b.SPID = @@SPID -OPTION (RECOMPILE) ; +IF EXISTS (SELECT 1/0 FROM #configuration WHERE 'long running query warning (seconds)' = LOWER(parameter_name)) +BEGIN + SELECT @long_running_query_warning_seconds = CAST(value * 1000 AS BIGINT) + FROM #configuration + WHERE 'long running query warning (seconds)' = LOWER(parameter_name) ; -/* Compute the total CPU, etc across our active set of the plan cache. - * Yes, there's a flaw - this doesn't include anything outside of our @Top - * metric. - */ -RAISERROR('Computing CPU, duration, read, and write metrics', 0, 1) WITH NOWAIT; -DECLARE @total_duration BIGINT, - @total_cpu BIGINT, - @total_reads BIGINT, - @total_writes BIGINT, - @total_execution_count BIGINT; + SET @msg = ' Setting "long running query warning (seconds)" to ' + CAST(@long_running_query_warning_seconds AS VARCHAR(10)); -SELECT @total_cpu = SUM(TotalCPU), - @total_duration = SUM(TotalDuration), - @total_reads = SUM(TotalReads), - @total_writes = SUM(TotalWrites), - @total_execution_count = SUM(ExecutionCount) -FROM #p -OPTION (RECOMPILE) ; + RAISERROR(@msg, 0, 1) WITH NOWAIT; +END; -DECLARE @cr NVARCHAR(1) = NCHAR(13); -DECLARE @lf NVARCHAR(1) = NCHAR(10); -DECLARE @tab NVARCHAR(1) = NCHAR(9); +IF EXISTS (SELECT 1/0 FROM #configuration WHERE 'unused memory grant' = LOWER(parameter_name)) +BEGIN + SELECT @memory_grant_warning_percent = CAST(value AS INT) + FROM #configuration + WHERE 'unused memory grant' = LOWER(parameter_name) ; -/* Update CPU percentage for stored procedures */ -RAISERROR(N'Update CPU percentage for stored procedures', 0, 1) WITH NOWAIT; -UPDATE ##BlitzCacheProcs -SET PercentCPU = y.PercentCPU, - PercentDuration = y.PercentDuration, - PercentReads = y.PercentReads, - PercentWrites = y.PercentWrites, - PercentExecutions = y.PercentExecutions, - ExecutionsPerMinute = y.ExecutionsPerMinute, - /* Strip newlines and tabs. Tabs are replaced with multiple spaces - so that the later whitespace trim will completely eliminate them - */ - QueryText = REPLACE(REPLACE(REPLACE(QueryText, @cr, ' '), @lf, ' '), @tab, ' ') -FROM ( - SELECT PlanHandle, - CASE @total_cpu WHEN 0 THEN 0 - ELSE CAST((100. * TotalCPU) / @total_cpu AS MONEY) END AS PercentCPU, - CASE @total_duration WHEN 0 THEN 0 - ELSE CAST((100. * TotalDuration) / @total_duration AS MONEY) END AS PercentDuration, - CASE @total_reads WHEN 0 THEN 0 - ELSE CAST((100. * TotalReads) / @total_reads AS MONEY) END AS PercentReads, - CASE @total_writes WHEN 0 THEN 0 - ELSE CAST((100. * TotalWrites) / @total_writes AS MONEY) END AS PercentWrites, - CASE @total_execution_count WHEN 0 THEN 0 - ELSE CAST((100. * ExecutionCount) / @total_execution_count AS MONEY) END AS PercentExecutions, - CASE DATEDIFF(mi, PlanCreationTime, LastExecutionTime) - WHEN 0 THEN 0 - ELSE CAST((1.00 * ExecutionCount / DATEDIFF(mi, PlanCreationTime, LastExecutionTime)) AS MONEY) - END AS ExecutionsPerMinute - FROM ( - SELECT PlanHandle, - TotalCPU, - TotalDuration, - TotalReads, - TotalWrites, - ExecutionCount, - PlanCreationTime, - LastExecutionTime - FROM ##BlitzCacheProcs - WHERE PlanHandle IS NOT NULL - AND SPID = @@SPID - GROUP BY PlanHandle, - TotalCPU, - TotalDuration, - TotalReads, - TotalWrites, - ExecutionCount, - PlanCreationTime, - LastExecutionTime - ) AS x -) AS y -WHERE ##BlitzCacheProcs.PlanHandle = y.PlanHandle - AND ##BlitzCacheProcs.PlanHandle IS NOT NULL - AND ##BlitzCacheProcs.SPID = @@SPID -OPTION (RECOMPILE) ; + SET @msg = ' Setting "unused memory grant" to ' + CAST(@memory_grant_warning_percent AS VARCHAR(10)); + RAISERROR(@msg, 0, 1) WITH NOWAIT; +END; -RAISERROR(N'Gather percentage information from grouped results', 0, 1) WITH NOWAIT; -UPDATE ##BlitzCacheProcs -SET PercentCPU = y.PercentCPU, - PercentDuration = y.PercentDuration, - PercentReads = y.PercentReads, - PercentWrites = y.PercentWrites, - PercentExecutions = y.PercentExecutions, - ExecutionsPerMinute = y.ExecutionsPerMinute, - /* Strip newlines and tabs. Tabs are replaced with multiple spaces - so that the later whitespace trim will completely eliminate them - */ - QueryText = REPLACE(REPLACE(REPLACE(QueryText, @cr, ' '), @lf, ' '), @tab, ' ') -FROM ( - SELECT DatabaseName, - SqlHandle, - QueryHash, - CASE @total_cpu WHEN 0 THEN 0 - ELSE CAST((100. * TotalCPU) / @total_cpu AS MONEY) END AS PercentCPU, - CASE @total_duration WHEN 0 THEN 0 - ELSE CAST((100. * TotalDuration) / @total_duration AS MONEY) END AS PercentDuration, - CASE @total_reads WHEN 0 THEN 0 - ELSE CAST((100. * TotalReads) / @total_reads AS MONEY) END AS PercentReads, - CASE @total_writes WHEN 0 THEN 0 - ELSE CAST((100. * TotalWrites) / @total_writes AS MONEY) END AS PercentWrites, - CASE @total_execution_count WHEN 0 THEN 0 - ELSE CAST((100. * ExecutionCount) / @total_execution_count AS MONEY) END AS PercentExecutions, - CASE DATEDIFF(mi, PlanCreationTime, LastExecutionTime) - WHEN 0 THEN 0 - ELSE CAST((1.00 * ExecutionCount / DATEDIFF(mi, PlanCreationTime, LastExecutionTime)) AS MONEY) - END AS ExecutionsPerMinute - FROM ( - SELECT DatabaseName, - SqlHandle, - QueryHash, - TotalCPU, - TotalDuration, - TotalReads, - TotalWrites, - ExecutionCount, - PlanCreationTime, - LastExecutionTime - FROM ##BlitzCacheProcs - WHERE SPID = @@SPID - GROUP BY DatabaseName, - SqlHandle, - QueryHash, - TotalCPU, - TotalDuration, - TotalReads, - TotalWrites, - ExecutionCount, - PlanCreationTime, - LastExecutionTime - ) AS x -) AS y -WHERE ##BlitzCacheProcs.SqlHandle = y.SqlHandle - AND ##BlitzCacheProcs.QueryHash = y.QueryHash - AND ##BlitzCacheProcs.DatabaseName = y.DatabaseName - AND ##BlitzCacheProcs.PlanHandle IS NULL -OPTION (RECOMPILE) ; +DECLARE @ctp INT ; +SELECT @ctp = NULLIF(CAST(value AS INT), 0) +FROM sys.configurations +WHERE name = 'cost threshold for parallelism' +OPTION (RECOMPILE); -/* Testing using XML nodes to speed up processing */ -RAISERROR(N'Begin XML nodes processing', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -SELECT QueryHash , - SqlHandle , - PlanHandle, - q.n.query('.') AS statement, - 0 AS is_cursor -INTO #statements -FROM ##BlitzCacheProcs p - CROSS APPLY p.QueryPlan.nodes('//p:StmtSimple') AS q(n) -WHERE p.SPID = @@SPID -OPTION (RECOMPILE) ; +/* Update to populate checks columns */ +RAISERROR('Checking for query level SQL Server issues.', 0, 1) WITH NOWAIT; WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -INSERT #statements -SELECT QueryHash , - SqlHandle , - PlanHandle, - q.n.query('.') AS statement, - 1 AS is_cursor -FROM ##BlitzCacheProcs p - CROSS APPLY p.QueryPlan.nodes('//p:StmtCursor') AS q(n) -WHERE p.SPID = @@SPID -OPTION (RECOMPILE) ; +UPDATE ##BlitzCacheProcs +SET frequent_execution = CASE WHEN ExecutionsPerMinute > @execution_threshold THEN 1 END , + parameter_sniffing = CASE WHEN ExecutionCount > 3 AND AverageReads > @parameter_sniffing_io_threshold + AND min_worker_time < ((1.0 - (@parameter_sniffing_warning_pct / 100.0)) * AverageCPU) THEN 1 + WHEN ExecutionCount > 3 AND AverageReads > @parameter_sniffing_io_threshold + AND max_worker_time > ((1.0 + (@parameter_sniffing_warning_pct / 100.0)) * AverageCPU) THEN 1 + WHEN ExecutionCount > 3 AND AverageReads > @parameter_sniffing_io_threshold + AND MinReturnedRows < ((1.0 - (@parameter_sniffing_warning_pct / 100.0)) * AverageReturnedRows) THEN 1 + WHEN ExecutionCount > 3 AND AverageReads > @parameter_sniffing_io_threshold + AND MaxReturnedRows > ((1.0 + (@parameter_sniffing_warning_pct / 100.0)) * AverageReturnedRows) THEN 1 END , + near_parallel = CASE WHEN is_parallel <> 1 AND QueryPlanCost BETWEEN @ctp * (1 - (@ctp_threshold_pct / 100.0)) AND @ctp THEN 1 END, + long_running = CASE WHEN AverageDuration > @long_running_query_warning_seconds THEN 1 + WHEN max_worker_time > @long_running_query_warning_seconds THEN 1 + WHEN max_elapsed_time > @long_running_query_warning_seconds THEN 1 END, + is_key_lookup_expensive = CASE WHEN QueryPlanCost >= (@ctp / 2) AND key_lookup_cost >= QueryPlanCost * .5 THEN 1 END, + is_sort_expensive = CASE WHEN QueryPlanCost >= (@ctp / 2) AND sort_cost >= QueryPlanCost * .5 THEN 1 END, + is_remote_query_expensive = CASE WHEN remote_query_cost >= QueryPlanCost * .05 THEN 1 END, + is_unused_grant = CASE WHEN PercentMemoryGrantUsed <= @memory_grant_warning_percent AND MinGrantKB > @MinMemoryPerQuery THEN 1 END, + long_running_low_cpu = CASE WHEN AverageDuration > AverageCPU * 4 AND AverageCPU < 500. THEN 1 END, + low_cost_high_cpu = CASE WHEN QueryPlanCost <= 10 AND AverageCPU > 5000. THEN 1 END, + is_spool_expensive = CASE WHEN QueryPlanCost > (@ctp / 5) AND index_spool_cost >= QueryPlanCost * .1 THEN 1 END, + is_spool_more_rows = CASE WHEN index_spool_rows >= (AverageReturnedRows / ISNULL(NULLIF(ExecutionCount, 0), 1)) THEN 1 END, + is_table_spool_expensive = CASE WHEN QueryPlanCost > (@ctp / 5) AND table_spool_cost >= QueryPlanCost / 4 THEN 1 END, + is_table_spool_more_rows = CASE WHEN table_spool_rows >= (AverageReturnedRows / ISNULL(NULLIF(ExecutionCount, 0), 1)) THEN 1 END, + is_bad_estimate = CASE WHEN AverageReturnedRows > 0 AND (estimated_rows * 1000 < AverageReturnedRows OR estimated_rows > AverageReturnedRows * 1000) THEN 1 END, + is_big_spills = CASE WHEN (AvgSpills / 128.) > 499. THEN 1 END +WHERE SPID = @@SPID +OPTION (RECOMPILE); -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -SELECT QueryHash , - SqlHandle , - q.n.query('.') AS query_plan -INTO #query_plan -FROM #statements p - CROSS APPLY p.statement.nodes('//p:QueryPlan') AS q(n) -OPTION (RECOMPILE) ; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -SELECT QueryHash , - SqlHandle , - q.n.query('.') AS relop -INTO #relop -FROM #query_plan p - CROSS APPLY p.query_plan.nodes('//p:RelOp') AS q(n) -OPTION (RECOMPILE) ; --- high level plan stuff -RAISERROR(N'Gathering high level plan information', 0, 1) WITH NOWAIT; -UPDATE ##BlitzCacheProcs -SET NumberOfDistinctPlans = distinct_plan_count, - NumberOfPlans = number_of_plans , - plan_multiple_plans = CASE WHEN distinct_plan_count < number_of_plans THEN number_of_plans END -FROM - ( - SELECT - DatabaseName = - DB_NAME(CONVERT(int, pa.value)), - QueryHash = - qs.query_hash, - number_of_plans = - COUNT_BIG(qs.query_plan_hash), - distinct_plan_count = - COUNT_BIG(DISTINCT qs.query_plan_hash) - FROM sys.dm_exec_query_stats AS qs - CROSS APPLY sys.dm_exec_plan_attributes(qs.plan_handle) pa - WHERE pa.attribute = 'dbid' - GROUP BY - DB_NAME(CONVERT(int, pa.value)), - qs.query_hash -) AS x -WHERE ##BlitzCacheProcs.QueryHash = x.QueryHash -AND ##BlitzCacheProcs.DatabaseName = x.DatabaseName -OPTION (RECOMPILE) ; +RAISERROR('Checking for forced parameterization and cursors.', 0, 1) WITH NOWAIT; --- query level checks -RAISERROR(N'Performing query level checks', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE ##BlitzCacheProcs -SET missing_index_count = query_plan.value('count(//p:QueryPlan/p:MissingIndexes/p:MissingIndexGroup)', 'int') , - unmatched_index_count = CASE WHEN is_trivial <> 1 THEN query_plan.value('count(//p:QueryPlan/p:UnmatchedIndexes/p:Parameterization/p:Object)', 'int') END , - SerialDesiredMemory = query_plan.value('sum(//p:QueryPlan/p:MemoryGrantInfo/@SerialDesiredMemory)', 'float') , - SerialRequiredMemory = query_plan.value('sum(//p:QueryPlan/p:MemoryGrantInfo/@SerialRequiredMemory)', 'float'), - CachedPlanSize = query_plan.value('sum(//p:QueryPlan/@CachedPlanSize)', 'float') , - CompileTime = query_plan.value('sum(//p:QueryPlan/@CompileTime)', 'float') , - CompileCPU = query_plan.value('sum(//p:QueryPlan/@CompileCPU)', 'float') , - CompileMemory = query_plan.value('sum(//p:QueryPlan/@CompileMemory)', 'float'), - MaxCompileMemory = query_plan.value('sum(//p:QueryPlan/p:OptimizerHardwareDependentProperties/@MaxCompileMemory)', 'float') -FROM #query_plan qp -WHERE qp.QueryHash = ##BlitzCacheProcs.QueryHash -AND qp.SqlHandle = ##BlitzCacheProcs.SqlHandle +/* Set options checks */ +UPDATE p + SET is_forced_parameterized = CASE WHEN (CAST(pa.value AS INT) & 131072 = 131072) THEN 1 END , + is_forced_plan = CASE WHEN (CAST(pa.value AS INT) & 4 = 4) THEN 1 END , + SetOptions = SUBSTRING( + CASE WHEN (CAST(pa.value AS INT) & 1 = 1) THEN ', ANSI_PADDING' ELSE '' END + + CASE WHEN (CAST(pa.value AS INT) & 8 = 8) THEN ', CONCAT_NULL_YIELDS_NULL' ELSE '' END + + CASE WHEN (CAST(pa.value AS INT) & 16 = 16) THEN ', ANSI_WARNINGS' ELSE '' END + + CASE WHEN (CAST(pa.value AS INT) & 32 = 32) THEN ', ANSI_NULLS' ELSE '' END + + CASE WHEN (CAST(pa.value AS INT) & 64 = 64) THEN ', QUOTED_IDENTIFIER' ELSE '' END + + CASE WHEN (CAST(pa.value AS INT) & 4096 = 4096) THEN ', ARITH_ABORT' ELSE '' END + + CASE WHEN (CAST(pa.value AS INT) & 8192 = 8191) THEN ', NUMERIC_ROUNDABORT' ELSE '' END + , 2, 200000) +FROM ##BlitzCacheProcs p + CROSS APPLY sys.dm_exec_plan_attributes(p.PlanHandle) pa +WHERE pa.attribute = 'set_options' AND SPID = @@SPID OPTION (RECOMPILE); --- statement level checks -RAISERROR(N'Performing compile timeout checks', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET compile_timeout = 1 -FROM #statements s -JOIN ##BlitzCacheProcs b -ON s.QueryHash = b.QueryHash -AND SPID = @@SPID -WHERE statement.exist('/p:StmtSimple/@StatementOptmEarlyAbortReason[.="TimeOut"]') = 1 -OPTION (RECOMPILE); -RAISERROR(N'Performing compile memory limit exceeded checks', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET compile_memory_limit_exceeded = 1 -FROM #statements s -JOIN ##BlitzCacheProcs b -ON s.QueryHash = b.QueryHash +/* Cursor checks */ +UPDATE p +SET is_cursor = CASE WHEN CAST(pa.value AS INT) <> 0 THEN 1 END +FROM ##BlitzCacheProcs p + CROSS APPLY sys.dm_exec_plan_attributes(p.PlanHandle) pa +WHERE pa.attribute LIKE '%cursor%' AND SPID = @@SPID -WHERE statement.exist('/p:StmtSimple/@StatementOptmEarlyAbortReason[.="MemoryLimitExceeded"]') = 1 OPTION (RECOMPILE); -IF @ExpertMode > 0 -BEGIN -RAISERROR(N'Performing unparameterized query checks', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p), -unparameterized_query AS ( - SELECT s.QueryHash, - unparameterized_query = CASE WHEN statement.exist('//p:StmtSimple[@StatementOptmLevel[.="FULL"]]/p:QueryPlan/p:ParameterList') = 1 AND - statement.exist('//p:StmtSimple[@StatementOptmLevel[.="FULL"]]/p:QueryPlan/p:ParameterList/p:ColumnReference') = 0 THEN 1 - WHEN statement.exist('//p:StmtSimple[@StatementOptmLevel[.="FULL"]]/p:QueryPlan/p:ParameterList') = 0 AND - statement.exist('//p:StmtSimple[@StatementOptmLevel[.="FULL"]]/*/p:RelOp/descendant::p:ScalarOperator/p:Identifier/p:ColumnReference[contains(@Column, "@")]') = 1 THEN 1 - END - FROM #statements AS s - ) -UPDATE b -SET b.unparameterized_query = u.unparameterized_query -FROM ##BlitzCacheProcs b -JOIN unparameterized_query u -ON u.QueryHash = b.QueryHash +UPDATE p +SET is_cursor = 1 +FROM ##BlitzCacheProcs p +WHERE QueryHash = 0x0000000000000000 +OR QueryPlanHash = 0x0000000000000000 AND SPID = @@SPID -WHERE u.unparameterized_query = 1 OPTION (RECOMPILE); -END; - - -IF @ExpertMode > 0 -BEGIN -RAISERROR(N'Performing index DML checks', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p), -index_dml AS ( - SELECT s.QueryHash, - index_dml = CASE WHEN statement.exist('//p:StmtSimple/@StatementType[.="CREATE INDEX"]') = 1 THEN 1 - WHEN statement.exist('//p:StmtSimple/@StatementType[.="DROP INDEX"]') = 1 THEN 1 - END - FROM #statements s - ) - UPDATE b - SET b.index_dml = i.index_dml - FROM ##BlitzCacheProcs AS b - JOIN index_dml i - ON i.QueryHash = b.QueryHash - WHERE i.index_dml = 1 - AND b.SPID = @@SPID - OPTION (RECOMPILE); -END; - - -IF @ExpertMode > 0 -BEGIN -RAISERROR(N'Performing table DML checks', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p), -table_dml AS ( - SELECT s.QueryHash, - table_dml = CASE WHEN statement.exist('//p:StmtSimple/@StatementType[.="CREATE TABLE"]') = 1 THEN 1 - WHEN statement.exist('//p:StmtSimple/@StatementType[.="DROP OBJECT"]') = 1 THEN 1 - END - FROM #statements AS s - ) - UPDATE b - SET b.table_dml = t.table_dml - FROM ##BlitzCacheProcs AS b - JOIN table_dml t - ON t.QueryHash = b.QueryHash - WHERE t.table_dml = 1 - AND b.SPID = @@SPID - OPTION (RECOMPILE); -END; - - -IF @ExpertMode > 0 -BEGIN -RAISERROR(N'Gathering row estimates', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES ('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) -INSERT INTO #est_rows -SELECT DISTINCT - CONVERT(BINARY(8), RIGHT('0000000000000000' + SUBSTRING(c.n.value('@QueryHash', 'VARCHAR(18)'), 3, 18), 16), 2) AS QueryHash, - c.n.value('(/p:StmtSimple/@StatementEstRows)[1]', 'FLOAT') AS estimated_rows -FROM #statements AS s -CROSS APPLY s.statement.nodes('/p:StmtSimple') AS c(n) -WHERE c.n.exist('/p:StmtSimple[@StatementEstRows > 0]') = 1; - - UPDATE b - SET b.estimated_rows = er.estimated_rows - FROM ##BlitzCacheProcs AS b - JOIN #est_rows er - ON er.QueryHash = b.QueryHash - WHERE b.SPID = @@SPID - AND b.QueryType = 'Statement' - OPTION (RECOMPILE); -END; -RAISERROR(N'Gathering trivial plans', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES ( 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) -UPDATE b -SET b.is_trivial = 1 -FROM ##BlitzCacheProcs AS b -JOIN ( -SELECT s.SqlHandle -FROM #statements AS s -JOIN ( SELECT r.SqlHandle - FROM #relop AS r - WHERE r.relop.exist('//p:RelOp[contains(@LogicalOp, "Scan")]') = 1 ) AS r - ON r.SqlHandle = s.SqlHandle -WHERE s.statement.exist('//p:StmtSimple[@StatementOptmLevel[.="TRIVIAL"]]/p:QueryPlan/p:ParameterList') = 1 -) AS s -ON b.SqlHandle = s.SqlHandle -OPTION (RECOMPILE); ---Gather costs -RAISERROR(N'Gathering statement costs', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -INSERT INTO #plan_cost ( QueryPlanCost, SqlHandle, PlanHandle, QueryHash, QueryPlanHash ) -SELECT DISTINCT - statement.value('sum(/p:StmtSimple/@StatementSubTreeCost)', 'float') QueryPlanCost, - s.SqlHandle, - s.PlanHandle, - CONVERT(BINARY(8), RIGHT('0000000000000000' + SUBSTRING(q.n.value('@QueryHash', 'VARCHAR(18)'), 3, 18), 16), 2) AS QueryHash, - CONVERT(BINARY(8), RIGHT('0000000000000000' + SUBSTRING(q.n.value('@QueryPlanHash', 'VARCHAR(18)'), 3, 18), 16), 2) AS QueryPlanHash -FROM #statements s -CROSS APPLY s.statement.nodes('/p:StmtSimple') AS q(n) -WHERE statement.value('sum(/p:StmtSimple/@StatementSubTreeCost)', 'float') > 0 -OPTION (RECOMPILE); - -RAISERROR(N'Updating statement costs', 0, 1) WITH NOWAIT; -WITH pc AS ( - SELECT SUM(DISTINCT pc.QueryPlanCost) AS QueryPlanCostSum, pc.QueryHash, pc.QueryPlanHash, pc.SqlHandle, pc.PlanHandle - FROM #plan_cost AS pc - GROUP BY pc.QueryHash, pc.QueryPlanHash, pc.SqlHandle, pc.PlanHandle -) - UPDATE b - SET b.QueryPlanCost = ISNULL(pc.QueryPlanCostSum, 0) - FROM pc - JOIN ##BlitzCacheProcs b - ON b.SqlHandle = pc.SqlHandle - AND b.QueryHash = pc.QueryHash - WHERE b.QueryType NOT LIKE '%Procedure%' - OPTION (RECOMPILE); - -IF EXISTS ( -SELECT 1 -FROM ##BlitzCacheProcs AS b -WHERE b.QueryType LIKE 'Procedure%' -) - -BEGIN - -RAISERROR(N'Gathering stored procedure costs', 0, 1) WITH NOWAIT; -;WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -, QueryCost AS ( - SELECT - DISTINCT - statement.value('sum(/p:StmtSimple/@StatementSubTreeCost)', 'float') AS SubTreeCost, - s.PlanHandle, - s.SqlHandle - FROM #statements AS s - WHERE PlanHandle IS NOT NULL -) -, QueryCostUpdate AS ( - SELECT - SUM(qc.SubTreeCost) OVER (PARTITION BY SqlHandle, PlanHandle) PlanTotalQuery, - qc.PlanHandle, - qc.SqlHandle - FROM QueryCost qc -) -INSERT INTO #proc_costs -SELECT qcu.PlanTotalQuery, PlanHandle, SqlHandle -FROM QueryCostUpdate AS qcu +RAISERROR('Populating Warnings column', 0, 1) WITH NOWAIT; +/* Populate warnings */ +UPDATE ##BlitzCacheProcs +SET Warnings = SUBSTRING( + CASE WHEN warning_no_join_predicate = 1 THEN ', No Join Predicate' ELSE '' END + + CASE WHEN compile_timeout = 1 THEN ', Compilation Timeout' ELSE '' END + + CASE WHEN compile_memory_limit_exceeded = 1 THEN ', Compile Memory Limit Exceeded' ELSE '' END + + CASE WHEN busy_loops = 1 THEN ', Busy Loops' ELSE '' END + + CASE WHEN is_forced_plan = 1 THEN ', Forced Plan' ELSE '' END + + CASE WHEN is_forced_parameterized = 1 THEN ', Forced Parameterization' ELSE '' END + + CASE WHEN unparameterized_query = 1 THEN ', Unparameterized Query' ELSE '' END + + CASE WHEN missing_index_count > 0 THEN ', Missing Indexes (' + CAST(missing_index_count AS VARCHAR(3)) + ')' ELSE '' END + + CASE WHEN unmatched_index_count > 0 THEN ', Unmatched Indexes (' + CAST(unmatched_index_count AS VARCHAR(3)) + ')' ELSE '' END + + CASE WHEN is_cursor = 1 THEN ', Cursor' + + CASE WHEN is_optimistic_cursor = 1 THEN '; optimistic' ELSE '' END + + CASE WHEN is_forward_only_cursor = 0 THEN '; not forward only' ELSE '' END + + CASE WHEN is_cursor_dynamic = 1 THEN '; dynamic' ELSE '' END + + CASE WHEN is_fast_forward_cursor = 1 THEN '; fast forward' ELSE '' END + ELSE '' END + + CASE WHEN is_parallel = 1 THEN ', Parallel' ELSE '' END + + CASE WHEN near_parallel = 1 THEN ', Nearly Parallel' ELSE '' END + + CASE WHEN frequent_execution = 1 THEN ', Frequent Execution' ELSE '' END + + CASE WHEN plan_warnings = 1 THEN ', Plan Warnings' ELSE '' END + + CASE WHEN parameter_sniffing = 1 THEN ', Parameter Sniffing' ELSE '' END + + CASE WHEN long_running = 1 THEN ', Long Running Query' ELSE '' END + + CASE WHEN downlevel_estimator = 1 THEN ', Downlevel CE' ELSE '' END + + CASE WHEN implicit_conversions = 1 THEN ', Implicit Conversions' ELSE '' END + + CASE WHEN tvf_join = 1 THEN ', Function Join' ELSE '' END + + CASE WHEN plan_multiple_plans > 0 THEN ', Multiple Plans' + COALESCE(' (' + CAST(plan_multiple_plans AS VARCHAR(10)) + ')', '') ELSE '' END + + CASE WHEN is_trivial = 1 THEN ', Trivial Plans' ELSE '' END + + CASE WHEN is_forced_serial = 1 THEN ', Forced Serialization' ELSE '' END + + CASE WHEN is_key_lookup_expensive = 1 THEN ', Expensive Key Lookup' ELSE '' END + + CASE WHEN is_remote_query_expensive = 1 THEN ', Expensive Remote Query' ELSE '' END + + CASE WHEN trace_flags_session IS NOT NULL THEN ', Session Level Trace Flag(s) Enabled: ' + trace_flags_session ELSE '' END + + CASE WHEN is_unused_grant = 1 THEN ', Unused Memory Grant' ELSE '' END + + CASE WHEN function_count > 0 THEN ', Calls ' + CONVERT(VARCHAR(10), function_count) + ' Function(s)' ELSE '' END + + CASE WHEN clr_function_count > 0 THEN ', Calls ' + CONVERT(VARCHAR(10), clr_function_count) + ' CLR Function(s)' ELSE '' END + + CASE WHEN PlanCreationTimeHours <= 4 THEN ', Plan created last 4hrs' ELSE '' END + + CASE WHEN is_table_variable = 1 THEN ', Table Variables' ELSE '' END + + CASE WHEN no_stats_warning = 1 THEN ', Columns With No Statistics' ELSE '' END + + CASE WHEN relop_warnings = 1 THEN ', Operator Warnings' ELSE '' END + + CASE WHEN is_table_scan = 1 THEN ', Table Scans (Heaps)' ELSE '' END + + CASE WHEN backwards_scan = 1 THEN ', Backwards Scans' ELSE '' END + + CASE WHEN forced_index = 1 THEN ', Forced Indexes' ELSE '' END + + CASE WHEN forced_seek = 1 THEN ', Forced Seeks' ELSE '' END + + CASE WHEN forced_scan = 1 THEN ', Forced Scans' ELSE '' END + + CASE WHEN columnstore_row_mode = 1 THEN ', ColumnStore Row Mode ' ELSE '' END + + CASE WHEN is_computed_scalar = 1 THEN ', Computed Column UDF ' ELSE '' END + + CASE WHEN is_sort_expensive = 1 THEN ', Expensive Sort' ELSE '' END + + CASE WHEN is_computed_filter = 1 THEN ', Filter UDF' ELSE '' END + + CASE WHEN index_ops >= 5 THEN ', >= 5 Indexes Modified' ELSE '' END + + CASE WHEN is_row_level = 1 THEN ', Row Level Security' ELSE '' END + + CASE WHEN is_spatial = 1 THEN ', Spatial Index' ELSE '' END + + CASE WHEN index_dml = 1 THEN ', Index DML' ELSE '' END + + CASE WHEN table_dml = 1 THEN ', Table DML' ELSE '' END + + CASE WHEN low_cost_high_cpu = 1 THEN ', Low Cost High CPU' ELSE '' END + + CASE WHEN long_running_low_cpu = 1 THEN + ', Long Running With Low CPU' ELSE '' END + + CASE WHEN stale_stats = 1 THEN + ', Statistics used have > 100k modifications in the last 7 days' ELSE '' END + + CASE WHEN is_adaptive = 1 THEN + ', Adaptive Joins' ELSE '' END + + CASE WHEN is_spool_expensive = 1 THEN + ', Expensive Index Spool' ELSE '' END + + CASE WHEN is_spool_more_rows = 1 THEN + ', Large Index Row Spool' ELSE '' END + + CASE WHEN is_table_spool_expensive = 1 THEN + ', Expensive Table Spool' ELSE '' END + + CASE WHEN is_table_spool_more_rows = 1 THEN + ', Many Rows Table Spool' ELSE '' END + + CASE WHEN is_bad_estimate = 1 THEN + ', Row Estimate Mismatch' ELSE '' END + + CASE WHEN is_paul_white_electric = 1 THEN ', SWITCH!' ELSE '' END + + CASE WHEN is_row_goal = 1 THEN ', Row Goals' ELSE '' END + + CASE WHEN is_big_spills = 1 THEN ', >500mb Spills' ELSE '' END + + CASE WHEN is_mstvf = 1 THEN ', MSTVFs' ELSE '' END + + CASE WHEN is_mm_join = 1 THEN ', Many to Many Merge' ELSE '' END + + CASE WHEN is_nonsargable = 1 THEN ', non-SARGables' ELSE '' END + + CASE WHEN CompileTime > 5000 THEN ', Long Compile Time' ELSE '' END + + CASE WHEN CompileCPU > 5000 THEN ', High Compile CPU' ELSE '' END + + CASE WHEN CompileMemory > 1024 AND ((CompileMemory) / (1 * CASE WHEN MaxCompileMemory = 0 THEN 1 ELSE MaxCompileMemory END) * 100.) >= 10. THEN ', High Compile Memory' ELSE '' END + + CASE WHEN select_with_writes > 0 THEN ', Select w/ Writes' ELSE '' END + , 3, 200000) +WHERE SPID = @@SPID OPTION (RECOMPILE); +RAISERROR('Populating Warnings column for stored procedures', 0, 1) WITH NOWAIT; +WITH statement_warnings AS + ( +SELECT DISTINCT + SqlHandle, + Warnings = SUBSTRING( + CASE WHEN warning_no_join_predicate = 1 THEN ', No Join Predicate' ELSE '' END + + CASE WHEN compile_timeout = 1 THEN ', Compilation Timeout' ELSE '' END + + CASE WHEN compile_memory_limit_exceeded = 1 THEN ', Compile Memory Limit Exceeded' ELSE '' END + + CASE WHEN busy_loops = 1 THEN ', Busy Loops' ELSE '' END + + CASE WHEN is_forced_plan = 1 THEN ', Forced Plan' ELSE '' END + + CASE WHEN is_forced_parameterized = 1 THEN ', Forced Parameterization' ELSE '' END + + --CASE WHEN unparameterized_query = 1 THEN ', Unparameterized Query' ELSE '' END + + CASE WHEN missing_index_count > 0 THEN ', Missing Indexes (' + CONVERT(VARCHAR(10), (SELECT SUM(b2.missing_index_count) FROM ##BlitzCacheProcs AS b2 WHERE b2.SqlHandle = b.SqlHandle AND b2.QueryHash IS NOT NULL AND SPID = @@SPID) ) + ')' ELSE '' END + + CASE WHEN unmatched_index_count > 0 THEN ', Unmatched Indexes (' + CONVERT(VARCHAR(10), (SELECT SUM(b2.unmatched_index_count) FROM ##BlitzCacheProcs AS b2 WHERE b2.SqlHandle = b.SqlHandle AND b2.QueryHash IS NOT NULL AND SPID = @@SPID) ) + ')' ELSE '' END + + CASE WHEN is_cursor = 1 THEN ', Cursor' + + CASE WHEN is_optimistic_cursor = 1 THEN '; optimistic' ELSE '' END + + CASE WHEN is_forward_only_cursor = 0 THEN '; not forward only' ELSE '' END + + CASE WHEN is_cursor_dynamic = 1 THEN '; dynamic' ELSE '' END + + CASE WHEN is_fast_forward_cursor = 1 THEN '; fast forward' ELSE '' END + ELSE '' END + + CASE WHEN is_parallel = 1 THEN ', Parallel' ELSE '' END + + CASE WHEN near_parallel = 1 THEN ', Nearly Parallel' ELSE '' END + + CASE WHEN frequent_execution = 1 THEN ', Frequent Execution' ELSE '' END + + CASE WHEN plan_warnings = 1 THEN ', Plan Warnings' ELSE '' END + + CASE WHEN parameter_sniffing = 1 THEN ', Parameter Sniffing' ELSE '' END + + CASE WHEN long_running = 1 THEN ', Long Running Query' ELSE '' END + + CASE WHEN downlevel_estimator = 1 THEN ', Downlevel CE' ELSE '' END + + CASE WHEN implicit_conversions = 1 THEN ', Implicit Conversions' ELSE '' END + + CASE WHEN tvf_join = 1 THEN ', Function Join' ELSE '' END + + CASE WHEN plan_multiple_plans > 0 THEN ', Multiple Plans' + COALESCE(' (' + CAST(plan_multiple_plans AS VARCHAR(10)) + ')', '') ELSE '' END + + CASE WHEN is_trivial = 1 THEN ', Trivial Plans' ELSE '' END + + CASE WHEN is_forced_serial = 1 THEN ', Forced Serialization' ELSE '' END + + CASE WHEN is_key_lookup_expensive = 1 THEN ', Expensive Key Lookup' ELSE '' END + + CASE WHEN is_remote_query_expensive = 1 THEN ', Expensive Remote Query' ELSE '' END + + CASE WHEN trace_flags_session IS NOT NULL THEN ', Session Level Trace Flag(s) Enabled: ' + trace_flags_session ELSE '' END + + CASE WHEN is_unused_grant = 1 THEN ', Unused Memory Grant' ELSE '' END + + CASE WHEN function_count > 0 THEN ', Calls ' + CONVERT(VARCHAR(10), (SELECT SUM(b2.function_count) FROM ##BlitzCacheProcs AS b2 WHERE b2.SqlHandle = b.SqlHandle AND b2.QueryHash IS NOT NULL AND SPID = @@SPID) ) + ' Function(s)' ELSE '' END + + CASE WHEN clr_function_count > 0 THEN ', Calls ' + CONVERT(VARCHAR(10), (SELECT SUM(b2.clr_function_count) FROM ##BlitzCacheProcs AS b2 WHERE b2.SqlHandle = b.SqlHandle AND b2.QueryHash IS NOT NULL AND SPID = @@SPID) ) + ' CLR Function(s)' ELSE '' END + + CASE WHEN PlanCreationTimeHours <= 4 THEN ', Plan created last 4hrs' ELSE '' END + + CASE WHEN is_table_variable = 1 THEN ', Table Variables' ELSE '' END + + CASE WHEN no_stats_warning = 1 THEN ', Columns With No Statistics' ELSE '' END + + CASE WHEN relop_warnings = 1 THEN ', Operator Warnings' ELSE '' END + + CASE WHEN is_table_scan = 1 THEN ', Table Scans' ELSE '' END + + CASE WHEN backwards_scan = 1 THEN ', Backwards Scans' ELSE '' END + + CASE WHEN forced_index = 1 THEN ', Forced Indexes' ELSE '' END + + CASE WHEN forced_seek = 1 THEN ', Forced Seeks' ELSE '' END + + CASE WHEN forced_scan = 1 THEN ', Forced Scans' ELSE '' END + + CASE WHEN columnstore_row_mode = 1 THEN ', ColumnStore Row Mode ' ELSE '' END + + CASE WHEN is_computed_scalar = 1 THEN ', Computed Column UDF ' ELSE '' END + + CASE WHEN is_sort_expensive = 1 THEN ', Expensive Sort' ELSE '' END + + CASE WHEN is_computed_filter = 1 THEN ', Filter UDF' ELSE '' END + + CASE WHEN index_ops >= 5 THEN ', >= 5 Indexes Modified' ELSE '' END + + CASE WHEN is_row_level = 1 THEN ', Row Level Security' ELSE '' END + + CASE WHEN is_spatial = 1 THEN ', Spatial Index' ELSE '' END + + CASE WHEN index_dml = 1 THEN ', Index DML' ELSE '' END + + CASE WHEN table_dml = 1 THEN ', Table DML' ELSE '' END + + CASE WHEN low_cost_high_cpu = 1 THEN ', Low Cost High CPU' ELSE '' END + + CASE WHEN long_running_low_cpu = 1 THEN + ', Long Running With Low CPU' ELSE '' END + + CASE WHEN stale_stats = 1 THEN + ', Statistics used have > 100k modifications in the last 7 days' ELSE '' END + + CASE WHEN is_adaptive = 1 THEN + ', Adaptive Joins' ELSE '' END + + CASE WHEN is_spool_expensive = 1 THEN + ', Expensive Index Spool' ELSE '' END + + CASE WHEN is_spool_more_rows = 1 THEN + ', Large Index Row Spool' ELSE '' END + + CASE WHEN is_table_spool_expensive = 1 THEN + ', Expensive Table Spool' ELSE '' END + + CASE WHEN is_table_spool_more_rows = 1 THEN + ', Many Rows Table Spool' ELSE '' END + + CASE WHEN is_bad_estimate = 1 THEN + ', Row Estimate Mismatch' ELSE '' END + + CASE WHEN is_paul_white_electric = 1 THEN ', SWITCH!' ELSE '' END + + CASE WHEN is_row_goal = 1 THEN ', Row Goals' ELSE '' END + + CASE WHEN is_big_spills = 1 THEN ', >500mb spills' ELSE '' END + + CASE WHEN is_mstvf = 1 THEN ', MSTVFs' ELSE '' END + + CASE WHEN is_mm_join = 1 THEN ', Many to Many Merge' ELSE '' END + + CASE WHEN is_nonsargable = 1 THEN ', non-SARGables' ELSE '' END + + CASE WHEN CompileTime > 5000 THEN ', Long Compile Time' ELSE '' END + + CASE WHEN CompileCPU > 5000 THEN ', High Compile CPU' ELSE '' END + + CASE WHEN CompileMemory > 1024 AND ((CompileMemory) / (1 * CASE WHEN MaxCompileMemory = 0 THEN 1 ELSE MaxCompileMemory END) * 100.) >= 10. THEN ', High Compile Memory' ELSE '' END + + CASE WHEN select_with_writes > 0 THEN ', Select w/ Writes' ELSE '' END + , 3, 200000) +FROM ##BlitzCacheProcs b +WHERE SPID = @@SPID +AND QueryType LIKE 'Statement (parent%' + ) UPDATE b - SET b.QueryPlanCost = ca.PlanTotalQuery +SET b.Warnings = s.Warnings FROM ##BlitzCacheProcs AS b -CROSS APPLY ( - SELECT TOP 1 PlanTotalQuery - FROM #proc_costs qcu - WHERE qcu.PlanHandle = b.PlanHandle - ORDER BY PlanTotalQuery DESC -) ca -WHERE b.QueryType LIKE 'Procedure%' -AND b.SPID = @@SPID +JOIN statement_warnings s +ON b.SqlHandle = s.SqlHandle +WHERE QueryType LIKE 'Procedure or Function%' +AND SPID = @@SPID OPTION (RECOMPILE); -END; - +RAISERROR('Checking for plans with >128 levels of nesting', 0, 1) WITH NOWAIT; +WITH plan_handle AS ( +SELECT b.PlanHandle +FROM ##BlitzCacheProcs b + CROSS APPLY sys.dm_exec_text_query_plan(b.PlanHandle, 0, -1) tqp + CROSS APPLY sys.dm_exec_query_plan(b.PlanHandle) qp + WHERE tqp.encrypted = 0 + AND b.SPID = @@SPID + AND (qp.query_plan IS NULL + AND tqp.query_plan IS NOT NULL) +) UPDATE b -SET b.QueryPlanCost = 0.0 +SET Warnings = ISNULL('Your query plan is >128 levels of nested nodes, and can''t be converted to XML. Use SELECT * FROM sys.dm_exec_text_query_plan('+ CONVERT(VARCHAR(128), ph.PlanHandle, 1) + ', 0, -1) to get more information' + , 'We couldn''t find a plan for this query. More info on possible reasons: https://www.brentozar.com/go/noplans') FROM ##BlitzCacheProcs b -WHERE b.QueryPlanCost IS NULL +LEFT JOIN plan_handle ph ON +b.PlanHandle = ph.PlanHandle +WHERE b.QueryPlan IS NULL AND b.SPID = @@SPID -OPTION (RECOMPILE); +OPTION (RECOMPILE); -RAISERROR(N'Checking for plan warnings', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE ##BlitzCacheProcs -SET plan_warnings = 1 -FROM #query_plan qp -WHERE qp.SqlHandle = ##BlitzCacheProcs.SqlHandle +RAISERROR('Checking for plans with no warnings', 0, 1) WITH NOWAIT; +UPDATE ##BlitzCacheProcs +SET Warnings = 'No warnings detected. ' + CASE @ExpertMode + WHEN 0 + THEN ' Try running sp_BlitzCache with @ExpertMode = 1 to find more advanced problems.' + ELSE '' + END +WHERE Warnings = '' OR Warnings IS NULL AND SPID = @@SPID -AND query_plan.exist('/p:QueryPlan/p:Warnings') = 1 OPTION (RECOMPILE); -RAISERROR(N'Checking for implicit conversion', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE ##BlitzCacheProcs -SET implicit_conversions = 1 -FROM #query_plan qp -WHERE qp.SqlHandle = ##BlitzCacheProcs.SqlHandle -AND SPID = @@SPID -AND query_plan.exist('/p:QueryPlan/p:Warnings/p:PlanAffectingConvert/@Expression[contains(., "CONVERT_IMPLICIT")]') = 1 -OPTION (RECOMPILE); --- operator level checks -IF @ExpertMode > 0 +Results: +IF @ExportToExcel = 1 BEGIN -RAISERROR(N'Performing busy loops checks', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE p -SET busy_loops = CASE WHEN (x.estimated_executions / 100.0) > x.estimated_rows THEN 1 END -FROM ##BlitzCacheProcs p - JOIN ( - SELECT qs.SqlHandle, - relop.value('sum(/p:RelOp/@EstimateRows)', 'float') AS estimated_rows , - relop.value('sum(/p:RelOp/@EstimateRewinds)', 'float') + relop.value('sum(/p:RelOp/@EstimateRebinds)', 'float') + 1.0 AS estimated_executions - FROM #relop qs - ) AS x ON p.SqlHandle = x.SqlHandle -WHERE SPID = @@SPID -OPTION (RECOMPILE); -END; + RAISERROR('Displaying results with Excel formatting (no plans).', 0, 1) WITH NOWAIT; + /* excel output */ + UPDATE ##BlitzCacheProcs + SET QueryText = SUBSTRING(REPLACE(REPLACE(REPLACE(LTRIM(RTRIM(QueryText)),' ','<>'),'><',''),'<>',' '), 1, 32000) + OPTION(RECOMPILE); -RAISERROR(N'Performing TVF join check', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE p -SET p.tvf_join = CASE WHEN x.tvf_join = 1 THEN 1 END -FROM ##BlitzCacheProcs p - JOIN ( - SELECT r.SqlHandle, - 1 AS tvf_join - FROM #relop AS r - WHERE r.relop.exist('//p:RelOp[(@LogicalOp[.="Table-valued function"])]') = 1 - AND r.relop.exist('//p:RelOp[contains(@LogicalOp, "Join")]') = 1 - ) AS x ON p.SqlHandle = x.SqlHandle -WHERE SPID = @@SPID -OPTION (RECOMPILE); + SET @sql = N' + SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + SELECT TOP (@Top) + DatabaseName AS [Database Name], + QueryPlanCost AS [Cost], + QueryText, + QueryType AS [Query Type], + Warnings, + ExecutionCount, + ExecutionsPerMinute AS [Executions / Minute], + PercentExecutions AS [Execution Weight], + PercentExecutionsByType AS [% Executions (Type)], + SerialDesiredMemory AS [Serial Desired Memory], + SerialRequiredMemory AS [Serial Required Memory], + TotalCPU AS [Total CPU (ms)], + AverageCPU AS [Avg CPU (ms)], + PercentCPU AS [CPU Weight], + PercentCPUByType AS [% CPU (Type)], + TotalDuration AS [Total Duration (ms)], + AverageDuration AS [Avg Duration (ms)], + PercentDuration AS [Duration Weight], + PercentDurationByType AS [% Duration (Type)], + TotalReads AS [Total Reads], + AverageReads AS [Average Reads], + PercentReads AS [Read Weight], + PercentReadsByType AS [% Reads (Type)], + TotalWrites AS [Total Writes], + AverageWrites AS [Average Writes], + PercentWrites AS [Write Weight], + PercentWritesByType AS [% Writes (Type)], + TotalReturnedRows, + AverageReturnedRows, + MinReturnedRows, + MaxReturnedRows, + MinGrantKB, + MaxGrantKB, + MinUsedGrantKB, + MaxUsedGrantKB, + PercentMemoryGrantUsed, + AvgMaxMemoryGrant, + MinSpills, + MaxSpills, + TotalSpills, + AvgSpills, + NumberOfPlans, + NumberOfDistinctPlans, + PlanCreationTime AS [Created At], + LastExecutionTime AS [Last Execution], + StatementStartOffset, + StatementEndOffset, + PlanGenerationNum, + PlanHandle AS [Plan Handle], + SqlHandle AS [SQL Handle], + QueryHash, + QueryPlanHash, + COALESCE(SetOptions, '''') AS [SET Options] + FROM ##BlitzCacheProcs + WHERE 1 = 1 + AND SPID = @@SPID ' + @nl; -IF @ExpertMode > 0 -BEGIN -RAISERROR(N'Checking for operator warnings', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -, x AS ( -SELECT r.SqlHandle, - c.n.exist('//p:Warnings[(@NoJoinPredicate[.="1"])]') AS warning_no_join_predicate, - c.n.exist('//p:ColumnsWithNoStatistics') AS no_stats_warning , - c.n.exist('//p:Warnings') AS relop_warnings -FROM #relop AS r -CROSS APPLY r.relop.nodes('/p:RelOp/p:Warnings') AS c(n) -) -UPDATE p -SET p.warning_no_join_predicate = x.warning_no_join_predicate, - p.no_stats_warning = x.no_stats_warning, - p.relop_warnings = x.relop_warnings -FROM ##BlitzCacheProcs AS p -JOIN x ON x.SqlHandle = p.SqlHandle -AND SPID = @@SPID -OPTION (RECOMPILE); -END; + IF @MinimumExecutionCount IS NOT NULL + BEGIN + SET @sql += N' AND ExecutionCount >= @MinimumExecutionCount '; + END; + + IF @MinutesBack IS NOT NULL + BEGIN + SET @sql += N' AND LastCompletionTime >= DATEADD(MINUTE, @min_back, GETDATE() ) '; + END; + SELECT @sql += N' ORDER BY ' + CASE @SortOrder WHEN N'cpu' THEN N' TotalCPU ' + WHEN N'reads' THEN N' TotalReads ' + WHEN N'writes' THEN N' TotalWrites ' + WHEN N'duration' THEN N' TotalDuration ' + WHEN N'executions' THEN N' ExecutionCount ' + WHEN N'compiles' THEN N' PlanCreationTime ' + WHEN N'memory grant' THEN N' MaxGrantKB' + WHEN N'unused grant' THEN N' MaxGrantKB - MaxUsedGrantKB' + WHEN N'spills' THEN N' MaxSpills' + WHEN N'duplicate' THEN N' plan_multiple_plans ' /* Issue #3345 */ + WHEN N'avg cpu' THEN N' AverageCPU' + WHEN N'avg reads' THEN N' AverageReads' + WHEN N'avg writes' THEN N' AverageWrites' + WHEN N'avg duration' THEN N' AverageDuration' + WHEN N'avg executions' THEN N' ExecutionsPerMinute' + WHEN N'avg memory grant' THEN N' AvgMaxMemoryGrant' + WHEN N'avg spills' THEN N' AvgSpills' + END + N' DESC '; -RAISERROR(N'Checking for table variables', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -, x AS ( -SELECT r.SqlHandle, - c.n.value('substring(@Table, 2, 1)','VARCHAR(100)') AS first_char -FROM #relop r -CROSS APPLY r.relop.nodes('//p:Object') AS c(n) -) -UPDATE p -SET is_table_variable = 1 -FROM ##BlitzCacheProcs AS p -JOIN x ON x.SqlHandle = p.SqlHandle -AND SPID = @@SPID -WHERE x.first_char = '@' -OPTION (RECOMPILE); + SET @sql += N' OPTION (RECOMPILE) ; '; -IF @ExpertMode > 0 -BEGIN -RAISERROR(N'Checking for functions', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -, x AS ( -SELECT qs.SqlHandle, - n.fn.value('count(distinct-values(//p:UserDefinedFunction[not(@IsClrFunction)]))', 'INT') AS function_count, - n.fn.value('count(distinct-values(//p:UserDefinedFunction[@IsClrFunction = "1"]))', 'INT') AS clr_function_count -FROM #relop qs -CROSS APPLY relop.nodes('/p:RelOp/p:ComputeScalar/p:DefinedValues/p:DefinedValue/p:ScalarOperator') n(fn) -) -UPDATE p -SET p.function_count = x.function_count, - p.clr_function_count = x.clr_function_count -FROM ##BlitzCacheProcs AS p -JOIN x ON x.SqlHandle = p.SqlHandle -AND SPID = @@SPID -OPTION (RECOMPILE); -END; + IF @sql IS NULL + BEGIN + RAISERROR('@sql is null, which means dynamic SQL generation went terribly wrong', 0, 1) WITH NOWAIT; + END + IF @Debug = 1 + BEGIN + RAISERROR('Printing @sql, the dynamic SQL we generated:', 0, 1) WITH NOWAIT; + PRINT SUBSTRING(@sql, 0, 4000); + PRINT SUBSTRING(@sql, 4000, 8000); + PRINT SUBSTRING(@sql, 8000, 12000); + PRINT SUBSTRING(@sql, 12000, 16000); + PRINT SUBSTRING(@sql, 16000, 20000); + PRINT SUBSTRING(@sql, 20000, 24000); + PRINT SUBSTRING(@sql, 24000, 28000); + PRINT SUBSTRING(@sql, 28000, 32000); + PRINT SUBSTRING(@sql, 32000, 36000); + PRINT SUBSTRING(@sql, 36000, 40000); + END; -RAISERROR(N'Checking for expensive key lookups', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE ##BlitzCacheProcs -SET key_lookup_cost = x.key_lookup_cost -FROM ( -SELECT - qs.SqlHandle, - MAX(relop.value('sum(/p:RelOp/@EstimatedTotalSubtreeCost)', 'float')) AS key_lookup_cost -FROM #relop qs -WHERE [relop].exist('/p:RelOp/p:IndexScan[(@Lookup[.="1"])]') = 1 -GROUP BY qs.SqlHandle -) AS x -WHERE ##BlitzCacheProcs.SqlHandle = x.SqlHandle -AND SPID = @@SPID -OPTION (RECOMPILE); + EXEC sp_executesql @sql, N'@Top INT, @min_duration INT, @min_back INT, @MinimumExecutionCount INT', @Top, @DurationFilter_i, @MinutesBack, @MinimumExecutionCount; +END; -RAISERROR(N'Checking for expensive remote queries', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE ##BlitzCacheProcs -SET remote_query_cost = x.remote_query_cost -FROM ( -SELECT - qs.SqlHandle, - MAX(relop.value('sum(/p:RelOp/@EstimatedTotalSubtreeCost)', 'float')) AS remote_query_cost -FROM #relop qs -WHERE [relop].exist('/p:RelOp[(@PhysicalOp[contains(., "Remote")])]') = 1 -GROUP BY qs.SqlHandle -) AS x -WHERE ##BlitzCacheProcs.SqlHandle = x.SqlHandle -AND SPID = @@SPID -OPTION (RECOMPILE); +RAISERROR('Displaying analysis of plan cache.', 0, 1) WITH NOWAIT; -RAISERROR(N'Checking for expensive sorts', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE ##BlitzCacheProcs -SET sort_cost = y.max_sort_cost -FROM ( - SELECT x.SqlHandle, MAX((x.sort_io + x.sort_cpu)) AS max_sort_cost - FROM ( - SELECT - qs.SqlHandle, - relop.value('sum(/p:RelOp/@EstimateIO)', 'float') AS sort_io, - relop.value('sum(/p:RelOp/@EstimateCPU)', 'float') AS sort_cpu - FROM #relop qs - WHERE [relop].exist('/p:RelOp[(@PhysicalOp[.="Sort"])]') = 1 - ) AS x - GROUP BY x.SqlHandle - ) AS y -WHERE ##BlitzCacheProcs.SqlHandle = y.SqlHandle -AND SPID = @@SPID -OPTION (RECOMPILE); +DECLARE @columns NVARCHAR(MAX) = N'' ; -IF NOT EXISTS(SELECT 1/0 FROM #statements AS s WHERE s.is_cursor = 1) +IF @ExpertMode = 0 +BEGIN + RAISERROR(N'Returning ExpertMode = 0', 0, 1) WITH NOWAIT; + SET @columns = N' DatabaseName AS [Database], + QueryPlanCost AS [Cost], + QueryText AS [Query Text], + QueryType AS [Query Type], + Warnings AS [Warnings], + QueryPlan AS [Query Plan], + missing_indexes AS [Missing Indexes], + implicit_conversion_info AS [Implicit Conversion Info], + cached_execution_parameters AS [Cached Execution Parameters], + CONVERT(NVARCHAR(30), CAST((ExecutionCount) AS BIGINT), 1) AS [# Executions], + CONVERT(NVARCHAR(30), CAST((ExecutionsPerMinute) AS BIGINT), 1) AS [Executions / Minute], + CONVERT(NVARCHAR(30), CAST((PercentExecutions) AS BIGINT), 1) AS [Execution Weight], + CONVERT(NVARCHAR(30), CAST((TotalCPU) AS BIGINT), 1) AS [Total CPU (ms)], + CONVERT(NVARCHAR(30), CAST((AverageCPU) AS BIGINT), 1) AS [Avg CPU (ms)], + CONVERT(NVARCHAR(30), CAST((PercentCPU) AS BIGINT), 1) AS [CPU Weight], + CONVERT(NVARCHAR(30), CAST((TotalDuration) AS BIGINT), 1) AS [Total Duration (ms)], + CONVERT(NVARCHAR(30), CAST((AverageDuration) AS BIGINT), 1) AS [Avg Duration (ms)], + CONVERT(NVARCHAR(30), CAST((PercentDuration) AS BIGINT), 1) AS [Duration Weight], + CONVERT(NVARCHAR(30), CAST((TotalReads) AS BIGINT), 1) AS [Total Reads], + CONVERT(NVARCHAR(30), CAST((AverageReads) AS BIGINT), 1) AS [Avg Reads], + CONVERT(NVARCHAR(30), CAST((PercentReads) AS BIGINT), 1) AS [Read Weight], + CONVERT(NVARCHAR(30), CAST((TotalWrites) AS BIGINT), 1) AS [Total Writes], + CONVERT(NVARCHAR(30), CAST((AverageWrites) AS BIGINT), 1) AS [Avg Writes], + CONVERT(NVARCHAR(30), CAST((PercentWrites) AS BIGINT), 1) AS [Write Weight], + CONVERT(NVARCHAR(30), CAST((AverageReturnedRows) AS BIGINT), 1) AS [Average Rows], + CONVERT(NVARCHAR(30), CAST((MinGrantKB) AS BIGINT), 1) AS [Minimum Memory Grant KB], + CONVERT(NVARCHAR(30), CAST((MaxGrantKB) AS BIGINT), 1) AS [Maximum Memory Grant KB], + CONVERT(NVARCHAR(30), CAST((MinUsedGrantKB) AS BIGINT), 1) AS [Minimum Used Grant KB], + CONVERT(NVARCHAR(30), CAST((MaxUsedGrantKB) AS BIGINT), 1) AS [Maximum Used Grant KB], + CONVERT(NVARCHAR(30), CAST((AvgMaxMemoryGrant) AS BIGINT), 1) AS [Average Max Memory Grant], + CONVERT(NVARCHAR(30), CAST((MinSpills) AS BIGINT), 1) AS [Min Spills], + CONVERT(NVARCHAR(30), CAST((MaxSpills) AS BIGINT), 1) AS [Max Spills], + CONVERT(NVARCHAR(30), CAST((TotalSpills) AS BIGINT), 1) AS [Total Spills], + CONVERT(NVARCHAR(30), CAST((AvgSpills) AS MONEY), 1) AS [Avg Spills], + PlanCreationTime AS [Created At], + LastExecutionTime AS [Last Execution], + LastCompletionTime AS [Last Completion], + PlanHandle AS [Plan Handle], + SqlHandle AS [SQL Handle], + COALESCE(SetOptions, '''') AS [SET Options], + QueryHash AS [Query Hash], + PlanGenerationNum, + [Remove Plan Handle From Cache]'; +END; +ELSE BEGIN + SET @columns = N' DatabaseName AS [Database], + QueryPlanCost AS [Cost], + QueryText AS [Query Text], + QueryType AS [Query Type], + Warnings AS [Warnings], + QueryPlan AS [Query Plan], + missing_indexes AS [Missing Indexes], + implicit_conversion_info AS [Implicit Conversion Info], + cached_execution_parameters AS [Cached Execution Parameters], ' + @nl; -RAISERROR(N'No cursor plans found, skipping', 0, 1) WITH NOWAIT; + IF @ExpertMode = 2 /* Opserver */ + BEGIN + RAISERROR(N'Returning Expert Mode = 2', 0, 1) WITH NOWAIT; + SET @columns += N' + SUBSTRING( + CASE WHEN warning_no_join_predicate = 1 THEN '', 20'' ELSE '''' END + + CASE WHEN compile_timeout = 1 THEN '', 18'' ELSE '''' END + + CASE WHEN compile_memory_limit_exceeded = 1 THEN '', 19'' ELSE '''' END + + CASE WHEN busy_loops = 1 THEN '', 16'' ELSE '''' END + + CASE WHEN is_forced_plan = 1 THEN '', 3'' ELSE '''' END + + CASE WHEN is_forced_parameterized > 0 THEN '', 5'' ELSE '''' END + + CASE WHEN unparameterized_query = 1 THEN '', 23'' ELSE '''' END + + CASE WHEN missing_index_count > 0 THEN '', 10'' ELSE '''' END + + CASE WHEN unmatched_index_count > 0 THEN '', 22'' ELSE '''' END + + CASE WHEN is_cursor = 1 THEN '', 4'' ELSE '''' END + + CASE WHEN is_parallel = 1 THEN '', 6'' ELSE '''' END + + CASE WHEN near_parallel = 1 THEN '', 7'' ELSE '''' END + + CASE WHEN frequent_execution = 1 THEN '', 1'' ELSE '''' END + + CASE WHEN plan_warnings = 1 THEN '', 8'' ELSE '''' END + + CASE WHEN parameter_sniffing = 1 THEN '', 2'' ELSE '''' END + + CASE WHEN long_running = 1 THEN '', 9'' ELSE '''' END + + CASE WHEN downlevel_estimator = 1 THEN '', 13'' ELSE '''' END + + CASE WHEN implicit_conversions = 1 THEN '', 14'' ELSE '''' END + + CASE WHEN tvf_join = 1 THEN '', 17'' ELSE '''' END + + CASE WHEN plan_multiple_plans > 0 THEN '', 21'' ELSE '''' END + + CASE WHEN unmatched_index_count > 0 THEN '', 22'' ELSE '''' END + + CASE WHEN is_trivial = 1 THEN '', 24'' ELSE '''' END + + CASE WHEN is_forced_serial = 1 THEN '', 25'' ELSE '''' END + + CASE WHEN is_key_lookup_expensive = 1 THEN '', 26'' ELSE '''' END + + CASE WHEN is_remote_query_expensive = 1 THEN '', 28'' ELSE '''' END + + CASE WHEN trace_flags_session IS NOT NULL THEN '', 29'' ELSE '''' END + + CASE WHEN is_unused_grant = 1 THEN '', 30'' ELSE '''' END + + CASE WHEN function_count > 0 THEN '', 31'' ELSE '''' END + + CASE WHEN clr_function_count > 0 THEN '', 32'' ELSE '''' END + + CASE WHEN PlanCreationTimeHours <= 4 THEN '', 33'' ELSE '''' END + + CASE WHEN is_table_variable = 1 THEN '', 34'' ELSE '''' END + + CASE WHEN no_stats_warning = 1 THEN '', 35'' ELSE '''' END + + CASE WHEN relop_warnings = 1 THEN '', 36'' ELSE '''' END + + CASE WHEN is_table_scan = 1 THEN '', 37'' ELSE '''' END + + CASE WHEN backwards_scan = 1 THEN '', 38'' ELSE '''' END + + CASE WHEN forced_index = 1 THEN '', 39'' ELSE '''' END + + CASE WHEN forced_seek = 1 OR forced_scan = 1 THEN '', 40'' ELSE '''' END + + CASE WHEN columnstore_row_mode = 1 THEN '', 41'' ELSE '''' END + + CASE WHEN is_computed_scalar = 1 THEN '', 42'' ELSE '''' END + + CASE WHEN is_sort_expensive = 1 THEN '', 43'' ELSE '''' END + + CASE WHEN is_computed_filter = 1 THEN '', 44'' ELSE '''' END + + CASE WHEN index_ops >= 5 THEN '', 45'' ELSE '''' END + + CASE WHEN is_row_level = 1 THEN '', 46'' ELSE '''' END + + CASE WHEN is_spatial = 1 THEN '', 47'' ELSE '''' END + + CASE WHEN index_dml = 1 THEN '', 48'' ELSE '''' END + + CASE WHEN table_dml = 1 THEN '', 49'' ELSE '''' END + + CASE WHEN long_running_low_cpu = 1 THEN '', 50'' ELSE '''' END + + CASE WHEN low_cost_high_cpu = 1 THEN '', 51'' ELSE '''' END + + CASE WHEN stale_stats = 1 THEN '', 52'' ELSE '''' END + + CASE WHEN is_adaptive = 1 THEN '', 53'' ELSE '''' END + + CASE WHEN is_spool_expensive = 1 THEN + '', 54'' ELSE '''' END + + CASE WHEN is_spool_more_rows = 1 THEN + '', 55'' ELSE '''' END + + CASE WHEN is_table_spool_expensive = 1 THEN + '', 67'' ELSE '''' END + + CASE WHEN is_table_spool_more_rows = 1 THEN + '', 68'' ELSE '''' END + + CASE WHEN is_bad_estimate = 1 THEN + '', 56'' ELSE '''' END + + CASE WHEN is_paul_white_electric = 1 THEN '', 57'' ELSE '''' END + + CASE WHEN is_row_goal = 1 THEN '', 58'' ELSE '''' END + + CASE WHEN is_big_spills = 1 THEN '', 59'' ELSE '''' END + + CASE WHEN is_mstvf = 1 THEN '', 60'' ELSE '''' END + + CASE WHEN is_mm_join = 1 THEN '', 61'' ELSE '''' END + + CASE WHEN is_nonsargable = 1 THEN '', 62'' ELSE '''' END + + CASE WHEN CompileTime > 5000 THEN '', 63 '' ELSE '''' END + + CASE WHEN CompileCPU > 5000 THEN '', 64 '' ELSE '''' END + + CASE WHEN CompileMemory > 1024 AND ((CompileMemory) / (1 * CASE WHEN MaxCompileMemory = 0 THEN 1 ELSE MaxCompileMemory END) * 100.) >= 10. THEN '', 65 '' ELSE '''' END + + CASE WHEN select_with_writes > 0 THEN '', 66'' ELSE '''' END + , 3, 200000) AS opserver_warning , ' + @nl ; + END; + + SET @columns += N' + CONVERT(NVARCHAR(30), CAST((ExecutionCount) AS BIGINT), 1) AS [# Executions], + CONVERT(NVARCHAR(30), CAST((ExecutionsPerMinute) AS BIGINT), 1) AS [Executions / Minute], + CONVERT(NVARCHAR(30), CAST((PercentExecutions) AS BIGINT), 1) AS [Execution Weight], + CONVERT(NVARCHAR(30), CAST((SerialDesiredMemory) AS BIGINT), 1) AS [Serial Desired Memory], + CONVERT(NVARCHAR(30), CAST((SerialRequiredMemory) AS BIGINT), 1) AS [Serial Required Memory], + CONVERT(NVARCHAR(30), CAST((TotalCPU) AS BIGINT), 1) AS [Total CPU (ms)], + CONVERT(NVARCHAR(30), CAST((AverageCPU) AS BIGINT), 1) AS [Avg CPU (ms)], + CONVERT(NVARCHAR(30), CAST((PercentCPU) AS BIGINT), 1) AS [CPU Weight], + CONVERT(NVARCHAR(30), CAST((TotalDuration) AS BIGINT), 1) AS [Total Duration (ms)], + CONVERT(NVARCHAR(30), CAST((AverageDuration) AS BIGINT), 1) AS [Avg Duration (ms)], + CONVERT(NVARCHAR(30), CAST((PercentDuration) AS BIGINT), 1) AS [Duration Weight], + CONVERT(NVARCHAR(30), CAST((TotalReads) AS BIGINT), 1) AS [Total Reads], + CONVERT(NVARCHAR(30), CAST((AverageReads) AS BIGINT), 1) AS [Average Reads], + CONVERT(NVARCHAR(30), CAST((PercentReads) AS BIGINT), 1) AS [Read Weight], + CONVERT(NVARCHAR(30), CAST((TotalWrites) AS BIGINT), 1) AS [Total Writes], + CONVERT(NVARCHAR(30), CAST((AverageWrites) AS BIGINT), 1) AS [Average Writes], + CONVERT(NVARCHAR(30), CAST((PercentWrites) AS BIGINT), 1) AS [Write Weight], + CONVERT(NVARCHAR(30), CAST((PercentExecutionsByType) AS BIGINT), 1) AS [% Executions (Type)], + CONVERT(NVARCHAR(30), CAST((PercentCPUByType) AS BIGINT), 1) AS [% CPU (Type)], + CONVERT(NVARCHAR(30), CAST((PercentDurationByType) AS BIGINT), 1) AS [% Duration (Type)], + CONVERT(NVARCHAR(30), CAST((PercentReadsByType) AS BIGINT), 1) AS [% Reads (Type)], + CONVERT(NVARCHAR(30), CAST((PercentWritesByType) AS BIGINT), 1) AS [% Writes (Type)], + CONVERT(NVARCHAR(30), CAST((TotalReturnedRows) AS BIGINT), 1) AS [Total Rows], + CONVERT(NVARCHAR(30), CAST((AverageReturnedRows) AS BIGINT), 1) AS [Avg Rows], + CONVERT(NVARCHAR(30), CAST((MinReturnedRows) AS BIGINT), 1) AS [Min Rows], + CONVERT(NVARCHAR(30), CAST((MaxReturnedRows) AS BIGINT), 1) AS [Max Rows], + CONVERT(NVARCHAR(30), CAST((MinGrantKB) AS BIGINT), 1) AS [Minimum Memory Grant KB], + CONVERT(NVARCHAR(30), CAST((MaxGrantKB) AS BIGINT), 1) AS [Maximum Memory Grant KB], + CONVERT(NVARCHAR(30), CAST((MinUsedGrantKB) AS BIGINT), 1) AS [Minimum Used Grant KB], + CONVERT(NVARCHAR(30), CAST((MaxUsedGrantKB) AS BIGINT), 1) AS [Maximum Used Grant KB], + CONVERT(NVARCHAR(30), CAST((AvgMaxMemoryGrant) AS BIGINT), 1) AS [Average Max Memory Grant], + CONVERT(NVARCHAR(30), CAST((MinSpills) AS BIGINT), 1) AS [Min Spills], + CONVERT(NVARCHAR(30), CAST((MaxSpills) AS BIGINT), 1) AS [Max Spills], + CONVERT(NVARCHAR(30), CAST((TotalSpills) AS BIGINT), 1) AS [Total Spills], + CONVERT(NVARCHAR(30), CAST((AvgSpills) AS MONEY), 1) AS [Avg Spills], + CONVERT(NVARCHAR(30), CAST((NumberOfPlans) AS BIGINT), 1) AS [# Plans], + CONVERT(NVARCHAR(30), CAST((NumberOfDistinctPlans) AS BIGINT), 1) AS [# Distinct Plans], + PlanCreationTime AS [Created At], + LastExecutionTime AS [Last Execution], + LastCompletionTime AS [Last Completion], + CONVERT(NVARCHAR(30), CAST((CachedPlanSize) AS BIGINT), 1) AS [Cached Plan Size (KB)], + CONVERT(NVARCHAR(30), CAST((CompileTime) AS BIGINT), 1) AS [Compile Time (ms)], + CONVERT(NVARCHAR(30), CAST((CompileCPU) AS BIGINT), 1) AS [Compile CPU (ms)], + CONVERT(NVARCHAR(30), CAST((CompileMemory) AS BIGINT), 1) AS [Compile memory (KB)], + COALESCE(SetOptions, '''') AS [SET Options], + PlanHandle AS [Plan Handle], + SqlHandle AS [SQL Handle], + [SQL Handle More Info], + QueryHash AS [Query Hash], + [Query Hash More Info], + QueryPlanHash AS [Query Plan Hash], + StatementStartOffset, + StatementEndOffset, + PlanGenerationNum, + [Remove Plan Handle From Cache], + [Remove SQL Handle From Cache]'; +END; -END +SET @sql = N' +SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; +SELECT TOP (@Top) ' + @columns + @nl + N' +FROM ##BlitzCacheProcs +WHERE SPID = @spid ' + @nl; -IF EXISTS(SELECT 1/0 FROM #statements AS s WHERE s.is_cursor = 1) -BEGIN +IF @MinimumExecutionCount IS NOT NULL + BEGIN + SET @sql += N' AND ExecutionCount >= @MinimumExecutionCount ' + @nl; + END; -RAISERROR(N'Cursor plans found, investigating', 0, 1) WITH NOWAIT; +IF @MinutesBack IS NOT NULL + BEGIN + SET @sql += N' AND LastCompletionTime >= DATEADD(MINUTE, @min_back, GETDATE() ) ' + @nl; + END; -RAISERROR(N'Checking for Optimistic cursors', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET b.is_optimistic_cursor = 1 -FROM ##BlitzCacheProcs b -JOIN #statements AS qs -ON b.SqlHandle = qs.SqlHandle -CROSS APPLY qs.statement.nodes('/p:StmtCursor') AS n1(fn) -WHERE SPID = @@SPID -AND n1.fn.exist('//p:CursorPlan/@CursorConcurrency[.="Optimistic"]') = 1 -AND qs.is_cursor = 1 -OPTION (RECOMPILE); +SELECT @sql += N' ORDER BY ' + CASE @SortOrder WHEN N'cpu' THEN N' TotalCPU ' + WHEN N'reads' THEN N' TotalReads ' + WHEN N'writes' THEN N' TotalWrites ' + WHEN N'duration' THEN N' TotalDuration ' + WHEN N'executions' THEN N' ExecutionCount ' + WHEN N'compiles' THEN N' PlanCreationTime ' + WHEN N'memory grant' THEN N' MaxGrantKB' + WHEN N'unused grant' THEN N' MaxGrantKB - MaxUsedGrantKB ' + WHEN N'duplicate' THEN N' plan_multiple_plans ' + WHEN N'spills' THEN N' MaxSpills ' + WHEN N'avg cpu' THEN N' AverageCPU' + WHEN N'avg reads' THEN N' AverageReads' + WHEN N'avg writes' THEN N' AverageWrites' + WHEN N'avg duration' THEN N' AverageDuration' + WHEN N'avg executions' THEN N' ExecutionsPerMinute' + WHEN N'avg memory grant' THEN N' AvgMaxMemoryGrant' + WHEN N'avg spills' THEN N' AvgSpills' + END + N' DESC '; +SET @sql += N' OPTION (RECOMPILE) ; '; +IF @Debug = 1 + BEGIN + PRINT SUBSTRING(@sql, 0, 4000); + PRINT SUBSTRING(@sql, 4000, 8000); + PRINT SUBSTRING(@sql, 8000, 12000); + PRINT SUBSTRING(@sql, 12000, 16000); + PRINT SUBSTRING(@sql, 16000, 20000); + PRINT SUBSTRING(@sql, 20000, 24000); + PRINT SUBSTRING(@sql, 24000, 28000); + PRINT SUBSTRING(@sql, 28000, 32000); + PRINT SUBSTRING(@sql, 32000, 36000); + PRINT SUBSTRING(@sql, 36000, 40000); + END; +IF(@OutputType <> 'NONE') +BEGIN + EXEC sp_executesql @sql, N'@Top INT, @spid INT, @MinimumExecutionCount INT, @min_back INT', @Top, @@SPID, @MinimumExecutionCount, @MinutesBack; +END; -RAISERROR(N'Checking if cursor is Forward Only', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET b.is_forward_only_cursor = 1 -FROM ##BlitzCacheProcs b -JOIN #statements AS qs -ON b.SqlHandle = qs.SqlHandle -CROSS APPLY qs.statement.nodes('/p:StmtCursor') AS n1(fn) -WHERE SPID = @@SPID -AND n1.fn.exist('//p:CursorPlan/@ForwardOnly[.="true"]') = 1 -AND qs.is_cursor = 1 -OPTION (RECOMPILE); +/* -RAISERROR(N'Checking if cursor is Fast Forward', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET b.is_fast_forward_cursor = 1 -FROM ##BlitzCacheProcs b -JOIN #statements AS qs -ON b.SqlHandle = qs.SqlHandle -CROSS APPLY qs.statement.nodes('/p:StmtCursor') AS n1(fn) -WHERE SPID = @@SPID -AND n1.fn.exist('//p:CursorPlan/@CursorActualType[.="FastForward"]') = 1 -AND qs.is_cursor = 1 -OPTION (RECOMPILE); +This section will check if: + * >= 30% of plans were created in the last hour + * Check on the memory_clerks DMV for space used by TokenAndPermUserStore + * Compare that to the size of the buffer pool + * If it's >10%, +*/ +IF EXISTS +( + SELECT 1/0 + FROM #plan_creation AS pc + WHERE pc.percent_1 >= 30 +) +BEGIN +SELECT @common_version = + CONVERT(DECIMAL(10,2), c.common_version) +FROM #checkversion AS c; -RAISERROR(N'Checking for Dynamic cursors', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET b.is_cursor_dynamic = 1 -FROM ##BlitzCacheProcs b -JOIN #statements AS qs -ON b.SqlHandle = qs.SqlHandle -CROSS APPLY qs.statement.nodes('/p:StmtCursor') AS n1(fn) -WHERE SPID = @@SPID -AND n1.fn.exist('//p:CursorPlan/@CursorActualType[.="Dynamic"]') = 1 -AND qs.is_cursor = 1 -OPTION (RECOMPILE); +IF @common_version >= 11 + SET @user_perm_sql = N' + SET @buffer_pool_memory_gb = 0; + SELECT @buffer_pool_memory_gb = SUM(pages_kb)/ 1024. / 1024. + FROM sys.dm_os_memory_clerks + WHERE type = ''MEMORYCLERK_SQLBUFFERPOOL'';' +ELSE + SET @user_perm_sql = N' + SET @buffer_pool_memory_gb = 0; + SELECT @buffer_pool_memory_gb = SUM(single_pages_kb + multi_pages_kb)/ 1024. / 1024. + FROM sys.dm_os_memory_clerks + WHERE type = ''MEMORYCLERK_SQLBUFFERPOOL'';' -END +EXEC sys.sp_executesql @user_perm_sql, + N'@buffer_pool_memory_gb DECIMAL(10,2) OUTPUT', + @buffer_pool_memory_gb = @buffer_pool_memory_gb OUTPUT; -IF @ExpertMode > 0 +IF @common_version >= 11 BEGIN -RAISERROR(N'Checking for bad scans and plan forcing', 0, 1) WITH NOWAIT; -;WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET -b.is_table_scan = x.is_table_scan, -b.backwards_scan = x.backwards_scan, -b.forced_index = x.forced_index, -b.forced_seek = x.forced_seek, -b.forced_scan = x.forced_scan -FROM ##BlitzCacheProcs b -JOIN ( -SELECT - qs.SqlHandle, - 0 AS is_table_scan, - q.n.exist('@ScanDirection[.="BACKWARD"]') AS backwards_scan, - q.n.value('@ForcedIndex', 'bit') AS forced_index, - q.n.value('@ForceSeek', 'bit') AS forced_seek, - q.n.value('@ForceScan', 'bit') AS forced_scan -FROM #relop qs -CROSS APPLY qs.relop.nodes('//p:IndexScan') AS q(n) -UNION ALL -SELECT - qs.SqlHandle, - 1 AS is_table_scan, - q.n.exist('@ScanDirection[.="BACKWARD"]') AS backwards_scan, - q.n.value('@ForcedIndex', 'bit') AS forced_index, - q.n.value('@ForceSeek', 'bit') AS forced_seek, - q.n.value('@ForceScan', 'bit') AS forced_scan -FROM #relop qs -CROSS APPLY qs.relop.nodes('//p:TableScan') AS q(n) -) AS x ON b.SqlHandle = x.SqlHandle -WHERE SPID = @@SPID -OPTION (RECOMPILE); -END; - + SET @user_perm_sql = N' + SELECT @user_perm_gb = CASE WHEN (pages_kb / 1024.0 / 1024.) >= 2. + THEN CONVERT(DECIMAL(38, 2), (pages_kb / 1024.0 / 1024.)) + ELSE 0 + END + FROM sys.dm_os_memory_clerks + WHERE type = ''USERSTORE_TOKENPERM'' + AND name = ''TokenAndPermUserStore'';'; +END; -IF @ExpertMode > 0 +IF @common_version < 11 BEGIN -RAISERROR(N'Checking for computed columns that reference scalar UDFs', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE ##BlitzCacheProcs -SET is_computed_scalar = x.computed_column_function -FROM ( -SELECT qs.SqlHandle, - n.fn.value('count(distinct-values(//p:UserDefinedFunction[not(@IsClrFunction)]))', 'INT') AS computed_column_function -FROM #relop qs -CROSS APPLY relop.nodes('/p:RelOp/p:ComputeScalar/p:DefinedValues/p:DefinedValue/p:ScalarOperator') n(fn) -WHERE n.fn.exist('/p:RelOp/p:ComputeScalar/p:DefinedValues/p:DefinedValue/p:ColumnReference[(@ComputedColumn[.="1"])]') = 1 -) AS x -WHERE ##BlitzCacheProcs.SqlHandle = x.SqlHandle -AND SPID = @@SPID -OPTION (RECOMPILE); -END; + SET @user_perm_sql = N' + SELECT @user_perm_gb = CASE WHEN ((single_pages_kb + multi_pages_kb) / 1024.0 / 1024.) >= 2. + THEN CONVERT(DECIMAL(38, 2), ((single_pages_kb + multi_pages_kb) / 1024.0 / 1024.)) + ELSE 0 + END + FROM sys.dm_os_memory_clerks + WHERE type = ''USERSTORE_TOKENPERM'' + AND name = ''TokenAndPermUserStore'';'; +END; +EXEC sys.sp_executesql @user_perm_sql, + N'@user_perm_gb DECIMAL(10,2) OUTPUT', + @user_perm_gb = @user_perm_gb_out OUTPUT; -RAISERROR(N'Checking for filters that reference scalar UDFs', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE ##BlitzCacheProcs -SET is_computed_filter = x.filter_function -FROM ( -SELECT -r.SqlHandle, -c.n.value('count(distinct-values(//p:UserDefinedFunction[not(@IsClrFunction)]))', 'INT') AS filter_function -FROM #relop AS r -CROSS APPLY r.relop.nodes('/p:RelOp/p:Filter/p:Predicate/p:ScalarOperator/p:Compare/p:ScalarOperator/p:UserDefinedFunction') c(n) -) x -WHERE ##BlitzCacheProcs.SqlHandle = x.SqlHandle -AND SPID = @@SPID -OPTION (RECOMPILE); +IF @buffer_pool_memory_gb > 0 + BEGIN + IF (@user_perm_gb_out / (1. * @buffer_pool_memory_gb)) * 100. >= 10 + BEGIN + SET @is_tokenstore_big = 1; + SET @user_perm_percent = (@user_perm_gb_out / (1. * @buffer_pool_memory_gb)) * 100.; + END + END -IF @ExpertMode > 0 -BEGIN -RAISERROR(N'Checking modification queries that hit lots of indexes', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p), -IndexOps AS -( - SELECT - r.QueryHash, - c.n.value('@PhysicalOp', 'VARCHAR(100)') AS op_name, - c.n.exist('@PhysicalOp[.="Index Insert"]') AS ii, - c.n.exist('@PhysicalOp[.="Index Update"]') AS iu, - c.n.exist('@PhysicalOp[.="Index Delete"]') AS id, - c.n.exist('@PhysicalOp[.="Clustered Index Insert"]') AS cii, - c.n.exist('@PhysicalOp[.="Clustered Index Update"]') AS ciu, - c.n.exist('@PhysicalOp[.="Clustered Index Delete"]') AS cid, - c.n.exist('@PhysicalOp[.="Table Insert"]') AS ti, - c.n.exist('@PhysicalOp[.="Table Update"]') AS tu, - c.n.exist('@PhysicalOp[.="Table Delete"]') AS td - FROM #relop AS r - CROSS APPLY r.relop.nodes('/p:RelOp') c(n) - OUTER APPLY r.relop.nodes('/p:RelOp/p:ScalarInsert/p:Object') q(n) - OUTER APPLY r.relop.nodes('/p:RelOp/p:Update/p:Object') o2(n) - OUTER APPLY r.relop.nodes('/p:RelOp/p:SimpleUpdate/p:Object') o3(n) -), iops AS -( - SELECT ios.QueryHash, - SUM(CONVERT(TINYINT, ios.ii)) AS index_insert_count, - SUM(CONVERT(TINYINT, ios.iu)) AS index_update_count, - SUM(CONVERT(TINYINT, ios.id)) AS index_delete_count, - SUM(CONVERT(TINYINT, ios.cii)) AS cx_insert_count, - SUM(CONVERT(TINYINT, ios.ciu)) AS cx_update_count, - SUM(CONVERT(TINYINT, ios.cid)) AS cx_delete_count, - SUM(CONVERT(TINYINT, ios.ti)) AS table_insert_count, - SUM(CONVERT(TINYINT, ios.tu)) AS table_update_count, - SUM(CONVERT(TINYINT, ios.td)) AS table_delete_count - FROM IndexOps AS ios - WHERE ios.op_name IN ('Index Insert', 'Index Delete', 'Index Update', - 'Clustered Index Insert', 'Clustered Index Delete', 'Clustered Index Update', - 'Table Insert', 'Table Delete', 'Table Update') - GROUP BY ios.QueryHash) -UPDATE b -SET b.index_insert_count = iops.index_insert_count, - b.index_update_count = iops.index_update_count, - b.index_delete_count = iops.index_delete_count, - b.cx_insert_count = iops.cx_insert_count, - b.cx_update_count = iops.cx_update_count, - b.cx_delete_count = iops.cx_delete_count, - b.table_insert_count = iops.table_insert_count, - b.table_update_count = iops.table_update_count, - b.table_delete_count = iops.table_delete_count -FROM ##BlitzCacheProcs AS b -JOIN iops ON iops.QueryHash = b.QueryHash -WHERE SPID = @@SPID -OPTION (RECOMPILE); -END; +END -IF @ExpertMode > 0 -BEGIN -RAISERROR(N'Checking for Spatial index use', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE ##BlitzCacheProcs -SET is_spatial = x.is_spatial -FROM ( -SELECT qs.SqlHandle, - 1 AS is_spatial -FROM #relop qs -CROSS APPLY relop.nodes('/p:RelOp//p:Object') n(fn) -WHERE n.fn.exist('(@IndexKind[.="Spatial"])') = 1 -) AS x -WHERE ##BlitzCacheProcs.SqlHandle = x.SqlHandle -AND SPID = @@SPID -OPTION (RECOMPILE); -END; +IF @HideSummary = 0 AND @ExportToExcel = 0 +BEGIN + IF @Reanalyze = 0 + BEGIN + RAISERROR('Building query plan summary data.', 0, 1) WITH NOWAIT; -RAISERROR('Checking for wonky Index Spools', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES ( - 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) -, selects -AS ( SELECT s.QueryHash - FROM #statements AS s - WHERE s.statement.exist('/p:StmtSimple/@StatementType[.="SELECT"]') = 1 ) -, spools -AS ( SELECT DISTINCT r.QueryHash, - c.n.value('@EstimateRows', 'FLOAT') AS estimated_rows, - c.n.value('@EstimateIO', 'FLOAT') AS estimated_io, - c.n.value('@EstimateCPU', 'FLOAT') AS estimated_cpu, - c.n.value('@EstimateRebinds', 'FLOAT') AS estimated_rebinds -FROM #relop AS r -JOIN selects AS s -ON s.QueryHash = r.QueryHash -CROSS APPLY r.relop.nodes('/p:RelOp') AS c(n) -WHERE r.relop.exist('/p:RelOp[@PhysicalOp="Index Spool" and @LogicalOp="Eager Spool"]') = 1 -) -UPDATE b - SET b.index_spool_rows = sp.estimated_rows, - b.index_spool_cost = ((sp.estimated_io * sp.estimated_cpu) * CASE WHEN sp.estimated_rebinds < 1 THEN 1 ELSE sp.estimated_rebinds END) -FROM ##BlitzCacheProcs b -JOIN spools sp -ON sp.QueryHash = b.QueryHash -OPTION (RECOMPILE); + /* Build summary data */ + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs + WHERE frequent_execution = 1 + AND SPID = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 1, + 100, + 'Execution Pattern', + 'Frequent Execution', + 'https://www.brentozar.com/blitzcache/frequently-executed-queries/', + 'Queries are being executed more than ' + + CAST (@execution_threshold AS VARCHAR(5)) + + ' times per minute. This can put additional load on the server, even when queries are lightweight.') ; -RAISERROR('Checking for wonky Table Spools', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES ( - 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) -, selects -AS ( SELECT s.QueryHash - FROM #statements AS s - WHERE s.statement.exist('/p:StmtSimple/@StatementType[.="SELECT"]') = 1 ) -, spools -AS ( SELECT DISTINCT r.QueryHash, - c.n.value('@EstimateRows', 'FLOAT') AS estimated_rows, - c.n.value('@EstimateIO', 'FLOAT') AS estimated_io, - c.n.value('@EstimateCPU', 'FLOAT') AS estimated_cpu, - c.n.value('@EstimateRebinds', 'FLOAT') AS estimated_rebinds -FROM #relop AS r -JOIN selects AS s -ON s.QueryHash = r.QueryHash -CROSS APPLY r.relop.nodes('/p:RelOp') AS c(n) -WHERE r.relop.exist('/p:RelOp[@PhysicalOp="Table Spool" and @LogicalOp="Lazy Spool"]') = 1 -) -UPDATE b - SET b.table_spool_rows = (sp.estimated_rows * sp.estimated_rebinds), - b.table_spool_cost = ((sp.estimated_io * sp.estimated_cpu * sp.estimated_rows) * CASE WHEN sp.estimated_rebinds < 1 THEN 1 ELSE sp.estimated_rebinds END) -FROM ##BlitzCacheProcs b -JOIN spools sp -ON sp.QueryHash = b.QueryHash -OPTION (RECOMPILE); + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs + WHERE parameter_sniffing = 1 + AND SPID = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 2, + 50, + 'Parameterization', + 'Parameter Sniffing', + 'https://www.brentozar.com/blitzcache/parameter-sniffing/', + 'There are signs of parameter sniffing (wide variance in rows return or time to execute). Investigate query patterns and tune code appropriately.') ; + /* Forced execution plans */ + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs + WHERE is_forced_plan = 1 + AND SPID = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 3, + 50, + 'Parameterization', + 'Forced Plan', + 'https://www.brentozar.com/blitzcache/forced-plans/', + 'Execution plans have been compiled with forced plans, either through FORCEPLAN, plan guides, or forced parameterization. This will make general tuning efforts less effective.'); -RAISERROR('Checking for selects that cause non-spill and index spool writes', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES ( - 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) -, selects -AS ( SELECT CONVERT(BINARY(8), - RIGHT('0000000000000000' - + SUBSTRING(s.statement.value('(/p:StmtSimple/@QueryHash)[1]', 'VARCHAR(18)'), - 3, 18), 16), 2) AS QueryHash - FROM #statements AS s - JOIN ##BlitzCacheProcs b - ON s.QueryHash = b.QueryHash - WHERE b.index_spool_rows IS NULL - AND b.index_spool_cost IS NULL - AND b.table_spool_cost IS NULL - AND b.table_spool_rows IS NULL - AND b.is_big_spills IS NULL - AND b.AverageWrites > 1024. - AND s.statement.exist('/p:StmtSimple/@StatementType[.="SELECT"]') = 1 -) -UPDATE b - SET b.select_with_writes = 1 -FROM ##BlitzCacheProcs b -JOIN selects AS s -ON s.QueryHash = b.QueryHash -AND b.AverageWrites > 1024.; + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs + WHERE is_cursor = 1 + AND SPID = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 4, + 200, + 'Cursors', + 'Cursor', + 'https://www.brentozar.com/blitzcache/cursors-found-slow-queries/', + 'There are cursors in the plan cache. This is neither good nor bad, but it is a thing. Cursors are weird in SQL Server.'); -/* 2012+ only */ -IF @v >= 11 -BEGIN + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs + WHERE is_cursor = 1 + AND is_optimistic_cursor = 1 + AND SPID = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 4, + 200, + 'Cursors', + 'Optimistic Cursors', + 'https://www.brentozar.com/blitzcache/cursors-found-slow-queries/', + 'There are optimistic cursors in the plan cache, which can harm performance.'); - RAISERROR(N'Checking for forced serialization', 0, 1) WITH NOWAIT; - WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) - UPDATE ##BlitzCacheProcs - SET is_forced_serial = 1 - FROM #query_plan qp - WHERE qp.SqlHandle = ##BlitzCacheProcs.SqlHandle - AND SPID = @@SPID - AND query_plan.exist('/p:QueryPlan/@NonParallelPlanReason') = 1 - AND (##BlitzCacheProcs.is_parallel = 0 OR ##BlitzCacheProcs.is_parallel IS NULL) - OPTION (RECOMPILE); - - IF @ExpertMode > 0 - BEGIN - RAISERROR(N'Checking for ColumnStore queries operating in Row Mode instead of Batch Mode', 0, 1) WITH NOWAIT; - WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) - UPDATE ##BlitzCacheProcs - SET columnstore_row_mode = x.is_row_mode - FROM ( - SELECT - qs.SqlHandle, - relop.exist('/p:RelOp[(@EstimatedExecutionMode[.="Row"])]') AS is_row_mode - FROM #relop qs - WHERE [relop].exist('/p:RelOp/p:IndexScan[(@Storage[.="ColumnStore"])]') = 1 - ) AS x - WHERE ##BlitzCacheProcs.SqlHandle = x.SqlHandle - AND SPID = @@SPID - OPTION (RECOMPILE); - END; + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs + WHERE is_cursor = 1 + AND is_forward_only_cursor = 0 + AND SPID = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 4, + 200, + 'Cursors', + 'Non-forward Only Cursors', + 'https://www.brentozar.com/blitzcache/cursors-found-slow-queries/', + 'There are non-forward only cursors in the plan cache, which can harm performance.'); -END; + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs + WHERE is_cursor = 1 + AND is_cursor_dynamic = 1 + AND SPID = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 4, + 200, + 'Cursors', + 'Dynamic Cursors', + 'https://www.brentozar.com/blitzcache/cursors-found-slow-queries/', + 'Dynamic Cursors inhibit parallelism!.'); -/* 2014+ only */ -IF @v >= 12 -BEGIN - RAISERROR('Checking for downlevel cardinality estimators being used on SQL Server 2014.', 0, 1) WITH NOWAIT; + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs + WHERE is_cursor = 1 + AND is_fast_forward_cursor = 1 + AND SPID = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 4, + 200, + 'Cursors', + 'Fast Forward Cursors', + 'https://www.brentozar.com/blitzcache/cursors-found-slow-queries/', + 'Fast forward cursors inhibit parallelism!.'); - WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) - UPDATE p - SET downlevel_estimator = CASE WHEN statement.value('min(//p:StmtSimple/@CardinalityEstimationModelVersion)', 'int') < (@v * 10) THEN 1 END - FROM ##BlitzCacheProcs p - JOIN #statements s ON p.QueryHash = s.QueryHash - WHERE SPID = @@SPID - OPTION (RECOMPILE); -END ; + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs + WHERE is_forced_parameterized = 1 + AND SPID = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 5, + 50, + 'Parameterization', + 'Forced Parameterization', + 'https://www.brentozar.com/blitzcache/forced-parameterization/', + 'Execution plans have been compiled with forced parameterization.') ; -/* 2016+ only */ -IF @v >= 13 AND @ExpertMode > 0 -BEGIN - RAISERROR('Checking for row level security in 2016 only', 0, 1) WITH NOWAIT; + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs p + WHERE p.is_parallel = 1 + AND SPID = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 6, + 200, + 'Execution Plans', + 'Parallel', + 'https://www.brentozar.com/blitzcache/parallel-plans-detected/', + 'Parallel plans detected. These warrant investigation, but are neither good nor bad.') ; - WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) - UPDATE p - SET p.is_row_level = 1 - FROM ##BlitzCacheProcs p - JOIN #statements s ON p.QueryHash = s.QueryHash - WHERE SPID = @@SPID - AND statement.exist('/p:StmtSimple/@SecurityPolicyApplied[.="true"]') = 1 - OPTION (RECOMPILE); -END ; + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs p + WHERE near_parallel = 1 + AND SPID = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 7, + 200, + 'Execution Plans', + 'Nearly Parallel', + 'https://www.brentozar.com/blitzcache/query-cost-near-cost-threshold-parallelism/', + 'Queries near the cost threshold for parallelism. These may go parallel when you least expect it.') ; -/* 2017+ only */ -IF @v >= 14 OR (@v = 13 AND @build >= 5026) -BEGIN + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs p + WHERE plan_warnings = 1 + AND SPID = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 8, + 50, + 'Execution Plans', + 'Plan Warnings', + 'https://www.brentozar.com/blitzcache/query-plan-warnings/', + 'Warnings detected in execution plans. SQL Server is telling you that something bad is going on that requires your attention.') ; -IF @ExpertMode > 0 -BEGIN -RAISERROR('Gathering stats information', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -INSERT INTO #stats_agg -SELECT qp.SqlHandle, - x.c.value('@LastUpdate', 'DATETIME2(7)') AS LastUpdate, - x.c.value('@ModificationCount', 'BIGINT') AS ModificationCount, - x.c.value('@SamplingPercent', 'FLOAT') AS SamplingPercent, - x.c.value('@Statistics', 'NVARCHAR(258)') AS [Statistics], - x.c.value('@Table', 'NVARCHAR(258)') AS [Table], - x.c.value('@Schema', 'NVARCHAR(258)') AS [Schema], - x.c.value('@Database', 'NVARCHAR(258)') AS [Database] -FROM #query_plan AS qp -CROSS APPLY qp.query_plan.nodes('//p:OptimizerStatsUsage/p:StatisticsInfo') x (c) -OPTION (RECOMPILE); + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs p + WHERE long_running = 1 + AND SPID = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 9, + 50, + 'Performance', + 'Long Running Query', + 'https://www.brentozar.com/blitzcache/long-running-queries/', + 'Long running queries have been found. These are queries with an average duration longer than ' + + CAST(@long_running_query_warning_seconds / 1000 / 1000 AS VARCHAR(5)) + + ' second(s). These queries should be investigated for additional tuning options.') ; + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs p + WHERE p.missing_index_count > 0 + AND SPID = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 10, + 50, + 'Performance', + 'Missing Indexes', + 'https://www.brentozar.com/blitzcache/missing-index-request/', + 'Queries found with missing indexes.'); -RAISERROR('Checking for stale stats', 0, 1) WITH NOWAIT; -WITH stale_stats AS ( - SELECT sa.SqlHandle - FROM #stats_agg AS sa - GROUP BY sa.SqlHandle - HAVING MAX(sa.LastUpdate) <= DATEADD(DAY, -7, SYSDATETIME()) - AND AVG(sa.ModificationCount) >= 100000 -) -UPDATE b -SET stale_stats = 1 -FROM ##BlitzCacheProcs b -JOIN stale_stats os -ON b.SqlHandle = os.SqlHandle -AND b.SPID = @@SPID -OPTION (RECOMPILE); -END; + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs p + WHERE p.downlevel_estimator = 1 + AND SPID = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 13, + 200, + 'Cardinality', + 'Downlevel CE', + 'https://www.brentozar.com/blitzcache/legacy-cardinality-estimator/', + 'A legacy cardinality estimator is being used by one or more queries. Investigate whether you need to be using this cardinality estimator. This may be caused by compatibility levels, global trace flags, or query level trace flags.'); -IF @v >= 14 AND @ExpertMode > 0 -BEGIN -RAISERROR('Checking for adaptive joins', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p), -aj AS ( - SELECT - SqlHandle - FROM #relop AS r - CROSS APPLY r.relop.nodes('//p:RelOp') x(c) - WHERE x.c.exist('@IsAdaptive[.=1]') = 1 -) -UPDATE b -SET b.is_adaptive = 1 -FROM ##BlitzCacheProcs b -JOIN aj -ON b.SqlHandle = aj.SqlHandle -AND b.SPID = @@SPID -OPTION (RECOMPILE); -END; + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs p + WHERE implicit_conversions = 1 + AND SPID = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 14, + 50, + 'Performance', + 'Implicit Conversions', + 'https://www.brentozar.com/go/implicit', + 'One or more queries are comparing two fields that are not of the same data type.') ; -IF ((@v >= 14 - OR (@v = 13 AND @build >= 5026) - OR (@v = 12 AND @build >= 6024)) - AND @ExpertMode > 0) + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs + WHERE busy_loops = 1 + AND SPID = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 16, + 100, + 'Performance', + 'Busy Loops', + 'https://www.brentozar.com/blitzcache/busy-loops/', + 'Operations have been found that are executed 100 times more often than the number of rows returned by each iteration. This is an indicator that something is off in query execution.'); -BEGIN; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p), -row_goals AS( -SELECT qs.QueryHash -FROM #relop qs -WHERE relop.value('sum(/p:RelOp/@EstimateRowsWithoutRowGoal)', 'float') > 0 -) -UPDATE b -SET b.is_row_goal = 1 -FROM ##BlitzCacheProcs b -JOIN row_goals -ON b.QueryHash = row_goals.QueryHash -AND b.SPID = @@SPID -OPTION (RECOMPILE); -END ; + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs + WHERE tvf_join = 1 + AND SPID = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 17, + 50, + 'Performance', + 'Function Join', + 'https://www.brentozar.com/blitzcache/tvf-join/', + 'Execution plans have been found that join to table valued functions (TVFs). TVFs produce inaccurate estimates of the number of rows returned and can lead to any number of query plan problems.'); -END; + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs + WHERE compile_timeout = 1 + AND SPID = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 18, + 50, + 'Execution Plans', + 'Compilation Timeout', + 'https://www.brentozar.com/blitzcache/compilation-timeout/', + 'Query compilation timed out for one or more queries. SQL Server did not find a plan that meets acceptable performance criteria in the time allotted so the best guess was returned. There is a very good chance that this plan isn''t even below average - it''s probably terrible.'); + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs + WHERE compile_memory_limit_exceeded = 1 + AND SPID = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 19, + 50, + 'Execution Plans', + 'Compile Memory Limit Exceeded', + 'https://www.brentozar.com/blitzcache/compile-memory-limit-exceeded/', + 'The optimizer has a limited amount of memory available. One or more queries are complex enough that SQL Server was unable to allocate enough memory to fully optimize the query. A best fit plan was found, and it''s probably terrible.'); -/* END Testing using XML nodes to speed up processing */ + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs + WHERE warning_no_join_predicate = 1 + AND SPID = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 20, + 50, + 'Execution Plans', + 'No Join Predicate', + 'https://www.brentozar.com/blitzcache/no-join-predicate/', + 'Operators in a query have no join predicate. This means that all rows from one table will be matched with all rows from anther table producing a Cartesian product. That''s a whole lot of rows. This may be your goal, but it''s important to investigate why this is happening.'); + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs + WHERE plan_multiple_plans > 0 + AND SPID = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 21, + 200, + 'Execution Plans', + 'Multiple Plans', + 'https://www.brentozar.com/blitzcache/multiple-plans/', + 'Queries exist with multiple execution plans (as determined by query_plan_hash). Investigate possible ways to parameterize these queries or otherwise reduce the plan count.'); -/* Update to grab stored procedure name for individual statements */ -RAISERROR(N'Attempting to get stored procedure name for individual statements', 0, 1) WITH NOWAIT; -UPDATE p -SET QueryType = QueryType + ' (parent ' + - + QUOTENAME(OBJECT_SCHEMA_NAME(s.object_id, s.database_id)) - + '.' - + QUOTENAME(OBJECT_NAME(s.object_id, s.database_id)) + ')' -FROM ##BlitzCacheProcs p - JOIN sys.dm_exec_procedure_stats s ON p.SqlHandle = s.sql_handle -WHERE QueryType = 'Statement' -AND SPID = @@SPID -OPTION (RECOMPILE); - -/* Update to grab stored procedure name for individual statements when PSPO is detected */ -UPDATE p -SET QueryType = QueryType + ' (parent ' + - + QUOTENAME(OBJECT_SCHEMA_NAME(s.object_id, s.database_id)) - + '.' - + QUOTENAME(OBJECT_NAME(s.object_id, s.database_id)) + ')' -FROM ##BlitzCacheProcs p - OUTER APPLY ( - SELECT REPLACE(REPLACE(REPLACE(REPLACE(p.QueryText, ' (', '('), '( ', '('), ' =', '='), '= ', '=') AS NormalizedQueryText - ) a - OUTER APPLY ( - SELECT CHARINDEX('option(PLAN PER VALUE(ObjectID=', a.NormalizedQueryText) AS OptionStart - ) b - OUTER APPLY ( - SELECT SUBSTRING(a.NormalizedQueryText, b.OptionStart + 31, LEN(a.NormalizedQueryText) - b.OptionStart - 30) AS OptionSubstring - WHERE b.OptionStart > 0 - ) c - OUTER APPLY ( - SELECT PATINDEX('%[^0-9]%', c.OptionSubstring) AS ObjectLength - ) d - OUTER APPLY ( - SELECT TRY_CAST(SUBSTRING(OptionSubstring, 1, d.ObjectLength - 1) AS INT) AS ObjectId - ) e - JOIN sys.dm_exec_procedure_stats s ON DB_ID(p.DatabaseName) = s.database_id AND e.ObjectId = s.object_id -WHERE p.QueryType = 'Statement' -AND p.SPID = @@SPID -AND s.object_id IS NOT NULL -OPTION (RECOMPILE); - -RAISERROR(N'Attempting to get function name for individual statements', 0, 1) WITH NOWAIT; -DECLARE @function_update_sql NVARCHAR(MAX) = N'' -IF EXISTS (SELECT 1/0 FROM sys.all_objects AS o WHERE o.name = 'dm_exec_function_stats') - BEGIN - SET @function_update_sql = @function_update_sql + N' - UPDATE p - SET QueryType = QueryType + '' (parent '' + - + QUOTENAME(OBJECT_SCHEMA_NAME(s.object_id, s.database_id)) - + ''.'' - + QUOTENAME(OBJECT_NAME(s.object_id, s.database_id)) + '')'' - FROM ##BlitzCacheProcs p - JOIN sys.dm_exec_function_stats s ON p.SqlHandle = s.sql_handle - WHERE QueryType = ''Statement'' - AND SPID = @@SPID - OPTION (RECOMPILE); - ' - EXEC sys.sp_executesql @function_update_sql - END + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs + WHERE unmatched_index_count > 0 + AND SPID = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 22, + 100, + 'Performance', + 'Unmatched Indexes', + 'https://www.brentozar.com/blitzcache/unmatched-indexes', + 'An index could have been used, but SQL Server chose not to use it - likely due to parameterization and filtered indexes.'); + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs + WHERE unparameterized_query = 1 + AND SPID = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 23, + 100, + 'Parameterization', + 'Unparameterized Query', + 'https://www.brentozar.com/blitzcache/unparameterized-queries', + 'Unparameterized queries found. These could be ad hoc queries, data exploration, or queries using "OPTIMIZE FOR UNKNOWN".'); -/* Trace Flag Checks 2012 SP3, 2014 SP2 and 2016 SP1 only)*/ -IF @v >= 11 -BEGIN + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs + WHERE is_trivial = 1 + AND SPID = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 24, + 100, + 'Execution Plans', + 'Trivial Plans', + 'https://www.brentozar.com/blitzcache/trivial-plans', + 'Trivial plans get almost no optimization. If you''re finding these in the top worst queries, something may be going wrong.'); + + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs p + WHERE p.is_forced_serial= 1 + AND SPID = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 25, + 10, + 'Execution Plans', + 'Forced Serialization', + 'https://www.brentozar.com/blitzcache/forced-serialization/', + 'Something in your plan is forcing a serial query. Further investigation is needed if this is not by design.') ; -RAISERROR(N'Trace flag checks', 0, 1) WITH NOWAIT; -;WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -, tf_pretty AS ( -SELECT qp.QueryHash, - qp.SqlHandle, - q.n.value('@Value', 'INT') AS trace_flag, - q.n.value('@Scope', 'VARCHAR(10)') AS scope -FROM #query_plan qp -CROSS APPLY qp.query_plan.nodes('/p:QueryPlan/p:TraceFlags/p:TraceFlag') AS q(n) -) -INSERT INTO #trace_flags -SELECT DISTINCT tf1.SqlHandle , tf1.QueryHash, - STUFF(( - SELECT DISTINCT ', ' + CONVERT(VARCHAR(5), tf2.trace_flag) - FROM tf_pretty AS tf2 - WHERE tf1.SqlHandle = tf2.SqlHandle - AND tf1.QueryHash = tf2.QueryHash - AND tf2.scope = 'Global' - FOR XML PATH(N'')), 1, 2, N'' - ) AS global_trace_flags, - STUFF(( - SELECT DISTINCT ', ' + CONVERT(VARCHAR(5), tf2.trace_flag) - FROM tf_pretty AS tf2 - WHERE tf1.SqlHandle = tf2.SqlHandle - AND tf1.QueryHash = tf2.QueryHash - AND tf2.scope = 'Session' - FOR XML PATH(N'')), 1, 2, N'' - ) AS session_trace_flags -FROM tf_pretty AS tf1 -OPTION (RECOMPILE); + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs p + WHERE p.is_key_lookup_expensive= 1 + AND SPID = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 26, + 100, + 'Execution Plans', + 'Expensive Key Lookup', + 'https://www.brentozar.com/blitzcache/expensive-key-lookups/', + 'There''s a key lookup in your plan that costs >=50% of the total plan cost.') ; -UPDATE p -SET p.trace_flags_session = tf.session_trace_flags -FROM ##BlitzCacheProcs p -JOIN #trace_flags tf ON tf.QueryHash = p.QueryHash -WHERE SPID = @@SPID -OPTION (RECOMPILE); + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs p + WHERE p.is_remote_query_expensive= 1 + AND SPID = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 28, + 100, + 'Execution Plans', + 'Expensive Remote Query', + 'https://www.brentozar.com/blitzcache/expensive-remote-query/', + 'There''s a remote query in your plan that costs >=50% of the total plan cost.') ; -END; + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs p + WHERE p.trace_flags_session IS NOT NULL + AND SPID = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 29, + 200, + 'Trace Flags', + 'Session Level Trace Flags Enabled', + 'https://www.brentozar.com/blitz/trace-flags-enabled-globally/', + 'Someone is enabling session level Trace Flags in a query.') ; + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs p + WHERE p.is_unused_grant IS NOT NULL + AND SPID = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 30, + 100, + 'Memory Grant', + 'Unused Memory Grant', + 'https://www.brentozar.com/blitzcache/unused-memory-grants/', + 'Queries have large unused memory grants. This can cause concurrency issues, if queries are waiting a long time to get memory to run.') ; -RAISERROR(N'Checking for MSTVFs', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET b.is_mstvf = 1 -FROM #relop AS r -JOIN ##BlitzCacheProcs AS b -ON b.SqlHandle = r.SqlHandle -WHERE r.relop.exist('/p:RelOp[(@EstimateRows="100" or @EstimateRows="1") and @LogicalOp="Table-valued function"]') = 1 -OPTION (RECOMPILE); + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs p + WHERE p.function_count > 0 + AND SPID = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 31, + 100, + 'Compute Scalar That References A Function', + 'Calls Functions', + 'https://www.brentozar.com/blitzcache/compute-scalar-functions/', + 'Both of these will force queries to run serially, run at least once per row, and may result in poor cardinality estimates.') ; + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs p + WHERE p.clr_function_count > 0 + AND SPID = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 32, + 100, + 'Compute Scalar That References A CLR Function', + 'Calls CLR Functions', + 'https://www.brentozar.com/blitzcache/compute-scalar-functions/', + 'May force queries to run serially, run at least once per row, and may result in poor cardinality estimates.') ; -IF @ExpertMode > 0 -BEGIN -RAISERROR(N'Checking for many to many merge joins', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET b.is_mm_join = 1 -FROM #relop AS r -JOIN ##BlitzCacheProcs AS b -ON b.SqlHandle = r.SqlHandle -WHERE r.relop.exist('/p:RelOp/p:Merge/@ManyToMany[.="1"]') = 1 -OPTION (RECOMPILE); -END ; + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs p + WHERE p.is_table_variable = 1 + AND SPID = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 33, + 100, + 'Table Variables detected', + 'Table Variables', + 'https://www.brentozar.com/blitzcache/table-variables/', + 'All modifications are single threaded, and selects have really low row estimates.') ; -IF @ExpertMode > 0 -BEGIN -RAISERROR(N'Is Paul White Electric?', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p), -is_paul_white_electric AS ( -SELECT 1 AS [is_paul_white_electric], -r.SqlHandle -FROM #relop AS r -CROSS APPLY r.relop.nodes('//p:RelOp') c(n) -WHERE c.n.exist('@PhysicalOp[.="Switch"]') = 1 -) -UPDATE b -SET b.is_paul_white_electric = ipwe.is_paul_white_electric -FROM ##BlitzCacheProcs AS b -JOIN is_paul_white_electric ipwe -ON ipwe.SqlHandle = b.SqlHandle -WHERE b.SPID = @@SPID -OPTION (RECOMPILE); -END ; + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs p + WHERE p.no_stats_warning = 1 + AND SPID = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 35, + 100, + 'Statistics', + 'Columns With No Statistics', + 'https://www.brentozar.com/blitzcache/columns-no-statistics/', + 'Sometimes this happens with indexed views, other times because auto create stats is turned off.') ; + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs p + WHERE p.relop_warnings = 1 + AND SPID = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 36, + 100, + 'Warnings', + 'Operator Warnings', + 'https://www.brentozar.com/blitzcache/query-plan-warnings/', + 'Check the plan for more details.') ; -RAISERROR(N'Checking for non-sargable predicates', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES ( 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) -, nsarg - AS ( SELECT r.QueryHash, 1 AS fn, 0 AS jo, 0 AS lk - FROM #relop AS r - CROSS APPLY r.relop.nodes('/p:RelOp/p:IndexScan/p:Predicate/p:ScalarOperator/p:Compare/p:ScalarOperator') AS ca(x) - WHERE ( ca.x.exist('//p:ScalarOperator/p:Intrinsic/@FunctionName') = 1 - OR ca.x.exist('//p:ScalarOperator/p:IF') = 1 ) - UNION ALL - SELECT r.QueryHash, 0 AS fn, 1 AS jo, 0 AS lk - FROM #relop AS r - CROSS APPLY r.relop.nodes('/p:RelOp//p:ScalarOperator') AS ca(x) - WHERE r.relop.exist('/p:RelOp[contains(@LogicalOp, "Join")]') = 1 - AND ca.x.exist('//p:ScalarOperator[contains(@ScalarString, "Expr")]') = 1 - UNION ALL - SELECT r.QueryHash, 0 AS fn, 0 AS jo, 1 AS lk - FROM #relop AS r - CROSS APPLY r.relop.nodes('/p:RelOp/p:IndexScan/p:Predicate/p:ScalarOperator') AS ca(x) - CROSS APPLY ca.x.nodes('//p:Const') AS co(x) - WHERE ca.x.exist('//p:ScalarOperator/p:Intrinsic/@FunctionName[.="like"]') = 1 - AND ( ( co.x.value('substring(@ConstValue, 1, 1)', 'VARCHAR(100)') <> 'N' - AND co.x.value('substring(@ConstValue, 2, 1)', 'VARCHAR(100)') = '%' ) - OR ( co.x.value('substring(@ConstValue, 1, 1)', 'VARCHAR(100)') = 'N' - AND co.x.value('substring(@ConstValue, 3, 1)', 'VARCHAR(100)') = '%' ))), - d_nsarg - AS ( SELECT DISTINCT - nsarg.QueryHash - FROM nsarg - WHERE nsarg.fn = 1 - OR nsarg.jo = 1 - OR nsarg.lk = 1 ) -UPDATE b -SET b.is_nonsargable = 1 -FROM d_nsarg AS d -JOIN ##BlitzCacheProcs AS b - ON b.QueryHash = d.QueryHash -WHERE b.SPID = @@SPID -OPTION ( RECOMPILE ); + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs p + WHERE p.is_table_scan = 1 + AND SPID = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 37, + 100, + 'Indexes', + 'Table Scans (Heaps)', + 'https://www.brentozar.com/archive/2012/05/video-heaps/', + 'This may not be a problem. Run sp_BlitzIndex for more information.') ; + + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs p + WHERE p.backwards_scan = 1 + AND SPID = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 38, + 200, + 'Indexes', + 'Backwards Scans', + 'https://www.brentozar.com/blitzcache/backwards-scans/', + 'This isn''t always a problem. They can cause serial zones in plans, and may need an index to match sort order.') ; -/*Begin implicit conversion and parameter info */ + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs p + WHERE p.forced_index = 1 + AND SPID = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 39, + 100, + 'Indexes', + 'Forced Indexes', + 'https://www.brentozar.com/blitzcache/optimizer-forcing/', + 'This can cause inefficient plans, and will prevent missing index requests.') ; -RAISERROR(N'Getting information about implicit conversions and stored proc parameters', 0, 1) WITH NOWAIT; + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs p + WHERE p.forced_seek = 1 + AND SPID = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 40, + 100, + 'Indexes', + 'Forced Seeks', + 'https://www.brentozar.com/blitzcache/optimizer-forcing/', + 'This can cause inefficient plans by taking seek vs scan choice away from the optimizer.') ; -RAISERROR(N'Getting variable info', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES ( 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) -INSERT #variable_info ( SPID, QueryHash, SqlHandle, proc_name, variable_name, variable_datatype, compile_time_value ) -SELECT DISTINCT @@SPID, - qp.QueryHash, - qp.SqlHandle, - b.QueryType AS proc_name, - q.n.value('@Column', 'NVARCHAR(258)') AS variable_name, - q.n.value('@ParameterDataType', 'NVARCHAR(258)') AS variable_datatype, - q.n.value('@ParameterCompiledValue', 'NVARCHAR(258)') AS compile_time_value -FROM #query_plan AS qp -JOIN ##BlitzCacheProcs AS b -ON (b.QueryType = 'adhoc' AND b.QueryHash = qp.QueryHash) -OR (b.QueryType <> 'adhoc' AND b.SqlHandle = qp.SqlHandle) -CROSS APPLY qp.query_plan.nodes('//p:QueryPlan/p:ParameterList/p:ColumnReference') AS q(n) -WHERE b.SPID = @@SPID -OPTION (RECOMPILE); + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs p + WHERE p.forced_scan = 1 + AND SPID = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 40, + 100, + 'Indexes', + 'Forced Scans', + 'https://www.brentozar.com/blitzcache/optimizer-forcing/', + 'This can cause inefficient plans by taking seek vs scan choice away from the optimizer.') ; + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs p + WHERE p.columnstore_row_mode = 1 + AND SPID = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 41, + 100, + 'Indexes', + 'ColumnStore Row Mode', + 'https://www.brentozar.com/blitzcache/columnstore-indexes-operating-row-mode/', + 'ColumnStore indexes operating in Row Mode indicate really poor query choices.') ; -RAISERROR(N'Getting conversion info', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES ( 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) -INSERT #conversion_info ( SPID, QueryHash, SqlHandle, proc_name, expression ) -SELECT DISTINCT @@SPID, - qp.QueryHash, - qp.SqlHandle, - b.QueryType AS proc_name, - qq.c.value('@Expression', 'NVARCHAR(4000)') AS expression -FROM #query_plan AS qp -JOIN ##BlitzCacheProcs AS b -ON (b.QueryType = 'adhoc' AND b.QueryHash = qp.QueryHash) -OR (b.QueryType <> 'adhoc' AND b.SqlHandle = qp.SqlHandle) -CROSS APPLY qp.query_plan.nodes('//p:QueryPlan/p:Warnings/p:PlanAffectingConvert') AS qq(c) -WHERE qq.c.exist('@ConvertIssue[.="Seek Plan"]') = 1 - AND qp.QueryHash IS NOT NULL - AND b.implicit_conversions = 1 -AND b.SPID = @@SPID -OPTION (RECOMPILE); + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs p + WHERE p.is_computed_scalar = 1 + AND SPID = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 42, + 50, + 'Functions', + 'Computed Column UDF', + 'https://www.brentozar.com/blitzcache/computed-columns-referencing-functions/', + 'This can cause a whole mess of bad serializartion problems.') ; + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs p + WHERE p.is_sort_expensive = 1 + AND SPID = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 43, + 100, + 'Execution Plans', + 'Expensive Sort', + 'https://www.brentozar.com/blitzcache/expensive-sorts/', + 'There''s a sort in your plan that costs >=50% of the total plan cost.') ; -RAISERROR(N'Parsing conversion info', 0, 1) WITH NOWAIT; -INSERT #stored_proc_info ( SPID, SqlHandle, QueryHash, proc_name, variable_name, variable_datatype, converted_column_name, column_name, converted_to, compile_time_value ) -SELECT @@SPID AS SPID, - ci.SqlHandle, - ci.QueryHash, - REPLACE(REPLACE(REPLACE(ci.proc_name, ')', ''), 'Statement (parent ', ''), 'Procedure or Function: ', '') AS proc_name, - CASE WHEN ci.at_charindex > 0 - AND ci.bracket_charindex > 0 - THEN SUBSTRING(ci.expression, ci.at_charindex, ci.bracket_charindex) - ELSE N'**no_variable**' - END AS variable_name, - N'**no_variable**' AS variable_datatype, - CASE WHEN ci.at_charindex = 0 - AND ci.comma_charindex > 0 - AND ci.second_comma_charindex > 0 - THEN SUBSTRING(ci.expression, ci.comma_charindex, ci.second_comma_charindex) - ELSE N'**no_column**' - END AS converted_column_name, - CASE WHEN ci.at_charindex = 0 - AND ci.equal_charindex > 0 - AND ci.convert_implicit_charindex = 0 - THEN SUBSTRING(ci.expression, ci.equal_charindex, 4000) - WHEN ci.at_charindex = 0 - AND (ci.equal_charindex -1) > 0 - AND ci.convert_implicit_charindex > 0 - THEN SUBSTRING(ci.expression, 0, ci.equal_charindex -1) - WHEN ci.at_charindex > 0 - AND ci.comma_charindex > 0 - AND ci.second_comma_charindex > 0 - THEN SUBSTRING(ci.expression, ci.comma_charindex, ci.second_comma_charindex) - ELSE N'**no_column **' - END AS column_name, - CASE WHEN ci.paren_charindex > 0 - 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 - 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 -FROM #conversion_info AS ci -OPTION (RECOMPILE); + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs p + WHERE p.is_computed_filter = 1 + AND SPID = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 44, + 50, + 'Functions', + 'Filter UDF', + 'https://www.brentozar.com/blitzcache/compute-scalar-functions/', + 'Someone put a Scalar UDF in the WHERE clause!') ; + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs p + WHERE p.index_ops >= 5 + AND SPID = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 45, + 100, + 'Indexes', + '>= 5 Indexes Modified', + 'https://www.brentozar.com/blitzcache/many-indexes-modified/', + 'This can cause lots of hidden I/O -- Run sp_BlitzIndex for more information.') ; -RAISERROR(N'Updating variables for inserted procs', 0, 1) WITH NOWAIT; -UPDATE sp -SET sp.variable_datatype = vi.variable_datatype, - sp.compile_time_value = vi.compile_time_value -FROM #stored_proc_info AS sp -JOIN #variable_info AS vi -ON (sp.proc_name = 'adhoc' AND sp.QueryHash = vi.QueryHash) -OR (sp.proc_name <> 'adhoc' AND sp.SqlHandle = vi.SqlHandle) -AND sp.variable_name = vi.variable_name -OPTION (RECOMPILE); + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs p + WHERE p.is_row_level = 1 + AND SPID = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 46, + 200, + 'Complexity', + 'Row Level Security', + 'https://www.brentozar.com/blitzcache/row-level-security/', + 'You may see a lot of confusing junk in your query plan.') ; + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs p + WHERE p.is_spatial = 1 + AND SPID = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 47, + 200, + 'Complexity', + 'Spatial Index', + 'https://www.brentozar.com/blitzcache/spatial-indexes/', + 'Purely informational.') ; -RAISERROR(N'Inserting variables for other procs', 0, 1) WITH NOWAIT; -INSERT #stored_proc_info - ( SPID, SqlHandle, QueryHash, variable_name, variable_datatype, compile_time_value, proc_name ) -SELECT vi.SPID, vi.SqlHandle, vi.QueryHash, vi.variable_name, vi.variable_datatype, vi.compile_time_value, REPLACE(REPLACE(REPLACE(vi.proc_name, ')', ''), 'Statement (parent ', ''), 'Procedure or Function: ', '') AS proc_name -FROM #variable_info AS vi -WHERE NOT EXISTS -( - SELECT * - FROM #stored_proc_info AS sp - WHERE (sp.proc_name = 'adhoc' AND sp.QueryHash = vi.QueryHash) - OR (sp.proc_name <> 'adhoc' AND sp.SqlHandle = vi.SqlHandle) -) -OPTION (RECOMPILE); + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs p + WHERE p.index_dml = 1 + AND SPID = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 48, + 150, + 'Complexity', + 'Index DML', + 'https://www.brentozar.com/blitzcache/index-dml/', + 'This can cause recompiles and stuff.') ; + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs p + WHERE p.table_dml = 1 + AND SPID = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 49, + 150, + 'Complexity', + 'Table DML', + 'https://www.brentozar.com/blitzcache/table-dml/', + 'This can cause recompiles and stuff.') ; -RAISERROR(N'Updating procs', 0, 1) WITH NOWAIT; -UPDATE s -SET s.variable_datatype = CASE WHEN s.variable_datatype LIKE '%(%)%' - THEN LEFT(s.variable_datatype, CHARINDEX('(', s.variable_datatype) - 1) - ELSE s.variable_datatype - END, - s.converted_to = CASE WHEN s.converted_to LIKE '%(%)%' - THEN LEFT(s.converted_to, CHARINDEX('(', s.converted_to) - 1) - ELSE s.converted_to - END, - s.compile_time_value = CASE WHEN s.compile_time_value LIKE '%(%)%' - THEN SUBSTRING(s.compile_time_value, - CHARINDEX('(', s.compile_time_value) + 1, - CHARINDEX(')', s.compile_time_value, CHARINDEX('(', s.compile_time_value) + 1) - 1 - CHARINDEX('(', s.compile_time_value) - ) - WHEN variable_datatype NOT IN ('bit', 'tinyint', 'smallint', 'int', 'bigint') - AND s.variable_datatype NOT LIKE '%binary%' - AND s.compile_time_value NOT LIKE 'N''%''' - AND s.compile_time_value NOT LIKE '''%''' - AND s.compile_time_value <> s.column_name - AND s.compile_time_value <> '**idk_man**' - THEN QUOTENAME(compile_time_value, '''') - ELSE s.compile_time_value - END -FROM #stored_proc_info AS s -OPTION (RECOMPILE); + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs p + WHERE p.long_running_low_cpu = 1 + AND SPID = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 50, + 150, + 'Blocking', + 'Long Running Low CPU', + 'https://www.brentozar.com/blitzcache/long-running-low-cpu/', + 'This can be a sign of blocking, linked servers, or poor client application code (ASYNC_NETWORK_IO).') ; + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs p + WHERE p.low_cost_high_cpu = 1 + AND SPID = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 51, + 150, + 'Complexity', + 'Low Cost Query With High CPU', + 'https://www.brentozar.com/blitzcache/low-cost-high-cpu/', + 'This can be a sign of functions or Dynamic SQL that calls black-box code.') ; -RAISERROR(N'Updating SET options', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE s -SET set_options = set_options.ansi_set_options -FROM #stored_proc_info AS s -JOIN ( - SELECT x.SqlHandle, - N'SET ANSI_NULLS ' + CASE WHEN [ANSI_NULLS] = 'true' THEN N'ON ' ELSE N'OFF ' END + NCHAR(10) + - N'SET ANSI_PADDING ' + CASE WHEN [ANSI_PADDING] = 'true' THEN N'ON ' ELSE N'OFF ' END + NCHAR(10) + - N'SET ANSI_WARNINGS ' + CASE WHEN [ANSI_WARNINGS] = 'true' THEN N'ON ' ELSE N'OFF ' END + NCHAR(10) + - N'SET ARITHABORT ' + CASE WHEN [ARITHABORT] = 'true' THEN N'ON ' ELSE N' OFF ' END + NCHAR(10) + - N'SET CONCAT_NULL_YIELDS_NULL ' + CASE WHEN [CONCAT_NULL_YIELDS_NULL] = 'true' THEN N'ON ' ELSE N'OFF ' END + NCHAR(10) + - N'SET NUMERIC_ROUNDABORT ' + CASE WHEN [NUMERIC_ROUNDABORT] = 'true' THEN N'ON ' ELSE N'OFF ' END + NCHAR(10) + - N'SET QUOTED_IDENTIFIER ' + CASE WHEN [QUOTED_IDENTIFIER] = 'true' THEN N'ON ' ELSE N'OFF ' + NCHAR(10) END AS [ansi_set_options] - FROM ( - SELECT - s.SqlHandle, - so.o.value('@ANSI_NULLS', 'NVARCHAR(20)') AS [ANSI_NULLS], - so.o.value('@ANSI_PADDING', 'NVARCHAR(20)') AS [ANSI_PADDING], - so.o.value('@ANSI_WARNINGS', 'NVARCHAR(20)') AS [ANSI_WARNINGS], - so.o.value('@ARITHABORT', 'NVARCHAR(20)') AS [ARITHABORT], - so.o.value('@CONCAT_NULL_YIELDS_NULL', 'NVARCHAR(20)') AS [CONCAT_NULL_YIELDS_NULL], - so.o.value('@NUMERIC_ROUNDABORT', 'NVARCHAR(20)') AS [NUMERIC_ROUNDABORT], - so.o.value('@QUOTED_IDENTIFIER', 'NVARCHAR(20)') AS [QUOTED_IDENTIFIER] - FROM #statements AS s - CROSS APPLY s.statement.nodes('//p:StatementSetOptions') AS so(o) - ) AS x -) AS set_options ON set_options.SqlHandle = s.SqlHandle -OPTION(RECOMPILE); + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs p + WHERE p.stale_stats = 1 + AND SPID = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 52, + 150, + 'Statistics', + 'Statistics used have > 100k modifications in the last 7 days', + 'https://www.brentozar.com/blitzcache/stale-statistics/', + 'Ever heard of updating statistics?') ; + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs p + WHERE p.is_adaptive = 1 + AND SPID = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 53, + 200, + 'Complexity', + 'Adaptive joins', + 'https://www.brentozar.com/blitzcache/adaptive-joins/', + 'This join will sometimes do seeks, and sometimes do scans.') ; -RAISERROR(N'Updating conversion XML', 0, 1) WITH NOWAIT; -WITH precheck AS ( -SELECT spi.SPID, - spi.SqlHandle, - spi.proc_name, - (SELECT - CASE WHEN spi.proc_name <> 'Statement' - THEN N'The stored procedure ' + spi.proc_name - ELSE N'This ad hoc statement' - END - + N' had the following implicit conversions: ' - + CHAR(10) - + STUFF(( - SELECT DISTINCT - @nl - + CASE WHEN spi2.variable_name <> N'**no_variable**' - THEN N'The variable ' - WHEN spi2.variable_name = N'**no_variable**' AND (spi2.column_name = spi2.converted_column_name OR spi2.column_name LIKE '%CONVERT_IMPLICIT%') - THEN N'The compiled value ' - WHEN spi2.column_name LIKE '%Expr%' - THEN 'The expression ' - ELSE N'The column ' - END - + CASE WHEN spi2.variable_name <> N'**no_variable**' - THEN spi2.variable_name - WHEN spi2.variable_name = N'**no_variable**' AND (spi2.column_name = spi2.converted_column_name OR spi2.column_name LIKE '%CONVERT_IMPLICIT%') - THEN spi2.compile_time_value - ELSE spi2.column_name - END - + N' has a data type of ' - + CASE WHEN spi2.variable_datatype = N'**no_variable**' THEN spi2.converted_to - ELSE spi2.variable_datatype - END - + N' which caused implicit conversion on the column ' - + CASE WHEN spi2.column_name LIKE N'%CONVERT_IMPLICIT%' - THEN spi2.converted_column_name - WHEN spi2.column_name = N'**no_column**' - THEN spi2.converted_column_name - WHEN spi2.converted_column_name = N'**no_column**' - THEN spi2.column_name - WHEN spi2.column_name <> spi2.converted_column_name - THEN spi2.converted_column_name - ELSE spi2.column_name - END - + CASE WHEN spi2.variable_name = N'**no_variable**' AND (spi2.column_name = spi2.converted_column_name OR spi2.column_name LIKE '%CONVERT_IMPLICIT%') - THEN N'' - WHEN spi2.column_name LIKE '%Expr%' - THEN N'' - WHEN spi2.compile_time_value NOT IN ('**declared in proc**', '**idk_man**') - AND spi2.compile_time_value <> spi2.column_name - THEN ' with the value ' + RTRIM(spi2.compile_time_value) - ELSE N'' - END - + '.' - FROM #stored_proc_info AS spi2 - WHERE spi.SqlHandle = spi2.SqlHandle - FOR XML PATH(N''), TYPE).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 1, N'') - AS [processing-instruction(ClickMe)] FOR XML PATH(''), TYPE ) - AS implicit_conversion_info -FROM #stored_proc_info AS spi -GROUP BY spi.SPID, spi.SqlHandle, spi.proc_name -) -UPDATE b -SET b.implicit_conversion_info = pk.implicit_conversion_info -FROM ##BlitzCacheProcs AS b -JOIN precheck pk -ON pk.SqlHandle = b.SqlHandle -AND pk.SPID = b.SPID -OPTION (RECOMPILE); + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs p + WHERE p.is_spool_expensive = 1 + ) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 54, + 150, + 'Indexes', + 'Expensive Index Spool', + 'https://www.brentozar.com/blitzcache/eager-index-spools/', + 'Check operator predicates and output for index definition guidance') ; + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs p + WHERE p.is_spool_more_rows = 1 + ) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 55, + 150, + 'Indexes', + 'Large Index Row Spool', + 'https://www.brentozar.com/blitzcache/eager-index-spools/', + 'Check operator predicates and output for index definition guidance') ; + + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs p + WHERE p.is_bad_estimate = 1 + ) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 56, + 100, + 'Complexity', + 'Row Estimate Mismatch', + 'https://www.brentozar.com/blitzcache/bad-estimates/', + 'Estimated rows are different from average rows by a factor of 10000. This may indicate a performance problem if mismatches occur regularly') ; + + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs p + WHERE p.is_paul_white_electric = 1 + ) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 57, + 200, + 'Is Paul White Electric?', + 'This query has a Switch operator in it!', + 'https://www.sql.kiwi/2013/06/hello-operator-my-switch-is-bored.html', + 'You should email this query plan to Paul: SQLkiwi at gmail dot com') ; -RAISERROR(N'Updating cached parameter XML for stored procs', 0, 1) WITH NOWAIT; -WITH precheck AS ( -SELECT spi.SPID, - spi.SqlHandle, - spi.proc_name, - (SELECT - set_options - + @nl - + @nl - + N'EXEC ' - + spi.proc_name - + N' ' - + STUFF(( - SELECT DISTINCT N', ' - + CASE WHEN spi2.variable_name <> N'**no_variable**' AND spi2.compile_time_value <> N'**idk_man**' - THEN spi2.variable_name + N' = ' - ELSE @nl + N' We could not find any cached parameter values for this stored proc. ' - END - + CASE WHEN spi2.variable_name = N'**no_variable**' OR spi2.compile_time_value = N'**idk_man**' - THEN @nl + N'More info on possible reasons: https://www.brentozar.com/go/noplans ' - WHEN spi2.compile_time_value = N'NULL' - THEN spi2.compile_time_value - ELSE RTRIM(spi2.compile_time_value) - END - FROM #stored_proc_info AS spi2 - WHERE spi.SqlHandle = spi2.SqlHandle - AND spi2.proc_name <> N'Statement' - FOR XML PATH(N''), TYPE).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 1, N'') - AS [processing-instruction(ClickMe)] FOR XML PATH(''), TYPE ) - AS cached_execution_parameters -FROM #stored_proc_info AS spi -GROUP BY spi.SPID, spi.SqlHandle, spi.proc_name, spi.set_options -) -UPDATE b -SET b.cached_execution_parameters = pk.cached_execution_parameters -FROM ##BlitzCacheProcs AS b -JOIN precheck pk -ON pk.SqlHandle = b.SqlHandle -AND pk.SPID = b.SPID -WHERE b.QueryType <> N'Statement' -OPTION (RECOMPILE); + IF @v >= 14 OR (@v = 13 AND @build >= 5026) + BEGIN + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + SELECT + @@SPID, + 997, + 200, + 'Database Level Statistics', + 'The database ' + sa.[Database] + ' last had a stats update on ' + CONVERT(NVARCHAR(10), CONVERT(DATE, MAX(sa.LastUpdate))) + ' and has ' + CONVERT(NVARCHAR(10), AVG(sa.ModificationCount)) + ' modifications on average.' AS [Finding], + 'https://www.brentozar.com/blitzcache/stale-statistics/' AS URL, + 'Consider updating statistics more frequently,' AS [Details] + FROM #stats_agg AS sa + GROUP BY sa.[Database] + HAVING MAX(sa.LastUpdate) <= DATEADD(DAY, -7, SYSDATETIME()) + AND AVG(sa.ModificationCount) >= 100000; -RAISERROR(N'Updating cached parameter XML for statements', 0, 1) WITH NOWAIT; -WITH precheck AS ( -SELECT spi.SPID, - spi.SqlHandle, - spi.proc_name, - (SELECT - set_options - + @nl - + @nl - + N' See QueryText column for full query text' - + @nl - + @nl - + STUFF(( - SELECT DISTINCT N', ' - + CASE WHEN spi2.variable_name <> N'**no_variable**' AND spi2.compile_time_value <> N'**idk_man**' - THEN spi2.variable_name + N' = ' - ELSE @nl + N' We could not find any cached parameter values for this stored proc. ' - END - + CASE WHEN spi2.variable_name = N'**no_variable**' OR spi2.compile_time_value = N'**idk_man**' - THEN @nl + N' More info on possible reasons: https://www.brentozar.com/go/noplans ' - WHEN spi2.compile_time_value = N'NULL' - THEN spi2.compile_time_value - ELSE RTRIM(spi2.compile_time_value) - END - FROM #stored_proc_info AS spi2 - WHERE spi.SqlHandle = spi2.SqlHandle - AND spi2.proc_name = N'Statement' - AND spi2.variable_name NOT LIKE N'%msparam%' - FOR XML PATH(N''), TYPE).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 1, N'') - AS [processing-instruction(ClickMe)] FOR XML PATH(''), TYPE ) - AS cached_execution_parameters -FROM #stored_proc_info AS spi -GROUP BY spi.SPID, spi.SqlHandle, spi.proc_name, spi.set_options -) -UPDATE b -SET b.cached_execution_parameters = pk.cached_execution_parameters -FROM ##BlitzCacheProcs AS b -JOIN precheck pk -ON pk.SqlHandle = b.SqlHandle -AND pk.SPID = b.SPID -WHERE b.QueryType = N'Statement' -OPTION (RECOMPILE); + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs p + WHERE p.is_row_goal = 1 + ) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 58, + 200, + 'Complexity', + 'Row Goals', + 'https://www.brentozar.com/go/rowgoals/', + 'This query had row goals introduced, which can be good or bad, and should be investigated for high read queries.') ; -RAISERROR(N'Filling in implicit conversion and cached plan parameter info', 0, 1) WITH NOWAIT; -UPDATE b -SET b.implicit_conversion_info = CASE WHEN b.implicit_conversion_info IS NULL - OR CONVERT(NVARCHAR(MAX), b.implicit_conversion_info) = N'' - THEN '' - ELSE b.implicit_conversion_info END, - b.cached_execution_parameters = CASE WHEN b.cached_execution_parameters IS NULL - OR CONVERT(NVARCHAR(MAX), b.cached_execution_parameters) = N'' - THEN '' - ELSE b.cached_execution_parameters END -FROM ##BlitzCacheProcs AS b -WHERE b.SPID = @@SPID -OPTION (RECOMPILE); + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs p + WHERE p.is_big_spills = 1 + ) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 59, + 100, + 'TempDB', + '>500mb Spills', + 'https://www.brentozar.com/blitzcache/tempdb-spills/', + 'This query spills >500mb to tempdb on average. One way or another, this query didn''t get enough memory') ; -/*End implicit conversion and parameter info*/ - -/*Begin Missing Index*/ -IF EXISTS ( SELECT 1/0 - FROM ##BlitzCacheProcs AS bbcp - WHERE bbcp.missing_index_count > 0 - OR bbcp.index_spool_cost > 0 - OR bbcp.index_spool_rows > 0 - AND bbcp.SPID = @@SPID ) - - BEGIN - RAISERROR(N'Inserting to #missing_index_xml', 0, 1) WITH NOWAIT; - WITH XMLNAMESPACES ( 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) - INSERT #missing_index_xml - SELECT qp.QueryHash, - qp.SqlHandle, - c.mg.value('@Impact', 'FLOAT') AS Impact, - c.mg.query('.') AS cmg - FROM #query_plan AS qp - CROSS APPLY qp.query_plan.nodes('//p:MissingIndexes/p:MissingIndexGroup') AS c(mg) - WHERE qp.QueryHash IS NOT NULL - OPTION(RECOMPILE); - - RAISERROR(N'Inserting to #missing_index_schema', 0, 1) WITH NOWAIT; - WITH XMLNAMESPACES ( 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) - INSERT #missing_index_schema - SELECT mix.QueryHash, mix.SqlHandle, mix.impact, - c.mi.value('@Database', 'NVARCHAR(128)'), - c.mi.value('@Schema', 'NVARCHAR(128)'), - c.mi.value('@Table', 'NVARCHAR(128)'), - c.mi.query('.') - FROM #missing_index_xml AS mix - CROSS APPLY mix.index_xml.nodes('//p:MissingIndex') AS c(mi) - OPTION(RECOMPILE); - - RAISERROR(N'Inserting to #missing_index_usage', 0, 1) WITH NOWAIT; - WITH XMLNAMESPACES ( 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) - INSERT #missing_index_usage - SELECT ms.QueryHash, ms.SqlHandle, ms.impact, ms.database_name, ms.schema_name, ms.table_name, - c.cg.value('@Usage', 'NVARCHAR(128)'), - c.cg.query('.') - FROM #missing_index_schema ms - CROSS APPLY ms.index_xml.nodes('//p:ColumnGroup') AS c(cg) - OPTION(RECOMPILE); - - RAISERROR(N'Inserting to #missing_index_detail', 0, 1) WITH NOWAIT; - WITH XMLNAMESPACES ( 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) - INSERT #missing_index_detail - SELECT miu.QueryHash, - miu.SqlHandle, - miu.impact, - miu.database_name, - miu.schema_name, - miu.table_name, - miu.usage, - c.c.value('@Name', 'NVARCHAR(128)') - FROM #missing_index_usage AS miu - CROSS APPLY miu.index_xml.nodes('//p:Column') AS c(c) - OPTION (RECOMPILE); - - RAISERROR(N'Inserting to missing indexes to #missing_index_pretty', 0, 1) WITH NOWAIT; - INSERT #missing_index_pretty - ( QueryHash, SqlHandle, impact, database_name, schema_name, table_name, equality, inequality, include, executions, query_cost, creation_hours, is_spool ) - SELECT DISTINCT m.QueryHash, m.SqlHandle, m.impact, m.database_name, m.schema_name, m.table_name - , STUFF(( SELECT DISTINCT N', ' + ISNULL(m2.column_name, '') AS column_name - FROM #missing_index_detail AS m2 - WHERE m2.usage = 'EQUALITY' - AND m.QueryHash = m2.QueryHash - AND m.SqlHandle = m2.SqlHandle - AND m.impact = m2.impact - AND m.database_name = m2.database_name - AND m.schema_name = m2.schema_name - AND m.table_name = m2.table_name - FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 2, N'') AS equality - , STUFF(( SELECT DISTINCT N', ' + ISNULL(m2.column_name, '') AS column_name - FROM #missing_index_detail AS m2 - WHERE m2.usage = 'INEQUALITY' - AND m.QueryHash = m2.QueryHash - AND m.SqlHandle = m2.SqlHandle - AND m.impact = m2.impact - AND m.database_name = m2.database_name - AND m.schema_name = m2.schema_name - AND m.table_name = m2.table_name - FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 2, N'') AS inequality - , STUFF(( SELECT DISTINCT N', ' + ISNULL(m2.column_name, '') AS column_name - FROM #missing_index_detail AS m2 - WHERE m2.usage = 'INCLUDE' - AND m.QueryHash = m2.QueryHash - AND m.SqlHandle = m2.SqlHandle - AND m.impact = m2.impact - AND m.database_name = m2.database_name - AND m.schema_name = m2.schema_name - AND m.table_name = m2.table_name - FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 2, N'') AS [include], - bbcp.ExecutionCount, - bbcp.QueryPlanCost, - bbcp.PlanCreationTimeHours, - 0 as is_spool - FROM #missing_index_detail AS m - JOIN ##BlitzCacheProcs AS bbcp - ON m.SqlHandle = bbcp.SqlHandle - AND m.QueryHash = bbcp.QueryHash - OPTION (RECOMPILE); - - RAISERROR(N'Inserting to #index_spool_ugly', 0, 1) WITH NOWAIT; - WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) - INSERT #index_spool_ugly - (QueryHash, SqlHandle, impact, database_name, schema_name, table_name, equality, inequality, include, executions, query_cost, creation_hours) - SELECT p.QueryHash, - p.SqlHandle, - (c.n.value('@EstimateIO', 'FLOAT') + (c.n.value('@EstimateCPU', 'FLOAT'))) - / ( 1 * NULLIF(p.QueryPlanCost, 0)) * 100 AS impact, - o.n.value('@Database', 'NVARCHAR(128)') AS output_database, - o.n.value('@Schema', 'NVARCHAR(128)') AS output_schema, - o.n.value('@Table', 'NVARCHAR(128)') AS output_table, - k.n.value('@Column', 'NVARCHAR(128)') AS range_column, - e.n.value('@Column', 'NVARCHAR(128)') AS expression_column, - o.n.value('@Column', 'NVARCHAR(128)') AS output_column, - p.ExecutionCount, - p.QueryPlanCost, - p.PlanCreationTimeHours - FROM #relop AS r - JOIN ##BlitzCacheProcs p - ON p.QueryHash = r.QueryHash - CROSS APPLY r.relop.nodes('/p:RelOp') AS c(n) - CROSS APPLY r.relop.nodes('/p:RelOp/p:OutputList/p:ColumnReference') AS o(n) - OUTER APPLY r.relop.nodes('/p:RelOp/p:Spool/p:SeekPredicateNew/p:SeekKeys/p:Prefix/p:RangeColumns/p:ColumnReference') AS k(n) - OUTER APPLY r.relop.nodes('/p:RelOp/p:Spool/p:SeekPredicateNew/p:SeekKeys/p:Prefix/p:RangeExpressions/p:ColumnReference') AS e(n) - WHERE r.relop.exist('/p:RelOp[@PhysicalOp="Index Spool" and @LogicalOp="Eager Spool"]') = 1 - - RAISERROR(N'Inserting to spools to #missing_index_pretty', 0, 1) WITH NOWAIT; - INSERT #missing_index_pretty - (QueryHash, SqlHandle, impact, database_name, schema_name, table_name, equality, inequality, include, executions, query_cost, creation_hours, is_spool) - SELECT DISTINCT - isu.QueryHash, - isu.SqlHandle, - isu.impact, - isu.database_name, - isu.schema_name, - isu.table_name - , STUFF(( SELECT DISTINCT N', ' + ISNULL(isu2.equality, '') AS column_name - FROM #index_spool_ugly AS isu2 - WHERE isu2.equality IS NOT NULL - AND isu.QueryHash = isu2.QueryHash - AND isu.SqlHandle = isu2.SqlHandle - AND isu.impact = isu2.impact - AND isu.database_name = isu2.database_name - AND isu.schema_name = isu2.schema_name - AND isu.table_name = isu2.table_name - FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 2, N'') AS equality - , STUFF(( SELECT DISTINCT N', ' + ISNULL(isu2.inequality, '') AS column_name - FROM #index_spool_ugly AS isu2 - WHERE isu2.inequality IS NOT NULL - AND isu.QueryHash = isu2.QueryHash - AND isu.SqlHandle = isu2.SqlHandle - AND isu.impact = isu2.impact - AND isu.database_name = isu2.database_name - AND isu.schema_name = isu2.schema_name - AND isu.table_name = isu2.table_name - FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 2, N'') AS inequality - , STUFF(( SELECT DISTINCT N', ' + ISNULL(isu2.include, '') AS column_name - FROM #index_spool_ugly AS isu2 - WHERE isu2.include IS NOT NULL - AND isu.QueryHash = isu2.QueryHash - AND isu.SqlHandle = isu2.SqlHandle - AND isu.impact = isu2.impact - AND isu.database_name = isu2.database_name - AND isu.schema_name = isu2.schema_name - AND isu.table_name = isu2.table_name - FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 2, N'') AS include, - isu.executions, - isu.query_cost, - isu.creation_hours, - 1 AS is_spool - FROM #index_spool_ugly AS isu + END; - RAISERROR(N'Updating missing index information', 0, 1) WITH NOWAIT; - WITH missing AS ( - SELECT DISTINCT - mip.QueryHash, - mip.SqlHandle, - mip.executions, - N'' - AS full_details - FROM #missing_index_pretty AS mip - ) - UPDATE bbcp - SET bbcp.missing_indexes = m.full_details - FROM ##BlitzCacheProcs AS bbcp - JOIN missing AS m - ON m.SqlHandle = bbcp.SqlHandle - AND m.QueryHash = bbcp.QueryHash - AND m.executions = bbcp.ExecutionCount - AND SPID = @@SPID - OPTION (RECOMPILE); + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs p + WHERE p.is_mstvf = 1 + ) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 60, + 100, + 'Functions', + 'MSTVFs', + 'https://www.brentozar.com/blitzcache/tvf-join/', + 'Execution plans have been found that join to table valued functions (TVFs). TVFs produce inaccurate estimates of the number of rows returned and can lead to any number of query plan problems.'); - END; + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs p + WHERE p.is_mm_join = 1 + ) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 61, + 100, + 'Complexity', + 'Many to Many Merge', + 'https://www.brentozar.com/archive/2018/04/many-mysteries-merge-joins/', + 'These use secret worktables that could be doing lots of reads. Occurs when join inputs aren''t known to be unique. Can be really bad when parallel.'); - RAISERROR(N'Filling in missing index blanks', 0, 1) WITH NOWAIT; - UPDATE b - SET b.missing_indexes = - CASE WHEN b.missing_indexes IS NULL - THEN '' - ELSE b.missing_indexes - END - FROM ##BlitzCacheProcs AS b - WHERE b.SPID = @@SPID - OPTION (RECOMPILE); + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs p + WHERE p.is_nonsargable = 1 + ) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 62, + 50, + 'Non-SARGable queries', + 'non-SARGables', + 'https://www.brentozar.com/blitzcache/non-sargable-predicates/', + 'Looks for intrinsic functions and expressions as predicates, and leading wildcard LIKE searches.'); -/*End Missing Index*/ + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs p + WHERE CompileTime > 5000 + ) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 63, + 100, + 'Complexity', + 'Long Compile Time', + 'https://www.brentozar.com/blitzcache/high-compilers/', + 'Queries are taking >5 seconds to compile. This can be normal for large plans, but be careful if they compile frequently'); + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs p + WHERE CompileCPU > 5000 + ) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 64, + 50, + 'Complexity', + 'High Compile CPU', + 'https://www.brentozar.com/blitzcache/high-compilers/', + 'Queries taking >5 seconds of CPU to compile. If CPU is high and plans like this compile frequently, they may be related'); -/* Set configuration values */ -RAISERROR(N'Setting configuration values', 0, 1) WITH NOWAIT; -DECLARE @execution_threshold INT = 1000 , - @parameter_sniffing_warning_pct TINYINT = 30, - /* This is in average reads */ - @parameter_sniffing_io_threshold BIGINT = 100000 , - @ctp_threshold_pct TINYINT = 10, - @long_running_query_warning_seconds BIGINT = 300 * 1000 , - @memory_grant_warning_percent INT = 10; + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs p + WHERE CompileMemory > 1024 + AND ((CompileMemory) / (1 * CASE WHEN MaxCompileMemory = 0 THEN 1 ELSE MaxCompileMemory END) * 100.) >= 10. + ) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 65, + 50, + 'Complexity', + 'High Compile Memory', + 'https://www.brentozar.com/blitzcache/high-compilers/', + 'Queries taking 10% of Max Compile Memory. If you see high RESOURCE_SEMAPHORE_QUERY_COMPILE waits, these may be related'); -IF EXISTS (SELECT 1/0 FROM #configuration WHERE 'frequent execution threshold' = LOWER(parameter_name)) -BEGIN - SELECT @execution_threshold = CAST(value AS INT) - FROM #configuration - WHERE 'frequent execution threshold' = LOWER(parameter_name) ; + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs p + WHERE p.select_with_writes = 1 + ) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 66, + 50, + 'Complexity', + 'Selects w/ Writes', + 'https://dba.stackexchange.com/questions/191825/', + 'This is thrown when reads cause writes that are not already flagged as big spills (2016+) or index spools.'); - SET @msg = ' Setting "frequent execution threshold" to ' + CAST(@execution_threshold AS VARCHAR(10)) ; + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs p + WHERE p.is_table_spool_expensive = 1 + ) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 67, + 150, + 'Expensive Table Spool', + 'You have a table spool, this is usually a sign that queries are doing unnecessary work', + 'https://sqlperformance.com/2019/09/sql-performance/nested-loops-joins-performance-spools', + 'Check for non-SARGable predicates, or a lot of work being done inside a nested loops join') ; - RAISERROR(@msg, 0, 1) WITH NOWAIT; -END; + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs p + WHERE p.is_table_spool_more_rows = 1 + ) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 68, + 150, + 'Table Spools Many Rows', + 'You have a table spool that spools more rows than the query returns', + 'https://sqlperformance.com/2019/09/sql-performance/nested-loops-joins-performance-spools', + 'Check for non-SARGable predicates, or a lot of work being done inside a nested loops join'); -IF EXISTS (SELECT 1/0 FROM #configuration WHERE 'parameter sniffing variance percent' = LOWER(parameter_name)) -BEGIN - SELECT @parameter_sniffing_warning_pct = CAST(value AS TINYINT) - FROM #configuration - WHERE 'parameter sniffing variance percent' = LOWER(parameter_name) ; + IF EXISTS (SELECT 1/0 + FROM #plan_creation p + WHERE (p.percent_24 > 0) + AND SPID = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + SELECT SPID, + 999, + CASE WHEN ISNULL(p.percent_24, 0) > 75 THEN 1 ELSE 254 END AS Priority, + 'Plan Cache Information', + CASE WHEN ISNULL(p.percent_24, 0) > 75 THEN 'Plan Cache Instability' ELSE 'Plan Cache Stability' END AS Finding, + 'https://www.brentozar.com/archive/2018/07/tsql2sday-how-much-plan-cache-history-do-you-have/', + 'You have ' + CONVERT(NVARCHAR(10), ISNULL(p.total_plans, 0)) + + ' total plans in your cache, with ' + + CONVERT(NVARCHAR(10), ISNULL(p.percent_24, 0)) + + '% plans created in the past 24 hours, ' + + CONVERT(NVARCHAR(10), ISNULL(p.percent_4, 0)) + + '% created in the past 4 hours, and ' + + CONVERT(NVARCHAR(10), ISNULL(p.percent_1, 0)) + + '% created in the past 1 hour. ' + + 'When these percentages are high, it may be a sign of memory pressure or plan cache instability.' + FROM #plan_creation p ; - SET @msg = ' Setting "parameter sniffing variance percent" to ' + CAST(@parameter_sniffing_warning_pct AS VARCHAR(3)) ; + IF EXISTS (SELECT 1/0 + FROM #plan_usage p + WHERE p.percent_duplicate > 5 + AND spid = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + SELECT spid, + 999, + CASE WHEN ISNULL(p.percent_duplicate, 0) > 75 THEN 1 ELSE 254 END AS Priority, + 'Plan Cache Information', + CASE WHEN ISNULL(p.percent_duplicate, 0) > 75 THEN 'Many Duplicate Plans' ELSE 'Duplicate Plans' END AS Finding, + 'https://www.brentozar.com/archive/2018/03/why-multiple-plans-for-one-query-are-bad/', + 'You have ' + CONVERT(NVARCHAR(10), p.total_plans) + + ' plans in your cache, and ' + + CONVERT(NVARCHAR(10), p.percent_duplicate) + + '% are duplicates with more than 5 entries' + + ', meaning similar queries are generating the same plan repeatedly.' + + ' Forced Parameterization may fix the issue. To find troublemakers, use: EXEC sp_BlitzCache @SortOrder = ''query hash''; ' + FROM #plan_usage AS p ; - RAISERROR(@msg, 0, 1) WITH NOWAIT; -END; + IF EXISTS (SELECT 1/0 + FROM #plan_usage p + WHERE p.percent_single > 5 + AND spid = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + SELECT spid, + 999, + CASE WHEN ISNULL(p.percent_single, 0) > 75 THEN 1 ELSE 254 END AS Priority, + 'Plan Cache Information', + CASE WHEN ISNULL(p.percent_single, 0) > 75 THEN 'Many Single-Use Plans' ELSE 'Single-Use Plans' END AS Finding, + 'https://www.brentozar.com/blitz/single-use-plans-procedure-cache/', + 'You have ' + CONVERT(NVARCHAR(10), p.total_plans) + + ' plans in your cache, and ' + + CONVERT(NVARCHAR(10), p.percent_single) + + '% are single use plans' + + ', meaning SQL Server thinks it''s seeing a lot of "new" queries and creating plans for them.' + + ' Forced Parameterization and/or Optimize For Ad Hoc Workloads may fix the issue.' + + 'To find troublemakers, use: EXEC sp_BlitzCache @SortOrder = ''query hash''; ' + FROM #plan_usage AS p ; -IF EXISTS (SELECT 1/0 FROM #configuration WHERE 'parameter sniffing io threshold' = LOWER(parameter_name)) -BEGIN - SELECT @parameter_sniffing_io_threshold = CAST(value AS BIGINT) - FROM #configuration - WHERE 'parameter sniffing io threshold' = LOWER(parameter_name) ; + IF @is_tokenstore_big = 1 + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + SELECT @@SPID, + 69, + 10, + N'Large USERSTORE_TOKENPERM cache: ' + CONVERT(NVARCHAR(11), @user_perm_gb_out) + N'GB', + N'The USERSTORE_TOKENPERM is taking up ' + CONVERT(NVARCHAR(11), @user_perm_percent) + + N'% of the buffer pool, and your plan cache seems to be unstable', + N'https://www.brentozar.com/go/userstore', + N'A growing USERSTORE_TOKENPERM cache can cause the plan cache to clear out' - SET @msg = ' Setting "parameter sniffing io threshold" to ' + CAST(@parameter_sniffing_io_threshold AS VARCHAR(10)); + IF @v >= 11 + BEGIN + IF EXISTS (SELECT 1/0 + FROM #trace_flags AS tf + WHERE tf.global_trace_flags IS NOT NULL + ) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 1000, + 255, + 'Global Trace Flags Enabled', + 'You have Global Trace Flags enabled on your server', + 'https://www.brentozar.com/blitz/trace-flags-enabled-globally/', + 'You have the following Global Trace Flags enabled: ' + (SELECT TOP 1 tf.global_trace_flags FROM #trace_flags AS tf WHERE tf.global_trace_flags IS NOT NULL)) ; + END; - RAISERROR(@msg, 0, 1) WITH NOWAIT; -END; + IF NOT EXISTS (SELECT 1/0 + FROM ##BlitzCacheResults AS bcr + WHERE bcr.Priority = 2147483646 + ) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 2147483646, + 255, + 'Need more help?' , + 'Paste your plan on the internet!', + 'http://pastetheplan.com', + 'This makes it easy to share plans and post them to Q&A sites like https://dba.stackexchange.com/!') ; -IF EXISTS (SELECT 1/0 FROM #configuration WHERE 'cost threshold for parallelism warning' = LOWER(parameter_name)) -BEGIN - SELECT @ctp_threshold_pct = CAST(value AS TINYINT) - FROM #configuration - WHERE 'cost threshold for parallelism warning' = LOWER(parameter_name) ; - SET @msg = ' Setting "cost threshold for parallelism warning" to ' + CAST(@ctp_threshold_pct AS VARCHAR(3)); - RAISERROR(@msg, 0, 1) WITH NOWAIT; + IF NOT EXISTS (SELECT 1/0 + FROM ##BlitzCacheResults AS bcr + WHERE bcr.Priority = 2147483647 + ) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 2147483647, + 255, + 'Thanks for using sp_BlitzCache!' , + 'From Your Community Volunteers', + 'http://FirstResponderKit.org', + 'We hope you found this tool useful. Current version: ' + @Version + ' released on ' + CONVERT(NVARCHAR(30), @VersionDate) + '.') ; + + END; + + + SELECT Priority, + FindingsGroup, + Finding, + URL, + Details, + CheckID + FROM ##BlitzCacheResults + WHERE SPID = @@SPID + GROUP BY Priority, + FindingsGroup, + Finding, + URL, + Details, + CheckID + ORDER BY Priority ASC, FindingsGroup, Finding, CheckID ASC + OPTION (RECOMPILE); END; -IF EXISTS (SELECT 1/0 FROM #configuration WHERE 'long running query warning (seconds)' = LOWER(parameter_name)) -BEGIN - SELECT @long_running_query_warning_seconds = CAST(value * 1000 AS BIGINT) - FROM #configuration - WHERE 'long running query warning (seconds)' = LOWER(parameter_name) ; - - SET @msg = ' Setting "long running query warning (seconds)" to ' + CAST(@long_running_query_warning_seconds AS VARCHAR(10)); +IF @Debug = 1 + BEGIN + + SELECT '##BlitzCacheResults' AS table_name, * + FROM ##BlitzCacheResults + OPTION ( RECOMPILE ); + + SELECT '##BlitzCacheProcs' AS table_name, * + FROM ##BlitzCacheProcs + OPTION ( RECOMPILE ); + + SELECT '#statements' AS table_name, * + FROM #statements AS s + OPTION (RECOMPILE); - RAISERROR(@msg, 0, 1) WITH NOWAIT; -END; + SELECT '#query_plan' AS table_name, * + FROM #query_plan AS qp + OPTION (RECOMPILE); + + SELECT '#relop' AS table_name, * + FROM #relop AS r + OPTION (RECOMPILE); -IF EXISTS (SELECT 1/0 FROM #configuration WHERE 'unused memory grant' = LOWER(parameter_name)) -BEGIN - SELECT @memory_grant_warning_percent = CAST(value AS INT) - FROM #configuration - WHERE 'unused memory grant' = LOWER(parameter_name) ; + SELECT '#only_query_hashes' AS table_name, * + FROM #only_query_hashes + OPTION ( RECOMPILE ); + + SELECT '#ignore_query_hashes' AS table_name, * + FROM #ignore_query_hashes + OPTION ( RECOMPILE ); + + SELECT '#only_sql_handles' AS table_name, * + FROM #only_sql_handles + OPTION ( RECOMPILE ); + + SELECT '#ignore_sql_handles' AS table_name, * + FROM #ignore_sql_handles + OPTION ( RECOMPILE ); + + SELECT '#p' AS table_name, * + FROM #p + OPTION ( RECOMPILE ); + + SELECT '#checkversion' AS table_name, * + FROM #checkversion + OPTION ( RECOMPILE ); + + SELECT '#configuration' AS table_name, * + FROM #configuration + OPTION ( RECOMPILE ); + + SELECT '#stored_proc_info' AS table_name, * + FROM #stored_proc_info + OPTION ( RECOMPILE ); - SET @msg = ' Setting "unused memory grant" to ' + CAST(@memory_grant_warning_percent AS VARCHAR(10)); + SELECT '#conversion_info' AS table_name, * + FROM #conversion_info AS ci + OPTION ( RECOMPILE ); + + SELECT '#variable_info' AS table_name, * + FROM #variable_info AS vi + OPTION ( RECOMPILE ); - RAISERROR(@msg, 0, 1) WITH NOWAIT; -END; + SELECT '#missing_index_xml' AS table_name, * + FROM #missing_index_xml AS mix + OPTION ( RECOMPILE ); -DECLARE @ctp INT ; + SELECT '#missing_index_schema' AS table_name, * + FROM #missing_index_schema AS mis + OPTION ( RECOMPILE ); -SELECT @ctp = NULLIF(CAST(value AS INT), 0) -FROM sys.configurations -WHERE name = 'cost threshold for parallelism' -OPTION (RECOMPILE); + SELECT '#missing_index_usage' AS table_name, * + FROM #missing_index_usage AS miu + OPTION ( RECOMPILE ); + SELECT '#missing_index_detail' AS table_name, * + FROM #missing_index_detail AS mid + OPTION ( RECOMPILE ); -/* Update to populate checks columns */ -RAISERROR('Checking for query level SQL Server issues.', 0, 1) WITH NOWAIT; + SELECT '#missing_index_pretty' AS table_name, * + FROM #missing_index_pretty AS mip + OPTION ( RECOMPILE ); -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE ##BlitzCacheProcs -SET frequent_execution = CASE WHEN ExecutionsPerMinute > @execution_threshold THEN 1 END , - parameter_sniffing = CASE WHEN ExecutionCount > 3 AND AverageReads > @parameter_sniffing_io_threshold - AND min_worker_time < ((1.0 - (@parameter_sniffing_warning_pct / 100.0)) * AverageCPU) THEN 1 - WHEN ExecutionCount > 3 AND AverageReads > @parameter_sniffing_io_threshold - AND max_worker_time > ((1.0 + (@parameter_sniffing_warning_pct / 100.0)) * AverageCPU) THEN 1 - WHEN ExecutionCount > 3 AND AverageReads > @parameter_sniffing_io_threshold - AND MinReturnedRows < ((1.0 - (@parameter_sniffing_warning_pct / 100.0)) * AverageReturnedRows) THEN 1 - WHEN ExecutionCount > 3 AND AverageReads > @parameter_sniffing_io_threshold - AND MaxReturnedRows > ((1.0 + (@parameter_sniffing_warning_pct / 100.0)) * AverageReturnedRows) THEN 1 END , - near_parallel = CASE WHEN is_parallel <> 1 AND QueryPlanCost BETWEEN @ctp * (1 - (@ctp_threshold_pct / 100.0)) AND @ctp THEN 1 END, - long_running = CASE WHEN AverageDuration > @long_running_query_warning_seconds THEN 1 - WHEN max_worker_time > @long_running_query_warning_seconds THEN 1 - WHEN max_elapsed_time > @long_running_query_warning_seconds THEN 1 END, - is_key_lookup_expensive = CASE WHEN QueryPlanCost >= (@ctp / 2) AND key_lookup_cost >= QueryPlanCost * .5 THEN 1 END, - is_sort_expensive = CASE WHEN QueryPlanCost >= (@ctp / 2) AND sort_cost >= QueryPlanCost * .5 THEN 1 END, - is_remote_query_expensive = CASE WHEN remote_query_cost >= QueryPlanCost * .05 THEN 1 END, - is_unused_grant = CASE WHEN PercentMemoryGrantUsed <= @memory_grant_warning_percent AND MinGrantKB > @MinMemoryPerQuery THEN 1 END, - long_running_low_cpu = CASE WHEN AverageDuration > AverageCPU * 4 AND AverageCPU < 500. THEN 1 END, - low_cost_high_cpu = CASE WHEN QueryPlanCost <= 10 AND AverageCPU > 5000. THEN 1 END, - is_spool_expensive = CASE WHEN QueryPlanCost > (@ctp / 5) AND index_spool_cost >= QueryPlanCost * .1 THEN 1 END, - is_spool_more_rows = CASE WHEN index_spool_rows >= (AverageReturnedRows / ISNULL(NULLIF(ExecutionCount, 0), 1)) THEN 1 END, - is_table_spool_expensive = CASE WHEN QueryPlanCost > (@ctp / 5) AND table_spool_cost >= QueryPlanCost / 4 THEN 1 END, - is_table_spool_more_rows = CASE WHEN table_spool_rows >= (AverageReturnedRows / ISNULL(NULLIF(ExecutionCount, 0), 1)) THEN 1 END, - is_bad_estimate = CASE WHEN AverageReturnedRows > 0 AND (estimated_rows * 1000 < AverageReturnedRows OR estimated_rows > AverageReturnedRows * 1000) THEN 1 END, - is_big_spills = CASE WHEN (AvgSpills / 128.) > 499. THEN 1 END -WHERE SPID = @@SPID -OPTION (RECOMPILE); + SELECT '#plan_creation' AS table_name, * + FROM #plan_creation + OPTION ( RECOMPILE ); + + SELECT '#plan_cost' AS table_name, * + FROM #plan_cost + OPTION ( RECOMPILE ); + + SELECT '#proc_costs' AS table_name, * + FROM #proc_costs + OPTION ( RECOMPILE ); + + SELECT '#stats_agg' AS table_name, * + FROM #stats_agg + OPTION ( RECOMPILE ); + + SELECT '#trace_flags' AS table_name, * + FROM #trace_flags + OPTION ( RECOMPILE ); + SELECT '#plan_usage' AS table_name, * + FROM #plan_usage + OPTION ( RECOMPILE ); + END; -RAISERROR('Checking for forced parameterization and cursors.', 0, 1) WITH NOWAIT; + IF @OutputTableName IS NOT NULL + --Allow for output to ##DB so don't check for DB or schema name here + GOTO OutputResultsToTable; +RETURN; --Avoid going into the AllSort GOTO -/* Set options checks */ -UPDATE p - SET is_forced_parameterized = CASE WHEN (CAST(pa.value AS INT) & 131072 = 131072) THEN 1 END , - is_forced_plan = CASE WHEN (CAST(pa.value AS INT) & 4 = 4) THEN 1 END , - SetOptions = SUBSTRING( - CASE WHEN (CAST(pa.value AS INT) & 1 = 1) THEN ', ANSI_PADDING' ELSE '' END + - CASE WHEN (CAST(pa.value AS INT) & 8 = 8) THEN ', CONCAT_NULL_YIELDS_NULL' ELSE '' END + - CASE WHEN (CAST(pa.value AS INT) & 16 = 16) THEN ', ANSI_WARNINGS' ELSE '' END + - CASE WHEN (CAST(pa.value AS INT) & 32 = 32) THEN ', ANSI_NULLS' ELSE '' END + - CASE WHEN (CAST(pa.value AS INT) & 64 = 64) THEN ', QUOTED_IDENTIFIER' ELSE '' END + - CASE WHEN (CAST(pa.value AS INT) & 4096 = 4096) THEN ', ARITH_ABORT' ELSE '' END + - CASE WHEN (CAST(pa.value AS INT) & 8192 = 8191) THEN ', NUMERIC_ROUNDABORT' ELSE '' END - , 2, 200000) -FROM ##BlitzCacheProcs p - CROSS APPLY sys.dm_exec_plan_attributes(p.PlanHandle) pa -WHERE pa.attribute = 'set_options' -AND SPID = @@SPID -OPTION (RECOMPILE); +/*Begin code to sort by all*/ +AllSorts: +RAISERROR('Beginning all sort loop', 0, 1) WITH NOWAIT; -/* Cursor checks */ -UPDATE p -SET is_cursor = CASE WHEN CAST(pa.value AS INT) <> 0 THEN 1 END -FROM ##BlitzCacheProcs p - CROSS APPLY sys.dm_exec_plan_attributes(p.PlanHandle) pa -WHERE pa.attribute LIKE '%cursor%' -AND SPID = @@SPID -OPTION (RECOMPILE); +IF ( + @Top > 10 + AND @SkipAnalysis = 0 + AND @BringThePain = 0 + ) + BEGIN + RAISERROR( + ' + You''ve chosen a value greater than 10 to sort the whole plan cache by. + That can take a long time and harm performance. + Please choose a number <= 10, or set @BringThePain = 1 to signify you understand this might be a bad idea. + ', 0, 1) WITH NOWAIT; + RETURN; + END; -UPDATE p -SET is_cursor = 1 -FROM ##BlitzCacheProcs p -WHERE QueryHash = 0x0000000000000000 -OR QueryPlanHash = 0x0000000000000000 -AND SPID = @@SPID -OPTION (RECOMPILE); +IF OBJECT_ID('tempdb..#checkversion_allsort') IS NULL + BEGIN + CREATE TABLE #checkversion_allsort + ( + version NVARCHAR(128), + common_version AS SUBSTRING(version, 1, CHARINDEX('.', version) + 1), + major AS PARSENAME(CONVERT(VARCHAR(32), version), 4), + minor AS PARSENAME(CONVERT(VARCHAR(32), version), 3), + build AS PARSENAME(CONVERT(VARCHAR(32), version), 2), + revision AS PARSENAME(CONVERT(VARCHAR(32), version), 1) + ); + INSERT INTO #checkversion_allsort + (version) + SELECT CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)) + OPTION ( RECOMPILE ); + END; -RAISERROR('Populating Warnings column', 0, 1) WITH NOWAIT; -/* Populate warnings */ -UPDATE ##BlitzCacheProcs -SET Warnings = SUBSTRING( - CASE WHEN warning_no_join_predicate = 1 THEN ', No Join Predicate' ELSE '' END + - CASE WHEN compile_timeout = 1 THEN ', Compilation Timeout' ELSE '' END + - CASE WHEN compile_memory_limit_exceeded = 1 THEN ', Compile Memory Limit Exceeded' ELSE '' END + - CASE WHEN busy_loops = 1 THEN ', Busy Loops' ELSE '' END + - CASE WHEN is_forced_plan = 1 THEN ', Forced Plan' ELSE '' END + - CASE WHEN is_forced_parameterized = 1 THEN ', Forced Parameterization' ELSE '' END + - CASE WHEN unparameterized_query = 1 THEN ', Unparameterized Query' ELSE '' END + - CASE WHEN missing_index_count > 0 THEN ', Missing Indexes (' + CAST(missing_index_count AS VARCHAR(3)) + ')' ELSE '' END + - CASE WHEN unmatched_index_count > 0 THEN ', Unmatched Indexes (' + CAST(unmatched_index_count AS VARCHAR(3)) + ')' ELSE '' END + - CASE WHEN is_cursor = 1 THEN ', Cursor' - + CASE WHEN is_optimistic_cursor = 1 THEN '; optimistic' ELSE '' END - + CASE WHEN is_forward_only_cursor = 0 THEN '; not forward only' ELSE '' END - + CASE WHEN is_cursor_dynamic = 1 THEN '; dynamic' ELSE '' END - + CASE WHEN is_fast_forward_cursor = 1 THEN '; fast forward' ELSE '' END - ELSE '' END + - CASE WHEN is_parallel = 1 THEN ', Parallel' ELSE '' END + - CASE WHEN near_parallel = 1 THEN ', Nearly Parallel' ELSE '' END + - CASE WHEN frequent_execution = 1 THEN ', Frequent Execution' ELSE '' END + - CASE WHEN plan_warnings = 1 THEN ', Plan Warnings' ELSE '' END + - CASE WHEN parameter_sniffing = 1 THEN ', Parameter Sniffing' ELSE '' END + - CASE WHEN long_running = 1 THEN ', Long Running Query' ELSE '' END + - CASE WHEN downlevel_estimator = 1 THEN ', Downlevel CE' ELSE '' END + - CASE WHEN implicit_conversions = 1 THEN ', Implicit Conversions' ELSE '' END + - CASE WHEN tvf_join = 1 THEN ', Function Join' ELSE '' END + - CASE WHEN plan_multiple_plans > 0 THEN ', Multiple Plans' + COALESCE(' (' + CAST(plan_multiple_plans AS VARCHAR(10)) + ')', '') ELSE '' END + - CASE WHEN is_trivial = 1 THEN ', Trivial Plans' ELSE '' END + - CASE WHEN is_forced_serial = 1 THEN ', Forced Serialization' ELSE '' END + - CASE WHEN is_key_lookup_expensive = 1 THEN ', Expensive Key Lookup' ELSE '' END + - CASE WHEN is_remote_query_expensive = 1 THEN ', Expensive Remote Query' ELSE '' END + - CASE WHEN trace_flags_session IS NOT NULL THEN ', Session Level Trace Flag(s) Enabled: ' + trace_flags_session ELSE '' END + - CASE WHEN is_unused_grant = 1 THEN ', Unused Memory Grant' ELSE '' END + - CASE WHEN function_count > 0 THEN ', Calls ' + CONVERT(VARCHAR(10), function_count) + ' Function(s)' ELSE '' END + - CASE WHEN clr_function_count > 0 THEN ', Calls ' + CONVERT(VARCHAR(10), clr_function_count) + ' CLR Function(s)' ELSE '' END + - CASE WHEN PlanCreationTimeHours <= 4 THEN ', Plan created last 4hrs' ELSE '' END + - CASE WHEN is_table_variable = 1 THEN ', Table Variables' ELSE '' END + - CASE WHEN no_stats_warning = 1 THEN ', Columns With No Statistics' ELSE '' END + - CASE WHEN relop_warnings = 1 THEN ', Operator Warnings' ELSE '' END + - CASE WHEN is_table_scan = 1 THEN ', Table Scans (Heaps)' ELSE '' END + - CASE WHEN backwards_scan = 1 THEN ', Backwards Scans' ELSE '' END + - CASE WHEN forced_index = 1 THEN ', Forced Indexes' ELSE '' END + - CASE WHEN forced_seek = 1 THEN ', Forced Seeks' ELSE '' END + - CASE WHEN forced_scan = 1 THEN ', Forced Scans' ELSE '' END + - CASE WHEN columnstore_row_mode = 1 THEN ', ColumnStore Row Mode ' ELSE '' END + - CASE WHEN is_computed_scalar = 1 THEN ', Computed Column UDF ' ELSE '' END + - CASE WHEN is_sort_expensive = 1 THEN ', Expensive Sort' ELSE '' END + - CASE WHEN is_computed_filter = 1 THEN ', Filter UDF' ELSE '' END + - CASE WHEN index_ops >= 5 THEN ', >= 5 Indexes Modified' ELSE '' END + - CASE WHEN is_row_level = 1 THEN ', Row Level Security' ELSE '' END + - CASE WHEN is_spatial = 1 THEN ', Spatial Index' ELSE '' END + - CASE WHEN index_dml = 1 THEN ', Index DML' ELSE '' END + - CASE WHEN table_dml = 1 THEN ', Table DML' ELSE '' END + - CASE WHEN low_cost_high_cpu = 1 THEN ', Low Cost High CPU' ELSE '' END + - CASE WHEN long_running_low_cpu = 1 THEN + ', Long Running With Low CPU' ELSE '' END + - CASE WHEN stale_stats = 1 THEN + ', Statistics used have > 100k modifications in the last 7 days' ELSE '' END + - CASE WHEN is_adaptive = 1 THEN + ', Adaptive Joins' ELSE '' END + - CASE WHEN is_spool_expensive = 1 THEN + ', Expensive Index Spool' ELSE '' END + - CASE WHEN is_spool_more_rows = 1 THEN + ', Large Index Row Spool' ELSE '' END + - CASE WHEN is_table_spool_expensive = 1 THEN + ', Expensive Table Spool' ELSE '' END + - CASE WHEN is_table_spool_more_rows = 1 THEN + ', Many Rows Table Spool' ELSE '' END + - CASE WHEN is_bad_estimate = 1 THEN + ', Row Estimate Mismatch' ELSE '' END + - CASE WHEN is_paul_white_electric = 1 THEN ', SWITCH!' ELSE '' END + - CASE WHEN is_row_goal = 1 THEN ', Row Goals' ELSE '' END + - CASE WHEN is_big_spills = 1 THEN ', >500mb Spills' ELSE '' END + - CASE WHEN is_mstvf = 1 THEN ', MSTVFs' ELSE '' END + - CASE WHEN is_mm_join = 1 THEN ', Many to Many Merge' ELSE '' END + - CASE WHEN is_nonsargable = 1 THEN ', non-SARGables' ELSE '' END + - CASE WHEN CompileTime > 5000 THEN ', Long Compile Time' ELSE '' END + - CASE WHEN CompileCPU > 5000 THEN ', High Compile CPU' ELSE '' END + - CASE WHEN CompileMemory > 1024 AND ((CompileMemory) / (1 * CASE WHEN MaxCompileMemory = 0 THEN 1 ELSE MaxCompileMemory END) * 100.) >= 10. THEN ', High Compile Memory' ELSE '' END + - CASE WHEN select_with_writes > 0 THEN ', Select w/ Writes' ELSE '' END - , 3, 200000) -WHERE SPID = @@SPID -OPTION (RECOMPILE); +SELECT @v = common_version, + @build = build +FROM #checkversion_allsort +OPTION ( RECOMPILE ); -RAISERROR('Populating Warnings column for stored procedures', 0, 1) WITH NOWAIT; -WITH statement_warnings AS - ( -SELECT DISTINCT - SqlHandle, - Warnings = SUBSTRING( - CASE WHEN warning_no_join_predicate = 1 THEN ', No Join Predicate' ELSE '' END + - CASE WHEN compile_timeout = 1 THEN ', Compilation Timeout' ELSE '' END + - CASE WHEN compile_memory_limit_exceeded = 1 THEN ', Compile Memory Limit Exceeded' ELSE '' END + - CASE WHEN busy_loops = 1 THEN ', Busy Loops' ELSE '' END + - CASE WHEN is_forced_plan = 1 THEN ', Forced Plan' ELSE '' END + - CASE WHEN is_forced_parameterized = 1 THEN ', Forced Parameterization' ELSE '' END + - --CASE WHEN unparameterized_query = 1 THEN ', Unparameterized Query' ELSE '' END + - CASE WHEN missing_index_count > 0 THEN ', Missing Indexes (' + CONVERT(VARCHAR(10), (SELECT SUM(b2.missing_index_count) FROM ##BlitzCacheProcs AS b2 WHERE b2.SqlHandle = b.SqlHandle AND b2.QueryHash IS NOT NULL AND SPID = @@SPID) ) + ')' ELSE '' END + - CASE WHEN unmatched_index_count > 0 THEN ', Unmatched Indexes (' + CONVERT(VARCHAR(10), (SELECT SUM(b2.unmatched_index_count) FROM ##BlitzCacheProcs AS b2 WHERE b2.SqlHandle = b.SqlHandle AND b2.QueryHash IS NOT NULL AND SPID = @@SPID) ) + ')' ELSE '' END + - CASE WHEN is_cursor = 1 THEN ', Cursor' - + CASE WHEN is_optimistic_cursor = 1 THEN '; optimistic' ELSE '' END - + CASE WHEN is_forward_only_cursor = 0 THEN '; not forward only' ELSE '' END - + CASE WHEN is_cursor_dynamic = 1 THEN '; dynamic' ELSE '' END - + CASE WHEN is_fast_forward_cursor = 1 THEN '; fast forward' ELSE '' END - ELSE '' END + - CASE WHEN is_parallel = 1 THEN ', Parallel' ELSE '' END + - CASE WHEN near_parallel = 1 THEN ', Nearly Parallel' ELSE '' END + - CASE WHEN frequent_execution = 1 THEN ', Frequent Execution' ELSE '' END + - CASE WHEN plan_warnings = 1 THEN ', Plan Warnings' ELSE '' END + - CASE WHEN parameter_sniffing = 1 THEN ', Parameter Sniffing' ELSE '' END + - CASE WHEN long_running = 1 THEN ', Long Running Query' ELSE '' END + - CASE WHEN downlevel_estimator = 1 THEN ', Downlevel CE' ELSE '' END + - CASE WHEN implicit_conversions = 1 THEN ', Implicit Conversions' ELSE '' END + - CASE WHEN tvf_join = 1 THEN ', Function Join' ELSE '' END + - CASE WHEN plan_multiple_plans > 0 THEN ', Multiple Plans' + COALESCE(' (' + CAST(plan_multiple_plans AS VARCHAR(10)) + ')', '') ELSE '' END + - CASE WHEN is_trivial = 1 THEN ', Trivial Plans' ELSE '' END + - CASE WHEN is_forced_serial = 1 THEN ', Forced Serialization' ELSE '' END + - CASE WHEN is_key_lookup_expensive = 1 THEN ', Expensive Key Lookup' ELSE '' END + - CASE WHEN is_remote_query_expensive = 1 THEN ', Expensive Remote Query' ELSE '' END + - CASE WHEN trace_flags_session IS NOT NULL THEN ', Session Level Trace Flag(s) Enabled: ' + trace_flags_session ELSE '' END + - CASE WHEN is_unused_grant = 1 THEN ', Unused Memory Grant' ELSE '' END + - CASE WHEN function_count > 0 THEN ', Calls ' + CONVERT(VARCHAR(10), (SELECT SUM(b2.function_count) FROM ##BlitzCacheProcs AS b2 WHERE b2.SqlHandle = b.SqlHandle AND b2.QueryHash IS NOT NULL AND SPID = @@SPID) ) + ' Function(s)' ELSE '' END + - CASE WHEN clr_function_count > 0 THEN ', Calls ' + CONVERT(VARCHAR(10), (SELECT SUM(b2.clr_function_count) FROM ##BlitzCacheProcs AS b2 WHERE b2.SqlHandle = b.SqlHandle AND b2.QueryHash IS NOT NULL AND SPID = @@SPID) ) + ' CLR Function(s)' ELSE '' END + - CASE WHEN PlanCreationTimeHours <= 4 THEN ', Plan created last 4hrs' ELSE '' END + - CASE WHEN is_table_variable = 1 THEN ', Table Variables' ELSE '' END + - CASE WHEN no_stats_warning = 1 THEN ', Columns With No Statistics' ELSE '' END + - CASE WHEN relop_warnings = 1 THEN ', Operator Warnings' ELSE '' END + - CASE WHEN is_table_scan = 1 THEN ', Table Scans' ELSE '' END + - CASE WHEN backwards_scan = 1 THEN ', Backwards Scans' ELSE '' END + - CASE WHEN forced_index = 1 THEN ', Forced Indexes' ELSE '' END + - CASE WHEN forced_seek = 1 THEN ', Forced Seeks' ELSE '' END + - CASE WHEN forced_scan = 1 THEN ', Forced Scans' ELSE '' END + - CASE WHEN columnstore_row_mode = 1 THEN ', ColumnStore Row Mode ' ELSE '' END + - CASE WHEN is_computed_scalar = 1 THEN ', Computed Column UDF ' ELSE '' END + - CASE WHEN is_sort_expensive = 1 THEN ', Expensive Sort' ELSE '' END + - CASE WHEN is_computed_filter = 1 THEN ', Filter UDF' ELSE '' END + - CASE WHEN index_ops >= 5 THEN ', >= 5 Indexes Modified' ELSE '' END + - CASE WHEN is_row_level = 1 THEN ', Row Level Security' ELSE '' END + - CASE WHEN is_spatial = 1 THEN ', Spatial Index' ELSE '' END + - CASE WHEN index_dml = 1 THEN ', Index DML' ELSE '' END + - CASE WHEN table_dml = 1 THEN ', Table DML' ELSE '' END + - CASE WHEN low_cost_high_cpu = 1 THEN ', Low Cost High CPU' ELSE '' END + - CASE WHEN long_running_low_cpu = 1 THEN + ', Long Running With Low CPU' ELSE '' END + - CASE WHEN stale_stats = 1 THEN + ', Statistics used have > 100k modifications in the last 7 days' ELSE '' END + - CASE WHEN is_adaptive = 1 THEN + ', Adaptive Joins' ELSE '' END + - CASE WHEN is_spool_expensive = 1 THEN + ', Expensive Index Spool' ELSE '' END + - CASE WHEN is_spool_more_rows = 1 THEN + ', Large Index Row Spool' ELSE '' END + - CASE WHEN is_table_spool_expensive = 1 THEN + ', Expensive Table Spool' ELSE '' END + - CASE WHEN is_table_spool_more_rows = 1 THEN + ', Many Rows Table Spool' ELSE '' END + - CASE WHEN is_bad_estimate = 1 THEN + ', Row Estimate Mismatch' ELSE '' END + - CASE WHEN is_paul_white_electric = 1 THEN ', SWITCH!' ELSE '' END + - CASE WHEN is_row_goal = 1 THEN ', Row Goals' ELSE '' END + - CASE WHEN is_big_spills = 1 THEN ', >500mb spills' ELSE '' END + - CASE WHEN is_mstvf = 1 THEN ', MSTVFs' ELSE '' END + - CASE WHEN is_mm_join = 1 THEN ', Many to Many Merge' ELSE '' END + - CASE WHEN is_nonsargable = 1 THEN ', non-SARGables' ELSE '' END + - CASE WHEN CompileTime > 5000 THEN ', Long Compile Time' ELSE '' END + - CASE WHEN CompileCPU > 5000 THEN ', High Compile CPU' ELSE '' END + - CASE WHEN CompileMemory > 1024 AND ((CompileMemory) / (1 * CASE WHEN MaxCompileMemory = 0 THEN 1 ELSE MaxCompileMemory END) * 100.) >= 10. THEN ', High Compile Memory' ELSE '' END + - CASE WHEN select_with_writes > 0 THEN ', Select w/ Writes' ELSE '' END - , 3, 200000) -FROM ##BlitzCacheProcs b -WHERE SPID = @@SPID -AND QueryType LIKE 'Statement (parent%' - ) -UPDATE b -SET b.Warnings = s.Warnings -FROM ##BlitzCacheProcs AS b -JOIN statement_warnings s -ON b.SqlHandle = s.SqlHandle -WHERE QueryType LIKE 'Procedure or Function%' -AND SPID = @@SPID -OPTION (RECOMPILE); +IF OBJECT_ID('tempdb.. #bou_allsort') IS NULL + BEGIN + CREATE TABLE #bou_allsort + ( + Id INT IDENTITY(1, 1), + DatabaseName NVARCHAR(128), + Cost FLOAT, + QueryText NVARCHAR(MAX), + QueryType NVARCHAR(258), + Warnings VARCHAR(MAX), + QueryPlan XML, + missing_indexes XML, + implicit_conversion_info XML, + cached_execution_parameters XML, + ExecutionCount NVARCHAR(30), + ExecutionsPerMinute MONEY, + ExecutionWeight MONEY, + TotalCPU NVARCHAR(30), + AverageCPU NVARCHAR(30), + CPUWeight MONEY, + TotalDuration NVARCHAR(30), + AverageDuration NVARCHAR(30), + DurationWeight MONEY, + TotalReads NVARCHAR(30), + AverageReads NVARCHAR(30), + ReadWeight MONEY, + TotalWrites NVARCHAR(30), + AverageWrites NVARCHAR(30), + WriteWeight MONEY, + AverageReturnedRows MONEY, + MinGrantKB NVARCHAR(30), + MaxGrantKB NVARCHAR(30), + MinUsedGrantKB NVARCHAR(30), + MaxUsedGrantKB NVARCHAR(30), + AvgMaxMemoryGrant MONEY, + MinSpills NVARCHAR(30), + MaxSpills NVARCHAR(30), + TotalSpills NVARCHAR(30), + AvgSpills MONEY, + PlanCreationTime DATETIME, + LastExecutionTime DATETIME, + LastCompletionTime DATETIME, + PlanHandle VARBINARY(64), + SqlHandle VARBINARY(64), + SetOptions VARCHAR(MAX), + QueryHash BINARY(8), + PlanGenerationNum NVARCHAR(30), + RemovePlanHandleFromCache NVARCHAR(200), + Pattern NVARCHAR(20) + ); + END; -RAISERROR('Checking for plans with >128 levels of nesting', 0, 1) WITH NOWAIT; -WITH plan_handle AS ( -SELECT b.PlanHandle -FROM ##BlitzCacheProcs b - CROSS APPLY sys.dm_exec_text_query_plan(b.PlanHandle, 0, -1) tqp - CROSS APPLY sys.dm_exec_query_plan(b.PlanHandle) qp - WHERE tqp.encrypted = 0 - AND b.SPID = @@SPID - AND (qp.query_plan IS NULL - AND tqp.query_plan IS NOT NULL) -) -UPDATE b -SET Warnings = ISNULL('Your query plan is >128 levels of nested nodes, and can''t be converted to XML. Use SELECT * FROM sys.dm_exec_text_query_plan('+ CONVERT(VARCHAR(128), ph.PlanHandle, 1) + ', 0, -1) to get more information' - , 'We couldn''t find a plan for this query. More info on possible reasons: https://www.brentozar.com/go/noplans') -FROM ##BlitzCacheProcs b -LEFT JOIN plan_handle ph ON -b.PlanHandle = ph.PlanHandle -WHERE b.QueryPlan IS NULL -AND b.SPID = @@SPID -OPTION (RECOMPILE); -RAISERROR('Checking for plans with no warnings', 0, 1) WITH NOWAIT; -UPDATE ##BlitzCacheProcs -SET Warnings = 'No warnings detected. ' + CASE @ExpertMode - WHEN 0 - THEN ' Try running sp_BlitzCache with @ExpertMode = 1 to find more advanced problems.' - ELSE '' - END -WHERE Warnings = '' OR Warnings IS NULL -AND SPID = @@SPID -OPTION (RECOMPILE); +IF @SortOrder = 'all' +BEGIN +RAISERROR('Beginning for ALL', 0, 1) WITH NOWAIT; +SET @AllSortSql += N' + DECLARE @ISH NVARCHAR(MAX) = N'''' + INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, + TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, + ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, + MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) + + EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''cpu'', + @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; + + UPDATE #bou_allsort SET Pattern = ''cpu'' WHERE Pattern IS NULL OPTION(RECOMPILE); -Results: -IF @ExportToExcel = 1 -BEGIN - RAISERROR('Displaying results with Excel formatting (no plans).', 0, 1) WITH NOWAIT; + SELECT TOP 1 @ISH = STUFF((SELECT DISTINCT N'','' + CONVERT(NVARCHAR(MAX),b2.SqlHandle, 1) FROM #bou_allsort AS b2 FOR XML PATH(N''''), TYPE).value(N''.[1]'', N''NVARCHAR(MAX)''), 1, 1, N'''') OPTION(RECOMPILE); - /* excel output */ - UPDATE ##BlitzCacheProcs - SET QueryText = SUBSTRING(REPLACE(REPLACE(REPLACE(LTRIM(RTRIM(QueryText)),' ','<>'),'><',''),'<>',' '), 1, 32000) - OPTION(RECOMPILE); + INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, + TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, + ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, + MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) + + EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''reads'', @IgnoreSqlHandles = @ISH, + @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; + + UPDATE #bou_allsort SET Pattern = ''reads'' WHERE Pattern IS NULL OPTION(RECOMPILE); - SET @sql = N' - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT TOP (@Top) - DatabaseName AS [Database Name], - QueryPlanCost AS [Cost], - QueryText, - QueryType AS [Query Type], - Warnings, - ExecutionCount, - ExecutionsPerMinute AS [Executions / Minute], - PercentExecutions AS [Execution Weight], - PercentExecutionsByType AS [% Executions (Type)], - SerialDesiredMemory AS [Serial Desired Memory], - SerialRequiredMemory AS [Serial Required Memory], - TotalCPU AS [Total CPU (ms)], - AverageCPU AS [Avg CPU (ms)], - PercentCPU AS [CPU Weight], - PercentCPUByType AS [% CPU (Type)], - TotalDuration AS [Total Duration (ms)], - AverageDuration AS [Avg Duration (ms)], - PercentDuration AS [Duration Weight], - PercentDurationByType AS [% Duration (Type)], - TotalReads AS [Total Reads], - AverageReads AS [Average Reads], - PercentReads AS [Read Weight], - PercentReadsByType AS [% Reads (Type)], - TotalWrites AS [Total Writes], - AverageWrites AS [Average Writes], - PercentWrites AS [Write Weight], - PercentWritesByType AS [% Writes (Type)], - TotalReturnedRows, - AverageReturnedRows, - MinReturnedRows, - MaxReturnedRows, - MinGrantKB, - MaxGrantKB, - MinUsedGrantKB, - MaxUsedGrantKB, - PercentMemoryGrantUsed, - AvgMaxMemoryGrant, - MinSpills, - MaxSpills, - TotalSpills, - AvgSpills, - NumberOfPlans, - NumberOfDistinctPlans, - PlanCreationTime AS [Created At], - LastExecutionTime AS [Last Execution], - StatementStartOffset, - StatementEndOffset, - PlanGenerationNum, - PlanHandle AS [Plan Handle], - SqlHandle AS [SQL Handle], - QueryHash, - QueryPlanHash, - COALESCE(SetOptions, '''') AS [SET Options] - FROM ##BlitzCacheProcs - WHERE 1 = 1 - AND SPID = @@SPID ' + @nl; + SELECT TOP 1 @ISH = STUFF((SELECT DISTINCT N'','' + CONVERT(NVARCHAR(MAX),b2.SqlHandle, 1) FROM #bou_allsort AS b2 FOR XML PATH(N''''), TYPE).value(N''.[1]'', N''NVARCHAR(MAX)''), 1, 1, N'''') OPTION(RECOMPILE); - IF @MinimumExecutionCount IS NOT NULL - BEGIN - SET @sql += N' AND ExecutionCount >= @MinimumExecutionCount '; - END; - - IF @MinutesBack IS NOT NULL - BEGIN - SET @sql += N' AND LastCompletionTime >= DATEADD(MINUTE, @min_back, GETDATE() ) '; - END; + INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, + TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, + ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, + MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) + + EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''writes'', @IgnoreSqlHandles = @ISH, + @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; + + UPDATE #bou_allsort SET Pattern = ''writes'' WHERE Pattern IS NULL OPTION(RECOMPILE); - SELECT @sql += N' ORDER BY ' + CASE @SortOrder WHEN N'cpu' THEN N' TotalCPU ' - WHEN N'reads' THEN N' TotalReads ' - WHEN N'writes' THEN N' TotalWrites ' - WHEN N'duration' THEN N' TotalDuration ' - WHEN N'executions' THEN N' ExecutionCount ' - WHEN N'compiles' THEN N' PlanCreationTime ' - WHEN N'memory grant' THEN N' MaxGrantKB' - WHEN N'unused grant' THEN N' MaxGrantKB - MaxUsedGrantKB' - WHEN N'spills' THEN N' MaxSpills' - WHEN N'duplicate' THEN N' plan_multiple_plans ' /* Issue #3345 */ - WHEN N'avg cpu' THEN N' AverageCPU' - WHEN N'avg reads' THEN N' AverageReads' - WHEN N'avg writes' THEN N' AverageWrites' - WHEN N'avg duration' THEN N' AverageDuration' - WHEN N'avg executions' THEN N' ExecutionsPerMinute' - WHEN N'avg memory grant' THEN N' AvgMaxMemoryGrant' - WHEN N'avg spills' THEN N' AvgSpills' - END + N' DESC '; + SELECT TOP 1 @ISH = STUFF((SELECT DISTINCT N'','' + CONVERT(NVARCHAR(MAX),b2.SqlHandle, 1) FROM #bou_allsort AS b2 FOR XML PATH(N''''), TYPE).value(N''.[1]'', N''NVARCHAR(MAX)''), 1, 1, N'''') OPTION(RECOMPILE); - SET @sql += N' OPTION (RECOMPILE) ; '; + INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, + TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, + ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, + MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) + + EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''duration'', @IgnoreSqlHandles = @ISH, + @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; + + UPDATE #bou_allsort SET Pattern = ''duration'' WHERE Pattern IS NULL OPTION(RECOMPILE); - IF @sql IS NULL - BEGIN - RAISERROR('@sql is null, which means dynamic SQL generation went terribly wrong', 0, 1) WITH NOWAIT; - END + SELECT TOP 1 @ISH = STUFF((SELECT DISTINCT N'','' + CONVERT(NVARCHAR(MAX),b2.SqlHandle, 1) FROM #bou_allsort AS b2 FOR XML PATH(N''''), TYPE).value(N''.[1]'', N''NVARCHAR(MAX)''), 1, 1, N'''') OPTION(RECOMPILE); - IF @Debug = 1 - BEGIN - RAISERROR('Printing @sql, the dynamic SQL we generated:', 0, 1) WITH NOWAIT; - PRINT SUBSTRING(@sql, 0, 4000); - PRINT SUBSTRING(@sql, 4000, 8000); - PRINT SUBSTRING(@sql, 8000, 12000); - PRINT SUBSTRING(@sql, 12000, 16000); - PRINT SUBSTRING(@sql, 16000, 20000); - PRINT SUBSTRING(@sql, 20000, 24000); - PRINT SUBSTRING(@sql, 24000, 28000); - PRINT SUBSTRING(@sql, 28000, 32000); - PRINT SUBSTRING(@sql, 32000, 36000); - PRINT SUBSTRING(@sql, 36000, 40000); - END; - - EXEC sp_executesql @sql, N'@Top INT, @min_duration INT, @min_back INT, @MinimumExecutionCount INT', @Top, @DurationFilter_i, @MinutesBack, @MinimumExecutionCount; -END; - - -RAISERROR('Displaying analysis of plan cache.', 0, 1) WITH NOWAIT; + INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, + TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, + ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, + MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) + + EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''executions'', @IgnoreSqlHandles = @ISH, + @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; + + UPDATE #bou_allsort SET Pattern = ''executions'' WHERE Pattern IS NULL OPTION(RECOMPILE); + + '; + + IF @VersionShowsMemoryGrants = 0 + BEGIN + IF @ExportToExcel = 1 + BEGIN + SET @AllSortSql += N' UPDATE #bou_allsort + SET + QueryPlan = NULL, + implicit_conversion_info = NULL, + cached_execution_parameters = NULL, + missing_indexes = NULL + OPTION (RECOMPILE); -DECLARE @columns NVARCHAR(MAX) = N'' ; + UPDATE ##BlitzCacheProcs + SET QueryText = SUBSTRING(REPLACE(REPLACE(REPLACE(LTRIM(RTRIM(QueryText)),'' '',''<>''),''><'',''''),''<>'','' ''), 1, 32000) + OPTION(RECOMPILE);'; + END; -IF @ExpertMode = 0 -BEGIN - RAISERROR(N'Returning ExpertMode = 0', 0, 1) WITH NOWAIT; - SET @columns = N' DatabaseName AS [Database], - QueryPlanCost AS [Cost], - QueryText AS [Query Text], - QueryType AS [Query Type], - Warnings AS [Warnings], - QueryPlan AS [Query Plan], - missing_indexes AS [Missing Indexes], - implicit_conversion_info AS [Implicit Conversion Info], - cached_execution_parameters AS [Cached Execution Parameters], - CONVERT(NVARCHAR(30), CAST((ExecutionCount) AS BIGINT), 1) AS [# Executions], - CONVERT(NVARCHAR(30), CAST((ExecutionsPerMinute) AS BIGINT), 1) AS [Executions / Minute], - CONVERT(NVARCHAR(30), CAST((PercentExecutions) AS BIGINT), 1) AS [Execution Weight], - CONVERT(NVARCHAR(30), CAST((TotalCPU) AS BIGINT), 1) AS [Total CPU (ms)], - CONVERT(NVARCHAR(30), CAST((AverageCPU) AS BIGINT), 1) AS [Avg CPU (ms)], - CONVERT(NVARCHAR(30), CAST((PercentCPU) AS BIGINT), 1) AS [CPU Weight], - CONVERT(NVARCHAR(30), CAST((TotalDuration) AS BIGINT), 1) AS [Total Duration (ms)], - CONVERT(NVARCHAR(30), CAST((AverageDuration) AS BIGINT), 1) AS [Avg Duration (ms)], - CONVERT(NVARCHAR(30), CAST((PercentDuration) AS BIGINT), 1) AS [Duration Weight], - CONVERT(NVARCHAR(30), CAST((TotalReads) AS BIGINT), 1) AS [Total Reads], - CONVERT(NVARCHAR(30), CAST((AverageReads) AS BIGINT), 1) AS [Avg Reads], - CONVERT(NVARCHAR(30), CAST((PercentReads) AS BIGINT), 1) AS [Read Weight], - CONVERT(NVARCHAR(30), CAST((TotalWrites) AS BIGINT), 1) AS [Total Writes], - CONVERT(NVARCHAR(30), CAST((AverageWrites) AS BIGINT), 1) AS [Avg Writes], - CONVERT(NVARCHAR(30), CAST((PercentWrites) AS BIGINT), 1) AS [Write Weight], - CONVERT(NVARCHAR(30), CAST((AverageReturnedRows) AS BIGINT), 1) AS [Average Rows], - CONVERT(NVARCHAR(30), CAST((MinGrantKB) AS BIGINT), 1) AS [Minimum Memory Grant KB], - CONVERT(NVARCHAR(30), CAST((MaxGrantKB) AS BIGINT), 1) AS [Maximum Memory Grant KB], - CONVERT(NVARCHAR(30), CAST((MinUsedGrantKB) AS BIGINT), 1) AS [Minimum Used Grant KB], - CONVERT(NVARCHAR(30), CAST((MaxUsedGrantKB) AS BIGINT), 1) AS [Maximum Used Grant KB], - CONVERT(NVARCHAR(30), CAST((AvgMaxMemoryGrant) AS BIGINT), 1) AS [Average Max Memory Grant], - CONVERT(NVARCHAR(30), CAST((MinSpills) AS BIGINT), 1) AS [Min Spills], - CONVERT(NVARCHAR(30), CAST((MaxSpills) AS BIGINT), 1) AS [Max Spills], - CONVERT(NVARCHAR(30), CAST((TotalSpills) AS BIGINT), 1) AS [Total Spills], - CONVERT(NVARCHAR(30), CAST((AvgSpills) AS MONEY), 1) AS [Avg Spills], - PlanCreationTime AS [Created At], - LastExecutionTime AS [Last Execution], - LastCompletionTime AS [Last Completion], - PlanHandle AS [Plan Handle], - SqlHandle AS [SQL Handle], - COALESCE(SetOptions, '''') AS [SET Options], - QueryHash AS [Query Hash], - PlanGenerationNum, - [Remove Plan Handle From Cache]'; -END; -ELSE -BEGIN - SET @columns = N' DatabaseName AS [Database], - QueryPlanCost AS [Cost], - QueryText AS [Query Text], - QueryType AS [Query Type], - Warnings AS [Warnings], - QueryPlan AS [Query Plan], - missing_indexes AS [Missing Indexes], - implicit_conversion_info AS [Implicit Conversion Info], - cached_execution_parameters AS [Cached Execution Parameters], ' + @nl; + END; + + IF @VersionShowsMemoryGrants = 1 + BEGIN + SET @AllSortSql += N' SELECT TOP 1 @ISH = STUFF((SELECT DISTINCT N'','' + CONVERT(NVARCHAR(MAX),b2.SqlHandle, 1) FROM #bou_allsort AS b2 FOR XML PATH(N''''), TYPE).value(N''.[1]'', N''NVARCHAR(MAX)''), 1, 1, N'''') OPTION(RECOMPILE); + + INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, + TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, + ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, + MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) + + EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''memory grant'', @IgnoreSqlHandles = @ISH, + @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; + + UPDATE #bou_allsort SET Pattern = ''memory grant'' WHERE Pattern IS NULL OPTION(RECOMPILE);'; + IF @ExportToExcel = 1 + BEGIN + SET @AllSortSql += N' UPDATE #bou_allsort + SET + QueryPlan = NULL, + implicit_conversion_info = NULL, + cached_execution_parameters = NULL, + missing_indexes = NULL + OPTION (RECOMPILE); - IF @ExpertMode = 2 /* Opserver */ - BEGIN - RAISERROR(N'Returning Expert Mode = 2', 0, 1) WITH NOWAIT; - SET @columns += N' - SUBSTRING( - CASE WHEN warning_no_join_predicate = 1 THEN '', 20'' ELSE '''' END + - CASE WHEN compile_timeout = 1 THEN '', 18'' ELSE '''' END + - CASE WHEN compile_memory_limit_exceeded = 1 THEN '', 19'' ELSE '''' END + - CASE WHEN busy_loops = 1 THEN '', 16'' ELSE '''' END + - CASE WHEN is_forced_plan = 1 THEN '', 3'' ELSE '''' END + - CASE WHEN is_forced_parameterized > 0 THEN '', 5'' ELSE '''' END + - CASE WHEN unparameterized_query = 1 THEN '', 23'' ELSE '''' END + - CASE WHEN missing_index_count > 0 THEN '', 10'' ELSE '''' END + - CASE WHEN unmatched_index_count > 0 THEN '', 22'' ELSE '''' END + - CASE WHEN is_cursor = 1 THEN '', 4'' ELSE '''' END + - CASE WHEN is_parallel = 1 THEN '', 6'' ELSE '''' END + - CASE WHEN near_parallel = 1 THEN '', 7'' ELSE '''' END + - CASE WHEN frequent_execution = 1 THEN '', 1'' ELSE '''' END + - CASE WHEN plan_warnings = 1 THEN '', 8'' ELSE '''' END + - CASE WHEN parameter_sniffing = 1 THEN '', 2'' ELSE '''' END + - CASE WHEN long_running = 1 THEN '', 9'' ELSE '''' END + - CASE WHEN downlevel_estimator = 1 THEN '', 13'' ELSE '''' END + - CASE WHEN implicit_conversions = 1 THEN '', 14'' ELSE '''' END + - CASE WHEN tvf_join = 1 THEN '', 17'' ELSE '''' END + - CASE WHEN plan_multiple_plans > 0 THEN '', 21'' ELSE '''' END + - CASE WHEN unmatched_index_count > 0 THEN '', 22'' ELSE '''' END + - CASE WHEN is_trivial = 1 THEN '', 24'' ELSE '''' END + - CASE WHEN is_forced_serial = 1 THEN '', 25'' ELSE '''' END + - CASE WHEN is_key_lookup_expensive = 1 THEN '', 26'' ELSE '''' END + - CASE WHEN is_remote_query_expensive = 1 THEN '', 28'' ELSE '''' END + - CASE WHEN trace_flags_session IS NOT NULL THEN '', 29'' ELSE '''' END + - CASE WHEN is_unused_grant = 1 THEN '', 30'' ELSE '''' END + - CASE WHEN function_count > 0 THEN '', 31'' ELSE '''' END + - CASE WHEN clr_function_count > 0 THEN '', 32'' ELSE '''' END + - CASE WHEN PlanCreationTimeHours <= 4 THEN '', 33'' ELSE '''' END + - CASE WHEN is_table_variable = 1 THEN '', 34'' ELSE '''' END + - CASE WHEN no_stats_warning = 1 THEN '', 35'' ELSE '''' END + - CASE WHEN relop_warnings = 1 THEN '', 36'' ELSE '''' END + - CASE WHEN is_table_scan = 1 THEN '', 37'' ELSE '''' END + - CASE WHEN backwards_scan = 1 THEN '', 38'' ELSE '''' END + - CASE WHEN forced_index = 1 THEN '', 39'' ELSE '''' END + - CASE WHEN forced_seek = 1 OR forced_scan = 1 THEN '', 40'' ELSE '''' END + - CASE WHEN columnstore_row_mode = 1 THEN '', 41'' ELSE '''' END + - CASE WHEN is_computed_scalar = 1 THEN '', 42'' ELSE '''' END + - CASE WHEN is_sort_expensive = 1 THEN '', 43'' ELSE '''' END + - CASE WHEN is_computed_filter = 1 THEN '', 44'' ELSE '''' END + - CASE WHEN index_ops >= 5 THEN '', 45'' ELSE '''' END + - CASE WHEN is_row_level = 1 THEN '', 46'' ELSE '''' END + - CASE WHEN is_spatial = 1 THEN '', 47'' ELSE '''' END + - CASE WHEN index_dml = 1 THEN '', 48'' ELSE '''' END + - CASE WHEN table_dml = 1 THEN '', 49'' ELSE '''' END + - CASE WHEN long_running_low_cpu = 1 THEN '', 50'' ELSE '''' END + - CASE WHEN low_cost_high_cpu = 1 THEN '', 51'' ELSE '''' END + - CASE WHEN stale_stats = 1 THEN '', 52'' ELSE '''' END + - CASE WHEN is_adaptive = 1 THEN '', 53'' ELSE '''' END + - CASE WHEN is_spool_expensive = 1 THEN + '', 54'' ELSE '''' END + - CASE WHEN is_spool_more_rows = 1 THEN + '', 55'' ELSE '''' END + - CASE WHEN is_table_spool_expensive = 1 THEN + '', 67'' ELSE '''' END + - CASE WHEN is_table_spool_more_rows = 1 THEN + '', 68'' ELSE '''' END + - CASE WHEN is_bad_estimate = 1 THEN + '', 56'' ELSE '''' END + - CASE WHEN is_paul_white_electric = 1 THEN '', 57'' ELSE '''' END + - CASE WHEN is_row_goal = 1 THEN '', 58'' ELSE '''' END + - CASE WHEN is_big_spills = 1 THEN '', 59'' ELSE '''' END + - CASE WHEN is_mstvf = 1 THEN '', 60'' ELSE '''' END + - CASE WHEN is_mm_join = 1 THEN '', 61'' ELSE '''' END + - CASE WHEN is_nonsargable = 1 THEN '', 62'' ELSE '''' END + - CASE WHEN CompileTime > 5000 THEN '', 63 '' ELSE '''' END + - CASE WHEN CompileCPU > 5000 THEN '', 64 '' ELSE '''' END + - CASE WHEN CompileMemory > 1024 AND ((CompileMemory) / (1 * CASE WHEN MaxCompileMemory = 0 THEN 1 ELSE MaxCompileMemory END) * 100.) >= 10. THEN '', 65 '' ELSE '''' END + - CASE WHEN select_with_writes > 0 THEN '', 66'' ELSE '''' END - , 3, 200000) AS opserver_warning , ' + @nl ; - END; - - SET @columns += N' - CONVERT(NVARCHAR(30), CAST((ExecutionCount) AS BIGINT), 1) AS [# Executions], - CONVERT(NVARCHAR(30), CAST((ExecutionsPerMinute) AS BIGINT), 1) AS [Executions / Minute], - CONVERT(NVARCHAR(30), CAST((PercentExecutions) AS BIGINT), 1) AS [Execution Weight], - CONVERT(NVARCHAR(30), CAST((SerialDesiredMemory) AS BIGINT), 1) AS [Serial Desired Memory], - CONVERT(NVARCHAR(30), CAST((SerialRequiredMemory) AS BIGINT), 1) AS [Serial Required Memory], - CONVERT(NVARCHAR(30), CAST((TotalCPU) AS BIGINT), 1) AS [Total CPU (ms)], - CONVERT(NVARCHAR(30), CAST((AverageCPU) AS BIGINT), 1) AS [Avg CPU (ms)], - CONVERT(NVARCHAR(30), CAST((PercentCPU) AS BIGINT), 1) AS [CPU Weight], - CONVERT(NVARCHAR(30), CAST((TotalDuration) AS BIGINT), 1) AS [Total Duration (ms)], - CONVERT(NVARCHAR(30), CAST((AverageDuration) AS BIGINT), 1) AS [Avg Duration (ms)], - CONVERT(NVARCHAR(30), CAST((PercentDuration) AS BIGINT), 1) AS [Duration Weight], - CONVERT(NVARCHAR(30), CAST((TotalReads) AS BIGINT), 1) AS [Total Reads], - CONVERT(NVARCHAR(30), CAST((AverageReads) AS BIGINT), 1) AS [Average Reads], - CONVERT(NVARCHAR(30), CAST((PercentReads) AS BIGINT), 1) AS [Read Weight], - CONVERT(NVARCHAR(30), CAST((TotalWrites) AS BIGINT), 1) AS [Total Writes], - CONVERT(NVARCHAR(30), CAST((AverageWrites) AS BIGINT), 1) AS [Average Writes], - CONVERT(NVARCHAR(30), CAST((PercentWrites) AS BIGINT), 1) AS [Write Weight], - CONVERT(NVARCHAR(30), CAST((PercentExecutionsByType) AS BIGINT), 1) AS [% Executions (Type)], - CONVERT(NVARCHAR(30), CAST((PercentCPUByType) AS BIGINT), 1) AS [% CPU (Type)], - CONVERT(NVARCHAR(30), CAST((PercentDurationByType) AS BIGINT), 1) AS [% Duration (Type)], - CONVERT(NVARCHAR(30), CAST((PercentReadsByType) AS BIGINT), 1) AS [% Reads (Type)], - CONVERT(NVARCHAR(30), CAST((PercentWritesByType) AS BIGINT), 1) AS [% Writes (Type)], - CONVERT(NVARCHAR(30), CAST((TotalReturnedRows) AS BIGINT), 1) AS [Total Rows], - CONVERT(NVARCHAR(30), CAST((AverageReturnedRows) AS BIGINT), 1) AS [Avg Rows], - CONVERT(NVARCHAR(30), CAST((MinReturnedRows) AS BIGINT), 1) AS [Min Rows], - CONVERT(NVARCHAR(30), CAST((MaxReturnedRows) AS BIGINT), 1) AS [Max Rows], - CONVERT(NVARCHAR(30), CAST((MinGrantKB) AS BIGINT), 1) AS [Minimum Memory Grant KB], - CONVERT(NVARCHAR(30), CAST((MaxGrantKB) AS BIGINT), 1) AS [Maximum Memory Grant KB], - CONVERT(NVARCHAR(30), CAST((MinUsedGrantKB) AS BIGINT), 1) AS [Minimum Used Grant KB], - CONVERT(NVARCHAR(30), CAST((MaxUsedGrantKB) AS BIGINT), 1) AS [Maximum Used Grant KB], - CONVERT(NVARCHAR(30), CAST((AvgMaxMemoryGrant) AS BIGINT), 1) AS [Average Max Memory Grant], - CONVERT(NVARCHAR(30), CAST((MinSpills) AS BIGINT), 1) AS [Min Spills], - CONVERT(NVARCHAR(30), CAST((MaxSpills) AS BIGINT), 1) AS [Max Spills], - CONVERT(NVARCHAR(30), CAST((TotalSpills) AS BIGINT), 1) AS [Total Spills], - CONVERT(NVARCHAR(30), CAST((AvgSpills) AS MONEY), 1) AS [Avg Spills], - CONVERT(NVARCHAR(30), CAST((NumberOfPlans) AS BIGINT), 1) AS [# Plans], - CONVERT(NVARCHAR(30), CAST((NumberOfDistinctPlans) AS BIGINT), 1) AS [# Distinct Plans], - PlanCreationTime AS [Created At], - LastExecutionTime AS [Last Execution], - LastCompletionTime AS [Last Completion], - CONVERT(NVARCHAR(30), CAST((CachedPlanSize) AS BIGINT), 1) AS [Cached Plan Size (KB)], - CONVERT(NVARCHAR(30), CAST((CompileTime) AS BIGINT), 1) AS [Compile Time (ms)], - CONVERT(NVARCHAR(30), CAST((CompileCPU) AS BIGINT), 1) AS [Compile CPU (ms)], - CONVERT(NVARCHAR(30), CAST((CompileMemory) AS BIGINT), 1) AS [Compile memory (KB)], - COALESCE(SetOptions, '''') AS [SET Options], - PlanHandle AS [Plan Handle], - SqlHandle AS [SQL Handle], - [SQL Handle More Info], - QueryHash AS [Query Hash], - [Query Hash More Info], - QueryPlanHash AS [Query Plan Hash], - StatementStartOffset, - StatementEndOffset, - PlanGenerationNum, - [Remove Plan Handle From Cache], - [Remove SQL Handle From Cache]'; -END; + UPDATE ##BlitzCacheProcs + SET QueryText = SUBSTRING(REPLACE(REPLACE(REPLACE(LTRIM(RTRIM(QueryText)),'' '',''<>''),''><'',''''),''<>'','' ''), 1, 32000) + OPTION(RECOMPILE);'; + END; -SET @sql = N' -SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT TOP (@Top) ' + @columns + @nl + N' -FROM ##BlitzCacheProcs -WHERE SPID = @spid ' + @nl; + END; -IF @MinimumExecutionCount IS NOT NULL - BEGIN - SET @sql += N' AND ExecutionCount >= @MinimumExecutionCount ' + @nl; - END; + IF @VersionShowsSpills = 0 + BEGIN + IF @ExportToExcel = 1 + BEGIN + SET @AllSortSql += N' UPDATE #bou_allsort + SET + QueryPlan = NULL, + implicit_conversion_info = NULL, + cached_execution_parameters = NULL, + missing_indexes = NULL + OPTION (RECOMPILE); -IF @MinutesBack IS NOT NULL - BEGIN - SET @sql += N' AND LastCompletionTime >= DATEADD(MINUTE, @min_back, GETDATE() ) ' + @nl; - END; + UPDATE ##BlitzCacheProcs + SET QueryText = SUBSTRING(REPLACE(REPLACE(REPLACE(LTRIM(RTRIM(QueryText)),'' '',''<>''),''><'',''''),''<>'','' ''), 1, 32000) + OPTION(RECOMPILE);'; + END; -SELECT @sql += N' ORDER BY ' + CASE @SortOrder WHEN N'cpu' THEN N' TotalCPU ' - WHEN N'reads' THEN N' TotalReads ' - WHEN N'writes' THEN N' TotalWrites ' - WHEN N'duration' THEN N' TotalDuration ' - WHEN N'executions' THEN N' ExecutionCount ' - WHEN N'compiles' THEN N' PlanCreationTime ' - WHEN N'memory grant' THEN N' MaxGrantKB' - WHEN N'unused grant' THEN N' MaxGrantKB - MaxUsedGrantKB ' - WHEN N'duplicate' THEN N' plan_multiple_plans ' - WHEN N'spills' THEN N' MaxSpills ' - WHEN N'avg cpu' THEN N' AverageCPU' - WHEN N'avg reads' THEN N' AverageReads' - WHEN N'avg writes' THEN N' AverageWrites' - WHEN N'avg duration' THEN N' AverageDuration' - WHEN N'avg executions' THEN N' ExecutionsPerMinute' - WHEN N'avg memory grant' THEN N' AvgMaxMemoryGrant' - WHEN N'avg spills' THEN N' AvgSpills' - END + N' DESC '; -SET @sql += N' OPTION (RECOMPILE) ; '; + END; + + IF @VersionShowsSpills = 1 + BEGIN + SET @AllSortSql += N' SELECT TOP 1 @ISH = STUFF((SELECT DISTINCT N'','' + CONVERT(NVARCHAR(MAX),b2.SqlHandle, 1) FROM #bou_allsort AS b2 FOR XML PATH(N''''), TYPE).value(N''.[1]'', N''NVARCHAR(MAX)''), 1, 1, N'''') OPTION(RECOMPILE); + + INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, + TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, + ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, + MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) + + EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''spills'', @IgnoreSqlHandles = @ISH, + @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; + + UPDATE #bou_allsort SET Pattern = ''spills'' WHERE Pattern IS NULL OPTION(RECOMPILE);'; + IF @ExportToExcel = 1 + BEGIN + SET @AllSortSql += N' UPDATE #bou_allsort + SET + QueryPlan = NULL, + implicit_conversion_info = NULL, + cached_execution_parameters = NULL, + missing_indexes = NULL + OPTION (RECOMPILE); -IF @Debug = 1 - BEGIN - PRINT SUBSTRING(@sql, 0, 4000); - PRINT SUBSTRING(@sql, 4000, 8000); - PRINT SUBSTRING(@sql, 8000, 12000); - PRINT SUBSTRING(@sql, 12000, 16000); - PRINT SUBSTRING(@sql, 16000, 20000); - PRINT SUBSTRING(@sql, 20000, 24000); - PRINT SUBSTRING(@sql, 24000, 28000); - PRINT SUBSTRING(@sql, 28000, 32000); - PRINT SUBSTRING(@sql, 32000, 36000); - PRINT SUBSTRING(@sql, 36000, 40000); - END; -IF(@OutputType <> 'NONE') -BEGIN - EXEC sp_executesql @sql, N'@Top INT, @spid INT, @MinimumExecutionCount INT, @min_back INT', @Top, @@SPID, @MinimumExecutionCount, @MinutesBack; -END; + UPDATE ##BlitzCacheProcs + SET QueryText = SUBSTRING(REPLACE(REPLACE(REPLACE(LTRIM(RTRIM(QueryText)),'' '',''<>''),''><'',''''),''<>'','' ''), 1, 32000) + OPTION(RECOMPILE);'; + END; -/* + END; -This section will check if: - * >= 30% of plans were created in the last hour - * Check on the memory_clerks DMV for space used by TokenAndPermUserStore - * Compare that to the size of the buffer pool - * If it's >10%, -*/ -IF EXISTS -( - SELECT 1/0 - FROM #plan_creation AS pc - WHERE pc.percent_1 >= 30 -) -BEGIN + IF(@OutputType <> 'NONE') + BEGIN + SET @AllSortSql += N' SELECT DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters,ExecutionCount, ExecutionsPerMinute, ExecutionWeight, + TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, + ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, + MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache, Pattern + FROM #bou_allsort + ORDER BY Id + OPTION(RECOMPILE); '; + END; +END; -SELECT @common_version = - CONVERT(DECIMAL(10,2), c.common_version) -FROM #checkversion AS c; -IF @common_version >= 11 - SET @user_perm_sql = N' - SET @buffer_pool_memory_gb = 0; - SELECT @buffer_pool_memory_gb = SUM(pages_kb)/ 1024. / 1024. - FROM sys.dm_os_memory_clerks - WHERE type = ''MEMORYCLERK_SQLBUFFERPOOL'';' -ELSE - SET @user_perm_sql = N' - SET @buffer_pool_memory_gb = 0; - SELECT @buffer_pool_memory_gb = SUM(single_pages_kb + multi_pages_kb)/ 1024. / 1024. - FROM sys.dm_os_memory_clerks - WHERE type = ''MEMORYCLERK_SQLBUFFERPOOL'';' +IF @SortOrder = 'all avg' +BEGIN +RAISERROR('Beginning for ALL AVG', 0, 1) WITH NOWAIT; +SET @AllSortSql += N' + DECLARE @ISH NVARCHAR(MAX) = N'''' + + INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, + TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, + ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, + MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) + + EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''avg cpu'', + @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; + + UPDATE #bou_allsort SET Pattern = ''avg cpu'' WHERE Pattern IS NULL OPTION(RECOMPILE); -EXEC sys.sp_executesql @user_perm_sql, - N'@buffer_pool_memory_gb DECIMAL(10,2) OUTPUT', - @buffer_pool_memory_gb = @buffer_pool_memory_gb OUTPUT; + SELECT TOP 1 @ISH = STUFF((SELECT DISTINCT N'','' + CONVERT(NVARCHAR(MAX),b2.SqlHandle, 1) FROM #bou_allsort AS b2 FOR XML PATH(N''''), TYPE).value(N''.[1]'', N''NVARCHAR(MAX)''), 1, 1, N'''') OPTION(RECOMPILE); -IF @common_version >= 11 -BEGIN - SET @user_perm_sql = N' - SELECT @user_perm_gb = CASE WHEN (pages_kb / 1024.0 / 1024.) >= 2. - THEN CONVERT(DECIMAL(38, 2), (pages_kb / 1024.0 / 1024.)) - ELSE 0 - END - FROM sys.dm_os_memory_clerks - WHERE type = ''USERSTORE_TOKENPERM'' - AND name = ''TokenAndPermUserStore'';'; -END; + INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, + TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, + ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, + MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) + + EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''avg reads'', @IgnoreSqlHandles = @ISH, + @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; + + UPDATE #bou_allsort SET Pattern = ''avg reads'' WHERE Pattern IS NULL OPTION(RECOMPILE); -IF @common_version < 11 -BEGIN - SET @user_perm_sql = N' - SELECT @user_perm_gb = CASE WHEN ((single_pages_kb + multi_pages_kb) / 1024.0 / 1024.) >= 2. - THEN CONVERT(DECIMAL(38, 2), ((single_pages_kb + multi_pages_kb) / 1024.0 / 1024.)) - ELSE 0 - END - FROM sys.dm_os_memory_clerks - WHERE type = ''USERSTORE_TOKENPERM'' - AND name = ''TokenAndPermUserStore'';'; -END; + SELECT TOP 1 @ISH = STUFF((SELECT DISTINCT N'','' + CONVERT(NVARCHAR(MAX),b2.SqlHandle, 1) FROM #bou_allsort AS b2 FOR XML PATH(N''''), TYPE).value(N''.[1]'', N''NVARCHAR(MAX)''), 1, 1, N'''') OPTION(RECOMPILE); -EXEC sys.sp_executesql @user_perm_sql, - N'@user_perm_gb DECIMAL(10,2) OUTPUT', - @user_perm_gb = @user_perm_gb_out OUTPUT; + INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, + TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, + ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, + MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) + + EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''avg writes'', @IgnoreSqlHandles = @ISH, + @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; + + UPDATE #bou_allsort SET Pattern = ''avg writes'' WHERE Pattern IS NULL OPTION(RECOMPILE); -IF @buffer_pool_memory_gb > 0 - BEGIN - IF (@user_perm_gb_out / (1. * @buffer_pool_memory_gb)) * 100. >= 10 - BEGIN - SET @is_tokenstore_big = 1; - SET @user_perm_percent = (@user_perm_gb_out / (1. * @buffer_pool_memory_gb)) * 100.; - END - END + SELECT TOP 1 @ISH = STUFF((SELECT DISTINCT N'','' + CONVERT(NVARCHAR(MAX),b2.SqlHandle, 1) FROM #bou_allsort AS b2 FOR XML PATH(N''''), TYPE).value(N''.[1]'', N''NVARCHAR(MAX)''), 1, 1, N'''') OPTION(RECOMPILE); -END + INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, + TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, + ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, + MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) + + EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''avg duration'', @IgnoreSqlHandles = @ISH, + @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; + + UPDATE #bou_allsort SET Pattern = ''avg duration'' WHERE Pattern IS NULL OPTION(RECOMPILE); + SELECT TOP 1 @ISH = STUFF((SELECT DISTINCT N'','' + CONVERT(NVARCHAR(MAX),b2.SqlHandle, 1) FROM #bou_allsort AS b2 FOR XML PATH(N''''), TYPE).value(N''.[1]'', N''NVARCHAR(MAX)''), 1, 1, N'''') OPTION(RECOMPILE); + INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, + TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, + ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, + MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) + + EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''avg executions'', @IgnoreSqlHandles = @ISH, + @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; + + UPDATE #bou_allsort SET Pattern = ''avg executions'' WHERE Pattern IS NULL OPTION(RECOMPILE); + + '; + + IF @VersionShowsMemoryGrants = 0 + BEGIN + IF @ExportToExcel = 1 + BEGIN + SET @AllSortSql += N' UPDATE #bou_allsort + SET + QueryPlan = NULL, + implicit_conversion_info = NULL, + cached_execution_parameters = NULL, + missing_indexes = NULL + OPTION (RECOMPILE); -IF @HideSummary = 0 AND @ExportToExcel = 0 -BEGIN - IF @Reanalyze = 0 - BEGIN - RAISERROR('Building query plan summary data.', 0, 1) WITH NOWAIT; + UPDATE ##BlitzCacheProcs + SET QueryText = SUBSTRING(REPLACE(REPLACE(REPLACE(LTRIM(RTRIM(QueryText)),'' '',''<>''),''><'',''''),''<>'','' ''), 1, 32000) + OPTION(RECOMPILE);'; + END; - /* Build summary data */ - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs - WHERE frequent_execution = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 1, - 100, - 'Execution Pattern', - 'Frequent Execution', - 'https://www.brentozar.com/blitzcache/frequently-executed-queries/', - 'Queries are being executed more than ' - + CAST (@execution_threshold AS VARCHAR(5)) - + ' times per minute. This can put additional load on the server, even when queries are lightweight.') ; + END; + + IF @VersionShowsMemoryGrants = 1 + BEGIN + SET @AllSortSql += N' SELECT TOP 1 @ISH = STUFF((SELECT DISTINCT N'','' + CONVERT(NVARCHAR(MAX),b2.SqlHandle, 1) FROM #bou_allsort AS b2 FOR XML PATH(N''''), TYPE).value(N''.[1]'', N''NVARCHAR(MAX)''), 1, 1, N'''') OPTION(RECOMPILE); + + INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, + TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, + ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, + MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) + + EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''avg memory grant'', @IgnoreSqlHandles = @ISH, + @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; + + UPDATE #bou_allsort SET Pattern = ''avg memory grant'' WHERE Pattern IS NULL OPTION(RECOMPILE);'; + IF @ExportToExcel = 1 + BEGIN + SET @AllSortSql += N' UPDATE #bou_allsort + SET + QueryPlan = NULL, + implicit_conversion_info = NULL, + cached_execution_parameters = NULL, + missing_indexes = NULL + OPTION (RECOMPILE); - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs - WHERE parameter_sniffing = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 2, - 50, - 'Parameterization', - 'Parameter Sniffing', - 'https://www.brentozar.com/blitzcache/parameter-sniffing/', - 'There are signs of parameter sniffing (wide variance in rows return or time to execute). Investigate query patterns and tune code appropriately.') ; + UPDATE ##BlitzCacheProcs + SET QueryText = SUBSTRING(REPLACE(REPLACE(REPLACE(LTRIM(RTRIM(QueryText)),'' '',''<>''),''><'',''''),''<>'','' ''), 1, 32000) + OPTION(RECOMPILE);'; + END; - /* Forced execution plans */ - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs - WHERE is_forced_plan = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 3, - 50, - 'Parameterization', - 'Forced Plan', - 'https://www.brentozar.com/blitzcache/forced-plans/', - 'Execution plans have been compiled with forced plans, either through FORCEPLAN, plan guides, or forced parameterization. This will make general tuning efforts less effective.'); + END; - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs - WHERE is_cursor = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 4, - 200, - 'Cursors', - 'Cursor', - 'https://www.brentozar.com/blitzcache/cursors-found-slow-queries/', - 'There are cursors in the plan cache. This is neither good nor bad, but it is a thing. Cursors are weird in SQL Server.'); + IF @VersionShowsSpills = 0 + BEGIN + IF @ExportToExcel = 1 + BEGIN + SET @AllSortSql += N' UPDATE #bou_allsort + SET + QueryPlan = NULL, + implicit_conversion_info = NULL, + cached_execution_parameters = NULL, + missing_indexes = NULL + OPTION (RECOMPILE); - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs - WHERE is_cursor = 1 - AND is_optimistic_cursor = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 4, - 200, - 'Cursors', - 'Optimistic Cursors', - 'https://www.brentozar.com/blitzcache/cursors-found-slow-queries/', - 'There are optimistic cursors in the plan cache, which can harm performance.'); + UPDATE ##BlitzCacheProcs + SET QueryText = SUBSTRING(REPLACE(REPLACE(REPLACE(LTRIM(RTRIM(QueryText)),'' '',''<>''),''><'',''''),''<>'','' ''), 1, 32000) + OPTION(RECOMPILE);'; + END; - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs - WHERE is_cursor = 1 - AND is_forward_only_cursor = 0 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 4, - 200, - 'Cursors', - 'Non-forward Only Cursors', - 'https://www.brentozar.com/blitzcache/cursors-found-slow-queries/', - 'There are non-forward only cursors in the plan cache, which can harm performance.'); + END; + + IF @VersionShowsSpills = 1 + BEGIN + SET @AllSortSql += N' SELECT TOP 1 @ISH = STUFF((SELECT DISTINCT N'','' + CONVERT(NVARCHAR(MAX),b2.SqlHandle, 1) FROM #bou_allsort AS b2 FOR XML PATH(N''''), TYPE).value(N''.[1]'', N''NVARCHAR(MAX)''), 1, 1, N'''') OPTION(RECOMPILE); + + INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, + TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, + ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, + MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) + + EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''avg spills'', @IgnoreSqlHandles = @ISH, + @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; + + UPDATE #bou_allsort SET Pattern = ''avg spills'' WHERE Pattern IS NULL OPTION(RECOMPILE);'; + IF @ExportToExcel = 1 + BEGIN + SET @AllSortSql += N' UPDATE #bou_allsort + SET + QueryPlan = NULL, + implicit_conversion_info = NULL, + cached_execution_parameters = NULL, + missing_indexes = NULL + OPTION (RECOMPILE); - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs - WHERE is_cursor = 1 - AND is_cursor_dynamic = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 4, - 200, - 'Cursors', - 'Dynamic Cursors', - 'https://www.brentozar.com/blitzcache/cursors-found-slow-queries/', - 'Dynamic Cursors inhibit parallelism!.'); + UPDATE ##BlitzCacheProcs + SET QueryText = SUBSTRING(REPLACE(REPLACE(REPLACE(LTRIM(RTRIM(QueryText)),'' '',''<>''),''><'',''''),''<>'','' ''), 1, 32000) + OPTION(RECOMPILE);'; + END; - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs - WHERE is_cursor = 1 - AND is_fast_forward_cursor = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 4, - 200, - 'Cursors', - 'Fast Forward Cursors', - 'https://www.brentozar.com/blitzcache/cursors-found-slow-queries/', - 'Fast forward cursors inhibit parallelism!.'); + END; - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs - WHERE is_forced_parameterized = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 5, - 50, - 'Parameterization', - 'Forced Parameterization', - 'https://www.brentozar.com/blitzcache/forced-parameterization/', - 'Execution plans have been compiled with forced parameterization.') ; + IF(@OutputType <> 'NONE') + BEGIN + SET @AllSortSql += N' SELECT DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters,ExecutionCount, ExecutionsPerMinute, ExecutionWeight, + TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, + ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, + MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache, Pattern + FROM #bou_allsort + ORDER BY Id + OPTION(RECOMPILE); '; + END; +END; - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.is_parallel = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 6, - 200, - 'Execution Plans', - 'Parallel', - 'https://www.brentozar.com/blitzcache/parallel-plans-detected/', - 'Parallel plans detected. These warrant investigation, but are neither good nor bad.') ; + IF @Debug = 1 + BEGIN + PRINT SUBSTRING(@AllSortSql, 0, 4000); + PRINT SUBSTRING(@AllSortSql, 4000, 8000); + PRINT SUBSTRING(@AllSortSql, 8000, 12000); + PRINT SUBSTRING(@AllSortSql, 12000, 16000); + PRINT SUBSTRING(@AllSortSql, 16000, 20000); + PRINT SUBSTRING(@AllSortSql, 20000, 24000); + PRINT SUBSTRING(@AllSortSql, 24000, 28000); + PRINT SUBSTRING(@AllSortSql, 28000, 32000); + PRINT SUBSTRING(@AllSortSql, 32000, 36000); + PRINT SUBSTRING(@AllSortSql, 36000, 40000); + END; - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE near_parallel = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 7, - 200, - 'Execution Plans', - 'Nearly Parallel', - 'https://www.brentozar.com/blitzcache/query-cost-near-cost-threshold-parallelism/', - 'Queries near the cost threshold for parallelism. These may go parallel when you least expect it.') ; + EXEC sys.sp_executesql @stmt = @AllSortSql, @params = N'@i_DatabaseName NVARCHAR(128), @i_Top INT, @i_SkipAnalysis BIT, @i_OutputDatabaseName NVARCHAR(258), @i_OutputSchemaName NVARCHAR(258), @i_OutputTableName NVARCHAR(258), @i_CheckDateOverride DATETIMEOFFSET, @i_MinutesBack INT ', + @i_DatabaseName = @DatabaseName, @i_Top = @Top, @i_SkipAnalysis = @SkipAnalysis, @i_OutputDatabaseName = @OutputDatabaseName, @i_OutputSchemaName = @OutputSchemaName, @i_OutputTableName = @OutputTableName, @i_CheckDateOverride = @CheckDateOverride, @i_MinutesBack = @MinutesBack; - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE plan_warnings = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 8, - 50, - 'Execution Plans', - 'Plan Warnings', - 'https://www.brentozar.com/blitzcache/query-plan-warnings/', - 'Warnings detected in execution plans. SQL Server is telling you that something bad is going on that requires your attention.') ; +/* Avoid going into OutputResultsToTable + ... otherwise the last result (e.g. spills) would be recorded twice into the output table. +*/ +RETURN; - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE long_running = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 9, - 50, - 'Performance', - 'Long Running Query', - 'https://www.brentozar.com/blitzcache/long-running-queries/', - 'Long running queries have been found. These are queries with an average duration longer than ' - + CAST(@long_running_query_warning_seconds / 1000 / 1000 AS VARCHAR(5)) - + ' second(s). These queries should be investigated for additional tuning options.') ; +/*End of AllSort section*/ - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.missing_index_count > 0 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 10, - 50, - 'Performance', - 'Missing Indexes', - 'https://www.brentozar.com/blitzcache/missing-index-request/', - 'Queries found with missing indexes.'); - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.downlevel_estimator = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 13, - 200, - 'Cardinality', - 'Downlevel CE', - 'https://www.brentozar.com/blitzcache/legacy-cardinality-estimator/', - 'A legacy cardinality estimator is being used by one or more queries. Investigate whether you need to be using this cardinality estimator. This may be caused by compatibility levels, global trace flags, or query level trace flags.'); +/*Begin code to write results to table */ +OutputResultsToTable: + +RAISERROR('Writing results to table.', 0, 1) WITH NOWAIT; - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE implicit_conversions = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 14, - 50, - 'Performance', - 'Implicit Conversions', - 'https://www.brentozar.com/go/implicit', - 'One or more queries are comparing two fields that are not of the same data type.') ; +SELECT @OutputServerName = QUOTENAME(@OutputServerName), + @OutputDatabaseName = QUOTENAME(@OutputDatabaseName), + @OutputSchemaName = QUOTENAME(@OutputSchemaName), + @OutputTableName = QUOTENAME(@OutputTableName); - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs - WHERE busy_loops = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 16, - 100, - 'Performance', - 'Busy Loops', - 'https://www.brentozar.com/blitzcache/busy-loops/', - 'Operations have been found that are executed 100 times more often than the number of rows returned by each iteration. This is an indicator that something is off in query execution.'); +/* Checks if @OutputServerName is populated with a valid linked server, and that the database name specified is valid */ +DECLARE @ValidOutputServer BIT; +DECLARE @ValidOutputLocation BIT; +DECLARE @LinkedServerDBCheck NVARCHAR(2000); +DECLARE @ValidLinkedServerDB INT; +DECLARE @tmpdbchk table (cnt int); +IF @OutputServerName IS NOT NULL + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Outputting to a remote server.', 0, 1) WITH NOWAIT; + + IF EXISTS (SELECT server_id FROM sys.servers WHERE QUOTENAME([name]) = @OutputServerName) + BEGIN + SET @LinkedServerDBCheck = 'SELECT 1 WHERE EXISTS (SELECT * FROM '+@OutputServerName+'.master.sys.databases WHERE QUOTENAME([name]) = '''+@OutputDatabaseName+''')'; + INSERT INTO @tmpdbchk EXEC sys.sp_executesql @LinkedServerDBCheck; + SET @ValidLinkedServerDB = (SELECT COUNT(*) FROM @tmpdbchk); + IF (@ValidLinkedServerDB > 0) + BEGIN + SET @ValidOutputServer = 1; + SET @ValidOutputLocation = 1; + END; + ELSE + RAISERROR('The specified database was not found on the output server', 16, 0); + END; + ELSE + BEGIN + RAISERROR('The specified output server was not found', 16, 0); + END; + END; +ELSE + BEGIN + IF @OutputDatabaseName IS NOT NULL + AND @OutputSchemaName IS NOT NULL + AND @OutputTableName IS NOT NULL + AND EXISTS ( SELECT * + FROM sys.databases + WHERE QUOTENAME([name]) = @OutputDatabaseName) + BEGIN + SET @ValidOutputLocation = 1; + END; + ELSE IF @OutputDatabaseName IS NOT NULL + AND @OutputSchemaName IS NOT NULL + AND @OutputTableName IS NOT NULL + AND NOT EXISTS ( SELECT * + FROM sys.databases + WHERE QUOTENAME([name]) = @OutputDatabaseName) + BEGIN + RAISERROR('The specified output database was not found on this server', 16, 0); + END; + ELSE + BEGIN + SET @ValidOutputLocation = 0; + END; + END; - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs - WHERE tvf_join = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 17, - 50, - 'Performance', - 'Function Join', - 'https://www.brentozar.com/blitzcache/tvf-join/', - 'Execution plans have been found that join to table valued functions (TVFs). TVFs produce inaccurate estimates of the number of rows returned and can lead to any number of query plan problems.'); + /* @OutputTableName lets us export the results to a permanent table */ + DECLARE @StringToExecute NVARCHAR(MAX) = N'' ; - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs - WHERE compile_timeout = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 18, - 50, - 'Execution Plans', - 'Compilation Timeout', - 'https://www.brentozar.com/blitzcache/compilation-timeout/', - 'Query compilation timed out for one or more queries. SQL Server did not find a plan that meets acceptable performance criteria in the time allotted so the best guess was returned. There is a very good chance that this plan isn''t even below average - it''s probably terrible.'); + IF @ValidOutputLocation = 1 + BEGIN + SET @StringToExecute = N'USE ' + + @OutputDatabaseName + + N'; IF EXISTS(SELECT * FROM ' + + @OutputDatabaseName + + N'.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' + + @OutputSchemaName + + N''') AND NOT EXISTS (SELECT * FROM ' + + @OutputDatabaseName + + N'.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''' + + @OutputSchemaName + N''' AND QUOTENAME(TABLE_NAME) = ''' + + @OutputTableName + N''') CREATE TABLE ' + + @OutputSchemaName + N'.' + + @OutputTableName + + CONVERT + ( + nvarchar(MAX), + N'(ID bigint NOT NULL IDENTITY(1,1), + ServerName NVARCHAR(258), + CheckDate DATETIMEOFFSET, + Version NVARCHAR(258), + QueryType NVARCHAR(258), + Warnings varchar(max), + DatabaseName sysname, + SerialDesiredMemory float, + SerialRequiredMemory float, + AverageCPU bigint, + TotalCPU bigint, + PercentCPUByType money, + CPUWeight money, + AverageDuration bigint, + TotalDuration bigint, + DurationWeight money, + PercentDurationByType money, + AverageReads bigint, + TotalReads bigint, + ReadWeight money, + PercentReadsByType money, + AverageWrites bigint, + TotalWrites bigint, + WriteWeight money, + PercentWritesByType money, + ExecutionCount bigint, + ExecutionWeight money, + PercentExecutionsByType money, + ExecutionsPerMinute money, + PlanCreationTime datetime,' + N' + PlanCreationTimeHours AS DATEDIFF(HOUR,CONVERT(DATETIMEOFFSET(7),[PlanCreationTime]),[CheckDate]), + LastExecutionTime datetime, + LastCompletionTime datetime, + PlanHandle varbinary(64), + [Remove Plan Handle From Cache] AS + CASE WHEN [PlanHandle] IS NOT NULL + THEN ''DBCC FREEPROCCACHE ('' + CONVERT(VARCHAR(128), [PlanHandle], 1) + '');'' + ELSE ''N/A'' END, + SqlHandle varbinary(64), + [Remove SQL Handle From Cache] AS + CASE WHEN [SqlHandle] IS NOT NULL + THEN ''DBCC FREEPROCCACHE ('' + CONVERT(VARCHAR(128), [SqlHandle], 1) + '');'' + ELSE ''N/A'' END, + [SQL Handle More Info] AS + CASE WHEN [SqlHandle] IS NOT NULL + THEN ''EXEC sp_BlitzCache @OnlySqlHandles = '''''' + CONVERT(VARCHAR(128), [SqlHandle], 1) + ''''''; '' + ELSE ''N/A'' END, + QueryHash binary(8), + [Query Hash More Info] AS + CASE WHEN [QueryHash] IS NOT NULL + THEN ''EXEC sp_BlitzCache @OnlyQueryHashes = '''''' + CONVERT(VARCHAR(32), [QueryHash], 1) + ''''''; '' + ELSE ''N/A'' END, + QueryPlanHash binary(8), + StatementStartOffset int, + StatementEndOffset int, + PlanGenerationNum bigint, + MinReturnedRows bigint, + MaxReturnedRows bigint, + AverageReturnedRows money, + TotalReturnedRows bigint, + QueryText nvarchar(max), + QueryPlan xml, + NumberOfPlans int, + NumberOfDistinctPlans int, + MinGrantKB BIGINT, + MaxGrantKB BIGINT, + MinUsedGrantKB BIGINT, + MaxUsedGrantKB BIGINT, + PercentMemoryGrantUsed MONEY, + AvgMaxMemoryGrant MONEY, + MinSpills BIGINT, + MaxSpills BIGINT, + TotalSpills BIGINT, + AvgSpills MONEY, + QueryPlanCost FLOAT, + Pattern NVARCHAR(20), + JoinKey AS ServerName + Cast(CheckDate AS NVARCHAR(50)), + CONSTRAINT [PK_' + REPLACE(REPLACE(@OutputTableName,N'[',N''),N']',N'') + N'] PRIMARY KEY CLUSTERED(ID ASC));' + ); - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs - WHERE compile_memory_limit_exceeded = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 19, - 50, - 'Execution Plans', - 'Compile Memory Limit Exceeded', - 'https://www.brentozar.com/blitzcache/compile-memory-limit-exceeded/', - 'The optimizer has a limited amount of memory available. One or more queries are complex enough that SQL Server was unable to allocate enough memory to fully optimize the query. A best fit plan was found, and it''s probably terrible.'); + SET @StringToExecute += N'IF EXISTS(SELECT * FROM ' + +@OutputDatabaseName + +N'.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' + +@OutputSchemaName + +N''') AND EXISTS (SELECT * FROM ' + +@OutputDatabaseName+ + N'.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''' + +@OutputSchemaName + +N''' AND QUOTENAME(TABLE_NAME) = ''' + +@OutputTableName + +N''') AND EXISTS (SELECT * FROM ' + +@OutputDatabaseName+ + N'.sys.computed_columns WHERE [name] = N''PlanCreationTimeHours'' AND QUOTENAME(OBJECT_NAME(object_id)) = N''' + +@OutputTableName + +N''' AND [definition] = N''(datediff(hour,[PlanCreationTime],sysdatetime()))'') +BEGIN + RAISERROR(''We noticed that you are running an old computed column definition for PlanCreationTimeHours, fixing that now'',0,0) WITH NOWAIT; + ALTER TABLE '+@OutputDatabaseName+N'.'+@OutputSchemaName+N'.'+@OutputTableName+N' DROP COLUMN [PlanCreationTimeHours]; + ALTER TABLE '+@OutputDatabaseName+N'.'+@OutputSchemaName+N'.'+@OutputTableName+N' ADD [PlanCreationTimeHours] AS DATEDIFF(HOUR,CONVERT(DATETIMEOFFSET(7),[PlanCreationTime]),[CheckDate]); +END '; - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs - WHERE warning_no_join_predicate = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 20, - 50, - 'Execution Plans', - 'No Join Predicate', - 'https://www.brentozar.com/blitzcache/no-join-predicate/', - 'Operators in a query have no join predicate. This means that all rows from one table will be matched with all rows from anther table producing a Cartesian product. That''s a whole lot of rows. This may be your goal, but it''s important to investigate why this is happening.'); + IF @ValidOutputServer = 1 + BEGIN + SET @StringToExecute = REPLACE(@StringToExecute,''''+@OutputSchemaName+'''',''''''+@OutputSchemaName+''''''); + SET @StringToExecute = REPLACE(@StringToExecute,''''+@OutputTableName+'''',''''''+@OutputTableName+''''''); + SET @StringToExecute = REPLACE(@StringToExecute,'xml','nvarchar(max)'); + SET @StringToExecute = REPLACE(@StringToExecute,'''DBCC FREEPROCCACHE ('' + CONVERT(VARCHAR(128), [PlanHandle], 1) + '');''','''''DBCC FREEPROCCACHE ('''' + CONVERT(VARCHAR(128), [PlanHandle], 1) + '''');'''''); + SET @StringToExecute = REPLACE(@StringToExecute,'''DBCC FREEPROCCACHE ('' + CONVERT(VARCHAR(128), [SqlHandle], 1) + '');''','''''DBCC FREEPROCCACHE ('''' + CONVERT(VARCHAR(128), [SqlHandle], 1) + '''');'''''); + SET @StringToExecute = REPLACE(@StringToExecute,'''EXEC sp_BlitzCache @OnlySqlHandles = '''''' + CONVERT(VARCHAR(128), [SqlHandle], 1) + ''''''; ''','''''EXEC sp_BlitzCache @OnlySqlHandles = '''''''' + CONVERT(VARCHAR(128), [SqlHandle], 1) + ''''''''; '''''); + SET @StringToExecute = REPLACE(@StringToExecute,'''EXEC sp_BlitzCache @OnlyQueryHashes = '''''' + CONVERT(VARCHAR(32), [QueryHash], 1) + ''''''; ''','''''EXEC sp_BlitzCache @OnlyQueryHashes = '''''''' + CONVERT(VARCHAR(32), [QueryHash], 1) + ''''''''; '''''); + SET @StringToExecute = REPLACE(@StringToExecute,'''N/A''','''''N/A'''''); + + IF @Debug = 1 + BEGIN + PRINT SUBSTRING(@StringToExecute, 0, 4000); + PRINT SUBSTRING(@StringToExecute, 4000, 8000); + PRINT SUBSTRING(@StringToExecute, 8000, 12000); + PRINT SUBSTRING(@StringToExecute, 12000, 16000); + PRINT SUBSTRING(@StringToExecute, 16000, 20000); + PRINT SUBSTRING(@StringToExecute, 20000, 24000); + PRINT SUBSTRING(@StringToExecute, 24000, 28000); + PRINT SUBSTRING(@StringToExecute, 28000, 32000); + PRINT SUBSTRING(@StringToExecute, 32000, 36000); + PRINT SUBSTRING(@StringToExecute, 36000, 40000); + END; + EXEC('EXEC('''+@StringToExecute+''') AT ' + @OutputServerName); + END; + ELSE + BEGIN + IF @Debug = 1 + BEGIN + PRINT SUBSTRING(@StringToExecute, 0, 4000); + PRINT SUBSTRING(@StringToExecute, 4000, 8000); + PRINT SUBSTRING(@StringToExecute, 8000, 12000); + PRINT SUBSTRING(@StringToExecute, 12000, 16000); + PRINT SUBSTRING(@StringToExecute, 16000, 20000); + PRINT SUBSTRING(@StringToExecute, 20000, 24000); + PRINT SUBSTRING(@StringToExecute, 24000, 28000); + PRINT SUBSTRING(@StringToExecute, 28000, 32000); + PRINT SUBSTRING(@StringToExecute, 32000, 36000); + PRINT SUBSTRING(@StringToExecute, 36000, 40000); + END; + EXEC(@StringToExecute); + END; + + /* If the table doesn't have the new LastCompletionTime column, add it. See Github #2377. */ + SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableName; + SET @StringToExecute = N'IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns + WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + ''')) AND name = ''LastCompletionTime'') + ALTER TABLE ' + @ObjectFullName + N' ADD LastCompletionTime DATETIME NULL;'; + IF @ValidOutputServer = 1 + BEGIN + SET @StringToExecute = REPLACE(@StringToExecute,'''LastCompletionTime''','''''LastCompletionTime'''''); + SET @StringToExecute = REPLACE(@StringToExecute,'''' + @ObjectFullName + '''','''''' + @ObjectFullName + ''''''); + EXEC('EXEC('''+@StringToExecute+''') AT ' + @OutputServerName); + END; + ELSE + BEGIN + EXEC(@StringToExecute); + END; - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs - WHERE plan_multiple_plans > 0 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 21, - 200, - 'Execution Plans', - 'Multiple Plans', - 'https://www.brentozar.com/blitzcache/multiple-plans/', - 'Queries exist with multiple execution plans (as determined by query_plan_hash). Investigate possible ways to parameterize these queries or otherwise reduce the plan count.'); + /* If the table doesn't have the new PlanGenerationNum column, add it. See Github #2514. */ + SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableName; + SET @StringToExecute = N'IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns + WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''PlanGenerationNum'') + ALTER TABLE ' + @ObjectFullName + N' ADD PlanGenerationNum BIGINT NULL;'; + IF @ValidOutputServer = 1 + BEGIN + SET @StringToExecute = REPLACE(@StringToExecute,'''PlanGenerationNum''','''''PlanGenerationNum'''''); + SET @StringToExecute = REPLACE(@StringToExecute,'''' + @ObjectFullName + '''','''''' + @ObjectFullName + ''''''); + EXEC('EXEC('''+@StringToExecute+''') AT ' + @OutputServerName); + END; + ELSE + BEGIN + EXEC(@StringToExecute); + END; + + /* If the table doesn't have the new Pattern column, add it */ + SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableName; + SET @StringToExecute = N'IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns + WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''Pattern'') + ALTER TABLE ' + @ObjectFullName + N' ADD Pattern NVARCHAR(20) NULL;'; + IF @ValidOutputServer = 1 + BEGIN + SET @StringToExecute = REPLACE(@StringToExecute,'''Pattern''','''''Pattern'''''); + SET @StringToExecute = REPLACE(@StringToExecute,'''' + @ObjectFullName + '''','''''' + @ObjectFullName + ''''''); + EXEC('EXEC('''+@StringToExecute+''') AT ' + @OutputServerName); + END; + ELSE + BEGIN + EXEC(@StringToExecute); + END + + IF @CheckDateOverride IS NULL + BEGIN + SET @CheckDateOverride = SYSDATETIMEOFFSET(); + END; + + IF @ValidOutputServer = 1 + BEGIN + SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' + + @OutputServerName + '.' + + @OutputDatabaseName + + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' + + @OutputSchemaName + ''') INSERT ' + + @OutputServerName + '.' + + @OutputDatabaseName + '.' + + @OutputSchemaName + '.' + + @OutputTableName + + ' (ServerName, CheckDate, Version, QueryType, DatabaseName, AverageCPU, TotalCPU, PercentCPUByType, CPUWeight, AverageDuration, TotalDuration, DurationWeight, PercentDurationByType, AverageReads, TotalReads, ReadWeight, PercentReadsByType, ' + + ' AverageWrites, TotalWrites, WriteWeight, PercentWritesByType, ExecutionCount, ExecutionWeight, PercentExecutionsByType, ' + + ' ExecutionsPerMinute, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, QueryHash, QueryPlanHash, StatementStartOffset, StatementEndOffset, PlanGenerationNum, MinReturnedRows, MaxReturnedRows, AverageReturnedRows, TotalReturnedRows, QueryText, QueryPlan, NumberOfPlans, NumberOfDistinctPlans, Warnings, ' + + ' SerialRequiredMemory, SerialDesiredMemory, MinGrantKB, MaxGrantKB, MinUsedGrantKB, MaxUsedGrantKB, PercentMemoryGrantUsed, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, QueryPlanCost, Pattern ) ' + + 'SELECT TOP (@Top) ' + + QUOTENAME(CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128)), '''') + ', @CheckDateOverride, ' + + QUOTENAME(CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)), '''') + ', ' + + ' QueryType, DatabaseName, AverageCPU, TotalCPU, PercentCPUByType, PercentCPU, AverageDuration, TotalDuration, PercentDuration, PercentDurationByType, AverageReads, TotalReads, PercentReads, PercentReadsByType, ' + + ' AverageWrites, TotalWrites, PercentWrites, PercentWritesByType, ExecutionCount, PercentExecutions, PercentExecutionsByType, ' + + ' ExecutionsPerMinute, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, QueryHash, QueryPlanHash, StatementStartOffset, StatementEndOffset, PlanGenerationNum, MinReturnedRows, MaxReturnedRows, AverageReturnedRows, TotalReturnedRows, QueryText, CAST(QueryPlan AS NVARCHAR(MAX)), NumberOfPlans, NumberOfDistinctPlans, Warnings, ' + + ' SerialRequiredMemory, SerialDesiredMemory, MinGrantKB, MaxGrantKB, MinUsedGrantKB, MaxUsedGrantKB, PercentMemoryGrantUsed, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, QueryPlanCost, Pattern ' + + ' FROM ##BlitzCacheProcs ' + + ' WHERE 1=1 '; - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs - WHERE unmatched_index_count > 0 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 22, - 100, - 'Performance', - 'Unmatched Indexes', - 'https://www.brentozar.com/blitzcache/unmatched-indexes', - 'An index could have been used, but SQL Server chose not to use it - likely due to parameterization and filtered indexes.'); + IF @MinimumExecutionCount IS NOT NULL + BEGIN + SET @StringToExecute += N' AND ExecutionCount >= @MinimumExecutionCount '; + END; + IF @MinutesBack IS NOT NULL + BEGIN + SET @StringToExecute += N' AND LastCompletionTime >= DATEADD(MINUTE, @min_back, GETDATE() ) '; + END; + SET @StringToExecute += N' AND SPID = @@SPID '; + SELECT @StringToExecute += N' ORDER BY ' + CASE @SortOrder WHEN 'cpu' THEN N' TotalCPU ' + WHEN N'reads' THEN N' TotalReads ' + WHEN N'writes' THEN N' TotalWrites ' + WHEN N'duration' THEN N' TotalDuration ' + WHEN N'executions' THEN N' ExecutionCount ' + WHEN N'compiles' THEN N' PlanCreationTime ' + WHEN N'memory grant' THEN N' MaxGrantKB' + WHEN N'spills' THEN N' MaxSpills' + WHEN N'avg cpu' THEN N' AverageCPU' + WHEN N'avg reads' THEN N' AverageReads' + WHEN N'avg writes' THEN N' AverageWrites' + WHEN N'avg duration' THEN N' AverageDuration' + WHEN N'avg executions' THEN N' ExecutionsPerMinute' + WHEN N'avg memory grant' THEN N' AvgMaxMemoryGrant' + WHEN N'avg spills' THEN N' AvgSpills' + WHEN N'unused grant' THEN N' MaxGrantKB - MaxUsedGrantKB' + ELSE N' TotalCPU ' + END + N' DESC '; + SET @StringToExecute += N' OPTION (RECOMPILE) ; '; + + IF @Debug = 1 + BEGIN + PRINT SUBSTRING(@StringToExecute, 1, 4000); + PRINT SUBSTRING(@StringToExecute, 4001, 4000); + PRINT SUBSTRING(@StringToExecute, 8001, 4000); + PRINT SUBSTRING(@StringToExecute, 12001, 4000); + PRINT SUBSTRING(@StringToExecute, 16001, 4000); + PRINT SUBSTRING(@StringToExecute, 20001, 4000); + PRINT SUBSTRING(@StringToExecute, 24001, 4000); + PRINT SUBSTRING(@StringToExecute, 28001, 4000); + PRINT SUBSTRING(@StringToExecute, 32001, 4000); + PRINT SUBSTRING(@StringToExecute, 36001, 4000); + END; - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs - WHERE unparameterized_query = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 23, - 100, - 'Parameterization', - 'Unparameterized Query', - 'https://www.brentozar.com/blitzcache/unparameterized-queries', - 'Unparameterized queries found. These could be ad hoc queries, data exploration, or queries using "OPTIMIZE FOR UNKNOWN".'); + EXEC sp_executesql @StringToExecute, N'@Top INT, @min_duration INT, @min_back INT, @CheckDateOverride DATETIMEOFFSET, @MinimumExecutionCount INT', @Top, @DurationFilter_i, @MinutesBack, @CheckDateOverride, @MinimumExecutionCount; + END; + ELSE + BEGIN + SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' + + @OutputDatabaseName + + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' + + @OutputSchemaName + ''') INSERT ' + + @OutputDatabaseName + '.' + + @OutputSchemaName + '.' + + @OutputTableName + + ' (ServerName, CheckDate, Version, QueryType, DatabaseName, AverageCPU, TotalCPU, PercentCPUByType, CPUWeight, AverageDuration, TotalDuration, DurationWeight, PercentDurationByType, AverageReads, TotalReads, ReadWeight, PercentReadsByType, ' + + ' AverageWrites, TotalWrites, WriteWeight, PercentWritesByType, ExecutionCount, ExecutionWeight, PercentExecutionsByType, ' + + ' ExecutionsPerMinute, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, QueryHash, QueryPlanHash, StatementStartOffset, StatementEndOffset, PlanGenerationNum, MinReturnedRows, MaxReturnedRows, AverageReturnedRows, TotalReturnedRows, QueryText, QueryPlan, NumberOfPlans, NumberOfDistinctPlans, Warnings, ' + + ' SerialRequiredMemory, SerialDesiredMemory, MinGrantKB, MaxGrantKB, MinUsedGrantKB, MaxUsedGrantKB, PercentMemoryGrantUsed, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, QueryPlanCost, Pattern ) ' + + 'SELECT TOP (@Top) ' + + QUOTENAME(CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128)), '''') + ', @CheckDateOverride, ' + + QUOTENAME(CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)), '''') + ', ' + + ' QueryType, DatabaseName, AverageCPU, TotalCPU, PercentCPUByType, PercentCPU, AverageDuration, TotalDuration, PercentDuration, PercentDurationByType, AverageReads, TotalReads, PercentReads, PercentReadsByType, ' + + ' AverageWrites, TotalWrites, PercentWrites, PercentWritesByType, ExecutionCount, PercentExecutions, PercentExecutionsByType, ' + + ' ExecutionsPerMinute, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, QueryHash, QueryPlanHash, StatementStartOffset, StatementEndOffset, PlanGenerationNum, MinReturnedRows, MaxReturnedRows, AverageReturnedRows, TotalReturnedRows, QueryText, QueryPlan, NumberOfPlans, NumberOfDistinctPlans, Warnings, ' + + ' SerialRequiredMemory, SerialDesiredMemory, MinGrantKB, MaxGrantKB, MinUsedGrantKB, MaxUsedGrantKB, PercentMemoryGrantUsed, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, QueryPlanCost, Pattern ' + + ' FROM ##BlitzCacheProcs ' + + ' WHERE 1=1 '; - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs - WHERE is_trivial = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 24, - 100, - 'Execution Plans', - 'Trivial Plans', - 'https://www.brentozar.com/blitzcache/trivial-plans', - 'Trivial plans get almost no optimization. If you''re finding these in the top worst queries, something may be going wrong.'); - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.is_forced_serial= 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 25, - 10, - 'Execution Plans', - 'Forced Serialization', - 'https://www.brentozar.com/blitzcache/forced-serialization/', - 'Something in your plan is forcing a serial query. Further investigation is needed if this is not by design.') ; + IF @MinimumExecutionCount IS NOT NULL + BEGIN + SET @StringToExecute += N' AND ExecutionCount >= @MinimumExecutionCount '; + END; + IF @MinutesBack IS NOT NULL + BEGIN + SET @StringToExecute += N' AND LastCompletionTime >= DATEADD(MINUTE, @min_back, GETDATE() ) '; + END; + SET @StringToExecute += N' AND SPID = @@SPID '; + SELECT @StringToExecute += N' ORDER BY ' + CASE @SortOrder WHEN 'cpu' THEN N' TotalCPU ' + WHEN N'reads' THEN N' TotalReads ' + WHEN N'writes' THEN N' TotalWrites ' + WHEN N'duration' THEN N' TotalDuration ' + WHEN N'executions' THEN N' ExecutionCount ' + WHEN N'compiles' THEN N' PlanCreationTime ' + WHEN N'memory grant' THEN N' MaxGrantKB' + WHEN N'spills' THEN N' MaxSpills' + WHEN N'avg cpu' THEN N' AverageCPU' + WHEN N'avg reads' THEN N' AverageReads' + WHEN N'avg writes' THEN N' AverageWrites' + WHEN N'avg duration' THEN N' AverageDuration' + WHEN N'avg executions' THEN N' ExecutionsPerMinute' + WHEN N'avg memory grant' THEN N' AvgMaxMemoryGrant' + WHEN N'avg spills' THEN N' AvgSpills' + WHEN N'unused grant' THEN N' MaxGrantKB - MaxUsedGrantKB' + ELSE N' TotalCPU ' + END + N' DESC '; + SET @StringToExecute += N' OPTION (RECOMPILE) ; '; + + IF @Debug = 1 + BEGIN + PRINT SUBSTRING(@StringToExecute, 0, 4000); + PRINT SUBSTRING(@StringToExecute, 4000, 8000); + PRINT SUBSTRING(@StringToExecute, 8000, 12000); + PRINT SUBSTRING(@StringToExecute, 12000, 16000); + PRINT SUBSTRING(@StringToExecute, 16000, 20000); + PRINT SUBSTRING(@StringToExecute, 20000, 24000); + PRINT SUBSTRING(@StringToExecute, 24000, 28000); + PRINT SUBSTRING(@StringToExecute, 28000, 32000); + PRINT SUBSTRING(@StringToExecute, 32000, 36000); + PRINT SUBSTRING(@StringToExecute, 36000, 40000); + END; - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.is_key_lookup_expensive= 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 26, - 100, - 'Execution Plans', - 'Expensive Key Lookup', - 'https://www.brentozar.com/blitzcache/expensive-key-lookups/', - 'There''s a key lookup in your plan that costs >=50% of the total plan cost.') ; + EXEC sp_executesql @StringToExecute, N'@Top INT, @min_duration INT, @min_back INT, @CheckDateOverride DATETIMEOFFSET, @MinimumExecutionCount INT', @Top, @DurationFilter_i, @MinutesBack, @CheckDateOverride, @MinimumExecutionCount; + END; + END; + ELSE IF (SUBSTRING(@OutputTableName, 2, 2) = '##') + BEGIN + IF @ValidOutputServer = 1 + BEGIN + RAISERROR('Due to the nature of temporary tables, outputting to a linked server requires a permanent table.', 16, 0); + END; + ELSE IF @OutputTableName IN ('##BlitzCacheProcs','##BlitzCacheResults') + BEGIN + RAISERROR('OutputTableName is a reserved name for this procedure. We only use ##BlitzCacheProcs and ##BlitzCacheResults, please choose another table name.', 16, 0); + END; + ELSE + BEGIN + SET @StringToExecute = N' IF (OBJECT_ID(''tempdb..' + + @OutputTableName + + ''') IS NOT NULL) DROP TABLE ' + @OutputTableName + ';' + + 'CREATE TABLE ' + + @OutputTableName + + ' (ID bigint NOT NULL IDENTITY(1,1), + ServerName NVARCHAR(258), + CheckDate DATETIMEOFFSET, + Version NVARCHAR(258), + QueryType NVARCHAR(258), + Warnings varchar(max), + DatabaseName sysname, + SerialDesiredMemory float, + SerialRequiredMemory float, + AverageCPU bigint, + TotalCPU bigint, + PercentCPUByType money, + CPUWeight money, + AverageDuration bigint, + TotalDuration bigint, + DurationWeight money, + PercentDurationByType money, + AverageReads bigint, + TotalReads bigint, + ReadWeight money, + PercentReadsByType money, + AverageWrites bigint, + TotalWrites bigint, + WriteWeight money, + PercentWritesByType money, + ExecutionCount bigint, + ExecutionWeight money, + PercentExecutionsByType money, + ExecutionsPerMinute money, + PlanCreationTime datetime,' + N' + PlanCreationTimeHours AS DATEDIFF(HOUR, PlanCreationTime, SYSDATETIME()), + LastExecutionTime datetime, + LastCompletionTime datetime, + PlanHandle varbinary(64), + [Remove Plan Handle From Cache] AS + CASE WHEN [PlanHandle] IS NOT NULL + THEN ''DBCC FREEPROCCACHE ('' + CONVERT(VARCHAR(128), [PlanHandle], 1) + '');'' + ELSE ''N/A'' END, + SqlHandle varbinary(64), + [Remove SQL Handle From Cache] AS + CASE WHEN [SqlHandle] IS NOT NULL + THEN ''DBCC FREEPROCCACHE ('' + CONVERT(VARCHAR(128), [SqlHandle], 1) + '');'' + ELSE ''N/A'' END, + [SQL Handle More Info] AS + CASE WHEN [SqlHandle] IS NOT NULL + THEN ''EXEC sp_BlitzCache @OnlySqlHandles = '''''' + CONVERT(VARCHAR(128), [SqlHandle], 1) + ''''''; '' + ELSE ''N/A'' END, + QueryHash binary(8), + [Query Hash More Info] AS + CASE WHEN [QueryHash] IS NOT NULL + THEN ''EXEC sp_BlitzCache @OnlyQueryHashes = '''''' + CONVERT(VARCHAR(32), [QueryHash], 1) + ''''''; '' + ELSE ''N/A'' END, + QueryPlanHash binary(8), + StatementStartOffset int, + StatementEndOffset int, + PlanGenerationNum bigint, + MinReturnedRows bigint, + MaxReturnedRows bigint, + AverageReturnedRows money, + TotalReturnedRows bigint, + QueryText nvarchar(max), + QueryPlan xml, + NumberOfPlans int, + NumberOfDistinctPlans int, + MinGrantKB BIGINT, + MaxGrantKB BIGINT, + MinUsedGrantKB BIGINT, + MaxUsedGrantKB BIGINT, + PercentMemoryGrantUsed MONEY, + AvgMaxMemoryGrant MONEY, + MinSpills BIGINT, + MaxSpills BIGINT, + TotalSpills BIGINT, + AvgSpills MONEY, + QueryPlanCost FLOAT, + Pattern NVARCHAR(20), + JoinKey AS ServerName + Cast(CheckDate AS NVARCHAR(50)), + CONSTRAINT [PK_' + REPLACE(REPLACE(@OutputTableName,'[',''),']','') + '] PRIMARY KEY CLUSTERED(ID ASC));'; + SET @StringToExecute += N' INSERT ' + + @OutputTableName + + ' (ServerName, CheckDate, Version, QueryType, DatabaseName, AverageCPU, TotalCPU, PercentCPUByType, CPUWeight, AverageDuration, TotalDuration, DurationWeight, PercentDurationByType, AverageReads, TotalReads, ReadWeight, PercentReadsByType, ' + + ' AverageWrites, TotalWrites, WriteWeight, PercentWritesByType, ExecutionCount, ExecutionWeight, PercentExecutionsByType, ' + + ' ExecutionsPerMinute, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, QueryHash, QueryPlanHash, StatementStartOffset, StatementEndOffset, PlanGenerationNum, MinReturnedRows, MaxReturnedRows, AverageReturnedRows, TotalReturnedRows, QueryText, QueryPlan, NumberOfPlans, NumberOfDistinctPlans, Warnings, ' + + ' SerialRequiredMemory, SerialDesiredMemory, MinGrantKB, MaxGrantKB, MinUsedGrantKB, MaxUsedGrantKB, PercentMemoryGrantUsed, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, QueryPlanCost, Pattern ) ' + + 'SELECT TOP (@Top) ' + + QUOTENAME(CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128)), '''') + ', @CheckDateOverride, ' + + QUOTENAME(CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)), '''') + ', ' + + ' QueryType, DatabaseName, AverageCPU, TotalCPU, PercentCPUByType, PercentCPU, AverageDuration, TotalDuration, PercentDuration, PercentDurationByType, AverageReads, TotalReads, PercentReads, PercentReadsByType, ' + + ' AverageWrites, TotalWrites, PercentWrites, PercentWritesByType, ExecutionCount, PercentExecutions, PercentExecutionsByType, ' + + ' ExecutionsPerMinute, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, QueryHash, QueryPlanHash, StatementStartOffset, StatementEndOffset, PlanGenerationNum, MinReturnedRows, MaxReturnedRows, AverageReturnedRows, TotalReturnedRows, QueryText, QueryPlan, NumberOfPlans, NumberOfDistinctPlans, Warnings, ' + + ' SerialRequiredMemory, SerialDesiredMemory, MinGrantKB, MaxGrantKB, MinUsedGrantKB, MaxUsedGrantKB, PercentMemoryGrantUsed, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, QueryPlanCost, Pattern ' + + ' FROM ##BlitzCacheProcs ' + + ' WHERE 1=1 '; + + IF @MinimumExecutionCount IS NOT NULL + BEGIN + SET @StringToExecute += N' AND ExecutionCount >= @MinimumExecutionCount '; + END; - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.is_remote_query_expensive= 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 28, - 100, - 'Execution Plans', - 'Expensive Remote Query', - 'https://www.brentozar.com/blitzcache/expensive-remote-query/', - 'There''s a remote query in your plan that costs >=50% of the total plan cost.') ; + IF @MinutesBack IS NOT NULL + BEGIN + SET @StringToExecute += N' AND LastCompletionTime >= DATEADD(MINUTE, @min_back, GETDATE() ) '; + END; - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.trace_flags_session IS NOT NULL - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 29, - 200, - 'Trace Flags', - 'Session Level Trace Flags Enabled', - 'https://www.brentozar.com/blitz/trace-flags-enabled-globally/', - 'Someone is enabling session level Trace Flags in a query.') ; + SET @StringToExecute += N' AND SPID = @@SPID '; + + SELECT @StringToExecute += N' ORDER BY ' + CASE @SortOrder WHEN 'cpu' THEN N' TotalCPU ' + WHEN N'reads' THEN N' TotalReads ' + WHEN N'writes' THEN N' TotalWrites ' + WHEN N'duration' THEN N' TotalDuration ' + WHEN N'executions' THEN N' ExecutionCount ' + WHEN N'compiles' THEN N' PlanCreationTime ' + WHEN N'memory grant' THEN N' MaxGrantKB' + WHEN N'spills' THEN N' MaxSpills' + WHEN N'avg cpu' THEN N' AverageCPU' + WHEN N'avg reads' THEN N' AverageReads' + WHEN N'avg writes' THEN N' AverageWrites' + WHEN N'avg duration' THEN N' AverageDuration' + WHEN N'avg executions' THEN N' ExecutionsPerMinute' + WHEN N'avg memory grant' THEN N' AvgMaxMemoryGrant' + WHEN N'avg spills' THEN N' AvgSpills' + WHEN N'unused grant' THEN N' MaxGrantKB - MaxUsedGrantKB' + ELSE N' TotalCPU ' + END + N' DESC '; - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.is_unused_grant IS NOT NULL - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 30, - 100, - 'Memory Grant', - 'Unused Memory Grant', - 'https://www.brentozar.com/blitzcache/unused-memory-grants/', - 'Queries have large unused memory grants. This can cause concurrency issues, if queries are waiting a long time to get memory to run.') ; - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.function_count > 0 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 31, - 100, - 'Compute Scalar That References A Function', - 'Calls Functions', - 'https://www.brentozar.com/blitzcache/compute-scalar-functions/', - 'Both of these will force queries to run serially, run at least once per row, and may result in poor cardinality estimates.') ; - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.clr_function_count > 0 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 32, - 100, - 'Compute Scalar That References A CLR Function', - 'Calls CLR Functions', - 'https://www.brentozar.com/blitzcache/compute-scalar-functions/', - 'May force queries to run serially, run at least once per row, and may result in poor cardinality estimates.') ; + SET @StringToExecute += N' OPTION (RECOMPILE) ; '; + + IF @Debug = 1 + BEGIN + PRINT SUBSTRING(@StringToExecute, 0, 4000); + PRINT SUBSTRING(@StringToExecute, 4000, 8000); + PRINT SUBSTRING(@StringToExecute, 8000, 12000); + PRINT SUBSTRING(@StringToExecute, 12000, 16000); + PRINT SUBSTRING(@StringToExecute, 16000, 20000); + PRINT SUBSTRING(@StringToExecute, 20000, 24000); + PRINT SUBSTRING(@StringToExecute, 24000, 28000); + PRINT SUBSTRING(@StringToExecute, 28000, 32000); + PRINT SUBSTRING(@StringToExecute, 32000, 36000); + PRINT SUBSTRING(@StringToExecute, 36000, 40000); + PRINT SUBSTRING(@StringToExecute, 34000, 40000); + END; + EXEC sp_executesql @StringToExecute, N'@Top INT, @min_duration INT, @min_back INT, @CheckDateOverride DATETIMEOFFSET, @MinimumExecutionCount INT', @Top, @DurationFilter_i, @MinutesBack, @CheckDateOverride, @MinimumExecutionCount; + END; + END; + ELSE IF (SUBSTRING(@OutputTableName, 2, 1) = '#') + BEGIN + RAISERROR('Due to the nature of Dymamic SQL, only global (i.e. double pound (##)) temp tables are supported for @OutputTableName', 16, 0); +END; /* End of writing results to table */ +END; /*Final End*/ - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.is_table_variable = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 33, - 100, - 'Table Variables detected', - 'Table Variables', - 'https://www.brentozar.com/blitzcache/table-variables/', - 'All modifications are single threaded, and selects have really low row estimates.') ; +GO +SET ANSI_NULLS ON; +SET ANSI_PADDING ON; +SET ANSI_WARNINGS ON; +SET ARITHABORT ON; +SET CONCAT_NULL_YIELDS_NULL ON; +SET QUOTED_IDENTIFIER ON; +SET STATISTICS IO OFF; +SET STATISTICS TIME OFF; +GO - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.no_stats_warning = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 35, - 100, - 'Statistics', - 'Columns With No Statistics', - 'https://www.brentozar.com/blitzcache/columns-no-statistics/', - 'Sometimes this happens with indexed views, other times because auto create stats is turned off.') ; +IF OBJECT_ID('dbo.sp_BlitzIndex') IS NULL + EXEC ('CREATE PROCEDURE dbo.sp_BlitzIndex AS RETURN 0;'); +GO - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.relop_warnings = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 36, - 100, - 'Warnings', - 'Operator Warnings', - 'https://www.brentozar.com/blitzcache/query-plan-warnings/', - 'Check the plan for more details.') ; +ALTER PROCEDURE dbo.sp_BlitzIndex + @DatabaseName NVARCHAR(128) = NULL, /*Defaults to current DB if not specified*/ + @SchemaName NVARCHAR(128) = NULL, /*Requires table_name as well.*/ + @TableName NVARCHAR(128) = NULL, /*Requires schema_name as well.*/ + @Mode TINYINT=0, /*0=Diagnose, 1=Summarize, 2=Index Usage Detail, 3=Missing Index Detail, 4=Diagnose Details*/ + /*Note:@Mode doesn't matter if you're specifying schema_name and @TableName.*/ + @Filter TINYINT = 0, /* 0=no filter (default). 1=No low-usage warnings for objects with 0 reads. 2=Only warn for objects >= 500MB */ + /*Note:@Filter doesn't do anything unless @Mode=0*/ + @SkipPartitions BIT = 0, + @SkipStatistics BIT = 1, + @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, + @IgnoreDatabases NVARCHAR(MAX) = NULL, /* Comma-delimited list of databases you want to skip */ + @ThresholdMB INT = 250 /* Number of megabytes that an object must be before we include it in basic results */, + @OutputType VARCHAR(20) = 'TABLE' , + @OutputServerName NVARCHAR(256) = NULL , + @OutputDatabaseName NVARCHAR(256) = NULL , + @OutputSchemaName NVARCHAR(256) = NULL , + @OutputTableName NVARCHAR(256) = NULL , + @IncludeInactiveIndexes BIT = 0 /* Will skip indexes with no reads or writes */, + @ShowAllMissingIndexRequests BIT = 0 /*Will make all missing index requests show up*/, + @ShowPartitionRanges BIT = 0 /* Will add partition range values column to columnstore visualization */, + @SortOrder NVARCHAR(50) = NULL, /* Only affects @Mode = 2. */ + @SortDirection NVARCHAR(4) = 'DESC', /* Only affects @Mode = 2. */ + @Help TINYINT = 0, + @Debug BIT = 0, + @Version VARCHAR(30) = NULL OUTPUT, + @VersionDate DATETIME = NULL OUTPUT, + @VersionCheckMode BIT = 0 +WITH RECOMPILE +AS +SET NOCOUNT ON; +SET STATISTICS XML OFF; +SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.is_table_scan = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 37, - 100, - 'Indexes', - 'Table Scans (Heaps)', - 'https://www.brentozar.com/archive/2012/05/video-heaps/', - 'This may not be a problem. Run sp_BlitzIndex for more information.') ; - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.backwards_scan = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 38, - 200, - 'Indexes', - 'Backwards Scans', - 'https://www.brentozar.com/blitzcache/backwards-scans/', - 'This isn''t always a problem. They can cause serial zones in plans, and may need an index to match sort order.') ; +SELECT @Version = '8.19', @VersionDate = '20240222'; +SET @OutputType = UPPER(@OutputType); - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.forced_index = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 39, - 100, - 'Indexes', - 'Forced Indexes', - 'https://www.brentozar.com/blitzcache/optimizer-forcing/', - 'This can cause inefficient plans, and will prevent missing index requests.') ; +IF(@VersionCheckMode = 1) +BEGIN + RETURN; +END; - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.forced_seek = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 40, - 100, - 'Indexes', - 'Forced Seeks', - 'https://www.brentozar.com/blitzcache/optimizer-forcing/', - 'This can cause inefficient plans by taking seek vs scan choice away from the optimizer.') ; +IF @Help = 1 +BEGIN +PRINT ' +/* +sp_BlitzIndex from http://FirstResponderKit.org + +This script analyzes the design and performance of your indexes. - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.forced_scan = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 40, - 100, - 'Indexes', - 'Forced Scans', - 'https://www.brentozar.com/blitzcache/optimizer-forcing/', - 'This can cause inefficient plans by taking seek vs scan choice away from the optimizer.') ; +To learn more, visit http://FirstResponderKit.org where you can download new +versions for free, watch training videos on how it works, get more info on +the findings, contribute your own code, and more. - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.columnstore_row_mode = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 41, - 100, - 'Indexes', - 'ColumnStore Row Mode', - 'https://www.brentozar.com/blitzcache/columnstore-indexes-operating-row-mode/', - 'ColumnStore indexes operating in Row Mode indicate really poor query choices.') ; +Known limitations of this version: + - Only Microsoft-supported versions of SQL Server. Sorry, 2005 and 2000. + - Index create statements are just to give you a rough idea of the syntax. It includes filters and fillfactor. + -- Example 1: index creates use ONLINE=? instead of ONLINE=ON / ONLINE=OFF. This is because it is important + for the user to understand if it is going to be offline and not just run a script. + -- Example 2: they do not include all the options the index may have been created with (padding, compression + filegroup/partition scheme etc.) + -- (The compression and filegroup index create syntax is not trivial because it is set at the partition + level and is not trivial to code.) + - Does not advise you about data modeling for clustered indexes and primary keys (primarily looks for signs of problems.) - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.is_computed_scalar = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 42, - 50, - 'Functions', - 'Computed Column UDF', - 'https://www.brentozar.com/blitzcache/computed-columns-referencing-functions/', - 'This can cause a whole mess of bad serializartion problems.') ; +Unknown limitations of this version: + - We knew them once, but we forgot. - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.is_sort_expensive = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 43, - 100, - 'Execution Plans', - 'Expensive Sort', - 'https://www.brentozar.com/blitzcache/expensive-sorts/', - 'There''s a sort in your plan that costs >=50% of the total plan cost.') ; - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.is_computed_filter = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 44, - 50, - 'Functions', - 'Filter UDF', - 'https://www.brentozar.com/blitzcache/compute-scalar-functions/', - 'Someone put a Scalar UDF in the WHERE clause!') ; +MIT License - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.index_ops >= 5 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 45, - 100, - 'Indexes', - '>= 5 Indexes Modified', - 'https://www.brentozar.com/blitzcache/many-indexes-modified/', - 'This can cause lots of hidden I/O -- Run sp_BlitzIndex for more information.') ; +Copyright (c) Brent Ozar Unlimited - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.is_row_level = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 46, - 200, - 'Complexity', - 'Row Level Security', - 'https://www.brentozar.com/blitzcache/row-level-security/', - 'You may see a lot of confusing junk in your query plan.') ; +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.is_spatial = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 47, - 200, - 'Complexity', - 'Spatial Index', - 'https://www.brentozar.com/blitzcache/spatial-indexes/', - 'Purely informational.') ; +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.index_dml = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 48, - 150, - 'Complexity', - 'Index DML', - 'https://www.brentozar.com/blitzcache/index-dml/', - 'This can cause recompiles and stuff.') ; +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +'; +RETURN; +END; /* @Help = 1 */ - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.table_dml = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 49, - 150, - 'Complexity', - 'Table DML', - 'https://www.brentozar.com/blitzcache/table-dml/', - 'This can cause recompiles and stuff.') ; +DECLARE @ScriptVersionName NVARCHAR(50); +DECLARE @DaysUptime NUMERIC(23,2); +DECLARE @DatabaseID INT; +DECLARE @ObjectID INT; +DECLARE @dsql NVARCHAR(MAX); +DECLARE @params NVARCHAR(MAX); +DECLARE @msg NVARCHAR(4000); +DECLARE @ErrorSeverity INT; +DECLARE @ErrorState INT; +DECLARE @Rowcount BIGINT; +DECLARE @SQLServerProductVersion NVARCHAR(128); +DECLARE @SQLServerEdition INT; +DECLARE @FilterMB INT; +DECLARE @collation NVARCHAR(256); +DECLARE @NumDatabases INT; +DECLARE @LineFeed NVARCHAR(5); +DECLARE @DaysUptimeInsertValue NVARCHAR(256); +DECLARE @DatabaseToIgnore NVARCHAR(MAX); +DECLARE @ColumnList NVARCHAR(MAX); +DECLARE @ColumnListWithApostrophes NVARCHAR(MAX); +DECLARE @PartitionCount INT; +DECLARE @OptimizeForSequentialKey BIT = 0; +DECLARE @StringToExecute NVARCHAR(MAX); - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.long_running_low_cpu = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 50, - 150, - 'Blocking', - 'Long Running Low CPU', - 'https://www.brentozar.com/blitzcache/long-running-low-cpu/', - 'This can be a sign of blocking, linked servers, or poor client application code (ASYNC_NETWORK_IO).') ; - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.low_cost_high_cpu = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 51, - 150, - 'Complexity', - 'Low Cost Query With High CPU', - 'https://www.brentozar.com/blitzcache/low-cost-high-cpu/', - 'This can be a sign of functions or Dynamic SQL that calls black-box code.') ; +/* Let's get @SortOrder set to lower case here for comparisons later */ +SET @SortOrder = REPLACE(LOWER(@SortOrder), N' ', N'_'); +SET @SortDirection = LOWER(@SortDirection); - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.stale_stats = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 52, - 150, - 'Statistics', - 'Statistics used have > 100k modifications in the last 7 days', - 'https://www.brentozar.com/blitzcache/stale-statistics/', - 'Ever heard of updating statistics?') ; +SET @LineFeed = CHAR(13) + CHAR(10); +SELECT @SQLServerProductVersion = CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)); +SELECT @SQLServerEdition =CAST(SERVERPROPERTY('EngineEdition') AS INT); /* We default to online index creates where EngineEdition=3*/ +SET @FilterMB=250; +SELECT @ScriptVersionName = 'sp_BlitzIndex(TM) v' + @Version + ' - ' + DATENAME(MM, @VersionDate) + ' ' + RIGHT('0'+DATENAME(DD, @VersionDate),2) + ', ' + DATENAME(YY, @VersionDate); +SET @IgnoreDatabases = REPLACE(REPLACE(LTRIM(RTRIM(@IgnoreDatabases)), CHAR(10), ''), CHAR(13), ''); - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.is_adaptive = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 53, - 200, - 'Complexity', - 'Adaptive joins', - 'https://www.brentozar.com/blitzcache/adaptive-joins/', - 'This join will sometimes do seeks, and sometimes do scans.') ; +SELECT + @OptimizeForSequentialKey = + CASE WHEN EXISTS + ( + SELECT + 1/0 + FROM sys.all_columns AS ac + WHERE ac.object_id = OBJECT_ID('sys.indexes') + AND ac.name = N'optimize_for_sequential_key' + ) + THEN 1 + ELSE 0 + END; - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.is_spool_expensive = 1 - ) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 54, - 150, - 'Indexes', - 'Expensive Index Spool', - 'https://www.brentozar.com/blitzcache/eager-index-spools/', - 'Check operator predicates and output for index definition guidance') ; +RAISERROR(N'Starting run. %s', 0,1, @ScriptVersionName) WITH NOWAIT; - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.is_spool_more_rows = 1 - ) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 55, - 150, - 'Indexes', - 'Large Index Row Spool', - 'https://www.brentozar.com/blitzcache/eager-index-spools/', - 'Check operator predicates and output for index definition guidance') ; - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.is_bad_estimate = 1 - ) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 56, - 100, - 'Complexity', - 'Row Estimate Mismatch', - 'https://www.brentozar.com/blitzcache/bad-estimates/', - 'Estimated rows are different from average rows by a factor of 10000. This may indicate a performance problem if mismatches occur regularly') ; - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.is_paul_white_electric = 1 - ) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 57, - 200, - 'Is Paul White Electric?', - 'This query has a Switch operator in it!', - 'https://www.sql.kiwi/2013/06/hello-operator-my-switch-is-bored.html', - 'You should email this query plan to Paul: SQLkiwi at gmail dot com') ; + +IF(@OutputType NOT IN ('TABLE','NONE')) +BEGIN + RAISERROR('Invalid value for parameter @OutputType. Expected: (TABLE;NONE)',12,1); + RETURN; +END; - IF @v >= 14 OR (@v = 13 AND @build >= 5026) - BEGIN +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; + SET @OutputType = 'NONE' +END; + +IF(@OutputType = 'NONE') +BEGIN - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - SELECT - @@SPID, - 997, - 200, - 'Database Level Statistics', - 'The database ' + sa.[Database] + ' last had a stats update on ' + CONVERT(NVARCHAR(10), CONVERT(DATE, MAX(sa.LastUpdate))) + ' and has ' + CONVERT(NVARCHAR(10), AVG(sa.ModificationCount)) + ' modifications on average.' AS [Finding], - 'https://www.brentozar.com/blitzcache/stale-statistics/' AS URL, - 'Consider updating statistics more frequently,' AS [Details] - FROM #stats_agg AS sa - GROUP BY sa.[Database] - HAVING MAX(sa.LastUpdate) <= DATEADD(DAY, -7, SYSDATETIME()) - AND AVG(sa.ModificationCount) >= 100000; + IF ((@OutputServerName IS NOT NULL) AND (@OutputTableName IS NULL OR @OutputSchemaName IS NULL OR @OutputDatabaseName IS NULL)) + BEGIN + RAISERROR('Parameter @OutputServerName is specified, rest of @Output* parameters needs to also be specified',12,1); + RETURN; + END; - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.is_row_goal = 1 - ) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 58, - 200, - 'Complexity', - 'Row Goals', - 'https://www.brentozar.com/go/rowgoals/', - 'This query had row goals introduced, which can be good or bad, and should be investigated for high read queries.') ; + IF(@OutputTableName IS NULL OR @OutputSchemaName IS NULL OR @OutputDatabaseName IS NULL) + BEGIN + RAISERROR('This procedure should be called with a value for @OutputTableName, @OutputSchemaName and @OutputDatabaseName parameters, as @OutputType is set to NONE',12,1); + RETURN; + END; + /* Output is supported for all modes, no reason to not bring pain and output + IF(@BringThePain = 1) + BEGIN + RAISERROR('Incompatible Parameters: @BringThePain set to 1 and @OutputType set to NONE',12,1); + RETURN; + END; + */ + /* Eventually limit by mode + IF(@Mode not in (0,4)) + BEGIN + RAISERROR('Incompatible Parameters: @Mode set to %d and @OutputType set to NONE',12,1,@Mode); + RETURN; + END; + */ +END; - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.is_big_spills = 1 - ) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 59, - 100, - 'TempDB', - '>500mb Spills', - 'https://www.brentozar.com/blitzcache/tempdb-spills/', - 'This query spills >500mb to tempdb on average. One way or another, this query didn''t get enough memory') ; +IF OBJECT_ID('tempdb..#IndexSanity') IS NOT NULL + DROP TABLE #IndexSanity; +IF OBJECT_ID('tempdb..#IndexPartitionSanity') IS NOT NULL + DROP TABLE #IndexPartitionSanity; - END; +IF OBJECT_ID('tempdb..#IndexSanitySize') IS NOT NULL + DROP TABLE #IndexSanitySize; - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.is_mstvf = 1 - ) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 60, - 100, - 'Functions', - 'MSTVFs', - 'https://www.brentozar.com/blitzcache/tvf-join/', - 'Execution plans have been found that join to table valued functions (TVFs). TVFs produce inaccurate estimates of the number of rows returned and can lead to any number of query plan problems.'); +IF OBJECT_ID('tempdb..#IndexColumns') IS NOT NULL + DROP TABLE #IndexColumns; - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.is_mm_join = 1 - ) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 61, - 100, - 'Complexity', - 'Many to Many Merge', - 'https://www.brentozar.com/archive/2018/04/many-mysteries-merge-joins/', - 'These use secret worktables that could be doing lots of reads. Occurs when join inputs aren''t known to be unique. Can be really bad when parallel.'); +IF OBJECT_ID('tempdb..#MissingIndexes') IS NOT NULL + DROP TABLE #MissingIndexes; - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.is_nonsargable = 1 - ) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 62, - 50, - 'Non-SARGable queries', - 'non-SARGables', - 'https://www.brentozar.com/blitzcache/non-sargable-predicates/', - 'Looks for intrinsic functions and expressions as predicates, and leading wildcard LIKE searches.'); +IF OBJECT_ID('tempdb..#ForeignKeys') IS NOT NULL + DROP TABLE #ForeignKeys; - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE CompileTime > 5000 - ) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 63, - 100, - 'Complexity', - 'Long Compile Time', - 'https://www.brentozar.com/blitzcache/high-compilers/', - 'Queries are taking >5 seconds to compile. This can be normal for large plans, but be careful if they compile frequently'); +IF OBJECT_ID('tempdb..#UnindexedForeignKeys') IS NOT NULL + DROP TABLE #UnindexedForeignKeys; - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE CompileCPU > 5000 - ) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 64, - 50, - 'Complexity', - 'High Compile CPU', - 'https://www.brentozar.com/blitzcache/high-compilers/', - 'Queries taking >5 seconds of CPU to compile. If CPU is high and plans like this compile frequently, they may be related'); +IF OBJECT_ID('tempdb..#BlitzIndexResults') IS NOT NULL + DROP TABLE #BlitzIndexResults; + +IF OBJECT_ID('tempdb..#IndexCreateTsql') IS NOT NULL + DROP TABLE #IndexCreateTsql; - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE CompileMemory > 1024 - AND ((CompileMemory) / (1 * CASE WHEN MaxCompileMemory = 0 THEN 1 ELSE MaxCompileMemory END) * 100.) >= 10. - ) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 65, - 50, - 'Complexity', - 'High Compile Memory', - 'https://www.brentozar.com/blitzcache/high-compilers/', - 'Queries taking 10% of Max Compile Memory. If you see high RESOURCE_SEMAPHORE_QUERY_COMPILE waits, these may be related'); +IF OBJECT_ID('tempdb..#DatabaseList') IS NOT NULL + DROP TABLE #DatabaseList; - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.select_with_writes = 1 - ) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 66, - 50, - 'Complexity', - 'Selects w/ Writes', - 'https://dba.stackexchange.com/questions/191825/', - 'This is thrown when reads cause writes that are not already flagged as big spills (2016+) or index spools.'); +IF OBJECT_ID('tempdb..#Statistics') IS NOT NULL + DROP TABLE #Statistics; - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.is_table_spool_expensive = 1 - ) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 67, - 150, - 'Expensive Table Spool', - 'You have a table spool, this is usually a sign that queries are doing unnecessary work', - 'https://sqlperformance.com/2019/09/sql-performance/nested-loops-joins-performance-spools', - 'Check for non-SARGable predicates, or a lot of work being done inside a nested loops join') ; +IF OBJECT_ID('tempdb..#PartitionCompressionInfo') IS NOT NULL + DROP TABLE #PartitionCompressionInfo; - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.is_table_spool_more_rows = 1 - ) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 68, - 150, - 'Table Spools Many Rows', - 'You have a table spool that spools more rows than the query returns', - 'https://sqlperformance.com/2019/09/sql-performance/nested-loops-joins-performance-spools', - 'Check for non-SARGable predicates, or a lot of work being done inside a nested loops join'); +IF OBJECT_ID('tempdb..#ComputedColumns') IS NOT NULL + DROP TABLE #ComputedColumns; + +IF OBJECT_ID('tempdb..#TraceStatus') IS NOT NULL + DROP TABLE #TraceStatus; - IF EXISTS (SELECT 1/0 - FROM #plan_creation p - WHERE (p.percent_24 > 0) - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - SELECT SPID, - 999, - CASE WHEN ISNULL(p.percent_24, 0) > 75 THEN 1 ELSE 254 END AS Priority, - 'Plan Cache Information', - CASE WHEN ISNULL(p.percent_24, 0) > 75 THEN 'Plan Cache Instability' ELSE 'Plan Cache Stability' END AS Finding, - 'https://www.brentozar.com/archive/2018/07/tsql2sday-how-much-plan-cache-history-do-you-have/', - 'You have ' + CONVERT(NVARCHAR(10), ISNULL(p.total_plans, 0)) - + ' total plans in your cache, with ' - + CONVERT(NVARCHAR(10), ISNULL(p.percent_24, 0)) - + '% plans created in the past 24 hours, ' - + CONVERT(NVARCHAR(10), ISNULL(p.percent_4, 0)) - + '% created in the past 4 hours, and ' - + CONVERT(NVARCHAR(10), ISNULL(p.percent_1, 0)) - + '% created in the past 1 hour. ' - + 'When these percentages are high, it may be a sign of memory pressure or plan cache instability.' - FROM #plan_creation p ; +IF OBJECT_ID('tempdb..#TemporalTables') IS NOT NULL + DROP TABLE #TemporalTables; - IF EXISTS (SELECT 1/0 - FROM #plan_usage p - WHERE p.percent_duplicate > 5 - AND spid = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - SELECT spid, - 999, - CASE WHEN ISNULL(p.percent_duplicate, 0) > 75 THEN 1 ELSE 254 END AS Priority, - 'Plan Cache Information', - CASE WHEN ISNULL(p.percent_duplicate, 0) > 75 THEN 'Many Duplicate Plans' ELSE 'Duplicate Plans' END AS Finding, - 'https://www.brentozar.com/archive/2018/03/why-multiple-plans-for-one-query-are-bad/', - 'You have ' + CONVERT(NVARCHAR(10), p.total_plans) - + ' plans in your cache, and ' - + CONVERT(NVARCHAR(10), p.percent_duplicate) - + '% are duplicates with more than 5 entries' - + ', meaning similar queries are generating the same plan repeatedly.' - + ' Forced Parameterization may fix the issue. To find troublemakers, use: EXEC sp_BlitzCache @SortOrder = ''query hash''; ' - FROM #plan_usage AS p ; +IF OBJECT_ID('tempdb..#CheckConstraints') IS NOT NULL + DROP TABLE #CheckConstraints; - IF EXISTS (SELECT 1/0 - FROM #plan_usage p - WHERE p.percent_single > 5 - AND spid = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - SELECT spid, - 999, - CASE WHEN ISNULL(p.percent_single, 0) > 75 THEN 1 ELSE 254 END AS Priority, - 'Plan Cache Information', - CASE WHEN ISNULL(p.percent_single, 0) > 75 THEN 'Many Single-Use Plans' ELSE 'Single-Use Plans' END AS Finding, - 'https://www.brentozar.com/blitz/single-use-plans-procedure-cache/', - 'You have ' + CONVERT(NVARCHAR(10), p.total_plans) - + ' plans in your cache, and ' - + CONVERT(NVARCHAR(10), p.percent_single) - + '% are single use plans' - + ', meaning SQL Server thinks it''s seeing a lot of "new" queries and creating plans for them.' - + ' Forced Parameterization and/or Optimize For Ad Hoc Workloads may fix the issue.' - + 'To find troublemakers, use: EXEC sp_BlitzCache @SortOrder = ''query hash''; ' - FROM #plan_usage AS p ; +IF OBJECT_ID('tempdb..#FilteredIndexes') IS NOT NULL + DROP TABLE #FilteredIndexes; - IF @is_tokenstore_big = 1 - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - SELECT @@SPID, - 69, - 10, - N'Large USERSTORE_TOKENPERM cache: ' + CONVERT(NVARCHAR(11), @user_perm_gb_out) + N'GB', - N'The USERSTORE_TOKENPERM is taking up ' + CONVERT(NVARCHAR(11), @user_perm_percent) - + N'% of the buffer pool, and your plan cache seems to be unstable', - N'https://www.brentozar.com/go/userstore', - N'A growing USERSTORE_TOKENPERM cache can cause the plan cache to clear out' +IF OBJECT_ID('tempdb..#Ignore_Databases') IS NOT NULL + DROP TABLE #Ignore_Databases + +IF OBJECT_ID('tempdb..#dm_db_partition_stats_etc') IS NOT NULL + DROP TABLE #dm_db_partition_stats_etc +IF OBJECT_ID('tempdb..#dm_db_index_operational_stats') IS NOT NULL + DROP TABLE #dm_db_index_operational_stats - IF @v >= 11 - BEGIN - IF EXISTS (SELECT 1/0 - FROM #trace_flags AS tf - WHERE tf.global_trace_flags IS NOT NULL - ) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 1000, - 255, - 'Global Trace Flags Enabled', - 'You have Global Trace Flags enabled on your server', - 'https://www.brentozar.com/blitz/trace-flags-enabled-globally/', - 'You have the following Global Trace Flags enabled: ' + (SELECT TOP 1 tf.global_trace_flags FROM #trace_flags AS tf WHERE tf.global_trace_flags IS NOT NULL)) ; - END; + RAISERROR (N'Create temp tables.',0,1) WITH NOWAIT; + CREATE TABLE #BlitzIndexResults + ( + blitz_result_id INT IDENTITY PRIMARY KEY, + check_id INT NOT NULL, + index_sanity_id INT NULL, + Priority INT NULL, + findings_group NVARCHAR(4000) NOT NULL, + finding NVARCHAR(200) NOT NULL, + [database_name] NVARCHAR(128) NULL, + URL NVARCHAR(200) NOT NULL, + details NVARCHAR(MAX) NOT NULL, + index_definition NVARCHAR(MAX) NOT NULL, + secret_columns NVARCHAR(MAX) NULL, + index_usage_summary NVARCHAR(MAX) NULL, + index_size_summary NVARCHAR(MAX) NULL, + create_tsql NVARCHAR(MAX) NULL, + more_info NVARCHAR(MAX) NULL, + sample_query_plan XML NULL + ); - IF NOT EXISTS (SELECT 1/0 - FROM ##BlitzCacheResults AS bcr - WHERE bcr.Priority = 2147483646 - ) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 2147483646, - 255, - 'Need more help?' , - 'Paste your plan on the internet!', - 'http://pastetheplan.com', - 'This makes it easy to share plans and post them to Q&A sites like https://dba.stackexchange.com/!') ; + CREATE TABLE #IndexSanity + ( + [index_sanity_id] INT IDENTITY PRIMARY KEY CLUSTERED, + [database_id] SMALLINT NOT NULL , + [object_id] INT NOT NULL , + [index_id] INT NOT NULL , + [index_type] TINYINT NOT NULL, + [database_name] NVARCHAR(128) NOT NULL , + [schema_name] NVARCHAR(128) NOT NULL , + [object_name] NVARCHAR(128) NOT NULL , + index_name NVARCHAR(128) NULL , + key_column_names NVARCHAR(MAX) NULL , + key_column_names_with_sort_order NVARCHAR(MAX) NULL , + key_column_names_with_sort_order_no_types NVARCHAR(MAX) NULL , + count_key_columns INT NULL , + include_column_names NVARCHAR(MAX) NULL , + include_column_names_no_types NVARCHAR(MAX) NULL , + count_included_columns INT NULL , + partition_key_column_name NVARCHAR(MAX) NULL, + filter_definition NVARCHAR(MAX) NOT NULL , + optimize_for_sequential_key BIT NULL, + is_indexed_view BIT NOT NULL , + is_unique BIT NOT NULL , + is_primary_key BIT NOT NULL , + is_unique_constraint BIT NOT NULL , + is_XML bit NOT NULL, + is_spatial BIT NOT NULL, + is_NC_columnstore BIT NOT NULL, + is_CX_columnstore BIT NOT NULL, + is_in_memory_oltp BIT NOT NULL , + is_disabled BIT NOT NULL , + is_hypothetical BIT NOT NULL , + is_padded BIT NOT NULL , + fill_factor SMALLINT NOT NULL , + user_seeks BIGINT NOT NULL , + user_scans BIGINT NOT NULL , + user_lookups BIGINT NOT NULL , + user_updates BIGINT NULL , + last_user_seek DATETIME NULL , + last_user_scan DATETIME NULL , + last_user_lookup DATETIME NULL , + last_user_update DATETIME NULL , + is_referenced_by_foreign_key BIT DEFAULT(0), + secret_columns NVARCHAR(MAX) NULL, + count_secret_columns INT NULL, + create_date DATETIME NOT NULL, + modify_date DATETIME NOT NULL, + filter_columns_not_in_index NVARCHAR(MAX), + [db_schema_object_name] AS [schema_name] + N'.' + [object_name] , + [db_schema_object_indexid] AS [schema_name] + N'.' + [object_name] + + CASE WHEN [index_name] IS NOT NULL THEN N'.' + index_name + ELSE N'' + END + N' (' + CAST(index_id AS NVARCHAR(20)) + N')' , + first_key_column_name AS CASE WHEN count_key_columns > 1 + THEN LEFT(key_column_names, CHARINDEX(',', key_column_names, 0) - 1) + ELSE key_column_names + END , + index_definition AS + CASE WHEN partition_key_column_name IS NOT NULL + THEN N'[PARTITIONED BY:' + partition_key_column_name + N']' + ELSE '' + END + + CASE index_id + WHEN 0 THEN N'[HEAP] ' + WHEN 1 THEN N'[CX] ' + ELSE N'' END + CASE WHEN is_indexed_view = 1 THEN N'[VIEW] ' + ELSE N'' END + CASE WHEN is_primary_key = 1 THEN N'[PK] ' + ELSE N'' END + CASE WHEN is_XML = 1 THEN N'[XML] ' + ELSE N'' END + CASE WHEN is_spatial = 1 THEN N'[SPATIAL] ' + ELSE N'' END + CASE WHEN is_NC_columnstore = 1 THEN N'[COLUMNSTORE] ' + ELSE N'' END + CASE WHEN is_in_memory_oltp = 1 THEN N'[IN-MEMORY] ' + ELSE N'' END + CASE WHEN is_disabled = 1 THEN N'[DISABLED] ' + ELSE N'' END + CASE WHEN is_hypothetical = 1 THEN N'[HYPOTHETICAL] ' + ELSE N'' END + CASE WHEN is_unique = 1 AND is_primary_key = 0 AND is_unique_constraint = 0 THEN N'[UNIQUE] ' + ELSE N'' END + CASE WHEN is_unique_constraint = 1 AND is_primary_key = 0 THEN N'[UNIQUE CONSTRAINT] ' + ELSE N'' END + CASE WHEN count_key_columns > 0 THEN + N'[' + CAST(count_key_columns AS NVARCHAR(10)) + N' KEY' + + CASE WHEN count_key_columns > 1 THEN N'S' ELSE N'' END + + N'] ' + LTRIM(key_column_names_with_sort_order) + ELSE N'' END + CASE WHEN count_included_columns > 0 THEN + N' [' + CAST(count_included_columns AS NVARCHAR(10)) + N' INCLUDE' + + + CASE WHEN count_included_columns > 1 THEN N'S' ELSE N'' END + + N'] ' + include_column_names + ELSE N'' END + CASE WHEN filter_definition <> N'' THEN N' [FILTER] ' + filter_definition + ELSE N'' END , + [total_reads] AS user_seeks + user_scans + user_lookups, + [reads_per_write] AS CAST(CASE WHEN user_updates > 0 + THEN ( user_seeks + user_scans + user_lookups ) / (1.0 * user_updates) + ELSE 0 END AS MONEY) , + [index_usage_summary] AS + CASE WHEN is_spatial = 1 THEN N'Not Tracked' + WHEN is_disabled = 1 THEN N'Disabled' + ELSE N'Reads: ' + + REPLACE(CONVERT(NVARCHAR(30),CAST((user_seeks + user_scans + user_lookups) AS MONEY), 1), N'.00', N'') + + CASE WHEN user_seeks + user_scans + user_lookups > 0 THEN + N' (' + + RTRIM( + CASE WHEN user_seeks > 0 THEN REPLACE(CONVERT(NVARCHAR(30),CAST((user_seeks) AS MONEY), 1), N'.00', N'') + N' seek ' ELSE N'' END + + CASE WHEN user_scans > 0 THEN REPLACE(CONVERT(NVARCHAR(30),CAST((user_scans) AS MONEY), 1), N'.00', N'') + N' scan ' ELSE N'' END + + CASE WHEN user_lookups > 0 THEN REPLACE(CONVERT(NVARCHAR(30),CAST((user_lookups) AS MONEY), 1), N'.00', N'') + N' lookup' ELSE N'' END + ) + + N') ' + ELSE N' ' + END + + N'Writes: ' + + REPLACE(CONVERT(NVARCHAR(30),CAST(user_updates AS MONEY), 1), N'.00', N'') + END /* First "end" is about is_spatial */, + [more_info] AS + CASE WHEN is_in_memory_oltp = 1 + THEN N'EXEC dbo.sp_BlitzInMemoryOLTP @dbName=' + QUOTENAME([database_name],N'''') + + N', @tableName=' + QUOTENAME([object_name],N'''') + N';' + ELSE N'EXEC dbo.sp_BlitzIndex @DatabaseName=' + QUOTENAME([database_name],N'''') + + N', @SchemaName=' + QUOTENAME([schema_name],N'''') + N', @TableName=' + QUOTENAME([object_name],N'''') + N';' + END + ); + RAISERROR (N'Adding UQ index on #IndexSanity (database_id, object_id, index_id)',0,1) WITH NOWAIT; + IF NOT EXISTS(SELECT 1 FROM tempdb.sys.indexes WHERE name='uq_database_id_object_id_index_id') + CREATE UNIQUE INDEX uq_database_id_object_id_index_id ON #IndexSanity (database_id, object_id, index_id); + CREATE TABLE #IndexPartitionSanity + ( + [index_partition_sanity_id] INT IDENTITY, + [index_sanity_id] INT NULL , + [database_id] INT NOT NULL , + [object_id] INT NOT NULL , + [schema_name] NVARCHAR(128) NOT NULL, + [index_id] INT NOT NULL , + [partition_number] INT NOT NULL , + row_count BIGINT NOT NULL , + reserved_MB NUMERIC(29,2) NOT NULL , + reserved_LOB_MB NUMERIC(29,2) NOT NULL , + reserved_row_overflow_MB NUMERIC(29,2) NOT NULL , + reserved_dictionary_MB NUMERIC(29,2) NOT NULL , + leaf_insert_count BIGINT NULL , + leaf_delete_count BIGINT NULL , + leaf_update_count BIGINT NULL , + range_scan_count BIGINT NULL , + singleton_lookup_count BIGINT NULL , + forwarded_fetch_count BIGINT NULL , + lob_fetch_in_pages BIGINT NULL , + lob_fetch_in_bytes BIGINT NULL , + row_overflow_fetch_in_pages BIGINT NULL , + row_overflow_fetch_in_bytes BIGINT NULL , + row_lock_count BIGINT NULL , + row_lock_wait_count BIGINT NULL , + row_lock_wait_in_ms BIGINT NULL , + page_lock_count BIGINT NULL , + page_lock_wait_count BIGINT NULL , + page_lock_wait_in_ms BIGINT NULL , + index_lock_promotion_attempt_count BIGINT NULL , + index_lock_promotion_count BIGINT NULL, + data_compression_desc NVARCHAR(60) NULL, + page_latch_wait_count BIGINT NULL, + page_latch_wait_in_ms BIGINT NULL, + page_io_latch_wait_count BIGINT NULL, + page_io_latch_wait_in_ms BIGINT NULL, + lock_escalation_desc nvarchar(60) NULL + ); - IF NOT EXISTS (SELECT 1/0 - FROM ##BlitzCacheResults AS bcr - WHERE bcr.Priority = 2147483647 - ) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 2147483647, - 255, - 'Thanks for using sp_BlitzCache!' , - 'From Your Community Volunteers', - 'http://FirstResponderKit.org', - 'We hope you found this tool useful. Current version: ' + @Version + ' released on ' + CONVERT(NVARCHAR(30), @VersionDate) + '.') ; - - END; - - - SELECT Priority, - FindingsGroup, - Finding, - URL, - Details, - CheckID - FROM ##BlitzCacheResults - WHERE SPID = @@SPID - GROUP BY Priority, - FindingsGroup, - Finding, - URL, - Details, - CheckID - ORDER BY Priority ASC, FindingsGroup, Finding, CheckID ASC - OPTION (RECOMPILE); -END; - -IF @Debug = 1 - BEGIN - - SELECT '##BlitzCacheResults' AS table_name, * - FROM ##BlitzCacheResults - OPTION ( RECOMPILE ); - - SELECT '##BlitzCacheProcs' AS table_name, * - FROM ##BlitzCacheProcs - OPTION ( RECOMPILE ); - - SELECT '#statements' AS table_name, * - FROM #statements AS s - OPTION (RECOMPILE); - - SELECT '#query_plan' AS table_name, * - FROM #query_plan AS qp - OPTION (RECOMPILE); - - SELECT '#relop' AS table_name, * - FROM #relop AS r - OPTION (RECOMPILE); - - SELECT '#only_query_hashes' AS table_name, * - FROM #only_query_hashes - OPTION ( RECOMPILE ); - - SELECT '#ignore_query_hashes' AS table_name, * - FROM #ignore_query_hashes - OPTION ( RECOMPILE ); - - SELECT '#only_sql_handles' AS table_name, * - FROM #only_sql_handles - OPTION ( RECOMPILE ); - - SELECT '#ignore_sql_handles' AS table_name, * - FROM #ignore_sql_handles - OPTION ( RECOMPILE ); - - SELECT '#p' AS table_name, * - FROM #p - OPTION ( RECOMPILE ); - - SELECT '#checkversion' AS table_name, * - FROM #checkversion - OPTION ( RECOMPILE ); - - SELECT '#configuration' AS table_name, * - FROM #configuration - OPTION ( RECOMPILE ); - - SELECT '#stored_proc_info' AS table_name, * - FROM #stored_proc_info - OPTION ( RECOMPILE ); + CREATE TABLE #IndexSanitySize + ( + [index_sanity_size_id] INT IDENTITY NOT NULL , + [index_sanity_id] INT NULL , + [database_id] INT NOT NULL, + [schema_name] NVARCHAR(128) NOT NULL, + partition_count INT NOT NULL , + total_rows BIGINT NOT NULL , + total_reserved_MB NUMERIC(29,2) NOT NULL , + total_reserved_LOB_MB NUMERIC(29,2) NOT NULL , + total_reserved_row_overflow_MB NUMERIC(29,2) NOT NULL , + total_reserved_dictionary_MB NUMERIC(29,2) NOT NULL , + total_leaf_delete_count BIGINT NULL, + total_leaf_update_count BIGINT NULL, + total_range_scan_count BIGINT NULL, + total_singleton_lookup_count BIGINT NULL, + total_forwarded_fetch_count BIGINT NULL, + total_row_lock_count BIGINT NULL , + total_row_lock_wait_count BIGINT NULL , + total_row_lock_wait_in_ms BIGINT NULL , + avg_row_lock_wait_in_ms BIGINT NULL , + total_page_lock_count BIGINT NULL , + total_page_lock_wait_count BIGINT NULL , + total_page_lock_wait_in_ms BIGINT NULL , + avg_page_lock_wait_in_ms BIGINT NULL , + total_index_lock_promotion_attempt_count BIGINT NULL , + total_index_lock_promotion_count BIGINT NULL , + data_compression_desc NVARCHAR(4000) NULL, + page_latch_wait_count BIGINT NULL, + page_latch_wait_in_ms BIGINT NULL, + page_io_latch_wait_count BIGINT NULL, + page_io_latch_wait_in_ms BIGINT NULL, + lock_escalation_desc nvarchar(60) NULL, + index_size_summary AS ISNULL( + CASE WHEN partition_count > 1 + THEN N'[' + CAST(partition_count AS NVARCHAR(10)) + N' PARTITIONS] ' + ELSE N'' + END + REPLACE(CONVERT(NVARCHAR(30),CAST([total_rows] AS MONEY), 1), N'.00', N'') + N' rows; ' + + CASE WHEN total_reserved_MB > 1024 THEN + CAST(CAST(total_reserved_MB/1024. AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'GB' + ELSE + CAST(CAST(total_reserved_MB AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'MB' + END + + CASE WHEN total_reserved_LOB_MB > 1024 THEN + N'; ' + CAST(CAST(total_reserved_LOB_MB/1024. AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'GB ' + CASE WHEN total_reserved_dictionary_MB = 0 THEN N'LOB' ELSE N'Columnstore' END + WHEN total_reserved_LOB_MB > 0 THEN + N'; ' + CAST(CAST(total_reserved_LOB_MB AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'MB ' + CASE WHEN total_reserved_dictionary_MB = 0 THEN N'LOB' ELSE N'Columnstore' END + ELSE '' + END + + CASE WHEN total_reserved_row_overflow_MB > 1024 THEN + N'; ' + CAST(CAST(total_reserved_row_overflow_MB/1024. AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'GB Row Overflow' + WHEN total_reserved_row_overflow_MB > 0 THEN + N'; ' + CAST(CAST(total_reserved_row_overflow_MB AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'MB Row Overflow' + ELSE '' + END + + CASE WHEN total_reserved_dictionary_MB > 1024 THEN + N'; ' + CAST(CAST(total_reserved_dictionary_MB/1024. AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'GB Dictionaries' + WHEN total_reserved_dictionary_MB > 0 THEN + N'; ' + CAST(CAST(total_reserved_dictionary_MB AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'MB Dictionaries' + ELSE '' + END , + N'Error- NULL in computed column'), + index_op_stats AS ISNULL( + ( + REPLACE(CONVERT(NVARCHAR(30),CAST(total_singleton_lookup_count AS MONEY), 1),N'.00',N'') + N' singleton lookups; ' + + REPLACE(CONVERT(NVARCHAR(30),CAST(total_range_scan_count AS MONEY), 1),N'.00',N'') + N' scans/seeks; ' + + REPLACE(CONVERT(NVARCHAR(30),CAST(total_leaf_delete_count AS MONEY), 1),N'.00',N'') + N' deletes; ' + + REPLACE(CONVERT(NVARCHAR(30),CAST(total_leaf_update_count AS MONEY), 1),N'.00',N'') + N' updates; ' + + CASE WHEN ISNULL(total_forwarded_fetch_count,0) >0 THEN + REPLACE(CONVERT(NVARCHAR(30),CAST(total_forwarded_fetch_count AS MONEY), 1),N'.00',N'') + N' forward records fetched; ' + ELSE N'' END - SELECT '#conversion_info' AS table_name, * - FROM #conversion_info AS ci - OPTION ( RECOMPILE ); - - SELECT '#variable_info' AS table_name, * - FROM #variable_info AS vi - OPTION ( RECOMPILE ); + /* rows will only be in this dmv when data is in memory for the table */ + ), N'Table metadata not in memory'), + index_lock_wait_summary AS ISNULL( + CASE WHEN total_row_lock_wait_count = 0 AND total_page_lock_wait_count = 0 AND + total_index_lock_promotion_attempt_count = 0 THEN N'0 lock waits; ' + + CASE WHEN lock_escalation_desc = N'DISABLE' THEN N'Lock escalation DISABLE.' + ELSE N'' + END + ELSE + CASE WHEN total_row_lock_wait_count > 0 THEN + N'Row lock waits: ' + REPLACE(CONVERT(NVARCHAR(30),CAST(total_row_lock_wait_count AS MONEY), 1), N'.00', N'') + + N'; total duration: ' + + CASE WHEN total_row_lock_wait_in_ms >= 60000 THEN /*More than 1 min*/ + REPLACE(CONVERT(NVARCHAR(30),CAST((total_row_lock_wait_in_ms/60000) AS MONEY), 1), N'.00', N'') + N' minutes; ' + ELSE + REPLACE(CONVERT(NVARCHAR(30),CAST(ISNULL(total_row_lock_wait_in_ms/1000,0) AS MONEY), 1), N'.00', N'') + N' seconds; ' + END + + N'avg duration: ' + + CASE WHEN avg_row_lock_wait_in_ms >= 60000 THEN /*More than 1 min*/ + REPLACE(CONVERT(NVARCHAR(30),CAST((avg_row_lock_wait_in_ms/60000) AS MONEY), 1), N'.00', N'') + N' minutes; ' + ELSE + REPLACE(CONVERT(NVARCHAR(30),CAST(ISNULL(avg_row_lock_wait_in_ms/1000,0) AS MONEY), 1), N'.00', N'') + N' seconds; ' + END + ELSE N'' + END + + CASE WHEN total_page_lock_wait_count > 0 THEN + N'Page lock waits: ' + REPLACE(CONVERT(NVARCHAR(30),CAST(total_page_lock_wait_count AS MONEY), 1), N'.00', N'') + + N'; total duration: ' + + CASE WHEN total_page_lock_wait_in_ms >= 60000 THEN /*More than 1 min*/ + REPLACE(CONVERT(NVARCHAR(30),CAST((total_page_lock_wait_in_ms/60000) AS MONEY), 1), N'.00', N'') + N' minutes; ' + ELSE + REPLACE(CONVERT(NVARCHAR(30),CAST(ISNULL(total_page_lock_wait_in_ms/1000,0) AS MONEY), 1), N'.00', N'') + N' seconds; ' + END + + N'avg duration: ' + + CASE WHEN avg_page_lock_wait_in_ms >= 60000 THEN /*More than 1 min*/ + REPLACE(CONVERT(NVARCHAR(30),CAST((avg_page_lock_wait_in_ms/60000) AS MONEY), 1), N'.00', N'') + N' minutes; ' + ELSE + REPLACE(CONVERT(NVARCHAR(30),CAST(ISNULL(avg_page_lock_wait_in_ms/1000,0) AS MONEY), 1), N'.00', N'') + N' seconds; ' + END + ELSE N'' + END + + CASE WHEN total_index_lock_promotion_attempt_count > 0 THEN + N'Lock escalation attempts: ' + REPLACE(CONVERT(NVARCHAR(30),CAST(total_index_lock_promotion_attempt_count AS MONEY), 1), N'.00', N'') + + N'; Actual Escalations: ' + REPLACE(CONVERT(NVARCHAR(30),CAST(ISNULL(total_index_lock_promotion_count,0) AS MONEY), 1), N'.00', N'') +N'; ' + ELSE N'' + END + + CASE WHEN lock_escalation_desc = N'DISABLE' THEN + N'Lock escalation is disabled.' + ELSE N'' + END + END + ,'Error- NULL in computed column') + ); - SELECT '#missing_index_xml' AS table_name, * - FROM #missing_index_xml AS mix - OPTION ( RECOMPILE ); + CREATE TABLE #IndexColumns + ( + [database_id] INT NOT NULL, + [schema_name] NVARCHAR(128), + [object_id] INT NOT NULL , + [index_id] INT NOT NULL , + [key_ordinal] INT NULL , + is_included_column BIT NULL , + is_descending_key BIT NULL , + [partition_ordinal] INT NULL , + column_name NVARCHAR(256) NOT NULL , + system_type_name NVARCHAR(256) NOT NULL, + max_length SMALLINT NOT NULL, + [precision] TINYINT NOT NULL, + [scale] TINYINT NOT NULL, + collation_name NVARCHAR(256) NULL, + is_nullable BIT NULL, + is_identity BIT NULL, + is_computed BIT NULL, + is_replicated BIT NULL, + is_sparse BIT NULL, + is_filestream BIT NULL, + seed_value DECIMAL(38,0) NULL, + increment_value DECIMAL(38,0) NULL , + last_value DECIMAL(38,0) NULL, + is_not_for_replication BIT NULL + ); + CREATE CLUSTERED INDEX CLIX_database_id_object_id_index_id ON #IndexColumns + (database_id, object_id, index_id); - SELECT '#missing_index_schema' AS table_name, * - FROM #missing_index_schema AS mis - OPTION ( RECOMPILE ); + CREATE TABLE #MissingIndexes + ([database_id] INT NOT NULL, + [object_id] INT NOT NULL, + [database_name] NVARCHAR(128) NOT NULL , + [schema_name] NVARCHAR(128) NOT NULL , + [table_name] NVARCHAR(128), + [statement] NVARCHAR(512) NOT NULL, + magic_benefit_number AS (( user_seeks + user_scans ) * avg_total_user_cost * avg_user_impact), + avg_total_user_cost NUMERIC(29,4) NOT NULL, + avg_user_impact NUMERIC(29,1) NOT NULL, + user_seeks BIGINT NOT NULL, + user_scans BIGINT NOT NULL, + unique_compiles BIGINT NULL, + equality_columns NVARCHAR(MAX), + equality_columns_with_data_type NVARCHAR(MAX), + inequality_columns NVARCHAR(MAX), + inequality_columns_with_data_type NVARCHAR(MAX), + included_columns NVARCHAR(MAX), + included_columns_with_data_type NVARCHAR(MAX), + is_low BIT, + [index_estimated_impact] AS + REPLACE(CONVERT(NVARCHAR(256),CAST(CAST( + (user_seeks + user_scans) + AS BIGINT) AS MONEY), 1), '.00', '') + N' use' + + CASE WHEN (user_seeks + user_scans) > 1 THEN N's' ELSE N'' END + +N'; Impact: ' + CAST(avg_user_impact AS NVARCHAR(30)) + + N'%; Avg query cost: ' + + CAST(avg_total_user_cost AS NVARCHAR(30)), + [missing_index_details] AS + CASE WHEN COALESCE(equality_columns_with_data_type,equality_columns) IS NOT NULL + THEN N'EQUALITY: ' + COALESCE(CAST(equality_columns_with_data_type AS NVARCHAR(MAX)), CAST(equality_columns AS NVARCHAR(MAX))) + N' ' + ELSE N'' END + - SELECT '#missing_index_usage' AS table_name, * - FROM #missing_index_usage AS miu - OPTION ( RECOMPILE ); + CASE WHEN COALESCE(inequality_columns_with_data_type,inequality_columns) IS NOT NULL + THEN N'INEQUALITY: ' + COALESCE(CAST(inequality_columns_with_data_type AS NVARCHAR(MAX)), CAST(inequality_columns AS NVARCHAR(MAX))) + N' ' + ELSE N'' END + - SELECT '#missing_index_detail' AS table_name, * - FROM #missing_index_detail AS mid - OPTION ( RECOMPILE ); + CASE WHEN COALESCE(included_columns_with_data_type,included_columns) IS NOT NULL + THEN N'INCLUDE: ' + COALESCE(CAST(included_columns_with_data_type AS NVARCHAR(MAX)), CAST(included_columns AS NVARCHAR(MAX))) + N' ' + ELSE N'' END, + [create_tsql] AS N'CREATE INDEX [' + + LEFT(REPLACE(REPLACE(REPLACE(REPLACE( + ISNULL(equality_columns,N'')+ + CASE WHEN equality_columns IS NOT NULL AND inequality_columns IS NOT NULL THEN N'_' ELSE N'' END + + ISNULL(inequality_columns,''),',','') + ,'[',''),']',''),' ','_') + + CASE WHEN included_columns IS NOT NULL THEN N'_Includes' ELSE N'' END, 128) + N'] ON ' + + [statement] + N' (' + ISNULL(equality_columns,N'') + + CASE WHEN equality_columns IS NOT NULL AND inequality_columns IS NOT NULL THEN N', ' ELSE N'' END + + CASE WHEN inequality_columns IS NOT NULL THEN inequality_columns ELSE N'' END + + ') ' + CASE WHEN included_columns IS NOT NULL THEN N' INCLUDE (' + included_columns + N')' ELSE N'' END + + N' WITH (' + + N'FILLFACTOR=100, ONLINE=?, SORT_IN_TEMPDB=?, DATA_COMPRESSION=?' + + N')' + + N';' + , + [more_info] AS N'EXEC dbo.sp_BlitzIndex @DatabaseName=' + QUOTENAME([database_name],'''') + + N', @SchemaName=' + QUOTENAME([schema_name],'''') + N', @TableName=' + QUOTENAME([table_name],'''') + N';', + [sample_query_plan] XML NULL + ); - SELECT '#missing_index_pretty' AS table_name, * - FROM #missing_index_pretty AS mip - OPTION ( RECOMPILE ); + CREATE TABLE #ForeignKeys ( + [database_id] INT NOT NULL, + [database_name] NVARCHAR(128) NOT NULL , + [schema_name] NVARCHAR(128) NOT NULL , + foreign_key_name NVARCHAR(256), + parent_object_id INT, + parent_object_name NVARCHAR(256), + referenced_object_id INT, + referenced_object_name NVARCHAR(256), + is_disabled BIT, + is_not_trusted BIT, + is_not_for_replication BIT, + parent_fk_columns NVARCHAR(MAX), + referenced_fk_columns NVARCHAR(MAX), + update_referential_action_desc NVARCHAR(16), + delete_referential_action_desc NVARCHAR(60) + ); - SELECT '#plan_creation' AS table_name, * - FROM #plan_creation - OPTION ( RECOMPILE ); - - SELECT '#plan_cost' AS table_name, * - FROM #plan_cost - OPTION ( RECOMPILE ); - - SELECT '#proc_costs' AS table_name, * - FROM #proc_costs - OPTION ( RECOMPILE ); - - SELECT '#stats_agg' AS table_name, * - FROM #stats_agg - OPTION ( RECOMPILE ); - - SELECT '#trace_flags' AS table_name, * - FROM #trace_flags - OPTION ( RECOMPILE ); + CREATE TABLE #UnindexedForeignKeys + ( + [database_id] INT NOT NULL, + [database_name] NVARCHAR(128) NOT NULL , + [schema_name] NVARCHAR(128) NOT NULL , + foreign_key_name NVARCHAR(256), + parent_object_name NVARCHAR(256), + parent_object_id INT, + referenced_object_name NVARCHAR(256), + referenced_object_id INT + ); + + CREATE TABLE #IndexCreateTsql ( + index_sanity_id INT NOT NULL, + create_tsql NVARCHAR(MAX) NOT NULL + ); - SELECT '#plan_usage' AS table_name, * - FROM #plan_usage - OPTION ( RECOMPILE ); + CREATE TABLE #DatabaseList ( + DatabaseName NVARCHAR(256), + secondary_role_allow_connections_desc NVARCHAR(50) - END; + ); - IF @OutputTableName IS NOT NULL - --Allow for output to ##DB so don't check for DB or schema name here - GOTO OutputResultsToTable; -RETURN; --Avoid going into the AllSort GOTO + CREATE TABLE #PartitionCompressionInfo ( + [index_sanity_id] INT NULL, + [partition_compression_detail] NVARCHAR(4000) NULL + ); -/*Begin code to sort by all*/ -AllSorts: -RAISERROR('Beginning all sort loop', 0, 1) WITH NOWAIT; + CREATE TABLE #Statistics ( + database_id INT NOT NULL, + database_name NVARCHAR(256) NOT NULL, + table_name NVARCHAR(128) NULL, + schema_name NVARCHAR(128) NULL, + index_name NVARCHAR(128) NULL, + column_names NVARCHAR(MAX) NULL, + statistics_name NVARCHAR(128) NULL, + last_statistics_update DATETIME NULL, + days_since_last_stats_update INT NULL, + rows BIGINT NULL, + rows_sampled BIGINT NULL, + percent_sampled DECIMAL(18, 1) NULL, + histogram_steps INT NULL, + modification_counter BIGINT NULL, + percent_modifications DECIMAL(18, 1) NULL, + modifications_before_auto_update INT 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 + ); + CREATE TABLE #ComputedColumns + ( + index_sanity_id INT IDENTITY(1, 1) NOT NULL, + database_name NVARCHAR(128) NULL, + database_id INT NOT NULL, + table_name NVARCHAR(128) NOT NULL, + schema_name NVARCHAR(128) NOT NULL, + column_name NVARCHAR(128) NULL, + is_nullable BIT NULL, + definition NVARCHAR(MAX) NULL, + uses_database_collation BIT NOT NULL, + is_persisted BIT NOT NULL, + is_computed BIT NOT NULL, + is_function INT NOT NULL, + column_definition NVARCHAR(MAX) NULL + ); + + CREATE TABLE #TraceStatus + ( + TraceFlag NVARCHAR(10) , + status BIT , + Global BIT , + Session BIT + ); -IF ( - @Top > 10 - AND @SkipAnalysis = 0 - AND @BringThePain = 0 - ) - BEGIN - RAISERROR( - ' - You''ve chosen a value greater than 10 to sort the whole plan cache by. - That can take a long time and harm performance. - Please choose a number <= 10, or set @BringThePain = 1 to signify you understand this might be a bad idea. - ', 0, 1) WITH NOWAIT; - RETURN; - END; + CREATE TABLE #TemporalTables + ( + index_sanity_id INT IDENTITY(1, 1) NOT NULL, + database_name NVARCHAR(128) NOT NULL, + database_id INT NOT NULL, + schema_name NVARCHAR(128) NOT NULL, + table_name NVARCHAR(128) NOT NULL, + history_table_name NVARCHAR(128) NOT NULL, + history_schema_name NVARCHAR(128) NOT NULL, + start_column_name NVARCHAR(128) NOT NULL, + end_column_name NVARCHAR(128) NOT NULL, + period_name NVARCHAR(128) NOT NULL + ); + CREATE TABLE #CheckConstraints + ( + index_sanity_id INT IDENTITY(1, 1) NOT NULL, + database_name NVARCHAR(128) NULL, + database_id INT NOT NULL, + table_name NVARCHAR(128) NOT NULL, + schema_name NVARCHAR(128) NOT NULL, + constraint_name NVARCHAR(128) NULL, + is_disabled BIT NULL, + definition NVARCHAR(MAX) NULL, + uses_database_collation BIT NOT NULL, + is_not_trusted BIT NOT NULL, + is_function INT NOT NULL, + column_definition NVARCHAR(MAX) NULL + ); -IF OBJECT_ID('tempdb..#checkversion_allsort') IS NULL - BEGIN - CREATE TABLE #checkversion_allsort - ( - version NVARCHAR(128), - common_version AS SUBSTRING(version, 1, CHARINDEX('.', version) + 1), - major AS PARSENAME(CONVERT(VARCHAR(32), version), 4), - minor AS PARSENAME(CONVERT(VARCHAR(32), version), 3), - build AS PARSENAME(CONVERT(VARCHAR(32), version), 2), - revision AS PARSENAME(CONVERT(VARCHAR(32), version), 1) - ); + CREATE TABLE #FilteredIndexes + ( + index_sanity_id INT IDENTITY(1, 1) NOT NULL, + database_name NVARCHAR(128) NULL, + database_id INT NOT NULL, + schema_name NVARCHAR(128) NOT NULL, + table_name NVARCHAR(128) NOT NULL, + index_name NVARCHAR(128) NULL, + column_name NVARCHAR(128) NULL + ); - INSERT INTO #checkversion_allsort - (version) - SELECT CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)) - OPTION ( RECOMPILE ); - END; + CREATE TABLE #Ignore_Databases + ( + DatabaseName NVARCHAR(128), + Reason NVARCHAR(100) + ); +/* Sanitize our inputs */ +SELECT + @OutputServerName = QUOTENAME(@OutputServerName), + @OutputDatabaseName = QUOTENAME(@OutputDatabaseName), + @OutputSchemaName = QUOTENAME(@OutputSchemaName), + @OutputTableName = QUOTENAME(@OutputTableName); + + +IF @GetAllDatabases = 1 + BEGIN + INSERT INTO #DatabaseList (DatabaseName) + SELECT DB_NAME(database_id) + FROM sys.databases + WHERE user_access_desc = 'MULTI_USER' + AND state_desc = 'ONLINE' + AND database_id > 4 + AND DB_NAME(database_id) NOT LIKE 'ReportServer%' + AND DB_NAME(database_id) NOT LIKE 'rdsadmin%' + AND LOWER(name) NOT IN('dbatools', 'dbadmin', 'dbmaintenance') + AND is_distributor = 0 + OPTION ( RECOMPILE ); -SELECT @v = common_version, - @build = build -FROM #checkversion_allsort -OPTION ( RECOMPILE ); - -IF OBJECT_ID('tempdb.. #bou_allsort') IS NULL - BEGIN - CREATE TABLE #bou_allsort - ( - Id INT IDENTITY(1, 1), - DatabaseName NVARCHAR(128), - Cost FLOAT, - QueryText NVARCHAR(MAX), - QueryType NVARCHAR(258), - Warnings VARCHAR(MAX), - QueryPlan XML, - missing_indexes XML, - implicit_conversion_info XML, - cached_execution_parameters XML, - ExecutionCount NVARCHAR(30), - ExecutionsPerMinute MONEY, - ExecutionWeight MONEY, - TotalCPU NVARCHAR(30), - AverageCPU NVARCHAR(30), - CPUWeight MONEY, - TotalDuration NVARCHAR(30), - AverageDuration NVARCHAR(30), - DurationWeight MONEY, - TotalReads NVARCHAR(30), - AverageReads NVARCHAR(30), - ReadWeight MONEY, - TotalWrites NVARCHAR(30), - AverageWrites NVARCHAR(30), - WriteWeight MONEY, - AverageReturnedRows MONEY, - MinGrantKB NVARCHAR(30), - MaxGrantKB NVARCHAR(30), - MinUsedGrantKB NVARCHAR(30), - MaxUsedGrantKB NVARCHAR(30), - AvgMaxMemoryGrant MONEY, - MinSpills NVARCHAR(30), - MaxSpills NVARCHAR(30), - TotalSpills NVARCHAR(30), - AvgSpills MONEY, - PlanCreationTime DATETIME, - LastExecutionTime DATETIME, - LastCompletionTime DATETIME, - PlanHandle VARBINARY(64), - SqlHandle VARBINARY(64), - SetOptions VARCHAR(MAX), - QueryHash BINARY(8), - PlanGenerationNum NVARCHAR(30), - RemovePlanHandleFromCache NVARCHAR(200), - Pattern NVARCHAR(20) - ); - END; - - -IF @SortOrder = 'all' -BEGIN -RAISERROR('Beginning for ALL', 0, 1) WITH NOWAIT; -SET @AllSortSql += N' - DECLARE @ISH NVARCHAR(MAX) = N'''' - - INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, - TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, - ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) - - EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''cpu'', - @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; - - UPDATE #bou_allsort SET Pattern = ''cpu'' WHERE Pattern IS NULL OPTION(RECOMPILE); - - SELECT TOP 1 @ISH = STUFF((SELECT DISTINCT N'','' + CONVERT(NVARCHAR(MAX),b2.SqlHandle, 1) FROM #bou_allsort AS b2 FOR XML PATH(N''''), TYPE).value(N''.[1]'', N''NVARCHAR(MAX)''), 1, 1, N'''') OPTION(RECOMPILE); - - INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, - TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, - ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) - - EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''reads'', @IgnoreSqlHandles = @ISH, - @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; - - UPDATE #bou_allsort SET Pattern = ''reads'' WHERE Pattern IS NULL OPTION(RECOMPILE); - - SELECT TOP 1 @ISH = STUFF((SELECT DISTINCT N'','' + CONVERT(NVARCHAR(MAX),b2.SqlHandle, 1) FROM #bou_allsort AS b2 FOR XML PATH(N''''), TYPE).value(N''.[1]'', N''NVARCHAR(MAX)''), 1, 1, N'''') OPTION(RECOMPILE); - - INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, - TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, - ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) - - EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''writes'', @IgnoreSqlHandles = @ISH, - @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; - - UPDATE #bou_allsort SET Pattern = ''writes'' WHERE Pattern IS NULL OPTION(RECOMPILE); + /* Skip non-readable databases in an AG - see Github issue #1160 */ + IF EXISTS (SELECT * FROM sys.all_objects o INNER JOIN sys.all_columns c ON o.object_id = c.object_id AND o.name = 'dm_hadr_availability_replica_states' AND c.name = 'role_desc') + BEGIN + SET @dsql = N'UPDATE #DatabaseList SET secondary_role_allow_connections_desc = ''NO'' WHERE DatabaseName IN ( + SELECT d.name + FROM sys.dm_hadr_availability_replica_states rs + INNER JOIN sys.databases d ON rs.replica_id = d.replica_id + INNER JOIN sys.availability_replicas r ON rs.replica_id = r.replica_id + WHERE rs.role_desc = ''SECONDARY'' + AND r.secondary_role_allow_connections_desc = ''NO'') + OPTION ( RECOMPILE );'; + EXEC sp_executesql @dsql; - SELECT TOP 1 @ISH = STUFF((SELECT DISTINCT N'','' + CONVERT(NVARCHAR(MAX),b2.SqlHandle, 1) FROM #bou_allsort AS b2 FOR XML PATH(N''''), TYPE).value(N''.[1]'', N''NVARCHAR(MAX)''), 1, 1, N'''') OPTION(RECOMPILE); + IF EXISTS (SELECT * FROM #DatabaseList WHERE secondary_role_allow_connections_desc = 'NO') + BEGIN + INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, database_name, URL, details, index_definition, + index_usage_summary, index_size_summary ) + VALUES ( 1, + 0, + N'Skipped non-readable AG secondary databases.', + N'You are running this on an AG secondary, and some of your databases are configured as non-readable when this is a secondary node.', + N'To analyze those databases, run sp_BlitzIndex on the primary, or on a readable secondary.', + 'http://FirstResponderKit.org', '', '', '', '' + ); + END; + END; - INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, - TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, - ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) - - EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''duration'', @IgnoreSqlHandles = @ISH, - @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; - - UPDATE #bou_allsort SET Pattern = ''duration'' WHERE Pattern IS NULL OPTION(RECOMPILE); + IF @IgnoreDatabases IS NOT NULL + AND LEN(@IgnoreDatabases) > 0 + BEGIN + RAISERROR(N'Setting up filter to ignore databases', 0, 1) WITH NOWAIT; + SET @DatabaseToIgnore = ''; - SELECT TOP 1 @ISH = STUFF((SELECT DISTINCT N'','' + CONVERT(NVARCHAR(MAX),b2.SqlHandle, 1) FROM #bou_allsort AS b2 FOR XML PATH(N''''), TYPE).value(N''.[1]'', N''NVARCHAR(MAX)''), 1, 1, N'''') OPTION(RECOMPILE); + WHILE LEN(@IgnoreDatabases) > 0 + BEGIN + IF PATINDEX('%,%', @IgnoreDatabases) > 0 + BEGIN + SET @DatabaseToIgnore = SUBSTRING(@IgnoreDatabases, 0, PATINDEX('%,%',@IgnoreDatabases)) ; + + INSERT INTO #Ignore_Databases (DatabaseName, Reason) + SELECT LTRIM(RTRIM(@DatabaseToIgnore)), 'Specified in the @IgnoreDatabases parameter' + OPTION (RECOMPILE) ; + + SET @IgnoreDatabases = SUBSTRING(@IgnoreDatabases, LEN(@DatabaseToIgnore + ',') + 1, LEN(@IgnoreDatabases)) ; + END; + ELSE + BEGIN + SET @DatabaseToIgnore = @IgnoreDatabases ; + SET @IgnoreDatabases = NULL ; - INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, - TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, - ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) - - EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''executions'', @IgnoreSqlHandles = @ISH, - @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; - - UPDATE #bou_allsort SET Pattern = ''executions'' WHERE Pattern IS NULL OPTION(RECOMPILE); - - '; - - IF @VersionShowsMemoryGrants = 0 - BEGIN - IF @ExportToExcel = 1 - BEGIN - SET @AllSortSql += N' UPDATE #bou_allsort - SET - QueryPlan = NULL, - implicit_conversion_info = NULL, - cached_execution_parameters = NULL, - missing_indexes = NULL - OPTION (RECOMPILE); + INSERT INTO #Ignore_Databases (DatabaseName, Reason) + SELECT LTRIM(RTRIM(@DatabaseToIgnore)), 'Specified in the @IgnoreDatabases parameter' + OPTION (RECOMPILE) ; + END; + END; + + END - UPDATE ##BlitzCacheProcs - SET QueryText = SUBSTRING(REPLACE(REPLACE(REPLACE(LTRIM(RTRIM(QueryText)),'' '',''<>''),''><'',''''),''<>'','' ''), 1, 32000) - OPTION(RECOMPILE);'; - END; + END; +ELSE + BEGIN + INSERT INTO #DatabaseList + ( DatabaseName ) + SELECT CASE + WHEN @DatabaseName IS NULL OR @DatabaseName = N'' + THEN DB_NAME() + ELSE @DatabaseName END; + END; - END; - - IF @VersionShowsMemoryGrants = 1 - BEGIN - SET @AllSortSql += N' SELECT TOP 1 @ISH = STUFF((SELECT DISTINCT N'','' + CONVERT(NVARCHAR(MAX),b2.SqlHandle, 1) FROM #bou_allsort AS b2 FOR XML PATH(N''''), TYPE).value(N''.[1]'', N''NVARCHAR(MAX)''), 1, 1, N'''') OPTION(RECOMPILE); - - INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, - TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, - ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) - - EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''memory grant'', @IgnoreSqlHandles = @ISH, - @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; - - UPDATE #bou_allsort SET Pattern = ''memory grant'' WHERE Pattern IS NULL OPTION(RECOMPILE);'; - IF @ExportToExcel = 1 - BEGIN - SET @AllSortSql += N' UPDATE #bou_allsort - SET - QueryPlan = NULL, - implicit_conversion_info = NULL, - cached_execution_parameters = NULL, - missing_indexes = NULL - OPTION (RECOMPILE); +SET @NumDatabases = (SELECT COUNT(*) FROM #DatabaseList AS D LEFT OUTER JOIN #Ignore_Databases AS I ON D.DatabaseName = I.DatabaseName WHERE I.DatabaseName IS NULL); +SET @msg = N'Number of databases to examine: ' + CAST(@NumDatabases AS NVARCHAR(50)); +RAISERROR (@msg,0,1) WITH NOWAIT; - UPDATE ##BlitzCacheProcs - SET QueryText = SUBSTRING(REPLACE(REPLACE(REPLACE(LTRIM(RTRIM(QueryText)),'' '',''<>''),''><'',''''),''<>'','' ''), 1, 32000) - OPTION(RECOMPILE);'; - END; - END; - IF @VersionShowsSpills = 0 - BEGIN - IF @ExportToExcel = 1 - BEGIN - SET @AllSortSql += N' UPDATE #bou_allsort - SET - QueryPlan = NULL, - implicit_conversion_info = NULL, - cached_execution_parameters = NULL, - missing_indexes = NULL - OPTION (RECOMPILE); +/* Running on 50+ databases can take a reaaallly long time, so we want explicit permission to do so (and only after warning about it) */ - UPDATE ##BlitzCacheProcs - SET QueryText = SUBSTRING(REPLACE(REPLACE(REPLACE(LTRIM(RTRIM(QueryText)),'' '',''<>''),''><'',''''),''<>'','' ''), 1, 32000) - OPTION(RECOMPILE);'; - END; - END; - - IF @VersionShowsSpills = 1 - BEGIN - SET @AllSortSql += N' SELECT TOP 1 @ISH = STUFF((SELECT DISTINCT N'','' + CONVERT(NVARCHAR(MAX),b2.SqlHandle, 1) FROM #bou_allsort AS b2 FOR XML PATH(N''''), TYPE).value(N''.[1]'', N''NVARCHAR(MAX)''), 1, 1, N'''') OPTION(RECOMPILE); - - INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, - TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, - ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) - - EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''spills'', @IgnoreSqlHandles = @ISH, - @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; - - UPDATE #bou_allsort SET Pattern = ''spills'' WHERE Pattern IS NULL OPTION(RECOMPILE);'; - IF @ExportToExcel = 1 - BEGIN - SET @AllSortSql += N' UPDATE #bou_allsort - SET - QueryPlan = NULL, - implicit_conversion_info = NULL, - cached_execution_parameters = NULL, - missing_indexes = NULL - OPTION (RECOMPILE); +BEGIN TRY + IF @NumDatabases >= 50 AND @BringThePain != 1 AND @TableName IS NULL + BEGIN - UPDATE ##BlitzCacheProcs - SET QueryText = SUBSTRING(REPLACE(REPLACE(REPLACE(LTRIM(RTRIM(QueryText)),'' '',''<>''),''><'',''''),''<>'','' ''), 1, 32000) - OPTION(RECOMPILE);'; - END; + INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, URL, details, index_definition, + index_usage_summary, index_size_summary ) + VALUES ( -1, + 0 , + @ScriptVersionName, + CASE WHEN @GetAllDatabases = 1 THEN N'All Databases' ELSE N'Database ' + QUOTENAME(@DatabaseName) + N' as of ' + CONVERT(NVARCHAR(16), GETDATE(), 121) END, + N'From Your Community Volunteers', + N'http://FirstResponderKit.org', + N'', + N'', + N'' + ); + INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, database_name, URL, details, index_definition, + index_usage_summary, index_size_summary ) + VALUES ( 1, + 0, + N'You''re trying to run sp_BlitzIndex on a server with ' + CAST(@NumDatabases AS NVARCHAR(8)) + N' databases. ', + N'Running sp_BlitzIndex on a server with 50+ databases may cause temporary problems for the server and/or user.', + N'If you''re sure you want to do this, run again with the parameter @BringThePain = 1.', + 'http://FirstResponderKit.org', + '', + '', + '', + '' + ); + + if(@OutputType <> 'NONE') + BEGIN + SELECT bir.blitz_result_id, + bir.check_id, + bir.index_sanity_id, + bir.Priority, + bir.findings_group, + bir.finding, + bir.database_name, + bir.URL, + bir.details, + bir.index_definition, + bir.secret_columns, + bir.index_usage_summary, + bir.index_size_summary, + bir.create_tsql, + bir.more_info + FROM #BlitzIndexResults AS bir; + RAISERROR('Running sp_BlitzIndex on a server with 50+ databases may cause temporary problems for the server', 12, 1); + END; - END; + RETURN; - IF(@OutputType <> 'NONE') - BEGIN - SET @AllSortSql += N' SELECT DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters,ExecutionCount, ExecutionsPerMinute, ExecutionWeight, - TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, - ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache, Pattern - FROM #bou_allsort - ORDER BY Id - OPTION(RECOMPILE); '; - END; -END; + END; +END TRY +BEGIN CATCH + RAISERROR (N'Failure to execute due to number of databases.', 0,1) WITH NOWAIT; + SELECT @msg = ERROR_MESSAGE(), + @ErrorSeverity = ERROR_SEVERITY(), + @ErrorState = ERROR_STATE(); -IF @SortOrder = 'all avg' -BEGIN -RAISERROR('Beginning for ALL AVG', 0, 1) WITH NOWAIT; -SET @AllSortSql += N' - DECLARE @ISH NVARCHAR(MAX) = N'''' - - INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, - TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, - ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) - - EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''avg cpu'', - @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; - - UPDATE #bou_allsort SET Pattern = ''avg cpu'' WHERE Pattern IS NULL OPTION(RECOMPILE); + RAISERROR (@msg, @ErrorSeverity, @ErrorState); + + WHILE @@trancount > 0 + ROLLBACK; - SELECT TOP 1 @ISH = STUFF((SELECT DISTINCT N'','' + CONVERT(NVARCHAR(MAX),b2.SqlHandle, 1) FROM #bou_allsort AS b2 FOR XML PATH(N''''), TYPE).value(N''.[1]'', N''NVARCHAR(MAX)''), 1, 1, N'''') OPTION(RECOMPILE); + RETURN; + END CATCH; - INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, - TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, - ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) - - EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''avg reads'', @IgnoreSqlHandles = @ISH, - @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; - - UPDATE #bou_allsort SET Pattern = ''avg reads'' WHERE Pattern IS NULL OPTION(RECOMPILE); - SELECT TOP 1 @ISH = STUFF((SELECT DISTINCT N'','' + CONVERT(NVARCHAR(MAX),b2.SqlHandle, 1) FROM #bou_allsort AS b2 FOR XML PATH(N''''), TYPE).value(N''.[1]'', N''NVARCHAR(MAX)''), 1, 1, N'''') OPTION(RECOMPILE); +RAISERROR (N'Checking partition counts to exclude databases with over 100 partitions',0,1) WITH NOWAIT; +IF @BringThePain = 0 AND @SkipPartitions = 0 AND @TableName IS NULL + BEGIN + DECLARE partition_cursor CURSOR FOR + SELECT dl.DatabaseName + FROM #DatabaseList dl + LEFT OUTER JOIN #Ignore_Databases i ON dl.DatabaseName = i.DatabaseName + WHERE COALESCE(dl.secondary_role_allow_connections_desc, 'OK') <> 'NO' + AND i.DatabaseName IS NULL - INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, - TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, - ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) - - EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''avg writes'', @IgnoreSqlHandles = @ISH, - @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; - - UPDATE #bou_allsort SET Pattern = ''avg writes'' WHERE Pattern IS NULL OPTION(RECOMPILE); + OPEN partition_cursor + FETCH NEXT FROM partition_cursor INTO @DatabaseName + + WHILE @@FETCH_STATUS = 0 + BEGIN + /* Count the total number of partitions */ + SET @dsql = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + SELECT @RowcountOUT = SUM(1) FROM ' + QUOTENAME(@DatabaseName) + '.sys.partitions WHERE partition_number > 1 OPTION ( RECOMPILE );'; + EXEC sp_executesql @dsql, N'@RowcountOUT BIGINT OUTPUT', @RowcountOUT = @Rowcount OUTPUT; + IF @Rowcount > 100 + BEGIN + RAISERROR (N'Skipping database %s because > 100 partitions were found. To check this database, you must set @BringThePain = 1.',0,1,@DatabaseName) WITH NOWAIT; + INSERT INTO #Ignore_Databases (DatabaseName, Reason) + SELECT @DatabaseName, 'Over 100 partitions found - use @BringThePain = 1 to analyze' + END; + FETCH NEXT FROM partition_cursor INTO @DatabaseName + END; + CLOSE partition_cursor + DEALLOCATE partition_cursor - SELECT TOP 1 @ISH = STUFF((SELECT DISTINCT N'','' + CONVERT(NVARCHAR(MAX),b2.SqlHandle, 1) FROM #bou_allsort AS b2 FOR XML PATH(N''''), TYPE).value(N''.[1]'', N''NVARCHAR(MAX)''), 1, 1, N'''') OPTION(RECOMPILE); + END; - INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, - TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, - ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) - - EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''avg duration'', @IgnoreSqlHandles = @ISH, - @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; - - UPDATE #bou_allsort SET Pattern = ''avg duration'' WHERE Pattern IS NULL OPTION(RECOMPILE); +INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, URL, details, index_definition, + index_usage_summary, index_size_summary ) +SELECT 1, 0 , + 'Database Skipped', + i.DatabaseName, + 'http://FirstResponderKit.org', + i.Reason, '', '', '' +FROM #Ignore_Databases i; - SELECT TOP 1 @ISH = STUFF((SELECT DISTINCT N'','' + CONVERT(NVARCHAR(MAX),b2.SqlHandle, 1) FROM #bou_allsort AS b2 FOR XML PATH(N''''), TYPE).value(N''.[1]'', N''NVARCHAR(MAX)''), 1, 1, N'''') OPTION(RECOMPILE); - INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, - TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, - ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) - - EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''avg executions'', @IgnoreSqlHandles = @ISH, - @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; - - UPDATE #bou_allsort SET Pattern = ''avg executions'' WHERE Pattern IS NULL OPTION(RECOMPILE); - - '; - - IF @VersionShowsMemoryGrants = 0 - BEGIN - IF @ExportToExcel = 1 - BEGIN - SET @AllSortSql += N' UPDATE #bou_allsort - SET - QueryPlan = NULL, - implicit_conversion_info = NULL, - cached_execution_parameters = NULL, - missing_indexes = NULL - OPTION (RECOMPILE); +/* Last startup */ +IF COLUMNPROPERTY(OBJECT_ID('sys.dm_os_sys_info'),'sqlserver_start_time','ColumnID') IS NOT NULL +BEGIN + SELECT @DaysUptime = CAST(DATEDIFF(HOUR, sqlserver_start_time, GETDATE()) / 24. AS NUMERIC (23,2)) + FROM sys.dm_os_sys_info; +END +ELSE +BEGIN + SELECT @DaysUptime = CAST(DATEDIFF(HOUR, create_date, GETDATE()) / 24. AS NUMERIC (23,2)) + FROM sys.databases + WHERE database_id = 2; +END - UPDATE ##BlitzCacheProcs - SET QueryText = SUBSTRING(REPLACE(REPLACE(REPLACE(LTRIM(RTRIM(QueryText)),'' '',''<>''),''><'',''''),''<>'','' ''), 1, 32000) - OPTION(RECOMPILE);'; - END; +IF @DaysUptime = 0 OR @DaysUptime IS NULL + SET @DaysUptime = .01; - END; - - IF @VersionShowsMemoryGrants = 1 - BEGIN - SET @AllSortSql += N' SELECT TOP 1 @ISH = STUFF((SELECT DISTINCT N'','' + CONVERT(NVARCHAR(MAX),b2.SqlHandle, 1) FROM #bou_allsort AS b2 FOR XML PATH(N''''), TYPE).value(N''.[1]'', N''NVARCHAR(MAX)''), 1, 1, N'''') OPTION(RECOMPILE); - - INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, - TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, - ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) - - EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''avg memory grant'', @IgnoreSqlHandles = @ISH, - @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; - - UPDATE #bou_allsort SET Pattern = ''avg memory grant'' WHERE Pattern IS NULL OPTION(RECOMPILE);'; - IF @ExportToExcel = 1 - BEGIN - SET @AllSortSql += N' UPDATE #bou_allsort - SET - QueryPlan = NULL, - implicit_conversion_info = NULL, - cached_execution_parameters = NULL, - missing_indexes = NULL - OPTION (RECOMPILE); +SELECT @DaysUptimeInsertValue = 'Server: ' + (CONVERT(VARCHAR(256), (SERVERPROPERTY('ServerName')))) + ' Days Uptime: ' + RTRIM(@DaysUptime); - UPDATE ##BlitzCacheProcs - SET QueryText = SUBSTRING(REPLACE(REPLACE(REPLACE(LTRIM(RTRIM(QueryText)),'' '',''<>''),''><'',''''),''<>'','' ''), 1, 32000) - OPTION(RECOMPILE);'; - END; - END; +/* Permission granted or unnecessary? Ok, let's go! */ - IF @VersionShowsSpills = 0 - BEGIN - IF @ExportToExcel = 1 - BEGIN - SET @AllSortSql += N' UPDATE #bou_allsort - SET - QueryPlan = NULL, - implicit_conversion_info = NULL, - cached_execution_parameters = NULL, - missing_indexes = NULL - OPTION (RECOMPILE); - - UPDATE ##BlitzCacheProcs - SET QueryText = SUBSTRING(REPLACE(REPLACE(REPLACE(LTRIM(RTRIM(QueryText)),'' '',''<>''),''><'',''''),''<>'','' ''), 1, 32000) - OPTION(RECOMPILE);'; - END; - - END; - - IF @VersionShowsSpills = 1 - BEGIN - SET @AllSortSql += N' SELECT TOP 1 @ISH = STUFF((SELECT DISTINCT N'','' + CONVERT(NVARCHAR(MAX),b2.SqlHandle, 1) FROM #bou_allsort AS b2 FOR XML PATH(N''''), TYPE).value(N''.[1]'', N''NVARCHAR(MAX)''), 1, 1, N'''') OPTION(RECOMPILE); - - INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, - TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, - ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) - - EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''avg spills'', @IgnoreSqlHandles = @ISH, - @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; - - UPDATE #bou_allsort SET Pattern = ''avg spills'' WHERE Pattern IS NULL OPTION(RECOMPILE);'; - IF @ExportToExcel = 1 - BEGIN - SET @AllSortSql += N' UPDATE #bou_allsort - SET - QueryPlan = NULL, - implicit_conversion_info = NULL, - cached_execution_parameters = NULL, - missing_indexes = NULL - OPTION (RECOMPILE); - - UPDATE ##BlitzCacheProcs - SET QueryText = SUBSTRING(REPLACE(REPLACE(REPLACE(LTRIM(RTRIM(QueryText)),'' '',''<>''),''><'',''''),''<>'','' ''), 1, 32000) - OPTION(RECOMPILE);'; - END; - - END; - - IF(@OutputType <> 'NONE') - BEGIN - SET @AllSortSql += N' SELECT DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters,ExecutionCount, ExecutionsPerMinute, ExecutionWeight, - TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, - ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache, Pattern - FROM #bou_allsort - ORDER BY Id - OPTION(RECOMPILE); '; - END; -END; - - IF @Debug = 1 - BEGIN - PRINT SUBSTRING(@AllSortSql, 0, 4000); - PRINT SUBSTRING(@AllSortSql, 4000, 8000); - PRINT SUBSTRING(@AllSortSql, 8000, 12000); - PRINT SUBSTRING(@AllSortSql, 12000, 16000); - PRINT SUBSTRING(@AllSortSql, 16000, 20000); - PRINT SUBSTRING(@AllSortSql, 20000, 24000); - PRINT SUBSTRING(@AllSortSql, 24000, 28000); - PRINT SUBSTRING(@AllSortSql, 28000, 32000); - PRINT SUBSTRING(@AllSortSql, 32000, 36000); - PRINT SUBSTRING(@AllSortSql, 36000, 40000); - END; - - EXEC sys.sp_executesql @stmt = @AllSortSql, @params = N'@i_DatabaseName NVARCHAR(128), @i_Top INT, @i_SkipAnalysis BIT, @i_OutputDatabaseName NVARCHAR(258), @i_OutputSchemaName NVARCHAR(258), @i_OutputTableName NVARCHAR(258), @i_CheckDateOverride DATETIMEOFFSET, @i_MinutesBack INT ', - @i_DatabaseName = @DatabaseName, @i_Top = @Top, @i_SkipAnalysis = @SkipAnalysis, @i_OutputDatabaseName = @OutputDatabaseName, @i_OutputSchemaName = @OutputSchemaName, @i_OutputTableName = @OutputTableName, @i_CheckDateOverride = @CheckDateOverride, @i_MinutesBack = @MinutesBack; - -/* Avoid going into OutputResultsToTable - ... otherwise the last result (e.g. spills) would be recorded twice into the output table. -*/ -RETURN; - -/*End of AllSort section*/ - - -/*Begin code to write results to table */ -OutputResultsToTable: - -RAISERROR('Writing results to table.', 0, 1) WITH NOWAIT; - -SELECT @OutputServerName = QUOTENAME(@OutputServerName), - @OutputDatabaseName = QUOTENAME(@OutputDatabaseName), - @OutputSchemaName = QUOTENAME(@OutputSchemaName), - @OutputTableName = QUOTENAME(@OutputTableName); - -/* Checks if @OutputServerName is populated with a valid linked server, and that the database name specified is valid */ -DECLARE @ValidOutputServer BIT; -DECLARE @ValidOutputLocation BIT; -DECLARE @LinkedServerDBCheck NVARCHAR(2000); -DECLARE @ValidLinkedServerDB INT; -DECLARE @tmpdbchk table (cnt int); -IF @OutputServerName IS NOT NULL - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Outputting to a remote server.', 0, 1) WITH NOWAIT; - - IF EXISTS (SELECT server_id FROM sys.servers WHERE QUOTENAME([name]) = @OutputServerName) - BEGIN - SET @LinkedServerDBCheck = 'SELECT 1 WHERE EXISTS (SELECT * FROM '+@OutputServerName+'.master.sys.databases WHERE QUOTENAME([name]) = '''+@OutputDatabaseName+''')'; - INSERT INTO @tmpdbchk EXEC sys.sp_executesql @LinkedServerDBCheck; - SET @ValidLinkedServerDB = (SELECT COUNT(*) FROM @tmpdbchk); - IF (@ValidLinkedServerDB > 0) - BEGIN - SET @ValidOutputServer = 1; - SET @ValidOutputLocation = 1; - END; - ELSE - RAISERROR('The specified database was not found on the output server', 16, 0); - END; - ELSE - BEGIN - RAISERROR('The specified output server was not found', 16, 0); - END; - END; -ELSE - BEGIN - IF @OutputDatabaseName IS NOT NULL - AND @OutputSchemaName IS NOT NULL - AND @OutputTableName IS NOT NULL - AND EXISTS ( SELECT * - FROM sys.databases - WHERE QUOTENAME([name]) = @OutputDatabaseName) - BEGIN - SET @ValidOutputLocation = 1; - END; - ELSE IF @OutputDatabaseName IS NOT NULL - AND @OutputSchemaName IS NOT NULL - AND @OutputTableName IS NOT NULL - AND NOT EXISTS ( SELECT * - FROM sys.databases - WHERE QUOTENAME([name]) = @OutputDatabaseName) - BEGIN - RAISERROR('The specified output database was not found on this server', 16, 0); - END; - ELSE - BEGIN - SET @ValidOutputLocation = 0; - END; - END; - - /* @OutputTableName lets us export the results to a permanent table */ - DECLARE @StringToExecute NVARCHAR(MAX) = N'' ; - - IF @ValidOutputLocation = 1 - BEGIN - SET @StringToExecute = N'USE ' - + @OutputDatabaseName - + N'; IF EXISTS(SELECT * FROM ' - + @OutputDatabaseName - + N'.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' - + @OutputSchemaName - + N''') AND NOT EXISTS (SELECT * FROM ' - + @OutputDatabaseName - + N'.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''' - + @OutputSchemaName + N''' AND QUOTENAME(TABLE_NAME) = ''' - + @OutputTableName + N''') CREATE TABLE ' - + @OutputSchemaName + N'.' - + @OutputTableName - + CONVERT - ( - nvarchar(MAX), - N'(ID bigint NOT NULL IDENTITY(1,1), - ServerName NVARCHAR(258), - CheckDate DATETIMEOFFSET, - Version NVARCHAR(258), - QueryType NVARCHAR(258), - Warnings varchar(max), - DatabaseName sysname, - SerialDesiredMemory float, - SerialRequiredMemory float, - AverageCPU bigint, - TotalCPU bigint, - PercentCPUByType money, - CPUWeight money, - AverageDuration bigint, - TotalDuration bigint, - DurationWeight money, - PercentDurationByType money, - AverageReads bigint, - TotalReads bigint, - ReadWeight money, - PercentReadsByType money, - AverageWrites bigint, - TotalWrites bigint, - WriteWeight money, - PercentWritesByType money, - ExecutionCount bigint, - ExecutionWeight money, - PercentExecutionsByType money, - ExecutionsPerMinute money, - PlanCreationTime datetime,' + N' - PlanCreationTimeHours AS DATEDIFF(HOUR,CONVERT(DATETIMEOFFSET(7),[PlanCreationTime]),[CheckDate]), - LastExecutionTime datetime, - LastCompletionTime datetime, - PlanHandle varbinary(64), - [Remove Plan Handle From Cache] AS - CASE WHEN [PlanHandle] IS NOT NULL - THEN ''DBCC FREEPROCCACHE ('' + CONVERT(VARCHAR(128), [PlanHandle], 1) + '');'' - ELSE ''N/A'' END, - SqlHandle varbinary(64), - [Remove SQL Handle From Cache] AS - CASE WHEN [SqlHandle] IS NOT NULL - THEN ''DBCC FREEPROCCACHE ('' + CONVERT(VARCHAR(128), [SqlHandle], 1) + '');'' - ELSE ''N/A'' END, - [SQL Handle More Info] AS - CASE WHEN [SqlHandle] IS NOT NULL - THEN ''EXEC sp_BlitzCache @OnlySqlHandles = '''''' + CONVERT(VARCHAR(128), [SqlHandle], 1) + ''''''; '' - ELSE ''N/A'' END, - QueryHash binary(8), - [Query Hash More Info] AS - CASE WHEN [QueryHash] IS NOT NULL - THEN ''EXEC sp_BlitzCache @OnlyQueryHashes = '''''' + CONVERT(VARCHAR(32), [QueryHash], 1) + ''''''; '' - ELSE ''N/A'' END, - QueryPlanHash binary(8), - StatementStartOffset int, - StatementEndOffset int, - PlanGenerationNum bigint, - MinReturnedRows bigint, - MaxReturnedRows bigint, - AverageReturnedRows money, - TotalReturnedRows bigint, - QueryText nvarchar(max), - QueryPlan xml, - NumberOfPlans int, - NumberOfDistinctPlans int, - MinGrantKB BIGINT, - MaxGrantKB BIGINT, - MinUsedGrantKB BIGINT, - MaxUsedGrantKB BIGINT, - PercentMemoryGrantUsed MONEY, - AvgMaxMemoryGrant MONEY, - MinSpills BIGINT, - MaxSpills BIGINT, - TotalSpills BIGINT, - AvgSpills MONEY, - QueryPlanCost FLOAT, - Pattern NVARCHAR(20), - JoinKey AS ServerName + Cast(CheckDate AS NVARCHAR(50)), - CONSTRAINT [PK_' + REPLACE(REPLACE(@OutputTableName,N'[',N''),N']',N'') + N'] PRIMARY KEY CLUSTERED(ID ASC));' - ); - - SET @StringToExecute += N'IF EXISTS(SELECT * FROM ' - +@OutputDatabaseName - +N'.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' - +@OutputSchemaName - +N''') AND EXISTS (SELECT * FROM ' - +@OutputDatabaseName+ - N'.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''' - +@OutputSchemaName - +N''' AND QUOTENAME(TABLE_NAME) = ''' - +@OutputTableName - +N''') AND EXISTS (SELECT * FROM ' - +@OutputDatabaseName+ - N'.sys.computed_columns WHERE [name] = N''PlanCreationTimeHours'' AND QUOTENAME(OBJECT_NAME(object_id)) = N''' - +@OutputTableName - +N''' AND [definition] = N''(datediff(hour,[PlanCreationTime],sysdatetime()))'') -BEGIN - RAISERROR(''We noticed that you are running an old computed column definition for PlanCreationTimeHours, fixing that now'',0,0) WITH NOWAIT; - ALTER TABLE '+@OutputDatabaseName+N'.'+@OutputSchemaName+N'.'+@OutputTableName+N' DROP COLUMN [PlanCreationTimeHours]; - ALTER TABLE '+@OutputDatabaseName+N'.'+@OutputSchemaName+N'.'+@OutputTableName+N' ADD [PlanCreationTimeHours] AS DATEDIFF(HOUR,CONVERT(DATETIMEOFFSET(7),[PlanCreationTime]),[CheckDate]); -END '; - - IF @ValidOutputServer = 1 - BEGIN - SET @StringToExecute = REPLACE(@StringToExecute,''''+@OutputSchemaName+'''',''''''+@OutputSchemaName+''''''); - SET @StringToExecute = REPLACE(@StringToExecute,''''+@OutputTableName+'''',''''''+@OutputTableName+''''''); - SET @StringToExecute = REPLACE(@StringToExecute,'xml','nvarchar(max)'); - SET @StringToExecute = REPLACE(@StringToExecute,'''DBCC FREEPROCCACHE ('' + CONVERT(VARCHAR(128), [PlanHandle], 1) + '');''','''''DBCC FREEPROCCACHE ('''' + CONVERT(VARCHAR(128), [PlanHandle], 1) + '''');'''''); - SET @StringToExecute = REPLACE(@StringToExecute,'''DBCC FREEPROCCACHE ('' + CONVERT(VARCHAR(128), [SqlHandle], 1) + '');''','''''DBCC FREEPROCCACHE ('''' + CONVERT(VARCHAR(128), [SqlHandle], 1) + '''');'''''); - SET @StringToExecute = REPLACE(@StringToExecute,'''EXEC sp_BlitzCache @OnlySqlHandles = '''''' + CONVERT(VARCHAR(128), [SqlHandle], 1) + ''''''; ''','''''EXEC sp_BlitzCache @OnlySqlHandles = '''''''' + CONVERT(VARCHAR(128), [SqlHandle], 1) + ''''''''; '''''); - SET @StringToExecute = REPLACE(@StringToExecute,'''EXEC sp_BlitzCache @OnlyQueryHashes = '''''' + CONVERT(VARCHAR(32), [QueryHash], 1) + ''''''; ''','''''EXEC sp_BlitzCache @OnlyQueryHashes = '''''''' + CONVERT(VARCHAR(32), [QueryHash], 1) + ''''''''; '''''); - SET @StringToExecute = REPLACE(@StringToExecute,'''N/A''','''''N/A'''''); - - IF @Debug = 1 - BEGIN - PRINT SUBSTRING(@StringToExecute, 0, 4000); - PRINT SUBSTRING(@StringToExecute, 4000, 8000); - PRINT SUBSTRING(@StringToExecute, 8000, 12000); - PRINT SUBSTRING(@StringToExecute, 12000, 16000); - PRINT SUBSTRING(@StringToExecute, 16000, 20000); - PRINT SUBSTRING(@StringToExecute, 20000, 24000); - PRINT SUBSTRING(@StringToExecute, 24000, 28000); - PRINT SUBSTRING(@StringToExecute, 28000, 32000); - PRINT SUBSTRING(@StringToExecute, 32000, 36000); - PRINT SUBSTRING(@StringToExecute, 36000, 40000); - END; - EXEC('EXEC('''+@StringToExecute+''') AT ' + @OutputServerName); - END; - ELSE - BEGIN - IF @Debug = 1 - BEGIN - PRINT SUBSTRING(@StringToExecute, 0, 4000); - PRINT SUBSTRING(@StringToExecute, 4000, 8000); - PRINT SUBSTRING(@StringToExecute, 8000, 12000); - PRINT SUBSTRING(@StringToExecute, 12000, 16000); - PRINT SUBSTRING(@StringToExecute, 16000, 20000); - PRINT SUBSTRING(@StringToExecute, 20000, 24000); - PRINT SUBSTRING(@StringToExecute, 24000, 28000); - PRINT SUBSTRING(@StringToExecute, 28000, 32000); - PRINT SUBSTRING(@StringToExecute, 32000, 36000); - PRINT SUBSTRING(@StringToExecute, 36000, 40000); - END; - EXEC(@StringToExecute); - END; - - /* If the table doesn't have the new LastCompletionTime column, add it. See Github #2377. */ - SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableName; - SET @StringToExecute = N'IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns - WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + ''')) AND name = ''LastCompletionTime'') - ALTER TABLE ' + @ObjectFullName + N' ADD LastCompletionTime DATETIME NULL;'; - IF @ValidOutputServer = 1 - BEGIN - SET @StringToExecute = REPLACE(@StringToExecute,'''LastCompletionTime''','''''LastCompletionTime'''''); - SET @StringToExecute = REPLACE(@StringToExecute,'''' + @ObjectFullName + '''','''''' + @ObjectFullName + ''''''); - EXEC('EXEC('''+@StringToExecute+''') AT ' + @OutputServerName); - END; - ELSE - BEGIN - EXEC(@StringToExecute); - END; - - /* If the table doesn't have the new PlanGenerationNum column, add it. See Github #2514. */ - SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableName; - SET @StringToExecute = N'IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns - WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''PlanGenerationNum'') - ALTER TABLE ' + @ObjectFullName + N' ADD PlanGenerationNum BIGINT NULL;'; - IF @ValidOutputServer = 1 - BEGIN - SET @StringToExecute = REPLACE(@StringToExecute,'''PlanGenerationNum''','''''PlanGenerationNum'''''); - SET @StringToExecute = REPLACE(@StringToExecute,'''' + @ObjectFullName + '''','''''' + @ObjectFullName + ''''''); - EXEC('EXEC('''+@StringToExecute+''') AT ' + @OutputServerName); - END; - ELSE - BEGIN - EXEC(@StringToExecute); - END; - - /* If the table doesn't have the new Pattern column, add it */ - SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableName; - SET @StringToExecute = N'IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns - WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''Pattern'') - ALTER TABLE ' + @ObjectFullName + N' ADD Pattern NVARCHAR(20) NULL;'; - IF @ValidOutputServer = 1 - BEGIN - SET @StringToExecute = REPLACE(@StringToExecute,'''Pattern''','''''Pattern'''''); - SET @StringToExecute = REPLACE(@StringToExecute,'''' + @ObjectFullName + '''','''''' + @ObjectFullName + ''''''); - EXEC('EXEC('''+@StringToExecute+''') AT ' + @OutputServerName); - END; - ELSE - BEGIN - EXEC(@StringToExecute); - END - - IF @CheckDateOverride IS NULL - BEGIN - SET @CheckDateOverride = SYSDATETIMEOFFSET(); - END; - - IF @ValidOutputServer = 1 - BEGIN - SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' - + @OutputServerName + '.' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' - + @OutputSchemaName + ''') INSERT ' - + @OutputServerName + '.' - + @OutputDatabaseName + '.' - + @OutputSchemaName + '.' - + @OutputTableName - + ' (ServerName, CheckDate, Version, QueryType, DatabaseName, AverageCPU, TotalCPU, PercentCPUByType, CPUWeight, AverageDuration, TotalDuration, DurationWeight, PercentDurationByType, AverageReads, TotalReads, ReadWeight, PercentReadsByType, ' - + ' AverageWrites, TotalWrites, WriteWeight, PercentWritesByType, ExecutionCount, ExecutionWeight, PercentExecutionsByType, ' - + ' ExecutionsPerMinute, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, QueryHash, QueryPlanHash, StatementStartOffset, StatementEndOffset, PlanGenerationNum, MinReturnedRows, MaxReturnedRows, AverageReturnedRows, TotalReturnedRows, QueryText, QueryPlan, NumberOfPlans, NumberOfDistinctPlans, Warnings, ' - + ' SerialRequiredMemory, SerialDesiredMemory, MinGrantKB, MaxGrantKB, MinUsedGrantKB, MaxUsedGrantKB, PercentMemoryGrantUsed, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, QueryPlanCost, Pattern ) ' - + 'SELECT TOP (@Top) ' - + QUOTENAME(CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128)), '''') + ', @CheckDateOverride, ' - + QUOTENAME(CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)), '''') + ', ' - + ' QueryType, DatabaseName, AverageCPU, TotalCPU, PercentCPUByType, PercentCPU, AverageDuration, TotalDuration, PercentDuration, PercentDurationByType, AverageReads, TotalReads, PercentReads, PercentReadsByType, ' - + ' AverageWrites, TotalWrites, PercentWrites, PercentWritesByType, ExecutionCount, PercentExecutions, PercentExecutionsByType, ' - + ' ExecutionsPerMinute, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, QueryHash, QueryPlanHash, StatementStartOffset, StatementEndOffset, PlanGenerationNum, MinReturnedRows, MaxReturnedRows, AverageReturnedRows, TotalReturnedRows, QueryText, CAST(QueryPlan AS NVARCHAR(MAX)), NumberOfPlans, NumberOfDistinctPlans, Warnings, ' - + ' SerialRequiredMemory, SerialDesiredMemory, MinGrantKB, MaxGrantKB, MinUsedGrantKB, MaxUsedGrantKB, PercentMemoryGrantUsed, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, QueryPlanCost, Pattern ' - + ' FROM ##BlitzCacheProcs ' - + ' WHERE 1=1 '; - - IF @MinimumExecutionCount IS NOT NULL - BEGIN - SET @StringToExecute += N' AND ExecutionCount >= @MinimumExecutionCount '; - END; - IF @MinutesBack IS NOT NULL - BEGIN - SET @StringToExecute += N' AND LastCompletionTime >= DATEADD(MINUTE, @min_back, GETDATE() ) '; - END; - SET @StringToExecute += N' AND SPID = @@SPID '; - SELECT @StringToExecute += N' ORDER BY ' + CASE @SortOrder WHEN 'cpu' THEN N' TotalCPU ' - WHEN N'reads' THEN N' TotalReads ' - WHEN N'writes' THEN N' TotalWrites ' - WHEN N'duration' THEN N' TotalDuration ' - WHEN N'executions' THEN N' ExecutionCount ' - WHEN N'compiles' THEN N' PlanCreationTime ' - WHEN N'memory grant' THEN N' MaxGrantKB' - WHEN N'spills' THEN N' MaxSpills' - WHEN N'avg cpu' THEN N' AverageCPU' - WHEN N'avg reads' THEN N' AverageReads' - WHEN N'avg writes' THEN N' AverageWrites' - WHEN N'avg duration' THEN N' AverageDuration' - WHEN N'avg executions' THEN N' ExecutionsPerMinute' - WHEN N'avg memory grant' THEN N' AvgMaxMemoryGrant' - WHEN N'avg spills' THEN N' AvgSpills' - WHEN N'unused grant' THEN N' MaxGrantKB - MaxUsedGrantKB' - ELSE N' TotalCPU ' - END + N' DESC '; - SET @StringToExecute += N' OPTION (RECOMPILE) ; '; - - IF @Debug = 1 - BEGIN - PRINT SUBSTRING(@StringToExecute, 1, 4000); - PRINT SUBSTRING(@StringToExecute, 4001, 4000); - PRINT SUBSTRING(@StringToExecute, 8001, 4000); - PRINT SUBSTRING(@StringToExecute, 12001, 4000); - PRINT SUBSTRING(@StringToExecute, 16001, 4000); - PRINT SUBSTRING(@StringToExecute, 20001, 4000); - PRINT SUBSTRING(@StringToExecute, 24001, 4000); - PRINT SUBSTRING(@StringToExecute, 28001, 4000); - PRINT SUBSTRING(@StringToExecute, 32001, 4000); - PRINT SUBSTRING(@StringToExecute, 36001, 4000); - END; - - EXEC sp_executesql @StringToExecute, N'@Top INT, @min_duration INT, @min_back INT, @CheckDateOverride DATETIMEOFFSET, @MinimumExecutionCount INT', @Top, @DurationFilter_i, @MinutesBack, @CheckDateOverride, @MinimumExecutionCount; - END; - ELSE - BEGIN - SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' - + @OutputSchemaName + ''') INSERT ' - + @OutputDatabaseName + '.' - + @OutputSchemaName + '.' - + @OutputTableName - + ' (ServerName, CheckDate, Version, QueryType, DatabaseName, AverageCPU, TotalCPU, PercentCPUByType, CPUWeight, AverageDuration, TotalDuration, DurationWeight, PercentDurationByType, AverageReads, TotalReads, ReadWeight, PercentReadsByType, ' - + ' AverageWrites, TotalWrites, WriteWeight, PercentWritesByType, ExecutionCount, ExecutionWeight, PercentExecutionsByType, ' - + ' ExecutionsPerMinute, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, QueryHash, QueryPlanHash, StatementStartOffset, StatementEndOffset, PlanGenerationNum, MinReturnedRows, MaxReturnedRows, AverageReturnedRows, TotalReturnedRows, QueryText, QueryPlan, NumberOfPlans, NumberOfDistinctPlans, Warnings, ' - + ' SerialRequiredMemory, SerialDesiredMemory, MinGrantKB, MaxGrantKB, MinUsedGrantKB, MaxUsedGrantKB, PercentMemoryGrantUsed, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, QueryPlanCost, Pattern ) ' - + 'SELECT TOP (@Top) ' - + QUOTENAME(CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128)), '''') + ', @CheckDateOverride, ' - + QUOTENAME(CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)), '''') + ', ' - + ' QueryType, DatabaseName, AverageCPU, TotalCPU, PercentCPUByType, PercentCPU, AverageDuration, TotalDuration, PercentDuration, PercentDurationByType, AverageReads, TotalReads, PercentReads, PercentReadsByType, ' - + ' AverageWrites, TotalWrites, PercentWrites, PercentWritesByType, ExecutionCount, PercentExecutions, PercentExecutionsByType, ' - + ' ExecutionsPerMinute, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, QueryHash, QueryPlanHash, StatementStartOffset, StatementEndOffset, PlanGenerationNum, MinReturnedRows, MaxReturnedRows, AverageReturnedRows, TotalReturnedRows, QueryText, QueryPlan, NumberOfPlans, NumberOfDistinctPlans, Warnings, ' - + ' SerialRequiredMemory, SerialDesiredMemory, MinGrantKB, MaxGrantKB, MinUsedGrantKB, MaxUsedGrantKB, PercentMemoryGrantUsed, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, QueryPlanCost, Pattern ' - + ' FROM ##BlitzCacheProcs ' - + ' WHERE 1=1 '; - - IF @MinimumExecutionCount IS NOT NULL - BEGIN - SET @StringToExecute += N' AND ExecutionCount >= @MinimumExecutionCount '; - END; - IF @MinutesBack IS NOT NULL - BEGIN - SET @StringToExecute += N' AND LastCompletionTime >= DATEADD(MINUTE, @min_back, GETDATE() ) '; - END; - SET @StringToExecute += N' AND SPID = @@SPID '; - SELECT @StringToExecute += N' ORDER BY ' + CASE @SortOrder WHEN 'cpu' THEN N' TotalCPU ' - WHEN N'reads' THEN N' TotalReads ' - WHEN N'writes' THEN N' TotalWrites ' - WHEN N'duration' THEN N' TotalDuration ' - WHEN N'executions' THEN N' ExecutionCount ' - WHEN N'compiles' THEN N' PlanCreationTime ' - WHEN N'memory grant' THEN N' MaxGrantKB' - WHEN N'spills' THEN N' MaxSpills' - WHEN N'avg cpu' THEN N' AverageCPU' - WHEN N'avg reads' THEN N' AverageReads' - WHEN N'avg writes' THEN N' AverageWrites' - WHEN N'avg duration' THEN N' AverageDuration' - WHEN N'avg executions' THEN N' ExecutionsPerMinute' - WHEN N'avg memory grant' THEN N' AvgMaxMemoryGrant' - WHEN N'avg spills' THEN N' AvgSpills' - WHEN N'unused grant' THEN N' MaxGrantKB - MaxUsedGrantKB' - ELSE N' TotalCPU ' - END + N' DESC '; - SET @StringToExecute += N' OPTION (RECOMPILE) ; '; - - IF @Debug = 1 - BEGIN - PRINT SUBSTRING(@StringToExecute, 0, 4000); - PRINT SUBSTRING(@StringToExecute, 4000, 8000); - PRINT SUBSTRING(@StringToExecute, 8000, 12000); - PRINT SUBSTRING(@StringToExecute, 12000, 16000); - PRINT SUBSTRING(@StringToExecute, 16000, 20000); - PRINT SUBSTRING(@StringToExecute, 20000, 24000); - PRINT SUBSTRING(@StringToExecute, 24000, 28000); - PRINT SUBSTRING(@StringToExecute, 28000, 32000); - PRINT SUBSTRING(@StringToExecute, 32000, 36000); - PRINT SUBSTRING(@StringToExecute, 36000, 40000); - END; - - EXEC sp_executesql @StringToExecute, N'@Top INT, @min_duration INT, @min_back INT, @CheckDateOverride DATETIMEOFFSET, @MinimumExecutionCount INT', @Top, @DurationFilter_i, @MinutesBack, @CheckDateOverride, @MinimumExecutionCount; - END; - END; - ELSE IF (SUBSTRING(@OutputTableName, 2, 2) = '##') - BEGIN - IF @ValidOutputServer = 1 - BEGIN - RAISERROR('Due to the nature of temporary tables, outputting to a linked server requires a permanent table.', 16, 0); - END; - ELSE IF @OutputTableName IN ('##BlitzCacheProcs','##BlitzCacheResults') - BEGIN - RAISERROR('OutputTableName is a reserved name for this procedure. We only use ##BlitzCacheProcs and ##BlitzCacheResults, please choose another table name.', 16, 0); - END; - ELSE - BEGIN - SET @StringToExecute = N' IF (OBJECT_ID(''tempdb..' - + @OutputTableName - + ''') IS NOT NULL) DROP TABLE ' + @OutputTableName + ';' - + 'CREATE TABLE ' - + @OutputTableName - + ' (ID bigint NOT NULL IDENTITY(1,1), - ServerName NVARCHAR(258), - CheckDate DATETIMEOFFSET, - Version NVARCHAR(258), - QueryType NVARCHAR(258), - Warnings varchar(max), - DatabaseName sysname, - SerialDesiredMemory float, - SerialRequiredMemory float, - AverageCPU bigint, - TotalCPU bigint, - PercentCPUByType money, - CPUWeight money, - AverageDuration bigint, - TotalDuration bigint, - DurationWeight money, - PercentDurationByType money, - AverageReads bigint, - TotalReads bigint, - ReadWeight money, - PercentReadsByType money, - AverageWrites bigint, - TotalWrites bigint, - WriteWeight money, - PercentWritesByType money, - ExecutionCount bigint, - ExecutionWeight money, - PercentExecutionsByType money, - ExecutionsPerMinute money, - PlanCreationTime datetime,' + N' - PlanCreationTimeHours AS DATEDIFF(HOUR, PlanCreationTime, SYSDATETIME()), - LastExecutionTime datetime, - LastCompletionTime datetime, - PlanHandle varbinary(64), - [Remove Plan Handle From Cache] AS - CASE WHEN [PlanHandle] IS NOT NULL - THEN ''DBCC FREEPROCCACHE ('' + CONVERT(VARCHAR(128), [PlanHandle], 1) + '');'' - ELSE ''N/A'' END, - SqlHandle varbinary(64), - [Remove SQL Handle From Cache] AS - CASE WHEN [SqlHandle] IS NOT NULL - THEN ''DBCC FREEPROCCACHE ('' + CONVERT(VARCHAR(128), [SqlHandle], 1) + '');'' - ELSE ''N/A'' END, - [SQL Handle More Info] AS - CASE WHEN [SqlHandle] IS NOT NULL - THEN ''EXEC sp_BlitzCache @OnlySqlHandles = '''''' + CONVERT(VARCHAR(128), [SqlHandle], 1) + ''''''; '' - ELSE ''N/A'' END, - QueryHash binary(8), - [Query Hash More Info] AS - CASE WHEN [QueryHash] IS NOT NULL - THEN ''EXEC sp_BlitzCache @OnlyQueryHashes = '''''' + CONVERT(VARCHAR(32), [QueryHash], 1) + ''''''; '' - ELSE ''N/A'' END, - QueryPlanHash binary(8), - StatementStartOffset int, - StatementEndOffset int, - PlanGenerationNum bigint, - MinReturnedRows bigint, - MaxReturnedRows bigint, - AverageReturnedRows money, - TotalReturnedRows bigint, - QueryText nvarchar(max), - QueryPlan xml, - NumberOfPlans int, - NumberOfDistinctPlans int, - MinGrantKB BIGINT, - MaxGrantKB BIGINT, - MinUsedGrantKB BIGINT, - MaxUsedGrantKB BIGINT, - PercentMemoryGrantUsed MONEY, - AvgMaxMemoryGrant MONEY, - MinSpills BIGINT, - MaxSpills BIGINT, - TotalSpills BIGINT, - AvgSpills MONEY, - QueryPlanCost FLOAT, - Pattern NVARCHAR(20), - JoinKey AS ServerName + Cast(CheckDate AS NVARCHAR(50)), - CONSTRAINT [PK_' + REPLACE(REPLACE(@OutputTableName,'[',''),']','') + '] PRIMARY KEY CLUSTERED(ID ASC));'; - SET @StringToExecute += N' INSERT ' - + @OutputTableName - + ' (ServerName, CheckDate, Version, QueryType, DatabaseName, AverageCPU, TotalCPU, PercentCPUByType, CPUWeight, AverageDuration, TotalDuration, DurationWeight, PercentDurationByType, AverageReads, TotalReads, ReadWeight, PercentReadsByType, ' - + ' AverageWrites, TotalWrites, WriteWeight, PercentWritesByType, ExecutionCount, ExecutionWeight, PercentExecutionsByType, ' - + ' ExecutionsPerMinute, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, QueryHash, QueryPlanHash, StatementStartOffset, StatementEndOffset, PlanGenerationNum, MinReturnedRows, MaxReturnedRows, AverageReturnedRows, TotalReturnedRows, QueryText, QueryPlan, NumberOfPlans, NumberOfDistinctPlans, Warnings, ' - + ' SerialRequiredMemory, SerialDesiredMemory, MinGrantKB, MaxGrantKB, MinUsedGrantKB, MaxUsedGrantKB, PercentMemoryGrantUsed, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, QueryPlanCost, Pattern ) ' - + 'SELECT TOP (@Top) ' - + QUOTENAME(CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128)), '''') + ', @CheckDateOverride, ' - + QUOTENAME(CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)), '''') + ', ' - + ' QueryType, DatabaseName, AverageCPU, TotalCPU, PercentCPUByType, PercentCPU, AverageDuration, TotalDuration, PercentDuration, PercentDurationByType, AverageReads, TotalReads, PercentReads, PercentReadsByType, ' - + ' AverageWrites, TotalWrites, PercentWrites, PercentWritesByType, ExecutionCount, PercentExecutions, PercentExecutionsByType, ' - + ' ExecutionsPerMinute, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, QueryHash, QueryPlanHash, StatementStartOffset, StatementEndOffset, PlanGenerationNum, MinReturnedRows, MaxReturnedRows, AverageReturnedRows, TotalReturnedRows, QueryText, QueryPlan, NumberOfPlans, NumberOfDistinctPlans, Warnings, ' - + ' SerialRequiredMemory, SerialDesiredMemory, MinGrantKB, MaxGrantKB, MinUsedGrantKB, MaxUsedGrantKB, PercentMemoryGrantUsed, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, QueryPlanCost, Pattern ' - + ' FROM ##BlitzCacheProcs ' - + ' WHERE 1=1 '; - - IF @MinimumExecutionCount IS NOT NULL - BEGIN - SET @StringToExecute += N' AND ExecutionCount >= @MinimumExecutionCount '; - END; - - IF @MinutesBack IS NOT NULL - BEGIN - SET @StringToExecute += N' AND LastCompletionTime >= DATEADD(MINUTE, @min_back, GETDATE() ) '; - END; - - SET @StringToExecute += N' AND SPID = @@SPID '; - - SELECT @StringToExecute += N' ORDER BY ' + CASE @SortOrder WHEN 'cpu' THEN N' TotalCPU ' - WHEN N'reads' THEN N' TotalReads ' - WHEN N'writes' THEN N' TotalWrites ' - WHEN N'duration' THEN N' TotalDuration ' - WHEN N'executions' THEN N' ExecutionCount ' - WHEN N'compiles' THEN N' PlanCreationTime ' - WHEN N'memory grant' THEN N' MaxGrantKB' - WHEN N'spills' THEN N' MaxSpills' - WHEN N'avg cpu' THEN N' AverageCPU' - WHEN N'avg reads' THEN N' AverageReads' - WHEN N'avg writes' THEN N' AverageWrites' - WHEN N'avg duration' THEN N' AverageDuration' - WHEN N'avg executions' THEN N' ExecutionsPerMinute' - WHEN N'avg memory grant' THEN N' AvgMaxMemoryGrant' - WHEN N'avg spills' THEN N' AvgSpills' - WHEN N'unused grant' THEN N' MaxGrantKB - MaxUsedGrantKB' - ELSE N' TotalCPU ' - END + N' DESC '; - - SET @StringToExecute += N' OPTION (RECOMPILE) ; '; - - IF @Debug = 1 - BEGIN - PRINT SUBSTRING(@StringToExecute, 0, 4000); - PRINT SUBSTRING(@StringToExecute, 4000, 8000); - PRINT SUBSTRING(@StringToExecute, 8000, 12000); - PRINT SUBSTRING(@StringToExecute, 12000, 16000); - PRINT SUBSTRING(@StringToExecute, 16000, 20000); - PRINT SUBSTRING(@StringToExecute, 20000, 24000); - PRINT SUBSTRING(@StringToExecute, 24000, 28000); - PRINT SUBSTRING(@StringToExecute, 28000, 32000); - PRINT SUBSTRING(@StringToExecute, 32000, 36000); - PRINT SUBSTRING(@StringToExecute, 36000, 40000); - PRINT SUBSTRING(@StringToExecute, 34000, 40000); - END; - EXEC sp_executesql @StringToExecute, N'@Top INT, @min_duration INT, @min_back INT, @CheckDateOverride DATETIMEOFFSET, @MinimumExecutionCount INT', @Top, @DurationFilter_i, @MinutesBack, @CheckDateOverride, @MinimumExecutionCount; - END; - END; - ELSE IF (SUBSTRING(@OutputTableName, 2, 1) = '#') - BEGIN - RAISERROR('Due to the nature of Dymamic SQL, only global (i.e. double pound (##)) temp tables are supported for @OutputTableName', 16, 0); -END; /* End of writing results to table */ - -END; /*Final End*/ - -GO -SET ANSI_NULLS ON; -SET ANSI_PADDING ON; -SET ANSI_WARNINGS ON; -SET ARITHABORT ON; -SET CONCAT_NULL_YIELDS_NULL ON; -SET QUOTED_IDENTIFIER ON; -SET STATISTICS IO OFF; -SET STATISTICS TIME OFF; -GO - -IF OBJECT_ID('dbo.sp_BlitzIndex') IS NULL - EXEC ('CREATE PROCEDURE dbo.sp_BlitzIndex AS RETURN 0;'); -GO - -ALTER PROCEDURE dbo.sp_BlitzIndex - @DatabaseName NVARCHAR(128) = NULL, /*Defaults to current DB if not specified*/ - @SchemaName NVARCHAR(128) = NULL, /*Requires table_name as well.*/ - @TableName NVARCHAR(128) = NULL, /*Requires schema_name as well.*/ - @Mode TINYINT=0, /*0=Diagnose, 1=Summarize, 2=Index Usage Detail, 3=Missing Index Detail, 4=Diagnose Details*/ - /*Note:@Mode doesn't matter if you're specifying schema_name and @TableName.*/ - @Filter TINYINT = 0, /* 0=no filter (default). 1=No low-usage warnings for objects with 0 reads. 2=Only warn for objects >= 500MB */ - /*Note:@Filter doesn't do anything unless @Mode=0*/ - @SkipPartitions BIT = 0, - @SkipStatistics BIT = 1, - @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, - @IgnoreDatabases NVARCHAR(MAX) = NULL, /* Comma-delimited list of databases you want to skip */ - @ThresholdMB INT = 250 /* Number of megabytes that an object must be before we include it in basic results */, - @OutputType VARCHAR(20) = 'TABLE' , - @OutputServerName NVARCHAR(256) = NULL , - @OutputDatabaseName NVARCHAR(256) = NULL , - @OutputSchemaName NVARCHAR(256) = NULL , - @OutputTableName NVARCHAR(256) = NULL , - @IncludeInactiveIndexes BIT = 0 /* Will skip indexes with no reads or writes */, - @ShowAllMissingIndexRequests BIT = 0 /*Will make all missing index requests show up*/, - @ShowPartitionRanges BIT = 0 /* Will add partition range values column to columnstore visualization */, - @SortOrder NVARCHAR(50) = NULL, /* Only affects @Mode = 2. */ - @SortDirection NVARCHAR(4) = 'DESC', /* Only affects @Mode = 2. */ - @Help TINYINT = 0, - @Debug BIT = 0, - @Version VARCHAR(30) = NULL OUTPUT, - @VersionDate DATETIME = NULL OUTPUT, - @VersionCheckMode BIT = 0 -WITH RECOMPILE -AS -SET NOCOUNT ON; -SET STATISTICS XML OFF; -SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - -SELECT @Version = '8.19', @VersionDate = '20240222'; -SET @OutputType = UPPER(@OutputType); - -IF(@VersionCheckMode = 1) -BEGIN - RETURN; -END; - -IF @Help = 1 -BEGIN -PRINT ' -/* -sp_BlitzIndex from http://FirstResponderKit.org - -This script analyzes the design and performance of your indexes. - -To learn more, visit http://FirstResponderKit.org where you can download new -versions for free, watch training videos on how it works, get more info on -the findings, contribute your own code, and more. - -Known limitations of this version: - - Only Microsoft-supported versions of SQL Server. Sorry, 2005 and 2000. - - Index create statements are just to give you a rough idea of the syntax. It includes filters and fillfactor. - -- Example 1: index creates use ONLINE=? instead of ONLINE=ON / ONLINE=OFF. This is because it is important - for the user to understand if it is going to be offline and not just run a script. - -- Example 2: they do not include all the options the index may have been created with (padding, compression - filegroup/partition scheme etc.) - -- (The compression and filegroup index create syntax is not trivial because it is set at the partition - level and is not trivial to code.) - - Does not advise you about data modeling for clustered indexes and primary keys (primarily looks for signs of problems.) - -Unknown limitations of this version: - - We knew them once, but we forgot. - - -MIT License - -Copyright (c) Brent Ozar Unlimited - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -'; -RETURN; -END; /* @Help = 1 */ - -DECLARE @ScriptVersionName NVARCHAR(50); -DECLARE @DaysUptime NUMERIC(23,2); -DECLARE @DatabaseID INT; -DECLARE @ObjectID INT; -DECLARE @dsql NVARCHAR(MAX); -DECLARE @params NVARCHAR(MAX); -DECLARE @msg NVARCHAR(4000); -DECLARE @ErrorSeverity INT; -DECLARE @ErrorState INT; -DECLARE @Rowcount BIGINT; -DECLARE @SQLServerProductVersion NVARCHAR(128); -DECLARE @SQLServerEdition INT; -DECLARE @FilterMB INT; -DECLARE @collation NVARCHAR(256); -DECLARE @NumDatabases INT; -DECLARE @LineFeed NVARCHAR(5); -DECLARE @DaysUptimeInsertValue NVARCHAR(256); -DECLARE @DatabaseToIgnore NVARCHAR(MAX); -DECLARE @ColumnList NVARCHAR(MAX); -DECLARE @ColumnListWithApostrophes NVARCHAR(MAX); -DECLARE @PartitionCount INT; -DECLARE @OptimizeForSequentialKey BIT = 0; -DECLARE @StringToExecute NVARCHAR(MAX); - - -/* Let's get @SortOrder set to lower case here for comparisons later */ -SET @SortOrder = REPLACE(LOWER(@SortOrder), N' ', N'_'); -SET @SortDirection = LOWER(@SortDirection); - -SET @LineFeed = CHAR(13) + CHAR(10); -SELECT @SQLServerProductVersion = CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)); -SELECT @SQLServerEdition =CAST(SERVERPROPERTY('EngineEdition') AS INT); /* We default to online index creates where EngineEdition=3*/ -SET @FilterMB=250; -SELECT @ScriptVersionName = 'sp_BlitzIndex(TM) v' + @Version + ' - ' + DATENAME(MM, @VersionDate) + ' ' + RIGHT('0'+DATENAME(DD, @VersionDate),2) + ', ' + DATENAME(YY, @VersionDate); -SET @IgnoreDatabases = REPLACE(REPLACE(LTRIM(RTRIM(@IgnoreDatabases)), CHAR(10), ''), CHAR(13), ''); - -SELECT - @OptimizeForSequentialKey = - CASE WHEN EXISTS - ( - SELECT - 1/0 - FROM sys.all_columns AS ac - WHERE ac.object_id = OBJECT_ID('sys.indexes') - AND ac.name = N'optimize_for_sequential_key' - ) - THEN 1 - ELSE 0 - END; - -RAISERROR(N'Starting run. %s', 0,1, @ScriptVersionName) WITH NOWAIT; - - -IF(@OutputType NOT IN ('TABLE','NONE')) -BEGIN - RAISERROR('Invalid value for parameter @OutputType. Expected: (TABLE;NONE)',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; - SET @OutputType = 'NONE' -END; - -IF(@OutputType = 'NONE') -BEGIN - - IF ((@OutputServerName IS NOT NULL) AND (@OutputTableName IS NULL OR @OutputSchemaName IS NULL OR @OutputDatabaseName IS NULL)) - BEGIN - RAISERROR('Parameter @OutputServerName is specified, rest of @Output* parameters needs to also be specified',12,1); - RETURN; - END; - - IF(@OutputTableName IS NULL OR @OutputSchemaName IS NULL OR @OutputDatabaseName IS NULL) - BEGIN - RAISERROR('This procedure should be called with a value for @OutputTableName, @OutputSchemaName and @OutputDatabaseName parameters, as @OutputType is set to NONE',12,1); - RETURN; - END; - /* Output is supported for all modes, no reason to not bring pain and output - IF(@BringThePain = 1) - BEGIN - RAISERROR('Incompatible Parameters: @BringThePain set to 1 and @OutputType set to NONE',12,1); - RETURN; - END; - */ - /* Eventually limit by mode - IF(@Mode not in (0,4)) - BEGIN - RAISERROR('Incompatible Parameters: @Mode set to %d and @OutputType set to NONE',12,1,@Mode); - RETURN; - END; - */ -END; - -IF OBJECT_ID('tempdb..#IndexSanity') IS NOT NULL - DROP TABLE #IndexSanity; - -IF OBJECT_ID('tempdb..#IndexPartitionSanity') IS NOT NULL - DROP TABLE #IndexPartitionSanity; - -IF OBJECT_ID('tempdb..#IndexSanitySize') IS NOT NULL - DROP TABLE #IndexSanitySize; - -IF OBJECT_ID('tempdb..#IndexColumns') IS NOT NULL - DROP TABLE #IndexColumns; - -IF OBJECT_ID('tempdb..#MissingIndexes') IS NOT NULL - DROP TABLE #MissingIndexes; - -IF OBJECT_ID('tempdb..#ForeignKeys') IS NOT NULL - DROP TABLE #ForeignKeys; - -IF OBJECT_ID('tempdb..#UnindexedForeignKeys') IS NOT NULL - DROP TABLE #UnindexedForeignKeys; - -IF OBJECT_ID('tempdb..#BlitzIndexResults') IS NOT NULL - DROP TABLE #BlitzIndexResults; - -IF OBJECT_ID('tempdb..#IndexCreateTsql') IS NOT NULL - DROP TABLE #IndexCreateTsql; - -IF OBJECT_ID('tempdb..#DatabaseList') IS NOT NULL - DROP TABLE #DatabaseList; - -IF OBJECT_ID('tempdb..#Statistics') IS NOT NULL - DROP TABLE #Statistics; - -IF OBJECT_ID('tempdb..#PartitionCompressionInfo') IS NOT NULL - DROP TABLE #PartitionCompressionInfo; - -IF OBJECT_ID('tempdb..#ComputedColumns') IS NOT NULL - DROP TABLE #ComputedColumns; - -IF OBJECT_ID('tempdb..#TraceStatus') IS NOT NULL - DROP TABLE #TraceStatus; - -IF OBJECT_ID('tempdb..#TemporalTables') IS NOT NULL - DROP TABLE #TemporalTables; - -IF OBJECT_ID('tempdb..#CheckConstraints') IS NOT NULL - DROP TABLE #CheckConstraints; - -IF OBJECT_ID('tempdb..#FilteredIndexes') IS NOT NULL - DROP TABLE #FilteredIndexes; - -IF OBJECT_ID('tempdb..#Ignore_Databases') IS NOT NULL - DROP TABLE #Ignore_Databases - -IF OBJECT_ID('tempdb..#dm_db_partition_stats_etc') IS NOT NULL - DROP TABLE #dm_db_partition_stats_etc -IF OBJECT_ID('tempdb..#dm_db_index_operational_stats') IS NOT NULL - DROP TABLE #dm_db_index_operational_stats - - RAISERROR (N'Create temp tables.',0,1) WITH NOWAIT; - CREATE TABLE #BlitzIndexResults - ( - blitz_result_id INT IDENTITY PRIMARY KEY, - check_id INT NOT NULL, - index_sanity_id INT NULL, - Priority INT NULL, - findings_group NVARCHAR(4000) NOT NULL, - finding NVARCHAR(200) NOT NULL, - [database_name] NVARCHAR(128) NULL, - URL NVARCHAR(200) NOT NULL, - details NVARCHAR(MAX) NOT NULL, - index_definition NVARCHAR(MAX) NOT NULL, - secret_columns NVARCHAR(MAX) NULL, - index_usage_summary NVARCHAR(MAX) NULL, - index_size_summary NVARCHAR(MAX) NULL, - create_tsql NVARCHAR(MAX) NULL, - more_info NVARCHAR(MAX) NULL, - sample_query_plan XML NULL - ); - - CREATE TABLE #IndexSanity - ( - [index_sanity_id] INT IDENTITY PRIMARY KEY CLUSTERED, - [database_id] SMALLINT NOT NULL , - [object_id] INT NOT NULL , - [index_id] INT NOT NULL , - [index_type] TINYINT NOT NULL, - [database_name] NVARCHAR(128) NOT NULL , - [schema_name] NVARCHAR(128) NOT NULL , - [object_name] NVARCHAR(128) NOT NULL , - index_name NVARCHAR(128) NULL , - key_column_names NVARCHAR(MAX) NULL , - key_column_names_with_sort_order NVARCHAR(MAX) NULL , - key_column_names_with_sort_order_no_types NVARCHAR(MAX) NULL , - count_key_columns INT NULL , - include_column_names NVARCHAR(MAX) NULL , - include_column_names_no_types NVARCHAR(MAX) NULL , - count_included_columns INT NULL , - partition_key_column_name NVARCHAR(MAX) NULL, - filter_definition NVARCHAR(MAX) NOT NULL , - optimize_for_sequential_key BIT NULL, - is_indexed_view BIT NOT NULL , - is_unique BIT NOT NULL , - is_primary_key BIT NOT NULL , - is_unique_constraint BIT NOT NULL , - is_XML bit NOT NULL, - is_spatial BIT NOT NULL, - is_NC_columnstore BIT NOT NULL, - is_CX_columnstore BIT NOT NULL, - is_in_memory_oltp BIT NOT NULL , - is_disabled BIT NOT NULL , - is_hypothetical BIT NOT NULL , - is_padded BIT NOT NULL , - fill_factor SMALLINT NOT NULL , - user_seeks BIGINT NOT NULL , - user_scans BIGINT NOT NULL , - user_lookups BIGINT NOT NULL , - user_updates BIGINT NULL , - last_user_seek DATETIME NULL , - last_user_scan DATETIME NULL , - last_user_lookup DATETIME NULL , - last_user_update DATETIME NULL , - is_referenced_by_foreign_key BIT DEFAULT(0), - secret_columns NVARCHAR(MAX) NULL, - count_secret_columns INT NULL, - create_date DATETIME NOT NULL, - modify_date DATETIME NOT NULL, - filter_columns_not_in_index NVARCHAR(MAX), - [db_schema_object_name] AS [schema_name] + N'.' + [object_name] , - [db_schema_object_indexid] AS [schema_name] + N'.' + [object_name] - + CASE WHEN [index_name] IS NOT NULL THEN N'.' + index_name - ELSE N'' - END + N' (' + CAST(index_id AS NVARCHAR(20)) + N')' , - first_key_column_name AS CASE WHEN count_key_columns > 1 - THEN LEFT(key_column_names, CHARINDEX(',', key_column_names, 0) - 1) - ELSE key_column_names - END , - index_definition AS - CASE WHEN partition_key_column_name IS NOT NULL - THEN N'[PARTITIONED BY:' + partition_key_column_name + N']' - ELSE '' - END + - CASE index_id - WHEN 0 THEN N'[HEAP] ' - WHEN 1 THEN N'[CX] ' - ELSE N'' END + CASE WHEN is_indexed_view = 1 THEN N'[VIEW] ' - ELSE N'' END + CASE WHEN is_primary_key = 1 THEN N'[PK] ' - ELSE N'' END + CASE WHEN is_XML = 1 THEN N'[XML] ' - ELSE N'' END + CASE WHEN is_spatial = 1 THEN N'[SPATIAL] ' - ELSE N'' END + CASE WHEN is_NC_columnstore = 1 THEN N'[COLUMNSTORE] ' - ELSE N'' END + CASE WHEN is_in_memory_oltp = 1 THEN N'[IN-MEMORY] ' - ELSE N'' END + CASE WHEN is_disabled = 1 THEN N'[DISABLED] ' - ELSE N'' END + CASE WHEN is_hypothetical = 1 THEN N'[HYPOTHETICAL] ' - ELSE N'' END + CASE WHEN is_unique = 1 AND is_primary_key = 0 AND is_unique_constraint = 0 THEN N'[UNIQUE] ' - ELSE N'' END + CASE WHEN is_unique_constraint = 1 AND is_primary_key = 0 THEN N'[UNIQUE CONSTRAINT] ' - ELSE N'' END + CASE WHEN count_key_columns > 0 THEN - N'[' + CAST(count_key_columns AS NVARCHAR(10)) + N' KEY' - + CASE WHEN count_key_columns > 1 THEN N'S' ELSE N'' END - + N'] ' + LTRIM(key_column_names_with_sort_order) - ELSE N'' END + CASE WHEN count_included_columns > 0 THEN - N' [' + CAST(count_included_columns AS NVARCHAR(10)) + N' INCLUDE' + - + CASE WHEN count_included_columns > 1 THEN N'S' ELSE N'' END - + N'] ' + include_column_names - ELSE N'' END + CASE WHEN filter_definition <> N'' THEN N' [FILTER] ' + filter_definition - ELSE N'' END , - [total_reads] AS user_seeks + user_scans + user_lookups, - [reads_per_write] AS CAST(CASE WHEN user_updates > 0 - THEN ( user_seeks + user_scans + user_lookups ) / (1.0 * user_updates) - ELSE 0 END AS MONEY) , - [index_usage_summary] AS - CASE WHEN is_spatial = 1 THEN N'Not Tracked' - WHEN is_disabled = 1 THEN N'Disabled' - ELSE N'Reads: ' + - REPLACE(CONVERT(NVARCHAR(30),CAST((user_seeks + user_scans + user_lookups) AS MONEY), 1), N'.00', N'') - + CASE WHEN user_seeks + user_scans + user_lookups > 0 THEN - N' (' - + RTRIM( - CASE WHEN user_seeks > 0 THEN REPLACE(CONVERT(NVARCHAR(30),CAST((user_seeks) AS MONEY), 1), N'.00', N'') + N' seek ' ELSE N'' END - + CASE WHEN user_scans > 0 THEN REPLACE(CONVERT(NVARCHAR(30),CAST((user_scans) AS MONEY), 1), N'.00', N'') + N' scan ' ELSE N'' END - + CASE WHEN user_lookups > 0 THEN REPLACE(CONVERT(NVARCHAR(30),CAST((user_lookups) AS MONEY), 1), N'.00', N'') + N' lookup' ELSE N'' END - ) - + N') ' - ELSE N' ' - END - + N'Writes: ' + - REPLACE(CONVERT(NVARCHAR(30),CAST(user_updates AS MONEY), 1), N'.00', N'') - END /* First "end" is about is_spatial */, - [more_info] AS - CASE WHEN is_in_memory_oltp = 1 - THEN N'EXEC dbo.sp_BlitzInMemoryOLTP @dbName=' + QUOTENAME([database_name],N'''') + - N', @tableName=' + QUOTENAME([object_name],N'''') + N';' - ELSE N'EXEC dbo.sp_BlitzIndex @DatabaseName=' + QUOTENAME([database_name],N'''') + - N', @SchemaName=' + QUOTENAME([schema_name],N'''') + N', @TableName=' + QUOTENAME([object_name],N'''') + N';' - END - ); - RAISERROR (N'Adding UQ index on #IndexSanity (database_id, object_id, index_id)',0,1) WITH NOWAIT; - IF NOT EXISTS(SELECT 1 FROM tempdb.sys.indexes WHERE name='uq_database_id_object_id_index_id') - CREATE UNIQUE INDEX uq_database_id_object_id_index_id ON #IndexSanity (database_id, object_id, index_id); - - - CREATE TABLE #IndexPartitionSanity - ( - [index_partition_sanity_id] INT IDENTITY, - [index_sanity_id] INT NULL , - [database_id] INT NOT NULL , - [object_id] INT NOT NULL , - [schema_name] NVARCHAR(128) NOT NULL, - [index_id] INT NOT NULL , - [partition_number] INT NOT NULL , - row_count BIGINT NOT NULL , - reserved_MB NUMERIC(29,2) NOT NULL , - reserved_LOB_MB NUMERIC(29,2) NOT NULL , - reserved_row_overflow_MB NUMERIC(29,2) NOT NULL , - reserved_dictionary_MB NUMERIC(29,2) NOT NULL , - leaf_insert_count BIGINT NULL , - leaf_delete_count BIGINT NULL , - leaf_update_count BIGINT NULL , - range_scan_count BIGINT NULL , - singleton_lookup_count BIGINT NULL , - forwarded_fetch_count BIGINT NULL , - lob_fetch_in_pages BIGINT NULL , - lob_fetch_in_bytes BIGINT NULL , - row_overflow_fetch_in_pages BIGINT NULL , - row_overflow_fetch_in_bytes BIGINT NULL , - row_lock_count BIGINT NULL , - row_lock_wait_count BIGINT NULL , - row_lock_wait_in_ms BIGINT NULL , - page_lock_count BIGINT NULL , - page_lock_wait_count BIGINT NULL , - page_lock_wait_in_ms BIGINT NULL , - index_lock_promotion_attempt_count BIGINT NULL , - index_lock_promotion_count BIGINT NULL, - data_compression_desc NVARCHAR(60) NULL, - page_latch_wait_count BIGINT NULL, - page_latch_wait_in_ms BIGINT NULL, - page_io_latch_wait_count BIGINT NULL, - page_io_latch_wait_in_ms BIGINT NULL, - lock_escalation_desc nvarchar(60) NULL - ); - - CREATE TABLE #IndexSanitySize - ( - [index_sanity_size_id] INT IDENTITY NOT NULL , - [index_sanity_id] INT NULL , - [database_id] INT NOT NULL, - [schema_name] NVARCHAR(128) NOT NULL, - partition_count INT NOT NULL , - total_rows BIGINT NOT NULL , - total_reserved_MB NUMERIC(29,2) NOT NULL , - total_reserved_LOB_MB NUMERIC(29,2) NOT NULL , - total_reserved_row_overflow_MB NUMERIC(29,2) NOT NULL , - total_reserved_dictionary_MB NUMERIC(29,2) NOT NULL , - total_leaf_delete_count BIGINT NULL, - total_leaf_update_count BIGINT NULL, - total_range_scan_count BIGINT NULL, - total_singleton_lookup_count BIGINT NULL, - total_forwarded_fetch_count BIGINT NULL, - total_row_lock_count BIGINT NULL , - total_row_lock_wait_count BIGINT NULL , - total_row_lock_wait_in_ms BIGINT NULL , - avg_row_lock_wait_in_ms BIGINT NULL , - total_page_lock_count BIGINT NULL , - total_page_lock_wait_count BIGINT NULL , - total_page_lock_wait_in_ms BIGINT NULL , - avg_page_lock_wait_in_ms BIGINT NULL , - total_index_lock_promotion_attempt_count BIGINT NULL , - total_index_lock_promotion_count BIGINT NULL , - data_compression_desc NVARCHAR(4000) NULL, - page_latch_wait_count BIGINT NULL, - page_latch_wait_in_ms BIGINT NULL, - page_io_latch_wait_count BIGINT NULL, - page_io_latch_wait_in_ms BIGINT NULL, - lock_escalation_desc nvarchar(60) NULL, - index_size_summary AS ISNULL( - CASE WHEN partition_count > 1 - THEN N'[' + CAST(partition_count AS NVARCHAR(10)) + N' PARTITIONS] ' - ELSE N'' - END + REPLACE(CONVERT(NVARCHAR(30),CAST([total_rows] AS MONEY), 1), N'.00', N'') + N' rows; ' - + CASE WHEN total_reserved_MB > 1024 THEN - CAST(CAST(total_reserved_MB/1024. AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'GB' - ELSE - CAST(CAST(total_reserved_MB AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'MB' - END - + CASE WHEN total_reserved_LOB_MB > 1024 THEN - N'; ' + CAST(CAST(total_reserved_LOB_MB/1024. AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'GB ' + CASE WHEN total_reserved_dictionary_MB = 0 THEN N'LOB' ELSE N'Columnstore' END - WHEN total_reserved_LOB_MB > 0 THEN - N'; ' + CAST(CAST(total_reserved_LOB_MB AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'MB ' + CASE WHEN total_reserved_dictionary_MB = 0 THEN N'LOB' ELSE N'Columnstore' END - ELSE '' - END - + CASE WHEN total_reserved_row_overflow_MB > 1024 THEN - N'; ' + CAST(CAST(total_reserved_row_overflow_MB/1024. AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'GB Row Overflow' - WHEN total_reserved_row_overflow_MB > 0 THEN - N'; ' + CAST(CAST(total_reserved_row_overflow_MB AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'MB Row Overflow' - ELSE '' - END - + CASE WHEN total_reserved_dictionary_MB > 1024 THEN - N'; ' + CAST(CAST(total_reserved_dictionary_MB/1024. AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'GB Dictionaries' - WHEN total_reserved_dictionary_MB > 0 THEN - N'; ' + CAST(CAST(total_reserved_dictionary_MB AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'MB Dictionaries' - ELSE '' - END , - N'Error- NULL in computed column'), - index_op_stats AS ISNULL( - ( - REPLACE(CONVERT(NVARCHAR(30),CAST(total_singleton_lookup_count AS MONEY), 1),N'.00',N'') + N' singleton lookups; ' - + REPLACE(CONVERT(NVARCHAR(30),CAST(total_range_scan_count AS MONEY), 1),N'.00',N'') + N' scans/seeks; ' - + REPLACE(CONVERT(NVARCHAR(30),CAST(total_leaf_delete_count AS MONEY), 1),N'.00',N'') + N' deletes; ' - + REPLACE(CONVERT(NVARCHAR(30),CAST(total_leaf_update_count AS MONEY), 1),N'.00',N'') + N' updates; ' - + CASE WHEN ISNULL(total_forwarded_fetch_count,0) >0 THEN - REPLACE(CONVERT(NVARCHAR(30),CAST(total_forwarded_fetch_count AS MONEY), 1),N'.00',N'') + N' forward records fetched; ' - ELSE N'' END - - /* rows will only be in this dmv when data is in memory for the table */ - ), N'Table metadata not in memory'), - index_lock_wait_summary AS ISNULL( - CASE WHEN total_row_lock_wait_count = 0 AND total_page_lock_wait_count = 0 AND - total_index_lock_promotion_attempt_count = 0 THEN N'0 lock waits; ' - + CASE WHEN lock_escalation_desc = N'DISABLE' THEN N'Lock escalation DISABLE.' - ELSE N'' - END - ELSE - CASE WHEN total_row_lock_wait_count > 0 THEN - N'Row lock waits: ' + REPLACE(CONVERT(NVARCHAR(30),CAST(total_row_lock_wait_count AS MONEY), 1), N'.00', N'') - + N'; total duration: ' + - CASE WHEN total_row_lock_wait_in_ms >= 60000 THEN /*More than 1 min*/ - REPLACE(CONVERT(NVARCHAR(30),CAST((total_row_lock_wait_in_ms/60000) AS MONEY), 1), N'.00', N'') + N' minutes; ' - ELSE - REPLACE(CONVERT(NVARCHAR(30),CAST(ISNULL(total_row_lock_wait_in_ms/1000,0) AS MONEY), 1), N'.00', N'') + N' seconds; ' - END - + N'avg duration: ' + - CASE WHEN avg_row_lock_wait_in_ms >= 60000 THEN /*More than 1 min*/ - REPLACE(CONVERT(NVARCHAR(30),CAST((avg_row_lock_wait_in_ms/60000) AS MONEY), 1), N'.00', N'') + N' minutes; ' - ELSE - REPLACE(CONVERT(NVARCHAR(30),CAST(ISNULL(avg_row_lock_wait_in_ms/1000,0) AS MONEY), 1), N'.00', N'') + N' seconds; ' - END - ELSE N'' - END + - CASE WHEN total_page_lock_wait_count > 0 THEN - N'Page lock waits: ' + REPLACE(CONVERT(NVARCHAR(30),CAST(total_page_lock_wait_count AS MONEY), 1), N'.00', N'') - + N'; total duration: ' + - CASE WHEN total_page_lock_wait_in_ms >= 60000 THEN /*More than 1 min*/ - REPLACE(CONVERT(NVARCHAR(30),CAST((total_page_lock_wait_in_ms/60000) AS MONEY), 1), N'.00', N'') + N' minutes; ' - ELSE - REPLACE(CONVERT(NVARCHAR(30),CAST(ISNULL(total_page_lock_wait_in_ms/1000,0) AS MONEY), 1), N'.00', N'') + N' seconds; ' - END - + N'avg duration: ' + - CASE WHEN avg_page_lock_wait_in_ms >= 60000 THEN /*More than 1 min*/ - REPLACE(CONVERT(NVARCHAR(30),CAST((avg_page_lock_wait_in_ms/60000) AS MONEY), 1), N'.00', N'') + N' minutes; ' - ELSE - REPLACE(CONVERT(NVARCHAR(30),CAST(ISNULL(avg_page_lock_wait_in_ms/1000,0) AS MONEY), 1), N'.00', N'') + N' seconds; ' - END - ELSE N'' - END + - CASE WHEN total_index_lock_promotion_attempt_count > 0 THEN - N'Lock escalation attempts: ' + REPLACE(CONVERT(NVARCHAR(30),CAST(total_index_lock_promotion_attempt_count AS MONEY), 1), N'.00', N'') - + N'; Actual Escalations: ' + REPLACE(CONVERT(NVARCHAR(30),CAST(ISNULL(total_index_lock_promotion_count,0) AS MONEY), 1), N'.00', N'') +N'; ' - ELSE N'' - END + - CASE WHEN lock_escalation_desc = N'DISABLE' THEN - N'Lock escalation is disabled.' - ELSE N'' - END - END - ,'Error- NULL in computed column') - ); - - CREATE TABLE #IndexColumns - ( - [database_id] INT NOT NULL, - [schema_name] NVARCHAR(128), - [object_id] INT NOT NULL , - [index_id] INT NOT NULL , - [key_ordinal] INT NULL , - is_included_column BIT NULL , - is_descending_key BIT NULL , - [partition_ordinal] INT NULL , - column_name NVARCHAR(256) NOT NULL , - system_type_name NVARCHAR(256) NOT NULL, - max_length SMALLINT NOT NULL, - [precision] TINYINT NOT NULL, - [scale] TINYINT NOT NULL, - collation_name NVARCHAR(256) NULL, - is_nullable BIT NULL, - is_identity BIT NULL, - is_computed BIT NULL, - is_replicated BIT NULL, - is_sparse BIT NULL, - is_filestream BIT NULL, - seed_value DECIMAL(38,0) NULL, - increment_value DECIMAL(38,0) NULL , - last_value DECIMAL(38,0) NULL, - is_not_for_replication BIT NULL - ); - CREATE CLUSTERED INDEX CLIX_database_id_object_id_index_id ON #IndexColumns - (database_id, object_id, index_id); - - CREATE TABLE #MissingIndexes - ([database_id] INT NOT NULL, - [object_id] INT NOT NULL, - [database_name] NVARCHAR(128) NOT NULL , - [schema_name] NVARCHAR(128) NOT NULL , - [table_name] NVARCHAR(128), - [statement] NVARCHAR(512) NOT NULL, - magic_benefit_number AS (( user_seeks + user_scans ) * avg_total_user_cost * avg_user_impact), - avg_total_user_cost NUMERIC(29,4) NOT NULL, - avg_user_impact NUMERIC(29,1) NOT NULL, - user_seeks BIGINT NOT NULL, - user_scans BIGINT NOT NULL, - unique_compiles BIGINT NULL, - equality_columns NVARCHAR(MAX), - equality_columns_with_data_type NVARCHAR(MAX), - inequality_columns NVARCHAR(MAX), - inequality_columns_with_data_type NVARCHAR(MAX), - included_columns NVARCHAR(MAX), - included_columns_with_data_type NVARCHAR(MAX), - is_low BIT, - [index_estimated_impact] AS - REPLACE(CONVERT(NVARCHAR(256),CAST(CAST( - (user_seeks + user_scans) - AS BIGINT) AS MONEY), 1), '.00', '') + N' use' - + CASE WHEN (user_seeks + user_scans) > 1 THEN N's' ELSE N'' END - +N'; Impact: ' + CAST(avg_user_impact AS NVARCHAR(30)) - + N'%; Avg query cost: ' - + CAST(avg_total_user_cost AS NVARCHAR(30)), - [missing_index_details] AS - CASE WHEN COALESCE(equality_columns_with_data_type,equality_columns) IS NOT NULL - THEN N'EQUALITY: ' + COALESCE(CAST(equality_columns_with_data_type AS NVARCHAR(MAX)), CAST(equality_columns AS NVARCHAR(MAX))) + N' ' - ELSE N'' END + - - CASE WHEN COALESCE(inequality_columns_with_data_type,inequality_columns) IS NOT NULL - THEN N'INEQUALITY: ' + COALESCE(CAST(inequality_columns_with_data_type AS NVARCHAR(MAX)), CAST(inequality_columns AS NVARCHAR(MAX))) + N' ' - ELSE N'' END + - - CASE WHEN COALESCE(included_columns_with_data_type,included_columns) IS NOT NULL - THEN N'INCLUDE: ' + COALESCE(CAST(included_columns_with_data_type AS NVARCHAR(MAX)), CAST(included_columns AS NVARCHAR(MAX))) + N' ' - ELSE N'' END, - [create_tsql] AS N'CREATE INDEX [' - + LEFT(REPLACE(REPLACE(REPLACE(REPLACE( - ISNULL(equality_columns,N'')+ - CASE WHEN equality_columns IS NOT NULL AND inequality_columns IS NOT NULL THEN N'_' ELSE N'' END - + ISNULL(inequality_columns,''),',','') - ,'[',''),']',''),' ','_') - + CASE WHEN included_columns IS NOT NULL THEN N'_Includes' ELSE N'' END, 128) + N'] ON ' - + [statement] + N' (' + ISNULL(equality_columns,N'') - + CASE WHEN equality_columns IS NOT NULL AND inequality_columns IS NOT NULL THEN N', ' ELSE N'' END - + CASE WHEN inequality_columns IS NOT NULL THEN inequality_columns ELSE N'' END + - ') ' + CASE WHEN included_columns IS NOT NULL THEN N' INCLUDE (' + included_columns + N')' ELSE N'' END - + N' WITH (' - + N'FILLFACTOR=100, ONLINE=?, SORT_IN_TEMPDB=?, DATA_COMPRESSION=?' - + N')' - + N';' - , - [more_info] AS N'EXEC dbo.sp_BlitzIndex @DatabaseName=' + QUOTENAME([database_name],'''') + - N', @SchemaName=' + QUOTENAME([schema_name],'''') + N', @TableName=' + QUOTENAME([table_name],'''') + N';', - [sample_query_plan] XML NULL - ); - - CREATE TABLE #ForeignKeys ( - [database_id] INT NOT NULL, - [database_name] NVARCHAR(128) NOT NULL , - [schema_name] NVARCHAR(128) NOT NULL , - foreign_key_name NVARCHAR(256), - parent_object_id INT, - parent_object_name NVARCHAR(256), - referenced_object_id INT, - referenced_object_name NVARCHAR(256), - is_disabled BIT, - is_not_trusted BIT, - is_not_for_replication BIT, - parent_fk_columns NVARCHAR(MAX), - referenced_fk_columns NVARCHAR(MAX), - update_referential_action_desc NVARCHAR(16), - delete_referential_action_desc NVARCHAR(60) - ); - - CREATE TABLE #UnindexedForeignKeys - ( - [database_id] INT NOT NULL, - [database_name] NVARCHAR(128) NOT NULL , - [schema_name] NVARCHAR(128) NOT NULL , - foreign_key_name NVARCHAR(256), - parent_object_name NVARCHAR(256), - parent_object_id INT, - referenced_object_name NVARCHAR(256), - referenced_object_id INT - ); - - CREATE TABLE #IndexCreateTsql ( - index_sanity_id INT NOT NULL, - create_tsql NVARCHAR(MAX) NOT NULL - ); - - CREATE TABLE #DatabaseList ( - DatabaseName NVARCHAR(256), - secondary_role_allow_connections_desc NVARCHAR(50) - - ); - - CREATE TABLE #PartitionCompressionInfo ( - [index_sanity_id] INT NULL, - [partition_compression_detail] NVARCHAR(4000) NULL - ); - - CREATE TABLE #Statistics ( - database_id INT NOT NULL, - database_name NVARCHAR(256) NOT NULL, - table_name NVARCHAR(128) NULL, - schema_name NVARCHAR(128) NULL, - index_name NVARCHAR(128) NULL, - column_names NVARCHAR(MAX) NULL, - statistics_name NVARCHAR(128) NULL, - last_statistics_update DATETIME NULL, - days_since_last_stats_update INT NULL, - rows BIGINT NULL, - rows_sampled BIGINT NULL, - percent_sampled DECIMAL(18, 1) NULL, - histogram_steps INT NULL, - modification_counter BIGINT NULL, - percent_modifications DECIMAL(18, 1) NULL, - modifications_before_auto_update INT 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 - ); - - CREATE TABLE #ComputedColumns - ( - index_sanity_id INT IDENTITY(1, 1) NOT NULL, - database_name NVARCHAR(128) NULL, - database_id INT NOT NULL, - table_name NVARCHAR(128) NOT NULL, - schema_name NVARCHAR(128) NOT NULL, - column_name NVARCHAR(128) NULL, - is_nullable BIT NULL, - definition NVARCHAR(MAX) NULL, - uses_database_collation BIT NOT NULL, - is_persisted BIT NOT NULL, - is_computed BIT NOT NULL, - is_function INT NOT NULL, - column_definition NVARCHAR(MAX) NULL - ); - - CREATE TABLE #TraceStatus - ( - TraceFlag NVARCHAR(10) , - status BIT , - Global BIT , - Session BIT - ); - - CREATE TABLE #TemporalTables - ( - index_sanity_id INT IDENTITY(1, 1) NOT NULL, - database_name NVARCHAR(128) NOT NULL, - database_id INT NOT NULL, - schema_name NVARCHAR(128) NOT NULL, - table_name NVARCHAR(128) NOT NULL, - history_table_name NVARCHAR(128) NOT NULL, - history_schema_name NVARCHAR(128) NOT NULL, - start_column_name NVARCHAR(128) NOT NULL, - end_column_name NVARCHAR(128) NOT NULL, - period_name NVARCHAR(128) NOT NULL - ); - - CREATE TABLE #CheckConstraints - ( - index_sanity_id INT IDENTITY(1, 1) NOT NULL, - database_name NVARCHAR(128) NULL, - database_id INT NOT NULL, - table_name NVARCHAR(128) NOT NULL, - schema_name NVARCHAR(128) NOT NULL, - constraint_name NVARCHAR(128) NULL, - is_disabled BIT NULL, - definition NVARCHAR(MAX) NULL, - uses_database_collation BIT NOT NULL, - is_not_trusted BIT NOT NULL, - is_function INT NOT NULL, - column_definition NVARCHAR(MAX) NULL - ); - - CREATE TABLE #FilteredIndexes - ( - index_sanity_id INT IDENTITY(1, 1) NOT NULL, - database_name NVARCHAR(128) NULL, - database_id INT NOT NULL, - schema_name NVARCHAR(128) NOT NULL, - table_name NVARCHAR(128) NOT NULL, - index_name NVARCHAR(128) NULL, - column_name NVARCHAR(128) NULL - ); - - CREATE TABLE #Ignore_Databases - ( - DatabaseName NVARCHAR(128), - Reason NVARCHAR(100) - ); - -/* Sanitize our inputs */ -SELECT - @OutputServerName = QUOTENAME(@OutputServerName), - @OutputDatabaseName = QUOTENAME(@OutputDatabaseName), - @OutputSchemaName = QUOTENAME(@OutputSchemaName), - @OutputTableName = QUOTENAME(@OutputTableName); - - -IF @GetAllDatabases = 1 - BEGIN - INSERT INTO #DatabaseList (DatabaseName) - SELECT DB_NAME(database_id) - FROM sys.databases - WHERE user_access_desc = 'MULTI_USER' - AND state_desc = 'ONLINE' - AND database_id > 4 - AND DB_NAME(database_id) NOT LIKE 'ReportServer%' - AND DB_NAME(database_id) NOT LIKE 'rdsadmin%' - AND LOWER(name) NOT IN('dbatools', 'dbadmin', 'dbmaintenance') - AND is_distributor = 0 - OPTION ( RECOMPILE ); - - /* Skip non-readable databases in an AG - see Github issue #1160 */ - IF EXISTS (SELECT * FROM sys.all_objects o INNER JOIN sys.all_columns c ON o.object_id = c.object_id AND o.name = 'dm_hadr_availability_replica_states' AND c.name = 'role_desc') - BEGIN - SET @dsql = N'UPDATE #DatabaseList SET secondary_role_allow_connections_desc = ''NO'' WHERE DatabaseName IN ( - SELECT d.name - FROM sys.dm_hadr_availability_replica_states rs - INNER JOIN sys.databases d ON rs.replica_id = d.replica_id - INNER JOIN sys.availability_replicas r ON rs.replica_id = r.replica_id - WHERE rs.role_desc = ''SECONDARY'' - AND r.secondary_role_allow_connections_desc = ''NO'') - OPTION ( RECOMPILE );'; - EXEC sp_executesql @dsql; - - IF EXISTS (SELECT * FROM #DatabaseList WHERE secondary_role_allow_connections_desc = 'NO') - BEGIN - INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, database_name, URL, details, index_definition, - index_usage_summary, index_size_summary ) - VALUES ( 1, - 0, - N'Skipped non-readable AG secondary databases.', - N'You are running this on an AG secondary, and some of your databases are configured as non-readable when this is a secondary node.', - N'To analyze those databases, run sp_BlitzIndex on the primary, or on a readable secondary.', - 'http://FirstResponderKit.org', '', '', '', '' - ); - END; - END; - - IF @IgnoreDatabases IS NOT NULL - AND LEN(@IgnoreDatabases) > 0 - BEGIN - RAISERROR(N'Setting up filter to ignore databases', 0, 1) WITH NOWAIT; - SET @DatabaseToIgnore = ''; - - WHILE LEN(@IgnoreDatabases) > 0 - BEGIN - IF PATINDEX('%,%', @IgnoreDatabases) > 0 - BEGIN - SET @DatabaseToIgnore = SUBSTRING(@IgnoreDatabases, 0, PATINDEX('%,%',@IgnoreDatabases)) ; - - INSERT INTO #Ignore_Databases (DatabaseName, Reason) - SELECT LTRIM(RTRIM(@DatabaseToIgnore)), 'Specified in the @IgnoreDatabases parameter' - OPTION (RECOMPILE) ; - - SET @IgnoreDatabases = SUBSTRING(@IgnoreDatabases, LEN(@DatabaseToIgnore + ',') + 1, LEN(@IgnoreDatabases)) ; - END; - ELSE - BEGIN - SET @DatabaseToIgnore = @IgnoreDatabases ; - SET @IgnoreDatabases = NULL ; - - INSERT INTO #Ignore_Databases (DatabaseName, Reason) - SELECT LTRIM(RTRIM(@DatabaseToIgnore)), 'Specified in the @IgnoreDatabases parameter' - OPTION (RECOMPILE) ; - END; - END; - - END - - END; -ELSE - BEGIN - INSERT INTO #DatabaseList - ( DatabaseName ) - SELECT CASE - WHEN @DatabaseName IS NULL OR @DatabaseName = N'' - THEN DB_NAME() - ELSE @DatabaseName END; - END; - -SET @NumDatabases = (SELECT COUNT(*) FROM #DatabaseList AS D LEFT OUTER JOIN #Ignore_Databases AS I ON D.DatabaseName = I.DatabaseName WHERE I.DatabaseName IS NULL); -SET @msg = N'Number of databases to examine: ' + CAST(@NumDatabases AS NVARCHAR(50)); -RAISERROR (@msg,0,1) WITH NOWAIT; - - - -/* Running on 50+ databases can take a reaaallly long time, so we want explicit permission to do so (and only after warning about it) */ - - -BEGIN TRY - IF @NumDatabases >= 50 AND @BringThePain != 1 AND @TableName IS NULL - BEGIN - - INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, URL, details, index_definition, - index_usage_summary, index_size_summary ) - VALUES ( -1, - 0 , - @ScriptVersionName, - CASE WHEN @GetAllDatabases = 1 THEN N'All Databases' ELSE N'Database ' + QUOTENAME(@DatabaseName) + N' as of ' + CONVERT(NVARCHAR(16), GETDATE(), 121) END, - N'From Your Community Volunteers', - N'http://FirstResponderKit.org', - N'', - N'', - N'' - ); - INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, database_name, URL, details, index_definition, - index_usage_summary, index_size_summary ) - VALUES ( 1, - 0, - N'You''re trying to run sp_BlitzIndex on a server with ' + CAST(@NumDatabases AS NVARCHAR(8)) + N' databases. ', - N'Running sp_BlitzIndex on a server with 50+ databases may cause temporary problems for the server and/or user.', - N'If you''re sure you want to do this, run again with the parameter @BringThePain = 1.', - 'http://FirstResponderKit.org', - '', - '', - '', - '' - ); - - if(@OutputType <> 'NONE') - BEGIN - SELECT bir.blitz_result_id, - bir.check_id, - bir.index_sanity_id, - bir.Priority, - bir.findings_group, - bir.finding, - bir.database_name, - bir.URL, - bir.details, - bir.index_definition, - bir.secret_columns, - bir.index_usage_summary, - bir.index_size_summary, - bir.create_tsql, - bir.more_info - FROM #BlitzIndexResults AS bir; - RAISERROR('Running sp_BlitzIndex on a server with 50+ databases may cause temporary problems for the server', 12, 1); - END; - - RETURN; - - END; -END TRY -BEGIN CATCH - RAISERROR (N'Failure to execute due to number of databases.', 0,1) WITH NOWAIT; - - SELECT @msg = ERROR_MESSAGE(), - @ErrorSeverity = ERROR_SEVERITY(), - @ErrorState = ERROR_STATE(); - - RAISERROR (@msg, @ErrorSeverity, @ErrorState); - - WHILE @@trancount > 0 - ROLLBACK; - - RETURN; - END CATCH; - - -RAISERROR (N'Checking partition counts to exclude databases with over 100 partitions',0,1) WITH NOWAIT; -IF @BringThePain = 0 AND @SkipPartitions = 0 AND @TableName IS NULL - BEGIN - DECLARE partition_cursor CURSOR FOR - SELECT dl.DatabaseName - FROM #DatabaseList dl - LEFT OUTER JOIN #Ignore_Databases i ON dl.DatabaseName = i.DatabaseName - WHERE COALESCE(dl.secondary_role_allow_connections_desc, 'OK') <> 'NO' - AND i.DatabaseName IS NULL - - OPEN partition_cursor - FETCH NEXT FROM partition_cursor INTO @DatabaseName - - WHILE @@FETCH_STATUS = 0 - BEGIN - /* Count the total number of partitions */ - SET @dsql = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @RowcountOUT = SUM(1) FROM ' + QUOTENAME(@DatabaseName) + '.sys.partitions WHERE partition_number > 1 OPTION ( RECOMPILE );'; - EXEC sp_executesql @dsql, N'@RowcountOUT BIGINT OUTPUT', @RowcountOUT = @Rowcount OUTPUT; - IF @Rowcount > 100 - BEGIN - RAISERROR (N'Skipping database %s because > 100 partitions were found. To check this database, you must set @BringThePain = 1.',0,1,@DatabaseName) WITH NOWAIT; - INSERT INTO #Ignore_Databases (DatabaseName, Reason) - SELECT @DatabaseName, 'Over 100 partitions found - use @BringThePain = 1 to analyze' - END; - FETCH NEXT FROM partition_cursor INTO @DatabaseName - END; - CLOSE partition_cursor - DEALLOCATE partition_cursor - - END; - -INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, URL, details, index_definition, - index_usage_summary, index_size_summary ) -SELECT 1, 0 , - 'Database Skipped', - i.DatabaseName, - 'http://FirstResponderKit.org', - i.Reason, '', '', '' -FROM #Ignore_Databases i; - - -/* Last startup */ -IF COLUMNPROPERTY(OBJECT_ID('sys.dm_os_sys_info'),'sqlserver_start_time','ColumnID') IS NOT NULL -BEGIN - SELECT @DaysUptime = CAST(DATEDIFF(HOUR, sqlserver_start_time, GETDATE()) / 24. AS NUMERIC (23,2)) - FROM sys.dm_os_sys_info; -END -ELSE -BEGIN - SELECT @DaysUptime = CAST(DATEDIFF(HOUR, create_date, GETDATE()) / 24. AS NUMERIC (23,2)) - FROM sys.databases - WHERE database_id = 2; -END - -IF @DaysUptime = 0 OR @DaysUptime IS NULL - SET @DaysUptime = .01; - -SELECT @DaysUptimeInsertValue = 'Server: ' + (CONVERT(VARCHAR(256), (SERVERPROPERTY('ServerName')))) + ' Days Uptime: ' + RTRIM(@DaysUptime); - - -/* Permission granted or unnecessary? Ok, let's go! */ - -RAISERROR (N'Starting loop through databases',0,1) WITH NOWAIT; -DECLARE c1 CURSOR -LOCAL FAST_FORWARD -FOR -SELECT dl.DatabaseName -FROM #DatabaseList dl -LEFT OUTER JOIN #Ignore_Databases i ON dl.DatabaseName = i.DatabaseName -WHERE COALESCE(dl.secondary_role_allow_connections_desc, 'OK') <> 'NO' - AND i.DatabaseName IS NULL -ORDER BY dl.DatabaseName; - -OPEN c1; -FETCH NEXT FROM c1 INTO @DatabaseName; - WHILE @@FETCH_STATUS = 0 - -BEGIN - - RAISERROR (@LineFeed, 0, 1) WITH NOWAIT; - RAISERROR (@LineFeed, 0, 1) WITH NOWAIT; - RAISERROR (@DatabaseName, 0, 1) WITH NOWAIT; - -SELECT @DatabaseID = [database_id] -FROM sys.databases - WHERE [name] = @DatabaseName - AND user_access_desc='MULTI_USER' - AND state_desc = 'ONLINE'; - ----------------------------------------- ---STEP 1: OBSERVE THE PATIENT ---This step puts index information into temp tables. ----------------------------------------- -BEGIN TRY - BEGIN - DECLARE @d VARCHAR(19) = CONVERT(VARCHAR(19), GETDATE(), 121); - RAISERROR (N'starting at %s',0,1, @d) WITH NOWAIT; - - --Validate SQL Server Version - - IF (SELECT LEFT(@SQLServerProductVersion, - CHARINDEX('.',@SQLServerProductVersion,0)-1 - )) <= 9 - BEGIN - SET @msg=N'sp_BlitzIndex is only supported on SQL Server 2008 and higher. The version of this instance is: ' + @SQLServerProductVersion; - RAISERROR(@msg,16,1); - END; - - --Short circuit here if database name does not exist. - IF @DatabaseName IS NULL OR @DatabaseID IS NULL - BEGIN - SET @msg='Database does not exist or is not online/multi-user: cannot proceed.'; - RAISERROR(@msg,16,1); - END; - - --Validate parameters. - IF (@Mode NOT IN (0,1,2,3,4)) - BEGIN - SET @msg=N'Invalid @Mode parameter. 0=diagnose, 1=summarize, 2=index detail, 3=missing index detail, 4=diagnose detail'; - RAISERROR(@msg,16,1); - END; - - IF (@Mode <> 0 AND @TableName IS NOT NULL) - BEGIN - SET @msg=N'Setting the @Mode doesn''t change behavior if you supply @TableName. Use default @Mode=0 to see table detail.'; - RAISERROR(@msg,16,1); - END; - - IF ((@Mode <> 0 OR @TableName IS NOT NULL) AND @Filter <> 0) - BEGIN - SET @msg=N'@Filter only applies when @Mode=0 and @TableName is not specified. Please try again.'; - RAISERROR(@msg,16,1); - END; - - IF (@SchemaName IS NOT NULL AND @TableName IS NULL) - BEGIN - SET @msg='We can''t run against a whole schema! Specify a @TableName, or leave both NULL for diagnosis.'; - RAISERROR(@msg,16,1); - END; - - - IF (@TableName IS NOT NULL AND @SchemaName IS NULL) - BEGIN - SET @SchemaName=N'dbo'; - SET @msg='@SchemaName wasn''t specified-- assuming schema=dbo.'; - RAISERROR(@msg,1,1) WITH NOWAIT; - END; - - --If a table is specified, grab the object id. - --Short circuit if it doesn't exist. - IF @TableName IS NOT NULL - BEGIN - SET @dsql = N' - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @ObjectID= OBJECT_ID - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.objects AS so - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS sc on - so.schema_id=sc.schema_id - where so.type in (''U'', ''V'') - and so.name=' + QUOTENAME(@TableName,'''')+ N' - and sc.name=' + QUOTENAME(@SchemaName,'''')+ N' - /*Has a row in sys.indexes. This lets us get indexed views.*/ - and exists ( - SELECT si.name - FROM ' + QUOTENAME(@DatabaseName) + '.sys.indexes AS si - WHERE so.object_id=si.object_id) - OPTION (RECOMPILE);'; - - SET @params='@ObjectID INT OUTPUT'; - - IF @dsql IS NULL - RAISERROR('@dsql is null',16,1); - - EXEC sp_executesql @dsql, @params, @ObjectID=@ObjectID OUTPUT; - - IF @ObjectID IS NULL - BEGIN - SET @msg=N'Oh, this is awkward. I can''t find the table or indexed view you''re looking for in that database.' + CHAR(10) + - N'Please check your parameters.'; - RAISERROR(@msg,1,1); - RETURN; - END; - END; - - --set @collation - SELECT @collation=collation_name - FROM sys.databases - WHERE database_id=@DatabaseID; - - --insert columns for clustered indexes and heaps - --collect info on identity columns for this one - SET @dsql = N'/* sp_BlitzIndex */ - SET LOCK_TIMEOUT 1000; /* To fix locking bug in sys.identity_columns. See Github issue #2176. */ - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT ' + CAST(@DatabaseID AS NVARCHAR(16)) + ', - s.name, - si.object_id, - si.index_id, - sc.key_ordinal, - sc.is_included_column, - sc.is_descending_key, - sc.partition_ordinal, - c.name as column_name, - st.name as system_type_name, - c.max_length, - c.[precision], - c.[scale], - c.collation_name, - c.is_nullable, - c.is_identity, - c.is_computed, - c.is_replicated, - ' + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N'c.is_sparse' ELSE N'NULL as is_sparse' END + N', - ' + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N'c.is_filestream' ELSE N'NULL as is_filestream' END + N', - CAST(ic.seed_value AS DECIMAL(38,0)), - CAST(ic.increment_value AS DECIMAL(38,0)), - CAST(ic.last_value AS DECIMAL(38,0)), - ic.is_not_for_replication - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.indexes si - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns c ON - si.object_id=c.object_id - LEFT JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.index_columns sc ON - sc.object_id = si.object_id - and sc.index_id=si.index_id - AND sc.column_id=c.column_id - LEFT JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.identity_columns ic ON - c.object_id=ic.object_id and - c.column_id=ic.column_id - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.types st ON - c.system_type_id=st.system_type_id - AND c.user_type_id=st.user_type_id - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects AS so ON si.object_id = so.object_id - AND so.is_ms_shipped = 0 - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s ON s.schema_id = so.schema_id - WHERE si.index_id in (0,1) ' - + CASE WHEN @ObjectID IS NOT NULL - THEN N' AND si.object_id=' + CAST(@ObjectID AS NVARCHAR(30)) - ELSE N'' END - + N'OPTION (RECOMPILE);'; - - IF @dsql IS NULL - RAISERROR('@dsql is null',16,1); - - RAISERROR (N'Inserting data into #IndexColumns for clustered indexes and heaps',0,1) WITH NOWAIT; - IF @Debug = 1 - BEGIN - PRINT SUBSTRING(@dsql, 1, 4000); - PRINT SUBSTRING(@dsql, 4001, 4000); - PRINT SUBSTRING(@dsql, 8001, 4000); - PRINT SUBSTRING(@dsql, 12001, 4000); - PRINT SUBSTRING(@dsql, 16001, 4000); - PRINT SUBSTRING(@dsql, 20001, 4000); - PRINT SUBSTRING(@dsql, 24001, 4000); - PRINT SUBSTRING(@dsql, 28001, 4000); - PRINT SUBSTRING(@dsql, 32001, 4000); - PRINT SUBSTRING(@dsql, 36001, 4000); - END; - BEGIN TRY - INSERT #IndexColumns ( database_id, [schema_name], [object_id], index_id, key_ordinal, is_included_column, is_descending_key, partition_ordinal, - column_name, system_type_name, max_length, precision, scale, collation_name, is_nullable, is_identity, is_computed, - is_replicated, is_sparse, is_filestream, seed_value, increment_value, last_value, is_not_for_replication ) - EXEC sp_executesql @dsql; - END TRY - BEGIN CATCH - RAISERROR (N'Failure inserting data into #IndexColumns for clustered indexes and heaps.', 0,1) WITH NOWAIT; - - IF @dsql IS NOT NULL - BEGIN - SET @msg= 'Last @dsql: ' + @dsql; - RAISERROR(@msg, 0, 1) WITH NOWAIT; - END; - - SELECT @msg = @DatabaseName + N' database failed to process. ' + ERROR_MESSAGE(), - @ErrorSeverity = 0, @ErrorState = ERROR_STATE(); - RAISERROR (@msg,@ErrorSeverity, @ErrorState )WITH NOWAIT; - - WHILE @@trancount > 0 - ROLLBACK; - - RETURN; - END CATCH; - - - --insert columns for nonclustered indexes - --this uses a full join to sys.index_columns - --We don't collect info on identity columns here. They may be in NC indexes, but we just analyze identities in the base table. - SET @dsql = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT ' + CAST(@DatabaseID AS NVARCHAR(16)) + ', - s.name, - si.object_id, - si.index_id, - sc.key_ordinal, - sc.is_included_column, - sc.is_descending_key, - sc.partition_ordinal, - c.name as column_name, - st.name as system_type_name, - c.max_length, - c.[precision], - c.[scale], - c.collation_name, - c.is_nullable, - c.is_identity, - c.is_computed, - c.is_replicated, - ' + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N'c.is_sparse' ELSE N'NULL AS is_sparse' END + N', - ' + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N'c.is_filestream' ELSE N'NULL AS is_filestream' END + N' - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.indexes AS si - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns AS c ON - si.object_id=c.object_id - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.index_columns AS sc ON - sc.object_id = si.object_id - and sc.index_id=si.index_id - AND sc.column_id=c.column_id - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.types AS st ON - c.system_type_id=st.system_type_id - AND c.user_type_id=st.user_type_id - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects AS so ON si.object_id = so.object_id - AND so.is_ms_shipped = 0 - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s ON s.schema_id = so.schema_id - WHERE si.index_id not in (0,1) ' - + CASE WHEN @ObjectID IS NOT NULL - THEN N' AND si.object_id=' + CAST(@ObjectID AS NVARCHAR(30)) - ELSE N'' END - + N'OPTION (RECOMPILE);'; - - IF @dsql IS NULL - RAISERROR('@dsql is null',16,1); - - RAISERROR (N'Inserting data into #IndexColumns for nonclustered indexes',0,1) WITH NOWAIT; - IF @Debug = 1 - BEGIN - PRINT SUBSTRING(@dsql, 0, 4000); - PRINT SUBSTRING(@dsql, 4000, 8000); - PRINT SUBSTRING(@dsql, 8000, 12000); - PRINT SUBSTRING(@dsql, 12000, 16000); - PRINT SUBSTRING(@dsql, 16000, 20000); - PRINT SUBSTRING(@dsql, 20000, 24000); - PRINT SUBSTRING(@dsql, 24000, 28000); - PRINT SUBSTRING(@dsql, 28000, 32000); - PRINT SUBSTRING(@dsql, 32000, 36000); - PRINT SUBSTRING(@dsql, 36000, 40000); - END; - INSERT #IndexColumns ( database_id, [schema_name], [object_id], index_id, key_ordinal, is_included_column, is_descending_key, partition_ordinal, - column_name, system_type_name, max_length, precision, scale, collation_name, is_nullable, is_identity, is_computed, - is_replicated, is_sparse, is_filestream ) - EXEC sp_executesql @dsql; - - SET @dsql = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT ' + CAST(@DatabaseID AS NVARCHAR(10)) + N' AS database_id, - so.object_id, - si.index_id, - si.type, - @i_DatabaseName AS database_name, - COALESCE(sc.NAME, ''Unknown'') AS [schema_name], - COALESCE(so.name, ''Unknown'') AS [object_name], - COALESCE(si.name, ''Unknown'') AS [index_name], - CASE WHEN so.[type] = CAST(''V'' AS CHAR(2)) THEN 1 ELSE 0 END, - si.is_unique, - si.is_primary_key, - si.is_unique_constraint, - CASE when si.type = 3 THEN 1 ELSE 0 END AS is_XML, - CASE when si.type = 4 THEN 1 ELSE 0 END AS is_spatial, - CASE when si.type = 6 THEN 1 ELSE 0 END AS is_NC_columnstore, - CASE when si.type = 5 then 1 else 0 end as is_CX_columnstore, - CASE when si.data_space_id = 0 then 1 else 0 end as is_in_memory_oltp, - si.is_disabled, - si.is_hypothetical, - si.is_padded, - si.fill_factor,' - + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N' - CASE WHEN si.filter_definition IS NOT NULL THEN si.filter_definition - ELSE N'''' - END AS filter_definition' ELSE N''''' AS filter_definition' END - + CASE - WHEN @OptimizeForSequentialKey = 1 - THEN N', si.optimize_for_sequential_key' - ELSE N', CONVERT(BIT, NULL) AS optimize_for_sequential_key' - END - + N', - ISNULL(us.user_seeks, 0), - ISNULL(us.user_scans, 0), - ISNULL(us.user_lookups, 0), - ISNULL(us.user_updates, 0), - us.last_user_seek, - us.last_user_scan, - us.last_user_lookup, - us.last_user_update, - so.create_date, - so.modify_date - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.indexes AS si WITH (NOLOCK) - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects AS so WITH (NOLOCK) ON si.object_id = so.object_id - AND so.is_ms_shipped = 0 /*Exclude objects shipped by Microsoft*/ - AND so.type <> ''TF'' /*Exclude table valued functions*/ - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas sc ON so.schema_id = sc.schema_id - LEFT JOIN sys.dm_db_index_usage_stats AS us WITH (NOLOCK) ON si.[object_id] = us.[object_id] - AND si.index_id = us.index_id - AND us.database_id = ' + CAST(@DatabaseID AS NVARCHAR(10)) + N' - WHERE si.[type] IN ( 0, 1, 2, 3, 4, 5, 6 ) - /* Heaps, clustered, nonclustered, XML, spatial, Cluster Columnstore, NC Columnstore */ ' + - CASE WHEN @TableName IS NOT NULL THEN N' and so.name=' + QUOTENAME(@TableName,N'''') + N' ' ELSE N'' END + - CASE WHEN ( @IncludeInactiveIndexes = 0 - AND @Mode IN (0, 4) - AND @TableName IS NULL ) - THEN N'AND ( us.user_seeks + us.user_scans + us.user_lookups + us.user_updates ) > 0' - ELSE N'' - END - + N'OPTION ( RECOMPILE ); - '; - IF @dsql IS NULL - RAISERROR('@dsql is null',16,1); - - RAISERROR (N'Inserting data into #IndexSanity',0,1) WITH NOWAIT; - IF @Debug = 1 - BEGIN - PRINT SUBSTRING(@dsql, 0, 4000); - PRINT SUBSTRING(@dsql, 4000, 8000); - PRINT SUBSTRING(@dsql, 8000, 12000); - PRINT SUBSTRING(@dsql, 12000, 16000); - PRINT SUBSTRING(@dsql, 16000, 20000); - PRINT SUBSTRING(@dsql, 20000, 24000); - PRINT SUBSTRING(@dsql, 24000, 28000); - PRINT SUBSTRING(@dsql, 28000, 32000); - PRINT SUBSTRING(@dsql, 32000, 36000); - PRINT SUBSTRING(@dsql, 36000, 40000); - END; - INSERT #IndexSanity ( [database_id], [object_id], [index_id], [index_type], [database_name], [schema_name], [object_name], - index_name, is_indexed_view, is_unique, is_primary_key, is_unique_constraint, is_XML, is_spatial, is_NC_columnstore, is_CX_columnstore, is_in_memory_oltp, - is_disabled, is_hypothetical, is_padded, fill_factor, filter_definition, [optimize_for_sequential_key], user_seeks, user_scans, - user_lookups, user_updates, last_user_seek, last_user_scan, last_user_lookup, last_user_update, - create_date, modify_date ) - EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; - - - RAISERROR (N'Checking partition count',0,1) WITH NOWAIT; - IF @BringThePain = 0 AND @SkipPartitions = 0 AND @TableName IS NULL - BEGIN - /* Count the total number of partitions */ - SET @dsql = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @RowcountOUT = SUM(1) FROM ' + QUOTENAME(@DatabaseName) + '.sys.partitions WHERE partition_number > 1 OPTION ( RECOMPILE );'; - EXEC sp_executesql @dsql, N'@RowcountOUT BIGINT OUTPUT', @RowcountOUT = @Rowcount OUTPUT; - IF @Rowcount > 100 - BEGIN - RAISERROR (N'Setting @SkipPartitions = 1 because > 100 partitions were found. To check them, you must set @BringThePain = 1.',0,1) WITH NOWAIT; - SET @SkipPartitions = 1; - INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, URL, details, index_definition, - index_usage_summary, index_size_summary ) - VALUES ( 1, 0 , - 'Some Checks Were Skipped', - '@SkipPartitions Forced to 1', - 'http://FirstResponderKit.org', CAST(@Rowcount AS NVARCHAR(50)) + ' partitions found. To analyze them, use @BringThePain = 1.', 'We try to keep things quick - and warning, running @BringThePain = 1 can take tens of minutes.', '', '' - ); - END; - END; - - - - IF (@SkipPartitions = 0) - BEGIN - IF (SELECT LEFT(@SQLServerProductVersion, - CHARINDEX('.',@SQLServerProductVersion,0)-1 )) <= 2147483647 --Make change here - BEGIN - - RAISERROR (N'Preferring non-2012 syntax with LEFT JOIN to sys.dm_db_index_operational_stats',0,1) WITH NOWAIT; - - --NOTE: If you want to use the newer syntax for 2012+, you'll have to change 2147483647 to 11 on line ~819 - --This change was made because on a table with lots of paritions, the OUTER APPLY was crazy slow. - - -- get relevant columns from sys.dm_db_partition_stats, sys.partitions and sys.objects - DROP TABLE if exists #dm_db_partition_stats_etc - create table #dm_db_partition_stats_etc - ( - database_id smallint not null - , object_id int not null - , sname sysname NULL - , index_id int - , partition_number int - , partition_id bigint - , row_count bigint - , reserved_MB bigint - , reserved_LOB_MB bigint - , reserved_row_overflow_MB bigint - , lock_escalation_desc nvarchar(60) - , data_compression_desc nvarchar(60) - ) - - -- get relevant info from sys.dm_db_index_operational_stats - drop TABLE if exists #dm_db_index_operational_stats - create table #dm_db_index_operational_stats - ( - database_id smallint not null - , object_id int not null - , index_id int - , partition_number int - , hobt_id bigint - , leaf_insert_count bigint - , leaf_delete_count bigint - , leaf_update_count bigint - , range_scan_count bigint - , singleton_lookup_count bigint - , forwarded_fetch_count bigint - , lob_fetch_in_pages bigint - , lob_fetch_in_bytes bigint - , row_overflow_fetch_in_pages bigint - , row_overflow_fetch_in_bytes bigint - , row_lock_count bigint - , row_lock_wait_count bigint - , row_lock_wait_in_ms bigint - , page_lock_count bigint - , page_lock_wait_count bigint - , page_lock_wait_in_ms bigint - , index_lock_promotion_attempt_count bigint - , index_lock_promotion_count bigint - , page_latch_wait_count bigint - , page_latch_wait_in_ms bigint - , page_io_latch_wait_count bigint - , page_io_latch_wait_in_ms bigint - ) - - SET @dsql = N' - DECLARE @d VARCHAR(19) = CONVERT(VARCHAR(19), GETDATE(), 121) - RAISERROR (N''start getting data into #dm_db_partition_stats_etc at %s'',0,1, @d) WITH NOWAIT; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - INSERT INTO #dm_db_partition_stats_etc - ( - database_id, object_id, sname, index_id, partition_number, partition_id, row_count, reserved_MB, reserved_LOB_MB, reserved_row_overflow_MB, lock_escalation_desc, data_compression_desc - ) - SELECT ' + CAST(@DatabaseID AS NVARCHAR(10)) + N' AS database_id, - ps.object_id, - s.name as sname, - ps.index_id, - ps.partition_number, - ps.partition_id, - ps.row_count, - ps.reserved_page_count * 8. / 1024. AS reserved_MB, - ps.lob_reserved_page_count * 8. / 1024. AS reserved_LOB_MB, - ps.row_overflow_reserved_page_count * 8. / 1024. AS reserved_row_overflow_MB, - le.lock_escalation_desc, - ' + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N'par.data_compression_desc ' ELSE N'null as data_compression_desc ' END + N' -'; - - SET @dsql = @dsql + N' - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_partition_stats AS ps - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partitions AS par on ps.partition_id=par.partition_id - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects AS so ON ps.object_id = so.object_id - AND so.is_ms_shipped = 0 /*Exclude objects shipped by Microsoft*/ - AND so.type <> ''TF'' /*Exclude table valued functions*/ - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s ON s.schema_id = so.schema_id - OUTER APPLY (SELECT st.lock_escalation_desc - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.tables st - WHERE st.object_id = ps.object_id - AND ps.index_id < 2 ) le - WHERE 1=1 - ' + CASE WHEN @ObjectID IS NOT NULL THEN N'AND so.object_id=' + CAST(@ObjectID AS NVARCHAR(30)) + N' ' ELSE N' ' END + N' - ' + CASE WHEN @Filter = 2 THEN N'AND ps.reserved_page_count * 8./1024. > ' + CAST(@FilterMB AS NVARCHAR(5)) + N' ' ELSE N' ' END + N' - GROUP BY ps.object_id, - s.name, - ps.index_id, - ps.partition_number, - ps.partition_id, - ps.row_count, - ps.reserved_page_count, - ps.lob_reserved_page_count, - ps.row_overflow_reserved_page_count, - le.lock_escalation_desc, - ' + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N'par.data_compression_desc ' ELSE N'null as data_compression_desc ' END + N' - ORDER BY ps.object_id, ps.index_id, ps.partition_number - /*OPTION ( RECOMPILE );*/ - OPTION ( RECOMPILE , min_grant_percent = 1); - - SET @d = CONVERT(VARCHAR(19), GETDATE(), 121) - RAISERROR (N''start getting data into #dm_db_index_operational_stats at %s.'',0,1, @d) WITH NOWAIT; - - insert into #dm_db_index_operational_stats - ( - database_id - , object_id - , index_id - , partition_number - , hobt_id - , leaf_insert_count - , leaf_delete_count - , leaf_update_count - , range_scan_count - , singleton_lookup_count - , forwarded_fetch_count - , lob_fetch_in_pages - , lob_fetch_in_bytes - , row_overflow_fetch_in_pages - , row_overflow_fetch_in_bytes - , row_lock_count - , row_lock_wait_count - , row_lock_wait_in_ms - , page_lock_count - , page_lock_wait_count - , page_lock_wait_in_ms - , index_lock_promotion_attempt_count - , index_lock_promotion_count - , page_latch_wait_count - , page_latch_wait_in_ms - , page_io_latch_wait_count - , page_io_latch_wait_in_ms - ) - - select os.database_id - , os.object_id - , os.index_id - , os.partition_number - , os.hobt_id - , os.leaf_insert_count - , os.leaf_delete_count - , os.leaf_update_count - , os.range_scan_count - , os.singleton_lookup_count - , os.forwarded_fetch_count - , os.lob_fetch_in_pages - , os.lob_fetch_in_bytes - , os.row_overflow_fetch_in_pages - , os.row_overflow_fetch_in_bytes - , os.row_lock_count - , os.row_lock_wait_count - , os.row_lock_wait_in_ms - , os.page_lock_count - , os.page_lock_wait_count - , os.page_lock_wait_in_ms - , os.index_lock_promotion_attempt_count - , os.index_lock_promotion_count - , os.page_latch_wait_count - , os.page_latch_wait_in_ms - , os.page_io_latch_wait_count - , os.page_io_latch_wait_in_ms - from ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_index_operational_stats('+ CAST(@DatabaseID AS NVARCHAR(10)) +', NULL, NULL,NULL) AS os - OPTION ( RECOMPILE , min_grant_percent = 1); - - SET @d = CONVERT(VARCHAR(19), GETDATE(), 121) - RAISERROR (N''finished getting data into #dm_db_index_operational_stats at %s.'',0,1, @d) WITH NOWAIT; - '; - END; - ELSE - BEGIN - RAISERROR (N'Using 2012 syntax to query sys.dm_db_index_operational_stats',0,1) WITH NOWAIT; - --This is the syntax that will be used if you change 2147483647 to 11 on line ~819. - --If you have a lot of paritions and this suddenly starts running for a long time, change it back. - SET @dsql = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT ' + CAST(@DatabaseID AS NVARCHAR(10)) + N' AS database_id, - ps.object_id, - s.name, - ps.index_id, - ps.partition_number, - ps.row_count, - ps.reserved_page_count * 8. / 1024. AS reserved_MB, - ps.lob_reserved_page_count * 8. / 1024. AS reserved_LOB_MB, - ps.row_overflow_reserved_page_count * 8. / 1024. AS reserved_row_overflow_MB, - le.lock_escalation_desc, - ' + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N'par.data_compression_desc ' ELSE N'null as data_compression_desc' END + N', - SUM(os.leaf_insert_count), - SUM(os.leaf_delete_count), - SUM(os.leaf_update_count), - SUM(os.range_scan_count), - SUM(os.singleton_lookup_count), - SUM(os.forwarded_fetch_count), - SUM(os.lob_fetch_in_pages), - SUM(os.lob_fetch_in_bytes), - SUM(os.row_overflow_fetch_in_pages), - SUM(os.row_overflow_fetch_in_bytes), - SUM(os.row_lock_count), - SUM(os.row_lock_wait_count), - SUM(os.row_lock_wait_in_ms), - SUM(os.page_lock_count), - SUM(os.page_lock_wait_count), - SUM(os.page_lock_wait_in_ms), - SUM(os.index_lock_promotion_attempt_count), - SUM(os.index_lock_promotion_count), - SUM(os.page_latch_wait_count), - SUM(os.page_latch_wait_in_ms), - SUM(os.page_io_latch_wait_count), - SUM(os.page_io_latch_wait_in_ms)'; - - /* Get columnstore dictionary size - more info: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2585 */ - IF EXISTS (SELECT * FROM sys.all_objects WHERE name = 'column_store_dictionaries') - SET @dsql = @dsql + N' COALESCE((SELECT SUM (on_disk_size / 1024.0 / 1024) FROM ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_dictionaries dict WHERE dict.partition_id = ps.partition_id),0) AS reserved_dictionary_MB '; - ELSE - SET @dsql = @dsql + N' 0 AS reserved_dictionary_MB '; - - - SET @dsql = @dsql + N' - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_partition_stats AS ps - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partitions AS par on ps.partition_id=par.partition_id - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects AS so ON ps.object_id = so.object_id - AND so.is_ms_shipped = 0 /*Exclude objects shipped by Microsoft*/ - AND so.type <> ''TF'' /*Exclude table valued functions*/ - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s ON s.schema_id = so.schema_id - OUTER APPLY ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_index_operational_stats(' - + CAST(@DatabaseID AS NVARCHAR(10)) + N', ps.object_id, ps.index_id,ps.partition_number) AS os - OUTER APPLY (SELECT st.lock_escalation_desc - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.tables st - WHERE st.object_id = ps.object_id - AND ps.index_id < 2 ) le - WHERE 1=1 - ' + CASE WHEN @ObjectID IS NOT NULL THEN N'AND so.object_id=' + CAST(@ObjectID AS NVARCHAR(30)) + N' ' ELSE N' ' END + N' - ' + CASE WHEN @Filter = 2 THEN N'AND ps.reserved_page_count * 8./1024. > ' + CAST(@FilterMB AS NVARCHAR(5)) + N' ' ELSE N' ' END + ' - GROUP BY ps.object_id, - s.name, - ps.index_id, - ps.partition_number, - ps.partition_id, - ps.row_count, - ps.reserved_page_count, - ps.lob_reserved_page_count, - ps.row_overflow_reserved_page_count, - le.lock_escalation_desc, - ' + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N'par.data_compression_desc ' ELSE N'null as data_compression_desc ' END + N' - ORDER BY ps.object_id, ps.index_id, ps.partition_number - OPTION ( RECOMPILE ); - '; - END; - - IF @dsql IS NULL - RAISERROR('@dsql is null',16,1); - - RAISERROR (N'Inserting data into #IndexPartitionSanity',0,1) WITH NOWAIT; - IF @Debug = 1 - BEGIN - PRINT SUBSTRING(@dsql, 0, 4000); - PRINT SUBSTRING(@dsql, 4000, 8000); - PRINT SUBSTRING(@dsql, 8000, 12000); - PRINT SUBSTRING(@dsql, 12000, 16000); - PRINT SUBSTRING(@dsql, 16000, 20000); - PRINT SUBSTRING(@dsql, 20000, 24000); - PRINT SUBSTRING(@dsql, 24000, 28000); - PRINT SUBSTRING(@dsql, 28000, 32000); - PRINT SUBSTRING(@dsql, 32000, 36000); - PRINT SUBSTRING(@dsql, 36000, 40000); - END; - EXEC sp_executesql @dsql; - INSERT #IndexPartitionSanity ( [database_id], - [object_id], - [schema_name], - index_id, - partition_number, - row_count, - reserved_MB, - reserved_LOB_MB, - reserved_row_overflow_MB, - lock_escalation_desc, - data_compression_desc, - leaf_insert_count, - leaf_delete_count, - leaf_update_count, - range_scan_count, - singleton_lookup_count, - forwarded_fetch_count, - lob_fetch_in_pages, - lob_fetch_in_bytes, - row_overflow_fetch_in_pages, - row_overflow_fetch_in_bytes, - row_lock_count, - row_lock_wait_count, - row_lock_wait_in_ms, - page_lock_count, - page_lock_wait_count, - page_lock_wait_in_ms, - index_lock_promotion_attempt_count, - index_lock_promotion_count, - page_latch_wait_count, - page_latch_wait_in_ms, - page_io_latch_wait_count, - page_io_latch_wait_in_ms, - reserved_dictionary_MB) - select h.database_id, h.object_id, h.sname, h.index_id, h.partition_number, h.row_count, h.reserved_MB, h.reserved_LOB_MB, h.reserved_row_overflow_MB, h.lock_escalation_desc, h.data_compression_desc, - SUM(os.leaf_insert_count), - SUM(os.leaf_delete_count), - SUM(os.leaf_update_count), - SUM(os.range_scan_count), - SUM(os.singleton_lookup_count), - SUM(os.forwarded_fetch_count), - SUM(os.lob_fetch_in_pages), - SUM(os.lob_fetch_in_bytes), - SUM(os.row_overflow_fetch_in_pages), - SUM(os.row_overflow_fetch_in_bytes), - SUM(os.row_lock_count), - SUM(os.row_lock_wait_count), - SUM(os.row_lock_wait_in_ms), - SUM(os.page_lock_count), - SUM(os.page_lock_wait_count), - SUM(os.page_lock_wait_in_ms), - SUM(os.index_lock_promotion_attempt_count), - SUM(os.index_lock_promotion_count), - SUM(os.page_latch_wait_count), - SUM(os.page_latch_wait_in_ms), - SUM(os.page_io_latch_wait_count), - SUM(os.page_io_latch_wait_in_ms) - ,COALESCE((SELECT SUM (dict.on_disk_size / 1024.0 / 1024) FROM sys.column_store_dictionaries dict WHERE dict.partition_id = h.partition_id),0) AS reserved_dictionary_MB - from #dm_db_partition_stats_etc h - left JOIN #dm_db_index_operational_stats as os ON - h.object_id=os.object_id and h.index_id=os.index_id and h.partition_number=os.partition_number - group by h.database_id, h.object_id, h.sname, h.index_id, h.partition_number, h.partition_id, h.row_count, h.reserved_MB, h.reserved_LOB_MB, h.reserved_row_overflow_MB, h.lock_escalation_desc, h.data_compression_desc - - END; --End Check For @SkipPartitions = 0 - - - - RAISERROR (N'Inserting data into #MissingIndexes',0,1) WITH NOWAIT; - SET @dsql=N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' - - - SET @dsql = @dsql + 'WITH ColumnNamesWithDataTypes AS(SELECT id.index_handle,id.object_id,cn.IndexColumnType,STUFF((SELECT '', '' + cn_inner.ColumnName + '' '' + - N'' {'' + CASE WHEN ty.name IN ( ''varchar'', ''char'' ) THEN ty.name + ''('' + CASE WHEN co.max_length = -1 THEN ''max'' ELSE CAST(co.max_length AS VARCHAR(25)) END + '')'' - WHEN ty.name IN ( ''nvarchar'', ''nchar'' ) THEN ty.name + ''('' + CASE WHEN co.max_length = -1 THEN ''max'' ELSE CAST(co.max_length / 2 AS VARCHAR(25)) END + '')'' - WHEN ty.name IN ( ''decimal'', ''numeric'' ) THEN ty.name + ''('' + CAST(co.precision AS VARCHAR(25)) + '', '' + CAST(co.scale AS VARCHAR(25)) + '')'' - WHEN ty.name IN ( ''datetime2'' ) THEN ty.name + ''('' + CAST(co.scale AS VARCHAR(25)) + '')'' - ELSE ty.name END + ''}'' - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_details AS id_inner - CROSS APPLY( - SELECT LTRIM(RTRIM(v.value(''(./text())[1]'', ''varchar(max)''))) AS ColumnName, ''Equality'' AS IndexColumnType - FROM (VALUES (CONVERT(XML, N'''' + REPLACE((SELECT CAST(id_inner.equality_columns AS nvarchar(max)) FOR XML PATH('''')), N'','', N'''') + N''''))) x(n) - CROSS APPLY n.nodes(''x'') node(v) - UNION ALL - SELECT LTRIM(RTRIM(v.value(N''(./text())[1]'', ''varchar(max)''))) AS ColumnName, ''Inequality'' AS IndexColumnType - FROM (VALUES (CONVERT(XML, N'''' + REPLACE((SELECT CAST(id_inner.inequality_columns AS nvarchar(max)) FOR XML PATH('''')), N'','', N'''') + N''''))) x(n) - CROSS APPLY n.nodes(''x'') node(v) - UNION ALL - SELECT LTRIM(RTRIM(v.value(''(./text())[1]'', ''varchar(max)''))) AS ColumnName, ''Included'' AS IndexColumnType - FROM (VALUES (CONVERT(XML, N'''' + REPLACE((SELECT CAST(id_inner.included_columns AS nvarchar(max)) FOR XML PATH('''')), N'','', N'''') + N''''))) x(n) - CROSS APPLY n.nodes(''x'') node(v) - )AS cn_inner' - + /*split the string otherwise dsql cuts some of it out*/ - ' JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns AS co ON co.object_id = id_inner.object_id AND ''['' + co.name + '']'' = cn_inner.ColumnName - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.types AS ty ON ty.user_type_id = co.user_type_id - WHERE id_inner.index_handle = id.index_handle - AND id_inner.object_id = id.object_id - AND cn_inner.IndexColumnType = cn.IndexColumnType - FOR XML PATH('''') - ),1,1,'''') AS ReplaceColumnNames - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_details AS id - CROSS APPLY( - SELECT LTRIM(RTRIM(v.value(''(./text())[1]'', ''varchar(max)''))) AS ColumnName, ''Equality'' AS IndexColumnType - FROM (VALUES (CONVERT(XML, N'''' + REPLACE((SELECT CAST(id.equality_columns AS nvarchar(max)) FOR XML PATH('''')), N'','', N'''') + N''''))) x(n) - CROSS APPLY n.nodes(''x'') node(v) - UNION ALL - SELECT LTRIM(RTRIM(v.value(''(./text())[1]'', ''varchar(max)''))) AS ColumnName, ''Inequality'' AS IndexColumnType - FROM (VALUES (CONVERT(XML, N'''' + REPLACE((SELECT CAST(id.inequality_columns AS nvarchar(max)) FOR XML PATH('''')), N'','', N'''') + N''''))) x(n) - CROSS APPLY n.nodes(''x'') node(v) - UNION ALL - SELECT LTRIM(RTRIM(v.value(''(./text())[1]'', ''varchar(max)''))) AS ColumnName, ''Included'' AS IndexColumnType - FROM (VALUES (CONVERT(XML, N'''' + REPLACE((SELECT CAST(id.included_columns AS nvarchar(max)) FOR XML PATH('''')), N'','', N'''') + N''''))) x(n) - CROSS APPLY n.nodes(''x'') node(v) - )AS cn - GROUP BY id.index_handle,id.object_id,cn.IndexColumnType - ) - SELECT id.database_id, id.object_id, @i_DatabaseName, sc.[name], so.[name], id.statement , gs.avg_total_user_cost, - gs.avg_user_impact, gs.user_seeks, gs.user_scans, gs.unique_compiles, id.equality_columns, id.inequality_columns, id.included_columns, - ( - SELECT ColumnNamesWithDataTypes.ReplaceColumnNames - FROM ColumnNamesWithDataTypes WHERE ColumnNamesWithDataTypes.index_handle = id.index_handle - AND ColumnNamesWithDataTypes.object_id = id.object_id - AND ColumnNamesWithDataTypes.IndexColumnType = ''Equality'' - ) AS equality_columns_with_data_type - ,( - SELECT ColumnNamesWithDataTypes.ReplaceColumnNames - FROM ColumnNamesWithDataTypes WHERE ColumnNamesWithDataTypes.index_handle = id.index_handle - AND ColumnNamesWithDataTypes.object_id = id.object_id - AND ColumnNamesWithDataTypes.IndexColumnType = ''Inequality'' - ) AS inequality_columns_with_data_type - ,( - SELECT ColumnNamesWithDataTypes.ReplaceColumnNames - FROM ColumnNamesWithDataTypes WHERE ColumnNamesWithDataTypes.index_handle = id.index_handle - AND ColumnNamesWithDataTypes.object_id = id.object_id - AND ColumnNamesWithDataTypes.IndexColumnType = ''Included'' - ) AS included_columns_with_data_type ' - - /* Get the sample query plan if it's available, and if there are less than 1,000 rows in the DMV: */ - IF NOT EXISTS - ( - SELECT - 1/0 - FROM sys.all_objects AS o - WHERE o.name = 'dm_db_missing_index_group_stats_query' - ) - SELECT - @dsql += N' , NULL AS sample_query_plan ' - ELSE - BEGIN - /* The DMV is only supposed to have 600 rows in it. If it's got more, - they could see performance slowdowns - see Github #3085. */ - DECLARE @MissingIndexPlans BIGINT; - SET @StringToExecute = N'SELECT @MissingIndexPlans = COUNT(*) FROM ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_group_stats_query;' - EXEC sp_executesql @StringToExecute, N'@MissingIndexPlans BIGINT OUT', @MissingIndexPlans OUT; - - IF @MissingIndexPlans > 1000 - BEGIN - SELECT @dsql += N' , NULL AS sample_query_plan /* Over 1000 plans found, skipping */ '; - RAISERROR (N'Over 1000 plans found in sys.dm_db_missing_index_group_stats_query - your SQL Server is hitting a bug: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/3085',0,1) WITH NOWAIT; - END - ELSE - SELECT - @dsql += N' - , sample_query_plan = - ( - SELECT TOP (1) - p.query_plan - FROM sys.dm_db_missing_index_group_stats gs - CROSS APPLY - ( - SELECT TOP (1) - s.plan_handle - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_group_stats_query q - INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.dm_exec_query_stats s - ON q.query_plan_hash = s.query_plan_hash - WHERE gs.group_handle = q.group_handle - ORDER BY (q.user_seeks + q.user_scans) DESC, s.total_logical_reads DESC - ) q2 - CROSS APPLY sys.dm_exec_query_plan(q2.plan_handle) p - WHERE ig.index_group_handle = gs.group_handle - ) ' - END - - - - SET @dsql = @dsql + N'FROM ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_groups ig - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_details id ON ig.index_handle = id.index_handle - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_group_stats gs ON ig.index_group_handle = gs.group_handle - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects so on - id.object_id=so.object_id - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas sc on - so.schema_id=sc.schema_id - WHERE id.database_id = ' + CAST(@DatabaseID AS NVARCHAR(30)) + ' - ' + CASE WHEN @ObjectID IS NULL THEN N'' - ELSE N'and id.object_id=' + CAST(@ObjectID AS NVARCHAR(30)) - END + - N'OPTION (RECOMPILE);'; - - IF @dsql IS NULL - RAISERROR('@dsql is null',16,1); - IF @Debug = 1 - BEGIN - PRINT SUBSTRING(@dsql, 0, 4000); - PRINT SUBSTRING(@dsql, 4000, 8000); - PRINT SUBSTRING(@dsql, 8000, 12000); - PRINT SUBSTRING(@dsql, 12000, 16000); - PRINT SUBSTRING(@dsql, 16000, 20000); - PRINT SUBSTRING(@dsql, 20000, 24000); - PRINT SUBSTRING(@dsql, 24000, 28000); - PRINT SUBSTRING(@dsql, 28000, 32000); - PRINT SUBSTRING(@dsql, 32000, 36000); - PRINT SUBSTRING(@dsql, 36000, 40000); - END; - INSERT #MissingIndexes ( [database_id], [object_id], [database_name], [schema_name], [table_name], [statement], avg_total_user_cost, - avg_user_impact, user_seeks, user_scans, unique_compiles, equality_columns, - inequality_columns, included_columns, equality_columns_with_data_type, inequality_columns_with_data_type, - included_columns_with_data_type, sample_query_plan) - EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; - - SET @dsql = N' - SELECT DB_ID(N' + QUOTENAME(@DatabaseName,'''') + N') AS [database_id], - @i_DatabaseName AS database_name, - s.name, - fk_object.name AS foreign_key_name, - parent_object.[object_id] AS parent_object_id, - parent_object.name AS parent_object_name, - referenced_object.[object_id] AS referenced_object_id, - referenced_object.name AS referenced_object_name, - fk.is_disabled, - fk.is_not_trusted, - fk.is_not_for_replication, - parent.fk_columns, - referenced.fk_columns, - [update_referential_action_desc], - [delete_referential_action_desc] - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.foreign_keys fk - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects fk_object ON fk.object_id=fk_object.object_id - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects parent_object ON fk.parent_object_id=parent_object.object_id - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects referenced_object ON fk.referenced_object_id=referenced_object.object_id - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s ON fk.schema_id=s.schema_id - CROSS APPLY ( SELECT STUFF( (SELECT N'', '' + c_parent.name AS fk_columns - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.foreign_key_columns fkc - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns c_parent ON fkc.parent_object_id=c_parent.[object_id] - AND fkc.parent_column_id=c_parent.column_id - WHERE fk.parent_object_id=fkc.parent_object_id - AND fk.[object_id]=fkc.constraint_object_id - ORDER BY fkc.constraint_column_id - FOR XML PATH('''') , - TYPE).value(''.'', ''nvarchar(max)''), 1, 1, '''')/*This is how we remove the first comma*/ ) parent ( fk_columns ) - CROSS APPLY ( SELECT STUFF( (SELECT N'', '' + c_referenced.name AS fk_columns - FROM ' + QUOTENAME(@DatabaseName) + N'.sys. foreign_key_columns fkc - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns c_referenced ON fkc.referenced_object_id=c_referenced.[object_id] - AND fkc.referenced_column_id=c_referenced.column_id - WHERE fk.referenced_object_id=fkc.referenced_object_id - and fk.[object_id]=fkc.constraint_object_id - ORDER BY fkc.constraint_column_id /*order by col name, we don''t have anything better*/ - FOR XML PATH('''') , - TYPE).value(''.'', ''nvarchar(max)''), 1, 1, '''') ) referenced ( fk_columns ) - ' + CASE WHEN @ObjectID IS NOT NULL THEN - 'WHERE fk.parent_object_id=' + CAST(@ObjectID AS NVARCHAR(30)) + N' OR fk.referenced_object_id=' + CAST(@ObjectID AS NVARCHAR(30)) + N' ' - ELSE N' ' END + ' - ORDER BY parent_object_name, foreign_key_name - OPTION (RECOMPILE);'; - IF @dsql IS NULL - RAISERROR('@dsql is null',16,1); - - RAISERROR (N'Inserting data into #ForeignKeys',0,1) WITH NOWAIT; - IF @Debug = 1 - BEGIN - PRINT SUBSTRING(@dsql, 0, 4000); - PRINT SUBSTRING(@dsql, 4000, 8000); - PRINT SUBSTRING(@dsql, 8000, 12000); - PRINT SUBSTRING(@dsql, 12000, 16000); - PRINT SUBSTRING(@dsql, 16000, 20000); - PRINT SUBSTRING(@dsql, 20000, 24000); - PRINT SUBSTRING(@dsql, 24000, 28000); - PRINT SUBSTRING(@dsql, 28000, 32000); - PRINT SUBSTRING(@dsql, 32000, 36000); - PRINT SUBSTRING(@dsql, 36000, 40000); - END; - INSERT #ForeignKeys ( [database_id], [database_name], [schema_name], foreign_key_name, parent_object_id,parent_object_name, referenced_object_id, referenced_object_name, - is_disabled, is_not_trusted, is_not_for_replication, parent_fk_columns, referenced_fk_columns, - [update_referential_action_desc], [delete_referential_action_desc] ) - EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; - - - SET @dsql = N' - SELECT - DB_ID(N' + QUOTENAME(@DatabaseName,'''') + N') AS [database_id], - @i_DatabaseName AS database_name, - foreign_key_schema = - s.name, - foreign_key_name = - fk.name, - foreign_key_table = - OBJECT_NAME(fk.parent_object_id, DB_ID(N' + QUOTENAME(@DatabaseName,'''') + N')), - fk.parent_object_id, - foreign_key_referenced_table = - OBJECT_NAME(fk.referenced_object_id, DB_ID(N' + QUOTENAME(@DatabaseName,'''') + N')), - fk.referenced_object_id - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.foreign_keys fk - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s - ON s.schema_id = fk.schema_id - WHERE fk.is_disabled = 0 - AND EXISTS - ( - SELECT - 1/0 - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.foreign_key_columns fkc - WHERE fkc.constraint_object_id = fk.object_id - AND NOT EXISTS - ( - SELECT - 1/0 - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.index_columns ic - WHERE ic.object_id = fkc.parent_object_id - AND ic.column_id = fkc.parent_column_id - AND ic.index_column_id = fkc.constraint_column_id - ) - ) - OPTION (RECOMPILE);' - IF @dsql IS NULL - RAISERROR('@dsql is null',16,1); - - RAISERROR (N'Inserting data into #ForeignKeys',0,1) WITH NOWAIT; - IF @Debug = 1 - BEGIN - PRINT SUBSTRING(@dsql, 0, 4000); - PRINT SUBSTRING(@dsql, 4000, 8000); - PRINT SUBSTRING(@dsql, 8000, 12000); - PRINT SUBSTRING(@dsql, 12000, 16000); - PRINT SUBSTRING(@dsql, 16000, 20000); - PRINT SUBSTRING(@dsql, 20000, 24000); - PRINT SUBSTRING(@dsql, 24000, 28000); - PRINT SUBSTRING(@dsql, 28000, 32000); - PRINT SUBSTRING(@dsql, 32000, 36000); - PRINT SUBSTRING(@dsql, 36000, 40000); - END; - - INSERT - #UnindexedForeignKeys - ( - database_id, - database_name, - schema_name, - foreign_key_name, - parent_object_name, - parent_object_id, - referenced_object_name, - referenced_object_id - ) - EXEC sys.sp_executesql - @dsql, - N'@i_DatabaseName sysname', - @DatabaseName; - - - IF @SkipStatistics = 0 /* AND DB_NAME() = @DatabaseName /* Can only get stats in the current database - see https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/1947 */ */ - BEGIN - IF ((PARSENAME(@SQLServerProductVersion, 4) >= 12) - OR (PARSENAME(@SQLServerProductVersion, 4) = 11 AND PARSENAME(@SQLServerProductVersion, 2) >= 3000) - OR (PARSENAME(@SQLServerProductVersion, 4) = 10 AND PARSENAME(@SQLServerProductVersion, 3) = 50 AND PARSENAME(@SQLServerProductVersion, 2) >= 2500)) - 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, - 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) - 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, - ISNULL(i.name, ''System Or User Statistic'') AS index_name, - ca.column_names AS column_names, - s.name AS statistics_name, - CONVERT(DATETIME, ddsp.last_updated) AS last_statistics_update, - DATEDIFF(DAY, ddsp.last_updated, GETDATE()) AS days_since_last_stats_update, - ddsp.rows, - ddsp.rows_sampled, - CAST(ddsp.rows_sampled / ( 1. * NULLIF(ddsp.rows, 0) ) * 100 AS DECIMAL(18, 1)) AS percent_sampled, - ddsp.steps AS histogram_steps, - ddsp.modification_counter, - CASE WHEN ddsp.modification_counter > 0 - THEN CAST(ddsp.modification_counter / ( 1. * NULLIF(ddsp.rows, 0) ) * 100 AS DECIMAL(18, 1)) - ELSE ddsp.modification_counter - END AS percent_modifications, - CASE WHEN ddsp.rows < 500 THEN 500 - ELSE CAST(( ddsp.rows * .20 ) + 500 AS INT) - 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 - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.stats AS s - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects obj - ON s.object_id = obj.object_id - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas sch - ON sch.schema_id = obj.schema_id - LEFT JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.indexes AS i - ON i.object_id = s.object_id - AND i.index_id = s.stats_id - OUTER APPLY ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_stats_properties(s.object_id, s.stats_id) AS ddsp - CROSS APPLY ( SELECT STUFF((SELECT '', '' + c.name - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.stats_columns AS sc - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns AS c - ON sc.column_id = c.column_id AND sc.object_id = c.object_id - WHERE sc.stats_id = s.stats_id AND sc.object_id = s.object_id - ORDER BY sc.stats_column_id - FOR XML PATH(''''), TYPE).value(''.'', ''nvarchar(max)''), 1, 2, '''') - ) ca (column_names) - WHERE obj.is_ms_shipped = 0 - OPTION (RECOMPILE);'; - - IF @dsql IS NULL - RAISERROR('@dsql is null',16,1); - - RAISERROR (N'Inserting data into #Statistics',0,1) WITH NOWAIT; - IF @Debug = 1 - BEGIN - PRINT SUBSTRING(@dsql, 0, 4000); - PRINT SUBSTRING(@dsql, 4000, 8000); - PRINT SUBSTRING(@dsql, 8000, 12000); - PRINT SUBSTRING(@dsql, 12000, 16000); - PRINT SUBSTRING(@dsql, 16000, 20000); - PRINT SUBSTRING(@dsql, 20000, 24000); - PRINT SUBSTRING(@dsql, 24000, 28000); - PRINT SUBSTRING(@dsql, 28000, 32000); - PRINT SUBSTRING(@dsql, 32000, 36000); - PRINT SUBSTRING(@dsql, 36000, 40000); - END; - - EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; - END; - ELSE - 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, - 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) - 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, - ISNULL(i.name, ''System Or User Statistic'') AS index_name, - ca.column_names AS column_names, - s.name AS statistics_name, - CONVERT(DATETIME, STATS_DATE(s.object_id, s.stats_id)) AS last_statistics_update, - DATEDIFF(DAY, STATS_DATE(s.object_id, s.stats_id), GETDATE()) AS days_since_last_stats_update, - si.rowcnt, - si.rowmodctr, - CASE WHEN si.rowmodctr > 0 THEN CAST(si.rowmodctr / ( 1. * NULLIF(si.rowcnt, 0) ) * 100 AS DECIMAL(18, 1)) - ELSE si.rowmodctr - END AS percent_modifications, - CASE WHEN si.rowcnt < 500 THEN 500 - ELSE CAST(( si.rowcnt * .20 ) + 500 AS INT) - 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, - ' - + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' - THEN N's.has_filter, - s.filter_definition' - ELSE N'NULL AS has_filter, - NULL AS filter_definition' END - + N' - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.stats AS s - INNER HASH JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.sysindexes si - ON si.name = s.name AND s.object_id = si.id - INNER HASH JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects obj - ON s.object_id = obj.object_id - INNER HASH JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas sch - ON sch.schema_id = obj.schema_id - LEFT HASH JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.indexes AS i - ON i.object_id = s.object_id - AND i.index_id = s.stats_id - CROSS APPLY ( SELECT STUFF((SELECT '', '' + c.name - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.stats_columns AS sc - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns AS c - ON sc.column_id = c.column_id AND sc.object_id = c.object_id - WHERE sc.stats_id = s.stats_id AND sc.object_id = s.object_id - ORDER BY sc.stats_column_id - FOR XML PATH(''''), TYPE).value(''.'', ''nvarchar(max)''), 1, 2, '''') - ) ca (column_names) - WHERE obj.is_ms_shipped = 0 - AND si.rowcnt > 0 - OPTION (RECOMPILE);'; - - IF @dsql IS NULL - RAISERROR('@dsql is null',16,1); - - RAISERROR (N'Inserting data into #Statistics',0,1) WITH NOWAIT; - IF @Debug = 1 - BEGIN - PRINT SUBSTRING(@dsql, 0, 4000); - PRINT SUBSTRING(@dsql, 4000, 8000); - PRINT SUBSTRING(@dsql, 8000, 12000); - PRINT SUBSTRING(@dsql, 12000, 16000); - PRINT SUBSTRING(@dsql, 16000, 20000); - PRINT SUBSTRING(@dsql, 20000, 24000); - PRINT SUBSTRING(@dsql, 24000, 28000); - PRINT SUBSTRING(@dsql, 28000, 32000); - PRINT SUBSTRING(@dsql, 32000, 36000); - PRINT SUBSTRING(@dsql, 36000, 40000); - END; - - EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; - END; - - END; - - IF (PARSENAME(@SQLServerProductVersion, 4) >= 10) - BEGIN - RAISERROR (N'Gathering Computed Column Info.',0,1) WITH NOWAIT; - SET @dsql=N'SELECT DB_ID(@i_DatabaseName) AS [database_id], - @i_DatabaseName AS database_name, - t.name AS table_name, - s.name AS schema_name, - c.name AS column_name, - cc.is_nullable, - cc.definition, - cc.uses_database_collation, - cc.is_persisted, - cc.is_computed, - CASE WHEN cc.definition LIKE ''%|].|[%'' ESCAPE ''|'' THEN 1 ELSE 0 END AS is_function, - ''ALTER TABLE '' + QUOTENAME(s.name) + ''.'' + QUOTENAME(t.name) + - '' ADD '' + QUOTENAME(c.name) + '' AS '' + cc.definition + - CASE WHEN is_persisted = 1 THEN '' PERSISTED'' ELSE '''' END + '';'' COLLATE DATABASE_DEFAULT AS [column_definition] - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.computed_columns AS cc - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns AS c - ON cc.object_id = c.object_id - AND cc.column_id = c.column_id - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.tables AS t - ON t.object_id = cc.object_id - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s - ON s.schema_id = t.schema_id - OPTION (RECOMPILE);'; - - IF @dsql IS NULL RAISERROR('@dsql is null',16,1); - - INSERT #ComputedColumns - ( database_id, [database_name], table_name, schema_name, column_name, is_nullable, definition, - uses_database_collation, is_persisted, is_computed, is_function, column_definition ) - EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; - - END; - - RAISERROR (N'Gathering Trace Flag Information',0,1) WITH NOWAIT; - INSERT #TraceStatus - EXEC ('DBCC TRACESTATUS(-1) WITH NO_INFOMSGS'); - - IF (PARSENAME(@SQLServerProductVersion, 4) >= 13) - BEGIN - RAISERROR (N'Gathering Temporal Table Info',0,1) WITH NOWAIT; - SET @dsql=N'SELECT ' + QUOTENAME(@DatabaseName,'''') + N' AS database_name, - DB_ID(N' + QUOTENAME(@DatabaseName,'''') + N') AS [database_id], - s.name AS schema_name, - t.name AS table_name, - oa.hsn as history_schema_name, - oa.htn AS history_table_name, - c1.name AS start_column_name, - c2.name AS end_column_name, - p.name AS period_name - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.periods AS p - INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.tables AS t - ON p.object_id = t.object_id - INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns AS c1 - ON t.object_id = c1.object_id - AND p.start_column_id = c1.column_id - INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns AS c2 - ON t.object_id = c2.object_id - AND p.end_column_id = c2.column_id - INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s - ON t.schema_id = s.schema_id - CROSS APPLY ( SELECT s2.name as hsn, t2.name htn - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.tables AS t2 - INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s2 - ON t2.schema_id = s2.schema_id - WHERE t2.object_id = t.history_table_id - AND t2.temporal_type = 1 /*History table*/ ) AS oa - WHERE t.temporal_type IN ( 2, 4 ) /*BOL currently points to these types, but has no definition for 4*/ - OPTION (RECOMPILE); - '; - - IF @dsql IS NULL - RAISERROR('@dsql is null',16,1); - - INSERT #TemporalTables ( database_name, database_id, schema_name, table_name, history_schema_name, - history_table_name, start_column_name, end_column_name, period_name ) - - EXEC sp_executesql @dsql; - - SET @dsql=N'SELECT DB_ID(@i_DatabaseName) AS [database_id], - @i_DatabaseName AS database_name, - t.name AS table_name, - s.name AS schema_name, - cc.name AS constraint_name, - cc.is_disabled, - cc.definition, - cc.uses_database_collation, - cc.is_not_trusted, - CASE WHEN cc.definition LIKE ''%|].|[%'' ESCAPE ''|'' THEN 1 ELSE 0 END AS is_function, - ''ALTER TABLE '' + QUOTENAME(s.name) + ''.'' + QUOTENAME(t.name) + - '' ADD CONSTRAINT '' + QUOTENAME(cc.name) + '' CHECK '' + cc.definition + '';'' COLLATE DATABASE_DEFAULT AS [column_definition] - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.check_constraints AS cc - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.tables AS t - ON t.object_id = cc.parent_object_id - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s - ON s.schema_id = t.schema_id - OPTION (RECOMPILE);'; - - INSERT #CheckConstraints - ( database_id, [database_name], table_name, schema_name, constraint_name, is_disabled, definition, - uses_database_collation, is_not_trusted, is_function, column_definition ) - EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; - - - SET @dsql=N'SELECT DB_ID(@i_DatabaseName) AS [database_id], - @i_DatabaseName AS database_name, - s.name AS missing_schema_name, - t.name AS missing_table_name, - i.name AS missing_index_name, - c.name AS missing_column_name - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.sql_expression_dependencies AS sed - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.tables AS t - ON t.object_id = sed.referenced_id - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s - ON t.schema_id = s.schema_id - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.indexes AS i - ON i.object_id = sed.referenced_id - AND i.index_id = sed.referencing_minor_id - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns AS c - ON c.object_id = sed.referenced_id - AND c.column_id = sed.referenced_minor_id - WHERE sed.referencing_class = 7 - AND sed.referenced_class = 1 - AND i.has_filter = 1 - AND NOT EXISTS ( SELECT 1/0 - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.index_columns AS ic - WHERE ic.index_id = sed.referencing_minor_id - AND ic.column_id = sed.referenced_minor_id - AND ic.object_id = sed.referenced_id ) - OPTION(RECOMPILE);' - - INSERT #FilteredIndexes ( database_id, database_name, schema_name, table_name, index_name, column_name ) - EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; - - - END; - -END; -END TRY -BEGIN CATCH - RAISERROR (N'Failure populating temp tables.', 0,1) WITH NOWAIT; - - IF @dsql IS NOT NULL - BEGIN - SET @msg= 'Last @dsql: ' + @dsql; - RAISERROR(@msg, 0, 1) WITH NOWAIT; - END; - - SELECT @msg = @DatabaseName + N' database failed to process. ' + ERROR_MESSAGE(), @ErrorSeverity = ERROR_SEVERITY(), @ErrorState = ERROR_STATE(); - RAISERROR (@msg,@ErrorSeverity, @ErrorState )WITH NOWAIT; - - - WHILE @@trancount > 0 - ROLLBACK; - - RETURN; -END CATCH; - FETCH NEXT FROM c1 INTO @DatabaseName; -END; -DEALLOCATE c1; - - - - - - ----------------------------------------- ---STEP 2: PREP THE TEMP TABLES ---EVERY QUERY AFTER THIS GOES AGAINST TEMP TABLES ONLY. ----------------------------------------- - -RAISERROR (N'Updating #IndexSanity.key_column_names',0,1) WITH NOWAIT; -UPDATE #IndexSanity -SET key_column_names = D1.key_column_names -FROM #IndexSanity si - CROSS APPLY ( SELECT RTRIM(STUFF( (SELECT N', ' + c.column_name - + N' {' + system_type_name + - CASE max_length WHEN -1 THEN N' (max)' ELSE - CASE - WHEN system_type_name IN (N'char',N'varchar',N'binary',N'varbinary') THEN N' (' + CAST(max_length AS NVARCHAR(20)) + N')' - WHEN system_type_name IN (N'nchar',N'nvarchar') THEN N' (' + CAST(max_length/2 AS NVARCHAR(20)) + N')' - ELSE N' ' + CAST(max_length AS NVARCHAR(50)) - END - END - + N'}' - AS col_definition - FROM #IndexColumns c - WHERE c.database_id= si.database_id - AND c.schema_name = si.schema_name - AND c.object_id = si.object_id - AND c.index_id = si.index_id - AND c.is_included_column = 0 /*Just Keys*/ - AND c.key_ordinal > 0 /*Ignore non-key columns, such as partitioning keys*/ - ORDER BY c.object_id, c.index_id, c.key_ordinal - FOR XML PATH('') ,TYPE).value('.', 'nvarchar(max)'), 1, 1, '')) - ) D1 ( key_column_names ); - -RAISERROR (N'Updating #IndexSanity.partition_key_column_name',0,1) WITH NOWAIT; -UPDATE #IndexSanity -SET partition_key_column_name = D1.partition_key_column_name -FROM #IndexSanity si - CROSS APPLY ( SELECT RTRIM(STUFF( (SELECT N', ' + c.column_name AS col_definition - FROM #IndexColumns c - WHERE c.database_id= si.database_id - AND c.schema_name = si.schema_name - AND c.object_id = si.object_id - AND c.index_id = si.index_id - AND c.partition_ordinal <> 0 /*Just Partitioned Keys*/ - ORDER BY c.object_id, c.index_id, c.key_ordinal - FOR XML PATH('') , TYPE).value('.', 'nvarchar(max)'), 1, 1,''))) D1 - ( partition_key_column_name ); - -RAISERROR (N'Updating #IndexSanity.key_column_names_with_sort_order',0,1) WITH NOWAIT; -UPDATE #IndexSanity -SET key_column_names_with_sort_order = D2.key_column_names_with_sort_order -FROM #IndexSanity si - CROSS APPLY ( SELECT RTRIM(STUFF( (SELECT N', ' + c.column_name + CASE c.is_descending_key - WHEN 1 THEN N' DESC' - ELSE N'' - END - + N' {' + system_type_name + - CASE max_length WHEN -1 THEN N' (max)' ELSE - CASE - WHEN system_type_name IN (N'char',N'varchar',N'binary',N'varbinary') THEN N' (' + CAST(max_length AS NVARCHAR(20)) + N')' - WHEN system_type_name IN (N'nchar',N'nvarchar') THEN N' (' + CAST(max_length/2 AS NVARCHAR(20)) + N')' - ELSE N' ' + CAST(max_length AS NVARCHAR(50)) - END - END - + N'}' - AS col_definition - FROM #IndexColumns c - WHERE c.database_id= si.database_id - AND c.schema_name = si.schema_name - AND c.object_id = si.object_id - AND c.index_id = si.index_id - AND c.is_included_column = 0 /*Just Keys*/ - AND c.key_ordinal > 0 /*Ignore non-key columns, such as partitioning keys*/ - ORDER BY c.object_id, c.index_id, c.key_ordinal - FOR XML PATH('') , TYPE).value('.', 'nvarchar(max)'), 1, 1, '')) - ) D2 ( key_column_names_with_sort_order ); - -RAISERROR (N'Updating #IndexSanity.key_column_names_with_sort_order_no_types (for create tsql)',0,1) WITH NOWAIT; -UPDATE #IndexSanity -SET key_column_names_with_sort_order_no_types = D2.key_column_names_with_sort_order_no_types -FROM #IndexSanity si - CROSS APPLY ( SELECT RTRIM(STUFF( (SELECT N', ' + QUOTENAME(c.column_name) + CASE c.is_descending_key - WHEN 1 THEN N' DESC' - ELSE N'' - END AS col_definition - FROM #IndexColumns c - WHERE c.database_id= si.database_id - AND c.schema_name = si.schema_name - AND c.object_id = si.object_id - AND c.index_id = si.index_id - AND c.is_included_column = 0 /*Just Keys*/ - AND c.key_ordinal > 0 /*Ignore non-key columns, such as partitioning keys*/ - ORDER BY c.object_id, c.index_id, c.key_ordinal - FOR XML PATH('') , TYPE).value('.', 'nvarchar(max)'), 1, 1, '')) - ) D2 ( key_column_names_with_sort_order_no_types ); - -RAISERROR (N'Updating #IndexSanity.include_column_names',0,1) WITH NOWAIT; -UPDATE #IndexSanity -SET include_column_names = D3.include_column_names -FROM #IndexSanity si - CROSS APPLY ( SELECT RTRIM(STUFF( (SELECT N', ' + c.column_name - + N' {' + system_type_name + - CASE max_length WHEN -1 THEN N' (max)' ELSE - CASE - WHEN system_type_name IN (N'char',N'varchar',N'binary',N'varbinary') THEN N' (' + CAST(max_length AS NVARCHAR(20)) + N')' - WHEN system_type_name IN (N'nchar',N'nvarchar') THEN N' (' + CAST(max_length/2 AS NVARCHAR(20)) + N')' - ELSE N' ' + CAST(max_length AS NVARCHAR(50)) - END - END - + N'}' - FROM #IndexColumns c - WHERE c.database_id= si.database_id - AND c.schema_name = si.schema_name - AND c.object_id = si.object_id - AND c.index_id = si.index_id - AND c.is_included_column = 1 /*Just includes*/ - ORDER BY c.column_name /*Order doesn't matter in includes, - this is here to make rows easy to compare.*/ - FOR XML PATH('') , TYPE).value('.', 'nvarchar(max)'), 1, 1, '')) - ) D3 ( include_column_names ); - -RAISERROR (N'Updating #IndexSanity.include_column_names_no_types (for create tsql)',0,1) WITH NOWAIT; -UPDATE #IndexSanity -SET include_column_names_no_types = D3.include_column_names_no_types -FROM #IndexSanity si - CROSS APPLY ( SELECT RTRIM(STUFF( (SELECT N', ' + QUOTENAME(c.column_name) - FROM #IndexColumns c - WHERE c.database_id= si.database_id - AND c.schema_name = si.schema_name - AND c.object_id = si.object_id - AND c.index_id = si.index_id - AND c.is_included_column = 1 /*Just includes*/ - ORDER BY c.column_name /*Order doesn't matter in includes, - this is here to make rows easy to compare.*/ - FOR XML PATH('') , TYPE).value('.', 'nvarchar(max)'), 1, 1, '')) - ) D3 ( include_column_names_no_types ); - -RAISERROR (N'Updating #IndexSanity.count_key_columns and count_include_columns',0,1) WITH NOWAIT; -UPDATE #IndexSanity -SET count_included_columns = D4.count_included_columns, - count_key_columns = D4.count_key_columns -FROM #IndexSanity si - CROSS APPLY ( SELECT SUM(CASE WHEN is_included_column = 'true' THEN 1 - ELSE 0 - END) AS count_included_columns, - SUM(CASE WHEN is_included_column = 'false' AND c.key_ordinal > 0 THEN 1 - ELSE 0 - END) AS count_key_columns - FROM #IndexColumns c - WHERE c.database_id= si.database_id - AND c.schema_name = si.schema_name - AND c.object_id = si.object_id - AND c.index_id = si.index_id - ) AS D4 ( count_included_columns, count_key_columns ); - -RAISERROR (N'Updating index_sanity_id on #IndexPartitionSanity',0,1) WITH NOWAIT; -UPDATE #IndexPartitionSanity -SET index_sanity_id = i.index_sanity_id -FROM #IndexPartitionSanity ps - JOIN #IndexSanity i ON ps.[object_id] = i.[object_id] - AND ps.index_id = i.index_id - AND i.database_id = ps.database_id - AND i.schema_name = ps.schema_name; - - -RAISERROR (N'Inserting data into #IndexSanitySize',0,1) WITH NOWAIT; -INSERT #IndexSanitySize ( [index_sanity_id], [database_id], [schema_name], [lock_escalation_desc], partition_count, total_rows, total_reserved_MB, - total_reserved_LOB_MB, total_reserved_row_overflow_MB, total_reserved_dictionary_MB, total_range_scan_count, - total_singleton_lookup_count, total_leaf_delete_count, total_leaf_update_count, - total_forwarded_fetch_count,total_row_lock_count, - total_row_lock_wait_count, total_row_lock_wait_in_ms, avg_row_lock_wait_in_ms, - total_page_lock_count, total_page_lock_wait_count, total_page_lock_wait_in_ms, - avg_page_lock_wait_in_ms, total_index_lock_promotion_attempt_count, - total_index_lock_promotion_count, data_compression_desc, - page_latch_wait_count, page_latch_wait_in_ms, page_io_latch_wait_count, page_io_latch_wait_in_ms) - SELECT index_sanity_id, ipp.database_id, ipp.schema_name, ipp.lock_escalation_desc, - COUNT(*), SUM(row_count), SUM(reserved_MB), - SUM(reserved_LOB_MB) - SUM(reserved_dictionary_MB), /* Subtract columnstore dictionaries from LOB data */ - SUM(reserved_row_overflow_MB), - SUM(reserved_dictionary_MB), - SUM(range_scan_count), - SUM(singleton_lookup_count), - SUM(leaf_delete_count), - SUM(leaf_update_count), - SUM(forwarded_fetch_count), - SUM(row_lock_count), - SUM(row_lock_wait_count), - SUM(row_lock_wait_in_ms), - CASE WHEN SUM(row_lock_wait_in_ms) > 0 THEN - SUM(row_lock_wait_in_ms)/(1.*SUM(row_lock_wait_count)) - ELSE 0 END AS avg_row_lock_wait_in_ms, - SUM(page_lock_count), - SUM(page_lock_wait_count), - SUM(page_lock_wait_in_ms), - CASE WHEN SUM(page_lock_wait_in_ms) > 0 THEN - SUM(page_lock_wait_in_ms)/(1.*SUM(page_lock_wait_count)) - ELSE 0 END AS avg_page_lock_wait_in_ms, - SUM(index_lock_promotion_attempt_count), - SUM(index_lock_promotion_count), - LEFT(MAX(data_compression_info.data_compression_rollup),4000), - SUM(page_latch_wait_count), - SUM(page_latch_wait_in_ms), - SUM(page_io_latch_wait_count), - SUM(page_io_latch_wait_in_ms) - FROM #IndexPartitionSanity ipp - /* individual partitions can have distinct compression settings, just roll them into a list here*/ - OUTER APPLY (SELECT STUFF(( - SELECT N', ' + data_compression_desc - FROM #IndexPartitionSanity ipp2 - WHERE ipp.[object_id]=ipp2.[object_id] - AND ipp.[index_id]=ipp2.[index_id] - AND ipp.database_id = ipp2.database_id - AND ipp.schema_name = ipp2.schema_name - ORDER BY ipp2.partition_number - FOR XML PATH(''),TYPE).value('.', 'nvarchar(max)'), 1, 1, '')) - data_compression_info(data_compression_rollup) - GROUP BY index_sanity_id, ipp.database_id, ipp.schema_name, ipp.lock_escalation_desc - ORDER BY index_sanity_id -OPTION ( RECOMPILE ); - -RAISERROR (N'Determining index usefulness',0,1) WITH NOWAIT; -UPDATE #MissingIndexes -SET is_low = CASE WHEN (user_seeks + user_scans) < 5000 - OR unique_compiles = 1 - THEN 1 - ELSE 0 - END; - -RAISERROR (N'Updating #IndexSanity.referenced_by_foreign_key',0,1) WITH NOWAIT; -UPDATE #IndexSanity - SET is_referenced_by_foreign_key=1 -FROM #IndexSanity s -JOIN #ForeignKeys fk ON - s.object_id=fk.referenced_object_id - AND s.database_id=fk.database_id - AND LEFT(s.key_column_names,LEN(fk.referenced_fk_columns)) = fk.referenced_fk_columns; - -RAISERROR (N'Update index_secret on #IndexSanity for NC indexes.',0,1) WITH NOWAIT; -UPDATE nc -SET secret_columns= - N'[' + - CASE tb.count_key_columns WHEN 0 THEN '1' ELSE CAST(tb.count_key_columns AS NVARCHAR(10)) END + - CASE nc.is_unique WHEN 1 THEN N' INCLUDE' ELSE N' KEY' END + - CASE WHEN tb.count_key_columns > 1 THEN N'S] ' ELSE N'] ' END + - CASE tb.index_id WHEN 0 THEN '[RID]' ELSE LTRIM(tb.key_column_names) + - /* Uniquifiers only needed on non-unique clustereds-- not heaps */ - CASE tb.is_unique WHEN 0 THEN ' [UNIQUIFIER]' ELSE N'' END - END - , count_secret_columns= - CASE tb.index_id WHEN 0 THEN 1 ELSE - tb.count_key_columns + - CASE tb.is_unique WHEN 0 THEN 1 ELSE 0 END - END -FROM #IndexSanity AS nc -JOIN #IndexSanity AS tb ON nc.object_id=tb.object_id - AND nc.database_id = tb.database_id - AND nc.schema_name = tb.schema_name - AND tb.index_id IN (0,1) -WHERE nc.index_id > 1; - -RAISERROR (N'Update index_secret on #IndexSanity for heaps and non-unique clustered.',0,1) WITH NOWAIT; -UPDATE tb -SET secret_columns= CASE tb.index_id WHEN 0 THEN '[RID]' ELSE '[UNIQUIFIER]' END - , count_secret_columns = 1 -FROM #IndexSanity AS tb -WHERE tb.index_id = 0 /*Heaps-- these have the RID */ - OR (tb.index_id=1 AND tb.is_unique=0); /* Non-unique CX: has uniquifer (when needed) */ - - -RAISERROR (N'Populate #IndexCreateTsql.',0,1) WITH NOWAIT; -INSERT #IndexCreateTsql (index_sanity_id, create_tsql) -SELECT - index_sanity_id, - ISNULL ( - CASE index_id WHEN 0 THEN N'ALTER TABLE ' + QUOTENAME([database_name]) + N'.' + QUOTENAME([schema_name]) + N'.' + QUOTENAME([object_name]) + ' REBUILD;' - ELSE - CASE WHEN is_XML = 1 OR is_spatial = 1 OR is_in_memory_oltp = 1 THEN N'' /* Not even trying for these just yet...*/ - ELSE - CASE WHEN is_primary_key=1 THEN - N'ALTER TABLE ' + QUOTENAME([database_name]) + N'.' + QUOTENAME([schema_name]) + - N'.' + QUOTENAME([object_name]) + - N' ADD CONSTRAINT [' + - index_name + - N'] PRIMARY KEY ' + - CASE WHEN index_id=1 THEN N'CLUSTERED (' ELSE N'(' END + - key_column_names_with_sort_order_no_types + N' )' - WHEN is_unique_constraint = 1 AND is_primary_key = 0 - THEN - N'ALTER TABLE ' + QUOTENAME([database_name]) + N'.' + QUOTENAME([schema_name]) + - N'.' + QUOTENAME([object_name]) + - N' ADD CONSTRAINT [' + - index_name + - N'] UNIQUE ' + - CASE WHEN index_id=1 THEN N'CLUSTERED (' ELSE N'(' END + - key_column_names_with_sort_order_no_types + N' )' - WHEN is_CX_columnstore= 1 THEN - N'CREATE CLUSTERED COLUMNSTORE INDEX ' + QUOTENAME(index_name) + N' on ' + QUOTENAME([database_name]) + N'.' + QUOTENAME([schema_name]) + N'.' + QUOTENAME([object_name]) - ELSE /*Else not a PK or cx columnstore */ - N'CREATE ' + - CASE WHEN is_unique=1 THEN N'UNIQUE ' ELSE N'' END + - CASE WHEN index_id=1 THEN N'CLUSTERED ' ELSE N'' END + - CASE WHEN is_NC_columnstore=1 THEN N'NONCLUSTERED COLUMNSTORE ' - ELSE N'' END + - N'INDEX [' - + index_name + N'] ON ' + - QUOTENAME([database_name]) + N'.' + - QUOTENAME([schema_name]) + N'.' + QUOTENAME([object_name]) + - CASE WHEN is_NC_columnstore=1 THEN - N' (' + ISNULL(include_column_names_no_types,'') + N' )' - ELSE /*Else not columnstore */ - N' (' + ISNULL(key_column_names_with_sort_order_no_types,'') + N' )' - + CASE WHEN include_column_names_no_types IS NOT NULL THEN - N' INCLUDE (' + include_column_names_no_types + N')' - ELSE N'' - END - END /*End non-columnstore case */ - + CASE WHEN filter_definition <> N'' THEN N' WHERE ' + filter_definition ELSE N'' END - END /*End Non-PK index CASE */ - + CASE WHEN is_NC_columnstore=0 AND is_CX_columnstore=0 THEN - N' WITH (' - + N'FILLFACTOR=' + CASE fill_factor WHEN 0 THEN N'100' ELSE CAST(fill_factor AS NVARCHAR(5)) END + ', ' - + N'ONLINE=?, SORT_IN_TEMPDB=?, DATA_COMPRESSION=?' - + N')' - ELSE N'' END - + N';' - END /*End non-spatial and non-xml CASE */ - END, '[Unknown Error]') - AS create_tsql -FROM #IndexSanity; - -RAISERROR (N'Populate #PartitionCompressionInfo.',0,1) WITH NOWAIT; -IF OBJECT_ID('tempdb..#maps') IS NOT NULL DROP TABLE #maps; -WITH maps - AS - ( - SELECT ips.index_sanity_id, - ips.partition_number, - ips.data_compression_desc, - ips.partition_number - ROW_NUMBER() OVER ( PARTITION BY ips.index_sanity_id, ips.data_compression_desc - ORDER BY ips.partition_number ) AS rn - FROM #IndexPartitionSanity AS ips - ) -SELECT * -INTO #maps -FROM maps; - -IF OBJECT_ID('tempdb..#grps') IS NOT NULL DROP TABLE #grps; -WITH grps - AS - ( - SELECT MIN(maps.partition_number) AS MinKey, - MAX(maps.partition_number) AS MaxKey, - maps.index_sanity_id, - maps.data_compression_desc - FROM #maps AS maps - GROUP BY maps.rn, maps.index_sanity_id, maps.data_compression_desc - ) -SELECT * -INTO #grps -FROM grps; - -INSERT #PartitionCompressionInfo ( index_sanity_id, partition_compression_detail ) -SELECT DISTINCT - grps.index_sanity_id, - SUBSTRING( - ( STUFF( - ( SELECT N', ' + N' Partition' - + CASE - WHEN grps2.MinKey < grps2.MaxKey - THEN - + N's ' + CAST(grps2.MinKey AS NVARCHAR(10)) + N' - ' - + CAST(grps2.MaxKey AS NVARCHAR(10)) + N' use ' + grps2.data_compression_desc - ELSE - N' ' + CAST(grps2.MinKey AS NVARCHAR(10)) + N' uses ' + grps2.data_compression_desc - END AS Partitions - FROM #grps AS grps2 - WHERE grps2.index_sanity_id = grps.index_sanity_id - ORDER BY grps2.MinKey, grps2.MaxKey - FOR XML PATH(''), TYPE ).value('.', 'NVARCHAR(MAX)'), 1, 1, '')), 0, 8000) AS partition_compression_detail -FROM #grps AS grps; - -RAISERROR (N'Update #PartitionCompressionInfo.',0,1) WITH NOWAIT; -UPDATE sz -SET sz.data_compression_desc = pci.partition_compression_detail -FROM #IndexSanitySize sz -JOIN #PartitionCompressionInfo AS pci -ON pci.index_sanity_id = sz.index_sanity_id; - -RAISERROR (N'Update #IndexSanity for filtered indexes with columns not in the index definition.',0,1) WITH NOWAIT; -UPDATE #IndexSanity -SET filter_columns_not_in_index = D1.filter_columns_not_in_index -FROM #IndexSanity si - CROSS APPLY ( SELECT RTRIM(STUFF( (SELECT N', ' + c.column_name AS col_definition - FROM #FilteredIndexes AS c - WHERE c.database_id= si.database_id - AND c.schema_name = si.schema_name - AND c.table_name = si.object_name - AND c.index_name = si.index_name - ORDER BY c.index_sanity_id - FOR XML PATH('') , TYPE).value('.', 'nvarchar(max)'), 1, 1,''))) D1 - ( filter_columns_not_in_index ); - - -IF @Debug = 1 -BEGIN - SELECT '#BlitzIndexResults' AS table_name, * FROM #BlitzIndexResults AS bir; - SELECT '#IndexSanity' AS table_name, * FROM #IndexSanity; - SELECT '#IndexPartitionSanity' AS table_name, * FROM #IndexPartitionSanity; - SELECT '#IndexSanitySize' AS table_name, * FROM #IndexSanitySize; - SELECT '#IndexColumns' AS table_name, * FROM #IndexColumns; - SELECT '#MissingIndexes' AS table_name, * FROM #MissingIndexes; - SELECT '#ForeignKeys' AS table_name, * FROM #ForeignKeys; - SELECT '#UnindexedForeignKeys' AS table_name, * FROM #UnindexedForeignKeys; - SELECT '#BlitzIndexResults' AS table_name, * FROM #BlitzIndexResults; - SELECT '#IndexCreateTsql' AS table_name, * FROM #IndexCreateTsql; - SELECT '#DatabaseList' AS table_name, * FROM #DatabaseList; - SELECT '#Statistics' AS table_name, * FROM #Statistics; - SELECT '#PartitionCompressionInfo' AS table_name, * FROM #PartitionCompressionInfo; - SELECT '#ComputedColumns' AS table_name, * FROM #ComputedColumns; - SELECT '#TraceStatus' AS table_name, * FROM #TraceStatus; - SELECT '#CheckConstraints' AS table_name, * FROM #CheckConstraints; - SELECT '#FilteredIndexes' AS table_name, * FROM #FilteredIndexes; -END - - ----------------------------------------- ---STEP 3: DIAGNOSE THE PATIENT ----------------------------------------- - - -BEGIN TRY ----------------------------------------- ---If @TableName is specified, just return information for that table. ---The @Mode parameter doesn't matter if you're looking at a specific table. ----------------------------------------- -IF @TableName IS NOT NULL -BEGIN - RAISERROR(N'@TableName specified, giving detail only on that table.', 0,1) WITH NOWAIT; - - --We do a left join here in case this is a disabled NC. - --In that case, it won't have any size info/pages allocated. - - IF (@ShowColumnstoreOnly = 0) - BEGIN - WITH table_mode_cte AS ( - SELECT - s.db_schema_object_indexid, - s.key_column_names, - s.index_definition, - ISNULL(s.secret_columns,N'') AS secret_columns, - s.fill_factor, - s.index_usage_summary, - sz.index_op_stats, - ISNULL(sz.index_size_summary,'') /*disabled NCs will be null*/ AS index_size_summary, - partition_compression_detail , - ISNULL(sz.index_lock_wait_summary,'') AS index_lock_wait_summary, - s.is_referenced_by_foreign_key, - (SELECT COUNT(*) - FROM #ForeignKeys fk WHERE fk.parent_object_id=s.object_id - AND PATINDEX (fk.parent_fk_columns, s.key_column_names)=1) AS FKs_covered_by_index, - s.last_user_seek, - s.last_user_scan, - s.last_user_lookup, - s.last_user_update, - s.create_date, - s.modify_date, - sz.page_latch_wait_count, - CONVERT(VARCHAR(10), (sz.page_latch_wait_in_ms / 1000) / 86400) + ':' + CONVERT(VARCHAR(20), DATEADD(s, (sz.page_latch_wait_in_ms / 1000), 0), 108) AS page_latch_wait_time, - sz.page_io_latch_wait_count, - CONVERT(VARCHAR(10), (sz.page_io_latch_wait_in_ms / 1000) / 86400) + ':' + CONVERT(VARCHAR(20), DATEADD(s, (sz.page_io_latch_wait_in_ms / 1000), 0), 108) AS page_io_latch_wait_time, - ct.create_tsql, - CASE - WHEN s.is_primary_key = 1 AND s.index_definition <> '[HEAP]' - THEN N'--ALTER TABLE ' + QUOTENAME(s.[database_name]) + N'.' + QUOTENAME(s.[schema_name]) + N'.' + QUOTENAME(s.[object_name]) - + N' DROP CONSTRAINT ' + QUOTENAME(s.index_name) + N';' - WHEN s.is_primary_key = 0 AND is_unique_constraint = 1 AND s.index_definition <> '[HEAP]' - THEN N'--ALTER TABLE ' + QUOTENAME(s.[database_name]) + N'.' + QUOTENAME(s.[schema_name]) + N'.' + QUOTENAME(s.[object_name]) - + N' DROP CONSTRAINT ' + QUOTENAME(s.index_name) + N';' - WHEN s.is_primary_key = 0 AND s.index_definition <> '[HEAP]' - THEN N'--DROP INDEX '+ QUOTENAME(s.index_name) + N' ON ' + QUOTENAME(s.[database_name]) + N'.' + - QUOTENAME(s.[schema_name]) + N'.' + QUOTENAME(s.[object_name]) + N';' - ELSE N'' - END AS drop_tsql, - 1 AS display_order - FROM #IndexSanity s - LEFT JOIN #IndexSanitySize sz ON - s.index_sanity_id=sz.index_sanity_id - LEFT JOIN #IndexCreateTsql ct ON - s.index_sanity_id=ct.index_sanity_id - LEFT JOIN #PartitionCompressionInfo pci ON - pci.index_sanity_id = s.index_sanity_id - WHERE s.[object_id]=@ObjectID - UNION ALL - SELECT N'Database ' + QUOTENAME(@DatabaseName) + N' as of ' + CONVERT(NVARCHAR(16),GETDATE(),121) + - N' (' + @ScriptVersionName + ')' , - N'SQL Server First Responder Kit' , - N'http://FirstResponderKit.org' , - N'From Your Community Volunteers', - NULL,@DaysUptimeInsertValue,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL, - 0 AS display_order - ) - SELECT - db_schema_object_indexid AS [Details: db_schema.table.index(indexid)], - index_definition AS [Definition: [Property]] ColumnName {datatype maxbytes}], - secret_columns AS [Secret Columns], - fill_factor AS [Fillfactor], - index_usage_summary AS [Usage Stats], - index_op_stats AS [Op Stats], - index_size_summary AS [Size], - partition_compression_detail AS [Compression Type], - index_lock_wait_summary AS [Lock Waits], - is_referenced_by_foreign_key AS [Referenced by FK?], - FKs_covered_by_index AS [FK Covered by Index?], - last_user_seek AS [Last User Seek], - last_user_scan AS [Last User Scan], - last_user_lookup AS [Last User Lookup], - last_user_update AS [Last User Write], - create_date AS [Created], - modify_date AS [Last Modified], - page_latch_wait_count AS [Page Latch Wait Count], - page_latch_wait_time as [Page Latch Wait Time (D:H:M:S)], - page_io_latch_wait_count AS [Page IO Latch Wait Count], - page_io_latch_wait_time as [Page IO Latch Wait Time (D:H:M:S)], - create_tsql AS [Create TSQL], - drop_tsql AS [Drop TSQL] - FROM table_mode_cte - ORDER BY display_order ASC, key_column_names ASC - OPTION ( RECOMPILE ); - - IF (SELECT TOP 1 [object_id] FROM #MissingIndexes mi) IS NOT NULL - BEGIN; - - WITH create_date AS ( - SELECT i.database_id, - i.schema_name, - i.[object_id], - ISNULL(NULLIF(MAX(DATEDIFF(DAY, i.create_date, SYSDATETIME())), 0), 1) AS create_days - FROM #IndexSanity AS i - GROUP BY i.database_id, i.schema_name, i.object_id - ) - SELECT N'Missing index.' AS Finding , - N'https://www.brentozar.com/go/Indexaphobia' AS URL , - mi.[statement] + - ' Est. Benefit: ' - + CASE WHEN magic_benefit_number >= 922337203685477 THEN '>= 922,337,203,685,477' - ELSE REPLACE(CONVERT(NVARCHAR(256),CAST(CAST( - (magic_benefit_number / CASE WHEN cd.create_days < @DaysUptime THEN cd.create_days ELSE @DaysUptime END) - AS BIGINT) AS MONEY), 1), '.00', '') - END AS [Estimated Benefit], - missing_index_details AS [Missing Index Request] , - index_estimated_impact AS [Estimated Impact], - create_tsql AS [Create TSQL], - sample_query_plan AS [Sample Query Plan] - FROM #MissingIndexes mi - LEFT JOIN create_date AS cd - ON mi.[object_id] = cd.object_id - AND mi.database_id = cd.database_id - AND mi.schema_name = cd.schema_name - WHERE mi.[object_id] = @ObjectID - AND (@ShowAllMissingIndexRequests=1 - /* Minimum benefit threshold = 100k/day of uptime OR since table creation date, whichever is lower*/ - OR (magic_benefit_number / CASE WHEN cd.create_days < @DaysUptime THEN cd.create_days ELSE @DaysUptime END) >= 100000) - ORDER BY magic_benefit_number DESC - OPTION ( RECOMPILE ); - END; - ELSE - SELECT 'No missing indexes.' AS finding; - - SELECT - column_name AS [Column Name], - (SELECT COUNT(*) - FROM #IndexColumns c2 - WHERE c2.column_name=c.column_name - AND c2.key_ordinal IS NOT NULL) - + CASE WHEN c.index_id = 1 AND c.key_ordinal IS NOT NULL THEN - -1+ (SELECT COUNT(DISTINCT index_id) - FROM #IndexColumns c3 - WHERE c3.index_id NOT IN (0,1)) - ELSE 0 END - AS [Found In], - system_type_name + - CASE max_length WHEN -1 THEN N' (max)' ELSE - CASE - WHEN system_type_name IN (N'char',N'varchar',N'binary',N'varbinary') THEN N' (' + CAST(max_length AS NVARCHAR(20)) + N')' - WHEN system_type_name IN (N'nchar',N'nvarchar') THEN N' (' + CAST(max_length/2 AS NVARCHAR(20)) + N')' - ELSE '' - END - END - AS [Type], - CASE is_computed WHEN 1 THEN 'yes' ELSE '' END AS [Computed?], - max_length AS [Length (max bytes)], - [precision] AS [Prec], - [scale] AS [Scale], - CASE is_nullable WHEN 1 THEN 'yes' ELSE '' END AS [Nullable?], - CASE is_identity WHEN 1 THEN 'yes' ELSE '' END AS [Identity?], - CASE is_replicated WHEN 1 THEN 'yes' ELSE '' END AS [Replicated?], - CASE is_sparse WHEN 1 THEN 'yes' ELSE '' END AS [Sparse?], - CASE is_filestream WHEN 1 THEN 'yes' ELSE '' END AS [Filestream?], - collation_name AS [Collation] - FROM #IndexColumns AS c - WHERE index_id IN (0,1); - - IF (SELECT TOP 1 parent_object_id FROM #ForeignKeys) IS NOT NULL - BEGIN - SELECT [database_name] + N':' + parent_object_name + N': ' + foreign_key_name AS [Foreign Key], - parent_fk_columns AS [Foreign Key Columns], - referenced_object_name AS [Referenced Table], - referenced_fk_columns AS [Referenced Table Columns], - is_disabled AS [Is Disabled?], - is_not_trusted AS [Not Trusted?], - is_not_for_replication [Not for Replication?], - [update_referential_action_desc] AS [Cascading Updates?], - [delete_referential_action_desc] AS [Cascading Deletes?] - FROM #ForeignKeys - ORDER BY [Foreign Key] - OPTION ( RECOMPILE ); - END; - ELSE - SELECT 'No foreign keys.' AS finding; - - /* Show histograms for all stats on this table. More info: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/1900 */ - IF EXISTS (SELECT * FROM sys.all_objects WHERE name = 'dm_db_stats_histogram') - BEGIN - SET @dsql = N'USE ' + QUOTENAME(@DatabaseName) + N'; - SELECT s.name AS [Stat Name], c.name AS [Leading Column Name], hist.step_number AS [Step Number], - hist.range_high_key AS [Range High Key], hist.range_rows AS [Range Rows], - hist.equal_rows AS [Equal Rows], hist.distinct_range_rows AS [Distinct Range Rows], hist.average_range_rows AS [Average Range Rows], - s.auto_created AS [Auto-Created], s.user_created AS [User-Created], - props.last_updated AS [Last Updated], props.modification_counter AS [Modification Counter], props.rows AS [Table Rows], - props.rows_sampled AS [Rows Sampled], s.stats_id AS [StatsID] - FROM sys.stats AS s - INNER JOIN sys.stats_columns sc ON s.object_id = sc.object_id AND s.stats_id = sc.stats_id AND sc.stats_column_id = 1 - INNER JOIN sys.columns c ON sc.object_id = c.object_id AND sc.column_id = c.column_id - CROSS APPLY sys.dm_db_stats_properties(s.object_id, s.stats_id) AS props - CROSS APPLY sys.dm_db_stats_histogram(s.[object_id], s.stats_id) AS hist - WHERE s.object_id = @ObjectID - ORDER BY s.auto_created, s.user_created, s.name, hist.step_number;'; - EXEC sp_executesql @dsql, N'@ObjectID INT', @ObjectID; - END - END - - /* Visualize columnstore index contents. More info: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2584 */ - IF 2 = (SELECT SUM(1) FROM sys.all_objects WHERE name IN ('column_store_row_groups','column_store_segments')) - BEGIN - RAISERROR(N'Visualizing columnstore index contents.', 0,1) WITH NOWAIT; - - SET @dsql = N'USE ' + QUOTENAME(@DatabaseName) + N'; - IF EXISTS(SELECT * FROM ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_row_groups WHERE object_id = @ObjectID) - BEGIN - SELECT @ColumnList = N'''', @ColumnListWithApostrophes = N''''; - WITH DistinctColumns AS ( - SELECT DISTINCT QUOTENAME(c.name) AS column_name, QUOTENAME(c.name,'''''''') AS ColumnNameWithApostrophes, c.column_id - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.partitions p - INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns c ON p.object_id = c.object_id - INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.index_columns ic on ic.column_id = c.column_id and ic.object_id = c.object_id AND ic.index_id = p.index_id - INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.types t ON c.system_type_id = t.system_type_id AND c.user_type_id = t.user_type_id - WHERE p.object_id = @ObjectID - AND EXISTS (SELECT * FROM ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_segments seg WHERE p.partition_id = seg.partition_id AND seg.column_id = ic.index_column_id) - AND p.data_compression IN (3,4) - ) - SELECT @ColumnList = @ColumnList + column_name + N'', '', - @ColumnListWithApostrophes = @ColumnListWithApostrophes + ColumnNameWithApostrophes + N'', '' - FROM DistinctColumns - ORDER BY column_id; - SELECT @PartitionCount = COUNT(1) FROM ' + QUOTENAME(@DatabaseName) + N'.sys.partitions WHERE object_id = @ObjectID AND data_compression IN (3,4); - END'; - - IF @Debug = 1 - BEGIN - PRINT SUBSTRING(@dsql, 0, 4000); - PRINT SUBSTRING(@dsql, 4000, 8000); - PRINT SUBSTRING(@dsql, 8000, 12000); - PRINT SUBSTRING(@dsql, 12000, 16000); - PRINT SUBSTRING(@dsql, 16000, 20000); - PRINT SUBSTRING(@dsql, 20000, 24000); - PRINT SUBSTRING(@dsql, 24000, 28000); - PRINT SUBSTRING(@dsql, 28000, 32000); - PRINT SUBSTRING(@dsql, 32000, 36000); - PRINT SUBSTRING(@dsql, 36000, 40000); - END; - - EXEC sp_executesql @dsql, N'@ObjectID INT, @ColumnList NVARCHAR(MAX) OUTPUT, @ColumnListWithApostrophes NVARCHAR(MAX) OUTPUT, @PartitionCount INT OUTPUT', @ObjectID, @ColumnList OUTPUT, @ColumnListWithApostrophes OUTPUT, @PartitionCount OUTPUT; - - IF @PartitionCount < 2 - SET @ShowPartitionRanges = 0; - - IF @Debug = 1 - SELECT @ColumnList AS ColumnstoreColumnList, @ColumnListWithApostrophes AS ColumnstoreColumnListWithApostrophes, @PartitionCount AS PartitionCount, @ShowPartitionRanges AS ShowPartitionRanges; - - IF @ColumnList <> '' - BEGIN - /* Remove the trailing comma */ - SET @ColumnList = LEFT(@ColumnList, LEN(@ColumnList) - 1); - SET @ColumnListWithApostrophes = LEFT(@ColumnListWithApostrophes, LEN(@ColumnListWithApostrophes) - 1); - - SET @dsql = N'USE ' + QUOTENAME(@DatabaseName) + N'; - SELECT partition_number, ' - + CASE WHEN @ShowPartitionRanges = 1 THEN N' COALESCE(range_start_op + '' '' + range_start + '' '', '''') + COALESCE(range_end_op + '' '' + range_end, '''') AS partition_range, ' ELSE N' ' END - + N' row_group_id, total_rows, deleted_rows, ' - + @ColumnList - + CASE WHEN @ShowPartitionRanges = 1 THEN N' , - state_desc, trim_reason_desc, transition_to_compressed_state_desc, has_vertipaq_optimization - FROM ( - SELECT column_name, partition_number, row_group_id, total_rows, deleted_rows, details, - range_start_op, - CASE - WHEN format_type IS NULL THEN CAST(range_start_value AS NVARCHAR(4000)) - ELSE CONVERT(NVARCHAR(4000), range_start_value, format_type) END range_start, - range_end_op, - CASE - WHEN format_type IS NULL THEN CAST(range_end_value AS NVARCHAR(4000)) - ELSE CONVERT(NVARCHAR(4000), range_end_value, format_type) END range_end' ELSE N' ' END + N', - state_desc, trim_reason_desc, transition_to_compressed_state_desc, has_vertipaq_optimization - FROM ( - SELECT c.name AS column_name, p.partition_number, rg.row_group_id, rg.total_rows, rg.deleted_rows, - phys.state_desc, phys.trim_reason_desc, phys.transition_to_compressed_state_desc, phys.has_vertipaq_optimization, - details = CAST(seg.min_data_id AS VARCHAR(20)) + '' to '' + CAST(seg.max_data_id AS VARCHAR(20)) + '', '' + CAST(CAST(((COALESCE(d.on_disk_size,0) + COALESCE(seg.on_disk_size,0)) / 1024.0 / 1024) AS DECIMAL(18,0)) AS VARCHAR(20)) + '' MB''' - + CASE WHEN @ShowPartitionRanges = 1 THEN N', - CASE - WHEN pp.system_type_id IN (40, 41, 42, 43, 58, 61) THEN 126 - WHEN pp.system_type_id IN (59, 62) THEN 3 - WHEN pp.system_type_id IN (60, 122) THEN 2 - ELSE NULL END format_type, - CASE WHEN pf.boundary_value_on_right = 0 THEN ''>'' ELSE ''>='' END range_start_op, - prvs.value range_start_value, - CASE WHEN pf.boundary_value_on_right = 0 THEN ''<='' ELSE ''<'' END range_end_op, - prve.value range_end_value ' ELSE N' ' END + N' - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_row_groups rg - INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns c ON rg.object_id = c.object_id - INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partitions p ON rg.object_id = p.object_id AND rg.partition_number = p.partition_number - INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.index_columns ic on ic.column_id = c.column_id AND ic.object_id = c.object_id AND ic.index_id = p.index_id - LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_column_store_row_group_physical_stats phys ON rg.row_group_id = phys.row_group_id AND rg.object_id = phys.object_id AND rg.partition_number = phys.partition_number AND rg.index_id = phys.index_id ' + CASE WHEN @ShowPartitionRanges = 1 THEN N' - LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.indexes i ON i.object_id = rg.object_id AND i.index_id = rg.index_id - LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partition_schemes ps ON ps.data_space_id = i.data_space_id - LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partition_functions pf ON pf.function_id = ps.function_id - LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partition_parameters pp ON pp.function_id = pf.function_id - LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partition_range_values prvs ON prvs.function_id = pf.function_id AND prvs.boundary_id = p.partition_number - 1 - LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partition_range_values prve ON prve.function_id = pf.function_id AND prve.boundary_id = p.partition_number ' ELSE N' ' END - + N' LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_segments seg ON p.partition_id = seg.partition_id AND ic.index_column_id = seg.column_id AND rg.row_group_id = seg.segment_id - LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_dictionaries d ON p.hobt_id = d.hobt_id AND c.column_id = d.column_id AND seg.secondary_dictionary_id = d.dictionary_id - WHERE rg.object_id = @ObjectID - AND rg.state IN (1, 2, 3) - AND c.name IN ( ' + @ColumnListWithApostrophes + N')' - + CASE WHEN @ShowPartitionRanges = 1 THEN N' - ) AS y ' ELSE N' ' END + N' - ) AS x - PIVOT (MAX(details) FOR column_name IN ( ' + @ColumnList + N')) AS pivot1 - ORDER BY partition_number, row_group_id;'; - - IF @Debug = 1 - BEGIN - PRINT SUBSTRING(@dsql, 0, 4000); - PRINT SUBSTRING(@dsql, 4000, 8000); - PRINT SUBSTRING(@dsql, 8000, 12000); - PRINT SUBSTRING(@dsql, 12000, 16000); - PRINT SUBSTRING(@dsql, 16000, 20000); - PRINT SUBSTRING(@dsql, 20000, 24000); - PRINT SUBSTRING(@dsql, 24000, 28000); - PRINT SUBSTRING(@dsql, 28000, 32000); - PRINT SUBSTRING(@dsql, 32000, 36000); - PRINT SUBSTRING(@dsql, 36000, 40000); - END; - - IF @dsql IS NULL - RAISERROR('@dsql is null',16,1); - ELSE - EXEC sp_executesql @dsql, N'@ObjectID INT', @ObjectID; - END - ELSE /* No columns were found for this object */ - BEGIN - SELECT N'No compressed columnstore rowgroups were found for this object.' AS Columnstore_Visualization - UNION ALL - SELECT N'SELECT * FROM ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_row_groups WHERE object_id = ' + CAST(@ObjectID AS NVARCHAR(100)); - END - RAISERROR(N'Done visualizing columnstore index contents.', 0,1) WITH NOWAIT; - END - - IF @ShowColumnstoreOnly = 1 - RETURN; - -END; /* IF @TableName IS NOT NULL */ - - - - - - - - -ELSE /* @TableName IS NULL, so we operate in normal mode 0/1/2/3/4 */ -BEGIN - -/* Validate and check table output params */ - - - /* Checks if @OutputServerName is populated with a valid linked server, and that the database name specified is valid */ - DECLARE @ValidOutputServer BIT; - DECLARE @ValidOutputLocation BIT; - DECLARE @LinkedServerDBCheck NVARCHAR(2000); - DECLARE @ValidLinkedServerDB INT; - DECLARE @tmpdbchk TABLE (cnt INT); - - IF @OutputServerName IS NOT NULL - BEGIN - IF (SUBSTRING(@OutputTableName, 2, 1) = '#') - BEGIN - RAISERROR('Due to the nature of temporary tables, outputting to a linked server requires a permanent table.', 16, 0); - END; - ELSE IF EXISTS (SELECT server_id FROM sys.servers WHERE QUOTENAME([name]) = @OutputServerName) - BEGIN - SET @LinkedServerDBCheck = 'SELECT 1 WHERE EXISTS (SELECT * FROM '+@OutputServerName+'.master.sys.databases WHERE QUOTENAME([name]) = '''+@OutputDatabaseName+''')'; - INSERT INTO @tmpdbchk EXEC sys.sp_executesql @LinkedServerDBCheck; - SET @ValidLinkedServerDB = (SELECT COUNT(*) FROM @tmpdbchk); - IF (@ValidLinkedServerDB > 0) - BEGIN - SET @ValidOutputServer = 1; - SET @ValidOutputLocation = 1; - END; - ELSE - RAISERROR('The specified database was not found on the output server', 16, 0); - END; - ELSE - BEGIN - RAISERROR('The specified output server was not found', 16, 0); - END; - END; - ELSE - BEGIN - IF (SUBSTRING(@OutputTableName, 2, 2) = '##') - BEGIN - SET @StringToExecute = N' IF (OBJECT_ID(''[tempdb].[dbo].@@@OutputTableName@@@'') IS NOT NULL) DROP TABLE @@@OutputTableName@@@'; - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); - EXEC(@StringToExecute); - - SET @OutputServerName = QUOTENAME(CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128))); - SET @OutputDatabaseName = '[tempdb]'; - SET @OutputSchemaName = '[dbo]'; - SET @ValidOutputLocation = 1; - END; - ELSE IF (SUBSTRING(@OutputTableName, 2, 1) = '#') - BEGIN - RAISERROR('Due to the nature of Dymamic SQL, only global (i.e. double pound (##)) temp tables are supported for @OutputTableName', 16, 0); - END; - ELSE IF @OutputDatabaseName IS NOT NULL - AND @OutputSchemaName IS NOT NULL - AND @OutputTableName IS NOT NULL - AND EXISTS ( SELECT * - FROM sys.databases - WHERE QUOTENAME([name]) = @OutputDatabaseName) - BEGIN - SET @ValidOutputLocation = 1; - SET @OutputServerName = QUOTENAME(CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128))); - END; - ELSE IF @OutputDatabaseName IS NOT NULL - AND @OutputSchemaName IS NOT NULL - AND @OutputTableName IS NOT NULL - AND NOT EXISTS ( SELECT * - FROM sys.databases - WHERE QUOTENAME([name]) = @OutputDatabaseName) - BEGIN - RAISERROR('The specified output database was not found on this server', 16, 0); - END; - ELSE - BEGIN - SET @ValidOutputLocation = 0; - END; - END; - - IF (@ValidOutputLocation = 0 AND @OutputType = 'NONE') - BEGIN - RAISERROR('Invalid output location and no output asked',12,1); - RETURN; - END; - - /* @OutputTableName lets us export the results to a permanent table */ - DECLARE @RunID UNIQUEIDENTIFIER; - SET @RunID = NEWID(); - - DECLARE @TableExists BIT; - DECLARE @SchemaExists BIT; - - DECLARE @TableExistsSql NVARCHAR(MAX); - - IF (@ValidOutputLocation = 1 AND COALESCE(@OutputServerName, @OutputDatabaseName, @OutputSchemaName, @OutputTableName) IS NOT NULL) - BEGIN - SET @StringToExecute = - N'SET @SchemaExists = 0; - SET @TableExists = 0; - IF EXISTS(SELECT * FROM @@@OutputServerName@@@.@@@OutputDatabaseName@@@.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''@@@OutputSchemaName@@@'') - SET @SchemaExists = 1 - IF EXISTS (SELECT * FROM @@@OutputServerName@@@.@@@OutputDatabaseName@@@.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''@@@OutputSchemaName@@@'' AND QUOTENAME(TABLE_NAME) = ''@@@OutputTableName@@@'') - BEGIN - SET @TableExists = 1 - IF NOT EXISTS(SELECT * FROM @@@OutputServerName@@@.@@@OutputDatabaseName@@@.INFORMATION_SCHEMA.COLUMNS WHERE QUOTENAME(TABLE_SCHEMA) = ''@@@OutputSchemaName@@@'' - AND QUOTENAME(TABLE_NAME) = ''@@@OutputTableName@@@'' AND QUOTENAME(COLUMN_NAME) = ''[total_forwarded_fetch_count]'') - EXEC @@@OutputServerName@@@.@@@OutputDatabaseName@@@.dbo.sp_executesql N''ALTER TABLE @@@OutputSchemaName@@@.@@@OutputTableName@@@ ADD [total_forwarded_fetch_count] BIGINT'' - END'; - - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputServerName@@@', @OutputServerName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputSchemaName@@@', @OutputSchemaName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); - - EXEC sp_executesql @StringToExecute, N'@TableExists BIT OUTPUT, @SchemaExists BIT OUTPUT', @TableExists OUTPUT, @SchemaExists OUTPUT; - - - SET @TableExistsSql = - N'IF EXISTS(SELECT * FROM @@@OutputServerName@@@.@@@OutputDatabaseName@@@.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''@@@OutputSchemaName@@@'') - AND NOT EXISTS (SELECT * FROM @@@OutputServerName@@@.@@@OutputDatabaseName@@@.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''@@@OutputSchemaName@@@'' AND QUOTENAME(TABLE_NAME) = ''@@@OutputTableName@@@'') - SET @TableExists = 0 - ELSE - SET @TableExists = 1'; - - SET @TableExistsSql = REPLACE(@TableExistsSql, '@@@OutputServerName@@@', @OutputServerName); - SET @TableExistsSql = REPLACE(@TableExistsSql, '@@@OutputDatabaseName@@@', @OutputDatabaseName); - SET @TableExistsSql = REPLACE(@TableExistsSql, '@@@OutputSchemaName@@@', @OutputSchemaName); - SET @TableExistsSql = REPLACE(@TableExistsSql, '@@@OutputTableName@@@', @OutputTableName); - - END - - - - - IF @Mode IN (0, 4) /* DIAGNOSE */ - BEGIN; - IF @Mode IN (0, 4) /* DIAGNOSE priorities 1-100 */ - BEGIN; - RAISERROR(N'@Mode=0 or 4, running rules for priorities 1-100.', 0,1) WITH NOWAIT; - - ---------------------------------------- - --Multiple Index Personalities: Check_id 0-10 - ---------------------------------------- - RAISERROR('check_id 1: Duplicate keys', 0,1) WITH NOWAIT; - WITH duplicate_indexes - AS ( SELECT [object_id], key_column_names, database_id, [schema_name] - FROM #IndexSanity AS ip - WHERE index_type IN (1,2) /* Clustered, NC only*/ - AND is_hypothetical = 0 - AND is_disabled = 0 - AND is_primary_key = 0 - AND EXISTS ( - SELECT 1/0 - FROM #IndexSanitySize ips - WHERE ip.index_sanity_id = ips.index_sanity_id - AND ip.database_id = ips.database_id - AND ip.schema_name = ips.schema_name - AND ips.total_reserved_MB >= CASE - WHEN (@GetAllDatabases = 1 OR @Mode = 0) - THEN @ThresholdMB - ELSE ips.total_reserved_MB - END - ) - GROUP BY [object_id], key_column_names, database_id, [schema_name] - HAVING COUNT(*) > 1) - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 1 AS check_id, - ip.index_sanity_id, - 20 AS Priority, - 'Multiple Index Personalities' AS findings_group, - 'Duplicate keys' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/duplicateindex' AS URL, - N'Index Name: ' + ip.index_name + N' Table Name: ' + ip.db_schema_object_name AS details, - ip.index_definition, - ip.secret_columns, - ip.index_usage_summary, - ips.index_size_summary - FROM duplicate_indexes di - JOIN #IndexSanity ip ON di.[object_id] = ip.[object_id] - AND ip.database_id = di.database_id - AND ip.[schema_name] = di.[schema_name] - AND di.key_column_names = ip.key_column_names - JOIN #IndexSanitySize ips ON ip.index_sanity_id = ips.index_sanity_id - AND ip.database_id = ips.database_id - AND ip.schema_name = ips.schema_name - /* WHERE clause limits to only @ThresholdMB or larger duplicate indexes when getting all databases or using PainRelief mode */ - WHERE ips.total_reserved_MB >= CASE WHEN (@GetAllDatabases = 1 OR @Mode = 0) THEN @ThresholdMB ELSE ips.total_reserved_MB END - AND ip.is_primary_key = 0 - ORDER BY ips.total_rows DESC, ip.[schema_name], ip.[object_name], ip.key_column_names_with_sort_order - OPTION ( RECOMPILE ); - - RAISERROR('check_id 2: Keys w/ identical leading columns.', 0,1) WITH NOWAIT; - WITH borderline_duplicate_indexes - AS ( SELECT DISTINCT database_id, [object_id], first_key_column_name, key_column_names, - COUNT([object_id]) OVER ( PARTITION BY database_id, [object_id], first_key_column_name ) AS number_dupes - FROM #IndexSanity - WHERE index_type IN (1,2) /* Clustered, NC only*/ - AND is_hypothetical=0 - AND is_disabled=0 - AND is_primary_key = 0) - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 2 AS check_id, - ip.index_sanity_id, - 30 AS Priority, - 'Multiple Index Personalities' AS findings_group, - 'Borderline duplicate keys' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/duplicateindex' AS URL, - ip.db_schema_object_indexid AS details, - ip.index_definition, - ip.secret_columns, - ip.index_usage_summary, - ips.index_size_summary - FROM #IndexSanity AS ip - JOIN #IndexSanitySize ips ON ip.index_sanity_id = ips.index_sanity_id - WHERE EXISTS ( - SELECT di.[object_id] - FROM borderline_duplicate_indexes AS di - WHERE di.[object_id] = ip.[object_id] AND - di.database_id = ip.database_id AND - di.first_key_column_name = ip.first_key_column_name AND - di.key_column_names <> ip.key_column_names AND - di.number_dupes > 1 - ) - AND ip.is_primary_key = 0 - ORDER BY ips.total_rows DESC, ip.[schema_name], ip.[object_name], ip.key_column_names, ip.include_column_names - OPTION ( RECOMPILE ); - - ---------------------------------------- - --Aggressive Indexes: Check_id 10-19 - ---------------------------------------- - - RAISERROR(N'check_id 11: Total lock wait time > 5 minutes (row + page)', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 11 AS check_id, - i.index_sanity_id, - 70 AS Priority, - N'Aggressive ' - + CASE COALESCE((SELECT SUM(1) - FROM #IndexSanity iMe - INNER JOIN #IndexSanity iOthers - ON iMe.database_id = iOthers.database_id - AND iMe.object_id = iOthers.object_id - AND iOthers.index_id > 1 - WHERE i.index_sanity_id = iMe.index_sanity_id - AND iOthers.is_hypothetical = 0 - AND iOthers.is_disabled = 0 - ), 0) - WHEN 0 THEN N'Under-Indexing' - WHEN 1 THEN N'Under-Indexing' - WHEN 2 THEN N'Under-Indexing' - WHEN 3 THEN N'Under-Indexing' - WHEN 4 THEN N'Indexes' - WHEN 5 THEN N'Indexes' - WHEN 6 THEN N'Indexes' - WHEN 7 THEN N'Indexes' - WHEN 8 THEN N'Indexes' - WHEN 9 THEN N'Indexes' - ELSE N'Over-Indexing' - END AS findings_group, - N'Total lock wait time > 5 minutes (row + page)' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/AggressiveIndexes' AS URL, - (i.db_schema_object_indexid + N': ' + - sz.index_lock_wait_summary + N' NC indexes on table: ') COLLATE DATABASE_DEFAULT + - CAST(COALESCE((SELECT SUM(1) - FROM #IndexSanity iMe - INNER JOIN #IndexSanity iOthers - ON iMe.database_id = iOthers.database_id - AND iMe.object_id = iOthers.object_id - AND iOthers.index_id > 1 - WHERE i.index_sanity_id = iMe.index_sanity_id - AND iOthers.is_hypothetical = 0 - AND iOthers.is_disabled = 0 - ), 0) - AS NVARCHAR(30)) AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - sz.index_size_summary - FROM #IndexSanity AS i - JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id - WHERE (total_row_lock_wait_in_ms + total_page_lock_wait_in_ms) > 300000 - GROUP BY i.index_sanity_id, [database_name], i.db_schema_object_indexid, sz.index_lock_wait_summary, i.index_definition, i.secret_columns, i.index_usage_summary, sz.index_size_summary, sz.index_sanity_id - ORDER BY SUM(total_row_lock_wait_in_ms + total_page_lock_wait_in_ms) DESC, 4, [database_name], 8 - OPTION ( RECOMPILE ); - - - - ---------------------------------------- - --Index Hoarder: Check_id 20-29 - ---------------------------------------- - RAISERROR(N'check_id 20: >= 10 NC indexes on any given table. Yes, 10 is an arbitrary number.', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 20 AS check_id, - MAX(i.index_sanity_id) AS index_sanity_id, - 10 AS Priority, - 'Index Hoarder' AS findings_group, - 'Many NC Indexes on a Single Table' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/IndexHoarder' AS URL, - CAST (COUNT(*) AS NVARCHAR(30)) + ' NC indexes on ' + i.db_schema_object_name AS details, - i.db_schema_object_name + ' (' + CAST (COUNT(*) AS NVARCHAR(30)) + ' indexes)' AS index_definition, - '' AS secret_columns, - REPLACE(CONVERT(NVARCHAR(30),CAST(SUM(total_reads) AS MONEY), 1), N'.00', N'') + N' reads (ALL); ' - + REPLACE(CONVERT(NVARCHAR(30),CAST(SUM(user_updates) AS MONEY), 1), N'.00', N'') + N' writes (ALL); ', - REPLACE(CONVERT(NVARCHAR(30),CAST(MAX(total_rows) AS MONEY), 1), N'.00', N'') + N' rows (MAX)' - + CASE WHEN SUM(total_reserved_MB) > 1024 THEN - N'; ' + CAST(CAST(SUM(total_reserved_MB)/1024. AS NUMERIC(29,1)) AS NVARCHAR(30)) + 'GB (ALL)' - WHEN SUM(total_reserved_MB) > 0 THEN - N'; ' + CAST(CAST(SUM(total_reserved_MB) AS NUMERIC(29,1)) AS NVARCHAR(30)) + 'MB (ALL)' - ELSE '' - END AS index_size_summary - FROM #IndexSanity i - JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id - WHERE index_id NOT IN ( 0, 1 ) - GROUP BY db_schema_object_name, [i].[database_name] - HAVING COUNT(*) >= 10 - ORDER BY i.db_schema_object_name DESC - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 22: NC indexes with 0 reads. (Borderline) and >= 10,000 writes', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 22 AS check_id, - i.index_sanity_id, - 10 AS Priority, - N'Index Hoarder' AS findings_group, - N'Unused NC Index with High Writes' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/IndexHoarder' AS URL, - N'Reads: 0,' - + N' Writes: ' - + REPLACE(CONVERT(NVARCHAR(30), CAST((i.user_updates) AS MONEY), 1), N'.00', N'') - + N' on: ' - + i.db_schema_object_indexid - AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - sz.index_size_summary - FROM #IndexSanity AS i - JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.total_reads=0 - AND i.user_updates >= 10000 - 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 - AND @Filter <> 1 /* 1 = "ignore unused */ - ORDER BY i.db_schema_object_indexid - OPTION ( RECOMPILE ); - - - RAISERROR(N'check_id 34: Filtered index definition columns not in index definition', 0,1) WITH NOWAIT; - - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 34 AS check_id, - i.index_sanity_id, - 80 AS Priority, - N'Abnormal Psychology' AS findings_group, - N'Filter Columns Not In Index Definition' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/IndexFeatures' AS URL, - N'The index ' - + QUOTENAME(i.index_name) - + N' on [' - + i.db_schema_object_name - + N'] has a filter on [' - + i.filter_definition - + N'] but is missing [' - + LTRIM(i.filter_columns_not_in_index) - + N'] from the index definition.' - AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - sz.index_size_summary - FROM #IndexSanity i - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.filter_columns_not_in_index IS NOT NULL - ORDER BY i.db_schema_object_indexid - OPTION ( RECOMPILE ); - - ---------------------------------------- - --Self Loathing Indexes : Check_id 40-49 - ---------------------------------------- - - RAISERROR(N'check_id 40: Fillfactor in nonclustered 80 percent or less', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 40 AS check_id, - i.index_sanity_id, - 100 AS Priority, - N'Self Loathing Indexes' AS findings_group, - N'Low Fill Factor on Nonclustered Index' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/SelfLoathing' AS URL, - CAST(fill_factor AS NVARCHAR(10)) + N'% fill factor on ' + db_schema_object_indexid + N'. '+ - CASE WHEN (last_user_update IS NULL OR user_updates < 1) - THEN N'No writes have been made.' - ELSE - N'Last write was ' + CONVERT(NVARCHAR(16),last_user_update,121) + N' and ' + - CAST(user_updates AS NVARCHAR(25)) + N' updates have been made.' - END - AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - sz.index_size_summary - FROM #IndexSanity AS i - JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id - WHERE index_id > 1 - AND fill_factor BETWEEN 1 AND 80 OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 40: Fillfactor in clustered 80 percent or less', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 40 AS check_id, - i.index_sanity_id, - 100 AS Priority, - N'Self Loathing Indexes' AS findings_group, - N'Low Fill Factor on Clustered Index' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/SelfLoathing' AS URL, - N'Fill factor on ' + db_schema_object_indexid + N' is ' + CAST(fill_factor AS NVARCHAR(10)) + N'%. '+ - CASE WHEN (last_user_update IS NULL OR user_updates < 1) - THEN N'No writes have been made.' - ELSE - N'Last write was ' + CONVERT(NVARCHAR(16),last_user_update,121) + N' and ' + - CAST(user_updates AS NVARCHAR(25)) + N' updates have been made.' - END - AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - sz.index_size_summary - FROM #IndexSanity AS i - JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id - WHERE index_id = 1 - AND fill_factor BETWEEN 1 AND 80 OPTION ( RECOMPILE ); - - - RAISERROR(N'check_id 43: Heaps with forwarded records', 0,1) WITH NOWAIT; - WITH heaps_cte - AS ( SELECT [object_id], - [database_id], - [schema_name], - SUM(forwarded_fetch_count) AS forwarded_fetch_count, - SUM(leaf_delete_count) AS leaf_delete_count - FROM #IndexPartitionSanity - GROUP BY [object_id], - [database_id], - [schema_name] - HAVING SUM(forwarded_fetch_count) > 0) - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 43 AS check_id, - i.index_sanity_id, - 100 AS Priority, - N'Self Loathing Indexes' AS findings_group, - N'Heaps with Forwarded Fetches' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/SelfLoathing' AS URL, - CASE WHEN h.forwarded_fetch_count >= 922337203685477 THEN '>= 922,337,203,685,477' - WHEN @DaysUptime < 1 THEN CAST(h.forwarded_fetch_count AS NVARCHAR(256)) + N' forwarded fetches against heap: ' + db_schema_object_indexid - ELSE REPLACE(CONVERT(NVARCHAR(256),CAST(CAST( - (h.forwarded_fetch_count /*/@DaysUptime */) - AS BIGINT) AS MONEY), 1), '.00', '') - END + N' forwarded fetches per day against heap: ' - + db_schema_object_indexid AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - sz.index_size_summary - FROM #IndexSanity i - JOIN heaps_cte h ON i.[object_id] = h.[object_id] - AND i.[database_id] = h.[database_id] - AND i.[schema_name] = h.[schema_name] - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.index_id = 0 - AND h.forwarded_fetch_count / @DaysUptime > 1000 - AND sz.total_reserved_MB >= CASE WHEN NOT (@GetAllDatabases = 1 OR @Mode = 4) THEN @ThresholdMB ELSE sz.total_reserved_MB END - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 44: Large Heaps with reads or writes.', 0,1) WITH NOWAIT; - WITH heaps_cte - AS ( SELECT [object_id], - [database_id], - [schema_name], - SUM(forwarded_fetch_count) AS forwarded_fetch_count, - SUM(leaf_delete_count) AS leaf_delete_count - FROM #IndexPartitionSanity - GROUP BY [object_id], - [database_id], - [schema_name] - HAVING SUM(forwarded_fetch_count) > 0 - OR SUM(leaf_delete_count) > 0) - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 44 AS check_id, - i.index_sanity_id, - 100 AS Priority, - N'Self Loathing Indexes' AS findings_group, - N'Large Active Heap' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/SelfLoathing' AS URL, - N'Should this table be a heap? ' + db_schema_object_indexid AS details, - i.index_definition, - 'N/A' AS secret_columns, - i.index_usage_summary, - sz.index_size_summary - FROM #IndexSanity i - LEFT JOIN heaps_cte h ON i.[object_id] = h.[object_id] - AND i.[database_id] = h.[database_id] - AND i.[schema_name] = h.[schema_name] - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.index_id = 0 - AND (i.total_reads > 0 OR i.user_updates > 0) - AND sz.total_rows >= 100000 - AND h.[object_id] IS NULL /*don't duplicate the prior check.*/ - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 45: Medium Heaps with reads or writes.', 0,1) WITH NOWAIT; - WITH heaps_cte - AS ( SELECT [object_id], - [database_id], - [schema_name], - SUM(forwarded_fetch_count) AS forwarded_fetch_count, - SUM(leaf_delete_count) AS leaf_delete_count - FROM #IndexPartitionSanity - GROUP BY [object_id], - [database_id], - [schema_name] - HAVING SUM(forwarded_fetch_count) > 0 - OR SUM(leaf_delete_count) > 0) - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 45 AS check_id, - i.index_sanity_id, - 100 AS Priority, - N'Self Loathing Indexes' AS findings_group, - N'Medium Active heap' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/SelfLoathing' AS URL, - N'Should this table be a heap? ' + db_schema_object_indexid AS details, - i.index_definition, - 'N/A' AS secret_columns, - i.index_usage_summary, - sz.index_size_summary - FROM #IndexSanity i - LEFT JOIN heaps_cte h ON i.[object_id] = h.[object_id] - AND i.[database_id] = h.[database_id] - AND i.[schema_name] = h.[schema_name] - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.index_id = 0 - AND - (i.total_reads > 0 OR i.user_updates > 0) - AND sz.total_rows >= 10000 AND sz.total_rows < 100000 - AND h.[object_id] IS NULL /*don't duplicate the prior check.*/ - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 46: Small Heaps with reads or writes.', 0,1) WITH NOWAIT; - WITH heaps_cte - AS ( SELECT [object_id], - [database_id], - [schema_name], - SUM(forwarded_fetch_count) AS forwarded_fetch_count, - SUM(leaf_delete_count) AS leaf_delete_count - FROM #IndexPartitionSanity - GROUP BY [object_id], - [database_id], - [schema_name] - HAVING SUM(forwarded_fetch_count) > 0 - OR SUM(leaf_delete_count) > 0) - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 46 AS check_id, - i.index_sanity_id, - 100 AS Priority, - N'Self Loathing Indexes' AS findings_group, - N'Small Active heap' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/SelfLoathing' AS URL, - N'Should this table be a heap? ' + db_schema_object_indexid AS details, - i.index_definition, - 'N/A' AS secret_columns, - i.index_usage_summary, - sz.index_size_summary - FROM #IndexSanity i - LEFT JOIN heaps_cte h ON i.[object_id] = h.[object_id] - AND i.[database_id] = h.[database_id] - AND i.[schema_name] = h.[schema_name] - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.index_id = 0 - AND - (i.total_reads > 0 OR i.user_updates > 0) - AND sz.total_rows < 10000 - AND h.[object_id] IS NULL /*don't duplicate the prior check.*/ - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 47: Heap with a Nonclustered Primary Key', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 47 AS check_id, - i.index_sanity_id, - 100 AS Priority, - N'Self Loathing Indexes' AS findings_group, - N'Heap with a Nonclustered Primary Key' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/SelfLoathing' AS URL, - db_schema_object_indexid + N' is a HEAP with a Nonclustered Primary Key' AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - sz.index_size_summary - FROM #IndexSanity i - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.index_type = 2 AND i.is_primary_key = 1 - AND EXISTS - ( - SELECT 1/0 - FROM #IndexSanity AS isa - WHERE i.database_id = isa.database_id - AND i.object_id = isa.object_id - AND isa.index_id = 0 - ) - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 48: Nonclustered indexes with a bad read to write ratio', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 48 AS check_id, - i.index_sanity_id, - 100 AS Priority, - N'Index Hoarder' AS findings_group, - N'NC index with High Writes:Reads' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/IndexHoarder' AS URL, - N'Reads: ' - + REPLACE(CONVERT(NVARCHAR(30), CAST((i.total_reads) AS MONEY), 1), N'.00', N'') - + N' Writes: ' - + REPLACE(CONVERT(NVARCHAR(30), CAST((i.user_updates) AS MONEY), 1), N'.00', N'') - + N' on: ' - + i.db_schema_object_indexid AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - sz.index_size_summary - FROM #IndexSanity i - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.total_reads > 0 /*Not totally unused*/ - AND i.user_updates >= 10000 /*Decent write activity*/ - AND i.total_reads < 10000 - AND ((i.total_reads * 10) < i.user_updates) /*10x more writes than reads*/ - 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 - ORDER BY i.db_schema_object_indexid - OPTION ( RECOMPILE ); - - ---------------------------------------- - --Indexaphobia - --Missing indexes with value >= 5 million: : Check_id 50-59 - ---------------------------------------- - RAISERROR(N'check_id 50: Indexaphobia.', 0,1) WITH NOWAIT; - WITH index_size_cte - AS ( SELECT i.database_id, - i.schema_name, - i.[object_id], - MAX(i.index_sanity_id) AS index_sanity_id, - ISNULL(NULLIF(MAX(DATEDIFF(DAY, i.create_date, SYSDATETIME())), 0), 1) AS create_days, - ISNULL ( - CAST(SUM(CASE WHEN index_id NOT IN (0,1) THEN 1 ELSE 0 END) - AS NVARCHAR(30))+ N' NC indexes exist (' + - CASE WHEN SUM(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) > 1024 - THEN CAST(CAST(SUM(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END )/1024. - - AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'GB); ' - ELSE CAST(SUM(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) - AS NVARCHAR(30)) + N'MB); ' - END + - CASE WHEN MAX(sz.[total_rows]) >= 922337203685477 THEN '>= 922,337,203,685,477' - ELSE REPLACE(CONVERT(NVARCHAR(30),CAST(MAX(sz.[total_rows]) AS MONEY), 1), '.00', '') - END + - + N' Estimated Rows;' - ,N'') AS index_size_summary - FROM #IndexSanity AS i - LEFT JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id AND i.database_id = sz.database_id - WHERE i.is_hypothetical = 0 - AND i.is_disabled = 0 - GROUP BY i.database_id, i.schema_name, i.[object_id]) - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - index_usage_summary, index_size_summary, create_tsql, more_info, sample_query_plan ) - - SELECT check_id, t.index_sanity_id, t.check_id, t.findings_group, t.finding, t.[Database Name], t.URL, t.details, t.[definition], - index_estimated_impact, t.index_size_summary, create_tsql, more_info, sample_query_plan - FROM - ( - SELECT ROW_NUMBER() OVER (ORDER BY magic_benefit_number DESC) AS rownum, - 50 AS check_id, - sz.index_sanity_id, - 40 AS Priority, - N'Indexaphobia' AS findings_group, - N'High Value Missing Index' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/Indexaphobia' AS URL, - mi.[statement] + - N' Est. benefit per day: ' + - CASE WHEN magic_benefit_number >= 922337203685477 THEN '>= 922,337,203,685,477' - ELSE REPLACE(CONVERT(NVARCHAR(256),CAST(CAST( - (magic_benefit_number/@DaysUptime) - AS BIGINT) AS MONEY), 1), '.00', '') - END AS details, - missing_index_details AS [definition], - index_estimated_impact, - sz.index_size_summary, - mi.create_tsql, - mi.more_info, - magic_benefit_number, - mi.is_low, - mi.sample_query_plan - FROM #MissingIndexes mi - LEFT JOIN index_size_cte sz ON mi.[object_id] = sz.object_id - AND mi.database_id = sz.database_id - AND mi.schema_name = sz.schema_name - /* Minimum benefit threshold = 100k/day of uptime OR since table creation date, whichever is lower*/ - WHERE @ShowAllMissingIndexRequests=1 - OR ( @Mode = 4 AND (magic_benefit_number / CASE WHEN sz.create_days < @DaysUptime THEN sz.create_days ELSE @DaysUptime END) >= 100000 ) - OR (magic_benefit_number / CASE WHEN sz.create_days < @DaysUptime THEN sz.create_days ELSE @DaysUptime END) >= 100000 - ) AS t - WHERE t.rownum <= CASE WHEN (@Mode <> 4) THEN 20 ELSE t.rownum END - ORDER BY magic_benefit_number DESC - OPTION ( RECOMPILE ); - - - - RAISERROR(N'check_id 68: Identity columns within 30 percent of the end of range', 0,1) WITH NOWAIT; - -- Allowed Ranges: - --int -2,147,483,648 to 2,147,483,647 - --smallint -32,768 to 32,768 - --tinyint 0 to 255 - - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 68 AS check_id, - i.index_sanity_id, - 80 AS Priority, - N'Abnormal Psychology' AS findings_group, - N'Identity Column Within ' + - CAST (calc1.percent_remaining AS NVARCHAR(256)) - + N' Percent End of Range' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, - i.db_schema_object_name + N'.' + QUOTENAME(ic.column_name) - + N' is an identity with type ' + ic.system_type_name - + N', last value of ' - + ISNULL((CONVERT(NVARCHAR(256),CAST(ic.last_value AS DECIMAL(38,0)), 1)),N'NULL') - + N', seed of ' - + ISNULL((CONVERT(NVARCHAR(256),CAST(ic.seed_value AS DECIMAL(38,0)), 1)),N'NULL') - + N', increment of ' + CAST(ic.increment_value AS NVARCHAR(256)) - + N', and range of ' + - CASE ic.system_type_name WHEN 'int' THEN N'+/- 2,147,483,647' - WHEN 'smallint' THEN N'+/- 32,768' - WHEN 'tinyint' THEN N'0 to 255' - ELSE 'unknown' - END - AS details, - i.index_definition, - secret_columns, - ISNULL(i.index_usage_summary,''), - ISNULL(ip.index_size_summary,'') - FROM #IndexSanity i - JOIN #IndexColumns ic ON - i.object_id=ic.object_id - AND i.database_id = ic.database_id - AND i.schema_name = ic.schema_name - AND i.index_id IN (0,1) /* heaps and cx only */ - AND ic.is_identity=1 - AND ic.system_type_name IN ('tinyint', 'smallint', 'int') - JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id - CROSS APPLY ( - SELECT CAST(CASE WHEN ic.increment_value >= 0 - THEN - CASE ic.system_type_name - WHEN 'int' THEN (2147483647 - (ISNULL(ic.last_value,ic.seed_value) + ic.increment_value)) / 2147483647.*100 - WHEN 'smallint' THEN (32768 - (ISNULL(ic.last_value,ic.seed_value) + ic.increment_value)) / 32768.*100 - WHEN 'tinyint' THEN ( 255 - (ISNULL(ic.last_value,ic.seed_value) + ic.increment_value)) / 255.*100 - ELSE 999 - END - ELSE --ic.increment_value is negative - CASE ic.system_type_name - WHEN 'int' THEN ABS(-2147483647 - (ISNULL(ic.last_value,ic.seed_value) + ic.increment_value)) / 2147483647.*100 - WHEN 'smallint' THEN ABS(-32768 - (ISNULL(ic.last_value,ic.seed_value) + ic.increment_value)) / 32768.*100 - WHEN 'tinyint' THEN ABS( 0 - (ISNULL(ic.last_value,ic.seed_value) + ic.increment_value)) / 255.*100 - ELSE -1 - END - END AS NUMERIC(5,1)) AS percent_remaining - ) AS calc1 - WHERE i.index_id IN (1,0) - AND calc1.percent_remaining <= 30 - OPTION (RECOMPILE); - - - RAISERROR(N'check_id 72: Columnstore indexes with Trace Flag 834', 0,1) WITH NOWAIT; - IF EXISTS (SELECT * FROM #IndexSanity WHERE index_type IN (5,6)) - AND EXISTS (SELECT * FROM #TraceStatus WHERE TraceFlag = 834 AND status = 1) - BEGIN - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 72 AS check_id, - i.index_sanity_id, - 80 AS Priority, - N'Abnormal Psychology' AS findings_group, - 'Columnstore Indexes with Trace Flag 834' AS finding, - [database_name] AS [Database Name], - N'https://support.microsoft.com/en-us/kb/3210239' AS URL, - i.db_schema_object_indexid AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - ISNULL(sz.index_size_summary,'') AS index_size_summary - FROM #IndexSanity AS i - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.index_type IN (5,6) - OPTION ( RECOMPILE ); - END; - - ---------------------------------------- - --Statistics Info: Check_id 90-99 - ---------------------------------------- - - RAISERROR(N'check_id 90: Outdated 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 ) - SELECT 90 AS check_id, - 90 AS Priority, - 'Functioning Statistaholics' AS findings_group, - 'Statistics Not Updated Recently', - s.database_name, - 'https://www.brentozar.com/go/stats' AS URL, - 'Statistics on this table were last updated ' + - CASE WHEN s.last_statistics_update IS NULL THEN N' NEVER ' - ELSE CONVERT(NVARCHAR(20), s.last_statistics_update) + - ' have had ' + CONVERT(NVARCHAR(100), s.modification_counter) + - ' modifications in that time, which is ' + - CONVERT(NVARCHAR(100), s.percent_modifications) + - '% of the table.' - END 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.last_statistics_update <= CONVERT(DATETIME, GETDATE() - 30) - AND s.percent_modifications >= 10. - AND s.rows >= 10000 - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 91: Statistics with a low 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 91 AS check_id, - 90 AS Priority, - 'Functioning Statistaholics' AS findings_group, - 'Low Sampling Rates', - s.database_name, - 'https://www.brentozar.com/go/stats' AS URL, - 'Only ' + CONVERT(NVARCHAR(100), s.percent_sampled) + '% of the rows were sampled during the last statistics update. This may lead to poor cardinality estimates.' 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.rows BETWEEN 10000 AND 1000000 AND s.percent_sampled < 10) - OR (s.rows > 1000000 AND s.percent_sampled < 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 ) - SELECT 92 AS check_id, - 90 AS Priority, - 'Functioning Statistaholics' AS findings_group, - 'Statistics With NO RECOMPUTE', - s.database_name, - 'https://www.brentozar.com/go/stats' AS URL, - 'The statistic ' + QUOTENAME(s.statistics_name) + ' is set to not recompute. This can be helpful if data is really skewed, but harmful if you expect automatic statistics updates.' 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.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 ) - SELECT 94 AS check_id, - 100 AS Priority, - 'Serial Forcer' AS findings_group, - 'Check Constraint with Scalar UDF' AS finding, - cc.database_name, - 'https://www.brentozar.com/go/computedscalar' AS URL, - 'The check constraint ' + QUOTENAME(cc.constraint_name) + ' on ' + QUOTENAME(cc.schema_name) + '.' + QUOTENAME(cc.table_name) + ' is based on ' + cc.definition - + '. That indicates it may reference a scalar function, or a CLR function with data access, which can cause all queries and maintenance to run serially.' AS details, - cc.column_definition, - 'N/A' AS secret_columns, - 'N/A' AS index_usage_summary, - 'N/A' AS index_size_summary - FROM #CheckConstraints AS cc - WHERE cc.is_function = 1 - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 99: Computed Columns 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 ) - SELECT 99 AS check_id, - 100 AS Priority, - 'Serial Forcer' AS findings_group, - 'Computed Column with Scalar UDF' AS finding, - cc.database_name, - 'https://www.brentozar.com/go/serialudf' AS URL, - 'The computed column ' + QUOTENAME(cc.column_name) + ' on ' + QUOTENAME(cc.schema_name) + '.' + QUOTENAME(cc.table_name) + ' is based on ' + cc.definition - + '. That indicates it may reference a scalar function, or a CLR function with data access, which can cause all queries and maintenance to run serially.' AS details, - cc.column_definition, - 'N/A' AS secret_columns, - 'N/A' AS index_usage_summary, - 'N/A' AS index_size_summary - FROM #ComputedColumns AS cc - WHERE cc.is_function = 1 - OPTION ( RECOMPILE ); - - - - END /* IF @Mode IN (0, 4) DIAGNOSE priorities 1-100 */ - - - - - - - - - IF @Mode = 4 /* DIAGNOSE*/ - BEGIN; - RAISERROR(N'@Mode=4, running rules for priorities 101+.', 0,1) WITH NOWAIT; - - RAISERROR(N'check_id 21: More Than 5 Percent NC Indexes Are Unused', 0,1) WITH NOWAIT; - DECLARE @percent_NC_indexes_unused NUMERIC(29,1); - DECLARE @NC_indexes_unused_reserved_MB NUMERIC(29,1); - - SELECT @percent_NC_indexes_unused = ( 100.00 * SUM(CASE - WHEN total_reads = 0 - THEN 1 - ELSE 0 - END) ) / COUNT(*), - @NC_indexes_unused_reserved_MB = SUM(CASE - WHEN total_reads = 0 - THEN sz.total_reserved_MB - ELSE 0 - END) - FROM #IndexSanity i - 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()) - 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, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 21 AS check_id, - MAX(i.index_sanity_id) AS index_sanity_id, - 150 AS Priority, - N'Index Hoarder' AS findings_group, - N'More Than 5 Percent NC Indexes Are Unused' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/IndexHoarder' AS URL, - CAST (@percent_NC_indexes_unused AS NVARCHAR(30)) + N' percent NC indexes (' + CAST(COUNT(*) AS NVARCHAR(10)) + N') unused. ' + - N'These take up ' + CAST (@NC_indexes_unused_reserved_MB AS NVARCHAR(30)) + N'MB of space.' AS details, - i.database_name + ' (' + CAST (COUNT(*) AS NVARCHAR(30)) + N' indexes)' AS index_definition, - '' AS secret_columns, - CAST(SUM(total_reads) AS NVARCHAR(256)) + N' reads (ALL); ' - + CAST(SUM([user_updates]) AS NVARCHAR(256)) + N' writes (ALL)' AS index_usage_summary, - - REPLACE(CONVERT(NVARCHAR(30),CAST(MAX([total_rows]) AS MONEY), 1), '.00', '') + N' rows (MAX)' - + CASE WHEN SUM(total_reserved_MB) > 1024 THEN - N'; ' + CAST(CAST(SUM(total_reserved_MB)/1024. AS NUMERIC(29,1)) AS NVARCHAR(30)) + 'GB (ALL)' - WHEN SUM(total_reserved_MB) > 0 THEN - N'; ' + CAST(CAST(SUM(total_reserved_MB) AS NUMERIC(29,1)) AS NVARCHAR(30)) + 'MB (ALL)' - ELSE '' - END AS index_size_summary - FROM #IndexSanity i - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - 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()) - GROUP BY i.database_name - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 23: Indexes with 7 or more columns. (Borderline)', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 23 AS check_id, - i.index_sanity_id, - 150 AS Priority, - N'Index Hoarder' AS findings_group, - N'Borderline: Wide Indexes (7 or More Columns)' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/IndexHoarder' AS URL, - CAST(count_key_columns + count_included_columns AS NVARCHAR(10)) + ' columns on ' - + i.db_schema_object_indexid AS details, i.index_definition, - i.secret_columns, - i.index_usage_summary, - sz.index_size_summary - FROM #IndexSanity AS i - JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id - WHERE ( count_key_columns + count_included_columns ) >= 7 - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 24: Wide clustered indexes (> 3 columns or > 16 bytes).', 0,1) WITH NOWAIT; - WITH count_columns AS ( - SELECT database_id, [object_id], - SUM(CASE max_length WHEN -1 THEN 0 ELSE max_length END) AS sum_max_length - FROM #IndexColumns ic - WHERE index_id IN (1,0) /*Heap or clustered only*/ - AND key_ordinal > 0 - GROUP BY database_id, object_id - ) - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 24 AS check_id, - i.index_sanity_id, - 150 AS Priority, - N'Index Hoarder' AS findings_group, - N'Wide Clustered Index (> 3 columns OR > 16 bytes)' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/IndexHoarder' AS URL, - CAST (i.count_key_columns AS NVARCHAR(10)) + N' columns with potential size of ' - + CAST(cc.sum_max_length AS NVARCHAR(10)) - + N' bytes in clustered index:' + i.db_schema_object_name - + N'. ' + - (SELECT CAST(COUNT(*) AS NVARCHAR(23)) - FROM #IndexSanity i2 - WHERE i2.[object_id]=i.[object_id] - AND i2.database_id = i.database_id - AND i2.index_id <> 1 - AND i2.is_disabled=0 - AND i2.is_hypothetical=0) - + N' NC indexes on the table.' - AS details, - i.index_definition, - secret_columns, - i.index_usage_summary, - ip.index_size_summary - FROM #IndexSanity i - JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id - JOIN count_columns AS cc ON i.[object_id]=cc.[object_id] - AND i.database_id = cc.database_id - WHERE index_id = 1 /* clustered only */ - AND - (count_key_columns > 3 /*More than three key columns.*/ - OR cc.sum_max_length > 16 /*More than 16 bytes in key */) - AND i.is_CX_columnstore = 0 - ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 25: Addicted to nullable columns.', 0,1) WITH NOWAIT; - WITH count_columns AS ( - SELECT [object_id], - [database_id], - [schema_name], - SUM(CASE is_nullable WHEN 1 THEN 0 ELSE 1 END) AS non_nullable_columns, - COUNT(*) AS total_columns - FROM #IndexColumns ic - WHERE index_id IN (1,0) /*Heap or clustered only*/ - GROUP BY [object_id], - [database_id], - [schema_name] - ) - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 25 AS check_id, - i.index_sanity_id, - 200 AS Priority, - N'Index Hoarder' AS findings_group, - N'Addicted to Nulls' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/IndexHoarder' AS URL, - i.db_schema_object_name - + N' allows null in ' + CAST((total_columns-non_nullable_columns) AS NVARCHAR(10)) - + N' of ' + CAST(total_columns AS NVARCHAR(10)) - + N' columns.' AS details, - i.index_definition, - secret_columns, - ISNULL(i.index_usage_summary,''), - ISNULL(ip.index_size_summary,'') - FROM #IndexSanity i - JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id - JOIN count_columns AS cc ON i.[object_id]=cc.[object_id] - AND cc.database_id = ip.database_id - AND cc.[schema_name] = ip.[schema_name] - WHERE i.index_id IN (1,0) - AND cc.non_nullable_columns < 2 - AND cc.total_columns > 3 - ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 26: Wide tables (35+ cols or > 2000 non-LOB bytes).', 0,1) WITH NOWAIT; - WITH count_columns AS ( - SELECT [object_id], - [database_id], - [schema_name], - SUM(CASE max_length WHEN -1 THEN 1 ELSE 0 END) AS count_lob_columns, - SUM(CASE max_length WHEN -1 THEN 0 ELSE max_length END) AS sum_max_length, - COUNT(*) AS total_columns - FROM #IndexColumns ic - WHERE index_id IN (1,0) /*Heap or clustered only*/ - GROUP BY [object_id], - [database_id], - [schema_name] - ) - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 26 AS check_id, - i.index_sanity_id, - 150 AS Priority, - N'Index Hoarder' AS findings_group, - N'Wide Tables: 35+ cols or > 2000 non-LOB bytes' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/IndexHoarder' AS URL, - i.db_schema_object_name - + N' has ' + CAST((total_columns) AS NVARCHAR(10)) - + N' total columns with a max possible width of ' + CAST(sum_max_length AS NVARCHAR(10)) - + N' bytes.' + - CASE WHEN count_lob_columns > 0 THEN CAST((count_lob_columns) AS NVARCHAR(10)) - + ' columns are LOB types.' ELSE '' - END - AS details, - i.index_definition, - secret_columns, - ISNULL(i.index_usage_summary,''), - ISNULL(ip.index_size_summary,'') - FROM #IndexSanity i - JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id - JOIN count_columns AS cc ON i.[object_id]=cc.[object_id] - AND cc.database_id = i.database_id - AND cc.[schema_name] = i.[schema_name] - WHERE i.index_id IN (1,0) - AND - (cc.total_columns >= 35 OR - cc.sum_max_length >= 2000) - ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 27: Addicted to strings.', 0,1) WITH NOWAIT; - WITH count_columns AS ( - SELECT [object_id], - [database_id], - [schema_name], - SUM(CASE WHEN system_type_name IN ('varchar','nvarchar','char') OR max_length=-1 THEN 1 ELSE 0 END) AS string_or_LOB_columns, - COUNT(*) AS total_columns - FROM #IndexColumns ic - WHERE index_id IN (1,0) /*Heap or clustered only*/ - GROUP BY [object_id], - [database_id], - [schema_name] - ) - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 27 AS check_id, - i.index_sanity_id, - 200 AS Priority, - N'Index Hoarder' AS findings_group, - N'Addicted to strings' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/IndexHoarder' AS URL, - i.db_schema_object_name - + N' uses string or LOB types for ' + CAST((string_or_LOB_columns) AS NVARCHAR(10)) - + N' of ' + CAST(total_columns AS NVARCHAR(10)) - + N' columns. Check if data types are valid.' AS details, - i.index_definition, - secret_columns, - ISNULL(i.index_usage_summary,''), - ISNULL(ip.index_size_summary,'') - FROM #IndexSanity i - JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id - JOIN count_columns AS cc ON i.[object_id]=cc.[object_id] - AND cc.database_id = i.database_id - AND cc.[schema_name] = i.[schema_name] - CROSS APPLY (SELECT cc.total_columns - string_or_LOB_columns AS non_string_or_lob_columns) AS calc1 - WHERE i.index_id IN (1,0) - AND calc1.non_string_or_lob_columns <= 1 - AND cc.total_columns > 3 - ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 28: Non-unique clustered index.', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 28 AS check_id, - i.index_sanity_id, - 150 AS Priority, - N'Index Hoarder' AS findings_group, - N'Non-Unique Clustered Index' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/IndexHoarder' AS URL, - N'Uniquifiers will be required! Clustered index: ' + i.db_schema_object_name - + N' and all NC indexes. ' + - (SELECT CAST(COUNT(*) AS NVARCHAR(23)) - FROM #IndexSanity i2 - WHERE i2.[object_id]=i.[object_id] - AND i2.database_id = i.database_id - AND i2.index_id <> 1 - AND i2.is_disabled=0 - AND i2.is_hypothetical=0) - + N' NC indexes on the table.' - AS details, - i.index_definition, - secret_columns, - i.index_usage_summary, - ip.index_size_summary - FROM #IndexSanity i - JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id - WHERE index_id = 1 /* clustered only */ - AND is_unique=0 /* not unique */ - AND is_CX_columnstore=0 /* not a clustered columnstore-- no unique option on those */ - ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 29: NC indexes with 0 reads. (Borderline) and < 10,000 writes', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 29 AS check_id, - i.index_sanity_id, - 150 AS Priority, - N'Index Hoarder' AS findings_group, - N'Unused NC index with Low Writes' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/IndexHoarder' AS URL, - N'0 reads: ' + i.db_schema_object_indexid AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - sz.index_size_summary - FROM #IndexSanity AS i - JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.total_reads=0 - AND i.user_updates < 10000 - 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()) - ORDER BY i.db_schema_object_indexid - OPTION ( RECOMPILE ); - - - ---------------------------------------- - --Feature-Phobic Indexes: Check_id 30-39 - ---------------------------------------- - RAISERROR(N'check_id 30: No indexes with includes', 0,1) WITH NOWAIT; - /* This does not work the way you'd expect with @GetAllDatabases = 1. For details: - https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/825 - */ - - SELECT database_name, - SUM(CASE WHEN count_included_columns > 0 THEN 1 ELSE 0 END) AS number_indexes_with_includes, - 100.* SUM(CASE WHEN count_included_columns > 0 THEN 1 ELSE 0 END) / ( 1.0 * COUNT(*) ) AS percent_indexes_with_includes - INTO #index_includes - FROM #IndexSanity - WHERE is_hypothetical = 0 - AND is_disabled = 0 - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) - GROUP BY database_name; - - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 30 AS check_id, - NULL AS index_sanity_id, - 250 AS Priority, - N'Feature-Phobic Indexes' AS findings_group, - database_name AS [Database Name], - N'No Indexes Use Includes' AS finding, 'https://www.brentozar.com/go/IndexFeatures' AS URL, - N'No Indexes Use Includes' AS details, - database_name + N' (Entire database)' AS index_definition, - N'' AS secret_columns, - N'N/A' AS index_usage_summary, - N'N/A' AS index_size_summary - FROM #index_includes - WHERE number_indexes_with_includes = 0 - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 31: < 3 percent of indexes have includes', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 31 AS check_id, - NULL AS index_sanity_id, - 250 AS Priority, - N'Feature-Phobic Indexes' AS findings_group, - N'Few Indexes Use Includes' AS findings, - database_name AS [Database Name], - N'https://www.brentozar.com/go/IndexFeatures' AS URL, - N'Only ' + CAST(percent_indexes_with_includes AS NVARCHAR(20)) + '% of indexes have includes' AS details, - N'Entire database' AS index_definition, - N'' AS secret_columns, - N'N/A' AS index_usage_summary, - N'N/A' AS index_size_summary - FROM #index_includes - WHERE number_indexes_with_includes > 0 AND percent_indexes_with_includes <= 3 - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 32: filtered indexes and indexed views', 0,1) WITH NOWAIT; - - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT DISTINCT - 32 AS check_id, - NULL AS index_sanity_id, - 250 AS Priority, - N'Feature-Phobic Indexes' AS findings_group, - N'No Filtered Indexes or Indexed Views' AS finding, - i.database_name AS [Database Name], - N'https://www.brentozar.com/go/IndexFeatures' AS URL, - N'These are NOT always needed-- but do you know when you would use them?' AS details, - i.database_name + N' (Entire database)' AS index_definition, - N'' AS secret_columns, - N'N/A' AS index_usage_summary, - N'N/A' AS index_size_summary - FROM #IndexSanity i - WHERE i.database_name NOT IN ( - SELECT database_name - FROM #IndexSanity - WHERE filter_definition <> '' ) - AND i.database_name NOT IN ( - SELECT database_name - FROM #IndexSanity - WHERE is_indexed_view = 1 ) - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 33: Potential filtered indexes based on column names.', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 33 AS check_id, - i.index_sanity_id AS index_sanity_id, - 250 AS Priority, - N'Feature-Phobic Indexes' AS findings_group, - N'Potential Filtered Index (Based on Column Name)' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/IndexFeatures' AS URL, - N'A column name in this index suggests it might be a candidate for filtering (is%, %archive%, %active%, %flag%)' AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - sz.index_size_summary - FROM #IndexColumns ic - JOIN #IndexSanity i ON ic.[object_id]=i.[object_id] - AND ic.database_id =i.database_id - AND ic.schema_name = i.schema_name - AND ic.[index_id]=i.[index_id] - AND i.[index_id] > 1 /* non-clustered index */ - JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id - WHERE (column_name LIKE 'is%' - OR column_name LIKE '%archive%' - OR column_name LIKE '%active%' - OR column_name LIKE '%flag%') - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 41: Hypothetical indexes ', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 41 AS check_id, - i.index_sanity_id, - 150 AS Priority, - N'Self Loathing Indexes' AS findings_group, - N'Hypothetical Index' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/SelfLoathing' AS URL, - N'Hypothetical Index: ' + db_schema_object_indexid AS details, - i.index_definition, - i.secret_columns, - N'' AS index_usage_summary, - N'' AS index_size_summary - FROM #IndexSanity AS i - WHERE is_hypothetical = 1 - OPTION ( RECOMPILE ); - - - RAISERROR(N'check_id 42: Disabled indexes', 0,1) WITH NOWAIT; - --Note: disabled NC indexes will have O rows in #IndexSanitySize! - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 42 AS check_id, - index_sanity_id, - 150 AS Priority, - N'Self Loathing Indexes' AS findings_group, - N'Disabled Index' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/SelfLoathing' AS URL, - N'Disabled Index:' + db_schema_object_indexid AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - 'DISABLED' AS index_size_summary - FROM #IndexSanity AS i - WHERE is_disabled = 1 - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 49: Heaps with deletes', 0,1) WITH NOWAIT; - WITH heaps_cte - AS ( SELECT [object_id], - [database_id], - [schema_name], - SUM(leaf_delete_count) AS leaf_delete_count - FROM #IndexPartitionSanity - GROUP BY [object_id], - [database_id], - [schema_name] - HAVING SUM(forwarded_fetch_count) < 1000 * @DaysUptime /* Only alert about indexes with no forwarded fetches - we already alerted about those in check_id 43 */ - AND SUM(leaf_delete_count) > 0) - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 49 AS check_id, - i.index_sanity_id, - 200 AS Priority, - N'Self Loathing Indexes' AS findings_group, - N'Heaps with Deletes' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/SelfLoathing' AS URL, - CAST(h.leaf_delete_count AS NVARCHAR(256)) + N' deletes against heap:' - + db_schema_object_indexid AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - sz.index_size_summary - FROM #IndexSanity i - JOIN heaps_cte h ON i.[object_id] = h.[object_id] - AND i.[database_id] = h.[database_id] - AND i.[schema_name] = h.[schema_name] - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.index_id = 0 - AND sz.total_reserved_MB >= CASE WHEN NOT (@GetAllDatabases = 1 OR @Mode = 4) THEN @ThresholdMB ELSE sz.total_reserved_MB END - OPTION ( RECOMPILE ); - - ---------------------------------------- - --Abnormal Psychology : Check_id 60-79 - ---------------------------------------- - RAISERROR(N'check_id 60: XML indexes', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 60 AS check_id, - i.index_sanity_id, - 150 AS Priority, - N'Abnormal Psychology' AS findings_group, - N'XML Index' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, - i.db_schema_object_indexid AS details, - i.index_definition, - i.secret_columns, - N'' AS index_usage_summary, - ISNULL(sz.index_size_summary,'') AS index_size_summary - FROM #IndexSanity AS i - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.is_XML = 1 - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 61: Columnstore indexes', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 61 AS check_id, - i.index_sanity_id, - 150 AS Priority, - N'Abnormal Psychology' AS findings_group, - CASE WHEN i.is_NC_columnstore=1 - THEN N'NC Columnstore Index' - ELSE N'Clustered Columnstore Index' - END AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, - i.db_schema_object_indexid AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - ISNULL(sz.index_size_summary,'') AS index_size_summary - FROM #IndexSanity AS i - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.is_NC_columnstore = 1 OR i.is_CX_columnstore=1 - OPTION ( RECOMPILE ); - - - RAISERROR(N'check_id 62: Spatial indexes', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 62 AS check_id, - i.index_sanity_id, - 150 AS Priority, - N'Abnormal Psychology' AS findings_group, - N'Spatial Index' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, - i.db_schema_object_indexid AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - ISNULL(sz.index_size_summary,'') AS index_size_summary - FROM #IndexSanity AS i - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.is_spatial = 1 - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 63: Compressed indexes', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 63 AS check_id, - i.index_sanity_id, - 150 AS Priority, - N'Abnormal Psychology' AS findings_group, - N'Compressed Index' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, - i.db_schema_object_indexid + N'. COMPRESSION: ' + sz.data_compression_desc AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - ISNULL(sz.index_size_summary,'') AS index_size_summary - FROM #IndexSanity AS i - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE sz.data_compression_desc LIKE '%PAGE%' OR sz.data_compression_desc LIKE '%ROW%' - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 64: Partitioned', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 64 AS check_id, - i.index_sanity_id, - 150 AS Priority, - N'Abnormal Psychology' AS findings_group, - N'Partitioned Index' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, - i.db_schema_object_indexid AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - ISNULL(sz.index_size_summary,'') AS index_size_summary - FROM #IndexSanity AS i - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.partition_key_column_name IS NOT NULL - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 65: Non-Aligned Partitioned', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 65 AS check_id, - i.index_sanity_id, - 150 AS Priority, - N'Abnormal Psychology' AS findings_group, - N'Non-Aligned Index on a Partitioned Table' AS finding, - i.[database_name] AS [Database Name], - N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, - i.db_schema_object_indexid AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - ISNULL(sz.index_size_summary,'') AS index_size_summary - FROM #IndexSanity AS i - JOIN #IndexSanity AS iParent ON - i.[object_id]=iParent.[object_id] - AND i.database_id = iParent.database_id - AND i.schema_name = iParent.schema_name - AND iParent.index_id IN (0,1) /* could be a partitioned heap or clustered table */ - AND iParent.partition_key_column_name IS NOT NULL /* parent is partitioned*/ - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.partition_key_column_name IS NULL - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 66: Recently created tables/indexes (1 week)', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 66 AS check_id, - i.index_sanity_id, - 200 AS Priority, - N'Abnormal Psychology' AS findings_group, - N'Recently Created Tables/Indexes (1 week)' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, - i.db_schema_object_indexid + N' was created on ' + - CONVERT(NVARCHAR(16),i.create_date,121) + - N'. Tables/indexes which are dropped/created regularly require special methods for index tuning.' - AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - ISNULL(sz.index_size_summary,'') AS index_size_summary - FROM #IndexSanity AS i - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.create_date >= DATEADD(dd,-7,GETDATE()) - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 67: Recently modified tables/indexes (2 days)', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 67 AS check_id, - i.index_sanity_id, - 200 AS Priority, - N'Abnormal Psychology' AS findings_group, - N'Recently Modified Tables/Indexes (2 days)' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, - i.db_schema_object_indexid + N' was modified on ' + - CONVERT(NVARCHAR(16),i.modify_date,121) + - N'. A large amount of recently modified indexes may mean a lot of rebuilds are occurring each night.' - AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - ISNULL(sz.index_size_summary,'') AS index_size_summary - FROM #IndexSanity AS i - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.modify_date > DATEADD(dd,-2,GETDATE()) - AND /*Exclude recently created tables.*/ - i.create_date < DATEADD(dd,-7,GETDATE()) - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 69: Column collation does not match database collation', 0,1) WITH NOWAIT; - WITH count_columns AS ( - SELECT [object_id], - database_id, - schema_name, - COUNT(*) AS column_count - FROM #IndexColumns ic - WHERE index_id IN (1,0) /*Heap or clustered only*/ - AND collation_name <> @collation - GROUP BY [object_id], - database_id, - schema_name - ) - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 69 AS check_id, - i.index_sanity_id, - 150 AS Priority, - N'Abnormal Psychology' AS findings_group, - N'Column Collation Does Not Match Database Collation' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, - i.db_schema_object_name - + N' has ' + CAST(column_count AS NVARCHAR(20)) - + N' column' + CASE WHEN column_count > 1 THEN 's' ELSE '' END - + N' with a different collation than the db collation of ' - + @collation AS details, - i.index_definition, - secret_columns, - ISNULL(i.index_usage_summary,''), - ISNULL(ip.index_size_summary,'') - FROM #IndexSanity i - JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id - JOIN count_columns AS cc ON i.[object_id]=cc.[object_id] - AND cc.database_id = i.database_id - AND cc.schema_name = i.schema_name - WHERE i.index_id IN (1,0) - ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 70: Replicated columns', 0,1) WITH NOWAIT; - WITH count_columns AS ( - SELECT [object_id], - database_id, - schema_name, - COUNT(*) AS column_count, - SUM(CASE is_replicated WHEN 1 THEN 1 ELSE 0 END) AS replicated_column_count - FROM #IndexColumns ic - WHERE index_id IN (1,0) /*Heap or clustered only*/ - GROUP BY object_id, - database_id, - schema_name - ) - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 70 AS check_id, - i.index_sanity_id, - 200 AS Priority, - N'Abnormal Psychology' AS findings_group, - N'Replicated Columns' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, - i.db_schema_object_name - + N' has ' + CAST(replicated_column_count AS NVARCHAR(20)) - + N' out of ' + CAST(column_count AS NVARCHAR(20)) - + N' column' + CASE WHEN column_count > 1 THEN 's' ELSE '' END - + N' in one or more publications.' - AS details, - i.index_definition, - secret_columns, - ISNULL(i.index_usage_summary,''), - ISNULL(ip.index_size_summary,'') - FROM #IndexSanity i - JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id - JOIN count_columns AS cc ON i.[object_id]=cc.[object_id] - AND i.database_id = cc.database_id - AND i.schema_name = cc.schema_name - WHERE i.index_id IN (1,0) - AND replicated_column_count > 0 - ORDER BY i.db_schema_object_name DESC - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 71: Cascading updates or cascading deletes.', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary, more_info ) - SELECT 71 AS check_id, - NULL AS index_sanity_id, - 150 AS Priority, - N'Abnormal Psychology' AS findings_group, - N'Cascading Updates or Deletes' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, - N'Foreign Key ' + QUOTENAME(foreign_key_name) + - N' on ' + QUOTENAME(parent_object_name) + N'(' + LTRIM(parent_fk_columns) + N')' - + N' referencing ' + QUOTENAME(referenced_object_name) + N'(' + LTRIM(referenced_fk_columns) + N')' - + N' has settings:' - + CASE [delete_referential_action_desc] WHEN N'NO_ACTION' THEN N'' ELSE N' ON DELETE ' +[delete_referential_action_desc] END - + CASE [update_referential_action_desc] WHEN N'NO_ACTION' THEN N'' ELSE N' ON UPDATE ' + [update_referential_action_desc] END - AS details, - [fk].[database_name] AS index_definition, - N'N/A' AS secret_columns, - N'N/A' AS index_usage_summary, - N'N/A' AS index_size_summary, - (SELECT TOP 1 more_info FROM #IndexSanity i WHERE i.object_id=fk.parent_object_id AND i.database_id = fk.database_id AND i.schema_name = fk.schema_name) - AS more_info - FROM #ForeignKeys fk - WHERE ([delete_referential_action_desc] <> N'NO_ACTION' - OR [update_referential_action_desc] <> N'NO_ACTION') - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 72: Unindexed foreign keys.', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary, more_info ) - SELECT 72 AS check_id, - NULL AS index_sanity_id, - 150 AS Priority, - N'Abnormal Psychology' AS findings_group, - N'Unindexed Foreign Keys' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, - N'Foreign Key ' + QUOTENAME(foreign_key_name) + - N' on ' + QUOTENAME(parent_object_name) + N'' - + N' referencing ' + QUOTENAME(referenced_object_name) + N'' - + N' does not appear to have a supporting index.' AS details, - N'N/A' AS index_definition, - N'N/A' AS secret_columns, - N'N/A' AS index_usage_summary, - N'N/A' AS index_size_summary, - (SELECT TOP 1 more_info FROM #IndexSanity i WHERE i.object_id=fk.parent_object_id AND i.database_id = fk.database_id AND i.schema_name = fk.schema_name) - AS more_info - FROM #UnindexedForeignKeys AS fk - OPTION ( RECOMPILE ); - - - RAISERROR(N'check_id 73: In-Memory OLTP', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 73 AS check_id, - i.index_sanity_id, - 150 AS Priority, - N'Abnormal Psychology' AS findings_group, - N'In-Memory OLTP' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, - i.db_schema_object_indexid AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - ISNULL(sz.index_size_summary,'') AS index_size_summary - FROM #IndexSanity AS i - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.is_in_memory_oltp = 1 - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 74: Identity column with unusual seed', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 74 AS check_id, - i.index_sanity_id, - 200 AS Priority, - N'Abnormal Psychology' AS findings_group, - N'Identity Column Using a Negative Seed or Increment Other Than 1' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, - i.db_schema_object_name + N'.' + QUOTENAME(ic.column_name) - + N' is an identity with type ' + ic.system_type_name - + N', last value of ' - + ISNULL((CONVERT(NVARCHAR(256),CAST(ic.last_value AS DECIMAL(38,0)), 1)),N'NULL') - + N', seed of ' - + ISNULL((CONVERT(NVARCHAR(256),CAST(ic.seed_value AS DECIMAL(38,0)), 1)),N'NULL') - + N', increment of ' + CAST(ic.increment_value AS NVARCHAR(256)) - + N', and range of ' + - CASE ic.system_type_name WHEN 'int' THEN N'+/- 2,147,483,647' - WHEN 'smallint' THEN N'+/- 32,768' - WHEN 'tinyint' THEN N'0 to 255' - ELSE 'unknown' - END - AS details, - i.index_definition, - secret_columns, - ISNULL(i.index_usage_summary,''), - ISNULL(ip.index_size_summary,'') - FROM #IndexSanity i - JOIN #IndexColumns ic ON - i.object_id=ic.object_id - AND i.database_id = ic.database_id - AND i.schema_name = ic.schema_name - AND i.index_id IN (0,1) /* heaps and cx only */ - AND ic.is_identity=1 - AND ic.system_type_name IN ('tinyint', 'smallint', 'int') - JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id - WHERE i.index_id IN (1,0) - AND (ic.seed_value < 0 OR ic.increment_value <> 1) - ORDER BY finding, details DESC - OPTION ( RECOMPILE ); - - ---------------------------------------- - --Workaholics: Check_id 80-89 - ---------------------------------------- - - RAISERROR(N'check_id 80: Most scanned indexes (index_usage_stats)', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - - --Workaholics according to index_usage_stats - --This isn't perfect: it mentions the number of scans present in a plan - --A "scan" isn't necessarily a full scan, but hey, we gotta do the best with what we've got. - --in the case of things like indexed views, the operator might be in the plan but never executed - SELECT TOP 5 - 80 AS check_id, - i.index_sanity_id AS index_sanity_id, - 200 AS Priority, - N'Workaholics' AS findings_group, - N'Scan-a-lots (index-usage-stats)' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/Workaholics' AS URL, - REPLACE(CONVERT( NVARCHAR(50),CAST(i.user_scans AS MONEY),1),'.00','') - + N' scans against ' + i.db_schema_object_indexid - + N'. Latest scan: ' + ISNULL(CAST(i.last_user_scan AS NVARCHAR(128)),'?') + N'. ' - + N'ScanFactor=' + CAST(((i.user_scans * iss.total_reserved_MB)/1000000.) AS NVARCHAR(256)) AS details, - ISNULL(i.key_column_names_with_sort_order,'N/A') AS index_definition, - ISNULL(i.secret_columns,'') AS secret_columns, - i.index_usage_summary AS index_usage_summary, - iss.index_size_summary AS index_size_summary - FROM #IndexSanity i - JOIN #IndexSanitySize iss ON i.index_sanity_id=iss.index_sanity_id - WHERE ISNULL(i.user_scans,0) > 0 - ORDER BY i.user_scans * iss.total_reserved_MB DESC - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 81: Top recent accesses (op stats)', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - --Workaholics according to index_operational_stats - --This isn't perfect either: range_scan_count contains full scans, partial scans, even seeks in nested loop ops - --But this can help bubble up some most-accessed tables - SELECT TOP 5 - 81 AS check_id, - i.index_sanity_id AS index_sanity_id, - 200 AS Priority, - N'Workaholics' AS findings_group, - N'Top Recent Accesses (index-op-stats)' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/Workaholics' AS URL, - ISNULL(REPLACE( - CONVERT(NVARCHAR(50),CAST((iss.total_range_scan_count + iss.total_singleton_lookup_count) AS MONEY),1), - N'.00',N'') - + N' uses of ' + i.db_schema_object_indexid + N'. ' - + REPLACE(CONVERT(NVARCHAR(50), CAST(iss.total_range_scan_count AS MONEY),1),N'.00',N'') + N' scans or seeks. ' - + REPLACE(CONVERT(NVARCHAR(50), CAST(iss.total_singleton_lookup_count AS MONEY), 1),N'.00',N'') + N' singleton lookups. ' - + N'OpStatsFactor=' + CAST(((((iss.total_range_scan_count + iss.total_singleton_lookup_count) * iss.total_reserved_MB))/1000000.) AS VARCHAR(256)),'') AS details, - ISNULL(i.key_column_names_with_sort_order,'N/A') AS index_definition, - ISNULL(i.secret_columns,'') AS secret_columns, - i.index_usage_summary AS index_usage_summary, - iss.index_size_summary AS index_size_summary - FROM #IndexSanity i - JOIN #IndexSanitySize iss ON i.index_sanity_id=iss.index_sanity_id - WHERE (ISNULL(iss.total_range_scan_count,0) > 0 OR ISNULL(iss.total_singleton_lookup_count,0) > 0) - ORDER BY ((iss.total_range_scan_count + iss.total_singleton_lookup_count) * iss.total_reserved_MB) DESC - OPTION ( RECOMPILE ); - - - - RAISERROR(N'check_id 93: Statistics with filters', 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 93 AS check_id, - 200 AS Priority, - 'Functioning Statistaholics' AS findings_group, - 'Filter Fixation', - s.database_name, - 'https://www.brentozar.com/go/stats' AS URL, - 'The statistic ' + QUOTENAME(s.statistics_name) + ' is filtered on [' + s.filter_definition + ']. It could be part of a filtered index, or just a filtered statistic. This is purely informational.' 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_filter = 1 - OPTION ( RECOMPILE ); - - - RAISERROR(N'check_id 100: Computed Columns that are not Persisted.', 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 100 AS check_id, - 200 AS Priority, - 'Cold Calculators' AS findings_group, - 'Definition Defeatists' AS finding, - cc.database_name, - '' AS URL, - 'The computed column ' + QUOTENAME(cc.column_name) + ' on ' + QUOTENAME(cc.schema_name) + '.' + QUOTENAME(cc.table_name) + ' is not persisted, which means it will be calculated when a query runs.' + - 'You can change this with the following command, if the definition is deterministic: ALTER TABLE ' + QUOTENAME(cc.schema_name) + '.' + QUOTENAME(cc.table_name) + ' ALTER COLUMN ' + cc.column_name + - ' ADD PERSISTED' AS details, - cc.column_definition, - 'N/A' AS secret_columns, - 'N/A' AS index_usage_summary, - 'N/A' AS index_size_summary - FROM #ComputedColumns AS cc - WHERE cc.is_persisted = 0 - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 110: Temporal Tables.', 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 110 AS check_id, - 200 AS Priority, - 'Abnormal Psychology' AS findings_group, - 'Temporal Tables', - t.database_name, - '' AS URL, - 'The table ' + QUOTENAME(t.schema_name) + '.' + QUOTENAME(t.table_name) + ' is a temporal table, with rows versioned in ' - + QUOTENAME(t.history_schema_name) + '.' + QUOTENAME(t.history_table_name) + ' on History columns ' + QUOTENAME(t.start_column_name) + ' and ' + QUOTENAME(t.end_column_name) + '.' - AS details, - '' AS index_definition, - 'N/A' AS secret_columns, - 'N/A' AS index_usage_summary, - 'N/A' AS index_size_summary - FROM #TemporalTables AS t - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 121: Optimized For Sequental Keys.', 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 121 AS check_id, - 200 AS Priority, - 'Medicated Indexes' AS findings_group, - 'Optimized For Sequential Keys', - i.database_name, - '' AS URL, - 'The table ' + QUOTENAME(i.schema_name) + '.' + QUOTENAME(i.object_name) + ' is optimized for sequential keys.' AS details, - '' AS index_definition, - 'N/A' AS secret_columns, - 'N/A' AS index_usage_summary, - 'N/A' AS index_size_summary - FROM #IndexSanity AS i - WHERE i.optimize_for_sequential_key = 1 - OPTION ( RECOMPILE ); - - - - END /* IF @Mode = 4 */ - - - - - - - - - RAISERROR(N'Insert a row to help people find help', 0,1) WITH NOWAIT; - IF DATEDIFF(MM, @VersionDate, GETDATE()) > 6 - BEGIN - INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, URL, details, index_definition, - index_usage_summary, index_size_summary ) - VALUES ( -1, 0 , - 'Outdated sp_BlitzIndex', 'sp_BlitzIndex is Over 6 Months Old', 'http://FirstResponderKit.org/', - 'Fine wine gets better with age, but this ' + @ScriptVersionName + ' is more like bad cheese. Time to get a new one.', - @DaysUptimeInsertValue,N'',N'' - ); - END; - - IF EXISTS(SELECT * FROM #BlitzIndexResults) - BEGIN - INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, URL, details, index_definition, - index_usage_summary, index_size_summary ) - VALUES ( -1, 0 , - @ScriptVersionName, - CASE WHEN @GetAllDatabases = 1 THEN N'All Databases' ELSE N'Database ' + QUOTENAME(@DatabaseName) + N' as of ' + CONVERT(NVARCHAR(16),GETDATE(),121) END, - N'From Your Community Volunteers' , N'http://FirstResponderKit.org' , - @DaysUptimeInsertValue,N'',N'' - ); - END; - ELSE IF @Mode = 0 OR (@GetAllDatabases = 1 AND @Mode <> 4) - BEGIN - INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, URL, details, index_definition, - index_usage_summary, index_size_summary ) - VALUES ( -1, 0 , - @ScriptVersionName, - CASE WHEN @GetAllDatabases = 1 THEN N'All Databases' ELSE N'Database ' + QUOTENAME(@DatabaseName) + N' as of ' + CONVERT(NVARCHAR(16),GETDATE(),121) END, - N'From Your Community Volunteers' , N'http://FirstResponderKit.org' , - @DaysUptimeInsertValue, N'',N'' - ); - INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, URL, details, index_definition, - index_usage_summary, index_size_summary ) - VALUES ( 1, 0 , - N'No Major Problems Found', - N'Nice Work!', - N'http://FirstResponderKit.org', - N'Consider running with @Mode = 4 in individual databases (not all) for more detailed diagnostics.', - N'The default Mode 0 only looks for very serious index issues.', - @DaysUptimeInsertValue, N'' - ); - - END; - ELSE - BEGIN - INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, URL, details, index_definition, - index_usage_summary, index_size_summary ) - VALUES ( -1, 0 , - @ScriptVersionName, - CASE WHEN @GetAllDatabases = 1 THEN N'All Databases' ELSE N'Database ' + QUOTENAME(@DatabaseName) + N' as of ' + CONVERT(NVARCHAR(16),GETDATE(),121) END, - N'From Your Community Volunteers' , N'http://FirstResponderKit.org' , - @DaysUptimeInsertValue, N'',N'' - ); - INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, URL, details, index_definition, - index_usage_summary, index_size_summary ) - VALUES ( 1, 0 , - N'No Problems Found', - N'Nice job! Or more likely, you have a nearly empty database.', - N'http://FirstResponderKit.org', 'Time to go read some blog posts.', - @DaysUptimeInsertValue, N'', N'' - ); - - END; - - RAISERROR(N'Returning results.', 0,1) WITH NOWAIT; - - /*Return results.*/ - IF (@ValidOutputLocation = 1 AND COALESCE(@OutputServerName, @OutputDatabaseName, @OutputSchemaName, @OutputTableName) IS NOT NULL) - BEGIN - IF NOT @SchemaExists = 1 - BEGIN - RAISERROR (N'Invalid schema name, data could not be saved.', 16, 0); - RETURN; - END - - IF @TableExists = 0 - BEGIN - SET @StringToExecute = - N'CREATE TABLE @@@OutputDatabaseName@@@.@@@OutputSchemaName@@@.@@@OutputTableName@@@ - ( - [id] INT IDENTITY(1,1) NOT NULL, - [run_id] UNIQUEIDENTIFIER, - [run_datetime] DATETIME, - [server_name] NVARCHAR(128), - [priority] INT, - [finding] NVARCHAR(4000), - [database_name] NVARCHAR(128), - [details] NVARCHAR(MAX), - [index_definition] NVARCHAR(MAX), - [secret_columns] NVARCHAR(MAX), - [index_usage_summary] NVARCHAR(MAX), - [index_size_summary] NVARCHAR(MAX), - [more_info] NVARCHAR(MAX), - [url] NVARCHAR(MAX), - [create_tsql] NVARCHAR(MAX), - [sample_query_plan] XML, - CONSTRAINT [PK_ID_@@@RunID@@@] PRIMARY KEY CLUSTERED ([id] ASC) - );'; - - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputSchemaName@@@', @OutputSchemaName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@RunID@@@', @RunID); - - IF @ValidOutputServer = 1 - BEGIN - SET @StringToExecute = REPLACE(@StringToExecute,'''',''''''); - EXEC('EXEC('''+@StringToExecute+''') AT ' + @OutputServerName); - END; - ELSE - BEGIN - EXEC(@StringToExecute); - END; - END; /* @TableExists = 0 */ - - -- Re-check that table now exists (if not we failed creating it) - SET @TableExists = NULL; - EXEC sp_executesql @TableExistsSql, N'@TableExists BIT OUTPUT', @TableExists OUTPUT; - - IF NOT @TableExists = 1 - BEGIN - RAISERROR('Creation of the output table failed.', 16, 0); - RETURN; - END; - - SET @StringToExecute = - N'INSERT @@@OutputServerName@@@.@@@OutputDatabaseName@@@.@@@OutputSchemaName@@@.@@@OutputTableName@@@ - ( - [run_id], - [run_datetime], - [server_name], - [priority], - [finding], - [database_name], - [details], - [index_definition], - [secret_columns], - [index_usage_summary], - [index_size_summary], - [more_info], - [url], - [create_tsql], - [sample_query_plan] - ) - SELECT - ''@@@RunID@@@'', - ''@@@GETDATE@@@'', - ''@@@LocalServerName@@@'', - -- Below should be a copy/paste of the real query - -- Make sure all quotes are escaped - Priority, ISNULL(br.findings_group,N'''') + - CASE WHEN ISNULL(br.finding,N'''') <> N'''' THEN N'': '' ELSE N'''' END - + br.finding AS [Finding], - br.[database_name] AS [Database Name], - br.details AS [Details: schema.table.index(indexid)], - br.index_definition AS [Definition: [Property]] ColumnName {datatype maxbytes}], - ISNULL(br.secret_columns,'''') AS [Secret Columns], - br.index_usage_summary AS [Usage], - br.index_size_summary AS [Size], - COALESCE(br.more_info,sn.more_info,'''') AS [More Info], - br.URL, - COALESCE(br.create_tsql,ts.create_tsql,'''') AS [Create TSQL], - br.sample_query_plan AS [Sample Query Plan] - FROM #BlitzIndexResults br - LEFT JOIN #IndexSanity sn ON - br.index_sanity_id=sn.index_sanity_id - LEFT JOIN #IndexCreateTsql ts ON - br.index_sanity_id=ts.index_sanity_id - ORDER BY br.Priority ASC, br.check_id ASC, br.blitz_result_id ASC, br.findings_group ASC - OPTION (RECOMPILE);'; - - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputServerName@@@', @OutputServerName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputSchemaName@@@', @OutputSchemaName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@RunID@@@', @RunID); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@GETDATE@@@', GETDATE()); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@LocalServerName@@@', CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128))); - EXEC(@StringToExecute); - - END - ELSE - BEGIN - IF(@OutputType <> 'NONE') - BEGIN - SELECT Priority, ISNULL(br.findings_group,N'') + - CASE WHEN ISNULL(br.finding,N'') <> N'' THEN N': ' ELSE N'' END - + br.finding AS [Finding], - br.[database_name] AS [Database Name], - br.details AS [Details: schema.table.index(indexid)], - br.index_definition AS [Definition: [Property]] ColumnName {datatype maxbytes}], - ISNULL(br.secret_columns,'') AS [Secret Columns], - br.index_usage_summary AS [Usage], - br.index_size_summary AS [Size], - COALESCE(br.more_info,sn.more_info,'') AS [More Info], - br.URL, - COALESCE(br.create_tsql,ts.create_tsql,'') AS [Create TSQL], - br.sample_query_plan AS [Sample Query Plan] - FROM #BlitzIndexResults br - LEFT JOIN #IndexSanity sn ON - br.index_sanity_id=sn.index_sanity_id - LEFT JOIN #IndexCreateTsql ts ON - br.index_sanity_id=ts.index_sanity_id - ORDER BY br.Priority ASC, br.check_id ASC, br.blitz_result_id ASC, br.findings_group ASC - OPTION (RECOMPILE); - END; - - END; - - END /* End @Mode=0 or 4 (diagnose)*/ - - - - - - - - - ELSE IF (@Mode=1) /*Summarize*/ - BEGIN - --This mode is to give some overall stats on the database. - IF (@ValidOutputLocation = 1 AND COALESCE(@OutputServerName, @OutputDatabaseName, @OutputSchemaName, @OutputTableName) IS NOT NULL) - BEGIN - - IF NOT @SchemaExists = 1 - BEGIN - RAISERROR (N'Invalid schema name, data could not be saved.', 16, 0); - RETURN; - END - - IF @TableExists = 0 - BEGIN - SET @StringToExecute = - N'CREATE TABLE @@@OutputDatabaseName@@@.@@@OutputSchemaName@@@.@@@OutputTableName@@@ - ( - [id] INT IDENTITY(1,1) NOT NULL, - [run_id] UNIQUEIDENTIFIER, - [run_datetime] DATETIME, - [server_name] NVARCHAR(128), - [database_name] NVARCHAR(128), - [object_count] INT, - [reserved_gb] NUMERIC(29,1), - [reserved_lob_gb] NUMERIC(29,1), - [reserved_row_overflow_gb] NUMERIC(29,1), - [clustered_table_count] INT, - [clustered_table_gb] NUMERIC(29,1), - [nc_index_count] INT, - [nc_index_gb] NUMERIC(29,1), - [table_nc_index_ratio] NUMERIC(29,1), - [heap_count] INT, - [heap_gb] NUMERIC(29,1), - [partioned_table_count] INT, - [partioned_nc_count] INT, - [partioned_gb] NUMERIC(29,1), - [filtered_index_count] INT, - [indexed_view_count] INT, - [max_table_row_count] INT, - [max_table_gb] NUMERIC(29,1), - [max_nc_index_gb] NUMERIC(29,1), - [table_count_over_1gb] INT, - [table_count_over_10gb] INT, - [table_count_over_100gb] INT, - [nc_index_count_over_1gb] INT, - [nc_index_count_over_10gb] INT, - [nc_index_count_over_100gb] INT, - [min_create_date] DATETIME, - [max_create_date] DATETIME, - [max_modify_date] DATETIME, - [display_order] INT, - CONSTRAINT [PK_ID_@@@RunID@@@] PRIMARY KEY CLUSTERED ([id] ASC) - );'; - - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputSchemaName@@@', @OutputSchemaName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@RunID@@@', @RunID); - - IF @ValidOutputServer = 1 - BEGIN - SET @StringToExecute = REPLACE(@StringToExecute,'''',''''''); - EXEC('EXEC('''+@StringToExecute+''') AT ' + @OutputServerName); - END; - ELSE - BEGIN - EXEC(@StringToExecute); - END; - END; /* @TableExists = 0 */ - - -- Re-check that table now exists (if not we failed creating it) - SET @TableExists = NULL; - EXEC sp_executesql @TableExistsSql, N'@TableExists BIT OUTPUT', @TableExists OUTPUT; - - IF NOT @TableExists = 1 - BEGIN - RAISERROR('Creation of the output table failed.', 16, 0); - RETURN; - END; - - SET @StringToExecute = - N'INSERT @@@OutputServerName@@@.@@@OutputDatabaseName@@@.@@@OutputSchemaName@@@.@@@OutputTableName@@@ - ( - [run_id], - [run_datetime], - [server_name], - [database_name], - [object_count], - [reserved_gb], - [reserved_lob_gb], - [reserved_row_overflow_gb], - [clustered_table_count], - [clustered_table_gb], - [nc_index_count], - [nc_index_gb], - [table_nc_index_ratio], - [heap_count], - [heap_gb], - [partioned_table_count], - [partioned_nc_count], - [partioned_gb], - [filtered_index_count], - [indexed_view_count], - [max_table_row_count], - [max_table_gb], - [max_nc_index_gb], - [table_count_over_1gb], - [table_count_over_10gb], - [table_count_over_100gb], - [nc_index_count_over_1gb], - [nc_index_count_over_10gb], - [nc_index_count_over_100gb], - [min_create_date], - [max_create_date], - [max_modify_date], - [display_order] - ) - SELECT ''@@@RunID@@@'', - ''@@@GETDATE@@@'', - ''@@@LocalServerName@@@'', - -- Below should be a copy/paste of the real query - -- Make sure all quotes are escaped - -- NOTE! information line is skipped from output and the query below - -- NOTE! initial columns are not casted to nvarchar due to not outputing informational line - DB_NAME(i.database_id) AS [Database Name], - COUNT(*) AS [Number Objects], - CAST(SUM(sz.total_reserved_MB)/ - 1024. AS NUMERIC(29,1)) AS [All GB], - CAST(SUM(sz.total_reserved_LOB_MB)/ - 1024. AS NUMERIC(29,1)) AS [LOB GB], - CAST(SUM(sz.total_reserved_row_overflow_MB)/ - 1024. AS NUMERIC(29,1)) AS [Row Overflow GB], - SUM(CASE WHEN index_id=1 THEN 1 ELSE 0 END) AS [Clustered Tables], - CAST(SUM(CASE WHEN index_id=1 THEN sz.total_reserved_MB ELSE 0 END) - /1024. AS NUMERIC(29,1)) AS [Clustered Tables GB], - SUM(CASE WHEN index_id NOT IN (0,1) THEN 1 ELSE 0 END) AS [NC Indexes], - CAST(SUM(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) - /1024. AS NUMERIC(29,1)) AS [NC Indexes GB], - CASE WHEN SUM(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) > 0 THEN - CAST(SUM(CASE WHEN index_id IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) - / SUM(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) AS NUMERIC(29,1)) - ELSE 0 END AS [ratio table: NC Indexes], - SUM(CASE WHEN index_id=0 THEN 1 ELSE 0 END) AS [Heaps], - CAST(SUM(CASE WHEN index_id=0 THEN sz.total_reserved_MB ELSE 0 END) - /1024. AS NUMERIC(29,1)) AS [Heaps GB], - SUM(CASE WHEN index_id IN (0,1) AND partition_key_column_name IS NOT NULL THEN 1 ELSE 0 END) AS [Partitioned Tables], - SUM(CASE WHEN index_id NOT IN (0,1) AND partition_key_column_name IS NOT NULL THEN 1 ELSE 0 END) AS [Partitioned NCs], - CAST(SUM(CASE WHEN partition_key_column_name IS NOT NULL THEN sz.total_reserved_MB ELSE 0 END)/1024. AS NUMERIC(29,1)) AS [Partitioned GB], - SUM(CASE WHEN filter_definition <> '''' THEN 1 ELSE 0 END) AS [Filtered Indexes], - SUM(CASE WHEN is_indexed_view=1 THEN 1 ELSE 0 END) AS [Indexed Views], - MAX(total_rows) AS [Max Row Count], - CAST(MAX(CASE WHEN index_id IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) - /1024. AS NUMERIC(29,1)) AS [Max Table GB], - CAST(MAX(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) - /1024. AS NUMERIC(29,1)) AS [Max NC Index GB], - SUM(CASE WHEN index_id IN (0,1) AND sz.total_reserved_MB > 1024 THEN 1 ELSE 0 END) AS [Count Tables > 1GB], - SUM(CASE WHEN index_id IN (0,1) AND sz.total_reserved_MB > 10240 THEN 1 ELSE 0 END) AS [Count Tables > 10GB], - SUM(CASE WHEN index_id IN (0,1) AND sz.total_reserved_MB > 102400 THEN 1 ELSE 0 END) AS [Count Tables > 100GB], - SUM(CASE WHEN index_id NOT IN (0,1) AND sz.total_reserved_MB > 1024 THEN 1 ELSE 0 END) AS [Count NCs > 1GB], - SUM(CASE WHEN index_id NOT IN (0,1) AND sz.total_reserved_MB > 10240 THEN 1 ELSE 0 END) AS [Count NCs > 10GB], - SUM(CASE WHEN index_id NOT IN (0,1) AND sz.total_reserved_MB > 102400 THEN 1 ELSE 0 END) AS [Count NCs > 100GB], - MIN(create_date) AS [Oldest Create Date], - MAX(create_date) AS [Most Recent Create Date], - MAX(modify_date) AS [Most Recent Modify Date], - 1 AS [Display Order] - FROM #IndexSanity AS i - --left join here so we don''t lose disabled nc indexes - LEFT JOIN #IndexSanitySize AS sz - ON i.index_sanity_id=sz.index_sanity_id - GROUP BY DB_NAME(i.database_id) - ORDER BY [Display Order] ASC - OPTION (RECOMPILE);'; - - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputServerName@@@', @OutputServerName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputSchemaName@@@', @OutputSchemaName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@RunID@@@', @RunID); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@GETDATE@@@', GETDATE()); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@LocalServerName@@@', CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128))); - EXEC(@StringToExecute); - - END; /* @ValidOutputLocation = 1 */ - ELSE - BEGIN - IF(@OutputType <> 'NONE') - BEGIN - - RAISERROR(N'@Mode=1, we are summarizing.', 0,1) WITH NOWAIT; - - SELECT DB_NAME(i.database_id) AS [Database Name], - CAST((COUNT(*)) AS NVARCHAR(256)) AS [Number Objects], - CAST(CAST(SUM(sz.total_reserved_MB)/ - 1024. AS NUMERIC(29,1)) AS NVARCHAR(500)) AS [All GB], - CAST(CAST(SUM(sz.total_reserved_LOB_MB)/ - 1024. AS NUMERIC(29,1)) AS NVARCHAR(500)) AS [LOB GB], - CAST(CAST(SUM(sz.total_reserved_row_overflow_MB)/ - 1024. AS NUMERIC(29,1)) AS NVARCHAR(500)) AS [Row Overflow GB], - CAST(SUM(CASE WHEN index_id=1 THEN 1 ELSE 0 END)AS NVARCHAR(50)) AS [Clustered Tables], - CAST(SUM(CASE WHEN index_id=1 THEN sz.total_reserved_MB ELSE 0 END) - /1024. AS NUMERIC(29,1)) AS [Clustered Tables GB], - SUM(CASE WHEN index_id NOT IN (0,1) THEN 1 ELSE 0 END) AS [NC Indexes], - CAST(SUM(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) - /1024. AS NUMERIC(29,1)) AS [NC Indexes GB], - CASE WHEN SUM(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) > 0 THEN - CAST(SUM(CASE WHEN index_id IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) - / SUM(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) AS NUMERIC(29,1)) - ELSE 0 END AS [ratio table: NC Indexes], - SUM(CASE WHEN index_id=0 THEN 1 ELSE 0 END) AS [Heaps], - CAST(SUM(CASE WHEN index_id=0 THEN sz.total_reserved_MB ELSE 0 END) - /1024. AS NUMERIC(29,1)) AS [Heaps GB], - SUM(CASE WHEN index_id IN (0,1) AND partition_key_column_name IS NOT NULL THEN 1 ELSE 0 END) AS [Partitioned Tables], - SUM(CASE WHEN index_id NOT IN (0,1) AND partition_key_column_name IS NOT NULL THEN 1 ELSE 0 END) AS [Partitioned NCs], - CAST(SUM(CASE WHEN partition_key_column_name IS NOT NULL THEN sz.total_reserved_MB ELSE 0 END)/1024. AS NUMERIC(29,1)) AS [Partitioned GB], - SUM(CASE WHEN filter_definition <> '' THEN 1 ELSE 0 END) AS [Filtered Indexes], - SUM(CASE WHEN is_indexed_view=1 THEN 1 ELSE 0 END) AS [Indexed Views], - MAX(total_rows) AS [Max Row Count], - CAST(MAX(CASE WHEN index_id IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) - /1024. AS NUMERIC(29,1)) AS [Max Table GB], - CAST(MAX(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) - /1024. AS NUMERIC(29,1)) AS [Max NC Index GB], - SUM(CASE WHEN index_id IN (0,1) AND sz.total_reserved_MB > 1024 THEN 1 ELSE 0 END) AS [Count Tables > 1GB], - SUM(CASE WHEN index_id IN (0,1) AND sz.total_reserved_MB > 10240 THEN 1 ELSE 0 END) AS [Count Tables > 10GB], - SUM(CASE WHEN index_id IN (0,1) AND sz.total_reserved_MB > 102400 THEN 1 ELSE 0 END) AS [Count Tables > 100GB], - SUM(CASE WHEN index_id NOT IN (0,1) AND sz.total_reserved_MB > 1024 THEN 1 ELSE 0 END) AS [Count NCs > 1GB], - SUM(CASE WHEN index_id NOT IN (0,1) AND sz.total_reserved_MB > 10240 THEN 1 ELSE 0 END) AS [Count NCs > 10GB], - SUM(CASE WHEN index_id NOT IN (0,1) AND sz.total_reserved_MB > 102400 THEN 1 ELSE 0 END) AS [Count NCs > 100GB], - MIN(create_date) AS [Oldest Create Date], - MAX(create_date) AS [Most Recent Create Date], - MAX(modify_date) AS [Most Recent Modify Date], - 1 AS [Display Order] - FROM #IndexSanity AS i - --left join here so we don't lose disabled nc indexes - LEFT JOIN #IndexSanitySize AS sz - ON i.index_sanity_id=sz.index_sanity_id - GROUP BY DB_NAME(i.database_id) - UNION ALL - SELECT CASE WHEN @GetAllDatabases = 1 THEN N'All Databases' ELSE N'Database ' + N' as of ' + CONVERT(NVARCHAR(16),GETDATE(),121) END, - @ScriptVersionName, - N'From Your Community Volunteers' , - N'http://FirstResponderKit.org' , - @DaysUptimeInsertValue, - NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL, - NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL, - NULL,NULL,0 AS display_order - ORDER BY [Display Order] ASC - OPTION (RECOMPILE); - END; - END; - - END; /* End @Mode=1 (summarize)*/ - - - - - - - - - ELSE IF (@Mode=2) /*Index Detail*/ - BEGIN - --This mode just spits out all the detail without filters. - --This supports slicing AND dicing in Excel - RAISERROR(N'@Mode=2, here''s the details on existing indexes.', 0,1) WITH NOWAIT; - - IF (@ValidOutputLocation = 1 AND COALESCE(@OutputServerName, @OutputDatabaseName, @OutputSchemaName, @OutputTableName) IS NOT NULL) - BEGIN - IF @SchemaExists = 1 - BEGIN - IF @TableExists = 0 - BEGIN - SET @StringToExecute = - N'CREATE TABLE @@@OutputDatabaseName@@@.@@@OutputSchemaName@@@.@@@OutputTableName@@@ - ( - [id] INT IDENTITY(1,1) NOT NULL, - [run_id] UNIQUEIDENTIFIER, - [run_datetime] DATETIME, - [server_name] NVARCHAR(128), - [database_name] NVARCHAR(128), - [schema_name] NVARCHAR(128), - [table_name] NVARCHAR(128), - [index_name] NVARCHAR(128), - [Drop_Tsql] NVARCHAR(MAX), - [Create_Tsql] NVARCHAR(MAX), - [index_id] INT, - [db_schema_object_indexid] NVARCHAR(500), - [object_type] NVARCHAR(15), - [index_definition] NVARCHAR(MAX), - [key_column_names_with_sort_order] NVARCHAR(MAX), - [count_key_columns] INT, - [include_column_names] NVARCHAR(MAX), - [count_included_columns] INT, - [secret_columns] NVARCHAR(MAX), - [count_secret_columns] INT, - [partition_key_column_name] NVARCHAR(MAX), - [filter_definition] NVARCHAR(MAX), - [is_indexed_view] BIT, - [is_primary_key] BIT, - [is_unique_constraint] BIT, - [is_XML] BIT, - [is_spatial] BIT, - [is_NC_columnstore] BIT, - [is_CX_columnstore] BIT, - [is_in_memory_oltp] BIT, - [is_disabled] BIT, - [is_hypothetical] BIT, - [is_padded] BIT, - [fill_factor] INT, - [is_referenced_by_foreign_key] BIT, - [last_user_seek] DATETIME, - [last_user_scan] DATETIME, - [last_user_lookup] DATETIME, - [last_user_update] DATETIME, - [total_reads] BIGINT, - [user_updates] BIGINT, - [reads_per_write] MONEY, - [index_usage_summary] NVARCHAR(200), - [total_singleton_lookup_count] BIGINT, - [total_range_scan_count] BIGINT, - [total_leaf_delete_count] BIGINT, - [total_leaf_update_count] BIGINT, - [index_op_stats] NVARCHAR(200), - [partition_count] INT, - [total_rows] BIGINT, - [total_reserved_MB] NUMERIC(29,2), - [total_reserved_LOB_MB] NUMERIC(29,2), - [total_reserved_row_overflow_MB] NUMERIC(29,2), - [index_size_summary] NVARCHAR(300), - [total_row_lock_count] BIGINT, - [total_row_lock_wait_count] BIGINT, - [total_row_lock_wait_in_ms] BIGINT, - [avg_row_lock_wait_in_ms] BIGINT, - [total_page_lock_count] BIGINT, - [total_page_lock_wait_count] BIGINT, - [total_page_lock_wait_in_ms] BIGINT, - [avg_page_lock_wait_in_ms] BIGINT, - [total_index_lock_promotion_attempt_count] BIGINT, - [total_index_lock_promotion_count] BIGINT, - [total_forwarded_fetch_count] BIGINT, - [data_compression_desc] NVARCHAR(4000), - [page_latch_wait_count] BIGINT, - [page_latch_wait_in_ms] BIGINT, - [page_io_latch_wait_count] BIGINT, - [page_io_latch_wait_in_ms] BIGINT, - [create_date] DATETIME, - [modify_date] DATETIME, - [more_info] NVARCHAR(500), - [display_order] INT, - CONSTRAINT [PK_ID_@@@RunID@@@] PRIMARY KEY CLUSTERED ([id] ASC) - );'; - - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputSchemaName@@@', @OutputSchemaName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@RunID@@@', @RunID); - - IF @ValidOutputServer = 1 - BEGIN - SET @StringToExecute = REPLACE(@StringToExecute,'''',''''''); - EXEC('EXEC('''+@StringToExecute+''') AT ' + @OutputServerName); - END; - ELSE - BEGIN - EXEC(@StringToExecute); - END; - END; /* @TableExists = 0 */ - - - -- Re-check that table now exists (if not we failed creating it) - SET @TableExists = NULL; - EXEC sp_executesql @TableExistsSql, N'@TableExists BIT OUTPUT', @TableExists OUTPUT; - - IF @TableExists = 1 - BEGIN - SET @StringToExecute = - N'INSERT @@@OutputServerName@@@.@@@OutputDatabaseName@@@.@@@OutputSchemaName@@@.@@@OutputTableName@@@ - ( - [run_id], - [run_datetime], - [server_name], - [database_name], - [schema_name], - [table_name], - [index_name], - [Drop_Tsql], - [Create_Tsql], - [index_id], - [db_schema_object_indexid], - [object_type], - [index_definition], - [key_column_names_with_sort_order], - [count_key_columns], - [include_column_names], - [count_included_columns], - [secret_columns], - [count_secret_columns], - [partition_key_column_name], - [filter_definition], - [is_indexed_view], - [is_primary_key], - [is_unique_constraint], - [is_XML], - [is_spatial], - [is_NC_columnstore], - [is_CX_columnstore], - [is_in_memory_oltp], - [is_disabled], - [is_hypothetical], - [is_padded], - [fill_factor], - [is_referenced_by_foreign_key], - [last_user_seek], - [last_user_scan], - [last_user_lookup], - [last_user_update], - [total_reads], - [user_updates], - [reads_per_write], - [index_usage_summary], - [total_singleton_lookup_count], - [total_range_scan_count], - [total_leaf_delete_count], - [total_leaf_update_count], - [index_op_stats], - [partition_count], - [total_rows], - [total_reserved_MB], - [total_reserved_LOB_MB], - [total_reserved_row_overflow_MB], - [index_size_summary], - [total_row_lock_count], - [total_row_lock_wait_count], - [total_row_lock_wait_in_ms], - [avg_row_lock_wait_in_ms], - [total_page_lock_count], - [total_page_lock_wait_count], - [total_page_lock_wait_in_ms], - [avg_page_lock_wait_in_ms], - [total_index_lock_promotion_attempt_count], - [total_index_lock_promotion_count], - [total_forwarded_fetch_count], - [data_compression_desc], - [page_latch_wait_count], - [page_latch_wait_in_ms], - [page_io_latch_wait_count], - [page_io_latch_wait_in_ms], - [create_date], - [modify_date], - [more_info], - [display_order] - ) - SELECT ''@@@RunID@@@'', - ''@@@GETDATE@@@'', - ''@@@LocalServerName@@@'', - -- Below should be a copy/paste of the real query - -- Make sure all quotes are escaped - i.[database_name] AS [Database Name], - i.[schema_name] AS [Schema Name], - i.[object_name] AS [Object Name], - ISNULL(i.index_name, '''') AS [Index Name], - CASE - WHEN i.is_primary_key = 1 AND i.index_definition <> ''[HEAP]'' - THEN N''--ALTER TABLE '' + QUOTENAME(i.[database_name]) + N''.'' + QUOTENAME(i.[schema_name]) + N''.'' + QUOTENAME(i.[object_name]) + - N'' DROP CONSTRAINT '' + QUOTENAME(i.index_name) + N'';'' - WHEN i.is_primary_key = 0 AND i.is_unique_constraint = 1 AND i.index_definition <> ''[HEAP]'' - THEN N''--ALTER TABLE '' + QUOTENAME(i.[database_name]) + N''.'' + QUOTENAME(i.[schema_name]) + N''.'' + QUOTENAME(i.[object_name]) + - N'' DROP CONSTRAINT '' + QUOTENAME(i.index_name) + N'';'' - WHEN i.is_primary_key = 0 AND i.index_definition <> ''[HEAP]'' - THEN N''--DROP INDEX ''+ QUOTENAME(i.index_name) + N'' ON '' + QUOTENAME(i.[database_name]) + N''.'' + - QUOTENAME(i.[schema_name]) + N''.'' + QUOTENAME(i.[object_name]) + N'';'' - ELSE N'''' - END AS [Drop TSQL], - CASE - WHEN i.index_definition = ''[HEAP]'' THEN N'''' - ELSE N''--'' + ict.create_tsql END AS [Create TSQL], - CAST(i.index_id AS NVARCHAR(10))AS [Index ID], - db_schema_object_indexid AS [Details: schema.table.index(indexid)], - CASE WHEN index_id IN ( 1, 0 ) THEN ''TABLE'' - ELSE ''NonClustered'' - END AS [Object Type], - LEFT(index_definition,4000) AS [Definition: [Property]] ColumnName {datatype maxbytes}], - ISNULL(LTRIM(key_column_names_with_sort_order), '''') AS [Key Column Names With Sort], - ISNULL(count_key_columns, 0) AS [Count Key Columns], - ISNULL(include_column_names, '''') AS [Include Column Names], - ISNULL(count_included_columns,0) AS [Count Included Columns], - ISNULL(secret_columns,'''') AS [Secret Column Names], - ISNULL(count_secret_columns,0) AS [Count Secret Columns], - ISNULL(partition_key_column_name, '''') AS [Partition Key Column Name], - ISNULL(filter_definition, '''') AS [Filter Definition], - is_indexed_view AS [Is Indexed View], - is_primary_key AS [Is Primary Key], - is_unique_constraint AS [Is Unique Constraint], - is_XML AS [Is XML], - is_spatial AS [Is Spatial], - is_NC_columnstore AS [Is NC Columnstore], - is_CX_columnstore AS [Is CX Columnstore], - is_in_memory_oltp AS [Is In-Memory OLTP], - is_disabled AS [Is Disabled], - is_hypothetical AS [Is Hypothetical], - is_padded AS [Is Padded], - fill_factor AS [Fill Factor], - is_referenced_by_foreign_key AS [Is Reference by Foreign Key], - last_user_seek AS [Last User Seek], - last_user_scan AS [Last User Scan], - last_user_lookup AS [Last User Lookup], - last_user_update AS [Last User Update], - total_reads AS [Total Reads], - user_updates AS [User Updates], - reads_per_write AS [Reads Per Write], - index_usage_summary AS [Index Usage], - sz.total_singleton_lookup_count AS [Singleton Lookups], - sz.total_range_scan_count AS [Range Scans], - sz.total_leaf_delete_count AS [Leaf Deletes], - sz.total_leaf_update_count AS [Leaf Updates], - sz.index_op_stats AS [Index Op Stats], - sz.partition_count AS [Partition Count], - sz.total_rows AS [Rows], - sz.total_reserved_MB AS [Reserved MB], - sz.total_reserved_LOB_MB AS [Reserved LOB MB], - sz.total_reserved_row_overflow_MB AS [Reserved Row Overflow MB], - sz.index_size_summary AS [Index Size], - sz.total_row_lock_count AS [Row Lock Count], - sz.total_row_lock_wait_count AS [Row Lock Wait Count], - sz.total_row_lock_wait_in_ms AS [Row Lock Wait ms], - sz.avg_row_lock_wait_in_ms AS [Avg Row Lock Wait ms], - sz.total_page_lock_count AS [Page Lock Count], - sz.total_page_lock_wait_count AS [Page Lock Wait Count], - sz.total_page_lock_wait_in_ms AS [Page Lock Wait ms], - sz.avg_page_lock_wait_in_ms AS [Avg Page Lock Wait ms], - sz.total_index_lock_promotion_attempt_count AS [Lock Escalation Attempts], - sz.total_index_lock_promotion_count AS [Lock Escalations], - sz.total_forwarded_fetch_count AS [Forwarded Fetches], - sz.data_compression_desc AS [Data Compression], - sz.page_latch_wait_count, - sz.page_latch_wait_in_ms, - sz.page_io_latch_wait_count, - sz.page_io_latch_wait_in_ms, - i.create_date AS [Create Date], - i.modify_date AS [Modify Date], - more_info AS [More Info], - 1 AS [Display Order] - FROM #IndexSanity AS i - LEFT JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id - LEFT JOIN #IndexCreateTsql AS ict ON i.index_sanity_id = ict.index_sanity_id - ORDER BY [Database Name], [Schema Name], [Object Name], [Index ID] - OPTION (RECOMPILE);'; - - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputServerName@@@', @OutputServerName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputSchemaName@@@', @OutputSchemaName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@RunID@@@', @RunID); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@GETDATE@@@', GETDATE()); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@LocalServerName@@@', CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128))); - EXEC(@StringToExecute); - END; /* @TableExists = 1 */ - ELSE - RAISERROR('Creation of the output table failed.', 16, 0); - END; /* @TableExists = 0 */ - ELSE - RAISERROR (N'Invalid schema name, data could not be saved.', 16, 0); - END; /* @ValidOutputLocation = 1 */ - ELSE - - IF(@OutputType <> 'NONE') - BEGIN - SELECT i.[database_name] AS [Database Name], - i.[schema_name] AS [Schema Name], - i.[object_name] AS [Object Name], - ISNULL(i.index_name, '') AS [Index Name], - CAST(i.index_id AS NVARCHAR(10))AS [Index ID], - db_schema_object_indexid AS [Details: schema.table.index(indexid)], - CASE WHEN index_id IN ( 1, 0 ) THEN 'TABLE' - ELSE 'NonClustered' - END AS [Object Type], - index_definition AS [Definition: [Property]] ColumnName {datatype maxbytes}], - ISNULL(LTRIM(key_column_names_with_sort_order), '') AS [Key Column Names With Sort], - ISNULL(count_key_columns, 0) AS [Count Key Columns], - ISNULL(include_column_names, '') AS [Include Column Names], - ISNULL(count_included_columns,0) AS [Count Included Columns], - ISNULL(secret_columns,'') AS [Secret Column Names], - ISNULL(count_secret_columns,0) AS [Count Secret Columns], - ISNULL(partition_key_column_name, '') AS [Partition Key Column Name], - ISNULL(filter_definition, '') AS [Filter Definition], - is_indexed_view AS [Is Indexed View], - is_primary_key AS [Is Primary Key], - is_unique_constraint AS [Is Unique Constraint] , - is_XML AS [Is XML], - is_spatial AS [Is Spatial], - is_NC_columnstore AS [Is NC Columnstore], - is_CX_columnstore AS [Is CX Columnstore], - is_in_memory_oltp AS [Is In-Memory OLTP], - is_disabled AS [Is Disabled], - is_hypothetical AS [Is Hypothetical], - is_padded AS [Is Padded], - fill_factor AS [Fill Factor], - is_referenced_by_foreign_key AS [Is Reference by Foreign Key], - last_user_seek AS [Last User Seek], - last_user_scan AS [Last User Scan], - last_user_lookup AS [Last User Lookup], - last_user_update AS [Last User Update], - total_reads AS [Total Reads], - user_updates AS [User Updates], - reads_per_write AS [Reads Per Write], - index_usage_summary AS [Index Usage], - sz.total_singleton_lookup_count AS [Singleton Lookups], - sz.total_range_scan_count AS [Range Scans], - sz.total_leaf_delete_count AS [Leaf Deletes], - sz.total_leaf_update_count AS [Leaf Updates], - sz.index_op_stats AS [Index Op Stats], - sz.partition_count AS [Partition Count], - sz.total_rows AS [Rows], - sz.total_reserved_MB AS [Reserved MB], - sz.total_reserved_LOB_MB AS [Reserved LOB MB], - sz.total_reserved_row_overflow_MB AS [Reserved Row Overflow MB], - sz.index_size_summary AS [Index Size], - sz.total_row_lock_count AS [Row Lock Count], - sz.total_row_lock_wait_count AS [Row Lock Wait Count], - sz.total_row_lock_wait_in_ms AS [Row Lock Wait ms], - sz.avg_row_lock_wait_in_ms AS [Avg Row Lock Wait ms], - sz.total_page_lock_count AS [Page Lock Count], - sz.total_page_lock_wait_count AS [Page Lock Wait Count], - sz.total_page_lock_wait_in_ms AS [Page Lock Wait ms], - sz.avg_page_lock_wait_in_ms AS [Avg Page Lock Wait ms], - sz.total_index_lock_promotion_attempt_count AS [Lock Escalation Attempts], - sz.total_index_lock_promotion_count AS [Lock Escalations], - sz.page_latch_wait_count AS [Page Latch Wait Count], - sz.page_latch_wait_in_ms AS [Page Latch Wait ms], - sz.page_io_latch_wait_count AS [Page IO Latch Wait Count], - sz.page_io_latch_wait_in_ms as [Page IO Latch Wait ms], - sz.total_forwarded_fetch_count AS [Forwarded Fetches], - sz.data_compression_desc AS [Data Compression], - i.create_date AS [Create Date], - i.modify_date AS [Modify Date], - more_info AS [More Info], - CASE - WHEN i.is_primary_key = 1 AND i.index_definition <> '[HEAP]' - THEN N'--ALTER TABLE ' + QUOTENAME(i.[database_name]) + N'.' + QUOTENAME(i.[schema_name]) + N'.' + QUOTENAME(i.[object_name]) - + N' DROP CONSTRAINT ' + QUOTENAME(i.index_name) + N';' - WHEN i.is_primary_key = 0 AND i.is_unique_constraint = 1 AND i.index_definition <> '[HEAP]' - THEN N'--ALTER TABLE ' + QUOTENAME(i.[database_name]) + N'.' + QUOTENAME(i.[schema_name]) + N'.' + QUOTENAME(i.[object_name]) - + N' DROP CONSTRAINT ' + QUOTENAME(i.index_name) + N';' - WHEN i.is_primary_key = 0 AND i.index_definition <> '[HEAP]' - THEN N'--DROP INDEX '+ QUOTENAME(i.index_name) + N' ON ' + QUOTENAME(i.[database_name]) + N'.' + - QUOTENAME(i.[schema_name]) + N'.' + QUOTENAME(i.[object_name]) + N';' - ELSE N'' - END AS [Drop TSQL], - CASE - WHEN i.index_definition = '[HEAP]' THEN N'' - ELSE N'--' + ict.create_tsql END AS [Create TSQL], - 1 AS [Display Order] - FROM #IndexSanity AS i --left join here so we don't lose disabled nc indexes - LEFT JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id - LEFT JOIN #IndexCreateTsql AS ict ON i.index_sanity_id = ict.index_sanity_id - ORDER BY /* Shout out to DHutmacher */ - /*DESC*/ - CASE WHEN @SortOrder = N'rows' AND @SortDirection = N'desc' THEN sz.total_rows ELSE NULL END DESC, - CASE WHEN @SortOrder = N'reserved_mb' AND @SortDirection = N'desc' THEN sz.total_reserved_MB ELSE NULL END DESC, - CASE WHEN @SortOrder = N'size' AND @SortDirection = N'desc' THEN sz.total_reserved_MB ELSE NULL END DESC, - CASE WHEN @SortOrder = N'reserved_lob_mb' AND @SortDirection = N'desc' THEN sz.total_reserved_LOB_MB ELSE NULL END DESC, - CASE WHEN @SortOrder = N'lob' AND @SortDirection = N'desc' THEN sz.total_reserved_LOB_MB ELSE NULL END DESC, - CASE WHEN @SortOrder = N'total_row_lock_wait_in_ms' AND @SortDirection = N'desc' THEN COALESCE(sz.total_row_lock_wait_in_ms,0) ELSE NULL END DESC, - CASE WHEN @SortOrder = N'total_page_lock_wait_in_ms' AND @SortDirection = N'desc' THEN COALESCE(sz.total_page_lock_wait_in_ms,0) ELSE NULL END DESC, - CASE WHEN @SortOrder = N'lock_time' AND @SortDirection = N'desc' THEN (COALESCE(sz.total_row_lock_wait_in_ms,0) + COALESCE(sz.total_page_lock_wait_in_ms,0)) ELSE NULL END DESC, - CASE WHEN @SortOrder = N'total_reads' AND @SortDirection = N'desc' THEN total_reads ELSE NULL END DESC, - CASE WHEN @SortOrder = N'reads' AND @SortDirection = N'desc' THEN total_reads ELSE NULL END DESC, - CASE WHEN @SortOrder = N'user_updates' AND @SortDirection = N'desc' THEN user_updates ELSE NULL END DESC, - CASE WHEN @SortOrder = N'writes' AND @SortDirection = N'desc' THEN user_updates ELSE NULL END DESC, - CASE WHEN @SortOrder = N'reads_per_write' AND @SortDirection = N'desc' THEN reads_per_write ELSE NULL END DESC, - CASE WHEN @SortOrder = N'ratio' AND @SortDirection = N'desc' THEN reads_per_write ELSE NULL END DESC, - CASE WHEN @SortOrder = N'forward_fetches' AND @SortDirection = N'desc' THEN sz.total_forwarded_fetch_count ELSE NULL END DESC, - CASE WHEN @SortOrder = N'fetches' AND @SortDirection = N'desc' THEN sz.total_forwarded_fetch_count ELSE NULL END DESC, - CASE WHEN @SortOrder = N'create_date' AND @SortDirection = N'desc' THEN CONVERT(DATETIME, i.create_date) ELSE NULL END DESC, - CASE WHEN @SortOrder = N'modify_date' AND @SortDirection = N'desc' THEN CONVERT(DATETIME, i.modify_date) ELSE NULL END DESC, - /*ASC*/ - CASE WHEN @SortOrder = N'rows' AND @SortDirection = N'asc' THEN sz.total_rows ELSE NULL END ASC, - CASE WHEN @SortOrder = N'reserved_mb' AND @SortDirection = N'asc' THEN sz.total_reserved_MB ELSE NULL END ASC, - CASE WHEN @SortOrder = N'size' AND @SortDirection = N'asc' THEN sz.total_reserved_MB ELSE NULL END ASC, - CASE WHEN @SortOrder = N'reserved_lob_mb' AND @SortDirection = N'asc' THEN sz.total_reserved_LOB_MB ELSE NULL END ASC, - CASE WHEN @SortOrder = N'lob' AND @SortDirection = N'asc' THEN sz.total_reserved_LOB_MB ELSE NULL END ASC, - CASE WHEN @SortOrder = N'total_row_lock_wait_in_ms' AND @SortDirection = N'asc' THEN COALESCE(sz.total_row_lock_wait_in_ms,0) ELSE NULL END ASC, - CASE WHEN @SortOrder = N'total_page_lock_wait_in_ms' AND @SortDirection = N'asc' THEN COALESCE(sz.total_page_lock_wait_in_ms,0) ELSE NULL END ASC, - CASE WHEN @SortOrder = N'lock_time' AND @SortDirection = N'asc' THEN (COALESCE(sz.total_row_lock_wait_in_ms,0) + COALESCE(sz.total_page_lock_wait_in_ms,0)) ELSE NULL END ASC, - CASE WHEN @SortOrder = N'total_reads' AND @SortDirection = N'asc' THEN total_reads ELSE NULL END ASC, - CASE WHEN @SortOrder = N'reads' AND @SortDirection = N'asc' THEN total_reads ELSE NULL END ASC, - CASE WHEN @SortOrder = N'user_updates' AND @SortDirection = N'asc' THEN user_updates ELSE NULL END ASC, - CASE WHEN @SortOrder = N'writes' AND @SortDirection = N'asc' THEN user_updates ELSE NULL END ASC, - CASE WHEN @SortOrder = N'reads_per_write' AND @SortDirection = N'asc' THEN reads_per_write ELSE NULL END ASC, - CASE WHEN @SortOrder = N'ratio' AND @SortDirection = N'asc' THEN reads_per_write ELSE NULL END ASC, - CASE WHEN @SortOrder = N'forward_fetches' AND @SortDirection = N'asc' THEN sz.total_forwarded_fetch_count ELSE NULL END ASC, - CASE WHEN @SortOrder = N'fetches' AND @SortDirection = N'asc' THEN sz.total_forwarded_fetch_count ELSE NULL END ASC, - CASE WHEN @SortOrder = N'create_date' AND @SortDirection = N'asc' THEN CONVERT(DATETIME, i.create_date) ELSE NULL END ASC, - CASE WHEN @SortOrder = N'modify_date' AND @SortDirection = N'asc' THEN CONVERT(DATETIME, i.modify_date) ELSE NULL END ASC, - i.[database_name], [Schema Name], [Object Name], [Index ID] - OPTION (RECOMPILE); - END; - - END; /* End @Mode=2 (index detail)*/ - - - - - - - - - ELSE IF (@Mode=3) /*Missing index Detail*/ - BEGIN - IF (@ValidOutputLocation = 1 AND COALESCE(@OutputServerName, @OutputDatabaseName, @OutputSchemaName, @OutputTableName) IS NOT NULL) - BEGIN - - IF NOT @SchemaExists = 1 - BEGIN - RAISERROR (N'Invalid schema name, data could not be saved.', 16, 0); - RETURN; - END - - IF @TableExists = 0 - BEGIN - SET @StringToExecute = - N'CREATE TABLE @@@OutputDatabaseName@@@.@@@OutputSchemaName@@@.@@@OutputTableName@@@ - ( - [id] INT IDENTITY(1,1) NOT NULL, - [run_id] UNIQUEIDENTIFIER, - [run_datetime] DATETIME, - [server_name] NVARCHAR(128), - [database_name] NVARCHAR(128), - [schema_name] NVARCHAR(128), - [table_name] NVARCHAR(128), - [magic_benefit_number] BIGINT, - [missing_index_details] NVARCHAR(MAX), - [avg_total_user_cost] NUMERIC(29,4), - [avg_user_impact] NUMERIC(29,1), - [user_seeks] BIGINT, - [user_scans] BIGINT, - [unique_compiles] BIGINT, - [equality_columns_with_data_type] NVARCHAR(MAX), - [inequality_columns_with_data_type] NVARCHAR(MAX), - [included_columns_with_data_type] NVARCHAR(MAX), - [index_estimated_impact] NVARCHAR(256), - [create_tsql] NVARCHAR(MAX), - [more_info] NVARCHAR(600), - [display_order] INT, - [is_low] BIT, - [sample_query_plan] XML, - CONSTRAINT [PK_ID_@@@RunID@@@] PRIMARY KEY CLUSTERED ([id] ASC) - );'; - - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputSchemaName@@@', @OutputSchemaName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@RunID@@@', @RunID); - - IF @ValidOutputServer = 1 - BEGIN - SET @StringToExecute = REPLACE(@StringToExecute,'''',''''''); - EXEC('EXEC('''+@StringToExecute+''') AT ' + @OutputServerName); - END; - ELSE - BEGIN - EXEC(@StringToExecute); - END; - END; /* @TableExists = 0 */ - - -- Re-check that table now exists (if not we failed creating it) - SET @TableExists = NULL; - EXEC sp_executesql @TableExistsSql, N'@TableExists BIT OUTPUT', @TableExists OUTPUT; - - IF NOT @TableExists = 1 - BEGIN - RAISERROR('Creation of the output table failed.', 16, 0); - RETURN; - END; - SET @StringToExecute = - N'WITH create_date AS ( - SELECT i.database_id, - i.schema_name, - i.[object_id], - ISNULL(NULLIF(MAX(DATEDIFF(DAY, i.create_date, SYSDATETIME())), 0), 1) AS create_days - FROM #IndexSanity AS i - GROUP BY i.database_id, i.schema_name, i.object_id - ) - INSERT @@@OutputServerName@@@.@@@OutputDatabaseName@@@.@@@OutputSchemaName@@@.@@@OutputTableName@@@ - ( - [run_id], - [run_datetime], - [server_name], - [database_name], - [schema_name], - [table_name], - [magic_benefit_number], - [missing_index_details], - [avg_total_user_cost], - [avg_user_impact], - [user_seeks], - [user_scans], - [unique_compiles], - [equality_columns_with_data_type], - [inequality_columns_with_data_type], - [included_columns_with_data_type], - [index_estimated_impact], - [create_tsql], - [more_info], - [display_order], - [is_low], - [sample_query_plan] - ) - SELECT ''@@@RunID@@@'', - ''@@@GETDATE@@@'', - ''@@@LocalServerName@@@'', - -- Below should be a copy/paste of the real query - -- Make sure all quotes are escaped - -- NOTE! information line is skipped from output and the query below - -- NOTE! CTE block is above insert in the copied SQL - mi.database_name AS [Database Name], - mi.[schema_name] AS [Schema], - mi.table_name AS [Table], - CAST((mi.magic_benefit_number / CASE WHEN cd.create_days < @DaysUptime THEN cd.create_days ELSE @DaysUptime END) AS BIGINT) - AS [Magic Benefit Number], - mi.missing_index_details AS [Missing Index Details], - mi.avg_total_user_cost AS [Avg Query Cost], - mi.avg_user_impact AS [Est Index Improvement], - mi.user_seeks AS [Seeks], - mi.user_scans AS [Scans], - mi.unique_compiles AS [Compiles], - mi.equality_columns_with_data_type AS [Equality Columns], - mi.inequality_columns_with_data_type AS [Inequality Columns], - mi.included_columns_with_data_type AS [Included Columns], - mi.index_estimated_impact AS [Estimated Impact], - mi.create_tsql AS [Create TSQL], - mi.more_info AS [More Info], - 1 AS [Display Order], - mi.is_low, - mi.sample_query_plan AS [Sample Query Plan] - FROM #MissingIndexes AS mi - LEFT JOIN create_date AS cd - ON mi.[object_id] = cd.object_id - AND mi.database_id = cd.database_id - AND mi.schema_name = cd.schema_name - /* Minimum benefit threshold = 100k/day of uptime OR since table creation date, whichever is lower*/ - WHERE @ShowAllMissingIndexRequests=1 - OR (mi.magic_benefit_number / CASE WHEN cd.create_days < @DaysUptime THEN cd.create_days ELSE @DaysUptime END) >= 100000 - ORDER BY [Display Order] ASC, [Magic Benefit Number] DESC - OPTION (RECOMPILE);'; - - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputServerName@@@', @OutputServerName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputSchemaName@@@', @OutputSchemaName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@RunID@@@', @RunID); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@GETDATE@@@', GETDATE()); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@LocalServerName@@@', CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128))); - EXEC sp_executesql @StringToExecute, N'@DaysUptime NUMERIC(23,2), @ShowAllMissingIndexRequests BIT', @DaysUptime = @DaysUptime, @ShowAllMissingIndexRequests = @ShowAllMissingIndexRequests; - - END; /* @ValidOutputLocation = 1 */ - ELSE - BEGIN - IF(@OutputType <> 'NONE') - BEGIN - WITH create_date AS ( - SELECT i.database_id, - i.schema_name, - i.[object_id], - ISNULL(NULLIF(MAX(DATEDIFF(DAY, i.create_date, SYSDATETIME())), 0), 1) AS create_days - FROM #IndexSanity AS i - GROUP BY i.database_id, i.schema_name, i.object_id - ) - SELECT - mi.database_name AS [Database Name], - mi.[schema_name] AS [Schema], - mi.table_name AS [Table], - CAST((mi.magic_benefit_number / CASE WHEN cd.create_days < @DaysUptime THEN cd.create_days ELSE @DaysUptime END) AS BIGINT) - AS [Magic Benefit Number], - mi.missing_index_details AS [Missing Index Details], - mi.avg_total_user_cost AS [Avg Query Cost], - mi.avg_user_impact AS [Est Index Improvement], - mi.user_seeks AS [Seeks], - mi.user_scans AS [Scans], - mi.unique_compiles AS [Compiles], - mi.equality_columns_with_data_type AS [Equality Columns], - mi.inequality_columns_with_data_type AS [Inequality Columns], - mi.included_columns_with_data_type AS [Included Columns], - mi.index_estimated_impact AS [Estimated Impact], - mi.create_tsql AS [Create TSQL], - mi.more_info AS [More Info], - 1 AS [Display Order], - mi.is_low, - mi.sample_query_plan AS [Sample Query Plan] - FROM #MissingIndexes AS mi - LEFT JOIN create_date AS cd - ON mi.[object_id] = cd.object_id - AND mi.database_id = cd.database_id - AND mi.schema_name = cd.schema_name - /* Minimum benefit threshold = 100k/day of uptime OR since table creation date, whichever is lower*/ - WHERE @ShowAllMissingIndexRequests=1 - OR (mi.magic_benefit_number / CASE WHEN cd.create_days < @DaysUptime THEN cd.create_days ELSE @DaysUptime END) >= 100000 - UNION ALL - SELECT - @ScriptVersionName, - N'From Your Community Volunteers' , - N'http://FirstResponderKit.org' , - 100000000000, - @DaysUptimeInsertValue, - NULL,NULL,NULL,NULL,NULL,NULL,NULL, - NULL, NULL, NULL, NULL, 0 AS [Display Order], NULL AS is_low, NULL - ORDER BY [Display Order] ASC, [Magic Benefit Number] DESC - OPTION (RECOMPILE); - END; - - - IF (@BringThePain = 1 - AND @DatabaseName IS NOT NULL - AND @GetAllDatabases = 0) - - BEGIN - EXEC sp_BlitzCache @SortOrder = 'sp_BlitzIndex', @DatabaseName = @DatabaseName, @BringThePain = 1, @QueryFilter = 'statement', @HideSummary = 1; - END; - - END; - - - - - - END; /* End @Mode=3 (index detail)*/ - SET @d = CONVERT(VARCHAR(19), GETDATE(), 121); - RAISERROR (N'finishing at %s',0,1, @d) WITH NOWAIT; -END /* End @TableName IS NULL (mode 0/1/2/3/4) */ -END TRY - -BEGIN CATCH - RAISERROR (N'Failure analyzing temp tables.', 0,1) WITH NOWAIT; - - SELECT @msg = ERROR_MESSAGE(), @ErrorSeverity = ERROR_SEVERITY(), @ErrorState = ERROR_STATE(); - - RAISERROR (@msg, - @ErrorSeverity, - @ErrorState - ); - - WHILE @@trancount > 0 - ROLLBACK; - - RETURN; - END CATCH; -GO -IF OBJECT_ID('dbo.sp_BlitzLock') IS NULL -BEGIN - EXEC ('CREATE PROCEDURE dbo.sp_BlitzLock AS RETURN 0;'); -END; -GO - -ALTER PROCEDURE - dbo.sp_BlitzLock -( - @DatabaseName sysname = NULL, - @StartDate datetime = NULL, - @EndDate datetime = NULL, - @ObjectName nvarchar(1024) = NULL, - @StoredProcName nvarchar(1024) = NULL, - @AppName sysname = NULL, - @HostName sysname = NULL, - @LoginName sysname = NULL, - @EventSessionName sysname = N'system_health', - @TargetSessionType sysname = NULL, - @VictimsOnly bit = 0, - @Debug bit = 0, - @Help bit = 0, - @Version varchar(30) = NULL OUTPUT, - @VersionDate datetime = NULL OUTPUT, - @VersionCheckMode bit = 0, - @OutputDatabaseName sysname = NULL, - @OutputSchemaName sysname = N'dbo', /*ditto as below*/ - @OutputTableName sysname = N'BlitzLock', /*put a standard here no need to check later in the script*/ - @ExportToExcel bit = 0 -) -WITH RECOMPILE -AS -BEGIN - SET STATISTICS XML OFF; - SET NOCOUNT ON; - SET XACT_ABORT OFF; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - - SELECT @Version = '8.19', @VersionDate = '20240222'; - - IF @VersionCheckMode = 1 - BEGIN - RETURN; - END; - - IF @Help = 1 - BEGIN - PRINT N' - /* - sp_BlitzLock from http://FirstResponderKit.org - - This script checks for and analyzes deadlocks from the system health session or a custom extended event path - - Variables you can use: - - @DatabaseName: If you want to filter to a specific database - - @StartDate: The date you want to start searching on, defaults to last 7 days - - @EndDate: The date you want to stop searching on, defaults to current date - - @ObjectName: If you want to filter to a specific able. - The object name has to be fully qualified ''Database.Schema.Table'' - - @StoredProcName: If you want to search for a single stored proc - The proc name has to be fully qualified ''Database.Schema.Sproc'' - - @AppName: If you want to filter to a specific application - - @HostName: If you want to filter to a specific host - - @LoginName: If you want to filter to a specific login - - @EventSessionName: If you want to point this at an XE session rather than the system health session. - - @TargetSessionType: Can be ''ring_buffer'' or ''event_file''. Leave NULL to auto-detect. - - @OutputDatabaseName: If you want to output information to a specific database - - @OutputSchemaName: Specify a schema name to output information to a specific Schema - - @OutputTableName: Specify table name to to output information to a specific table - - To learn more, visit http://FirstResponderKit.org where you can download new - versions for free, watch training videos on how it works, get more info on - the findings, contribute your own code, and more. - - Known limitations of this version: - - Only SQL Server 2012 and newer is supported - - If your tables have weird characters in them (https://en.wikipedia.org/wiki/List_of_xml_and_HTML_character_entity_references) you may get errors trying to parse the xml. - I took a long look at this one, and: - 1) Trying to account for all the weird places these could crop up is a losing effort. - 2) Replace is slow af on lots of xml. - - Unknown limitations of this version: - - None. (If we knew them, they would be known. Duh.) - - MIT License - - Copyright (c) Brent Ozar Unlimited - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE. - - */'; - - RETURN; - END; /* @Help = 1 */ - - /*Declare local variables used in the procudure*/ - DECLARE - @DatabaseId int = - DB_ID(@DatabaseName), - @ProductVersion nvarchar(128) = - CAST(SERVERPROPERTY('ProductVersion') AS nvarchar(128)), - @ProductVersionMajor float = - SUBSTRING - ( - CAST(SERVERPROPERTY('ProductVersion') AS nvarchar(128)), - 1, - CHARINDEX('.', CAST(SERVERPROPERTY('ProductVersion') AS nvarchar(128))) + 1 - ), - @ProductVersionMinor int = - PARSENAME - ( - CONVERT - ( - varchar(32), - CAST(SERVERPROPERTY('ProductVersion') AS nvarchar(128)) - ), - 2 - ), - @ObjectFullName nvarchar(MAX) = N'', - @Azure bit = - CASE - WHEN - ( - SELECT - CONVERT - ( - integer, - SERVERPROPERTY('EngineEdition') - ) - ) = 5 - THEN 1 - ELSE 0 - END, - @MI bit = - CASE - WHEN - ( - SELECT - CONVERT - ( - integer, - SERVERPROPERTY('EngineEdition') - ) - ) = 8 - THEN 1 - ELSE 0 - END, - @RDS bit = - CASE - WHEN LEFT(CAST(SERVERPROPERTY('ComputerNamePhysicalNetBIOS') AS varchar(8000)), 8) <> 'EC2AMAZ-' - AND LEFT(CAST(SERVERPROPERTY('MachineName') AS varchar(8000)), 8) <> 'EC2AMAZ-' - AND DB_ID('rdsadmin') IS NULL - THEN 0 - ELSE 1 - END, - @d varchar(40) = '', - @StringToExecute nvarchar(4000) = N'', - @StringToExecuteParams nvarchar(500) = N'', - @r sysname = NULL, - @OutputTableFindings nvarchar(100) = N'[BlitzLockFindings]', - @DeadlockCount int = 0, - @ServerName sysname = @@SERVERNAME, - @OutputDatabaseCheck bit = -1, - @SessionId int = 0, - @TargetSessionId int = 0, - @FileName nvarchar(4000) = N'', - @inputbuf_bom nvarchar(1) = CONVERT(nvarchar(1), 0x0a00, 0), - @deadlock_result nvarchar(MAX) = N'', - @StartDateOriginal datetime = @StartDate, - @EndDateOriginal datetime = @EndDate, - @StartDateUTC datetime, - @EndDateUTC datetime; - - /*Temporary objects used in the procedure*/ - DECLARE - @sysAssObjId AS table - ( - database_id int, - partition_id bigint, - schema_name sysname, - table_name sysname - ); - - CREATE TABLE - #x - ( - x xml NOT NULL - DEFAULT N'x' - ); - - CREATE TABLE - #deadlock_data - ( - deadlock_xml xml NOT NULL - DEFAULT N'x' - ); - - CREATE TABLE - #t - ( - id int NOT NULL - ); - - CREATE TABLE - #deadlock_findings - ( - id int IDENTITY PRIMARY KEY, - check_id int NOT NULL, - database_name nvarchar(256), - object_name nvarchar(1000), - finding_group nvarchar(100), - finding nvarchar(4000), - sort_order bigint - ); - - /*Set these to some sane defaults if NULLs are passed in*/ - /*Normally I'd hate this, but we RECOMPILE everything*/ - - SELECT - @StartDate = - CASE - WHEN @StartDate IS NULL - THEN - DATEADD - ( - MINUTE, - DATEDIFF - ( - MINUTE, - SYSDATETIME(), - GETUTCDATE() - ), - DATEADD - ( - DAY, - -7, - SYSDATETIME() - ) - ) - ELSE - DATEADD - ( - MINUTE, - DATEDIFF - ( - MINUTE, - SYSDATETIME(), - GETUTCDATE() - ), - @StartDate - ) - END, - @EndDate = - CASE - WHEN @EndDate IS NULL - THEN - DATEADD - ( - MINUTE, - DATEDIFF - ( - MINUTE, - SYSDATETIME(), - GETUTCDATE() - ), - SYSDATETIME() - ) - ELSE - DATEADD - ( - MINUTE, - DATEDIFF - ( - MINUTE, - SYSDATETIME(), - GETUTCDATE() - ), - @EndDate - ) - END; - - SELECT - @StartDateUTC = @StartDate, - @EndDateUTC = @EndDate; - - IF - ( - @MI = 1 - AND @EventSessionName = N'system_health' - AND @TargetSessionType IS NULL - ) - BEGIN - SET - @TargetSessionType = N'ring_buffer'; - END; - - IF @Azure = 0 - BEGIN - IF NOT EXISTS - ( - SELECT - 1/0 - FROM sys.server_event_sessions AS ses - JOIN sys.dm_xe_sessions AS dxs - ON dxs.name = ses.name - WHERE ses.name = @EventSessionName - AND dxs.create_time IS NOT NULL - ) - BEGIN - RAISERROR('A session with the name %s does not exist or is not currently active.', 11, 1, @EventSessionName) WITH NOWAIT; - RETURN; - END; - END; - - IF @Azure = 1 - BEGIN - IF NOT EXISTS - ( - SELECT - 1/0 - FROM sys.database_event_sessions AS ses - JOIN sys.dm_xe_database_sessions AS dxs - ON dxs.name = ses.name - WHERE ses.name = @EventSessionName - AND dxs.create_time IS NOT NULL - ) - BEGIN - RAISERROR('A session with the name %s does not exist or is not currently active.', 11, 1, @EventSessionName) WITH NOWAIT; - RETURN; - END; - END; - - IF @OutputDatabaseName IS NOT NULL - BEGIN /*IF databaseName is set, do some sanity checks and put [] around def.*/ - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('@OutputDatabaseName set to %s, checking validity at %s', 0, 1, @OutputDatabaseName, @d) WITH NOWAIT; - - IF NOT EXISTS - ( - SELECT - 1/0 - FROM sys.databases AS d - WHERE d.name = @OutputDatabaseName - ) /*If database is invalid raiserror and set bitcheck*/ - BEGIN - RAISERROR('Database Name (%s) for output of table is invalid please, Output to Table will not be performed', 0, 1, @OutputDatabaseName) WITH NOWAIT; - SET @OutputDatabaseCheck = -1; /* -1 invalid/false, 0 = good/true */ - END; - ELSE - BEGIN - SET @OutputDatabaseCheck = 0; - - SELECT - @StringToExecute = - N'SELECT @r = o.name FROM ' + - @OutputDatabaseName + - N'.sys.objects AS o WHERE o.type_desc = N''USER_TABLE'' AND o.name = ' + - QUOTENAME - ( - @OutputTableName, - N'''' - ) + - N' AND o.schema_id = SCHEMA_ID(' + - QUOTENAME - ( - @OutputSchemaName, - N'''' - ) + - N');', - @StringToExecuteParams = - N'@r sysname OUTPUT'; - - IF @Debug = 1 BEGIN PRINT @StringToExecute; END; - EXEC sys.sp_executesql - @StringToExecute, - @StringToExecuteParams, - @r OUTPUT; - - IF @Debug = 1 - BEGIN - RAISERROR('@r is set to: %s for schema name %s and table name %s', 0, 1, @r, @OutputSchemaName, @OutputTableName) WITH NOWAIT; - END; - - /*protection spells*/ - SELECT - @ObjectFullName = - QUOTENAME(@OutputDatabaseName) + - N'.' + - QUOTENAME(@OutputSchemaName) + - N'.' + - QUOTENAME(@OutputTableName), - @OutputDatabaseName = - QUOTENAME(@OutputDatabaseName), - @OutputTableName = - QUOTENAME(@OutputTableName), - @OutputSchemaName = - QUOTENAME(@OutputSchemaName); - - IF (@r IS NOT NULL) /*if it is not null, there is a table, so check for newly added columns*/ - BEGIN - /* If the table doesn't have the new spid column, add it. See Github #3101. */ - SET @StringToExecute = - N'IF NOT EXISTS (SELECT 1/0 FROM ' + - @OutputDatabaseName + - N'.sys.all_columns AS o WHERE o.object_id = (OBJECT_ID(''' + - @ObjectFullName + - N''')) AND o.name = N''spid'') - /*Add spid column*/ - ALTER TABLE ' + - @ObjectFullName + - N' ADD spid smallint NULL;'; - - IF @Debug = 1 BEGIN PRINT @StringToExecute; END; - EXEC sys.sp_executesql - @StringToExecute; - - /* If the table doesn't have the new wait_resource column, add it. See Github #3101. */ - SET @StringToExecute = - N'IF NOT EXISTS (SELECT 1/0 FROM ' + - @OutputDatabaseName + - N'.sys.all_columns AS o WHERE o.object_id = (OBJECT_ID(''' + - @ObjectFullName + - N''')) AND o.name = N''wait_resource'') - /*Add wait_resource column*/ - ALTER TABLE ' + - @ObjectFullName + - N' ADD wait_resource nvarchar(MAX) NULL;'; - - IF @Debug = 1 BEGIN PRINT @StringToExecute; END; - EXEC sys.sp_executesql - @StringToExecute; - - /* If the table doesn't have the new client option column, add it. See Github #3101. */ - SET @StringToExecute = - N'IF NOT EXISTS (SELECT 1/0 FROM ' + - @OutputDatabaseName + - N'.sys.all_columns AS o WHERE o.object_id = (OBJECT_ID(''' + - @ObjectFullName + - N''')) AND o.name = N''client_option_1'') - /*Add wait_resource column*/ - ALTER TABLE ' + - @ObjectFullName + - N' ADD client_option_1 varchar(500) NULL;'; - - IF @Debug = 1 BEGIN PRINT @StringToExecute; END; - EXEC sys.sp_executesql - @StringToExecute; - - /* If the table doesn't have the new client option column, add it. See Github #3101. */ - SET @StringToExecute = - N'IF NOT EXISTS (SELECT 1/0 FROM ' + - @OutputDatabaseName + - N'.sys.all_columns AS o WHERE o.object_id = (OBJECT_ID(''' + - @ObjectFullName + - N''')) AND o.name = N''client_option_2'') - /*Add wait_resource column*/ - ALTER TABLE ' + - @ObjectFullName + - N' ADD client_option_2 varchar(500) NULL;'; - - IF @Debug = 1 BEGIN PRINT @StringToExecute; END; - EXEC sys.sp_executesql - @StringToExecute; - - /* If the table doesn't have the new lock mode column, add it. See Github #3101. */ - SET @StringToExecute = - N'IF NOT EXISTS (SELECT 1/0 FROM ' + - @OutputDatabaseName + - N'.sys.all_columns AS o WHERE o.object_id = (OBJECT_ID(''' + - @ObjectFullName + - N''')) AND o.name = N''lock_mode'') - /*Add wait_resource column*/ - ALTER TABLE ' + - @ObjectFullName + - N' ADD lock_mode nvarchar(256) NULL;'; - - IF @Debug = 1 BEGIN PRINT @StringToExecute; END; - EXEC sys.sp_executesql - @StringToExecute; - - /* If the table doesn't have the new status column, add it. See Github #3101. */ - SET @StringToExecute = - N'IF NOT EXISTS (SELECT 1/0 FROM ' + - @OutputDatabaseName + - N'.sys.all_columns AS o WHERE o.object_id = (OBJECT_ID(''' + - @ObjectFullName + - N''')) AND o.name = N''status'') - /*Add wait_resource column*/ - ALTER TABLE ' + - @ObjectFullName + - N' ADD status nvarchar(256) NULL;'; - - IF @Debug = 1 BEGIN PRINT @StringToExecute; END; - EXEC sys.sp_executesql - @StringToExecute; - END; - ELSE /* end if @r is not null. if it is null there is no table, create it from above execution */ - BEGIN - SELECT - @StringToExecute = - N'USE ' + - @OutputDatabaseName + - N'; - CREATE TABLE ' + - @OutputSchemaName + - N'.' + - @OutputTableName + - N' ( - ServerName nvarchar(256), - deadlock_type nvarchar(256), - event_date datetime, - database_name nvarchar(256), - spid smallint, - deadlock_group nvarchar(256), - query xml, - object_names xml, - isolation_level nvarchar(256), - owner_mode nvarchar(256), - waiter_mode nvarchar(256), - lock_mode nvarchar(256), - transaction_count bigint, - client_option_1 varchar(500), - client_option_2 varchar(500), - login_name nvarchar(256), - host_name nvarchar(256), - client_app nvarchar(1024), - wait_time bigint, - wait_resource nvarchar(max), - priority smallint, - log_used bigint, - last_tran_started datetime, - last_batch_started datetime, - last_batch_completed datetime, - transaction_name nvarchar(256), - status nvarchar(256), - owner_waiter_type nvarchar(256), - owner_activity nvarchar(256), - owner_waiter_activity nvarchar(256), - owner_merging nvarchar(256), - owner_spilling nvarchar(256), - owner_waiting_to_close nvarchar(256), - waiter_waiter_type nvarchar(256), - waiter_owner_activity nvarchar(256), - waiter_waiter_activity nvarchar(256), - waiter_merging nvarchar(256), - waiter_spilling nvarchar(256), - waiter_waiting_to_close nvarchar(256), - deadlock_graph xml - )'; - - IF @Debug = 1 BEGIN PRINT @StringToExecute; END; - EXEC sys.sp_executesql - @StringToExecute; - - /*table created.*/ - SELECT - @StringToExecute = - N'SELECT @r = o.name FROM ' + - @OutputDatabaseName + - N'.sys.objects AS o - WHERE o.type_desc = N''USER_TABLE'' - AND o.name = N''BlitzLockFindings''', - @StringToExecuteParams = - N'@r sysname OUTPUT'; - - IF @Debug = 1 BEGIN PRINT @StringToExecute; END; - EXEC sys.sp_executesql - @StringToExecute, - @StringToExecuteParams, - @r OUTPUT; - - IF (@r IS NULL) /*if table does not exist*/ - BEGIN - SELECT - @OutputTableFindings = - QUOTENAME(N'BlitzLockFindings'), - @StringToExecute = - N'USE ' + - @OutputDatabaseName + - N'; - CREATE TABLE ' + - @OutputSchemaName + - N'.' + - @OutputTableFindings + - N' ( - ServerName nvarchar(256), - check_id INT, - database_name nvarchar(256), - object_name nvarchar(1000), - finding_group nvarchar(100), - finding nvarchar(4000) - );'; - - IF @Debug = 1 BEGIN PRINT @StringToExecute; END; - EXEC sys.sp_executesql - @StringToExecute; - END; - END; - - /*create synonym for deadlockfindings.*/ - IF EXISTS - ( - SELECT - 1/0 - FROM sys.objects AS o - WHERE o.name = N'DeadlockFindings' - AND o.type_desc = N'SYNONYM' - ) - BEGIN - RAISERROR('Found synonym DeadlockFindings, dropping', 0, 1) WITH NOWAIT; - DROP SYNONYM DeadlockFindings; - END; - - RAISERROR('Creating synonym DeadlockFindings', 0, 1) WITH NOWAIT; - SET @StringToExecute = - N'CREATE SYNONYM DeadlockFindings FOR ' + - @OutputDatabaseName + - N'.' + - @OutputSchemaName + - N'.' + - @OutputTableFindings; - - IF @Debug = 1 BEGIN PRINT @StringToExecute; END; - EXEC sys.sp_executesql - @StringToExecute; - - /*create synonym for deadlock table.*/ - IF EXISTS - ( - SELECT - 1/0 - FROM sys.objects AS o - WHERE o.name = N'DeadLockTbl' - AND o.type_desc = N'SYNONYM' - ) - BEGIN - RAISERROR('Found synonym DeadLockTbl, dropping', 0, 1) WITH NOWAIT; - DROP SYNONYM DeadLockTbl; - END; - - RAISERROR('Creating synonym DeadLockTbl', 0, 1) WITH NOWAIT; - SET @StringToExecute = - N'CREATE SYNONYM DeadLockTbl FOR ' + - @OutputDatabaseName + - N'.' + - @OutputSchemaName + - N'.' + - @OutputTableName; - - IF @Debug = 1 BEGIN PRINT @StringToExecute; END; - EXEC sys.sp_executesql - @StringToExecute; - END; - END; - - /* WITH ROWCOUNT doesn't work on Amazon RDS - see: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2037 */ - IF @RDS = 0 - BEGIN; - BEGIN TRY; - RAISERROR('@RDS = 0, updating #t with high row and page counts', 0, 1) WITH NOWAIT; - UPDATE STATISTICS - #t - WITH - ROWCOUNT = 9223372036854775807, - PAGECOUNT = 9223372036854775807; - END TRY - BEGIN CATCH; - /* Misleading error returned, if run without permissions to update statistics the error returned is "Cannot find object". - Catching specific error, and returning message with better info. If any other error is returned, then throw as normal */ - IF (ERROR_NUMBER() = 1088) - BEGIN; - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Cannot run UPDATE STATISTICS on a #temp table without db_owner or sysadmin permissions', 0, 1) WITH NOWAIT; - END; - ELSE - BEGIN; - THROW; - END; - END CATCH; - END; - - /*If @TargetSessionType, we need to figure out if it's ring buffer or event file*/ - /*Azure has differently named views, so we need to separate. Thanks, Azure.*/ - - IF - ( - @Azure = 0 - AND @TargetSessionType IS NULL - ) - BEGIN - RAISERROR('@TargetSessionType is NULL, assigning for non-Azure instance', 0, 1) WITH NOWAIT; - - SELECT TOP (1) - @TargetSessionType = t.target_name - FROM sys.dm_xe_sessions AS s - JOIN sys.dm_xe_session_targets AS t - ON s.address = t.event_session_address - WHERE s.name = @EventSessionName - AND t.target_name IN (N'event_file', N'ring_buffer') - ORDER BY t.target_name - OPTION(RECOMPILE); - - RAISERROR('@TargetSessionType assigned as %s for non-Azure', 0, 1, @TargetSessionType) WITH NOWAIT; - END; - - IF - ( - @Azure = 1 - AND @TargetSessionType IS NULL - ) - BEGIN - RAISERROR('@TargetSessionType is NULL, assigning for Azure instance', 0, 1) WITH NOWAIT; - - SELECT TOP (1) - @TargetSessionType = t.target_name - FROM sys.dm_xe_database_sessions AS s - JOIN sys.dm_xe_database_session_targets AS t - ON s.address = t.event_session_address - WHERE s.name = @EventSessionName - AND t.target_name IN (N'event_file', N'ring_buffer') - ORDER BY t.target_name - OPTION(RECOMPILE); - - RAISERROR('@TargetSessionType assigned as %s for Azure', 0, 1, @TargetSessionType) WITH NOWAIT; - END; - - - /*The system health stuff gets handled different from user extended events.*/ - /*These next sections deal with user events, dependent on target.*/ - - /*If ring buffers*/ - IF - ( - @TargetSessionType LIKE N'ring%' - AND @EventSessionName NOT LIKE N'system_health%' - ) - BEGIN - IF @Azure = 0 - BEGIN - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('@TargetSessionType is ring_buffer, inserting XML for non-Azure at %s', 0, 1, @d) WITH NOWAIT; - - INSERT - #x WITH(TABLOCKX) - ( - x - ) - SELECT - x = TRY_CAST(t.target_data AS xml) - FROM sys.dm_xe_session_targets AS t - JOIN sys.dm_xe_sessions AS s - ON s.address = t.event_session_address - WHERE s.name = @EventSessionName - AND t.target_name = N'ring_buffer' - OPTION(RECOMPILE); - - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - END; - - IF @Azure = 1 - BEGIN - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('@TargetSessionType is ring_buffer, inserting XML for Azure at %s', 0, 1, @d) WITH NOWAIT; - - INSERT - #x WITH(TABLOCKX) - ( - x - ) - SELECT - x = TRY_CAST(t.target_data AS xml) - FROM sys.dm_xe_database_session_targets AS t - JOIN sys.dm_xe_database_sessions AS s - ON s.address = t.event_session_address - WHERE s.name = @EventSessionName - AND t.target_name = N'ring_buffer' - OPTION(RECOMPILE); - - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - END; - END; - - - /*If event file*/ - IF - ( - @TargetSessionType LIKE N'event%' - AND @EventSessionName NOT LIKE N'system_health%' - ) - BEGIN - IF @Azure = 0 - BEGIN - RAISERROR('@TargetSessionType is event_file, assigning XML for non-Azure', 0, 1) WITH NOWAIT; - - SELECT - @SessionId = t.event_session_id, - @TargetSessionId = t.target_id - FROM sys.server_event_session_targets AS t - JOIN sys.server_event_sessions AS s - ON s.event_session_id = t.event_session_id - WHERE t.name = @TargetSessionType - AND s.name = @EventSessionName - OPTION(RECOMPILE); - - /*We get the file name automatically, here*/ - RAISERROR('Assigning @FileName...', 0, 1) WITH NOWAIT; - SELECT - @FileName = - CASE - WHEN f.file_name LIKE N'%.xel' - THEN REPLACE(f.file_name, N'.xel', N'*.xel') - ELSE f.file_name + N'*.xel' - END - FROM - ( - SELECT - file_name = - CONVERT(nvarchar(4000), f.value) - FROM sys.server_event_session_fields AS f - WHERE f.event_session_id = @SessionId - AND f.object_id = @TargetSessionId - AND f.name = N'filename' - ) AS f - OPTION(RECOMPILE); - END; - - IF @Azure = 1 - BEGIN - RAISERROR('@TargetSessionType is event_file, assigning XML for Azure', 0, 1) WITH NOWAIT; - SELECT - @SessionId = - t.event_session_address, - @TargetSessionId = - t.target_name - FROM sys.dm_xe_database_session_targets t - JOIN sys.dm_xe_database_sessions s - ON s.address = t.event_session_address - WHERE t.target_name = @TargetSessionType - AND s.name = @EventSessionName - OPTION(RECOMPILE); - - /*We get the file name automatically, here*/ - RAISERROR('Assigning @FileName...', 0, 1) WITH NOWAIT; - SELECT - @FileName = - CASE - WHEN f.file_name LIKE N'%.xel' - THEN REPLACE(f.file_name, N'.xel', N'*.xel') - ELSE f.file_name + N'*.xel' - END - FROM - ( - SELECT - file_name = - CONVERT(nvarchar(4000), f.value) - FROM sys.server_event_session_fields AS f - WHERE f.event_session_id = @SessionId - AND f.object_id = @TargetSessionId - AND f.name = N'filename' - ) AS f - OPTION(RECOMPILE); - END; - - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Reading for event_file %s', 0, 1, @FileName) WITH NOWAIT; - - INSERT - #x WITH(TABLOCKX) - ( - x - ) - SELECT - x = TRY_CAST(f.event_data AS xml) - FROM sys.fn_xe_file_target_read_file(@FileName, NULL, NULL, NULL) AS f - LEFT JOIN #t AS t - ON 1 = 1 - OPTION(RECOMPILE); - - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - END; - - /*The XML is parsed differently if it comes from the event file or ring buffer*/ - - /*If ring buffers*/ - IF - ( - @TargetSessionType LIKE N'ring%' - AND @EventSessionName NOT LIKE N'system_health%' - ) - BEGIN - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Inserting to #deadlock_data for ring buffer data', 0, 1) WITH NOWAIT; - - INSERT - #deadlock_data WITH(TABLOCKX) - ( - deadlock_xml - ) - SELECT - deadlock_xml = - e.x.query(N'.') - FROM #x AS x - LEFT JOIN #t AS t - ON 1 = 1 - CROSS APPLY x.x.nodes('/RingBufferTarget/event') AS e(x) - WHERE - ( - e.x.exist('@name[ .= "xml_deadlock_report"]') = 1 - OR e.x.exist('@name[ .= "database_xml_deadlock_report"]') = 1 - OR e.x.exist('@name[ .= "xml_deadlock_report_filtered"]') = 1 - ) - AND e.x.exist('@timestamp[. >= sql:variable("@StartDate") and .< sql:variable("@EndDate")]') = 1 - OPTION(RECOMPILE); - - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - END; - - /*If event file*/ - IF - ( - @TargetSessionType LIKE N'event_file%' - AND @EventSessionName NOT LIKE N'system_health%' - ) - BEGIN - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Inserting to #deadlock_data for event file data', 0, 1) WITH NOWAIT; - - IF @Debug = 1 BEGIN SET STATISTICS XML ON; END; - - INSERT - #deadlock_data WITH(TABLOCKX) - ( - deadlock_xml - ) - SELECT - deadlock_xml = - e.x.query('.') - FROM #x AS x - LEFT JOIN #t AS t - ON 1 = 1 - CROSS APPLY x.x.nodes('/event') AS e(x) - WHERE - ( - e.x.exist('@name[ .= "xml_deadlock_report"]') = 1 - OR e.x.exist('@name[ .= "database_xml_deadlock_report"]') = 1 - OR e.x.exist('@name[ .= "xml_deadlock_report_filtered"]') = 1 - ) - AND e.x.exist('@timestamp[. >= sql:variable("@StartDate") and .< sql:variable("@EndDate")]') = 1 - OPTION(RECOMPILE); - - IF @Debug = 1 BEGIN SET STATISTICS XML OFF; END; - - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - END; - - /*This section deals with event file*/ - IF - ( - @TargetSessionType LIKE N'event%' - AND @EventSessionName LIKE N'system_health%' - ) - BEGIN - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Grab the initial set of xml to parse at %s', 0, 1, @d) WITH NOWAIT; - - IF @Debug = 1 BEGIN SET STATISTICS XML ON; END; - - SELECT - xml.deadlock_xml - INTO #xml - FROM - ( - SELECT - deadlock_xml = - TRY_CAST(fx.event_data AS xml) - FROM sys.fn_xe_file_target_read_file(N'system_health*.xel', NULL, NULL, NULL) AS fx - LEFT JOIN #t AS t - ON 1 = 1 - WHERE fx.object_name = N'xml_deadlock_report' - ) AS xml - CROSS APPLY xml.deadlock_xml.nodes('/event') AS e(x) - WHERE 1 = 1 - AND e.x.exist('@timestamp[. >= sql:variable("@StartDate") and .< sql:variable("@EndDate")]') = 1 - OPTION(RECOMPILE); - - INSERT - #deadlock_data WITH(TABLOCKX) - SELECT - deadlock_xml = - xml.deadlock_xml - FROM #xml AS xml - LEFT JOIN #t AS t - ON 1 = 1 - WHERE xml.deadlock_xml IS NOT NULL - OPTION(RECOMPILE); - - IF @Debug = 1 BEGIN SET STATISTICS XML OFF; END; - - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - END; - - /*Parse process and input buffer xml*/ - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Initial Parse process and input buffer xml %s', 0, 1, @d) WITH NOWAIT; - - SELECT - d1.deadlock_xml, - event_date = d1.deadlock_xml.value('(event/@timestamp)[1]', 'datetime2'), - victim_id = d1.deadlock_xml.value('(//deadlock/victim-list/victimProcess/@id)[1]', 'nvarchar(256)'), - is_parallel = d1.deadlock_xml.exist('//deadlock/resource-list/exchangeEvent'), - is_parallel_batch = d1.deadlock_xml.exist('//deadlock/resource-list/SyncPoint'), - deadlock_graph = d1.deadlock_xml.query('/event/data/value/deadlock') - INTO #dd - FROM #deadlock_data AS d1 - LEFT JOIN #t AS t - ON 1 = 1 - OPTION(RECOMPILE); - - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Final Parse process and input buffer xml %s', 0, 1, @d) WITH NOWAIT; - - SELECT - q.event_date, - q.victim_id, - is_parallel = - CONVERT(bit, q.is_parallel), - q.deadlock_graph, - q.id, - q.spid, - q.database_id, - database_name = - ISNULL - ( - DB_NAME(q.database_id), - N'UNKNOWN' - ), - q.current_database_name, - q.priority, - q.log_used, - q.wait_resource, - q.wait_time, - q.transaction_name, - q.last_tran_started, - q.last_batch_started, - q.last_batch_completed, - q.lock_mode, - q.status, - q.transaction_count, - q.client_app, - q.host_name, - q.login_name, - q.isolation_level, - client_option_1 = - SUBSTRING - ( - CASE WHEN q.clientoption1 & 1 = 1 THEN ', DISABLE_DEF_CNST_CHECK' ELSE '' END + - CASE WHEN q.clientoption1 & 2 = 2 THEN ', IMPLICIT_TRANSACTIONS' ELSE '' END + - CASE WHEN q.clientoption1 & 4 = 4 THEN ', CURSOR_CLOSE_ON_COMMIT' ELSE '' END + - CASE WHEN q.clientoption1 & 8 = 8 THEN ', ANSI_WARNINGS' ELSE '' END + - CASE WHEN q.clientoption1 & 16 = 16 THEN ', ANSI_PADDING' ELSE '' END + - CASE WHEN q.clientoption1 & 32 = 32 THEN ', ANSI_NULLS' ELSE '' END + - CASE WHEN q.clientoption1 & 64 = 64 THEN ', ARITHABORT' ELSE '' END + - CASE WHEN q.clientoption1 & 128 = 128 THEN ', ARITHIGNORE' ELSE '' END + - CASE WHEN q.clientoption1 & 256 = 256 THEN ', QUOTED_IDENTIFIER' ELSE '' END + - CASE WHEN q.clientoption1 & 512 = 512 THEN ', NOCOUNT' ELSE '' END + - CASE WHEN q.clientoption1 & 1024 = 1024 THEN ', ANSI_NULL_DFLT_ON' ELSE '' END + - CASE WHEN q.clientoption1 & 2048 = 2048 THEN ', ANSI_NULL_DFLT_OFF' ELSE '' END + - CASE WHEN q.clientoption1 & 4096 = 4096 THEN ', CONCAT_NULL_YIELDS_NULL' ELSE '' END + - CASE WHEN q.clientoption1 & 8192 = 8192 THEN ', NUMERIC_ROUNDABORT' ELSE '' END + - CASE WHEN q.clientoption1 & 16384 = 16384 THEN ', XACT_ABORT' ELSE '' END, - 3, - 500 - ), - client_option_2 = - SUBSTRING - ( - CASE WHEN q.clientoption2 & 1024 = 1024 THEN ', DB CHAINING' ELSE '' END + - CASE WHEN q.clientoption2 & 2048 = 2048 THEN ', NUMERIC ROUNDABORT' ELSE '' END + - CASE WHEN q.clientoption2 & 4096 = 4096 THEN ', ARITHABORT' ELSE '' END + - CASE WHEN q.clientoption2 & 8192 = 8192 THEN ', ANSI PADDING' ELSE '' END + - CASE WHEN q.clientoption2 & 16384 = 16384 THEN ', ANSI NULL DEFAULT' ELSE '' END + - CASE WHEN q.clientoption2 & 65536 = 65536 THEN ', CONCAT NULL YIELDS NULL' ELSE '' END + - CASE WHEN q.clientoption2 & 131072 = 131072 THEN ', RECURSIVE TRIGGERS' ELSE '' END + - CASE WHEN q.clientoption2 & 1048576 = 1048576 THEN ', DEFAULT TO LOCAL CURSOR' ELSE '' END + - CASE WHEN q.clientoption2 & 8388608 = 8388608 THEN ', QUOTED IDENTIFIER' ELSE '' END + - CASE WHEN q.clientoption2 & 16777216 = 16777216 THEN ', AUTO CREATE STATISTICS' ELSE '' END + - CASE WHEN q.clientoption2 & 33554432 = 33554432 THEN ', CURSOR CLOSE ON COMMIT' ELSE '' END + - CASE WHEN q.clientoption2 & 67108864 = 67108864 THEN ', ANSI NULLS' ELSE '' END + - CASE WHEN q.clientoption2 & 268435456 = 268435456 THEN ', ANSI WARNINGS' ELSE '' END + - CASE WHEN q.clientoption2 & 536870912 = 536870912 THEN ', FULL TEXT ENABLED' ELSE '' END + - CASE WHEN q.clientoption2 & 1073741824 = 1073741824 THEN ', AUTO UPDATE STATISTICS' ELSE '' END + - CASE WHEN q.clientoption2 & 1469283328 = 1469283328 THEN ', ALL SETTABLE OPTIONS' ELSE '' END, - 3, - 500 - ), - q.process_xml - INTO #deadlock_process - FROM - ( - SELECT - dd.deadlock_xml, - event_date = - DATEADD - ( - MINUTE, - DATEDIFF - ( - MINUTE, - GETUTCDATE(), - SYSDATETIME() - ), - dd.event_date - ), - dd.victim_id, - is_parallel = - CONVERT(tinyint, dd.is_parallel) + - CONVERT(tinyint, dd.is_parallel_batch), - dd.deadlock_graph, - id = ca.dp.value('@id', 'nvarchar(256)'), - spid = ca.dp.value('@spid', 'smallint'), - database_id = ca.dp.value('@currentdb', 'bigint'), - current_database_name = ca.dp.value('@currentdbname', 'nvarchar(256)'), - priority = ca.dp.value('@priority', 'smallint'), - log_used = ca.dp.value('@logused', 'bigint'), - wait_resource = ca.dp.value('@waitresource', 'nvarchar(256)'), - wait_time = ca.dp.value('@waittime', 'bigint'), - transaction_name = ca.dp.value('@transactionname', 'nvarchar(256)'), - last_tran_started = ca.dp.value('@lasttranstarted', 'datetime'), - last_batch_started = ca.dp.value('@lastbatchstarted', 'datetime'), - last_batch_completed = ca.dp.value('@lastbatchcompleted', 'datetime'), - lock_mode = ca.dp.value('@lockMode', 'nvarchar(256)'), - status = ca.dp.value('@status', 'nvarchar(256)'), - transaction_count = ca.dp.value('@trancount', 'bigint'), - client_app = ca.dp.value('@clientapp', 'nvarchar(1024)'), - host_name = ca.dp.value('@hostname', 'nvarchar(256)'), - login_name = ca.dp.value('@loginname', 'nvarchar(256)'), - isolation_level = ca.dp.value('@isolationlevel', 'nvarchar(256)'), - clientoption1 = ca.dp.value('@clientoption1', 'bigint'), - clientoption2 = ca.dp.value('@clientoption2', 'bigint'), - process_xml = ISNULL(ca.dp.query(N'.'), N'') - FROM #dd AS dd - CROSS APPLY dd.deadlock_xml.nodes('//deadlock/process-list/process') AS ca(dp) - WHERE (ca.dp.exist('@currentdb[. = sql:variable("@DatabaseId")]') = 1 OR @DatabaseName IS NULL) - AND (ca.dp.exist('@clientapp[. = sql:variable("@AppName")]') = 1 OR @AppName IS NULL) - AND (ca.dp.exist('@hostname[. = sql:variable("@HostName")]') = 1 OR @HostName IS NULL) - AND (ca.dp.exist('@loginname[. = sql:variable("@LoginName")]') = 1 OR @LoginName IS NULL) - ) AS q - LEFT JOIN #t AS t - ON 1 = 1 - OPTION(RECOMPILE); - - - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - /*Parse execution stack xml*/ - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Parse execution stack xml %s', 0, 1, @d) WITH NOWAIT; - - SELECT DISTINCT - dp.id, - dp.event_date, - proc_name = ca.dp.value('@procname', 'nvarchar(1024)'), - sql_handle = ca.dp.value('@sqlhandle', 'nvarchar(131)') - INTO #deadlock_stack - FROM #deadlock_process AS dp - CROSS APPLY dp.process_xml.nodes('//executionStack/frame') AS ca(dp) - WHERE (ca.dp.exist('@procname[. = sql:variable("@StoredProcName")]') = 1 OR @StoredProcName IS NULL) - AND ca.dp.exist('@sqlhandle[ .= "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"]') = 0 - OPTION(RECOMPILE); - - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - /*Grab the full resource list*/ - SET @d = CONVERT(varchar(40), GETDATE(), 109); - - RAISERROR('Grab the full resource list %s', 0, 1, @d) WITH NOWAIT; - - SELECT - event_date = - DATEADD - ( - MINUTE, - DATEDIFF - ( - MINUTE, - GETUTCDATE(), - SYSDATETIME() - ), - dr.event_date - ), - dr.victim_id, - dr.resource_xml - INTO - #deadlock_resource - FROM - ( - SELECT - event_date = dd.deadlock_xml.value('(event/@timestamp)[1]', 'datetime2'), - victim_id = dd.deadlock_xml.value('(//deadlock/victim-list/victimProcess/@id)[1]', 'nvarchar(256)'), - resource_xml = ISNULL(ca.dp.query(N'.'), N'') - FROM #deadlock_data AS dd - CROSS APPLY dd.deadlock_xml.nodes('//deadlock/resource-list') AS ca(dp) - ) AS dr - OPTION(RECOMPILE); - - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - /*Parse object locks*/ - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Parse object locks %s', 0, 1, @d) WITH NOWAIT; - - SELECT DISTINCT - ca.event_date, - ca.database_id, - database_name = - ISNULL - ( - DB_NAME(ca.database_id), - N'UNKNOWN' - ), - object_name = - REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE( - REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE( - REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE( - ca.object_name COLLATE Latin1_General_BIN2, - NCHAR(31), N'?'), NCHAR(30), N'?'), NCHAR(29), N'?'), NCHAR(28), N'?'), NCHAR(27), N'?'), - NCHAR(26), N'?'), NCHAR(25), N'?'), NCHAR(24), N'?'), NCHAR(23), N'?'), NCHAR(22), N'?'), - NCHAR(21), N'?'), NCHAR(20), N'?'), NCHAR(19), N'?'), NCHAR(18), N'?'), NCHAR(17), N'?'), - NCHAR(16), N'?'), NCHAR(15), N'?'), NCHAR(14), N'?'), NCHAR(12), N'?'), NCHAR(11), N'?'), - NCHAR(8), N'?'), NCHAR(7), N'?'), NCHAR(6), N'?'), NCHAR(5), N'?'), NCHAR(4), N'?'), - NCHAR(3), N'?'), NCHAR(2), N'?'), NCHAR(1), N'?'), NCHAR(0), N'?'), - ca.lock_mode, - ca.index_name, - ca.associatedObjectId, - waiter_id = w.l.value('@id', 'nvarchar(256)'), - waiter_mode = w.l.value('@mode', 'nvarchar(256)'), - owner_id = o.l.value('@id', 'nvarchar(256)'), - owner_mode = o.l.value('@mode', 'nvarchar(256)'), - lock_type = CAST(N'OBJECT' AS nvarchar(100)) - INTO #deadlock_owner_waiter - FROM - ( - SELECT - dr.event_date, - database_id = ca.dr.value('@dbid', 'bigint'), - object_name = ca.dr.value('@objectname', 'nvarchar(1024)'), - lock_mode = ca.dr.value('@mode', 'nvarchar(256)'), - index_name = ca.dr.value('@indexname', 'nvarchar(256)'), - associatedObjectId = ca.dr.value('@associatedObjectId', 'bigint'), - dr = ca.dr.query('.') - FROM #deadlock_resource AS dr - CROSS APPLY dr.resource_xml.nodes('//resource-list/objectlock') AS ca(dr) - WHERE (ca.dr.exist('@objectname[. = sql:variable("@ObjectName")]') = 1 OR @ObjectName IS NULL) - ) AS ca - CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l) - CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l) - OPTION(RECOMPILE); - - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - /*Parse page locks*/ - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Parse page locks %s', 0, 1, @d) WITH NOWAIT; - - INSERT - #deadlock_owner_waiter WITH(TABLOCKX) - SELECT DISTINCT - ca.event_date, - ca.database_id, - database_name = - ISNULL - ( - DB_NAME(ca.database_id), - N'UNKNOWN' - ), - ca.object_name, - ca.lock_mode, - ca.index_name, - ca.associatedObjectId, - waiter_id = w.l.value('@id', 'nvarchar(256)'), - waiter_mode = w.l.value('@mode', 'nvarchar(256)'), - owner_id = o.l.value('@id', 'nvarchar(256)'), - owner_mode = o.l.value('@mode', 'nvarchar(256)'), - lock_type = N'PAGE' - FROM - ( - SELECT - dr.event_date, - database_id = ca.dr.value('@dbid', 'bigint'), - object_name = ca.dr.value('@objectname', 'nvarchar(1024)'), - lock_mode = ca.dr.value('@mode', 'nvarchar(256)'), - index_name = ca.dr.value('@indexname', 'nvarchar(256)'), - associatedObjectId = ca.dr.value('@associatedObjectId', 'bigint'), - dr = ca.dr.query('.') - FROM #deadlock_resource AS dr - CROSS APPLY dr.resource_xml.nodes('//resource-list/pagelock') AS ca(dr) - WHERE (ca.dr.exist('@objectname[. = sql:variable("@ObjectName")]') = 1 OR @ObjectName IS NULL) - ) AS ca - CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l) - CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l) - OPTION(RECOMPILE); - - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - /*Parse key locks*/ - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Parse key locks %s', 0, 1, @d) WITH NOWAIT; - - INSERT - #deadlock_owner_waiter WITH(TABLOCKX) - SELECT DISTINCT - ca.event_date, - ca.database_id, - database_name = - ISNULL - ( - DB_NAME(ca.database_id), - N'UNKNOWN' - ), - ca.object_name, - ca.lock_mode, - ca.index_name, - ca.associatedObjectId, - waiter_id = w.l.value('@id', 'nvarchar(256)'), - waiter_mode = w.l.value('@mode', 'nvarchar(256)'), - owner_id = o.l.value('@id', 'nvarchar(256)'), - owner_mode = o.l.value('@mode', 'nvarchar(256)'), - lock_type = N'KEY' - FROM - ( - SELECT - dr.event_date, - database_id = ca.dr.value('@dbid', 'bigint'), - object_name = ca.dr.value('@objectname', 'nvarchar(1024)'), - lock_mode = ca.dr.value('@mode', 'nvarchar(256)'), - index_name = ca.dr.value('@indexname', 'nvarchar(256)'), - associatedObjectId = ca.dr.value('@associatedObjectId', 'bigint'), - dr = ca.dr.query('.') - FROM #deadlock_resource AS dr - CROSS APPLY dr.resource_xml.nodes('//resource-list/keylock') AS ca(dr) - WHERE (ca.dr.exist('@objectname[. = sql:variable("@ObjectName")]') = 1 OR @ObjectName IS NULL) - ) AS ca - CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l) - CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l) - OPTION(RECOMPILE); - - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - /*Parse RID locks*/ - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Parse RID locks %s', 0, 1, @d) WITH NOWAIT; - - INSERT - #deadlock_owner_waiter WITH(TABLOCKX) - SELECT DISTINCT - ca.event_date, - ca.database_id, - database_name = - ISNULL - ( - DB_NAME(ca.database_id), - N'UNKNOWN' - ), - ca.object_name, - ca.lock_mode, - ca.index_name, - ca.associatedObjectId, - waiter_id = w.l.value('@id', 'nvarchar(256)'), - waiter_mode = w.l.value('@mode', 'nvarchar(256)'), - owner_id = o.l.value('@id', 'nvarchar(256)'), - owner_mode = o.l.value('@mode', 'nvarchar(256)'), - lock_type = N'RID' - FROM - ( - SELECT - dr.event_date, - database_id = ca.dr.value('@dbid', 'bigint'), - object_name = ca.dr.value('@objectname', 'nvarchar(1024)'), - lock_mode = ca.dr.value('@mode', 'nvarchar(256)'), - index_name = ca.dr.value('@indexname', 'nvarchar(256)'), - associatedObjectId = ca.dr.value('@associatedObjectId', 'bigint'), - dr = ca.dr.query('.') - FROM #deadlock_resource AS dr - CROSS APPLY dr.resource_xml.nodes('//resource-list/ridlock') AS ca(dr) - WHERE (ca.dr.exist('@objectname[. = sql:variable("@ObjectName")]') = 1 OR @ObjectName IS NULL) - ) AS ca - CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l) - CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l) - OPTION(RECOMPILE); - - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - /*Parse row group locks*/ - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Parse row group locks %s', 0, 1, @d) WITH NOWAIT; - - INSERT - #deadlock_owner_waiter WITH(TABLOCKX) - SELECT DISTINCT - ca.event_date, - ca.database_id, - database_name = - ISNULL - ( - DB_NAME(ca.database_id), - N'UNKNOWN' - ), - ca.object_name, - ca.lock_mode, - ca.index_name, - ca.associatedObjectId, - waiter_id = w.l.value('@id', 'nvarchar(256)'), - waiter_mode = w.l.value('@mode', 'nvarchar(256)'), - owner_id = o.l.value('@id', 'nvarchar(256)'), - owner_mode = o.l.value('@mode', 'nvarchar(256)'), - lock_type = N'ROWGROUP' - FROM - ( - SELECT - dr.event_date, - database_id = ca.dr.value('@dbid', 'bigint'), - object_name = ca.dr.value('@objectname', 'nvarchar(1024)'), - lock_mode = ca.dr.value('@mode', 'nvarchar(256)'), - index_name = ca.dr.value('@indexname', 'nvarchar(256)'), - associatedObjectId = ca.dr.value('@associatedObjectId', 'bigint'), - dr = ca.dr.query('.') - FROM #deadlock_resource AS dr - CROSS APPLY dr.resource_xml.nodes('//resource-list/rowgrouplock') AS ca(dr) - WHERE (ca.dr.exist('@objectname[. = sql:variable("@ObjectName")]') = 1 OR @ObjectName IS NULL) - ) AS ca - CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l) - CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l) - OPTION(RECOMPILE); - - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Fixing heaps in #deadlock_owner_waiter %s', 0, 1, @d) WITH NOWAIT; - - UPDATE - d - SET - d.index_name = - d.object_name + N'.HEAP' - FROM #deadlock_owner_waiter AS d - WHERE d.lock_type IN - ( - N'HEAP', - N'RID' - ) - OPTION(RECOMPILE); - - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - /*Parse parallel deadlocks*/ - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Parse parallel deadlocks %s', 0, 1, @d) WITH NOWAIT; - - SELECT DISTINCT - ca.id, - ca.event_date, - ca.wait_type, - ca.node_id, - ca.waiter_type, - ca.owner_activity, - ca.waiter_activity, - ca.merging, - ca.spilling, - ca.waiting_to_close, - waiter_id = w.l.value('@id', 'nvarchar(256)'), - owner_id = o.l.value('@id', 'nvarchar(256)') - INTO #deadlock_resource_parallel - FROM - ( - SELECT - dr.event_date, - id = ca.dr.value('@id', 'nvarchar(256)'), - wait_type = ca.dr.value('@WaitType', 'nvarchar(256)'), - node_id = ca.dr.value('@nodeId', 'bigint'), - /* These columns are in 2017 CU5+ ONLY */ - waiter_type = ca.dr.value('@waiterType', 'nvarchar(256)'), - owner_activity = ca.dr.value('@ownerActivity', 'nvarchar(256)'), - waiter_activity = ca.dr.value('@waiterActivity', 'nvarchar(256)'), - merging = ca.dr.value('@merging', 'nvarchar(256)'), - spilling = ca.dr.value('@spilling', 'nvarchar(256)'), - waiting_to_close = ca.dr.value('@waitingToClose', 'nvarchar(256)'), - /* These columns are in 2017 CU5+ ONLY */ - dr = ca.dr.query('.') - FROM #deadlock_resource AS dr - CROSS APPLY dr.resource_xml.nodes('//resource-list/exchangeEvent') AS ca(dr) - ) AS ca - CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l) - CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l) - OPTION(RECOMPILE); - - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - /*Get rid of parallel noise*/ - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Get rid of parallel noise %s', 0, 1, @d) WITH NOWAIT; - - WITH - c AS - ( - SELECT - *, - rn = - ROW_NUMBER() OVER - ( - PARTITION BY - drp.owner_id, - drp.waiter_id - ORDER BY - drp.event_date - ) - FROM #deadlock_resource_parallel AS drp - ) - DELETE - FROM c - WHERE c.rn > 1 - OPTION(RECOMPILE); - - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - /*Get rid of nonsense*/ - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Get rid of nonsense %s', 0, 1, @d) WITH NOWAIT; - - DELETE dow - FROM #deadlock_owner_waiter AS dow - WHERE dow.owner_id = dow.waiter_id - OPTION(RECOMPILE); - - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - /*Add some nonsense*/ - ALTER TABLE - #deadlock_process - ADD - waiter_mode nvarchar(256), - owner_mode nvarchar(256), - is_victim AS - CONVERT - ( - bit, - CASE - WHEN id = victim_id - THEN 1 - ELSE 0 - END - ) PERSISTED; - - /*Update some nonsense*/ - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Update some nonsense part 1 %s', 0, 1, @d) WITH NOWAIT; - - UPDATE - dp - SET - dp.owner_mode = dow.owner_mode - FROM #deadlock_process AS dp - JOIN #deadlock_owner_waiter AS dow - ON dp.id = dow.owner_id - AND dp.event_date = dow.event_date - WHERE dp.is_victim = 0 - OPTION(RECOMPILE); - - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Update some nonsense part 2 %s', 0, 1, @d) WITH NOWAIT; - - UPDATE - dp - SET - dp.waiter_mode = dow.waiter_mode - FROM #deadlock_process AS dp - JOIN #deadlock_owner_waiter AS dow - ON dp.victim_id = dow.waiter_id - AND dp.event_date = dow.event_date - WHERE dp.is_victim = 1 - OPTION(RECOMPILE); - - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - /*Get Agent Job and Step names*/ - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Get Agent Job and Step names %s', 0, 1, @d) WITH NOWAIT; - - SELECT - x.event_date, - x.victim_id, - x.id, - x.database_id, - x.client_app, - x.job_id, - x.step_id, - job_id_guid = - CONVERT - ( - uniqueidentifier, - TRY_CAST - ( - N'' - AS xml - ).value('xs:hexBinary(substring(sql:column("x.job_id"), 0))', 'binary(16)') - ) - INTO #agent_job - FROM - ( - SELECT - dp.event_date, - dp.victim_id, - dp.id, - dp.database_id, - dp.client_app, - job_id = - SUBSTRING - ( - dp.client_app, - CHARINDEX(N'0x', dp.client_app) + LEN(N'0x'), - 32 - ), - step_id = - CASE - WHEN CHARINDEX(N': Step ', dp.client_app) > 0 - AND CHARINDEX(N')', dp.client_app, CHARINDEX(N': Step ', dp.client_app)) > 0 - THEN - SUBSTRING - ( - dp.client_app, - CHARINDEX(N': Step ', dp.client_app) + LEN(N': Step '), - CHARINDEX(N')', dp.client_app, CHARINDEX(N': Step ', dp.client_app)) - - (CHARINDEX(N': Step ', dp.client_app) + LEN(N': Step ')) - ) - ELSE dp.client_app - END - FROM #deadlock_process AS dp - WHERE dp.client_app LIKE N'SQLAgent - %' - AND dp.client_app <> N'SQLAgent - Initial Boot Probe' - ) AS x - OPTION(RECOMPILE); - - ALTER TABLE - #agent_job - ADD - job_name nvarchar(256), - step_name nvarchar(256); - - IF - ( - @Azure = 0 - AND @RDS = 0 - ) - BEGIN - SET @StringToExecute = - N' - UPDATE - aj - SET - aj.job_name = j.name, - aj.step_name = s.step_name - FROM msdb.dbo.sysjobs AS j - JOIN msdb.dbo.sysjobsteps AS s - ON j.job_id = s.job_id - JOIN #agent_job AS aj - ON aj.job_id_guid = j.job_id - AND aj.step_id = s.step_id - OPTION(RECOMPILE); - '; - - IF @Debug = 1 BEGIN PRINT @StringToExecute; END; - EXEC sys.sp_executesql - @StringToExecute; - - END; - - UPDATE - dp - SET - dp.client_app = - CASE - WHEN dp.client_app LIKE N'SQLAgent - %' - THEN N'SQLAgent - Job: ' + - aj.job_name + - N' Step: ' + - aj.step_name - ELSE dp.client_app - END - FROM #deadlock_process AS dp - JOIN #agent_job AS aj - ON dp.event_date = aj.event_date - AND dp.victim_id = aj.victim_id - AND dp.id = aj.id - OPTION(RECOMPILE); - - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - /*Get each and every table of all databases*/ - IF @Azure = 0 - BEGIN - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Inserting to @sysAssObjId %s', 0, 1, @d) WITH NOWAIT; - - INSERT INTO - @sysAssObjId - ( - database_id, - partition_id, - schema_name, - table_name - ) - EXECUTE sys.sp_MSforeachdb - N' - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - - USE [?]; - - IF EXISTS - ( - SELECT - 1/0 - FROM #deadlock_process AS dp - WHERE dp.database_id = DB_ID() - ) - BEGIN - SELECT - database_id = - DB_ID(), - p.partition_id, - schema_name = - s.name, - table_name = - t.name - FROM sys.partitions p - JOIN sys.tables t - ON t.object_id = p.object_id - JOIN sys.schemas s - ON s.schema_id = t.schema_id - WHERE s.name IS NOT NULL - AND t.name IS NOT NULL - OPTION(RECOMPILE); - END; - '; - - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - END; - - IF @Azure = 1 - BEGIN - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Inserting to @sysAssObjId at %s', 0, 1, @d) WITH NOWAIT; - - INSERT INTO - @sysAssObjId - ( - database_id, - partition_id, - schema_name, - table_name - ) - SELECT - database_id = - DB_ID(), - p.partition_id, - schema_name = - s.name, - table_name = - t.name - FROM sys.partitions p - JOIN sys.tables t - ON t.object_id = p.object_id - JOIN sys.schemas s - ON s.schema_id = t.schema_id - WHERE s.name IS NOT NULL - AND t.name IS NOT NULL - OPTION(RECOMPILE); - - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - END; - - /*Begin checks based on parsed values*/ - - /* - First, revert these back since we already converted the event data to local time, - and searches will break if we use the times converted over to UTC for the event data - */ - SELECT - @StartDate = @StartDateOriginal, - @EndDate = @EndDateOriginal; - - /*Check 1 is deadlocks by database*/ - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Check 1 database deadlocks %s', 0, 1, @d) WITH NOWAIT; - - INSERT - #deadlock_findings WITH(TABLOCKX) - ( - check_id, - database_name, - object_name, - finding_group, - finding, - sort_order - ) - SELECT - check_id = 1, - dp.database_name, - object_name = N'-', - finding_group = N'Total Database Deadlocks', - finding = - N'This database had ' + - CONVERT - ( - nvarchar(20), - COUNT_BIG(DISTINCT dp.event_date) - ) + - N' deadlocks.', - sort_order = - ROW_NUMBER() - OVER (ORDER BY COUNT_BIG(DISTINCT dp.event_date) DESC) - FROM #deadlock_process AS dp - WHERE 1 = 1 - AND (dp.database_name = @DatabaseName OR @DatabaseName IS NULL) - AND (dp.event_date >= @StartDate OR @StartDate IS NULL) - AND (dp.event_date < @EndDate OR @EndDate IS NULL) - AND (dp.client_app = @AppName OR @AppName IS NULL) - AND (dp.host_name = @HostName OR @HostName IS NULL) - AND (dp.login_name = @LoginName OR @LoginName IS NULL) - GROUP BY dp.database_name - OPTION(RECOMPILE); - - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - /*Check 2 is deadlocks with selects*/ - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Check 2 select deadlocks %s', 0, 1, @d) WITH NOWAIT; - - INSERT - #deadlock_findings WITH(TABLOCKX) - ( - check_id, - database_name, - object_name, - finding_group, - finding, - sort_order - ) - SELECT - check_id = 2, - dow.database_name, - object_name = - CASE - WHEN EXISTS - ( - SELECT - 1/0 - FROM sys.databases AS d - WHERE d.name COLLATE DATABASE_DEFAULT = dow.database_name COLLATE DATABASE_DEFAULT - AND d.is_read_committed_snapshot_on = 1 - ) - THEN N'You already enabled RCSI, but...' - ELSE N'You Might Need RCSI' - END, - finding_group = N'Total Deadlocks Involving Selects', - finding = - N'There have been ' + - CONVERT - ( - nvarchar(20), - COUNT_BIG(DISTINCT dow.event_date) - ) + - N' deadlock(s) between read queries and modification queries.', - sort_order = - ROW_NUMBER() - OVER (ORDER BY COUNT_BIG(DISTINCT dow.event_date) DESC) - FROM #deadlock_owner_waiter AS dow - WHERE 1 = 1 - AND dow.lock_mode IN - ( - N'S', - N'IS' - ) - OR dow.owner_mode IN - ( - N'S', - N'IS' - ) - OR dow.waiter_mode IN - ( - N'S', - N'IS' - ) - AND (dow.database_id = @DatabaseId OR @DatabaseName IS NULL) - AND (dow.event_date >= @StartDate OR @StartDate IS NULL) - AND (dow.event_date < @EndDate OR @EndDate IS NULL) - AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) - GROUP BY - dow.database_name - OPTION(RECOMPILE); - - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - /*Check 3 is deadlocks by object*/ - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Check 3 object deadlocks %s', 0, 1, @d) WITH NOWAIT; - - INSERT - #deadlock_findings WITH(TABLOCKX) - ( - check_id, - database_name, - object_name, - finding_group, - finding, - sort_order - ) - SELECT - check_id = 3, - dow.database_name, - object_name = - ISNULL - ( - dow.object_name, - N'UNKNOWN' - ), - finding_group = N'Total Object Deadlocks', - finding = - N'This object was involved in ' + - CONVERT - ( - nvarchar(20), - COUNT_BIG(DISTINCT dow.event_date) - ) + - N' deadlock(s).', - sort_order = - ROW_NUMBER() - OVER (ORDER BY COUNT_BIG(DISTINCT dow.event_date) DESC) - FROM #deadlock_owner_waiter AS dow - WHERE 1 = 1 - AND (dow.database_id = @DatabaseId OR @DatabaseName IS NULL) - AND (dow.event_date >= @StartDate OR @StartDate IS NULL) - AND (dow.event_date < @EndDate OR @EndDate IS NULL) - AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) - GROUP BY - dow.database_name, - dow.object_name - OPTION(RECOMPILE); - - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - /*Check 3 continuation, number of deadlocks per index*/ - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Check 3 (continued) index deadlocks %s', 0, 1, @d) WITH NOWAIT; - - INSERT - #deadlock_findings WITH(TABLOCKX) - ( - check_id, - database_name, - object_name, - finding_group, - finding, - sort_order - ) - SELECT - check_id = 3, - dow.database_name, - index_name = dow.index_name, - finding_group = N'Total Index Deadlocks', - finding = - N'This index was involved in ' + - CONVERT - ( - nvarchar(20), - COUNT_BIG(DISTINCT dow.event_date) - ) + - N' deadlock(s).', - sort_order = - ROW_NUMBER() - OVER (ORDER BY COUNT_BIG(DISTINCT dow.event_date) DESC) - FROM #deadlock_owner_waiter AS dow - WHERE 1 = 1 - AND (dow.database_id = @DatabaseId OR @DatabaseName IS NULL) - AND (dow.event_date >= @StartDate OR @StartDate IS NULL) - AND (dow.event_date < @EndDate OR @EndDate IS NULL) - AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) - AND dow.lock_type NOT IN - ( - N'HEAP', - N'RID' - ) - AND dow.index_name IS NOT NULL - GROUP BY - dow.database_name, - dow.index_name - OPTION(RECOMPILE); - - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - /*Check 3 continuation, number of deadlocks per heap*/ - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Check 3 (continued) heap deadlocks %s', 0, 1, @d) WITH NOWAIT; - - INSERT - #deadlock_findings WITH(TABLOCKX) - ( - check_id, - database_name, - object_name, - finding_group, - finding, - sort_order - ) - SELECT - check_id = 3, - dow.database_name, - index_name = dow.index_name, - finding_group = N'Total Heap Deadlocks', - finding = - N'This heap was involved in ' + - CONVERT - ( - nvarchar(20), - COUNT_BIG(DISTINCT dow.event_date) - ) + - N' deadlock(s).', - sort_order = - ROW_NUMBER() - OVER (ORDER BY COUNT_BIG(DISTINCT dow.event_date) DESC) - FROM #deadlock_owner_waiter AS dow - WHERE 1 = 1 - AND (dow.database_id = @DatabaseId OR @DatabaseName IS NULL) - AND (dow.event_date >= @StartDate OR @StartDate IS NULL) - AND (dow.event_date < @EndDate OR @EndDate IS NULL) - AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) - AND dow.lock_type IN - ( - N'HEAP', - N'RID' - ) - GROUP BY - dow.database_name, - dow.index_name - OPTION(RECOMPILE); - - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - /*Check 4 looks for Serializable deadlocks*/ - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Check 4 serializable deadlocks %s', 0, 1, @d) WITH NOWAIT; - - INSERT - #deadlock_findings WITH(TABLOCKX) - ( - check_id, - database_name, - object_name, - finding_group, - finding, - sort_order - ) - SELECT - check_id = 4, - database_name = - dp.database_name, - object_name = N'-', - finding_group = N'Serializable Deadlocking', - finding = - N'This database has had ' + - CONVERT - ( - nvarchar(20), - COUNT_BIG(DISTINCT dp.event_date) - ) + - N' instances of Serializable deadlocks.', - sort_order = - ROW_NUMBER() - OVER (ORDER BY COUNT_BIG(DISTINCT dp.event_date) DESC) - FROM #deadlock_process AS dp - WHERE dp.isolation_level LIKE N'serializable%' - AND (dp.database_name = @DatabaseName OR @DatabaseName IS NULL) - AND (dp.event_date >= @StartDate OR @StartDate IS NULL) - AND (dp.event_date < @EndDate OR @EndDate IS NULL) - AND (dp.client_app = @AppName OR @AppName IS NULL) - AND (dp.host_name = @HostName OR @HostName IS NULL) - AND (dp.login_name = @LoginName OR @LoginName IS NULL) - GROUP BY dp.database_name - OPTION(RECOMPILE); - - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - /*Check 5 looks for Repeatable Read deadlocks*/ - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Check 5 repeatable read deadlocks %s', 0, 1, @d) WITH NOWAIT; - - INSERT - #deadlock_findings WITH(TABLOCKX) - ( - check_id, - database_name, - object_name, - finding_group, - finding, - sort_order - ) - SELECT - check_id = 5, - dp.database_name, - object_name = N'-', - finding_group = N'Repeatable Read Deadlocking', - finding = - N'This database has had ' + - CONVERT - ( - nvarchar(20), - COUNT_BIG(DISTINCT dp.event_date) - ) + - N' instances of Repeatable Read deadlocks.', - sort_order = - ROW_NUMBER() - OVER (ORDER BY COUNT_BIG(DISTINCT dp.event_date) DESC) - FROM #deadlock_process AS dp - WHERE dp.isolation_level LIKE N'repeatable%' - AND (dp.database_name = @DatabaseName OR @DatabaseName IS NULL) - AND (dp.event_date >= @StartDate OR @StartDate IS NULL) - AND (dp.event_date < @EndDate OR @EndDate IS NULL) - AND (dp.client_app = @AppName OR @AppName IS NULL) - AND (dp.host_name = @HostName OR @HostName IS NULL) - AND (dp.login_name = @LoginName OR @LoginName IS NULL) - GROUP BY dp.database_name - OPTION(RECOMPILE); - - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - /*Check 6 breaks down app, host, and login information*/ - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Check 6 app/host/login deadlocks %s', 0, 1, @d) WITH NOWAIT; - - INSERT - #deadlock_findings WITH(TABLOCKX) - ( - check_id, - database_name, - object_name, - finding_group, - finding, - sort_order - ) - SELECT - check_id = 6, - database_name = - dp.database_name, - object_name = N'-', - finding_group = N'Login, App, and Host deadlocks', - finding = - N'This database has had ' + - CONVERT - ( - nvarchar(20), - COUNT_BIG(DISTINCT dp.event_date) - ) + - N' instances of deadlocks involving the login ' + - ISNULL - ( - dp.login_name, - N'UNKNOWN' - ) + - N' from the application ' + - ISNULL - ( - dp.client_app, - N'UNKNOWN' - ) + - N' on host ' + - ISNULL - ( - dp.host_name, - N'UNKNOWN' - ) + - N'.', - sort_order = - ROW_NUMBER() - OVER (ORDER BY COUNT_BIG(DISTINCT dp.event_date) DESC) - FROM #deadlock_process AS dp - WHERE 1 = 1 - AND (dp.database_name = @DatabaseName OR @DatabaseName IS NULL) - AND (dp.event_date >= @StartDate OR @StartDate IS NULL) - AND (dp.event_date < @EndDate OR @EndDate IS NULL) - AND (dp.client_app = @AppName OR @AppName IS NULL) - AND (dp.host_name = @HostName OR @HostName IS NULL) - AND (dp.login_name = @LoginName OR @LoginName IS NULL) - GROUP BY - dp.database_name, - dp.login_name, - dp.client_app, - dp.host_name - OPTION(RECOMPILE); - - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - /*Check 7 breaks down the types of deadlocks (object, page, key, etc.)*/ - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Check 7 types of deadlocks %s', 0, 1, @d) WITH NOWAIT; - - WITH - lock_types AS - ( - SELECT - database_name = - dp.database_name, - dow.object_name, - lock = - CASE - WHEN CHARINDEX(N':', dp.wait_resource) > 0 - THEN SUBSTRING - ( - dp.wait_resource, - 1, - CHARINDEX(N':', dp.wait_resource) - 1 - ) - ELSE dp.wait_resource - END, - lock_count = - CONVERT - ( - nvarchar(20), - COUNT_BIG(DISTINCT dp.event_date) - ) - FROM #deadlock_process AS dp - JOIN #deadlock_owner_waiter AS dow - ON (dp.id = dow.owner_id - OR dp.victim_id = dow.waiter_id) - AND dp.event_date = dow.event_date - WHERE 1 = 1 - AND (dp.database_name = @DatabaseName OR @DatabaseName IS NULL) - AND (dp.event_date >= @StartDate OR @StartDate IS NULL) - AND (dp.event_date < @EndDate OR @EndDate IS NULL) - AND (dp.client_app = @AppName OR @AppName IS NULL) - AND (dp.host_name = @HostName OR @HostName IS NULL) - AND (dp.login_name = @LoginName OR @LoginName IS NULL) - AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) - AND dow.object_name IS NOT NULL - GROUP BY - dp.database_name, - CASE - WHEN CHARINDEX(N':', dp.wait_resource) > 0 - THEN SUBSTRING - ( - dp.wait_resource, - 1, - CHARINDEX(N':', dp.wait_resource) - 1 - ) - ELSE dp.wait_resource - END, - dow.object_name - ) - INSERT - #deadlock_findings WITH(TABLOCKX) - ( - check_id, - database_name, - object_name, - finding_group, - finding, - sort_order - ) - SELECT - check_id = 7, - lt.database_name, - lt.object_name, - finding_group = N'Types of locks by object', - finding = - N'This object has had ' + - STUFF - ( - ( - SELECT - N', ' + - lt2.lock_count + - N' ' + - lt2.lock - FROM lock_types AS lt2 - WHERE lt2.database_name = lt.database_name - AND lt2.object_name = lt.object_name - FOR XML - PATH(N''), - TYPE - ).value(N'.[1]', N'nvarchar(MAX)'), - 1, - 1, - N'' - ) + N' locks.', - sort_order = - ROW_NUMBER() - OVER (ORDER BY CONVERT(bigint, lt.lock_count) DESC) - FROM lock_types AS lt - OPTION(RECOMPILE); - - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - /*Check 8 gives you more info queries for sp_BlitzCache & BlitzQueryStore*/ - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Check 8 more info part 1 BlitzCache %s', 0, 1, @d) WITH NOWAIT; - - WITH - deadlock_stack AS - ( - SELECT DISTINCT - ds.id, - ds.event_date, - ds.proc_name, - database_name = - PARSENAME(ds.proc_name, 3), - schema_name = - PARSENAME(ds.proc_name, 2), - proc_only_name = - PARSENAME(ds.proc_name, 1), - sql_handle_csv = - N'''' + - STUFF - ( - ( - SELECT DISTINCT - N',' + - ds2.sql_handle - FROM #deadlock_stack AS ds2 - WHERE ds2.id = ds.id - AND ds2.event_date = ds.event_date - AND ds2.sql_handle <> 0x - FOR XML - PATH(N''), - TYPE - ).value(N'.[1]', N'nvarchar(MAX)'), - 1, - 1, - N'' - ) + - N'''' - FROM #deadlock_stack AS ds - WHERE ds.sql_handle <> 0x - GROUP BY - PARSENAME(ds.proc_name, 3), - PARSENAME(ds.proc_name, 2), - PARSENAME(ds.proc_name, 1), - ds.proc_name, - ds.id, - ds.event_date - ) - INSERT - #deadlock_findings WITH(TABLOCKX) - ( - check_id, - database_name, - object_name, - finding_group, - finding - ) - SELECT DISTINCT - check_id = 8, - dow.database_name, - object_name = ds.proc_name, - finding_group = N'More Info - Query', - finding = N'EXEC sp_BlitzCache ' + - CASE - WHEN ds.proc_name = N'adhoc' - THEN N'@OnlySqlHandles = ' + ds.sql_handle_csv - ELSE N'@StoredProcName = ' + QUOTENAME(ds.proc_only_name, N'''') - END + N';' - FROM deadlock_stack AS ds - JOIN #deadlock_owner_waiter AS dow - ON dow.owner_id = ds.id - AND dow.event_date = ds.event_date - WHERE 1 = 1 - AND (dow.database_id = @DatabaseId OR @DatabaseName IS NULL) - AND (dow.event_date >= @StartDate OR @StartDate IS NULL) - AND (dow.event_date < @EndDate OR @EndDate IS NULL) - AND (dow.object_name = @StoredProcName OR @StoredProcName IS NULL) - AND ds.proc_name NOT LIKE 'Unknown%' - OPTION(RECOMPILE); - - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - IF (@ProductVersionMajor >= 13 OR @Azure = 1) - BEGIN - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Check 8 more info part 2 BlitzQueryStore %s', 0, 1, @d) WITH NOWAIT; - - WITH - deadlock_stack AS - ( - SELECT DISTINCT - ds.id, - ds.sql_handle, - ds.proc_name, - ds.event_date, - database_name = - PARSENAME(ds.proc_name, 3), - schema_name = - PARSENAME(ds.proc_name, 2), - proc_only_name = - PARSENAME(ds.proc_name, 1) - FROM #deadlock_stack AS ds - ) - INSERT - #deadlock_findings WITH(TABLOCKX) - ( - check_id, - database_name, - object_name, - finding_group, - finding - ) - SELECT DISTINCT - check_id = 8, - dow.database_name, - object_name = ds.proc_name, - finding_group = N'More Info - Query', - finding = - N'EXEC sp_BlitzQueryStore ' + - N'@DatabaseName = ' + - QUOTENAME(ds.database_name, N'''') + - N', ' + - N'@StoredProcName = ' + - QUOTENAME(ds.proc_only_name, N'''') + - N';' - FROM deadlock_stack AS ds - JOIN #deadlock_owner_waiter AS dow - ON dow.owner_id = ds.id - AND dow.event_date = ds.event_date - WHERE ds.proc_name <> N'adhoc' - AND (dow.database_id = @DatabaseId OR @DatabaseName IS NULL) - AND (dow.event_date >= @StartDate OR @StartDate IS NULL) - AND (dow.event_date < @EndDate OR @EndDate IS NULL) - AND (dow.object_name = @StoredProcName OR @StoredProcName IS NULL) - OPTION(RECOMPILE); - - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - END; - - /*Check 9 gives you stored procedure deadlock counts*/ - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Check 9 stored procedure deadlocks %s', 0, 1, @d) WITH NOWAIT; - - INSERT - #deadlock_findings WITH(TABLOCKX) - ( - check_id, - database_name, - object_name, - finding_group, - finding, - sort_order - ) - SELECT - check_id = 9, - database_name = - dp.database_name, - object_name = ds.proc_name, - finding_group = N'Stored Procedure Deadlocks', - finding = - N'The stored procedure ' + - PARSENAME(ds.proc_name, 2) + - N'.' + - PARSENAME(ds.proc_name, 1) + - N' has been involved in ' + - CONVERT - ( - nvarchar(10), - COUNT_BIG(DISTINCT ds.id) - ) + - N' deadlocks.', - sort_order = - ROW_NUMBER() - OVER (ORDER BY COUNT_BIG(DISTINCT ds.id) DESC) - FROM #deadlock_stack AS ds - JOIN #deadlock_process AS dp - ON dp.id = ds.id - AND ds.event_date = dp.event_date - WHERE ds.proc_name <> N'adhoc' - AND (ds.proc_name = @StoredProcName OR @StoredProcName IS NULL) - AND (dp.database_name = @DatabaseName OR @DatabaseName IS NULL) - AND (dp.event_date >= @StartDate OR @StartDate IS NULL) - AND (dp.event_date < @EndDate OR @EndDate IS NULL) - AND (dp.client_app = @AppName OR @AppName IS NULL) - AND (dp.host_name = @HostName OR @HostName IS NULL) - AND (dp.login_name = @LoginName OR @LoginName IS NULL) - GROUP BY - dp.database_name, - ds.proc_name - OPTION(RECOMPILE); - - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - /*Check 10 gives you more info queries for sp_BlitzIndex */ - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Check 10 more info, BlitzIndex %s', 0, 1, @d) WITH NOWAIT; - - WITH - bi AS - ( - SELECT DISTINCT - dow.object_name, - dow.database_name, - schema_name = s.schema_name, - table_name = s.table_name - FROM #deadlock_owner_waiter AS dow - JOIN @sysAssObjId AS s - ON s.database_id = dow.database_id - AND s.partition_id = dow.associatedObjectId - WHERE 1 = 1 - AND (dow.database_id = @DatabaseId OR @DatabaseName IS NULL) - AND (dow.event_date >= @StartDate OR @StartDate IS NULL) - AND (dow.event_date < @EndDate OR @EndDate IS NULL) - AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) - AND dow.object_name IS NOT NULL - ) - INSERT - #deadlock_findings WITH(TABLOCKX) - ( - check_id, - database_name, - object_name, - finding_group, - finding - ) - SELECT DISTINCT - check_id = 10, - bi.database_name, - bi.object_name, - finding_group = N'More Info - Table', - finding = - N'EXEC sp_BlitzIndex ' + - N'@DatabaseName = ' + - QUOTENAME(bi.database_name, N'''') + - N', @SchemaName = ' + - QUOTENAME(bi.schema_name, N'''') + - N', @TableName = ' + - QUOTENAME(bi.table_name, N'''') + - N';' - FROM bi - OPTION(RECOMPILE); - - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - /*Check 11 gets total deadlock wait time per object*/ - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Check 11 deadlock wait time per object %s', 0, 1, @d) WITH NOWAIT; - - WITH - chopsuey AS - ( - - SELECT - database_name = - dp.database_name, - dow.object_name, - wait_days = - CONVERT - ( - nvarchar(30), - ( - SUM - ( - CONVERT - ( - bigint, - dp.wait_time - ) - ) / 1000 / 86400 - ) - ), - wait_time_hms = - /*the more wait time you rack up the less accurate this gets, - it's either that or erroring out*/ - CASE - WHEN - SUM - ( - CONVERT - ( - bigint, - dp.wait_time - ) - )/1000 > 2147483647 - THEN - CONVERT - ( - nvarchar(30), - DATEADD - ( - MINUTE, - ( - ( - SUM - ( - CONVERT - ( - bigint, - dp.wait_time - ) - ) - )/ - 60000 - ), - 0 - ), - 14 - ) - WHEN - SUM - ( - CONVERT - ( - bigint, - dp.wait_time - ) - ) BETWEEN 2147483648 AND 2147483647000 - THEN - CONVERT - ( - nvarchar(30), - DATEADD - ( - SECOND, - ( - ( - SUM - ( - CONVERT - ( - bigint, - dp.wait_time - ) - ) - )/ - 1000 - ), - 0 - ), - 14 - ) - ELSE - CONVERT - ( - nvarchar(30), - DATEADD - ( - MILLISECOND, - ( - SUM - ( - CONVERT - ( - bigint, - dp.wait_time - ) - ) - ), - 0 - ), - 14 - ) - END, - total_waits = - SUM(CONVERT(bigint, dp.wait_time)) - FROM #deadlock_owner_waiter AS dow - JOIN #deadlock_process AS dp - ON (dp.id = dow.owner_id - OR dp.victim_id = dow.waiter_id) - AND dp.event_date = dow.event_date - WHERE 1 = 1 - AND (dp.database_name = @DatabaseName OR @DatabaseName IS NULL) - AND (dp.event_date >= @StartDate OR @StartDate IS NULL) - AND (dp.event_date < @EndDate OR @EndDate IS NULL) - AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) - AND (dp.client_app = @AppName OR @AppName IS NULL) - AND (dp.host_name = @HostName OR @HostName IS NULL) - AND (dp.login_name = @LoginName OR @LoginName IS NULL) - GROUP BY - dp.database_name, - dow.object_name - ) - INSERT - #deadlock_findings WITH(TABLOCKX) - ( - check_id, - database_name, - object_name, - finding_group, - finding, - sort_order - ) - SELECT - check_id = 11, - cs.database_name, - cs.object_name, - finding_group = N'Total object deadlock wait time', - finding = - N'This object has had ' + - CONVERT - ( - nvarchar(30), - cs.wait_days - ) + - N' ' + - CONVERT - ( - nvarchar(30), - cs.wait_time_hms, - 14 - ) + - N' [dd hh:mm:ss:ms] of deadlock wait time.', - sort_order = - ROW_NUMBER() - OVER (ORDER BY cs.total_waits DESC) - FROM chopsuey AS cs - WHERE cs.object_name IS NOT NULL - OPTION(RECOMPILE); - - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - /*Check 12 gets total deadlock wait time per database*/ - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Check 12 deadlock wait time per database %s', 0, 1, @d) WITH NOWAIT; - - WITH - wait_time AS - ( - SELECT - database_name = - dp.database_name, - total_wait_time_ms = - SUM - ( - CONVERT - ( - bigint, - dp.wait_time - ) - ) - FROM #deadlock_owner_waiter AS dow - JOIN #deadlock_process AS dp - ON (dp.id = dow.owner_id - OR dp.victim_id = dow.waiter_id) - AND dp.event_date = dow.event_date - WHERE 1 = 1 - AND (dp.database_name = @DatabaseName OR @DatabaseName IS NULL) - AND (dp.event_date >= @StartDate OR @StartDate IS NULL) - AND (dp.event_date < @EndDate OR @EndDate IS NULL) - AND (dp.client_app = @AppName OR @AppName IS NULL) - AND (dp.host_name = @HostName OR @HostName IS NULL) - AND (dp.login_name = @LoginName OR @LoginName IS NULL) - GROUP BY - dp.database_name - ) - INSERT - #deadlock_findings WITH(TABLOCKX) - ( - check_id, - database_name, - object_name, - finding_group, - finding, - sort_order - ) - SELECT - check_id = 12, - wt.database_name, - object_name = N'-', - finding_group = N'Total database deadlock wait time', - N'This database has had ' + - CONVERT - ( - nvarchar(30), - ( - SUM - ( - CONVERT - ( - bigint, - wt.total_wait_time_ms - ) - ) / 1000 / 86400 - ) - ) + - N' ' + - /*the more wait time you rack up the less accurate this gets, - it's either that or erroring out*/ - CASE - WHEN - SUM - ( - CONVERT - ( - bigint, - wt.total_wait_time_ms - ) - )/1000 > 2147483647 - THEN - CONVERT - ( - nvarchar(30), - DATEADD - ( - MINUTE, - ( - ( - SUM - ( - CONVERT - ( - bigint, - wt.total_wait_time_ms - ) - ) - )/ - 60000 - ), - 0 - ), - 14 - ) - WHEN - SUM - ( - CONVERT - ( - bigint, - wt.total_wait_time_ms - ) - ) BETWEEN 2147483648 AND 2147483647000 - THEN - CONVERT - ( - nvarchar(30), - DATEADD - ( - SECOND, - ( - ( - SUM - ( - CONVERT - ( - bigint, - wt.total_wait_time_ms - ) - ) - )/ - 1000 - ), - 0 - ), - 14 - ) - ELSE - CONVERT - ( - nvarchar(30), - DATEADD - ( - MILLISECOND, - ( - SUM - ( - CONVERT - ( - bigint, - wt.total_wait_time_ms - ) - ) - ), - 0 - ), - 14 - ) END + - N' [dd hh:mm:ss:ms] of deadlock wait time.', - sort_order = - ROW_NUMBER() - OVER (ORDER BY SUM(CONVERT(bigint, wt.total_wait_time_ms)) DESC) - FROM wait_time AS wt - GROUP BY - wt.database_name - OPTION(RECOMPILE); - - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - /*Check 13 gets total deadlock wait time for SQL Agent*/ - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Check 13 deadlock count for SQL Agent %s', 0, 1, @d) WITH NOWAIT; - - INSERT - #deadlock_findings WITH(TABLOCKX) - ( - check_id, - database_name, - object_name, - finding_group, - finding, - sort_order - ) - SELECT - check_id = 13, - database_name = - DB_NAME(aj.database_id), - object_name = - N'SQLAgent - Job: ' + - aj.job_name + - N' Step: ' + - aj.step_name, - finding_group = N'Agent Job Deadlocks', - finding = - N'There have been ' + - RTRIM(COUNT_BIG(DISTINCT aj.event_date)) + - N' deadlocks from this Agent Job and Step.', - sort_order = - ROW_NUMBER() - OVER (ORDER BY COUNT_BIG(DISTINCT aj.event_date) DESC) - FROM #agent_job AS aj - GROUP BY - DB_NAME(aj.database_id), - aj.job_name, - aj.step_name - OPTION(RECOMPILE); - - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - /*Check 14 is total parallel deadlocks*/ - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Check 14 parallel deadlocks %s', 0, 1, @d) WITH NOWAIT; - - INSERT - #deadlock_findings WITH(TABLOCKX) - ( - check_id, - database_name, - object_name, - finding_group, - finding - ) - SELECT - check_id = 14, - database_name = N'-', - object_name = N'-', - finding_group = N'Total parallel deadlocks', - finding = - N'There have been ' + - CONVERT - ( - nvarchar(20), - COUNT_BIG(DISTINCT drp.event_date) - ) + - N' parallel deadlocks.' - FROM #deadlock_resource_parallel AS drp - HAVING COUNT_BIG(DISTINCT drp.event_date) > 0 - OPTION(RECOMPILE); - - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - /*Check 15 is total deadlocks involving sleeping sessions*/ - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Check 15 sleeping and background deadlocks %s', 0, 1, @d) WITH NOWAIT; - - INSERT - #deadlock_findings WITH(TABLOCKX) - ( - check_id, - database_name, - object_name, - finding_group, - finding - ) - SELECT - check_id = 15, - database_name = N'-', - object_name = N'-', - finding_group = N'Total deadlocks involving sleeping sessions', - finding = - N'There have been ' + - CONVERT - ( - nvarchar(20), - COUNT_BIG(DISTINCT dp.event_date) - ) + - N' sleepy deadlocks.' - FROM #deadlock_process AS dp - WHERE dp.status = N'sleeping' - HAVING COUNT_BIG(DISTINCT dp.event_date) > 0 - OPTION(RECOMPILE); - - INSERT - #deadlock_findings WITH(TABLOCKX) - ( - check_id, - database_name, - object_name, - finding_group, - finding - ) - SELECT - check_id = 15, - database_name = N'-', - object_name = N'-', - finding_group = N'Total deadlocks involving background processes', - finding = - N'There have been ' + - CONVERT - ( - nvarchar(20), - COUNT_BIG(DISTINCT dp.event_date) - ) + - N' deadlocks with background task.' - FROM #deadlock_process AS dp - WHERE dp.status = N'background' - HAVING COUNT_BIG(DISTINCT dp.event_date) > 0 - OPTION(RECOMPILE); - - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - /*Check 16 is total deadlocks involving implicit transactions*/ - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Check 16 implicit transaction deadlocks %s', 0, 1, @d) WITH NOWAIT; - - INSERT - #deadlock_findings WITH(TABLOCKX) - ( - check_id, - database_name, - object_name, - finding_group, - finding - ) - SELECT - check_id = 14, - database_name = N'-', - object_name = N'-', - finding_group = N'Total implicit transaction deadlocks', - finding = - N'There have been ' + - CONVERT - ( - nvarchar(20), - COUNT_BIG(DISTINCT dp.event_date) - ) + - N' implicit transaction deadlocks.' - FROM #deadlock_process AS dp - WHERE dp.transaction_name = N'implicit_transaction' - HAVING COUNT_BIG(DISTINCT dp.event_date) > 0 - OPTION(RECOMPILE); - - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - /*Thank you goodnight*/ - INSERT - #deadlock_findings WITH(TABLOCKX) - ( - check_id, - database_name, - object_name, - finding_group, - finding - ) - VALUES - ( - -1, - N'sp_BlitzLock version ' + CONVERT(nvarchar(10), @Version), - N'Results for ' + CONVERT(nvarchar(10), @StartDate, 23) + N' through ' + CONVERT(nvarchar(10), @EndDate, 23), - N'http://FirstResponderKit.org/', - N'To get help or add your own contributions to the SQL Server First Responder Kit, join us at http://FirstResponderKit.org.' - ); - - RAISERROR('Finished rollup at %s', 0, 1, @d) WITH NOWAIT; - - /*Results*/ - BEGIN - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Results 1 %s', 0, 1, @d) WITH NOWAIT; - - CREATE CLUSTERED INDEX cx_whatever_dp ON #deadlock_process (event_date, id); - CREATE CLUSTERED INDEX cx_whatever_drp ON #deadlock_resource_parallel (event_date, owner_id); - CREATE CLUSTERED INDEX cx_whatever_dow ON #deadlock_owner_waiter (event_date, owner_id, waiter_id); - - IF @Debug = 1 BEGIN SET STATISTICS XML ON; END; - - WITH - deadlocks AS - ( - SELECT - deadlock_type = - N'Regular Deadlock', - dp.event_date, - dp.id, - dp.victim_id, - dp.spid, - dp.database_id, - dp.database_name, - dp.current_database_name, - dp.priority, - dp.log_used, - wait_resource = - dp.wait_resource COLLATE DATABASE_DEFAULT, - object_names = - CONVERT - ( - xml, - STUFF - ( - ( - SELECT DISTINCT - object_name = - NCHAR(10) + - N' ' + - ISNULL(c.object_name, N'') + - N' ' COLLATE DATABASE_DEFAULT - FROM #deadlock_owner_waiter AS c - WHERE (dp.id = c.owner_id - OR dp.victim_id = c.waiter_id) - AND dp.event_date = c.event_date - FOR XML - PATH(N''), - TYPE - ).value(N'.[1]', N'nvarchar(4000)'), - 1, - 1, - N'' - ) - ), - dp.wait_time, - dp.transaction_name, - dp.status, - dp.last_tran_started, - dp.last_batch_started, - dp.last_batch_completed, - dp.lock_mode, - dp.transaction_count, - dp.client_app, - dp.host_name, - dp.login_name, - dp.isolation_level, - dp.client_option_1, - dp.client_option_2, - inputbuf = - dp.process_xml.value('(//process/inputbuf/text())[1]', 'nvarchar(MAX)'), - en = - DENSE_RANK() OVER (ORDER BY dp.event_date), - qn = - ROW_NUMBER() OVER (PARTITION BY dp.event_date ORDER BY dp.event_date) - 1, - dn = - ROW_NUMBER() OVER (PARTITION BY dp.event_date, dp.id ORDER BY dp.event_date), - dp.is_victim, - owner_mode = - ISNULL(dp.owner_mode, N'-'), - owner_waiter_type = NULL, - owner_activity = NULL, - owner_waiter_activity = NULL, - owner_merging = NULL, - owner_spilling = NULL, - owner_waiting_to_close = NULL, - waiter_mode = - ISNULL(dp.waiter_mode, N'-'), - waiter_waiter_type = NULL, - waiter_owner_activity = NULL, - waiter_waiter_activity = NULL, - waiter_merging = NULL, - waiter_spilling = NULL, - waiter_waiting_to_close = NULL, - dp.deadlock_graph - FROM #deadlock_process AS dp - WHERE dp.victim_id IS NOT NULL - AND dp.is_parallel = 0 - - UNION ALL - - SELECT - deadlock_type = - N'Parallel Deadlock', - dp.event_date, - dp.id, - dp.victim_id, - dp.spid, - dp.database_id, - dp.database_name, - dp.current_database_name, - dp.priority, - dp.log_used, - dp.wait_resource COLLATE DATABASE_DEFAULT, - object_names = - CONVERT - ( - xml, - STUFF - ( - ( - SELECT DISTINCT - object_name = - NCHAR(10) + - N' ' + - ISNULL(c.object_name, N'') + - N' ' COLLATE DATABASE_DEFAULT - FROM #deadlock_owner_waiter AS c - WHERE (dp.id = c.owner_id - OR dp.victim_id = c.waiter_id) - AND dp.event_date = c.event_date - FOR XML - PATH(N''), - TYPE - ).value(N'.[1]', N'nvarchar(4000)'), - 1, - 1, - N'' - ) - ), - dp.wait_time, - dp.transaction_name, - dp.status, - dp.last_tran_started, - dp.last_batch_started, - dp.last_batch_completed, - dp.lock_mode, - dp.transaction_count, - dp.client_app, - dp.host_name, - dp.login_name, - dp.isolation_level, - dp.client_option_1, - dp.client_option_2, - inputbuf = - dp.process_xml.value('(//process/inputbuf/text())[1]', 'nvarchar(MAX)'), - en = - DENSE_RANK() OVER (ORDER BY dp.event_date), - qn = - ROW_NUMBER() OVER (PARTITION BY dp.event_date ORDER BY dp.event_date) - 1, - dn = - ROW_NUMBER() OVER (PARTITION BY dp.event_date, dp.id ORDER BY dp.event_date), - is_victim = 1, - owner_mode = cao.wait_type COLLATE DATABASE_DEFAULT, - owner_waiter_type = cao.waiter_type, - owner_activity = cao.owner_activity, - owner_waiter_activity = cao.waiter_activity, - owner_merging = cao.merging, - owner_spilling = cao.spilling, - owner_waiting_to_close = cao.waiting_to_close, - waiter_mode = caw.wait_type COLLATE DATABASE_DEFAULT, - waiter_waiter_type = caw.waiter_type, - waiter_owner_activity = caw.owner_activity, - waiter_waiter_activity = caw.waiter_activity, - waiter_merging = caw.merging, - waiter_spilling = caw.spilling, - waiter_waiting_to_close = caw.waiting_to_close, - dp.deadlock_graph - FROM #deadlock_process AS dp - OUTER APPLY - ( - SELECT TOP (1) - drp.* - FROM #deadlock_resource_parallel AS drp - WHERE drp.owner_id = dp.id - AND drp.wait_type IN - ( - N'e_waitPortOpen', - N'e_waitPipeNewRow' - ) - ORDER BY drp.event_date - ) AS cao - OUTER APPLY - ( - SELECT TOP (1) - drp.* - FROM #deadlock_resource_parallel AS drp - WHERE drp.owner_id = dp.id - AND drp.wait_type IN - ( - N'e_waitPortOpen', - N'e_waitPipeGetRow' - ) - ORDER BY drp.event_date - ) AS caw - WHERE dp.is_parallel = 1 - ) - SELECT - d.deadlock_type, - d.event_date, - d.id, - d.victim_id, - d.spid, - deadlock_group = - N'Deadlock #' + - CONVERT - ( - nvarchar(10), - d.en - ) + - N', Query #' - + CASE - WHEN d.qn = 0 - THEN N'1' - ELSE CONVERT(nvarchar(10), d.qn) - END + CASE - WHEN d.is_victim = 1 - THEN N' - VICTIM' - ELSE N'' - END, - d.database_id, - d.database_name, - d.current_database_name, - d.priority, - d.log_used, - d.wait_resource, - d.object_names, - d.wait_time, - d.transaction_name, - d.status, - d.last_tran_started, - d.last_batch_started, - d.last_batch_completed, - d.lock_mode, - d.transaction_count, - d.client_app, - d.host_name, - d.login_name, - d.isolation_level, - d.client_option_1, - d.client_option_2, - inputbuf = - CASE - WHEN d.inputbuf - LIKE @inputbuf_bom + N'Proc |[Database Id = %' ESCAPE N'|' - THEN - OBJECT_SCHEMA_NAME - ( - SUBSTRING - ( - d.inputbuf, - CHARINDEX(N'Object Id = ', d.inputbuf) + 12, - LEN(d.inputbuf) - (CHARINDEX(N'Object Id = ', d.inputbuf) + 12) - ) - , - SUBSTRING - ( - d.inputbuf, - CHARINDEX(N'Database Id = ', d.inputbuf) + 14, - CHARINDEX(N'Object Id', d.inputbuf) - (CHARINDEX(N'Database Id = ', d.inputbuf) + 14) - ) - ) + - N'.' + - OBJECT_NAME - ( - SUBSTRING - ( - d.inputbuf, - CHARINDEX(N'Object Id = ', d.inputbuf) + 12, - LEN(d.inputbuf) - (CHARINDEX(N'Object Id = ', d.inputbuf) + 12) - ) - , - SUBSTRING - ( - d.inputbuf, - CHARINDEX(N'Database Id = ', d.inputbuf) + 14, - CHARINDEX(N'Object Id', d.inputbuf) - (CHARINDEX(N'Database Id = ', d.inputbuf) + 14) - ) - ) - ELSE d.inputbuf - END COLLATE Latin1_General_BIN2, - d.owner_mode, - d.owner_waiter_type, - d.owner_activity, - d.owner_waiter_activity, - d.owner_merging, - d.owner_spilling, - d.owner_waiting_to_close, - d.waiter_mode, - d.waiter_waiter_type, - d.waiter_owner_activity, - d.waiter_waiter_activity, - d.waiter_merging, - d.waiter_spilling, - d.waiter_waiting_to_close, - d.deadlock_graph, - d.is_victim - INTO #deadlocks - FROM deadlocks AS d - WHERE d.dn = 1 - AND (d.is_victim = @VictimsOnly - OR @VictimsOnly = 0) - AND d.qn < CASE - WHEN d.deadlock_type = N'Parallel Deadlock' - THEN 2 - ELSE 2147483647 - END - AND (DB_NAME(d.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (d.event_date >= @StartDate OR @StartDate IS NULL) - AND (d.event_date < @EndDate OR @EndDate IS NULL) - AND (CONVERT(nvarchar(MAX), d.object_names) COLLATE Latin1_General_BIN2 LIKE N'%' + @ObjectName + N'%' OR @ObjectName IS NULL) - AND (d.client_app = @AppName OR @AppName IS NULL) - AND (d.host_name = @HostName OR @HostName IS NULL) - AND (d.login_name = @LoginName OR @LoginName IS NULL) - OPTION (RECOMPILE, LOOP JOIN, HASH JOIN); - - UPDATE d - SET d.inputbuf = - REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE( - REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE( - REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE( - d.inputbuf, - NCHAR(31), N'?'), NCHAR(30), N'?'), NCHAR(29), N'?'), NCHAR(28), N'?'), NCHAR(27), N'?'), - NCHAR(26), N'?'), NCHAR(25), N'?'), NCHAR(24), N'?'), NCHAR(23), N'?'), NCHAR(22), N'?'), - NCHAR(21), N'?'), NCHAR(20), N'?'), NCHAR(19), N'?'), NCHAR(18), N'?'), NCHAR(17), N'?'), - NCHAR(16), N'?'), NCHAR(15), N'?'), NCHAR(14), N'?'), NCHAR(12), N'?'), NCHAR(11), N'?'), - NCHAR(8), N'?'), NCHAR(7), N'?'), NCHAR(6), N'?'), NCHAR(5), N'?'), NCHAR(4), N'?'), - NCHAR(3), N'?'), NCHAR(2), N'?'), NCHAR(1), N'?'), NCHAR(0), N'?') - FROM #deadlocks AS d - OPTION(RECOMPILE); - - SELECT - d.deadlock_type, - d.event_date, - database_name = - DB_NAME(d.database_id), - database_name_x = - d.database_name, - d.current_database_name, - d.spid, - d.deadlock_group, - d.client_option_1, - d.client_option_2, - d.lock_mode, - query_xml = - ( - SELECT - [processing-instruction(query)] = - d.inputbuf - FOR XML - PATH(N''), - TYPE - ), - query_string = - d.inputbuf, - d.object_names, - d.isolation_level, - d.owner_mode, - d.waiter_mode, - d.transaction_count, - d.login_name, - d.host_name, - d.client_app, - d.wait_time, - d.wait_resource, - d.priority, - d.log_used, - d.last_tran_started, - d.last_batch_started, - d.last_batch_completed, - d.transaction_name, - d.status, - /*These columns will be NULL for regular (non-parallel) deadlocks*/ - parallel_deadlock_details = - ( - SELECT - d.owner_waiter_type, - d.owner_activity, - d.owner_waiter_activity, - d.owner_merging, - d.owner_spilling, - d.owner_waiting_to_close, - d.waiter_waiter_type, - d.waiter_owner_activity, - d.waiter_waiter_activity, - d.waiter_merging, - d.waiter_spilling, - d.waiter_waiting_to_close - FOR XML - PATH('parallel_deadlock_details'), - TYPE - ), - d.owner_waiter_type, - d.owner_activity, - d.owner_waiter_activity, - d.owner_merging, - d.owner_spilling, - d.owner_waiting_to_close, - d.waiter_waiter_type, - d.waiter_owner_activity, - d.waiter_waiter_activity, - d.waiter_merging, - d.waiter_spilling, - d.waiter_waiting_to_close, - /*end parallel deadlock columns*/ - d.deadlock_graph, - d.is_victim, - d.id - INTO #deadlock_results - FROM #deadlocks AS d; - - IF @Debug = 1 BEGIN SET STATISTICS XML OFF; END; - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - /*There's too much risk of errors sending the*/ - IF @OutputDatabaseCheck = 0 - BEGIN - SET @ExportToExcel = 0; - END; - - SET @deadlock_result += N' - SELECT - server_name = - @@SERVERNAME, - dr.deadlock_type, - dr.event_date, - database_name = - COALESCE - ( - dr.database_name, - dr.database_name_x, - dr.current_database_name - ), - dr.spid, - dr.deadlock_group, - ' + CASE @ExportToExcel - WHEN 1 - THEN N' - query = dr.query_string, - object_names = - REPLACE( - REPLACE( - CONVERT - ( - nvarchar(MAX), - dr.object_names - ) COLLATE Latin1_General_BIN2, - '''', ''''), - '''', ''''),' - ELSE N'query = dr.query_xml, - dr.object_names,' - END + N' - dr.isolation_level, - dr.owner_mode, - dr.waiter_mode, - dr.lock_mode, - dr.transaction_count, - dr.client_option_1, - dr.client_option_2, - dr.login_name, - dr.host_name, - dr.client_app, - dr.wait_time, - dr.wait_resource, - dr.priority, - dr.log_used, - dr.last_tran_started, - dr.last_batch_started, - dr.last_batch_completed, - dr.transaction_name, - dr.status,' + - CASE - WHEN (@ExportToExcel = 1 - OR @OutputDatabaseCheck = 0) - THEN N' - dr.owner_waiter_type, - dr.owner_activity, - dr.owner_waiter_activity, - dr.owner_merging, - dr.owner_spilling, - dr.owner_waiting_to_close, - dr.waiter_waiter_type, - dr.waiter_owner_activity, - dr.waiter_waiter_activity, - dr.waiter_merging, - dr.waiter_spilling, - dr.waiter_waiting_to_close,' - ELSE N' - dr.parallel_deadlock_details,' - END + - CASE - @ExportToExcel - WHEN 1 - THEN N' - deadlock_graph = - REPLACE(REPLACE( - REPLACE(REPLACE( - CONVERT - ( - nvarchar(MAX), - dr.deadlock_graph - ) COLLATE Latin1_General_BIN2, - ''NCHAR(10)'', ''''), ''NCHAR(13)'', ''''), - ''CHAR(10)'', ''''), ''CHAR(13)'', '''')' - ELSE N' - dr.deadlock_graph' - END + N' - FROM #deadlock_results AS dr - ORDER BY - dr.event_date, - dr.is_victim DESC - OPTION(RECOMPILE, LOOP JOIN, HASH JOIN); - '; +RAISERROR (N'Starting loop through databases',0,1) WITH NOWAIT; +DECLARE c1 CURSOR +LOCAL FAST_FORWARD +FOR +SELECT dl.DatabaseName +FROM #DatabaseList dl +LEFT OUTER JOIN #Ignore_Databases i ON dl.DatabaseName = i.DatabaseName +WHERE COALESCE(dl.secondary_role_allow_connections_desc, 'OK') <> 'NO' + AND i.DatabaseName IS NULL +ORDER BY dl.DatabaseName; - IF (@OutputDatabaseCheck = 0) - BEGIN - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Results to table %s', 0, 1, @d) WITH NOWAIT; +OPEN c1; +FETCH NEXT FROM c1 INTO @DatabaseName; + WHILE @@FETCH_STATUS = 0 - IF @Debug = 1 - BEGIN - PRINT @deadlock_result; - SET STATISTICS XML ON; - END; +BEGIN + + RAISERROR (@LineFeed, 0, 1) WITH NOWAIT; + RAISERROR (@LineFeed, 0, 1) WITH NOWAIT; + RAISERROR (@DatabaseName, 0, 1) WITH NOWAIT; - INSERT INTO - DeadLockTbl - ( - ServerName, - deadlock_type, - event_date, - database_name, - spid, - deadlock_group, - query, - object_names, - isolation_level, - owner_mode, - waiter_mode, - lock_mode, - transaction_count, - client_option_1, - client_option_2, - login_name, - host_name, - client_app, - wait_time, - wait_resource, - priority, - log_used, - last_tran_started, - last_batch_started, - last_batch_completed, - transaction_name, - status, - owner_waiter_type, - owner_activity, - owner_waiter_activity, - owner_merging, - owner_spilling, - owner_waiting_to_close, - waiter_waiter_type, - waiter_owner_activity, - waiter_waiter_activity, - waiter_merging, - waiter_spilling, - waiter_waiting_to_close, - deadlock_graph - ) - EXEC sys.sp_executesql - @deadlock_result; +SELECT @DatabaseID = [database_id] +FROM sys.databases + WHERE [name] = @DatabaseName + AND user_access_desc='MULTI_USER' + AND state_desc = 'ONLINE'; - IF @Debug = 1 - BEGIN - SET STATISTICS XML OFF; - END; +---------------------------------------- +--STEP 1: OBSERVE THE PATIENT +--This step puts index information into temp tables. +---------------------------------------- +BEGIN TRY + BEGIN + DECLARE @d VARCHAR(19) = CONVERT(VARCHAR(19), GETDATE(), 121); + RAISERROR (N'starting at %s',0,1, @d) WITH NOWAIT; - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + --Validate SQL Server Version - DROP SYNONYM DeadLockTbl; + IF (SELECT LEFT(@SQLServerProductVersion, + CHARINDEX('.',@SQLServerProductVersion,0)-1 + )) <= 9 + BEGIN + SET @msg=N'sp_BlitzIndex is only supported on SQL Server 2008 and higher. The version of this instance is: ' + @SQLServerProductVersion; + RAISERROR(@msg,16,1); + END; - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Findings to table %s', 0, 1, @d) WITH NOWAIT; + --Short circuit here if database name does not exist. + IF @DatabaseName IS NULL OR @DatabaseID IS NULL + BEGIN + SET @msg='Database does not exist or is not online/multi-user: cannot proceed.'; + RAISERROR(@msg,16,1); + END; - INSERT INTO - DeadlockFindings - ( - ServerName, - check_id, - database_name, - object_name, - finding_group, - finding - ) - SELECT - @@SERVERNAME, - df.check_id, - df.database_name, - df.object_name, - df.finding_group, - df.finding - FROM #deadlock_findings AS df - ORDER BY df.check_id - OPTION(RECOMPILE); + --Validate parameters. + IF (@Mode NOT IN (0,1,2,3,4)) + BEGIN + SET @msg=N'Invalid @Mode parameter. 0=diagnose, 1=summarize, 2=index detail, 3=missing index detail, 4=diagnose detail'; + RAISERROR(@msg,16,1); + END; - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + IF (@Mode <> 0 AND @TableName IS NOT NULL) + BEGIN + SET @msg=N'Setting the @Mode doesn''t change behavior if you supply @TableName. Use default @Mode=0 to see table detail.'; + RAISERROR(@msg,16,1); + END; - DROP SYNONYM DeadlockFindings; /*done with inserting.*/ + IF ((@Mode <> 0 OR @TableName IS NOT NULL) AND @Filter <> 0) + BEGIN + SET @msg=N'@Filter only applies when @Mode=0 and @TableName is not specified. Please try again.'; + RAISERROR(@msg,16,1); END; - ELSE /*Output to database is not set output to client app*/ + + IF (@SchemaName IS NOT NULL AND @TableName IS NULL) BEGIN - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Results to client %s', 0, 1, @d) WITH NOWAIT; - - IF @Debug = 1 BEGIN SET STATISTICS XML ON; END; - - EXEC sys.sp_executesql - @deadlock_result; - - IF @Debug = 1 - BEGIN - SET STATISTICS XML OFF; - PRINT @deadlock_result; - END; - - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + SET @msg='We can''t run against a whole schema! Specify a @TableName, or leave both NULL for diagnosis.'; + RAISERROR(@msg,16,1); + END; - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Getting available execution plans for deadlocks %s', 0, 1, @d) WITH NOWAIT; - - SELECT DISTINCT - available_plans = - 'available_plans', - ds.proc_name, - sql_handle = - CONVERT(varbinary(64), ds.sql_handle, 1), - dow.database_name, - dow.database_id, - dow.object_name, - query_xml = - TRY_CAST(dr.query_xml AS nvarchar(MAX)) - INTO #available_plans - FROM #deadlock_stack AS ds - JOIN #deadlock_owner_waiter AS dow - ON dow.owner_id = ds.id - AND dow.event_date = ds.event_date - JOIN #deadlock_results AS dr - ON dr.id = ds.id - AND dr.event_date = ds.event_date - OPTION(RECOMPILE); - SELECT - deqs.sql_handle, - deqs.plan_handle, - deqs.statement_start_offset, - deqs.statement_end_offset, - deqs.creation_time, - deqs.last_execution_time, - deqs.execution_count, - total_worker_time_ms = - deqs.total_worker_time / 1000., - avg_worker_time_ms = - CONVERT(decimal(38, 6), deqs.total_worker_time / 1000. / deqs.execution_count), - total_elapsed_time_ms = - deqs.total_elapsed_time / 1000., - avg_elapsed_time_ms = - CONVERT(decimal(38, 6), deqs.total_elapsed_time / 1000. / deqs.execution_count), - executions_per_second = - ISNULL - ( - deqs.execution_count / - NULLIF - ( - DATEDIFF - ( - SECOND, - deqs.creation_time, - NULLIF(deqs.last_execution_time, '1900-01-01 00:00:00.000') - ), - 0 - ), - 0 - ), - total_physical_reads_mb = - deqs.total_physical_reads * 8. / 1024., - total_logical_writes_mb = - deqs.total_logical_writes * 8. / 1024., - total_logical_reads_mb = - deqs.total_logical_reads * 8. / 1024., - min_grant_mb = - deqs.min_grant_kb * 8. / 1024., - max_grant_mb = - deqs.max_grant_kb * 8. / 1024., - min_used_grant_mb = - deqs.min_used_grant_kb * 8. / 1024., - max_used_grant_mb = - deqs.max_used_grant_kb * 8. / 1024., - deqs.min_reserved_threads, - deqs.max_reserved_threads, - deqs.min_used_threads, - deqs.max_used_threads, - deqs.total_rows - INTO #dm_exec_query_stats - FROM sys.dm_exec_query_stats AS deqs - WHERE EXISTS - ( - SELECT - 1/0 - FROM #available_plans AS ap - WHERE ap.sql_handle = deqs.sql_handle - ) - AND deqs.query_hash IS NOT NULL; - - CREATE CLUSTERED INDEX - deqs - ON #dm_exec_query_stats - ( - sql_handle, - plan_handle - ); - - SELECT - ap.available_plans, - ap.database_name, - query_text = - TRY_CAST(ap.query_xml AS xml), - ap.query_plan, - ap.creation_time, - ap.last_execution_time, - ap.execution_count, - ap.executions_per_second, - ap.total_worker_time_ms, - ap.avg_worker_time_ms, - ap.total_elapsed_time_ms, - ap.avg_elapsed_time_ms, - ap.total_logical_reads_mb, - ap.total_physical_reads_mb, - ap.total_logical_writes_mb, - ap.min_grant_mb, - ap.max_grant_mb, - ap.min_used_grant_mb, - ap.max_used_grant_mb, - ap.min_reserved_threads, - ap.max_reserved_threads, - ap.min_used_threads, - ap.max_used_threads, - ap.total_rows, - ap.sql_handle, - ap.statement_start_offset, - ap.statement_end_offset - FROM - ( - - SELECT - ap.*, - c.statement_start_offset, - c.statement_end_offset, - c.creation_time, - c.last_execution_time, - c.execution_count, - c.total_worker_time_ms, - c.avg_worker_time_ms, - c.total_elapsed_time_ms, - c.avg_elapsed_time_ms, - c.executions_per_second, - c.total_physical_reads_mb, - c.total_logical_writes_mb, - c.total_logical_reads_mb, - c.min_grant_mb, - c.max_grant_mb, - c.min_used_grant_mb, - c.max_used_grant_mb, - c.min_reserved_threads, - c.max_reserved_threads, - c.min_used_threads, - c.max_used_threads, - c.total_rows, - c.query_plan - FROM #available_plans AS ap - OUTER APPLY - ( - SELECT - deqs.*, - query_plan = - TRY_CAST(deps.query_plan AS xml) - FROM #dm_exec_query_stats deqs - OUTER APPLY sys.dm_exec_text_query_plan - ( - deqs.plan_handle, - deqs.statement_start_offset, - deqs.statement_end_offset - ) AS deps - WHERE deqs.sql_handle = ap.sql_handle - AND deps.dbid = ap.database_id - ) AS c - ) AS ap - WHERE ap.query_plan IS NOT NULL - ORDER BY - ap.avg_worker_time_ms DESC - OPTION(RECOMPILE, LOOP JOIN, HASH JOIN); + IF (@TableName IS NOT NULL AND @SchemaName IS NULL) + BEGIN + SET @SchemaName=N'dbo'; + SET @msg='@SchemaName wasn''t specified-- assuming schema=dbo.'; + RAISERROR(@msg,1,1) WITH NOWAIT; + END; - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Returning findings %s', 0, 1, @d) WITH NOWAIT; - - SELECT - df.check_id, - df.database_name, - df.object_name, - df.finding_group, - df.finding - FROM #deadlock_findings AS df - ORDER BY - df.check_id, - df.sort_order - OPTION(RECOMPILE); + --If a table is specified, grab the object id. + --Short circuit if it doesn't exist. + IF @TableName IS NOT NULL + BEGIN + SET @dsql = N' + SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + SELECT @ObjectID= OBJECT_ID + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.objects AS so + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS sc on + so.schema_id=sc.schema_id + where so.type in (''U'', ''V'') + and so.name=' + QUOTENAME(@TableName,'''')+ N' + and sc.name=' + QUOTENAME(@SchemaName,'''')+ N' + /*Has a row in sys.indexes. This lets us get indexed views.*/ + and exists ( + SELECT si.name + FROM ' + QUOTENAME(@DatabaseName) + '.sys.indexes AS si + WHERE so.object_id=si.object_id) + OPTION (RECOMPILE);'; + + SET @params='@ObjectID INT OUTPUT'; + + IF @dsql IS NULL + RAISERROR('@dsql is null',16,1); + + EXEC sp_executesql @dsql, @params, @ObjectID=@ObjectID OUTPUT; - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - END; /*done with output to client app.*/ + IF @ObjectID IS NULL + BEGIN + SET @msg=N'Oh, this is awkward. I can''t find the table or indexed view you''re looking for in that database.' + CHAR(10) + + N'Please check your parameters.'; + RAISERROR(@msg,1,1); + RETURN; + END; END; + --set @collation + SELECT @collation=collation_name + FROM sys.databases + WHERE database_id=@DatabaseID; + + --insert columns for clustered indexes and heaps + --collect info on identity columns for this one + SET @dsql = N'/* sp_BlitzIndex */ + SET LOCK_TIMEOUT 1000; /* To fix locking bug in sys.identity_columns. See Github issue #2176. */ + SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + SELECT ' + CAST(@DatabaseID AS NVARCHAR(16)) + ', + s.name, + si.object_id, + si.index_id, + sc.key_ordinal, + sc.is_included_column, + sc.is_descending_key, + sc.partition_ordinal, + c.name as column_name, + st.name as system_type_name, + c.max_length, + c.[precision], + c.[scale], + c.collation_name, + c.is_nullable, + c.is_identity, + c.is_computed, + c.is_replicated, + ' + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N'c.is_sparse' ELSE N'NULL as is_sparse' END + N', + ' + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N'c.is_filestream' ELSE N'NULL as is_filestream' END + N', + CAST(ic.seed_value AS DECIMAL(38,0)), + CAST(ic.increment_value AS DECIMAL(38,0)), + CAST(ic.last_value AS DECIMAL(38,0)), + ic.is_not_for_replication + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.indexes si + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns c ON + si.object_id=c.object_id + LEFT JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.index_columns sc ON + sc.object_id = si.object_id + and sc.index_id=si.index_id + AND sc.column_id=c.column_id + LEFT JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.identity_columns ic ON + c.object_id=ic.object_id and + c.column_id=ic.column_id + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.types st ON + c.system_type_id=st.system_type_id + AND c.user_type_id=st.user_type_id + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects AS so ON si.object_id = so.object_id + AND so.is_ms_shipped = 0 + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s ON s.schema_id = so.schema_id + WHERE si.index_id in (0,1) ' + + CASE WHEN @ObjectID IS NOT NULL + THEN N' AND si.object_id=' + CAST(@ObjectID AS NVARCHAR(30)) + ELSE N'' END + + N'OPTION (RECOMPILE);'; + + IF @dsql IS NULL + RAISERROR('@dsql is null',16,1); + + RAISERROR (N'Inserting data into #IndexColumns for clustered indexes and heaps',0,1) WITH NOWAIT; IF @Debug = 1 - BEGIN - SELECT - table_name = N'#deadlock_data', - * - FROM #deadlock_data AS dd - OPTION(RECOMPILE); + BEGIN + PRINT SUBSTRING(@dsql, 1, 4000); + PRINT SUBSTRING(@dsql, 4001, 4000); + PRINT SUBSTRING(@dsql, 8001, 4000); + PRINT SUBSTRING(@dsql, 12001, 4000); + PRINT SUBSTRING(@dsql, 16001, 4000); + PRINT SUBSTRING(@dsql, 20001, 4000); + PRINT SUBSTRING(@dsql, 24001, 4000); + PRINT SUBSTRING(@dsql, 28001, 4000); + PRINT SUBSTRING(@dsql, 32001, 4000); + PRINT SUBSTRING(@dsql, 36001, 4000); + END; + BEGIN TRY + INSERT #IndexColumns ( database_id, [schema_name], [object_id], index_id, key_ordinal, is_included_column, is_descending_key, partition_ordinal, + column_name, system_type_name, max_length, precision, scale, collation_name, is_nullable, is_identity, is_computed, + is_replicated, is_sparse, is_filestream, seed_value, increment_value, last_value, is_not_for_replication ) + EXEC sp_executesql @dsql; + END TRY + BEGIN CATCH + RAISERROR (N'Failure inserting data into #IndexColumns for clustered indexes and heaps.', 0,1) WITH NOWAIT; - SELECT - table_name = N'#dd', - * - FROM #dd AS d - OPTION(RECOMPILE); + IF @dsql IS NOT NULL + BEGIN + SET @msg= 'Last @dsql: ' + @dsql; + RAISERROR(@msg, 0, 1) WITH NOWAIT; + END; - SELECT - table_name = N'#deadlock_resource', - * - FROM #deadlock_resource AS dr - OPTION(RECOMPILE); + SELECT @msg = @DatabaseName + N' database failed to process. ' + ERROR_MESSAGE(), + @ErrorSeverity = 0, @ErrorState = ERROR_STATE(); + RAISERROR (@msg,@ErrorSeverity, @ErrorState )WITH NOWAIT; - SELECT - table_name = N'#deadlock_resource_parallel', - * - FROM #deadlock_resource_parallel AS drp - OPTION(RECOMPILE); + WHILE @@trancount > 0 + ROLLBACK; + + RETURN; + END CATCH; + + + --insert columns for nonclustered indexes + --this uses a full join to sys.index_columns + --We don't collect info on identity columns here. They may be in NC indexes, but we just analyze identities in the base table. + SET @dsql = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + SELECT ' + CAST(@DatabaseID AS NVARCHAR(16)) + ', + s.name, + si.object_id, + si.index_id, + sc.key_ordinal, + sc.is_included_column, + sc.is_descending_key, + sc.partition_ordinal, + c.name as column_name, + st.name as system_type_name, + c.max_length, + c.[precision], + c.[scale], + c.collation_name, + c.is_nullable, + c.is_identity, + c.is_computed, + c.is_replicated, + ' + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N'c.is_sparse' ELSE N'NULL AS is_sparse' END + N', + ' + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N'c.is_filestream' ELSE N'NULL AS is_filestream' END + N' + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.indexes AS si + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns AS c ON + si.object_id=c.object_id + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.index_columns AS sc ON + sc.object_id = si.object_id + and sc.index_id=si.index_id + AND sc.column_id=c.column_id + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.types AS st ON + c.system_type_id=st.system_type_id + AND c.user_type_id=st.user_type_id + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects AS so ON si.object_id = so.object_id + AND so.is_ms_shipped = 0 + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s ON s.schema_id = so.schema_id + WHERE si.index_id not in (0,1) ' + + CASE WHEN @ObjectID IS NOT NULL + THEN N' AND si.object_id=' + CAST(@ObjectID AS NVARCHAR(30)) + ELSE N'' END + + N'OPTION (RECOMPILE);'; - SELECT - table_name = N'#deadlock_owner_waiter', - * - FROM #deadlock_owner_waiter AS dow - OPTION(RECOMPILE); + IF @dsql IS NULL + RAISERROR('@dsql is null',16,1); - SELECT - table_name = N'#deadlock_process', - * - FROM #deadlock_process AS dp - OPTION(RECOMPILE); + RAISERROR (N'Inserting data into #IndexColumns for nonclustered indexes',0,1) WITH NOWAIT; + IF @Debug = 1 + BEGIN + PRINT SUBSTRING(@dsql, 0, 4000); + PRINT SUBSTRING(@dsql, 4000, 8000); + PRINT SUBSTRING(@dsql, 8000, 12000); + PRINT SUBSTRING(@dsql, 12000, 16000); + PRINT SUBSTRING(@dsql, 16000, 20000); + PRINT SUBSTRING(@dsql, 20000, 24000); + PRINT SUBSTRING(@dsql, 24000, 28000); + PRINT SUBSTRING(@dsql, 28000, 32000); + PRINT SUBSTRING(@dsql, 32000, 36000); + PRINT SUBSTRING(@dsql, 36000, 40000); + END; + INSERT #IndexColumns ( database_id, [schema_name], [object_id], index_id, key_ordinal, is_included_column, is_descending_key, partition_ordinal, + column_name, system_type_name, max_length, precision, scale, collation_name, is_nullable, is_identity, is_computed, + is_replicated, is_sparse, is_filestream ) + EXEC sp_executesql @dsql; + + SET @dsql = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + SELECT ' + CAST(@DatabaseID AS NVARCHAR(10)) + N' AS database_id, + so.object_id, + si.index_id, + si.type, + @i_DatabaseName AS database_name, + COALESCE(sc.NAME, ''Unknown'') AS [schema_name], + COALESCE(so.name, ''Unknown'') AS [object_name], + COALESCE(si.name, ''Unknown'') AS [index_name], + CASE WHEN so.[type] = CAST(''V'' AS CHAR(2)) THEN 1 ELSE 0 END, + si.is_unique, + si.is_primary_key, + si.is_unique_constraint, + CASE when si.type = 3 THEN 1 ELSE 0 END AS is_XML, + CASE when si.type = 4 THEN 1 ELSE 0 END AS is_spatial, + CASE when si.type = 6 THEN 1 ELSE 0 END AS is_NC_columnstore, + CASE when si.type = 5 then 1 else 0 end as is_CX_columnstore, + CASE when si.data_space_id = 0 then 1 else 0 end as is_in_memory_oltp, + si.is_disabled, + si.is_hypothetical, + si.is_padded, + si.fill_factor,' + + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N' + CASE WHEN si.filter_definition IS NOT NULL THEN si.filter_definition + ELSE N'''' + END AS filter_definition' ELSE N''''' AS filter_definition' END + + CASE + WHEN @OptimizeForSequentialKey = 1 + THEN N', si.optimize_for_sequential_key' + ELSE N', CONVERT(BIT, NULL) AS optimize_for_sequential_key' + END + + N', + ISNULL(us.user_seeks, 0), + ISNULL(us.user_scans, 0), + ISNULL(us.user_lookups, 0), + ISNULL(us.user_updates, 0), + us.last_user_seek, + us.last_user_scan, + us.last_user_lookup, + us.last_user_update, + so.create_date, + so.modify_date + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.indexes AS si WITH (NOLOCK) + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects AS so WITH (NOLOCK) ON si.object_id = so.object_id + AND so.is_ms_shipped = 0 /*Exclude objects shipped by Microsoft*/ + AND so.type <> ''TF'' /*Exclude table valued functions*/ + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas sc ON so.schema_id = sc.schema_id + LEFT JOIN sys.dm_db_index_usage_stats AS us WITH (NOLOCK) ON si.[object_id] = us.[object_id] + AND si.index_id = us.index_id + AND us.database_id = ' + CAST(@DatabaseID AS NVARCHAR(10)) + N' + WHERE si.[type] IN ( 0, 1, 2, 3, 4, 5, 6 ) + /* Heaps, clustered, nonclustered, XML, spatial, Cluster Columnstore, NC Columnstore */ ' + + CASE WHEN @TableName IS NOT NULL THEN N' and so.name=' + QUOTENAME(@TableName,N'''') + N' ' ELSE N'' END + + CASE WHEN ( @IncludeInactiveIndexes = 0 + AND @Mode IN (0, 4) + AND @TableName IS NULL ) + THEN N'AND ( us.user_seeks + us.user_scans + us.user_lookups + us.user_updates ) > 0' + ELSE N'' + END + + N'OPTION ( RECOMPILE ); + '; + IF @dsql IS NULL + RAISERROR('@dsql is null',16,1); - SELECT - table_name = N'#deadlock_stack', - * - FROM #deadlock_stack AS ds - OPTION(RECOMPILE); + RAISERROR (N'Inserting data into #IndexSanity',0,1) WITH NOWAIT; + IF @Debug = 1 + BEGIN + PRINT SUBSTRING(@dsql, 0, 4000); + PRINT SUBSTRING(@dsql, 4000, 8000); + PRINT SUBSTRING(@dsql, 8000, 12000); + PRINT SUBSTRING(@dsql, 12000, 16000); + PRINT SUBSTRING(@dsql, 16000, 20000); + PRINT SUBSTRING(@dsql, 20000, 24000); + PRINT SUBSTRING(@dsql, 24000, 28000); + PRINT SUBSTRING(@dsql, 28000, 32000); + PRINT SUBSTRING(@dsql, 32000, 36000); + PRINT SUBSTRING(@dsql, 36000, 40000); + END; + INSERT #IndexSanity ( [database_id], [object_id], [index_id], [index_type], [database_name], [schema_name], [object_name], + index_name, is_indexed_view, is_unique, is_primary_key, is_unique_constraint, is_XML, is_spatial, is_NC_columnstore, is_CX_columnstore, is_in_memory_oltp, + is_disabled, is_hypothetical, is_padded, fill_factor, filter_definition, [optimize_for_sequential_key], user_seeks, user_scans, + user_lookups, user_updates, last_user_seek, last_user_scan, last_user_lookup, last_user_update, + create_date, modify_date ) + EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; - SELECT - table_name = N'#deadlocks', - * - FROM #deadlocks AS d - OPTION(RECOMPILE); - SELECT - table_name = N'#deadlock_results', - * - FROM #deadlock_results AS dr - OPTION(RECOMPILE); + RAISERROR (N'Checking partition count',0,1) WITH NOWAIT; + IF @BringThePain = 0 AND @SkipPartitions = 0 AND @TableName IS NULL + BEGIN + /* Count the total number of partitions */ + SET @dsql = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + SELECT @RowcountOUT = SUM(1) FROM ' + QUOTENAME(@DatabaseName) + '.sys.partitions WHERE partition_number > 1 OPTION ( RECOMPILE );'; + EXEC sp_executesql @dsql, N'@RowcountOUT BIGINT OUTPUT', @RowcountOUT = @Rowcount OUTPUT; + IF @Rowcount > 100 + BEGIN + RAISERROR (N'Setting @SkipPartitions = 1 because > 100 partitions were found. To check them, you must set @BringThePain = 1.',0,1) WITH NOWAIT; + SET @SkipPartitions = 1; + INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, URL, details, index_definition, + index_usage_summary, index_size_summary ) + VALUES ( 1, 0 , + 'Some Checks Were Skipped', + '@SkipPartitions Forced to 1', + 'http://FirstResponderKit.org', CAST(@Rowcount AS NVARCHAR(50)) + ' partitions found. To analyze them, use @BringThePain = 1.', 'We try to keep things quick - and warning, running @BringThePain = 1 can take tens of minutes.', '', '' + ); + END; + END; - SELECT - table_name = N'#x', - * - FROM #x AS x - OPTION(RECOMPILE); - SELECT - table_name = N'@sysAssObjId', - * - FROM @sysAssObjId AS s - OPTION(RECOMPILE); - SELECT - table_name = N'#available_plans', - * - FROM #available_plans AS ap - OPTION(RECOMPILE); + IF (@SkipPartitions = 0) + BEGIN + IF (SELECT LEFT(@SQLServerProductVersion, + CHARINDEX('.',@SQLServerProductVersion,0)-1 )) <= 2147483647 --Make change here + BEGIN + + RAISERROR (N'Preferring non-2012 syntax with LEFT JOIN to sys.dm_db_index_operational_stats',0,1) WITH NOWAIT; - SELECT - table_name = N'#dm_exec_query_stats', - * - FROM #dm_exec_query_stats - OPTION(RECOMPILE); + --NOTE: If you want to use the newer syntax for 2012+, you'll have to change 2147483647 to 11 on line ~819 + --This change was made because on a table with lots of paritions, the OUTER APPLY was crazy slow. - SELECT - procedure_parameters = - 'procedure_parameters', - DatabaseName = - @DatabaseName, - StartDate = - @StartDate, - EndDate = - @EndDate, - ObjectName = - @ObjectName, - StoredProcName = - @StoredProcName, - AppName = - @AppName, - HostName = - @HostName, - LoginName = - @LoginName, - EventSessionName = - @EventSessionName, - TargetSessionType = - @TargetSessionType, - VictimsOnly = - @VictimsOnly, - Debug = - @Debug, - Help = - @Help, - Version = - @Version, - VersionDate = - @VersionDate, - VersionCheckMode = - @VersionCheckMode, - OutputDatabaseName = - @OutputDatabaseName, - OutputSchemaName = - @OutputSchemaName, - OutputTableName = - @OutputTableName, - ExportToExcel = - @ExportToExcel; + -- get relevant columns from sys.dm_db_partition_stats, sys.partitions and sys.objects + IF OBJECT_ID('tempdb..#dm_db_partition_stats_etc') IS NOT NULL + DROP TABLE #dm_db_partition_stats_etc; - SELECT - declared_variables = - 'declared_variables', - DatabaseId = - @DatabaseId, - StartDateUTC = - @StartDateUTC, - EndDateUTC = - @EndDateUTC, - ProductVersion = - @ProductVersion, - ProductVersionMajor = - @ProductVersionMajor, - ProductVersionMinor = - @ProductVersionMinor, - ObjectFullName = - @ObjectFullName, - Azure = - @Azure, - RDS = - @RDS, - d = - @d, - StringToExecute = - @StringToExecute, - StringToExecuteParams = - @StringToExecuteParams, - r = - @r, - OutputTableFindings = - @OutputTableFindings, - DeadlockCount = - @DeadlockCount, - ServerName = - @ServerName, - OutputDatabaseCheck = - @OutputDatabaseCheck, - SessionId = - @SessionId, - TargetSessionId = - @TargetSessionId, - FileName = - @FileName, - inputbuf_bom = - @inputbuf_bom, - deadlock_result = - @deadlock_result; - END; /*End debug*/ - END; /*Final End*/ -GO -SET ANSI_NULLS ON; -SET ANSI_PADDING ON; -SET ANSI_WARNINGS ON; -SET ARITHABORT ON; -SET CONCAT_NULL_YIELDS_NULL ON; -SET QUOTED_IDENTIFIER ON; -SET STATISTICS IO OFF; -SET STATISTICS TIME OFF; -GO + create table #dm_db_partition_stats_etc + ( + database_id smallint not null + , object_id int not null + , sname sysname NULL + , index_id int + , partition_number int + , partition_id bigint + , row_count bigint + , reserved_MB bigint + , reserved_LOB_MB bigint + , reserved_row_overflow_MB bigint + , lock_escalation_desc nvarchar(60) + , data_compression_desc nvarchar(60) + ) -DECLARE @msg NVARCHAR(MAX) = N''; + -- get relevant info from sys.dm_db_index_operational_stats + IF OBJECT_ID('tempdb..#dm_db_index_operational_stats') IS NOT NULL + DROP TABLE #dm_db_index_operational_stats; + create table #dm_db_index_operational_stats + ( + database_id smallint not null + , object_id int not null + , index_id int + , partition_number int + , hobt_id bigint + , leaf_insert_count bigint + , leaf_delete_count bigint + , leaf_update_count bigint + , range_scan_count bigint + , singleton_lookup_count bigint + , forwarded_fetch_count bigint + , lob_fetch_in_pages bigint + , lob_fetch_in_bytes bigint + , row_overflow_fetch_in_pages bigint + , row_overflow_fetch_in_bytes bigint + , row_lock_count bigint + , row_lock_wait_count bigint + , row_lock_wait_in_ms bigint + , page_lock_count bigint + , page_lock_wait_count bigint + , page_lock_wait_in_ms bigint + , index_lock_promotion_attempt_count bigint + , index_lock_promotion_count bigint + , page_latch_wait_count bigint + , page_latch_wait_in_ms bigint + , page_io_latch_wait_count bigint + , page_io_latch_wait_in_ms bigint + ) + + SET @dsql = N' + DECLARE @d VARCHAR(19) = CONVERT(VARCHAR(19), GETDATE(), 121) + RAISERROR (N''start getting data into #dm_db_partition_stats_etc at %s'',0,1, @d) WITH NOWAIT; + SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + INSERT INTO #dm_db_partition_stats_etc + ( + database_id, object_id, sname, index_id, partition_number, partition_id, row_count, reserved_MB, reserved_LOB_MB, reserved_row_overflow_MB, lock_escalation_desc, data_compression_desc + ) + SELECT ' + CAST(@DatabaseID AS NVARCHAR(10)) + N' AS database_id, + ps.object_id, + s.name as sname, + ps.index_id, + ps.partition_number, + ps.partition_id, + ps.row_count, + ps.reserved_page_count * 8. / 1024. AS reserved_MB, + ps.lob_reserved_page_count * 8. / 1024. AS reserved_LOB_MB, + ps.row_overflow_reserved_page_count * 8. / 1024. AS reserved_row_overflow_MB, + le.lock_escalation_desc, + ' + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N'par.data_compression_desc ' ELSE N'null as data_compression_desc ' END + N' +'; - -- Must be a compatible, on-prem version of SQL (2016+) -IF ( (SELECT CONVERT(NVARCHAR(128), SERVERPROPERTY ('EDITION'))) <> 'SQL Azure' - AND (SELECT PARSENAME(CONVERT(NVARCHAR(128), SERVERPROPERTY ('PRODUCTVERSION')), 4)) < 13 - ) - -- or Azure Database (not Azure Data Warehouse), running at database compat level 130+ -OR ( (SELECT CONVERT(NVARCHAR(128), SERVERPROPERTY ('EDITION'))) = 'SQL Azure' - AND (SELECT SERVERPROPERTY ('ENGINEEDITION')) NOT IN (5,8) - AND (SELECT [compatibility_level] FROM sys.databases WHERE [name] = DB_NAME()) < 130 - ) -BEGIN - SELECT @msg = N'Sorry, sp_BlitzQueryStore doesn''t work on versions of SQL prior to 2016, or Azure Database compatibility < 130.' + REPLICATE(CHAR(13), 7933); - PRINT @msg; - RETURN; -END; + SET @dsql = @dsql + N' + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_partition_stats AS ps + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partitions AS par on ps.partition_id=par.partition_id + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects AS so ON ps.object_id = so.object_id + AND so.is_ms_shipped = 0 /*Exclude objects shipped by Microsoft*/ + AND so.type <> ''TF'' /*Exclude table valued functions*/ + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s ON s.schema_id = so.schema_id + OUTER APPLY (SELECT st.lock_escalation_desc + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.tables st + WHERE st.object_id = ps.object_id + AND ps.index_id < 2 ) le + WHERE 1=1 + ' + CASE WHEN @ObjectID IS NOT NULL THEN N'AND so.object_id=' + CAST(@ObjectID AS NVARCHAR(30)) + N' ' ELSE N' ' END + N' + ' + CASE WHEN @Filter = 2 THEN N'AND ps.reserved_page_count * 8./1024. > ' + CAST(@FilterMB AS NVARCHAR(5)) + N' ' ELSE N' ' END + N' + GROUP BY ps.object_id, + s.name, + ps.index_id, + ps.partition_number, + ps.partition_id, + ps.row_count, + ps.reserved_page_count, + ps.lob_reserved_page_count, + ps.row_overflow_reserved_page_count, + le.lock_escalation_desc, + ' + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N'par.data_compression_desc ' ELSE N'null as data_compression_desc ' END + N' + ORDER BY ps.object_id, ps.index_id, ps.partition_number + OPTION ( RECOMPILE ' + + CASE WHEN (PARSENAME(@SQLServerProductVersion, 4) ) > 12 THEN N', min_grant_percent = 1 ' + ELSE N' ' + END + N'); -IF OBJECT_ID('dbo.sp_BlitzQueryStore') IS NULL - EXEC ('CREATE PROCEDURE dbo.sp_BlitzQueryStore AS RETURN 0;'); -GO + SET @d = CONVERT(VARCHAR(19), GETDATE(), 121) + RAISERROR (N''start getting data into #dm_db_index_operational_stats at %s.'',0,1, @d) WITH NOWAIT; + + insert into #dm_db_index_operational_stats + ( + database_id + , object_id + , index_id + , partition_number + , hobt_id + , leaf_insert_count + , leaf_delete_count + , leaf_update_count + , range_scan_count + , singleton_lookup_count + , forwarded_fetch_count + , lob_fetch_in_pages + , lob_fetch_in_bytes + , row_overflow_fetch_in_pages + , row_overflow_fetch_in_bytes + , row_lock_count + , row_lock_wait_count + , row_lock_wait_in_ms + , page_lock_count + , page_lock_wait_count + , page_lock_wait_in_ms + , index_lock_promotion_attempt_count + , index_lock_promotion_count + , page_latch_wait_count + , page_latch_wait_in_ms + , page_io_latch_wait_count + , page_io_latch_wait_in_ms + ) + + select os.database_id + , os.object_id + , os.index_id + , os.partition_number ' + + CASE WHEN (PARSENAME(@SQLServerProductVersion, 4) ) > 12 THEN N', os.hobt_id ' + ELSE N', NULL AS hobt_id ' + END + N' + , os.leaf_insert_count + , os.leaf_delete_count + , os.leaf_update_count + , os.range_scan_count + , os.singleton_lookup_count + , os.forwarded_fetch_count + , os.lob_fetch_in_pages + , os.lob_fetch_in_bytes + , os.row_overflow_fetch_in_pages + , os.row_overflow_fetch_in_bytes + , os.row_lock_count + , os.row_lock_wait_count + , os.row_lock_wait_in_ms + , os.page_lock_count + , os.page_lock_wait_count + , os.page_lock_wait_in_ms + , os.index_lock_promotion_attempt_count + , os.index_lock_promotion_count + , os.page_latch_wait_count + , os.page_latch_wait_in_ms + , os.page_io_latch_wait_count + , os.page_io_latch_wait_in_ms + from ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_index_operational_stats('+ CAST(@DatabaseID AS NVARCHAR(10)) +', NULL, NULL,NULL) AS os + OPTION ( RECOMPILE ' + + CASE WHEN (PARSENAME(@SQLServerProductVersion, 4) ) > 12 THEN N', min_grant_percent = 1 ' + ELSE N' ' + END + N'); -ALTER PROCEDURE dbo.sp_BlitzQueryStore - @Help BIT = 0, - @DatabaseName NVARCHAR(128) = NULL , - @Top INT = 3, - @StartDate DATETIME2 = NULL, - @EndDate DATETIME2 = NULL, - @MinimumExecutionCount INT = NULL, - @DurationFilter DECIMAL(38,4) = NULL , - @StoredProcName NVARCHAR(128) = NULL, - @Failed BIT = 0, - @PlanIdFilter INT = NULL, - @QueryIdFilter INT = NULL, - @ExportToExcel BIT = 0, - @HideSummary BIT = 0 , - @SkipXML BIT = 0, - @Debug BIT = 0, - @ExpertMode BIT = 0, - @Version VARCHAR(30) = NULL OUTPUT, - @VersionDate DATETIME = NULL OUTPUT, - @VersionCheckMode BIT = 0 -WITH RECOMPILE -AS -BEGIN /*First BEGIN*/ + SET @d = CONVERT(VARCHAR(19), GETDATE(), 121) + RAISERROR (N''finished getting data into #dm_db_index_operational_stats at %s.'',0,1, @d) WITH NOWAIT; + '; + END; + ELSE + BEGIN + RAISERROR (N'Using 2012 syntax to query sys.dm_db_index_operational_stats',0,1) WITH NOWAIT; + --This is the syntax that will be used if you change 2147483647 to 11 on line ~819. + --If you have a lot of paritions and this suddenly starts running for a long time, change it back. + SET @dsql = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + SELECT ' + CAST(@DatabaseID AS NVARCHAR(10)) + N' AS database_id, + ps.object_id, + s.name, + ps.index_id, + ps.partition_number, + ps.row_count, + ps.reserved_page_count * 8. / 1024. AS reserved_MB, + ps.lob_reserved_page_count * 8. / 1024. AS reserved_LOB_MB, + ps.row_overflow_reserved_page_count * 8. / 1024. AS reserved_row_overflow_MB, + le.lock_escalation_desc, + ' + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N'par.data_compression_desc ' ELSE N'null as data_compression_desc' END + N', + SUM(os.leaf_insert_count), + SUM(os.leaf_delete_count), + SUM(os.leaf_update_count), + SUM(os.range_scan_count), + SUM(os.singleton_lookup_count), + SUM(os.forwarded_fetch_count), + SUM(os.lob_fetch_in_pages), + SUM(os.lob_fetch_in_bytes), + SUM(os.row_overflow_fetch_in_pages), + SUM(os.row_overflow_fetch_in_bytes), + SUM(os.row_lock_count), + SUM(os.row_lock_wait_count), + SUM(os.row_lock_wait_in_ms), + SUM(os.page_lock_count), + SUM(os.page_lock_wait_count), + SUM(os.page_lock_wait_in_ms), + SUM(os.index_lock_promotion_attempt_count), + SUM(os.index_lock_promotion_count), + SUM(os.page_latch_wait_count), + SUM(os.page_latch_wait_in_ms), + SUM(os.page_io_latch_wait_count), + SUM(os.page_io_latch_wait_in_ms)'; -SET NOCOUNT ON; -SET STATISTICS XML OFF; -SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + /* Get columnstore dictionary size - more info: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2585 */ + IF EXISTS (SELECT * FROM sys.all_objects WHERE name = 'column_store_dictionaries') + SET @dsql = @dsql + N' COALESCE((SELECT SUM (on_disk_size / 1024.0 / 1024) FROM ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_dictionaries dict WHERE dict.partition_id = ps.partition_id),0) AS reserved_dictionary_MB '; + ELSE + SET @dsql = @dsql + N' 0 AS reserved_dictionary_MB '; -SELECT @Version = '8.19', @VersionDate = '20240222'; -IF(@VersionCheckMode = 1) -BEGIN - RETURN; -END; + SET @dsql = @dsql + N' + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_partition_stats AS ps + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partitions AS par on ps.partition_id=par.partition_id + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects AS so ON ps.object_id = so.object_id + AND so.is_ms_shipped = 0 /*Exclude objects shipped by Microsoft*/ + AND so.type <> ''TF'' /*Exclude table valued functions*/ + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s ON s.schema_id = so.schema_id + OUTER APPLY ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_index_operational_stats(' + + CAST(@DatabaseID AS NVARCHAR(10)) + N', ps.object_id, ps.index_id,ps.partition_number) AS os + OUTER APPLY (SELECT st.lock_escalation_desc + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.tables st + WHERE st.object_id = ps.object_id + AND ps.index_id < 2 ) le + WHERE 1=1 + ' + CASE WHEN @ObjectID IS NOT NULL THEN N'AND so.object_id=' + CAST(@ObjectID AS NVARCHAR(30)) + N' ' ELSE N' ' END + N' + ' + CASE WHEN @Filter = 2 THEN N'AND ps.reserved_page_count * 8./1024. > ' + CAST(@FilterMB AS NVARCHAR(5)) + N' ' ELSE N' ' END + ' + GROUP BY ps.object_id, + s.name, + ps.index_id, + ps.partition_number, + ps.partition_id, + ps.row_count, + ps.reserved_page_count, + ps.lob_reserved_page_count, + ps.row_overflow_reserved_page_count, + le.lock_escalation_desc, + ' + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N'par.data_compression_desc ' ELSE N'null as data_compression_desc ' END + N' + ORDER BY ps.object_id, ps.index_id, ps.partition_number + OPTION ( RECOMPILE ); + '; + END; -DECLARE /*Variables for the variable Gods*/ - @msg NVARCHAR(MAX) = N'', --Used to format RAISERROR messages in some places - @sql_select NVARCHAR(MAX) = N'', --Used to hold SELECT statements for dynamic SQL - @sql_where NVARCHAR(MAX) = N'', -- Used to hold WHERE clause for dynamic SQL - @duration_filter_ms DECIMAL(38,4) = (@DurationFilter * 1000.), --We accept Duration in seconds, but we filter in milliseconds (this is grandfathered from sp_BlitzCache) - @execution_threshold INT = 1000, --Threshold at which we consider a query to be frequently executed - @ctp_threshold_pct TINYINT = 10, --Percentage of CTFP at which we consider a query to be near parallel - @long_running_query_warning_seconds BIGINT = 300 * 1000 ,--Number of seconds (converted to milliseconds) at which a query is considered long running - @memory_grant_warning_percent INT = 10,--Percent of memory grant used compared to what's granted; used to trigger unused memory grant warning - @ctp INT,--Holds the CTFP value for the server - @min_memory_per_query INT,--Holds the server configuration value for min memory per query - @cr NVARCHAR(1) = NCHAR(13),--Special character - @lf NVARCHAR(1) = NCHAR(10),--Special character - @tab NVARCHAR(1) = NCHAR(9),--Special character - @error_severity INT,--Holds error info for try/catch blocks - @error_state INT,--Holds error info for try/catch blocks - @sp_params NVARCHAR(MAX) = N'@sp_Top INT, @sp_StartDate DATETIME2, @sp_EndDate DATETIME2, @sp_MinimumExecutionCount INT, @sp_MinDuration INT, @sp_StoredProcName NVARCHAR(128), @sp_PlanIdFilter INT, @sp_QueryIdFilter INT',--Holds parameters used in dynamic SQL - @is_azure_db BIT = 0, --Are we using Azure? I'm not. You might be. That's cool. - @compatibility_level TINYINT = 0, --Some functionality (T-SQL) isn't available in lower compat levels. We can use this to weed out those issues as we go. - @log_size_mb DECIMAL(38,2) = 0, - @avg_tempdb_data_file DECIMAL(38,2) = 0; - -/*Grabs CTFP setting*/ -SELECT @ctp = NULLIF(CAST(value AS INT), 0) -FROM sys.configurations -WHERE name = N'cost threshold for parallelism' -OPTION (RECOMPILE); + IF @dsql IS NULL + RAISERROR('@dsql is null',16,1); -/*Grabs min query memory setting*/ -SELECT @min_memory_per_query = CONVERT(INT, c.value) -FROM sys.configurations AS c -WHERE c.name = N'min memory per query (KB)' -OPTION (RECOMPILE); + RAISERROR (N'Inserting data into #IndexPartitionSanity',0,1) WITH NOWAIT; + IF @Debug = 1 + BEGIN + PRINT SUBSTRING(@dsql, 0, 4000); + PRINT SUBSTRING(@dsql, 4000, 8000); + PRINT SUBSTRING(@dsql, 8000, 12000); + PRINT SUBSTRING(@dsql, 12000, 16000); + PRINT SUBSTRING(@dsql, 16000, 20000); + PRINT SUBSTRING(@dsql, 20000, 24000); + PRINT SUBSTRING(@dsql, 24000, 28000); + PRINT SUBSTRING(@dsql, 28000, 32000); + PRINT SUBSTRING(@dsql, 32000, 36000); + PRINT SUBSTRING(@dsql, 36000, 40000); + END; + EXEC sp_executesql @dsql; + INSERT #IndexPartitionSanity ( [database_id], + [object_id], + [schema_name], + index_id, + partition_number, + row_count, + reserved_MB, + reserved_LOB_MB, + reserved_row_overflow_MB, + lock_escalation_desc, + data_compression_desc, + leaf_insert_count, + leaf_delete_count, + leaf_update_count, + range_scan_count, + singleton_lookup_count, + forwarded_fetch_count, + lob_fetch_in_pages, + lob_fetch_in_bytes, + row_overflow_fetch_in_pages, + row_overflow_fetch_in_bytes, + row_lock_count, + row_lock_wait_count, + row_lock_wait_in_ms, + page_lock_count, + page_lock_wait_count, + page_lock_wait_in_ms, + index_lock_promotion_attempt_count, + index_lock_promotion_count, + page_latch_wait_count, + page_latch_wait_in_ms, + page_io_latch_wait_count, + page_io_latch_wait_in_ms, + reserved_dictionary_MB) + select h.database_id, h.object_id, h.sname, h.index_id, h.partition_number, h.row_count, h.reserved_MB, h.reserved_LOB_MB, h.reserved_row_overflow_MB, h.lock_escalation_desc, h.data_compression_desc, + SUM(os.leaf_insert_count), + SUM(os.leaf_delete_count), + SUM(os.leaf_update_count), + SUM(os.range_scan_count), + SUM(os.singleton_lookup_count), + SUM(os.forwarded_fetch_count), + SUM(os.lob_fetch_in_pages), + SUM(os.lob_fetch_in_bytes), + SUM(os.row_overflow_fetch_in_pages), + SUM(os.row_overflow_fetch_in_bytes), + SUM(os.row_lock_count), + SUM(os.row_lock_wait_count), + SUM(os.row_lock_wait_in_ms), + SUM(os.page_lock_count), + SUM(os.page_lock_wait_count), + SUM(os.page_lock_wait_in_ms), + SUM(os.index_lock_promotion_attempt_count), + SUM(os.index_lock_promotion_count), + SUM(os.page_latch_wait_count), + SUM(os.page_latch_wait_in_ms), + SUM(os.page_io_latch_wait_count), + SUM(os.page_io_latch_wait_in_ms) + ,COALESCE((SELECT SUM (dict.on_disk_size / 1024.0 / 1024) FROM sys.column_store_dictionaries dict WHERE dict.partition_id = h.partition_id),0) AS reserved_dictionary_MB + from #dm_db_partition_stats_etc h + left JOIN #dm_db_index_operational_stats as os ON + h.object_id=os.object_id and h.index_id=os.index_id and h.partition_number=os.partition_number + group by h.database_id, h.object_id, h.sname, h.index_id, h.partition_number, h.partition_id, h.row_count, h.reserved_MB, h.reserved_LOB_MB, h.reserved_row_overflow_MB, h.lock_escalation_desc, h.data_compression_desc + + END; --End Check For @SkipPartitions = 0 -/*Check if this is Azure first*/ -IF (SELECT CONVERT(NVARCHAR(128), SERVERPROPERTY ('EDITION'))) <> 'SQL Azure' - BEGIN - /*Grabs log size for datbase*/ - SELECT @log_size_mb = AVG(((mf.size * 8) / 1024.)) - FROM sys.master_files AS mf - WHERE mf.database_id = DB_ID(@DatabaseName) - AND mf.type_desc = 'LOG'; - - /*Grab avg tempdb file size*/ - SELECT @avg_tempdb_data_file = AVG(((mf.size * 8) / 1024.)) - FROM sys.master_files AS mf - WHERE mf.database_id = DB_ID('tempdb') - AND mf.type_desc = 'ROWS'; - END; -/*Help section*/ + IF @Mode NOT IN(1, 2) + BEGIN + RAISERROR (N'Inserting data into #MissingIndexes',0,1) WITH NOWAIT; + SET @dsql=N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' -IF @Help = 1 - BEGIN - PRINT N' - sp_BlitzQueryStore from http://FirstResponderKit.org - - This script displays your most resource-intensive queries from the Query Store, - and points to ways you can tune these queries to make them faster. - - - To learn more, visit http://FirstResponderKit.org where you can download new - versions for free, watch training videos on how it works, get more info on - the findings, contribute your own code, and more. - - Known limitations of this version: - - This query will not run on SQL Server versions less than 2016. - - This query will not run on Azure Databases with compatibility less than 130. - - This query will not run on Azure Data Warehouse. + SET @dsql = @dsql + ' +WITH + ColumnNamesWithDataTypes AS +( + SELECT + id.index_handle, + id.object_id, + cn.IndexColumnType, + STUFF + ( + ( + SELECT + '', '' + + cn_inner.ColumnName + + '' '' + + N'' {'' + + CASE + WHEN ty.name IN (''varchar'', ''char'') + THEN ty.name + + ''('' + + CASE + WHEN co.max_length = -1 + THEN ''max'' + ELSE CAST(co.max_length AS VARCHAR(25)) + END + + '')'' + WHEN ty.name IN (''nvarchar'', ''nchar'') + THEN ty.name + + ''('' + + CASE + WHEN co.max_length = -1 + THEN ''max'' + ELSE CAST(co.max_length / 2 AS VARCHAR(25)) + END + + '')'' + WHEN ty.name IN (''decimal'', ''numeric'') + THEN ty.name + + ''('' + + CAST(co.precision AS VARCHAR(25)) + + '', '' + + CAST(co.scale AS VARCHAR(25)) + + '')'' + WHEN ty.name IN (''datetime2'') + THEN ty.name + + ''('' + + CAST(co.scale AS VARCHAR(25)) + + '')'' + ELSE ty.name END + ''}'' + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_details AS id_inner + CROSS APPLY + ( + SELECT + LTRIM(RTRIM(v.value(''(./text())[1]'', ''varchar(max)''))) AS ColumnName, + ''Equality'' AS IndexColumnType + FROM + ( + VALUES + (CONVERT(XML, N'''' + REPLACE((SELECT CAST(id_inner.equality_columns AS nvarchar(max)) FOR XML PATH('''')), N'','', N'''') + N'''')) + ) x (n) + CROSS APPLY n.nodes(''x'') node(v) + UNION ALL + SELECT + LTRIM(RTRIM(v.value(N''(./text())[1]'', ''varchar(max)''))) AS ColumnName, + ''Inequality'' AS IndexColumnType + FROM + ( + VALUES + (CONVERT(XML, N'''' + REPLACE((SELECT CAST(id_inner.inequality_columns AS nvarchar(max)) FOR XML PATH('''')), N'','', N'''') + N'''')) + ) x (n) + CROSS APPLY n.nodes(''x'') node(v) + UNION ALL + SELECT + LTRIM(RTRIM(v.value(''(./text())[1]'', ''varchar(max)''))) AS ColumnName, + ''Included'' AS IndexColumnType + FROM + ( + VALUES (CONVERT(XML, N'''' + REPLACE((SELECT CAST(id_inner.included_columns AS nvarchar(max)) FOR XML PATH('''')), N'','', N'''') + N'''')) + ) x (n) + CROSS APPLY n.nodes(''x'') node(v) + ) AS cn_inner + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns AS co + ON co.object_id = id_inner.object_id + AND ''['' + co.name + '']'' = cn_inner.ColumnName + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.types AS ty + ON ty.user_type_id = co.user_type_id + WHERE id_inner.index_handle = id.index_handle + AND id_inner.object_id = id.object_id + AND cn_inner.IndexColumnType = cn.IndexColumnType + FOR XML PATH('''') + ), + 1, + 1, + '''' + ) AS ReplaceColumnNames + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_details AS id + CROSS APPLY + ( + SELECT + LTRIM(RTRIM(v.value(''(./text())[1]'', ''varchar(max)''))) AS ColumnName, + ''Equality'' AS IndexColumnType + FROM + ( + VALUES (CONVERT(XML, N'''' + REPLACE((SELECT CAST(id.equality_columns AS nvarchar(max)) FOR XML PATH('''')), N'','', N'''') + N'''')) + ) x (n) + CROSS APPLY n.nodes(''x'') node(v) + UNION ALL + SELECT + LTRIM(RTRIM(v.value(''(./text())[1]'', ''varchar(max)''))) AS ColumnName, + ''Inequality'' AS IndexColumnType + FROM + ( + VALUES (CONVERT(XML, N'''' + REPLACE((SELECT CAST(id.inequality_columns AS nvarchar(max)) FOR XML PATH('''')), N'','', N'''') + N'''')) + ) x (n) + CROSS APPLY n.nodes(''x'') node(v) + UNION ALL + SELECT + LTRIM(RTRIM(v.value(''(./text())[1]'', ''varchar(max)''))) AS ColumnName, + ''Included'' AS IndexColumnType + FROM + ( + VALUES (CONVERT(XML, N'''' + REPLACE((SELECT CAST(id.included_columns AS nvarchar(max)) FOR XML PATH('''')), N'','', N'''') + N'''')) + ) x (n) + CROSS APPLY n.nodes(''x'') node(v) + )AS cn + GROUP BY + id.index_handle, + id.object_id, + cn.IndexColumnType +) +SELECT + * +INTO #ColumnNamesWithDataTypes +FROM ColumnNamesWithDataTypes +OPTION(RECOMPILE); - Unknown limitations of this version: - - Could be tickling - - - MIT License - - Copyright (c) Brent Ozar Unlimited - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE. - '; - /*Parameter info*/ - SELECT N'@Help' AS [Parameter Name] , - N'BIT' AS [Data Type] , - N'0' AS [Default Value], - N'Displays this help message.' AS [Parameter Description] - UNION ALL - SELECT N'@DatabaseName', - N'NVARCHAR(128)', - N'NULL', - N'The name of the database you want to check the query store for.' - UNION ALL - SELECT N'@Top', - N'INT', - N'3', - N'The number of records to retrieve and analyze from the query store. The following system views are used: query_store_query, query_context_settings, query_store_wait_stats, query_store_runtime_stats,query_store_plan.' - - UNION ALL - SELECT N'@StartDate', - N'DATETIME2(7)', - N'NULL', - N'Get query store info starting from this date. When not specified, sp_BlitzQueryStore gets info from the last 7 days' - UNION ALL - SELECT N'@EndDate', - N'DATETIME2(7)', - N'NULL', - N'Get query store info until this date. When not specified, sp_BlitzQueryStore gets info from the last 7 days' - UNION ALL - SELECT N'@MinimumExecutionCount', - N'INT', - N'NULL', - N'When a value is specified, sp_BlitzQueryStore gets info for queries where count_executions >= @MinimumExecutionCount' - UNION ALL - SELECT N'@DurationFilter', - N'DECIMAL(38,4)', - N'NULL', - N'Time unit - seconds. When a value is specified, sp_BlitzQueryStore gets info for queries where the average duration >= @DurationFilter' - UNION ALL - SELECT N'@StoredProcName', - N'NVARCHAR(128)', - N'NULL', - N'Get information for this specific stored procedure.' - UNION ALL - SELECT N'@Failed', - N'BIT', - N'0', - N'When set to 1, only information about failed queries is returned.' - UNION ALL - SELECT N'@PlanIdFilter', - N'INT', - N'NULL', - N'The ID of the plan you want to check for.' - UNION ALL - SELECT N'@QueryIdFilter', - N'INT', - N'NULL', - N'The ID of the query you want to check for.' - UNION ALL - SELECT N'@ExportToExcel', - N'BIT', - N'0', - N'When set to 1, prepares output for exporting to Excel. Newlines and additional whitespace are removed from query text and the execution plan is not displayed.' - UNION ALL - SELECT N'@HideSummary', - N'BIT', - N'0', - N'When set to 1, hides the findings summary result set.' - UNION ALL - SELECT N'@SkipXML', - N'BIT', - N'0', - N'When set to 1, missing_indexes, implicit_conversion_info, cached_execution_parameters, are not returned. Does not affect query_plan_xml' - UNION ALL - SELECT N'@Debug', - N'BIT', - N'0', - N'Setting this to 1 will print dynamic SQL and select data from all tables used.' - UNION ALL - SELECT N'@ExpertMode', - N'BIT', - N'0', - N'When set to 1, more checks are done. Examples: many to many merge joins, row goals, adaptive joins, stats info, bad scans and plan forcing, computed columns that reference scalar UDFs.' - UNION ALL - SELECT N'@VersionCheckMode', - N'BIT', - N'0', - N'Outputs the version number and date.' +SELECT + id.database_id, + id.object_id, + @i_DatabaseName, + sc.[name], + so.[name], + id.statement, + gs.avg_total_user_cost, + gs.avg_user_impact, + gs.user_seeks, + gs.user_scans, + gs.unique_compiles, + id.equality_columns, + id.inequality_columns, + id.included_columns, + ( + SELECT + ColumnNamesWithDataTypes.ReplaceColumnNames + FROM #ColumnNamesWithDataTypes ColumnNamesWithDataTypes + WHERE ColumnNamesWithDataTypes.index_handle = id.index_handle + AND ColumnNamesWithDataTypes.object_id = id.object_id + AND ColumnNamesWithDataTypes.IndexColumnType = ''Equality'' + ) AS equality_columns_with_data_type, + ( + SELECT + ColumnNamesWithDataTypes.ReplaceColumnNames + FROM #ColumnNamesWithDataTypes ColumnNamesWithDataTypes + WHERE ColumnNamesWithDataTypes.index_handle = id.index_handle + AND ColumnNamesWithDataTypes.object_id = id.object_id + AND ColumnNamesWithDataTypes.IndexColumnType = ''Inequality'' + ) AS inequality_columns_with_data_type, + ( + SELECT ColumnNamesWithDataTypes.ReplaceColumnNames + FROM #ColumnNamesWithDataTypes ColumnNamesWithDataTypes + WHERE ColumnNamesWithDataTypes.index_handle = id.index_handle + AND ColumnNamesWithDataTypes.object_id = id.object_id + AND ColumnNamesWithDataTypes.IndexColumnType = ''Included'' + ) AS included_columns_with_data_type,'; - /* Column definitions */ - SELECT 'database_name' AS [Column Name], - 'NVARCHAR(258)' AS [Data Type], - 'The name of the database where the plan was encountered.' AS [Column Description] - UNION ALL - SELECT 'query_cost', - 'FLOAT', - 'The cost of the execution plan in query bucks.' - UNION ALL - SELECT 'plan_id', - 'BIGINT', - 'The ID of the plan from sys.query_store_plan.' - UNION ALL - SELECT 'query_id', - 'BIGINT', - 'The ID of the query from sys.query_store_query.' - UNION ALL - SELECT 'query_id_all_plan_ids', - 'VARCHAR(8000)', - 'Comma-separated list of all query plan IDs associated with this query.' - UNION ALL - SELECT 'query_sql_text', - 'NVARCHAR(MAX)', - 'The text of the query, as provided by the user/app. Includes whitespaces, hints and comments. Comments and spaces before and after the query text are ignored.' - UNION ALL - SELECT 'proc_or_function_name', - 'NVARCHAR(258)', - 'If the query is part of a function/stored procedure, you''ll see here the name of its parent object.' - UNION ALL - SELECT 'query_plan_xml', - ' XML', - 'The query plan. Click to display a graphical plan.' - UNION ALL - SELECT 'warnings', - 'VARCHAR(MAX)', - 'A list of individual warnings generated by this query.' - UNION ALL - SELECT 'pattern', - 'NVARCHAR(512)', - 'A list of performance related patterns identified for this query.' - UNION ALL - SELECT 'parameter_sniffing_symptoms', - 'NVARCHAR(4000)', - 'A list of all the identified symptoms that are usually indicators of parameter sniffing.' - UNION ALL - SELECT 'last_force_failure_reason_desc', - 'NVARCHAR(258)', - 'Reason why plan forcing failed. NONE if plan isn''t forced.' - UNION ALL - SELECT 'top_three_waits', - 'NVARCHAR(MAX)', - 'The top 3 wait types, and their times in milliseconds, recorded for this query.' - UNION ALL - SELECT 'missing_indexes', - 'XML', - 'Missing index recommendations retrieved from the query plan.' - UNION ALL - SELECT 'implicit_conversion_info', - 'XML', - 'Information about the implicit conversion warnings,if any, retrieved from the query plan.' - UNION ALL - SELECT 'cached_execution_parameters', - 'XML', - 'Names, data types, and values for the parameters used when the query plan was compiled.' - UNION ALL - SELECT 'count_executions ', - 'BIGINT', - 'The number of executions of this particular query.' - UNION ALL - SELECT 'count_compiles', - 'BIGINT', - 'The number of plan compilations for this particular query.' - UNION ALL - SELECT 'total_cpu_time', - 'BIGINT', - 'Total CPU time, reported in milliseconds, that was consumed by all executions of this query.' - UNION ALL - SELECT 'avg_cpu_time ', - 'BIGINT', - 'Average CPU time, reported in milliseconds, consumed by each execution of this query.' - UNION ALL - SELECT 'total_duration', - 'BIGINT', - 'Total elapsed time, reported in milliseconds, consumed by all executions of this query.' - UNION ALL - SELECT 'avg_duration', - 'BIGINT', - 'Average elapsed time, reported in milliseconds, consumed by each execution of this query.' - UNION ALL - SELECT 'total_logical_io_reads', - 'BIGINT', - 'Total logical reads, reported in MB, performed by this query.' - UNION ALL - SELECT 'avg_logical_io_reads', - 'BIGINT', - 'Average logical reads, reported in MB, performed by each execution of this query.' - UNION ALL - SELECT 'total_physical_io_reads', - 'BIGINT', - 'Total physical reads, reported in MB, performed by this query.' - UNION ALL - SELECT 'avg_physical_io_reads', - 'BIGINT', - 'Average physical reads, reported in MB, performed by each execution of this query.' - UNION ALL - SELECT 'total_logical_io_writes', - 'BIGINT', - 'Total logical writes, reported in MB, performed by this query.' - UNION ALL - SELECT 'avg_logical_io_writes', - 'BIGINT', - 'Average logical writes, reported in MB, performed by each execution of this query.' - UNION ALL - SELECT 'total_rowcount', - 'BIGINT', - 'Total number of rows returned for all executions of this query.' - UNION ALL - SELECT 'avg_rowcount', - 'BIGINT', - 'Average number of rows returned by each execution of this query.' - UNION ALL - SELECT 'total_query_max_used_memory', - 'DECIMAL(38,2)', - 'Total max memory grant, reported in MB, used by this query.' - UNION ALL - SELECT 'avg_query_max_used_memory', - 'DECIMAL(38,2)', - 'Average max memory grant, reported in MB, used by each execution of this query.' - UNION ALL - SELECT 'total_tempdb_space_used', - 'DECIMAL(38,2)', - 'Total tempdb space, reported in MB, used by this query.' - UNION ALL - SELECT 'avg_tempdb_space_used', - 'DECIMAL(38,2)', - 'Average tempdb space, reported in MB, used by each execution of this query.' - UNION ALL - SELECT 'total_log_bytes_used', - 'DECIMAL(38,2)', - 'Total number of bytes in the database log used by this query.' - UNION ALL - SELECT 'avg_log_bytes_used', - 'DECIMAL(38,2)', - 'Average number of bytes in the database log used by each execution of this query.' - UNION ALL - SELECT 'total_num_physical_io_reads', - 'DECIMAL(38,2)', - 'Total number of physical I/O reads performed by this query (expressed as a number of read I/O operations).' - UNION ALL - SELECT 'avg_num_physical_io_reads', - 'DECIMAL(38,2)', - 'Average number of physical I/O reads performed by each execution of this query (expressed as a number of read I/O operations).' - UNION ALL - SELECT 'first_execution_time', - 'DATETIME2', - 'First execution time for this query within the aggregation interval. This is the end time of the query execution.' - UNION ALL - SELECT 'last_execution_time', - 'DATETIME2', - 'Last execution time for this query within the aggregation interval. This is the end time of the query execution.' - UNION ALL - SELECT 'context_settings', - 'NVARCHAR(512)', - 'Contains information about context settings associated with this query.'; - RETURN; + /* Get the sample query plan if it's available, and if there are less than 1,000 rows in the DMV: */ + IF NOT EXISTS + ( + SELECT + 1/0 + FROM sys.all_objects AS o + WHERE o.name = 'dm_db_missing_index_group_stats_query' + ) + SELECT + @dsql += N' + NULL AS sample_query_plan' + ELSE + BEGIN + /* The DMV is only supposed to have 600 rows in it. If it's got more, + they could see performance slowdowns - see Github #3085. */ + DECLARE @MissingIndexPlans BIGINT; + SET @StringToExecute = N'SELECT @MissingIndexPlans = COUNT(*) FROM ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_group_stats_query;' + EXEC sp_executesql @StringToExecute, N'@MissingIndexPlans BIGINT OUT', @MissingIndexPlans OUT; -END; + IF @MissingIndexPlans > 1000 + BEGIN + SELECT @dsql += N' + NULL AS sample_query_plan /* Over 1000 plans found, skipping */'; + RAISERROR (N'Over 1000 plans found in sys.dm_db_missing_index_group_stats_query - your SQL Server is hitting a bug: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/3085',0,1) WITH NOWAIT; + END + ELSE + SELECT + @dsql += N' + sample_query_plan = + ( + SELECT TOP (1) + p.query_plan + FROM sys.dm_db_missing_index_group_stats gs + CROSS APPLY + ( + SELECT TOP (1) + s.plan_handle + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_group_stats_query q + INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.dm_exec_query_stats s + ON q.query_plan_hash = s.query_plan_hash + WHERE gs.group_handle = q.group_handle + ORDER BY + (q.user_seeks + q.user_scans) DESC, + s.total_logical_reads DESC + ) q2 + CROSS APPLY sys.dm_exec_query_plan(q2.plan_handle) p + WHERE ig.index_group_handle = gs.group_handle + )' + END + + -/*Making sure your version is copasetic*/ -IF ( (SELECT CONVERT(NVARCHAR(128), SERVERPROPERTY ('EDITION'))) = 'SQL Azure' ) - BEGIN - SET @is_azure_db = 1; + SET @dsql = @dsql + N' +FROM ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_groups ig +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_details id + ON ig.index_handle = id.index_handle +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_group_stats gs + ON ig.index_group_handle = gs.group_handle +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects so + ON id.object_id=so.object_id +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas sc + ON so.schema_id=sc.schema_id +WHERE id.database_id = ' + CAST(@DatabaseID AS NVARCHAR(30)) + +CASE + WHEN @ObjectID IS NULL + THEN N'' + ELSE N' +AND id.object_id = ' + CAST(@ObjectID AS NVARCHAR(30)) +END + +N' +OPTION (RECOMPILE);'; - IF ( (SELECT SERVERPROPERTY ('ENGINEEDITION')) NOT IN (5,8) - OR (SELECT [compatibility_level] FROM sys.databases WHERE [name] = DB_NAME()) < 130 - ) - BEGIN - SELECT @msg = N'Sorry, sp_BlitzQueryStore doesn''t work on Azure Data Warehouse, or Azure Databases with DB compatibility < 130.' + REPLICATE(CHAR(13), 7933); - PRINT @msg; - RETURN; + IF @dsql IS NULL + RAISERROR('@dsql is null',16,1); + IF @Debug = 1 + BEGIN + PRINT SUBSTRING(@dsql, 0, 4000); + PRINT SUBSTRING(@dsql, 4000, 8000); + PRINT SUBSTRING(@dsql, 8000, 12000); + PRINT SUBSTRING(@dsql, 12000, 16000); + PRINT SUBSTRING(@dsql, 16000, 20000); + PRINT SUBSTRING(@dsql, 20000, 24000); + PRINT SUBSTRING(@dsql, 24000, 28000); + PRINT SUBSTRING(@dsql, 28000, 32000); + PRINT SUBSTRING(@dsql, 32000, 36000); + PRINT SUBSTRING(@dsql, 36000, 40000); + END; + INSERT #MissingIndexes ( [database_id], [object_id], [database_name], [schema_name], [table_name], [statement], avg_total_user_cost, + avg_user_impact, user_seeks, user_scans, unique_compiles, equality_columns, + inequality_columns, included_columns, equality_columns_with_data_type, inequality_columns_with_data_type, + included_columns_with_data_type, sample_query_plan) + EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; END; - END; -ELSE IF ( (SELECT PARSENAME(CONVERT(NVARCHAR(128), SERVERPROPERTY ('PRODUCTVERSION')), 4) ) < 13 ) - BEGIN - SELECT @msg = N'Sorry, sp_BlitzQueryStore doesn''t work on versions of SQL prior to 2016.' + REPLICATE(CHAR(13), 7933); - PRINT @msg; - RETURN; - END; -/*Making sure at least one database uses QS*/ -IF ( SELECT COUNT(*) - FROM sys.databases AS d - WHERE d.is_query_store_on = 1 - AND d.user_access_desc='MULTI_USER' - AND d.state_desc = 'ONLINE' - AND d.name NOT IN ('master', 'model', 'msdb', 'tempdb', '32767') - AND d.is_distributor = 0 ) = 0 - BEGIN - SELECT @msg = N'You don''t currently have any databases with Query Store enabled.' + REPLICATE(CHAR(13), 7933); - PRINT @msg; - RETURN; - END; - -/*Making sure your databases are using QDS.*/ -RAISERROR('Checking database validity', 0, 1) WITH NOWAIT; + SET @dsql = N' + SELECT DB_ID(N' + QUOTENAME(@DatabaseName,'''') + N') AS [database_id], + @i_DatabaseName AS database_name, + s.name, + fk_object.name AS foreign_key_name, + parent_object.[object_id] AS parent_object_id, + parent_object.name AS parent_object_name, + referenced_object.[object_id] AS referenced_object_id, + referenced_object.name AS referenced_object_name, + fk.is_disabled, + fk.is_not_trusted, + fk.is_not_for_replication, + parent.fk_columns, + referenced.fk_columns, + [update_referential_action_desc], + [delete_referential_action_desc] + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.foreign_keys fk + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects fk_object ON fk.object_id=fk_object.object_id + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects parent_object ON fk.parent_object_id=parent_object.object_id + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects referenced_object ON fk.referenced_object_id=referenced_object.object_id + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s ON fk.schema_id=s.schema_id + CROSS APPLY ( SELECT STUFF( (SELECT N'', '' + c_parent.name AS fk_columns + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.foreign_key_columns fkc + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns c_parent ON fkc.parent_object_id=c_parent.[object_id] + AND fkc.parent_column_id=c_parent.column_id + WHERE fk.parent_object_id=fkc.parent_object_id + AND fk.[object_id]=fkc.constraint_object_id + ORDER BY fkc.constraint_column_id + FOR XML PATH('''') , + TYPE).value(''.'', ''nvarchar(max)''), 1, 1, '''')/*This is how we remove the first comma*/ ) parent ( fk_columns ) + CROSS APPLY ( SELECT STUFF( (SELECT N'', '' + c_referenced.name AS fk_columns + FROM ' + QUOTENAME(@DatabaseName) + N'.sys. foreign_key_columns fkc + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns c_referenced ON fkc.referenced_object_id=c_referenced.[object_id] + AND fkc.referenced_column_id=c_referenced.column_id + WHERE fk.referenced_object_id=fkc.referenced_object_id + and fk.[object_id]=fkc.constraint_object_id + ORDER BY fkc.constraint_column_id /*order by col name, we don''t have anything better*/ + FOR XML PATH('''') , + TYPE).value(''.'', ''nvarchar(max)''), 1, 1, '''') ) referenced ( fk_columns ) + ' + CASE WHEN @ObjectID IS NOT NULL THEN + 'WHERE fk.parent_object_id=' + CAST(@ObjectID AS NVARCHAR(30)) + N' OR fk.referenced_object_id=' + CAST(@ObjectID AS NVARCHAR(30)) + N' ' + ELSE N' ' END + ' + ORDER BY parent_object_name, foreign_key_name + OPTION (RECOMPILE);'; + IF @dsql IS NULL + RAISERROR('@dsql is null',16,1); -IF (@is_azure_db = 1 AND SERVERPROPERTY ('ENGINEEDITION') <> 8) - SET @DatabaseName = DB_NAME(); -ELSE -BEGIN + RAISERROR (N'Inserting data into #ForeignKeys',0,1) WITH NOWAIT; + IF @Debug = 1 + BEGIN + PRINT SUBSTRING(@dsql, 0, 4000); + PRINT SUBSTRING(@dsql, 4000, 8000); + PRINT SUBSTRING(@dsql, 8000, 12000); + PRINT SUBSTRING(@dsql, 12000, 16000); + PRINT SUBSTRING(@dsql, 16000, 20000); + PRINT SUBSTRING(@dsql, 20000, 24000); + PRINT SUBSTRING(@dsql, 24000, 28000); + PRINT SUBSTRING(@dsql, 28000, 32000); + PRINT SUBSTRING(@dsql, 32000, 36000); + PRINT SUBSTRING(@dsql, 36000, 40000); + END; + INSERT #ForeignKeys ( [database_id], [database_name], [schema_name], foreign_key_name, parent_object_id,parent_object_name, referenced_object_id, referenced_object_name, + is_disabled, is_not_trusted, is_not_for_replication, parent_fk_columns, referenced_fk_columns, + [update_referential_action_desc], [delete_referential_action_desc] ) + EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; + + IF @Mode NOT IN(1, 2) + BEGIN + SET @dsql = N' + SELECT + DB_ID(N' + QUOTENAME(@DatabaseName,'''') + N') AS [database_id], + @i_DatabaseName AS database_name, + foreign_key_schema = + s.name, + foreign_key_name = + fk.name, + foreign_key_table = + OBJECT_NAME(fk.parent_object_id, DB_ID(N' + QUOTENAME(@DatabaseName,'''') + N')), + fk.parent_object_id, + foreign_key_referenced_table = + OBJECT_NAME(fk.referenced_object_id, DB_ID(N' + QUOTENAME(@DatabaseName,'''') + N')), + fk.referenced_object_id + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.foreign_keys fk + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s + ON s.schema_id = fk.schema_id + WHERE fk.is_disabled = 0 + AND EXISTS + ( + SELECT + 1/0 + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.foreign_key_columns fkc + WHERE fkc.constraint_object_id = fk.object_id + AND NOT EXISTS + ( + SELECT + 1/0 + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.index_columns ic + WHERE ic.object_id = fkc.parent_object_id + AND ic.column_id = fkc.parent_column_id + AND ic.index_column_id = fkc.constraint_column_id + ) + ) + OPTION (RECOMPILE);' + IF @dsql IS NULL + RAISERROR('@dsql is null',16,1); - /*If we're on Azure SQL DB we don't need to check all this @DatabaseName stuff...*/ + RAISERROR (N'Inserting data into #UnindexedForeignKeys',0,1) WITH NOWAIT; + IF @Debug = 1 + BEGIN + PRINT SUBSTRING(@dsql, 0, 4000); + PRINT SUBSTRING(@dsql, 4000, 8000); + PRINT SUBSTRING(@dsql, 8000, 12000); + PRINT SUBSTRING(@dsql, 12000, 16000); + PRINT SUBSTRING(@dsql, 16000, 20000); + PRINT SUBSTRING(@dsql, 20000, 24000); + PRINT SUBSTRING(@dsql, 24000, 28000); + PRINT SUBSTRING(@dsql, 28000, 32000); + PRINT SUBSTRING(@dsql, 32000, 36000); + PRINT SUBSTRING(@dsql, 36000, 40000); + END; - SET @DatabaseName = LTRIM(RTRIM(@DatabaseName)); + INSERT + #UnindexedForeignKeys + ( + database_id, + database_name, + schema_name, + foreign_key_name, + parent_object_name, + parent_object_id, + referenced_object_name, + referenced_object_id + ) + EXEC sys.sp_executesql + @dsql, + N'@i_DatabaseName sysname', + @DatabaseName; + END; - /*Did you set @DatabaseName?*/ - RAISERROR('Making sure [%s] isn''t NULL', 0, 1, @DatabaseName) WITH NOWAIT; - IF (@DatabaseName IS NULL) - BEGIN - RAISERROR('@DatabaseName cannot be NULL', 0, 1) WITH NOWAIT; - RETURN; - END; - /*Does the database exist?*/ - RAISERROR('Making sure [%s] exists', 0, 1, @DatabaseName) WITH NOWAIT; - IF ((DB_ID(@DatabaseName)) IS NULL) - BEGIN - RAISERROR('The @DatabaseName you specified ([%s]) does not exist. Please check the name and try again.', 0, 1, @DatabaseName) WITH NOWAIT; - RETURN; - END; + IF @Mode NOT IN(1, 2) + BEGIN + IF @SkipStatistics = 0 /* AND DB_NAME() = @DatabaseName /* Can only get stats in the current database - see https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/1947 */ */ + BEGIN + IF ((PARSENAME(@SQLServerProductVersion, 4) >= 12) + OR (PARSENAME(@SQLServerProductVersion, 4) = 11 AND PARSENAME(@SQLServerProductVersion, 2) >= 3000) + OR (PARSENAME(@SQLServerProductVersion, 4) = 10 AND PARSENAME(@SQLServerProductVersion, 3) = 50 AND PARSENAME(@SQLServerProductVersion, 2) >= 2500)) + 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, + 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) + 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, + ISNULL(i.name, ''System Or User Statistic'') AS index_name, + ca.column_names AS column_names, + s.name AS statistics_name, + CONVERT(DATETIME, ddsp.last_updated) AS last_statistics_update, + DATEDIFF(DAY, ddsp.last_updated, GETDATE()) AS days_since_last_stats_update, + ddsp.rows, + ddsp.rows_sampled, + CAST(ddsp.rows_sampled / ( 1. * NULLIF(ddsp.rows, 0) ) * 100 AS DECIMAL(18, 1)) AS percent_sampled, + ddsp.steps AS histogram_steps, + ddsp.modification_counter, + CASE WHEN ddsp.modification_counter > 0 + THEN CAST(ddsp.modification_counter / ( 1. * NULLIF(ddsp.rows, 0) ) * 100 AS DECIMAL(18, 1)) + ELSE ddsp.modification_counter + END AS percent_modifications, + CASE WHEN ddsp.rows < 500 THEN 500 + ELSE CAST(( ddsp.rows * .20 ) + 500 AS INT) + 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 + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.stats AS s + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects obj + ON s.object_id = obj.object_id + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas sch + ON sch.schema_id = obj.schema_id + LEFT JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.indexes AS i + ON i.object_id = s.object_id + AND i.index_id = s.stats_id + OUTER APPLY ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_stats_properties(s.object_id, s.stats_id) AS ddsp + CROSS APPLY ( SELECT STUFF((SELECT '', '' + c.name + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.stats_columns AS sc + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns AS c + ON sc.column_id = c.column_id AND sc.object_id = c.object_id + WHERE sc.stats_id = s.stats_id AND sc.object_id = s.object_id + ORDER BY sc.stats_column_id + FOR XML PATH(''''), TYPE).value(''.'', ''nvarchar(max)''), 1, 2, '''') + ) ca (column_names) + WHERE obj.is_ms_shipped = 0 + OPTION (RECOMPILE);'; + + IF @dsql IS NULL + RAISERROR('@dsql is null',16,1); - /*Is it online?*/ - RAISERROR('Making sure [%s] is online', 0, 1, @DatabaseName) WITH NOWAIT; - IF (DATABASEPROPERTYEX(@DatabaseName, 'Collation')) IS NULL - BEGIN - RAISERROR('The @DatabaseName you specified ([%s]) is not readable. Please check the name and try again. Better yet, check your server.', 0, 1, @DatabaseName); - RETURN; - END; -END; + RAISERROR (N'Inserting data into #Statistics',0,1) WITH NOWAIT; + IF @Debug = 1 + BEGIN + PRINT SUBSTRING(@dsql, 0, 4000); + PRINT SUBSTRING(@dsql, 4000, 8000); + PRINT SUBSTRING(@dsql, 8000, 12000); + PRINT SUBSTRING(@dsql, 12000, 16000); + PRINT SUBSTRING(@dsql, 16000, 20000); + PRINT SUBSTRING(@dsql, 20000, 24000); + PRINT SUBSTRING(@dsql, 24000, 28000); + PRINT SUBSTRING(@dsql, 28000, 32000); + PRINT SUBSTRING(@dsql, 32000, 36000); + PRINT SUBSTRING(@dsql, 36000, 40000); + END; + + EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; + END; + ELSE + 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, + 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) + 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, + ISNULL(i.name, ''System Or User Statistic'') AS index_name, + ca.column_names AS column_names, + s.name AS statistics_name, + CONVERT(DATETIME, STATS_DATE(s.object_id, s.stats_id)) AS last_statistics_update, + DATEDIFF(DAY, STATS_DATE(s.object_id, s.stats_id), GETDATE()) AS days_since_last_stats_update, + si.rowcnt, + si.rowmodctr, + CASE WHEN si.rowmodctr > 0 THEN CAST(si.rowmodctr / ( 1. * NULLIF(si.rowcnt, 0) ) * 100 AS DECIMAL(18, 1)) + ELSE si.rowmodctr + END AS percent_modifications, + CASE WHEN si.rowcnt < 500 THEN 500 + ELSE CAST(( si.rowcnt * .20 ) + 500 AS INT) + 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, + ' + + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' + THEN N's.has_filter, + s.filter_definition' + ELSE N'NULL AS has_filter, + NULL AS filter_definition' END + + N' + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.stats AS s + INNER HASH JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.sysindexes si + ON si.name = s.name AND s.object_id = si.id + INNER HASH JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects obj + ON s.object_id = obj.object_id + INNER HASH JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas sch + ON sch.schema_id = obj.schema_id + LEFT HASH JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.indexes AS i + ON i.object_id = s.object_id + AND i.index_id = s.stats_id + CROSS APPLY ( SELECT STUFF((SELECT '', '' + c.name + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.stats_columns AS sc + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns AS c + ON sc.column_id = c.column_id AND sc.object_id = c.object_id + WHERE sc.stats_id = s.stats_id AND sc.object_id = s.object_id + ORDER BY sc.stats_column_id + FOR XML PATH(''''), TYPE).value(''.'', ''nvarchar(max)''), 1, 2, '''') + ) ca (column_names) + WHERE obj.is_ms_shipped = 0 + AND si.rowcnt > 0 + OPTION (RECOMPILE);'; -/*Does it have Query Store enabled?*/ -RAISERROR('Making sure [%s] has Query Store enabled', 0, 1, @DatabaseName) WITH NOWAIT; -IF - - ( SELECT [d].[name] - FROM [sys].[databases] AS d - WHERE [d].[is_query_store_on] = 1 - AND [d].[user_access_desc]='MULTI_USER' - AND [d].[state_desc] = 'ONLINE' - AND [d].[database_id] = (SELECT database_id FROM sys.databases WHERE name = @DatabaseName) - ) IS NULL -BEGIN - RAISERROR('The @DatabaseName you specified ([%s]) does not have the Query Store enabled. Please check the name or settings, and try again.', 0, 1, @DatabaseName) WITH NOWAIT; - RETURN; -END; + IF @dsql IS NULL + RAISERROR('@dsql is null',16,1); -/*Check database compat level*/ + RAISERROR (N'Inserting data into #Statistics',0,1) WITH NOWAIT; + IF @Debug = 1 + BEGIN + PRINT SUBSTRING(@dsql, 0, 4000); + PRINT SUBSTRING(@dsql, 4000, 8000); + PRINT SUBSTRING(@dsql, 8000, 12000); + PRINT SUBSTRING(@dsql, 12000, 16000); + PRINT SUBSTRING(@dsql, 16000, 20000); + PRINT SUBSTRING(@dsql, 20000, 24000); + PRINT SUBSTRING(@dsql, 24000, 28000); + PRINT SUBSTRING(@dsql, 28000, 32000); + PRINT SUBSTRING(@dsql, 32000, 36000); + PRINT SUBSTRING(@dsql, 36000, 40000); + END; + + EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; + END; + END; + END; -RAISERROR('Checking database compatibility level', 0, 1) WITH NOWAIT; + IF @Mode NOT IN(1, 2) + BEGIN + IF (PARSENAME(@SQLServerProductVersion, 4) >= 10) + BEGIN + RAISERROR (N'Gathering Computed Column Info.',0,1) WITH NOWAIT; + SET @dsql=N'SELECT DB_ID(@i_DatabaseName) AS [database_id], + @i_DatabaseName AS database_name, + t.name AS table_name, + s.name AS schema_name, + c.name AS column_name, + cc.is_nullable, + cc.definition, + cc.uses_database_collation, + cc.is_persisted, + cc.is_computed, + CASE WHEN cc.definition LIKE ''%|].|[%'' ESCAPE ''|'' THEN 1 ELSE 0 END AS is_function, + ''ALTER TABLE '' + QUOTENAME(s.name) + ''.'' + QUOTENAME(t.name) + + '' ADD '' + QUOTENAME(c.name) + '' AS '' + cc.definition + + CASE WHEN is_persisted = 1 THEN '' PERSISTED'' ELSE '''' END + '';'' COLLATE DATABASE_DEFAULT AS [column_definition] + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.computed_columns AS cc + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns AS c + ON cc.object_id = c.object_id + AND cc.column_id = c.column_id + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.tables AS t + ON t.object_id = cc.object_id + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s + ON s.schema_id = t.schema_id + OPTION (RECOMPILE);'; -SELECT @compatibility_level = d.compatibility_level -FROM sys.databases AS d -WHERE d.name = @DatabaseName; + IF @dsql IS NULL RAISERROR('@dsql is null',16,1); -RAISERROR('The @DatabaseName you specified ([%s])is running in compatibility level ([%d]).', 0, 1, @DatabaseName, @compatibility_level) WITH NOWAIT; + INSERT #ComputedColumns + ( database_id, [database_name], table_name, schema_name, column_name, is_nullable, definition, + uses_database_collation, is_persisted, is_computed, is_function, column_definition ) + EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; + END; + END; + IF @Mode NOT IN(1, 2) + BEGIN + RAISERROR (N'Gathering Trace Flag Information',0,1) WITH NOWAIT; + INSERT #TraceStatus + EXEC ('DBCC TRACESTATUS(-1) WITH NO_INFOMSGS'); -/*Making sure top is set to something if NULL*/ -IF ( @Top IS NULL ) - BEGIN - SET @Top = 3; - END; + IF (PARSENAME(@SQLServerProductVersion, 4) >= 13) + BEGIN + RAISERROR (N'Gathering Temporal Table Info',0,1) WITH NOWAIT; + SET @dsql=N'SELECT ' + QUOTENAME(@DatabaseName,'''') + N' AS database_name, + DB_ID(N' + QUOTENAME(@DatabaseName,'''') + N') AS [database_id], + s.name AS schema_name, + t.name AS table_name, + oa.hsn as history_schema_name, + oa.htn AS history_table_name, + c1.name AS start_column_name, + c2.name AS end_column_name, + p.name AS period_name + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.periods AS p + INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.tables AS t + ON p.object_id = t.object_id + INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns AS c1 + ON t.object_id = c1.object_id + AND p.start_column_id = c1.column_id + INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns AS c2 + ON t.object_id = c2.object_id + AND p.end_column_id = c2.column_id + INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s + ON t.schema_id = s.schema_id + CROSS APPLY ( SELECT s2.name as hsn, t2.name htn + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.tables AS t2 + INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s2 + ON t2.schema_id = s2.schema_id + WHERE t2.object_id = t.history_table_id + AND t2.temporal_type = 1 /*History table*/ ) AS oa + WHERE t.temporal_type IN ( 2, 4 ) /*BOL currently points to these types, but has no definition for 4*/ + OPTION (RECOMPILE); + '; + + IF @dsql IS NULL + RAISERROR('@dsql is null',16,1); + + INSERT #TemporalTables ( database_name, database_id, schema_name, table_name, history_schema_name, + history_table_name, start_column_name, end_column_name, period_name ) + + EXEC sp_executesql @dsql; + END; -/* -This section determines if you have the Query Store wait stats DMV -*/ + SET @dsql=N'SELECT DB_ID(@i_DatabaseName) AS [database_id], + @i_DatabaseName AS database_name, + t.name AS table_name, + s.name AS schema_name, + cc.name AS constraint_name, + cc.is_disabled, + cc.definition, + cc.uses_database_collation, + cc.is_not_trusted, + CASE WHEN cc.definition LIKE ''%|].|[%'' ESCAPE ''|'' THEN 1 ELSE 0 END AS is_function, + ''ALTER TABLE '' + QUOTENAME(s.name) + ''.'' + QUOTENAME(t.name) + + '' ADD CONSTRAINT '' + QUOTENAME(cc.name) + '' CHECK '' + cc.definition + '';'' COLLATE DATABASE_DEFAULT AS [column_definition] + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.check_constraints AS cc + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.tables AS t + ON t.object_id = cc.parent_object_id + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s + ON s.schema_id = t.schema_id + OPTION (RECOMPILE);'; + + INSERT #CheckConstraints + ( database_id, [database_name], table_name, schema_name, constraint_name, is_disabled, definition, + uses_database_collation, is_not_trusted, is_function, column_definition ) + EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; -RAISERROR('Checking for query_store_wait_stats', 0, 1) WITH NOWAIT; -DECLARE @ws_out INT, - @waitstats BIT, - @ws_sql NVARCHAR(MAX) = N'SELECT @i_out = COUNT(*) FROM ' + QUOTENAME(@DatabaseName) + N'.sys.all_objects WHERE name = ''query_store_wait_stats'' OPTION (RECOMPILE);', - @ws_params NVARCHAR(MAX) = N'@i_out INT OUTPUT'; + IF @Mode NOT IN(1, 2) + BEGIN + SET @dsql=N'SELECT DB_ID(@i_DatabaseName) AS [database_id], + @i_DatabaseName AS database_name, + s.name AS missing_schema_name, + t.name AS missing_table_name, + i.name AS missing_index_name, + c.name AS missing_column_name + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.sql_expression_dependencies AS sed + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.tables AS t + ON t.object_id = sed.referenced_id + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s + ON t.schema_id = s.schema_id + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.indexes AS i + ON i.object_id = sed.referenced_id + AND i.index_id = sed.referencing_minor_id + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns AS c + ON c.object_id = sed.referenced_id + AND c.column_id = sed.referenced_minor_id + WHERE sed.referencing_class = 7 + AND sed.referenced_class = 1 + AND i.has_filter = 1 + AND NOT EXISTS ( SELECT 1/0 + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.index_columns AS ic + WHERE ic.index_id = sed.referencing_minor_id + AND ic.column_id = sed.referenced_minor_id + AND ic.object_id = sed.referenced_id ) + OPTION(RECOMPILE);' -EXEC sys.sp_executesql @ws_sql, @ws_params, @i_out = @ws_out OUTPUT; + INSERT #FilteredIndexes ( database_id, database_name, schema_name, table_name, index_name, column_name ) + EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; + END; + END; + +END; +END TRY +BEGIN CATCH + RAISERROR (N'Failure populating temp tables.', 0,1) WITH NOWAIT; -SELECT @waitstats = CASE @ws_out WHEN 0 THEN 0 ELSE 1 END; + IF @dsql IS NOT NULL + BEGIN + SET @msg= 'Last @dsql: ' + @dsql; + RAISERROR(@msg, 0, 1) WITH NOWAIT; + END; -SET @msg = N'Wait stats DMV ' + CASE @waitstats - WHEN 0 THEN N' does not exist, skipping.' - WHEN 1 THEN N' exists, will analyze.' - END; -RAISERROR(@msg, 0, 1) WITH NOWAIT; + SELECT @msg = @DatabaseName + N' database failed to process. ' + ERROR_MESSAGE(), @ErrorSeverity = ERROR_SEVERITY(), @ErrorState = ERROR_STATE(); + RAISERROR (@msg,@ErrorSeverity, @ErrorState )WITH NOWAIT; + + + WHILE @@trancount > 0 + ROLLBACK; -/* -This section determines if you have some additional columns present in 2017, in case they get back ported. -*/ + RETURN; +END CATCH; + FETCH NEXT FROM c1 INTO @DatabaseName; +END; +DEALLOCATE c1; -RAISERROR('Checking for new columns in query_store_runtime_stats', 0, 1) WITH NOWAIT; - -DECLARE @nc_out INT, - @new_columns BIT, - @nc_sql NVARCHAR(MAX) = N'SELECT @i_out = COUNT(*) - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.all_columns AS ac - WHERE OBJECT_NAME(object_id) = ''query_store_runtime_stats'' - AND ac.name IN ( - ''avg_num_physical_io_reads'', - ''last_num_physical_io_reads'', - ''min_num_physical_io_reads'', - ''max_num_physical_io_reads'', - ''avg_log_bytes_used'', - ''last_log_bytes_used'', - ''min_log_bytes_used'', - ''max_log_bytes_used'', - ''avg_tempdb_space_used'', - ''last_tempdb_space_used'', - ''min_tempdb_space_used'', - ''max_tempdb_space_used'' - ) OPTION (RECOMPILE);', - @nc_params NVARCHAR(MAX) = N'@i_out INT OUTPUT'; - -EXEC sys.sp_executesql @nc_sql, @ws_params, @i_out = @nc_out OUTPUT; - -SELECT @new_columns = CASE @nc_out WHEN 12 THEN 1 ELSE 0 END; - -SET @msg = N'New query_store_runtime_stats columns ' + CASE @new_columns - WHEN 0 THEN N' do not exist, skipping.' - WHEN 1 THEN N' exist, will analyze.' - END; -RAISERROR(@msg, 0, 1) WITH NOWAIT; -/* -This section determines if Parameter Sensitive Plan Optimization is enabled on SQL Server 2022+. -*/ -RAISERROR('Checking for Parameter Sensitive Plan Optimization ', 0, 1) WITH NOWAIT; -DECLARE @pspo_out BIT, - @pspo_enabled BIT, - @pspo_sql NVARCHAR(MAX) = N'SELECT @i_out = CONVERT(bit,dsc.value) - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.database_scoped_configurations dsc - WHERE dsc.name = ''PARAMETER_SENSITIVE_PLAN_OPTIMIZATION'';', - @pspo_params NVARCHAR(MAX) = N'@i_out INT OUTPUT'; -EXEC sys.sp_executesql @pspo_sql, @pspo_params, @i_out = @pspo_out OUTPUT; -SET @pspo_enabled = CASE WHEN @pspo_out = 1 THEN 1 ELSE 0 END; +---------------------------------------- +--STEP 2: PREP THE TEMP TABLES +--EVERY QUERY AFTER THIS GOES AGAINST TEMP TABLES ONLY. +---------------------------------------- -SET @msg = N'Parameter Sensitive Plan Optimization ' + CASE @pspo_enabled - WHEN 0 THEN N' not enabled, skipping.' - WHEN 1 THEN N' enabled, will analyze.' - END; -RAISERROR(@msg, 0, 1) WITH NOWAIT; - -/* -These are the temp tables we use -*/ +RAISERROR (N'Updating #IndexSanity.key_column_names',0,1) WITH NOWAIT; +UPDATE #IndexSanity +SET key_column_names = D1.key_column_names +FROM #IndexSanity si + CROSS APPLY ( SELECT RTRIM(STUFF( (SELECT N', ' + c.column_name + + N' {' + system_type_name + + CASE max_length WHEN -1 THEN N' (max)' ELSE + CASE + WHEN system_type_name IN (N'char',N'varchar',N'binary',N'varbinary') THEN N' (' + CAST(max_length AS NVARCHAR(20)) + N')' + WHEN system_type_name IN (N'nchar',N'nvarchar') THEN N' (' + CAST(max_length/2 AS NVARCHAR(20)) + N')' + ELSE N' ' + CAST(max_length AS NVARCHAR(50)) + END + END + + N'}' + AS col_definition + FROM #IndexColumns c + WHERE c.database_id= si.database_id + AND c.schema_name = si.schema_name + AND c.object_id = si.object_id + AND c.index_id = si.index_id + AND c.is_included_column = 0 /*Just Keys*/ + AND c.key_ordinal > 0 /*Ignore non-key columns, such as partitioning keys*/ + ORDER BY c.object_id, c.index_id, c.key_ordinal + FOR XML PATH('') ,TYPE).value('.', 'nvarchar(max)'), 1, 1, '')) + ) D1 ( key_column_names ); +RAISERROR (N'Updating #IndexSanity.partition_key_column_name',0,1) WITH NOWAIT; +UPDATE #IndexSanity +SET partition_key_column_name = D1.partition_key_column_name +FROM #IndexSanity si + CROSS APPLY ( SELECT RTRIM(STUFF( (SELECT N', ' + c.column_name AS col_definition + FROM #IndexColumns c + WHERE c.database_id= si.database_id + AND c.schema_name = si.schema_name + AND c.object_id = si.object_id + AND c.index_id = si.index_id + AND c.partition_ordinal <> 0 /*Just Partitioned Keys*/ + ORDER BY c.object_id, c.index_id, c.key_ordinal + FOR XML PATH('') , TYPE).value('.', 'nvarchar(max)'), 1, 1,''))) D1 + ( partition_key_column_name ); -/* -This one holds the grouped data that helps use figure out which periods to examine -*/ +RAISERROR (N'Updating #IndexSanity.key_column_names_with_sort_order',0,1) WITH NOWAIT; +UPDATE #IndexSanity +SET key_column_names_with_sort_order = D2.key_column_names_with_sort_order +FROM #IndexSanity si + CROSS APPLY ( SELECT RTRIM(STUFF( (SELECT N', ' + c.column_name + CASE c.is_descending_key + WHEN 1 THEN N' DESC' + ELSE N'' + END + + N' {' + system_type_name + + CASE max_length WHEN -1 THEN N' (max)' ELSE + CASE + WHEN system_type_name IN (N'char',N'varchar',N'binary',N'varbinary') THEN N' (' + CAST(max_length AS NVARCHAR(20)) + N')' + WHEN system_type_name IN (N'nchar',N'nvarchar') THEN N' (' + CAST(max_length/2 AS NVARCHAR(20)) + N')' + ELSE N' ' + CAST(max_length AS NVARCHAR(50)) + END + END + + N'}' + AS col_definition + FROM #IndexColumns c + WHERE c.database_id= si.database_id + AND c.schema_name = si.schema_name + AND c.object_id = si.object_id + AND c.index_id = si.index_id + AND c.is_included_column = 0 /*Just Keys*/ + AND c.key_ordinal > 0 /*Ignore non-key columns, such as partitioning keys*/ + ORDER BY c.object_id, c.index_id, c.key_ordinal + FOR XML PATH('') , TYPE).value('.', 'nvarchar(max)'), 1, 1, '')) + ) D2 ( key_column_names_with_sort_order ); -RAISERROR(N'Creating temp tables', 0, 1) WITH NOWAIT; +RAISERROR (N'Updating #IndexSanity.key_column_names_with_sort_order_no_types (for create tsql)',0,1) WITH NOWAIT; +UPDATE #IndexSanity +SET key_column_names_with_sort_order_no_types = D2.key_column_names_with_sort_order_no_types +FROM #IndexSanity si + CROSS APPLY ( SELECT RTRIM(STUFF( (SELECT N', ' + QUOTENAME(c.column_name) + CASE c.is_descending_key + WHEN 1 THEN N' DESC' + ELSE N'' + END AS col_definition + FROM #IndexColumns c + WHERE c.database_id= si.database_id + AND c.schema_name = si.schema_name + AND c.object_id = si.object_id + AND c.index_id = si.index_id + AND c.is_included_column = 0 /*Just Keys*/ + AND c.key_ordinal > 0 /*Ignore non-key columns, such as partitioning keys*/ + ORDER BY c.object_id, c.index_id, c.key_ordinal + FOR XML PATH('') , TYPE).value('.', 'nvarchar(max)'), 1, 1, '')) + ) D2 ( key_column_names_with_sort_order_no_types ); -DROP TABLE IF EXISTS #grouped_interval; +RAISERROR (N'Updating #IndexSanity.include_column_names',0,1) WITH NOWAIT; +UPDATE #IndexSanity +SET include_column_names = D3.include_column_names +FROM #IndexSanity si + CROSS APPLY ( SELECT RTRIM(STUFF( (SELECT N', ' + c.column_name + + N' {' + system_type_name + + CASE max_length WHEN -1 THEN N' (max)' ELSE + CASE + WHEN system_type_name IN (N'char',N'varchar',N'binary',N'varbinary') THEN N' (' + CAST(max_length AS NVARCHAR(20)) + N')' + WHEN system_type_name IN (N'nchar',N'nvarchar') THEN N' (' + CAST(max_length/2 AS NVARCHAR(20)) + N')' + ELSE N' ' + CAST(max_length AS NVARCHAR(50)) + END + END + + N'}' + FROM #IndexColumns c + WHERE c.database_id= si.database_id + AND c.schema_name = si.schema_name + AND c.object_id = si.object_id + AND c.index_id = si.index_id + AND c.is_included_column = 1 /*Just includes*/ + ORDER BY c.column_name /*Order doesn't matter in includes, + this is here to make rows easy to compare.*/ + FOR XML PATH('') , TYPE).value('.', 'nvarchar(max)'), 1, 1, '')) + ) D3 ( include_column_names ); -CREATE TABLE #grouped_interval -( - flat_date DATE NULL, - start_range DATETIME NULL, - end_range DATETIME NULL, - total_avg_duration_ms DECIMAL(38, 2) NULL, - total_avg_cpu_time_ms DECIMAL(38, 2) NULL, - total_avg_logical_io_reads_mb DECIMAL(38, 2) NULL, - total_avg_physical_io_reads_mb DECIMAL(38, 2) NULL, - total_avg_logical_io_writes_mb DECIMAL(38, 2) NULL, - total_avg_query_max_used_memory_mb DECIMAL(38, 2) NULL, - total_rowcount DECIMAL(38, 2) NULL, - total_count_executions BIGINT NULL, - total_avg_log_bytes_mb DECIMAL(38, 2) NULL, - total_avg_tempdb_space DECIMAL(38, 2) NULL, - total_max_duration_ms DECIMAL(38, 2) NULL, - total_max_cpu_time_ms DECIMAL(38, 2) NULL, - total_max_logical_io_reads_mb DECIMAL(38, 2) NULL, - total_max_physical_io_reads_mb DECIMAL(38, 2) NULL, - total_max_logical_io_writes_mb DECIMAL(38, 2) NULL, - total_max_query_max_used_memory_mb DECIMAL(38, 2) NULL, - total_max_log_bytes_mb DECIMAL(38, 2) NULL, - total_max_tempdb_space DECIMAL(38, 2) NULL, - INDEX gi_ix_dates CLUSTERED (start_range, end_range) -); +RAISERROR (N'Updating #IndexSanity.include_column_names_no_types (for create tsql)',0,1) WITH NOWAIT; +UPDATE #IndexSanity +SET include_column_names_no_types = D3.include_column_names_no_types +FROM #IndexSanity si + CROSS APPLY ( SELECT RTRIM(STUFF( (SELECT N', ' + QUOTENAME(c.column_name) + FROM #IndexColumns c + WHERE c.database_id= si.database_id + AND c.schema_name = si.schema_name + AND c.object_id = si.object_id + AND c.index_id = si.index_id + AND c.is_included_column = 1 /*Just includes*/ + ORDER BY c.column_name /*Order doesn't matter in includes, + this is here to make rows easy to compare.*/ + FOR XML PATH('') , TYPE).value('.', 'nvarchar(max)'), 1, 1, '')) + ) D3 ( include_column_names_no_types ); +RAISERROR (N'Updating #IndexSanity.count_key_columns and count_include_columns',0,1) WITH NOWAIT; +UPDATE #IndexSanity +SET count_included_columns = D4.count_included_columns, + count_key_columns = D4.count_key_columns +FROM #IndexSanity si + CROSS APPLY ( SELECT SUM(CASE WHEN is_included_column = 'true' THEN 1 + ELSE 0 + END) AS count_included_columns, + SUM(CASE WHEN is_included_column = 'false' AND c.key_ordinal > 0 THEN 1 + ELSE 0 + END) AS count_key_columns + FROM #IndexColumns c + WHERE c.database_id= si.database_id + AND c.schema_name = si.schema_name + AND c.object_id = si.object_id + AND c.index_id = si.index_id + ) AS D4 ( count_included_columns, count_key_columns ); -/* -These are the plans we focus on based on what we find in the grouped intervals -*/ -DROP TABLE IF EXISTS #working_plans; +RAISERROR (N'Updating index_sanity_id on #IndexPartitionSanity',0,1) WITH NOWAIT; +UPDATE #IndexPartitionSanity +SET index_sanity_id = i.index_sanity_id +FROM #IndexPartitionSanity ps + JOIN #IndexSanity i ON ps.[object_id] = i.[object_id] + AND ps.index_id = i.index_id + AND i.database_id = ps.database_id + AND i.schema_name = ps.schema_name; -CREATE TABLE #working_plans -( - plan_id BIGINT, - query_id BIGINT, - pattern NVARCHAR(258), - INDEX wp_ix_ids CLUSTERED (plan_id, query_id) -); +RAISERROR (N'Inserting data into #IndexSanitySize',0,1) WITH NOWAIT; +INSERT #IndexSanitySize ( [index_sanity_id], [database_id], [schema_name], [lock_escalation_desc], partition_count, total_rows, total_reserved_MB, + total_reserved_LOB_MB, total_reserved_row_overflow_MB, total_reserved_dictionary_MB, total_range_scan_count, + total_singleton_lookup_count, total_leaf_delete_count, total_leaf_update_count, + total_forwarded_fetch_count,total_row_lock_count, + total_row_lock_wait_count, total_row_lock_wait_in_ms, avg_row_lock_wait_in_ms, + total_page_lock_count, total_page_lock_wait_count, total_page_lock_wait_in_ms, + avg_page_lock_wait_in_ms, total_index_lock_promotion_attempt_count, + total_index_lock_promotion_count, data_compression_desc, + page_latch_wait_count, page_latch_wait_in_ms, page_io_latch_wait_count, page_io_latch_wait_in_ms) + SELECT index_sanity_id, ipp.database_id, ipp.schema_name, ipp.lock_escalation_desc, + COUNT(*), SUM(row_count), SUM(reserved_MB), + SUM(reserved_LOB_MB) - SUM(reserved_dictionary_MB), /* Subtract columnstore dictionaries from LOB data */ + SUM(reserved_row_overflow_MB), + SUM(reserved_dictionary_MB), + SUM(range_scan_count), + SUM(singleton_lookup_count), + SUM(leaf_delete_count), + SUM(leaf_update_count), + SUM(forwarded_fetch_count), + SUM(row_lock_count), + SUM(row_lock_wait_count), + SUM(row_lock_wait_in_ms), + CASE WHEN SUM(row_lock_wait_in_ms) > 0 THEN + SUM(row_lock_wait_in_ms)/(1.*SUM(row_lock_wait_count)) + ELSE 0 END AS avg_row_lock_wait_in_ms, + SUM(page_lock_count), + SUM(page_lock_wait_count), + SUM(page_lock_wait_in_ms), + CASE WHEN SUM(page_lock_wait_in_ms) > 0 THEN + SUM(page_lock_wait_in_ms)/(1.*SUM(page_lock_wait_count)) + ELSE 0 END AS avg_page_lock_wait_in_ms, + SUM(index_lock_promotion_attempt_count), + SUM(index_lock_promotion_count), + LEFT(MAX(data_compression_info.data_compression_rollup),4000), + SUM(page_latch_wait_count), + SUM(page_latch_wait_in_ms), + SUM(page_io_latch_wait_count), + SUM(page_io_latch_wait_in_ms) + FROM #IndexPartitionSanity ipp + /* individual partitions can have distinct compression settings, just roll them into a list here*/ + OUTER APPLY (SELECT STUFF(( + SELECT N', ' + data_compression_desc + FROM #IndexPartitionSanity ipp2 + WHERE ipp.[object_id]=ipp2.[object_id] + AND ipp.[index_id]=ipp2.[index_id] + AND ipp.database_id = ipp2.database_id + AND ipp.schema_name = ipp2.schema_name + ORDER BY ipp2.partition_number + FOR XML PATH(''),TYPE).value('.', 'nvarchar(max)'), 1, 1, '')) + data_compression_info(data_compression_rollup) + GROUP BY index_sanity_id, ipp.database_id, ipp.schema_name, ipp.lock_escalation_desc + ORDER BY index_sanity_id +OPTION ( RECOMPILE ); -/* -These are the gathered metrics we get from query store to generate some warnings and help you find your worst offenders -*/ -DROP TABLE IF EXISTS #working_metrics; +RAISERROR (N'Determining index usefulness',0,1) WITH NOWAIT; +UPDATE #MissingIndexes +SET is_low = CASE WHEN (user_seeks + user_scans) < 5000 + OR unique_compiles = 1 + THEN 1 + ELSE 0 + END; -CREATE TABLE #working_metrics -( - database_name NVARCHAR(258), - plan_id BIGINT, - query_id BIGINT, - query_id_all_plan_ids VARCHAR(8000), - /*these columns are from query_store_query*/ - proc_or_function_name NVARCHAR(258), - batch_sql_handle VARBINARY(64), - query_hash BINARY(8), - query_parameterization_type_desc NVARCHAR(258), - parameter_sniffing_symptoms NVARCHAR(4000), - count_compiles BIGINT, - avg_compile_duration DECIMAL(38,2), - last_compile_duration DECIMAL(38,2), - avg_bind_duration DECIMAL(38,2), - last_bind_duration DECIMAL(38,2), - avg_bind_cpu_time DECIMAL(38,2), - last_bind_cpu_time DECIMAL(38,2), - avg_optimize_duration DECIMAL(38,2), - last_optimize_duration DECIMAL(38,2), - avg_optimize_cpu_time DECIMAL(38,2), - last_optimize_cpu_time DECIMAL(38,2), - avg_compile_memory_kb DECIMAL(38,2), - last_compile_memory_kb DECIMAL(38,2), - /*These come from query_store_runtime_stats*/ - execution_type_desc NVARCHAR(128), - first_execution_time DATETIME2, - last_execution_time DATETIME2, - count_executions BIGINT, - avg_duration DECIMAL(38,2) , - last_duration DECIMAL(38,2), - min_duration DECIMAL(38,2), - max_duration DECIMAL(38,2), - avg_cpu_time DECIMAL(38,2), - last_cpu_time DECIMAL(38,2), - min_cpu_time DECIMAL(38,2), - max_cpu_time DECIMAL(38,2), - avg_logical_io_reads DECIMAL(38,2), - last_logical_io_reads DECIMAL(38,2), - min_logical_io_reads DECIMAL(38,2), - max_logical_io_reads DECIMAL(38,2), - avg_logical_io_writes DECIMAL(38,2), - last_logical_io_writes DECIMAL(38,2), - min_logical_io_writes DECIMAL(38,2), - max_logical_io_writes DECIMAL(38,2), - avg_physical_io_reads DECIMAL(38,2), - last_physical_io_reads DECIMAL(38,2), - min_physical_io_reads DECIMAL(38,2), - max_physical_io_reads DECIMAL(38,2), - avg_clr_time DECIMAL(38,2), - last_clr_time DECIMAL(38,2), - min_clr_time DECIMAL(38,2), - max_clr_time DECIMAL(38,2), - avg_dop BIGINT, - last_dop BIGINT, - min_dop BIGINT, - max_dop BIGINT, - avg_query_max_used_memory DECIMAL(38,2), - last_query_max_used_memory DECIMAL(38,2), - min_query_max_used_memory DECIMAL(38,2), - max_query_max_used_memory DECIMAL(38,2), - avg_rowcount DECIMAL(38,2), - last_rowcount DECIMAL(38,2), - min_rowcount DECIMAL(38,2), - max_rowcount DECIMAL(38,2), - /*These are 2017 only, AFAIK*/ - avg_num_physical_io_reads DECIMAL(38,2), - last_num_physical_io_reads DECIMAL(38,2), - min_num_physical_io_reads DECIMAL(38,2), - max_num_physical_io_reads DECIMAL(38,2), - avg_log_bytes_used DECIMAL(38,2), - last_log_bytes_used DECIMAL(38,2), - min_log_bytes_used DECIMAL(38,2), - max_log_bytes_used DECIMAL(38,2), - avg_tempdb_space_used DECIMAL(38,2), - last_tempdb_space_used DECIMAL(38,2), - min_tempdb_space_used DECIMAL(38,2), - max_tempdb_space_used DECIMAL(38,2), - /*These are computed columns to make some stuff easier down the line*/ - total_compile_duration AS avg_compile_duration * count_compiles, - total_bind_duration AS avg_bind_duration * count_compiles, - total_bind_cpu_time AS avg_bind_cpu_time * count_compiles, - total_optimize_duration AS avg_optimize_duration * count_compiles, - total_optimize_cpu_time AS avg_optimize_cpu_time * count_compiles, - total_compile_memory_kb AS avg_compile_memory_kb * count_compiles, - total_duration AS avg_duration * count_executions, - total_cpu_time AS avg_cpu_time * count_executions, - total_logical_io_reads AS avg_logical_io_reads * count_executions, - total_logical_io_writes AS avg_logical_io_writes * count_executions, - total_physical_io_reads AS avg_physical_io_reads * count_executions, - total_clr_time AS avg_clr_time * count_executions, - total_query_max_used_memory AS avg_query_max_used_memory * count_executions, - total_rowcount AS avg_rowcount * count_executions, - total_num_physical_io_reads AS avg_num_physical_io_reads * count_executions, - total_log_bytes_used AS avg_log_bytes_used * count_executions, - total_tempdb_space_used AS avg_tempdb_space_used * count_executions, - xpm AS NULLIF(count_executions, 0) / NULLIF(DATEDIFF(MINUTE, first_execution_time, last_execution_time), 0), - percent_memory_grant_used AS CONVERT(MONEY, ISNULL(NULLIF(( max_query_max_used_memory * 1.00 ), 0) / NULLIF(min_query_max_used_memory, 0), 0) * 100.), - INDEX wm_ix_ids CLUSTERED (plan_id, query_id, query_hash) -); +RAISERROR (N'Updating #IndexSanity.referenced_by_foreign_key',0,1) WITH NOWAIT; +UPDATE #IndexSanity + SET is_referenced_by_foreign_key=1 +FROM #IndexSanity s +JOIN #ForeignKeys fk ON + s.object_id=fk.referenced_object_id + AND s.database_id=fk.database_id + AND LEFT(s.key_column_names,LEN(fk.referenced_fk_columns)) = fk.referenced_fk_columns; +RAISERROR (N'Update index_secret on #IndexSanity for NC indexes.',0,1) WITH NOWAIT; +UPDATE nc +SET secret_columns= + N'[' + + CASE tb.count_key_columns WHEN 0 THEN '1' ELSE CAST(tb.count_key_columns AS NVARCHAR(10)) END + + CASE nc.is_unique WHEN 1 THEN N' INCLUDE' ELSE N' KEY' END + + CASE WHEN tb.count_key_columns > 1 THEN N'S] ' ELSE N'] ' END + + CASE tb.index_id WHEN 0 THEN '[RID]' ELSE LTRIM(tb.key_column_names) + + /* Uniquifiers only needed on non-unique clustereds-- not heaps */ + CASE tb.is_unique WHEN 0 THEN ' [UNIQUIFIER]' ELSE N'' END + END + , count_secret_columns= + CASE tb.index_id WHEN 0 THEN 1 ELSE + tb.count_key_columns + + CASE tb.is_unique WHEN 0 THEN 1 ELSE 0 END + END +FROM #IndexSanity AS nc +JOIN #IndexSanity AS tb ON nc.object_id=tb.object_id + AND nc.database_id = tb.database_id + AND nc.schema_name = tb.schema_name + AND tb.index_id IN (0,1) +WHERE nc.index_id > 1; -/* -This is where we store some additional metrics, along with the query plan and text -*/ -DROP TABLE IF EXISTS #working_plan_text; +RAISERROR (N'Update index_secret on #IndexSanity for heaps and non-unique clustered.',0,1) WITH NOWAIT; +UPDATE tb +SET secret_columns= CASE tb.index_id WHEN 0 THEN '[RID]' ELSE '[UNIQUIFIER]' END + , count_secret_columns = 1 +FROM #IndexSanity AS tb +WHERE tb.index_id = 0 /*Heaps-- these have the RID */ + OR (tb.index_id=1 AND tb.is_unique=0); /* Non-unique CX: has uniquifer (when needed) */ -CREATE TABLE #working_plan_text -( - database_name NVARCHAR(258), - plan_id BIGINT, - query_id BIGINT, - /*These are from query_store_plan*/ - plan_group_id BIGINT, - engine_version NVARCHAR(64), - compatibility_level INT, - query_plan_hash BINARY(8), - query_plan_xml XML, - is_online_index_plan BIT, - is_trivial_plan BIT, - is_parallel_plan BIT, - is_forced_plan BIT, - is_natively_compiled BIT, - force_failure_count BIGINT, - last_force_failure_reason_desc NVARCHAR(258), - count_compiles BIGINT, - initial_compile_start_time DATETIME2, - last_compile_start_time DATETIME2, - last_execution_time DATETIME2, - avg_compile_duration DECIMAL(38,2), - last_compile_duration BIGINT, - /*These are from query_store_query*/ - query_sql_text NVARCHAR(MAX), - statement_sql_handle VARBINARY(64), - is_part_of_encrypted_module BIT, - has_restricted_text BIT, - /*This is from query_context_settings*/ - context_settings NVARCHAR(512), - /*This is from #working_plans*/ - pattern NVARCHAR(512), - top_three_waits NVARCHAR(MAX), - INDEX wpt_ix_ids CLUSTERED (plan_id, query_id, query_plan_hash) -); +RAISERROR (N'Populate #IndexCreateTsql.',0,1) WITH NOWAIT; +INSERT #IndexCreateTsql (index_sanity_id, create_tsql) +SELECT + index_sanity_id, + ISNULL ( + CASE index_id WHEN 0 THEN N'ALTER TABLE ' + QUOTENAME([database_name]) + N'.' + QUOTENAME([schema_name]) + N'.' + QUOTENAME([object_name]) + ' REBUILD;' + ELSE + CASE WHEN is_XML = 1 OR is_spatial = 1 OR is_in_memory_oltp = 1 THEN N'' /* Not even trying for these just yet...*/ + ELSE + CASE WHEN is_primary_key=1 THEN + N'ALTER TABLE ' + QUOTENAME([database_name]) + N'.' + QUOTENAME([schema_name]) + + N'.' + QUOTENAME([object_name]) + + N' ADD CONSTRAINT [' + + index_name + + N'] PRIMARY KEY ' + + CASE WHEN index_id=1 THEN N'CLUSTERED (' ELSE N'(' END + + key_column_names_with_sort_order_no_types + N' )' + WHEN is_unique_constraint = 1 AND is_primary_key = 0 + THEN + N'ALTER TABLE ' + QUOTENAME([database_name]) + N'.' + QUOTENAME([schema_name]) + + N'.' + QUOTENAME([object_name]) + + N' ADD CONSTRAINT [' + + index_name + + N'] UNIQUE ' + + CASE WHEN index_id=1 THEN N'CLUSTERED (' ELSE N'(' END + + key_column_names_with_sort_order_no_types + N' )' + WHEN is_CX_columnstore= 1 THEN + N'CREATE CLUSTERED COLUMNSTORE INDEX ' + QUOTENAME(index_name) + N' on ' + QUOTENAME([database_name]) + N'.' + QUOTENAME([schema_name]) + N'.' + QUOTENAME([object_name]) + ELSE /*Else not a PK or cx columnstore */ + N'CREATE ' + + CASE WHEN is_unique=1 THEN N'UNIQUE ' ELSE N'' END + + CASE WHEN index_id=1 THEN N'CLUSTERED ' ELSE N'' END + + CASE WHEN is_NC_columnstore=1 THEN N'NONCLUSTERED COLUMNSTORE ' + ELSE N'' END + + N'INDEX [' + + index_name + N'] ON ' + + QUOTENAME([database_name]) + N'.' + + QUOTENAME([schema_name]) + N'.' + QUOTENAME([object_name]) + + CASE WHEN is_NC_columnstore=1 THEN + N' (' + ISNULL(include_column_names_no_types,'') + N' )' + ELSE /*Else not columnstore */ + N' (' + ISNULL(key_column_names_with_sort_order_no_types,'') + N' )' + + CASE WHEN include_column_names_no_types IS NOT NULL THEN + N' INCLUDE (' + include_column_names_no_types + N')' + ELSE N'' + END + END /*End non-columnstore case */ + + CASE WHEN filter_definition <> N'' THEN N' WHERE ' + filter_definition ELSE N'' END + END /*End Non-PK index CASE */ + + CASE WHEN is_NC_columnstore=0 AND is_CX_columnstore=0 THEN + N' WITH (' + + N'FILLFACTOR=' + CASE fill_factor WHEN 0 THEN N'100' ELSE CAST(fill_factor AS NVARCHAR(5)) END + ', ' + + N'ONLINE=?, SORT_IN_TEMPDB=?, DATA_COMPRESSION=?' + + N')' + ELSE N'' END + + N';' + END /*End non-spatial and non-xml CASE */ + END, '[Unknown Error]') + AS create_tsql +FROM #IndexSanity; + +RAISERROR (N'Populate #PartitionCompressionInfo.',0,1) WITH NOWAIT; +IF OBJECT_ID('tempdb..#maps') IS NOT NULL DROP TABLE #maps; +WITH maps + AS + ( + SELECT ips.index_sanity_id, + ips.partition_number, + ips.data_compression_desc, + ips.partition_number - ROW_NUMBER() OVER ( PARTITION BY ips.index_sanity_id, ips.data_compression_desc + ORDER BY ips.partition_number ) AS rn + FROM #IndexPartitionSanity AS ips + ) +SELECT * +INTO #maps +FROM maps; -/* -This is where we store warnings that we generate from the XML and metrics -*/ -DROP TABLE IF EXISTS #working_warnings; +IF OBJECT_ID('tempdb..#grps') IS NOT NULL DROP TABLE #grps; +WITH grps + AS + ( + SELECT MIN(maps.partition_number) AS MinKey, + MAX(maps.partition_number) AS MaxKey, + maps.index_sanity_id, + maps.data_compression_desc + FROM #maps AS maps + GROUP BY maps.rn, maps.index_sanity_id, maps.data_compression_desc + ) +SELECT * +INTO #grps +FROM grps; -CREATE TABLE #working_warnings -( - plan_id BIGINT, - query_id BIGINT, - query_hash BINARY(8), - sql_handle VARBINARY(64), - proc_or_function_name NVARCHAR(258), - plan_multiple_plans BIT, - is_forced_plan BIT, - is_forced_parameterized BIT, - is_cursor BIT, - is_optimistic_cursor BIT, - is_forward_only_cursor BIT, - is_fast_forward_cursor BIT, - is_cursor_dynamic BIT, - is_parallel BIT, - is_forced_serial BIT, - is_key_lookup_expensive BIT, - key_lookup_cost FLOAT, - is_remote_query_expensive BIT, - remote_query_cost FLOAT, - frequent_execution BIT, - parameter_sniffing BIT, - unparameterized_query BIT, - near_parallel BIT, - plan_warnings BIT, - long_running BIT, - downlevel_estimator BIT, - implicit_conversions BIT, - tvf_estimate BIT, - compile_timeout BIT, - compile_memory_limit_exceeded BIT, - warning_no_join_predicate BIT, - query_cost FLOAT, - missing_index_count INT, - unmatched_index_count INT, - is_trivial BIT, - trace_flags_session NVARCHAR(1000), - is_unused_grant BIT, - function_count INT, - clr_function_count INT, - is_table_variable BIT, - no_stats_warning BIT, - relop_warnings BIT, - is_table_scan BIT, - backwards_scan BIT, - forced_index BIT, - forced_seek BIT, - forced_scan BIT, - columnstore_row_mode BIT, - is_computed_scalar BIT , - is_sort_expensive BIT, - sort_cost FLOAT, - is_computed_filter BIT, - op_name NVARCHAR(100) NULL, - index_insert_count INT NULL, - index_update_count INT NULL, - index_delete_count INT NULL, - cx_insert_count INT NULL, - cx_update_count INT NULL, - cx_delete_count INT NULL, - table_insert_count INT NULL, - table_update_count INT NULL, - table_delete_count INT NULL, - index_ops AS (index_insert_count + index_update_count + index_delete_count + - cx_insert_count + cx_update_count + cx_delete_count + - table_insert_count + table_update_count + table_delete_count), - is_row_level BIT, - is_spatial BIT, - index_dml BIT, - table_dml BIT, - long_running_low_cpu BIT, - low_cost_high_cpu BIT, - stale_stats BIT, - is_adaptive BIT, - is_slow_plan BIT, - is_compile_more BIT, - index_spool_cost FLOAT, - index_spool_rows FLOAT, - is_spool_expensive BIT, - is_spool_more_rows BIT, - estimated_rows FLOAT, - is_bad_estimate BIT, - is_big_log BIT, - is_big_tempdb BIT, - is_paul_white_electric BIT, - is_row_goal BIT, - is_mstvf BIT, - is_mm_join BIT, - is_nonsargable BIT, - busy_loops BIT, - tvf_join BIT, - implicit_conversion_info XML, - cached_execution_parameters XML, - missing_indexes XML, - warnings NVARCHAR(4000) - INDEX ww_ix_ids CLUSTERED (plan_id, query_id, query_hash, sql_handle) -); +INSERT #PartitionCompressionInfo ( index_sanity_id, partition_compression_detail ) +SELECT DISTINCT + grps.index_sanity_id, + SUBSTRING( + ( STUFF( + ( SELECT N', ' + N' Partition' + + CASE + WHEN grps2.MinKey < grps2.MaxKey + THEN + + N's ' + CAST(grps2.MinKey AS NVARCHAR(10)) + N' - ' + + CAST(grps2.MaxKey AS NVARCHAR(10)) + N' use ' + grps2.data_compression_desc + ELSE + N' ' + CAST(grps2.MinKey AS NVARCHAR(10)) + N' uses ' + grps2.data_compression_desc + END AS Partitions + FROM #grps AS grps2 + WHERE grps2.index_sanity_id = grps.index_sanity_id + ORDER BY grps2.MinKey, grps2.MaxKey + FOR XML PATH(''), TYPE ).value('.', 'NVARCHAR(MAX)'), 1, 1, '')), 0, 8000) AS partition_compression_detail +FROM #grps AS grps; + +RAISERROR (N'Update #PartitionCompressionInfo.',0,1) WITH NOWAIT; +UPDATE sz +SET sz.data_compression_desc = pci.partition_compression_detail +FROM #IndexSanitySize sz +JOIN #PartitionCompressionInfo AS pci +ON pci.index_sanity_id = sz.index_sanity_id; +RAISERROR (N'Update #IndexSanity for filtered indexes with columns not in the index definition.',0,1) WITH NOWAIT; +UPDATE #IndexSanity +SET filter_columns_not_in_index = D1.filter_columns_not_in_index +FROM #IndexSanity si + CROSS APPLY ( SELECT RTRIM(STUFF( (SELECT N', ' + c.column_name AS col_definition + FROM #FilteredIndexes AS c + WHERE c.database_id= si.database_id + AND c.schema_name = si.schema_name + AND c.table_name = si.object_name + AND c.index_name = si.index_name + ORDER BY c.index_sanity_id + FOR XML PATH('') , TYPE).value('.', 'nvarchar(max)'), 1, 1,''))) D1 + ( filter_columns_not_in_index ); -DROP TABLE IF EXISTS #working_wait_stats; -CREATE TABLE #working_wait_stats -( - plan_id BIGINT, - wait_category TINYINT, - wait_category_desc NVARCHAR(258), - total_query_wait_time_ms BIGINT, - avg_query_wait_time_ms DECIMAL(38, 2), - last_query_wait_time_ms BIGINT, - min_query_wait_time_ms BIGINT, - max_query_wait_time_ms BIGINT, - wait_category_mapped AS CASE wait_category - WHEN 0 THEN N'UNKNOWN' - WHEN 1 THEN N'SOS_SCHEDULER_YIELD' - WHEN 2 THEN N'THREADPOOL' - WHEN 3 THEN N'LCK_M_%' - WHEN 4 THEN N'LATCH_%' - WHEN 5 THEN N'PAGELATCH_%' - WHEN 6 THEN N'PAGEIOLATCH_%' - WHEN 7 THEN N'RESOURCE_SEMAPHORE_QUERY_COMPILE' - WHEN 8 THEN N'CLR%, SQLCLR%' - WHEN 9 THEN N'DBMIRROR%' - WHEN 10 THEN N'XACT%, DTC%, TRAN_MARKLATCH_%, MSQL_XACT_%, TRANSACTION_MUTEX' - WHEN 11 THEN N'SLEEP_%, LAZYWRITER_SLEEP, SQLTRACE_BUFFER_FLUSH, SQLTRACE_INCREMENTAL_FLUSH_SLEEP, SQLTRACE_WAIT_ENTRIES, FT_IFTS_SCHEDULER_IDLE_WAIT, XE_DISPATCHER_WAIT, REQUEST_FOR_DEADLOCK_SEARCH, LOGMGR_QUEUE, ONDEMAND_TASK_QUEUE, CHECKPOINT_QUEUE, XE_TIMER_EVENT' - WHEN 12 THEN N'PREEMPTIVE_%' - WHEN 13 THEN N'BROKER_% (but not BROKER_RECEIVE_WAITFOR)' - WHEN 14 THEN N'LOGMGR, LOGBUFFER, LOGMGR_RESERVE_APPEND, LOGMGR_FLUSH, LOGMGR_PMM_LOG, CHKPT, WRITELOG' - WHEN 15 THEN N'ASYNC_NETWORK_IO, NET_WAITFOR_PACKET, PROXY_NETWORK_IO, EXTERNAL_SCRIPT_NETWORK_IOF' - WHEN 16 THEN N'CXPACKET, EXCHANGE, CXCONSUMER' - WHEN 17 THEN N'RESOURCE_SEMAPHORE, CMEMTHREAD, CMEMPARTITIONED, EE_PMOLOCK, MEMORY_ALLOCATION_EXT, RESERVED_MEMORY_ALLOCATION_EXT, MEMORY_GRANT_UPDATE' - WHEN 18 THEN N'WAITFOR, WAIT_FOR_RESULTS, BROKER_RECEIVE_WAITFOR' - WHEN 19 THEN N'TRACEWRITE, SQLTRACE_LOCK, SQLTRACE_FILE_BUFFER, SQLTRACE_FILE_WRITE_IO_COMPLETION, SQLTRACE_FILE_READ_IO_COMPLETION, SQLTRACE_PENDING_BUFFER_WRITERS, SQLTRACE_SHUTDOWN, QUERY_TRACEOUT, TRACE_EVTNOTIFF' - WHEN 20 THEN N'FT_RESTART_CRAWL, FULLTEXT GATHERER, MSSEARCH, FT_METADATA_MUTEX, FT_IFTSHC_MUTEX, FT_IFTSISM_MUTEX, FT_IFTS_RWLOCK, FT_COMPROWSET_RWLOCK, FT_MASTER_MERGE, FT_PROPERTYLIST_CACHE, FT_MASTER_MERGE_COORDINATOR, PWAIT_RESOURCE_SEMAPHORE_FT_PARALLEL_QUERY_SYNC' - WHEN 21 THEN N'ASYNC_IO_COMPLETION, IO_COMPLETION, BACKUPIO, WRITE_COMPLETION, IO_QUEUE_LIMIT, IO_RETRY' - WHEN 22 THEN N'SE_REPL_%, REPL_%, HADR_% (but not HADR_THROTTLE_LOG_RATE_GOVERNOR), PWAIT_HADR_%, REPLICA_WRITES, FCB_REPLICA_WRITE, FCB_REPLICA_READ, PWAIT_HADRSIM' - WHEN 23 THEN N'LOG_RATE_GOVERNOR, POOL_LOG_RATE_GOVERNOR, HADR_THROTTLE_LOG_RATE_GOVERNOR, INSTANCE_LOG_RATE_GOVERNOR' - END, - INDEX wws_ix_ids CLUSTERED ( plan_id) -); +IF @Debug = 1 +BEGIN + SELECT '#BlitzIndexResults' AS table_name, * FROM #BlitzIndexResults AS bir; + SELECT '#IndexSanity' AS table_name, * FROM #IndexSanity; + SELECT '#IndexPartitionSanity' AS table_name, * FROM #IndexPartitionSanity; + SELECT '#IndexSanitySize' AS table_name, * FROM #IndexSanitySize; + SELECT '#IndexColumns' AS table_name, * FROM #IndexColumns; + SELECT '#MissingIndexes' AS table_name, * FROM #MissingIndexes; + SELECT '#ForeignKeys' AS table_name, * FROM #ForeignKeys; + SELECT '#UnindexedForeignKeys' AS table_name, * FROM #UnindexedForeignKeys; + SELECT '#BlitzIndexResults' AS table_name, * FROM #BlitzIndexResults; + SELECT '#IndexCreateTsql' AS table_name, * FROM #IndexCreateTsql; + SELECT '#DatabaseList' AS table_name, * FROM #DatabaseList; + SELECT '#Statistics' AS table_name, * FROM #Statistics; + SELECT '#PartitionCompressionInfo' AS table_name, * FROM #PartitionCompressionInfo; + SELECT '#ComputedColumns' AS table_name, * FROM #ComputedColumns; + SELECT '#TraceStatus' AS table_name, * FROM #TraceStatus; + SELECT '#CheckConstraints' AS table_name, * FROM #CheckConstraints; + SELECT '#FilteredIndexes' AS table_name, * FROM #FilteredIndexes; +END -/* -The next three tables hold plan XML parsed out to different degrees -*/ -DROP TABLE IF EXISTS #statements; +---------------------------------------- +--STEP 3: DIAGNOSE THE PATIENT +---------------------------------------- -CREATE TABLE #statements -( - plan_id BIGINT, - query_id BIGINT, - query_hash BINARY(8), - sql_handle VARBINARY(64), - statement XML, - is_cursor BIT - INDEX s_ix_ids CLUSTERED (plan_id, query_id, query_hash, sql_handle) -); +BEGIN TRY +---------------------------------------- +--If @TableName is specified, just return information for that table. +--The @Mode parameter doesn't matter if you're looking at a specific table. +---------------------------------------- +IF @TableName IS NOT NULL +BEGIN + RAISERROR(N'@TableName specified, giving detail only on that table.', 0,1) WITH NOWAIT; -DROP TABLE IF EXISTS #query_plan; + --We do a left join here in case this is a disabled NC. + --In that case, it won't have any size info/pages allocated. + + IF (@ShowColumnstoreOnly = 0) + BEGIN + WITH table_mode_cte AS ( + SELECT + s.db_schema_object_indexid, + s.key_column_names, + s.index_definition, + ISNULL(s.secret_columns,N'') AS secret_columns, + s.fill_factor, + s.index_usage_summary, + sz.index_op_stats, + ISNULL(sz.index_size_summary,'') /*disabled NCs will be null*/ AS index_size_summary, + partition_compression_detail , + ISNULL(sz.index_lock_wait_summary,'') AS index_lock_wait_summary, + s.is_referenced_by_foreign_key, + (SELECT COUNT(*) + FROM #ForeignKeys fk WHERE fk.parent_object_id=s.object_id + AND PATINDEX (fk.parent_fk_columns, s.key_column_names)=1) AS FKs_covered_by_index, + s.last_user_seek, + s.last_user_scan, + s.last_user_lookup, + s.last_user_update, + s.create_date, + s.modify_date, + sz.page_latch_wait_count, + CONVERT(VARCHAR(10), (sz.page_latch_wait_in_ms / 1000) / 86400) + ':' + CONVERT(VARCHAR(20), DATEADD(s, (sz.page_latch_wait_in_ms / 1000), 0), 108) AS page_latch_wait_time, + sz.page_io_latch_wait_count, + CONVERT(VARCHAR(10), (sz.page_io_latch_wait_in_ms / 1000) / 86400) + ':' + CONVERT(VARCHAR(20), DATEADD(s, (sz.page_io_latch_wait_in_ms / 1000), 0), 108) AS page_io_latch_wait_time, + ct.create_tsql, + CASE + WHEN s.is_primary_key = 1 AND s.index_definition <> '[HEAP]' + THEN N'--ALTER TABLE ' + QUOTENAME(s.[database_name]) + N'.' + QUOTENAME(s.[schema_name]) + N'.' + QUOTENAME(s.[object_name]) + + N' DROP CONSTRAINT ' + QUOTENAME(s.index_name) + N';' + WHEN s.is_primary_key = 0 AND is_unique_constraint = 1 AND s.index_definition <> '[HEAP]' + THEN N'--ALTER TABLE ' + QUOTENAME(s.[database_name]) + N'.' + QUOTENAME(s.[schema_name]) + N'.' + QUOTENAME(s.[object_name]) + + N' DROP CONSTRAINT ' + QUOTENAME(s.index_name) + N';' + WHEN s.is_primary_key = 0 AND s.index_definition <> '[HEAP]' + THEN N'--DROP INDEX '+ QUOTENAME(s.index_name) + N' ON ' + QUOTENAME(s.[database_name]) + N'.' + + QUOTENAME(s.[schema_name]) + N'.' + QUOTENAME(s.[object_name]) + N';' + ELSE N'' + END AS drop_tsql, + 1 AS display_order + FROM #IndexSanity s + LEFT JOIN #IndexSanitySize sz ON + s.index_sanity_id=sz.index_sanity_id + LEFT JOIN #IndexCreateTsql ct ON + s.index_sanity_id=ct.index_sanity_id + LEFT JOIN #PartitionCompressionInfo pci ON + pci.index_sanity_id = s.index_sanity_id + WHERE s.[object_id]=@ObjectID + UNION ALL + SELECT N'Database ' + QUOTENAME(@DatabaseName) + N' as of ' + CONVERT(NVARCHAR(16),GETDATE(),121) + + N' (' + @ScriptVersionName + ')' , + N'SQL Server First Responder Kit' , + N'http://FirstResponderKit.org' , + N'From Your Community Volunteers', + NULL,@DaysUptimeInsertValue,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL, + 0 AS display_order + ) + SELECT + db_schema_object_indexid AS [Details: db_schema.table.index(indexid)], + index_definition AS [Definition: [Property]] ColumnName {datatype maxbytes}], + secret_columns AS [Secret Columns], + fill_factor AS [Fillfactor], + index_usage_summary AS [Usage Stats], + index_op_stats AS [Op Stats], + index_size_summary AS [Size], + partition_compression_detail AS [Compression Type], + index_lock_wait_summary AS [Lock Waits], + is_referenced_by_foreign_key AS [Referenced by FK?], + FKs_covered_by_index AS [FK Covered by Index?], + last_user_seek AS [Last User Seek], + last_user_scan AS [Last User Scan], + last_user_lookup AS [Last User Lookup], + last_user_update AS [Last User Write], + create_date AS [Created], + modify_date AS [Last Modified], + page_latch_wait_count AS [Page Latch Wait Count], + page_latch_wait_time as [Page Latch Wait Time (D:H:M:S)], + page_io_latch_wait_count AS [Page IO Latch Wait Count], + page_io_latch_wait_time as [Page IO Latch Wait Time (D:H:M:S)], + create_tsql AS [Create TSQL], + drop_tsql AS [Drop TSQL] + FROM table_mode_cte + ORDER BY display_order ASC, key_column_names ASC + OPTION ( RECOMPILE ); -CREATE TABLE #query_plan -( - plan_id BIGINT, - query_id BIGINT, - query_hash BINARY(8), - sql_handle VARBINARY(64), - query_plan XML, - INDEX qp_ix_ids CLUSTERED (plan_id, query_id, query_hash, sql_handle) -); + IF (SELECT TOP 1 [object_id] FROM #MissingIndexes mi) IS NOT NULL + BEGIN; + WITH create_date AS ( + SELECT i.database_id, + i.schema_name, + i.[object_id], + ISNULL(NULLIF(MAX(DATEDIFF(DAY, i.create_date, SYSDATETIME())), 0), 1) AS create_days + FROM #IndexSanity AS i + GROUP BY i.database_id, i.schema_name, i.object_id + ) + SELECT N'Missing index.' AS Finding , + N'https://www.brentozar.com/go/Indexaphobia' AS URL , + mi.[statement] + + ' Est. Benefit: ' + + CASE WHEN magic_benefit_number >= 922337203685477 THEN '>= 922,337,203,685,477' + ELSE REPLACE(CONVERT(NVARCHAR(256),CAST(CAST( + (magic_benefit_number / CASE WHEN cd.create_days < @DaysUptime THEN cd.create_days ELSE @DaysUptime END) + AS BIGINT) AS MONEY), 1), '.00', '') + END AS [Estimated Benefit], + missing_index_details AS [Missing Index Request] , + index_estimated_impact AS [Estimated Impact], + create_tsql AS [Create TSQL], + sample_query_plan AS [Sample Query Plan] + FROM #MissingIndexes mi + LEFT JOIN create_date AS cd + ON mi.[object_id] = cd.object_id + AND mi.database_id = cd.database_id + AND mi.schema_name = cd.schema_name + WHERE mi.[object_id] = @ObjectID + AND (@ShowAllMissingIndexRequests=1 + /* Minimum benefit threshold = 100k/day of uptime OR since table creation date, whichever is lower*/ + OR (magic_benefit_number / CASE WHEN cd.create_days < @DaysUptime THEN cd.create_days ELSE @DaysUptime END) >= 100000) + ORDER BY magic_benefit_number DESC + OPTION ( RECOMPILE ); + END; + ELSE + SELECT 'No missing indexes.' AS finding; -DROP TABLE IF EXISTS #relop; + SELECT + column_name AS [Column Name], + (SELECT COUNT(*) + FROM #IndexColumns c2 + WHERE c2.column_name=c.column_name + AND c2.key_ordinal IS NOT NULL) + + CASE WHEN c.index_id = 1 AND c.key_ordinal IS NOT NULL THEN + -1+ (SELECT COUNT(DISTINCT index_id) + FROM #IndexColumns c3 + WHERE c3.index_id NOT IN (0,1)) + ELSE 0 END + AS [Found In], + system_type_name + + CASE max_length WHEN -1 THEN N' (max)' ELSE + CASE + WHEN system_type_name IN (N'char',N'varchar',N'binary',N'varbinary') THEN N' (' + CAST(max_length AS NVARCHAR(20)) + N')' + WHEN system_type_name IN (N'nchar',N'nvarchar') THEN N' (' + CAST(max_length/2 AS NVARCHAR(20)) + N')' + ELSE '' + END + END + AS [Type], + CASE is_computed WHEN 1 THEN 'yes' ELSE '' END AS [Computed?], + max_length AS [Length (max bytes)], + [precision] AS [Prec], + [scale] AS [Scale], + CASE is_nullable WHEN 1 THEN 'yes' ELSE '' END AS [Nullable?], + CASE is_identity WHEN 1 THEN 'yes' ELSE '' END AS [Identity?], + CASE is_replicated WHEN 1 THEN 'yes' ELSE '' END AS [Replicated?], + CASE is_sparse WHEN 1 THEN 'yes' ELSE '' END AS [Sparse?], + CASE is_filestream WHEN 1 THEN 'yes' ELSE '' END AS [Filestream?], + collation_name AS [Collation] + FROM #IndexColumns AS c + WHERE index_id IN (0,1); -CREATE TABLE #relop -( - plan_id BIGINT, - query_id BIGINT, - query_hash BINARY(8), - sql_handle VARBINARY(64), - relop XML, - INDEX ix_ids CLUSTERED (plan_id, query_id, query_hash, sql_handle) -); + IF (SELECT TOP 1 parent_object_id FROM #ForeignKeys) IS NOT NULL + BEGIN + SELECT [database_name] + N':' + parent_object_name + N': ' + foreign_key_name AS [Foreign Key], + parent_fk_columns AS [Foreign Key Columns], + referenced_object_name AS [Referenced Table], + referenced_fk_columns AS [Referenced Table Columns], + is_disabled AS [Is Disabled?], + is_not_trusted AS [Not Trusted?], + is_not_for_replication [Not for Replication?], + [update_referential_action_desc] AS [Cascading Updates?], + [delete_referential_action_desc] AS [Cascading Deletes?] + FROM #ForeignKeys + ORDER BY [Foreign Key] + OPTION ( RECOMPILE ); + END; + ELSE + SELECT 'No foreign keys.' AS finding; + /* Show histograms for all stats on this table. More info: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/1900 */ + IF EXISTS (SELECT * FROM sys.all_objects WHERE name = 'dm_db_stats_histogram') + BEGIN + SET @dsql = N'USE ' + QUOTENAME(@DatabaseName) + N'; + SELECT s.name AS [Stat Name], c.name AS [Leading Column Name], hist.step_number AS [Step Number], + hist.range_high_key AS [Range High Key], hist.range_rows AS [Range Rows], + hist.equal_rows AS [Equal Rows], hist.distinct_range_rows AS [Distinct Range Rows], hist.average_range_rows AS [Average Range Rows], + s.auto_created AS [Auto-Created], s.user_created AS [User-Created], + props.last_updated AS [Last Updated], props.modification_counter AS [Modification Counter], props.rows AS [Table Rows], + props.rows_sampled AS [Rows Sampled], s.stats_id AS [StatsID] + FROM sys.stats AS s + INNER JOIN sys.stats_columns sc ON s.object_id = sc.object_id AND s.stats_id = sc.stats_id AND sc.stats_column_id = 1 + INNER JOIN sys.columns c ON sc.object_id = c.object_id AND sc.column_id = c.column_id + CROSS APPLY sys.dm_db_stats_properties(s.object_id, s.stats_id) AS props + CROSS APPLY sys.dm_db_stats_histogram(s.[object_id], s.stats_id) AS hist + WHERE s.object_id = @ObjectID + ORDER BY s.auto_created, s.user_created, s.name, hist.step_number;'; + EXEC sp_executesql @dsql, N'@ObjectID INT', @ObjectID; + END + END -DROP TABLE IF EXISTS #plan_cost; + /* Visualize columnstore index contents. More info: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2584 */ + IF 2 = (SELECT SUM(1) FROM sys.all_objects WHERE name IN ('column_store_row_groups','column_store_segments')) + BEGIN + RAISERROR(N'Visualizing columnstore index contents.', 0,1) WITH NOWAIT; -CREATE TABLE #plan_cost -( - query_plan_cost DECIMAL(38,2), - sql_handle VARBINARY(64), - plan_id INT, - INDEX px_ix_ids CLUSTERED (sql_handle, plan_id) -); + SET @dsql = N'USE ' + QUOTENAME(@DatabaseName) + N'; + IF EXISTS(SELECT * FROM ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_row_groups WHERE object_id = @ObjectID) + BEGIN + SELECT @ColumnList = N'''', @ColumnListWithApostrophes = N''''; + WITH DistinctColumns AS ( + SELECT DISTINCT QUOTENAME(c.name) AS column_name, QUOTENAME(c.name,'''''''') AS ColumnNameWithApostrophes, c.column_id + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.partitions p + INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns c ON p.object_id = c.object_id + INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.index_columns ic on ic.column_id = c.column_id and ic.object_id = c.object_id AND ic.index_id = p.index_id + INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.types t ON c.system_type_id = t.system_type_id AND c.user_type_id = t.user_type_id + WHERE p.object_id = @ObjectID + AND EXISTS (SELECT * FROM ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_segments seg WHERE p.partition_id = seg.partition_id AND seg.column_id = ic.index_column_id) + AND p.data_compression IN (3,4) + ) + SELECT @ColumnList = @ColumnList + column_name + N'', '', + @ColumnListWithApostrophes = @ColumnListWithApostrophes + ColumnNameWithApostrophes + N'', '' + FROM DistinctColumns + ORDER BY column_id; + SELECT @PartitionCount = COUNT(1) FROM ' + QUOTENAME(@DatabaseName) + N'.sys.partitions WHERE object_id = @ObjectID AND data_compression IN (3,4); + END'; + IF @Debug = 1 + BEGIN + PRINT SUBSTRING(@dsql, 0, 4000); + PRINT SUBSTRING(@dsql, 4000, 8000); + PRINT SUBSTRING(@dsql, 8000, 12000); + PRINT SUBSTRING(@dsql, 12000, 16000); + PRINT SUBSTRING(@dsql, 16000, 20000); + PRINT SUBSTRING(@dsql, 20000, 24000); + PRINT SUBSTRING(@dsql, 24000, 28000); + PRINT SUBSTRING(@dsql, 28000, 32000); + PRINT SUBSTRING(@dsql, 32000, 36000); + PRINT SUBSTRING(@dsql, 36000, 40000); + END; -DROP TABLE IF EXISTS #est_rows; + EXEC sp_executesql @dsql, N'@ObjectID INT, @ColumnList NVARCHAR(MAX) OUTPUT, @ColumnListWithApostrophes NVARCHAR(MAX) OUTPUT, @PartitionCount INT OUTPUT', @ObjectID, @ColumnList OUTPUT, @ColumnListWithApostrophes OUTPUT, @PartitionCount OUTPUT; -CREATE TABLE #est_rows -( - estimated_rows DECIMAL(38,2), - query_hash BINARY(8), - INDEX px_ix_ids CLUSTERED (query_hash) -); + IF @PartitionCount < 2 + SET @ShowPartitionRanges = 0; + IF @Debug = 1 + SELECT @ColumnList AS ColumnstoreColumnList, @ColumnListWithApostrophes AS ColumnstoreColumnListWithApostrophes, @PartitionCount AS PartitionCount, @ShowPartitionRanges AS ShowPartitionRanges; -DROP TABLE IF EXISTS #stats_agg; + IF @ColumnList <> '' + BEGIN + /* Remove the trailing comma */ + SET @ColumnList = LEFT(@ColumnList, LEN(@ColumnList) - 1); + SET @ColumnListWithApostrophes = LEFT(@ColumnListWithApostrophes, LEN(@ColumnListWithApostrophes) - 1); -CREATE TABLE #stats_agg -( - sql_handle VARBINARY(64), - last_update DATETIME2, - modification_count BIGINT, - sampling_percent DECIMAL(38, 2), - [statistics] NVARCHAR(258), - [table] NVARCHAR(258), - [schema] NVARCHAR(258), - [database] NVARCHAR(258), - INDEX sa_ix_ids CLUSTERED (sql_handle) -); + SET @dsql = N'USE ' + QUOTENAME(@DatabaseName) + N'; + SELECT partition_number, ' + + CASE WHEN @ShowPartitionRanges = 1 THEN N' COALESCE(range_start_op + '' '' + range_start + '' '', '''') + COALESCE(range_end_op + '' '' + range_end, '''') AS partition_range, ' ELSE N' ' END + + N' row_group_id, total_rows, deleted_rows, ' + + @ColumnList + + CASE WHEN @ShowPartitionRanges = 1 THEN N' , + state_desc, trim_reason_desc, transition_to_compressed_state_desc, has_vertipaq_optimization + FROM ( + SELECT column_name, partition_number, row_group_id, total_rows, deleted_rows, details, + range_start_op, + CASE + WHEN format_type IS NULL THEN CAST(range_start_value AS NVARCHAR(4000)) + ELSE CONVERT(NVARCHAR(4000), range_start_value, format_type) END range_start, + range_end_op, + CASE + WHEN format_type IS NULL THEN CAST(range_end_value AS NVARCHAR(4000)) + ELSE CONVERT(NVARCHAR(4000), range_end_value, format_type) END range_end' ELSE N' ' END + N', + state_desc, trim_reason_desc, transition_to_compressed_state_desc, has_vertipaq_optimization + FROM ( + SELECT c.name AS column_name, p.partition_number, rg.row_group_id, rg.total_rows, rg.deleted_rows, + phys.state_desc, phys.trim_reason_desc, phys.transition_to_compressed_state_desc, phys.has_vertipaq_optimization, + details = CAST(seg.min_data_id AS VARCHAR(20)) + '' to '' + CAST(seg.max_data_id AS VARCHAR(20)) + '', '' + CAST(CAST(((COALESCE(d.on_disk_size,0) + COALESCE(seg.on_disk_size,0)) / 1024.0 / 1024) AS DECIMAL(18,0)) AS VARCHAR(20)) + '' MB''' + + CASE WHEN @ShowPartitionRanges = 1 THEN N', + CASE + WHEN pp.system_type_id IN (40, 41, 42, 43, 58, 61) THEN 126 + WHEN pp.system_type_id IN (59, 62) THEN 3 + WHEN pp.system_type_id IN (60, 122) THEN 2 + ELSE NULL END format_type, + CASE WHEN pf.boundary_value_on_right = 0 THEN ''>'' ELSE ''>='' END range_start_op, + prvs.value range_start_value, + CASE WHEN pf.boundary_value_on_right = 0 THEN ''<='' ELSE ''<'' END range_end_op, + prve.value range_end_value ' ELSE N' ' END + N' + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_row_groups rg + INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns c ON rg.object_id = c.object_id + INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partitions p ON rg.object_id = p.object_id AND rg.partition_number = p.partition_number + INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.index_columns ic on ic.column_id = c.column_id AND ic.object_id = c.object_id AND ic.index_id = p.index_id + LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_column_store_row_group_physical_stats phys ON rg.row_group_id = phys.row_group_id AND rg.object_id = phys.object_id AND rg.partition_number = phys.partition_number AND rg.index_id = phys.index_id ' + CASE WHEN @ShowPartitionRanges = 1 THEN N' + LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.indexes i ON i.object_id = rg.object_id AND i.index_id = rg.index_id + LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partition_schemes ps ON ps.data_space_id = i.data_space_id + LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partition_functions pf ON pf.function_id = ps.function_id + LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partition_parameters pp ON pp.function_id = pf.function_id + LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partition_range_values prvs ON prvs.function_id = pf.function_id AND prvs.boundary_id = p.partition_number - 1 + LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partition_range_values prve ON prve.function_id = pf.function_id AND prve.boundary_id = p.partition_number ' ELSE N' ' END + + N' LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_segments seg ON p.partition_id = seg.partition_id AND ic.index_column_id = seg.column_id AND rg.row_group_id = seg.segment_id + LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_dictionaries d ON p.hobt_id = d.hobt_id AND c.column_id = d.column_id AND seg.secondary_dictionary_id = d.dictionary_id + WHERE rg.object_id = @ObjectID + AND rg.state IN (1, 2, 3) + AND c.name IN ( ' + @ColumnListWithApostrophes + N')' + + CASE WHEN @ShowPartitionRanges = 1 THEN N' + ) AS y ' ELSE N' ' END + N' + ) AS x + PIVOT (MAX(details) FOR column_name IN ( ' + @ColumnList + N')) AS pivot1 + ORDER BY partition_number, row_group_id;'; + + IF @Debug = 1 + BEGIN + PRINT SUBSTRING(@dsql, 0, 4000); + PRINT SUBSTRING(@dsql, 4000, 8000); + PRINT SUBSTRING(@dsql, 8000, 12000); + PRINT SUBSTRING(@dsql, 12000, 16000); + PRINT SUBSTRING(@dsql, 16000, 20000); + PRINT SUBSTRING(@dsql, 20000, 24000); + PRINT SUBSTRING(@dsql, 24000, 28000); + PRINT SUBSTRING(@dsql, 28000, 32000); + PRINT SUBSTRING(@dsql, 32000, 36000); + PRINT SUBSTRING(@dsql, 36000, 40000); + END; + IF @dsql IS NULL + RAISERROR('@dsql is null',16,1); + ELSE + EXEC sp_executesql @dsql, N'@ObjectID INT', @ObjectID; + END + ELSE /* No columns were found for this object */ + BEGIN + SELECT N'No compressed columnstore rowgroups were found for this object.' AS Columnstore_Visualization + UNION ALL + SELECT N'SELECT * FROM ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_row_groups WHERE object_id = ' + CAST(@ObjectID AS NVARCHAR(100)); + END + RAISERROR(N'Done visualizing columnstore index contents.', 0,1) WITH NOWAIT; + END -DROP TABLE IF EXISTS #trace_flags; + IF @ShowColumnstoreOnly = 1 + RETURN; -CREATE TABLE #trace_flags -( - sql_handle VARBINARY(54), - global_trace_flags NVARCHAR(4000), - session_trace_flags NVARCHAR(4000), - INDEX tf_ix_ids CLUSTERED (sql_handle) -); +END; /* IF @TableName IS NOT NULL */ -DROP TABLE IF EXISTS #warning_results; -CREATE TABLE #warning_results -( - ID INT IDENTITY(1,1) PRIMARY KEY CLUSTERED, - CheckID INT, - Priority TINYINT, - FindingsGroup NVARCHAR(50), - Finding NVARCHAR(200), - URL NVARCHAR(200), - Details NVARCHAR(4000) -); -/*These next three tables hold information about implicit conversion and cached parameters */ -DROP TABLE IF EXISTS #stored_proc_info; -CREATE TABLE #stored_proc_info -( - sql_handle VARBINARY(64), - query_hash BINARY(8), - variable_name NVARCHAR(258), - variable_datatype NVARCHAR(258), - converted_column_name NVARCHAR(258), - compile_time_value NVARCHAR(258), - proc_name NVARCHAR(1000), - column_name NVARCHAR(4000), - converted_to NVARCHAR(258), - set_options NVARCHAR(1000) - INDEX tf_ix_ids CLUSTERED (sql_handle, query_hash) -); -DROP TABLE IF EXISTS #variable_info; -CREATE TABLE #variable_info -( - query_hash BINARY(8), - sql_handle VARBINARY(64), - proc_name NVARCHAR(1000), - variable_name NVARCHAR(258), - variable_datatype NVARCHAR(258), - compile_time_value NVARCHAR(258), - INDEX vif_ix_ids CLUSTERED (sql_handle, query_hash) -); -DROP TABLE IF EXISTS #conversion_info; +ELSE /* @TableName IS NULL, so we operate in normal mode 0/1/2/3/4 */ +BEGIN -CREATE TABLE #conversion_info -( - query_hash BINARY(8), - sql_handle VARBINARY(64), - proc_name NVARCHAR(128), - expression NVARCHAR(4000), - at_charindex AS CHARINDEX('@', expression), - bracket_charindex AS CHARINDEX(']', expression, CHARINDEX('@', expression)) - CHARINDEX('@', expression), - comma_charindex AS CHARINDEX(',', expression) + 1, - second_comma_charindex AS - CHARINDEX(',', expression, CHARINDEX(',', expression) + 1) - CHARINDEX(',', expression) - 1, - equal_charindex AS CHARINDEX('=', expression) + 1, - paren_charindex AS CHARINDEX('(', expression) + 1, - comma_paren_charindex AS - CHARINDEX(',', expression, CHARINDEX('(', expression) + 1) - CHARINDEX('(', expression) - 1, - convert_implicit_charindex AS CHARINDEX('=CONVERT_IMPLICIT', expression), - INDEX cif_ix_ids CLUSTERED (sql_handle, query_hash) -); +/* Validate and check table output params */ -/* These tables support the Missing Index details clickable*/ + /* Checks if @OutputServerName is populated with a valid linked server, and that the database name specified is valid */ + DECLARE @ValidOutputServer BIT; + DECLARE @ValidOutputLocation BIT; + DECLARE @LinkedServerDBCheck NVARCHAR(2000); + DECLARE @ValidLinkedServerDB INT; + DECLARE @tmpdbchk TABLE (cnt INT); + + IF @OutputServerName IS NOT NULL + BEGIN + IF (SUBSTRING(@OutputTableName, 2, 1) = '#') + BEGIN + RAISERROR('Due to the nature of temporary tables, outputting to a linked server requires a permanent table.', 16, 0); + END; + ELSE IF EXISTS (SELECT server_id FROM sys.servers WHERE QUOTENAME([name]) = @OutputServerName) + BEGIN + SET @LinkedServerDBCheck = 'SELECT 1 WHERE EXISTS (SELECT * FROM '+@OutputServerName+'.master.sys.databases WHERE QUOTENAME([name]) = '''+@OutputDatabaseName+''')'; + INSERT INTO @tmpdbchk EXEC sys.sp_executesql @LinkedServerDBCheck; + SET @ValidLinkedServerDB = (SELECT COUNT(*) FROM @tmpdbchk); + IF (@ValidLinkedServerDB > 0) + BEGIN + SET @ValidOutputServer = 1; + SET @ValidOutputLocation = 1; + END; + ELSE + RAISERROR('The specified database was not found on the output server', 16, 0); + END; + ELSE + BEGIN + RAISERROR('The specified output server was not found', 16, 0); + END; + END; + ELSE + BEGIN + IF (SUBSTRING(@OutputTableName, 2, 2) = '##') + BEGIN + SET @StringToExecute = N' IF (OBJECT_ID(''[tempdb].[dbo].@@@OutputTableName@@@'') IS NOT NULL) DROP TABLE @@@OutputTableName@@@'; + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); + EXEC(@StringToExecute); + + SET @OutputServerName = QUOTENAME(CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128))); + SET @OutputDatabaseName = '[tempdb]'; + SET @OutputSchemaName = '[dbo]'; + SET @ValidOutputLocation = 1; + END; + ELSE IF (SUBSTRING(@OutputTableName, 2, 1) = '#') + BEGIN + RAISERROR('Due to the nature of Dymamic SQL, only global (i.e. double pound (##)) temp tables are supported for @OutputTableName', 16, 0); + END; + ELSE IF @OutputDatabaseName IS NOT NULL + AND @OutputSchemaName IS NOT NULL + AND @OutputTableName IS NOT NULL + AND EXISTS ( SELECT * + FROM sys.databases + WHERE QUOTENAME([name]) = @OutputDatabaseName) + BEGIN + SET @ValidOutputLocation = 1; + SET @OutputServerName = QUOTENAME(CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128))); + END; + ELSE IF @OutputDatabaseName IS NOT NULL + AND @OutputSchemaName IS NOT NULL + AND @OutputTableName IS NOT NULL + AND NOT EXISTS ( SELECT * + FROM sys.databases + WHERE QUOTENAME([name]) = @OutputDatabaseName) + BEGIN + RAISERROR('The specified output database was not found on this server', 16, 0); + END; + ELSE + BEGIN + SET @ValidOutputLocation = 0; + END; + END; + + IF (@ValidOutputLocation = 0 AND @OutputType = 'NONE') + BEGIN + RAISERROR('Invalid output location and no output asked',12,1); + RETURN; + END; + + /* @OutputTableName lets us export the results to a permanent table */ + DECLARE @RunID UNIQUEIDENTIFIER; + SET @RunID = NEWID(); -DROP TABLE IF EXISTS #missing_index_xml; + DECLARE @TableExists BIT; + DECLARE @SchemaExists BIT; -CREATE TABLE #missing_index_xml -( - query_hash BINARY(8), - sql_handle VARBINARY(64), - impact FLOAT, - index_xml XML, - INDEX mix_ix_ids CLUSTERED (sql_handle, query_hash) -); + DECLARE @TableExistsSql NVARCHAR(MAX); -DROP TABLE IF EXISTS #missing_index_schema; + IF (@ValidOutputLocation = 1 AND COALESCE(@OutputServerName, @OutputDatabaseName, @OutputSchemaName, @OutputTableName) IS NOT NULL) + BEGIN + SET @StringToExecute = + N'SET @SchemaExists = 0; + SET @TableExists = 0; + IF EXISTS(SELECT * FROM @@@OutputServerName@@@.@@@OutputDatabaseName@@@.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''@@@OutputSchemaName@@@'') + SET @SchemaExists = 1 + IF EXISTS (SELECT * FROM @@@OutputServerName@@@.@@@OutputDatabaseName@@@.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''@@@OutputSchemaName@@@'' AND QUOTENAME(TABLE_NAME) = ''@@@OutputTableName@@@'') + BEGIN + SET @TableExists = 1 + IF NOT EXISTS(SELECT * FROM @@@OutputServerName@@@.@@@OutputDatabaseName@@@.INFORMATION_SCHEMA.COLUMNS WHERE QUOTENAME(TABLE_SCHEMA) = ''@@@OutputSchemaName@@@'' + AND QUOTENAME(TABLE_NAME) = ''@@@OutputTableName@@@'' AND QUOTENAME(COLUMN_NAME) = ''[total_forwarded_fetch_count]'') + EXEC @@@OutputServerName@@@.@@@OutputDatabaseName@@@.dbo.sp_executesql N''ALTER TABLE @@@OutputSchemaName@@@.@@@OutputTableName@@@ ADD [total_forwarded_fetch_count] BIGINT'' + END'; + + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputServerName@@@', @OutputServerName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputSchemaName@@@', @OutputSchemaName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); + + EXEC sp_executesql @StringToExecute, N'@TableExists BIT OUTPUT, @SchemaExists BIT OUTPUT', @TableExists OUTPUT, @SchemaExists OUTPUT; -CREATE TABLE #missing_index_schema -( - query_hash BINARY(8), - sql_handle VARBINARY(64), - impact FLOAT, - database_name NVARCHAR(128), - schema_name NVARCHAR(128), - table_name NVARCHAR(128), - index_xml XML, - INDEX mis_ix_ids CLUSTERED (sql_handle, query_hash) -); + SET @TableExistsSql = + N'IF EXISTS(SELECT * FROM @@@OutputServerName@@@.@@@OutputDatabaseName@@@.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''@@@OutputSchemaName@@@'') + AND NOT EXISTS (SELECT * FROM @@@OutputServerName@@@.@@@OutputDatabaseName@@@.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''@@@OutputSchemaName@@@'' AND QUOTENAME(TABLE_NAME) = ''@@@OutputTableName@@@'') + SET @TableExists = 0 + ELSE + SET @TableExists = 1'; + + SET @TableExistsSql = REPLACE(@TableExistsSql, '@@@OutputServerName@@@', @OutputServerName); + SET @TableExistsSql = REPLACE(@TableExistsSql, '@@@OutputDatabaseName@@@', @OutputDatabaseName); + SET @TableExistsSql = REPLACE(@TableExistsSql, '@@@OutputSchemaName@@@', @OutputSchemaName); + SET @TableExistsSql = REPLACE(@TableExistsSql, '@@@OutputTableName@@@', @OutputTableName); -DROP TABLE IF EXISTS #missing_index_usage; + END -CREATE TABLE #missing_index_usage -( - query_hash BINARY(8), - sql_handle VARBINARY(64), - impact FLOAT, - database_name NVARCHAR(128), - schema_name NVARCHAR(128), - table_name NVARCHAR(128), - usage NVARCHAR(128), - index_xml XML, - INDEX miu_ix_ids CLUSTERED (sql_handle, query_hash) -); -DROP TABLE IF EXISTS #missing_index_detail; -CREATE TABLE #missing_index_detail -( - query_hash BINARY(8), - sql_handle VARBINARY(64), - impact FLOAT, - database_name NVARCHAR(128), - schema_name NVARCHAR(128), - table_name NVARCHAR(128), - usage NVARCHAR(128), - column_name NVARCHAR(128), - INDEX mid_ix_ids CLUSTERED (sql_handle, query_hash) -); + IF @Mode IN (0, 4) /* DIAGNOSE */ + BEGIN; + IF @Mode IN (0, 4) /* DIAGNOSE priorities 1-100 */ + BEGIN; + RAISERROR(N'@Mode=0 or 4, running rules for priorities 1-100.', 0,1) WITH NOWAIT; -DROP TABLE IF EXISTS #missing_index_pretty; + ---------------------------------------- + --Multiple Index Personalities: Check_id 0-10 + ---------------------------------------- + RAISERROR('check_id 1: Duplicate keys', 0,1) WITH NOWAIT; + WITH duplicate_indexes + AS ( SELECT [object_id], key_column_names, database_id, [schema_name] + FROM #IndexSanity AS ip + WHERE index_type IN (1,2) /* Clustered, NC only*/ + AND is_hypothetical = 0 + AND is_disabled = 0 + AND is_primary_key = 0 + AND EXISTS ( + SELECT 1/0 + FROM #IndexSanitySize ips + WHERE ip.index_sanity_id = ips.index_sanity_id + AND ip.database_id = ips.database_id + AND ip.schema_name = ips.schema_name + AND ips.total_reserved_MB >= CASE + WHEN (@GetAllDatabases = 1 OR @Mode = 0) + THEN @ThresholdMB + ELSE ips.total_reserved_MB + END + ) + GROUP BY [object_id], key_column_names, database_id, [schema_name] + HAVING COUNT(*) > 1) + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 1 AS check_id, + ip.index_sanity_id, + 20 AS Priority, + 'Multiple Index Personalities' AS findings_group, + 'Duplicate keys' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/duplicateindex' AS URL, + N'Index Name: ' + ip.index_name + N' Table Name: ' + ip.db_schema_object_name AS details, + ip.index_definition, + ip.secret_columns, + ip.index_usage_summary, + ips.index_size_summary + FROM duplicate_indexes di + JOIN #IndexSanity ip ON di.[object_id] = ip.[object_id] + AND ip.database_id = di.database_id + AND ip.[schema_name] = di.[schema_name] + AND di.key_column_names = ip.key_column_names + JOIN #IndexSanitySize ips ON ip.index_sanity_id = ips.index_sanity_id + AND ip.database_id = ips.database_id + AND ip.schema_name = ips.schema_name + /* WHERE clause limits to only @ThresholdMB or larger duplicate indexes when getting all databases or using PainRelief mode */ + WHERE ips.total_reserved_MB >= CASE WHEN (@GetAllDatabases = 1 OR @Mode = 0) THEN @ThresholdMB ELSE ips.total_reserved_MB END + AND ip.is_primary_key = 0 + ORDER BY ips.total_rows DESC, ip.[schema_name], ip.[object_name], ip.key_column_names_with_sort_order + OPTION ( RECOMPILE ); -CREATE TABLE #missing_index_pretty -( - query_hash BINARY(8), - sql_handle VARBINARY(64), - impact FLOAT, - database_name NVARCHAR(128), - schema_name NVARCHAR(128), - table_name NVARCHAR(128), - equality NVARCHAR(MAX), - inequality NVARCHAR(MAX), - [include] NVARCHAR(MAX), - is_spool BIT, - details AS N'/* ' - + CHAR(10) - + CASE is_spool - WHEN 0 - THEN N'The Query Processor estimates that implementing the ' - ELSE N'We estimate that implementing the ' - END - + CONVERT(NVARCHAR(30), impact) - + '%.' - + CHAR(10) - + N'*/' - + CHAR(10) + CHAR(13) - + N'/* ' - + CHAR(10) - + N'USE ' - + database_name - + CHAR(10) - + N'GO' - + CHAR(10) + CHAR(13) - + N'CREATE NONCLUSTERED INDEX ix_' - + ISNULL(REPLACE(REPLACE(REPLACE(equality,'[', ''), ']', ''), ', ', '_'), '') - + ISNULL(REPLACE(REPLACE(REPLACE(inequality,'[', ''), ']', ''), ', ', '_'), '') - + CASE WHEN [include] IS NOT NULL THEN + N'_Includes' ELSE N'' END - + CHAR(10) - + N' ON ' - + schema_name - + N'.' - + table_name - + N' (' + - + CASE WHEN equality IS NOT NULL - THEN equality - + CASE WHEN inequality IS NOT NULL - THEN N', ' + inequality - ELSE N'' - END - ELSE inequality - END - + N')' - + CHAR(10) - + CASE WHEN include IS NOT NULL - THEN N'INCLUDE (' + include + N') WITH (FILLFACTOR=100, ONLINE=?, SORT_IN_TEMPDB=?, DATA_COMPRESSION=?);' - ELSE N' WITH (FILLFACTOR=100, ONLINE=?, SORT_IN_TEMPDB=?, DATA_COMPRESSION=?);' - END - + CHAR(10) - + N'GO' - + CHAR(10) - + N'*/', - INDEX mip_ix_ids CLUSTERED (sql_handle, query_hash) -); + RAISERROR('check_id 2: Keys w/ identical leading columns.', 0,1) WITH NOWAIT; + WITH borderline_duplicate_indexes + AS ( SELECT DISTINCT database_id, [object_id], first_key_column_name, key_column_names, + COUNT([object_id]) OVER ( PARTITION BY database_id, [object_id], first_key_column_name ) AS number_dupes + FROM #IndexSanity + WHERE index_type IN (1,2) /* Clustered, NC only*/ + AND is_hypothetical=0 + AND is_disabled=0 + AND is_primary_key = 0) + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 2 AS check_id, + ip.index_sanity_id, + 30 AS Priority, + 'Multiple Index Personalities' AS findings_group, + 'Borderline duplicate keys' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/duplicateindex' AS URL, + ip.db_schema_object_indexid AS details, + ip.index_definition, + ip.secret_columns, + ip.index_usage_summary, + ips.index_size_summary + FROM #IndexSanity AS ip + JOIN #IndexSanitySize ips ON ip.index_sanity_id = ips.index_sanity_id + WHERE EXISTS ( + SELECT di.[object_id] + FROM borderline_duplicate_indexes AS di + WHERE di.[object_id] = ip.[object_id] AND + di.database_id = ip.database_id AND + di.first_key_column_name = ip.first_key_column_name AND + di.key_column_names <> ip.key_column_names AND + di.number_dupes > 1 + ) + AND ip.is_primary_key = 0 + ORDER BY ips.total_rows DESC, ip.[schema_name], ip.[object_name], ip.key_column_names, ip.include_column_names + OPTION ( RECOMPILE ); -DROP TABLE IF EXISTS #index_spool_ugly; + ---------------------------------------- + --Aggressive Indexes: Check_id 10-19 + ---------------------------------------- -CREATE TABLE #index_spool_ugly -( - query_hash BINARY(8), - sql_handle VARBINARY(64), - impact FLOAT, - database_name NVARCHAR(128), - schema_name NVARCHAR(128), - table_name NVARCHAR(128), - equality NVARCHAR(MAX), - inequality NVARCHAR(MAX), - [include] NVARCHAR(MAX), - INDEX isu_ix_ids CLUSTERED (sql_handle, query_hash) -); + RAISERROR(N'check_id 11: Total lock wait time > 5 minutes (row + page)', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 11 AS check_id, + i.index_sanity_id, + 70 AS Priority, + N'Aggressive ' + + CASE COALESCE((SELECT SUM(1) + FROM #IndexSanity iMe + INNER JOIN #IndexSanity iOthers + ON iMe.database_id = iOthers.database_id + AND iMe.object_id = iOthers.object_id + AND iOthers.index_id > 1 + WHERE i.index_sanity_id = iMe.index_sanity_id + AND iOthers.is_hypothetical = 0 + AND iOthers.is_disabled = 0 + ), 0) + WHEN 0 THEN N'Under-Indexing' + WHEN 1 THEN N'Under-Indexing' + WHEN 2 THEN N'Under-Indexing' + WHEN 3 THEN N'Under-Indexing' + WHEN 4 THEN N'Indexes' + WHEN 5 THEN N'Indexes' + WHEN 6 THEN N'Indexes' + WHEN 7 THEN N'Indexes' + WHEN 8 THEN N'Indexes' + WHEN 9 THEN N'Indexes' + ELSE N'Over-Indexing' + END AS findings_group, + N'Total lock wait time > 5 minutes (row + page)' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/AggressiveIndexes' AS URL, + (i.db_schema_object_indexid + N': ' + + sz.index_lock_wait_summary + N' NC indexes on table: ') COLLATE DATABASE_DEFAULT + + CAST(COALESCE((SELECT SUM(1) + FROM #IndexSanity iMe + INNER JOIN #IndexSanity iOthers + ON iMe.database_id = iOthers.database_id + AND iMe.object_id = iOthers.object_id + AND iOthers.index_id > 1 + WHERE i.index_sanity_id = iMe.index_sanity_id + AND iOthers.is_hypothetical = 0 + AND iOthers.is_disabled = 0 + ), 0) + AS NVARCHAR(30)) AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + sz.index_size_summary + FROM #IndexSanity AS i + JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id + WHERE (total_row_lock_wait_in_ms + total_page_lock_wait_in_ms) > 300000 + GROUP BY i.index_sanity_id, [database_name], i.db_schema_object_indexid, sz.index_lock_wait_summary, i.index_definition, i.secret_columns, i.index_usage_summary, sz.index_size_summary, sz.index_sanity_id + ORDER BY SUM(total_row_lock_wait_in_ms + total_page_lock_wait_in_ms) DESC, 4, [database_name], 8 + OPTION ( RECOMPILE ); -/*Sets up WHERE clause that gets used quite a bit*/ ---Date stuff ---If they're both NULL, we'll just look at the last 7 days -IF (@StartDate IS NULL AND @EndDate IS NULL) - BEGIN - RAISERROR(N'@StartDate and @EndDate are NULL, checking last 7 days', 0, 1) WITH NOWAIT; - SET @sql_where += N' AND qsrs.last_execution_time >= DATEADD(DAY, -7, DATEDIFF(DAY, 0, SYSDATETIME() )) - '; - END; + ---------------------------------------- + --Index Hoarder: Check_id 20-29 + ---------------------------------------- + RAISERROR(N'check_id 20: >= 10 NC indexes on any given table. Yes, 10 is an arbitrary number.', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 20 AS check_id, + MAX(i.index_sanity_id) AS index_sanity_id, + 10 AS Priority, + 'Index Hoarder' AS findings_group, + 'Many NC Indexes on a Single Table' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/IndexHoarder' AS URL, + CAST (COUNT(*) AS NVARCHAR(30)) + ' NC indexes on ' + i.db_schema_object_name AS details, + i.db_schema_object_name + ' (' + CAST (COUNT(*) AS NVARCHAR(30)) + ' indexes)' AS index_definition, + '' AS secret_columns, + REPLACE(CONVERT(NVARCHAR(30),CAST(SUM(total_reads) AS MONEY), 1), N'.00', N'') + N' reads (ALL); ' + + REPLACE(CONVERT(NVARCHAR(30),CAST(SUM(user_updates) AS MONEY), 1), N'.00', N'') + N' writes (ALL); ', + REPLACE(CONVERT(NVARCHAR(30),CAST(MAX(total_rows) AS MONEY), 1), N'.00', N'') + N' rows (MAX)' + + CASE WHEN SUM(total_reserved_MB) > 1024 THEN + N'; ' + CAST(CAST(SUM(total_reserved_MB)/1024. AS NUMERIC(29,1)) AS NVARCHAR(30)) + 'GB (ALL)' + WHEN SUM(total_reserved_MB) > 0 THEN + N'; ' + CAST(CAST(SUM(total_reserved_MB) AS NUMERIC(29,1)) AS NVARCHAR(30)) + 'MB (ALL)' + ELSE '' + END AS index_size_summary + FROM #IndexSanity i + JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id + WHERE index_id NOT IN ( 0, 1 ) + GROUP BY db_schema_object_name, [i].[database_name] + HAVING COUNT(*) >= 10 + ORDER BY i.db_schema_object_name DESC + OPTION ( RECOMPILE ); ---Hey, that's nice of me -IF @StartDate IS NOT NULL - BEGIN - RAISERROR(N'Setting start date filter', 0, 1) WITH NOWAIT; - SET @sql_where += N' AND qsrs.last_execution_time >= @sp_StartDate - '; - END; + RAISERROR(N'check_id 22: NC indexes with 0 reads. (Borderline) and >= 10,000 writes', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 22 AS check_id, + i.index_sanity_id, + 10 AS Priority, + N'Index Hoarder' AS findings_group, + N'Unused NC Index with High Writes' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/IndexHoarder' AS URL, + N'Reads: 0,' + + N' Writes: ' + + REPLACE(CONVERT(NVARCHAR(30), CAST((i.user_updates) AS MONEY), 1), N'.00', N'') + + N' on: ' + + i.db_schema_object_indexid + AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + sz.index_size_summary + FROM #IndexSanity AS i + JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id + WHERE i.total_reads=0 + AND i.user_updates >= 10000 + 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 + AND @Filter <> 1 /* 1 = "ignore unused */ + ORDER BY i.db_schema_object_indexid + OPTION ( RECOMPILE ); ---Alright, sensible -IF @EndDate IS NOT NULL - BEGIN - RAISERROR(N'Setting end date filter', 0, 1) WITH NOWAIT; - SET @sql_where += N' AND qsrs.last_execution_time < @sp_EndDate - '; - END; ---C'mon, why would you do that? -IF (@StartDate IS NULL AND @EndDate IS NOT NULL) - BEGIN - RAISERROR(N'Setting reasonable start date filter', 0, 1) WITH NOWAIT; - SET @sql_where += N' AND qsrs.last_execution_time >= DATEADD(DAY, -7, @sp_EndDate) - '; - END; + RAISERROR(N'check_id 34: Filtered index definition columns not in index definition', 0,1) WITH NOWAIT; + + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 34 AS check_id, + i.index_sanity_id, + 80 AS Priority, + N'Abnormal Psychology' AS findings_group, + N'Filter Columns Not In Index Definition' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/IndexFeatures' AS URL, + N'The index ' + + QUOTENAME(i.index_name) + + N' on [' + + i.db_schema_object_name + + N'] has a filter on [' + + i.filter_definition + + N'] but is missing [' + + LTRIM(i.filter_columns_not_in_index) + + N'] from the index definition.' + AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + sz.index_size_summary + FROM #IndexSanity i + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + WHERE i.filter_columns_not_in_index IS NOT NULL + ORDER BY i.db_schema_object_indexid + OPTION ( RECOMPILE ); + + ---------------------------------------- + --Self Loathing Indexes : Check_id 40-49 + ---------------------------------------- + + RAISERROR(N'check_id 40: Fillfactor in nonclustered 80 percent or less', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 40 AS check_id, + i.index_sanity_id, + 100 AS Priority, + N'Self Loathing Indexes' AS findings_group, + N'Low Fill Factor on Nonclustered Index' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/SelfLoathing' AS URL, + CAST(fill_factor AS NVARCHAR(10)) + N'% fill factor on ' + db_schema_object_indexid + N'. '+ + CASE WHEN (last_user_update IS NULL OR user_updates < 1) + THEN N'No writes have been made.' + ELSE + N'Last write was ' + CONVERT(NVARCHAR(16),last_user_update,121) + N' and ' + + CAST(user_updates AS NVARCHAR(25)) + N' updates have been made.' + END + AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + sz.index_size_summary + FROM #IndexSanity AS i + JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id + WHERE index_id > 1 + AND fill_factor BETWEEN 1 AND 80 OPTION ( RECOMPILE ); ---Jeez, abusive -IF (@StartDate IS NOT NULL AND @EndDate IS NULL) - BEGIN - RAISERROR(N'Setting reasonable end date filter', 0, 1) WITH NOWAIT; - SET @sql_where += N' AND qsrs.last_execution_time < DATEADD(DAY, 7, @sp_StartDate) - '; - END; + RAISERROR(N'check_id 40: Fillfactor in clustered 80 percent or less', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 40 AS check_id, + i.index_sanity_id, + 100 AS Priority, + N'Self Loathing Indexes' AS findings_group, + N'Low Fill Factor on Clustered Index' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/SelfLoathing' AS URL, + N'Fill factor on ' + db_schema_object_indexid + N' is ' + CAST(fill_factor AS NVARCHAR(10)) + N'%. '+ + CASE WHEN (last_user_update IS NULL OR user_updates < 1) + THEN N'No writes have been made.' + ELSE + N'Last write was ' + CONVERT(NVARCHAR(16),last_user_update,121) + N' and ' + + CAST(user_updates AS NVARCHAR(25)) + N' updates have been made.' + END + AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + sz.index_size_summary + FROM #IndexSanity AS i + JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id + WHERE index_id = 1 + AND fill_factor BETWEEN 1 AND 80 OPTION ( RECOMPILE ); ---I care about minimum execution counts -IF @MinimumExecutionCount IS NOT NULL - BEGIN - RAISERROR(N'Setting execution filter', 0, 1) WITH NOWAIT; - SET @sql_where += N' AND qsrs.count_executions >= @sp_MinimumExecutionCount - '; - END; ---You care about stored proc names -IF @StoredProcName IS NOT NULL - BEGIN + RAISERROR(N'check_id 43: Heaps with forwarded records', 0,1) WITH NOWAIT; + WITH heaps_cte + AS ( SELECT [object_id], + [database_id], + [schema_name], + SUM(forwarded_fetch_count) AS forwarded_fetch_count, + SUM(leaf_delete_count) AS leaf_delete_count + FROM #IndexPartitionSanity + GROUP BY [object_id], + [database_id], + [schema_name] + HAVING SUM(forwarded_fetch_count) > 0) + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 43 AS check_id, + i.index_sanity_id, + 100 AS Priority, + N'Self Loathing Indexes' AS findings_group, + N'Heaps with Forwarded Fetches' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/SelfLoathing' AS URL, + CASE WHEN h.forwarded_fetch_count >= 922337203685477 THEN '>= 922,337,203,685,477' + WHEN @DaysUptime < 1 THEN CAST(h.forwarded_fetch_count AS NVARCHAR(256)) + N' forwarded fetches against heap: ' + db_schema_object_indexid + ELSE REPLACE(CONVERT(NVARCHAR(256),CAST(CAST( + (h.forwarded_fetch_count /*/@DaysUptime */) + AS BIGINT) AS MONEY), 1), '.00', '') + END + N' forwarded fetches per day against heap: ' + + db_schema_object_indexid AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + sz.index_size_summary + FROM #IndexSanity i + JOIN heaps_cte h ON i.[object_id] = h.[object_id] + AND i.[database_id] = h.[database_id] + AND i.[schema_name] = h.[schema_name] + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + WHERE i.index_id = 0 + AND h.forwarded_fetch_count / @DaysUptime > 1000 + AND sz.total_reserved_MB >= CASE WHEN NOT (@GetAllDatabases = 1 OR @Mode = 4) THEN @ThresholdMB ELSE sz.total_reserved_MB END + OPTION ( RECOMPILE ); - IF (@pspo_enabled = 1) - BEGIN - RAISERROR(N'Setting stored proc filter, PSPO enabled', 0, 1) WITH NOWAIT; - /* If PSPO is enabled, the object_id for a variant query would be 0. To include it, we check whether the object_id = 0 query - is a variant query, and whether it's parent query belongs to @sp_StoredProcName. */ - SET @sql_where += N' AND (object_name(qsq.object_id, DB_ID(' + QUOTENAME(@DatabaseName, '''') + N')) = @sp_StoredProcName - OR (qsq.object_id = 0 - AND EXISTS( - SELECT 1 - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_variant vr - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query pqsq - ON pqsq.query_id = vr.parent_query_id - WHERE - vr.query_variant_query_id = qsq.query_id - AND object_name(pqsq.object_id, DB_ID(' + QUOTENAME(@DatabaseName, '''') + N')) = @sp_StoredProcName - ) - )) - '; - END - ELSE - BEGIN - RAISERROR(N'Setting stored proc filter', 0, 1) WITH NOWAIT; - SET @sql_where += N' AND object_name(qsq.object_id, DB_ID(' + QUOTENAME(@DatabaseName, '''') + N')) = @sp_StoredProcName - '; - END - END; + RAISERROR(N'check_id 44: Large Heaps with reads or writes.', 0,1) WITH NOWAIT; + WITH heaps_cte + AS ( SELECT [object_id], + [database_id], + [schema_name], + SUM(forwarded_fetch_count) AS forwarded_fetch_count, + SUM(leaf_delete_count) AS leaf_delete_count + FROM #IndexPartitionSanity + GROUP BY [object_id], + [database_id], + [schema_name] + HAVING SUM(forwarded_fetch_count) > 0 + OR SUM(leaf_delete_count) > 0) + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 44 AS check_id, + i.index_sanity_id, + 100 AS Priority, + N'Self Loathing Indexes' AS findings_group, + N'Large Active Heap' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/SelfLoathing' AS URL, + N'Should this table be a heap? ' + db_schema_object_indexid AS details, + i.index_definition, + 'N/A' AS secret_columns, + i.index_usage_summary, + sz.index_size_summary + FROM #IndexSanity i + LEFT JOIN heaps_cte h ON i.[object_id] = h.[object_id] + AND i.[database_id] = h.[database_id] + AND i.[schema_name] = h.[schema_name] + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + WHERE i.index_id = 0 + AND (i.total_reads > 0 OR i.user_updates > 0) + AND sz.total_rows >= 100000 + AND h.[object_id] IS NULL /*don't duplicate the prior check.*/ + OPTION ( RECOMPILE ); ---I will always love you, but hopefully this query will eventually end -IF @DurationFilter IS NOT NULL - BEGIN - RAISERROR(N'Setting duration filter', 0, 1) WITH NOWAIT; - SET @sql_where += N' AND (qsrs.avg_duration / 1000.) >= @sp_MinDuration - '; - END; + RAISERROR(N'check_id 45: Medium Heaps with reads or writes.', 0,1) WITH NOWAIT; + WITH heaps_cte + AS ( SELECT [object_id], + [database_id], + [schema_name], + SUM(forwarded_fetch_count) AS forwarded_fetch_count, + SUM(leaf_delete_count) AS leaf_delete_count + FROM #IndexPartitionSanity + GROUP BY [object_id], + [database_id], + [schema_name] + HAVING SUM(forwarded_fetch_count) > 0 + OR SUM(leaf_delete_count) > 0) + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 45 AS check_id, + i.index_sanity_id, + 100 AS Priority, + N'Self Loathing Indexes' AS findings_group, + N'Medium Active heap' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/SelfLoathing' AS URL, + N'Should this table be a heap? ' + db_schema_object_indexid AS details, + i.index_definition, + 'N/A' AS secret_columns, + i.index_usage_summary, + sz.index_size_summary + FROM #IndexSanity i + LEFT JOIN heaps_cte h ON i.[object_id] = h.[object_id] + AND i.[database_id] = h.[database_id] + AND i.[schema_name] = h.[schema_name] + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + WHERE i.index_id = 0 + AND + (i.total_reads > 0 OR i.user_updates > 0) + AND sz.total_rows >= 10000 AND sz.total_rows < 100000 + AND h.[object_id] IS NULL /*don't duplicate the prior check.*/ + OPTION ( RECOMPILE ); ---I don't know why you'd go looking for failed queries, but hey -IF (@Failed = 0 OR @Failed IS NULL) - BEGIN - RAISERROR(N'Setting failed query filter to 0', 0, 1) WITH NOWAIT; - SET @sql_where += N' AND qsrs.execution_type = 0 - '; - END; -IF (@Failed = 1) - BEGIN - RAISERROR(N'Setting failed query filter to 3, 4', 0, 1) WITH NOWAIT; - SET @sql_where += N' AND qsrs.execution_type IN (3, 4) - '; - END; + RAISERROR(N'check_id 46: Small Heaps with reads or writes.', 0,1) WITH NOWAIT; + WITH heaps_cte + AS ( SELECT [object_id], + [database_id], + [schema_name], + SUM(forwarded_fetch_count) AS forwarded_fetch_count, + SUM(leaf_delete_count) AS leaf_delete_count + FROM #IndexPartitionSanity + GROUP BY [object_id], + [database_id], + [schema_name] + HAVING SUM(forwarded_fetch_count) > 0 + OR SUM(leaf_delete_count) > 0) + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 46 AS check_id, + i.index_sanity_id, + 100 AS Priority, + N'Self Loathing Indexes' AS findings_group, + N'Small Active heap' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/SelfLoathing' AS URL, + N'Should this table be a heap? ' + db_schema_object_indexid AS details, + i.index_definition, + 'N/A' AS secret_columns, + i.index_usage_summary, + sz.index_size_summary + FROM #IndexSanity i + LEFT JOIN heaps_cte h ON i.[object_id] = h.[object_id] + AND i.[database_id] = h.[database_id] + AND i.[schema_name] = h.[schema_name] + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + WHERE i.index_id = 0 + AND + (i.total_reads > 0 OR i.user_updates > 0) + AND sz.total_rows < 10000 + AND h.[object_id] IS NULL /*don't duplicate the prior check.*/ + OPTION ( RECOMPILE ); -/*Filtering for plan_id or query_id*/ -IF (@PlanIdFilter IS NOT NULL) - BEGIN - RAISERROR(N'Setting plan_id filter', 0, 1) WITH NOWAIT; - SET @sql_where += N' AND qsp.plan_id = @sp_PlanIdFilter - '; - END; + RAISERROR(N'check_id 47: Heap with a Nonclustered Primary Key', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 47 AS check_id, + i.index_sanity_id, + 100 AS Priority, + N'Self Loathing Indexes' AS findings_group, + N'Heap with a Nonclustered Primary Key' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/SelfLoathing' AS URL, + db_schema_object_indexid + N' is a HEAP with a Nonclustered Primary Key' AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + sz.index_size_summary + FROM #IndexSanity i + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + WHERE i.index_type = 2 AND i.is_primary_key = 1 + AND EXISTS + ( + SELECT 1/0 + FROM #IndexSanity AS isa + WHERE i.database_id = isa.database_id + AND i.object_id = isa.object_id + AND isa.index_id = 0 + ) + OPTION ( RECOMPILE ); -IF (@QueryIdFilter IS NOT NULL) - BEGIN - RAISERROR(N'Setting query_id filter', 0, 1) WITH NOWAIT; - SET @sql_where += N' AND qsq.query_id = @sp_QueryIdFilter - '; - END; + RAISERROR(N'check_id 48: Nonclustered indexes with a bad read to write ratio', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 48 AS check_id, + i.index_sanity_id, + 100 AS Priority, + N'Index Hoarder' AS findings_group, + N'NC index with High Writes:Reads' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/IndexHoarder' AS URL, + N'Reads: ' + + REPLACE(CONVERT(NVARCHAR(30), CAST((i.total_reads) AS MONEY), 1), N'.00', N'') + + N' Writes: ' + + REPLACE(CONVERT(NVARCHAR(30), CAST((i.user_updates) AS MONEY), 1), N'.00', N'') + + N' on: ' + + i.db_schema_object_indexid AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + sz.index_size_summary + FROM #IndexSanity i + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + WHERE i.total_reads > 0 /*Not totally unused*/ + AND i.user_updates >= 10000 /*Decent write activity*/ + AND i.total_reads < 10000 + AND ((i.total_reads * 10) < i.user_updates) /*10x more writes than reads*/ + 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 + ORDER BY i.db_schema_object_indexid + OPTION ( RECOMPILE ); -IF @Debug = 1 - RAISERROR(N'Starting WHERE clause:', 0, 1) WITH NOWAIT; - PRINT @sql_where; + ---------------------------------------- + --Indexaphobia + --Missing indexes with value >= 5 million: : Check_id 50-59 + ---------------------------------------- + RAISERROR(N'check_id 50: Indexaphobia.', 0,1) WITH NOWAIT; + WITH index_size_cte + AS ( SELECT i.database_id, + i.schema_name, + i.[object_id], + MAX(i.index_sanity_id) AS index_sanity_id, + ISNULL(NULLIF(MAX(DATEDIFF(DAY, i.create_date, SYSDATETIME())), 0), 1) AS create_days, + ISNULL ( + CAST(SUM(CASE WHEN index_id NOT IN (0,1) THEN 1 ELSE 0 END) + AS NVARCHAR(30))+ N' NC indexes exist (' + + CASE WHEN SUM(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) > 1024 + THEN CAST(CAST(SUM(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END )/1024. -IF @sql_where IS NULL - BEGIN - RAISERROR(N'@sql_where is NULL', 0, 1) WITH NOWAIT; - RETURN; - END; + AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'GB); ' + ELSE CAST(SUM(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) + AS NVARCHAR(30)) + N'MB); ' + END + + CASE WHEN MAX(sz.[total_rows]) >= 922337203685477 THEN '>= 922,337,203,685,477' + ELSE REPLACE(CONVERT(NVARCHAR(30),CAST(MAX(sz.[total_rows]) AS MONEY), 1), '.00', '') + END + + + N' Estimated Rows;' + ,N'') AS index_size_summary + FROM #IndexSanity AS i + LEFT JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id AND i.database_id = sz.database_id + WHERE i.is_hypothetical = 0 + AND i.is_disabled = 0 + GROUP BY i.database_id, i.schema_name, i.[object_id]) + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + index_usage_summary, index_size_summary, create_tsql, more_info, sample_query_plan ) + + SELECT check_id, t.index_sanity_id, t.check_id, t.findings_group, t.finding, t.[Database Name], t.URL, t.details, t.[definition], + index_estimated_impact, t.index_size_summary, create_tsql, more_info, sample_query_plan + FROM + ( + SELECT ROW_NUMBER() OVER (ORDER BY magic_benefit_number DESC) AS rownum, + 50 AS check_id, + sz.index_sanity_id, + 40 AS Priority, + N'Indexaphobia' AS findings_group, + N'High Value Missing Index' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/Indexaphobia' AS URL, + mi.[statement] + + N' Est. benefit per day: ' + + CASE WHEN magic_benefit_number >= 922337203685477 THEN '>= 922,337,203,685,477' + ELSE REPLACE(CONVERT(NVARCHAR(256),CAST(CAST( + (magic_benefit_number/@DaysUptime) + AS BIGINT) AS MONEY), 1), '.00', '') + END AS details, + missing_index_details AS [definition], + index_estimated_impact, + sz.index_size_summary, + mi.create_tsql, + mi.more_info, + magic_benefit_number, + mi.is_low, + mi.sample_query_plan + FROM #MissingIndexes mi + LEFT JOIN index_size_cte sz ON mi.[object_id] = sz.object_id + AND mi.database_id = sz.database_id + AND mi.schema_name = sz.schema_name + /* Minimum benefit threshold = 100k/day of uptime OR since table creation date, whichever is lower*/ + WHERE @ShowAllMissingIndexRequests=1 + OR ( @Mode = 4 AND (magic_benefit_number / CASE WHEN sz.create_days < @DaysUptime THEN sz.create_days ELSE @DaysUptime END) >= 100000 ) + OR (magic_benefit_number / CASE WHEN sz.create_days < @DaysUptime THEN sz.create_days ELSE @DaysUptime END) >= 100000 + ) AS t + WHERE t.rownum <= CASE WHEN (@Mode <> 4) THEN 20 ELSE t.rownum END + ORDER BY magic_benefit_number DESC + OPTION ( RECOMPILE ); -IF (@ExportToExcel = 1 OR @SkipXML = 1) - BEGIN - RAISERROR(N'Exporting to Excel or skipping XML, hiding summary', 0, 1) WITH NOWAIT; - SET @HideSummary = 1; - END; -IF @StoredProcName IS NOT NULL - BEGIN - - DECLARE @sql NVARCHAR(MAX); - DECLARE @out INT; - DECLARE @proc_params NVARCHAR(MAX) = N'@sp_StartDate DATETIME2, @sp_EndDate DATETIME2, @sp_MinimumExecutionCount INT, @sp_MinDuration INT, @sp_StoredProcName NVARCHAR(128), @sp_PlanIdFilter INT, @sp_QueryIdFilter INT, @i_out INT OUTPUT'; - - - SET @sql = N'SELECT @i_out = COUNT(*) - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp - ON qsp.plan_id = qsrs.plan_id - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq - ON qsq.query_id = qsp.query_id - WHERE 1 = 1 - AND qsq.is_internal_query = 0 - AND qsp.query_plan IS NOT NULL - '; - - SET @sql += @sql_where; - EXEC sys.sp_executesql @sql, - @proc_params, - @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter, @i_out = @out OUTPUT; - - IF @out = 0 - BEGIN + RAISERROR(N'check_id 68: Identity columns within 30 percent of the end of range', 0,1) WITH NOWAIT; + -- Allowed Ranges: + --int -2,147,483,648 to 2,147,483,647 + --smallint -32,768 to 32,768 + --tinyint 0 to 255 - SET @msg = N'We couldn''t find the Stored Procedure ' + QUOTENAME(@StoredProcName) + N' in the Query Store views for ' + QUOTENAME(@DatabaseName) + N' between ' + CONVERT(NVARCHAR(30), ISNULL(@StartDate, DATEADD(DAY, -7, DATEDIFF(DAY, 0, SYSDATETIME() ))) ) + N' and ' + CONVERT(NVARCHAR(30), ISNULL(@EndDate, SYSDATETIME())) + - '. Try removing schema prefixes or adjusting dates. If it was executed from a different database context, try searching there instead.'; - RAISERROR(@msg, 0, 1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 68 AS check_id, + i.index_sanity_id, + 80 AS Priority, + N'Abnormal Psychology' AS findings_group, + N'Identity Column Within ' + + CAST (calc1.percent_remaining AS NVARCHAR(256)) + + N' Percent End of Range' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, + i.db_schema_object_name + N'.' + QUOTENAME(ic.column_name) + + N' is an identity with type ' + ic.system_type_name + + N', last value of ' + + ISNULL((CONVERT(NVARCHAR(256),CAST(ic.last_value AS DECIMAL(38,0)), 1)),N'NULL') + + N', seed of ' + + ISNULL((CONVERT(NVARCHAR(256),CAST(ic.seed_value AS DECIMAL(38,0)), 1)),N'NULL') + + N', increment of ' + CAST(ic.increment_value AS NVARCHAR(256)) + + N', and range of ' + + CASE ic.system_type_name WHEN 'int' THEN N'+/- 2,147,483,647' + WHEN 'smallint' THEN N'+/- 32,768' + WHEN 'tinyint' THEN N'0 to 255' + ELSE 'unknown' + END + AS details, + i.index_definition, + secret_columns, + ISNULL(i.index_usage_summary,''), + ISNULL(ip.index_size_summary,'') + FROM #IndexSanity i + JOIN #IndexColumns ic ON + i.object_id=ic.object_id + AND i.database_id = ic.database_id + AND i.schema_name = ic.schema_name + AND i.index_id IN (0,1) /* heaps and cx only */ + AND ic.is_identity=1 + AND ic.system_type_name IN ('tinyint', 'smallint', 'int') + JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id + CROSS APPLY ( + SELECT CAST(CASE WHEN ic.increment_value >= 0 + THEN + CASE ic.system_type_name + WHEN 'int' THEN (2147483647 - (ISNULL(ic.last_value,ic.seed_value) + ic.increment_value)) / 2147483647.*100 + WHEN 'smallint' THEN (32768 - (ISNULL(ic.last_value,ic.seed_value) + ic.increment_value)) / 32768.*100 + WHEN 'tinyint' THEN ( 255 - (ISNULL(ic.last_value,ic.seed_value) + ic.increment_value)) / 255.*100 + ELSE 999 + END + ELSE --ic.increment_value is negative + CASE ic.system_type_name + WHEN 'int' THEN ABS(-2147483647 - (ISNULL(ic.last_value,ic.seed_value) + ic.increment_value)) / 2147483647.*100 + WHEN 'smallint' THEN ABS(-32768 - (ISNULL(ic.last_value,ic.seed_value) + ic.increment_value)) / 32768.*100 + WHEN 'tinyint' THEN ABS( 0 - (ISNULL(ic.last_value,ic.seed_value) + ic.increment_value)) / 255.*100 + ELSE -1 + END + END AS NUMERIC(5,1)) AS percent_remaining + ) AS calc1 + WHERE i.index_id IN (1,0) + AND calc1.percent_remaining <= 30 + OPTION (RECOMPILE); - SELECT @msg AS [Blue Flowers, Blue Flowers, Blue Flowers]; - - RETURN; - - END; - - END; + RAISERROR(N'check_id 72: Columnstore indexes with Trace Flag 834', 0,1) WITH NOWAIT; + IF EXISTS (SELECT * FROM #IndexSanity WHERE index_type IN (5,6)) + AND EXISTS (SELECT * FROM #TraceStatus WHERE TraceFlag = 834 AND status = 1) + BEGIN + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 72 AS check_id, + i.index_sanity_id, + 80 AS Priority, + N'Abnormal Psychology' AS findings_group, + 'Columnstore Indexes with Trace Flag 834' AS finding, + [database_name] AS [Database Name], + N'https://support.microsoft.com/en-us/kb/3210239' AS URL, + i.db_schema_object_indexid AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + ISNULL(sz.index_size_summary,'') AS index_size_summary + FROM #IndexSanity AS i + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + WHERE i.index_type IN (5,6) + OPTION ( RECOMPILE ); + END; + ---------------------------------------- + --Statistics Info: Check_id 90-99 + ---------------------------------------- + RAISERROR(N'check_id 90: Outdated 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 ) + SELECT 90 AS check_id, + 90 AS Priority, + 'Functioning Statistaholics' AS findings_group, + 'Statistics Not Updated Recently', + s.database_name, + 'https://www.brentozar.com/go/stats' AS URL, + 'Statistics on this table were last updated ' + + CASE WHEN s.last_statistics_update IS NULL THEN N' NEVER ' + ELSE CONVERT(NVARCHAR(20), s.last_statistics_update) + + ' have had ' + CONVERT(NVARCHAR(100), s.modification_counter) + + ' modifications in that time, which is ' + + CONVERT(NVARCHAR(100), s.percent_modifications) + + '% of the table.' + END 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.last_statistics_update <= CONVERT(DATETIME, GETDATE() - 30) + AND s.percent_modifications >= 10. + AND s.rows >= 10000 + OPTION ( RECOMPILE ); -/* -This is our grouped interval query. + RAISERROR(N'check_id 91: Statistics with a low 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 91 AS check_id, + 90 AS Priority, + 'Functioning Statistaholics' AS findings_group, + 'Low Sampling Rates', + s.database_name, + 'https://www.brentozar.com/go/stats' AS URL, + 'Only ' + CONVERT(NVARCHAR(100), s.percent_sampled) + '% of the rows were sampled during the last statistics update. This may lead to poor cardinality estimates.' 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.rows BETWEEN 10000 AND 1000000 AND s.percent_sampled < 10) + OR (s.rows > 1000000 AND s.percent_sampled < 1) + OPTION ( RECOMPILE ); -By default, it looks at queries: - In the last 7 days - That aren't system queries - That have a query plan (some won't, if nested level is > 128, along with other reasons) - And haven't failed - This stuff, along with some other options, will be configurable in the stored proc + 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 ) + SELECT 92 AS check_id, + 90 AS Priority, + 'Functioning Statistaholics' AS findings_group, + 'Statistics With NO RECOMPUTE', + s.database_name, + 'https://www.brentozar.com/go/stats' AS URL, + 'The statistic ' + QUOTENAME(s.statistics_name) + ' is set to not recompute. This can be helpful if data is really skewed, but harmful if you expect automatic statistics updates.' 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.no_recompute = 1 + OPTION ( RECOMPILE ); -*/ -IF @sql_where IS NOT NULL -BEGIN TRY - BEGIN + 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 ) + SELECT 94 AS check_id, + 100 AS Priority, + 'Serial Forcer' AS findings_group, + 'Check Constraint with Scalar UDF' AS finding, + cc.database_name, + 'https://www.brentozar.com/go/computedscalar' AS URL, + 'The check constraint ' + QUOTENAME(cc.constraint_name) + ' on ' + QUOTENAME(cc.schema_name) + '.' + QUOTENAME(cc.table_name) + ' is based on ' + cc.definition + + '. That indicates it may reference a scalar function, or a CLR function with data access, which can cause all queries and maintenance to run serially.' AS details, + cc.column_definition, + 'N/A' AS secret_columns, + 'N/A' AS index_usage_summary, + 'N/A' AS index_size_summary + FROM #CheckConstraints AS cc + WHERE cc.is_function = 1 + OPTION ( RECOMPILE ); - RAISERROR(N'Populating temp tables', 0, 1) WITH NOWAIT; - -RAISERROR(N'Gathering intervals', 0, 1) WITH NOWAIT; - -SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; -SET @sql_select += N' -SELECT CONVERT(DATE, qsrs.last_execution_time) AS flat_date, - MIN(DATEADD(HOUR, DATEDIFF(HOUR, 0, qsrs.last_execution_time), 0)) AS start_range, - MAX(DATEADD(HOUR, DATEDIFF(HOUR, 0, qsrs.last_execution_time) + 1, 0)) AS end_range, - SUM(qsrs.avg_duration / 1000.) / SUM(qsrs.count_executions) AS total_avg_duration_ms, - SUM(qsrs.avg_cpu_time / 1000.) / SUM(qsrs.count_executions) AS total_avg_cpu_time_ms, - SUM((qsrs.avg_logical_io_reads * 8 ) / 1024.) / SUM(qsrs.count_executions) AS total_avg_logical_io_reads_mb, - SUM((qsrs.avg_physical_io_reads* 8 ) / 1024.) / SUM(qsrs.count_executions) AS total_avg_physical_io_reads_mb, - SUM((qsrs.avg_logical_io_writes* 8 ) / 1024.) / SUM(qsrs.count_executions) AS total_avg_logical_io_writes_mb, - SUM((qsrs.avg_query_max_used_memory * 8 ) / 1024.) / SUM(qsrs.count_executions) AS total_avg_query_max_used_memory_mb, - SUM(qsrs.avg_rowcount) AS total_rowcount, - SUM(qsrs.count_executions) AS total_count_executions, - SUM(qsrs.max_duration / 1000.) AS total_max_duration_ms, - SUM(qsrs.max_cpu_time / 1000.) AS total_max_cpu_time_ms, - SUM((qsrs.max_logical_io_reads * 8 ) / 1024.) AS total_max_logical_io_reads_mb, - SUM((qsrs.max_physical_io_reads* 8 ) / 1024.) AS total_max_physical_io_reads_mb, - SUM((qsrs.max_logical_io_writes* 8 ) / 1024.) AS total_max_logical_io_writes_mb, - SUM((qsrs.max_query_max_used_memory * 8 ) / 1024.) AS total_max_query_max_used_memory_mb '; - IF @new_columns = 1 - BEGIN - SET @sql_select += N', - SUM((qsrs.avg_log_bytes_used) / 1048576.) / SUM(qsrs.count_executions) AS total_avg_log_bytes_mb, - SUM(qsrs.avg_tempdb_space_used) / SUM(qsrs.count_executions) AS total_avg_tempdb_space, - SUM((qsrs.max_log_bytes_used) / 1048576.) AS total_max_log_bytes_mb, - SUM(qsrs.max_tempdb_space_used) AS total_max_tempdb_space - '; - END; - IF @new_columns = 0 - BEGIN - SET @sql_select += N', - NULL AS total_avg_log_bytes_mb, - NULL AS total_avg_tempdb_space, - NULL AS total_max_log_bytes_mb, - NULL AS total_max_tempdb_space - '; - END; + RAISERROR(N'check_id 99: Computed Columns 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 ) + SELECT 99 AS check_id, + 100 AS Priority, + 'Serial Forcer' AS findings_group, + 'Computed Column with Scalar UDF' AS finding, + cc.database_name, + 'https://www.brentozar.com/go/serialudf' AS URL, + 'The computed column ' + QUOTENAME(cc.column_name) + ' on ' + QUOTENAME(cc.schema_name) + '.' + QUOTENAME(cc.table_name) + ' is based on ' + cc.definition + + '. That indicates it may reference a scalar function, or a CLR function with data access, which can cause all queries and maintenance to run serially.' AS details, + cc.column_definition, + 'N/A' AS secret_columns, + 'N/A' AS index_usage_summary, + 'N/A' AS index_size_summary + FROM #ComputedColumns AS cc + WHERE cc.is_function = 1 + OPTION ( RECOMPILE ); -SET @sql_select += N'FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp - ON qsp.plan_id = qsrs.plan_id - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq - ON qsq.query_id = qsp.query_id - WHERE 1 = 1 - AND qsq.is_internal_query = 0 - AND qsp.query_plan IS NOT NULL - '; + END /* IF @Mode IN (0, 4) DIAGNOSE priorities 1-100 */ -SET @sql_select += @sql_where; -SET @sql_select += - N'GROUP BY CONVERT(DATE, qsrs.last_execution_time) - OPTION (RECOMPILE); - '; -IF @Debug = 1 - PRINT @sql_select; -IF @sql_select IS NULL - BEGIN - RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; - RETURN; - END; -INSERT #grouped_interval WITH (TABLOCK) - ( flat_date, start_range, end_range, total_avg_duration_ms, - total_avg_cpu_time_ms, total_avg_logical_io_reads_mb, total_avg_physical_io_reads_mb, - total_avg_logical_io_writes_mb, total_avg_query_max_used_memory_mb, total_rowcount, - total_count_executions, total_max_duration_ms, total_max_cpu_time_ms, total_max_logical_io_reads_mb, - total_max_physical_io_reads_mb, total_max_logical_io_writes_mb, total_max_query_max_used_memory_mb, - total_avg_log_bytes_mb, total_avg_tempdb_space, total_max_log_bytes_mb, total_max_tempdb_space ) -EXEC sys.sp_executesql @stmt = @sql_select, - @params = @sp_params, - @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; -/* -The next group of queries looks at plans in the ranges we found in the grouped interval query + IF @Mode = 4 /* DIAGNOSE*/ + BEGIN; + RAISERROR(N'@Mode=4, running rules for priorities 101+.', 0,1) WITH NOWAIT; -We take the highest value from each metric (duration, cpu, etc) and find the top plans by that metric in the range + RAISERROR(N'check_id 21: More Than 5 Percent NC Indexes Are Unused', 0,1) WITH NOWAIT; + DECLARE @percent_NC_indexes_unused NUMERIC(29,1); + DECLARE @NC_indexes_unused_reserved_MB NUMERIC(29,1); -They insert into the #working_plans table -*/ + SELECT @percent_NC_indexes_unused = ( 100.00 * SUM(CASE + WHEN total_reads = 0 + THEN 1 + ELSE 0 + END) ) / COUNT(*), + @NC_indexes_unused_reserved_MB = SUM(CASE + WHEN total_reads = 0 + THEN sz.total_reserved_MB + ELSE 0 + END) + FROM #IndexSanity i + 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()) + 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, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 21 AS check_id, + MAX(i.index_sanity_id) AS index_sanity_id, + 150 AS Priority, + N'Index Hoarder' AS findings_group, + N'More Than 5 Percent NC Indexes Are Unused' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/IndexHoarder' AS URL, + CAST (@percent_NC_indexes_unused AS NVARCHAR(30)) + N' percent NC indexes (' + CAST(COUNT(*) AS NVARCHAR(10)) + N') unused. ' + + N'These take up ' + CAST (@NC_indexes_unused_reserved_MB AS NVARCHAR(30)) + N'MB of space.' AS details, + i.database_name + ' (' + CAST (COUNT(*) AS NVARCHAR(30)) + N' indexes)' AS index_definition, + '' AS secret_columns, + CAST(SUM(total_reads) AS NVARCHAR(256)) + N' reads (ALL); ' + + CAST(SUM([user_updates]) AS NVARCHAR(256)) + N' writes (ALL)' AS index_usage_summary, + + REPLACE(CONVERT(NVARCHAR(30),CAST(MAX([total_rows]) AS MONEY), 1), '.00', '') + N' rows (MAX)' + + CASE WHEN SUM(total_reserved_MB) > 1024 THEN + N'; ' + CAST(CAST(SUM(total_reserved_MB)/1024. AS NUMERIC(29,1)) AS NVARCHAR(30)) + 'GB (ALL)' + WHEN SUM(total_reserved_MB) > 0 THEN + N'; ' + CAST(CAST(SUM(total_reserved_MB) AS NUMERIC(29,1)) AS NVARCHAR(30)) + 'MB (ALL)' + ELSE '' + END AS index_size_summary + FROM #IndexSanity i + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + 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()) + GROUP BY i.database_name + OPTION ( RECOMPILE ); + RAISERROR(N'check_id 23: Indexes with 7 or more columns. (Borderline)', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 23 AS check_id, + i.index_sanity_id, + 150 AS Priority, + N'Index Hoarder' AS findings_group, + N'Borderline: Wide Indexes (7 or More Columns)' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/IndexHoarder' AS URL, + CAST(count_key_columns + count_included_columns AS NVARCHAR(10)) + ' columns on ' + + i.db_schema_object_indexid AS details, i.index_definition, + i.secret_columns, + i.index_usage_summary, + sz.index_size_summary + FROM #IndexSanity AS i + JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id + WHERE ( count_key_columns + count_included_columns ) >= 7 + OPTION ( RECOMPILE ); + RAISERROR(N'check_id 24: Wide clustered indexes (> 3 columns or > 16 bytes).', 0,1) WITH NOWAIT; + WITH count_columns AS ( + SELECT database_id, [object_id], + SUM(CASE max_length WHEN -1 THEN 0 ELSE max_length END) AS sum_max_length + FROM #IndexColumns ic + WHERE index_id IN (1,0) /*Heap or clustered only*/ + AND key_ordinal > 0 + GROUP BY database_id, object_id + ) + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 24 AS check_id, + i.index_sanity_id, + 150 AS Priority, + N'Index Hoarder' AS findings_group, + N'Wide Clustered Index (> 3 columns OR > 16 bytes)' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/IndexHoarder' AS URL, + CAST (i.count_key_columns AS NVARCHAR(10)) + N' columns with potential size of ' + + CAST(cc.sum_max_length AS NVARCHAR(10)) + + N' bytes in clustered index:' + i.db_schema_object_name + + N'. ' + + (SELECT CAST(COUNT(*) AS NVARCHAR(23)) + FROM #IndexSanity i2 + WHERE i2.[object_id]=i.[object_id] + AND i2.database_id = i.database_id + AND i2.index_id <> 1 + AND i2.is_disabled=0 + AND i2.is_hypothetical=0) + + N' NC indexes on the table.' + AS details, + i.index_definition, + secret_columns, + i.index_usage_summary, + ip.index_size_summary + FROM #IndexSanity i + JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id + JOIN count_columns AS cc ON i.[object_id]=cc.[object_id] + AND i.database_id = cc.database_id + WHERE index_id = 1 /* clustered only */ + AND + (count_key_columns > 3 /*More than three key columns.*/ + OR cc.sum_max_length > 16 /*More than 16 bytes in key */) + AND i.is_CX_columnstore = 0 + ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); -/*Get longest duration plans*/ - -RAISERROR(N'Gathering longest duration plans', 0, 1) WITH NOWAIT; - -SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; -SET @sql_select += N' -WITH duration_max -AS ( SELECT TOP 1 - gi.start_range, - gi.end_range - FROM #grouped_interval AS gi - ORDER BY gi.total_avg_duration_ms DESC ) -INSERT #working_plans WITH (TABLOCK) - ( plan_id, query_id, pattern ) -SELECT TOP ( @sp_Top ) - qsp.plan_id, qsp.query_id, ''avg duration'' -FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp -JOIN duration_max AS dm -ON qsp.last_execution_time >= dm.start_range - AND qsp.last_execution_time < dm.end_range -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs -ON qsrs.plan_id = qsp.plan_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq -ON qsq.query_id = qsp.query_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt -ON qsqt.query_text_id = qsq.query_text_id -WHERE 1 = 1 - AND qsq.is_internal_query = 0 - AND qsp.query_plan IS NOT NULL - '; + RAISERROR(N'check_id 25: Addicted to nullable columns.', 0,1) WITH NOWAIT; + WITH count_columns AS ( + SELECT [object_id], + [database_id], + [schema_name], + SUM(CASE is_nullable WHEN 1 THEN 0 ELSE 1 END) AS non_nullable_columns, + COUNT(*) AS total_columns + FROM #IndexColumns ic + WHERE index_id IN (1,0) /*Heap or clustered only*/ + GROUP BY [object_id], + [database_id], + [schema_name] + ) + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 25 AS check_id, + i.index_sanity_id, + 200 AS Priority, + N'Index Hoarder' AS findings_group, + N'Addicted to Nulls' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/IndexHoarder' AS URL, + i.db_schema_object_name + + N' allows null in ' + CAST((total_columns-non_nullable_columns) AS NVARCHAR(10)) + + N' of ' + CAST(total_columns AS NVARCHAR(10)) + + N' columns.' AS details, + i.index_definition, + secret_columns, + ISNULL(i.index_usage_summary,''), + ISNULL(ip.index_size_summary,'') + FROM #IndexSanity i + JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id + JOIN count_columns AS cc ON i.[object_id]=cc.[object_id] + AND cc.database_id = ip.database_id + AND cc.[schema_name] = ip.[schema_name] + WHERE i.index_id IN (1,0) + AND cc.non_nullable_columns < 2 + AND cc.total_columns > 3 + ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); -SET @sql_select += @sql_where; + RAISERROR(N'check_id 26: Wide tables (35+ cols or > 2000 non-LOB bytes).', 0,1) WITH NOWAIT; + WITH count_columns AS ( + SELECT [object_id], + [database_id], + [schema_name], + SUM(CASE max_length WHEN -1 THEN 1 ELSE 0 END) AS count_lob_columns, + SUM(CASE max_length WHEN -1 THEN 0 ELSE max_length END) AS sum_max_length, + COUNT(*) AS total_columns + FROM #IndexColumns ic + WHERE index_id IN (1,0) /*Heap or clustered only*/ + GROUP BY [object_id], + [database_id], + [schema_name] + ) + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 26 AS check_id, + i.index_sanity_id, + 150 AS Priority, + N'Index Hoarder' AS findings_group, + N'Wide Tables: 35+ cols or > 2000 non-LOB bytes' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/IndexHoarder' AS URL, + i.db_schema_object_name + + N' has ' + CAST((total_columns) AS NVARCHAR(10)) + + N' total columns with a max possible width of ' + CAST(sum_max_length AS NVARCHAR(10)) + + N' bytes.' + + CASE WHEN count_lob_columns > 0 THEN CAST((count_lob_columns) AS NVARCHAR(10)) + + ' columns are LOB types.' ELSE '' + END + AS details, + i.index_definition, + secret_columns, + ISNULL(i.index_usage_summary,''), + ISNULL(ip.index_size_summary,'') + FROM #IndexSanity i + JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id + JOIN count_columns AS cc ON i.[object_id]=cc.[object_id] + AND cc.database_id = i.database_id + AND cc.[schema_name] = i.[schema_name] + WHERE i.index_id IN (1,0) + AND + (cc.total_columns >= 35 OR + cc.sum_max_length >= 2000) + ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); + + RAISERROR(N'check_id 27: Addicted to strings.', 0,1) WITH NOWAIT; + WITH count_columns AS ( + SELECT [object_id], + [database_id], + [schema_name], + SUM(CASE WHEN system_type_name IN ('varchar','nvarchar','char') OR max_length=-1 THEN 1 ELSE 0 END) AS string_or_LOB_columns, + COUNT(*) AS total_columns + FROM #IndexColumns ic + WHERE index_id IN (1,0) /*Heap or clustered only*/ + GROUP BY [object_id], + [database_id], + [schema_name] + ) + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 27 AS check_id, + i.index_sanity_id, + 200 AS Priority, + N'Index Hoarder' AS findings_group, + N'Addicted to strings' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/IndexHoarder' AS URL, + i.db_schema_object_name + + N' uses string or LOB types for ' + CAST((string_or_LOB_columns) AS NVARCHAR(10)) + + N' of ' + CAST(total_columns AS NVARCHAR(10)) + + N' columns. Check if data types are valid.' AS details, + i.index_definition, + secret_columns, + ISNULL(i.index_usage_summary,''), + ISNULL(ip.index_size_summary,'') + FROM #IndexSanity i + JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id + JOIN count_columns AS cc ON i.[object_id]=cc.[object_id] + AND cc.database_id = i.database_id + AND cc.[schema_name] = i.[schema_name] + CROSS APPLY (SELECT cc.total_columns - string_or_LOB_columns AS non_string_or_lob_columns) AS calc1 + WHERE i.index_id IN (1,0) + AND calc1.non_string_or_lob_columns <= 1 + AND cc.total_columns > 3 + ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); -SET @sql_select += N'ORDER BY qsrs.avg_duration DESC - OPTION (RECOMPILE); - '; + RAISERROR(N'check_id 28: Non-unique clustered index.', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 28 AS check_id, + i.index_sanity_id, + 150 AS Priority, + N'Index Hoarder' AS findings_group, + N'Non-Unique Clustered Index' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/IndexHoarder' AS URL, + N'Uniquifiers will be required! Clustered index: ' + i.db_schema_object_name + + N' and all NC indexes. ' + + (SELECT CAST(COUNT(*) AS NVARCHAR(23)) + FROM #IndexSanity i2 + WHERE i2.[object_id]=i.[object_id] + AND i2.database_id = i.database_id + AND i2.index_id <> 1 + AND i2.is_disabled=0 + AND i2.is_hypothetical=0) + + N' NC indexes on the table.' + AS details, + i.index_definition, + secret_columns, + i.index_usage_summary, + ip.index_size_summary + FROM #IndexSanity i + JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id + WHERE index_id = 1 /* clustered only */ + AND is_unique=0 /* not unique */ + AND is_CX_columnstore=0 /* not a clustered columnstore-- no unique option on those */ + ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); -IF @Debug = 1 - PRINT @sql_select; + RAISERROR(N'check_id 29: NC indexes with 0 reads. (Borderline) and < 10,000 writes', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 29 AS check_id, + i.index_sanity_id, + 150 AS Priority, + N'Index Hoarder' AS findings_group, + N'Unused NC index with Low Writes' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/IndexHoarder' AS URL, + N'0 reads: ' + i.db_schema_object_indexid AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + sz.index_size_summary + FROM #IndexSanity AS i + JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id + WHERE i.total_reads=0 + AND i.user_updates < 10000 + 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()) + ORDER BY i.db_schema_object_indexid + OPTION ( RECOMPILE ); -IF @sql_select IS NULL - BEGIN - RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; - RETURN; - END; -EXEC sys.sp_executesql @stmt = @sql_select, - @params = @sp_params, - @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; - - -SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; -SET @sql_select += N' -WITH duration_max -AS ( SELECT TOP 1 - gi.start_range, - gi.end_range - FROM #grouped_interval AS gi - ORDER BY gi.total_max_duration_ms DESC ) -INSERT #working_plans WITH (TABLOCK) - ( plan_id, query_id, pattern ) -SELECT TOP ( @sp_Top ) - qsp.plan_id, qsp.query_id, ''max duration'' -FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp -JOIN duration_max AS dm -ON qsp.last_execution_time >= dm.start_range - AND qsp.last_execution_time < dm.end_range -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs -ON qsrs.plan_id = qsp.plan_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq -ON qsq.query_id = qsp.query_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt -ON qsqt.query_text_id = qsq.query_text_id -WHERE 1 = 1 - AND qsq.is_internal_query = 0 - AND qsp.query_plan IS NOT NULL - '; + ---------------------------------------- + --Feature-Phobic Indexes: Check_id 30-39 + ---------------------------------------- + RAISERROR(N'check_id 30: No indexes with includes', 0,1) WITH NOWAIT; + /* This does not work the way you'd expect with @GetAllDatabases = 1. For details: + https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/825 + */ -SET @sql_select += @sql_where; + SELECT database_name, + SUM(CASE WHEN count_included_columns > 0 THEN 1 ELSE 0 END) AS number_indexes_with_includes, + 100.* SUM(CASE WHEN count_included_columns > 0 THEN 1 ELSE 0 END) / ( 1.0 * COUNT(*) ) AS percent_indexes_with_includes + INTO #index_includes + FROM #IndexSanity + WHERE is_hypothetical = 0 + AND is_disabled = 0 + AND NOT (@GetAllDatabases = 1 OR @Mode = 0) + GROUP BY database_name; -SET @sql_select += N'ORDER BY qsrs.max_duration DESC - OPTION (RECOMPILE); - '; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 30 AS check_id, + NULL AS index_sanity_id, + 250 AS Priority, + N'Feature-Phobic Indexes' AS findings_group, + database_name AS [Database Name], + N'No Indexes Use Includes' AS finding, 'https://www.brentozar.com/go/IndexFeatures' AS URL, + N'No Indexes Use Includes' AS details, + database_name + N' (Entire database)' AS index_definition, + N'' AS secret_columns, + N'N/A' AS index_usage_summary, + N'N/A' AS index_size_summary + FROM #index_includes + WHERE number_indexes_with_includes = 0 + OPTION ( RECOMPILE ); -IF @Debug = 1 - PRINT @sql_select; + RAISERROR(N'check_id 31: < 3 percent of indexes have includes', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 31 AS check_id, + NULL AS index_sanity_id, + 250 AS Priority, + N'Feature-Phobic Indexes' AS findings_group, + N'Few Indexes Use Includes' AS findings, + database_name AS [Database Name], + N'https://www.brentozar.com/go/IndexFeatures' AS URL, + N'Only ' + CAST(percent_indexes_with_includes AS NVARCHAR(20)) + '% of indexes have includes' AS details, + N'Entire database' AS index_definition, + N'' AS secret_columns, + N'N/A' AS index_usage_summary, + N'N/A' AS index_size_summary + FROM #index_includes + WHERE number_indexes_with_includes > 0 AND percent_indexes_with_includes <= 3 + OPTION ( RECOMPILE ); -IF @sql_select IS NULL - BEGIN - RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; - RETURN; - END; + RAISERROR(N'check_id 32: filtered indexes and indexed views', 0,1) WITH NOWAIT; -EXEC sys.sp_executesql @stmt = @sql_select, - @params = @sp_params, - @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; - - -/*Get longest cpu plans*/ - -RAISERROR(N'Gathering highest cpu plans', 0, 1) WITH NOWAIT; - -SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; -SET @sql_select += N' -WITH cpu_max -AS ( SELECT TOP 1 - gi.start_range, - gi.end_range - FROM #grouped_interval AS gi - ORDER BY gi.total_avg_cpu_time_ms DESC ) -INSERT #working_plans WITH (TABLOCK) - ( plan_id, query_id, pattern ) -SELECT TOP ( @sp_Top ) - qsp.plan_id, qsp.query_id, ''avg cpu'' -FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp -JOIN cpu_max AS dm -ON qsp.last_execution_time >= dm.start_range - AND qsp.last_execution_time < dm.end_range -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs -ON qsrs.plan_id = qsp.plan_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq -ON qsq.query_id = qsp.query_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt -ON qsqt.query_text_id = qsq.query_text_id -WHERE 1 = 1 - AND qsq.is_internal_query = 0 - AND qsp.query_plan IS NOT NULL - '; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT DISTINCT + 32 AS check_id, + NULL AS index_sanity_id, + 250 AS Priority, + N'Feature-Phobic Indexes' AS findings_group, + N'No Filtered Indexes or Indexed Views' AS finding, + i.database_name AS [Database Name], + N'https://www.brentozar.com/go/IndexFeatures' AS URL, + N'These are NOT always needed-- but do you know when you would use them?' AS details, + i.database_name + N' (Entire database)' AS index_definition, + N'' AS secret_columns, + N'N/A' AS index_usage_summary, + N'N/A' AS index_size_summary + FROM #IndexSanity i + WHERE i.database_name NOT IN ( + SELECT database_name + FROM #IndexSanity + WHERE filter_definition <> '' ) + AND i.database_name NOT IN ( + SELECT database_name + FROM #IndexSanity + WHERE is_indexed_view = 1 ) + OPTION ( RECOMPILE ); -SET @sql_select += @sql_where; + RAISERROR(N'check_id 33: Potential filtered indexes based on column names.', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 33 AS check_id, + i.index_sanity_id AS index_sanity_id, + 250 AS Priority, + N'Feature-Phobic Indexes' AS findings_group, + N'Potential Filtered Index (Based on Column Name)' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/IndexFeatures' AS URL, + N'A column name in this index suggests it might be a candidate for filtering (is%, %archive%, %active%, %flag%)' AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + sz.index_size_summary + FROM #IndexColumns ic + JOIN #IndexSanity i ON ic.[object_id]=i.[object_id] + AND ic.database_id =i.database_id + AND ic.schema_name = i.schema_name + AND ic.[index_id]=i.[index_id] + AND i.[index_id] > 1 /* non-clustered index */ + JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id + WHERE (column_name LIKE 'is%' + OR column_name LIKE '%archive%' + OR column_name LIKE '%active%' + OR column_name LIKE '%flag%') + OPTION ( RECOMPILE ); -SET @sql_select += N'ORDER BY qsrs.avg_cpu_time DESC - OPTION (RECOMPILE); - '; + RAISERROR(N'check_id 41: Hypothetical indexes ', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 41 AS check_id, + i.index_sanity_id, + 150 AS Priority, + N'Self Loathing Indexes' AS findings_group, + N'Hypothetical Index' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/SelfLoathing' AS URL, + N'Hypothetical Index: ' + db_schema_object_indexid AS details, + i.index_definition, + i.secret_columns, + N'' AS index_usage_summary, + N'' AS index_size_summary + FROM #IndexSanity AS i + WHERE is_hypothetical = 1 + OPTION ( RECOMPILE ); -IF @Debug = 1 - PRINT @sql_select; -IF @sql_select IS NULL - BEGIN - RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; - RETURN; - END; + RAISERROR(N'check_id 42: Disabled indexes', 0,1) WITH NOWAIT; + --Note: disabled NC indexes will have O rows in #IndexSanitySize! + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 42 AS check_id, + index_sanity_id, + 150 AS Priority, + N'Self Loathing Indexes' AS findings_group, + N'Disabled Index' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/SelfLoathing' AS URL, + N'Disabled Index:' + db_schema_object_indexid AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + 'DISABLED' AS index_size_summary + FROM #IndexSanity AS i + WHERE is_disabled = 1 + OPTION ( RECOMPILE ); -EXEC sys.sp_executesql @stmt = @sql_select, - @params = @sp_params, - @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; - - -SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; -SET @sql_select += N' -WITH cpu_max -AS ( SELECT TOP 1 - gi.start_range, - gi.end_range - FROM #grouped_interval AS gi - ORDER BY gi.total_max_cpu_time_ms DESC ) -INSERT #working_plans WITH (TABLOCK) - ( plan_id, query_id, pattern ) -SELECT TOP ( @sp_Top ) - qsp.plan_id, qsp.query_id, ''max cpu'' -FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp -JOIN cpu_max AS dm -ON qsp.last_execution_time >= dm.start_range - AND qsp.last_execution_time < dm.end_range -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs -ON qsrs.plan_id = qsp.plan_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq -ON qsq.query_id = qsp.query_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt -ON qsqt.query_text_id = qsq.query_text_id -WHERE 1 = 1 - AND qsq.is_internal_query = 0 - AND qsp.query_plan IS NOT NULL - '; + RAISERROR(N'check_id 49: Heaps with deletes', 0,1) WITH NOWAIT; + WITH heaps_cte + AS ( SELECT [object_id], + [database_id], + [schema_name], + SUM(leaf_delete_count) AS leaf_delete_count + FROM #IndexPartitionSanity + GROUP BY [object_id], + [database_id], + [schema_name] + HAVING SUM(forwarded_fetch_count) < 1000 * @DaysUptime /* Only alert about indexes with no forwarded fetches - we already alerted about those in check_id 43 */ + AND SUM(leaf_delete_count) > 0) + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 49 AS check_id, + i.index_sanity_id, + 200 AS Priority, + N'Self Loathing Indexes' AS findings_group, + N'Heaps with Deletes' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/SelfLoathing' AS URL, + CAST(h.leaf_delete_count AS NVARCHAR(256)) + N' deletes against heap:' + + db_schema_object_indexid AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + sz.index_size_summary + FROM #IndexSanity i + JOIN heaps_cte h ON i.[object_id] = h.[object_id] + AND i.[database_id] = h.[database_id] + AND i.[schema_name] = h.[schema_name] + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + WHERE i.index_id = 0 + AND sz.total_reserved_MB >= CASE WHEN NOT (@GetAllDatabases = 1 OR @Mode = 4) THEN @ThresholdMB ELSE sz.total_reserved_MB END + OPTION ( RECOMPILE ); -SET @sql_select += @sql_where; + ---------------------------------------- + --Abnormal Psychology : Check_id 60-79 + ---------------------------------------- + RAISERROR(N'check_id 60: XML indexes', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 60 AS check_id, + i.index_sanity_id, + 150 AS Priority, + N'Abnormal Psychology' AS findings_group, + N'XML Index' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, + i.db_schema_object_indexid AS details, + i.index_definition, + i.secret_columns, + N'' AS index_usage_summary, + ISNULL(sz.index_size_summary,'') AS index_size_summary + FROM #IndexSanity AS i + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + WHERE i.is_XML = 1 + OPTION ( RECOMPILE ); -SET @sql_select += N'ORDER BY qsrs.max_cpu_time DESC - OPTION (RECOMPILE); - '; + RAISERROR(N'check_id 61: Columnstore indexes', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 61 AS check_id, + i.index_sanity_id, + 150 AS Priority, + N'Abnormal Psychology' AS findings_group, + CASE WHEN i.is_NC_columnstore=1 + THEN N'NC Columnstore Index' + ELSE N'Clustered Columnstore Index' + END AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, + i.db_schema_object_indexid AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + ISNULL(sz.index_size_summary,'') AS index_size_summary + FROM #IndexSanity AS i + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + WHERE i.is_NC_columnstore = 1 OR i.is_CX_columnstore=1 + OPTION ( RECOMPILE ); -IF @Debug = 1 - PRINT @sql_select; -IF @sql_select IS NULL - BEGIN - RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; - RETURN; - END; + RAISERROR(N'check_id 62: Spatial indexes', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 62 AS check_id, + i.index_sanity_id, + 150 AS Priority, + N'Abnormal Psychology' AS findings_group, + N'Spatial Index' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, + i.db_schema_object_indexid AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + ISNULL(sz.index_size_summary,'') AS index_size_summary + FROM #IndexSanity AS i + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + WHERE i.is_spatial = 1 + OPTION ( RECOMPILE ); -EXEC sys.sp_executesql @stmt = @sql_select, - @params = @sp_params, - @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; - - -/*Get highest logical read plans*/ - -RAISERROR(N'Gathering highest logical read plans', 0, 1) WITH NOWAIT; - -SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; -SET @sql_select += N' -WITH logical_reads_max -AS ( SELECT TOP 1 - gi.start_range, - gi.end_range - FROM #grouped_interval AS gi - ORDER BY gi.total_avg_logical_io_reads_mb DESC ) -INSERT #working_plans WITH (TABLOCK) - ( plan_id, query_id, pattern ) -SELECT TOP ( @sp_Top ) - qsp.plan_id, qsp.query_id, ''avg logical reads'' -FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp -JOIN logical_reads_max AS dm -ON qsp.last_execution_time >= dm.start_range - AND qsp.last_execution_time < dm.end_range -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs -ON qsrs.plan_id = qsp.plan_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq -ON qsq.query_id = qsp.query_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt -ON qsqt.query_text_id = qsq.query_text_id -WHERE 1 = 1 - AND qsq.is_internal_query = 0 - AND qsp.query_plan IS NOT NULL - '; + RAISERROR(N'check_id 63: Compressed indexes', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 63 AS check_id, + i.index_sanity_id, + 150 AS Priority, + N'Abnormal Psychology' AS findings_group, + N'Compressed Index' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, + i.db_schema_object_indexid + N'. COMPRESSION: ' + sz.data_compression_desc AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + ISNULL(sz.index_size_summary,'') AS index_size_summary + FROM #IndexSanity AS i + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + WHERE sz.data_compression_desc LIKE '%PAGE%' OR sz.data_compression_desc LIKE '%ROW%' + OPTION ( RECOMPILE ); -SET @sql_select += @sql_where; + RAISERROR(N'check_id 64: Partitioned', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 64 AS check_id, + i.index_sanity_id, + 150 AS Priority, + N'Abnormal Psychology' AS findings_group, + N'Partitioned Index' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, + i.db_schema_object_indexid AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + ISNULL(sz.index_size_summary,'') AS index_size_summary + FROM #IndexSanity AS i + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + WHERE i.partition_key_column_name IS NOT NULL + OPTION ( RECOMPILE ); -SET @sql_select += N'ORDER BY qsrs.avg_logical_io_reads DESC - OPTION (RECOMPILE); - '; + RAISERROR(N'check_id 65: Non-Aligned Partitioned', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 65 AS check_id, + i.index_sanity_id, + 150 AS Priority, + N'Abnormal Psychology' AS findings_group, + N'Non-Aligned Index on a Partitioned Table' AS finding, + i.[database_name] AS [Database Name], + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, + i.db_schema_object_indexid AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + ISNULL(sz.index_size_summary,'') AS index_size_summary + FROM #IndexSanity AS i + JOIN #IndexSanity AS iParent ON + i.[object_id]=iParent.[object_id] + AND i.database_id = iParent.database_id + AND i.schema_name = iParent.schema_name + AND iParent.index_id IN (0,1) /* could be a partitioned heap or clustered table */ + AND iParent.partition_key_column_name IS NOT NULL /* parent is partitioned*/ + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + WHERE i.partition_key_column_name IS NULL + OPTION ( RECOMPILE ); -IF @Debug = 1 - PRINT @sql_select; + RAISERROR(N'check_id 66: Recently created tables/indexes (1 week)', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 66 AS check_id, + i.index_sanity_id, + 200 AS Priority, + N'Abnormal Psychology' AS findings_group, + N'Recently Created Tables/Indexes (1 week)' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, + i.db_schema_object_indexid + N' was created on ' + + CONVERT(NVARCHAR(16),i.create_date,121) + + N'. Tables/indexes which are dropped/created regularly require special methods for index tuning.' + AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + ISNULL(sz.index_size_summary,'') AS index_size_summary + FROM #IndexSanity AS i + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + WHERE i.create_date >= DATEADD(dd,-7,GETDATE()) + OPTION ( RECOMPILE ); -IF @sql_select IS NULL - BEGIN - RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; - RETURN; - END; + RAISERROR(N'check_id 67: Recently modified tables/indexes (2 days)', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 67 AS check_id, + i.index_sanity_id, + 200 AS Priority, + N'Abnormal Psychology' AS findings_group, + N'Recently Modified Tables/Indexes (2 days)' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, + i.db_schema_object_indexid + N' was modified on ' + + CONVERT(NVARCHAR(16),i.modify_date,121) + + N'. A large amount of recently modified indexes may mean a lot of rebuilds are occurring each night.' + AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + ISNULL(sz.index_size_summary,'') AS index_size_summary + FROM #IndexSanity AS i + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + WHERE i.modify_date > DATEADD(dd,-2,GETDATE()) + AND /*Exclude recently created tables.*/ + i.create_date < DATEADD(dd,-7,GETDATE()) + OPTION ( RECOMPILE ); -EXEC sys.sp_executesql @stmt = @sql_select, - @params = @sp_params, - @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; - - -SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; -SET @sql_select += N' -WITH logical_reads_max -AS ( SELECT TOP 1 - gi.start_range, - gi.end_range - FROM #grouped_interval AS gi - ORDER BY gi.total_max_logical_io_reads_mb DESC ) -INSERT #working_plans WITH (TABLOCK) - ( plan_id, query_id, pattern ) -SELECT TOP ( @sp_Top ) - qsp.plan_id, qsp.query_id, ''max logical reads'' -FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp -JOIN logical_reads_max AS dm -ON qsp.last_execution_time >= dm.start_range - AND qsp.last_execution_time < dm.end_range -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs -ON qsrs.plan_id = qsp.plan_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq -ON qsq.query_id = qsp.query_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt -ON qsqt.query_text_id = qsq.query_text_id -WHERE 1 = 1 - AND qsq.is_internal_query = 0 - AND qsp.query_plan IS NOT NULL - '; + RAISERROR(N'check_id 69: Column collation does not match database collation', 0,1) WITH NOWAIT; + WITH count_columns AS ( + SELECT [object_id], + database_id, + schema_name, + COUNT(*) AS column_count + FROM #IndexColumns ic + WHERE index_id IN (1,0) /*Heap or clustered only*/ + AND collation_name <> @collation + GROUP BY [object_id], + database_id, + schema_name + ) + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 69 AS check_id, + i.index_sanity_id, + 150 AS Priority, + N'Abnormal Psychology' AS findings_group, + N'Column Collation Does Not Match Database Collation' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, + i.db_schema_object_name + + N' has ' + CAST(column_count AS NVARCHAR(20)) + + N' column' + CASE WHEN column_count > 1 THEN 's' ELSE '' END + + N' with a different collation than the db collation of ' + + @collation AS details, + i.index_definition, + secret_columns, + ISNULL(i.index_usage_summary,''), + ISNULL(ip.index_size_summary,'') + FROM #IndexSanity i + JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id + JOIN count_columns AS cc ON i.[object_id]=cc.[object_id] + AND cc.database_id = i.database_id + AND cc.schema_name = i.schema_name + WHERE i.index_id IN (1,0) + ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); -SET @sql_select += @sql_where; + RAISERROR(N'check_id 70: Replicated columns', 0,1) WITH NOWAIT; + WITH count_columns AS ( + SELECT [object_id], + database_id, + schema_name, + COUNT(*) AS column_count, + SUM(CASE is_replicated WHEN 1 THEN 1 ELSE 0 END) AS replicated_column_count + FROM #IndexColumns ic + WHERE index_id IN (1,0) /*Heap or clustered only*/ + GROUP BY object_id, + database_id, + schema_name + ) + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 70 AS check_id, + i.index_sanity_id, + 200 AS Priority, + N'Abnormal Psychology' AS findings_group, + N'Replicated Columns' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, + i.db_schema_object_name + + N' has ' + CAST(replicated_column_count AS NVARCHAR(20)) + + N' out of ' + CAST(column_count AS NVARCHAR(20)) + + N' column' + CASE WHEN column_count > 1 THEN 's' ELSE '' END + + N' in one or more publications.' + AS details, + i.index_definition, + secret_columns, + ISNULL(i.index_usage_summary,''), + ISNULL(ip.index_size_summary,'') + FROM #IndexSanity i + JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id + JOIN count_columns AS cc ON i.[object_id]=cc.[object_id] + AND i.database_id = cc.database_id + AND i.schema_name = cc.schema_name + WHERE i.index_id IN (1,0) + AND replicated_column_count > 0 + ORDER BY i.db_schema_object_name DESC + OPTION ( RECOMPILE ); -SET @sql_select += N'ORDER BY qsrs.max_logical_io_reads DESC - OPTION (RECOMPILE); - '; + RAISERROR(N'check_id 71: Cascading updates or cascading deletes.', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary, more_info ) + SELECT 71 AS check_id, + NULL AS index_sanity_id, + 150 AS Priority, + N'Abnormal Psychology' AS findings_group, + N'Cascading Updates or Deletes' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, + N'Foreign Key ' + QUOTENAME(foreign_key_name) + + N' on ' + QUOTENAME(parent_object_name) + N'(' + LTRIM(parent_fk_columns) + N')' + + N' referencing ' + QUOTENAME(referenced_object_name) + N'(' + LTRIM(referenced_fk_columns) + N')' + + N' has settings:' + + CASE [delete_referential_action_desc] WHEN N'NO_ACTION' THEN N'' ELSE N' ON DELETE ' +[delete_referential_action_desc] END + + CASE [update_referential_action_desc] WHEN N'NO_ACTION' THEN N'' ELSE N' ON UPDATE ' + [update_referential_action_desc] END + AS details, + [fk].[database_name] AS index_definition, + N'N/A' AS secret_columns, + N'N/A' AS index_usage_summary, + N'N/A' AS index_size_summary, + (SELECT TOP 1 more_info FROM #IndexSanity i WHERE i.object_id=fk.parent_object_id AND i.database_id = fk.database_id AND i.schema_name = fk.schema_name) + AS more_info + FROM #ForeignKeys fk + WHERE ([delete_referential_action_desc] <> N'NO_ACTION' + OR [update_referential_action_desc] <> N'NO_ACTION') + OPTION ( RECOMPILE ); -IF @Debug = 1 - PRINT @sql_select; + RAISERROR(N'check_id 72: Unindexed foreign keys.', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary, more_info ) + SELECT 72 AS check_id, + NULL AS index_sanity_id, + 150 AS Priority, + N'Abnormal Psychology' AS findings_group, + N'Unindexed Foreign Keys' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, + N'Foreign Key ' + QUOTENAME(foreign_key_name) + + N' on ' + QUOTENAME(parent_object_name) + N'' + + N' referencing ' + QUOTENAME(referenced_object_name) + N'' + + N' does not appear to have a supporting index.' AS details, + N'N/A' AS index_definition, + N'N/A' AS secret_columns, + N'N/A' AS index_usage_summary, + N'N/A' AS index_size_summary, + (SELECT TOP 1 more_info FROM #IndexSanity i WHERE i.object_id=fk.parent_object_id AND i.database_id = fk.database_id AND i.schema_name = fk.schema_name) + AS more_info + FROM #UnindexedForeignKeys AS fk + OPTION ( RECOMPILE ); -IF @sql_select IS NULL - BEGIN - RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; - RETURN; - END; -EXEC sys.sp_executesql @stmt = @sql_select, - @params = @sp_params, - @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; - - -/*Get highest physical read plans*/ - -RAISERROR(N'Gathering highest physical read plans', 0, 1) WITH NOWAIT; - -SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; -SET @sql_select += N' -WITH physical_read_max -AS ( SELECT TOP 1 - gi.start_range, - gi.end_range - FROM #grouped_interval AS gi - ORDER BY gi.total_avg_physical_io_reads_mb DESC ) -INSERT #working_plans WITH (TABLOCK) - ( plan_id, query_id, pattern ) -SELECT TOP ( @sp_Top ) - qsp.plan_id, qsp.query_id, ''avg physical reads'' -FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp -JOIN physical_read_max AS dm -ON qsp.last_execution_time >= dm.start_range - AND qsp.last_execution_time < dm.end_range -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs -ON qsrs.plan_id = qsp.plan_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq -ON qsq.query_id = qsp.query_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt -ON qsqt.query_text_id = qsq.query_text_id -WHERE 1 = 1 - AND qsq.is_internal_query = 0 - AND qsp.query_plan IS NOT NULL - '; + RAISERROR(N'check_id 73: In-Memory OLTP', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 73 AS check_id, + i.index_sanity_id, + 150 AS Priority, + N'Abnormal Psychology' AS findings_group, + N'In-Memory OLTP' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, + i.db_schema_object_indexid AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + ISNULL(sz.index_size_summary,'') AS index_size_summary + FROM #IndexSanity AS i + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + WHERE i.is_in_memory_oltp = 1 + OPTION ( RECOMPILE ); -SET @sql_select += @sql_where; + RAISERROR(N'check_id 74: Identity column with unusual seed', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 74 AS check_id, + i.index_sanity_id, + 200 AS Priority, + N'Abnormal Psychology' AS findings_group, + N'Identity Column Using a Negative Seed or Increment Other Than 1' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, + i.db_schema_object_name + N'.' + QUOTENAME(ic.column_name) + + N' is an identity with type ' + ic.system_type_name + + N', last value of ' + + ISNULL((CONVERT(NVARCHAR(256),CAST(ic.last_value AS DECIMAL(38,0)), 1)),N'NULL') + + N', seed of ' + + ISNULL((CONVERT(NVARCHAR(256),CAST(ic.seed_value AS DECIMAL(38,0)), 1)),N'NULL') + + N', increment of ' + CAST(ic.increment_value AS NVARCHAR(256)) + + N', and range of ' + + CASE ic.system_type_name WHEN 'int' THEN N'+/- 2,147,483,647' + WHEN 'smallint' THEN N'+/- 32,768' + WHEN 'tinyint' THEN N'0 to 255' + ELSE 'unknown' + END + AS details, + i.index_definition, + secret_columns, + ISNULL(i.index_usage_summary,''), + ISNULL(ip.index_size_summary,'') + FROM #IndexSanity i + JOIN #IndexColumns ic ON + i.object_id=ic.object_id + AND i.database_id = ic.database_id + AND i.schema_name = ic.schema_name + AND i.index_id IN (0,1) /* heaps and cx only */ + AND ic.is_identity=1 + AND ic.system_type_name IN ('tinyint', 'smallint', 'int') + JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id + WHERE i.index_id IN (1,0) + AND (ic.seed_value < 0 OR ic.increment_value <> 1) + ORDER BY finding, details DESC + OPTION ( RECOMPILE ); -SET @sql_select += N'ORDER BY qsrs.avg_physical_io_reads DESC - OPTION (RECOMPILE); - '; + ---------------------------------------- + --Workaholics: Check_id 80-89 + ---------------------------------------- -IF @Debug = 1 - PRINT @sql_select; + RAISERROR(N'check_id 80: Most scanned indexes (index_usage_stats)', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) -IF @sql_select IS NULL - BEGIN - RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; - RETURN; - END; + --Workaholics according to index_usage_stats + --This isn't perfect: it mentions the number of scans present in a plan + --A "scan" isn't necessarily a full scan, but hey, we gotta do the best with what we've got. + --in the case of things like indexed views, the operator might be in the plan but never executed + SELECT TOP 5 + 80 AS check_id, + i.index_sanity_id AS index_sanity_id, + 200 AS Priority, + N'Workaholics' AS findings_group, + N'Scan-a-lots (index-usage-stats)' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/Workaholics' AS URL, + REPLACE(CONVERT( NVARCHAR(50),CAST(i.user_scans AS MONEY),1),'.00','') + + N' scans against ' + i.db_schema_object_indexid + + N'. Latest scan: ' + ISNULL(CAST(i.last_user_scan AS NVARCHAR(128)),'?') + N'. ' + + N'ScanFactor=' + CAST(((i.user_scans * iss.total_reserved_MB)/1000000.) AS NVARCHAR(256)) AS details, + ISNULL(i.key_column_names_with_sort_order,'N/A') AS index_definition, + ISNULL(i.secret_columns,'') AS secret_columns, + i.index_usage_summary AS index_usage_summary, + iss.index_size_summary AS index_size_summary + FROM #IndexSanity i + JOIN #IndexSanitySize iss ON i.index_sanity_id=iss.index_sanity_id + WHERE ISNULL(i.user_scans,0) > 0 + ORDER BY i.user_scans * iss.total_reserved_MB DESC + OPTION ( RECOMPILE ); -EXEC sys.sp_executesql @stmt = @sql_select, - @params = @sp_params, - @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; - - -SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; -SET @sql_select += N' -WITH physical_read_max -AS ( SELECT TOP 1 - gi.start_range, - gi.end_range - FROM #grouped_interval AS gi - ORDER BY gi.total_max_physical_io_reads_mb DESC ) -INSERT #working_plans WITH (TABLOCK) - ( plan_id, query_id, pattern ) -SELECT TOP ( @sp_Top ) - qsp.plan_id, qsp.query_id, ''max physical reads'' -FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp -JOIN physical_read_max AS dm -ON qsp.last_execution_time >= dm.start_range - AND qsp.last_execution_time < dm.end_range -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs -ON qsrs.plan_id = qsp.plan_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq -ON qsq.query_id = qsp.query_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt -ON qsqt.query_text_id = qsq.query_text_id -WHERE 1 = 1 - AND qsq.is_internal_query = 0 - AND qsp.query_plan IS NOT NULL - '; + RAISERROR(N'check_id 81: Top recent accesses (op stats)', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + --Workaholics according to index_operational_stats + --This isn't perfect either: range_scan_count contains full scans, partial scans, even seeks in nested loop ops + --But this can help bubble up some most-accessed tables + SELECT TOP 5 + 81 AS check_id, + i.index_sanity_id AS index_sanity_id, + 200 AS Priority, + N'Workaholics' AS findings_group, + N'Top Recent Accesses (index-op-stats)' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/Workaholics' AS URL, + ISNULL(REPLACE( + CONVERT(NVARCHAR(50),CAST((iss.total_range_scan_count + iss.total_singleton_lookup_count) AS MONEY),1), + N'.00',N'') + + N' uses of ' + i.db_schema_object_indexid + N'. ' + + REPLACE(CONVERT(NVARCHAR(50), CAST(iss.total_range_scan_count AS MONEY),1),N'.00',N'') + N' scans or seeks. ' + + REPLACE(CONVERT(NVARCHAR(50), CAST(iss.total_singleton_lookup_count AS MONEY), 1),N'.00',N'') + N' singleton lookups. ' + + N'OpStatsFactor=' + CAST(((((iss.total_range_scan_count + iss.total_singleton_lookup_count) * iss.total_reserved_MB))/1000000.) AS VARCHAR(256)),'') AS details, + ISNULL(i.key_column_names_with_sort_order,'N/A') AS index_definition, + ISNULL(i.secret_columns,'') AS secret_columns, + i.index_usage_summary AS index_usage_summary, + iss.index_size_summary AS index_size_summary + FROM #IndexSanity i + JOIN #IndexSanitySize iss ON i.index_sanity_id=iss.index_sanity_id + WHERE (ISNULL(iss.total_range_scan_count,0) > 0 OR ISNULL(iss.total_singleton_lookup_count,0) > 0) + ORDER BY ((iss.total_range_scan_count + iss.total_singleton_lookup_count) * iss.total_reserved_MB) DESC + OPTION ( RECOMPILE ); -SET @sql_select += @sql_where; -SET @sql_select += N'ORDER BY qsrs.max_physical_io_reads DESC - OPTION (RECOMPILE); - '; -IF @Debug = 1 - PRINT @sql_select; + RAISERROR(N'check_id 93: Statistics with filters', 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 93 AS check_id, + 200 AS Priority, + 'Functioning Statistaholics' AS findings_group, + 'Filter Fixation', + s.database_name, + 'https://www.brentozar.com/go/stats' AS URL, + 'The statistic ' + QUOTENAME(s.statistics_name) + ' is filtered on [' + s.filter_definition + ']. It could be part of a filtered index, or just a filtered statistic. This is purely informational.' 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_filter = 1 + OPTION ( RECOMPILE ); -IF @sql_select IS NULL - BEGIN - RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; - RETURN; - END; -EXEC sys.sp_executesql @stmt = @sql_select, - @params = @sp_params, - @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; - - -/*Get highest logical write plans*/ - -RAISERROR(N'Gathering highest write plans', 0, 1) WITH NOWAIT; - -SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; -SET @sql_select += N' -WITH logical_writes_max -AS ( SELECT TOP 1 - gi.start_range, - gi.end_range - FROM #grouped_interval AS gi - ORDER BY gi.total_avg_logical_io_writes_mb DESC ) -INSERT #working_plans WITH (TABLOCK) - ( plan_id, query_id, pattern ) -SELECT TOP ( @sp_Top ) - qsp.plan_id, qsp.query_id, ''avg writes'' -FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp -JOIN logical_writes_max AS dm -ON qsp.last_execution_time >= dm.start_range - AND qsp.last_execution_time < dm.end_range -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs -ON qsrs.plan_id = qsp.plan_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq -ON qsq.query_id = qsp.query_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt -ON qsqt.query_text_id = qsq.query_text_id -WHERE 1 = 1 - AND qsq.is_internal_query = 0 - AND qsp.query_plan IS NOT NULL - '; + RAISERROR(N'check_id 100: Computed Columns that are not Persisted.', 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 100 AS check_id, + 200 AS Priority, + 'Cold Calculators' AS findings_group, + 'Definition Defeatists' AS finding, + cc.database_name, + '' AS URL, + 'The computed column ' + QUOTENAME(cc.column_name) + ' on ' + QUOTENAME(cc.schema_name) + '.' + QUOTENAME(cc.table_name) + ' is not persisted, which means it will be calculated when a query runs.' + + 'You can change this with the following command, if the definition is deterministic: ALTER TABLE ' + QUOTENAME(cc.schema_name) + '.' + QUOTENAME(cc.table_name) + ' ALTER COLUMN ' + cc.column_name + + ' ADD PERSISTED' AS details, + cc.column_definition, + 'N/A' AS secret_columns, + 'N/A' AS index_usage_summary, + 'N/A' AS index_size_summary + FROM #ComputedColumns AS cc + WHERE cc.is_persisted = 0 + OPTION ( RECOMPILE ); -SET @sql_select += @sql_where; + RAISERROR(N'check_id 110: Temporal Tables.', 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 ) -SET @sql_select += N'ORDER BY qsrs.avg_logical_io_writes DESC - OPTION (RECOMPILE); - '; + SELECT 110 AS check_id, + 200 AS Priority, + 'Abnormal Psychology' AS findings_group, + 'Temporal Tables', + t.database_name, + '' AS URL, + 'The table ' + QUOTENAME(t.schema_name) + '.' + QUOTENAME(t.table_name) + ' is a temporal table, with rows versioned in ' + + QUOTENAME(t.history_schema_name) + '.' + QUOTENAME(t.history_table_name) + ' on History columns ' + QUOTENAME(t.start_column_name) + ' and ' + QUOTENAME(t.end_column_name) + '.' + AS details, + '' AS index_definition, + 'N/A' AS secret_columns, + 'N/A' AS index_usage_summary, + 'N/A' AS index_size_summary + FROM #TemporalTables AS t + OPTION ( RECOMPILE ); -IF @Debug = 1 - PRINT @sql_select; + RAISERROR(N'check_id 121: Optimized For Sequental Keys.', 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 ) -IF @sql_select IS NULL - BEGIN - RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; - RETURN; - END; + SELECT 121 AS check_id, + 200 AS Priority, + 'Medicated Indexes' AS findings_group, + 'Optimized For Sequential Keys', + i.database_name, + '' AS URL, + 'The table ' + QUOTENAME(i.schema_name) + '.' + QUOTENAME(i.object_name) + ' is optimized for sequential keys.' AS details, + '' AS index_definition, + 'N/A' AS secret_columns, + 'N/A' AS index_usage_summary, + 'N/A' AS index_size_summary + FROM #IndexSanity AS i + WHERE i.optimize_for_sequential_key = 1 + OPTION ( RECOMPILE ); -EXEC sys.sp_executesql @stmt = @sql_select, - @params = @sp_params, - @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; - - -SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; -SET @sql_select += N' -WITH logical_writes_max -AS ( SELECT TOP 1 - gi.start_range, - gi.end_range - FROM #grouped_interval AS gi - ORDER BY gi.total_max_logical_io_writes_mb DESC ) -INSERT #working_plans WITH (TABLOCK) - ( plan_id, query_id, pattern ) -SELECT TOP ( @sp_Top ) - qsp.plan_id, qsp.query_id, ''max writes'' -FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp -JOIN logical_writes_max AS dm -ON qsp.last_execution_time >= dm.start_range - AND qsp.last_execution_time < dm.end_range -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs -ON qsrs.plan_id = qsp.plan_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq -ON qsq.query_id = qsp.query_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt -ON qsqt.query_text_id = qsq.query_text_id -WHERE 1 = 1 - AND qsq.is_internal_query = 0 - AND qsp.query_plan IS NOT NULL - '; -SET @sql_select += @sql_where; -SET @sql_select += N'ORDER BY qsrs.max_logical_io_writes DESC - OPTION (RECOMPILE); - '; + END /* IF @Mode = 4 */ -IF @Debug = 1 - PRINT @sql_select; -IF @sql_select IS NULL - BEGIN - RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; - RETURN; - END; -EXEC sys.sp_executesql @stmt = @sql_select, - @params = @sp_params, - @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; - - -/*Get highest memory use plans*/ - -RAISERROR(N'Gathering highest memory use plans', 0, 1) WITH NOWAIT; - -SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; -SET @sql_select += N' -WITH memory_max -AS ( SELECT TOP 1 - gi.start_range, - gi.end_range - FROM #grouped_interval AS gi - ORDER BY gi.total_avg_query_max_used_memory_mb DESC ) -INSERT #working_plans WITH (TABLOCK) - ( plan_id, query_id, pattern ) -SELECT TOP ( @sp_Top ) - qsp.plan_id, qsp.query_id, ''avg memory'' -FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp -JOIN memory_max AS dm -ON qsp.last_execution_time >= dm.start_range - AND qsp.last_execution_time < dm.end_range -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs -ON qsrs.plan_id = qsp.plan_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq -ON qsq.query_id = qsp.query_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt -ON qsqt.query_text_id = qsq.query_text_id -WHERE 1 = 1 - AND qsq.is_internal_query = 0 - AND qsp.query_plan IS NOT NULL - '; -SET @sql_select += @sql_where; -SET @sql_select += N'ORDER BY qsrs.avg_query_max_used_memory DESC - OPTION (RECOMPILE); - '; -IF @Debug = 1 - PRINT @sql_select; -IF @sql_select IS NULL - BEGIN - RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; - RETURN; - END; + + RAISERROR(N'Insert a row to help people find help', 0,1) WITH NOWAIT; + IF DATEDIFF(MM, @VersionDate, GETDATE()) > 6 + BEGIN + INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, URL, details, index_definition, + index_usage_summary, index_size_summary ) + VALUES ( -1, 0 , + 'Outdated sp_BlitzIndex', 'sp_BlitzIndex is Over 6 Months Old', 'http://FirstResponderKit.org/', + 'Fine wine gets better with age, but this ' + @ScriptVersionName + ' is more like bad cheese. Time to get a new one.', + @DaysUptimeInsertValue,N'',N'' + ); + END; -EXEC sys.sp_executesql @stmt = @sql_select, - @params = @sp_params, - @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; - -SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; -SET @sql_select += N' -WITH memory_max -AS ( SELECT TOP 1 - gi.start_range, - gi.end_range - FROM #grouped_interval AS gi - ORDER BY gi.total_max_query_max_used_memory_mb DESC ) -INSERT #working_plans WITH (TABLOCK) - ( plan_id, query_id, pattern ) -SELECT TOP ( @sp_Top ) - qsp.plan_id, qsp.query_id, ''max memory'' -FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp -JOIN memory_max AS dm -ON qsp.last_execution_time >= dm.start_range - AND qsp.last_execution_time < dm.end_range -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs -ON qsrs.plan_id = qsp.plan_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq -ON qsq.query_id = qsp.query_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt -ON qsqt.query_text_id = qsq.query_text_id -WHERE 1 = 1 - AND qsq.is_internal_query = 0 - AND qsp.query_plan IS NOT NULL - '; + IF EXISTS(SELECT * FROM #BlitzIndexResults) + BEGIN + INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, URL, details, index_definition, + index_usage_summary, index_size_summary ) + VALUES ( -1, 0 , + @ScriptVersionName, + CASE WHEN @GetAllDatabases = 1 THEN N'All Databases' ELSE N'Database ' + QUOTENAME(@DatabaseName) + N' as of ' + CONVERT(NVARCHAR(16),GETDATE(),121) END, + N'From Your Community Volunteers' , N'http://FirstResponderKit.org' , + @DaysUptimeInsertValue,N'',N'' + ); + END; + ELSE IF @Mode = 0 OR (@GetAllDatabases = 1 AND @Mode <> 4) + BEGIN + INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, URL, details, index_definition, + index_usage_summary, index_size_summary ) + VALUES ( -1, 0 , + @ScriptVersionName, + CASE WHEN @GetAllDatabases = 1 THEN N'All Databases' ELSE N'Database ' + QUOTENAME(@DatabaseName) + N' as of ' + CONVERT(NVARCHAR(16),GETDATE(),121) END, + N'From Your Community Volunteers' , N'http://FirstResponderKit.org' , + @DaysUptimeInsertValue, N'',N'' + ); + INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, URL, details, index_definition, + index_usage_summary, index_size_summary ) + VALUES ( 1, 0 , + N'No Major Problems Found', + N'Nice Work!', + N'http://FirstResponderKit.org', + N'Consider running with @Mode = 4 in individual databases (not all) for more detailed diagnostics.', + N'The default Mode 0 only looks for very serious index issues.', + @DaysUptimeInsertValue, N'' + ); -SET @sql_select += @sql_where; + END; + ELSE + BEGIN + INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, URL, details, index_definition, + index_usage_summary, index_size_summary ) + VALUES ( -1, 0 , + @ScriptVersionName, + CASE WHEN @GetAllDatabases = 1 THEN N'All Databases' ELSE N'Database ' + QUOTENAME(@DatabaseName) + N' as of ' + CONVERT(NVARCHAR(16),GETDATE(),121) END, + N'From Your Community Volunteers' , N'http://FirstResponderKit.org' , + @DaysUptimeInsertValue, N'',N'' + ); + INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, URL, details, index_definition, + index_usage_summary, index_size_summary ) + VALUES ( 1, 0 , + N'No Problems Found', + N'Nice job! Or more likely, you have a nearly empty database.', + N'http://FirstResponderKit.org', 'Time to go read some blog posts.', + @DaysUptimeInsertValue, N'', N'' + ); -SET @sql_select += N'ORDER BY qsrs.max_query_max_used_memory DESC - OPTION (RECOMPILE); - '; + END; -IF @Debug = 1 - PRINT @sql_select; + RAISERROR(N'Returning results.', 0,1) WITH NOWAIT; + + /*Return results.*/ + IF (@ValidOutputLocation = 1 AND COALESCE(@OutputServerName, @OutputDatabaseName, @OutputSchemaName, @OutputTableName) IS NOT NULL) + BEGIN + IF NOT @SchemaExists = 1 + BEGIN + RAISERROR (N'Invalid schema name, data could not be saved.', 16, 0); + RETURN; + END -IF @sql_select IS NULL - BEGIN - RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; - RETURN; - END; + IF @TableExists = 0 + BEGIN + SET @StringToExecute = + N'CREATE TABLE @@@OutputDatabaseName@@@.@@@OutputSchemaName@@@.@@@OutputTableName@@@ + ( + [id] INT IDENTITY(1,1) NOT NULL, + [run_id] UNIQUEIDENTIFIER, + [run_datetime] DATETIME, + [server_name] NVARCHAR(128), + [priority] INT, + [finding] NVARCHAR(4000), + [database_name] NVARCHAR(128), + [details] NVARCHAR(MAX), + [index_definition] NVARCHAR(MAX), + [secret_columns] NVARCHAR(MAX), + [index_usage_summary] NVARCHAR(MAX), + [index_size_summary] NVARCHAR(MAX), + [more_info] NVARCHAR(MAX), + [url] NVARCHAR(MAX), + [create_tsql] NVARCHAR(MAX), + [sample_query_plan] XML, + CONSTRAINT [PK_ID_@@@RunID@@@] PRIMARY KEY CLUSTERED ([id] ASC) + );'; + + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputSchemaName@@@', @OutputSchemaName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@RunID@@@', @RunID); + + IF @ValidOutputServer = 1 + BEGIN + SET @StringToExecute = REPLACE(@StringToExecute,'''',''''''); + EXEC('EXEC('''+@StringToExecute+''') AT ' + @OutputServerName); + END; + ELSE + BEGIN + EXEC(@StringToExecute); + END; + END; /* @TableExists = 0 */ -EXEC sys.sp_executesql @stmt = @sql_select, - @params = @sp_params, - @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; - - -/*Get highest row count plans*/ - -RAISERROR(N'Gathering highest row count plans', 0, 1) WITH NOWAIT; - -SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; -SET @sql_select += N' -WITH rowcount_max -AS ( SELECT TOP 1 - gi.start_range, - gi.end_range - FROM #grouped_interval AS gi - ORDER BY gi.total_rowcount DESC ) -INSERT #working_plans WITH (TABLOCK) - ( plan_id, query_id, pattern ) -SELECT TOP ( @sp_Top ) - qsp.plan_id, qsp.query_id, ''avg rows'' -FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp -JOIN rowcount_max AS dm -ON qsp.last_execution_time >= dm.start_range - AND qsp.last_execution_time < dm.end_range -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs -ON qsrs.plan_id = qsp.plan_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq -ON qsq.query_id = qsp.query_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt -ON qsqt.query_text_id = qsq.query_text_id -WHERE 1 = 1 - AND qsq.is_internal_query = 0 - AND qsp.query_plan IS NOT NULL - '; + -- Re-check that table now exists (if not we failed creating it) + SET @TableExists = NULL; + EXEC sp_executesql @TableExistsSql, N'@TableExists BIT OUTPUT', @TableExists OUTPUT; + + IF NOT @TableExists = 1 + BEGIN + RAISERROR('Creation of the output table failed.', 16, 0); + RETURN; + END; -SET @sql_select += @sql_where; + SET @StringToExecute = + N'INSERT @@@OutputServerName@@@.@@@OutputDatabaseName@@@.@@@OutputSchemaName@@@.@@@OutputTableName@@@ + ( + [run_id], + [run_datetime], + [server_name], + [priority], + [finding], + [database_name], + [details], + [index_definition], + [secret_columns], + [index_usage_summary], + [index_size_summary], + [more_info], + [url], + [create_tsql], + [sample_query_plan] + ) + SELECT + ''@@@RunID@@@'', + ''@@@GETDATE@@@'', + ''@@@LocalServerName@@@'', + -- Below should be a copy/paste of the real query + -- Make sure all quotes are escaped + Priority, ISNULL(br.findings_group,N'''') + + CASE WHEN ISNULL(br.finding,N'''') <> N'''' THEN N'': '' ELSE N'''' END + + br.finding AS [Finding], + br.[database_name] AS [Database Name], + br.details AS [Details: schema.table.index(indexid)], + br.index_definition AS [Definition: [Property]] ColumnName {datatype maxbytes}], + ISNULL(br.secret_columns,'''') AS [Secret Columns], + br.index_usage_summary AS [Usage], + br.index_size_summary AS [Size], + COALESCE(br.more_info,sn.more_info,'''') AS [More Info], + br.URL, + COALESCE(br.create_tsql,ts.create_tsql,'''') AS [Create TSQL], + br.sample_query_plan AS [Sample Query Plan] + FROM #BlitzIndexResults br + LEFT JOIN #IndexSanity sn ON + br.index_sanity_id=sn.index_sanity_id + LEFT JOIN #IndexCreateTsql ts ON + br.index_sanity_id=ts.index_sanity_id + ORDER BY br.Priority ASC, br.check_id ASC, br.blitz_result_id ASC, br.findings_group ASC + OPTION (RECOMPILE);'; + + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputServerName@@@', @OutputServerName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputSchemaName@@@', @OutputSchemaName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@RunID@@@', @RunID); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@GETDATE@@@', GETDATE()); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@LocalServerName@@@', CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128))); + EXEC(@StringToExecute); -SET @sql_select += N'ORDER BY qsrs.avg_rowcount DESC + END + ELSE + BEGIN + IF(@OutputType <> 'NONE') + BEGIN + SELECT Priority, ISNULL(br.findings_group,N'') + + CASE WHEN ISNULL(br.finding,N'') <> N'' THEN N': ' ELSE N'' END + + br.finding AS [Finding], + br.[database_name] AS [Database Name], + br.details AS [Details: schema.table.index(indexid)], + br.index_definition AS [Definition: [Property]] ColumnName {datatype maxbytes}], + ISNULL(br.secret_columns,'') AS [Secret Columns], + br.index_usage_summary AS [Usage], + br.index_size_summary AS [Size], + COALESCE(br.more_info,sn.more_info,'') AS [More Info], + br.URL, + COALESCE(br.create_tsql,ts.create_tsql,'') AS [Create TSQL], + br.sample_query_plan AS [Sample Query Plan] + FROM #BlitzIndexResults br + LEFT JOIN #IndexSanity sn ON + br.index_sanity_id=sn.index_sanity_id + LEFT JOIN #IndexCreateTsql ts ON + br.index_sanity_id=ts.index_sanity_id + ORDER BY br.Priority ASC, br.check_id ASC, br.blitz_result_id ASC, br.findings_group ASC OPTION (RECOMPILE); - '; + END; -IF @Debug = 1 - PRINT @sql_select; + END; -IF @sql_select IS NULL - BEGIN - RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; - RETURN; - END; + END /* End @Mode=0 or 4 (diagnose)*/ -EXEC sys.sp_executesql @stmt = @sql_select, - @params = @sp_params, - @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; -IF @new_columns = 1 -BEGIN -RAISERROR(N'Gathering new 2017 new column info...', 0, 1) WITH NOWAIT; - -/*Get highest log byte count plans*/ - -RAISERROR(N'Gathering highest log byte use plans', 0, 1) WITH NOWAIT; - -SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; -SET @sql_select += N' -WITH rowcount_max -AS ( SELECT TOP 1 - gi.start_range, - gi.end_range - FROM #grouped_interval AS gi - ORDER BY gi.total_avg_log_bytes_mb DESC ) -INSERT #working_plans WITH (TABLOCK) - ( plan_id, query_id, pattern ) -SELECT TOP ( @sp_Top ) - qsp.plan_id, qsp.query_id, ''avg log bytes'' -FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp -JOIN rowcount_max AS dm -ON qsp.last_execution_time >= dm.start_range - AND qsp.last_execution_time < dm.end_range -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs -ON qsrs.plan_id = qsp.plan_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq -ON qsq.query_id = qsp.query_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt -ON qsqt.query_text_id = qsq.query_text_id -WHERE 1 = 1 - AND qsq.is_internal_query = 0 - AND qsp.query_plan IS NOT NULL - '; -SET @sql_select += @sql_where; -SET @sql_select += N'ORDER BY qsrs.avg_log_bytes_used DESC - OPTION (RECOMPILE); - '; -IF @Debug = 1 - PRINT @sql_select; -IF @sql_select IS NULL + ELSE IF (@Mode=1) /*Summarize*/ BEGIN - RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; - RETURN; - END; + --This mode is to give some overall stats on the database. + IF (@ValidOutputLocation = 1 AND COALESCE(@OutputServerName, @OutputDatabaseName, @OutputSchemaName, @OutputTableName) IS NOT NULL) + BEGIN -EXEC sys.sp_executesql @stmt = @sql_select, - @params = @sp_params, - @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; - -RAISERROR(N'Gathering highest log byte use plans', 0, 1) WITH NOWAIT; - -SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; -SET @sql_select += N' -WITH rowcount_max -AS ( SELECT TOP 1 - gi.start_range, - gi.end_range - FROM #grouped_interval AS gi - ORDER BY gi.total_max_log_bytes_mb DESC ) -INSERT #working_plans WITH (TABLOCK) - ( plan_id, query_id, pattern ) -SELECT TOP ( @sp_Top ) - qsp.plan_id, qsp.query_id, ''max log bytes'' -FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp -JOIN rowcount_max AS dm -ON qsp.last_execution_time >= dm.start_range - AND qsp.last_execution_time < dm.end_range -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs -ON qsrs.plan_id = qsp.plan_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq -ON qsq.query_id = qsp.query_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt -ON qsqt.query_text_id = qsq.query_text_id -WHERE 1 = 1 - AND qsq.is_internal_query = 0 - AND qsp.query_plan IS NOT NULL - '; + IF NOT @SchemaExists = 1 + BEGIN + RAISERROR (N'Invalid schema name, data could not be saved.', 16, 0); + RETURN; + END -SET @sql_select += @sql_where; + IF @TableExists = 0 + BEGIN + SET @StringToExecute = + N'CREATE TABLE @@@OutputDatabaseName@@@.@@@OutputSchemaName@@@.@@@OutputTableName@@@ + ( + [id] INT IDENTITY(1,1) NOT NULL, + [run_id] UNIQUEIDENTIFIER, + [run_datetime] DATETIME, + [server_name] NVARCHAR(128), + [database_name] NVARCHAR(128), + [object_count] INT, + [reserved_gb] NUMERIC(29,1), + [reserved_lob_gb] NUMERIC(29,1), + [reserved_row_overflow_gb] NUMERIC(29,1), + [clustered_table_count] INT, + [clustered_table_gb] NUMERIC(29,1), + [nc_index_count] INT, + [nc_index_gb] NUMERIC(29,1), + [table_nc_index_ratio] NUMERIC(29,1), + [heap_count] INT, + [heap_gb] NUMERIC(29,1), + [partioned_table_count] INT, + [partioned_nc_count] INT, + [partioned_gb] NUMERIC(29,1), + [filtered_index_count] INT, + [indexed_view_count] INT, + [max_table_row_count] INT, + [max_table_gb] NUMERIC(29,1), + [max_nc_index_gb] NUMERIC(29,1), + [table_count_over_1gb] INT, + [table_count_over_10gb] INT, + [table_count_over_100gb] INT, + [nc_index_count_over_1gb] INT, + [nc_index_count_over_10gb] INT, + [nc_index_count_over_100gb] INT, + [min_create_date] DATETIME, + [max_create_date] DATETIME, + [max_modify_date] DATETIME, + [display_order] INT, + CONSTRAINT [PK_ID_@@@RunID@@@] PRIMARY KEY CLUSTERED ([id] ASC) + );'; + + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputSchemaName@@@', @OutputSchemaName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@RunID@@@', @RunID); + + IF @ValidOutputServer = 1 + BEGIN + SET @StringToExecute = REPLACE(@StringToExecute,'''',''''''); + EXEC('EXEC('''+@StringToExecute+''') AT ' + @OutputServerName); + END; + ELSE + BEGIN + EXEC(@StringToExecute); + END; + END; /* @TableExists = 0 */ -SET @sql_select += N'ORDER BY qsrs.max_log_bytes_used DESC - OPTION (RECOMPILE); - '; + -- Re-check that table now exists (if not we failed creating it) + SET @TableExists = NULL; + EXEC sp_executesql @TableExistsSql, N'@TableExists BIT OUTPUT', @TableExists OUTPUT; + + IF NOT @TableExists = 1 + BEGIN + RAISERROR('Creation of the output table failed.', 16, 0); + RETURN; + END; + + SET @StringToExecute = + N'INSERT @@@OutputServerName@@@.@@@OutputDatabaseName@@@.@@@OutputSchemaName@@@.@@@OutputTableName@@@ + ( + [run_id], + [run_datetime], + [server_name], + [database_name], + [object_count], + [reserved_gb], + [reserved_lob_gb], + [reserved_row_overflow_gb], + [clustered_table_count], + [clustered_table_gb], + [nc_index_count], + [nc_index_gb], + [table_nc_index_ratio], + [heap_count], + [heap_gb], + [partioned_table_count], + [partioned_nc_count], + [partioned_gb], + [filtered_index_count], + [indexed_view_count], + [max_table_row_count], + [max_table_gb], + [max_nc_index_gb], + [table_count_over_1gb], + [table_count_over_10gb], + [table_count_over_100gb], + [nc_index_count_over_1gb], + [nc_index_count_over_10gb], + [nc_index_count_over_100gb], + [min_create_date], + [max_create_date], + [max_modify_date], + [display_order] + ) + SELECT ''@@@RunID@@@'', + ''@@@GETDATE@@@'', + ''@@@LocalServerName@@@'', + -- Below should be a copy/paste of the real query + -- Make sure all quotes are escaped + -- NOTE! information line is skipped from output and the query below + -- NOTE! initial columns are not casted to nvarchar due to not outputing informational line + DB_NAME(i.database_id) AS [Database Name], + COUNT(*) AS [Number Objects], + CAST(SUM(sz.total_reserved_MB)/ + 1024. AS NUMERIC(29,1)) AS [All GB], + CAST(SUM(sz.total_reserved_LOB_MB)/ + 1024. AS NUMERIC(29,1)) AS [LOB GB], + CAST(SUM(sz.total_reserved_row_overflow_MB)/ + 1024. AS NUMERIC(29,1)) AS [Row Overflow GB], + SUM(CASE WHEN index_id=1 THEN 1 ELSE 0 END) AS [Clustered Tables], + CAST(SUM(CASE WHEN index_id=1 THEN sz.total_reserved_MB ELSE 0 END) + /1024. AS NUMERIC(29,1)) AS [Clustered Tables GB], + SUM(CASE WHEN index_id NOT IN (0,1) THEN 1 ELSE 0 END) AS [NC Indexes], + CAST(SUM(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) + /1024. AS NUMERIC(29,1)) AS [NC Indexes GB], + CASE WHEN SUM(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) > 0 THEN + CAST(SUM(CASE WHEN index_id IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) + / SUM(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) AS NUMERIC(29,1)) + ELSE 0 END AS [ratio table: NC Indexes], + SUM(CASE WHEN index_id=0 THEN 1 ELSE 0 END) AS [Heaps], + CAST(SUM(CASE WHEN index_id=0 THEN sz.total_reserved_MB ELSE 0 END) + /1024. AS NUMERIC(29,1)) AS [Heaps GB], + SUM(CASE WHEN index_id IN (0,1) AND partition_key_column_name IS NOT NULL THEN 1 ELSE 0 END) AS [Partitioned Tables], + SUM(CASE WHEN index_id NOT IN (0,1) AND partition_key_column_name IS NOT NULL THEN 1 ELSE 0 END) AS [Partitioned NCs], + CAST(SUM(CASE WHEN partition_key_column_name IS NOT NULL THEN sz.total_reserved_MB ELSE 0 END)/1024. AS NUMERIC(29,1)) AS [Partitioned GB], + SUM(CASE WHEN filter_definition <> '''' THEN 1 ELSE 0 END) AS [Filtered Indexes], + SUM(CASE WHEN is_indexed_view=1 THEN 1 ELSE 0 END) AS [Indexed Views], + MAX(total_rows) AS [Max Row Count], + CAST(MAX(CASE WHEN index_id IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) + /1024. AS NUMERIC(29,1)) AS [Max Table GB], + CAST(MAX(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) + /1024. AS NUMERIC(29,1)) AS [Max NC Index GB], + SUM(CASE WHEN index_id IN (0,1) AND sz.total_reserved_MB > 1024 THEN 1 ELSE 0 END) AS [Count Tables > 1GB], + SUM(CASE WHEN index_id IN (0,1) AND sz.total_reserved_MB > 10240 THEN 1 ELSE 0 END) AS [Count Tables > 10GB], + SUM(CASE WHEN index_id IN (0,1) AND sz.total_reserved_MB > 102400 THEN 1 ELSE 0 END) AS [Count Tables > 100GB], + SUM(CASE WHEN index_id NOT IN (0,1) AND sz.total_reserved_MB > 1024 THEN 1 ELSE 0 END) AS [Count NCs > 1GB], + SUM(CASE WHEN index_id NOT IN (0,1) AND sz.total_reserved_MB > 10240 THEN 1 ELSE 0 END) AS [Count NCs > 10GB], + SUM(CASE WHEN index_id NOT IN (0,1) AND sz.total_reserved_MB > 102400 THEN 1 ELSE 0 END) AS [Count NCs > 100GB], + MIN(create_date) AS [Oldest Create Date], + MAX(create_date) AS [Most Recent Create Date], + MAX(modify_date) AS [Most Recent Modify Date], + 1 AS [Display Order] + FROM #IndexSanity AS i + --left join here so we don''t lose disabled nc indexes + LEFT JOIN #IndexSanitySize AS sz + ON i.index_sanity_id=sz.index_sanity_id + GROUP BY DB_NAME(i.database_id) + ORDER BY [Display Order] ASC + OPTION (RECOMPILE);'; + + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputServerName@@@', @OutputServerName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputSchemaName@@@', @OutputSchemaName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@RunID@@@', @RunID); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@GETDATE@@@', GETDATE()); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@LocalServerName@@@', CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128))); + EXEC(@StringToExecute); -IF @Debug = 1 - PRINT @sql_select; + END; /* @ValidOutputLocation = 1 */ + ELSE + BEGIN + IF(@OutputType <> 'NONE') + BEGIN -IF @sql_select IS NULL - BEGIN - RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; - RETURN; - END; + RAISERROR(N'@Mode=1, we are summarizing.', 0,1) WITH NOWAIT; -EXEC sys.sp_executesql @stmt = @sql_select, - @params = @sp_params, - @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; - - -/*Get highest tempdb use plans*/ - -RAISERROR(N'Gathering highest tempdb use plans', 0, 1) WITH NOWAIT; - -SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; -SET @sql_select += N' -WITH rowcount_max -AS ( SELECT TOP 1 - gi.start_range, - gi.end_range - FROM #grouped_interval AS gi - ORDER BY gi.total_avg_tempdb_space DESC ) -INSERT #working_plans WITH (TABLOCK) - ( plan_id, query_id, pattern ) -SELECT TOP ( @sp_Top ) - qsp.plan_id, qsp.query_id, ''avg tempdb space'' -FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp -JOIN rowcount_max AS dm -ON qsp.last_execution_time >= dm.start_range - AND qsp.last_execution_time < dm.end_range -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs -ON qsrs.plan_id = qsp.plan_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq -ON qsq.query_id = qsp.query_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt -ON qsqt.query_text_id = qsq.query_text_id -WHERE 1 = 1 - AND qsq.is_internal_query = 0 - AND qsp.query_plan IS NOT NULL - '; + SELECT DB_NAME(i.database_id) AS [Database Name], + CAST((COUNT(*)) AS NVARCHAR(256)) AS [Number Objects], + CAST(CAST(SUM(sz.total_reserved_MB)/ + 1024. AS NUMERIC(29,1)) AS NVARCHAR(500)) AS [All GB], + CAST(CAST(SUM(sz.total_reserved_LOB_MB)/ + 1024. AS NUMERIC(29,1)) AS NVARCHAR(500)) AS [LOB GB], + CAST(CAST(SUM(sz.total_reserved_row_overflow_MB)/ + 1024. AS NUMERIC(29,1)) AS NVARCHAR(500)) AS [Row Overflow GB], + CAST(SUM(CASE WHEN index_id=1 THEN 1 ELSE 0 END)AS NVARCHAR(50)) AS [Clustered Tables], + CAST(SUM(CASE WHEN index_id=1 THEN sz.total_reserved_MB ELSE 0 END) + /1024. AS NUMERIC(29,1)) AS [Clustered Tables GB], + SUM(CASE WHEN index_id NOT IN (0,1) THEN 1 ELSE 0 END) AS [NC Indexes], + CAST(SUM(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) + /1024. AS NUMERIC(29,1)) AS [NC Indexes GB], + CASE WHEN SUM(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) > 0 THEN + CAST(SUM(CASE WHEN index_id IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) + / SUM(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) AS NUMERIC(29,1)) + ELSE 0 END AS [ratio table: NC Indexes], + SUM(CASE WHEN index_id=0 THEN 1 ELSE 0 END) AS [Heaps], + CAST(SUM(CASE WHEN index_id=0 THEN sz.total_reserved_MB ELSE 0 END) + /1024. AS NUMERIC(29,1)) AS [Heaps GB], + SUM(CASE WHEN index_id IN (0,1) AND partition_key_column_name IS NOT NULL THEN 1 ELSE 0 END) AS [Partitioned Tables], + SUM(CASE WHEN index_id NOT IN (0,1) AND partition_key_column_name IS NOT NULL THEN 1 ELSE 0 END) AS [Partitioned NCs], + CAST(SUM(CASE WHEN partition_key_column_name IS NOT NULL THEN sz.total_reserved_MB ELSE 0 END)/1024. AS NUMERIC(29,1)) AS [Partitioned GB], + SUM(CASE WHEN filter_definition <> '' THEN 1 ELSE 0 END) AS [Filtered Indexes], + SUM(CASE WHEN is_indexed_view=1 THEN 1 ELSE 0 END) AS [Indexed Views], + MAX(total_rows) AS [Max Row Count], + CAST(MAX(CASE WHEN index_id IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) + /1024. AS NUMERIC(29,1)) AS [Max Table GB], + CAST(MAX(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) + /1024. AS NUMERIC(29,1)) AS [Max NC Index GB], + SUM(CASE WHEN index_id IN (0,1) AND sz.total_reserved_MB > 1024 THEN 1 ELSE 0 END) AS [Count Tables > 1GB], + SUM(CASE WHEN index_id IN (0,1) AND sz.total_reserved_MB > 10240 THEN 1 ELSE 0 END) AS [Count Tables > 10GB], + SUM(CASE WHEN index_id IN (0,1) AND sz.total_reserved_MB > 102400 THEN 1 ELSE 0 END) AS [Count Tables > 100GB], + SUM(CASE WHEN index_id NOT IN (0,1) AND sz.total_reserved_MB > 1024 THEN 1 ELSE 0 END) AS [Count NCs > 1GB], + SUM(CASE WHEN index_id NOT IN (0,1) AND sz.total_reserved_MB > 10240 THEN 1 ELSE 0 END) AS [Count NCs > 10GB], + SUM(CASE WHEN index_id NOT IN (0,1) AND sz.total_reserved_MB > 102400 THEN 1 ELSE 0 END) AS [Count NCs > 100GB], + MIN(create_date) AS [Oldest Create Date], + MAX(create_date) AS [Most Recent Create Date], + MAX(modify_date) AS [Most Recent Modify Date], + 1 AS [Display Order] + FROM #IndexSanity AS i + --left join here so we don't lose disabled nc indexes + LEFT JOIN #IndexSanitySize AS sz + ON i.index_sanity_id=sz.index_sanity_id + GROUP BY DB_NAME(i.database_id) + UNION ALL + SELECT CASE WHEN @GetAllDatabases = 1 THEN N'All Databases' ELSE N'Database ' + N' as of ' + CONVERT(NVARCHAR(16),GETDATE(),121) END, + @ScriptVersionName, + N'From Your Community Volunteers' , + N'http://FirstResponderKit.org' , + @DaysUptimeInsertValue, + NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL, + NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL, + NULL,NULL,0 AS display_order + ORDER BY [Display Order] ASC + OPTION (RECOMPILE); + END; + END; -SET @sql_select += @sql_where; + END; /* End @Mode=1 (summarize)*/ -SET @sql_select += N'ORDER BY qsrs.avg_tempdb_space_used DESC - OPTION (RECOMPILE); - '; -IF @Debug = 1 - PRINT @sql_select; -IF @sql_select IS NULL - BEGIN - RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; - RETURN; - END; -EXEC sys.sp_executesql @stmt = @sql_select, - @params = @sp_params, - @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; - -SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; -SET @sql_select += N' -WITH rowcount_max -AS ( SELECT TOP 1 - gi.start_range, - gi.end_range - FROM #grouped_interval AS gi - ORDER BY gi.total_max_tempdb_space DESC ) -INSERT #working_plans WITH (TABLOCK) - ( plan_id, query_id, pattern ) -SELECT TOP ( @sp_Top ) - qsp.plan_id, qsp.query_id, ''max tempdb space'' -FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp -JOIN rowcount_max AS dm -ON qsp.last_execution_time >= dm.start_range - AND qsp.last_execution_time < dm.end_range -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs -ON qsrs.plan_id = qsp.plan_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq -ON qsq.query_id = qsp.query_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt -ON qsqt.query_text_id = qsq.query_text_id -WHERE 1 = 1 - AND qsq.is_internal_query = 0 - AND qsp.query_plan IS NOT NULL - '; -SET @sql_select += @sql_where; -SET @sql_select += N'ORDER BY qsrs.max_tempdb_space_used DESC - OPTION (RECOMPILE); - '; -IF @Debug = 1 - PRINT @sql_select; -IF @sql_select IS NULL + ELSE IF (@Mode=2) /*Index Detail*/ BEGIN - RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; - RETURN; - END; - -EXEC sys.sp_executesql @stmt = @sql_select, - @params = @sp_params, - @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; - - -END; - - -/* -This rolls up the different patterns we find before deduplicating. - -The point of this is so we know if a query was gathered by one or more of the search queries - -*/ - -RAISERROR(N'Updating patterns', 0, 1) WITH NOWAIT; - -WITH patterns AS ( -SELECT wp.plan_id, wp.query_id, - pattern_path = STUFF((SELECT DISTINCT N', ' + wp2.pattern - FROM #working_plans AS wp2 - WHERE wp.plan_id = wp2.plan_id - AND wp.query_id = wp2.query_id - FOR XML PATH(N''), TYPE).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 2, N'') -FROM #working_plans AS wp -) -UPDATE wp -SET wp.pattern = patterns.pattern_path -FROM #working_plans AS wp -JOIN patterns -ON wp.plan_id = patterns.plan_id -AND wp.query_id = patterns.query_id -OPTION (RECOMPILE); - - -/* -This dedupes our results so we hopefully don't double-work the same plan -*/ - -RAISERROR(N'Deduplicating gathered plans', 0, 1) WITH NOWAIT; - -WITH dedupe AS ( -SELECT * , ROW_NUMBER() OVER (PARTITION BY wp.plan_id ORDER BY wp.plan_id) AS dupes -FROM #working_plans AS wp -) -DELETE dedupe -WHERE dedupe.dupes > 1 -OPTION (RECOMPILE); - -SET @msg = N'Removed ' + CONVERT(NVARCHAR(10), @@ROWCOUNT) + N' duplicate plan_ids.'; -RAISERROR(@msg, 0, 1) WITH NOWAIT; - + --This mode just spits out all the detail without filters. + --This supports slicing AND dicing in Excel + RAISERROR(N'@Mode=2, here''s the details on existing indexes.', 0,1) WITH NOWAIT; -/* -This gathers data for the #working_metrics table -*/ + IF (@ValidOutputLocation = 1 AND COALESCE(@OutputServerName, @OutputDatabaseName, @OutputSchemaName, @OutputTableName) IS NOT NULL) + BEGIN + IF @SchemaExists = 1 + BEGIN + IF @TableExists = 0 + BEGIN + SET @StringToExecute = + N'CREATE TABLE @@@OutputDatabaseName@@@.@@@OutputSchemaName@@@.@@@OutputTableName@@@ + ( + [id] INT IDENTITY(1,1) NOT NULL, + [run_id] UNIQUEIDENTIFIER, + [run_datetime] DATETIME, + [server_name] NVARCHAR(128), + [database_name] NVARCHAR(128), + [schema_name] NVARCHAR(128), + [table_name] NVARCHAR(128), + [index_name] NVARCHAR(128), + [Drop_Tsql] NVARCHAR(MAX), + [Create_Tsql] NVARCHAR(MAX), + [index_id] INT, + [db_schema_object_indexid] NVARCHAR(500), + [object_type] NVARCHAR(15), + [index_definition] NVARCHAR(MAX), + [key_column_names_with_sort_order] NVARCHAR(MAX), + [count_key_columns] INT, + [include_column_names] NVARCHAR(MAX), + [count_included_columns] INT, + [secret_columns] NVARCHAR(MAX), + [count_secret_columns] INT, + [partition_key_column_name] NVARCHAR(MAX), + [filter_definition] NVARCHAR(MAX), + [is_indexed_view] BIT, + [is_primary_key] BIT, + [is_unique_constraint] BIT, + [is_XML] BIT, + [is_spatial] BIT, + [is_NC_columnstore] BIT, + [is_CX_columnstore] BIT, + [is_in_memory_oltp] BIT, + [is_disabled] BIT, + [is_hypothetical] BIT, + [is_padded] BIT, + [fill_factor] INT, + [is_referenced_by_foreign_key] BIT, + [last_user_seek] DATETIME, + [last_user_scan] DATETIME, + [last_user_lookup] DATETIME, + [last_user_update] DATETIME, + [total_reads] BIGINT, + [user_updates] BIGINT, + [reads_per_write] MONEY, + [index_usage_summary] NVARCHAR(200), + [total_singleton_lookup_count] BIGINT, + [total_range_scan_count] BIGINT, + [total_leaf_delete_count] BIGINT, + [total_leaf_update_count] BIGINT, + [index_op_stats] NVARCHAR(200), + [partition_count] INT, + [total_rows] BIGINT, + [total_reserved_MB] NUMERIC(29,2), + [total_reserved_LOB_MB] NUMERIC(29,2), + [total_reserved_row_overflow_MB] NUMERIC(29,2), + [index_size_summary] NVARCHAR(300), + [total_row_lock_count] BIGINT, + [total_row_lock_wait_count] BIGINT, + [total_row_lock_wait_in_ms] BIGINT, + [avg_row_lock_wait_in_ms] BIGINT, + [total_page_lock_count] BIGINT, + [total_page_lock_wait_count] BIGINT, + [total_page_lock_wait_in_ms] BIGINT, + [avg_page_lock_wait_in_ms] BIGINT, + [total_index_lock_promotion_attempt_count] BIGINT, + [total_index_lock_promotion_count] BIGINT, + [total_forwarded_fetch_count] BIGINT, + [data_compression_desc] NVARCHAR(4000), + [page_latch_wait_count] BIGINT, + [page_latch_wait_in_ms] BIGINT, + [page_io_latch_wait_count] BIGINT, + [page_io_latch_wait_in_ms] BIGINT, + [create_date] DATETIME, + [modify_date] DATETIME, + [more_info] NVARCHAR(500), + [display_order] INT, + CONSTRAINT [PK_ID_@@@RunID@@@] PRIMARY KEY CLUSTERED ([id] ASC) + );'; + + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputSchemaName@@@', @OutputSchemaName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@RunID@@@', @RunID); + + IF @ValidOutputServer = 1 + BEGIN + SET @StringToExecute = REPLACE(@StringToExecute,'''',''''''); + EXEC('EXEC('''+@StringToExecute+''') AT ' + @OutputServerName); + END; + ELSE + BEGIN + EXEC(@StringToExecute); + END; + END; /* @TableExists = 0 */ + + -- Re-check that table now exists (if not we failed creating it) + SET @TableExists = NULL; + EXEC sp_executesql @TableExistsSql, N'@TableExists BIT OUTPUT', @TableExists OUTPUT; + + IF @TableExists = 1 + BEGIN + SET @StringToExecute = + N'INSERT @@@OutputServerName@@@.@@@OutputDatabaseName@@@.@@@OutputSchemaName@@@.@@@OutputTableName@@@ + ( + [run_id], + [run_datetime], + [server_name], + [database_name], + [schema_name], + [table_name], + [index_name], + [Drop_Tsql], + [Create_Tsql], + [index_id], + [db_schema_object_indexid], + [object_type], + [index_definition], + [key_column_names_with_sort_order], + [count_key_columns], + [include_column_names], + [count_included_columns], + [secret_columns], + [count_secret_columns], + [partition_key_column_name], + [filter_definition], + [is_indexed_view], + [is_primary_key], + [is_unique_constraint], + [is_XML], + [is_spatial], + [is_NC_columnstore], + [is_CX_columnstore], + [is_in_memory_oltp], + [is_disabled], + [is_hypothetical], + [is_padded], + [fill_factor], + [is_referenced_by_foreign_key], + [last_user_seek], + [last_user_scan], + [last_user_lookup], + [last_user_update], + [total_reads], + [user_updates], + [reads_per_write], + [index_usage_summary], + [total_singleton_lookup_count], + [total_range_scan_count], + [total_leaf_delete_count], + [total_leaf_update_count], + [index_op_stats], + [partition_count], + [total_rows], + [total_reserved_MB], + [total_reserved_LOB_MB], + [total_reserved_row_overflow_MB], + [index_size_summary], + [total_row_lock_count], + [total_row_lock_wait_count], + [total_row_lock_wait_in_ms], + [avg_row_lock_wait_in_ms], + [total_page_lock_count], + [total_page_lock_wait_count], + [total_page_lock_wait_in_ms], + [avg_page_lock_wait_in_ms], + [total_index_lock_promotion_attempt_count], + [total_index_lock_promotion_count], + [total_forwarded_fetch_count], + [data_compression_desc], + [page_latch_wait_count], + [page_latch_wait_in_ms], + [page_io_latch_wait_count], + [page_io_latch_wait_in_ms], + [create_date], + [modify_date], + [more_info], + [display_order] + ) + SELECT ''@@@RunID@@@'', + ''@@@GETDATE@@@'', + ''@@@LocalServerName@@@'', + -- Below should be a copy/paste of the real query + -- Make sure all quotes are escaped + i.[database_name] AS [Database Name], + i.[schema_name] AS [Schema Name], + i.[object_name] AS [Object Name], + ISNULL(i.index_name, '''') AS [Index Name], + CASE + WHEN i.is_primary_key = 1 AND i.index_definition <> ''[HEAP]'' + THEN N''--ALTER TABLE '' + QUOTENAME(i.[database_name]) + N''.'' + QUOTENAME(i.[schema_name]) + N''.'' + QUOTENAME(i.[object_name]) + + N'' DROP CONSTRAINT '' + QUOTENAME(i.index_name) + N'';'' + WHEN i.is_primary_key = 0 AND i.is_unique_constraint = 1 AND i.index_definition <> ''[HEAP]'' + THEN N''--ALTER TABLE '' + QUOTENAME(i.[database_name]) + N''.'' + QUOTENAME(i.[schema_name]) + N''.'' + QUOTENAME(i.[object_name]) + + N'' DROP CONSTRAINT '' + QUOTENAME(i.index_name) + N'';'' + WHEN i.is_primary_key = 0 AND i.index_definition <> ''[HEAP]'' + THEN N''--DROP INDEX ''+ QUOTENAME(i.index_name) + N'' ON '' + QUOTENAME(i.[database_name]) + N''.'' + + QUOTENAME(i.[schema_name]) + N''.'' + QUOTENAME(i.[object_name]) + N'';'' + ELSE N'''' + END AS [Drop TSQL], + CASE + WHEN i.index_definition = ''[HEAP]'' THEN N'''' + ELSE N''--'' + ict.create_tsql END AS [Create TSQL], + CAST(i.index_id AS NVARCHAR(10))AS [Index ID], + db_schema_object_indexid AS [Details: schema.table.index(indexid)], + CASE WHEN index_id IN ( 1, 0 ) THEN ''TABLE'' + ELSE ''NonClustered'' + END AS [Object Type], + LEFT(index_definition,4000) AS [Definition: [Property]] ColumnName {datatype maxbytes}], + ISNULL(LTRIM(key_column_names_with_sort_order), '''') AS [Key Column Names With Sort], + ISNULL(count_key_columns, 0) AS [Count Key Columns], + ISNULL(include_column_names, '''') AS [Include Column Names], + ISNULL(count_included_columns,0) AS [Count Included Columns], + ISNULL(secret_columns,'''') AS [Secret Column Names], + ISNULL(count_secret_columns,0) AS [Count Secret Columns], + ISNULL(partition_key_column_name, '''') AS [Partition Key Column Name], + ISNULL(filter_definition, '''') AS [Filter Definition], + is_indexed_view AS [Is Indexed View], + is_primary_key AS [Is Primary Key], + is_unique_constraint AS [Is Unique Constraint], + is_XML AS [Is XML], + is_spatial AS [Is Spatial], + is_NC_columnstore AS [Is NC Columnstore], + is_CX_columnstore AS [Is CX Columnstore], + is_in_memory_oltp AS [Is In-Memory OLTP], + is_disabled AS [Is Disabled], + is_hypothetical AS [Is Hypothetical], + is_padded AS [Is Padded], + fill_factor AS [Fill Factor], + is_referenced_by_foreign_key AS [Is Reference by Foreign Key], + last_user_seek AS [Last User Seek], + last_user_scan AS [Last User Scan], + last_user_lookup AS [Last User Lookup], + last_user_update AS [Last User Update], + total_reads AS [Total Reads], + user_updates AS [User Updates], + reads_per_write AS [Reads Per Write], + index_usage_summary AS [Index Usage], + sz.total_singleton_lookup_count AS [Singleton Lookups], + sz.total_range_scan_count AS [Range Scans], + sz.total_leaf_delete_count AS [Leaf Deletes], + sz.total_leaf_update_count AS [Leaf Updates], + sz.index_op_stats AS [Index Op Stats], + sz.partition_count AS [Partition Count], + sz.total_rows AS [Rows], + sz.total_reserved_MB AS [Reserved MB], + sz.total_reserved_LOB_MB AS [Reserved LOB MB], + sz.total_reserved_row_overflow_MB AS [Reserved Row Overflow MB], + sz.index_size_summary AS [Index Size], + sz.total_row_lock_count AS [Row Lock Count], + sz.total_row_lock_wait_count AS [Row Lock Wait Count], + sz.total_row_lock_wait_in_ms AS [Row Lock Wait ms], + sz.avg_row_lock_wait_in_ms AS [Avg Row Lock Wait ms], + sz.total_page_lock_count AS [Page Lock Count], + sz.total_page_lock_wait_count AS [Page Lock Wait Count], + sz.total_page_lock_wait_in_ms AS [Page Lock Wait ms], + sz.avg_page_lock_wait_in_ms AS [Avg Page Lock Wait ms], + sz.total_index_lock_promotion_attempt_count AS [Lock Escalation Attempts], + sz.total_index_lock_promotion_count AS [Lock Escalations], + sz.total_forwarded_fetch_count AS [Forwarded Fetches], + sz.data_compression_desc AS [Data Compression], + sz.page_latch_wait_count, + sz.page_latch_wait_in_ms, + sz.page_io_latch_wait_count, + sz.page_io_latch_wait_in_ms, + i.create_date AS [Create Date], + i.modify_date AS [Modify Date], + more_info AS [More Info], + 1 AS [Display Order] + FROM #IndexSanity AS i + LEFT JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id + LEFT JOIN #IndexCreateTsql AS ict ON i.index_sanity_id = ict.index_sanity_id + ORDER BY [Database Name], [Schema Name], [Object Name], [Index ID] + OPTION (RECOMPILE);'; + + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputServerName@@@', @OutputServerName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputSchemaName@@@', @OutputSchemaName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@RunID@@@', @RunID); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@GETDATE@@@', GETDATE()); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@LocalServerName@@@', CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128))); + EXEC(@StringToExecute); + END; /* @TableExists = 1 */ + ELSE + RAISERROR('Creation of the output table failed.', 16, 0); + END; /* @TableExists = 0 */ + ELSE + RAISERROR (N'Invalid schema name, data could not be saved.', 16, 0); + END; /* @ValidOutputLocation = 1 */ + ELSE + + IF(@OutputType <> 'NONE') + BEGIN + SELECT i.[database_name] AS [Database Name], + i.[schema_name] AS [Schema Name], + i.[object_name] AS [Object Name], + ISNULL(i.index_name, '') AS [Index Name], + CAST(i.index_id AS NVARCHAR(10))AS [Index ID], + db_schema_object_indexid AS [Details: schema.table.index(indexid)], + CASE WHEN index_id IN ( 1, 0 ) THEN 'TABLE' + ELSE 'NonClustered' + END AS [Object Type], + index_definition AS [Definition: [Property]] ColumnName {datatype maxbytes}], + ISNULL(LTRIM(key_column_names_with_sort_order), '') AS [Key Column Names With Sort], + ISNULL(count_key_columns, 0) AS [Count Key Columns], + ISNULL(include_column_names, '') AS [Include Column Names], + ISNULL(count_included_columns,0) AS [Count Included Columns], + ISNULL(secret_columns,'') AS [Secret Column Names], + ISNULL(count_secret_columns,0) AS [Count Secret Columns], + ISNULL(partition_key_column_name, '') AS [Partition Key Column Name], + ISNULL(filter_definition, '') AS [Filter Definition], + is_indexed_view AS [Is Indexed View], + is_primary_key AS [Is Primary Key], + is_unique_constraint AS [Is Unique Constraint] , + is_XML AS [Is XML], + is_spatial AS [Is Spatial], + is_NC_columnstore AS [Is NC Columnstore], + is_CX_columnstore AS [Is CX Columnstore], + is_in_memory_oltp AS [Is In-Memory OLTP], + is_disabled AS [Is Disabled], + is_hypothetical AS [Is Hypothetical], + is_padded AS [Is Padded], + fill_factor AS [Fill Factor], + is_referenced_by_foreign_key AS [Is Reference by Foreign Key], + last_user_seek AS [Last User Seek], + last_user_scan AS [Last User Scan], + last_user_lookup AS [Last User Lookup], + last_user_update AS [Last User Update], + total_reads AS [Total Reads], + user_updates AS [User Updates], + reads_per_write AS [Reads Per Write], + index_usage_summary AS [Index Usage], + sz.total_singleton_lookup_count AS [Singleton Lookups], + sz.total_range_scan_count AS [Range Scans], + sz.total_leaf_delete_count AS [Leaf Deletes], + sz.total_leaf_update_count AS [Leaf Updates], + sz.index_op_stats AS [Index Op Stats], + sz.partition_count AS [Partition Count], + sz.total_rows AS [Rows], + sz.total_reserved_MB AS [Reserved MB], + sz.total_reserved_LOB_MB AS [Reserved LOB MB], + sz.total_reserved_row_overflow_MB AS [Reserved Row Overflow MB], + sz.index_size_summary AS [Index Size], + sz.total_row_lock_count AS [Row Lock Count], + sz.total_row_lock_wait_count AS [Row Lock Wait Count], + sz.total_row_lock_wait_in_ms AS [Row Lock Wait ms], + sz.avg_row_lock_wait_in_ms AS [Avg Row Lock Wait ms], + sz.total_page_lock_count AS [Page Lock Count], + sz.total_page_lock_wait_count AS [Page Lock Wait Count], + sz.total_page_lock_wait_in_ms AS [Page Lock Wait ms], + sz.avg_page_lock_wait_in_ms AS [Avg Page Lock Wait ms], + sz.total_index_lock_promotion_attempt_count AS [Lock Escalation Attempts], + sz.total_index_lock_promotion_count AS [Lock Escalations], + sz.page_latch_wait_count AS [Page Latch Wait Count], + sz.page_latch_wait_in_ms AS [Page Latch Wait ms], + sz.page_io_latch_wait_count AS [Page IO Latch Wait Count], + sz.page_io_latch_wait_in_ms as [Page IO Latch Wait ms], + sz.total_forwarded_fetch_count AS [Forwarded Fetches], + sz.data_compression_desc AS [Data Compression], + i.create_date AS [Create Date], + i.modify_date AS [Modify Date], + more_info AS [More Info], + CASE + WHEN i.is_primary_key = 1 AND i.index_definition <> '[HEAP]' + THEN N'--ALTER TABLE ' + QUOTENAME(i.[database_name]) + N'.' + QUOTENAME(i.[schema_name]) + N'.' + QUOTENAME(i.[object_name]) + + N' DROP CONSTRAINT ' + QUOTENAME(i.index_name) + N';' + WHEN i.is_primary_key = 0 AND i.is_unique_constraint = 1 AND i.index_definition <> '[HEAP]' + THEN N'--ALTER TABLE ' + QUOTENAME(i.[database_name]) + N'.' + QUOTENAME(i.[schema_name]) + N'.' + QUOTENAME(i.[object_name]) + + N' DROP CONSTRAINT ' + QUOTENAME(i.index_name) + N';' + WHEN i.is_primary_key = 0 AND i.index_definition <> '[HEAP]' + THEN N'--DROP INDEX '+ QUOTENAME(i.index_name) + N' ON ' + QUOTENAME(i.[database_name]) + N'.' + + QUOTENAME(i.[schema_name]) + N'.' + QUOTENAME(i.[object_name]) + N';' + ELSE N'' + END AS [Drop TSQL], + CASE + WHEN i.index_definition = '[HEAP]' THEN N'' + ELSE N'--' + ict.create_tsql END AS [Create TSQL], + 1 AS [Display Order] + INTO #Mode2Temp + FROM #IndexSanity AS i --left join here so we don't lose disabled nc indexes + LEFT JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id + LEFT JOIN #IndexCreateTsql AS ict ON i.index_sanity_id = ict.index_sanity_id + OPTION(RECOMPILE); -RAISERROR(N'Collecting worker metrics', 0, 1) WITH NOWAIT; - -SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; -SET @sql_select += N' -SELECT ' + QUOTENAME(@DatabaseName, '''') + N' AS database_name, wp.plan_id, wp.query_id, - QUOTENAME(object_schema_name(qsq.object_id, DB_ID(' + QUOTENAME(@DatabaseName, '''') + N'))) + ''.'' + - QUOTENAME(object_name(qsq.object_id, DB_ID(' + QUOTENAME(@DatabaseName, '''') + N'))) AS proc_or_function_name, - qsq.batch_sql_handle, qsq.query_hash, qsq.query_parameterization_type_desc, qsq.count_compiles, - (qsq.avg_compile_duration / 1000.), - (qsq.last_compile_duration / 1000.), - (qsq.avg_bind_duration / 1000.), - (qsq.last_bind_duration / 1000.), - (qsq.avg_bind_cpu_time / 1000.), - (qsq.last_bind_cpu_time / 1000.), - (qsq.avg_optimize_duration / 1000.), - (qsq.last_optimize_duration / 1000.), - (qsq.avg_optimize_cpu_time / 1000.), - (qsq.last_optimize_cpu_time / 1000.), - (qsq.avg_compile_memory_kb / 1024.), - (qsq.last_compile_memory_kb / 1024.), - qsrs.execution_type_desc, qsrs.first_execution_time, qsrs.last_execution_time, qsrs.count_executions, - (qsrs.avg_duration / 1000.), - (qsrs.last_duration / 1000.), - (qsrs.min_duration / 1000.), - (qsrs.max_duration / 1000.), - (qsrs.avg_cpu_time / 1000.), - (qsrs.last_cpu_time / 1000.), - (qsrs.min_cpu_time / 1000.), - (qsrs.max_cpu_time / 1000.), - ((qsrs.avg_logical_io_reads * 8 ) / 1024.), - ((qsrs.last_logical_io_reads * 8 ) / 1024.), - ((qsrs.min_logical_io_reads * 8 ) / 1024.), - ((qsrs.max_logical_io_reads * 8 ) / 1024.), - ((qsrs.avg_logical_io_writes * 8 ) / 1024.), - ((qsrs.last_logical_io_writes * 8 ) / 1024.), - ((qsrs.min_logical_io_writes * 8 ) / 1024.), - ((qsrs.max_logical_io_writes * 8 ) / 1024.), - ((qsrs.avg_physical_io_reads * 8 ) / 1024.), - ((qsrs.last_physical_io_reads * 8 ) / 1024.), - ((qsrs.min_physical_io_reads * 8 ) / 1024.), - ((qsrs.max_physical_io_reads * 8 ) / 1024.), - (qsrs.avg_clr_time / 1000.), - (qsrs.last_clr_time / 1000.), - (qsrs.min_clr_time / 1000.), - (qsrs.max_clr_time / 1000.), - qsrs.avg_dop, qsrs.last_dop, qsrs.min_dop, qsrs.max_dop, - ((qsrs.avg_query_max_used_memory * 8 ) / 1024.), - ((qsrs.last_query_max_used_memory * 8 ) / 1024.), - ((qsrs.min_query_max_used_memory * 8 ) / 1024.), - ((qsrs.max_query_max_used_memory * 8 ) / 1024.), - qsrs.avg_rowcount, qsrs.last_rowcount, qsrs.min_rowcount, qsrs.max_rowcount,'; - - IF @new_columns = 1 - BEGIN - SET @sql_select += N' - qsrs.avg_num_physical_io_reads, qsrs.last_num_physical_io_reads, qsrs.min_num_physical_io_reads, qsrs.max_num_physical_io_reads, - (qsrs.avg_log_bytes_used / 100000000), - (qsrs.last_log_bytes_used / 100000000), - (qsrs.min_log_bytes_used / 100000000), - (qsrs.max_log_bytes_used / 100000000), - ((qsrs.avg_tempdb_space_used * 8 ) / 1024.), - ((qsrs.last_tempdb_space_used * 8 ) / 1024.), - ((qsrs.min_tempdb_space_used * 8 ) / 1024.), - ((qsrs.max_tempdb_space_used * 8 ) / 1024.) - '; - END; - IF @new_columns = 0 + IF @@ROWCOUNT > 0 + BEGIN + SELECT + sz.* + FROM #Mode2Temp AS sz + ORDER BY /* Shout out to DHutmacher */ + /*DESC*/ + CASE WHEN @SortOrder = N'rows' AND @SortDirection = N'desc' THEN sz.[Rows] ELSE NULL END DESC, + CASE WHEN @SortOrder = N'reserved_mb' AND @SortDirection = N'desc' THEN sz.[Reserved MB] ELSE NULL END DESC, + CASE WHEN @SortOrder = N'size' AND @SortDirection = N'desc' THEN sz.[Reserved MB] ELSE NULL END DESC, + CASE WHEN @SortOrder = N'reserved_lob_mb' AND @SortDirection = N'desc' THEN sz.[Reserved LOB MB] ELSE NULL END DESC, + CASE WHEN @SortOrder = N'lob' AND @SortDirection = N'desc' THEN sz.[Reserved LOB MB] ELSE NULL END DESC, + CASE WHEN @SortOrder = N'total_row_lock_wait_in_ms' AND @SortDirection = N'desc' THEN COALESCE(sz.[Row Lock Wait ms],0) ELSE NULL END DESC, + CASE WHEN @SortOrder = N'total_page_lock_wait_in_ms' AND @SortDirection = N'desc' THEN COALESCE(sz.[Page Lock Wait ms],0) ELSE NULL END DESC, + CASE WHEN @SortOrder = N'lock_time' AND @SortDirection = N'desc' THEN (COALESCE(sz.[Row Lock Wait ms],0) + COALESCE(sz.[Page Lock Wait ms],0)) ELSE NULL END DESC, + CASE WHEN @SortOrder = N'total_reads' AND @SortDirection = N'desc' THEN [Total Reads] ELSE NULL END DESC, + CASE WHEN @SortOrder = N'reads' AND @SortDirection = N'desc' THEN [Total Reads] ELSE NULL END DESC, + CASE WHEN @SortOrder = N'user_updates' AND @SortDirection = N'desc' THEN [User Updates] ELSE NULL END DESC, + CASE WHEN @SortOrder = N'writes' AND @SortDirection = N'desc' THEN [User Updates] ELSE NULL END DESC, + CASE WHEN @SortOrder = N'reads_per_write' AND @SortDirection = N'desc' THEN [Reads Per Write] ELSE NULL END DESC, + CASE WHEN @SortOrder = N'ratio' AND @SortDirection = N'desc' THEN [Reads Per Write] ELSE NULL END DESC, + CASE WHEN @SortOrder = N'forward_fetches' AND @SortDirection = N'desc' THEN sz.[Forwarded Fetches] ELSE NULL END DESC, + CASE WHEN @SortOrder = N'fetches' AND @SortDirection = N'desc' THEN sz.[Forwarded Fetches] ELSE NULL END DESC, + CASE WHEN @SortOrder = N'create_date' AND @SortDirection = N'desc' THEN CONVERT(DATETIME, sz.[Create Date]) ELSE NULL END DESC, + CASE WHEN @SortOrder = N'modify_date' AND @SortDirection = N'desc' THEN CONVERT(DATETIME, sz.[Modify Date]) ELSE NULL END DESC, + /*ASC*/ + CASE WHEN @SortOrder = N'rows' AND @SortDirection = N'asc' THEN sz.[Rows] ELSE NULL END ASC, + CASE WHEN @SortOrder = N'reserved_mb' AND @SortDirection = N'asc' THEN sz.[Reserved MB] ELSE NULL END ASC, + CASE WHEN @SortOrder = N'size' AND @SortDirection = N'asc' THEN sz.[Reserved MB] ELSE NULL END ASC, + CASE WHEN @SortOrder = N'reserved_lob_mb' AND @SortDirection = N'asc' THEN sz.[Reserved LOB MB] ELSE NULL END ASC, + CASE WHEN @SortOrder = N'lob' AND @SortDirection = N'asc' THEN sz.[Reserved LOB MB] ELSE NULL END ASC, + CASE WHEN @SortOrder = N'total_row_lock_wait_in_ms' AND @SortDirection = N'asc' THEN COALESCE(sz.[Row Lock Wait ms],0) ELSE NULL END ASC, + CASE WHEN @SortOrder = N'total_page_lock_wait_in_ms' AND @SortDirection = N'asc' THEN COALESCE(sz.[Page Lock Wait ms],0) ELSE NULL END ASC, + CASE WHEN @SortOrder = N'lock_time' AND @SortDirection = N'asc' THEN (COALESCE(sz.[Row Lock Wait ms],0) + COALESCE(sz.[Page Lock Wait ms],0)) ELSE NULL END ASC, + CASE WHEN @SortOrder = N'total_reads' AND @SortDirection = N'asc' THEN [Total Reads] ELSE NULL END ASC, + CASE WHEN @SortOrder = N'reads' AND @SortDirection = N'asc' THEN [Total Reads] ELSE NULL END ASC, + CASE WHEN @SortOrder = N'user_updates' AND @SortDirection = N'asc' THEN [User Updates] ELSE NULL END ASC, + CASE WHEN @SortOrder = N'writes' AND @SortDirection = N'asc' THEN [User Updates] ELSE NULL END ASC, + CASE WHEN @SortOrder = N'reads_per_write' AND @SortDirection = N'asc' THEN [Reads Per Write] ELSE NULL END ASC, + CASE WHEN @SortOrder = N'ratio' AND @SortDirection = N'asc' THEN [Reads Per Write] ELSE NULL END ASC, + CASE WHEN @SortOrder = N'forward_fetches' AND @SortDirection = N'asc' THEN sz.[Forwarded Fetches] ELSE NULL END ASC, + CASE WHEN @SortOrder = N'fetches' AND @SortDirection = N'asc' THEN sz.[Forwarded Fetches] ELSE NULL END ASC, + CASE WHEN @SortOrder = N'create_date' AND @SortDirection = N'asc' THEN CONVERT(DATETIME, sz.[Create Date]) ELSE NULL END ASC, + CASE WHEN @SortOrder = N'modify_date' AND @SortDirection = N'asc' THEN CONVERT(DATETIME, sz.[Modify Date]) ELSE NULL END ASC, + sz.[Database Name], [Schema Name], [Object Name], [Index ID] + OPTION (RECOMPILE); + END + ELSE BEGIN - SET @sql_select += N' - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL - '; - END; -SET @sql_select += -N'FROM #working_plans AS wp -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq -ON wp.query_id = qsq.query_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs -ON qsrs.plan_id = wp.plan_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp -ON qsp.plan_id = wp.plan_id -AND qsp.query_id = wp.query_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt -ON qsqt.query_text_id = qsq.query_text_id -WHERE 1 = 1 - AND qsq.is_internal_query = 0 - AND qsp.query_plan IS NOT NULL - '; - -SET @sql_select += @sql_where; - -SET @sql_select += N'OPTION (RECOMPILE); - '; - -IF @Debug = 1 - PRINT @sql_select; - -IF @sql_select IS NULL - BEGIN - RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; - RETURN; - END; - -INSERT #working_metrics WITH (TABLOCK) - ( database_name, plan_id, query_id, - proc_or_function_name, - batch_sql_handle, query_hash, query_parameterization_type_desc, count_compiles, - avg_compile_duration, last_compile_duration, avg_bind_duration, last_bind_duration, avg_bind_cpu_time, last_bind_cpu_time, avg_optimize_duration, - last_optimize_duration, avg_optimize_cpu_time, last_optimize_cpu_time, avg_compile_memory_kb, last_compile_memory_kb, execution_type_desc, - first_execution_time, last_execution_time, count_executions, avg_duration, last_duration, min_duration, max_duration, avg_cpu_time, last_cpu_time, - min_cpu_time, max_cpu_time, avg_logical_io_reads, last_logical_io_reads, min_logical_io_reads, max_logical_io_reads, avg_logical_io_writes, - last_logical_io_writes, min_logical_io_writes, max_logical_io_writes, avg_physical_io_reads, last_physical_io_reads, min_physical_io_reads, - max_physical_io_reads, avg_clr_time, last_clr_time, min_clr_time, max_clr_time, avg_dop, last_dop, min_dop, max_dop, avg_query_max_used_memory, - last_query_max_used_memory, min_query_max_used_memory, max_query_max_used_memory, avg_rowcount, last_rowcount, min_rowcount, max_rowcount, - /* 2017 only columns */ - avg_num_physical_io_reads, last_num_physical_io_reads, min_num_physical_io_reads, max_num_physical_io_reads, - avg_log_bytes_used, last_log_bytes_used, min_log_bytes_used, max_log_bytes_used, - avg_tempdb_space_used, last_tempdb_space_used, min_tempdb_space_used, max_tempdb_space_used ) - -EXEC sys.sp_executesql @stmt = @sql_select, - @params = @sp_params, - @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; - - -/*If PSPO is enabled, get procedure names for variant queries.*/ -IF (@pspo_enabled = 1) -BEGIN - DECLARE - @pspo_names NVARCHAR(MAX) = ''; - - SET @pspo_names = - 'UPDATE wm - SET - wm.proc_or_function_name = - QUOTENAME(object_schema_name(qsq.object_id, DB_ID(' + QUOTENAME(@DatabaseName, '''') + N'))) + ''.'' + - QUOTENAME(object_name(qsq.object_id, DB_ID(' + QUOTENAME(@DatabaseName, '''') + N'))) - FROM #working_metrics wm - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_variant AS vr - ON vr.query_variant_query_id = wm.query_id - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq - ON qsq.query_id = vr.parent_query_id - AND qsq.object_id > 0 - WHERE - wm.proc_or_function_name IS NULL;' - - EXEC sys.sp_executesql @pspo_names; -END; - - -/*This just helps us classify our queries*/ -UPDATE #working_metrics -SET proc_or_function_name = N'Statement' -WHERE proc_or_function_name IS NULL -OPTION(RECOMPILE); - -SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; -SET @sql_select += N' - WITH patterns AS ( - SELECT query_id, planid_path = STUFF((SELECT DISTINCT N'', '' + RTRIM(qsp2.plan_id) - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp2 - WHERE qsp.query_id = qsp2.query_id - FOR XML PATH(N''''), TYPE).value(N''.[1]'', N''NVARCHAR(MAX)''), 1, 2, N'''') - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp - ) - UPDATE wm - SET wm.query_id_all_plan_ids = patterns.planid_path - FROM #working_metrics AS wm - JOIN patterns - ON wm.query_id = patterns.query_id - OPTION (RECOMPILE); -' - -EXEC sys.sp_executesql @stmt = @sql_select; - -/* -This gathers data for the #working_plan_text table -*/ - - -RAISERROR(N'Gathering working plans', 0, 1) WITH NOWAIT; - -SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; -SET @sql_select += N' -SELECT ' + QUOTENAME(@DatabaseName, '''') + N' AS database_name, wp.plan_id, wp.query_id, - qsp.plan_group_id, qsp.engine_version, qsp.compatibility_level, qsp.query_plan_hash, TRY_CAST(qsp.query_plan AS XML), qsp.is_online_index_plan, qsp.is_trivial_plan, - qsp.is_parallel_plan, qsp.is_forced_plan, qsp.is_natively_compiled, qsp.force_failure_count, qsp.last_force_failure_reason_desc, qsp.count_compiles, - qsp.initial_compile_start_time, qsp.last_compile_start_time, qsp.last_execution_time, - (qsp.avg_compile_duration / 1000.), - (qsp.last_compile_duration / 1000.), - qsqt.query_sql_text, qsqt.statement_sql_handle, qsqt.is_part_of_encrypted_module, qsqt.has_restricted_text -FROM #working_plans AS wp -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp -ON qsp.plan_id = wp.plan_id - AND qsp.query_id = wp.query_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq -ON wp.query_id = qsq.query_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt -ON qsqt.query_text_id = qsq.query_text_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs -ON qsrs.plan_id = wp.plan_id -WHERE 1 = 1 - AND qsq.is_internal_query = 0 - AND qsp.query_plan IS NOT NULL - '; - -SET @sql_select += @sql_where; - -SET @sql_select += N'OPTION (RECOMPILE); - '; - -IF @Debug = 1 - PRINT @sql_select; - -IF @sql_select IS NULL - BEGIN - RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; - RETURN; - END; + SELECT + DatabaseDetails = + N'Database ' + + ISNULL(@DatabaseName, DB_NAME()) + + N' has ' + + ISNULL(RTRIM(@Rowcount), 0) + + N' partitions.', + BringThePain = + CASE + WHEN @BringThePain IN (0, 1) AND ISNULL(@Rowcount, 0) = 0 + THEN N'Check the database name, it looks like nothing is here.' + WHEN @BringThePain = 0 AND ISNULL(@Rowcount, 0) > 0 + THEN N'Please re-run with @BringThePain = 1' + END; + END + END; + END; /* End @Mode=2 (index detail)*/ -INSERT #working_plan_text WITH (TABLOCK) - ( database_name, plan_id, query_id, - plan_group_id, engine_version, compatibility_level, query_plan_hash, query_plan_xml, is_online_index_plan, is_trivial_plan, - is_parallel_plan, is_forced_plan, is_natively_compiled, force_failure_count, last_force_failure_reason_desc, count_compiles, - initial_compile_start_time, last_compile_start_time, last_execution_time, avg_compile_duration, last_compile_duration, - query_sql_text, statement_sql_handle, is_part_of_encrypted_module, has_restricted_text ) -EXEC sys.sp_executesql @stmt = @sql_select, - @params = @sp_params, - @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; -/* -This gets us context settings for our queries and adds it to the #working_plan_text table -*/ -RAISERROR(N'Gathering context settings', 0, 1) WITH NOWAIT; - -SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; -SET @sql_select += N' -UPDATE wp -SET wp.context_settings = SUBSTRING( - CASE WHEN (CAST(qcs.set_options AS INT) & 1 = 1) THEN '', ANSI_PADDING'' ELSE '''' END + - CASE WHEN (CAST(qcs.set_options AS INT) & 8 = 8) THEN '', CONCAT_NULL_YIELDS_NULL'' ELSE '''' END + - CASE WHEN (CAST(qcs.set_options AS INT) & 16 = 16) THEN '', ANSI_WARNINGS'' ELSE '''' END + - CASE WHEN (CAST(qcs.set_options AS INT) & 32 = 32) THEN '', ANSI_NULLS'' ELSE '''' END + - CASE WHEN (CAST(qcs.set_options AS INT) & 64 = 64) THEN '', QUOTED_IDENTIFIER'' ELSE '''' END + - CASE WHEN (CAST(qcs.set_options AS INT) & 4096 = 4096) THEN '', ARITH_ABORT'' ELSE '''' END + - CASE WHEN (CAST(qcs.set_options AS INT) & 8192 = 8192) THEN '', NUMERIC_ROUNDABORT'' ELSE '''' END - , 2, 200000) -FROM #working_plan_text wp -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq -ON wp.query_id = qsq.query_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_context_settings AS qcs -ON qcs.context_settings_id = qsq.context_settings_id -OPTION (RECOMPILE); -'; -IF @Debug = 1 - PRINT @sql_select; -IF @sql_select IS NULL + ELSE IF (@Mode=3) /*Missing index Detail*/ BEGIN - RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; - RETURN; - END; - -EXEC sys.sp_executesql @stmt = @sql_select; - - -/*This adds the patterns we found from each interval to the #working_plan_text table*/ + IF (@ValidOutputLocation = 1 AND COALESCE(@OutputServerName, @OutputDatabaseName, @OutputSchemaName, @OutputTableName) IS NOT NULL) + BEGIN -RAISERROR(N'Add patterns to working plans', 0, 1) WITH NOWAIT; + IF NOT @SchemaExists = 1 + BEGIN + RAISERROR (N'Invalid schema name, data could not be saved.', 16, 0); + RETURN; + END -UPDATE wpt -SET wpt.pattern = wp.pattern -FROM #working_plans AS wp -JOIN #working_plan_text AS wpt -ON wpt.plan_id = wp.plan_id -AND wpt.query_id = wp.query_id -OPTION (RECOMPILE); + IF @TableExists = 0 + BEGIN + SET @StringToExecute = + N'CREATE TABLE @@@OutputDatabaseName@@@.@@@OutputSchemaName@@@.@@@OutputTableName@@@ + ( + [id] INT IDENTITY(1,1) NOT NULL, + [run_id] UNIQUEIDENTIFIER, + [run_datetime] DATETIME, + [server_name] NVARCHAR(128), + [database_name] NVARCHAR(128), + [schema_name] NVARCHAR(128), + [table_name] NVARCHAR(128), + [magic_benefit_number] BIGINT, + [missing_index_details] NVARCHAR(MAX), + [avg_total_user_cost] NUMERIC(29,4), + [avg_user_impact] NUMERIC(29,1), + [user_seeks] BIGINT, + [user_scans] BIGINT, + [unique_compiles] BIGINT, + [equality_columns_with_data_type] NVARCHAR(MAX), + [inequality_columns_with_data_type] NVARCHAR(MAX), + [included_columns_with_data_type] NVARCHAR(MAX), + [index_estimated_impact] NVARCHAR(256), + [create_tsql] NVARCHAR(MAX), + [more_info] NVARCHAR(600), + [display_order] INT, + [is_low] BIT, + [sample_query_plan] XML, + CONSTRAINT [PK_ID_@@@RunID@@@] PRIMARY KEY CLUSTERED ([id] ASC) + );'; + + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputSchemaName@@@', @OutputSchemaName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@RunID@@@', @RunID); + + IF @ValidOutputServer = 1 + BEGIN + SET @StringToExecute = REPLACE(@StringToExecute,'''',''''''); + EXEC('EXEC('''+@StringToExecute+''') AT ' + @OutputServerName); + END; + ELSE + BEGIN + EXEC(@StringToExecute); + END; + END; /* @TableExists = 0 */ -/*This cleans up query text a bit*/ + -- Re-check that table now exists (if not we failed creating it) + SET @TableExists = NULL; + EXEC sp_executesql @TableExistsSql, N'@TableExists BIT OUTPUT', @TableExists OUTPUT; + + IF NOT @TableExists = 1 + BEGIN + RAISERROR('Creation of the output table failed.', 16, 0); + RETURN; + END; + SET @StringToExecute = + N'WITH create_date AS ( + SELECT i.database_id, + i.schema_name, + i.[object_id], + ISNULL(NULLIF(MAX(DATEDIFF(DAY, i.create_date, SYSDATETIME())), 0), 1) AS create_days + FROM #IndexSanity AS i + GROUP BY i.database_id, i.schema_name, i.object_id + ) + INSERT @@@OutputServerName@@@.@@@OutputDatabaseName@@@.@@@OutputSchemaName@@@.@@@OutputTableName@@@ + ( + [run_id], + [run_datetime], + [server_name], + [database_name], + [schema_name], + [table_name], + [magic_benefit_number], + [missing_index_details], + [avg_total_user_cost], + [avg_user_impact], + [user_seeks], + [user_scans], + [unique_compiles], + [equality_columns_with_data_type], + [inequality_columns_with_data_type], + [included_columns_with_data_type], + [index_estimated_impact], + [create_tsql], + [more_info], + [display_order], + [is_low], + [sample_query_plan] + ) + SELECT ''@@@RunID@@@'', + ''@@@GETDATE@@@'', + ''@@@LocalServerName@@@'', + -- Below should be a copy/paste of the real query + -- Make sure all quotes are escaped + -- NOTE! information line is skipped from output and the query below + -- NOTE! CTE block is above insert in the copied SQL + mi.database_name AS [Database Name], + mi.[schema_name] AS [Schema], + mi.table_name AS [Table], + CAST((mi.magic_benefit_number / CASE WHEN cd.create_days < @DaysUptime THEN cd.create_days ELSE @DaysUptime END) AS BIGINT) + AS [Magic Benefit Number], + mi.missing_index_details AS [Missing Index Details], + mi.avg_total_user_cost AS [Avg Query Cost], + mi.avg_user_impact AS [Est Index Improvement], + mi.user_seeks AS [Seeks], + mi.user_scans AS [Scans], + mi.unique_compiles AS [Compiles], + mi.equality_columns_with_data_type AS [Equality Columns], + mi.inequality_columns_with_data_type AS [Inequality Columns], + mi.included_columns_with_data_type AS [Included Columns], + mi.index_estimated_impact AS [Estimated Impact], + mi.create_tsql AS [Create TSQL], + mi.more_info AS [More Info], + 1 AS [Display Order], + mi.is_low, + mi.sample_query_plan AS [Sample Query Plan] + FROM #MissingIndexes AS mi + LEFT JOIN create_date AS cd + ON mi.[object_id] = cd.object_id + AND mi.database_id = cd.database_id + AND mi.schema_name = cd.schema_name + /* Minimum benefit threshold = 100k/day of uptime OR since table creation date, whichever is lower*/ + WHERE @ShowAllMissingIndexRequests=1 + OR (mi.magic_benefit_number / CASE WHEN cd.create_days < @DaysUptime THEN cd.create_days ELSE @DaysUptime END) >= 100000 + ORDER BY [Display Order] ASC, [Magic Benefit Number] DESC + OPTION (RECOMPILE);'; + + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputServerName@@@', @OutputServerName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputSchemaName@@@', @OutputSchemaName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@RunID@@@', @RunID); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@GETDATE@@@', GETDATE()); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@LocalServerName@@@', CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128))); + EXEC sp_executesql @StringToExecute, N'@DaysUptime NUMERIC(23,2), @ShowAllMissingIndexRequests BIT', @DaysUptime = @DaysUptime, @ShowAllMissingIndexRequests = @ShowAllMissingIndexRequests; -RAISERROR(N'Clean awkward characters from query text', 0, 1) WITH NOWAIT; + END; /* @ValidOutputLocation = 1 */ + ELSE + BEGIN + IF(@OutputType <> 'NONE') + BEGIN + WITH create_date AS ( + SELECT i.database_id, + i.schema_name, + i.[object_id], + ISNULL(NULLIF(MAX(DATEDIFF(DAY, i.create_date, SYSDATETIME())), 0), 1) AS create_days + FROM #IndexSanity AS i + GROUP BY i.database_id, i.schema_name, i.object_id + ) + SELECT + mi.database_name AS [Database Name], + mi.[schema_name] AS [Schema], + mi.table_name AS [Table], + CAST((mi.magic_benefit_number / CASE WHEN cd.create_days < @DaysUptime THEN cd.create_days ELSE @DaysUptime END) AS BIGINT) + AS [Magic Benefit Number], + mi.missing_index_details AS [Missing Index Details], + mi.avg_total_user_cost AS [Avg Query Cost], + mi.avg_user_impact AS [Est Index Improvement], + mi.user_seeks AS [Seeks], + mi.user_scans AS [Scans], + mi.unique_compiles AS [Compiles], + mi.equality_columns_with_data_type AS [Equality Columns], + mi.inequality_columns_with_data_type AS [Inequality Columns], + mi.included_columns_with_data_type AS [Included Columns], + mi.index_estimated_impact AS [Estimated Impact], + mi.create_tsql AS [Create TSQL], + mi.more_info AS [More Info], + 1 AS [Display Order], + mi.is_low, + mi.sample_query_plan AS [Sample Query Plan] + FROM #MissingIndexes AS mi + LEFT JOIN create_date AS cd + ON mi.[object_id] = cd.object_id + AND mi.database_id = cd.database_id + AND mi.schema_name = cd.schema_name + /* Minimum benefit threshold = 100k/day of uptime OR since table creation date, whichever is lower*/ + WHERE @ShowAllMissingIndexRequests=1 + OR (mi.magic_benefit_number / CASE WHEN cd.create_days < @DaysUptime THEN cd.create_days ELSE @DaysUptime END) >= 100000 + UNION ALL + SELECT + @ScriptVersionName, + N'From Your Community Volunteers' , + N'http://FirstResponderKit.org' , + 100000000000, + @DaysUptimeInsertValue, + NULL,NULL,NULL,NULL,NULL,NULL,NULL, + NULL, NULL, NULL, NULL, 0 AS [Display Order], NULL AS is_low, NULL + ORDER BY [Display Order] ASC, [Magic Benefit Number] DESC + OPTION (RECOMPILE); + END; -UPDATE b -SET b.query_sql_text = REPLACE(REPLACE(REPLACE(b.query_sql_text, @cr, ' '), @lf, ' '), @tab, ' ') -FROM #working_plan_text AS b -OPTION (RECOMPILE); + IF (@BringThePain = 1 + AND @DatabaseName IS NOT NULL + AND @GetAllDatabases = 0) -/*This populates #working_wait_stats when available*/ + BEGIN + EXEC sp_BlitzCache @SortOrder = 'sp_BlitzIndex', @DatabaseName = @DatabaseName, @BringThePain = 1, @QueryFilter = 'statement', @HideSummary = 1; + END; -IF @waitstats = 1 + END; - BEGIN - - RAISERROR(N'Collecting wait stats info', 0, 1) WITH NOWAIT; - - - SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; - SET @sql_select += N' - SELECT qws.plan_id, - qws.wait_category, - qws.wait_category_desc, - SUM(qws.total_query_wait_time_ms) AS total_query_wait_time_ms, - SUM(qws.avg_query_wait_time_ms) AS avg_query_wait_time_ms, - SUM(qws.last_query_wait_time_ms) AS last_query_wait_time_ms, - SUM(qws.min_query_wait_time_ms) AS min_query_wait_time_ms, - SUM(qws.max_query_wait_time_ms) AS max_query_wait_time_ms - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_wait_stats qws - JOIN #working_plans AS wp - ON qws.plan_id = wp.plan_id - GROUP BY qws.plan_id, qws.wait_category, qws.wait_category_desc - HAVING SUM(qws.min_query_wait_time_ms) >= 5 - OPTION (RECOMPILE); - '; - - IF @Debug = 1 - PRINT @sql_select; - - IF @sql_select IS NULL - BEGIN - RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; - RETURN; - END; - - INSERT #working_wait_stats WITH (TABLOCK) - ( plan_id, wait_category, wait_category_desc, total_query_wait_time_ms, avg_query_wait_time_ms, last_query_wait_time_ms, min_query_wait_time_ms, max_query_wait_time_ms ) - - EXEC sys.sp_executesql @stmt = @sql_select; - - - /*This updates #working_plan_text with the top three waits from the wait stats DMV*/ - - RAISERROR(N'Update working_plan_text with top three waits', 0, 1) WITH NOWAIT; - - - UPDATE wpt - SET wpt.top_three_waits = x.top_three_waits - FROM #working_plan_text AS wpt - JOIN ( - SELECT wws.plan_id, - top_three_waits = STUFF((SELECT TOP 3 N', ' + wws2.wait_category_desc + N' (' + CONVERT(NVARCHAR(20), SUM(CONVERT(BIGINT, wws2.avg_query_wait_time_ms))) + N' ms) ' - FROM #working_wait_stats AS wws2 - WHERE wws.plan_id = wws2.plan_id - GROUP BY wws2.wait_category_desc - ORDER BY SUM(wws2.avg_query_wait_time_ms) DESC - FOR XML PATH(N''), TYPE).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 2, N'') - FROM #working_wait_stats AS wws - GROUP BY wws.plan_id - ) AS x - ON x.plan_id = wpt.plan_id - OPTION (RECOMPILE); -END; -/*End wait stats population*/ -UPDATE #working_plan_text -SET top_three_waits = CASE - WHEN @waitstats = 0 - THEN N'The query store waits stats DMV is not available' - ELSE N'No Significant waits detected!' - END -WHERE top_three_waits IS NULL -OPTION(RECOMPILE); -END; + END; /* End @Mode=3 (index detail)*/ + SET @d = CONVERT(VARCHAR(19), GETDATE(), 121); + RAISERROR (N'finishing at %s',0,1, @d) WITH NOWAIT; +END /* End @TableName IS NULL (mode 0/1/2/3/4) */ END TRY + BEGIN CATCH - RAISERROR (N'Failure populating temp tables.', 0,1) WITH NOWAIT; + RAISERROR (N'Failure analyzing temp tables.', 0,1) WITH NOWAIT; - IF @sql_select IS NOT NULL - BEGIN - SET @msg = N'Last @sql_select: ' + @sql_select; - RAISERROR(@msg, 0, 1) WITH NOWAIT; - END; + SELECT @msg = ERROR_MESSAGE(), @ErrorSeverity = ERROR_SEVERITY(), @ErrorState = ERROR_STATE(); - SELECT @msg = @DatabaseName + N' database failed to process. ' + ERROR_MESSAGE(), @error_severity = ERROR_SEVERITY(), @error_state = ERROR_STATE(); - RAISERROR (@msg, @error_severity, @error_state) WITH NOWAIT; - + RAISERROR (@msg, + @ErrorSeverity, + @ErrorState + ); - WHILE @@TRANCOUNT > 0 + WHILE @@trancount > 0 ROLLBACK; RETURN; -END CATCH; - -IF (@SkipXML = 0) -BEGIN TRY + END CATCH; +GO +IF OBJECT_ID('dbo.sp_BlitzLock') IS NULL BEGIN + EXEC ('CREATE PROCEDURE dbo.sp_BlitzLock AS RETURN 0;'); +END; +GO -/* -This sets up the #working_warnings table with the IDs we're interested in so we can tie warnings back to them -*/ - -RAISERROR(N'Populate working warnings table with gathered plans', 0, 1) WITH NOWAIT; - - -SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; -SET @sql_select += N' -SELECT DISTINCT wp.plan_id, wp.query_id, qsq.query_hash, qsqt.statement_sql_handle -FROM #working_plans AS wp -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp -ON qsp.plan_id = wp.plan_id - AND qsp.query_id = wp.query_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq -ON wp.query_id = qsq.query_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt -ON qsqt.query_text_id = qsq.query_text_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs -ON qsrs.plan_id = wp.plan_id -WHERE 1 = 1 - AND qsq.is_internal_query = 0 - AND qsp.query_plan IS NOT NULL - '; - -SET @sql_select += @sql_where; - -SET @sql_select += N'OPTION (RECOMPILE); - '; - -IF @Debug = 1 - PRINT @sql_select; - -IF @sql_select IS NULL - BEGIN - RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; - RETURN; - END; - -INSERT #working_warnings WITH (TABLOCK) - ( plan_id, query_id, query_hash, sql_handle ) -EXEC sys.sp_executesql @stmt = @sql_select, - @params = @sp_params, - @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; - -/* -This looks for queries in the query stores that we picked up from an internal that have multiple plans in cache - -This and several of the following queries all replaced XML parsing to find plan attributes. Sweet. - -Thanks, Query Store -*/ - -RAISERROR(N'Populating object name in #working_warnings', 0, 1) WITH NOWAIT; -UPDATE w -SET w.proc_or_function_name = ISNULL(wm.proc_or_function_name, N'Statement') -FROM #working_warnings AS w -JOIN #working_metrics AS wm -ON w.plan_id = wm.plan_id - AND w.query_id = wm.query_id -OPTION (RECOMPILE); - - -RAISERROR(N'Checking for multiple plans', 0, 1) WITH NOWAIT; - -SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; -SET @sql_select += N' -UPDATE ww -SET ww.plan_multiple_plans = 1 -FROM #working_warnings AS ww -JOIN +ALTER PROCEDURE + dbo.sp_BlitzLock ( -SELECT wp.query_id, COUNT(qsp.plan_id) AS plans -FROM #working_plans AS wp -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp -ON qsp.plan_id = wp.plan_id - AND qsp.query_id = wp.query_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq -ON wp.query_id = qsq.query_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt -ON qsqt.query_text_id = qsq.query_text_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs -ON qsrs.plan_id = wp.plan_id -WHERE 1 = 1 - AND qsq.is_internal_query = 0 - AND qsp.query_plan IS NOT NULL - '; - -SET @sql_select += @sql_where; - -SET @sql_select += -N'GROUP BY wp.query_id - HAVING COUNT(qsp.plan_id) > 1 -) AS x - ON ww.query_id = x.query_id -OPTION (RECOMPILE); -'; + @DatabaseName sysname = NULL, + @StartDate datetime = NULL, + @EndDate datetime = NULL, + @ObjectName nvarchar(1024) = NULL, + @StoredProcName nvarchar(1024) = NULL, + @AppName sysname = NULL, + @HostName sysname = NULL, + @LoginName sysname = NULL, + @EventSessionName sysname = N'system_health', + @TargetSessionType sysname = NULL, + @VictimsOnly bit = 0, + @Debug bit = 0, + @Help bit = 0, + @Version varchar(30) = NULL OUTPUT, + @VersionDate datetime = NULL OUTPUT, + @VersionCheckMode bit = 0, + @OutputDatabaseName sysname = NULL, + @OutputSchemaName sysname = N'dbo', /*ditto as below*/ + @OutputTableName sysname = N'BlitzLock', /*put a standard here no need to check later in the script*/ + @ExportToExcel bit = 0 +) +WITH RECOMPILE +AS +BEGIN + SET STATISTICS XML OFF; + SET NOCOUNT ON; + SET XACT_ABORT OFF; + SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -IF @Debug = 1 - PRINT @sql_select; + SELECT @Version = '8.19', @VersionDate = '20240222'; -IF @sql_select IS NULL + IF @VersionCheckMode = 1 BEGIN - RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; RETURN; END; -EXEC sys.sp_executesql @stmt = @sql_select, - @params = @sp_params, - @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; - -/* -This looks for forced plans -*/ - -RAISERROR(N'Checking for forced plans', 0, 1) WITH NOWAIT; - -UPDATE ww -SET ww.is_forced_plan = 1 -FROM #working_warnings AS ww -JOIN #working_plan_text AS wp -ON ww.plan_id = wp.plan_id - AND ww.query_id = wp.query_id - AND wp.is_forced_plan = 1 -OPTION (RECOMPILE); - - -/* -This looks for forced parameterization -*/ - -RAISERROR(N'Checking for forced parameterization', 0, 1) WITH NOWAIT; - -UPDATE ww -SET ww.is_forced_parameterized = 1 -FROM #working_warnings AS ww -JOIN #working_metrics AS wm -ON ww.plan_id = wm.plan_id - AND ww.query_id = wm.query_id - AND wm.query_parameterization_type_desc = 'Forced' -OPTION (RECOMPILE); - - -/* -This looks for unparameterized queries -*/ - -RAISERROR(N'Checking for unparameterized plans', 0, 1) WITH NOWAIT; - -UPDATE ww -SET ww.unparameterized_query = 1 -FROM #working_warnings AS ww -JOIN #working_metrics AS wm -ON ww.plan_id = wm.plan_id - AND ww.query_id = wm.query_id - AND wm.query_parameterization_type_desc = 'None' - AND ww.proc_or_function_name = 'Statement' -OPTION (RECOMPILE); - - -/* -This looks for cursors -*/ - -RAISERROR(N'Checking for cursors', 0, 1) WITH NOWAIT; -UPDATE ww -SET ww.is_cursor = 1 -FROM #working_warnings AS ww -JOIN #working_plan_text AS wp -ON ww.plan_id = wp.plan_id - AND ww.query_id = wp.query_id - AND wp.plan_group_id > 0 -OPTION (RECOMPILE); - - -UPDATE ww -SET ww.is_cursor = 1 -FROM #working_warnings AS ww -JOIN #working_plan_text AS wp -ON ww.plan_id = wp.plan_id - AND ww.query_id = wp.query_id -WHERE ww.query_hash = 0x0000000000000000 -OR wp.query_plan_hash = 0x0000000000000000 -OPTION (RECOMPILE); - -/* -This looks for parallel plans -*/ -UPDATE ww -SET ww.is_parallel = 1 -FROM #working_warnings AS ww -JOIN #working_plan_text AS wp -ON ww.plan_id = wp.plan_id - AND ww.query_id = wp.query_id - AND wp.is_parallel_plan = 1 -OPTION (RECOMPILE); - -/*This looks for old CE*/ - -RAISERROR(N'Checking for legacy CE', 0, 1) WITH NOWAIT; - -UPDATE w -SET w.downlevel_estimator = 1 -FROM #working_warnings AS w -JOIN #working_plan_text AS wpt -ON w.plan_id = wpt.plan_id -AND w.query_id = wpt.query_id -/*PLEASE DON'T TELL ANYONE I DID THIS*/ -WHERE PARSENAME(wpt.engine_version, 4) < PARSENAME(CONVERT(VARCHAR(128), SERVERPROPERTY ('PRODUCTVERSION')), 4) -OPTION (RECOMPILE); -/*NO SERIOUSLY THIS IS A HORRIBLE IDEA*/ - - -/*Plans that compile 2x more than they execute*/ - -RAISERROR(N'Checking for plans that compile 2x more than they execute', 0, 1) WITH NOWAIT; - -UPDATE ww -SET ww.is_compile_more = 1 -FROM #working_warnings AS ww -JOIN #working_metrics AS wm -ON ww.plan_id = wm.plan_id - AND ww.query_id = wm.query_id - AND wm.count_compiles > (wm.count_executions * 2) -OPTION (RECOMPILE); - -/*Plans that compile 2x more than they execute*/ - -RAISERROR(N'Checking for plans that take more than 5 seconds to bind, compile, or optimize', 0, 1) WITH NOWAIT; - -UPDATE ww -SET ww.is_slow_plan = 1 -FROM #working_warnings AS ww -JOIN #working_metrics AS wm -ON ww.plan_id = wm.plan_id - AND ww.query_id = wm.query_id - AND (wm.avg_bind_duration > 5000 - OR - wm.avg_compile_duration > 5000 - OR - wm.avg_optimize_duration > 5000 - OR - wm.avg_optimize_cpu_time > 5000) -OPTION (RECOMPILE); - - - -/* -This parses the XML from our top plans into smaller chunks for easier consumption -*/ - -RAISERROR(N'Begin XML nodes parsing', 0, 1) WITH NOWAIT; - -RAISERROR(N'Inserting #statements', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -INSERT #statements WITH (TABLOCK) ( plan_id, query_id, query_hash, sql_handle, statement, is_cursor ) - SELECT ww.plan_id, ww.query_id, ww.query_hash, ww.sql_handle, q.n.query('.') AS statement, 0 AS is_cursor - FROM #working_warnings AS ww - JOIN #working_plan_text AS wp - ON ww.plan_id = wp.plan_id - AND ww.query_id = wp.query_id - CROSS APPLY wp.query_plan_xml.nodes('//p:StmtSimple') AS q(n) -OPTION (RECOMPILE); - -RAISERROR(N'Inserting parsed cursor XML to #statements', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -INSERT #statements WITH (TABLOCK) ( plan_id, query_id, query_hash, sql_handle, statement, is_cursor ) - SELECT ww.plan_id, ww.query_id, ww.query_hash, ww.sql_handle, q.n.query('.') AS statement, 1 AS is_cursor - FROM #working_warnings AS ww - JOIN #working_plan_text AS wp - ON ww.plan_id = wp.plan_id - AND ww.query_id = wp.query_id - CROSS APPLY wp.query_plan_xml.nodes('//p:StmtCursor') AS q(n) -OPTION (RECOMPILE); - -RAISERROR(N'Inserting to #query_plan', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -INSERT #query_plan WITH (TABLOCK) ( plan_id, query_id, query_hash, sql_handle, query_plan ) -SELECT s.plan_id, s.query_id, s.query_hash, s.sql_handle, q.n.query('.') AS query_plan -FROM #statements AS s - CROSS APPLY s.statement.nodes('//p:QueryPlan') AS q(n) -OPTION (RECOMPILE); - -RAISERROR(N'Inserting to #relop', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -INSERT #relop WITH (TABLOCK) ( plan_id, query_id, query_hash, sql_handle, relop) -SELECT qp.plan_id, qp.query_id, qp.query_hash, qp.sql_handle, q.n.query('.') AS relop -FROM #query_plan qp - CROSS APPLY qp.query_plan.nodes('//p:RelOp') AS q(n) -OPTION (RECOMPILE); + IF @Help = 1 + BEGIN + PRINT N' + /* + sp_BlitzLock from http://FirstResponderKit.org + This script checks for and analyzes deadlocks from the system health session or a custom extended event path --- statement level checks + Variables you can use: -RAISERROR(N'Performing compile timeout checks', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET b.compile_timeout = 1 -FROM #statements s -JOIN #working_warnings AS b -ON s.query_hash = b.query_hash -WHERE s.statement.exist('/p:StmtSimple/@StatementOptmEarlyAbortReason[.="TimeOut"]') = 1 -OPTION (RECOMPILE); + @DatabaseName: If you want to filter to a specific database + @StartDate: The date you want to start searching on, defaults to last 7 days -RAISERROR(N'Performing compile memory limit exceeded checks', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET b.compile_memory_limit_exceeded = 1 -FROM #statements s -JOIN #working_warnings AS b -ON s.query_hash = b.query_hash -WHERE s.statement.exist('/p:StmtSimple/@StatementOptmEarlyAbortReason[.="MemoryLimitExceeded"]') = 1 -OPTION (RECOMPILE); + @EndDate: The date you want to stop searching on, defaults to current date -IF @ExpertMode > 0 -BEGIN -RAISERROR(N'Performing index DML checks', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p), -index_dml AS ( - SELECT s.query_hash, - index_dml = CASE WHEN s.statement.exist('//p:StmtSimple/@StatementType[.="CREATE INDEX"]') = 1 THEN 1 - WHEN s.statement.exist('//p:StmtSimple/@StatementType[.="DROP INDEX"]') = 1 THEN 1 - END - FROM #statements s - ) - UPDATE b - SET b.index_dml = i.index_dml - FROM #working_warnings AS b - JOIN index_dml i - ON i.query_hash = b.query_hash - WHERE i.index_dml = 1 -OPTION (RECOMPILE); + @ObjectName: If you want to filter to a specific able. + The object name has to be fully qualified ''Database.Schema.Table'' + @StoredProcName: If you want to search for a single stored proc + The proc name has to be fully qualified ''Database.Schema.Sproc'' -RAISERROR(N'Performing table DML checks', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p), -table_dml AS ( - SELECT s.query_hash, - table_dml = CASE WHEN s.statement.exist('//p:StmtSimple/@StatementType[.="CREATE TABLE"]') = 1 THEN 1 - WHEN s.statement.exist('//p:StmtSimple/@StatementType[.="DROP OBJECT"]') = 1 THEN 1 - END - FROM #statements AS s - ) - UPDATE b - SET b.table_dml = t.table_dml - FROM #working_warnings AS b - JOIN table_dml t - ON t.query_hash = b.query_hash - WHERE t.table_dml = 1 -OPTION (RECOMPILE); -END; + @AppName: If you want to filter to a specific application + @HostName: If you want to filter to a specific host -RAISERROR(N'Gathering trivial plans', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES ( 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) -UPDATE b -SET b.is_trivial = 1 -FROM #working_warnings AS b -JOIN ( -SELECT s.sql_handle -FROM #statements AS s -JOIN ( SELECT r.sql_handle - FROM #relop AS r - WHERE r.relop.exist('//p:RelOp[contains(@LogicalOp, "Scan")]') = 1 ) AS r - ON r.sql_handle = s.sql_handle -WHERE s.statement.exist('//p:StmtSimple[@StatementOptmLevel[.="TRIVIAL"]]/p:QueryPlan/p:ParameterList') = 1 -) AS s -ON b.sql_handle = s.sql_handle -OPTION (RECOMPILE); + @LoginName: If you want to filter to a specific login -IF @ExpertMode > 0 -BEGIN -RAISERROR(N'Gathering row estimates', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES ('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) -INSERT #est_rows (query_hash, estimated_rows) -SELECT DISTINCT - CONVERT(BINARY(8), RIGHT('0000000000000000' + SUBSTRING(c.n.value('@QueryHash', 'VARCHAR(18)'), 3, 18), 16), 2) AS query_hash, - c.n.value('(/p:StmtSimple/@StatementEstRows)[1]', 'FLOAT') AS estimated_rows -FROM #statements AS s -CROSS APPLY s.statement.nodes('/p:StmtSimple') AS c(n) -WHERE c.n.exist('/p:StmtSimple[@StatementEstRows > 0]') = 1; + @EventSessionName: If you want to point this at an XE session rather than the system health session. - UPDATE b - SET b.estimated_rows = er.estimated_rows - FROM #working_warnings AS b - JOIN #est_rows er - ON er.query_hash = b.query_hash - OPTION (RECOMPILE); -END; + @TargetSessionType: Can be ''ring_buffer'' or ''event_file''. Leave NULL to auto-detect. + @OutputDatabaseName: If you want to output information to a specific database -/*Begin plan cost calculations*/ -RAISERROR(N'Gathering statement costs', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -INSERT #plan_cost WITH (TABLOCK) - ( query_plan_cost, sql_handle, plan_id ) -SELECT DISTINCT - s.statement.value('sum(/p:StmtSimple/@StatementSubTreeCost)', 'float') query_plan_cost, - s.sql_handle, - s.plan_id -FROM #statements s -OUTER APPLY s.statement.nodes('/p:StmtSimple') AS q(n) -WHERE s.statement.value('sum(/p:StmtSimple/@StatementSubTreeCost)', 'float') > 0 -OPTION (RECOMPILE); + @OutputSchemaName: Specify a schema name to output information to a specific Schema + @OutputTableName: Specify table name to to output information to a specific table -RAISERROR(N'Updating statement costs', 0, 1) WITH NOWAIT; -WITH pc AS ( - SELECT SUM(DISTINCT pc.query_plan_cost) AS queryplancostsum, pc.sql_handle, pc.plan_id - FROM #plan_cost AS pc - GROUP BY pc.sql_handle, pc.plan_id - ) - UPDATE b - SET b.query_cost = ISNULL(pc.queryplancostsum, 0) - FROM #working_warnings AS b - JOIN pc - ON pc.sql_handle = b.sql_handle - AND pc.plan_id = b.plan_id -OPTION (RECOMPILE); + To learn more, visit http://FirstResponderKit.org where you can download new + versions for free, watch training videos on how it works, get more info on + the findings, contribute your own code, and more. + Known limitations of this version: + - Only SQL Server 2012 and newer is supported + - If your tables have weird characters in them (https://en.wikipedia.org/wiki/List_of_xml_and_HTML_character_entity_references) you may get errors trying to parse the xml. + I took a long look at this one, and: + 1) Trying to account for all the weird places these could crop up is a losing effort. + 2) Replace is slow af on lots of xml. -/*End plan cost calculations*/ + Unknown limitations of this version: + - None. (If we knew them, they would be known. Duh.) + MIT License -RAISERROR(N'Checking for plan warnings', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET b.plan_warnings = 1 -FROM #query_plan qp -JOIN #working_warnings b -ON qp.sql_handle = b.sql_handle -AND qp.query_plan.exist('/p:QueryPlan/p:Warnings') = 1 -OPTION (RECOMPILE); + Copyright (c) Brent Ozar Unlimited + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: -RAISERROR(N'Checking for implicit conversion', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET b.implicit_conversions = 1 -FROM #query_plan qp -JOIN #working_warnings b -ON qp.sql_handle = b.sql_handle -AND qp.query_plan.exist('/p:QueryPlan/p:Warnings/p:PlanAffectingConvert/@Expression[contains(., "CONVERT_IMPLICIT")]') = 1 -OPTION (RECOMPILE); + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. -IF @ExpertMode > 0 -BEGIN -RAISERROR(N'Performing busy loops checks', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE p -SET busy_loops = CASE WHEN (x.estimated_executions / 100.0) > x.estimated_rows THEN 1 END -FROM #working_warnings p - JOIN ( - SELECT qs.sql_handle, - relop.value('sum(/p:RelOp/@EstimateRows)', 'float') AS estimated_rows , - relop.value('sum(/p:RelOp/@EstimateRewinds)', 'float') + relop.value('sum(/p:RelOp/@EstimateRebinds)', 'float') + 1.0 AS estimated_executions - FROM #relop qs - ) AS x ON p.sql_handle = x.sql_handle -OPTION (RECOMPILE); -END; + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + */'; -RAISERROR(N'Performing TVF join check', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE p -SET p.tvf_join = CASE WHEN x.tvf_join = 1 THEN 1 END -FROM #working_warnings p - JOIN ( - SELECT r.sql_handle, - 1 AS tvf_join - FROM #relop AS r - WHERE r.relop.exist('//p:RelOp[(@LogicalOp[.="Table-valued function"])]') = 1 - AND r.relop.exist('//p:RelOp[contains(@LogicalOp, "Join")]') = 1 - ) AS x ON p.sql_handle = x.sql_handle -OPTION (RECOMPILE); + RETURN; + END; /* @Help = 1 */ -IF @ExpertMode > 0 -BEGIN -RAISERROR(N'Checking for operator warnings', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -, x AS ( -SELECT r.sql_handle, - c.n.exist('//p:Warnings[(@NoJoinPredicate[.="1"])]') AS warning_no_join_predicate, - c.n.exist('//p:ColumnsWithNoStatistics') AS no_stats_warning , - c.n.exist('//p:Warnings') AS relop_warnings -FROM #relop AS r -CROSS APPLY r.relop.nodes('/p:RelOp/p:Warnings') AS c(n) -) -UPDATE b -SET b.warning_no_join_predicate = x.warning_no_join_predicate, - b.no_stats_warning = x.no_stats_warning, - b.relop_warnings = x.relop_warnings -FROM #working_warnings b -JOIN x ON x.sql_handle = b.sql_handle -OPTION (RECOMPILE); -END; + /*Declare local variables used in the procudure*/ + DECLARE + @DatabaseId int = + DB_ID(@DatabaseName), + @ProductVersion nvarchar(128) = + CAST(SERVERPROPERTY('ProductVersion') AS nvarchar(128)), + @ProductVersionMajor float = + SUBSTRING + ( + CAST(SERVERPROPERTY('ProductVersion') AS nvarchar(128)), + 1, + CHARINDEX('.', CAST(SERVERPROPERTY('ProductVersion') AS nvarchar(128))) + 1 + ), + @ProductVersionMinor int = + PARSENAME + ( + CONVERT + ( + varchar(32), + CAST(SERVERPROPERTY('ProductVersion') AS nvarchar(128)) + ), + 2 + ), + @ObjectFullName nvarchar(MAX) = N'', + @Azure bit = + CASE + WHEN + ( + SELECT + CONVERT + ( + integer, + SERVERPROPERTY('EngineEdition') + ) + ) = 5 + THEN 1 + ELSE 0 + END, + @MI bit = + CASE + WHEN + ( + SELECT + CONVERT + ( + integer, + SERVERPROPERTY('EngineEdition') + ) + ) = 8 + THEN 1 + ELSE 0 + END, + @RDS bit = + CASE + WHEN LEFT(CAST(SERVERPROPERTY('ComputerNamePhysicalNetBIOS') AS varchar(8000)), 8) <> 'EC2AMAZ-' + AND LEFT(CAST(SERVERPROPERTY('MachineName') AS varchar(8000)), 8) <> 'EC2AMAZ-' + AND DB_ID('rdsadmin') IS NULL + THEN 0 + ELSE 1 + END, + @d varchar(40) = '', + @StringToExecute nvarchar(4000) = N'', + @StringToExecuteParams nvarchar(500) = N'', + @r sysname = NULL, + @OutputTableFindings nvarchar(100) = N'[BlitzLockFindings]', + @DeadlockCount int = 0, + @ServerName sysname = @@SERVERNAME, + @OutputDatabaseCheck bit = -1, + @SessionId int = 0, + @TargetSessionId int = 0, + @FileName nvarchar(4000) = N'', + @inputbuf_bom nvarchar(1) = CONVERT(nvarchar(1), 0x0a00, 0), + @deadlock_result nvarchar(MAX) = N'', + @StartDateOriginal datetime = @StartDate, + @EndDateOriginal datetime = @EndDate, + @StartDateUTC datetime, + @EndDateUTC datetime; + /*Temporary objects used in the procedure*/ + DECLARE + @sysAssObjId AS table + ( + database_id int, + partition_id bigint, + schema_name sysname, + table_name sysname + ); -RAISERROR(N'Checking for table variables', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -, x AS ( -SELECT r.sql_handle, - c.n.value('substring(@Table, 2, 1)','VARCHAR(100)') AS first_char -FROM #relop r -CROSS APPLY r.relop.nodes('//p:Object') AS c(n) -) -UPDATE b -SET b.is_table_variable = 1 -FROM #working_warnings b -JOIN x ON x.sql_handle = b.sql_handle -JOIN #working_metrics AS wm -ON b.plan_id = wm.plan_id -AND b.query_id = wm.query_id -AND wm.batch_sql_handle IS NOT NULL -WHERE x.first_char = '@' -OPTION (RECOMPILE); + CREATE TABLE + #x + ( + x xml NOT NULL + DEFAULT N'x' + ); + + CREATE TABLE + #deadlock_data + ( + deadlock_xml xml NOT NULL + DEFAULT N'x' + ); + CREATE TABLE + #t + ( + id int NOT NULL + ); -IF @ExpertMode > 0 -BEGIN -RAISERROR(N'Checking for functions', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -, x AS ( -SELECT r.sql_handle, - n.fn.value('count(distinct-values(//p:UserDefinedFunction[not(@IsClrFunction)]))', 'INT') AS function_count, - n.fn.value('count(distinct-values(//p:UserDefinedFunction[@IsClrFunction = "1"]))', 'INT') AS clr_function_count -FROM #relop r -CROSS APPLY r.relop.nodes('/p:RelOp/p:ComputeScalar/p:DefinedValues/p:DefinedValue/p:ScalarOperator') n(fn) -) -UPDATE b -SET b.function_count = x.function_count, - b.clr_function_count = x.clr_function_count -FROM #working_warnings b -JOIN x ON x.sql_handle = b.sql_handle -OPTION (RECOMPILE); -END; + CREATE TABLE + #deadlock_findings + ( + id int IDENTITY PRIMARY KEY, + check_id int NOT NULL, + database_name nvarchar(256), + object_name nvarchar(1000), + finding_group nvarchar(100), + finding nvarchar(4000), + sort_order bigint + ); + /*Set these to some sane defaults if NULLs are passed in*/ + /*Normally I'd hate this, but we RECOMPILE everything*/ -RAISERROR(N'Checking for expensive key lookups', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET b.key_lookup_cost = x.key_lookup_cost -FROM #working_warnings b -JOIN ( -SELECT - r.sql_handle, - MAX(r.relop.value('sum(/p:RelOp/@EstimatedTotalSubtreeCost)', 'float')) AS key_lookup_cost -FROM #relop r -WHERE r.relop.exist('/p:RelOp/p:IndexScan[(@Lookup[.="1"])]') = 1 -GROUP BY r.sql_handle -) AS x ON x.sql_handle = b.sql_handle -OPTION (RECOMPILE); + SELECT + @StartDate = + CASE + WHEN @StartDate IS NULL + THEN + DATEADD + ( + MINUTE, + DATEDIFF + ( + MINUTE, + SYSDATETIME(), + GETUTCDATE() + ), + DATEADD + ( + DAY, + -7, + SYSDATETIME() + ) + ) + ELSE + DATEADD + ( + MINUTE, + DATEDIFF + ( + MINUTE, + SYSDATETIME(), + GETUTCDATE() + ), + @StartDate + ) + END, + @EndDate = + CASE + WHEN @EndDate IS NULL + THEN + DATEADD + ( + MINUTE, + DATEDIFF + ( + MINUTE, + SYSDATETIME(), + GETUTCDATE() + ), + SYSDATETIME() + ) + ELSE + DATEADD + ( + MINUTE, + DATEDIFF + ( + MINUTE, + SYSDATETIME(), + GETUTCDATE() + ), + @EndDate + ) + END; + SELECT + @StartDateUTC = @StartDate, + @EndDateUTC = @EndDate; -RAISERROR(N'Checking for expensive remote queries', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET b.remote_query_cost = x.remote_query_cost -FROM #working_warnings b -JOIN ( -SELECT - r.sql_handle, - MAX(r.relop.value('sum(/p:RelOp/@EstimatedTotalSubtreeCost)', 'float')) AS remote_query_cost -FROM #relop r -WHERE r.relop.exist('/p:RelOp[(@PhysicalOp[contains(., "Remote")])]') = 1 -GROUP BY r.sql_handle -) AS x ON x.sql_handle = b.sql_handle -OPTION (RECOMPILE); + IF + ( + @MI = 1 + AND @EventSessionName = N'system_health' + AND @TargetSessionType IS NULL + ) + BEGIN + SET + @TargetSessionType = N'ring_buffer'; + END; + IF @Azure = 0 + BEGIN + IF NOT EXISTS + ( + SELECT + 1/0 + FROM sys.server_event_sessions AS ses + JOIN sys.dm_xe_sessions AS dxs + ON dxs.name = ses.name + WHERE ses.name = @EventSessionName + AND dxs.create_time IS NOT NULL + ) + BEGIN + RAISERROR('A session with the name %s does not exist or is not currently active.', 11, 1, @EventSessionName) WITH NOWAIT; + RETURN; + END; + END; + + IF @Azure = 1 + BEGIN + IF NOT EXISTS + ( + SELECT + 1/0 + FROM sys.database_event_sessions AS ses + JOIN sys.dm_xe_database_sessions AS dxs + ON dxs.name = ses.name + WHERE ses.name = @EventSessionName + AND dxs.create_time IS NOT NULL + ) + BEGIN + RAISERROR('A session with the name %s does not exist or is not currently active.', 11, 1, @EventSessionName) WITH NOWAIT; + RETURN; + END; + END; -RAISERROR(N'Checking for expensive sorts', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET sort_cost = y.max_sort_cost -FROM #working_warnings b -JOIN ( - SELECT x.sql_handle, MAX((x.sort_io + x.sort_cpu)) AS max_sort_cost - FROM ( - SELECT - qs.sql_handle, - relop.value('sum(/p:RelOp/@EstimateIO)', 'float') AS sort_io, - relop.value('sum(/p:RelOp/@EstimateCPU)', 'float') AS sort_cpu - FROM #relop qs - WHERE [relop].exist('/p:RelOp[(@PhysicalOp[.="Sort"])]') = 1 - ) AS x - GROUP BY x.sql_handle - ) AS y -ON b.sql_handle = y.sql_handle -OPTION (RECOMPILE); + IF @OutputDatabaseName IS NOT NULL + BEGIN /*IF databaseName is set, do some sanity checks and put [] around def.*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('@OutputDatabaseName set to %s, checking validity at %s', 0, 1, @OutputDatabaseName, @d) WITH NOWAIT; -IF NOT EXISTS(SELECT 1/0 FROM #statements AS s WHERE s.is_cursor = 1) -BEGIN + IF NOT EXISTS + ( + SELECT + 1/0 + FROM sys.databases AS d + WHERE d.name = @OutputDatabaseName + ) /*If database is invalid raiserror and set bitcheck*/ + BEGIN + RAISERROR('Database Name (%s) for output of table is invalid please, Output to Table will not be performed', 0, 1, @OutputDatabaseName) WITH NOWAIT; + SET @OutputDatabaseCheck = -1; /* -1 invalid/false, 0 = good/true */ + END; + ELSE + BEGIN + SET @OutputDatabaseCheck = 0; -RAISERROR(N'No cursor plans found, skipping', 0, 1) WITH NOWAIT; + SELECT + @StringToExecute = + N'SELECT @r = o.name FROM ' + + @OutputDatabaseName + + N'.sys.objects AS o WHERE o.type_desc = N''USER_TABLE'' AND o.name = ' + + QUOTENAME + ( + @OutputTableName, + N'''' + ) + + N' AND o.schema_id = SCHEMA_ID(' + + QUOTENAME + ( + @OutputSchemaName, + N'''' + ) + + N');', + @StringToExecuteParams = + N'@r sysname OUTPUT'; -END + IF @Debug = 1 BEGIN PRINT @StringToExecute; END; + EXEC sys.sp_executesql + @StringToExecute, + @StringToExecuteParams, + @r OUTPUT; -IF EXISTS(SELECT 1/0 FROM #statements AS s WHERE s.is_cursor = 1) -BEGIN + IF @Debug = 1 + BEGIN + RAISERROR('@r is set to: %s for schema name %s and table name %s', 0, 1, @r, @OutputSchemaName, @OutputTableName) WITH NOWAIT; + END; -RAISERROR(N'Cursor plans found, investigating', 0, 1) WITH NOWAIT; + /*protection spells*/ + SELECT + @ObjectFullName = + QUOTENAME(@OutputDatabaseName) + + N'.' + + QUOTENAME(@OutputSchemaName) + + N'.' + + QUOTENAME(@OutputTableName), + @OutputDatabaseName = + QUOTENAME(@OutputDatabaseName), + @OutputTableName = + QUOTENAME(@OutputTableName), + @OutputSchemaName = + QUOTENAME(@OutputSchemaName); -RAISERROR(N'Checking for Optimistic cursors', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET b.is_optimistic_cursor = 1 -FROM #working_warnings b -JOIN #statements AS s -ON b.sql_handle = s.sql_handle -CROSS APPLY s.statement.nodes('/p:StmtCursor') AS n1(fn) -WHERE n1.fn.exist('//p:CursorPlan/@CursorConcurrency[.="Optimistic"]') = 1 -AND s.is_cursor = 1 -OPTION (RECOMPILE); + IF (@r IS NOT NULL) /*if it is not null, there is a table, so check for newly added columns*/ + BEGIN + /* If the table doesn't have the new spid column, add it. See Github #3101. */ + SET @StringToExecute = + N'IF NOT EXISTS (SELECT 1/0 FROM ' + + @OutputDatabaseName + + N'.sys.all_columns AS o WHERE o.object_id = (OBJECT_ID(''' + + @ObjectFullName + + N''')) AND o.name = N''spid'') + /*Add spid column*/ + ALTER TABLE ' + + @ObjectFullName + + N' ADD spid smallint NULL;'; + IF @Debug = 1 BEGIN PRINT @StringToExecute; END; + EXEC sys.sp_executesql + @StringToExecute; -RAISERROR(N'Checking if cursor is Forward Only', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET b.is_forward_only_cursor = 1 -FROM #working_warnings b -JOIN #statements AS s -ON b.sql_handle = s.sql_handle -CROSS APPLY s.statement.nodes('/p:StmtCursor') AS n1(fn) -WHERE n1.fn.exist('//p:CursorPlan/@ForwardOnly[.="true"]') = 1 -AND s.is_cursor = 1 -OPTION (RECOMPILE); + /* If the table doesn't have the new wait_resource column, add it. See Github #3101. */ + SET @StringToExecute = + N'IF NOT EXISTS (SELECT 1/0 FROM ' + + @OutputDatabaseName + + N'.sys.all_columns AS o WHERE o.object_id = (OBJECT_ID(''' + + @ObjectFullName + + N''')) AND o.name = N''wait_resource'') + /*Add wait_resource column*/ + ALTER TABLE ' + + @ObjectFullName + + N' ADD wait_resource nvarchar(MAX) NULL;'; + IF @Debug = 1 BEGIN PRINT @StringToExecute; END; + EXEC sys.sp_executesql + @StringToExecute; -RAISERROR(N'Checking if cursor is Fast Forward', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET b.is_fast_forward_cursor = 1 -FROM #working_warnings b -JOIN #statements AS s -ON b.sql_handle = s.sql_handle -CROSS APPLY s.statement.nodes('/p:StmtCursor') AS n1(fn) -WHERE n1.fn.exist('//p:CursorPlan/@CursorActualType[.="FastForward"]') = 1 -AND s.is_cursor = 1 -OPTION (RECOMPILE); + /* If the table doesn't have the new client option column, add it. See Github #3101. */ + SET @StringToExecute = + N'IF NOT EXISTS (SELECT 1/0 FROM ' + + @OutputDatabaseName + + N'.sys.all_columns AS o WHERE o.object_id = (OBJECT_ID(''' + + @ObjectFullName + + N''')) AND o.name = N''client_option_1'') + /*Add wait_resource column*/ + ALTER TABLE ' + + @ObjectFullName + + N' ADD client_option_1 varchar(500) NULL;'; + IF @Debug = 1 BEGIN PRINT @StringToExecute; END; + EXEC sys.sp_executesql + @StringToExecute; -RAISERROR(N'Checking for Dynamic cursors', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET b.is_cursor_dynamic = 1 -FROM #working_warnings b -JOIN #statements AS s -ON b.sql_handle = s.sql_handle -CROSS APPLY s.statement.nodes('/p:StmtCursor') AS n1(fn) -WHERE n1.fn.exist('//p:CursorPlan/@CursorActualType[.="Dynamic"]') = 1 -AND s.is_cursor = 1 -OPTION (RECOMPILE); + /* If the table doesn't have the new client option column, add it. See Github #3101. */ + SET @StringToExecute = + N'IF NOT EXISTS (SELECT 1/0 FROM ' + + @OutputDatabaseName + + N'.sys.all_columns AS o WHERE o.object_id = (OBJECT_ID(''' + + @ObjectFullName + + N''')) AND o.name = N''client_option_2'') + /*Add wait_resource column*/ + ALTER TABLE ' + + @ObjectFullName + + N' ADD client_option_2 varchar(500) NULL;'; -END + IF @Debug = 1 BEGIN PRINT @StringToExecute; END; + EXEC sys.sp_executesql + @StringToExecute; -IF @ExpertMode > 0 -BEGIN -RAISERROR(N'Checking for bad scans and plan forcing', 0, 1) WITH NOWAIT; -;WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET -b.is_table_scan = x.is_table_scan, -b.backwards_scan = x.backwards_scan, -b.forced_index = x.forced_index, -b.forced_seek = x.forced_seek, -b.forced_scan = x.forced_scan -FROM #working_warnings b -JOIN ( -SELECT - r.sql_handle, - 0 AS is_table_scan, - q.n.exist('@ScanDirection[.="BACKWARD"]') AS backwards_scan, - q.n.value('@ForcedIndex', 'bit') AS forced_index, - q.n.value('@ForceSeek', 'bit') AS forced_seek, - q.n.value('@ForceScan', 'bit') AS forced_scan -FROM #relop r -CROSS APPLY r.relop.nodes('//p:IndexScan') AS q(n) -UNION ALL -SELECT - r.sql_handle, - 1 AS is_table_scan, - q.n.exist('@ScanDirection[.="BACKWARD"]') AS backwards_scan, - q.n.value('@ForcedIndex', 'bit') AS forced_index, - q.n.value('@ForceSeek', 'bit') AS forced_seek, - q.n.value('@ForceScan', 'bit') AS forced_scan -FROM #relop r -CROSS APPLY r.relop.nodes('//p:TableScan') AS q(n) -) AS x ON b.sql_handle = x.sql_handle -OPTION (RECOMPILE); -END; + /* If the table doesn't have the new lock mode column, add it. See Github #3101. */ + SET @StringToExecute = + N'IF NOT EXISTS (SELECT 1/0 FROM ' + + @OutputDatabaseName + + N'.sys.all_columns AS o WHERE o.object_id = (OBJECT_ID(''' + + @ObjectFullName + + N''')) AND o.name = N''lock_mode'') + /*Add wait_resource column*/ + ALTER TABLE ' + + @ObjectFullName + + N' ADD lock_mode nvarchar(256) NULL;'; + IF @Debug = 1 BEGIN PRINT @StringToExecute; END; + EXEC sys.sp_executesql + @StringToExecute; -IF @ExpertMode > 0 -BEGIN -RAISERROR(N'Checking for computed columns that reference scalar UDFs', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET b.is_computed_scalar = x.computed_column_function -FROM #working_warnings b -JOIN ( -SELECT r.sql_handle, - n.fn.value('count(distinct-values(//p:UserDefinedFunction[not(@IsClrFunction)]))', 'INT') AS computed_column_function -FROM #relop r -CROSS APPLY r.relop.nodes('/p:RelOp/p:ComputeScalar/p:DefinedValues/p:DefinedValue/p:ScalarOperator') n(fn) -WHERE n.fn.exist('/p:RelOp/p:ComputeScalar/p:DefinedValues/p:DefinedValue/p:ColumnReference[(@ComputedColumn[.="1"])]') = 1 -) AS x ON x.sql_handle = b.sql_handle -OPTION (RECOMPILE); -END; + /* If the table doesn't have the new status column, add it. See Github #3101. */ + SET @StringToExecute = + N'IF NOT EXISTS (SELECT 1/0 FROM ' + + @OutputDatabaseName + + N'.sys.all_columns AS o WHERE o.object_id = (OBJECT_ID(''' + + @ObjectFullName + + N''')) AND o.name = N''status'') + /*Add wait_resource column*/ + ALTER TABLE ' + + @ObjectFullName + + N' ADD status nvarchar(256) NULL;'; + IF @Debug = 1 BEGIN PRINT @StringToExecute; END; + EXEC sys.sp_executesql + @StringToExecute; + END; + ELSE /* end if @r is not null. if it is null there is no table, create it from above execution */ + BEGIN + SELECT + @StringToExecute = + N'USE ' + + @OutputDatabaseName + + N'; + CREATE TABLE ' + + @OutputSchemaName + + N'.' + + @OutputTableName + + N' ( + ServerName nvarchar(256), + deadlock_type nvarchar(256), + event_date datetime, + database_name nvarchar(256), + spid smallint, + deadlock_group nvarchar(256), + query xml, + object_names xml, + isolation_level nvarchar(256), + owner_mode nvarchar(256), + waiter_mode nvarchar(256), + lock_mode nvarchar(256), + transaction_count bigint, + client_option_1 varchar(500), + client_option_2 varchar(500), + login_name nvarchar(256), + host_name nvarchar(256), + client_app nvarchar(1024), + wait_time bigint, + wait_resource nvarchar(max), + priority smallint, + log_used bigint, + last_tran_started datetime, + last_batch_started datetime, + last_batch_completed datetime, + transaction_name nvarchar(256), + status nvarchar(256), + owner_waiter_type nvarchar(256), + owner_activity nvarchar(256), + owner_waiter_activity nvarchar(256), + owner_merging nvarchar(256), + owner_spilling nvarchar(256), + owner_waiting_to_close nvarchar(256), + waiter_waiter_type nvarchar(256), + waiter_owner_activity nvarchar(256), + waiter_waiter_activity nvarchar(256), + waiter_merging nvarchar(256), + waiter_spilling nvarchar(256), + waiter_waiting_to_close nvarchar(256), + deadlock_graph xml + )'; -RAISERROR(N'Checking for filters that reference scalar UDFs', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET b.is_computed_filter = x.filter_function -FROM #working_warnings b -JOIN ( -SELECT -r.sql_handle, -c.n.value('count(distinct-values(//p:UserDefinedFunction[not(@IsClrFunction)]))', 'INT') AS filter_function -FROM #relop AS r -CROSS APPLY r.relop.nodes('/p:RelOp/p:Filter/p:Predicate/p:ScalarOperator/p:Compare/p:ScalarOperator/p:UserDefinedFunction') c(n) -) x ON x.sql_handle = b.sql_handle -OPTION (RECOMPILE); + IF @Debug = 1 BEGIN PRINT @StringToExecute; END; + EXEC sys.sp_executesql + @StringToExecute; + /*table created.*/ + SELECT + @StringToExecute = + N'SELECT @r = o.name FROM ' + + @OutputDatabaseName + + N'.sys.objects AS o + WHERE o.type_desc = N''USER_TABLE'' + AND o.name = N''BlitzLockFindings''', + @StringToExecuteParams = + N'@r sysname OUTPUT'; -IF @ExpertMode > 0 -BEGIN -RAISERROR(N'Checking modification queries that hit lots of indexes', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p), -IndexOps AS -( - SELECT - r.query_hash, - c.n.value('@PhysicalOp', 'VARCHAR(100)') AS op_name, - c.n.exist('@PhysicalOp[.="Index Insert"]') AS ii, - c.n.exist('@PhysicalOp[.="Index Update"]') AS iu, - c.n.exist('@PhysicalOp[.="Index Delete"]') AS id, - c.n.exist('@PhysicalOp[.="Clustered Index Insert"]') AS cii, - c.n.exist('@PhysicalOp[.="Clustered Index Update"]') AS ciu, - c.n.exist('@PhysicalOp[.="Clustered Index Delete"]') AS cid, - c.n.exist('@PhysicalOp[.="Table Insert"]') AS ti, - c.n.exist('@PhysicalOp[.="Table Update"]') AS tu, - c.n.exist('@PhysicalOp[.="Table Delete"]') AS td - FROM #relop AS r - CROSS APPLY r.relop.nodes('/p:RelOp') c(n) - OUTER APPLY r.relop.nodes('/p:RelOp/p:ScalarInsert/p:Object') q(n) - OUTER APPLY r.relop.nodes('/p:RelOp/p:Update/p:Object') o2(n) - OUTER APPLY r.relop.nodes('/p:RelOp/p:SimpleUpdate/p:Object') o3(n) -), iops AS -( - SELECT ios.query_hash, - SUM(CONVERT(TINYINT, ios.ii)) AS index_insert_count, - SUM(CONVERT(TINYINT, ios.iu)) AS index_update_count, - SUM(CONVERT(TINYINT, ios.id)) AS index_delete_count, - SUM(CONVERT(TINYINT, ios.cii)) AS cx_insert_count, - SUM(CONVERT(TINYINT, ios.ciu)) AS cx_update_count, - SUM(CONVERT(TINYINT, ios.cid)) AS cx_delete_count, - SUM(CONVERT(TINYINT, ios.ti)) AS table_insert_count, - SUM(CONVERT(TINYINT, ios.tu)) AS table_update_count, - SUM(CONVERT(TINYINT, ios.td)) AS table_delete_count - FROM IndexOps AS ios - WHERE ios.op_name IN ('Index Insert', 'Index Delete', 'Index Update', - 'Clustered Index Insert', 'Clustered Index Delete', 'Clustered Index Update', - 'Table Insert', 'Table Delete', 'Table Update') - GROUP BY ios.query_hash) -UPDATE b -SET b.index_insert_count = iops.index_insert_count, - b.index_update_count = iops.index_update_count, - b.index_delete_count = iops.index_delete_count, - b.cx_insert_count = iops.cx_insert_count, - b.cx_update_count = iops.cx_update_count, - b.cx_delete_count = iops.cx_delete_count, - b.table_insert_count = iops.table_insert_count, - b.table_update_count = iops.table_update_count, - b.table_delete_count = iops.table_delete_count -FROM #working_warnings AS b -JOIN iops ON iops.query_hash = b.query_hash -OPTION (RECOMPILE); -END; + IF @Debug = 1 BEGIN PRINT @StringToExecute; END; + EXEC sys.sp_executesql + @StringToExecute, + @StringToExecuteParams, + @r OUTPUT; + IF (@r IS NULL) /*if table does not exist*/ + BEGIN + SELECT + @OutputTableFindings = + QUOTENAME(N'BlitzLockFindings'), + @StringToExecute = + N'USE ' + + @OutputDatabaseName + + N'; + CREATE TABLE ' + + @OutputSchemaName + + N'.' + + @OutputTableFindings + + N' ( + ServerName nvarchar(256), + check_id INT, + database_name nvarchar(256), + object_name nvarchar(1000), + finding_group nvarchar(100), + finding nvarchar(4000) + );'; -IF @ExpertMode > 0 -BEGIN -RAISERROR(N'Checking for Spatial index use', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET b.is_spatial = x.is_spatial -FROM #working_warnings AS b -JOIN ( -SELECT r.sql_handle, - 1 AS is_spatial -FROM #relop r -CROSS APPLY r.relop.nodes('/p:RelOp//p:Object') n(fn) -WHERE n.fn.exist('(@IndexKind[.="Spatial"])') = 1 -) AS x ON x.sql_handle = b.sql_handle -OPTION (RECOMPILE); -END; + IF @Debug = 1 BEGIN PRINT @StringToExecute; END; + EXEC sys.sp_executesql + @StringToExecute; + END; + END; -RAISERROR(N'Checking for forced serialization', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET b.is_forced_serial = 1 -FROM #query_plan qp -JOIN #working_warnings AS b -ON qp.sql_handle = b.sql_handle -AND b.is_parallel IS NULL -AND qp.query_plan.exist('/p:QueryPlan/@NonParallelPlanReason') = 1 -OPTION (RECOMPILE); + /*create synonym for deadlockfindings.*/ + IF EXISTS + ( + SELECT + 1/0 + FROM sys.objects AS o + WHERE o.name = N'DeadlockFindings' + AND o.type_desc = N'SYNONYM' + ) + BEGIN + RAISERROR('Found synonym DeadlockFindings, dropping', 0, 1) WITH NOWAIT; + DROP SYNONYM DeadlockFindings; + END; + RAISERROR('Creating synonym DeadlockFindings', 0, 1) WITH NOWAIT; + SET @StringToExecute = + N'CREATE SYNONYM DeadlockFindings FOR ' + + @OutputDatabaseName + + N'.' + + @OutputSchemaName + + N'.' + + @OutputTableFindings; -IF @ExpertMode > 0 -BEGIN -RAISERROR(N'Checking for ColumnStore queries operating in Row Mode instead of Batch Mode', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET b.columnstore_row_mode = x.is_row_mode -FROM #working_warnings AS b -JOIN ( -SELECT - r.sql_handle, - r.relop.exist('/p:RelOp[(@EstimatedExecutionMode[.="Row"])]') AS is_row_mode -FROM #relop r -WHERE r.relop.exist('/p:RelOp/p:IndexScan[(@Storage[.="ColumnStore"])]') = 1 -) AS x ON x.sql_handle = b.sql_handle -OPTION (RECOMPILE); -END; + IF @Debug = 1 BEGIN PRINT @StringToExecute; END; + EXEC sys.sp_executesql + @StringToExecute; + /*create synonym for deadlock table.*/ + IF EXISTS + ( + SELECT + 1/0 + FROM sys.objects AS o + WHERE o.name = N'DeadLockTbl' + AND o.type_desc = N'SYNONYM' + ) + BEGIN + RAISERROR('Found synonym DeadLockTbl, dropping', 0, 1) WITH NOWAIT; + DROP SYNONYM DeadLockTbl; + END; -IF @ExpertMode > 0 -BEGIN -RAISERROR('Checking for row level security only', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET b.is_row_level = 1 -FROM #working_warnings b -JOIN #statements s -ON s.query_hash = b.query_hash -WHERE s.statement.exist('/p:StmtSimple/@SecurityPolicyApplied[.="true"]') = 1 -OPTION (RECOMPILE); -END; + RAISERROR('Creating synonym DeadLockTbl', 0, 1) WITH NOWAIT; + SET @StringToExecute = + N'CREATE SYNONYM DeadLockTbl FOR ' + + @OutputDatabaseName + + N'.' + + @OutputSchemaName + + N'.' + + @OutputTableName; + IF @Debug = 1 BEGIN PRINT @StringToExecute; END; + EXEC sys.sp_executesql + @StringToExecute; + END; + END; -IF @ExpertMode > 0 -BEGIN -RAISERROR('Checking for wonky Index Spools', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES ( - 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) -, selects -AS ( SELECT s.plan_id, s.query_id - FROM #statements AS s - WHERE s.statement.exist('/p:StmtSimple/@StatementType[.="SELECT"]') = 1 ) -, spools -AS ( SELECT DISTINCT r.plan_id, - r.query_id, - c.n.value('@EstimateRows', 'FLOAT') AS estimated_rows, - c.n.value('@EstimateIO', 'FLOAT') AS estimated_io, - c.n.value('@EstimateCPU', 'FLOAT') AS estimated_cpu, - c.n.value('@EstimateRebinds', 'FLOAT') AS estimated_rebinds -FROM #relop AS r -JOIN selects AS s -ON s.plan_id = r.plan_id - AND s.query_id = r.query_id -CROSS APPLY r.relop.nodes('/p:RelOp') AS c(n) -WHERE r.relop.exist('/p:RelOp[@PhysicalOp="Index Spool" and @LogicalOp="Eager Spool"]') = 1 -) -UPDATE ww - SET ww.index_spool_rows = sp.estimated_rows, - ww.index_spool_cost = ((sp.estimated_io * sp.estimated_cpu) * CASE WHEN sp.estimated_rebinds < 1 THEN 1 ELSE sp.estimated_rebinds END) + /* WITH ROWCOUNT doesn't work on Amazon RDS - see: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2037 */ + IF @RDS = 0 + BEGIN; + BEGIN TRY; + RAISERROR('@RDS = 0, updating #t with high row and page counts', 0, 1) WITH NOWAIT; + UPDATE STATISTICS + #t + WITH + ROWCOUNT = 9223372036854775807, + PAGECOUNT = 9223372036854775807; + END TRY + BEGIN CATCH; + /* Misleading error returned, if run without permissions to update statistics the error returned is "Cannot find object". + Catching specific error, and returning message with better info. If any other error is returned, then throw as normal */ + IF (ERROR_NUMBER() = 1088) + BEGIN; + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Cannot run UPDATE STATISTICS on a #temp table without db_owner or sysadmin permissions', 0, 1) WITH NOWAIT; + END; + ELSE + BEGIN; + THROW; + END; + END CATCH; + END; -FROM #working_warnings ww -JOIN spools sp -ON ww.plan_id = sp.plan_id -AND ww.query_id = sp.query_id -OPTION (RECOMPILE); -END; + /*If @TargetSessionType, we need to figure out if it's ring buffer or event file*/ + /*Azure has differently named views, so we need to separate. Thanks, Azure.*/ + IF + ( + @Azure = 0 + AND @TargetSessionType IS NULL + ) + BEGIN + RAISERROR('@TargetSessionType is NULL, assigning for non-Azure instance', 0, 1) WITH NOWAIT; -IF (PARSENAME(CONVERT(VARCHAR(128), SERVERPROPERTY ('PRODUCTVERSION')), 4)) >= 14 -OR ((PARSENAME(CONVERT(VARCHAR(128), SERVERPROPERTY ('PRODUCTVERSION')), 4)) = 13 - AND PARSENAME(CONVERT(VARCHAR(128), SERVERPROPERTY ('PRODUCTVERSION')), 2) >= 5026) + SELECT TOP (1) + @TargetSessionType = t.target_name + FROM sys.dm_xe_sessions AS s + JOIN sys.dm_xe_session_targets AS t + ON s.address = t.event_session_address + WHERE s.name = @EventSessionName + AND t.target_name IN (N'event_file', N'ring_buffer') + ORDER BY t.target_name + OPTION(RECOMPILE); -BEGIN + RAISERROR('@TargetSessionType assigned as %s for non-Azure', 0, 1, @TargetSessionType) WITH NOWAIT; + END; -RAISERROR(N'Beginning 2017 and 2016 SP2 specfic checks', 0, 1) WITH NOWAIT; + IF + ( + @Azure = 1 + AND @TargetSessionType IS NULL + ) + BEGIN + RAISERROR('@TargetSessionType is NULL, assigning for Azure instance', 0, 1) WITH NOWAIT; -IF @ExpertMode > 0 -BEGIN -RAISERROR('Gathering stats information', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -INSERT #stats_agg WITH (TABLOCK) - (sql_handle, last_update, modification_count, sampling_percent, [statistics], [table], [schema], [database]) -SELECT qp.sql_handle, - x.c.value('@LastUpdate', 'DATETIME2(7)') AS LastUpdate, - x.c.value('@ModificationCount', 'BIGINT') AS ModificationCount, - x.c.value('@SamplingPercent', 'FLOAT') AS SamplingPercent, - x.c.value('@Statistics', 'NVARCHAR(258)') AS [Statistics], - x.c.value('@Table', 'NVARCHAR(258)') AS [Table], - x.c.value('@Schema', 'NVARCHAR(258)') AS [Schema], - x.c.value('@Database', 'NVARCHAR(258)') AS [Database] -FROM #query_plan AS qp -CROSS APPLY qp.query_plan.nodes('//p:OptimizerStatsUsage/p:StatisticsInfo') x (c) -OPTION (RECOMPILE); + SELECT TOP (1) + @TargetSessionType = t.target_name + FROM sys.dm_xe_database_sessions AS s + JOIN sys.dm_xe_database_session_targets AS t + ON s.address = t.event_session_address + WHERE s.name = @EventSessionName + AND t.target_name IN (N'event_file', N'ring_buffer') + ORDER BY t.target_name + OPTION(RECOMPILE); + RAISERROR('@TargetSessionType assigned as %s for Azure', 0, 1, @TargetSessionType) WITH NOWAIT; + END; -RAISERROR('Checking for stale stats', 0, 1) WITH NOWAIT; -WITH stale_stats AS ( - SELECT sa.sql_handle - FROM #stats_agg AS sa - GROUP BY sa.sql_handle - HAVING MAX(sa.last_update) <= DATEADD(DAY, -7, SYSDATETIME()) - AND AVG(sa.modification_count) >= 100000 -) -UPDATE b -SET b.stale_stats = 1 -FROM #working_warnings AS b -JOIN stale_stats os -ON b.sql_handle = os.sql_handle -OPTION (RECOMPILE); -END; + /*The system health stuff gets handled different from user extended events.*/ + /*These next sections deal with user events, dependent on target.*/ -IF (PARSENAME(CONVERT(VARCHAR(128), SERVERPROPERTY ('PRODUCTVERSION')), 4)) >= 14 - AND @ExpertMode > 0 -BEGIN -RAISERROR(N'Checking for Adaptive Joins', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p), -aj AS ( - SELECT r.sql_handle - FROM #relop AS r - CROSS APPLY r.relop.nodes('//p:RelOp') x(c) - WHERE x.c.exist('@IsAdaptive[.=1]') = 1 -) -UPDATE b -SET b.is_adaptive = 1 -FROM #working_warnings AS b -JOIN aj -ON b.sql_handle = aj.sql_handle -OPTION (RECOMPILE); -END; + /*If ring buffers*/ + IF + ( + @TargetSessionType LIKE N'ring%' + AND @EventSessionName NOT LIKE N'system_health%' + ) + BEGIN + IF @Azure = 0 + BEGIN + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('@TargetSessionType is ring_buffer, inserting XML for non-Azure at %s', 0, 1, @d) WITH NOWAIT; + INSERT + #x WITH(TABLOCKX) + ( + x + ) + SELECT + x = TRY_CAST(t.target_data AS xml) + FROM sys.dm_xe_session_targets AS t + JOIN sys.dm_xe_sessions AS s + ON s.address = t.event_session_address + WHERE s.name = @EventSessionName + AND t.target_name = N'ring_buffer' + OPTION(RECOMPILE); -IF @ExpertMode > 0 -BEGIN; -RAISERROR(N'Checking for Row Goals', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p), -row_goals AS( -SELECT qs.query_hash -FROM #relop qs -WHERE relop.value('sum(/p:RelOp/@EstimateRowsWithoutRowGoal)', 'float') > 0 -) -UPDATE b -SET b.is_row_goal = 1 -FROM #working_warnings b -JOIN row_goals -ON b.query_hash = row_goals.query_hash -OPTION (RECOMPILE); -END; + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + END; -END; + IF @Azure = 1 + BEGIN + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('@TargetSessionType is ring_buffer, inserting XML for Azure at %s', 0, 1, @d) WITH NOWAIT; + INSERT + #x WITH(TABLOCKX) + ( + x + ) + SELECT + x = TRY_CAST(t.target_data AS xml) + FROM sys.dm_xe_database_session_targets AS t + JOIN sys.dm_xe_database_sessions AS s + ON s.address = t.event_session_address + WHERE s.name = @EventSessionName + AND t.target_name = N'ring_buffer' + OPTION(RECOMPILE); -RAISERROR(N'Performing query level checks', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET b.missing_index_count = query_plan.value('count(//p:QueryPlan/p:MissingIndexes/p:MissingIndexGroup)', 'int') , - b.unmatched_index_count = CASE WHEN is_trivial <> 1 THEN query_plan.value('count(//p:QueryPlan/p:UnmatchedIndexes/p:Parameterization/p:Object)', 'int') END -FROM #query_plan qp -JOIN #working_warnings AS b -ON b.query_hash = qp.query_hash -OPTION (RECOMPILE); + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + END; + END; -RAISERROR(N'Trace flag checks', 0, 1) WITH NOWAIT; -;WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -, tf_pretty AS ( -SELECT qp.sql_handle, - q.n.value('@Value', 'INT') AS trace_flag, - q.n.value('@Scope', 'VARCHAR(10)') AS scope -FROM #query_plan qp -CROSS APPLY qp.query_plan.nodes('/p:QueryPlan/p:TraceFlags/p:TraceFlag') AS q(n) -) -INSERT #trace_flags WITH (TABLOCK) - (sql_handle, global_trace_flags, session_trace_flags ) -SELECT DISTINCT tf1.sql_handle , - STUFF(( - SELECT DISTINCT N', ' + CONVERT(NVARCHAR(5), tf2.trace_flag) - FROM tf_pretty AS tf2 - WHERE tf1.sql_handle = tf2.sql_handle - AND tf2.scope = 'Global' - FOR XML PATH(N'')), 1, 2, N'' - ) AS global_trace_flags, - STUFF(( - SELECT DISTINCT N', ' + CONVERT(NVARCHAR(5), tf2.trace_flag) - FROM tf_pretty AS tf2 - WHERE tf1.sql_handle = tf2.sql_handle - AND tf2.scope = 'Session' - FOR XML PATH(N'')), 1, 2, N'' - ) AS session_trace_flags -FROM tf_pretty AS tf1 -OPTION (RECOMPILE); + /*If event file*/ + IF + ( + @TargetSessionType LIKE N'event%' + AND @EventSessionName NOT LIKE N'system_health%' + ) + BEGIN + IF @Azure = 0 + BEGIN + RAISERROR('@TargetSessionType is event_file, assigning XML for non-Azure', 0, 1) WITH NOWAIT; -UPDATE b -SET b.trace_flags_session = tf.session_trace_flags -FROM #working_warnings AS b -JOIN #trace_flags tf -ON tf.sql_handle = b.sql_handle -OPTION (RECOMPILE); + SELECT + @SessionId = t.event_session_id, + @TargetSessionId = t.target_id + FROM sys.server_event_session_targets AS t + JOIN sys.server_event_sessions AS s + ON s.event_session_id = t.event_session_id + WHERE t.name = @TargetSessionType + AND s.name = @EventSessionName + OPTION(RECOMPILE); + /*We get the file name automatically, here*/ + RAISERROR('Assigning @FileName...', 0, 1) WITH NOWAIT; + SELECT + @FileName = + CASE + WHEN f.file_name LIKE N'%.xel' + THEN REPLACE(f.file_name, N'.xel', N'*.xel') + ELSE f.file_name + N'*.xel' + END + FROM + ( + SELECT + file_name = + CONVERT(nvarchar(4000), f.value) + FROM sys.server_event_session_fields AS f + WHERE f.event_session_id = @SessionId + AND f.object_id = @TargetSessionId + AND f.name = N'filename' + ) AS f + OPTION(RECOMPILE); + END; -RAISERROR(N'Checking for MSTVFs', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET b.is_mstvf = 1 -FROM #relop AS r -JOIN #working_warnings AS b -ON b.sql_handle = r.sql_handle -WHERE r.relop.exist('/p:RelOp[(@EstimateRows="100" or @EstimateRows="1") and @LogicalOp="Table-valued function"]') = 1 -OPTION (RECOMPILE); + IF @Azure = 1 + BEGIN + RAISERROR('@TargetSessionType is event_file, assigning XML for Azure', 0, 1) WITH NOWAIT; + SELECT + @SessionId = + t.event_session_address, + @TargetSessionId = + t.target_name + FROM sys.dm_xe_database_session_targets t + JOIN sys.dm_xe_database_sessions s + ON s.address = t.event_session_address + WHERE t.target_name = @TargetSessionType + AND s.name = @EventSessionName + OPTION(RECOMPILE); -IF @ExpertMode > 0 -BEGIN -RAISERROR(N'Checking for many to many merge joins', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET b.is_mm_join = 1 -FROM #relop AS r -JOIN #working_warnings AS b -ON b.sql_handle = r.sql_handle -WHERE r.relop.exist('/p:RelOp/p:Merge/@ManyToMany[.="1"]') = 1 -OPTION (RECOMPILE); -END; + /*We get the file name automatically, here*/ + RAISERROR('Assigning @FileName...', 0, 1) WITH NOWAIT; + SELECT + @FileName = + CASE + WHEN f.file_name LIKE N'%.xel' + THEN REPLACE(f.file_name, N'.xel', N'*.xel') + ELSE f.file_name + N'*.xel' + END + FROM + ( + SELECT + file_name = + CONVERT(nvarchar(4000), f.value) + FROM sys.server_event_session_fields AS f + WHERE f.event_session_id = @SessionId + AND f.object_id = @TargetSessionId + AND f.name = N'filename' + ) AS f + OPTION(RECOMPILE); + END; + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Reading for event_file %s', 0, 1, @FileName) WITH NOWAIT; -IF @ExpertMode > 0 -BEGIN -RAISERROR(N'Is Paul White Electric?', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p), -is_paul_white_electric AS ( -SELECT 1 AS [is_paul_white_electric], -r.sql_handle -FROM #relop AS r -CROSS APPLY r.relop.nodes('//p:RelOp') c(n) -WHERE c.n.exist('@PhysicalOp[.="Switch"]') = 1 -) -UPDATE b -SET b.is_paul_white_electric = ipwe.is_paul_white_electric -FROM #working_warnings AS b -JOIN is_paul_white_electric ipwe -ON ipwe.sql_handle = b.sql_handle -OPTION (RECOMPILE); -END; + INSERT + #x WITH(TABLOCKX) + ( + x + ) + SELECT + x = TRY_CAST(f.event_data AS xml) + FROM sys.fn_xe_file_target_read_file(@FileName, NULL, NULL, NULL) AS f + LEFT JOIN #t AS t + ON 1 = 1 + OPTION(RECOMPILE); + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + END; + /*The XML is parsed differently if it comes from the event file or ring buffer*/ -RAISERROR(N'Checking for non-sargable predicates', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES ( 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) -, nsarg - AS ( SELECT r.query_hash, 1 AS fn, 0 AS jo, 0 AS lk - FROM #relop AS r - CROSS APPLY r.relop.nodes('/p:RelOp/p:IndexScan/p:Predicate/p:ScalarOperator/p:Compare/p:ScalarOperator') AS ca(x) - WHERE ( ca.x.exist('//p:ScalarOperator/p:Intrinsic/@FunctionName') = 1 - OR ca.x.exist('//p:ScalarOperator/p:IF') = 1 ) - UNION ALL - SELECT r.query_hash, 0 AS fn, 1 AS jo, 0 AS lk - FROM #relop AS r - CROSS APPLY r.relop.nodes('/p:RelOp//p:ScalarOperator') AS ca(x) - WHERE r.relop.exist('/p:RelOp[contains(@LogicalOp, "Join")]') = 1 - AND ca.x.exist('//p:ScalarOperator[contains(@ScalarString, "Expr")]') = 1 - UNION ALL - SELECT r.query_hash, 0 AS fn, 0 AS jo, 1 AS lk - FROM #relop AS r - CROSS APPLY r.relop.nodes('/p:RelOp/p:IndexScan/p:Predicate/p:ScalarOperator') AS ca(x) - CROSS APPLY ca.x.nodes('//p:Const') AS co(x) - WHERE ca.x.exist('//p:ScalarOperator/p:Intrinsic/@FunctionName[.="like"]') = 1 - AND ( ( co.x.value('substring(@ConstValue, 1, 1)', 'VARCHAR(100)') <> 'N' - AND co.x.value('substring(@ConstValue, 2, 1)', 'VARCHAR(100)') = '%' ) - OR ( co.x.value('substring(@ConstValue, 1, 1)', 'VARCHAR(100)') = 'N' - AND co.x.value('substring(@ConstValue, 3, 1)', 'VARCHAR(100)') = '%' ))), - d_nsarg - AS ( SELECT DISTINCT - nsarg.query_hash - FROM nsarg - WHERE nsarg.fn = 1 - OR nsarg.jo = 1 - OR nsarg.lk = 1 ) -UPDATE b -SET b.is_nonsargable = 1 -FROM d_nsarg AS d -JOIN #working_warnings AS b - ON b.query_hash = d.query_hash -OPTION ( RECOMPILE ); + /*If ring buffers*/ + IF + ( + @TargetSessionType LIKE N'ring%' + AND @EventSessionName NOT LIKE N'system_health%' + ) + BEGIN + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Inserting to #deadlock_data for ring buffer data', 0, 1) WITH NOWAIT; + INSERT + #deadlock_data WITH(TABLOCKX) + ( + deadlock_xml + ) + SELECT + deadlock_xml = + e.x.query(N'.') + FROM #x AS x + LEFT JOIN #t AS t + ON 1 = 1 + CROSS APPLY x.x.nodes('/RingBufferTarget/event') AS e(x) + WHERE + ( + e.x.exist('@name[ .= "xml_deadlock_report"]') = 1 + OR e.x.exist('@name[ .= "database_xml_deadlock_report"]') = 1 + OR e.x.exist('@name[ .= "xml_deadlock_report_filtered"]') = 1 + ) + AND e.x.exist('@timestamp[. >= sql:variable("@StartDate") and .< sql:variable("@EndDate")]') = 1 + OPTION(RECOMPILE); - RAISERROR(N'Getting information about implicit conversions and stored proc parameters', 0, 1) WITH NOWAIT; + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + END; - RAISERROR(N'Getting variable info', 0, 1) WITH NOWAIT; - WITH XMLNAMESPACES ( 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) - INSERT #variable_info ( query_hash, sql_handle, proc_name, variable_name, variable_datatype, compile_time_value ) - SELECT DISTINCT - qp.query_hash, - qp.sql_handle, - b.proc_or_function_name AS proc_name, - q.n.value('@Column', 'NVARCHAR(258)') AS variable_name, - q.n.value('@ParameterDataType', 'NVARCHAR(258)') AS variable_datatype, - q.n.value('@ParameterCompiledValue', 'NVARCHAR(258)') AS compile_time_value - FROM #query_plan AS qp - JOIN #working_warnings AS b - ON (b.query_hash = qp.query_hash AND b.proc_or_function_name = 'adhoc') - OR (b.sql_handle = qp.sql_handle AND b.proc_or_function_name <> 'adhoc') - CROSS APPLY qp.query_plan.nodes('//p:QueryPlan/p:ParameterList/p:ColumnReference') AS q(n) - OPTION (RECOMPILE); + /*If event file*/ + IF + ( + @TargetSessionType LIKE N'event_file%' + AND @EventSessionName NOT LIKE N'system_health%' + ) + BEGIN + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Inserting to #deadlock_data for event file data', 0, 1) WITH NOWAIT; - RAISERROR(N'Getting conversion info', 0, 1) WITH NOWAIT; - WITH XMLNAMESPACES ( 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) - INSERT #conversion_info ( query_hash, sql_handle, proc_name, expression ) - SELECT DISTINCT - qp.query_hash, - qp.sql_handle, - b.proc_or_function_name AS proc_name, - qq.c.value('@Expression', 'NVARCHAR(4000)') AS expression - FROM #query_plan AS qp - JOIN #working_warnings AS b - ON (b.query_hash = qp.query_hash AND b.proc_or_function_name = 'adhoc') - OR (b.sql_handle = qp.sql_handle AND b.proc_or_function_name <> 'adhoc') - CROSS APPLY qp.query_plan.nodes('//p:QueryPlan/p:Warnings/p:PlanAffectingConvert') AS qq(c) - WHERE qq.c.exist('@ConvertIssue[.="Seek Plan"]') = 1 - AND b.implicit_conversions = 1 - OPTION (RECOMPILE); + IF @Debug = 1 BEGIN SET STATISTICS XML ON; END; - RAISERROR(N'Parsing conversion info', 0, 1) WITH NOWAIT; - INSERT #stored_proc_info ( sql_handle, query_hash, proc_name, variable_name, variable_datatype, converted_column_name, column_name, converted_to, compile_time_value ) - SELECT ci.sql_handle, - ci.query_hash, - ci.proc_name, - CASE WHEN ci.at_charindex > 0 - AND ci.bracket_charindex > 0 - THEN SUBSTRING(ci.expression, ci.at_charindex, ci.bracket_charindex) - ELSE N'**no_variable**' - END AS variable_name, - N'**no_variable**' AS variable_datatype, - CASE WHEN ci.at_charindex = 0 - AND ci.comma_charindex > 0 - AND ci.second_comma_charindex > 0 - THEN SUBSTRING(ci.expression, ci.comma_charindex, ci.second_comma_charindex) - ELSE N'**no_column**' - END AS converted_column_name, - CASE WHEN ci.at_charindex = 0 - AND ci.equal_charindex > 0 - AND ci.convert_implicit_charindex = 0 - THEN SUBSTRING(ci.expression, ci.equal_charindex, 4000) - WHEN ci.at_charindex = 0 - AND (ci.equal_charindex -1) > 0 - AND ci.convert_implicit_charindex > 0 - THEN SUBSTRING(ci.expression, 0, ci.equal_charindex -1) - WHEN ci.at_charindex > 0 - AND ci.comma_charindex > 0 - AND ci.second_comma_charindex > 0 - THEN SUBSTRING(ci.expression, ci.comma_charindex, ci.second_comma_charindex) - ELSE N'**no_column **' - END AS column_name, - CASE WHEN ci.paren_charindex > 0 - 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 - 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 - FROM #conversion_info AS ci - OPTION (RECOMPILE); + INSERT + #deadlock_data WITH(TABLOCKX) + ( + deadlock_xml + ) + SELECT + deadlock_xml = + e.x.query('.') + FROM #x AS x + LEFT JOIN #t AS t + ON 1 = 1 + CROSS APPLY x.x.nodes('/event') AS e(x) + WHERE + ( + e.x.exist('@name[ .= "xml_deadlock_report"]') = 1 + OR e.x.exist('@name[ .= "database_xml_deadlock_report"]') = 1 + OR e.x.exist('@name[ .= "xml_deadlock_report_filtered"]') = 1 + ) + AND e.x.exist('@timestamp[. >= sql:variable("@StartDate") and .< sql:variable("@EndDate")]') = 1 + OPTION(RECOMPILE); - RAISERROR(N'Updating variables inserted procs', 0, 1) WITH NOWAIT; - UPDATE sp - SET sp.variable_datatype = vi.variable_datatype, - sp.compile_time_value = vi.compile_time_value - FROM #stored_proc_info AS sp - JOIN #variable_info AS vi - ON (sp.proc_name = 'adhoc' AND sp.query_hash = vi.query_hash) - OR (sp.proc_name <> 'adhoc' AND sp.sql_handle = vi.sql_handle) - AND sp.variable_name = vi.variable_name - OPTION (RECOMPILE); - - - RAISERROR(N'Inserting variables for other procs', 0, 1) WITH NOWAIT; - INSERT #stored_proc_info - ( sql_handle, query_hash, variable_name, variable_datatype, compile_time_value, proc_name ) - SELECT vi.sql_handle, vi.query_hash, vi.variable_name, vi.variable_datatype, vi.compile_time_value, vi.proc_name - FROM #variable_info AS vi - WHERE NOT EXISTS - ( - SELECT * - FROM #stored_proc_info AS sp - WHERE (sp.proc_name = 'adhoc' AND sp.query_hash = vi.query_hash) - OR (sp.proc_name <> 'adhoc' AND sp.sql_handle = vi.sql_handle) - ) - OPTION (RECOMPILE); - - RAISERROR(N'Updating procs', 0, 1) WITH NOWAIT; - UPDATE s - SET s.variable_datatype = CASE WHEN s.variable_datatype LIKE '%(%)%' - THEN LEFT(s.variable_datatype, CHARINDEX('(', s.variable_datatype) - 1) - ELSE s.variable_datatype - END, - s.converted_to = CASE WHEN s.converted_to LIKE '%(%)%' - THEN LEFT(s.converted_to, CHARINDEX('(', s.converted_to) - 1) - ELSE s.converted_to - END, - s.compile_time_value = CASE WHEN s.compile_time_value LIKE '%(%)%' - THEN SUBSTRING(s.compile_time_value, - CHARINDEX('(', s.compile_time_value) + 1, - CHARINDEX(')', s.compile_time_value) - 1 - CHARINDEX('(', s.compile_time_value) - ) - WHEN variable_datatype NOT IN ('bit', 'tinyint', 'smallint', 'int', 'bigint') - AND s.variable_datatype NOT LIKE '%binary%' - AND s.compile_time_value NOT LIKE 'N''%''' - AND s.compile_time_value NOT LIKE '''%''' - AND s.compile_time_value <> s.column_name - AND s.compile_time_value <> '**idk_man**' - THEN QUOTENAME(compile_time_value, '''') - ELSE s.compile_time_value - END - FROM #stored_proc_info AS s - OPTION (RECOMPILE); + IF @Debug = 1 BEGIN SET STATISTICS XML OFF; END; - - RAISERROR(N'Updating SET options', 0, 1) WITH NOWAIT; - WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) - UPDATE s - SET set_options = set_options.ansi_set_options - FROM #stored_proc_info AS s - JOIN ( - SELECT x.sql_handle, - N'SET ANSI_NULLS ' + CASE WHEN [ANSI_NULLS] = 'true' THEN N'ON ' ELSE N'OFF ' END + NCHAR(10) + - N'SET ANSI_PADDING ' + CASE WHEN [ANSI_PADDING] = 'true' THEN N'ON ' ELSE N'OFF ' END + NCHAR(10) + - N'SET ANSI_WARNINGS ' + CASE WHEN [ANSI_WARNINGS] = 'true' THEN N'ON ' ELSE N'OFF ' END + NCHAR(10) + - N'SET ARITHABORT ' + CASE WHEN [ARITHABORT] = 'true' THEN N'ON ' ELSE N' OFF ' END + NCHAR(10) + - N'SET CONCAT_NULL_YIELDS_NULL ' + CASE WHEN [CONCAT_NULL_YIELDS_NULL] = 'true' THEN N'ON ' ELSE N'OFF ' END + NCHAR(10) + - N'SET NUMERIC_ROUNDABORT ' + CASE WHEN [NUMERIC_ROUNDABORT] = 'true' THEN N'ON ' ELSE N'OFF ' END + NCHAR(10) + - N'SET QUOTED_IDENTIFIER ' + CASE WHEN [QUOTED_IDENTIFIER] = 'true' THEN N'ON ' ELSE N'OFF ' + NCHAR(10) END AS [ansi_set_options] - FROM ( - SELECT - s.sql_handle, - so.o.value('@ANSI_NULLS', 'NVARCHAR(20)') AS [ANSI_NULLS], - so.o.value('@ANSI_PADDING', 'NVARCHAR(20)') AS [ANSI_PADDING], - so.o.value('@ANSI_WARNINGS', 'NVARCHAR(20)') AS [ANSI_WARNINGS], - so.o.value('@ARITHABORT', 'NVARCHAR(20)') AS [ARITHABORT], - so.o.value('@CONCAT_NULL_YIELDS_NULL', 'NVARCHAR(20)') AS [CONCAT_NULL_YIELDS_NULL], - so.o.value('@NUMERIC_ROUNDABORT', 'NVARCHAR(20)') AS [NUMERIC_ROUNDABORT], - so.o.value('@QUOTED_IDENTIFIER', 'NVARCHAR(20)') AS [QUOTED_IDENTIFIER] - FROM #statements AS s - CROSS APPLY s.statement.nodes('//p:StatementSetOptions') AS so(o) - ) AS x - ) AS set_options ON set_options.sql_handle = s.sql_handle - OPTION(RECOMPILE); + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + END; + /*This section deals with event file*/ + IF + ( + @TargetSessionType LIKE N'event%' + AND @EventSessionName LIKE N'system_health%' + ) + BEGIN + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Grab the initial set of xml to parse at %s', 0, 1, @d) WITH NOWAIT; - RAISERROR(N'Updating conversion XML', 0, 1) WITH NOWAIT; - WITH precheck AS ( - SELECT spi.sql_handle, - spi.proc_name, - (SELECT CASE WHEN spi.proc_name <> 'Statement' - THEN N'The stored procedure ' + spi.proc_name - ELSE N'This ad hoc statement' - END - + N' had the following implicit conversions: ' - + CHAR(10) - + STUFF(( - SELECT DISTINCT - @cr + @lf - + CASE WHEN spi2.variable_name <> N'**no_variable**' - THEN N'The variable ' - WHEN spi2.variable_name = N'**no_variable**' AND (spi2.column_name = spi2.converted_column_name OR spi2.column_name LIKE '%CONVERT_IMPLICIT%') - THEN N'The compiled value ' - WHEN spi2.column_name LIKE '%Expr%' - THEN 'The expression ' - ELSE N'The column ' - END - + CASE WHEN spi2.variable_name <> N'**no_variable**' - THEN spi2.variable_name - WHEN spi2.variable_name = N'**no_variable**' AND (spi2.column_name = spi2.converted_column_name OR spi2.column_name LIKE '%CONVERT_IMPLICIT%') - THEN spi2.compile_time_value - - ELSE spi2.column_name - END - + N' has a data type of ' - + CASE WHEN spi2.variable_datatype = N'**no_variable**' THEN spi2.converted_to - ELSE spi2.variable_datatype - END - + N' which caused implicit conversion on the column ' - + CASE WHEN spi2.column_name LIKE N'%CONVERT_IMPLICIT%' - THEN spi2.converted_column_name - WHEN spi2.column_name = N'**no_column**' - THEN spi2.converted_column_name - WHEN spi2.converted_column_name = N'**no_column**' - THEN spi2.column_name - WHEN spi2.column_name <> spi2.converted_column_name - THEN spi2.converted_column_name - ELSE spi2.column_name - END - + CASE WHEN spi2.variable_name = N'**no_variable**' AND (spi2.column_name = spi2.converted_column_name OR spi2.column_name LIKE '%CONVERT_IMPLICIT%') - THEN N'' - WHEN spi2.column_name LIKE '%Expr%' - THEN N'' - WHEN spi2.compile_time_value NOT IN ('**declared in proc**', '**idk_man**') - AND spi2.compile_time_value <> spi2.column_name - THEN ' with the value ' + RTRIM(spi2.compile_time_value) - ELSE N'' - END - + '.' - FROM #stored_proc_info AS spi2 - WHERE spi.sql_handle = spi2.sql_handle - FOR XML PATH(N''), TYPE).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 1, N'') - AS [processing-instruction(ClickMe)] FOR XML PATH(''), TYPE ) - AS implicit_conversion_info - FROM #stored_proc_info AS spi - GROUP BY spi.sql_handle, spi.proc_name - ) - UPDATE b - SET b.implicit_conversion_info = pk.implicit_conversion_info - FROM #working_warnings AS b - JOIN precheck AS pk - ON pk.sql_handle = b.sql_handle - OPTION (RECOMPILE); - - RAISERROR(N'Updating cached parameter XML for procs', 0, 1) WITH NOWAIT; - WITH precheck AS ( - SELECT spi.sql_handle, - spi.proc_name, - (SELECT set_options - + @cr + @lf - + @cr + @lf - + N'EXEC ' - + spi.proc_name - + N' ' - + STUFF(( - SELECT DISTINCT N', ' - + CASE WHEN spi2.variable_name <> N'**no_variable**' AND spi2.compile_time_value <> N'**idk_man**' - THEN spi2.variable_name + N' = ' - ELSE @cr + @lf + N' We could not find any cached parameter values for this stored proc. ' - END - + CASE WHEN spi2.variable_name = N'**no_variable**' OR spi2.compile_time_value = N'**idk_man**' - THEN @cr + @lf + N' Possible reasons include declared variables inside the procedure, recompile hints, etc. ' - WHEN spi2.compile_time_value = N'NULL' - THEN spi2.compile_time_value - ELSE RTRIM(spi2.compile_time_value) - END - FROM #stored_proc_info AS spi2 - WHERE spi.sql_handle = spi2.sql_handle - AND spi2.proc_name <> N'Statement' - FOR XML PATH(N''), TYPE).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 1, N'') - AS [processing-instruction(ClickMe)] FOR XML PATH(''), TYPE ) - AS cached_execution_parameters - FROM #stored_proc_info AS spi - GROUP BY spi.sql_handle, spi.proc_name, set_options - ) - UPDATE b - SET b.cached_execution_parameters = pk.cached_execution_parameters - FROM #working_warnings AS b - JOIN precheck AS pk - ON pk.sql_handle = b.sql_handle - WHERE b.proc_or_function_name <> N'Statement' - OPTION (RECOMPILE); - - - RAISERROR(N'Updating cached parameter XML for statements', 0, 1) WITH NOWAIT; - WITH precheck AS ( - SELECT spi.sql_handle, - spi.proc_name, - (SELECT - set_options - + @cr + @lf - + @cr + @lf - + N' See QueryText column for full query text' - + @cr + @lf - + @cr + @lf - + STUFF(( - SELECT DISTINCT N', ' - + CASE WHEN spi2.variable_name <> N'**no_variable**' AND spi2.compile_time_value <> N'**idk_man**' - THEN spi2.variable_name + N' = ' - ELSE + @cr + @lf + N' We could not find any cached parameter values for this stored proc. ' - END - + CASE WHEN spi2.variable_name = N'**no_variable**' OR spi2.compile_time_value = N'**idk_man**' - THEN + @cr + @lf + N' Possible reasons include declared variables inside the procedure, recompile hints, etc. ' - WHEN spi2.compile_time_value = N'NULL' - THEN spi2.compile_time_value - ELSE RTRIM(spi2.compile_time_value) - END - FROM #stored_proc_info AS spi2 - WHERE spi.sql_handle = spi2.sql_handle - AND spi2.proc_name = N'Statement' - AND spi2.variable_name NOT LIKE N'%msparam%' - FOR XML PATH(N''), TYPE).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 1, N'') - AS [processing-instruction(ClickMe)] FOR XML PATH(''), TYPE ) - AS cached_execution_parameters - FROM #stored_proc_info AS spi - GROUP BY spi.sql_handle, spi.proc_name, spi.set_options - ) - UPDATE b - SET b.cached_execution_parameters = pk.cached_execution_parameters - FROM #working_warnings AS b - JOIN precheck AS pk - ON pk.sql_handle = b.sql_handle - WHERE b.proc_or_function_name = N'Statement' - OPTION (RECOMPILE); + IF @Debug = 1 BEGIN SET STATISTICS XML ON; END; + SELECT + xml.deadlock_xml + INTO #xml + FROM + ( + SELECT + deadlock_xml = + TRY_CAST(fx.event_data AS xml) + FROM sys.fn_xe_file_target_read_file(N'system_health*.xel', NULL, NULL, NULL) AS fx + LEFT JOIN #t AS t + ON 1 = 1 + WHERE fx.object_name = N'xml_deadlock_report' + ) AS xml + CROSS APPLY xml.deadlock_xml.nodes('/event') AS e(x) + WHERE 1 = 1 + AND e.x.exist('@timestamp[. >= sql:variable("@StartDate") and .< sql:variable("@EndDate")]') = 1 + OPTION(RECOMPILE); -RAISERROR(N'Filling in implicit conversion info', 0, 1) WITH NOWAIT; -UPDATE b -SET b.implicit_conversion_info = CASE WHEN b.implicit_conversion_info IS NULL - OR CONVERT(NVARCHAR(MAX), b.implicit_conversion_info) = N'' - THEN N'' - ELSE b.implicit_conversion_info - END, - b.cached_execution_parameters = CASE WHEN b.cached_execution_parameters IS NULL - OR CONVERT(NVARCHAR(MAX), b.cached_execution_parameters) = N'' - THEN N'' - ELSE b.cached_execution_parameters - END -FROM #working_warnings AS b -OPTION (RECOMPILE); + INSERT + #deadlock_data WITH(TABLOCKX) + SELECT + deadlock_xml = + xml.deadlock_xml + FROM #xml AS xml + LEFT JOIN #t AS t + ON 1 = 1 + WHERE xml.deadlock_xml IS NOT NULL + OPTION(RECOMPILE); -/*End implicit conversion and parameter info*/ + IF @Debug = 1 BEGIN SET STATISTICS XML OFF; END; -/*Begin Missing Index*/ -IF EXISTS ( SELECT 1/0 - FROM #working_warnings AS ww - WHERE ww.missing_index_count > 0 - OR ww.index_spool_cost > 0 - OR ww.index_spool_rows > 0 ) - - BEGIN - - RAISERROR(N'Inserting to #missing_index_xml', 0, 1) WITH NOWAIT; - WITH XMLNAMESPACES ( 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) - INSERT #missing_index_xml - SELECT qp.query_hash, - qp.sql_handle, - c.mg.value('@Impact', 'FLOAT') AS Impact, - c.mg.query('.') AS cmg - FROM #query_plan AS qp - CROSS APPLY qp.query_plan.nodes('//p:MissingIndexes/p:MissingIndexGroup') AS c(mg) - WHERE qp.query_hash IS NOT NULL - AND c.mg.value('@Impact', 'FLOAT') > 70.0 - OPTION (RECOMPILE); + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + END; - RAISERROR(N'Inserting to #missing_index_schema', 0, 1) WITH NOWAIT; - WITH XMLNAMESPACES ( 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) - INSERT #missing_index_schema - SELECT mix.query_hash, mix.sql_handle, mix.impact, - c.mi.value('@Database', 'NVARCHAR(128)'), - c.mi.value('@Schema', 'NVARCHAR(128)'), - c.mi.value('@Table', 'NVARCHAR(128)'), - c.mi.query('.') - FROM #missing_index_xml AS mix - CROSS APPLY mix.index_xml.nodes('//p:MissingIndex') AS c(mi) - OPTION (RECOMPILE); - - RAISERROR(N'Inserting to #missing_index_usage', 0, 1) WITH NOWAIT; - WITH XMLNAMESPACES ( 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) - INSERT #missing_index_usage - SELECT ms.query_hash, ms.sql_handle, ms.impact, ms.database_name, ms.schema_name, ms.table_name, - c.cg.value('@Usage', 'NVARCHAR(128)'), - c.cg.query('.') - FROM #missing_index_schema ms - CROSS APPLY ms.index_xml.nodes('//p:ColumnGroup') AS c(cg) - OPTION (RECOMPILE); - - RAISERROR(N'Inserting to #missing_index_detail', 0, 1) WITH NOWAIT; - WITH XMLNAMESPACES ( 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) - INSERT #missing_index_detail - SELECT miu.query_hash, - miu.sql_handle, - miu.impact, - miu.database_name, - miu.schema_name, - miu.table_name, - miu.usage, - c.c.value('@Name', 'NVARCHAR(128)') - FROM #missing_index_usage AS miu - CROSS APPLY miu.index_xml.nodes('//p:Column') AS c(c) - OPTION (RECOMPILE); - - RAISERROR(N'Inserting to #missing_index_pretty', 0, 1) WITH NOWAIT; - INSERT #missing_index_pretty - SELECT DISTINCT m.query_hash, m.sql_handle, m.impact, m.database_name, m.schema_name, m.table_name - , STUFF(( SELECT DISTINCT N', ' + ISNULL(m2.column_name, '') AS column_name - FROM #missing_index_detail AS m2 - WHERE m2.usage = 'EQUALITY' - AND m.query_hash = m2.query_hash - AND m.sql_handle = m2.sql_handle - AND m.impact = m2.impact - AND m.database_name = m2.database_name - AND m.schema_name = m2.schema_name - AND m.table_name = m2.table_name - FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 2, N'') AS equality - , STUFF(( SELECT DISTINCT N', ' + ISNULL(m2.column_name, '') AS column_name - FROM #missing_index_detail AS m2 - WHERE m2.usage = 'INEQUALITY' - AND m.query_hash = m2.query_hash - AND m.sql_handle = m2.sql_handle - AND m.impact = m2.impact - AND m.database_name = m2.database_name - AND m.schema_name = m2.schema_name - AND m.table_name = m2.table_name - FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 2, N'') AS inequality - , STUFF(( SELECT DISTINCT N', ' + ISNULL(m2.column_name, '') AS column_name - FROM #missing_index_detail AS m2 - WHERE m2.usage = 'INCLUDE' - AND m.query_hash = m2.query_hash - AND m.sql_handle = m2.sql_handle - AND m.impact = m2.impact - AND m.database_name = m2.database_name - AND m.schema_name = m2.schema_name - AND m.table_name = m2.table_name - FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 2, N'') AS [include], - 0 AS is_spool - FROM #missing_index_detail AS m - GROUP BY m.query_hash, m.sql_handle, m.impact, m.database_name, m.schema_name, m.table_name - OPTION (RECOMPILE); + /*Parse process and input buffer xml*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Initial Parse process and input buffer xml %s', 0, 1, @d) WITH NOWAIT; - RAISERROR(N'Inserting to #index_spool_ugly', 0, 1) WITH NOWAIT; - WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) - INSERT #index_spool_ugly - (query_hash, sql_handle, impact, database_name, schema_name, table_name, equality, inequality, include) - SELECT r.query_hash, - r.sql_handle, - (c.n.value('@EstimateIO', 'FLOAT') + (c.n.value('@EstimateCPU', 'FLOAT'))) - / ( 1 * NULLIF(ww.query_cost, 0)) * 100 AS impact, - o.n.value('@Database', 'NVARCHAR(128)') AS output_database, - o.n.value('@Schema', 'NVARCHAR(128)') AS output_schema, - o.n.value('@Table', 'NVARCHAR(128)') AS output_table, - k.n.value('@Column', 'NVARCHAR(128)') AS range_column, - e.n.value('@Column', 'NVARCHAR(128)') AS expression_column, - o.n.value('@Column', 'NVARCHAR(128)') AS output_column - FROM #relop AS r - JOIN #working_warnings AS ww - ON ww.query_hash = r.query_hash - CROSS APPLY r.relop.nodes('/p:RelOp') AS c(n) - CROSS APPLY r.relop.nodes('/p:RelOp/p:OutputList/p:ColumnReference') AS o(n) - OUTER APPLY r.relop.nodes('/p:RelOp/p:Spool/p:SeekPredicateNew/p:SeekKeys/p:Prefix/p:RangeColumns/p:ColumnReference') AS k(n) - OUTER APPLY r.relop.nodes('/p:RelOp/p:Spool/p:SeekPredicateNew/p:SeekKeys/p:Prefix/p:RangeExpressions/p:ColumnReference') AS e(n) - WHERE r.relop.exist('/p:RelOp[@PhysicalOp="Index Spool" and @LogicalOp="Eager Spool"]') = 1 - - RAISERROR(N'Inserting to spools to #missing_index_pretty', 0, 1) WITH NOWAIT; - INSERT #missing_index_pretty - (query_hash, sql_handle, impact, database_name, schema_name, table_name, equality, inequality, include, is_spool) - SELECT DISTINCT - isu.query_hash, - isu.sql_handle, - isu.impact, - isu.database_name, - isu.schema_name, - isu.table_name - , STUFF(( SELECT DISTINCT N', ' + ISNULL(isu2.equality, '') AS column_name - FROM #index_spool_ugly AS isu2 - WHERE isu2.equality IS NOT NULL - AND isu.query_hash = isu2.query_hash - AND isu.sql_handle = isu2.sql_handle - AND isu.impact = isu2.impact - AND isu.database_name = isu2.database_name - AND isu.schema_name = isu2.schema_name - AND isu.table_name = isu2.table_name - FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 2, N'') AS equality - , STUFF(( SELECT DISTINCT N', ' + ISNULL(isu2.inequality, '') AS column_name - FROM #index_spool_ugly AS isu2 - WHERE isu2.inequality IS NOT NULL - AND isu.query_hash = isu2.query_hash - AND isu.sql_handle = isu2.sql_handle - AND isu.impact = isu2.impact - AND isu.database_name = isu2.database_name - AND isu.schema_name = isu2.schema_name - AND isu.table_name = isu2.table_name - FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 2, N'') AS inequality - , STUFF(( SELECT DISTINCT N', ' + ISNULL(isu2.include, '') AS column_name - FROM #index_spool_ugly AS isu2 - WHERE isu2.include IS NOT NULL - AND isu.query_hash = isu2.query_hash - AND isu.sql_handle = isu2.sql_handle - AND isu.impact = isu2.impact - AND isu.database_name = isu2.database_name - AND isu.schema_name = isu2.schema_name - AND isu.table_name = isu2.table_name - FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 2, N'') AS include, - 1 AS is_spool - FROM #index_spool_ugly AS isu + SELECT + d1.deadlock_xml, + event_date = d1.deadlock_xml.value('(event/@timestamp)[1]', 'datetime2'), + victim_id = d1.deadlock_xml.value('(//deadlock/victim-list/victimProcess/@id)[1]', 'nvarchar(256)'), + is_parallel = d1.deadlock_xml.exist('//deadlock/resource-list/exchangeEvent'), + is_parallel_batch = d1.deadlock_xml.exist('//deadlock/resource-list/SyncPoint'), + deadlock_graph = d1.deadlock_xml.query('/event/data/value/deadlock') + INTO #dd + FROM #deadlock_data AS d1 + LEFT JOIN #t AS t + ON 1 = 1 + OPTION(RECOMPILE); + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - RAISERROR(N'Updating missing index information', 0, 1) WITH NOWAIT; - WITH missing AS ( - SELECT DISTINCT - mip.query_hash, - mip.sql_handle, - N'' - AS full_details - FROM #missing_index_pretty AS mip - GROUP BY mip.query_hash, mip.sql_handle, mip.impact - ) - UPDATE ww - SET ww.missing_indexes = m.full_details - FROM #working_warnings AS ww - JOIN missing AS m - ON m.sql_handle = ww.sql_handle - OPTION (RECOMPILE); + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Final Parse process and input buffer xml %s', 0, 1, @d) WITH NOWAIT; - RAISERROR(N'Filling in missing index blanks', 0, 1) WITH NOWAIT; - UPDATE ww - SET ww.missing_indexes = - CASE WHEN ww.missing_indexes IS NULL - THEN '' - ELSE ww.missing_indexes - END - FROM #working_warnings AS ww - OPTION (RECOMPILE); + SELECT + q.event_date, + q.victim_id, + is_parallel = + CONVERT(bit, q.is_parallel), + q.deadlock_graph, + q.id, + q.spid, + q.database_id, + database_name = + ISNULL + ( + DB_NAME(q.database_id), + N'UNKNOWN' + ), + q.current_database_name, + q.priority, + q.log_used, + q.wait_resource, + q.wait_time, + q.transaction_name, + q.last_tran_started, + q.last_batch_started, + q.last_batch_completed, + q.lock_mode, + q.status, + q.transaction_count, + q.client_app, + q.host_name, + q.login_name, + q.isolation_level, + client_option_1 = + SUBSTRING + ( + CASE WHEN q.clientoption1 & 1 = 1 THEN ', DISABLE_DEF_CNST_CHECK' ELSE '' END + + CASE WHEN q.clientoption1 & 2 = 2 THEN ', IMPLICIT_TRANSACTIONS' ELSE '' END + + CASE WHEN q.clientoption1 & 4 = 4 THEN ', CURSOR_CLOSE_ON_COMMIT' ELSE '' END + + CASE WHEN q.clientoption1 & 8 = 8 THEN ', ANSI_WARNINGS' ELSE '' END + + CASE WHEN q.clientoption1 & 16 = 16 THEN ', ANSI_PADDING' ELSE '' END + + CASE WHEN q.clientoption1 & 32 = 32 THEN ', ANSI_NULLS' ELSE '' END + + CASE WHEN q.clientoption1 & 64 = 64 THEN ', ARITHABORT' ELSE '' END + + CASE WHEN q.clientoption1 & 128 = 128 THEN ', ARITHIGNORE' ELSE '' END + + CASE WHEN q.clientoption1 & 256 = 256 THEN ', QUOTED_IDENTIFIER' ELSE '' END + + CASE WHEN q.clientoption1 & 512 = 512 THEN ', NOCOUNT' ELSE '' END + + CASE WHEN q.clientoption1 & 1024 = 1024 THEN ', ANSI_NULL_DFLT_ON' ELSE '' END + + CASE WHEN q.clientoption1 & 2048 = 2048 THEN ', ANSI_NULL_DFLT_OFF' ELSE '' END + + CASE WHEN q.clientoption1 & 4096 = 4096 THEN ', CONCAT_NULL_YIELDS_NULL' ELSE '' END + + CASE WHEN q.clientoption1 & 8192 = 8192 THEN ', NUMERIC_ROUNDABORT' ELSE '' END + + CASE WHEN q.clientoption1 & 16384 = 16384 THEN ', XACT_ABORT' ELSE '' END, + 3, + 500 + ), + client_option_2 = + SUBSTRING + ( + CASE WHEN q.clientoption2 & 1024 = 1024 THEN ', DB CHAINING' ELSE '' END + + CASE WHEN q.clientoption2 & 2048 = 2048 THEN ', NUMERIC ROUNDABORT' ELSE '' END + + CASE WHEN q.clientoption2 & 4096 = 4096 THEN ', ARITHABORT' ELSE '' END + + CASE WHEN q.clientoption2 & 8192 = 8192 THEN ', ANSI PADDING' ELSE '' END + + CASE WHEN q.clientoption2 & 16384 = 16384 THEN ', ANSI NULL DEFAULT' ELSE '' END + + CASE WHEN q.clientoption2 & 65536 = 65536 THEN ', CONCAT NULL YIELDS NULL' ELSE '' END + + CASE WHEN q.clientoption2 & 131072 = 131072 THEN ', RECURSIVE TRIGGERS' ELSE '' END + + CASE WHEN q.clientoption2 & 1048576 = 1048576 THEN ', DEFAULT TO LOCAL CURSOR' ELSE '' END + + CASE WHEN q.clientoption2 & 8388608 = 8388608 THEN ', QUOTED IDENTIFIER' ELSE '' END + + CASE WHEN q.clientoption2 & 16777216 = 16777216 THEN ', AUTO CREATE STATISTICS' ELSE '' END + + CASE WHEN q.clientoption2 & 33554432 = 33554432 THEN ', CURSOR CLOSE ON COMMIT' ELSE '' END + + CASE WHEN q.clientoption2 & 67108864 = 67108864 THEN ', ANSI NULLS' ELSE '' END + + CASE WHEN q.clientoption2 & 268435456 = 268435456 THEN ', ANSI WARNINGS' ELSE '' END + + CASE WHEN q.clientoption2 & 536870912 = 536870912 THEN ', FULL TEXT ENABLED' ELSE '' END + + CASE WHEN q.clientoption2 & 1073741824 = 1073741824 THEN ', AUTO UPDATE STATISTICS' ELSE '' END + + CASE WHEN q.clientoption2 & 1469283328 = 1469283328 THEN ', ALL SETTABLE OPTIONS' ELSE '' END, + 3, + 500 + ), + q.process_xml + INTO #deadlock_process + FROM + ( + SELECT + dd.deadlock_xml, + event_date = + DATEADD + ( + MINUTE, + DATEDIFF + ( + MINUTE, + GETUTCDATE(), + SYSDATETIME() + ), + dd.event_date + ), + dd.victim_id, + is_parallel = + CONVERT(tinyint, dd.is_parallel) + + CONVERT(tinyint, dd.is_parallel_batch), + dd.deadlock_graph, + id = ca.dp.value('@id', 'nvarchar(256)'), + spid = ca.dp.value('@spid', 'smallint'), + database_id = ca.dp.value('@currentdb', 'bigint'), + current_database_name = ca.dp.value('@currentdbname', 'nvarchar(256)'), + priority = ca.dp.value('@priority', 'smallint'), + log_used = ca.dp.value('@logused', 'bigint'), + wait_resource = ca.dp.value('@waitresource', 'nvarchar(256)'), + wait_time = ca.dp.value('@waittime', 'bigint'), + transaction_name = ca.dp.value('@transactionname', 'nvarchar(256)'), + last_tran_started = ca.dp.value('@lasttranstarted', 'datetime'), + last_batch_started = ca.dp.value('@lastbatchstarted', 'datetime'), + last_batch_completed = ca.dp.value('@lastbatchcompleted', 'datetime'), + lock_mode = ca.dp.value('@lockMode', 'nvarchar(256)'), + status = ca.dp.value('@status', 'nvarchar(256)'), + transaction_count = ca.dp.value('@trancount', 'bigint'), + client_app = ca.dp.value('@clientapp', 'nvarchar(1024)'), + host_name = ca.dp.value('@hostname', 'nvarchar(256)'), + login_name = ca.dp.value('@loginname', 'nvarchar(256)'), + isolation_level = ca.dp.value('@isolationlevel', 'nvarchar(256)'), + clientoption1 = ca.dp.value('@clientoption1', 'bigint'), + clientoption2 = ca.dp.value('@clientoption2', 'bigint'), + process_xml = ISNULL(ca.dp.query(N'.'), N'') + FROM #dd AS dd + CROSS APPLY dd.deadlock_xml.nodes('//deadlock/process-list/process') AS ca(dp) + WHERE (ca.dp.exist('@currentdb[. = sql:variable("@DatabaseId")]') = 1 OR @DatabaseName IS NULL) + AND (ca.dp.exist('@clientapp[. = sql:variable("@AppName")]') = 1 OR @AppName IS NULL) + AND (ca.dp.exist('@hostname[. = sql:variable("@HostName")]') = 1 OR @HostName IS NULL) + AND (ca.dp.exist('@loginname[. = sql:variable("@LoginName")]') = 1 OR @LoginName IS NULL) + ) AS q + LEFT JOIN #t AS t + ON 1 = 1 + OPTION(RECOMPILE); -END -/*End Missing Index*/ -RAISERROR(N'General query dispositions: frequent executions, long running, etc.', 0, 1) WITH NOWAIT; + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET b.frequent_execution = CASE WHEN wm.xpm > @execution_threshold THEN 1 END , - b.near_parallel = CASE WHEN b.query_cost BETWEEN @ctp * (1 - (@ctp_threshold_pct / 100.0)) AND @ctp THEN 1 END, - b.long_running = CASE WHEN wm.avg_duration > @long_running_query_warning_seconds THEN 1 - WHEN wm.max_duration > @long_running_query_warning_seconds THEN 1 - WHEN wm.avg_cpu_time > @long_running_query_warning_seconds THEN 1 - WHEN wm.max_cpu_time > @long_running_query_warning_seconds THEN 1 END, - b.is_key_lookup_expensive = CASE WHEN b.query_cost >= (@ctp / 2) AND b.key_lookup_cost >= b.query_cost * .5 THEN 1 END, - b.is_sort_expensive = CASE WHEN b.query_cost >= (@ctp / 2) AND b.sort_cost >= b.query_cost * .5 THEN 1 END, - b.is_remote_query_expensive = CASE WHEN b.remote_query_cost >= b.query_cost * .05 THEN 1 END, - b.is_unused_grant = CASE WHEN percent_memory_grant_used <= @memory_grant_warning_percent AND min_query_max_used_memory > @min_memory_per_query THEN 1 END, - b.long_running_low_cpu = CASE WHEN wm.avg_duration > wm.avg_cpu_time * 4 AND avg_cpu_time < 500. THEN 1 END, - b.low_cost_high_cpu = CASE WHEN b.query_cost < 10 AND wm.avg_cpu_time > 5000. THEN 1 END, - b.is_spool_expensive = CASE WHEN b.query_cost > (@ctp / 2) AND b.index_spool_cost >= b.query_cost * .1 THEN 1 END, - b.is_spool_more_rows = CASE WHEN b.index_spool_rows >= wm.min_rowcount THEN 1 END, - b.is_bad_estimate = CASE WHEN wm.avg_rowcount > 0 AND (b.estimated_rows * 1000 < wm.avg_rowcount OR b.estimated_rows > wm.avg_rowcount * 1000) THEN 1 END, - b.is_big_log = CASE WHEN wm.avg_log_bytes_used >= (@log_size_mb / 2.) THEN 1 END, - b.is_big_tempdb = CASE WHEN wm.avg_tempdb_space_used >= (@avg_tempdb_data_file / 2.) THEN 1 END -FROM #working_warnings AS b -JOIN #working_metrics AS wm -ON b.plan_id = wm.plan_id -AND b.query_id = wm.query_id -JOIN #working_plan_text AS wpt -ON b.plan_id = wpt.plan_id -AND b.query_id = wpt.query_id -OPTION (RECOMPILE); + /*Parse execution stack xml*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Parse execution stack xml %s', 0, 1, @d) WITH NOWAIT; + SELECT DISTINCT + dp.id, + dp.event_date, + proc_name = ca.dp.value('@procname', 'nvarchar(1024)'), + sql_handle = ca.dp.value('@sqlhandle', 'nvarchar(131)') + INTO #deadlock_stack + FROM #deadlock_process AS dp + CROSS APPLY dp.process_xml.nodes('//executionStack/frame') AS ca(dp) + WHERE (ca.dp.exist('@procname[. = sql:variable("@StoredProcName")]') = 1 OR @StoredProcName IS NULL) + AND ca.dp.exist('@sqlhandle[ .= "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"]') = 0 + OPTION(RECOMPILE); -RAISERROR('Populating Warnings column', 0, 1) WITH NOWAIT; -/* Populate warnings */ -UPDATE b -SET b.warnings = SUBSTRING( - CASE WHEN b.warning_no_join_predicate = 1 THEN ', No Join Predicate' ELSE '' END + - CASE WHEN b.compile_timeout = 1 THEN ', Compilation Timeout' ELSE '' END + - CASE WHEN b.compile_memory_limit_exceeded = 1 THEN ', Compile Memory Limit Exceeded' ELSE '' END + - CASE WHEN b.is_forced_plan = 1 THEN ', Forced Plan' ELSE '' END + - CASE WHEN b.is_forced_parameterized = 1 THEN ', Forced Parameterization' ELSE '' END + - CASE WHEN b.unparameterized_query = 1 THEN ', Unparameterized Query' ELSE '' END + - CASE WHEN b.missing_index_count > 0 THEN ', Missing Indexes (' + CAST(b.missing_index_count AS NVARCHAR(3)) + ')' ELSE '' END + - CASE WHEN b.unmatched_index_count > 0 THEN ', Unmatched Indexes (' + CAST(b.unmatched_index_count AS NVARCHAR(3)) + ')' ELSE '' END + - CASE WHEN b.is_cursor = 1 THEN ', Cursor' - + CASE WHEN b.is_optimistic_cursor = 1 THEN '; optimistic' ELSE '' END - + CASE WHEN b.is_forward_only_cursor = 0 THEN '; not forward only' ELSE '' END - + CASE WHEN b.is_cursor_dynamic = 1 THEN '; dynamic' ELSE '' END - + CASE WHEN b.is_fast_forward_cursor = 1 THEN '; fast forward' ELSE '' END - ELSE '' END + - CASE WHEN b.is_parallel = 1 THEN ', Parallel' ELSE '' END + - CASE WHEN b.near_parallel = 1 THEN ', Nearly Parallel' ELSE '' END + - CASE WHEN b.frequent_execution = 1 THEN ', Frequent Execution' ELSE '' END + - CASE WHEN b.plan_warnings = 1 THEN ', Plan Warnings' ELSE '' END + - CASE WHEN b.parameter_sniffing = 1 THEN ', Parameter Sniffing' ELSE '' END + - CASE WHEN b.long_running = 1 THEN ', Long Running Query' ELSE '' END + - CASE WHEN b.downlevel_estimator = 1 THEN ', Downlevel CE' ELSE '' END + - CASE WHEN b.implicit_conversions = 1 THEN ', Implicit Conversions' ELSE '' END + - CASE WHEN b.plan_multiple_plans = 1 THEN ', Multiple Plans' ELSE '' END + - CASE WHEN b.is_trivial = 1 THEN ', Trivial Plans' ELSE '' END + - CASE WHEN b.is_forced_serial = 1 THEN ', Forced Serialization' ELSE '' END + - CASE WHEN b.is_key_lookup_expensive = 1 THEN ', Expensive Key Lookup' ELSE '' END + - CASE WHEN b.is_remote_query_expensive = 1 THEN ', Expensive Remote Query' ELSE '' END + - CASE WHEN b.trace_flags_session IS NOT NULL THEN ', Session Level Trace Flag(s) Enabled: ' + b.trace_flags_session ELSE '' END + - CASE WHEN b.is_unused_grant = 1 THEN ', Unused Memory Grant' ELSE '' END + - CASE WHEN b.function_count > 0 THEN ', Calls ' + CONVERT(VARCHAR(10), b.function_count) + ' function(s)' ELSE '' END + - CASE WHEN b.clr_function_count > 0 THEN ', Calls ' + CONVERT(VARCHAR(10), b.clr_function_count) + ' CLR function(s)' ELSE '' END + - CASE WHEN b.is_table_variable = 1 THEN ', Table Variables' ELSE '' END + - CASE WHEN b.no_stats_warning = 1 THEN ', Columns With No Statistics' ELSE '' END + - CASE WHEN b.relop_warnings = 1 THEN ', Operator Warnings' ELSE '' END + - CASE WHEN b.is_table_scan = 1 THEN ', Table Scans' ELSE '' END + - CASE WHEN b.backwards_scan = 1 THEN ', Backwards Scans' ELSE '' END + - CASE WHEN b.forced_index = 1 THEN ', Forced Indexes' ELSE '' END + - CASE WHEN b.forced_seek = 1 THEN ', Forced Seeks' ELSE '' END + - CASE WHEN b.forced_scan = 1 THEN ', Forced Scans' ELSE '' END + - CASE WHEN b.columnstore_row_mode = 1 THEN ', ColumnStore Row Mode ' ELSE '' END + - CASE WHEN b.is_computed_scalar = 1 THEN ', Computed Column UDF ' ELSE '' END + - CASE WHEN b.is_sort_expensive = 1 THEN ', Expensive Sort' ELSE '' END + - CASE WHEN b.is_computed_filter = 1 THEN ', Filter UDF' ELSE '' END + - CASE WHEN b.index_ops >= 5 THEN ', >= 5 Indexes Modified' ELSE '' END + - CASE WHEN b.is_row_level = 1 THEN ', Row Level Security' ELSE '' END + - CASE WHEN b.is_spatial = 1 THEN ', Spatial Index' ELSE '' END + - CASE WHEN b.index_dml = 1 THEN ', Index DML' ELSE '' END + - CASE WHEN b.table_dml = 1 THEN ', Table DML' ELSE '' END + - CASE WHEN b.low_cost_high_cpu = 1 THEN ', Low Cost High CPU' ELSE '' END + - CASE WHEN b.long_running_low_cpu = 1 THEN + ', Long Running With Low CPU' ELSE '' END + - CASE WHEN b.stale_stats = 1 THEN + ', Statistics used have > 100k modifications in the last 7 days' ELSE '' END + - CASE WHEN b.is_adaptive = 1 THEN + ', Adaptive Joins' ELSE '' END + - CASE WHEN b.is_spool_expensive = 1 THEN + ', Expensive Index Spool' ELSE '' END + - CASE WHEN b.is_spool_more_rows = 1 THEN + ', Large Index Row Spool' ELSE '' END + - CASE WHEN b.is_bad_estimate = 1 THEN + ', Row estimate mismatch' ELSE '' END + - CASE WHEN b.is_big_log = 1 THEN + ', High log use' ELSE '' END + - CASE WHEN b.is_big_tempdb = 1 THEN ', High tempdb use' ELSE '' END + - CASE WHEN b.is_paul_white_electric = 1 THEN ', SWITCH!' ELSE '' END + - CASE WHEN b.is_row_goal = 1 THEN ', Row Goals' ELSE '' END + - CASE WHEN b.is_mstvf = 1 THEN ', MSTVFs' ELSE '' END + - CASE WHEN b.is_mm_join = 1 THEN ', Many to Many Merge' ELSE '' END + - CASE WHEN b.is_nonsargable = 1 THEN ', non-SARGables' ELSE '' END - , 2, 200000) -FROM #working_warnings b -OPTION (RECOMPILE); + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + /*Grab the full resource list*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); -END; -END TRY -BEGIN CATCH - RAISERROR (N'Failure generating warnings.', 0,1) WITH NOWAIT; + RAISERROR('Grab the full resource list %s', 0, 1, @d) WITH NOWAIT; - IF @sql_select IS NOT NULL - BEGIN - SET @msg = N'Last @sql_select: ' + @sql_select; - RAISERROR(@msg, 0, 1) WITH NOWAIT; - END; + SELECT + event_date = + DATEADD + ( + MINUTE, + DATEDIFF + ( + MINUTE, + GETUTCDATE(), + SYSDATETIME() + ), + dr.event_date + ), + dr.victim_id, + dr.resource_xml + INTO + #deadlock_resource + FROM + ( + SELECT + event_date = dd.deadlock_xml.value('(event/@timestamp)[1]', 'datetime2'), + victim_id = dd.deadlock_xml.value('(//deadlock/victim-list/victimProcess/@id)[1]', 'nvarchar(256)'), + resource_xml = ISNULL(ca.dp.query(N'.'), N'') + FROM #deadlock_data AS dd + CROSS APPLY dd.deadlock_xml.nodes('//deadlock/resource-list') AS ca(dp) + ) AS dr + OPTION(RECOMPILE); - SELECT @msg = @DatabaseName + N' database failed to process. ' + ERROR_MESSAGE(), @error_severity = ERROR_SEVERITY(), @error_state = ERROR_STATE(); - RAISERROR (@msg, @error_severity, @error_state) WITH NOWAIT; - - - WHILE @@TRANCOUNT > 0 - ROLLBACK; + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - RETURN; -END CATCH; + /*Parse object locks*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Parse object locks %s', 0, 1, @d) WITH NOWAIT; + SELECT DISTINCT + ca.event_date, + ca.database_id, + database_name = + ISNULL + ( + DB_NAME(ca.database_id), + N'UNKNOWN' + ), + object_name = + REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE( + REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE( + REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE( + ca.object_name COLLATE Latin1_General_BIN2, + NCHAR(31), N'?'), NCHAR(30), N'?'), NCHAR(29), N'?'), NCHAR(28), N'?'), NCHAR(27), N'?'), + NCHAR(26), N'?'), NCHAR(25), N'?'), NCHAR(24), N'?'), NCHAR(23), N'?'), NCHAR(22), N'?'), + NCHAR(21), N'?'), NCHAR(20), N'?'), NCHAR(19), N'?'), NCHAR(18), N'?'), NCHAR(17), N'?'), + NCHAR(16), N'?'), NCHAR(15), N'?'), NCHAR(14), N'?'), NCHAR(12), N'?'), NCHAR(11), N'?'), + NCHAR(8), N'?'), NCHAR(7), N'?'), NCHAR(6), N'?'), NCHAR(5), N'?'), NCHAR(4), N'?'), + NCHAR(3), N'?'), NCHAR(2), N'?'), NCHAR(1), N'?'), NCHAR(0), N'?'), + ca.lock_mode, + ca.index_name, + ca.associatedObjectId, + waiter_id = w.l.value('@id', 'nvarchar(256)'), + waiter_mode = w.l.value('@mode', 'nvarchar(256)'), + owner_id = o.l.value('@id', 'nvarchar(256)'), + owner_mode = o.l.value('@mode', 'nvarchar(256)'), + lock_type = CAST(N'OBJECT' AS nvarchar(100)) + INTO #deadlock_owner_waiter + FROM + ( + SELECT + dr.event_date, + database_id = ca.dr.value('@dbid', 'bigint'), + object_name = ca.dr.value('@objectname', 'nvarchar(1024)'), + lock_mode = ca.dr.value('@mode', 'nvarchar(256)'), + index_name = ca.dr.value('@indexname', 'nvarchar(256)'), + associatedObjectId = ca.dr.value('@associatedObjectId', 'bigint'), + dr = ca.dr.query('.') + FROM #deadlock_resource AS dr + CROSS APPLY dr.resource_xml.nodes('//resource-list/objectlock') AS ca(dr) + WHERE (ca.dr.exist('@objectname[. = sql:variable("@ObjectName")]') = 1 OR @ObjectName IS NULL) + ) AS ca + CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l) + CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l) + OPTION(RECOMPILE); -BEGIN TRY -BEGIN + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; -RAISERROR(N'Checking for parameter sniffing symptoms', 0, 1) WITH NOWAIT; + /*Parse page locks*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Parse page locks %s', 0, 1, @d) WITH NOWAIT; -UPDATE b -SET b.parameter_sniffing_symptoms = -CASE WHEN b.count_executions < 2 THEN 'Too few executions to compare (< 2).' - ELSE - SUBSTRING( - /*Duration*/ - CASE WHEN (b.min_duration * 100) < (b.avg_duration) THEN ', Fast sometimes' ELSE '' END + - CASE WHEN (b.max_duration) > (b.avg_duration * 100) THEN ', Slow sometimes' ELSE '' END + - CASE WHEN (b.last_duration * 100) < (b.avg_duration) THEN ', Fast last run' ELSE '' END + - CASE WHEN (b.last_duration) > (b.avg_duration * 100) THEN ', Slow last run' ELSE '' END + - /*CPU*/ - CASE WHEN (b.min_cpu_time / b.avg_dop) * 100 < (b.avg_cpu_time / b.avg_dop) THEN ', Low CPU sometimes' ELSE '' END + - CASE WHEN (b.max_cpu_time / b.max_dop) > (b.avg_cpu_time / b.avg_dop) * 100 THEN ', High CPU sometimes' ELSE '' END + - CASE WHEN (b.last_cpu_time / b.last_dop) * 100 < (b.avg_cpu_time / b.avg_dop) THEN ', Low CPU last run' ELSE '' END + - CASE WHEN (b.last_cpu_time / b.last_dop) > (b.avg_cpu_time / b.avg_dop) * 100 THEN ', High CPU last run' ELSE '' END + - /*Logical Reads*/ - CASE WHEN (b.min_logical_io_reads * 100) < (b.avg_logical_io_reads) THEN ', Low reads sometimes' ELSE '' END + - CASE WHEN (b.max_logical_io_reads) > (b.avg_logical_io_reads * 100) THEN ', High reads sometimes' ELSE '' END + - CASE WHEN (b.last_logical_io_reads * 100) < (b.avg_logical_io_reads) THEN ', Low reads last run' ELSE '' END + - CASE WHEN (b.last_logical_io_reads) > (b.avg_logical_io_reads * 100) THEN ', High reads last run' ELSE '' END + - /*Logical Writes*/ - CASE WHEN (b.min_logical_io_writes * 100) < (b.avg_logical_io_writes) THEN ', Low writes sometimes' ELSE '' END + - CASE WHEN (b.max_logical_io_writes) > (b.avg_logical_io_writes * 100) THEN ', High writes sometimes' ELSE '' END + - CASE WHEN (b.last_logical_io_writes * 100) < (b.avg_logical_io_writes) THEN ', Low writes last run' ELSE '' END + - CASE WHEN (b.last_logical_io_writes) > (b.avg_logical_io_writes * 100) THEN ', High writes last run' ELSE '' END + - /*Physical Reads*/ - CASE WHEN (b.min_physical_io_reads * 100) < (b.avg_physical_io_reads) THEN ', Low physical reads sometimes' ELSE '' END + - CASE WHEN (b.max_physical_io_reads) > (b.avg_physical_io_reads * 100) THEN ', High physical reads sometimes' ELSE '' END + - CASE WHEN (b.last_physical_io_reads * 100) < (b.avg_physical_io_reads) THEN ', Low physical reads last run' ELSE '' END + - CASE WHEN (b.last_physical_io_reads) > (b.avg_physical_io_reads * 100) THEN ', High physical reads last run' ELSE '' END + - /*Memory*/ - CASE WHEN (b.min_query_max_used_memory * 100) < (b.avg_query_max_used_memory) THEN ', Low memory sometimes' ELSE '' END + - CASE WHEN (b.max_query_max_used_memory) > (b.avg_query_max_used_memory * 100) THEN ', High memory sometimes' ELSE '' END + - CASE WHEN (b.last_query_max_used_memory * 100) < (b.avg_query_max_used_memory) THEN ', Low memory last run' ELSE '' END + - CASE WHEN (b.last_query_max_used_memory) > (b.avg_query_max_used_memory * 100) THEN ', High memory last run' ELSE '' END + - /*Duration*/ - CASE WHEN b.min_rowcount * 100 < b.avg_rowcount THEN ', Low row count sometimes' ELSE '' END + - CASE WHEN b.max_rowcount > b.avg_rowcount * 100 THEN ', High row count sometimes' ELSE '' END + - CASE WHEN b.last_rowcount * 100 < b.avg_rowcount THEN ', Low row count run' ELSE '' END + - CASE WHEN b.last_rowcount > b.avg_rowcount * 100 THEN ', High row count last run' ELSE '' END + - /*DOP*/ - CASE WHEN b.min_dop <> b.max_dop THEN ', Serial sometimes' ELSE '' END + - CASE WHEN b.min_dop <> b.max_dop AND b.last_dop = 1 THEN ', Serial last run' ELSE '' END + - CASE WHEN b.min_dop <> b.max_dop AND b.last_dop > 1 THEN ', Parallel last run' ELSE '' END + - /*tempdb*/ - CASE WHEN b.min_tempdb_space_used * 100 < b.avg_tempdb_space_used THEN ', Low tempdb sometimes' ELSE '' END + - CASE WHEN b.max_tempdb_space_used > b.avg_tempdb_space_used * 100 THEN ', High tempdb sometimes' ELSE '' END + - CASE WHEN b.last_tempdb_space_used * 100 < b.avg_tempdb_space_used THEN ', Low tempdb run' ELSE '' END + - CASE WHEN b.last_tempdb_space_used > b.avg_tempdb_space_used * 100 THEN ', High tempdb last run' ELSE '' END + - /*tlog*/ - CASE WHEN b.min_log_bytes_used * 100 < b.avg_log_bytes_used THEN ', Low log use sometimes' ELSE '' END + - CASE WHEN b.max_log_bytes_used > b.avg_log_bytes_used * 100 THEN ', High log use sometimes' ELSE '' END + - CASE WHEN b.last_log_bytes_used * 100 < b.avg_log_bytes_used THEN ', Low log use run' ELSE '' END + - CASE WHEN b.last_log_bytes_used > b.avg_log_bytes_used * 100 THEN ', High log use last run' ELSE '' END - , 2, 200000) - END -FROM #working_metrics AS b -OPTION (RECOMPILE); + INSERT + #deadlock_owner_waiter WITH(TABLOCKX) + SELECT DISTINCT + ca.event_date, + ca.database_id, + database_name = + ISNULL + ( + DB_NAME(ca.database_id), + N'UNKNOWN' + ), + ca.object_name, + ca.lock_mode, + ca.index_name, + ca.associatedObjectId, + waiter_id = w.l.value('@id', 'nvarchar(256)'), + waiter_mode = w.l.value('@mode', 'nvarchar(256)'), + owner_id = o.l.value('@id', 'nvarchar(256)'), + owner_mode = o.l.value('@mode', 'nvarchar(256)'), + lock_type = N'PAGE' + FROM + ( + SELECT + dr.event_date, + database_id = ca.dr.value('@dbid', 'bigint'), + object_name = ca.dr.value('@objectname', 'nvarchar(1024)'), + lock_mode = ca.dr.value('@mode', 'nvarchar(256)'), + index_name = ca.dr.value('@indexname', 'nvarchar(256)'), + associatedObjectId = ca.dr.value('@associatedObjectId', 'bigint'), + dr = ca.dr.query('.') + FROM #deadlock_resource AS dr + CROSS APPLY dr.resource_xml.nodes('//resource-list/pagelock') AS ca(dr) + WHERE (ca.dr.exist('@objectname[. = sql:variable("@ObjectName")]') = 1 OR @ObjectName IS NULL) + ) AS ca + CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l) + CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l) + OPTION(RECOMPILE); -END; -END TRY -BEGIN CATCH - RAISERROR (N'Failure analyzing parameter sniffing', 0,1) WITH NOWAIT; + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - IF @sql_select IS NOT NULL - BEGIN - SET @msg = N'Last @sql_select: ' + @sql_select; - RAISERROR(@msg, 0, 1) WITH NOWAIT; - END; + /*Parse key locks*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Parse key locks %s', 0, 1, @d) WITH NOWAIT; - SELECT @msg = @DatabaseName + N' database failed to process. ' + ERROR_MESSAGE(), @error_severity = ERROR_SEVERITY(), @error_state = ERROR_STATE(); - RAISERROR (@msg, @error_severity, @error_state) WITH NOWAIT; - - - WHILE @@TRANCOUNT > 0 - ROLLBACK; + INSERT + #deadlock_owner_waiter WITH(TABLOCKX) + SELECT DISTINCT + ca.event_date, + ca.database_id, + database_name = + ISNULL + ( + DB_NAME(ca.database_id), + N'UNKNOWN' + ), + ca.object_name, + ca.lock_mode, + ca.index_name, + ca.associatedObjectId, + waiter_id = w.l.value('@id', 'nvarchar(256)'), + waiter_mode = w.l.value('@mode', 'nvarchar(256)'), + owner_id = o.l.value('@id', 'nvarchar(256)'), + owner_mode = o.l.value('@mode', 'nvarchar(256)'), + lock_type = N'KEY' + FROM + ( + SELECT + dr.event_date, + database_id = ca.dr.value('@dbid', 'bigint'), + object_name = ca.dr.value('@objectname', 'nvarchar(1024)'), + lock_mode = ca.dr.value('@mode', 'nvarchar(256)'), + index_name = ca.dr.value('@indexname', 'nvarchar(256)'), + associatedObjectId = ca.dr.value('@associatedObjectId', 'bigint'), + dr = ca.dr.query('.') + FROM #deadlock_resource AS dr + CROSS APPLY dr.resource_xml.nodes('//resource-list/keylock') AS ca(dr) + WHERE (ca.dr.exist('@objectname[. = sql:variable("@ObjectName")]') = 1 OR @ObjectName IS NULL) + ) AS ca + CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l) + CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l) + OPTION(RECOMPILE); - RETURN; -END CATCH; + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; -BEGIN TRY + /*Parse RID locks*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Parse RID locks %s', 0, 1, @d) WITH NOWAIT; -BEGIN + INSERT + #deadlock_owner_waiter WITH(TABLOCKX) + SELECT DISTINCT + ca.event_date, + ca.database_id, + database_name = + ISNULL + ( + DB_NAME(ca.database_id), + N'UNKNOWN' + ), + ca.object_name, + ca.lock_mode, + ca.index_name, + ca.associatedObjectId, + waiter_id = w.l.value('@id', 'nvarchar(256)'), + waiter_mode = w.l.value('@mode', 'nvarchar(256)'), + owner_id = o.l.value('@id', 'nvarchar(256)'), + owner_mode = o.l.value('@mode', 'nvarchar(256)'), + lock_type = N'RID' + FROM + ( + SELECT + dr.event_date, + database_id = ca.dr.value('@dbid', 'bigint'), + object_name = ca.dr.value('@objectname', 'nvarchar(1024)'), + lock_mode = ca.dr.value('@mode', 'nvarchar(256)'), + index_name = ca.dr.value('@indexname', 'nvarchar(256)'), + associatedObjectId = ca.dr.value('@associatedObjectId', 'bigint'), + dr = ca.dr.query('.') + FROM #deadlock_resource AS dr + CROSS APPLY dr.resource_xml.nodes('//resource-list/ridlock') AS ca(dr) + WHERE (ca.dr.exist('@objectname[. = sql:variable("@ObjectName")]') = 1 OR @ObjectName IS NULL) + ) AS ca + CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l) + CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l) + OPTION(RECOMPILE); -IF (@Failed = 0 AND @ExportToExcel = 0 AND @SkipXML = 0) -BEGIN + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; -RAISERROR(N'Returning regular results', 0, 1) WITH NOWAIT; + /*Parse row group locks*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Parse row group locks %s', 0, 1, @d) WITH NOWAIT; -WITH x AS ( -SELECT wpt.database_name, ww.query_cost, wm.plan_id, wm.query_id, wm.query_id_all_plan_ids, wpt.query_sql_text, wm.proc_or_function_name, wpt.query_plan_xml, ww.warnings, wpt.pattern, - wm.parameter_sniffing_symptoms, wpt.top_three_waits, ww.missing_indexes, ww.implicit_conversion_info, ww.cached_execution_parameters, wm.count_executions, wm.count_compiles, wm.total_cpu_time, wm.avg_cpu_time, - wm.total_duration, wm.avg_duration, wm.total_logical_io_reads, wm.avg_logical_io_reads, - wm.total_physical_io_reads, wm.avg_physical_io_reads, wm.total_logical_io_writes, wm.avg_logical_io_writes, wm.total_rowcount, wm.avg_rowcount, - wm.total_query_max_used_memory, wm.avg_query_max_used_memory, wm.total_tempdb_space_used, wm.avg_tempdb_space_used, - wm.total_log_bytes_used, wm.avg_log_bytes_used, wm.total_num_physical_io_reads, wm.avg_num_physical_io_reads, - wm.first_execution_time, wm.last_execution_time, wpt.last_force_failure_reason_desc, wpt.context_settings, ROW_NUMBER() OVER (PARTITION BY wm.plan_id, wm.query_id, wm.last_execution_time ORDER BY wm.plan_id) AS rn -FROM #working_plan_text AS wpt -JOIN #working_warnings AS ww - ON wpt.plan_id = ww.plan_id - AND wpt.query_id = ww.query_id -JOIN #working_metrics AS wm - ON wpt.plan_id = wm.plan_id - AND wpt.query_id = wm.query_id -) -SELECT * -FROM x -WHERE x.rn = 1 -ORDER BY x.last_execution_time -OPTION (RECOMPILE); + INSERT + #deadlock_owner_waiter WITH(TABLOCKX) + SELECT DISTINCT + ca.event_date, + ca.database_id, + database_name = + ISNULL + ( + DB_NAME(ca.database_id), + N'UNKNOWN' + ), + ca.object_name, + ca.lock_mode, + ca.index_name, + ca.associatedObjectId, + waiter_id = w.l.value('@id', 'nvarchar(256)'), + waiter_mode = w.l.value('@mode', 'nvarchar(256)'), + owner_id = o.l.value('@id', 'nvarchar(256)'), + owner_mode = o.l.value('@mode', 'nvarchar(256)'), + lock_type = N'ROWGROUP' + FROM + ( + SELECT + dr.event_date, + database_id = ca.dr.value('@dbid', 'bigint'), + object_name = ca.dr.value('@objectname', 'nvarchar(1024)'), + lock_mode = ca.dr.value('@mode', 'nvarchar(256)'), + index_name = ca.dr.value('@indexname', 'nvarchar(256)'), + associatedObjectId = ca.dr.value('@associatedObjectId', 'bigint'), + dr = ca.dr.query('.') + FROM #deadlock_resource AS dr + CROSS APPLY dr.resource_xml.nodes('//resource-list/rowgrouplock') AS ca(dr) + WHERE (ca.dr.exist('@objectname[. = sql:variable("@ObjectName")]') = 1 OR @ObjectName IS NULL) + ) AS ca + CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l) + CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l) + OPTION(RECOMPILE); -END; + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; -IF (@Failed = 1 AND @ExportToExcel = 0 AND @SkipXML = 0) -BEGIN + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Fixing heaps in #deadlock_owner_waiter %s', 0, 1, @d) WITH NOWAIT; -RAISERROR(N'Returning results for failed queries', 0, 1) WITH NOWAIT; + UPDATE + d + SET + d.index_name = + d.object_name + N'.HEAP' + FROM #deadlock_owner_waiter AS d + WHERE d.lock_type IN + ( + N'HEAP', + N'RID' + ) + OPTION(RECOMPILE); -WITH x AS ( -SELECT wpt.database_name, ww.query_cost, wm.plan_id, wm.query_id, wm.query_id_all_plan_ids, wpt.query_sql_text, wm.proc_or_function_name, wpt.query_plan_xml, ww.warnings, wpt.pattern, - wm.parameter_sniffing_symptoms, wpt.last_force_failure_reason_desc, wpt.top_three_waits, ww.missing_indexes, ww.implicit_conversion_info, ww.cached_execution_parameters, - wm.count_executions, wm.count_compiles, wm.total_cpu_time, wm.avg_cpu_time, - wm.total_duration, wm.avg_duration, wm.total_logical_io_reads, wm.avg_logical_io_reads, - wm.total_physical_io_reads, wm.avg_physical_io_reads, wm.total_logical_io_writes, wm.avg_logical_io_writes, wm.total_rowcount, wm.avg_rowcount, - wm.total_query_max_used_memory, wm.avg_query_max_used_memory, wm.total_tempdb_space_used, wm.avg_tempdb_space_used, - wm.total_log_bytes_used, wm.avg_log_bytes_used, wm.total_num_physical_io_reads, wm.avg_num_physical_io_reads, - wm.first_execution_time, wm.last_execution_time, wpt.context_settings, ROW_NUMBER() OVER (PARTITION BY wm.plan_id, wm.query_id, wm.last_execution_time ORDER BY wm.plan_id) AS rn -FROM #working_plan_text AS wpt -JOIN #working_warnings AS ww - ON wpt.plan_id = ww.plan_id - AND wpt.query_id = ww.query_id -JOIN #working_metrics AS wm - ON wpt.plan_id = wm.plan_id - AND wpt.query_id = wm.query_id -) -SELECT * -FROM x -WHERE x.rn = 1 -ORDER BY x.last_execution_time -OPTION (RECOMPILE); + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; -END; + /*Parse parallel deadlocks*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Parse parallel deadlocks %s', 0, 1, @d) WITH NOWAIT; -IF (@ExportToExcel = 1 AND @SkipXML = 0) -BEGIN + SELECT DISTINCT + ca.id, + ca.event_date, + ca.wait_type, + ca.node_id, + ca.waiter_type, + ca.owner_activity, + ca.waiter_activity, + ca.merging, + ca.spilling, + ca.waiting_to_close, + waiter_id = w.l.value('@id', 'nvarchar(256)'), + owner_id = o.l.value('@id', 'nvarchar(256)') + INTO #deadlock_resource_parallel + FROM + ( + SELECT + dr.event_date, + id = ca.dr.value('@id', 'nvarchar(256)'), + wait_type = ca.dr.value('@WaitType', 'nvarchar(256)'), + node_id = ca.dr.value('@nodeId', 'bigint'), + /* These columns are in 2017 CU5+ ONLY */ + waiter_type = ca.dr.value('@waiterType', 'nvarchar(256)'), + owner_activity = ca.dr.value('@ownerActivity', 'nvarchar(256)'), + waiter_activity = ca.dr.value('@waiterActivity', 'nvarchar(256)'), + merging = ca.dr.value('@merging', 'nvarchar(256)'), + spilling = ca.dr.value('@spilling', 'nvarchar(256)'), + waiting_to_close = ca.dr.value('@waitingToClose', 'nvarchar(256)'), + /* These columns are in 2017 CU5+ ONLY */ + dr = ca.dr.query('.') + FROM #deadlock_resource AS dr + CROSS APPLY dr.resource_xml.nodes('//resource-list/exchangeEvent') AS ca(dr) + ) AS ca + CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l) + CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l) + OPTION(RECOMPILE); -RAISERROR(N'Returning results for Excel export', 0, 1) WITH NOWAIT; + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; -UPDATE #working_plan_text -SET query_sql_text = SUBSTRING(REPLACE(REPLACE(REPLACE(LTRIM(RTRIM(query_sql_text)),' ','<>'),'><',''),'<>',' '), 1, 31000) -OPTION (RECOMPILE); + /*Get rid of parallel noise*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Get rid of parallel noise %s', 0, 1, @d) WITH NOWAIT; -WITH x AS ( -SELECT wpt.database_name, ww.query_cost, wm.plan_id, wm.query_id, wm.query_id_all_plan_ids, wpt.query_sql_text, wm.proc_or_function_name, ww.warnings, wpt.pattern, - wm.parameter_sniffing_symptoms, wpt.last_force_failure_reason_desc, wpt.top_three_waits, wm.count_executions, wm.count_compiles, wm.total_cpu_time, wm.avg_cpu_time, - wm.total_duration, wm.avg_duration, wm.total_logical_io_reads, wm.avg_logical_io_reads, - wm.total_physical_io_reads, wm.avg_physical_io_reads, wm.total_logical_io_writes, wm.avg_logical_io_writes, wm.total_rowcount, wm.avg_rowcount, - wm.total_query_max_used_memory, wm.avg_query_max_used_memory, wm.total_tempdb_space_used, wm.avg_tempdb_space_used, - wm.total_log_bytes_used, wm.avg_log_bytes_used, wm.total_num_physical_io_reads, wm.avg_num_physical_io_reads, - wm.first_execution_time, wm.last_execution_time, wpt.context_settings, ROW_NUMBER() OVER (PARTITION BY wm.plan_id, wm.query_id, wm.last_execution_time ORDER BY wm.plan_id) AS rn -FROM #working_plan_text AS wpt -JOIN #working_warnings AS ww - ON wpt.plan_id = ww.plan_id - AND wpt.query_id = ww.query_id -JOIN #working_metrics AS wm - ON wpt.plan_id = wm.plan_id - AND wpt.query_id = wm.query_id -) -SELECT * -FROM x -WHERE x.rn = 1 -ORDER BY x.last_execution_time -OPTION (RECOMPILE); + WITH + c AS + ( + SELECT + *, + rn = + ROW_NUMBER() OVER + ( + PARTITION BY + drp.owner_id, + drp.waiter_id + ORDER BY + drp.event_date + ) + FROM #deadlock_resource_parallel AS drp + ) + DELETE + FROM c + WHERE c.rn > 1 + OPTION(RECOMPILE); -END; + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; -IF (@ExportToExcel = 0 AND @SkipXML = 1) -BEGIN + /*Get rid of nonsense*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Get rid of nonsense %s', 0, 1, @d) WITH NOWAIT; -RAISERROR(N'Returning results for skipped XML', 0, 1) WITH NOWAIT; + DELETE dow + FROM #deadlock_owner_waiter AS dow + WHERE dow.owner_id = dow.waiter_id + OPTION(RECOMPILE); -WITH x AS ( -SELECT wpt.database_name, wm.plan_id, wm.query_id, wm.query_id_all_plan_ids, wpt.query_sql_text, wpt.query_plan_xml, wpt.pattern, - wm.parameter_sniffing_symptoms, wpt.top_three_waits, wm.count_executions, wm.count_compiles, wm.total_cpu_time, wm.avg_cpu_time, - wm.total_duration, wm.avg_duration, wm.total_logical_io_reads, wm.avg_logical_io_reads, - wm.total_physical_io_reads, wm.avg_physical_io_reads, wm.total_logical_io_writes, wm.avg_logical_io_writes, wm.total_rowcount, wm.avg_rowcount, - wm.total_query_max_used_memory, wm.avg_query_max_used_memory, wm.total_tempdb_space_used, wm.avg_tempdb_space_used, - wm.total_log_bytes_used, wm.avg_log_bytes_used, wm.total_num_physical_io_reads, wm.avg_num_physical_io_reads, - wm.first_execution_time, wm.last_execution_time, wpt.last_force_failure_reason_desc, wpt.context_settings, ROW_NUMBER() OVER (PARTITION BY wm.plan_id, wm.query_id, wm.last_execution_time ORDER BY wm.plan_id) AS rn -FROM #working_plan_text AS wpt -JOIN #working_metrics AS wm - ON wpt.plan_id = wm.plan_id - AND wpt.query_id = wm.query_id -) -SELECT * -FROM x -WHERE x.rn = 1 -ORDER BY x.last_execution_time -OPTION (RECOMPILE); + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; -END; + /*Add some nonsense*/ + ALTER TABLE + #deadlock_process + ADD + waiter_mode nvarchar(256), + owner_mode nvarchar(256), + is_victim AS + CONVERT + ( + bit, + CASE + WHEN id = victim_id + THEN 1 + ELSE 0 + END + ) PERSISTED; -END; -END TRY -BEGIN CATCH - RAISERROR (N'Failure returning results', 0,1) WITH NOWAIT; + /*Update some nonsense*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Update some nonsense part 1 %s', 0, 1, @d) WITH NOWAIT; - IF @sql_select IS NOT NULL - BEGIN - SET @msg = N'Last @sql_select: ' + @sql_select; - RAISERROR(@msg, 0, 1) WITH NOWAIT; - END; + UPDATE + dp + SET + dp.owner_mode = dow.owner_mode + FROM #deadlock_process AS dp + JOIN #deadlock_owner_waiter AS dow + ON dp.id = dow.owner_id + AND dp.event_date = dow.event_date + WHERE dp.is_victim = 0 + OPTION(RECOMPILE); - SELECT @msg = @DatabaseName + N' database failed to process. ' + ERROR_MESSAGE(), @error_severity = ERROR_SEVERITY(), @error_state = ERROR_STATE(); - RAISERROR (@msg, @error_severity, @error_state) WITH NOWAIT; - - - WHILE @@TRANCOUNT > 0 - ROLLBACK; + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - RETURN; -END CATCH; + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Update some nonsense part 2 %s', 0, 1, @d) WITH NOWAIT; -BEGIN TRY -BEGIN + UPDATE + dp + SET + dp.waiter_mode = dow.waiter_mode + FROM #deadlock_process AS dp + JOIN #deadlock_owner_waiter AS dow + ON dp.victim_id = dow.waiter_id + AND dp.event_date = dow.event_date + WHERE dp.is_victim = 1 + OPTION(RECOMPILE); -IF (@ExportToExcel = 0 AND @HideSummary = 0 AND @SkipXML = 0) -BEGIN - RAISERROR('Building query plan summary data.', 0, 1) WITH NOWAIT; + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - /* Build summary data */ - IF EXISTS (SELECT 1/0 - FROM #working_warnings - WHERE frequent_execution = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 1, - 100, - 'Execution Pattern', - 'Frequently Executed Queries', - 'https://www.brentozar.com/blitzcache/frequently-executed-queries/', - 'Queries are being executed more than ' - + CAST (@execution_threshold AS VARCHAR(5)) - + ' times per minute. This can put additional load on the server, even when queries are lightweight.') ; + /*Get Agent Job and Step names*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Get Agent Job and Step names %s', 0, 1, @d) WITH NOWAIT; - IF EXISTS (SELECT 1/0 - FROM #working_warnings - WHERE parameter_sniffing = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 2, - 50, - 'Parameterization', - 'Parameter Sniffing', - 'https://www.brentozar.com/blitzcache/parameter-sniffing/', - 'There are signs of parameter sniffing (wide variance in rows return or time to execute). Investigate query patterns and tune code appropriately.') ; + SELECT + x.event_date, + x.victim_id, + x.id, + x.database_id, + x.client_app, + x.job_id, + x.step_id, + job_id_guid = + CONVERT + ( + uniqueidentifier, + TRY_CAST + ( + N'' + AS xml + ).value('xs:hexBinary(substring(sql:column("x.job_id"), 0))', 'binary(16)') + ) + INTO #agent_job + FROM + ( + SELECT + dp.event_date, + dp.victim_id, + dp.id, + dp.database_id, + dp.client_app, + job_id = + SUBSTRING + ( + dp.client_app, + CHARINDEX(N'0x', dp.client_app) + LEN(N'0x'), + 32 + ), + step_id = + CASE + WHEN CHARINDEX(N': Step ', dp.client_app) > 0 + AND CHARINDEX(N')', dp.client_app, CHARINDEX(N': Step ', dp.client_app)) > 0 + THEN + SUBSTRING + ( + dp.client_app, + CHARINDEX(N': Step ', dp.client_app) + LEN(N': Step '), + CHARINDEX(N')', dp.client_app, CHARINDEX(N': Step ', dp.client_app)) - + (CHARINDEX(N': Step ', dp.client_app) + LEN(N': Step ')) + ) + ELSE dp.client_app + END + FROM #deadlock_process AS dp + WHERE dp.client_app LIKE N'SQLAgent - %' + AND dp.client_app <> N'SQLAgent - Initial Boot Probe' + ) AS x + OPTION(RECOMPILE); - /* Forced execution plans */ - IF EXISTS (SELECT 1/0 - FROM #working_warnings - WHERE is_forced_plan = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 3, - 5, - 'Parameterization', - 'Forced Plans', - 'https://www.brentozar.com/blitzcache/forced-plans/', - 'Execution plans have been compiled with forced plans, either through FORCEPLAN, plan guides, or forced parameterization. This will make general tuning efforts less effective.'); + ALTER TABLE + #agent_job + ADD + job_name nvarchar(256), + step_name nvarchar(256); - IF EXISTS (SELECT 1/0 - FROM #working_warnings - WHERE is_cursor = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 4, - 200, - 'Cursors', - 'Cursors', - 'https://www.brentozar.com/blitzcache/cursors-found-slow-queries/', - 'There are cursors in the plan cache. This is neither good nor bad, but it is a thing. Cursors are weird in SQL Server.'); + IF + ( + @Azure = 0 + AND @RDS = 0 + ) + BEGIN + SET @StringToExecute = + N' + UPDATE + aj + SET + aj.job_name = j.name, + aj.step_name = s.step_name + FROM msdb.dbo.sysjobs AS j + JOIN msdb.dbo.sysjobsteps AS s + ON j.job_id = s.job_id + JOIN #agent_job AS aj + ON aj.job_id_guid = j.job_id + AND aj.step_id = s.step_id + OPTION(RECOMPILE); + '; - IF EXISTS (SELECT 1/0 - FROM #working_warnings - WHERE is_cursor = 1 - AND is_optimistic_cursor = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 4, - 200, - 'Cursors', - 'Optimistic Cursors', - 'https://www.brentozar.com/blitzcache/cursors-found-slow-queries/', - 'There are optimistic cursors in the plan cache, which can harm performance.'); + IF @Debug = 1 BEGIN PRINT @StringToExecute; END; + EXEC sys.sp_executesql + @StringToExecute; - IF EXISTS (SELECT 1/0 - FROM #working_warnings - WHERE is_cursor = 1 - AND is_forward_only_cursor = 0 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 4, - 200, - 'Cursors', - 'Non-forward Only Cursors', - 'https://www.brentozar.com/blitzcache/cursors-found-slow-queries/', - 'There are non-forward only cursors in the plan cache, which can harm performance.'); + END; - IF EXISTS (SELECT 1/0 - FROM #working_warnings - WHERE is_cursor = 1 - AND is_cursor_dynamic = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (4, - 200, - 'Cursors', - 'Dynamic Cursors', - 'https://www.brentozar.com/blitzcache/cursors-found-slow-queries/', - 'Dynamic Cursors inhibit parallelism!.'); + UPDATE + dp + SET + dp.client_app = + CASE + WHEN dp.client_app LIKE N'SQLAgent - %' + THEN N'SQLAgent - Job: ' + + aj.job_name + + N' Step: ' + + aj.step_name + ELSE dp.client_app + END + FROM #deadlock_process AS dp + JOIN #agent_job AS aj + ON dp.event_date = aj.event_date + AND dp.victim_id = aj.victim_id + AND dp.id = aj.id + OPTION(RECOMPILE); - IF EXISTS (SELECT 1/0 - FROM #working_warnings - WHERE is_cursor = 1 - AND is_fast_forward_cursor = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (4, - 200, - 'Cursors', - 'Fast Forward Cursors', - 'https://www.brentozar.com/blitzcache/cursors-found-slow-queries/', - 'Fast forward cursors inhibit parallelism!.'); - - IF EXISTS (SELECT 1/0 - FROM #working_warnings - WHERE is_forced_parameterized = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 5, - 50, - 'Parameterization', - 'Forced Parameterization', - 'https://www.brentozar.com/blitzcache/forced-parameterization/', - 'Execution plans have been compiled with forced parameterization.') ; + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.is_parallel = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 6, - 200, - 'Execution Plans', - 'Parallelism', - 'https://www.brentozar.com/blitzcache/parallel-plans-detected/', - 'Parallel plans detected. These warrant investigation, but are neither good nor bad.') ; + /*Get each and every table of all databases*/ + IF @Azure = 0 + BEGIN + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Inserting to @sysAssObjId %s', 0, 1, @d) WITH NOWAIT; - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.near_parallel = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 7, - 200, - 'Execution Plans', - 'Nearly Parallel', - 'https://www.brentozar.com/blitzcache/query-cost-near-cost-threshold-parallelism/', - 'Queries near the cost threshold for parallelism. These may go parallel when you least expect it.') ; + INSERT INTO + @sysAssObjId + ( + database_id, + partition_id, + schema_name, + table_name + ) + EXECUTE sys.sp_MSforeachdb + N' + SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.plan_warnings = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 8, - 50, - 'Execution Plans', - 'Query Plan Warnings', - 'https://www.brentozar.com/blitzcache/query-plan-warnings/', - 'Warnings detected in execution plans. SQL Server is telling you that something bad is going on that requires your attention.') ; + USE [?]; - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.long_running = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 9, - 50, - 'Performance', - 'Long Running Queries', - 'https://www.brentozar.com/blitzcache/long-running-queries/', - 'Long running queries have been found. These are queries with an average duration longer than ' - + CAST(@long_running_query_warning_seconds / 1000 / 1000 AS VARCHAR(5)) - + ' second(s). These queries should be investigated for additional tuning options.') ; + IF EXISTS + ( + SELECT + 1/0 + FROM #deadlock_process AS dp + WHERE dp.database_id = DB_ID() + ) + BEGIN + SELECT + database_id = + DB_ID(), + p.partition_id, + schema_name = + s.name, + table_name = + t.name + FROM sys.partitions p + JOIN sys.tables t + ON t.object_id = p.object_id + JOIN sys.schemas s + ON s.schema_id = t.schema_id + WHERE s.name IS NOT NULL + AND t.name IS NOT NULL + OPTION(RECOMPILE); + END; + '; - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.missing_index_count > 0 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 10, - 50, - 'Performance', - 'Missing Index Request', - 'https://www.brentozar.com/blitzcache/missing-index-request/', - 'Queries found with missing indexes.'); + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + END; - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.downlevel_estimator = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 13, - 200, - 'Cardinality', - 'Legacy Cardinality Estimator in Use', - 'https://www.brentozar.com/blitzcache/legacy-cardinality-estimator/', - 'A legacy cardinality estimator is being used by one or more queries. Investigate whether you need to be using this cardinality estimator. This may be caused by compatibility levels, global trace flags, or query level trace flags.'); + IF @Azure = 1 + BEGIN + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Inserting to @sysAssObjId at %s', 0, 1, @d) WITH NOWAIT; - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.implicit_conversions = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 14, - 50, - 'Performance', - 'Implicit Conversions', - 'https://www.brentozar.com/go/implicit', - 'One or more queries are comparing two fields that are not of the same data type.') ; + INSERT INTO + @sysAssObjId + ( + database_id, + partition_id, + schema_name, + table_name + ) + SELECT + database_id = + DB_ID(), + p.partition_id, + schema_name = + s.name, + table_name = + t.name + FROM sys.partitions p + JOIN sys.tables t + ON t.object_id = p.object_id + JOIN sys.schemas s + ON s.schema_id = t.schema_id + WHERE s.name IS NOT NULL + AND t.name IS NOT NULL + OPTION(RECOMPILE); - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE busy_loops = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 16, - 100, - 'Performance', - 'Busy Loops', - 'https://www.brentozar.com/blitzcache/busy-loops/', - 'Operations have been found that are executed 100 times more often than the number of rows returned by each iteration. This is an indicator that something is off in query execution.'); + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + END; - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE tvf_join = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 17, - 50, - 'Performance', - 'Joining to table valued functions', - 'https://www.brentozar.com/blitzcache/tvf-join/', - 'Execution plans have been found that join to table valued functions (TVFs). TVFs produce inaccurate estimates of the number of rows returned and can lead to any number of query plan problems.'); + /*Begin checks based on parsed values*/ - IF EXISTS (SELECT 1/0 - FROM #working_warnings - WHERE compile_timeout = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 18, - 50, - 'Execution Plans', - 'Compilation timeout', - 'https://www.brentozar.com/blitzcache/compilation-timeout/', - 'Query compilation timed out for one or more queries. SQL Server did not find a plan that meets acceptable performance criteria in the time allotted so the best guess was returned. There is a very good chance that this plan isn''t even below average - it''s probably terrible.'); + /* + First, revert these back since we already converted the event data to local time, + and searches will break if we use the times converted over to UTC for the event data + */ + SELECT + @StartDate = @StartDateOriginal, + @EndDate = @EndDateOriginal; - IF EXISTS (SELECT 1/0 - FROM #working_warnings - WHERE compile_memory_limit_exceeded = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 19, - 50, - 'Execution Plans', - 'Compilation memory limit exceeded', - 'https://www.brentozar.com/blitzcache/compile-memory-limit-exceeded/', - 'The optimizer has a limited amount of memory available. One or more queries are complex enough that SQL Server was unable to allocate enough memory to fully optimize the query. A best fit plan was found, and it''s probably terrible.'); + /*Check 1 is deadlocks by database*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Check 1 database deadlocks %s', 0, 1, @d) WITH NOWAIT; - IF EXISTS (SELECT 1/0 - FROM #working_warnings - WHERE warning_no_join_predicate = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 20, - 10, - 'Execution Plans', - 'No join predicate', - 'https://www.brentozar.com/blitzcache/no-join-predicate/', - 'Operators in a query have no join predicate. This means that all rows from one table will be matched with all rows from anther table producing a Cartesian product. That''s a whole lot of rows. This may be your goal, but it''s important to investigate why this is happening.'); + INSERT + #deadlock_findings WITH(TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding, + sort_order + ) + SELECT + check_id = 1, + dp.database_name, + object_name = N'-', + finding_group = N'Total Database Deadlocks', + finding = + N'This database had ' + + CONVERT + ( + nvarchar(20), + COUNT_BIG(DISTINCT dp.event_date) + ) + + N' deadlocks.', + sort_order = + ROW_NUMBER() + OVER (ORDER BY COUNT_BIG(DISTINCT dp.event_date) DESC) + FROM #deadlock_process AS dp + WHERE 1 = 1 + AND (dp.database_name = @DatabaseName OR @DatabaseName IS NULL) + AND (dp.event_date >= @StartDate OR @StartDate IS NULL) + AND (dp.event_date < @EndDate OR @EndDate IS NULL) + AND (dp.client_app = @AppName OR @AppName IS NULL) + AND (dp.host_name = @HostName OR @HostName IS NULL) + AND (dp.login_name = @LoginName OR @LoginName IS NULL) + GROUP BY dp.database_name + OPTION(RECOMPILE); - IF EXISTS (SELECT 1/0 - FROM #working_warnings - WHERE plan_multiple_plans = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 21, - 200, - 'Execution Plans', - 'Multiple execution plans', - 'https://www.brentozar.com/blitzcache/multiple-plans/', - 'Queries exist with multiple execution plans (as determined by query_plan_hash). Investigate possible ways to parameterize these queries or otherwise reduce the plan count.'); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - IF EXISTS (SELECT 1/0 - FROM #working_warnings - WHERE unmatched_index_count > 0 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 22, - 100, - 'Performance', - 'Unmatched indexes', - 'https://www.brentozar.com/blitzcache/unmatched-indexes', - 'An index could have been used, but SQL Server chose not to use it - likely due to parameterization and filtered indexes.'); + /*Check 2 is deadlocks with selects*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Check 2 select deadlocks %s', 0, 1, @d) WITH NOWAIT; - IF EXISTS (SELECT 1/0 - FROM #working_warnings - WHERE unparameterized_query = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 23, - 100, - 'Parameterization', - 'Unparameterized queries', - 'https://www.brentozar.com/blitzcache/unparameterized-queries', - 'Unparameterized queries found. These could be ad hoc queries, data exploration, or queries using "OPTIMIZE FOR UNKNOWN".'); + INSERT + #deadlock_findings WITH(TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding, + sort_order + ) + SELECT + check_id = 2, + dow.database_name, + object_name = + CASE + WHEN EXISTS + ( + SELECT + 1/0 + FROM sys.databases AS d + WHERE d.name COLLATE DATABASE_DEFAULT = dow.database_name COLLATE DATABASE_DEFAULT + AND d.is_read_committed_snapshot_on = 1 + ) + THEN N'You already enabled RCSI, but...' + ELSE N'You Might Need RCSI' + END, + finding_group = N'Total Deadlocks Involving Selects', + finding = + N'There have been ' + + CONVERT + ( + nvarchar(20), + COUNT_BIG(DISTINCT dow.event_date) + ) + + N' deadlock(s) between read queries and modification queries.', + sort_order = + ROW_NUMBER() + OVER (ORDER BY COUNT_BIG(DISTINCT dow.event_date) DESC) + FROM #deadlock_owner_waiter AS dow + WHERE 1 = 1 + AND dow.lock_mode IN + ( + N'S', + N'IS' + ) + OR dow.owner_mode IN + ( + N'S', + N'IS' + ) + OR dow.waiter_mode IN + ( + N'S', + N'IS' + ) + AND (dow.database_id = @DatabaseId OR @DatabaseName IS NULL) + AND (dow.event_date >= @StartDate OR @StartDate IS NULL) + AND (dow.event_date < @EndDate OR @EndDate IS NULL) + AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) + GROUP BY + dow.database_name + OPTION(RECOMPILE); - IF EXISTS (SELECT 1/0 - FROM #working_warnings - WHERE is_trivial = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 24, - 100, - 'Execution Plans', - 'Trivial Plans', - 'https://www.brentozar.com/blitzcache/trivial-plans', - 'Trivial plans get almost no optimization. If you''re finding these in the top worst queries, something may be going wrong.'); - - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.is_forced_serial= 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 25, - 10, - 'Execution Plans', - 'Forced Serialization', - 'https://www.brentozar.com/blitzcache/forced-serialization/', - 'Something in your plan is forcing a serial query. Further investigation is needed if this is not by design.') ; + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.is_key_lookup_expensive= 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 26, - 100, - 'Execution Plans', - 'Expensive Key Lookups', - 'https://www.brentozar.com/blitzcache/expensive-key-lookups/', - 'There''s a key lookup in your plan that costs >=50% of the total plan cost.') ; + /*Check 3 is deadlocks by object*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Check 3 object deadlocks %s', 0, 1, @d) WITH NOWAIT; - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.is_remote_query_expensive= 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 28, - 100, - 'Execution Plans', - 'Expensive Remote Query', - 'https://www.brentozar.com/blitzcache/expensive-remote-query/', - 'There''s a remote query in your plan that costs >=50% of the total plan cost.') ; + INSERT + #deadlock_findings WITH(TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding, + sort_order + ) + SELECT + check_id = 3, + dow.database_name, + object_name = + ISNULL + ( + dow.object_name, + N'UNKNOWN' + ), + finding_group = N'Total Object Deadlocks', + finding = + N'This object was involved in ' + + CONVERT + ( + nvarchar(20), + COUNT_BIG(DISTINCT dow.event_date) + ) + + N' deadlock(s).', + sort_order = + ROW_NUMBER() + OVER (ORDER BY COUNT_BIG(DISTINCT dow.event_date) DESC) + FROM #deadlock_owner_waiter AS dow + WHERE 1 = 1 + AND (dow.database_id = @DatabaseId OR @DatabaseName IS NULL) + AND (dow.event_date >= @StartDate OR @StartDate IS NULL) + AND (dow.event_date < @EndDate OR @EndDate IS NULL) + AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) + GROUP BY + dow.database_name, + dow.object_name + OPTION(RECOMPILE); - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.trace_flags_session IS NOT NULL - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 29, - 100, - 'Trace Flags', - 'Session Level Trace Flags Enabled', - 'https://www.brentozar.com/blitz/trace-flags-enabled-globally/', - 'Someone is enabling session level Trace Flags in a query.') ; + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.is_unused_grant IS NOT NULL - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 30, - 100, - 'Unused memory grants', - 'Queries are asking for more memory than they''re using', - 'https://www.brentozar.com/blitzcache/unused-memory-grants/', - 'Queries have large unused memory grants. This can cause concurrency issues, if queries are waiting a long time to get memory to run.') ; + /*Check 3 continuation, number of deadlocks per index*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Check 3 (continued) index deadlocks %s', 0, 1, @d) WITH NOWAIT; - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.function_count > 0 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 31, - 100, - 'Compute Scalar That References A Function', - 'This could be trouble if you''re using Scalar Functions or MSTVFs', - 'https://www.brentozar.com/blitzcache/compute-scalar-functions/', - 'Both of these will force queries to run serially, run at least once per row, and may result in poor cardinality estimates.') ; + INSERT + #deadlock_findings WITH(TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding, + sort_order + ) + SELECT + check_id = 3, + dow.database_name, + index_name = dow.index_name, + finding_group = N'Total Index Deadlocks', + finding = + N'This index was involved in ' + + CONVERT + ( + nvarchar(20), + COUNT_BIG(DISTINCT dow.event_date) + ) + + N' deadlock(s).', + sort_order = + ROW_NUMBER() + OVER (ORDER BY COUNT_BIG(DISTINCT dow.event_date) DESC) + FROM #deadlock_owner_waiter AS dow + WHERE 1 = 1 + AND (dow.database_id = @DatabaseId OR @DatabaseName IS NULL) + AND (dow.event_date >= @StartDate OR @StartDate IS NULL) + AND (dow.event_date < @EndDate OR @EndDate IS NULL) + AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) + AND dow.lock_type NOT IN + ( + N'HEAP', + N'RID' + ) + AND dow.index_name IS NOT NULL + GROUP BY + dow.database_name, + dow.index_name + OPTION(RECOMPILE); - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.clr_function_count > 0 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 32, - 100, - 'Compute Scalar That References A CLR Function', - 'This could be trouble if your CLR functions perform data access', - 'https://www.brentozar.com/blitzcache/compute-scalar-functions/', - 'May force queries to run serially, run at least once per row, and may result in poor cardinlity estimates.') ; + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + /*Check 3 continuation, number of deadlocks per heap*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Check 3 (continued) heap deadlocks %s', 0, 1, @d) WITH NOWAIT; - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.is_table_variable = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 33, - 100, - 'Table Variables detected', - 'Beware nasty side effects', - 'https://www.brentozar.com/blitzcache/table-variables/', - 'All modifications are single threaded, and selects have really low row estimates.') ; + INSERT + #deadlock_findings WITH(TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding, + sort_order + ) + SELECT + check_id = 3, + dow.database_name, + index_name = dow.index_name, + finding_group = N'Total Heap Deadlocks', + finding = + N'This heap was involved in ' + + CONVERT + ( + nvarchar(20), + COUNT_BIG(DISTINCT dow.event_date) + ) + + N' deadlock(s).', + sort_order = + ROW_NUMBER() + OVER (ORDER BY COUNT_BIG(DISTINCT dow.event_date) DESC) + FROM #deadlock_owner_waiter AS dow + WHERE 1 = 1 + AND (dow.database_id = @DatabaseId OR @DatabaseName IS NULL) + AND (dow.event_date >= @StartDate OR @StartDate IS NULL) + AND (dow.event_date < @EndDate OR @EndDate IS NULL) + AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) + AND dow.lock_type IN + ( + N'HEAP', + N'RID' + ) + GROUP BY + dow.database_name, + dow.index_name + OPTION(RECOMPILE); - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.no_stats_warning = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 35, - 100, - 'Columns with no statistics', - 'Poor cardinality estimates may ensue', - 'https://www.brentozar.com/blitzcache/columns-no-statistics/', - 'Sometimes this happens with indexed views, other times because auto create stats is turned off.') ; + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.relop_warnings = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 36, - 100, - 'Operator Warnings', - 'SQL is throwing operator level plan warnings', - 'https://www.brentozar.com/blitzcache/query-plan-warnings/', - 'Check the plan for more details.') ; + /*Check 4 looks for Serializable deadlocks*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Check 4 serializable deadlocks %s', 0, 1, @d) WITH NOWAIT; - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.is_table_scan = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 37, - 100, - 'Table Scans', - 'Your database has HEAPs', - 'https://www.brentozar.com/archive/2012/05/video-heaps/', - 'This may not be a problem. Run sp_BlitzIndex for more information.') ; - - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.backwards_scan = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 38, - 100, - 'Backwards Scans', - 'Indexes are being read backwards', - 'https://www.brentozar.com/blitzcache/backwards-scans/', - 'This isn''t always a problem. They can cause serial zones in plans, and may need an index to match sort order.') ; + INSERT + #deadlock_findings WITH(TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding, + sort_order + ) + SELECT + check_id = 4, + database_name = + dp.database_name, + object_name = N'-', + finding_group = N'Serializable Deadlocking', + finding = + N'This database has had ' + + CONVERT + ( + nvarchar(20), + COUNT_BIG(DISTINCT dp.event_date) + ) + + N' instances of Serializable deadlocks.', + sort_order = + ROW_NUMBER() + OVER (ORDER BY COUNT_BIG(DISTINCT dp.event_date) DESC) + FROM #deadlock_process AS dp + WHERE dp.isolation_level LIKE N'serializable%' + AND (dp.database_name = @DatabaseName OR @DatabaseName IS NULL) + AND (dp.event_date >= @StartDate OR @StartDate IS NULL) + AND (dp.event_date < @EndDate OR @EndDate IS NULL) + AND (dp.client_app = @AppName OR @AppName IS NULL) + AND (dp.host_name = @HostName OR @HostName IS NULL) + AND (dp.login_name = @LoginName OR @LoginName IS NULL) + GROUP BY dp.database_name + OPTION(RECOMPILE); - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.forced_index = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 39, - 100, - 'Index forcing', - 'Someone is using hints to force index usage', - 'https://www.brentozar.com/blitzcache/optimizer-forcing/', - 'This can cause inefficient plans, and will prevent missing index requests.') ; + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.forced_seek = 1 - OR p.forced_scan = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 40, - 100, - 'Seek/Scan forcing', - 'Someone is using hints to force index seeks/scans', - 'https://www.brentozar.com/blitzcache/optimizer-forcing/', - 'This can cause inefficient plans by taking seek vs scan choice away from the optimizer.') ; + /*Check 5 looks for Repeatable Read deadlocks*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Check 5 repeatable read deadlocks %s', 0, 1, @d) WITH NOWAIT; - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.columnstore_row_mode = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 41, - 100, - 'ColumnStore indexes operating in Row Mode', - 'Batch Mode is optimal for ColumnStore indexes', - 'https://www.brentozar.com/blitzcache/columnstore-indexes-operating-row-mode/', - 'ColumnStore indexes operating in Row Mode indicate really poor query choices.') ; + INSERT + #deadlock_findings WITH(TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding, + sort_order + ) + SELECT + check_id = 5, + dp.database_name, + object_name = N'-', + finding_group = N'Repeatable Read Deadlocking', + finding = + N'This database has had ' + + CONVERT + ( + nvarchar(20), + COUNT_BIG(DISTINCT dp.event_date) + ) + + N' instances of Repeatable Read deadlocks.', + sort_order = + ROW_NUMBER() + OVER (ORDER BY COUNT_BIG(DISTINCT dp.event_date) DESC) + FROM #deadlock_process AS dp + WHERE dp.isolation_level LIKE N'repeatable%' + AND (dp.database_name = @DatabaseName OR @DatabaseName IS NULL) + AND (dp.event_date >= @StartDate OR @StartDate IS NULL) + AND (dp.event_date < @EndDate OR @EndDate IS NULL) + AND (dp.client_app = @AppName OR @AppName IS NULL) + AND (dp.host_name = @HostName OR @HostName IS NULL) + AND (dp.login_name = @LoginName OR @LoginName IS NULL) + GROUP BY dp.database_name + OPTION(RECOMPILE); - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.is_computed_scalar = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 42, - 50, - 'Computed Columns Referencing Scalar UDFs', - 'This makes a whole lot of stuff run serially', - 'https://www.brentozar.com/blitzcache/computed-columns-referencing-functions/', - 'This can cause a whole mess of bad serializartion problems.') ; + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.is_sort_expensive = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 43, - 100, - 'Execution Plans', - 'Expensive Sort', - 'https://www.brentozar.com/blitzcache/expensive-sorts/', - 'There''s a sort in your plan that costs >=50% of the total plan cost.') ; + /*Check 6 breaks down app, host, and login information*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Check 6 app/host/login deadlocks %s', 0, 1, @d) WITH NOWAIT; - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.is_computed_filter = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 44, - 50, - 'Filters Referencing Scalar UDFs', - 'This forces serialization', - 'https://www.brentozar.com/blitzcache/compute-scalar-functions/', - 'Someone put a Scalar UDF in the WHERE clause!') ; + INSERT + #deadlock_findings WITH(TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding, + sort_order + ) + SELECT + check_id = 6, + database_name = + dp.database_name, + object_name = N'-', + finding_group = N'Login, App, and Host deadlocks', + finding = + N'This database has had ' + + CONVERT + ( + nvarchar(20), + COUNT_BIG(DISTINCT dp.event_date) + ) + + N' instances of deadlocks involving the login ' + + ISNULL + ( + dp.login_name, + N'UNKNOWN' + ) + + N' from the application ' + + ISNULL + ( + dp.client_app, + N'UNKNOWN' + ) + + N' on host ' + + ISNULL + ( + dp.host_name, + N'UNKNOWN' + ) + + N'.', + sort_order = + ROW_NUMBER() + OVER (ORDER BY COUNT_BIG(DISTINCT dp.event_date) DESC) + FROM #deadlock_process AS dp + WHERE 1 = 1 + AND (dp.database_name = @DatabaseName OR @DatabaseName IS NULL) + AND (dp.event_date >= @StartDate OR @StartDate IS NULL) + AND (dp.event_date < @EndDate OR @EndDate IS NULL) + AND (dp.client_app = @AppName OR @AppName IS NULL) + AND (dp.host_name = @HostName OR @HostName IS NULL) + AND (dp.login_name = @LoginName OR @LoginName IS NULL) + GROUP BY + dp.database_name, + dp.login_name, + dp.client_app, + dp.host_name + OPTION(RECOMPILE); - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.index_ops >= 5 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 45, - 100, - 'Many Indexes Modified', - 'Write Queries Are Hitting >= 5 Indexes', - 'https://www.brentozar.com/blitzcache/many-indexes-modified/', - 'This can cause lots of hidden I/O -- Run sp_BlitzIndex for more information.') ; + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + + /*Check 7 breaks down the types of deadlocks (object, page, key, etc.)*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Check 7 types of deadlocks %s', 0, 1, @d) WITH NOWAIT; - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.is_row_level = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 46, - 100, - 'Plan Confusion', - 'Row Level Security is in use', - 'https://www.brentozar.com/blitzcache/row-level-security/', - 'You may see a lot of confusing junk in your query plan.') ; + WITH + lock_types AS + ( + SELECT + database_name = + dp.database_name, + dow.object_name, + lock = + CASE + WHEN CHARINDEX(N':', dp.wait_resource) > 0 + THEN SUBSTRING + ( + dp.wait_resource, + 1, + CHARINDEX(N':', dp.wait_resource) - 1 + ) + ELSE dp.wait_resource + END, + lock_count = + CONVERT + ( + nvarchar(20), + COUNT_BIG(DISTINCT dp.event_date) + ) + FROM #deadlock_process AS dp + JOIN #deadlock_owner_waiter AS dow + ON (dp.id = dow.owner_id + OR dp.victim_id = dow.waiter_id) + AND dp.event_date = dow.event_date + WHERE 1 = 1 + AND (dp.database_name = @DatabaseName OR @DatabaseName IS NULL) + AND (dp.event_date >= @StartDate OR @StartDate IS NULL) + AND (dp.event_date < @EndDate OR @EndDate IS NULL) + AND (dp.client_app = @AppName OR @AppName IS NULL) + AND (dp.host_name = @HostName OR @HostName IS NULL) + AND (dp.login_name = @LoginName OR @LoginName IS NULL) + AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) + AND dow.object_name IS NOT NULL + GROUP BY + dp.database_name, + CASE + WHEN CHARINDEX(N':', dp.wait_resource) > 0 + THEN SUBSTRING + ( + dp.wait_resource, + 1, + CHARINDEX(N':', dp.wait_resource) - 1 + ) + ELSE dp.wait_resource + END, + dow.object_name + ) + INSERT + #deadlock_findings WITH(TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding, + sort_order + ) + SELECT + check_id = 7, + lt.database_name, + lt.object_name, + finding_group = N'Types of locks by object', + finding = + N'This object has had ' + + STUFF + ( + ( + SELECT + N', ' + + lt2.lock_count + + N' ' + + lt2.lock + FROM lock_types AS lt2 + WHERE lt2.database_name = lt.database_name + AND lt2.object_name = lt.object_name + FOR XML + PATH(N''), + TYPE + ).value(N'.[1]', N'nvarchar(MAX)'), + 1, + 1, + N'' + ) + N' locks.', + sort_order = + ROW_NUMBER() + OVER (ORDER BY CONVERT(bigint, lt.lock_count) DESC) + FROM lock_types AS lt + OPTION(RECOMPILE); - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.is_spatial = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 47, - 200, - 'Spatial Abuse', - 'You hit a Spatial Index', - 'https://www.brentozar.com/blitzcache/spatial-indexes/', - 'Purely informational.') ; + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.index_dml = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 48, - 150, - 'Index DML', - 'Indexes were created or dropped', - 'https://www.brentozar.com/blitzcache/index-dml/', - 'This can cause recompiles and stuff.') ; + /*Check 8 gives you more info queries for sp_BlitzCache & BlitzQueryStore*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Check 8 more info part 1 BlitzCache %s', 0, 1, @d) WITH NOWAIT; - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.table_dml = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 49, - 150, - 'Table DML', - 'Tables were created or dropped', - 'https://www.brentozar.com/blitzcache/table-dml/', - 'This can cause recompiles and stuff.') ; + WITH + deadlock_stack AS + ( + SELECT DISTINCT + ds.id, + ds.event_date, + ds.proc_name, + database_name = + PARSENAME(ds.proc_name, 3), + schema_name = + PARSENAME(ds.proc_name, 2), + proc_only_name = + PARSENAME(ds.proc_name, 1), + sql_handle_csv = + N'''' + + STUFF + ( + ( + SELECT DISTINCT + N',' + + ds2.sql_handle + FROM #deadlock_stack AS ds2 + WHERE ds2.id = ds.id + AND ds2.event_date = ds.event_date + AND ds2.sql_handle <> 0x + FOR XML + PATH(N''), + TYPE + ).value(N'.[1]', N'nvarchar(MAX)'), + 1, + 1, + N'' + ) + + N'''' + FROM #deadlock_stack AS ds + WHERE ds.sql_handle <> 0x + GROUP BY + PARSENAME(ds.proc_name, 3), + PARSENAME(ds.proc_name, 2), + PARSENAME(ds.proc_name, 1), + ds.proc_name, + ds.id, + ds.event_date + ) + INSERT + #deadlock_findings WITH(TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding + ) + SELECT DISTINCT + check_id = 8, + dow.database_name, + object_name = ds.proc_name, + finding_group = N'More Info - Query', + finding = N'EXEC sp_BlitzCache ' + + CASE + WHEN ds.proc_name = N'adhoc' + THEN N'@OnlySqlHandles = ' + ds.sql_handle_csv + ELSE N'@StoredProcName = ' + QUOTENAME(ds.proc_only_name, N'''') + END + N';' + FROM deadlock_stack AS ds + JOIN #deadlock_owner_waiter AS dow + ON dow.owner_id = ds.id + AND dow.event_date = ds.event_date + WHERE 1 = 1 + AND (dow.database_id = @DatabaseId OR @DatabaseName IS NULL) + AND (dow.event_date >= @StartDate OR @StartDate IS NULL) + AND (dow.event_date < @EndDate OR @EndDate IS NULL) + AND (dow.object_name = @StoredProcName OR @StoredProcName IS NULL) + AND ds.proc_name NOT LIKE 'Unknown%' + OPTION(RECOMPILE); - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.long_running_low_cpu = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 50, - 150, - 'Long Running Low CPU', - 'You have a query that runs for much longer than it uses CPU', - 'https://www.brentozar.com/blitzcache/long-running-low-cpu/', - 'This can be a sign of blocking, linked servers, or poor client application code (ASYNC_NETWORK_IO).') ; + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.low_cost_high_cpu = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 51, - 150, - 'Low Cost Query With High CPU', - 'You have a low cost query that uses a lot of CPU', - 'https://www.brentozar.com/blitzcache/low-cost-high-cpu/', - 'This can be a sign of functions or Dynamic SQL that calls black-box code.') ; + IF (@ProductVersionMajor >= 13 OR @Azure = 1) + BEGIN + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Check 8 more info part 2 BlitzQueryStore %s', 0, 1, @d) WITH NOWAIT; - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.stale_stats = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 52, - 150, - 'Biblical Statistics', - 'Statistics used in queries are >7 days old with >100k modifications', - 'https://www.brentozar.com/blitzcache/stale-statistics/', - 'Ever heard of updating statistics?') ; + WITH + deadlock_stack AS + ( + SELECT DISTINCT + ds.id, + ds.sql_handle, + ds.proc_name, + ds.event_date, + database_name = + PARSENAME(ds.proc_name, 3), + schema_name = + PARSENAME(ds.proc_name, 2), + proc_only_name = + PARSENAME(ds.proc_name, 1) + FROM #deadlock_stack AS ds + ) + INSERT + #deadlock_findings WITH(TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding + ) + SELECT DISTINCT + check_id = 8, + dow.database_name, + object_name = ds.proc_name, + finding_group = N'More Info - Query', + finding = + N'EXEC sp_BlitzQueryStore ' + + N'@DatabaseName = ' + + QUOTENAME(ds.database_name, N'''') + + N', ' + + N'@StoredProcName = ' + + QUOTENAME(ds.proc_only_name, N'''') + + N';' + FROM deadlock_stack AS ds + JOIN #deadlock_owner_waiter AS dow + ON dow.owner_id = ds.id + AND dow.event_date = ds.event_date + WHERE ds.proc_name <> N'adhoc' + AND (dow.database_id = @DatabaseId OR @DatabaseName IS NULL) + AND (dow.event_date >= @StartDate OR @StartDate IS NULL) + AND (dow.event_date < @EndDate OR @EndDate IS NULL) + AND (dow.object_name = @StoredProcName OR @StoredProcName IS NULL) + OPTION(RECOMPILE); - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.is_adaptive = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 53, - 150, - 'Adaptive joins', - 'This is pretty cool -- you''re living in the future.', - 'https://www.brentozar.com/blitzcache/adaptive-joins/', - 'Joe Sack rules.') ; + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + END; - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.is_spool_expensive = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 54, - 150, - 'Expensive Index Spool', - 'You have an index spool, this is usually a sign that there''s an index missing somewhere.', - 'https://www.brentozar.com/blitzcache/eager-index-spools/', - 'Check operator predicates and output for index definition guidance') ; + /*Check 9 gives you stored procedure deadlock counts*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Check 9 stored procedure deadlocks %s', 0, 1, @d) WITH NOWAIT; - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.is_spool_more_rows = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 55, - 150, - 'Index Spools Many Rows', - 'You have an index spool that spools more rows than the query returns', - 'https://www.brentozar.com/blitzcache/eager-index-spools/', - 'Check operator predicates and output for index definition guidance') ; + INSERT + #deadlock_findings WITH(TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding, + sort_order + ) + SELECT + check_id = 9, + database_name = + dp.database_name, + object_name = ds.proc_name, + finding_group = N'Stored Procedure Deadlocks', + finding = + N'The stored procedure ' + + PARSENAME(ds.proc_name, 2) + + N'.' + + PARSENAME(ds.proc_name, 1) + + N' has been involved in ' + + CONVERT + ( + nvarchar(10), + COUNT_BIG(DISTINCT ds.id) + ) + + N' deadlocks.', + sort_order = + ROW_NUMBER() + OVER (ORDER BY COUNT_BIG(DISTINCT ds.id) DESC) + FROM #deadlock_stack AS ds + JOIN #deadlock_process AS dp + ON dp.id = ds.id + AND ds.event_date = dp.event_date + WHERE ds.proc_name <> N'adhoc' + AND (ds.proc_name = @StoredProcName OR @StoredProcName IS NULL) + AND (dp.database_name = @DatabaseName OR @DatabaseName IS NULL) + AND (dp.event_date >= @StartDate OR @StartDate IS NULL) + AND (dp.event_date < @EndDate OR @EndDate IS NULL) + AND (dp.client_app = @AppName OR @AppName IS NULL) + AND (dp.host_name = @HostName OR @HostName IS NULL) + AND (dp.login_name = @LoginName OR @LoginName IS NULL) + GROUP BY + dp.database_name, + ds.proc_name + OPTION(RECOMPILE); - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.is_bad_estimate = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 56, - 100, - 'Potentially bad cardinality estimates', - 'Estimated rows are different from average rows by a factor of 10000', - 'https://www.brentozar.com/blitzcache/bad-estimates/', - 'This may indicate a performance problem if mismatches occur regularly') ; + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.is_big_log = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 57, - 100, - 'High transaction log use', - 'This query on average uses more than half of the transaction log', - 'http://michaeljswart.com/2014/09/take-care-when-scripting-batches/', - 'This is probably a sign that you need to start batching queries') ; - - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.is_big_tempdb = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 58, - 100, - 'High tempdb use', - 'This query uses more than half of a data file on average', - 'No URL yet', - 'You should take a look at tempdb waits to see if you''re having problems') ; - - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.is_row_goal = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (59, - 200, - 'Row Goals', - 'This query had row goals introduced', - 'https://www.brentozar.com/archive/2018/01/sql-server-2017-cu3-adds-optimizer-row-goal-information-query-plans/', - 'This can be good or bad, and should be investigated for high read queries') ; + /*Check 10 gives you more info queries for sp_BlitzIndex */ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Check 10 more info, BlitzIndex %s', 0, 1, @d) WITH NOWAIT; - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.is_mstvf = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 60, - 100, - 'MSTVFs', - 'These have many of the same problems scalar UDFs have', - 'https://www.brentozar.com/blitzcache/tvf-join/', - 'Execution plans have been found that join to table valued functions (TVFs). TVFs produce inaccurate estimates of the number of rows returned and can lead to any number of query plan problems.'); + WITH + bi AS + ( + SELECT DISTINCT + dow.object_name, + dow.database_name, + schema_name = s.schema_name, + table_name = s.table_name + FROM #deadlock_owner_waiter AS dow + JOIN @sysAssObjId AS s + ON s.database_id = dow.database_id + AND s.partition_id = dow.associatedObjectId + WHERE 1 = 1 + AND (dow.database_id = @DatabaseId OR @DatabaseName IS NULL) + AND (dow.event_date >= @StartDate OR @StartDate IS NULL) + AND (dow.event_date < @EndDate OR @EndDate IS NULL) + AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) + AND dow.object_name IS NOT NULL + ) + INSERT + #deadlock_findings WITH(TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding + ) + SELECT DISTINCT + check_id = 10, + bi.database_name, + bi.object_name, + finding_group = N'More Info - Table', + finding = + N'EXEC sp_BlitzIndex ' + + N'@DatabaseName = ' + + QUOTENAME(bi.database_name, N'''') + + N', @SchemaName = ' + + QUOTENAME(bi.schema_name, N'''') + + N', @TableName = ' + + QUOTENAME(bi.table_name, N'''') + + N';' + FROM bi + OPTION(RECOMPILE); - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.is_mstvf = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (61, - 100, - 'Many to Many Merge', - 'These use secret worktables that could be doing lots of reads', - 'https://www.brentozar.com/archive/2018/04/many-mysteries-merge-joins/', - 'Occurs when join inputs aren''t known to be unique. Can be really bad when parallel.'); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.is_nonsargable = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (62, - 50, - 'Non-SARGable queries', - 'Queries may be using', - 'https://www.brentozar.com/blitzcache/non-sargable-predicates/', - 'Occurs when join inputs aren''t known to be unique. Can be really bad when parallel.'); - - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.is_paul_white_electric = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 998, - 200, - 'Is Paul White Electric?', - 'This query has a Switch operator in it!', - 'https://www.sql.kiwi/2013/06/hello-operator-my-switch-is-bored.html', - 'You should email this query plan to Paul: SQLkiwi at gmail dot com') ; - - - - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - SELECT - 999, - 200, - 'Database Level Statistics', - 'The database ' + sa.[database] + ' last had a stats update on ' + CONVERT(NVARCHAR(10), CONVERT(DATE, MAX(sa.last_update))) + ' and has ' + CONVERT(NVARCHAR(10), AVG(sa.modification_count)) + ' modifications on average.' AS Finding, - 'https://www.brentozar.com/blitzcache/stale-statistics/' AS URL, - 'Consider updating statistics more frequently,' AS Details - FROM #stats_agg AS sa - GROUP BY sa.[database] - HAVING MAX(sa.last_update) <= DATEADD(DAY, -7, SYSDATETIME()) - AND AVG(sa.modification_count) >= 100000; + /*Check 11 gets total deadlock wait time per object*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Check 11 deadlock wait time per object %s', 0, 1, @d) WITH NOWAIT; - - IF EXISTS (SELECT 1/0 - FROM #trace_flags AS tf - WHERE tf.global_trace_flags IS NOT NULL - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 1000, - 255, - 'Global Trace Flags Enabled', - 'You have Global Trace Flags enabled on your server', - 'https://www.brentozar.com/blitz/trace-flags-enabled-globally/', - 'You have the following Global Trace Flags enabled: ' + (SELECT TOP 1 tf.global_trace_flags FROM #trace_flags AS tf WHERE tf.global_trace_flags IS NOT NULL)) ; + WITH + chopsuey AS + ( + + SELECT + database_name = + dp.database_name, + dow.object_name, + wait_days = + CONVERT + ( + nvarchar(30), + ( + SUM + ( + CONVERT + ( + bigint, + dp.wait_time + ) + ) / 1000 / 86400 + ) + ), + wait_time_hms = + /*the more wait time you rack up the less accurate this gets, + it's either that or erroring out*/ + CASE + WHEN + SUM + ( + CONVERT + ( + bigint, + dp.wait_time + ) + )/1000 > 2147483647 + THEN + CONVERT + ( + nvarchar(30), + DATEADD + ( + MINUTE, + ( + ( + SUM + ( + CONVERT + ( + bigint, + dp.wait_time + ) + ) + )/ + 60000 + ), + 0 + ), + 14 + ) + WHEN + SUM + ( + CONVERT + ( + bigint, + dp.wait_time + ) + ) BETWEEN 2147483648 AND 2147483647000 + THEN + CONVERT + ( + nvarchar(30), + DATEADD + ( + SECOND, + ( + ( + SUM + ( + CONVERT + ( + bigint, + dp.wait_time + ) + ) + )/ + 1000 + ), + 0 + ), + 14 + ) + ELSE + CONVERT + ( + nvarchar(30), + DATEADD + ( + MILLISECOND, + ( + SUM + ( + CONVERT + ( + bigint, + dp.wait_time + ) + ) + ), + 0 + ), + 14 + ) + END, + total_waits = + SUM(CONVERT(bigint, dp.wait_time)) + FROM #deadlock_owner_waiter AS dow + JOIN #deadlock_process AS dp + ON (dp.id = dow.owner_id + OR dp.victim_id = dow.waiter_id) + AND dp.event_date = dow.event_date + WHERE 1 = 1 + AND (dp.database_name = @DatabaseName OR @DatabaseName IS NULL) + AND (dp.event_date >= @StartDate OR @StartDate IS NULL) + AND (dp.event_date < @EndDate OR @EndDate IS NULL) + AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) + AND (dp.client_app = @AppName OR @AppName IS NULL) + AND (dp.host_name = @HostName OR @HostName IS NULL) + AND (dp.login_name = @LoginName OR @LoginName IS NULL) + GROUP BY + dp.database_name, + dow.object_name + ) + INSERT + #deadlock_findings WITH(TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding, + sort_order + ) + SELECT + check_id = 11, + cs.database_name, + cs.object_name, + finding_group = N'Total object deadlock wait time', + finding = + N'This object has had ' + + CONVERT + ( + nvarchar(30), + cs.wait_days + ) + + N' ' + + CONVERT + ( + nvarchar(30), + cs.wait_time_hms, + 14 + ) + + N' [dd hh:mm:ss:ms] of deadlock wait time.', + sort_order = + ROW_NUMBER() + OVER (ORDER BY cs.total_waits DESC) + FROM chopsuey AS cs + WHERE cs.object_name IS NOT NULL + OPTION(RECOMPILE); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - /* - Return worsts - */ - WITH worsts AS ( - SELECT gi.flat_date, - gi.start_range, - gi.end_range, - gi.total_avg_duration_ms, - gi.total_avg_cpu_time_ms, - gi.total_avg_logical_io_reads_mb, - gi.total_avg_physical_io_reads_mb, - gi.total_avg_logical_io_writes_mb, - gi.total_avg_query_max_used_memory_mb, - gi.total_rowcount, - gi.total_avg_log_bytes_mb, - gi.total_avg_tempdb_space, - gi.total_max_duration_ms, - gi.total_max_cpu_time_ms, - gi.total_max_logical_io_reads_mb, - gi.total_max_physical_io_reads_mb, - gi.total_max_logical_io_writes_mb, - gi.total_max_query_max_used_memory_mb, - gi.total_max_log_bytes_mb, - gi.total_max_tempdb_space, - CONVERT(NVARCHAR(20), gi.flat_date) AS worst_date, - CASE WHEN DATEPART(HOUR, gi.start_range) = 0 THEN ' midnight ' - WHEN DATEPART(HOUR, gi.start_range) <= 12 THEN CONVERT(NVARCHAR(3), DATEPART(HOUR, gi.start_range)) + 'am ' - WHEN DATEPART(HOUR, gi.start_range) > 12 THEN CONVERT(NVARCHAR(3), DATEPART(HOUR, gi.start_range) -12) + 'pm ' - END AS worst_start_time, - CASE WHEN DATEPART(HOUR, gi.end_range) = 0 THEN ' midnight ' - WHEN DATEPART(HOUR, gi.end_range) <= 12 THEN CONVERT(NVARCHAR(3), DATEPART(HOUR, gi.end_range)) + 'am ' - WHEN DATEPART(HOUR, gi.end_range) > 12 THEN CONVERT(NVARCHAR(3), DATEPART(HOUR, gi.end_range) -12) + 'pm ' - END AS worst_end_time - FROM #grouped_interval AS gi - ), /*averages*/ - duration_worst AS ( - SELECT TOP 1 'Your worst avg duration range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg - FROM worsts - ORDER BY worsts.total_avg_duration_ms DESC - ), - cpu_worst AS ( - SELECT TOP 1 'Your worst avg cpu range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg - FROM worsts - ORDER BY worsts.total_avg_cpu_time_ms DESC - ), - logical_reads_worst AS ( - SELECT TOP 1 'Your worst avg logical read range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg - FROM worsts - ORDER BY worsts.total_avg_logical_io_reads_mb DESC - ), - physical_reads_worst AS ( - SELECT TOP 1 'Your worst avg physical read range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg - FROM worsts - ORDER BY worsts.total_avg_physical_io_reads_mb DESC - ), - logical_writes_worst AS ( - SELECT TOP 1 'Your worst avg logical write range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg - FROM worsts - ORDER BY worsts.total_avg_logical_io_writes_mb DESC - ), - memory_worst AS ( - SELECT TOP 1 'Your worst avg memory range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg - FROM worsts - ORDER BY worsts.total_avg_query_max_used_memory_mb DESC - ), - rowcount_worst AS ( - SELECT TOP 1 'Your worst avg row count range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg - FROM worsts - ORDER BY worsts.total_rowcount DESC - ), - logbytes_worst AS ( - SELECT TOP 1 'Your worst avg log bytes range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg - FROM worsts - ORDER BY worsts.total_avg_log_bytes_mb DESC - ), - tempdb_worst AS ( - SELECT TOP 1 'Your worst avg tempdb range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg - FROM worsts - ORDER BY worsts.total_avg_tempdb_space DESC - )/*maxes*/, - max_duration_worst AS ( - SELECT TOP 1 'Your worst max duration range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg - FROM worsts - ORDER BY worsts.total_max_duration_ms DESC - ), - max_cpu_worst AS ( - SELECT TOP 1 'Your worst max cpu range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg - FROM worsts - ORDER BY worsts.total_max_cpu_time_ms DESC - ), - max_logical_reads_worst AS ( - SELECT TOP 1 'Your worst max logical read range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg - FROM worsts - ORDER BY worsts.total_max_logical_io_reads_mb DESC - ), - max_physical_reads_worst AS ( - SELECT TOP 1 'Your worst max physical read range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg - FROM worsts - ORDER BY worsts.total_max_physical_io_reads_mb DESC - ), - max_logical_writes_worst AS ( - SELECT TOP 1 'Your worst max logical write range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg - FROM worsts - ORDER BY worsts.total_max_logical_io_writes_mb DESC - ), - max_memory_worst AS ( - SELECT TOP 1 'Your worst max memory range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg - FROM worsts - ORDER BY worsts.total_max_query_max_used_memory_mb DESC - ), - max_logbytes_worst AS ( - SELECT TOP 1 'Your worst max log bytes range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg - FROM worsts - ORDER BY worsts.total_max_log_bytes_mb DESC - ), - max_tempdb_worst AS ( - SELECT TOP 1 'Your worst max tempdb range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg - FROM worsts - ORDER BY worsts.total_max_tempdb_space DESC - ) - INSERT #warning_results ( CheckID, Priority, FindingsGroup, Finding, URL, Details ) - /*averages*/ - SELECT 1002, 255, 'Worsts', 'Worst Avg Duration', 'N/A', duration_worst.msg - FROM duration_worst - UNION ALL - SELECT 1002, 255, 'Worsts', 'Worst Avg CPU', 'N/A', cpu_worst.msg - FROM cpu_worst - UNION ALL - SELECT 1002, 255, 'Worsts', 'Worst Avg Logical Reads', 'N/A', logical_reads_worst.msg - FROM logical_reads_worst - UNION ALL - SELECT 1002, 255, 'Worsts', 'Worst Avg Physical Reads', 'N/A', physical_reads_worst.msg - FROM physical_reads_worst - UNION ALL - SELECT 1002, 255, 'Worsts', 'Worst Avg Logical Writes', 'N/A', logical_writes_worst.msg - FROM logical_writes_worst - UNION ALL - SELECT 1002, 255, 'Worsts', 'Worst Avg Memory', 'N/A', memory_worst.msg - FROM memory_worst - UNION ALL - SELECT 1002, 255, 'Worsts', 'Worst Row Counts', 'N/A', rowcount_worst.msg - FROM rowcount_worst - UNION ALL - SELECT 1002, 255, 'Worsts', 'Worst Avg Log Bytes', 'N/A', logbytes_worst.msg - FROM logbytes_worst - UNION ALL - SELECT 1002, 255, 'Worsts', 'Worst Avg tempdb', 'N/A', tempdb_worst.msg - FROM tempdb_worst - UNION ALL - /*maxes*/ - SELECT 1002, 255, 'Worsts', 'Worst Max Duration', 'N/A', max_duration_worst.msg - FROM max_duration_worst - UNION ALL - SELECT 1002, 255, 'Worsts', 'Worst Max CPU', 'N/A', max_cpu_worst.msg - FROM max_cpu_worst - UNION ALL - SELECT 1002, 255, 'Worsts', 'Worst Max Logical Reads', 'N/A', max_logical_reads_worst.msg - FROM max_logical_reads_worst - UNION ALL - SELECT 1002, 255, 'Worsts', 'Worst Max Physical Reads', 'N/A', max_physical_reads_worst.msg - FROM max_physical_reads_worst - UNION ALL - SELECT 1002, 255, 'Worsts', 'Worst Max Logical Writes', 'N/A', max_logical_writes_worst.msg - FROM max_logical_writes_worst - UNION ALL - SELECT 1002, 255, 'Worsts', 'Worst Max Memory', 'N/A', max_memory_worst.msg - FROM max_memory_worst - UNION ALL - SELECT 1002, 255, 'Worsts', 'Worst Max Log Bytes', 'N/A', max_logbytes_worst.msg - FROM max_logbytes_worst - UNION ALL - SELECT 1002, 255, 'Worsts', 'Worst Max tempdb', 'N/A', max_tempdb_worst.msg - FROM max_tempdb_worst - OPTION (RECOMPILE); + /*Check 12 gets total deadlock wait time per database*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Check 12 deadlock wait time per database %s', 0, 1, @d) WITH NOWAIT; + WITH + wait_time AS + ( + SELECT + database_name = + dp.database_name, + total_wait_time_ms = + SUM + ( + CONVERT + ( + bigint, + dp.wait_time + ) + ) + FROM #deadlock_owner_waiter AS dow + JOIN #deadlock_process AS dp + ON (dp.id = dow.owner_id + OR dp.victim_id = dow.waiter_id) + AND dp.event_date = dow.event_date + WHERE 1 = 1 + AND (dp.database_name = @DatabaseName OR @DatabaseName IS NULL) + AND (dp.event_date >= @StartDate OR @StartDate IS NULL) + AND (dp.event_date < @EndDate OR @EndDate IS NULL) + AND (dp.client_app = @AppName OR @AppName IS NULL) + AND (dp.host_name = @HostName OR @HostName IS NULL) + AND (dp.login_name = @LoginName OR @LoginName IS NULL) + GROUP BY + dp.database_name + ) + INSERT + #deadlock_findings WITH(TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding, + sort_order + ) + SELECT + check_id = 12, + wt.database_name, + object_name = N'-', + finding_group = N'Total database deadlock wait time', + N'This database has had ' + + CONVERT + ( + nvarchar(30), + ( + SUM + ( + CONVERT + ( + bigint, + wt.total_wait_time_ms + ) + ) / 1000 / 86400 + ) + ) + + N' ' + + /*the more wait time you rack up the less accurate this gets, + it's either that or erroring out*/ + CASE + WHEN + SUM + ( + CONVERT + ( + bigint, + wt.total_wait_time_ms + ) + )/1000 > 2147483647 + THEN + CONVERT + ( + nvarchar(30), + DATEADD + ( + MINUTE, + ( + ( + SUM + ( + CONVERT + ( + bigint, + wt.total_wait_time_ms + ) + ) + )/ + 60000 + ), + 0 + ), + 14 + ) + WHEN + SUM + ( + CONVERT + ( + bigint, + wt.total_wait_time_ms + ) + ) BETWEEN 2147483648 AND 2147483647000 + THEN + CONVERT + ( + nvarchar(30), + DATEADD + ( + SECOND, + ( + ( + SUM + ( + CONVERT + ( + bigint, + wt.total_wait_time_ms + ) + ) + )/ + 1000 + ), + 0 + ), + 14 + ) + ELSE + CONVERT + ( + nvarchar(30), + DATEADD + ( + MILLISECOND, + ( + SUM + ( + CONVERT + ( + bigint, + wt.total_wait_time_ms + ) + ) + ), + 0 + ), + 14 + ) END + + N' [dd hh:mm:ss:ms] of deadlock wait time.', + sort_order = + ROW_NUMBER() + OVER (ORDER BY SUM(CONVERT(bigint, wt.total_wait_time_ms)) DESC) + FROM wait_time AS wt + GROUP BY + wt.database_name + OPTION(RECOMPILE); - IF NOT EXISTS (SELECT 1/0 - FROM #warning_results AS bcr - WHERE bcr.Priority = 2147483646 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (2147483646, - 255, - 'Need more help?' , - 'Paste your plan on the internet!', - 'http://pastetheplan.com', - 'This makes it easy to share plans and post them to Q&A sites like https://dba.stackexchange.com/!') ; + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + /*Check 13 gets total deadlock wait time for SQL Agent*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Check 13 deadlock count for SQL Agent %s', 0, 1, @d) WITH NOWAIT; - IF NOT EXISTS (SELECT 1/0 - FROM #warning_results AS bcr - WHERE bcr.Priority = 2147483647 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 2147483647, - 255, - 'Thanks for using sp_BlitzQueryStore!' , - 'From Your Community Volunteers', - 'http://FirstResponderKit.org', - 'We hope you found this tool useful. Current version: ' + @Version + ' released on ' + CONVERT(NVARCHAR(30), @VersionDate) + '.') ; - + INSERT + #deadlock_findings WITH(TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding, + sort_order + ) + SELECT + check_id = 13, + database_name = + DB_NAME(aj.database_id), + object_name = + N'SQLAgent - Job: ' + + aj.job_name + + N' Step: ' + + aj.step_name, + finding_group = N'Agent Job Deadlocks', + finding = + N'There have been ' + + RTRIM(COUNT_BIG(DISTINCT aj.event_date)) + + N' deadlocks from this Agent Job and Step.', + sort_order = + ROW_NUMBER() + OVER (ORDER BY COUNT_BIG(DISTINCT aj.event_date) DESC) + FROM #agent_job AS aj + GROUP BY + DB_NAME(aj.database_id), + aj.job_name, + aj.step_name + OPTION(RECOMPILE); - - SELECT Priority, - FindingsGroup, - Finding, - URL, - Details, - CheckID - FROM #warning_results - GROUP BY Priority, - FindingsGroup, - Finding, - URL, - Details, - CheckID - ORDER BY Priority ASC, FindingsGroup, Finding, CheckID ASC - OPTION (RECOMPILE); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + /*Check 14 is total parallel deadlocks*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Check 14 parallel deadlocks %s', 0, 1, @d) WITH NOWAIT; + INSERT + #deadlock_findings WITH(TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding + ) + SELECT + check_id = 14, + database_name = N'-', + object_name = N'-', + finding_group = N'Total parallel deadlocks', + finding = + N'There have been ' + + CONVERT + ( + nvarchar(20), + COUNT_BIG(DISTINCT drp.event_date) + ) + + N' parallel deadlocks.' + FROM #deadlock_resource_parallel AS drp + HAVING COUNT_BIG(DISTINCT drp.event_date) > 0 + OPTION(RECOMPILE); -END; + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; -END; -END TRY -BEGIN CATCH - RAISERROR (N'Failure returning warnings', 0,1) WITH NOWAIT; + /*Check 15 is total deadlocks involving sleeping sessions*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Check 15 sleeping and background deadlocks %s', 0, 1, @d) WITH NOWAIT; - IF @sql_select IS NOT NULL - BEGIN - SET @msg = N'Last @sql_select: ' + @sql_select; - RAISERROR(@msg, 0, 1) WITH NOWAIT; - END; + INSERT + #deadlock_findings WITH(TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding + ) + SELECT + check_id = 15, + database_name = N'-', + object_name = N'-', + finding_group = N'Total deadlocks involving sleeping sessions', + finding = + N'There have been ' + + CONVERT + ( + nvarchar(20), + COUNT_BIG(DISTINCT dp.event_date) + ) + + N' sleepy deadlocks.' + FROM #deadlock_process AS dp + WHERE dp.status = N'sleeping' + HAVING COUNT_BIG(DISTINCT dp.event_date) > 0 + OPTION(RECOMPILE); - SELECT @msg = @DatabaseName + N' database failed to process. ' + ERROR_MESSAGE(), @error_severity = ERROR_SEVERITY(), @error_state = ERROR_STATE(); - RAISERROR (@msg, @error_severity, @error_state) WITH NOWAIT; - - - WHILE @@TRANCOUNT > 0 - ROLLBACK; + INSERT + #deadlock_findings WITH(TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding + ) + SELECT + check_id = 15, + database_name = N'-', + object_name = N'-', + finding_group = N'Total deadlocks involving background processes', + finding = + N'There have been ' + + CONVERT + ( + nvarchar(20), + COUNT_BIG(DISTINCT dp.event_date) + ) + + N' deadlocks with background task.' + FROM #deadlock_process AS dp + WHERE dp.status = N'background' + HAVING COUNT_BIG(DISTINCT dp.event_date) > 0 + OPTION(RECOMPILE); - RETURN; -END CATCH; + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; -IF @Debug = 1 + /*Check 16 is total deadlocks involving implicit transactions*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Check 16 implicit transaction deadlocks %s', 0, 1, @d) WITH NOWAIT; -BEGIN TRY + INSERT + #deadlock_findings WITH(TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding + ) + SELECT + check_id = 14, + database_name = N'-', + object_name = N'-', + finding_group = N'Total implicit transaction deadlocks', + finding = + N'There have been ' + + CONVERT + ( + nvarchar(20), + COUNT_BIG(DISTINCT dp.event_date) + ) + + N' implicit transaction deadlocks.' + FROM #deadlock_process AS dp + WHERE dp.transaction_name = N'implicit_transaction' + HAVING COUNT_BIG(DISTINCT dp.event_date) > 0 + OPTION(RECOMPILE); -BEGIN + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; -RAISERROR(N'Returning debugging data from temp tables', 0, 1) WITH NOWAIT; + /*Thank you goodnight*/ + INSERT + #deadlock_findings WITH(TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding + ) + VALUES + ( + -1, + N'sp_BlitzLock version ' + CONVERT(nvarchar(10), @Version), + N'Results for ' + CONVERT(nvarchar(10), @StartDate, 23) + N' through ' + CONVERT(nvarchar(10), @EndDate, 23), + N'http://FirstResponderKit.org/', + N'To get help or add your own contributions to the SQL Server First Responder Kit, join us at http://FirstResponderKit.org.' + ); ---Table content debugging + RAISERROR('Finished rollup at %s', 0, 1, @d) WITH NOWAIT; -SELECT '#working_metrics' AS table_name, * -FROM #working_metrics AS wm -OPTION (RECOMPILE); + /*Results*/ + BEGIN + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Results 1 %s', 0, 1, @d) WITH NOWAIT; -SELECT '#working_plan_text' AS table_name, * -FROM #working_plan_text AS wpt -OPTION (RECOMPILE); + CREATE CLUSTERED INDEX cx_whatever_dp ON #deadlock_process (event_date, id); + CREATE CLUSTERED INDEX cx_whatever_drp ON #deadlock_resource_parallel (event_date, owner_id); + CREATE CLUSTERED INDEX cx_whatever_dow ON #deadlock_owner_waiter (event_date, owner_id, waiter_id); -SELECT '#working_warnings' AS table_name, * -FROM #working_warnings AS ww -OPTION (RECOMPILE); + IF @Debug = 1 BEGIN SET STATISTICS XML ON; END; -SELECT '#working_wait_stats' AS table_name, * -FROM #working_wait_stats wws -OPTION (RECOMPILE); + WITH + deadlocks AS + ( + SELECT + deadlock_type = + N'Regular Deadlock', + dp.event_date, + dp.id, + dp.victim_id, + dp.spid, + dp.database_id, + dp.database_name, + dp.current_database_name, + dp.priority, + dp.log_used, + wait_resource = + dp.wait_resource COLLATE DATABASE_DEFAULT, + object_names = + CONVERT + ( + xml, + STUFF + ( + ( + SELECT DISTINCT + object_name = + NCHAR(10) + + N' ' + + ISNULL(c.object_name, N'') + + N' ' COLLATE DATABASE_DEFAULT + FROM #deadlock_owner_waiter AS c + WHERE (dp.id = c.owner_id + OR dp.victim_id = c.waiter_id) + AND dp.event_date = c.event_date + FOR XML + PATH(N''), + TYPE + ).value(N'.[1]', N'nvarchar(4000)'), + 1, + 1, + N'' + ) + ), + dp.wait_time, + dp.transaction_name, + dp.status, + dp.last_tran_started, + dp.last_batch_started, + dp.last_batch_completed, + dp.lock_mode, + dp.transaction_count, + dp.client_app, + dp.host_name, + dp.login_name, + dp.isolation_level, + dp.client_option_1, + dp.client_option_2, + inputbuf = + dp.process_xml.value('(//process/inputbuf/text())[1]', 'nvarchar(MAX)'), + en = + DENSE_RANK() OVER (ORDER BY dp.event_date), + qn = + ROW_NUMBER() OVER (PARTITION BY dp.event_date ORDER BY dp.event_date) - 1, + dn = + ROW_NUMBER() OVER (PARTITION BY dp.event_date, dp.id ORDER BY dp.event_date), + dp.is_victim, + owner_mode = + ISNULL(dp.owner_mode, N'-'), + owner_waiter_type = NULL, + owner_activity = NULL, + owner_waiter_activity = NULL, + owner_merging = NULL, + owner_spilling = NULL, + owner_waiting_to_close = NULL, + waiter_mode = + ISNULL(dp.waiter_mode, N'-'), + waiter_waiter_type = NULL, + waiter_owner_activity = NULL, + waiter_waiter_activity = NULL, + waiter_merging = NULL, + waiter_spilling = NULL, + waiter_waiting_to_close = NULL, + dp.deadlock_graph + FROM #deadlock_process AS dp + WHERE dp.victim_id IS NOT NULL + AND dp.is_parallel = 0 -SELECT '#grouped_interval' AS table_name, * -FROM #grouped_interval -OPTION (RECOMPILE); + UNION ALL -SELECT '#working_plans' AS table_name, * -FROM #working_plans -OPTION (RECOMPILE); + SELECT + deadlock_type = + N'Parallel Deadlock', + dp.event_date, + dp.id, + dp.victim_id, + dp.spid, + dp.database_id, + dp.database_name, + dp.current_database_name, + dp.priority, + dp.log_used, + dp.wait_resource COLLATE DATABASE_DEFAULT, + object_names = + CONVERT + ( + xml, + STUFF + ( + ( + SELECT DISTINCT + object_name = + NCHAR(10) + + N' ' + + ISNULL(c.object_name, N'') + + N' ' COLLATE DATABASE_DEFAULT + FROM #deadlock_owner_waiter AS c + WHERE (dp.id = c.owner_id + OR dp.victim_id = c.waiter_id) + AND dp.event_date = c.event_date + FOR XML + PATH(N''), + TYPE + ).value(N'.[1]', N'nvarchar(4000)'), + 1, + 1, + N'' + ) + ), + dp.wait_time, + dp.transaction_name, + dp.status, + dp.last_tran_started, + dp.last_batch_started, + dp.last_batch_completed, + dp.lock_mode, + dp.transaction_count, + dp.client_app, + dp.host_name, + dp.login_name, + dp.isolation_level, + dp.client_option_1, + dp.client_option_2, + inputbuf = + dp.process_xml.value('(//process/inputbuf/text())[1]', 'nvarchar(MAX)'), + en = + DENSE_RANK() OVER (ORDER BY dp.event_date), + qn = + ROW_NUMBER() OVER (PARTITION BY dp.event_date ORDER BY dp.event_date) - 1, + dn = + ROW_NUMBER() OVER (PARTITION BY dp.event_date, dp.id ORDER BY dp.event_date), + is_victim = 1, + owner_mode = cao.wait_type COLLATE DATABASE_DEFAULT, + owner_waiter_type = cao.waiter_type, + owner_activity = cao.owner_activity, + owner_waiter_activity = cao.waiter_activity, + owner_merging = cao.merging, + owner_spilling = cao.spilling, + owner_waiting_to_close = cao.waiting_to_close, + waiter_mode = caw.wait_type COLLATE DATABASE_DEFAULT, + waiter_waiter_type = caw.waiter_type, + waiter_owner_activity = caw.owner_activity, + waiter_waiter_activity = caw.waiter_activity, + waiter_merging = caw.merging, + waiter_spilling = caw.spilling, + waiter_waiting_to_close = caw.waiting_to_close, + dp.deadlock_graph + FROM #deadlock_process AS dp + OUTER APPLY + ( + SELECT TOP (1) + drp.* + FROM #deadlock_resource_parallel AS drp + WHERE drp.owner_id = dp.id + AND drp.wait_type IN + ( + N'e_waitPortOpen', + N'e_waitPipeNewRow' + ) + ORDER BY drp.event_date + ) AS cao + OUTER APPLY + ( + SELECT TOP (1) + drp.* + FROM #deadlock_resource_parallel AS drp + WHERE drp.owner_id = dp.id + AND drp.wait_type IN + ( + N'e_waitPortOpen', + N'e_waitPipeGetRow' + ) + ORDER BY drp.event_date + ) AS caw + WHERE dp.is_parallel = 1 + ) + SELECT + d.deadlock_type, + d.event_date, + d.id, + d.victim_id, + d.spid, + deadlock_group = + N'Deadlock #' + + CONVERT + ( + nvarchar(10), + d.en + ) + + N', Query #' + + CASE + WHEN d.qn = 0 + THEN N'1' + ELSE CONVERT(nvarchar(10), d.qn) + END + CASE + WHEN d.is_victim = 1 + THEN N' - VICTIM' + ELSE N'' + END, + d.database_id, + d.database_name, + d.current_database_name, + d.priority, + d.log_used, + d.wait_resource, + d.object_names, + d.wait_time, + d.transaction_name, + d.status, + d.last_tran_started, + d.last_batch_started, + d.last_batch_completed, + d.lock_mode, + d.transaction_count, + d.client_app, + d.host_name, + d.login_name, + d.isolation_level, + d.client_option_1, + d.client_option_2, + inputbuf = + CASE + WHEN d.inputbuf + LIKE @inputbuf_bom + N'Proc |[Database Id = %' ESCAPE N'|' + THEN + OBJECT_SCHEMA_NAME + ( + SUBSTRING + ( + d.inputbuf, + CHARINDEX(N'Object Id = ', d.inputbuf) + 12, + LEN(d.inputbuf) - (CHARINDEX(N'Object Id = ', d.inputbuf) + 12) + ) + , + SUBSTRING + ( + d.inputbuf, + CHARINDEX(N'Database Id = ', d.inputbuf) + 14, + CHARINDEX(N'Object Id', d.inputbuf) - (CHARINDEX(N'Database Id = ', d.inputbuf) + 14) + ) + ) + + N'.' + + OBJECT_NAME + ( + SUBSTRING + ( + d.inputbuf, + CHARINDEX(N'Object Id = ', d.inputbuf) + 12, + LEN(d.inputbuf) - (CHARINDEX(N'Object Id = ', d.inputbuf) + 12) + ) + , + SUBSTRING + ( + d.inputbuf, + CHARINDEX(N'Database Id = ', d.inputbuf) + 14, + CHARINDEX(N'Object Id', d.inputbuf) - (CHARINDEX(N'Database Id = ', d.inputbuf) + 14) + ) + ) + ELSE d.inputbuf + END COLLATE Latin1_General_BIN2, + d.owner_mode, + d.owner_waiter_type, + d.owner_activity, + d.owner_waiter_activity, + d.owner_merging, + d.owner_spilling, + d.owner_waiting_to_close, + d.waiter_mode, + d.waiter_waiter_type, + d.waiter_owner_activity, + d.waiter_waiter_activity, + d.waiter_merging, + d.waiter_spilling, + d.waiter_waiting_to_close, + d.deadlock_graph, + d.is_victim + INTO #deadlocks + FROM deadlocks AS d + WHERE d.dn = 1 + AND (d.is_victim = @VictimsOnly + OR @VictimsOnly = 0) + AND d.qn < CASE + WHEN d.deadlock_type = N'Parallel Deadlock' + THEN 2 + ELSE 2147483647 + END + AND (DB_NAME(d.database_id) = @DatabaseName OR @DatabaseName IS NULL) + AND (d.event_date >= @StartDate OR @StartDate IS NULL) + AND (d.event_date < @EndDate OR @EndDate IS NULL) + AND (CONVERT(nvarchar(MAX), d.object_names) COLLATE Latin1_General_BIN2 LIKE N'%' + @ObjectName + N'%' OR @ObjectName IS NULL) + AND (d.client_app = @AppName OR @AppName IS NULL) + AND (d.host_name = @HostName OR @HostName IS NULL) + AND (d.login_name = @LoginName OR @LoginName IS NULL) + OPTION (RECOMPILE, LOOP JOIN, HASH JOIN); -SELECT '#stats_agg' AS table_name, * -FROM #stats_agg -OPTION (RECOMPILE); + UPDATE d + SET d.inputbuf = + REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE( + REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE( + REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE( + d.inputbuf, + NCHAR(31), N'?'), NCHAR(30), N'?'), NCHAR(29), N'?'), NCHAR(28), N'?'), NCHAR(27), N'?'), + NCHAR(26), N'?'), NCHAR(25), N'?'), NCHAR(24), N'?'), NCHAR(23), N'?'), NCHAR(22), N'?'), + NCHAR(21), N'?'), NCHAR(20), N'?'), NCHAR(19), N'?'), NCHAR(18), N'?'), NCHAR(17), N'?'), + NCHAR(16), N'?'), NCHAR(15), N'?'), NCHAR(14), N'?'), NCHAR(12), N'?'), NCHAR(11), N'?'), + NCHAR(8), N'?'), NCHAR(7), N'?'), NCHAR(6), N'?'), NCHAR(5), N'?'), NCHAR(4), N'?'), + NCHAR(3), N'?'), NCHAR(2), N'?'), NCHAR(1), N'?'), NCHAR(0), N'?') + FROM #deadlocks AS d + OPTION(RECOMPILE); -SELECT '#trace_flags' AS table_name, * -FROM #trace_flags -OPTION (RECOMPILE); + SELECT + d.deadlock_type, + d.event_date, + database_name = + DB_NAME(d.database_id), + database_name_x = + d.database_name, + d.current_database_name, + d.spid, + d.deadlock_group, + d.client_option_1, + d.client_option_2, + d.lock_mode, + query_xml = + ( + SELECT + [processing-instruction(query)] = + d.inputbuf + FOR XML + PATH(N''), + TYPE + ), + query_string = + d.inputbuf, + d.object_names, + d.isolation_level, + d.owner_mode, + d.waiter_mode, + d.transaction_count, + d.login_name, + d.host_name, + d.client_app, + d.wait_time, + d.wait_resource, + d.priority, + d.log_used, + d.last_tran_started, + d.last_batch_started, + d.last_batch_completed, + d.transaction_name, + d.status, + /*These columns will be NULL for regular (non-parallel) deadlocks*/ + parallel_deadlock_details = + ( + SELECT + d.owner_waiter_type, + d.owner_activity, + d.owner_waiter_activity, + d.owner_merging, + d.owner_spilling, + d.owner_waiting_to_close, + d.waiter_waiter_type, + d.waiter_owner_activity, + d.waiter_waiter_activity, + d.waiter_merging, + d.waiter_spilling, + d.waiter_waiting_to_close + FOR XML + PATH('parallel_deadlock_details'), + TYPE + ), + d.owner_waiter_type, + d.owner_activity, + d.owner_waiter_activity, + d.owner_merging, + d.owner_spilling, + d.owner_waiting_to_close, + d.waiter_waiter_type, + d.waiter_owner_activity, + d.waiter_waiter_activity, + d.waiter_merging, + d.waiter_spilling, + d.waiter_waiting_to_close, + /*end parallel deadlock columns*/ + d.deadlock_graph, + d.is_victim, + d.id + INTO #deadlock_results + FROM #deadlocks AS d; -SELECT '#statements' AS table_name, * -FROM #statements AS s -OPTION (RECOMPILE); + IF @Debug = 1 BEGIN SET STATISTICS XML OFF; END; + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; -SELECT '#query_plan' AS table_name, * -FROM #query_plan AS qp -OPTION (RECOMPILE); + /*There's too much risk of errors sending the*/ + IF @OutputDatabaseCheck = 0 + BEGIN + SET @ExportToExcel = 0; + END; -SELECT '#relop' AS table_name, * -FROM #relop AS r -OPTION (RECOMPILE); + SET @deadlock_result += N' + SELECT + server_name = + @@SERVERNAME, + dr.deadlock_type, + dr.event_date, + database_name = + COALESCE + ( + dr.database_name, + dr.database_name_x, + dr.current_database_name + ), + dr.spid, + dr.deadlock_group, + ' + CASE @ExportToExcel + WHEN 1 + THEN N' + query = dr.query_string, + object_names = + REPLACE( + REPLACE( + CONVERT + ( + nvarchar(MAX), + dr.object_names + ) COLLATE Latin1_General_BIN2, + '''', ''''), + '''', ''''),' + ELSE N'query = dr.query_xml, + dr.object_names,' + END + N' + dr.isolation_level, + dr.owner_mode, + dr.waiter_mode, + dr.lock_mode, + dr.transaction_count, + dr.client_option_1, + dr.client_option_2, + dr.login_name, + dr.host_name, + dr.client_app, + dr.wait_time, + dr.wait_resource, + dr.priority, + dr.log_used, + dr.last_tran_started, + dr.last_batch_started, + dr.last_batch_completed, + dr.transaction_name, + dr.status,' + + CASE + WHEN (@ExportToExcel = 1 + OR @OutputDatabaseCheck = 0) + THEN N' + dr.owner_waiter_type, + dr.owner_activity, + dr.owner_waiter_activity, + dr.owner_merging, + dr.owner_spilling, + dr.owner_waiting_to_close, + dr.waiter_waiter_type, + dr.waiter_owner_activity, + dr.waiter_waiter_activity, + dr.waiter_merging, + dr.waiter_spilling, + dr.waiter_waiting_to_close,' + ELSE N' + dr.parallel_deadlock_details,' + END + + CASE + @ExportToExcel + WHEN 1 + THEN N' + deadlock_graph = + REPLACE(REPLACE( + REPLACE(REPLACE( + CONVERT + ( + nvarchar(MAX), + dr.deadlock_graph + ) COLLATE Latin1_General_BIN2, + ''NCHAR(10)'', ''''), ''NCHAR(13)'', ''''), + ''CHAR(10)'', ''''), ''CHAR(13)'', '''')' + ELSE N' + dr.deadlock_graph' + END + N' + FROM #deadlock_results AS dr + ORDER BY + dr.event_date, + dr.is_victim DESC + OPTION(RECOMPILE, LOOP JOIN, HASH JOIN); + '; -SELECT '#plan_cost' AS table_name, * -FROM #plan_cost AS pc -OPTION (RECOMPILE); + IF (@OutputDatabaseCheck = 0) + BEGIN + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Results to table %s', 0, 1, @d) WITH NOWAIT; -SELECT '#est_rows' AS table_name, * -FROM #est_rows AS er -OPTION (RECOMPILE); + IF @Debug = 1 + BEGIN + PRINT @deadlock_result; + SET STATISTICS XML ON; + END; -SELECT '#stored_proc_info' AS table_name, * -FROM #stored_proc_info AS spi -OPTION(RECOMPILE); + INSERT INTO + DeadLockTbl + ( + ServerName, + deadlock_type, + event_date, + database_name, + spid, + deadlock_group, + query, + object_names, + isolation_level, + owner_mode, + waiter_mode, + lock_mode, + transaction_count, + client_option_1, + client_option_2, + login_name, + host_name, + client_app, + wait_time, + wait_resource, + priority, + log_used, + last_tran_started, + last_batch_started, + last_batch_completed, + transaction_name, + status, + owner_waiter_type, + owner_activity, + owner_waiter_activity, + owner_merging, + owner_spilling, + owner_waiting_to_close, + waiter_waiter_type, + waiter_owner_activity, + waiter_waiter_activity, + waiter_merging, + waiter_spilling, + waiter_waiting_to_close, + deadlock_graph + ) + EXEC sys.sp_executesql + @deadlock_result; -SELECT '#conversion_info' AS table_name, * -FROM #conversion_info AS ci -OPTION ( RECOMPILE ); + IF @Debug = 1 + BEGIN + SET STATISTICS XML OFF; + END; -SELECT '#variable_info' AS table_name, * -FROM #variable_info AS vi -OPTION ( RECOMPILE ); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; -SELECT '#missing_index_xml' AS table_name, * -FROM #missing_index_xml -OPTION ( RECOMPILE ); + DROP SYNONYM DeadLockTbl; -SELECT '#missing_index_schema' AS table_name, * -FROM #missing_index_schema -OPTION ( RECOMPILE ); + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Findings to table %s', 0, 1, @d) WITH NOWAIT; -SELECT '#missing_index_usage' AS table_name, * -FROM #missing_index_usage -OPTION ( RECOMPILE ); + INSERT INTO + DeadlockFindings + ( + ServerName, + check_id, + database_name, + object_name, + finding_group, + finding + ) + SELECT + @@SERVERNAME, + df.check_id, + df.database_name, + df.object_name, + df.finding_group, + df.finding + FROM #deadlock_findings AS df + ORDER BY df.check_id + OPTION(RECOMPILE); -SELECT '#missing_index_detail' AS table_name, * -FROM #missing_index_detail -OPTION ( RECOMPILE ); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; -SELECT '#missing_index_pretty' AS table_name, * -FROM #missing_index_pretty -OPTION ( RECOMPILE ); + DROP SYNONYM DeadlockFindings; /*done with inserting.*/ + END; + ELSE /*Output to database is not set output to client app*/ + BEGIN + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Results to client %s', 0, 1, @d) WITH NOWAIT; + + IF @Debug = 1 BEGIN SET STATISTICS XML ON; END; + + EXEC sys.sp_executesql + @deadlock_result; + + IF @Debug = 1 + BEGIN + SET STATISTICS XML OFF; + PRINT @deadlock_result; + END; + + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; -END; + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Getting available execution plans for deadlocks %s', 0, 1, @d) WITH NOWAIT; + + SELECT DISTINCT + available_plans = + 'available_plans', + ds.proc_name, + sql_handle = + CONVERT(varbinary(64), ds.sql_handle, 1), + dow.database_name, + dow.database_id, + dow.object_name, + query_xml = + TRY_CAST(dr.query_xml AS nvarchar(MAX)) + INTO #available_plans + FROM #deadlock_stack AS ds + JOIN #deadlock_owner_waiter AS dow + ON dow.owner_id = ds.id + AND dow.event_date = ds.event_date + JOIN #deadlock_results AS dr + ON dr.id = ds.id + AND dr.event_date = ds.event_date + OPTION(RECOMPILE); -END TRY -BEGIN CATCH - RAISERROR (N'Failure returning debug temp tables', 0,1) WITH NOWAIT; + SELECT + deqs.sql_handle, + deqs.plan_handle, + deqs.statement_start_offset, + deqs.statement_end_offset, + deqs.creation_time, + deqs.last_execution_time, + deqs.execution_count, + total_worker_time_ms = + deqs.total_worker_time / 1000., + avg_worker_time_ms = + CONVERT(decimal(38, 6), deqs.total_worker_time / 1000. / deqs.execution_count), + total_elapsed_time_ms = + deqs.total_elapsed_time / 1000., + avg_elapsed_time_ms = + CONVERT(decimal(38, 6), deqs.total_elapsed_time / 1000. / deqs.execution_count), + executions_per_second = + ISNULL + ( + deqs.execution_count / + NULLIF + ( + DATEDIFF + ( + SECOND, + deqs.creation_time, + NULLIF(deqs.last_execution_time, '1900-01-01 00:00:00.000') + ), + 0 + ), + 0 + ), + total_physical_reads_mb = + deqs.total_physical_reads * 8. / 1024., + total_logical_writes_mb = + deqs.total_logical_writes * 8. / 1024., + total_logical_reads_mb = + deqs.total_logical_reads * 8. / 1024., + min_grant_mb = + deqs.min_grant_kb * 8. / 1024., + max_grant_mb = + deqs.max_grant_kb * 8. / 1024., + min_used_grant_mb = + deqs.min_used_grant_kb * 8. / 1024., + max_used_grant_mb = + deqs.max_used_grant_kb * 8. / 1024., + deqs.min_reserved_threads, + deqs.max_reserved_threads, + deqs.min_used_threads, + deqs.max_used_threads, + deqs.total_rows + INTO #dm_exec_query_stats + FROM sys.dm_exec_query_stats AS deqs + WHERE EXISTS + ( + SELECT + 1/0 + FROM #available_plans AS ap + WHERE ap.sql_handle = deqs.sql_handle + ) + AND deqs.query_hash IS NOT NULL; + + CREATE CLUSTERED INDEX + deqs + ON #dm_exec_query_stats + ( + sql_handle, + plan_handle + ); + + SELECT + ap.available_plans, + ap.database_name, + query_text = + TRY_CAST(ap.query_xml AS xml), + ap.query_plan, + ap.creation_time, + ap.last_execution_time, + ap.execution_count, + ap.executions_per_second, + ap.total_worker_time_ms, + ap.avg_worker_time_ms, + ap.total_elapsed_time_ms, + ap.avg_elapsed_time_ms, + ap.total_logical_reads_mb, + ap.total_physical_reads_mb, + ap.total_logical_writes_mb, + ap.min_grant_mb, + ap.max_grant_mb, + ap.min_used_grant_mb, + ap.max_used_grant_mb, + ap.min_reserved_threads, + ap.max_reserved_threads, + ap.min_used_threads, + ap.max_used_threads, + ap.total_rows, + ap.sql_handle, + ap.statement_start_offset, + ap.statement_end_offset + FROM + ( + + SELECT + ap.*, + c.statement_start_offset, + c.statement_end_offset, + c.creation_time, + c.last_execution_time, + c.execution_count, + c.total_worker_time_ms, + c.avg_worker_time_ms, + c.total_elapsed_time_ms, + c.avg_elapsed_time_ms, + c.executions_per_second, + c.total_physical_reads_mb, + c.total_logical_writes_mb, + c.total_logical_reads_mb, + c.min_grant_mb, + c.max_grant_mb, + c.min_used_grant_mb, + c.max_used_grant_mb, + c.min_reserved_threads, + c.max_reserved_threads, + c.min_used_threads, + c.max_used_threads, + c.total_rows, + c.query_plan + FROM #available_plans AS ap + OUTER APPLY + ( + SELECT + deqs.*, + query_plan = + TRY_CAST(deps.query_plan AS xml) + FROM #dm_exec_query_stats deqs + OUTER APPLY sys.dm_exec_text_query_plan + ( + deqs.plan_handle, + deqs.statement_start_offset, + deqs.statement_end_offset + ) AS deps + WHERE deqs.sql_handle = ap.sql_handle + AND deps.dbid = ap.database_id + ) AS c + ) AS ap + WHERE ap.query_plan IS NOT NULL + ORDER BY + ap.avg_worker_time_ms DESC + OPTION(RECOMPILE, LOOP JOIN, HASH JOIN); - IF @sql_select IS NOT NULL - BEGIN - SET @msg = N'Last @sql_select: ' + @sql_select; - RAISERROR(@msg, 0, 1) WITH NOWAIT; + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Returning findings %s', 0, 1, @d) WITH NOWAIT; + + SELECT + df.check_id, + df.database_name, + df.object_name, + df.finding_group, + df.finding + FROM #deadlock_findings AS df + ORDER BY + df.check_id, + df.sort_order + OPTION(RECOMPILE); + + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + END; /*done with output to client app.*/ END; - SELECT @msg = @DatabaseName + N' database failed to process. ' + ERROR_MESSAGE(), @error_severity = ERROR_SEVERITY(), @error_state = ERROR_STATE(); - RAISERROR (@msg, @error_severity, @error_state) WITH NOWAIT; - - - WHILE @@TRANCOUNT > 0 - ROLLBACK; - - RETURN; -END CATCH; + IF @Debug = 1 + BEGIN + SELECT + table_name = N'#deadlock_data', + * + FROM #deadlock_data AS dd + OPTION(RECOMPILE); -/* -Ways to run this thing + SELECT + table_name = N'#dd', + * + FROM #dd AS d + OPTION(RECOMPILE); ---Debug -EXEC sp_BlitzQueryStore @DatabaseName = 'StackOverflow', @Debug = 1 + SELECT + table_name = N'#deadlock_resource', + * + FROM #deadlock_resource AS dr + OPTION(RECOMPILE); ---Get the top 1 -EXEC sp_BlitzQueryStore @DatabaseName = 'StackOverflow', @Top = 1, @Debug = 1 + SELECT + table_name = N'#deadlock_resource_parallel', + * + FROM #deadlock_resource_parallel AS drp + OPTION(RECOMPILE); ---Use a StartDate -EXEC sp_BlitzQueryStore @DatabaseName = 'StackOverflow', @Top = 1, @StartDate = '20170527' - ---Use an EndDate -EXEC sp_BlitzQueryStore @DatabaseName = 'StackOverflow', @Top = 1, @EndDate = '20170527' - ---Use Both -EXEC sp_BlitzQueryStore @DatabaseName = 'StackOverflow', @Top = 1, @StartDate = '20170526', @EndDate = '20170527' + SELECT + table_name = N'#deadlock_owner_waiter', + * + FROM #deadlock_owner_waiter AS dow + OPTION(RECOMPILE); ---Set a minimum execution count -EXEC sp_BlitzQueryStore @DatabaseName = 'StackOverflow', @Top = 1, @MinimumExecutionCount = 10 + SELECT + table_name = N'#deadlock_process', + * + FROM #deadlock_process AS dp + OPTION(RECOMPILE); ---Set a duration minimum -EXEC sp_BlitzQueryStore @DatabaseName = 'StackOverflow', @Top = 1, @DurationFilter = 5 + SELECT + table_name = N'#deadlock_stack', + * + FROM #deadlock_stack AS ds + OPTION(RECOMPILE); ---Look for a stored procedure name (that doesn't exist!) -EXEC sp_BlitzQueryStore @DatabaseName = 'StackOverflow', @Top = 1, @StoredProcName = 'blah' + SELECT + table_name = N'#deadlocks', + * + FROM #deadlocks AS d + OPTION(RECOMPILE); ---Look for a stored procedure name that does (at least On My Computer®) -EXEC sp_BlitzQueryStore @DatabaseName = 'StackOverflow', @Top = 1, @StoredProcName = 'UserReportExtended' + SELECT + table_name = N'#deadlock_results', + * + FROM #deadlock_results AS dr + OPTION(RECOMPILE); ---Look for failed queries -EXEC sp_BlitzQueryStore @DatabaseName = 'StackOverflow', @Top = 1, @Failed = 1 + SELECT + table_name = N'#x', + * + FROM #x AS x + OPTION(RECOMPILE); ---Filter by plan_id -EXEC sp_BlitzQueryStore @DatabaseName = 'StackOverflow', @PlanIdFilter = 3356 + SELECT + table_name = N'@sysAssObjId', + * + FROM @sysAssObjId AS s + OPTION(RECOMPILE); ---Filter by query_id -EXEC sp_BlitzQueryStore @DatabaseName = 'StackOverflow', @QueryIdFilter = 2958 + SELECT + table_name = N'#available_plans', + * + FROM #available_plans AS ap + OPTION(RECOMPILE); -*/ + SELECT + table_name = N'#dm_exec_query_stats', + * + FROM #dm_exec_query_stats + OPTION(RECOMPILE); -END; + SELECT + procedure_parameters = + 'procedure_parameters', + DatabaseName = + @DatabaseName, + StartDate = + @StartDate, + EndDate = + @EndDate, + ObjectName = + @ObjectName, + StoredProcName = + @StoredProcName, + AppName = + @AppName, + HostName = + @HostName, + LoginName = + @LoginName, + EventSessionName = + @EventSessionName, + TargetSessionType = + @TargetSessionType, + VictimsOnly = + @VictimsOnly, + Debug = + @Debug, + Help = + @Help, + Version = + @Version, + VersionDate = + @VersionDate, + VersionCheckMode = + @VersionCheckMode, + OutputDatabaseName = + @OutputDatabaseName, + OutputSchemaName = + @OutputSchemaName, + OutputTableName = + @OutputTableName, + ExportToExcel = + @ExportToExcel; -GO + SELECT + declared_variables = + 'declared_variables', + DatabaseId = + @DatabaseId, + StartDateUTC = + @StartDateUTC, + EndDateUTC = + @EndDateUTC, + ProductVersion = + @ProductVersion, + ProductVersionMajor = + @ProductVersionMajor, + ProductVersionMinor = + @ProductVersionMinor, + ObjectFullName = + @ObjectFullName, + Azure = + @Azure, + RDS = + @RDS, + d = + @d, + StringToExecute = + @StringToExecute, + StringToExecuteParams = + @StringToExecuteParams, + r = + @r, + OutputTableFindings = + @OutputTableFindings, + DeadlockCount = + @DeadlockCount, + ServerName = + @ServerName, + OutputDatabaseCheck = + @OutputDatabaseCheck, + SessionId = + @SessionId, + TargetSessionId = + @TargetSessionId, + FileName = + @FileName, + inputbuf_bom = + @inputbuf_bom, + deadlock_result = + @deadlock_result; + END; /*End debug*/ + END; /*Final End*/ +GO IF OBJECT_ID('dbo.sp_BlitzWho') IS NULL EXEC ('CREATE PROCEDURE dbo.sp_BlitzWho AS RETURN 0;') GO @@ -42976,7 +34421,7 @@ VALUES (16, 4003, 'CU1', 'https://support.microsoft.com/en-us/help/5022375', '2023-02-16', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 1'), (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'), - (15, 4355, 'CU25', 'https://support.microsoft.com/kb/5033688', '2023-02-15', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 25'), + (15, 4355, 'CU25', 'https://support.microsoft.com/kb/5033688', '2024-02-15', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 25'), (15, 4345, 'CU24', 'https://support.microsoft.com/kb/5031908', '2023-12-14', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 24'), (15, 4335, 'CU23', 'https://support.microsoft.com/kb/5030333', '2023-10-12', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 23'), (15, 4322, 'CU22', 'https://support.microsoft.com/kb/5027702', '2023-08-14', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 22'), @@ -43585,7 +35030,11 @@ IF @LogMessage IS NOT NULL END; IF @SinceStartup = 1 - SELECT @Seconds = 0, @ExpertMode = 1; + BEGIN + SET @Seconds = 0 + IF @ExpertMode = 0 + SET @ExpertMode = 1 + END; IF @OutputType = 'SCHEMA' @@ -46343,7 +37792,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 50 AS Priority, 'Server Performance' AS FindingGroup, 'Slow Data File Reads' AS Finding, - 'https://www.brentozar.com/go/slow/' AS URL, + 'https://www.brentozar.com/blitz/slow-storage-reads-writes/' AS URL, 'Your server is experiencing PAGEIOLATCH% waits due to slow data file reads. This file is one of the reasons why.' + @LineFeed + 'File: ' + fNow.PhysicalName + @LineFeed + 'Number of reads during the sample: ' + CAST((fNow.num_of_reads - fBase.num_of_reads) AS NVARCHAR(20)) + @LineFeed @@ -46373,7 +37822,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 50 AS Priority, 'Server Performance' AS FindingGroup, 'Slow Log File Writes' AS Finding, - 'https://www.brentozar.com/go/slow/' AS URL, + 'https://www.brentozar.com/blitz/slow-storage-reads-writes/' AS URL, 'Your server is experiencing WRITELOG waits due to slow log file writes. This file is one of the reasons why.' + @LineFeed + 'File: ' + fNow.PhysicalName + @LineFeed + 'Number of writes during the sample: ' + CAST((fNow.num_of_writes - fBase.num_of_writes) AS NVARCHAR(20)) + @LineFeed @@ -46550,8 +37999,10 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 'Garbage Collection in Progress' AS Finding, 'https://www.brentozar.com/go/garbage/' AS URL, CAST(ps.value_delta AS NVARCHAR(50)) + ' rows processed (from SQL Server YYYY XTP Garbage Collection:Rows processed/sec counter)' + @LineFeed - + 'This can happen due to memory pressure (causing In-Memory OLTP to shrink its footprint) or' + @LineFeed - + 'due to transactional workloads that constantly insert/delete data.' AS Details, + + 'This can happen for a few reasons: ' + @LineFeed + + 'Memory-Optimized TempDB, or ' + @LineFeed + + 'transactional workloads that constantly insert/delete data in In-Memory OLTP tables, or ' + @LineFeed + + 'memory pressure (causing In-Memory OLTP to shrink its footprint) or' AS Details, 'Sadly, you cannot choose when garbage collection occurs. This is one of the many gotchas of Hekaton. Learn more: http://nedotter.com/archive/2016/04/row-version-lifecycle-for-in-memory-oltp/' AS HowToStopIt FROM #PerfmonStats ps INNER JOIN #PerfmonStats psComp ON psComp.Pass = 2 AND psComp.object_name LIKE '%XTP Garbage Collection' AND psComp.counter_name = 'Rows processed/sec' AND psComp.value_delta > 100 @@ -46659,7 +38110,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','SQL Re-Compilations/sec', NULL); /* Server Info - SQL Compilations/sec - CheckID 25 */ - IF @ExpertMode = 1 + IF @ExpertMode >= 1 BEGIN IF (@Debug = 1) BEGIN @@ -46682,7 +38133,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, END /* Server Info - SQL Re-Compilations/sec - CheckID 26 */ - IF @ExpertMode = 1 + IF @ExpertMode >= 1 BEGIN IF (@Debug = 1) BEGIN @@ -48031,7 +39482,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, ID, CAST(Details AS NVARCHAR(4000)); END; - ELSE IF @ExpertMode = 1 AND @OutputType <> 'NONE' AND @OutputResultSets LIKE N'%Findings%' + ELSE IF @ExpertMode >= 1 AND @OutputType <> 'NONE' AND @OutputResultSets LIKE N'%Findings%' BEGIN IF @SinceStartup = 0 SELECT r.[Priority] , @@ -48268,7 +39719,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, INNER JOIN #QueryStats qsFirst ON qsNow.[sql_handle] = qsFirst.[sql_handle] AND qsNow.statement_start_offset = qsFirst.statement_start_offset AND qsNow.statement_end_offset = qsFirst.statement_end_offset AND qsNow.plan_generation_num = qsFirst.plan_generation_num AND qsNow.plan_handle = qsFirst.plan_handle AND qsFirst.Pass = 1 WHERE qsNow.Pass = 2; END; - ELSE + ELSE IF @OutputResultSets LIKE N'%BlitzCache%' BEGIN SELECT 'Plan Cache' AS [Pattern], 'Plan cache not analyzed' AS [Finding], 'Use @CheckProcedureCache = 1 or run sp_BlitzCache for more analysis' AS [More Info], CONVERT(XML, @StockDetailsHeader + 'firstresponderkit.org' + @StockDetailsFooter) AS [Details]; END; diff --git a/Install-Core-Blitz-No-Query-Store.sql b/Install-Azure.sql similarity index 70% rename from Install-Core-Blitz-No-Query-Store.sql rename to Install-Azure.sql index eb2b1df80..ffe645fd3 100644 --- a/Install-Core-Blitz-No-Query-Store.sql +++ b/Install-Azure.sql @@ -1,12554 +1,4892 @@ -IF OBJECT_ID('dbo.sp_Blitz') IS NULL - EXEC ('CREATE PROCEDURE dbo.sp_Blitz AS RETURN 0;'); -GO - -ALTER PROCEDURE [dbo].[sp_Blitz] - @Help TINYINT = 0 , - @CheckUserDatabaseObjects TINYINT = 1 , - @CheckProcedureCache TINYINT = 0 , - @OutputType VARCHAR(20) = 'TABLE' , - @OutputProcedureCache TINYINT = 0 , - @CheckProcedureCacheFilter VARCHAR(10) = NULL , - @CheckServerInfo TINYINT = 0 , - @SkipChecksServer NVARCHAR(256) = NULL , - @SkipChecksDatabase NVARCHAR(256) = NULL , - @SkipChecksSchema NVARCHAR(256) = NULL , - @SkipChecksTable NVARCHAR(256) = NULL , - @IgnorePrioritiesBelow INT = NULL , - @IgnorePrioritiesAbove INT = NULL , - @OutputServerName NVARCHAR(256) = NULL , - @OutputDatabaseName NVARCHAR(256) = NULL , - @OutputSchemaName NVARCHAR(256) = NULL , - @OutputTableName NVARCHAR(256) = NULL , - @OutputXMLasNVARCHAR TINYINT = 0 , - @EmailRecipients VARCHAR(MAX) = NULL , - @EmailProfile sysname = NULL , - @SummaryMode TINYINT = 0 , - @BringThePain TINYINT = 0 , - @UsualDBOwner sysname = NULL , - @SkipBlockingChecks TINYINT = 1 , - @Debug TINYINT = 0 , - @Version VARCHAR(30) = NULL OUTPUT, - @VersionDate DATETIME = NULL OUTPUT, - @VersionCheckMode BIT = 0 -WITH RECOMPILE -AS - SET NOCOUNT ON; - SET STATISTICS XML OFF; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - - - SELECT @Version = '8.19', @VersionDate = '20240222'; - SET @OutputType = UPPER(@OutputType); +SET ANSI_NULLS ON; +SET QUOTED_IDENTIFIER ON - IF(@VersionCheckMode = 1) - BEGIN - RETURN; - END; +IF NOT EXISTS (SELECT * FROM sys.objects WHERE [object_id] = OBJECT_ID(N'[dbo].[sp_BlitzAnalysis]') AND [type] in (N'P', N'PC')) +BEGIN +EXEC dbo.sp_executesql @statement = N'CREATE PROCEDURE [dbo].[sp_BlitzAnalysis] AS' +END +GO - IF @Help = 1 - BEGIN - PRINT ' - /* - sp_Blitz from http://FirstResponderKit.org - - This script checks the health of your SQL Server and gives you a prioritized - to-do list of the most urgent things you should consider fixing. +ALTER PROCEDURE [dbo].[sp_BlitzAnalysis] ( +@Help TINYINT = 0, +@StartDate DATETIMEOFFSET(7) = NULL, +@EndDate DATETIMEOFFSET(7) = NULL, +@OutputDatabaseName NVARCHAR(256) = 'DBAtools', +@OutputSchemaName NVARCHAR(256) = N'dbo', +@OutputTableNameBlitzFirst NVARCHAR(256) = N'BlitzFirst', +@OutputTableNameFileStats NVARCHAR(256) = N'BlitzFirst_FileStats', +@OutputTableNamePerfmonStats NVARCHAR(256) = N'BlitzFirst_PerfmonStats', +@OutputTableNameWaitStats NVARCHAR(256) = N'BlitzFirst_WaitStats', +@OutputTableNameBlitzCache NVARCHAR(256) = N'BlitzCache', +@OutputTableNameBlitzWho NVARCHAR(256) = N'BlitzWho', +@Servername NVARCHAR(128) = @@SERVERNAME, +@Databasename NVARCHAR(128) = NULL, +@BlitzCacheSortorder NVARCHAR(20) = N'cpu', +@MaxBlitzFirstPriority INT = 249, +@ReadLatencyThreshold INT = 100, +@WriteLatencyThreshold INT = 100, +@WaitStatsTop TINYINT = 10, +@Version VARCHAR(30) = NULL OUTPUT, +@VersionDate DATETIME = NULL OUTPUT, +@VersionCheckMode BIT = 0, +@BringThePain BIT = 0, +@Maxdop INT = 1, +@Debug BIT = 0 +) +AS +SET NOCOUNT ON; +SET STATISTICS XML OFF; - To learn more, visit http://FirstResponderKit.org where you can download new - versions for free, watch training videos on how it works, get more info on - the findings, contribute your own code, and more. +SELECT @Version = '8.19', @VersionDate = '20240222'; - Known limitations of this version: - - Only Microsoft-supported versions of SQL Server. Sorry, 2005 and 2000. - - If a database name has a question mark in it, some tests will fail. Gotta - love that unsupported sp_MSforeachdb. - - If you have offline databases, sp_Blitz fails the first time you run it, - but does work the second time. (Hoo, boy, this will be fun to debug.) - - @OutputServerName will output QueryPlans as NVARCHAR(MAX) since Microsoft - has refused to support XML columns in Linked Server queries. The bug is now - 16 years old! *~ \o/ ~* +IF(@VersionCheckMode = 1) +BEGIN + RETURN; +END; - Unknown limitations of this version: - - None. (If we knew them, they would be known. Duh.) +IF (@Help = 1) +BEGIN + PRINT 'EXEC sp_BlitzAnalysis +@StartDate = NULL, /* Specify a datetime or NULL will get an hour ago */ +@EndDate = NULL, /* Specify a datetime or NULL will get an hour of data since @StartDate */ +@OutputDatabaseName = N''DBA'', /* Specify the database name where where we can find your logged blitz data */ +@OutputSchemaName = N''dbo'', /* Specify the schema */ +@OutputTableNameBlitzFirst = N''BlitzFirst'', /* Table name where you are storing sp_BlitzFirst output, Set to NULL to ignore */ +@OutputTableNameFileStats = N''BlitzFirst_FileStats'', /* Table name where you are storing sp_BlitzFirst filestats output, Set to NULL to ignore */ +@OutputTableNamePerfmonStats = N''BlitzFirst_PerfmonStats'', /* Table name where you are storing sp_BlitzFirst Perfmon output, Set to NULL to ignore */ +@OutputTableNameWaitStats = N''BlitzFirst_WaitStats'', /* Table name where you are storing sp_BlitzFirst Wait stats output, Set to NULL to ignore */ +@OutputTableNameBlitzCache = N''BlitzCache'', /* Table name where you are storing sp_BlitzCache output, Set to NULL to ignore */ +@OutputTableNameBlitzWho = N''BlitzWho'', /* Table name where you are storing sp_BlitzWho output, Set to NULL to ignore */ +@Databasename = NULL, /* Filters results for BlitzCache, FileStats (will also include tempdb), BlitzWho. Leave as NULL for all databases */ +@MaxBlitzFirstPriority = 249, /* Max priority to include in the results */ +@BlitzCacheSortorder = ''cpu'', /* Accepted values ''all'' ''cpu'' ''reads'' ''writes'' ''duration'' ''executions'' ''memory grant'' ''spills'' */ +@WaitStatsTop = 3, /* Controls the top for wait stats only */ +@Maxdop = 1, /* Control the degree of parallelism that the queries within this proc can use if they want to*/ +@Debug = 0; /* Show sp_BlitzAnalysis SQL commands in the messages tab as they execute */ - Changes - for the full list of improvements and fixes in this version, see: - https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/ +/* +Additional parameters: +@ReadLatencyThreshold INT /* Default: 100 - Sets the threshold in ms to compare against io_stall_read_average_ms in your filestats table */ +@WriteLatencyThreshold INT /* Default: 100 - Sets the threshold in ms to compare against io_stall_write_average_ms in your filestats table */ +@BringThePain BIT /* Default: 0 - If you are getting more than 4 hours of data with blitzcachesortorder set to ''all'' you will need to set BringThePain to 1 */ +*/'; + RETURN; +END - Parameter explanations: +/* Declare all local variables required */ +DECLARE @FullOutputTableNameBlitzFirst NVARCHAR(1000); +DECLARE @FullOutputTableNameFileStats NVARCHAR(1000); +DECLARE @FullOutputTableNamePerfmonStats NVARCHAR(1000); +DECLARE @FullOutputTableNameWaitStats NVARCHAR(1000); +DECLARE @FullOutputTableNameBlitzCache NVARCHAR(1000); +DECLARE @FullOutputTableNameBlitzWho NVARCHAR(1000); +DECLARE @Sql NVARCHAR(MAX); +DECLARE @NewLine NVARCHAR(2) = CHAR(13); +DECLARE @IncludeMemoryGrants BIT; +DECLARE @IncludeSpills BIT; - @CheckUserDatabaseObjects 1=review user databases for triggers, heaps, etc. Takes more time for more databases and objects. - @CheckServerInfo 1=show server info like CPUs, memory, virtualization - @CheckProcedureCache 1=top 20-50 resource-intensive cache plans and analyze them for common performance issues. - @OutputProcedureCache 1=output the top 20-50 resource-intensive plans even if they did not trigger an alarm - @CheckProcedureCacheFilter ''CPU'' | ''Reads'' | ''Duration'' | ''ExecCount'' - @OutputType ''TABLE''=table | ''COUNT''=row with number found | ''MARKDOWN''=bulleted list (including server info, excluding security findings) | ''SCHEMA''=version and field list | ''XML'' =table output as XML | ''NONE'' = none - @IgnorePrioritiesBelow 50=ignore priorities below 50 - @IgnorePrioritiesAbove 50=ignore priorities above 50 - @Debug 0=silent (Default) | 1=messages per step | 2=outputs dynamic queries - For the rest of the parameters, see https://www.BrentOzar.com/blitz/documentation for details. +/* Validate the database name */ +IF (DB_ID(@OutputDatabaseName) IS NULL) +BEGIN + RAISERROR('Invalid database name provided for parameter @OutputDatabaseName: %s',11,0,@OutputDatabaseName); + RETURN; +END - MIT License - - Copyright for portions of sp_Blitz are held by Microsoft as part of project - tigertoolbox and are provided under the MIT license: - https://github.com/Microsoft/tigertoolbox - - All other copyrights for sp_Blitz are held by Brent Ozar Unlimited. +/* Set fully qualified table names */ +SET @FullOutputTableNameBlitzFirst = QUOTENAME(@OutputDatabaseName)+N'.'+QUOTENAME(@OutputSchemaName)+N'.'+QUOTENAME(@OutputTableNameBlitzFirst); +SET @FullOutputTableNameFileStats = QUOTENAME(@OutputDatabaseName)+N'.'+QUOTENAME(@OutputSchemaName)+N'.'+QUOTENAME(@OutputTableNameFileStats+N'_Deltas'); +SET @FullOutputTableNamePerfmonStats = QUOTENAME(@OutputDatabaseName)+N'.'+QUOTENAME(@OutputSchemaName)+N'.'+QUOTENAME(@OutputTableNamePerfmonStats+N'_Actuals'); +SET @FullOutputTableNameWaitStats = QUOTENAME(@OutputDatabaseName)+N'.'+QUOTENAME(@OutputSchemaName)+N'.'+QUOTENAME(@OutputTableNameWaitStats+N'_Deltas'); +SET @FullOutputTableNameBlitzCache = QUOTENAME(@OutputDatabaseName)+N'.'+QUOTENAME(@OutputSchemaName)+N'.'+QUOTENAME(@OutputTableNameBlitzCache); +SET @FullOutputTableNameBlitzWho = QUOTENAME(@OutputDatabaseName)+N'.'+QUOTENAME(@OutputSchemaName)+N'.'+QUOTENAME(@OutputTableNameBlitzWho+N'_Deltas'); - Copyright (c) Brent Ozar Unlimited +IF OBJECT_ID('tempdb.dbo.#BlitzFirstCounts') IS NOT NULL +BEGIN + DROP TABLE #BlitzFirstCounts; +END - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: +CREATE TABLE #BlitzFirstCounts ( + [Priority] TINYINT NOT NULL, + [FindingsGroup] VARCHAR(50) NOT NULL, + [Finding] VARCHAR(200) NOT NULL, + [TotalOccurrences] INT NULL, + [FirstOccurrence] DATETIMEOFFSET(7) NULL, + [LastOccurrence] DATETIMEOFFSET(7) NULL +); - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. +/* Validate variables and set defaults as required */ +IF (@BlitzCacheSortorder IS NULL) +BEGIN + SET @BlitzCacheSortorder = N'cpu'; +END - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE. +SET @BlitzCacheSortorder = LOWER(@BlitzCacheSortorder); - */'; +IF (@OutputTableNameBlitzCache IS NOT NULL AND @BlitzCacheSortorder NOT IN (N'all',N'cpu',N'reads',N'writes',N'duration',N'executions',N'memory grant',N'spills')) +BEGIN + RAISERROR('Invalid sort option specified for @BlitzCacheSortorder, supported values are ''all'', ''cpu'', ''reads'', ''writes'', ''duration'', ''executions'', ''memory grant'', ''spills''',11,0) WITH NOWAIT; RETURN; - END; /* @Help = 1 */ +END - ELSE IF @OutputType = 'SCHEMA' - BEGIN - SELECT FieldList = '[Priority] TINYINT, [FindingsGroup] VARCHAR(50), [Finding] VARCHAR(200), [DatabaseName] NVARCHAR(128), [URL] VARCHAR(200), [Details] NVARCHAR(4000), [QueryPlan] NVARCHAR(MAX), [QueryPlanFiltered] NVARCHAR(MAX), [CheckID] INT'; +/* Set @Maxdop to 1 if NULL was passed in */ +IF (@Maxdop IS NULL) +BEGIN + SET @Maxdop = 1; +END - END;/* IF @OutputType = 'SCHEMA' */ - ELSE - BEGIN +/* iF @Maxdop is set higher than the core count just set it to 0 */ +IF (@Maxdop > (SELECT CAST(cpu_count AS INT) FROM sys.dm_os_sys_info)) +BEGIN + SET @Maxdop = 0; +END - DECLARE @StringToExecute NVARCHAR(4000) - ,@curr_tracefilename NVARCHAR(500) - ,@base_tracefilename NVARCHAR(500) - ,@indx int - ,@query_result_separator CHAR(1) - ,@EmailSubject NVARCHAR(255) - ,@EmailBody NVARCHAR(MAX) - ,@EmailAttachmentFilename NVARCHAR(255) - ,@ProductVersion NVARCHAR(128) - ,@ProductVersionMajor DECIMAL(10,2) - ,@ProductVersionMinor DECIMAL(10,2) - ,@CurrentName NVARCHAR(128) - ,@CurrentDefaultValue NVARCHAR(200) - ,@CurrentCheckID INT - ,@CurrentPriority INT - ,@CurrentFinding VARCHAR(200) - ,@CurrentURL VARCHAR(200) - ,@CurrentDetails NVARCHAR(4000) - ,@MsSinceWaitsCleared DECIMAL(38,0) - ,@CpuMsSinceWaitsCleared DECIMAL(38,0) - ,@ResultText NVARCHAR(MAX) - ,@crlf NVARCHAR(2) - ,@Processors int - ,@NUMANodes int - ,@MinServerMemory bigint - ,@MaxServerMemory bigint - ,@ColumnStoreIndexesInUse bit - ,@TraceFileIssue bit - -- Flag for Windows OS to help with Linux support - ,@IsWindowsOperatingSystem BIT - ,@DaysUptime NUMERIC(23,2) - /* For First Responder Kit consistency check:*/ - ,@spBlitzFullName VARCHAR(1024) - ,@BlitzIsOutdatedComparedToOthers BIT - ,@tsql NVARCHAR(MAX) - ,@VersionCheckModeExistsTSQL NVARCHAR(MAX) - ,@BlitzProcDbName VARCHAR(256) - ,@ExecRet INT - ,@InnerExecRet INT - ,@TmpCnt INT - ,@PreviousComponentName VARCHAR(256) - ,@PreviousComponentFullPath VARCHAR(1024) - ,@CurrentStatementId INT - ,@CurrentComponentSchema VARCHAR(256) - ,@CurrentComponentName VARCHAR(256) - ,@CurrentComponentType VARCHAR(256) - ,@CurrentComponentVersionDate DATETIME2 - ,@CurrentComponentFullName VARCHAR(1024) - ,@CurrentComponentMandatory BIT - ,@MaximumVersionDate DATETIME - ,@StatementCheckName VARCHAR(256) - ,@StatementOutputsCounter BIT - ,@OutputCounterExpectedValue INT - ,@StatementOutputsExecRet BIT - ,@StatementOutputsDateTime BIT - ,@CurrentComponentMandatoryCheckOK BIT - ,@CurrentComponentVersionCheckModeOK BIT - ,@canExitLoop BIT - ,@frkIsConsistent BIT - ,@NeedToTurnNumericRoundabortBackOn BIT - ,@sa bit = 1 - ,@SUSER_NAME sysname = SUSER_SNAME() - ,@SkipDBCC bit = 0 - ,@SkipTrace bit = 0 - ,@SkipXPRegRead bit = 0 - ,@SkipXPFixedDrives bit = 0 - ,@SkipXPCMDShell bit = 0 - ,@SkipMaster bit = 0 - ,@SkipMSDB_objs bit = 0 - ,@SkipMSDB_jobs bit = 0 - ,@SkipModel bit = 0 - ,@SkipTempDB bit = 0 - ,@SkipValidateLogins bit = 0 - ,@SkipGetAlertInfo bit = 0 - - DECLARE - @db_perms table - ( - database_name sysname, - permission_name sysname - ); +/* We need to check if your SQL version has memory grant and spills columns in sys.dm_exec_query_stats */ +SELECT @IncludeMemoryGrants = + CASE + WHEN (EXISTS(SELECT * FROM sys.all_columns WHERE [object_id] = OBJECT_ID('sys.dm_exec_query_stats') AND name = 'max_grant_kb')) THEN 1 + ELSE 0 + END; - INSERT - @db_perms - ( - database_name, - permission_name - ) - SELECT - database_name = - DB_NAME(d.database_id), - fmp.permission_name - FROM sys.databases AS d - CROSS APPLY fn_my_permissions(d.name, 'DATABASE') AS fmp - WHERE fmp.permission_name = N'SELECT'; /*Databases where we don't have read permissions*/ - - /* End of declarations for First Responder Kit consistency check:*/ - ; +SELECT @IncludeSpills = + CASE + WHEN (EXISTS(SELECT * FROM sys.all_columns WHERE [object_id] = OBJECT_ID('sys.dm_exec_query_stats') AND name = 'max_spills')) THEN 1 + ELSE 0 + END; - /* Create temp table for check 73 */ - IF OBJECT_ID('tempdb..#AlertInfo') IS NOT NULL - EXEC sp_executesql N'DROP TABLE #AlertInfo;'; - CREATE TABLE #AlertInfo - ( - FailSafeOperator NVARCHAR(255) , - NotificationMethod INT , - ForwardingServer NVARCHAR(255) , - ForwardingSeverity INT , - PagerToTemplate NVARCHAR(255) , - PagerCCTemplate NVARCHAR(255) , - PagerSubjectTemplate NVARCHAR(255) , - PagerSendSubjectOnly NVARCHAR(255) , - ForwardAlways INT - ); +IF (@StartDate IS NULL) +BEGIN + RAISERROR('Setting @StartDate to: 1 hour ago',0,0) WITH NOWAIT; + /* Set StartDate to be an hour ago */ + SET @StartDate = DATEADD(HOUR,-1,SYSDATETIMEOFFSET()); - /* Create temp table for check 2301 */ - IF OBJECT_ID('tempdb..#InvalidLogins') IS NOT NULL - EXEC sp_executesql N'DROP TABLE #InvalidLogins;'; - - CREATE TABLE #InvalidLogins - ( - LoginSID varbinary(85), - LoginName VARCHAR(256) - ); + IF (@EndDate IS NULL) + BEGIN + RAISERROR('Setting @EndDate to: Now',0,0) WITH NOWAIT; + /* Get data right up to now */ + SET @EndDate = SYSDATETIMEOFFSET(); + END +END - /*Starting permissions checks here, but only if we're not a sysadmin*/ - IF - ( - SELECT - sa = - ISNULL - ( - IS_SRVROLEMEMBER(N'sysadmin'), - 0 - ) - ) = 0 - BEGIN - IF @Debug IN (1, 2) RAISERROR('User not SA, checking permissions', 0, 1) WITH NOWAIT; - - SET @sa = 0; /*Setting this to 0 to skip DBCC COMMANDS*/ - - IF NOT EXISTS - ( - SELECT - 1/0 - FROM sys.fn_my_permissions(NULL, NULL) AS fmp - WHERE fmp.permission_name = N'VIEW SERVER STATE' - ) - BEGIN - RAISERROR('The user %s does not have VIEW SERVER STATE permissions.', 0, 11, @SUSER_NAME) WITH NOWAIT; - RETURN; - END; /*If we don't have this, we can't do anything at all.*/ +IF (@EndDate IS NULL) +BEGIN + /* Default to an hour of data or SYSDATETIMEOFFSET() if now is earlier than the hour added to @StartDate */ + IF(DATEADD(HOUR,1,@StartDate) < SYSDATETIMEOFFSET()) + BEGIN + RAISERROR('@EndDate was NULL - Setting to return 1 hour of information, if you want more then set @EndDate aswell',0,0) WITH NOWAIT; + SET @EndDate = DATEADD(HOUR,1,@StartDate); + END + ELSE + BEGIN + RAISERROR('@EndDate was NULL - Setting to SYSDATETIMEOFFSET()',0,0) WITH NOWAIT; + SET @EndDate = SYSDATETIMEOFFSET(); + END +END - IF NOT EXISTS - ( - SELECT - 1/0 - FROM fn_my_permissions(N'sys.traces', N'OBJECT') AS fmp - WHERE fmp.permission_name = N'ALTER' - ) - BEGIN - SET @SkipTrace = 1; - END; /*We need this permission to execute trace stuff, apparently*/ +/* Default to dbo schema if NULL is passed in */ +IF (@OutputSchemaName IS NULL) +BEGIN + SET @OutputSchemaName = 'dbo'; +END - IF NOT EXISTS - ( - SELECT - 1/0 - FROM fn_my_permissions(N'xp_fixeddrives', N'OBJECT') AS fmp - WHERE fmp.permission_name = N'EXECUTE' - ) - BEGIN - SET @SkipXPFixedDrives = 1; - END; /*Need execute on xp_fixeddrives*/ - - IF NOT EXISTS - ( - SELECT - 1/0 - FROM fn_my_permissions(N'xp_cmdshell', N'OBJECT') AS fmp - WHERE fmp.permission_name = N'EXECUTE' - ) - BEGIN - SET @SkipXPCMDShell = 1; - END; /*Need execute on xp_cmdshell*/ - - IF ISNULL(@SkipValidateLogins, 0) != 1 /*If @SkipValidateLogins hasn't been set to 1 by the caller*/ - BEGIN - BEGIN TRY - /* Try to fill the table for check 2301 */ - INSERT INTO #InvalidLogins - ( - [LoginSID] - ,[LoginName] - ) - EXEC sp_validatelogins; - - SET @SkipValidateLogins = 0; /*We can execute sp_validatelogins*/ - END TRY - BEGIN CATCH - SET @SkipValidateLogins = 1; /*We have don't have execute rights or sp_validatelogins throws an error so skip it*/ - END CATCH; - END; /*Need execute on sp_validatelogins*/ - - IF ISNULL(@SkipGetAlertInfo, 0) != 1 /*If @SkipGetAlertInfo hasn't been set to 1 by the caller*/ - BEGIN - BEGIN TRY - /* Try to fill the table for check 73 */ - INSERT INTO #AlertInfo - EXEC [master].[dbo].[sp_MSgetalertinfo] @includeaddresses = 0; - - SET @SkipGetAlertInfo = 0; /*We can execute sp_MSgetalertinfo*/ - END TRY - BEGIN CATCH - SET @SkipGetAlertInfo = 1; /*We have don't have execute rights or sp_MSgetalertinfo throws an error so skip it*/ - END CATCH; - END; /*Need execute on sp_MSgetalertinfo*/ - - IF ISNULL(@SkipModel, 0) != 1 /*If @SkipModel hasn't been set to 1 by the caller*/ - BEGIN - IF EXISTS - ( - SELECT 1/0 - FROM @db_perms - WHERE database_name = N'model' - ) - BEGIN - BEGIN TRY - IF EXISTS - ( - SELECT 1/0 - FROM model.sys.objects - ) - BEGIN - SET @SkipModel = 0; /*We have read permissions in the model database, and can view the objects*/ - END; - END TRY - BEGIN CATCH - SET @SkipModel = 1; /*We have read permissions in the model database ... oh wait we got tricked, we can't view the objects*/ - END CATCH; - END; - ELSE - BEGIN - SET @SkipModel = 1; /*We don't have read permissions in the model database*/ - END; - END; - - IF ISNULL(@SkipMSDB_objs, 0) != 1 /*If @SkipMSDB_objs hasn't been set to 1 by the caller*/ - BEGIN - IF EXISTS - ( - SELECT 1/0 - FROM @db_perms - WHERE database_name = N'msdb' - ) - BEGIN - BEGIN TRY - IF EXISTS - ( - SELECT 1/0 - FROM msdb.sys.objects - ) - BEGIN - SET @SkipMSDB_objs = 0; /*We have read permissions in the msdb database, and can view the objects*/ - END; - END TRY - BEGIN CATCH - SET @SkipMSDB_objs = 1; /*We have read permissions in the msdb database ... oh wait we got tricked, we can't view the objects*/ - END CATCH; - END; - ELSE - BEGIN - SET @SkipMSDB_objs = 1; /*We don't have read permissions in the msdb database*/ - END; - END; - - IF ISNULL(@SkipMSDB_jobs, 0) != 1 /*If @SkipMSDB_jobs hasn't been set to 1 by the caller*/ - BEGIN - IF EXISTS - ( - SELECT 1/0 - FROM @db_perms - WHERE database_name = N'msdb' - ) - BEGIN - BEGIN TRY - IF EXISTS - ( - SELECT 1/0 - FROM msdb.dbo.sysjobs - ) - BEGIN - SET @SkipMSDB_jobs = 0; /*We have read permissions in the msdb database, and can view the objects*/ - END; - END TRY - BEGIN CATCH - SET @SkipMSDB_jobs = 1; /*We have read permissions in the msdb database ... oh wait we got tricked, we can't view the objects*/ - END CATCH; - END; - ELSE - BEGIN - SET @SkipMSDB_jobs = 1; /*We don't have read permissions in the msdb database*/ - END; - END; - END; - - SET @crlf = NCHAR(13) + NCHAR(10); - SET @ResultText = 'sp_Blitz Results: ' + @crlf; - - /* Last startup */ - SELECT @DaysUptime = CAST(DATEDIFF(HOUR, create_date, GETDATE()) / 24. AS NUMERIC(23, 2)) - FROM sys.databases - WHERE database_id = 2; - - IF @DaysUptime = 0 - SET @DaysUptime = .01; - - /* - Set the session state of Numeric_RoundAbort to off if any databases have Numeric Round-Abort enabled. - Stops arithmetic overflow errors during data conversion. See Github issue #2302 for more info. - */ - IF ( (8192 & @@OPTIONS) = 8192 ) /* Numeric RoundAbort is currently on, so we may need to turn it off temporarily */ - BEGIN - IF EXISTS (SELECT 1 - FROM sys.databases - WHERE is_numeric_roundabort_on = 1) /* A database has it turned on */ - BEGIN - SET @NeedToTurnNumericRoundabortBackOn = 1; - SET NUMERIC_ROUNDABORT OFF; - END; - END; - - - - - /* - --TOURSTOP01-- - See https://www.BrentOzar.com/go/blitztour for a guided tour. - - We start by creating #BlitzResults. It's a temp table that will store all of - the results from our checks. Throughout the rest of this stored procedure, - we're running a series of checks looking for dangerous things inside the SQL - Server. When we find a problem, we insert rows into #BlitzResults. At the - end, we return these results to the end user. - - #BlitzResults has a CheckID field, but there's no Check table. As we do - checks, we insert data into this table, and we manually put in the CheckID. - For a list of checks, visit http://FirstResponderKit.org. - */ - IF OBJECT_ID('tempdb..#BlitzResults') IS NOT NULL - DROP TABLE #BlitzResults; - CREATE TABLE #BlitzResults - ( - ID INT IDENTITY(1, 1) , - CheckID INT , - DatabaseName NVARCHAR(128) , - Priority TINYINT , - FindingsGroup VARCHAR(50) , - Finding VARCHAR(200) , - URL VARCHAR(200) , - Details NVARCHAR(4000) , - QueryPlan [XML] NULL , - QueryPlanFiltered [NVARCHAR](MAX) NULL - ); - - IF OBJECT_ID('tempdb..#TemporaryDatabaseResults') IS NOT NULL - DROP TABLE #TemporaryDatabaseResults; - CREATE TABLE #TemporaryDatabaseResults - ( - DatabaseName NVARCHAR(128) , - Finding NVARCHAR(128) - ); +/* Prompt the user for @BringThePain = 1 if they are searching a timeframe greater than 4 hours and they are using BlitzCacheSortorder = 'all' */ +IF(@BlitzCacheSortorder = 'all' AND DATEDIFF(HOUR,@StartDate,@EndDate) > 4 AND @BringThePain = 0) +BEGIN + RAISERROR('Wow! hold up now, are you sure you wanna do this? Are sure you want to query over 4 hours of data with @BlitzCacheSortorder set to ''all''? IF you do then set @BringThePain = 1 but I gotta warn you this might hurt a bit!',11,1) WITH NOWAIT; + RETURN; +END - /* First Responder Kit consistency (temporary tables) */ - - IF(OBJECT_ID('tempdb..#FRKObjects') IS NOT NULL) - BEGIN - EXEC sp_executesql N'DROP TABLE #FRKObjects;'; - END; +/* Output report window information */ +SELECT + @Servername AS [ServerToReportOn], + CAST(1 AS NVARCHAR(20)) + N' - '+ CAST(@MaxBlitzFirstPriority AS NVARCHAR(20)) AS [PrioritesToInclude], + @StartDate AS [StartDatetime], + @EndDate AS [EndDatetime];; - -- this one represents FRK objects - CREATE TABLE #FRKObjects ( - DatabaseName VARCHAR(256) NOT NULL, - ObjectSchemaName VARCHAR(256) NULL, - ObjectName VARCHAR(256) NOT NULL, - ObjectType VARCHAR(256) NOT NULL, - MandatoryComponent BIT NOT NULL - ); - - - IF(OBJECT_ID('tempdb..#StatementsToRun4FRKVersionCheck') IS NOT NULL) - BEGIN - EXEC sp_executesql N'DROP TABLE #StatementsToRun4FRKVersionCheck;'; - END; +/* BlitzFirst data */ +SET @Sql = N' +INSERT INTO #BlitzFirstCounts ([Priority],[FindingsGroup],[Finding],[TotalOccurrences],[FirstOccurrence],[LastOccurrence]) +SELECT +[Priority], +[FindingsGroup], +[Finding], +COUNT(*) AS [TotalOccurrences], +MIN(CheckDate) AS [FirstOccurrence], +MAX(CheckDate) AS [LastOccurrence] +FROM '+@FullOutputTableNameBlitzFirst+N' +WHERE [ServerName] = @Servername +AND [Priority] BETWEEN 1 AND @MaxBlitzFirstPriority +AND CheckDate BETWEEN @StartDate AND @EndDate +AND [CheckID] > -1 +GROUP BY [Priority],[FindingsGroup],[Finding]; - -- This one will contain the statements to be executed - -- order: 1- Mandatory, 2- VersionCheckMode, 3- VersionCheck +IF EXISTS(SELECT 1 FROM #BlitzFirstCounts) +BEGIN + SELECT + [Priority], + [FindingsGroup], + [Finding], + [TotalOccurrences], + [FirstOccurrence], + [LastOccurrence] + FROM #BlitzFirstCounts + ORDER BY [Priority] ASC,[TotalOccurrences] DESC; +END +ELSE +BEGIN + SELECT N''No findings with a priority between 1 and ''+CAST(@MaxBlitzFirstPriority AS NVARCHAR(10))+N'' found for this period''; +END +SELECT + [ServerName] +,[CheckDate] +,[CheckID] +,[Priority] +,[Finding] +,[URL] +,[Details] +,[HowToStopIt] +,[QueryPlan] +,[QueryText] +FROM '+@FullOutputTableNameBlitzFirst+N' Findings +WHERE [ServerName] = @Servername +AND [Priority] BETWEEN 1 AND @MaxBlitzFirstPriority +AND [CheckDate] BETWEEN @StartDate AND @EndDate +AND [CheckID] > -1 +ORDER BY CheckDate ASC,[Priority] ASC +OPTION (RECOMPILE, MAXDOP '+CAST(@Maxdop AS NVARCHAR(2))+N');'; - CREATE TABLE #StatementsToRun4FRKVersionCheck ( - StatementId INT IDENTITY(1,1), - CheckName VARCHAR(256), - SubjectName VARCHAR(256), - SubjectFullPath VARCHAR(1024), - StatementText NVARCHAR(MAX), - StatementOutputsCounter BIT, - OutputCounterExpectedValue INT, - StatementOutputsExecRet BIT, - StatementOutputsDateTime BIT - ); - /* End of First Responder Kit consistency (temporary tables) */ - - - /* - You can build your own table with a list of checks to skip. For example, you - might have some databases that you don't care about, or some checks you don't - want to run. Then, when you run sp_Blitz, you can specify these parameters: - @SkipChecksDatabase = 'DBAtools', - @SkipChecksSchema = 'dbo', - @SkipChecksTable = 'BlitzChecksToSkip' - Pass in the database, schema, and table that contains the list of checks you - want to skip. This part of the code checks those parameters, gets the list, - and then saves those in a temp table. As we run each check, we'll see if we - need to skip it. - */ - /* --TOURSTOP07-- */ - IF OBJECT_ID('tempdb..#SkipChecks') IS NOT NULL - DROP TABLE #SkipChecks; - CREATE TABLE #SkipChecks - ( - DatabaseName NVARCHAR(128) , - CheckID INT , - ServerName NVARCHAR(128) - ); - CREATE CLUSTERED INDEX IX_CheckID_DatabaseName ON #SkipChecks(CheckID, DatabaseName); +RAISERROR('Getting BlitzFirst info from %s',0,0,@FullOutputTableNameBlitzFirst) WITH NOWAIT; - INSERT INTO #SkipChecks - (DatabaseName) - SELECT - DB_NAME(d.database_id) - FROM sys.databases AS d - WHERE (DB_NAME(d.database_id) LIKE 'rdsadmin%' - OR LOWER(d.name) IN ('dbatools', 'dbadmin', 'dbmaintenance')) - OPTION(RECOMPILE); +IF (@Debug = 1) +BEGIN + PRINT @Sql; +END - /*Skip checks for database where we don't have read permissions*/ - INSERT INTO - #SkipChecks - ( - DatabaseName - ) - SELECT - DB_NAME(d.database_id) - FROM sys.databases AS d - WHERE NOT EXISTS - ( - SELECT - 1/0 - FROM @db_perms AS dp - WHERE dp.database_name = DB_NAME(d.database_id) - ); +IF (OBJECT_ID(@FullOutputTableNameBlitzFirst) IS NULL) +BEGIN + IF (@OutputTableNameBlitzFirst IS NULL) + BEGIN + RAISERROR('BlitzFirst data skipped',10,0); + SELECT N'Skipped logged BlitzFirst data as NULL was passed to parameter @OutputTableNameBlitzFirst'; + END + ELSE + BEGIN + RAISERROR('Table provided for BlitzFirst data: %s does not exist',10,0,@FullOutputTableNameBlitzFirst); + SELECT N'No BlitzFirst data available as the table cannot be found'; + END - /*Skip individial checks where we don't have permissions*/ - INSERT #SkipChecks (DatabaseName, CheckID, ServerName) - SELECT - v.* - FROM (VALUES(NULL, 29, NULL)) AS v (DatabaseName, CheckID, ServerName) /*Looks for user tables in model*/ - WHERE @SkipModel = 1; +END +ELSE /* Table exists then run the query */ +BEGIN + EXEC sp_executesql @Sql, + N'@StartDate DATETIMEOFFSET(7), + @EndDate DATETIMEOFFSET(7), + @Servername NVARCHAR(128), + @MaxBlitzFirstPriority INT', + @StartDate=@StartDate, + @EndDate=@EndDate, + @Servername=@Servername, + @MaxBlitzFirstPriority = @MaxBlitzFirstPriority; +END - INSERT #SkipChecks (DatabaseName, CheckID, ServerName) - SELECT - v.* - FROM (VALUES(NULL, 28, NULL)) AS v (DatabaseName, CheckID, ServerName) /*Tables in the MSDB Database*/ - WHERE @SkipMSDB_objs = 1; +/* Blitz WaitStats data */ +SET @Sql = N'SELECT +[ServerName], +[CheckDate], +[wait_type], +[WaitsRank], +[WaitCategory], +[Ignorable], +[ElapsedSeconds], +[wait_time_ms_delta], +[wait_time_minutes_delta], +[wait_time_minutes_per_minute], +[signal_wait_time_ms_delta], +[waiting_tasks_count_delta], +ISNULL((CAST([wait_time_ms_delta] AS DECIMAL(38,2))/NULLIF(CAST([waiting_tasks_count_delta] AS DECIMAL(38,2)),0)),0) AS [wait_time_ms_per_wait] +FROM +( + SELECT + [ServerName], + [CheckDate], + [wait_type], + [WaitCategory], + [Ignorable], + [ElapsedSeconds], + [wait_time_ms_delta], + [wait_time_minutes_delta], + [wait_time_minutes_per_minute], + [signal_wait_time_ms_delta], + [waiting_tasks_count_delta], + ROW_NUMBER() OVER(PARTITION BY [CheckDate] ORDER BY [CheckDate] ASC,[wait_time_ms_delta] DESC) AS [WaitsRank] + FROM '+@FullOutputTableNameWaitStats+N' AS [Waits] + WHERE [ServerName] = @Servername + AND [CheckDate] BETWEEN @StartDate AND @EndDate +) TopWaits +WHERE [WaitsRank] <= @WaitStatsTop +ORDER BY +[CheckDate] ASC, +[wait_time_ms_delta] DESC +OPTION(RECOMPILE, MAXDOP '+CAST(@Maxdop AS NVARCHAR(2))+N');' - INSERT #SkipChecks (DatabaseName, CheckID, ServerName) - SELECT - v.* - FROM (VALUES - /*sysjobs checks*/ - (NULL, 6, NULL), /*Jobs Owned By Users*/ - (NULL, 57, NULL), /*SQL Agent Job Runs at Startup*/ - (NULL, 79, NULL), /*Shrink Database Job*/ - (NULL, 94, NULL), /*Agent Jobs Without Failure Emails*/ - (NULL, 123, NULL), /*Agent Jobs Starting Simultaneously*/ - (NULL, 180, NULL), /*Shrink Database Step In Maintenance Plan*/ - (NULL, 181, NULL), /*Repetitive Maintenance Tasks*/ - - /*sysalerts checks*/ - (NULL, 30, NULL), /*Not All Alerts Configured*/ - (NULL, 59, NULL), /*Alerts Configured without Follow Up*/ - (NULL, 61, NULL), /*No Alerts for Sev 19-25*/ - (NULL, 96, NULL), /*No Alerts for Corruption*/ - (NULL, 98, NULL), /*Alerts Disabled*/ - (NULL, 219, NULL), /*Alerts Without Event Descriptions*/ - - /*sysoperators*/ - (NULL, 31, NULL) /*No Operators Configured/Enabled*/ - ) AS v (DatabaseName, CheckID, ServerName) - WHERE @SkipMSDB_jobs = 1; - - INSERT #SkipChecks (DatabaseName, CheckID, ServerName) - SELECT - v.* - FROM (VALUES(NULL, 68, NULL)) AS v (DatabaseName, CheckID, ServerName) /*DBCC command*/ - WHERE @sa = 0; +RAISERROR('Getting wait stats info from %s',0,0,@FullOutputTableNameWaitStats) WITH NOWAIT; - INSERT #SkipChecks (DatabaseName, CheckID, ServerName) - SELECT - v.* - FROM (VALUES(NULL, 69, NULL)) AS v (DatabaseName, CheckID, ServerName) /*DBCC command*/ - WHERE @sa = 0; +IF (@Debug = 1) +BEGIN + PRINT @Sql; +END - INSERT #SkipChecks (DatabaseName, CheckID, ServerName) - SELECT - v.* - FROM (VALUES(NULL, 92, NULL)) AS v (DatabaseName, CheckID, ServerName) /*xp_fixeddrives*/ - WHERE @SkipXPFixedDrives = 1; +IF (OBJECT_ID(@FullOutputTableNameWaitStats) IS NULL) +BEGIN + IF (@OutputTableNameWaitStats IS NULL) + BEGIN + RAISERROR('Wait stats data skipped',10,0); + SELECT N'Skipped logged wait stats data as NULL was passed to parameter @OutputTableNameWaitStats'; + END + ELSE + BEGIN + RAISERROR('Table provided for wait stats data: %s does not exist',10,0,@FullOutputTableNameWaitStats); + SELECT N'No wait stats data available as the table cannot be found'; + END +END +ELSE /* Table exists then run the query */ +BEGIN + EXEC sp_executesql @Sql, + N'@StartDate DATETIMEOFFSET(7), + @EndDate DATETIMEOFFSET(7), + @Servername NVARCHAR(128), + @WaitStatsTop TINYINT', + @StartDate=@StartDate, + @EndDate=@EndDate, + @Servername=@Servername, + @WaitStatsTop=@WaitStatsTop; +END - INSERT #SkipChecks (DatabaseName, CheckID, ServerName) - SELECT - v.* - FROM (VALUES(NULL, 106, NULL)) AS v (DatabaseName, CheckID, ServerName) /*alter trace*/ - WHERE @SkipTrace = 1; - - INSERT #SkipChecks (DatabaseName, CheckID, ServerName) - SELECT - v.* - FROM (VALUES(NULL, 211, NULL)) AS v (DatabaseName, CheckID, ServerName) /*xp_regread*/ - WHERE @sa = 0; - - INSERT #SkipChecks (DatabaseName, CheckID, ServerName) - SELECT - v.* - FROM (VALUES(NULL, 212, NULL)) AS v (DatabaseName, CheckID, ServerName) /*xp_regread*/ - WHERE @SkipXPCMDShell = 1; - - INSERT #SkipChecks (DatabaseName, CheckID, ServerName) - SELECT - v.* - FROM (VALUES(NULL, 2301, NULL)) AS v (DatabaseName, CheckID, ServerName) /*sp_validatelogins*/ - WHERE @SkipValidateLogins = 1; - - INSERT #SkipChecks (DatabaseName, CheckID, ServerName) - SELECT - v.* - FROM (VALUES(NULL, 73, NULL)) AS v (DatabaseName, CheckID, ServerName) /*sp_validatelogins*/ - WHERE @SkipGetAlertInfo = 1; - - IF @sa = 0 - BEGIN - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 223 AS CheckID , - 0 AS Priority , - 'Informational' AS FindingsGroup , - 'Some Checks Skipped' AS Finding , - '' AS URL , - 'User ''' + @SUSER_NAME + ''' is not part of the sysadmin role, so we skipped some checks that are not possible due to lack of permissions.' AS Details; - END; - /*End of SkipsChecks added due to permissions*/ - - IF @SkipChecksTable IS NOT NULL - AND @SkipChecksSchema IS NOT NULL - AND @SkipChecksDatabase IS NOT NULL - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Inserting SkipChecks', 0, 1) WITH NOWAIT; - - SET @StringToExecute = N'INSERT INTO #SkipChecks(DatabaseName, CheckID, ServerName ) - SELECT DISTINCT DatabaseName, CheckID, ServerName - FROM ' - IF LTRIM(RTRIM(@SkipChecksServer)) <> '' - BEGIN - SET @StringToExecute += QUOTENAME(@SkipChecksServer) + N'.'; - END - SET @StringToExecute += QUOTENAME(@SkipChecksDatabase) + N'.' + QUOTENAME(@SkipChecksSchema) + N'.' + QUOTENAME(@SkipChecksTable) - + N' WHERE ServerName IS NULL OR ServerName = CAST(SERVERPROPERTY(''ServerName'') AS NVARCHAR(128)) OPTION (RECOMPILE);'; - EXEC(@StringToExecute); - END; - - -- Flag for Windows OS to help with Linux support - IF EXISTS ( SELECT 1 - FROM sys.all_objects - WHERE name = 'dm_os_host_info' ) - BEGIN - SELECT @IsWindowsOperatingSystem = CASE WHEN host_platform = 'Windows' THEN 1 ELSE 0 END FROM sys.dm_os_host_info ; - END; - ELSE - BEGIN - SELECT @IsWindowsOperatingSystem = 1 ; - END; - - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 106 ) - AND (select convert(int,value_in_use) from sys.configurations where name = 'default trace enabled' ) = 1 - BEGIN - - select @curr_tracefilename = [path] from sys.traces where is_default = 1 ; - set @curr_tracefilename = reverse(@curr_tracefilename); - - -- Set the trace file path separator based on underlying OS - IF (@IsWindowsOperatingSystem = 1) AND @curr_tracefilename IS NOT NULL - BEGIN - select @indx = patindex('%\%', @curr_tracefilename) ; - set @curr_tracefilename = reverse(@curr_tracefilename) ; - set @base_tracefilename = left( @curr_tracefilename,len(@curr_tracefilename) - @indx) + '\log.trc' ; - END; - ELSE - BEGIN - select @indx = patindex('%/%', @curr_tracefilename) ; - set @curr_tracefilename = reverse(@curr_tracefilename) ; - set @base_tracefilename = left( @curr_tracefilename,len(@curr_tracefilename) - @indx) + '/log.trc' ; - END; - - END; - - /* If the server has any databases on Antiques Roadshow, skip the checks that would break due to CTEs. */ - IF @CheckUserDatabaseObjects = 1 AND EXISTS(SELECT * FROM sys.databases WHERE compatibility_level < 90) - BEGIN - SET @CheckUserDatabaseObjects = 0; - PRINT 'Databases with compatibility level < 90 found, so setting @CheckUserDatabaseObjects = 0.'; - PRINT 'The database-level checks rely on CTEs, which are not supported in SQL 2000 compat level databases.'; - PRINT 'Get with the cool kids and switch to a current compatibility level, Grandpa. To find the problems, run:'; - PRINT 'SELECT * FROM sys.databases WHERE compatibility_level < 90;'; - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 204 AS CheckID , - 0 AS Priority , - 'Informational' AS FindingsGroup , - '@CheckUserDatabaseObjects Disabled' AS Finding , - 'https://www.BrentOzar.com/blitz/' AS URL , - 'Since you have databases with compatibility_level < 90, we can''t run @CheckUserDatabaseObjects = 1. To find them: SELECT * FROM sys.databases WHERE compatibility_level < 90' AS Details; - END; - - /* --TOURSTOP08-- */ - /* If the server is Amazon RDS, skip checks that it doesn't allow */ - IF LEFT(CAST(SERVERPROPERTY('ComputerNamePhysicalNetBIOS') AS VARCHAR(8000)), 8) = 'EC2AMAZ-' - AND LEFT(CAST(SERVERPROPERTY('MachineName') AS VARCHAR(8000)), 8) = 'EC2AMAZ-' - AND db_id('rdsadmin') IS NOT NULL - AND EXISTS(SELECT * FROM master.sys.all_objects WHERE name IN ('rds_startup_tasks', 'rds_help_revlogin', 'rds_hexadecimal', 'rds_failover_tracking', 'rds_database_tracking', 'rds_track_change')) - BEGIN - 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 (29); /* tables in model database created by users - not allowed */ - INSERT INTO #SkipChecks (CheckID) VALUES (40); /* TempDB only has one data file in RDS */ - INSERT INTO #SkipChecks (CheckID) VALUES (62); /* Database compatibility level - cannot change in RDS */ - INSERT INTO #SkipChecks (CheckID) VALUES (68); /*Check for the last good DBCC CHECKDB date - can't run DBCC DBINFO() */ - INSERT INTO #SkipChecks (CheckID) VALUES (69); /* High VLF check - requires DBCC LOGINFO permission */ - INSERT INTO #SkipChecks (CheckID) VALUES (73); /* No Failsafe Operator Configured check */ - INSERT INTO #SkipChecks (CheckID) VALUES (92); /* Drive info check - requires xp_Fixeddrives permission */ - INSERT INTO #SkipChecks (CheckID) VALUES (100); /* Remote DAC disabled */ - INSERT INTO #SkipChecks (CheckID) VALUES (177); /* Disabled Internal Monitoring Features check - requires dm_server_registry access */ - INSERT INTO #SkipChecks (CheckID) VALUES (180); /* 180/181 are maintenance plans checks - Maint plans not available in RDS*/ - INSERT INTO #SkipChecks (CheckID) VALUES (181); /*Find repetitive maintenance tasks*/ - - -- can check errorlog using rdsadmin.dbo.rds_read_error_log, so allow this check - --INSERT INTO #SkipChecks (CheckID) VALUES (193); /* xp_readerrorlog checking for IFI */ - - INSERT INTO #SkipChecks (CheckID) VALUES (211); /* xp_regread not allowed - checking for power saving */ - INSERT INTO #SkipChecks (CheckID) VALUES (212); /* xp_regread not allowed - checking for additional instances */ - INSERT INTO #SkipChecks (CheckID) VALUES (2301); /* sp_validatelogins called by Invalid login defined with Windows Authentication */ - - -- Following are skipped due to limited permissions in msdb/SQLAgent in RDS - INSERT INTO #SkipChecks (CheckID) VALUES (30); /* SQL Server Agent alerts not configured */ - INSERT INTO #SkipChecks (CheckID) VALUES (31); /* check whether we have NO ENABLED operators */ - INSERT INTO #SkipChecks (CheckID) VALUES (57); /* SQL Agent Job Runs at Startup */ - INSERT INTO #SkipChecks (CheckID) VALUES (59); /* Alerts Configured without Follow Up */ - INSERT INTO #SkipChecks (CheckID) VALUES (61); /*SQL Server Agent alerts do not exist for severity levels 19 through 25*/ - INSERT INTO #SkipChecks (CheckID) VALUES (79); /* Shrink Database Job check */ - INSERT INTO #SkipChecks (CheckID) VALUES (94); /* job failure without operator notification check */ - INSERT INTO #SkipChecks (CheckID) VALUES (96); /* Agent alerts for corruption */ - INSERT INTO #SkipChecks (CheckID) VALUES (98); /* check for disabled alerts */ - INSERT INTO #SkipChecks (CheckID) VALUES (123); /* Agent Jobs Starting Simultaneously */ - INSERT INTO #SkipChecks (CheckID) VALUES (219); /* check for alerts that do NOT include event descriptions in their outputs via email/pager/net-send */ - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 223 AS CheckID , - 0 AS Priority , - 'Informational' AS FindingsGroup , - 'Some Checks Skipped' AS Finding , - 'https://aws.amazon.com/rds/sqlserver/' AS URL , - 'Amazon RDS detected, so we skipped some checks that are not currently possible, relevant, or practical there.' AS Details; - END; /* Amazon RDS skipped checks */ - - /* If the server is ExpressEdition, skip checks that it doesn't allow */ - IF CAST(SERVERPROPERTY('Edition') AS NVARCHAR(1000)) LIKE N'%Express%' - BEGIN - INSERT INTO #SkipChecks (CheckID) VALUES (30); /* Alerts not configured */ - INSERT INTO #SkipChecks (CheckID) VALUES (31); /* Operators not configured */ - INSERT INTO #SkipChecks (CheckID) VALUES (61); /* Agent alerts 19-25 */ - INSERT INTO #SkipChecks (CheckID) VALUES (73); /* Failsafe operator */ - INSERT INTO #SkipChecks (CheckID) VALUES (96); /* Agent alerts for corruption */ - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 223 AS CheckID , - 0 AS Priority , - 'Informational' AS FindingsGroup , - 'Some Checks Skipped' AS Finding , - 'https://stackoverflow.com/questions/1169634/limitations-of-sql-server-express' AS URL , - 'Express Edition detected, so we skipped some checks that are not currently possible, relevant, or practical there.' AS Details; - END; /* Express Edition skipped checks */ - - /* If the server is an Azure Managed Instance, skip checks that it doesn't allow */ - IF SERVERPROPERTY('EngineEdition') = 8 - BEGIN - INSERT INTO #SkipChecks (CheckID) VALUES (1); /* Full backups - because of the MI GUID name bug mentioned here: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/1481 */ - INSERT INTO #SkipChecks (CheckID) VALUES (2); /* Log backups - because of the MI GUID name bug mentioned here: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/1481 */ - 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 (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 (74); /* TraceFlag On - because Azure Managed Instances go wild and crazy with the trace flags */ - 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 (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 */ - INSERT INTO #SkipChecks (CheckID, DatabaseName) VALUES (80, 'model'); /* Max file size set */ - INSERT INTO #SkipChecks (CheckID, DatabaseName) VALUES (80, 'msdb'); /* Max file size set */ - INSERT INTO #SkipChecks (CheckID, DatabaseName) VALUES (80, 'tempdb'); /* Max file size set */ - INSERT INTO #SkipChecks (CheckID) VALUES (224); /* CheckID 224 - Performance - SSRS/SSAS/SSIS Installed */ - INSERT INTO #SkipChecks (CheckID) VALUES (92); /* CheckID 92 - drive space */ - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 223 AS CheckID , - 0 AS Priority , - 'Informational' AS FindingsGroup , - 'Some Checks Skipped' AS Finding , - 'https://docs.microsoft.com/en-us/azure/sql-database/sql-database-managed-instance-index' AS URL , - 'Managed Instance detected, so we skipped some checks that are not currently possible, relevant, or practical there.' AS Details; - END; /* Azure Managed Instance skipped checks */ - - /* - That's the end of the SkipChecks stuff. - The next several tables are used by various checks later. - */ - IF OBJECT_ID('tempdb..#ConfigurationDefaults') IS NOT NULL - DROP TABLE #ConfigurationDefaults; - CREATE TABLE #ConfigurationDefaults - ( - name NVARCHAR(128) , - DefaultValue BIGINT, - CheckID INT - ); - - IF OBJECT_ID ('tempdb..#Recompile') IS NOT NULL - DROP TABLE #Recompile; - CREATE TABLE #Recompile( - DBName varchar(200), - ProcName varchar(300), - RecompileFlag varchar(1), - SPSchema varchar(50) - ); - - IF OBJECT_ID('tempdb..#DatabaseDefaults') IS NOT NULL - DROP TABLE #DatabaseDefaults; - CREATE TABLE #DatabaseDefaults - ( - name NVARCHAR(128) , - DefaultValue NVARCHAR(200), - CheckID INT, - Priority INT, - Finding VARCHAR(200), - URL VARCHAR(200), - Details NVARCHAR(4000) - ); - - IF OBJECT_ID('tempdb..#DatabaseScopedConfigurationDefaults') IS NOT NULL - DROP TABLE #DatabaseScopedConfigurationDefaults; - CREATE TABLE #DatabaseScopedConfigurationDefaults - (ID INT IDENTITY(1,1), configuration_id INT, [name] NVARCHAR(60), default_value sql_variant, default_value_for_secondary sql_variant, CheckID INT, ); - - IF OBJECT_ID('tempdb..#DBCCs') IS NOT NULL - DROP TABLE #DBCCs; - CREATE TABLE #DBCCs - ( - ID INT IDENTITY(1, 1) - PRIMARY KEY , - ParentObject VARCHAR(255) , - Object VARCHAR(255) , - Field VARCHAR(255) , - Value VARCHAR(255) , - DbName NVARCHAR(128) NULL - ); - - IF OBJECT_ID('tempdb..#LogInfo2012') IS NOT NULL - DROP TABLE #LogInfo2012; - CREATE TABLE #LogInfo2012 - ( - recoveryunitid INT , - FileID SMALLINT , - FileSize BIGINT , - StartOffset BIGINT , - FSeqNo BIGINT , - [Status] TINYINT , - Parity TINYINT , - CreateLSN NUMERIC(38) - ); - - IF OBJECT_ID('tempdb..#LogInfo') IS NOT NULL - DROP TABLE #LogInfo; - CREATE TABLE #LogInfo - ( - FileID SMALLINT , - FileSize BIGINT , - StartOffset BIGINT , - FSeqNo BIGINT , - [Status] TINYINT , - Parity TINYINT , - CreateLSN NUMERIC(38) - ); - - IF OBJECT_ID('tempdb..#partdb') IS NOT NULL - DROP TABLE #partdb; - CREATE TABLE #partdb - ( - dbname NVARCHAR(128) , - objectname NVARCHAR(200) , - type_desc NVARCHAR(128) - ); - - IF OBJECT_ID('tempdb..#TraceStatus') IS NOT NULL - DROP TABLE #TraceStatus; - CREATE TABLE #TraceStatus - ( - TraceFlag VARCHAR(10) , - status BIT , - Global BIT , - Session BIT - ); - - IF OBJECT_ID('tempdb..#driveInfo') IS NOT NULL - DROP TABLE #driveInfo; - CREATE TABLE #driveInfo - ( - drive NVARCHAR(2), - logical_volume_name NVARCHAR(36), --Limit is 32 for NTFS, 11 for FAT - available_MB DECIMAL(18, 0), - total_MB DECIMAL(18, 0), - used_percent DECIMAL(18, 2) - ); - - IF OBJECT_ID('tempdb..#dm_exec_query_stats') IS NOT NULL - DROP TABLE #dm_exec_query_stats; - CREATE TABLE #dm_exec_query_stats - ( - [id] [int] NOT NULL - IDENTITY(1, 1) , - [sql_handle] [varbinary](64) NOT NULL , - [statement_start_offset] [int] NOT NULL , - [statement_end_offset] [int] NOT NULL , - [plan_generation_num] [bigint] NOT NULL , - [plan_handle] [varbinary](64) NOT NULL , - [creation_time] [datetime] NOT NULL , - [last_execution_time] [datetime] NOT NULL , - [execution_count] [bigint] NOT NULL , - [total_worker_time] [bigint] NOT NULL , - [last_worker_time] [bigint] NOT NULL , - [min_worker_time] [bigint] NOT NULL , - [max_worker_time] [bigint] NOT NULL , - [total_physical_reads] [bigint] NOT NULL , - [last_physical_reads] [bigint] NOT NULL , - [min_physical_reads] [bigint] NOT NULL , - [max_physical_reads] [bigint] NOT NULL , - [total_logical_writes] [bigint] NOT NULL , - [last_logical_writes] [bigint] NOT NULL , - [min_logical_writes] [bigint] NOT NULL , - [max_logical_writes] [bigint] NOT NULL , - [total_logical_reads] [bigint] NOT NULL , - [last_logical_reads] [bigint] NOT NULL , - [min_logical_reads] [bigint] NOT NULL , - [max_logical_reads] [bigint] NOT NULL , - [total_clr_time] [bigint] NOT NULL , - [last_clr_time] [bigint] NOT NULL , - [min_clr_time] [bigint] NOT NULL , - [max_clr_time] [bigint] NOT NULL , - [total_elapsed_time] [bigint] NOT NULL , - [last_elapsed_time] [bigint] NOT NULL , - [min_elapsed_time] [bigint] NOT NULL , - [max_elapsed_time] [bigint] NOT NULL , - [query_hash] [binary](8) NULL , - [query_plan_hash] [binary](8) NULL , - [query_plan] [xml] NULL , - [query_plan_filtered] [nvarchar](MAX) NULL , - [text] [nvarchar](MAX) COLLATE SQL_Latin1_General_CP1_CI_AS - NULL , - [text_filtered] [nvarchar](MAX) COLLATE SQL_Latin1_General_CP1_CI_AS - NULL - ); - - IF OBJECT_ID('tempdb..#ErrorLog') IS NOT NULL - DROP TABLE #ErrorLog; - CREATE TABLE #ErrorLog - ( - LogDate DATETIME , - ProcessInfo NVARCHAR(20) , - [Text] NVARCHAR(1000) - ); - - IF OBJECT_ID('tempdb..#fnTraceGettable') IS NOT NULL - DROP TABLE #fnTraceGettable; - CREATE TABLE #fnTraceGettable - ( - TextData NVARCHAR(4000) , - DatabaseName NVARCHAR(256) , - EventClass INT , - Severity INT , - StartTime DATETIME , - EndTime DATETIME , - Duration BIGINT , - NTUserName NVARCHAR(256) , - NTDomainName NVARCHAR(256) , - HostName NVARCHAR(256) , - ApplicationName NVARCHAR(256) , - LoginName NVARCHAR(256) , - DBUserName NVARCHAR(256) - ); - - IF OBJECT_ID('tempdb..#Instances') IS NOT NULL - DROP TABLE #Instances; - CREATE TABLE #Instances - ( - Instance_Number NVARCHAR(MAX) , - Instance_Name NVARCHAR(MAX) , - Data_Field NVARCHAR(MAX) - ); - - IF OBJECT_ID('tempdb..#IgnorableWaits') IS NOT NULL - DROP TABLE #IgnorableWaits; - CREATE TABLE #IgnorableWaits (wait_type NVARCHAR(60)); - INSERT INTO #IgnorableWaits VALUES ('BROKER_EVENTHANDLER'); - INSERT INTO #IgnorableWaits VALUES ('BROKER_RECEIVE_WAITFOR'); - INSERT INTO #IgnorableWaits VALUES ('BROKER_TASK_STOP'); - INSERT INTO #IgnorableWaits VALUES ('BROKER_TO_FLUSH'); - INSERT INTO #IgnorableWaits VALUES ('BROKER_TRANSMITTER'); - INSERT INTO #IgnorableWaits VALUES ('CHECKPOINT_QUEUE'); - INSERT INTO #IgnorableWaits VALUES ('CLR_AUTO_EVENT'); - INSERT INTO #IgnorableWaits VALUES ('CLR_MANUAL_EVENT'); - INSERT INTO #IgnorableWaits VALUES ('CLR_SEMAPHORE'); - INSERT INTO #IgnorableWaits VALUES ('DBMIRROR_DBM_EVENT'); - INSERT INTO #IgnorableWaits VALUES ('DBMIRROR_DBM_MUTEX'); - INSERT INTO #IgnorableWaits VALUES ('DBMIRROR_EVENTS_QUEUE'); - INSERT INTO #IgnorableWaits VALUES ('DBMIRROR_WORKER_QUEUE'); - INSERT INTO #IgnorableWaits VALUES ('DBMIRRORING_CMD'); - INSERT INTO #IgnorableWaits VALUES ('DIRTY_PAGE_POLL'); - INSERT INTO #IgnorableWaits VALUES ('DISPATCHER_QUEUE_SEMAPHORE'); - INSERT INTO #IgnorableWaits VALUES ('FT_IFTS_SCHEDULER_IDLE_WAIT'); - INSERT INTO #IgnorableWaits VALUES ('FT_IFTSHC_MUTEX'); - INSERT INTO #IgnorableWaits VALUES ('HADR_CLUSAPI_CALL'); - INSERT INTO #IgnorableWaits VALUES ('HADR_FABRIC_CALLBACK'); - INSERT INTO #IgnorableWaits VALUES ('HADR_FILESTREAM_IOMGR_IOCOMPLETION'); - INSERT INTO #IgnorableWaits VALUES ('HADR_LOGCAPTURE_WAIT'); - INSERT INTO #IgnorableWaits VALUES ('HADR_NOTIFICATION_DEQUEUE'); - INSERT INTO #IgnorableWaits VALUES ('HADR_TIMER_TASK'); - INSERT INTO #IgnorableWaits VALUES ('HADR_WORK_QUEUE'); - INSERT INTO #IgnorableWaits VALUES ('LAZYWRITER_SLEEP'); - INSERT INTO #IgnorableWaits VALUES ('LOGMGR_QUEUE'); - INSERT INTO #IgnorableWaits VALUES ('ONDEMAND_TASK_QUEUE'); - INSERT INTO #IgnorableWaits VALUES ('PARALLEL_REDO_DRAIN_WORKER'); - INSERT INTO #IgnorableWaits VALUES ('PARALLEL_REDO_LOG_CACHE'); - INSERT INTO #IgnorableWaits VALUES ('PARALLEL_REDO_TRAN_LIST'); - INSERT INTO #IgnorableWaits VALUES ('PARALLEL_REDO_WORKER_SYNC'); - INSERT INTO #IgnorableWaits VALUES ('PARALLEL_REDO_WORKER_WAIT_WORK'); - INSERT INTO #IgnorableWaits VALUES ('POPULATE_LOCK_ORDINALS'); - INSERT INTO #IgnorableWaits VALUES ('PREEMPTIVE_HADR_LEASE_MECHANISM'); - INSERT INTO #IgnorableWaits VALUES ('PREEMPTIVE_SP_SERVER_DIAGNOSTICS'); - INSERT INTO #IgnorableWaits VALUES ('QDS_ASYNC_QUEUE'); - INSERT INTO #IgnorableWaits VALUES ('QDS_CLEANUP_STALE_QUERIES_TASK_MAIN_LOOP_SLEEP'); - INSERT INTO #IgnorableWaits VALUES ('QDS_PERSIST_TASK_MAIN_LOOP_SLEEP'); - INSERT INTO #IgnorableWaits VALUES ('QDS_SHUTDOWN_QUEUE'); - INSERT INTO #IgnorableWaits VALUES ('REDO_THREAD_PENDING_WORK'); - INSERT INTO #IgnorableWaits VALUES ('REQUEST_FOR_DEADLOCK_SEARCH'); - INSERT INTO #IgnorableWaits VALUES ('SLEEP_SYSTEMTASK'); - INSERT INTO #IgnorableWaits VALUES ('SLEEP_TASK'); - INSERT INTO #IgnorableWaits VALUES ('SOS_WORK_DISPATCHER'); - INSERT INTO #IgnorableWaits VALUES ('SP_SERVER_DIAGNOSTICS_SLEEP'); - INSERT INTO #IgnorableWaits VALUES ('SQLTRACE_BUFFER_FLUSH'); - INSERT INTO #IgnorableWaits VALUES ('SQLTRACE_INCREMENTAL_FLUSH_SLEEP'); - INSERT INTO #IgnorableWaits VALUES ('UCS_SESSION_REGISTRATION'); - INSERT INTO #IgnorableWaits VALUES ('WAIT_XTP_OFFLINE_CKPT_NEW_LOG'); - INSERT INTO #IgnorableWaits VALUES ('WAITFOR'); - INSERT INTO #IgnorableWaits VALUES ('XE_DISPATCHER_WAIT'); - INSERT INTO #IgnorableWaits VALUES ('XE_LIVE_TARGET_TVF'); - INSERT INTO #IgnorableWaits VALUES ('XE_TIMER_EVENT'); - - IF @Debug IN (1, 2) RAISERROR('Setting @MsSinceWaitsCleared', 0, 1) WITH NOWAIT; - - SELECT @MsSinceWaitsCleared = DATEDIFF(MINUTE, create_date, CURRENT_TIMESTAMP) * 60000.0 - FROM sys.databases - WHERE name = 'tempdb'; - - /* Have they cleared wait stats? Using a 10% fudge factor */ - IF @MsSinceWaitsCleared * .9 > (SELECT MAX(wait_time_ms) FROM sys.dm_os_wait_stats WHERE wait_type IN ('SP_SERVER_DIAGNOSTICS_SLEEP', 'QDS_PERSIST_TASK_MAIN_LOOP_SLEEP', 'REQUEST_FOR_DEADLOCK_SEARCH', 'HADR_FILESTREAM_IOMGR_IOCOMPLETION', 'LAZYWRITER_SLEEP', 'SQLTRACE_INCREMENTAL_FLUSH_SLEEP', 'DIRTY_PAGE_POLL', 'LOGMGR_QUEUE')) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 185) WITH NOWAIT; - - SET @MsSinceWaitsCleared = (SELECT MAX(wait_time_ms) FROM sys.dm_os_wait_stats WHERE wait_type IN ('SP_SERVER_DIAGNOSTICS_SLEEP', 'QDS_PERSIST_TASK_MAIN_LOOP_SLEEP', 'REQUEST_FOR_DEADLOCK_SEARCH', 'HADR_FILESTREAM_IOMGR_IOCOMPLETION', 'LAZYWRITER_SLEEP', 'SQLTRACE_INCREMENTAL_FLUSH_SLEEP', 'DIRTY_PAGE_POLL', 'LOGMGR_QUEUE')); - IF @MsSinceWaitsCleared = 0 SET @MsSinceWaitsCleared = 1; - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - VALUES( 185, - 240, - 'Wait Stats', - 'Wait Stats Have Been Cleared', - 'https://www.brentozar.com/go/waits', - 'Someone ran DBCC SQLPERF to clear sys.dm_os_wait_stats at approximately: ' - + CONVERT(NVARCHAR(100), - DATEADD(MINUTE, (-1. * (@MsSinceWaitsCleared) / 1000. / 60.), GETDATE()), 120)); - END; - - /* @CpuMsSinceWaitsCleared is used for waits stats calculations */ - - IF @Debug IN (1, 2) RAISERROR('Setting @CpuMsSinceWaitsCleared', 0, 1) WITH NOWAIT; - - SELECT @CpuMsSinceWaitsCleared = @MsSinceWaitsCleared * scheduler_count - FROM sys.dm_os_sys_info; - - /* If we're outputting CSV or Markdown, don't bother checking the plan cache because we cannot export plans. */ - IF @OutputType = 'CSV' OR @OutputType = 'MARKDOWN' - SET @CheckProcedureCache = 0; - - /* If we're posting a question on Stack, include background info on the server */ - IF @OutputType = 'MARKDOWN' - SET @CheckServerInfo = 1; - - /* Only run CheckUserDatabaseObjects if there are less than 50 databases. */ - IF @BringThePain = 0 AND 50 <= (SELECT COUNT(*) FROM sys.databases) AND @CheckUserDatabaseObjects = 1 - BEGIN - SET @CheckUserDatabaseObjects = 0; - PRINT 'Running sp_Blitz @CheckUserDatabaseObjects = 1 on a server with 50+ databases may cause temporary problems for the server and/or user.'; - PRINT 'If you''re sure you want to do this, run again with the parameter @BringThePain = 1.'; - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 201 AS CheckID , - 0 AS Priority , - 'Informational' AS FindingsGroup , - '@CheckUserDatabaseObjects Disabled' AS Finding , - 'https://www.BrentOzar.com/blitz/' AS URL , - 'If you want to check 50+ databases, you have to also use @BringThePain = 1.' AS Details; - END; - - /* Sanitize our inputs */ - SELECT - @OutputServerName = QUOTENAME(@OutputServerName), - @OutputDatabaseName = QUOTENAME(@OutputDatabaseName), - @OutputSchemaName = QUOTENAME(@OutputSchemaName), - @OutputTableName = QUOTENAME(@OutputTableName); +/* BlitzFileStats info */ +SET @Sql = N' +SELECT +[ServerName], +[CheckDate], +CASE + WHEN MAX([io_stall_read_ms_average]) > @ReadLatencyThreshold THEN ''Yes'' + WHEN MAX([io_stall_write_ms_average]) > @WriteLatencyThreshold THEN ''Yes'' + ELSE ''No'' +END AS [io_stall_ms_breached], +LEFT([PhysicalName],LEN([PhysicalName])-CHARINDEX(''\'',REVERSE([PhysicalName]))+1) AS [PhysicalPath], +SUM([SizeOnDiskMB]) AS [SizeOnDiskMB], +SUM([SizeOnDiskMBgrowth]) AS [SizeOnDiskMBgrowth], +MAX([io_stall_read_ms]) AS [max_io_stall_read_ms], +MAX([io_stall_read_ms_average]) AS [max_io_stall_read_ms_average], +@ReadLatencyThreshold AS [is_stall_read_ms_threshold], +SUM([num_of_reads]) AS [num_of_reads], +SUM([megabytes_read]) AS [megabytes_read], +MAX([io_stall_write_ms]) AS [max_io_stall_write_ms], +MAX([io_stall_write_ms_average]) AS [max_io_stall_write_ms_average], +@WriteLatencyThreshold AS [io_stall_write_ms_average], +SUM([num_of_writes]) AS [num_of_writes], +SUM([megabytes_written]) AS [megabytes_written] +FROM '+@FullOutputTableNameFileStats+N' +WHERE [ServerName] = @Servername +AND [CheckDate] BETWEEN @StartDate AND @EndDate +' ++CASE + WHEN @Databasename IS NOT NULL THEN N'AND [DatabaseName] IN (N''tempdb'',@Databasename) +' + ELSE N'' +END ++N'GROUP BY +[ServerName], +[CheckDate], +LEFT([PhysicalName],LEN([PhysicalName])-CHARINDEX(''\'',REVERSE([PhysicalName]))+1) +ORDER BY +[CheckDate] ASC +OPTION (RECOMPILE, MAXDOP '+CAST(@Maxdop AS NVARCHAR(2))+N');' - /* Get the major and minor build numbers */ - - IF @Debug IN (1, 2) RAISERROR('Getting version information.', 0, 1) WITH NOWAIT; - - SET @ProductVersion = CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)); - SELECT @ProductVersionMajor = SUBSTRING(@ProductVersion, 1,CHARINDEX('.', @ProductVersion) + 1 ), - @ProductVersionMinor = PARSENAME(CONVERT(varchar(32), @ProductVersion), 2); - - /* - Whew! we're finally done with the setup, and we can start doing checks. - First, let's make sure we're actually supposed to do checks on this server. - The user could have passed in a SkipChecks table that specified to skip ALL - checks on this server, so let's check for that: - */ - IF ( ( SERVERPROPERTY('ServerName') NOT IN ( SELECT ServerName - FROM #SkipChecks - WHERE DatabaseName IS NULL - AND CheckID IS NULL ) ) - OR ( @SkipChecksTable IS NULL ) - ) - BEGIN +RAISERROR('Getting FileStats info from %s',0,0,@FullOutputTableNameFileStats) WITH NOWAIT; - /* - Extract DBCC DBINFO data from the server. This data is used for check 2 using - the dbi_LastLogBackupTime field and check 68 using the dbi_LastKnownGood field. - NB: DBCC DBINFO is not available on AWS RDS databases so if the server is RDS - (which will have previously triggered inserting a checkID 223 record) and at - least one of the relevant checks is not being skipped then we can extract the - dbinfo information. - */ - IF NOT EXISTS - ( - SELECT 1/0 - FROM #BlitzResults - WHERE CheckID = 223 AND URL = 'https://aws.amazon.com/rds/sqlserver/' - ) AND NOT EXISTS - ( - SELECT 1/0 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID IN (2, 68) - ) - BEGIN +IF (@Debug = 1) +BEGIN + PRINT @Sql; +END - IF @Debug IN (1, 2) RAISERROR('Extracting DBCC DBINFO data (used in checks 2 and 68).', 0, 1, 68) WITH NOWAIT; - - EXEC sp_MSforeachdb N'USE [?]; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - INSERT #DBCCs - (ParentObject, - Object, - Field, - Value) - EXEC (''DBCC DBInfo() With TableResults, NO_INFOMSGS''); - UPDATE #DBCCs SET DbName = N''?'' WHERE DbName IS NULL OPTION (RECOMPILE);'; - END +IF (OBJECT_ID(@FullOutputTableNameFileStats) IS NULL) +BEGIN + IF (@OutputTableNameFileStats IS NULL) + BEGIN + RAISERROR('File stats data skipped',10,0); + SELECT N'Skipped logged File stats data as NULL was passed to parameter @OutputTableNameFileStats'; + END + ELSE + BEGIN + RAISERROR('Table provided for FileStats data: %s does not exist',10,0,@FullOutputTableNameFileStats); + SELECT N'No File stats data available as the table cannot be found'; + END +END +ELSE /* Table exists then run the query */ +BEGIN + EXEC sp_executesql @Sql, + N'@StartDate DATETIMEOFFSET(7), + @EndDate DATETIMEOFFSET(7), + @Servername NVARCHAR(128), + @Databasename NVARCHAR(128), + @ReadLatencyThreshold INT, + @WriteLatencyThreshold INT', + @StartDate=@StartDate, + @EndDate=@EndDate, + @Servername=@Servername, + @Databasename = @Databasename, + @ReadLatencyThreshold = @ReadLatencyThreshold, + @WriteLatencyThreshold = @WriteLatencyThreshold; +END - /* - Our very first check! We'll put more comments in this one just to - explain exactly how it works. First, we check to see if we're - supposed to skip CheckID 1 (that's the check we're working on.) - */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 1 ) - BEGIN +/* Blitz Perfmon stats*/ +SET @Sql = N' +SELECT + [ServerName] + ,[CheckDate] + ,[counter_name] + ,[object_name] + ,[instance_name] + ,[cntr_value] +FROM '+@FullOutputTableNamePerfmonStats+N' +WHERE [ServerName] = @Servername +AND CheckDate BETWEEN @StartDate AND @EndDate +ORDER BY + [CheckDate] ASC, + [counter_name] ASC +OPTION (RECOMPILE, MAXDOP '+CAST(@Maxdop AS NVARCHAR(2))+N');' - /* - Below, we check master.sys.databases looking for databases - that haven't had a backup in the last week. If we find any, - we insert them into #BlitzResults, the temp table that - tracks our server's problems. Note that if the check does - NOT find any problems, we don't save that. We're only - saving the problems, not the successful checks. - */ - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 1) WITH NOWAIT; - - IF SERVERPROPERTY('EngineEdition') <> 8 /* Azure Managed Instances need a special query */ - BEGIN - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 1 AS CheckID , - d.[name] AS DatabaseName , - 1 AS Priority , - 'Backup' AS FindingsGroup , - 'Backups Not Performed Recently' AS Finding , - 'https://www.brentozar.com/go/nobak' AS URL , - 'Last backed up: ' - + COALESCE(CAST(MAX(b.backup_finish_date) AS VARCHAR(25)),'never') AS Details - FROM master.sys.databases d - LEFT OUTER JOIN msdb.dbo.backupset b ON d.name COLLATE SQL_Latin1_General_CP1_CI_AS = b.database_name COLLATE SQL_Latin1_General_CP1_CI_AS - AND b.type = 'D' - AND b.server_name = SERVERPROPERTY('ServerName') /*Backupset ran on current server */ - WHERE d.database_id <> 2 /* Bonus points if you know what that means */ - AND d.state NOT IN(1, 6, 10) /* Not currently offline or restoring, like log shipping databases */ - AND d.is_in_standby = 0 /* Not a log shipping target database */ - AND d.source_database_id IS NULL /* Excludes database snapshots */ - AND d.name NOT IN ( SELECT DISTINCT - DatabaseName - FROM #SkipChecks - WHERE CheckID IS NULL OR CheckID = 1) - /* - The above NOT IN filters out the databases we're not supposed to check. - */ - GROUP BY d.name - HAVING MAX(b.backup_finish_date) <= DATEADD(dd, - -7, GETDATE()) - OR MAX(b.backup_finish_date) IS NULL; - END; - - ELSE /* SERVERPROPERTY('EngineName') must be 8, Azure Managed Instances */ - BEGIN - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 1 AS CheckID , - d.[name] AS DatabaseName , - 1 AS Priority , - 'Backup' AS FindingsGroup , - 'Backups Not Performed Recently' AS Finding , - 'https://www.brentozar.com/go/nobak' AS URL , - 'Last backed up: ' - + COALESCE(CAST(MAX(b.backup_finish_date) AS VARCHAR(25)),'never') AS Details - FROM master.sys.databases d - LEFT OUTER JOIN msdb.dbo.backupset b ON d.name COLLATE SQL_Latin1_General_CP1_CI_AS = b.database_name COLLATE SQL_Latin1_General_CP1_CI_AS - AND b.type = 'D' - WHERE d.database_id <> 2 /* Bonus points if you know what that means */ - AND d.state NOT IN(1, 6, 10) /* Not currently offline or restoring, like log shipping databases */ - AND d.is_in_standby = 0 /* Not a log shipping target database */ - AND d.source_database_id IS NULL /* Excludes database snapshots */ - AND d.name NOT IN ( SELECT DISTINCT - DatabaseName - FROM #SkipChecks - WHERE CheckID IS NULL OR CheckID = 1) - /* - The above NOT IN filters out the databases we're not supposed to check. - */ - GROUP BY d.name - HAVING MAX(b.backup_finish_date) <= DATEADD(dd, - -7, GETDATE()) - OR MAX(b.backup_finish_date) IS NULL; - END; - - - - /* - And there you have it. The rest of this stored procedure works the same - way: it asks: - - Should I skip this check? - - If not, do I find problems? - - Insert the results into #BlitzResults - */ +RAISERROR('Getting Perfmon info from %s',0,0,@FullOutputTableNamePerfmonStats) WITH NOWAIT; - END; +IF (@Debug = 1) +BEGIN + PRINT @Sql; +END - /* - And that's the end of CheckID #1. +IF (OBJECT_ID(@FullOutputTableNamePerfmonStats) IS NULL) +BEGIN + IF (@OutputTableNamePerfmonStats IS NULL) + BEGIN + RAISERROR('Perfmon stats data skipped',10,0); + SELECT N'Skipped logged Perfmon stats data as NULL was passed to parameter @OutputTableNamePerfmonStats'; + END + ELSE + BEGIN + RAISERROR('Table provided for Perfmon stats data: %s does not exist',10,0,@FullOutputTableNamePerfmonStats); + SELECT N'No Perfmon data available as the table cannot be found'; + END +END +ELSE /* Table exists then run the query */ +BEGIN + EXEC sp_executesql @Sql, + N'@StartDate DATETIMEOFFSET(7), + @EndDate DATETIMEOFFSET(7), + @Servername NVARCHAR(128)', + @StartDate=@StartDate, + @EndDate=@EndDate, + @Servername=@Servername; +END - CheckID #2 is a little simpler because it only involves one query, and it's - more typical for queries that people contribute. But keep reading, because - the next check gets more complex again. - */ +/* Blitz cache data */ +RAISERROR('Sortorder for BlitzCache data: %s',0,0,@BlitzCacheSortorder) WITH NOWAIT; - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 2 ) - BEGIN +/* Set intial CTE */ +SET @Sql = N'WITH CheckDates AS ( +SELECT DISTINCT CheckDate +FROM ' ++@FullOutputTableNameBlitzCache ++N' +WHERE [ServerName] = @Servername +AND [CheckDate] BETWEEN @StartDate AND @EndDate' ++@NewLine ++CASE + WHEN @Databasename IS NOT NULL THEN N'AND [DatabaseName] = @Databasename'+@NewLine + ELSE N'' +END ++N')' +; - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 2) WITH NOWAIT; +SET @Sql += @NewLine; - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT DISTINCT - 2 AS CheckID , - d.name AS DatabaseName , - 1 AS Priority , - 'Backup' AS FindingsGroup , - 'Full Recovery Model w/o Log Backups' AS Finding , - 'https://www.brentozar.com/go/biglogs' AS URL , - ( 'The ' + CAST(CAST((SELECT ((SUM([mf].[size]) * 8.) / 1024.) FROM sys.[master_files] AS [mf] WHERE [mf].[database_id] = d.[database_id] AND [mf].[type_desc] = 'LOG') AS DECIMAL(18,2)) AS VARCHAR(30)) + 'MB log file has not been backed up in the last week.' ) AS Details - FROM master.sys.databases d - LEFT JOIN #DBCCs ll On ll.DbName = d.name And ll.Field = 'dbi_LastLogBackupTime' - WHERE d.recovery_model IN ( 1, 2 ) - AND d.database_id NOT IN ( 2, 3 ) - AND d.source_database_id IS NULL - AND d.state NOT IN(1, 6, 10) /* Not currently offline or restoring, like log shipping databases */ - AND d.is_in_standby = 0 /* Not a log shipping target database */ - AND d.source_database_id IS NULL /* Excludes database snapshots */ - AND d.name NOT IN ( SELECT DISTINCT - DatabaseName - FROM #SkipChecks - WHERE CheckID IS NULL OR CheckID = 2) - AND ( - ( - /* We couldn't get a value from the DBCC DBINFO data so let's check the msdb backup history information */ - [ll].[Value] Is Null - AND NOT EXISTS ( SELECT * - FROM msdb.dbo.backupset b - WHERE d.name COLLATE SQL_Latin1_General_CP1_CI_AS = b.database_name COLLATE SQL_Latin1_General_CP1_CI_AS - AND b.type = 'L' - AND b.backup_finish_date >= DATEADD(dd,-7, GETDATE()) - ) - ) - OR - ( - Convert(datetime,ll.Value,21) < DATEADD(dd,-7, GETDATE()) - ) - - ); - END; - - /* - CheckID #256 is searching for backups to NUL. - */ - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 256 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 2) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT DISTINCT - 256 AS CheckID , - d.name AS DatabaseName, - 1 AS Priority , - 'Backup' AS FindingsGroup , - 'Log Backups to NUL' AS Finding , - 'https://www.brentozar.com/go/nul' AS URL , - N'The transaction log file has been backed up ' + CAST((SELECT count(*) - FROM msdb.dbo.backupset AS b INNER JOIN - msdb.dbo.backupmediafamily AS bmf - ON b.media_set_id = bmf.media_set_id - WHERE b.database_name COLLATE SQL_Latin1_General_CP1_CI_AS = d.name COLLATE SQL_Latin1_General_CP1_CI_AS - AND bmf.physical_device_name = 'NUL' - AND b.type = 'L' - AND b.backup_finish_date >= DATEADD(dd, - -7, GETDATE())) AS NVARCHAR(8)) + ' time(s) to ''NUL'' in the last week, which means the backup does not exist. This breaks point-in-time recovery.' AS Details - FROM master.sys.databases AS d - WHERE d.recovery_model IN ( 1, 2 ) - AND d.database_id NOT IN ( 2, 3 ) - AND d.source_database_id IS NULL - AND d.state NOT IN(1, 6, 10) /* Not currently offline or restoring, like log shipping databases */ - AND d.is_in_standby = 0 /* Not a log shipping target database */ - AND d.source_database_id IS NULL /* Excludes database snapshots */ - --AND d.name NOT IN ( SELECT DISTINCT - -- DatabaseName - -- FROM #SkipChecks - -- WHERE CheckID IS NULL OR CheckID = 2) - AND EXISTS ( SELECT * - FROM msdb.dbo.backupset AS b INNER JOIN - msdb.dbo.backupmediafamily AS bmf - ON b.media_set_id = bmf.media_set_id - WHERE d.name COLLATE SQL_Latin1_General_CP1_CI_AS = b.database_name COLLATE SQL_Latin1_General_CP1_CI_AS - AND bmf.physical_device_name = 'NUL' - AND b.type = 'L' - AND b.backup_finish_date >= DATEADD(dd, - -7, GETDATE()) ); - END; - - /* - Next up, we've got CheckID 8. (These don't have to go in order.) This one - won't work on SQL Server 2005 because it relies on a new DMV that didn't - exist prior to SQL Server 2008. This means we have to check the SQL Server - version first, then build a dynamic string with the query we want to run: - */ - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 8 ) - BEGIN - IF @@VERSION NOT LIKE '%Microsoft SQL Server 2000%' - AND @@VERSION NOT LIKE '%Microsoft SQL Server 2005%' - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 8) WITH NOWAIT; - - SET @StringToExecute = 'INSERT INTO #BlitzResults - (CheckID, Priority, - FindingsGroup, - Finding, URL, - Details) - SELECT 8 AS CheckID, - 230 AS Priority, - ''Security'' AS FindingsGroup, - ''Server Audits Running'' AS Finding, - ''https://www.brentozar.com/go/audits'' AS URL, - (''SQL Server built-in audit functionality is being used by server audit: '' + [name]) AS Details FROM sys.dm_server_audit_status OPTION (RECOMPILE);'; - - IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; - IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; - - EXECUTE(@StringToExecute); - END; - END; - - /* - But what if you need to run a query in every individual database? - Hop down to the @CheckUserDatabaseObjects section. - - And that's the basic idea! You can read through the rest of the - checks if you like - some more exciting stuff happens closer to the - end of the stored proc, where we start doing things like checking - the plan cache, but those aren't as cleanly commented. - - If you'd like to contribute your own check, use one of the check - formats shown above and email it to Help@BrentOzar.com. You don't - have to pick a CheckID or a link - we'll take care of that when we - test and publish the code. Thanks! - */ - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 93 ) - BEGIN +/* Append additional CTEs based on sortorder */ +SET @Sql += ( +SELECT CAST(N',' AS NVARCHAR(MAX)) ++[SortOptions].[Aliasname]+N' AS ( +SELECT + [ServerName] + ,'+[SortOptions].[Aliasname]+N'.[CheckDate] + ,[Sortorder] + ,[TimeFrameRank] + ,ROW_NUMBER() OVER(ORDER BY ['+[SortOptions].[Columnname]+N'] DESC) AS [OverallRank] + ,'+[SortOptions].[Aliasname]+N'.[QueryType] + ,'+[SortOptions].[Aliasname]+N'.[QueryText] + ,'+[SortOptions].[Aliasname]+N'.[DatabaseName] + ,'+[SortOptions].[Aliasname]+N'.[AverageCPU] + ,'+[SortOptions].[Aliasname]+N'.[TotalCPU] + ,'+[SortOptions].[Aliasname]+N'.[PercentCPUByType] + ,'+[SortOptions].[Aliasname]+N'.[AverageDuration] + ,'+[SortOptions].[Aliasname]+N'.[TotalDuration] + ,'+[SortOptions].[Aliasname]+N'.[PercentDurationByType] + ,'+[SortOptions].[Aliasname]+N'.[AverageReads] + ,'+[SortOptions].[Aliasname]+N'.[TotalReads] + ,'+[SortOptions].[Aliasname]+N'.[PercentReadsByType] + ,'+[SortOptions].[Aliasname]+N'.[AverageWrites] + ,'+[SortOptions].[Aliasname]+N'.[TotalWrites] + ,'+[SortOptions].[Aliasname]+N'.[PercentWritesByType] + ,'+[SortOptions].[Aliasname]+N'.[ExecutionCount] + ,'+[SortOptions].[Aliasname]+N'.[ExecutionWeight] + ,'+[SortOptions].[Aliasname]+N'.[PercentExecutionsByType] + ,'+[SortOptions].[Aliasname]+N'.[ExecutionsPerMinute] + ,'+[SortOptions].[Aliasname]+N'.[PlanCreationTime] + ,'+[SortOptions].[Aliasname]+N'.[PlanCreationTimeHours] + ,'+[SortOptions].[Aliasname]+N'.[LastExecutionTime] + ,'+[SortOptions].[Aliasname]+N'.[PlanHandle] + ,'+[SortOptions].[Aliasname]+N'.[SqlHandle] + ,'+[SortOptions].[Aliasname]+N'.[SQL Handle More Info] + ,'+[SortOptions].[Aliasname]+N'.[QueryHash] + ,'+[SortOptions].[Aliasname]+N'.[Query Hash More Info] + ,'+[SortOptions].[Aliasname]+N'.[QueryPlanHash] + ,'+[SortOptions].[Aliasname]+N'.[StatementStartOffset] + ,'+[SortOptions].[Aliasname]+N'.[StatementEndOffset] + ,'+[SortOptions].[Aliasname]+N'.[MinReturnedRows] + ,'+[SortOptions].[Aliasname]+N'.[MaxReturnedRows] + ,'+[SortOptions].[Aliasname]+N'.[AverageReturnedRows] + ,'+[SortOptions].[Aliasname]+N'.[TotalReturnedRows] + ,'+[SortOptions].[Aliasname]+N'.[QueryPlan] + ,'+[SortOptions].[Aliasname]+N'.[NumberOfPlans] + ,'+[SortOptions].[Aliasname]+N'.[NumberOfDistinctPlans] + ,'+[SortOptions].[Aliasname]+N'.[MinGrantKB] + ,'+[SortOptions].[Aliasname]+N'.[MaxGrantKB] + ,'+[SortOptions].[Aliasname]+N'.[MinUsedGrantKB] + ,'+[SortOptions].[Aliasname]+N'.[MaxUsedGrantKB] + ,'+[SortOptions].[Aliasname]+N'.[PercentMemoryGrantUsed] + ,'+[SortOptions].[Aliasname]+N'.[AvgMaxMemoryGrant] + ,'+[SortOptions].[Aliasname]+N'.[MinSpills] + ,'+[SortOptions].[Aliasname]+N'.[MaxSpills] + ,'+[SortOptions].[Aliasname]+N'.[TotalSpills] + ,'+[SortOptions].[Aliasname]+N'.[AvgSpills] + ,'+[SortOptions].[Aliasname]+N'.[QueryPlanCost] +FROM CheckDates +CROSS APPLY ( + SELECT TOP (5) + [ServerName] + ,'+[SortOptions].[Aliasname]+N'.[CheckDate] + ,'+QUOTENAME(UPPER([SortOptions].[Sortorder]),N'''')+N' AS [Sortorder] + ,ROW_NUMBER() OVER(ORDER BY ['+[SortOptions].[Columnname]+N'] DESC) AS [TimeFrameRank] + ,'+[SortOptions].[Aliasname]+N'.[QueryType] + ,'+[SortOptions].[Aliasname]+N'.[QueryText] + ,'+[SortOptions].[Aliasname]+N'.[DatabaseName] + ,'+[SortOptions].[Aliasname]+N'.[AverageCPU] + ,'+[SortOptions].[Aliasname]+N'.[TotalCPU] + ,'+[SortOptions].[Aliasname]+N'.[PercentCPUByType] + ,'+[SortOptions].[Aliasname]+N'.[AverageDuration] + ,'+[SortOptions].[Aliasname]+N'.[TotalDuration] + ,'+[SortOptions].[Aliasname]+N'.[PercentDurationByType] + ,'+[SortOptions].[Aliasname]+N'.[AverageReads] + ,'+[SortOptions].[Aliasname]+N'.[TotalReads] + ,'+[SortOptions].[Aliasname]+N'.[PercentReadsByType] + ,'+[SortOptions].[Aliasname]+N'.[AverageWrites] + ,'+[SortOptions].[Aliasname]+N'.[TotalWrites] + ,'+[SortOptions].[Aliasname]+N'.[PercentWritesByType] + ,'+[SortOptions].[Aliasname]+N'.[ExecutionCount] + ,'+[SortOptions].[Aliasname]+N'.[ExecutionWeight] + ,'+[SortOptions].[Aliasname]+N'.[PercentExecutionsByType] + ,'+[SortOptions].[Aliasname]+N'.[ExecutionsPerMinute] + ,'+[SortOptions].[Aliasname]+N'.[PlanCreationTime] + ,'+[SortOptions].[Aliasname]+N'.[PlanCreationTimeHours] + ,'+[SortOptions].[Aliasname]+N'.[LastExecutionTime] + ,'+[SortOptions].[Aliasname]+N'.[PlanHandle] + ,'+[SortOptions].[Aliasname]+N'.[SqlHandle] + ,'+[SortOptions].[Aliasname]+N'.[SQL Handle More Info] + ,'+[SortOptions].[Aliasname]+N'.[QueryHash] + ,'+[SortOptions].[Aliasname]+N'.[Query Hash More Info] + ,'+[SortOptions].[Aliasname]+N'.[QueryPlanHash] + ,'+[SortOptions].[Aliasname]+N'.[StatementStartOffset] + ,'+[SortOptions].[Aliasname]+N'.[StatementEndOffset] + ,'+[SortOptions].[Aliasname]+N'.[MinReturnedRows] + ,'+[SortOptions].[Aliasname]+N'.[MaxReturnedRows] + ,'+[SortOptions].[Aliasname]+N'.[AverageReturnedRows] + ,'+[SortOptions].[Aliasname]+N'.[TotalReturnedRows] + ,'+[SortOptions].[Aliasname]+N'.[QueryPlan] + ,'+[SortOptions].[Aliasname]+N'.[NumberOfPlans] + ,'+[SortOptions].[Aliasname]+N'.[NumberOfDistinctPlans] + ,'+[SortOptions].[Aliasname]+N'.[MinGrantKB] + ,'+[SortOptions].[Aliasname]+N'.[MaxGrantKB] + ,'+[SortOptions].[Aliasname]+N'.[MinUsedGrantKB] + ,'+[SortOptions].[Aliasname]+N'.[MaxUsedGrantKB] + ,'+[SortOptions].[Aliasname]+N'.[PercentMemoryGrantUsed] + ,'+[SortOptions].[Aliasname]+N'.[AvgMaxMemoryGrant] + ,'+[SortOptions].[Aliasname]+N'.[MinSpills] + ,'+[SortOptions].[Aliasname]+N'.[MaxSpills] + ,'+[SortOptions].[Aliasname]+N'.[TotalSpills] + ,'+[SortOptions].[Aliasname]+N'.[AvgSpills] + ,'+[SortOptions].[Aliasname]+N'.[QueryPlanCost] + FROM '+@FullOutputTableNameBlitzCache+N' AS '+[SortOptions].[Aliasname]+N' + WHERE [ServerName] = @Servername + AND [CheckDate] BETWEEN @StartDate AND @EndDate + AND ['+[SortOptions].[Aliasname]+N'].[CheckDate] = [CheckDates].[CheckDate]' + +@NewLine + +CASE + WHEN @Databasename IS NOT NULL THEN N'AND ['+[SortOptions].[Aliasname]+N'].[DatabaseName] = @Databasename'+@NewLine + ELSE N'' + END + +CASE + WHEN [Sortorder] = N'cpu' THEN N'AND [TotalCPU] > 0' + WHEN [Sortorder] = N'reads' THEN N'AND [TotalReads] > 0' + WHEN [Sortorder] = N'writes' THEN N'AND [TotalWrites] > 0' + WHEN [Sortorder] = N'duration' THEN N'AND [TotalDuration] > 0' + WHEN [Sortorder] = N'executions' THEN N'AND [ExecutionCount] > 0' + WHEN [Sortorder] = N'memory grant' THEN N'AND [MaxGrantKB] > 0' + WHEN [Sortorder] = N'spills' THEN N'AND [MaxSpills] > 0' + ELSE N'' + END + +N' + ORDER BY ['+[SortOptions].[Columnname]+N'] DESC) '+[SortOptions].[Aliasname]+N' +)' +FROM (VALUES + (N'cpu',N'TopCPU',N'TotalCPU'), + (N'reads',N'TopReads',N'TotalReads'), + (N'writes',N'TopWrites',N'TotalWrites'), + (N'duration',N'TopDuration',N'TotalDuration'), + (N'executions',N'TopExecutions',N'ExecutionCount'), + (N'memory grant',N'TopMemoryGrants',N'MaxGrantKB'), + (N'spills',N'TopSpills',N'MaxSpills') + ) SortOptions(Sortorder,Aliasname,Columnname) +WHERE + CASE /* for spills and memory grant sorts make sure the underlying columns exist in the DMV otherwise do not include them */ + WHEN (@IncludeMemoryGrants = 0 OR @IncludeMemoryGrants IS NULL) AND ([SortOptions].[Sortorder] = N'memory grant' OR [SortOptions].[Sortorder] = N'all') THEN NULL + WHEN (@IncludeSpills = 0 OR @IncludeSpills IS NULL) AND ([SortOptions].[Sortorder] = N'spills' OR [SortOptions].[Sortorder] = N'all') THEN NULL + ELSE [SortOptions].[Sortorder] + END = ISNULL(NULLIF(@BlitzCacheSortorder,N'all'),[SortOptions].[Sortorder]) +FOR XML PATH(N''), TYPE).value(N'.[1]', N'NVARCHAR(MAX)'); - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 93) WITH NOWAIT; +SET @Sql += @NewLine; - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT - 93 AS CheckID , - 1 AS Priority , - 'Backup' AS FindingsGroup , - 'Backing Up to Same Drive Where Databases Reside' AS Finding , - 'https://www.brentozar.com/go/backup' AS URL , - CAST(COUNT(1) AS VARCHAR(50)) + ' backups done on drive ' - + UPPER(LEFT(bmf.physical_device_name, 3)) - + ' in the last two weeks, where database files also live. This represents a serious risk if that array fails.' Details - FROM msdb.dbo.backupmediafamily AS bmf - INNER JOIN msdb.dbo.backupset AS bs ON bmf.media_set_id = bs.media_set_id - AND bs.backup_start_date >= ( DATEADD(dd, - -14, GETDATE()) ) - /* Filter out databases that were recently restored: */ - LEFT OUTER JOIN msdb.dbo.restorehistory rh ON bs.database_name = rh.destination_database_name AND rh.restore_date > DATEADD(dd, -14, GETDATE()) - WHERE UPPER(LEFT(bmf.physical_device_name, 3)) <> 'HTT' AND - bmf.physical_device_name NOT LIKE '\\%' AND -- GitHub Issue #2141 - @IsWindowsOperatingSystem = 1 AND -- GitHub Issue #1995 - UPPER(LEFT(bmf.physical_device_name COLLATE SQL_Latin1_General_CP1_CI_AS, 3)) IN ( - SELECT DISTINCT - UPPER(LEFT(mf.physical_name COLLATE SQL_Latin1_General_CP1_CI_AS, 3)) - FROM sys.master_files AS mf - WHERE mf.database_id <> 2 ) - AND rh.destination_database_name IS NULL - GROUP BY UPPER(LEFT(bmf.physical_device_name, 3)); - END; +/* Build the select statements to return the data after CTE declarations */ +SET @Sql += ( +SELECT STUFF(( +SELECT @NewLine ++N'UNION ALL' ++@NewLine ++N'SELECT * +FROM '+[SortOptions].[Aliasname] +FROM (VALUES + (N'cpu',N'TopCPU',N'TotalCPU'), + (N'reads',N'TopReads',N'TotalReads'), + (N'writes',N'TopWrites',N'TotalWrites'), + (N'duration',N'TopDuration',N'TotalDuration'), + (N'executions',N'TopExecutions',N'ExecutionCount'), + (N'memory grant',N'TopMemoryGrants',N'MaxGrantKB'), + (N'spills',N'TopSpills',N'MaxSpills') + ) SortOptions(Sortorder,Aliasname,Columnname) +WHERE + CASE /* for spills and memory grant sorts make sure the underlying columns exist in the DMV otherwise do not include them */ + WHEN (@IncludeMemoryGrants = 0 OR @IncludeMemoryGrants IS NULL) AND ([SortOptions].[Sortorder] = N'memory grant' OR [SortOptions].[Sortorder] = N'all') THEN NULL + WHEN (@IncludeSpills = 0 OR @IncludeSpills IS NULL) AND ([SortOptions].[Sortorder] = N'spills' OR [SortOptions].[Sortorder] = N'all') THEN NULL + ELSE [SortOptions].[Sortorder] + END = ISNULL(NULLIF(@BlitzCacheSortorder,N'all'),[SortOptions].[Sortorder]) +FOR XML PATH(N''), TYPE).value(N'.[1]', N'NVARCHAR(MAX)'),1,11,N'') +); - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 119 ) - AND EXISTS ( SELECT * - FROM sys.all_objects o - WHERE o.name = 'dm_database_encryption_keys' ) - BEGIN +/* Append Order By */ +SET @Sql += @NewLine ++N'ORDER BY + [Sortorder] ASC, + [CheckDate] ASC, + [TimeFrameRank] ASC'; - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 119) WITH NOWAIT; - - SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, DatabaseName, URL, Details) - SELECT 119 AS CheckID, - 1 AS Priority, - ''Backup'' AS FindingsGroup, - ''TDE Certificate Not Backed Up Recently'' AS Finding, - db_name(dek.database_id) AS DatabaseName, - ''https://www.brentozar.com/go/tde'' AS URL, - ''The certificate '' + c.name + '' is used to encrypt database '' + db_name(dek.database_id) + ''. Last backup date: '' + COALESCE(CAST(c.pvt_key_last_backup_date AS VARCHAR(100)), ''Never'') AS Details - FROM master.sys.certificates c INNER JOIN sys.dm_database_encryption_keys dek ON c.thumbprint = dek.encryptor_thumbprint - WHERE pvt_key_last_backup_date IS NULL OR pvt_key_last_backup_date <= DATEADD(dd, -30, GETDATE()) OPTION (RECOMPILE);'; - - IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; - IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; - - EXECUTE(@StringToExecute); - END; +/* Append OPTION(RECOMPILE, MAXDOP) to complete the statement */ +SET @Sql += @NewLine ++N'OPTION(RECOMPILE, MAXDOP '+CAST(@Maxdop AS NVARCHAR(2))+N');'; - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 202 ) - AND EXISTS ( SELECT * - FROM sys.all_columns c - WHERE c.name = 'pvt_key_last_backup_date' ) - AND EXISTS ( SELECT * - FROM msdb.INFORMATION_SCHEMA.COLUMNS c - WHERE c.TABLE_NAME = 'backupset' AND c.COLUMN_NAME = 'encryptor_thumbprint' ) - BEGIN +RAISERROR('Getting BlitzCache info from %s',0,0,@FullOutputTableNameBlitzCache) WITH NOWAIT; - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 202) WITH NOWAIT; - - SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) - SELECT DISTINCT 202 AS CheckID, - 1 AS Priority, - ''Backup'' AS FindingsGroup, - ''Encryption Certificate Not Backed Up Recently'' AS Finding, - ''https://www.brentozar.com/go/tde'' AS URL, - ''The certificate '' + c.name + '' is used to encrypt database backups. Last backup date: '' + COALESCE(CAST(c.pvt_key_last_backup_date AS VARCHAR(100)), ''Never'') AS Details - FROM sys.certificates c - INNER JOIN msdb.dbo.backupset bs ON c.thumbprint = bs.encryptor_thumbprint - WHERE pvt_key_last_backup_date IS NULL OR pvt_key_last_backup_date <= DATEADD(dd, -30, GETDATE()) OPTION (RECOMPILE);'; - - IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; - IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; - - EXECUTE(@StringToExecute); - END; +IF (@Debug = 1) +BEGIN + PRINT SUBSTRING(@Sql, 0, 4000); + PRINT SUBSTRING(@Sql, 4000, 8000); + PRINT SUBSTRING(@Sql, 8000, 12000); + PRINT SUBSTRING(@Sql, 12000, 16000); + PRINT SUBSTRING(@Sql, 16000, 20000); + PRINT SUBSTRING(@Sql, 20000, 24000); + PRINT SUBSTRING(@Sql, 24000, 28000); +END - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 3 ) - BEGIN - IF DATEADD(dd, -60, GETDATE()) > (SELECT TOP 1 backup_start_date FROM msdb.dbo.backupset ORDER BY backup_start_date) +IF (OBJECT_ID(@FullOutputTableNameBlitzCache) IS NULL) +BEGIN + IF (@OutputTableNameBlitzCache IS NULL) + BEGIN + RAISERROR('BlitzCache data skipped',10,0); + SELECT N'Skipped logged BlitzCache data as NULL was passed to parameter @OutputTableNameBlitzCache'; + END + ELSE + BEGIN + RAISERROR('Table provided for BlitzCache data: %s does not exist',10,0,@FullOutputTableNameBlitzCache); + SELECT N'No BlitzCache data available as the table cannot be found'; + END +END +ELSE /* Table exists then run the query */ +BEGIN + EXEC sp_executesql @Sql, + N'@Servername NVARCHAR(128), + @Databasename NVARCHAR(128), + @BlitzCacheSortorder NVARCHAR(20), + @StartDate DATETIMEOFFSET(7), + @EndDate DATETIMEOFFSET(7)', + @Servername = @Servername, + @Databasename = @Databasename, + @BlitzCacheSortorder = @BlitzCacheSortorder, + @StartDate = @StartDate, + @EndDate = @EndDate; +END - BEGIN - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 3) WITH NOWAIT; - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT TOP 1 - 3 AS CheckID , - 'msdb' , - 200 AS Priority , - 'Backup' AS FindingsGroup , - 'MSDB Backup History Not Purged' AS Finding , - 'https://www.brentozar.com/go/history' AS URL , - ( 'Database backup history retained back to ' - + CAST(bs.backup_start_date AS VARCHAR(20)) ) AS Details - FROM msdb.dbo.backupset bs - LEFT OUTER JOIN msdb.dbo.restorehistory rh ON bs.database_name = rh.destination_database_name - WHERE rh.destination_database_name IS NULL - ORDER BY bs.backup_start_date ASC; - END; - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 186 ) - BEGIN - IF DATEADD(dd, -2, GETDATE()) < (SELECT TOP 1 backup_start_date FROM msdb.dbo.backupset ORDER BY backup_start_date) - - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 186) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT TOP 1 - 186 AS CheckID , - 'msdb' , - 200 AS Priority , - 'Backup' AS FindingsGroup , - 'MSDB Backup History Purged Too Frequently' AS Finding , - 'https://www.brentozar.com/go/history' AS URL , - ( 'Database backup history only retained back to ' - + CAST(bs.backup_start_date AS VARCHAR(20)) ) AS Details - FROM msdb.dbo.backupset bs - ORDER BY backup_start_date ASC; - END; - END; +/* BlitzWho data */ +SET @Sql = N' +SELECT [ServerName] + ,[CheckDate] + ,[elapsed_time] + ,[session_id] + ,[database_name] + ,[query_text_snippet] + ,[query_plan] + ,[live_query_plan] + ,[query_cost] + ,[status] + ,[wait_info] + ,[top_session_waits] + ,[blocking_session_id] + ,[open_transaction_count] + ,[is_implicit_transaction] + ,[nt_domain] + ,[host_name] + ,[login_name] + ,[nt_user_name] + ,[program_name] + ,[fix_parameter_sniffing] + ,[client_interface_name] + ,[login_time] + ,[start_time] + ,[request_time] + ,[request_cpu_time] + ,[degree_of_parallelism] + ,[request_logical_reads] + ,[Logical_Reads_MB] + ,[request_writes] + ,[Logical_Writes_MB] + ,[request_physical_reads] + ,[Physical_reads_MB] + ,[session_cpu] + ,[session_logical_reads] + ,[session_logical_reads_MB] + ,[session_physical_reads] + ,[session_physical_reads_MB] + ,[session_writes] + ,[session_writes_MB] + ,[tempdb_allocations_mb] + ,[memory_usage] + ,[estimated_completion_time] + ,[percent_complete] + ,[deadlock_priority] + ,[transaction_isolation_level] + ,[last_dop] + ,[min_dop] + ,[max_dop] + ,[last_grant_kb] + ,[min_grant_kb] + ,[max_grant_kb] + ,[last_used_grant_kb] + ,[min_used_grant_kb] + ,[max_used_grant_kb] + ,[last_ideal_grant_kb] + ,[min_ideal_grant_kb] + ,[max_ideal_grant_kb] + ,[last_reserved_threads] + ,[min_reserved_threads] + ,[max_reserved_threads] + ,[last_used_threads] + ,[min_used_threads] + ,[max_used_threads] + ,[grant_time] + ,[requested_memory_kb] + ,[grant_memory_kb] + ,[is_request_granted] + ,[required_memory_kb] + ,[query_memory_grant_used_memory_kb] + ,[ideal_memory_kb] + ,[is_small] + ,[timeout_sec] + ,[resource_semaphore_id] + ,[wait_order] + ,[wait_time_ms] + ,[next_candidate_for_memory_grant] + ,[target_memory_kb] + ,[max_target_memory_kb] + ,[total_memory_kb] + ,[available_memory_kb] + ,[granted_memory_kb] + ,[query_resource_semaphore_used_memory_kb] + ,[grantee_count] + ,[waiter_count] + ,[timeout_error_count] + ,[forced_grant_count] + ,[workload_group_name] + ,[resource_pool_name] + ,[context_info] + ,[query_hash] + ,[query_plan_hash] + ,[sql_handle] + ,[plan_handle] + ,[statement_start_offset] + ,[statement_end_offset] + FROM '+@FullOutputTableNameBlitzWho+N' + WHERE [ServerName] = @Servername + AND ([CheckDate] BETWEEN @StartDate AND @EndDate OR [start_time] BETWEEN CAST(@StartDate AS DATETIME) AND CAST(@EndDate AS DATETIME)) + ' + +CASE + WHEN @Databasename IS NOT NULL THEN N'AND [database_name] = @Databasename + ' + ELSE N'' + END ++N'ORDER BY [CheckDate] ASC + OPTION (RECOMPILE, MAXDOP '+CAST(@Maxdop AS NVARCHAR(2))+N');'; - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 178 ) - AND EXISTS (SELECT * - FROM msdb.dbo.backupset bs - WHERE bs.type = 'D' - AND bs.backup_size >= 50000000000 /* At least 50GB */ - AND DATEDIFF(SECOND, bs.backup_start_date, bs.backup_finish_date) <= 60 /* Backup took less than 60 seconds */ - AND bs.backup_finish_date >= DATEADD(DAY, -14, GETDATE()) /* In the last 2 weeks */) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 178) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 178 AS CheckID , - 200 AS Priority , - 'Performance' AS FindingsGroup , - 'Snapshot Backups Occurring' AS Finding , - 'https://www.brentozar.com/go/snaps' AS URL , - ( CAST(COUNT(*) AS VARCHAR(20)) + ' snapshot-looking backups have occurred in the last two weeks, indicating that IO may be freezing up.') AS Details - FROM msdb.dbo.backupset bs - WHERE bs.type = 'D' - AND bs.backup_size >= 50000000000 /* At least 50GB */ - AND DATEDIFF(SECOND, bs.backup_start_date, bs.backup_finish_date) <= 60 /* Backup took less than 60 seconds */ - AND bs.backup_finish_date >= DATEADD(DAY, -14, GETDATE()); /* In the last 2 weeks */ - END; +RAISERROR('Getting BlitzWho info from %s',0,0,@FullOutputTableNameBlitzWho) WITH NOWAIT; - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 236 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 236) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT TOP 1 236 AS CheckID , - 50 AS Priority , - 'Performance' AS FindingsGroup , - 'Snapshotting Too Many Databases' AS Finding , - 'https://www.brentozar.com/go/toomanysnaps' AS URL , - ( CAST(SUM(1) AS VARCHAR(20)) + ' databases snapshotted at once in the last two weeks, indicating that IO may be freezing up. Microsoft does not recommend VSS snaps for 35 or more databases.') AS Details - FROM msdb.dbo.backupset bs - WHERE bs.type = 'D' - AND bs.backup_finish_date >= DATEADD(DAY, -14, GETDATE()) /* In the last 2 weeks */ - GROUP BY bs.backup_finish_date - HAVING SUM(1) >= 35 - ORDER BY SUM(1) DESC; - END; +IF (@Debug = 1) +BEGIN + PRINT @Sql; +END - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 4 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 4) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 4 AS CheckID , - 230 AS Priority , - 'Security' AS FindingsGroup , - 'Sysadmins' AS Finding , - 'https://www.brentozar.com/go/sa' AS URL , - ( 'Login [' + l.name - + '] is a sysadmin - meaning they can do absolutely anything in SQL Server, including dropping databases or hiding their tracks.' ) AS Details - FROM master.sys.syslogins l - WHERE l.sysadmin = 1 - AND l.name <> SUSER_SNAME(0x01) - AND l.denylogin = 0 - AND l.name NOT LIKE 'NT SERVICE\%' - AND l.name <> 'l_certSignSmDetach'; /* Added in SQL 2016 */ - END; +IF (OBJECT_ID(@FullOutputTableNameBlitzWho) IS NULL) +BEGIN + IF (@OutputTableNameBlitzWho IS NULL) + BEGIN + RAISERROR('BlitzWho data skipped',10,0); + SELECT N'Skipped logged BlitzWho data as NULL was passed to parameter @OutputTableNameBlitzWho'; + END + ELSE + BEGIN + RAISERROR('Table provided for BlitzWho data: %s does not exist',10,0,@FullOutputTableNameBlitzWho); + SELECT N'No BlitzWho data available as the table cannot be found'; + END +END +ELSE +BEGIN + EXEC sp_executesql @Sql, + N'@StartDate DATETIMEOFFSET(7), + @EndDate DATETIMEOFFSET(7), + @Servername NVARCHAR(128), + @Databasename NVARCHAR(128)', + @StartDate=@StartDate, + @EndDate=@EndDate, + @Servername=@Servername, + @Databasename = @Databasename; +END - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE CheckID = 2301 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 2301) WITH NOWAIT; - - /* - #InvalidLogins is filled at the start during the permissions check - */ - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 2301 AS CheckID , - 230 AS Priority , - 'Security' AS FindingsGroup , - 'Invalid login defined with Windows Authentication' AS Finding , - 'https://docs.microsoft.com/en-us/sql/relational-databases/system-stored-procedures/sp-validatelogins-transact-sql' AS URL , - ( 'Windows user or group ' + QUOTENAME(LoginName) + ' is mapped to a SQL Server principal but no longer exists in the Windows environment.') AS Details - FROM #InvalidLogins - ; - END; - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 5 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 5) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 5 AS CheckID , - 230 AS Priority , - 'Security' AS FindingsGroup , - 'Security Admins' AS Finding , - 'https://www.brentozar.com/go/sa' AS URL , - ( 'Login [' + l.name - + '] is a security admin - meaning they can give themselves permission to do absolutely anything in SQL Server, including dropping databases or hiding their tracks.' ) AS Details - FROM master.sys.syslogins l - WHERE l.securityadmin = 1 - AND l.name <> SUSER_SNAME(0x01) - AND l.denylogin = 0; - END; - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 104 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 104) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( [CheckID] , - [Priority] , - [FindingsGroup] , - [Finding] , - [URL] , - [Details] - ) - SELECT 104 AS [CheckID] , - 230 AS [Priority] , - 'Security' AS [FindingsGroup] , - 'Login Can Control Server' AS [Finding] , - 'https://www.brentozar.com/go/sa' AS [URL] , - 'Login [' + pri.[name] - + '] has the CONTROL SERVER permission - meaning they can do absolutely anything in SQL Server, including dropping databases or hiding their tracks.' AS [Details] - FROM sys.server_principals AS pri - WHERE pri.[principal_id] IN ( - SELECT p.[grantee_principal_id] - FROM sys.server_permissions AS p - WHERE p.[state] IN ( 'G', 'W' ) - AND p.[class] = 100 - AND p.[type] = 'CL' ) - AND pri.[name] NOT LIKE '##%##'; - END; +GO +IF OBJECT_ID('dbo.sp_BlitzBackups') IS NULL + EXEC ('CREATE PROCEDURE dbo.sp_BlitzBackups AS RETURN 0;'); +GO +ALTER PROCEDURE [dbo].[sp_BlitzBackups] + @Help TINYINT = 0 , + @HoursBack INT = 168, + @MSDBName NVARCHAR(256) = 'msdb', + @AGName NVARCHAR(256) = NULL, + @RestoreSpeedFullMBps INT = NULL, + @RestoreSpeedDiffMBps INT = NULL, + @RestoreSpeedLogMBps INT = NULL, + @Debug TINYINT = 0, + @PushBackupHistoryToListener BIT = 0, + @WriteBackupsToListenerName NVARCHAR(256) = NULL, + @WriteBackupsToDatabaseName NVARCHAR(256) = NULL, + @WriteBackupsLastHours INT = 168, + @Version VARCHAR(30) = NULL OUTPUT, + @VersionDate DATETIME = NULL OUTPUT, + @VersionCheckMode BIT = 0 +WITH RECOMPILE +AS + BEGIN + SET NOCOUNT ON; + SET STATISTICS XML OFF; + SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + + SELECT @Version = '8.19', @VersionDate = '20240222'; + + IF(@VersionCheckMode = 1) + BEGIN + RETURN; + END; - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 6 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 6) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 6 AS CheckID , - 230 AS Priority , - 'Security' AS FindingsGroup , - 'Jobs Owned By Users' AS Finding , - 'https://www.brentozar.com/go/owners' AS URL , - ( 'Job [' + j.name + '] is owned by [' - + SUSER_SNAME(j.owner_sid) - + '] - 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); - END; + IF @Help = 1 PRINT ' + /* + sp_BlitzBackups from http://FirstResponderKit.org + + This script checks your backups to see how much data you might lose when + this server fails, and how long it might take to recover. - /* --TOURSTOP06-- */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 7 ) - BEGIN - /* --TOURSTOP02-- */ - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 7) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 7 AS CheckID , - 230 AS Priority , - 'Security' AS FindingsGroup , - 'Stored Procedure Runs at Startup' AS Finding , - 'https://www.brentozar.com/go/startup' AS URL , - ( 'Stored procedure [master].[' - + r.SPECIFIC_SCHEMA + '].[' - + r.SPECIFIC_NAME - + '] runs automatically when SQL Server starts up. Make sure you know exactly what this stored procedure is doing, because it could pose a security risk.' ) AS Details - FROM master.INFORMATION_SCHEMA.ROUTINES r - WHERE OBJECTPROPERTY(OBJECT_ID(ROUTINE_NAME), - 'ExecIsStartup') = 1; - END; + To learn more, visit http://FirstResponderKit.org where you can download new + versions for free, watch training videos on how it works, get more info on + the findings, contribute your own code, and more. - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 10 ) - BEGIN - IF @@VERSION NOT LIKE '%Microsoft SQL Server 2000%' - AND @@VERSION NOT LIKE '%Microsoft SQL Server 2005%' - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 10) WITH NOWAIT; - - SET @StringToExecute = 'INSERT INTO #BlitzResults - (CheckID, - Priority, - FindingsGroup, - Finding, - URL, - Details) - SELECT 10 AS CheckID, - 100 AS Priority, - ''Performance'' AS FindingsGroup, - ''Resource Governor Enabled'' AS Finding, - ''https://www.brentozar.com/go/rg'' AS URL, - (''Resource Governor is enabled. Queries may be throttled. Make sure you understand how the Classifier Function is configured.'') AS Details FROM sys.resource_governor_configuration WHERE is_enabled = 1 OPTION (RECOMPILE);'; - - IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; - IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; - - EXECUTE(@StringToExecute); - END; - END; + Known limitations of this version: + - Only Microsoft-supported versions of SQL Server. Sorry, 2005 and 2000. - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 11 ) - BEGIN - IF @@VERSION NOT LIKE '%Microsoft SQL Server 2000%' - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 11) WITH NOWAIT; - - SET @StringToExecute = 'INSERT INTO #BlitzResults - (CheckID, - Priority, - FindingsGroup, - Finding, - URL, - Details) - SELECT 11 AS CheckID, - 100 AS Priority, - ''Performance'' AS FindingsGroup, - ''Server Triggers Enabled'' AS Finding, - ''https://www.brentozar.com/go/logontriggers/'' AS URL, - (''Server Trigger ['' + [name] ++ ''] is enabled. Make sure you understand what that trigger is doing - the less work it does, the better.'') AS Details FROM sys.server_triggers WHERE is_disabled = 0 AND is_ms_shipped = 0 OPTION (RECOMPILE);'; - - IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; - IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; - - EXECUTE(@StringToExecute); - END; - END; + Unknown limitations of this version: + - None. (If we knew them, they would be known. Duh.) - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 12 ) - BEGIN + Changes - for the full list of improvements and fixes in this version, see: + https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/ - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 12) WITH NOWAIT; - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 12 AS CheckID , - [name] AS DatabaseName , - 10 AS Priority , - 'Performance' AS FindingsGroup , - 'Auto-Close Enabled' AS Finding , - 'https://www.brentozar.com/go/autoclose' AS URL , - ( 'Database [' + [name] - + '] has auto-close enabled. This setting can dramatically decrease performance.' ) AS Details - FROM sys.databases - WHERE is_auto_close_on = 1 - AND name NOT IN ( SELECT DISTINCT - DatabaseName - FROM #SkipChecks - WHERE CheckID IS NULL OR CheckID = 12); - END; + Parameter explanations: - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 13 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 13) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 13 AS CheckID , - [name] AS DatabaseName , - 10 AS Priority , - 'Performance' AS FindingsGroup , - 'Auto-Shrink Enabled' AS Finding , - 'https://www.brentozar.com/go/autoshrink' AS URL , - ( 'Database [' + [name] - + '] has auto-shrink enabled. This setting can dramatically decrease performance.' ) AS Details - FROM sys.databases - WHERE is_auto_shrink_on = 1 - AND state <> 6 /* Offline */ - AND name NOT IN ( SELECT DISTINCT - DatabaseName - FROM #SkipChecks - WHERE CheckID IS NULL OR CheckID = 13); - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 14 ) - BEGIN - IF @@VERSION NOT LIKE '%Microsoft SQL Server 2000%' - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 14) WITH NOWAIT; - - SET @StringToExecute = 'INSERT INTO #BlitzResults - (CheckID, - DatabaseName, - Priority, - FindingsGroup, - Finding, - URL, - Details) - SELECT 14 AS CheckID, - [name] as DatabaseName, - 50 AS Priority, - ''Reliability'' AS FindingsGroup, - ''Page Verification Not Optimal'' AS Finding, - ''https://www.brentozar.com/go/torn'' AS URL, - (''Database ['' + [name] + ''] has '' + [page_verify_option_desc] + '' for page verification. SQL Server may have a harder time recognizing and recovering from storage corruption. Consider using CHECKSUM instead.'') COLLATE database_default AS Details - FROM sys.databases - WHERE page_verify_option < 2 - AND name <> ''tempdb'' - AND state NOT IN (1, 6) /* Restoring, Offline */ - and name not in (select distinct DatabaseName from #SkipChecks WHERE CheckID IS NULL OR CheckID = 14) OPTION (RECOMPILE);'; - - IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; - IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; - - EXECUTE(@StringToExecute); - END; - END; + @HoursBack INT = 168 How many hours of history to examine, back from now. + You can check just the last 24 hours of backups, for example. + @MSDBName NVARCHAR(255) You can restore MSDB from different servers and check them + centrally. Also useful if you create a DBA utility database + and merge data from several servers in an AG into one DB. + @RestoreSpeedFullMBps INT By default, we use the backup speed from MSDB to guesstimate + how fast your restores will go. If you have done performance + tuning and testing of your backups (or if they horribly go even + slower in your DR environment, and you want to account for + that), then you can pass in different numbers here. + @RestoreSpeedDiffMBps INT See above. + @RestoreSpeedLogMBps INT See above. - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 15 ) - BEGIN + For more documentation: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/ - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 15) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 15 AS CheckID , - [name] AS DatabaseName , - 110 AS Priority , - 'Performance' AS FindingsGroup , - 'Auto-Create Stats Disabled' AS Finding , - 'https://www.brentozar.com/go/acs' AS URL , - ( 'Database [' + [name] - + '] has auto-create-stats disabled. SQL Server uses statistics to build better execution plans, and without the ability to automatically create more, performance may suffer.' ) AS Details - FROM sys.databases - WHERE is_auto_create_stats_on = 0 - AND name NOT IN ( SELECT DISTINCT - DatabaseName - FROM #SkipChecks - WHERE CheckID IS NULL OR CheckID = 15); - END; + MIT License + + Copyright (c) Brent Ozar Unlimited - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 16 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 16) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 16 AS CheckID , - [name] AS DatabaseName , - 110 AS Priority , - 'Performance' AS FindingsGroup , - 'Auto-Update Stats Disabled' AS Finding , - 'https://www.brentozar.com/go/aus' AS URL , - ( 'Database [' + [name] - + '] has auto-update-stats disabled. SQL Server uses statistics to build better execution plans, and without the ability to automatically update them, performance may suffer.' ) AS Details - FROM sys.databases - WHERE is_auto_update_stats_on = 0 - AND name NOT IN ( SELECT DISTINCT - DatabaseName - FROM #SkipChecks - WHERE CheckID IS NULL OR CheckID = 16); - END; + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 17 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 17) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 17 AS CheckID , - [name] AS DatabaseName , - 150 AS Priority , - 'Performance' AS FindingsGroup , - 'Stats Updated Asynchronously' AS Finding , - 'https://www.brentozar.com/go/asyncstats' AS URL , - ( 'Database [' + [name] - + '] has auto-update-stats-async enabled. When SQL Server gets a query for a table with out-of-date statistics, it will run the query with the stats it has - while updating stats to make later queries better. The initial run of the query may suffer, though.' ) AS Details - FROM sys.databases - WHERE is_auto_update_stats_async_on = 1 - AND name NOT IN ( SELECT DISTINCT - DatabaseName - FROM #SkipChecks - WHERE CheckID IS NULL OR CheckID = 17); - END; + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 20 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 20) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 20 AS CheckID , - [name] AS DatabaseName , - 200 AS Priority , - 'Informational' AS FindingsGroup , - 'Date Correlation On' AS Finding , - 'https://www.brentozar.com/go/corr' AS URL , - ( 'Database [' + [name] - + '] has date correlation enabled. This is not a default setting, and it has some performance overhead. It tells SQL Server that date fields in two tables are related, and SQL Server maintains statistics showing that relation.' ) AS Details - FROM sys.databases - WHERE is_date_correlation_on = 1 - AND name NOT IN ( SELECT DISTINCT - DatabaseName - FROM #SkipChecks - WHERE CheckID IS NULL OR CheckID = 20); - END; + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 21 ) - BEGIN - /* --TOURSTOP04-- */ - IF @@VERSION NOT LIKE '%Microsoft SQL Server 2000%' - AND @@VERSION NOT LIKE '%Microsoft SQL Server 2005%' - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 21) WITH NOWAIT; - - SET @StringToExecute = 'INSERT INTO #BlitzResults - (CheckID, - DatabaseName, - Priority, - FindingsGroup, - Finding, - URL, - Details) - SELECT 21 AS CheckID, - [name] as DatabaseName, - 200 AS Priority, - ''Informational'' AS FindingsGroup, - ''Database Encrypted'' AS Finding, - ''https://www.brentozar.com/go/tde'' AS URL, - (''Database ['' + [name] + ''] has Transparent Data Encryption enabled. Make absolutely sure you have backed up the certificate and private key, or else you will not be able to restore this database.'') AS Details - FROM sys.databases - WHERE is_encrypted = 1 - and name not in (select distinct DatabaseName from #SkipChecks WHERE CheckID IS NULL OR CheckID = 21) OPTION (RECOMPILE);'; - - IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; - IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; - - EXECUTE(@StringToExecute); - END; - END; - /* - Believe it or not, SQL Server doesn't track the default values - for sp_configure options! We'll make our own list here. - */ - - IF @Debug IN (1, 2) RAISERROR('Generating default configuration values', 0, 1) WITH NOWAIT; - - INSERT INTO #ConfigurationDefaults - VALUES ( 'access check cache bucket count', 0, 1001 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'access check cache quota', 0, 1002 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'Ad Hoc Distributed Queries', 0, 1003 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'affinity I/O mask', 0, 1004 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'affinity mask', 0, 1005 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'affinity64 mask', 0, 1066 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'affinity64 I/O mask', 0, 1067 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'Agent XPs', 0, 1071 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'allow updates', 0, 1007 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'awe enabled', 0, 1008 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'backup checksum default', 0, 1070 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'backup compression default', 0, 1073 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'blocked process threshold', 0, 1009 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'blocked process threshold (s)', 0, 1009 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'c2 audit mode', 0, 1010 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'clr enabled', 0, 1011 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'common criteria compliance enabled', 0, 1074 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'contained database authentication', 0, 1068 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'cost threshold for parallelism', 5, 1012 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'cross db ownership chaining', 0, 1013 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'cursor threshold', -1, 1014 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'Database Mail XPs', 0, 1072 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'default full-text language', 1033, 1016 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'default language', 0, 1017 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'default trace enabled', 1, 1018 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'disallow results from triggers', 0, 1019 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'EKM provider enabled', 0, 1075 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'filestream access level', 0, 1076 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'fill factor (%)', 0, 1020 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'ft crawl bandwidth (max)', 100, 1021 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'ft crawl bandwidth (min)', 0, 1022 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'ft notify bandwidth (max)', 100, 1023 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'ft notify bandwidth (min)', 0, 1024 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'index create memory (KB)', 0, 1025 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'in-doubt xact resolution', 0, 1026 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'lightweight pooling', 0, 1027 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'locks', 0, 1028 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'max degree of parallelism', 0, 1029 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'max full-text crawl range', 4, 1030 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'max server memory (MB)', 2147483647, 1031 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'max text repl size (B)', 65536, 1032 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'max worker threads', 0, 1033 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'media retention', 0, 1034 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'min memory per query (KB)', 1024, 1035 ); - /* Accepting both 0 and 16 below because both have been seen in the wild as defaults. */ - IF EXISTS ( SELECT * - FROM sys.configurations - WHERE name = 'min server memory (MB)' - AND value_in_use IN ( 0, 16 ) ) - INSERT INTO #ConfigurationDefaults - SELECT 'min server memory (MB)' , - CAST(value_in_use AS BIGINT), 1036 - FROM sys.configurations - WHERE name = 'min server memory (MB)'; - ELSE - INSERT INTO #ConfigurationDefaults - VALUES ( 'min server memory (MB)', 0, 1036 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'nested triggers', 1, 1037 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'network packet size (B)', 4096, 1038 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'Ole Automation Procedures', 0, 1039 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'open objects', 0, 1040 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'optimize for ad hoc workloads', 0, 1041 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'PH timeout (s)', 60, 1042 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'precompute rank', 0, 1043 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'priority boost', 0, 1044 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'query governor cost limit', 0, 1045 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'query wait (s)', -1, 1046 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'recovery interval (min)', 0, 1047 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'remote access', 1, 1048 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'remote admin connections', 0, 1049 ); - /* SQL Server 2012 changes a configuration default */ - IF @@VERSION LIKE '%Microsoft SQL Server 2005%' - OR @@VERSION LIKE '%Microsoft SQL Server 2008%' - BEGIN - INSERT INTO #ConfigurationDefaults - VALUES ( 'remote login timeout (s)', 20, 1069 ); - END; - ELSE - BEGIN - INSERT INTO #ConfigurationDefaults - VALUES ( 'remote login timeout (s)', 10, 1069 ); - END; - INSERT INTO #ConfigurationDefaults - VALUES ( 'remote proc trans', 0, 1050 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'remote query timeout (s)', 600, 1051 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'Replication XPs', 0, 1052 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'RPC parameter data validation', 0, 1053 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'scan for startup procs', 0, 1054 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'server trigger recursion', 1, 1055 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'set working set size', 0, 1056 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'show advanced options', 0, 1057 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'SMO and DMO XPs', 1, 1058 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'SQL Mail XPs', 0, 1059 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'transform noise words', 0, 1060 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'two digit year cutoff', 2049, 1061 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'user connections', 0, 1062 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'user options', 0, 1063 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'Web Assistant Procedures', 0, 1064 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'xp_cmdshell', 0, 1065 ); - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 22 ) - BEGIN - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 22) WITH NOWAIT; - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT cd.CheckID , - 200 AS Priority , - 'Non-Default Server Config' AS FindingsGroup , - cr.name AS Finding , - 'https://www.brentozar.com/go/conf' AS URL , - ( 'This sp_configure option has been changed. Its default value is ' - + COALESCE(CAST(cd.[DefaultValue] AS VARCHAR(100)), - '(unknown)') - + ' and it has been set to ' - + CAST(cr.value_in_use AS VARCHAR(100)) - + '.' ) AS Details - FROM sys.configurations cr - INNER JOIN #ConfigurationDefaults cd ON cd.name = cr.name - LEFT OUTER JOIN #ConfigurationDefaults cdUsed ON cdUsed.name = cr.name - AND cdUsed.DefaultValue = cr.value_in_use - WHERE cdUsed.name IS NULL; - END; + */'; +ELSE +BEGIN +DECLARE @StringToExecute NVARCHAR(MAX) = N'', + @InnerStringToExecute NVARCHAR(MAX) = N'', + @ProductVersion NVARCHAR(128), + @ProductVersionMajor DECIMAL(10, 2), + @ProductVersionMinor DECIMAL(10, 2), + @StartTime DATETIME2, @ResultText NVARCHAR(MAX), + @crlf NVARCHAR(2), + @MoreInfoHeader NVARCHAR(100), + @MoreInfoFooter NVARCHAR(100); - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 190 ) - BEGIN +IF @HoursBack > 0 + SET @HoursBack = @HoursBack * -1; - IF @Debug IN (1, 2) RAISERROR('Setting @MinServerMemory and @MaxServerMemory', 0, 1) WITH NOWAIT; +IF @WriteBackupsLastHours > 0 + SET @WriteBackupsLastHours = @WriteBackupsLastHours * -1; - SELECT @MinServerMemory = CAST(value_in_use as BIGINT) FROM sys.configurations WHERE name = 'min server memory (MB)'; - SELECT @MaxServerMemory = CAST(value_in_use as BIGINT) FROM sys.configurations WHERE name = 'max server memory (MB)'; - - IF (@MinServerMemory = @MaxServerMemory) - BEGIN +SELECT @crlf = NCHAR(13) + NCHAR(10), + @StartTime = DATEADD(hh, @HoursBack, GETDATE()), + @MoreInfoHeader = N''; - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 190) WITH NOWAIT; +SET @ProductVersion = CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)); +SELECT @ProductVersionMajor = SUBSTRING(@ProductVersion, 1, CHARINDEX('.', @ProductVersion) + 1), + @ProductVersionMinor = PARSENAME(CONVERT(VARCHAR(32), @ProductVersion), 2); - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - VALUES - ( 190, - 200, - 'Performance', - 'Non-Dynamic Memory', - 'https://www.brentozar.com/go/memory', - 'Minimum Server Memory setting is the same as the Maximum (both set to ' + CAST(@MinServerMemory AS NVARCHAR(50)) + '). This will not allow dynamic memory. Please revise memory settings' - ); - END; - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 188 ) - BEGIN +CREATE TABLE #Backups +( + id INT IDENTITY(1, 1), + database_name NVARCHAR(128), + database_guid UNIQUEIDENTIFIER, + RPOWorstCaseMinutes DECIMAL(18, 1), + RTOWorstCaseMinutes DECIMAL(18, 1), + RPOWorstCaseBackupSetID INT, + RPOWorstCaseBackupSetFinishTime DATETIME, + RPOWorstCaseBackupSetIDPrior INT, + RPOWorstCaseBackupSetPriorFinishTime DATETIME, + RPOWorstCaseMoreInfoQuery XML, + RTOWorstCaseBackupFileSizeMB DECIMAL(18, 2), + RTOWorstCaseMoreInfoQuery XML, + FullMBpsAvg DECIMAL(18, 2), + FullMBpsMin DECIMAL(18, 2), + FullMBpsMax DECIMAL(18, 2), + FullSizeMBAvg DECIMAL(18, 2), + FullSizeMBMin DECIMAL(18, 2), + FullSizeMBMax DECIMAL(18, 2), + FullCompressedSizeMBAvg DECIMAL(18, 2), + FullCompressedSizeMBMin DECIMAL(18, 2), + FullCompressedSizeMBMax DECIMAL(18, 2), + DiffMBpsAvg DECIMAL(18, 2), + DiffMBpsMin DECIMAL(18, 2), + DiffMBpsMax DECIMAL(18, 2), + DiffSizeMBAvg DECIMAL(18, 2), + DiffSizeMBMin DECIMAL(18, 2), + DiffSizeMBMax DECIMAL(18, 2), + DiffCompressedSizeMBAvg DECIMAL(18, 2), + DiffCompressedSizeMBMin DECIMAL(18, 2), + DiffCompressedSizeMBMax DECIMAL(18, 2), + LogMBpsAvg DECIMAL(18, 2), + LogMBpsMin DECIMAL(18, 2), + LogMBpsMax DECIMAL(18, 2), + LogSizeMBAvg DECIMAL(18, 2), + LogSizeMBMin DECIMAL(18, 2), + LogSizeMBMax DECIMAL(18, 2), + LogCompressedSizeMBAvg DECIMAL(18, 2), + LogCompressedSizeMBMin DECIMAL(18, 2), + LogCompressedSizeMBMax DECIMAL(18, 2) +); - /* Let's set variables so that our query is still SARGable */ - - IF @Debug IN (1, 2) RAISERROR('Setting @Processors.', 0, 1) WITH NOWAIT; - - SET @Processors = (SELECT cpu_count FROM sys.dm_os_sys_info); - - IF @Debug IN (1, 2) RAISERROR('Setting @NUMANodes', 0, 1) WITH NOWAIT; - - SET @NUMANodes = (SELECT COUNT(1) - FROM sys.dm_os_performance_counters pc - WHERE pc.object_name LIKE '%Buffer Node%' - AND counter_name = 'Page life expectancy'); - /* If Cost Threshold for Parallelism is default then flag as a potential issue */ - /* If MAXDOP is default and processors > 8 or NUMA nodes > 1 then flag as potential issue */ - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 188) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 188 AS CheckID , - 200 AS Priority , - 'Performance' AS FindingsGroup , - cr.name AS Finding , - 'https://www.brentozar.com/go/cxpacket' AS URL , - ( 'Set to ' + CAST(cr.value_in_use AS NVARCHAR(50)) + ', its default value. Changing this sp_configure setting may reduce CXPACKET waits.') - FROM sys.configurations cr - INNER JOIN #ConfigurationDefaults cd ON cd.name = cr.name - AND cr.value_in_use = cd.DefaultValue - WHERE cr.name = 'cost threshold for parallelism' - OR (cr.name = 'max degree of parallelism' AND (@NUMANodes > 1 OR @Processors > 8)); - END; +CREATE TABLE #RTORecoveryPoints +( + id INT IDENTITY(1, 1), + database_name NVARCHAR(128), + database_guid UNIQUEIDENTIFIER, + rto_worst_case_size_mb AS + ( COALESCE(log_file_size_mb, 0) + COALESCE(diff_file_size_mb, 0) + COALESCE(full_file_size_mb, 0)), + rto_worst_case_time_seconds AS + ( COALESCE(log_time_seconds, 0) + COALESCE(diff_time_seconds, 0) + COALESCE(full_time_seconds, 0)), + full_backup_set_id INT, + full_last_lsn NUMERIC(25, 0), + full_backup_set_uuid UNIQUEIDENTIFIER, + full_time_seconds BIGINT, + full_file_size_mb DECIMAL(18, 2), + diff_backup_set_id INT, + diff_last_lsn NUMERIC(25, 0), + diff_time_seconds BIGINT, + diff_file_size_mb DECIMAL(18, 2), + log_backup_set_id INT, + log_last_lsn NUMERIC(25, 0), + log_time_seconds BIGINT, + log_file_size_mb DECIMAL(18, 2), + log_backups INT +); - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 24 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 24) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT DISTINCT - 24 AS CheckID , - DB_NAME(database_id) AS DatabaseName , - 170 AS Priority , - 'File Configuration' AS FindingsGroup , - 'System Database on C Drive' AS Finding , - 'https://www.brentozar.com/go/cdrive' AS URL , - ( 'The ' + DB_NAME(database_id) - + ' database has a file on the C drive. Putting system databases on the C drive runs the risk of crashing the server when it runs out of space.' ) AS Details - FROM sys.master_files - WHERE UPPER(LEFT(physical_name, 1)) = 'C' - AND DB_NAME(database_id) IN ( 'master', - 'model', 'msdb' ); - END; +CREATE TABLE #Recoverability + ( + Id INT IDENTITY , + DatabaseName NVARCHAR(128), + DatabaseGUID UNIQUEIDENTIFIER, + LastBackupRecoveryModel NVARCHAR(60), + FirstFullBackupSizeMB DECIMAL (18,2), + FirstFullBackupDate DATETIME, + LastFullBackupSizeMB DECIMAL (18,2), + LastFullBackupDate DATETIME, + AvgFullBackupThroughputMB DECIMAL (18,2), + AvgFullBackupDurationSeconds INT, + AvgDiffBackupThroughputMB DECIMAL (18,2), + AvgDiffBackupDurationSeconds INT, + AvgLogBackupThroughputMB DECIMAL (18,2), + AvgLogBackupDurationSeconds INT, + AvgFullSizeMB DECIMAL (18,2), + AvgDiffSizeMB DECIMAL (18,2), + AvgLogSizeMB DECIMAL (18,2), + IsBigDiff AS CASE WHEN (AvgFullSizeMB > 10240. AND ((AvgDiffSizeMB * 100.) / AvgFullSizeMB >= 40.)) THEN 1 ELSE 0 END, + IsBigLog AS CASE WHEN (AvgFullSizeMB > 10240. AND ((AvgLogSizeMB * 100.) / AvgFullSizeMB >= 20.)) THEN 1 ELSE 0 END + ); - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 25 ) - AND SERVERPROPERTY('EngineEdition') <> 8 /* Azure Managed Instances */ - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 25) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT TOP 1 - 25 AS CheckID , - 'tempdb' , - 20 AS Priority , - 'File Configuration' AS FindingsGroup , - 'TempDB on C Drive' AS Finding , - 'https://www.brentozar.com/go/cdrive' AS URL , - CASE WHEN growth > 0 - THEN ( 'The tempdb database has files on the C drive. TempDB frequently grows unpredictably, putting your server at risk of running out of C drive space and crashing hard. C is also often much slower than other drives, so performance may be suffering.' ) - ELSE ( 'The tempdb database has files on the C drive. TempDB is not set to Autogrow, hopefully it is big enough. C is also often much slower than other drives, so performance may be suffering.' ) - END AS Details - FROM sys.master_files - WHERE UPPER(LEFT(physical_name, 1)) = 'C' - AND DB_NAME(database_id) = 'tempdb'; - END; +CREATE TABLE #Trending +( + DatabaseName NVARCHAR(128), + DatabaseGUID UNIQUEIDENTIFIER, + [0] DECIMAL(18, 2), + [-1] DECIMAL(18, 2), + [-2] DECIMAL(18, 2), + [-3] DECIMAL(18, 2), + [-4] DECIMAL(18, 2), + [-5] DECIMAL(18, 2), + [-6] DECIMAL(18, 2), + [-7] DECIMAL(18, 2), + [-8] DECIMAL(18, 2), + [-9] DECIMAL(18, 2), + [-10] DECIMAL(18, 2), + [-11] DECIMAL(18, 2), + [-12] DECIMAL(18, 2) +); - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 26 ) - AND SERVERPROPERTY('EngineEdition') <> 8 /* Azure Managed Instances */ - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 26) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT DISTINCT - 26 AS CheckID , - DB_NAME(database_id) AS DatabaseName , - 20 AS Priority , - 'Reliability' AS FindingsGroup , - 'User Databases on C Drive' AS Finding , - 'https://www.brentozar.com/go/cdrive' AS URL , - ( 'The ' + DB_NAME(database_id) - + ' database has a file on the C drive. Putting databases on the C drive runs the risk of crashing the server when it runs out of space.' ) AS Details - FROM sys.master_files - WHERE UPPER(LEFT(physical_name, 1)) = 'C' - AND DB_NAME(database_id) NOT IN ( 'master', - 'model', 'msdb', - 'tempdb' ) - AND DB_NAME(database_id) NOT IN ( - SELECT DISTINCT - DatabaseName - FROM #SkipChecks - WHERE CheckID IS NULL OR CheckID = 26 ); - END; - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 27 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 27) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 27 AS CheckID , - 'master' AS DatabaseName , - 200 AS Priority , - 'Informational' AS FindingsGroup , - 'Tables in the Master Database' AS Finding , - 'https://www.brentozar.com/go/mastuser' AS URL , - ( 'The ' + name - + ' table in the master database was created by end users on ' - + CAST(create_date AS VARCHAR(20)) - + '. Tables in the master database may not be restored in the event of a disaster.' ) AS Details - FROM master.sys.tables - WHERE is_ms_shipped = 0 - AND name NOT IN ('CommandLog','SqlServerVersions','$ndo$srvproperty'); - /* That last one is the Dynamics NAV licensing table: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2426 */ - END; +CREATE TABLE #Warnings +( + Id INT IDENTITY(1, 1) PRIMARY KEY CLUSTERED, + CheckId INT, + Priority INT, + DatabaseName VARCHAR(128), + Finding VARCHAR(256), + Warning VARCHAR(8000) +); - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 28 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 28) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 28 AS CheckID , - 'msdb' AS DatabaseName , - 200 AS Priority , - 'Informational' AS FindingsGroup , - 'Tables in the MSDB Database' AS Finding , - 'https://www.brentozar.com/go/msdbuser' AS URL , - ( 'The ' + name - + ' table in the msdb database was created by end users on ' - + CAST(create_date AS VARCHAR(20)) - + '. Tables in the msdb database may not be restored in the event of a disaster.' ) AS Details - FROM msdb.sys.tables - WHERE is_ms_shipped = 0 AND name NOT LIKE '%DTA_%'; - END; +IF NOT EXISTS(SELECT * FROM sys.databases WHERE name = @MSDBName) + BEGIN + RAISERROR('@MSDBName was specified, but the database does not exist.', 16, 1) WITH NOWAIT; + RETURN; + END - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 29 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 29) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 29 AS CheckID , - 'model' AS DatabaseName , - 200 AS Priority , - 'Informational' AS FindingsGroup , - 'Tables in the Model Database' AS Finding , - 'https://www.brentozar.com/go/model' AS URL , - ( 'The ' + name - + ' table in the model database was created by end users on ' - + CAST(create_date AS VARCHAR(20)) - + '. Tables in the model database are automatically copied into all new databases.' ) AS Details - FROM model.sys.tables - WHERE is_ms_shipped = 0; - END; +IF @PushBackupHistoryToListener = 1 +GOTO PushBackupHistoryToListener - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 30 ) - BEGIN - IF ( SELECT COUNT(*) - FROM msdb.dbo.sysalerts - WHERE severity BETWEEN 19 AND 25 - ) < 7 - - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 30) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 30 AS CheckID , - 200 AS Priority , - 'Monitoring' AS FindingsGroup , - 'Not All Alerts Configured' AS Finding , - 'https://www.brentozar.com/go/alert' AS URL , - ( 'Not all SQL Server Agent alerts have been configured. This is a free, easy way to get notified of corruption, job failures, or major outages even before monitoring systems pick it up.' ) AS Details; - END; - END; - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 59 ) - BEGIN - IF EXISTS ( SELECT * - FROM msdb.dbo.sysalerts - WHERE enabled = 1 - AND COALESCE(has_notification, 0) = 0 - AND (job_id IS NULL OR job_id = 0x)) + RAISERROR('Inserting to #Backups', 0, 1) WITH NOWAIT; - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 59) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 59 AS CheckID , - 200 AS Priority , - 'Monitoring' AS FindingsGroup , - 'Alerts Configured without Follow Up' AS Finding , - 'https://www.brentozar.com/go/alert' AS URL , - ( 'SQL Server Agent alerts have been configured but they either do not notify anyone or else they do not take any action. This is a free, easy way to get notified of corruption, job failures, or major outages even before monitoring systems pick it up.' ) AS Details; - - END; - END; + SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 96 ) - BEGIN - IF NOT EXISTS ( SELECT * - FROM msdb.dbo.sysalerts - WHERE message_id IN ( 823, 824, 825 ) ) - - BEGIN; - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 96) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 96 AS CheckID , - 200 AS Priority , - 'Monitoring' AS FindingsGroup , - 'No Alerts for Corruption' AS Finding , - 'https://www.brentozar.com/go/alert' AS URL , - ( 'SQL Server Agent alerts do not exist for errors 823, 824, and 825. These three errors can give you notification about early hardware failure. Enabling them can prevent you a lot of heartbreak.' ) AS Details; - - END; - END; + SET @StringToExecute += N'WITH Backups AS (SELECT bs.database_name, bs.database_guid, bs.type AS backup_type ' + @crlf + + ' , MBpsAvg = CAST(AVG(( bs.backup_size / ( CASE WHEN DATEDIFF(ss, bs.backup_start_date, bs.backup_finish_date) = 0 THEN 1 ELSE DATEDIFF(ss, bs.backup_start_date, bs.backup_finish_date) END ) / 1048576 )) AS INT) ' + @crlf + + ' , MBpsMin = CAST(MIN(( bs.backup_size / ( CASE WHEN DATEDIFF(ss, bs.backup_start_date, bs.backup_finish_date) = 0 THEN 1 ELSE DATEDIFF(ss, bs.backup_start_date, bs.backup_finish_date) END ) / 1048576 )) AS INT) ' + @crlf + + ' , MBpsMax = CAST(MAX(( bs.backup_size / ( CASE WHEN DATEDIFF(ss, bs.backup_start_date, bs.backup_finish_date) = 0 THEN 1 ELSE DATEDIFF(ss, bs.backup_start_date, bs.backup_finish_date) END ) / 1048576 )) AS INT) ' + @crlf + + ' , SizeMBAvg = AVG(backup_size / 1048576.0) ' + @crlf + + ' , SizeMBMin = MIN(backup_size / 1048576.0) ' + @crlf + + ' , SizeMBMax = MAX(backup_size / 1048576.0) ' + @crlf + + ' , CompressedSizeMBAvg = AVG(compressed_backup_size / 1048576.0) ' + @crlf + + ' , CompressedSizeMBMin = MIN(compressed_backup_size / 1048576.0) ' + @crlf + + ' , CompressedSizeMBMax = MAX(compressed_backup_size / 1048576.0) ' + @crlf; - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 61 ) - BEGIN - IF NOT EXISTS ( SELECT * - FROM msdb.dbo.sysalerts - WHERE severity BETWEEN 19 AND 25 ) - - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 61) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 61 AS CheckID , - 200 AS Priority , - 'Monitoring' AS FindingsGroup , - 'No Alerts for Sev 19-25' AS Finding , - 'https://www.brentozar.com/go/alert' AS URL , - ( 'SQL Server Agent alerts do not exist for severity levels 19 through 25. These are some very severe SQL Server errors. Knowing that these are happening may let you recover from errors faster.' ) AS Details; - - END; - END; + SET @StringToExecute += N' FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset bs ' + @crlf + + N' WHERE bs.backup_finish_date >= @StartTime AND bs.is_damaged = 0 ' + @crlf + + N' GROUP BY bs.database_name, bs.database_guid, bs.type)' + @crlf; - --check for disabled alerts - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 98 ) - BEGIN - IF EXISTS ( SELECT name - FROM msdb.dbo.sysalerts - WHERE enabled = 0 ) - - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 98) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 98 AS CheckID , - 200 AS Priority , - 'Monitoring' AS FindingsGroup , - 'Alerts Disabled' AS Finding , - 'https://www.brentozar.com/go/alert' AS URL , - ( 'The following Alert is disabled, please review and enable if desired: ' - + name ) AS Details - FROM msdb.dbo.sysalerts - WHERE enabled = 0; - END; - END; + SET @StringToExecute += + N'INSERT INTO #Backups(database_name, database_guid, ' + @crlf + + N' FullMBpsAvg, FullMBpsMin, FullMBpsMax, FullSizeMBAvg, FullSizeMBMin, FullSizeMBMax, FullCompressedSizeMBAvg, FullCompressedSizeMBMin, FullCompressedSizeMBMax, ' + @crlf + + N' DiffMBpsAvg, DiffMBpsMin, DiffMBpsMax, DiffSizeMBAvg, DiffSizeMBMin, DiffSizeMBMax, DiffCompressedSizeMBAvg, DiffCompressedSizeMBMin, DiffCompressedSizeMBMax, ' + @crlf + + N' LogMBpsAvg, LogMBpsMin, LogMBpsMax, LogSizeMBAvg, LogSizeMBMin, LogSizeMBMax, LogCompressedSizeMBAvg, LogCompressedSizeMBMin, LogCompressedSizeMBMax ) ' + @crlf + + N'SELECT bF.database_name, bF.database_guid ' + @crlf + + N' , bF.MBpsAvg AS FullMBpsAvg ' + @crlf + + N' , bF.MBpsMin AS FullMBpsMin ' + @crlf + + N' , bF.MBpsMax AS FullMBpsMax ' + @crlf + + N' , bF.SizeMBAvg AS FullSizeMBAvg ' + @crlf + + N' , bF.SizeMBMin AS FullSizeMBMin ' + @crlf + + N' , bF.SizeMBMax AS FullSizeMBMax ' + @crlf + + N' , bF.CompressedSizeMBAvg AS FullCompressedSizeMBAvg ' + @crlf + + N' , bF.CompressedSizeMBMin AS FullCompressedSizeMBMin ' + @crlf + + N' , bF.CompressedSizeMBMax AS FullCompressedSizeMBMax ' + @crlf + + N' , bD.MBpsAvg AS DiffMBpsAvg ' + @crlf + + N' , bD.MBpsMin AS DiffMBpsMin ' + @crlf + + N' , bD.MBpsMax AS DiffMBpsMax ' + @crlf + + N' , bD.SizeMBAvg AS DiffSizeMBAvg ' + @crlf + + N' , bD.SizeMBMin AS DiffSizeMBMin ' + @crlf + + N' , bD.SizeMBMax AS DiffSizeMBMax ' + @crlf + + N' , bD.CompressedSizeMBAvg AS DiffCompressedSizeMBAvg ' + @crlf + + N' , bD.CompressedSizeMBMin AS DiffCompressedSizeMBMin ' + @crlf + + N' , bD.CompressedSizeMBMax AS DiffCompressedSizeMBMax ' + @crlf + + N' , bL.MBpsAvg AS LogMBpsAvg ' + @crlf + + N' , bL.MBpsMin AS LogMBpsMin ' + @crlf + + N' , bL.MBpsMax AS LogMBpsMax ' + @crlf + + N' , bL.SizeMBAvg AS LogSizeMBAvg ' + @crlf + + N' , bL.SizeMBMin AS LogSizeMBMin ' + @crlf + + N' , bL.SizeMBMax AS LogSizeMBMax ' + @crlf + + N' , bL.CompressedSizeMBAvg AS LogCompressedSizeMBAvg ' + @crlf + + N' , bL.CompressedSizeMBMin AS LogCompressedSizeMBMin ' + @crlf + + N' , bL.CompressedSizeMBMax AS LogCompressedSizeMBMax ' + @crlf + + N' FROM Backups bF ' + @crlf + + N' LEFT OUTER JOIN Backups bD ON bF.database_name = bD.database_name AND bF.database_guid = bD.database_guid AND bD.backup_type = ''I''' + @crlf + + N' LEFT OUTER JOIN Backups bL ON bF.database_name = bL.database_name AND bF.database_guid = bL.database_guid AND bL.backup_type = ''L''' + @crlf + + N' WHERE bF.backup_type = ''D''; ' + @crlf; - --check for alerts that do NOT include event descriptions in their outputs via email/pager/net-send - IF NOT EXISTS ( - SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL - AND CheckID = 219 - ) - BEGIN; - IF @Debug IN (1, 2) - BEGIN; - RAISERROR ('Running CheckId [%d].', 0, 1, 219) WITH NOWAIT; - END; + IF @Debug = 1 + PRINT @StringToExecute; - INSERT INTO #BlitzResults ( - CheckID - ,[Priority] - ,FindingsGroup - ,Finding - ,[URL] - ,Details - ) - SELECT 219 AS CheckID - ,200 AS [Priority] - ,'Monitoring' AS FindingsGroup - ,'Alerts Without Event Descriptions' AS Finding - ,'https://www.brentozar.com/go/alert' AS [URL] - ,('The following Alert is not including detailed event descriptions in its output messages: ' + QUOTENAME([name]) - + '. You can fix it by ticking the relevant boxes in its Properties --> Options page.') AS Details - FROM msdb.dbo.sysalerts - WHERE [enabled] = 1 - AND include_event_description = 0 --bitmask: 1 = email, 2 = pager, 4 = net send - ; - END; + EXEC sys.sp_executesql @StringToExecute, N'@StartTime DATETIME2', @StartTime; - --check whether we have NO ENABLED operators! - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 31 ) - BEGIN; - IF NOT EXISTS ( SELECT * - FROM msdb.dbo.sysoperators - WHERE enabled = 1 ) - - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 31) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 31 AS CheckID , - 200 AS Priority , - 'Monitoring' AS FindingsGroup , - 'No Operators Configured/Enabled' AS Finding , - 'https://www.brentozar.com/go/op' AS URL , - ( 'No SQL Server Agent operators (emails) have been configured. This is a free, easy way to get notified of corruption, job failures, or major outages even before monitoring systems pick it up.' ) AS Details; - - END; - END; - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 34 ) - BEGIN - IF EXISTS ( SELECT * - FROM sys.all_objects - WHERE name = 'dm_db_mirroring_auto_page_repair' ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 34) WITH NOWAIT; - - SET @StringToExecute = 'INSERT INTO #BlitzResults - (CheckID, - DatabaseName, - Priority, - FindingsGroup, - Finding, - URL, - Details) - SELECT DISTINCT - 34 AS CheckID , - db.name , - 1 AS Priority , - ''Corruption'' AS FindingsGroup , - ''Database Corruption Detected'' AS Finding , - ''https://www.brentozar.com/go/repair'' AS URL , - ( ''Database mirroring has automatically repaired at least one corrupt page in the last 30 days. For more information, query the DMV sys.dm_db_mirroring_auto_page_repair.'' ) AS Details - FROM (SELECT rp2.database_id, rp2.modification_time - FROM sys.dm_db_mirroring_auto_page_repair rp2 - WHERE rp2.[database_id] not in ( - SELECT db2.[database_id] - FROM sys.databases as db2 - WHERE db2.[state] = 1 - ) ) as rp - INNER JOIN master.sys.databases db ON rp.database_id = db.database_id - WHERE rp.modification_time >= DATEADD(dd, -30, GETDATE()) OPTION (RECOMPILE);'; - - IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; - IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; - - EXECUTE(@StringToExecute); - END; - END; + RAISERROR('Updating #Backups with worst RPO case', 0, 1) WITH NOWAIT; - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 89 ) - BEGIN - IF EXISTS ( SELECT * - FROM sys.all_objects - WHERE name = 'dm_hadr_auto_page_repair' ) - BEGIN + SET @StringToExecute =N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 89) WITH NOWAIT; - - SET @StringToExecute = 'INSERT INTO #BlitzResults - (CheckID, - DatabaseName, - Priority, - FindingsGroup, - Finding, - URL, - Details) - SELECT DISTINCT - 89 AS CheckID , - db.name , - 1 AS Priority , - ''Corruption'' AS FindingsGroup , - ''Database Corruption Detected'' AS Finding , - ''https://www.brentozar.com/go/repair'' AS URL , - ( ''Availability Groups has automatically repaired at least one corrupt page in the last 30 days. For more information, query the DMV sys.dm_hadr_auto_page_repair.'' ) AS Details - FROM sys.dm_hadr_auto_page_repair rp - INNER JOIN master.sys.databases db ON rp.database_id = db.database_id - WHERE rp.modification_time >= DATEADD(dd, -30, GETDATE()) OPTION (RECOMPILE) ;'; - - IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; - IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; - - EXECUTE(@StringToExecute); - END; - END; + SET @StringToExecute += N' + SELECT bs.database_name, bs.database_guid, bs.backup_set_id, bsPrior.backup_set_id AS backup_set_id_prior, + bs.backup_finish_date, bsPrior.backup_finish_date AS backup_finish_date_prior, + DATEDIFF(ss, bsPrior.backup_finish_date, bs.backup_finish_date) AS backup_gap_seconds + INTO #backup_gaps + FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS bs + CROSS APPLY ( + SELECT TOP 1 bs1.backup_set_id, bs1.backup_finish_date + FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS bs1 + WHERE bs.database_name = bs1.database_name + AND bs.database_guid = bs1.database_guid + AND bs.backup_finish_date > bs1.backup_finish_date + AND bs.backup_set_id > bs1.backup_set_id + ORDER BY bs1.backup_finish_date DESC, bs1.backup_set_id DESC + ) bsPrior + WHERE bs.backup_finish_date > @StartTime + + CREATE CLUSTERED INDEX cx_backup_gaps ON #backup_gaps (database_name, database_guid, backup_set_id, backup_finish_date, backup_gap_seconds); + + WITH max_gaps AS ( + SELECT g.database_name, g.database_guid, g.backup_set_id, g.backup_set_id_prior, g.backup_finish_date_prior, + g.backup_finish_date, MAX(g.backup_gap_seconds) AS max_backup_gap_seconds + FROM #backup_gaps AS g + GROUP BY g.database_name, g.database_guid, g.backup_set_id, g.backup_set_id_prior, g.backup_finish_date_prior, g.backup_finish_date + ) + UPDATE #Backups + SET RPOWorstCaseMinutes = bg.max_backup_gap_seconds / 60.0 + , RPOWorstCaseBackupSetID = bg.backup_set_id + , RPOWorstCaseBackupSetFinishTime = bg.backup_finish_date + , RPOWorstCaseBackupSetIDPrior = bg.backup_set_id_prior + , RPOWorstCaseBackupSetPriorFinishTime = bg.backup_finish_date_prior + FROM #Backups b + INNER HASH JOIN max_gaps bg ON b.database_name = bg.database_name AND b.database_guid = bg.database_guid + LEFT OUTER HASH JOIN max_gaps bgBigger ON bg.database_name = bgBigger.database_name AND bg.database_guid = bgBigger.database_guid AND bg.max_backup_gap_seconds < bgBigger.max_backup_gap_seconds + WHERE bgBigger.backup_set_id IS NULL; + '; + IF @Debug = 1 + PRINT @StringToExecute; - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 90 ) - BEGIN - IF EXISTS ( SELECT * - FROM msdb.sys.all_objects - WHERE name = 'suspect_pages' ) - BEGIN + EXEC sys.sp_executesql @StringToExecute, N'@StartTime DATETIME2', @StartTime; - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 90) WITH NOWAIT; - - SET @StringToExecute = 'INSERT INTO #BlitzResults - (CheckID, - DatabaseName, - Priority, - FindingsGroup, - Finding, - URL, - Details) - SELECT DISTINCT - 90 AS CheckID , - db.name , - 1 AS Priority , - ''Corruption'' AS FindingsGroup , - ''Database Corruption Detected'' AS Finding , - ''https://www.brentozar.com/go/repair'' AS URL , - ( ''SQL Server has detected at least one corrupt page in the last 30 days. For more information, query the system table msdb.dbo.suspect_pages.'' ) AS Details - FROM msdb.dbo.suspect_pages sp - INNER JOIN master.sys.databases db ON sp.database_id = db.database_id - WHERE sp.last_update_date >= DATEADD(dd, -30, GETDATE()) OPTION (RECOMPILE);'; - - IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; - IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; - - EXECUTE(@StringToExecute); - END; - END; + RAISERROR('Updating #Backups with worst RPO case queries', 0, 1) WITH NOWAIT; - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 36 ) - BEGIN + UPDATE #Backups + SET RPOWorstCaseMoreInfoQuery = @MoreInfoHeader + N'SELECT * ' + @crlf + + N' FROM ' + QUOTENAME(@MSDBName) + '.dbo.backupset ' + @crlf + + N' WHERE database_name = ''' + database_name + ''' ' + @crlf + + N' AND database_guid = ''' + CAST(database_guid AS NVARCHAR(50)) + ''' ' + @crlf + + N' AND backup_finish_date >= DATEADD(hh, -2, ''' + CAST(CONVERT(DATETIME, RPOWorstCaseBackupSetPriorFinishTime, 102) AS NVARCHAR(100)) + ''') ' + @crlf + + N' AND backup_finish_date <= DATEADD(hh, 2, ''' + CAST(CONVERT(DATETIME, RPOWorstCaseBackupSetPriorFinishTime, 102) AS NVARCHAR(100)) + ''') ' + @crlf + + N' ORDER BY backup_finish_date;' + + @MoreInfoFooter; - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 36) WITH NOWAIT; - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT DISTINCT - 36 AS CheckID , - 150 AS Priority , - 'Performance' AS FindingsGroup , - 'Slow Storage Reads on Drive ' - + UPPER(LEFT(mf.physical_name, 1)) AS Finding , - 'https://www.brentozar.com/go/slow' AS URL , - 'Reads are averaging longer than 200ms for at least one database on this drive. For specific database file speeds, run the query from the information link.' AS Details - FROM sys.dm_io_virtual_file_stats(NULL, NULL) - AS fs - INNER JOIN sys.master_files AS mf ON fs.database_id = mf.database_id - AND fs.[file_id] = mf.[file_id] - WHERE ( io_stall_read_ms / ( 1.0 + num_of_reads ) ) > 200 - AND num_of_reads > 100000; - END; +/* RTO */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 37 ) - BEGIN +RAISERROR('Gathering RTO information', 0, 1) WITH NOWAIT; - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 37) WITH NOWAIT; - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT DISTINCT - 37 AS CheckID , - 150 AS Priority , - 'Performance' AS FindingsGroup , - 'Slow Storage Writes on Drive ' - + UPPER(LEFT(mf.physical_name, 1)) AS Finding , - 'https://www.brentozar.com/go/slow' AS URL , - 'Writes are averaging longer than 100ms for at least one database on this drive. For specific database file speeds, run the query from the information link.' AS Details - FROM sys.dm_io_virtual_file_stats(NULL, NULL) - AS fs - INNER JOIN sys.master_files AS mf ON fs.database_id = mf.database_id - AND fs.[file_id] = mf.[file_id] - WHERE ( io_stall_write_ms / ( 1.0 - + num_of_writes ) ) > 100 - AND num_of_writes > 100000; - END; + SET @StringToExecute =N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 40 ) - BEGIN - IF ( SELECT COUNT(*) - FROM tempdb.sys.database_files - WHERE type_desc = 'ROWS' - ) = 1 - BEGIN + SET @StringToExecute += N' + INSERT INTO #RTORecoveryPoints(database_name, database_guid, log_last_lsn) + SELECT database_name, database_guid, MAX(last_lsn) AS log_last_lsn + FROM ' + QUOTENAME(@MSDBName) + '.dbo.backupset bLastLog + WHERE type = ''L'' + AND bLastLog.backup_finish_date >= @StartTime + GROUP BY database_name, database_guid; + '; - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 40) WITH NOWAIT; + IF @Debug = 1 + PRINT @StringToExecute; - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - VALUES ( 40 , - 'tempdb' , - 170 , - 'File Configuration' , - 'TempDB Only Has 1 Data File' , - 'https://www.brentozar.com/go/tempdb' , - 'TempDB is only configured with one data file. More data files are usually required to alleviate SGAM contention.' - ); - END; - END; + EXEC sys.sp_executesql @StringToExecute, N'@StartTime DATETIME2', @StartTime; - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 183 ) +/* Find the most recent full backups for those logs */ - BEGIN +RAISERROR('Updating #RTORecoveryPoints', 0, 1) WITH NOWAIT; - IF ( SELECT COUNT (distinct [size]) - FROM tempdb.sys.database_files - WHERE type_desc = 'ROWS' - HAVING MAX((size * 8) / (1024. * 1024)) - MIN((size * 8) / (1024. * 1024)) > 1. - ) <> 1 - BEGIN + SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 183) WITH NOWAIT; + SET @StringToExecute += N' + UPDATE #RTORecoveryPoints + SET log_backup_set_id = bLasted.backup_set_id + ,full_backup_set_id = bLasted.backup_set_id + ,full_last_lsn = bLasted.last_lsn + ,full_backup_set_uuid = bLasted.backup_set_uuid + FROM #RTORecoveryPoints rp + CROSS APPLY ( + SELECT TOP 1 bLog.backup_set_id AS backup_set_id_log, bLastFull.backup_set_id, bLastFull.last_lsn, bLastFull.backup_set_uuid, bLastFull.database_guid, bLastFull.database_name + FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset bLog + INNER JOIN ' + QUOTENAME(@MSDBName) + N'.dbo.backupset bLastFull + ON bLog.database_guid = bLastFull.database_guid + AND bLog.database_name = bLastFull.database_name + AND bLog.first_lsn > bLastFull.last_lsn + AND bLastFull.type = ''D'' + WHERE rp.database_guid = bLog.database_guid + AND rp.database_name = bLog.database_name + ) bLasted + LEFT OUTER JOIN ' + QUOTENAME(@MSDBName) + N'.dbo.backupset bLaterFulls ON bLasted.database_guid = bLaterFulls.database_guid AND bLasted.database_name = bLaterFulls.database_name + AND bLasted.last_lsn < bLaterFulls.last_lsn + AND bLaterFulls.first_lsn < bLasted.last_lsn + AND bLaterFulls.type = ''D'' + WHERE bLaterFulls.backup_set_id IS NULL; + '; - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - VALUES ( 183 , - 'tempdb' , - 170 , - 'File Configuration' , - 'TempDB Unevenly Sized Data Files' , - 'https://www.brentozar.com/go/tempdb' , - 'TempDB data files are not configured with the same size. Unevenly sized tempdb data files will result in unevenly sized workloads.' - ); - END; - END; + IF @Debug = 1 + PRINT @StringToExecute; - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 44 ) - BEGIN + EXEC sys.sp_executesql @StringToExecute; - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 44) WITH NOWAIT; +/* Add any full backups in the StartDate range that weren't part of the above log backup chain */ - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 44 AS CheckID , - 150 AS Priority , - 'Performance' AS FindingsGroup , - 'Queries Forcing Order Hints' AS Finding , - 'https://www.brentozar.com/go/hints' AS URL , - CAST(occurrence AS VARCHAR(10)) - + ' instances of order hinting have been recorded since restart. This means queries are bossing the SQL Server optimizer around, and if they don''t know what they''re doing, this can cause more harm than good. This can also explain why DBA tuning efforts aren''t working.' AS Details - FROM sys.dm_exec_query_optimizer_info - WHERE counter = 'order hint' - AND occurrence > 1000; - END; +RAISERROR('Add any full backups in the StartDate range that weren''t part of the above log backup chain', 0, 1) WITH NOWAIT; - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 45 ) - BEGIN + SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 45) WITH NOWAIT; + SET @StringToExecute += N' + INSERT INTO #RTORecoveryPoints(database_name, database_guid, full_backup_set_id, full_last_lsn, full_backup_set_uuid) + SELECT bFull.database_name, bFull.database_guid, bFull.backup_set_id, bFull.last_lsn, bFull.backup_set_uuid + FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset bFull + LEFT OUTER JOIN #RTORecoveryPoints rp ON bFull.backup_set_uuid = rp.full_backup_set_uuid + WHERE bFull.type = ''D'' + AND bFull.backup_finish_date IS NOT NULL + AND rp.full_backup_set_uuid IS NULL + AND bFull.backup_finish_date >= @StartTime; + '; - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 45 AS CheckID , - 150 AS Priority , - 'Performance' AS FindingsGroup , - 'Queries Forcing Join Hints' AS Finding , - 'https://www.brentozar.com/go/hints' AS URL , - CAST(occurrence AS VARCHAR(10)) - + ' instances of join hinting have been recorded since restart. This means queries are bossing the SQL Server optimizer around, and if they don''t know what they''re doing, this can cause more harm than good. This can also explain why DBA tuning efforts aren''t working.' AS Details - FROM sys.dm_exec_query_optimizer_info - WHERE counter = 'join hint' - AND occurrence > 1000; - END; + IF @Debug = 1 + PRINT @StringToExecute; - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 49 ) - BEGIN + EXEC sys.sp_executesql @StringToExecute, N'@StartTime DATETIME2', @StartTime; - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 49) WITH NOWAIT; +/* Fill out the most recent log for that full, but before the next full */ - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT DISTINCT - 49 AS CheckID , - 200 AS Priority , - 'Informational' AS FindingsGroup , - 'Linked Server Configured' AS Finding , - 'https://www.brentozar.com/go/link' AS URL , - +CASE WHEN l.remote_name = 'sa' - THEN COALESCE(s.data_source, s.provider) - + ' is configured as a linked server. Check its security configuration as it is connecting with sa, because any user who queries it will get admin-level permissions.' - ELSE COALESCE(s.data_source, s.provider) - + ' is configured as a linked server. Check its security configuration to make sure it isn''t connecting with SA or some other bone-headed administrative login, because any user who queries it might get admin-level permissions.' - END AS Details - FROM sys.servers s - INNER JOIN sys.linked_logins l ON s.server_id = l.server_id - WHERE s.is_linked = 1; - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 50 ) - BEGIN - IF @@VERSION NOT LIKE '%Microsoft SQL Server 2000%' - AND @@VERSION NOT LIKE '%Microsoft SQL Server 2005%' - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 50) WITH NOWAIT; - - SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) - SELECT 50 AS CheckID , - 100 AS Priority , - ''Performance'' AS FindingsGroup , - ''Max Memory Set Too High'' AS Finding , - ''https://www.brentozar.com/go/max'' AS URL , - ''SQL Server max memory is set to '' - + CAST(c.value_in_use AS VARCHAR(20)) - + '' megabytes, but the server only has '' - + CAST(( CAST(m.total_physical_memory_kb AS BIGINT) / 1024 ) AS VARCHAR(20)) - + '' megabytes. SQL Server may drain the system dry of memory, and under certain conditions, this can cause Windows to swap to disk.'' AS Details - FROM sys.dm_os_sys_memory m - INNER JOIN sys.configurations c ON c.name = ''max server memory (MB)'' - WHERE CAST(m.total_physical_memory_kb AS BIGINT) < ( CAST(c.value_in_use AS BIGINT) * 1024 ) OPTION (RECOMPILE);'; - - IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; - IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; - - EXECUTE(@StringToExecute); - END; - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 51 ) - BEGIN - IF @@VERSION NOT LIKE '%Microsoft SQL Server 2000%' - AND @@VERSION NOT LIKE '%Microsoft SQL Server 2005%' - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 51) WITH NOWAIT - - SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) - SELECT 51 AS CheckID , - 1 AS Priority , - ''Performance'' AS FindingsGroup , - ''Memory Dangerously Low'' AS Finding , - ''https://www.brentozar.com/go/max'' AS URL , - ''The server has '' + CAST(( CAST(m.total_physical_memory_kb AS BIGINT) / 1024 ) AS VARCHAR(20)) + '' megabytes of physical memory, but only '' + CAST(( CAST(m.available_physical_memory_kb AS BIGINT) / 1024 ) AS VARCHAR(20)) - + '' megabytes are available. As the server runs out of memory, there is danger of swapping to disk, which will kill performance.'' AS Details - FROM sys.dm_os_sys_memory m - WHERE CAST(m.available_physical_memory_kb AS BIGINT) < 262144 OPTION (RECOMPILE);'; - - IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; - IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; - - EXECUTE(@StringToExecute); - END; - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 159 ) - BEGIN - IF @@VERSION NOT LIKE '%Microsoft SQL Server 2000%' - AND @@VERSION NOT LIKE '%Microsoft SQL Server 2005%' - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 159) WITH NOWAIT; - - SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) - SELECT DISTINCT 159 AS CheckID , - 1 AS Priority , - ''Performance'' AS FindingsGroup , - ''Memory Dangerously Low in NUMA Nodes'' AS Finding , - ''https://www.brentozar.com/go/max'' AS URL , - ''At least one NUMA node is reporting THREAD_RESOURCES_LOW in sys.dm_os_nodes and can no longer create threads.'' AS Details - FROM sys.dm_os_nodes m - WHERE node_state_desc LIKE ''%THREAD_RESOURCES_LOW%'' OPTION (RECOMPILE);'; - - IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; - IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; - - EXECUTE(@StringToExecute); - END; - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 53 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 53) WITH NOWAIT; - - DECLARE @AOFCI AS INT, @AOAG AS INT, @HAType AS VARCHAR(10), @errmsg AS VARCHAR(200) - - SELECT @AOAG = CAST(SERVERPROPERTY('IsHadrEnabled') AS INT) - SELECT @AOFCI = CAST(SERVERPROPERTY('IsClustered') AS INT) - - IF (SELECT COUNT(DISTINCT join_state_desc) FROM sys.dm_hadr_availability_replica_cluster_states) = 2 - BEGIN --if count is 2 both JOINED_STANDALONE and JOINED_FCI is configured - SET @AOFCI = 1 - END - - SELECT @HAType = - CASE - WHEN @AOFCI = 1 AND @AOAG =1 THEN 'FCIAG' - WHEN @AOFCI = 1 AND @AOAG =0 THEN 'FCI' - WHEN @AOFCI = 0 AND @AOAG =1 THEN 'AG' - ELSE 'STANDALONE' - END - - IF (@HAType IN ('FCIAG','FCI','AG')) - BEGIN - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - - SELECT DISTINCT 53 AS CheckID , - 200 AS Priority , - 'Informational' AS FindingsGroup , - 'Cluster Node' AS Finding , - 'https://BrentOzar.com/go/node' AS URL , - 'This is a node in a cluster.' AS Details - - IF @HAType = 'AG' - BEGIN - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT DISTINCT 53 AS CheckID , - 200 AS Priority , - 'Informational' AS FindingsGroup , - 'Cluster Node Info' AS Finding , - 'https://BrentOzar.com/go/node' AS URL, - 'The cluster nodes are: ' + STUFF((SELECT ', ' + CASE ar.replica_server_name WHEN dhags.primary_replica THEN 'PRIMARY' - ELSE 'SECONDARY' - END + '=' + UPPER(ar.replica_server_name) - FROM sys.availability_groups AS ag - LEFT OUTER JOIN sys.availability_replicas AS ar ON ag.group_id = ar.group_id - LEFT OUTER JOIN sys.dm_hadr_availability_group_states as dhags ON ag.group_id = dhags.group_id - ORDER BY 1 - FOR XML PATH ('') - ), 1, 1, '') + ' - High Availability solution used is AlwaysOn Availability Group (AG)' As Details - END - - IF @HAType = 'FCI' - BEGIN - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT DISTINCT 53 AS CheckID , - 200 AS Priority , - 'Informational' AS FindingsGroup , - 'Cluster Node Info' AS Finding , - 'https://BrentOzar.com/go/node' AS URL, - 'The cluster nodes are: ' + STUFF((SELECT ', ' + CASE is_current_owner - WHEN 1 THEN 'PRIMARY' - ELSE 'SECONDARY' - END + '=' + UPPER(NodeName) - FROM sys.dm_os_cluster_nodes - ORDER BY 1 - FOR XML PATH ('') - ), 1, 1, '') + ' - High Availability solution used is AlwaysOn Failover Cluster Instance (FCI)' As Details - END - - IF @HAType = 'FCIAG' - BEGIN - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT DISTINCT 53 AS CheckID , - 200 AS Priority , - 'Informational' AS FindingsGroup , - 'Cluster Node Info' AS Finding , - 'https://BrentOzar.com/go/node' AS URL, - 'The cluster nodes are: ' + STUFF((SELECT ', ' + HighAvailabilityRoleDesc + '=' + ServerName - FROM (SELECT UPPER(ar.replica_server_name) AS ServerName - ,CASE ar.replica_server_name WHEN dhags.primary_replica THEN 'PRIMARY' - ELSE 'SECONDARY' - END AS HighAvailabilityRoleDesc - FROM sys.availability_groups AS ag - LEFT OUTER JOIN sys.availability_replicas AS ar ON ag.group_id = ar.group_id - LEFT OUTER JOIN sys.dm_hadr_availability_group_states as dhags ON ag.group_id = dhags.group_id - WHERE UPPER(CAST(SERVERPROPERTY('ServerName') AS VARCHAR)) <> ar.replica_server_name - UNION ALL - SELECT UPPER(NodeName) AS ServerName - ,CASE is_current_owner - WHEN 1 THEN 'PRIMARY' - ELSE 'SECONDARY' - END AS HighAvailabilityRoleDesc - FROM sys.dm_os_cluster_nodes) AS Z - ORDER BY 1 - FOR XML PATH ('') - ), 1, 1, '') + ' - High Availability solution used is AlwaysOn FCI with AG (Failover Cluster Instance with Availability Group).'As Details - END - END +RAISERROR('Fill out the most recent log for that full, but before the next full', 0, 1) WITH NOWAIT; - END; + SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 55 ) - BEGIN + SET @StringToExecute += N' + UPDATE rp + SET log_last_lsn = (SELECT MAX(last_lsn) FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset bLog WHERE bLog.first_lsn >= rp.full_last_lsn AND bLog.first_lsn <= rpNextFull.full_last_lsn AND bLog.type = ''L'') + FROM #RTORecoveryPoints rp + INNER JOIN #RTORecoveryPoints rpNextFull ON rp.database_guid = rpNextFull.database_guid AND rp.database_name = rpNextFull.database_name + AND rp.full_last_lsn < rpNextFull.full_last_lsn + LEFT OUTER JOIN #RTORecoveryPoints rpEarlierFull ON rp.database_guid = rpEarlierFull.database_guid AND rp.database_name = rpEarlierFull.database_name + AND rp.full_last_lsn < rpEarlierFull.full_last_lsn + AND rpNextFull.full_last_lsn > rpEarlierFull.full_last_lsn + WHERE rpEarlierFull.full_backup_set_id IS NULL; + '; - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 55) WITH NOWAIT; + IF @Debug = 1 + PRINT @StringToExecute; - IF @UsualDBOwner IS NULL - SET @UsualDBOwner = SUSER_SNAME(0x01); + EXEC sys.sp_executesql @StringToExecute; - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 55 AS CheckID , - [name] AS DatabaseName , - 230 AS Priority , - 'Security' AS FindingsGroup , - 'Database Owner <> ' + @UsualDBOwner AS Finding , - 'https://www.brentozar.com/go/owndb' AS URL , - ( 'Database name: ' + [name] + ' ' - + 'Owner name: ' + SUSER_SNAME(owner_sid) ) AS Details - FROM sys.databases - WHERE (((SUSER_SNAME(owner_sid) <> SUSER_SNAME(0x01)) AND (name IN (N'master', N'model', N'msdb', N'tempdb'))) - OR ((SUSER_SNAME(owner_sid) <> @UsualDBOwner) AND (name NOT IN (N'master', N'model', N'msdb', N'tempdb'))) - ) - AND name NOT IN ( SELECT DISTINCT DatabaseName - FROM #SkipChecks - WHERE CheckID IS NULL OR CheckID = 55); - END; +/* Fill out a diff in that range */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 213 ) - BEGIN +RAISERROR('Fill out a diff in that range', 0, 1) WITH NOWAIT; - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 213) WITH NOWAIT; + SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 213 AS CheckID , - [name] AS DatabaseName , - 230 AS Priority , - 'Security' AS FindingsGroup , - 'Database Owner is Unknown' AS Finding , - '' AS URL , - ( 'Database name: ' + [name] + ' ' - + 'Owner name: ' + ISNULL(SUSER_SNAME(owner_sid),'~~ UNKNOWN ~~') ) AS Details - FROM sys.databases - WHERE SUSER_SNAME(owner_sid) is NULL - AND name NOT IN ( SELECT DISTINCT DatabaseName - FROM #SkipChecks - WHERE CheckID IS NULL OR CheckID = 213); - END; + SET @StringToExecute += N' + UPDATE #RTORecoveryPoints + SET diff_last_lsn = (SELECT TOP 1 bDiff.last_lsn FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset bDiff + WHERE rp.database_guid = bDiff.database_guid AND rp.database_name = bDiff.database_name + AND bDiff.type = ''I'' + AND bDiff.last_lsn < rp.log_last_lsn + AND rp.full_backup_set_uuid = bDiff.differential_base_guid + ORDER BY bDiff.last_lsn DESC) + FROM #RTORecoveryPoints rp + WHERE diff_last_lsn IS NULL; + '; - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 57 ) - BEGIN + IF @Debug = 1 + PRINT @StringToExecute; - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 57) WITH NOWAIT; + EXEC sys.sp_executesql @StringToExecute; - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 57 AS CheckID , - 230 AS Priority , - 'Security' AS FindingsGroup , - 'SQL Agent Job Runs at Startup' AS Finding , - 'https://www.brentozar.com/go/startup' AS URL , - ( 'Job [' + j.name - + '] runs automatically when SQL Server Agent starts up. Make sure you know exactly what this job is doing, because it could pose a security risk.' ) AS Details - FROM msdb.dbo.sysschedules sched - JOIN msdb.dbo.sysjobschedules jsched ON sched.schedule_id = jsched.schedule_id - JOIN msdb.dbo.sysjobs j ON jsched.job_id = j.job_id - WHERE sched.freq_type = 64 - AND sched.enabled = 1; - END; +/* Get time & size totals for full & diff */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 97 ) - BEGIN +RAISERROR('Get time & size totals for full & diff', 0, 1) WITH NOWAIT; - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 97) WITH NOWAIT; + SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 97 AS CheckID , - 100 AS Priority , - 'Performance' AS FindingsGroup , - 'Unusual SQL Server Edition' AS Finding , - 'https://www.brentozar.com/go/workgroup' AS URL , - ( 'This server is using ' - + CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) - + ', which is capped at low amounts of CPU and memory.' ) AS Details - WHERE CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) NOT LIKE '%Standard%' - AND CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) NOT LIKE '%Enterprise%' - AND CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) NOT LIKE '%Data Center%' - AND CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) NOT LIKE '%Developer%' - AND CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) NOT LIKE '%Business Intelligence%'; - END; + SET @StringToExecute += N' + UPDATE #RTORecoveryPoints + SET full_time_seconds = DATEDIFF(ss,bFull.backup_start_date, bFull.backup_finish_date) + , full_file_size_mb = bFull.backup_size / 1048576.0 + , diff_backup_set_id = bDiff.backup_set_id + , diff_time_seconds = DATEDIFF(ss,bDiff.backup_start_date, bDiff.backup_finish_date) + , diff_file_size_mb = bDiff.backup_size / 1048576.0 + FROM #RTORecoveryPoints rp + INNER JOIN ' + QUOTENAME(@MSDBName) + N'.dbo.backupset bFull ON rp.database_guid = bFull.database_guid AND rp.database_name = bFull.database_name AND rp.full_last_lsn = bFull.last_lsn + LEFT OUTER JOIN ' + QUOTENAME(@MSDBName) + N'.dbo.backupset bDiff ON rp.database_guid = bDiff.database_guid AND rp.database_name = bDiff.database_name AND rp.diff_last_lsn = bDiff.last_lsn AND bDiff.last_lsn IS NOT NULL; + '; - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 154 ) - AND SERVERPROPERTY('EngineEdition') <> 8 - BEGIN + IF @Debug = 1 + PRINT @StringToExecute; + + EXEC sys.sp_executesql @StringToExecute; - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 154) WITH NOWAIT; - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 154 AS CheckID , - 10 AS Priority , - 'Performance' AS FindingsGroup , - '32-bit SQL Server Installed' AS Finding , - 'https://www.brentozar.com/go/32bit' AS URL , - ( 'This server uses the 32-bit x86 binaries for SQL Server instead of the 64-bit x64 binaries. The amount of memory available for query workspace and execution plans is heavily limited.' ) AS Details - WHERE CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) NOT LIKE '%64%'; - END; +/* Get time & size totals for logs */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 62 ) - BEGIN +RAISERROR('Get time & size totals for logs', 0, 1) WITH NOWAIT; - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 62) WITH NOWAIT; + SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details + SET @StringToExecute += N' + WITH LogTotals AS ( + SELECT rp.id, log_time_seconds = SUM(DATEDIFF(ss,bLog.backup_start_date, bLog.backup_finish_date)) + , log_file_size = SUM(bLog.backup_size) + , SUM(1) AS log_backups + FROM #RTORecoveryPoints rp + INNER JOIN ' + QUOTENAME(@MSDBName) + N'.dbo.backupset bLog ON rp.database_guid = bLog.database_guid AND rp.database_name = bLog.database_name AND bLog.type = ''L'' + AND bLog.first_lsn > COALESCE(rp.diff_last_lsn, rp.full_last_lsn) + AND bLog.first_lsn <= rp.log_last_lsn + GROUP BY rp.id ) - SELECT 62 AS CheckID , - [name] AS DatabaseName , - 200 AS Priority , - 'Performance' AS FindingsGroup , - 'Old Compatibility Level' AS Finding , - 'https://www.brentozar.com/go/compatlevel' AS URL , - ( 'Database ' + [name] - + ' is compatibility level ' - + CAST(compatibility_level AS VARCHAR(20)) - + ', which may cause unwanted results when trying to run queries that have newer T-SQL features.' ) AS Details - FROM sys.databases - WHERE name NOT IN ( SELECT DISTINCT - DatabaseName - FROM #SkipChecks - WHERE CheckID IS NULL OR CheckID = 62) - AND compatibility_level <= 90; - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 94 ) - BEGIN + UPDATE #RTORecoveryPoints + SET log_time_seconds = lt.log_time_seconds + , log_file_size_mb = lt.log_file_size / 1048576.0 + , log_backups = lt.log_backups + FROM #RTORecoveryPoints rp + INNER JOIN LogTotals lt ON rp.id = lt.id; + '; - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 94) WITH NOWAIT; + IF @Debug = 1 + PRINT @StringToExecute; - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 94 AS CheckID , - 200 AS [Priority] , - 'Monitoring' AS FindingsGroup , - 'Agent Jobs Without Failure Emails' AS Finding , - 'https://www.brentozar.com/go/alerts' AS URL , - 'The job ' + [name] - + ' has not been set up to notify an operator if it fails.' AS Details - FROM msdb.[dbo].[sysjobs] j - WHERE j.enabled = 1 - AND j.notify_email_operator_id = 0 - AND j.notify_netsend_operator_id = 0 - AND j.notify_page_operator_id = 0 - AND j.category_id <> 100; /* Exclude SSRS category */ - END; + EXEC sys.sp_executesql @StringToExecute; - IF EXISTS ( SELECT 1 - FROM sys.configurations - WHERE name = 'remote admin connections' - AND value_in_use = 0 ) - AND NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 100 ) - BEGIN +RAISERROR('Gathering RTO worst cases', 0, 1) WITH NOWAIT; - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 100) WITH NOWAIT; + SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details + SET @StringToExecute += N' + WITH WorstCases AS ( + SELECT rp.* + FROM #RTORecoveryPoints rp + LEFT OUTER JOIN #RTORecoveryPoints rpNewer + ON rp.database_guid = rpNewer.database_guid + AND rp.database_name = rpNewer.database_name + AND rp.full_last_lsn < rpNewer.full_last_lsn + AND rpNewer.rto_worst_case_size_mb = (SELECT TOP 1 rto_worst_case_size_mb FROM #RTORecoveryPoints s WHERE rp.database_guid = s.database_guid AND rp.database_name = s.database_name ORDER BY rto_worst_case_size_mb DESC) + WHERE rp.rto_worst_case_size_mb = (SELECT TOP 1 rto_worst_case_size_mb FROM #RTORecoveryPoints s WHERE rp.database_guid = s.database_guid AND rp.database_name = s.database_name ORDER BY rto_worst_case_size_mb DESC) + /* OR rp.rto_worst_case_time_seconds = (SELECT TOP 1 rto_worst_case_time_seconds FROM #RTORecoveryPoints s WHERE rp.database_guid = s.database_guid AND rp.database_name = s.database_name ORDER BY rto_worst_case_time_seconds DESC) */ + AND rpNewer.database_guid IS NULL ) - SELECT 100 AS CheckID , - 170 AS Priority , - 'Reliability' AS FindingGroup , - 'Remote DAC Disabled' AS Finding , - 'https://www.brentozar.com/go/dac' AS URL , - 'Remote access to the Dedicated Admin Connection (DAC) is not enabled. The DAC can make remote troubleshooting much easier when SQL Server is unresponsive.'; - END; - - IF EXISTS ( SELECT * - FROM sys.dm_os_schedulers - WHERE is_online = 0 ) - AND NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 101 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 101) WITH NOWAIT; + UPDATE #Backups + SET RTOWorstCaseMinutes = + /* Fulls */ + (CASE WHEN @RestoreSpeedFullMBps IS NULL + THEN wc.full_time_seconds / 60.0 + ELSE @RestoreSpeedFullMBps / wc.full_file_size_mb + END) - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 101 AS CheckID , - 50 AS Priority , - 'Performance' AS FindingGroup , - 'CPU Schedulers Offline' AS Finding , - 'https://www.brentozar.com/go/schedulers' AS URL , - 'Some CPU cores are not accessible to SQL Server due to affinity masking or licensing problems.'; - END; + /* Diffs, which might not have been taken */ + + (CASE WHEN @RestoreSpeedDiffMBps IS NOT NULL AND wc.diff_file_size_mb IS NOT NULL + THEN @RestoreSpeedDiffMBps / wc.diff_file_size_mb + ELSE COALESCE(wc.diff_time_seconds,0) / 60.0 + END) - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 110 ) - AND EXISTS (SELECT * FROM master.sys.all_objects WHERE name = 'dm_os_memory_nodes') - BEGIN + /* Logs, which might not have been taken */ + + (CASE WHEN @RestoreSpeedLogMBps IS NOT NULL AND wc.log_file_size_mb IS NOT NULL + THEN @RestoreSpeedLogMBps / wc.log_file_size_mb + ELSE COALESCE(wc.log_time_seconds,0) / 60.0 + END) + , RTOWorstCaseBackupFileSizeMB = wc.rto_worst_case_size_mb + FROM #Backups b + INNER JOIN WorstCases wc + ON b.database_guid = wc.database_guid + AND b.database_name = wc.database_name; + '; - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 110) WITH NOWAIT; - - SET @StringToExecute = 'IF EXISTS (SELECT * - FROM sys.dm_os_nodes n - INNER JOIN sys.dm_os_memory_nodes m ON n.memory_node_id = m.memory_node_id - WHERE n.node_state_desc = ''OFFLINE'') - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 110 AS CheckID , - 50 AS Priority , - ''Performance'' AS FindingGroup , - ''Memory Nodes Offline'' AS Finding , - ''https://www.brentozar.com/go/schedulers'' AS URL , - ''Due to affinity masking or licensing problems, some of the memory may not be available.'' OPTION (RECOMPILE)'; - - IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; - IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; - - EXECUTE(@StringToExecute); - END; + IF @Debug = 1 + PRINT @StringToExecute; - IF EXISTS ( SELECT * - FROM sys.databases - WHERE state > 1 ) - AND NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 102 ) - BEGIN + EXEC sys.sp_executesql @StringToExecute, N'@RestoreSpeedFullMBps INT, @RestoreSpeedDiffMBps INT, @RestoreSpeedLogMBps INT', @RestoreSpeedFullMBps, @RestoreSpeedDiffMBps, @RestoreSpeedLogMBps; - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 102) WITH NOWAIT; - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 102 AS CheckID , - [name] , - 20 AS Priority , - 'Reliability' AS FindingGroup , - 'Unusual Database State: ' + [state_desc] AS Finding , - 'https://www.brentozar.com/go/repair' AS URL , - 'This database may not be online.' - FROM sys.databases - WHERE state > 1; - END; - IF EXISTS ( SELECT * - FROM master.sys.extended_procedures ) - AND NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 105 ) - BEGIN +/*Populating Recoverability*/ - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 105) WITH NOWAIT; - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 105 AS CheckID , - 'master' , - 200 AS Priority , - 'Reliability' AS FindingGroup , - 'Extended Stored Procedures in Master' AS Finding , - 'https://www.brentozar.com/go/clr' AS URL , - 'The [' + name - + '] extended stored procedure is in the master database. CLR may be in use, and the master database now needs to be part of your backup/recovery planning.' - FROM master.sys.extended_procedures; - END; + /*Get distinct list of databases*/ + SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 107 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 107) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 107 AS CheckID , - 50 AS Priority , - 'Performance' AS FindingGroup , - 'Poison Wait Detected: ' + wait_type AS Finding , - 'https://www.brentozar.com/go/poison/#' + wait_type AS URL , - CONVERT(VARCHAR(10), (SUM([wait_time_ms]) / 1000) / 86400) + ':' + CONVERT(VARCHAR(20), DATEADD(s, (SUM([wait_time_ms]) / 1000), 0), 108) + ' of this wait have been recorded. This wait often indicates killer performance problems.' - FROM sys.[dm_os_wait_stats] - WHERE wait_type IN('IO_QUEUE_LIMIT', 'IO_RETRY', 'LOG_RATE_GOVERNOR', 'POOL_LOG_RATE_GOVERNOR', 'PREEMPTIVE_DEBUG', 'RESMGR_THROTTLED', 'RESOURCE_SEMAPHORE', 'RESOURCE_SEMAPHORE_QUERY_COMPILE','SE_REPL_CATCHUP_THROTTLE','SE_REPL_COMMIT_ACK','SE_REPL_COMMIT_TURN','SE_REPL_ROLLBACK_ACK','SE_REPL_SLOW_SECONDARY_THROTTLE','THREADPOOL') - GROUP BY wait_type - HAVING SUM([wait_time_ms]) > (SELECT 5000 * datediff(HH,create_date,CURRENT_TIMESTAMP) AS hours_since_startup FROM sys.databases WHERE name='tempdb') - AND SUM([wait_time_ms]) > 60000; - END; + SET @StringToExecute += N' + SELECT DISTINCT b.database_name, database_guid + FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b;' - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 121 ) - BEGIN + IF @Debug = 1 + PRINT @StringToExecute; - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 121) WITH NOWAIT; + INSERT #Recoverability ( DatabaseName, DatabaseGUID ) + EXEC sys.sp_executesql @StringToExecute; - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 121 AS CheckID , - 50 AS Priority , - 'Performance' AS FindingGroup , - 'Poison Wait Detected: Serializable Locking' AS Finding , - 'https://www.brentozar.com/go/serializable' AS URL , - CONVERT(VARCHAR(10), (SUM([wait_time_ms]) / 1000) / 86400) + ':' + CONVERT(VARCHAR(20), DATEADD(s, (SUM([wait_time_ms]) / 1000), 0), 108) + ' of LCK_M_R% waits have been recorded. This wait often indicates killer performance problems.' - FROM sys.[dm_os_wait_stats] - WHERE wait_type IN ('LCK_M_RS_S', 'LCK_M_RS_U', 'LCK_M_RIn_NL','LCK_M_RIn_S', 'LCK_M_RIn_U','LCK_M_RIn_X', 'LCK_M_RX_S', 'LCK_M_RX_U','LCK_M_RX_X') - HAVING SUM([wait_time_ms]) > (SELECT 5000 * datediff(HH,create_date,CURRENT_TIMESTAMP) AS hours_since_startup FROM sys.databases WHERE name='tempdb') - AND SUM([wait_time_ms]) > 60000; - END; + /*Find most recent recovery model, backup size, and backup date*/ + SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 111 ) - BEGIN + SET @StringToExecute += N' + UPDATE r + SET r.LastBackupRecoveryModel = ca.recovery_model, + r.LastFullBackupSizeMB = ca.compressed_backup_size, + r.LastFullBackupDate = ca.backup_finish_date + FROM #Recoverability r + CROSS APPLY ( + SELECT TOP 1 b.recovery_model, (b.compressed_backup_size / 1048576.0) AS compressed_backup_size, b.backup_finish_date + FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b + WHERE r.DatabaseName = b.database_name + AND r.DatabaseGUID = b.database_guid + AND b.type = ''D'' + AND b.backup_finish_date > @StartTime + ORDER BY b.backup_finish_date DESC + ) ca;' - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 111) WITH NOWAIT; + IF @Debug = 1 + PRINT @StringToExecute; - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - DatabaseName , - URL , - Details - ) - SELECT 111 AS CheckID , - 50 AS Priority , - 'Reliability' AS FindingGroup , - 'Possibly Broken Log Shipping' AS Finding , - d.[name] , - 'https://www.brentozar.com/go/shipping' AS URL , - d.[name] + ' is in a restoring state, but has not had a backup applied in the last two days. This is a possible indication of a broken transaction log shipping setup.' - FROM [master].sys.databases d - INNER JOIN [master].sys.database_mirroring dm ON d.database_id = dm.database_id - AND dm.mirroring_role IS NULL - WHERE ( d.[state] = 1 - OR (d.[state] = 0 AND d.[is_in_standby] = 1) ) - AND NOT EXISTS(SELECT * FROM msdb.dbo.restorehistory rh - INNER JOIN msdb.dbo.backupset bs ON rh.backup_set_id = bs.backup_set_id - WHERE d.[name] COLLATE SQL_Latin1_General_CP1_CI_AS = rh.destination_database_name COLLATE SQL_Latin1_General_CP1_CI_AS - AND rh.restore_date >= DATEADD(dd, -2, GETDATE())); + EXEC sys.sp_executesql @StringToExecute, N'@StartTime DATETIME2', @StartTime; - END; + /*Find first backup size and date*/ + SET @StringToExecute =N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 112 ) - AND EXISTS (SELECT * FROM master.sys.all_objects WHERE name = 'change_tracking_databases') - BEGIN + SET @StringToExecute += N' + UPDATE r + SET r.FirstFullBackupSizeMB = ca.compressed_backup_size, + r.FirstFullBackupDate = ca.backup_finish_date + FROM #Recoverability r + CROSS APPLY ( + SELECT TOP 1 (b.compressed_backup_size / 1048576.0) AS compressed_backup_size, b.backup_finish_date + FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b + WHERE r.DatabaseName = b.database_name + AND r.DatabaseGUID = b.database_guid + AND b.type = ''D'' + AND b.backup_finish_date > @StartTime + ORDER BY b.backup_finish_date ASC + ) ca;' - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 112) WITH NOWAIT; - - SET @StringToExecute = 'INSERT INTO #BlitzResults - (CheckID, - Priority, - FindingsGroup, - Finding, - DatabaseName, - URL, - Details) - SELECT 112 AS CheckID, - 100 AS Priority, - ''Performance'' AS FindingsGroup, - ''Change Tracking Enabled'' AS Finding, - d.[name], - ''https://www.brentozar.com/go/tracking'' AS URL, - ( d.[name] + '' has change tracking enabled. This is not a default setting, and it has some performance overhead. It keeps track of changes to rows in tables that have change tracking turned on.'' ) AS Details FROM sys.change_tracking_databases AS ctd INNER JOIN sys.databases AS d ON ctd.database_id = d.database_id OPTION (RECOMPILE)'; - - IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; - IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; - - EXECUTE(@StringToExecute); - END; + IF @Debug = 1 + PRINT @StringToExecute; - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 116 ) - AND EXISTS (SELECT * FROM msdb.sys.all_columns WHERE name = 'compressed_backup_size') - BEGIN + EXEC sys.sp_executesql @StringToExecute, N'@StartTime DATETIME2', @StartTime; - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 116) WITH NOWAIT - SET @StringToExecute = 'INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 116 AS CheckID , - 200 AS Priority , - ''Informational'' AS FindingGroup , - ''Backup Compression Default Off'' AS Finding , - ''https://www.brentozar.com/go/backup'' AS URL , - ''Uncompressed full backups have happened recently, and backup compression is not turned on at the server level. Backup compression is included with SQL Server 2008R2 & newer, even in Standard Edition. We recommend turning backup compression on by default so that ad-hoc backups will get compressed.'' - FROM sys.configurations - WHERE configuration_id = 1579 AND CAST(value_in_use AS INT) = 0 - AND EXISTS (SELECT * FROM msdb.dbo.backupset WHERE backup_size = compressed_backup_size AND type = ''D'' AND backup_finish_date >= DATEADD(DD, -14, GETDATE())) OPTION (RECOMPILE);'; - - IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; - IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; - - EXECUTE(@StringToExecute); - END; + /*Find average backup throughputs for full, diff, and log*/ + SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 117 ) - AND EXISTS (SELECT * FROM master.sys.all_objects WHERE name = 'dm_exec_query_resource_semaphores') - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 117) WITH NOWAIT; - - SET @StringToExecute = 'IF 0 < (SELECT SUM([forced_grant_count]) FROM sys.dm_exec_query_resource_semaphores WHERE [forced_grant_count] IS NOT NULL) - INSERT INTO #BlitzResults - (CheckID, - Priority, - FindingsGroup, - Finding, - URL, - Details) - SELECT 117 AS CheckID, - 100 AS Priority, - ''Performance'' AS FindingsGroup, - ''Memory Pressure Affecting Queries'' AS Finding, - ''https://www.brentozar.com/go/grants'' AS URL, - CAST(SUM(forced_grant_count) AS NVARCHAR(100)) + '' forced grants reported in the DMV sys.dm_exec_query_resource_semaphores, indicating memory pressure has affected query runtimes.'' - FROM sys.dm_exec_query_resource_semaphores WHERE [forced_grant_count] IS NOT NULL OPTION (RECOMPILE);'; - - IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; - IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; - - EXECUTE(@StringToExecute); - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 124 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 124) WITH NOWAIT; - - INSERT INTO #BlitzResults - (CheckID, - Priority, - FindingsGroup, - Finding, - URL, - Details) - SELECT 124, - 150, - 'Performance', - 'Deadlocks Happening Daily', - 'https://www.brentozar.com/go/deadlocks', - CAST(CAST(p.cntr_value / @DaysUptime AS BIGINT) AS NVARCHAR(100)) + ' average deadlocks per day. To find them, run sp_BlitzLock.' AS Details - FROM sys.dm_os_performance_counters p - INNER JOIN sys.databases d ON d.name = 'tempdb' - WHERE RTRIM(p.counter_name) = 'Number of Deadlocks/sec' - AND RTRIM(p.instance_name) = '_Total' - AND p.cntr_value > 0 - AND (1.0 * p.cntr_value / NULLIF(datediff(DD,create_date,CURRENT_TIMESTAMP),0)) > 10; - END; - - IF DATEADD(mi, -15, GETDATE()) < (SELECT TOP 1 creation_time FROM sys.dm_exec_query_stats ORDER BY creation_time) - AND NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 125 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 125) WITH NOWAIT; - - DECLARE @user_perm_sql NVARCHAR(MAX) = N''; - DECLARE @user_perm_gb_out DECIMAL(38,2); - - IF @ProductVersionMajor >= 11 - - BEGIN - - SET @user_perm_sql += N' - SELECT @user_perm_gb = CASE WHEN (pages_kb / 1024. / 1024.) >= 2. - THEN CONVERT(DECIMAL(38, 2), (pages_kb / 1024. / 1024.)) - ELSE NULL - END - FROM sys.dm_os_memory_clerks - WHERE type = ''USERSTORE_TOKENPERM'' - AND name = ''TokenAndPermUserStore'' - '; - - END - - IF @ProductVersionMajor < 11 - - BEGIN - SET @user_perm_sql += N' - SELECT @user_perm_gb = CASE WHEN ((single_pages_kb + multi_pages_kb) / 1024.0 / 1024.) >= 2. - THEN CONVERT(DECIMAL(38, 2), ((single_pages_kb + multi_pages_kb) / 1024.0 / 1024.)) - ELSE NULL - END - FROM sys.dm_os_memory_clerks - WHERE type = ''USERSTORE_TOKENPERM'' - AND name = ''TokenAndPermUserStore'' - '; - - END - - EXEC sys.sp_executesql @user_perm_sql, - N'@user_perm_gb DECIMAL(38,2) OUTPUT', - @user_perm_gb = @user_perm_gb_out OUTPUT - - INSERT INTO #BlitzResults - (CheckID, - Priority, - FindingsGroup, - Finding, - URL, - Details) - SELECT TOP 1 125, 10, 'Performance', 'Plan Cache Erased Recently', 'https://www.brentozar.com/askbrent/plan-cache-erased-recently/', - 'The oldest query in the plan cache was created at ' + CAST(creation_time AS NVARCHAR(50)) - + CASE WHEN @user_perm_gb_out IS NULL - THEN '. Someone ran DBCC FREEPROCCACHE, restarted SQL Server, or it is under horrific memory pressure.' - ELSE '. You also have ' + CONVERT(NVARCHAR(20), @user_perm_gb_out) + ' GB of USERSTORE_TOKENPERM, which could indicate unusual memory consumption.' - END - FROM sys.dm_exec_query_stats WITH (NOLOCK) - ORDER BY creation_time; - END; - - IF EXISTS (SELECT * FROM sys.configurations WHERE name = 'priority boost' AND (value = 1 OR value_in_use = 1)) - AND NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 126 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 126) WITH NOWAIT; - - INSERT INTO #BlitzResults - (CheckID, - Priority, - FindingsGroup, - Finding, - URL, - Details) - VALUES(126, 5, 'Reliability', 'Priority Boost Enabled', 'https://www.brentozar.com/go/priorityboost/', - 'Priority Boost sounds awesome, but it can actually cause your SQL Server to crash.'); - END; + SET @StringToExecute += N' + UPDATE r + SET r.AvgFullBackupThroughputMB = ca_full.AvgFullSpeed, + r.AvgDiffBackupThroughputMB = ca_diff.AvgDiffSpeed, + r.AvgLogBackupThroughputMB = ca_log.AvgLogSpeed, + r.AvgFullBackupDurationSeconds = AvgFullDuration, + r.AvgDiffBackupDurationSeconds = AvgDiffDuration, + r.AvgLogBackupDurationSeconds = AvgLogDuration + FROM #Recoverability AS r + OUTER APPLY ( + SELECT b.database_name, + AVG( b.compressed_backup_size / ( DATEDIFF(ss, b.backup_start_date, b.backup_finish_date) ) / 1048576.0 ) AS AvgFullSpeed, + AVG( DATEDIFF(ss, b.backup_start_date, b.backup_finish_date) ) AS AvgFullDuration + FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset b + WHERE r.DatabaseName = b.database_name + AND r.DatabaseGUID = b.database_guid + AND b.type = ''D'' + AND DATEDIFF(SECOND, b.backup_start_date, b.backup_finish_date) > 0 + AND b.backup_finish_date > @StartTime + GROUP BY b.database_name + ) ca_full + OUTER APPLY ( + SELECT b.database_name, + AVG( b.compressed_backup_size / ( DATEDIFF(ss, b.backup_start_date, b.backup_finish_date) ) / 1048576.0 ) AS AvgDiffSpeed, + AVG( DATEDIFF(ss, b.backup_start_date, b.backup_finish_date) ) AS AvgDiffDuration + FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset b + WHERE r.DatabaseName = b.database_name + AND r.DatabaseGUID = b.database_guid + AND b.type = ''I'' + AND DATEDIFF(SECOND, b.backup_start_date, b.backup_finish_date) > 0 + AND b.backup_finish_date > @StartTime + GROUP BY b.database_name + ) ca_diff + OUTER APPLY ( + SELECT b.database_name, + AVG( b.compressed_backup_size / ( DATEDIFF(ss, b.backup_start_date, b.backup_finish_date) ) / 1048576.0 ) AS AvgLogSpeed, + AVG( DATEDIFF(ss, b.backup_start_date, b.backup_finish_date) ) AS AvgLogDuration + FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset b + WHERE r.DatabaseName = b.database_name + AND r.DatabaseGUID = b.database_guid + AND b.type = ''L'' + AND DATEDIFF(SECOND, b.backup_start_date, b.backup_finish_date) > 0 + AND b.backup_finish_date > @StartTime + GROUP BY b.database_name + ) ca_log;' - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 128 ) - AND SERVERPROPERTY('EngineEdition') <> 8 /* Azure Managed Instances */ - BEGIN + IF @Debug = 1 + PRINT @StringToExecute; - IF (@ProductVersionMajor = 15 AND @ProductVersionMinor < 2000) OR - (@ProductVersionMajor = 14 AND @ProductVersionMinor < 1000) OR - (@ProductVersionMajor = 13 AND @ProductVersionMinor < 6300) OR - (@ProductVersionMajor = 12 AND @ProductVersionMinor < 6024) OR - (@ProductVersionMajor = 11 /*AND @ProductVersionMinor < 7001)*/) OR - (@ProductVersionMajor = 10.5 /*AND @ProductVersionMinor < 6000*/) OR - (@ProductVersionMajor = 10 /*AND @ProductVersionMinor < 6000*/) OR - (@ProductVersionMajor = 9 /*AND @ProductVersionMinor <= 5000*/) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 128) WITH NOWAIT; - - INSERT INTO #BlitzResults(CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES(128, 20, 'Reliability', 'Unsupported Build of SQL Server', 'https://www.brentozar.com/go/unsupported', - 'Version ' + CAST(@ProductVersionMajor AS VARCHAR(100)) + - CASE WHEN @ProductVersionMajor >= 12 THEN - '.' + CAST(@ProductVersionMinor AS VARCHAR(100)) + ' is no longer supported by Microsoft. You need to apply a service pack.' - ELSE ' is no longer supported by Microsoft. You should be making plans to upgrade to a modern version of SQL Server.' END); - END; + EXEC sys.sp_executesql @StringToExecute, N'@StartTime DATETIME2', @StartTime; - END; - - /* Reliability - Dangerous Build of SQL Server (Corruption) */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 129 ) - AND SERVERPROPERTY('EngineEdition') <> 8 /* Azure Managed Instances */ - BEGIN - IF (@ProductVersionMajor = 11 AND @ProductVersionMinor >= 3000 AND @ProductVersionMinor <= 3436) OR - (@ProductVersionMajor = 11 AND @ProductVersionMinor = 5058) OR - (@ProductVersionMajor = 12 AND @ProductVersionMinor >= 2000 AND @ProductVersionMinor <= 2342) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 129) WITH NOWAIT; - - INSERT INTO #BlitzResults(CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES(129, 20, 'Reliability', 'Dangerous Build of SQL Server (Corruption)', 'http://sqlperformance.com/2014/06/sql-indexes/hotfix-sql-2012-rebuilds', - 'There are dangerous known bugs with version ' + CAST(@ProductVersionMajor AS VARCHAR(100)) + '.' + CAST(@ProductVersionMinor AS VARCHAR(100)) + '. Check the URL for details and apply the right service pack or hotfix.'); - END; - END; + /*Find max and avg diff and log sizes*/ + SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - /* Reliability - Dangerous Build of SQL Server (Security) */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 157 ) - AND SERVERPROPERTY('EngineEdition') <> 8 /* Azure Managed Instances */ - BEGIN - IF (@ProductVersionMajor = 10 AND @ProductVersionMinor >= 5500 AND @ProductVersionMinor <= 5512) OR - (@ProductVersionMajor = 10 AND @ProductVersionMinor >= 5750 AND @ProductVersionMinor <= 5867) OR - (@ProductVersionMajor = 10.5 AND @ProductVersionMinor >= 4000 AND @ProductVersionMinor <= 4017) OR - (@ProductVersionMajor = 10.5 AND @ProductVersionMinor >= 4251 AND @ProductVersionMinor <= 4319) OR - (@ProductVersionMajor = 11 AND @ProductVersionMinor >= 3000 AND @ProductVersionMinor <= 3129) OR - (@ProductVersionMajor = 11 AND @ProductVersionMinor >= 3300 AND @ProductVersionMinor <= 3447) OR - (@ProductVersionMajor = 12 AND @ProductVersionMinor >= 2000 AND @ProductVersionMinor <= 2253) OR - (@ProductVersionMajor = 12 AND @ProductVersionMinor >= 2300 AND @ProductVersionMinor <= 2370) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 157) WITH NOWAIT; - - INSERT INTO #BlitzResults(CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES(157, 20, 'Reliability', 'Dangerous Build of SQL Server (Security)', 'https://technet.microsoft.com/en-us/library/security/MS14-044', - 'There are dangerous known bugs with version ' + CAST(@ProductVersionMajor AS VARCHAR(100)) + '.' + CAST(@ProductVersionMinor AS VARCHAR(100)) + '. Check the URL for details and apply the right service pack or hotfix.'); - END; + SET @StringToExecute += N' + UPDATE r + SET r.AvgFullSizeMB = fulls.avg_full_size, + r.AvgDiffSizeMB = diffs.avg_diff_size, + r.AvgLogSizeMB = logs.avg_log_size + FROM #Recoverability AS r + OUTER APPLY ( + SELECT b.database_name, AVG(b.compressed_backup_size / 1048576.0) AS avg_full_size + FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b + WHERE r.DatabaseName = b.database_name + AND r.DatabaseGUID = b.database_guid + AND b.type = ''D'' + AND b.backup_finish_date > @StartTime + GROUP BY b.database_name + ) AS fulls + OUTER APPLY ( + SELECT b.database_name, AVG(b.compressed_backup_size / 1048576.0) AS avg_diff_size + FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b + WHERE r.DatabaseName = b.database_name + AND r.DatabaseGUID = b.database_guid + AND b.type = ''I'' + AND b.backup_finish_date > @StartTime + GROUP BY b.database_name + ) AS diffs + OUTER APPLY ( + SELECT b.database_name, AVG(b.compressed_backup_size / 1048576.0) AS avg_log_size + FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b + WHERE r.DatabaseName = b.database_name + AND r.DatabaseGUID = b.database_guid + AND b.type = ''L'' + AND b.backup_finish_date > @StartTime + GROUP BY b.database_name + ) AS logs;' - END; - - /* Check if SQL 2016 Standard Edition but not SP1 */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 189 ) - AND SERVERPROPERTY('EngineEdition') <> 8 /* Azure Managed Instances */ - BEGIN - IF (@ProductVersionMajor = 13 AND @ProductVersionMinor < 4001 AND @@VERSION LIKE '%Standard Edition%') - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 189) WITH NOWAIT; - - INSERT INTO #BlitzResults(CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES(189, 100, 'Features', 'Missing Features', 'https://blogs.msdn.microsoft.com/sqlreleaseservices/sql-server-2016-service-pack-1-sp1-released/', - 'SQL 2016 Standard Edition is being used but not Service Pack 1. Check the URL for a list of Enterprise Features that are included in Standard Edition as of SP1.'); - END; + IF @Debug = 1 + PRINT @StringToExecute; - END; - - /* Check if SQL 2017 but not CU3 */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 216 ) - AND SERVERPROPERTY('EngineEdition') <> 8 /* Azure Managed Instances */ - BEGIN - IF (@ProductVersionMajor = 14 AND @ProductVersionMinor < 3015) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 216) WITH NOWAIT; - - INSERT INTO #BlitzResults(CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES(216, 100, 'Features', 'Missing Features', 'https://support.microsoft.com/en-us/help/4041814', - 'SQL 2017 is being used but not Cumulative Update 3. We''d recommend patching to take advantage of increased analytics when running BlitzCache.'); - END; - - END; - - /* Cumulative Update Available */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 217 ) - AND SERVERPROPERTY('EngineEdition') NOT IN (5,8) /* Azure Managed Instances and Azure SQL DB*/ - AND EXISTS (SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 'SqlServerVersions' AND TABLE_TYPE = 'BASE TABLE') - AND NOT EXISTS (SELECT * FROM #BlitzResults WHERE CheckID IN (128, 129, 157, 189, 216)) /* Other version checks */ - BEGIN - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 217) WITH NOWAIT; - - INSERT INTO #BlitzResults(CheckID, Priority, FindingsGroup, Finding, URL, Details) - SELECT TOP 1 217, 100, 'Reliability', 'Cumulative Update Available', COALESCE(v.Url, 'https://SQLServerUpdates.com/'), - v.MinorVersionName + ' was released on ' + CAST(CONVERT(DATETIME, v.ReleaseDate, 112) AS VARCHAR(100)) - FROM dbo.SqlServerVersions v - WHERE v.MajorVersionNumber = @ProductVersionMajor - AND v.MinorVersionNumber > @ProductVersionMinor - ORDER BY v.MinorVersionNumber DESC; - END; - - /* Performance - High Memory Use for In-Memory OLTP (Hekaton) */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 145 ) - AND EXISTS ( SELECT * - FROM sys.all_objects o - WHERE o.name = 'dm_db_xtp_table_memory_stats' ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 145) WITH NOWAIT; - - SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) - SELECT 145 AS CheckID, - 10 AS Priority, - ''Performance'' AS FindingsGroup, - ''High Memory Use for In-Memory OLTP (Hekaton)'' AS Finding, - ''https://www.brentozar.com/go/hekaton'' AS URL, - CAST(CAST((SUM(mem.pages_kb / 1024.0) / CAST(value_in_use AS INT) * 100) AS INT) AS NVARCHAR(100)) + ''% of your '' + CAST(CAST((CAST(value_in_use AS DECIMAL(38,1)) / 1024) AS MONEY) AS NVARCHAR(100)) + ''GB of your max server memory is being used for in-memory OLTP tables (Hekaton). Microsoft recommends having 2X your Hekaton table space available in memory just for Hekaton, with a max of 250GB of in-memory data regardless of your server memory capacity.'' AS Details - FROM sys.configurations c INNER JOIN sys.dm_os_memory_clerks mem ON mem.type = ''MEMORYCLERK_XTP'' - WHERE c.name = ''max server memory (MB)'' - GROUP BY c.value_in_use - HAVING CAST(value_in_use AS DECIMAL(38,2)) * .25 < SUM(mem.pages_kb / 1024.0) - OR SUM(mem.pages_kb / 1024.0) > 250000 OPTION (RECOMPILE)'; - - IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; - IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; - - EXECUTE(@StringToExecute); - END; - - /* Performance - In-Memory OLTP (Hekaton) In Use */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 146 ) - AND EXISTS ( SELECT * - FROM sys.all_objects o - WHERE o.name = 'dm_db_xtp_table_memory_stats' ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 146) WITH NOWAIT; - - SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) - SELECT 146 AS CheckID, - 200 AS Priority, - ''Performance'' AS FindingsGroup, - ''In-Memory OLTP (Hekaton) In Use'' AS Finding, - ''https://www.brentozar.com/go/hekaton'' AS URL, - CAST(CAST((SUM(mem.pages_kb / 1024.0) / CAST(value_in_use AS INT) * 100) AS DECIMAL(6,1)) AS NVARCHAR(100)) + ''% of your '' + CAST(CAST((CAST(value_in_use AS DECIMAL(38,1)) / 1024) AS MONEY) AS NVARCHAR(100)) + ''GB of your max server memory is being used for in-memory OLTP tables (Hekaton).'' AS Details - FROM sys.configurations c INNER JOIN sys.dm_os_memory_clerks mem ON mem.type = ''MEMORYCLERK_XTP'' - WHERE c.name = ''max server memory (MB)'' - GROUP BY c.value_in_use - HAVING SUM(mem.pages_kb / 1024.0) > 1000 OPTION (RECOMPILE)'; - - IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; - IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; - - EXECUTE(@StringToExecute); - END; - - /* In-Memory OLTP (Hekaton) - Transaction Errors */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 147 ) - AND EXISTS ( SELECT * - FROM sys.all_objects o - WHERE o.name = 'dm_xtp_transaction_stats' ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 147) WITH NOWAIT - - SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) - SELECT 147 AS CheckID, - 100 AS Priority, - ''In-Memory OLTP (Hekaton)'' AS FindingsGroup, - ''Transaction Errors'' AS Finding, - ''https://www.brentozar.com/go/hekaton'' AS URL, - ''Since restart: '' + CAST(validation_failures AS NVARCHAR(100)) + '' validation failures, '' + CAST(dependencies_failed AS NVARCHAR(100)) + '' dependency failures, '' + CAST(write_conflicts AS NVARCHAR(100)) + '' write conflicts, '' + CAST(unique_constraint_violations AS NVARCHAR(100)) + '' unique constraint violations.'' AS Details - FROM sys.dm_xtp_transaction_stats - WHERE validation_failures <> 0 - OR dependencies_failed <> 0 - OR write_conflicts <> 0 - OR unique_constraint_violations <> 0 OPTION (RECOMPILE);'; - - IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; - IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; - - EXECUTE(@StringToExecute); - END; - - /* Reliability - Database Files on Network File Shares */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 148 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 148) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT DISTINCT 148 AS CheckID , - d.[name] AS DatabaseName , - 170 AS Priority , - 'Reliability' AS FindingsGroup , - 'Database Files on Network File Shares' AS Finding , - 'https://www.brentozar.com/go/nas' AS URL , - ( 'Files for this database are on: ' + LEFT(mf.physical_name, 30)) AS Details - FROM sys.databases d - INNER JOIN sys.master_files mf ON d.database_id = mf.database_id - WHERE mf.physical_name LIKE '\\%' - AND d.name NOT IN ( SELECT DISTINCT - DatabaseName - FROM #SkipChecks - WHERE CheckID IS NULL OR CheckID = 148); - END; - - /* Reliability - Database Files Stored in Azure */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 149 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 149) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT DISTINCT 149 AS CheckID , - d.[name] AS DatabaseName , - 170 AS Priority , - 'Reliability' AS FindingsGroup , - 'Database Files Stored in Azure' AS Finding , - 'https://www.brentozar.com/go/azurefiles' AS URL , - ( 'Files for this database are on: ' + LEFT(mf.physical_name, 30)) AS Details - FROM sys.databases d - INNER JOIN sys.master_files mf ON d.database_id = mf.database_id - WHERE mf.physical_name LIKE 'http://%' - AND d.name NOT IN ( SELECT DISTINCT - DatabaseName - FROM #SkipChecks - WHERE CheckID IS NULL OR CheckID = 149); - END; - - /* Reliability - Errors Logged Recently in the Default Trace */ - - /* First, let's check that there aren't any issues with the trace files */ - BEGIN TRY - - IF @SkipTrace = 0 - BEGIN - INSERT INTO #fnTraceGettable - ( TextData , - DatabaseName , - EventClass , - Severity , - StartTime , - EndTime , - Duration , - NTUserName , - NTDomainName , - HostName , - ApplicationName , - LoginName , - DBUserName - ) - SELECT TOP 20000 - CONVERT(NVARCHAR(4000),t.TextData) , - t.DatabaseName , - t.EventClass , - t.Severity , - t.StartTime , - t.EndTime , - t.Duration , - t.NTUserName , - t.NTDomainName , - t.HostName , - t.ApplicationName , - t.LoginName , - t.DBUserName - FROM sys.fn_trace_gettable(@base_tracefilename, DEFAULT) t - WHERE - ( - t.EventClass = 22 - AND t.Severity >= 17 - AND t.StartTime > DATEADD(dd, -30, GETDATE()) - ) - OR - ( - t.EventClass IN (92, 93) - AND t.StartTime > DATEADD(dd, -30, GETDATE()) - AND t.Duration > 15000000 - ) - OR - ( - t.EventClass IN (94, 95, 116) - ) - END; + EXEC sys.sp_executesql @StringToExecute, N'@StartTime DATETIME2', @StartTime; + +/*Trending - only works if backupfile is populated, which means in msdb */ +IF @MSDBName = N'msdb' +BEGIN + SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' --+ @crlf; - SET @TraceFileIssue = 0 + SET @StringToExecute += N' + SELECT p.DatabaseName, + p.DatabaseGUID, + p.[0], + p.[-1], + p.[-2], + p.[-3], + p.[-4], + p.[-5], + p.[-6], + p.[-7], + p.[-8], + p.[-9], + p.[-10], + p.[-11], + p.[-12] + FROM ( SELECT b.database_name AS DatabaseName, + b.database_guid AS DatabaseGUID, + DATEDIFF(MONTH, @StartTime, b.backup_start_date) AS MonthsAgo , + CONVERT(DECIMAL(18, 2), AVG(bf.file_size / 1048576.0)) AS AvgSizeMB + FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b + INNER JOIN ' + QUOTENAME(@MSDBName) + N'.dbo.backupfile AS bf + ON b.backup_set_id = bf.backup_set_id + WHERE b.database_name NOT IN ( ''master'', ''msdb'', ''model'', ''tempdb'' ) + AND bf.file_type = ''D'' + AND b.backup_start_date >= DATEADD(YEAR, -1, @StartTime) + AND b.backup_start_date <= SYSDATETIME() + GROUP BY b.database_name, + b.database_guid, + DATEDIFF(mm, @StartTime, b.backup_start_date) + ) AS bckstat PIVOT ( SUM(bckstat.AvgSizeMB) FOR bckstat.MonthsAgo IN ( [0], [-1], [-2], [-3], [-4], [-5], [-6], [-7], [-8], [-9], [-10], [-11], [-12] ) ) AS p + ORDER BY p.DatabaseName; + ' - END TRY - BEGIN CATCH + IF @Debug = 1 + PRINT @StringToExecute; - SET @TraceFileIssue = 1 - - END CATCH - - IF @TraceFileIssue = 1 - BEGIN - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 199 ) - - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT - '199' AS CheckID , - '' AS DatabaseName , - 50 AS Priority , - 'Reliability' AS FindingsGroup , - 'There Is An Error With The Default Trace' AS Finding , - 'https://www.brentozar.com/go/defaulttrace' AS URL , - 'Somebody has been messing with your trace files. Check the files are present at ' + @base_tracefilename AS Details - END - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 150 ) - AND @base_tracefilename IS NOT NULL - AND @TraceFileIssue = 0 - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 150) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT DISTINCT 150 AS CheckID , - t.DatabaseName, - 170 AS Priority , - 'Reliability' AS FindingsGroup , - 'Errors Logged Recently in the Default Trace' AS Finding , - 'https://www.brentozar.com/go/defaulttrace' AS URL , - CAST(t.TextData AS NVARCHAR(4000)) AS Details - FROM #fnTraceGettable t - WHERE t.EventClass = 22 - /* Removed these as they're unnecessary, we filter this when inserting data into #fnTraceGettable */ - --AND t.Severity >= 17 - --AND t.StartTime > DATEADD(dd, -30, GETDATE()); - END; - - /* Performance - File Growths Slow */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 151 ) - AND @base_tracefilename IS NOT NULL - AND @TraceFileIssue = 0 - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 151) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT DISTINCT 151 AS CheckID , - t.DatabaseName, - 50 AS Priority , - 'Performance' AS FindingsGroup , - 'File Growths Slow' AS Finding , - 'https://www.brentozar.com/go/filegrowth' AS URL , - CAST(COUNT(*) AS NVARCHAR(100)) + ' growths took more than 15 seconds each. Consider setting file autogrowth to a smaller increment.' AS Details - FROM #fnTraceGettable t - WHERE t.EventClass IN (92, 93) - /* Removed these as they're unnecessary, we filter this when inserting data into #fnTraceGettable */ - --AND t.StartTime > DATEADD(dd, -30, GETDATE()) - --AND t.Duration > 15000000 - GROUP BY t.DatabaseName - HAVING COUNT(*) > 1; - END; - - /* Performance - Many Plans for One Query */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 160 ) - AND EXISTS (SELECT * FROM sys.all_columns WHERE name = 'query_hash') - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 160) WITH NOWAIT; - - SET @StringToExecute = N'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) - SELECT TOP 1 160 AS CheckID, - 100 AS Priority, - ''Performance'' AS FindingsGroup, - ''Many Plans for One Query'' AS Finding, - ''https://www.brentozar.com/go/parameterization'' AS URL, - CAST(COUNT(DISTINCT plan_handle) AS NVARCHAR(50)) + '' plans are present for a single query in the plan cache - meaning we probably have parameterization issues.'' AS Details - FROM sys.dm_exec_query_stats qs - CROSS APPLY sys.dm_exec_plan_attributes(qs.plan_handle) pa - WHERE pa.attribute = ''dbid'' - GROUP BY qs.query_hash, pa.value - HAVING COUNT(DISTINCT plan_handle) > '; - - IF 50 > (SELECT COUNT(*) FROM sys.databases) - SET @StringToExecute = @StringToExecute + N' 50 '; - ELSE - SELECT @StringToExecute = @StringToExecute + CAST(COUNT(*) * 2 AS NVARCHAR(50)) FROM sys.databases; + INSERT #Trending ( DatabaseName, DatabaseGUID, [0], [-1], [-2], [-3], [-4], [-5], [-6], [-7], [-8], [-9], [-10], [-11], [-12] ) + EXEC sys.sp_executesql @StringToExecute, N'@StartTime DATETIME2', @StartTime; - SET @StringToExecute = @StringToExecute + N' ORDER BY COUNT(DISTINCT plan_handle) DESC OPTION (RECOMPILE);'; - - IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; - IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; - - EXECUTE(@StringToExecute); - END; - - /* Performance - High Number of Cached Plans */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 161 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 161) WITH NOWAIT; - - SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) - SELECT TOP 1 161 AS CheckID, - 100 AS Priority, - ''Performance'' AS FindingsGroup, - ''High Number of Cached Plans'' AS Finding, - ''https://www.brentozar.com/go/planlimits'' AS URL, - ''Your server configuration is limited to '' + CAST(ht.buckets_count * 4 AS VARCHAR(20)) + '' '' + ht.name + '', and you are currently caching '' + CAST(cc.entries_count AS VARCHAR(20)) + ''.'' AS Details - FROM sys.dm_os_memory_cache_hash_tables ht - INNER JOIN sys.dm_os_memory_cache_counters cc ON ht.name = cc.name AND ht.type = cc.type - where ht.name IN ( ''SQL Plans'' , ''Object Plans'' , ''Bound Trees'' ) - AND cc.entries_count >= (3 * ht.buckets_count) OPTION (RECOMPILE)'; - - IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; - IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; - - EXECUTE(@StringToExecute); - END; +END - /* Performance - Too Much Free Memory */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 165 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 165) WITH NOWAIT; - - INSERT INTO #BlitzResults - (CheckID, - Priority, - FindingsGroup, - Finding, - URL, - Details) - SELECT 165, 50, 'Performance', 'Too Much Free Memory', 'https://www.brentozar.com/go/freememory', - CAST((CAST(cFree.cntr_value AS BIGINT) / 1024 / 1024 ) AS NVARCHAR(100)) + N'GB of free memory inside SQL Server''s buffer pool, which is ' + CAST((CAST(cTotal.cntr_value AS BIGINT) / 1024 / 1024) AS NVARCHAR(100)) + N'GB. You would think lots of free memory would be good, but check out the URL for more information.' AS Details - FROM sys.dm_os_performance_counters cFree - INNER JOIN sys.dm_os_performance_counters cTotal ON cTotal.object_name LIKE N'%Memory Manager%' - AND cTotal.counter_name = N'Total Server Memory (KB) ' - WHERE cFree.object_name LIKE N'%Memory Manager%' - AND cFree.counter_name = N'Free Memory (KB) ' - AND CAST(cTotal.cntr_value AS BIGINT) > 20480000000 - AND CAST(cTotal.cntr_value AS BIGINT) * .3 <= CAST(cFree.cntr_value AS BIGINT) - AND CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) NOT LIKE '%Standard%'; +/*End Trending*/ - END; +/*End populating Recoverability*/ - /* Outdated sp_Blitz - sp_Blitz is Over 6 Months Old */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 155 ) - AND DATEDIFF(MM, @VersionDate, GETDATE()) > 6 - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 155) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 155 AS CheckID , - 0 AS Priority , - 'Outdated sp_Blitz' AS FindingsGroup , - 'sp_Blitz is Over 6 Months Old' AS Finding , - 'http://FirstResponderKit.org/' AS URL , - 'Some things get better with age, like fine wine and your T-SQL. However, sp_Blitz is not one of those things - time to go download the current one.' AS Details; - END; - - /* Populate a list of database defaults. I'm doing this kind of oddly - - it reads like a lot of work, but this way it compiles & runs on all - versions of SQL Server. - */ - - IF @Debug IN (1, 2) RAISERROR('Generating database defaults.', 0, 1) WITH NOWAIT; - - INSERT INTO #DatabaseDefaults - SELECT 'is_supplemental_logging_enabled', 0, 131, 210, 'Supplemental Logging Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL - FROM sys.all_columns - WHERE name = 'is_supplemental_logging_enabled' AND object_id = OBJECT_ID('sys.databases'); - INSERT INTO #DatabaseDefaults - SELECT 'snapshot_isolation_state', 0, 132, 210, 'Snapshot Isolation Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL - FROM sys.all_columns - WHERE name = 'snapshot_isolation_state' AND object_id = OBJECT_ID('sys.databases'); - INSERT INTO #DatabaseDefaults - SELECT 'is_read_committed_snapshot_on', - CASE WHEN SERVERPROPERTY('EngineEdition') = 5 THEN 1 ELSE 0 END, /* RCSI is always enabled in Azure SQL DB per https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/1919 */ - 133, 210, CASE WHEN SERVERPROPERTY('EngineEdition') = 5 THEN 'Read Committed Snapshot Isolation Disabled' ELSE 'Read Committed Snapshot Isolation Enabled' END, 'https://www.brentozar.com/go/dbdefaults', NULL - FROM sys.all_columns - WHERE name = 'is_read_committed_snapshot_on' AND object_id = OBJECT_ID('sys.databases'); - INSERT INTO #DatabaseDefaults - SELECT 'is_auto_create_stats_incremental_on', 0, 134, 210, 'Auto Create Stats Incremental Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL - FROM sys.all_columns - WHERE name = 'is_auto_create_stats_incremental_on' AND object_id = OBJECT_ID('sys.databases'); - INSERT INTO #DatabaseDefaults - SELECT 'is_ansi_null_default_on', 0, 135, 210, 'ANSI NULL Default Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL - FROM sys.all_columns - WHERE name = 'is_ansi_null_default_on' AND object_id = OBJECT_ID('sys.databases'); - INSERT INTO #DatabaseDefaults - SELECT 'is_recursive_triggers_on', 0, 136, 210, 'Recursive Triggers Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL - FROM sys.all_columns - WHERE name = 'is_recursive_triggers_on' AND object_id = OBJECT_ID('sys.databases'); - INSERT INTO #DatabaseDefaults - SELECT 'is_trustworthy_on', 0, 137, 210, 'Trustworthy Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL - FROM sys.all_columns - WHERE name = 'is_trustworthy_on' AND object_id = OBJECT_ID('sys.databases'); - INSERT INTO #DatabaseDefaults - SELECT 'is_broker_enabled', 0, 230, 210, 'Broker Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL - FROM sys.all_columns - WHERE name = 'is_broker_enabled' AND object_id = OBJECT_ID('sys.databases'); - INSERT INTO #DatabaseDefaults - SELECT 'is_honor_broker_priority_on', 0, 231, 210, 'Honor Broker Priority Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL - FROM sys.all_columns - WHERE name = 'is_honor_broker_priority_on' AND object_id = OBJECT_ID('sys.databases'); - INSERT INTO #DatabaseDefaults - SELECT 'is_parameterization_forced', 0, 138, 210, 'Forced Parameterization Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL - FROM sys.all_columns - WHERE name = 'is_parameterization_forced' AND object_id = OBJECT_ID('sys.databases'); - /* Not alerting for this since we actually want it and we have a separate check for it: - INSERT INTO #DatabaseDefaults - SELECT 'is_query_store_on', 0, 139, 210, 'Query Store Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL - FROM sys.all_columns - WHERE name = 'is_query_store_on' AND object_id = OBJECT_ID('sys.databases'); - */ - INSERT INTO #DatabaseDefaults - SELECT 'is_cdc_enabled', 0, 140, 210, 'Change Data Capture Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL - FROM sys.all_columns - WHERE name = 'is_cdc_enabled' AND object_id = OBJECT_ID('sys.databases'); - INSERT INTO #DatabaseDefaults - SELECT 'containment', 0, 141, 210, 'Containment Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL - FROM sys.all_columns - WHERE name = 'containment' AND object_id = OBJECT_ID('sys.databases'); - --INSERT INTO #DatabaseDefaults - -- SELECT 'target_recovery_time_in_seconds', 0, 142, 210, 'Target Recovery Time Changed', 'https://www.brentozar.com/go/dbdefaults', NULL - -- FROM sys.all_columns - -- WHERE name = 'target_recovery_time_in_seconds' AND object_id = OBJECT_ID('sys.databases'); - INSERT INTO #DatabaseDefaults - SELECT 'delayed_durability', 0, 143, 210, 'Delayed Durability Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL - FROM sys.all_columns - WHERE name = 'delayed_durability' AND object_id = OBJECT_ID('sys.databases'); - INSERT INTO #DatabaseDefaults - SELECT 'is_memory_optimized_elevate_to_snapshot_on', 0, 144, 210, 'Memory Optimized Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL - FROM sys.all_columns - WHERE name = 'is_memory_optimized_elevate_to_snapshot_on' AND object_id = OBJECT_ID('sys.databases') - AND SERVERPROPERTY('EngineEdition') <> 8; /* Hekaton is always enabled in Managed Instances per https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/1919 */ - INSERT INTO #DatabaseDefaults - SELECT 'is_accelerated_database_recovery_on', 0, 145, 210, 'Acclerated Database Recovery Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL - FROM sys.all_columns - WHERE name = 'is_accelerated_database_recovery_on' AND object_id = OBJECT_ID('sys.databases') AND SERVERPROPERTY('EngineEdition') NOT IN (5, 8) ; - - DECLARE DatabaseDefaultsLoop CURSOR FOR - SELECT name, DefaultValue, CheckID, Priority, Finding, URL, Details - FROM #DatabaseDefaults; - - OPEN DatabaseDefaultsLoop; - FETCH NEXT FROM DatabaseDefaultsLoop into @CurrentName, @CurrentDefaultValue, @CurrentCheckID, @CurrentPriority, @CurrentFinding, @CurrentURL, @CurrentDetails; - WHILE @@FETCH_STATUS = 0 - BEGIN +RAISERROR('Returning data', 0, 1) WITH NOWAIT; - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, @CurrentCheckID) WITH NOWAIT; - - /* Target Recovery Time (142) can be either 0 or 60 due to a number of bugs */ - IF @CurrentCheckID = 142 - SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details) - SELECT ' + CAST(@CurrentCheckID AS NVARCHAR(200)) + ', d.[name], ' + CAST(@CurrentPriority AS NVARCHAR(200)) + ', ''Non-Default Database Config'', ''' + @CurrentFinding + ''',''' + @CurrentURL + ''',''' + COALESCE(@CurrentDetails, 'This database setting is not the default.') + ''' - FROM sys.databases d - WHERE d.database_id > 4 AND d.state = 0 AND (d.[' + @CurrentName + '] NOT IN (0, 60) OR d.[' + @CurrentName + '] IS NULL) OPTION (RECOMPILE);'; - ELSE - SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details) - SELECT ' + CAST(@CurrentCheckID AS NVARCHAR(200)) + ', d.[name], ' + CAST(@CurrentPriority AS NVARCHAR(200)) + ', ''Non-Default Database Config'', ''' + @CurrentFinding + ''',''' + @CurrentURL + ''',''' + COALESCE(@CurrentDetails, 'This database setting is not the default.') + ''' - FROM sys.databases d - WHERE d.database_id > 4 AND d.state = 0 AND (d.[' + @CurrentName + '] <> ' + @CurrentDefaultValue + ' OR d.[' + @CurrentName + '] IS NULL) OPTION (RECOMPILE);'; - - IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; - IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; - - EXEC (@StringToExecute); + SELECT b.* + FROM #Backups AS b + ORDER BY b.database_name; - FETCH NEXT FROM DatabaseDefaultsLoop into @CurrentName, @CurrentDefaultValue, @CurrentCheckID, @CurrentPriority, @CurrentFinding, @CurrentURL, @CurrentDetails; - END; + SELECT r.*, + t.[0], t.[-1], t.[-2], t.[-3], t.[-4], t.[-5], t.[-6], t.[-7], t.[-8], t.[-9], t.[-10], t.[-11], t.[-12] + FROM #Recoverability AS r + LEFT JOIN #Trending t + ON r.DatabaseName = t.DatabaseName + AND r.DatabaseGUID = t.DatabaseGUID + WHERE r.LastBackupRecoveryModel IS NOT NULL + ORDER BY r.DatabaseName - CLOSE DatabaseDefaultsLoop; - DEALLOCATE DatabaseDefaultsLoop; -/* Check if target recovery interval <> 60 */ -IF - @ProductVersionMajor >= 10 - AND NOT EXISTS - ( - SELECT - 1/0 - FROM #SkipChecks AS sc - WHERE sc.DatabaseName IS NULL - AND sc.CheckID = 257 - ) - BEGIN - IF EXISTS - ( - SELECT - 1/0 - FROM sys.all_columns AS ac - WHERE ac.name = 'target_recovery_time_in_seconds' - AND ac.object_id = OBJECT_ID('sys.databases') - ) - BEGIN - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 257) WITH NOWAIT; - - DECLARE - @tri nvarchar(max) = N' - SELECT - DatabaseName = - d.name, - CheckId = - 257, - Priority = - 50, - FindingsGroup = - N''Performance'', - Finding = - N''Recovery Interval Not Optimal'', - URL = - N''https://sqlperformance.com/2020/05/system-configuration/0-to-60-switching-to-indirect-checkpoints'', - Details = - N''The database '' + - QUOTENAME(d.name) + - N'' has a target recovery interval of '' + - RTRIM(d.target_recovery_time_in_seconds) + - CASE - WHEN d.target_recovery_time_in_seconds = 0 - THEN N'', which is a legacy default, and should be changed to 60.'' - WHEN d.target_recovery_time_in_seconds <> 0 - THEN N'', which is probably a mistake, and should be changed to 60.'' - END - FROM sys.databases AS d - WHERE d.database_id > 4 - AND d.is_read_only = 0 - AND d.is_in_standby = 0 - AND d.target_recovery_time_in_seconds <> 60; - '; - - INSERT INTO - #BlitzResults - ( - DatabaseName, - CheckID, - Priority, - FindingsGroup, - Finding, - URL, - Details - ) - EXEC sys.sp_executesql - @tri; +RAISERROR('Rules analysis starting', 0, 1) WITH NOWAIT; - END; - END; - +/*Looking for out of band backups by finding most common backup operator user_name and noting backups taken by other user_names*/ -/*This checks to see if Agent is Offline*/ -IF @ProductVersionMajor >= 10 - AND NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 167 ) - BEGIN - IF EXISTS ( SELECT 1 - FROM sys.all_objects - WHERE name = 'dm_server_services' ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 167) WITH NOWAIT; - - INSERT INTO [#BlitzResults] - ( [CheckID] , - [Priority] , - [FindingsGroup] , - [Finding] , - [URL] , - [Details] ) - - SELECT - 167 AS [CheckID] , - 250 AS [Priority] , - 'Server Info' AS [FindingsGroup] , - 'Agent is Currently Offline' AS [Finding] , - '' AS [URL] , - ( 'Oops! It looks like the ' + [servicename] + ' service is ' + [status_desc] + '. The startup type is ' + [startup_type_desc] + '.' - ) AS [Details] - FROM - [sys].[dm_server_services] - WHERE [status_desc] <> 'Running' - AND [servicename] LIKE 'SQL Server Agent%' - AND CAST(SERVERPROPERTY('Edition') AS VARCHAR(1000)) NOT LIKE '%xpress%'; + SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - END; - END; - -/*This checks to see if the Full Text thingy is offline*/ -IF @ProductVersionMajor >= 10 - AND NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 168 ) - BEGIN - IF EXISTS ( SELECT 1 - FROM sys.all_objects - WHERE name = 'dm_server_services' ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 168) WITH NOWAIT; - - INSERT INTO [#BlitzResults] - ( [CheckID] , - [Priority] , - [FindingsGroup] , - [Finding] , - [URL] , - [Details] ) - - SELECT - 168 AS [CheckID] , - 250 AS [Priority] , - 'Server Info' AS [FindingsGroup] , - 'Full-text Filter Daemon Launcher is Currently Offline' AS [Finding] , - '' AS [URL] , - ( 'Oops! It looks like the ' + [servicename] + ' service is ' + [status_desc] + '. The startup type is ' + [startup_type_desc] + '.' - ) AS [Details] - FROM - [sys].[dm_server_services] - WHERE [status_desc] <> 'Running' - AND [servicename] LIKE 'SQL Full-text Filter Daemon Launcher%'; - - END; - END; + SET @StringToExecute += N' + WITH common_people AS ( + SELECT TOP 1 b.user_name, COUNT_BIG(*) AS Records + FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b + GROUP BY b.user_name + ORDER BY Records DESC + ) + SELECT + 1 AS CheckId, + 100 AS [Priority], + b.database_name AS [Database Name], + ''Non-Agent backups taken'' AS [Finding], + ''The database '' + QUOTENAME(b.database_name) + '' has been backed up by '' + QUOTENAME(b.user_name) + '' '' + CONVERT(VARCHAR(10), COUNT(*)) + '' times.'' AS [Warning] + FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b + WHERE b.user_name NOT LIKE ''%Agent%'' AND b.user_name NOT LIKE ''%AGENT%'' + AND NOT EXISTS ( + SELECT 1 + FROM common_people AS cp + WHERE cp.user_name = b.user_name + ) + GROUP BY b.database_name, b.user_name + HAVING COUNT(*) > 1;' + @crlf; -/*This checks which service account SQL Server is running as.*/ -IF @ProductVersionMajor >= 10 - AND NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 169 ) + IF @Debug = 1 + PRINT @StringToExecute; - BEGIN - IF EXISTS ( SELECT 1 - FROM sys.all_objects - WHERE name = 'dm_server_services' ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 169) WITH NOWAIT; - - INSERT INTO [#BlitzResults] - ( [CheckID] , - [Priority] , - [FindingsGroup] , - [Finding] , - [URL] , - [Details] ) - - SELECT - 169 AS [CheckID] , - 250 AS [Priority] , - 'Informational' AS [FindingsGroup] , - 'SQL Server is running under an NT Service account' AS [Finding] , - 'https://www.brentozar.com/go/setup' AS [URL] , - ( 'I''m running as ' + [service_account] + '.' - ) AS [Details] - FROM - [sys].[dm_server_services] - WHERE [service_account] LIKE 'NT Service%' - AND [servicename] LIKE 'SQL Server%' - AND [servicename] NOT LIKE 'SQL Server Agent%' - AND [servicename] NOT LIKE 'SQL Server Launchpad%'; + INSERT #Warnings (CheckId, Priority, DatabaseName, Finding, Warning ) + EXEC sys.sp_executesql @StringToExecute; + + /*Looking for compatibility level changing. Only looking for databases that have changed more than twice (It''s possible someone may have changed up, had CE problems, and then changed back)*/ - END; - END; + SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; -/*This checks which service account SQL Agent is running as.*/ -IF @ProductVersionMajor >= 10 - AND NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 170 ) + SET @StringToExecute += N'SELECT + 2 AS CheckId, + 100 AS [Priority], + b.database_name AS [Database Name], + ''Compatibility level changing'' AS [Finding], + ''The database '' + QUOTENAME(b.database_name) + '' has changed compatibility levels '' + CONVERT(VARCHAR(10), COUNT(DISTINCT b.compatibility_level)) + '' times.'' AS [Warning] + FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b + GROUP BY b.database_name + HAVING COUNT(DISTINCT b.compatibility_level) > 2;' + @crlf; - BEGIN - IF EXISTS ( SELECT 1 - FROM sys.all_objects - WHERE name = 'dm_server_services' ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 170) WITH NOWAIT; - - INSERT INTO [#BlitzResults] - ( [CheckID] , - [Priority] , - [FindingsGroup] , - [Finding] , - [URL] , - [Details] ) - - SELECT - 170 AS [CheckID] , - 250 AS [Priority] , - 'Informational' AS [FindingsGroup] , - 'SQL Server Agent is running under an NT Service account' AS [Finding] , - 'https://www.brentozar.com/go/setup' AS [URL] , - ( 'I''m running as ' + [service_account] + '.' - ) AS [Details] - FROM - [sys].[dm_server_services] - WHERE [service_account] LIKE 'NT Service%' - AND [servicename] LIKE 'SQL Server Agent%'; + IF @Debug = 1 + PRINT @StringToExecute; - END; - END; - -/*This checks that First Responder Kit is consistent. -It assumes that all the objects of the kit resides in the same database, the one in which this SP is stored -It also is ready to check for installation in another schema. -*/ -IF( - NOT EXISTS ( - SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 226 - ) -) -BEGIN + INSERT #Warnings ( CheckId, Priority, DatabaseName, Finding, Warning ) + EXEC sys.sp_executesql @StringToExecute; + + /*Looking for password protected backups. This hasn''t been a popular option ever, and was largely replaced by encrypted backups, but it''s simple to check for.*/ - IF @Debug IN (1, 2) RAISERROR('Running check with id %d',0,1,2000); - - SET @spBlitzFullName = QUOTENAME(DB_NAME()) + '.' +QUOTENAME(OBJECT_SCHEMA_NAME(@@PROCID)) + '.' + QUOTENAME(OBJECT_NAME(@@PROCID)); - SET @BlitzIsOutdatedComparedToOthers = 0; - SET @tsql = NULL; - SET @VersionCheckModeExistsTSQL = NULL; - SET @BlitzProcDbName = DB_NAME(); - SET @ExecRet = NULL; - SET @InnerExecRet = NULL; - SET @TmpCnt = NULL; - - SET @PreviousComponentName = NULL; - SET @PreviousComponentFullPath = NULL; - SET @CurrentStatementId = NULL; - SET @CurrentComponentSchema = NULL; - SET @CurrentComponentName = NULL; - SET @CurrentComponentType = NULL; - SET @CurrentComponentVersionDate = NULL; - SET @CurrentComponentFullName = NULL; - SET @CurrentComponentMandatory = NULL; - SET @MaximumVersionDate = NULL; - - SET @StatementCheckName = NULL; - SET @StatementOutputsCounter = NULL; - SET @OutputCounterExpectedValue = NULL; - SET @StatementOutputsExecRet = NULL; - SET @StatementOutputsDateTime = NULL; - - SET @CurrentComponentMandatoryCheckOK = NULL; - SET @CurrentComponentVersionCheckModeOK = NULL; - - SET @canExitLoop = 0; - SET @frkIsConsistent = 0; - - - SET @tsql = 'USE ' + QUOTENAME(@BlitzProcDbName) + ';' + @crlf + - 'WITH FRKComponents (' + @crlf + - ' ObjectName,' + @crlf + - ' ObjectType,' + @crlf + - ' MandatoryComponent' + @crlf + - ')' + @crlf + - 'AS (' + @crlf + - ' SELECT ''sp_AllNightLog'',''P'' ,0' + @crlf + - ' UNION ALL' + @crlf + - ' SELECT ''sp_AllNightLog_Setup'', ''P'',0' + @crlf + - ' UNION ALL ' + @crlf + - ' SELECT ''sp_Blitz'',''P'',0' + @crlf + - ' UNION ALL ' + @crlf + - ' SELECT ''sp_BlitzBackups'',''P'',0' + @crlf + - ' UNION ALL ' + @crlf + - ' SELECT ''sp_BlitzCache'',''P'',0' + @crlf + - ' UNION ALL ' + @crlf + - ' SELECT ''sp_BlitzFirst'',''P'',0' + @crlf + - ' UNION ALL' + @crlf + - ' SELECT ''sp_BlitzIndex'',''P'',0' + @crlf + - ' UNION ALL ' + @crlf + - ' SELECT ''sp_BlitzLock'',''P'',0' + @crlf + - ' UNION ALL ' + @crlf + - ' SELECT ''sp_BlitzQueryStore'',''P'',0' + @crlf + - ' UNION ALL ' + @crlf + - ' SELECT ''sp_BlitzWho'',''P'',0' + @crlf + - ' UNION ALL ' + @crlf + - ' SELECT ''sp_DatabaseRestore'',''P'',0' + @crlf + - ' UNION ALL ' + @crlf + - ' SELECT ''sp_ineachdb'',''P'',0' + @crlf + - ' UNION ALL' + @crlf + - ' SELECT ''SqlServerVersions'',''U'',0' + @crlf + - ')' + @crlf + - 'INSERT INTO #FRKObjects (' + @crlf + - ' DatabaseName,ObjectSchemaName,ObjectName, ObjectType,MandatoryComponent' + @crlf + - ')' + @crlf + - 'SELECT DB_NAME(),SCHEMA_NAME(o.schema_id), c.ObjectName,c.ObjectType,c.MandatoryComponent' + @crlf + - 'FROM ' + @crlf + - ' FRKComponents c' + @crlf + - 'LEFT JOIN ' + @crlf + - ' sys.objects o' + @crlf + - 'ON c.ObjectName = o.[name]' + @crlf + - 'AND c.ObjectType = o.[type]' + @crlf + - --'WHERE o.schema_id IS NOT NULL' + @crlf + - ';' - ; - - EXEC @ExecRet = sp_executesql @tsql ; - - -- TODO: add check for statement success - - -- TODO: based on SP requirements and presence (SchemaName is not null) ==> update MandatoryComponent column - - -- Filling #StatementsToRun4FRKVersionCheck - INSERT INTO #StatementsToRun4FRKVersionCheck ( - CheckName,StatementText,SubjectName,SubjectFullPath, StatementOutputsCounter,OutputCounterExpectedValue,StatementOutputsExecRet,StatementOutputsDateTime - ) - SELECT - 'Mandatory', - 'SELECT @cnt = COUNT(*) FROM #FRKObjects WHERE ObjectSchemaName IS NULL AND ObjectName = ''' + ObjectName + ''' AND MandatoryComponent = 1;', - ObjectName, - QUOTENAME(DatabaseName) + '.' + QUOTENAME(ObjectSchemaName) + '.' + QUOTENAME(ObjectName), - 1, - 0, - 0, - 0 - FROM #FRKObjects - UNION ALL - SELECT - 'VersionCheckMode', - 'SELECT @cnt = COUNT(*) FROM ' + - QUOTENAME(DatabaseName) + '.sys.all_parameters ' + - 'where object_id = OBJECT_ID(''' + QUOTENAME(DatabaseName) + '.' + QUOTENAME(ObjectSchemaName) + '.' + QUOTENAME(ObjectName) + ''') AND [name] = ''@VersionCheckMode'';', - ObjectName, - QUOTENAME(DatabaseName) + '.' + QUOTENAME(ObjectSchemaName) + '.' + QUOTENAME(ObjectName), - 1, - 1, - 0, - 0 - FROM #FRKObjects - WHERE ObjectType = 'P' - AND ObjectSchemaName IS NOT NULL - UNION ALL - SELECT - 'VersionCheck', - 'EXEC @ExecRet = ' + QUOTENAME(DatabaseName) + '.' + QUOTENAME(ObjectSchemaName) + '.' + QUOTENAME(ObjectName) + ' @VersionCheckMode = 1 , @VersionDate = @ObjDate OUTPUT;', - ObjectName, - QUOTENAME(DatabaseName) + '.' + QUOTENAME(ObjectSchemaName) + '.' + QUOTENAME(ObjectName), - 0, - 0, - 1, - 1 - FROM #FRKObjects - WHERE ObjectType = 'P' - AND ObjectSchemaName IS NOT NULL - ; - IF(@Debug in (1,2)) - BEGIN - SELECT * - FROM #StatementsToRun4FRKVersionCheck ORDER BY SubjectName,SubjectFullPath,StatementId -- in case of schema change ; - END; - - - -- loop on queries... - WHILE(@canExitLoop = 0) - BEGIN - SET @CurrentStatementId = NULL; - - SELECT TOP 1 - @StatementCheckName = CheckName, - @CurrentStatementId = StatementId , - @CurrentComponentName = SubjectName, - @CurrentComponentFullName = SubjectFullPath, - @tsql = StatementText, - @StatementOutputsCounter = StatementOutputsCounter, - @OutputCounterExpectedValue = OutputCounterExpectedValue , - @StatementOutputsExecRet = StatementOutputsExecRet, - @StatementOutputsDateTime = StatementOutputsDateTime - FROM #StatementsToRun4FRKVersionCheck - ORDER BY SubjectName, SubjectFullPath,StatementId /* in case of schema change */ - ; - - -- loop exit condition - IF(@CurrentStatementId IS NULL) - BEGIN - BREAK; - END; + SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - IF @Debug IN (1, 2) RAISERROR(' Statement: %s',0,1,@tsql); + SET @StringToExecute += N'SELECT + 3 AS CheckId, + 100 AS [Priority], + b.database_name AS [Database Name], + ''Password backups'' AS [Finding], + ''The database '' + QUOTENAME(b.database_name) + '' has been backed up with a password '' + CONVERT(VARCHAR(10), COUNT(*)) + '' times. Who has the password?'' AS [Warning] + FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b + WHERE b.is_password_protected = 1 + GROUP BY b.database_name;' + @crlf; - -- we start a new component - IF(@PreviousComponentName IS NULL OR - (@PreviousComponentName IS NOT NULL AND @PreviousComponentName <> @CurrentComponentName) OR - (@PreviousComponentName IS NOT NULL AND @PreviousComponentName = @CurrentComponentName AND @PreviousComponentFullPath <> @CurrentComponentFullName) - ) - BEGIN - -- reset variables - SET @CurrentComponentMandatoryCheckOK = 0; - SET @CurrentComponentVersionCheckModeOK = 0; - SET @PreviousComponentName = @CurrentComponentName; - SET @PreviousComponentFullPath = @CurrentComponentFullName ; - END; + IF @Debug = 1 + PRINT @StringToExecute; - IF(@StatementCheckName NOT IN ('Mandatory','VersionCheckMode','VersionCheck')) - BEGIN - INSERT INTO #BlitzResults( - CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT - 226 AS CheckID , - 253 AS Priority , - 'First Responder Kit' AS FindingsGroup , - 'Version Check Failed (code generator changed)' AS Finding , - 'http://FirstResponderKit.org' AS URL , - 'Download an updated First Responder Kit. Your version check failed because a change has been made to the version check code generator.' + @crlf + - 'Error: No handler for check with name "' + ISNULL(@StatementCheckName,'') + '"' AS Details - ; - - -- we will stop the test because it's possible to get the same message for other components - SET @canExitLoop = 1; - CONTINUE; - END; + INSERT #Warnings ( CheckId, Priority, DatabaseName, Finding, Warning ) + EXEC sys.sp_executesql @StringToExecute; + + /*Looking for snapshot backups. There are legit reasons for these, but we should flag them so the questions get asked. What questions? Good question.*/ - IF(@StatementCheckName = 'Mandatory') - BEGIN - -- outputs counter - EXEC @ExecRet = sp_executesql @tsql, N'@cnt INT OUTPUT',@cnt = @TmpCnt OUTPUT; + SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - IF(@ExecRet <> 0) - BEGIN - - INSERT INTO #BlitzResults( - CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT - 226 AS CheckID , - 253 AS Priority , - 'First Responder Kit' AS FindingsGroup , - 'Version Check Failed (dynamic query failure)' AS Finding , - 'http://FirstResponderKit.org' AS URL , - 'Download an updated First Responder Kit. Your version check failed due to dynamic query failure.' + @crlf + - 'Error: following query failed at execution (check if component [' + ISNULL(@CurrentComponentName,@CurrentComponentName) + '] is mandatory and missing)' + @crlf + - @tsql AS Details - ; - - -- we will stop the test because it's possible to get the same message for other components - SET @canExitLoop = 1; - CONTINUE; - END; + SET @StringToExecute += N'SELECT + 4 AS CheckId, + 100 AS [Priority], + b.database_name AS [Database Name], + ''Snapshot backups'' AS [Finding], + ''The database '' + QUOTENAME(b.database_name) + '' has had '' + CONVERT(VARCHAR(10), COUNT(*)) + '' snapshot backups. This message is purely informational.'' AS [Warning] + FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b + WHERE b.is_snapshot = 1 + GROUP BY b.database_name;' + @crlf; - IF(@TmpCnt <> @OutputCounterExpectedValue) - BEGIN - INSERT INTO #BlitzResults( - CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT - 227 AS CheckID , - 253 AS Priority , - 'First Responder Kit' AS FindingsGroup , - 'Component Missing: ' + @CurrentComponentName AS Finding , - 'http://FirstResponderKit.org' AS URL , - 'Download an updated version of the First Responder Kit to install it.' AS Details - ; - - -- as it's missing, no value for SubjectFullPath - DELETE FROM #StatementsToRun4FRKVersionCheck WHERE SubjectName = @CurrentComponentName ; - CONTINUE; - END; + IF @Debug = 1 + PRINT @StringToExecute; - SET @CurrentComponentMandatoryCheckOK = 1; - END; - - IF(@StatementCheckName = 'VersionCheckMode') - BEGIN - IF(@CurrentComponentMandatoryCheckOK = 0) - BEGIN - INSERT INTO #BlitzResults( - CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT - 226 AS CheckID , - 253 AS Priority , - 'First Responder Kit' AS FindingsGroup , - 'Version Check Failed (unexpectedly modified checks ordering)' AS Finding , - 'http://FirstResponderKit.org' AS URL , - 'Download an updated First Responder Kit. Version check failed because "Mandatory" check has not been completed before for current component' + @crlf + - 'Error: version check mode happenned before "Mandatory" check for component called "' + @CurrentComponentFullName + '"' - ; - - -- we will stop the test because it's possible to get the same message for other components - SET @canExitLoop = 1; - CONTINUE; - END; + INSERT #Warnings ( CheckId, Priority, DatabaseName, Finding, Warning ) + EXEC sys.sp_executesql @StringToExecute; + + /*It''s fine to take backups of read only databases, but it''s not always necessary (there''s no new data, after all).*/ - -- outputs counter - EXEC @ExecRet = sp_executesql @tsql, N'@cnt INT OUTPUT',@cnt = @TmpCnt OUTPUT; + SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - IF(@ExecRet <> 0) - BEGIN - INSERT INTO #BlitzResults( - CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT - 226 AS CheckID , - 253 AS Priority , - 'First Responder Kit' AS FindingsGroup , - 'Version Check Failed (dynamic query failure)' AS Finding , - 'http://FirstResponderKit.org' AS URL , - 'Download an updated First Responder Kit. Version check failed because a change has been made to the code generator.' + @crlf + - 'Error: following query failed at execution (check if component [' + @CurrentComponentFullName + '] can run in VersionCheckMode)' + @crlf + - @tsql AS Details - ; - - -- we will stop the test because it's possible to get the same message for other components - SET @canExitLoop = 1; - CONTINUE; - END; + SET @StringToExecute += N'SELECT + 5 AS CheckId, + 100 AS [Priority], + b.database_name AS [Database Name], + ''Read only state backups'' AS [Finding], + ''The database '' + QUOTENAME(b.database_name) + '' has been backed up '' + CONVERT(VARCHAR(10), COUNT(*)) + '' times while in a read-only state. This can be normal if it''''s a secondary, but a bit odd otherwise.'' AS [Warning] + FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b + WHERE b.is_readonly = 1 + GROUP BY b.database_name;' + @crlf; - IF(@TmpCnt <> @OutputCounterExpectedValue) - BEGIN - INSERT INTO #BlitzResults( - CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT - 228 AS CheckID , - 253 AS Priority , - 'First Responder Kit' AS FindingsGroup , - 'Component Outdated: ' + @CurrentComponentFullName AS Finding , - 'http://FirstResponderKit.org' AS URL , - 'Download an updated First Responder Kit. Component ' + @CurrentComponentFullName + ' is not at the minimum version required to run this procedure' + @crlf + - 'VersionCheckMode has been introduced in component version date after "20190320". This means its version is lower than or equal to that date.' AS Details; - ; - - DELETE FROM #StatementsToRun4FRKVersionCheck WHERE SubjectFullPath = @CurrentComponentFullName ; - CONTINUE; - END; + IF @Debug = 1 + PRINT @StringToExecute; - SET @CurrentComponentVersionCheckModeOK = 1; - END; + INSERT #Warnings ( CheckId, Priority, DatabaseName, Finding, Warning ) + EXEC sys.sp_executesql @StringToExecute; + + /*So, I''ve come across people who think they need to change their database to single user mode to take a backup. Or that doing that will help something. I just need to know, here.*/ - IF(@StatementCheckName = 'VersionCheck') - BEGIN - IF(@CurrentComponentMandatoryCheckOK = 0 OR @CurrentComponentVersionCheckModeOK = 0) - BEGIN - INSERT INTO #BlitzResults( - CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT - 226 AS CheckID , - 253 AS Priority , - 'First Responder Kit' AS FindingsGroup , - 'Version Check Failed (unexpectedly modified checks ordering)' AS Finding , - 'http://FirstResponderKit.org' AS URL , - 'Download an updated First Responder Kit. Version check failed because "VersionCheckMode" check has not been completed before for component called "' + @CurrentComponentFullName + '"' + @crlf + - 'Error: VersionCheck happenned before "VersionCheckMode" check for component called "' + @CurrentComponentFullName + '"' - ; - - -- we will stop the test because it's possible to get the same message for other components - SET @canExitLoop = 1; - CONTINUE; - END; + SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - EXEC @ExecRet = sp_executesql @tsql , N'@ExecRet INT OUTPUT, @ObjDate DATETIME OUTPUT', @ExecRet = @InnerExecRet OUTPUT, @ObjDate = @CurrentComponentVersionDate OUTPUT; + SET @StringToExecute += N'SELECT + 6 AS CheckId, + 100 AS [Priority], + b.database_name AS [Database Name], + ''Single user mode backups'' AS [Finding], + ''The database '' + QUOTENAME(b.database_name) + '' has been backed up '' + CONVERT(VARCHAR(10), COUNT(*)) + '' times while in single-user mode. This is really weird! Make sure your backup process doesn''''t include a mode change anywhere.'' AS [Warning] + FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b + WHERE b.is_single_user = 1 + GROUP BY b.database_name;' + @crlf; - IF(@ExecRet <> 0) - BEGIN - INSERT INTO #BlitzResults( - CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT - 226 AS CheckID , - 253 AS Priority , - 'First Responder Kit' AS FindingsGroup , - 'Version Check Failed (dynamic query failure)' AS Finding , - 'http://FirstResponderKit.org' AS URL , - 'Download an updated First Responder Kit. The version check failed because a change has been made to the code generator.' + @crlf + - 'Error: following query failed at execution (check if component [' + @CurrentComponentFullName + '] is at the expected version)' + @crlf + - @tsql AS Details - ; - - -- we will stop the test because it's possible to get the same message for other components - SET @canExitLoop = 1; - CONTINUE; - END; - - - IF(@InnerExecRet <> 0) - BEGIN - INSERT INTO #BlitzResults( - CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT - 226 AS CheckID , - 253 AS Priority , - 'First Responder Kit' AS FindingsGroup , - 'Version Check Failed (Failed dynamic SP call to ' + @CurrentComponentFullName + ')' AS Finding , - 'http://FirstResponderKit.org' AS URL , - 'Download an updated First Responder Kit. Error: following query failed at execution (check if component [' + @CurrentComponentFullName + '] is at the expected version)' + @crlf + - 'Return code: ' + CONVERT(VARCHAR(10),@InnerExecRet) + @crlf + - 'T-SQL Query: ' + @crlf + - @tsql AS Details - ; - - -- advance to next component - DELETE FROM #StatementsToRun4FRKVersionCheck WHERE SubjectFullPath = @CurrentComponentFullName ; - CONTINUE; - END; + IF @Debug = 1 + PRINT @StringToExecute; - IF(@CurrentComponentVersionDate < @VersionDate) - BEGIN - - INSERT INTO #BlitzResults( - CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT - 228 AS CheckID , - 253 AS Priority , - 'First Responder Kit' AS FindingsGroup , - 'Component Outdated: ' + @CurrentComponentFullName AS Finding , - 'http://FirstResponderKit.org' AS URL , - 'Download and install the latest First Responder Kit - you''re running some older code, and it doesn''t get better with age.' AS Details - ; - - RAISERROR('Component %s is outdated',10,1,@CurrentComponentFullName); - -- advance to next component - DELETE FROM #StatementsToRun4FRKVersionCheck WHERE SubjectFullPath = @CurrentComponentFullName ; - CONTINUE; - END; + INSERT #Warnings ( CheckId, Priority, DatabaseName, Finding, Warning ) + EXEC sys.sp_executesql @StringToExecute; + + /*C''mon, it''s 2017. Take your backups with CHECKSUMS, people.*/ - ELSE IF(@CurrentComponentVersionDate > @VersionDate AND @BlitzIsOutdatedComparedToOthers = 0) - BEGIN - SET @BlitzIsOutdatedComparedToOthers = 1; - RAISERROR('Procedure %s is outdated',10,1,@spBlitzFullName); - IF(@MaximumVersionDate IS NULL OR @MaximumVersionDate < @CurrentComponentVersionDate) - BEGIN - SET @MaximumVersionDate = @CurrentComponentVersionDate; - END; - END; - /* Kept for debug purpose: - ELSE - BEGIN - INSERT INTO #BlitzResults( - CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT - 2000 AS CheckID , - 250 AS Priority , - 'Informational' AS FindingsGroup , - 'First Responder kit component ' + @CurrentComponentFullName + ' is at the expected version' AS Finding , - 'https://www.BrentOzar.com/blitz/' AS URL , - 'Version date is: ' + CONVERT(VARCHAR(32),@CurrentComponentVersionDate,121) AS Details - ; - END; - */ - END; + SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - -- could be performed differently to minimize computation - DELETE FROM #StatementsToRun4FRKVersionCheck WHERE StatementId = @CurrentStatementId ; - END; -END; + SET @StringToExecute += N'SELECT + 7 AS CheckId, + 100 AS [Priority], + b.database_name AS [Database Name], + ''No CHECKSUMS'' AS [Finding], + ''The database '' + QUOTENAME(b.database_name) + '' has been backed up '' + CONVERT(VARCHAR(10), COUNT(*)) + '' times without CHECKSUMS in the past 30 days. CHECKSUMS can help alert you to corruption errors.'' AS [Warning] + FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b + WHERE b.has_backup_checksums = 0 + AND b.backup_finish_date >= DATEADD(DAY, -30, SYSDATETIME()) + GROUP BY b.database_name;' + @crlf; + IF @Debug = 1 + PRINT @StringToExecute; -/*This counts memory dumps and gives min and max date of in view*/ -IF @ProductVersionMajor >= 10 - AND NOT (@ProductVersionMajor = 10.5 AND @ProductVersionMinor < 4297) /* Skip due to crash bug: https://support.microsoft.com/en-us/help/2908087 */ - AND NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 171 ) - BEGIN - IF EXISTS ( SELECT 1 - FROM sys.all_objects - WHERE name = 'dm_server_memory_dumps' ) - BEGIN - IF EXISTS (SELECT * FROM [sys].[dm_server_memory_dumps] WHERE [creation_time] >= DATEADD(YEAR, -1, GETDATE())) + INSERT #Warnings ( CheckId, Priority, DatabaseName, Finding, Warning ) + EXEC sys.sp_executesql @StringToExecute; + + /*Damaged is a Black Flag album. You don''t want your backups to be like a Black Flag album. */ - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 171) WITH NOWAIT; - - INSERT INTO [#BlitzResults] - ( [CheckID] , - [Priority] , - [FindingsGroup] , - [Finding] , - [URL] , - [Details] ) - - SELECT - 171 AS [CheckID] , - 20 AS [Priority] , - 'Reliability' AS [FindingsGroup] , - 'Memory Dumps Have Occurred' AS [Finding] , - 'https://www.brentozar.com/go/dump' AS [URL] , - ( 'That ain''t good. I''ve had ' + - CAST(COUNT(*) AS VARCHAR(100)) + ' memory dumps between ' + - CAST(CAST(MIN([creation_time]) AS DATETIME) AS VARCHAR(100)) + - ' and ' + - CAST(CAST(MAX([creation_time]) AS DATETIME) AS VARCHAR(100)) + - '!' - ) AS [Details] - FROM - [sys].[dm_server_memory_dumps] - WHERE [creation_time] >= DATEADD(year, -1, GETDATE()); + SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - END; - END; - END; - -/*Checks to see if you're on Developer or Evaluation*/ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 173 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 173) WITH NOWAIT; - - INSERT INTO [#BlitzResults] - ( [CheckID] , - [Priority] , - [FindingsGroup] , - [Finding] , - [URL] , - [Details] ) - - SELECT - 173 AS [CheckID] , - 200 AS [Priority] , - 'Licensing' AS [FindingsGroup] , - 'Non-Production License' AS [Finding] , - 'https://www.brentozar.com/go/licensing' AS [URL] , - ( 'We''re not the licensing police, but if this is supposed to be a production server, and you''re running ' + - CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) + - ' the good folks at Microsoft might get upset with you. Better start counting those cores.' - ) AS [Details] - WHERE CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) LIKE '%Developer%' - OR CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) LIKE '%Evaluation%'; - - END; + SET @StringToExecute += N'SELECT + 8 AS CheckId, + 100 AS [Priority], + b.database_name AS [Database Name], + ''Damaged backups'' AS [Finding], + ''The database '' + QUOTENAME(b.database_name) + '' has had '' + CONVERT(VARCHAR(10), COUNT(*)) + '' damaged backups taken without stopping to throw an error. This is done by specifying CONTINUE_AFTER_ERROR in your BACKUP commands.'' AS [Warning] + FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b + WHERE b.is_damaged = 1 + GROUP BY b.database_name;' + @crlf; -/*Checks to see if Buffer Pool Extensions are in use*/ - IF @ProductVersionMajor >= 12 - AND NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 174 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 174) WITH NOWAIT; - - INSERT INTO [#BlitzResults] - ( [CheckID] , - [Priority] , - [FindingsGroup] , - [Finding] , - [URL] , - [Details] ) - - SELECT - 174 AS [CheckID] , - 200 AS [Priority] , - 'Performance' AS [FindingsGroup] , - 'Buffer Pool Extensions Enabled' AS [Finding] , - 'https://www.brentozar.com/go/bpe' AS [URL] , - ( 'You have Buffer Pool Extensions enabled, and one lives here: ' + - [path] + - '. It''s currently ' + - CASE WHEN [current_size_in_kb] / 1024. / 1024. > 0 - THEN CAST([current_size_in_kb] / 1024. / 1024. AS VARCHAR(100)) - + ' GB' - ELSE CAST([current_size_in_kb] / 1024. AS VARCHAR(100)) - + ' MB' - END + - '. Did you know that BPEs only provide single threaded access 8KB (one page) at a time?' - ) AS [Details] - FROM sys.dm_os_buffer_pool_extension_configuration - WHERE [state_description] <> 'BUFFER POOL EXTENSION DISABLED'; + IF @Debug = 1 + PRINT @StringToExecute; - END; + INSERT #Warnings ( CheckId, Priority, DatabaseName, Finding, Warning ) + EXEC sys.sp_executesql @StringToExecute; + + /*Checking for encrypted backups and the last backup of the encryption key.*/ -/*Check for too many tempdb files*/ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 175 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 175) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT DISTINCT - 175 AS CheckID , - 'TempDB' AS DatabaseName , - 170 AS Priority , - 'File Configuration' AS FindingsGroup , - 'TempDB Has >16 Data Files' AS Finding , - 'https://www.brentozar.com/go/tempdb' AS URL , - 'Woah, Nelly! TempDB has ' + CAST(COUNT_BIG(*) AS VARCHAR(30)) + '. Did you forget to terminate a loop somewhere?' AS Details - FROM sys.[master_files] AS [mf] - WHERE [mf].[database_id] = 2 AND [mf].[type] = 0 - HAVING COUNT_BIG(*) > 16; - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 176 ) - BEGIN - - IF EXISTS ( SELECT 1 - FROM sys.all_objects - WHERE name = 'dm_xe_sessions' ) - - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 176) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT DISTINCT - 176 AS CheckID , - '' AS DatabaseName , - 200 AS Priority , - 'Monitoring' AS FindingsGroup , - 'Extended Events Hyperextension' AS Finding , - 'https://www.brentozar.com/go/xe' AS URL , - 'Hey big spender, you have ' + CAST(COUNT_BIG(*) AS VARCHAR(30)) + ' Extended Events sessions running. You sure you meant to do that?' AS Details - FROM sys.dm_xe_sessions - WHERE [name] NOT IN - ( 'AlwaysOn_health', - 'system_health', - 'telemetry_xevents', - 'sp_server_diagnostics', - 'sp_server_diagnostics session', - 'hkenginexesession' ) - AND name NOT LIKE '%$A%' - HAVING COUNT_BIG(*) >= 2; - END; - END; - - /*Harmful startup parameter*/ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 177 ) - BEGIN - - IF EXISTS ( SELECT 1 - FROM sys.all_objects - WHERE name = 'dm_server_registry' ) - - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 177) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT DISTINCT - 177 AS CheckID , - '' AS DatabaseName , - 5 AS Priority , - 'Monitoring' AS FindingsGroup , - 'Disabled Internal Monitoring Features' AS Finding , - 'https://msdn.microsoft.com/en-us/library/ms190737.aspx' AS URL , - 'You have -x as a startup parameter. You should head to the URL and read more about what it does to your system.' AS Details - FROM - [sys].[dm_server_registry] AS [dsr] - WHERE - [dsr].[registry_key] LIKE N'%MSSQLServer\Parameters' - AND [dsr].[value_data] = '-x';; - END; - END; - - - /* Reliability - Dangerous Third Party Modules - 179 */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 179 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 179) WITH NOWAIT; - - INSERT INTO [#BlitzResults] - ( [CheckID] , - [Priority] , - [FindingsGroup] , - [Finding] , - [URL] , - [Details] ) - - SELECT - 179 AS [CheckID] , - 5 AS [Priority] , - 'Reliability' AS [FindingsGroup] , - 'Dangerous Third Party Modules' AS [Finding] , - 'https://support.microsoft.com/en-us/kb/2033238' AS [URL] , - ( COALESCE(company, '') + ' - ' + COALESCE(description, '') + ' - ' + COALESCE(name, '') + ' - suspected dangerous third party module is installed.') AS [Details] - FROM sys.dm_os_loaded_modules - WHERE UPPER(name) LIKE UPPER('%\ENTAPI.DLL') OR UPPER(name) LIKE '%MFEBOPK.SYS' /* McAfee VirusScan Enterprise */ - OR UPPER(name) LIKE UPPER('%\HIPI.DLL') OR UPPER(name) LIKE UPPER('%\HcSQL.dll') OR UPPER(name) LIKE UPPER('%\HcApi.dll') OR UPPER(name) LIKE UPPER('%\HcThe.dll') /* McAfee Host Intrusion */ - OR UPPER(name) LIKE UPPER('%\SOPHOS_DETOURED.DLL') OR UPPER(name) LIKE UPPER('%\SOPHOS_DETOURED_x64.DLL') OR UPPER(name) LIKE UPPER('%\SWI_IFSLSP_64.dll') OR UPPER(name) LIKE UPPER('%\SOPHOS~%.dll') /* Sophos AV */ - OR UPPER(name) LIKE UPPER('%\PIOLEDB.DLL') OR UPPER(name) LIKE UPPER('%\PISDK.DLL') /* OSISoft PI data access */ - OR UPPER(name) LIKE UPPER('%ScriptControl%.dll') OR UPPER(name) LIKE UPPER('%umppc%.dll') /* CrowdStrike */ - OR UPPER(name) LIKE UPPER('%perfiCrcPerfMonMgr.DLL') /* Trend Micro OfficeScan */ - OR UPPER(name) LIKE UPPER('%NLEMSQL.SYS') /* NetLib Encryptionizer-Software. */ - OR UPPER(name) LIKE UPPER('%MFETDIK.SYS') /* McAfee Anti-Virus Mini-Firewall */ - OR UPPER(name) LIKE UPPER('%ANTIVIRUS%'); /* To pick up sqlmaggieAntiVirus_64.dll (malware) or anything else labelled AntiVirus */ - /* MS docs link for blacklisted modules: https://learn.microsoft.com/en-us/troubleshoot/sql/performance/performance-consistency-issues-filter-drivers-modules */ - END; + /*2014 ONLY*/ - /*Find shrink database tasks*/ +IF @ProductVersionMajor >= 12 + BEGIN - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 180 ) - AND CONVERT(VARCHAR(128), SERVERPROPERTY ('productversion')) LIKE '1%' /* Only run on 2008+ */ - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 180) WITH NOWAIT; - - WITH XMLNAMESPACES ('www.microsoft.com/SqlServer/Dts' AS [dts]) - ,[maintenance_plan_steps] AS ( - SELECT [name] - , [id] -- ID required to link maintenace plan with jobs and jobhistory (sp_Blitz Issue #776) - , CAST(CAST([packagedata] AS VARBINARY(MAX)) AS XML) AS [maintenance_plan_xml] - FROM [msdb].[dbo].[sysssispackages] - WHERE [packagetype] = 6 - ) - INSERT INTO [#BlitzResults] - ( [CheckID] , - [Priority] , - [FindingsGroup] , - [Finding] , - [URL] , - [Details] ) - SELECT - 180 AS [CheckID] , - -- sp_Blitz Issue #776 - -- Job has history and was executed in the last 30 days - CASE WHEN (cast(datediff(dd, substring(cast(sjh.run_date as nvarchar(10)), 1, 4) + '-' + substring(cast(sjh.run_date as nvarchar(10)), 5, 2) + '-' + substring(cast(sjh.run_date as nvarchar(10)), 7, 2), GETDATE()) AS INT) < 30) OR (j.[enabled] = 1 AND ssc.[enabled] = 1 )THEN - 100 - ELSE -- no job history (implicit) AND job not run in the past 30 days AND (Job disabled OR Job Schedule disabled) - 200 - END AS Priority, - 'Performance' AS [FindingsGroup] , - 'Shrink Database Step In Maintenance Plan' AS [Finding] , - 'https://www.brentozar.com/go/autoshrink' AS [URL] , - 'The maintenance plan ' + [mps].[name] + ' has a step to shrink databases in it. Shrinking databases is as outdated as maintenance plans.' - + CASE WHEN COALESCE(ssc.name,'0') != '0' THEN + ' (Schedule: [' + ssc.name + '])' ELSE + '' END AS [Details] - FROM [maintenance_plan_steps] [mps] - CROSS APPLY [maintenance_plan_xml].[nodes]('//dts:Executables/dts:Executable') [t]([c]) - join msdb.dbo.sysmaintplan_subplans as sms - on mps.id = sms.plan_id - JOIN msdb.dbo.sysjobs j - on sms.job_id = j.job_id - LEFT OUTER JOIN msdb.dbo.sysjobsteps AS step - ON j.job_id = step.job_id - LEFT OUTER JOIN msdb.dbo.sysjobschedules AS sjsc - ON j.job_id = sjsc.job_id - LEFT OUTER JOIN msdb.dbo.sysschedules AS ssc - ON sjsc.schedule_id = ssc.schedule_id - AND sjsc.job_id = j.job_id - LEFT OUTER JOIN msdb.dbo.sysjobhistory AS sjh - ON j.job_id = sjh.job_id - AND step.step_id = sjh.step_id - AND sjh.run_date IN (SELECT max(sjh2.run_date) FROM msdb.dbo.sysjobhistory AS sjh2 WHERE sjh2.job_id = j.job_id) -- get the latest entry date - AND sjh.run_time IN (SELECT max(sjh3.run_time) FROM msdb.dbo.sysjobhistory AS sjh3 WHERE sjh3.job_id = j.job_id AND sjh3.run_date = sjh.run_date) -- get the latest entry time - WHERE [c].[value]('(@dts:ObjectName)', 'VARCHAR(128)') = 'Shrink Database Task'; + SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - END; + SET @StringToExecute += N'SELECT + 9 AS CheckId, + 100 AS [Priority], + b.database_name AS [Database Name], + ''Encrypted backups'' AS [Finding], + ''The database '' + QUOTENAME(b.database_name) + '' has had '' + CONVERT(VARCHAR(10), COUNT(*)) + '' '' + b.encryptor_type + '' backups, and the last time a certificate was backed up is ' + + CASE WHEN LOWER(@MSDBName) <> N'msdb' + THEN + N'...well, that information is on another server, anyway.'' AS [Warning]' + ELSE + CONVERT(VARCHAR(30), (SELECT MAX(c.pvt_key_last_backup_date) FROM sys.certificates AS c WHERE c.name NOT LIKE '##%')) + N'.'' AS [Warning]' + END + + N' + FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b + WHERE b.encryptor_type IS NOT NULL + GROUP BY b.database_name, b.encryptor_type;' + @crlf; - /*Find repetitive maintenance tasks*/ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 181 ) - AND CONVERT(VARCHAR(128), SERVERPROPERTY ('productversion')) LIKE '1%' /* Only run on 2008+ */ - BEGIN - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 181) WITH NOWAIT; - - WITH XMLNAMESPACES ('www.microsoft.com/SqlServer/Dts' AS [dts]) - ,[maintenance_plan_steps] AS ( - SELECT [name] - , CAST(CAST([packagedata] AS VARBINARY(MAX)) AS XML) AS [maintenance_plan_xml] - FROM [msdb].[dbo].[sysssispackages] - WHERE [packagetype] = 6 - ), [maintenance_plan_table] AS ( - SELECT [mps].[name] - ,[c].[value]('(@dts:ObjectName)', 'NVARCHAR(128)') AS [step_name] - FROM [maintenance_plan_steps] [mps] - CROSS APPLY [maintenance_plan_xml].[nodes]('//dts:Executables/dts:Executable') [t]([c]) - ), [mp_steps_pretty] AS (SELECT DISTINCT [m1].[name] , - STUFF((SELECT N', ' + [m2].[step_name] FROM [maintenance_plan_table] AS [m2] WHERE [m1].[name] = [m2].[name] - FOR XML PATH(N'')), 1, 2, N'') AS [maintenance_plan_steps] - FROM [maintenance_plan_table] AS [m1]) - - INSERT INTO [#BlitzResults] - ( [CheckID] , - [Priority] , - [FindingsGroup] , - [Finding] , - [URL] , - [Details] ) - - SELECT - 181 AS [CheckID] , - 100 AS [Priority] , - 'Performance' AS [FindingsGroup] , - 'Repetitive Steps In Maintenance Plans' AS [Finding] , - 'https://ola.hallengren.com/' AS [URL] , - 'The maintenance plan ' + [m].[name] + ' is doing repetitive work on indexes and statistics. Perhaps it''s time to try something more modern?' AS [Details] - FROM [mp_steps_pretty] m - WHERE m.[maintenance_plan_steps] LIKE '%Rebuild%Reorganize%' - OR m.[maintenance_plan_steps] LIKE '%Rebuild%Update%'; + IF @Debug = 1 + PRINT @StringToExecute; - END; - + INSERT #Warnings ( CheckId, Priority, DatabaseName, Finding, Warning ) + EXEC sys.sp_executesql @StringToExecute; + + END + + /*Looking for backups that have BULK LOGGED data in them -- this can screw up point in time LOG recovery.*/ - /* Reliability - No Failover Cluster Nodes Available - 184 */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 184 ) - AND CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)) NOT LIKE '10%' - AND CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)) NOT LIKE '9%' - BEGIN - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 184) WITH NOWAIT; - - SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) - SELECT TOP 1 - 184 AS CheckID , - 20 AS Priority , - ''Reliability'' AS FindingsGroup , - ''No Failover Cluster Nodes Available'' AS Finding , - ''https://www.brentozar.com/go/node'' AS URL , - ''There are no failover cluster nodes available if the active node fails'' AS Details - FROM ( - SELECT SUM(CASE WHEN [status] = 0 AND [is_current_owner] = 0 THEN 1 ELSE 0 END) AS [available_nodes] - FROM sys.dm_os_cluster_nodes - ) a - WHERE [available_nodes] < 1 OPTION (RECOMPILE)'; - - IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; - IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; - - EXECUTE(@StringToExecute); - END; + SET @StringToExecute =N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - /* Reliability - TempDB File Error */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 191 ) - AND (SELECT COUNT(*) FROM sys.master_files WHERE database_id = 2) <> (SELECT COUNT(*) FROM tempdb.sys.database_files) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 191) WITH NOWAIT - - INSERT INTO [#BlitzResults] - ( [CheckID] , - [Priority] , - [FindingsGroup] , - [Finding] , - [URL] , - [Details] ) - - SELECT - 191 AS [CheckID] , - 50 AS [Priority] , - 'Reliability' AS [FindingsGroup] , - 'TempDB File Error' AS [Finding] , - 'https://www.brentozar.com/go/tempdboops' AS [URL] , - 'Mismatch between the number of TempDB files in sys.master_files versus tempdb.sys.database_files' AS [Details]; - END; + SET @StringToExecute += N'SELECT + 10 AS CheckId, + 100 AS [Priority], + b.database_name AS [Database Name], + ''Bulk logged backups'' AS [Finding], + ''The database '' + QUOTENAME(b.database_name) + '' has had '' + CONVERT(VARCHAR(10), COUNT(*)) + '' backups with bulk logged data. This can make point in time recovery awkward. '' AS [Warning] + FROM ' + QUOTENAME(@MSDBName) + '.dbo.backupset AS b + WHERE b.has_bulk_logged_data = 1 + GROUP BY b.database_name;' + @crlf; -/*Perf - Odd number of cores in a socket*/ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL - AND CheckID = 198 ) - AND EXISTS ( SELECT 1 - FROM sys.dm_os_schedulers - WHERE is_online = 1 - AND scheduler_id < 255 - AND parent_node_id < 64 - GROUP BY parent_node_id, - is_online - HAVING ( COUNT(cpu_id) + 2 ) % 2 = 1 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 198) WITH NOWAIT - - INSERT INTO #BlitzResults - ( - CheckID, - DatabaseName, - Priority, - FindingsGroup, - Finding, - URL, - Details - ) - SELECT 198 AS CheckID, - NULL AS DatabaseName, - 10 AS Priority, - 'Performance' AS FindingsGroup, - 'CPU w/Odd Number of Cores' AS Finding, - 'https://www.brentozar.com/go/oddity' AS URL, - 'Node ' + CONVERT(VARCHAR(10), parent_node_id) + ' has ' + CONVERT(VARCHAR(10), COUNT(cpu_id)) - + CASE WHEN COUNT(cpu_id) = 1 THEN ' core assigned to it. This is a really bad NUMA configuration.' - ELSE ' cores assigned to it. This is a really bad NUMA configuration.' - END AS Details - FROM sys.dm_os_schedulers - WHERE is_online = 1 - AND scheduler_id < 255 - AND parent_node_id < 64 - AND EXISTS ( - SELECT 1 - FROM ( SELECT memory_node_id, SUM(online_scheduler_count) AS schedulers - FROM sys.dm_os_nodes - WHERE memory_node_id < 64 - GROUP BY memory_node_id ) AS nodes - HAVING MIN(nodes.schedulers) <> MAX(nodes.schedulers) - ) - GROUP BY parent_node_id, - is_online - HAVING ( COUNT(cpu_id) + 2 ) % 2 = 1; - - END; + IF @Debug = 1 + PRINT @StringToExecute; -/*Begin: checking default trace for odd DBCC activity*/ - - --Grab relevant event data - IF @TraceFileIssue = 0 - BEGIN - SELECT UPPER( - REPLACE( - SUBSTRING(CONVERT(NVARCHAR(MAX), t.TextData), 0, - ISNULL( - NULLIF( - CHARINDEX('(', CONVERT(NVARCHAR(MAX), t.TextData)), - 0), - LEN(CONVERT(NVARCHAR(MAX), t.TextData)) + 1 )) --This replaces everything up to an open paren, if one exists. - , SUBSTRING(CONVERT(NVARCHAR(MAX), t.TextData), - ISNULL( - NULLIF( - CHARINDEX(' WITH ',CONVERT(NVARCHAR(MAX), t.TextData)) - , 0), - LEN(CONVERT(NVARCHAR(MAX), t.TextData)) + 1), - LEN(CONVERT(NVARCHAR(MAX), t.TextData)) + 1 ) - , '') --This replaces any optional WITH clause to a DBCC command, like tableresults. - ) AS [dbcc_event_trunc_upper], - UPPER( - REPLACE( - CONVERT(NVARCHAR(MAX), t.TextData), SUBSTRING(CONVERT(NVARCHAR(MAX), t.TextData), - ISNULL( - NULLIF( - CHARINDEX(' WITH ',CONVERT(NVARCHAR(MAX), t.TextData)) - , 0), - LEN(CONVERT(NVARCHAR(MAX), t.TextData)) + 1), - LEN(CONVERT(NVARCHAR(MAX), t.TextData)) + 1 ), '')) AS [dbcc_event_full_upper], - MIN(t.StartTime) OVER (PARTITION BY CONVERT(NVARCHAR(128), t.TextData)) AS min_start_time, - MAX(t.StartTime) OVER (PARTITION BY CONVERT(NVARCHAR(128), t.TextData)) AS max_start_time, - t.NTUserName AS [nt_user_name], - t.NTDomainName AS [nt_domain_name], - t.HostName AS [host_name], - t.ApplicationName AS [application_name], - t.LoginName [login_name], - t.DBUserName AS [db_user_name] - INTO #dbcc_events_from_trace - FROM #fnTraceGettable AS t - WHERE t.EventClass = 116 - OPTION(RECOMPILE) - END; + INSERT #Warnings ( CheckId, Priority, DatabaseName, Finding, Warning ) + EXEC sys.sp_executesql @StringToExecute; + + /*Looking for recovery model being switched between FULL and SIMPLE, because it''s a bad practice.*/ - /*Overall count of DBCC events excluding silly stuff*/ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 203 ) - AND @TraceFileIssue = 0 - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 203) WITH NOWAIT - - INSERT INTO [#BlitzResults] - ( [CheckID] , - [Priority] , - [FindingsGroup] , - [Finding] , - [URL] , - [Details] ) - SELECT 203 AS CheckID , - 50 AS Priority , - 'DBCC Events' AS FindingsGroup , - 'Overall Events' AS Finding , - 'https://www.BrentOzar.com/go/dbcc' AS URL , - CAST(COUNT(*) AS NVARCHAR(100)) + ' DBCC events have taken place between ' + CONVERT(NVARCHAR(30), MIN(d.min_start_time)) + ' and ' + CONVERT(NVARCHAR(30), MAX(d.max_start_time)) + - '. This does not include CHECKDB and other usually benign DBCC events.' - AS Details - FROM #dbcc_events_from_trace d - /* This WHERE clause below looks horrible, but it's because users can run stuff like - DBCC LOGINFO - with lots of spaces (or carriage returns, or comments) in between the DBCC and the - command they're trying to run. See Github issues 1062, 1074, 1075. - */ - WHERE d.dbcc_event_full_upper NOT LIKE '%DBCC%ADDINSTANCE%' - AND d.dbcc_event_full_upper NOT LIKE '%DBCC%AUTOPILOT%' - AND d.dbcc_event_full_upper NOT LIKE '%DBCC%CHECKALLOC%' - AND d.dbcc_event_full_upper NOT LIKE '%DBCC%CHECKCATALOG%' - AND d.dbcc_event_full_upper NOT LIKE '%DBCC%CHECKCONSTRAINTS%' - AND d.dbcc_event_full_upper NOT LIKE '%DBCC%CHECKDB%' - AND d.dbcc_event_full_upper NOT LIKE '%DBCC%CHECKFILEGROUP%' - AND d.dbcc_event_full_upper NOT LIKE '%DBCC%CHECKIDENT%' - AND d.dbcc_event_full_upper NOT LIKE '%DBCC%CHECKPRIMARYFILE%' - AND d.dbcc_event_full_upper NOT LIKE '%DBCC%CHECKTABLE%' - AND d.dbcc_event_full_upper NOT LIKE '%DBCC%CLEANTABLE%' - AND d.dbcc_event_full_upper NOT LIKE '%DBCC%DBINFO%' - AND d.dbcc_event_full_upper NOT LIKE '%DBCC%ERRORLOG%' - AND d.dbcc_event_full_upper NOT LIKE '%DBCC%INCREMENTINSTANCE%' - AND d.dbcc_event_full_upper NOT LIKE '%DBCC%INPUTBUFFER%' - AND d.dbcc_event_full_upper NOT LIKE '%DBCC%LOGINFO%' - AND d.dbcc_event_full_upper NOT LIKE '%DBCC%OPENTRAN%' - AND d.dbcc_event_full_upper NOT LIKE '%DBCC%SETINSTANCE%' - AND d.dbcc_event_full_upper NOT LIKE '%DBCC%SHOWFILESTATS%' - AND d.dbcc_event_full_upper NOT LIKE '%DBCC%SHOW_STATISTICS%' - AND d.dbcc_event_full_upper NOT LIKE '%DBCC%SQLPERF%NETSTATS%' - AND d.dbcc_event_full_upper NOT LIKE '%DBCC%SQLPERF%LOGSPACE%' - AND d.dbcc_event_full_upper NOT LIKE '%DBCC%TRACEON%' - AND d.dbcc_event_full_upper NOT LIKE '%DBCC%TRACEOFF%' - AND d.dbcc_event_full_upper NOT LIKE '%DBCC%TRACESTATUS%' - AND d.dbcc_event_full_upper NOT LIKE '%DBCC%USEROPTIONS%' - AND d.application_name NOT LIKE 'Critical Care(R) Collector' - AND d.application_name NOT LIKE '%Red Gate Software Ltd SQL Prompt%' - AND d.application_name NOT LIKE '%Spotlight Diagnostic Server%' - AND d.application_name NOT LIKE '%SQL Diagnostic Manager%' - AND d.application_name NOT LIKE 'SQL Server Checkup%' - AND d.application_name NOT LIKE '%Sentry%' - AND d.application_name NOT LIKE '%LiteSpeed%' - AND d.application_name NOT LIKE '%SQL Monitor - Monitoring%' - + SET @StringToExecute =N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - HAVING COUNT(*) > 0; - - END; + SET @StringToExecute += N'SELECT + 11 AS CheckId, + 100 AS [Priority], + b.database_name AS [Database Name], + ''Recovery model switched'' AS [Finding], + ''The database '' + QUOTENAME(b.database_name) + '' has changed recovery models from between FULL and SIMPLE '' + CONVERT(VARCHAR(10), COUNT(DISTINCT b.recovery_model)) + '' times. This breaks the log chain and is generally a bad idea.'' AS [Warning] + FROM ' + QUOTENAME(@MSDBName) + '.dbo.backupset AS b + WHERE b.recovery_model <> ''BULK-LOGGED'' + GROUP BY b.database_name + HAVING COUNT(DISTINCT b.recovery_model) > 4;' + @crlf; - /*Check for someone running drop clean buffers*/ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 207 ) - AND @TraceFileIssue = 0 - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 207) WITH NOWAIT - - INSERT INTO [#BlitzResults] - ( [CheckID] , - [Priority] , - [FindingsGroup] , - [Finding] , - [URL] , - [Details] ) - SELECT 207 AS CheckID , - 10 AS Priority , - 'Performance' AS FindingsGroup , - 'DBCC DROPCLEANBUFFERS Ran Recently' AS Finding , - 'https://www.BrentOzar.com/go/dbcc' AS URL , - 'The user ' + COALESCE(d.nt_user_name, d.login_name) + ' has run DBCC DROPCLEANBUFFERS ' + CAST(COUNT(*) AS NVARCHAR(100)) + ' times between ' + CONVERT(NVARCHAR(30), MIN(d.min_start_time)) + ' and ' + CONVERT(NVARCHAR(30), MAX(d.max_start_time)) + - '. If this is a production box, know that you''re clearing all data out of memory when this happens. What kind of monster would do that?' - AS Details - FROM #dbcc_events_from_trace d - WHERE d.dbcc_event_full_upper = N'DBCC DROPCLEANBUFFERS' - GROUP BY COALESCE(d.nt_user_name, d.login_name) - HAVING COUNT(*) > 0; + IF @Debug = 1 + PRINT @StringToExecute; - END; + INSERT #Warnings ( CheckId, Priority, DatabaseName, Finding, Warning ) + EXEC sys.sp_executesql @StringToExecute; - /*Check for someone running free proc cache*/ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 208 ) - AND @TraceFileIssue = 0 - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 208) WITH NOWAIT - - INSERT INTO [#BlitzResults] - ( [CheckID] , - [Priority] , - [FindingsGroup] , - [Finding] , - [URL] , - [Details] ) - SELECT 208 AS CheckID , - 10 AS Priority , - 'DBCC Events' AS FindingsGroup , - 'DBCC FREEPROCCACHE Ran Recently' AS Finding , - 'https://www.BrentOzar.com/go/dbcc' AS URL , - 'The user ' + COALESCE(d.nt_user_name, d.login_name) + ' has run DBCC FREEPROCCACHE ' + CAST(COUNT(*) AS NVARCHAR(100)) + ' times between ' + CONVERT(NVARCHAR(30), MIN(d.min_start_time)) + ' and ' + CONVERT(NVARCHAR(30), MAX(d.max_start_time)) + - '. This has bad idea jeans written all over its butt, like most other bad idea jeans.' - AS Details - FROM #dbcc_events_from_trace d - WHERE d.dbcc_event_full_upper = N'DBCC FREEPROCCACHE' - GROUP BY COALESCE(d.nt_user_name, d.login_name) - HAVING COUNT(*) > 0; + /*Looking for uncompressed backups.*/ - END; + SET @StringToExecute =N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - /*Check for someone clearing wait stats*/ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 205 ) - AND @TraceFileIssue = 0 - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 205) WITH NOWAIT - - INSERT INTO [#BlitzResults] - ( [CheckID] , - [Priority] , - [FindingsGroup] , - [Finding] , - [URL] , - [Details] ) - SELECT 205 AS CheckID , - 50 AS Priority , - 'Performance' AS FindingsGroup , - 'Wait Stats Cleared Recently' AS Finding , - 'https://www.BrentOzar.com/go/dbcc' AS URL , - 'The user ' + COALESCE(d.nt_user_name, d.login_name) + ' has run DBCC SQLPERF(''SYS.DM_OS_WAIT_STATS'',CLEAR) ' + CAST(COUNT(*) AS NVARCHAR(100)) + ' times between ' + CONVERT(NVARCHAR(30), MIN(d.min_start_time)) + ' and ' + CONVERT(NVARCHAR(30), MAX(d.max_start_time)) + - '. Why are you clearing wait stats? What are you hiding?' - AS Details - FROM #dbcc_events_from_trace d - WHERE d.dbcc_event_full_upper = N'DBCC SQLPERF(''SYS.DM_OS_WAIT_STATS'',CLEAR)' - GROUP BY COALESCE(d.nt_user_name, d.login_name) - HAVING COUNT(*) > 0; + SET @StringToExecute += N'SELECT + 12 AS CheckId, + 100 AS [Priority], + b.database_name AS [Database Name], + ''Uncompressed backups'' AS [Finding], + ''The database '' + QUOTENAME(b.database_name) + '' has had '' + CONVERT(VARCHAR(10), COUNT(*)) + '' uncompressed backups in the last 30 days. This is a free way to save time and space. And SPACETIME. If your version of SQL supports it.'' AS [Warning] + FROM ' + QUOTENAME(@MSDBName) + '.dbo.backupset AS b + WHERE backup_size = compressed_backup_size AND type = ''D'' + AND b.backup_finish_date >= DATEADD(DAY, -30, SYSDATETIME()) + GROUP BY b.database_name;' + @crlf; - END; + IF @Debug = 1 + PRINT @StringToExecute; - /*Check for someone writing to pages. Yeah, right?*/ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 209 ) - AND @TraceFileIssue = 0 - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 209) WITH NOWAIT - - INSERT INTO [#BlitzResults] - ( [CheckID] , - [Priority] , - [FindingsGroup] , - [Finding] , - [URL] , - [Details] ) - SELECT 209 AS CheckID , - 10 AS Priority , - 'Reliability' AS FindingsGroup , - 'DBCC WRITEPAGE Used Recently' AS Finding , - 'https://www.BrentOzar.com/go/dbcc' AS URL , - 'The user ' + COALESCE(d.nt_user_name, d.login_name) + ' has run DBCC WRITEPAGE ' + CAST(COUNT(*) AS NVARCHAR(100)) + ' times between ' + CONVERT(NVARCHAR(30), MIN(d.min_start_time)) + ' and ' + CONVERT(NVARCHAR(30), MAX(d.max_start_time)) + - '. So, uh, are they trying to fix corruption, or cause corruption?' - AS Details - FROM #dbcc_events_from_trace d - WHERE d.dbcc_event_trunc_upper = N'DBCC WRITEPAGE' - GROUP BY COALESCE(d.nt_user_name, d.login_name) - HAVING COUNT(*) > 0; + INSERT #Warnings ( CheckId, Priority, DatabaseName, Finding, Warning ) + EXEC sys.sp_executesql @StringToExecute; - END; +RAISERROR('Rules analysis starting on temp tables', 0, 1) WITH NOWAIT; - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 210 ) - AND @TraceFileIssue = 0 - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 210) WITH NOWAIT - - INSERT INTO [#BlitzResults] - ( [CheckID] , - [Priority] , - [FindingsGroup] , - [Finding] , - [URL] , - [Details] ) - - SELECT 210 AS CheckID , - 10 AS Priority , - 'Performance' AS FindingsGroup , - 'DBCC SHRINK% Ran Recently' AS Finding , - 'https://www.BrentOzar.com/go/dbcc' AS URL , - 'The user ' + COALESCE(d.nt_user_name, d.login_name) + ' has run file shrinks ' + CAST(COUNT(*) AS NVARCHAR(100)) + ' times between ' + CONVERT(NVARCHAR(30), MIN(d.min_start_time)) + ' and ' + CONVERT(NVARCHAR(30), MAX(d.max_start_time)) + - '. So, uh, are they trying to cause bad performance on purpose?' - AS Details - FROM #dbcc_events_from_trace d - WHERE d.dbcc_event_trunc_upper LIKE N'DBCC SHRINK%' - GROUP BY COALESCE(d.nt_user_name, d.login_name) - HAVING COUNT(*) > 0; + INSERT #Warnings ( CheckId, Priority, DatabaseName, Finding, Warning ) + SELECT + 13 AS CheckId, + 100 AS Priority, + r.DatabaseName as [DatabaseName], + 'Big Diffs' AS [Finding], + 'On average, Differential backups for this database are >=40% of the size of the average Full backup.' AS [Warning] + FROM #Recoverability AS r + WHERE r.IsBigDiff = 1 - END; + INSERT #Warnings ( CheckId, Priority, DatabaseName, Finding, Warning ) + SELECT + 13 AS CheckId, + 100 AS Priority, + r.DatabaseName as [DatabaseName], + 'Big Logs' AS [Finding], + 'On average, Log backups for this database are >=20% of the size of the average Full backup.' AS [Warning] + FROM #Recoverability AS r + WHERE r.IsBigLog = 1 -/*End: checking default trace for odd DBCC activity*/ - - /*Begin check for autoshrink events*/ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 206 ) - AND @TraceFileIssue = 0 - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 206) WITH NOWAIT - - INSERT INTO [#BlitzResults] - ( [CheckID] , - [Priority] , - [FindingsGroup] , - [Finding] , - [URL] , - [Details] ) - - SELECT 206 AS CheckID , - 10 AS Priority , - 'Performance' AS FindingsGroup , - 'Auto-Shrink Ran Recently' AS Finding , - '' AS URL , - N'The database ' + QUOTENAME(t.DatabaseName) + N' has had ' - + CONVERT(NVARCHAR(10), COUNT(*)) - + N' auto shrink events between ' - + CONVERT(NVARCHAR(30), MIN(t.StartTime)) + ' and ' + CONVERT(NVARCHAR(30), MAX(t.StartTime)) - + ' that lasted on average ' - + CONVERT(NVARCHAR(10), AVG(DATEDIFF(SECOND, t.StartTime, t.EndTime))) - + ' seconds.' AS Details - FROM #fnTraceGettable AS t - WHERE t.EventClass IN (94, 95) - GROUP BY t.DatabaseName - HAVING AVG(DATEDIFF(SECOND, t.StartTime, t.EndTime)) > 5; - - END; - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 215 ) - AND @TraceFileIssue = 0 - AND EXISTS (SELECT * FROM sys.all_columns WHERE name = 'database_id' AND object_id = OBJECT_ID('sys.dm_exec_sessions')) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 215) WITH NOWAIT - - SET @StringToExecute = 'INSERT INTO [#BlitzResults] - ( [CheckID] , - [Priority] , - [FindingsGroup] , - [Finding] , - [DatabaseName] , - [URL] , - [Details] ) - - SELECT 215 AS CheckID , - 100 AS Priority , - ''Performance'' AS FindingsGroup , - ''Implicit Transactions'' AS Finding , - DB_NAME(s.database_id) AS DatabaseName, - ''https://www.brentozar.com/go/ImplicitTransactions/'' AS URL , - N''The database '' + - DB_NAME(s.database_id) - + '' has '' - + CONVERT(NVARCHAR(20), COUNT_BIG(*)) - + '' open implicit transactions with an oldest begin time of '' - + CONVERT(NVARCHAR(30), MIN(tat.transaction_begin_time)) - + '' Run sp_BlitzWho and check the is_implicit_transaction column to see the culprits.'' AS details - FROM sys.dm_tran_active_transactions AS tat - LEFT JOIN sys.dm_tran_session_transactions AS tst - ON tst.transaction_id = tat.transaction_id - LEFT JOIN sys.dm_exec_sessions AS s - ON s.session_id = tst.session_id - WHERE tat.name = ''implicit_transaction'' - GROUP BY DB_NAME(s.database_id), transaction_type, transaction_state;'; - - - IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; - IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; - - EXECUTE(@StringToExecute); +/*Insert thank you stuff last*/ + INSERT #Warnings ( CheckId, Priority, DatabaseName, Finding, Warning ) + SELECT + 2147483647 AS [CheckId], + 2147483647 AS [Priority], + 'From Your Community Volunteers' AS [DatabaseName], + 'sp_BlitzBackups Version: ' + @Version + ', Version Date: ' + CONVERT(VARCHAR(30), @VersionDate) + '.' AS [Finding], + 'Thanks for using our stored procedure. We hope you find it useful! Check out our other free SQL Server scripts at firstresponderkit.org!' AS [Warning]; - - END; +RAISERROR('Rules analysis finished', 0, 1) WITH NOWAIT; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 221 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 221) WITH NOWAIT; - - WITH reboot_airhorn - AS - ( - SELECT create_date - FROM sys.databases - WHERE database_id = 2 - UNION ALL - SELECT CAST(DATEADD(SECOND, ( ms_ticks / 1000 ) * ( -1 ), GETDATE()) AS DATETIME) - FROM sys.dm_os_sys_info - ) - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 221 AS CheckID, - 10 AS Priority, - 'Reliability' AS FindingsGroup, - 'Server restarted in last 24 hours' AS Finding, - '' AS URL, - 'Surprise! Your server was last restarted on: ' + CONVERT(VARCHAR(30), MAX(reboot_airhorn.create_date)) AS details - FROM reboot_airhorn - HAVING MAX(reboot_airhorn.create_date) >= DATEADD(HOUR, -24, GETDATE()); - +SELECT w.CheckId, w.Priority, w.DatabaseName, w.Finding, w.Warning +FROM #Warnings AS w +ORDER BY w.Priority, w.CheckId; - END; +DROP TABLE #Backups, #Warnings, #Recoverability, #RTORecoveryPoints - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 229 ) - AND CAST(SERVERPROPERTY('Edition') AS NVARCHAR(4000)) LIKE '%Evaluation%' - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 229) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 229 AS CheckID, - 1 AS Priority, - 'Reliability' AS FindingsGroup, - 'Evaluation Edition' AS Finding, - 'https://www.BrentOzar.com/go/workgroup' AS URL, - 'This server will stop working on: ' + CAST(CONVERT(DATETIME, DATEADD(DD, 180, create_date), 102) AS VARCHAR(100)) AS details - FROM sys.server_principals - WHERE sid = 0x010100000000000512000000; - - END; +RETURN; +PushBackupHistoryToListener: +RAISERROR('Pushing backup history to listener', 0, 1) WITH NOWAIT; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 233 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 233) WITH NOWAIT; - - - IF EXISTS (SELECT * FROM sys.all_columns WHERE object_id = OBJECT_ID('sys.dm_os_memory_clerks') AND name = 'pages_kb') - BEGIN - /* SQL 2012+ version */ - SET @StringToExecute = N' - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 233 AS CheckID, - 50 AS Priority, - ''Performance'' AS FindingsGroup, - ''Memory Leak in USERSTORE_TOKENPERM Cache'' AS Finding, - ''https://www.BrentOzar.com/go/userstore'' AS URL, - N''UserStore_TokenPerm clerk is using '' + CAST(CAST(SUM(CASE WHEN type = ''USERSTORE_TOKENPERM'' AND name = ''TokenAndPermUserStore'' THEN pages_kb * 1.0 ELSE 0.0 END) / 1024.0 / 1024.0 AS INT) AS NVARCHAR(100)) - + N''GB RAM, total buffer pool is '' + CAST(CAST(SUM(pages_kb) / 1024.0 / 1024.0 AS INT) AS NVARCHAR(100)) + N''GB.'' - AS details - FROM sys.dm_os_memory_clerks - HAVING SUM(CASE WHEN type = ''USERSTORE_TOKENPERM'' AND name = ''TokenAndPermUserStore'' THEN pages_kb * 1.0 ELSE 0.0 END) / SUM(pages_kb) >= 0.1 - AND SUM(pages_kb) / 1024.0 / 1024.0 >= 1; /* At least 1GB RAM overall */'; - EXEC sp_executesql @StringToExecute; - END - ELSE - BEGIN - /* Antiques Roadshow SQL 2008R2 - version */ - SET @StringToExecute = N' - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 233 AS CheckID, - 50 AS Priority, - ''Performance'' AS FindingsGroup, - ''Memory Leak in USERSTORE_TOKENPERM Cache'' AS Finding, - ''https://www.BrentOzar.com/go/userstore'' AS URL, - N''UserStore_TokenPerm clerk is using '' + CAST(CAST(SUM(CASE WHEN type = ''USERSTORE_TOKENPERM'' AND name = ''TokenAndPermUserStore'' THEN single_pages_kb + multi_pages_kb * 1.0 ELSE 0.0 END) / 1024.0 / 1024.0 AS INT) AS NVARCHAR(100)) - + N''GB RAM, total buffer pool is '' + CAST(CAST(SUM(single_pages_kb + multi_pages_kb) / 1024.0 / 1024.0 AS INT) AS NVARCHAR(100)) + N''GB.'' - AS details - FROM sys.dm_os_memory_clerks - HAVING SUM(CASE WHEN type = ''USERSTORE_TOKENPERM'' AND name = ''TokenAndPermUserStore'' THEN single_pages_kb + multi_pages_kb * 1.0 ELSE 0.0 END) / SUM(single_pages_kb + multi_pages_kb) >= 0.1 - AND SUM(single_pages_kb + multi_pages_kb) / 1024.0 / 1024.0 >= 1; /* At least 1GB RAM overall */'; - EXEC sp_executesql @StringToExecute; - END - - END; +DECLARE @msg NVARCHAR(4000) = N''; +DECLARE @RemoteCheck TABLE (c INT NULL); +IF @WriteBackupsToDatabaseName IS NULL + BEGIN + RAISERROR('@WriteBackupsToDatabaseName can''t be NULL.', 16, 1) WITH NOWAIT + RETURN; + END +IF LOWER(@WriteBackupsToDatabaseName) = N'msdb' + BEGIN + RAISERROR('We can''t write to the real msdb, we have to write to a fake msdb.', 16, 1) WITH NOWAIT + RETURN; + END +IF @WriteBackupsToListenerName IS NULL +BEGIN + IF @AGName IS NULL + BEGIN + RAISERROR('@WriteBackupsToListenerName and @AGName can''t both be NULL.', 16, 1) WITH NOWAIT; + RETURN; + END + ELSE + BEGIN + SELECT @WriteBackupsToListenerName = dns_name + FROM sys.availability_groups AS ag + JOIN sys.availability_group_listeners AS agl + ON ag.group_id = agl.group_id + WHERE name = @AGName; + END - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 234 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 234) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - DatabaseName , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 234 AS CheckID, - 100 AS Priority, - db_name(f.database_id) AS DatabaseName, - 'Reliability' AS FindingsGroup, - 'SQL Server Update May Fail' AS Finding, - 'https://desertdba.com/failovers-cant-serve-two-masters/' AS URL, - 'This database has a file with a logical name of ''master'', which can break SQL Server updates. Rename it in SSMS by right-clicking on the database, go into Properties, and rename the file. Takes effect instantly.' AS details - FROM master.sys.master_files f - WHERE (f.name = N'master') - AND f.database_id > 4 - AND db_name(f.database_id) <> 'master'; /* Thanks Michaels3 for catching this */ - END; +END +IF @WriteBackupsToListenerName IS NOT NULL +BEGIN + IF NOT EXISTS + ( + SELECT * + FROM sys.servers s + WHERE name = @WriteBackupsToListenerName + ) + BEGIN + SET @msg = N'We need a linked server to write data across. Please set one up for ' + @WriteBackupsToListenerName + N'.'; + RAISERROR(@msg, 16, 1) WITH NOWAIT; + RETURN; + END +END + SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; + SET @StringToExecute += N'SELECT TOP 1 1 FROM ' + + QUOTENAME(@WriteBackupsToListenerName) + N'.master.sys.databases d WHERE d.name = @i_WriteBackupsToDatabaseName;' - IF @CheckUserDatabaseObjects = 1 - BEGIN + IF @Debug = 1 + PRINT @StringToExecute; - IF @Debug IN (1, 2) RAISERROR('Starting @CheckUserDatabaseObjects section.', 0, 1) WITH NOWAIT - - /* - But what if you need to run a query in every individual database? - Check out CheckID 99 below. Yes, it uses sp_MSforeachdb, and no, - we're not happy about that. sp_MSforeachdb is known to have a lot - of issues, like skipping databases sometimes. However, this is the - only built-in option that we have. If you're writing your own code - for database maintenance, consider Aaron Bertrand's alternative: - http://www.mssqltips.com/sqlservertip/2201/making-a-more-reliable-and-flexible-spmsforeachdb/ - We don't include that as part of sp_Blitz, of course, because - copying and distributing copyrighted code from others without their - written permission isn't a good idea. - */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 99 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 99) WITH NOWAIT; - - EXEC dbo.sp_MSforeachdb 'USE [?]; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; IF EXISTS (SELECT * FROM sys.tables WITH (NOLOCK) WHERE name = ''sysmergepublications'' ) IF EXISTS ( SELECT * FROM sysmergepublications WITH (NOLOCK) WHERE retention = 0) INSERT INTO #BlitzResults (CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details) SELECT DISTINCT 99, DB_NAME(), 110, ''Performance'', ''Infinite merge replication metadata retention period'', ''https://www.brentozar.com/go/merge'', (''The ['' + DB_NAME() + ''] database has merge replication metadata retention period set to infinite - this can be the case of significant performance issues.'')'; - END; - /* - Note that by using sp_MSforeachdb, we're running the query in all - databases. We're not checking #SkipChecks here for each database to - see if we should run the check in this database. That means we may - still run a skipped check if it involves sp_MSforeachdb. We just - don't output those results in the last step. - */ - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 163 ) - AND EXISTS(SELECT * FROM sys.all_objects WHERE name = 'database_query_store_options') - BEGIN - /* --TOURSTOP03-- */ - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 163) WITH NOWAIT; - - EXEC dbo.sp_MSforeachdb 'USE [?]; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - INSERT INTO #BlitzResults - (CheckID, - DatabaseName, - Priority, - FindingsGroup, - Finding, - URL, - Details) - SELECT TOP 1 163, - N''?'', - 200, - ''Performance'', - ''Query Store Disabled'', - ''https://www.brentozar.com/go/querystore'', - (''The new SQL Server 2016 Query Store feature has not been enabled on this database.'') - FROM [?].sys.database_query_store_options WHERE desired_state = 0 - AND N''?'' NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''DWConfiguration'', ''DWDiagnostics'', ''DWQueue'', ''ReportServer'', ''ReportServerTempDB'') OPTION (RECOMPILE)'; - END; + INSERT @RemoteCheck (c) + EXEC sp_executesql @StringToExecute, N'@i_WriteBackupsToDatabaseName NVARCHAR(256)', @i_WriteBackupsToDatabaseName = @WriteBackupsToDatabaseName; - - IF @ProductVersionMajor = 13 AND @ProductVersionMinor < 2149 --2016 CU1 has the fix in it - AND NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 182 ) - AND CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) NOT LIKE '%Enterprise%' - AND CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) NOT LIKE '%Developer%' - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 182) WITH NOWAIT; - - SET @StringToExecute = 'INSERT INTO #BlitzResults - (CheckID, - DatabaseName, - Priority, - FindingsGroup, - Finding, - URL, - Details) - SELECT TOP 1 - 182, - ''Server'', - 20, - ''Reliability'', - ''Query Store Cleanup Disabled'', - ''https://www.brentozar.com/go/cleanup'', - (''SQL 2016 RTM has a bug involving dumps that happen every time Query Store cleanup jobs run. This is fixed in CU1 and later: https://sqlserverupdates.com/sql-server-2016-updates/'') - FROM sys.databases AS d - WHERE d.is_query_store_on = 1 OPTION (RECOMPILE);'; - - IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; - IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; - - EXECUTE(@StringToExecute); - END; + IF @@ROWCOUNT = 0 + BEGIN + SET @msg = N'The database ' + @WriteBackupsToDatabaseName + N' doesn''t appear to exist on that server.' + RAISERROR(@msg, 16, 1) WITH NOWAIT + RETURN; + END - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 235 ) - AND EXISTS(SELECT * FROM sys.all_objects WHERE name = 'database_query_store_options') - BEGIN - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 235) WITH NOWAIT; - - EXEC dbo.sp_MSforeachdb 'USE [?]; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - INSERT INTO #BlitzResults - (CheckID, - DatabaseName, - Priority, - FindingsGroup, - Finding, - URL, - Details) - SELECT TOP 1 235, - N''?'', - 150, - ''Performance'', - ''Inconsistent Query Store metadata'', - '''', - (''Query store state in master metadata and database specific metadata not in sync.'') - FROM [?].sys.database_query_store_options dqso - join master.sys.databases D on D.name = N''?'' - WHERE ((dqso.actual_state = 0 AND D.is_query_store_on = 1) OR (dqso.actual_state <> 0 AND D.is_query_store_on = 0)) - AND N''?'' NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''DWConfiguration'', ''DWDiagnostics'', ''DWQueue'', ''ReportServer'', ''ReportServerTempDB'') OPTION (RECOMPILE)'; - END; + SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 41 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 41) WITH NOWAIT; - - EXEC dbo.sp_MSforeachdb 'use [?]; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - INSERT INTO #BlitzResults - (CheckID, - DatabaseName, - Priority, - FindingsGroup, - Finding, - URL, - Details) - SELECT 41, - N''?'', - 170, - ''File Configuration'', - ''Multiple Log Files on One Drive'', - ''https://www.brentozar.com/go/manylogs'', - (''The ['' + DB_NAME() + ''] database has multiple log files on the '' + LEFT(physical_name, 1) + '' drive. This is not a performance booster because log file access is sequential, not parallel.'') - FROM [?].sys.database_files WHERE type_desc = ''LOG'' - AND N''?'' <> ''[tempdb]'' - GROUP BY LEFT(physical_name, 1) - HAVING COUNT(*) > 1 - AND SUM(size) < 268435456 OPTION (RECOMPILE);'; - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 42 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 42) WITH NOWAIT; - - EXEC dbo.sp_MSforeachdb 'use [?]; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - INSERT INTO #BlitzResults - (CheckID, - DatabaseName, - Priority, - FindingsGroup, - Finding, - URL, - Details) - SELECT DISTINCT 42, - N''?'', - 170, - ''File Configuration'', - ''Uneven File Growth Settings in One Filegroup'', - ''https://www.brentozar.com/go/grow'', - (''The ['' + DB_NAME() + ''] database has multiple data files in one filegroup, but they are not all set up to grow in identical amounts. This can lead to uneven file activity inside the filegroup.'') - FROM [?].sys.database_files - WHERE type_desc = ''ROWS'' - GROUP BY data_space_id - HAVING COUNT(DISTINCT growth) > 1 OR COUNT(DISTINCT is_percent_growth) > 1 OPTION (RECOMPILE);'; - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 82 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 82) WITH NOWAIT; - - EXEC sp_MSforeachdb 'use [?]; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - INSERT INTO #BlitzResults - (CheckID, - DatabaseName, - Priority, - FindingsGroup, - Finding, - URL, Details) - SELECT DISTINCT 82 AS CheckID, - N''?'' as DatabaseName, - 170 AS Priority, - ''File Configuration'' AS FindingsGroup, - ''File growth set to percent'', - ''https://www.brentozar.com/go/percentgrowth'' AS URL, - ''The ['' + DB_NAME() + ''] database file '' + f.physical_name + '' has grown to '' + CONVERT(NVARCHAR(10), CONVERT(NUMERIC(38, 2), (f.size / 128.) / 1024.)) + '' GB, and is using percent filegrowth settings. This can lead to slow performance during growths if Instant File Initialization is not enabled.'' - FROM [?].sys.database_files f - WHERE is_percent_growth = 1 and size > 128000 OPTION (RECOMPILE);'; - END; - - /* addition by Henrik Staun Poulsen, Stovi Software */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 158 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 158) WITH NOWAIT; - - EXEC sp_MSforeachdb 'use [?]; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - INSERT INTO #BlitzResults - (CheckID, - DatabaseName, - Priority, - FindingsGroup, - Finding, - URL, Details) - SELECT DISTINCT 158 AS CheckID, - N''?'' as DatabaseName, - 170 AS Priority, - ''File Configuration'' AS FindingsGroup, - ''File growth set to 1MB'', - ''https://www.brentozar.com/go/percentgrowth'' AS URL, - ''The ['' + DB_NAME() + ''] database file '' + f.physical_name + '' is using 1MB filegrowth settings, but it has grown to '' + CAST((CAST(f.size AS BIGINT) * 8 / 1000000) AS NVARCHAR(10)) + '' GB. Time to up the growth amount.'' - FROM [?].sys.database_files f - WHERE is_percent_growth = 0 and growth=128 and size > 128000 OPTION (RECOMPILE);'; - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 33 ) - BEGIN - IF @@VERSION NOT LIKE '%Microsoft SQL Server 2000%' - AND @@VERSION NOT LIKE '%Microsoft SQL Server 2005%' - AND @SkipBlockingChecks = 0 - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 33) WITH NOWAIT; - - EXEC dbo.sp_MSforeachdb 'USE [?]; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - INSERT INTO #BlitzResults - (CheckID, - DatabaseName, - Priority, - FindingsGroup, - Finding, - URL, - Details) - SELECT DISTINCT 33, - db_name(), - 200, - ''Licensing'', - ''Enterprise Edition Features In Use'', - ''https://www.brentozar.com/go/ee'', - (''The ['' + DB_NAME() + ''] database is using '' + feature_name + ''. If this database is restored onto a Standard Edition server, the restore will fail on versions prior to 2016 SP1.'') - FROM [?].sys.dm_db_persisted_sku_features OPTION (RECOMPILE);'; - END; - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 19 ) - BEGIN - /* Method 1: Check sys.databases parameters */ - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 19) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - - SELECT 19 AS CheckID , - [name] AS DatabaseName , - 200 AS Priority , - 'Informational' AS FindingsGroup , - 'Replication In Use' AS Finding , - 'https://www.brentozar.com/go/repl' AS URL , - ( 'Database [' + [name] - + '] is a replication publisher, subscriber, or distributor.' ) AS Details - FROM sys.databases - WHERE name NOT IN ( SELECT DISTINCT - DatabaseName - FROM #SkipChecks - WHERE CheckID IS NULL OR CheckID = 19) - AND (is_published = 1 - OR is_subscribed = 1 - OR is_merge_published = 1 - OR is_distributor = 1); - - /* Method B: check subscribers for MSreplication_objects tables */ - EXEC dbo.sp_MSforeachdb 'USE [?]; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - INSERT INTO #BlitzResults - (CheckID, - DatabaseName, - Priority, - FindingsGroup, - Finding, - URL, - Details) - SELECT DISTINCT 19, - db_name(), - 200, - ''Informational'', - ''Replication In Use'', - ''https://www.brentozar.com/go/repl'', - (''['' + DB_NAME() + ''] has MSreplication_objects tables in it, indicating it is a replication subscriber.'') - FROM [?].sys.tables - WHERE name = ''dbo.MSreplication_objects'' AND ''?'' <> ''master'' OPTION (RECOMPILE)'; - - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 32 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 32) WITH NOWAIT; - - EXEC dbo.sp_MSforeachdb 'USE [?]; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - INSERT INTO #BlitzResults - (CheckID, - DatabaseName, - Priority, - FindingsGroup, - Finding, - URL, - Details) - SELECT 32, - N''?'', - 150, - ''Performance'', - ''Triggers on Tables'', - ''https://www.brentozar.com/go/trig'', - (''The ['' + DB_NAME() + ''] database has '' + CAST(SUM(1) AS NVARCHAR(50)) + '' triggers.'') - FROM [?].sys.triggers t INNER JOIN [?].sys.objects o ON t.parent_id = o.object_id - INNER JOIN [?].sys.schemas s ON o.schema_id = s.schema_id WHERE t.is_ms_shipped = 0 AND DB_NAME() != ''ReportServer'' - HAVING SUM(1) > 0 OPTION (RECOMPILE)'; - END; + SET @StringToExecute += N'SELECT TOP 1 1 FROM ' + + QUOTENAME(@WriteBackupsToListenerName) + '.' + QUOTENAME(@WriteBackupsToDatabaseName) + '.sys.tables WHERE name = ''backupset'' AND SCHEMA_NAME(schema_id) = ''dbo''; + ' + @crlf; - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 164 ) - AND EXISTS(SELECT * FROM sys.all_objects WHERE name = 'fn_validate_plan_guide') - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 164) WITH NOWAIT; - - EXEC dbo.sp_MSforeachdb 'USE [?]; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SET QUOTED_IDENTIFIER ON; - INSERT INTO #BlitzResults - (CheckID, - DatabaseName, - Priority, - FindingsGroup, - Finding, - URL, - Details) - SELECT DISTINCT 164, - N''?'', - 100, - ''Reliability'', - ''Plan Guides Failing'', - ''https://www.brentozar.com/go/misguided'', - (''The ['' + DB_NAME() + ''] database has plan guides that are no longer valid, so the queries involved may be failing silently.'') - FROM [?].sys.plan_guides g CROSS APPLY fn_validate_plan_guide(g.plan_guide_id) OPTION (RECOMPILE)'; - END; + IF @Debug = 1 + PRINT @StringToExecute; - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 46 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 46) WITH NOWAIT; - - EXEC dbo.sp_MSforeachdb 'USE [?]; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - INSERT INTO #BlitzResults - (CheckID, - DatabaseName, - Priority, - FindingsGroup, - Finding, - URL, - Details) - SELECT 46, - N''?'', - 150, - ''Performance'', - ''Leftover Fake Indexes From Wizards'', - ''https://www.brentozar.com/go/hypo'', - (''The index ['' + DB_NAME() + ''].['' + s.name + ''].['' + o.name + ''].['' + i.name + ''] is a leftover hypothetical index from the Index Tuning Wizard or Database Tuning Advisor. This index is not actually helping performance and should be removed.'') - from [?].sys.indexes i INNER JOIN [?].sys.objects o ON i.object_id = o.object_id INNER JOIN [?].sys.schemas s ON o.schema_id = s.schema_id - WHERE i.is_hypothetical = 1 OPTION (RECOMPILE);'; - END; + INSERT @RemoteCheck (c) + EXEC sp_executesql @StringToExecute; - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 47 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 47) WITH NOWAIT; - - EXEC dbo.sp_MSforeachdb 'USE [?]; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - INSERT INTO #BlitzResults - (CheckID, - DatabaseName, - Priority, - FindingsGroup, - Finding, - URL, - Details) - SELECT 47, - N''?'', - 100, - ''Performance'', - ''Indexes Disabled'', - ''https://www.brentozar.com/go/ixoff'', - (''The index ['' + DB_NAME() + ''].['' + s.name + ''].['' + o.name + ''].['' + i.name + ''] is disabled. This index is not actually helping performance and should either be enabled or removed.'') - from [?].sys.indexes i INNER JOIN [?].sys.objects o ON i.object_id = o.object_id INNER JOIN [?].sys.schemas s ON o.schema_id = s.schema_id - WHERE i.is_disabled = 1 OPTION (RECOMPILE);'; - END; + IF @@ROWCOUNT = 0 + BEGIN - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 48 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 48) WITH NOWAIT; - - EXEC dbo.sp_MSforeachdb 'USE [?]; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - INSERT INTO #BlitzResults - (CheckID, - DatabaseName, - Priority, - FindingsGroup, - Finding, - URL, - Details) - SELECT DISTINCT 48, - N''?'', - 150, - ''Performance'', - ''Foreign Keys Not Trusted'', - ''https://www.brentozar.com/go/trust'', - (''The ['' + DB_NAME() + ''] database has foreign keys that were probably disabled, data was changed, and then the key was enabled again. Simply enabling the key is not enough for the optimizer to use this key - we have to alter the table using the WITH CHECK CHECK CONSTRAINT parameter.'') - from [?].sys.foreign_keys i INNER JOIN [?].sys.objects o ON i.parent_object_id = o.object_id INNER JOIN [?].sys.schemas s ON o.schema_id = s.schema_id - WHERE i.is_not_trusted = 1 AND i.is_not_for_replication = 0 AND i.is_disabled = 0 AND N''?'' NOT IN (''master'', ''model'', ''msdb'', ''ReportServer'', ''ReportServerTempDB'') OPTION (RECOMPILE);'; - END; + SET @msg = N'The database ' + @WriteBackupsToDatabaseName + N' doesn''t appear to have a table called dbo.backupset in it.' + RAISERROR(@msg, 0, 1) WITH NOWAIT + RAISERROR('Don''t worry, we''ll create it for you!', 0, 1) WITH NOWAIT + + SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 56 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 56) WITH NOWAIT; - - EXEC dbo.sp_MSforeachdb 'USE [?]; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - INSERT INTO #BlitzResults - (CheckID, - DatabaseName, - Priority, - FindingsGroup, - Finding, - URL, - Details) - SELECT 56, - N''?'', - 150, - ''Performance'', - ''Check Constraint Not Trusted'', - ''https://www.brentozar.com/go/trust'', - (''The check constraint ['' + DB_NAME() + ''].['' + s.name + ''].['' + o.name + ''].['' + i.name + ''] is not trusted - meaning, it was disabled, data was changed, and then the constraint was enabled again. Simply enabling the constraint is not enough for the optimizer to use this constraint - we have to alter the table using the WITH CHECK CHECK CONSTRAINT parameter.'') - from [?].sys.check_constraints i INNER JOIN [?].sys.objects o ON i.parent_object_id = o.object_id - INNER JOIN [?].sys.schemas s ON o.schema_id = s.schema_id - WHERE i.is_not_trusted = 1 AND i.is_not_for_replication = 0 AND i.is_disabled = 0 OPTION (RECOMPILE);'; - END; + SET @StringToExecute += N'CREATE TABLE ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.dbo.backupset + ( backup_set_id INT IDENTITY(1, 1), backup_set_uuid UNIQUEIDENTIFIER, media_set_id INT, first_family_number TINYINT, first_media_number SMALLINT, + last_family_number TINYINT, last_media_number SMALLINT, catalog_family_number TINYINT, catalog_media_number SMALLINT, position INT, expiration_date DATETIME, + software_vendor_id INT, name NVARCHAR(128), description NVARCHAR(255), user_name NVARCHAR(128), software_major_version TINYINT, software_minor_version TINYINT, + software_build_version SMALLINT, time_zone SMALLINT, mtf_minor_version TINYINT, first_lsn NUMERIC(25, 0), last_lsn NUMERIC(25, 0), checkpoint_lsn NUMERIC(25, 0), + database_backup_lsn NUMERIC(25, 0), database_creation_date DATETIME, backup_start_date DATETIME, backup_finish_date DATETIME, type CHAR(1), sort_order SMALLINT, + code_page SMALLINT, compatibility_level TINYINT, database_version INT, backup_size NUMERIC(20, 0), database_name NVARCHAR(128), server_name NVARCHAR(128), + machine_name NVARCHAR(128), flags INT, unicode_locale INT, unicode_compare_style INT, collation_name NVARCHAR(128), is_password_protected BIT, recovery_model NVARCHAR(60), + has_bulk_logged_data BIT, is_snapshot BIT, is_readonly BIT, is_single_user BIT, has_backup_checksums BIT, is_damaged BIT, begins_log_chain BIT, has_incomplete_metadata BIT, + is_force_offline BIT, is_copy_only BIT, first_recovery_fork_guid UNIQUEIDENTIFIER, last_recovery_fork_guid UNIQUEIDENTIFIER, fork_point_lsn NUMERIC(25, 0), database_guid UNIQUEIDENTIFIER, + family_guid UNIQUEIDENTIFIER, differential_base_lsn NUMERIC(25, 0), differential_base_guid UNIQUEIDENTIFIER, compressed_backup_size NUMERIC(20, 0), key_algorithm NVARCHAR(32), + encryptor_thumbprint VARBINARY(20) , encryptor_type NVARCHAR(32) + ); + ' + @crlf; + + SET @InnerStringToExecute = N'EXEC( ''' + @StringToExecute + ''' ) AT ' + QUOTENAME(@WriteBackupsToListenerName) + N';' + + IF @Debug = 1 + PRINT @InnerStringToExecute; + + EXEC sp_executesql @InnerStringToExecute - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 95 ) - BEGIN - IF @@VERSION NOT LIKE '%Microsoft SQL Server 2000%' - AND @@VERSION NOT LIKE '%Microsoft SQL Server 2005%' - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 95) WITH NOWAIT; - - EXEC dbo.sp_MSforeachdb 'USE [?]; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - INSERT INTO #BlitzResults - (CheckID, - DatabaseName, - Priority, - FindingsGroup, - Finding, - URL, - Details) - SELECT TOP 1 95 AS CheckID, - N''?'' as DatabaseName, - 110 AS Priority, - ''Performance'' AS FindingsGroup, - ''Plan Guides Enabled'' AS Finding, - ''https://www.brentozar.com/go/guides'' AS URL, - (''Database ['' + DB_NAME() + ''] has query plan guides so a query will always get a specific execution plan. If you are having trouble getting query performance to improve, it might be due to a frozen plan. Review the DMV sys.plan_guides to learn more about the plan guides in place on this server.'') AS Details - FROM [?].sys.plan_guides WHERE is_disabled = 0 OPTION (RECOMPILE);'; - END; - END; - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 60 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 60) WITH NOWAIT; - - EXEC sp_MSforeachdb 'USE [?]; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - INSERT INTO #BlitzResults - (CheckID, - DatabaseName, - Priority, - FindingsGroup, - Finding, - URL, - Details) - SELECT 60 AS CheckID, - N''?'' as DatabaseName, - 100 AS Priority, - ''Performance'' AS FindingsGroup, - ''Fill Factor Changed'', - ''https://www.brentozar.com/go/fillfactor'' AS URL, - ''The ['' + DB_NAME() + ''] database has '' + CAST(SUM(1) AS NVARCHAR(50)) + '' objects with fill factor = '' + CAST(fill_factor AS NVARCHAR(5)) + ''%. This can cause memory and storage performance problems, but may also prevent page splits.'' - FROM [?].sys.indexes - WHERE fill_factor <> 0 AND fill_factor < 80 AND is_disabled = 0 AND is_hypothetical = 0 - GROUP BY fill_factor OPTION (RECOMPILE);'; - END; + RAISERROR('We''ll even make the indexes!', 0, 1) WITH NOWAIT - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 78 ) - BEGIN + /*Checking for and creating the PK/CX*/ - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 78) WITH NOWAIT; - - EXECUTE master.sys.sp_MSforeachdb 'USE [?]; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - INSERT INTO #Recompile - SELECT DISTINCT DBName = DB_Name(), SPName = SO.name, SM.is_recompiled, ISR.SPECIFIC_SCHEMA - FROM sys.sql_modules AS SM - LEFT OUTER JOIN master.sys.databases AS sDB ON SM.object_id = DB_id() - LEFT OUTER JOIN dbo.sysobjects AS SO ON SM.object_id = SO.id and type = ''P'' - LEFT OUTER JOIN INFORMATION_SCHEMA.ROUTINES AS ISR on ISR.Routine_Name = SO.name AND ISR.SPECIFIC_CATALOG = DB_Name() - WHERE SM.is_recompiled=1 OPTION (RECOMPILE); /* oh the rich irony of recompile here */ - '; - INSERT INTO #BlitzResults - (Priority, - FindingsGroup, - Finding, - DatabaseName, - URL, - Details, - CheckID) - SELECT [Priority] = '100', - FindingsGroup = 'Performance', - Finding = 'Stored Procedure WITH RECOMPILE', - DatabaseName = DBName, - URL = 'https://www.brentozar.com/go/recompile', - Details = '[' + DBName + '].[' + SPSchema + '].[' + ProcName + '] has WITH RECOMPILE in the stored procedure code, which may cause increased CPU usage due to constant recompiles of the code.', - CheckID = '78' - FROM #Recompile AS TR - WHERE ProcName NOT LIKE 'sp_AllNightLog%' AND ProcName NOT LIKE 'sp_AskBrent%' AND ProcName NOT LIKE 'sp_Blitz%' AND ProcName NOT LIKE 'sp_PressureDetector' - AND DBName NOT IN ('master', 'model', 'msdb', 'tempdb'); - DROP TABLE #Recompile; - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 86 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 86) WITH NOWAIT; - - EXEC dbo.sp_MSforeachdb 'USE [?]; INSERT INTO #BlitzResults (CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details) SELECT DISTINCT 86, DB_NAME(), 230, ''Security'', ''Elevated Permissions on a Database'', ''https://www.brentozar.com/go/elevated'', (''In ['' + DB_NAME() + ''], user ['' + u.name + ''] has the role ['' + g.name + '']. This user can perform tasks beyond just reading and writing data.'') FROM (SELECT memberuid = convert(int, member_principal_id), groupuid = convert(int, role_principal_id) FROM [?].sys.database_role_members) m inner join [?].dbo.sysusers u on m.memberuid = u.uid inner join sysusers g on m.groupuid = g.uid where u.name <> ''dbo'' and g.name in (''db_owner'' , ''db_accessadmin'' , ''db_securityadmin'' , ''db_ddladmin'') OPTION (RECOMPILE);'; - END; + SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; + + SET @StringToExecute += N' + + IF NOT EXISTS ( + SELECT t.name, i.name + FROM ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.sys.tables AS t + JOIN ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.sys.indexes AS i + ON t.object_id = i.object_id + WHERE t.name = ? + AND i.name LIKE ? + ) - /*Check for non-aligned indexes in partioned databases*/ - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 72 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 72) WITH NOWAIT; - - EXEC dbo.sp_MSforeachdb 'USE [?]; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - insert into #partdb(dbname, objectname, type_desc) - SELECT distinct db_name(DB_ID()) as DBName,o.name Object_Name,ds.type_desc - FROM sys.objects AS o JOIN sys.indexes AS i ON o.object_id = i.object_id - JOIN sys.data_spaces ds on ds.data_space_id = i.data_space_id - LEFT OUTER JOIN sys.dm_db_index_usage_stats AS s ON i.object_id = s.object_id AND i.index_id = s.index_id AND s.database_id = DB_ID() - WHERE o.type = ''u'' - -- Clustered and Non-Clustered indexes - AND i.type IN (1, 2) - AND o.object_id in - ( - SELECT a.object_id from - (SELECT ob.object_id, ds.type_desc from sys.objects ob JOIN sys.indexes ind on ind.object_id = ob.object_id join sys.data_spaces ds on ds.data_space_id = ind.data_space_id - GROUP BY ob.object_id, ds.type_desc ) a group by a.object_id having COUNT (*) > 1 - ) OPTION (RECOMPILE);'; - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT DISTINCT - 72 AS CheckID , - dbname AS DatabaseName , - 100 AS Priority , - 'Performance' AS FindingsGroup , - 'The partitioned database ' + dbname - + ' may have non-aligned indexes' AS Finding , - 'https://www.brentozar.com/go/aligned' AS URL , - 'Having non-aligned indexes on partitioned tables may cause inefficient query plans and CPU pressure' AS Details - FROM #partdb - WHERE dbname IS NOT NULL - AND dbname NOT IN ( SELECT DISTINCT - DatabaseName - FROM #SkipChecks - WHERE CheckID IS NULL OR CheckID = 72); - DROP TABLE #partdb; - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 113 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 113) WITH NOWAIT; - - EXEC dbo.sp_MSforeachdb 'USE [?]; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - INSERT INTO #BlitzResults - (CheckID, - DatabaseName, - Priority, - FindingsGroup, - Finding, - URL, - Details) - SELECT DISTINCT 113, - N''?'', - 50, - ''Reliability'', - ''Full Text Indexes Not Updating'', - ''https://www.brentozar.com/go/fulltext'', - (''At least one full text index in this database has not been crawled in the last week.'') - from [?].sys.fulltext_indexes i WHERE change_tracking_state_desc <> ''AUTO'' AND i.is_enabled = 1 AND i.crawl_end_date < DATEADD(dd, -7, GETDATE()) OPTION (RECOMPILE);'; - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 115 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 115) WITH NOWAIT; - - EXEC dbo.sp_MSforeachdb 'USE [?]; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - INSERT INTO #BlitzResults - (CheckID, - DatabaseName, - Priority, - FindingsGroup, - Finding, - URL, - Details) - SELECT 115, - N''?'', - 110, - ''Performance'', - ''Parallelism Rocket Surgery'', - ''https://www.brentozar.com/go/makeparallel'', - (''['' + DB_NAME() + ''] has a make_parallel function, indicating that an advanced developer may be manhandling SQL Server into forcing queries to go parallel.'') - from [?].INFORMATION_SCHEMA.ROUTINES WHERE ROUTINE_NAME = ''make_parallel'' AND ROUTINE_TYPE = ''FUNCTION'' OPTION (RECOMPILE);'; - END; + BEGIN + ALTER TABLE ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.[dbo].[backupset] ADD PRIMARY KEY CLUSTERED ([backup_set_id] ASC) + END + ' - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 122 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 122) WITH NOWAIT; - - /* SQL Server 2012 and newer uses temporary stats for Availability Groups, and those show up as user-created */ - IF EXISTS (SELECT * - FROM sys.all_columns c - INNER JOIN sys.all_objects o ON c.object_id = o.object_id - WHERE c.name = 'is_temporary' AND o.name = 'stats') - - EXEC dbo.sp_MSforeachdb 'USE [?]; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - INSERT INTO #BlitzResults - (CheckID, - DatabaseName, - Priority, - FindingsGroup, - Finding, - URL, - Details) - SELECT TOP 1 122, - N''?'', - 200, - ''Performance'', - ''User-Created Statistics In Place'', - ''https://www.brentozar.com/go/userstats'', - (''['' + DB_NAME() + ''] has '' + CAST(SUM(1) AS NVARCHAR(10)) + '' user-created statistics. This indicates that someone is being a rocket scientist with the stats, and might actually be slowing things down, especially during stats updates.'') - from [?].sys.stats WHERE user_created = 1 AND is_temporary = 0 - HAVING SUM(1) > 0 OPTION (RECOMPILE);'; - - ELSE - EXEC dbo.sp_MSforeachdb 'USE [?]; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - INSERT INTO #BlitzResults - (CheckID, - DatabaseName, - Priority, - FindingsGroup, - Finding, - URL, - Details) - SELECT 122, - N''?'', - 200, - ''Performance'', - ''User-Created Statistics In Place'', - ''https://www.brentozar.com/go/userstats'', - (''['' + DB_NAME() + ''] has '' + CAST(SUM(1) AS NVARCHAR(10)) + '' user-created statistics. This indicates that someone is being a rocket scientist with the stats, and might actually be slowing things down, especially during stats updates.'') - from [?].sys.stats WHERE user_created = 1 - HAVING SUM(1) > 0 OPTION (RECOMPILE);'; - - END; /* IF NOT EXISTS ( SELECT 1 */ - - /*Check for high VLF count: this will omit any database snapshots*/ - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 69 ) - BEGIN - IF @ProductVersionMajor >= 11 - - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d] (2012 version of Log Info).', 0, 1, 69) WITH NOWAIT; - - EXEC sp_MSforeachdb N'USE [?]; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - INSERT INTO #LogInfo2012 - EXEC sp_executesql N''DBCC LogInfo() WITH NO_INFOMSGS''; - IF @@ROWCOUNT > 999 - BEGIN - INSERT INTO #BlitzResults - ( CheckID - ,DatabaseName - ,Priority - ,FindingsGroup - ,Finding - ,URL - ,Details) - SELECT 69 - ,DB_NAME() - ,170 - ,''File Configuration'' - ,''High VLF Count'' - ,''https://www.brentozar.com/go/vlf'' - ,''The ['' + DB_NAME() + ''] database has '' + CAST(COUNT(*) as VARCHAR(20)) + '' virtual log files (VLFs). This may be slowing down startup, restores, and even inserts/updates/deletes.'' - FROM #LogInfo2012 - WHERE EXISTS (SELECT name FROM master.sys.databases - WHERE source_database_id is null) OPTION (RECOMPILE); - END - TRUNCATE TABLE #LogInfo2012;'; - DROP TABLE #LogInfo2012; - END; - ELSE - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d] (pre-2012 version of Log Info).', 0, 1, 69) WITH NOWAIT; - - EXEC sp_MSforeachdb N'USE [?]; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - INSERT INTO #LogInfo - EXEC sp_executesql N''DBCC LogInfo() WITH NO_INFOMSGS''; - IF @@ROWCOUNT > 999 - BEGIN - INSERT INTO #BlitzResults - ( CheckID - ,DatabaseName - ,Priority - ,FindingsGroup - ,Finding - ,URL - ,Details) - SELECT 69 - ,DB_NAME() - ,170 - ,''File Configuration'' - ,''High VLF Count'' - ,''https://www.brentozar.com/go/vlf'' - ,''The ['' + DB_NAME() + ''] database has '' + CAST(COUNT(*) as VARCHAR(20)) + '' virtual log files (VLFs). This may be slowing down startup, restores, and even inserts/updates/deletes.'' - FROM #LogInfo - WHERE EXISTS (SELECT name FROM master.sys.databases - WHERE source_database_id is null) OPTION (RECOMPILE); - END - TRUNCATE TABLE #LogInfo;'; - DROP TABLE #LogInfo; - END; - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 80 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 80) WITH NOWAIT; - - EXEC dbo.sp_MSforeachdb 'USE [?]; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - INSERT INTO #BlitzResults (CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details) - SELECT DISTINCT 80, DB_NAME(), 170, ''Reliability'', ''Max File Size Set'', ''https://www.brentozar.com/go/maxsize'', - (''The ['' + DB_NAME() + ''] database file '' + df.name + '' has a max file size set to '' - + CAST(CAST(df.max_size AS BIGINT) * 8 / 1024 AS VARCHAR(100)) - + ''MB. If it runs out of space, the database will stop working even though there may be drive space available.'') - FROM sys.database_files df - WHERE 0 = (SELECT is_read_only FROM sys.databases WHERE name = ''?'') - AND df.max_size <> 268435456 - AND df.max_size <> -1 - AND df.type <> 2 - AND df.growth > 0 - AND df.name <> ''DWDiagnostics'' OPTION (RECOMPILE);'; - - DELETE br - FROM #BlitzResults br - INNER JOIN #SkipChecks sc ON sc.CheckID = 80 AND br.DatabaseName = sc.DatabaseName; - END; - + SET @InnerStringToExecute = N'EXEC( ''' + @StringToExecute + ''', ''backupset'', ''PK[_][_]%'' ) AT ' + QUOTENAME(@WriteBackupsToListenerName) + N';' - /* Check if columnstore indexes are in use - for Github issue #615 */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 74 ) /* Trace flags */ - BEGIN - TRUNCATE TABLE #TemporaryDatabaseResults; - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 74) WITH NOWAIT; - - EXEC dbo.sp_MSforeachdb 'USE [?]; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; IF EXISTS(SELECT * FROM sys.indexes WHERE type IN (5,6)) INSERT INTO #TemporaryDatabaseResults (DatabaseName, Finding) VALUES (DB_NAME(), ''Yup'') OPTION (RECOMPILE);'; - IF EXISTS (SELECT * FROM #TemporaryDatabaseResults) SET @ColumnStoreIndexesInUse = 1; - END; + IF @Debug = 1 + PRINT @InnerStringToExecute; + + EXEC sp_executesql @InnerStringToExecute - /* Non-Default Database Scoped Config - Github issue #598 */ - IF EXISTS ( SELECT * FROM sys.all_objects WHERE [name] = 'database_scoped_configurations' ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d] through [%d] and [%d] through [%d].', 0, 1, 194, 197, 237, 255) WITH NOWAIT; - - INSERT INTO #DatabaseScopedConfigurationDefaults (configuration_id, [name], default_value, default_value_for_secondary, CheckID) - SELECT 1, 'MAXDOP', '0', NULL, 194 - UNION ALL - SELECT 2, 'LEGACY_CARDINALITY_ESTIMATION', '0', NULL, 195 - UNION ALL - SELECT 3, 'PARAMETER_SNIFFING', '1', NULL, 196 - UNION ALL - SELECT 4, 'QUERY_OPTIMIZER_HOTFIXES', '0', NULL, 197 - UNION ALL - SELECT 6, 'IDENTITY_CACHE', '1', NULL, 237 - UNION ALL - SELECT 7, 'INTERLEAVED_EXECUTION_TVF', '1', NULL, 238 - UNION ALL - SELECT 8, 'BATCH_MODE_MEMORY_GRANT_FEEDBACK', '1', NULL, 239 - UNION ALL - SELECT 9, 'BATCH_MODE_ADAPTIVE_JOINS', '1', NULL, 240 - UNION ALL - SELECT 10, 'TSQL_SCALAR_UDF_INLINING', '1', NULL, 241 - UNION ALL - SELECT 11, 'ELEVATE_ONLINE', 'OFF', NULL, 242 - UNION ALL - SELECT 12, 'ELEVATE_RESUMABLE', 'OFF', NULL, 243 - UNION ALL - SELECT 13, 'OPTIMIZE_FOR_AD_HOC_WORKLOADS', '0', NULL, 244 - UNION ALL - SELECT 14, 'XTP_PROCEDURE_EXECUTION_STATISTICS', '0', NULL, 245 - UNION ALL - SELECT 15, 'XTP_QUERY_EXECUTION_STATISTICS', '0', NULL, 246 - UNION ALL - SELECT 16, 'ROW_MODE_MEMORY_GRANT_FEEDBACK', '1', NULL, 247 - UNION ALL - SELECT 17, 'ISOLATE_SECURITY_POLICY_CARDINALITY', '0', NULL, 248 - UNION ALL - SELECT 18, 'BATCH_MODE_ON_ROWSTORE', '1', NULL, 249 - UNION ALL - SELECT 19, 'DEFERRED_COMPILATION_TV', '1', NULL, 250 - UNION ALL - SELECT 20, 'ACCELERATED_PLAN_FORCING', '1', NULL, 251 - UNION ALL - SELECT 21, 'GLOBAL_TEMPORARY_TABLE_AUTO_DROP', '1', NULL, 252 - UNION ALL - SELECT 22, 'LIGHTWEIGHT_QUERY_PROFILING', '1', NULL, 253 - UNION ALL - SELECT 23, 'VERBOSE_TRUNCATION_WARNINGS', '1', NULL, 254 - UNION ALL - SELECT 24, 'LAST_QUERY_PLAN_STATS', '0', NULL, 255; - EXEC dbo.sp_MSforeachdb 'USE [?]; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; INSERT INTO #BlitzResults (CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details) - SELECT def1.CheckID, DB_NAME(), 210, ''Non-Default Database Scoped Config'', dsc.[name], ''https://www.brentozar.com/go/dbscope'', (''Set value: '' + COALESCE(CAST(dsc.value AS NVARCHAR(100)),''Empty'') + '' Default: '' + COALESCE(CAST(def1.default_value AS NVARCHAR(100)),''Empty'') + '' Set value for secondary: '' + COALESCE(CAST(dsc.value_for_secondary AS NVARCHAR(100)),''Empty'') + '' Default value for secondary: '' + COALESCE(CAST(def1.default_value_for_secondary AS NVARCHAR(100)),''Empty'')) - FROM [?].sys.database_scoped_configurations dsc - INNER JOIN #DatabaseScopedConfigurationDefaults def1 ON dsc.configuration_id = def1.configuration_id - LEFT OUTER JOIN #DatabaseScopedConfigurationDefaults def ON dsc.configuration_id = def.configuration_id AND (cast(dsc.value as nvarchar(100)) = cast(def.default_value as nvarchar(100)) OR dsc.value IS NULL) AND (dsc.value_for_secondary = def.default_value_for_secondary OR dsc.value_for_secondary IS NULL) - LEFT OUTER JOIN #SkipChecks sk ON (sk.CheckID IS NULL OR def.CheckID = sk.CheckID) AND (sk.DatabaseName IS NULL OR sk.DatabaseName = DB_NAME()) - WHERE def.configuration_id IS NULL AND sk.CheckID IS NULL ORDER BY 1 - OPTION (RECOMPILE);'; - END; - /* Check 218 - Show me the dodgy SET Options */ - IF NOT EXISTS ( - SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL - AND CheckID = 218 - ) - BEGIN - IF @Debug IN (1,2) - BEGIN - RAISERROR ('Running CheckId [%d].',0,1,218) WITH NOWAIT; - END - EXECUTE sp_MSforeachdb 'USE [?]; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - INSERT INTO #BlitzResults (CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details) - SELECT 218 AS CheckID - ,''?'' AS DatabaseName - ,150 AS Priority - ,''Performance'' AS FindingsGroup - ,''Objects created with dangerous SET Options'' AS Finding - ,''https://www.brentozar.com/go/badset'' AS URL - ,''The '' + QUOTENAME(DB_NAME()) - + '' database has '' + CONVERT(VARCHAR(20),COUNT(1)) - + '' objects that were created with dangerous ANSI_NULL or QUOTED_IDENTIFIER options.'' - + '' These objects can break when using filtered indexes, indexed views'' - + '' and other advanced SQL features.'' AS Details - FROM sys.sql_modules sm - JOIN sys.objects o ON o.[object_id] = sm.[object_id] - AND ( - sm.uses_ansi_nulls <> 1 - OR sm.uses_quoted_identifier <> 1 - ) - AND o.is_ms_shipped = 0 - HAVING COUNT(1) > 0;'; - END; --of Check 218. - - /* Check 225 - Reliability - Resumable Index Operation Paused */ - IF NOT EXISTS ( - SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL - AND CheckID = 225 - ) - AND EXISTS (SELECT * FROM sys.all_objects WHERE name = 'index_resumable_operations') - BEGIN - IF @Debug IN (1,2) - BEGIN - RAISERROR ('Running CheckId [%d].',0,1,218) WITH NOWAIT; - END + /*Checking for and creating index on backup_set_uuid*/ - EXECUTE sp_MSforeachdb 'USE [?]; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - INSERT INTO #BlitzResults (CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details) - SELECT 225 AS CheckID - ,''?'' AS DatabaseName - ,200 AS Priority - ,''Reliability'' AS FindingsGroup - ,''Resumable Index Operation Paused'' AS Finding - ,''https://www.brentozar.com/go/resumable'' AS URL - ,iro.state_desc + N'' since '' + CONVERT(NVARCHAR(50), last_pause_time, 120) + '', '' - + CAST(iro.percent_complete AS NVARCHAR(20)) + ''% complete: '' - + CAST(iro.sql_text AS NVARCHAR(1000)) AS Details - FROM sys.index_resumable_operations iro - JOIN sys.objects o ON iro.[object_id] = o.[object_id] - WHERE iro.state <> 0;'; - END; --of Check 225. - - --/* Check 220 - Statistics Without Histograms */ - --IF NOT EXISTS ( - -- SELECT 1 - -- FROM #SkipChecks - -- WHERE DatabaseName IS NULL - -- AND CheckID = 220 - -- ) - -- AND EXISTS (SELECT * FROM sys.all_objects WHERE name = 'dm_db_stats_histogram') - --BEGIN - -- IF @Debug IN (1,2) - -- BEGIN - -- RAISERROR ('Running CheckId [%d].',0,1,220) WITH NOWAIT; - -- END - - -- EXECUTE sp_MSforeachdb 'USE [?]; - -- SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - -- INSERT INTO #BlitzResults (CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details) - -- SELECT 220 AS CheckID - -- ,DB_NAME() AS DatabaseName - -- ,110 AS Priority - -- ,''Performance'' AS FindingsGroup - -- ,''Statistics Without Histograms'' AS Finding - -- ,''https://www.brentozar.com/go/brokenstats'' AS URL - -- ,CAST(COUNT(DISTINCT o.object_id) AS VARCHAR(100)) + '' tables have statistics that have not been updated since the database was restored or upgraded,'' - -- + '' and have no data in their histogram. See the More Info URL for a script to update them. '' AS Details - -- FROM sys.all_objects o - -- INNER JOIN sys.stats s ON o.object_id = s.object_id AND s.has_filter = 0 - -- OUTER APPLY sys.dm_db_stats_histogram(o.object_id, s.stats_id) h - -- WHERE o.is_ms_shipped = 0 AND o.type_desc = ''USER_TABLE'' - -- AND h.object_id IS NULL - -- AND 0 < (SELECT SUM(row_count) FROM sys.dm_db_partition_stats ps WHERE ps.object_id = o.object_id) - -- AND ''?'' NOT IN (''master'', ''model'', ''msdb'', ''tempdb'') - -- HAVING COUNT(DISTINCT o.object_id) > 0;'; - --END; --of Check 220. - - /*Check for the last good DBCC CHECKDB date */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 68 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 68) WITH NOWAIT; - - /* Removed as populating the #DBCCs table now done in advance as data is uses for multiple checks*/ - --EXEC sp_MSforeachdb N'USE [?]; - --SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - --INSERT #DBCCs - -- (ParentObject, - -- Object, - -- Field, - -- Value) - --EXEC (''DBCC DBInfo() With TableResults, NO_INFOMSGS''); - --UPDATE #DBCCs SET DbName = N''?'' WHERE DbName IS NULL OPTION (RECOMPILE);'; - - WITH DB2 - AS ( SELECT DISTINCT - Field , - Value , - DbName - FROM #DBCCs - INNER JOIN sys.databases d ON #DBCCs.DbName = d.name - WHERE Field = 'dbi_dbccLastKnownGood' - AND d.create_date < DATEADD(dd, -14, GETDATE()) - ) - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 68 AS CheckID , - DB2.DbName AS DatabaseName , - 1 AS PRIORITY , - 'Reliability' AS FindingsGroup , - 'Last good DBCC CHECKDB over 2 weeks old' AS Finding , - 'https://www.brentozar.com/go/checkdb' AS URL , - 'Last successful CHECKDB: ' - + CASE DB2.Value - WHEN '1900-01-01 00:00:00.000' - THEN ' never.' - ELSE DB2.Value - END AS Details - FROM DB2 - WHERE DB2.DbName <> 'tempdb' - AND DB2.DbName NOT IN ( SELECT DISTINCT - DatabaseName - FROM - #SkipChecks - WHERE CheckID IS NULL OR CheckID = 68) - AND DB2.DbName NOT IN ( SELECT name - FROM sys.databases - WHERE is_read_only = 1) - AND CONVERT(DATETIME, DB2.Value, 121) < DATEADD(DD, - -14, - CURRENT_TIMESTAMP); - END; + SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; + + SET @StringToExecute += N'IF NOT EXISTS ( + SELECT t.name, i.name + FROM ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.sys.tables AS t + JOIN ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.sys.indexes AS i + ON t.object_id = i.object_id + WHERE t.name = ? + AND i.name = ? + ) - END; /* IF @CheckUserDatabaseObjects = 1 */ + BEGIN + CREATE NONCLUSTERED INDEX [backupsetuuid] ON ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.[dbo].[backupset] ([backup_set_uuid] ASC) + END + ' - IF @CheckProcedureCache = 1 - - BEGIN + SET @InnerStringToExecute = N'EXEC( ''' + @StringToExecute + ''', ''backupset'', ''backupsetuuid'' ) AT ' + QUOTENAME(@WriteBackupsToListenerName) + N';' + + IF @Debug = 1 + PRINT @InnerStringToExecute; + + EXEC sp_executesql @InnerStringToExecute + + + + /*Checking for and creating index on media_set_id*/ - IF @Debug IN (1, 2) RAISERROR('Begin checking procedure cache', 0, 1) WITH NOWAIT; - - BEGIN + SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; + + SET @StringToExecute += 'IF NOT EXISTS ( + SELECT t.name, i.name + FROM ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.sys.tables AS t + JOIN ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.sys.indexes AS i + ON t.object_id = i.object_id + WHERE t.name = ? + AND i.name = ? + ) - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 35 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 35) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 35 AS CheckID , - 100 AS Priority , - 'Performance' AS FindingsGroup , - 'Single-Use Plans in Procedure Cache' AS Finding , - 'https://www.brentozar.com/go/single' AS URL , - ( CAST(COUNT(*) AS VARCHAR(10)) - + ' query plans are taking up memory in the procedure cache. This may be wasted memory if we cache plans for queries that never get called again. This may be a good use case for SQL Server 2008''s Optimize for Ad Hoc or for Forced Parameterization.' ) AS Details - FROM sys.dm_exec_cached_plans AS cp - WHERE cp.usecounts = 1 - AND cp.objtype = 'Adhoc' - AND EXISTS ( SELECT - 1 - FROM sys.configurations - WHERE - name = 'optimize for ad hoc workloads' - AND value_in_use = 0 ) - HAVING COUNT(*) > 1; - END; + BEGIN + CREATE NONCLUSTERED INDEX [backupsetMediaSetId] ON ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.[dbo].[backupset] ([media_set_id] ASC) + END + ' - /* Set up the cache tables. Different on 2005 since it doesn't support query_hash, query_plan_hash. */ - IF @@VERSION LIKE '%Microsoft SQL Server 2005%' - BEGIN - IF @CheckProcedureCacheFilter = 'CPU' - OR @CheckProcedureCacheFilter IS NULL - BEGIN - SET @StringToExecute = 'WITH queries ([sql_handle],[statement_start_offset],[statement_end_offset],[plan_generation_num],[plan_handle],[creation_time],[last_execution_time],[execution_count],[total_worker_time],[last_worker_time],[min_worker_time],[max_worker_time],[total_physical_reads],[last_physical_reads],[min_physical_reads],[max_physical_reads],[total_logical_writes],[last_logical_writes],[min_logical_writes],[max_logical_writes],[total_logical_reads],[last_logical_reads],[min_logical_reads],[max_logical_reads],[total_clr_time],[last_clr_time],[min_clr_time],[max_clr_time],[total_elapsed_time],[last_elapsed_time],[min_elapsed_time],[max_elapsed_time]) - AS (SELECT TOP 20 qs.[sql_handle],qs.[statement_start_offset],qs.[statement_end_offset],qs.[plan_generation_num],qs.[plan_handle],qs.[creation_time],qs.[last_execution_time],qs.[execution_count],qs.[total_worker_time],qs.[last_worker_time],qs.[min_worker_time],qs.[max_worker_time],qs.[total_physical_reads],qs.[last_physical_reads],qs.[min_physical_reads],qs.[max_physical_reads],qs.[total_logical_writes],qs.[last_logical_writes],qs.[min_logical_writes],qs.[max_logical_writes],qs.[total_logical_reads],qs.[last_logical_reads],qs.[min_logical_reads],qs.[max_logical_reads],qs.[total_clr_time],qs.[last_clr_time],qs.[min_clr_time],qs.[max_clr_time],qs.[total_elapsed_time],qs.[last_elapsed_time],qs.[min_elapsed_time],qs.[max_elapsed_time] - FROM sys.dm_exec_query_stats qs - ORDER BY qs.total_worker_time DESC) - INSERT INTO #dm_exec_query_stats ([sql_handle],[statement_start_offset],[statement_end_offset],[plan_generation_num],[plan_handle],[creation_time],[last_execution_time],[execution_count],[total_worker_time],[last_worker_time],[min_worker_time],[max_worker_time],[total_physical_reads],[last_physical_reads],[min_physical_reads],[max_physical_reads],[total_logical_writes],[last_logical_writes],[min_logical_writes],[max_logical_writes],[total_logical_reads],[last_logical_reads],[min_logical_reads],[max_logical_reads],[total_clr_time],[last_clr_time],[min_clr_time],[max_clr_time],[total_elapsed_time],[last_elapsed_time],[min_elapsed_time],[max_elapsed_time]) - SELECT qs.[sql_handle],qs.[statement_start_offset],qs.[statement_end_offset],qs.[plan_generation_num],qs.[plan_handle],qs.[creation_time],qs.[last_execution_time],qs.[execution_count],qs.[total_worker_time],qs.[last_worker_time],qs.[min_worker_time],qs.[max_worker_time],qs.[total_physical_reads],qs.[last_physical_reads],qs.[min_physical_reads],qs.[max_physical_reads],qs.[total_logical_writes],qs.[last_logical_writes],qs.[min_logical_writes],qs.[max_logical_writes],qs.[total_logical_reads],qs.[last_logical_reads],qs.[min_logical_reads],qs.[max_logical_reads],qs.[total_clr_time],qs.[last_clr_time],qs.[min_clr_time],qs.[max_clr_time],qs.[total_elapsed_time],qs.[last_elapsed_time],qs.[min_elapsed_time],qs.[max_elapsed_time] - FROM queries qs - LEFT OUTER JOIN #dm_exec_query_stats qsCaught ON qs.sql_handle = qsCaught.sql_handle AND qs.plan_handle = qsCaught.plan_handle AND qs.statement_start_offset = qsCaught.statement_start_offset - WHERE qsCaught.sql_handle IS NULL OPTION (RECOMPILE);'; - EXECUTE(@StringToExecute); - END; + SET @InnerStringToExecute = N'EXEC( ''' + @StringToExecute + ''', ''backupset'', ''backupsetMediaSetId'' ) AT ' + QUOTENAME(@WriteBackupsToListenerName) + N';' + + IF @Debug = 1 + PRINT @InnerStringToExecute; + + EXEC sp_executesql @InnerStringToExecute + + + + /*Checking for and creating index on backup_finish_date*/ - IF @CheckProcedureCacheFilter = 'Reads' - OR @CheckProcedureCacheFilter IS NULL - BEGIN - SET @StringToExecute = 'WITH queries ([sql_handle],[statement_start_offset],[statement_end_offset],[plan_generation_num],[plan_handle],[creation_time],[last_execution_time],[execution_count],[total_worker_time],[last_worker_time],[min_worker_time],[max_worker_time],[total_physical_reads],[last_physical_reads],[min_physical_reads],[max_physical_reads],[total_logical_writes],[last_logical_writes],[min_logical_writes],[max_logical_writes],[total_logical_reads],[last_logical_reads],[min_logical_reads],[max_logical_reads],[total_clr_time],[last_clr_time],[min_clr_time],[max_clr_time],[total_elapsed_time],[last_elapsed_time],[min_elapsed_time],[max_elapsed_time]) - AS (SELECT TOP 20 qs.[sql_handle],qs.[statement_start_offset],qs.[statement_end_offset],qs.[plan_generation_num],qs.[plan_handle],qs.[creation_time],qs.[last_execution_time],qs.[execution_count],qs.[total_worker_time],qs.[last_worker_time],qs.[min_worker_time],qs.[max_worker_time],qs.[total_physical_reads],qs.[last_physical_reads],qs.[min_physical_reads],qs.[max_physical_reads],qs.[total_logical_writes],qs.[last_logical_writes],qs.[min_logical_writes],qs.[max_logical_writes],qs.[total_logical_reads],qs.[last_logical_reads],qs.[min_logical_reads],qs.[max_logical_reads],qs.[total_clr_time],qs.[last_clr_time],qs.[min_clr_time],qs.[max_clr_time],qs.[total_elapsed_time],qs.[last_elapsed_time],qs.[min_elapsed_time],qs.[max_elapsed_time] - FROM sys.dm_exec_query_stats qs - ORDER BY qs.total_logical_reads DESC) - INSERT INTO #dm_exec_query_stats ([sql_handle],[statement_start_offset],[statement_end_offset],[plan_generation_num],[plan_handle],[creation_time],[last_execution_time],[execution_count],[total_worker_time],[last_worker_time],[min_worker_time],[max_worker_time],[total_physical_reads],[last_physical_reads],[min_physical_reads],[max_physical_reads],[total_logical_writes],[last_logical_writes],[min_logical_writes],[max_logical_writes],[total_logical_reads],[last_logical_reads],[min_logical_reads],[max_logical_reads],[total_clr_time],[last_clr_time],[min_clr_time],[max_clr_time],[total_elapsed_time],[last_elapsed_time],[min_elapsed_time],[max_elapsed_time]) - SELECT qs.[sql_handle],qs.[statement_start_offset],qs.[statement_end_offset],qs.[plan_generation_num],qs.[plan_handle],qs.[creation_time],qs.[last_execution_time],qs.[execution_count],qs.[total_worker_time],qs.[last_worker_time],qs.[min_worker_time],qs.[max_worker_time],qs.[total_physical_reads],qs.[last_physical_reads],qs.[min_physical_reads],qs.[max_physical_reads],qs.[total_logical_writes],qs.[last_logical_writes],qs.[min_logical_writes],qs.[max_logical_writes],qs.[total_logical_reads],qs.[last_logical_reads],qs.[min_logical_reads],qs.[max_logical_reads],qs.[total_clr_time],qs.[last_clr_time],qs.[min_clr_time],qs.[max_clr_time],qs.[total_elapsed_time],qs.[last_elapsed_time],qs.[min_elapsed_time],qs.[max_elapsed_time] - FROM queries qs - LEFT OUTER JOIN #dm_exec_query_stats qsCaught ON qs.sql_handle = qsCaught.sql_handle AND qs.plan_handle = qsCaught.plan_handle AND qs.statement_start_offset = qsCaught.statement_start_offset - WHERE qsCaught.sql_handle IS NULL OPTION (RECOMPILE);'; - EXECUTE(@StringToExecute); - END; + SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; + + SET @StringToExecute += N'IF NOT EXISTS ( + SELECT t.name, i.name + FROM ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.sys.tables AS t + JOIN ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.sys.indexes AS i + ON t.object_id = i.object_id + WHERE t.name = ? + AND i.name = ? + ) - IF @CheckProcedureCacheFilter = 'ExecCount' - OR @CheckProcedureCacheFilter IS NULL - BEGIN - SET @StringToExecute = 'WITH queries ([sql_handle],[statement_start_offset],[statement_end_offset],[plan_generation_num],[plan_handle],[creation_time],[last_execution_time],[execution_count],[total_worker_time],[last_worker_time],[min_worker_time],[max_worker_time],[total_physical_reads],[last_physical_reads],[min_physical_reads],[max_physical_reads],[total_logical_writes],[last_logical_writes],[min_logical_writes],[max_logical_writes],[total_logical_reads],[last_logical_reads],[min_logical_reads],[max_logical_reads],[total_clr_time],[last_clr_time],[min_clr_time],[max_clr_time],[total_elapsed_time],[last_elapsed_time],[min_elapsed_time],[max_elapsed_time]) - AS (SELECT TOP 20 qs.[sql_handle],qs.[statement_start_offset],qs.[statement_end_offset],qs.[plan_generation_num],qs.[plan_handle],qs.[creation_time],qs.[last_execution_time],qs.[execution_count],qs.[total_worker_time],qs.[last_worker_time],qs.[min_worker_time],qs.[max_worker_time],qs.[total_physical_reads],qs.[last_physical_reads],qs.[min_physical_reads],qs.[max_physical_reads],qs.[total_logical_writes],qs.[last_logical_writes],qs.[min_logical_writes],qs.[max_logical_writes],qs.[total_logical_reads],qs.[last_logical_reads],qs.[min_logical_reads],qs.[max_logical_reads],qs.[total_clr_time],qs.[last_clr_time],qs.[min_clr_time],qs.[max_clr_time],qs.[total_elapsed_time],qs.[last_elapsed_time],qs.[min_elapsed_time],qs.[max_elapsed_time] - FROM sys.dm_exec_query_stats qs - ORDER BY qs.execution_count DESC) - INSERT INTO #dm_exec_query_stats ([sql_handle],[statement_start_offset],[statement_end_offset],[plan_generation_num],[plan_handle],[creation_time],[last_execution_time],[execution_count],[total_worker_time],[last_worker_time],[min_worker_time],[max_worker_time],[total_physical_reads],[last_physical_reads],[min_physical_reads],[max_physical_reads],[total_logical_writes],[last_logical_writes],[min_logical_writes],[max_logical_writes],[total_logical_reads],[last_logical_reads],[min_logical_reads],[max_logical_reads],[total_clr_time],[last_clr_time],[min_clr_time],[max_clr_time],[total_elapsed_time],[last_elapsed_time],[min_elapsed_time],[max_elapsed_time]) - SELECT qs.[sql_handle],qs.[statement_start_offset],qs.[statement_end_offset],qs.[plan_generation_num],qs.[plan_handle],qs.[creation_time],qs.[last_execution_time],qs.[execution_count],qs.[total_worker_time],qs.[last_worker_time],qs.[min_worker_time],qs.[max_worker_time],qs.[total_physical_reads],qs.[last_physical_reads],qs.[min_physical_reads],qs.[max_physical_reads],qs.[total_logical_writes],qs.[last_logical_writes],qs.[min_logical_writes],qs.[max_logical_writes],qs.[total_logical_reads],qs.[last_logical_reads],qs.[min_logical_reads],qs.[max_logical_reads],qs.[total_clr_time],qs.[last_clr_time],qs.[min_clr_time],qs.[max_clr_time],qs.[total_elapsed_time],qs.[last_elapsed_time],qs.[min_elapsed_time],qs.[max_elapsed_time] - FROM queries qs - LEFT OUTER JOIN #dm_exec_query_stats qsCaught ON qs.sql_handle = qsCaught.sql_handle AND qs.plan_handle = qsCaught.plan_handle AND qs.statement_start_offset = qsCaught.statement_start_offset - WHERE qsCaught.sql_handle IS NULL OPTION (RECOMPILE);'; - EXECUTE(@StringToExecute); - END; + BEGIN + CREATE NONCLUSTERED INDEX [backupsetDate] ON ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.[dbo].[backupset] ([backup_finish_date] ASC) + END + ' - IF @CheckProcedureCacheFilter = 'Duration' - OR @CheckProcedureCacheFilter IS NULL - BEGIN - SET @StringToExecute = 'WITH queries ([sql_handle],[statement_start_offset],[statement_end_offset],[plan_generation_num],[plan_handle],[creation_time],[last_execution_time],[execution_count],[total_worker_time],[last_worker_time],[min_worker_time],[max_worker_time],[total_physical_reads],[last_physical_reads],[min_physical_reads],[max_physical_reads],[total_logical_writes],[last_logical_writes],[min_logical_writes],[max_logical_writes],[total_logical_reads],[last_logical_reads],[min_logical_reads],[max_logical_reads],[total_clr_time],[last_clr_time],[min_clr_time],[max_clr_time],[total_elapsed_time],[last_elapsed_time],[min_elapsed_time],[max_elapsed_time]) - AS (SELECT TOP 20 qs.[sql_handle],qs.[statement_start_offset],qs.[statement_end_offset],qs.[plan_generation_num],qs.[plan_handle],qs.[creation_time],qs.[last_execution_time],qs.[execution_count],qs.[total_worker_time],qs.[last_worker_time],qs.[min_worker_time],qs.[max_worker_time],qs.[total_physical_reads],qs.[last_physical_reads],qs.[min_physical_reads],qs.[max_physical_reads],qs.[total_logical_writes],qs.[last_logical_writes],qs.[min_logical_writes],qs.[max_logical_writes],qs.[total_logical_reads],qs.[last_logical_reads],qs.[min_logical_reads],qs.[max_logical_reads],qs.[total_clr_time],qs.[last_clr_time],qs.[min_clr_time],qs.[max_clr_time],qs.[total_elapsed_time],qs.[last_elapsed_time],qs.[min_elapsed_time],qs.[max_elapsed_time] - FROM sys.dm_exec_query_stats qs - ORDER BY qs.total_elapsed_time DESC) - INSERT INTO #dm_exec_query_stats ([sql_handle],[statement_start_offset],[statement_end_offset],[plan_generation_num],[plan_handle],[creation_time],[last_execution_time],[execution_count],[total_worker_time],[last_worker_time],[min_worker_time],[max_worker_time],[total_physical_reads],[last_physical_reads],[min_physical_reads],[max_physical_reads],[total_logical_writes],[last_logical_writes],[min_logical_writes],[max_logical_writes],[total_logical_reads],[last_logical_reads],[min_logical_reads],[max_logical_reads],[total_clr_time],[last_clr_time],[min_clr_time],[max_clr_time],[total_elapsed_time],[last_elapsed_time],[min_elapsed_time],[max_elapsed_time]) - SELECT qs.[sql_handle],qs.[statement_start_offset],qs.[statement_end_offset],qs.[plan_generation_num],qs.[plan_handle],qs.[creation_time],qs.[last_execution_time],qs.[execution_count],qs.[total_worker_time],qs.[last_worker_time],qs.[min_worker_time],qs.[max_worker_time],qs.[total_physical_reads],qs.[last_physical_reads],qs.[min_physical_reads],qs.[max_physical_reads],qs.[total_logical_writes],qs.[last_logical_writes],qs.[min_logical_writes],qs.[max_logical_writes],qs.[total_logical_reads],qs.[last_logical_reads],qs.[min_logical_reads],qs.[max_logical_reads],qs.[total_clr_time],qs.[last_clr_time],qs.[min_clr_time],qs.[max_clr_time],qs.[total_elapsed_time],qs.[last_elapsed_time],qs.[min_elapsed_time],qs.[max_elapsed_time] - FROM queries qs - LEFT OUTER JOIN #dm_exec_query_stats qsCaught ON qs.sql_handle = qsCaught.sql_handle AND qs.plan_handle = qsCaught.plan_handle AND qs.statement_start_offset = qsCaught.statement_start_offset - WHERE qsCaught.sql_handle IS NULL OPTION (RECOMPILE);'; - EXECUTE(@StringToExecute); - END; + SET @InnerStringToExecute = N'EXEC( ''' + @StringToExecute + ''', ''backupset'', ''backupsetDate'' ) AT ' + QUOTENAME(@WriteBackupsToListenerName) + N';' + + IF @Debug = 1 + PRINT @InnerStringToExecute; + + EXEC sp_executesql @InnerStringToExecute - END; - IF @ProductVersionMajor >= 10 - BEGIN - IF @CheckProcedureCacheFilter = 'CPU' - OR @CheckProcedureCacheFilter IS NULL - BEGIN - SET @StringToExecute = 'WITH queries ([sql_handle],[statement_start_offset],[statement_end_offset],[plan_generation_num],[plan_handle],[creation_time],[last_execution_time],[execution_count],[total_worker_time],[last_worker_time],[min_worker_time],[max_worker_time],[total_physical_reads],[last_physical_reads],[min_physical_reads],[max_physical_reads],[total_logical_writes],[last_logical_writes],[min_logical_writes],[max_logical_writes],[total_logical_reads],[last_logical_reads],[min_logical_reads],[max_logical_reads],[total_clr_time],[last_clr_time],[min_clr_time],[max_clr_time],[total_elapsed_time],[last_elapsed_time],[min_elapsed_time],[max_elapsed_time],[query_hash],[query_plan_hash]) - AS (SELECT TOP 20 qs.[sql_handle],qs.[statement_start_offset],qs.[statement_end_offset],qs.[plan_generation_num],qs.[plan_handle],qs.[creation_time],qs.[last_execution_time],qs.[execution_count],qs.[total_worker_time],qs.[last_worker_time],qs.[min_worker_time],qs.[max_worker_time],qs.[total_physical_reads],qs.[last_physical_reads],qs.[min_physical_reads],qs.[max_physical_reads],qs.[total_logical_writes],qs.[last_logical_writes],qs.[min_logical_writes],qs.[max_logical_writes],qs.[total_logical_reads],qs.[last_logical_reads],qs.[min_logical_reads],qs.[max_logical_reads],qs.[total_clr_time],qs.[last_clr_time],qs.[min_clr_time],qs.[max_clr_time],qs.[total_elapsed_time],qs.[last_elapsed_time],qs.[min_elapsed_time],qs.[max_elapsed_time],qs.[query_hash],qs.[query_plan_hash] - FROM sys.dm_exec_query_stats qs - ORDER BY qs.total_worker_time DESC) - INSERT INTO #dm_exec_query_stats ([sql_handle],[statement_start_offset],[statement_end_offset],[plan_generation_num],[plan_handle],[creation_time],[last_execution_time],[execution_count],[total_worker_time],[last_worker_time],[min_worker_time],[max_worker_time],[total_physical_reads],[last_physical_reads],[min_physical_reads],[max_physical_reads],[total_logical_writes],[last_logical_writes],[min_logical_writes],[max_logical_writes],[total_logical_reads],[last_logical_reads],[min_logical_reads],[max_logical_reads],[total_clr_time],[last_clr_time],[min_clr_time],[max_clr_time],[total_elapsed_time],[last_elapsed_time],[min_elapsed_time],[max_elapsed_time],[query_hash],[query_plan_hash]) - SELECT qs.[sql_handle],qs.[statement_start_offset],qs.[statement_end_offset],qs.[plan_generation_num],qs.[plan_handle],qs.[creation_time],qs.[last_execution_time],qs.[execution_count],qs.[total_worker_time],qs.[last_worker_time],qs.[min_worker_time],qs.[max_worker_time],qs.[total_physical_reads],qs.[last_physical_reads],qs.[min_physical_reads],qs.[max_physical_reads],qs.[total_logical_writes],qs.[last_logical_writes],qs.[min_logical_writes],qs.[max_logical_writes],qs.[total_logical_reads],qs.[last_logical_reads],qs.[min_logical_reads],qs.[max_logical_reads],qs.[total_clr_time],qs.[last_clr_time],qs.[min_clr_time],qs.[max_clr_time],qs.[total_elapsed_time],qs.[last_elapsed_time],qs.[min_elapsed_time],qs.[max_elapsed_time],qs.[query_hash],qs.[query_plan_hash] - FROM queries qs - LEFT OUTER JOIN #dm_exec_query_stats qsCaught ON qs.sql_handle = qsCaught.sql_handle AND qs.plan_handle = qsCaught.plan_handle AND qs.statement_start_offset = qsCaught.statement_start_offset - WHERE qsCaught.sql_handle IS NULL OPTION (RECOMPILE);'; - EXECUTE(@StringToExecute); - END; - IF @CheckProcedureCacheFilter = 'Reads' - OR @CheckProcedureCacheFilter IS NULL - BEGIN - SET @StringToExecute = 'WITH queries ([sql_handle],[statement_start_offset],[statement_end_offset],[plan_generation_num],[plan_handle],[creation_time],[last_execution_time],[execution_count],[total_worker_time],[last_worker_time],[min_worker_time],[max_worker_time],[total_physical_reads],[last_physical_reads],[min_physical_reads],[max_physical_reads],[total_logical_writes],[last_logical_writes],[min_logical_writes],[max_logical_writes],[total_logical_reads],[last_logical_reads],[min_logical_reads],[max_logical_reads],[total_clr_time],[last_clr_time],[min_clr_time],[max_clr_time],[total_elapsed_time],[last_elapsed_time],[min_elapsed_time],[max_elapsed_time],[query_hash],[query_plan_hash]) - AS (SELECT TOP 20 qs.[sql_handle],qs.[statement_start_offset],qs.[statement_end_offset],qs.[plan_generation_num],qs.[plan_handle],qs.[creation_time],qs.[last_execution_time],qs.[execution_count],qs.[total_worker_time],qs.[last_worker_time],qs.[min_worker_time],qs.[max_worker_time],qs.[total_physical_reads],qs.[last_physical_reads],qs.[min_physical_reads],qs.[max_physical_reads],qs.[total_logical_writes],qs.[last_logical_writes],qs.[min_logical_writes],qs.[max_logical_writes],qs.[total_logical_reads],qs.[last_logical_reads],qs.[min_logical_reads],qs.[max_logical_reads],qs.[total_clr_time],qs.[last_clr_time],qs.[min_clr_time],qs.[max_clr_time],qs.[total_elapsed_time],qs.[last_elapsed_time],qs.[min_elapsed_time],qs.[max_elapsed_time],qs.[query_hash],qs.[query_plan_hash] - FROM sys.dm_exec_query_stats qs - ORDER BY qs.total_logical_reads DESC) - INSERT INTO #dm_exec_query_stats ([sql_handle],[statement_start_offset],[statement_end_offset],[plan_generation_num],[plan_handle],[creation_time],[last_execution_time],[execution_count],[total_worker_time],[last_worker_time],[min_worker_time],[max_worker_time],[total_physical_reads],[last_physical_reads],[min_physical_reads],[max_physical_reads],[total_logical_writes],[last_logical_writes],[min_logical_writes],[max_logical_writes],[total_logical_reads],[last_logical_reads],[min_logical_reads],[max_logical_reads],[total_clr_time],[last_clr_time],[min_clr_time],[max_clr_time],[total_elapsed_time],[last_elapsed_time],[min_elapsed_time],[max_elapsed_time],[query_hash],[query_plan_hash]) - SELECT qs.[sql_handle],qs.[statement_start_offset],qs.[statement_end_offset],qs.[plan_generation_num],qs.[plan_handle],qs.[creation_time],qs.[last_execution_time],qs.[execution_count],qs.[total_worker_time],qs.[last_worker_time],qs.[min_worker_time],qs.[max_worker_time],qs.[total_physical_reads],qs.[last_physical_reads],qs.[min_physical_reads],qs.[max_physical_reads],qs.[total_logical_writes],qs.[last_logical_writes],qs.[min_logical_writes],qs.[max_logical_writes],qs.[total_logical_reads],qs.[last_logical_reads],qs.[min_logical_reads],qs.[max_logical_reads],qs.[total_clr_time],qs.[last_clr_time],qs.[min_clr_time],qs.[max_clr_time],qs.[total_elapsed_time],qs.[last_elapsed_time],qs.[min_elapsed_time],qs.[max_elapsed_time],qs.[query_hash],qs.[query_plan_hash] - FROM queries qs - LEFT OUTER JOIN #dm_exec_query_stats qsCaught ON qs.sql_handle = qsCaught.sql_handle AND qs.plan_handle = qsCaught.plan_handle AND qs.statement_start_offset = qsCaught.statement_start_offset - WHERE qsCaught.sql_handle IS NULL OPTION (RECOMPILE);'; - EXECUTE(@StringToExecute); - END; + + /*Checking for and creating index on database_name*/ - IF @CheckProcedureCacheFilter = 'ExecCount' - OR @CheckProcedureCacheFilter IS NULL - BEGIN - SET @StringToExecute = 'WITH queries ([sql_handle],[statement_start_offset],[statement_end_offset],[plan_generation_num],[plan_handle],[creation_time],[last_execution_time],[execution_count],[total_worker_time],[last_worker_time],[min_worker_time],[max_worker_time],[total_physical_reads],[last_physical_reads],[min_physical_reads],[max_physical_reads],[total_logical_writes],[last_logical_writes],[min_logical_writes],[max_logical_writes],[total_logical_reads],[last_logical_reads],[min_logical_reads],[max_logical_reads],[total_clr_time],[last_clr_time],[min_clr_time],[max_clr_time],[total_elapsed_time],[last_elapsed_time],[min_elapsed_time],[max_elapsed_time],[query_hash],[query_plan_hash]) - AS (SELECT TOP 20 qs.[sql_handle],qs.[statement_start_offset],qs.[statement_end_offset],qs.[plan_generation_num],qs.[plan_handle],qs.[creation_time],qs.[last_execution_time],qs.[execution_count],qs.[total_worker_time],qs.[last_worker_time],qs.[min_worker_time],qs.[max_worker_time],qs.[total_physical_reads],qs.[last_physical_reads],qs.[min_physical_reads],qs.[max_physical_reads],qs.[total_logical_writes],qs.[last_logical_writes],qs.[min_logical_writes],qs.[max_logical_writes],qs.[total_logical_reads],qs.[last_logical_reads],qs.[min_logical_reads],qs.[max_logical_reads],qs.[total_clr_time],qs.[last_clr_time],qs.[min_clr_time],qs.[max_clr_time],qs.[total_elapsed_time],qs.[last_elapsed_time],qs.[min_elapsed_time],qs.[max_elapsed_time],qs.[query_hash],qs.[query_plan_hash] - FROM sys.dm_exec_query_stats qs - ORDER BY qs.execution_count DESC) - INSERT INTO #dm_exec_query_stats ([sql_handle],[statement_start_offset],[statement_end_offset],[plan_generation_num],[plan_handle],[creation_time],[last_execution_time],[execution_count],[total_worker_time],[last_worker_time],[min_worker_time],[max_worker_time],[total_physical_reads],[last_physical_reads],[min_physical_reads],[max_physical_reads],[total_logical_writes],[last_logical_writes],[min_logical_writes],[max_logical_writes],[total_logical_reads],[last_logical_reads],[min_logical_reads],[max_logical_reads],[total_clr_time],[last_clr_time],[min_clr_time],[max_clr_time],[total_elapsed_time],[last_elapsed_time],[min_elapsed_time],[max_elapsed_time],[query_hash],[query_plan_hash]) - SELECT qs.[sql_handle],qs.[statement_start_offset],qs.[statement_end_offset],qs.[plan_generation_num],qs.[plan_handle],qs.[creation_time],qs.[last_execution_time],qs.[execution_count],qs.[total_worker_time],qs.[last_worker_time],qs.[min_worker_time],qs.[max_worker_time],qs.[total_physical_reads],qs.[last_physical_reads],qs.[min_physical_reads],qs.[max_physical_reads],qs.[total_logical_writes],qs.[last_logical_writes],qs.[min_logical_writes],qs.[max_logical_writes],qs.[total_logical_reads],qs.[last_logical_reads],qs.[min_logical_reads],qs.[max_logical_reads],qs.[total_clr_time],qs.[last_clr_time],qs.[min_clr_time],qs.[max_clr_time],qs.[total_elapsed_time],qs.[last_elapsed_time],qs.[min_elapsed_time],qs.[max_elapsed_time],qs.[query_hash],qs.[query_plan_hash] - FROM queries qs - LEFT OUTER JOIN #dm_exec_query_stats qsCaught ON qs.sql_handle = qsCaught.sql_handle AND qs.plan_handle = qsCaught.plan_handle AND qs.statement_start_offset = qsCaught.statement_start_offset - WHERE qsCaught.sql_handle IS NULL OPTION (RECOMPILE);'; - EXECUTE(@StringToExecute); - END; + SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; + + SET @StringToExecute += N'IF NOT EXISTS ( + SELECT t.name, i.name + FROM ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.sys.tables AS t + JOIN ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.sys.indexes AS i + ON t.object_id = i.object_id + WHERE t.name = ? + AND i.name = ? + ) - IF @CheckProcedureCacheFilter = 'Duration' - OR @CheckProcedureCacheFilter IS NULL - BEGIN - SET @StringToExecute = 'WITH queries ([sql_handle],[statement_start_offset],[statement_end_offset],[plan_generation_num],[plan_handle],[creation_time],[last_execution_time],[execution_count],[total_worker_time],[last_worker_time],[min_worker_time],[max_worker_time],[total_physical_reads],[last_physical_reads],[min_physical_reads],[max_physical_reads],[total_logical_writes],[last_logical_writes],[min_logical_writes],[max_logical_writes],[total_logical_reads],[last_logical_reads],[min_logical_reads],[max_logical_reads],[total_clr_time],[last_clr_time],[min_clr_time],[max_clr_time],[total_elapsed_time],[last_elapsed_time],[min_elapsed_time],[max_elapsed_time],[query_hash],[query_plan_hash]) - AS (SELECT TOP 20 qs.[sql_handle],qs.[statement_start_offset],qs.[statement_end_offset],qs.[plan_generation_num],qs.[plan_handle],qs.[creation_time],qs.[last_execution_time],qs.[execution_count],qs.[total_worker_time],qs.[last_worker_time],qs.[min_worker_time],qs.[max_worker_time],qs.[total_physical_reads],qs.[last_physical_reads],qs.[min_physical_reads],qs.[max_physical_reads],qs.[total_logical_writes],qs.[last_logical_writes],qs.[min_logical_writes],qs.[max_logical_writes],qs.[total_logical_reads],qs.[last_logical_reads],qs.[min_logical_reads],qs.[max_logical_reads],qs.[total_clr_time],qs.[last_clr_time],qs.[min_clr_time],qs.[max_clr_time],qs.[total_elapsed_time],qs.[last_elapsed_time],qs.[min_elapsed_time],qs.[max_elapsed_time],qs.[query_hash],qs.[query_plan_hash] - FROM sys.dm_exec_query_stats qs - ORDER BY qs.total_elapsed_time DESC) - INSERT INTO #dm_exec_query_stats ([sql_handle],[statement_start_offset],[statement_end_offset],[plan_generation_num],[plan_handle],[creation_time],[last_execution_time],[execution_count],[total_worker_time],[last_worker_time],[min_worker_time],[max_worker_time],[total_physical_reads],[last_physical_reads],[min_physical_reads],[max_physical_reads],[total_logical_writes],[last_logical_writes],[min_logical_writes],[max_logical_writes],[total_logical_reads],[last_logical_reads],[min_logical_reads],[max_logical_reads],[total_clr_time],[last_clr_time],[min_clr_time],[max_clr_time],[total_elapsed_time],[last_elapsed_time],[min_elapsed_time],[max_elapsed_time],[query_hash],[query_plan_hash]) - SELECT qs.[sql_handle],qs.[statement_start_offset],qs.[statement_end_offset],qs.[plan_generation_num],qs.[plan_handle],qs.[creation_time],qs.[last_execution_time],qs.[execution_count],qs.[total_worker_time],qs.[last_worker_time],qs.[min_worker_time],qs.[max_worker_time],qs.[total_physical_reads],qs.[last_physical_reads],qs.[min_physical_reads],qs.[max_physical_reads],qs.[total_logical_writes],qs.[last_logical_writes],qs.[min_logical_writes],qs.[max_logical_writes],qs.[total_logical_reads],qs.[last_logical_reads],qs.[min_logical_reads],qs.[max_logical_reads],qs.[total_clr_time],qs.[last_clr_time],qs.[min_clr_time],qs.[max_clr_time],qs.[total_elapsed_time],qs.[last_elapsed_time],qs.[min_elapsed_time],qs.[max_elapsed_time],qs.[query_hash],qs.[query_plan_hash] - FROM queries qs - LEFT OUTER JOIN #dm_exec_query_stats qsCaught ON qs.sql_handle = qsCaught.sql_handle AND qs.plan_handle = qsCaught.plan_handle AND qs.statement_start_offset = qsCaught.statement_start_offset - WHERE qsCaught.sql_handle IS NULL OPTION (RECOMPILE);'; - EXECUTE(@StringToExecute); - END; + BEGIN + CREATE NONCLUSTERED INDEX [backupsetDatabaseName] ON ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.[dbo].[backupset] ([database_name] ASC ) INCLUDE ([backup_set_id], [media_set_id]) + END - /* Populate the query_plan_filtered field. Only works in 2005SP2+, but we're just doing it in 2008 to be safe. */ - UPDATE #dm_exec_query_stats - SET query_plan_filtered = qp.query_plan - FROM #dm_exec_query_stats qs - CROSS APPLY sys.dm_exec_text_query_plan(qs.plan_handle, - qs.statement_start_offset, - qs.statement_end_offset) - AS qp; + ' - END; + SET @InnerStringToExecute = N'EXEC( ''' + @StringToExecute + ''', ''backupset'', ''backupsetDatabaseName'' ) AT ' + QUOTENAME(@WriteBackupsToListenerName) + N';' + + IF @Debug = 1 + PRINT @InnerStringToExecute; + + EXEC sp_executesql @InnerStringToExecute - /* Populate the additional query_plan, text, and text_filtered fields */ - UPDATE #dm_exec_query_stats - SET query_plan = qp.query_plan , - [text] = st.[text] , - text_filtered = SUBSTRING(st.text, - ( qs.statement_start_offset - / 2 ) + 1, - ( ( CASE qs.statement_end_offset - WHEN -1 - THEN DATALENGTH(st.text) - ELSE qs.statement_end_offset - END - - qs.statement_start_offset ) - / 2 ) + 1) - FROM #dm_exec_query_stats qs - CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) AS st - CROSS APPLY sys.dm_exec_query_plan(qs.plan_handle) - AS qp; - - /* Dump instances of our own script. We're not trying to tune ourselves. */ - DELETE #dm_exec_query_stats - WHERE text LIKE '%sp_Blitz%' - OR text LIKE '%#BlitzResults%'; - - /* Look for implicit conversions */ - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 63 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 63) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details , - QueryPlan , - QueryPlanFiltered - ) - SELECT 63 AS CheckID , - 120 AS Priority , - 'Query Plans' AS FindingsGroup , - 'Implicit Conversion' AS Finding , - 'https://www.brentozar.com/go/implicit' AS URL , - ( 'One of the top resource-intensive queries is comparing two fields that are not the same datatype.' ) AS Details , - qs.query_plan , - qs.query_plan_filtered - FROM #dm_exec_query_stats qs - WHERE COALESCE(qs.query_plan_filtered, - CAST(qs.query_plan AS NVARCHAR(MAX))) LIKE '%CONVERT_IMPLICIT%' - AND COALESCE(qs.query_plan_filtered, - CAST(qs.query_plan AS NVARCHAR(MAX))) LIKE '%PhysicalOp="Index Scan"%'; - END; + RAISERROR('Table and indexes created! You''re welcome!', 0, 1) WITH NOWAIT + END - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 64 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 64) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details , - QueryPlan , - QueryPlanFiltered - ) - SELECT 64 AS CheckID , - 120 AS Priority , - 'Query Plans' AS FindingsGroup , - 'Implicit Conversion Affecting Cardinality' AS Finding , - 'https://www.brentozar.com/go/implicit' AS URL , - ( 'One of the top resource-intensive queries has an implicit conversion that is affecting cardinality estimation.' ) AS Details , - qs.query_plan , - qs.query_plan_filtered - FROM #dm_exec_query_stats qs - WHERE COALESCE(qs.query_plan_filtered, - CAST(qs.query_plan AS NVARCHAR(MAX))) LIKE '%= 10 - AND NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 187 ) - - IF SERVERPROPERTY('IsHadrEnabled') = 1 - BEGIN + SELECT @StartDate = MIN(b.backup_start_date) + FROM msdb.dbo.backupset b + WHERE b.backup_start_date >= @StartDate + AND NOT EXISTS ( + SELECT 1 + FROM ' + QUOTENAME(@WriteBackupsToListenerName) + N'.' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.dbo.backupset b2 + WHERE b.backup_set_uuid = b2.backup_set_uuid + AND b2.backup_start_date >= @StartDate + ) - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 187) WITH NOWAIT; - - INSERT INTO [#BlitzResults] - ( [CheckID] , - [Priority] , - [FindingsGroup] , - [Finding] , - [URL] , - [Details] ) - SELECT - 187 AS [CheckID] , - 230 AS [Priority] , - 'Security' AS [FindingsGroup] , - 'Endpoints Owned by Users' AS [Finding] , - 'https://www.brentozar.com/go/owners' AS [URL] , - ( 'Endpoint ' + ep.[name] + ' is owned by ' + SUSER_NAME(ep.principal_id) + '. If the endpoint owner login is disabled or not available due to Active Directory problems, the high availability will stop working.' - ) AS [Details] - FROM sys.database_mirroring_endpoints ep - LEFT OUTER JOIN sys.dm_server_services s ON SUSER_NAME(ep.principal_id) = s.service_account - WHERE s.service_account IS NULL AND ep.principal_id <> 1; - END; - - - /*Verify that the servername is set */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 70 ) - BEGIN - IF @@SERVERNAME IS NULL - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 70) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 70 AS CheckID , - 200 AS Priority , - 'Informational' AS FindingsGroup , - '@@Servername Not Set' AS Finding , - 'https://www.brentozar.com/go/servername' AS URL , - '@@Servername variable is null. You can fix it by executing: "sp_addserver '''', local"' AS Details; - END; + SET @StartDateNext = DATEADD(MINUTE, 10, @StartDate); - IF /* @@SERVERNAME IS set */ - (@@SERVERNAME IS NOT NULL - AND - /* not a named instance */ - CHARINDEX(CHAR(92),CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128))) = 0 - AND - /* not clustered, when computername may be different than the servername */ - SERVERPROPERTY('IsClustered') = 0 - AND - /* @@SERVERNAME is different than the computer name */ - @@SERVERNAME <> CAST(ISNULL(SERVERPROPERTY('ComputerNamePhysicalNetBIOS'),@@SERVERNAME) AS NVARCHAR(128)) ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 70) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 70 AS CheckID , - 200 AS Priority , - 'Configuration' AS FindingsGroup , - '@@Servername Not Correct' AS Finding , - 'https://www.brentozar.com/go/servername' AS URL , - 'The @@Servername is different than the computer name, which may trigger certificate errors.' AS Details; - END; + IF + ( @StartDate IS NULL ) + BEGIN + SET @msg = N''No data to move, exiting.'' + RAISERROR(@msg, 0, 1) WITH NOWAIT - END; - /*Check to see if a failsafe operator has been configured*/ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 73 ) - BEGIN + RETURN; + END - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 73) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 73 AS CheckID , - 200 AS Priority , - 'Monitoring' AS FindingsGroup , - 'No Failsafe Operator Configured' AS Finding , - 'https://www.brentozar.com/go/failsafe' AS URL , - ( 'No failsafe operator is configured on this server. This is a good idea just in-case there are issues with the [msdb] database that prevents alerting.' ) AS Details - FROM #AlertInfo - WHERE FailSafeOperator IS NULL; - END; + RAISERROR(''Starting insert loop'', 0, 1) WITH NOWAIT; - /*Identify globally enabled trace flags*/ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 74 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 74) WITH NOWAIT; - - INSERT INTO #TraceStatus - EXEC ( ' DBCC TRACESTATUS(-1) WITH NO_INFOMSGS' - ); - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 74 AS CheckID , - 200 AS Priority , - 'Informational' AS FindingsGroup , - 'Trace Flag On' AS Finding , - CASE WHEN [T].[TraceFlag] = '834' AND @ColumnStoreIndexesInUse = 1 THEN 'https://support.microsoft.com/en-us/kb/3210239' - ELSE'https://www.BrentOzar.com/go/traceflags/' END AS URL , - 'Trace flag ' + - CASE WHEN [T].[TraceFlag] = '652' THEN '652 enabled globally, which disables pre-fetching during index scans. This is usually a very bad idea.' - WHEN [T].[TraceFlag] = '661' THEN '661 enabled globally, which disables ghost record removal, causing the database to grow in size. This is usually a very bad idea.' - WHEN [T].[TraceFlag] = '834' AND @ColumnStoreIndexesInUse = 1 THEN '834 is enabled globally, but you also have columnstore indexes. That combination is not recommended by Microsoft.' - WHEN [T].[TraceFlag] = '1117' THEN '1117 enabled globally, which grows all files in a filegroup at the same time.' - WHEN [T].[TraceFlag] = '1118' THEN '1118 enabled globally, which tries to reduce SGAM waits.' - WHEN [T].[TraceFlag] = '1211' THEN '1211 enabled globally, which disables lock escalation when you least expect it. This is usually a very bad idea.' - WHEN [T].[TraceFlag] = '1204' THEN '1204 enabled globally, which captures deadlock graphs in the error log.' - WHEN [T].[TraceFlag] = '1222' THEN '1222 enabled globally, which captures deadlock graphs in the error log.' - WHEN [T].[TraceFlag] = '1224' THEN '1224 enabled globally, which disables lock escalation until the server has memory pressure. This is usually a very bad idea.' - WHEN [T].[TraceFlag] = '1806' THEN '1806 enabled globally, which disables Instant File Initialization, causing restores and file growths to take longer. This is usually a very bad idea.' - WHEN [T].[TraceFlag] = '2330' THEN '2330 enabled globally, which disables missing index requests. This is usually a very bad idea.' - WHEN [T].[TraceFlag] = '2371' THEN '2371 enabled globally, which changes the auto update stats threshold.' - WHEN [T].[TraceFlag] = '3023' THEN '3023 enabled globally, which performs checksums by default when doing database backups.' - WHEN [T].[TraceFlag] = '3226' THEN '3226 enabled globally, which keeps the event log clean by not reporting successful backups.' - WHEN [T].[TraceFlag] = '3505' THEN '3505 enabled globally, which disables Checkpoints. This is usually a very bad idea.' - WHEN [T].[TraceFlag] = '4199' THEN '4199 enabled globally, which enables non-default Query Optimizer fixes, changing query plans from the default behaviors.' - WHEN [T].[TraceFlag] = '8048' THEN '8048 enabled globally, which tries to reduce CMEMTHREAD waits on servers with a lot of logical processors.' - WHEN [T].[TraceFlag] = '8017' AND (CAST(SERVERPROPERTY('Edition') AS NVARCHAR(1000)) LIKE N'%Express%') THEN '8017 is enabled globally, but this is the default for Express Edition.' - WHEN [T].[TraceFlag] = '8017' AND (CAST(SERVERPROPERTY('Edition') AS NVARCHAR(1000)) NOT LIKE N'%Express%') THEN '8017 is enabled globally, which disables the creation of schedulers for all logical processors.' - WHEN [T].[TraceFlag] = '8649' THEN '8649 enabled globally, which cost threshold for parallelism down to 0. This is usually a very bad idea.' - ELSE [T].[TraceFlag] + ' is enabled globally.' END - AS Details - FROM #TraceStatus T; - END; + WHILE EXISTS ( + SELECT 1 + FROM msdb.dbo.backupset b + WHERE NOT EXISTS ( + SELECT 1 + FROM ' + QUOTENAME(@WriteBackupsToListenerName) + N'.' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.dbo.backupset b2 + WHERE b.backup_set_uuid = b2.backup_set_uuid + AND b2.backup_start_date >= @StartDate + ) + ) + BEGIN + + SET @msg = N''Inserting data for '' + CONVERT(NVARCHAR(30), @StartDate) + '' through '' + + CONVERT(NVARCHAR(30), @StartDateNext) + ''.'' + RAISERROR(@msg, 0, 1) WITH NOWAIT + + ' - /* High CMEMTHREAD waits that could need trace flag 8048. - This check has to be run AFTER the globally enabled trace flag check, - since it uses the #TraceStatus table to know if flags are enabled. - */ - IF @ProductVersionMajor >= 11 AND NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 162 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 162) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 162 AS CheckID , - 50 AS Priority , - 'Performance' AS FindingGroup , - 'Poison Wait Detected: CMEMTHREAD & NUMA' AS Finding , - 'https://www.brentozar.com/go/poison' AS URL , - CONVERT(VARCHAR(10), (MAX([wait_time_ms]) / 1000) / 86400) + ':' + CONVERT(VARCHAR(20), DATEADD(s, (MAX([wait_time_ms]) / 1000), 0), 108) + ' of this wait have been recorded' - + CASE WHEN ts.status = 1 THEN ' despite enabling trace flag 8048 already.' - ELSE '. In servers with over 8 cores per NUMA node, when CMEMTHREAD waits are a bottleneck, trace flag 8048 may be needed.' - END - FROM sys.dm_os_nodes n - INNER JOIN sys.[dm_os_wait_stats] w ON w.wait_type = 'CMEMTHREAD' - LEFT OUTER JOIN #TraceStatus ts ON ts.TraceFlag = 8048 AND ts.status = 1 - WHERE n.node_id = 0 AND n.online_scheduler_count >= 8 - AND EXISTS (SELECT * FROM sys.dm_os_nodes WHERE node_id > 0 AND node_state_desc NOT LIKE '%DAC') - GROUP BY w.wait_type, ts.status - HAVING SUM([wait_time_ms]) > (SELECT 5000 * datediff(HH,create_date,CURRENT_TIMESTAMP) AS hours_since_startup FROM sys.databases WHERE name='tempdb') - AND SUM([wait_time_ms]) > 60000; - END; + SET @StringToExecute += N'INSERT ' + QUOTENAME(@WriteBackupsToListenerName) + N'.' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.dbo.backupset + ' + SET @StringToExecute += N' (database_name, database_guid, backup_set_uuid, type, backup_size, backup_start_date, backup_finish_date, media_set_id, time_zone, + compressed_backup_size, recovery_model, server_name, machine_name, first_lsn, last_lsn, user_name, compatibility_level, + is_password_protected, is_snapshot, is_readonly, is_single_user, has_backup_checksums, is_damaged, ' + CASE WHEN @ProductVersionMajor >= 12 + THEN + N'encryptor_type, has_bulk_logged_data)' + @crlf + ELSE + N'has_bulk_logged_data)' + @crlf + END + + SET @StringToExecute +=N' + SELECT database_name, database_guid, backup_set_uuid, type, backup_size, backup_start_date, backup_finish_date, media_set_id, time_zone, + compressed_backup_size, recovery_model, server_name, machine_name, first_lsn, last_lsn, user_name, compatibility_level, + is_password_protected, is_snapshot, is_readonly, is_single_user, has_backup_checksums, is_damaged, ' + CASE WHEN @ProductVersionMajor >= 12 + THEN + N'encryptor_type, has_bulk_logged_data' + @crlf + ELSE + N'has_bulk_logged_data' + @crlf + END + SET @StringToExecute +=N' + FROM msdb.dbo.backupset b + WHERE 1=1 + AND b.backup_start_date >= @StartDate + AND b.backup_start_date < @StartDateNext + AND NOT EXISTS ( + SELECT 1 + FROM ' + QUOTENAME(@WriteBackupsToListenerName) + N'.' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.dbo.backupset b2 + WHERE b.backup_set_uuid = b2.backup_set_uuid + AND b2.backup_start_date >= @StartDate + )' + @crlf; - /*Check for transaction log file larger than data file */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 75 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 75) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 75 AS CheckID , - DB_NAME(a.database_id) , - 50 AS Priority , - 'Reliability' AS FindingsGroup , - 'Transaction Log Larger than Data File' AS Finding , - 'https://www.brentozar.com/go/biglog' AS URL , - 'The database [' + DB_NAME(a.database_id) - + '] has a ' + CAST((CAST(a.size AS BIGINT) * 8 / 1000000) AS NVARCHAR(20)) + ' GB transaction log file, larger than the total data file sizes. This may indicate that transaction log backups are not being performed or not performed often enough.' AS Details - FROM sys.master_files a - WHERE a.type = 1 - AND DB_NAME(a.database_id) NOT IN ( - SELECT DISTINCT - DatabaseName - FROM #SkipChecks - WHERE CheckID = 75 OR CheckID IS NULL) - AND a.size > 125000 /* Size is measured in pages here, so this gets us log files over 1GB. */ - AND a.size > ( SELECT SUM(CAST(b.size AS BIGINT)) - FROM sys.master_files b - WHERE a.database_id = b.database_id - AND b.type = 0 - ) - AND a.database_id IN ( - SELECT database_id - FROM sys.databases - WHERE source_database_id IS NULL ); - END; + SET @StringToExecute +=N' + SET @RC = @@ROWCOUNT; + + SET @msg = N''Inserted '' + CONVERT(NVARCHAR(30), @RC) + '' rows for ''+ CONVERT(NVARCHAR(30), @StartDate) + '' through '' + CONVERT(NVARCHAR(30), @StartDateNext) + ''.'' + RAISERROR(@msg, 0, 1) WITH NOWAIT + + SET @StartDate = @StartDateNext; + SET @StartDateNext = DATEADD(MINUTE, 10, @StartDate); - /*Check for collation conflicts between user databases and tempdb */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 76 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 76) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 76 AS CheckID , - name AS DatabaseName , - 200 AS Priority , - 'Informational' AS FindingsGroup , - 'Collation is ' + collation_name AS Finding , - 'https://www.brentozar.com/go/collate' AS URL , - 'Collation differences between user databases and tempdb can cause conflicts especially when comparing string values' AS Details - FROM sys.databases - WHERE name NOT IN ( 'master', 'model', 'msdb') - AND name NOT LIKE 'ReportServer%' - AND name NOT IN ( SELECT DISTINCT - DatabaseName - FROM #SkipChecks - WHERE CheckID IS NULL OR CheckID = 76) - AND collation_name <> ( SELECT - collation_name - FROM - sys.databases - WHERE - name = 'tempdb' - ); - END; + IF + ( @StartDate > SYSDATETIME() ) + BEGIN + + SET @msg = N''No more data to move, exiting.'' + RAISERROR(@msg, 0, 1) WITH NOWAIT + + BREAK; - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 77 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 77) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 77 AS CheckID , - dSnap.[name] AS DatabaseName , - 170 AS Priority , - 'Reliability' AS FindingsGroup , - 'Database Snapshot Online' AS Finding , - 'https://www.brentozar.com/go/snapshot' AS URL , - 'Database [' + dSnap.[name] - + '] is a snapshot of [' - + dOriginal.[name] - + ']. Make sure you have enough drive space to maintain the snapshot as the original database grows.' AS Details - FROM sys.databases dSnap - INNER JOIN sys.databases dOriginal ON dSnap.source_database_id = dOriginal.database_id - AND dSnap.name NOT IN ( - SELECT DISTINCT DatabaseName - FROM #SkipChecks - WHERE CheckID = 77 OR CheckID IS NULL); - END; + END + END' + @crlf; - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 79 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 79) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 79 AS CheckID , - -- sp_Blitz Issue #776 - -- Job has history and was executed in the last 30 days OR Job is enabled AND Job Schedule is enabled - CASE WHEN (cast(datediff(dd, substring(cast(sjh.run_date as nvarchar(10)), 1, 4) + '-' + substring(cast(sjh.run_date as nvarchar(10)), 5, 2) + '-' + substring(cast(sjh.run_date as nvarchar(10)), 7, 2), GETDATE()) AS INT) < 30) OR (j.[enabled] = 1 AND ssc.[enabled] = 1 )THEN - 100 - ELSE -- no job history (implicit) AND job not run in the past 30 days AND (Job disabled OR Job Schedule disabled) - 200 - END AS Priority, - 'Performance' AS FindingsGroup , - 'Shrink Database Job' AS Finding , - 'https://www.brentozar.com/go/autoshrink' AS URL , - 'In the [' + j.[name] + '] job, step [' - + step.[step_name] - + '] has SHRINKDATABASE or SHRINKFILE, which may be causing database fragmentation.' - + CASE WHEN COALESCE(ssc.name,'0') != '0' THEN + ' (Schedule: [' + ssc.name + '])' ELSE + '' END AS Details - FROM msdb.dbo.sysjobs j - INNER JOIN msdb.dbo.sysjobsteps step ON j.job_id = step.job_id - LEFT OUTER JOIN msdb.dbo.sysjobschedules AS sjsc - ON j.job_id = sjsc.job_id - LEFT OUTER JOIN msdb.dbo.sysschedules AS ssc - ON sjsc.schedule_id = ssc.schedule_id - AND sjsc.job_id = j.job_id - LEFT OUTER JOIN msdb.dbo.sysjobhistory AS sjh - ON j.job_id = sjh.job_id - AND step.step_id = sjh.step_id - AND sjh.run_date IN (SELECT max(sjh2.run_date) FROM msdb.dbo.sysjobhistory AS sjh2 WHERE sjh2.job_id = j.job_id) -- get the latest entry date - AND sjh.run_time IN (SELECT max(sjh3.run_time) FROM msdb.dbo.sysjobhistory AS sjh3 WHERE sjh3.job_id = j.job_id AND sjh3.run_date = sjh.run_date) -- get the latest entry time - WHERE step.command LIKE N'%SHRINKDATABASE%' - OR step.command LIKE N'%SHRINKFILE%'; - END; + IF @Debug = 1 + PRINT @StringToExecute; - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 81 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 81) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 81 AS CheckID , - 200 AS Priority , - 'Non-Active Server Config' AS FindingsGroup , - cr.name AS Finding , - 'https://www.BrentOzar.com/blitz/sp_configure/' AS URL , - ( 'This sp_configure option isn''t running under its set value. Its set value is ' - + CAST(cr.[value] AS VARCHAR(100)) - + ' and its running value is ' - + CAST(cr.value_in_use AS VARCHAR(100)) - + '. When someone does a RECONFIGURE or restarts the instance, this setting will start taking effect.' ) AS Details - FROM sys.configurations cr - WHERE cr.value <> cr.value_in_use - AND NOT (cr.name = 'min server memory (MB)' AND cr.value IN (0,16) AND cr.value_in_use IN (0,16)); - END; + EXEC sp_executesql @StringToExecute, N'@i_WriteBackupsLastHours INT', @i_WriteBackupsLastHours = @WriteBackupsLastHours; - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 123 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 123) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT TOP 1 123 AS CheckID , - 200 AS Priority , - 'Informational' AS FindingsGroup , - 'Agent Jobs Starting Simultaneously' AS Finding , - 'https://www.brentozar.com/go/busyagent/' AS URL , - ( 'Multiple SQL Server Agent jobs are configured to start simultaneously. For detailed schedule listings, see the query in the URL.' ) AS Details - FROM msdb.dbo.sysjobactivity - WHERE start_execution_date > DATEADD(dd, -14, GETDATE()) - GROUP BY start_execution_date HAVING COUNT(*) > 1; - END; +END; - IF @CheckServerInfo = 1 - BEGIN +END; -/*This checks Windows version. It would be better if Microsoft gave everything a separate build number, but whatever.*/ -IF @ProductVersionMajor >= 10 - AND NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 172 ) - BEGIN - -- sys.dm_os_host_info includes both Windows and Linux info - IF EXISTS (SELECT 1 - FROM sys.all_objects - WHERE name = 'dm_os_host_info' ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 172) WITH NOWAIT; - - INSERT INTO [#BlitzResults] - ( [CheckID] , - [Priority] , - [FindingsGroup] , - [Finding] , - [URL] , - [Details] ) - - SELECT - 172 AS [CheckID] , - 250 AS [Priority] , - 'Server Info' AS [FindingsGroup] , - 'Operating System Version' AS [Finding] , - ( CASE WHEN @IsWindowsOperatingSystem = 1 - THEN 'https://en.wikipedia.org/wiki/List_of_Microsoft_Windows_versions' - ELSE 'https://en.wikipedia.org/wiki/List_of_Linux_distributions' - END - ) AS [URL] , - ( CASE - WHEN [ohi].[host_platform] = 'Linux' THEN 'You''re running the ' + CAST([ohi].[host_distribution] AS VARCHAR(35)) + ' distribution of ' + CAST([ohi].[host_platform] AS VARCHAR(35)) + ', version ' + CAST([ohi].[host_release] AS VARCHAR(5)) - WHEN [ohi].[host_platform] = 'Windows' AND [ohi].[host_release] = '5' THEN 'You''re running Windows 2000, version ' + CAST([ohi].[host_release] AS VARCHAR(5)) - WHEN [ohi].[host_platform] = 'Windows' AND [ohi].[host_release] > '5' THEN 'You''re running ' + CAST([ohi].[host_distribution] AS VARCHAR(50)) + ', version ' + CAST([ohi].[host_release] AS VARCHAR(5)) - ELSE 'You''re running ' + CAST([ohi].[host_distribution] AS VARCHAR(35)) + ', version ' + CAST([ohi].[host_release] AS VARCHAR(5)) - END - ) AS [Details] - FROM [sys].[dm_os_host_info] [ohi]; - END; - ELSE - BEGIN - -- Otherwise, stick with Windows-only detection - - IF EXISTS ( SELECT 1 - FROM sys.all_objects - WHERE name = 'dm_os_windows_info' ) - - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 172) WITH NOWAIT; - - INSERT INTO [#BlitzResults] - ( [CheckID] , - [Priority] , - [FindingsGroup] , - [Finding] , - [URL] , - [Details] ) - - SELECT - 172 AS [CheckID] , - 250 AS [Priority] , - 'Server Info' AS [FindingsGroup] , - 'Windows Version' AS [Finding] , - 'https://en.wikipedia.org/wiki/List_of_Microsoft_Windows_versions' AS [URL] , - ( CASE - WHEN [owi].[windows_release] = '5' THEN 'You''re running Windows 2000, version ' + CAST([owi].[windows_release] AS VARCHAR(5)) - WHEN [owi].[windows_release] > '5' AND [owi].[windows_release] < '6' THEN 'You''re running Windows Server 2003/2003R2 era, version ' + CAST([owi].[windows_release] AS VARCHAR(5)) - WHEN [owi].[windows_release] >= '6' AND [owi].[windows_release] <= '6.1' THEN 'You''re running Windows Server 2008/2008R2 era, version ' + CAST([owi].[windows_release] AS VARCHAR(5)) - WHEN [owi].[windows_release] >= '6.2' AND [owi].[windows_release] <= '6.3' THEN 'You''re running Windows Server 2012/2012R2 era, version ' + CAST([owi].[windows_release] AS VARCHAR(5)) - WHEN [owi].[windows_release] = '10.0' THEN 'You''re running Windows Server 2016/2019 era, version ' + CAST([owi].[windows_release] AS VARCHAR(5)) - ELSE 'You''re running Windows Server, version ' + CAST([owi].[windows_release] AS VARCHAR(5)) - END - ) AS [Details] - FROM [sys].[dm_os_windows_info] [owi]; - - END; - END; - END; - -/* -This check hits the dm_os_process_memory system view -to see if locked_page_allocations_kb is > 0, -which could indicate that locked pages in memory is enabled. -*/ -IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 166 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 166) WITH NOWAIT; - - INSERT INTO [#BlitzResults] - ( [CheckID] , - [Priority] , - [FindingsGroup] , - [Finding] , - [URL] , - [Details] ) - SELECT - 166 AS [CheckID] , - 250 AS [Priority] , - 'Server Info' AS [FindingsGroup] , - 'Locked Pages In Memory Enabled' AS [Finding] , - 'https://www.brentozar.com/go/lpim' AS [URL] , - ( 'You currently have ' - + CASE WHEN [dopm].[locked_page_allocations_kb] / 1024. / 1024. > 0 - THEN CAST([dopm].[locked_page_allocations_kb] / 1024 / 1024 AS VARCHAR(100)) - + ' GB' - ELSE CAST([dopm].[locked_page_allocations_kb] / 1024 AS VARCHAR(100)) - + ' MB' - END + ' of pages locked in memory.' ) AS [Details] - FROM - [sys].[dm_os_process_memory] AS [dopm] - WHERE - [dopm].[locked_page_allocations_kb] > 0; - END; - - /* Server Info - Locked Pages In Memory Enabled - Check 166 - SQL Server 2016 SP1 and newer */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 166 ) - AND EXISTS ( SELECT * - FROM sys.all_objects o - INNER JOIN sys.all_columns c ON o.object_id = c.object_id - WHERE o.name = 'dm_os_sys_info' - AND c.name = 'sql_memory_model' ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 166) WITH NOWAIT; - - SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) - SELECT 166 AS CheckID , - 250 AS Priority , - ''Server Info'' AS FindingsGroup , - ''Memory Model Unconventional'' AS Finding , - ''https://www.brentozar.com/go/lpim'' AS URL , - ''Memory Model: '' + CAST(sql_memory_model_desc AS NVARCHAR(100)) - FROM sys.dm_os_sys_info WHERE sql_memory_model <> 1 OPTION (RECOMPILE);'; - - IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; - IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; - - EXECUTE(@StringToExecute); - END; - - /* Performance - Instant File Initialization Not Enabled - Check 192 */ - /* Server Info - Instant File Initialization Enabled - Check 193 */ - IF NOT EXISTS ( SELECT 1/0 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 192 /* IFI disabled check disabled */ - ) OR NOT EXISTS - ( SELECT 1/0 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 193 /* IFI enabled check disabled */ - ) - BEGIN - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d] and CheckId [%d].', 0, 1, 192, 193) WITH NOWAIT; - - DECLARE @IFISetting varchar(1) = N'N' - ,@IFIReadDMVFailed bit = 0 - ,@IFIAllFailed bit = 0; - - /* See if we can get the instant_file_initialization_enabled column from sys.dm_server_services */ - IF EXISTS - ( - SELECT 1/0 - FROM sys.all_columns - WHERE [object_id] = OBJECT_ID(N'[sys].[dm_server_services]') - AND [name] = N'instant_file_initialization_enabled' - ) - BEGIN - /* This needs to be a "dynamic" SQL statement because if the 'instant_file_initialization_enabled' column doesn't exist the procedure might fail on a bind error */ - SET @StringToExecute = N'SELECT @IFISetting = instant_file_initialization_enabled' + @crlf + - N'FROM sys.dm_server_services' + @crlf + - N'WHERE filename LIKE ''%sqlservr.exe%''' + @crlf + - N'OPTION (RECOMPILE);'; - - IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; - IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; - - EXEC dbo.sp_executesql - @StringToExecute - ,N'@IFISetting varchar(1) OUTPUT' - ,@IFISetting = @IFISetting OUTPUT - - SET @IFIReadDMVFailed = 0; - END - ELSE - /* We couldn't get the instant_file_initialization_enabled column from sys.dm_server_services, fall back to read error log */ - BEGIN - SET @IFIReadDMVFailed = 1; - /* If this is Amazon RDS, we'll use the rdsadmin.dbo.rds_read_error_log */ - IF LEFT(CAST(SERVERPROPERTY('ComputerNamePhysicalNetBIOS') AS VARCHAR(8000)), 8) = 'EC2AMAZ-' - AND LEFT(CAST(SERVERPROPERTY('MachineName') AS VARCHAR(8000)), 8) = 'EC2AMAZ-' - AND db_id('rdsadmin') IS NOT NULL - AND EXISTS ( SELECT 1/0 - FROM master.sys.all_objects - WHERE name IN ('rds_startup_tasks', 'rds_help_revlogin', 'rds_hexadecimal', 'rds_failover_tracking', 'rds_database_tracking', 'rds_track_change') - ) - BEGIN - /* Amazon RDS detected, read rdsadmin.dbo.rds_read_error_log */ - INSERT INTO #ErrorLog - EXEC rdsadmin.dbo.rds_read_error_log 0, 1, N'Database Instant File Initialization: enabled'; - END - ELSE - BEGIN - /* Try to read the error log, this might fail due to permissions */ - BEGIN TRY - INSERT INTO #ErrorLog - EXEC sys.xp_readerrorlog 0, 1, N'Database Instant File Initialization: enabled'; - END TRY - BEGIN CATCH - IF @Debug IN (1, 2) RAISERROR('No permissions to execute xp_readerrorlog.', 0, 1) WITH NOWAIT; - SET @IFIAllFailed = 1; - END CATCH - END; - END; - - IF @IFIAllFailed = 0 - BEGIN - IF @IFIReadDMVFailed = 1 - /* We couldn't read the DMV so set the @IFISetting variable using the error log */ - BEGIN - IF EXISTS ( SELECT 1/0 - FROM #ErrorLog - WHERE LEFT([Text], 45) = N'Database Instant File Initialization: enabled' - ) - BEGIN - SET @IFISetting = 'Y'; - END - ELSE - BEGIN - SET @IFISetting = 'N'; - END; - END; - - IF NOT EXISTS ( SELECT 1/0 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 192 /* IFI disabled check disabled */ - ) AND @IFISetting = 'N' - BEGIN - INSERT INTO #BlitzResults - ( - CheckID , - [Priority] , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT - 192 AS [CheckID] , - 50 AS [Priority] , - 'Performance' AS [FindingsGroup] , - 'Instant File Initialization Not Enabled' AS [Finding] , - 'https://www.brentozar.com/go/instant' AS [URL] , - 'Consider enabling IFI for faster restores and data file growths.' AS [Details] - END; - - IF NOT EXISTS ( SELECT 1/0 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 193 /* IFI enabled check disabled */ - ) AND @IFISetting = 'Y' - BEGIN - INSERT INTO #BlitzResults - ( - CheckID , - [Priority] , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT - 193 AS [CheckID] , - 250 AS [Priority] , - 'Server Info' AS [FindingsGroup] , - 'Instant File Initialization Enabled' AS [Finding] , - 'https://www.brentozar.com/go/instant' AS [URL] , - 'The service account has the Perform Volume Maintenance Tasks permission.' AS [Details] - END; - END; - END; - - /* End of checkId 192 */ - /* End of checkId 193 */ - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 130 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 130) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 130 AS CheckID , - 250 AS Priority , - 'Server Info' AS FindingsGroup , - 'Server Name' AS Finding , - 'https://www.brentozar.com/go/servername' AS URL , - @@SERVERNAME AS Details - WHERE @@SERVERNAME IS NOT NULL; - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 83 ) - BEGIN - IF EXISTS ( SELECT * - FROM sys.all_objects - WHERE name = 'dm_server_services' ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 83) WITH NOWAIT; - - -- DATETIMEOFFSET and DATETIME have different minimum values, so there's - -- a small workaround here to force 1753-01-01 if the minimum is detected - SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) - SELECT 83 AS CheckID , - 250 AS Priority , - ''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 + ''.'' - FROM sys.dm_server_services OPTION (RECOMPILE);'; - - IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; - IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; - - EXECUTE(@StringToExecute); - END; - END; - - /* Check 84 - SQL Server 2012 */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 84 ) - BEGIN - IF EXISTS ( SELECT * - FROM sys.all_objects o - INNER JOIN sys.all_columns c ON o.object_id = c.object_id - WHERE o.name = 'dm_os_sys_info' - AND c.name = 'physical_memory_kb' ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 84) WITH NOWAIT; - - SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) - SELECT 84 AS CheckID , - 250 AS Priority , - ''Server Info'' AS FindingsGroup , - ''Hardware'' AS Finding , - '''' AS URL , - ''Logical processors: '' + CAST(cpu_count AS VARCHAR(50)) + ''. Physical memory: '' + CAST( CAST(ROUND((physical_memory_kb / 1024.0 / 1024), 1) AS INT) AS VARCHAR(50)) + ''GB.'' - FROM sys.dm_os_sys_info OPTION (RECOMPILE);'; - - IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; - IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; - - EXECUTE(@StringToExecute); - END; - - /* Check 84 - SQL Server 2008 */ - IF EXISTS ( SELECT * - FROM sys.all_objects o - INNER JOIN sys.all_columns c ON o.object_id = c.object_id - WHERE o.name = 'dm_os_sys_info' - AND c.name = 'physical_memory_in_bytes' ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 84) WITH NOWAIT; - - SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) - SELECT 84 AS CheckID , - 250 AS Priority , - ''Server Info'' AS FindingsGroup , - ''Hardware'' AS Finding , - '''' AS URL , - ''Logical processors: '' + CAST(cpu_count AS VARCHAR(50)) + ''. Physical memory: '' + CAST( CAST(ROUND((physical_memory_in_bytes / 1024.0 / 1024 / 1024), 1) AS INT) AS VARCHAR(50)) + ''GB.'' - FROM sys.dm_os_sys_info OPTION (RECOMPILE);'; - - IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; - IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; - - EXECUTE(@StringToExecute); - END; - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 85 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 85) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 85 AS CheckID , - 250 AS Priority , - 'Server Info' AS FindingsGroup , - 'SQL Server Service' AS Finding , - '' AS URL , - N'Version: ' - + CAST(SERVERPROPERTY('productversion') AS NVARCHAR(100)) - + N'. Patch Level: ' - + CAST(SERVERPROPERTY('productlevel') AS NVARCHAR(100)) - + CASE WHEN SERVERPROPERTY('ProductUpdateLevel') IS NULL - THEN N'' - ELSE N'. Cumulative Update: ' - + CAST(SERVERPROPERTY('ProductUpdateLevel') AS NVARCHAR(100)) - END - + N'. Edition: ' - + CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) - + N'. Availability Groups Enabled: ' - + CAST(COALESCE(SERVERPROPERTY('IsHadrEnabled'), - 0) AS VARCHAR(100)) - + N'. Availability Groups Manager Status: ' - + CAST(COALESCE(SERVERPROPERTY('HadrManagerStatus'), - 0) AS VARCHAR(100)); - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 88 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 88) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 88 AS CheckID , - 250 AS Priority , - 'Server Info' AS FindingsGroup , - 'SQL Server Last Restart' AS Finding , - '' AS URL , - CAST(create_date AS VARCHAR(100)) - FROM sys.databases - WHERE database_id = 2; - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 91 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 91) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 91 AS CheckID , - 250 AS Priority , - 'Server Info' AS FindingsGroup , - 'Server Last Restart' AS Finding , - '' AS URL , - CAST(DATEADD(SECOND, (ms_ticks/1000)*(-1), GETDATE()) AS nvarchar(25)) - FROM sys.dm_os_sys_info; - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 92 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 92) WITH NOWAIT; - - INSERT INTO #driveInfo - ( drive, available_MB ) - EXEC master..xp_fixeddrives; - - IF EXISTS (SELECT * FROM sys.all_objects WHERE name = 'dm_os_volume_stats') - BEGIN - SET @StringToExecute = 'Update #driveInfo - SET - logical_volume_name = v.logical_volume_name, - total_MB = v.total_MB, - used_percent = v.used_percent - FROM - #driveInfo - inner join ( - SELECT DISTINCT - SUBSTRING(volume_mount_point, 1, 1) AS volume_mount_point - ,CASE WHEN ISNULL(logical_volume_name,'''') = '''' THEN '''' ELSE ''('' + logical_volume_name + '')'' END AS logical_volume_name - ,total_bytes/1024/1024 AS total_MB - ,available_bytes/1024/1024 AS available_MB - ,(CONVERT(DECIMAL(5,2),(total_bytes/1.0 - available_bytes)/total_bytes * 100)) AS used_percent - FROM - (SELECT TOP 1 WITH TIES - database_id - ,file_id - ,SUBSTRING(physical_name,1,1) AS Drive - FROM sys.master_files - ORDER BY ROW_NUMBER() OVER(PARTITION BY SUBSTRING(physical_name,1,1) ORDER BY database_id) - ) f - CROSS APPLY - sys.dm_os_volume_stats(f.database_id, f.file_id) - ) as v on #driveInfo.drive = v.volume_mount_point;'; - EXECUTE(@StringToExecute); - END; - - SET @StringToExecute ='INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 92 AS CheckID , - 250 AS Priority , - ''Server Info'' AS FindingsGroup , - ''Drive '' + i.drive + '' Space'' AS Finding , - '''' AS URL , - CASE WHEN i.total_MB IS NULL THEN - CAST(CAST(i.available_MB/1024 AS NUMERIC(18,2)) AS VARCHAR(30)) - + '' GB free on '' + i.drive - + '' drive'' - ELSE CAST(CAST(i.available_MB/1024 AS NUMERIC(18,2)) AS VARCHAR(30)) - + '' GB free on '' + i.drive - + '' drive '' + i.logical_volume_name - + '' out of '' + CAST(CAST(i.total_MB/1024 AS NUMERIC(18,2)) AS VARCHAR(30)) - + '' GB total ('' + CAST(i.used_percent AS VARCHAR(30)) + ''% used)'' END - AS Details - FROM #driveInfo AS i;' - - IF (@ProductVersionMajor >= 11) - BEGIN - SET @StringToExecute=REPLACE(@StringToExecute,'CAST(i.available_MB/1024 AS NUMERIC(18,2))','FORMAT(i.available_MB/1024,''N2'')'); - SET @StringToExecute=REPLACE(@StringToExecute,'CAST(i.total_MB/1024 AS NUMERIC(18,2))','FORMAT(i.total_MB/1024,''N2'')'); - END; - - EXECUTE(@StringToExecute); - - DROP TABLE #driveInfo; - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 103 ) - AND EXISTS ( SELECT * - FROM sys.all_objects o - INNER JOIN sys.all_columns c ON o.object_id = c.object_id - WHERE o.name = 'dm_os_sys_info' - AND c.name = 'virtual_machine_type_desc' ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 103) WITH NOWAIT; - - SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) - SELECT 103 AS CheckID, - 250 AS Priority, - ''Server Info'' AS FindingsGroup, - ''Virtual Server'' AS Finding, - ''https://www.brentozar.com/go/virtual'' AS URL, - ''Type: ('' + virtual_machine_type_desc + '')'' AS Details - FROM sys.dm_os_sys_info - WHERE virtual_machine_type <> 0 OPTION (RECOMPILE);'; - - IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; - IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; - - EXECUTE(@StringToExecute); - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 214 ) - AND EXISTS ( SELECT * - FROM sys.all_objects o - INNER JOIN sys.all_columns c ON o.object_id = c.object_id - WHERE o.name = 'dm_os_sys_info' - AND c.name = 'container_type_desc' ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 214) WITH NOWAIT; - - SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) - SELECT 214 AS CheckID, - 250 AS Priority, - ''Server Info'' AS FindingsGroup, - ''Container'' AS Finding, - ''https://www.brentozar.com/go/virtual'' AS URL, - ''Type: ('' + container_type_desc + '')'' AS Details - FROM sys.dm_os_sys_info - WHERE container_type_desc <> ''NONE'' OPTION (RECOMPILE);'; - - IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; - IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; - - EXECUTE(@StringToExecute); - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 114 ) - AND EXISTS ( SELECT * - FROM sys.all_objects o - WHERE o.name = 'dm_os_memory_nodes' ) - AND EXISTS ( SELECT * - FROM sys.all_objects o - INNER JOIN sys.all_columns c ON o.object_id = c.object_id - WHERE o.name = 'dm_os_nodes' - AND c.name = 'processor_group' ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 114) WITH NOWAIT; - - SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) - SELECT 114 AS CheckID , - 250 AS Priority , - ''Server Info'' AS FindingsGroup , - ''Hardware - NUMA Config'' AS Finding , - '''' AS URL , - ''Node: '' + CAST(n.node_id AS NVARCHAR(10)) + '' State: '' + node_state_desc - + '' Online schedulers: '' + CAST(n.online_scheduler_count AS NVARCHAR(10)) + '' Offline schedulers: '' + CAST(oac.offline_schedulers AS VARCHAR(100)) + '' Processor Group: '' + CAST(n.processor_group AS NVARCHAR(10)) - + '' Memory node: '' + CAST(n.memory_node_id AS NVARCHAR(10)) + '' Memory VAS Reserved GB: '' + CAST(CAST((m.virtual_address_space_reserved_kb / 1024.0 / 1024) AS INT) AS NVARCHAR(100)) - FROM sys.dm_os_nodes n - INNER JOIN sys.dm_os_memory_nodes m ON n.memory_node_id = m.memory_node_id - OUTER APPLY (SELECT - COUNT(*) AS [offline_schedulers] - FROM sys.dm_os_schedulers dos - WHERE n.node_id = dos.parent_node_id - AND dos.status = ''VISIBLE OFFLINE'' - ) oac - WHERE n.node_state_desc NOT LIKE ''%DAC%'' - ORDER BY n.node_id OPTION (RECOMPILE);'; - - IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; - IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; - - EXECUTE(@StringToExecute); - END; - - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 211 ) - BEGIN - - /* Variables for check 211: */ - DECLARE - @powerScheme varchar(36) - ,@cpu_speed_mhz int - ,@cpu_speed_ghz decimal(18,2) - ,@ExecResult int; - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 211) WITH NOWAIT; - IF @sa = 0 RAISERROR('The errors: ''xp_regread() returned error 5, ''Access is denied.'''' can be safely ignored', 0, 1) WITH NOWAIT; - - /* Get power plan if set by group policy [Git Hub Issue #1620] */ - EXEC xp_regread @rootkey = N'HKEY_LOCAL_MACHINE', - @key = N'SOFTWARE\Policies\Microsoft\Power\PowerSettings', - @value_name = N'ActivePowerScheme', - @value = @powerScheme OUTPUT, - @no_output = N'no_output'; - - IF @powerScheme IS NULL /* If power plan was not set by group policy, get local value [Git Hub Issue #1620]*/ - EXEC xp_regread @rootkey = N'HKEY_LOCAL_MACHINE', - @key = N'SYSTEM\CurrentControlSet\Control\Power\User\PowerSchemes', - @value_name = N'ActivePowerScheme', - @value = @powerScheme OUTPUT; - - /* Get the cpu speed*/ - EXEC @ExecResult = xp_regread @rootkey = N'HKEY_LOCAL_MACHINE', - @key = N'HARDWARE\DESCRIPTION\System\CentralProcessor\0', - @value_name = N'~MHz', - @value = @cpu_speed_mhz OUTPUT; - - /* Convert the Megahertz to Gigahertz */ - IF @ExecResult != 0 RAISERROR('We couldn''t retrieve the CPU speed, you will see Unknown in the results', 0, 1) - - SET @cpu_speed_ghz = CAST(CAST(@cpu_speed_mhz AS decimal) / 1000 AS decimal(18,2)); - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 211 AS CheckId, - 250 AS Priority, - 'Server Info' AS FindingsGroup, - 'Power Plan' AS Finding, - 'https://www.brentozar.com/blitz/power-mode/' AS URL, - 'Your server has ' - + ISNULL(CAST(@cpu_speed_ghz as VARCHAR(8)), 'Unknown ') - + 'GHz CPUs, and is in ' - + CASE @powerScheme - WHEN 'a1841308-3541-4fab-bc81-f71556f20b4a' - THEN 'power saving mode -- are you sure this is a production SQL Server?' - WHEN '381b4222-f694-41f0-9685-ff5bb260df2e' - THEN 'balanced power mode -- Uh... you want your CPUs to run at full speed, right?' - WHEN '8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c' - THEN 'high performance power mode' - WHEN 'e9a42b02-d5df-448d-aa00-03f14749eb61' - THEN 'ultimate performance power mode' - ELSE 'an unknown power mode.' - END AS Details - - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 212 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 212) WITH NOWAIT; - - INSERT INTO #Instances (Instance_Number, Instance_Name, Data_Field) - EXEC master.sys.xp_regread @rootkey = 'HKEY_LOCAL_MACHINE', - @key = 'SOFTWARE\Microsoft\Microsoft SQL Server', - @value_name = 'InstalledInstances' - - IF (SELECT COUNT(*) FROM #Instances) > 1 - BEGIN - - DECLARE @InstanceCount NVARCHAR(MAX) - SELECT @InstanceCount = COUNT(*) FROM #Instances - - INSERT INTO #BlitzResults - ( - CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT - 212 AS CheckId , - 250 AS Priority , - 'Server Info' AS FindingsGroup , - 'Instance Stacking' AS Finding , - 'https://www.brentozar.com/go/babygotstacked/' AS URL , - 'Your Server has ' + @InstanceCount + ' Instances of SQL Server installed. More than one is usually a bad idea. Read the URL for more info.' - END; - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 106 ) - AND (select convert(int,value_in_use) from sys.configurations where name = 'default trace enabled' ) = 1 - AND DATALENGTH( COALESCE( @base_tracefilename, '' ) ) > DATALENGTH('.TRC') - AND @TraceFileIssue = 0 - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 106) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT - 106 AS CheckID - ,250 AS Priority - ,'Server Info' AS FindingsGroup - ,'Default Trace Contents' AS Finding - ,'https://www.brentozar.com/go/trace' AS URL - ,'The default trace holds '+cast(DATEDIFF(hour,MIN(StartTime),GETDATE())as VARCHAR(30))+' hours of data' - +' between '+cast(Min(StartTime) as VARCHAR(30))+' and '+cast(GETDATE()as VARCHAR(30)) - +('. The default trace files are located in: '+left( @curr_tracefilename,len(@curr_tracefilename) - @indx) - ) as Details - FROM ::fn_trace_gettable( @base_tracefilename, default ) - WHERE EventClass BETWEEN 65500 and 65600; - END; /* CheckID 106 */ - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 152 ) - BEGIN - IF EXISTS (SELECT * FROM sys.dm_os_wait_stats ws - LEFT OUTER JOIN #IgnorableWaits i ON ws.wait_type = i.wait_type - WHERE wait_time_ms > .1 * @CpuMsSinceWaitsCleared AND waiting_tasks_count > 0 - AND i.wait_type IS NULL) - BEGIN - /* Check for waits that have had more than 10% of the server's wait time */ - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 152) WITH NOWAIT; - - WITH os(wait_type, waiting_tasks_count, wait_time_ms, max_wait_time_ms, signal_wait_time_ms) - AS - (SELECT ws.wait_type, waiting_tasks_count, wait_time_ms, max_wait_time_ms, signal_wait_time_ms - FROM sys.dm_os_wait_stats ws - LEFT OUTER JOIN #IgnorableWaits i ON ws.wait_type = i.wait_type - WHERE i.wait_type IS NULL - AND wait_time_ms > .1 * @CpuMsSinceWaitsCleared - AND waiting_tasks_count > 0) - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT TOP 9 - 152 AS CheckID - ,240 AS Priority - ,'Wait Stats' AS FindingsGroup - , CAST(ROW_NUMBER() OVER(ORDER BY os.wait_time_ms DESC) AS NVARCHAR(10)) + N' - ' + os.wait_type AS Finding - ,'https://www.sqlskills.com/help/waits/' + LOWER(os.wait_type) + '/' AS URL - , Details = CAST(CAST(SUM(os.wait_time_ms / 1000.0 / 60 / 60) OVER (PARTITION BY os.wait_type) AS NUMERIC(18,1)) AS NVARCHAR(20)) + N' hours of waits, ' + - CAST(CAST((SUM(60.0 * os.wait_time_ms) OVER (PARTITION BY os.wait_type) ) / @MsSinceWaitsCleared AS NUMERIC(18,1)) AS NVARCHAR(20)) + N' minutes average wait time per hour, ' + - /* CAST(CAST( - 100.* SUM(os.wait_time_ms) OVER (PARTITION BY os.wait_type) - / (1. * SUM(os.wait_time_ms) OVER () ) - AS NUMERIC(18,1)) AS NVARCHAR(40)) + N'% of waits, ' + */ - CAST(CAST( - 100. * SUM(os.signal_wait_time_ms) OVER (PARTITION BY os.wait_type) - / (1. * SUM(os.wait_time_ms) OVER ()) - AS NUMERIC(18,1)) AS NVARCHAR(40)) + N'% signal wait, ' + - CAST(SUM(os.waiting_tasks_count) OVER (PARTITION BY os.wait_type) AS NVARCHAR(40)) + N' waiting tasks, ' + - CAST(CASE WHEN SUM(os.waiting_tasks_count) OVER (PARTITION BY os.wait_type) > 0 - THEN - CAST( - SUM(os.wait_time_ms) OVER (PARTITION BY os.wait_type) - / (1. * SUM(os.waiting_tasks_count) OVER (PARTITION BY os.wait_type)) - AS NUMERIC(18,1)) - ELSE 0 END AS NVARCHAR(40)) + N' ms average wait time.' - FROM os - ORDER BY SUM(os.wait_time_ms / 1000.0 / 60 / 60) OVER (PARTITION BY os.wait_type) DESC; - END; /* IF EXISTS (SELECT * FROM sys.dm_os_wait_stats WHERE wait_time_ms > 0 AND waiting_tasks_count > 0) */ - - /* If no waits were found, add a note about that */ - IF NOT EXISTS (SELECT * FROM #BlitzResults WHERE CheckID IN (107, 108, 109, 121, 152, 162)) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 153) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - VALUES (153, 240, 'Wait Stats', 'No Significant Waits Detected', 'https://www.brentozar.com/go/waits', 'This server might be just sitting around idle, or someone may have cleared wait stats recently.'); - END; - END; /* CheckID 152 */ - - /* CheckID 222 - Server Info - Azure Managed Instance */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 222 ) - AND 4 = ( SELECT COUNT(*) - FROM sys.all_objects o - INNER JOIN sys.all_columns c ON o.object_id = c.object_id - WHERE o.name = 'dm_os_job_object' - AND c.name IN ('cpu_rate', 'memory_limit_mb', 'process_memory_limit_mb', 'workingset_limit_mb' )) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 222) WITH NOWAIT; - - SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) - SELECT 222 AS CheckID , - 250 AS Priority , - ''Server Info'' AS FindingsGroup , - ''Azure Managed Instance'' AS Finding , - ''https://www.BrentOzar.com/go/azurevm'' AS URL , - ''cpu_rate: '' + CAST(COALESCE(cpu_rate, 0) AS VARCHAR(20)) + - '', memory_limit_mb: '' + CAST(COALESCE(memory_limit_mb, 0) AS NVARCHAR(20)) + - '', process_memory_limit_mb: '' + CAST(COALESCE(process_memory_limit_mb, 0) AS NVARCHAR(20)) + - '', workingset_limit_mb: '' + CAST(COALESCE(workingset_limit_mb, 0) AS NVARCHAR(20)) - FROM sys.dm_os_job_object OPTION (RECOMPILE);'; - - IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; - IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; - - EXECUTE(@StringToExecute); - END; - - /* CheckID 224 - Performance - SSRS/SSAS/SSIS Installed */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 224 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 224) WITH NOWAIT; - - IF (SELECT value_in_use FROM sys.configurations WHERE [name] = 'xp_cmdshell') = 1 - BEGIN - - IF OBJECT_ID('tempdb..#services') IS NOT NULL DROP TABLE #services; - CREATE TABLE #services (cmdshell_output varchar(max)); - - INSERT INTO #services - EXEC /**/xp_cmdshell/**/ 'net start' /* added comments around command since some firewalls block this string TL 20210221 */ - - IF EXISTS (SELECT 1 - FROM #services - WHERE cmdshell_output LIKE '%SQL Server Reporting Services%' - OR cmdshell_output LIKE '%SQL Server Integration Services%' - OR cmdshell_output LIKE '%SQL Server Analysis Services%') - BEGIN - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT - 224 AS CheckID - ,200 AS Priority - ,'Performance' AS FindingsGroup - ,'SSAS/SSIS/SSRS Installed' AS Finding - ,'https://www.BrentOzar.com/go/services' AS URL - ,'Did you know you have other SQL Server services installed on this box other than the engine? It can be a real performance pain.' as Details - - END; - - END; - END; - - /* CheckID 232 - Server Info - Data Size */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 232 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 232) WITH NOWAIT; - - IF OBJECT_ID('tempdb..#MasterFiles') IS NOT NULL - DROP TABLE #MasterFiles; - CREATE TABLE #MasterFiles (database_id INT, file_id INT, type_desc NVARCHAR(50), name NVARCHAR(255), physical_name NVARCHAR(255), size BIGINT); - /* Azure SQL Database doesn't have sys.master_files, so we have to build our own. */ - IF ((SERVERPROPERTY('Edition')) = 'SQL Azure' - AND (OBJECT_ID('sys.master_files') IS NULL)) - SET @StringToExecute = 'INSERT INTO #MasterFiles (database_id, file_id, type_desc, name, physical_name, size) SELECT DB_ID(), file_id, type_desc, name, physical_name, size FROM sys.database_files;'; - ELSE - SET @StringToExecute = 'INSERT INTO #MasterFiles (database_id, file_id, type_desc, name, physical_name, size) SELECT database_id, file_id, type_desc, name, physical_name, size FROM sys.master_files;'; - EXEC(@StringToExecute); - - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 232 AS CheckID - ,250 AS Priority - ,'Server Info' AS FindingsGroup - ,'Data Size' AS Finding - ,'' AS URL - ,CAST(COUNT(DISTINCT database_id) AS NVARCHAR(100)) + N' databases, ' + CAST(CAST(SUM (CAST(size AS BIGINT)*8./1024./1024.) AS MONEY) AS VARCHAR(100)) + ' GB total file size' as Details - FROM #MasterFiles - WHERE database_id > 4; - - END; - - - END; /* IF @CheckServerInfo = 1 */ - END; /* IF ( ( SERVERPROPERTY('ServerName') NOT IN ( SELECT ServerName */ - - /* Delete priorites they wanted to skip. */ - IF @IgnorePrioritiesAbove IS NOT NULL - DELETE #BlitzResults - WHERE [Priority] > @IgnorePrioritiesAbove AND CheckID <> -1; - - IF @IgnorePrioritiesBelow IS NOT NULL - DELETE #BlitzResults - WHERE [Priority] < @IgnorePrioritiesBelow AND CheckID <> -1; - - /* Delete checks they wanted to skip. */ - IF @SkipChecksTable IS NOT NULL - BEGIN - DELETE FROM #BlitzResults - WHERE DatabaseName IN ( SELECT DatabaseName - FROM #SkipChecks - WHERE CheckID IS NULL - AND (ServerName IS NULL OR ServerName = SERVERPROPERTY('ServerName'))); - DELETE FROM #BlitzResults - WHERE CheckID IN ( SELECT CheckID - FROM #SkipChecks - WHERE DatabaseName IS NULL - AND (ServerName IS NULL OR ServerName = SERVERPROPERTY('ServerName'))); - DELETE r FROM #BlitzResults r - INNER JOIN #SkipChecks c ON r.DatabaseName = c.DatabaseName and r.CheckID = c.CheckID - AND (ServerName IS NULL OR ServerName = SERVERPROPERTY('ServerName')); - END; - - /* Add summary mode */ - IF @SummaryMode > 0 - BEGIN - UPDATE #BlitzResults - SET Finding = br.Finding + ' (' + CAST(brTotals.recs AS NVARCHAR(20)) + ')' - FROM #BlitzResults br - INNER JOIN (SELECT FindingsGroup, Finding, Priority, COUNT(*) AS recs FROM #BlitzResults GROUP BY FindingsGroup, Finding, Priority) brTotals ON br.FindingsGroup = brTotals.FindingsGroup AND br.Finding = brTotals.Finding AND br.Priority = brTotals.Priority - WHERE brTotals.recs > 1; - - DELETE br - FROM #BlitzResults br - WHERE EXISTS (SELECT * FROM #BlitzResults brLower WHERE br.FindingsGroup = brLower.FindingsGroup AND br.Finding = brLower.Finding AND br.Priority = brLower.Priority AND br.ID > brLower.ID); - - END; - - /* Add credits for the nice folks who put so much time into building and maintaining this for free: */ - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - VALUES ( -1 , - 255 , - 'Thanks!' , - 'From Your Community Volunteers' , - 'http://FirstResponderKit.org' , - 'We hope you found this tool useful.' - ); - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details +GO +SET ANSI_NULLS ON; +SET ANSI_PADDING ON; +SET ANSI_WARNINGS ON; +SET ARITHABORT ON; +SET CONCAT_NULL_YIELDS_NULL ON; +SET QUOTED_IDENTIFIER ON; +SET STATISTICS IO OFF; +SET STATISTICS TIME OFF; +GO - ) - VALUES ( -1 , - 0 , - 'sp_Blitz ' + CAST(CONVERT(DATETIME, @VersionDate, 102) AS VARCHAR(100)), - 'SQL Server First Responder Kit' , - 'http://FirstResponderKit.org/' , - 'To get help or add your own contributions, join us at http://FirstResponderKit.org.' +IF ( +SELECT + CASE + WHEN CONVERT(NVARCHAR(128), SERVERPROPERTY ('PRODUCTVERSION')) LIKE '8%' THEN 0 + WHEN CONVERT(NVARCHAR(128), SERVERPROPERTY ('PRODUCTVERSION')) LIKE '9%' THEN 0 + ELSE 1 + END +) = 0 +BEGIN + DECLARE @msg VARCHAR(8000); + SELECT @msg = 'Sorry, sp_BlitzCache doesn''t work on versions of SQL prior to 2008.' + REPLICATE(CHAR(13), 7933); + PRINT @msg; + RETURN; +END; - ); +IF OBJECT_ID('dbo.sp_BlitzCache') IS NULL + EXEC ('CREATE PROCEDURE dbo.sp_BlitzCache AS RETURN 0;'); +GO - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details +IF OBJECT_ID('dbo.sp_BlitzCache') IS NOT NULL AND OBJECT_ID('tempdb.dbo.##BlitzCacheProcs', 'U') IS NOT NULL + EXEC ('DROP TABLE ##BlitzCacheProcs;'); +GO - ) - SELECT 156 , - 254 , - 'Rundate' , - GETDATE() , - 'http://FirstResponderKit.org/' , - 'Captain''s log: stardate something and something...'; - - IF @EmailRecipients IS NOT NULL - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Sending an email.', 0, 1) WITH NOWAIT; - - /* Database mail won't work off a local temp table. I'm not happy about this hacky workaround either. */ - IF (OBJECT_ID('tempdb..##BlitzResults', 'U') IS NOT NULL) DROP TABLE ##BlitzResults; - SELECT * INTO ##BlitzResults FROM #BlitzResults; - SET @query_result_separator = char(9); - SET @StringToExecute = 'SET NOCOUNT ON;SELECT [Priority] , [FindingsGroup] , [Finding] , [DatabaseName] , [URL] , [Details] , CheckID FROM ##BlitzResults ORDER BY Priority , FindingsGroup , Finding , DatabaseName , Details; SET NOCOUNT OFF;'; - SET @EmailSubject = 'sp_Blitz Results for ' + @@SERVERNAME; - SET @EmailBody = 'sp_Blitz ' + CAST(CONVERT(DATETIME, @VersionDate, 102) AS VARCHAR(100)) + '. http://FirstResponderKit.org'; - IF @EmailProfile IS NULL - EXEC msdb.dbo.sp_send_dbmail - @recipients = @EmailRecipients, - @subject = @EmailSubject, - @body = @EmailBody, - @query_attachment_filename = 'sp_Blitz-Results.csv', - @attach_query_result_as_file = 1, - @query_result_header = 1, - @query_result_width = 32767, - @append_query_error = 1, - @query_result_no_padding = 1, - @query_result_separator = @query_result_separator, - @query = @StringToExecute; - ELSE - EXEC msdb.dbo.sp_send_dbmail - @profile_name = @EmailProfile, - @recipients = @EmailRecipients, - @subject = @EmailSubject, - @body = @EmailBody, - @query_attachment_filename = 'sp_Blitz-Results.csv', - @attach_query_result_as_file = 1, - @query_result_header = 1, - @query_result_width = 32767, - @append_query_error = 1, - @query_result_no_padding = 1, - @query_result_separator = @query_result_separator, - @query = @StringToExecute; - IF (OBJECT_ID('tempdb..##BlitzResults', 'U') IS NOT NULL) DROP TABLE ##BlitzResults; - END; +IF OBJECT_ID('dbo.sp_BlitzCache') IS NOT NULL AND OBJECT_ID('tempdb.dbo.##BlitzCacheResults', 'U') IS NOT NULL + EXEC ('DROP TABLE ##BlitzCacheResults;'); +GO - /* Checks if @OutputServerName is populated with a valid linked server, and that the database name specified is valid */ - DECLARE @ValidOutputServer BIT; - DECLARE @ValidOutputLocation BIT; - DECLARE @LinkedServerDBCheck NVARCHAR(2000); - DECLARE @ValidLinkedServerDB INT; - DECLARE @tmpdbchk table (cnt int); - IF @OutputServerName IS NOT NULL - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Outputting to a remote server.', 0, 1) WITH NOWAIT; - - IF EXISTS (SELECT server_id FROM sys.servers WHERE QUOTENAME([name]) = @OutputServerName) - BEGIN - SET @LinkedServerDBCheck = 'SELECT 1 WHERE EXISTS (SELECT * FROM '+@OutputServerName+'.master.sys.databases WHERE QUOTENAME([name]) = '''+@OutputDatabaseName+''')'; - INSERT INTO @tmpdbchk EXEC sys.sp_executesql @LinkedServerDBCheck; - SET @ValidLinkedServerDB = (SELECT COUNT(*) FROM @tmpdbchk); - IF (@ValidLinkedServerDB > 0) - BEGIN - SET @ValidOutputServer = 1; - SET @ValidOutputLocation = 1; - END; - ELSE - RAISERROR('The specified database was not found on the output server', 16, 0); - END; - ELSE - BEGIN - RAISERROR('The specified output server was not found', 16, 0); - END; - END; - ELSE - BEGIN - IF @OutputDatabaseName IS NOT NULL - AND @OutputSchemaName IS NOT NULL - AND @OutputTableName IS NOT NULL - AND EXISTS ( SELECT * - FROM sys.databases - WHERE QUOTENAME([name]) = @OutputDatabaseName) - BEGIN - SET @ValidOutputLocation = 1; - END; - ELSE IF @OutputDatabaseName IS NOT NULL - AND @OutputSchemaName IS NOT NULL - AND @OutputTableName IS NOT NULL - AND NOT EXISTS ( SELECT * - FROM sys.databases - WHERE QUOTENAME([name]) = @OutputDatabaseName) - BEGIN - RAISERROR('The specified output database was not found on this server', 16, 0); - END; - ELSE - BEGIN - SET @ValidOutputLocation = 0; - END; - END; +CREATE TABLE ##BlitzCacheResults ( + SPID INT, + ID INT IDENTITY(1,1), + CheckID INT, + Priority TINYINT, + FindingsGroup VARCHAR(50), + Finding VARCHAR(500), + URL VARCHAR(200), + Details VARCHAR(4000) +); - /* @OutputTableName lets us export the results to a permanent table */ - IF @ValidOutputLocation = 1 - BEGIN - SET @StringToExecute = 'USE ' - + @OutputDatabaseName - + '; IF EXISTS(SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' - + @OutputSchemaName - + ''') AND NOT EXISTS (SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''' - + @OutputSchemaName + ''' AND QUOTENAME(TABLE_NAME) = ''' - + @OutputTableName + ''') CREATE TABLE ' - + @OutputSchemaName + '.' - + @OutputTableName - + ' (ID INT IDENTITY(1,1) NOT NULL, - ServerName NVARCHAR(128), - CheckDate DATETIMEOFFSET, - Priority TINYINT , - FindingsGroup VARCHAR(50) , - Finding VARCHAR(200) , - DatabaseName NVARCHAR(128), - URL VARCHAR(200) , - Details NVARCHAR(4000) , - QueryPlan [XML] NULL , - QueryPlanFiltered [NVARCHAR](MAX) NULL, - CheckID INT , - CONSTRAINT [PK_' + CAST(NEWID() AS CHAR(36)) + '] PRIMARY KEY CLUSTERED (ID ASC));'; - IF @ValidOutputServer = 1 - BEGIN - SET @StringToExecute = REPLACE(@StringToExecute,''''+@OutputSchemaName+'''',''''''+@OutputSchemaName+''''''); - SET @StringToExecute = REPLACE(@StringToExecute,''''+@OutputTableName+'''',''''''+@OutputTableName+''''''); - SET @StringToExecute = REPLACE(@StringToExecute,'[XML]','[NVARCHAR](MAX)'); - EXEC('EXEC('''+@StringToExecute+''') AT ' + @OutputServerName); - END; - ELSE - BEGIN - IF @OutputXMLasNVARCHAR = 1 - BEGIN - SET @StringToExecute = REPLACE(@StringToExecute,'[XML]','[NVARCHAR](MAX)'); - END; - EXEC(@StringToExecute); - END; - IF @ValidOutputServer = 1 - BEGIN - SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' - + @OutputServerName + '.' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' - + @OutputSchemaName + ''') INSERT ' - + @OutputServerName + '.' - + @OutputDatabaseName + '.' - + @OutputSchemaName + '.' - + @OutputTableName - + ' (ServerName, CheckDate, CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details, QueryPlan, QueryPlanFiltered) SELECT ''' - + CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128)) - + ''', SYSDATETIMEOFFSET(), CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details, CAST(QueryPlan AS NVARCHAR(MAX)), QueryPlanFiltered FROM #BlitzResults ORDER BY Priority , FindingsGroup , Finding , DatabaseName , Details'; - - EXEC(@StringToExecute); - END; - ELSE - BEGIN - IF @OutputXMLasNVARCHAR = 1 - BEGIN - SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' - + @OutputSchemaName + ''') INSERT ' - + @OutputDatabaseName + '.' - + @OutputSchemaName + '.' - + @OutputTableName - + ' (ServerName, CheckDate, CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details, QueryPlan, QueryPlanFiltered) SELECT ''' - + CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128)) - + ''', SYSDATETIMEOFFSET(), CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details, CAST(QueryPlan AS NVARCHAR(MAX)), QueryPlanFiltered FROM #BlitzResults ORDER BY Priority , FindingsGroup , Finding , DatabaseName , Details'; - END; - ELSE - begin - SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' - + @OutputSchemaName + ''') INSERT ' - + @OutputDatabaseName + '.' - + @OutputSchemaName + '.' - + @OutputTableName - + ' (ServerName, CheckDate, CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details, QueryPlan, QueryPlanFiltered) SELECT ''' - + CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128)) - + ''', SYSDATETIMEOFFSET(), CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details, QueryPlan, QueryPlanFiltered FROM #BlitzResults ORDER BY Priority , FindingsGroup , Finding , DatabaseName , Details'; - END; - EXEC(@StringToExecute); - - END; - END; - ELSE IF (SUBSTRING(@OutputTableName, 2, 2) = '##') - BEGIN - IF @ValidOutputServer = 1 - BEGIN - RAISERROR('Due to the nature of temporary tables, outputting to a linked server requires a permanent table.', 16, 0); - END; - ELSE - BEGIN - SET @StringToExecute = N' IF (OBJECT_ID(''tempdb..' - + @OutputTableName - + ''') IS NOT NULL) DROP TABLE ' + @OutputTableName + ';' - + 'CREATE TABLE ' - + @OutputTableName - + ' (ID INT IDENTITY(1,1) NOT NULL, - ServerName NVARCHAR(128), - CheckDate DATETIMEOFFSET, - Priority TINYINT , - FindingsGroup VARCHAR(50) , - Finding VARCHAR(200) , - DatabaseName NVARCHAR(128), - URL VARCHAR(200) , - Details NVARCHAR(4000) , - QueryPlan [XML] NULL , - QueryPlanFiltered [NVARCHAR](MAX) NULL, - CheckID INT , - CONSTRAINT [PK_' + CAST(NEWID() AS CHAR(36)) + '] PRIMARY KEY CLUSTERED (ID ASC));' - + ' INSERT ' - + @OutputTableName - + ' (ServerName, CheckDate, CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details, QueryPlan, QueryPlanFiltered) SELECT ''' - + CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128)) - + ''', SYSDATETIMEOFFSET(), CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details, QueryPlan, QueryPlanFiltered FROM #BlitzResults ORDER BY Priority , FindingsGroup , Finding , DatabaseName , Details'; - - EXEC(@StringToExecute); - END; - END; - ELSE IF (SUBSTRING(@OutputTableName, 2, 1) = '#') - BEGIN - RAISERROR('Due to the nature of Dymamic SQL, only global (i.e. double pound (##)) temp tables are supported for @OutputTableName', 16, 0); - END; - - DECLARE @separator AS VARCHAR(1); - IF @OutputType = 'RSV' - SET @separator = CHAR(31); - ELSE - SET @separator = ','; - - IF @OutputType = 'COUNT' - BEGIN - SELECT COUNT(*) AS Warnings - FROM #BlitzResults; - END; - ELSE - IF @OutputType IN ( 'CSV', 'RSV' ) - BEGIN - - SELECT Result = CAST([Priority] AS NVARCHAR(100)) - + @separator + CAST(CheckID AS NVARCHAR(100)) - + @separator + COALESCE([FindingsGroup], - '(N/A)') + @separator - + COALESCE([Finding], '(N/A)') + @separator - + COALESCE(DatabaseName, '(N/A)') + @separator - + COALESCE([URL], '(N/A)') + @separator - + COALESCE([Details], '(N/A)') - FROM #BlitzResults - ORDER BY Priority , - FindingsGroup , - Finding , - DatabaseName , - Details; - END; - ELSE IF @OutputXMLasNVARCHAR = 1 AND @OutputType <> 'NONE' - BEGIN - SELECT [Priority] , - [FindingsGroup] , - [Finding] , - [DatabaseName] , - [URL] , - [Details] , - CAST([QueryPlan] AS NVARCHAR(MAX)) AS QueryPlan, - [QueryPlanFiltered] , - CheckID - FROM #BlitzResults - ORDER BY Priority , - FindingsGroup , - Finding , - DatabaseName , - Details; - END; - ELSE IF @OutputType = 'MARKDOWN' - BEGIN - WITH Results AS (SELECT row_number() OVER (ORDER BY Priority, FindingsGroup, Finding, DatabaseName, Details) AS rownum, * - FROM #BlitzResults - WHERE Priority > 0 AND Priority < 255 AND FindingsGroup IS NOT NULL AND Finding IS NOT NULL - AND FindingsGroup <> 'Security' /* Specifically excluding security checks for public exports */) - SELECT - Markdown = CONVERT(XML, STUFF((SELECT - CASE - WHEN r.Priority <> COALESCE(rPrior.Priority, 0) OR r.FindingsGroup <> rPrior.FindingsGroup THEN @crlf + N'**Priority ' + CAST(COALESCE(r.Priority,N'') AS NVARCHAR(5)) + N': ' + COALESCE(r.FindingsGroup,N'') + N'**:' + @crlf + @crlf - ELSE N'' - END - + CASE WHEN r.Finding <> COALESCE(rPrior.Finding,N'') AND r.Finding <> COALESCE(rNext.Finding,N'') THEN N'- ' + COALESCE(r.Finding,N'') + N' ' + COALESCE(r.DatabaseName, N'') + N' - ' + COALESCE(r.Details,N'') + @crlf - WHEN r.Finding <> COALESCE(rPrior.Finding,N'') AND r.Finding = rNext.Finding AND r.Details = rNext.Details THEN N'- ' + COALESCE(r.Finding,N'') + N' - ' + COALESCE(r.Details,N'') + @crlf + @crlf + N' * ' + COALESCE(r.DatabaseName, N'') + @crlf - WHEN r.Finding <> COALESCE(rPrior.Finding,N'') AND r.Finding = rNext.Finding THEN N'- ' + COALESCE(r.Finding,N'') + @crlf + @crlf + CASE WHEN r.DatabaseName IS NULL THEN N'' ELSE N' * ' + COALESCE(r.DatabaseName,N'') END + CASE WHEN r.Details <> rPrior.Details THEN N' - ' + COALESCE(r.Details,N'') + @crlf ELSE '' END - ELSE CASE WHEN r.DatabaseName IS NULL THEN N'' ELSE N' * ' + COALESCE(r.DatabaseName,N'') END + CASE WHEN r.Details <> rPrior.Details THEN N' - ' + COALESCE(r.Details,N'') + @crlf ELSE N'' + @crlf END - END + @crlf - FROM Results r - LEFT OUTER JOIN Results rPrior ON r.rownum = rPrior.rownum + 1 - LEFT OUTER JOIN Results rNext ON r.rownum = rNext.rownum - 1 - ORDER BY r.rownum FOR XML PATH(N''), ROOT('Markdown'), TYPE).value('/Markdown[1]','VARCHAR(MAX)'), 1, 2, '') - + ''); - END; - ELSE IF @OutputType = 'XML' - BEGIN - /* --TOURSTOP05-- */ - SELECT [Priority] , - [FindingsGroup] , - [Finding] , - [DatabaseName] , - [URL] , - [Details] , - [QueryPlanFiltered] , - CheckID - FROM #BlitzResults - ORDER BY Priority , - FindingsGroup , - Finding , - DatabaseName , - Details - FOR XML PATH('Result'), ROOT('sp_Blitz_Output'); - END; - ELSE IF @OutputType <> 'NONE' - BEGIN - /* --TOURSTOP05-- */ - SELECT [Priority] , - [FindingsGroup] , - [Finding] , - [DatabaseName] , - [URL] , - [Details] , - [QueryPlan] , - [QueryPlanFiltered] , - CheckID - FROM #BlitzResults - ORDER BY Priority , - FindingsGroup , - Finding , - DatabaseName , - Details; - END; - - DROP TABLE #BlitzResults; - - IF @OutputProcedureCache = 1 - AND @CheckProcedureCache = 1 - SELECT TOP 20 - total_worker_time / execution_count AS AvgCPU , - total_worker_time AS TotalCPU , - CAST(ROUND(100.00 * total_worker_time - / ( SELECT SUM(total_worker_time) - FROM sys.dm_exec_query_stats - ), 2) AS MONEY) AS PercentCPU , - total_elapsed_time / execution_count AS AvgDuration , - total_elapsed_time AS TotalDuration , - CAST(ROUND(100.00 * total_elapsed_time - / ( SELECT SUM(total_elapsed_time) - FROM sys.dm_exec_query_stats - ), 2) AS MONEY) AS PercentDuration , - total_logical_reads / execution_count AS AvgReads , - total_logical_reads AS TotalReads , - CAST(ROUND(100.00 * total_logical_reads - / ( SELECT SUM(total_logical_reads) - FROM sys.dm_exec_query_stats - ), 2) AS MONEY) AS PercentReads , - execution_count , - CAST(ROUND(100.00 * execution_count - / ( SELECT SUM(execution_count) - FROM sys.dm_exec_query_stats - ), 2) AS MONEY) AS PercentExecutions , - CASE WHEN DATEDIFF(mi, creation_time, - qs.last_execution_time) = 0 THEN 0 - ELSE CAST(( 1.00 * execution_count / DATEDIFF(mi, - creation_time, - qs.last_execution_time) ) AS MONEY) - END AS executions_per_minute , - qs.creation_time AS plan_creation_time , - qs.last_execution_time , - text , - text_filtered , - query_plan , - query_plan_filtered , - sql_handle , - query_hash , - plan_handle , - query_plan_hash - FROM #dm_exec_query_stats qs - ORDER BY CASE UPPER(@CheckProcedureCacheFilter) - WHEN 'CPU' THEN total_worker_time - WHEN 'READS' THEN total_logical_reads - WHEN 'EXECCOUNT' THEN execution_count - WHEN 'DURATION' THEN total_elapsed_time - ELSE total_worker_time - END DESC; - - END; /* ELSE -- IF @OutputType = 'SCHEMA' */ - - /* - Cleanups - drop temporary tables that have been created by this SP. - */ - - IF(OBJECT_ID('tempdb..#InvalidLogins') IS NOT NULL) - BEGIN - EXEC sp_executesql N'DROP TABLE #InvalidLogins;'; - END; - - IF OBJECT_ID('tempdb..#AlertInfo') IS NOT NULL - BEGIN - EXEC sp_executesql N'DROP TABLE #AlertInfo;'; - END; - - /* - Reset the Nmumeric_RoundAbort session state back to enabled if it was disabled earlier. - See Github issue #2302 for more info. - */ - IF @NeedToTurnNumericRoundabortBackOn = 1 - SET NUMERIC_ROUNDABORT ON; - - SET NOCOUNT OFF; -GO - -/* ---Sample execution call with the most common parameters: -EXEC [dbo].[sp_Blitz] - @CheckUserDatabaseObjects = 1 , - @CheckProcedureCache = 0 , - @OutputType = 'TABLE' , - @OutputProcedureCache = 0 , - @CheckProcedureCacheFilter = NULL, - @CheckServerInfo = 1 -*/ -SET ANSI_NULLS ON; -SET QUOTED_IDENTIFIER ON +CREATE TABLE ##BlitzCacheProcs ( + SPID INT , + QueryType NVARCHAR(258), + DatabaseName sysname, + AverageCPU DECIMAL(38,4), + AverageCPUPerMinute DECIMAL(38,4), + TotalCPU DECIMAL(38,4), + PercentCPUByType MONEY, + PercentCPU MONEY, + AverageDuration DECIMAL(38,4), + TotalDuration DECIMAL(38,4), + PercentDuration MONEY, + PercentDurationByType MONEY, + AverageReads BIGINT, + TotalReads BIGINT, + PercentReads MONEY, + PercentReadsByType MONEY, + ExecutionCount BIGINT, + PercentExecutions MONEY, + PercentExecutionsByType MONEY, + ExecutionsPerMinute MONEY, + TotalWrites BIGINT, + AverageWrites MONEY, + PercentWrites MONEY, + PercentWritesByType MONEY, + WritesPerMinute MONEY, + PlanCreationTime DATETIME, + PlanCreationTimeHours AS DATEDIFF(HOUR, PlanCreationTime, SYSDATETIME()), + LastExecutionTime DATETIME, + LastCompletionTime DATETIME, + PlanHandle VARBINARY(64), + [Remove Plan Handle From Cache] AS + CASE WHEN [PlanHandle] IS NOT NULL + THEN 'DBCC FREEPROCCACHE (' + CONVERT(VARCHAR(128), [PlanHandle], 1) + ');' + ELSE 'N/A' END, + SqlHandle VARBINARY(64), + [Remove SQL Handle From Cache] AS + CASE WHEN [SqlHandle] IS NOT NULL + THEN 'DBCC FREEPROCCACHE (' + CONVERT(VARCHAR(128), [SqlHandle], 1) + ');' + ELSE 'N/A' END, + [SQL Handle More Info] AS + CASE WHEN [SqlHandle] IS NOT NULL + THEN 'EXEC sp_BlitzCache @OnlySqlHandles = ''' + CONVERT(VARCHAR(128), [SqlHandle], 1) + '''; ' + ELSE 'N/A' END, + QueryHash BINARY(8), + [Query Hash More Info] AS + CASE WHEN [QueryHash] IS NOT NULL + THEN 'EXEC sp_BlitzCache @OnlyQueryHashes = ''' + CONVERT(VARCHAR(32), [QueryHash], 1) + '''; ' + ELSE 'N/A' END, + QueryPlanHash BINARY(8), + StatementStartOffset INT, + StatementEndOffset INT, + PlanGenerationNum BIGINT, + MinReturnedRows BIGINT, + MaxReturnedRows BIGINT, + AverageReturnedRows MONEY, + TotalReturnedRows BIGINT, + LastReturnedRows BIGINT, + /*The Memory Grant columns are only supported + in certain versions, giggle giggle. + */ + MinGrantKB BIGINT, + MaxGrantKB BIGINT, + MinUsedGrantKB BIGINT, + MaxUsedGrantKB BIGINT, + PercentMemoryGrantUsed MONEY, + AvgMaxMemoryGrant MONEY, + MinSpills BIGINT, + MaxSpills BIGINT, + TotalSpills BIGINT, + AvgSpills MONEY, + QueryText NVARCHAR(MAX), + QueryPlan XML, + /* these next four columns are the total for the type of query. + don't actually use them for anything apart from math by type. + */ + TotalWorkerTimeForType BIGINT, + TotalElapsedTimeForType BIGINT, + TotalReadsForType DECIMAL(30), + TotalExecutionCountForType BIGINT, + TotalWritesForType DECIMAL(30), + NumberOfPlans INT, + NumberOfDistinctPlans INT, + SerialDesiredMemory FLOAT, + SerialRequiredMemory FLOAT, + CachedPlanSize FLOAT, + CompileTime FLOAT, + CompileCPU FLOAT , + CompileMemory FLOAT , + MaxCompileMemory FLOAT , + min_worker_time BIGINT, + max_worker_time BIGINT, + is_forced_plan BIT, + is_forced_parameterized BIT, + is_cursor BIT, + is_optimistic_cursor BIT, + is_forward_only_cursor BIT, + is_fast_forward_cursor BIT, + is_cursor_dynamic BIT, + is_parallel BIT, + is_forced_serial BIT, + is_key_lookup_expensive BIT, + key_lookup_cost FLOAT, + is_remote_query_expensive BIT, + remote_query_cost FLOAT, + frequent_execution BIT, + parameter_sniffing BIT, + unparameterized_query BIT, + near_parallel BIT, + plan_warnings BIT, + plan_multiple_plans INT, + long_running BIT, + downlevel_estimator BIT, + implicit_conversions BIT, + busy_loops BIT, + tvf_join BIT, + tvf_estimate BIT, + compile_timeout BIT, + compile_memory_limit_exceeded BIT, + warning_no_join_predicate BIT, + QueryPlanCost FLOAT, + missing_index_count INT, + unmatched_index_count INT, + min_elapsed_time BIGINT, + max_elapsed_time BIGINT, + age_minutes MONEY, + age_minutes_lifetime MONEY, + is_trivial BIT, + trace_flags_session VARCHAR(1000), + is_unused_grant BIT, + function_count INT, + clr_function_count INT, + is_table_variable BIT, + no_stats_warning BIT, + relop_warnings BIT, + is_table_scan BIT, + backwards_scan BIT, + forced_index BIT, + forced_seek BIT, + forced_scan BIT, + columnstore_row_mode BIT, + is_computed_scalar BIT , + is_sort_expensive BIT, + sort_cost FLOAT, + is_computed_filter BIT, + op_name VARCHAR(100) NULL, + index_insert_count INT NULL, + index_update_count INT NULL, + index_delete_count INT NULL, + cx_insert_count INT NULL, + cx_update_count INT NULL, + cx_delete_count INT NULL, + table_insert_count INT NULL, + table_update_count INT NULL, + table_delete_count INT NULL, + index_ops AS (index_insert_count + index_update_count + index_delete_count + + cx_insert_count + cx_update_count + cx_delete_count + + table_insert_count + table_update_count + table_delete_count), + is_row_level BIT, + is_spatial BIT, + index_dml BIT, + table_dml BIT, + long_running_low_cpu BIT, + low_cost_high_cpu BIT, + stale_stats BIT, + is_adaptive BIT, + index_spool_cost FLOAT, + index_spool_rows FLOAT, + table_spool_cost FLOAT, + table_spool_rows FLOAT, + is_spool_expensive BIT, + is_spool_more_rows BIT, + is_table_spool_expensive BIT, + is_table_spool_more_rows BIT, + estimated_rows FLOAT, + is_bad_estimate BIT, + is_paul_white_electric BIT, + is_row_goal BIT, + is_big_spills BIT, + is_mstvf BIT, + is_mm_join BIT, + is_nonsargable BIT, + select_with_writes BIT, + implicit_conversion_info XML, + cached_execution_parameters XML, + missing_indexes XML, + SetOptions VARCHAR(MAX), + Warnings VARCHAR(MAX), + Pattern NVARCHAR(20) + ); +GO -IF NOT EXISTS (SELECT * FROM sys.objects WHERE [object_id] = OBJECT_ID(N'[dbo].[sp_BlitzAnalysis]') AND [type] in (N'P', N'PC')) -BEGIN -EXEC dbo.sp_executesql @statement = N'CREATE PROCEDURE [dbo].[sp_BlitzAnalysis] AS' -END -GO - -ALTER PROCEDURE [dbo].[sp_BlitzAnalysis] ( -@Help TINYINT = 0, -@StartDate DATETIMEOFFSET(7) = NULL, -@EndDate DATETIMEOFFSET(7) = NULL, -@OutputDatabaseName NVARCHAR(256) = 'DBAtools', -@OutputSchemaName NVARCHAR(256) = N'dbo', -@OutputTableNameBlitzFirst NVARCHAR(256) = N'BlitzFirst', -@OutputTableNameFileStats NVARCHAR(256) = N'BlitzFirst_FileStats', -@OutputTableNamePerfmonStats NVARCHAR(256) = N'BlitzFirst_PerfmonStats', -@OutputTableNameWaitStats NVARCHAR(256) = N'BlitzFirst_WaitStats', -@OutputTableNameBlitzCache NVARCHAR(256) = N'BlitzCache', -@OutputTableNameBlitzWho NVARCHAR(256) = N'BlitzWho', -@Servername NVARCHAR(128) = @@SERVERNAME, -@Databasename NVARCHAR(128) = NULL, -@BlitzCacheSortorder NVARCHAR(20) = N'cpu', -@MaxBlitzFirstPriority INT = 249, -@ReadLatencyThreshold INT = 100, -@WriteLatencyThreshold INT = 100, -@WaitStatsTop TINYINT = 10, -@Version VARCHAR(30) = NULL OUTPUT, -@VersionDate DATETIME = NULL OUTPUT, -@VersionCheckMode BIT = 0, -@BringThePain BIT = 0, -@Maxdop INT = 1, -@Debug BIT = 0 -) -AS +ALTER PROCEDURE dbo.sp_BlitzCache + @Help BIT = 0, + @Top INT = NULL, + @SortOrder VARCHAR(50) = 'CPU', + @UseTriggersAnyway BIT = NULL, + @ExportToExcel BIT = 0, + @ExpertMode TINYINT = 0, + @OutputType VARCHAR(20) = 'TABLE' , + @OutputServerName NVARCHAR(258) = NULL , + @OutputDatabaseName NVARCHAR(258) = NULL , + @OutputSchemaName NVARCHAR(258) = NULL , + @OutputTableName NVARCHAR(258) = NULL , -- do NOT use ##BlitzCacheResults or ##BlitzCacheProcs as they are used as work tables in this procedure + @ConfigurationDatabaseName NVARCHAR(128) = NULL , + @ConfigurationSchemaName NVARCHAR(258) = NULL , + @ConfigurationTableName NVARCHAR(258) = NULL , + @DurationFilter DECIMAL(38,4) = NULL , + @HideSummary BIT = 0 , + @IgnoreSystemDBs BIT = 1 , + @OnlyQueryHashes VARCHAR(MAX) = NULL , + @IgnoreQueryHashes VARCHAR(MAX) = NULL , + @OnlySqlHandles VARCHAR(MAX) = NULL , + @IgnoreSqlHandles VARCHAR(MAX) = NULL , + @QueryFilter VARCHAR(10) = 'ALL' , + @DatabaseName NVARCHAR(128) = NULL , + @StoredProcName NVARCHAR(128) = NULL, + @SlowlySearchPlansFor NVARCHAR(4000) = NULL, + @Reanalyze BIT = 0 , + @SkipAnalysis BIT = 0 , + @BringThePain BIT = 0 , + @MinimumExecutionCount INT = 0, + @Debug BIT = 0, + @CheckDateOverride DATETIMEOFFSET = NULL, + @MinutesBack INT = NULL, + @Version VARCHAR(30) = NULL OUTPUT, + @VersionDate DATETIME = NULL OUTPUT, + @VersionCheckMode BIT = 0 +WITH RECOMPILE +AS +BEGIN SET NOCOUNT ON; SET STATISTICS XML OFF; +SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; SELECT @Version = '8.19', @VersionDate = '20240222'; +SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) BEGIN RETURN; END; -IF (@Help = 1) -BEGIN - PRINT 'EXEC sp_BlitzAnalysis -@StartDate = NULL, /* Specify a datetime or NULL will get an hour ago */ -@EndDate = NULL, /* Specify a datetime or NULL will get an hour of data since @StartDate */ -@OutputDatabaseName = N''DBA'', /* Specify the database name where where we can find your logged blitz data */ -@OutputSchemaName = N''dbo'', /* Specify the schema */ -@OutputTableNameBlitzFirst = N''BlitzFirst'', /* Table name where you are storing sp_BlitzFirst output, Set to NULL to ignore */ -@OutputTableNameFileStats = N''BlitzFirst_FileStats'', /* Table name where you are storing sp_BlitzFirst filestats output, Set to NULL to ignore */ -@OutputTableNamePerfmonStats = N''BlitzFirst_PerfmonStats'', /* Table name where you are storing sp_BlitzFirst Perfmon output, Set to NULL to ignore */ -@OutputTableNameWaitStats = N''BlitzFirst_WaitStats'', /* Table name where you are storing sp_BlitzFirst Wait stats output, Set to NULL to ignore */ -@OutputTableNameBlitzCache = N''BlitzCache'', /* Table name where you are storing sp_BlitzCache output, Set to NULL to ignore */ -@OutputTableNameBlitzWho = N''BlitzWho'', /* Table name where you are storing sp_BlitzWho output, Set to NULL to ignore */ -@Databasename = NULL, /* Filters results for BlitzCache, FileStats (will also include tempdb), BlitzWho. Leave as NULL for all databases */ -@MaxBlitzFirstPriority = 249, /* Max priority to include in the results */ -@BlitzCacheSortorder = ''cpu'', /* Accepted values ''all'' ''cpu'' ''reads'' ''writes'' ''duration'' ''executions'' ''memory grant'' ''spills'' */ -@WaitStatsTop = 3, /* Controls the top for wait stats only */ -@Maxdop = 1, /* Control the degree of parallelism that the queries within this proc can use if they want to*/ -@Debug = 0; /* Show sp_BlitzAnalysis SQL commands in the messages tab as they execute */ - -/* -Additional parameters: -@ReadLatencyThreshold INT /* Default: 100 - Sets the threshold in ms to compare against io_stall_read_average_ms in your filestats table */ -@WriteLatencyThreshold INT /* Default: 100 - Sets the threshold in ms to compare against io_stall_write_average_ms in your filestats table */ -@BringThePain BIT /* Default: 0 - If you are getting more than 4 hours of data with blitzcachesortorder set to ''all'' you will need to set BringThePain to 1 */ -*/'; - RETURN; -END +DECLARE @nl NVARCHAR(2) = NCHAR(13) + NCHAR(10) ; + +IF @Help = 1 + BEGIN + PRINT ' + sp_BlitzCache from http://FirstResponderKit.org + + This script displays your most resource-intensive queries from the plan cache, + and points to ways you can tune these queries to make them faster. -/* Declare all local variables required */ -DECLARE @FullOutputTableNameBlitzFirst NVARCHAR(1000); -DECLARE @FullOutputTableNameFileStats NVARCHAR(1000); -DECLARE @FullOutputTableNamePerfmonStats NVARCHAR(1000); -DECLARE @FullOutputTableNameWaitStats NVARCHAR(1000); -DECLARE @FullOutputTableNameBlitzCache NVARCHAR(1000); -DECLARE @FullOutputTableNameBlitzWho NVARCHAR(1000); -DECLARE @Sql NVARCHAR(MAX); -DECLARE @NewLine NVARCHAR(2) = CHAR(13); -DECLARE @IncludeMemoryGrants BIT; -DECLARE @IncludeSpills BIT; -/* Validate the database name */ -IF (DB_ID(@OutputDatabaseName) IS NULL) -BEGIN - RAISERROR('Invalid database name provided for parameter @OutputDatabaseName: %s',11,0,@OutputDatabaseName); - RETURN; -END + To learn more, visit http://FirstResponderKit.org where you can download new + versions for free, watch training videos on how it works, get more info on + the findings, contribute your own code, and more. -/* Set fully qualified table names */ -SET @FullOutputTableNameBlitzFirst = QUOTENAME(@OutputDatabaseName)+N'.'+QUOTENAME(@OutputSchemaName)+N'.'+QUOTENAME(@OutputTableNameBlitzFirst); -SET @FullOutputTableNameFileStats = QUOTENAME(@OutputDatabaseName)+N'.'+QUOTENAME(@OutputSchemaName)+N'.'+QUOTENAME(@OutputTableNameFileStats+N'_Deltas'); -SET @FullOutputTableNamePerfmonStats = QUOTENAME(@OutputDatabaseName)+N'.'+QUOTENAME(@OutputSchemaName)+N'.'+QUOTENAME(@OutputTableNamePerfmonStats+N'_Actuals'); -SET @FullOutputTableNameWaitStats = QUOTENAME(@OutputDatabaseName)+N'.'+QUOTENAME(@OutputSchemaName)+N'.'+QUOTENAME(@OutputTableNameWaitStats+N'_Deltas'); -SET @FullOutputTableNameBlitzCache = QUOTENAME(@OutputDatabaseName)+N'.'+QUOTENAME(@OutputSchemaName)+N'.'+QUOTENAME(@OutputTableNameBlitzCache); -SET @FullOutputTableNameBlitzWho = QUOTENAME(@OutputDatabaseName)+N'.'+QUOTENAME(@OutputSchemaName)+N'.'+QUOTENAME(@OutputTableNameBlitzWho+N'_Deltas'); + Known limitations of this version: + - SQL Server 2008 and 2008R2 have a bug in trigger stats, so that output is + excluded by default. + - @IgnoreQueryHashes and @OnlyQueryHashes require a CSV list of hashes + with no spaces between the hash values. -IF OBJECT_ID('tempdb.dbo.#BlitzFirstCounts') IS NOT NULL -BEGIN - DROP TABLE #BlitzFirstCounts; -END + Unknown limitations of this version: + - May or may not be vulnerable to the wick effect. -CREATE TABLE #BlitzFirstCounts ( - [Priority] TINYINT NOT NULL, - [FindingsGroup] VARCHAR(50) NOT NULL, - [Finding] VARCHAR(200) NOT NULL, - [TotalOccurrences] INT NULL, - [FirstOccurrence] DATETIMEOFFSET(7) NULL, - [LastOccurrence] DATETIMEOFFSET(7) NULL -); + Changes - for the full list of improvements and fixes in this version, see: + https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/ -/* Validate variables and set defaults as required */ -IF (@BlitzCacheSortorder IS NULL) -BEGIN - SET @BlitzCacheSortorder = N'cpu'; -END -SET @BlitzCacheSortorder = LOWER(@BlitzCacheSortorder); -IF (@OutputTableNameBlitzCache IS NOT NULL AND @BlitzCacheSortorder NOT IN (N'all',N'cpu',N'reads',N'writes',N'duration',N'executions',N'memory grant',N'spills')) -BEGIN - RAISERROR('Invalid sort option specified for @BlitzCacheSortorder, supported values are ''all'', ''cpu'', ''reads'', ''writes'', ''duration'', ''executions'', ''memory grant'', ''spills''',11,0) WITH NOWAIT; - RETURN; -END + MIT License -/* Set @Maxdop to 1 if NULL was passed in */ -IF (@Maxdop IS NULL) -BEGIN - SET @Maxdop = 1; -END + Copyright (c) Brent Ozar Unlimited -/* iF @Maxdop is set higher than the core count just set it to 0 */ -IF (@Maxdop > (SELECT CAST(cpu_count AS INT) FROM sys.dm_os_sys_info)) -BEGIN - SET @Maxdop = 0; -END + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: -/* We need to check if your SQL version has memory grant and spills columns in sys.dm_exec_query_stats */ -SELECT @IncludeMemoryGrants = - CASE - WHEN (EXISTS(SELECT * FROM sys.all_columns WHERE [object_id] = OBJECT_ID('sys.dm_exec_query_stats') AND name = 'max_grant_kb')) THEN 1 - ELSE 0 - END; + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. -SELECT @IncludeSpills = - CASE - WHEN (EXISTS(SELECT * FROM sys.all_columns WHERE [object_id] = OBJECT_ID('sys.dm_exec_query_stats') AND name = 'max_spills')) THEN 1 - ELSE 0 - END; + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + '; + + SELECT N'@Help' AS [Parameter Name] , + N'BIT' AS [Data Type] , + N'Displays this help message.' AS [Parameter Description] -IF (@StartDate IS NULL) -BEGIN - RAISERROR('Setting @StartDate to: 1 hour ago',0,0) WITH NOWAIT; - /* Set StartDate to be an hour ago */ - SET @StartDate = DATEADD(HOUR,-1,SYSDATETIMEOFFSET()); + UNION ALL + SELECT N'@Top', + N'INT', + N'The number of records to retrieve and analyze from the plan cache. The following DMVs are used as the plan cache: dm_exec_query_stats, dm_exec_procedure_stats, dm_exec_trigger_stats.' - IF (@EndDate IS NULL) - BEGIN - RAISERROR('Setting @EndDate to: Now',0,0) WITH NOWAIT; - /* Get data right up to now */ - SET @EndDate = SYSDATETIMEOFFSET(); - END -END + UNION ALL + SELECT N'@SortOrder', + N'VARCHAR(10)', + N'Data processing and display order. @SortOrder will still be used, even when preparing output for a table or for excel. Possible values are: "CPU", "Reads", "Writes", "Duration", "Executions", "Recent Compilations", "Memory Grant", "Unused Grant", "Spills", "Query Hash", "Duplicate". Additionally, the word "Average" or "Avg" can be used to sort on averages rather than total. "Executions per minute" and "Executions / minute" can be used to sort by execution per minute. For the truly lazy, "xpm" can also be used. Note that when you use all or all avg, the only parameters you can use are @Top and @DatabaseName. All others will be ignored.' -IF (@EndDate IS NULL) -BEGIN - /* Default to an hour of data or SYSDATETIMEOFFSET() if now is earlier than the hour added to @StartDate */ - IF(DATEADD(HOUR,1,@StartDate) < SYSDATETIMEOFFSET()) - BEGIN - RAISERROR('@EndDate was NULL - Setting to return 1 hour of information, if you want more then set @EndDate aswell',0,0) WITH NOWAIT; - SET @EndDate = DATEADD(HOUR,1,@StartDate); - END - ELSE - BEGIN - RAISERROR('@EndDate was NULL - Setting to SYSDATETIMEOFFSET()',0,0) WITH NOWAIT; - SET @EndDate = SYSDATETIMEOFFSET(); - END -END + UNION ALL + SELECT N'@UseTriggersAnyway', + N'BIT', + N'On SQL Server 2008R2 and earlier, trigger execution count is incorrect - trigger execution count is incremented once per execution of a SQL agent job. If you still want to see relative execution count of triggers, then you can force sp_BlitzCache to include this information.' -/* Default to dbo schema if NULL is passed in */ -IF (@OutputSchemaName IS NULL) -BEGIN - SET @OutputSchemaName = 'dbo'; -END + UNION ALL + SELECT N'@ExportToExcel', + N'BIT', + N'Prepare output for exporting to Excel. Newlines and additional whitespace are removed from query text and the execution plan is not displayed.' -/* Prompt the user for @BringThePain = 1 if they are searching a timeframe greater than 4 hours and they are using BlitzCacheSortorder = 'all' */ -IF(@BlitzCacheSortorder = 'all' AND DATEDIFF(HOUR,@StartDate,@EndDate) > 4 AND @BringThePain = 0) -BEGIN - RAISERROR('Wow! hold up now, are you sure you wanna do this? Are sure you want to query over 4 hours of data with @BlitzCacheSortorder set to ''all''? IF you do then set @BringThePain = 1 but I gotta warn you this might hurt a bit!',11,1) WITH NOWAIT; - RETURN; -END + UNION ALL + SELECT N'@ExpertMode', + N'TINYINT', + N'Default 0. When set to 1, results include more columns. When 2, mode is optimized for Opserver, the open source dashboard.' + UNION ALL + SELECT N'@OutputType', + N'NVARCHAR(258)', + N'If set to "NONE", this will tell this procedure not to run any query leading to a results set thrown to caller.' + + UNION ALL + SELECT N'@OutputDatabaseName', + N'NVARCHAR(128)', + N'The output database. If this does not exist SQL Server will divide by zero and everything will fall apart.' -/* Output report window information */ -SELECT - @Servername AS [ServerToReportOn], - CAST(1 AS NVARCHAR(20)) + N' - '+ CAST(@MaxBlitzFirstPriority AS NVARCHAR(20)) AS [PrioritesToInclude], - @StartDate AS [StartDatetime], - @EndDate AS [EndDatetime];; + UNION ALL + SELECT N'@OutputSchemaName', + N'NVARCHAR(258)', + N'The output schema. If this does not exist SQL Server will divide by zero and everything will fall apart.' + UNION ALL + SELECT N'@OutputTableName', + N'NVARCHAR(258)', + N'The output table. If this does not exist, it will be created for you.' -/* BlitzFirst data */ -SET @Sql = N' -INSERT INTO #BlitzFirstCounts ([Priority],[FindingsGroup],[Finding],[TotalOccurrences],[FirstOccurrence],[LastOccurrence]) -SELECT -[Priority], -[FindingsGroup], -[Finding], -COUNT(*) AS [TotalOccurrences], -MIN(CheckDate) AS [FirstOccurrence], -MAX(CheckDate) AS [LastOccurrence] -FROM '+@FullOutputTableNameBlitzFirst+N' -WHERE [ServerName] = @Servername -AND [Priority] BETWEEN 1 AND @MaxBlitzFirstPriority -AND CheckDate BETWEEN @StartDate AND @EndDate -AND [CheckID] > -1 -GROUP BY [Priority],[FindingsGroup],[Finding]; + UNION ALL + SELECT N'@DurationFilter', + N'DECIMAL(38,4)', + N'Excludes queries with an average duration (in seconds) less than @DurationFilter.' -IF EXISTS(SELECT 1 FROM #BlitzFirstCounts) -BEGIN - SELECT - [Priority], - [FindingsGroup], - [Finding], - [TotalOccurrences], - [FirstOccurrence], - [LastOccurrence] - FROM #BlitzFirstCounts - ORDER BY [Priority] ASC,[TotalOccurrences] DESC; -END -ELSE -BEGIN - SELECT N''No findings with a priority between 1 and ''+CAST(@MaxBlitzFirstPriority AS NVARCHAR(10))+N'' found for this period''; -END -SELECT - [ServerName] -,[CheckDate] -,[CheckID] -,[Priority] -,[Finding] -,[URL] -,[Details] -,[HowToStopIt] -,[QueryPlan] -,[QueryText] -FROM '+@FullOutputTableNameBlitzFirst+N' Findings -WHERE [ServerName] = @Servername -AND [Priority] BETWEEN 1 AND @MaxBlitzFirstPriority -AND [CheckDate] BETWEEN @StartDate AND @EndDate -AND [CheckID] > -1 -ORDER BY CheckDate ASC,[Priority] ASC -OPTION (RECOMPILE, MAXDOP '+CAST(@Maxdop AS NVARCHAR(2))+N');'; + UNION ALL + SELECT N'@HideSummary', + N'BIT', + N'Hides the findings summary result set.' + UNION ALL + SELECT N'@IgnoreSystemDBs', + N'BIT', + N'Ignores plans found in the system databases (master, model, msdb, tempdb, dbmaintenance, dbadmin, dbatools and resourcedb)' -RAISERROR('Getting BlitzFirst info from %s',0,0,@FullOutputTableNameBlitzFirst) WITH NOWAIT; + UNION ALL + SELECT N'@OnlyQueryHashes', + N'VARCHAR(MAX)', + N'A list of query hashes to query. All other query hashes will be ignored. Stored procedures and triggers will be ignored.' -IF (@Debug = 1) -BEGIN - PRINT @Sql; -END + UNION ALL + SELECT N'@IgnoreQueryHashes', + N'VARCHAR(MAX)', + N'A list of query hashes to ignore.' + + UNION ALL + SELECT N'@OnlySqlHandles', + N'VARCHAR(MAX)', + N'One or more sql_handles to use for filtering results.' + + UNION ALL + SELECT N'@IgnoreSqlHandles', + N'VARCHAR(MAX)', + N'One or more sql_handles to ignore.' -IF (OBJECT_ID(@FullOutputTableNameBlitzFirst) IS NULL) -BEGIN - IF (@OutputTableNameBlitzFirst IS NULL) - BEGIN - RAISERROR('BlitzFirst data skipped',10,0); - SELECT N'Skipped logged BlitzFirst data as NULL was passed to parameter @OutputTableNameBlitzFirst'; - END - ELSE - BEGIN - RAISERROR('Table provided for BlitzFirst data: %s does not exist',10,0,@FullOutputTableNameBlitzFirst); - SELECT N'No BlitzFirst data available as the table cannot be found'; - END + UNION ALL + SELECT N'@DatabaseName', + N'NVARCHAR(128)', + N'A database name which is used for filtering results.' -END -ELSE /* Table exists then run the query */ -BEGIN - EXEC sp_executesql @Sql, - N'@StartDate DATETIMEOFFSET(7), - @EndDate DATETIMEOFFSET(7), - @Servername NVARCHAR(128), - @MaxBlitzFirstPriority INT', - @StartDate=@StartDate, - @EndDate=@EndDate, - @Servername=@Servername, - @MaxBlitzFirstPriority = @MaxBlitzFirstPriority; -END + UNION ALL + SELECT N'@StoredProcName', + N'NVARCHAR(128)', + N'Name of stored procedure you want to find plans for.' -/* Blitz WaitStats data */ -SET @Sql = N'SELECT -[ServerName], -[CheckDate], -[wait_type], -[WaitsRank], -[WaitCategory], -[Ignorable], -[ElapsedSeconds], -[wait_time_ms_delta], -[wait_time_minutes_delta], -[wait_time_minutes_per_minute], -[signal_wait_time_ms_delta], -[waiting_tasks_count_delta], -ISNULL((CAST([wait_time_ms_delta] AS DECIMAL(38,2))/NULLIF(CAST([waiting_tasks_count_delta] AS DECIMAL(38,2)),0)),0) AS [wait_time_ms_per_wait] -FROM -( - SELECT - [ServerName], - [CheckDate], - [wait_type], - [WaitCategory], - [Ignorable], - [ElapsedSeconds], - [wait_time_ms_delta], - [wait_time_minutes_delta], - [wait_time_minutes_per_minute], - [signal_wait_time_ms_delta], - [waiting_tasks_count_delta], - ROW_NUMBER() OVER(PARTITION BY [CheckDate] ORDER BY [CheckDate] ASC,[wait_time_ms_delta] DESC) AS [WaitsRank] - FROM '+@FullOutputTableNameWaitStats+N' AS [Waits] - WHERE [ServerName] = @Servername - AND [CheckDate] BETWEEN @StartDate AND @EndDate -) TopWaits -WHERE [WaitsRank] <= @WaitStatsTop -ORDER BY -[CheckDate] ASC, -[wait_time_ms_delta] DESC -OPTION(RECOMPILE, MAXDOP '+CAST(@Maxdop AS NVARCHAR(2))+N');' + UNION ALL + SELECT N'@SlowlySearchPlansFor', + N'NVARCHAR(4000)', + N'String to search for in plan text. % wildcards allowed.' -RAISERROR('Getting wait stats info from %s',0,0,@FullOutputTableNameWaitStats) WITH NOWAIT; + UNION ALL + SELECT N'@BringThePain', + N'BIT', + N'When using @SortOrder = ''all'' and @Top > 10, we require you to set @BringThePain = 1 so you understand that sp_BlitzCache will take a while to run.' -IF (@Debug = 1) -BEGIN - PRINT @Sql; -END + UNION ALL + SELECT N'@QueryFilter', + N'VARCHAR(10)', + N'Filter out stored procedures or statements. The default value is ''ALL''. Allowed values are ''procedures'', ''statements'', ''functions'', or ''all'' (any variation in capitalization is acceptable).' -IF (OBJECT_ID(@FullOutputTableNameWaitStats) IS NULL) -BEGIN - IF (@OutputTableNameWaitStats IS NULL) - BEGIN - RAISERROR('Wait stats data skipped',10,0); - SELECT N'Skipped logged wait stats data as NULL was passed to parameter @OutputTableNameWaitStats'; - END - ELSE - BEGIN - RAISERROR('Table provided for wait stats data: %s does not exist',10,0,@FullOutputTableNameWaitStats); - SELECT N'No wait stats data available as the table cannot be found'; - END -END -ELSE /* Table exists then run the query */ -BEGIN - EXEC sp_executesql @Sql, - N'@StartDate DATETIMEOFFSET(7), - @EndDate DATETIMEOFFSET(7), - @Servername NVARCHAR(128), - @WaitStatsTop TINYINT', - @StartDate=@StartDate, - @EndDate=@EndDate, - @Servername=@Servername, - @WaitStatsTop=@WaitStatsTop; -END + UNION ALL + SELECT N'@Reanalyze', + N'BIT', + N'The default is 0. When set to 0, sp_BlitzCache will re-evalute the plan cache. Set this to 1 to reanalyze existing results' + + UNION ALL + SELECT N'@MinimumExecutionCount', + N'INT', + N'Queries with fewer than this number of executions will be omitted from results.' + + UNION ALL + SELECT N'@Debug', + N'BIT', + N'Setting this to 1 will print dynamic SQL and select data from all tables used.' -/* BlitzFileStats info */ -SET @Sql = N' -SELECT -[ServerName], -[CheckDate], -CASE - WHEN MAX([io_stall_read_ms_average]) > @ReadLatencyThreshold THEN ''Yes'' - WHEN MAX([io_stall_write_ms_average]) > @WriteLatencyThreshold THEN ''Yes'' - ELSE ''No'' -END AS [io_stall_ms_breached], -LEFT([PhysicalName],LEN([PhysicalName])-CHARINDEX(''\'',REVERSE([PhysicalName]))+1) AS [PhysicalPath], -SUM([SizeOnDiskMB]) AS [SizeOnDiskMB], -SUM([SizeOnDiskMBgrowth]) AS [SizeOnDiskMBgrowth], -MAX([io_stall_read_ms]) AS [max_io_stall_read_ms], -MAX([io_stall_read_ms_average]) AS [max_io_stall_read_ms_average], -@ReadLatencyThreshold AS [is_stall_read_ms_threshold], -SUM([num_of_reads]) AS [num_of_reads], -SUM([megabytes_read]) AS [megabytes_read], -MAX([io_stall_write_ms]) AS [max_io_stall_write_ms], -MAX([io_stall_write_ms_average]) AS [max_io_stall_write_ms_average], -@WriteLatencyThreshold AS [io_stall_write_ms_average], -SUM([num_of_writes]) AS [num_of_writes], -SUM([megabytes_written]) AS [megabytes_written] -FROM '+@FullOutputTableNameFileStats+N' -WHERE [ServerName] = @Servername -AND [CheckDate] BETWEEN @StartDate AND @EndDate -' -+CASE - WHEN @Databasename IS NOT NULL THEN N'AND [DatabaseName] IN (N''tempdb'',@Databasename) -' - ELSE N'' -END -+N'GROUP BY -[ServerName], -[CheckDate], -LEFT([PhysicalName],LEN([PhysicalName])-CHARINDEX(''\'',REVERSE([PhysicalName]))+1) -ORDER BY -[CheckDate] ASC -OPTION (RECOMPILE, MAXDOP '+CAST(@Maxdop AS NVARCHAR(2))+N');' + UNION ALL + SELECT N'@MinutesBack', + N'INT', + N'How many minutes back to begin plan cache analysis. If you put in a positive number, we''ll flip it to negative.' -RAISERROR('Getting FileStats info from %s',0,0,@FullOutputTableNameFileStats) WITH NOWAIT; + UNION ALL + SELECT N'@Version', + N'VARCHAR(30)', + N'OUTPUT parameter holding version number.' + + UNION ALL + SELECT N'@VersionDate', + N'DATETIME', + N'OUTPUT parameter holding version date.' -IF (@Debug = 1) -BEGIN - PRINT @Sql; -END + UNION ALL + SELECT N'@VersionCheckMode', + N'BIT', + N'Setting this to 1 will make the procedure stop after setting @Version and @VersionDate.'; -IF (OBJECT_ID(@FullOutputTableNameFileStats) IS NULL) -BEGIN - IF (@OutputTableNameFileStats IS NULL) - BEGIN - RAISERROR('File stats data skipped',10,0); - SELECT N'Skipped logged File stats data as NULL was passed to parameter @OutputTableNameFileStats'; - END - ELSE - BEGIN - RAISERROR('Table provided for FileStats data: %s does not exist',10,0,@FullOutputTableNameFileStats); - SELECT N'No File stats data available as the table cannot be found'; - END -END -ELSE /* Table exists then run the query */ -BEGIN - EXEC sp_executesql @Sql, - N'@StartDate DATETIMEOFFSET(7), - @EndDate DATETIMEOFFSET(7), - @Servername NVARCHAR(128), - @Databasename NVARCHAR(128), - @ReadLatencyThreshold INT, - @WriteLatencyThreshold INT', - @StartDate=@StartDate, - @EndDate=@EndDate, - @Servername=@Servername, - @Databasename = @Databasename, - @ReadLatencyThreshold = @ReadLatencyThreshold, - @WriteLatencyThreshold = @WriteLatencyThreshold; -END -/* Blitz Perfmon stats*/ -SET @Sql = N' -SELECT - [ServerName] - ,[CheckDate] - ,[counter_name] - ,[object_name] - ,[instance_name] - ,[cntr_value] -FROM '+@FullOutputTableNamePerfmonStats+N' -WHERE [ServerName] = @Servername -AND CheckDate BETWEEN @StartDate AND @EndDate -ORDER BY - [CheckDate] ASC, - [counter_name] ASC -OPTION (RECOMPILE, MAXDOP '+CAST(@Maxdop AS NVARCHAR(2))+N');' + /* Column definitions */ + SELECT N'# Executions' AS [Column Name], + N'BIGINT' AS [Data Type], + N'The number of executions of this particular query. This is computed across statements, procedures, and triggers and aggregated by the SQL handle.' AS [Column Description] -RAISERROR('Getting Perfmon info from %s',0,0,@FullOutputTableNamePerfmonStats) WITH NOWAIT; + UNION ALL + SELECT N'Executions / Minute', + N'MONEY', + N'Number of executions per minute - calculated for the life of the current plan. Plan life is the last execution time minus the plan creation time.' -IF (@Debug = 1) -BEGIN - PRINT @Sql; -END + UNION ALL + SELECT N'Execution Weight', + N'MONEY', + N'An arbitrary metric of total "execution-ness". A weight of 2 is "one more" than a weight of 1.' -IF (OBJECT_ID(@FullOutputTableNamePerfmonStats) IS NULL) -BEGIN - IF (@OutputTableNamePerfmonStats IS NULL) - BEGIN - RAISERROR('Perfmon stats data skipped',10,0); - SELECT N'Skipped logged Perfmon stats data as NULL was passed to parameter @OutputTableNamePerfmonStats'; - END - ELSE - BEGIN - RAISERROR('Table provided for Perfmon stats data: %s does not exist',10,0,@FullOutputTableNamePerfmonStats); - SELECT N'No Perfmon data available as the table cannot be found'; - END -END -ELSE /* Table exists then run the query */ -BEGIN - EXEC sp_executesql @Sql, - N'@StartDate DATETIMEOFFSET(7), - @EndDate DATETIMEOFFSET(7), - @Servername NVARCHAR(128)', - @StartDate=@StartDate, - @EndDate=@EndDate, - @Servername=@Servername; -END + UNION ALL + SELECT N'Database', + N'sysname', + N'The name of the database where the plan was encountered. If the database name cannot be determined for some reason, a value of NA will be substituted. A value of 32767 indicates the plan comes from ResourceDB.' -/* Blitz cache data */ -RAISERROR('Sortorder for BlitzCache data: %s',0,0,@BlitzCacheSortorder) WITH NOWAIT; + UNION ALL + SELECT N'Total CPU', + N'BIGINT', + N'Total CPU time, reported in milliseconds, that was consumed by all executions of this query since the last compilation.' -/* Set intial CTE */ -SET @Sql = N'WITH CheckDates AS ( -SELECT DISTINCT CheckDate -FROM ' -+@FullOutputTableNameBlitzCache -+N' -WHERE [ServerName] = @Servername -AND [CheckDate] BETWEEN @StartDate AND @EndDate' -+@NewLine -+CASE - WHEN @Databasename IS NOT NULL THEN N'AND [DatabaseName] = @Databasename'+@NewLine - ELSE N'' -END -+N')' -; + UNION ALL + SELECT N'Avg CPU', + N'BIGINT', + N'Average CPU time, reported in milliseconds, consumed by each execution of this query since the last compilation.' -SET @Sql += @NewLine; + UNION ALL + SELECT N'CPU Weight', + N'MONEY', + N'An arbitrary metric of total "CPU-ness". A weight of 2 is "one more" than a weight of 1.' -/* Append additional CTEs based on sortorder */ -SET @Sql += ( -SELECT CAST(N',' AS NVARCHAR(MAX)) -+[SortOptions].[Aliasname]+N' AS ( -SELECT - [ServerName] - ,'+[SortOptions].[Aliasname]+N'.[CheckDate] - ,[Sortorder] - ,[TimeFrameRank] - ,ROW_NUMBER() OVER(ORDER BY ['+[SortOptions].[Columnname]+N'] DESC) AS [OverallRank] - ,'+[SortOptions].[Aliasname]+N'.[QueryType] - ,'+[SortOptions].[Aliasname]+N'.[QueryText] - ,'+[SortOptions].[Aliasname]+N'.[DatabaseName] - ,'+[SortOptions].[Aliasname]+N'.[AverageCPU] - ,'+[SortOptions].[Aliasname]+N'.[TotalCPU] - ,'+[SortOptions].[Aliasname]+N'.[PercentCPUByType] - ,'+[SortOptions].[Aliasname]+N'.[AverageDuration] - ,'+[SortOptions].[Aliasname]+N'.[TotalDuration] - ,'+[SortOptions].[Aliasname]+N'.[PercentDurationByType] - ,'+[SortOptions].[Aliasname]+N'.[AverageReads] - ,'+[SortOptions].[Aliasname]+N'.[TotalReads] - ,'+[SortOptions].[Aliasname]+N'.[PercentReadsByType] - ,'+[SortOptions].[Aliasname]+N'.[AverageWrites] - ,'+[SortOptions].[Aliasname]+N'.[TotalWrites] - ,'+[SortOptions].[Aliasname]+N'.[PercentWritesByType] - ,'+[SortOptions].[Aliasname]+N'.[ExecutionCount] - ,'+[SortOptions].[Aliasname]+N'.[ExecutionWeight] - ,'+[SortOptions].[Aliasname]+N'.[PercentExecutionsByType] - ,'+[SortOptions].[Aliasname]+N'.[ExecutionsPerMinute] - ,'+[SortOptions].[Aliasname]+N'.[PlanCreationTime] - ,'+[SortOptions].[Aliasname]+N'.[PlanCreationTimeHours] - ,'+[SortOptions].[Aliasname]+N'.[LastExecutionTime] - ,'+[SortOptions].[Aliasname]+N'.[PlanHandle] - ,'+[SortOptions].[Aliasname]+N'.[SqlHandle] - ,'+[SortOptions].[Aliasname]+N'.[SQL Handle More Info] - ,'+[SortOptions].[Aliasname]+N'.[QueryHash] - ,'+[SortOptions].[Aliasname]+N'.[Query Hash More Info] - ,'+[SortOptions].[Aliasname]+N'.[QueryPlanHash] - ,'+[SortOptions].[Aliasname]+N'.[StatementStartOffset] - ,'+[SortOptions].[Aliasname]+N'.[StatementEndOffset] - ,'+[SortOptions].[Aliasname]+N'.[MinReturnedRows] - ,'+[SortOptions].[Aliasname]+N'.[MaxReturnedRows] - ,'+[SortOptions].[Aliasname]+N'.[AverageReturnedRows] - ,'+[SortOptions].[Aliasname]+N'.[TotalReturnedRows] - ,'+[SortOptions].[Aliasname]+N'.[QueryPlan] - ,'+[SortOptions].[Aliasname]+N'.[NumberOfPlans] - ,'+[SortOptions].[Aliasname]+N'.[NumberOfDistinctPlans] - ,'+[SortOptions].[Aliasname]+N'.[MinGrantKB] - ,'+[SortOptions].[Aliasname]+N'.[MaxGrantKB] - ,'+[SortOptions].[Aliasname]+N'.[MinUsedGrantKB] - ,'+[SortOptions].[Aliasname]+N'.[MaxUsedGrantKB] - ,'+[SortOptions].[Aliasname]+N'.[PercentMemoryGrantUsed] - ,'+[SortOptions].[Aliasname]+N'.[AvgMaxMemoryGrant] - ,'+[SortOptions].[Aliasname]+N'.[MinSpills] - ,'+[SortOptions].[Aliasname]+N'.[MaxSpills] - ,'+[SortOptions].[Aliasname]+N'.[TotalSpills] - ,'+[SortOptions].[Aliasname]+N'.[AvgSpills] - ,'+[SortOptions].[Aliasname]+N'.[QueryPlanCost] -FROM CheckDates -CROSS APPLY ( - SELECT TOP (5) - [ServerName] - ,'+[SortOptions].[Aliasname]+N'.[CheckDate] - ,'+QUOTENAME(UPPER([SortOptions].[Sortorder]),N'''')+N' AS [Sortorder] - ,ROW_NUMBER() OVER(ORDER BY ['+[SortOptions].[Columnname]+N'] DESC) AS [TimeFrameRank] - ,'+[SortOptions].[Aliasname]+N'.[QueryType] - ,'+[SortOptions].[Aliasname]+N'.[QueryText] - ,'+[SortOptions].[Aliasname]+N'.[DatabaseName] - ,'+[SortOptions].[Aliasname]+N'.[AverageCPU] - ,'+[SortOptions].[Aliasname]+N'.[TotalCPU] - ,'+[SortOptions].[Aliasname]+N'.[PercentCPUByType] - ,'+[SortOptions].[Aliasname]+N'.[AverageDuration] - ,'+[SortOptions].[Aliasname]+N'.[TotalDuration] - ,'+[SortOptions].[Aliasname]+N'.[PercentDurationByType] - ,'+[SortOptions].[Aliasname]+N'.[AverageReads] - ,'+[SortOptions].[Aliasname]+N'.[TotalReads] - ,'+[SortOptions].[Aliasname]+N'.[PercentReadsByType] - ,'+[SortOptions].[Aliasname]+N'.[AverageWrites] - ,'+[SortOptions].[Aliasname]+N'.[TotalWrites] - ,'+[SortOptions].[Aliasname]+N'.[PercentWritesByType] - ,'+[SortOptions].[Aliasname]+N'.[ExecutionCount] - ,'+[SortOptions].[Aliasname]+N'.[ExecutionWeight] - ,'+[SortOptions].[Aliasname]+N'.[PercentExecutionsByType] - ,'+[SortOptions].[Aliasname]+N'.[ExecutionsPerMinute] - ,'+[SortOptions].[Aliasname]+N'.[PlanCreationTime] - ,'+[SortOptions].[Aliasname]+N'.[PlanCreationTimeHours] - ,'+[SortOptions].[Aliasname]+N'.[LastExecutionTime] - ,'+[SortOptions].[Aliasname]+N'.[PlanHandle] - ,'+[SortOptions].[Aliasname]+N'.[SqlHandle] - ,'+[SortOptions].[Aliasname]+N'.[SQL Handle More Info] - ,'+[SortOptions].[Aliasname]+N'.[QueryHash] - ,'+[SortOptions].[Aliasname]+N'.[Query Hash More Info] - ,'+[SortOptions].[Aliasname]+N'.[QueryPlanHash] - ,'+[SortOptions].[Aliasname]+N'.[StatementStartOffset] - ,'+[SortOptions].[Aliasname]+N'.[StatementEndOffset] - ,'+[SortOptions].[Aliasname]+N'.[MinReturnedRows] - ,'+[SortOptions].[Aliasname]+N'.[MaxReturnedRows] - ,'+[SortOptions].[Aliasname]+N'.[AverageReturnedRows] - ,'+[SortOptions].[Aliasname]+N'.[TotalReturnedRows] - ,'+[SortOptions].[Aliasname]+N'.[QueryPlan] - ,'+[SortOptions].[Aliasname]+N'.[NumberOfPlans] - ,'+[SortOptions].[Aliasname]+N'.[NumberOfDistinctPlans] - ,'+[SortOptions].[Aliasname]+N'.[MinGrantKB] - ,'+[SortOptions].[Aliasname]+N'.[MaxGrantKB] - ,'+[SortOptions].[Aliasname]+N'.[MinUsedGrantKB] - ,'+[SortOptions].[Aliasname]+N'.[MaxUsedGrantKB] - ,'+[SortOptions].[Aliasname]+N'.[PercentMemoryGrantUsed] - ,'+[SortOptions].[Aliasname]+N'.[AvgMaxMemoryGrant] - ,'+[SortOptions].[Aliasname]+N'.[MinSpills] - ,'+[SortOptions].[Aliasname]+N'.[MaxSpills] - ,'+[SortOptions].[Aliasname]+N'.[TotalSpills] - ,'+[SortOptions].[Aliasname]+N'.[AvgSpills] - ,'+[SortOptions].[Aliasname]+N'.[QueryPlanCost] - FROM '+@FullOutputTableNameBlitzCache+N' AS '+[SortOptions].[Aliasname]+N' - WHERE [ServerName] = @Servername - AND [CheckDate] BETWEEN @StartDate AND @EndDate - AND ['+[SortOptions].[Aliasname]+N'].[CheckDate] = [CheckDates].[CheckDate]' - +@NewLine - +CASE - WHEN @Databasename IS NOT NULL THEN N'AND ['+[SortOptions].[Aliasname]+N'].[DatabaseName] = @Databasename'+@NewLine - ELSE N'' - END - +CASE - WHEN [Sortorder] = N'cpu' THEN N'AND [TotalCPU] > 0' - WHEN [Sortorder] = N'reads' THEN N'AND [TotalReads] > 0' - WHEN [Sortorder] = N'writes' THEN N'AND [TotalWrites] > 0' - WHEN [Sortorder] = N'duration' THEN N'AND [TotalDuration] > 0' - WHEN [Sortorder] = N'executions' THEN N'AND [ExecutionCount] > 0' - WHEN [Sortorder] = N'memory grant' THEN N'AND [MaxGrantKB] > 0' - WHEN [Sortorder] = N'spills' THEN N'AND [MaxSpills] > 0' - ELSE N'' - END - +N' - ORDER BY ['+[SortOptions].[Columnname]+N'] DESC) '+[SortOptions].[Aliasname]+N' -)' -FROM (VALUES - (N'cpu',N'TopCPU',N'TotalCPU'), - (N'reads',N'TopReads',N'TotalReads'), - (N'writes',N'TopWrites',N'TotalWrites'), - (N'duration',N'TopDuration',N'TotalDuration'), - (N'executions',N'TopExecutions',N'ExecutionCount'), - (N'memory grant',N'TopMemoryGrants',N'MaxGrantKB'), - (N'spills',N'TopSpills',N'MaxSpills') - ) SortOptions(Sortorder,Aliasname,Columnname) -WHERE - CASE /* for spills and memory grant sorts make sure the underlying columns exist in the DMV otherwise do not include them */ - WHEN (@IncludeMemoryGrants = 0 OR @IncludeMemoryGrants IS NULL) AND ([SortOptions].[Sortorder] = N'memory grant' OR [SortOptions].[Sortorder] = N'all') THEN NULL - WHEN (@IncludeSpills = 0 OR @IncludeSpills IS NULL) AND ([SortOptions].[Sortorder] = N'spills' OR [SortOptions].[Sortorder] = N'all') THEN NULL - ELSE [SortOptions].[Sortorder] - END = ISNULL(NULLIF(@BlitzCacheSortorder,N'all'),[SortOptions].[Sortorder]) -FOR XML PATH(N''), TYPE).value(N'.[1]', N'NVARCHAR(MAX)'); + UNION ALL + SELECT N'Total Duration', + N'BIGINT', + N'Total elapsed time, reported in milliseconds, consumed by all executions of this query since last compilation.' -SET @Sql += @NewLine; + UNION ALL + SELECT N'Avg Duration', + N'BIGINT', + N'Average elapsed time, reported in milliseconds, consumed by each execution of this query since the last compilation.' -/* Build the select statements to return the data after CTE declarations */ -SET @Sql += ( -SELECT STUFF(( -SELECT @NewLine -+N'UNION ALL' -+@NewLine -+N'SELECT * -FROM '+[SortOptions].[Aliasname] -FROM (VALUES - (N'cpu',N'TopCPU',N'TotalCPU'), - (N'reads',N'TopReads',N'TotalReads'), - (N'writes',N'TopWrites',N'TotalWrites'), - (N'duration',N'TopDuration',N'TotalDuration'), - (N'executions',N'TopExecutions',N'ExecutionCount'), - (N'memory grant',N'TopMemoryGrants',N'MaxGrantKB'), - (N'spills',N'TopSpills',N'MaxSpills') - ) SortOptions(Sortorder,Aliasname,Columnname) -WHERE - CASE /* for spills and memory grant sorts make sure the underlying columns exist in the DMV otherwise do not include them */ - WHEN (@IncludeMemoryGrants = 0 OR @IncludeMemoryGrants IS NULL) AND ([SortOptions].[Sortorder] = N'memory grant' OR [SortOptions].[Sortorder] = N'all') THEN NULL - WHEN (@IncludeSpills = 0 OR @IncludeSpills IS NULL) AND ([SortOptions].[Sortorder] = N'spills' OR [SortOptions].[Sortorder] = N'all') THEN NULL - ELSE [SortOptions].[Sortorder] - END = ISNULL(NULLIF(@BlitzCacheSortorder,N'all'),[SortOptions].[Sortorder]) -FOR XML PATH(N''), TYPE).value(N'.[1]', N'NVARCHAR(MAX)'),1,11,N'') -); + UNION ALL + SELECT N'Duration Weight', + N'MONEY', + N'An arbitrary metric of total "Duration-ness". A weight of 2 is "one more" than a weight of 1.' -/* Append Order By */ -SET @Sql += @NewLine -+N'ORDER BY - [Sortorder] ASC, - [CheckDate] ASC, - [TimeFrameRank] ASC'; + UNION ALL + SELECT N'Total Reads', + N'BIGINT', + N'Total logical reads performed by this query since last compilation.' -/* Append OPTION(RECOMPILE, MAXDOP) to complete the statement */ -SET @Sql += @NewLine -+N'OPTION(RECOMPILE, MAXDOP '+CAST(@Maxdop AS NVARCHAR(2))+N');'; + UNION ALL + SELECT N'Average Reads', + N'BIGINT', + N'Average logical reads performed by each execution of this query since the last compilation.' -RAISERROR('Getting BlitzCache info from %s',0,0,@FullOutputTableNameBlitzCache) WITH NOWAIT; + UNION ALL + SELECT N'Read Weight', + N'MONEY', + N'An arbitrary metric of "Read-ness". A weight of 2 is "one more" than a weight of 1.' -IF (@Debug = 1) -BEGIN - PRINT SUBSTRING(@Sql, 0, 4000); - PRINT SUBSTRING(@Sql, 4000, 8000); - PRINT SUBSTRING(@Sql, 8000, 12000); - PRINT SUBSTRING(@Sql, 12000, 16000); - PRINT SUBSTRING(@Sql, 16000, 20000); - PRINT SUBSTRING(@Sql, 20000, 24000); - PRINT SUBSTRING(@Sql, 24000, 28000); -END + UNION ALL + SELECT N'Total Writes', + N'BIGINT', + N'Total logical writes performed by this query since last compilation.' -IF (OBJECT_ID(@FullOutputTableNameBlitzCache) IS NULL) -BEGIN - IF (@OutputTableNameBlitzCache IS NULL) - BEGIN - RAISERROR('BlitzCache data skipped',10,0); - SELECT N'Skipped logged BlitzCache data as NULL was passed to parameter @OutputTableNameBlitzCache'; - END - ELSE - BEGIN - RAISERROR('Table provided for BlitzCache data: %s does not exist',10,0,@FullOutputTableNameBlitzCache); - SELECT N'No BlitzCache data available as the table cannot be found'; - END -END -ELSE /* Table exists then run the query */ -BEGIN - EXEC sp_executesql @Sql, - N'@Servername NVARCHAR(128), - @Databasename NVARCHAR(128), - @BlitzCacheSortorder NVARCHAR(20), - @StartDate DATETIMEOFFSET(7), - @EndDate DATETIMEOFFSET(7)', - @Servername = @Servername, - @Databasename = @Databasename, - @BlitzCacheSortorder = @BlitzCacheSortorder, - @StartDate = @StartDate, - @EndDate = @EndDate; -END + UNION ALL + SELECT N'Average Writes', + N'BIGINT', + N'Average logical writes performed by each execution this query since last compilation.' + UNION ALL + SELECT N'Write Weight', + N'MONEY', + N'An arbitrary metric of "Write-ness". A weight of 2 is "one more" than a weight of 1.' + UNION ALL + SELECT N'Query Type', + N'NVARCHAR(258)', + N'The type of query being examined. This can be "Procedure", "Statement", or "Trigger".' -/* BlitzWho data */ -SET @Sql = N' -SELECT [ServerName] - ,[CheckDate] - ,[elapsed_time] - ,[session_id] - ,[database_name] - ,[query_text_snippet] - ,[query_plan] - ,[live_query_plan] - ,[query_cost] - ,[status] - ,[wait_info] - ,[top_session_waits] - ,[blocking_session_id] - ,[open_transaction_count] - ,[is_implicit_transaction] - ,[nt_domain] - ,[host_name] - ,[login_name] - ,[nt_user_name] - ,[program_name] - ,[fix_parameter_sniffing] - ,[client_interface_name] - ,[login_time] - ,[start_time] - ,[request_time] - ,[request_cpu_time] - ,[degree_of_parallelism] - ,[request_logical_reads] - ,[Logical_Reads_MB] - ,[request_writes] - ,[Logical_Writes_MB] - ,[request_physical_reads] - ,[Physical_reads_MB] - ,[session_cpu] - ,[session_logical_reads] - ,[session_logical_reads_MB] - ,[session_physical_reads] - ,[session_physical_reads_MB] - ,[session_writes] - ,[session_writes_MB] - ,[tempdb_allocations_mb] - ,[memory_usage] - ,[estimated_completion_time] - ,[percent_complete] - ,[deadlock_priority] - ,[transaction_isolation_level] - ,[last_dop] - ,[min_dop] - ,[max_dop] - ,[last_grant_kb] - ,[min_grant_kb] - ,[max_grant_kb] - ,[last_used_grant_kb] - ,[min_used_grant_kb] - ,[max_used_grant_kb] - ,[last_ideal_grant_kb] - ,[min_ideal_grant_kb] - ,[max_ideal_grant_kb] - ,[last_reserved_threads] - ,[min_reserved_threads] - ,[max_reserved_threads] - ,[last_used_threads] - ,[min_used_threads] - ,[max_used_threads] - ,[grant_time] - ,[requested_memory_kb] - ,[grant_memory_kb] - ,[is_request_granted] - ,[required_memory_kb] - ,[query_memory_grant_used_memory_kb] - ,[ideal_memory_kb] - ,[is_small] - ,[timeout_sec] - ,[resource_semaphore_id] - ,[wait_order] - ,[wait_time_ms] - ,[next_candidate_for_memory_grant] - ,[target_memory_kb] - ,[max_target_memory_kb] - ,[total_memory_kb] - ,[available_memory_kb] - ,[granted_memory_kb] - ,[query_resource_semaphore_used_memory_kb] - ,[grantee_count] - ,[waiter_count] - ,[timeout_error_count] - ,[forced_grant_count] - ,[workload_group_name] - ,[resource_pool_name] - ,[context_info] - ,[query_hash] - ,[query_plan_hash] - ,[sql_handle] - ,[plan_handle] - ,[statement_start_offset] - ,[statement_end_offset] - FROM '+@FullOutputTableNameBlitzWho+N' - WHERE [ServerName] = @Servername - AND ([CheckDate] BETWEEN @StartDate AND @EndDate OR [start_time] BETWEEN CAST(@StartDate AS DATETIME) AND CAST(@EndDate AS DATETIME)) - ' - +CASE - WHEN @Databasename IS NOT NULL THEN N'AND [database_name] = @Databasename - ' - ELSE N'' - END -+N'ORDER BY [CheckDate] ASC - OPTION (RECOMPILE, MAXDOP '+CAST(@Maxdop AS NVARCHAR(2))+N');'; - -RAISERROR('Getting BlitzWho info from %s',0,0,@FullOutputTableNameBlitzWho) WITH NOWAIT; + UNION ALL + SELECT N'Query Text', + N'NVARCHAR(4000)', + N'The text of the query. This may be truncated by either SQL Server or by sp_BlitzCache(tm) for display purposes.' -IF (@Debug = 1) -BEGIN - PRINT @Sql; -END + UNION ALL + SELECT N'% Executions (Type)', + N'MONEY', + N'Percent of executions relative to the type of query - e.g. 17.2% of all stored procedure executions.' -IF (OBJECT_ID(@FullOutputTableNameBlitzWho) IS NULL) -BEGIN - IF (@OutputTableNameBlitzWho IS NULL) - BEGIN - RAISERROR('BlitzWho data skipped',10,0); - SELECT N'Skipped logged BlitzWho data as NULL was passed to parameter @OutputTableNameBlitzWho'; - END - ELSE - BEGIN - RAISERROR('Table provided for BlitzWho data: %s does not exist',10,0,@FullOutputTableNameBlitzWho); - SELECT N'No BlitzWho data available as the table cannot be found'; - END -END -ELSE -BEGIN - EXEC sp_executesql @Sql, - N'@StartDate DATETIMEOFFSET(7), - @EndDate DATETIMEOFFSET(7), - @Servername NVARCHAR(128), - @Databasename NVARCHAR(128)', - @StartDate=@StartDate, - @EndDate=@EndDate, - @Servername=@Servername, - @Databasename = @Databasename; -END + UNION ALL + SELECT N'% CPU (Type)', + N'MONEY', + N'Percent of CPU time consumed by this query for a given type of query - e.g. 22% of CPU of all stored procedures executed.' + UNION ALL + SELECT N'% Duration (Type)', + N'MONEY', + N'Percent of elapsed time consumed by this query for a given type of query - e.g. 12% of all statements executed.' -GO -IF OBJECT_ID('dbo.sp_BlitzBackups') IS NULL - EXEC ('CREATE PROCEDURE dbo.sp_BlitzBackups AS RETURN 0;'); -GO -ALTER PROCEDURE [dbo].[sp_BlitzBackups] - @Help TINYINT = 0 , - @HoursBack INT = 168, - @MSDBName NVARCHAR(256) = 'msdb', - @AGName NVARCHAR(256) = NULL, - @RestoreSpeedFullMBps INT = NULL, - @RestoreSpeedDiffMBps INT = NULL, - @RestoreSpeedLogMBps INT = NULL, - @Debug TINYINT = 0, - @PushBackupHistoryToListener BIT = 0, - @WriteBackupsToListenerName NVARCHAR(256) = NULL, - @WriteBackupsToDatabaseName NVARCHAR(256) = NULL, - @WriteBackupsLastHours INT = 168, - @Version VARCHAR(30) = NULL OUTPUT, - @VersionDate DATETIME = NULL OUTPUT, - @VersionCheckMode BIT = 0 -WITH RECOMPILE -AS - BEGIN - SET NOCOUNT ON; - SET STATISTICS XML OFF; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - - SELECT @Version = '8.19', @VersionDate = '20240222'; - - IF(@VersionCheckMode = 1) - BEGIN - RETURN; - END; + UNION ALL + SELECT N'% Reads (Type)', + N'MONEY', + N'Percent of reads consumed by this query for a given type of query - e.g. 34.2% of all stored procedures executed.' - IF @Help = 1 PRINT ' - /* - sp_BlitzBackups from http://FirstResponderKit.org - - This script checks your backups to see how much data you might lose when - this server fails, and how long it might take to recover. + UNION ALL + SELECT N'% Writes (Type)', + N'MONEY', + N'Percent of writes performed by this query for a given type of query - e.g. 43.2% of all statements executed.' - To learn more, visit http://FirstResponderKit.org where you can download new - versions for free, watch training videos on how it works, get more info on - the findings, contribute your own code, and more. + UNION ALL + SELECT N'Total Rows', + N'BIGINT', + N'Total number of rows returned for all executions of this query. This only applies to query level stats, not stored procedures or triggers.' - Known limitations of this version: - - Only Microsoft-supported versions of SQL Server. Sorry, 2005 and 2000. + UNION ALL + SELECT N'Average Rows', + N'MONEY', + N'Average number of rows returned by each execution of the query.' - Unknown limitations of this version: - - None. (If we knew them, they would be known. Duh.) + UNION ALL + SELECT N'Min Rows', + N'BIGINT', + N'The minimum number of rows returned by any execution of this query.' - Changes - for the full list of improvements and fixes in this version, see: - https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/ + UNION ALL + SELECT N'Max Rows', + N'BIGINT', + N'The maximum number of rows returned by any execution of this query.' + UNION ALL + SELECT N'MinGrantKB', + N'BIGINT', + N'The minimum memory grant the query received in kb.' - Parameter explanations: + UNION ALL + SELECT N'MaxGrantKB', + N'BIGINT', + N'The maximum memory grant the query received in kb.' - @HoursBack INT = 168 How many hours of history to examine, back from now. - You can check just the last 24 hours of backups, for example. - @MSDBName NVARCHAR(255) You can restore MSDB from different servers and check them - centrally. Also useful if you create a DBA utility database - and merge data from several servers in an AG into one DB. - @RestoreSpeedFullMBps INT By default, we use the backup speed from MSDB to guesstimate - how fast your restores will go. If you have done performance - tuning and testing of your backups (or if they horribly go even - slower in your DR environment, and you want to account for - that), then you can pass in different numbers here. - @RestoreSpeedDiffMBps INT See above. - @RestoreSpeedLogMBps INT See above. + UNION ALL + SELECT N'MinUsedGrantKB', + N'BIGINT', + N'The minimum used memory grant the query received in kb.' - For more documentation: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/ + UNION ALL + SELECT N'MaxUsedGrantKB', + N'BIGINT', + N'The maximum used memory grant the query received in kb.' - MIT License - - Copyright (c) Brent Ozar Unlimited + UNION ALL + SELECT N'MinSpills', + N'BIGINT', + N'The minimum amount this query has spilled to tempdb in 8k pages.' - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: + UNION ALL + SELECT N'MaxSpills', + N'BIGINT', + N'The maximum amount this query has spilled to tempdb in 8k pages.' - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. + UNION ALL + SELECT N'TotalSpills', + N'BIGINT', + N'The total amount this query has spilled to tempdb in 8k pages.' - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE. + UNION ALL + SELECT N'AvgSpills', + N'BIGINT', + N'The average amount this query has spilled to tempdb in 8k pages.' + UNION ALL + SELECT N'PercentMemoryGrantUsed', + N'MONEY', + N'Result of dividing the maximum grant used by the minimum granted.' + UNION ALL + SELECT N'AvgMaxMemoryGrant', + N'MONEY', + N'The average maximum memory grant for a query.' + UNION ALL + SELECT N'# Plans', + N'INT', + N'The total number of execution plans found that match a given query.' - */'; -ELSE -BEGIN -DECLARE @StringToExecute NVARCHAR(MAX) = N'', - @InnerStringToExecute NVARCHAR(MAX) = N'', - @ProductVersion NVARCHAR(128), - @ProductVersionMajor DECIMAL(10, 2), - @ProductVersionMinor DECIMAL(10, 2), - @StartTime DATETIME2, @ResultText NVARCHAR(MAX), - @crlf NVARCHAR(2), - @MoreInfoHeader NVARCHAR(100), - @MoreInfoFooter NVARCHAR(100); + UNION ALL + SELECT N'# Distinct Plans', + N'INT', + N'The number of distinct execution plans that match a given query. ' + + NCHAR(13) + NCHAR(10) + + N'This may be caused by running the same query across multiple databases or because of a lack of proper parameterization in the database.' -IF @HoursBack > 0 - SET @HoursBack = @HoursBack * -1; + UNION ALL + SELECT N'Created At', + N'DATETIME', + N'Time that the execution plan was last compiled.' -IF @WriteBackupsLastHours > 0 - SET @WriteBackupsLastHours = @WriteBackupsLastHours * -1; + UNION ALL + SELECT N'Last Execution', + N'DATETIME', + N'The last time that this query was executed.' -SELECT @crlf = NCHAR(13) + NCHAR(10), - @StartTime = DATEADD(hh, @HoursBack, GETDATE()), - @MoreInfoHeader = N''; + UNION ALL + SELECT N'Query Plan', + N'XML', + N'The query plan. Click to display a graphical plan or, if you need to patch SSMS, a pile of XML.' -SET @ProductVersion = CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)); -SELECT @ProductVersionMajor = SUBSTRING(@ProductVersion, 1, CHARINDEX('.', @ProductVersion) + 1), - @ProductVersionMinor = PARSENAME(CONVERT(VARCHAR(32), @ProductVersion), 2); - -CREATE TABLE #Backups -( - id INT IDENTITY(1, 1), - database_name NVARCHAR(128), - database_guid UNIQUEIDENTIFIER, - RPOWorstCaseMinutes DECIMAL(18, 1), - RTOWorstCaseMinutes DECIMAL(18, 1), - RPOWorstCaseBackupSetID INT, - RPOWorstCaseBackupSetFinishTime DATETIME, - RPOWorstCaseBackupSetIDPrior INT, - RPOWorstCaseBackupSetPriorFinishTime DATETIME, - RPOWorstCaseMoreInfoQuery XML, - RTOWorstCaseBackupFileSizeMB DECIMAL(18, 2), - RTOWorstCaseMoreInfoQuery XML, - FullMBpsAvg DECIMAL(18, 2), - FullMBpsMin DECIMAL(18, 2), - FullMBpsMax DECIMAL(18, 2), - FullSizeMBAvg DECIMAL(18, 2), - FullSizeMBMin DECIMAL(18, 2), - FullSizeMBMax DECIMAL(18, 2), - FullCompressedSizeMBAvg DECIMAL(18, 2), - FullCompressedSizeMBMin DECIMAL(18, 2), - FullCompressedSizeMBMax DECIMAL(18, 2), - DiffMBpsAvg DECIMAL(18, 2), - DiffMBpsMin DECIMAL(18, 2), - DiffMBpsMax DECIMAL(18, 2), - DiffSizeMBAvg DECIMAL(18, 2), - DiffSizeMBMin DECIMAL(18, 2), - DiffSizeMBMax DECIMAL(18, 2), - DiffCompressedSizeMBAvg DECIMAL(18, 2), - DiffCompressedSizeMBMin DECIMAL(18, 2), - DiffCompressedSizeMBMax DECIMAL(18, 2), - LogMBpsAvg DECIMAL(18, 2), - LogMBpsMin DECIMAL(18, 2), - LogMBpsMax DECIMAL(18, 2), - LogSizeMBAvg DECIMAL(18, 2), - LogSizeMBMin DECIMAL(18, 2), - LogSizeMBMax DECIMAL(18, 2), - LogCompressedSizeMBAvg DECIMAL(18, 2), - LogCompressedSizeMBMin DECIMAL(18, 2), - LogCompressedSizeMBMax DECIMAL(18, 2) -); - -CREATE TABLE #RTORecoveryPoints -( - id INT IDENTITY(1, 1), - database_name NVARCHAR(128), - database_guid UNIQUEIDENTIFIER, - rto_worst_case_size_mb AS - ( COALESCE(log_file_size_mb, 0) + COALESCE(diff_file_size_mb, 0) + COALESCE(full_file_size_mb, 0)), - rto_worst_case_time_seconds AS - ( COALESCE(log_time_seconds, 0) + COALESCE(diff_time_seconds, 0) + COALESCE(full_time_seconds, 0)), - full_backup_set_id INT, - full_last_lsn NUMERIC(25, 0), - full_backup_set_uuid UNIQUEIDENTIFIER, - full_time_seconds BIGINT, - full_file_size_mb DECIMAL(18, 2), - diff_backup_set_id INT, - diff_last_lsn NUMERIC(25, 0), - diff_time_seconds BIGINT, - diff_file_size_mb DECIMAL(18, 2), - log_backup_set_id INT, - log_last_lsn NUMERIC(25, 0), - log_time_seconds BIGINT, - log_file_size_mb DECIMAL(18, 2), - log_backups INT -); + UNION ALL + SELECT N'Plan Handle', + N'VARBINARY(64)', + N'An arbitrary identifier referring to the compiled plan this query is a part of.' -CREATE TABLE #Recoverability - ( - Id INT IDENTITY , - DatabaseName NVARCHAR(128), - DatabaseGUID UNIQUEIDENTIFIER, - LastBackupRecoveryModel NVARCHAR(60), - FirstFullBackupSizeMB DECIMAL (18,2), - FirstFullBackupDate DATETIME, - LastFullBackupSizeMB DECIMAL (18,2), - LastFullBackupDate DATETIME, - AvgFullBackupThroughputMB DECIMAL (18,2), - AvgFullBackupDurationSeconds INT, - AvgDiffBackupThroughputMB DECIMAL (18,2), - AvgDiffBackupDurationSeconds INT, - AvgLogBackupThroughputMB DECIMAL (18,2), - AvgLogBackupDurationSeconds INT, - AvgFullSizeMB DECIMAL (18,2), - AvgDiffSizeMB DECIMAL (18,2), - AvgLogSizeMB DECIMAL (18,2), - IsBigDiff AS CASE WHEN (AvgFullSizeMB > 10240. AND ((AvgDiffSizeMB * 100.) / AvgFullSizeMB >= 40.)) THEN 1 ELSE 0 END, - IsBigLog AS CASE WHEN (AvgFullSizeMB > 10240. AND ((AvgLogSizeMB * 100.) / AvgFullSizeMB >= 20.)) THEN 1 ELSE 0 END - ); + UNION ALL + SELECT N'SQL Handle', + N'VARBINARY(64)', + N'An arbitrary identifier referring to a batch or stored procedure that this query is a part of.' -CREATE TABLE #Trending -( - DatabaseName NVARCHAR(128), - DatabaseGUID UNIQUEIDENTIFIER, - [0] DECIMAL(18, 2), - [-1] DECIMAL(18, 2), - [-2] DECIMAL(18, 2), - [-3] DECIMAL(18, 2), - [-4] DECIMAL(18, 2), - [-5] DECIMAL(18, 2), - [-6] DECIMAL(18, 2), - [-7] DECIMAL(18, 2), - [-8] DECIMAL(18, 2), - [-9] DECIMAL(18, 2), - [-10] DECIMAL(18, 2), - [-11] DECIMAL(18, 2), - [-12] DECIMAL(18, 2) -); + UNION ALL + SELECT N'Query Hash', + N'BINARY(8)', + N'A hash of the query. Queries with the same query hash have similar logic but only differ by literal values or database.' + UNION ALL + SELECT N'Warnings', + N'VARCHAR(MAX)', + N'A list of individual warnings generated by this query.' ; -CREATE TABLE #Warnings -( - Id INT IDENTITY(1, 1) PRIMARY KEY CLUSTERED, - CheckId INT, - Priority INT, - DatabaseName VARCHAR(128), - Finding VARCHAR(256), - Warning VARCHAR(8000) -); -IF NOT EXISTS(SELECT * FROM sys.databases WHERE name = @MSDBName) - BEGIN - RAISERROR('@MSDBName was specified, but the database does not exist.', 16, 1) WITH NOWAIT; - RETURN; - END + + /* Configuration table description */ + SELECT N'Frequent Execution Threshold' AS [Configuration Parameter] , + N'100' AS [Default Value] , + N'Executions / Minute' AS [Unit of Measure] , + N'Executions / Minute before a "Frequent Execution Threshold" warning is triggered.' AS [Description] -IF @PushBackupHistoryToListener = 1 -GOTO PushBackupHistoryToListener + UNION ALL + SELECT N'Parameter Sniffing Variance Percent' , + N'30' , + N'Percent' , + N'Variance required between min/max values and average values before a "Parameter Sniffing" warning is triggered. Applies to worker time and returned rows.' + UNION ALL + SELECT N'Parameter Sniffing IO Threshold' , + N'100,000' , + N'Logical reads' , + N'Minimum number of average logical reads before parameter sniffing checks are evaluated.' - RAISERROR('Inserting to #Backups', 0, 1) WITH NOWAIT; + UNION ALL + SELECT N'Cost Threshold for Parallelism Warning' AS [Configuration Parameter] , + N'10' , + N'Percent' , + N'Trigger a "Nearly Parallel" warning when a query''s cost is within X percent of the cost threshold for parallelism.' - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; + UNION ALL + SELECT N'Long Running Query Warning' AS [Configuration Parameter] , + N'300' , + N'Seconds' , + N'Triggers a "Long Running Query Warning" when average duration, max CPU time, or max clock time is higher than this number.' - SET @StringToExecute += N'WITH Backups AS (SELECT bs.database_name, bs.database_guid, bs.type AS backup_type ' + @crlf - + ' , MBpsAvg = CAST(AVG(( bs.backup_size / ( CASE WHEN DATEDIFF(ss, bs.backup_start_date, bs.backup_finish_date) = 0 THEN 1 ELSE DATEDIFF(ss, bs.backup_start_date, bs.backup_finish_date) END ) / 1048576 )) AS INT) ' + @crlf - + ' , MBpsMin = CAST(MIN(( bs.backup_size / ( CASE WHEN DATEDIFF(ss, bs.backup_start_date, bs.backup_finish_date) = 0 THEN 1 ELSE DATEDIFF(ss, bs.backup_start_date, bs.backup_finish_date) END ) / 1048576 )) AS INT) ' + @crlf - + ' , MBpsMax = CAST(MAX(( bs.backup_size / ( CASE WHEN DATEDIFF(ss, bs.backup_start_date, bs.backup_finish_date) = 0 THEN 1 ELSE DATEDIFF(ss, bs.backup_start_date, bs.backup_finish_date) END ) / 1048576 )) AS INT) ' + @crlf - + ' , SizeMBAvg = AVG(backup_size / 1048576.0) ' + @crlf - + ' , SizeMBMin = MIN(backup_size / 1048576.0) ' + @crlf - + ' , SizeMBMax = MAX(backup_size / 1048576.0) ' + @crlf - + ' , CompressedSizeMBAvg = AVG(compressed_backup_size / 1048576.0) ' + @crlf - + ' , CompressedSizeMBMin = MIN(compressed_backup_size / 1048576.0) ' + @crlf - + ' , CompressedSizeMBMax = MAX(compressed_backup_size / 1048576.0) ' + @crlf; + UNION ALL + SELECT N'Unused Memory Grant Warning' AS [Configuration Parameter] , + N'10' , + N'Percent' , + N'Triggers an "Unused Memory Grant Warning" when a query uses >= X percent of its memory grant.'; + RETURN; + END; /* IF @Help = 1 */ - SET @StringToExecute += N' FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset bs ' + @crlf - + N' WHERE bs.backup_finish_date >= @StartTime AND bs.is_damaged = 0 ' + @crlf - + N' GROUP BY bs.database_name, bs.database_guid, bs.type)' + @crlf; - SET @StringToExecute += + N'INSERT INTO #Backups(database_name, database_guid, ' + @crlf - + N' FullMBpsAvg, FullMBpsMin, FullMBpsMax, FullSizeMBAvg, FullSizeMBMin, FullSizeMBMax, FullCompressedSizeMBAvg, FullCompressedSizeMBMin, FullCompressedSizeMBMax, ' + @crlf - + N' DiffMBpsAvg, DiffMBpsMin, DiffMBpsMax, DiffSizeMBAvg, DiffSizeMBMin, DiffSizeMBMax, DiffCompressedSizeMBAvg, DiffCompressedSizeMBMin, DiffCompressedSizeMBMax, ' + @crlf - + N' LogMBpsAvg, LogMBpsMin, LogMBpsMax, LogSizeMBAvg, LogSizeMBMin, LogSizeMBMax, LogCompressedSizeMBAvg, LogCompressedSizeMBMin, LogCompressedSizeMBMax ) ' + @crlf - + N'SELECT bF.database_name, bF.database_guid ' + @crlf - + N' , bF.MBpsAvg AS FullMBpsAvg ' + @crlf - + N' , bF.MBpsMin AS FullMBpsMin ' + @crlf - + N' , bF.MBpsMax AS FullMBpsMax ' + @crlf - + N' , bF.SizeMBAvg AS FullSizeMBAvg ' + @crlf - + N' , bF.SizeMBMin AS FullSizeMBMin ' + @crlf - + N' , bF.SizeMBMax AS FullSizeMBMax ' + @crlf - + N' , bF.CompressedSizeMBAvg AS FullCompressedSizeMBAvg ' + @crlf - + N' , bF.CompressedSizeMBMin AS FullCompressedSizeMBMin ' + @crlf - + N' , bF.CompressedSizeMBMax AS FullCompressedSizeMBMax ' + @crlf - + N' , bD.MBpsAvg AS DiffMBpsAvg ' + @crlf - + N' , bD.MBpsMin AS DiffMBpsMin ' + @crlf - + N' , bD.MBpsMax AS DiffMBpsMax ' + @crlf - + N' , bD.SizeMBAvg AS DiffSizeMBAvg ' + @crlf - + N' , bD.SizeMBMin AS DiffSizeMBMin ' + @crlf - + N' , bD.SizeMBMax AS DiffSizeMBMax ' + @crlf - + N' , bD.CompressedSizeMBAvg AS DiffCompressedSizeMBAvg ' + @crlf - + N' , bD.CompressedSizeMBMin AS DiffCompressedSizeMBMin ' + @crlf - + N' , bD.CompressedSizeMBMax AS DiffCompressedSizeMBMax ' + @crlf - + N' , bL.MBpsAvg AS LogMBpsAvg ' + @crlf - + N' , bL.MBpsMin AS LogMBpsMin ' + @crlf - + N' , bL.MBpsMax AS LogMBpsMax ' + @crlf - + N' , bL.SizeMBAvg AS LogSizeMBAvg ' + @crlf - + N' , bL.SizeMBMin AS LogSizeMBMin ' + @crlf - + N' , bL.SizeMBMax AS LogSizeMBMax ' + @crlf - + N' , bL.CompressedSizeMBAvg AS LogCompressedSizeMBAvg ' + @crlf - + N' , bL.CompressedSizeMBMin AS LogCompressedSizeMBMin ' + @crlf - + N' , bL.CompressedSizeMBMax AS LogCompressedSizeMBMax ' + @crlf - + N' FROM Backups bF ' + @crlf - + N' LEFT OUTER JOIN Backups bD ON bF.database_name = bD.database_name AND bF.database_guid = bD.database_guid AND bD.backup_type = ''I''' + @crlf - + N' LEFT OUTER JOIN Backups bL ON bF.database_name = bL.database_name AND bF.database_guid = bL.database_guid AND bL.backup_type = ''L''' + @crlf - + N' WHERE bF.backup_type = ''D''; ' + @crlf; +/*Validate version*/ +IF ( +SELECT + CASE + WHEN CONVERT(NVARCHAR(128), SERVERPROPERTY ('PRODUCTVERSION')) LIKE '8%' THEN 0 + WHEN CONVERT(NVARCHAR(128), SERVERPROPERTY ('PRODUCTVERSION')) LIKE '9%' THEN 0 + ELSE 1 + END +) = 0 +BEGIN + DECLARE @version_msg VARCHAR(8000); + SELECT @version_msg = 'Sorry, sp_BlitzCache doesn''t work on versions of SQL prior to 2008.' + REPLICATE(CHAR(13), 7933); + PRINT @version_msg; + RETURN; +END; - IF @Debug = 1 - PRINT @StringToExecute; +IF(@OutputType = 'NONE' AND (@OutputTableName IS NULL OR @OutputSchemaName IS NULL OR @OutputDatabaseName IS NULL)) +BEGIN + RAISERROR('This procedure should be called with a value for all @Output* parameters, as @OutputType is set to NONE',12,1); + RETURN; +END; - EXEC sys.sp_executesql @StringToExecute, N'@StartTime DATETIME2', @StartTime; +IF(@OutputType = 'NONE') +BEGIN + SET @HideSummary = 1; +END; - RAISERROR('Updating #Backups with worst RPO case', 0, 1) WITH NOWAIT; +/* Lets get @SortOrder set to lower case here for comparisons later */ +SET @SortOrder = LOWER(@SortOrder); - SET @StringToExecute =N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - - SET @StringToExecute += N' - SELECT bs.database_name, bs.database_guid, bs.backup_set_id, bsPrior.backup_set_id AS backup_set_id_prior, - bs.backup_finish_date, bsPrior.backup_finish_date AS backup_finish_date_prior, - DATEDIFF(ss, bsPrior.backup_finish_date, bs.backup_finish_date) AS backup_gap_seconds - INTO #backup_gaps - FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS bs - CROSS APPLY ( - SELECT TOP 1 bs1.backup_set_id, bs1.backup_finish_date - FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS bs1 - WHERE bs.database_name = bs1.database_name - AND bs.database_guid = bs1.database_guid - AND bs.backup_finish_date > bs1.backup_finish_date - AND bs.backup_set_id > bs1.backup_set_id - ORDER BY bs1.backup_finish_date DESC, bs1.backup_set_id DESC - ) bsPrior - WHERE bs.backup_finish_date > @StartTime - - CREATE CLUSTERED INDEX cx_backup_gaps ON #backup_gaps (database_name, database_guid, backup_set_id, backup_finish_date, backup_gap_seconds); - - WITH max_gaps AS ( - SELECT g.database_name, g.database_guid, g.backup_set_id, g.backup_set_id_prior, g.backup_finish_date_prior, - g.backup_finish_date, MAX(g.backup_gap_seconds) AS max_backup_gap_seconds - FROM #backup_gaps AS g - GROUP BY g.database_name, g.database_guid, g.backup_set_id, g.backup_set_id_prior, g.backup_finish_date_prior, g.backup_finish_date - ) - UPDATE #Backups - SET RPOWorstCaseMinutes = bg.max_backup_gap_seconds / 60.0 - , RPOWorstCaseBackupSetID = bg.backup_set_id - , RPOWorstCaseBackupSetFinishTime = bg.backup_finish_date - , RPOWorstCaseBackupSetIDPrior = bg.backup_set_id_prior - , RPOWorstCaseBackupSetPriorFinishTime = bg.backup_finish_date_prior - FROM #Backups b - INNER HASH JOIN max_gaps bg ON b.database_name = bg.database_name AND b.database_guid = bg.database_guid - LEFT OUTER HASH JOIN max_gaps bgBigger ON bg.database_name = bgBigger.database_name AND bg.database_guid = bgBigger.database_guid AND bg.max_backup_gap_seconds < bgBigger.max_backup_gap_seconds - WHERE bgBigger.backup_set_id IS NULL; - '; - IF @Debug = 1 - PRINT @StringToExecute; - - EXEC sys.sp_executesql @StringToExecute, N'@StartTime DATETIME2', @StartTime; +/* Set @Top based on sort */ +IF ( + @Top IS NULL + AND @SortOrder IN ( 'all', 'all sort' ) + ) + BEGIN + SET @Top = 5; + END; - RAISERROR('Updating #Backups with worst RPO case queries', 0, 1) WITH NOWAIT; +IF ( + @Top IS NULL + AND @SortOrder NOT IN ( 'all', 'all sort' ) + ) + BEGIN + SET @Top = 10; + END; - UPDATE #Backups - SET RPOWorstCaseMoreInfoQuery = @MoreInfoHeader + N'SELECT * ' + @crlf - + N' FROM ' + QUOTENAME(@MSDBName) + '.dbo.backupset ' + @crlf - + N' WHERE database_name = ''' + database_name + ''' ' + @crlf - + N' AND database_guid = ''' + CAST(database_guid AS NVARCHAR(50)) + ''' ' + @crlf - + N' AND backup_finish_date >= DATEADD(hh, -2, ''' + CAST(CONVERT(DATETIME, RPOWorstCaseBackupSetPriorFinishTime, 102) AS NVARCHAR(100)) + ''') ' + @crlf - + N' AND backup_finish_date <= DATEADD(hh, 2, ''' + CAST(CONVERT(DATETIME, RPOWorstCaseBackupSetPriorFinishTime, 102) AS NVARCHAR(100)) + ''') ' + @crlf - + N' ORDER BY backup_finish_date;' - + @MoreInfoFooter; +/* If they want to sort by query hash, populate the @OnlyQueryHashes list for them */ +IF @SortOrder LIKE 'query hash%' + BEGIN + RAISERROR('Beginning query hash sort', 0, 1) WITH NOWAIT; -/* RTO */ + SELECT TOP(@Top) qs.query_hash, + MAX(qs.max_worker_time) AS max_worker_time, + COUNT_BIG(*) AS records + INTO #query_hash_grouped + FROM sys.dm_exec_query_stats AS qs + CROSS APPLY ( SELECT pa.value + FROM sys.dm_exec_plan_attributes(qs.plan_handle) AS pa + WHERE pa.attribute = 'dbid' ) AS ca + GROUP BY qs.query_hash, ca.value + HAVING COUNT_BIG(*) > 1 + ORDER BY max_worker_time DESC, + records DESC; + + SELECT TOP (1) + @OnlyQueryHashes = STUFF((SELECT DISTINCT N',' + CONVERT(NVARCHAR(MAX), qhg.query_hash, 1) + FROM #query_hash_grouped AS qhg + WHERE qhg.query_hash <> 0x00 + FOR XML PATH(N''), TYPE).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 1, N'') + OPTION(RECOMPILE); -RAISERROR('Gathering RTO information', 0, 1) WITH NOWAIT; + /* When they ran it, @SortOrder probably looked like 'query hash, cpu', so strip the first sort order out: */ + SELECT @SortOrder = LTRIM(REPLACE(REPLACE(@SortOrder,'query hash', ''), ',', '')); + + /* If they just called it with @SortOrder = 'query hash', set it to 'cpu' for backwards compatibility: */ + IF @SortOrder = '' SET @SortOrder = 'cpu'; + END - SET @StringToExecute =N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - SET @StringToExecute += N' - INSERT INTO #RTORecoveryPoints(database_name, database_guid, log_last_lsn) - SELECT database_name, database_guid, MAX(last_lsn) AS log_last_lsn - FROM ' + QUOTENAME(@MSDBName) + '.dbo.backupset bLastLog - WHERE type = ''L'' - AND bLastLog.backup_finish_date >= @StartTime - GROUP BY database_name, database_guid; - '; +/* If they want to sort by duplicate, create a table with the worst offenders - issue #3345 */ +IF @SortOrder LIKE 'duplicate%' + BEGIN + RAISERROR('Beginning duplicate query hash sort', 0, 1) WITH NOWAIT; - IF @Debug = 1 - PRINT @StringToExecute; + /* Find the query hashes that are the most duplicated */ + WITH MostCommonQueries AS ( + SELECT TOP(@Top) qs.query_hash, + COUNT_BIG(*) AS plans + FROM sys.dm_exec_query_stats AS qs + GROUP BY qs.query_hash + HAVING COUNT_BIG(*) > 100 + ORDER BY COUNT_BIG(*) DESC + ) + SELECT mcq_recent.sql_handle, mcq_recent.plan_handle, mcq_recent.creation_time AS duplicate_creation_time, mcq.plans + INTO #duplicate_query_filter + FROM MostCommonQueries mcq + CROSS APPLY ( SELECT TOP 1 qs.sql_handle, qs.plan_handle, qs.creation_time + FROM sys.dm_exec_query_stats qs + WHERE qs.query_hash = mcq.query_hash + ORDER BY qs.creation_time DESC) AS mcq_recent + OPTION (RECOMPILE); - EXEC sys.sp_executesql @StringToExecute, N'@StartTime DATETIME2', @StartTime; + SET @MinimumExecutionCount = 0; + END -/* Find the most recent full backups for those logs */ -RAISERROR('Updating #RTORecoveryPoints', 0, 1) WITH NOWAIT; +/* validate user inputs */ +IF @Top IS NULL + OR @SortOrder IS NULL + OR @QueryFilter IS NULL + OR @Reanalyze IS NULL +BEGIN + RAISERROR(N'Several parameters (@Top, @SortOrder, @QueryFilter, @renalyze) are required. Do not set them to NULL. Please try again.', 16, 1) WITH NOWAIT; + RETURN; +END; - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; +RAISERROR(N'Checking @MinutesBack validity.', 0, 1) WITH NOWAIT; +IF @MinutesBack IS NOT NULL + BEGIN + IF @MinutesBack > 0 + BEGIN + RAISERROR(N'Setting @MinutesBack to a negative number', 0, 1) WITH NOWAIT; + SET @MinutesBack *=-1; + END; + IF @MinutesBack = 0 + BEGIN + RAISERROR(N'@MinutesBack can''t be 0, setting to -1', 0, 1) WITH NOWAIT; + SET @MinutesBack = -1; + END; + END; - SET @StringToExecute += N' - UPDATE #RTORecoveryPoints - SET log_backup_set_id = bLasted.backup_set_id - ,full_backup_set_id = bLasted.backup_set_id - ,full_last_lsn = bLasted.last_lsn - ,full_backup_set_uuid = bLasted.backup_set_uuid - FROM #RTORecoveryPoints rp - CROSS APPLY ( - SELECT TOP 1 bLog.backup_set_id AS backup_set_id_log, bLastFull.backup_set_id, bLastFull.last_lsn, bLastFull.backup_set_uuid, bLastFull.database_guid, bLastFull.database_name - FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset bLog - INNER JOIN ' + QUOTENAME(@MSDBName) + N'.dbo.backupset bLastFull - ON bLog.database_guid = bLastFull.database_guid - AND bLog.database_name = bLastFull.database_name - AND bLog.first_lsn > bLastFull.last_lsn - AND bLastFull.type = ''D'' - WHERE rp.database_guid = bLog.database_guid - AND rp.database_name = bLog.database_name - ) bLasted - LEFT OUTER JOIN ' + QUOTENAME(@MSDBName) + N'.dbo.backupset bLaterFulls ON bLasted.database_guid = bLaterFulls.database_guid AND bLasted.database_name = bLaterFulls.database_name - AND bLasted.last_lsn < bLaterFulls.last_lsn - AND bLaterFulls.first_lsn < bLasted.last_lsn - AND bLaterFulls.type = ''D'' - WHERE bLaterFulls.backup_set_id IS NULL; - '; - IF @Debug = 1 - PRINT @StringToExecute; - EXEC sys.sp_executesql @StringToExecute; +DECLARE @DurationFilter_i INT, + @MinMemoryPerQuery INT, + @msg NVARCHAR(4000), + @NoobSaibot BIT = 0, + @VersionShowsAirQuoteActualPlans BIT, + @ObjectFullName NVARCHAR(2000), + @user_perm_sql NVARCHAR(MAX) = N'', + @user_perm_gb_out DECIMAL(10,2), + @common_version DECIMAL(10,2), + @buffer_pool_memory_gb DECIMAL(10,2), + @user_perm_percent DECIMAL(10,2), + @is_tokenstore_big BIT = 0, + @sort NVARCHAR(MAX) = N'', + @sort_filter NVARCHAR(MAX) = N''; -/* Add any full backups in the StartDate range that weren't part of the above log backup chain */ -RAISERROR('Add any full backups in the StartDate range that weren''t part of the above log backup chain', 0, 1) WITH NOWAIT; +IF @SortOrder = 'sp_BlitzIndex' +BEGIN + RAISERROR(N'OUTSTANDING!', 0, 1) WITH NOWAIT; + SET @SortOrder = 'reads'; + SET @NoobSaibot = 1; - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; +END - SET @StringToExecute += N' - INSERT INTO #RTORecoveryPoints(database_name, database_guid, full_backup_set_id, full_last_lsn, full_backup_set_uuid) - SELECT bFull.database_name, bFull.database_guid, bFull.backup_set_id, bFull.last_lsn, bFull.backup_set_uuid - FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset bFull - LEFT OUTER JOIN #RTORecoveryPoints rp ON bFull.backup_set_uuid = rp.full_backup_set_uuid - WHERE bFull.type = ''D'' - AND bFull.backup_finish_date IS NOT NULL - AND rp.full_backup_set_uuid IS NULL - AND bFull.backup_finish_date >= @StartTime; - '; - IF @Debug = 1 - PRINT @StringToExecute; +/* Change duration from seconds to milliseconds */ +IF @DurationFilter IS NOT NULL + BEGIN + RAISERROR(N'Converting Duration Filter to milliseconds', 0, 1) WITH NOWAIT; + SET @DurationFilter_i = CAST((@DurationFilter * 1000.0) AS INT); + END; - EXEC sys.sp_executesql @StringToExecute, N'@StartTime DATETIME2', @StartTime; +RAISERROR(N'Checking database validity', 0, 1) WITH NOWAIT; +SET @DatabaseName = LTRIM(RTRIM(@DatabaseName)) ; -/* Fill out the most recent log for that full, but before the next full */ +IF SERVERPROPERTY('EngineEdition') IN (5, 6) AND DB_NAME() <> @DatabaseName +BEGIN + RAISERROR('You specified a database name other than the current database, but Azure SQL DB does not allow you to change databases. Execute sp_BlitzCache from the database you want to analyze.', 16, 1); + RETURN; +END; +IF (DB_ID(@DatabaseName)) IS NULL AND @DatabaseName <> N'' +BEGIN + RAISERROR('The database you specified does not exist. Please check the name and try again.', 16, 1); + RETURN; +END; +IF (SELECT DATABASEPROPERTYEX(ISNULL(@DatabaseName, 'master'), 'Collation')) IS NULL AND SERVERPROPERTY('EngineEdition') NOT IN (5, 6, 8) +BEGIN + RAISERROR('The database you specified is not readable. Please check the name and try again. Better yet, check your server.', 16, 1); + RETURN; +END; -RAISERROR('Fill out the most recent log for that full, but before the next full', 0, 1) WITH NOWAIT; +SELECT @MinMemoryPerQuery = CONVERT(INT, c.value) FROM sys.configurations AS c WHERE c.name = 'min memory per query (KB)'; - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; +SET @SortOrder = REPLACE(REPLACE(@SortOrder, 'average', 'avg'), '.', ''); - SET @StringToExecute += N' - UPDATE rp - SET log_last_lsn = (SELECT MAX(last_lsn) FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset bLog WHERE bLog.first_lsn >= rp.full_last_lsn AND bLog.first_lsn <= rpNextFull.full_last_lsn AND bLog.type = ''L'') - FROM #RTORecoveryPoints rp - INNER JOIN #RTORecoveryPoints rpNextFull ON rp.database_guid = rpNextFull.database_guid AND rp.database_name = rpNextFull.database_name - AND rp.full_last_lsn < rpNextFull.full_last_lsn - LEFT OUTER JOIN #RTORecoveryPoints rpEarlierFull ON rp.database_guid = rpEarlierFull.database_guid AND rp.database_name = rpEarlierFull.database_name - AND rp.full_last_lsn < rpEarlierFull.full_last_lsn - AND rpNextFull.full_last_lsn > rpEarlierFull.full_last_lsn - WHERE rpEarlierFull.full_backup_set_id IS NULL; - '; - - IF @Debug = 1 - PRINT @StringToExecute; - - EXEC sys.sp_executesql @StringToExecute; +SET @SortOrder = CASE + WHEN @SortOrder IN ('executions per minute','execution per minute','executions / minute','execution / minute','xpm') THEN 'avg executions' + WHEN @SortOrder IN ('recent compilations','recent compilation','compile') THEN 'compiles' + WHEN @SortOrder IN ('read') THEN 'reads' + WHEN @SortOrder IN ('avg read') THEN 'avg reads' + WHEN @SortOrder IN ('write') THEN 'writes' + WHEN @SortOrder IN ('avg write') THEN 'avg writes' + WHEN @SortOrder IN ('memory grants') THEN 'memory grant' + WHEN @SortOrder IN ('avg memory grants') THEN 'avg memory grant' + WHEN @SortOrder IN ('unused grants','unused memory', 'unused memory grant', 'unused memory grants') THEN 'unused grant' + WHEN @SortOrder IN ('spill') THEN 'spills' + WHEN @SortOrder IN ('avg spill') THEN 'avg spills' + WHEN @SortOrder IN ('execution') THEN 'executions' + WHEN @SortOrder IN ('duplicates') THEN 'duplicate' + ELSE @SortOrder END + +RAISERROR(N'Checking sort order', 0, 1) WITH NOWAIT; +IF @SortOrder NOT IN ('cpu', 'avg cpu', 'reads', 'avg reads', 'writes', 'avg writes', + 'duration', 'avg duration', 'executions', 'avg executions', + 'compiles', 'memory grant', 'avg memory grant', 'unused grant', + 'spills', 'avg spills', 'all', 'all avg', 'sp_BlitzIndex', + 'query hash', 'duplicate') + BEGIN + RAISERROR(N'Invalid sort order chosen, reverting to cpu', 16, 1) WITH NOWAIT; + SET @SortOrder = 'cpu'; + END; -/* Fill out a diff in that range */ +SET @QueryFilter = LOWER(@QueryFilter); -RAISERROR('Fill out a diff in that range', 0, 1) WITH NOWAIT; +IF LEFT(@QueryFilter, 3) NOT IN ('all', 'sta', 'pro', 'fun') + BEGIN + RAISERROR(N'Invalid query filter chosen. Reverting to all.', 0, 1) WITH NOWAIT; + SET @QueryFilter = 'all'; + END; - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; +IF @SkipAnalysis = 1 + BEGIN + RAISERROR(N'Skip Analysis set to 1, hiding Summary', 0, 1) WITH NOWAIT; + SET @HideSummary = 1; + END; - SET @StringToExecute += N' - UPDATE #RTORecoveryPoints - SET diff_last_lsn = (SELECT TOP 1 bDiff.last_lsn FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset bDiff - WHERE rp.database_guid = bDiff.database_guid AND rp.database_name = bDiff.database_name - AND bDiff.type = ''I'' - AND bDiff.last_lsn < rp.log_last_lsn - AND rp.full_backup_set_uuid = bDiff.differential_base_guid - ORDER BY bDiff.last_lsn DESC) - FROM #RTORecoveryPoints rp - WHERE diff_last_lsn IS NULL; - '; +DECLARE @AllSortSql NVARCHAR(MAX) = N''; +DECLARE @VersionShowsMemoryGrants BIT; +IF EXISTS(SELECT * FROM sys.all_columns WHERE object_id = OBJECT_ID('sys.dm_exec_query_stats') AND name = 'max_grant_kb') + SET @VersionShowsMemoryGrants = 1; +ELSE + SET @VersionShowsMemoryGrants = 0; - IF @Debug = 1 - PRINT @StringToExecute; +DECLARE @VersionShowsSpills BIT; +IF EXISTS(SELECT * FROM sys.all_columns WHERE object_id = OBJECT_ID('sys.dm_exec_query_stats') AND name = 'max_spills') + SET @VersionShowsSpills = 1; +ELSE + SET @VersionShowsSpills = 0; - EXEC sys.sp_executesql @StringToExecute; +IF EXISTS(SELECT * FROM sys.all_columns WHERE object_id = OBJECT_ID('sys.dm_exec_query_plan_stats') AND name = 'query_plan') + SET @VersionShowsAirQuoteActualPlans = 1; +ELSE + SET @VersionShowsAirQuoteActualPlans = 0; -/* Get time & size totals for full & diff */ +IF @Reanalyze = 1 + BEGIN + IF OBJECT_ID('tempdb..##BlitzCacheResults') IS NULL + BEGIN + RAISERROR(N'##BlitzCacheResults does not exist, can''t reanalyze', 0, 1) WITH NOWAIT; + SET @Reanalyze = 0; + END + ELSE + BEGIN + RAISERROR(N'Reanalyzing current data, skipping to results', 0, 1) WITH NOWAIT; + GOTO Results; + END; + END; -RAISERROR('Get time & size totals for full & diff', 0, 1) WITH NOWAIT; - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; +IF @SortOrder IN ('all', 'all avg') + BEGIN + RAISERROR(N'Checking all sort orders, please be patient', 0, 1) WITH NOWAIT; + GOTO AllSorts; + END; - SET @StringToExecute += N' - UPDATE #RTORecoveryPoints - SET full_time_seconds = DATEDIFF(ss,bFull.backup_start_date, bFull.backup_finish_date) - , full_file_size_mb = bFull.backup_size / 1048576.0 - , diff_backup_set_id = bDiff.backup_set_id - , diff_time_seconds = DATEDIFF(ss,bDiff.backup_start_date, bDiff.backup_finish_date) - , diff_file_size_mb = bDiff.backup_size / 1048576.0 - FROM #RTORecoveryPoints rp - INNER JOIN ' + QUOTENAME(@MSDBName) + N'.dbo.backupset bFull ON rp.database_guid = bFull.database_guid AND rp.database_name = bFull.database_name AND rp.full_last_lsn = bFull.last_lsn - LEFT OUTER JOIN ' + QUOTENAME(@MSDBName) + N'.dbo.backupset bDiff ON rp.database_guid = bDiff.database_guid AND rp.database_name = bDiff.database_name AND rp.diff_last_lsn = bDiff.last_lsn AND bDiff.last_lsn IS NOT NULL; - '; +RAISERROR(N'Creating temp tables for internal processing', 0, 1) WITH NOWAIT; +IF OBJECT_ID('tempdb..#only_query_hashes') IS NOT NULL + DROP TABLE #only_query_hashes ; - IF @Debug = 1 - PRINT @StringToExecute; - - EXEC sys.sp_executesql @StringToExecute; +IF OBJECT_ID('tempdb..#ignore_query_hashes') IS NOT NULL + DROP TABLE #ignore_query_hashes ; +IF OBJECT_ID('tempdb..#only_sql_handles') IS NOT NULL + DROP TABLE #only_sql_handles ; -/* Get time & size totals for logs */ +IF OBJECT_ID('tempdb..#ignore_sql_handles') IS NOT NULL + DROP TABLE #ignore_sql_handles ; + +IF OBJECT_ID('tempdb..#p') IS NOT NULL + DROP TABLE #p; -RAISERROR('Get time & size totals for logs', 0, 1) WITH NOWAIT; +IF OBJECT_ID ('tempdb..#checkversion') IS NOT NULL + DROP TABLE #checkversion; - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; +IF OBJECT_ID ('tempdb..#configuration') IS NOT NULL + DROP TABLE #configuration; - SET @StringToExecute += N' - WITH LogTotals AS ( - SELECT rp.id, log_time_seconds = SUM(DATEDIFF(ss,bLog.backup_start_date, bLog.backup_finish_date)) - , log_file_size = SUM(bLog.backup_size) - , SUM(1) AS log_backups - FROM #RTORecoveryPoints rp - INNER JOIN ' + QUOTENAME(@MSDBName) + N'.dbo.backupset bLog ON rp.database_guid = bLog.database_guid AND rp.database_name = bLog.database_name AND bLog.type = ''L'' - AND bLog.first_lsn > COALESCE(rp.diff_last_lsn, rp.full_last_lsn) - AND bLog.first_lsn <= rp.log_last_lsn - GROUP BY rp.id - ) - UPDATE #RTORecoveryPoints - SET log_time_seconds = lt.log_time_seconds - , log_file_size_mb = lt.log_file_size / 1048576.0 - , log_backups = lt.log_backups - FROM #RTORecoveryPoints rp - INNER JOIN LogTotals lt ON rp.id = lt.id; - '; +IF OBJECT_ID ('tempdb..#stored_proc_info') IS NOT NULL + DROP TABLE #stored_proc_info; - IF @Debug = 1 - PRINT @StringToExecute; +IF OBJECT_ID ('tempdb..#plan_creation') IS NOT NULL + DROP TABLE #plan_creation; - EXEC sys.sp_executesql @StringToExecute; +IF OBJECT_ID ('tempdb..#est_rows') IS NOT NULL + DROP TABLE #est_rows; -RAISERROR('Gathering RTO worst cases', 0, 1) WITH NOWAIT; +IF OBJECT_ID ('tempdb..#plan_cost') IS NOT NULL + DROP TABLE #plan_cost; - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; +IF OBJECT_ID ('tempdb..#proc_costs') IS NOT NULL + DROP TABLE #proc_costs; - SET @StringToExecute += N' - WITH WorstCases AS ( - SELECT rp.* - FROM #RTORecoveryPoints rp - LEFT OUTER JOIN #RTORecoveryPoints rpNewer - ON rp.database_guid = rpNewer.database_guid - AND rp.database_name = rpNewer.database_name - AND rp.full_last_lsn < rpNewer.full_last_lsn - AND rpNewer.rto_worst_case_size_mb = (SELECT TOP 1 rto_worst_case_size_mb FROM #RTORecoveryPoints s WHERE rp.database_guid = s.database_guid AND rp.database_name = s.database_name ORDER BY rto_worst_case_size_mb DESC) - WHERE rp.rto_worst_case_size_mb = (SELECT TOP 1 rto_worst_case_size_mb FROM #RTORecoveryPoints s WHERE rp.database_guid = s.database_guid AND rp.database_name = s.database_name ORDER BY rto_worst_case_size_mb DESC) - /* OR rp.rto_worst_case_time_seconds = (SELECT TOP 1 rto_worst_case_time_seconds FROM #RTORecoveryPoints s WHERE rp.database_guid = s.database_guid AND rp.database_name = s.database_name ORDER BY rto_worst_case_time_seconds DESC) */ - AND rpNewer.database_guid IS NULL - ) - UPDATE #Backups - SET RTOWorstCaseMinutes = - /* Fulls */ - (CASE WHEN @RestoreSpeedFullMBps IS NULL - THEN wc.full_time_seconds / 60.0 - ELSE @RestoreSpeedFullMBps / wc.full_file_size_mb - END) +IF OBJECT_ID ('tempdb..#stats_agg') IS NOT NULL + DROP TABLE #stats_agg; - /* Diffs, which might not have been taken */ - + (CASE WHEN @RestoreSpeedDiffMBps IS NOT NULL AND wc.diff_file_size_mb IS NOT NULL - THEN @RestoreSpeedDiffMBps / wc.diff_file_size_mb - ELSE COALESCE(wc.diff_time_seconds,0) / 60.0 - END) +IF OBJECT_ID ('tempdb..#trace_flags') IS NOT NULL + DROP TABLE #trace_flags; - /* Logs, which might not have been taken */ - + (CASE WHEN @RestoreSpeedLogMBps IS NOT NULL AND wc.log_file_size_mb IS NOT NULL - THEN @RestoreSpeedLogMBps / wc.log_file_size_mb - ELSE COALESCE(wc.log_time_seconds,0) / 60.0 - END) - , RTOWorstCaseBackupFileSizeMB = wc.rto_worst_case_size_mb - FROM #Backups b - INNER JOIN WorstCases wc - ON b.database_guid = wc.database_guid - AND b.database_name = wc.database_name; - '; +IF OBJECT_ID('tempdb..#variable_info') IS NOT NULL + DROP TABLE #variable_info; - IF @Debug = 1 - PRINT @StringToExecute; +IF OBJECT_ID('tempdb..#conversion_info') IS NOT NULL + DROP TABLE #conversion_info; - EXEC sys.sp_executesql @StringToExecute, N'@RestoreSpeedFullMBps INT, @RestoreSpeedDiffMBps INT, @RestoreSpeedLogMBps INT', @RestoreSpeedFullMBps, @RestoreSpeedDiffMBps, @RestoreSpeedLogMBps; +IF OBJECT_ID('tempdb..#missing_index_xml') IS NOT NULL + DROP TABLE #missing_index_xml; +IF OBJECT_ID('tempdb..#missing_index_schema') IS NOT NULL + DROP TABLE #missing_index_schema; +IF OBJECT_ID('tempdb..#missing_index_usage') IS NOT NULL + DROP TABLE #missing_index_usage; -/*Populating Recoverability*/ +IF OBJECT_ID('tempdb..#missing_index_detail') IS NOT NULL + DROP TABLE #missing_index_detail; +IF OBJECT_ID('tempdb..#missing_index_pretty') IS NOT NULL + DROP TABLE #missing_index_pretty; - /*Get distinct list of databases*/ - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; +IF OBJECT_ID('tempdb..#index_spool_ugly') IS NOT NULL + DROP TABLE #index_spool_ugly; + +IF OBJECT_ID('tempdb..#ReadableDBs') IS NOT NULL + DROP TABLE #ReadableDBs; - SET @StringToExecute += N' - SELECT DISTINCT b.database_name, database_guid - FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b;' +IF OBJECT_ID('tempdb..#plan_usage') IS NOT NULL + DROP TABLE #plan_usage; - IF @Debug = 1 - PRINT @StringToExecute; +CREATE TABLE #only_query_hashes ( + query_hash BINARY(8) +); - INSERT #Recoverability ( DatabaseName, DatabaseGUID ) - EXEC sys.sp_executesql @StringToExecute; +CREATE TABLE #ignore_query_hashes ( + query_hash BINARY(8) +); +CREATE TABLE #only_sql_handles ( + sql_handle VARBINARY(64) +); - /*Find most recent recovery model, backup size, and backup date*/ - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; +CREATE TABLE #ignore_sql_handles ( + sql_handle VARBINARY(64) +); - SET @StringToExecute += N' - UPDATE r - SET r.LastBackupRecoveryModel = ca.recovery_model, - r.LastFullBackupSizeMB = ca.compressed_backup_size, - r.LastFullBackupDate = ca.backup_finish_date - FROM #Recoverability r - CROSS APPLY ( - SELECT TOP 1 b.recovery_model, (b.compressed_backup_size / 1048576.0) AS compressed_backup_size, b.backup_finish_date - FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b - WHERE r.DatabaseName = b.database_name - AND r.DatabaseGUID = b.database_guid - AND b.type = ''D'' - AND b.backup_finish_date > @StartTime - ORDER BY b.backup_finish_date DESC - ) ca;' +CREATE TABLE #p ( + SqlHandle VARBINARY(64), + TotalCPU BIGINT, + TotalDuration BIGINT, + TotalReads BIGINT, + TotalWrites BIGINT, + ExecutionCount BIGINT +); - IF @Debug = 1 - PRINT @StringToExecute; +CREATE TABLE #checkversion ( + version NVARCHAR(128), + common_version AS SUBSTRING(version, 1, CHARINDEX('.', version) + 1 ), + major AS PARSENAME(CONVERT(VARCHAR(32), version), 4), + minor AS PARSENAME(CONVERT(VARCHAR(32), version), 3), + build AS PARSENAME(CONVERT(VARCHAR(32), version), 2), + revision AS PARSENAME(CONVERT(VARCHAR(32), version), 1) +); - EXEC sys.sp_executesql @StringToExecute, N'@StartTime DATETIME2', @StartTime; +CREATE TABLE #configuration ( + parameter_name VARCHAR(100), + value DECIMAL(38,0) +); - /*Find first backup size and date*/ - SET @StringToExecute =N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; +CREATE TABLE #plan_creation +( + percent_24 DECIMAL(5, 2), + percent_4 DECIMAL(5, 2), + percent_1 DECIMAL(5, 2), + total_plans INT, + SPID INT +); - SET @StringToExecute += N' - UPDATE r - SET r.FirstFullBackupSizeMB = ca.compressed_backup_size, - r.FirstFullBackupDate = ca.backup_finish_date - FROM #Recoverability r - CROSS APPLY ( - SELECT TOP 1 (b.compressed_backup_size / 1048576.0) AS compressed_backup_size, b.backup_finish_date - FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b - WHERE r.DatabaseName = b.database_name - AND r.DatabaseGUID = b.database_guid - AND b.type = ''D'' - AND b.backup_finish_date > @StartTime - ORDER BY b.backup_finish_date ASC - ) ca;' +CREATE TABLE #est_rows +( + QueryHash BINARY(8), + estimated_rows FLOAT +); - IF @Debug = 1 - PRINT @StringToExecute; +CREATE TABLE #plan_cost +( + QueryPlanCost FLOAT, + SqlHandle VARBINARY(64), + PlanHandle VARBINARY(64), + QueryHash BINARY(8), + QueryPlanHash BINARY(8) +); - EXEC sys.sp_executesql @StringToExecute, N'@StartTime DATETIME2', @StartTime; +CREATE TABLE #proc_costs +( + PlanTotalQuery FLOAT, + PlanHandle VARBINARY(64), + SqlHandle VARBINARY(64) +); +CREATE TABLE #stats_agg +( + SqlHandle VARBINARY(64), + LastUpdate DATETIME2(7), + ModificationCount BIGINT, + SamplingPercent FLOAT, + [Statistics] NVARCHAR(258), + [Table] NVARCHAR(258), + [Schema] NVARCHAR(258), + [Database] NVARCHAR(258), +); - /*Find average backup throughputs for full, diff, and log*/ - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; +CREATE TABLE #trace_flags +( + SqlHandle VARBINARY(64), + QueryHash BINARY(8), + global_trace_flags VARCHAR(1000), + session_trace_flags VARCHAR(1000) +); - SET @StringToExecute += N' - UPDATE r - SET r.AvgFullBackupThroughputMB = ca_full.AvgFullSpeed, - r.AvgDiffBackupThroughputMB = ca_diff.AvgDiffSpeed, - r.AvgLogBackupThroughputMB = ca_log.AvgLogSpeed, - r.AvgFullBackupDurationSeconds = AvgFullDuration, - r.AvgDiffBackupDurationSeconds = AvgDiffDuration, - r.AvgLogBackupDurationSeconds = AvgLogDuration - FROM #Recoverability AS r - OUTER APPLY ( - SELECT b.database_name, - AVG( b.compressed_backup_size / ( DATEDIFF(ss, b.backup_start_date, b.backup_finish_date) ) / 1048576.0 ) AS AvgFullSpeed, - AVG( DATEDIFF(ss, b.backup_start_date, b.backup_finish_date) ) AS AvgFullDuration - FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset b - WHERE r.DatabaseName = b.database_name - AND r.DatabaseGUID = b.database_guid - AND b.type = ''D'' - AND DATEDIFF(SECOND, b.backup_start_date, b.backup_finish_date) > 0 - AND b.backup_finish_date > @StartTime - GROUP BY b.database_name - ) ca_full - OUTER APPLY ( - SELECT b.database_name, - AVG( b.compressed_backup_size / ( DATEDIFF(ss, b.backup_start_date, b.backup_finish_date) ) / 1048576.0 ) AS AvgDiffSpeed, - AVG( DATEDIFF(ss, b.backup_start_date, b.backup_finish_date) ) AS AvgDiffDuration - FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset b - WHERE r.DatabaseName = b.database_name - AND r.DatabaseGUID = b.database_guid - AND b.type = ''I'' - AND DATEDIFF(SECOND, b.backup_start_date, b.backup_finish_date) > 0 - AND b.backup_finish_date > @StartTime - GROUP BY b.database_name - ) ca_diff - OUTER APPLY ( - SELECT b.database_name, - AVG( b.compressed_backup_size / ( DATEDIFF(ss, b.backup_start_date, b.backup_finish_date) ) / 1048576.0 ) AS AvgLogSpeed, - AVG( DATEDIFF(ss, b.backup_start_date, b.backup_finish_date) ) AS AvgLogDuration - FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset b - WHERE r.DatabaseName = b.database_name - AND r.DatabaseGUID = b.database_guid - AND b.type = ''L'' - AND DATEDIFF(SECOND, b.backup_start_date, b.backup_finish_date) > 0 - AND b.backup_finish_date > @StartTime - GROUP BY b.database_name - ) ca_log;' +CREATE TABLE #stored_proc_info +( + SPID INT, + SqlHandle VARBINARY(64), + QueryHash BINARY(8), + variable_name NVARCHAR(258), + variable_datatype NVARCHAR(258), + converted_column_name NVARCHAR(258), + compile_time_value NVARCHAR(258), + proc_name NVARCHAR(1000), + column_name NVARCHAR(4000), + converted_to NVARCHAR(258), + set_options NVARCHAR(1000) +); - IF @Debug = 1 - PRINT @StringToExecute; +CREATE TABLE #variable_info +( + SPID INT, + QueryHash BINARY(8), + SqlHandle VARBINARY(64), + proc_name NVARCHAR(1000), + variable_name NVARCHAR(258), + variable_datatype NVARCHAR(258), + compile_time_value NVARCHAR(258) +); - EXEC sys.sp_executesql @StringToExecute, N'@StartTime DATETIME2', @StartTime; +CREATE TABLE #conversion_info +( + SPID INT, + QueryHash BINARY(8), + SqlHandle VARBINARY(64), + proc_name NVARCHAR(258), + expression NVARCHAR(4000), + at_charindex AS CHARINDEX('@', expression), + bracket_charindex AS CHARINDEX(']', expression, CHARINDEX('@', expression)) - CHARINDEX('@', expression), + comma_charindex AS CHARINDEX(',', expression) + 1, + second_comma_charindex AS + CHARINDEX(',', expression, CHARINDEX(',', expression) + 1) - CHARINDEX(',', expression) - 1, + equal_charindex AS CHARINDEX('=', expression) + 1, + paren_charindex AS CHARINDEX('(', expression) + 1, + comma_paren_charindex AS + CHARINDEX(',', expression, CHARINDEX('(', expression) + 1) - CHARINDEX('(', expression) - 1, + convert_implicit_charindex AS CHARINDEX('=CONVERT_IMPLICIT', expression) +); - /*Find max and avg diff and log sizes*/ - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; +CREATE TABLE #missing_index_xml +( + QueryHash BINARY(8), + SqlHandle VARBINARY(64), + impact FLOAT, + index_xml XML +); - SET @StringToExecute += N' - UPDATE r - SET r.AvgFullSizeMB = fulls.avg_full_size, - r.AvgDiffSizeMB = diffs.avg_diff_size, - r.AvgLogSizeMB = logs.avg_log_size - FROM #Recoverability AS r - OUTER APPLY ( - SELECT b.database_name, AVG(b.compressed_backup_size / 1048576.0) AS avg_full_size - FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b - WHERE r.DatabaseName = b.database_name - AND r.DatabaseGUID = b.database_guid - AND b.type = ''D'' - AND b.backup_finish_date > @StartTime - GROUP BY b.database_name - ) AS fulls - OUTER APPLY ( - SELECT b.database_name, AVG(b.compressed_backup_size / 1048576.0) AS avg_diff_size - FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b - WHERE r.DatabaseName = b.database_name - AND r.DatabaseGUID = b.database_guid - AND b.type = ''I'' - AND b.backup_finish_date > @StartTime - GROUP BY b.database_name - ) AS diffs - OUTER APPLY ( - SELECT b.database_name, AVG(b.compressed_backup_size / 1048576.0) AS avg_log_size - FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b - WHERE r.DatabaseName = b.database_name - AND r.DatabaseGUID = b.database_guid - AND b.type = ''L'' - AND b.backup_finish_date > @StartTime - GROUP BY b.database_name - ) AS logs;' - IF @Debug = 1 - PRINT @StringToExecute; +CREATE TABLE #missing_index_schema +( + QueryHash BINARY(8), + SqlHandle VARBINARY(64), + impact FLOAT, + database_name NVARCHAR(128), + schema_name NVARCHAR(128), + table_name NVARCHAR(128), + index_xml XML +); - EXEC sys.sp_executesql @StringToExecute, N'@StartTime DATETIME2', @StartTime; - -/*Trending - only works if backupfile is populated, which means in msdb */ -IF @MSDBName = N'msdb' -BEGIN - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' --+ @crlf; - SET @StringToExecute += N' - SELECT p.DatabaseName, - p.DatabaseGUID, - p.[0], - p.[-1], - p.[-2], - p.[-3], - p.[-4], - p.[-5], - p.[-6], - p.[-7], - p.[-8], - p.[-9], - p.[-10], - p.[-11], - p.[-12] - FROM ( SELECT b.database_name AS DatabaseName, - b.database_guid AS DatabaseGUID, - DATEDIFF(MONTH, @StartTime, b.backup_start_date) AS MonthsAgo , - CONVERT(DECIMAL(18, 2), AVG(bf.file_size / 1048576.0)) AS AvgSizeMB - FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b - INNER JOIN ' + QUOTENAME(@MSDBName) + N'.dbo.backupfile AS bf - ON b.backup_set_id = bf.backup_set_id - WHERE b.database_name NOT IN ( ''master'', ''msdb'', ''model'', ''tempdb'' ) - AND bf.file_type = ''D'' - AND b.backup_start_date >= DATEADD(YEAR, -1, @StartTime) - AND b.backup_start_date <= SYSDATETIME() - GROUP BY b.database_name, - b.database_guid, - DATEDIFF(mm, @StartTime, b.backup_start_date) - ) AS bckstat PIVOT ( SUM(bckstat.AvgSizeMB) FOR bckstat.MonthsAgo IN ( [0], [-1], [-2], [-3], [-4], [-5], [-6], [-7], [-8], [-9], [-10], [-11], [-12] ) ) AS p - ORDER BY p.DatabaseName; - ' - - IF @Debug = 1 - PRINT @StringToExecute; - - INSERT #Trending ( DatabaseName, DatabaseGUID, [0], [-1], [-2], [-3], [-4], [-5], [-6], [-7], [-8], [-9], [-10], [-11], [-12] ) - EXEC sys.sp_executesql @StringToExecute, N'@StartTime DATETIME2', @StartTime; - -END - -/*End Trending*/ - -/*End populating Recoverability*/ +CREATE TABLE #missing_index_usage +( + QueryHash BINARY(8), + SqlHandle VARBINARY(64), + impact FLOAT, + database_name NVARCHAR(128), + schema_name NVARCHAR(128), + table_name NVARCHAR(128), + usage NVARCHAR(128), + index_xml XML +); -RAISERROR('Returning data', 0, 1) WITH NOWAIT; - SELECT b.* - FROM #Backups AS b - ORDER BY b.database_name; +CREATE TABLE #missing_index_detail +( + QueryHash BINARY(8), + SqlHandle VARBINARY(64), + impact FLOAT, + database_name NVARCHAR(128), + schema_name NVARCHAR(128), + table_name NVARCHAR(128), + usage NVARCHAR(128), + column_name NVARCHAR(128) +); - SELECT r.*, - t.[0], t.[-1], t.[-2], t.[-3], t.[-4], t.[-5], t.[-6], t.[-7], t.[-8], t.[-9], t.[-10], t.[-11], t.[-12] - FROM #Recoverability AS r - LEFT JOIN #Trending t - ON r.DatabaseName = t.DatabaseName - AND r.DatabaseGUID = t.DatabaseGUID - WHERE r.LastBackupRecoveryModel IS NOT NULL - ORDER BY r.DatabaseName +CREATE TABLE #missing_index_pretty +( + QueryHash BINARY(8), + SqlHandle VARBINARY(64), + impact FLOAT, + database_name NVARCHAR(128), + schema_name NVARCHAR(128), + table_name NVARCHAR(128), + equality NVARCHAR(MAX), + inequality NVARCHAR(MAX), + [include] NVARCHAR(MAX), + executions NVARCHAR(128), + query_cost NVARCHAR(128), + creation_hours NVARCHAR(128), + is_spool BIT, + details AS N'/* ' + + CHAR(10) + + CASE is_spool + WHEN 0 + THEN N'The Query Processor estimates that implementing the ' + ELSE N'We estimate that implementing the ' + END + + N'following index could improve query cost (' + query_cost + N')' + + CHAR(10) + + N'by ' + + CONVERT(NVARCHAR(30), impact) + + N'% for ' + executions + N' executions of the query' + + N' over the last ' + + CASE WHEN creation_hours < 24 + THEN creation_hours + N' hours.' + WHEN creation_hours = 24 + THEN ' 1 day.' + WHEN creation_hours > 24 + THEN (CONVERT(NVARCHAR(128), creation_hours / 24)) + N' days.' + ELSE N'' + END + + CHAR(10) + + N'*/' + + CHAR(10) + CHAR(13) + + N'/* ' + + CHAR(10) + + N'USE ' + + database_name + + CHAR(10) + + N'GO' + + CHAR(10) + CHAR(13) + + N'CREATE NONCLUSTERED INDEX ix_' + + ISNULL(REPLACE(REPLACE(REPLACE(equality,'[', ''), ']', ''), ', ', '_'), '') + + ISNULL(REPLACE(REPLACE(REPLACE(inequality,'[', ''), ']', ''), ', ', '_'), '') + + CASE WHEN [include] IS NOT NULL THEN + N'_Includes' ELSE N'' END + + CHAR(10) + + N' ON ' + + schema_name + + N'.' + + table_name + + N' (' + + + CASE WHEN equality IS NOT NULL + THEN equality + + CASE WHEN inequality IS NOT NULL + THEN N', ' + inequality + ELSE N'' + END + ELSE inequality + END + + N')' + + CHAR(10) + + CASE WHEN include IS NOT NULL + THEN N'INCLUDE (' + include + N') WITH (FILLFACTOR=100, ONLINE=?, SORT_IN_TEMPDB=?, DATA_COMPRESSION=?);' + ELSE N' WITH (FILLFACTOR=100, ONLINE=?, SORT_IN_TEMPDB=?, DATA_COMPRESSION=?);' + END + + CHAR(10) + + N'GO' + + CHAR(10) + + N'*/' +); -RAISERROR('Rules analysis starting', 0, 1) WITH NOWAIT; -/*Looking for out of band backups by finding most common backup operator user_name and noting backups taken by other user_names*/ +CREATE TABLE #index_spool_ugly +( + QueryHash BINARY(8), + SqlHandle VARBINARY(64), + impact FLOAT, + database_name NVARCHAR(128), + schema_name NVARCHAR(128), + table_name NVARCHAR(128), + equality NVARCHAR(MAX), + inequality NVARCHAR(MAX), + [include] NVARCHAR(MAX), + executions NVARCHAR(128), + query_cost NVARCHAR(128), + creation_hours NVARCHAR(128) +); - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - SET @StringToExecute += N' - WITH common_people AS ( - SELECT TOP 1 b.user_name, COUNT_BIG(*) AS Records - FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b - GROUP BY b.user_name - ORDER BY Records DESC - ) - SELECT - 1 AS CheckId, - 100 AS [Priority], - b.database_name AS [Database Name], - ''Non-Agent backups taken'' AS [Finding], - ''The database '' + QUOTENAME(b.database_name) + '' has been backed up by '' + QUOTENAME(b.user_name) + '' '' + CONVERT(VARCHAR(10), COUNT(*)) + '' times.'' AS [Warning] - FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b - WHERE b.user_name NOT LIKE ''%Agent%'' AND b.user_name NOT LIKE ''%AGENT%'' - AND NOT EXISTS ( - SELECT 1 - FROM common_people AS cp - WHERE cp.user_name = b.user_name - ) - GROUP BY b.database_name, b.user_name - HAVING COUNT(*) > 1;' + @crlf; +CREATE TABLE #ReadableDBs +( +database_id INT +); - IF @Debug = 1 - PRINT @StringToExecute; - INSERT #Warnings (CheckId, Priority, DatabaseName, Finding, Warning ) - EXEC sys.sp_executesql @StringToExecute; - - /*Looking for compatibility level changing. Only looking for databases that have changed more than twice (It''s possible someone may have changed up, had CE problems, and then changed back)*/ +CREATE TABLE #plan_usage +( + duplicate_plan_hashes BIGINT NULL, + percent_duplicate DECIMAL(9, 2) NULL, + single_use_plan_count BIGINT NULL, + percent_single DECIMAL(9, 2) NULL, + total_plans BIGINT NULL, + spid INT +); - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - SET @StringToExecute += N'SELECT - 2 AS CheckId, - 100 AS [Priority], - b.database_name AS [Database Name], - ''Compatibility level changing'' AS [Finding], - ''The database '' + QUOTENAME(b.database_name) + '' has changed compatibility levels '' + CONVERT(VARCHAR(10), COUNT(DISTINCT b.compatibility_level)) + '' times.'' AS [Warning] - FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b - GROUP BY b.database_name - HAVING COUNT(DISTINCT b.compatibility_level) > 2;' + @crlf; +IF EXISTS (SELECT * FROM sys.all_objects o WHERE o.name = 'dm_hadr_database_replica_states') +BEGIN + RAISERROR('Checking for Read intent databases to exclude',0,0) WITH NOWAIT; - IF @Debug = 1 - PRINT @StringToExecute; + EXEC('INSERT INTO #ReadableDBs (database_id) SELECT DBs.database_id FROM sys.databases DBs INNER JOIN sys.availability_replicas Replicas ON DBs.replica_id = Replicas.replica_id WHERE replica_server_name NOT IN (SELECT DISTINCT primary_replica FROM sys.dm_hadr_availability_group_states States) AND Replicas.secondary_role_allow_connections_desc = ''READ_ONLY'' AND replica_server_name = @@SERVERNAME OPTION (RECOMPILE);'); + EXEC('INSERT INTO #ReadableDBs VALUES (32767) ;'); -- Exclude internal resource database as well +END - INSERT #Warnings ( CheckId, Priority, DatabaseName, Finding, Warning ) - EXEC sys.sp_executesql @StringToExecute; - - /*Looking for password protected backups. This hasn''t been a popular option ever, and was largely replaced by encrypted backups, but it''s simple to check for.*/ +RAISERROR(N'Checking plan cache age', 0, 1) WITH NOWAIT; +WITH x AS ( +SELECT SUM(CASE WHEN DATEDIFF(HOUR, deqs.creation_time, SYSDATETIME()) <= 24 THEN 1 ELSE 0 END) AS [plans_24], + SUM(CASE WHEN DATEDIFF(HOUR, deqs.creation_time, SYSDATETIME()) <= 4 THEN 1 ELSE 0 END) AS [plans_4], + SUM(CASE WHEN DATEDIFF(HOUR, deqs.creation_time, SYSDATETIME()) <= 1 THEN 1 ELSE 0 END) AS [plans_1], + COUNT(deqs.creation_time) AS [total_plans] +FROM sys.dm_exec_query_stats AS deqs +) +INSERT INTO #plan_creation ( percent_24, percent_4, percent_1, total_plans, SPID ) +SELECT CONVERT(DECIMAL(5,2), NULLIF(x.plans_24, 0) / (1. * NULLIF(x.total_plans, 0))) * 100 AS [percent_24], + CONVERT(DECIMAL(5,2), NULLIF(x.plans_4 , 0) / (1. * NULLIF(x.total_plans, 0))) * 100 AS [percent_4], + CONVERT(DECIMAL(5,2), NULLIF(x.plans_1 , 0) / (1. * NULLIF(x.total_plans, 0))) * 100 AS [percent_1], + x.total_plans, + @@SPID AS SPID +FROM x +OPTION (RECOMPILE); - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - SET @StringToExecute += N'SELECT - 3 AS CheckId, - 100 AS [Priority], - b.database_name AS [Database Name], - ''Password backups'' AS [Finding], - ''The database '' + QUOTENAME(b.database_name) + '' has been backed up with a password '' + CONVERT(VARCHAR(10), COUNT(*)) + '' times. Who has the password?'' AS [Warning] - FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b - WHERE b.is_password_protected = 1 - GROUP BY b.database_name;' + @crlf; - - IF @Debug = 1 - PRINT @StringToExecute; - - INSERT #Warnings ( CheckId, Priority, DatabaseName, Finding, Warning ) - EXEC sys.sp_executesql @StringToExecute; - - /*Looking for snapshot backups. There are legit reasons for these, but we should flag them so the questions get asked. What questions? Good question.*/ - - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; +RAISERROR(N'Checking for single use plans and plans with many queries', 0, 1) WITH NOWAIT; +WITH total_plans AS +( + SELECT + COUNT_BIG(deqs.query_plan_hash) AS total_plans + FROM sys.dm_exec_query_stats AS deqs +), + many_plans AS +( + SELECT + SUM(x.duplicate_plan_hashes) AS duplicate_plan_hashes + FROM + ( + SELECT + COUNT_BIG(qs.query_plan_hash) AS duplicate_plan_hashes + FROM sys.dm_exec_query_stats qs + LEFT JOIN sys.dm_exec_procedure_stats ps ON qs.plan_handle = ps.plan_handle + CROSS APPLY sys.dm_exec_plan_attributes(qs.plan_handle) pa + WHERE pa.attribute = N'dbid' + AND pa.value <> 32767 /*Omit Resource database-based queries, we're not going to "fix" them no matter what. Addresses #3314*/ + AND qs.query_plan_hash <> 0x0000000000000000 + GROUP BY + /* qs.query_plan_hash, BGO 20210524 commenting this out to fix #2909 */ + qs.query_hash, + ps.object_id, + pa.value + HAVING COUNT_BIG(qs.query_plan_hash) > 5 + ) AS x +), + single_use_plans AS +( + SELECT + COUNT_BIG(*) AS single_use_plan_count + FROM sys.dm_exec_query_stats AS s + WHERE s.execution_count = 1 +) +INSERT + #plan_usage +( + duplicate_plan_hashes, + percent_duplicate, + single_use_plan_count, + percent_single, + total_plans, + spid +) +SELECT + m.duplicate_plan_hashes, + CONVERT + ( + decimal(5,2), + m.duplicate_plan_hashes + / (1. * NULLIF(t.total_plans, 0)) + ) * 100. AS percent_duplicate, + s.single_use_plan_count, + CONVERT + ( + decimal(5,2), + s.single_use_plan_count + / (1. * NULLIF(t.total_plans, 0)) + ) * 100. AS percent_single, + t.total_plans, + @@SPID +FROM many_plans AS m +CROSS JOIN single_use_plans AS s +CROSS JOIN total_plans AS t; - SET @StringToExecute += N'SELECT - 4 AS CheckId, - 100 AS [Priority], - b.database_name AS [Database Name], - ''Snapshot backups'' AS [Finding], - ''The database '' + QUOTENAME(b.database_name) + '' has had '' + CONVERT(VARCHAR(10), COUNT(*)) + '' snapshot backups. This message is purely informational.'' AS [Warning] - FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b - WHERE b.is_snapshot = 1 - GROUP BY b.database_name;' + @crlf; - IF @Debug = 1 - PRINT @StringToExecute; +/* +Erik Darling: + Quoting this out to see if the above query fixes the issue + 2021-05-17, Issue #2909 - INSERT #Warnings ( CheckId, Priority, DatabaseName, Finding, Warning ) - EXEC sys.sp_executesql @StringToExecute; - - /*It''s fine to take backups of read only databases, but it''s not always necessary (there''s no new data, after all).*/ +UPDATE #plan_usage + SET percent_duplicate = CASE WHEN percent_duplicate > 100 THEN 100 ELSE percent_duplicate END, + percent_single = CASE WHEN percent_duplicate > 100 THEN 100 ELSE percent_duplicate END; +*/ - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; +SET @OnlySqlHandles = LTRIM(RTRIM(@OnlySqlHandles)) ; +SET @OnlyQueryHashes = LTRIM(RTRIM(@OnlyQueryHashes)) ; +SET @IgnoreQueryHashes = LTRIM(RTRIM(@IgnoreQueryHashes)) ; - SET @StringToExecute += N'SELECT - 5 AS CheckId, - 100 AS [Priority], - b.database_name AS [Database Name], - ''Read only state backups'' AS [Finding], - ''The database '' + QUOTENAME(b.database_name) + '' has been backed up '' + CONVERT(VARCHAR(10), COUNT(*)) + '' times while in a read-only state. This can be normal if it''''s a secondary, but a bit odd otherwise.'' AS [Warning] - FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b - WHERE b.is_readonly = 1 - GROUP BY b.database_name;' + @crlf; +DECLARE @individual VARCHAR(100) ; - IF @Debug = 1 - PRINT @StringToExecute; +IF (@OnlySqlHandles IS NOT NULL AND @IgnoreSqlHandles IS NOT NULL) +BEGIN +RAISERROR('You shouldn''t need to ignore and filter on SqlHandle at the same time.', 0, 1) WITH NOWAIT; +RETURN; +END; - INSERT #Warnings ( CheckId, Priority, DatabaseName, Finding, Warning ) - EXEC sys.sp_executesql @StringToExecute; - - /*So, I''ve come across people who think they need to change their database to single user mode to take a backup. Or that doing that will help something. I just need to know, here.*/ +IF (@StoredProcName IS NOT NULL AND (@OnlySqlHandles IS NOT NULL OR @IgnoreSqlHandles IS NOT NULL)) +BEGIN +RAISERROR('You can''t filter on stored procedure name and SQL Handle.', 0, 1) WITH NOWAIT; +RETURN; +END; - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; +IF @OnlySqlHandles IS NOT NULL + AND LEN(@OnlySqlHandles) > 0 +BEGIN + RAISERROR(N'Processing SQL Handles', 0, 1) WITH NOWAIT; + SET @individual = ''; - SET @StringToExecute += N'SELECT - 6 AS CheckId, - 100 AS [Priority], - b.database_name AS [Database Name], - ''Single user mode backups'' AS [Finding], - ''The database '' + QUOTENAME(b.database_name) + '' has been backed up '' + CONVERT(VARCHAR(10), COUNT(*)) + '' times while in single-user mode. This is really weird! Make sure your backup process doesn''''t include a mode change anywhere.'' AS [Warning] - FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b - WHERE b.is_single_user = 1 - GROUP BY b.database_name;' + @crlf; + WHILE LEN(@OnlySqlHandles) > 0 + BEGIN + IF PATINDEX('%,%', @OnlySqlHandles) > 0 + BEGIN + SET @individual = SUBSTRING(@OnlySqlHandles, 0, PATINDEX('%,%',@OnlySqlHandles)) ; + + INSERT INTO #only_sql_handles + SELECT CAST('' AS XML).value('xs:hexBinary( substring(sql:variable("@individual"), sql:column("t.pos")) )', 'varbinary(max)') + FROM (SELECT CASE SUBSTRING(@individual, 1, 2) WHEN '0x' THEN 3 ELSE 0 END) AS t(pos) + OPTION (RECOMPILE) ; + + --SELECT CAST(SUBSTRING(@individual, 1, 2) AS BINARY(8)); - IF @Debug = 1 - PRINT @StringToExecute; + SET @OnlySqlHandles = SUBSTRING(@OnlySqlHandles, LEN(@individual + ',') + 1, LEN(@OnlySqlHandles)) ; + END; + ELSE + BEGIN + SET @individual = @OnlySqlHandles; + SET @OnlySqlHandles = NULL; - INSERT #Warnings ( CheckId, Priority, DatabaseName, Finding, Warning ) - EXEC sys.sp_executesql @StringToExecute; - - /*C''mon, it''s 2017. Take your backups with CHECKSUMS, people.*/ + INSERT INTO #only_sql_handles + SELECT CAST('' AS XML).value('xs:hexBinary( substring(sql:variable("@individual"), sql:column("t.pos")) )', 'varbinary(max)') + FROM (SELECT CASE SUBSTRING(@individual, 1, 2) WHEN '0x' THEN 3 ELSE 0 END) AS t(pos) + OPTION (RECOMPILE) ; - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; + --SELECT CAST(SUBSTRING(@individual, 1, 2) AS VARBINARY(MAX)) ; + END; + END; +END; - SET @StringToExecute += N'SELECT - 7 AS CheckId, - 100 AS [Priority], - b.database_name AS [Database Name], - ''No CHECKSUMS'' AS [Finding], - ''The database '' + QUOTENAME(b.database_name) + '' has been backed up '' + CONVERT(VARCHAR(10), COUNT(*)) + '' times without CHECKSUMS in the past 30 days. CHECKSUMS can help alert you to corruption errors.'' AS [Warning] - FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b - WHERE b.has_backup_checksums = 0 - AND b.backup_finish_date >= DATEADD(DAY, -30, SYSDATETIME()) - GROUP BY b.database_name;' + @crlf; +IF @IgnoreSqlHandles IS NOT NULL + AND LEN(@IgnoreSqlHandles) > 0 +BEGIN + RAISERROR(N'Processing SQL Handles To Ignore', 0, 1) WITH NOWAIT; + SET @individual = ''; - IF @Debug = 1 - PRINT @StringToExecute; + WHILE LEN(@IgnoreSqlHandles) > 0 + BEGIN + IF PATINDEX('%,%', @IgnoreSqlHandles) > 0 + BEGIN + SET @individual = SUBSTRING(@IgnoreSqlHandles, 0, PATINDEX('%,%',@IgnoreSqlHandles)) ; + + INSERT INTO #ignore_sql_handles + SELECT CAST('' AS XML).value('xs:hexBinary( substring(sql:variable("@individual"), sql:column("t.pos")) )', 'varbinary(max)') + FROM (SELECT CASE SUBSTRING(@individual, 1, 2) WHEN '0x' THEN 3 ELSE 0 END) AS t(pos) + OPTION (RECOMPILE) ; + + --SELECT CAST(SUBSTRING(@individual, 1, 2) AS BINARY(8)); - INSERT #Warnings ( CheckId, Priority, DatabaseName, Finding, Warning ) - EXEC sys.sp_executesql @StringToExecute; - - /*Damaged is a Black Flag album. You don''t want your backups to be like a Black Flag album. */ + SET @IgnoreSqlHandles = SUBSTRING(@IgnoreSqlHandles, LEN(@individual + ',') + 1, LEN(@IgnoreSqlHandles)) ; + END; + ELSE + BEGIN + SET @individual = @IgnoreSqlHandles; + SET @IgnoreSqlHandles = NULL; - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; + INSERT INTO #ignore_sql_handles + SELECT CAST('' AS XML).value('xs:hexBinary( substring(sql:variable("@individual"), sql:column("t.pos")) )', 'varbinary(max)') + FROM (SELECT CASE SUBSTRING(@individual, 1, 2) WHEN '0x' THEN 3 ELSE 0 END) AS t(pos) + OPTION (RECOMPILE) ; - SET @StringToExecute += N'SELECT - 8 AS CheckId, - 100 AS [Priority], - b.database_name AS [Database Name], - ''Damaged backups'' AS [Finding], - ''The database '' + QUOTENAME(b.database_name) + '' has had '' + CONVERT(VARCHAR(10), COUNT(*)) + '' damaged backups taken without stopping to throw an error. This is done by specifying CONTINUE_AFTER_ERROR in your BACKUP commands.'' AS [Warning] - FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b - WHERE b.is_damaged = 1 - GROUP BY b.database_name;' + @crlf; + --SELECT CAST(SUBSTRING(@individual, 1, 2) AS VARBINARY(MAX)) ; + END; + END; +END; - IF @Debug = 1 - PRINT @StringToExecute; +IF @StoredProcName IS NOT NULL AND @StoredProcName <> N'' - INSERT #Warnings ( CheckId, Priority, DatabaseName, Finding, Warning ) - EXEC sys.sp_executesql @StringToExecute; +BEGIN + RAISERROR(N'Setting up filter for stored procedure name', 0, 1) WITH NOWAIT; - /*Checking for encrypted backups and the last backup of the encryption key.*/ - - /*2014 ONLY*/ - -IF @ProductVersionMajor >= 12 - BEGIN + DECLARE @function_search_sql NVARCHAR(MAX) = N'' + + INSERT #only_sql_handles + ( sql_handle ) + SELECT ISNULL(deps.sql_handle, CONVERT(VARBINARY(64),'0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000')) + FROM sys.dm_exec_procedure_stats AS deps + WHERE OBJECT_NAME(deps.object_id, deps.database_id) = @StoredProcName - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - - SET @StringToExecute += N'SELECT - 9 AS CheckId, - 100 AS [Priority], - b.database_name AS [Database Name], - ''Encrypted backups'' AS [Finding], - ''The database '' + QUOTENAME(b.database_name) + '' has had '' + CONVERT(VARCHAR(10), COUNT(*)) + '' '' + b.encryptor_type + '' backups, and the last time a certificate was backed up is ' - + CASE WHEN LOWER(@MSDBName) <> N'msdb' - THEN + N'...well, that information is on another server, anyway.'' AS [Warning]' - ELSE + CONVERT(VARCHAR(30), (SELECT MAX(c.pvt_key_last_backup_date) FROM sys.certificates AS c WHERE c.name NOT LIKE '##%')) + N'.'' AS [Warning]' - END + - N' - FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b - WHERE b.encryptor_type IS NOT NULL - GROUP BY b.database_name, b.encryptor_type;' + @crlf; - - IF @Debug = 1 - PRINT @StringToExecute; + UNION ALL + + SELECT ISNULL(dets.sql_handle, CONVERT(VARBINARY(64),'0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000')) + FROM sys.dm_exec_trigger_stats AS dets + WHERE OBJECT_NAME(dets.object_id, dets.database_id) = @StoredProcName + OPTION (RECOMPILE); - INSERT #Warnings ( CheckId, Priority, DatabaseName, Finding, Warning ) - EXEC sys.sp_executesql @StringToExecute; - - END - - /*Looking for backups that have BULK LOGGED data in them -- this can screw up point in time LOG recovery.*/ + IF EXISTS (SELECT 1/0 FROM sys.all_objects AS o WHERE o.name = 'dm_exec_function_stats') + BEGIN + SET @function_search_sql = @function_search_sql + N' + SELECT ISNULL(defs.sql_handle, CONVERT(VARBINARY(64),''0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'')) + FROM sys.dm_exec_function_stats AS defs + WHERE OBJECT_NAME(defs.object_id, defs.database_id) = @i_StoredProcName + OPTION (RECOMPILE); + ' + INSERT #only_sql_handles ( sql_handle ) + EXEC sys.sp_executesql @function_search_sql, N'@i_StoredProcName NVARCHAR(128)', @StoredProcName + END + + IF (SELECT COUNT(*) FROM #only_sql_handles) = 0 + BEGIN + RAISERROR(N'No information for that stored procedure was found.', 0, 1) WITH NOWAIT; + RETURN; + END; - SET @StringToExecute =N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; +END; - SET @StringToExecute += N'SELECT - 10 AS CheckId, - 100 AS [Priority], - b.database_name AS [Database Name], - ''Bulk logged backups'' AS [Finding], - ''The database '' + QUOTENAME(b.database_name) + '' has had '' + CONVERT(VARCHAR(10), COUNT(*)) + '' backups with bulk logged data. This can make point in time recovery awkward. '' AS [Warning] - FROM ' + QUOTENAME(@MSDBName) + '.dbo.backupset AS b - WHERE b.has_bulk_logged_data = 1 - GROUP BY b.database_name;' + @crlf; - IF @Debug = 1 - PRINT @StringToExecute; - INSERT #Warnings ( CheckId, Priority, DatabaseName, Finding, Warning ) - EXEC sys.sp_executesql @StringToExecute; - - /*Looking for recovery model being switched between FULL and SIMPLE, because it''s a bad practice.*/ +IF ((@OnlyQueryHashes IS NOT NULL AND LEN(@OnlyQueryHashes) > 0) + OR (@IgnoreQueryHashes IS NOT NULL AND LEN(@IgnoreQueryHashes) > 0)) + AND LEFT(@QueryFilter, 3) IN ('pro', 'fun') +BEGIN + RAISERROR('You cannot limit by query hash and filter by stored procedure', 16, 1); + RETURN; +END; - SET @StringToExecute =N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; +/* If the user is attempting to limit by query hash, set up the + #only_query_hashes temp table. This will be used to narrow down + results. - SET @StringToExecute += N'SELECT - 11 AS CheckId, - 100 AS [Priority], - b.database_name AS [Database Name], - ''Recovery model switched'' AS [Finding], - ''The database '' + QUOTENAME(b.database_name) + '' has changed recovery models from between FULL and SIMPLE '' + CONVERT(VARCHAR(10), COUNT(DISTINCT b.recovery_model)) + '' times. This breaks the log chain and is generally a bad idea.'' AS [Warning] - FROM ' + QUOTENAME(@MSDBName) + '.dbo.backupset AS b - WHERE b.recovery_model <> ''BULK-LOGGED'' - GROUP BY b.database_name - HAVING COUNT(DISTINCT b.recovery_model) > 4;' + @crlf; + Just a reminder: Using @OnlyQueryHashes will ignore stored + procedures and triggers. + */ +IF @OnlyQueryHashes IS NOT NULL + AND LEN(@OnlyQueryHashes) > 0 +BEGIN + RAISERROR(N'Setting up filter for Query Hashes', 0, 1) WITH NOWAIT; + SET @individual = ''; - IF @Debug = 1 - PRINT @StringToExecute; + WHILE LEN(@OnlyQueryHashes) > 0 + BEGIN + IF PATINDEX('%,%', @OnlyQueryHashes) > 0 + BEGIN + SET @individual = SUBSTRING(@OnlyQueryHashes, 0, PATINDEX('%,%',@OnlyQueryHashes)) ; + + INSERT INTO #only_query_hashes + SELECT CAST('' AS XML).value('xs:hexBinary( substring(sql:variable("@individual"), sql:column("t.pos")) )', 'varbinary(max)') + FROM (SELECT CASE SUBSTRING(@individual, 1, 2) WHEN '0x' THEN 3 ELSE 0 END) AS t(pos) + OPTION (RECOMPILE) ; + + --SELECT CAST(SUBSTRING(@individual, 1, 2) AS BINARY(8)); - INSERT #Warnings ( CheckId, Priority, DatabaseName, Finding, Warning ) - EXEC sys.sp_executesql @StringToExecute; + SET @OnlyQueryHashes = SUBSTRING(@OnlyQueryHashes, LEN(@individual + ',') + 1, LEN(@OnlyQueryHashes)) ; + END; + ELSE + BEGIN + SET @individual = @OnlyQueryHashes; + SET @OnlyQueryHashes = NULL; - /*Looking for uncompressed backups.*/ + INSERT INTO #only_query_hashes + SELECT CAST('' AS XML).value('xs:hexBinary( substring(sql:variable("@individual"), sql:column("t.pos")) )', 'varbinary(max)') + FROM (SELECT CASE SUBSTRING(@individual, 1, 2) WHEN '0x' THEN 3 ELSE 0 END) AS t(pos) + OPTION (RECOMPILE) ; - SET @StringToExecute =N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; + --SELECT CAST(SUBSTRING(@individual, 1, 2) AS VARBINARY(MAX)) ; + END; + END; +END; - SET @StringToExecute += N'SELECT - 12 AS CheckId, - 100 AS [Priority], - b.database_name AS [Database Name], - ''Uncompressed backups'' AS [Finding], - ''The database '' + QUOTENAME(b.database_name) + '' has had '' + CONVERT(VARCHAR(10), COUNT(*)) + '' uncompressed backups in the last 30 days. This is a free way to save time and space. And SPACETIME. If your version of SQL supports it.'' AS [Warning] - FROM ' + QUOTENAME(@MSDBName) + '.dbo.backupset AS b - WHERE backup_size = compressed_backup_size AND type = ''D'' - AND b.backup_finish_date >= DATEADD(DAY, -30, SYSDATETIME()) - GROUP BY b.database_name;' + @crlf; +/* If the user is setting up a list of query hashes to ignore, those + values will be inserted into #ignore_query_hashes. This is used to + exclude values from query results. - IF @Debug = 1 - PRINT @StringToExecute; + Just a reminder: Using @IgnoreQueryHashes will ignore stored + procedures and triggers. + */ +IF @IgnoreQueryHashes IS NOT NULL + AND LEN(@IgnoreQueryHashes) > 0 +BEGIN + RAISERROR(N'Setting up filter to ignore query hashes', 0, 1) WITH NOWAIT; + SET @individual = '' ; - INSERT #Warnings ( CheckId, Priority, DatabaseName, Finding, Warning ) - EXEC sys.sp_executesql @StringToExecute; + WHILE LEN(@IgnoreQueryHashes) > 0 + BEGIN + IF PATINDEX('%,%', @IgnoreQueryHashes) > 0 + BEGIN + SET @individual = SUBSTRING(@IgnoreQueryHashes, 0, PATINDEX('%,%',@IgnoreQueryHashes)) ; + + INSERT INTO #ignore_query_hashes + SELECT CAST('' AS XML).value('xs:hexBinary( substring(sql:variable("@individual"), sql:column("t.pos")) )', 'varbinary(max)') + FROM (SELECT CASE SUBSTRING(@individual, 1, 2) WHEN '0x' THEN 3 ELSE 0 END) AS t(pos) + OPTION (RECOMPILE) ; + + SET @IgnoreQueryHashes = SUBSTRING(@IgnoreQueryHashes, LEN(@individual + ',') + 1, LEN(@IgnoreQueryHashes)) ; + END; + ELSE + BEGIN + SET @individual = @IgnoreQueryHashes ; + SET @IgnoreQueryHashes = NULL ; -RAISERROR('Rules analysis starting on temp tables', 0, 1) WITH NOWAIT; + INSERT INTO #ignore_query_hashes + SELECT CAST('' AS XML).value('xs:hexBinary( substring(sql:variable("@individual"), sql:column("t.pos")) )', 'varbinary(max)') + FROM (SELECT CASE SUBSTRING(@individual, 1, 2) WHEN '0x' THEN 3 ELSE 0 END) AS t(pos) + OPTION (RECOMPILE) ; + END; + END; +END; - INSERT #Warnings ( CheckId, Priority, DatabaseName, Finding, Warning ) - SELECT - 13 AS CheckId, - 100 AS Priority, - r.DatabaseName as [DatabaseName], - 'Big Diffs' AS [Finding], - 'On average, Differential backups for this database are >=40% of the size of the average Full backup.' AS [Warning] - FROM #Recoverability AS r - WHERE r.IsBigDiff = 1 +IF @ConfigurationDatabaseName IS NOT NULL +BEGIN + RAISERROR(N'Reading values from Configuration Database', 0, 1) WITH NOWAIT; + DECLARE @config_sql NVARCHAR(MAX) = N'INSERT INTO #configuration SELECT parameter_name, value FROM ' + + QUOTENAME(@ConfigurationDatabaseName) + + '.' + QUOTENAME(@ConfigurationSchemaName) + + '.' + QUOTENAME(@ConfigurationTableName) + + ' ; ' ; + EXEC(@config_sql); +END; - INSERT #Warnings ( CheckId, Priority, DatabaseName, Finding, Warning ) - SELECT - 13 AS CheckId, - 100 AS Priority, - r.DatabaseName as [DatabaseName], - 'Big Logs' AS [Finding], - 'On average, Log backups for this database are >=20% of the size of the average Full backup.' AS [Warning] - FROM #Recoverability AS r - WHERE r.IsBigLog = 1 +RAISERROR(N'Setting up variables', 0, 1) WITH NOWAIT; +DECLARE @sql NVARCHAR(MAX) = N'', + @insert_list NVARCHAR(MAX) = N'', + @plans_triggers_select_list NVARCHAR(MAX) = N'', + @body NVARCHAR(MAX) = N'', + @body_where NVARCHAR(MAX) = N'WHERE 1 = 1 ' + @nl, + @body_order NVARCHAR(MAX) = N'ORDER BY #sortable# DESC OPTION (RECOMPILE) ', + + @q NVARCHAR(1) = N'''', + @pv VARCHAR(20), + @pos TINYINT, + @v DECIMAL(6,2), + @build INT; +RAISERROR (N'Determining SQL Server version.',0,1) WITH NOWAIT; -/*Insert thank you stuff last*/ - INSERT #Warnings ( CheckId, Priority, DatabaseName, Finding, Warning ) +INSERT INTO #checkversion (version) +SELECT CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)) +OPTION (RECOMPILE); - SELECT - 2147483647 AS [CheckId], - 2147483647 AS [Priority], - 'From Your Community Volunteers' AS [DatabaseName], - 'sp_BlitzBackups Version: ' + @Version + ', Version Date: ' + CONVERT(VARCHAR(30), @VersionDate) + '.' AS [Finding], - 'Thanks for using our stored procedure. We hope you find it useful! Check out our other free SQL Server scripts at firstresponderkit.org!' AS [Warning]; -RAISERROR('Rules analysis finished', 0, 1) WITH NOWAIT; +SELECT @v = common_version , + @build = build +FROM #checkversion +OPTION (RECOMPILE); -SELECT w.CheckId, w.Priority, w.DatabaseName, w.Finding, w.Warning -FROM #Warnings AS w -ORDER BY w.Priority, w.CheckId; +IF (@SortOrder IN ('memory grant', 'avg memory grant')) AND @VersionShowsMemoryGrants = 0 +BEGIN + RAISERROR('Your version of SQL does not support sorting by memory grant or average memory grant. Please use another sort order.', 16, 1); + RETURN; +END; -DROP TABLE #Backups, #Warnings, #Recoverability, #RTORecoveryPoints +IF (@SortOrder IN ('spills', 'avg spills') AND @VersionShowsSpills = 0) +BEGIN + RAISERROR('Your version of SQL does not support sorting by spills. Please use another sort order.', 16, 1); + RETURN; +END; +IF ((LEFT(@QueryFilter, 3) = 'fun') AND (@v < 13)) +BEGIN + RAISERROR('Your version of SQL does not support filtering by functions. Please use another filter.', 16, 1); + RETURN; +END; -RETURN; +RAISERROR (N'Creating dynamic SQL based on SQL Server version.',0,1) WITH NOWAIT; -PushBackupHistoryToListener: +SET @insert_list += N' +SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; +INSERT INTO ##BlitzCacheProcs (SPID, QueryType, DatabaseName, AverageCPU, TotalCPU, AverageCPUPerMinute, PercentCPUByType, PercentDurationByType, + PercentReadsByType, PercentExecutionsByType, AverageDuration, TotalDuration, AverageReads, TotalReads, ExecutionCount, + ExecutionsPerMinute, TotalWrites, AverageWrites, PercentWritesByType, WritesPerMinute, PlanCreationTime, + LastExecutionTime, LastCompletionTime, StatementStartOffset, StatementEndOffset, PlanGenerationNum, MinReturnedRows, MaxReturnedRows, AverageReturnedRows, TotalReturnedRows, + LastReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, MaxUsedGrantKB, PercentMemoryGrantUsed, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, + QueryText, QueryPlan, TotalWorkerTimeForType, TotalElapsedTimeForType, TotalReadsForType, + TotalExecutionCountForType, TotalWritesForType, SqlHandle, PlanHandle, QueryHash, QueryPlanHash, + min_worker_time, max_worker_time, is_parallel, min_elapsed_time, max_elapsed_time, age_minutes, age_minutes_lifetime, Pattern) ' ; -RAISERROR('Pushing backup history to listener', 0, 1) WITH NOWAIT; +SET @body += N' +FROM (SELECT TOP (@Top) x.*, xpa.*, + CAST((CASE WHEN DATEDIFF(mi, cached_time, GETDATE()) > 0 AND execution_count > 1 + THEN DATEDIFF(mi, cached_time, GETDATE()) + ELSE NULL END) as MONEY) as age_minutes, + CAST((CASE WHEN DATEDIFF(mi, cached_time, last_execution_time) > 0 AND execution_count > 1 + THEN DATEDIFF(mi, cached_time, last_execution_time) + ELSE Null END) as MONEY) as age_minutes_lifetime + FROM sys.#view# x + CROSS APPLY (SELECT * FROM sys.dm_exec_plan_attributes(x.plan_handle) AS ixpa + WHERE ixpa.attribute = ''dbid'') AS xpa ' + @nl ; -DECLARE @msg NVARCHAR(4000) = N''; -DECLARE @RemoteCheck TABLE (c INT NULL); +IF @SortOrder = 'duplicate' /* Issue #3345 */ + BEGIN + SET @body += N' INNER JOIN #duplicate_query_filter AS dqf ON x.sql_handle = dqf.sql_handle AND x.plan_handle = dqf.plan_handle AND x.creation_time = dqf.duplicate_creation_time ' + @nl ; + END +IF @VersionShowsAirQuoteActualPlans = 1 + BEGIN + SET @body += N' CROSS APPLY sys.dm_exec_query_plan_stats(x.plan_handle) AS deqps ' + @nl ; + END -IF @WriteBackupsToDatabaseName IS NULL - BEGIN - RAISERROR('@WriteBackupsToDatabaseName can''t be NULL.', 16, 1) WITH NOWAIT - RETURN; - END +SET @body += N' WHERE 1 = 1 ' + @nl ; -IF LOWER(@WriteBackupsToDatabaseName) = N'msdb' - BEGIN - RAISERROR('We can''t write to the real msdb, we have to write to a fake msdb.', 16, 1) WITH NOWAIT - RETURN; - END + IF EXISTS (SELECT * FROM sys.all_objects o WHERE o.name = 'dm_hadr_database_replica_states') + BEGIN + RAISERROR(N'Ignoring readable secondaries databases by default', 0, 1) WITH NOWAIT; + SET @body += N' AND CAST(xpa.value AS INT) NOT IN (SELECT database_id FROM #ReadableDBs)' + @nl ; + END -IF @WriteBackupsToListenerName IS NULL -BEGIN - IF @AGName IS NULL - BEGIN - RAISERROR('@WriteBackupsToListenerName and @AGName can''t both be NULL.', 16, 1) WITH NOWAIT; - RETURN; - END - ELSE - BEGIN - SELECT @WriteBackupsToListenerName = dns_name - FROM sys.availability_groups AS ag - JOIN sys.availability_group_listeners AS agl - ON ag.group_id = agl.group_id - WHERE name = @AGName; - END +IF @IgnoreSystemDBs = 1 + BEGIN + RAISERROR(N'Ignoring system databases by default', 0, 1) WITH NOWAIT; + SET @body += N' AND COALESCE(LOWER(DB_NAME(CAST(xpa.value AS INT))), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'', ''dbmaintenance'', ''dbadmin'', ''dbatools'') AND COALESCE(DB_NAME(CAST(xpa.value AS INT)), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ; + END; -END +IF @DatabaseName IS NOT NULL OR @DatabaseName <> N'' + BEGIN + RAISERROR(N'Filtering database name chosen', 0, 1) WITH NOWAIT; + SET @body += N' AND CAST(xpa.value AS BIGINT) = DB_ID(N' + + QUOTENAME(@DatabaseName, N'''') + + N') ' + @nl; + END; -IF @WriteBackupsToListenerName IS NOT NULL +IF (SELECT COUNT(*) FROM #only_sql_handles) > 0 BEGIN - IF NOT EXISTS - ( - SELECT * - FROM sys.servers s - WHERE name = @WriteBackupsToListenerName - ) - BEGIN - SET @msg = N'We need a linked server to write data across. Please set one up for ' + @WriteBackupsToListenerName + N'.'; - RAISERROR(@msg, 16, 1) WITH NOWAIT; - RETURN; - END -END - - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - - SET @StringToExecute += N'SELECT TOP 1 1 FROM ' - + QUOTENAME(@WriteBackupsToListenerName) + N'.master.sys.databases d WHERE d.name = @i_WriteBackupsToDatabaseName;' - - IF @Debug = 1 - PRINT @StringToExecute; - - INSERT @RemoteCheck (c) - EXEC sp_executesql @StringToExecute, N'@i_WriteBackupsToDatabaseName NVARCHAR(256)', @i_WriteBackupsToDatabaseName = @WriteBackupsToDatabaseName; + RAISERROR(N'Including only chosen SQL Handles', 0, 1) WITH NOWAIT; + SET @body += N' AND EXISTS(SELECT 1/0 FROM #only_sql_handles q WHERE q.sql_handle = x.sql_handle) ' + @nl ; +END; - IF @@ROWCOUNT = 0 - BEGIN - SET @msg = N'The database ' + @WriteBackupsToDatabaseName + N' doesn''t appear to exist on that server.' - RAISERROR(@msg, 16, 1) WITH NOWAIT - RETURN; - END +IF (SELECT COUNT(*) FROM #ignore_sql_handles) > 0 +BEGIN + RAISERROR(N'Including only chosen SQL Handles', 0, 1) WITH NOWAIT; + SET @body += N' AND NOT EXISTS(SELECT 1/0 FROM #ignore_sql_handles q WHERE q.sql_handle = x.sql_handle) ' + @nl ; +END; +IF (SELECT COUNT(*) FROM #only_query_hashes) > 0 + AND (SELECT COUNT(*) FROM #ignore_query_hashes) = 0 + AND (SELECT COUNT(*) FROM #only_sql_handles) = 0 + AND (SELECT COUNT(*) FROM #ignore_sql_handles) = 0 +BEGIN + RAISERROR(N'Including only chosen Query Hashes', 0, 1) WITH NOWAIT; + SET @body += N' AND EXISTS(SELECT 1/0 FROM #only_query_hashes q WHERE q.query_hash = x.query_hash) ' + @nl ; +END; - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; +/* filtering for query hashes */ +IF (SELECT COUNT(*) FROM #ignore_query_hashes) > 0 + AND (SELECT COUNT(*) FROM #only_query_hashes) = 0 +BEGIN + RAISERROR(N'Excluding chosen Query Hashes', 0, 1) WITH NOWAIT; + SET @body += N' AND NOT EXISTS(SELECT 1/0 FROM #ignore_query_hashes iq WHERE iq.query_hash = x.query_hash) ' + @nl ; +END; +/* end filtering for query hashes */ - SET @StringToExecute += N'SELECT TOP 1 1 FROM ' - + QUOTENAME(@WriteBackupsToListenerName) + '.' + QUOTENAME(@WriteBackupsToDatabaseName) + '.sys.tables WHERE name = ''backupset'' AND SCHEMA_NAME(schema_id) = ''dbo''; - ' + @crlf; +IF @DurationFilter IS NOT NULL + BEGIN + RAISERROR(N'Setting duration filter', 0, 1) WITH NOWAIT; + SET @body += N' AND (total_elapsed_time / 1000.0) / execution_count > @min_duration ' + @nl ; + END; - IF @Debug = 1 - PRINT @StringToExecute; +IF @MinutesBack IS NOT NULL + BEGIN + RAISERROR(N'Setting minutes back filter', 0, 1) WITH NOWAIT; + SET @body += N' AND DATEADD(SECOND, (x.last_elapsed_time / 1000000.), x.last_execution_time) >= DATEADD(MINUTE, @min_back, GETDATE()) ' + @nl ; + END; - INSERT @RemoteCheck (c) - EXEC sp_executesql @StringToExecute; +IF @SlowlySearchPlansFor IS NOT NULL + BEGIN + RAISERROR(N'Setting string search for @SlowlySearchPlansFor, so remember, this is gonna be slow', 0, 1) WITH NOWAIT; + SET @SlowlySearchPlansFor = REPLACE((REPLACE((REPLACE((REPLACE(@SlowlySearchPlansFor, N'[', N'_')), N']', N'_')), N'^', N'_')), N'''', N''''''); + SET @body_where += N' AND CAST(qp.query_plan AS NVARCHAR(MAX)) LIKE N''%' + @SlowlySearchPlansFor + N'%'' ' + @nl; + END - IF @@ROWCOUNT = 0 - BEGIN - SET @msg = N'The database ' + @WriteBackupsToDatabaseName + N' doesn''t appear to have a table called dbo.backupset in it.' - RAISERROR(@msg, 0, 1) WITH NOWAIT - RAISERROR('Don''t worry, we''ll create it for you!', 0, 1) WITH NOWAIT - - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; +/* Apply the sort order here to only grab relevant plans. + This should make it faster to process since we'll be pulling back fewer + plans for processing. + */ +RAISERROR(N'Applying chosen sort order', 0, 1) WITH NOWAIT; +SELECT @body += N' ORDER BY ' + + CASE @SortOrder WHEN N'cpu' THEN N'total_worker_time' + WHEN N'reads' THEN N'total_logical_reads' + WHEN N'writes' THEN N'total_logical_writes' + WHEN N'duration' THEN N'total_elapsed_time' + WHEN N'executions' THEN N'execution_count' + WHEN N'compiles' THEN N'cached_time' + WHEN N'memory grant' THEN N'max_grant_kb' + WHEN N'unused grant' THEN N'max_grant_kb - max_used_grant_kb' + WHEN N'spills' THEN N'max_spills' + WHEN N'duplicate' THEN N'total_worker_time' /* Issue #3345 */ + /* And now the averages */ + WHEN N'avg cpu' THEN N'total_worker_time / execution_count' + WHEN N'avg reads' THEN N'total_logical_reads / execution_count' + WHEN N'avg writes' THEN N'total_logical_writes / execution_count' + WHEN N'avg duration' THEN N'total_elapsed_time / execution_count' + WHEN N'avg memory grant' THEN N'CASE WHEN max_grant_kb = 0 THEN 0 ELSE max_grant_kb / execution_count END' + WHEN N'avg spills' THEN N'CASE WHEN total_spills = 0 THEN 0 ELSE total_spills / execution_count END' + WHEN N'avg executions' THEN 'CASE WHEN execution_count = 0 THEN 0 + WHEN COALESCE(CAST((CASE WHEN DATEDIFF(mi, cached_time, GETDATE()) > 0 AND execution_count > 1 + THEN DATEDIFF(mi, cached_time, GETDATE()) + ELSE NULL END) as MONEY), CAST((CASE WHEN DATEDIFF(mi, cached_time, last_execution_time) > 0 AND execution_count > 1 + THEN DATEDIFF(mi, cached_time, last_execution_time) + ELSE Null END) as MONEY), 0) = 0 THEN 0 + ELSE CAST((1.00 * execution_count / COALESCE(CAST((CASE WHEN DATEDIFF(mi, cached_time, GETDATE()) > 0 AND execution_count > 1 + THEN DATEDIFF(mi, cached_time, GETDATE()) + ELSE NULL END) as MONEY), CAST((CASE WHEN DATEDIFF(mi, cached_time, last_execution_time) > 0 AND execution_count > 1 + THEN DATEDIFF(mi, cached_time, last_execution_time) + ELSE Null END) as MONEY))) AS money) + END ' + END + N' DESC ' + @nl ; - SET @StringToExecute += N'CREATE TABLE ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.dbo.backupset - ( backup_set_id INT IDENTITY(1, 1), backup_set_uuid UNIQUEIDENTIFIER, media_set_id INT, first_family_number TINYINT, first_media_number SMALLINT, - last_family_number TINYINT, last_media_number SMALLINT, catalog_family_number TINYINT, catalog_media_number SMALLINT, position INT, expiration_date DATETIME, - software_vendor_id INT, name NVARCHAR(128), description NVARCHAR(255), user_name NVARCHAR(128), software_major_version TINYINT, software_minor_version TINYINT, - software_build_version SMALLINT, time_zone SMALLINT, mtf_minor_version TINYINT, first_lsn NUMERIC(25, 0), last_lsn NUMERIC(25, 0), checkpoint_lsn NUMERIC(25, 0), - database_backup_lsn NUMERIC(25, 0), database_creation_date DATETIME, backup_start_date DATETIME, backup_finish_date DATETIME, type CHAR(1), sort_order SMALLINT, - code_page SMALLINT, compatibility_level TINYINT, database_version INT, backup_size NUMERIC(20, 0), database_name NVARCHAR(128), server_name NVARCHAR(128), - machine_name NVARCHAR(128), flags INT, unicode_locale INT, unicode_compare_style INT, collation_name NVARCHAR(128), is_password_protected BIT, recovery_model NVARCHAR(60), - has_bulk_logged_data BIT, is_snapshot BIT, is_readonly BIT, is_single_user BIT, has_backup_checksums BIT, is_damaged BIT, begins_log_chain BIT, has_incomplete_metadata BIT, - is_force_offline BIT, is_copy_only BIT, first_recovery_fork_guid UNIQUEIDENTIFIER, last_recovery_fork_guid UNIQUEIDENTIFIER, fork_point_lsn NUMERIC(25, 0), database_guid UNIQUEIDENTIFIER, - family_guid UNIQUEIDENTIFIER, differential_base_lsn NUMERIC(25, 0), differential_base_guid UNIQUEIDENTIFIER, compressed_backup_size NUMERIC(20, 0), key_algorithm NVARCHAR(32), - encryptor_thumbprint VARBINARY(20) , encryptor_type NVARCHAR(32) - ); - ' + @crlf; - - SET @InnerStringToExecute = N'EXEC( ''' + @StringToExecute + ''' ) AT ' + QUOTENAME(@WriteBackupsToListenerName) + N';' - - IF @Debug = 1 - PRINT @InnerStringToExecute; - - EXEC sp_executesql @InnerStringToExecute + +SET @body += N') AS qs + CROSS JOIN(SELECT SUM(execution_count) AS t_TotalExecs, + SUM(CAST(total_elapsed_time AS BIGINT) / 1000.0) AS t_TotalElapsed, + SUM(CAST(total_worker_time AS BIGINT) / 1000.0) AS t_TotalWorker, + SUM(CAST(total_logical_reads AS DECIMAL(30))) AS t_TotalReads, + SUM(CAST(total_logical_writes AS DECIMAL(30))) AS t_TotalWrites + FROM sys.#view#) AS t + CROSS APPLY sys.dm_exec_plan_attributes(qs.plan_handle) AS pa + CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) AS st + CROSS APPLY sys.dm_exec_query_plan(qs.plan_handle) AS qp ' + @nl ; - RAISERROR('We''ll even make the indexes!', 0, 1) WITH NOWAIT +IF @VersionShowsAirQuoteActualPlans = 1 + BEGIN + SET @body += N' CROSS APPLY sys.dm_exec_query_plan_stats(qs.plan_handle) AS deqps ' + @nl ; + END - /*Checking for and creating the PK/CX*/ +SET @body_where += N' AND pa.attribute = ' + QUOTENAME('dbid', @q ) + @nl ; - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - - SET @StringToExecute += N' - - IF NOT EXISTS ( - SELECT t.name, i.name - FROM ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.sys.tables AS t - JOIN ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.sys.indexes AS i - ON t.object_id = i.object_id - WHERE t.name = ? - AND i.name LIKE ? - ) +IF @NoobSaibot = 1 +BEGIN + SET @body_where += N' AND qp.query_plan.exist(''declare namespace p="http://schemas.microsoft.com/sqlserver/2004/07/showplan";//p:StmtSimple//p:MissingIndex'') = 1' + @nl ; +END - BEGIN - ALTER TABLE ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.[dbo].[backupset] ADD PRIMARY KEY CLUSTERED ([backup_set_id] ASC) - END - ' +SET @plans_triggers_select_list += N' +SELECT TOP (@Top) + @@SPID , + ''Procedure or Function: '' + + QUOTENAME(COALESCE(OBJECT_SCHEMA_NAME(qs.object_id, qs.database_id),'''')) + + ''.'' + + QUOTENAME(COALESCE(OBJECT_NAME(qs.object_id, qs.database_id),'''')) AS QueryType, + COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), N''-- N/A --'') AS DatabaseName, + (total_worker_time / 1000.0) / execution_count AS AvgCPU , + (total_worker_time / 1000.0) AS TotalCPU , + CASE WHEN total_worker_time = 0 THEN 0 + WHEN COALESCE(age_minutes, DATEDIFF(mi, qs.cached_time, qs.last_execution_time), 0) = 0 THEN 0 + ELSE CAST((total_worker_time / 1000.0) / COALESCE(age_minutes, DATEDIFF(mi, qs.cached_time, qs.last_execution_time)) AS MONEY) + END AS AverageCPUPerMinute , + CASE WHEN t.t_TotalWorker = 0 THEN 0 + ELSE CAST(ROUND(100.00 * (total_worker_time / 1000.0) / t.t_TotalWorker, 2) AS MONEY) + END AS PercentCPUByType, + CASE WHEN t.t_TotalElapsed = 0 THEN 0 + ELSE CAST(ROUND(100.00 * (total_elapsed_time / 1000.0) / t.t_TotalElapsed, 2) AS MONEY) + END AS PercentDurationByType, + CASE WHEN t.t_TotalReads = 0 THEN 0 + ELSE CAST(ROUND(100.00 * total_logical_reads / t.t_TotalReads, 2) AS MONEY) + END AS PercentReadsByType, + CASE WHEN t.t_TotalExecs = 0 THEN 0 + ELSE CAST(ROUND(100.00 * execution_count / t.t_TotalExecs, 2) AS MONEY) + END AS PercentExecutionsByType, + (total_elapsed_time / 1000.0) / execution_count AS AvgDuration , + (total_elapsed_time / 1000.0) AS TotalDuration , + total_logical_reads / execution_count AS AvgReads , + total_logical_reads AS TotalReads , + execution_count AS ExecutionCount , + CASE WHEN execution_count = 0 THEN 0 + WHEN COALESCE(age_minutes, DATEDIFF(mi, qs.cached_time, qs.last_execution_time), 0) = 0 THEN 0 + ELSE CAST((1.00 * execution_count / COALESCE(age_minutes, DATEDIFF(mi, qs.cached_time, qs.last_execution_time))) AS money) + END AS ExecutionsPerMinute , + total_logical_writes AS TotalWrites , + total_logical_writes / execution_count AS AverageWrites , + CASE WHEN t.t_TotalWrites = 0 THEN 0 + ELSE CAST(ROUND(100.00 * total_logical_writes / t.t_TotalWrites, 2) AS MONEY) + END AS PercentWritesByType, + CASE WHEN total_logical_writes = 0 THEN 0 + WHEN COALESCE(age_minutes, DATEDIFF(mi, qs.cached_time, qs.last_execution_time), 0) = 0 THEN 0 + ELSE CAST((1.00 * total_logical_writes / COALESCE(age_minutes, DATEDIFF(mi, qs.cached_time, qs.last_execution_time), 0)) AS money) + END AS WritesPerMinute, + qs.cached_time AS PlanCreationTime, + qs.last_execution_time AS LastExecutionTime, + DATEADD(SECOND, (qs.last_elapsed_time / 1000000.), qs.last_execution_time) AS LastCompletionTime, + NULL AS StatementStartOffset, + NULL AS StatementEndOffset, + NULL AS PlanGenerationNum, + NULL AS MinReturnedRows, + NULL AS MaxReturnedRows, + NULL AS AvgReturnedRows, + NULL AS TotalReturnedRows, + NULL AS LastReturnedRows, + NULL AS MinGrantKB, + NULL AS MaxGrantKB, + NULL AS MinUsedGrantKB, + NULL AS MaxUsedGrantKB, + NULL AS PercentMemoryGrantUsed, + NULL AS AvgMaxMemoryGrant,'; - SET @InnerStringToExecute = N'EXEC( ''' + @StringToExecute + ''', ''backupset'', ''PK[_][_]%'' ) AT ' + QUOTENAME(@WriteBackupsToListenerName) + N';' - - IF @Debug = 1 - PRINT @InnerStringToExecute; - - EXEC sp_executesql @InnerStringToExecute + IF @VersionShowsSpills = 1 + BEGIN + RAISERROR(N'Getting spill information for newer versions of SQL', 0, 1) WITH NOWAIT; + SET @plans_triggers_select_list += N' + min_spills AS MinSpills, + max_spills AS MaxSpills, + total_spills AS TotalSpills, + CAST(ISNULL(NULLIF(( total_spills * 1. ), 0) / NULLIF(execution_count, 0), 0) AS MONEY) AS AvgSpills, '; + END; + ELSE + BEGIN + RAISERROR(N'Substituting NULLs for spill columns in older versions of SQL', 0, 1) WITH NOWAIT; + SET @plans_triggers_select_list += N' + NULL AS MinSpills, + NULL AS MaxSpills, + NULL AS TotalSpills, + NULL AS AvgSpills, ' ; + END; + + SET @plans_triggers_select_list += + N'st.text AS QueryText ,'; + IF @VersionShowsAirQuoteActualPlans = 1 + BEGIN + SET @plans_triggers_select_list += N' CASE WHEN DATALENGTH(COALESCE(deqps.query_plan,'''')) > DATALENGTH(COALESCE(qp.query_plan,'''')) THEN deqps.query_plan ELSE qp.query_plan END AS QueryPlan, ' + @nl ; + END; + ELSE + BEGIN + SET @plans_triggers_select_list += N' qp.query_plan AS QueryPlan, ' + @nl ; + END; + SET @plans_triggers_select_list += + N't.t_TotalWorker, + t.t_TotalElapsed, + t.t_TotalReads, + t.t_TotalExecs, + t.t_TotalWrites, + qs.sql_handle AS SqlHandle, + qs.plan_handle AS PlanHandle, + NULL AS QueryHash, + NULL AS QueryPlanHash, + qs.min_worker_time / 1000.0, + qs.max_worker_time / 1000.0, + CASE WHEN qp.query_plan.value(''declare namespace p="http://schemas.microsoft.com/sqlserver/2004/07/showplan";max(//p:RelOp/@Parallel)'', ''float'') > 0 THEN 1 ELSE 0 END, + qs.min_elapsed_time / 1000.0, + qs.max_elapsed_time / 1000.0, + age_minutes, + age_minutes_lifetime, + @SortOrder '; - /*Checking for and creating index on backup_set_uuid*/ - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - - SET @StringToExecute += N'IF NOT EXISTS ( - SELECT t.name, i.name - FROM ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.sys.tables AS t - JOIN ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.sys.indexes AS i - ON t.object_id = i.object_id - WHERE t.name = ? - AND i.name = ? - ) +IF LEFT(@QueryFilter, 3) IN ('all', 'sta') +BEGIN + SET @sql += @insert_list; + + SET @sql += N' + SELECT TOP (@Top) + @@SPID , + ''Statement'' AS QueryType, + COALESCE(DB_NAME(CAST(pa.value AS INT)), N''-- N/A --'') AS DatabaseName, + (total_worker_time / 1000.0) / execution_count AS AvgCPU , + (total_worker_time / 1000.0) AS TotalCPU , + CASE WHEN total_worker_time = 0 THEN 0 + WHEN COALESCE(age_minutes, DATEDIFF(mi, qs.creation_time, qs.last_execution_time), 0) = 0 THEN 0 + ELSE CAST((total_worker_time / 1000.0) / COALESCE(age_minutes, DATEDIFF(mi, qs.creation_time, qs.last_execution_time)) AS MONEY) + END AS AverageCPUPerMinute , + CASE WHEN t.t_TotalWorker = 0 THEN 0 + ELSE CAST(ROUND(100.00 * total_worker_time / t.t_TotalWorker, 2) AS MONEY) + END AS PercentCPUByType, + CASE WHEN t.t_TotalElapsed = 0 THEN 0 + ELSE CAST(ROUND(100.00 * total_elapsed_time / t.t_TotalElapsed, 2) AS MONEY) + END AS PercentDurationByType, + CASE WHEN t.t_TotalReads = 0 THEN 0 + ELSE CAST(ROUND(100.00 * total_logical_reads / t.t_TotalReads, 2) AS MONEY) + END AS PercentReadsByType, + CAST(ROUND(100.00 * execution_count / t.t_TotalExecs, 2) AS MONEY) AS PercentExecutionsByType, + (total_elapsed_time / 1000.0) / execution_count AS AvgDuration , + (total_elapsed_time / 1000.0) AS TotalDuration , + total_logical_reads / execution_count AS AvgReads , + total_logical_reads AS TotalReads , + execution_count AS ExecutionCount , + CASE WHEN execution_count = 0 THEN 0 + WHEN COALESCE(age_minutes, DATEDIFF(mi, qs.creation_time, qs.last_execution_time), 0) = 0 THEN 0 + ELSE CAST((1.00 * execution_count / COALESCE(age_minutes, DATEDIFF(mi, qs.creation_time, qs.last_execution_time))) AS money) + END AS ExecutionsPerMinute , + total_logical_writes AS TotalWrites , + total_logical_writes / execution_count AS AverageWrites , + CASE WHEN t.t_TotalWrites = 0 THEN 0 + ELSE CAST(ROUND(100.00 * total_logical_writes / t.t_TotalWrites, 2) AS MONEY) + END AS PercentWritesByType, + CASE WHEN total_logical_writes = 0 THEN 0 + WHEN COALESCE(age_minutes, DATEDIFF(mi, qs.creation_time, qs.last_execution_time), 0) = 0 THEN 0 + ELSE CAST((1.00 * total_logical_writes / COALESCE(age_minutes, DATEDIFF(mi, qs.creation_time, qs.last_execution_time), 0)) AS money) + END AS WritesPerMinute, + qs.creation_time AS PlanCreationTime, + qs.last_execution_time AS LastExecutionTime, + DATEADD(SECOND, (qs.last_elapsed_time / 1000000.), qs.last_execution_time) AS LastCompletionTime, + qs.statement_start_offset AS StatementStartOffset, + qs.statement_end_offset AS StatementEndOffset, + qs.plan_generation_num AS PlanGenerationNum, '; + + IF (@v >= 11) OR (@v >= 10.5 AND @build >= 2500) + BEGIN + RAISERROR(N'Adding additional info columns for newer versions of SQL', 0, 1) WITH NOWAIT; + SET @sql += N' + qs.min_rows AS MinReturnedRows, + qs.max_rows AS MaxReturnedRows, + CAST(qs.total_rows as MONEY) / execution_count AS AvgReturnedRows, + qs.total_rows AS TotalReturnedRows, + qs.last_rows AS LastReturnedRows, ' ; + END; + ELSE + BEGIN + RAISERROR(N'Substituting NULLs for more info columns in older versions of SQL', 0, 1) WITH NOWAIT; + SET @sql += N' + NULL AS MinReturnedRows, + NULL AS MaxReturnedRows, + NULL AS AvgReturnedRows, + NULL AS TotalReturnedRows, + NULL AS LastReturnedRows, ' ; + END; - BEGIN - CREATE NONCLUSTERED INDEX [backupsetuuid] ON ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.[dbo].[backupset] ([backup_set_uuid] ASC) - END - ' + IF @VersionShowsMemoryGrants = 1 + BEGIN + RAISERROR(N'Getting memory grant information for newer versions of SQL', 0, 1) WITH NOWAIT; + SET @sql += N' + min_grant_kb AS MinGrantKB, + max_grant_kb AS MaxGrantKB, + min_used_grant_kb AS MinUsedGrantKB, + max_used_grant_kb AS MaxUsedGrantKB, + CAST(ISNULL(NULLIF(( total_used_grant_kb * 1.00 ), 0) / NULLIF(total_grant_kb, 0), 0) * 100. AS MONEY) AS PercentMemoryGrantUsed, + CAST(ISNULL(NULLIF(( total_grant_kb * 1. ), 0) / NULLIF(execution_count, 0), 0) AS MONEY) AS AvgMaxMemoryGrant, '; + END; + ELSE + BEGIN + RAISERROR(N'Substituting NULLs for memory grant columns in older versions of SQL', 0, 1) WITH NOWAIT; + SET @sql += N' + NULL AS MinGrantKB, + NULL AS MaxGrantKB, + NULL AS MinUsedGrantKB, + NULL AS MaxUsedGrantKB, + NULL AS PercentMemoryGrantUsed, + NULL AS AvgMaxMemoryGrant, ' ; + END; - SET @InnerStringToExecute = N'EXEC( ''' + @StringToExecute + ''', ''backupset'', ''backupsetuuid'' ) AT ' + QUOTENAME(@WriteBackupsToListenerName) + N';' - - IF @Debug = 1 - PRINT @InnerStringToExecute; - - EXEC sp_executesql @InnerStringToExecute - - - - /*Checking for and creating index on media_set_id*/ + IF @VersionShowsSpills = 1 + BEGIN + RAISERROR(N'Getting spill information for newer versions of SQL', 0, 1) WITH NOWAIT; + SET @sql += N' + min_spills AS MinSpills, + max_spills AS MaxSpills, + total_spills AS TotalSpills, + CAST(ISNULL(NULLIF(( total_spills * 1. ), 0) / NULLIF(execution_count, 0), 0) AS MONEY) AS AvgSpills,'; + END; + ELSE + BEGIN + RAISERROR(N'Substituting NULLs for spill columns in older versions of SQL', 0, 1) WITH NOWAIT; + SET @sql += N' + NULL AS MinSpills, + NULL AS MaxSpills, + NULL AS TotalSpills, + NULL AS AvgSpills, ' ; + END; + + SET @sql += N' + SUBSTRING(st.text, ( qs.statement_start_offset / 2 ) + 1, ( ( CASE qs.statement_end_offset + WHEN -1 THEN DATALENGTH(st.text) + ELSE qs.statement_end_offset + END - qs.statement_start_offset ) / 2 ) + 1) AS QueryText , ' + @nl ; - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - - SET @StringToExecute += 'IF NOT EXISTS ( - SELECT t.name, i.name - FROM ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.sys.tables AS t - JOIN ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.sys.indexes AS i - ON t.object_id = i.object_id - WHERE t.name = ? - AND i.name = ? - ) - BEGIN - CREATE NONCLUSTERED INDEX [backupsetMediaSetId] ON ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.[dbo].[backupset] ([media_set_id] ASC) - END - ' + IF @VersionShowsAirQuoteActualPlans = 1 + BEGIN + SET @sql += N' CASE WHEN DATALENGTH(COALESCE(deqps.query_plan,'''')) > DATALENGTH(COALESCE(qp.query_plan,'''')) THEN deqps.query_plan ELSE qp.query_plan END AS QueryPlan, ' + @nl ; + END + ELSE + BEGIN + SET @sql += N' query_plan AS QueryPlan, ' + @nl ; + END - SET @InnerStringToExecute = N'EXEC( ''' + @StringToExecute + ''', ''backupset'', ''backupsetMediaSetId'' ) AT ' + QUOTENAME(@WriteBackupsToListenerName) + N';' - - IF @Debug = 1 - PRINT @InnerStringToExecute; - - EXEC sp_executesql @InnerStringToExecute - - - - /*Checking for and creating index on backup_finish_date*/ + SET @sql += N' + t.t_TotalWorker, + t.t_TotalElapsed, + t.t_TotalReads, + t.t_TotalExecs, + t.t_TotalWrites, + qs.sql_handle AS SqlHandle, + qs.plan_handle AS PlanHandle, + qs.query_hash AS QueryHash, + qs.query_plan_hash AS QueryPlanHash, + qs.min_worker_time / 1000.0, + qs.max_worker_time / 1000.0, + CASE WHEN qp.query_plan.value(''declare namespace p="http://schemas.microsoft.com/sqlserver/2004/07/showplan";max(//p:RelOp/@Parallel)'', ''float'') > 0 THEN 1 ELSE 0 END, + qs.min_elapsed_time / 1000.0, + qs.max_worker_time / 1000.0, + age_minutes, + age_minutes_lifetime, + @SortOrder '; + + SET @sql += REPLACE(REPLACE(@body, '#view#', 'dm_exec_query_stats'), 'cached_time', 'creation_time') ; - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - - SET @StringToExecute += N'IF NOT EXISTS ( - SELECT t.name, i.name - FROM ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.sys.tables AS t - JOIN ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.sys.indexes AS i - ON t.object_id = i.object_id - WHERE t.name = ? - AND i.name = ? - ) + SET @sort_filter += CASE @SortOrder WHEN N'cpu' THEN N'AND total_worker_time > 0' + WHEN N'reads' THEN N'AND total_logical_reads > 0' + WHEN N'writes' THEN N'AND total_logical_writes > 0' + WHEN N'duration' THEN N'AND total_elapsed_time > 0' + WHEN N'executions' THEN N'AND execution_count > 0' + /* WHEN N'compiles' THEN N'AND (age_minutes + age_minutes_lifetime) > 0' BGO 2021-01-24 commenting out for https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2772 */ + WHEN N'memory grant' THEN N'AND max_grant_kb > 0' + WHEN N'unused grant' THEN N'AND max_grant_kb > 0' + WHEN N'spills' THEN N'AND max_spills > 0' + /* And now the averages */ + WHEN N'avg cpu' THEN N'AND (total_worker_time / execution_count) > 0' + WHEN N'avg reads' THEN N'AND (total_logical_reads / execution_count) > 0' + WHEN N'avg writes' THEN N'AND (total_logical_writes / execution_count) > 0' + WHEN N'avg duration' THEN N'AND (total_elapsed_time / execution_count) > 0' + WHEN N'avg memory grant' THEN N'AND CASE WHEN max_grant_kb = 0 THEN 0 ELSE (max_grant_kb / execution_count) END > 0' + WHEN N'avg spills' THEN N'AND CASE WHEN total_spills = 0 THEN 0 ELSE (total_spills / execution_count) END > 0' + WHEN N'avg executions' THEN N'AND CASE WHEN execution_count = 0 THEN 0 + WHEN COALESCE(age_minutes, age_minutes_lifetime, 0) = 0 THEN 0 + ELSE CAST((1.00 * execution_count / COALESCE(age_minutes, age_minutes_lifetime)) AS money) + END > 0' + ELSE N' /* No minimum threshold set */ ' + END; - BEGIN - CREATE NONCLUSTERED INDEX [backupsetDate] ON ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.[dbo].[backupset] ([backup_finish_date] ASC) - END - ' + SET @sql += REPLACE(@body_where, 'cached_time', 'creation_time') ; - SET @InnerStringToExecute = N'EXEC( ''' + @StringToExecute + ''', ''backupset'', ''backupsetDate'' ) AT ' + QUOTENAME(@WriteBackupsToListenerName) + N';' - - IF @Debug = 1 - PRINT @InnerStringToExecute; - - EXEC sp_executesql @InnerStringToExecute + SET @sql += @sort_filter + @nl; + + SET @sql += @body_order + @nl + @nl + @nl; + IF @SortOrder = 'compiles' + BEGIN + RAISERROR(N'Sorting by compiles', 0, 1) WITH NOWAIT; + SET @sql = REPLACE(@sql, '#sortable#', 'creation_time'); + END; +END; - - /*Checking for and creating index on database_name*/ - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - - SET @StringToExecute += N'IF NOT EXISTS ( - SELECT t.name, i.name - FROM ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.sys.tables AS t - JOIN ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.sys.indexes AS i - ON t.object_id = i.object_id - WHERE t.name = ? - AND i.name = ? - ) +IF (@QueryFilter = 'all' + AND (SELECT COUNT(*) FROM #only_query_hashes) = 0 + AND (SELECT COUNT(*) FROM #ignore_query_hashes) = 0) + AND (@SortOrder NOT IN ('memory grant', 'avg memory grant', 'unused grant', 'duplicate')) /* Issue #3345 added 'duplicate' */ + OR (LEFT(@QueryFilter, 3) = 'pro') +BEGIN + SET @sql += @insert_list; + SET @sql += REPLACE(@plans_triggers_select_list, '#query_type#', 'Stored Procedure') ; - BEGIN - CREATE NONCLUSTERED INDEX [backupsetDatabaseName] ON ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.[dbo].[backupset] ([database_name] ASC ) INCLUDE ([backup_set_id], [media_set_id]) - END + SET @sql += REPLACE(@body, '#view#', 'dm_exec_procedure_stats') ; + SET @sql += @body_where ; - ' + IF @IgnoreSystemDBs = 1 + SET @sql += N' AND COALESCE(LOWER(DB_NAME(database_id)), LOWER(CAST(pa.value AS sysname)), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'', ''dbmaintenance'', ''dbadmin'', ''dbatools'') AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ; - SET @InnerStringToExecute = N'EXEC( ''' + @StringToExecute + ''', ''backupset'', ''backupsetDatabaseName'' ) AT ' + QUOTENAME(@WriteBackupsToListenerName) + N';' - - IF @Debug = 1 - PRINT @InnerStringToExecute; - - EXEC sp_executesql @InnerStringToExecute + SET @sql += @sort_filter + @nl; - RAISERROR('Table and indexes created! You''re welcome!', 0, 1) WITH NOWAIT - END + SET @sql += @body_order + @nl + @nl + @nl ; +END; +IF (@v >= 13 + AND @QueryFilter = 'all' + AND (SELECT COUNT(*) FROM #only_query_hashes) = 0 + AND (SELECT COUNT(*) FROM #ignore_query_hashes) = 0) + AND (@SortOrder NOT IN ('memory grant', 'avg memory grant', 'unused grant', 'duplicate')) /* Issue #3345 added 'duplicate' */ + AND (@SortOrder NOT IN ('spills', 'avg spills')) + OR (LEFT(@QueryFilter, 3) = 'fun') +BEGIN + SET @sql += @insert_list; + SET @sql += REPLACE(REPLACE(@plans_triggers_select_list, '#query_type#', 'Function') + , N' + min_spills AS MinSpills, + max_spills AS MaxSpills, + total_spills AS TotalSpills, + CAST(ISNULL(NULLIF(( total_spills * 1. ), 0) / NULLIF(execution_count, 0), 0) AS MONEY) AS AvgSpills, ', + N' + NULL AS MinSpills, + NULL AS MaxSpills, + NULL AS TotalSpills, + NULL AS AvgSpills, ') ; - RAISERROR('Beginning inserts', 0, 1) WITH NOWAIT; - RAISERROR(@crlf, 0, 1) WITH NOWAIT; + SET @sql += REPLACE(@body, '#view#', 'dm_exec_function_stats') ; + SET @sql += @body_where ; - /* - Batching code comes from the lovely and talented Michael J. Swart - http://michaeljswart.com/2014/09/take-care-when-scripting-batches/ - If you're ever in Canada, he says you can stay at his house, too. - */ + IF @IgnoreSystemDBs = 1 + SET @sql += N' AND COALESCE(LOWER(DB_NAME(database_id)), LOWER(CAST(pa.value AS sysname)), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'', ''dbmaintenance'', ''dbadmin'', ''dbatools'') AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ; + SET @sql += @sort_filter + @nl; - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; + SET @sql += @body_order + @nl + @nl + @nl ; +END; - SET @StringToExecute += N' - DECLARE - @StartDate DATETIME = DATEADD(HOUR, @i_WriteBackupsLastHours, SYSDATETIME()), - @StartDateNext DATETIME, - @RC INT = 1, - @msg NVARCHAR(4000) = N''''; - - SELECT @StartDate = MIN(b.backup_start_date) - FROM msdb.dbo.backupset b - WHERE b.backup_start_date >= @StartDate - AND NOT EXISTS ( - SELECT 1 - FROM ' + QUOTENAME(@WriteBackupsToListenerName) + N'.' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.dbo.backupset b2 - WHERE b.backup_set_uuid = b2.backup_set_uuid - AND b2.backup_start_date >= @StartDate - ) +/******************************************************************************* + * + * Because the trigger execution count in SQL Server 2008R2 and earlier is not + * correct, we ignore triggers for these versions of SQL Server. If you'd like + * to include trigger numbers, just know that the ExecutionCount, + * PercentExecutions, and ExecutionsPerMinute are wildly inaccurate for + * triggers on these versions of SQL Server. + * + * This is why we can't have nice things. + * + ******************************************************************************/ +IF (@UseTriggersAnyway = 1 OR @v >= 11) + AND (SELECT COUNT(*) FROM #only_query_hashes) = 0 + AND (SELECT COUNT(*) FROM #ignore_query_hashes) = 0 + AND (@QueryFilter = 'all') + AND (@SortOrder NOT IN ('memory grant', 'avg memory grant', 'unused grant', 'duplicate')) /* Issue #3345 added 'duplicate' */ +BEGIN + RAISERROR (N'Adding SQL to collect trigger stats.',0,1) WITH NOWAIT; - SET @StartDateNext = DATEADD(MINUTE, 10, @StartDate); + /* Trigger level information from the plan cache */ + SET @sql += @insert_list ; - IF - ( @StartDate IS NULL ) - BEGIN - SET @msg = N''No data to move, exiting.'' - RAISERROR(@msg, 0, 1) WITH NOWAIT + SET @sql += REPLACE(@plans_triggers_select_list, '#query_type#', 'Trigger') ; - RETURN; - END + SET @sql += REPLACE(@body, '#view#', 'dm_exec_trigger_stats') ; - RAISERROR(''Starting insert loop'', 0, 1) WITH NOWAIT; + SET @sql += @body_where ; - WHILE EXISTS ( - SELECT 1 - FROM msdb.dbo.backupset b - WHERE NOT EXISTS ( - SELECT 1 - FROM ' + QUOTENAME(@WriteBackupsToListenerName) + N'.' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.dbo.backupset b2 - WHERE b.backup_set_uuid = b2.backup_set_uuid - AND b2.backup_start_date >= @StartDate - ) - ) - BEGIN - - SET @msg = N''Inserting data for '' + CONVERT(NVARCHAR(30), @StartDate) + '' through '' + + CONVERT(NVARCHAR(30), @StartDateNext) + ''.'' - RAISERROR(@msg, 0, 1) WITH NOWAIT - - ' + IF @IgnoreSystemDBs = 1 + SET @sql += N' AND COALESCE(LOWER(DB_NAME(database_id)), LOWER(CAST(pa.value AS sysname)), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'', ''dbmaintenance'', ''dbadmin'', ''dbatools'') AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ; - SET @StringToExecute += N'INSERT ' + QUOTENAME(@WriteBackupsToListenerName) + N'.' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.dbo.backupset - ' - SET @StringToExecute += N' (database_name, database_guid, backup_set_uuid, type, backup_size, backup_start_date, backup_finish_date, media_set_id, time_zone, - compressed_backup_size, recovery_model, server_name, machine_name, first_lsn, last_lsn, user_name, compatibility_level, - is_password_protected, is_snapshot, is_readonly, is_single_user, has_backup_checksums, is_damaged, ' + CASE WHEN @ProductVersionMajor >= 12 - THEN + N'encryptor_type, has_bulk_logged_data)' + @crlf - ELSE + N'has_bulk_logged_data)' + @crlf - END - - SET @StringToExecute +=N' - SELECT database_name, database_guid, backup_set_uuid, type, backup_size, backup_start_date, backup_finish_date, media_set_id, time_zone, - compressed_backup_size, recovery_model, server_name, machine_name, first_lsn, last_lsn, user_name, compatibility_level, - is_password_protected, is_snapshot, is_readonly, is_single_user, has_backup_checksums, is_damaged, ' + CASE WHEN @ProductVersionMajor >= 12 - THEN + N'encryptor_type, has_bulk_logged_data' + @crlf - ELSE + N'has_bulk_logged_data' + @crlf - END - SET @StringToExecute +=N' - FROM msdb.dbo.backupset b - WHERE 1=1 - AND b.backup_start_date >= @StartDate - AND b.backup_start_date < @StartDateNext - AND NOT EXISTS ( - SELECT 1 - FROM ' + QUOTENAME(@WriteBackupsToListenerName) + N'.' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.dbo.backupset b2 - WHERE b.backup_set_uuid = b2.backup_set_uuid - AND b2.backup_start_date >= @StartDate - )' + @crlf; + SET @sql += @sort_filter + @nl; + SET @sql += @body_order + @nl + @nl + @nl ; +END; - SET @StringToExecute +=N' - SET @RC = @@ROWCOUNT; - - SET @msg = N''Inserted '' + CONVERT(NVARCHAR(30), @RC) + '' rows for ''+ CONVERT(NVARCHAR(30), @StartDate) + '' through '' + CONVERT(NVARCHAR(30), @StartDateNext) + ''.'' - RAISERROR(@msg, 0, 1) WITH NOWAIT - - SET @StartDate = @StartDateNext; - SET @StartDateNext = DATEADD(MINUTE, 10, @StartDate); - IF - ( @StartDate > SYSDATETIME() ) - BEGIN - - SET @msg = N''No more data to move, exiting.'' - RAISERROR(@msg, 0, 1) WITH NOWAIT - - BREAK; - END - END' + @crlf; +SELECT @sort = CASE @SortOrder WHEN N'cpu' THEN N'total_worker_time' + WHEN N'reads' THEN N'total_logical_reads' + WHEN N'writes' THEN N'total_logical_writes' + WHEN N'duration' THEN N'total_elapsed_time' + WHEN N'executions' THEN N'execution_count' + WHEN N'compiles' THEN N'cached_time' + WHEN N'memory grant' THEN N'max_grant_kb' + WHEN N'unused grant' THEN N'max_grant_kb - max_used_grant_kb' + WHEN N'spills' THEN N'max_spills' + WHEN N'duplicate' THEN N'total_worker_time' /* Issue #3345 */ + /* And now the averages */ + WHEN N'avg cpu' THEN N'total_worker_time / execution_count' + WHEN N'avg reads' THEN N'total_logical_reads / execution_count' + WHEN N'avg writes' THEN N'total_logical_writes / execution_count' + WHEN N'avg duration' THEN N'total_elapsed_time / execution_count' + WHEN N'avg memory grant' THEN N'CASE WHEN max_grant_kb = 0 THEN 0 ELSE max_grant_kb / execution_count END' + WHEN N'avg spills' THEN N'CASE WHEN total_spills = 0 THEN 0 ELSE total_spills / execution_count END' + WHEN N'avg executions' THEN N'CASE WHEN execution_count = 0 THEN 0 + WHEN COALESCE(age_minutes, age_minutes_lifetime, 0) = 0 THEN 0 + ELSE CAST((1.00 * execution_count / COALESCE(age_minutes, age_minutes_lifetime)) AS money) + END' + END ; - IF @Debug = 1 - PRINT @StringToExecute; +SELECT @sql = REPLACE(@sql, '#sortable#', @sort); - EXEC sp_executesql @StringToExecute, N'@i_WriteBackupsLastHours INT', @i_WriteBackupsLastHours = @WriteBackupsLastHours; +SET @sql += N' +SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; +INSERT INTO #p (SqlHandle, TotalCPU, TotalReads, TotalDuration, TotalWrites, ExecutionCount) +SELECT SqlHandle, + TotalCPU, + TotalReads, + TotalDuration, + TotalWrites, + ExecutionCount +FROM (SELECT SqlHandle, + TotalCPU, + TotalReads, + TotalDuration, + TotalWrites, + ExecutionCount, + ROW_NUMBER() OVER (PARTITION BY SqlHandle ORDER BY #sortable# DESC) AS rn + FROM ##BlitzCacheProcs + WHERE SPID = @@SPID) AS x +WHERE x.rn = 1 +OPTION (RECOMPILE); -END; +/* + This block was used to delete duplicate queries, but has been removed. + For more info: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2026 +WITH d AS ( +SELECT SPID, + ROW_NUMBER() OVER (PARTITION BY SqlHandle, QueryHash ORDER BY #sortable# DESC) AS rn +FROM ##BlitzCacheProcs +WHERE SPID = @@SPID +) +DELETE d +WHERE d.rn > 1 +AND SPID = @@SPID +OPTION (RECOMPILE); +*/ +'; -END; +SELECT @sort = CASE @SortOrder WHEN N'cpu' THEN N'TotalCPU' + WHEN N'reads' THEN N'TotalReads' + WHEN N'writes' THEN N'TotalWrites' + WHEN N'duration' THEN N'TotalDuration' + WHEN N'executions' THEN N'ExecutionCount' + WHEN N'compiles' THEN N'PlanCreationTime' + WHEN N'memory grant' THEN N'MaxGrantKB' + WHEN N'unused grant' THEN N'MaxGrantKB - MaxUsedGrantKB' + WHEN N'spills' THEN N'MaxSpills' + WHEN N'duplicate' THEN N'TotalCPU' /* Issue #3345 */ + /* And now the averages */ + WHEN N'avg cpu' THEN N'TotalCPU / ExecutionCount' + WHEN N'avg reads' THEN N'TotalReads / ExecutionCount' + WHEN N'avg writes' THEN N'TotalWrites / ExecutionCount' + WHEN N'avg duration' THEN N'TotalDuration / ExecutionCount' + WHEN N'avg memory grant' THEN N'AvgMaxMemoryGrant' + WHEN N'avg spills' THEN N'AvgSpills' + WHEN N'avg executions' THEN N'CASE WHEN ExecutionCount = 0 THEN 0 + WHEN COALESCE(age_minutes, age_minutes_lifetime, 0) = 0 THEN 0 + ELSE CAST((1.00 * ExecutionCount / COALESCE(age_minutes, age_minutes_lifetime)) AS money) + END' + END ; -GO -SET ANSI_NULLS ON; -SET ANSI_PADDING ON; -SET ANSI_WARNINGS ON; -SET ARITHABORT ON; -SET CONCAT_NULL_YIELDS_NULL ON; -SET QUOTED_IDENTIFIER ON; -SET STATISTICS IO OFF; -SET STATISTICS TIME OFF; -GO +SELECT @sql = REPLACE(@sql, '#sortable#', @sort); -IF ( -SELECT - CASE - WHEN CONVERT(NVARCHAR(128), SERVERPROPERTY ('PRODUCTVERSION')) LIKE '8%' THEN 0 - WHEN CONVERT(NVARCHAR(128), SERVERPROPERTY ('PRODUCTVERSION')) LIKE '9%' THEN 0 - ELSE 1 - END -) = 0 -BEGIN - DECLARE @msg VARCHAR(8000); - SELECT @msg = 'Sorry, sp_BlitzCache doesn''t work on versions of SQL prior to 2008.' + REPLICATE(CHAR(13), 7933); - PRINT @msg; - RETURN; -END; -IF OBJECT_ID('dbo.sp_BlitzCache') IS NULL - EXEC ('CREATE PROCEDURE dbo.sp_BlitzCache AS RETURN 0;'); -GO +IF @Debug = 1 + BEGIN + PRINT N'Printing dynamic SQL stored in @sql: '; + PRINT SUBSTRING(@sql, 0, 4000); + PRINT SUBSTRING(@sql, 4000, 8000); + PRINT SUBSTRING(@sql, 8000, 12000); + PRINT SUBSTRING(@sql, 12000, 16000); + PRINT SUBSTRING(@sql, 16000, 20000); + PRINT SUBSTRING(@sql, 20000, 24000); + PRINT SUBSTRING(@sql, 24000, 28000); + PRINT SUBSTRING(@sql, 28000, 32000); + PRINT SUBSTRING(@sql, 32000, 36000); + PRINT SUBSTRING(@sql, 36000, 40000); + END; -IF OBJECT_ID('dbo.sp_BlitzCache') IS NOT NULL AND OBJECT_ID('tempdb.dbo.##BlitzCacheProcs', 'U') IS NOT NULL - EXEC ('DROP TABLE ##BlitzCacheProcs;'); -GO +RAISERROR(N'Creating temp tables for results and warnings.', 0, 1) WITH NOWAIT; -IF OBJECT_ID('dbo.sp_BlitzCache') IS NOT NULL AND OBJECT_ID('tempdb.dbo.##BlitzCacheResults', 'U') IS NOT NULL - EXEC ('DROP TABLE ##BlitzCacheResults;'); -GO -CREATE TABLE ##BlitzCacheResults ( - SPID INT, - ID INT IDENTITY(1,1), - CheckID INT, - Priority TINYINT, - FindingsGroup VARCHAR(50), - Finding VARCHAR(500), - URL VARCHAR(200), - Details VARCHAR(4000) -); +IF OBJECT_ID('tempdb.dbo.##BlitzCacheResults') IS NULL +BEGIN + CREATE TABLE ##BlitzCacheResults ( + SPID INT, + ID INT IDENTITY(1,1), + CheckID INT, + Priority TINYINT, + FindingsGroup VARCHAR(50), + Finding VARCHAR(500), + URL VARCHAR(200), + Details VARCHAR(4000) + ); +END; +ELSE +BEGIN + RAISERROR(N'Cleaning up old warnings for your SPID', 0, 1) WITH NOWAIT; + DELETE ##BlitzCacheResults + WHERE SPID = @@SPID + OPTION (RECOMPILE) ; +END -CREATE TABLE ##BlitzCacheProcs ( + +IF OBJECT_ID('tempdb.dbo.##BlitzCacheProcs') IS NULL +BEGIN + CREATE TABLE ##BlitzCacheProcs ( SPID INT , QueryType NVARCHAR(258), DatabaseName sysname, @@ -12606,9 +4944,6 @@ CREATE TABLE ##BlitzCacheProcs ( AverageReturnedRows MONEY, TotalReturnedRows BIGINT, LastReturnedRows BIGINT, - /*The Memory Grant columns are only supported - in certain versions, giggle giggle. - */ MinGrantKB BIGINT, MaxGrantKB BIGINT, MinUsedGrantKB BIGINT, @@ -12626,9 +4961,9 @@ CREATE TABLE ##BlitzCacheProcs ( */ TotalWorkerTimeForType BIGINT, TotalElapsedTimeForType BIGINT, - TotalReadsForType DECIMAL(30), + TotalReadsForType BIGINT, TotalExecutionCountForType BIGINT, - TotalWritesForType DECIMAL(30), + TotalWritesForType BIGINT, NumberOfPlans INT, NumberOfDistinctPlans INT, SerialDesiredMemory FLOAT, @@ -12645,7 +4980,7 @@ CREATE TABLE ##BlitzCacheProcs ( is_cursor BIT, is_optimistic_cursor BIT, is_forward_only_cursor BIT, - is_fast_forward_cursor BIT, + is_fast_forward_cursor BIT, is_cursor_dynamic BIT, is_parallel BIT, is_forced_serial BIT, @@ -12704,8 +5039,8 @@ CREATE TABLE ##BlitzCacheProcs ( table_update_count INT NULL, table_delete_count INT NULL, index_ops AS (index_insert_count + index_update_count + index_delete_count + - cx_insert_count + cx_update_count + cx_delete_count + - table_insert_count + table_update_count + table_delete_count), + cx_insert_count + cx_update_count + cx_delete_count + + table_insert_count + table_update_count + table_delete_count), is_row_level BIT, is_spatial BIT, index_dml BIT, @@ -12736,20157 +5071,5464 @@ CREATE TABLE ##BlitzCacheProcs ( missing_indexes XML, SetOptions VARCHAR(MAX), Warnings VARCHAR(MAX), - Pattern NVARCHAR(20) + Pattern NVARCHAR(20) ); -GO +END; +ELSE +BEGIN + RAISERROR(N'Cleaning up old plans for your SPID', 0, 1) WITH NOWAIT; + DELETE ##BlitzCacheProcs + WHERE SPID = @@SPID + OPTION (RECOMPILE) ; +END -ALTER PROCEDURE dbo.sp_BlitzCache - @Help BIT = 0, - @Top INT = NULL, - @SortOrder VARCHAR(50) = 'CPU', - @UseTriggersAnyway BIT = NULL, - @ExportToExcel BIT = 0, - @ExpertMode TINYINT = 0, - @OutputType VARCHAR(20) = 'TABLE' , - @OutputServerName NVARCHAR(258) = NULL , - @OutputDatabaseName NVARCHAR(258) = NULL , - @OutputSchemaName NVARCHAR(258) = NULL , - @OutputTableName NVARCHAR(258) = NULL , -- do NOT use ##BlitzCacheResults or ##BlitzCacheProcs as they are used as work tables in this procedure - @ConfigurationDatabaseName NVARCHAR(128) = NULL , - @ConfigurationSchemaName NVARCHAR(258) = NULL , - @ConfigurationTableName NVARCHAR(258) = NULL , - @DurationFilter DECIMAL(38,4) = NULL , - @HideSummary BIT = 0 , - @IgnoreSystemDBs BIT = 1 , - @OnlyQueryHashes VARCHAR(MAX) = NULL , - @IgnoreQueryHashes VARCHAR(MAX) = NULL , - @OnlySqlHandles VARCHAR(MAX) = NULL , - @IgnoreSqlHandles VARCHAR(MAX) = NULL , - @QueryFilter VARCHAR(10) = 'ALL' , - @DatabaseName NVARCHAR(128) = NULL , - @StoredProcName NVARCHAR(128) = NULL, - @SlowlySearchPlansFor NVARCHAR(4000) = NULL, - @Reanalyze BIT = 0 , - @SkipAnalysis BIT = 0 , - @BringThePain BIT = 0 , - @MinimumExecutionCount INT = 0, - @Debug BIT = 0, - @CheckDateOverride DATETIMEOFFSET = NULL, - @MinutesBack INT = NULL, - @Version VARCHAR(30) = NULL OUTPUT, - @VersionDate DATETIME = NULL OUTPUT, - @VersionCheckMode BIT = 0 -WITH RECOMPILE -AS +IF @Reanalyze = 0 BEGIN -SET NOCOUNT ON; -SET STATISTICS XML OFF; -SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - -SELECT @Version = '8.19', @VersionDate = '20240222'; -SET @OutputType = UPPER(@OutputType); + RAISERROR('Collecting execution plan information.', 0, 1) WITH NOWAIT; -IF(@VersionCheckMode = 1) -BEGIN - RETURN; + EXEC sp_executesql @sql, N'@Top INT, @min_duration INT, @min_back INT, @SortOrder NVARCHAR(20)', @Top, @DurationFilter_i, @MinutesBack, @SortOrder; END; -DECLARE @nl NVARCHAR(2) = NCHAR(13) + NCHAR(10) ; - -IF @Help = 1 - BEGIN - PRINT ' - sp_BlitzCache from http://FirstResponderKit.org - - This script displays your most resource-intensive queries from the plan cache, - and points to ways you can tune these queries to make them faster. +IF @SkipAnalysis = 1 + BEGIN + RAISERROR(N'Skipping analysis, going to results', 0, 1) WITH NOWAIT; + GOTO Results ; + END; - To learn more, visit http://FirstResponderKit.org where you can download new - versions for free, watch training videos on how it works, get more info on - the findings, contribute your own code, and more. +/* Update ##BlitzCacheProcs to get Stored Proc info + * This should get totals for all statements in a Stored Proc + */ +RAISERROR(N'Attempting to aggregate stored proc info from separate statements', 0, 1) WITH NOWAIT; +;WITH agg AS ( + SELECT + b.SqlHandle, + SUM(b.MinReturnedRows) AS MinReturnedRows, + SUM(b.MaxReturnedRows) AS MaxReturnedRows, + SUM(b.AverageReturnedRows) AS AverageReturnedRows, + SUM(b.TotalReturnedRows) AS TotalReturnedRows, + SUM(b.LastReturnedRows) AS LastReturnedRows, + SUM(b.MinGrantKB) AS MinGrantKB, + SUM(b.MaxGrantKB) AS MaxGrantKB, + SUM(b.MinUsedGrantKB) AS MinUsedGrantKB, + SUM(b.MaxUsedGrantKB) AS MaxUsedGrantKB, + SUM(b.MinSpills) AS MinSpills, + SUM(b.MaxSpills) AS MaxSpills, + SUM(b.TotalSpills) AS TotalSpills + FROM ##BlitzCacheProcs b + WHERE b.SPID = @@SPID + AND b.QueryHash IS NOT NULL + GROUP BY b.SqlHandle +) +UPDATE b + SET + b.MinReturnedRows = b2.MinReturnedRows, + b.MaxReturnedRows = b2.MaxReturnedRows, + b.AverageReturnedRows = b2.AverageReturnedRows, + b.TotalReturnedRows = b2.TotalReturnedRows, + b.LastReturnedRows = b2.LastReturnedRows, + b.MinGrantKB = b2.MinGrantKB, + b.MaxGrantKB = b2.MaxGrantKB, + b.MinUsedGrantKB = b2.MinUsedGrantKB, + b.MaxUsedGrantKB = b2.MaxUsedGrantKB, + b.MinSpills = b2.MinSpills, + b.MaxSpills = b2.MaxSpills, + b.TotalSpills = b2.TotalSpills +FROM ##BlitzCacheProcs b +JOIN agg b2 +ON b2.SqlHandle = b.SqlHandle +WHERE b.QueryHash IS NULL +AND b.SPID = @@SPID +OPTION (RECOMPILE) ; - Known limitations of this version: - - SQL Server 2008 and 2008R2 have a bug in trigger stats, so that output is - excluded by default. - - @IgnoreQueryHashes and @OnlyQueryHashes require a CSV list of hashes - with no spaces between the hash values. +/* Compute the total CPU, etc across our active set of the plan cache. + * Yes, there's a flaw - this doesn't include anything outside of our @Top + * metric. + */ +RAISERROR('Computing CPU, duration, read, and write metrics', 0, 1) WITH NOWAIT; +DECLARE @total_duration BIGINT, + @total_cpu BIGINT, + @total_reads BIGINT, + @total_writes BIGINT, + @total_execution_count BIGINT; - Unknown limitations of this version: - - May or may not be vulnerable to the wick effect. +SELECT @total_cpu = SUM(TotalCPU), + @total_duration = SUM(TotalDuration), + @total_reads = SUM(TotalReads), + @total_writes = SUM(TotalWrites), + @total_execution_count = SUM(ExecutionCount) +FROM #p +OPTION (RECOMPILE) ; - Changes - for the full list of improvements and fixes in this version, see: - https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/ +DECLARE @cr NVARCHAR(1) = NCHAR(13); +DECLARE @lf NVARCHAR(1) = NCHAR(10); +DECLARE @tab NVARCHAR(1) = NCHAR(9); +/* Update CPU percentage for stored procedures */ +RAISERROR(N'Update CPU percentage for stored procedures', 0, 1) WITH NOWAIT; +UPDATE ##BlitzCacheProcs +SET PercentCPU = y.PercentCPU, + PercentDuration = y.PercentDuration, + PercentReads = y.PercentReads, + PercentWrites = y.PercentWrites, + PercentExecutions = y.PercentExecutions, + ExecutionsPerMinute = y.ExecutionsPerMinute, + /* Strip newlines and tabs. Tabs are replaced with multiple spaces + so that the later whitespace trim will completely eliminate them + */ + QueryText = REPLACE(REPLACE(REPLACE(QueryText, @cr, ' '), @lf, ' '), @tab, ' ') +FROM ( + SELECT PlanHandle, + CASE @total_cpu WHEN 0 THEN 0 + ELSE CAST((100. * TotalCPU) / @total_cpu AS MONEY) END AS PercentCPU, + CASE @total_duration WHEN 0 THEN 0 + ELSE CAST((100. * TotalDuration) / @total_duration AS MONEY) END AS PercentDuration, + CASE @total_reads WHEN 0 THEN 0 + ELSE CAST((100. * TotalReads) / @total_reads AS MONEY) END AS PercentReads, + CASE @total_writes WHEN 0 THEN 0 + ELSE CAST((100. * TotalWrites) / @total_writes AS MONEY) END AS PercentWrites, + CASE @total_execution_count WHEN 0 THEN 0 + ELSE CAST((100. * ExecutionCount) / @total_execution_count AS MONEY) END AS PercentExecutions, + CASE DATEDIFF(mi, PlanCreationTime, LastExecutionTime) + WHEN 0 THEN 0 + ELSE CAST((1.00 * ExecutionCount / DATEDIFF(mi, PlanCreationTime, LastExecutionTime)) AS MONEY) + END AS ExecutionsPerMinute + FROM ( + SELECT PlanHandle, + TotalCPU, + TotalDuration, + TotalReads, + TotalWrites, + ExecutionCount, + PlanCreationTime, + LastExecutionTime + FROM ##BlitzCacheProcs + WHERE PlanHandle IS NOT NULL + AND SPID = @@SPID + GROUP BY PlanHandle, + TotalCPU, + TotalDuration, + TotalReads, + TotalWrites, + ExecutionCount, + PlanCreationTime, + LastExecutionTime + ) AS x +) AS y +WHERE ##BlitzCacheProcs.PlanHandle = y.PlanHandle + AND ##BlitzCacheProcs.PlanHandle IS NOT NULL + AND ##BlitzCacheProcs.SPID = @@SPID +OPTION (RECOMPILE) ; - MIT License +RAISERROR(N'Gather percentage information from grouped results', 0, 1) WITH NOWAIT; +UPDATE ##BlitzCacheProcs +SET PercentCPU = y.PercentCPU, + PercentDuration = y.PercentDuration, + PercentReads = y.PercentReads, + PercentWrites = y.PercentWrites, + PercentExecutions = y.PercentExecutions, + ExecutionsPerMinute = y.ExecutionsPerMinute, + /* Strip newlines and tabs. Tabs are replaced with multiple spaces + so that the later whitespace trim will completely eliminate them + */ + QueryText = REPLACE(REPLACE(REPLACE(QueryText, @cr, ' '), @lf, ' '), @tab, ' ') +FROM ( + SELECT DatabaseName, + SqlHandle, + QueryHash, + CASE @total_cpu WHEN 0 THEN 0 + ELSE CAST((100. * TotalCPU) / @total_cpu AS MONEY) END AS PercentCPU, + CASE @total_duration WHEN 0 THEN 0 + ELSE CAST((100. * TotalDuration) / @total_duration AS MONEY) END AS PercentDuration, + CASE @total_reads WHEN 0 THEN 0 + ELSE CAST((100. * TotalReads) / @total_reads AS MONEY) END AS PercentReads, + CASE @total_writes WHEN 0 THEN 0 + ELSE CAST((100. * TotalWrites) / @total_writes AS MONEY) END AS PercentWrites, + CASE @total_execution_count WHEN 0 THEN 0 + ELSE CAST((100. * ExecutionCount) / @total_execution_count AS MONEY) END AS PercentExecutions, + CASE DATEDIFF(mi, PlanCreationTime, LastExecutionTime) + WHEN 0 THEN 0 + ELSE CAST((1.00 * ExecutionCount / DATEDIFF(mi, PlanCreationTime, LastExecutionTime)) AS MONEY) + END AS ExecutionsPerMinute + FROM ( + SELECT DatabaseName, + SqlHandle, + QueryHash, + TotalCPU, + TotalDuration, + TotalReads, + TotalWrites, + ExecutionCount, + PlanCreationTime, + LastExecutionTime + FROM ##BlitzCacheProcs + WHERE SPID = @@SPID + GROUP BY DatabaseName, + SqlHandle, + QueryHash, + TotalCPU, + TotalDuration, + TotalReads, + TotalWrites, + ExecutionCount, + PlanCreationTime, + LastExecutionTime + ) AS x +) AS y +WHERE ##BlitzCacheProcs.SqlHandle = y.SqlHandle + AND ##BlitzCacheProcs.QueryHash = y.QueryHash + AND ##BlitzCacheProcs.DatabaseName = y.DatabaseName + AND ##BlitzCacheProcs.PlanHandle IS NULL +OPTION (RECOMPILE) ; - Copyright (c) Brent Ozar Unlimited - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. +/* Testing using XML nodes to speed up processing */ +RAISERROR(N'Begin XML nodes processing', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +SELECT QueryHash , + SqlHandle , + PlanHandle, + q.n.query('.') AS statement, + 0 AS is_cursor +INTO #statements +FROM ##BlitzCacheProcs p + CROSS APPLY p.QueryPlan.nodes('//p:StmtSimple') AS q(n) +WHERE p.SPID = @@SPID +OPTION (RECOMPILE) ; - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE. - '; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +INSERT #statements +SELECT QueryHash , + SqlHandle , + PlanHandle, + q.n.query('.') AS statement, + 1 AS is_cursor +FROM ##BlitzCacheProcs p + CROSS APPLY p.QueryPlan.nodes('//p:StmtCursor') AS q(n) +WHERE p.SPID = @@SPID +OPTION (RECOMPILE) ; - - SELECT N'@Help' AS [Parameter Name] , - N'BIT' AS [Data Type] , - N'Displays this help message.' AS [Parameter Description] +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +SELECT QueryHash , + SqlHandle , + q.n.query('.') AS query_plan +INTO #query_plan +FROM #statements p + CROSS APPLY p.statement.nodes('//p:QueryPlan') AS q(n) +OPTION (RECOMPILE) ; - UNION ALL - SELECT N'@Top', - N'INT', - N'The number of records to retrieve and analyze from the plan cache. The following DMVs are used as the plan cache: dm_exec_query_stats, dm_exec_procedure_stats, dm_exec_trigger_stats.' +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +SELECT QueryHash , + SqlHandle , + q.n.query('.') AS relop +INTO #relop +FROM #query_plan p + CROSS APPLY p.query_plan.nodes('//p:RelOp') AS q(n) +OPTION (RECOMPILE) ; - UNION ALL - SELECT N'@SortOrder', - N'VARCHAR(10)', - N'Data processing and display order. @SortOrder will still be used, even when preparing output for a table or for excel. Possible values are: "CPU", "Reads", "Writes", "Duration", "Executions", "Recent Compilations", "Memory Grant", "Unused Grant", "Spills", "Query Hash", "Duplicate". Additionally, the word "Average" or "Avg" can be used to sort on averages rather than total. "Executions per minute" and "Executions / minute" can be used to sort by execution per minute. For the truly lazy, "xpm" can also be used. Note that when you use all or all avg, the only parameters you can use are @Top and @DatabaseName. All others will be ignored.' +-- high level plan stuff +RAISERROR(N'Gathering high level plan information', 0, 1) WITH NOWAIT; +UPDATE ##BlitzCacheProcs +SET NumberOfDistinctPlans = distinct_plan_count, + NumberOfPlans = number_of_plans , + plan_multiple_plans = CASE WHEN distinct_plan_count < number_of_plans THEN number_of_plans END +FROM + ( + SELECT + DatabaseName = + DB_NAME(CONVERT(int, pa.value)), + QueryHash = + qs.query_hash, + number_of_plans = + COUNT_BIG(qs.query_plan_hash), + distinct_plan_count = + COUNT_BIG(DISTINCT qs.query_plan_hash) + FROM sys.dm_exec_query_stats AS qs + CROSS APPLY sys.dm_exec_plan_attributes(qs.plan_handle) pa + WHERE pa.attribute = 'dbid' + GROUP BY + DB_NAME(CONVERT(int, pa.value)), + qs.query_hash +) AS x +WHERE ##BlitzCacheProcs.QueryHash = x.QueryHash +AND ##BlitzCacheProcs.DatabaseName = x.DatabaseName +OPTION (RECOMPILE) ; - UNION ALL - SELECT N'@UseTriggersAnyway', - N'BIT', - N'On SQL Server 2008R2 and earlier, trigger execution count is incorrect - trigger execution count is incremented once per execution of a SQL agent job. If you still want to see relative execution count of triggers, then you can force sp_BlitzCache to include this information.' +-- query level checks +RAISERROR(N'Performing query level checks', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +UPDATE ##BlitzCacheProcs +SET missing_index_count = query_plan.value('count(//p:QueryPlan/p:MissingIndexes/p:MissingIndexGroup)', 'int') , + unmatched_index_count = CASE WHEN is_trivial <> 1 THEN query_plan.value('count(//p:QueryPlan/p:UnmatchedIndexes/p:Parameterization/p:Object)', 'int') END , + SerialDesiredMemory = query_plan.value('sum(//p:QueryPlan/p:MemoryGrantInfo/@SerialDesiredMemory)', 'float') , + SerialRequiredMemory = query_plan.value('sum(//p:QueryPlan/p:MemoryGrantInfo/@SerialRequiredMemory)', 'float'), + CachedPlanSize = query_plan.value('sum(//p:QueryPlan/@CachedPlanSize)', 'float') , + CompileTime = query_plan.value('sum(//p:QueryPlan/@CompileTime)', 'float') , + CompileCPU = query_plan.value('sum(//p:QueryPlan/@CompileCPU)', 'float') , + CompileMemory = query_plan.value('sum(//p:QueryPlan/@CompileMemory)', 'float'), + MaxCompileMemory = query_plan.value('sum(//p:QueryPlan/p:OptimizerHardwareDependentProperties/@MaxCompileMemory)', 'float') +FROM #query_plan qp +WHERE qp.QueryHash = ##BlitzCacheProcs.QueryHash +AND qp.SqlHandle = ##BlitzCacheProcs.SqlHandle +AND SPID = @@SPID +OPTION (RECOMPILE); - UNION ALL - SELECT N'@ExportToExcel', - N'BIT', - N'Prepare output for exporting to Excel. Newlines and additional whitespace are removed from query text and the execution plan is not displayed.' +-- statement level checks +RAISERROR(N'Performing compile timeout checks', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +UPDATE b +SET compile_timeout = 1 +FROM #statements s +JOIN ##BlitzCacheProcs b +ON s.QueryHash = b.QueryHash +AND SPID = @@SPID +WHERE statement.exist('/p:StmtSimple/@StatementOptmEarlyAbortReason[.="TimeOut"]') = 1 +OPTION (RECOMPILE); - UNION ALL - SELECT N'@ExpertMode', - N'TINYINT', - N'Default 0. When set to 1, results include more columns. When 2, mode is optimized for Opserver, the open source dashboard.' - UNION ALL - SELECT N'@OutputType', - N'NVARCHAR(258)', - N'If set to "NONE", this will tell this procedure not to run any query leading to a results set thrown to caller.' - - UNION ALL - SELECT N'@OutputDatabaseName', - N'NVARCHAR(128)', - N'The output database. If this does not exist SQL Server will divide by zero and everything will fall apart.' +RAISERROR(N'Performing compile memory limit exceeded checks', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +UPDATE b +SET compile_memory_limit_exceeded = 1 +FROM #statements s +JOIN ##BlitzCacheProcs b +ON s.QueryHash = b.QueryHash +AND SPID = @@SPID +WHERE statement.exist('/p:StmtSimple/@StatementOptmEarlyAbortReason[.="MemoryLimitExceeded"]') = 1 +OPTION (RECOMPILE); - UNION ALL - SELECT N'@OutputSchemaName', - N'NVARCHAR(258)', - N'The output schema. If this does not exist SQL Server will divide by zero and everything will fall apart.' +IF @ExpertMode > 0 +BEGIN +RAISERROR(N'Performing unparameterized query checks', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p), +unparameterized_query AS ( + SELECT s.QueryHash, + unparameterized_query = CASE WHEN statement.exist('//p:StmtSimple[@StatementOptmLevel[.="FULL"]]/p:QueryPlan/p:ParameterList') = 1 AND + statement.exist('//p:StmtSimple[@StatementOptmLevel[.="FULL"]]/p:QueryPlan/p:ParameterList/p:ColumnReference') = 0 THEN 1 + WHEN statement.exist('//p:StmtSimple[@StatementOptmLevel[.="FULL"]]/p:QueryPlan/p:ParameterList') = 0 AND + statement.exist('//p:StmtSimple[@StatementOptmLevel[.="FULL"]]/*/p:RelOp/descendant::p:ScalarOperator/p:Identifier/p:ColumnReference[contains(@Column, "@")]') = 1 THEN 1 + END + FROM #statements AS s + ) +UPDATE b +SET b.unparameterized_query = u.unparameterized_query +FROM ##BlitzCacheProcs b +JOIN unparameterized_query u +ON u.QueryHash = b.QueryHash +AND SPID = @@SPID +WHERE u.unparameterized_query = 1 +OPTION (RECOMPILE); +END; - UNION ALL - SELECT N'@OutputTableName', - N'NVARCHAR(258)', - N'The output table. If this does not exist, it will be created for you.' - UNION ALL - SELECT N'@DurationFilter', - N'DECIMAL(38,4)', - N'Excludes queries with an average duration (in seconds) less than @DurationFilter.' +IF @ExpertMode > 0 +BEGIN +RAISERROR(N'Performing index DML checks', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p), +index_dml AS ( + SELECT s.QueryHash, + index_dml = CASE WHEN statement.exist('//p:StmtSimple/@StatementType[.="CREATE INDEX"]') = 1 THEN 1 + WHEN statement.exist('//p:StmtSimple/@StatementType[.="DROP INDEX"]') = 1 THEN 1 + END + FROM #statements s + ) + UPDATE b + SET b.index_dml = i.index_dml + FROM ##BlitzCacheProcs AS b + JOIN index_dml i + ON i.QueryHash = b.QueryHash + WHERE i.index_dml = 1 + AND b.SPID = @@SPID + OPTION (RECOMPILE); +END; - UNION ALL - SELECT N'@HideSummary', - N'BIT', - N'Hides the findings summary result set.' - UNION ALL - SELECT N'@IgnoreSystemDBs', - N'BIT', - N'Ignores plans found in the system databases (master, model, msdb, tempdb, dbmaintenance, dbadmin, dbatools and resourcedb)' +IF @ExpertMode > 0 +BEGIN +RAISERROR(N'Performing table DML checks', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p), +table_dml AS ( + SELECT s.QueryHash, + table_dml = CASE WHEN statement.exist('//p:StmtSimple/@StatementType[.="CREATE TABLE"]') = 1 THEN 1 + WHEN statement.exist('//p:StmtSimple/@StatementType[.="DROP OBJECT"]') = 1 THEN 1 + END + FROM #statements AS s + ) + UPDATE b + SET b.table_dml = t.table_dml + FROM ##BlitzCacheProcs AS b + JOIN table_dml t + ON t.QueryHash = b.QueryHash + WHERE t.table_dml = 1 + AND b.SPID = @@SPID + OPTION (RECOMPILE); +END; - UNION ALL - SELECT N'@OnlyQueryHashes', - N'VARCHAR(MAX)', - N'A list of query hashes to query. All other query hashes will be ignored. Stored procedures and triggers will be ignored.' - UNION ALL - SELECT N'@IgnoreQueryHashes', - N'VARCHAR(MAX)', - N'A list of query hashes to ignore.' - - UNION ALL - SELECT N'@OnlySqlHandles', - N'VARCHAR(MAX)', - N'One or more sql_handles to use for filtering results.' - - UNION ALL - SELECT N'@IgnoreSqlHandles', - N'VARCHAR(MAX)', - N'One or more sql_handles to ignore.' +IF @ExpertMode > 0 +BEGIN +RAISERROR(N'Gathering row estimates', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES ('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) +INSERT INTO #est_rows +SELECT DISTINCT + CONVERT(BINARY(8), RIGHT('0000000000000000' + SUBSTRING(c.n.value('@QueryHash', 'VARCHAR(18)'), 3, 18), 16), 2) AS QueryHash, + c.n.value('(/p:StmtSimple/@StatementEstRows)[1]', 'FLOAT') AS estimated_rows +FROM #statements AS s +CROSS APPLY s.statement.nodes('/p:StmtSimple') AS c(n) +WHERE c.n.exist('/p:StmtSimple[@StatementEstRows > 0]') = 1; - UNION ALL - SELECT N'@DatabaseName', - N'NVARCHAR(128)', - N'A database name which is used for filtering results.' + UPDATE b + SET b.estimated_rows = er.estimated_rows + FROM ##BlitzCacheProcs AS b + JOIN #est_rows er + ON er.QueryHash = b.QueryHash + WHERE b.SPID = @@SPID + AND b.QueryType = 'Statement' + OPTION (RECOMPILE); +END; - UNION ALL - SELECT N'@StoredProcName', - N'NVARCHAR(128)', - N'Name of stored procedure you want to find plans for.' +RAISERROR(N'Gathering trivial plans', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES ( 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) +UPDATE b +SET b.is_trivial = 1 +FROM ##BlitzCacheProcs AS b +JOIN ( +SELECT s.SqlHandle +FROM #statements AS s +JOIN ( SELECT r.SqlHandle + FROM #relop AS r + WHERE r.relop.exist('//p:RelOp[contains(@LogicalOp, "Scan")]') = 1 ) AS r + ON r.SqlHandle = s.SqlHandle +WHERE s.statement.exist('//p:StmtSimple[@StatementOptmLevel[.="TRIVIAL"]]/p:QueryPlan/p:ParameterList') = 1 +) AS s +ON b.SqlHandle = s.SqlHandle +OPTION (RECOMPILE); - UNION ALL - SELECT N'@SlowlySearchPlansFor', - N'NVARCHAR(4000)', - N'String to search for in plan text. % wildcards allowed.' - UNION ALL - SELECT N'@BringThePain', - N'BIT', - N'When using @SortOrder = ''all'' and @Top > 10, we require you to set @BringThePain = 1 so you understand that sp_BlitzCache will take a while to run.' +--Gather costs +RAISERROR(N'Gathering statement costs', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +INSERT INTO #plan_cost ( QueryPlanCost, SqlHandle, PlanHandle, QueryHash, QueryPlanHash ) +SELECT DISTINCT + statement.value('sum(/p:StmtSimple/@StatementSubTreeCost)', 'float') QueryPlanCost, + s.SqlHandle, + s.PlanHandle, + CONVERT(BINARY(8), RIGHT('0000000000000000' + SUBSTRING(q.n.value('@QueryHash', 'VARCHAR(18)'), 3, 18), 16), 2) AS QueryHash, + CONVERT(BINARY(8), RIGHT('0000000000000000' + SUBSTRING(q.n.value('@QueryPlanHash', 'VARCHAR(18)'), 3, 18), 16), 2) AS QueryPlanHash +FROM #statements s +CROSS APPLY s.statement.nodes('/p:StmtSimple') AS q(n) +WHERE statement.value('sum(/p:StmtSimple/@StatementSubTreeCost)', 'float') > 0 +OPTION (RECOMPILE); - UNION ALL - SELECT N'@QueryFilter', - N'VARCHAR(10)', - N'Filter out stored procedures or statements. The default value is ''ALL''. Allowed values are ''procedures'', ''statements'', ''functions'', or ''all'' (any variation in capitalization is acceptable).' +RAISERROR(N'Updating statement costs', 0, 1) WITH NOWAIT; +WITH pc AS ( + SELECT SUM(DISTINCT pc.QueryPlanCost) AS QueryPlanCostSum, pc.QueryHash, pc.QueryPlanHash, pc.SqlHandle, pc.PlanHandle + FROM #plan_cost AS pc + GROUP BY pc.QueryHash, pc.QueryPlanHash, pc.SqlHandle, pc.PlanHandle +) + UPDATE b + SET b.QueryPlanCost = ISNULL(pc.QueryPlanCostSum, 0) + FROM pc + JOIN ##BlitzCacheProcs b + ON b.SqlHandle = pc.SqlHandle + AND b.QueryHash = pc.QueryHash + WHERE b.QueryType NOT LIKE '%Procedure%' + OPTION (RECOMPILE); - UNION ALL - SELECT N'@Reanalyze', - N'BIT', - N'The default is 0. When set to 0, sp_BlitzCache will re-evalute the plan cache. Set this to 1 to reanalyze existing results' - - UNION ALL - SELECT N'@MinimumExecutionCount', - N'INT', - N'Queries with fewer than this number of executions will be omitted from results.' - - UNION ALL - SELECT N'@Debug', - N'BIT', - N'Setting this to 1 will print dynamic SQL and select data from all tables used.' +IF EXISTS ( +SELECT 1 +FROM ##BlitzCacheProcs AS b +WHERE b.QueryType LIKE 'Procedure%' +) - UNION ALL - SELECT N'@MinutesBack', - N'INT', - N'How many minutes back to begin plan cache analysis. If you put in a positive number, we''ll flip it to negative.'; +BEGIN +RAISERROR(N'Gathering stored procedure costs', 0, 1) WITH NOWAIT; +;WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +, QueryCost AS ( + SELECT + DISTINCT + statement.value('sum(/p:StmtSimple/@StatementSubTreeCost)', 'float') AS SubTreeCost, + s.PlanHandle, + s.SqlHandle + FROM #statements AS s + WHERE PlanHandle IS NOT NULL +) +, QueryCostUpdate AS ( + SELECT + SUM(qc.SubTreeCost) OVER (PARTITION BY SqlHandle, PlanHandle) PlanTotalQuery, + qc.PlanHandle, + qc.SqlHandle + FROM QueryCost qc +) +INSERT INTO #proc_costs +SELECT qcu.PlanTotalQuery, PlanHandle, SqlHandle +FROM QueryCostUpdate AS qcu +OPTION (RECOMPILE); - /* Column definitions */ - SELECT N'# Executions' AS [Column Name], - N'BIGINT' AS [Data Type], - N'The number of executions of this particular query. This is computed across statements, procedures, and triggers and aggregated by the SQL handle.' AS [Column Description] - UNION ALL - SELECT N'Executions / Minute', - N'MONEY', - N'Number of executions per minute - calculated for the life of the current plan. Plan life is the last execution time minus the plan creation time.' +UPDATE b + SET b.QueryPlanCost = ca.PlanTotalQuery +FROM ##BlitzCacheProcs AS b +CROSS APPLY ( + SELECT TOP 1 PlanTotalQuery + FROM #proc_costs qcu + WHERE qcu.PlanHandle = b.PlanHandle + ORDER BY PlanTotalQuery DESC +) ca +WHERE b.QueryType LIKE 'Procedure%' +AND b.SPID = @@SPID +OPTION (RECOMPILE); - UNION ALL - SELECT N'Execution Weight', - N'MONEY', - N'An arbitrary metric of total "execution-ness". A weight of 2 is "one more" than a weight of 1.' +END; - UNION ALL - SELECT N'Database', - N'sysname', - N'The name of the database where the plan was encountered. If the database name cannot be determined for some reason, a value of NA will be substituted. A value of 32767 indicates the plan comes from ResourceDB.' +UPDATE b +SET b.QueryPlanCost = 0.0 +FROM ##BlitzCacheProcs b +WHERE b.QueryPlanCost IS NULL +AND b.SPID = @@SPID +OPTION (RECOMPILE); - UNION ALL - SELECT N'Total CPU', - N'BIGINT', - N'Total CPU time, reported in milliseconds, that was consumed by all executions of this query since the last compilation.' +RAISERROR(N'Checking for plan warnings', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +UPDATE ##BlitzCacheProcs +SET plan_warnings = 1 +FROM #query_plan qp +WHERE qp.SqlHandle = ##BlitzCacheProcs.SqlHandle +AND SPID = @@SPID +AND query_plan.exist('/p:QueryPlan/p:Warnings') = 1 +OPTION (RECOMPILE); - UNION ALL - SELECT N'Avg CPU', - N'BIGINT', - N'Average CPU time, reported in milliseconds, consumed by each execution of this query since the last compilation.' +RAISERROR(N'Checking for implicit conversion', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +UPDATE ##BlitzCacheProcs +SET implicit_conversions = 1 +FROM #query_plan qp +WHERE qp.SqlHandle = ##BlitzCacheProcs.SqlHandle +AND SPID = @@SPID +AND query_plan.exist('/p:QueryPlan/p:Warnings/p:PlanAffectingConvert/@Expression[contains(., "CONVERT_IMPLICIT")]') = 1 +OPTION (RECOMPILE); - UNION ALL - SELECT N'CPU Weight', - N'MONEY', - N'An arbitrary metric of total "CPU-ness". A weight of 2 is "one more" than a weight of 1.' +-- operator level checks +IF @ExpertMode > 0 +BEGIN +RAISERROR(N'Performing busy loops checks', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +UPDATE p +SET busy_loops = CASE WHEN (x.estimated_executions / 100.0) > x.estimated_rows THEN 1 END +FROM ##BlitzCacheProcs p + JOIN ( + SELECT qs.SqlHandle, + relop.value('sum(/p:RelOp/@EstimateRows)', 'float') AS estimated_rows , + relop.value('sum(/p:RelOp/@EstimateRewinds)', 'float') + relop.value('sum(/p:RelOp/@EstimateRebinds)', 'float') + 1.0 AS estimated_executions + FROM #relop qs + ) AS x ON p.SqlHandle = x.SqlHandle +WHERE SPID = @@SPID +OPTION (RECOMPILE); +END; - UNION ALL - SELECT N'Total Duration', - N'BIGINT', - N'Total elapsed time, reported in milliseconds, consumed by all executions of this query since last compilation.' - UNION ALL - SELECT N'Avg Duration', - N'BIGINT', - N'Average elapsed time, reported in milliseconds, consumed by each execution of this query since the last compilation.' +RAISERROR(N'Performing TVF join check', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +UPDATE p +SET p.tvf_join = CASE WHEN x.tvf_join = 1 THEN 1 END +FROM ##BlitzCacheProcs p + JOIN ( + SELECT r.SqlHandle, + 1 AS tvf_join + FROM #relop AS r + WHERE r.relop.exist('//p:RelOp[(@LogicalOp[.="Table-valued function"])]') = 1 + AND r.relop.exist('//p:RelOp[contains(@LogicalOp, "Join")]') = 1 + ) AS x ON p.SqlHandle = x.SqlHandle +WHERE SPID = @@SPID +OPTION (RECOMPILE); - UNION ALL - SELECT N'Duration Weight', - N'MONEY', - N'An arbitrary metric of total "Duration-ness". A weight of 2 is "one more" than a weight of 1.' +IF @ExpertMode > 0 +BEGIN +RAISERROR(N'Checking for operator warnings', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +, x AS ( +SELECT r.SqlHandle, + c.n.exist('//p:Warnings[(@NoJoinPredicate[.="1"])]') AS warning_no_join_predicate, + c.n.exist('//p:ColumnsWithNoStatistics') AS no_stats_warning , + c.n.exist('//p:Warnings') AS relop_warnings +FROM #relop AS r +CROSS APPLY r.relop.nodes('/p:RelOp/p:Warnings') AS c(n) +) +UPDATE p +SET p.warning_no_join_predicate = x.warning_no_join_predicate, + p.no_stats_warning = x.no_stats_warning, + p.relop_warnings = x.relop_warnings +FROM ##BlitzCacheProcs AS p +JOIN x ON x.SqlHandle = p.SqlHandle +AND SPID = @@SPID +OPTION (RECOMPILE); +END; - UNION ALL - SELECT N'Total Reads', - N'BIGINT', - N'Total logical reads performed by this query since last compilation.' - UNION ALL - SELECT N'Average Reads', - N'BIGINT', - N'Average logical reads performed by each execution of this query since the last compilation.' +RAISERROR(N'Checking for table variables', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +, x AS ( +SELECT r.SqlHandle, + c.n.value('substring(@Table, 2, 1)','VARCHAR(100)') AS first_char +FROM #relop r +CROSS APPLY r.relop.nodes('//p:Object') AS c(n) +) +UPDATE p +SET is_table_variable = 1 +FROM ##BlitzCacheProcs AS p +JOIN x ON x.SqlHandle = p.SqlHandle +AND SPID = @@SPID +WHERE x.first_char = '@' +OPTION (RECOMPILE); - UNION ALL - SELECT N'Read Weight', - N'MONEY', - N'An arbitrary metric of "Read-ness". A weight of 2 is "one more" than a weight of 1.' - - UNION ALL - SELECT N'Total Writes', - N'BIGINT', - N'Total logical writes performed by this query since last compilation.' - - UNION ALL - SELECT N'Average Writes', - N'BIGINT', - N'Average logical writes performed by each execution this query since last compilation.' - - UNION ALL - SELECT N'Write Weight', - N'MONEY', - N'An arbitrary metric of "Write-ness". A weight of 2 is "one more" than a weight of 1.' +IF @ExpertMode > 0 +BEGIN +RAISERROR(N'Checking for functions', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +, x AS ( +SELECT qs.SqlHandle, + n.fn.value('count(distinct-values(//p:UserDefinedFunction[not(@IsClrFunction)]))', 'INT') AS function_count, + n.fn.value('count(distinct-values(//p:UserDefinedFunction[@IsClrFunction = "1"]))', 'INT') AS clr_function_count +FROM #relop qs +CROSS APPLY relop.nodes('/p:RelOp/p:ComputeScalar/p:DefinedValues/p:DefinedValue/p:ScalarOperator') n(fn) +) +UPDATE p +SET p.function_count = x.function_count, + p.clr_function_count = x.clr_function_count +FROM ##BlitzCacheProcs AS p +JOIN x ON x.SqlHandle = p.SqlHandle +AND SPID = @@SPID +OPTION (RECOMPILE); +END; - UNION ALL - SELECT N'Query Type', - N'NVARCHAR(258)', - N'The type of query being examined. This can be "Procedure", "Statement", or "Trigger".' - UNION ALL - SELECT N'Query Text', - N'NVARCHAR(4000)', - N'The text of the query. This may be truncated by either SQL Server or by sp_BlitzCache(tm) for display purposes.' +RAISERROR(N'Checking for expensive key lookups', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +UPDATE ##BlitzCacheProcs +SET key_lookup_cost = x.key_lookup_cost +FROM ( +SELECT + qs.SqlHandle, + MAX(relop.value('sum(/p:RelOp/@EstimatedTotalSubtreeCost)', 'float')) AS key_lookup_cost +FROM #relop qs +WHERE [relop].exist('/p:RelOp/p:IndexScan[(@Lookup[.="1"])]') = 1 +GROUP BY qs.SqlHandle +) AS x +WHERE ##BlitzCacheProcs.SqlHandle = x.SqlHandle +AND SPID = @@SPID +OPTION (RECOMPILE); - UNION ALL - SELECT N'% Executions (Type)', - N'MONEY', - N'Percent of executions relative to the type of query - e.g. 17.2% of all stored procedure executions.' - UNION ALL - SELECT N'% CPU (Type)', - N'MONEY', - N'Percent of CPU time consumed by this query for a given type of query - e.g. 22% of CPU of all stored procedures executed.' +RAISERROR(N'Checking for expensive remote queries', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +UPDATE ##BlitzCacheProcs +SET remote_query_cost = x.remote_query_cost +FROM ( +SELECT + qs.SqlHandle, + MAX(relop.value('sum(/p:RelOp/@EstimatedTotalSubtreeCost)', 'float')) AS remote_query_cost +FROM #relop qs +WHERE [relop].exist('/p:RelOp[(@PhysicalOp[contains(., "Remote")])]') = 1 +GROUP BY qs.SqlHandle +) AS x +WHERE ##BlitzCacheProcs.SqlHandle = x.SqlHandle +AND SPID = @@SPID +OPTION (RECOMPILE); - UNION ALL - SELECT N'% Duration (Type)', - N'MONEY', - N'Percent of elapsed time consumed by this query for a given type of query - e.g. 12% of all statements executed.' +RAISERROR(N'Checking for expensive sorts', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +UPDATE ##BlitzCacheProcs +SET sort_cost = y.max_sort_cost +FROM ( + SELECT x.SqlHandle, MAX((x.sort_io + x.sort_cpu)) AS max_sort_cost + FROM ( + SELECT + qs.SqlHandle, + relop.value('sum(/p:RelOp/@EstimateIO)', 'float') AS sort_io, + relop.value('sum(/p:RelOp/@EstimateCPU)', 'float') AS sort_cpu + FROM #relop qs + WHERE [relop].exist('/p:RelOp[(@PhysicalOp[.="Sort"])]') = 1 + ) AS x + GROUP BY x.SqlHandle + ) AS y +WHERE ##BlitzCacheProcs.SqlHandle = y.SqlHandle +AND SPID = @@SPID +OPTION (RECOMPILE); - UNION ALL - SELECT N'% Reads (Type)', - N'MONEY', - N'Percent of reads consumed by this query for a given type of query - e.g. 34.2% of all stored procedures executed.' +IF NOT EXISTS(SELECT 1/0 FROM #statements AS s WHERE s.is_cursor = 1) +BEGIN - UNION ALL - SELECT N'% Writes (Type)', - N'MONEY', - N'Percent of writes performed by this query for a given type of query - e.g. 43.2% of all statements executed.' +RAISERROR(N'No cursor plans found, skipping', 0, 1) WITH NOWAIT; - UNION ALL - SELECT N'Total Rows', - N'BIGINT', - N'Total number of rows returned for all executions of this query. This only applies to query level stats, not stored procedures or triggers.' +END - UNION ALL - SELECT N'Average Rows', - N'MONEY', - N'Average number of rows returned by each execution of the query.' +IF EXISTS(SELECT 1/0 FROM #statements AS s WHERE s.is_cursor = 1) +BEGIN - UNION ALL - SELECT N'Min Rows', - N'BIGINT', - N'The minimum number of rows returned by any execution of this query.' +RAISERROR(N'Cursor plans found, investigating', 0, 1) WITH NOWAIT; - UNION ALL - SELECT N'Max Rows', - N'BIGINT', - N'The maximum number of rows returned by any execution of this query.' +RAISERROR(N'Checking for Optimistic cursors', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +UPDATE b +SET b.is_optimistic_cursor = 1 +FROM ##BlitzCacheProcs b +JOIN #statements AS qs +ON b.SqlHandle = qs.SqlHandle +CROSS APPLY qs.statement.nodes('/p:StmtCursor') AS n1(fn) +WHERE SPID = @@SPID +AND n1.fn.exist('//p:CursorPlan/@CursorConcurrency[.="Optimistic"]') = 1 +AND qs.is_cursor = 1 +OPTION (RECOMPILE); - UNION ALL - SELECT N'MinGrantKB', - N'BIGINT', - N'The minimum memory grant the query received in kb.' - UNION ALL - SELECT N'MaxGrantKB', - N'BIGINT', - N'The maximum memory grant the query received in kb.' +RAISERROR(N'Checking if cursor is Forward Only', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +UPDATE b +SET b.is_forward_only_cursor = 1 +FROM ##BlitzCacheProcs b +JOIN #statements AS qs +ON b.SqlHandle = qs.SqlHandle +CROSS APPLY qs.statement.nodes('/p:StmtCursor') AS n1(fn) +WHERE SPID = @@SPID +AND n1.fn.exist('//p:CursorPlan/@ForwardOnly[.="true"]') = 1 +AND qs.is_cursor = 1 +OPTION (RECOMPILE); - UNION ALL - SELECT N'MinUsedGrantKB', - N'BIGINT', - N'The minimum used memory grant the query received in kb.' +RAISERROR(N'Checking if cursor is Fast Forward', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +UPDATE b +SET b.is_fast_forward_cursor = 1 +FROM ##BlitzCacheProcs b +JOIN #statements AS qs +ON b.SqlHandle = qs.SqlHandle +CROSS APPLY qs.statement.nodes('/p:StmtCursor') AS n1(fn) +WHERE SPID = @@SPID +AND n1.fn.exist('//p:CursorPlan/@CursorActualType[.="FastForward"]') = 1 +AND qs.is_cursor = 1 +OPTION (RECOMPILE); - UNION ALL - SELECT N'MaxUsedGrantKB', - N'BIGINT', - N'The maximum used memory grant the query received in kb.' - UNION ALL - SELECT N'MinSpills', - N'BIGINT', - N'The minimum amount this query has spilled to tempdb in 8k pages.' +RAISERROR(N'Checking for Dynamic cursors', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +UPDATE b +SET b.is_cursor_dynamic = 1 +FROM ##BlitzCacheProcs b +JOIN #statements AS qs +ON b.SqlHandle = qs.SqlHandle +CROSS APPLY qs.statement.nodes('/p:StmtCursor') AS n1(fn) +WHERE SPID = @@SPID +AND n1.fn.exist('//p:CursorPlan/@CursorActualType[.="Dynamic"]') = 1 +AND qs.is_cursor = 1 +OPTION (RECOMPILE); - UNION ALL - SELECT N'MaxSpills', - N'BIGINT', - N'The maximum amount this query has spilled to tempdb in 8k pages.' +END - UNION ALL - SELECT N'TotalSpills', - N'BIGINT', - N'The total amount this query has spilled to tempdb in 8k pages.' +IF @ExpertMode > 0 +BEGIN +RAISERROR(N'Checking for bad scans and plan forcing', 0, 1) WITH NOWAIT; +;WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +UPDATE b +SET +b.is_table_scan = x.is_table_scan, +b.backwards_scan = x.backwards_scan, +b.forced_index = x.forced_index, +b.forced_seek = x.forced_seek, +b.forced_scan = x.forced_scan +FROM ##BlitzCacheProcs b +JOIN ( +SELECT + qs.SqlHandle, + 0 AS is_table_scan, + q.n.exist('@ScanDirection[.="BACKWARD"]') AS backwards_scan, + q.n.value('@ForcedIndex', 'bit') AS forced_index, + q.n.value('@ForceSeek', 'bit') AS forced_seek, + q.n.value('@ForceScan', 'bit') AS forced_scan +FROM #relop qs +CROSS APPLY qs.relop.nodes('//p:IndexScan') AS q(n) +UNION ALL +SELECT + qs.SqlHandle, + 1 AS is_table_scan, + q.n.exist('@ScanDirection[.="BACKWARD"]') AS backwards_scan, + q.n.value('@ForcedIndex', 'bit') AS forced_index, + q.n.value('@ForceSeek', 'bit') AS forced_seek, + q.n.value('@ForceScan', 'bit') AS forced_scan +FROM #relop qs +CROSS APPLY qs.relop.nodes('//p:TableScan') AS q(n) +) AS x ON b.SqlHandle = x.SqlHandle +WHERE SPID = @@SPID +OPTION (RECOMPILE); +END; - UNION ALL - SELECT N'AvgSpills', - N'BIGINT', - N'The average amount this query has spilled to tempdb in 8k pages.' - UNION ALL - SELECT N'PercentMemoryGrantUsed', - N'MONEY', - N'Result of dividing the maximum grant used by the minimum granted.' - - UNION ALL - SELECT N'AvgMaxMemoryGrant', - N'MONEY', - N'The average maximum memory grant for a query.' - - UNION ALL - SELECT N'# Plans', - N'INT', - N'The total number of execution plans found that match a given query.' - - UNION ALL - SELECT N'# Distinct Plans', - N'INT', - N'The number of distinct execution plans that match a given query. ' - + NCHAR(13) + NCHAR(10) - + N'This may be caused by running the same query across multiple databases or because of a lack of proper parameterization in the database.' - - UNION ALL - SELECT N'Created At', - N'DATETIME', - N'Time that the execution plan was last compiled.' +IF @ExpertMode > 0 +BEGIN +RAISERROR(N'Checking for computed columns that reference scalar UDFs', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +UPDATE ##BlitzCacheProcs +SET is_computed_scalar = x.computed_column_function +FROM ( +SELECT qs.SqlHandle, + n.fn.value('count(distinct-values(//p:UserDefinedFunction[not(@IsClrFunction)]))', 'INT') AS computed_column_function +FROM #relop qs +CROSS APPLY relop.nodes('/p:RelOp/p:ComputeScalar/p:DefinedValues/p:DefinedValue/p:ScalarOperator') n(fn) +WHERE n.fn.exist('/p:RelOp/p:ComputeScalar/p:DefinedValues/p:DefinedValue/p:ColumnReference[(@ComputedColumn[.="1"])]') = 1 +) AS x +WHERE ##BlitzCacheProcs.SqlHandle = x.SqlHandle +AND SPID = @@SPID +OPTION (RECOMPILE); +END; - UNION ALL - SELECT N'Last Execution', - N'DATETIME', - N'The last time that this query was executed.' - UNION ALL - SELECT N'Query Plan', - N'XML', - N'The query plan. Click to display a graphical plan or, if you need to patch SSMS, a pile of XML.' +RAISERROR(N'Checking for filters that reference scalar UDFs', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +UPDATE ##BlitzCacheProcs +SET is_computed_filter = x.filter_function +FROM ( +SELECT +r.SqlHandle, +c.n.value('count(distinct-values(//p:UserDefinedFunction[not(@IsClrFunction)]))', 'INT') AS filter_function +FROM #relop AS r +CROSS APPLY r.relop.nodes('/p:RelOp/p:Filter/p:Predicate/p:ScalarOperator/p:Compare/p:ScalarOperator/p:UserDefinedFunction') c(n) +) x +WHERE ##BlitzCacheProcs.SqlHandle = x.SqlHandle +AND SPID = @@SPID +OPTION (RECOMPILE); - UNION ALL - SELECT N'Plan Handle', - N'VARBINARY(64)', - N'An arbitrary identifier referring to the compiled plan this query is a part of.' +IF @ExpertMode > 0 +BEGIN +RAISERROR(N'Checking modification queries that hit lots of indexes', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p), +IndexOps AS +( + SELECT + r.QueryHash, + c.n.value('@PhysicalOp', 'VARCHAR(100)') AS op_name, + c.n.exist('@PhysicalOp[.="Index Insert"]') AS ii, + c.n.exist('@PhysicalOp[.="Index Update"]') AS iu, + c.n.exist('@PhysicalOp[.="Index Delete"]') AS id, + c.n.exist('@PhysicalOp[.="Clustered Index Insert"]') AS cii, + c.n.exist('@PhysicalOp[.="Clustered Index Update"]') AS ciu, + c.n.exist('@PhysicalOp[.="Clustered Index Delete"]') AS cid, + c.n.exist('@PhysicalOp[.="Table Insert"]') AS ti, + c.n.exist('@PhysicalOp[.="Table Update"]') AS tu, + c.n.exist('@PhysicalOp[.="Table Delete"]') AS td + FROM #relop AS r + CROSS APPLY r.relop.nodes('/p:RelOp') c(n) + OUTER APPLY r.relop.nodes('/p:RelOp/p:ScalarInsert/p:Object') q(n) + OUTER APPLY r.relop.nodes('/p:RelOp/p:Update/p:Object') o2(n) + OUTER APPLY r.relop.nodes('/p:RelOp/p:SimpleUpdate/p:Object') o3(n) +), iops AS +( + SELECT ios.QueryHash, + SUM(CONVERT(TINYINT, ios.ii)) AS index_insert_count, + SUM(CONVERT(TINYINT, ios.iu)) AS index_update_count, + SUM(CONVERT(TINYINT, ios.id)) AS index_delete_count, + SUM(CONVERT(TINYINT, ios.cii)) AS cx_insert_count, + SUM(CONVERT(TINYINT, ios.ciu)) AS cx_update_count, + SUM(CONVERT(TINYINT, ios.cid)) AS cx_delete_count, + SUM(CONVERT(TINYINT, ios.ti)) AS table_insert_count, + SUM(CONVERT(TINYINT, ios.tu)) AS table_update_count, + SUM(CONVERT(TINYINT, ios.td)) AS table_delete_count + FROM IndexOps AS ios + WHERE ios.op_name IN ('Index Insert', 'Index Delete', 'Index Update', + 'Clustered Index Insert', 'Clustered Index Delete', 'Clustered Index Update', + 'Table Insert', 'Table Delete', 'Table Update') + GROUP BY ios.QueryHash) +UPDATE b +SET b.index_insert_count = iops.index_insert_count, + b.index_update_count = iops.index_update_count, + b.index_delete_count = iops.index_delete_count, + b.cx_insert_count = iops.cx_insert_count, + b.cx_update_count = iops.cx_update_count, + b.cx_delete_count = iops.cx_delete_count, + b.table_insert_count = iops.table_insert_count, + b.table_update_count = iops.table_update_count, + b.table_delete_count = iops.table_delete_count +FROM ##BlitzCacheProcs AS b +JOIN iops ON iops.QueryHash = b.QueryHash +WHERE SPID = @@SPID +OPTION (RECOMPILE); +END; - UNION ALL - SELECT N'SQL Handle', - N'VARBINARY(64)', - N'An arbitrary identifier referring to a batch or stored procedure that this query is a part of.' - UNION ALL - SELECT N'Query Hash', - N'BINARY(8)', - N'A hash of the query. Queries with the same query hash have similar logic but only differ by literal values or database.' +IF @ExpertMode > 0 +BEGIN +RAISERROR(N'Checking for Spatial index use', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +UPDATE ##BlitzCacheProcs +SET is_spatial = x.is_spatial +FROM ( +SELECT qs.SqlHandle, + 1 AS is_spatial +FROM #relop qs +CROSS APPLY relop.nodes('/p:RelOp//p:Object') n(fn) +WHERE n.fn.exist('(@IndexKind[.="Spatial"])') = 1 +) AS x +WHERE ##BlitzCacheProcs.SqlHandle = x.SqlHandle +AND SPID = @@SPID +OPTION (RECOMPILE); +END; - UNION ALL - SELECT N'Warnings', - N'VARCHAR(MAX)', - N'A list of individual warnings generated by this query.' ; +RAISERROR('Checking for wonky Index Spools', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES ( + 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) +, selects +AS ( SELECT s.QueryHash + FROM #statements AS s + WHERE s.statement.exist('/p:StmtSimple/@StatementType[.="SELECT"]') = 1 ) +, spools +AS ( SELECT DISTINCT r.QueryHash, + c.n.value('@EstimateRows', 'FLOAT') AS estimated_rows, + c.n.value('@EstimateIO', 'FLOAT') AS estimated_io, + c.n.value('@EstimateCPU', 'FLOAT') AS estimated_cpu, + c.n.value('@EstimateRebinds', 'FLOAT') AS estimated_rebinds +FROM #relop AS r +JOIN selects AS s +ON s.QueryHash = r.QueryHash +CROSS APPLY r.relop.nodes('/p:RelOp') AS c(n) +WHERE r.relop.exist('/p:RelOp[@PhysicalOp="Index Spool" and @LogicalOp="Eager Spool"]') = 1 +) +UPDATE b + SET b.index_spool_rows = sp.estimated_rows, + b.index_spool_cost = ((sp.estimated_io * sp.estimated_cpu) * CASE WHEN sp.estimated_rebinds < 1 THEN 1 ELSE sp.estimated_rebinds END) +FROM ##BlitzCacheProcs b +JOIN spools sp +ON sp.QueryHash = b.QueryHash +OPTION (RECOMPILE); - - /* Configuration table description */ - SELECT N'Frequent Execution Threshold' AS [Configuration Parameter] , - N'100' AS [Default Value] , - N'Executions / Minute' AS [Unit of Measure] , - N'Executions / Minute before a "Frequent Execution Threshold" warning is triggered.' AS [Description] +RAISERROR('Checking for wonky Table Spools', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES ( + 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) +, selects +AS ( SELECT s.QueryHash + FROM #statements AS s + WHERE s.statement.exist('/p:StmtSimple/@StatementType[.="SELECT"]') = 1 ) +, spools +AS ( SELECT DISTINCT r.QueryHash, + c.n.value('@EstimateRows', 'FLOAT') AS estimated_rows, + c.n.value('@EstimateIO', 'FLOAT') AS estimated_io, + c.n.value('@EstimateCPU', 'FLOAT') AS estimated_cpu, + c.n.value('@EstimateRebinds', 'FLOAT') AS estimated_rebinds +FROM #relop AS r +JOIN selects AS s +ON s.QueryHash = r.QueryHash +CROSS APPLY r.relop.nodes('/p:RelOp') AS c(n) +WHERE r.relop.exist('/p:RelOp[@PhysicalOp="Table Spool" and @LogicalOp="Lazy Spool"]') = 1 +) +UPDATE b + SET b.table_spool_rows = (sp.estimated_rows * sp.estimated_rebinds), + b.table_spool_cost = ((sp.estimated_io * sp.estimated_cpu * sp.estimated_rows) * CASE WHEN sp.estimated_rebinds < 1 THEN 1 ELSE sp.estimated_rebinds END) +FROM ##BlitzCacheProcs b +JOIN spools sp +ON sp.QueryHash = b.QueryHash +OPTION (RECOMPILE); - UNION ALL - SELECT N'Parameter Sniffing Variance Percent' , - N'30' , - N'Percent' , - N'Variance required between min/max values and average values before a "Parameter Sniffing" warning is triggered. Applies to worker time and returned rows.' - UNION ALL - SELECT N'Parameter Sniffing IO Threshold' , - N'100,000' , - N'Logical reads' , - N'Minimum number of average logical reads before parameter sniffing checks are evaluated.' +RAISERROR('Checking for selects that cause non-spill and index spool writes', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES ( + 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) +, selects +AS ( SELECT CONVERT(BINARY(8), + RIGHT('0000000000000000' + + SUBSTRING(s.statement.value('(/p:StmtSimple/@QueryHash)[1]', 'VARCHAR(18)'), + 3, 18), 16), 2) AS QueryHash + FROM #statements AS s + JOIN ##BlitzCacheProcs b + ON s.QueryHash = b.QueryHash + WHERE b.index_spool_rows IS NULL + AND b.index_spool_cost IS NULL + AND b.table_spool_cost IS NULL + AND b.table_spool_rows IS NULL + AND b.is_big_spills IS NULL + AND b.AverageWrites > 1024. + AND s.statement.exist('/p:StmtSimple/@StatementType[.="SELECT"]') = 1 +) +UPDATE b + SET b.select_with_writes = 1 +FROM ##BlitzCacheProcs b +JOIN selects AS s +ON s.QueryHash = b.QueryHash +AND b.AverageWrites > 1024.; - UNION ALL - SELECT N'Cost Threshold for Parallelism Warning' AS [Configuration Parameter] , - N'10' , - N'Percent' , - N'Trigger a "Nearly Parallel" warning when a query''s cost is within X percent of the cost threshold for parallelism.' +/* 2012+ only */ +IF @v >= 11 +BEGIN - UNION ALL - SELECT N'Long Running Query Warning' AS [Configuration Parameter] , - N'300' , - N'Seconds' , - N'Triggers a "Long Running Query Warning" when average duration, max CPU time, or max clock time is higher than this number.' + RAISERROR(N'Checking for forced serialization', 0, 1) WITH NOWAIT; + WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) + UPDATE ##BlitzCacheProcs + SET is_forced_serial = 1 + FROM #query_plan qp + WHERE qp.SqlHandle = ##BlitzCacheProcs.SqlHandle + AND SPID = @@SPID + AND query_plan.exist('/p:QueryPlan/@NonParallelPlanReason') = 1 + AND (##BlitzCacheProcs.is_parallel = 0 OR ##BlitzCacheProcs.is_parallel IS NULL) + OPTION (RECOMPILE); + + IF @ExpertMode > 0 + BEGIN + RAISERROR(N'Checking for ColumnStore queries operating in Row Mode instead of Batch Mode', 0, 1) WITH NOWAIT; + WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) + UPDATE ##BlitzCacheProcs + SET columnstore_row_mode = x.is_row_mode + FROM ( + SELECT + qs.SqlHandle, + relop.exist('/p:RelOp[(@EstimatedExecutionMode[.="Row"])]') AS is_row_mode + FROM #relop qs + WHERE [relop].exist('/p:RelOp/p:IndexScan[(@Storage[.="ColumnStore"])]') = 1 + ) AS x + WHERE ##BlitzCacheProcs.SqlHandle = x.SqlHandle + AND SPID = @@SPID + OPTION (RECOMPILE); + END; - UNION ALL - SELECT N'Unused Memory Grant Warning' AS [Configuration Parameter] , - N'10' , - N'Percent' , - N'Triggers an "Unused Memory Grant Warning" when a query uses >= X percent of its memory grant.'; - RETURN; - END; /* IF @Help = 1 */ +END; +/* 2014+ only */ +IF @v >= 12 +BEGIN + RAISERROR('Checking for downlevel cardinality estimators being used on SQL Server 2014.', 0, 1) WITH NOWAIT; + WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) + UPDATE p + SET downlevel_estimator = CASE WHEN statement.value('min(//p:StmtSimple/@CardinalityEstimationModelVersion)', 'int') < (@v * 10) THEN 1 END + FROM ##BlitzCacheProcs p + JOIN #statements s ON p.QueryHash = s.QueryHash + WHERE SPID = @@SPID + OPTION (RECOMPILE); +END ; -/*Validate version*/ -IF ( -SELECT - CASE - WHEN CONVERT(NVARCHAR(128), SERVERPROPERTY ('PRODUCTVERSION')) LIKE '8%' THEN 0 - WHEN CONVERT(NVARCHAR(128), SERVERPROPERTY ('PRODUCTVERSION')) LIKE '9%' THEN 0 - ELSE 1 - END -) = 0 +/* 2016+ only */ +IF @v >= 13 AND @ExpertMode > 0 BEGIN - DECLARE @version_msg VARCHAR(8000); - SELECT @version_msg = 'Sorry, sp_BlitzCache doesn''t work on versions of SQL prior to 2008.' + REPLICATE(CHAR(13), 7933); - PRINT @version_msg; - RETURN; -END; + RAISERROR('Checking for row level security in 2016 only', 0, 1) WITH NOWAIT; -IF(@OutputType = 'NONE' AND (@OutputTableName IS NULL OR @OutputSchemaName IS NULL OR @OutputDatabaseName IS NULL)) + WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) + UPDATE p + SET p.is_row_level = 1 + FROM ##BlitzCacheProcs p + JOIN #statements s ON p.QueryHash = s.QueryHash + WHERE SPID = @@SPID + AND statement.exist('/p:StmtSimple/@SecurityPolicyApplied[.="true"]') = 1 + OPTION (RECOMPILE); +END ; + +/* 2017+ only */ +IF @v >= 14 OR (@v = 13 AND @build >= 5026) BEGIN - RAISERROR('This procedure should be called with a value for all @Output* parameters, as @OutputType is set to NONE',12,1); - RETURN; -END; -IF(@OutputType = 'NONE') +IF @ExpertMode > 0 BEGIN - SET @HideSummary = 1; -END; +RAISERROR('Gathering stats information', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +INSERT INTO #stats_agg +SELECT qp.SqlHandle, + x.c.value('@LastUpdate', 'DATETIME2(7)') AS LastUpdate, + x.c.value('@ModificationCount', 'BIGINT') AS ModificationCount, + x.c.value('@SamplingPercent', 'FLOAT') AS SamplingPercent, + x.c.value('@Statistics', 'NVARCHAR(258)') AS [Statistics], + x.c.value('@Table', 'NVARCHAR(258)') AS [Table], + x.c.value('@Schema', 'NVARCHAR(258)') AS [Schema], + x.c.value('@Database', 'NVARCHAR(258)') AS [Database] +FROM #query_plan AS qp +CROSS APPLY qp.query_plan.nodes('//p:OptimizerStatsUsage/p:StatisticsInfo') x (c) +OPTION (RECOMPILE); -/* Lets get @SortOrder set to lower case here for comparisons later */ -SET @SortOrder = LOWER(@SortOrder); +RAISERROR('Checking for stale stats', 0, 1) WITH NOWAIT; +WITH stale_stats AS ( + SELECT sa.SqlHandle + FROM #stats_agg AS sa + GROUP BY sa.SqlHandle + HAVING MAX(sa.LastUpdate) <= DATEADD(DAY, -7, SYSDATETIME()) + AND AVG(sa.ModificationCount) >= 100000 +) +UPDATE b +SET stale_stats = 1 +FROM ##BlitzCacheProcs b +JOIN stale_stats os +ON b.SqlHandle = os.SqlHandle +AND b.SPID = @@SPID +OPTION (RECOMPILE); +END; -/* Set @Top based on sort */ -IF ( - @Top IS NULL - AND @SortOrder IN ( 'all', 'all sort' ) - ) - BEGIN - SET @Top = 5; - END; +IF @v >= 14 AND @ExpertMode > 0 +BEGIN +RAISERROR('Checking for adaptive joins', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p), +aj AS ( + SELECT + SqlHandle + FROM #relop AS r + CROSS APPLY r.relop.nodes('//p:RelOp') x(c) + WHERE x.c.exist('@IsAdaptive[.=1]') = 1 +) +UPDATE b +SET b.is_adaptive = 1 +FROM ##BlitzCacheProcs b +JOIN aj +ON b.SqlHandle = aj.SqlHandle +AND b.SPID = @@SPID +OPTION (RECOMPILE); +END; -IF ( - @Top IS NULL - AND @SortOrder NOT IN ( 'all', 'all sort' ) - ) - BEGIN - SET @Top = 10; - END; +IF ((@v >= 14 + OR (@v = 13 AND @build >= 5026) + OR (@v = 12 AND @build >= 6024)) + AND @ExpertMode > 0) +BEGIN; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p), +row_goals AS( +SELECT qs.QueryHash +FROM #relop qs +WHERE relop.value('sum(/p:RelOp/@EstimateRowsWithoutRowGoal)', 'float') > 0 +) +UPDATE b +SET b.is_row_goal = 1 +FROM ##BlitzCacheProcs b +JOIN row_goals +ON b.QueryHash = row_goals.QueryHash +AND b.SPID = @@SPID +OPTION (RECOMPILE); +END ; -/* If they want to sort by query hash, populate the @OnlyQueryHashes list for them */ -IF @SortOrder LIKE 'query hash%' - BEGIN - RAISERROR('Beginning query hash sort', 0, 1) WITH NOWAIT; +END; - SELECT TOP(@Top) qs.query_hash, - MAX(qs.max_worker_time) AS max_worker_time, - COUNT_BIG(*) AS records - INTO #query_hash_grouped - FROM sys.dm_exec_query_stats AS qs - CROSS APPLY ( SELECT pa.value - FROM sys.dm_exec_plan_attributes(qs.plan_handle) AS pa - WHERE pa.attribute = 'dbid' ) AS ca - GROUP BY qs.query_hash, ca.value - HAVING COUNT_BIG(*) > 1 - ORDER BY max_worker_time DESC, - records DESC; - - SELECT TOP (1) - @OnlyQueryHashes = STUFF((SELECT DISTINCT N',' + CONVERT(NVARCHAR(MAX), qhg.query_hash, 1) - FROM #query_hash_grouped AS qhg - WHERE qhg.query_hash <> 0x00 - FOR XML PATH(N''), TYPE).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 1, N'') - OPTION(RECOMPILE); - /* When they ran it, @SortOrder probably looked like 'query hash, cpu', so strip the first sort order out: */ - SELECT @SortOrder = LTRIM(REPLACE(REPLACE(@SortOrder,'query hash', ''), ',', '')); - - /* If they just called it with @SortOrder = 'query hash', set it to 'cpu' for backwards compatibility: */ - IF @SortOrder = '' SET @SortOrder = 'cpu'; +/* END Testing using XML nodes to speed up processing */ - END +/* Update to grab stored procedure name for individual statements */ +RAISERROR(N'Attempting to get stored procedure name for individual statements', 0, 1) WITH NOWAIT; +UPDATE p +SET QueryType = QueryType + ' (parent ' + + + QUOTENAME(OBJECT_SCHEMA_NAME(s.object_id, s.database_id)) + + '.' + + QUOTENAME(OBJECT_NAME(s.object_id, s.database_id)) + ')' +FROM ##BlitzCacheProcs p + JOIN sys.dm_exec_procedure_stats s ON p.SqlHandle = s.sql_handle +WHERE QueryType = 'Statement' +AND SPID = @@SPID +OPTION (RECOMPILE); -/* If they want to sort by duplicate, create a table with the worst offenders - issue #3345 */ -IF @SortOrder LIKE 'duplicate%' - BEGIN - RAISERROR('Beginning duplicate query hash sort', 0, 1) WITH NOWAIT; +/* Update to grab stored procedure name for individual statements when PSPO is detected */ +UPDATE p +SET QueryType = QueryType + ' (parent ' + + + QUOTENAME(OBJECT_SCHEMA_NAME(s.object_id, s.database_id)) + + '.' + + QUOTENAME(OBJECT_NAME(s.object_id, s.database_id)) + ')' +FROM ##BlitzCacheProcs p + OUTER APPLY ( + SELECT REPLACE(REPLACE(REPLACE(REPLACE(p.QueryText, ' (', '('), '( ', '('), ' =', '='), '= ', '=') AS NormalizedQueryText + ) a + OUTER APPLY ( + SELECT CHARINDEX('option(PLAN PER VALUE(ObjectID=', a.NormalizedQueryText) AS OptionStart + ) b + OUTER APPLY ( + SELECT SUBSTRING(a.NormalizedQueryText, b.OptionStart + 31, LEN(a.NormalizedQueryText) - b.OptionStart - 30) AS OptionSubstring + WHERE b.OptionStart > 0 + ) c + OUTER APPLY ( + SELECT PATINDEX('%[^0-9]%', c.OptionSubstring) AS ObjectLength + ) d + OUTER APPLY ( + SELECT TRY_CAST(SUBSTRING(OptionSubstring, 1, d.ObjectLength - 1) AS INT) AS ObjectId + ) e + JOIN sys.dm_exec_procedure_stats s ON DB_ID(p.DatabaseName) = s.database_id AND e.ObjectId = s.object_id +WHERE p.QueryType = 'Statement' +AND p.SPID = @@SPID +AND s.object_id IS NOT NULL +OPTION (RECOMPILE); - /* Find the query hashes that are the most duplicated */ - WITH MostCommonQueries AS ( - SELECT TOP(@Top) qs.query_hash, - COUNT_BIG(*) AS plans - FROM sys.dm_exec_query_stats AS qs - GROUP BY qs.query_hash - HAVING COUNT_BIG(*) > 100 - ORDER BY COUNT_BIG(*) DESC - ) - SELECT mcq_recent.sql_handle, mcq_recent.plan_handle, mcq_recent.creation_time AS duplicate_creation_time, mcq.plans - INTO #duplicate_query_filter - FROM MostCommonQueries mcq - CROSS APPLY ( SELECT TOP 1 qs.sql_handle, qs.plan_handle, qs.creation_time - FROM sys.dm_exec_query_stats qs - WHERE qs.query_hash = mcq.query_hash - ORDER BY qs.creation_time DESC) AS mcq_recent - OPTION (RECOMPILE); - - SET @MinimumExecutionCount = 0; - END +RAISERROR(N'Attempting to get function name for individual statements', 0, 1) WITH NOWAIT; +DECLARE @function_update_sql NVARCHAR(MAX) = N'' +IF EXISTS (SELECT 1/0 FROM sys.all_objects AS o WHERE o.name = 'dm_exec_function_stats') + BEGIN + SET @function_update_sql = @function_update_sql + N' + UPDATE p + SET QueryType = QueryType + '' (parent '' + + + QUOTENAME(OBJECT_SCHEMA_NAME(s.object_id, s.database_id)) + + ''.'' + + QUOTENAME(OBJECT_NAME(s.object_id, s.database_id)) + '')'' + FROM ##BlitzCacheProcs p + JOIN sys.dm_exec_function_stats s ON p.SqlHandle = s.sql_handle + WHERE QueryType = ''Statement'' + AND SPID = @@SPID + OPTION (RECOMPILE); + ' + EXEC sys.sp_executesql @function_update_sql + END -/* validate user inputs */ -IF @Top IS NULL - OR @SortOrder IS NULL - OR @QueryFilter IS NULL - OR @Reanalyze IS NULL +/* Trace Flag Checks 2012 SP3, 2014 SP2 and 2016 SP1 only)*/ +IF @v >= 11 BEGIN - RAISERROR(N'Several parameters (@Top, @SortOrder, @QueryFilter, @renalyze) are required. Do not set them to NULL. Please try again.', 16, 1) WITH NOWAIT; - RETURN; -END; -RAISERROR(N'Checking @MinutesBack validity.', 0, 1) WITH NOWAIT; -IF @MinutesBack IS NOT NULL - BEGIN - IF @MinutesBack > 0 - BEGIN - RAISERROR(N'Setting @MinutesBack to a negative number', 0, 1) WITH NOWAIT; - SET @MinutesBack *=-1; - END; - IF @MinutesBack = 0 - BEGIN - RAISERROR(N'@MinutesBack can''t be 0, setting to -1', 0, 1) WITH NOWAIT; - SET @MinutesBack = -1; - END; - END; +RAISERROR(N'Trace flag checks', 0, 1) WITH NOWAIT; +;WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +, tf_pretty AS ( +SELECT qp.QueryHash, + qp.SqlHandle, + q.n.value('@Value', 'INT') AS trace_flag, + q.n.value('@Scope', 'VARCHAR(10)') AS scope +FROM #query_plan qp +CROSS APPLY qp.query_plan.nodes('/p:QueryPlan/p:TraceFlags/p:TraceFlag') AS q(n) +) +INSERT INTO #trace_flags +SELECT DISTINCT tf1.SqlHandle , tf1.QueryHash, + STUFF(( + SELECT DISTINCT ', ' + CONVERT(VARCHAR(5), tf2.trace_flag) + FROM tf_pretty AS tf2 + WHERE tf1.SqlHandle = tf2.SqlHandle + AND tf1.QueryHash = tf2.QueryHash + AND tf2.scope = 'Global' + FOR XML PATH(N'')), 1, 2, N'' + ) AS global_trace_flags, + STUFF(( + SELECT DISTINCT ', ' + CONVERT(VARCHAR(5), tf2.trace_flag) + FROM tf_pretty AS tf2 + WHERE tf1.SqlHandle = tf2.SqlHandle + AND tf1.QueryHash = tf2.QueryHash + AND tf2.scope = 'Session' + FOR XML PATH(N'')), 1, 2, N'' + ) AS session_trace_flags +FROM tf_pretty AS tf1 +OPTION (RECOMPILE); +UPDATE p +SET p.trace_flags_session = tf.session_trace_flags +FROM ##BlitzCacheProcs p +JOIN #trace_flags tf ON tf.QueryHash = p.QueryHash +WHERE SPID = @@SPID +OPTION (RECOMPILE); +END; -DECLARE @DurationFilter_i INT, - @MinMemoryPerQuery INT, - @msg NVARCHAR(4000), - @NoobSaibot BIT = 0, - @VersionShowsAirQuoteActualPlans BIT, - @ObjectFullName NVARCHAR(2000), - @user_perm_sql NVARCHAR(MAX) = N'', - @user_perm_gb_out DECIMAL(10,2), - @common_version DECIMAL(10,2), - @buffer_pool_memory_gb DECIMAL(10,2), - @user_perm_percent DECIMAL(10,2), - @is_tokenstore_big BIT = 0, - @sort NVARCHAR(MAX) = N'', - @sort_filter NVARCHAR(MAX) = N''; +RAISERROR(N'Checking for MSTVFs', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +UPDATE b +SET b.is_mstvf = 1 +FROM #relop AS r +JOIN ##BlitzCacheProcs AS b +ON b.SqlHandle = r.SqlHandle +WHERE r.relop.exist('/p:RelOp[(@EstimateRows="100" or @EstimateRows="1") and @LogicalOp="Table-valued function"]') = 1 +OPTION (RECOMPILE); -IF @SortOrder = 'sp_BlitzIndex' + +IF @ExpertMode > 0 BEGIN - RAISERROR(N'OUTSTANDING!', 0, 1) WITH NOWAIT; - SET @SortOrder = 'reads'; - SET @NoobSaibot = 1; +RAISERROR(N'Checking for many to many merge joins', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +UPDATE b +SET b.is_mm_join = 1 +FROM #relop AS r +JOIN ##BlitzCacheProcs AS b +ON b.SqlHandle = r.SqlHandle +WHERE r.relop.exist('/p:RelOp/p:Merge/@ManyToMany[.="1"]') = 1 +OPTION (RECOMPILE); +END ; -END +IF @ExpertMode > 0 +BEGIN +RAISERROR(N'Is Paul White Electric?', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p), +is_paul_white_electric AS ( +SELECT 1 AS [is_paul_white_electric], +r.SqlHandle +FROM #relop AS r +CROSS APPLY r.relop.nodes('//p:RelOp') c(n) +WHERE c.n.exist('@PhysicalOp[.="Switch"]') = 1 +) +UPDATE b +SET b.is_paul_white_electric = ipwe.is_paul_white_electric +FROM ##BlitzCacheProcs AS b +JOIN is_paul_white_electric ipwe +ON ipwe.SqlHandle = b.SqlHandle +WHERE b.SPID = @@SPID +OPTION (RECOMPILE); +END ; -/* Change duration from seconds to milliseconds */ -IF @DurationFilter IS NOT NULL - BEGIN - RAISERROR(N'Converting Duration Filter to milliseconds', 0, 1) WITH NOWAIT; - SET @DurationFilter_i = CAST((@DurationFilter * 1000.0) AS INT); - END; -RAISERROR(N'Checking database validity', 0, 1) WITH NOWAIT; -SET @DatabaseName = LTRIM(RTRIM(@DatabaseName)) ; +RAISERROR(N'Checking for non-sargable predicates', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES ( 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) +, nsarg + AS ( SELECT r.QueryHash, 1 AS fn, 0 AS jo, 0 AS lk + FROM #relop AS r + CROSS APPLY r.relop.nodes('/p:RelOp/p:IndexScan/p:Predicate/p:ScalarOperator/p:Compare/p:ScalarOperator') AS ca(x) + WHERE ( ca.x.exist('//p:ScalarOperator/p:Intrinsic/@FunctionName') = 1 + OR ca.x.exist('//p:ScalarOperator/p:IF') = 1 ) + UNION ALL + SELECT r.QueryHash, 0 AS fn, 1 AS jo, 0 AS lk + FROM #relop AS r + CROSS APPLY r.relop.nodes('/p:RelOp//p:ScalarOperator') AS ca(x) + WHERE r.relop.exist('/p:RelOp[contains(@LogicalOp, "Join")]') = 1 + AND ca.x.exist('//p:ScalarOperator[contains(@ScalarString, "Expr")]') = 1 + UNION ALL + SELECT r.QueryHash, 0 AS fn, 0 AS jo, 1 AS lk + FROM #relop AS r + CROSS APPLY r.relop.nodes('/p:RelOp/p:IndexScan/p:Predicate/p:ScalarOperator') AS ca(x) + CROSS APPLY ca.x.nodes('//p:Const') AS co(x) + WHERE ca.x.exist('//p:ScalarOperator/p:Intrinsic/@FunctionName[.="like"]') = 1 + AND ( ( co.x.value('substring(@ConstValue, 1, 1)', 'VARCHAR(100)') <> 'N' + AND co.x.value('substring(@ConstValue, 2, 1)', 'VARCHAR(100)') = '%' ) + OR ( co.x.value('substring(@ConstValue, 1, 1)', 'VARCHAR(100)') = 'N' + AND co.x.value('substring(@ConstValue, 3, 1)', 'VARCHAR(100)') = '%' ))), + d_nsarg + AS ( SELECT DISTINCT + nsarg.QueryHash + FROM nsarg + WHERE nsarg.fn = 1 + OR nsarg.jo = 1 + OR nsarg.lk = 1 ) +UPDATE b +SET b.is_nonsargable = 1 +FROM d_nsarg AS d +JOIN ##BlitzCacheProcs AS b + ON b.QueryHash = d.QueryHash +WHERE b.SPID = @@SPID +OPTION ( RECOMPILE ); -IF SERVERPROPERTY('EngineEdition') IN (5, 6) AND DB_NAME() <> @DatabaseName -BEGIN - RAISERROR('You specified a database name other than the current database, but Azure SQL DB does not allow you to change databases. Execute sp_BlitzCache from the database you want to analyze.', 16, 1); - RETURN; -END; -IF (DB_ID(@DatabaseName)) IS NULL AND @DatabaseName <> N'' -BEGIN - RAISERROR('The database you specified does not exist. Please check the name and try again.', 16, 1); - RETURN; -END; -IF (SELECT DATABASEPROPERTYEX(ISNULL(@DatabaseName, 'master'), 'Collation')) IS NULL AND SERVERPROPERTY('EngineEdition') NOT IN (5, 6, 8) -BEGIN - RAISERROR('The database you specified is not readable. Please check the name and try again. Better yet, check your server.', 16, 1); - RETURN; -END; +/*Begin implicit conversion and parameter info */ -SELECT @MinMemoryPerQuery = CONVERT(INT, c.value) FROM sys.configurations AS c WHERE c.name = 'min memory per query (KB)'; +RAISERROR(N'Getting information about implicit conversions and stored proc parameters', 0, 1) WITH NOWAIT; -SET @SortOrder = REPLACE(REPLACE(@SortOrder, 'average', 'avg'), '.', ''); +RAISERROR(N'Getting variable info', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES ( 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) +INSERT #variable_info ( SPID, QueryHash, SqlHandle, proc_name, variable_name, variable_datatype, compile_time_value ) +SELECT DISTINCT @@SPID, + qp.QueryHash, + qp.SqlHandle, + b.QueryType AS proc_name, + q.n.value('@Column', 'NVARCHAR(258)') AS variable_name, + q.n.value('@ParameterDataType', 'NVARCHAR(258)') AS variable_datatype, + q.n.value('@ParameterCompiledValue', 'NVARCHAR(258)') AS compile_time_value +FROM #query_plan AS qp +JOIN ##BlitzCacheProcs AS b +ON (b.QueryType = 'adhoc' AND b.QueryHash = qp.QueryHash) +OR (b.QueryType <> 'adhoc' AND b.SqlHandle = qp.SqlHandle) +CROSS APPLY qp.query_plan.nodes('//p:QueryPlan/p:ParameterList/p:ColumnReference') AS q(n) +WHERE b.SPID = @@SPID +OPTION (RECOMPILE); -SET @SortOrder = CASE - WHEN @SortOrder IN ('executions per minute','execution per minute','executions / minute','execution / minute','xpm') THEN 'avg executions' - WHEN @SortOrder IN ('recent compilations','recent compilation','compile') THEN 'compiles' - WHEN @SortOrder IN ('read') THEN 'reads' - WHEN @SortOrder IN ('avg read') THEN 'avg reads' - WHEN @SortOrder IN ('write') THEN 'writes' - WHEN @SortOrder IN ('avg write') THEN 'avg writes' - WHEN @SortOrder IN ('memory grants') THEN 'memory grant' - WHEN @SortOrder IN ('avg memory grants') THEN 'avg memory grant' - WHEN @SortOrder IN ('unused grants','unused memory', 'unused memory grant', 'unused memory grants') THEN 'unused grant' - WHEN @SortOrder IN ('spill') THEN 'spills' - WHEN @SortOrder IN ('avg spill') THEN 'avg spills' - WHEN @SortOrder IN ('execution') THEN 'executions' - WHEN @SortOrder IN ('duplicates') THEN 'duplicate' - ELSE @SortOrder END - -RAISERROR(N'Checking sort order', 0, 1) WITH NOWAIT; -IF @SortOrder NOT IN ('cpu', 'avg cpu', 'reads', 'avg reads', 'writes', 'avg writes', - 'duration', 'avg duration', 'executions', 'avg executions', - 'compiles', 'memory grant', 'avg memory grant', 'unused grant', - 'spills', 'avg spills', 'all', 'all avg', 'sp_BlitzIndex', - 'query hash', 'duplicate') - BEGIN - RAISERROR(N'Invalid sort order chosen, reverting to cpu', 16, 1) WITH NOWAIT; - SET @SortOrder = 'cpu'; - END; -SET @QueryFilter = LOWER(@QueryFilter); - -IF LEFT(@QueryFilter, 3) NOT IN ('all', 'sta', 'pro', 'fun') - BEGIN - RAISERROR(N'Invalid query filter chosen. Reverting to all.', 0, 1) WITH NOWAIT; - SET @QueryFilter = 'all'; - END; - -IF @SkipAnalysis = 1 - BEGIN - RAISERROR(N'Skip Analysis set to 1, hiding Summary', 0, 1) WITH NOWAIT; - SET @HideSummary = 1; - END; - -DECLARE @AllSortSql NVARCHAR(MAX) = N''; -DECLARE @VersionShowsMemoryGrants BIT; -IF EXISTS(SELECT * FROM sys.all_columns WHERE object_id = OBJECT_ID('sys.dm_exec_query_stats') AND name = 'max_grant_kb') - SET @VersionShowsMemoryGrants = 1; -ELSE - SET @VersionShowsMemoryGrants = 0; - -DECLARE @VersionShowsSpills BIT; -IF EXISTS(SELECT * FROM sys.all_columns WHERE object_id = OBJECT_ID('sys.dm_exec_query_stats') AND name = 'max_spills') - SET @VersionShowsSpills = 1; -ELSE - SET @VersionShowsSpills = 0; - -IF EXISTS(SELECT * FROM sys.all_columns WHERE object_id = OBJECT_ID('sys.dm_exec_query_plan_stats') AND name = 'query_plan') - SET @VersionShowsAirQuoteActualPlans = 1; -ELSE - SET @VersionShowsAirQuoteActualPlans = 0; - -IF @Reanalyze = 1 - BEGIN - IF OBJECT_ID('tempdb..##BlitzCacheResults') IS NULL - BEGIN - RAISERROR(N'##BlitzCacheResults does not exist, can''t reanalyze', 0, 1) WITH NOWAIT; - SET @Reanalyze = 0; - END - ELSE - BEGIN - RAISERROR(N'Reanalyzing current data, skipping to results', 0, 1) WITH NOWAIT; - GOTO Results; - END; - END; - - -IF @SortOrder IN ('all', 'all avg') - BEGIN - RAISERROR(N'Checking all sort orders, please be patient', 0, 1) WITH NOWAIT; - GOTO AllSorts; - END; - -RAISERROR(N'Creating temp tables for internal processing', 0, 1) WITH NOWAIT; -IF OBJECT_ID('tempdb..#only_query_hashes') IS NOT NULL - DROP TABLE #only_query_hashes ; - -IF OBJECT_ID('tempdb..#ignore_query_hashes') IS NOT NULL - DROP TABLE #ignore_query_hashes ; - -IF OBJECT_ID('tempdb..#only_sql_handles') IS NOT NULL - DROP TABLE #only_sql_handles ; - -IF OBJECT_ID('tempdb..#ignore_sql_handles') IS NOT NULL - DROP TABLE #ignore_sql_handles ; - -IF OBJECT_ID('tempdb..#p') IS NOT NULL - DROP TABLE #p; - -IF OBJECT_ID ('tempdb..#checkversion') IS NOT NULL - DROP TABLE #checkversion; - -IF OBJECT_ID ('tempdb..#configuration') IS NOT NULL - DROP TABLE #configuration; - -IF OBJECT_ID ('tempdb..#stored_proc_info') IS NOT NULL - DROP TABLE #stored_proc_info; - -IF OBJECT_ID ('tempdb..#plan_creation') IS NOT NULL - DROP TABLE #plan_creation; - -IF OBJECT_ID ('tempdb..#est_rows') IS NOT NULL - DROP TABLE #est_rows; - -IF OBJECT_ID ('tempdb..#plan_cost') IS NOT NULL - DROP TABLE #plan_cost; - -IF OBJECT_ID ('tempdb..#proc_costs') IS NOT NULL - DROP TABLE #proc_costs; +RAISERROR(N'Getting conversion info', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES ( 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) +INSERT #conversion_info ( SPID, QueryHash, SqlHandle, proc_name, expression ) +SELECT DISTINCT @@SPID, + qp.QueryHash, + qp.SqlHandle, + b.QueryType AS proc_name, + qq.c.value('@Expression', 'NVARCHAR(4000)') AS expression +FROM #query_plan AS qp +JOIN ##BlitzCacheProcs AS b +ON (b.QueryType = 'adhoc' AND b.QueryHash = qp.QueryHash) +OR (b.QueryType <> 'adhoc' AND b.SqlHandle = qp.SqlHandle) +CROSS APPLY qp.query_plan.nodes('//p:QueryPlan/p:Warnings/p:PlanAffectingConvert') AS qq(c) +WHERE qq.c.exist('@ConvertIssue[.="Seek Plan"]') = 1 + AND qp.QueryHash IS NOT NULL + AND b.implicit_conversions = 1 +AND b.SPID = @@SPID +OPTION (RECOMPILE); -IF OBJECT_ID ('tempdb..#stats_agg') IS NOT NULL - DROP TABLE #stats_agg; -IF OBJECT_ID ('tempdb..#trace_flags') IS NOT NULL - DROP TABLE #trace_flags; +RAISERROR(N'Parsing conversion info', 0, 1) WITH NOWAIT; +INSERT #stored_proc_info ( SPID, SqlHandle, QueryHash, proc_name, variable_name, variable_datatype, converted_column_name, column_name, converted_to, compile_time_value ) +SELECT @@SPID AS SPID, + ci.SqlHandle, + ci.QueryHash, + REPLACE(REPLACE(REPLACE(ci.proc_name, ')', ''), 'Statement (parent ', ''), 'Procedure or Function: ', '') AS proc_name, + CASE WHEN ci.at_charindex > 0 + AND ci.bracket_charindex > 0 + THEN SUBSTRING(ci.expression, ci.at_charindex, ci.bracket_charindex) + ELSE N'**no_variable**' + END AS variable_name, + N'**no_variable**' AS variable_datatype, + CASE WHEN ci.at_charindex = 0 + AND ci.comma_charindex > 0 + AND ci.second_comma_charindex > 0 + THEN SUBSTRING(ci.expression, ci.comma_charindex, ci.second_comma_charindex) + ELSE N'**no_column**' + END AS converted_column_name, + CASE WHEN ci.at_charindex = 0 + AND ci.equal_charindex > 0 + AND ci.convert_implicit_charindex = 0 + THEN SUBSTRING(ci.expression, ci.equal_charindex, 4000) + WHEN ci.at_charindex = 0 + AND (ci.equal_charindex -1) > 0 + AND ci.convert_implicit_charindex > 0 + THEN SUBSTRING(ci.expression, 0, ci.equal_charindex -1) + WHEN ci.at_charindex > 0 + AND ci.comma_charindex > 0 + AND ci.second_comma_charindex > 0 + THEN SUBSTRING(ci.expression, ci.comma_charindex, ci.second_comma_charindex) + ELSE N'**no_column **' + END AS column_name, + CASE WHEN ci.paren_charindex > 0 + 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 + 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 +FROM #conversion_info AS ci +OPTION (RECOMPILE); -IF OBJECT_ID('tempdb..#variable_info') IS NOT NULL - DROP TABLE #variable_info; -IF OBJECT_ID('tempdb..#conversion_info') IS NOT NULL - DROP TABLE #conversion_info; +RAISERROR(N'Updating variables for inserted procs', 0, 1) WITH NOWAIT; +UPDATE sp +SET sp.variable_datatype = vi.variable_datatype, + sp.compile_time_value = vi.compile_time_value +FROM #stored_proc_info AS sp +JOIN #variable_info AS vi +ON (sp.proc_name = 'adhoc' AND sp.QueryHash = vi.QueryHash) +OR (sp.proc_name <> 'adhoc' AND sp.SqlHandle = vi.SqlHandle) +AND sp.variable_name = vi.variable_name +OPTION (RECOMPILE); -IF OBJECT_ID('tempdb..#missing_index_xml') IS NOT NULL - DROP TABLE #missing_index_xml; -IF OBJECT_ID('tempdb..#missing_index_schema') IS NOT NULL - DROP TABLE #missing_index_schema; +RAISERROR(N'Inserting variables for other procs', 0, 1) WITH NOWAIT; +INSERT #stored_proc_info + ( SPID, SqlHandle, QueryHash, variable_name, variable_datatype, compile_time_value, proc_name ) +SELECT vi.SPID, vi.SqlHandle, vi.QueryHash, vi.variable_name, vi.variable_datatype, vi.compile_time_value, REPLACE(REPLACE(REPLACE(vi.proc_name, ')', ''), 'Statement (parent ', ''), 'Procedure or Function: ', '') AS proc_name +FROM #variable_info AS vi +WHERE NOT EXISTS +( + SELECT * + FROM #stored_proc_info AS sp + WHERE (sp.proc_name = 'adhoc' AND sp.QueryHash = vi.QueryHash) + OR (sp.proc_name <> 'adhoc' AND sp.SqlHandle = vi.SqlHandle) +) +OPTION (RECOMPILE); -IF OBJECT_ID('tempdb..#missing_index_usage') IS NOT NULL - DROP TABLE #missing_index_usage; -IF OBJECT_ID('tempdb..#missing_index_detail') IS NOT NULL - DROP TABLE #missing_index_detail; +RAISERROR(N'Updating procs', 0, 1) WITH NOWAIT; +UPDATE s +SET s.variable_datatype = CASE WHEN s.variable_datatype LIKE '%(%)%' + THEN LEFT(s.variable_datatype, CHARINDEX('(', s.variable_datatype) - 1) + ELSE s.variable_datatype + END, + s.converted_to = CASE WHEN s.converted_to LIKE '%(%)%' + THEN LEFT(s.converted_to, CHARINDEX('(', s.converted_to) - 1) + ELSE s.converted_to + END, + s.compile_time_value = CASE WHEN s.compile_time_value LIKE '%(%)%' + THEN SUBSTRING(s.compile_time_value, + CHARINDEX('(', s.compile_time_value) + 1, + CHARINDEX(')', s.compile_time_value, CHARINDEX('(', s.compile_time_value) + 1) - 1 - CHARINDEX('(', s.compile_time_value) + ) + WHEN variable_datatype NOT IN ('bit', 'tinyint', 'smallint', 'int', 'bigint') + AND s.variable_datatype NOT LIKE '%binary%' + AND s.compile_time_value NOT LIKE 'N''%''' + AND s.compile_time_value NOT LIKE '''%''' + AND s.compile_time_value <> s.column_name + AND s.compile_time_value <> '**idk_man**' + THEN QUOTENAME(compile_time_value, '''') + ELSE s.compile_time_value + END +FROM #stored_proc_info AS s +OPTION (RECOMPILE); -IF OBJECT_ID('tempdb..#missing_index_pretty') IS NOT NULL - DROP TABLE #missing_index_pretty; -IF OBJECT_ID('tempdb..#index_spool_ugly') IS NOT NULL - DROP TABLE #index_spool_ugly; - -IF OBJECT_ID('tempdb..#ReadableDBs') IS NOT NULL - DROP TABLE #ReadableDBs; +RAISERROR(N'Updating SET options', 0, 1) WITH NOWAIT; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +UPDATE s +SET set_options = set_options.ansi_set_options +FROM #stored_proc_info AS s +JOIN ( + SELECT x.SqlHandle, + N'SET ANSI_NULLS ' + CASE WHEN [ANSI_NULLS] = 'true' THEN N'ON ' ELSE N'OFF ' END + NCHAR(10) + + N'SET ANSI_PADDING ' + CASE WHEN [ANSI_PADDING] = 'true' THEN N'ON ' ELSE N'OFF ' END + NCHAR(10) + + N'SET ANSI_WARNINGS ' + CASE WHEN [ANSI_WARNINGS] = 'true' THEN N'ON ' ELSE N'OFF ' END + NCHAR(10) + + N'SET ARITHABORT ' + CASE WHEN [ARITHABORT] = 'true' THEN N'ON ' ELSE N' OFF ' END + NCHAR(10) + + N'SET CONCAT_NULL_YIELDS_NULL ' + CASE WHEN [CONCAT_NULL_YIELDS_NULL] = 'true' THEN N'ON ' ELSE N'OFF ' END + NCHAR(10) + + N'SET NUMERIC_ROUNDABORT ' + CASE WHEN [NUMERIC_ROUNDABORT] = 'true' THEN N'ON ' ELSE N'OFF ' END + NCHAR(10) + + N'SET QUOTED_IDENTIFIER ' + CASE WHEN [QUOTED_IDENTIFIER] = 'true' THEN N'ON ' ELSE N'OFF ' + NCHAR(10) END AS [ansi_set_options] + FROM ( + SELECT + s.SqlHandle, + so.o.value('@ANSI_NULLS', 'NVARCHAR(20)') AS [ANSI_NULLS], + so.o.value('@ANSI_PADDING', 'NVARCHAR(20)') AS [ANSI_PADDING], + so.o.value('@ANSI_WARNINGS', 'NVARCHAR(20)') AS [ANSI_WARNINGS], + so.o.value('@ARITHABORT', 'NVARCHAR(20)') AS [ARITHABORT], + so.o.value('@CONCAT_NULL_YIELDS_NULL', 'NVARCHAR(20)') AS [CONCAT_NULL_YIELDS_NULL], + so.o.value('@NUMERIC_ROUNDABORT', 'NVARCHAR(20)') AS [NUMERIC_ROUNDABORT], + so.o.value('@QUOTED_IDENTIFIER', 'NVARCHAR(20)') AS [QUOTED_IDENTIFIER] + FROM #statements AS s + CROSS APPLY s.statement.nodes('//p:StatementSetOptions') AS so(o) + ) AS x +) AS set_options ON set_options.SqlHandle = s.SqlHandle +OPTION(RECOMPILE); -IF OBJECT_ID('tempdb..#plan_usage') IS NOT NULL - DROP TABLE #plan_usage; -CREATE TABLE #only_query_hashes ( - query_hash BINARY(8) -); - -CREATE TABLE #ignore_query_hashes ( - query_hash BINARY(8) -); - -CREATE TABLE #only_sql_handles ( - sql_handle VARBINARY(64) -); - -CREATE TABLE #ignore_sql_handles ( - sql_handle VARBINARY(64) -); - -CREATE TABLE #p ( - SqlHandle VARBINARY(64), - TotalCPU BIGINT, - TotalDuration BIGINT, - TotalReads BIGINT, - TotalWrites BIGINT, - ExecutionCount BIGINT -); - -CREATE TABLE #checkversion ( - version NVARCHAR(128), - common_version AS SUBSTRING(version, 1, CHARINDEX('.', version) + 1 ), - major AS PARSENAME(CONVERT(VARCHAR(32), version), 4), - minor AS PARSENAME(CONVERT(VARCHAR(32), version), 3), - build AS PARSENAME(CONVERT(VARCHAR(32), version), 2), - revision AS PARSENAME(CONVERT(VARCHAR(32), version), 1) -); - -CREATE TABLE #configuration ( - parameter_name VARCHAR(100), - value DECIMAL(38,0) -); +RAISERROR(N'Updating conversion XML', 0, 1) WITH NOWAIT; +WITH precheck AS ( +SELECT spi.SPID, + spi.SqlHandle, + spi.proc_name, + (SELECT + CASE WHEN spi.proc_name <> 'Statement' + THEN N'The stored procedure ' + spi.proc_name + ELSE N'This ad hoc statement' + END + + N' had the following implicit conversions: ' + + CHAR(10) + + STUFF(( + SELECT DISTINCT + @nl + + CASE WHEN spi2.variable_name <> N'**no_variable**' + THEN N'The variable ' + WHEN spi2.variable_name = N'**no_variable**' AND (spi2.column_name = spi2.converted_column_name OR spi2.column_name LIKE '%CONVERT_IMPLICIT%') + THEN N'The compiled value ' + WHEN spi2.column_name LIKE '%Expr%' + THEN 'The expression ' + ELSE N'The column ' + END + + CASE WHEN spi2.variable_name <> N'**no_variable**' + THEN spi2.variable_name + WHEN spi2.variable_name = N'**no_variable**' AND (spi2.column_name = spi2.converted_column_name OR spi2.column_name LIKE '%CONVERT_IMPLICIT%') + THEN spi2.compile_time_value + ELSE spi2.column_name + END + + N' has a data type of ' + + CASE WHEN spi2.variable_datatype = N'**no_variable**' THEN spi2.converted_to + ELSE spi2.variable_datatype + END + + N' which caused implicit conversion on the column ' + + CASE WHEN spi2.column_name LIKE N'%CONVERT_IMPLICIT%' + THEN spi2.converted_column_name + WHEN spi2.column_name = N'**no_column**' + THEN spi2.converted_column_name + WHEN spi2.converted_column_name = N'**no_column**' + THEN spi2.column_name + WHEN spi2.column_name <> spi2.converted_column_name + THEN spi2.converted_column_name + ELSE spi2.column_name + END + + CASE WHEN spi2.variable_name = N'**no_variable**' AND (spi2.column_name = spi2.converted_column_name OR spi2.column_name LIKE '%CONVERT_IMPLICIT%') + THEN N'' + WHEN spi2.column_name LIKE '%Expr%' + THEN N'' + WHEN spi2.compile_time_value NOT IN ('**declared in proc**', '**idk_man**') + AND spi2.compile_time_value <> spi2.column_name + THEN ' with the value ' + RTRIM(spi2.compile_time_value) + ELSE N'' + END + + '.' + FROM #stored_proc_info AS spi2 + WHERE spi.SqlHandle = spi2.SqlHandle + FOR XML PATH(N''), TYPE).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 1, N'') + AS [processing-instruction(ClickMe)] FOR XML PATH(''), TYPE ) + AS implicit_conversion_info +FROM #stored_proc_info AS spi +GROUP BY spi.SPID, spi.SqlHandle, spi.proc_name +) +UPDATE b +SET b.implicit_conversion_info = pk.implicit_conversion_info +FROM ##BlitzCacheProcs AS b +JOIN precheck pk +ON pk.SqlHandle = b.SqlHandle +AND pk.SPID = b.SPID +OPTION (RECOMPILE); -CREATE TABLE #plan_creation -( - percent_24 DECIMAL(5, 2), - percent_4 DECIMAL(5, 2), - percent_1 DECIMAL(5, 2), - total_plans INT, - SPID INT -); -CREATE TABLE #est_rows -( - QueryHash BINARY(8), - estimated_rows FLOAT -); +RAISERROR(N'Updating cached parameter XML for stored procs', 0, 1) WITH NOWAIT; +WITH precheck AS ( +SELECT spi.SPID, + spi.SqlHandle, + spi.proc_name, + (SELECT + set_options + + @nl + + @nl + + N'EXEC ' + + spi.proc_name + + N' ' + + STUFF(( + SELECT DISTINCT N', ' + + CASE WHEN spi2.variable_name <> N'**no_variable**' AND spi2.compile_time_value <> N'**idk_man**' + THEN spi2.variable_name + N' = ' + ELSE @nl + N' We could not find any cached parameter values for this stored proc. ' + END + + CASE WHEN spi2.variable_name = N'**no_variable**' OR spi2.compile_time_value = N'**idk_man**' + THEN @nl + N'More info on possible reasons: https://www.brentozar.com/go/noplans ' + WHEN spi2.compile_time_value = N'NULL' + THEN spi2.compile_time_value + ELSE RTRIM(spi2.compile_time_value) + END + FROM #stored_proc_info AS spi2 + WHERE spi.SqlHandle = spi2.SqlHandle + AND spi2.proc_name <> N'Statement' + FOR XML PATH(N''), TYPE).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 1, N'') + AS [processing-instruction(ClickMe)] FOR XML PATH(''), TYPE ) + AS cached_execution_parameters +FROM #stored_proc_info AS spi +GROUP BY spi.SPID, spi.SqlHandle, spi.proc_name, spi.set_options +) +UPDATE b +SET b.cached_execution_parameters = pk.cached_execution_parameters +FROM ##BlitzCacheProcs AS b +JOIN precheck pk +ON pk.SqlHandle = b.SqlHandle +AND pk.SPID = b.SPID +WHERE b.QueryType <> N'Statement' +OPTION (RECOMPILE); -CREATE TABLE #plan_cost -( - QueryPlanCost FLOAT, - SqlHandle VARBINARY(64), - PlanHandle VARBINARY(64), - QueryHash BINARY(8), - QueryPlanHash BINARY(8) -); -CREATE TABLE #proc_costs -( - PlanTotalQuery FLOAT, - PlanHandle VARBINARY(64), - SqlHandle VARBINARY(64) -); +RAISERROR(N'Updating cached parameter XML for statements', 0, 1) WITH NOWAIT; +WITH precheck AS ( +SELECT spi.SPID, + spi.SqlHandle, + spi.proc_name, + (SELECT + set_options + + @nl + + @nl + + N' See QueryText column for full query text' + + @nl + + @nl + + STUFF(( + SELECT DISTINCT N', ' + + CASE WHEN spi2.variable_name <> N'**no_variable**' AND spi2.compile_time_value <> N'**idk_man**' + THEN spi2.variable_name + N' = ' + ELSE @nl + N' We could not find any cached parameter values for this stored proc. ' + END + + CASE WHEN spi2.variable_name = N'**no_variable**' OR spi2.compile_time_value = N'**idk_man**' + THEN @nl + N' More info on possible reasons: https://www.brentozar.com/go/noplans ' + WHEN spi2.compile_time_value = N'NULL' + THEN spi2.compile_time_value + ELSE RTRIM(spi2.compile_time_value) + END + FROM #stored_proc_info AS spi2 + WHERE spi.SqlHandle = spi2.SqlHandle + AND spi2.proc_name = N'Statement' + AND spi2.variable_name NOT LIKE N'%msparam%' + FOR XML PATH(N''), TYPE).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 1, N'') + AS [processing-instruction(ClickMe)] FOR XML PATH(''), TYPE ) + AS cached_execution_parameters +FROM #stored_proc_info AS spi +GROUP BY spi.SPID, spi.SqlHandle, spi.proc_name, spi.set_options +) +UPDATE b +SET b.cached_execution_parameters = pk.cached_execution_parameters +FROM ##BlitzCacheProcs AS b +JOIN precheck pk +ON pk.SqlHandle = b.SqlHandle +AND pk.SPID = b.SPID +WHERE b.QueryType = N'Statement' +OPTION (RECOMPILE); -CREATE TABLE #stats_agg -( - SqlHandle VARBINARY(64), - LastUpdate DATETIME2(7), - ModificationCount BIGINT, - SamplingPercent FLOAT, - [Statistics] NVARCHAR(258), - [Table] NVARCHAR(258), - [Schema] NVARCHAR(258), - [Database] NVARCHAR(258), -); +RAISERROR(N'Filling in implicit conversion and cached plan parameter info', 0, 1) WITH NOWAIT; +UPDATE b +SET b.implicit_conversion_info = CASE WHEN b.implicit_conversion_info IS NULL + OR CONVERT(NVARCHAR(MAX), b.implicit_conversion_info) = N'' + THEN '' + ELSE b.implicit_conversion_info END, + b.cached_execution_parameters = CASE WHEN b.cached_execution_parameters IS NULL + OR CONVERT(NVARCHAR(MAX), b.cached_execution_parameters) = N'' + THEN '' + ELSE b.cached_execution_parameters END +FROM ##BlitzCacheProcs AS b +WHERE b.SPID = @@SPID +OPTION (RECOMPILE); -CREATE TABLE #trace_flags -( - SqlHandle VARBINARY(64), - QueryHash BINARY(8), - global_trace_flags VARCHAR(1000), - session_trace_flags VARCHAR(1000) -); +/*End implicit conversion and parameter info*/ -CREATE TABLE #stored_proc_info -( - SPID INT, - SqlHandle VARBINARY(64), - QueryHash BINARY(8), - variable_name NVARCHAR(258), - variable_datatype NVARCHAR(258), - converted_column_name NVARCHAR(258), - compile_time_value NVARCHAR(258), - proc_name NVARCHAR(1000), - column_name NVARCHAR(4000), - converted_to NVARCHAR(258), - set_options NVARCHAR(1000) -); +/*Begin Missing Index*/ +IF EXISTS ( SELECT 1/0 + FROM ##BlitzCacheProcs AS bbcp + WHERE bbcp.missing_index_count > 0 + OR bbcp.index_spool_cost > 0 + OR bbcp.index_spool_rows > 0 + AND bbcp.SPID = @@SPID ) + + BEGIN + RAISERROR(N'Inserting to #missing_index_xml', 0, 1) WITH NOWAIT; + WITH XMLNAMESPACES ( 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) + INSERT #missing_index_xml + SELECT qp.QueryHash, + qp.SqlHandle, + c.mg.value('@Impact', 'FLOAT') AS Impact, + c.mg.query('.') AS cmg + FROM #query_plan AS qp + CROSS APPLY qp.query_plan.nodes('//p:MissingIndexes/p:MissingIndexGroup') AS c(mg) + WHERE qp.QueryHash IS NOT NULL + OPTION(RECOMPILE); + + RAISERROR(N'Inserting to #missing_index_schema', 0, 1) WITH NOWAIT; + WITH XMLNAMESPACES ( 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) + INSERT #missing_index_schema + SELECT mix.QueryHash, mix.SqlHandle, mix.impact, + c.mi.value('@Database', 'NVARCHAR(128)'), + c.mi.value('@Schema', 'NVARCHAR(128)'), + c.mi.value('@Table', 'NVARCHAR(128)'), + c.mi.query('.') + FROM #missing_index_xml AS mix + CROSS APPLY mix.index_xml.nodes('//p:MissingIndex') AS c(mi) + OPTION(RECOMPILE); + + RAISERROR(N'Inserting to #missing_index_usage', 0, 1) WITH NOWAIT; + WITH XMLNAMESPACES ( 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) + INSERT #missing_index_usage + SELECT ms.QueryHash, ms.SqlHandle, ms.impact, ms.database_name, ms.schema_name, ms.table_name, + c.cg.value('@Usage', 'NVARCHAR(128)'), + c.cg.query('.') + FROM #missing_index_schema ms + CROSS APPLY ms.index_xml.nodes('//p:ColumnGroup') AS c(cg) + OPTION(RECOMPILE); + + RAISERROR(N'Inserting to #missing_index_detail', 0, 1) WITH NOWAIT; + WITH XMLNAMESPACES ( 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) + INSERT #missing_index_detail + SELECT miu.QueryHash, + miu.SqlHandle, + miu.impact, + miu.database_name, + miu.schema_name, + miu.table_name, + miu.usage, + c.c.value('@Name', 'NVARCHAR(128)') + FROM #missing_index_usage AS miu + CROSS APPLY miu.index_xml.nodes('//p:Column') AS c(c) + OPTION (RECOMPILE); + + RAISERROR(N'Inserting to missing indexes to #missing_index_pretty', 0, 1) WITH NOWAIT; + INSERT #missing_index_pretty + ( QueryHash, SqlHandle, impact, database_name, schema_name, table_name, equality, inequality, include, executions, query_cost, creation_hours, is_spool ) + SELECT DISTINCT m.QueryHash, m.SqlHandle, m.impact, m.database_name, m.schema_name, m.table_name + , STUFF(( SELECT DISTINCT N', ' + ISNULL(m2.column_name, '') AS column_name + FROM #missing_index_detail AS m2 + WHERE m2.usage = 'EQUALITY' + AND m.QueryHash = m2.QueryHash + AND m.SqlHandle = m2.SqlHandle + AND m.impact = m2.impact + AND m.database_name = m2.database_name + AND m.schema_name = m2.schema_name + AND m.table_name = m2.table_name + FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 2, N'') AS equality + , STUFF(( SELECT DISTINCT N', ' + ISNULL(m2.column_name, '') AS column_name + FROM #missing_index_detail AS m2 + WHERE m2.usage = 'INEQUALITY' + AND m.QueryHash = m2.QueryHash + AND m.SqlHandle = m2.SqlHandle + AND m.impact = m2.impact + AND m.database_name = m2.database_name + AND m.schema_name = m2.schema_name + AND m.table_name = m2.table_name + FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 2, N'') AS inequality + , STUFF(( SELECT DISTINCT N', ' + ISNULL(m2.column_name, '') AS column_name + FROM #missing_index_detail AS m2 + WHERE m2.usage = 'INCLUDE' + AND m.QueryHash = m2.QueryHash + AND m.SqlHandle = m2.SqlHandle + AND m.impact = m2.impact + AND m.database_name = m2.database_name + AND m.schema_name = m2.schema_name + AND m.table_name = m2.table_name + FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 2, N'') AS [include], + bbcp.ExecutionCount, + bbcp.QueryPlanCost, + bbcp.PlanCreationTimeHours, + 0 as is_spool + FROM #missing_index_detail AS m + JOIN ##BlitzCacheProcs AS bbcp + ON m.SqlHandle = bbcp.SqlHandle + AND m.QueryHash = bbcp.QueryHash + OPTION (RECOMPILE); + + RAISERROR(N'Inserting to #index_spool_ugly', 0, 1) WITH NOWAIT; + WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) + INSERT #index_spool_ugly + (QueryHash, SqlHandle, impact, database_name, schema_name, table_name, equality, inequality, include, executions, query_cost, creation_hours) + SELECT p.QueryHash, + p.SqlHandle, + (c.n.value('@EstimateIO', 'FLOAT') + (c.n.value('@EstimateCPU', 'FLOAT'))) + / ( 1 * NULLIF(p.QueryPlanCost, 0)) * 100 AS impact, + o.n.value('@Database', 'NVARCHAR(128)') AS output_database, + o.n.value('@Schema', 'NVARCHAR(128)') AS output_schema, + o.n.value('@Table', 'NVARCHAR(128)') AS output_table, + k.n.value('@Column', 'NVARCHAR(128)') AS range_column, + e.n.value('@Column', 'NVARCHAR(128)') AS expression_column, + o.n.value('@Column', 'NVARCHAR(128)') AS output_column, + p.ExecutionCount, + p.QueryPlanCost, + p.PlanCreationTimeHours + FROM #relop AS r + JOIN ##BlitzCacheProcs p + ON p.QueryHash = r.QueryHash + CROSS APPLY r.relop.nodes('/p:RelOp') AS c(n) + CROSS APPLY r.relop.nodes('/p:RelOp/p:OutputList/p:ColumnReference') AS o(n) + OUTER APPLY r.relop.nodes('/p:RelOp/p:Spool/p:SeekPredicateNew/p:SeekKeys/p:Prefix/p:RangeColumns/p:ColumnReference') AS k(n) + OUTER APPLY r.relop.nodes('/p:RelOp/p:Spool/p:SeekPredicateNew/p:SeekKeys/p:Prefix/p:RangeExpressions/p:ColumnReference') AS e(n) + WHERE r.relop.exist('/p:RelOp[@PhysicalOp="Index Spool" and @LogicalOp="Eager Spool"]') = 1 -CREATE TABLE #variable_info -( - SPID INT, - QueryHash BINARY(8), - SqlHandle VARBINARY(64), - proc_name NVARCHAR(1000), - variable_name NVARCHAR(258), - variable_datatype NVARCHAR(258), - compile_time_value NVARCHAR(258) -); + RAISERROR(N'Inserting to spools to #missing_index_pretty', 0, 1) WITH NOWAIT; + INSERT #missing_index_pretty + (QueryHash, SqlHandle, impact, database_name, schema_name, table_name, equality, inequality, include, executions, query_cost, creation_hours, is_spool) + SELECT DISTINCT + isu.QueryHash, + isu.SqlHandle, + isu.impact, + isu.database_name, + isu.schema_name, + isu.table_name + , STUFF(( SELECT DISTINCT N', ' + ISNULL(isu2.equality, '') AS column_name + FROM #index_spool_ugly AS isu2 + WHERE isu2.equality IS NOT NULL + AND isu.QueryHash = isu2.QueryHash + AND isu.SqlHandle = isu2.SqlHandle + AND isu.impact = isu2.impact + AND isu.database_name = isu2.database_name + AND isu.schema_name = isu2.schema_name + AND isu.table_name = isu2.table_name + FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 2, N'') AS equality + , STUFF(( SELECT DISTINCT N', ' + ISNULL(isu2.inequality, '') AS column_name + FROM #index_spool_ugly AS isu2 + WHERE isu2.inequality IS NOT NULL + AND isu.QueryHash = isu2.QueryHash + AND isu.SqlHandle = isu2.SqlHandle + AND isu.impact = isu2.impact + AND isu.database_name = isu2.database_name + AND isu.schema_name = isu2.schema_name + AND isu.table_name = isu2.table_name + FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 2, N'') AS inequality + , STUFF(( SELECT DISTINCT N', ' + ISNULL(isu2.include, '') AS column_name + FROM #index_spool_ugly AS isu2 + WHERE isu2.include IS NOT NULL + AND isu.QueryHash = isu2.QueryHash + AND isu.SqlHandle = isu2.SqlHandle + AND isu.impact = isu2.impact + AND isu.database_name = isu2.database_name + AND isu.schema_name = isu2.schema_name + AND isu.table_name = isu2.table_name + FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 2, N'') AS include, + isu.executions, + isu.query_cost, + isu.creation_hours, + 1 AS is_spool + FROM #index_spool_ugly AS isu -CREATE TABLE #conversion_info -( - SPID INT, - QueryHash BINARY(8), - SqlHandle VARBINARY(64), - proc_name NVARCHAR(258), - expression NVARCHAR(4000), - at_charindex AS CHARINDEX('@', expression), - bracket_charindex AS CHARINDEX(']', expression, CHARINDEX('@', expression)) - CHARINDEX('@', expression), - comma_charindex AS CHARINDEX(',', expression) + 1, - second_comma_charindex AS - CHARINDEX(',', expression, CHARINDEX(',', expression) + 1) - CHARINDEX(',', expression) - 1, - equal_charindex AS CHARINDEX('=', expression) + 1, - paren_charindex AS CHARINDEX('(', expression) + 1, - comma_paren_charindex AS - CHARINDEX(',', expression, CHARINDEX('(', expression) + 1) - CHARINDEX('(', expression) - 1, - convert_implicit_charindex AS CHARINDEX('=CONVERT_IMPLICIT', expression) -); + RAISERROR(N'Updating missing index information', 0, 1) WITH NOWAIT; + WITH missing AS ( + SELECT DISTINCT + mip.QueryHash, + mip.SqlHandle, + mip.executions, + N'' + AS full_details + FROM #missing_index_pretty AS mip + ) + UPDATE bbcp + SET bbcp.missing_indexes = m.full_details + FROM ##BlitzCacheProcs AS bbcp + JOIN missing AS m + ON m.SqlHandle = bbcp.SqlHandle + AND m.QueryHash = bbcp.QueryHash + AND m.executions = bbcp.ExecutionCount + AND SPID = @@SPID + OPTION (RECOMPILE); -CREATE TABLE #missing_index_xml -( - QueryHash BINARY(8), - SqlHandle VARBINARY(64), - impact FLOAT, - index_xml XML -); + END; + RAISERROR(N'Filling in missing index blanks', 0, 1) WITH NOWAIT; + UPDATE b + SET b.missing_indexes = + CASE WHEN b.missing_indexes IS NULL + THEN '' + ELSE b.missing_indexes + END + FROM ##BlitzCacheProcs AS b + WHERE b.SPID = @@SPID + OPTION (RECOMPILE); -CREATE TABLE #missing_index_schema -( - QueryHash BINARY(8), - SqlHandle VARBINARY(64), - impact FLOAT, - database_name NVARCHAR(128), - schema_name NVARCHAR(128), - table_name NVARCHAR(128), - index_xml XML -); +/*End Missing Index*/ -CREATE TABLE #missing_index_usage -( - QueryHash BINARY(8), - SqlHandle VARBINARY(64), - impact FLOAT, - database_name NVARCHAR(128), - schema_name NVARCHAR(128), - table_name NVARCHAR(128), - usage NVARCHAR(128), - index_xml XML -); +/* Set configuration values */ +RAISERROR(N'Setting configuration values', 0, 1) WITH NOWAIT; +DECLARE @execution_threshold INT = 1000 , + @parameter_sniffing_warning_pct TINYINT = 30, + /* This is in average reads */ + @parameter_sniffing_io_threshold BIGINT = 100000 , + @ctp_threshold_pct TINYINT = 10, + @long_running_query_warning_seconds BIGINT = 300 * 1000 , + @memory_grant_warning_percent INT = 10; +IF EXISTS (SELECT 1/0 FROM #configuration WHERE 'frequent execution threshold' = LOWER(parameter_name)) +BEGIN + SELECT @execution_threshold = CAST(value AS INT) + FROM #configuration + WHERE 'frequent execution threshold' = LOWER(parameter_name) ; -CREATE TABLE #missing_index_detail -( - QueryHash BINARY(8), - SqlHandle VARBINARY(64), - impact FLOAT, - database_name NVARCHAR(128), - schema_name NVARCHAR(128), - table_name NVARCHAR(128), - usage NVARCHAR(128), - column_name NVARCHAR(128) -); + SET @msg = ' Setting "frequent execution threshold" to ' + CAST(@execution_threshold AS VARCHAR(10)) ; + RAISERROR(@msg, 0, 1) WITH NOWAIT; +END; -CREATE TABLE #missing_index_pretty -( - QueryHash BINARY(8), - SqlHandle VARBINARY(64), - impact FLOAT, - database_name NVARCHAR(128), - schema_name NVARCHAR(128), - table_name NVARCHAR(128), - equality NVARCHAR(MAX), - inequality NVARCHAR(MAX), - [include] NVARCHAR(MAX), - executions NVARCHAR(128), - query_cost NVARCHAR(128), - creation_hours NVARCHAR(128), - is_spool BIT, - details AS N'/* ' - + CHAR(10) - + CASE is_spool - WHEN 0 - THEN N'The Query Processor estimates that implementing the ' - ELSE N'We estimate that implementing the ' - END - + N'following index could improve query cost (' + query_cost + N')' - + CHAR(10) - + N'by ' - + CONVERT(NVARCHAR(30), impact) - + N'% for ' + executions + N' executions of the query' - + N' over the last ' + - CASE WHEN creation_hours < 24 - THEN creation_hours + N' hours.' - WHEN creation_hours = 24 - THEN ' 1 day.' - WHEN creation_hours > 24 - THEN (CONVERT(NVARCHAR(128), creation_hours / 24)) + N' days.' - ELSE N'' - END - + CHAR(10) - + N'*/' - + CHAR(10) + CHAR(13) - + N'/* ' - + CHAR(10) - + N'USE ' - + database_name - + CHAR(10) - + N'GO' - + CHAR(10) + CHAR(13) - + N'CREATE NONCLUSTERED INDEX ix_' - + ISNULL(REPLACE(REPLACE(REPLACE(equality,'[', ''), ']', ''), ', ', '_'), '') - + ISNULL(REPLACE(REPLACE(REPLACE(inequality,'[', ''), ']', ''), ', ', '_'), '') - + CASE WHEN [include] IS NOT NULL THEN + N'_Includes' ELSE N'' END - + CHAR(10) - + N' ON ' - + schema_name - + N'.' - + table_name - + N' (' + - + CASE WHEN equality IS NOT NULL - THEN equality - + CASE WHEN inequality IS NOT NULL - THEN N', ' + inequality - ELSE N'' - END - ELSE inequality - END - + N')' - + CHAR(10) - + CASE WHEN include IS NOT NULL - THEN N'INCLUDE (' + include + N') WITH (FILLFACTOR=100, ONLINE=?, SORT_IN_TEMPDB=?, DATA_COMPRESSION=?);' - ELSE N' WITH (FILLFACTOR=100, ONLINE=?, SORT_IN_TEMPDB=?, DATA_COMPRESSION=?);' - END - + CHAR(10) - + N'GO' - + CHAR(10) - + N'*/' -); +IF EXISTS (SELECT 1/0 FROM #configuration WHERE 'parameter sniffing variance percent' = LOWER(parameter_name)) +BEGIN + SELECT @parameter_sniffing_warning_pct = CAST(value AS TINYINT) + FROM #configuration + WHERE 'parameter sniffing variance percent' = LOWER(parameter_name) ; + SET @msg = ' Setting "parameter sniffing variance percent" to ' + CAST(@parameter_sniffing_warning_pct AS VARCHAR(3)) ; -CREATE TABLE #index_spool_ugly -( - QueryHash BINARY(8), - SqlHandle VARBINARY(64), - impact FLOAT, - database_name NVARCHAR(128), - schema_name NVARCHAR(128), - table_name NVARCHAR(128), - equality NVARCHAR(MAX), - inequality NVARCHAR(MAX), - [include] NVARCHAR(MAX), - executions NVARCHAR(128), - query_cost NVARCHAR(128), - creation_hours NVARCHAR(128) -); + RAISERROR(@msg, 0, 1) WITH NOWAIT; +END; +IF EXISTS (SELECT 1/0 FROM #configuration WHERE 'parameter sniffing io threshold' = LOWER(parameter_name)) +BEGIN + SELECT @parameter_sniffing_io_threshold = CAST(value AS BIGINT) + FROM #configuration + WHERE 'parameter sniffing io threshold' = LOWER(parameter_name) ; -CREATE TABLE #ReadableDBs -( -database_id INT -); + SET @msg = ' Setting "parameter sniffing io threshold" to ' + CAST(@parameter_sniffing_io_threshold AS VARCHAR(10)); + RAISERROR(@msg, 0, 1) WITH NOWAIT; +END; -CREATE TABLE #plan_usage -( - duplicate_plan_hashes BIGINT NULL, - percent_duplicate DECIMAL(9, 2) NULL, - single_use_plan_count BIGINT NULL, - percent_single DECIMAL(9, 2) NULL, - total_plans BIGINT NULL, - spid INT -); +IF EXISTS (SELECT 1/0 FROM #configuration WHERE 'cost threshold for parallelism warning' = LOWER(parameter_name)) +BEGIN + SELECT @ctp_threshold_pct = CAST(value AS TINYINT) + FROM #configuration + WHERE 'cost threshold for parallelism warning' = LOWER(parameter_name) ; + SET @msg = ' Setting "cost threshold for parallelism warning" to ' + CAST(@ctp_threshold_pct AS VARCHAR(3)); -IF EXISTS (SELECT * FROM sys.all_objects o WHERE o.name = 'dm_hadr_database_replica_states') + RAISERROR(@msg, 0, 1) WITH NOWAIT; +END; + +IF EXISTS (SELECT 1/0 FROM #configuration WHERE 'long running query warning (seconds)' = LOWER(parameter_name)) BEGIN - RAISERROR('Checking for Read intent databases to exclude',0,0) WITH NOWAIT; + SELECT @long_running_query_warning_seconds = CAST(value * 1000 AS BIGINT) + FROM #configuration + WHERE 'long running query warning (seconds)' = LOWER(parameter_name) ; - EXEC('INSERT INTO #ReadableDBs (database_id) SELECT DBs.database_id FROM sys.databases DBs INNER JOIN sys.availability_replicas Replicas ON DBs.replica_id = Replicas.replica_id WHERE replica_server_name NOT IN (SELECT DISTINCT primary_replica FROM sys.dm_hadr_availability_group_states States) AND Replicas.secondary_role_allow_connections_desc = ''READ_ONLY'' AND replica_server_name = @@SERVERNAME OPTION (RECOMPILE);'); - EXEC('INSERT INTO #ReadableDBs VALUES (32767) ;'); -- Exclude internal resource database as well -END + SET @msg = ' Setting "long running query warning (seconds)" to ' + CAST(@long_running_query_warning_seconds AS VARCHAR(10)); -RAISERROR(N'Checking plan cache age', 0, 1) WITH NOWAIT; -WITH x AS ( -SELECT SUM(CASE WHEN DATEDIFF(HOUR, deqs.creation_time, SYSDATETIME()) <= 24 THEN 1 ELSE 0 END) AS [plans_24], - SUM(CASE WHEN DATEDIFF(HOUR, deqs.creation_time, SYSDATETIME()) <= 4 THEN 1 ELSE 0 END) AS [plans_4], - SUM(CASE WHEN DATEDIFF(HOUR, deqs.creation_time, SYSDATETIME()) <= 1 THEN 1 ELSE 0 END) AS [plans_1], - COUNT(deqs.creation_time) AS [total_plans] -FROM sys.dm_exec_query_stats AS deqs -) -INSERT INTO #plan_creation ( percent_24, percent_4, percent_1, total_plans, SPID ) -SELECT CONVERT(DECIMAL(5,2), NULLIF(x.plans_24, 0) / (1. * NULLIF(x.total_plans, 0))) * 100 AS [percent_24], - CONVERT(DECIMAL(5,2), NULLIF(x.plans_4 , 0) / (1. * NULLIF(x.total_plans, 0))) * 100 AS [percent_4], - CONVERT(DECIMAL(5,2), NULLIF(x.plans_1 , 0) / (1. * NULLIF(x.total_plans, 0))) * 100 AS [percent_1], - x.total_plans, - @@SPID AS SPID -FROM x + RAISERROR(@msg, 0, 1) WITH NOWAIT; +END; + +IF EXISTS (SELECT 1/0 FROM #configuration WHERE 'unused memory grant' = LOWER(parameter_name)) +BEGIN + SELECT @memory_grant_warning_percent = CAST(value AS INT) + FROM #configuration + WHERE 'unused memory grant' = LOWER(parameter_name) ; + + SET @msg = ' Setting "unused memory grant" to ' + CAST(@memory_grant_warning_percent AS VARCHAR(10)); + + RAISERROR(@msg, 0, 1) WITH NOWAIT; +END; + +DECLARE @ctp INT ; + +SELECT @ctp = NULLIF(CAST(value AS INT), 0) +FROM sys.configurations +WHERE name = 'cost threshold for parallelism' OPTION (RECOMPILE); -RAISERROR(N'Checking for single use plans and plans with many queries', 0, 1) WITH NOWAIT; -WITH total_plans AS -( - SELECT - COUNT_BIG(deqs.query_plan_hash) AS total_plans - FROM sys.dm_exec_query_stats AS deqs -), - many_plans AS -( - SELECT - SUM(x.duplicate_plan_hashes) AS duplicate_plan_hashes - FROM - ( - SELECT - COUNT_BIG(qs.query_plan_hash) AS duplicate_plan_hashes - FROM sys.dm_exec_query_stats qs - LEFT JOIN sys.dm_exec_procedure_stats ps ON qs.plan_handle = ps.plan_handle - CROSS APPLY sys.dm_exec_plan_attributes(qs.plan_handle) pa - WHERE pa.attribute = N'dbid' - AND pa.value <> 32767 /*Omit Resource database-based queries, we're not going to "fix" them no matter what. Addresses #3314*/ - AND qs.query_plan_hash <> 0x0000000000000000 - GROUP BY - /* qs.query_plan_hash, BGO 20210524 commenting this out to fix #2909 */ - qs.query_hash, - ps.object_id, - pa.value - HAVING COUNT_BIG(qs.query_plan_hash) > 5 - ) AS x -), - single_use_plans AS -( - SELECT - COUNT_BIG(*) AS single_use_plan_count - FROM sys.dm_exec_query_stats AS s - WHERE s.execution_count = 1 -) -INSERT - #plan_usage -( - duplicate_plan_hashes, - percent_duplicate, - single_use_plan_count, - percent_single, - total_plans, - spid -) -SELECT - m.duplicate_plan_hashes, - CONVERT - ( - decimal(5,2), - m.duplicate_plan_hashes - / (1. * NULLIF(t.total_plans, 0)) - ) * 100. AS percent_duplicate, - s.single_use_plan_count, - CONVERT - ( - decimal(5,2), - s.single_use_plan_count - / (1. * NULLIF(t.total_plans, 0)) - ) * 100. AS percent_single, - t.total_plans, - @@SPID -FROM many_plans AS m -CROSS JOIN single_use_plans AS s -CROSS JOIN total_plans AS t; - - -/* -Erik Darling: - Quoting this out to see if the above query fixes the issue - 2021-05-17, Issue #2909 - -UPDATE #plan_usage - SET percent_duplicate = CASE WHEN percent_duplicate > 100 THEN 100 ELSE percent_duplicate END, - percent_single = CASE WHEN percent_duplicate > 100 THEN 100 ELSE percent_duplicate END; -*/ - -SET @OnlySqlHandles = LTRIM(RTRIM(@OnlySqlHandles)) ; -SET @OnlyQueryHashes = LTRIM(RTRIM(@OnlyQueryHashes)) ; -SET @IgnoreQueryHashes = LTRIM(RTRIM(@IgnoreQueryHashes)) ; +/* Update to populate checks columns */ +RAISERROR('Checking for query level SQL Server issues.', 0, 1) WITH NOWAIT; -DECLARE @individual VARCHAR(100) ; +WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) +UPDATE ##BlitzCacheProcs +SET frequent_execution = CASE WHEN ExecutionsPerMinute > @execution_threshold THEN 1 END , + parameter_sniffing = CASE WHEN ExecutionCount > 3 AND AverageReads > @parameter_sniffing_io_threshold + AND min_worker_time < ((1.0 - (@parameter_sniffing_warning_pct / 100.0)) * AverageCPU) THEN 1 + WHEN ExecutionCount > 3 AND AverageReads > @parameter_sniffing_io_threshold + AND max_worker_time > ((1.0 + (@parameter_sniffing_warning_pct / 100.0)) * AverageCPU) THEN 1 + WHEN ExecutionCount > 3 AND AverageReads > @parameter_sniffing_io_threshold + AND MinReturnedRows < ((1.0 - (@parameter_sniffing_warning_pct / 100.0)) * AverageReturnedRows) THEN 1 + WHEN ExecutionCount > 3 AND AverageReads > @parameter_sniffing_io_threshold + AND MaxReturnedRows > ((1.0 + (@parameter_sniffing_warning_pct / 100.0)) * AverageReturnedRows) THEN 1 END , + near_parallel = CASE WHEN is_parallel <> 1 AND QueryPlanCost BETWEEN @ctp * (1 - (@ctp_threshold_pct / 100.0)) AND @ctp THEN 1 END, + long_running = CASE WHEN AverageDuration > @long_running_query_warning_seconds THEN 1 + WHEN max_worker_time > @long_running_query_warning_seconds THEN 1 + WHEN max_elapsed_time > @long_running_query_warning_seconds THEN 1 END, + is_key_lookup_expensive = CASE WHEN QueryPlanCost >= (@ctp / 2) AND key_lookup_cost >= QueryPlanCost * .5 THEN 1 END, + is_sort_expensive = CASE WHEN QueryPlanCost >= (@ctp / 2) AND sort_cost >= QueryPlanCost * .5 THEN 1 END, + is_remote_query_expensive = CASE WHEN remote_query_cost >= QueryPlanCost * .05 THEN 1 END, + is_unused_grant = CASE WHEN PercentMemoryGrantUsed <= @memory_grant_warning_percent AND MinGrantKB > @MinMemoryPerQuery THEN 1 END, + long_running_low_cpu = CASE WHEN AverageDuration > AverageCPU * 4 AND AverageCPU < 500. THEN 1 END, + low_cost_high_cpu = CASE WHEN QueryPlanCost <= 10 AND AverageCPU > 5000. THEN 1 END, + is_spool_expensive = CASE WHEN QueryPlanCost > (@ctp / 5) AND index_spool_cost >= QueryPlanCost * .1 THEN 1 END, + is_spool_more_rows = CASE WHEN index_spool_rows >= (AverageReturnedRows / ISNULL(NULLIF(ExecutionCount, 0), 1)) THEN 1 END, + is_table_spool_expensive = CASE WHEN QueryPlanCost > (@ctp / 5) AND table_spool_cost >= QueryPlanCost / 4 THEN 1 END, + is_table_spool_more_rows = CASE WHEN table_spool_rows >= (AverageReturnedRows / ISNULL(NULLIF(ExecutionCount, 0), 1)) THEN 1 END, + is_bad_estimate = CASE WHEN AverageReturnedRows > 0 AND (estimated_rows * 1000 < AverageReturnedRows OR estimated_rows > AverageReturnedRows * 1000) THEN 1 END, + is_big_spills = CASE WHEN (AvgSpills / 128.) > 499. THEN 1 END +WHERE SPID = @@SPID +OPTION (RECOMPILE); -IF (@OnlySqlHandles IS NOT NULL AND @IgnoreSqlHandles IS NOT NULL) -BEGIN -RAISERROR('You shouldn''t need to ignore and filter on SqlHandle at the same time.', 0, 1) WITH NOWAIT; -RETURN; -END; -IF (@StoredProcName IS NOT NULL AND (@OnlySqlHandles IS NOT NULL OR @IgnoreSqlHandles IS NOT NULL)) -BEGIN -RAISERROR('You can''t filter on stored procedure name and SQL Handle.', 0, 1) WITH NOWAIT; -RETURN; -END; -IF @OnlySqlHandles IS NOT NULL - AND LEN(@OnlySqlHandles) > 0 -BEGIN - RAISERROR(N'Processing SQL Handles', 0, 1) WITH NOWAIT; - SET @individual = ''; +RAISERROR('Checking for forced parameterization and cursors.', 0, 1) WITH NOWAIT; - WHILE LEN(@OnlySqlHandles) > 0 - BEGIN - IF PATINDEX('%,%', @OnlySqlHandles) > 0 - BEGIN - SET @individual = SUBSTRING(@OnlySqlHandles, 0, PATINDEX('%,%',@OnlySqlHandles)) ; - - INSERT INTO #only_sql_handles - SELECT CAST('' AS XML).value('xs:hexBinary( substring(sql:variable("@individual"), sql:column("t.pos")) )', 'varbinary(max)') - FROM (SELECT CASE SUBSTRING(@individual, 1, 2) WHEN '0x' THEN 3 ELSE 0 END) AS t(pos) - OPTION (RECOMPILE) ; - - --SELECT CAST(SUBSTRING(@individual, 1, 2) AS BINARY(8)); +/* Set options checks */ +UPDATE p + SET is_forced_parameterized = CASE WHEN (CAST(pa.value AS INT) & 131072 = 131072) THEN 1 END , + is_forced_plan = CASE WHEN (CAST(pa.value AS INT) & 4 = 4) THEN 1 END , + SetOptions = SUBSTRING( + CASE WHEN (CAST(pa.value AS INT) & 1 = 1) THEN ', ANSI_PADDING' ELSE '' END + + CASE WHEN (CAST(pa.value AS INT) & 8 = 8) THEN ', CONCAT_NULL_YIELDS_NULL' ELSE '' END + + CASE WHEN (CAST(pa.value AS INT) & 16 = 16) THEN ', ANSI_WARNINGS' ELSE '' END + + CASE WHEN (CAST(pa.value AS INT) & 32 = 32) THEN ', ANSI_NULLS' ELSE '' END + + CASE WHEN (CAST(pa.value AS INT) & 64 = 64) THEN ', QUOTED_IDENTIFIER' ELSE '' END + + CASE WHEN (CAST(pa.value AS INT) & 4096 = 4096) THEN ', ARITH_ABORT' ELSE '' END + + CASE WHEN (CAST(pa.value AS INT) & 8192 = 8191) THEN ', NUMERIC_ROUNDABORT' ELSE '' END + , 2, 200000) +FROM ##BlitzCacheProcs p + CROSS APPLY sys.dm_exec_plan_attributes(p.PlanHandle) pa +WHERE pa.attribute = 'set_options' +AND SPID = @@SPID +OPTION (RECOMPILE); - SET @OnlySqlHandles = SUBSTRING(@OnlySqlHandles, LEN(@individual + ',') + 1, LEN(@OnlySqlHandles)) ; - END; - ELSE - BEGIN - SET @individual = @OnlySqlHandles; - SET @OnlySqlHandles = NULL; - INSERT INTO #only_sql_handles - SELECT CAST('' AS XML).value('xs:hexBinary( substring(sql:variable("@individual"), sql:column("t.pos")) )', 'varbinary(max)') - FROM (SELECT CASE SUBSTRING(@individual, 1, 2) WHEN '0x' THEN 3 ELSE 0 END) AS t(pos) - OPTION (RECOMPILE) ; +/* Cursor checks */ +UPDATE p +SET is_cursor = CASE WHEN CAST(pa.value AS INT) <> 0 THEN 1 END +FROM ##BlitzCacheProcs p + CROSS APPLY sys.dm_exec_plan_attributes(p.PlanHandle) pa +WHERE pa.attribute LIKE '%cursor%' +AND SPID = @@SPID +OPTION (RECOMPILE); - --SELECT CAST(SUBSTRING(@individual, 1, 2) AS VARBINARY(MAX)) ; - END; - END; -END; +UPDATE p +SET is_cursor = 1 +FROM ##BlitzCacheProcs p +WHERE QueryHash = 0x0000000000000000 +OR QueryPlanHash = 0x0000000000000000 +AND SPID = @@SPID +OPTION (RECOMPILE); -IF @IgnoreSqlHandles IS NOT NULL - AND LEN(@IgnoreSqlHandles) > 0 -BEGIN - RAISERROR(N'Processing SQL Handles To Ignore', 0, 1) WITH NOWAIT; - SET @individual = ''; - WHILE LEN(@IgnoreSqlHandles) > 0 - BEGIN - IF PATINDEX('%,%', @IgnoreSqlHandles) > 0 - BEGIN - SET @individual = SUBSTRING(@IgnoreSqlHandles, 0, PATINDEX('%,%',@IgnoreSqlHandles)) ; - - INSERT INTO #ignore_sql_handles - SELECT CAST('' AS XML).value('xs:hexBinary( substring(sql:variable("@individual"), sql:column("t.pos")) )', 'varbinary(max)') - FROM (SELECT CASE SUBSTRING(@individual, 1, 2) WHEN '0x' THEN 3 ELSE 0 END) AS t(pos) - OPTION (RECOMPILE) ; - - --SELECT CAST(SUBSTRING(@individual, 1, 2) AS BINARY(8)); - SET @IgnoreSqlHandles = SUBSTRING(@IgnoreSqlHandles, LEN(@individual + ',') + 1, LEN(@IgnoreSqlHandles)) ; - END; - ELSE - BEGIN - SET @individual = @IgnoreSqlHandles; - SET @IgnoreSqlHandles = NULL; +RAISERROR('Populating Warnings column', 0, 1) WITH NOWAIT; +/* Populate warnings */ +UPDATE ##BlitzCacheProcs +SET Warnings = SUBSTRING( + CASE WHEN warning_no_join_predicate = 1 THEN ', No Join Predicate' ELSE '' END + + CASE WHEN compile_timeout = 1 THEN ', Compilation Timeout' ELSE '' END + + CASE WHEN compile_memory_limit_exceeded = 1 THEN ', Compile Memory Limit Exceeded' ELSE '' END + + CASE WHEN busy_loops = 1 THEN ', Busy Loops' ELSE '' END + + CASE WHEN is_forced_plan = 1 THEN ', Forced Plan' ELSE '' END + + CASE WHEN is_forced_parameterized = 1 THEN ', Forced Parameterization' ELSE '' END + + CASE WHEN unparameterized_query = 1 THEN ', Unparameterized Query' ELSE '' END + + CASE WHEN missing_index_count > 0 THEN ', Missing Indexes (' + CAST(missing_index_count AS VARCHAR(3)) + ')' ELSE '' END + + CASE WHEN unmatched_index_count > 0 THEN ', Unmatched Indexes (' + CAST(unmatched_index_count AS VARCHAR(3)) + ')' ELSE '' END + + CASE WHEN is_cursor = 1 THEN ', Cursor' + + CASE WHEN is_optimistic_cursor = 1 THEN '; optimistic' ELSE '' END + + CASE WHEN is_forward_only_cursor = 0 THEN '; not forward only' ELSE '' END + + CASE WHEN is_cursor_dynamic = 1 THEN '; dynamic' ELSE '' END + + CASE WHEN is_fast_forward_cursor = 1 THEN '; fast forward' ELSE '' END + ELSE '' END + + CASE WHEN is_parallel = 1 THEN ', Parallel' ELSE '' END + + CASE WHEN near_parallel = 1 THEN ', Nearly Parallel' ELSE '' END + + CASE WHEN frequent_execution = 1 THEN ', Frequent Execution' ELSE '' END + + CASE WHEN plan_warnings = 1 THEN ', Plan Warnings' ELSE '' END + + CASE WHEN parameter_sniffing = 1 THEN ', Parameter Sniffing' ELSE '' END + + CASE WHEN long_running = 1 THEN ', Long Running Query' ELSE '' END + + CASE WHEN downlevel_estimator = 1 THEN ', Downlevel CE' ELSE '' END + + CASE WHEN implicit_conversions = 1 THEN ', Implicit Conversions' ELSE '' END + + CASE WHEN tvf_join = 1 THEN ', Function Join' ELSE '' END + + CASE WHEN plan_multiple_plans > 0 THEN ', Multiple Plans' + COALESCE(' (' + CAST(plan_multiple_plans AS VARCHAR(10)) + ')', '') ELSE '' END + + CASE WHEN is_trivial = 1 THEN ', Trivial Plans' ELSE '' END + + CASE WHEN is_forced_serial = 1 THEN ', Forced Serialization' ELSE '' END + + CASE WHEN is_key_lookup_expensive = 1 THEN ', Expensive Key Lookup' ELSE '' END + + CASE WHEN is_remote_query_expensive = 1 THEN ', Expensive Remote Query' ELSE '' END + + CASE WHEN trace_flags_session IS NOT NULL THEN ', Session Level Trace Flag(s) Enabled: ' + trace_flags_session ELSE '' END + + CASE WHEN is_unused_grant = 1 THEN ', Unused Memory Grant' ELSE '' END + + CASE WHEN function_count > 0 THEN ', Calls ' + CONVERT(VARCHAR(10), function_count) + ' Function(s)' ELSE '' END + + CASE WHEN clr_function_count > 0 THEN ', Calls ' + CONVERT(VARCHAR(10), clr_function_count) + ' CLR Function(s)' ELSE '' END + + CASE WHEN PlanCreationTimeHours <= 4 THEN ', Plan created last 4hrs' ELSE '' END + + CASE WHEN is_table_variable = 1 THEN ', Table Variables' ELSE '' END + + CASE WHEN no_stats_warning = 1 THEN ', Columns With No Statistics' ELSE '' END + + CASE WHEN relop_warnings = 1 THEN ', Operator Warnings' ELSE '' END + + CASE WHEN is_table_scan = 1 THEN ', Table Scans (Heaps)' ELSE '' END + + CASE WHEN backwards_scan = 1 THEN ', Backwards Scans' ELSE '' END + + CASE WHEN forced_index = 1 THEN ', Forced Indexes' ELSE '' END + + CASE WHEN forced_seek = 1 THEN ', Forced Seeks' ELSE '' END + + CASE WHEN forced_scan = 1 THEN ', Forced Scans' ELSE '' END + + CASE WHEN columnstore_row_mode = 1 THEN ', ColumnStore Row Mode ' ELSE '' END + + CASE WHEN is_computed_scalar = 1 THEN ', Computed Column UDF ' ELSE '' END + + CASE WHEN is_sort_expensive = 1 THEN ', Expensive Sort' ELSE '' END + + CASE WHEN is_computed_filter = 1 THEN ', Filter UDF' ELSE '' END + + CASE WHEN index_ops >= 5 THEN ', >= 5 Indexes Modified' ELSE '' END + + CASE WHEN is_row_level = 1 THEN ', Row Level Security' ELSE '' END + + CASE WHEN is_spatial = 1 THEN ', Spatial Index' ELSE '' END + + CASE WHEN index_dml = 1 THEN ', Index DML' ELSE '' END + + CASE WHEN table_dml = 1 THEN ', Table DML' ELSE '' END + + CASE WHEN low_cost_high_cpu = 1 THEN ', Low Cost High CPU' ELSE '' END + + CASE WHEN long_running_low_cpu = 1 THEN + ', Long Running With Low CPU' ELSE '' END + + CASE WHEN stale_stats = 1 THEN + ', Statistics used have > 100k modifications in the last 7 days' ELSE '' END + + CASE WHEN is_adaptive = 1 THEN + ', Adaptive Joins' ELSE '' END + + CASE WHEN is_spool_expensive = 1 THEN + ', Expensive Index Spool' ELSE '' END + + CASE WHEN is_spool_more_rows = 1 THEN + ', Large Index Row Spool' ELSE '' END + + CASE WHEN is_table_spool_expensive = 1 THEN + ', Expensive Table Spool' ELSE '' END + + CASE WHEN is_table_spool_more_rows = 1 THEN + ', Many Rows Table Spool' ELSE '' END + + CASE WHEN is_bad_estimate = 1 THEN + ', Row Estimate Mismatch' ELSE '' END + + CASE WHEN is_paul_white_electric = 1 THEN ', SWITCH!' ELSE '' END + + CASE WHEN is_row_goal = 1 THEN ', Row Goals' ELSE '' END + + CASE WHEN is_big_spills = 1 THEN ', >500mb Spills' ELSE '' END + + CASE WHEN is_mstvf = 1 THEN ', MSTVFs' ELSE '' END + + CASE WHEN is_mm_join = 1 THEN ', Many to Many Merge' ELSE '' END + + CASE WHEN is_nonsargable = 1 THEN ', non-SARGables' ELSE '' END + + CASE WHEN CompileTime > 5000 THEN ', Long Compile Time' ELSE '' END + + CASE WHEN CompileCPU > 5000 THEN ', High Compile CPU' ELSE '' END + + CASE WHEN CompileMemory > 1024 AND ((CompileMemory) / (1 * CASE WHEN MaxCompileMemory = 0 THEN 1 ELSE MaxCompileMemory END) * 100.) >= 10. THEN ', High Compile Memory' ELSE '' END + + CASE WHEN select_with_writes > 0 THEN ', Select w/ Writes' ELSE '' END + , 3, 200000) +WHERE SPID = @@SPID +OPTION (RECOMPILE); - INSERT INTO #ignore_sql_handles - SELECT CAST('' AS XML).value('xs:hexBinary( substring(sql:variable("@individual"), sql:column("t.pos")) )', 'varbinary(max)') - FROM (SELECT CASE SUBSTRING(@individual, 1, 2) WHEN '0x' THEN 3 ELSE 0 END) AS t(pos) - OPTION (RECOMPILE) ; - --SELECT CAST(SUBSTRING(@individual, 1, 2) AS VARBINARY(MAX)) ; - END; - END; -END; +RAISERROR('Populating Warnings column for stored procedures', 0, 1) WITH NOWAIT; +WITH statement_warnings AS + ( +SELECT DISTINCT + SqlHandle, + Warnings = SUBSTRING( + CASE WHEN warning_no_join_predicate = 1 THEN ', No Join Predicate' ELSE '' END + + CASE WHEN compile_timeout = 1 THEN ', Compilation Timeout' ELSE '' END + + CASE WHEN compile_memory_limit_exceeded = 1 THEN ', Compile Memory Limit Exceeded' ELSE '' END + + CASE WHEN busy_loops = 1 THEN ', Busy Loops' ELSE '' END + + CASE WHEN is_forced_plan = 1 THEN ', Forced Plan' ELSE '' END + + CASE WHEN is_forced_parameterized = 1 THEN ', Forced Parameterization' ELSE '' END + + --CASE WHEN unparameterized_query = 1 THEN ', Unparameterized Query' ELSE '' END + + CASE WHEN missing_index_count > 0 THEN ', Missing Indexes (' + CONVERT(VARCHAR(10), (SELECT SUM(b2.missing_index_count) FROM ##BlitzCacheProcs AS b2 WHERE b2.SqlHandle = b.SqlHandle AND b2.QueryHash IS NOT NULL AND SPID = @@SPID) ) + ')' ELSE '' END + + CASE WHEN unmatched_index_count > 0 THEN ', Unmatched Indexes (' + CONVERT(VARCHAR(10), (SELECT SUM(b2.unmatched_index_count) FROM ##BlitzCacheProcs AS b2 WHERE b2.SqlHandle = b.SqlHandle AND b2.QueryHash IS NOT NULL AND SPID = @@SPID) ) + ')' ELSE '' END + + CASE WHEN is_cursor = 1 THEN ', Cursor' + + CASE WHEN is_optimistic_cursor = 1 THEN '; optimistic' ELSE '' END + + CASE WHEN is_forward_only_cursor = 0 THEN '; not forward only' ELSE '' END + + CASE WHEN is_cursor_dynamic = 1 THEN '; dynamic' ELSE '' END + + CASE WHEN is_fast_forward_cursor = 1 THEN '; fast forward' ELSE '' END + ELSE '' END + + CASE WHEN is_parallel = 1 THEN ', Parallel' ELSE '' END + + CASE WHEN near_parallel = 1 THEN ', Nearly Parallel' ELSE '' END + + CASE WHEN frequent_execution = 1 THEN ', Frequent Execution' ELSE '' END + + CASE WHEN plan_warnings = 1 THEN ', Plan Warnings' ELSE '' END + + CASE WHEN parameter_sniffing = 1 THEN ', Parameter Sniffing' ELSE '' END + + CASE WHEN long_running = 1 THEN ', Long Running Query' ELSE '' END + + CASE WHEN downlevel_estimator = 1 THEN ', Downlevel CE' ELSE '' END + + CASE WHEN implicit_conversions = 1 THEN ', Implicit Conversions' ELSE '' END + + CASE WHEN tvf_join = 1 THEN ', Function Join' ELSE '' END + + CASE WHEN plan_multiple_plans > 0 THEN ', Multiple Plans' + COALESCE(' (' + CAST(plan_multiple_plans AS VARCHAR(10)) + ')', '') ELSE '' END + + CASE WHEN is_trivial = 1 THEN ', Trivial Plans' ELSE '' END + + CASE WHEN is_forced_serial = 1 THEN ', Forced Serialization' ELSE '' END + + CASE WHEN is_key_lookup_expensive = 1 THEN ', Expensive Key Lookup' ELSE '' END + + CASE WHEN is_remote_query_expensive = 1 THEN ', Expensive Remote Query' ELSE '' END + + CASE WHEN trace_flags_session IS NOT NULL THEN ', Session Level Trace Flag(s) Enabled: ' + trace_flags_session ELSE '' END + + CASE WHEN is_unused_grant = 1 THEN ', Unused Memory Grant' ELSE '' END + + CASE WHEN function_count > 0 THEN ', Calls ' + CONVERT(VARCHAR(10), (SELECT SUM(b2.function_count) FROM ##BlitzCacheProcs AS b2 WHERE b2.SqlHandle = b.SqlHandle AND b2.QueryHash IS NOT NULL AND SPID = @@SPID) ) + ' Function(s)' ELSE '' END + + CASE WHEN clr_function_count > 0 THEN ', Calls ' + CONVERT(VARCHAR(10), (SELECT SUM(b2.clr_function_count) FROM ##BlitzCacheProcs AS b2 WHERE b2.SqlHandle = b.SqlHandle AND b2.QueryHash IS NOT NULL AND SPID = @@SPID) ) + ' CLR Function(s)' ELSE '' END + + CASE WHEN PlanCreationTimeHours <= 4 THEN ', Plan created last 4hrs' ELSE '' END + + CASE WHEN is_table_variable = 1 THEN ', Table Variables' ELSE '' END + + CASE WHEN no_stats_warning = 1 THEN ', Columns With No Statistics' ELSE '' END + + CASE WHEN relop_warnings = 1 THEN ', Operator Warnings' ELSE '' END + + CASE WHEN is_table_scan = 1 THEN ', Table Scans' ELSE '' END + + CASE WHEN backwards_scan = 1 THEN ', Backwards Scans' ELSE '' END + + CASE WHEN forced_index = 1 THEN ', Forced Indexes' ELSE '' END + + CASE WHEN forced_seek = 1 THEN ', Forced Seeks' ELSE '' END + + CASE WHEN forced_scan = 1 THEN ', Forced Scans' ELSE '' END + + CASE WHEN columnstore_row_mode = 1 THEN ', ColumnStore Row Mode ' ELSE '' END + + CASE WHEN is_computed_scalar = 1 THEN ', Computed Column UDF ' ELSE '' END + + CASE WHEN is_sort_expensive = 1 THEN ', Expensive Sort' ELSE '' END + + CASE WHEN is_computed_filter = 1 THEN ', Filter UDF' ELSE '' END + + CASE WHEN index_ops >= 5 THEN ', >= 5 Indexes Modified' ELSE '' END + + CASE WHEN is_row_level = 1 THEN ', Row Level Security' ELSE '' END + + CASE WHEN is_spatial = 1 THEN ', Spatial Index' ELSE '' END + + CASE WHEN index_dml = 1 THEN ', Index DML' ELSE '' END + + CASE WHEN table_dml = 1 THEN ', Table DML' ELSE '' END + + CASE WHEN low_cost_high_cpu = 1 THEN ', Low Cost High CPU' ELSE '' END + + CASE WHEN long_running_low_cpu = 1 THEN + ', Long Running With Low CPU' ELSE '' END + + CASE WHEN stale_stats = 1 THEN + ', Statistics used have > 100k modifications in the last 7 days' ELSE '' END + + CASE WHEN is_adaptive = 1 THEN + ', Adaptive Joins' ELSE '' END + + CASE WHEN is_spool_expensive = 1 THEN + ', Expensive Index Spool' ELSE '' END + + CASE WHEN is_spool_more_rows = 1 THEN + ', Large Index Row Spool' ELSE '' END + + CASE WHEN is_table_spool_expensive = 1 THEN + ', Expensive Table Spool' ELSE '' END + + CASE WHEN is_table_spool_more_rows = 1 THEN + ', Many Rows Table Spool' ELSE '' END + + CASE WHEN is_bad_estimate = 1 THEN + ', Row Estimate Mismatch' ELSE '' END + + CASE WHEN is_paul_white_electric = 1 THEN ', SWITCH!' ELSE '' END + + CASE WHEN is_row_goal = 1 THEN ', Row Goals' ELSE '' END + + CASE WHEN is_big_spills = 1 THEN ', >500mb spills' ELSE '' END + + CASE WHEN is_mstvf = 1 THEN ', MSTVFs' ELSE '' END + + CASE WHEN is_mm_join = 1 THEN ', Many to Many Merge' ELSE '' END + + CASE WHEN is_nonsargable = 1 THEN ', non-SARGables' ELSE '' END + + CASE WHEN CompileTime > 5000 THEN ', Long Compile Time' ELSE '' END + + CASE WHEN CompileCPU > 5000 THEN ', High Compile CPU' ELSE '' END + + CASE WHEN CompileMemory > 1024 AND ((CompileMemory) / (1 * CASE WHEN MaxCompileMemory = 0 THEN 1 ELSE MaxCompileMemory END) * 100.) >= 10. THEN ', High Compile Memory' ELSE '' END + + CASE WHEN select_with_writes > 0 THEN ', Select w/ Writes' ELSE '' END + , 3, 200000) +FROM ##BlitzCacheProcs b +WHERE SPID = @@SPID +AND QueryType LIKE 'Statement (parent%' + ) +UPDATE b +SET b.Warnings = s.Warnings +FROM ##BlitzCacheProcs AS b +JOIN statement_warnings s +ON b.SqlHandle = s.SqlHandle +WHERE QueryType LIKE 'Procedure or Function%' +AND SPID = @@SPID +OPTION (RECOMPILE); -IF @StoredProcName IS NOT NULL AND @StoredProcName <> N'' +RAISERROR('Checking for plans with >128 levels of nesting', 0, 1) WITH NOWAIT; +WITH plan_handle AS ( +SELECT b.PlanHandle +FROM ##BlitzCacheProcs b + CROSS APPLY sys.dm_exec_text_query_plan(b.PlanHandle, 0, -1) tqp + CROSS APPLY sys.dm_exec_query_plan(b.PlanHandle) qp + WHERE tqp.encrypted = 0 + AND b.SPID = @@SPID + AND (qp.query_plan IS NULL + AND tqp.query_plan IS NOT NULL) +) +UPDATE b +SET Warnings = ISNULL('Your query plan is >128 levels of nested nodes, and can''t be converted to XML. Use SELECT * FROM sys.dm_exec_text_query_plan('+ CONVERT(VARCHAR(128), ph.PlanHandle, 1) + ', 0, -1) to get more information' + , 'We couldn''t find a plan for this query. More info on possible reasons: https://www.brentozar.com/go/noplans') +FROM ##BlitzCacheProcs b +LEFT JOIN plan_handle ph ON +b.PlanHandle = ph.PlanHandle +WHERE b.QueryPlan IS NULL +AND b.SPID = @@SPID +OPTION (RECOMPILE); +RAISERROR('Checking for plans with no warnings', 0, 1) WITH NOWAIT; +UPDATE ##BlitzCacheProcs +SET Warnings = 'No warnings detected. ' + CASE @ExpertMode + WHEN 0 + THEN ' Try running sp_BlitzCache with @ExpertMode = 1 to find more advanced problems.' + ELSE '' + END +WHERE Warnings = '' OR Warnings IS NULL +AND SPID = @@SPID +OPTION (RECOMPILE); + + +Results: +IF @ExportToExcel = 1 BEGIN - RAISERROR(N'Setting up filter for stored procedure name', 0, 1) WITH NOWAIT; - - DECLARE @function_search_sql NVARCHAR(MAX) = N'' - - INSERT #only_sql_handles - ( sql_handle ) - SELECT ISNULL(deps.sql_handle, CONVERT(VARBINARY(64),'0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000')) - FROM sys.dm_exec_procedure_stats AS deps - WHERE OBJECT_NAME(deps.object_id, deps.database_id) = @StoredProcName + RAISERROR('Displaying results with Excel formatting (no plans).', 0, 1) WITH NOWAIT; - UNION ALL - - SELECT ISNULL(dets.sql_handle, CONVERT(VARBINARY(64),'0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000')) - FROM sys.dm_exec_trigger_stats AS dets - WHERE OBJECT_NAME(dets.object_id, dets.database_id) = @StoredProcName - OPTION (RECOMPILE); + /* excel output */ + UPDATE ##BlitzCacheProcs + SET QueryText = SUBSTRING(REPLACE(REPLACE(REPLACE(LTRIM(RTRIM(QueryText)),' ','<>'),'><',''),'<>',' '), 1, 32000) + OPTION(RECOMPILE); - IF EXISTS (SELECT 1/0 FROM sys.all_objects AS o WHERE o.name = 'dm_exec_function_stats') - BEGIN - SET @function_search_sql = @function_search_sql + N' - SELECT ISNULL(defs.sql_handle, CONVERT(VARBINARY(64),''0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'')) - FROM sys.dm_exec_function_stats AS defs - WHERE OBJECT_NAME(defs.object_id, defs.database_id) = @i_StoredProcName - OPTION (RECOMPILE); - ' - INSERT #only_sql_handles ( sql_handle ) - EXEC sys.sp_executesql @function_search_sql, N'@i_StoredProcName NVARCHAR(128)', @StoredProcName - END - - IF (SELECT COUNT(*) FROM #only_sql_handles) = 0 - BEGIN - RAISERROR(N'No information for that stored procedure was found.', 0, 1) WITH NOWAIT; - RETURN; - END; + SET @sql = N' + SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + SELECT TOP (@Top) + DatabaseName AS [Database Name], + QueryPlanCost AS [Cost], + QueryText, + QueryType AS [Query Type], + Warnings, + ExecutionCount, + ExecutionsPerMinute AS [Executions / Minute], + PercentExecutions AS [Execution Weight], + PercentExecutionsByType AS [% Executions (Type)], + SerialDesiredMemory AS [Serial Desired Memory], + SerialRequiredMemory AS [Serial Required Memory], + TotalCPU AS [Total CPU (ms)], + AverageCPU AS [Avg CPU (ms)], + PercentCPU AS [CPU Weight], + PercentCPUByType AS [% CPU (Type)], + TotalDuration AS [Total Duration (ms)], + AverageDuration AS [Avg Duration (ms)], + PercentDuration AS [Duration Weight], + PercentDurationByType AS [% Duration (Type)], + TotalReads AS [Total Reads], + AverageReads AS [Average Reads], + PercentReads AS [Read Weight], + PercentReadsByType AS [% Reads (Type)], + TotalWrites AS [Total Writes], + AverageWrites AS [Average Writes], + PercentWrites AS [Write Weight], + PercentWritesByType AS [% Writes (Type)], + TotalReturnedRows, + AverageReturnedRows, + MinReturnedRows, + MaxReturnedRows, + MinGrantKB, + MaxGrantKB, + MinUsedGrantKB, + MaxUsedGrantKB, + PercentMemoryGrantUsed, + AvgMaxMemoryGrant, + MinSpills, + MaxSpills, + TotalSpills, + AvgSpills, + NumberOfPlans, + NumberOfDistinctPlans, + PlanCreationTime AS [Created At], + LastExecutionTime AS [Last Execution], + StatementStartOffset, + StatementEndOffset, + PlanGenerationNum, + PlanHandle AS [Plan Handle], + SqlHandle AS [SQL Handle], + QueryHash, + QueryPlanHash, + COALESCE(SetOptions, '''') AS [SET Options] + FROM ##BlitzCacheProcs + WHERE 1 = 1 + AND SPID = @@SPID ' + @nl; -END; + IF @MinimumExecutionCount IS NOT NULL + BEGIN + SET @sql += N' AND ExecutionCount >= @MinimumExecutionCount '; + END; + + IF @MinutesBack IS NOT NULL + BEGIN + SET @sql += N' AND LastCompletionTime >= DATEADD(MINUTE, @min_back, GETDATE() ) '; + END; + SELECT @sql += N' ORDER BY ' + CASE @SortOrder WHEN N'cpu' THEN N' TotalCPU ' + WHEN N'reads' THEN N' TotalReads ' + WHEN N'writes' THEN N' TotalWrites ' + WHEN N'duration' THEN N' TotalDuration ' + WHEN N'executions' THEN N' ExecutionCount ' + WHEN N'compiles' THEN N' PlanCreationTime ' + WHEN N'memory grant' THEN N' MaxGrantKB' + WHEN N'unused grant' THEN N' MaxGrantKB - MaxUsedGrantKB' + WHEN N'spills' THEN N' MaxSpills' + WHEN N'duplicate' THEN N' plan_multiple_plans ' /* Issue #3345 */ + WHEN N'avg cpu' THEN N' AverageCPU' + WHEN N'avg reads' THEN N' AverageReads' + WHEN N'avg writes' THEN N' AverageWrites' + WHEN N'avg duration' THEN N' AverageDuration' + WHEN N'avg executions' THEN N' ExecutionsPerMinute' + WHEN N'avg memory grant' THEN N' AvgMaxMemoryGrant' + WHEN N'avg spills' THEN N' AvgSpills' + END + N' DESC '; + SET @sql += N' OPTION (RECOMPILE) ; '; -IF ((@OnlyQueryHashes IS NOT NULL AND LEN(@OnlyQueryHashes) > 0) - OR (@IgnoreQueryHashes IS NOT NULL AND LEN(@IgnoreQueryHashes) > 0)) - AND LEFT(@QueryFilter, 3) IN ('pro', 'fun') -BEGIN - RAISERROR('You cannot limit by query hash and filter by stored procedure', 16, 1); - RETURN; -END; - -/* If the user is attempting to limit by query hash, set up the - #only_query_hashes temp table. This will be used to narrow down - results. - - Just a reminder: Using @OnlyQueryHashes will ignore stored - procedures and triggers. - */ -IF @OnlyQueryHashes IS NOT NULL - AND LEN(@OnlyQueryHashes) > 0 -BEGIN - RAISERROR(N'Setting up filter for Query Hashes', 0, 1) WITH NOWAIT; - SET @individual = ''; - - WHILE LEN(@OnlyQueryHashes) > 0 - BEGIN - IF PATINDEX('%,%', @OnlyQueryHashes) > 0 - BEGIN - SET @individual = SUBSTRING(@OnlyQueryHashes, 0, PATINDEX('%,%',@OnlyQueryHashes)) ; - - INSERT INTO #only_query_hashes - SELECT CAST('' AS XML).value('xs:hexBinary( substring(sql:variable("@individual"), sql:column("t.pos")) )', 'varbinary(max)') - FROM (SELECT CASE SUBSTRING(@individual, 1, 2) WHEN '0x' THEN 3 ELSE 0 END) AS t(pos) - OPTION (RECOMPILE) ; - - --SELECT CAST(SUBSTRING(@individual, 1, 2) AS BINARY(8)); - - SET @OnlyQueryHashes = SUBSTRING(@OnlyQueryHashes, LEN(@individual + ',') + 1, LEN(@OnlyQueryHashes)) ; - END; - ELSE - BEGIN - SET @individual = @OnlyQueryHashes; - SET @OnlyQueryHashes = NULL; - - INSERT INTO #only_query_hashes - SELECT CAST('' AS XML).value('xs:hexBinary( substring(sql:variable("@individual"), sql:column("t.pos")) )', 'varbinary(max)') - FROM (SELECT CASE SUBSTRING(@individual, 1, 2) WHEN '0x' THEN 3 ELSE 0 END) AS t(pos) - OPTION (RECOMPILE) ; - - --SELECT CAST(SUBSTRING(@individual, 1, 2) AS VARBINARY(MAX)) ; - END; - END; -END; - -/* If the user is setting up a list of query hashes to ignore, those - values will be inserted into #ignore_query_hashes. This is used to - exclude values from query results. - - Just a reminder: Using @IgnoreQueryHashes will ignore stored - procedures and triggers. - */ -IF @IgnoreQueryHashes IS NOT NULL - AND LEN(@IgnoreQueryHashes) > 0 -BEGIN - RAISERROR(N'Setting up filter to ignore query hashes', 0, 1) WITH NOWAIT; - SET @individual = '' ; - - WHILE LEN(@IgnoreQueryHashes) > 0 - BEGIN - IF PATINDEX('%,%', @IgnoreQueryHashes) > 0 - BEGIN - SET @individual = SUBSTRING(@IgnoreQueryHashes, 0, PATINDEX('%,%',@IgnoreQueryHashes)) ; - - INSERT INTO #ignore_query_hashes - SELECT CAST('' AS XML).value('xs:hexBinary( substring(sql:variable("@individual"), sql:column("t.pos")) )', 'varbinary(max)') - FROM (SELECT CASE SUBSTRING(@individual, 1, 2) WHEN '0x' THEN 3 ELSE 0 END) AS t(pos) - OPTION (RECOMPILE) ; - - SET @IgnoreQueryHashes = SUBSTRING(@IgnoreQueryHashes, LEN(@individual + ',') + 1, LEN(@IgnoreQueryHashes)) ; - END; - ELSE - BEGIN - SET @individual = @IgnoreQueryHashes ; - SET @IgnoreQueryHashes = NULL ; + IF @sql IS NULL + BEGIN + RAISERROR('@sql is null, which means dynamic SQL generation went terribly wrong', 0, 1) WITH NOWAIT; + END - INSERT INTO #ignore_query_hashes - SELECT CAST('' AS XML).value('xs:hexBinary( substring(sql:variable("@individual"), sql:column("t.pos")) )', 'varbinary(max)') - FROM (SELECT CASE SUBSTRING(@individual, 1, 2) WHEN '0x' THEN 3 ELSE 0 END) AS t(pos) - OPTION (RECOMPILE) ; - END; - END; -END; + IF @Debug = 1 + BEGIN + RAISERROR('Printing @sql, the dynamic SQL we generated:', 0, 1) WITH NOWAIT; + PRINT SUBSTRING(@sql, 0, 4000); + PRINT SUBSTRING(@sql, 4000, 8000); + PRINT SUBSTRING(@sql, 8000, 12000); + PRINT SUBSTRING(@sql, 12000, 16000); + PRINT SUBSTRING(@sql, 16000, 20000); + PRINT SUBSTRING(@sql, 20000, 24000); + PRINT SUBSTRING(@sql, 24000, 28000); + PRINT SUBSTRING(@sql, 28000, 32000); + PRINT SUBSTRING(@sql, 32000, 36000); + PRINT SUBSTRING(@sql, 36000, 40000); + END; -IF @ConfigurationDatabaseName IS NOT NULL -BEGIN - RAISERROR(N'Reading values from Configuration Database', 0, 1) WITH NOWAIT; - DECLARE @config_sql NVARCHAR(MAX) = N'INSERT INTO #configuration SELECT parameter_name, value FROM ' - + QUOTENAME(@ConfigurationDatabaseName) - + '.' + QUOTENAME(@ConfigurationSchemaName) - + '.' + QUOTENAME(@ConfigurationTableName) - + ' ; ' ; - EXEC(@config_sql); + EXEC sp_executesql @sql, N'@Top INT, @min_duration INT, @min_back INT, @MinimumExecutionCount INT', @Top, @DurationFilter_i, @MinutesBack, @MinimumExecutionCount; END; -RAISERROR(N'Setting up variables', 0, 1) WITH NOWAIT; -DECLARE @sql NVARCHAR(MAX) = N'', - @insert_list NVARCHAR(MAX) = N'', - @plans_triggers_select_list NVARCHAR(MAX) = N'', - @body NVARCHAR(MAX) = N'', - @body_where NVARCHAR(MAX) = N'WHERE 1 = 1 ' + @nl, - @body_order NVARCHAR(MAX) = N'ORDER BY #sortable# DESC OPTION (RECOMPILE) ', - - @q NVARCHAR(1) = N'''', - @pv VARCHAR(20), - @pos TINYINT, - @v DECIMAL(6,2), - @build INT; - - -RAISERROR (N'Determining SQL Server version.',0,1) WITH NOWAIT; - -INSERT INTO #checkversion (version) -SELECT CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)) -OPTION (RECOMPILE); - -SELECT @v = common_version , - @build = build -FROM #checkversion -OPTION (RECOMPILE); +RAISERROR('Displaying analysis of plan cache.', 0, 1) WITH NOWAIT; -IF (@SortOrder IN ('memory grant', 'avg memory grant')) AND @VersionShowsMemoryGrants = 0 -BEGIN - RAISERROR('Your version of SQL does not support sorting by memory grant or average memory grant. Please use another sort order.', 16, 1); - RETURN; -END; +DECLARE @columns NVARCHAR(MAX) = N'' ; -IF (@SortOrder IN ('spills', 'avg spills') AND @VersionShowsSpills = 0) +IF @ExpertMode = 0 BEGIN - RAISERROR('Your version of SQL does not support sorting by spills. Please use another sort order.', 16, 1); - RETURN; + RAISERROR(N'Returning ExpertMode = 0', 0, 1) WITH NOWAIT; + SET @columns = N' DatabaseName AS [Database], + QueryPlanCost AS [Cost], + QueryText AS [Query Text], + QueryType AS [Query Type], + Warnings AS [Warnings], + QueryPlan AS [Query Plan], + missing_indexes AS [Missing Indexes], + implicit_conversion_info AS [Implicit Conversion Info], + cached_execution_parameters AS [Cached Execution Parameters], + CONVERT(NVARCHAR(30), CAST((ExecutionCount) AS BIGINT), 1) AS [# Executions], + CONVERT(NVARCHAR(30), CAST((ExecutionsPerMinute) AS BIGINT), 1) AS [Executions / Minute], + CONVERT(NVARCHAR(30), CAST((PercentExecutions) AS BIGINT), 1) AS [Execution Weight], + CONVERT(NVARCHAR(30), CAST((TotalCPU) AS BIGINT), 1) AS [Total CPU (ms)], + CONVERT(NVARCHAR(30), CAST((AverageCPU) AS BIGINT), 1) AS [Avg CPU (ms)], + CONVERT(NVARCHAR(30), CAST((PercentCPU) AS BIGINT), 1) AS [CPU Weight], + CONVERT(NVARCHAR(30), CAST((TotalDuration) AS BIGINT), 1) AS [Total Duration (ms)], + CONVERT(NVARCHAR(30), CAST((AverageDuration) AS BIGINT), 1) AS [Avg Duration (ms)], + CONVERT(NVARCHAR(30), CAST((PercentDuration) AS BIGINT), 1) AS [Duration Weight], + CONVERT(NVARCHAR(30), CAST((TotalReads) AS BIGINT), 1) AS [Total Reads], + CONVERT(NVARCHAR(30), CAST((AverageReads) AS BIGINT), 1) AS [Avg Reads], + CONVERT(NVARCHAR(30), CAST((PercentReads) AS BIGINT), 1) AS [Read Weight], + CONVERT(NVARCHAR(30), CAST((TotalWrites) AS BIGINT), 1) AS [Total Writes], + CONVERT(NVARCHAR(30), CAST((AverageWrites) AS BIGINT), 1) AS [Avg Writes], + CONVERT(NVARCHAR(30), CAST((PercentWrites) AS BIGINT), 1) AS [Write Weight], + CONVERT(NVARCHAR(30), CAST((AverageReturnedRows) AS BIGINT), 1) AS [Average Rows], + CONVERT(NVARCHAR(30), CAST((MinGrantKB) AS BIGINT), 1) AS [Minimum Memory Grant KB], + CONVERT(NVARCHAR(30), CAST((MaxGrantKB) AS BIGINT), 1) AS [Maximum Memory Grant KB], + CONVERT(NVARCHAR(30), CAST((MinUsedGrantKB) AS BIGINT), 1) AS [Minimum Used Grant KB], + CONVERT(NVARCHAR(30), CAST((MaxUsedGrantKB) AS BIGINT), 1) AS [Maximum Used Grant KB], + CONVERT(NVARCHAR(30), CAST((AvgMaxMemoryGrant) AS BIGINT), 1) AS [Average Max Memory Grant], + CONVERT(NVARCHAR(30), CAST((MinSpills) AS BIGINT), 1) AS [Min Spills], + CONVERT(NVARCHAR(30), CAST((MaxSpills) AS BIGINT), 1) AS [Max Spills], + CONVERT(NVARCHAR(30), CAST((TotalSpills) AS BIGINT), 1) AS [Total Spills], + CONVERT(NVARCHAR(30), CAST((AvgSpills) AS MONEY), 1) AS [Avg Spills], + PlanCreationTime AS [Created At], + LastExecutionTime AS [Last Execution], + LastCompletionTime AS [Last Completion], + PlanHandle AS [Plan Handle], + SqlHandle AS [SQL Handle], + COALESCE(SetOptions, '''') AS [SET Options], + QueryHash AS [Query Hash], + PlanGenerationNum, + [Remove Plan Handle From Cache]'; END; - -IF ((LEFT(@QueryFilter, 3) = 'fun') AND (@v < 13)) +ELSE BEGIN - RAISERROR('Your version of SQL does not support filtering by functions. Please use another filter.', 16, 1); - RETURN; -END; - -RAISERROR (N'Creating dynamic SQL based on SQL Server version.',0,1) WITH NOWAIT; - -SET @insert_list += N' -SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -INSERT INTO ##BlitzCacheProcs (SPID, QueryType, DatabaseName, AverageCPU, TotalCPU, AverageCPUPerMinute, PercentCPUByType, PercentDurationByType, - PercentReadsByType, PercentExecutionsByType, AverageDuration, TotalDuration, AverageReads, TotalReads, ExecutionCount, - ExecutionsPerMinute, TotalWrites, AverageWrites, PercentWritesByType, WritesPerMinute, PlanCreationTime, - LastExecutionTime, LastCompletionTime, StatementStartOffset, StatementEndOffset, PlanGenerationNum, MinReturnedRows, MaxReturnedRows, AverageReturnedRows, TotalReturnedRows, - LastReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, MaxUsedGrantKB, PercentMemoryGrantUsed, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, - QueryText, QueryPlan, TotalWorkerTimeForType, TotalElapsedTimeForType, TotalReadsForType, - TotalExecutionCountForType, TotalWritesForType, SqlHandle, PlanHandle, QueryHash, QueryPlanHash, - min_worker_time, max_worker_time, is_parallel, min_elapsed_time, max_elapsed_time, age_minutes, age_minutes_lifetime, Pattern) ' ; - -SET @body += N' -FROM (SELECT TOP (@Top) x.*, xpa.*, - CAST((CASE WHEN DATEDIFF(mi, cached_time, GETDATE()) > 0 AND execution_count > 1 - THEN DATEDIFF(mi, cached_time, GETDATE()) - ELSE NULL END) as MONEY) as age_minutes, - CAST((CASE WHEN DATEDIFF(mi, cached_time, last_execution_time) > 0 AND execution_count > 1 - THEN DATEDIFF(mi, cached_time, last_execution_time) - ELSE Null END) as MONEY) as age_minutes_lifetime - FROM sys.#view# x - CROSS APPLY (SELECT * FROM sys.dm_exec_plan_attributes(x.plan_handle) AS ixpa - WHERE ixpa.attribute = ''dbid'') AS xpa ' + @nl ; - -IF @SortOrder = 'duplicate' /* Issue #3345 */ - BEGIN - SET @body += N' INNER JOIN #duplicate_query_filter AS dqf ON x.sql_handle = dqf.sql_handle AND x.plan_handle = dqf.plan_handle AND x.creation_time = dqf.duplicate_creation_time ' + @nl ; - END - -IF @VersionShowsAirQuoteActualPlans = 1 - BEGIN - SET @body += N' CROSS APPLY sys.dm_exec_query_plan_stats(x.plan_handle) AS deqps ' + @nl ; - END - -SET @body += N' WHERE 1 = 1 ' + @nl ; - - IF EXISTS (SELECT * FROM sys.all_objects o WHERE o.name = 'dm_hadr_database_replica_states') - BEGIN - RAISERROR(N'Ignoring readable secondaries databases by default', 0, 1) WITH NOWAIT; - SET @body += N' AND CAST(xpa.value AS INT) NOT IN (SELECT database_id FROM #ReadableDBs)' + @nl ; - END + SET @columns = N' DatabaseName AS [Database], + QueryPlanCost AS [Cost], + QueryText AS [Query Text], + QueryType AS [Query Type], + Warnings AS [Warnings], + QueryPlan AS [Query Plan], + missing_indexes AS [Missing Indexes], + implicit_conversion_info AS [Implicit Conversion Info], + cached_execution_parameters AS [Cached Execution Parameters], ' + @nl; -IF @IgnoreSystemDBs = 1 + IF @ExpertMode = 2 /* Opserver */ BEGIN - RAISERROR(N'Ignoring system databases by default', 0, 1) WITH NOWAIT; - SET @body += N' AND COALESCE(LOWER(DB_NAME(CAST(xpa.value AS INT))), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'', ''dbmaintenance'', ''dbadmin'', ''dbatools'') AND COALESCE(DB_NAME(CAST(xpa.value AS INT)), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ; - END; - -IF @DatabaseName IS NOT NULL OR @DatabaseName <> N'' - BEGIN - RAISERROR(N'Filtering database name chosen', 0, 1) WITH NOWAIT; - SET @body += N' AND CAST(xpa.value AS BIGINT) = DB_ID(N' - + QUOTENAME(@DatabaseName, N'''') - + N') ' + @nl; - END; + RAISERROR(N'Returning Expert Mode = 2', 0, 1) WITH NOWAIT; + SET @columns += N' + SUBSTRING( + CASE WHEN warning_no_join_predicate = 1 THEN '', 20'' ELSE '''' END + + CASE WHEN compile_timeout = 1 THEN '', 18'' ELSE '''' END + + CASE WHEN compile_memory_limit_exceeded = 1 THEN '', 19'' ELSE '''' END + + CASE WHEN busy_loops = 1 THEN '', 16'' ELSE '''' END + + CASE WHEN is_forced_plan = 1 THEN '', 3'' ELSE '''' END + + CASE WHEN is_forced_parameterized > 0 THEN '', 5'' ELSE '''' END + + CASE WHEN unparameterized_query = 1 THEN '', 23'' ELSE '''' END + + CASE WHEN missing_index_count > 0 THEN '', 10'' ELSE '''' END + + CASE WHEN unmatched_index_count > 0 THEN '', 22'' ELSE '''' END + + CASE WHEN is_cursor = 1 THEN '', 4'' ELSE '''' END + + CASE WHEN is_parallel = 1 THEN '', 6'' ELSE '''' END + + CASE WHEN near_parallel = 1 THEN '', 7'' ELSE '''' END + + CASE WHEN frequent_execution = 1 THEN '', 1'' ELSE '''' END + + CASE WHEN plan_warnings = 1 THEN '', 8'' ELSE '''' END + + CASE WHEN parameter_sniffing = 1 THEN '', 2'' ELSE '''' END + + CASE WHEN long_running = 1 THEN '', 9'' ELSE '''' END + + CASE WHEN downlevel_estimator = 1 THEN '', 13'' ELSE '''' END + + CASE WHEN implicit_conversions = 1 THEN '', 14'' ELSE '''' END + + CASE WHEN tvf_join = 1 THEN '', 17'' ELSE '''' END + + CASE WHEN plan_multiple_plans > 0 THEN '', 21'' ELSE '''' END + + CASE WHEN unmatched_index_count > 0 THEN '', 22'' ELSE '''' END + + CASE WHEN is_trivial = 1 THEN '', 24'' ELSE '''' END + + CASE WHEN is_forced_serial = 1 THEN '', 25'' ELSE '''' END + + CASE WHEN is_key_lookup_expensive = 1 THEN '', 26'' ELSE '''' END + + CASE WHEN is_remote_query_expensive = 1 THEN '', 28'' ELSE '''' END + + CASE WHEN trace_flags_session IS NOT NULL THEN '', 29'' ELSE '''' END + + CASE WHEN is_unused_grant = 1 THEN '', 30'' ELSE '''' END + + CASE WHEN function_count > 0 THEN '', 31'' ELSE '''' END + + CASE WHEN clr_function_count > 0 THEN '', 32'' ELSE '''' END + + CASE WHEN PlanCreationTimeHours <= 4 THEN '', 33'' ELSE '''' END + + CASE WHEN is_table_variable = 1 THEN '', 34'' ELSE '''' END + + CASE WHEN no_stats_warning = 1 THEN '', 35'' ELSE '''' END + + CASE WHEN relop_warnings = 1 THEN '', 36'' ELSE '''' END + + CASE WHEN is_table_scan = 1 THEN '', 37'' ELSE '''' END + + CASE WHEN backwards_scan = 1 THEN '', 38'' ELSE '''' END + + CASE WHEN forced_index = 1 THEN '', 39'' ELSE '''' END + + CASE WHEN forced_seek = 1 OR forced_scan = 1 THEN '', 40'' ELSE '''' END + + CASE WHEN columnstore_row_mode = 1 THEN '', 41'' ELSE '''' END + + CASE WHEN is_computed_scalar = 1 THEN '', 42'' ELSE '''' END + + CASE WHEN is_sort_expensive = 1 THEN '', 43'' ELSE '''' END + + CASE WHEN is_computed_filter = 1 THEN '', 44'' ELSE '''' END + + CASE WHEN index_ops >= 5 THEN '', 45'' ELSE '''' END + + CASE WHEN is_row_level = 1 THEN '', 46'' ELSE '''' END + + CASE WHEN is_spatial = 1 THEN '', 47'' ELSE '''' END + + CASE WHEN index_dml = 1 THEN '', 48'' ELSE '''' END + + CASE WHEN table_dml = 1 THEN '', 49'' ELSE '''' END + + CASE WHEN long_running_low_cpu = 1 THEN '', 50'' ELSE '''' END + + CASE WHEN low_cost_high_cpu = 1 THEN '', 51'' ELSE '''' END + + CASE WHEN stale_stats = 1 THEN '', 52'' ELSE '''' END + + CASE WHEN is_adaptive = 1 THEN '', 53'' ELSE '''' END + + CASE WHEN is_spool_expensive = 1 THEN + '', 54'' ELSE '''' END + + CASE WHEN is_spool_more_rows = 1 THEN + '', 55'' ELSE '''' END + + CASE WHEN is_table_spool_expensive = 1 THEN + '', 67'' ELSE '''' END + + CASE WHEN is_table_spool_more_rows = 1 THEN + '', 68'' ELSE '''' END + + CASE WHEN is_bad_estimate = 1 THEN + '', 56'' ELSE '''' END + + CASE WHEN is_paul_white_electric = 1 THEN '', 57'' ELSE '''' END + + CASE WHEN is_row_goal = 1 THEN '', 58'' ELSE '''' END + + CASE WHEN is_big_spills = 1 THEN '', 59'' ELSE '''' END + + CASE WHEN is_mstvf = 1 THEN '', 60'' ELSE '''' END + + CASE WHEN is_mm_join = 1 THEN '', 61'' ELSE '''' END + + CASE WHEN is_nonsargable = 1 THEN '', 62'' ELSE '''' END + + CASE WHEN CompileTime > 5000 THEN '', 63 '' ELSE '''' END + + CASE WHEN CompileCPU > 5000 THEN '', 64 '' ELSE '''' END + + CASE WHEN CompileMemory > 1024 AND ((CompileMemory) / (1 * CASE WHEN MaxCompileMemory = 0 THEN 1 ELSE MaxCompileMemory END) * 100.) >= 10. THEN '', 65 '' ELSE '''' END + + CASE WHEN select_with_writes > 0 THEN '', 66'' ELSE '''' END + , 3, 200000) AS opserver_warning , ' + @nl ; + END; + + SET @columns += N' + CONVERT(NVARCHAR(30), CAST((ExecutionCount) AS BIGINT), 1) AS [# Executions], + CONVERT(NVARCHAR(30), CAST((ExecutionsPerMinute) AS BIGINT), 1) AS [Executions / Minute], + CONVERT(NVARCHAR(30), CAST((PercentExecutions) AS BIGINT), 1) AS [Execution Weight], + CONVERT(NVARCHAR(30), CAST((SerialDesiredMemory) AS BIGINT), 1) AS [Serial Desired Memory], + CONVERT(NVARCHAR(30), CAST((SerialRequiredMemory) AS BIGINT), 1) AS [Serial Required Memory], + CONVERT(NVARCHAR(30), CAST((TotalCPU) AS BIGINT), 1) AS [Total CPU (ms)], + CONVERT(NVARCHAR(30), CAST((AverageCPU) AS BIGINT), 1) AS [Avg CPU (ms)], + CONVERT(NVARCHAR(30), CAST((PercentCPU) AS BIGINT), 1) AS [CPU Weight], + CONVERT(NVARCHAR(30), CAST((TotalDuration) AS BIGINT), 1) AS [Total Duration (ms)], + CONVERT(NVARCHAR(30), CAST((AverageDuration) AS BIGINT), 1) AS [Avg Duration (ms)], + CONVERT(NVARCHAR(30), CAST((PercentDuration) AS BIGINT), 1) AS [Duration Weight], + CONVERT(NVARCHAR(30), CAST((TotalReads) AS BIGINT), 1) AS [Total Reads], + CONVERT(NVARCHAR(30), CAST((AverageReads) AS BIGINT), 1) AS [Average Reads], + CONVERT(NVARCHAR(30), CAST((PercentReads) AS BIGINT), 1) AS [Read Weight], + CONVERT(NVARCHAR(30), CAST((TotalWrites) AS BIGINT), 1) AS [Total Writes], + CONVERT(NVARCHAR(30), CAST((AverageWrites) AS BIGINT), 1) AS [Average Writes], + CONVERT(NVARCHAR(30), CAST((PercentWrites) AS BIGINT), 1) AS [Write Weight], + CONVERT(NVARCHAR(30), CAST((PercentExecutionsByType) AS BIGINT), 1) AS [% Executions (Type)], + CONVERT(NVARCHAR(30), CAST((PercentCPUByType) AS BIGINT), 1) AS [% CPU (Type)], + CONVERT(NVARCHAR(30), CAST((PercentDurationByType) AS BIGINT), 1) AS [% Duration (Type)], + CONVERT(NVARCHAR(30), CAST((PercentReadsByType) AS BIGINT), 1) AS [% Reads (Type)], + CONVERT(NVARCHAR(30), CAST((PercentWritesByType) AS BIGINT), 1) AS [% Writes (Type)], + CONVERT(NVARCHAR(30), CAST((TotalReturnedRows) AS BIGINT), 1) AS [Total Rows], + CONVERT(NVARCHAR(30), CAST((AverageReturnedRows) AS BIGINT), 1) AS [Avg Rows], + CONVERT(NVARCHAR(30), CAST((MinReturnedRows) AS BIGINT), 1) AS [Min Rows], + CONVERT(NVARCHAR(30), CAST((MaxReturnedRows) AS BIGINT), 1) AS [Max Rows], + CONVERT(NVARCHAR(30), CAST((MinGrantKB) AS BIGINT), 1) AS [Minimum Memory Grant KB], + CONVERT(NVARCHAR(30), CAST((MaxGrantKB) AS BIGINT), 1) AS [Maximum Memory Grant KB], + CONVERT(NVARCHAR(30), CAST((MinUsedGrantKB) AS BIGINT), 1) AS [Minimum Used Grant KB], + CONVERT(NVARCHAR(30), CAST((MaxUsedGrantKB) AS BIGINT), 1) AS [Maximum Used Grant KB], + CONVERT(NVARCHAR(30), CAST((AvgMaxMemoryGrant) AS BIGINT), 1) AS [Average Max Memory Grant], + CONVERT(NVARCHAR(30), CAST((MinSpills) AS BIGINT), 1) AS [Min Spills], + CONVERT(NVARCHAR(30), CAST((MaxSpills) AS BIGINT), 1) AS [Max Spills], + CONVERT(NVARCHAR(30), CAST((TotalSpills) AS BIGINT), 1) AS [Total Spills], + CONVERT(NVARCHAR(30), CAST((AvgSpills) AS MONEY), 1) AS [Avg Spills], + CONVERT(NVARCHAR(30), CAST((NumberOfPlans) AS BIGINT), 1) AS [# Plans], + CONVERT(NVARCHAR(30), CAST((NumberOfDistinctPlans) AS BIGINT), 1) AS [# Distinct Plans], + PlanCreationTime AS [Created At], + LastExecutionTime AS [Last Execution], + LastCompletionTime AS [Last Completion], + CONVERT(NVARCHAR(30), CAST((CachedPlanSize) AS BIGINT), 1) AS [Cached Plan Size (KB)], + CONVERT(NVARCHAR(30), CAST((CompileTime) AS BIGINT), 1) AS [Compile Time (ms)], + CONVERT(NVARCHAR(30), CAST((CompileCPU) AS BIGINT), 1) AS [Compile CPU (ms)], + CONVERT(NVARCHAR(30), CAST((CompileMemory) AS BIGINT), 1) AS [Compile memory (KB)], + COALESCE(SetOptions, '''') AS [SET Options], + PlanHandle AS [Plan Handle], + SqlHandle AS [SQL Handle], + [SQL Handle More Info], + QueryHash AS [Query Hash], + [Query Hash More Info], + QueryPlanHash AS [Query Plan Hash], + StatementStartOffset, + StatementEndOffset, + PlanGenerationNum, + [Remove Plan Handle From Cache], + [Remove SQL Handle From Cache]'; +END; -IF (SELECT COUNT(*) FROM #only_sql_handles) > 0 -BEGIN - RAISERROR(N'Including only chosen SQL Handles', 0, 1) WITH NOWAIT; - SET @body += N' AND EXISTS(SELECT 1/0 FROM #only_sql_handles q WHERE q.sql_handle = x.sql_handle) ' + @nl ; -END; +SET @sql = N' +SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; +SELECT TOP (@Top) ' + @columns + @nl + N' +FROM ##BlitzCacheProcs +WHERE SPID = @spid ' + @nl; -IF (SELECT COUNT(*) FROM #ignore_sql_handles) > 0 -BEGIN - RAISERROR(N'Including only chosen SQL Handles', 0, 1) WITH NOWAIT; - SET @body += N' AND NOT EXISTS(SELECT 1/0 FROM #ignore_sql_handles q WHERE q.sql_handle = x.sql_handle) ' + @nl ; -END; +IF @MinimumExecutionCount IS NOT NULL + BEGIN + SET @sql += N' AND ExecutionCount >= @MinimumExecutionCount ' + @nl; + END; -IF (SELECT COUNT(*) FROM #only_query_hashes) > 0 - AND (SELECT COUNT(*) FROM #ignore_query_hashes) = 0 - AND (SELECT COUNT(*) FROM #only_sql_handles) = 0 - AND (SELECT COUNT(*) FROM #ignore_sql_handles) = 0 -BEGIN - RAISERROR(N'Including only chosen Query Hashes', 0, 1) WITH NOWAIT; - SET @body += N' AND EXISTS(SELECT 1/0 FROM #only_query_hashes q WHERE q.query_hash = x.query_hash) ' + @nl ; -END; +IF @MinutesBack IS NOT NULL + BEGIN + SET @sql += N' AND LastCompletionTime >= DATEADD(MINUTE, @min_back, GETDATE() ) ' + @nl; + END; -/* filtering for query hashes */ -IF (SELECT COUNT(*) FROM #ignore_query_hashes) > 0 - AND (SELECT COUNT(*) FROM #only_query_hashes) = 0 -BEGIN - RAISERROR(N'Excluding chosen Query Hashes', 0, 1) WITH NOWAIT; - SET @body += N' AND NOT EXISTS(SELECT 1/0 FROM #ignore_query_hashes iq WHERE iq.query_hash = x.query_hash) ' + @nl ; +SELECT @sql += N' ORDER BY ' + CASE @SortOrder WHEN N'cpu' THEN N' TotalCPU ' + WHEN N'reads' THEN N' TotalReads ' + WHEN N'writes' THEN N' TotalWrites ' + WHEN N'duration' THEN N' TotalDuration ' + WHEN N'executions' THEN N' ExecutionCount ' + WHEN N'compiles' THEN N' PlanCreationTime ' + WHEN N'memory grant' THEN N' MaxGrantKB' + WHEN N'unused grant' THEN N' MaxGrantKB - MaxUsedGrantKB ' + WHEN N'duplicate' THEN N' plan_multiple_plans ' + WHEN N'spills' THEN N' MaxSpills ' + WHEN N'avg cpu' THEN N' AverageCPU' + WHEN N'avg reads' THEN N' AverageReads' + WHEN N'avg writes' THEN N' AverageWrites' + WHEN N'avg duration' THEN N' AverageDuration' + WHEN N'avg executions' THEN N' ExecutionsPerMinute' + WHEN N'avg memory grant' THEN N' AvgMaxMemoryGrant' + WHEN N'avg spills' THEN N' AvgSpills' + END + N' DESC '; +SET @sql += N' OPTION (RECOMPILE) ; '; + +IF @Debug = 1 + BEGIN + PRINT SUBSTRING(@sql, 0, 4000); + PRINT SUBSTRING(@sql, 4000, 8000); + PRINT SUBSTRING(@sql, 8000, 12000); + PRINT SUBSTRING(@sql, 12000, 16000); + PRINT SUBSTRING(@sql, 16000, 20000); + PRINT SUBSTRING(@sql, 20000, 24000); + PRINT SUBSTRING(@sql, 24000, 28000); + PRINT SUBSTRING(@sql, 28000, 32000); + PRINT SUBSTRING(@sql, 32000, 36000); + PRINT SUBSTRING(@sql, 36000, 40000); + END; +IF(@OutputType <> 'NONE') +BEGIN + EXEC sp_executesql @sql, N'@Top INT, @spid INT, @MinimumExecutionCount INT, @min_back INT', @Top, @@SPID, @MinimumExecutionCount, @MinutesBack; END; -/* end filtering for query hashes */ -IF @DurationFilter IS NOT NULL - BEGIN - RAISERROR(N'Setting duration filter', 0, 1) WITH NOWAIT; - SET @body += N' AND (total_elapsed_time / 1000.0) / execution_count > @min_duration ' + @nl ; - END; +/* -IF @MinutesBack IS NOT NULL - BEGIN - RAISERROR(N'Setting minutes back filter', 0, 1) WITH NOWAIT; - SET @body += N' AND DATEADD(SECOND, (x.last_elapsed_time / 1000000.), x.last_execution_time) >= DATEADD(MINUTE, @min_back, GETDATE()) ' + @nl ; - END; +This section will check if: + * >= 30% of plans were created in the last hour + * Check on the memory_clerks DMV for space used by TokenAndPermUserStore + * Compare that to the size of the buffer pool + * If it's >10%, +*/ +IF EXISTS +( + SELECT 1/0 + FROM #plan_creation AS pc + WHERE pc.percent_1 >= 30 +) +BEGIN -IF @SlowlySearchPlansFor IS NOT NULL - BEGIN - RAISERROR(N'Setting string search for @SlowlySearchPlansFor, so remember, this is gonna be slow', 0, 1) WITH NOWAIT; - SET @SlowlySearchPlansFor = REPLACE((REPLACE((REPLACE((REPLACE(@SlowlySearchPlansFor, N'[', N'_')), N']', N'_')), N'^', N'_')), N'''', N''''''); - SET @body_where += N' AND CAST(qp.query_plan AS NVARCHAR(MAX)) LIKE N''%' + @SlowlySearchPlansFor + N'%'' ' + @nl; - END +SELECT @common_version = + CONVERT(DECIMAL(10,2), c.common_version) +FROM #checkversion AS c; +IF @common_version >= 11 + SET @user_perm_sql = N' + SET @buffer_pool_memory_gb = 0; + SELECT @buffer_pool_memory_gb = SUM(pages_kb)/ 1024. / 1024. + FROM sys.dm_os_memory_clerks + WHERE type = ''MEMORYCLERK_SQLBUFFERPOOL'';' +ELSE + SET @user_perm_sql = N' + SET @buffer_pool_memory_gb = 0; + SELECT @buffer_pool_memory_gb = SUM(single_pages_kb + multi_pages_kb)/ 1024. / 1024. + FROM sys.dm_os_memory_clerks + WHERE type = ''MEMORYCLERK_SQLBUFFERPOOL'';' -/* Apply the sort order here to only grab relevant plans. - This should make it faster to process since we'll be pulling back fewer - plans for processing. - */ -RAISERROR(N'Applying chosen sort order', 0, 1) WITH NOWAIT; -SELECT @body += N' ORDER BY ' + - CASE @SortOrder WHEN N'cpu' THEN N'total_worker_time' - WHEN N'reads' THEN N'total_logical_reads' - WHEN N'writes' THEN N'total_logical_writes' - WHEN N'duration' THEN N'total_elapsed_time' - WHEN N'executions' THEN N'execution_count' - WHEN N'compiles' THEN N'cached_time' - WHEN N'memory grant' THEN N'max_grant_kb' - WHEN N'unused grant' THEN N'max_grant_kb - max_used_grant_kb' - WHEN N'spills' THEN N'max_spills' - WHEN N'duplicate' THEN N'total_worker_time' /* Issue #3345 */ - /* And now the averages */ - WHEN N'avg cpu' THEN N'total_worker_time / execution_count' - WHEN N'avg reads' THEN N'total_logical_reads / execution_count' - WHEN N'avg writes' THEN N'total_logical_writes / execution_count' - WHEN N'avg duration' THEN N'total_elapsed_time / execution_count' - WHEN N'avg memory grant' THEN N'CASE WHEN max_grant_kb = 0 THEN 0 ELSE max_grant_kb / execution_count END' - WHEN N'avg spills' THEN N'CASE WHEN total_spills = 0 THEN 0 ELSE total_spills / execution_count END' - WHEN N'avg executions' THEN 'CASE WHEN execution_count = 0 THEN 0 - WHEN COALESCE(CAST((CASE WHEN DATEDIFF(mi, cached_time, GETDATE()) > 0 AND execution_count > 1 - THEN DATEDIFF(mi, cached_time, GETDATE()) - ELSE NULL END) as MONEY), CAST((CASE WHEN DATEDIFF(mi, cached_time, last_execution_time) > 0 AND execution_count > 1 - THEN DATEDIFF(mi, cached_time, last_execution_time) - ELSE Null END) as MONEY), 0) = 0 THEN 0 - ELSE CAST((1.00 * execution_count / COALESCE(CAST((CASE WHEN DATEDIFF(mi, cached_time, GETDATE()) > 0 AND execution_count > 1 - THEN DATEDIFF(mi, cached_time, GETDATE()) - ELSE NULL END) as MONEY), CAST((CASE WHEN DATEDIFF(mi, cached_time, last_execution_time) > 0 AND execution_count > 1 - THEN DATEDIFF(mi, cached_time, last_execution_time) - ELSE Null END) as MONEY))) AS money) - END ' - END + N' DESC ' + @nl ; +EXEC sys.sp_executesql @user_perm_sql, + N'@buffer_pool_memory_gb DECIMAL(10,2) OUTPUT', + @buffer_pool_memory_gb = @buffer_pool_memory_gb OUTPUT; +IF @common_version >= 11 +BEGIN + SET @user_perm_sql = N' + SELECT @user_perm_gb = CASE WHEN (pages_kb / 1024.0 / 1024.) >= 2. + THEN CONVERT(DECIMAL(38, 2), (pages_kb / 1024.0 / 1024.)) + ELSE 0 + END + FROM sys.dm_os_memory_clerks + WHERE type = ''USERSTORE_TOKENPERM'' + AND name = ''TokenAndPermUserStore'';'; +END; - -SET @body += N') AS qs - CROSS JOIN(SELECT SUM(execution_count) AS t_TotalExecs, - SUM(CAST(total_elapsed_time AS BIGINT) / 1000.0) AS t_TotalElapsed, - SUM(CAST(total_worker_time AS BIGINT) / 1000.0) AS t_TotalWorker, - SUM(CAST(total_logical_reads AS DECIMAL(30))) AS t_TotalReads, - SUM(CAST(total_logical_writes AS DECIMAL(30))) AS t_TotalWrites - FROM sys.#view#) AS t - CROSS APPLY sys.dm_exec_plan_attributes(qs.plan_handle) AS pa - CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) AS st - CROSS APPLY sys.dm_exec_query_plan(qs.plan_handle) AS qp ' + @nl ; +IF @common_version < 11 +BEGIN + SET @user_perm_sql = N' + SELECT @user_perm_gb = CASE WHEN ((single_pages_kb + multi_pages_kb) / 1024.0 / 1024.) >= 2. + THEN CONVERT(DECIMAL(38, 2), ((single_pages_kb + multi_pages_kb) / 1024.0 / 1024.)) + ELSE 0 + END + FROM sys.dm_os_memory_clerks + WHERE type = ''USERSTORE_TOKENPERM'' + AND name = ''TokenAndPermUserStore'';'; +END; -IF @VersionShowsAirQuoteActualPlans = 1 - BEGIN - SET @body += N' CROSS APPLY sys.dm_exec_query_plan_stats(qs.plan_handle) AS deqps ' + @nl ; - END +EXEC sys.sp_executesql @user_perm_sql, + N'@user_perm_gb DECIMAL(10,2) OUTPUT', + @user_perm_gb = @user_perm_gb_out OUTPUT; -SET @body_where += N' AND pa.attribute = ' + QUOTENAME('dbid', @q ) + @nl ; +IF @buffer_pool_memory_gb > 0 + BEGIN + IF (@user_perm_gb_out / (1. * @buffer_pool_memory_gb)) * 100. >= 10 + BEGIN + SET @is_tokenstore_big = 1; + SET @user_perm_percent = (@user_perm_gb_out / (1. * @buffer_pool_memory_gb)) * 100.; + END + END -IF @NoobSaibot = 1 -BEGIN - SET @body_where += N' AND qp.query_plan.exist(''declare namespace p="http://schemas.microsoft.com/sqlserver/2004/07/showplan";//p:StmtSimple//p:MissingIndex'') = 1' + @nl ; END -SET @plans_triggers_select_list += N' -SELECT TOP (@Top) - @@SPID , - ''Procedure or Function: '' - + QUOTENAME(COALESCE(OBJECT_SCHEMA_NAME(qs.object_id, qs.database_id),'''')) - + ''.'' - + QUOTENAME(COALESCE(OBJECT_NAME(qs.object_id, qs.database_id),'''')) AS QueryType, - COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), N''-- N/A --'') AS DatabaseName, - (total_worker_time / 1000.0) / execution_count AS AvgCPU , - (total_worker_time / 1000.0) AS TotalCPU , - CASE WHEN total_worker_time = 0 THEN 0 - WHEN COALESCE(age_minutes, DATEDIFF(mi, qs.cached_time, qs.last_execution_time), 0) = 0 THEN 0 - ELSE CAST((total_worker_time / 1000.0) / COALESCE(age_minutes, DATEDIFF(mi, qs.cached_time, qs.last_execution_time)) AS MONEY) - END AS AverageCPUPerMinute , - CASE WHEN t.t_TotalWorker = 0 THEN 0 - ELSE CAST(ROUND(100.00 * (total_worker_time / 1000.0) / t.t_TotalWorker, 2) AS MONEY) - END AS PercentCPUByType, - CASE WHEN t.t_TotalElapsed = 0 THEN 0 - ELSE CAST(ROUND(100.00 * (total_elapsed_time / 1000.0) / t.t_TotalElapsed, 2) AS MONEY) - END AS PercentDurationByType, - CASE WHEN t.t_TotalReads = 0 THEN 0 - ELSE CAST(ROUND(100.00 * total_logical_reads / t.t_TotalReads, 2) AS MONEY) - END AS PercentReadsByType, - CASE WHEN t.t_TotalExecs = 0 THEN 0 - ELSE CAST(ROUND(100.00 * execution_count / t.t_TotalExecs, 2) AS MONEY) - END AS PercentExecutionsByType, - (total_elapsed_time / 1000.0) / execution_count AS AvgDuration , - (total_elapsed_time / 1000.0) AS TotalDuration , - total_logical_reads / execution_count AS AvgReads , - total_logical_reads AS TotalReads , - execution_count AS ExecutionCount , - CASE WHEN execution_count = 0 THEN 0 - WHEN COALESCE(age_minutes, DATEDIFF(mi, qs.cached_time, qs.last_execution_time), 0) = 0 THEN 0 - ELSE CAST((1.00 * execution_count / COALESCE(age_minutes, DATEDIFF(mi, qs.cached_time, qs.last_execution_time))) AS money) - END AS ExecutionsPerMinute , - total_logical_writes AS TotalWrites , - total_logical_writes / execution_count AS AverageWrites , - CASE WHEN t.t_TotalWrites = 0 THEN 0 - ELSE CAST(ROUND(100.00 * total_logical_writes / t.t_TotalWrites, 2) AS MONEY) - END AS PercentWritesByType, - CASE WHEN total_logical_writes = 0 THEN 0 - WHEN COALESCE(age_minutes, DATEDIFF(mi, qs.cached_time, qs.last_execution_time), 0) = 0 THEN 0 - ELSE CAST((1.00 * total_logical_writes / COALESCE(age_minutes, DATEDIFF(mi, qs.cached_time, qs.last_execution_time), 0)) AS money) - END AS WritesPerMinute, - qs.cached_time AS PlanCreationTime, - qs.last_execution_time AS LastExecutionTime, - DATEADD(SECOND, (qs.last_elapsed_time / 1000000.), qs.last_execution_time) AS LastCompletionTime, - NULL AS StatementStartOffset, - NULL AS StatementEndOffset, - NULL AS PlanGenerationNum, - NULL AS MinReturnedRows, - NULL AS MaxReturnedRows, - NULL AS AvgReturnedRows, - NULL AS TotalReturnedRows, - NULL AS LastReturnedRows, - NULL AS MinGrantKB, - NULL AS MaxGrantKB, - NULL AS MinUsedGrantKB, - NULL AS MaxUsedGrantKB, - NULL AS PercentMemoryGrantUsed, - NULL AS AvgMaxMemoryGrant,'; - IF @VersionShowsSpills = 1 - BEGIN - RAISERROR(N'Getting spill information for newer versions of SQL', 0, 1) WITH NOWAIT; - SET @plans_triggers_select_list += N' - min_spills AS MinSpills, - max_spills AS MaxSpills, - total_spills AS TotalSpills, - CAST(ISNULL(NULLIF(( total_spills * 1. ), 0) / NULLIF(execution_count, 0), 0) AS MONEY) AS AvgSpills, '; - END; - ELSE + +IF @HideSummary = 0 AND @ExportToExcel = 0 +BEGIN + IF @Reanalyze = 0 BEGIN - RAISERROR(N'Substituting NULLs for spill columns in older versions of SQL', 0, 1) WITH NOWAIT; - SET @plans_triggers_select_list += N' - NULL AS MinSpills, - NULL AS MaxSpills, - NULL AS TotalSpills, - NULL AS AvgSpills, ' ; - END; - - SET @plans_triggers_select_list += - N'st.text AS QueryText ,'; + RAISERROR('Building query plan summary data.', 0, 1) WITH NOWAIT; - IF @VersionShowsAirQuoteActualPlans = 1 - BEGIN - SET @plans_triggers_select_list += N' CASE WHEN DATALENGTH(COALESCE(deqps.query_plan,'''')) > DATALENGTH(COALESCE(qp.query_plan,'''')) THEN deqps.query_plan ELSE qp.query_plan END AS QueryPlan, ' + @nl ; - END; - ELSE - BEGIN - SET @plans_triggers_select_list += N' qp.query_plan AS QueryPlan, ' + @nl ; - END; + /* Build summary data */ + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs + WHERE frequent_execution = 1 + AND SPID = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 1, + 100, + 'Execution Pattern', + 'Frequent Execution', + 'https://www.brentozar.com/blitzcache/frequently-executed-queries/', + 'Queries are being executed more than ' + + CAST (@execution_threshold AS VARCHAR(5)) + + ' times per minute. This can put additional load on the server, even when queries are lightweight.') ; - SET @plans_triggers_select_list += - N't.t_TotalWorker, - t.t_TotalElapsed, - t.t_TotalReads, - t.t_TotalExecs, - t.t_TotalWrites, - qs.sql_handle AS SqlHandle, - qs.plan_handle AS PlanHandle, - NULL AS QueryHash, - NULL AS QueryPlanHash, - qs.min_worker_time / 1000.0, - qs.max_worker_time / 1000.0, - CASE WHEN qp.query_plan.value(''declare namespace p="http://schemas.microsoft.com/sqlserver/2004/07/showplan";max(//p:RelOp/@Parallel)'', ''float'') > 0 THEN 1 ELSE 0 END, - qs.min_elapsed_time / 1000.0, - qs.max_elapsed_time / 1000.0, - age_minutes, - age_minutes_lifetime, - @SortOrder '; + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs + WHERE parameter_sniffing = 1 + AND SPID = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 2, + 50, + 'Parameterization', + 'Parameter Sniffing', + 'https://www.brentozar.com/blitzcache/parameter-sniffing/', + 'There are signs of parameter sniffing (wide variance in rows return or time to execute). Investigate query patterns and tune code appropriately.') ; + /* Forced execution plans */ + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs + WHERE is_forced_plan = 1 + AND SPID = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 3, + 50, + 'Parameterization', + 'Forced Plan', + 'https://www.brentozar.com/blitzcache/forced-plans/', + 'Execution plans have been compiled with forced plans, either through FORCEPLAN, plan guides, or forced parameterization. This will make general tuning efforts less effective.'); -IF LEFT(@QueryFilter, 3) IN ('all', 'sta') -BEGIN - SET @sql += @insert_list; - - SET @sql += N' - SELECT TOP (@Top) - @@SPID , - ''Statement'' AS QueryType, - COALESCE(DB_NAME(CAST(pa.value AS INT)), N''-- N/A --'') AS DatabaseName, - (total_worker_time / 1000.0) / execution_count AS AvgCPU , - (total_worker_time / 1000.0) AS TotalCPU , - CASE WHEN total_worker_time = 0 THEN 0 - WHEN COALESCE(age_minutes, DATEDIFF(mi, qs.creation_time, qs.last_execution_time), 0) = 0 THEN 0 - ELSE CAST((total_worker_time / 1000.0) / COALESCE(age_minutes, DATEDIFF(mi, qs.creation_time, qs.last_execution_time)) AS MONEY) - END AS AverageCPUPerMinute , - CASE WHEN t.t_TotalWorker = 0 THEN 0 - ELSE CAST(ROUND(100.00 * total_worker_time / t.t_TotalWorker, 2) AS MONEY) - END AS PercentCPUByType, - CASE WHEN t.t_TotalElapsed = 0 THEN 0 - ELSE CAST(ROUND(100.00 * total_elapsed_time / t.t_TotalElapsed, 2) AS MONEY) - END AS PercentDurationByType, - CASE WHEN t.t_TotalReads = 0 THEN 0 - ELSE CAST(ROUND(100.00 * total_logical_reads / t.t_TotalReads, 2) AS MONEY) - END AS PercentReadsByType, - CAST(ROUND(100.00 * execution_count / t.t_TotalExecs, 2) AS MONEY) AS PercentExecutionsByType, - (total_elapsed_time / 1000.0) / execution_count AS AvgDuration , - (total_elapsed_time / 1000.0) AS TotalDuration , - total_logical_reads / execution_count AS AvgReads , - total_logical_reads AS TotalReads , - execution_count AS ExecutionCount , - CASE WHEN execution_count = 0 THEN 0 - WHEN COALESCE(age_minutes, DATEDIFF(mi, qs.creation_time, qs.last_execution_time), 0) = 0 THEN 0 - ELSE CAST((1.00 * execution_count / COALESCE(age_minutes, DATEDIFF(mi, qs.creation_time, qs.last_execution_time))) AS money) - END AS ExecutionsPerMinute , - total_logical_writes AS TotalWrites , - total_logical_writes / execution_count AS AverageWrites , - CASE WHEN t.t_TotalWrites = 0 THEN 0 - ELSE CAST(ROUND(100.00 * total_logical_writes / t.t_TotalWrites, 2) AS MONEY) - END AS PercentWritesByType, - CASE WHEN total_logical_writes = 0 THEN 0 - WHEN COALESCE(age_minutes, DATEDIFF(mi, qs.creation_time, qs.last_execution_time), 0) = 0 THEN 0 - ELSE CAST((1.00 * total_logical_writes / COALESCE(age_minutes, DATEDIFF(mi, qs.creation_time, qs.last_execution_time), 0)) AS money) - END AS WritesPerMinute, - qs.creation_time AS PlanCreationTime, - qs.last_execution_time AS LastExecutionTime, - DATEADD(SECOND, (qs.last_elapsed_time / 1000000.), qs.last_execution_time) AS LastCompletionTime, - qs.statement_start_offset AS StatementStartOffset, - qs.statement_end_offset AS StatementEndOffset, - qs.plan_generation_num AS PlanGenerationNum, '; - - IF (@v >= 11) OR (@v >= 10.5 AND @build >= 2500) - BEGIN - RAISERROR(N'Adding additional info columns for newer versions of SQL', 0, 1) WITH NOWAIT; - SET @sql += N' - qs.min_rows AS MinReturnedRows, - qs.max_rows AS MaxReturnedRows, - CAST(qs.total_rows as MONEY) / execution_count AS AvgReturnedRows, - qs.total_rows AS TotalReturnedRows, - qs.last_rows AS LastReturnedRows, ' ; - END; - ELSE - BEGIN - RAISERROR(N'Substituting NULLs for more info columns in older versions of SQL', 0, 1) WITH NOWAIT; - SET @sql += N' - NULL AS MinReturnedRows, - NULL AS MaxReturnedRows, - NULL AS AvgReturnedRows, - NULL AS TotalReturnedRows, - NULL AS LastReturnedRows, ' ; - END; - - IF @VersionShowsMemoryGrants = 1 - BEGIN - RAISERROR(N'Getting memory grant information for newer versions of SQL', 0, 1) WITH NOWAIT; - SET @sql += N' - min_grant_kb AS MinGrantKB, - max_grant_kb AS MaxGrantKB, - min_used_grant_kb AS MinUsedGrantKB, - max_used_grant_kb AS MaxUsedGrantKB, - CAST(ISNULL(NULLIF(( total_used_grant_kb * 1.00 ), 0) / NULLIF(total_grant_kb, 0), 0) * 100. AS MONEY) AS PercentMemoryGrantUsed, - CAST(ISNULL(NULLIF(( total_grant_kb * 1. ), 0) / NULLIF(execution_count, 0), 0) AS MONEY) AS AvgMaxMemoryGrant, '; - END; - ELSE - BEGIN - RAISERROR(N'Substituting NULLs for memory grant columns in older versions of SQL', 0, 1) WITH NOWAIT; - SET @sql += N' - NULL AS MinGrantKB, - NULL AS MaxGrantKB, - NULL AS MinUsedGrantKB, - NULL AS MaxUsedGrantKB, - NULL AS PercentMemoryGrantUsed, - NULL AS AvgMaxMemoryGrant, ' ; - END; + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs + WHERE is_cursor = 1 + AND SPID = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 4, + 200, + 'Cursors', + 'Cursor', + 'https://www.brentozar.com/blitzcache/cursors-found-slow-queries/', + 'There are cursors in the plan cache. This is neither good nor bad, but it is a thing. Cursors are weird in SQL Server.'); - IF @VersionShowsSpills = 1 - BEGIN - RAISERROR(N'Getting spill information for newer versions of SQL', 0, 1) WITH NOWAIT; - SET @sql += N' - min_spills AS MinSpills, - max_spills AS MaxSpills, - total_spills AS TotalSpills, - CAST(ISNULL(NULLIF(( total_spills * 1. ), 0) / NULLIF(execution_count, 0), 0) AS MONEY) AS AvgSpills,'; - END; - ELSE - BEGIN - RAISERROR(N'Substituting NULLs for spill columns in older versions of SQL', 0, 1) WITH NOWAIT; - SET @sql += N' - NULL AS MinSpills, - NULL AS MaxSpills, - NULL AS TotalSpills, - NULL AS AvgSpills, ' ; - END; - - SET @sql += N' - SUBSTRING(st.text, ( qs.statement_start_offset / 2 ) + 1, ( ( CASE qs.statement_end_offset - WHEN -1 THEN DATALENGTH(st.text) - ELSE qs.statement_end_offset - END - qs.statement_start_offset ) / 2 ) + 1) AS QueryText , ' + @nl ; + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs + WHERE is_cursor = 1 + AND is_optimistic_cursor = 1 + AND SPID = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 4, + 200, + 'Cursors', + 'Optimistic Cursors', + 'https://www.brentozar.com/blitzcache/cursors-found-slow-queries/', + 'There are optimistic cursors in the plan cache, which can harm performance.'); + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs + WHERE is_cursor = 1 + AND is_forward_only_cursor = 0 + AND SPID = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 4, + 200, + 'Cursors', + 'Non-forward Only Cursors', + 'https://www.brentozar.com/blitzcache/cursors-found-slow-queries/', + 'There are non-forward only cursors in the plan cache, which can harm performance.'); - IF @VersionShowsAirQuoteActualPlans = 1 - BEGIN - SET @sql += N' CASE WHEN DATALENGTH(COALESCE(deqps.query_plan,'''')) > DATALENGTH(COALESCE(qp.query_plan,'''')) THEN deqps.query_plan ELSE qp.query_plan END AS QueryPlan, ' + @nl ; - END - ELSE - BEGIN - SET @sql += N' query_plan AS QueryPlan, ' + @nl ; - END + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs + WHERE is_cursor = 1 + AND is_cursor_dynamic = 1 + AND SPID = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 4, + 200, + 'Cursors', + 'Dynamic Cursors', + 'https://www.brentozar.com/blitzcache/cursors-found-slow-queries/', + 'Dynamic Cursors inhibit parallelism!.'); - SET @sql += N' - t.t_TotalWorker, - t.t_TotalElapsed, - t.t_TotalReads, - t.t_TotalExecs, - t.t_TotalWrites, - qs.sql_handle AS SqlHandle, - qs.plan_handle AS PlanHandle, - qs.query_hash AS QueryHash, - qs.query_plan_hash AS QueryPlanHash, - qs.min_worker_time / 1000.0, - qs.max_worker_time / 1000.0, - CASE WHEN qp.query_plan.value(''declare namespace p="http://schemas.microsoft.com/sqlserver/2004/07/showplan";max(//p:RelOp/@Parallel)'', ''float'') > 0 THEN 1 ELSE 0 END, - qs.min_elapsed_time / 1000.0, - qs.max_worker_time / 1000.0, - age_minutes, - age_minutes_lifetime, - @SortOrder '; - - SET @sql += REPLACE(REPLACE(@body, '#view#', 'dm_exec_query_stats'), 'cached_time', 'creation_time') ; + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs + WHERE is_cursor = 1 + AND is_fast_forward_cursor = 1 + AND SPID = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 4, + 200, + 'Cursors', + 'Fast Forward Cursors', + 'https://www.brentozar.com/blitzcache/cursors-found-slow-queries/', + 'Fast forward cursors inhibit parallelism!.'); - SET @sort_filter += CASE @SortOrder WHEN N'cpu' THEN N'AND total_worker_time > 0' - WHEN N'reads' THEN N'AND total_logical_reads > 0' - WHEN N'writes' THEN N'AND total_logical_writes > 0' - WHEN N'duration' THEN N'AND total_elapsed_time > 0' - WHEN N'executions' THEN N'AND execution_count > 0' - /* WHEN N'compiles' THEN N'AND (age_minutes + age_minutes_lifetime) > 0' BGO 2021-01-24 commenting out for https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2772 */ - WHEN N'memory grant' THEN N'AND max_grant_kb > 0' - WHEN N'unused grant' THEN N'AND max_grant_kb > 0' - WHEN N'spills' THEN N'AND max_spills > 0' - /* And now the averages */ - WHEN N'avg cpu' THEN N'AND (total_worker_time / execution_count) > 0' - WHEN N'avg reads' THEN N'AND (total_logical_reads / execution_count) > 0' - WHEN N'avg writes' THEN N'AND (total_logical_writes / execution_count) > 0' - WHEN N'avg duration' THEN N'AND (total_elapsed_time / execution_count) > 0' - WHEN N'avg memory grant' THEN N'AND CASE WHEN max_grant_kb = 0 THEN 0 ELSE (max_grant_kb / execution_count) END > 0' - WHEN N'avg spills' THEN N'AND CASE WHEN total_spills = 0 THEN 0 ELSE (total_spills / execution_count) END > 0' - WHEN N'avg executions' THEN N'AND CASE WHEN execution_count = 0 THEN 0 - WHEN COALESCE(age_minutes, age_minutes_lifetime, 0) = 0 THEN 0 - ELSE CAST((1.00 * execution_count / COALESCE(age_minutes, age_minutes_lifetime)) AS money) - END > 0' - ELSE N' /* No minimum threshold set */ ' - END; + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs + WHERE is_forced_parameterized = 1 + AND SPID = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 5, + 50, + 'Parameterization', + 'Forced Parameterization', + 'https://www.brentozar.com/blitzcache/forced-parameterization/', + 'Execution plans have been compiled with forced parameterization.') ; - SET @sql += REPLACE(@body_where, 'cached_time', 'creation_time') ; + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs p + WHERE p.is_parallel = 1 + AND SPID = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 6, + 200, + 'Execution Plans', + 'Parallel', + 'https://www.brentozar.com/blitzcache/parallel-plans-detected/', + 'Parallel plans detected. These warrant investigation, but are neither good nor bad.') ; - SET @sql += @sort_filter + @nl; - - SET @sql += @body_order + @nl + @nl + @nl; + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs p + WHERE near_parallel = 1 + AND SPID = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 7, + 200, + 'Execution Plans', + 'Nearly Parallel', + 'https://www.brentozar.com/blitzcache/query-cost-near-cost-threshold-parallelism/', + 'Queries near the cost threshold for parallelism. These may go parallel when you least expect it.') ; - IF @SortOrder = 'compiles' - BEGIN - RAISERROR(N'Sorting by compiles', 0, 1) WITH NOWAIT; - SET @sql = REPLACE(@sql, '#sortable#', 'creation_time'); - END; -END; - - -IF (@QueryFilter = 'all' - AND (SELECT COUNT(*) FROM #only_query_hashes) = 0 - AND (SELECT COUNT(*) FROM #ignore_query_hashes) = 0) - AND (@SortOrder NOT IN ('memory grant', 'avg memory grant', 'unused grant', 'duplicate')) /* Issue #3345 added 'duplicate' */ - OR (LEFT(@QueryFilter, 3) = 'pro') -BEGIN - SET @sql += @insert_list; - SET @sql += REPLACE(@plans_triggers_select_list, '#query_type#', 'Stored Procedure') ; + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs p + WHERE plan_warnings = 1 + AND SPID = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 8, + 50, + 'Execution Plans', + 'Plan Warnings', + 'https://www.brentozar.com/blitzcache/query-plan-warnings/', + 'Warnings detected in execution plans. SQL Server is telling you that something bad is going on that requires your attention.') ; - SET @sql += REPLACE(@body, '#view#', 'dm_exec_procedure_stats') ; - SET @sql += @body_where ; + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs p + WHERE long_running = 1 + AND SPID = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 9, + 50, + 'Performance', + 'Long Running Query', + 'https://www.brentozar.com/blitzcache/long-running-queries/', + 'Long running queries have been found. These are queries with an average duration longer than ' + + CAST(@long_running_query_warning_seconds / 1000 / 1000 AS VARCHAR(5)) + + ' second(s). These queries should be investigated for additional tuning options.') ; - IF @IgnoreSystemDBs = 1 - SET @sql += N' AND COALESCE(LOWER(DB_NAME(database_id)), LOWER(CAST(pa.value AS sysname)), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'', ''dbmaintenance'', ''dbadmin'', ''dbatools'') AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ; + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs p + WHERE p.missing_index_count > 0 + AND SPID = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 10, + 50, + 'Performance', + 'Missing Indexes', + 'https://www.brentozar.com/blitzcache/missing-index-request/', + 'Queries found with missing indexes.'); - SET @sql += @sort_filter + @nl; + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs p + WHERE p.downlevel_estimator = 1 + AND SPID = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 13, + 200, + 'Cardinality', + 'Downlevel CE', + 'https://www.brentozar.com/blitzcache/legacy-cardinality-estimator/', + 'A legacy cardinality estimator is being used by one or more queries. Investigate whether you need to be using this cardinality estimator. This may be caused by compatibility levels, global trace flags, or query level trace flags.'); - SET @sql += @body_order + @nl + @nl + @nl ; -END; + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs p + WHERE implicit_conversions = 1 + AND SPID = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 14, + 50, + 'Performance', + 'Implicit Conversions', + 'https://www.brentozar.com/go/implicit', + 'One or more queries are comparing two fields that are not of the same data type.') ; -IF (@v >= 13 - AND @QueryFilter = 'all' - AND (SELECT COUNT(*) FROM #only_query_hashes) = 0 - AND (SELECT COUNT(*) FROM #ignore_query_hashes) = 0) - AND (@SortOrder NOT IN ('memory grant', 'avg memory grant', 'unused grant', 'duplicate')) /* Issue #3345 added 'duplicate' */ - AND (@SortOrder NOT IN ('spills', 'avg spills')) - OR (LEFT(@QueryFilter, 3) = 'fun') -BEGIN - SET @sql += @insert_list; - SET @sql += REPLACE(REPLACE(@plans_triggers_select_list, '#query_type#', 'Function') - , N' - min_spills AS MinSpills, - max_spills AS MaxSpills, - total_spills AS TotalSpills, - CAST(ISNULL(NULLIF(( total_spills * 1. ), 0) / NULLIF(execution_count, 0), 0) AS MONEY) AS AvgSpills, ', - N' - NULL AS MinSpills, - NULL AS MaxSpills, - NULL AS TotalSpills, - NULL AS AvgSpills, ') ; + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs + WHERE busy_loops = 1 + AND SPID = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 16, + 100, + 'Performance', + 'Busy Loops', + 'https://www.brentozar.com/blitzcache/busy-loops/', + 'Operations have been found that are executed 100 times more often than the number of rows returned by each iteration. This is an indicator that something is off in query execution.'); - SET @sql += REPLACE(@body, '#view#', 'dm_exec_function_stats') ; - SET @sql += @body_where ; + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs + WHERE tvf_join = 1 + AND SPID = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 17, + 50, + 'Performance', + 'Function Join', + 'https://www.brentozar.com/blitzcache/tvf-join/', + 'Execution plans have been found that join to table valued functions (TVFs). TVFs produce inaccurate estimates of the number of rows returned and can lead to any number of query plan problems.'); - IF @IgnoreSystemDBs = 1 - SET @sql += N' AND COALESCE(LOWER(DB_NAME(database_id)), LOWER(CAST(pa.value AS sysname)), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'', ''dbmaintenance'', ''dbadmin'', ''dbatools'') AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ; + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs + WHERE compile_timeout = 1 + AND SPID = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 18, + 50, + 'Execution Plans', + 'Compilation Timeout', + 'https://www.brentozar.com/blitzcache/compilation-timeout/', + 'Query compilation timed out for one or more queries. SQL Server did not find a plan that meets acceptable performance criteria in the time allotted so the best guess was returned. There is a very good chance that this plan isn''t even below average - it''s probably terrible.'); - SET @sql += @sort_filter + @nl; + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs + WHERE compile_memory_limit_exceeded = 1 + AND SPID = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 19, + 50, + 'Execution Plans', + 'Compile Memory Limit Exceeded', + 'https://www.brentozar.com/blitzcache/compile-memory-limit-exceeded/', + 'The optimizer has a limited amount of memory available. One or more queries are complex enough that SQL Server was unable to allocate enough memory to fully optimize the query. A best fit plan was found, and it''s probably terrible.'); - SET @sql += @body_order + @nl + @nl + @nl ; -END; + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs + WHERE warning_no_join_predicate = 1 + AND SPID = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 20, + 50, + 'Execution Plans', + 'No Join Predicate', + 'https://www.brentozar.com/blitzcache/no-join-predicate/', + 'Operators in a query have no join predicate. This means that all rows from one table will be matched with all rows from anther table producing a Cartesian product. That''s a whole lot of rows. This may be your goal, but it''s important to investigate why this is happening.'); -/******************************************************************************* - * - * Because the trigger execution count in SQL Server 2008R2 and earlier is not - * correct, we ignore triggers for these versions of SQL Server. If you'd like - * to include trigger numbers, just know that the ExecutionCount, - * PercentExecutions, and ExecutionsPerMinute are wildly inaccurate for - * triggers on these versions of SQL Server. - * - * This is why we can't have nice things. - * - ******************************************************************************/ -IF (@UseTriggersAnyway = 1 OR @v >= 11) - AND (SELECT COUNT(*) FROM #only_query_hashes) = 0 - AND (SELECT COUNT(*) FROM #ignore_query_hashes) = 0 - AND (@QueryFilter = 'all') - AND (@SortOrder NOT IN ('memory grant', 'avg memory grant', 'unused grant', 'duplicate')) /* Issue #3345 added 'duplicate' */ -BEGIN - RAISERROR (N'Adding SQL to collect trigger stats.',0,1) WITH NOWAIT; + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs + WHERE plan_multiple_plans > 0 + AND SPID = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 21, + 200, + 'Execution Plans', + 'Multiple Plans', + 'https://www.brentozar.com/blitzcache/multiple-plans/', + 'Queries exist with multiple execution plans (as determined by query_plan_hash). Investigate possible ways to parameterize these queries or otherwise reduce the plan count.'); - /* Trigger level information from the plan cache */ - SET @sql += @insert_list ; + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs + WHERE unmatched_index_count > 0 + AND SPID = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 22, + 100, + 'Performance', + 'Unmatched Indexes', + 'https://www.brentozar.com/blitzcache/unmatched-indexes', + 'An index could have been used, but SQL Server chose not to use it - likely due to parameterization and filtered indexes.'); - SET @sql += REPLACE(@plans_triggers_select_list, '#query_type#', 'Trigger') ; + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs + WHERE unparameterized_query = 1 + AND SPID = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 23, + 100, + 'Parameterization', + 'Unparameterized Query', + 'https://www.brentozar.com/blitzcache/unparameterized-queries', + 'Unparameterized queries found. These could be ad hoc queries, data exploration, or queries using "OPTIMIZE FOR UNKNOWN".'); - SET @sql += REPLACE(@body, '#view#', 'dm_exec_trigger_stats') ; + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs + WHERE is_trivial = 1 + AND SPID = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 24, + 100, + 'Execution Plans', + 'Trivial Plans', + 'https://www.brentozar.com/blitzcache/trivial-plans', + 'Trivial plans get almost no optimization. If you''re finding these in the top worst queries, something may be going wrong.'); + + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs p + WHERE p.is_forced_serial= 1 + AND SPID = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 25, + 10, + 'Execution Plans', + 'Forced Serialization', + 'https://www.brentozar.com/blitzcache/forced-serialization/', + 'Something in your plan is forcing a serial query. Further investigation is needed if this is not by design.') ; - SET @sql += @body_where ; + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs p + WHERE p.is_key_lookup_expensive= 1 + AND SPID = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 26, + 100, + 'Execution Plans', + 'Expensive Key Lookup', + 'https://www.brentozar.com/blitzcache/expensive-key-lookups/', + 'There''s a key lookup in your plan that costs >=50% of the total plan cost.') ; - IF @IgnoreSystemDBs = 1 - SET @sql += N' AND COALESCE(LOWER(DB_NAME(database_id)), LOWER(CAST(pa.value AS sysname)), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'', ''dbmaintenance'', ''dbadmin'', ''dbatools'') AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ; + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs p + WHERE p.is_remote_query_expensive= 1 + AND SPID = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 28, + 100, + 'Execution Plans', + 'Expensive Remote Query', + 'https://www.brentozar.com/blitzcache/expensive-remote-query/', + 'There''s a remote query in your plan that costs >=50% of the total plan cost.') ; - SET @sql += @sort_filter + @nl; + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs p + WHERE p.trace_flags_session IS NOT NULL + AND SPID = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 29, + 200, + 'Trace Flags', + 'Session Level Trace Flags Enabled', + 'https://www.brentozar.com/blitz/trace-flags-enabled-globally/', + 'Someone is enabling session level Trace Flags in a query.') ; - SET @sql += @body_order + @nl + @nl + @nl ; -END; + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs p + WHERE p.is_unused_grant IS NOT NULL + AND SPID = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 30, + 100, + 'Memory Grant', + 'Unused Memory Grant', + 'https://www.brentozar.com/blitzcache/unused-memory-grants/', + 'Queries have large unused memory grants. This can cause concurrency issues, if queries are waiting a long time to get memory to run.') ; + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs p + WHERE p.function_count > 0 + AND SPID = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 31, + 100, + 'Compute Scalar That References A Function', + 'Calls Functions', + 'https://www.brentozar.com/blitzcache/compute-scalar-functions/', + 'Both of these will force queries to run serially, run at least once per row, and may result in poor cardinality estimates.') ; + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs p + WHERE p.clr_function_count > 0 + AND SPID = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 32, + 100, + 'Compute Scalar That References A CLR Function', + 'Calls CLR Functions', + 'https://www.brentozar.com/blitzcache/compute-scalar-functions/', + 'May force queries to run serially, run at least once per row, and may result in poor cardinality estimates.') ; -SELECT @sort = CASE @SortOrder WHEN N'cpu' THEN N'total_worker_time' - WHEN N'reads' THEN N'total_logical_reads' - WHEN N'writes' THEN N'total_logical_writes' - WHEN N'duration' THEN N'total_elapsed_time' - WHEN N'executions' THEN N'execution_count' - WHEN N'compiles' THEN N'cached_time' - WHEN N'memory grant' THEN N'max_grant_kb' - WHEN N'unused grant' THEN N'max_grant_kb - max_used_grant_kb' - WHEN N'spills' THEN N'max_spills' - WHEN N'duplicate' THEN N'total_worker_time' /* Issue #3345 */ - /* And now the averages */ - WHEN N'avg cpu' THEN N'total_worker_time / execution_count' - WHEN N'avg reads' THEN N'total_logical_reads / execution_count' - WHEN N'avg writes' THEN N'total_logical_writes / execution_count' - WHEN N'avg duration' THEN N'total_elapsed_time / execution_count' - WHEN N'avg memory grant' THEN N'CASE WHEN max_grant_kb = 0 THEN 0 ELSE max_grant_kb / execution_count END' - WHEN N'avg spills' THEN N'CASE WHEN total_spills = 0 THEN 0 ELSE total_spills / execution_count END' - WHEN N'avg executions' THEN N'CASE WHEN execution_count = 0 THEN 0 - WHEN COALESCE(age_minutes, age_minutes_lifetime, 0) = 0 THEN 0 - ELSE CAST((1.00 * execution_count / COALESCE(age_minutes, age_minutes_lifetime)) AS money) - END' - END ; -SELECT @sql = REPLACE(@sql, '#sortable#', @sort); + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs p + WHERE p.is_table_variable = 1 + AND SPID = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 33, + 100, + 'Table Variables detected', + 'Table Variables', + 'https://www.brentozar.com/blitzcache/table-variables/', + 'All modifications are single threaded, and selects have really low row estimates.') ; -SET @sql += N' -SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -INSERT INTO #p (SqlHandle, TotalCPU, TotalReads, TotalDuration, TotalWrites, ExecutionCount) -SELECT SqlHandle, - TotalCPU, - TotalReads, - TotalDuration, - TotalWrites, - ExecutionCount -FROM (SELECT SqlHandle, - TotalCPU, - TotalReads, - TotalDuration, - TotalWrites, - ExecutionCount, - ROW_NUMBER() OVER (PARTITION BY SqlHandle ORDER BY #sortable# DESC) AS rn - FROM ##BlitzCacheProcs - WHERE SPID = @@SPID) AS x -WHERE x.rn = 1 -OPTION (RECOMPILE); + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs p + WHERE p.no_stats_warning = 1 + AND SPID = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 35, + 100, + 'Statistics', + 'Columns With No Statistics', + 'https://www.brentozar.com/blitzcache/columns-no-statistics/', + 'Sometimes this happens with indexed views, other times because auto create stats is turned off.') ; -/* - This block was used to delete duplicate queries, but has been removed. - For more info: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2026 -WITH d AS ( -SELECT SPID, - ROW_NUMBER() OVER (PARTITION BY SqlHandle, QueryHash ORDER BY #sortable# DESC) AS rn -FROM ##BlitzCacheProcs -WHERE SPID = @@SPID -) -DELETE d -WHERE d.rn > 1 -AND SPID = @@SPID -OPTION (RECOMPILE); -*/ -'; + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs p + WHERE p.relop_warnings = 1 + AND SPID = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 36, + 100, + 'Warnings', + 'Operator Warnings', + 'https://www.brentozar.com/blitzcache/query-plan-warnings/', + 'Check the plan for more details.') ; -SELECT @sort = CASE @SortOrder WHEN N'cpu' THEN N'TotalCPU' - WHEN N'reads' THEN N'TotalReads' - WHEN N'writes' THEN N'TotalWrites' - WHEN N'duration' THEN N'TotalDuration' - WHEN N'executions' THEN N'ExecutionCount' - WHEN N'compiles' THEN N'PlanCreationTime' - WHEN N'memory grant' THEN N'MaxGrantKB' - WHEN N'unused grant' THEN N'MaxGrantKB - MaxUsedGrantKB' - WHEN N'spills' THEN N'MaxSpills' - WHEN N'duplicate' THEN N'TotalCPU' /* Issue #3345 */ - /* And now the averages */ - WHEN N'avg cpu' THEN N'TotalCPU / ExecutionCount' - WHEN N'avg reads' THEN N'TotalReads / ExecutionCount' - WHEN N'avg writes' THEN N'TotalWrites / ExecutionCount' - WHEN N'avg duration' THEN N'TotalDuration / ExecutionCount' - WHEN N'avg memory grant' THEN N'AvgMaxMemoryGrant' - WHEN N'avg spills' THEN N'AvgSpills' - WHEN N'avg executions' THEN N'CASE WHEN ExecutionCount = 0 THEN 0 - WHEN COALESCE(age_minutes, age_minutes_lifetime, 0) = 0 THEN 0 - ELSE CAST((1.00 * ExecutionCount / COALESCE(age_minutes, age_minutes_lifetime)) AS money) - END' - END ; + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs p + WHERE p.is_table_scan = 1 + AND SPID = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 37, + 100, + 'Indexes', + 'Table Scans (Heaps)', + 'https://www.brentozar.com/archive/2012/05/video-heaps/', + 'This may not be a problem. Run sp_BlitzIndex for more information.') ; + + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs p + WHERE p.backwards_scan = 1 + AND SPID = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 38, + 200, + 'Indexes', + 'Backwards Scans', + 'https://www.brentozar.com/blitzcache/backwards-scans/', + 'This isn''t always a problem. They can cause serial zones in plans, and may need an index to match sort order.') ; -SELECT @sql = REPLACE(@sql, '#sortable#', @sort); + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs p + WHERE p.forced_index = 1 + AND SPID = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 39, + 100, + 'Indexes', + 'Forced Indexes', + 'https://www.brentozar.com/blitzcache/optimizer-forcing/', + 'This can cause inefficient plans, and will prevent missing index requests.') ; + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs p + WHERE p.forced_seek = 1 + AND SPID = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 40, + 100, + 'Indexes', + 'Forced Seeks', + 'https://www.brentozar.com/blitzcache/optimizer-forcing/', + 'This can cause inefficient plans by taking seek vs scan choice away from the optimizer.') ; -IF @Debug = 1 - BEGIN - PRINT N'Printing dynamic SQL stored in @sql: '; - PRINT SUBSTRING(@sql, 0, 4000); - PRINT SUBSTRING(@sql, 4000, 8000); - PRINT SUBSTRING(@sql, 8000, 12000); - PRINT SUBSTRING(@sql, 12000, 16000); - PRINT SUBSTRING(@sql, 16000, 20000); - PRINT SUBSTRING(@sql, 20000, 24000); - PRINT SUBSTRING(@sql, 24000, 28000); - PRINT SUBSTRING(@sql, 28000, 32000); - PRINT SUBSTRING(@sql, 32000, 36000); - PRINT SUBSTRING(@sql, 36000, 40000); - END; + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs p + WHERE p.forced_scan = 1 + AND SPID = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 40, + 100, + 'Indexes', + 'Forced Scans', + 'https://www.brentozar.com/blitzcache/optimizer-forcing/', + 'This can cause inefficient plans by taking seek vs scan choice away from the optimizer.') ; -RAISERROR(N'Creating temp tables for results and warnings.', 0, 1) WITH NOWAIT; + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs p + WHERE p.columnstore_row_mode = 1 + AND SPID = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 41, + 100, + 'Indexes', + 'ColumnStore Row Mode', + 'https://www.brentozar.com/blitzcache/columnstore-indexes-operating-row-mode/', + 'ColumnStore indexes operating in Row Mode indicate really poor query choices.') ; + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs p + WHERE p.is_computed_scalar = 1 + AND SPID = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 42, + 50, + 'Functions', + 'Computed Column UDF', + 'https://www.brentozar.com/blitzcache/computed-columns-referencing-functions/', + 'This can cause a whole mess of bad serializartion problems.') ; -IF OBJECT_ID('tempdb.dbo.##BlitzCacheResults') IS NULL -BEGIN - CREATE TABLE ##BlitzCacheResults ( - SPID INT, - ID INT IDENTITY(1,1), - CheckID INT, - Priority TINYINT, - FindingsGroup VARCHAR(50), - Finding VARCHAR(500), - URL VARCHAR(200), - Details VARCHAR(4000) - ); -END; -ELSE -BEGIN - RAISERROR(N'Cleaning up old warnings for your SPID', 0, 1) WITH NOWAIT; - DELETE ##BlitzCacheResults - WHERE SPID = @@SPID - OPTION (RECOMPILE) ; -END + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs p + WHERE p.is_sort_expensive = 1 + AND SPID = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 43, + 100, + 'Execution Plans', + 'Expensive Sort', + 'https://www.brentozar.com/blitzcache/expensive-sorts/', + 'There''s a sort in your plan that costs >=50% of the total plan cost.') ; + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs p + WHERE p.is_computed_filter = 1 + AND SPID = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 44, + 50, + 'Functions', + 'Filter UDF', + 'https://www.brentozar.com/blitzcache/compute-scalar-functions/', + 'Someone put a Scalar UDF in the WHERE clause!') ; -IF OBJECT_ID('tempdb.dbo.##BlitzCacheProcs') IS NULL -BEGIN - CREATE TABLE ##BlitzCacheProcs ( - SPID INT , - QueryType NVARCHAR(258), - DatabaseName sysname, - AverageCPU DECIMAL(38,4), - AverageCPUPerMinute DECIMAL(38,4), - TotalCPU DECIMAL(38,4), - PercentCPUByType MONEY, - PercentCPU MONEY, - AverageDuration DECIMAL(38,4), - TotalDuration DECIMAL(38,4), - PercentDuration MONEY, - PercentDurationByType MONEY, - AverageReads BIGINT, - TotalReads BIGINT, - PercentReads MONEY, - PercentReadsByType MONEY, - ExecutionCount BIGINT, - PercentExecutions MONEY, - PercentExecutionsByType MONEY, - ExecutionsPerMinute MONEY, - TotalWrites BIGINT, - AverageWrites MONEY, - PercentWrites MONEY, - PercentWritesByType MONEY, - WritesPerMinute MONEY, - PlanCreationTime DATETIME, - PlanCreationTimeHours AS DATEDIFF(HOUR, PlanCreationTime, SYSDATETIME()), - LastExecutionTime DATETIME, - LastCompletionTime DATETIME, - PlanHandle VARBINARY(64), - [Remove Plan Handle From Cache] AS - CASE WHEN [PlanHandle] IS NOT NULL - THEN 'DBCC FREEPROCCACHE (' + CONVERT(VARCHAR(128), [PlanHandle], 1) + ');' - ELSE 'N/A' END, - SqlHandle VARBINARY(64), - [Remove SQL Handle From Cache] AS - CASE WHEN [SqlHandle] IS NOT NULL - THEN 'DBCC FREEPROCCACHE (' + CONVERT(VARCHAR(128), [SqlHandle], 1) + ');' - ELSE 'N/A' END, - [SQL Handle More Info] AS - CASE WHEN [SqlHandle] IS NOT NULL - THEN 'EXEC sp_BlitzCache @OnlySqlHandles = ''' + CONVERT(VARCHAR(128), [SqlHandle], 1) + '''; ' - ELSE 'N/A' END, - QueryHash BINARY(8), - [Query Hash More Info] AS - CASE WHEN [QueryHash] IS NOT NULL - THEN 'EXEC sp_BlitzCache @OnlyQueryHashes = ''' + CONVERT(VARCHAR(32), [QueryHash], 1) + '''; ' - ELSE 'N/A' END, - QueryPlanHash BINARY(8), - StatementStartOffset INT, - StatementEndOffset INT, - PlanGenerationNum BIGINT, - MinReturnedRows BIGINT, - MaxReturnedRows BIGINT, - AverageReturnedRows MONEY, - TotalReturnedRows BIGINT, - LastReturnedRows BIGINT, - MinGrantKB BIGINT, - MaxGrantKB BIGINT, - MinUsedGrantKB BIGINT, - MaxUsedGrantKB BIGINT, - PercentMemoryGrantUsed MONEY, - AvgMaxMemoryGrant MONEY, - MinSpills BIGINT, - MaxSpills BIGINT, - TotalSpills BIGINT, - AvgSpills MONEY, - QueryText NVARCHAR(MAX), - QueryPlan XML, - /* these next four columns are the total for the type of query. - don't actually use them for anything apart from math by type. - */ - TotalWorkerTimeForType BIGINT, - TotalElapsedTimeForType BIGINT, - TotalReadsForType BIGINT, - TotalExecutionCountForType BIGINT, - TotalWritesForType BIGINT, - NumberOfPlans INT, - NumberOfDistinctPlans INT, - SerialDesiredMemory FLOAT, - SerialRequiredMemory FLOAT, - CachedPlanSize FLOAT, - CompileTime FLOAT, - CompileCPU FLOAT , - CompileMemory FLOAT , - MaxCompileMemory FLOAT , - min_worker_time BIGINT, - max_worker_time BIGINT, - is_forced_plan BIT, - is_forced_parameterized BIT, - is_cursor BIT, - is_optimistic_cursor BIT, - is_forward_only_cursor BIT, - is_fast_forward_cursor BIT, - is_cursor_dynamic BIT, - is_parallel BIT, - is_forced_serial BIT, - is_key_lookup_expensive BIT, - key_lookup_cost FLOAT, - is_remote_query_expensive BIT, - remote_query_cost FLOAT, - frequent_execution BIT, - parameter_sniffing BIT, - unparameterized_query BIT, - near_parallel BIT, - plan_warnings BIT, - plan_multiple_plans INT, - long_running BIT, - downlevel_estimator BIT, - implicit_conversions BIT, - busy_loops BIT, - tvf_join BIT, - tvf_estimate BIT, - compile_timeout BIT, - compile_memory_limit_exceeded BIT, - warning_no_join_predicate BIT, - QueryPlanCost FLOAT, - missing_index_count INT, - unmatched_index_count INT, - min_elapsed_time BIGINT, - max_elapsed_time BIGINT, - age_minutes MONEY, - age_minutes_lifetime MONEY, - is_trivial BIT, - trace_flags_session VARCHAR(1000), - is_unused_grant BIT, - function_count INT, - clr_function_count INT, - is_table_variable BIT, - no_stats_warning BIT, - relop_warnings BIT, - is_table_scan BIT, - backwards_scan BIT, - forced_index BIT, - forced_seek BIT, - forced_scan BIT, - columnstore_row_mode BIT, - is_computed_scalar BIT , - is_sort_expensive BIT, - sort_cost FLOAT, - is_computed_filter BIT, - op_name VARCHAR(100) NULL, - index_insert_count INT NULL, - index_update_count INT NULL, - index_delete_count INT NULL, - cx_insert_count INT NULL, - cx_update_count INT NULL, - cx_delete_count INT NULL, - table_insert_count INT NULL, - table_update_count INT NULL, - table_delete_count INT NULL, - index_ops AS (index_insert_count + index_update_count + index_delete_count + - cx_insert_count + cx_update_count + cx_delete_count + - table_insert_count + table_update_count + table_delete_count), - is_row_level BIT, - is_spatial BIT, - index_dml BIT, - table_dml BIT, - long_running_low_cpu BIT, - low_cost_high_cpu BIT, - stale_stats BIT, - is_adaptive BIT, - index_spool_cost FLOAT, - index_spool_rows FLOAT, - table_spool_cost FLOAT, - table_spool_rows FLOAT, - is_spool_expensive BIT, - is_spool_more_rows BIT, - is_table_spool_expensive BIT, - is_table_spool_more_rows BIT, - estimated_rows FLOAT, - is_bad_estimate BIT, - is_paul_white_electric BIT, - is_row_goal BIT, - is_big_spills BIT, - is_mstvf BIT, - is_mm_join BIT, - is_nonsargable BIT, - select_with_writes BIT, - implicit_conversion_info XML, - cached_execution_parameters XML, - missing_indexes XML, - SetOptions VARCHAR(MAX), - Warnings VARCHAR(MAX), - Pattern NVARCHAR(20) - ); -END; -ELSE -BEGIN - RAISERROR(N'Cleaning up old plans for your SPID', 0, 1) WITH NOWAIT; - DELETE ##BlitzCacheProcs - WHERE SPID = @@SPID - OPTION (RECOMPILE) ; -END + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs p + WHERE p.index_ops >= 5 + AND SPID = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 45, + 100, + 'Indexes', + '>= 5 Indexes Modified', + 'https://www.brentozar.com/blitzcache/many-indexes-modified/', + 'This can cause lots of hidden I/O -- Run sp_BlitzIndex for more information.') ; -IF @Reanalyze = 0 -BEGIN - RAISERROR('Collecting execution plan information.', 0, 1) WITH NOWAIT; + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs p + WHERE p.is_row_level = 1 + AND SPID = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 46, + 200, + 'Complexity', + 'Row Level Security', + 'https://www.brentozar.com/blitzcache/row-level-security/', + 'You may see a lot of confusing junk in your query plan.') ; - EXEC sp_executesql @sql, N'@Top INT, @min_duration INT, @min_back INT, @SortOrder NVARCHAR(20)', @Top, @DurationFilter_i, @MinutesBack, @SortOrder; -END; + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs p + WHERE p.is_spatial = 1 + AND SPID = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 47, + 200, + 'Complexity', + 'Spatial Index', + 'https://www.brentozar.com/blitzcache/spatial-indexes/', + 'Purely informational.') ; -IF @SkipAnalysis = 1 - BEGIN - RAISERROR(N'Skipping analysis, going to results', 0, 1) WITH NOWAIT; - GOTO Results ; - END; + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs p + WHERE p.index_dml = 1 + AND SPID = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 48, + 150, + 'Complexity', + 'Index DML', + 'https://www.brentozar.com/blitzcache/index-dml/', + 'This can cause recompiles and stuff.') ; + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs p + WHERE p.table_dml = 1 + AND SPID = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 49, + 150, + 'Complexity', + 'Table DML', + 'https://www.brentozar.com/blitzcache/table-dml/', + 'This can cause recompiles and stuff.') ; -/* Update ##BlitzCacheProcs to get Stored Proc info - * This should get totals for all statements in a Stored Proc - */ -RAISERROR(N'Attempting to aggregate stored proc info from separate statements', 0, 1) WITH NOWAIT; -;WITH agg AS ( - SELECT - b.SqlHandle, - SUM(b.MinReturnedRows) AS MinReturnedRows, - SUM(b.MaxReturnedRows) AS MaxReturnedRows, - SUM(b.AverageReturnedRows) AS AverageReturnedRows, - SUM(b.TotalReturnedRows) AS TotalReturnedRows, - SUM(b.LastReturnedRows) AS LastReturnedRows, - SUM(b.MinGrantKB) AS MinGrantKB, - SUM(b.MaxGrantKB) AS MaxGrantKB, - SUM(b.MinUsedGrantKB) AS MinUsedGrantKB, - SUM(b.MaxUsedGrantKB) AS MaxUsedGrantKB, - SUM(b.MinSpills) AS MinSpills, - SUM(b.MaxSpills) AS MaxSpills, - SUM(b.TotalSpills) AS TotalSpills - FROM ##BlitzCacheProcs b - WHERE b.SPID = @@SPID - AND b.QueryHash IS NOT NULL - GROUP BY b.SqlHandle -) -UPDATE b - SET - b.MinReturnedRows = b2.MinReturnedRows, - b.MaxReturnedRows = b2.MaxReturnedRows, - b.AverageReturnedRows = b2.AverageReturnedRows, - b.TotalReturnedRows = b2.TotalReturnedRows, - b.LastReturnedRows = b2.LastReturnedRows, - b.MinGrantKB = b2.MinGrantKB, - b.MaxGrantKB = b2.MaxGrantKB, - b.MinUsedGrantKB = b2.MinUsedGrantKB, - b.MaxUsedGrantKB = b2.MaxUsedGrantKB, - b.MinSpills = b2.MinSpills, - b.MaxSpills = b2.MaxSpills, - b.TotalSpills = b2.TotalSpills -FROM ##BlitzCacheProcs b -JOIN agg b2 -ON b2.SqlHandle = b.SqlHandle -WHERE b.QueryHash IS NULL -AND b.SPID = @@SPID -OPTION (RECOMPILE) ; + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs p + WHERE p.long_running_low_cpu = 1 + AND SPID = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 50, + 150, + 'Blocking', + 'Long Running Low CPU', + 'https://www.brentozar.com/blitzcache/long-running-low-cpu/', + 'This can be a sign of blocking, linked servers, or poor client application code (ASYNC_NETWORK_IO).') ; -/* Compute the total CPU, etc across our active set of the plan cache. - * Yes, there's a flaw - this doesn't include anything outside of our @Top - * metric. - */ -RAISERROR('Computing CPU, duration, read, and write metrics', 0, 1) WITH NOWAIT; -DECLARE @total_duration BIGINT, - @total_cpu BIGINT, - @total_reads BIGINT, - @total_writes BIGINT, - @total_execution_count BIGINT; + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs p + WHERE p.low_cost_high_cpu = 1 + AND SPID = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 51, + 150, + 'Complexity', + 'Low Cost Query With High CPU', + 'https://www.brentozar.com/blitzcache/low-cost-high-cpu/', + 'This can be a sign of functions or Dynamic SQL that calls black-box code.') ; -SELECT @total_cpu = SUM(TotalCPU), - @total_duration = SUM(TotalDuration), - @total_reads = SUM(TotalReads), - @total_writes = SUM(TotalWrites), - @total_execution_count = SUM(ExecutionCount) -FROM #p -OPTION (RECOMPILE) ; + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs p + WHERE p.stale_stats = 1 + AND SPID = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 52, + 150, + 'Statistics', + 'Statistics used have > 100k modifications in the last 7 days', + 'https://www.brentozar.com/blitzcache/stale-statistics/', + 'Ever heard of updating statistics?') ; -DECLARE @cr NVARCHAR(1) = NCHAR(13); -DECLARE @lf NVARCHAR(1) = NCHAR(10); -DECLARE @tab NVARCHAR(1) = NCHAR(9); + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs p + WHERE p.is_adaptive = 1 + AND SPID = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 53, + 200, + 'Complexity', + 'Adaptive joins', + 'https://www.brentozar.com/blitzcache/adaptive-joins/', + 'This join will sometimes do seeks, and sometimes do scans.') ; -/* Update CPU percentage for stored procedures */ -RAISERROR(N'Update CPU percentage for stored procedures', 0, 1) WITH NOWAIT; -UPDATE ##BlitzCacheProcs -SET PercentCPU = y.PercentCPU, - PercentDuration = y.PercentDuration, - PercentReads = y.PercentReads, - PercentWrites = y.PercentWrites, - PercentExecutions = y.PercentExecutions, - ExecutionsPerMinute = y.ExecutionsPerMinute, - /* Strip newlines and tabs. Tabs are replaced with multiple spaces - so that the later whitespace trim will completely eliminate them - */ - QueryText = REPLACE(REPLACE(REPLACE(QueryText, @cr, ' '), @lf, ' '), @tab, ' ') -FROM ( - SELECT PlanHandle, - CASE @total_cpu WHEN 0 THEN 0 - ELSE CAST((100. * TotalCPU) / @total_cpu AS MONEY) END AS PercentCPU, - CASE @total_duration WHEN 0 THEN 0 - ELSE CAST((100. * TotalDuration) / @total_duration AS MONEY) END AS PercentDuration, - CASE @total_reads WHEN 0 THEN 0 - ELSE CAST((100. * TotalReads) / @total_reads AS MONEY) END AS PercentReads, - CASE @total_writes WHEN 0 THEN 0 - ELSE CAST((100. * TotalWrites) / @total_writes AS MONEY) END AS PercentWrites, - CASE @total_execution_count WHEN 0 THEN 0 - ELSE CAST((100. * ExecutionCount) / @total_execution_count AS MONEY) END AS PercentExecutions, - CASE DATEDIFF(mi, PlanCreationTime, LastExecutionTime) - WHEN 0 THEN 0 - ELSE CAST((1.00 * ExecutionCount / DATEDIFF(mi, PlanCreationTime, LastExecutionTime)) AS MONEY) - END AS ExecutionsPerMinute - FROM ( - SELECT PlanHandle, - TotalCPU, - TotalDuration, - TotalReads, - TotalWrites, - ExecutionCount, - PlanCreationTime, - LastExecutionTime - FROM ##BlitzCacheProcs - WHERE PlanHandle IS NOT NULL - AND SPID = @@SPID - GROUP BY PlanHandle, - TotalCPU, - TotalDuration, - TotalReads, - TotalWrites, - ExecutionCount, - PlanCreationTime, - LastExecutionTime - ) AS x -) AS y -WHERE ##BlitzCacheProcs.PlanHandle = y.PlanHandle - AND ##BlitzCacheProcs.PlanHandle IS NOT NULL - AND ##BlitzCacheProcs.SPID = @@SPID -OPTION (RECOMPILE) ; + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs p + WHERE p.is_spool_expensive = 1 + ) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 54, + 150, + 'Indexes', + 'Expensive Index Spool', + 'https://www.brentozar.com/blitzcache/eager-index-spools/', + 'Check operator predicates and output for index definition guidance') ; + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs p + WHERE p.is_spool_more_rows = 1 + ) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 55, + 150, + 'Indexes', + 'Large Index Row Spool', + 'https://www.brentozar.com/blitzcache/eager-index-spools/', + 'Check operator predicates and output for index definition guidance') ; + + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs p + WHERE p.is_bad_estimate = 1 + ) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 56, + 100, + 'Complexity', + 'Row Estimate Mismatch', + 'https://www.brentozar.com/blitzcache/bad-estimates/', + 'Estimated rows are different from average rows by a factor of 10000. This may indicate a performance problem if mismatches occur regularly') ; + + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs p + WHERE p.is_paul_white_electric = 1 + ) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 57, + 200, + 'Is Paul White Electric?', + 'This query has a Switch operator in it!', + 'https://www.sql.kiwi/2013/06/hello-operator-my-switch-is-bored.html', + 'You should email this query plan to Paul: SQLkiwi at gmail dot com') ; -RAISERROR(N'Gather percentage information from grouped results', 0, 1) WITH NOWAIT; -UPDATE ##BlitzCacheProcs -SET PercentCPU = y.PercentCPU, - PercentDuration = y.PercentDuration, - PercentReads = y.PercentReads, - PercentWrites = y.PercentWrites, - PercentExecutions = y.PercentExecutions, - ExecutionsPerMinute = y.ExecutionsPerMinute, - /* Strip newlines and tabs. Tabs are replaced with multiple spaces - so that the later whitespace trim will completely eliminate them - */ - QueryText = REPLACE(REPLACE(REPLACE(QueryText, @cr, ' '), @lf, ' '), @tab, ' ') -FROM ( - SELECT DatabaseName, - SqlHandle, - QueryHash, - CASE @total_cpu WHEN 0 THEN 0 - ELSE CAST((100. * TotalCPU) / @total_cpu AS MONEY) END AS PercentCPU, - CASE @total_duration WHEN 0 THEN 0 - ELSE CAST((100. * TotalDuration) / @total_duration AS MONEY) END AS PercentDuration, - CASE @total_reads WHEN 0 THEN 0 - ELSE CAST((100. * TotalReads) / @total_reads AS MONEY) END AS PercentReads, - CASE @total_writes WHEN 0 THEN 0 - ELSE CAST((100. * TotalWrites) / @total_writes AS MONEY) END AS PercentWrites, - CASE @total_execution_count WHEN 0 THEN 0 - ELSE CAST((100. * ExecutionCount) / @total_execution_count AS MONEY) END AS PercentExecutions, - CASE DATEDIFF(mi, PlanCreationTime, LastExecutionTime) - WHEN 0 THEN 0 - ELSE CAST((1.00 * ExecutionCount / DATEDIFF(mi, PlanCreationTime, LastExecutionTime)) AS MONEY) - END AS ExecutionsPerMinute - FROM ( - SELECT DatabaseName, - SqlHandle, - QueryHash, - TotalCPU, - TotalDuration, - TotalReads, - TotalWrites, - ExecutionCount, - PlanCreationTime, - LastExecutionTime - FROM ##BlitzCacheProcs - WHERE SPID = @@SPID - GROUP BY DatabaseName, - SqlHandle, - QueryHash, - TotalCPU, - TotalDuration, - TotalReads, - TotalWrites, - ExecutionCount, - PlanCreationTime, - LastExecutionTime - ) AS x -) AS y -WHERE ##BlitzCacheProcs.SqlHandle = y.SqlHandle - AND ##BlitzCacheProcs.QueryHash = y.QueryHash - AND ##BlitzCacheProcs.DatabaseName = y.DatabaseName - AND ##BlitzCacheProcs.PlanHandle IS NULL -OPTION (RECOMPILE) ; + IF @v >= 14 OR (@v = 13 AND @build >= 5026) + BEGIN + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + SELECT + @@SPID, + 997, + 200, + 'Database Level Statistics', + 'The database ' + sa.[Database] + ' last had a stats update on ' + CONVERT(NVARCHAR(10), CONVERT(DATE, MAX(sa.LastUpdate))) + ' and has ' + CONVERT(NVARCHAR(10), AVG(sa.ModificationCount)) + ' modifications on average.' AS [Finding], + 'https://www.brentozar.com/blitzcache/stale-statistics/' AS URL, + 'Consider updating statistics more frequently,' AS [Details] + FROM #stats_agg AS sa + GROUP BY sa.[Database] + HAVING MAX(sa.LastUpdate) <= DATEADD(DAY, -7, SYSDATETIME()) + AND AVG(sa.ModificationCount) >= 100000; + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs p + WHERE p.is_row_goal = 1 + ) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 58, + 200, + 'Complexity', + 'Row Goals', + 'https://www.brentozar.com/go/rowgoals/', + 'This query had row goals introduced, which can be good or bad, and should be investigated for high read queries.') ; -/* Testing using XML nodes to speed up processing */ -RAISERROR(N'Begin XML nodes processing', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -SELECT QueryHash , - SqlHandle , - PlanHandle, - q.n.query('.') AS statement, - 0 AS is_cursor -INTO #statements -FROM ##BlitzCacheProcs p - CROSS APPLY p.QueryPlan.nodes('//p:StmtSimple') AS q(n) -WHERE p.SPID = @@SPID -OPTION (RECOMPILE) ; + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs p + WHERE p.is_big_spills = 1 + ) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 59, + 100, + 'TempDB', + '>500mb Spills', + 'https://www.brentozar.com/blitzcache/tempdb-spills/', + 'This query spills >500mb to tempdb on average. One way or another, this query didn''t get enough memory') ; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -INSERT #statements -SELECT QueryHash , - SqlHandle , - PlanHandle, - q.n.query('.') AS statement, - 1 AS is_cursor -FROM ##BlitzCacheProcs p - CROSS APPLY p.QueryPlan.nodes('//p:StmtCursor') AS q(n) -WHERE p.SPID = @@SPID -OPTION (RECOMPILE) ; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -SELECT QueryHash , - SqlHandle , - q.n.query('.') AS query_plan -INTO #query_plan -FROM #statements p - CROSS APPLY p.statement.nodes('//p:QueryPlan') AS q(n) -OPTION (RECOMPILE) ; + END; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -SELECT QueryHash , - SqlHandle , - q.n.query('.') AS relop -INTO #relop -FROM #query_plan p - CROSS APPLY p.query_plan.nodes('//p:RelOp') AS q(n) -OPTION (RECOMPILE) ; + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs p + WHERE p.is_mstvf = 1 + ) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 60, + 100, + 'Functions', + 'MSTVFs', + 'https://www.brentozar.com/blitzcache/tvf-join/', + 'Execution plans have been found that join to table valued functions (TVFs). TVFs produce inaccurate estimates of the number of rows returned and can lead to any number of query plan problems.'); --- high level plan stuff -RAISERROR(N'Gathering high level plan information', 0, 1) WITH NOWAIT; -UPDATE ##BlitzCacheProcs -SET NumberOfDistinctPlans = distinct_plan_count, - NumberOfPlans = number_of_plans , - plan_multiple_plans = CASE WHEN distinct_plan_count < number_of_plans THEN number_of_plans END -FROM - ( - SELECT - DatabaseName = - DB_NAME(CONVERT(int, pa.value)), - QueryHash = - qs.query_hash, - number_of_plans = - COUNT_BIG(qs.query_plan_hash), - distinct_plan_count = - COUNT_BIG(DISTINCT qs.query_plan_hash) - FROM sys.dm_exec_query_stats AS qs - CROSS APPLY sys.dm_exec_plan_attributes(qs.plan_handle) pa - WHERE pa.attribute = 'dbid' - GROUP BY - DB_NAME(CONVERT(int, pa.value)), - qs.query_hash -) AS x -WHERE ##BlitzCacheProcs.QueryHash = x.QueryHash -AND ##BlitzCacheProcs.DatabaseName = x.DatabaseName -OPTION (RECOMPILE) ; + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs p + WHERE p.is_mm_join = 1 + ) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 61, + 100, + 'Complexity', + 'Many to Many Merge', + 'https://www.brentozar.com/archive/2018/04/many-mysteries-merge-joins/', + 'These use secret worktables that could be doing lots of reads. Occurs when join inputs aren''t known to be unique. Can be really bad when parallel.'); --- query level checks -RAISERROR(N'Performing query level checks', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE ##BlitzCacheProcs -SET missing_index_count = query_plan.value('count(//p:QueryPlan/p:MissingIndexes/p:MissingIndexGroup)', 'int') , - unmatched_index_count = CASE WHEN is_trivial <> 1 THEN query_plan.value('count(//p:QueryPlan/p:UnmatchedIndexes/p:Parameterization/p:Object)', 'int') END , - SerialDesiredMemory = query_plan.value('sum(//p:QueryPlan/p:MemoryGrantInfo/@SerialDesiredMemory)', 'float') , - SerialRequiredMemory = query_plan.value('sum(//p:QueryPlan/p:MemoryGrantInfo/@SerialRequiredMemory)', 'float'), - CachedPlanSize = query_plan.value('sum(//p:QueryPlan/@CachedPlanSize)', 'float') , - CompileTime = query_plan.value('sum(//p:QueryPlan/@CompileTime)', 'float') , - CompileCPU = query_plan.value('sum(//p:QueryPlan/@CompileCPU)', 'float') , - CompileMemory = query_plan.value('sum(//p:QueryPlan/@CompileMemory)', 'float'), - MaxCompileMemory = query_plan.value('sum(//p:QueryPlan/p:OptimizerHardwareDependentProperties/@MaxCompileMemory)', 'float') -FROM #query_plan qp -WHERE qp.QueryHash = ##BlitzCacheProcs.QueryHash -AND qp.SqlHandle = ##BlitzCacheProcs.SqlHandle -AND SPID = @@SPID -OPTION (RECOMPILE); - --- statement level checks -RAISERROR(N'Performing compile timeout checks', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET compile_timeout = 1 -FROM #statements s -JOIN ##BlitzCacheProcs b -ON s.QueryHash = b.QueryHash -AND SPID = @@SPID -WHERE statement.exist('/p:StmtSimple/@StatementOptmEarlyAbortReason[.="TimeOut"]') = 1 -OPTION (RECOMPILE); + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs p + WHERE p.is_nonsargable = 1 + ) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 62, + 50, + 'Non-SARGable queries', + 'non-SARGables', + 'https://www.brentozar.com/blitzcache/non-sargable-predicates/', + 'Looks for intrinsic functions and expressions as predicates, and leading wildcard LIKE searches.'); -RAISERROR(N'Performing compile memory limit exceeded checks', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET compile_memory_limit_exceeded = 1 -FROM #statements s -JOIN ##BlitzCacheProcs b -ON s.QueryHash = b.QueryHash -AND SPID = @@SPID -WHERE statement.exist('/p:StmtSimple/@StatementOptmEarlyAbortReason[.="MemoryLimitExceeded"]') = 1 -OPTION (RECOMPILE); + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs p + WHERE CompileTime > 5000 + ) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 63, + 100, + 'Complexity', + 'Long Compile Time', + 'https://www.brentozar.com/blitzcache/high-compilers/', + 'Queries are taking >5 seconds to compile. This can be normal for large plans, but be careful if they compile frequently'); -IF @ExpertMode > 0 -BEGIN -RAISERROR(N'Performing unparameterized query checks', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p), -unparameterized_query AS ( - SELECT s.QueryHash, - unparameterized_query = CASE WHEN statement.exist('//p:StmtSimple[@StatementOptmLevel[.="FULL"]]/p:QueryPlan/p:ParameterList') = 1 AND - statement.exist('//p:StmtSimple[@StatementOptmLevel[.="FULL"]]/p:QueryPlan/p:ParameterList/p:ColumnReference') = 0 THEN 1 - WHEN statement.exist('//p:StmtSimple[@StatementOptmLevel[.="FULL"]]/p:QueryPlan/p:ParameterList') = 0 AND - statement.exist('//p:StmtSimple[@StatementOptmLevel[.="FULL"]]/*/p:RelOp/descendant::p:ScalarOperator/p:Identifier/p:ColumnReference[contains(@Column, "@")]') = 1 THEN 1 - END - FROM #statements AS s - ) -UPDATE b -SET b.unparameterized_query = u.unparameterized_query -FROM ##BlitzCacheProcs b -JOIN unparameterized_query u -ON u.QueryHash = b.QueryHash -AND SPID = @@SPID -WHERE u.unparameterized_query = 1 -OPTION (RECOMPILE); -END; + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs p + WHERE CompileCPU > 5000 + ) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 64, + 50, + 'Complexity', + 'High Compile CPU', + 'https://www.brentozar.com/blitzcache/high-compilers/', + 'Queries taking >5 seconds of CPU to compile. If CPU is high and plans like this compile frequently, they may be related'); + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs p + WHERE CompileMemory > 1024 + AND ((CompileMemory) / (1 * CASE WHEN MaxCompileMemory = 0 THEN 1 ELSE MaxCompileMemory END) * 100.) >= 10. + ) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 65, + 50, + 'Complexity', + 'High Compile Memory', + 'https://www.brentozar.com/blitzcache/high-compilers/', + 'Queries taking 10% of Max Compile Memory. If you see high RESOURCE_SEMAPHORE_QUERY_COMPILE waits, these may be related'); -IF @ExpertMode > 0 -BEGIN -RAISERROR(N'Performing index DML checks', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p), -index_dml AS ( - SELECT s.QueryHash, - index_dml = CASE WHEN statement.exist('//p:StmtSimple/@StatementType[.="CREATE INDEX"]') = 1 THEN 1 - WHEN statement.exist('//p:StmtSimple/@StatementType[.="DROP INDEX"]') = 1 THEN 1 - END - FROM #statements s - ) - UPDATE b - SET b.index_dml = i.index_dml - FROM ##BlitzCacheProcs AS b - JOIN index_dml i - ON i.QueryHash = b.QueryHash - WHERE i.index_dml = 1 - AND b.SPID = @@SPID - OPTION (RECOMPILE); -END; + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs p + WHERE p.select_with_writes = 1 + ) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 66, + 50, + 'Complexity', + 'Selects w/ Writes', + 'https://dba.stackexchange.com/questions/191825/', + 'This is thrown when reads cause writes that are not already flagged as big spills (2016+) or index spools.'); + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs p + WHERE p.is_table_spool_expensive = 1 + ) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 67, + 150, + 'Expensive Table Spool', + 'You have a table spool, this is usually a sign that queries are doing unnecessary work', + 'https://sqlperformance.com/2019/09/sql-performance/nested-loops-joins-performance-spools', + 'Check for non-SARGable predicates, or a lot of work being done inside a nested loops join') ; -IF @ExpertMode > 0 -BEGIN -RAISERROR(N'Performing table DML checks', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p), -table_dml AS ( - SELECT s.QueryHash, - table_dml = CASE WHEN statement.exist('//p:StmtSimple/@StatementType[.="CREATE TABLE"]') = 1 THEN 1 - WHEN statement.exist('//p:StmtSimple/@StatementType[.="DROP OBJECT"]') = 1 THEN 1 - END - FROM #statements AS s - ) - UPDATE b - SET b.table_dml = t.table_dml - FROM ##BlitzCacheProcs AS b - JOIN table_dml t - ON t.QueryHash = b.QueryHash - WHERE t.table_dml = 1 - AND b.SPID = @@SPID - OPTION (RECOMPILE); -END; + IF EXISTS (SELECT 1/0 + FROM ##BlitzCacheProcs p + WHERE p.is_table_spool_more_rows = 1 + ) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 68, + 150, + 'Table Spools Many Rows', + 'You have a table spool that spools more rows than the query returns', + 'https://sqlperformance.com/2019/09/sql-performance/nested-loops-joins-performance-spools', + 'Check for non-SARGable predicates, or a lot of work being done inside a nested loops join'); + IF EXISTS (SELECT 1/0 + FROM #plan_creation p + WHERE (p.percent_24 > 0) + AND SPID = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + SELECT SPID, + 999, + CASE WHEN ISNULL(p.percent_24, 0) > 75 THEN 1 ELSE 254 END AS Priority, + 'Plan Cache Information', + CASE WHEN ISNULL(p.percent_24, 0) > 75 THEN 'Plan Cache Instability' ELSE 'Plan Cache Stability' END AS Finding, + 'https://www.brentozar.com/archive/2018/07/tsql2sday-how-much-plan-cache-history-do-you-have/', + 'You have ' + CONVERT(NVARCHAR(10), ISNULL(p.total_plans, 0)) + + ' total plans in your cache, with ' + + CONVERT(NVARCHAR(10), ISNULL(p.percent_24, 0)) + + '% plans created in the past 24 hours, ' + + CONVERT(NVARCHAR(10), ISNULL(p.percent_4, 0)) + + '% created in the past 4 hours, and ' + + CONVERT(NVARCHAR(10), ISNULL(p.percent_1, 0)) + + '% created in the past 1 hour. ' + + 'When these percentages are high, it may be a sign of memory pressure or plan cache instability.' + FROM #plan_creation p ; -IF @ExpertMode > 0 -BEGIN -RAISERROR(N'Gathering row estimates', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES ('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) -INSERT INTO #est_rows -SELECT DISTINCT - CONVERT(BINARY(8), RIGHT('0000000000000000' + SUBSTRING(c.n.value('@QueryHash', 'VARCHAR(18)'), 3, 18), 16), 2) AS QueryHash, - c.n.value('(/p:StmtSimple/@StatementEstRows)[1]', 'FLOAT') AS estimated_rows -FROM #statements AS s -CROSS APPLY s.statement.nodes('/p:StmtSimple') AS c(n) -WHERE c.n.exist('/p:StmtSimple[@StatementEstRows > 0]') = 1; + IF EXISTS (SELECT 1/0 + FROM #plan_usage p + WHERE p.percent_duplicate > 5 + AND spid = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + SELECT spid, + 999, + CASE WHEN ISNULL(p.percent_duplicate, 0) > 75 THEN 1 ELSE 254 END AS Priority, + 'Plan Cache Information', + CASE WHEN ISNULL(p.percent_duplicate, 0) > 75 THEN 'Many Duplicate Plans' ELSE 'Duplicate Plans' END AS Finding, + 'https://www.brentozar.com/archive/2018/03/why-multiple-plans-for-one-query-are-bad/', + 'You have ' + CONVERT(NVARCHAR(10), p.total_plans) + + ' plans in your cache, and ' + + CONVERT(NVARCHAR(10), p.percent_duplicate) + + '% are duplicates with more than 5 entries' + + ', meaning similar queries are generating the same plan repeatedly.' + + ' Forced Parameterization may fix the issue. To find troublemakers, use: EXEC sp_BlitzCache @SortOrder = ''query hash''; ' + FROM #plan_usage AS p ; - UPDATE b - SET b.estimated_rows = er.estimated_rows - FROM ##BlitzCacheProcs AS b - JOIN #est_rows er - ON er.QueryHash = b.QueryHash - WHERE b.SPID = @@SPID - AND b.QueryType = 'Statement' - OPTION (RECOMPILE); -END; + IF EXISTS (SELECT 1/0 + FROM #plan_usage p + WHERE p.percent_single > 5 + AND spid = @@SPID) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + SELECT spid, + 999, + CASE WHEN ISNULL(p.percent_single, 0) > 75 THEN 1 ELSE 254 END AS Priority, + 'Plan Cache Information', + CASE WHEN ISNULL(p.percent_single, 0) > 75 THEN 'Many Single-Use Plans' ELSE 'Single-Use Plans' END AS Finding, + 'https://www.brentozar.com/blitz/single-use-plans-procedure-cache/', + 'You have ' + CONVERT(NVARCHAR(10), p.total_plans) + + ' plans in your cache, and ' + + CONVERT(NVARCHAR(10), p.percent_single) + + '% are single use plans' + + ', meaning SQL Server thinks it''s seeing a lot of "new" queries and creating plans for them.' + + ' Forced Parameterization and/or Optimize For Ad Hoc Workloads may fix the issue.' + + 'To find troublemakers, use: EXEC sp_BlitzCache @SortOrder = ''query hash''; ' + FROM #plan_usage AS p ; -RAISERROR(N'Gathering trivial plans', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES ( 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) -UPDATE b -SET b.is_trivial = 1 -FROM ##BlitzCacheProcs AS b -JOIN ( -SELECT s.SqlHandle -FROM #statements AS s -JOIN ( SELECT r.SqlHandle - FROM #relop AS r - WHERE r.relop.exist('//p:RelOp[contains(@LogicalOp, "Scan")]') = 1 ) AS r - ON r.SqlHandle = s.SqlHandle -WHERE s.statement.exist('//p:StmtSimple[@StatementOptmLevel[.="TRIVIAL"]]/p:QueryPlan/p:ParameterList') = 1 -) AS s -ON b.SqlHandle = s.SqlHandle -OPTION (RECOMPILE); + IF @is_tokenstore_big = 1 + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + SELECT @@SPID, + 69, + 10, + N'Large USERSTORE_TOKENPERM cache: ' + CONVERT(NVARCHAR(11), @user_perm_gb_out) + N'GB', + N'The USERSTORE_TOKENPERM is taking up ' + CONVERT(NVARCHAR(11), @user_perm_percent) + + N'% of the buffer pool, and your plan cache seems to be unstable', + N'https://www.brentozar.com/go/userstore', + N'A growing USERSTORE_TOKENPERM cache can cause the plan cache to clear out' + IF @v >= 11 + BEGIN + IF EXISTS (SELECT 1/0 + FROM #trace_flags AS tf + WHERE tf.global_trace_flags IS NOT NULL + ) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 1000, + 255, + 'Global Trace Flags Enabled', + 'You have Global Trace Flags enabled on your server', + 'https://www.brentozar.com/blitz/trace-flags-enabled-globally/', + 'You have the following Global Trace Flags enabled: ' + (SELECT TOP 1 tf.global_trace_flags FROM #trace_flags AS tf WHERE tf.global_trace_flags IS NOT NULL)) ; + END; ---Gather costs -RAISERROR(N'Gathering statement costs', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -INSERT INTO #plan_cost ( QueryPlanCost, SqlHandle, PlanHandle, QueryHash, QueryPlanHash ) -SELECT DISTINCT - statement.value('sum(/p:StmtSimple/@StatementSubTreeCost)', 'float') QueryPlanCost, - s.SqlHandle, - s.PlanHandle, - CONVERT(BINARY(8), RIGHT('0000000000000000' + SUBSTRING(q.n.value('@QueryHash', 'VARCHAR(18)'), 3, 18), 16), 2) AS QueryHash, - CONVERT(BINARY(8), RIGHT('0000000000000000' + SUBSTRING(q.n.value('@QueryPlanHash', 'VARCHAR(18)'), 3, 18), 16), 2) AS QueryPlanHash -FROM #statements s -CROSS APPLY s.statement.nodes('/p:StmtSimple') AS q(n) -WHERE statement.value('sum(/p:StmtSimple/@StatementSubTreeCost)', 'float') > 0 -OPTION (RECOMPILE); + IF NOT EXISTS (SELECT 1/0 + FROM ##BlitzCacheResults AS bcr + WHERE bcr.Priority = 2147483646 + ) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 2147483646, + 255, + 'Need more help?' , + 'Paste your plan on the internet!', + 'http://pastetheplan.com', + 'This makes it easy to share plans and post them to Q&A sites like https://dba.stackexchange.com/!') ; -RAISERROR(N'Updating statement costs', 0, 1) WITH NOWAIT; -WITH pc AS ( - SELECT SUM(DISTINCT pc.QueryPlanCost) AS QueryPlanCostSum, pc.QueryHash, pc.QueryPlanHash, pc.SqlHandle, pc.PlanHandle - FROM #plan_cost AS pc - GROUP BY pc.QueryHash, pc.QueryPlanHash, pc.SqlHandle, pc.PlanHandle -) - UPDATE b - SET b.QueryPlanCost = ISNULL(pc.QueryPlanCostSum, 0) - FROM pc - JOIN ##BlitzCacheProcs b - ON b.SqlHandle = pc.SqlHandle - AND b.QueryHash = pc.QueryHash - WHERE b.QueryType NOT LIKE '%Procedure%' - OPTION (RECOMPILE); -IF EXISTS ( -SELECT 1 -FROM ##BlitzCacheProcs AS b -WHERE b.QueryType LIKE 'Procedure%' -) -BEGIN + IF NOT EXISTS (SELECT 1/0 + FROM ##BlitzCacheResults AS bcr + WHERE bcr.Priority = 2147483647 + ) + INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 2147483647, + 255, + 'Thanks for using sp_BlitzCache!' , + 'From Your Community Volunteers', + 'http://FirstResponderKit.org', + 'We hope you found this tool useful. Current version: ' + @Version + ' released on ' + CONVERT(NVARCHAR(30), @VersionDate) + '.') ; + + END; + + + SELECT Priority, + FindingsGroup, + Finding, + URL, + Details, + CheckID + FROM ##BlitzCacheResults + WHERE SPID = @@SPID + GROUP BY Priority, + FindingsGroup, + Finding, + URL, + Details, + CheckID + ORDER BY Priority ASC, FindingsGroup, Finding, CheckID ASC + OPTION (RECOMPILE); +END; -RAISERROR(N'Gathering stored procedure costs', 0, 1) WITH NOWAIT; -;WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -, QueryCost AS ( - SELECT - DISTINCT - statement.value('sum(/p:StmtSimple/@StatementSubTreeCost)', 'float') AS SubTreeCost, - s.PlanHandle, - s.SqlHandle - FROM #statements AS s - WHERE PlanHandle IS NOT NULL -) -, QueryCostUpdate AS ( - SELECT - SUM(qc.SubTreeCost) OVER (PARTITION BY SqlHandle, PlanHandle) PlanTotalQuery, - qc.PlanHandle, - qc.SqlHandle - FROM QueryCost qc -) -INSERT INTO #proc_costs -SELECT qcu.PlanTotalQuery, PlanHandle, SqlHandle -FROM QueryCostUpdate AS qcu -OPTION (RECOMPILE); +IF @Debug = 1 + BEGIN + + SELECT '##BlitzCacheResults' AS table_name, * + FROM ##BlitzCacheResults + OPTION ( RECOMPILE ); + + SELECT '##BlitzCacheProcs' AS table_name, * + FROM ##BlitzCacheProcs + OPTION ( RECOMPILE ); + + SELECT '#statements' AS table_name, * + FROM #statements AS s + OPTION (RECOMPILE); + SELECT '#query_plan' AS table_name, * + FROM #query_plan AS qp + OPTION (RECOMPILE); + + SELECT '#relop' AS table_name, * + FROM #relop AS r + OPTION (RECOMPILE); -UPDATE b - SET b.QueryPlanCost = ca.PlanTotalQuery -FROM ##BlitzCacheProcs AS b -CROSS APPLY ( - SELECT TOP 1 PlanTotalQuery - FROM #proc_costs qcu - WHERE qcu.PlanHandle = b.PlanHandle - ORDER BY PlanTotalQuery DESC -) ca -WHERE b.QueryType LIKE 'Procedure%' -AND b.SPID = @@SPID -OPTION (RECOMPILE); + SELECT '#only_query_hashes' AS table_name, * + FROM #only_query_hashes + OPTION ( RECOMPILE ); + + SELECT '#ignore_query_hashes' AS table_name, * + FROM #ignore_query_hashes + OPTION ( RECOMPILE ); + + SELECT '#only_sql_handles' AS table_name, * + FROM #only_sql_handles + OPTION ( RECOMPILE ); + + SELECT '#ignore_sql_handles' AS table_name, * + FROM #ignore_sql_handles + OPTION ( RECOMPILE ); + + SELECT '#p' AS table_name, * + FROM #p + OPTION ( RECOMPILE ); + + SELECT '#checkversion' AS table_name, * + FROM #checkversion + OPTION ( RECOMPILE ); + + SELECT '#configuration' AS table_name, * + FROM #configuration + OPTION ( RECOMPILE ); + + SELECT '#stored_proc_info' AS table_name, * + FROM #stored_proc_info + OPTION ( RECOMPILE ); -END; + SELECT '#conversion_info' AS table_name, * + FROM #conversion_info AS ci + OPTION ( RECOMPILE ); + + SELECT '#variable_info' AS table_name, * + FROM #variable_info AS vi + OPTION ( RECOMPILE ); -UPDATE b -SET b.QueryPlanCost = 0.0 -FROM ##BlitzCacheProcs b -WHERE b.QueryPlanCost IS NULL -AND b.SPID = @@SPID -OPTION (RECOMPILE); + SELECT '#missing_index_xml' AS table_name, * + FROM #missing_index_xml AS mix + OPTION ( RECOMPILE ); -RAISERROR(N'Checking for plan warnings', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE ##BlitzCacheProcs -SET plan_warnings = 1 -FROM #query_plan qp -WHERE qp.SqlHandle = ##BlitzCacheProcs.SqlHandle -AND SPID = @@SPID -AND query_plan.exist('/p:QueryPlan/p:Warnings') = 1 -OPTION (RECOMPILE); + SELECT '#missing_index_schema' AS table_name, * + FROM #missing_index_schema AS mis + OPTION ( RECOMPILE ); -RAISERROR(N'Checking for implicit conversion', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE ##BlitzCacheProcs -SET implicit_conversions = 1 -FROM #query_plan qp -WHERE qp.SqlHandle = ##BlitzCacheProcs.SqlHandle -AND SPID = @@SPID -AND query_plan.exist('/p:QueryPlan/p:Warnings/p:PlanAffectingConvert/@Expression[contains(., "CONVERT_IMPLICIT")]') = 1 -OPTION (RECOMPILE); + SELECT '#missing_index_usage' AS table_name, * + FROM #missing_index_usage AS miu + OPTION ( RECOMPILE ); --- operator level checks -IF @ExpertMode > 0 -BEGIN -RAISERROR(N'Performing busy loops checks', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE p -SET busy_loops = CASE WHEN (x.estimated_executions / 100.0) > x.estimated_rows THEN 1 END -FROM ##BlitzCacheProcs p - JOIN ( - SELECT qs.SqlHandle, - relop.value('sum(/p:RelOp/@EstimateRows)', 'float') AS estimated_rows , - relop.value('sum(/p:RelOp/@EstimateRewinds)', 'float') + relop.value('sum(/p:RelOp/@EstimateRebinds)', 'float') + 1.0 AS estimated_executions - FROM #relop qs - ) AS x ON p.SqlHandle = x.SqlHandle -WHERE SPID = @@SPID -OPTION (RECOMPILE); -END; + SELECT '#missing_index_detail' AS table_name, * + FROM #missing_index_detail AS mid + OPTION ( RECOMPILE ); + SELECT '#missing_index_pretty' AS table_name, * + FROM #missing_index_pretty AS mip + OPTION ( RECOMPILE ); -RAISERROR(N'Performing TVF join check', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE p -SET p.tvf_join = CASE WHEN x.tvf_join = 1 THEN 1 END -FROM ##BlitzCacheProcs p - JOIN ( - SELECT r.SqlHandle, - 1 AS tvf_join - FROM #relop AS r - WHERE r.relop.exist('//p:RelOp[(@LogicalOp[.="Table-valued function"])]') = 1 - AND r.relop.exist('//p:RelOp[contains(@LogicalOp, "Join")]') = 1 - ) AS x ON p.SqlHandle = x.SqlHandle -WHERE SPID = @@SPID -OPTION (RECOMPILE); + SELECT '#plan_creation' AS table_name, * + FROM #plan_creation + OPTION ( RECOMPILE ); + + SELECT '#plan_cost' AS table_name, * + FROM #plan_cost + OPTION ( RECOMPILE ); + + SELECT '#proc_costs' AS table_name, * + FROM #proc_costs + OPTION ( RECOMPILE ); + + SELECT '#stats_agg' AS table_name, * + FROM #stats_agg + OPTION ( RECOMPILE ); + + SELECT '#trace_flags' AS table_name, * + FROM #trace_flags + OPTION ( RECOMPILE ); -IF @ExpertMode > 0 -BEGIN -RAISERROR(N'Checking for operator warnings', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -, x AS ( -SELECT r.SqlHandle, - c.n.exist('//p:Warnings[(@NoJoinPredicate[.="1"])]') AS warning_no_join_predicate, - c.n.exist('//p:ColumnsWithNoStatistics') AS no_stats_warning , - c.n.exist('//p:Warnings') AS relop_warnings -FROM #relop AS r -CROSS APPLY r.relop.nodes('/p:RelOp/p:Warnings') AS c(n) -) -UPDATE p -SET p.warning_no_join_predicate = x.warning_no_join_predicate, - p.no_stats_warning = x.no_stats_warning, - p.relop_warnings = x.relop_warnings -FROM ##BlitzCacheProcs AS p -JOIN x ON x.SqlHandle = p.SqlHandle -AND SPID = @@SPID -OPTION (RECOMPILE); -END; + SELECT '#plan_usage' AS table_name, * + FROM #plan_usage + OPTION ( RECOMPILE ); + END; -RAISERROR(N'Checking for table variables', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -, x AS ( -SELECT r.SqlHandle, - c.n.value('substring(@Table, 2, 1)','VARCHAR(100)') AS first_char -FROM #relop r -CROSS APPLY r.relop.nodes('//p:Object') AS c(n) -) -UPDATE p -SET is_table_variable = 1 -FROM ##BlitzCacheProcs AS p -JOIN x ON x.SqlHandle = p.SqlHandle -AND SPID = @@SPID -WHERE x.first_char = '@' -OPTION (RECOMPILE); + IF @OutputTableName IS NOT NULL + --Allow for output to ##DB so don't check for DB or schema name here + GOTO OutputResultsToTable; +RETURN; --Avoid going into the AllSort GOTO -IF @ExpertMode > 0 -BEGIN -RAISERROR(N'Checking for functions', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -, x AS ( -SELECT qs.SqlHandle, - n.fn.value('count(distinct-values(//p:UserDefinedFunction[not(@IsClrFunction)]))', 'INT') AS function_count, - n.fn.value('count(distinct-values(//p:UserDefinedFunction[@IsClrFunction = "1"]))', 'INT') AS clr_function_count -FROM #relop qs -CROSS APPLY relop.nodes('/p:RelOp/p:ComputeScalar/p:DefinedValues/p:DefinedValue/p:ScalarOperator') n(fn) -) -UPDATE p -SET p.function_count = x.function_count, - p.clr_function_count = x.clr_function_count -FROM ##BlitzCacheProcs AS p -JOIN x ON x.SqlHandle = p.SqlHandle -AND SPID = @@SPID -OPTION (RECOMPILE); -END; +/*Begin code to sort by all*/ +AllSorts: +RAISERROR('Beginning all sort loop', 0, 1) WITH NOWAIT; -RAISERROR(N'Checking for expensive key lookups', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE ##BlitzCacheProcs -SET key_lookup_cost = x.key_lookup_cost -FROM ( -SELECT - qs.SqlHandle, - MAX(relop.value('sum(/p:RelOp/@EstimatedTotalSubtreeCost)', 'float')) AS key_lookup_cost -FROM #relop qs -WHERE [relop].exist('/p:RelOp/p:IndexScan[(@Lookup[.="1"])]') = 1 -GROUP BY qs.SqlHandle -) AS x -WHERE ##BlitzCacheProcs.SqlHandle = x.SqlHandle -AND SPID = @@SPID -OPTION (RECOMPILE); +IF ( + @Top > 10 + AND @SkipAnalysis = 0 + AND @BringThePain = 0 + ) + BEGIN + RAISERROR( + ' + You''ve chosen a value greater than 10 to sort the whole plan cache by. + That can take a long time and harm performance. + Please choose a number <= 10, or set @BringThePain = 1 to signify you understand this might be a bad idea. + ', 0, 1) WITH NOWAIT; + RETURN; + END; -RAISERROR(N'Checking for expensive remote queries', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE ##BlitzCacheProcs -SET remote_query_cost = x.remote_query_cost -FROM ( -SELECT - qs.SqlHandle, - MAX(relop.value('sum(/p:RelOp/@EstimatedTotalSubtreeCost)', 'float')) AS remote_query_cost -FROM #relop qs -WHERE [relop].exist('/p:RelOp[(@PhysicalOp[contains(., "Remote")])]') = 1 -GROUP BY qs.SqlHandle -) AS x -WHERE ##BlitzCacheProcs.SqlHandle = x.SqlHandle -AND SPID = @@SPID -OPTION (RECOMPILE); +IF OBJECT_ID('tempdb..#checkversion_allsort') IS NULL + BEGIN + CREATE TABLE #checkversion_allsort + ( + version NVARCHAR(128), + common_version AS SUBSTRING(version, 1, CHARINDEX('.', version) + 1), + major AS PARSENAME(CONVERT(VARCHAR(32), version), 4), + minor AS PARSENAME(CONVERT(VARCHAR(32), version), 3), + build AS PARSENAME(CONVERT(VARCHAR(32), version), 2), + revision AS PARSENAME(CONVERT(VARCHAR(32), version), 1) + ); -RAISERROR(N'Checking for expensive sorts', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE ##BlitzCacheProcs -SET sort_cost = y.max_sort_cost -FROM ( - SELECT x.SqlHandle, MAX((x.sort_io + x.sort_cpu)) AS max_sort_cost - FROM ( - SELECT - qs.SqlHandle, - relop.value('sum(/p:RelOp/@EstimateIO)', 'float') AS sort_io, - relop.value('sum(/p:RelOp/@EstimateCPU)', 'float') AS sort_cpu - FROM #relop qs - WHERE [relop].exist('/p:RelOp[(@PhysicalOp[.="Sort"])]') = 1 - ) AS x - GROUP BY x.SqlHandle - ) AS y -WHERE ##BlitzCacheProcs.SqlHandle = y.SqlHandle -AND SPID = @@SPID -OPTION (RECOMPILE); + INSERT INTO #checkversion_allsort + (version) + SELECT CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)) + OPTION ( RECOMPILE ); + END; -IF NOT EXISTS(SELECT 1/0 FROM #statements AS s WHERE s.is_cursor = 1) -BEGIN -RAISERROR(N'No cursor plans found, skipping', 0, 1) WITH NOWAIT; +SELECT @v = common_version, + @build = build +FROM #checkversion_allsort +OPTION ( RECOMPILE ); -END +IF OBJECT_ID('tempdb.. #bou_allsort') IS NULL + BEGIN + CREATE TABLE #bou_allsort + ( + Id INT IDENTITY(1, 1), + DatabaseName NVARCHAR(128), + Cost FLOAT, + QueryText NVARCHAR(MAX), + QueryType NVARCHAR(258), + Warnings VARCHAR(MAX), + QueryPlan XML, + missing_indexes XML, + implicit_conversion_info XML, + cached_execution_parameters XML, + ExecutionCount NVARCHAR(30), + ExecutionsPerMinute MONEY, + ExecutionWeight MONEY, + TotalCPU NVARCHAR(30), + AverageCPU NVARCHAR(30), + CPUWeight MONEY, + TotalDuration NVARCHAR(30), + AverageDuration NVARCHAR(30), + DurationWeight MONEY, + TotalReads NVARCHAR(30), + AverageReads NVARCHAR(30), + ReadWeight MONEY, + TotalWrites NVARCHAR(30), + AverageWrites NVARCHAR(30), + WriteWeight MONEY, + AverageReturnedRows MONEY, + MinGrantKB NVARCHAR(30), + MaxGrantKB NVARCHAR(30), + MinUsedGrantKB NVARCHAR(30), + MaxUsedGrantKB NVARCHAR(30), + AvgMaxMemoryGrant MONEY, + MinSpills NVARCHAR(30), + MaxSpills NVARCHAR(30), + TotalSpills NVARCHAR(30), + AvgSpills MONEY, + PlanCreationTime DATETIME, + LastExecutionTime DATETIME, + LastCompletionTime DATETIME, + PlanHandle VARBINARY(64), + SqlHandle VARBINARY(64), + SetOptions VARCHAR(MAX), + QueryHash BINARY(8), + PlanGenerationNum NVARCHAR(30), + RemovePlanHandleFromCache NVARCHAR(200), + Pattern NVARCHAR(20) + ); + END; -IF EXISTS(SELECT 1/0 FROM #statements AS s WHERE s.is_cursor = 1) + +IF @SortOrder = 'all' BEGIN +RAISERROR('Beginning for ALL', 0, 1) WITH NOWAIT; +SET @AllSortSql += N' + DECLARE @ISH NVARCHAR(MAX) = N'''' -RAISERROR(N'Cursor plans found, investigating', 0, 1) WITH NOWAIT; + INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, + TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, + ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, + MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) + + EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''cpu'', + @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; + + UPDATE #bou_allsort SET Pattern = ''cpu'' WHERE Pattern IS NULL OPTION(RECOMPILE); -RAISERROR(N'Checking for Optimistic cursors', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET b.is_optimistic_cursor = 1 -FROM ##BlitzCacheProcs b -JOIN #statements AS qs -ON b.SqlHandle = qs.SqlHandle -CROSS APPLY qs.statement.nodes('/p:StmtCursor') AS n1(fn) -WHERE SPID = @@SPID -AND n1.fn.exist('//p:CursorPlan/@CursorConcurrency[.="Optimistic"]') = 1 -AND qs.is_cursor = 1 -OPTION (RECOMPILE); + SELECT TOP 1 @ISH = STUFF((SELECT DISTINCT N'','' + CONVERT(NVARCHAR(MAX),b2.SqlHandle, 1) FROM #bou_allsort AS b2 FOR XML PATH(N''''), TYPE).value(N''.[1]'', N''NVARCHAR(MAX)''), 1, 1, N'''') OPTION(RECOMPILE); + INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, + TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, + ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, + MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) + + EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''reads'', @IgnoreSqlHandles = @ISH, + @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; + + UPDATE #bou_allsort SET Pattern = ''reads'' WHERE Pattern IS NULL OPTION(RECOMPILE); -RAISERROR(N'Checking if cursor is Forward Only', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET b.is_forward_only_cursor = 1 -FROM ##BlitzCacheProcs b -JOIN #statements AS qs -ON b.SqlHandle = qs.SqlHandle -CROSS APPLY qs.statement.nodes('/p:StmtCursor') AS n1(fn) -WHERE SPID = @@SPID -AND n1.fn.exist('//p:CursorPlan/@ForwardOnly[.="true"]') = 1 -AND qs.is_cursor = 1 -OPTION (RECOMPILE); + SELECT TOP 1 @ISH = STUFF((SELECT DISTINCT N'','' + CONVERT(NVARCHAR(MAX),b2.SqlHandle, 1) FROM #bou_allsort AS b2 FOR XML PATH(N''''), TYPE).value(N''.[1]'', N''NVARCHAR(MAX)''), 1, 1, N'''') OPTION(RECOMPILE); -RAISERROR(N'Checking if cursor is Fast Forward', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET b.is_fast_forward_cursor = 1 -FROM ##BlitzCacheProcs b -JOIN #statements AS qs -ON b.SqlHandle = qs.SqlHandle -CROSS APPLY qs.statement.nodes('/p:StmtCursor') AS n1(fn) -WHERE SPID = @@SPID -AND n1.fn.exist('//p:CursorPlan/@CursorActualType[.="FastForward"]') = 1 -AND qs.is_cursor = 1 -OPTION (RECOMPILE); + INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, + TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, + ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, + MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) + + EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''writes'', @IgnoreSqlHandles = @ISH, + @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; + + UPDATE #bou_allsort SET Pattern = ''writes'' WHERE Pattern IS NULL OPTION(RECOMPILE); + SELECT TOP 1 @ISH = STUFF((SELECT DISTINCT N'','' + CONVERT(NVARCHAR(MAX),b2.SqlHandle, 1) FROM #bou_allsort AS b2 FOR XML PATH(N''''), TYPE).value(N''.[1]'', N''NVARCHAR(MAX)''), 1, 1, N'''') OPTION(RECOMPILE); -RAISERROR(N'Checking for Dynamic cursors', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET b.is_cursor_dynamic = 1 -FROM ##BlitzCacheProcs b -JOIN #statements AS qs -ON b.SqlHandle = qs.SqlHandle -CROSS APPLY qs.statement.nodes('/p:StmtCursor') AS n1(fn) -WHERE SPID = @@SPID -AND n1.fn.exist('//p:CursorPlan/@CursorActualType[.="Dynamic"]') = 1 -AND qs.is_cursor = 1 -OPTION (RECOMPILE); + INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, + TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, + ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, + MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) + + EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''duration'', @IgnoreSqlHandles = @ISH, + @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; + + UPDATE #bou_allsort SET Pattern = ''duration'' WHERE Pattern IS NULL OPTION(RECOMPILE); -END + SELECT TOP 1 @ISH = STUFF((SELECT DISTINCT N'','' + CONVERT(NVARCHAR(MAX),b2.SqlHandle, 1) FROM #bou_allsort AS b2 FOR XML PATH(N''''), TYPE).value(N''.[1]'', N''NVARCHAR(MAX)''), 1, 1, N'''') OPTION(RECOMPILE); -IF @ExpertMode > 0 -BEGIN -RAISERROR(N'Checking for bad scans and plan forcing', 0, 1) WITH NOWAIT; -;WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET -b.is_table_scan = x.is_table_scan, -b.backwards_scan = x.backwards_scan, -b.forced_index = x.forced_index, -b.forced_seek = x.forced_seek, -b.forced_scan = x.forced_scan -FROM ##BlitzCacheProcs b -JOIN ( -SELECT - qs.SqlHandle, - 0 AS is_table_scan, - q.n.exist('@ScanDirection[.="BACKWARD"]') AS backwards_scan, - q.n.value('@ForcedIndex', 'bit') AS forced_index, - q.n.value('@ForceSeek', 'bit') AS forced_seek, - q.n.value('@ForceScan', 'bit') AS forced_scan -FROM #relop qs -CROSS APPLY qs.relop.nodes('//p:IndexScan') AS q(n) -UNION ALL -SELECT - qs.SqlHandle, - 1 AS is_table_scan, - q.n.exist('@ScanDirection[.="BACKWARD"]') AS backwards_scan, - q.n.value('@ForcedIndex', 'bit') AS forced_index, - q.n.value('@ForceSeek', 'bit') AS forced_seek, - q.n.value('@ForceScan', 'bit') AS forced_scan -FROM #relop qs -CROSS APPLY qs.relop.nodes('//p:TableScan') AS q(n) -) AS x ON b.SqlHandle = x.SqlHandle -WHERE SPID = @@SPID -OPTION (RECOMPILE); -END; + INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, + TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, + ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, + MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) + + EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''executions'', @IgnoreSqlHandles = @ISH, + @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; + + UPDATE #bou_allsort SET Pattern = ''executions'' WHERE Pattern IS NULL OPTION(RECOMPILE); + + '; + + IF @VersionShowsMemoryGrants = 0 + BEGIN + IF @ExportToExcel = 1 + BEGIN + SET @AllSortSql += N' UPDATE #bou_allsort + SET + QueryPlan = NULL, + implicit_conversion_info = NULL, + cached_execution_parameters = NULL, + missing_indexes = NULL + OPTION (RECOMPILE); + UPDATE ##BlitzCacheProcs + SET QueryText = SUBSTRING(REPLACE(REPLACE(REPLACE(LTRIM(RTRIM(QueryText)),'' '',''<>''),''><'',''''),''<>'','' ''), 1, 32000) + OPTION(RECOMPILE);'; + END; -IF @ExpertMode > 0 -BEGIN -RAISERROR(N'Checking for computed columns that reference scalar UDFs', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE ##BlitzCacheProcs -SET is_computed_scalar = x.computed_column_function -FROM ( -SELECT qs.SqlHandle, - n.fn.value('count(distinct-values(//p:UserDefinedFunction[not(@IsClrFunction)]))', 'INT') AS computed_column_function -FROM #relop qs -CROSS APPLY relop.nodes('/p:RelOp/p:ComputeScalar/p:DefinedValues/p:DefinedValue/p:ScalarOperator') n(fn) -WHERE n.fn.exist('/p:RelOp/p:ComputeScalar/p:DefinedValues/p:DefinedValue/p:ColumnReference[(@ComputedColumn[.="1"])]') = 1 -) AS x -WHERE ##BlitzCacheProcs.SqlHandle = x.SqlHandle -AND SPID = @@SPID -OPTION (RECOMPILE); -END; + END; + + IF @VersionShowsMemoryGrants = 1 + BEGIN + SET @AllSortSql += N' SELECT TOP 1 @ISH = STUFF((SELECT DISTINCT N'','' + CONVERT(NVARCHAR(MAX),b2.SqlHandle, 1) FROM #bou_allsort AS b2 FOR XML PATH(N''''), TYPE).value(N''.[1]'', N''NVARCHAR(MAX)''), 1, 1, N'''') OPTION(RECOMPILE); + + INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, + TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, + ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, + MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) + + EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''memory grant'', @IgnoreSqlHandles = @ISH, + @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; + + UPDATE #bou_allsort SET Pattern = ''memory grant'' WHERE Pattern IS NULL OPTION(RECOMPILE);'; + IF @ExportToExcel = 1 + BEGIN + SET @AllSortSql += N' UPDATE #bou_allsort + SET + QueryPlan = NULL, + implicit_conversion_info = NULL, + cached_execution_parameters = NULL, + missing_indexes = NULL + OPTION (RECOMPILE); + UPDATE ##BlitzCacheProcs + SET QueryText = SUBSTRING(REPLACE(REPLACE(REPLACE(LTRIM(RTRIM(QueryText)),'' '',''<>''),''><'',''''),''<>'','' ''), 1, 32000) + OPTION(RECOMPILE);'; + END; -RAISERROR(N'Checking for filters that reference scalar UDFs', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE ##BlitzCacheProcs -SET is_computed_filter = x.filter_function -FROM ( -SELECT -r.SqlHandle, -c.n.value('count(distinct-values(//p:UserDefinedFunction[not(@IsClrFunction)]))', 'INT') AS filter_function -FROM #relop AS r -CROSS APPLY r.relop.nodes('/p:RelOp/p:Filter/p:Predicate/p:ScalarOperator/p:Compare/p:ScalarOperator/p:UserDefinedFunction') c(n) -) x -WHERE ##BlitzCacheProcs.SqlHandle = x.SqlHandle -AND SPID = @@SPID -OPTION (RECOMPILE); + END; -IF @ExpertMode > 0 -BEGIN -RAISERROR(N'Checking modification queries that hit lots of indexes', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p), -IndexOps AS -( - SELECT - r.QueryHash, - c.n.value('@PhysicalOp', 'VARCHAR(100)') AS op_name, - c.n.exist('@PhysicalOp[.="Index Insert"]') AS ii, - c.n.exist('@PhysicalOp[.="Index Update"]') AS iu, - c.n.exist('@PhysicalOp[.="Index Delete"]') AS id, - c.n.exist('@PhysicalOp[.="Clustered Index Insert"]') AS cii, - c.n.exist('@PhysicalOp[.="Clustered Index Update"]') AS ciu, - c.n.exist('@PhysicalOp[.="Clustered Index Delete"]') AS cid, - c.n.exist('@PhysicalOp[.="Table Insert"]') AS ti, - c.n.exist('@PhysicalOp[.="Table Update"]') AS tu, - c.n.exist('@PhysicalOp[.="Table Delete"]') AS td - FROM #relop AS r - CROSS APPLY r.relop.nodes('/p:RelOp') c(n) - OUTER APPLY r.relop.nodes('/p:RelOp/p:ScalarInsert/p:Object') q(n) - OUTER APPLY r.relop.nodes('/p:RelOp/p:Update/p:Object') o2(n) - OUTER APPLY r.relop.nodes('/p:RelOp/p:SimpleUpdate/p:Object') o3(n) -), iops AS -( - SELECT ios.QueryHash, - SUM(CONVERT(TINYINT, ios.ii)) AS index_insert_count, - SUM(CONVERT(TINYINT, ios.iu)) AS index_update_count, - SUM(CONVERT(TINYINT, ios.id)) AS index_delete_count, - SUM(CONVERT(TINYINT, ios.cii)) AS cx_insert_count, - SUM(CONVERT(TINYINT, ios.ciu)) AS cx_update_count, - SUM(CONVERT(TINYINT, ios.cid)) AS cx_delete_count, - SUM(CONVERT(TINYINT, ios.ti)) AS table_insert_count, - SUM(CONVERT(TINYINT, ios.tu)) AS table_update_count, - SUM(CONVERT(TINYINT, ios.td)) AS table_delete_count - FROM IndexOps AS ios - WHERE ios.op_name IN ('Index Insert', 'Index Delete', 'Index Update', - 'Clustered Index Insert', 'Clustered Index Delete', 'Clustered Index Update', - 'Table Insert', 'Table Delete', 'Table Update') - GROUP BY ios.QueryHash) -UPDATE b -SET b.index_insert_count = iops.index_insert_count, - b.index_update_count = iops.index_update_count, - b.index_delete_count = iops.index_delete_count, - b.cx_insert_count = iops.cx_insert_count, - b.cx_update_count = iops.cx_update_count, - b.cx_delete_count = iops.cx_delete_count, - b.table_insert_count = iops.table_insert_count, - b.table_update_count = iops.table_update_count, - b.table_delete_count = iops.table_delete_count -FROM ##BlitzCacheProcs AS b -JOIN iops ON iops.QueryHash = b.QueryHash -WHERE SPID = @@SPID -OPTION (RECOMPILE); -END; + IF @VersionShowsSpills = 0 + BEGIN + IF @ExportToExcel = 1 + BEGIN + SET @AllSortSql += N' UPDATE #bou_allsort + SET + QueryPlan = NULL, + implicit_conversion_info = NULL, + cached_execution_parameters = NULL, + missing_indexes = NULL + OPTION (RECOMPILE); + UPDATE ##BlitzCacheProcs + SET QueryText = SUBSTRING(REPLACE(REPLACE(REPLACE(LTRIM(RTRIM(QueryText)),'' '',''<>''),''><'',''''),''<>'','' ''), 1, 32000) + OPTION(RECOMPILE);'; + END; -IF @ExpertMode > 0 -BEGIN -RAISERROR(N'Checking for Spatial index use', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE ##BlitzCacheProcs -SET is_spatial = x.is_spatial -FROM ( -SELECT qs.SqlHandle, - 1 AS is_spatial -FROM #relop qs -CROSS APPLY relop.nodes('/p:RelOp//p:Object') n(fn) -WHERE n.fn.exist('(@IndexKind[.="Spatial"])') = 1 -) AS x -WHERE ##BlitzCacheProcs.SqlHandle = x.SqlHandle -AND SPID = @@SPID -OPTION (RECOMPILE); -END; + END; + + IF @VersionShowsSpills = 1 + BEGIN + SET @AllSortSql += N' SELECT TOP 1 @ISH = STUFF((SELECT DISTINCT N'','' + CONVERT(NVARCHAR(MAX),b2.SqlHandle, 1) FROM #bou_allsort AS b2 FOR XML PATH(N''''), TYPE).value(N''.[1]'', N''NVARCHAR(MAX)''), 1, 1, N'''') OPTION(RECOMPILE); + + INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, + TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, + ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, + MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) + + EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''spills'', @IgnoreSqlHandles = @ISH, + @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; + + UPDATE #bou_allsort SET Pattern = ''spills'' WHERE Pattern IS NULL OPTION(RECOMPILE);'; + IF @ExportToExcel = 1 + BEGIN + SET @AllSortSql += N' UPDATE #bou_allsort + SET + QueryPlan = NULL, + implicit_conversion_info = NULL, + cached_execution_parameters = NULL, + missing_indexes = NULL + OPTION (RECOMPILE); + UPDATE ##BlitzCacheProcs + SET QueryText = SUBSTRING(REPLACE(REPLACE(REPLACE(LTRIM(RTRIM(QueryText)),'' '',''<>''),''><'',''''),''<>'','' ''), 1, 32000) + OPTION(RECOMPILE);'; + END; -RAISERROR('Checking for wonky Index Spools', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES ( - 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) -, selects -AS ( SELECT s.QueryHash - FROM #statements AS s - WHERE s.statement.exist('/p:StmtSimple/@StatementType[.="SELECT"]') = 1 ) -, spools -AS ( SELECT DISTINCT r.QueryHash, - c.n.value('@EstimateRows', 'FLOAT') AS estimated_rows, - c.n.value('@EstimateIO', 'FLOAT') AS estimated_io, - c.n.value('@EstimateCPU', 'FLOAT') AS estimated_cpu, - c.n.value('@EstimateRebinds', 'FLOAT') AS estimated_rebinds -FROM #relop AS r -JOIN selects AS s -ON s.QueryHash = r.QueryHash -CROSS APPLY r.relop.nodes('/p:RelOp') AS c(n) -WHERE r.relop.exist('/p:RelOp[@PhysicalOp="Index Spool" and @LogicalOp="Eager Spool"]') = 1 -) -UPDATE b - SET b.index_spool_rows = sp.estimated_rows, - b.index_spool_cost = ((sp.estimated_io * sp.estimated_cpu) * CASE WHEN sp.estimated_rebinds < 1 THEN 1 ELSE sp.estimated_rebinds END) -FROM ##BlitzCacheProcs b -JOIN spools sp -ON sp.QueryHash = b.QueryHash -OPTION (RECOMPILE); + END; -RAISERROR('Checking for wonky Table Spools', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES ( - 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) -, selects -AS ( SELECT s.QueryHash - FROM #statements AS s - WHERE s.statement.exist('/p:StmtSimple/@StatementType[.="SELECT"]') = 1 ) -, spools -AS ( SELECT DISTINCT r.QueryHash, - c.n.value('@EstimateRows', 'FLOAT') AS estimated_rows, - c.n.value('@EstimateIO', 'FLOAT') AS estimated_io, - c.n.value('@EstimateCPU', 'FLOAT') AS estimated_cpu, - c.n.value('@EstimateRebinds', 'FLOAT') AS estimated_rebinds -FROM #relop AS r -JOIN selects AS s -ON s.QueryHash = r.QueryHash -CROSS APPLY r.relop.nodes('/p:RelOp') AS c(n) -WHERE r.relop.exist('/p:RelOp[@PhysicalOp="Table Spool" and @LogicalOp="Lazy Spool"]') = 1 -) -UPDATE b - SET b.table_spool_rows = (sp.estimated_rows * sp.estimated_rebinds), - b.table_spool_cost = ((sp.estimated_io * sp.estimated_cpu * sp.estimated_rows) * CASE WHEN sp.estimated_rebinds < 1 THEN 1 ELSE sp.estimated_rebinds END) -FROM ##BlitzCacheProcs b -JOIN spools sp -ON sp.QueryHash = b.QueryHash -OPTION (RECOMPILE); + IF(@OutputType <> 'NONE') + BEGIN + SET @AllSortSql += N' SELECT DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters,ExecutionCount, ExecutionsPerMinute, ExecutionWeight, + TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, + ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, + MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache, Pattern + FROM #bou_allsort + ORDER BY Id + OPTION(RECOMPILE); '; + END; +END; -RAISERROR('Checking for selects that cause non-spill and index spool writes', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES ( - 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) -, selects -AS ( SELECT CONVERT(BINARY(8), - RIGHT('0000000000000000' - + SUBSTRING(s.statement.value('(/p:StmtSimple/@QueryHash)[1]', 'VARCHAR(18)'), - 3, 18), 16), 2) AS QueryHash - FROM #statements AS s - JOIN ##BlitzCacheProcs b - ON s.QueryHash = b.QueryHash - WHERE b.index_spool_rows IS NULL - AND b.index_spool_cost IS NULL - AND b.table_spool_cost IS NULL - AND b.table_spool_rows IS NULL - AND b.is_big_spills IS NULL - AND b.AverageWrites > 1024. - AND s.statement.exist('/p:StmtSimple/@StatementType[.="SELECT"]') = 1 -) -UPDATE b - SET b.select_with_writes = 1 -FROM ##BlitzCacheProcs b -JOIN selects AS s -ON s.QueryHash = b.QueryHash -AND b.AverageWrites > 1024.; +IF @SortOrder = 'all avg' +BEGIN +RAISERROR('Beginning for ALL AVG', 0, 1) WITH NOWAIT; +SET @AllSortSql += N' + DECLARE @ISH NVARCHAR(MAX) = N'''' + + INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, + TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, + ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, + MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) + + EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''avg cpu'', + @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; + + UPDATE #bou_allsort SET Pattern = ''avg cpu'' WHERE Pattern IS NULL OPTION(RECOMPILE); -/* 2012+ only */ -IF @v >= 11 -BEGIN + SELECT TOP 1 @ISH = STUFF((SELECT DISTINCT N'','' + CONVERT(NVARCHAR(MAX),b2.SqlHandle, 1) FROM #bou_allsort AS b2 FOR XML PATH(N''''), TYPE).value(N''.[1]'', N''NVARCHAR(MAX)''), 1, 1, N'''') OPTION(RECOMPILE); - RAISERROR(N'Checking for forced serialization', 0, 1) WITH NOWAIT; - WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) - UPDATE ##BlitzCacheProcs - SET is_forced_serial = 1 - FROM #query_plan qp - WHERE qp.SqlHandle = ##BlitzCacheProcs.SqlHandle - AND SPID = @@SPID - AND query_plan.exist('/p:QueryPlan/@NonParallelPlanReason') = 1 - AND (##BlitzCacheProcs.is_parallel = 0 OR ##BlitzCacheProcs.is_parallel IS NULL) - OPTION (RECOMPILE); - - IF @ExpertMode > 0 - BEGIN - RAISERROR(N'Checking for ColumnStore queries operating in Row Mode instead of Batch Mode', 0, 1) WITH NOWAIT; - WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) - UPDATE ##BlitzCacheProcs - SET columnstore_row_mode = x.is_row_mode - FROM ( - SELECT - qs.SqlHandle, - relop.exist('/p:RelOp[(@EstimatedExecutionMode[.="Row"])]') AS is_row_mode - FROM #relop qs - WHERE [relop].exist('/p:RelOp/p:IndexScan[(@Storage[.="ColumnStore"])]') = 1 - ) AS x - WHERE ##BlitzCacheProcs.SqlHandle = x.SqlHandle - AND SPID = @@SPID - OPTION (RECOMPILE); - END; + INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, + TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, + ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, + MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) + + EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''avg reads'', @IgnoreSqlHandles = @ISH, + @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; + + UPDATE #bou_allsort SET Pattern = ''avg reads'' WHERE Pattern IS NULL OPTION(RECOMPILE); -END; + SELECT TOP 1 @ISH = STUFF((SELECT DISTINCT N'','' + CONVERT(NVARCHAR(MAX),b2.SqlHandle, 1) FROM #bou_allsort AS b2 FOR XML PATH(N''''), TYPE).value(N''.[1]'', N''NVARCHAR(MAX)''), 1, 1, N'''') OPTION(RECOMPILE); -/* 2014+ only */ -IF @v >= 12 -BEGIN - RAISERROR('Checking for downlevel cardinality estimators being used on SQL Server 2014.', 0, 1) WITH NOWAIT; + INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, + TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, + ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, + MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) + + EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''avg writes'', @IgnoreSqlHandles = @ISH, + @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; + + UPDATE #bou_allsort SET Pattern = ''avg writes'' WHERE Pattern IS NULL OPTION(RECOMPILE); - WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) - UPDATE p - SET downlevel_estimator = CASE WHEN statement.value('min(//p:StmtSimple/@CardinalityEstimationModelVersion)', 'int') < (@v * 10) THEN 1 END - FROM ##BlitzCacheProcs p - JOIN #statements s ON p.QueryHash = s.QueryHash - WHERE SPID = @@SPID - OPTION (RECOMPILE); -END ; + SELECT TOP 1 @ISH = STUFF((SELECT DISTINCT N'','' + CONVERT(NVARCHAR(MAX),b2.SqlHandle, 1) FROM #bou_allsort AS b2 FOR XML PATH(N''''), TYPE).value(N''.[1]'', N''NVARCHAR(MAX)''), 1, 1, N'''') OPTION(RECOMPILE); -/* 2016+ only */ -IF @v >= 13 AND @ExpertMode > 0 -BEGIN - RAISERROR('Checking for row level security in 2016 only', 0, 1) WITH NOWAIT; + INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, + TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, + ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, + MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) + + EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''avg duration'', @IgnoreSqlHandles = @ISH, + @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; + + UPDATE #bou_allsort SET Pattern = ''avg duration'' WHERE Pattern IS NULL OPTION(RECOMPILE); - WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) - UPDATE p - SET p.is_row_level = 1 - FROM ##BlitzCacheProcs p - JOIN #statements s ON p.QueryHash = s.QueryHash - WHERE SPID = @@SPID - AND statement.exist('/p:StmtSimple/@SecurityPolicyApplied[.="true"]') = 1 - OPTION (RECOMPILE); -END ; + SELECT TOP 1 @ISH = STUFF((SELECT DISTINCT N'','' + CONVERT(NVARCHAR(MAX),b2.SqlHandle, 1) FROM #bou_allsort AS b2 FOR XML PATH(N''''), TYPE).value(N''.[1]'', N''NVARCHAR(MAX)''), 1, 1, N'''') OPTION(RECOMPILE); -/* 2017+ only */ -IF @v >= 14 OR (@v = 13 AND @build >= 5026) -BEGIN + INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, + TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, + ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, + MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) + + EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''avg executions'', @IgnoreSqlHandles = @ISH, + @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; + + UPDATE #bou_allsort SET Pattern = ''avg executions'' WHERE Pattern IS NULL OPTION(RECOMPILE); + + '; + + IF @VersionShowsMemoryGrants = 0 + BEGIN + IF @ExportToExcel = 1 + BEGIN + SET @AllSortSql += N' UPDATE #bou_allsort + SET + QueryPlan = NULL, + implicit_conversion_info = NULL, + cached_execution_parameters = NULL, + missing_indexes = NULL + OPTION (RECOMPILE); -IF @ExpertMode > 0 -BEGIN -RAISERROR('Gathering stats information', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -INSERT INTO #stats_agg -SELECT qp.SqlHandle, - x.c.value('@LastUpdate', 'DATETIME2(7)') AS LastUpdate, - x.c.value('@ModificationCount', 'BIGINT') AS ModificationCount, - x.c.value('@SamplingPercent', 'FLOAT') AS SamplingPercent, - x.c.value('@Statistics', 'NVARCHAR(258)') AS [Statistics], - x.c.value('@Table', 'NVARCHAR(258)') AS [Table], - x.c.value('@Schema', 'NVARCHAR(258)') AS [Schema], - x.c.value('@Database', 'NVARCHAR(258)') AS [Database] -FROM #query_plan AS qp -CROSS APPLY qp.query_plan.nodes('//p:OptimizerStatsUsage/p:StatisticsInfo') x (c) -OPTION (RECOMPILE); + UPDATE ##BlitzCacheProcs + SET QueryText = SUBSTRING(REPLACE(REPLACE(REPLACE(LTRIM(RTRIM(QueryText)),'' '',''<>''),''><'',''''),''<>'','' ''), 1, 32000) + OPTION(RECOMPILE);'; + END; + END; + + IF @VersionShowsMemoryGrants = 1 + BEGIN + SET @AllSortSql += N' SELECT TOP 1 @ISH = STUFF((SELECT DISTINCT N'','' + CONVERT(NVARCHAR(MAX),b2.SqlHandle, 1) FROM #bou_allsort AS b2 FOR XML PATH(N''''), TYPE).value(N''.[1]'', N''NVARCHAR(MAX)''), 1, 1, N'''') OPTION(RECOMPILE); + + INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, + TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, + ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, + MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) + + EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''avg memory grant'', @IgnoreSqlHandles = @ISH, + @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; + + UPDATE #bou_allsort SET Pattern = ''avg memory grant'' WHERE Pattern IS NULL OPTION(RECOMPILE);'; + IF @ExportToExcel = 1 + BEGIN + SET @AllSortSql += N' UPDATE #bou_allsort + SET + QueryPlan = NULL, + implicit_conversion_info = NULL, + cached_execution_parameters = NULL, + missing_indexes = NULL + OPTION (RECOMPILE); -RAISERROR('Checking for stale stats', 0, 1) WITH NOWAIT; -WITH stale_stats AS ( - SELECT sa.SqlHandle - FROM #stats_agg AS sa - GROUP BY sa.SqlHandle - HAVING MAX(sa.LastUpdate) <= DATEADD(DAY, -7, SYSDATETIME()) - AND AVG(sa.ModificationCount) >= 100000 -) -UPDATE b -SET stale_stats = 1 -FROM ##BlitzCacheProcs b -JOIN stale_stats os -ON b.SqlHandle = os.SqlHandle -AND b.SPID = @@SPID -OPTION (RECOMPILE); -END; + UPDATE ##BlitzCacheProcs + SET QueryText = SUBSTRING(REPLACE(REPLACE(REPLACE(LTRIM(RTRIM(QueryText)),'' '',''<>''),''><'',''''),''<>'','' ''), 1, 32000) + OPTION(RECOMPILE);'; + END; -IF @v >= 14 AND @ExpertMode > 0 -BEGIN -RAISERROR('Checking for adaptive joins', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p), -aj AS ( - SELECT - SqlHandle - FROM #relop AS r - CROSS APPLY r.relop.nodes('//p:RelOp') x(c) - WHERE x.c.exist('@IsAdaptive[.=1]') = 1 -) -UPDATE b -SET b.is_adaptive = 1 -FROM ##BlitzCacheProcs b -JOIN aj -ON b.SqlHandle = aj.SqlHandle -AND b.SPID = @@SPID -OPTION (RECOMPILE); -END; + END; -IF ((@v >= 14 - OR (@v = 13 AND @build >= 5026) - OR (@v = 12 AND @build >= 6024)) - AND @ExpertMode > 0) + IF @VersionShowsSpills = 0 + BEGIN + IF @ExportToExcel = 1 + BEGIN + SET @AllSortSql += N' UPDATE #bou_allsort + SET + QueryPlan = NULL, + implicit_conversion_info = NULL, + cached_execution_parameters = NULL, + missing_indexes = NULL + OPTION (RECOMPILE); -BEGIN; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p), -row_goals AS( -SELECT qs.QueryHash -FROM #relop qs -WHERE relop.value('sum(/p:RelOp/@EstimateRowsWithoutRowGoal)', 'float') > 0 -) -UPDATE b -SET b.is_row_goal = 1 -FROM ##BlitzCacheProcs b -JOIN row_goals -ON b.QueryHash = row_goals.QueryHash -AND b.SPID = @@SPID -OPTION (RECOMPILE); -END ; + UPDATE ##BlitzCacheProcs + SET QueryText = SUBSTRING(REPLACE(REPLACE(REPLACE(LTRIM(RTRIM(QueryText)),'' '',''<>''),''><'',''''),''<>'','' ''), 1, 32000) + OPTION(RECOMPILE);'; + END; + + END; + + IF @VersionShowsSpills = 1 + BEGIN + SET @AllSortSql += N' SELECT TOP 1 @ISH = STUFF((SELECT DISTINCT N'','' + CONVERT(NVARCHAR(MAX),b2.SqlHandle, 1) FROM #bou_allsort AS b2 FOR XML PATH(N''''), TYPE).value(N''.[1]'', N''NVARCHAR(MAX)''), 1, 1, N'''') OPTION(RECOMPILE); + + INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, + TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, + ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, + MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) + + EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''avg spills'', @IgnoreSqlHandles = @ISH, + @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; + + UPDATE #bou_allsort SET Pattern = ''avg spills'' WHERE Pattern IS NULL OPTION(RECOMPILE);'; + IF @ExportToExcel = 1 + BEGIN + SET @AllSortSql += N' UPDATE #bou_allsort + SET + QueryPlan = NULL, + implicit_conversion_info = NULL, + cached_execution_parameters = NULL, + missing_indexes = NULL + OPTION (RECOMPILE); + UPDATE ##BlitzCacheProcs + SET QueryText = SUBSTRING(REPLACE(REPLACE(REPLACE(LTRIM(RTRIM(QueryText)),'' '',''<>''),''><'',''''),''<>'','' ''), 1, 32000) + OPTION(RECOMPILE);'; + END; + + END; + + IF(@OutputType <> 'NONE') + BEGIN + SET @AllSortSql += N' SELECT DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters,ExecutionCount, ExecutionsPerMinute, ExecutionWeight, + TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, + ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, + MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache, Pattern + FROM #bou_allsort + ORDER BY Id + OPTION(RECOMPILE); '; + END; END; + IF @Debug = 1 + BEGIN + PRINT SUBSTRING(@AllSortSql, 0, 4000); + PRINT SUBSTRING(@AllSortSql, 4000, 8000); + PRINT SUBSTRING(@AllSortSql, 8000, 12000); + PRINT SUBSTRING(@AllSortSql, 12000, 16000); + PRINT SUBSTRING(@AllSortSql, 16000, 20000); + PRINT SUBSTRING(@AllSortSql, 20000, 24000); + PRINT SUBSTRING(@AllSortSql, 24000, 28000); + PRINT SUBSTRING(@AllSortSql, 28000, 32000); + PRINT SUBSTRING(@AllSortSql, 32000, 36000); + PRINT SUBSTRING(@AllSortSql, 36000, 40000); + END; -/* END Testing using XML nodes to speed up processing */ + EXEC sys.sp_executesql @stmt = @AllSortSql, @params = N'@i_DatabaseName NVARCHAR(128), @i_Top INT, @i_SkipAnalysis BIT, @i_OutputDatabaseName NVARCHAR(258), @i_OutputSchemaName NVARCHAR(258), @i_OutputTableName NVARCHAR(258), @i_CheckDateOverride DATETIMEOFFSET, @i_MinutesBack INT ', + @i_DatabaseName = @DatabaseName, @i_Top = @Top, @i_SkipAnalysis = @SkipAnalysis, @i_OutputDatabaseName = @OutputDatabaseName, @i_OutputSchemaName = @OutputSchemaName, @i_OutputTableName = @OutputTableName, @i_CheckDateOverride = @CheckDateOverride, @i_MinutesBack = @MinutesBack; +/* Avoid going into OutputResultsToTable + ... otherwise the last result (e.g. spills) would be recorded twice into the output table. +*/ +RETURN; -/* Update to grab stored procedure name for individual statements */ -RAISERROR(N'Attempting to get stored procedure name for individual statements', 0, 1) WITH NOWAIT; -UPDATE p -SET QueryType = QueryType + ' (parent ' + - + QUOTENAME(OBJECT_SCHEMA_NAME(s.object_id, s.database_id)) - + '.' - + QUOTENAME(OBJECT_NAME(s.object_id, s.database_id)) + ')' -FROM ##BlitzCacheProcs p - JOIN sys.dm_exec_procedure_stats s ON p.SqlHandle = s.sql_handle -WHERE QueryType = 'Statement' -AND SPID = @@SPID -OPTION (RECOMPILE); +/*End of AllSort section*/ -/* Update to grab stored procedure name for individual statements when PSPO is detected */ -UPDATE p -SET QueryType = QueryType + ' (parent ' + - + QUOTENAME(OBJECT_SCHEMA_NAME(s.object_id, s.database_id)) - + '.' - + QUOTENAME(OBJECT_NAME(s.object_id, s.database_id)) + ')' -FROM ##BlitzCacheProcs p - OUTER APPLY ( - SELECT REPLACE(REPLACE(REPLACE(REPLACE(p.QueryText, ' (', '('), '( ', '('), ' =', '='), '= ', '=') AS NormalizedQueryText - ) a - OUTER APPLY ( - SELECT CHARINDEX('option(PLAN PER VALUE(ObjectID=', a.NormalizedQueryText) AS OptionStart - ) b - OUTER APPLY ( - SELECT SUBSTRING(a.NormalizedQueryText, b.OptionStart + 31, LEN(a.NormalizedQueryText) - b.OptionStart - 30) AS OptionSubstring - WHERE b.OptionStart > 0 - ) c - OUTER APPLY ( - SELECT PATINDEX('%[^0-9]%', c.OptionSubstring) AS ObjectLength - ) d - OUTER APPLY ( - SELECT TRY_CAST(SUBSTRING(OptionSubstring, 1, d.ObjectLength - 1) AS INT) AS ObjectId - ) e - JOIN sys.dm_exec_procedure_stats s ON DB_ID(p.DatabaseName) = s.database_id AND e.ObjectId = s.object_id -WHERE p.QueryType = 'Statement' -AND p.SPID = @@SPID -AND s.object_id IS NOT NULL -OPTION (RECOMPILE); -RAISERROR(N'Attempting to get function name for individual statements', 0, 1) WITH NOWAIT; -DECLARE @function_update_sql NVARCHAR(MAX) = N'' -IF EXISTS (SELECT 1/0 FROM sys.all_objects AS o WHERE o.name = 'dm_exec_function_stats') - BEGIN - SET @function_update_sql = @function_update_sql + N' - UPDATE p - SET QueryType = QueryType + '' (parent '' + - + QUOTENAME(OBJECT_SCHEMA_NAME(s.object_id, s.database_id)) - + ''.'' - + QUOTENAME(OBJECT_NAME(s.object_id, s.database_id)) + '')'' - FROM ##BlitzCacheProcs p - JOIN sys.dm_exec_function_stats s ON p.SqlHandle = s.sql_handle - WHERE QueryType = ''Statement'' - AND SPID = @@SPID - OPTION (RECOMPILE); - ' - EXEC sys.sp_executesql @function_update_sql - END - - -/* Trace Flag Checks 2012 SP3, 2014 SP2 and 2016 SP1 only)*/ -IF @v >= 11 -BEGIN - -RAISERROR(N'Trace flag checks', 0, 1) WITH NOWAIT; -;WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -, tf_pretty AS ( -SELECT qp.QueryHash, - qp.SqlHandle, - q.n.value('@Value', 'INT') AS trace_flag, - q.n.value('@Scope', 'VARCHAR(10)') AS scope -FROM #query_plan qp -CROSS APPLY qp.query_plan.nodes('/p:QueryPlan/p:TraceFlags/p:TraceFlag') AS q(n) -) -INSERT INTO #trace_flags -SELECT DISTINCT tf1.SqlHandle , tf1.QueryHash, - STUFF(( - SELECT DISTINCT ', ' + CONVERT(VARCHAR(5), tf2.trace_flag) - FROM tf_pretty AS tf2 - WHERE tf1.SqlHandle = tf2.SqlHandle - AND tf1.QueryHash = tf2.QueryHash - AND tf2.scope = 'Global' - FOR XML PATH(N'')), 1, 2, N'' - ) AS global_trace_flags, - STUFF(( - SELECT DISTINCT ', ' + CONVERT(VARCHAR(5), tf2.trace_flag) - FROM tf_pretty AS tf2 - WHERE tf1.SqlHandle = tf2.SqlHandle - AND tf1.QueryHash = tf2.QueryHash - AND tf2.scope = 'Session' - FOR XML PATH(N'')), 1, 2, N'' - ) AS session_trace_flags -FROM tf_pretty AS tf1 -OPTION (RECOMPILE); +/*Begin code to write results to table */ +OutputResultsToTable: + +RAISERROR('Writing results to table.', 0, 1) WITH NOWAIT; -UPDATE p -SET p.trace_flags_session = tf.session_trace_flags -FROM ##BlitzCacheProcs p -JOIN #trace_flags tf ON tf.QueryHash = p.QueryHash -WHERE SPID = @@SPID -OPTION (RECOMPILE); +SELECT @OutputServerName = QUOTENAME(@OutputServerName), + @OutputDatabaseName = QUOTENAME(@OutputDatabaseName), + @OutputSchemaName = QUOTENAME(@OutputSchemaName), + @OutputTableName = QUOTENAME(@OutputTableName); -END; +/* Checks if @OutputServerName is populated with a valid linked server, and that the database name specified is valid */ +DECLARE @ValidOutputServer BIT; +DECLARE @ValidOutputLocation BIT; +DECLARE @LinkedServerDBCheck NVARCHAR(2000); +DECLARE @ValidLinkedServerDB INT; +DECLARE @tmpdbchk table (cnt int); +IF @OutputServerName IS NOT NULL + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Outputting to a remote server.', 0, 1) WITH NOWAIT; + + IF EXISTS (SELECT server_id FROM sys.servers WHERE QUOTENAME([name]) = @OutputServerName) + BEGIN + SET @LinkedServerDBCheck = 'SELECT 1 WHERE EXISTS (SELECT * FROM '+@OutputServerName+'.master.sys.databases WHERE QUOTENAME([name]) = '''+@OutputDatabaseName+''')'; + INSERT INTO @tmpdbchk EXEC sys.sp_executesql @LinkedServerDBCheck; + SET @ValidLinkedServerDB = (SELECT COUNT(*) FROM @tmpdbchk); + IF (@ValidLinkedServerDB > 0) + BEGIN + SET @ValidOutputServer = 1; + SET @ValidOutputLocation = 1; + END; + ELSE + RAISERROR('The specified database was not found on the output server', 16, 0); + END; + ELSE + BEGIN + RAISERROR('The specified output server was not found', 16, 0); + END; + END; +ELSE + BEGIN + IF @OutputDatabaseName IS NOT NULL + AND @OutputSchemaName IS NOT NULL + AND @OutputTableName IS NOT NULL + AND EXISTS ( SELECT * + FROM sys.databases + WHERE QUOTENAME([name]) = @OutputDatabaseName) + BEGIN + SET @ValidOutputLocation = 1; + END; + ELSE IF @OutputDatabaseName IS NOT NULL + AND @OutputSchemaName IS NOT NULL + AND @OutputTableName IS NOT NULL + AND NOT EXISTS ( SELECT * + FROM sys.databases + WHERE QUOTENAME([name]) = @OutputDatabaseName) + BEGIN + RAISERROR('The specified output database was not found on this server', 16, 0); + END; + ELSE + BEGIN + SET @ValidOutputLocation = 0; + END; + END; + /* @OutputTableName lets us export the results to a permanent table */ + DECLARE @StringToExecute NVARCHAR(MAX) = N'' ; -RAISERROR(N'Checking for MSTVFs', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET b.is_mstvf = 1 -FROM #relop AS r -JOIN ##BlitzCacheProcs AS b -ON b.SqlHandle = r.SqlHandle -WHERE r.relop.exist('/p:RelOp[(@EstimateRows="100" or @EstimateRows="1") and @LogicalOp="Table-valued function"]') = 1 -OPTION (RECOMPILE); + IF @ValidOutputLocation = 1 + BEGIN + SET @StringToExecute = N'USE ' + + @OutputDatabaseName + + N'; IF EXISTS(SELECT * FROM ' + + @OutputDatabaseName + + N'.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' + + @OutputSchemaName + + N''') AND NOT EXISTS (SELECT * FROM ' + + @OutputDatabaseName + + N'.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''' + + @OutputSchemaName + N''' AND QUOTENAME(TABLE_NAME) = ''' + + @OutputTableName + N''') CREATE TABLE ' + + @OutputSchemaName + N'.' + + @OutputTableName + + CONVERT + ( + nvarchar(MAX), + N'(ID bigint NOT NULL IDENTITY(1,1), + ServerName NVARCHAR(258), + CheckDate DATETIMEOFFSET, + Version NVARCHAR(258), + QueryType NVARCHAR(258), + Warnings varchar(max), + DatabaseName sysname, + SerialDesiredMemory float, + SerialRequiredMemory float, + AverageCPU bigint, + TotalCPU bigint, + PercentCPUByType money, + CPUWeight money, + AverageDuration bigint, + TotalDuration bigint, + DurationWeight money, + PercentDurationByType money, + AverageReads bigint, + TotalReads bigint, + ReadWeight money, + PercentReadsByType money, + AverageWrites bigint, + TotalWrites bigint, + WriteWeight money, + PercentWritesByType money, + ExecutionCount bigint, + ExecutionWeight money, + PercentExecutionsByType money, + ExecutionsPerMinute money, + PlanCreationTime datetime,' + N' + PlanCreationTimeHours AS DATEDIFF(HOUR,CONVERT(DATETIMEOFFSET(7),[PlanCreationTime]),[CheckDate]), + LastExecutionTime datetime, + LastCompletionTime datetime, + PlanHandle varbinary(64), + [Remove Plan Handle From Cache] AS + CASE WHEN [PlanHandle] IS NOT NULL + THEN ''DBCC FREEPROCCACHE ('' + CONVERT(VARCHAR(128), [PlanHandle], 1) + '');'' + ELSE ''N/A'' END, + SqlHandle varbinary(64), + [Remove SQL Handle From Cache] AS + CASE WHEN [SqlHandle] IS NOT NULL + THEN ''DBCC FREEPROCCACHE ('' + CONVERT(VARCHAR(128), [SqlHandle], 1) + '');'' + ELSE ''N/A'' END, + [SQL Handle More Info] AS + CASE WHEN [SqlHandle] IS NOT NULL + THEN ''EXEC sp_BlitzCache @OnlySqlHandles = '''''' + CONVERT(VARCHAR(128), [SqlHandle], 1) + ''''''; '' + ELSE ''N/A'' END, + QueryHash binary(8), + [Query Hash More Info] AS + CASE WHEN [QueryHash] IS NOT NULL + THEN ''EXEC sp_BlitzCache @OnlyQueryHashes = '''''' + CONVERT(VARCHAR(32), [QueryHash], 1) + ''''''; '' + ELSE ''N/A'' END, + QueryPlanHash binary(8), + StatementStartOffset int, + StatementEndOffset int, + PlanGenerationNum bigint, + MinReturnedRows bigint, + MaxReturnedRows bigint, + AverageReturnedRows money, + TotalReturnedRows bigint, + QueryText nvarchar(max), + QueryPlan xml, + NumberOfPlans int, + NumberOfDistinctPlans int, + MinGrantKB BIGINT, + MaxGrantKB BIGINT, + MinUsedGrantKB BIGINT, + MaxUsedGrantKB BIGINT, + PercentMemoryGrantUsed MONEY, + AvgMaxMemoryGrant MONEY, + MinSpills BIGINT, + MaxSpills BIGINT, + TotalSpills BIGINT, + AvgSpills MONEY, + QueryPlanCost FLOAT, + Pattern NVARCHAR(20), + JoinKey AS ServerName + Cast(CheckDate AS NVARCHAR(50)), + CONSTRAINT [PK_' + REPLACE(REPLACE(@OutputTableName,N'[',N''),N']',N'') + N'] PRIMARY KEY CLUSTERED(ID ASC));' + ); + SET @StringToExecute += N'IF EXISTS(SELECT * FROM ' + +@OutputDatabaseName + +N'.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' + +@OutputSchemaName + +N''') AND EXISTS (SELECT * FROM ' + +@OutputDatabaseName+ + N'.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''' + +@OutputSchemaName + +N''' AND QUOTENAME(TABLE_NAME) = ''' + +@OutputTableName + +N''') AND EXISTS (SELECT * FROM ' + +@OutputDatabaseName+ + N'.sys.computed_columns WHERE [name] = N''PlanCreationTimeHours'' AND QUOTENAME(OBJECT_NAME(object_id)) = N''' + +@OutputTableName + +N''' AND [definition] = N''(datediff(hour,[PlanCreationTime],sysdatetime()))'') +BEGIN + RAISERROR(''We noticed that you are running an old computed column definition for PlanCreationTimeHours, fixing that now'',0,0) WITH NOWAIT; + ALTER TABLE '+@OutputDatabaseName+N'.'+@OutputSchemaName+N'.'+@OutputTableName+N' DROP COLUMN [PlanCreationTimeHours]; + ALTER TABLE '+@OutputDatabaseName+N'.'+@OutputSchemaName+N'.'+@OutputTableName+N' ADD [PlanCreationTimeHours] AS DATEDIFF(HOUR,CONVERT(DATETIMEOFFSET(7),[PlanCreationTime]),[CheckDate]); +END '; -IF @ExpertMode > 0 -BEGIN -RAISERROR(N'Checking for many to many merge joins', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET b.is_mm_join = 1 -FROM #relop AS r -JOIN ##BlitzCacheProcs AS b -ON b.SqlHandle = r.SqlHandle -WHERE r.relop.exist('/p:RelOp/p:Merge/@ManyToMany[.="1"]') = 1 -OPTION (RECOMPILE); -END ; - - -IF @ExpertMode > 0 -BEGIN -RAISERROR(N'Is Paul White Electric?', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p), -is_paul_white_electric AS ( -SELECT 1 AS [is_paul_white_electric], -r.SqlHandle -FROM #relop AS r -CROSS APPLY r.relop.nodes('//p:RelOp') c(n) -WHERE c.n.exist('@PhysicalOp[.="Switch"]') = 1 -) -UPDATE b -SET b.is_paul_white_electric = ipwe.is_paul_white_electric -FROM ##BlitzCacheProcs AS b -JOIN is_paul_white_electric ipwe -ON ipwe.SqlHandle = b.SqlHandle -WHERE b.SPID = @@SPID -OPTION (RECOMPILE); -END ; - + IF @ValidOutputServer = 1 + BEGIN + SET @StringToExecute = REPLACE(@StringToExecute,''''+@OutputSchemaName+'''',''''''+@OutputSchemaName+''''''); + SET @StringToExecute = REPLACE(@StringToExecute,''''+@OutputTableName+'''',''''''+@OutputTableName+''''''); + SET @StringToExecute = REPLACE(@StringToExecute,'xml','nvarchar(max)'); + SET @StringToExecute = REPLACE(@StringToExecute,'''DBCC FREEPROCCACHE ('' + CONVERT(VARCHAR(128), [PlanHandle], 1) + '');''','''''DBCC FREEPROCCACHE ('''' + CONVERT(VARCHAR(128), [PlanHandle], 1) + '''');'''''); + SET @StringToExecute = REPLACE(@StringToExecute,'''DBCC FREEPROCCACHE ('' + CONVERT(VARCHAR(128), [SqlHandle], 1) + '');''','''''DBCC FREEPROCCACHE ('''' + CONVERT(VARCHAR(128), [SqlHandle], 1) + '''');'''''); + SET @StringToExecute = REPLACE(@StringToExecute,'''EXEC sp_BlitzCache @OnlySqlHandles = '''''' + CONVERT(VARCHAR(128), [SqlHandle], 1) + ''''''; ''','''''EXEC sp_BlitzCache @OnlySqlHandles = '''''''' + CONVERT(VARCHAR(128), [SqlHandle], 1) + ''''''''; '''''); + SET @StringToExecute = REPLACE(@StringToExecute,'''EXEC sp_BlitzCache @OnlyQueryHashes = '''''' + CONVERT(VARCHAR(32), [QueryHash], 1) + ''''''; ''','''''EXEC sp_BlitzCache @OnlyQueryHashes = '''''''' + CONVERT(VARCHAR(32), [QueryHash], 1) + ''''''''; '''''); + SET @StringToExecute = REPLACE(@StringToExecute,'''N/A''','''''N/A'''''); + + IF @Debug = 1 + BEGIN + PRINT SUBSTRING(@StringToExecute, 0, 4000); + PRINT SUBSTRING(@StringToExecute, 4000, 8000); + PRINT SUBSTRING(@StringToExecute, 8000, 12000); + PRINT SUBSTRING(@StringToExecute, 12000, 16000); + PRINT SUBSTRING(@StringToExecute, 16000, 20000); + PRINT SUBSTRING(@StringToExecute, 20000, 24000); + PRINT SUBSTRING(@StringToExecute, 24000, 28000); + PRINT SUBSTRING(@StringToExecute, 28000, 32000); + PRINT SUBSTRING(@StringToExecute, 32000, 36000); + PRINT SUBSTRING(@StringToExecute, 36000, 40000); + END; + EXEC('EXEC('''+@StringToExecute+''') AT ' + @OutputServerName); + END; + ELSE + BEGIN + IF @Debug = 1 + BEGIN + PRINT SUBSTRING(@StringToExecute, 0, 4000); + PRINT SUBSTRING(@StringToExecute, 4000, 8000); + PRINT SUBSTRING(@StringToExecute, 8000, 12000); + PRINT SUBSTRING(@StringToExecute, 12000, 16000); + PRINT SUBSTRING(@StringToExecute, 16000, 20000); + PRINT SUBSTRING(@StringToExecute, 20000, 24000); + PRINT SUBSTRING(@StringToExecute, 24000, 28000); + PRINT SUBSTRING(@StringToExecute, 28000, 32000); + PRINT SUBSTRING(@StringToExecute, 32000, 36000); + PRINT SUBSTRING(@StringToExecute, 36000, 40000); + END; + EXEC(@StringToExecute); + END; + + /* If the table doesn't have the new LastCompletionTime column, add it. See Github #2377. */ + SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableName; + SET @StringToExecute = N'IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns + WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + ''')) AND name = ''LastCompletionTime'') + ALTER TABLE ' + @ObjectFullName + N' ADD LastCompletionTime DATETIME NULL;'; + IF @ValidOutputServer = 1 + BEGIN + SET @StringToExecute = REPLACE(@StringToExecute,'''LastCompletionTime''','''''LastCompletionTime'''''); + SET @StringToExecute = REPLACE(@StringToExecute,'''' + @ObjectFullName + '''','''''' + @ObjectFullName + ''''''); + EXEC('EXEC('''+@StringToExecute+''') AT ' + @OutputServerName); + END; + ELSE + BEGIN + EXEC(@StringToExecute); + END; -RAISERROR(N'Checking for non-sargable predicates', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES ( 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) -, nsarg - AS ( SELECT r.QueryHash, 1 AS fn, 0 AS jo, 0 AS lk - FROM #relop AS r - CROSS APPLY r.relop.nodes('/p:RelOp/p:IndexScan/p:Predicate/p:ScalarOperator/p:Compare/p:ScalarOperator') AS ca(x) - WHERE ( ca.x.exist('//p:ScalarOperator/p:Intrinsic/@FunctionName') = 1 - OR ca.x.exist('//p:ScalarOperator/p:IF') = 1 ) - UNION ALL - SELECT r.QueryHash, 0 AS fn, 1 AS jo, 0 AS lk - FROM #relop AS r - CROSS APPLY r.relop.nodes('/p:RelOp//p:ScalarOperator') AS ca(x) - WHERE r.relop.exist('/p:RelOp[contains(@LogicalOp, "Join")]') = 1 - AND ca.x.exist('//p:ScalarOperator[contains(@ScalarString, "Expr")]') = 1 - UNION ALL - SELECT r.QueryHash, 0 AS fn, 0 AS jo, 1 AS lk - FROM #relop AS r - CROSS APPLY r.relop.nodes('/p:RelOp/p:IndexScan/p:Predicate/p:ScalarOperator') AS ca(x) - CROSS APPLY ca.x.nodes('//p:Const') AS co(x) - WHERE ca.x.exist('//p:ScalarOperator/p:Intrinsic/@FunctionName[.="like"]') = 1 - AND ( ( co.x.value('substring(@ConstValue, 1, 1)', 'VARCHAR(100)') <> 'N' - AND co.x.value('substring(@ConstValue, 2, 1)', 'VARCHAR(100)') = '%' ) - OR ( co.x.value('substring(@ConstValue, 1, 1)', 'VARCHAR(100)') = 'N' - AND co.x.value('substring(@ConstValue, 3, 1)', 'VARCHAR(100)') = '%' ))), - d_nsarg - AS ( SELECT DISTINCT - nsarg.QueryHash - FROM nsarg - WHERE nsarg.fn = 1 - OR nsarg.jo = 1 - OR nsarg.lk = 1 ) -UPDATE b -SET b.is_nonsargable = 1 -FROM d_nsarg AS d -JOIN ##BlitzCacheProcs AS b - ON b.QueryHash = d.QueryHash -WHERE b.SPID = @@SPID -OPTION ( RECOMPILE ); + /* If the table doesn't have the new PlanGenerationNum column, add it. See Github #2514. */ + SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableName; + SET @StringToExecute = N'IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns + WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''PlanGenerationNum'') + ALTER TABLE ' + @ObjectFullName + N' ADD PlanGenerationNum BIGINT NULL;'; + IF @ValidOutputServer = 1 + BEGIN + SET @StringToExecute = REPLACE(@StringToExecute,'''PlanGenerationNum''','''''PlanGenerationNum'''''); + SET @StringToExecute = REPLACE(@StringToExecute,'''' + @ObjectFullName + '''','''''' + @ObjectFullName + ''''''); + EXEC('EXEC('''+@StringToExecute+''') AT ' + @OutputServerName); + END; + ELSE + BEGIN + EXEC(@StringToExecute); + END; + + /* If the table doesn't have the new Pattern column, add it */ + SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableName; + SET @StringToExecute = N'IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns + WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''Pattern'') + ALTER TABLE ' + @ObjectFullName + N' ADD Pattern NVARCHAR(20) NULL;'; + IF @ValidOutputServer = 1 + BEGIN + SET @StringToExecute = REPLACE(@StringToExecute,'''Pattern''','''''Pattern'''''); + SET @StringToExecute = REPLACE(@StringToExecute,'''' + @ObjectFullName + '''','''''' + @ObjectFullName + ''''''); + EXEC('EXEC('''+@StringToExecute+''') AT ' + @OutputServerName); + END; + ELSE + BEGIN + EXEC(@StringToExecute); + END + + IF @CheckDateOverride IS NULL + BEGIN + SET @CheckDateOverride = SYSDATETIMEOFFSET(); + END; + + IF @ValidOutputServer = 1 + BEGIN + SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' + + @OutputServerName + '.' + + @OutputDatabaseName + + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' + + @OutputSchemaName + ''') INSERT ' + + @OutputServerName + '.' + + @OutputDatabaseName + '.' + + @OutputSchemaName + '.' + + @OutputTableName + + ' (ServerName, CheckDate, Version, QueryType, DatabaseName, AverageCPU, TotalCPU, PercentCPUByType, CPUWeight, AverageDuration, TotalDuration, DurationWeight, PercentDurationByType, AverageReads, TotalReads, ReadWeight, PercentReadsByType, ' + + ' AverageWrites, TotalWrites, WriteWeight, PercentWritesByType, ExecutionCount, ExecutionWeight, PercentExecutionsByType, ' + + ' ExecutionsPerMinute, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, QueryHash, QueryPlanHash, StatementStartOffset, StatementEndOffset, PlanGenerationNum, MinReturnedRows, MaxReturnedRows, AverageReturnedRows, TotalReturnedRows, QueryText, QueryPlan, NumberOfPlans, NumberOfDistinctPlans, Warnings, ' + + ' SerialRequiredMemory, SerialDesiredMemory, MinGrantKB, MaxGrantKB, MinUsedGrantKB, MaxUsedGrantKB, PercentMemoryGrantUsed, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, QueryPlanCost, Pattern ) ' + + 'SELECT TOP (@Top) ' + + QUOTENAME(CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128)), '''') + ', @CheckDateOverride, ' + + QUOTENAME(CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)), '''') + ', ' + + ' QueryType, DatabaseName, AverageCPU, TotalCPU, PercentCPUByType, PercentCPU, AverageDuration, TotalDuration, PercentDuration, PercentDurationByType, AverageReads, TotalReads, PercentReads, PercentReadsByType, ' + + ' AverageWrites, TotalWrites, PercentWrites, PercentWritesByType, ExecutionCount, PercentExecutions, PercentExecutionsByType, ' + + ' ExecutionsPerMinute, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, QueryHash, QueryPlanHash, StatementStartOffset, StatementEndOffset, PlanGenerationNum, MinReturnedRows, MaxReturnedRows, AverageReturnedRows, TotalReturnedRows, QueryText, CAST(QueryPlan AS NVARCHAR(MAX)), NumberOfPlans, NumberOfDistinctPlans, Warnings, ' + + ' SerialRequiredMemory, SerialDesiredMemory, MinGrantKB, MaxGrantKB, MinUsedGrantKB, MaxUsedGrantKB, PercentMemoryGrantUsed, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, QueryPlanCost, Pattern ' + + ' FROM ##BlitzCacheProcs ' + + ' WHERE 1=1 '; -/*Begin implicit conversion and parameter info */ + IF @MinimumExecutionCount IS NOT NULL + BEGIN + SET @StringToExecute += N' AND ExecutionCount >= @MinimumExecutionCount '; + END; + IF @MinutesBack IS NOT NULL + BEGIN + SET @StringToExecute += N' AND LastCompletionTime >= DATEADD(MINUTE, @min_back, GETDATE() ) '; + END; + SET @StringToExecute += N' AND SPID = @@SPID '; + SELECT @StringToExecute += N' ORDER BY ' + CASE @SortOrder WHEN 'cpu' THEN N' TotalCPU ' + WHEN N'reads' THEN N' TotalReads ' + WHEN N'writes' THEN N' TotalWrites ' + WHEN N'duration' THEN N' TotalDuration ' + WHEN N'executions' THEN N' ExecutionCount ' + WHEN N'compiles' THEN N' PlanCreationTime ' + WHEN N'memory grant' THEN N' MaxGrantKB' + WHEN N'spills' THEN N' MaxSpills' + WHEN N'avg cpu' THEN N' AverageCPU' + WHEN N'avg reads' THEN N' AverageReads' + WHEN N'avg writes' THEN N' AverageWrites' + WHEN N'avg duration' THEN N' AverageDuration' + WHEN N'avg executions' THEN N' ExecutionsPerMinute' + WHEN N'avg memory grant' THEN N' AvgMaxMemoryGrant' + WHEN N'avg spills' THEN N' AvgSpills' + WHEN N'unused grant' THEN N' MaxGrantKB - MaxUsedGrantKB' + ELSE N' TotalCPU ' + END + N' DESC '; + SET @StringToExecute += N' OPTION (RECOMPILE) ; '; + + IF @Debug = 1 + BEGIN + PRINT SUBSTRING(@StringToExecute, 1, 4000); + PRINT SUBSTRING(@StringToExecute, 4001, 4000); + PRINT SUBSTRING(@StringToExecute, 8001, 4000); + PRINT SUBSTRING(@StringToExecute, 12001, 4000); + PRINT SUBSTRING(@StringToExecute, 16001, 4000); + PRINT SUBSTRING(@StringToExecute, 20001, 4000); + PRINT SUBSTRING(@StringToExecute, 24001, 4000); + PRINT SUBSTRING(@StringToExecute, 28001, 4000); + PRINT SUBSTRING(@StringToExecute, 32001, 4000); + PRINT SUBSTRING(@StringToExecute, 36001, 4000); + END; -RAISERROR(N'Getting information about implicit conversions and stored proc parameters', 0, 1) WITH NOWAIT; + EXEC sp_executesql @StringToExecute, N'@Top INT, @min_duration INT, @min_back INT, @CheckDateOverride DATETIMEOFFSET, @MinimumExecutionCount INT', @Top, @DurationFilter_i, @MinutesBack, @CheckDateOverride, @MinimumExecutionCount; + END; + ELSE + BEGIN + SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' + + @OutputDatabaseName + + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' + + @OutputSchemaName + ''') INSERT ' + + @OutputDatabaseName + '.' + + @OutputSchemaName + '.' + + @OutputTableName + + ' (ServerName, CheckDate, Version, QueryType, DatabaseName, AverageCPU, TotalCPU, PercentCPUByType, CPUWeight, AverageDuration, TotalDuration, DurationWeight, PercentDurationByType, AverageReads, TotalReads, ReadWeight, PercentReadsByType, ' + + ' AverageWrites, TotalWrites, WriteWeight, PercentWritesByType, ExecutionCount, ExecutionWeight, PercentExecutionsByType, ' + + ' ExecutionsPerMinute, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, QueryHash, QueryPlanHash, StatementStartOffset, StatementEndOffset, PlanGenerationNum, MinReturnedRows, MaxReturnedRows, AverageReturnedRows, TotalReturnedRows, QueryText, QueryPlan, NumberOfPlans, NumberOfDistinctPlans, Warnings, ' + + ' SerialRequiredMemory, SerialDesiredMemory, MinGrantKB, MaxGrantKB, MinUsedGrantKB, MaxUsedGrantKB, PercentMemoryGrantUsed, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, QueryPlanCost, Pattern ) ' + + 'SELECT TOP (@Top) ' + + QUOTENAME(CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128)), '''') + ', @CheckDateOverride, ' + + QUOTENAME(CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)), '''') + ', ' + + ' QueryType, DatabaseName, AverageCPU, TotalCPU, PercentCPUByType, PercentCPU, AverageDuration, TotalDuration, PercentDuration, PercentDurationByType, AverageReads, TotalReads, PercentReads, PercentReadsByType, ' + + ' AverageWrites, TotalWrites, PercentWrites, PercentWritesByType, ExecutionCount, PercentExecutions, PercentExecutionsByType, ' + + ' ExecutionsPerMinute, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, QueryHash, QueryPlanHash, StatementStartOffset, StatementEndOffset, PlanGenerationNum, MinReturnedRows, MaxReturnedRows, AverageReturnedRows, TotalReturnedRows, QueryText, QueryPlan, NumberOfPlans, NumberOfDistinctPlans, Warnings, ' + + ' SerialRequiredMemory, SerialDesiredMemory, MinGrantKB, MaxGrantKB, MinUsedGrantKB, MaxUsedGrantKB, PercentMemoryGrantUsed, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, QueryPlanCost, Pattern ' + + ' FROM ##BlitzCacheProcs ' + + ' WHERE 1=1 '; -RAISERROR(N'Getting variable info', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES ( 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) -INSERT #variable_info ( SPID, QueryHash, SqlHandle, proc_name, variable_name, variable_datatype, compile_time_value ) -SELECT DISTINCT @@SPID, - qp.QueryHash, - qp.SqlHandle, - b.QueryType AS proc_name, - q.n.value('@Column', 'NVARCHAR(258)') AS variable_name, - q.n.value('@ParameterDataType', 'NVARCHAR(258)') AS variable_datatype, - q.n.value('@ParameterCompiledValue', 'NVARCHAR(258)') AS compile_time_value -FROM #query_plan AS qp -JOIN ##BlitzCacheProcs AS b -ON (b.QueryType = 'adhoc' AND b.QueryHash = qp.QueryHash) -OR (b.QueryType <> 'adhoc' AND b.SqlHandle = qp.SqlHandle) -CROSS APPLY qp.query_plan.nodes('//p:QueryPlan/p:ParameterList/p:ColumnReference') AS q(n) -WHERE b.SPID = @@SPID -OPTION (RECOMPILE); + IF @MinimumExecutionCount IS NOT NULL + BEGIN + SET @StringToExecute += N' AND ExecutionCount >= @MinimumExecutionCount '; + END; + IF @MinutesBack IS NOT NULL + BEGIN + SET @StringToExecute += N' AND LastCompletionTime >= DATEADD(MINUTE, @min_back, GETDATE() ) '; + END; + SET @StringToExecute += N' AND SPID = @@SPID '; + SELECT @StringToExecute += N' ORDER BY ' + CASE @SortOrder WHEN 'cpu' THEN N' TotalCPU ' + WHEN N'reads' THEN N' TotalReads ' + WHEN N'writes' THEN N' TotalWrites ' + WHEN N'duration' THEN N' TotalDuration ' + WHEN N'executions' THEN N' ExecutionCount ' + WHEN N'compiles' THEN N' PlanCreationTime ' + WHEN N'memory grant' THEN N' MaxGrantKB' + WHEN N'spills' THEN N' MaxSpills' + WHEN N'avg cpu' THEN N' AverageCPU' + WHEN N'avg reads' THEN N' AverageReads' + WHEN N'avg writes' THEN N' AverageWrites' + WHEN N'avg duration' THEN N' AverageDuration' + WHEN N'avg executions' THEN N' ExecutionsPerMinute' + WHEN N'avg memory grant' THEN N' AvgMaxMemoryGrant' + WHEN N'avg spills' THEN N' AvgSpills' + WHEN N'unused grant' THEN N' MaxGrantKB - MaxUsedGrantKB' + ELSE N' TotalCPU ' + END + N' DESC '; + SET @StringToExecute += N' OPTION (RECOMPILE) ; '; + + IF @Debug = 1 + BEGIN + PRINT SUBSTRING(@StringToExecute, 0, 4000); + PRINT SUBSTRING(@StringToExecute, 4000, 8000); + PRINT SUBSTRING(@StringToExecute, 8000, 12000); + PRINT SUBSTRING(@StringToExecute, 12000, 16000); + PRINT SUBSTRING(@StringToExecute, 16000, 20000); + PRINT SUBSTRING(@StringToExecute, 20000, 24000); + PRINT SUBSTRING(@StringToExecute, 24000, 28000); + PRINT SUBSTRING(@StringToExecute, 28000, 32000); + PRINT SUBSTRING(@StringToExecute, 32000, 36000); + PRINT SUBSTRING(@StringToExecute, 36000, 40000); + END; + EXEC sp_executesql @StringToExecute, N'@Top INT, @min_duration INT, @min_back INT, @CheckDateOverride DATETIMEOFFSET, @MinimumExecutionCount INT', @Top, @DurationFilter_i, @MinutesBack, @CheckDateOverride, @MinimumExecutionCount; + END; + END; + ELSE IF (SUBSTRING(@OutputTableName, 2, 2) = '##') + BEGIN + IF @ValidOutputServer = 1 + BEGIN + RAISERROR('Due to the nature of temporary tables, outputting to a linked server requires a permanent table.', 16, 0); + END; + ELSE IF @OutputTableName IN ('##BlitzCacheProcs','##BlitzCacheResults') + BEGIN + RAISERROR('OutputTableName is a reserved name for this procedure. We only use ##BlitzCacheProcs and ##BlitzCacheResults, please choose another table name.', 16, 0); + END; + ELSE + BEGIN + SET @StringToExecute = N' IF (OBJECT_ID(''tempdb..' + + @OutputTableName + + ''') IS NOT NULL) DROP TABLE ' + @OutputTableName + ';' + + 'CREATE TABLE ' + + @OutputTableName + + ' (ID bigint NOT NULL IDENTITY(1,1), + ServerName NVARCHAR(258), + CheckDate DATETIMEOFFSET, + Version NVARCHAR(258), + QueryType NVARCHAR(258), + Warnings varchar(max), + DatabaseName sysname, + SerialDesiredMemory float, + SerialRequiredMemory float, + AverageCPU bigint, + TotalCPU bigint, + PercentCPUByType money, + CPUWeight money, + AverageDuration bigint, + TotalDuration bigint, + DurationWeight money, + PercentDurationByType money, + AverageReads bigint, + TotalReads bigint, + ReadWeight money, + PercentReadsByType money, + AverageWrites bigint, + TotalWrites bigint, + WriteWeight money, + PercentWritesByType money, + ExecutionCount bigint, + ExecutionWeight money, + PercentExecutionsByType money, + ExecutionsPerMinute money, + PlanCreationTime datetime,' + N' + PlanCreationTimeHours AS DATEDIFF(HOUR, PlanCreationTime, SYSDATETIME()), + LastExecutionTime datetime, + LastCompletionTime datetime, + PlanHandle varbinary(64), + [Remove Plan Handle From Cache] AS + CASE WHEN [PlanHandle] IS NOT NULL + THEN ''DBCC FREEPROCCACHE ('' + CONVERT(VARCHAR(128), [PlanHandle], 1) + '');'' + ELSE ''N/A'' END, + SqlHandle varbinary(64), + [Remove SQL Handle From Cache] AS + CASE WHEN [SqlHandle] IS NOT NULL + THEN ''DBCC FREEPROCCACHE ('' + CONVERT(VARCHAR(128), [SqlHandle], 1) + '');'' + ELSE ''N/A'' END, + [SQL Handle More Info] AS + CASE WHEN [SqlHandle] IS NOT NULL + THEN ''EXEC sp_BlitzCache @OnlySqlHandles = '''''' + CONVERT(VARCHAR(128), [SqlHandle], 1) + ''''''; '' + ELSE ''N/A'' END, + QueryHash binary(8), + [Query Hash More Info] AS + CASE WHEN [QueryHash] IS NOT NULL + THEN ''EXEC sp_BlitzCache @OnlyQueryHashes = '''''' + CONVERT(VARCHAR(32), [QueryHash], 1) + ''''''; '' + ELSE ''N/A'' END, + QueryPlanHash binary(8), + StatementStartOffset int, + StatementEndOffset int, + PlanGenerationNum bigint, + MinReturnedRows bigint, + MaxReturnedRows bigint, + AverageReturnedRows money, + TotalReturnedRows bigint, + QueryText nvarchar(max), + QueryPlan xml, + NumberOfPlans int, + NumberOfDistinctPlans int, + MinGrantKB BIGINT, + MaxGrantKB BIGINT, + MinUsedGrantKB BIGINT, + MaxUsedGrantKB BIGINT, + PercentMemoryGrantUsed MONEY, + AvgMaxMemoryGrant MONEY, + MinSpills BIGINT, + MaxSpills BIGINT, + TotalSpills BIGINT, + AvgSpills MONEY, + QueryPlanCost FLOAT, + Pattern NVARCHAR(20), + JoinKey AS ServerName + Cast(CheckDate AS NVARCHAR(50)), + CONSTRAINT [PK_' + REPLACE(REPLACE(@OutputTableName,'[',''),']','') + '] PRIMARY KEY CLUSTERED(ID ASC));'; + SET @StringToExecute += N' INSERT ' + + @OutputTableName + + ' (ServerName, CheckDate, Version, QueryType, DatabaseName, AverageCPU, TotalCPU, PercentCPUByType, CPUWeight, AverageDuration, TotalDuration, DurationWeight, PercentDurationByType, AverageReads, TotalReads, ReadWeight, PercentReadsByType, ' + + ' AverageWrites, TotalWrites, WriteWeight, PercentWritesByType, ExecutionCount, ExecutionWeight, PercentExecutionsByType, ' + + ' ExecutionsPerMinute, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, QueryHash, QueryPlanHash, StatementStartOffset, StatementEndOffset, PlanGenerationNum, MinReturnedRows, MaxReturnedRows, AverageReturnedRows, TotalReturnedRows, QueryText, QueryPlan, NumberOfPlans, NumberOfDistinctPlans, Warnings, ' + + ' SerialRequiredMemory, SerialDesiredMemory, MinGrantKB, MaxGrantKB, MinUsedGrantKB, MaxUsedGrantKB, PercentMemoryGrantUsed, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, QueryPlanCost, Pattern ) ' + + 'SELECT TOP (@Top) ' + + QUOTENAME(CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128)), '''') + ', @CheckDateOverride, ' + + QUOTENAME(CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)), '''') + ', ' + + ' QueryType, DatabaseName, AverageCPU, TotalCPU, PercentCPUByType, PercentCPU, AverageDuration, TotalDuration, PercentDuration, PercentDurationByType, AverageReads, TotalReads, PercentReads, PercentReadsByType, ' + + ' AverageWrites, TotalWrites, PercentWrites, PercentWritesByType, ExecutionCount, PercentExecutions, PercentExecutionsByType, ' + + ' ExecutionsPerMinute, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, QueryHash, QueryPlanHash, StatementStartOffset, StatementEndOffset, PlanGenerationNum, MinReturnedRows, MaxReturnedRows, AverageReturnedRows, TotalReturnedRows, QueryText, QueryPlan, NumberOfPlans, NumberOfDistinctPlans, Warnings, ' + + ' SerialRequiredMemory, SerialDesiredMemory, MinGrantKB, MaxGrantKB, MinUsedGrantKB, MaxUsedGrantKB, PercentMemoryGrantUsed, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, QueryPlanCost, Pattern ' + + ' FROM ##BlitzCacheProcs ' + + ' WHERE 1=1 '; + + IF @MinimumExecutionCount IS NOT NULL + BEGIN + SET @StringToExecute += N' AND ExecutionCount >= @MinimumExecutionCount '; + END; -RAISERROR(N'Getting conversion info', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES ( 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) -INSERT #conversion_info ( SPID, QueryHash, SqlHandle, proc_name, expression ) -SELECT DISTINCT @@SPID, - qp.QueryHash, - qp.SqlHandle, - b.QueryType AS proc_name, - qq.c.value('@Expression', 'NVARCHAR(4000)') AS expression -FROM #query_plan AS qp -JOIN ##BlitzCacheProcs AS b -ON (b.QueryType = 'adhoc' AND b.QueryHash = qp.QueryHash) -OR (b.QueryType <> 'adhoc' AND b.SqlHandle = qp.SqlHandle) -CROSS APPLY qp.query_plan.nodes('//p:QueryPlan/p:Warnings/p:PlanAffectingConvert') AS qq(c) -WHERE qq.c.exist('@ConvertIssue[.="Seek Plan"]') = 1 - AND qp.QueryHash IS NOT NULL - AND b.implicit_conversions = 1 -AND b.SPID = @@SPID -OPTION (RECOMPILE); + IF @MinutesBack IS NOT NULL + BEGIN + SET @StringToExecute += N' AND LastCompletionTime >= DATEADD(MINUTE, @min_back, GETDATE() ) '; + END; + SET @StringToExecute += N' AND SPID = @@SPID '; + + SELECT @StringToExecute += N' ORDER BY ' + CASE @SortOrder WHEN 'cpu' THEN N' TotalCPU ' + WHEN N'reads' THEN N' TotalReads ' + WHEN N'writes' THEN N' TotalWrites ' + WHEN N'duration' THEN N' TotalDuration ' + WHEN N'executions' THEN N' ExecutionCount ' + WHEN N'compiles' THEN N' PlanCreationTime ' + WHEN N'memory grant' THEN N' MaxGrantKB' + WHEN N'spills' THEN N' MaxSpills' + WHEN N'avg cpu' THEN N' AverageCPU' + WHEN N'avg reads' THEN N' AverageReads' + WHEN N'avg writes' THEN N' AverageWrites' + WHEN N'avg duration' THEN N' AverageDuration' + WHEN N'avg executions' THEN N' ExecutionsPerMinute' + WHEN N'avg memory grant' THEN N' AvgMaxMemoryGrant' + WHEN N'avg spills' THEN N' AvgSpills' + WHEN N'unused grant' THEN N' MaxGrantKB - MaxUsedGrantKB' + ELSE N' TotalCPU ' + END + N' DESC '; -RAISERROR(N'Parsing conversion info', 0, 1) WITH NOWAIT; -INSERT #stored_proc_info ( SPID, SqlHandle, QueryHash, proc_name, variable_name, variable_datatype, converted_column_name, column_name, converted_to, compile_time_value ) -SELECT @@SPID AS SPID, - ci.SqlHandle, - ci.QueryHash, - REPLACE(REPLACE(REPLACE(ci.proc_name, ')', ''), 'Statement (parent ', ''), 'Procedure or Function: ', '') AS proc_name, - CASE WHEN ci.at_charindex > 0 - AND ci.bracket_charindex > 0 - THEN SUBSTRING(ci.expression, ci.at_charindex, ci.bracket_charindex) - ELSE N'**no_variable**' - END AS variable_name, - N'**no_variable**' AS variable_datatype, - CASE WHEN ci.at_charindex = 0 - AND ci.comma_charindex > 0 - AND ci.second_comma_charindex > 0 - THEN SUBSTRING(ci.expression, ci.comma_charindex, ci.second_comma_charindex) - ELSE N'**no_column**' - END AS converted_column_name, - CASE WHEN ci.at_charindex = 0 - AND ci.equal_charindex > 0 - AND ci.convert_implicit_charindex = 0 - THEN SUBSTRING(ci.expression, ci.equal_charindex, 4000) - WHEN ci.at_charindex = 0 - AND (ci.equal_charindex -1) > 0 - AND ci.convert_implicit_charindex > 0 - THEN SUBSTRING(ci.expression, 0, ci.equal_charindex -1) - WHEN ci.at_charindex > 0 - AND ci.comma_charindex > 0 - AND ci.second_comma_charindex > 0 - THEN SUBSTRING(ci.expression, ci.comma_charindex, ci.second_comma_charindex) - ELSE N'**no_column **' - END AS column_name, - CASE WHEN ci.paren_charindex > 0 - 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 - 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 -FROM #conversion_info AS ci -OPTION (RECOMPILE); + SET @StringToExecute += N' OPTION (RECOMPILE) ; '; + + IF @Debug = 1 + BEGIN + PRINT SUBSTRING(@StringToExecute, 0, 4000); + PRINT SUBSTRING(@StringToExecute, 4000, 8000); + PRINT SUBSTRING(@StringToExecute, 8000, 12000); + PRINT SUBSTRING(@StringToExecute, 12000, 16000); + PRINT SUBSTRING(@StringToExecute, 16000, 20000); + PRINT SUBSTRING(@StringToExecute, 20000, 24000); + PRINT SUBSTRING(@StringToExecute, 24000, 28000); + PRINT SUBSTRING(@StringToExecute, 28000, 32000); + PRINT SUBSTRING(@StringToExecute, 32000, 36000); + PRINT SUBSTRING(@StringToExecute, 36000, 40000); + PRINT SUBSTRING(@StringToExecute, 34000, 40000); + END; + EXEC sp_executesql @StringToExecute, N'@Top INT, @min_duration INT, @min_back INT, @CheckDateOverride DATETIMEOFFSET, @MinimumExecutionCount INT', @Top, @DurationFilter_i, @MinutesBack, @CheckDateOverride, @MinimumExecutionCount; + END; + END; + ELSE IF (SUBSTRING(@OutputTableName, 2, 1) = '#') + BEGIN + RAISERROR('Due to the nature of Dymamic SQL, only global (i.e. double pound (##)) temp tables are supported for @OutputTableName', 16, 0); +END; /* End of writing results to table */ +END; /*Final End*/ -RAISERROR(N'Updating variables for inserted procs', 0, 1) WITH NOWAIT; -UPDATE sp -SET sp.variable_datatype = vi.variable_datatype, - sp.compile_time_value = vi.compile_time_value -FROM #stored_proc_info AS sp -JOIN #variable_info AS vi -ON (sp.proc_name = 'adhoc' AND sp.QueryHash = vi.QueryHash) -OR (sp.proc_name <> 'adhoc' AND sp.SqlHandle = vi.SqlHandle) -AND sp.variable_name = vi.variable_name -OPTION (RECOMPILE); +GO +IF OBJECT_ID('dbo.sp_BlitzFirst') IS NULL + EXEC ('CREATE PROCEDURE dbo.sp_BlitzFirst AS RETURN 0;'); +GO -RAISERROR(N'Inserting variables for other procs', 0, 1) WITH NOWAIT; -INSERT #stored_proc_info - ( SPID, SqlHandle, QueryHash, variable_name, variable_datatype, compile_time_value, proc_name ) -SELECT vi.SPID, vi.SqlHandle, vi.QueryHash, vi.variable_name, vi.variable_datatype, vi.compile_time_value, REPLACE(REPLACE(REPLACE(vi.proc_name, ')', ''), 'Statement (parent ', ''), 'Procedure or Function: ', '') AS proc_name -FROM #variable_info AS vi -WHERE NOT EXISTS -( - SELECT * - FROM #stored_proc_info AS sp - WHERE (sp.proc_name = 'adhoc' AND sp.QueryHash = vi.QueryHash) - OR (sp.proc_name <> 'adhoc' AND sp.SqlHandle = vi.SqlHandle) -) -OPTION (RECOMPILE); +ALTER PROCEDURE [dbo].[sp_BlitzFirst] + @LogMessage NVARCHAR(4000) = NULL , + @Help TINYINT = 0 , + @AsOf DATETIMEOFFSET = NULL , + @ExpertMode TINYINT = 0 , + @Seconds INT = 5 , + @OutputType VARCHAR(20) = 'TABLE' , + @OutputServerName NVARCHAR(256) = NULL , + @OutputDatabaseName NVARCHAR(256) = NULL , + @OutputSchemaName NVARCHAR(256) = NULL , + @OutputTableName NVARCHAR(256) = NULL , + @OutputTableNameFileStats NVARCHAR(256) = NULL , + @OutputTableNamePerfmonStats NVARCHAR(256) = NULL , + @OutputTableNameWaitStats NVARCHAR(256) = NULL , + @OutputTableNameBlitzCache NVARCHAR(256) = NULL , + @OutputTableNameBlitzWho NVARCHAR(256) = NULL , + @OutputResultSets NVARCHAR(500) = N'BlitzWho_Start|Findings|FileStats|PerfmonStats|WaitStats|BlitzCache|BlitzWho_End' , + @OutputTableRetentionDays TINYINT = 7 , + @OutputXMLasNVARCHAR TINYINT = 0 , + @FilterPlansByDatabase VARCHAR(MAX) = NULL , + @CheckProcedureCache TINYINT = 0 , + @CheckServerInfo TINYINT = 1 , + @FileLatencyThresholdMS INT = 100 , + @SinceStartup TINYINT = 0 , + @ShowSleepingSPIDs TINYINT = 0 , + @BlitzCacheSkipAnalysis BIT = 1 , + @MemoryGrantThresholdPct DECIMAL(5,2) = 15.00, + @LogMessageCheckID INT = 38, + @LogMessagePriority TINYINT = 1, + @LogMessageFindingsGroup VARCHAR(50) = 'Logged Message', + @LogMessageFinding VARCHAR(200) = 'Logged from sp_BlitzFirst', + @LogMessageURL VARCHAR(200) = '', + @LogMessageCheckDate DATETIMEOFFSET = NULL, + @Debug BIT = 0, + @Version VARCHAR(30) = NULL OUTPUT, + @VersionDate DATETIME = NULL OUTPUT, + @VersionCheckMode BIT = 0 + WITH EXECUTE AS CALLER, RECOMPILE +AS +BEGIN +SET NOCOUNT ON; +SET STATISTICS XML OFF; +SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; +SELECT @Version = '8.19', @VersionDate = '20240222'; -RAISERROR(N'Updating procs', 0, 1) WITH NOWAIT; -UPDATE s -SET s.variable_datatype = CASE WHEN s.variable_datatype LIKE '%(%)%' - THEN LEFT(s.variable_datatype, CHARINDEX('(', s.variable_datatype) - 1) - ELSE s.variable_datatype - END, - s.converted_to = CASE WHEN s.converted_to LIKE '%(%)%' - THEN LEFT(s.converted_to, CHARINDEX('(', s.converted_to) - 1) - ELSE s.converted_to - END, - s.compile_time_value = CASE WHEN s.compile_time_value LIKE '%(%)%' - THEN SUBSTRING(s.compile_time_value, - CHARINDEX('(', s.compile_time_value) + 1, - CHARINDEX(')', s.compile_time_value, CHARINDEX('(', s.compile_time_value) + 1) - 1 - CHARINDEX('(', s.compile_time_value) - ) - WHEN variable_datatype NOT IN ('bit', 'tinyint', 'smallint', 'int', 'bigint') - AND s.variable_datatype NOT LIKE '%binary%' - AND s.compile_time_value NOT LIKE 'N''%''' - AND s.compile_time_value NOT LIKE '''%''' - AND s.compile_time_value <> s.column_name - AND s.compile_time_value <> '**idk_man**' - THEN QUOTENAME(compile_time_value, '''') - ELSE s.compile_time_value - END -FROM #stored_proc_info AS s -OPTION (RECOMPILE); +IF(@VersionCheckMode = 1) +BEGIN + RETURN; +END; +IF @Help = 1 +BEGIN +PRINT ' +sp_BlitzFirst from http://FirstResponderKit.org + +This script gives you a prioritized list of why your SQL Server is slow right now. -RAISERROR(N'Updating SET options', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE s -SET set_options = set_options.ansi_set_options -FROM #stored_proc_info AS s -JOIN ( - SELECT x.SqlHandle, - N'SET ANSI_NULLS ' + CASE WHEN [ANSI_NULLS] = 'true' THEN N'ON ' ELSE N'OFF ' END + NCHAR(10) + - N'SET ANSI_PADDING ' + CASE WHEN [ANSI_PADDING] = 'true' THEN N'ON ' ELSE N'OFF ' END + NCHAR(10) + - N'SET ANSI_WARNINGS ' + CASE WHEN [ANSI_WARNINGS] = 'true' THEN N'ON ' ELSE N'OFF ' END + NCHAR(10) + - N'SET ARITHABORT ' + CASE WHEN [ARITHABORT] = 'true' THEN N'ON ' ELSE N' OFF ' END + NCHAR(10) + - N'SET CONCAT_NULL_YIELDS_NULL ' + CASE WHEN [CONCAT_NULL_YIELDS_NULL] = 'true' THEN N'ON ' ELSE N'OFF ' END + NCHAR(10) + - N'SET NUMERIC_ROUNDABORT ' + CASE WHEN [NUMERIC_ROUNDABORT] = 'true' THEN N'ON ' ELSE N'OFF ' END + NCHAR(10) + - N'SET QUOTED_IDENTIFIER ' + CASE WHEN [QUOTED_IDENTIFIER] = 'true' THEN N'ON ' ELSE N'OFF ' + NCHAR(10) END AS [ansi_set_options] - FROM ( - SELECT - s.SqlHandle, - so.o.value('@ANSI_NULLS', 'NVARCHAR(20)') AS [ANSI_NULLS], - so.o.value('@ANSI_PADDING', 'NVARCHAR(20)') AS [ANSI_PADDING], - so.o.value('@ANSI_WARNINGS', 'NVARCHAR(20)') AS [ANSI_WARNINGS], - so.o.value('@ARITHABORT', 'NVARCHAR(20)') AS [ARITHABORT], - so.o.value('@CONCAT_NULL_YIELDS_NULL', 'NVARCHAR(20)') AS [CONCAT_NULL_YIELDS_NULL], - so.o.value('@NUMERIC_ROUNDABORT', 'NVARCHAR(20)') AS [NUMERIC_ROUNDABORT], - so.o.value('@QUOTED_IDENTIFIER', 'NVARCHAR(20)') AS [QUOTED_IDENTIFIER] - FROM #statements AS s - CROSS APPLY s.statement.nodes('//p:StatementSetOptions') AS so(o) - ) AS x -) AS set_options ON set_options.SqlHandle = s.SqlHandle -OPTION(RECOMPILE); +This is not an overall health check - for that, check out sp_Blitz. +To learn more, visit http://FirstResponderKit.org where you can download new +versions for free, watch training videos on how it works, get more info on +the findings, contribute your own code, and more. -RAISERROR(N'Updating conversion XML', 0, 1) WITH NOWAIT; -WITH precheck AS ( -SELECT spi.SPID, - spi.SqlHandle, - spi.proc_name, - (SELECT - CASE WHEN spi.proc_name <> 'Statement' - THEN N'The stored procedure ' + spi.proc_name - ELSE N'This ad hoc statement' - END - + N' had the following implicit conversions: ' - + CHAR(10) - + STUFF(( - SELECT DISTINCT - @nl - + CASE WHEN spi2.variable_name <> N'**no_variable**' - THEN N'The variable ' - WHEN spi2.variable_name = N'**no_variable**' AND (spi2.column_name = spi2.converted_column_name OR spi2.column_name LIKE '%CONVERT_IMPLICIT%') - THEN N'The compiled value ' - WHEN spi2.column_name LIKE '%Expr%' - THEN 'The expression ' - ELSE N'The column ' - END - + CASE WHEN spi2.variable_name <> N'**no_variable**' - THEN spi2.variable_name - WHEN spi2.variable_name = N'**no_variable**' AND (spi2.column_name = spi2.converted_column_name OR spi2.column_name LIKE '%CONVERT_IMPLICIT%') - THEN spi2.compile_time_value - ELSE spi2.column_name - END - + N' has a data type of ' - + CASE WHEN spi2.variable_datatype = N'**no_variable**' THEN spi2.converted_to - ELSE spi2.variable_datatype - END - + N' which caused implicit conversion on the column ' - + CASE WHEN spi2.column_name LIKE N'%CONVERT_IMPLICIT%' - THEN spi2.converted_column_name - WHEN spi2.column_name = N'**no_column**' - THEN spi2.converted_column_name - WHEN spi2.converted_column_name = N'**no_column**' - THEN spi2.column_name - WHEN spi2.column_name <> spi2.converted_column_name - THEN spi2.converted_column_name - ELSE spi2.column_name - END - + CASE WHEN spi2.variable_name = N'**no_variable**' AND (spi2.column_name = spi2.converted_column_name OR spi2.column_name LIKE '%CONVERT_IMPLICIT%') - THEN N'' - WHEN spi2.column_name LIKE '%Expr%' - THEN N'' - WHEN spi2.compile_time_value NOT IN ('**declared in proc**', '**idk_man**') - AND spi2.compile_time_value <> spi2.column_name - THEN ' with the value ' + RTRIM(spi2.compile_time_value) - ELSE N'' - END - + '.' - FROM #stored_proc_info AS spi2 - WHERE spi.SqlHandle = spi2.SqlHandle - FOR XML PATH(N''), TYPE).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 1, N'') - AS [processing-instruction(ClickMe)] FOR XML PATH(''), TYPE ) - AS implicit_conversion_info -FROM #stored_proc_info AS spi -GROUP BY spi.SPID, spi.SqlHandle, spi.proc_name -) -UPDATE b -SET b.implicit_conversion_info = pk.implicit_conversion_info -FROM ##BlitzCacheProcs AS b -JOIN precheck pk -ON pk.SqlHandle = b.SqlHandle -AND pk.SPID = b.SPID -OPTION (RECOMPILE); +Known limitations of this version: + - Only Microsoft-supported versions of SQL Server. Sorry, 2005 and 2000. It + may work just fine on 2005, and if it does, hug your parents. Just don''t + file support issues if it breaks. + - If a temp table called #CustomPerfmonCounters exists for any other session, + but not our session, this stored proc will fail with an error saying the + temp table #CustomPerfmonCounters does not exist. + - @OutputServerName is not functional yet. + - If @OutputDatabaseName, SchemaName, TableName, etc are quoted with brackets, + the write to table may silently fail. Look, I never said I was good at this. +Unknown limitations of this version: + - None. Like Zombo.com, the only limit is yourself. -RAISERROR(N'Updating cached parameter XML for stored procs', 0, 1) WITH NOWAIT; -WITH precheck AS ( -SELECT spi.SPID, - spi.SqlHandle, - spi.proc_name, - (SELECT - set_options - + @nl - + @nl - + N'EXEC ' - + spi.proc_name - + N' ' - + STUFF(( - SELECT DISTINCT N', ' - + CASE WHEN spi2.variable_name <> N'**no_variable**' AND spi2.compile_time_value <> N'**idk_man**' - THEN spi2.variable_name + N' = ' - ELSE @nl + N' We could not find any cached parameter values for this stored proc. ' - END - + CASE WHEN spi2.variable_name = N'**no_variable**' OR spi2.compile_time_value = N'**idk_man**' - THEN @nl + N'More info on possible reasons: https://www.brentozar.com/go/noplans ' - WHEN spi2.compile_time_value = N'NULL' - THEN spi2.compile_time_value - ELSE RTRIM(spi2.compile_time_value) - END - FROM #stored_proc_info AS spi2 - WHERE spi.SqlHandle = spi2.SqlHandle - AND spi2.proc_name <> N'Statement' - FOR XML PATH(N''), TYPE).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 1, N'') - AS [processing-instruction(ClickMe)] FOR XML PATH(''), TYPE ) - AS cached_execution_parameters -FROM #stored_proc_info AS spi -GROUP BY spi.SPID, spi.SqlHandle, spi.proc_name, spi.set_options -) -UPDATE b -SET b.cached_execution_parameters = pk.cached_execution_parameters -FROM ##BlitzCacheProcs AS b -JOIN precheck pk -ON pk.SqlHandle = b.SqlHandle -AND pk.SPID = b.SPID -WHERE b.QueryType <> N'Statement' -OPTION (RECOMPILE); +Changes - for the full list of improvements and fixes in this version, see: +https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/ -RAISERROR(N'Updating cached parameter XML for statements', 0, 1) WITH NOWAIT; -WITH precheck AS ( -SELECT spi.SPID, - spi.SqlHandle, - spi.proc_name, - (SELECT - set_options - + @nl - + @nl - + N' See QueryText column for full query text' - + @nl - + @nl - + STUFF(( - SELECT DISTINCT N', ' - + CASE WHEN spi2.variable_name <> N'**no_variable**' AND spi2.compile_time_value <> N'**idk_man**' - THEN spi2.variable_name + N' = ' - ELSE @nl + N' We could not find any cached parameter values for this stored proc. ' - END - + CASE WHEN spi2.variable_name = N'**no_variable**' OR spi2.compile_time_value = N'**idk_man**' - THEN @nl + N' More info on possible reasons: https://www.brentozar.com/go/noplans ' - WHEN spi2.compile_time_value = N'NULL' - THEN spi2.compile_time_value - ELSE RTRIM(spi2.compile_time_value) - END - FROM #stored_proc_info AS spi2 - WHERE spi.SqlHandle = spi2.SqlHandle - AND spi2.proc_name = N'Statement' - AND spi2.variable_name NOT LIKE N'%msparam%' - FOR XML PATH(N''), TYPE).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 1, N'') - AS [processing-instruction(ClickMe)] FOR XML PATH(''), TYPE ) - AS cached_execution_parameters -FROM #stored_proc_info AS spi -GROUP BY spi.SPID, spi.SqlHandle, spi.proc_name, spi.set_options -) -UPDATE b -SET b.cached_execution_parameters = pk.cached_execution_parameters -FROM ##BlitzCacheProcs AS b -JOIN precheck pk -ON pk.SqlHandle = b.SqlHandle -AND pk.SPID = b.SPID -WHERE b.QueryType = N'Statement' -OPTION (RECOMPILE); +MIT License -RAISERROR(N'Filling in implicit conversion and cached plan parameter info', 0, 1) WITH NOWAIT; -UPDATE b -SET b.implicit_conversion_info = CASE WHEN b.implicit_conversion_info IS NULL - OR CONVERT(NVARCHAR(MAX), b.implicit_conversion_info) = N'' - THEN '' - ELSE b.implicit_conversion_info END, - b.cached_execution_parameters = CASE WHEN b.cached_execution_parameters IS NULL - OR CONVERT(NVARCHAR(MAX), b.cached_execution_parameters) = N'' - THEN '' - ELSE b.cached_execution_parameters END -FROM ##BlitzCacheProcs AS b -WHERE b.SPID = @@SPID -OPTION (RECOMPILE); +Copyright (c) Brent Ozar Unlimited -/*End implicit conversion and parameter info*/ +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: -/*Begin Missing Index*/ -IF EXISTS ( SELECT 1/0 - FROM ##BlitzCacheProcs AS bbcp - WHERE bbcp.missing_index_count > 0 - OR bbcp.index_spool_cost > 0 - OR bbcp.index_spool_rows > 0 - AND bbcp.SPID = @@SPID ) - - BEGIN - RAISERROR(N'Inserting to #missing_index_xml', 0, 1) WITH NOWAIT; - WITH XMLNAMESPACES ( 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) - INSERT #missing_index_xml - SELECT qp.QueryHash, - qp.SqlHandle, - c.mg.value('@Impact', 'FLOAT') AS Impact, - c.mg.query('.') AS cmg - FROM #query_plan AS qp - CROSS APPLY qp.query_plan.nodes('//p:MissingIndexes/p:MissingIndexGroup') AS c(mg) - WHERE qp.QueryHash IS NOT NULL - OPTION(RECOMPILE); - - RAISERROR(N'Inserting to #missing_index_schema', 0, 1) WITH NOWAIT; - WITH XMLNAMESPACES ( 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) - INSERT #missing_index_schema - SELECT mix.QueryHash, mix.SqlHandle, mix.impact, - c.mi.value('@Database', 'NVARCHAR(128)'), - c.mi.value('@Schema', 'NVARCHAR(128)'), - c.mi.value('@Table', 'NVARCHAR(128)'), - c.mi.query('.') - FROM #missing_index_xml AS mix - CROSS APPLY mix.index_xml.nodes('//p:MissingIndex') AS c(mi) - OPTION(RECOMPILE); - - RAISERROR(N'Inserting to #missing_index_usage', 0, 1) WITH NOWAIT; - WITH XMLNAMESPACES ( 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) - INSERT #missing_index_usage - SELECT ms.QueryHash, ms.SqlHandle, ms.impact, ms.database_name, ms.schema_name, ms.table_name, - c.cg.value('@Usage', 'NVARCHAR(128)'), - c.cg.query('.') - FROM #missing_index_schema ms - CROSS APPLY ms.index_xml.nodes('//p:ColumnGroup') AS c(cg) - OPTION(RECOMPILE); - - RAISERROR(N'Inserting to #missing_index_detail', 0, 1) WITH NOWAIT; - WITH XMLNAMESPACES ( 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) - INSERT #missing_index_detail - SELECT miu.QueryHash, - miu.SqlHandle, - miu.impact, - miu.database_name, - miu.schema_name, - miu.table_name, - miu.usage, - c.c.value('@Name', 'NVARCHAR(128)') - FROM #missing_index_usage AS miu - CROSS APPLY miu.index_xml.nodes('//p:Column') AS c(c) - OPTION (RECOMPILE); - - RAISERROR(N'Inserting to missing indexes to #missing_index_pretty', 0, 1) WITH NOWAIT; - INSERT #missing_index_pretty - ( QueryHash, SqlHandle, impact, database_name, schema_name, table_name, equality, inequality, include, executions, query_cost, creation_hours, is_spool ) - SELECT DISTINCT m.QueryHash, m.SqlHandle, m.impact, m.database_name, m.schema_name, m.table_name - , STUFF(( SELECT DISTINCT N', ' + ISNULL(m2.column_name, '') AS column_name - FROM #missing_index_detail AS m2 - WHERE m2.usage = 'EQUALITY' - AND m.QueryHash = m2.QueryHash - AND m.SqlHandle = m2.SqlHandle - AND m.impact = m2.impact - AND m.database_name = m2.database_name - AND m.schema_name = m2.schema_name - AND m.table_name = m2.table_name - FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 2, N'') AS equality - , STUFF(( SELECT DISTINCT N', ' + ISNULL(m2.column_name, '') AS column_name - FROM #missing_index_detail AS m2 - WHERE m2.usage = 'INEQUALITY' - AND m.QueryHash = m2.QueryHash - AND m.SqlHandle = m2.SqlHandle - AND m.impact = m2.impact - AND m.database_name = m2.database_name - AND m.schema_name = m2.schema_name - AND m.table_name = m2.table_name - FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 2, N'') AS inequality - , STUFF(( SELECT DISTINCT N', ' + ISNULL(m2.column_name, '') AS column_name - FROM #missing_index_detail AS m2 - WHERE m2.usage = 'INCLUDE' - AND m.QueryHash = m2.QueryHash - AND m.SqlHandle = m2.SqlHandle - AND m.impact = m2.impact - AND m.database_name = m2.database_name - AND m.schema_name = m2.schema_name - AND m.table_name = m2.table_name - FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 2, N'') AS [include], - bbcp.ExecutionCount, - bbcp.QueryPlanCost, - bbcp.PlanCreationTimeHours, - 0 as is_spool - FROM #missing_index_detail AS m - JOIN ##BlitzCacheProcs AS bbcp - ON m.SqlHandle = bbcp.SqlHandle - AND m.QueryHash = bbcp.QueryHash - OPTION (RECOMPILE); - - RAISERROR(N'Inserting to #index_spool_ugly', 0, 1) WITH NOWAIT; - WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) - INSERT #index_spool_ugly - (QueryHash, SqlHandle, impact, database_name, schema_name, table_name, equality, inequality, include, executions, query_cost, creation_hours) - SELECT p.QueryHash, - p.SqlHandle, - (c.n.value('@EstimateIO', 'FLOAT') + (c.n.value('@EstimateCPU', 'FLOAT'))) - / ( 1 * NULLIF(p.QueryPlanCost, 0)) * 100 AS impact, - o.n.value('@Database', 'NVARCHAR(128)') AS output_database, - o.n.value('@Schema', 'NVARCHAR(128)') AS output_schema, - o.n.value('@Table', 'NVARCHAR(128)') AS output_table, - k.n.value('@Column', 'NVARCHAR(128)') AS range_column, - e.n.value('@Column', 'NVARCHAR(128)') AS expression_column, - o.n.value('@Column', 'NVARCHAR(128)') AS output_column, - p.ExecutionCount, - p.QueryPlanCost, - p.PlanCreationTimeHours - FROM #relop AS r - JOIN ##BlitzCacheProcs p - ON p.QueryHash = r.QueryHash - CROSS APPLY r.relop.nodes('/p:RelOp') AS c(n) - CROSS APPLY r.relop.nodes('/p:RelOp/p:OutputList/p:ColumnReference') AS o(n) - OUTER APPLY r.relop.nodes('/p:RelOp/p:Spool/p:SeekPredicateNew/p:SeekKeys/p:Prefix/p:RangeColumns/p:ColumnReference') AS k(n) - OUTER APPLY r.relop.nodes('/p:RelOp/p:Spool/p:SeekPredicateNew/p:SeekKeys/p:Prefix/p:RangeExpressions/p:ColumnReference') AS e(n) - WHERE r.relop.exist('/p:RelOp[@PhysicalOp="Index Spool" and @LogicalOp="Eager Spool"]') = 1 - - RAISERROR(N'Inserting to spools to #missing_index_pretty', 0, 1) WITH NOWAIT; - INSERT #missing_index_pretty - (QueryHash, SqlHandle, impact, database_name, schema_name, table_name, equality, inequality, include, executions, query_cost, creation_hours, is_spool) - SELECT DISTINCT - isu.QueryHash, - isu.SqlHandle, - isu.impact, - isu.database_name, - isu.schema_name, - isu.table_name - , STUFF(( SELECT DISTINCT N', ' + ISNULL(isu2.equality, '') AS column_name - FROM #index_spool_ugly AS isu2 - WHERE isu2.equality IS NOT NULL - AND isu.QueryHash = isu2.QueryHash - AND isu.SqlHandle = isu2.SqlHandle - AND isu.impact = isu2.impact - AND isu.database_name = isu2.database_name - AND isu.schema_name = isu2.schema_name - AND isu.table_name = isu2.table_name - FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 2, N'') AS equality - , STUFF(( SELECT DISTINCT N', ' + ISNULL(isu2.inequality, '') AS column_name - FROM #index_spool_ugly AS isu2 - WHERE isu2.inequality IS NOT NULL - AND isu.QueryHash = isu2.QueryHash - AND isu.SqlHandle = isu2.SqlHandle - AND isu.impact = isu2.impact - AND isu.database_name = isu2.database_name - AND isu.schema_name = isu2.schema_name - AND isu.table_name = isu2.table_name - FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 2, N'') AS inequality - , STUFF(( SELECT DISTINCT N', ' + ISNULL(isu2.include, '') AS column_name - FROM #index_spool_ugly AS isu2 - WHERE isu2.include IS NOT NULL - AND isu.QueryHash = isu2.QueryHash - AND isu.SqlHandle = isu2.SqlHandle - AND isu.impact = isu2.impact - AND isu.database_name = isu2.database_name - AND isu.schema_name = isu2.schema_name - AND isu.table_name = isu2.table_name - FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 2, N'') AS include, - isu.executions, - isu.query_cost, - isu.creation_hours, - 1 AS is_spool - FROM #index_spool_ugly AS isu +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. - RAISERROR(N'Updating missing index information', 0, 1) WITH NOWAIT; - WITH missing AS ( - SELECT DISTINCT - mip.QueryHash, - mip.SqlHandle, - mip.executions, - N'' - AS full_details - FROM #missing_index_pretty AS mip - ) - UPDATE bbcp - SET bbcp.missing_indexes = m.full_details - FROM ##BlitzCacheProcs AS bbcp - JOIN missing AS m - ON m.SqlHandle = bbcp.SqlHandle - AND m.QueryHash = bbcp.QueryHash - AND m.executions = bbcp.ExecutionCount - AND SPID = @@SPID - OPTION (RECOMPILE); +'; +RETURN; +END; /* @Help = 1 */ - END; +RAISERROR('Setting up configuration variables',10,1) WITH NOWAIT; +DECLARE @StringToExecute NVARCHAR(MAX), + @ParmDefinitions NVARCHAR(4000), + @Parm1 NVARCHAR(4000), + @OurSessionID INT, + @LineFeed NVARCHAR(10), + @StockWarningHeader NVARCHAR(MAX) = N'', + @StockWarningFooter NVARCHAR(MAX) = N'', + @StockDetailsHeader NVARCHAR(MAX) = N'', + @StockDetailsFooter NVARCHAR(MAX) = N'', + @StartSampleTime DATETIMEOFFSET, + @FinishSampleTime DATETIMEOFFSET, + @FinishSampleTimeWaitFor DATETIME, + @AsOf1 DATETIMEOFFSET, + @AsOf2 DATETIMEOFFSET, + @ServiceName sysname, + @OutputTableNameFileStats_View NVARCHAR(256), + @OutputTableNamePerfmonStats_View NVARCHAR(256), + @OutputTableNamePerfmonStatsActuals_View NVARCHAR(256), + @OutputTableNameWaitStats_View NVARCHAR(256), + @OutputTableNameWaitStats_Categories NVARCHAR(256), + @OutputTableCleanupDate DATE, + @ObjectFullName NVARCHAR(2000), + @BlitzWho NVARCHAR(MAX) = N'EXEC dbo.sp_BlitzWho @ShowSleepingSPIDs = ' + CONVERT(NVARCHAR(1), @ShowSleepingSPIDs) + N';', + @BlitzCacheMinutesBack INT, + @UnquotedOutputServerName NVARCHAR(256) = @OutputServerName , + @UnquotedOutputDatabaseName NVARCHAR(256) = @OutputDatabaseName , + @UnquotedOutputSchemaName NVARCHAR(256) = @OutputSchemaName , + @LocalServerName NVARCHAR(128) = CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128)), + @dm_exec_query_statistics_xml BIT = 0, + @total_cpu_usage BIT = 0, + @get_thread_time_ms NVARCHAR(MAX) = N'', + @thread_time_ms FLOAT = 0; - RAISERROR(N'Filling in missing index blanks', 0, 1) WITH NOWAIT; - UPDATE b - SET b.missing_indexes = - CASE WHEN b.missing_indexes IS NULL - THEN '' - ELSE b.missing_indexes - END - FROM ##BlitzCacheProcs AS b - WHERE b.SPID = @@SPID - OPTION (RECOMPILE); +/* Sanitize our inputs */ +SELECT + @OutputTableNameFileStats_View = QUOTENAME(@OutputTableNameFileStats + '_Deltas'), + @OutputTableNamePerfmonStats_View = QUOTENAME(@OutputTableNamePerfmonStats + '_Deltas'), + @OutputTableNamePerfmonStatsActuals_View = QUOTENAME(@OutputTableNamePerfmonStats + '_Actuals'), + @OutputTableNameWaitStats_View = QUOTENAME(@OutputTableNameWaitStats + '_Deltas'), + @OutputTableNameWaitStats_Categories = QUOTENAME(@OutputTableNameWaitStats + '_Categories'); -/*End Missing Index*/ +SELECT + @OutputDatabaseName = QUOTENAME(@OutputDatabaseName), + @OutputSchemaName = QUOTENAME(@OutputSchemaName), + @OutputTableName = QUOTENAME(@OutputTableName), + @OutputTableNameFileStats = QUOTENAME(@OutputTableNameFileStats), + @OutputTableNamePerfmonStats = QUOTENAME(@OutputTableNamePerfmonStats), + @OutputTableNameWaitStats = QUOTENAME(@OutputTableNameWaitStats), + @OutputTableCleanupDate = CAST( (DATEADD(DAY, -1 * @OutputTableRetentionDays, GETDATE() ) ) AS DATE), + /* @OutputTableNameBlitzCache = QUOTENAME(@OutputTableNameBlitzCache), We purposely don't sanitize this because sp_BlitzCache will */ + /* @OutputTableNameBlitzWho = QUOTENAME(@OutputTableNameBlitzWho), We purposely don't sanitize this because sp_BlitzWho will */ + @LineFeed = CHAR(13) + CHAR(10), + @OurSessionID = @@SPID, + @OutputType = UPPER(@OutputType); +IF(@OutputType = 'NONE' AND (@OutputTableName IS NULL OR @OutputSchemaName IS NULL OR @OutputDatabaseName IS NULL)) +BEGIN + RAISERROR('This procedure should be called with a value for all @Output* parameters, as @OutputType is set to NONE',12,1); + RETURN; +END; -/* Set configuration values */ -RAISERROR(N'Setting configuration values', 0, 1) WITH NOWAIT; -DECLARE @execution_threshold INT = 1000 , - @parameter_sniffing_warning_pct TINYINT = 30, - /* This is in average reads */ - @parameter_sniffing_io_threshold BIGINT = 100000 , - @ctp_threshold_pct TINYINT = 10, - @long_running_query_warning_seconds BIGINT = 300 * 1000 , - @memory_grant_warning_percent INT = 10; +IF UPPER(@OutputType) LIKE 'TOP 10%' SET @OutputType = 'Top10'; +IF @OutputType = 'Top10' SET @SinceStartup = 1; -IF EXISTS (SELECT 1/0 FROM #configuration WHERE 'frequent execution threshold' = LOWER(parameter_name)) -BEGIN - SELECT @execution_threshold = CAST(value AS INT) - FROM #configuration - WHERE 'frequent execution threshold' = LOWER(parameter_name) ; +/* Logged Message - CheckID 38 */ +IF @LogMessage IS NOT NULL + BEGIN - SET @msg = ' Setting "frequent execution threshold" to ' + CAST(@execution_threshold AS VARCHAR(10)) ; + RAISERROR('Saving LogMessage to table',10,1) WITH NOWAIT; - RAISERROR(@msg, 0, 1) WITH NOWAIT; -END; + /* Try to set the output table parameters if they don't exist */ + IF @OutputSchemaName IS NULL AND @OutputTableName IS NULL AND @OutputDatabaseName IS NULL + BEGIN + SET @OutputSchemaName = N'[dbo]'; + SET @OutputTableName = N'[BlitzFirst]'; -IF EXISTS (SELECT 1/0 FROM #configuration WHERE 'parameter sniffing variance percent' = LOWER(parameter_name)) -BEGIN - SELECT @parameter_sniffing_warning_pct = CAST(value AS TINYINT) - FROM #configuration - WHERE 'parameter sniffing variance percent' = LOWER(parameter_name) ; + /* Look for the table in the current database */ + SELECT TOP 1 @OutputDatabaseName = QUOTENAME(TABLE_CATALOG) + FROM INFORMATION_SCHEMA.TABLES + WHERE TABLE_SCHEMA = 'dbo' AND TABLE_NAME = 'BlitzFirst'; - SET @msg = ' Setting "parameter sniffing variance percent" to ' + CAST(@parameter_sniffing_warning_pct AS VARCHAR(3)) ; + IF @OutputDatabaseName IS NULL AND EXISTS (SELECT * FROM sys.databases WHERE name = 'DBAtools') + SET @OutputDatabaseName = '[DBAtools]'; - RAISERROR(@msg, 0, 1) WITH NOWAIT; -END; + END; -IF EXISTS (SELECT 1/0 FROM #configuration WHERE 'parameter sniffing io threshold' = LOWER(parameter_name)) -BEGIN - SELECT @parameter_sniffing_io_threshold = CAST(value AS BIGINT) - FROM #configuration - WHERE 'parameter sniffing io threshold' = LOWER(parameter_name) ; + IF @OutputDatabaseName IS NULL OR @OutputSchemaName IS NULL OR @OutputTableName IS NULL + OR NOT EXISTS ( SELECT * + FROM sys.databases + WHERE QUOTENAME([name]) = @OutputDatabaseName) + BEGIN + RAISERROR('We have a hard time logging a message without a valid @OutputDatabaseName, @OutputSchemaName, and @OutputTableName to log it to.', 0, 1) WITH NOWAIT; + RETURN; + END; + IF @LogMessageCheckDate IS NULL + SET @LogMessageCheckDate = SYSDATETIMEOFFSET(); + SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' + + @OutputDatabaseName + + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' + + @OutputSchemaName + ''') INSERT ' + + @OutputDatabaseName + '.' + + @OutputSchemaName + '.' + + @OutputTableName + + ' (ServerName, CheckDate, CheckID, Priority, FindingsGroup, Finding, Details, URL) VALUES( ' + + ' @SrvName, @LogMessageCheckDate, @LogMessageCheckID, @LogMessagePriority, @LogMessageFindingsGroup, @LogMessageFinding, @LogMessage, @LogMessageURL)'; - SET @msg = ' Setting "parameter sniffing io threshold" to ' + CAST(@parameter_sniffing_io_threshold AS VARCHAR(10)); + EXECUTE sp_executesql @StringToExecute, + N'@SrvName NVARCHAR(128), @LogMessageCheckID INT, @LogMessagePriority TINYINT, @LogMessageFindingsGroup VARCHAR(50), @LogMessageFinding VARCHAR(200), @LogMessage NVARCHAR(4000), @LogMessageCheckDate DATETIMEOFFSET, @LogMessageURL VARCHAR(200)', + @LocalServerName, @LogMessageCheckID, @LogMessagePriority, @LogMessageFindingsGroup, @LogMessageFinding, @LogMessage, @LogMessageCheckDate, @LogMessageURL; - RAISERROR(@msg, 0, 1) WITH NOWAIT; -END; + RAISERROR('LogMessage saved to table. We have made a note of your activity. Keep up the good work.',10,1) WITH NOWAIT; -IF EXISTS (SELECT 1/0 FROM #configuration WHERE 'cost threshold for parallelism warning' = LOWER(parameter_name)) -BEGIN - SELECT @ctp_threshold_pct = CAST(value AS TINYINT) - FROM #configuration - WHERE 'cost threshold for parallelism warning' = LOWER(parameter_name) ; + RETURN; + END; - SET @msg = ' Setting "cost threshold for parallelism warning" to ' + CAST(@ctp_threshold_pct AS VARCHAR(3)); +IF @SinceStartup = 1 + BEGIN + SET @Seconds = 0 + IF @ExpertMode = 0 + SET @ExpertMode = 1 + END; - RAISERROR(@msg, 0, 1) WITH NOWAIT; -END; -IF EXISTS (SELECT 1/0 FROM #configuration WHERE 'long running query warning (seconds)' = LOWER(parameter_name)) +IF @OutputType = 'SCHEMA' BEGIN - SELECT @long_running_query_warning_seconds = CAST(value * 1000 AS BIGINT) - FROM #configuration - WHERE 'long running query warning (seconds)' = LOWER(parameter_name) ; - - SET @msg = ' Setting "long running query warning (seconds)" to ' + CAST(@long_running_query_warning_seconds AS VARCHAR(10)); + SELECT FieldList = '[Priority] TINYINT, [FindingsGroup] VARCHAR(50), [Finding] VARCHAR(200), [URL] VARCHAR(200), [Details] NVARCHAR(4000), [HowToStopIt] NVARCHAR(MAX), [QueryPlan] XML, [QueryText] NVARCHAR(MAX)'; - RAISERROR(@msg, 0, 1) WITH NOWAIT; END; - -IF EXISTS (SELECT 1/0 FROM #configuration WHERE 'unused memory grant' = LOWER(parameter_name)) +ELSE IF @AsOf IS NOT NULL AND @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @OutputTableName IS NOT NULL BEGIN - SELECT @memory_grant_warning_percent = CAST(value AS INT) - FROM #configuration - WHERE 'unused memory grant' = LOWER(parameter_name) ; - - SET @msg = ' Setting "unused memory grant" to ' + CAST(@memory_grant_warning_percent AS VARCHAR(10)); + /* They want to look into the past. */ + SET @AsOf1= DATEADD(mi, -15, @AsOf); + SET @AsOf2= DATEADD(mi, +15, @AsOf); - RAISERROR(@msg, 0, 1) WITH NOWAIT; -END; + SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' + + @OutputDatabaseName + + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' + + @OutputSchemaName + ''') SELECT CheckDate, [Priority], [FindingsGroup], [Finding], [URL], CAST([Details] AS [XML]) AS Details,' + + '[HowToStopIt], [CheckID], [StartTime], [LoginName], [NTUserName], [OriginalLoginName], [ProgramName], [HostName], [DatabaseID],' + + '[DatabaseName], [OpenTransactionCount], [QueryPlan], [QueryText] FROM ' + + @OutputDatabaseName + '.' + + @OutputSchemaName + '.' + + @OutputTableName + + ' WHERE CheckDate >= @AsOf1' + + ' AND CheckDate <= @AsOf2' + + ' /*ORDER BY CheckDate, Priority , FindingsGroup , Finding , Details*/;'; + EXEC sp_executesql @StringToExecute, + N'@AsOf1 DATETIMEOFFSET, @AsOf2 DATETIMEOFFSET', + @AsOf1, @AsOf2 -DECLARE @ctp INT ; -SELECT @ctp = NULLIF(CAST(value AS INT), 0) -FROM sys.configurations -WHERE name = 'cost threshold for parallelism' -OPTION (RECOMPILE); +END; /* IF @AsOf IS NOT NULL AND @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @OutputTableName IS NOT NULL */ +ELSE IF @LogMessage IS NULL /* IF @OutputType = 'SCHEMA' */ +BEGIN + /* What's running right now? This is the first and last result set. */ + IF @SinceStartup = 0 AND @Seconds > 0 AND @ExpertMode = 1 AND @OutputType <> 'NONE' AND @OutputResultSets LIKE N'%BlitzWho_Start%' + BEGIN + IF OBJECT_ID('master.dbo.sp_BlitzWho') IS NULL AND OBJECT_ID('dbo.sp_BlitzWho') IS NULL + BEGIN + PRINT N'sp_BlitzWho is not installed in the current database_files. You can get a copy from http://FirstResponderKit.org'; + END; + ELSE + BEGIN + EXEC (@BlitzWho); + END; + END; /* IF @SinceStartup = 0 AND @Seconds > 0 AND @ExpertMode = 1 AND @OutputType <> 'NONE' - What's running right now? This is the first and last result set. */ + /* Set start/finish times AFTER sp_BlitzWho runs. For more info: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2244 */ + IF @Seconds = 0 AND SERVERPROPERTY('EngineEdition') = 5 /*SERVERPROPERTY('Edition') = 'SQL Azure'*/ + WITH WaitTimes AS ( + SELECT wait_type, wait_time_ms, + NTILE(3) OVER(ORDER BY wait_time_ms) AS grouper + FROM sys.dm_os_wait_stats w + WHERE wait_type IN ('DIRTY_PAGE_POLL','HADR_FILESTREAM_IOMGR_IOCOMPLETION','LAZYWRITER_SLEEP', + 'LOGMGR_QUEUE','REQUEST_FOR_DEADLOCK_SEARCH','XE_TIMER_EVENT') + ) + SELECT @StartSampleTime = DATEADD(mi, AVG(-wait_time_ms / 1000 / 60), SYSDATETIMEOFFSET()), @FinishSampleTime = SYSDATETIMEOFFSET() + FROM WaitTimes + WHERE grouper = 2; + ELSE IF @Seconds = 0 AND SERVERPROPERTY('EngineEdition') <> 5 /*SERVERPROPERTY('Edition') <> 'SQL Azure'*/ + SELECT @StartSampleTime = DATEADD(MINUTE,DATEDIFF(MINUTE, GETDATE(), GETUTCDATE()),create_date) , @FinishSampleTime = SYSDATETIMEOFFSET() + FROM sys.databases + WHERE database_id = 2; + ELSE + SELECT @StartSampleTime = SYSDATETIMEOFFSET(), + @FinishSampleTime = DATEADD(ss, @Seconds, SYSDATETIMEOFFSET()), + @FinishSampleTimeWaitFor = DATEADD(ss, @Seconds, GETDATE()); -/* Update to populate checks columns */ -RAISERROR('Checking for query level SQL Server issues.', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE ##BlitzCacheProcs -SET frequent_execution = CASE WHEN ExecutionsPerMinute > @execution_threshold THEN 1 END , - parameter_sniffing = CASE WHEN ExecutionCount > 3 AND AverageReads > @parameter_sniffing_io_threshold - AND min_worker_time < ((1.0 - (@parameter_sniffing_warning_pct / 100.0)) * AverageCPU) THEN 1 - WHEN ExecutionCount > 3 AND AverageReads > @parameter_sniffing_io_threshold - AND max_worker_time > ((1.0 + (@parameter_sniffing_warning_pct / 100.0)) * AverageCPU) THEN 1 - WHEN ExecutionCount > 3 AND AverageReads > @parameter_sniffing_io_threshold - AND MinReturnedRows < ((1.0 - (@parameter_sniffing_warning_pct / 100.0)) * AverageReturnedRows) THEN 1 - WHEN ExecutionCount > 3 AND AverageReads > @parameter_sniffing_io_threshold - AND MaxReturnedRows > ((1.0 + (@parameter_sniffing_warning_pct / 100.0)) * AverageReturnedRows) THEN 1 END , - near_parallel = CASE WHEN is_parallel <> 1 AND QueryPlanCost BETWEEN @ctp * (1 - (@ctp_threshold_pct / 100.0)) AND @ctp THEN 1 END, - long_running = CASE WHEN AverageDuration > @long_running_query_warning_seconds THEN 1 - WHEN max_worker_time > @long_running_query_warning_seconds THEN 1 - WHEN max_elapsed_time > @long_running_query_warning_seconds THEN 1 END, - is_key_lookup_expensive = CASE WHEN QueryPlanCost >= (@ctp / 2) AND key_lookup_cost >= QueryPlanCost * .5 THEN 1 END, - is_sort_expensive = CASE WHEN QueryPlanCost >= (@ctp / 2) AND sort_cost >= QueryPlanCost * .5 THEN 1 END, - is_remote_query_expensive = CASE WHEN remote_query_cost >= QueryPlanCost * .05 THEN 1 END, - is_unused_grant = CASE WHEN PercentMemoryGrantUsed <= @memory_grant_warning_percent AND MinGrantKB > @MinMemoryPerQuery THEN 1 END, - long_running_low_cpu = CASE WHEN AverageDuration > AverageCPU * 4 AND AverageCPU < 500. THEN 1 END, - low_cost_high_cpu = CASE WHEN QueryPlanCost <= 10 AND AverageCPU > 5000. THEN 1 END, - is_spool_expensive = CASE WHEN QueryPlanCost > (@ctp / 5) AND index_spool_cost >= QueryPlanCost * .1 THEN 1 END, - is_spool_more_rows = CASE WHEN index_spool_rows >= (AverageReturnedRows / ISNULL(NULLIF(ExecutionCount, 0), 1)) THEN 1 END, - is_table_spool_expensive = CASE WHEN QueryPlanCost > (@ctp / 5) AND table_spool_cost >= QueryPlanCost / 4 THEN 1 END, - is_table_spool_more_rows = CASE WHEN table_spool_rows >= (AverageReturnedRows / ISNULL(NULLIF(ExecutionCount, 0), 1)) THEN 1 END, - is_bad_estimate = CASE WHEN AverageReturnedRows > 0 AND (estimated_rows * 1000 < AverageReturnedRows OR estimated_rows > AverageReturnedRows * 1000) THEN 1 END, - is_big_spills = CASE WHEN (AvgSpills / 128.) > 499. THEN 1 END -WHERE SPID = @@SPID -OPTION (RECOMPILE); + IF EXISTS + ( + SELECT + 1/0 + FROM sys.all_columns AS ac + WHERE ac.object_id = OBJECT_ID('sys.dm_os_schedulers') + AND ac.name = 'total_cpu_usage_ms' + ) + BEGIN -RAISERROR('Checking for forced parameterization and cursors.', 0, 1) WITH NOWAIT; + SELECT + @total_cpu_usage = 1, + @get_thread_time_ms += + N' + SELECT + @thread_time_ms = + CONVERT + ( + FLOAT, + SUM(s.total_cpu_usage_ms) + ) + FROM sys.dm_os_schedulers AS s + WHERE s.status = ''VISIBLE ONLINE'' + AND s.is_online = 1 + OPTION(RECOMPILE); + '; -/* Set options checks */ -UPDATE p - SET is_forced_parameterized = CASE WHEN (CAST(pa.value AS INT) & 131072 = 131072) THEN 1 END , - is_forced_plan = CASE WHEN (CAST(pa.value AS INT) & 4 = 4) THEN 1 END , - SetOptions = SUBSTRING( - CASE WHEN (CAST(pa.value AS INT) & 1 = 1) THEN ', ANSI_PADDING' ELSE '' END + - CASE WHEN (CAST(pa.value AS INT) & 8 = 8) THEN ', CONCAT_NULL_YIELDS_NULL' ELSE '' END + - CASE WHEN (CAST(pa.value AS INT) & 16 = 16) THEN ', ANSI_WARNINGS' ELSE '' END + - CASE WHEN (CAST(pa.value AS INT) & 32 = 32) THEN ', ANSI_NULLS' ELSE '' END + - CASE WHEN (CAST(pa.value AS INT) & 64 = 64) THEN ', QUOTED_IDENTIFIER' ELSE '' END + - CASE WHEN (CAST(pa.value AS INT) & 4096 = 4096) THEN ', ARITH_ABORT' ELSE '' END + - CASE WHEN (CAST(pa.value AS INT) & 8192 = 8191) THEN ', NUMERIC_ROUNDABORT' ELSE '' END - , 2, 200000) -FROM ##BlitzCacheProcs p - CROSS APPLY sys.dm_exec_plan_attributes(p.PlanHandle) pa -WHERE pa.attribute = 'set_options' -AND SPID = @@SPID -OPTION (RECOMPILE); + END + ELSE + BEGIN + SELECT + @total_cpu_usage = 0, + @get_thread_time_ms += + N' + SELECT + @thread_time_ms = + CONVERT + ( + FLOAT, + SUM(s.total_worker_time / 1000.) + ) + FROM sys.dm_exec_query_stats AS s + OPTION(RECOMPILE); + '; + END + RAISERROR('Now starting diagnostic analysis',10,1) WITH NOWAIT; -/* Cursor checks */ -UPDATE p -SET is_cursor = CASE WHEN CAST(pa.value AS INT) <> 0 THEN 1 END -FROM ##BlitzCacheProcs p - CROSS APPLY sys.dm_exec_plan_attributes(p.PlanHandle) pa -WHERE pa.attribute LIKE '%cursor%' -AND SPID = @@SPID -OPTION (RECOMPILE); + /* + We start by creating #BlitzFirstResults. It's a temp table that will store + the results from our checks. Throughout the rest of this stored procedure, + we're running a series of checks looking for dangerous things inside the SQL + Server. When we find a problem, we insert rows into the temp table. At the + end, we return these results to the end user. -UPDATE p -SET is_cursor = 1 -FROM ##BlitzCacheProcs p -WHERE QueryHash = 0x0000000000000000 -OR QueryPlanHash = 0x0000000000000000 -AND SPID = @@SPID -OPTION (RECOMPILE); + #BlitzFirstResults has a CheckID field, but there's no Check table. As we do + checks, we insert data into this table, and we manually put in the CheckID. + We (Brent Ozar Unlimited) maintain a list of the checks by ID#. You can + download that from http://FirstResponderKit.org if you want to build + a tool that relies on the output of sp_BlitzFirst. + */ + IF OBJECT_ID('tempdb..#BlitzFirstResults') IS NOT NULL + DROP TABLE #BlitzFirstResults; + CREATE TABLE #BlitzFirstResults + ( + ID INT IDENTITY(1, 1) PRIMARY KEY CLUSTERED, + CheckID INT NOT NULL, + Priority TINYINT NOT NULL, + FindingsGroup VARCHAR(50) NOT NULL, + Finding VARCHAR(200) NOT NULL, + URL VARCHAR(200) NULL, + Details NVARCHAR(MAX) NULL, + HowToStopIt NVARCHAR(MAX) NULL, + QueryPlan [XML] NULL, + QueryText NVARCHAR(MAX) NULL, + StartTime DATETIMEOFFSET NULL, + LoginName NVARCHAR(128) NULL, + NTUserName NVARCHAR(128) NULL, + OriginalLoginName NVARCHAR(128) NULL, + ProgramName NVARCHAR(128) NULL, + HostName NVARCHAR(128) NULL, + DatabaseID INT NULL, + DatabaseName NVARCHAR(128) NULL, + OpenTransactionCount INT NULL, + QueryStatsNowID INT NULL, + QueryStatsFirstID INT NULL, + PlanHandle VARBINARY(64) NULL, + DetailsInt INT NULL, + QueryHash BINARY(8) + ); -RAISERROR('Populating Warnings column', 0, 1) WITH NOWAIT; -/* Populate warnings */ -UPDATE ##BlitzCacheProcs -SET Warnings = SUBSTRING( - CASE WHEN warning_no_join_predicate = 1 THEN ', No Join Predicate' ELSE '' END + - CASE WHEN compile_timeout = 1 THEN ', Compilation Timeout' ELSE '' END + - CASE WHEN compile_memory_limit_exceeded = 1 THEN ', Compile Memory Limit Exceeded' ELSE '' END + - CASE WHEN busy_loops = 1 THEN ', Busy Loops' ELSE '' END + - CASE WHEN is_forced_plan = 1 THEN ', Forced Plan' ELSE '' END + - CASE WHEN is_forced_parameterized = 1 THEN ', Forced Parameterization' ELSE '' END + - CASE WHEN unparameterized_query = 1 THEN ', Unparameterized Query' ELSE '' END + - CASE WHEN missing_index_count > 0 THEN ', Missing Indexes (' + CAST(missing_index_count AS VARCHAR(3)) + ')' ELSE '' END + - CASE WHEN unmatched_index_count > 0 THEN ', Unmatched Indexes (' + CAST(unmatched_index_count AS VARCHAR(3)) + ')' ELSE '' END + - CASE WHEN is_cursor = 1 THEN ', Cursor' - + CASE WHEN is_optimistic_cursor = 1 THEN '; optimistic' ELSE '' END - + CASE WHEN is_forward_only_cursor = 0 THEN '; not forward only' ELSE '' END - + CASE WHEN is_cursor_dynamic = 1 THEN '; dynamic' ELSE '' END - + CASE WHEN is_fast_forward_cursor = 1 THEN '; fast forward' ELSE '' END - ELSE '' END + - CASE WHEN is_parallel = 1 THEN ', Parallel' ELSE '' END + - CASE WHEN near_parallel = 1 THEN ', Nearly Parallel' ELSE '' END + - CASE WHEN frequent_execution = 1 THEN ', Frequent Execution' ELSE '' END + - CASE WHEN plan_warnings = 1 THEN ', Plan Warnings' ELSE '' END + - CASE WHEN parameter_sniffing = 1 THEN ', Parameter Sniffing' ELSE '' END + - CASE WHEN long_running = 1 THEN ', Long Running Query' ELSE '' END + - CASE WHEN downlevel_estimator = 1 THEN ', Downlevel CE' ELSE '' END + - CASE WHEN implicit_conversions = 1 THEN ', Implicit Conversions' ELSE '' END + - CASE WHEN tvf_join = 1 THEN ', Function Join' ELSE '' END + - CASE WHEN plan_multiple_plans > 0 THEN ', Multiple Plans' + COALESCE(' (' + CAST(plan_multiple_plans AS VARCHAR(10)) + ')', '') ELSE '' END + - CASE WHEN is_trivial = 1 THEN ', Trivial Plans' ELSE '' END + - CASE WHEN is_forced_serial = 1 THEN ', Forced Serialization' ELSE '' END + - CASE WHEN is_key_lookup_expensive = 1 THEN ', Expensive Key Lookup' ELSE '' END + - CASE WHEN is_remote_query_expensive = 1 THEN ', Expensive Remote Query' ELSE '' END + - CASE WHEN trace_flags_session IS NOT NULL THEN ', Session Level Trace Flag(s) Enabled: ' + trace_flags_session ELSE '' END + - CASE WHEN is_unused_grant = 1 THEN ', Unused Memory Grant' ELSE '' END + - CASE WHEN function_count > 0 THEN ', Calls ' + CONVERT(VARCHAR(10), function_count) + ' Function(s)' ELSE '' END + - CASE WHEN clr_function_count > 0 THEN ', Calls ' + CONVERT(VARCHAR(10), clr_function_count) + ' CLR Function(s)' ELSE '' END + - CASE WHEN PlanCreationTimeHours <= 4 THEN ', Plan created last 4hrs' ELSE '' END + - CASE WHEN is_table_variable = 1 THEN ', Table Variables' ELSE '' END + - CASE WHEN no_stats_warning = 1 THEN ', Columns With No Statistics' ELSE '' END + - CASE WHEN relop_warnings = 1 THEN ', Operator Warnings' ELSE '' END + - CASE WHEN is_table_scan = 1 THEN ', Table Scans (Heaps)' ELSE '' END + - CASE WHEN backwards_scan = 1 THEN ', Backwards Scans' ELSE '' END + - CASE WHEN forced_index = 1 THEN ', Forced Indexes' ELSE '' END + - CASE WHEN forced_seek = 1 THEN ', Forced Seeks' ELSE '' END + - CASE WHEN forced_scan = 1 THEN ', Forced Scans' ELSE '' END + - CASE WHEN columnstore_row_mode = 1 THEN ', ColumnStore Row Mode ' ELSE '' END + - CASE WHEN is_computed_scalar = 1 THEN ', Computed Column UDF ' ELSE '' END + - CASE WHEN is_sort_expensive = 1 THEN ', Expensive Sort' ELSE '' END + - CASE WHEN is_computed_filter = 1 THEN ', Filter UDF' ELSE '' END + - CASE WHEN index_ops >= 5 THEN ', >= 5 Indexes Modified' ELSE '' END + - CASE WHEN is_row_level = 1 THEN ', Row Level Security' ELSE '' END + - CASE WHEN is_spatial = 1 THEN ', Spatial Index' ELSE '' END + - CASE WHEN index_dml = 1 THEN ', Index DML' ELSE '' END + - CASE WHEN table_dml = 1 THEN ', Table DML' ELSE '' END + - CASE WHEN low_cost_high_cpu = 1 THEN ', Low Cost High CPU' ELSE '' END + - CASE WHEN long_running_low_cpu = 1 THEN + ', Long Running With Low CPU' ELSE '' END + - CASE WHEN stale_stats = 1 THEN + ', Statistics used have > 100k modifications in the last 7 days' ELSE '' END + - CASE WHEN is_adaptive = 1 THEN + ', Adaptive Joins' ELSE '' END + - CASE WHEN is_spool_expensive = 1 THEN + ', Expensive Index Spool' ELSE '' END + - CASE WHEN is_spool_more_rows = 1 THEN + ', Large Index Row Spool' ELSE '' END + - CASE WHEN is_table_spool_expensive = 1 THEN + ', Expensive Table Spool' ELSE '' END + - CASE WHEN is_table_spool_more_rows = 1 THEN + ', Many Rows Table Spool' ELSE '' END + - CASE WHEN is_bad_estimate = 1 THEN + ', Row Estimate Mismatch' ELSE '' END + - CASE WHEN is_paul_white_electric = 1 THEN ', SWITCH!' ELSE '' END + - CASE WHEN is_row_goal = 1 THEN ', Row Goals' ELSE '' END + - CASE WHEN is_big_spills = 1 THEN ', >500mb Spills' ELSE '' END + - CASE WHEN is_mstvf = 1 THEN ', MSTVFs' ELSE '' END + - CASE WHEN is_mm_join = 1 THEN ', Many to Many Merge' ELSE '' END + - CASE WHEN is_nonsargable = 1 THEN ', non-SARGables' ELSE '' END + - CASE WHEN CompileTime > 5000 THEN ', Long Compile Time' ELSE '' END + - CASE WHEN CompileCPU > 5000 THEN ', High Compile CPU' ELSE '' END + - CASE WHEN CompileMemory > 1024 AND ((CompileMemory) / (1 * CASE WHEN MaxCompileMemory = 0 THEN 1 ELSE MaxCompileMemory END) * 100.) >= 10. THEN ', High Compile Memory' ELSE '' END + - CASE WHEN select_with_writes > 0 THEN ', Select w/ Writes' ELSE '' END - , 3, 200000) -WHERE SPID = @@SPID -OPTION (RECOMPILE); + IF OBJECT_ID('tempdb..#WaitStats') IS NOT NULL + DROP TABLE #WaitStats; + CREATE TABLE #WaitStats ( + Pass TINYINT NOT NULL, + wait_type NVARCHAR(60), + wait_time_ms BIGINT, + thread_time_ms FLOAT, + signal_wait_time_ms BIGINT, + waiting_tasks_count BIGINT, + SampleTime datetimeoffset + ); + IF OBJECT_ID('tempdb..#FileStats') IS NOT NULL + DROP TABLE #FileStats; + CREATE TABLE #FileStats ( + ID INT IDENTITY(1, 1) PRIMARY KEY CLUSTERED, + Pass TINYINT NOT NULL, + SampleTime DATETIMEOFFSET NOT NULL, + DatabaseID INT NOT NULL, + FileID INT NOT NULL, + DatabaseName NVARCHAR(256) , + FileLogicalName NVARCHAR(256) , + TypeDesc NVARCHAR(60) , + SizeOnDiskMB BIGINT , + io_stall_read_ms BIGINT , + num_of_reads BIGINT , + bytes_read BIGINT , + io_stall_write_ms BIGINT , + num_of_writes BIGINT , + bytes_written BIGINT, + PhysicalName NVARCHAR(520) , + avg_stall_read_ms INT , + avg_stall_write_ms INT + ); -RAISERROR('Populating Warnings column for stored procedures', 0, 1) WITH NOWAIT; -WITH statement_warnings AS - ( -SELECT DISTINCT - SqlHandle, - Warnings = SUBSTRING( - CASE WHEN warning_no_join_predicate = 1 THEN ', No Join Predicate' ELSE '' END + - CASE WHEN compile_timeout = 1 THEN ', Compilation Timeout' ELSE '' END + - CASE WHEN compile_memory_limit_exceeded = 1 THEN ', Compile Memory Limit Exceeded' ELSE '' END + - CASE WHEN busy_loops = 1 THEN ', Busy Loops' ELSE '' END + - CASE WHEN is_forced_plan = 1 THEN ', Forced Plan' ELSE '' END + - CASE WHEN is_forced_parameterized = 1 THEN ', Forced Parameterization' ELSE '' END + - --CASE WHEN unparameterized_query = 1 THEN ', Unparameterized Query' ELSE '' END + - CASE WHEN missing_index_count > 0 THEN ', Missing Indexes (' + CONVERT(VARCHAR(10), (SELECT SUM(b2.missing_index_count) FROM ##BlitzCacheProcs AS b2 WHERE b2.SqlHandle = b.SqlHandle AND b2.QueryHash IS NOT NULL AND SPID = @@SPID) ) + ')' ELSE '' END + - CASE WHEN unmatched_index_count > 0 THEN ', Unmatched Indexes (' + CONVERT(VARCHAR(10), (SELECT SUM(b2.unmatched_index_count) FROM ##BlitzCacheProcs AS b2 WHERE b2.SqlHandle = b.SqlHandle AND b2.QueryHash IS NOT NULL AND SPID = @@SPID) ) + ')' ELSE '' END + - CASE WHEN is_cursor = 1 THEN ', Cursor' - + CASE WHEN is_optimistic_cursor = 1 THEN '; optimistic' ELSE '' END - + CASE WHEN is_forward_only_cursor = 0 THEN '; not forward only' ELSE '' END - + CASE WHEN is_cursor_dynamic = 1 THEN '; dynamic' ELSE '' END - + CASE WHEN is_fast_forward_cursor = 1 THEN '; fast forward' ELSE '' END - ELSE '' END + - CASE WHEN is_parallel = 1 THEN ', Parallel' ELSE '' END + - CASE WHEN near_parallel = 1 THEN ', Nearly Parallel' ELSE '' END + - CASE WHEN frequent_execution = 1 THEN ', Frequent Execution' ELSE '' END + - CASE WHEN plan_warnings = 1 THEN ', Plan Warnings' ELSE '' END + - CASE WHEN parameter_sniffing = 1 THEN ', Parameter Sniffing' ELSE '' END + - CASE WHEN long_running = 1 THEN ', Long Running Query' ELSE '' END + - CASE WHEN downlevel_estimator = 1 THEN ', Downlevel CE' ELSE '' END + - CASE WHEN implicit_conversions = 1 THEN ', Implicit Conversions' ELSE '' END + - CASE WHEN tvf_join = 1 THEN ', Function Join' ELSE '' END + - CASE WHEN plan_multiple_plans > 0 THEN ', Multiple Plans' + COALESCE(' (' + CAST(plan_multiple_plans AS VARCHAR(10)) + ')', '') ELSE '' END + - CASE WHEN is_trivial = 1 THEN ', Trivial Plans' ELSE '' END + - CASE WHEN is_forced_serial = 1 THEN ', Forced Serialization' ELSE '' END + - CASE WHEN is_key_lookup_expensive = 1 THEN ', Expensive Key Lookup' ELSE '' END + - CASE WHEN is_remote_query_expensive = 1 THEN ', Expensive Remote Query' ELSE '' END + - CASE WHEN trace_flags_session IS NOT NULL THEN ', Session Level Trace Flag(s) Enabled: ' + trace_flags_session ELSE '' END + - CASE WHEN is_unused_grant = 1 THEN ', Unused Memory Grant' ELSE '' END + - CASE WHEN function_count > 0 THEN ', Calls ' + CONVERT(VARCHAR(10), (SELECT SUM(b2.function_count) FROM ##BlitzCacheProcs AS b2 WHERE b2.SqlHandle = b.SqlHandle AND b2.QueryHash IS NOT NULL AND SPID = @@SPID) ) + ' Function(s)' ELSE '' END + - CASE WHEN clr_function_count > 0 THEN ', Calls ' + CONVERT(VARCHAR(10), (SELECT SUM(b2.clr_function_count) FROM ##BlitzCacheProcs AS b2 WHERE b2.SqlHandle = b.SqlHandle AND b2.QueryHash IS NOT NULL AND SPID = @@SPID) ) + ' CLR Function(s)' ELSE '' END + - CASE WHEN PlanCreationTimeHours <= 4 THEN ', Plan created last 4hrs' ELSE '' END + - CASE WHEN is_table_variable = 1 THEN ', Table Variables' ELSE '' END + - CASE WHEN no_stats_warning = 1 THEN ', Columns With No Statistics' ELSE '' END + - CASE WHEN relop_warnings = 1 THEN ', Operator Warnings' ELSE '' END + - CASE WHEN is_table_scan = 1 THEN ', Table Scans' ELSE '' END + - CASE WHEN backwards_scan = 1 THEN ', Backwards Scans' ELSE '' END + - CASE WHEN forced_index = 1 THEN ', Forced Indexes' ELSE '' END + - CASE WHEN forced_seek = 1 THEN ', Forced Seeks' ELSE '' END + - CASE WHEN forced_scan = 1 THEN ', Forced Scans' ELSE '' END + - CASE WHEN columnstore_row_mode = 1 THEN ', ColumnStore Row Mode ' ELSE '' END + - CASE WHEN is_computed_scalar = 1 THEN ', Computed Column UDF ' ELSE '' END + - CASE WHEN is_sort_expensive = 1 THEN ', Expensive Sort' ELSE '' END + - CASE WHEN is_computed_filter = 1 THEN ', Filter UDF' ELSE '' END + - CASE WHEN index_ops >= 5 THEN ', >= 5 Indexes Modified' ELSE '' END + - CASE WHEN is_row_level = 1 THEN ', Row Level Security' ELSE '' END + - CASE WHEN is_spatial = 1 THEN ', Spatial Index' ELSE '' END + - CASE WHEN index_dml = 1 THEN ', Index DML' ELSE '' END + - CASE WHEN table_dml = 1 THEN ', Table DML' ELSE '' END + - CASE WHEN low_cost_high_cpu = 1 THEN ', Low Cost High CPU' ELSE '' END + - CASE WHEN long_running_low_cpu = 1 THEN + ', Long Running With Low CPU' ELSE '' END + - CASE WHEN stale_stats = 1 THEN + ', Statistics used have > 100k modifications in the last 7 days' ELSE '' END + - CASE WHEN is_adaptive = 1 THEN + ', Adaptive Joins' ELSE '' END + - CASE WHEN is_spool_expensive = 1 THEN + ', Expensive Index Spool' ELSE '' END + - CASE WHEN is_spool_more_rows = 1 THEN + ', Large Index Row Spool' ELSE '' END + - CASE WHEN is_table_spool_expensive = 1 THEN + ', Expensive Table Spool' ELSE '' END + - CASE WHEN is_table_spool_more_rows = 1 THEN + ', Many Rows Table Spool' ELSE '' END + - CASE WHEN is_bad_estimate = 1 THEN + ', Row Estimate Mismatch' ELSE '' END + - CASE WHEN is_paul_white_electric = 1 THEN ', SWITCH!' ELSE '' END + - CASE WHEN is_row_goal = 1 THEN ', Row Goals' ELSE '' END + - CASE WHEN is_big_spills = 1 THEN ', >500mb spills' ELSE '' END + - CASE WHEN is_mstvf = 1 THEN ', MSTVFs' ELSE '' END + - CASE WHEN is_mm_join = 1 THEN ', Many to Many Merge' ELSE '' END + - CASE WHEN is_nonsargable = 1 THEN ', non-SARGables' ELSE '' END + - CASE WHEN CompileTime > 5000 THEN ', Long Compile Time' ELSE '' END + - CASE WHEN CompileCPU > 5000 THEN ', High Compile CPU' ELSE '' END + - CASE WHEN CompileMemory > 1024 AND ((CompileMemory) / (1 * CASE WHEN MaxCompileMemory = 0 THEN 1 ELSE MaxCompileMemory END) * 100.) >= 10. THEN ', High Compile Memory' ELSE '' END + - CASE WHEN select_with_writes > 0 THEN ', Select w/ Writes' ELSE '' END - , 3, 200000) -FROM ##BlitzCacheProcs b -WHERE SPID = @@SPID -AND QueryType LIKE 'Statement (parent%' - ) -UPDATE b -SET b.Warnings = s.Warnings -FROM ##BlitzCacheProcs AS b -JOIN statement_warnings s -ON b.SqlHandle = s.SqlHandle -WHERE QueryType LIKE 'Procedure or Function%' -AND SPID = @@SPID -OPTION (RECOMPILE); + IF OBJECT_ID('tempdb..#QueryStats') IS NOT NULL + DROP TABLE #QueryStats; + CREATE TABLE #QueryStats ( + ID INT IDENTITY(1, 1) PRIMARY KEY CLUSTERED, + Pass INT NOT NULL, + SampleTime DATETIMEOFFSET NOT NULL, + [sql_handle] VARBINARY(64), + statement_start_offset INT, + statement_end_offset INT, + plan_generation_num BIGINT, + plan_handle VARBINARY(64), + execution_count BIGINT, + total_worker_time BIGINT, + total_physical_reads BIGINT, + total_logical_writes BIGINT, + total_logical_reads BIGINT, + total_clr_time BIGINT, + total_elapsed_time BIGINT, + creation_time DATETIMEOFFSET, + query_hash BINARY(8), + query_plan_hash BINARY(8), + Points TINYINT + ); -RAISERROR('Checking for plans with >128 levels of nesting', 0, 1) WITH NOWAIT; -WITH plan_handle AS ( -SELECT b.PlanHandle -FROM ##BlitzCacheProcs b - CROSS APPLY sys.dm_exec_text_query_plan(b.PlanHandle, 0, -1) tqp - CROSS APPLY sys.dm_exec_query_plan(b.PlanHandle) qp - WHERE tqp.encrypted = 0 - AND b.SPID = @@SPID - AND (qp.query_plan IS NULL - AND tqp.query_plan IS NOT NULL) -) -UPDATE b -SET Warnings = ISNULL('Your query plan is >128 levels of nested nodes, and can''t be converted to XML. Use SELECT * FROM sys.dm_exec_text_query_plan('+ CONVERT(VARCHAR(128), ph.PlanHandle, 1) + ', 0, -1) to get more information' - , 'We couldn''t find a plan for this query. More info on possible reasons: https://www.brentozar.com/go/noplans') -FROM ##BlitzCacheProcs b -LEFT JOIN plan_handle ph ON -b.PlanHandle = ph.PlanHandle -WHERE b.QueryPlan IS NULL -AND b.SPID = @@SPID -OPTION (RECOMPILE); + IF OBJECT_ID('tempdb..#PerfmonStats') IS NOT NULL + DROP TABLE #PerfmonStats; + CREATE TABLE #PerfmonStats ( + ID INT IDENTITY(1, 1) PRIMARY KEY CLUSTERED, + Pass TINYINT NOT NULL, + SampleTime DATETIMEOFFSET NOT NULL, + [object_name] NVARCHAR(128) NOT NULL, + [counter_name] NVARCHAR(128) NOT NULL, + [instance_name] NVARCHAR(128) NULL, + [cntr_value] BIGINT NULL, + [cntr_type] INT NOT NULL, + [value_delta] BIGINT NULL, + [value_per_second] DECIMAL(18,2) NULL + ); -RAISERROR('Checking for plans with no warnings', 0, 1) WITH NOWAIT; -UPDATE ##BlitzCacheProcs -SET Warnings = 'No warnings detected. ' + CASE @ExpertMode - WHEN 0 - THEN ' Try running sp_BlitzCache with @ExpertMode = 1 to find more advanced problems.' - ELSE '' - END -WHERE Warnings = '' OR Warnings IS NULL -AND SPID = @@SPID -OPTION (RECOMPILE); + IF OBJECT_ID('tempdb..#PerfmonCounters') IS NOT NULL + DROP TABLE #PerfmonCounters; + CREATE TABLE #PerfmonCounters ( + ID INT IDENTITY(1, 1) PRIMARY KEY CLUSTERED, + [object_name] NVARCHAR(128) NOT NULL, + [counter_name] NVARCHAR(128) NOT NULL, + [instance_name] NVARCHAR(128) NULL + ); + IF OBJECT_ID('tempdb..#FilterPlansByDatabase') IS NOT NULL + DROP TABLE #FilterPlansByDatabase; + CREATE TABLE #FilterPlansByDatabase (DatabaseID INT PRIMARY KEY CLUSTERED); -Results: -IF @ExportToExcel = 1 -BEGIN - RAISERROR('Displaying results with Excel formatting (no plans).', 0, 1) WITH NOWAIT; + IF OBJECT_ID ('tempdb..#checkversion') IS NOT NULL + DROP TABLE #checkversion; + CREATE TABLE #checkversion ( + version NVARCHAR(128), + common_version AS SUBSTRING(version, 1, CHARINDEX('.', version) + 1 ), + major AS PARSENAME(CONVERT(VARCHAR(32), version), 4), + minor AS PARSENAME(CONVERT(VARCHAR(32), version), 3), + build AS PARSENAME(CONVERT(VARCHAR(32), version), 2), + revision AS PARSENAME(CONVERT(VARCHAR(32), version), 1) + ); - /* excel output */ - UPDATE ##BlitzCacheProcs - SET QueryText = SUBSTRING(REPLACE(REPLACE(REPLACE(LTRIM(RTRIM(QueryText)),' ','<>'),'><',''),'<>',' '), 1, 32000) - OPTION(RECOMPILE); + IF OBJECT_ID('tempdb..##WaitCategories') IS NULL + BEGIN + /* We reuse this one by default rather than recreate it every time. */ + CREATE TABLE ##WaitCategories + ( + WaitType NVARCHAR(60) PRIMARY KEY CLUSTERED, + WaitCategory NVARCHAR(128) NOT NULL, + Ignorable BIT DEFAULT 0 + ); + END; /* IF OBJECT_ID('tempdb..##WaitCategories') IS NULL */ - SET @sql = N' - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT TOP (@Top) - DatabaseName AS [Database Name], - QueryPlanCost AS [Cost], - QueryText, - QueryType AS [Query Type], - Warnings, - ExecutionCount, - ExecutionsPerMinute AS [Executions / Minute], - PercentExecutions AS [Execution Weight], - PercentExecutionsByType AS [% Executions (Type)], - SerialDesiredMemory AS [Serial Desired Memory], - SerialRequiredMemory AS [Serial Required Memory], - TotalCPU AS [Total CPU (ms)], - AverageCPU AS [Avg CPU (ms)], - PercentCPU AS [CPU Weight], - PercentCPUByType AS [% CPU (Type)], - TotalDuration AS [Total Duration (ms)], - AverageDuration AS [Avg Duration (ms)], - PercentDuration AS [Duration Weight], - PercentDurationByType AS [% Duration (Type)], - TotalReads AS [Total Reads], - AverageReads AS [Average Reads], - PercentReads AS [Read Weight], - PercentReadsByType AS [% Reads (Type)], - TotalWrites AS [Total Writes], - AverageWrites AS [Average Writes], - PercentWrites AS [Write Weight], - PercentWritesByType AS [% Writes (Type)], - TotalReturnedRows, - AverageReturnedRows, - MinReturnedRows, - MaxReturnedRows, - MinGrantKB, - MaxGrantKB, - MinUsedGrantKB, - MaxUsedGrantKB, - PercentMemoryGrantUsed, - AvgMaxMemoryGrant, - MinSpills, - MaxSpills, - TotalSpills, - AvgSpills, - NumberOfPlans, - NumberOfDistinctPlans, - PlanCreationTime AS [Created At], - LastExecutionTime AS [Last Execution], - StatementStartOffset, - StatementEndOffset, - PlanGenerationNum, - PlanHandle AS [Plan Handle], - SqlHandle AS [SQL Handle], - QueryHash, - QueryPlanHash, - COALESCE(SetOptions, '''') AS [SET Options] - FROM ##BlitzCacheProcs - WHERE 1 = 1 - AND SPID = @@SPID ' + @nl; - - IF @MinimumExecutionCount IS NOT NULL - BEGIN - SET @sql += N' AND ExecutionCount >= @MinimumExecutionCount '; - END; - - IF @MinutesBack IS NOT NULL - BEGIN - SET @sql += N' AND LastCompletionTime >= DATEADD(MINUTE, @min_back, GETDATE() ) '; - END; - - SELECT @sql += N' ORDER BY ' + CASE @SortOrder WHEN N'cpu' THEN N' TotalCPU ' - WHEN N'reads' THEN N' TotalReads ' - WHEN N'writes' THEN N' TotalWrites ' - WHEN N'duration' THEN N' TotalDuration ' - WHEN N'executions' THEN N' ExecutionCount ' - WHEN N'compiles' THEN N' PlanCreationTime ' - WHEN N'memory grant' THEN N' MaxGrantKB' - WHEN N'unused grant' THEN N' MaxGrantKB - MaxUsedGrantKB' - WHEN N'spills' THEN N' MaxSpills' - WHEN N'duplicate' THEN N' plan_multiple_plans ' /* Issue #3345 */ - WHEN N'avg cpu' THEN N' AverageCPU' - WHEN N'avg reads' THEN N' AverageReads' - WHEN N'avg writes' THEN N' AverageWrites' - WHEN N'avg duration' THEN N' AverageDuration' - WHEN N'avg executions' THEN N' ExecutionsPerMinute' - WHEN N'avg memory grant' THEN N' AvgMaxMemoryGrant' - WHEN N'avg spills' THEN N' AvgSpills' - END + N' DESC '; - - SET @sql += N' OPTION (RECOMPILE) ; '; - - IF @sql IS NULL - BEGIN - RAISERROR('@sql is null, which means dynamic SQL generation went terribly wrong', 0, 1) WITH NOWAIT; - END - - IF @Debug = 1 - BEGIN - RAISERROR('Printing @sql, the dynamic SQL we generated:', 0, 1) WITH NOWAIT; - PRINT SUBSTRING(@sql, 0, 4000); - PRINT SUBSTRING(@sql, 4000, 8000); - PRINT SUBSTRING(@sql, 8000, 12000); - PRINT SUBSTRING(@sql, 12000, 16000); - PRINT SUBSTRING(@sql, 16000, 20000); - PRINT SUBSTRING(@sql, 20000, 24000); - PRINT SUBSTRING(@sql, 24000, 28000); - PRINT SUBSTRING(@sql, 28000, 32000); - PRINT SUBSTRING(@sql, 32000, 36000); - PRINT SUBSTRING(@sql, 36000, 40000); - END; - - EXEC sp_executesql @sql, N'@Top INT, @min_duration INT, @min_back INT, @MinimumExecutionCount INT', @Top, @DurationFilter_i, @MinutesBack, @MinimumExecutionCount; -END; - - -RAISERROR('Displaying analysis of plan cache.', 0, 1) WITH NOWAIT; - -DECLARE @columns NVARCHAR(MAX) = N'' ; - -IF @ExpertMode = 0 -BEGIN - RAISERROR(N'Returning ExpertMode = 0', 0, 1) WITH NOWAIT; - SET @columns = N' DatabaseName AS [Database], - QueryPlanCost AS [Cost], - QueryText AS [Query Text], - QueryType AS [Query Type], - Warnings AS [Warnings], - QueryPlan AS [Query Plan], - missing_indexes AS [Missing Indexes], - implicit_conversion_info AS [Implicit Conversion Info], - cached_execution_parameters AS [Cached Execution Parameters], - CONVERT(NVARCHAR(30), CAST((ExecutionCount) AS BIGINT), 1) AS [# Executions], - CONVERT(NVARCHAR(30), CAST((ExecutionsPerMinute) AS BIGINT), 1) AS [Executions / Minute], - CONVERT(NVARCHAR(30), CAST((PercentExecutions) AS BIGINT), 1) AS [Execution Weight], - CONVERT(NVARCHAR(30), CAST((TotalCPU) AS BIGINT), 1) AS [Total CPU (ms)], - CONVERT(NVARCHAR(30), CAST((AverageCPU) AS BIGINT), 1) AS [Avg CPU (ms)], - CONVERT(NVARCHAR(30), CAST((PercentCPU) AS BIGINT), 1) AS [CPU Weight], - CONVERT(NVARCHAR(30), CAST((TotalDuration) AS BIGINT), 1) AS [Total Duration (ms)], - CONVERT(NVARCHAR(30), CAST((AverageDuration) AS BIGINT), 1) AS [Avg Duration (ms)], - CONVERT(NVARCHAR(30), CAST((PercentDuration) AS BIGINT), 1) AS [Duration Weight], - CONVERT(NVARCHAR(30), CAST((TotalReads) AS BIGINT), 1) AS [Total Reads], - CONVERT(NVARCHAR(30), CAST((AverageReads) AS BIGINT), 1) AS [Avg Reads], - CONVERT(NVARCHAR(30), CAST((PercentReads) AS BIGINT), 1) AS [Read Weight], - CONVERT(NVARCHAR(30), CAST((TotalWrites) AS BIGINT), 1) AS [Total Writes], - CONVERT(NVARCHAR(30), CAST((AverageWrites) AS BIGINT), 1) AS [Avg Writes], - CONVERT(NVARCHAR(30), CAST((PercentWrites) AS BIGINT), 1) AS [Write Weight], - CONVERT(NVARCHAR(30), CAST((AverageReturnedRows) AS BIGINT), 1) AS [Average Rows], - CONVERT(NVARCHAR(30), CAST((MinGrantKB) AS BIGINT), 1) AS [Minimum Memory Grant KB], - CONVERT(NVARCHAR(30), CAST((MaxGrantKB) AS BIGINT), 1) AS [Maximum Memory Grant KB], - CONVERT(NVARCHAR(30), CAST((MinUsedGrantKB) AS BIGINT), 1) AS [Minimum Used Grant KB], - CONVERT(NVARCHAR(30), CAST((MaxUsedGrantKB) AS BIGINT), 1) AS [Maximum Used Grant KB], - CONVERT(NVARCHAR(30), CAST((AvgMaxMemoryGrant) AS BIGINT), 1) AS [Average Max Memory Grant], - CONVERT(NVARCHAR(30), CAST((MinSpills) AS BIGINT), 1) AS [Min Spills], - CONVERT(NVARCHAR(30), CAST((MaxSpills) AS BIGINT), 1) AS [Max Spills], - CONVERT(NVARCHAR(30), CAST((TotalSpills) AS BIGINT), 1) AS [Total Spills], - CONVERT(NVARCHAR(30), CAST((AvgSpills) AS MONEY), 1) AS [Avg Spills], - PlanCreationTime AS [Created At], - LastExecutionTime AS [Last Execution], - LastCompletionTime AS [Last Completion], - PlanHandle AS [Plan Handle], - SqlHandle AS [SQL Handle], - COALESCE(SetOptions, '''') AS [SET Options], - QueryHash AS [Query Hash], - PlanGenerationNum, - [Remove Plan Handle From Cache]'; -END; -ELSE -BEGIN - SET @columns = N' DatabaseName AS [Database], - QueryPlanCost AS [Cost], - QueryText AS [Query Text], - QueryType AS [Query Type], - Warnings AS [Warnings], - QueryPlan AS [Query Plan], - missing_indexes AS [Missing Indexes], - implicit_conversion_info AS [Implicit Conversion Info], - cached_execution_parameters AS [Cached Execution Parameters], ' + @nl; - - IF @ExpertMode = 2 /* Opserver */ - BEGIN - RAISERROR(N'Returning Expert Mode = 2', 0, 1) WITH NOWAIT; - SET @columns += N' - SUBSTRING( - CASE WHEN warning_no_join_predicate = 1 THEN '', 20'' ELSE '''' END + - CASE WHEN compile_timeout = 1 THEN '', 18'' ELSE '''' END + - CASE WHEN compile_memory_limit_exceeded = 1 THEN '', 19'' ELSE '''' END + - CASE WHEN busy_loops = 1 THEN '', 16'' ELSE '''' END + - CASE WHEN is_forced_plan = 1 THEN '', 3'' ELSE '''' END + - CASE WHEN is_forced_parameterized > 0 THEN '', 5'' ELSE '''' END + - CASE WHEN unparameterized_query = 1 THEN '', 23'' ELSE '''' END + - CASE WHEN missing_index_count > 0 THEN '', 10'' ELSE '''' END + - CASE WHEN unmatched_index_count > 0 THEN '', 22'' ELSE '''' END + - CASE WHEN is_cursor = 1 THEN '', 4'' ELSE '''' END + - CASE WHEN is_parallel = 1 THEN '', 6'' ELSE '''' END + - CASE WHEN near_parallel = 1 THEN '', 7'' ELSE '''' END + - CASE WHEN frequent_execution = 1 THEN '', 1'' ELSE '''' END + - CASE WHEN plan_warnings = 1 THEN '', 8'' ELSE '''' END + - CASE WHEN parameter_sniffing = 1 THEN '', 2'' ELSE '''' END + - CASE WHEN long_running = 1 THEN '', 9'' ELSE '''' END + - CASE WHEN downlevel_estimator = 1 THEN '', 13'' ELSE '''' END + - CASE WHEN implicit_conversions = 1 THEN '', 14'' ELSE '''' END + - CASE WHEN tvf_join = 1 THEN '', 17'' ELSE '''' END + - CASE WHEN plan_multiple_plans > 0 THEN '', 21'' ELSE '''' END + - CASE WHEN unmatched_index_count > 0 THEN '', 22'' ELSE '''' END + - CASE WHEN is_trivial = 1 THEN '', 24'' ELSE '''' END + - CASE WHEN is_forced_serial = 1 THEN '', 25'' ELSE '''' END + - CASE WHEN is_key_lookup_expensive = 1 THEN '', 26'' ELSE '''' END + - CASE WHEN is_remote_query_expensive = 1 THEN '', 28'' ELSE '''' END + - CASE WHEN trace_flags_session IS NOT NULL THEN '', 29'' ELSE '''' END + - CASE WHEN is_unused_grant = 1 THEN '', 30'' ELSE '''' END + - CASE WHEN function_count > 0 THEN '', 31'' ELSE '''' END + - CASE WHEN clr_function_count > 0 THEN '', 32'' ELSE '''' END + - CASE WHEN PlanCreationTimeHours <= 4 THEN '', 33'' ELSE '''' END + - CASE WHEN is_table_variable = 1 THEN '', 34'' ELSE '''' END + - CASE WHEN no_stats_warning = 1 THEN '', 35'' ELSE '''' END + - CASE WHEN relop_warnings = 1 THEN '', 36'' ELSE '''' END + - CASE WHEN is_table_scan = 1 THEN '', 37'' ELSE '''' END + - CASE WHEN backwards_scan = 1 THEN '', 38'' ELSE '''' END + - CASE WHEN forced_index = 1 THEN '', 39'' ELSE '''' END + - CASE WHEN forced_seek = 1 OR forced_scan = 1 THEN '', 40'' ELSE '''' END + - CASE WHEN columnstore_row_mode = 1 THEN '', 41'' ELSE '''' END + - CASE WHEN is_computed_scalar = 1 THEN '', 42'' ELSE '''' END + - CASE WHEN is_sort_expensive = 1 THEN '', 43'' ELSE '''' END + - CASE WHEN is_computed_filter = 1 THEN '', 44'' ELSE '''' END + - CASE WHEN index_ops >= 5 THEN '', 45'' ELSE '''' END + - CASE WHEN is_row_level = 1 THEN '', 46'' ELSE '''' END + - CASE WHEN is_spatial = 1 THEN '', 47'' ELSE '''' END + - CASE WHEN index_dml = 1 THEN '', 48'' ELSE '''' END + - CASE WHEN table_dml = 1 THEN '', 49'' ELSE '''' END + - CASE WHEN long_running_low_cpu = 1 THEN '', 50'' ELSE '''' END + - CASE WHEN low_cost_high_cpu = 1 THEN '', 51'' ELSE '''' END + - CASE WHEN stale_stats = 1 THEN '', 52'' ELSE '''' END + - CASE WHEN is_adaptive = 1 THEN '', 53'' ELSE '''' END + - CASE WHEN is_spool_expensive = 1 THEN + '', 54'' ELSE '''' END + - CASE WHEN is_spool_more_rows = 1 THEN + '', 55'' ELSE '''' END + - CASE WHEN is_table_spool_expensive = 1 THEN + '', 67'' ELSE '''' END + - CASE WHEN is_table_spool_more_rows = 1 THEN + '', 68'' ELSE '''' END + - CASE WHEN is_bad_estimate = 1 THEN + '', 56'' ELSE '''' END + - CASE WHEN is_paul_white_electric = 1 THEN '', 57'' ELSE '''' END + - CASE WHEN is_row_goal = 1 THEN '', 58'' ELSE '''' END + - CASE WHEN is_big_spills = 1 THEN '', 59'' ELSE '''' END + - CASE WHEN is_mstvf = 1 THEN '', 60'' ELSE '''' END + - CASE WHEN is_mm_join = 1 THEN '', 61'' ELSE '''' END + - CASE WHEN is_nonsargable = 1 THEN '', 62'' ELSE '''' END + - CASE WHEN CompileTime > 5000 THEN '', 63 '' ELSE '''' END + - CASE WHEN CompileCPU > 5000 THEN '', 64 '' ELSE '''' END + - CASE WHEN CompileMemory > 1024 AND ((CompileMemory) / (1 * CASE WHEN MaxCompileMemory = 0 THEN 1 ELSE MaxCompileMemory END) * 100.) >= 10. THEN '', 65 '' ELSE '''' END + - CASE WHEN select_with_writes > 0 THEN '', 66'' ELSE '''' END - , 3, 200000) AS opserver_warning , ' + @nl ; - END; - - SET @columns += N' - CONVERT(NVARCHAR(30), CAST((ExecutionCount) AS BIGINT), 1) AS [# Executions], - CONVERT(NVARCHAR(30), CAST((ExecutionsPerMinute) AS BIGINT), 1) AS [Executions / Minute], - CONVERT(NVARCHAR(30), CAST((PercentExecutions) AS BIGINT), 1) AS [Execution Weight], - CONVERT(NVARCHAR(30), CAST((SerialDesiredMemory) AS BIGINT), 1) AS [Serial Desired Memory], - CONVERT(NVARCHAR(30), CAST((SerialRequiredMemory) AS BIGINT), 1) AS [Serial Required Memory], - CONVERT(NVARCHAR(30), CAST((TotalCPU) AS BIGINT), 1) AS [Total CPU (ms)], - CONVERT(NVARCHAR(30), CAST((AverageCPU) AS BIGINT), 1) AS [Avg CPU (ms)], - CONVERT(NVARCHAR(30), CAST((PercentCPU) AS BIGINT), 1) AS [CPU Weight], - CONVERT(NVARCHAR(30), CAST((TotalDuration) AS BIGINT), 1) AS [Total Duration (ms)], - CONVERT(NVARCHAR(30), CAST((AverageDuration) AS BIGINT), 1) AS [Avg Duration (ms)], - CONVERT(NVARCHAR(30), CAST((PercentDuration) AS BIGINT), 1) AS [Duration Weight], - CONVERT(NVARCHAR(30), CAST((TotalReads) AS BIGINT), 1) AS [Total Reads], - CONVERT(NVARCHAR(30), CAST((AverageReads) AS BIGINT), 1) AS [Average Reads], - CONVERT(NVARCHAR(30), CAST((PercentReads) AS BIGINT), 1) AS [Read Weight], - CONVERT(NVARCHAR(30), CAST((TotalWrites) AS BIGINT), 1) AS [Total Writes], - CONVERT(NVARCHAR(30), CAST((AverageWrites) AS BIGINT), 1) AS [Average Writes], - CONVERT(NVARCHAR(30), CAST((PercentWrites) AS BIGINT), 1) AS [Write Weight], - CONVERT(NVARCHAR(30), CAST((PercentExecutionsByType) AS BIGINT), 1) AS [% Executions (Type)], - CONVERT(NVARCHAR(30), CAST((PercentCPUByType) AS BIGINT), 1) AS [% CPU (Type)], - CONVERT(NVARCHAR(30), CAST((PercentDurationByType) AS BIGINT), 1) AS [% Duration (Type)], - CONVERT(NVARCHAR(30), CAST((PercentReadsByType) AS BIGINT), 1) AS [% Reads (Type)], - CONVERT(NVARCHAR(30), CAST((PercentWritesByType) AS BIGINT), 1) AS [% Writes (Type)], - CONVERT(NVARCHAR(30), CAST((TotalReturnedRows) AS BIGINT), 1) AS [Total Rows], - CONVERT(NVARCHAR(30), CAST((AverageReturnedRows) AS BIGINT), 1) AS [Avg Rows], - CONVERT(NVARCHAR(30), CAST((MinReturnedRows) AS BIGINT), 1) AS [Min Rows], - CONVERT(NVARCHAR(30), CAST((MaxReturnedRows) AS BIGINT), 1) AS [Max Rows], - CONVERT(NVARCHAR(30), CAST((MinGrantKB) AS BIGINT), 1) AS [Minimum Memory Grant KB], - CONVERT(NVARCHAR(30), CAST((MaxGrantKB) AS BIGINT), 1) AS [Maximum Memory Grant KB], - CONVERT(NVARCHAR(30), CAST((MinUsedGrantKB) AS BIGINT), 1) AS [Minimum Used Grant KB], - CONVERT(NVARCHAR(30), CAST((MaxUsedGrantKB) AS BIGINT), 1) AS [Maximum Used Grant KB], - CONVERT(NVARCHAR(30), CAST((AvgMaxMemoryGrant) AS BIGINT), 1) AS [Average Max Memory Grant], - CONVERT(NVARCHAR(30), CAST((MinSpills) AS BIGINT), 1) AS [Min Spills], - CONVERT(NVARCHAR(30), CAST((MaxSpills) AS BIGINT), 1) AS [Max Spills], - CONVERT(NVARCHAR(30), CAST((TotalSpills) AS BIGINT), 1) AS [Total Spills], - CONVERT(NVARCHAR(30), CAST((AvgSpills) AS MONEY), 1) AS [Avg Spills], - CONVERT(NVARCHAR(30), CAST((NumberOfPlans) AS BIGINT), 1) AS [# Plans], - CONVERT(NVARCHAR(30), CAST((NumberOfDistinctPlans) AS BIGINT), 1) AS [# Distinct Plans], - PlanCreationTime AS [Created At], - LastExecutionTime AS [Last Execution], - LastCompletionTime AS [Last Completion], - CONVERT(NVARCHAR(30), CAST((CachedPlanSize) AS BIGINT), 1) AS [Cached Plan Size (KB)], - CONVERT(NVARCHAR(30), CAST((CompileTime) AS BIGINT), 1) AS [Compile Time (ms)], - CONVERT(NVARCHAR(30), CAST((CompileCPU) AS BIGINT), 1) AS [Compile CPU (ms)], - CONVERT(NVARCHAR(30), CAST((CompileMemory) AS BIGINT), 1) AS [Compile memory (KB)], - COALESCE(SetOptions, '''') AS [SET Options], - PlanHandle AS [Plan Handle], - SqlHandle AS [SQL Handle], - [SQL Handle More Info], - QueryHash AS [Query Hash], - [Query Hash More Info], - QueryPlanHash AS [Query Plan Hash], - StatementStartOffset, - StatementEndOffset, - PlanGenerationNum, - [Remove Plan Handle From Cache], - [Remove SQL Handle From Cache]'; -END; - -SET @sql = N' -SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT TOP (@Top) ' + @columns + @nl + N' -FROM ##BlitzCacheProcs -WHERE SPID = @spid ' + @nl; - -IF @MinimumExecutionCount IS NOT NULL - BEGIN - SET @sql += N' AND ExecutionCount >= @MinimumExecutionCount ' + @nl; - END; - -IF @MinutesBack IS NOT NULL - BEGIN - SET @sql += N' AND LastCompletionTime >= DATEADD(MINUTE, @min_back, GETDATE() ) ' + @nl; - END; - -SELECT @sql += N' ORDER BY ' + CASE @SortOrder WHEN N'cpu' THEN N' TotalCPU ' - WHEN N'reads' THEN N' TotalReads ' - WHEN N'writes' THEN N' TotalWrites ' - WHEN N'duration' THEN N' TotalDuration ' - WHEN N'executions' THEN N' ExecutionCount ' - WHEN N'compiles' THEN N' PlanCreationTime ' - WHEN N'memory grant' THEN N' MaxGrantKB' - WHEN N'unused grant' THEN N' MaxGrantKB - MaxUsedGrantKB ' - WHEN N'duplicate' THEN N' plan_multiple_plans ' - WHEN N'spills' THEN N' MaxSpills ' - WHEN N'avg cpu' THEN N' AverageCPU' - WHEN N'avg reads' THEN N' AverageReads' - WHEN N'avg writes' THEN N' AverageWrites' - WHEN N'avg duration' THEN N' AverageDuration' - WHEN N'avg executions' THEN N' ExecutionsPerMinute' - WHEN N'avg memory grant' THEN N' AvgMaxMemoryGrant' - WHEN N'avg spills' THEN N' AvgSpills' - END + N' DESC '; -SET @sql += N' OPTION (RECOMPILE) ; '; - -IF @Debug = 1 - BEGIN - PRINT SUBSTRING(@sql, 0, 4000); - PRINT SUBSTRING(@sql, 4000, 8000); - PRINT SUBSTRING(@sql, 8000, 12000); - PRINT SUBSTRING(@sql, 12000, 16000); - PRINT SUBSTRING(@sql, 16000, 20000); - PRINT SUBSTRING(@sql, 20000, 24000); - PRINT SUBSTRING(@sql, 24000, 28000); - PRINT SUBSTRING(@sql, 28000, 32000); - PRINT SUBSTRING(@sql, 32000, 36000); - PRINT SUBSTRING(@sql, 36000, 40000); - END; -IF(@OutputType <> 'NONE') -BEGIN - EXEC sp_executesql @sql, N'@Top INT, @spid INT, @MinimumExecutionCount INT, @min_back INT', @Top, @@SPID, @MinimumExecutionCount, @MinutesBack; -END; - -/* - -This section will check if: - * >= 30% of plans were created in the last hour - * Check on the memory_clerks DMV for space used by TokenAndPermUserStore - * Compare that to the size of the buffer pool - * If it's >10%, -*/ -IF EXISTS -( - SELECT 1/0 - FROM #plan_creation AS pc - WHERE pc.percent_1 >= 30 -) -BEGIN - -SELECT @common_version = - CONVERT(DECIMAL(10,2), c.common_version) -FROM #checkversion AS c; - -IF @common_version >= 11 - SET @user_perm_sql = N' - SET @buffer_pool_memory_gb = 0; - SELECT @buffer_pool_memory_gb = SUM(pages_kb)/ 1024. / 1024. - FROM sys.dm_os_memory_clerks - WHERE type = ''MEMORYCLERK_SQLBUFFERPOOL'';' -ELSE - SET @user_perm_sql = N' - SET @buffer_pool_memory_gb = 0; - SELECT @buffer_pool_memory_gb = SUM(single_pages_kb + multi_pages_kb)/ 1024. / 1024. - FROM sys.dm_os_memory_clerks - WHERE type = ''MEMORYCLERK_SQLBUFFERPOOL'';' - -EXEC sys.sp_executesql @user_perm_sql, - N'@buffer_pool_memory_gb DECIMAL(10,2) OUTPUT', - @buffer_pool_memory_gb = @buffer_pool_memory_gb OUTPUT; - -IF @common_version >= 11 -BEGIN - SET @user_perm_sql = N' - SELECT @user_perm_gb = CASE WHEN (pages_kb / 1024.0 / 1024.) >= 2. - THEN CONVERT(DECIMAL(38, 2), (pages_kb / 1024.0 / 1024.)) - ELSE 0 - END - FROM sys.dm_os_memory_clerks - WHERE type = ''USERSTORE_TOKENPERM'' - AND name = ''TokenAndPermUserStore'';'; -END; - -IF @common_version < 11 -BEGIN - SET @user_perm_sql = N' - SELECT @user_perm_gb = CASE WHEN ((single_pages_kb + multi_pages_kb) / 1024.0 / 1024.) >= 2. - THEN CONVERT(DECIMAL(38, 2), ((single_pages_kb + multi_pages_kb) / 1024.0 / 1024.)) - ELSE 0 - END - FROM sys.dm_os_memory_clerks - WHERE type = ''USERSTORE_TOKENPERM'' - AND name = ''TokenAndPermUserStore'';'; -END; - -EXEC sys.sp_executesql @user_perm_sql, - N'@user_perm_gb DECIMAL(10,2) OUTPUT', - @user_perm_gb = @user_perm_gb_out OUTPUT; - -IF @buffer_pool_memory_gb > 0 - BEGIN - IF (@user_perm_gb_out / (1. * @buffer_pool_memory_gb)) * 100. >= 10 - BEGIN - SET @is_tokenstore_big = 1; - SET @user_perm_percent = (@user_perm_gb_out / (1. * @buffer_pool_memory_gb)) * 100.; - END - END - -END - - - -IF @HideSummary = 0 AND @ExportToExcel = 0 -BEGIN - IF @Reanalyze = 0 - BEGIN - RAISERROR('Building query plan summary data.', 0, 1) WITH NOWAIT; - - /* Build summary data */ - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs - WHERE frequent_execution = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 1, - 100, - 'Execution Pattern', - 'Frequent Execution', - 'https://www.brentozar.com/blitzcache/frequently-executed-queries/', - 'Queries are being executed more than ' - + CAST (@execution_threshold AS VARCHAR(5)) - + ' times per minute. This can put additional load on the server, even when queries are lightweight.') ; - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs - WHERE parameter_sniffing = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 2, - 50, - 'Parameterization', - 'Parameter Sniffing', - 'https://www.brentozar.com/blitzcache/parameter-sniffing/', - 'There are signs of parameter sniffing (wide variance in rows return or time to execute). Investigate query patterns and tune code appropriately.') ; - - /* Forced execution plans */ - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs - WHERE is_forced_plan = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 3, - 50, - 'Parameterization', - 'Forced Plan', - 'https://www.brentozar.com/blitzcache/forced-plans/', - 'Execution plans have been compiled with forced plans, either through FORCEPLAN, plan guides, or forced parameterization. This will make general tuning efforts less effective.'); - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs - WHERE is_cursor = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 4, - 200, - 'Cursors', - 'Cursor', - 'https://www.brentozar.com/blitzcache/cursors-found-slow-queries/', - 'There are cursors in the plan cache. This is neither good nor bad, but it is a thing. Cursors are weird in SQL Server.'); - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs - WHERE is_cursor = 1 - AND is_optimistic_cursor = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 4, - 200, - 'Cursors', - 'Optimistic Cursors', - 'https://www.brentozar.com/blitzcache/cursors-found-slow-queries/', - 'There are optimistic cursors in the plan cache, which can harm performance.'); - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs - WHERE is_cursor = 1 - AND is_forward_only_cursor = 0 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 4, - 200, - 'Cursors', - 'Non-forward Only Cursors', - 'https://www.brentozar.com/blitzcache/cursors-found-slow-queries/', - 'There are non-forward only cursors in the plan cache, which can harm performance.'); - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs - WHERE is_cursor = 1 - AND is_cursor_dynamic = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 4, - 200, - 'Cursors', - 'Dynamic Cursors', - 'https://www.brentozar.com/blitzcache/cursors-found-slow-queries/', - 'Dynamic Cursors inhibit parallelism!.'); - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs - WHERE is_cursor = 1 - AND is_fast_forward_cursor = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 4, - 200, - 'Cursors', - 'Fast Forward Cursors', - 'https://www.brentozar.com/blitzcache/cursors-found-slow-queries/', - 'Fast forward cursors inhibit parallelism!.'); - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs - WHERE is_forced_parameterized = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 5, - 50, - 'Parameterization', - 'Forced Parameterization', - 'https://www.brentozar.com/blitzcache/forced-parameterization/', - 'Execution plans have been compiled with forced parameterization.') ; - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.is_parallel = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 6, - 200, - 'Execution Plans', - 'Parallel', - 'https://www.brentozar.com/blitzcache/parallel-plans-detected/', - 'Parallel plans detected. These warrant investigation, but are neither good nor bad.') ; - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE near_parallel = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 7, - 200, - 'Execution Plans', - 'Nearly Parallel', - 'https://www.brentozar.com/blitzcache/query-cost-near-cost-threshold-parallelism/', - 'Queries near the cost threshold for parallelism. These may go parallel when you least expect it.') ; - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE plan_warnings = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 8, - 50, - 'Execution Plans', - 'Plan Warnings', - 'https://www.brentozar.com/blitzcache/query-plan-warnings/', - 'Warnings detected in execution plans. SQL Server is telling you that something bad is going on that requires your attention.') ; - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE long_running = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 9, - 50, - 'Performance', - 'Long Running Query', - 'https://www.brentozar.com/blitzcache/long-running-queries/', - 'Long running queries have been found. These are queries with an average duration longer than ' - + CAST(@long_running_query_warning_seconds / 1000 / 1000 AS VARCHAR(5)) - + ' second(s). These queries should be investigated for additional tuning options.') ; - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.missing_index_count > 0 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 10, - 50, - 'Performance', - 'Missing Indexes', - 'https://www.brentozar.com/blitzcache/missing-index-request/', - 'Queries found with missing indexes.'); - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.downlevel_estimator = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 13, - 200, - 'Cardinality', - 'Downlevel CE', - 'https://www.brentozar.com/blitzcache/legacy-cardinality-estimator/', - 'A legacy cardinality estimator is being used by one or more queries. Investigate whether you need to be using this cardinality estimator. This may be caused by compatibility levels, global trace flags, or query level trace flags.'); - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE implicit_conversions = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 14, - 50, - 'Performance', - 'Implicit Conversions', - 'https://www.brentozar.com/go/implicit', - 'One or more queries are comparing two fields that are not of the same data type.') ; - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs - WHERE busy_loops = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 16, - 100, - 'Performance', - 'Busy Loops', - 'https://www.brentozar.com/blitzcache/busy-loops/', - 'Operations have been found that are executed 100 times more often than the number of rows returned by each iteration. This is an indicator that something is off in query execution.'); - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs - WHERE tvf_join = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 17, - 50, - 'Performance', - 'Function Join', - 'https://www.brentozar.com/blitzcache/tvf-join/', - 'Execution plans have been found that join to table valued functions (TVFs). TVFs produce inaccurate estimates of the number of rows returned and can lead to any number of query plan problems.'); - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs - WHERE compile_timeout = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 18, - 50, - 'Execution Plans', - 'Compilation Timeout', - 'https://www.brentozar.com/blitzcache/compilation-timeout/', - 'Query compilation timed out for one or more queries. SQL Server did not find a plan that meets acceptable performance criteria in the time allotted so the best guess was returned. There is a very good chance that this plan isn''t even below average - it''s probably terrible.'); - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs - WHERE compile_memory_limit_exceeded = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 19, - 50, - 'Execution Plans', - 'Compile Memory Limit Exceeded', - 'https://www.brentozar.com/blitzcache/compile-memory-limit-exceeded/', - 'The optimizer has a limited amount of memory available. One or more queries are complex enough that SQL Server was unable to allocate enough memory to fully optimize the query. A best fit plan was found, and it''s probably terrible.'); - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs - WHERE warning_no_join_predicate = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 20, - 50, - 'Execution Plans', - 'No Join Predicate', - 'https://www.brentozar.com/blitzcache/no-join-predicate/', - 'Operators in a query have no join predicate. This means that all rows from one table will be matched with all rows from anther table producing a Cartesian product. That''s a whole lot of rows. This may be your goal, but it''s important to investigate why this is happening.'); - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs - WHERE plan_multiple_plans > 0 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 21, - 200, - 'Execution Plans', - 'Multiple Plans', - 'https://www.brentozar.com/blitzcache/multiple-plans/', - 'Queries exist with multiple execution plans (as determined by query_plan_hash). Investigate possible ways to parameterize these queries or otherwise reduce the plan count.'); - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs - WHERE unmatched_index_count > 0 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 22, - 100, - 'Performance', - 'Unmatched Indexes', - 'https://www.brentozar.com/blitzcache/unmatched-indexes', - 'An index could have been used, but SQL Server chose not to use it - likely due to parameterization and filtered indexes.'); - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs - WHERE unparameterized_query = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 23, - 100, - 'Parameterization', - 'Unparameterized Query', - 'https://www.brentozar.com/blitzcache/unparameterized-queries', - 'Unparameterized queries found. These could be ad hoc queries, data exploration, or queries using "OPTIMIZE FOR UNKNOWN".'); - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs - WHERE is_trivial = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 24, - 100, - 'Execution Plans', - 'Trivial Plans', - 'https://www.brentozar.com/blitzcache/trivial-plans', - 'Trivial plans get almost no optimization. If you''re finding these in the top worst queries, something may be going wrong.'); - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.is_forced_serial= 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 25, - 10, - 'Execution Plans', - 'Forced Serialization', - 'https://www.brentozar.com/blitzcache/forced-serialization/', - 'Something in your plan is forcing a serial query. Further investigation is needed if this is not by design.') ; - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.is_key_lookup_expensive= 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 26, - 100, - 'Execution Plans', - 'Expensive Key Lookup', - 'https://www.brentozar.com/blitzcache/expensive-key-lookups/', - 'There''s a key lookup in your plan that costs >=50% of the total plan cost.') ; - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.is_remote_query_expensive= 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 28, - 100, - 'Execution Plans', - 'Expensive Remote Query', - 'https://www.brentozar.com/blitzcache/expensive-remote-query/', - 'There''s a remote query in your plan that costs >=50% of the total plan cost.') ; - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.trace_flags_session IS NOT NULL - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 29, - 200, - 'Trace Flags', - 'Session Level Trace Flags Enabled', - 'https://www.brentozar.com/blitz/trace-flags-enabled-globally/', - 'Someone is enabling session level Trace Flags in a query.') ; - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.is_unused_grant IS NOT NULL - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 30, - 100, - 'Memory Grant', - 'Unused Memory Grant', - 'https://www.brentozar.com/blitzcache/unused-memory-grants/', - 'Queries have large unused memory grants. This can cause concurrency issues, if queries are waiting a long time to get memory to run.') ; - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.function_count > 0 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 31, - 100, - 'Compute Scalar That References A Function', - 'Calls Functions', - 'https://www.brentozar.com/blitzcache/compute-scalar-functions/', - 'Both of these will force queries to run serially, run at least once per row, and may result in poor cardinality estimates.') ; - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.clr_function_count > 0 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 32, - 100, - 'Compute Scalar That References A CLR Function', - 'Calls CLR Functions', - 'https://www.brentozar.com/blitzcache/compute-scalar-functions/', - 'May force queries to run serially, run at least once per row, and may result in poor cardinality estimates.') ; - - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.is_table_variable = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 33, - 100, - 'Table Variables detected', - 'Table Variables', - 'https://www.brentozar.com/blitzcache/table-variables/', - 'All modifications are single threaded, and selects have really low row estimates.') ; - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.no_stats_warning = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 35, - 100, - 'Statistics', - 'Columns With No Statistics', - 'https://www.brentozar.com/blitzcache/columns-no-statistics/', - 'Sometimes this happens with indexed views, other times because auto create stats is turned off.') ; - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.relop_warnings = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 36, - 100, - 'Warnings', - 'Operator Warnings', - 'https://www.brentozar.com/blitzcache/query-plan-warnings/', - 'Check the plan for more details.') ; - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.is_table_scan = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 37, - 100, - 'Indexes', - 'Table Scans (Heaps)', - 'https://www.brentozar.com/archive/2012/05/video-heaps/', - 'This may not be a problem. Run sp_BlitzIndex for more information.') ; - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.backwards_scan = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 38, - 200, - 'Indexes', - 'Backwards Scans', - 'https://www.brentozar.com/blitzcache/backwards-scans/', - 'This isn''t always a problem. They can cause serial zones in plans, and may need an index to match sort order.') ; - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.forced_index = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 39, - 100, - 'Indexes', - 'Forced Indexes', - 'https://www.brentozar.com/blitzcache/optimizer-forcing/', - 'This can cause inefficient plans, and will prevent missing index requests.') ; - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.forced_seek = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 40, - 100, - 'Indexes', - 'Forced Seeks', - 'https://www.brentozar.com/blitzcache/optimizer-forcing/', - 'This can cause inefficient plans by taking seek vs scan choice away from the optimizer.') ; - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.forced_scan = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 40, - 100, - 'Indexes', - 'Forced Scans', - 'https://www.brentozar.com/blitzcache/optimizer-forcing/', - 'This can cause inefficient plans by taking seek vs scan choice away from the optimizer.') ; - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.columnstore_row_mode = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 41, - 100, - 'Indexes', - 'ColumnStore Row Mode', - 'https://www.brentozar.com/blitzcache/columnstore-indexes-operating-row-mode/', - 'ColumnStore indexes operating in Row Mode indicate really poor query choices.') ; - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.is_computed_scalar = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 42, - 50, - 'Functions', - 'Computed Column UDF', - 'https://www.brentozar.com/blitzcache/computed-columns-referencing-functions/', - 'This can cause a whole mess of bad serializartion problems.') ; - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.is_sort_expensive = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 43, - 100, - 'Execution Plans', - 'Expensive Sort', - 'https://www.brentozar.com/blitzcache/expensive-sorts/', - 'There''s a sort in your plan that costs >=50% of the total plan cost.') ; - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.is_computed_filter = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 44, - 50, - 'Functions', - 'Filter UDF', - 'https://www.brentozar.com/blitzcache/compute-scalar-functions/', - 'Someone put a Scalar UDF in the WHERE clause!') ; - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.index_ops >= 5 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 45, - 100, - 'Indexes', - '>= 5 Indexes Modified', - 'https://www.brentozar.com/blitzcache/many-indexes-modified/', - 'This can cause lots of hidden I/O -- Run sp_BlitzIndex for more information.') ; - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.is_row_level = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 46, - 200, - 'Complexity', - 'Row Level Security', - 'https://www.brentozar.com/blitzcache/row-level-security/', - 'You may see a lot of confusing junk in your query plan.') ; - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.is_spatial = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 47, - 200, - 'Complexity', - 'Spatial Index', - 'https://www.brentozar.com/blitzcache/spatial-indexes/', - 'Purely informational.') ; - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.index_dml = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 48, - 150, - 'Complexity', - 'Index DML', - 'https://www.brentozar.com/blitzcache/index-dml/', - 'This can cause recompiles and stuff.') ; - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.table_dml = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 49, - 150, - 'Complexity', - 'Table DML', - 'https://www.brentozar.com/blitzcache/table-dml/', - 'This can cause recompiles and stuff.') ; - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.long_running_low_cpu = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 50, - 150, - 'Blocking', - 'Long Running Low CPU', - 'https://www.brentozar.com/blitzcache/long-running-low-cpu/', - 'This can be a sign of blocking, linked servers, or poor client application code (ASYNC_NETWORK_IO).') ; - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.low_cost_high_cpu = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 51, - 150, - 'Complexity', - 'Low Cost Query With High CPU', - 'https://www.brentozar.com/blitzcache/low-cost-high-cpu/', - 'This can be a sign of functions or Dynamic SQL that calls black-box code.') ; - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.stale_stats = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 52, - 150, - 'Statistics', - 'Statistics used have > 100k modifications in the last 7 days', - 'https://www.brentozar.com/blitzcache/stale-statistics/', - 'Ever heard of updating statistics?') ; - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.is_adaptive = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 53, - 200, - 'Complexity', - 'Adaptive joins', - 'https://www.brentozar.com/blitzcache/adaptive-joins/', - 'This join will sometimes do seeks, and sometimes do scans.') ; - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.is_spool_expensive = 1 - ) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 54, - 150, - 'Indexes', - 'Expensive Index Spool', - 'https://www.brentozar.com/blitzcache/eager-index-spools/', - 'Check operator predicates and output for index definition guidance') ; - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.is_spool_more_rows = 1 - ) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 55, - 150, - 'Indexes', - 'Large Index Row Spool', - 'https://www.brentozar.com/blitzcache/eager-index-spools/', - 'Check operator predicates and output for index definition guidance') ; - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.is_bad_estimate = 1 - ) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 56, - 100, - 'Complexity', - 'Row Estimate Mismatch', - 'https://www.brentozar.com/blitzcache/bad-estimates/', - 'Estimated rows are different from average rows by a factor of 10000. This may indicate a performance problem if mismatches occur regularly') ; - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.is_paul_white_electric = 1 - ) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 57, - 200, - 'Is Paul White Electric?', - 'This query has a Switch operator in it!', - 'https://www.sql.kiwi/2013/06/hello-operator-my-switch-is-bored.html', - 'You should email this query plan to Paul: SQLkiwi at gmail dot com') ; - - IF @v >= 14 OR (@v = 13 AND @build >= 5026) - BEGIN - - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - SELECT - @@SPID, - 997, - 200, - 'Database Level Statistics', - 'The database ' + sa.[Database] + ' last had a stats update on ' + CONVERT(NVARCHAR(10), CONVERT(DATE, MAX(sa.LastUpdate))) + ' and has ' + CONVERT(NVARCHAR(10), AVG(sa.ModificationCount)) + ' modifications on average.' AS [Finding], - 'https://www.brentozar.com/blitzcache/stale-statistics/' AS URL, - 'Consider updating statistics more frequently,' AS [Details] - FROM #stats_agg AS sa - GROUP BY sa.[Database] - HAVING MAX(sa.LastUpdate) <= DATEADD(DAY, -7, SYSDATETIME()) - AND AVG(sa.ModificationCount) >= 100000; - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.is_row_goal = 1 - ) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 58, - 200, - 'Complexity', - 'Row Goals', - 'https://www.brentozar.com/go/rowgoals/', - 'This query had row goals introduced, which can be good or bad, and should be investigated for high read queries.') ; - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.is_big_spills = 1 - ) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 59, - 100, - 'TempDB', - '>500mb Spills', - 'https://www.brentozar.com/blitzcache/tempdb-spills/', - 'This query spills >500mb to tempdb on average. One way or another, this query didn''t get enough memory') ; - - - END; - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.is_mstvf = 1 - ) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 60, - 100, - 'Functions', - 'MSTVFs', - 'https://www.brentozar.com/blitzcache/tvf-join/', - 'Execution plans have been found that join to table valued functions (TVFs). TVFs produce inaccurate estimates of the number of rows returned and can lead to any number of query plan problems.'); - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.is_mm_join = 1 - ) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 61, - 100, - 'Complexity', - 'Many to Many Merge', - 'https://www.brentozar.com/archive/2018/04/many-mysteries-merge-joins/', - 'These use secret worktables that could be doing lots of reads. Occurs when join inputs aren''t known to be unique. Can be really bad when parallel.'); - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.is_nonsargable = 1 - ) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 62, - 50, - 'Non-SARGable queries', - 'non-SARGables', - 'https://www.brentozar.com/blitzcache/non-sargable-predicates/', - 'Looks for intrinsic functions and expressions as predicates, and leading wildcard LIKE searches.'); - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE CompileTime > 5000 - ) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 63, - 100, - 'Complexity', - 'Long Compile Time', - 'https://www.brentozar.com/blitzcache/high-compilers/', - 'Queries are taking >5 seconds to compile. This can be normal for large plans, but be careful if they compile frequently'); - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE CompileCPU > 5000 - ) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 64, - 50, - 'Complexity', - 'High Compile CPU', - 'https://www.brentozar.com/blitzcache/high-compilers/', - 'Queries taking >5 seconds of CPU to compile. If CPU is high and plans like this compile frequently, they may be related'); - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE CompileMemory > 1024 - AND ((CompileMemory) / (1 * CASE WHEN MaxCompileMemory = 0 THEN 1 ELSE MaxCompileMemory END) * 100.) >= 10. - ) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 65, - 50, - 'Complexity', - 'High Compile Memory', - 'https://www.brentozar.com/blitzcache/high-compilers/', - 'Queries taking 10% of Max Compile Memory. If you see high RESOURCE_SEMAPHORE_QUERY_COMPILE waits, these may be related'); - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.select_with_writes = 1 - ) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 66, - 50, - 'Complexity', - 'Selects w/ Writes', - 'https://dba.stackexchange.com/questions/191825/', - 'This is thrown when reads cause writes that are not already flagged as big spills (2016+) or index spools.'); - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.is_table_spool_expensive = 1 - ) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 67, - 150, - 'Expensive Table Spool', - 'You have a table spool, this is usually a sign that queries are doing unnecessary work', - 'https://sqlperformance.com/2019/09/sql-performance/nested-loops-joins-performance-spools', - 'Check for non-SARGable predicates, or a lot of work being done inside a nested loops join') ; - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.is_table_spool_more_rows = 1 - ) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 68, - 150, - 'Table Spools Many Rows', - 'You have a table spool that spools more rows than the query returns', - 'https://sqlperformance.com/2019/09/sql-performance/nested-loops-joins-performance-spools', - 'Check for non-SARGable predicates, or a lot of work being done inside a nested loops join'); - - IF EXISTS (SELECT 1/0 - FROM #plan_creation p - WHERE (p.percent_24 > 0) - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - SELECT SPID, - 999, - CASE WHEN ISNULL(p.percent_24, 0) > 75 THEN 1 ELSE 254 END AS Priority, - 'Plan Cache Information', - CASE WHEN ISNULL(p.percent_24, 0) > 75 THEN 'Plan Cache Instability' ELSE 'Plan Cache Stability' END AS Finding, - 'https://www.brentozar.com/archive/2018/07/tsql2sday-how-much-plan-cache-history-do-you-have/', - 'You have ' + CONVERT(NVARCHAR(10), ISNULL(p.total_plans, 0)) - + ' total plans in your cache, with ' - + CONVERT(NVARCHAR(10), ISNULL(p.percent_24, 0)) - + '% plans created in the past 24 hours, ' - + CONVERT(NVARCHAR(10), ISNULL(p.percent_4, 0)) - + '% created in the past 4 hours, and ' - + CONVERT(NVARCHAR(10), ISNULL(p.percent_1, 0)) - + '% created in the past 1 hour. ' - + 'When these percentages are high, it may be a sign of memory pressure or plan cache instability.' - FROM #plan_creation p ; - - IF EXISTS (SELECT 1/0 - FROM #plan_usage p - WHERE p.percent_duplicate > 5 - AND spid = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - SELECT spid, - 999, - CASE WHEN ISNULL(p.percent_duplicate, 0) > 75 THEN 1 ELSE 254 END AS Priority, - 'Plan Cache Information', - CASE WHEN ISNULL(p.percent_duplicate, 0) > 75 THEN 'Many Duplicate Plans' ELSE 'Duplicate Plans' END AS Finding, - 'https://www.brentozar.com/archive/2018/03/why-multiple-plans-for-one-query-are-bad/', - 'You have ' + CONVERT(NVARCHAR(10), p.total_plans) - + ' plans in your cache, and ' - + CONVERT(NVARCHAR(10), p.percent_duplicate) - + '% are duplicates with more than 5 entries' - + ', meaning similar queries are generating the same plan repeatedly.' - + ' Forced Parameterization may fix the issue. To find troublemakers, use: EXEC sp_BlitzCache @SortOrder = ''query hash''; ' - FROM #plan_usage AS p ; - - IF EXISTS (SELECT 1/0 - FROM #plan_usage p - WHERE p.percent_single > 5 - AND spid = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - SELECT spid, - 999, - CASE WHEN ISNULL(p.percent_single, 0) > 75 THEN 1 ELSE 254 END AS Priority, - 'Plan Cache Information', - CASE WHEN ISNULL(p.percent_single, 0) > 75 THEN 'Many Single-Use Plans' ELSE 'Single-Use Plans' END AS Finding, - 'https://www.brentozar.com/blitz/single-use-plans-procedure-cache/', - 'You have ' + CONVERT(NVARCHAR(10), p.total_plans) - + ' plans in your cache, and ' - + CONVERT(NVARCHAR(10), p.percent_single) - + '% are single use plans' - + ', meaning SQL Server thinks it''s seeing a lot of "new" queries and creating plans for them.' - + ' Forced Parameterization and/or Optimize For Ad Hoc Workloads may fix the issue.' - + 'To find troublemakers, use: EXEC sp_BlitzCache @SortOrder = ''query hash''; ' - FROM #plan_usage AS p ; - - IF @is_tokenstore_big = 1 - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - SELECT @@SPID, - 69, - 10, - N'Large USERSTORE_TOKENPERM cache: ' + CONVERT(NVARCHAR(11), @user_perm_gb_out) + N'GB', - N'The USERSTORE_TOKENPERM is taking up ' + CONVERT(NVARCHAR(11), @user_perm_percent) - + N'% of the buffer pool, and your plan cache seems to be unstable', - N'https://www.brentozar.com/go/userstore', - N'A growing USERSTORE_TOKENPERM cache can cause the plan cache to clear out' - - IF @v >= 11 - BEGIN - IF EXISTS (SELECT 1/0 - FROM #trace_flags AS tf - WHERE tf.global_trace_flags IS NOT NULL - ) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 1000, - 255, - 'Global Trace Flags Enabled', - 'You have Global Trace Flags enabled on your server', - 'https://www.brentozar.com/blitz/trace-flags-enabled-globally/', - 'You have the following Global Trace Flags enabled: ' + (SELECT TOP 1 tf.global_trace_flags FROM #trace_flags AS tf WHERE tf.global_trace_flags IS NOT NULL)) ; - END; - - IF NOT EXISTS (SELECT 1/0 - FROM ##BlitzCacheResults AS bcr - WHERE bcr.Priority = 2147483646 - ) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 2147483646, - 255, - 'Need more help?' , - 'Paste your plan on the internet!', - 'http://pastetheplan.com', - 'This makes it easy to share plans and post them to Q&A sites like https://dba.stackexchange.com/!') ; - - - - IF NOT EXISTS (SELECT 1/0 - FROM ##BlitzCacheResults AS bcr - WHERE bcr.Priority = 2147483647 - ) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 2147483647, - 255, - 'Thanks for using sp_BlitzCache!' , - 'From Your Community Volunteers', - 'http://FirstResponderKit.org', - 'We hope you found this tool useful. Current version: ' + @Version + ' released on ' + CONVERT(NVARCHAR(30), @VersionDate) + '.') ; - - END; - - - SELECT Priority, - FindingsGroup, - Finding, - URL, - Details, - CheckID - FROM ##BlitzCacheResults - WHERE SPID = @@SPID - GROUP BY Priority, - FindingsGroup, - Finding, - URL, - Details, - CheckID - ORDER BY Priority ASC, FindingsGroup, Finding, CheckID ASC - OPTION (RECOMPILE); -END; - -IF @Debug = 1 - BEGIN - - SELECT '##BlitzCacheResults' AS table_name, * - FROM ##BlitzCacheResults - OPTION ( RECOMPILE ); - - SELECT '##BlitzCacheProcs' AS table_name, * - FROM ##BlitzCacheProcs - OPTION ( RECOMPILE ); - - SELECT '#statements' AS table_name, * - FROM #statements AS s - OPTION (RECOMPILE); - - SELECT '#query_plan' AS table_name, * - FROM #query_plan AS qp - OPTION (RECOMPILE); - - SELECT '#relop' AS table_name, * - FROM #relop AS r - OPTION (RECOMPILE); - - SELECT '#only_query_hashes' AS table_name, * - FROM #only_query_hashes - OPTION ( RECOMPILE ); - - SELECT '#ignore_query_hashes' AS table_name, * - FROM #ignore_query_hashes - OPTION ( RECOMPILE ); - - SELECT '#only_sql_handles' AS table_name, * - FROM #only_sql_handles - OPTION ( RECOMPILE ); - - SELECT '#ignore_sql_handles' AS table_name, * - FROM #ignore_sql_handles - OPTION ( RECOMPILE ); - - SELECT '#p' AS table_name, * - FROM #p - OPTION ( RECOMPILE ); - - SELECT '#checkversion' AS table_name, * - FROM #checkversion - OPTION ( RECOMPILE ); - - SELECT '#configuration' AS table_name, * - FROM #configuration - OPTION ( RECOMPILE ); - - SELECT '#stored_proc_info' AS table_name, * - FROM #stored_proc_info - OPTION ( RECOMPILE ); - - SELECT '#conversion_info' AS table_name, * - FROM #conversion_info AS ci - OPTION ( RECOMPILE ); - - SELECT '#variable_info' AS table_name, * - FROM #variable_info AS vi - OPTION ( RECOMPILE ); - - SELECT '#missing_index_xml' AS table_name, * - FROM #missing_index_xml AS mix - OPTION ( RECOMPILE ); - - SELECT '#missing_index_schema' AS table_name, * - FROM #missing_index_schema AS mis - OPTION ( RECOMPILE ); - - SELECT '#missing_index_usage' AS table_name, * - FROM #missing_index_usage AS miu - OPTION ( RECOMPILE ); - - SELECT '#missing_index_detail' AS table_name, * - FROM #missing_index_detail AS mid - OPTION ( RECOMPILE ); - - SELECT '#missing_index_pretty' AS table_name, * - FROM #missing_index_pretty AS mip - OPTION ( RECOMPILE ); - - SELECT '#plan_creation' AS table_name, * - FROM #plan_creation - OPTION ( RECOMPILE ); - - SELECT '#plan_cost' AS table_name, * - FROM #plan_cost - OPTION ( RECOMPILE ); - - SELECT '#proc_costs' AS table_name, * - FROM #proc_costs - OPTION ( RECOMPILE ); - - SELECT '#stats_agg' AS table_name, * - FROM #stats_agg - OPTION ( RECOMPILE ); - - SELECT '#trace_flags' AS table_name, * - FROM #trace_flags - OPTION ( RECOMPILE ); - - SELECT '#plan_usage' AS table_name, * - FROM #plan_usage - OPTION ( RECOMPILE ); - - END; - - IF @OutputTableName IS NOT NULL - --Allow for output to ##DB so don't check for DB or schema name here - GOTO OutputResultsToTable; -RETURN; --Avoid going into the AllSort GOTO - -/*Begin code to sort by all*/ -AllSorts: -RAISERROR('Beginning all sort loop', 0, 1) WITH NOWAIT; - - -IF ( - @Top > 10 - AND @SkipAnalysis = 0 - AND @BringThePain = 0 - ) - BEGIN - RAISERROR( - ' - You''ve chosen a value greater than 10 to sort the whole plan cache by. - That can take a long time and harm performance. - Please choose a number <= 10, or set @BringThePain = 1 to signify you understand this might be a bad idea. - ', 0, 1) WITH NOWAIT; - RETURN; - END; - - -IF OBJECT_ID('tempdb..#checkversion_allsort') IS NULL - BEGIN - CREATE TABLE #checkversion_allsort - ( - version NVARCHAR(128), - common_version AS SUBSTRING(version, 1, CHARINDEX('.', version) + 1), - major AS PARSENAME(CONVERT(VARCHAR(32), version), 4), - minor AS PARSENAME(CONVERT(VARCHAR(32), version), 3), - build AS PARSENAME(CONVERT(VARCHAR(32), version), 2), - revision AS PARSENAME(CONVERT(VARCHAR(32), version), 1) - ); - - INSERT INTO #checkversion_allsort - (version) - SELECT CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)) - OPTION ( RECOMPILE ); - END; - - -SELECT @v = common_version, - @build = build -FROM #checkversion_allsort -OPTION ( RECOMPILE ); - -IF OBJECT_ID('tempdb.. #bou_allsort') IS NULL - BEGIN - CREATE TABLE #bou_allsort - ( - Id INT IDENTITY(1, 1), - DatabaseName NVARCHAR(128), - Cost FLOAT, - QueryText NVARCHAR(MAX), - QueryType NVARCHAR(258), - Warnings VARCHAR(MAX), - QueryPlan XML, - missing_indexes XML, - implicit_conversion_info XML, - cached_execution_parameters XML, - ExecutionCount NVARCHAR(30), - ExecutionsPerMinute MONEY, - ExecutionWeight MONEY, - TotalCPU NVARCHAR(30), - AverageCPU NVARCHAR(30), - CPUWeight MONEY, - TotalDuration NVARCHAR(30), - AverageDuration NVARCHAR(30), - DurationWeight MONEY, - TotalReads NVARCHAR(30), - AverageReads NVARCHAR(30), - ReadWeight MONEY, - TotalWrites NVARCHAR(30), - AverageWrites NVARCHAR(30), - WriteWeight MONEY, - AverageReturnedRows MONEY, - MinGrantKB NVARCHAR(30), - MaxGrantKB NVARCHAR(30), - MinUsedGrantKB NVARCHAR(30), - MaxUsedGrantKB NVARCHAR(30), - AvgMaxMemoryGrant MONEY, - MinSpills NVARCHAR(30), - MaxSpills NVARCHAR(30), - TotalSpills NVARCHAR(30), - AvgSpills MONEY, - PlanCreationTime DATETIME, - LastExecutionTime DATETIME, - LastCompletionTime DATETIME, - PlanHandle VARBINARY(64), - SqlHandle VARBINARY(64), - SetOptions VARCHAR(MAX), - QueryHash BINARY(8), - PlanGenerationNum NVARCHAR(30), - RemovePlanHandleFromCache NVARCHAR(200), - Pattern NVARCHAR(20) - ); - END; - - -IF @SortOrder = 'all' -BEGIN -RAISERROR('Beginning for ALL', 0, 1) WITH NOWAIT; -SET @AllSortSql += N' - DECLARE @ISH NVARCHAR(MAX) = N'''' - - INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, - TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, - ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) - - EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''cpu'', - @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; - - UPDATE #bou_allsort SET Pattern = ''cpu'' WHERE Pattern IS NULL OPTION(RECOMPILE); - - SELECT TOP 1 @ISH = STUFF((SELECT DISTINCT N'','' + CONVERT(NVARCHAR(MAX),b2.SqlHandle, 1) FROM #bou_allsort AS b2 FOR XML PATH(N''''), TYPE).value(N''.[1]'', N''NVARCHAR(MAX)''), 1, 1, N'''') OPTION(RECOMPILE); - - INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, - TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, - ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) - - EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''reads'', @IgnoreSqlHandles = @ISH, - @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; - - UPDATE #bou_allsort SET Pattern = ''reads'' WHERE Pattern IS NULL OPTION(RECOMPILE); - - SELECT TOP 1 @ISH = STUFF((SELECT DISTINCT N'','' + CONVERT(NVARCHAR(MAX),b2.SqlHandle, 1) FROM #bou_allsort AS b2 FOR XML PATH(N''''), TYPE).value(N''.[1]'', N''NVARCHAR(MAX)''), 1, 1, N'''') OPTION(RECOMPILE); - - INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, - TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, - ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) - - EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''writes'', @IgnoreSqlHandles = @ISH, - @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; - - UPDATE #bou_allsort SET Pattern = ''writes'' WHERE Pattern IS NULL OPTION(RECOMPILE); - - SELECT TOP 1 @ISH = STUFF((SELECT DISTINCT N'','' + CONVERT(NVARCHAR(MAX),b2.SqlHandle, 1) FROM #bou_allsort AS b2 FOR XML PATH(N''''), TYPE).value(N''.[1]'', N''NVARCHAR(MAX)''), 1, 1, N'''') OPTION(RECOMPILE); - - INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, - TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, - ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) - - EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''duration'', @IgnoreSqlHandles = @ISH, - @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; - - UPDATE #bou_allsort SET Pattern = ''duration'' WHERE Pattern IS NULL OPTION(RECOMPILE); - - SELECT TOP 1 @ISH = STUFF((SELECT DISTINCT N'','' + CONVERT(NVARCHAR(MAX),b2.SqlHandle, 1) FROM #bou_allsort AS b2 FOR XML PATH(N''''), TYPE).value(N''.[1]'', N''NVARCHAR(MAX)''), 1, 1, N'''') OPTION(RECOMPILE); - - INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, - TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, - ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) - - EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''executions'', @IgnoreSqlHandles = @ISH, - @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; - - UPDATE #bou_allsort SET Pattern = ''executions'' WHERE Pattern IS NULL OPTION(RECOMPILE); - - '; - - IF @VersionShowsMemoryGrants = 0 - BEGIN - IF @ExportToExcel = 1 - BEGIN - SET @AllSortSql += N' UPDATE #bou_allsort - SET - QueryPlan = NULL, - implicit_conversion_info = NULL, - cached_execution_parameters = NULL, - missing_indexes = NULL - OPTION (RECOMPILE); - - UPDATE ##BlitzCacheProcs - SET QueryText = SUBSTRING(REPLACE(REPLACE(REPLACE(LTRIM(RTRIM(QueryText)),'' '',''<>''),''><'',''''),''<>'','' ''), 1, 32000) - OPTION(RECOMPILE);'; - END; - - END; - - IF @VersionShowsMemoryGrants = 1 - BEGIN - SET @AllSortSql += N' SELECT TOP 1 @ISH = STUFF((SELECT DISTINCT N'','' + CONVERT(NVARCHAR(MAX),b2.SqlHandle, 1) FROM #bou_allsort AS b2 FOR XML PATH(N''''), TYPE).value(N''.[1]'', N''NVARCHAR(MAX)''), 1, 1, N'''') OPTION(RECOMPILE); - - INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, - TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, - ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) - - EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''memory grant'', @IgnoreSqlHandles = @ISH, - @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; - - UPDATE #bou_allsort SET Pattern = ''memory grant'' WHERE Pattern IS NULL OPTION(RECOMPILE);'; - IF @ExportToExcel = 1 - BEGIN - SET @AllSortSql += N' UPDATE #bou_allsort - SET - QueryPlan = NULL, - implicit_conversion_info = NULL, - cached_execution_parameters = NULL, - missing_indexes = NULL - OPTION (RECOMPILE); - - UPDATE ##BlitzCacheProcs - SET QueryText = SUBSTRING(REPLACE(REPLACE(REPLACE(LTRIM(RTRIM(QueryText)),'' '',''<>''),''><'',''''),''<>'','' ''), 1, 32000) - OPTION(RECOMPILE);'; - END; - - END; - - IF @VersionShowsSpills = 0 - BEGIN - IF @ExportToExcel = 1 - BEGIN - SET @AllSortSql += N' UPDATE #bou_allsort - SET - QueryPlan = NULL, - implicit_conversion_info = NULL, - cached_execution_parameters = NULL, - missing_indexes = NULL - OPTION (RECOMPILE); - - UPDATE ##BlitzCacheProcs - SET QueryText = SUBSTRING(REPLACE(REPLACE(REPLACE(LTRIM(RTRIM(QueryText)),'' '',''<>''),''><'',''''),''<>'','' ''), 1, 32000) - OPTION(RECOMPILE);'; - END; - - END; - - IF @VersionShowsSpills = 1 - BEGIN - SET @AllSortSql += N' SELECT TOP 1 @ISH = STUFF((SELECT DISTINCT N'','' + CONVERT(NVARCHAR(MAX),b2.SqlHandle, 1) FROM #bou_allsort AS b2 FOR XML PATH(N''''), TYPE).value(N''.[1]'', N''NVARCHAR(MAX)''), 1, 1, N'''') OPTION(RECOMPILE); - - INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, - TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, - ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) - - EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''spills'', @IgnoreSqlHandles = @ISH, - @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; - - UPDATE #bou_allsort SET Pattern = ''spills'' WHERE Pattern IS NULL OPTION(RECOMPILE);'; - IF @ExportToExcel = 1 - BEGIN - SET @AllSortSql += N' UPDATE #bou_allsort - SET - QueryPlan = NULL, - implicit_conversion_info = NULL, - cached_execution_parameters = NULL, - missing_indexes = NULL - OPTION (RECOMPILE); - - UPDATE ##BlitzCacheProcs - SET QueryText = SUBSTRING(REPLACE(REPLACE(REPLACE(LTRIM(RTRIM(QueryText)),'' '',''<>''),''><'',''''),''<>'','' ''), 1, 32000) - OPTION(RECOMPILE);'; - END; - - END; - - IF(@OutputType <> 'NONE') - BEGIN - SET @AllSortSql += N' SELECT DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters,ExecutionCount, ExecutionsPerMinute, ExecutionWeight, - TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, - ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache, Pattern - FROM #bou_allsort - ORDER BY Id - OPTION(RECOMPILE); '; - END; -END; - - -IF @SortOrder = 'all avg' -BEGIN -RAISERROR('Beginning for ALL AVG', 0, 1) WITH NOWAIT; -SET @AllSortSql += N' - DECLARE @ISH NVARCHAR(MAX) = N'''' - - INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, - TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, - ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) - - EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''avg cpu'', - @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; - - UPDATE #bou_allsort SET Pattern = ''avg cpu'' WHERE Pattern IS NULL OPTION(RECOMPILE); - - SELECT TOP 1 @ISH = STUFF((SELECT DISTINCT N'','' + CONVERT(NVARCHAR(MAX),b2.SqlHandle, 1) FROM #bou_allsort AS b2 FOR XML PATH(N''''), TYPE).value(N''.[1]'', N''NVARCHAR(MAX)''), 1, 1, N'''') OPTION(RECOMPILE); - - INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, - TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, - ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) - - EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''avg reads'', @IgnoreSqlHandles = @ISH, - @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; - - UPDATE #bou_allsort SET Pattern = ''avg reads'' WHERE Pattern IS NULL OPTION(RECOMPILE); - - SELECT TOP 1 @ISH = STUFF((SELECT DISTINCT N'','' + CONVERT(NVARCHAR(MAX),b2.SqlHandle, 1) FROM #bou_allsort AS b2 FOR XML PATH(N''''), TYPE).value(N''.[1]'', N''NVARCHAR(MAX)''), 1, 1, N'''') OPTION(RECOMPILE); - - INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, - TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, - ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) - - EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''avg writes'', @IgnoreSqlHandles = @ISH, - @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; - - UPDATE #bou_allsort SET Pattern = ''avg writes'' WHERE Pattern IS NULL OPTION(RECOMPILE); - - SELECT TOP 1 @ISH = STUFF((SELECT DISTINCT N'','' + CONVERT(NVARCHAR(MAX),b2.SqlHandle, 1) FROM #bou_allsort AS b2 FOR XML PATH(N''''), TYPE).value(N''.[1]'', N''NVARCHAR(MAX)''), 1, 1, N'''') OPTION(RECOMPILE); - - INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, - TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, - ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) - - EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''avg duration'', @IgnoreSqlHandles = @ISH, - @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; - - UPDATE #bou_allsort SET Pattern = ''avg duration'' WHERE Pattern IS NULL OPTION(RECOMPILE); - - SELECT TOP 1 @ISH = STUFF((SELECT DISTINCT N'','' + CONVERT(NVARCHAR(MAX),b2.SqlHandle, 1) FROM #bou_allsort AS b2 FOR XML PATH(N''''), TYPE).value(N''.[1]'', N''NVARCHAR(MAX)''), 1, 1, N'''') OPTION(RECOMPILE); - - INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, - TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, - ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) - - EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''avg executions'', @IgnoreSqlHandles = @ISH, - @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; - - UPDATE #bou_allsort SET Pattern = ''avg executions'' WHERE Pattern IS NULL OPTION(RECOMPILE); - - '; - - IF @VersionShowsMemoryGrants = 0 - BEGIN - IF @ExportToExcel = 1 - BEGIN - SET @AllSortSql += N' UPDATE #bou_allsort - SET - QueryPlan = NULL, - implicit_conversion_info = NULL, - cached_execution_parameters = NULL, - missing_indexes = NULL - OPTION (RECOMPILE); - - UPDATE ##BlitzCacheProcs - SET QueryText = SUBSTRING(REPLACE(REPLACE(REPLACE(LTRIM(RTRIM(QueryText)),'' '',''<>''),''><'',''''),''<>'','' ''), 1, 32000) - OPTION(RECOMPILE);'; - END; - - END; - - IF @VersionShowsMemoryGrants = 1 - BEGIN - SET @AllSortSql += N' SELECT TOP 1 @ISH = STUFF((SELECT DISTINCT N'','' + CONVERT(NVARCHAR(MAX),b2.SqlHandle, 1) FROM #bou_allsort AS b2 FOR XML PATH(N''''), TYPE).value(N''.[1]'', N''NVARCHAR(MAX)''), 1, 1, N'''') OPTION(RECOMPILE); - - INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, - TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, - ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) - - EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''avg memory grant'', @IgnoreSqlHandles = @ISH, - @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; - - UPDATE #bou_allsort SET Pattern = ''avg memory grant'' WHERE Pattern IS NULL OPTION(RECOMPILE);'; - IF @ExportToExcel = 1 - BEGIN - SET @AllSortSql += N' UPDATE #bou_allsort - SET - QueryPlan = NULL, - implicit_conversion_info = NULL, - cached_execution_parameters = NULL, - missing_indexes = NULL - OPTION (RECOMPILE); - - UPDATE ##BlitzCacheProcs - SET QueryText = SUBSTRING(REPLACE(REPLACE(REPLACE(LTRIM(RTRIM(QueryText)),'' '',''<>''),''><'',''''),''<>'','' ''), 1, 32000) - OPTION(RECOMPILE);'; - END; - - END; - - IF @VersionShowsSpills = 0 - BEGIN - IF @ExportToExcel = 1 - BEGIN - SET @AllSortSql += N' UPDATE #bou_allsort - SET - QueryPlan = NULL, - implicit_conversion_info = NULL, - cached_execution_parameters = NULL, - missing_indexes = NULL - OPTION (RECOMPILE); - - UPDATE ##BlitzCacheProcs - SET QueryText = SUBSTRING(REPLACE(REPLACE(REPLACE(LTRIM(RTRIM(QueryText)),'' '',''<>''),''><'',''''),''<>'','' ''), 1, 32000) - OPTION(RECOMPILE);'; - END; - - END; - - IF @VersionShowsSpills = 1 - BEGIN - SET @AllSortSql += N' SELECT TOP 1 @ISH = STUFF((SELECT DISTINCT N'','' + CONVERT(NVARCHAR(MAX),b2.SqlHandle, 1) FROM #bou_allsort AS b2 FOR XML PATH(N''''), TYPE).value(N''.[1]'', N''NVARCHAR(MAX)''), 1, 1, N'''') OPTION(RECOMPILE); - - INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, - TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, - ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) - - EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''avg spills'', @IgnoreSqlHandles = @ISH, - @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; - - UPDATE #bou_allsort SET Pattern = ''avg spills'' WHERE Pattern IS NULL OPTION(RECOMPILE);'; - IF @ExportToExcel = 1 - BEGIN - SET @AllSortSql += N' UPDATE #bou_allsort - SET - QueryPlan = NULL, - implicit_conversion_info = NULL, - cached_execution_parameters = NULL, - missing_indexes = NULL - OPTION (RECOMPILE); - - UPDATE ##BlitzCacheProcs - SET QueryText = SUBSTRING(REPLACE(REPLACE(REPLACE(LTRIM(RTRIM(QueryText)),'' '',''<>''),''><'',''''),''<>'','' ''), 1, 32000) - OPTION(RECOMPILE);'; - END; - - END; - - IF(@OutputType <> 'NONE') - BEGIN - SET @AllSortSql += N' SELECT DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters,ExecutionCount, ExecutionsPerMinute, ExecutionWeight, - TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, - ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache, Pattern - FROM #bou_allsort - ORDER BY Id - OPTION(RECOMPILE); '; - END; -END; - - IF @Debug = 1 - BEGIN - PRINT SUBSTRING(@AllSortSql, 0, 4000); - PRINT SUBSTRING(@AllSortSql, 4000, 8000); - PRINT SUBSTRING(@AllSortSql, 8000, 12000); - PRINT SUBSTRING(@AllSortSql, 12000, 16000); - PRINT SUBSTRING(@AllSortSql, 16000, 20000); - PRINT SUBSTRING(@AllSortSql, 20000, 24000); - PRINT SUBSTRING(@AllSortSql, 24000, 28000); - PRINT SUBSTRING(@AllSortSql, 28000, 32000); - PRINT SUBSTRING(@AllSortSql, 32000, 36000); - PRINT SUBSTRING(@AllSortSql, 36000, 40000); - END; - - EXEC sys.sp_executesql @stmt = @AllSortSql, @params = N'@i_DatabaseName NVARCHAR(128), @i_Top INT, @i_SkipAnalysis BIT, @i_OutputDatabaseName NVARCHAR(258), @i_OutputSchemaName NVARCHAR(258), @i_OutputTableName NVARCHAR(258), @i_CheckDateOverride DATETIMEOFFSET, @i_MinutesBack INT ', - @i_DatabaseName = @DatabaseName, @i_Top = @Top, @i_SkipAnalysis = @SkipAnalysis, @i_OutputDatabaseName = @OutputDatabaseName, @i_OutputSchemaName = @OutputSchemaName, @i_OutputTableName = @OutputTableName, @i_CheckDateOverride = @CheckDateOverride, @i_MinutesBack = @MinutesBack; - -/* Avoid going into OutputResultsToTable - ... otherwise the last result (e.g. spills) would be recorded twice into the output table. -*/ -RETURN; - -/*End of AllSort section*/ - - -/*Begin code to write results to table */ -OutputResultsToTable: - -RAISERROR('Writing results to table.', 0, 1) WITH NOWAIT; - -SELECT @OutputServerName = QUOTENAME(@OutputServerName), - @OutputDatabaseName = QUOTENAME(@OutputDatabaseName), - @OutputSchemaName = QUOTENAME(@OutputSchemaName), - @OutputTableName = QUOTENAME(@OutputTableName); - -/* Checks if @OutputServerName is populated with a valid linked server, and that the database name specified is valid */ -DECLARE @ValidOutputServer BIT; -DECLARE @ValidOutputLocation BIT; -DECLARE @LinkedServerDBCheck NVARCHAR(2000); -DECLARE @ValidLinkedServerDB INT; -DECLARE @tmpdbchk table (cnt int); -IF @OutputServerName IS NOT NULL - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Outputting to a remote server.', 0, 1) WITH NOWAIT; - - IF EXISTS (SELECT server_id FROM sys.servers WHERE QUOTENAME([name]) = @OutputServerName) - BEGIN - SET @LinkedServerDBCheck = 'SELECT 1 WHERE EXISTS (SELECT * FROM '+@OutputServerName+'.master.sys.databases WHERE QUOTENAME([name]) = '''+@OutputDatabaseName+''')'; - INSERT INTO @tmpdbchk EXEC sys.sp_executesql @LinkedServerDBCheck; - SET @ValidLinkedServerDB = (SELECT COUNT(*) FROM @tmpdbchk); - IF (@ValidLinkedServerDB > 0) - BEGIN - SET @ValidOutputServer = 1; - SET @ValidOutputLocation = 1; - END; - ELSE - RAISERROR('The specified database was not found on the output server', 16, 0); - END; - ELSE - BEGIN - RAISERROR('The specified output server was not found', 16, 0); - END; - END; -ELSE - BEGIN - IF @OutputDatabaseName IS NOT NULL - AND @OutputSchemaName IS NOT NULL - AND @OutputTableName IS NOT NULL - AND EXISTS ( SELECT * - FROM sys.databases - WHERE QUOTENAME([name]) = @OutputDatabaseName) - BEGIN - SET @ValidOutputLocation = 1; - END; - ELSE IF @OutputDatabaseName IS NOT NULL - AND @OutputSchemaName IS NOT NULL - AND @OutputTableName IS NOT NULL - AND NOT EXISTS ( SELECT * - FROM sys.databases - WHERE QUOTENAME([name]) = @OutputDatabaseName) - BEGIN - RAISERROR('The specified output database was not found on this server', 16, 0); - END; - ELSE - BEGIN - SET @ValidOutputLocation = 0; - END; - END; - - /* @OutputTableName lets us export the results to a permanent table */ - DECLARE @StringToExecute NVARCHAR(MAX) = N'' ; - - IF @ValidOutputLocation = 1 - BEGIN - SET @StringToExecute = N'USE ' - + @OutputDatabaseName - + N'; IF EXISTS(SELECT * FROM ' - + @OutputDatabaseName - + N'.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' - + @OutputSchemaName - + N''') AND NOT EXISTS (SELECT * FROM ' - + @OutputDatabaseName - + N'.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''' - + @OutputSchemaName + N''' AND QUOTENAME(TABLE_NAME) = ''' - + @OutputTableName + N''') CREATE TABLE ' - + @OutputSchemaName + N'.' - + @OutputTableName - + CONVERT - ( - nvarchar(MAX), - N'(ID bigint NOT NULL IDENTITY(1,1), - ServerName NVARCHAR(258), - CheckDate DATETIMEOFFSET, - Version NVARCHAR(258), - QueryType NVARCHAR(258), - Warnings varchar(max), - DatabaseName sysname, - SerialDesiredMemory float, - SerialRequiredMemory float, - AverageCPU bigint, - TotalCPU bigint, - PercentCPUByType money, - CPUWeight money, - AverageDuration bigint, - TotalDuration bigint, - DurationWeight money, - PercentDurationByType money, - AverageReads bigint, - TotalReads bigint, - ReadWeight money, - PercentReadsByType money, - AverageWrites bigint, - TotalWrites bigint, - WriteWeight money, - PercentWritesByType money, - ExecutionCount bigint, - ExecutionWeight money, - PercentExecutionsByType money, - ExecutionsPerMinute money, - PlanCreationTime datetime,' + N' - PlanCreationTimeHours AS DATEDIFF(HOUR,CONVERT(DATETIMEOFFSET(7),[PlanCreationTime]),[CheckDate]), - LastExecutionTime datetime, - LastCompletionTime datetime, - PlanHandle varbinary(64), - [Remove Plan Handle From Cache] AS - CASE WHEN [PlanHandle] IS NOT NULL - THEN ''DBCC FREEPROCCACHE ('' + CONVERT(VARCHAR(128), [PlanHandle], 1) + '');'' - ELSE ''N/A'' END, - SqlHandle varbinary(64), - [Remove SQL Handle From Cache] AS - CASE WHEN [SqlHandle] IS NOT NULL - THEN ''DBCC FREEPROCCACHE ('' + CONVERT(VARCHAR(128), [SqlHandle], 1) + '');'' - ELSE ''N/A'' END, - [SQL Handle More Info] AS - CASE WHEN [SqlHandle] IS NOT NULL - THEN ''EXEC sp_BlitzCache @OnlySqlHandles = '''''' + CONVERT(VARCHAR(128), [SqlHandle], 1) + ''''''; '' - ELSE ''N/A'' END, - QueryHash binary(8), - [Query Hash More Info] AS - CASE WHEN [QueryHash] IS NOT NULL - THEN ''EXEC sp_BlitzCache @OnlyQueryHashes = '''''' + CONVERT(VARCHAR(32), [QueryHash], 1) + ''''''; '' - ELSE ''N/A'' END, - QueryPlanHash binary(8), - StatementStartOffset int, - StatementEndOffset int, - PlanGenerationNum bigint, - MinReturnedRows bigint, - MaxReturnedRows bigint, - AverageReturnedRows money, - TotalReturnedRows bigint, - QueryText nvarchar(max), - QueryPlan xml, - NumberOfPlans int, - NumberOfDistinctPlans int, - MinGrantKB BIGINT, - MaxGrantKB BIGINT, - MinUsedGrantKB BIGINT, - MaxUsedGrantKB BIGINT, - PercentMemoryGrantUsed MONEY, - AvgMaxMemoryGrant MONEY, - MinSpills BIGINT, - MaxSpills BIGINT, - TotalSpills BIGINT, - AvgSpills MONEY, - QueryPlanCost FLOAT, - Pattern NVARCHAR(20), - JoinKey AS ServerName + Cast(CheckDate AS NVARCHAR(50)), - CONSTRAINT [PK_' + REPLACE(REPLACE(@OutputTableName,N'[',N''),N']',N'') + N'] PRIMARY KEY CLUSTERED(ID ASC));' - ); - - SET @StringToExecute += N'IF EXISTS(SELECT * FROM ' - +@OutputDatabaseName - +N'.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' - +@OutputSchemaName - +N''') AND EXISTS (SELECT * FROM ' - +@OutputDatabaseName+ - N'.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''' - +@OutputSchemaName - +N''' AND QUOTENAME(TABLE_NAME) = ''' - +@OutputTableName - +N''') AND EXISTS (SELECT * FROM ' - +@OutputDatabaseName+ - N'.sys.computed_columns WHERE [name] = N''PlanCreationTimeHours'' AND QUOTENAME(OBJECT_NAME(object_id)) = N''' - +@OutputTableName - +N''' AND [definition] = N''(datediff(hour,[PlanCreationTime],sysdatetime()))'') -BEGIN - RAISERROR(''We noticed that you are running an old computed column definition for PlanCreationTimeHours, fixing that now'',0,0) WITH NOWAIT; - ALTER TABLE '+@OutputDatabaseName+N'.'+@OutputSchemaName+N'.'+@OutputTableName+N' DROP COLUMN [PlanCreationTimeHours]; - ALTER TABLE '+@OutputDatabaseName+N'.'+@OutputSchemaName+N'.'+@OutputTableName+N' ADD [PlanCreationTimeHours] AS DATEDIFF(HOUR,CONVERT(DATETIMEOFFSET(7),[PlanCreationTime]),[CheckDate]); -END '; - - IF @ValidOutputServer = 1 - BEGIN - SET @StringToExecute = REPLACE(@StringToExecute,''''+@OutputSchemaName+'''',''''''+@OutputSchemaName+''''''); - SET @StringToExecute = REPLACE(@StringToExecute,''''+@OutputTableName+'''',''''''+@OutputTableName+''''''); - SET @StringToExecute = REPLACE(@StringToExecute,'xml','nvarchar(max)'); - SET @StringToExecute = REPLACE(@StringToExecute,'''DBCC FREEPROCCACHE ('' + CONVERT(VARCHAR(128), [PlanHandle], 1) + '');''','''''DBCC FREEPROCCACHE ('''' + CONVERT(VARCHAR(128), [PlanHandle], 1) + '''');'''''); - SET @StringToExecute = REPLACE(@StringToExecute,'''DBCC FREEPROCCACHE ('' + CONVERT(VARCHAR(128), [SqlHandle], 1) + '');''','''''DBCC FREEPROCCACHE ('''' + CONVERT(VARCHAR(128), [SqlHandle], 1) + '''');'''''); - SET @StringToExecute = REPLACE(@StringToExecute,'''EXEC sp_BlitzCache @OnlySqlHandles = '''''' + CONVERT(VARCHAR(128), [SqlHandle], 1) + ''''''; ''','''''EXEC sp_BlitzCache @OnlySqlHandles = '''''''' + CONVERT(VARCHAR(128), [SqlHandle], 1) + ''''''''; '''''); - SET @StringToExecute = REPLACE(@StringToExecute,'''EXEC sp_BlitzCache @OnlyQueryHashes = '''''' + CONVERT(VARCHAR(32), [QueryHash], 1) + ''''''; ''','''''EXEC sp_BlitzCache @OnlyQueryHashes = '''''''' + CONVERT(VARCHAR(32), [QueryHash], 1) + ''''''''; '''''); - SET @StringToExecute = REPLACE(@StringToExecute,'''N/A''','''''N/A'''''); - - IF @Debug = 1 - BEGIN - PRINT SUBSTRING(@StringToExecute, 0, 4000); - PRINT SUBSTRING(@StringToExecute, 4000, 8000); - PRINT SUBSTRING(@StringToExecute, 8000, 12000); - PRINT SUBSTRING(@StringToExecute, 12000, 16000); - PRINT SUBSTRING(@StringToExecute, 16000, 20000); - PRINT SUBSTRING(@StringToExecute, 20000, 24000); - PRINT SUBSTRING(@StringToExecute, 24000, 28000); - PRINT SUBSTRING(@StringToExecute, 28000, 32000); - PRINT SUBSTRING(@StringToExecute, 32000, 36000); - PRINT SUBSTRING(@StringToExecute, 36000, 40000); - END; - EXEC('EXEC('''+@StringToExecute+''') AT ' + @OutputServerName); - END; - ELSE - BEGIN - IF @Debug = 1 - BEGIN - PRINT SUBSTRING(@StringToExecute, 0, 4000); - PRINT SUBSTRING(@StringToExecute, 4000, 8000); - PRINT SUBSTRING(@StringToExecute, 8000, 12000); - PRINT SUBSTRING(@StringToExecute, 12000, 16000); - PRINT SUBSTRING(@StringToExecute, 16000, 20000); - PRINT SUBSTRING(@StringToExecute, 20000, 24000); - PRINT SUBSTRING(@StringToExecute, 24000, 28000); - PRINT SUBSTRING(@StringToExecute, 28000, 32000); - PRINT SUBSTRING(@StringToExecute, 32000, 36000); - PRINT SUBSTRING(@StringToExecute, 36000, 40000); - END; - EXEC(@StringToExecute); - END; - - /* If the table doesn't have the new LastCompletionTime column, add it. See Github #2377. */ - SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableName; - SET @StringToExecute = N'IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns - WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + ''')) AND name = ''LastCompletionTime'') - ALTER TABLE ' + @ObjectFullName + N' ADD LastCompletionTime DATETIME NULL;'; - IF @ValidOutputServer = 1 - BEGIN - SET @StringToExecute = REPLACE(@StringToExecute,'''LastCompletionTime''','''''LastCompletionTime'''''); - SET @StringToExecute = REPLACE(@StringToExecute,'''' + @ObjectFullName + '''','''''' + @ObjectFullName + ''''''); - EXEC('EXEC('''+@StringToExecute+''') AT ' + @OutputServerName); - END; - ELSE - BEGIN - EXEC(@StringToExecute); - END; - - /* If the table doesn't have the new PlanGenerationNum column, add it. See Github #2514. */ - SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableName; - SET @StringToExecute = N'IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns - WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''PlanGenerationNum'') - ALTER TABLE ' + @ObjectFullName + N' ADD PlanGenerationNum BIGINT NULL;'; - IF @ValidOutputServer = 1 - BEGIN - SET @StringToExecute = REPLACE(@StringToExecute,'''PlanGenerationNum''','''''PlanGenerationNum'''''); - SET @StringToExecute = REPLACE(@StringToExecute,'''' + @ObjectFullName + '''','''''' + @ObjectFullName + ''''''); - EXEC('EXEC('''+@StringToExecute+''') AT ' + @OutputServerName); - END; - ELSE - BEGIN - EXEC(@StringToExecute); - END; - - /* If the table doesn't have the new Pattern column, add it */ - SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableName; - SET @StringToExecute = N'IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns - WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''Pattern'') - ALTER TABLE ' + @ObjectFullName + N' ADD Pattern NVARCHAR(20) NULL;'; - IF @ValidOutputServer = 1 - BEGIN - SET @StringToExecute = REPLACE(@StringToExecute,'''Pattern''','''''Pattern'''''); - SET @StringToExecute = REPLACE(@StringToExecute,'''' + @ObjectFullName + '''','''''' + @ObjectFullName + ''''''); - EXEC('EXEC('''+@StringToExecute+''') AT ' + @OutputServerName); - END; - ELSE - BEGIN - EXEC(@StringToExecute); - END - - IF @CheckDateOverride IS NULL - BEGIN - SET @CheckDateOverride = SYSDATETIMEOFFSET(); - END; - - IF @ValidOutputServer = 1 - BEGIN - SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' - + @OutputServerName + '.' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' - + @OutputSchemaName + ''') INSERT ' - + @OutputServerName + '.' - + @OutputDatabaseName + '.' - + @OutputSchemaName + '.' - + @OutputTableName - + ' (ServerName, CheckDate, Version, QueryType, DatabaseName, AverageCPU, TotalCPU, PercentCPUByType, CPUWeight, AverageDuration, TotalDuration, DurationWeight, PercentDurationByType, AverageReads, TotalReads, ReadWeight, PercentReadsByType, ' - + ' AverageWrites, TotalWrites, WriteWeight, PercentWritesByType, ExecutionCount, ExecutionWeight, PercentExecutionsByType, ' - + ' ExecutionsPerMinute, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, QueryHash, QueryPlanHash, StatementStartOffset, StatementEndOffset, PlanGenerationNum, MinReturnedRows, MaxReturnedRows, AverageReturnedRows, TotalReturnedRows, QueryText, QueryPlan, NumberOfPlans, NumberOfDistinctPlans, Warnings, ' - + ' SerialRequiredMemory, SerialDesiredMemory, MinGrantKB, MaxGrantKB, MinUsedGrantKB, MaxUsedGrantKB, PercentMemoryGrantUsed, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, QueryPlanCost, Pattern ) ' - + 'SELECT TOP (@Top) ' - + QUOTENAME(CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128)), '''') + ', @CheckDateOverride, ' - + QUOTENAME(CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)), '''') + ', ' - + ' QueryType, DatabaseName, AverageCPU, TotalCPU, PercentCPUByType, PercentCPU, AverageDuration, TotalDuration, PercentDuration, PercentDurationByType, AverageReads, TotalReads, PercentReads, PercentReadsByType, ' - + ' AverageWrites, TotalWrites, PercentWrites, PercentWritesByType, ExecutionCount, PercentExecutions, PercentExecutionsByType, ' - + ' ExecutionsPerMinute, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, QueryHash, QueryPlanHash, StatementStartOffset, StatementEndOffset, PlanGenerationNum, MinReturnedRows, MaxReturnedRows, AverageReturnedRows, TotalReturnedRows, QueryText, CAST(QueryPlan AS NVARCHAR(MAX)), NumberOfPlans, NumberOfDistinctPlans, Warnings, ' - + ' SerialRequiredMemory, SerialDesiredMemory, MinGrantKB, MaxGrantKB, MinUsedGrantKB, MaxUsedGrantKB, PercentMemoryGrantUsed, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, QueryPlanCost, Pattern ' - + ' FROM ##BlitzCacheProcs ' - + ' WHERE 1=1 '; - - IF @MinimumExecutionCount IS NOT NULL - BEGIN - SET @StringToExecute += N' AND ExecutionCount >= @MinimumExecutionCount '; - END; - IF @MinutesBack IS NOT NULL - BEGIN - SET @StringToExecute += N' AND LastCompletionTime >= DATEADD(MINUTE, @min_back, GETDATE() ) '; - END; - SET @StringToExecute += N' AND SPID = @@SPID '; - SELECT @StringToExecute += N' ORDER BY ' + CASE @SortOrder WHEN 'cpu' THEN N' TotalCPU ' - WHEN N'reads' THEN N' TotalReads ' - WHEN N'writes' THEN N' TotalWrites ' - WHEN N'duration' THEN N' TotalDuration ' - WHEN N'executions' THEN N' ExecutionCount ' - WHEN N'compiles' THEN N' PlanCreationTime ' - WHEN N'memory grant' THEN N' MaxGrantKB' - WHEN N'spills' THEN N' MaxSpills' - WHEN N'avg cpu' THEN N' AverageCPU' - WHEN N'avg reads' THEN N' AverageReads' - WHEN N'avg writes' THEN N' AverageWrites' - WHEN N'avg duration' THEN N' AverageDuration' - WHEN N'avg executions' THEN N' ExecutionsPerMinute' - WHEN N'avg memory grant' THEN N' AvgMaxMemoryGrant' - WHEN N'avg spills' THEN N' AvgSpills' - WHEN N'unused grant' THEN N' MaxGrantKB - MaxUsedGrantKB' - ELSE N' TotalCPU ' - END + N' DESC '; - SET @StringToExecute += N' OPTION (RECOMPILE) ; '; - - IF @Debug = 1 - BEGIN - PRINT SUBSTRING(@StringToExecute, 1, 4000); - PRINT SUBSTRING(@StringToExecute, 4001, 4000); - PRINT SUBSTRING(@StringToExecute, 8001, 4000); - PRINT SUBSTRING(@StringToExecute, 12001, 4000); - PRINT SUBSTRING(@StringToExecute, 16001, 4000); - PRINT SUBSTRING(@StringToExecute, 20001, 4000); - PRINT SUBSTRING(@StringToExecute, 24001, 4000); - PRINT SUBSTRING(@StringToExecute, 28001, 4000); - PRINT SUBSTRING(@StringToExecute, 32001, 4000); - PRINT SUBSTRING(@StringToExecute, 36001, 4000); - END; - - EXEC sp_executesql @StringToExecute, N'@Top INT, @min_duration INT, @min_back INT, @CheckDateOverride DATETIMEOFFSET, @MinimumExecutionCount INT', @Top, @DurationFilter_i, @MinutesBack, @CheckDateOverride, @MinimumExecutionCount; - END; - ELSE - BEGIN - SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' - + @OutputSchemaName + ''') INSERT ' - + @OutputDatabaseName + '.' - + @OutputSchemaName + '.' - + @OutputTableName - + ' (ServerName, CheckDate, Version, QueryType, DatabaseName, AverageCPU, TotalCPU, PercentCPUByType, CPUWeight, AverageDuration, TotalDuration, DurationWeight, PercentDurationByType, AverageReads, TotalReads, ReadWeight, PercentReadsByType, ' - + ' AverageWrites, TotalWrites, WriteWeight, PercentWritesByType, ExecutionCount, ExecutionWeight, PercentExecutionsByType, ' - + ' ExecutionsPerMinute, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, QueryHash, QueryPlanHash, StatementStartOffset, StatementEndOffset, PlanGenerationNum, MinReturnedRows, MaxReturnedRows, AverageReturnedRows, TotalReturnedRows, QueryText, QueryPlan, NumberOfPlans, NumberOfDistinctPlans, Warnings, ' - + ' SerialRequiredMemory, SerialDesiredMemory, MinGrantKB, MaxGrantKB, MinUsedGrantKB, MaxUsedGrantKB, PercentMemoryGrantUsed, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, QueryPlanCost, Pattern ) ' - + 'SELECT TOP (@Top) ' - + QUOTENAME(CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128)), '''') + ', @CheckDateOverride, ' - + QUOTENAME(CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)), '''') + ', ' - + ' QueryType, DatabaseName, AverageCPU, TotalCPU, PercentCPUByType, PercentCPU, AverageDuration, TotalDuration, PercentDuration, PercentDurationByType, AverageReads, TotalReads, PercentReads, PercentReadsByType, ' - + ' AverageWrites, TotalWrites, PercentWrites, PercentWritesByType, ExecutionCount, PercentExecutions, PercentExecutionsByType, ' - + ' ExecutionsPerMinute, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, QueryHash, QueryPlanHash, StatementStartOffset, StatementEndOffset, PlanGenerationNum, MinReturnedRows, MaxReturnedRows, AverageReturnedRows, TotalReturnedRows, QueryText, QueryPlan, NumberOfPlans, NumberOfDistinctPlans, Warnings, ' - + ' SerialRequiredMemory, SerialDesiredMemory, MinGrantKB, MaxGrantKB, MinUsedGrantKB, MaxUsedGrantKB, PercentMemoryGrantUsed, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, QueryPlanCost, Pattern ' - + ' FROM ##BlitzCacheProcs ' - + ' WHERE 1=1 '; - - IF @MinimumExecutionCount IS NOT NULL - BEGIN - SET @StringToExecute += N' AND ExecutionCount >= @MinimumExecutionCount '; - END; - IF @MinutesBack IS NOT NULL - BEGIN - SET @StringToExecute += N' AND LastCompletionTime >= DATEADD(MINUTE, @min_back, GETDATE() ) '; - END; - SET @StringToExecute += N' AND SPID = @@SPID '; - SELECT @StringToExecute += N' ORDER BY ' + CASE @SortOrder WHEN 'cpu' THEN N' TotalCPU ' - WHEN N'reads' THEN N' TotalReads ' - WHEN N'writes' THEN N' TotalWrites ' - WHEN N'duration' THEN N' TotalDuration ' - WHEN N'executions' THEN N' ExecutionCount ' - WHEN N'compiles' THEN N' PlanCreationTime ' - WHEN N'memory grant' THEN N' MaxGrantKB' - WHEN N'spills' THEN N' MaxSpills' - WHEN N'avg cpu' THEN N' AverageCPU' - WHEN N'avg reads' THEN N' AverageReads' - WHEN N'avg writes' THEN N' AverageWrites' - WHEN N'avg duration' THEN N' AverageDuration' - WHEN N'avg executions' THEN N' ExecutionsPerMinute' - WHEN N'avg memory grant' THEN N' AvgMaxMemoryGrant' - WHEN N'avg spills' THEN N' AvgSpills' - WHEN N'unused grant' THEN N' MaxGrantKB - MaxUsedGrantKB' - ELSE N' TotalCPU ' - END + N' DESC '; - SET @StringToExecute += N' OPTION (RECOMPILE) ; '; - - IF @Debug = 1 - BEGIN - PRINT SUBSTRING(@StringToExecute, 0, 4000); - PRINT SUBSTRING(@StringToExecute, 4000, 8000); - PRINT SUBSTRING(@StringToExecute, 8000, 12000); - PRINT SUBSTRING(@StringToExecute, 12000, 16000); - PRINT SUBSTRING(@StringToExecute, 16000, 20000); - PRINT SUBSTRING(@StringToExecute, 20000, 24000); - PRINT SUBSTRING(@StringToExecute, 24000, 28000); - PRINT SUBSTRING(@StringToExecute, 28000, 32000); - PRINT SUBSTRING(@StringToExecute, 32000, 36000); - PRINT SUBSTRING(@StringToExecute, 36000, 40000); - END; - - EXEC sp_executesql @StringToExecute, N'@Top INT, @min_duration INT, @min_back INT, @CheckDateOverride DATETIMEOFFSET, @MinimumExecutionCount INT', @Top, @DurationFilter_i, @MinutesBack, @CheckDateOverride, @MinimumExecutionCount; - END; - END; - ELSE IF (SUBSTRING(@OutputTableName, 2, 2) = '##') - BEGIN - IF @ValidOutputServer = 1 - BEGIN - RAISERROR('Due to the nature of temporary tables, outputting to a linked server requires a permanent table.', 16, 0); - END; - ELSE IF @OutputTableName IN ('##BlitzCacheProcs','##BlitzCacheResults') - BEGIN - RAISERROR('OutputTableName is a reserved name for this procedure. We only use ##BlitzCacheProcs and ##BlitzCacheResults, please choose another table name.', 16, 0); - END; - ELSE - BEGIN - SET @StringToExecute = N' IF (OBJECT_ID(''tempdb..' - + @OutputTableName - + ''') IS NOT NULL) DROP TABLE ' + @OutputTableName + ';' - + 'CREATE TABLE ' - + @OutputTableName - + ' (ID bigint NOT NULL IDENTITY(1,1), - ServerName NVARCHAR(258), - CheckDate DATETIMEOFFSET, - Version NVARCHAR(258), - QueryType NVARCHAR(258), - Warnings varchar(max), - DatabaseName sysname, - SerialDesiredMemory float, - SerialRequiredMemory float, - AverageCPU bigint, - TotalCPU bigint, - PercentCPUByType money, - CPUWeight money, - AverageDuration bigint, - TotalDuration bigint, - DurationWeight money, - PercentDurationByType money, - AverageReads bigint, - TotalReads bigint, - ReadWeight money, - PercentReadsByType money, - AverageWrites bigint, - TotalWrites bigint, - WriteWeight money, - PercentWritesByType money, - ExecutionCount bigint, - ExecutionWeight money, - PercentExecutionsByType money, - ExecutionsPerMinute money, - PlanCreationTime datetime,' + N' - PlanCreationTimeHours AS DATEDIFF(HOUR, PlanCreationTime, SYSDATETIME()), - LastExecutionTime datetime, - LastCompletionTime datetime, - PlanHandle varbinary(64), - [Remove Plan Handle From Cache] AS - CASE WHEN [PlanHandle] IS NOT NULL - THEN ''DBCC FREEPROCCACHE ('' + CONVERT(VARCHAR(128), [PlanHandle], 1) + '');'' - ELSE ''N/A'' END, - SqlHandle varbinary(64), - [Remove SQL Handle From Cache] AS - CASE WHEN [SqlHandle] IS NOT NULL - THEN ''DBCC FREEPROCCACHE ('' + CONVERT(VARCHAR(128), [SqlHandle], 1) + '');'' - ELSE ''N/A'' END, - [SQL Handle More Info] AS - CASE WHEN [SqlHandle] IS NOT NULL - THEN ''EXEC sp_BlitzCache @OnlySqlHandles = '''''' + CONVERT(VARCHAR(128), [SqlHandle], 1) + ''''''; '' - ELSE ''N/A'' END, - QueryHash binary(8), - [Query Hash More Info] AS - CASE WHEN [QueryHash] IS NOT NULL - THEN ''EXEC sp_BlitzCache @OnlyQueryHashes = '''''' + CONVERT(VARCHAR(32), [QueryHash], 1) + ''''''; '' - ELSE ''N/A'' END, - QueryPlanHash binary(8), - StatementStartOffset int, - StatementEndOffset int, - PlanGenerationNum bigint, - MinReturnedRows bigint, - MaxReturnedRows bigint, - AverageReturnedRows money, - TotalReturnedRows bigint, - QueryText nvarchar(max), - QueryPlan xml, - NumberOfPlans int, - NumberOfDistinctPlans int, - MinGrantKB BIGINT, - MaxGrantKB BIGINT, - MinUsedGrantKB BIGINT, - MaxUsedGrantKB BIGINT, - PercentMemoryGrantUsed MONEY, - AvgMaxMemoryGrant MONEY, - MinSpills BIGINT, - MaxSpills BIGINT, - TotalSpills BIGINT, - AvgSpills MONEY, - QueryPlanCost FLOAT, - Pattern NVARCHAR(20), - JoinKey AS ServerName + Cast(CheckDate AS NVARCHAR(50)), - CONSTRAINT [PK_' + REPLACE(REPLACE(@OutputTableName,'[',''),']','') + '] PRIMARY KEY CLUSTERED(ID ASC));'; - SET @StringToExecute += N' INSERT ' - + @OutputTableName - + ' (ServerName, CheckDate, Version, QueryType, DatabaseName, AverageCPU, TotalCPU, PercentCPUByType, CPUWeight, AverageDuration, TotalDuration, DurationWeight, PercentDurationByType, AverageReads, TotalReads, ReadWeight, PercentReadsByType, ' - + ' AverageWrites, TotalWrites, WriteWeight, PercentWritesByType, ExecutionCount, ExecutionWeight, PercentExecutionsByType, ' - + ' ExecutionsPerMinute, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, QueryHash, QueryPlanHash, StatementStartOffset, StatementEndOffset, PlanGenerationNum, MinReturnedRows, MaxReturnedRows, AverageReturnedRows, TotalReturnedRows, QueryText, QueryPlan, NumberOfPlans, NumberOfDistinctPlans, Warnings, ' - + ' SerialRequiredMemory, SerialDesiredMemory, MinGrantKB, MaxGrantKB, MinUsedGrantKB, MaxUsedGrantKB, PercentMemoryGrantUsed, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, QueryPlanCost, Pattern ) ' - + 'SELECT TOP (@Top) ' - + QUOTENAME(CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128)), '''') + ', @CheckDateOverride, ' - + QUOTENAME(CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)), '''') + ', ' - + ' QueryType, DatabaseName, AverageCPU, TotalCPU, PercentCPUByType, PercentCPU, AverageDuration, TotalDuration, PercentDuration, PercentDurationByType, AverageReads, TotalReads, PercentReads, PercentReadsByType, ' - + ' AverageWrites, TotalWrites, PercentWrites, PercentWritesByType, ExecutionCount, PercentExecutions, PercentExecutionsByType, ' - + ' ExecutionsPerMinute, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, QueryHash, QueryPlanHash, StatementStartOffset, StatementEndOffset, PlanGenerationNum, MinReturnedRows, MaxReturnedRows, AverageReturnedRows, TotalReturnedRows, QueryText, QueryPlan, NumberOfPlans, NumberOfDistinctPlans, Warnings, ' - + ' SerialRequiredMemory, SerialDesiredMemory, MinGrantKB, MaxGrantKB, MinUsedGrantKB, MaxUsedGrantKB, PercentMemoryGrantUsed, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, QueryPlanCost, Pattern ' - + ' FROM ##BlitzCacheProcs ' - + ' WHERE 1=1 '; - - IF @MinimumExecutionCount IS NOT NULL - BEGIN - SET @StringToExecute += N' AND ExecutionCount >= @MinimumExecutionCount '; - END; - - IF @MinutesBack IS NOT NULL - BEGIN - SET @StringToExecute += N' AND LastCompletionTime >= DATEADD(MINUTE, @min_back, GETDATE() ) '; - END; - - SET @StringToExecute += N' AND SPID = @@SPID '; - - SELECT @StringToExecute += N' ORDER BY ' + CASE @SortOrder WHEN 'cpu' THEN N' TotalCPU ' - WHEN N'reads' THEN N' TotalReads ' - WHEN N'writes' THEN N' TotalWrites ' - WHEN N'duration' THEN N' TotalDuration ' - WHEN N'executions' THEN N' ExecutionCount ' - WHEN N'compiles' THEN N' PlanCreationTime ' - WHEN N'memory grant' THEN N' MaxGrantKB' - WHEN N'spills' THEN N' MaxSpills' - WHEN N'avg cpu' THEN N' AverageCPU' - WHEN N'avg reads' THEN N' AverageReads' - WHEN N'avg writes' THEN N' AverageWrites' - WHEN N'avg duration' THEN N' AverageDuration' - WHEN N'avg executions' THEN N' ExecutionsPerMinute' - WHEN N'avg memory grant' THEN N' AvgMaxMemoryGrant' - WHEN N'avg spills' THEN N' AvgSpills' - WHEN N'unused grant' THEN N' MaxGrantKB - MaxUsedGrantKB' - ELSE N' TotalCPU ' - END + N' DESC '; - - SET @StringToExecute += N' OPTION (RECOMPILE) ; '; - - IF @Debug = 1 - BEGIN - PRINT SUBSTRING(@StringToExecute, 0, 4000); - PRINT SUBSTRING(@StringToExecute, 4000, 8000); - PRINT SUBSTRING(@StringToExecute, 8000, 12000); - PRINT SUBSTRING(@StringToExecute, 12000, 16000); - PRINT SUBSTRING(@StringToExecute, 16000, 20000); - PRINT SUBSTRING(@StringToExecute, 20000, 24000); - PRINT SUBSTRING(@StringToExecute, 24000, 28000); - PRINT SUBSTRING(@StringToExecute, 28000, 32000); - PRINT SUBSTRING(@StringToExecute, 32000, 36000); - PRINT SUBSTRING(@StringToExecute, 36000, 40000); - PRINT SUBSTRING(@StringToExecute, 34000, 40000); - END; - EXEC sp_executesql @StringToExecute, N'@Top INT, @min_duration INT, @min_back INT, @CheckDateOverride DATETIMEOFFSET, @MinimumExecutionCount INT', @Top, @DurationFilter_i, @MinutesBack, @CheckDateOverride, @MinimumExecutionCount; - END; - END; - ELSE IF (SUBSTRING(@OutputTableName, 2, 1) = '#') - BEGIN - RAISERROR('Due to the nature of Dymamic SQL, only global (i.e. double pound (##)) temp tables are supported for @OutputTableName', 16, 0); -END; /* End of writing results to table */ - -END; /*Final End*/ - -GO -SET ANSI_NULLS ON; -SET ANSI_PADDING ON; -SET ANSI_WARNINGS ON; -SET ARITHABORT ON; -SET CONCAT_NULL_YIELDS_NULL ON; -SET QUOTED_IDENTIFIER ON; -SET STATISTICS IO OFF; -SET STATISTICS TIME OFF; -GO - -IF OBJECT_ID('dbo.sp_BlitzIndex') IS NULL - EXEC ('CREATE PROCEDURE dbo.sp_BlitzIndex AS RETURN 0;'); -GO - -ALTER PROCEDURE dbo.sp_BlitzIndex - @DatabaseName NVARCHAR(128) = NULL, /*Defaults to current DB if not specified*/ - @SchemaName NVARCHAR(128) = NULL, /*Requires table_name as well.*/ - @TableName NVARCHAR(128) = NULL, /*Requires schema_name as well.*/ - @Mode TINYINT=0, /*0=Diagnose, 1=Summarize, 2=Index Usage Detail, 3=Missing Index Detail, 4=Diagnose Details*/ - /*Note:@Mode doesn't matter if you're specifying schema_name and @TableName.*/ - @Filter TINYINT = 0, /* 0=no filter (default). 1=No low-usage warnings for objects with 0 reads. 2=Only warn for objects >= 500MB */ - /*Note:@Filter doesn't do anything unless @Mode=0*/ - @SkipPartitions BIT = 0, - @SkipStatistics BIT = 1, - @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, - @IgnoreDatabases NVARCHAR(MAX) = NULL, /* Comma-delimited list of databases you want to skip */ - @ThresholdMB INT = 250 /* Number of megabytes that an object must be before we include it in basic results */, - @OutputType VARCHAR(20) = 'TABLE' , - @OutputServerName NVARCHAR(256) = NULL , - @OutputDatabaseName NVARCHAR(256) = NULL , - @OutputSchemaName NVARCHAR(256) = NULL , - @OutputTableName NVARCHAR(256) = NULL , - @IncludeInactiveIndexes BIT = 0 /* Will skip indexes with no reads or writes */, - @ShowAllMissingIndexRequests BIT = 0 /*Will make all missing index requests show up*/, - @ShowPartitionRanges BIT = 0 /* Will add partition range values column to columnstore visualization */, - @SortOrder NVARCHAR(50) = NULL, /* Only affects @Mode = 2. */ - @SortDirection NVARCHAR(4) = 'DESC', /* Only affects @Mode = 2. */ - @Help TINYINT = 0, - @Debug BIT = 0, - @Version VARCHAR(30) = NULL OUTPUT, - @VersionDate DATETIME = NULL OUTPUT, - @VersionCheckMode BIT = 0 -WITH RECOMPILE -AS -SET NOCOUNT ON; -SET STATISTICS XML OFF; -SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - -SELECT @Version = '8.19', @VersionDate = '20240222'; -SET @OutputType = UPPER(@OutputType); - -IF(@VersionCheckMode = 1) -BEGIN - RETURN; -END; - -IF @Help = 1 -BEGIN -PRINT ' -/* -sp_BlitzIndex from http://FirstResponderKit.org - -This script analyzes the design and performance of your indexes. - -To learn more, visit http://FirstResponderKit.org where you can download new -versions for free, watch training videos on how it works, get more info on -the findings, contribute your own code, and more. - -Known limitations of this version: - - Only Microsoft-supported versions of SQL Server. Sorry, 2005 and 2000. - - Index create statements are just to give you a rough idea of the syntax. It includes filters and fillfactor. - -- Example 1: index creates use ONLINE=? instead of ONLINE=ON / ONLINE=OFF. This is because it is important - for the user to understand if it is going to be offline and not just run a script. - -- Example 2: they do not include all the options the index may have been created with (padding, compression - filegroup/partition scheme etc.) - -- (The compression and filegroup index create syntax is not trivial because it is set at the partition - level and is not trivial to code.) - - Does not advise you about data modeling for clustered indexes and primary keys (primarily looks for signs of problems.) - -Unknown limitations of this version: - - We knew them once, but we forgot. - - -MIT License - -Copyright (c) Brent Ozar Unlimited - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -'; -RETURN; -END; /* @Help = 1 */ - -DECLARE @ScriptVersionName NVARCHAR(50); -DECLARE @DaysUptime NUMERIC(23,2); -DECLARE @DatabaseID INT; -DECLARE @ObjectID INT; -DECLARE @dsql NVARCHAR(MAX); -DECLARE @params NVARCHAR(MAX); -DECLARE @msg NVARCHAR(4000); -DECLARE @ErrorSeverity INT; -DECLARE @ErrorState INT; -DECLARE @Rowcount BIGINT; -DECLARE @SQLServerProductVersion NVARCHAR(128); -DECLARE @SQLServerEdition INT; -DECLARE @FilterMB INT; -DECLARE @collation NVARCHAR(256); -DECLARE @NumDatabases INT; -DECLARE @LineFeed NVARCHAR(5); -DECLARE @DaysUptimeInsertValue NVARCHAR(256); -DECLARE @DatabaseToIgnore NVARCHAR(MAX); -DECLARE @ColumnList NVARCHAR(MAX); -DECLARE @ColumnListWithApostrophes NVARCHAR(MAX); -DECLARE @PartitionCount INT; -DECLARE @OptimizeForSequentialKey BIT = 0; -DECLARE @StringToExecute NVARCHAR(MAX); - - -/* Let's get @SortOrder set to lower case here for comparisons later */ -SET @SortOrder = REPLACE(LOWER(@SortOrder), N' ', N'_'); -SET @SortDirection = LOWER(@SortDirection); - -SET @LineFeed = CHAR(13) + CHAR(10); -SELECT @SQLServerProductVersion = CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)); -SELECT @SQLServerEdition =CAST(SERVERPROPERTY('EngineEdition') AS INT); /* We default to online index creates where EngineEdition=3*/ -SET @FilterMB=250; -SELECT @ScriptVersionName = 'sp_BlitzIndex(TM) v' + @Version + ' - ' + DATENAME(MM, @VersionDate) + ' ' + RIGHT('0'+DATENAME(DD, @VersionDate),2) + ', ' + DATENAME(YY, @VersionDate); -SET @IgnoreDatabases = REPLACE(REPLACE(LTRIM(RTRIM(@IgnoreDatabases)), CHAR(10), ''), CHAR(13), ''); - -SELECT - @OptimizeForSequentialKey = - CASE WHEN EXISTS - ( - SELECT - 1/0 - FROM sys.all_columns AS ac - WHERE ac.object_id = OBJECT_ID('sys.indexes') - AND ac.name = N'optimize_for_sequential_key' - ) - THEN 1 - ELSE 0 - END; - -RAISERROR(N'Starting run. %s', 0,1, @ScriptVersionName) WITH NOWAIT; - - -IF(@OutputType NOT IN ('TABLE','NONE')) -BEGIN - RAISERROR('Invalid value for parameter @OutputType. Expected: (TABLE;NONE)',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; - SET @OutputType = 'NONE' -END; - -IF(@OutputType = 'NONE') -BEGIN - - IF ((@OutputServerName IS NOT NULL) AND (@OutputTableName IS NULL OR @OutputSchemaName IS NULL OR @OutputDatabaseName IS NULL)) - BEGIN - RAISERROR('Parameter @OutputServerName is specified, rest of @Output* parameters needs to also be specified',12,1); - RETURN; - END; - - IF(@OutputTableName IS NULL OR @OutputSchemaName IS NULL OR @OutputDatabaseName IS NULL) - BEGIN - RAISERROR('This procedure should be called with a value for @OutputTableName, @OutputSchemaName and @OutputDatabaseName parameters, as @OutputType is set to NONE',12,1); - RETURN; - END; - /* Output is supported for all modes, no reason to not bring pain and output - IF(@BringThePain = 1) - BEGIN - RAISERROR('Incompatible Parameters: @BringThePain set to 1 and @OutputType set to NONE',12,1); - RETURN; - END; - */ - /* Eventually limit by mode - IF(@Mode not in (0,4)) - BEGIN - RAISERROR('Incompatible Parameters: @Mode set to %d and @OutputType set to NONE',12,1,@Mode); - RETURN; - END; - */ -END; - -IF OBJECT_ID('tempdb..#IndexSanity') IS NOT NULL - DROP TABLE #IndexSanity; - -IF OBJECT_ID('tempdb..#IndexPartitionSanity') IS NOT NULL - DROP TABLE #IndexPartitionSanity; - -IF OBJECT_ID('tempdb..#IndexSanitySize') IS NOT NULL - DROP TABLE #IndexSanitySize; - -IF OBJECT_ID('tempdb..#IndexColumns') IS NOT NULL - DROP TABLE #IndexColumns; - -IF OBJECT_ID('tempdb..#MissingIndexes') IS NOT NULL - DROP TABLE #MissingIndexes; - -IF OBJECT_ID('tempdb..#ForeignKeys') IS NOT NULL - DROP TABLE #ForeignKeys; - -IF OBJECT_ID('tempdb..#UnindexedForeignKeys') IS NOT NULL - DROP TABLE #UnindexedForeignKeys; - -IF OBJECT_ID('tempdb..#BlitzIndexResults') IS NOT NULL - DROP TABLE #BlitzIndexResults; - -IF OBJECT_ID('tempdb..#IndexCreateTsql') IS NOT NULL - DROP TABLE #IndexCreateTsql; - -IF OBJECT_ID('tempdb..#DatabaseList') IS NOT NULL - DROP TABLE #DatabaseList; - -IF OBJECT_ID('tempdb..#Statistics') IS NOT NULL - DROP TABLE #Statistics; - -IF OBJECT_ID('tempdb..#PartitionCompressionInfo') IS NOT NULL - DROP TABLE #PartitionCompressionInfo; - -IF OBJECT_ID('tempdb..#ComputedColumns') IS NOT NULL - DROP TABLE #ComputedColumns; - -IF OBJECT_ID('tempdb..#TraceStatus') IS NOT NULL - DROP TABLE #TraceStatus; - -IF OBJECT_ID('tempdb..#TemporalTables') IS NOT NULL - DROP TABLE #TemporalTables; - -IF OBJECT_ID('tempdb..#CheckConstraints') IS NOT NULL - DROP TABLE #CheckConstraints; - -IF OBJECT_ID('tempdb..#FilteredIndexes') IS NOT NULL - DROP TABLE #FilteredIndexes; - -IF OBJECT_ID('tempdb..#Ignore_Databases') IS NOT NULL - DROP TABLE #Ignore_Databases - -IF OBJECT_ID('tempdb..#dm_db_partition_stats_etc') IS NOT NULL - DROP TABLE #dm_db_partition_stats_etc -IF OBJECT_ID('tempdb..#dm_db_index_operational_stats') IS NOT NULL - DROP TABLE #dm_db_index_operational_stats - - RAISERROR (N'Create temp tables.',0,1) WITH NOWAIT; - CREATE TABLE #BlitzIndexResults - ( - blitz_result_id INT IDENTITY PRIMARY KEY, - check_id INT NOT NULL, - index_sanity_id INT NULL, - Priority INT NULL, - findings_group NVARCHAR(4000) NOT NULL, - finding NVARCHAR(200) NOT NULL, - [database_name] NVARCHAR(128) NULL, - URL NVARCHAR(200) NOT NULL, - details NVARCHAR(MAX) NOT NULL, - index_definition NVARCHAR(MAX) NOT NULL, - secret_columns NVARCHAR(MAX) NULL, - index_usage_summary NVARCHAR(MAX) NULL, - index_size_summary NVARCHAR(MAX) NULL, - create_tsql NVARCHAR(MAX) NULL, - more_info NVARCHAR(MAX) NULL, - sample_query_plan XML NULL - ); - - CREATE TABLE #IndexSanity - ( - [index_sanity_id] INT IDENTITY PRIMARY KEY CLUSTERED, - [database_id] SMALLINT NOT NULL , - [object_id] INT NOT NULL , - [index_id] INT NOT NULL , - [index_type] TINYINT NOT NULL, - [database_name] NVARCHAR(128) NOT NULL , - [schema_name] NVARCHAR(128) NOT NULL , - [object_name] NVARCHAR(128) NOT NULL , - index_name NVARCHAR(128) NULL , - key_column_names NVARCHAR(MAX) NULL , - key_column_names_with_sort_order NVARCHAR(MAX) NULL , - key_column_names_with_sort_order_no_types NVARCHAR(MAX) NULL , - count_key_columns INT NULL , - include_column_names NVARCHAR(MAX) NULL , - include_column_names_no_types NVARCHAR(MAX) NULL , - count_included_columns INT NULL , - partition_key_column_name NVARCHAR(MAX) NULL, - filter_definition NVARCHAR(MAX) NOT NULL , - optimize_for_sequential_key BIT NULL, - is_indexed_view BIT NOT NULL , - is_unique BIT NOT NULL , - is_primary_key BIT NOT NULL , - is_unique_constraint BIT NOT NULL , - is_XML bit NOT NULL, - is_spatial BIT NOT NULL, - is_NC_columnstore BIT NOT NULL, - is_CX_columnstore BIT NOT NULL, - is_in_memory_oltp BIT NOT NULL , - is_disabled BIT NOT NULL , - is_hypothetical BIT NOT NULL , - is_padded BIT NOT NULL , - fill_factor SMALLINT NOT NULL , - user_seeks BIGINT NOT NULL , - user_scans BIGINT NOT NULL , - user_lookups BIGINT NOT NULL , - user_updates BIGINT NULL , - last_user_seek DATETIME NULL , - last_user_scan DATETIME NULL , - last_user_lookup DATETIME NULL , - last_user_update DATETIME NULL , - is_referenced_by_foreign_key BIT DEFAULT(0), - secret_columns NVARCHAR(MAX) NULL, - count_secret_columns INT NULL, - create_date DATETIME NOT NULL, - modify_date DATETIME NOT NULL, - filter_columns_not_in_index NVARCHAR(MAX), - [db_schema_object_name] AS [schema_name] + N'.' + [object_name] , - [db_schema_object_indexid] AS [schema_name] + N'.' + [object_name] - + CASE WHEN [index_name] IS NOT NULL THEN N'.' + index_name - ELSE N'' - END + N' (' + CAST(index_id AS NVARCHAR(20)) + N')' , - first_key_column_name AS CASE WHEN count_key_columns > 1 - THEN LEFT(key_column_names, CHARINDEX(',', key_column_names, 0) - 1) - ELSE key_column_names - END , - index_definition AS - CASE WHEN partition_key_column_name IS NOT NULL - THEN N'[PARTITIONED BY:' + partition_key_column_name + N']' - ELSE '' - END + - CASE index_id - WHEN 0 THEN N'[HEAP] ' - WHEN 1 THEN N'[CX] ' - ELSE N'' END + CASE WHEN is_indexed_view = 1 THEN N'[VIEW] ' - ELSE N'' END + CASE WHEN is_primary_key = 1 THEN N'[PK] ' - ELSE N'' END + CASE WHEN is_XML = 1 THEN N'[XML] ' - ELSE N'' END + CASE WHEN is_spatial = 1 THEN N'[SPATIAL] ' - ELSE N'' END + CASE WHEN is_NC_columnstore = 1 THEN N'[COLUMNSTORE] ' - ELSE N'' END + CASE WHEN is_in_memory_oltp = 1 THEN N'[IN-MEMORY] ' - ELSE N'' END + CASE WHEN is_disabled = 1 THEN N'[DISABLED] ' - ELSE N'' END + CASE WHEN is_hypothetical = 1 THEN N'[HYPOTHETICAL] ' - ELSE N'' END + CASE WHEN is_unique = 1 AND is_primary_key = 0 AND is_unique_constraint = 0 THEN N'[UNIQUE] ' - ELSE N'' END + CASE WHEN is_unique_constraint = 1 AND is_primary_key = 0 THEN N'[UNIQUE CONSTRAINT] ' - ELSE N'' END + CASE WHEN count_key_columns > 0 THEN - N'[' + CAST(count_key_columns AS NVARCHAR(10)) + N' KEY' - + CASE WHEN count_key_columns > 1 THEN N'S' ELSE N'' END - + N'] ' + LTRIM(key_column_names_with_sort_order) - ELSE N'' END + CASE WHEN count_included_columns > 0 THEN - N' [' + CAST(count_included_columns AS NVARCHAR(10)) + N' INCLUDE' + - + CASE WHEN count_included_columns > 1 THEN N'S' ELSE N'' END - + N'] ' + include_column_names - ELSE N'' END + CASE WHEN filter_definition <> N'' THEN N' [FILTER] ' + filter_definition - ELSE N'' END , - [total_reads] AS user_seeks + user_scans + user_lookups, - [reads_per_write] AS CAST(CASE WHEN user_updates > 0 - THEN ( user_seeks + user_scans + user_lookups ) / (1.0 * user_updates) - ELSE 0 END AS MONEY) , - [index_usage_summary] AS - CASE WHEN is_spatial = 1 THEN N'Not Tracked' - WHEN is_disabled = 1 THEN N'Disabled' - ELSE N'Reads: ' + - REPLACE(CONVERT(NVARCHAR(30),CAST((user_seeks + user_scans + user_lookups) AS MONEY), 1), N'.00', N'') - + CASE WHEN user_seeks + user_scans + user_lookups > 0 THEN - N' (' - + RTRIM( - CASE WHEN user_seeks > 0 THEN REPLACE(CONVERT(NVARCHAR(30),CAST((user_seeks) AS MONEY), 1), N'.00', N'') + N' seek ' ELSE N'' END - + CASE WHEN user_scans > 0 THEN REPLACE(CONVERT(NVARCHAR(30),CAST((user_scans) AS MONEY), 1), N'.00', N'') + N' scan ' ELSE N'' END - + CASE WHEN user_lookups > 0 THEN REPLACE(CONVERT(NVARCHAR(30),CAST((user_lookups) AS MONEY), 1), N'.00', N'') + N' lookup' ELSE N'' END - ) - + N') ' - ELSE N' ' - END - + N'Writes: ' + - REPLACE(CONVERT(NVARCHAR(30),CAST(user_updates AS MONEY), 1), N'.00', N'') - END /* First "end" is about is_spatial */, - [more_info] AS - CASE WHEN is_in_memory_oltp = 1 - THEN N'EXEC dbo.sp_BlitzInMemoryOLTP @dbName=' + QUOTENAME([database_name],N'''') + - N', @tableName=' + QUOTENAME([object_name],N'''') + N';' - ELSE N'EXEC dbo.sp_BlitzIndex @DatabaseName=' + QUOTENAME([database_name],N'''') + - N', @SchemaName=' + QUOTENAME([schema_name],N'''') + N', @TableName=' + QUOTENAME([object_name],N'''') + N';' - END - ); - RAISERROR (N'Adding UQ index on #IndexSanity (database_id, object_id, index_id)',0,1) WITH NOWAIT; - IF NOT EXISTS(SELECT 1 FROM tempdb.sys.indexes WHERE name='uq_database_id_object_id_index_id') - CREATE UNIQUE INDEX uq_database_id_object_id_index_id ON #IndexSanity (database_id, object_id, index_id); - - - CREATE TABLE #IndexPartitionSanity - ( - [index_partition_sanity_id] INT IDENTITY, - [index_sanity_id] INT NULL , - [database_id] INT NOT NULL , - [object_id] INT NOT NULL , - [schema_name] NVARCHAR(128) NOT NULL, - [index_id] INT NOT NULL , - [partition_number] INT NOT NULL , - row_count BIGINT NOT NULL , - reserved_MB NUMERIC(29,2) NOT NULL , - reserved_LOB_MB NUMERIC(29,2) NOT NULL , - reserved_row_overflow_MB NUMERIC(29,2) NOT NULL , - reserved_dictionary_MB NUMERIC(29,2) NOT NULL , - leaf_insert_count BIGINT NULL , - leaf_delete_count BIGINT NULL , - leaf_update_count BIGINT NULL , - range_scan_count BIGINT NULL , - singleton_lookup_count BIGINT NULL , - forwarded_fetch_count BIGINT NULL , - lob_fetch_in_pages BIGINT NULL , - lob_fetch_in_bytes BIGINT NULL , - row_overflow_fetch_in_pages BIGINT NULL , - row_overflow_fetch_in_bytes BIGINT NULL , - row_lock_count BIGINT NULL , - row_lock_wait_count BIGINT NULL , - row_lock_wait_in_ms BIGINT NULL , - page_lock_count BIGINT NULL , - page_lock_wait_count BIGINT NULL , - page_lock_wait_in_ms BIGINT NULL , - index_lock_promotion_attempt_count BIGINT NULL , - index_lock_promotion_count BIGINT NULL, - data_compression_desc NVARCHAR(60) NULL, - page_latch_wait_count BIGINT NULL, - page_latch_wait_in_ms BIGINT NULL, - page_io_latch_wait_count BIGINT NULL, - page_io_latch_wait_in_ms BIGINT NULL, - lock_escalation_desc nvarchar(60) NULL - ); - - CREATE TABLE #IndexSanitySize - ( - [index_sanity_size_id] INT IDENTITY NOT NULL , - [index_sanity_id] INT NULL , - [database_id] INT NOT NULL, - [schema_name] NVARCHAR(128) NOT NULL, - partition_count INT NOT NULL , - total_rows BIGINT NOT NULL , - total_reserved_MB NUMERIC(29,2) NOT NULL , - total_reserved_LOB_MB NUMERIC(29,2) NOT NULL , - total_reserved_row_overflow_MB NUMERIC(29,2) NOT NULL , - total_reserved_dictionary_MB NUMERIC(29,2) NOT NULL , - total_leaf_delete_count BIGINT NULL, - total_leaf_update_count BIGINT NULL, - total_range_scan_count BIGINT NULL, - total_singleton_lookup_count BIGINT NULL, - total_forwarded_fetch_count BIGINT NULL, - total_row_lock_count BIGINT NULL , - total_row_lock_wait_count BIGINT NULL , - total_row_lock_wait_in_ms BIGINT NULL , - avg_row_lock_wait_in_ms BIGINT NULL , - total_page_lock_count BIGINT NULL , - total_page_lock_wait_count BIGINT NULL , - total_page_lock_wait_in_ms BIGINT NULL , - avg_page_lock_wait_in_ms BIGINT NULL , - total_index_lock_promotion_attempt_count BIGINT NULL , - total_index_lock_promotion_count BIGINT NULL , - data_compression_desc NVARCHAR(4000) NULL, - page_latch_wait_count BIGINT NULL, - page_latch_wait_in_ms BIGINT NULL, - page_io_latch_wait_count BIGINT NULL, - page_io_latch_wait_in_ms BIGINT NULL, - lock_escalation_desc nvarchar(60) NULL, - index_size_summary AS ISNULL( - CASE WHEN partition_count > 1 - THEN N'[' + CAST(partition_count AS NVARCHAR(10)) + N' PARTITIONS] ' - ELSE N'' - END + REPLACE(CONVERT(NVARCHAR(30),CAST([total_rows] AS MONEY), 1), N'.00', N'') + N' rows; ' - + CASE WHEN total_reserved_MB > 1024 THEN - CAST(CAST(total_reserved_MB/1024. AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'GB' - ELSE - CAST(CAST(total_reserved_MB AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'MB' - END - + CASE WHEN total_reserved_LOB_MB > 1024 THEN - N'; ' + CAST(CAST(total_reserved_LOB_MB/1024. AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'GB ' + CASE WHEN total_reserved_dictionary_MB = 0 THEN N'LOB' ELSE N'Columnstore' END - WHEN total_reserved_LOB_MB > 0 THEN - N'; ' + CAST(CAST(total_reserved_LOB_MB AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'MB ' + CASE WHEN total_reserved_dictionary_MB = 0 THEN N'LOB' ELSE N'Columnstore' END - ELSE '' - END - + CASE WHEN total_reserved_row_overflow_MB > 1024 THEN - N'; ' + CAST(CAST(total_reserved_row_overflow_MB/1024. AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'GB Row Overflow' - WHEN total_reserved_row_overflow_MB > 0 THEN - N'; ' + CAST(CAST(total_reserved_row_overflow_MB AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'MB Row Overflow' - ELSE '' - END - + CASE WHEN total_reserved_dictionary_MB > 1024 THEN - N'; ' + CAST(CAST(total_reserved_dictionary_MB/1024. AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'GB Dictionaries' - WHEN total_reserved_dictionary_MB > 0 THEN - N'; ' + CAST(CAST(total_reserved_dictionary_MB AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'MB Dictionaries' - ELSE '' - END , - N'Error- NULL in computed column'), - index_op_stats AS ISNULL( - ( - REPLACE(CONVERT(NVARCHAR(30),CAST(total_singleton_lookup_count AS MONEY), 1),N'.00',N'') + N' singleton lookups; ' - + REPLACE(CONVERT(NVARCHAR(30),CAST(total_range_scan_count AS MONEY), 1),N'.00',N'') + N' scans/seeks; ' - + REPLACE(CONVERT(NVARCHAR(30),CAST(total_leaf_delete_count AS MONEY), 1),N'.00',N'') + N' deletes; ' - + REPLACE(CONVERT(NVARCHAR(30),CAST(total_leaf_update_count AS MONEY), 1),N'.00',N'') + N' updates; ' - + CASE WHEN ISNULL(total_forwarded_fetch_count,0) >0 THEN - REPLACE(CONVERT(NVARCHAR(30),CAST(total_forwarded_fetch_count AS MONEY), 1),N'.00',N'') + N' forward records fetched; ' - ELSE N'' END - - /* rows will only be in this dmv when data is in memory for the table */ - ), N'Table metadata not in memory'), - index_lock_wait_summary AS ISNULL( - CASE WHEN total_row_lock_wait_count = 0 AND total_page_lock_wait_count = 0 AND - total_index_lock_promotion_attempt_count = 0 THEN N'0 lock waits; ' - + CASE WHEN lock_escalation_desc = N'DISABLE' THEN N'Lock escalation DISABLE.' - ELSE N'' - END - ELSE - CASE WHEN total_row_lock_wait_count > 0 THEN - N'Row lock waits: ' + REPLACE(CONVERT(NVARCHAR(30),CAST(total_row_lock_wait_count AS MONEY), 1), N'.00', N'') - + N'; total duration: ' + - CASE WHEN total_row_lock_wait_in_ms >= 60000 THEN /*More than 1 min*/ - REPLACE(CONVERT(NVARCHAR(30),CAST((total_row_lock_wait_in_ms/60000) AS MONEY), 1), N'.00', N'') + N' minutes; ' - ELSE - REPLACE(CONVERT(NVARCHAR(30),CAST(ISNULL(total_row_lock_wait_in_ms/1000,0) AS MONEY), 1), N'.00', N'') + N' seconds; ' - END - + N'avg duration: ' + - CASE WHEN avg_row_lock_wait_in_ms >= 60000 THEN /*More than 1 min*/ - REPLACE(CONVERT(NVARCHAR(30),CAST((avg_row_lock_wait_in_ms/60000) AS MONEY), 1), N'.00', N'') + N' minutes; ' - ELSE - REPLACE(CONVERT(NVARCHAR(30),CAST(ISNULL(avg_row_lock_wait_in_ms/1000,0) AS MONEY), 1), N'.00', N'') + N' seconds; ' - END - ELSE N'' - END + - CASE WHEN total_page_lock_wait_count > 0 THEN - N'Page lock waits: ' + REPLACE(CONVERT(NVARCHAR(30),CAST(total_page_lock_wait_count AS MONEY), 1), N'.00', N'') - + N'; total duration: ' + - CASE WHEN total_page_lock_wait_in_ms >= 60000 THEN /*More than 1 min*/ - REPLACE(CONVERT(NVARCHAR(30),CAST((total_page_lock_wait_in_ms/60000) AS MONEY), 1), N'.00', N'') + N' minutes; ' - ELSE - REPLACE(CONVERT(NVARCHAR(30),CAST(ISNULL(total_page_lock_wait_in_ms/1000,0) AS MONEY), 1), N'.00', N'') + N' seconds; ' - END - + N'avg duration: ' + - CASE WHEN avg_page_lock_wait_in_ms >= 60000 THEN /*More than 1 min*/ - REPLACE(CONVERT(NVARCHAR(30),CAST((avg_page_lock_wait_in_ms/60000) AS MONEY), 1), N'.00', N'') + N' minutes; ' - ELSE - REPLACE(CONVERT(NVARCHAR(30),CAST(ISNULL(avg_page_lock_wait_in_ms/1000,0) AS MONEY), 1), N'.00', N'') + N' seconds; ' - END - ELSE N'' - END + - CASE WHEN total_index_lock_promotion_attempt_count > 0 THEN - N'Lock escalation attempts: ' + REPLACE(CONVERT(NVARCHAR(30),CAST(total_index_lock_promotion_attempt_count AS MONEY), 1), N'.00', N'') - + N'; Actual Escalations: ' + REPLACE(CONVERT(NVARCHAR(30),CAST(ISNULL(total_index_lock_promotion_count,0) AS MONEY), 1), N'.00', N'') +N'; ' - ELSE N'' - END + - CASE WHEN lock_escalation_desc = N'DISABLE' THEN - N'Lock escalation is disabled.' - ELSE N'' - END - END - ,'Error- NULL in computed column') - ); - - CREATE TABLE #IndexColumns - ( - [database_id] INT NOT NULL, - [schema_name] NVARCHAR(128), - [object_id] INT NOT NULL , - [index_id] INT NOT NULL , - [key_ordinal] INT NULL , - is_included_column BIT NULL , - is_descending_key BIT NULL , - [partition_ordinal] INT NULL , - column_name NVARCHAR(256) NOT NULL , - system_type_name NVARCHAR(256) NOT NULL, - max_length SMALLINT NOT NULL, - [precision] TINYINT NOT NULL, - [scale] TINYINT NOT NULL, - collation_name NVARCHAR(256) NULL, - is_nullable BIT NULL, - is_identity BIT NULL, - is_computed BIT NULL, - is_replicated BIT NULL, - is_sparse BIT NULL, - is_filestream BIT NULL, - seed_value DECIMAL(38,0) NULL, - increment_value DECIMAL(38,0) NULL , - last_value DECIMAL(38,0) NULL, - is_not_for_replication BIT NULL - ); - CREATE CLUSTERED INDEX CLIX_database_id_object_id_index_id ON #IndexColumns - (database_id, object_id, index_id); - - CREATE TABLE #MissingIndexes - ([database_id] INT NOT NULL, - [object_id] INT NOT NULL, - [database_name] NVARCHAR(128) NOT NULL , - [schema_name] NVARCHAR(128) NOT NULL , - [table_name] NVARCHAR(128), - [statement] NVARCHAR(512) NOT NULL, - magic_benefit_number AS (( user_seeks + user_scans ) * avg_total_user_cost * avg_user_impact), - avg_total_user_cost NUMERIC(29,4) NOT NULL, - avg_user_impact NUMERIC(29,1) NOT NULL, - user_seeks BIGINT NOT NULL, - user_scans BIGINT NOT NULL, - unique_compiles BIGINT NULL, - equality_columns NVARCHAR(MAX), - equality_columns_with_data_type NVARCHAR(MAX), - inequality_columns NVARCHAR(MAX), - inequality_columns_with_data_type NVARCHAR(MAX), - included_columns NVARCHAR(MAX), - included_columns_with_data_type NVARCHAR(MAX), - is_low BIT, - [index_estimated_impact] AS - REPLACE(CONVERT(NVARCHAR(256),CAST(CAST( - (user_seeks + user_scans) - AS BIGINT) AS MONEY), 1), '.00', '') + N' use' - + CASE WHEN (user_seeks + user_scans) > 1 THEN N's' ELSE N'' END - +N'; Impact: ' + CAST(avg_user_impact AS NVARCHAR(30)) - + N'%; Avg query cost: ' - + CAST(avg_total_user_cost AS NVARCHAR(30)), - [missing_index_details] AS - CASE WHEN COALESCE(equality_columns_with_data_type,equality_columns) IS NOT NULL - THEN N'EQUALITY: ' + COALESCE(CAST(equality_columns_with_data_type AS NVARCHAR(MAX)), CAST(equality_columns AS NVARCHAR(MAX))) + N' ' - ELSE N'' END + - - CASE WHEN COALESCE(inequality_columns_with_data_type,inequality_columns) IS NOT NULL - THEN N'INEQUALITY: ' + COALESCE(CAST(inequality_columns_with_data_type AS NVARCHAR(MAX)), CAST(inequality_columns AS NVARCHAR(MAX))) + N' ' - ELSE N'' END + - - CASE WHEN COALESCE(included_columns_with_data_type,included_columns) IS NOT NULL - THEN N'INCLUDE: ' + COALESCE(CAST(included_columns_with_data_type AS NVARCHAR(MAX)), CAST(included_columns AS NVARCHAR(MAX))) + N' ' - ELSE N'' END, - [create_tsql] AS N'CREATE INDEX [' - + LEFT(REPLACE(REPLACE(REPLACE(REPLACE( - ISNULL(equality_columns,N'')+ - CASE WHEN equality_columns IS NOT NULL AND inequality_columns IS NOT NULL THEN N'_' ELSE N'' END - + ISNULL(inequality_columns,''),',','') - ,'[',''),']',''),' ','_') - + CASE WHEN included_columns IS NOT NULL THEN N'_Includes' ELSE N'' END, 128) + N'] ON ' - + [statement] + N' (' + ISNULL(equality_columns,N'') - + CASE WHEN equality_columns IS NOT NULL AND inequality_columns IS NOT NULL THEN N', ' ELSE N'' END - + CASE WHEN inequality_columns IS NOT NULL THEN inequality_columns ELSE N'' END + - ') ' + CASE WHEN included_columns IS NOT NULL THEN N' INCLUDE (' + included_columns + N')' ELSE N'' END - + N' WITH (' - + N'FILLFACTOR=100, ONLINE=?, SORT_IN_TEMPDB=?, DATA_COMPRESSION=?' - + N')' - + N';' - , - [more_info] AS N'EXEC dbo.sp_BlitzIndex @DatabaseName=' + QUOTENAME([database_name],'''') + - N', @SchemaName=' + QUOTENAME([schema_name],'''') + N', @TableName=' + QUOTENAME([table_name],'''') + N';', - [sample_query_plan] XML NULL - ); - - CREATE TABLE #ForeignKeys ( - [database_id] INT NOT NULL, - [database_name] NVARCHAR(128) NOT NULL , - [schema_name] NVARCHAR(128) NOT NULL , - foreign_key_name NVARCHAR(256), - parent_object_id INT, - parent_object_name NVARCHAR(256), - referenced_object_id INT, - referenced_object_name NVARCHAR(256), - is_disabled BIT, - is_not_trusted BIT, - is_not_for_replication BIT, - parent_fk_columns NVARCHAR(MAX), - referenced_fk_columns NVARCHAR(MAX), - update_referential_action_desc NVARCHAR(16), - delete_referential_action_desc NVARCHAR(60) - ); - - CREATE TABLE #UnindexedForeignKeys - ( - [database_id] INT NOT NULL, - [database_name] NVARCHAR(128) NOT NULL , - [schema_name] NVARCHAR(128) NOT NULL , - foreign_key_name NVARCHAR(256), - parent_object_name NVARCHAR(256), - parent_object_id INT, - referenced_object_name NVARCHAR(256), - referenced_object_id INT - ); - - CREATE TABLE #IndexCreateTsql ( - index_sanity_id INT NOT NULL, - create_tsql NVARCHAR(MAX) NOT NULL - ); - - CREATE TABLE #DatabaseList ( - DatabaseName NVARCHAR(256), - secondary_role_allow_connections_desc NVARCHAR(50) - - ); - - CREATE TABLE #PartitionCompressionInfo ( - [index_sanity_id] INT NULL, - [partition_compression_detail] NVARCHAR(4000) NULL - ); - - CREATE TABLE #Statistics ( - database_id INT NOT NULL, - database_name NVARCHAR(256) NOT NULL, - table_name NVARCHAR(128) NULL, - schema_name NVARCHAR(128) NULL, - index_name NVARCHAR(128) NULL, - column_names NVARCHAR(MAX) NULL, - statistics_name NVARCHAR(128) NULL, - last_statistics_update DATETIME NULL, - days_since_last_stats_update INT NULL, - rows BIGINT NULL, - rows_sampled BIGINT NULL, - percent_sampled DECIMAL(18, 1) NULL, - histogram_steps INT NULL, - modification_counter BIGINT NULL, - percent_modifications DECIMAL(18, 1) NULL, - modifications_before_auto_update INT 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 - ); - - CREATE TABLE #ComputedColumns - ( - index_sanity_id INT IDENTITY(1, 1) NOT NULL, - database_name NVARCHAR(128) NULL, - database_id INT NOT NULL, - table_name NVARCHAR(128) NOT NULL, - schema_name NVARCHAR(128) NOT NULL, - column_name NVARCHAR(128) NULL, - is_nullable BIT NULL, - definition NVARCHAR(MAX) NULL, - uses_database_collation BIT NOT NULL, - is_persisted BIT NOT NULL, - is_computed BIT NOT NULL, - is_function INT NOT NULL, - column_definition NVARCHAR(MAX) NULL - ); - - CREATE TABLE #TraceStatus - ( - TraceFlag NVARCHAR(10) , - status BIT , - Global BIT , - Session BIT - ); - - CREATE TABLE #TemporalTables - ( - index_sanity_id INT IDENTITY(1, 1) NOT NULL, - database_name NVARCHAR(128) NOT NULL, - database_id INT NOT NULL, - schema_name NVARCHAR(128) NOT NULL, - table_name NVARCHAR(128) NOT NULL, - history_table_name NVARCHAR(128) NOT NULL, - history_schema_name NVARCHAR(128) NOT NULL, - start_column_name NVARCHAR(128) NOT NULL, - end_column_name NVARCHAR(128) NOT NULL, - period_name NVARCHAR(128) NOT NULL - ); - - CREATE TABLE #CheckConstraints - ( - index_sanity_id INT IDENTITY(1, 1) NOT NULL, - database_name NVARCHAR(128) NULL, - database_id INT NOT NULL, - table_name NVARCHAR(128) NOT NULL, - schema_name NVARCHAR(128) NOT NULL, - constraint_name NVARCHAR(128) NULL, - is_disabled BIT NULL, - definition NVARCHAR(MAX) NULL, - uses_database_collation BIT NOT NULL, - is_not_trusted BIT NOT NULL, - is_function INT NOT NULL, - column_definition NVARCHAR(MAX) NULL - ); - - CREATE TABLE #FilteredIndexes - ( - index_sanity_id INT IDENTITY(1, 1) NOT NULL, - database_name NVARCHAR(128) NULL, - database_id INT NOT NULL, - schema_name NVARCHAR(128) NOT NULL, - table_name NVARCHAR(128) NOT NULL, - index_name NVARCHAR(128) NULL, - column_name NVARCHAR(128) NULL - ); - - CREATE TABLE #Ignore_Databases - ( - DatabaseName NVARCHAR(128), - Reason NVARCHAR(100) - ); - -/* Sanitize our inputs */ -SELECT - @OutputServerName = QUOTENAME(@OutputServerName), - @OutputDatabaseName = QUOTENAME(@OutputDatabaseName), - @OutputSchemaName = QUOTENAME(@OutputSchemaName), - @OutputTableName = QUOTENAME(@OutputTableName); - - -IF @GetAllDatabases = 1 - BEGIN - INSERT INTO #DatabaseList (DatabaseName) - SELECT DB_NAME(database_id) - FROM sys.databases - WHERE user_access_desc = 'MULTI_USER' - AND state_desc = 'ONLINE' - AND database_id > 4 - AND DB_NAME(database_id) NOT LIKE 'ReportServer%' - AND DB_NAME(database_id) NOT LIKE 'rdsadmin%' - AND LOWER(name) NOT IN('dbatools', 'dbadmin', 'dbmaintenance') - AND is_distributor = 0 - OPTION ( RECOMPILE ); - - /* Skip non-readable databases in an AG - see Github issue #1160 */ - IF EXISTS (SELECT * FROM sys.all_objects o INNER JOIN sys.all_columns c ON o.object_id = c.object_id AND o.name = 'dm_hadr_availability_replica_states' AND c.name = 'role_desc') - BEGIN - SET @dsql = N'UPDATE #DatabaseList SET secondary_role_allow_connections_desc = ''NO'' WHERE DatabaseName IN ( - SELECT d.name - FROM sys.dm_hadr_availability_replica_states rs - INNER JOIN sys.databases d ON rs.replica_id = d.replica_id - INNER JOIN sys.availability_replicas r ON rs.replica_id = r.replica_id - WHERE rs.role_desc = ''SECONDARY'' - AND r.secondary_role_allow_connections_desc = ''NO'') - OPTION ( RECOMPILE );'; - EXEC sp_executesql @dsql; - - IF EXISTS (SELECT * FROM #DatabaseList WHERE secondary_role_allow_connections_desc = 'NO') - BEGIN - INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, database_name, URL, details, index_definition, - index_usage_summary, index_size_summary ) - VALUES ( 1, - 0, - N'Skipped non-readable AG secondary databases.', - N'You are running this on an AG secondary, and some of your databases are configured as non-readable when this is a secondary node.', - N'To analyze those databases, run sp_BlitzIndex on the primary, or on a readable secondary.', - 'http://FirstResponderKit.org', '', '', '', '' - ); - END; - END; - - IF @IgnoreDatabases IS NOT NULL - AND LEN(@IgnoreDatabases) > 0 - BEGIN - RAISERROR(N'Setting up filter to ignore databases', 0, 1) WITH NOWAIT; - SET @DatabaseToIgnore = ''; - - WHILE LEN(@IgnoreDatabases) > 0 - BEGIN - IF PATINDEX('%,%', @IgnoreDatabases) > 0 - BEGIN - SET @DatabaseToIgnore = SUBSTRING(@IgnoreDatabases, 0, PATINDEX('%,%',@IgnoreDatabases)) ; - - INSERT INTO #Ignore_Databases (DatabaseName, Reason) - SELECT LTRIM(RTRIM(@DatabaseToIgnore)), 'Specified in the @IgnoreDatabases parameter' - OPTION (RECOMPILE) ; - - SET @IgnoreDatabases = SUBSTRING(@IgnoreDatabases, LEN(@DatabaseToIgnore + ',') + 1, LEN(@IgnoreDatabases)) ; - END; - ELSE - BEGIN - SET @DatabaseToIgnore = @IgnoreDatabases ; - SET @IgnoreDatabases = NULL ; - - INSERT INTO #Ignore_Databases (DatabaseName, Reason) - SELECT LTRIM(RTRIM(@DatabaseToIgnore)), 'Specified in the @IgnoreDatabases parameter' - OPTION (RECOMPILE) ; - END; - END; - - END - - END; -ELSE - BEGIN - INSERT INTO #DatabaseList - ( DatabaseName ) - SELECT CASE - WHEN @DatabaseName IS NULL OR @DatabaseName = N'' - THEN DB_NAME() - ELSE @DatabaseName END; - END; - -SET @NumDatabases = (SELECT COUNT(*) FROM #DatabaseList AS D LEFT OUTER JOIN #Ignore_Databases AS I ON D.DatabaseName = I.DatabaseName WHERE I.DatabaseName IS NULL); -SET @msg = N'Number of databases to examine: ' + CAST(@NumDatabases AS NVARCHAR(50)); -RAISERROR (@msg,0,1) WITH NOWAIT; - - - -/* Running on 50+ databases can take a reaaallly long time, so we want explicit permission to do so (and only after warning about it) */ - - -BEGIN TRY - IF @NumDatabases >= 50 AND @BringThePain != 1 AND @TableName IS NULL - BEGIN - - INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, URL, details, index_definition, - index_usage_summary, index_size_summary ) - VALUES ( -1, - 0 , - @ScriptVersionName, - CASE WHEN @GetAllDatabases = 1 THEN N'All Databases' ELSE N'Database ' + QUOTENAME(@DatabaseName) + N' as of ' + CONVERT(NVARCHAR(16), GETDATE(), 121) END, - N'From Your Community Volunteers', - N'http://FirstResponderKit.org', - N'', - N'', - N'' - ); - INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, database_name, URL, details, index_definition, - index_usage_summary, index_size_summary ) - VALUES ( 1, - 0, - N'You''re trying to run sp_BlitzIndex on a server with ' + CAST(@NumDatabases AS NVARCHAR(8)) + N' databases. ', - N'Running sp_BlitzIndex on a server with 50+ databases may cause temporary problems for the server and/or user.', - N'If you''re sure you want to do this, run again with the parameter @BringThePain = 1.', - 'http://FirstResponderKit.org', - '', - '', - '', - '' - ); - - if(@OutputType <> 'NONE') - BEGIN - SELECT bir.blitz_result_id, - bir.check_id, - bir.index_sanity_id, - bir.Priority, - bir.findings_group, - bir.finding, - bir.database_name, - bir.URL, - bir.details, - bir.index_definition, - bir.secret_columns, - bir.index_usage_summary, - bir.index_size_summary, - bir.create_tsql, - bir.more_info - FROM #BlitzIndexResults AS bir; - RAISERROR('Running sp_BlitzIndex on a server with 50+ databases may cause temporary problems for the server', 12, 1); - END; - - RETURN; - - END; -END TRY -BEGIN CATCH - RAISERROR (N'Failure to execute due to number of databases.', 0,1) WITH NOWAIT; - - SELECT @msg = ERROR_MESSAGE(), - @ErrorSeverity = ERROR_SEVERITY(), - @ErrorState = ERROR_STATE(); - - RAISERROR (@msg, @ErrorSeverity, @ErrorState); - - WHILE @@trancount > 0 - ROLLBACK; - - RETURN; - END CATCH; - - -RAISERROR (N'Checking partition counts to exclude databases with over 100 partitions',0,1) WITH NOWAIT; -IF @BringThePain = 0 AND @SkipPartitions = 0 AND @TableName IS NULL - BEGIN - DECLARE partition_cursor CURSOR FOR - SELECT dl.DatabaseName - FROM #DatabaseList dl - LEFT OUTER JOIN #Ignore_Databases i ON dl.DatabaseName = i.DatabaseName - WHERE COALESCE(dl.secondary_role_allow_connections_desc, 'OK') <> 'NO' - AND i.DatabaseName IS NULL - - OPEN partition_cursor - FETCH NEXT FROM partition_cursor INTO @DatabaseName - - WHILE @@FETCH_STATUS = 0 - BEGIN - /* Count the total number of partitions */ - SET @dsql = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @RowcountOUT = SUM(1) FROM ' + QUOTENAME(@DatabaseName) + '.sys.partitions WHERE partition_number > 1 OPTION ( RECOMPILE );'; - EXEC sp_executesql @dsql, N'@RowcountOUT BIGINT OUTPUT', @RowcountOUT = @Rowcount OUTPUT; - IF @Rowcount > 100 - BEGIN - RAISERROR (N'Skipping database %s because > 100 partitions were found. To check this database, you must set @BringThePain = 1.',0,1,@DatabaseName) WITH NOWAIT; - INSERT INTO #Ignore_Databases (DatabaseName, Reason) - SELECT @DatabaseName, 'Over 100 partitions found - use @BringThePain = 1 to analyze' - END; - FETCH NEXT FROM partition_cursor INTO @DatabaseName - END; - CLOSE partition_cursor - DEALLOCATE partition_cursor - - END; - -INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, URL, details, index_definition, - index_usage_summary, index_size_summary ) -SELECT 1, 0 , - 'Database Skipped', - i.DatabaseName, - 'http://FirstResponderKit.org', - i.Reason, '', '', '' -FROM #Ignore_Databases i; - - -/* Last startup */ -IF COLUMNPROPERTY(OBJECT_ID('sys.dm_os_sys_info'),'sqlserver_start_time','ColumnID') IS NOT NULL -BEGIN - SELECT @DaysUptime = CAST(DATEDIFF(HOUR, sqlserver_start_time, GETDATE()) / 24. AS NUMERIC (23,2)) - FROM sys.dm_os_sys_info; -END -ELSE -BEGIN - SELECT @DaysUptime = CAST(DATEDIFF(HOUR, create_date, GETDATE()) / 24. AS NUMERIC (23,2)) - FROM sys.databases - WHERE database_id = 2; -END - -IF @DaysUptime = 0 OR @DaysUptime IS NULL - SET @DaysUptime = .01; - -SELECT @DaysUptimeInsertValue = 'Server: ' + (CONVERT(VARCHAR(256), (SERVERPROPERTY('ServerName')))) + ' Days Uptime: ' + RTRIM(@DaysUptime); - - -/* Permission granted or unnecessary? Ok, let's go! */ - -RAISERROR (N'Starting loop through databases',0,1) WITH NOWAIT; -DECLARE c1 CURSOR -LOCAL FAST_FORWARD -FOR -SELECT dl.DatabaseName -FROM #DatabaseList dl -LEFT OUTER JOIN #Ignore_Databases i ON dl.DatabaseName = i.DatabaseName -WHERE COALESCE(dl.secondary_role_allow_connections_desc, 'OK') <> 'NO' - AND i.DatabaseName IS NULL -ORDER BY dl.DatabaseName; - -OPEN c1; -FETCH NEXT FROM c1 INTO @DatabaseName; - WHILE @@FETCH_STATUS = 0 - -BEGIN - - RAISERROR (@LineFeed, 0, 1) WITH NOWAIT; - RAISERROR (@LineFeed, 0, 1) WITH NOWAIT; - RAISERROR (@DatabaseName, 0, 1) WITH NOWAIT; - -SELECT @DatabaseID = [database_id] -FROM sys.databases - WHERE [name] = @DatabaseName - AND user_access_desc='MULTI_USER' - AND state_desc = 'ONLINE'; - ----------------------------------------- ---STEP 1: OBSERVE THE PATIENT ---This step puts index information into temp tables. ----------------------------------------- -BEGIN TRY - BEGIN - DECLARE @d VARCHAR(19) = CONVERT(VARCHAR(19), GETDATE(), 121); - RAISERROR (N'starting at %s',0,1, @d) WITH NOWAIT; - - --Validate SQL Server Version - - IF (SELECT LEFT(@SQLServerProductVersion, - CHARINDEX('.',@SQLServerProductVersion,0)-1 - )) <= 9 - BEGIN - SET @msg=N'sp_BlitzIndex is only supported on SQL Server 2008 and higher. The version of this instance is: ' + @SQLServerProductVersion; - RAISERROR(@msg,16,1); - END; - - --Short circuit here if database name does not exist. - IF @DatabaseName IS NULL OR @DatabaseID IS NULL - BEGIN - SET @msg='Database does not exist or is not online/multi-user: cannot proceed.'; - RAISERROR(@msg,16,1); - END; - - --Validate parameters. - IF (@Mode NOT IN (0,1,2,3,4)) - BEGIN - SET @msg=N'Invalid @Mode parameter. 0=diagnose, 1=summarize, 2=index detail, 3=missing index detail, 4=diagnose detail'; - RAISERROR(@msg,16,1); - END; - - IF (@Mode <> 0 AND @TableName IS NOT NULL) - BEGIN - SET @msg=N'Setting the @Mode doesn''t change behavior if you supply @TableName. Use default @Mode=0 to see table detail.'; - RAISERROR(@msg,16,1); - END; - - IF ((@Mode <> 0 OR @TableName IS NOT NULL) AND @Filter <> 0) - BEGIN - SET @msg=N'@Filter only applies when @Mode=0 and @TableName is not specified. Please try again.'; - RAISERROR(@msg,16,1); - END; - - IF (@SchemaName IS NOT NULL AND @TableName IS NULL) - BEGIN - SET @msg='We can''t run against a whole schema! Specify a @TableName, or leave both NULL for diagnosis.'; - RAISERROR(@msg,16,1); - END; - - - IF (@TableName IS NOT NULL AND @SchemaName IS NULL) - BEGIN - SET @SchemaName=N'dbo'; - SET @msg='@SchemaName wasn''t specified-- assuming schema=dbo.'; - RAISERROR(@msg,1,1) WITH NOWAIT; - END; - - --If a table is specified, grab the object id. - --Short circuit if it doesn't exist. - IF @TableName IS NOT NULL - BEGIN - SET @dsql = N' - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @ObjectID= OBJECT_ID - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.objects AS so - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS sc on - so.schema_id=sc.schema_id - where so.type in (''U'', ''V'') - and so.name=' + QUOTENAME(@TableName,'''')+ N' - and sc.name=' + QUOTENAME(@SchemaName,'''')+ N' - /*Has a row in sys.indexes. This lets us get indexed views.*/ - and exists ( - SELECT si.name - FROM ' + QUOTENAME(@DatabaseName) + '.sys.indexes AS si - WHERE so.object_id=si.object_id) - OPTION (RECOMPILE);'; - - SET @params='@ObjectID INT OUTPUT'; - - IF @dsql IS NULL - RAISERROR('@dsql is null',16,1); - - EXEC sp_executesql @dsql, @params, @ObjectID=@ObjectID OUTPUT; - - IF @ObjectID IS NULL - BEGIN - SET @msg=N'Oh, this is awkward. I can''t find the table or indexed view you''re looking for in that database.' + CHAR(10) + - N'Please check your parameters.'; - RAISERROR(@msg,1,1); - RETURN; - END; - END; - - --set @collation - SELECT @collation=collation_name - FROM sys.databases - WHERE database_id=@DatabaseID; - - --insert columns for clustered indexes and heaps - --collect info on identity columns for this one - SET @dsql = N'/* sp_BlitzIndex */ - SET LOCK_TIMEOUT 1000; /* To fix locking bug in sys.identity_columns. See Github issue #2176. */ - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT ' + CAST(@DatabaseID AS NVARCHAR(16)) + ', - s.name, - si.object_id, - si.index_id, - sc.key_ordinal, - sc.is_included_column, - sc.is_descending_key, - sc.partition_ordinal, - c.name as column_name, - st.name as system_type_name, - c.max_length, - c.[precision], - c.[scale], - c.collation_name, - c.is_nullable, - c.is_identity, - c.is_computed, - c.is_replicated, - ' + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N'c.is_sparse' ELSE N'NULL as is_sparse' END + N', - ' + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N'c.is_filestream' ELSE N'NULL as is_filestream' END + N', - CAST(ic.seed_value AS DECIMAL(38,0)), - CAST(ic.increment_value AS DECIMAL(38,0)), - CAST(ic.last_value AS DECIMAL(38,0)), - ic.is_not_for_replication - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.indexes si - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns c ON - si.object_id=c.object_id - LEFT JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.index_columns sc ON - sc.object_id = si.object_id - and sc.index_id=si.index_id - AND sc.column_id=c.column_id - LEFT JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.identity_columns ic ON - c.object_id=ic.object_id and - c.column_id=ic.column_id - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.types st ON - c.system_type_id=st.system_type_id - AND c.user_type_id=st.user_type_id - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects AS so ON si.object_id = so.object_id - AND so.is_ms_shipped = 0 - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s ON s.schema_id = so.schema_id - WHERE si.index_id in (0,1) ' - + CASE WHEN @ObjectID IS NOT NULL - THEN N' AND si.object_id=' + CAST(@ObjectID AS NVARCHAR(30)) - ELSE N'' END - + N'OPTION (RECOMPILE);'; - - IF @dsql IS NULL - RAISERROR('@dsql is null',16,1); - - RAISERROR (N'Inserting data into #IndexColumns for clustered indexes and heaps',0,1) WITH NOWAIT; - IF @Debug = 1 - BEGIN - PRINT SUBSTRING(@dsql, 1, 4000); - PRINT SUBSTRING(@dsql, 4001, 4000); - PRINT SUBSTRING(@dsql, 8001, 4000); - PRINT SUBSTRING(@dsql, 12001, 4000); - PRINT SUBSTRING(@dsql, 16001, 4000); - PRINT SUBSTRING(@dsql, 20001, 4000); - PRINT SUBSTRING(@dsql, 24001, 4000); - PRINT SUBSTRING(@dsql, 28001, 4000); - PRINT SUBSTRING(@dsql, 32001, 4000); - PRINT SUBSTRING(@dsql, 36001, 4000); - END; - BEGIN TRY - INSERT #IndexColumns ( database_id, [schema_name], [object_id], index_id, key_ordinal, is_included_column, is_descending_key, partition_ordinal, - column_name, system_type_name, max_length, precision, scale, collation_name, is_nullable, is_identity, is_computed, - is_replicated, is_sparse, is_filestream, seed_value, increment_value, last_value, is_not_for_replication ) - EXEC sp_executesql @dsql; - END TRY - BEGIN CATCH - RAISERROR (N'Failure inserting data into #IndexColumns for clustered indexes and heaps.', 0,1) WITH NOWAIT; - - IF @dsql IS NOT NULL - BEGIN - SET @msg= 'Last @dsql: ' + @dsql; - RAISERROR(@msg, 0, 1) WITH NOWAIT; - END; - - SELECT @msg = @DatabaseName + N' database failed to process. ' + ERROR_MESSAGE(), - @ErrorSeverity = 0, @ErrorState = ERROR_STATE(); - RAISERROR (@msg,@ErrorSeverity, @ErrorState )WITH NOWAIT; - - WHILE @@trancount > 0 - ROLLBACK; - - RETURN; - END CATCH; - - - --insert columns for nonclustered indexes - --this uses a full join to sys.index_columns - --We don't collect info on identity columns here. They may be in NC indexes, but we just analyze identities in the base table. - SET @dsql = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT ' + CAST(@DatabaseID AS NVARCHAR(16)) + ', - s.name, - si.object_id, - si.index_id, - sc.key_ordinal, - sc.is_included_column, - sc.is_descending_key, - sc.partition_ordinal, - c.name as column_name, - st.name as system_type_name, - c.max_length, - c.[precision], - c.[scale], - c.collation_name, - c.is_nullable, - c.is_identity, - c.is_computed, - c.is_replicated, - ' + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N'c.is_sparse' ELSE N'NULL AS is_sparse' END + N', - ' + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N'c.is_filestream' ELSE N'NULL AS is_filestream' END + N' - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.indexes AS si - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns AS c ON - si.object_id=c.object_id - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.index_columns AS sc ON - sc.object_id = si.object_id - and sc.index_id=si.index_id - AND sc.column_id=c.column_id - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.types AS st ON - c.system_type_id=st.system_type_id - AND c.user_type_id=st.user_type_id - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects AS so ON si.object_id = so.object_id - AND so.is_ms_shipped = 0 - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s ON s.schema_id = so.schema_id - WHERE si.index_id not in (0,1) ' - + CASE WHEN @ObjectID IS NOT NULL - THEN N' AND si.object_id=' + CAST(@ObjectID AS NVARCHAR(30)) - ELSE N'' END - + N'OPTION (RECOMPILE);'; - - IF @dsql IS NULL - RAISERROR('@dsql is null',16,1); - - RAISERROR (N'Inserting data into #IndexColumns for nonclustered indexes',0,1) WITH NOWAIT; - IF @Debug = 1 - BEGIN - PRINT SUBSTRING(@dsql, 0, 4000); - PRINT SUBSTRING(@dsql, 4000, 8000); - PRINT SUBSTRING(@dsql, 8000, 12000); - PRINT SUBSTRING(@dsql, 12000, 16000); - PRINT SUBSTRING(@dsql, 16000, 20000); - PRINT SUBSTRING(@dsql, 20000, 24000); - PRINT SUBSTRING(@dsql, 24000, 28000); - PRINT SUBSTRING(@dsql, 28000, 32000); - PRINT SUBSTRING(@dsql, 32000, 36000); - PRINT SUBSTRING(@dsql, 36000, 40000); - END; - INSERT #IndexColumns ( database_id, [schema_name], [object_id], index_id, key_ordinal, is_included_column, is_descending_key, partition_ordinal, - column_name, system_type_name, max_length, precision, scale, collation_name, is_nullable, is_identity, is_computed, - is_replicated, is_sparse, is_filestream ) - EXEC sp_executesql @dsql; - - SET @dsql = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT ' + CAST(@DatabaseID AS NVARCHAR(10)) + N' AS database_id, - so.object_id, - si.index_id, - si.type, - @i_DatabaseName AS database_name, - COALESCE(sc.NAME, ''Unknown'') AS [schema_name], - COALESCE(so.name, ''Unknown'') AS [object_name], - COALESCE(si.name, ''Unknown'') AS [index_name], - CASE WHEN so.[type] = CAST(''V'' AS CHAR(2)) THEN 1 ELSE 0 END, - si.is_unique, - si.is_primary_key, - si.is_unique_constraint, - CASE when si.type = 3 THEN 1 ELSE 0 END AS is_XML, - CASE when si.type = 4 THEN 1 ELSE 0 END AS is_spatial, - CASE when si.type = 6 THEN 1 ELSE 0 END AS is_NC_columnstore, - CASE when si.type = 5 then 1 else 0 end as is_CX_columnstore, - CASE when si.data_space_id = 0 then 1 else 0 end as is_in_memory_oltp, - si.is_disabled, - si.is_hypothetical, - si.is_padded, - si.fill_factor,' - + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N' - CASE WHEN si.filter_definition IS NOT NULL THEN si.filter_definition - ELSE N'''' - END AS filter_definition' ELSE N''''' AS filter_definition' END - + CASE - WHEN @OptimizeForSequentialKey = 1 - THEN N', si.optimize_for_sequential_key' - ELSE N', CONVERT(BIT, NULL) AS optimize_for_sequential_key' - END - + N', - ISNULL(us.user_seeks, 0), - ISNULL(us.user_scans, 0), - ISNULL(us.user_lookups, 0), - ISNULL(us.user_updates, 0), - us.last_user_seek, - us.last_user_scan, - us.last_user_lookup, - us.last_user_update, - so.create_date, - so.modify_date - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.indexes AS si WITH (NOLOCK) - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects AS so WITH (NOLOCK) ON si.object_id = so.object_id - AND so.is_ms_shipped = 0 /*Exclude objects shipped by Microsoft*/ - AND so.type <> ''TF'' /*Exclude table valued functions*/ - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas sc ON so.schema_id = sc.schema_id - LEFT JOIN sys.dm_db_index_usage_stats AS us WITH (NOLOCK) ON si.[object_id] = us.[object_id] - AND si.index_id = us.index_id - AND us.database_id = ' + CAST(@DatabaseID AS NVARCHAR(10)) + N' - WHERE si.[type] IN ( 0, 1, 2, 3, 4, 5, 6 ) - /* Heaps, clustered, nonclustered, XML, spatial, Cluster Columnstore, NC Columnstore */ ' + - CASE WHEN @TableName IS NOT NULL THEN N' and so.name=' + QUOTENAME(@TableName,N'''') + N' ' ELSE N'' END + - CASE WHEN ( @IncludeInactiveIndexes = 0 - AND @Mode IN (0, 4) - AND @TableName IS NULL ) - THEN N'AND ( us.user_seeks + us.user_scans + us.user_lookups + us.user_updates ) > 0' - ELSE N'' - END - + N'OPTION ( RECOMPILE ); - '; - IF @dsql IS NULL - RAISERROR('@dsql is null',16,1); - - RAISERROR (N'Inserting data into #IndexSanity',0,1) WITH NOWAIT; - IF @Debug = 1 - BEGIN - PRINT SUBSTRING(@dsql, 0, 4000); - PRINT SUBSTRING(@dsql, 4000, 8000); - PRINT SUBSTRING(@dsql, 8000, 12000); - PRINT SUBSTRING(@dsql, 12000, 16000); - PRINT SUBSTRING(@dsql, 16000, 20000); - PRINT SUBSTRING(@dsql, 20000, 24000); - PRINT SUBSTRING(@dsql, 24000, 28000); - PRINT SUBSTRING(@dsql, 28000, 32000); - PRINT SUBSTRING(@dsql, 32000, 36000); - PRINT SUBSTRING(@dsql, 36000, 40000); - END; - INSERT #IndexSanity ( [database_id], [object_id], [index_id], [index_type], [database_name], [schema_name], [object_name], - index_name, is_indexed_view, is_unique, is_primary_key, is_unique_constraint, is_XML, is_spatial, is_NC_columnstore, is_CX_columnstore, is_in_memory_oltp, - is_disabled, is_hypothetical, is_padded, fill_factor, filter_definition, [optimize_for_sequential_key], user_seeks, user_scans, - user_lookups, user_updates, last_user_seek, last_user_scan, last_user_lookup, last_user_update, - create_date, modify_date ) - EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; - - - RAISERROR (N'Checking partition count',0,1) WITH NOWAIT; - IF @BringThePain = 0 AND @SkipPartitions = 0 AND @TableName IS NULL - BEGIN - /* Count the total number of partitions */ - SET @dsql = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @RowcountOUT = SUM(1) FROM ' + QUOTENAME(@DatabaseName) + '.sys.partitions WHERE partition_number > 1 OPTION ( RECOMPILE );'; - EXEC sp_executesql @dsql, N'@RowcountOUT BIGINT OUTPUT', @RowcountOUT = @Rowcount OUTPUT; - IF @Rowcount > 100 - BEGIN - RAISERROR (N'Setting @SkipPartitions = 1 because > 100 partitions were found. To check them, you must set @BringThePain = 1.',0,1) WITH NOWAIT; - SET @SkipPartitions = 1; - INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, URL, details, index_definition, - index_usage_summary, index_size_summary ) - VALUES ( 1, 0 , - 'Some Checks Were Skipped', - '@SkipPartitions Forced to 1', - 'http://FirstResponderKit.org', CAST(@Rowcount AS NVARCHAR(50)) + ' partitions found. To analyze them, use @BringThePain = 1.', 'We try to keep things quick - and warning, running @BringThePain = 1 can take tens of minutes.', '', '' - ); - END; - END; - - - - IF (@SkipPartitions = 0) - BEGIN - IF (SELECT LEFT(@SQLServerProductVersion, - CHARINDEX('.',@SQLServerProductVersion,0)-1 )) <= 2147483647 --Make change here - BEGIN - - RAISERROR (N'Preferring non-2012 syntax with LEFT JOIN to sys.dm_db_index_operational_stats',0,1) WITH NOWAIT; - - --NOTE: If you want to use the newer syntax for 2012+, you'll have to change 2147483647 to 11 on line ~819 - --This change was made because on a table with lots of paritions, the OUTER APPLY was crazy slow. - - -- get relevant columns from sys.dm_db_partition_stats, sys.partitions and sys.objects - DROP TABLE if exists #dm_db_partition_stats_etc - create table #dm_db_partition_stats_etc - ( - database_id smallint not null - , object_id int not null - , sname sysname NULL - , index_id int - , partition_number int - , partition_id bigint - , row_count bigint - , reserved_MB bigint - , reserved_LOB_MB bigint - , reserved_row_overflow_MB bigint - , lock_escalation_desc nvarchar(60) - , data_compression_desc nvarchar(60) - ) - - -- get relevant info from sys.dm_db_index_operational_stats - drop TABLE if exists #dm_db_index_operational_stats - create table #dm_db_index_operational_stats - ( - database_id smallint not null - , object_id int not null - , index_id int - , partition_number int - , hobt_id bigint - , leaf_insert_count bigint - , leaf_delete_count bigint - , leaf_update_count bigint - , range_scan_count bigint - , singleton_lookup_count bigint - , forwarded_fetch_count bigint - , lob_fetch_in_pages bigint - , lob_fetch_in_bytes bigint - , row_overflow_fetch_in_pages bigint - , row_overflow_fetch_in_bytes bigint - , row_lock_count bigint - , row_lock_wait_count bigint - , row_lock_wait_in_ms bigint - , page_lock_count bigint - , page_lock_wait_count bigint - , page_lock_wait_in_ms bigint - , index_lock_promotion_attempt_count bigint - , index_lock_promotion_count bigint - , page_latch_wait_count bigint - , page_latch_wait_in_ms bigint - , page_io_latch_wait_count bigint - , page_io_latch_wait_in_ms bigint - ) - - SET @dsql = N' - DECLARE @d VARCHAR(19) = CONVERT(VARCHAR(19), GETDATE(), 121) - RAISERROR (N''start getting data into #dm_db_partition_stats_etc at %s'',0,1, @d) WITH NOWAIT; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - INSERT INTO #dm_db_partition_stats_etc - ( - database_id, object_id, sname, index_id, partition_number, partition_id, row_count, reserved_MB, reserved_LOB_MB, reserved_row_overflow_MB, lock_escalation_desc, data_compression_desc - ) - SELECT ' + CAST(@DatabaseID AS NVARCHAR(10)) + N' AS database_id, - ps.object_id, - s.name as sname, - ps.index_id, - ps.partition_number, - ps.partition_id, - ps.row_count, - ps.reserved_page_count * 8. / 1024. AS reserved_MB, - ps.lob_reserved_page_count * 8. / 1024. AS reserved_LOB_MB, - ps.row_overflow_reserved_page_count * 8. / 1024. AS reserved_row_overflow_MB, - le.lock_escalation_desc, - ' + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N'par.data_compression_desc ' ELSE N'null as data_compression_desc ' END + N' -'; - - SET @dsql = @dsql + N' - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_partition_stats AS ps - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partitions AS par on ps.partition_id=par.partition_id - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects AS so ON ps.object_id = so.object_id - AND so.is_ms_shipped = 0 /*Exclude objects shipped by Microsoft*/ - AND so.type <> ''TF'' /*Exclude table valued functions*/ - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s ON s.schema_id = so.schema_id - OUTER APPLY (SELECT st.lock_escalation_desc - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.tables st - WHERE st.object_id = ps.object_id - AND ps.index_id < 2 ) le - WHERE 1=1 - ' + CASE WHEN @ObjectID IS NOT NULL THEN N'AND so.object_id=' + CAST(@ObjectID AS NVARCHAR(30)) + N' ' ELSE N' ' END + N' - ' + CASE WHEN @Filter = 2 THEN N'AND ps.reserved_page_count * 8./1024. > ' + CAST(@FilterMB AS NVARCHAR(5)) + N' ' ELSE N' ' END + N' - GROUP BY ps.object_id, - s.name, - ps.index_id, - ps.partition_number, - ps.partition_id, - ps.row_count, - ps.reserved_page_count, - ps.lob_reserved_page_count, - ps.row_overflow_reserved_page_count, - le.lock_escalation_desc, - ' + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N'par.data_compression_desc ' ELSE N'null as data_compression_desc ' END + N' - ORDER BY ps.object_id, ps.index_id, ps.partition_number - /*OPTION ( RECOMPILE );*/ - OPTION ( RECOMPILE , min_grant_percent = 1); - - SET @d = CONVERT(VARCHAR(19), GETDATE(), 121) - RAISERROR (N''start getting data into #dm_db_index_operational_stats at %s.'',0,1, @d) WITH NOWAIT; - - insert into #dm_db_index_operational_stats - ( - database_id - , object_id - , index_id - , partition_number - , hobt_id - , leaf_insert_count - , leaf_delete_count - , leaf_update_count - , range_scan_count - , singleton_lookup_count - , forwarded_fetch_count - , lob_fetch_in_pages - , lob_fetch_in_bytes - , row_overflow_fetch_in_pages - , row_overflow_fetch_in_bytes - , row_lock_count - , row_lock_wait_count - , row_lock_wait_in_ms - , page_lock_count - , page_lock_wait_count - , page_lock_wait_in_ms - , index_lock_promotion_attempt_count - , index_lock_promotion_count - , page_latch_wait_count - , page_latch_wait_in_ms - , page_io_latch_wait_count - , page_io_latch_wait_in_ms - ) - - select os.database_id - , os.object_id - , os.index_id - , os.partition_number - , os.hobt_id - , os.leaf_insert_count - , os.leaf_delete_count - , os.leaf_update_count - , os.range_scan_count - , os.singleton_lookup_count - , os.forwarded_fetch_count - , os.lob_fetch_in_pages - , os.lob_fetch_in_bytes - , os.row_overflow_fetch_in_pages - , os.row_overflow_fetch_in_bytes - , os.row_lock_count - , os.row_lock_wait_count - , os.row_lock_wait_in_ms - , os.page_lock_count - , os.page_lock_wait_count - , os.page_lock_wait_in_ms - , os.index_lock_promotion_attempt_count - , os.index_lock_promotion_count - , os.page_latch_wait_count - , os.page_latch_wait_in_ms - , os.page_io_latch_wait_count - , os.page_io_latch_wait_in_ms - from ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_index_operational_stats('+ CAST(@DatabaseID AS NVARCHAR(10)) +', NULL, NULL,NULL) AS os - OPTION ( RECOMPILE , min_grant_percent = 1); - - SET @d = CONVERT(VARCHAR(19), GETDATE(), 121) - RAISERROR (N''finished getting data into #dm_db_index_operational_stats at %s.'',0,1, @d) WITH NOWAIT; - '; - END; - ELSE - BEGIN - RAISERROR (N'Using 2012 syntax to query sys.dm_db_index_operational_stats',0,1) WITH NOWAIT; - --This is the syntax that will be used if you change 2147483647 to 11 on line ~819. - --If you have a lot of paritions and this suddenly starts running for a long time, change it back. - SET @dsql = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT ' + CAST(@DatabaseID AS NVARCHAR(10)) + N' AS database_id, - ps.object_id, - s.name, - ps.index_id, - ps.partition_number, - ps.row_count, - ps.reserved_page_count * 8. / 1024. AS reserved_MB, - ps.lob_reserved_page_count * 8. / 1024. AS reserved_LOB_MB, - ps.row_overflow_reserved_page_count * 8. / 1024. AS reserved_row_overflow_MB, - le.lock_escalation_desc, - ' + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N'par.data_compression_desc ' ELSE N'null as data_compression_desc' END + N', - SUM(os.leaf_insert_count), - SUM(os.leaf_delete_count), - SUM(os.leaf_update_count), - SUM(os.range_scan_count), - SUM(os.singleton_lookup_count), - SUM(os.forwarded_fetch_count), - SUM(os.lob_fetch_in_pages), - SUM(os.lob_fetch_in_bytes), - SUM(os.row_overflow_fetch_in_pages), - SUM(os.row_overflow_fetch_in_bytes), - SUM(os.row_lock_count), - SUM(os.row_lock_wait_count), - SUM(os.row_lock_wait_in_ms), - SUM(os.page_lock_count), - SUM(os.page_lock_wait_count), - SUM(os.page_lock_wait_in_ms), - SUM(os.index_lock_promotion_attempt_count), - SUM(os.index_lock_promotion_count), - SUM(os.page_latch_wait_count), - SUM(os.page_latch_wait_in_ms), - SUM(os.page_io_latch_wait_count), - SUM(os.page_io_latch_wait_in_ms)'; - - /* Get columnstore dictionary size - more info: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2585 */ - IF EXISTS (SELECT * FROM sys.all_objects WHERE name = 'column_store_dictionaries') - SET @dsql = @dsql + N' COALESCE((SELECT SUM (on_disk_size / 1024.0 / 1024) FROM ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_dictionaries dict WHERE dict.partition_id = ps.partition_id),0) AS reserved_dictionary_MB '; - ELSE - SET @dsql = @dsql + N' 0 AS reserved_dictionary_MB '; - - - SET @dsql = @dsql + N' - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_partition_stats AS ps - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partitions AS par on ps.partition_id=par.partition_id - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects AS so ON ps.object_id = so.object_id - AND so.is_ms_shipped = 0 /*Exclude objects shipped by Microsoft*/ - AND so.type <> ''TF'' /*Exclude table valued functions*/ - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s ON s.schema_id = so.schema_id - OUTER APPLY ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_index_operational_stats(' - + CAST(@DatabaseID AS NVARCHAR(10)) + N', ps.object_id, ps.index_id,ps.partition_number) AS os - OUTER APPLY (SELECT st.lock_escalation_desc - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.tables st - WHERE st.object_id = ps.object_id - AND ps.index_id < 2 ) le - WHERE 1=1 - ' + CASE WHEN @ObjectID IS NOT NULL THEN N'AND so.object_id=' + CAST(@ObjectID AS NVARCHAR(30)) + N' ' ELSE N' ' END + N' - ' + CASE WHEN @Filter = 2 THEN N'AND ps.reserved_page_count * 8./1024. > ' + CAST(@FilterMB AS NVARCHAR(5)) + N' ' ELSE N' ' END + ' - GROUP BY ps.object_id, - s.name, - ps.index_id, - ps.partition_number, - ps.partition_id, - ps.row_count, - ps.reserved_page_count, - ps.lob_reserved_page_count, - ps.row_overflow_reserved_page_count, - le.lock_escalation_desc, - ' + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N'par.data_compression_desc ' ELSE N'null as data_compression_desc ' END + N' - ORDER BY ps.object_id, ps.index_id, ps.partition_number - OPTION ( RECOMPILE ); - '; - END; - - IF @dsql IS NULL - RAISERROR('@dsql is null',16,1); - - RAISERROR (N'Inserting data into #IndexPartitionSanity',0,1) WITH NOWAIT; - IF @Debug = 1 - BEGIN - PRINT SUBSTRING(@dsql, 0, 4000); - PRINT SUBSTRING(@dsql, 4000, 8000); - PRINT SUBSTRING(@dsql, 8000, 12000); - PRINT SUBSTRING(@dsql, 12000, 16000); - PRINT SUBSTRING(@dsql, 16000, 20000); - PRINT SUBSTRING(@dsql, 20000, 24000); - PRINT SUBSTRING(@dsql, 24000, 28000); - PRINT SUBSTRING(@dsql, 28000, 32000); - PRINT SUBSTRING(@dsql, 32000, 36000); - PRINT SUBSTRING(@dsql, 36000, 40000); - END; - EXEC sp_executesql @dsql; - INSERT #IndexPartitionSanity ( [database_id], - [object_id], - [schema_name], - index_id, - partition_number, - row_count, - reserved_MB, - reserved_LOB_MB, - reserved_row_overflow_MB, - lock_escalation_desc, - data_compression_desc, - leaf_insert_count, - leaf_delete_count, - leaf_update_count, - range_scan_count, - singleton_lookup_count, - forwarded_fetch_count, - lob_fetch_in_pages, - lob_fetch_in_bytes, - row_overflow_fetch_in_pages, - row_overflow_fetch_in_bytes, - row_lock_count, - row_lock_wait_count, - row_lock_wait_in_ms, - page_lock_count, - page_lock_wait_count, - page_lock_wait_in_ms, - index_lock_promotion_attempt_count, - index_lock_promotion_count, - page_latch_wait_count, - page_latch_wait_in_ms, - page_io_latch_wait_count, - page_io_latch_wait_in_ms, - reserved_dictionary_MB) - select h.database_id, h.object_id, h.sname, h.index_id, h.partition_number, h.row_count, h.reserved_MB, h.reserved_LOB_MB, h.reserved_row_overflow_MB, h.lock_escalation_desc, h.data_compression_desc, - SUM(os.leaf_insert_count), - SUM(os.leaf_delete_count), - SUM(os.leaf_update_count), - SUM(os.range_scan_count), - SUM(os.singleton_lookup_count), - SUM(os.forwarded_fetch_count), - SUM(os.lob_fetch_in_pages), - SUM(os.lob_fetch_in_bytes), - SUM(os.row_overflow_fetch_in_pages), - SUM(os.row_overflow_fetch_in_bytes), - SUM(os.row_lock_count), - SUM(os.row_lock_wait_count), - SUM(os.row_lock_wait_in_ms), - SUM(os.page_lock_count), - SUM(os.page_lock_wait_count), - SUM(os.page_lock_wait_in_ms), - SUM(os.index_lock_promotion_attempt_count), - SUM(os.index_lock_promotion_count), - SUM(os.page_latch_wait_count), - SUM(os.page_latch_wait_in_ms), - SUM(os.page_io_latch_wait_count), - SUM(os.page_io_latch_wait_in_ms) - ,COALESCE((SELECT SUM (dict.on_disk_size / 1024.0 / 1024) FROM sys.column_store_dictionaries dict WHERE dict.partition_id = h.partition_id),0) AS reserved_dictionary_MB - from #dm_db_partition_stats_etc h - left JOIN #dm_db_index_operational_stats as os ON - h.object_id=os.object_id and h.index_id=os.index_id and h.partition_number=os.partition_number - group by h.database_id, h.object_id, h.sname, h.index_id, h.partition_number, h.partition_id, h.row_count, h.reserved_MB, h.reserved_LOB_MB, h.reserved_row_overflow_MB, h.lock_escalation_desc, h.data_compression_desc - - END; --End Check For @SkipPartitions = 0 - - - - RAISERROR (N'Inserting data into #MissingIndexes',0,1) WITH NOWAIT; - SET @dsql=N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' - - - SET @dsql = @dsql + 'WITH ColumnNamesWithDataTypes AS(SELECT id.index_handle,id.object_id,cn.IndexColumnType,STUFF((SELECT '', '' + cn_inner.ColumnName + '' '' + - N'' {'' + CASE WHEN ty.name IN ( ''varchar'', ''char'' ) THEN ty.name + ''('' + CASE WHEN co.max_length = -1 THEN ''max'' ELSE CAST(co.max_length AS VARCHAR(25)) END + '')'' - WHEN ty.name IN ( ''nvarchar'', ''nchar'' ) THEN ty.name + ''('' + CASE WHEN co.max_length = -1 THEN ''max'' ELSE CAST(co.max_length / 2 AS VARCHAR(25)) END + '')'' - WHEN ty.name IN ( ''decimal'', ''numeric'' ) THEN ty.name + ''('' + CAST(co.precision AS VARCHAR(25)) + '', '' + CAST(co.scale AS VARCHAR(25)) + '')'' - WHEN ty.name IN ( ''datetime2'' ) THEN ty.name + ''('' + CAST(co.scale AS VARCHAR(25)) + '')'' - ELSE ty.name END + ''}'' - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_details AS id_inner - CROSS APPLY( - SELECT LTRIM(RTRIM(v.value(''(./text())[1]'', ''varchar(max)''))) AS ColumnName, ''Equality'' AS IndexColumnType - FROM (VALUES (CONVERT(XML, N'''' + REPLACE((SELECT CAST(id_inner.equality_columns AS nvarchar(max)) FOR XML PATH('''')), N'','', N'''') + N''''))) x(n) - CROSS APPLY n.nodes(''x'') node(v) - UNION ALL - SELECT LTRIM(RTRIM(v.value(N''(./text())[1]'', ''varchar(max)''))) AS ColumnName, ''Inequality'' AS IndexColumnType - FROM (VALUES (CONVERT(XML, N'''' + REPLACE((SELECT CAST(id_inner.inequality_columns AS nvarchar(max)) FOR XML PATH('''')), N'','', N'''') + N''''))) x(n) - CROSS APPLY n.nodes(''x'') node(v) - UNION ALL - SELECT LTRIM(RTRIM(v.value(''(./text())[1]'', ''varchar(max)''))) AS ColumnName, ''Included'' AS IndexColumnType - FROM (VALUES (CONVERT(XML, N'''' + REPLACE((SELECT CAST(id_inner.included_columns AS nvarchar(max)) FOR XML PATH('''')), N'','', N'''') + N''''))) x(n) - CROSS APPLY n.nodes(''x'') node(v) - )AS cn_inner' - + /*split the string otherwise dsql cuts some of it out*/ - ' JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns AS co ON co.object_id = id_inner.object_id AND ''['' + co.name + '']'' = cn_inner.ColumnName - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.types AS ty ON ty.user_type_id = co.user_type_id - WHERE id_inner.index_handle = id.index_handle - AND id_inner.object_id = id.object_id - AND cn_inner.IndexColumnType = cn.IndexColumnType - FOR XML PATH('''') - ),1,1,'''') AS ReplaceColumnNames - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_details AS id - CROSS APPLY( - SELECT LTRIM(RTRIM(v.value(''(./text())[1]'', ''varchar(max)''))) AS ColumnName, ''Equality'' AS IndexColumnType - FROM (VALUES (CONVERT(XML, N'''' + REPLACE((SELECT CAST(id.equality_columns AS nvarchar(max)) FOR XML PATH('''')), N'','', N'''') + N''''))) x(n) - CROSS APPLY n.nodes(''x'') node(v) - UNION ALL - SELECT LTRIM(RTRIM(v.value(''(./text())[1]'', ''varchar(max)''))) AS ColumnName, ''Inequality'' AS IndexColumnType - FROM (VALUES (CONVERT(XML, N'''' + REPLACE((SELECT CAST(id.inequality_columns AS nvarchar(max)) FOR XML PATH('''')), N'','', N'''') + N''''))) x(n) - CROSS APPLY n.nodes(''x'') node(v) - UNION ALL - SELECT LTRIM(RTRIM(v.value(''(./text())[1]'', ''varchar(max)''))) AS ColumnName, ''Included'' AS IndexColumnType - FROM (VALUES (CONVERT(XML, N'''' + REPLACE((SELECT CAST(id.included_columns AS nvarchar(max)) FOR XML PATH('''')), N'','', N'''') + N''''))) x(n) - CROSS APPLY n.nodes(''x'') node(v) - )AS cn - GROUP BY id.index_handle,id.object_id,cn.IndexColumnType - ) - SELECT id.database_id, id.object_id, @i_DatabaseName, sc.[name], so.[name], id.statement , gs.avg_total_user_cost, - gs.avg_user_impact, gs.user_seeks, gs.user_scans, gs.unique_compiles, id.equality_columns, id.inequality_columns, id.included_columns, - ( - SELECT ColumnNamesWithDataTypes.ReplaceColumnNames - FROM ColumnNamesWithDataTypes WHERE ColumnNamesWithDataTypes.index_handle = id.index_handle - AND ColumnNamesWithDataTypes.object_id = id.object_id - AND ColumnNamesWithDataTypes.IndexColumnType = ''Equality'' - ) AS equality_columns_with_data_type - ,( - SELECT ColumnNamesWithDataTypes.ReplaceColumnNames - FROM ColumnNamesWithDataTypes WHERE ColumnNamesWithDataTypes.index_handle = id.index_handle - AND ColumnNamesWithDataTypes.object_id = id.object_id - AND ColumnNamesWithDataTypes.IndexColumnType = ''Inequality'' - ) AS inequality_columns_with_data_type - ,( - SELECT ColumnNamesWithDataTypes.ReplaceColumnNames - FROM ColumnNamesWithDataTypes WHERE ColumnNamesWithDataTypes.index_handle = id.index_handle - AND ColumnNamesWithDataTypes.object_id = id.object_id - AND ColumnNamesWithDataTypes.IndexColumnType = ''Included'' - ) AS included_columns_with_data_type ' - - /* Get the sample query plan if it's available, and if there are less than 1,000 rows in the DMV: */ - IF NOT EXISTS - ( - SELECT - 1/0 - FROM sys.all_objects AS o - WHERE o.name = 'dm_db_missing_index_group_stats_query' - ) - SELECT - @dsql += N' , NULL AS sample_query_plan ' - ELSE - BEGIN - /* The DMV is only supposed to have 600 rows in it. If it's got more, - they could see performance slowdowns - see Github #3085. */ - DECLARE @MissingIndexPlans BIGINT; - SET @StringToExecute = N'SELECT @MissingIndexPlans = COUNT(*) FROM ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_group_stats_query;' - EXEC sp_executesql @StringToExecute, N'@MissingIndexPlans BIGINT OUT', @MissingIndexPlans OUT; - - IF @MissingIndexPlans > 1000 - BEGIN - SELECT @dsql += N' , NULL AS sample_query_plan /* Over 1000 plans found, skipping */ '; - RAISERROR (N'Over 1000 plans found in sys.dm_db_missing_index_group_stats_query - your SQL Server is hitting a bug: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/3085',0,1) WITH NOWAIT; - END - ELSE - SELECT - @dsql += N' - , sample_query_plan = - ( - SELECT TOP (1) - p.query_plan - FROM sys.dm_db_missing_index_group_stats gs - CROSS APPLY - ( - SELECT TOP (1) - s.plan_handle - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_group_stats_query q - INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.dm_exec_query_stats s - ON q.query_plan_hash = s.query_plan_hash - WHERE gs.group_handle = q.group_handle - ORDER BY (q.user_seeks + q.user_scans) DESC, s.total_logical_reads DESC - ) q2 - CROSS APPLY sys.dm_exec_query_plan(q2.plan_handle) p - WHERE ig.index_group_handle = gs.group_handle - ) ' - END - - - - SET @dsql = @dsql + N'FROM ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_groups ig - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_details id ON ig.index_handle = id.index_handle - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_group_stats gs ON ig.index_group_handle = gs.group_handle - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects so on - id.object_id=so.object_id - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas sc on - so.schema_id=sc.schema_id - WHERE id.database_id = ' + CAST(@DatabaseID AS NVARCHAR(30)) + ' - ' + CASE WHEN @ObjectID IS NULL THEN N'' - ELSE N'and id.object_id=' + CAST(@ObjectID AS NVARCHAR(30)) - END + - N'OPTION (RECOMPILE);'; - - IF @dsql IS NULL - RAISERROR('@dsql is null',16,1); - IF @Debug = 1 - BEGIN - PRINT SUBSTRING(@dsql, 0, 4000); - PRINT SUBSTRING(@dsql, 4000, 8000); - PRINT SUBSTRING(@dsql, 8000, 12000); - PRINT SUBSTRING(@dsql, 12000, 16000); - PRINT SUBSTRING(@dsql, 16000, 20000); - PRINT SUBSTRING(@dsql, 20000, 24000); - PRINT SUBSTRING(@dsql, 24000, 28000); - PRINT SUBSTRING(@dsql, 28000, 32000); - PRINT SUBSTRING(@dsql, 32000, 36000); - PRINT SUBSTRING(@dsql, 36000, 40000); - END; - INSERT #MissingIndexes ( [database_id], [object_id], [database_name], [schema_name], [table_name], [statement], avg_total_user_cost, - avg_user_impact, user_seeks, user_scans, unique_compiles, equality_columns, - inequality_columns, included_columns, equality_columns_with_data_type, inequality_columns_with_data_type, - included_columns_with_data_type, sample_query_plan) - EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; - - SET @dsql = N' - SELECT DB_ID(N' + QUOTENAME(@DatabaseName,'''') + N') AS [database_id], - @i_DatabaseName AS database_name, - s.name, - fk_object.name AS foreign_key_name, - parent_object.[object_id] AS parent_object_id, - parent_object.name AS parent_object_name, - referenced_object.[object_id] AS referenced_object_id, - referenced_object.name AS referenced_object_name, - fk.is_disabled, - fk.is_not_trusted, - fk.is_not_for_replication, - parent.fk_columns, - referenced.fk_columns, - [update_referential_action_desc], - [delete_referential_action_desc] - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.foreign_keys fk - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects fk_object ON fk.object_id=fk_object.object_id - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects parent_object ON fk.parent_object_id=parent_object.object_id - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects referenced_object ON fk.referenced_object_id=referenced_object.object_id - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s ON fk.schema_id=s.schema_id - CROSS APPLY ( SELECT STUFF( (SELECT N'', '' + c_parent.name AS fk_columns - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.foreign_key_columns fkc - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns c_parent ON fkc.parent_object_id=c_parent.[object_id] - AND fkc.parent_column_id=c_parent.column_id - WHERE fk.parent_object_id=fkc.parent_object_id - AND fk.[object_id]=fkc.constraint_object_id - ORDER BY fkc.constraint_column_id - FOR XML PATH('''') , - TYPE).value(''.'', ''nvarchar(max)''), 1, 1, '''')/*This is how we remove the first comma*/ ) parent ( fk_columns ) - CROSS APPLY ( SELECT STUFF( (SELECT N'', '' + c_referenced.name AS fk_columns - FROM ' + QUOTENAME(@DatabaseName) + N'.sys. foreign_key_columns fkc - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns c_referenced ON fkc.referenced_object_id=c_referenced.[object_id] - AND fkc.referenced_column_id=c_referenced.column_id - WHERE fk.referenced_object_id=fkc.referenced_object_id - and fk.[object_id]=fkc.constraint_object_id - ORDER BY fkc.constraint_column_id /*order by col name, we don''t have anything better*/ - FOR XML PATH('''') , - TYPE).value(''.'', ''nvarchar(max)''), 1, 1, '''') ) referenced ( fk_columns ) - ' + CASE WHEN @ObjectID IS NOT NULL THEN - 'WHERE fk.parent_object_id=' + CAST(@ObjectID AS NVARCHAR(30)) + N' OR fk.referenced_object_id=' + CAST(@ObjectID AS NVARCHAR(30)) + N' ' - ELSE N' ' END + ' - ORDER BY parent_object_name, foreign_key_name - OPTION (RECOMPILE);'; - IF @dsql IS NULL - RAISERROR('@dsql is null',16,1); - - RAISERROR (N'Inserting data into #ForeignKeys',0,1) WITH NOWAIT; - IF @Debug = 1 - BEGIN - PRINT SUBSTRING(@dsql, 0, 4000); - PRINT SUBSTRING(@dsql, 4000, 8000); - PRINT SUBSTRING(@dsql, 8000, 12000); - PRINT SUBSTRING(@dsql, 12000, 16000); - PRINT SUBSTRING(@dsql, 16000, 20000); - PRINT SUBSTRING(@dsql, 20000, 24000); - PRINT SUBSTRING(@dsql, 24000, 28000); - PRINT SUBSTRING(@dsql, 28000, 32000); - PRINT SUBSTRING(@dsql, 32000, 36000); - PRINT SUBSTRING(@dsql, 36000, 40000); - END; - INSERT #ForeignKeys ( [database_id], [database_name], [schema_name], foreign_key_name, parent_object_id,parent_object_name, referenced_object_id, referenced_object_name, - is_disabled, is_not_trusted, is_not_for_replication, parent_fk_columns, referenced_fk_columns, - [update_referential_action_desc], [delete_referential_action_desc] ) - EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; - - - SET @dsql = N' - SELECT - DB_ID(N' + QUOTENAME(@DatabaseName,'''') + N') AS [database_id], - @i_DatabaseName AS database_name, - foreign_key_schema = - s.name, - foreign_key_name = - fk.name, - foreign_key_table = - OBJECT_NAME(fk.parent_object_id, DB_ID(N' + QUOTENAME(@DatabaseName,'''') + N')), - fk.parent_object_id, - foreign_key_referenced_table = - OBJECT_NAME(fk.referenced_object_id, DB_ID(N' + QUOTENAME(@DatabaseName,'''') + N')), - fk.referenced_object_id - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.foreign_keys fk - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s - ON s.schema_id = fk.schema_id - WHERE fk.is_disabled = 0 - AND EXISTS - ( - SELECT - 1/0 - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.foreign_key_columns fkc - WHERE fkc.constraint_object_id = fk.object_id - AND NOT EXISTS - ( - SELECT - 1/0 - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.index_columns ic - WHERE ic.object_id = fkc.parent_object_id - AND ic.column_id = fkc.parent_column_id - AND ic.index_column_id = fkc.constraint_column_id - ) - ) - OPTION (RECOMPILE);' - IF @dsql IS NULL - RAISERROR('@dsql is null',16,1); - - RAISERROR (N'Inserting data into #ForeignKeys',0,1) WITH NOWAIT; - IF @Debug = 1 - BEGIN - PRINT SUBSTRING(@dsql, 0, 4000); - PRINT SUBSTRING(@dsql, 4000, 8000); - PRINT SUBSTRING(@dsql, 8000, 12000); - PRINT SUBSTRING(@dsql, 12000, 16000); - PRINT SUBSTRING(@dsql, 16000, 20000); - PRINT SUBSTRING(@dsql, 20000, 24000); - PRINT SUBSTRING(@dsql, 24000, 28000); - PRINT SUBSTRING(@dsql, 28000, 32000); - PRINT SUBSTRING(@dsql, 32000, 36000); - PRINT SUBSTRING(@dsql, 36000, 40000); - END; - - INSERT - #UnindexedForeignKeys - ( - database_id, - database_name, - schema_name, - foreign_key_name, - parent_object_name, - parent_object_id, - referenced_object_name, - referenced_object_id - ) - EXEC sys.sp_executesql - @dsql, - N'@i_DatabaseName sysname', - @DatabaseName; - - - IF @SkipStatistics = 0 /* AND DB_NAME() = @DatabaseName /* Can only get stats in the current database - see https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/1947 */ */ - BEGIN - IF ((PARSENAME(@SQLServerProductVersion, 4) >= 12) - OR (PARSENAME(@SQLServerProductVersion, 4) = 11 AND PARSENAME(@SQLServerProductVersion, 2) >= 3000) - OR (PARSENAME(@SQLServerProductVersion, 4) = 10 AND PARSENAME(@SQLServerProductVersion, 3) = 50 AND PARSENAME(@SQLServerProductVersion, 2) >= 2500)) - 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, - 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) - 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, - ISNULL(i.name, ''System Or User Statistic'') AS index_name, - ca.column_names AS column_names, - s.name AS statistics_name, - CONVERT(DATETIME, ddsp.last_updated) AS last_statistics_update, - DATEDIFF(DAY, ddsp.last_updated, GETDATE()) AS days_since_last_stats_update, - ddsp.rows, - ddsp.rows_sampled, - CAST(ddsp.rows_sampled / ( 1. * NULLIF(ddsp.rows, 0) ) * 100 AS DECIMAL(18, 1)) AS percent_sampled, - ddsp.steps AS histogram_steps, - ddsp.modification_counter, - CASE WHEN ddsp.modification_counter > 0 - THEN CAST(ddsp.modification_counter / ( 1. * NULLIF(ddsp.rows, 0) ) * 100 AS DECIMAL(18, 1)) - ELSE ddsp.modification_counter - END AS percent_modifications, - CASE WHEN ddsp.rows < 500 THEN 500 - ELSE CAST(( ddsp.rows * .20 ) + 500 AS INT) - 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 - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.stats AS s - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects obj - ON s.object_id = obj.object_id - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas sch - ON sch.schema_id = obj.schema_id - LEFT JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.indexes AS i - ON i.object_id = s.object_id - AND i.index_id = s.stats_id - OUTER APPLY ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_stats_properties(s.object_id, s.stats_id) AS ddsp - CROSS APPLY ( SELECT STUFF((SELECT '', '' + c.name - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.stats_columns AS sc - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns AS c - ON sc.column_id = c.column_id AND sc.object_id = c.object_id - WHERE sc.stats_id = s.stats_id AND sc.object_id = s.object_id - ORDER BY sc.stats_column_id - FOR XML PATH(''''), TYPE).value(''.'', ''nvarchar(max)''), 1, 2, '''') - ) ca (column_names) - WHERE obj.is_ms_shipped = 0 - OPTION (RECOMPILE);'; - - IF @dsql IS NULL - RAISERROR('@dsql is null',16,1); - - RAISERROR (N'Inserting data into #Statistics',0,1) WITH NOWAIT; - IF @Debug = 1 - BEGIN - PRINT SUBSTRING(@dsql, 0, 4000); - PRINT SUBSTRING(@dsql, 4000, 8000); - PRINT SUBSTRING(@dsql, 8000, 12000); - PRINT SUBSTRING(@dsql, 12000, 16000); - PRINT SUBSTRING(@dsql, 16000, 20000); - PRINT SUBSTRING(@dsql, 20000, 24000); - PRINT SUBSTRING(@dsql, 24000, 28000); - PRINT SUBSTRING(@dsql, 28000, 32000); - PRINT SUBSTRING(@dsql, 32000, 36000); - PRINT SUBSTRING(@dsql, 36000, 40000); - END; - - EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; - END; - ELSE - 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, - 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) - 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, - ISNULL(i.name, ''System Or User Statistic'') AS index_name, - ca.column_names AS column_names, - s.name AS statistics_name, - CONVERT(DATETIME, STATS_DATE(s.object_id, s.stats_id)) AS last_statistics_update, - DATEDIFF(DAY, STATS_DATE(s.object_id, s.stats_id), GETDATE()) AS days_since_last_stats_update, - si.rowcnt, - si.rowmodctr, - CASE WHEN si.rowmodctr > 0 THEN CAST(si.rowmodctr / ( 1. * NULLIF(si.rowcnt, 0) ) * 100 AS DECIMAL(18, 1)) - ELSE si.rowmodctr - END AS percent_modifications, - CASE WHEN si.rowcnt < 500 THEN 500 - ELSE CAST(( si.rowcnt * .20 ) + 500 AS INT) - 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, - ' - + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' - THEN N's.has_filter, - s.filter_definition' - ELSE N'NULL AS has_filter, - NULL AS filter_definition' END - + N' - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.stats AS s - INNER HASH JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.sysindexes si - ON si.name = s.name AND s.object_id = si.id - INNER HASH JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects obj - ON s.object_id = obj.object_id - INNER HASH JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas sch - ON sch.schema_id = obj.schema_id - LEFT HASH JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.indexes AS i - ON i.object_id = s.object_id - AND i.index_id = s.stats_id - CROSS APPLY ( SELECT STUFF((SELECT '', '' + c.name - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.stats_columns AS sc - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns AS c - ON sc.column_id = c.column_id AND sc.object_id = c.object_id - WHERE sc.stats_id = s.stats_id AND sc.object_id = s.object_id - ORDER BY sc.stats_column_id - FOR XML PATH(''''), TYPE).value(''.'', ''nvarchar(max)''), 1, 2, '''') - ) ca (column_names) - WHERE obj.is_ms_shipped = 0 - AND si.rowcnt > 0 - OPTION (RECOMPILE);'; - - IF @dsql IS NULL - RAISERROR('@dsql is null',16,1); - - RAISERROR (N'Inserting data into #Statistics',0,1) WITH NOWAIT; - IF @Debug = 1 - BEGIN - PRINT SUBSTRING(@dsql, 0, 4000); - PRINT SUBSTRING(@dsql, 4000, 8000); - PRINT SUBSTRING(@dsql, 8000, 12000); - PRINT SUBSTRING(@dsql, 12000, 16000); - PRINT SUBSTRING(@dsql, 16000, 20000); - PRINT SUBSTRING(@dsql, 20000, 24000); - PRINT SUBSTRING(@dsql, 24000, 28000); - PRINT SUBSTRING(@dsql, 28000, 32000); - PRINT SUBSTRING(@dsql, 32000, 36000); - PRINT SUBSTRING(@dsql, 36000, 40000); - END; - - EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; - END; - - END; - - IF (PARSENAME(@SQLServerProductVersion, 4) >= 10) - BEGIN - RAISERROR (N'Gathering Computed Column Info.',0,1) WITH NOWAIT; - SET @dsql=N'SELECT DB_ID(@i_DatabaseName) AS [database_id], - @i_DatabaseName AS database_name, - t.name AS table_name, - s.name AS schema_name, - c.name AS column_name, - cc.is_nullable, - cc.definition, - cc.uses_database_collation, - cc.is_persisted, - cc.is_computed, - CASE WHEN cc.definition LIKE ''%|].|[%'' ESCAPE ''|'' THEN 1 ELSE 0 END AS is_function, - ''ALTER TABLE '' + QUOTENAME(s.name) + ''.'' + QUOTENAME(t.name) + - '' ADD '' + QUOTENAME(c.name) + '' AS '' + cc.definition + - CASE WHEN is_persisted = 1 THEN '' PERSISTED'' ELSE '''' END + '';'' COLLATE DATABASE_DEFAULT AS [column_definition] - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.computed_columns AS cc - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns AS c - ON cc.object_id = c.object_id - AND cc.column_id = c.column_id - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.tables AS t - ON t.object_id = cc.object_id - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s - ON s.schema_id = t.schema_id - OPTION (RECOMPILE);'; - - IF @dsql IS NULL RAISERROR('@dsql is null',16,1); - - INSERT #ComputedColumns - ( database_id, [database_name], table_name, schema_name, column_name, is_nullable, definition, - uses_database_collation, is_persisted, is_computed, is_function, column_definition ) - EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; - - END; - - RAISERROR (N'Gathering Trace Flag Information',0,1) WITH NOWAIT; - INSERT #TraceStatus - EXEC ('DBCC TRACESTATUS(-1) WITH NO_INFOMSGS'); - - IF (PARSENAME(@SQLServerProductVersion, 4) >= 13) - BEGIN - RAISERROR (N'Gathering Temporal Table Info',0,1) WITH NOWAIT; - SET @dsql=N'SELECT ' + QUOTENAME(@DatabaseName,'''') + N' AS database_name, - DB_ID(N' + QUOTENAME(@DatabaseName,'''') + N') AS [database_id], - s.name AS schema_name, - t.name AS table_name, - oa.hsn as history_schema_name, - oa.htn AS history_table_name, - c1.name AS start_column_name, - c2.name AS end_column_name, - p.name AS period_name - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.periods AS p - INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.tables AS t - ON p.object_id = t.object_id - INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns AS c1 - ON t.object_id = c1.object_id - AND p.start_column_id = c1.column_id - INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns AS c2 - ON t.object_id = c2.object_id - AND p.end_column_id = c2.column_id - INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s - ON t.schema_id = s.schema_id - CROSS APPLY ( SELECT s2.name as hsn, t2.name htn - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.tables AS t2 - INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s2 - ON t2.schema_id = s2.schema_id - WHERE t2.object_id = t.history_table_id - AND t2.temporal_type = 1 /*History table*/ ) AS oa - WHERE t.temporal_type IN ( 2, 4 ) /*BOL currently points to these types, but has no definition for 4*/ - OPTION (RECOMPILE); - '; - - IF @dsql IS NULL - RAISERROR('@dsql is null',16,1); - - INSERT #TemporalTables ( database_name, database_id, schema_name, table_name, history_schema_name, - history_table_name, start_column_name, end_column_name, period_name ) - - EXEC sp_executesql @dsql; - - SET @dsql=N'SELECT DB_ID(@i_DatabaseName) AS [database_id], - @i_DatabaseName AS database_name, - t.name AS table_name, - s.name AS schema_name, - cc.name AS constraint_name, - cc.is_disabled, - cc.definition, - cc.uses_database_collation, - cc.is_not_trusted, - CASE WHEN cc.definition LIKE ''%|].|[%'' ESCAPE ''|'' THEN 1 ELSE 0 END AS is_function, - ''ALTER TABLE '' + QUOTENAME(s.name) + ''.'' + QUOTENAME(t.name) + - '' ADD CONSTRAINT '' + QUOTENAME(cc.name) + '' CHECK '' + cc.definition + '';'' COLLATE DATABASE_DEFAULT AS [column_definition] - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.check_constraints AS cc - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.tables AS t - ON t.object_id = cc.parent_object_id - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s - ON s.schema_id = t.schema_id - OPTION (RECOMPILE);'; - - INSERT #CheckConstraints - ( database_id, [database_name], table_name, schema_name, constraint_name, is_disabled, definition, - uses_database_collation, is_not_trusted, is_function, column_definition ) - EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; - - - SET @dsql=N'SELECT DB_ID(@i_DatabaseName) AS [database_id], - @i_DatabaseName AS database_name, - s.name AS missing_schema_name, - t.name AS missing_table_name, - i.name AS missing_index_name, - c.name AS missing_column_name - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.sql_expression_dependencies AS sed - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.tables AS t - ON t.object_id = sed.referenced_id - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s - ON t.schema_id = s.schema_id - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.indexes AS i - ON i.object_id = sed.referenced_id - AND i.index_id = sed.referencing_minor_id - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns AS c - ON c.object_id = sed.referenced_id - AND c.column_id = sed.referenced_minor_id - WHERE sed.referencing_class = 7 - AND sed.referenced_class = 1 - AND i.has_filter = 1 - AND NOT EXISTS ( SELECT 1/0 - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.index_columns AS ic - WHERE ic.index_id = sed.referencing_minor_id - AND ic.column_id = sed.referenced_minor_id - AND ic.object_id = sed.referenced_id ) - OPTION(RECOMPILE);' - - INSERT #FilteredIndexes ( database_id, database_name, schema_name, table_name, index_name, column_name ) - EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; - - - END; - -END; -END TRY -BEGIN CATCH - RAISERROR (N'Failure populating temp tables.', 0,1) WITH NOWAIT; - - IF @dsql IS NOT NULL - BEGIN - SET @msg= 'Last @dsql: ' + @dsql; - RAISERROR(@msg, 0, 1) WITH NOWAIT; - END; - - SELECT @msg = @DatabaseName + N' database failed to process. ' + ERROR_MESSAGE(), @ErrorSeverity = ERROR_SEVERITY(), @ErrorState = ERROR_STATE(); - RAISERROR (@msg,@ErrorSeverity, @ErrorState )WITH NOWAIT; - - - WHILE @@trancount > 0 - ROLLBACK; - - RETURN; -END CATCH; - FETCH NEXT FROM c1 INTO @DatabaseName; -END; -DEALLOCATE c1; - - - - - - ----------------------------------------- ---STEP 2: PREP THE TEMP TABLES ---EVERY QUERY AFTER THIS GOES AGAINST TEMP TABLES ONLY. ----------------------------------------- - -RAISERROR (N'Updating #IndexSanity.key_column_names',0,1) WITH NOWAIT; -UPDATE #IndexSanity -SET key_column_names = D1.key_column_names -FROM #IndexSanity si - CROSS APPLY ( SELECT RTRIM(STUFF( (SELECT N', ' + c.column_name - + N' {' + system_type_name + - CASE max_length WHEN -1 THEN N' (max)' ELSE - CASE - WHEN system_type_name IN (N'char',N'varchar',N'binary',N'varbinary') THEN N' (' + CAST(max_length AS NVARCHAR(20)) + N')' - WHEN system_type_name IN (N'nchar',N'nvarchar') THEN N' (' + CAST(max_length/2 AS NVARCHAR(20)) + N')' - ELSE N' ' + CAST(max_length AS NVARCHAR(50)) - END - END - + N'}' - AS col_definition - FROM #IndexColumns c - WHERE c.database_id= si.database_id - AND c.schema_name = si.schema_name - AND c.object_id = si.object_id - AND c.index_id = si.index_id - AND c.is_included_column = 0 /*Just Keys*/ - AND c.key_ordinal > 0 /*Ignore non-key columns, such as partitioning keys*/ - ORDER BY c.object_id, c.index_id, c.key_ordinal - FOR XML PATH('') ,TYPE).value('.', 'nvarchar(max)'), 1, 1, '')) - ) D1 ( key_column_names ); - -RAISERROR (N'Updating #IndexSanity.partition_key_column_name',0,1) WITH NOWAIT; -UPDATE #IndexSanity -SET partition_key_column_name = D1.partition_key_column_name -FROM #IndexSanity si - CROSS APPLY ( SELECT RTRIM(STUFF( (SELECT N', ' + c.column_name AS col_definition - FROM #IndexColumns c - WHERE c.database_id= si.database_id - AND c.schema_name = si.schema_name - AND c.object_id = si.object_id - AND c.index_id = si.index_id - AND c.partition_ordinal <> 0 /*Just Partitioned Keys*/ - ORDER BY c.object_id, c.index_id, c.key_ordinal - FOR XML PATH('') , TYPE).value('.', 'nvarchar(max)'), 1, 1,''))) D1 - ( partition_key_column_name ); - -RAISERROR (N'Updating #IndexSanity.key_column_names_with_sort_order',0,1) WITH NOWAIT; -UPDATE #IndexSanity -SET key_column_names_with_sort_order = D2.key_column_names_with_sort_order -FROM #IndexSanity si - CROSS APPLY ( SELECT RTRIM(STUFF( (SELECT N', ' + c.column_name + CASE c.is_descending_key - WHEN 1 THEN N' DESC' - ELSE N'' - END - + N' {' + system_type_name + - CASE max_length WHEN -1 THEN N' (max)' ELSE - CASE - WHEN system_type_name IN (N'char',N'varchar',N'binary',N'varbinary') THEN N' (' + CAST(max_length AS NVARCHAR(20)) + N')' - WHEN system_type_name IN (N'nchar',N'nvarchar') THEN N' (' + CAST(max_length/2 AS NVARCHAR(20)) + N')' - ELSE N' ' + CAST(max_length AS NVARCHAR(50)) - END - END - + N'}' - AS col_definition - FROM #IndexColumns c - WHERE c.database_id= si.database_id - AND c.schema_name = si.schema_name - AND c.object_id = si.object_id - AND c.index_id = si.index_id - AND c.is_included_column = 0 /*Just Keys*/ - AND c.key_ordinal > 0 /*Ignore non-key columns, such as partitioning keys*/ - ORDER BY c.object_id, c.index_id, c.key_ordinal - FOR XML PATH('') , TYPE).value('.', 'nvarchar(max)'), 1, 1, '')) - ) D2 ( key_column_names_with_sort_order ); - -RAISERROR (N'Updating #IndexSanity.key_column_names_with_sort_order_no_types (for create tsql)',0,1) WITH NOWAIT; -UPDATE #IndexSanity -SET key_column_names_with_sort_order_no_types = D2.key_column_names_with_sort_order_no_types -FROM #IndexSanity si - CROSS APPLY ( SELECT RTRIM(STUFF( (SELECT N', ' + QUOTENAME(c.column_name) + CASE c.is_descending_key - WHEN 1 THEN N' DESC' - ELSE N'' - END AS col_definition - FROM #IndexColumns c - WHERE c.database_id= si.database_id - AND c.schema_name = si.schema_name - AND c.object_id = si.object_id - AND c.index_id = si.index_id - AND c.is_included_column = 0 /*Just Keys*/ - AND c.key_ordinal > 0 /*Ignore non-key columns, such as partitioning keys*/ - ORDER BY c.object_id, c.index_id, c.key_ordinal - FOR XML PATH('') , TYPE).value('.', 'nvarchar(max)'), 1, 1, '')) - ) D2 ( key_column_names_with_sort_order_no_types ); - -RAISERROR (N'Updating #IndexSanity.include_column_names',0,1) WITH NOWAIT; -UPDATE #IndexSanity -SET include_column_names = D3.include_column_names -FROM #IndexSanity si - CROSS APPLY ( SELECT RTRIM(STUFF( (SELECT N', ' + c.column_name - + N' {' + system_type_name + - CASE max_length WHEN -1 THEN N' (max)' ELSE - CASE - WHEN system_type_name IN (N'char',N'varchar',N'binary',N'varbinary') THEN N' (' + CAST(max_length AS NVARCHAR(20)) + N')' - WHEN system_type_name IN (N'nchar',N'nvarchar') THEN N' (' + CAST(max_length/2 AS NVARCHAR(20)) + N')' - ELSE N' ' + CAST(max_length AS NVARCHAR(50)) - END - END - + N'}' - FROM #IndexColumns c - WHERE c.database_id= si.database_id - AND c.schema_name = si.schema_name - AND c.object_id = si.object_id - AND c.index_id = si.index_id - AND c.is_included_column = 1 /*Just includes*/ - ORDER BY c.column_name /*Order doesn't matter in includes, - this is here to make rows easy to compare.*/ - FOR XML PATH('') , TYPE).value('.', 'nvarchar(max)'), 1, 1, '')) - ) D3 ( include_column_names ); - -RAISERROR (N'Updating #IndexSanity.include_column_names_no_types (for create tsql)',0,1) WITH NOWAIT; -UPDATE #IndexSanity -SET include_column_names_no_types = D3.include_column_names_no_types -FROM #IndexSanity si - CROSS APPLY ( SELECT RTRIM(STUFF( (SELECT N', ' + QUOTENAME(c.column_name) - FROM #IndexColumns c - WHERE c.database_id= si.database_id - AND c.schema_name = si.schema_name - AND c.object_id = si.object_id - AND c.index_id = si.index_id - AND c.is_included_column = 1 /*Just includes*/ - ORDER BY c.column_name /*Order doesn't matter in includes, - this is here to make rows easy to compare.*/ - FOR XML PATH('') , TYPE).value('.', 'nvarchar(max)'), 1, 1, '')) - ) D3 ( include_column_names_no_types ); - -RAISERROR (N'Updating #IndexSanity.count_key_columns and count_include_columns',0,1) WITH NOWAIT; -UPDATE #IndexSanity -SET count_included_columns = D4.count_included_columns, - count_key_columns = D4.count_key_columns -FROM #IndexSanity si - CROSS APPLY ( SELECT SUM(CASE WHEN is_included_column = 'true' THEN 1 - ELSE 0 - END) AS count_included_columns, - SUM(CASE WHEN is_included_column = 'false' AND c.key_ordinal > 0 THEN 1 - ELSE 0 - END) AS count_key_columns - FROM #IndexColumns c - WHERE c.database_id= si.database_id - AND c.schema_name = si.schema_name - AND c.object_id = si.object_id - AND c.index_id = si.index_id - ) AS D4 ( count_included_columns, count_key_columns ); - -RAISERROR (N'Updating index_sanity_id on #IndexPartitionSanity',0,1) WITH NOWAIT; -UPDATE #IndexPartitionSanity -SET index_sanity_id = i.index_sanity_id -FROM #IndexPartitionSanity ps - JOIN #IndexSanity i ON ps.[object_id] = i.[object_id] - AND ps.index_id = i.index_id - AND i.database_id = ps.database_id - AND i.schema_name = ps.schema_name; - - -RAISERROR (N'Inserting data into #IndexSanitySize',0,1) WITH NOWAIT; -INSERT #IndexSanitySize ( [index_sanity_id], [database_id], [schema_name], [lock_escalation_desc], partition_count, total_rows, total_reserved_MB, - total_reserved_LOB_MB, total_reserved_row_overflow_MB, total_reserved_dictionary_MB, total_range_scan_count, - total_singleton_lookup_count, total_leaf_delete_count, total_leaf_update_count, - total_forwarded_fetch_count,total_row_lock_count, - total_row_lock_wait_count, total_row_lock_wait_in_ms, avg_row_lock_wait_in_ms, - total_page_lock_count, total_page_lock_wait_count, total_page_lock_wait_in_ms, - avg_page_lock_wait_in_ms, total_index_lock_promotion_attempt_count, - total_index_lock_promotion_count, data_compression_desc, - page_latch_wait_count, page_latch_wait_in_ms, page_io_latch_wait_count, page_io_latch_wait_in_ms) - SELECT index_sanity_id, ipp.database_id, ipp.schema_name, ipp.lock_escalation_desc, - COUNT(*), SUM(row_count), SUM(reserved_MB), - SUM(reserved_LOB_MB) - SUM(reserved_dictionary_MB), /* Subtract columnstore dictionaries from LOB data */ - SUM(reserved_row_overflow_MB), - SUM(reserved_dictionary_MB), - SUM(range_scan_count), - SUM(singleton_lookup_count), - SUM(leaf_delete_count), - SUM(leaf_update_count), - SUM(forwarded_fetch_count), - SUM(row_lock_count), - SUM(row_lock_wait_count), - SUM(row_lock_wait_in_ms), - CASE WHEN SUM(row_lock_wait_in_ms) > 0 THEN - SUM(row_lock_wait_in_ms)/(1.*SUM(row_lock_wait_count)) - ELSE 0 END AS avg_row_lock_wait_in_ms, - SUM(page_lock_count), - SUM(page_lock_wait_count), - SUM(page_lock_wait_in_ms), - CASE WHEN SUM(page_lock_wait_in_ms) > 0 THEN - SUM(page_lock_wait_in_ms)/(1.*SUM(page_lock_wait_count)) - ELSE 0 END AS avg_page_lock_wait_in_ms, - SUM(index_lock_promotion_attempt_count), - SUM(index_lock_promotion_count), - LEFT(MAX(data_compression_info.data_compression_rollup),4000), - SUM(page_latch_wait_count), - SUM(page_latch_wait_in_ms), - SUM(page_io_latch_wait_count), - SUM(page_io_latch_wait_in_ms) - FROM #IndexPartitionSanity ipp - /* individual partitions can have distinct compression settings, just roll them into a list here*/ - OUTER APPLY (SELECT STUFF(( - SELECT N', ' + data_compression_desc - FROM #IndexPartitionSanity ipp2 - WHERE ipp.[object_id]=ipp2.[object_id] - AND ipp.[index_id]=ipp2.[index_id] - AND ipp.database_id = ipp2.database_id - AND ipp.schema_name = ipp2.schema_name - ORDER BY ipp2.partition_number - FOR XML PATH(''),TYPE).value('.', 'nvarchar(max)'), 1, 1, '')) - data_compression_info(data_compression_rollup) - GROUP BY index_sanity_id, ipp.database_id, ipp.schema_name, ipp.lock_escalation_desc - ORDER BY index_sanity_id -OPTION ( RECOMPILE ); - -RAISERROR (N'Determining index usefulness',0,1) WITH NOWAIT; -UPDATE #MissingIndexes -SET is_low = CASE WHEN (user_seeks + user_scans) < 5000 - OR unique_compiles = 1 - THEN 1 - ELSE 0 - END; - -RAISERROR (N'Updating #IndexSanity.referenced_by_foreign_key',0,1) WITH NOWAIT; -UPDATE #IndexSanity - SET is_referenced_by_foreign_key=1 -FROM #IndexSanity s -JOIN #ForeignKeys fk ON - s.object_id=fk.referenced_object_id - AND s.database_id=fk.database_id - AND LEFT(s.key_column_names,LEN(fk.referenced_fk_columns)) = fk.referenced_fk_columns; - -RAISERROR (N'Update index_secret on #IndexSanity for NC indexes.',0,1) WITH NOWAIT; -UPDATE nc -SET secret_columns= - N'[' + - CASE tb.count_key_columns WHEN 0 THEN '1' ELSE CAST(tb.count_key_columns AS NVARCHAR(10)) END + - CASE nc.is_unique WHEN 1 THEN N' INCLUDE' ELSE N' KEY' END + - CASE WHEN tb.count_key_columns > 1 THEN N'S] ' ELSE N'] ' END + - CASE tb.index_id WHEN 0 THEN '[RID]' ELSE LTRIM(tb.key_column_names) + - /* Uniquifiers only needed on non-unique clustereds-- not heaps */ - CASE tb.is_unique WHEN 0 THEN ' [UNIQUIFIER]' ELSE N'' END - END - , count_secret_columns= - CASE tb.index_id WHEN 0 THEN 1 ELSE - tb.count_key_columns + - CASE tb.is_unique WHEN 0 THEN 1 ELSE 0 END - END -FROM #IndexSanity AS nc -JOIN #IndexSanity AS tb ON nc.object_id=tb.object_id - AND nc.database_id = tb.database_id - AND nc.schema_name = tb.schema_name - AND tb.index_id IN (0,1) -WHERE nc.index_id > 1; - -RAISERROR (N'Update index_secret on #IndexSanity for heaps and non-unique clustered.',0,1) WITH NOWAIT; -UPDATE tb -SET secret_columns= CASE tb.index_id WHEN 0 THEN '[RID]' ELSE '[UNIQUIFIER]' END - , count_secret_columns = 1 -FROM #IndexSanity AS tb -WHERE tb.index_id = 0 /*Heaps-- these have the RID */ - OR (tb.index_id=1 AND tb.is_unique=0); /* Non-unique CX: has uniquifer (when needed) */ - - -RAISERROR (N'Populate #IndexCreateTsql.',0,1) WITH NOWAIT; -INSERT #IndexCreateTsql (index_sanity_id, create_tsql) -SELECT - index_sanity_id, - ISNULL ( - CASE index_id WHEN 0 THEN N'ALTER TABLE ' + QUOTENAME([database_name]) + N'.' + QUOTENAME([schema_name]) + N'.' + QUOTENAME([object_name]) + ' REBUILD;' - ELSE - CASE WHEN is_XML = 1 OR is_spatial = 1 OR is_in_memory_oltp = 1 THEN N'' /* Not even trying for these just yet...*/ - ELSE - CASE WHEN is_primary_key=1 THEN - N'ALTER TABLE ' + QUOTENAME([database_name]) + N'.' + QUOTENAME([schema_name]) + - N'.' + QUOTENAME([object_name]) + - N' ADD CONSTRAINT [' + - index_name + - N'] PRIMARY KEY ' + - CASE WHEN index_id=1 THEN N'CLUSTERED (' ELSE N'(' END + - key_column_names_with_sort_order_no_types + N' )' - WHEN is_unique_constraint = 1 AND is_primary_key = 0 - THEN - N'ALTER TABLE ' + QUOTENAME([database_name]) + N'.' + QUOTENAME([schema_name]) + - N'.' + QUOTENAME([object_name]) + - N' ADD CONSTRAINT [' + - index_name + - N'] UNIQUE ' + - CASE WHEN index_id=1 THEN N'CLUSTERED (' ELSE N'(' END + - key_column_names_with_sort_order_no_types + N' )' - WHEN is_CX_columnstore= 1 THEN - N'CREATE CLUSTERED COLUMNSTORE INDEX ' + QUOTENAME(index_name) + N' on ' + QUOTENAME([database_name]) + N'.' + QUOTENAME([schema_name]) + N'.' + QUOTENAME([object_name]) - ELSE /*Else not a PK or cx columnstore */ - N'CREATE ' + - CASE WHEN is_unique=1 THEN N'UNIQUE ' ELSE N'' END + - CASE WHEN index_id=1 THEN N'CLUSTERED ' ELSE N'' END + - CASE WHEN is_NC_columnstore=1 THEN N'NONCLUSTERED COLUMNSTORE ' - ELSE N'' END + - N'INDEX [' - + index_name + N'] ON ' + - QUOTENAME([database_name]) + N'.' + - QUOTENAME([schema_name]) + N'.' + QUOTENAME([object_name]) + - CASE WHEN is_NC_columnstore=1 THEN - N' (' + ISNULL(include_column_names_no_types,'') + N' )' - ELSE /*Else not columnstore */ - N' (' + ISNULL(key_column_names_with_sort_order_no_types,'') + N' )' - + CASE WHEN include_column_names_no_types IS NOT NULL THEN - N' INCLUDE (' + include_column_names_no_types + N')' - ELSE N'' - END - END /*End non-columnstore case */ - + CASE WHEN filter_definition <> N'' THEN N' WHERE ' + filter_definition ELSE N'' END - END /*End Non-PK index CASE */ - + CASE WHEN is_NC_columnstore=0 AND is_CX_columnstore=0 THEN - N' WITH (' - + N'FILLFACTOR=' + CASE fill_factor WHEN 0 THEN N'100' ELSE CAST(fill_factor AS NVARCHAR(5)) END + ', ' - + N'ONLINE=?, SORT_IN_TEMPDB=?, DATA_COMPRESSION=?' - + N')' - ELSE N'' END - + N';' - END /*End non-spatial and non-xml CASE */ - END, '[Unknown Error]') - AS create_tsql -FROM #IndexSanity; - -RAISERROR (N'Populate #PartitionCompressionInfo.',0,1) WITH NOWAIT; -IF OBJECT_ID('tempdb..#maps') IS NOT NULL DROP TABLE #maps; -WITH maps - AS - ( - SELECT ips.index_sanity_id, - ips.partition_number, - ips.data_compression_desc, - ips.partition_number - ROW_NUMBER() OVER ( PARTITION BY ips.index_sanity_id, ips.data_compression_desc - ORDER BY ips.partition_number ) AS rn - FROM #IndexPartitionSanity AS ips - ) -SELECT * -INTO #maps -FROM maps; - -IF OBJECT_ID('tempdb..#grps') IS NOT NULL DROP TABLE #grps; -WITH grps - AS - ( - SELECT MIN(maps.partition_number) AS MinKey, - MAX(maps.partition_number) AS MaxKey, - maps.index_sanity_id, - maps.data_compression_desc - FROM #maps AS maps - GROUP BY maps.rn, maps.index_sanity_id, maps.data_compression_desc - ) -SELECT * -INTO #grps -FROM grps; - -INSERT #PartitionCompressionInfo ( index_sanity_id, partition_compression_detail ) -SELECT DISTINCT - grps.index_sanity_id, - SUBSTRING( - ( STUFF( - ( SELECT N', ' + N' Partition' - + CASE - WHEN grps2.MinKey < grps2.MaxKey - THEN - + N's ' + CAST(grps2.MinKey AS NVARCHAR(10)) + N' - ' - + CAST(grps2.MaxKey AS NVARCHAR(10)) + N' use ' + grps2.data_compression_desc - ELSE - N' ' + CAST(grps2.MinKey AS NVARCHAR(10)) + N' uses ' + grps2.data_compression_desc - END AS Partitions - FROM #grps AS grps2 - WHERE grps2.index_sanity_id = grps.index_sanity_id - ORDER BY grps2.MinKey, grps2.MaxKey - FOR XML PATH(''), TYPE ).value('.', 'NVARCHAR(MAX)'), 1, 1, '')), 0, 8000) AS partition_compression_detail -FROM #grps AS grps; - -RAISERROR (N'Update #PartitionCompressionInfo.',0,1) WITH NOWAIT; -UPDATE sz -SET sz.data_compression_desc = pci.partition_compression_detail -FROM #IndexSanitySize sz -JOIN #PartitionCompressionInfo AS pci -ON pci.index_sanity_id = sz.index_sanity_id; - -RAISERROR (N'Update #IndexSanity for filtered indexes with columns not in the index definition.',0,1) WITH NOWAIT; -UPDATE #IndexSanity -SET filter_columns_not_in_index = D1.filter_columns_not_in_index -FROM #IndexSanity si - CROSS APPLY ( SELECT RTRIM(STUFF( (SELECT N', ' + c.column_name AS col_definition - FROM #FilteredIndexes AS c - WHERE c.database_id= si.database_id - AND c.schema_name = si.schema_name - AND c.table_name = si.object_name - AND c.index_name = si.index_name - ORDER BY c.index_sanity_id - FOR XML PATH('') , TYPE).value('.', 'nvarchar(max)'), 1, 1,''))) D1 - ( filter_columns_not_in_index ); - - -IF @Debug = 1 -BEGIN - SELECT '#BlitzIndexResults' AS table_name, * FROM #BlitzIndexResults AS bir; - SELECT '#IndexSanity' AS table_name, * FROM #IndexSanity; - SELECT '#IndexPartitionSanity' AS table_name, * FROM #IndexPartitionSanity; - SELECT '#IndexSanitySize' AS table_name, * FROM #IndexSanitySize; - SELECT '#IndexColumns' AS table_name, * FROM #IndexColumns; - SELECT '#MissingIndexes' AS table_name, * FROM #MissingIndexes; - SELECT '#ForeignKeys' AS table_name, * FROM #ForeignKeys; - SELECT '#UnindexedForeignKeys' AS table_name, * FROM #UnindexedForeignKeys; - SELECT '#BlitzIndexResults' AS table_name, * FROM #BlitzIndexResults; - SELECT '#IndexCreateTsql' AS table_name, * FROM #IndexCreateTsql; - SELECT '#DatabaseList' AS table_name, * FROM #DatabaseList; - SELECT '#Statistics' AS table_name, * FROM #Statistics; - SELECT '#PartitionCompressionInfo' AS table_name, * FROM #PartitionCompressionInfo; - SELECT '#ComputedColumns' AS table_name, * FROM #ComputedColumns; - SELECT '#TraceStatus' AS table_name, * FROM #TraceStatus; - SELECT '#CheckConstraints' AS table_name, * FROM #CheckConstraints; - SELECT '#FilteredIndexes' AS table_name, * FROM #FilteredIndexes; -END - - ----------------------------------------- ---STEP 3: DIAGNOSE THE PATIENT ----------------------------------------- - - -BEGIN TRY ----------------------------------------- ---If @TableName is specified, just return information for that table. ---The @Mode parameter doesn't matter if you're looking at a specific table. ----------------------------------------- -IF @TableName IS NOT NULL -BEGIN - RAISERROR(N'@TableName specified, giving detail only on that table.', 0,1) WITH NOWAIT; - - --We do a left join here in case this is a disabled NC. - --In that case, it won't have any size info/pages allocated. - - IF (@ShowColumnstoreOnly = 0) - BEGIN - WITH table_mode_cte AS ( - SELECT - s.db_schema_object_indexid, - s.key_column_names, - s.index_definition, - ISNULL(s.secret_columns,N'') AS secret_columns, - s.fill_factor, - s.index_usage_summary, - sz.index_op_stats, - ISNULL(sz.index_size_summary,'') /*disabled NCs will be null*/ AS index_size_summary, - partition_compression_detail , - ISNULL(sz.index_lock_wait_summary,'') AS index_lock_wait_summary, - s.is_referenced_by_foreign_key, - (SELECT COUNT(*) - FROM #ForeignKeys fk WHERE fk.parent_object_id=s.object_id - AND PATINDEX (fk.parent_fk_columns, s.key_column_names)=1) AS FKs_covered_by_index, - s.last_user_seek, - s.last_user_scan, - s.last_user_lookup, - s.last_user_update, - s.create_date, - s.modify_date, - sz.page_latch_wait_count, - CONVERT(VARCHAR(10), (sz.page_latch_wait_in_ms / 1000) / 86400) + ':' + CONVERT(VARCHAR(20), DATEADD(s, (sz.page_latch_wait_in_ms / 1000), 0), 108) AS page_latch_wait_time, - sz.page_io_latch_wait_count, - CONVERT(VARCHAR(10), (sz.page_io_latch_wait_in_ms / 1000) / 86400) + ':' + CONVERT(VARCHAR(20), DATEADD(s, (sz.page_io_latch_wait_in_ms / 1000), 0), 108) AS page_io_latch_wait_time, - ct.create_tsql, - CASE - WHEN s.is_primary_key = 1 AND s.index_definition <> '[HEAP]' - THEN N'--ALTER TABLE ' + QUOTENAME(s.[database_name]) + N'.' + QUOTENAME(s.[schema_name]) + N'.' + QUOTENAME(s.[object_name]) - + N' DROP CONSTRAINT ' + QUOTENAME(s.index_name) + N';' - WHEN s.is_primary_key = 0 AND is_unique_constraint = 1 AND s.index_definition <> '[HEAP]' - THEN N'--ALTER TABLE ' + QUOTENAME(s.[database_name]) + N'.' + QUOTENAME(s.[schema_name]) + N'.' + QUOTENAME(s.[object_name]) - + N' DROP CONSTRAINT ' + QUOTENAME(s.index_name) + N';' - WHEN s.is_primary_key = 0 AND s.index_definition <> '[HEAP]' - THEN N'--DROP INDEX '+ QUOTENAME(s.index_name) + N' ON ' + QUOTENAME(s.[database_name]) + N'.' + - QUOTENAME(s.[schema_name]) + N'.' + QUOTENAME(s.[object_name]) + N';' - ELSE N'' - END AS drop_tsql, - 1 AS display_order - FROM #IndexSanity s - LEFT JOIN #IndexSanitySize sz ON - s.index_sanity_id=sz.index_sanity_id - LEFT JOIN #IndexCreateTsql ct ON - s.index_sanity_id=ct.index_sanity_id - LEFT JOIN #PartitionCompressionInfo pci ON - pci.index_sanity_id = s.index_sanity_id - WHERE s.[object_id]=@ObjectID - UNION ALL - SELECT N'Database ' + QUOTENAME(@DatabaseName) + N' as of ' + CONVERT(NVARCHAR(16),GETDATE(),121) + - N' (' + @ScriptVersionName + ')' , - N'SQL Server First Responder Kit' , - N'http://FirstResponderKit.org' , - N'From Your Community Volunteers', - NULL,@DaysUptimeInsertValue,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL, - 0 AS display_order - ) - SELECT - db_schema_object_indexid AS [Details: db_schema.table.index(indexid)], - index_definition AS [Definition: [Property]] ColumnName {datatype maxbytes}], - secret_columns AS [Secret Columns], - fill_factor AS [Fillfactor], - index_usage_summary AS [Usage Stats], - index_op_stats AS [Op Stats], - index_size_summary AS [Size], - partition_compression_detail AS [Compression Type], - index_lock_wait_summary AS [Lock Waits], - is_referenced_by_foreign_key AS [Referenced by FK?], - FKs_covered_by_index AS [FK Covered by Index?], - last_user_seek AS [Last User Seek], - last_user_scan AS [Last User Scan], - last_user_lookup AS [Last User Lookup], - last_user_update AS [Last User Write], - create_date AS [Created], - modify_date AS [Last Modified], - page_latch_wait_count AS [Page Latch Wait Count], - page_latch_wait_time as [Page Latch Wait Time (D:H:M:S)], - page_io_latch_wait_count AS [Page IO Latch Wait Count], - page_io_latch_wait_time as [Page IO Latch Wait Time (D:H:M:S)], - create_tsql AS [Create TSQL], - drop_tsql AS [Drop TSQL] - FROM table_mode_cte - ORDER BY display_order ASC, key_column_names ASC - OPTION ( RECOMPILE ); - - IF (SELECT TOP 1 [object_id] FROM #MissingIndexes mi) IS NOT NULL - BEGIN; - - WITH create_date AS ( - SELECT i.database_id, - i.schema_name, - i.[object_id], - ISNULL(NULLIF(MAX(DATEDIFF(DAY, i.create_date, SYSDATETIME())), 0), 1) AS create_days - FROM #IndexSanity AS i - GROUP BY i.database_id, i.schema_name, i.object_id - ) - SELECT N'Missing index.' AS Finding , - N'https://www.brentozar.com/go/Indexaphobia' AS URL , - mi.[statement] + - ' Est. Benefit: ' - + CASE WHEN magic_benefit_number >= 922337203685477 THEN '>= 922,337,203,685,477' - ELSE REPLACE(CONVERT(NVARCHAR(256),CAST(CAST( - (magic_benefit_number / CASE WHEN cd.create_days < @DaysUptime THEN cd.create_days ELSE @DaysUptime END) - AS BIGINT) AS MONEY), 1), '.00', '') - END AS [Estimated Benefit], - missing_index_details AS [Missing Index Request] , - index_estimated_impact AS [Estimated Impact], - create_tsql AS [Create TSQL], - sample_query_plan AS [Sample Query Plan] - FROM #MissingIndexes mi - LEFT JOIN create_date AS cd - ON mi.[object_id] = cd.object_id - AND mi.database_id = cd.database_id - AND mi.schema_name = cd.schema_name - WHERE mi.[object_id] = @ObjectID - AND (@ShowAllMissingIndexRequests=1 - /* Minimum benefit threshold = 100k/day of uptime OR since table creation date, whichever is lower*/ - OR (magic_benefit_number / CASE WHEN cd.create_days < @DaysUptime THEN cd.create_days ELSE @DaysUptime END) >= 100000) - ORDER BY magic_benefit_number DESC - OPTION ( RECOMPILE ); - END; - ELSE - SELECT 'No missing indexes.' AS finding; - - SELECT - column_name AS [Column Name], - (SELECT COUNT(*) - FROM #IndexColumns c2 - WHERE c2.column_name=c.column_name - AND c2.key_ordinal IS NOT NULL) - + CASE WHEN c.index_id = 1 AND c.key_ordinal IS NOT NULL THEN - -1+ (SELECT COUNT(DISTINCT index_id) - FROM #IndexColumns c3 - WHERE c3.index_id NOT IN (0,1)) - ELSE 0 END - AS [Found In], - system_type_name + - CASE max_length WHEN -1 THEN N' (max)' ELSE - CASE - WHEN system_type_name IN (N'char',N'varchar',N'binary',N'varbinary') THEN N' (' + CAST(max_length AS NVARCHAR(20)) + N')' - WHEN system_type_name IN (N'nchar',N'nvarchar') THEN N' (' + CAST(max_length/2 AS NVARCHAR(20)) + N')' - ELSE '' - END - END - AS [Type], - CASE is_computed WHEN 1 THEN 'yes' ELSE '' END AS [Computed?], - max_length AS [Length (max bytes)], - [precision] AS [Prec], - [scale] AS [Scale], - CASE is_nullable WHEN 1 THEN 'yes' ELSE '' END AS [Nullable?], - CASE is_identity WHEN 1 THEN 'yes' ELSE '' END AS [Identity?], - CASE is_replicated WHEN 1 THEN 'yes' ELSE '' END AS [Replicated?], - CASE is_sparse WHEN 1 THEN 'yes' ELSE '' END AS [Sparse?], - CASE is_filestream WHEN 1 THEN 'yes' ELSE '' END AS [Filestream?], - collation_name AS [Collation] - FROM #IndexColumns AS c - WHERE index_id IN (0,1); - - IF (SELECT TOP 1 parent_object_id FROM #ForeignKeys) IS NOT NULL - BEGIN - SELECT [database_name] + N':' + parent_object_name + N': ' + foreign_key_name AS [Foreign Key], - parent_fk_columns AS [Foreign Key Columns], - referenced_object_name AS [Referenced Table], - referenced_fk_columns AS [Referenced Table Columns], - is_disabled AS [Is Disabled?], - is_not_trusted AS [Not Trusted?], - is_not_for_replication [Not for Replication?], - [update_referential_action_desc] AS [Cascading Updates?], - [delete_referential_action_desc] AS [Cascading Deletes?] - FROM #ForeignKeys - ORDER BY [Foreign Key] - OPTION ( RECOMPILE ); - END; - ELSE - SELECT 'No foreign keys.' AS finding; - - /* Show histograms for all stats on this table. More info: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/1900 */ - IF EXISTS (SELECT * FROM sys.all_objects WHERE name = 'dm_db_stats_histogram') - BEGIN - SET @dsql = N'USE ' + QUOTENAME(@DatabaseName) + N'; - SELECT s.name AS [Stat Name], c.name AS [Leading Column Name], hist.step_number AS [Step Number], - hist.range_high_key AS [Range High Key], hist.range_rows AS [Range Rows], - hist.equal_rows AS [Equal Rows], hist.distinct_range_rows AS [Distinct Range Rows], hist.average_range_rows AS [Average Range Rows], - s.auto_created AS [Auto-Created], s.user_created AS [User-Created], - props.last_updated AS [Last Updated], props.modification_counter AS [Modification Counter], props.rows AS [Table Rows], - props.rows_sampled AS [Rows Sampled], s.stats_id AS [StatsID] - FROM sys.stats AS s - INNER JOIN sys.stats_columns sc ON s.object_id = sc.object_id AND s.stats_id = sc.stats_id AND sc.stats_column_id = 1 - INNER JOIN sys.columns c ON sc.object_id = c.object_id AND sc.column_id = c.column_id - CROSS APPLY sys.dm_db_stats_properties(s.object_id, s.stats_id) AS props - CROSS APPLY sys.dm_db_stats_histogram(s.[object_id], s.stats_id) AS hist - WHERE s.object_id = @ObjectID - ORDER BY s.auto_created, s.user_created, s.name, hist.step_number;'; - EXEC sp_executesql @dsql, N'@ObjectID INT', @ObjectID; - END - END - - /* Visualize columnstore index contents. More info: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2584 */ - IF 2 = (SELECT SUM(1) FROM sys.all_objects WHERE name IN ('column_store_row_groups','column_store_segments')) - BEGIN - RAISERROR(N'Visualizing columnstore index contents.', 0,1) WITH NOWAIT; - - SET @dsql = N'USE ' + QUOTENAME(@DatabaseName) + N'; - IF EXISTS(SELECT * FROM ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_row_groups WHERE object_id = @ObjectID) - BEGIN - SELECT @ColumnList = N'''', @ColumnListWithApostrophes = N''''; - WITH DistinctColumns AS ( - SELECT DISTINCT QUOTENAME(c.name) AS column_name, QUOTENAME(c.name,'''''''') AS ColumnNameWithApostrophes, c.column_id - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.partitions p - INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns c ON p.object_id = c.object_id - INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.index_columns ic on ic.column_id = c.column_id and ic.object_id = c.object_id AND ic.index_id = p.index_id - INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.types t ON c.system_type_id = t.system_type_id AND c.user_type_id = t.user_type_id - WHERE p.object_id = @ObjectID - AND EXISTS (SELECT * FROM ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_segments seg WHERE p.partition_id = seg.partition_id AND seg.column_id = ic.index_column_id) - AND p.data_compression IN (3,4) - ) - SELECT @ColumnList = @ColumnList + column_name + N'', '', - @ColumnListWithApostrophes = @ColumnListWithApostrophes + ColumnNameWithApostrophes + N'', '' - FROM DistinctColumns - ORDER BY column_id; - SELECT @PartitionCount = COUNT(1) FROM ' + QUOTENAME(@DatabaseName) + N'.sys.partitions WHERE object_id = @ObjectID AND data_compression IN (3,4); - END'; - - IF @Debug = 1 - BEGIN - PRINT SUBSTRING(@dsql, 0, 4000); - PRINT SUBSTRING(@dsql, 4000, 8000); - PRINT SUBSTRING(@dsql, 8000, 12000); - PRINT SUBSTRING(@dsql, 12000, 16000); - PRINT SUBSTRING(@dsql, 16000, 20000); - PRINT SUBSTRING(@dsql, 20000, 24000); - PRINT SUBSTRING(@dsql, 24000, 28000); - PRINT SUBSTRING(@dsql, 28000, 32000); - PRINT SUBSTRING(@dsql, 32000, 36000); - PRINT SUBSTRING(@dsql, 36000, 40000); - END; - - EXEC sp_executesql @dsql, N'@ObjectID INT, @ColumnList NVARCHAR(MAX) OUTPUT, @ColumnListWithApostrophes NVARCHAR(MAX) OUTPUT, @PartitionCount INT OUTPUT', @ObjectID, @ColumnList OUTPUT, @ColumnListWithApostrophes OUTPUT, @PartitionCount OUTPUT; - - IF @PartitionCount < 2 - SET @ShowPartitionRanges = 0; - - IF @Debug = 1 - SELECT @ColumnList AS ColumnstoreColumnList, @ColumnListWithApostrophes AS ColumnstoreColumnListWithApostrophes, @PartitionCount AS PartitionCount, @ShowPartitionRanges AS ShowPartitionRanges; - - IF @ColumnList <> '' - BEGIN - /* Remove the trailing comma */ - SET @ColumnList = LEFT(@ColumnList, LEN(@ColumnList) - 1); - SET @ColumnListWithApostrophes = LEFT(@ColumnListWithApostrophes, LEN(@ColumnListWithApostrophes) - 1); - - SET @dsql = N'USE ' + QUOTENAME(@DatabaseName) + N'; - SELECT partition_number, ' - + CASE WHEN @ShowPartitionRanges = 1 THEN N' COALESCE(range_start_op + '' '' + range_start + '' '', '''') + COALESCE(range_end_op + '' '' + range_end, '''') AS partition_range, ' ELSE N' ' END - + N' row_group_id, total_rows, deleted_rows, ' - + @ColumnList - + CASE WHEN @ShowPartitionRanges = 1 THEN N' , - state_desc, trim_reason_desc, transition_to_compressed_state_desc, has_vertipaq_optimization - FROM ( - SELECT column_name, partition_number, row_group_id, total_rows, deleted_rows, details, - range_start_op, - CASE - WHEN format_type IS NULL THEN CAST(range_start_value AS NVARCHAR(4000)) - ELSE CONVERT(NVARCHAR(4000), range_start_value, format_type) END range_start, - range_end_op, - CASE - WHEN format_type IS NULL THEN CAST(range_end_value AS NVARCHAR(4000)) - ELSE CONVERT(NVARCHAR(4000), range_end_value, format_type) END range_end' ELSE N' ' END + N', - state_desc, trim_reason_desc, transition_to_compressed_state_desc, has_vertipaq_optimization - FROM ( - SELECT c.name AS column_name, p.partition_number, rg.row_group_id, rg.total_rows, rg.deleted_rows, - phys.state_desc, phys.trim_reason_desc, phys.transition_to_compressed_state_desc, phys.has_vertipaq_optimization, - details = CAST(seg.min_data_id AS VARCHAR(20)) + '' to '' + CAST(seg.max_data_id AS VARCHAR(20)) + '', '' + CAST(CAST(((COALESCE(d.on_disk_size,0) + COALESCE(seg.on_disk_size,0)) / 1024.0 / 1024) AS DECIMAL(18,0)) AS VARCHAR(20)) + '' MB''' - + CASE WHEN @ShowPartitionRanges = 1 THEN N', - CASE - WHEN pp.system_type_id IN (40, 41, 42, 43, 58, 61) THEN 126 - WHEN pp.system_type_id IN (59, 62) THEN 3 - WHEN pp.system_type_id IN (60, 122) THEN 2 - ELSE NULL END format_type, - CASE WHEN pf.boundary_value_on_right = 0 THEN ''>'' ELSE ''>='' END range_start_op, - prvs.value range_start_value, - CASE WHEN pf.boundary_value_on_right = 0 THEN ''<='' ELSE ''<'' END range_end_op, - prve.value range_end_value ' ELSE N' ' END + N' - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_row_groups rg - INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns c ON rg.object_id = c.object_id - INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partitions p ON rg.object_id = p.object_id AND rg.partition_number = p.partition_number - INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.index_columns ic on ic.column_id = c.column_id AND ic.object_id = c.object_id AND ic.index_id = p.index_id - LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_column_store_row_group_physical_stats phys ON rg.row_group_id = phys.row_group_id AND rg.object_id = phys.object_id AND rg.partition_number = phys.partition_number AND rg.index_id = phys.index_id ' + CASE WHEN @ShowPartitionRanges = 1 THEN N' - LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.indexes i ON i.object_id = rg.object_id AND i.index_id = rg.index_id - LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partition_schemes ps ON ps.data_space_id = i.data_space_id - LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partition_functions pf ON pf.function_id = ps.function_id - LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partition_parameters pp ON pp.function_id = pf.function_id - LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partition_range_values prvs ON prvs.function_id = pf.function_id AND prvs.boundary_id = p.partition_number - 1 - LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partition_range_values prve ON prve.function_id = pf.function_id AND prve.boundary_id = p.partition_number ' ELSE N' ' END - + N' LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_segments seg ON p.partition_id = seg.partition_id AND ic.index_column_id = seg.column_id AND rg.row_group_id = seg.segment_id - LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_dictionaries d ON p.hobt_id = d.hobt_id AND c.column_id = d.column_id AND seg.secondary_dictionary_id = d.dictionary_id - WHERE rg.object_id = @ObjectID - AND rg.state IN (1, 2, 3) - AND c.name IN ( ' + @ColumnListWithApostrophes + N')' - + CASE WHEN @ShowPartitionRanges = 1 THEN N' - ) AS y ' ELSE N' ' END + N' - ) AS x - PIVOT (MAX(details) FOR column_name IN ( ' + @ColumnList + N')) AS pivot1 - ORDER BY partition_number, row_group_id;'; - - IF @Debug = 1 - BEGIN - PRINT SUBSTRING(@dsql, 0, 4000); - PRINT SUBSTRING(@dsql, 4000, 8000); - PRINT SUBSTRING(@dsql, 8000, 12000); - PRINT SUBSTRING(@dsql, 12000, 16000); - PRINT SUBSTRING(@dsql, 16000, 20000); - PRINT SUBSTRING(@dsql, 20000, 24000); - PRINT SUBSTRING(@dsql, 24000, 28000); - PRINT SUBSTRING(@dsql, 28000, 32000); - PRINT SUBSTRING(@dsql, 32000, 36000); - PRINT SUBSTRING(@dsql, 36000, 40000); - END; - - IF @dsql IS NULL - RAISERROR('@dsql is null',16,1); - ELSE - EXEC sp_executesql @dsql, N'@ObjectID INT', @ObjectID; - END - ELSE /* No columns were found for this object */ - BEGIN - SELECT N'No compressed columnstore rowgroups were found for this object.' AS Columnstore_Visualization - UNION ALL - SELECT N'SELECT * FROM ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_row_groups WHERE object_id = ' + CAST(@ObjectID AS NVARCHAR(100)); - END - RAISERROR(N'Done visualizing columnstore index contents.', 0,1) WITH NOWAIT; - END - - IF @ShowColumnstoreOnly = 1 - RETURN; - -END; /* IF @TableName IS NOT NULL */ - - - - - - - - -ELSE /* @TableName IS NULL, so we operate in normal mode 0/1/2/3/4 */ -BEGIN - -/* Validate and check table output params */ - - - /* Checks if @OutputServerName is populated with a valid linked server, and that the database name specified is valid */ - DECLARE @ValidOutputServer BIT; - DECLARE @ValidOutputLocation BIT; - DECLARE @LinkedServerDBCheck NVARCHAR(2000); - DECLARE @ValidLinkedServerDB INT; - DECLARE @tmpdbchk TABLE (cnt INT); - - IF @OutputServerName IS NOT NULL - BEGIN - IF (SUBSTRING(@OutputTableName, 2, 1) = '#') - BEGIN - RAISERROR('Due to the nature of temporary tables, outputting to a linked server requires a permanent table.', 16, 0); - END; - ELSE IF EXISTS (SELECT server_id FROM sys.servers WHERE QUOTENAME([name]) = @OutputServerName) - BEGIN - SET @LinkedServerDBCheck = 'SELECT 1 WHERE EXISTS (SELECT * FROM '+@OutputServerName+'.master.sys.databases WHERE QUOTENAME([name]) = '''+@OutputDatabaseName+''')'; - INSERT INTO @tmpdbchk EXEC sys.sp_executesql @LinkedServerDBCheck; - SET @ValidLinkedServerDB = (SELECT COUNT(*) FROM @tmpdbchk); - IF (@ValidLinkedServerDB > 0) - BEGIN - SET @ValidOutputServer = 1; - SET @ValidOutputLocation = 1; - END; - ELSE - RAISERROR('The specified database was not found on the output server', 16, 0); - END; - ELSE - BEGIN - RAISERROR('The specified output server was not found', 16, 0); - END; - END; - ELSE - BEGIN - IF (SUBSTRING(@OutputTableName, 2, 2) = '##') - BEGIN - SET @StringToExecute = N' IF (OBJECT_ID(''[tempdb].[dbo].@@@OutputTableName@@@'') IS NOT NULL) DROP TABLE @@@OutputTableName@@@'; - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); - EXEC(@StringToExecute); - - SET @OutputServerName = QUOTENAME(CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128))); - SET @OutputDatabaseName = '[tempdb]'; - SET @OutputSchemaName = '[dbo]'; - SET @ValidOutputLocation = 1; - END; - ELSE IF (SUBSTRING(@OutputTableName, 2, 1) = '#') - BEGIN - RAISERROR('Due to the nature of Dymamic SQL, only global (i.e. double pound (##)) temp tables are supported for @OutputTableName', 16, 0); - END; - ELSE IF @OutputDatabaseName IS NOT NULL - AND @OutputSchemaName IS NOT NULL - AND @OutputTableName IS NOT NULL - AND EXISTS ( SELECT * - FROM sys.databases - WHERE QUOTENAME([name]) = @OutputDatabaseName) - BEGIN - SET @ValidOutputLocation = 1; - SET @OutputServerName = QUOTENAME(CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128))); - END; - ELSE IF @OutputDatabaseName IS NOT NULL - AND @OutputSchemaName IS NOT NULL - AND @OutputTableName IS NOT NULL - AND NOT EXISTS ( SELECT * - FROM sys.databases - WHERE QUOTENAME([name]) = @OutputDatabaseName) - BEGIN - RAISERROR('The specified output database was not found on this server', 16, 0); - END; - ELSE - BEGIN - SET @ValidOutputLocation = 0; - END; - END; - - IF (@ValidOutputLocation = 0 AND @OutputType = 'NONE') - BEGIN - RAISERROR('Invalid output location and no output asked',12,1); - RETURN; - END; - - /* @OutputTableName lets us export the results to a permanent table */ - DECLARE @RunID UNIQUEIDENTIFIER; - SET @RunID = NEWID(); - - DECLARE @TableExists BIT; - DECLARE @SchemaExists BIT; - - DECLARE @TableExistsSql NVARCHAR(MAX); - - IF (@ValidOutputLocation = 1 AND COALESCE(@OutputServerName, @OutputDatabaseName, @OutputSchemaName, @OutputTableName) IS NOT NULL) - BEGIN - SET @StringToExecute = - N'SET @SchemaExists = 0; - SET @TableExists = 0; - IF EXISTS(SELECT * FROM @@@OutputServerName@@@.@@@OutputDatabaseName@@@.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''@@@OutputSchemaName@@@'') - SET @SchemaExists = 1 - IF EXISTS (SELECT * FROM @@@OutputServerName@@@.@@@OutputDatabaseName@@@.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''@@@OutputSchemaName@@@'' AND QUOTENAME(TABLE_NAME) = ''@@@OutputTableName@@@'') - BEGIN - SET @TableExists = 1 - IF NOT EXISTS(SELECT * FROM @@@OutputServerName@@@.@@@OutputDatabaseName@@@.INFORMATION_SCHEMA.COLUMNS WHERE QUOTENAME(TABLE_SCHEMA) = ''@@@OutputSchemaName@@@'' - AND QUOTENAME(TABLE_NAME) = ''@@@OutputTableName@@@'' AND QUOTENAME(COLUMN_NAME) = ''[total_forwarded_fetch_count]'') - EXEC @@@OutputServerName@@@.@@@OutputDatabaseName@@@.dbo.sp_executesql N''ALTER TABLE @@@OutputSchemaName@@@.@@@OutputTableName@@@ ADD [total_forwarded_fetch_count] BIGINT'' - END'; - - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputServerName@@@', @OutputServerName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputSchemaName@@@', @OutputSchemaName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); - - EXEC sp_executesql @StringToExecute, N'@TableExists BIT OUTPUT, @SchemaExists BIT OUTPUT', @TableExists OUTPUT, @SchemaExists OUTPUT; - - - SET @TableExistsSql = - N'IF EXISTS(SELECT * FROM @@@OutputServerName@@@.@@@OutputDatabaseName@@@.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''@@@OutputSchemaName@@@'') - AND NOT EXISTS (SELECT * FROM @@@OutputServerName@@@.@@@OutputDatabaseName@@@.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''@@@OutputSchemaName@@@'' AND QUOTENAME(TABLE_NAME) = ''@@@OutputTableName@@@'') - SET @TableExists = 0 - ELSE - SET @TableExists = 1'; - - SET @TableExistsSql = REPLACE(@TableExistsSql, '@@@OutputServerName@@@', @OutputServerName); - SET @TableExistsSql = REPLACE(@TableExistsSql, '@@@OutputDatabaseName@@@', @OutputDatabaseName); - SET @TableExistsSql = REPLACE(@TableExistsSql, '@@@OutputSchemaName@@@', @OutputSchemaName); - SET @TableExistsSql = REPLACE(@TableExistsSql, '@@@OutputTableName@@@', @OutputTableName); - - END - - - - - IF @Mode IN (0, 4) /* DIAGNOSE */ - BEGIN; - IF @Mode IN (0, 4) /* DIAGNOSE priorities 1-100 */ - BEGIN; - RAISERROR(N'@Mode=0 or 4, running rules for priorities 1-100.', 0,1) WITH NOWAIT; - - ---------------------------------------- - --Multiple Index Personalities: Check_id 0-10 - ---------------------------------------- - RAISERROR('check_id 1: Duplicate keys', 0,1) WITH NOWAIT; - WITH duplicate_indexes - AS ( SELECT [object_id], key_column_names, database_id, [schema_name] - FROM #IndexSanity AS ip - WHERE index_type IN (1,2) /* Clustered, NC only*/ - AND is_hypothetical = 0 - AND is_disabled = 0 - AND is_primary_key = 0 - AND EXISTS ( - SELECT 1/0 - FROM #IndexSanitySize ips - WHERE ip.index_sanity_id = ips.index_sanity_id - AND ip.database_id = ips.database_id - AND ip.schema_name = ips.schema_name - AND ips.total_reserved_MB >= CASE - WHEN (@GetAllDatabases = 1 OR @Mode = 0) - THEN @ThresholdMB - ELSE ips.total_reserved_MB - END - ) - GROUP BY [object_id], key_column_names, database_id, [schema_name] - HAVING COUNT(*) > 1) - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 1 AS check_id, - ip.index_sanity_id, - 20 AS Priority, - 'Multiple Index Personalities' AS findings_group, - 'Duplicate keys' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/duplicateindex' AS URL, - N'Index Name: ' + ip.index_name + N' Table Name: ' + ip.db_schema_object_name AS details, - ip.index_definition, - ip.secret_columns, - ip.index_usage_summary, - ips.index_size_summary - FROM duplicate_indexes di - JOIN #IndexSanity ip ON di.[object_id] = ip.[object_id] - AND ip.database_id = di.database_id - AND ip.[schema_name] = di.[schema_name] - AND di.key_column_names = ip.key_column_names - JOIN #IndexSanitySize ips ON ip.index_sanity_id = ips.index_sanity_id - AND ip.database_id = ips.database_id - AND ip.schema_name = ips.schema_name - /* WHERE clause limits to only @ThresholdMB or larger duplicate indexes when getting all databases or using PainRelief mode */ - WHERE ips.total_reserved_MB >= CASE WHEN (@GetAllDatabases = 1 OR @Mode = 0) THEN @ThresholdMB ELSE ips.total_reserved_MB END - AND ip.is_primary_key = 0 - ORDER BY ips.total_rows DESC, ip.[schema_name], ip.[object_name], ip.key_column_names_with_sort_order - OPTION ( RECOMPILE ); - - RAISERROR('check_id 2: Keys w/ identical leading columns.', 0,1) WITH NOWAIT; - WITH borderline_duplicate_indexes - AS ( SELECT DISTINCT database_id, [object_id], first_key_column_name, key_column_names, - COUNT([object_id]) OVER ( PARTITION BY database_id, [object_id], first_key_column_name ) AS number_dupes - FROM #IndexSanity - WHERE index_type IN (1,2) /* Clustered, NC only*/ - AND is_hypothetical=0 - AND is_disabled=0 - AND is_primary_key = 0) - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 2 AS check_id, - ip.index_sanity_id, - 30 AS Priority, - 'Multiple Index Personalities' AS findings_group, - 'Borderline duplicate keys' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/duplicateindex' AS URL, - ip.db_schema_object_indexid AS details, - ip.index_definition, - ip.secret_columns, - ip.index_usage_summary, - ips.index_size_summary - FROM #IndexSanity AS ip - JOIN #IndexSanitySize ips ON ip.index_sanity_id = ips.index_sanity_id - WHERE EXISTS ( - SELECT di.[object_id] - FROM borderline_duplicate_indexes AS di - WHERE di.[object_id] = ip.[object_id] AND - di.database_id = ip.database_id AND - di.first_key_column_name = ip.first_key_column_name AND - di.key_column_names <> ip.key_column_names AND - di.number_dupes > 1 - ) - AND ip.is_primary_key = 0 - ORDER BY ips.total_rows DESC, ip.[schema_name], ip.[object_name], ip.key_column_names, ip.include_column_names - OPTION ( RECOMPILE ); - - ---------------------------------------- - --Aggressive Indexes: Check_id 10-19 - ---------------------------------------- - - RAISERROR(N'check_id 11: Total lock wait time > 5 minutes (row + page)', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 11 AS check_id, - i.index_sanity_id, - 70 AS Priority, - N'Aggressive ' - + CASE COALESCE((SELECT SUM(1) - FROM #IndexSanity iMe - INNER JOIN #IndexSanity iOthers - ON iMe.database_id = iOthers.database_id - AND iMe.object_id = iOthers.object_id - AND iOthers.index_id > 1 - WHERE i.index_sanity_id = iMe.index_sanity_id - AND iOthers.is_hypothetical = 0 - AND iOthers.is_disabled = 0 - ), 0) - WHEN 0 THEN N'Under-Indexing' - WHEN 1 THEN N'Under-Indexing' - WHEN 2 THEN N'Under-Indexing' - WHEN 3 THEN N'Under-Indexing' - WHEN 4 THEN N'Indexes' - WHEN 5 THEN N'Indexes' - WHEN 6 THEN N'Indexes' - WHEN 7 THEN N'Indexes' - WHEN 8 THEN N'Indexes' - WHEN 9 THEN N'Indexes' - ELSE N'Over-Indexing' - END AS findings_group, - N'Total lock wait time > 5 minutes (row + page)' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/AggressiveIndexes' AS URL, - (i.db_schema_object_indexid + N': ' + - sz.index_lock_wait_summary + N' NC indexes on table: ') COLLATE DATABASE_DEFAULT + - CAST(COALESCE((SELECT SUM(1) - FROM #IndexSanity iMe - INNER JOIN #IndexSanity iOthers - ON iMe.database_id = iOthers.database_id - AND iMe.object_id = iOthers.object_id - AND iOthers.index_id > 1 - WHERE i.index_sanity_id = iMe.index_sanity_id - AND iOthers.is_hypothetical = 0 - AND iOthers.is_disabled = 0 - ), 0) - AS NVARCHAR(30)) AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - sz.index_size_summary - FROM #IndexSanity AS i - JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id - WHERE (total_row_lock_wait_in_ms + total_page_lock_wait_in_ms) > 300000 - GROUP BY i.index_sanity_id, [database_name], i.db_schema_object_indexid, sz.index_lock_wait_summary, i.index_definition, i.secret_columns, i.index_usage_summary, sz.index_size_summary, sz.index_sanity_id - ORDER BY SUM(total_row_lock_wait_in_ms + total_page_lock_wait_in_ms) DESC, 4, [database_name], 8 - OPTION ( RECOMPILE ); - - - - ---------------------------------------- - --Index Hoarder: Check_id 20-29 - ---------------------------------------- - RAISERROR(N'check_id 20: >= 10 NC indexes on any given table. Yes, 10 is an arbitrary number.', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 20 AS check_id, - MAX(i.index_sanity_id) AS index_sanity_id, - 10 AS Priority, - 'Index Hoarder' AS findings_group, - 'Many NC Indexes on a Single Table' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/IndexHoarder' AS URL, - CAST (COUNT(*) AS NVARCHAR(30)) + ' NC indexes on ' + i.db_schema_object_name AS details, - i.db_schema_object_name + ' (' + CAST (COUNT(*) AS NVARCHAR(30)) + ' indexes)' AS index_definition, - '' AS secret_columns, - REPLACE(CONVERT(NVARCHAR(30),CAST(SUM(total_reads) AS MONEY), 1), N'.00', N'') + N' reads (ALL); ' - + REPLACE(CONVERT(NVARCHAR(30),CAST(SUM(user_updates) AS MONEY), 1), N'.00', N'') + N' writes (ALL); ', - REPLACE(CONVERT(NVARCHAR(30),CAST(MAX(total_rows) AS MONEY), 1), N'.00', N'') + N' rows (MAX)' - + CASE WHEN SUM(total_reserved_MB) > 1024 THEN - N'; ' + CAST(CAST(SUM(total_reserved_MB)/1024. AS NUMERIC(29,1)) AS NVARCHAR(30)) + 'GB (ALL)' - WHEN SUM(total_reserved_MB) > 0 THEN - N'; ' + CAST(CAST(SUM(total_reserved_MB) AS NUMERIC(29,1)) AS NVARCHAR(30)) + 'MB (ALL)' - ELSE '' - END AS index_size_summary - FROM #IndexSanity i - JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id - WHERE index_id NOT IN ( 0, 1 ) - GROUP BY db_schema_object_name, [i].[database_name] - HAVING COUNT(*) >= 10 - ORDER BY i.db_schema_object_name DESC - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 22: NC indexes with 0 reads. (Borderline) and >= 10,000 writes', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 22 AS check_id, - i.index_sanity_id, - 10 AS Priority, - N'Index Hoarder' AS findings_group, - N'Unused NC Index with High Writes' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/IndexHoarder' AS URL, - N'Reads: 0,' - + N' Writes: ' - + REPLACE(CONVERT(NVARCHAR(30), CAST((i.user_updates) AS MONEY), 1), N'.00', N'') - + N' on: ' - + i.db_schema_object_indexid - AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - sz.index_size_summary - FROM #IndexSanity AS i - JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.total_reads=0 - AND i.user_updates >= 10000 - 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 - AND @Filter <> 1 /* 1 = "ignore unused */ - ORDER BY i.db_schema_object_indexid - OPTION ( RECOMPILE ); - - - RAISERROR(N'check_id 34: Filtered index definition columns not in index definition', 0,1) WITH NOWAIT; - - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 34 AS check_id, - i.index_sanity_id, - 80 AS Priority, - N'Abnormal Psychology' AS findings_group, - N'Filter Columns Not In Index Definition' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/IndexFeatures' AS URL, - N'The index ' - + QUOTENAME(i.index_name) - + N' on [' - + i.db_schema_object_name - + N'] has a filter on [' - + i.filter_definition - + N'] but is missing [' - + LTRIM(i.filter_columns_not_in_index) - + N'] from the index definition.' - AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - sz.index_size_summary - FROM #IndexSanity i - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.filter_columns_not_in_index IS NOT NULL - ORDER BY i.db_schema_object_indexid - OPTION ( RECOMPILE ); - - ---------------------------------------- - --Self Loathing Indexes : Check_id 40-49 - ---------------------------------------- - - RAISERROR(N'check_id 40: Fillfactor in nonclustered 80 percent or less', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 40 AS check_id, - i.index_sanity_id, - 100 AS Priority, - N'Self Loathing Indexes' AS findings_group, - N'Low Fill Factor on Nonclustered Index' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/SelfLoathing' AS URL, - CAST(fill_factor AS NVARCHAR(10)) + N'% fill factor on ' + db_schema_object_indexid + N'. '+ - CASE WHEN (last_user_update IS NULL OR user_updates < 1) - THEN N'No writes have been made.' - ELSE - N'Last write was ' + CONVERT(NVARCHAR(16),last_user_update,121) + N' and ' + - CAST(user_updates AS NVARCHAR(25)) + N' updates have been made.' - END - AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - sz.index_size_summary - FROM #IndexSanity AS i - JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id - WHERE index_id > 1 - AND fill_factor BETWEEN 1 AND 80 OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 40: Fillfactor in clustered 80 percent or less', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 40 AS check_id, - i.index_sanity_id, - 100 AS Priority, - N'Self Loathing Indexes' AS findings_group, - N'Low Fill Factor on Clustered Index' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/SelfLoathing' AS URL, - N'Fill factor on ' + db_schema_object_indexid + N' is ' + CAST(fill_factor AS NVARCHAR(10)) + N'%. '+ - CASE WHEN (last_user_update IS NULL OR user_updates < 1) - THEN N'No writes have been made.' - ELSE - N'Last write was ' + CONVERT(NVARCHAR(16),last_user_update,121) + N' and ' + - CAST(user_updates AS NVARCHAR(25)) + N' updates have been made.' - END - AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - sz.index_size_summary - FROM #IndexSanity AS i - JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id - WHERE index_id = 1 - AND fill_factor BETWEEN 1 AND 80 OPTION ( RECOMPILE ); - - - RAISERROR(N'check_id 43: Heaps with forwarded records', 0,1) WITH NOWAIT; - WITH heaps_cte - AS ( SELECT [object_id], - [database_id], - [schema_name], - SUM(forwarded_fetch_count) AS forwarded_fetch_count, - SUM(leaf_delete_count) AS leaf_delete_count - FROM #IndexPartitionSanity - GROUP BY [object_id], - [database_id], - [schema_name] - HAVING SUM(forwarded_fetch_count) > 0) - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 43 AS check_id, - i.index_sanity_id, - 100 AS Priority, - N'Self Loathing Indexes' AS findings_group, - N'Heaps with Forwarded Fetches' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/SelfLoathing' AS URL, - CASE WHEN h.forwarded_fetch_count >= 922337203685477 THEN '>= 922,337,203,685,477' - WHEN @DaysUptime < 1 THEN CAST(h.forwarded_fetch_count AS NVARCHAR(256)) + N' forwarded fetches against heap: ' + db_schema_object_indexid - ELSE REPLACE(CONVERT(NVARCHAR(256),CAST(CAST( - (h.forwarded_fetch_count /*/@DaysUptime */) - AS BIGINT) AS MONEY), 1), '.00', '') - END + N' forwarded fetches per day against heap: ' - + db_schema_object_indexid AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - sz.index_size_summary - FROM #IndexSanity i - JOIN heaps_cte h ON i.[object_id] = h.[object_id] - AND i.[database_id] = h.[database_id] - AND i.[schema_name] = h.[schema_name] - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.index_id = 0 - AND h.forwarded_fetch_count / @DaysUptime > 1000 - AND sz.total_reserved_MB >= CASE WHEN NOT (@GetAllDatabases = 1 OR @Mode = 4) THEN @ThresholdMB ELSE sz.total_reserved_MB END - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 44: Large Heaps with reads or writes.', 0,1) WITH NOWAIT; - WITH heaps_cte - AS ( SELECT [object_id], - [database_id], - [schema_name], - SUM(forwarded_fetch_count) AS forwarded_fetch_count, - SUM(leaf_delete_count) AS leaf_delete_count - FROM #IndexPartitionSanity - GROUP BY [object_id], - [database_id], - [schema_name] - HAVING SUM(forwarded_fetch_count) > 0 - OR SUM(leaf_delete_count) > 0) - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 44 AS check_id, - i.index_sanity_id, - 100 AS Priority, - N'Self Loathing Indexes' AS findings_group, - N'Large Active Heap' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/SelfLoathing' AS URL, - N'Should this table be a heap? ' + db_schema_object_indexid AS details, - i.index_definition, - 'N/A' AS secret_columns, - i.index_usage_summary, - sz.index_size_summary - FROM #IndexSanity i - LEFT JOIN heaps_cte h ON i.[object_id] = h.[object_id] - AND i.[database_id] = h.[database_id] - AND i.[schema_name] = h.[schema_name] - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.index_id = 0 - AND (i.total_reads > 0 OR i.user_updates > 0) - AND sz.total_rows >= 100000 - AND h.[object_id] IS NULL /*don't duplicate the prior check.*/ - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 45: Medium Heaps with reads or writes.', 0,1) WITH NOWAIT; - WITH heaps_cte - AS ( SELECT [object_id], - [database_id], - [schema_name], - SUM(forwarded_fetch_count) AS forwarded_fetch_count, - SUM(leaf_delete_count) AS leaf_delete_count - FROM #IndexPartitionSanity - GROUP BY [object_id], - [database_id], - [schema_name] - HAVING SUM(forwarded_fetch_count) > 0 - OR SUM(leaf_delete_count) > 0) - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 45 AS check_id, - i.index_sanity_id, - 100 AS Priority, - N'Self Loathing Indexes' AS findings_group, - N'Medium Active heap' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/SelfLoathing' AS URL, - N'Should this table be a heap? ' + db_schema_object_indexid AS details, - i.index_definition, - 'N/A' AS secret_columns, - i.index_usage_summary, - sz.index_size_summary - FROM #IndexSanity i - LEFT JOIN heaps_cte h ON i.[object_id] = h.[object_id] - AND i.[database_id] = h.[database_id] - AND i.[schema_name] = h.[schema_name] - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.index_id = 0 - AND - (i.total_reads > 0 OR i.user_updates > 0) - AND sz.total_rows >= 10000 AND sz.total_rows < 100000 - AND h.[object_id] IS NULL /*don't duplicate the prior check.*/ - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 46: Small Heaps with reads or writes.', 0,1) WITH NOWAIT; - WITH heaps_cte - AS ( SELECT [object_id], - [database_id], - [schema_name], - SUM(forwarded_fetch_count) AS forwarded_fetch_count, - SUM(leaf_delete_count) AS leaf_delete_count - FROM #IndexPartitionSanity - GROUP BY [object_id], - [database_id], - [schema_name] - HAVING SUM(forwarded_fetch_count) > 0 - OR SUM(leaf_delete_count) > 0) - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 46 AS check_id, - i.index_sanity_id, - 100 AS Priority, - N'Self Loathing Indexes' AS findings_group, - N'Small Active heap' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/SelfLoathing' AS URL, - N'Should this table be a heap? ' + db_schema_object_indexid AS details, - i.index_definition, - 'N/A' AS secret_columns, - i.index_usage_summary, - sz.index_size_summary - FROM #IndexSanity i - LEFT JOIN heaps_cte h ON i.[object_id] = h.[object_id] - AND i.[database_id] = h.[database_id] - AND i.[schema_name] = h.[schema_name] - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.index_id = 0 - AND - (i.total_reads > 0 OR i.user_updates > 0) - AND sz.total_rows < 10000 - AND h.[object_id] IS NULL /*don't duplicate the prior check.*/ - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 47: Heap with a Nonclustered Primary Key', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 47 AS check_id, - i.index_sanity_id, - 100 AS Priority, - N'Self Loathing Indexes' AS findings_group, - N'Heap with a Nonclustered Primary Key' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/SelfLoathing' AS URL, - db_schema_object_indexid + N' is a HEAP with a Nonclustered Primary Key' AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - sz.index_size_summary - FROM #IndexSanity i - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.index_type = 2 AND i.is_primary_key = 1 - AND EXISTS - ( - SELECT 1/0 - FROM #IndexSanity AS isa - WHERE i.database_id = isa.database_id - AND i.object_id = isa.object_id - AND isa.index_id = 0 - ) - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 48: Nonclustered indexes with a bad read to write ratio', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 48 AS check_id, - i.index_sanity_id, - 100 AS Priority, - N'Index Hoarder' AS findings_group, - N'NC index with High Writes:Reads' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/IndexHoarder' AS URL, - N'Reads: ' - + REPLACE(CONVERT(NVARCHAR(30), CAST((i.total_reads) AS MONEY), 1), N'.00', N'') - + N' Writes: ' - + REPLACE(CONVERT(NVARCHAR(30), CAST((i.user_updates) AS MONEY), 1), N'.00', N'') - + N' on: ' - + i.db_schema_object_indexid AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - sz.index_size_summary - FROM #IndexSanity i - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.total_reads > 0 /*Not totally unused*/ - AND i.user_updates >= 10000 /*Decent write activity*/ - AND i.total_reads < 10000 - AND ((i.total_reads * 10) < i.user_updates) /*10x more writes than reads*/ - 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 - ORDER BY i.db_schema_object_indexid - OPTION ( RECOMPILE ); - - ---------------------------------------- - --Indexaphobia - --Missing indexes with value >= 5 million: : Check_id 50-59 - ---------------------------------------- - RAISERROR(N'check_id 50: Indexaphobia.', 0,1) WITH NOWAIT; - WITH index_size_cte - AS ( SELECT i.database_id, - i.schema_name, - i.[object_id], - MAX(i.index_sanity_id) AS index_sanity_id, - ISNULL(NULLIF(MAX(DATEDIFF(DAY, i.create_date, SYSDATETIME())), 0), 1) AS create_days, - ISNULL ( - CAST(SUM(CASE WHEN index_id NOT IN (0,1) THEN 1 ELSE 0 END) - AS NVARCHAR(30))+ N' NC indexes exist (' + - CASE WHEN SUM(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) > 1024 - THEN CAST(CAST(SUM(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END )/1024. - - AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'GB); ' - ELSE CAST(SUM(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) - AS NVARCHAR(30)) + N'MB); ' - END + - CASE WHEN MAX(sz.[total_rows]) >= 922337203685477 THEN '>= 922,337,203,685,477' - ELSE REPLACE(CONVERT(NVARCHAR(30),CAST(MAX(sz.[total_rows]) AS MONEY), 1), '.00', '') - END + - + N' Estimated Rows;' - ,N'') AS index_size_summary - FROM #IndexSanity AS i - LEFT JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id AND i.database_id = sz.database_id - WHERE i.is_hypothetical = 0 - AND i.is_disabled = 0 - GROUP BY i.database_id, i.schema_name, i.[object_id]) - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - index_usage_summary, index_size_summary, create_tsql, more_info, sample_query_plan ) - - SELECT check_id, t.index_sanity_id, t.check_id, t.findings_group, t.finding, t.[Database Name], t.URL, t.details, t.[definition], - index_estimated_impact, t.index_size_summary, create_tsql, more_info, sample_query_plan - FROM - ( - SELECT ROW_NUMBER() OVER (ORDER BY magic_benefit_number DESC) AS rownum, - 50 AS check_id, - sz.index_sanity_id, - 40 AS Priority, - N'Indexaphobia' AS findings_group, - N'High Value Missing Index' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/Indexaphobia' AS URL, - mi.[statement] + - N' Est. benefit per day: ' + - CASE WHEN magic_benefit_number >= 922337203685477 THEN '>= 922,337,203,685,477' - ELSE REPLACE(CONVERT(NVARCHAR(256),CAST(CAST( - (magic_benefit_number/@DaysUptime) - AS BIGINT) AS MONEY), 1), '.00', '') - END AS details, - missing_index_details AS [definition], - index_estimated_impact, - sz.index_size_summary, - mi.create_tsql, - mi.more_info, - magic_benefit_number, - mi.is_low, - mi.sample_query_plan - FROM #MissingIndexes mi - LEFT JOIN index_size_cte sz ON mi.[object_id] = sz.object_id - AND mi.database_id = sz.database_id - AND mi.schema_name = sz.schema_name - /* Minimum benefit threshold = 100k/day of uptime OR since table creation date, whichever is lower*/ - WHERE @ShowAllMissingIndexRequests=1 - OR ( @Mode = 4 AND (magic_benefit_number / CASE WHEN sz.create_days < @DaysUptime THEN sz.create_days ELSE @DaysUptime END) >= 100000 ) - OR (magic_benefit_number / CASE WHEN sz.create_days < @DaysUptime THEN sz.create_days ELSE @DaysUptime END) >= 100000 - ) AS t - WHERE t.rownum <= CASE WHEN (@Mode <> 4) THEN 20 ELSE t.rownum END - ORDER BY magic_benefit_number DESC - OPTION ( RECOMPILE ); - - - - RAISERROR(N'check_id 68: Identity columns within 30 percent of the end of range', 0,1) WITH NOWAIT; - -- Allowed Ranges: - --int -2,147,483,648 to 2,147,483,647 - --smallint -32,768 to 32,768 - --tinyint 0 to 255 - - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 68 AS check_id, - i.index_sanity_id, - 80 AS Priority, - N'Abnormal Psychology' AS findings_group, - N'Identity Column Within ' + - CAST (calc1.percent_remaining AS NVARCHAR(256)) - + N' Percent End of Range' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, - i.db_schema_object_name + N'.' + QUOTENAME(ic.column_name) - + N' is an identity with type ' + ic.system_type_name - + N', last value of ' - + ISNULL((CONVERT(NVARCHAR(256),CAST(ic.last_value AS DECIMAL(38,0)), 1)),N'NULL') - + N', seed of ' - + ISNULL((CONVERT(NVARCHAR(256),CAST(ic.seed_value AS DECIMAL(38,0)), 1)),N'NULL') - + N', increment of ' + CAST(ic.increment_value AS NVARCHAR(256)) - + N', and range of ' + - CASE ic.system_type_name WHEN 'int' THEN N'+/- 2,147,483,647' - WHEN 'smallint' THEN N'+/- 32,768' - WHEN 'tinyint' THEN N'0 to 255' - ELSE 'unknown' - END - AS details, - i.index_definition, - secret_columns, - ISNULL(i.index_usage_summary,''), - ISNULL(ip.index_size_summary,'') - FROM #IndexSanity i - JOIN #IndexColumns ic ON - i.object_id=ic.object_id - AND i.database_id = ic.database_id - AND i.schema_name = ic.schema_name - AND i.index_id IN (0,1) /* heaps and cx only */ - AND ic.is_identity=1 - AND ic.system_type_name IN ('tinyint', 'smallint', 'int') - JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id - CROSS APPLY ( - SELECT CAST(CASE WHEN ic.increment_value >= 0 - THEN - CASE ic.system_type_name - WHEN 'int' THEN (2147483647 - (ISNULL(ic.last_value,ic.seed_value) + ic.increment_value)) / 2147483647.*100 - WHEN 'smallint' THEN (32768 - (ISNULL(ic.last_value,ic.seed_value) + ic.increment_value)) / 32768.*100 - WHEN 'tinyint' THEN ( 255 - (ISNULL(ic.last_value,ic.seed_value) + ic.increment_value)) / 255.*100 - ELSE 999 - END - ELSE --ic.increment_value is negative - CASE ic.system_type_name - WHEN 'int' THEN ABS(-2147483647 - (ISNULL(ic.last_value,ic.seed_value) + ic.increment_value)) / 2147483647.*100 - WHEN 'smallint' THEN ABS(-32768 - (ISNULL(ic.last_value,ic.seed_value) + ic.increment_value)) / 32768.*100 - WHEN 'tinyint' THEN ABS( 0 - (ISNULL(ic.last_value,ic.seed_value) + ic.increment_value)) / 255.*100 - ELSE -1 - END - END AS NUMERIC(5,1)) AS percent_remaining - ) AS calc1 - WHERE i.index_id IN (1,0) - AND calc1.percent_remaining <= 30 - OPTION (RECOMPILE); - - - RAISERROR(N'check_id 72: Columnstore indexes with Trace Flag 834', 0,1) WITH NOWAIT; - IF EXISTS (SELECT * FROM #IndexSanity WHERE index_type IN (5,6)) - AND EXISTS (SELECT * FROM #TraceStatus WHERE TraceFlag = 834 AND status = 1) - BEGIN - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 72 AS check_id, - i.index_sanity_id, - 80 AS Priority, - N'Abnormal Psychology' AS findings_group, - 'Columnstore Indexes with Trace Flag 834' AS finding, - [database_name] AS [Database Name], - N'https://support.microsoft.com/en-us/kb/3210239' AS URL, - i.db_schema_object_indexid AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - ISNULL(sz.index_size_summary,'') AS index_size_summary - FROM #IndexSanity AS i - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.index_type IN (5,6) - OPTION ( RECOMPILE ); - END; - - ---------------------------------------- - --Statistics Info: Check_id 90-99 - ---------------------------------------- - - RAISERROR(N'check_id 90: Outdated 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 ) - SELECT 90 AS check_id, - 90 AS Priority, - 'Functioning Statistaholics' AS findings_group, - 'Statistics Not Updated Recently', - s.database_name, - 'https://www.brentozar.com/go/stats' AS URL, - 'Statistics on this table were last updated ' + - CASE WHEN s.last_statistics_update IS NULL THEN N' NEVER ' - ELSE CONVERT(NVARCHAR(20), s.last_statistics_update) + - ' have had ' + CONVERT(NVARCHAR(100), s.modification_counter) + - ' modifications in that time, which is ' + - CONVERT(NVARCHAR(100), s.percent_modifications) + - '% of the table.' - END 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.last_statistics_update <= CONVERT(DATETIME, GETDATE() - 30) - AND s.percent_modifications >= 10. - AND s.rows >= 10000 - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 91: Statistics with a low 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 91 AS check_id, - 90 AS Priority, - 'Functioning Statistaholics' AS findings_group, - 'Low Sampling Rates', - s.database_name, - 'https://www.brentozar.com/go/stats' AS URL, - 'Only ' + CONVERT(NVARCHAR(100), s.percent_sampled) + '% of the rows were sampled during the last statistics update. This may lead to poor cardinality estimates.' 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.rows BETWEEN 10000 AND 1000000 AND s.percent_sampled < 10) - OR (s.rows > 1000000 AND s.percent_sampled < 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 ) - SELECT 92 AS check_id, - 90 AS Priority, - 'Functioning Statistaholics' AS findings_group, - 'Statistics With NO RECOMPUTE', - s.database_name, - 'https://www.brentozar.com/go/stats' AS URL, - 'The statistic ' + QUOTENAME(s.statistics_name) + ' is set to not recompute. This can be helpful if data is really skewed, but harmful if you expect automatic statistics updates.' 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.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 ) - SELECT 94 AS check_id, - 100 AS Priority, - 'Serial Forcer' AS findings_group, - 'Check Constraint with Scalar UDF' AS finding, - cc.database_name, - 'https://www.brentozar.com/go/computedscalar' AS URL, - 'The check constraint ' + QUOTENAME(cc.constraint_name) + ' on ' + QUOTENAME(cc.schema_name) + '.' + QUOTENAME(cc.table_name) + ' is based on ' + cc.definition - + '. That indicates it may reference a scalar function, or a CLR function with data access, which can cause all queries and maintenance to run serially.' AS details, - cc.column_definition, - 'N/A' AS secret_columns, - 'N/A' AS index_usage_summary, - 'N/A' AS index_size_summary - FROM #CheckConstraints AS cc - WHERE cc.is_function = 1 - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 99: Computed Columns 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 ) - SELECT 99 AS check_id, - 100 AS Priority, - 'Serial Forcer' AS findings_group, - 'Computed Column with Scalar UDF' AS finding, - cc.database_name, - 'https://www.brentozar.com/go/serialudf' AS URL, - 'The computed column ' + QUOTENAME(cc.column_name) + ' on ' + QUOTENAME(cc.schema_name) + '.' + QUOTENAME(cc.table_name) + ' is based on ' + cc.definition - + '. That indicates it may reference a scalar function, or a CLR function with data access, which can cause all queries and maintenance to run serially.' AS details, - cc.column_definition, - 'N/A' AS secret_columns, - 'N/A' AS index_usage_summary, - 'N/A' AS index_size_summary - FROM #ComputedColumns AS cc - WHERE cc.is_function = 1 - OPTION ( RECOMPILE ); - - - - END /* IF @Mode IN (0, 4) DIAGNOSE priorities 1-100 */ - - - - - - - - - IF @Mode = 4 /* DIAGNOSE*/ - BEGIN; - RAISERROR(N'@Mode=4, running rules for priorities 101+.', 0,1) WITH NOWAIT; - - RAISERROR(N'check_id 21: More Than 5 Percent NC Indexes Are Unused', 0,1) WITH NOWAIT; - DECLARE @percent_NC_indexes_unused NUMERIC(29,1); - DECLARE @NC_indexes_unused_reserved_MB NUMERIC(29,1); - - SELECT @percent_NC_indexes_unused = ( 100.00 * SUM(CASE - WHEN total_reads = 0 - THEN 1 - ELSE 0 - END) ) / COUNT(*), - @NC_indexes_unused_reserved_MB = SUM(CASE - WHEN total_reads = 0 - THEN sz.total_reserved_MB - ELSE 0 - END) - FROM #IndexSanity i - 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()) - 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, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 21 AS check_id, - MAX(i.index_sanity_id) AS index_sanity_id, - 150 AS Priority, - N'Index Hoarder' AS findings_group, - N'More Than 5 Percent NC Indexes Are Unused' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/IndexHoarder' AS URL, - CAST (@percent_NC_indexes_unused AS NVARCHAR(30)) + N' percent NC indexes (' + CAST(COUNT(*) AS NVARCHAR(10)) + N') unused. ' + - N'These take up ' + CAST (@NC_indexes_unused_reserved_MB AS NVARCHAR(30)) + N'MB of space.' AS details, - i.database_name + ' (' + CAST (COUNT(*) AS NVARCHAR(30)) + N' indexes)' AS index_definition, - '' AS secret_columns, - CAST(SUM(total_reads) AS NVARCHAR(256)) + N' reads (ALL); ' - + CAST(SUM([user_updates]) AS NVARCHAR(256)) + N' writes (ALL)' AS index_usage_summary, - - REPLACE(CONVERT(NVARCHAR(30),CAST(MAX([total_rows]) AS MONEY), 1), '.00', '') + N' rows (MAX)' - + CASE WHEN SUM(total_reserved_MB) > 1024 THEN - N'; ' + CAST(CAST(SUM(total_reserved_MB)/1024. AS NUMERIC(29,1)) AS NVARCHAR(30)) + 'GB (ALL)' - WHEN SUM(total_reserved_MB) > 0 THEN - N'; ' + CAST(CAST(SUM(total_reserved_MB) AS NUMERIC(29,1)) AS NVARCHAR(30)) + 'MB (ALL)' - ELSE '' - END AS index_size_summary - FROM #IndexSanity i - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - 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()) - GROUP BY i.database_name - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 23: Indexes with 7 or more columns. (Borderline)', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 23 AS check_id, - i.index_sanity_id, - 150 AS Priority, - N'Index Hoarder' AS findings_group, - N'Borderline: Wide Indexes (7 or More Columns)' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/IndexHoarder' AS URL, - CAST(count_key_columns + count_included_columns AS NVARCHAR(10)) + ' columns on ' - + i.db_schema_object_indexid AS details, i.index_definition, - i.secret_columns, - i.index_usage_summary, - sz.index_size_summary - FROM #IndexSanity AS i - JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id - WHERE ( count_key_columns + count_included_columns ) >= 7 - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 24: Wide clustered indexes (> 3 columns or > 16 bytes).', 0,1) WITH NOWAIT; - WITH count_columns AS ( - SELECT database_id, [object_id], - SUM(CASE max_length WHEN -1 THEN 0 ELSE max_length END) AS sum_max_length - FROM #IndexColumns ic - WHERE index_id IN (1,0) /*Heap or clustered only*/ - AND key_ordinal > 0 - GROUP BY database_id, object_id - ) - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 24 AS check_id, - i.index_sanity_id, - 150 AS Priority, - N'Index Hoarder' AS findings_group, - N'Wide Clustered Index (> 3 columns OR > 16 bytes)' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/IndexHoarder' AS URL, - CAST (i.count_key_columns AS NVARCHAR(10)) + N' columns with potential size of ' - + CAST(cc.sum_max_length AS NVARCHAR(10)) - + N' bytes in clustered index:' + i.db_schema_object_name - + N'. ' + - (SELECT CAST(COUNT(*) AS NVARCHAR(23)) - FROM #IndexSanity i2 - WHERE i2.[object_id]=i.[object_id] - AND i2.database_id = i.database_id - AND i2.index_id <> 1 - AND i2.is_disabled=0 - AND i2.is_hypothetical=0) - + N' NC indexes on the table.' - AS details, - i.index_definition, - secret_columns, - i.index_usage_summary, - ip.index_size_summary - FROM #IndexSanity i - JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id - JOIN count_columns AS cc ON i.[object_id]=cc.[object_id] - AND i.database_id = cc.database_id - WHERE index_id = 1 /* clustered only */ - AND - (count_key_columns > 3 /*More than three key columns.*/ - OR cc.sum_max_length > 16 /*More than 16 bytes in key */) - AND i.is_CX_columnstore = 0 - ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 25: Addicted to nullable columns.', 0,1) WITH NOWAIT; - WITH count_columns AS ( - SELECT [object_id], - [database_id], - [schema_name], - SUM(CASE is_nullable WHEN 1 THEN 0 ELSE 1 END) AS non_nullable_columns, - COUNT(*) AS total_columns - FROM #IndexColumns ic - WHERE index_id IN (1,0) /*Heap or clustered only*/ - GROUP BY [object_id], - [database_id], - [schema_name] - ) - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 25 AS check_id, - i.index_sanity_id, - 200 AS Priority, - N'Index Hoarder' AS findings_group, - N'Addicted to Nulls' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/IndexHoarder' AS URL, - i.db_schema_object_name - + N' allows null in ' + CAST((total_columns-non_nullable_columns) AS NVARCHAR(10)) - + N' of ' + CAST(total_columns AS NVARCHAR(10)) - + N' columns.' AS details, - i.index_definition, - secret_columns, - ISNULL(i.index_usage_summary,''), - ISNULL(ip.index_size_summary,'') - FROM #IndexSanity i - JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id - JOIN count_columns AS cc ON i.[object_id]=cc.[object_id] - AND cc.database_id = ip.database_id - AND cc.[schema_name] = ip.[schema_name] - WHERE i.index_id IN (1,0) - AND cc.non_nullable_columns < 2 - AND cc.total_columns > 3 - ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 26: Wide tables (35+ cols or > 2000 non-LOB bytes).', 0,1) WITH NOWAIT; - WITH count_columns AS ( - SELECT [object_id], - [database_id], - [schema_name], - SUM(CASE max_length WHEN -1 THEN 1 ELSE 0 END) AS count_lob_columns, - SUM(CASE max_length WHEN -1 THEN 0 ELSE max_length END) AS sum_max_length, - COUNT(*) AS total_columns - FROM #IndexColumns ic - WHERE index_id IN (1,0) /*Heap or clustered only*/ - GROUP BY [object_id], - [database_id], - [schema_name] - ) - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 26 AS check_id, - i.index_sanity_id, - 150 AS Priority, - N'Index Hoarder' AS findings_group, - N'Wide Tables: 35+ cols or > 2000 non-LOB bytes' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/IndexHoarder' AS URL, - i.db_schema_object_name - + N' has ' + CAST((total_columns) AS NVARCHAR(10)) - + N' total columns with a max possible width of ' + CAST(sum_max_length AS NVARCHAR(10)) - + N' bytes.' + - CASE WHEN count_lob_columns > 0 THEN CAST((count_lob_columns) AS NVARCHAR(10)) - + ' columns are LOB types.' ELSE '' - END - AS details, - i.index_definition, - secret_columns, - ISNULL(i.index_usage_summary,''), - ISNULL(ip.index_size_summary,'') - FROM #IndexSanity i - JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id - JOIN count_columns AS cc ON i.[object_id]=cc.[object_id] - AND cc.database_id = i.database_id - AND cc.[schema_name] = i.[schema_name] - WHERE i.index_id IN (1,0) - AND - (cc.total_columns >= 35 OR - cc.sum_max_length >= 2000) - ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 27: Addicted to strings.', 0,1) WITH NOWAIT; - WITH count_columns AS ( - SELECT [object_id], - [database_id], - [schema_name], - SUM(CASE WHEN system_type_name IN ('varchar','nvarchar','char') OR max_length=-1 THEN 1 ELSE 0 END) AS string_or_LOB_columns, - COUNT(*) AS total_columns - FROM #IndexColumns ic - WHERE index_id IN (1,0) /*Heap or clustered only*/ - GROUP BY [object_id], - [database_id], - [schema_name] - ) - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 27 AS check_id, - i.index_sanity_id, - 200 AS Priority, - N'Index Hoarder' AS findings_group, - N'Addicted to strings' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/IndexHoarder' AS URL, - i.db_schema_object_name - + N' uses string or LOB types for ' + CAST((string_or_LOB_columns) AS NVARCHAR(10)) - + N' of ' + CAST(total_columns AS NVARCHAR(10)) - + N' columns. Check if data types are valid.' AS details, - i.index_definition, - secret_columns, - ISNULL(i.index_usage_summary,''), - ISNULL(ip.index_size_summary,'') - FROM #IndexSanity i - JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id - JOIN count_columns AS cc ON i.[object_id]=cc.[object_id] - AND cc.database_id = i.database_id - AND cc.[schema_name] = i.[schema_name] - CROSS APPLY (SELECT cc.total_columns - string_or_LOB_columns AS non_string_or_lob_columns) AS calc1 - WHERE i.index_id IN (1,0) - AND calc1.non_string_or_lob_columns <= 1 - AND cc.total_columns > 3 - ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 28: Non-unique clustered index.', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 28 AS check_id, - i.index_sanity_id, - 150 AS Priority, - N'Index Hoarder' AS findings_group, - N'Non-Unique Clustered Index' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/IndexHoarder' AS URL, - N'Uniquifiers will be required! Clustered index: ' + i.db_schema_object_name - + N' and all NC indexes. ' + - (SELECT CAST(COUNT(*) AS NVARCHAR(23)) - FROM #IndexSanity i2 - WHERE i2.[object_id]=i.[object_id] - AND i2.database_id = i.database_id - AND i2.index_id <> 1 - AND i2.is_disabled=0 - AND i2.is_hypothetical=0) - + N' NC indexes on the table.' - AS details, - i.index_definition, - secret_columns, - i.index_usage_summary, - ip.index_size_summary - FROM #IndexSanity i - JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id - WHERE index_id = 1 /* clustered only */ - AND is_unique=0 /* not unique */ - AND is_CX_columnstore=0 /* not a clustered columnstore-- no unique option on those */ - ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 29: NC indexes with 0 reads. (Borderline) and < 10,000 writes', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 29 AS check_id, - i.index_sanity_id, - 150 AS Priority, - N'Index Hoarder' AS findings_group, - N'Unused NC index with Low Writes' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/IndexHoarder' AS URL, - N'0 reads: ' + i.db_schema_object_indexid AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - sz.index_size_summary - FROM #IndexSanity AS i - JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.total_reads=0 - AND i.user_updates < 10000 - 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()) - ORDER BY i.db_schema_object_indexid - OPTION ( RECOMPILE ); - - - ---------------------------------------- - --Feature-Phobic Indexes: Check_id 30-39 - ---------------------------------------- - RAISERROR(N'check_id 30: No indexes with includes', 0,1) WITH NOWAIT; - /* This does not work the way you'd expect with @GetAllDatabases = 1. For details: - https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/825 - */ - - SELECT database_name, - SUM(CASE WHEN count_included_columns > 0 THEN 1 ELSE 0 END) AS number_indexes_with_includes, - 100.* SUM(CASE WHEN count_included_columns > 0 THEN 1 ELSE 0 END) / ( 1.0 * COUNT(*) ) AS percent_indexes_with_includes - INTO #index_includes - FROM #IndexSanity - WHERE is_hypothetical = 0 - AND is_disabled = 0 - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) - GROUP BY database_name; - - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 30 AS check_id, - NULL AS index_sanity_id, - 250 AS Priority, - N'Feature-Phobic Indexes' AS findings_group, - database_name AS [Database Name], - N'No Indexes Use Includes' AS finding, 'https://www.brentozar.com/go/IndexFeatures' AS URL, - N'No Indexes Use Includes' AS details, - database_name + N' (Entire database)' AS index_definition, - N'' AS secret_columns, - N'N/A' AS index_usage_summary, - N'N/A' AS index_size_summary - FROM #index_includes - WHERE number_indexes_with_includes = 0 - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 31: < 3 percent of indexes have includes', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 31 AS check_id, - NULL AS index_sanity_id, - 250 AS Priority, - N'Feature-Phobic Indexes' AS findings_group, - N'Few Indexes Use Includes' AS findings, - database_name AS [Database Name], - N'https://www.brentozar.com/go/IndexFeatures' AS URL, - N'Only ' + CAST(percent_indexes_with_includes AS NVARCHAR(20)) + '% of indexes have includes' AS details, - N'Entire database' AS index_definition, - N'' AS secret_columns, - N'N/A' AS index_usage_summary, - N'N/A' AS index_size_summary - FROM #index_includes - WHERE number_indexes_with_includes > 0 AND percent_indexes_with_includes <= 3 - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 32: filtered indexes and indexed views', 0,1) WITH NOWAIT; - - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT DISTINCT - 32 AS check_id, - NULL AS index_sanity_id, - 250 AS Priority, - N'Feature-Phobic Indexes' AS findings_group, - N'No Filtered Indexes or Indexed Views' AS finding, - i.database_name AS [Database Name], - N'https://www.brentozar.com/go/IndexFeatures' AS URL, - N'These are NOT always needed-- but do you know when you would use them?' AS details, - i.database_name + N' (Entire database)' AS index_definition, - N'' AS secret_columns, - N'N/A' AS index_usage_summary, - N'N/A' AS index_size_summary - FROM #IndexSanity i - WHERE i.database_name NOT IN ( - SELECT database_name - FROM #IndexSanity - WHERE filter_definition <> '' ) - AND i.database_name NOT IN ( - SELECT database_name - FROM #IndexSanity - WHERE is_indexed_view = 1 ) - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 33: Potential filtered indexes based on column names.', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 33 AS check_id, - i.index_sanity_id AS index_sanity_id, - 250 AS Priority, - N'Feature-Phobic Indexes' AS findings_group, - N'Potential Filtered Index (Based on Column Name)' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/IndexFeatures' AS URL, - N'A column name in this index suggests it might be a candidate for filtering (is%, %archive%, %active%, %flag%)' AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - sz.index_size_summary - FROM #IndexColumns ic - JOIN #IndexSanity i ON ic.[object_id]=i.[object_id] - AND ic.database_id =i.database_id - AND ic.schema_name = i.schema_name - AND ic.[index_id]=i.[index_id] - AND i.[index_id] > 1 /* non-clustered index */ - JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id - WHERE (column_name LIKE 'is%' - OR column_name LIKE '%archive%' - OR column_name LIKE '%active%' - OR column_name LIKE '%flag%') - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 41: Hypothetical indexes ', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 41 AS check_id, - i.index_sanity_id, - 150 AS Priority, - N'Self Loathing Indexes' AS findings_group, - N'Hypothetical Index' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/SelfLoathing' AS URL, - N'Hypothetical Index: ' + db_schema_object_indexid AS details, - i.index_definition, - i.secret_columns, - N'' AS index_usage_summary, - N'' AS index_size_summary - FROM #IndexSanity AS i - WHERE is_hypothetical = 1 - OPTION ( RECOMPILE ); - - - RAISERROR(N'check_id 42: Disabled indexes', 0,1) WITH NOWAIT; - --Note: disabled NC indexes will have O rows in #IndexSanitySize! - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 42 AS check_id, - index_sanity_id, - 150 AS Priority, - N'Self Loathing Indexes' AS findings_group, - N'Disabled Index' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/SelfLoathing' AS URL, - N'Disabled Index:' + db_schema_object_indexid AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - 'DISABLED' AS index_size_summary - FROM #IndexSanity AS i - WHERE is_disabled = 1 - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 49: Heaps with deletes', 0,1) WITH NOWAIT; - WITH heaps_cte - AS ( SELECT [object_id], - [database_id], - [schema_name], - SUM(leaf_delete_count) AS leaf_delete_count - FROM #IndexPartitionSanity - GROUP BY [object_id], - [database_id], - [schema_name] - HAVING SUM(forwarded_fetch_count) < 1000 * @DaysUptime /* Only alert about indexes with no forwarded fetches - we already alerted about those in check_id 43 */ - AND SUM(leaf_delete_count) > 0) - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 49 AS check_id, - i.index_sanity_id, - 200 AS Priority, - N'Self Loathing Indexes' AS findings_group, - N'Heaps with Deletes' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/SelfLoathing' AS URL, - CAST(h.leaf_delete_count AS NVARCHAR(256)) + N' deletes against heap:' - + db_schema_object_indexid AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - sz.index_size_summary - FROM #IndexSanity i - JOIN heaps_cte h ON i.[object_id] = h.[object_id] - AND i.[database_id] = h.[database_id] - AND i.[schema_name] = h.[schema_name] - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.index_id = 0 - AND sz.total_reserved_MB >= CASE WHEN NOT (@GetAllDatabases = 1 OR @Mode = 4) THEN @ThresholdMB ELSE sz.total_reserved_MB END - OPTION ( RECOMPILE ); - - ---------------------------------------- - --Abnormal Psychology : Check_id 60-79 - ---------------------------------------- - RAISERROR(N'check_id 60: XML indexes', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 60 AS check_id, - i.index_sanity_id, - 150 AS Priority, - N'Abnormal Psychology' AS findings_group, - N'XML Index' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, - i.db_schema_object_indexid AS details, - i.index_definition, - i.secret_columns, - N'' AS index_usage_summary, - ISNULL(sz.index_size_summary,'') AS index_size_summary - FROM #IndexSanity AS i - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.is_XML = 1 - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 61: Columnstore indexes', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 61 AS check_id, - i.index_sanity_id, - 150 AS Priority, - N'Abnormal Psychology' AS findings_group, - CASE WHEN i.is_NC_columnstore=1 - THEN N'NC Columnstore Index' - ELSE N'Clustered Columnstore Index' - END AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, - i.db_schema_object_indexid AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - ISNULL(sz.index_size_summary,'') AS index_size_summary - FROM #IndexSanity AS i - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.is_NC_columnstore = 1 OR i.is_CX_columnstore=1 - OPTION ( RECOMPILE ); - - - RAISERROR(N'check_id 62: Spatial indexes', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 62 AS check_id, - i.index_sanity_id, - 150 AS Priority, - N'Abnormal Psychology' AS findings_group, - N'Spatial Index' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, - i.db_schema_object_indexid AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - ISNULL(sz.index_size_summary,'') AS index_size_summary - FROM #IndexSanity AS i - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.is_spatial = 1 - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 63: Compressed indexes', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 63 AS check_id, - i.index_sanity_id, - 150 AS Priority, - N'Abnormal Psychology' AS findings_group, - N'Compressed Index' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, - i.db_schema_object_indexid + N'. COMPRESSION: ' + sz.data_compression_desc AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - ISNULL(sz.index_size_summary,'') AS index_size_summary - FROM #IndexSanity AS i - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE sz.data_compression_desc LIKE '%PAGE%' OR sz.data_compression_desc LIKE '%ROW%' - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 64: Partitioned', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 64 AS check_id, - i.index_sanity_id, - 150 AS Priority, - N'Abnormal Psychology' AS findings_group, - N'Partitioned Index' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, - i.db_schema_object_indexid AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - ISNULL(sz.index_size_summary,'') AS index_size_summary - FROM #IndexSanity AS i - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.partition_key_column_name IS NOT NULL - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 65: Non-Aligned Partitioned', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 65 AS check_id, - i.index_sanity_id, - 150 AS Priority, - N'Abnormal Psychology' AS findings_group, - N'Non-Aligned Index on a Partitioned Table' AS finding, - i.[database_name] AS [Database Name], - N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, - i.db_schema_object_indexid AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - ISNULL(sz.index_size_summary,'') AS index_size_summary - FROM #IndexSanity AS i - JOIN #IndexSanity AS iParent ON - i.[object_id]=iParent.[object_id] - AND i.database_id = iParent.database_id - AND i.schema_name = iParent.schema_name - AND iParent.index_id IN (0,1) /* could be a partitioned heap or clustered table */ - AND iParent.partition_key_column_name IS NOT NULL /* parent is partitioned*/ - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.partition_key_column_name IS NULL - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 66: Recently created tables/indexes (1 week)', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 66 AS check_id, - i.index_sanity_id, - 200 AS Priority, - N'Abnormal Psychology' AS findings_group, - N'Recently Created Tables/Indexes (1 week)' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, - i.db_schema_object_indexid + N' was created on ' + - CONVERT(NVARCHAR(16),i.create_date,121) + - N'. Tables/indexes which are dropped/created regularly require special methods for index tuning.' - AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - ISNULL(sz.index_size_summary,'') AS index_size_summary - FROM #IndexSanity AS i - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.create_date >= DATEADD(dd,-7,GETDATE()) - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 67: Recently modified tables/indexes (2 days)', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 67 AS check_id, - i.index_sanity_id, - 200 AS Priority, - N'Abnormal Psychology' AS findings_group, - N'Recently Modified Tables/Indexes (2 days)' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, - i.db_schema_object_indexid + N' was modified on ' + - CONVERT(NVARCHAR(16),i.modify_date,121) + - N'. A large amount of recently modified indexes may mean a lot of rebuilds are occurring each night.' - AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - ISNULL(sz.index_size_summary,'') AS index_size_summary - FROM #IndexSanity AS i - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.modify_date > DATEADD(dd,-2,GETDATE()) - AND /*Exclude recently created tables.*/ - i.create_date < DATEADD(dd,-7,GETDATE()) - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 69: Column collation does not match database collation', 0,1) WITH NOWAIT; - WITH count_columns AS ( - SELECT [object_id], - database_id, - schema_name, - COUNT(*) AS column_count - FROM #IndexColumns ic - WHERE index_id IN (1,0) /*Heap or clustered only*/ - AND collation_name <> @collation - GROUP BY [object_id], - database_id, - schema_name - ) - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 69 AS check_id, - i.index_sanity_id, - 150 AS Priority, - N'Abnormal Psychology' AS findings_group, - N'Column Collation Does Not Match Database Collation' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, - i.db_schema_object_name - + N' has ' + CAST(column_count AS NVARCHAR(20)) - + N' column' + CASE WHEN column_count > 1 THEN 's' ELSE '' END - + N' with a different collation than the db collation of ' - + @collation AS details, - i.index_definition, - secret_columns, - ISNULL(i.index_usage_summary,''), - ISNULL(ip.index_size_summary,'') - FROM #IndexSanity i - JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id - JOIN count_columns AS cc ON i.[object_id]=cc.[object_id] - AND cc.database_id = i.database_id - AND cc.schema_name = i.schema_name - WHERE i.index_id IN (1,0) - ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 70: Replicated columns', 0,1) WITH NOWAIT; - WITH count_columns AS ( - SELECT [object_id], - database_id, - schema_name, - COUNT(*) AS column_count, - SUM(CASE is_replicated WHEN 1 THEN 1 ELSE 0 END) AS replicated_column_count - FROM #IndexColumns ic - WHERE index_id IN (1,0) /*Heap or clustered only*/ - GROUP BY object_id, - database_id, - schema_name - ) - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 70 AS check_id, - i.index_sanity_id, - 200 AS Priority, - N'Abnormal Psychology' AS findings_group, - N'Replicated Columns' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, - i.db_schema_object_name - + N' has ' + CAST(replicated_column_count AS NVARCHAR(20)) - + N' out of ' + CAST(column_count AS NVARCHAR(20)) - + N' column' + CASE WHEN column_count > 1 THEN 's' ELSE '' END - + N' in one or more publications.' - AS details, - i.index_definition, - secret_columns, - ISNULL(i.index_usage_summary,''), - ISNULL(ip.index_size_summary,'') - FROM #IndexSanity i - JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id - JOIN count_columns AS cc ON i.[object_id]=cc.[object_id] - AND i.database_id = cc.database_id - AND i.schema_name = cc.schema_name - WHERE i.index_id IN (1,0) - AND replicated_column_count > 0 - ORDER BY i.db_schema_object_name DESC - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 71: Cascading updates or cascading deletes.', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary, more_info ) - SELECT 71 AS check_id, - NULL AS index_sanity_id, - 150 AS Priority, - N'Abnormal Psychology' AS findings_group, - N'Cascading Updates or Deletes' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, - N'Foreign Key ' + QUOTENAME(foreign_key_name) + - N' on ' + QUOTENAME(parent_object_name) + N'(' + LTRIM(parent_fk_columns) + N')' - + N' referencing ' + QUOTENAME(referenced_object_name) + N'(' + LTRIM(referenced_fk_columns) + N')' - + N' has settings:' - + CASE [delete_referential_action_desc] WHEN N'NO_ACTION' THEN N'' ELSE N' ON DELETE ' +[delete_referential_action_desc] END - + CASE [update_referential_action_desc] WHEN N'NO_ACTION' THEN N'' ELSE N' ON UPDATE ' + [update_referential_action_desc] END - AS details, - [fk].[database_name] AS index_definition, - N'N/A' AS secret_columns, - N'N/A' AS index_usage_summary, - N'N/A' AS index_size_summary, - (SELECT TOP 1 more_info FROM #IndexSanity i WHERE i.object_id=fk.parent_object_id AND i.database_id = fk.database_id AND i.schema_name = fk.schema_name) - AS more_info - FROM #ForeignKeys fk - WHERE ([delete_referential_action_desc] <> N'NO_ACTION' - OR [update_referential_action_desc] <> N'NO_ACTION') - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 72: Unindexed foreign keys.', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary, more_info ) - SELECT 72 AS check_id, - NULL AS index_sanity_id, - 150 AS Priority, - N'Abnormal Psychology' AS findings_group, - N'Unindexed Foreign Keys' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, - N'Foreign Key ' + QUOTENAME(foreign_key_name) + - N' on ' + QUOTENAME(parent_object_name) + N'' - + N' referencing ' + QUOTENAME(referenced_object_name) + N'' - + N' does not appear to have a supporting index.' AS details, - N'N/A' AS index_definition, - N'N/A' AS secret_columns, - N'N/A' AS index_usage_summary, - N'N/A' AS index_size_summary, - (SELECT TOP 1 more_info FROM #IndexSanity i WHERE i.object_id=fk.parent_object_id AND i.database_id = fk.database_id AND i.schema_name = fk.schema_name) - AS more_info - FROM #UnindexedForeignKeys AS fk - OPTION ( RECOMPILE ); - - - RAISERROR(N'check_id 73: In-Memory OLTP', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 73 AS check_id, - i.index_sanity_id, - 150 AS Priority, - N'Abnormal Psychology' AS findings_group, - N'In-Memory OLTP' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, - i.db_schema_object_indexid AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - ISNULL(sz.index_size_summary,'') AS index_size_summary - FROM #IndexSanity AS i - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.is_in_memory_oltp = 1 - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 74: Identity column with unusual seed', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 74 AS check_id, - i.index_sanity_id, - 200 AS Priority, - N'Abnormal Psychology' AS findings_group, - N'Identity Column Using a Negative Seed or Increment Other Than 1' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, - i.db_schema_object_name + N'.' + QUOTENAME(ic.column_name) - + N' is an identity with type ' + ic.system_type_name - + N', last value of ' - + ISNULL((CONVERT(NVARCHAR(256),CAST(ic.last_value AS DECIMAL(38,0)), 1)),N'NULL') - + N', seed of ' - + ISNULL((CONVERT(NVARCHAR(256),CAST(ic.seed_value AS DECIMAL(38,0)), 1)),N'NULL') - + N', increment of ' + CAST(ic.increment_value AS NVARCHAR(256)) - + N', and range of ' + - CASE ic.system_type_name WHEN 'int' THEN N'+/- 2,147,483,647' - WHEN 'smallint' THEN N'+/- 32,768' - WHEN 'tinyint' THEN N'0 to 255' - ELSE 'unknown' - END - AS details, - i.index_definition, - secret_columns, - ISNULL(i.index_usage_summary,''), - ISNULL(ip.index_size_summary,'') - FROM #IndexSanity i - JOIN #IndexColumns ic ON - i.object_id=ic.object_id - AND i.database_id = ic.database_id - AND i.schema_name = ic.schema_name - AND i.index_id IN (0,1) /* heaps and cx only */ - AND ic.is_identity=1 - AND ic.system_type_name IN ('tinyint', 'smallint', 'int') - JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id - WHERE i.index_id IN (1,0) - AND (ic.seed_value < 0 OR ic.increment_value <> 1) - ORDER BY finding, details DESC - OPTION ( RECOMPILE ); - - ---------------------------------------- - --Workaholics: Check_id 80-89 - ---------------------------------------- - - RAISERROR(N'check_id 80: Most scanned indexes (index_usage_stats)', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - - --Workaholics according to index_usage_stats - --This isn't perfect: it mentions the number of scans present in a plan - --A "scan" isn't necessarily a full scan, but hey, we gotta do the best with what we've got. - --in the case of things like indexed views, the operator might be in the plan but never executed - SELECT TOP 5 - 80 AS check_id, - i.index_sanity_id AS index_sanity_id, - 200 AS Priority, - N'Workaholics' AS findings_group, - N'Scan-a-lots (index-usage-stats)' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/Workaholics' AS URL, - REPLACE(CONVERT( NVARCHAR(50),CAST(i.user_scans AS MONEY),1),'.00','') - + N' scans against ' + i.db_schema_object_indexid - + N'. Latest scan: ' + ISNULL(CAST(i.last_user_scan AS NVARCHAR(128)),'?') + N'. ' - + N'ScanFactor=' + CAST(((i.user_scans * iss.total_reserved_MB)/1000000.) AS NVARCHAR(256)) AS details, - ISNULL(i.key_column_names_with_sort_order,'N/A') AS index_definition, - ISNULL(i.secret_columns,'') AS secret_columns, - i.index_usage_summary AS index_usage_summary, - iss.index_size_summary AS index_size_summary - FROM #IndexSanity i - JOIN #IndexSanitySize iss ON i.index_sanity_id=iss.index_sanity_id - WHERE ISNULL(i.user_scans,0) > 0 - ORDER BY i.user_scans * iss.total_reserved_MB DESC - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 81: Top recent accesses (op stats)', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - --Workaholics according to index_operational_stats - --This isn't perfect either: range_scan_count contains full scans, partial scans, even seeks in nested loop ops - --But this can help bubble up some most-accessed tables - SELECT TOP 5 - 81 AS check_id, - i.index_sanity_id AS index_sanity_id, - 200 AS Priority, - N'Workaholics' AS findings_group, - N'Top Recent Accesses (index-op-stats)' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/Workaholics' AS URL, - ISNULL(REPLACE( - CONVERT(NVARCHAR(50),CAST((iss.total_range_scan_count + iss.total_singleton_lookup_count) AS MONEY),1), - N'.00',N'') - + N' uses of ' + i.db_schema_object_indexid + N'. ' - + REPLACE(CONVERT(NVARCHAR(50), CAST(iss.total_range_scan_count AS MONEY),1),N'.00',N'') + N' scans or seeks. ' - + REPLACE(CONVERT(NVARCHAR(50), CAST(iss.total_singleton_lookup_count AS MONEY), 1),N'.00',N'') + N' singleton lookups. ' - + N'OpStatsFactor=' + CAST(((((iss.total_range_scan_count + iss.total_singleton_lookup_count) * iss.total_reserved_MB))/1000000.) AS VARCHAR(256)),'') AS details, - ISNULL(i.key_column_names_with_sort_order,'N/A') AS index_definition, - ISNULL(i.secret_columns,'') AS secret_columns, - i.index_usage_summary AS index_usage_summary, - iss.index_size_summary AS index_size_summary - FROM #IndexSanity i - JOIN #IndexSanitySize iss ON i.index_sanity_id=iss.index_sanity_id - WHERE (ISNULL(iss.total_range_scan_count,0) > 0 OR ISNULL(iss.total_singleton_lookup_count,0) > 0) - ORDER BY ((iss.total_range_scan_count + iss.total_singleton_lookup_count) * iss.total_reserved_MB) DESC - OPTION ( RECOMPILE ); - - - - RAISERROR(N'check_id 93: Statistics with filters', 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 93 AS check_id, - 200 AS Priority, - 'Functioning Statistaholics' AS findings_group, - 'Filter Fixation', - s.database_name, - 'https://www.brentozar.com/go/stats' AS URL, - 'The statistic ' + QUOTENAME(s.statistics_name) + ' is filtered on [' + s.filter_definition + ']. It could be part of a filtered index, or just a filtered statistic. This is purely informational.' 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_filter = 1 - OPTION ( RECOMPILE ); - - - RAISERROR(N'check_id 100: Computed Columns that are not Persisted.', 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 100 AS check_id, - 200 AS Priority, - 'Cold Calculators' AS findings_group, - 'Definition Defeatists' AS finding, - cc.database_name, - '' AS URL, - 'The computed column ' + QUOTENAME(cc.column_name) + ' on ' + QUOTENAME(cc.schema_name) + '.' + QUOTENAME(cc.table_name) + ' is not persisted, which means it will be calculated when a query runs.' + - 'You can change this with the following command, if the definition is deterministic: ALTER TABLE ' + QUOTENAME(cc.schema_name) + '.' + QUOTENAME(cc.table_name) + ' ALTER COLUMN ' + cc.column_name + - ' ADD PERSISTED' AS details, - cc.column_definition, - 'N/A' AS secret_columns, - 'N/A' AS index_usage_summary, - 'N/A' AS index_size_summary - FROM #ComputedColumns AS cc - WHERE cc.is_persisted = 0 - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 110: Temporal Tables.', 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 110 AS check_id, - 200 AS Priority, - 'Abnormal Psychology' AS findings_group, - 'Temporal Tables', - t.database_name, - '' AS URL, - 'The table ' + QUOTENAME(t.schema_name) + '.' + QUOTENAME(t.table_name) + ' is a temporal table, with rows versioned in ' - + QUOTENAME(t.history_schema_name) + '.' + QUOTENAME(t.history_table_name) + ' on History columns ' + QUOTENAME(t.start_column_name) + ' and ' + QUOTENAME(t.end_column_name) + '.' - AS details, - '' AS index_definition, - 'N/A' AS secret_columns, - 'N/A' AS index_usage_summary, - 'N/A' AS index_size_summary - FROM #TemporalTables AS t - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 121: Optimized For Sequental Keys.', 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 121 AS check_id, - 200 AS Priority, - 'Medicated Indexes' AS findings_group, - 'Optimized For Sequential Keys', - i.database_name, - '' AS URL, - 'The table ' + QUOTENAME(i.schema_name) + '.' + QUOTENAME(i.object_name) + ' is optimized for sequential keys.' AS details, - '' AS index_definition, - 'N/A' AS secret_columns, - 'N/A' AS index_usage_summary, - 'N/A' AS index_size_summary - FROM #IndexSanity AS i - WHERE i.optimize_for_sequential_key = 1 - OPTION ( RECOMPILE ); - - - - END /* IF @Mode = 4 */ - - - - - - - - - RAISERROR(N'Insert a row to help people find help', 0,1) WITH NOWAIT; - IF DATEDIFF(MM, @VersionDate, GETDATE()) > 6 - BEGIN - INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, URL, details, index_definition, - index_usage_summary, index_size_summary ) - VALUES ( -1, 0 , - 'Outdated sp_BlitzIndex', 'sp_BlitzIndex is Over 6 Months Old', 'http://FirstResponderKit.org/', - 'Fine wine gets better with age, but this ' + @ScriptVersionName + ' is more like bad cheese. Time to get a new one.', - @DaysUptimeInsertValue,N'',N'' - ); - END; - - IF EXISTS(SELECT * FROM #BlitzIndexResults) - BEGIN - INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, URL, details, index_definition, - index_usage_summary, index_size_summary ) - VALUES ( -1, 0 , - @ScriptVersionName, - CASE WHEN @GetAllDatabases = 1 THEN N'All Databases' ELSE N'Database ' + QUOTENAME(@DatabaseName) + N' as of ' + CONVERT(NVARCHAR(16),GETDATE(),121) END, - N'From Your Community Volunteers' , N'http://FirstResponderKit.org' , - @DaysUptimeInsertValue,N'',N'' - ); - END; - ELSE IF @Mode = 0 OR (@GetAllDatabases = 1 AND @Mode <> 4) - BEGIN - INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, URL, details, index_definition, - index_usage_summary, index_size_summary ) - VALUES ( -1, 0 , - @ScriptVersionName, - CASE WHEN @GetAllDatabases = 1 THEN N'All Databases' ELSE N'Database ' + QUOTENAME(@DatabaseName) + N' as of ' + CONVERT(NVARCHAR(16),GETDATE(),121) END, - N'From Your Community Volunteers' , N'http://FirstResponderKit.org' , - @DaysUptimeInsertValue, N'',N'' - ); - INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, URL, details, index_definition, - index_usage_summary, index_size_summary ) - VALUES ( 1, 0 , - N'No Major Problems Found', - N'Nice Work!', - N'http://FirstResponderKit.org', - N'Consider running with @Mode = 4 in individual databases (not all) for more detailed diagnostics.', - N'The default Mode 0 only looks for very serious index issues.', - @DaysUptimeInsertValue, N'' - ); - - END; - ELSE - BEGIN - INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, URL, details, index_definition, - index_usage_summary, index_size_summary ) - VALUES ( -1, 0 , - @ScriptVersionName, - CASE WHEN @GetAllDatabases = 1 THEN N'All Databases' ELSE N'Database ' + QUOTENAME(@DatabaseName) + N' as of ' + CONVERT(NVARCHAR(16),GETDATE(),121) END, - N'From Your Community Volunteers' , N'http://FirstResponderKit.org' , - @DaysUptimeInsertValue, N'',N'' - ); - INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, URL, details, index_definition, - index_usage_summary, index_size_summary ) - VALUES ( 1, 0 , - N'No Problems Found', - N'Nice job! Or more likely, you have a nearly empty database.', - N'http://FirstResponderKit.org', 'Time to go read some blog posts.', - @DaysUptimeInsertValue, N'', N'' - ); - - END; - - RAISERROR(N'Returning results.', 0,1) WITH NOWAIT; - - /*Return results.*/ - IF (@ValidOutputLocation = 1 AND COALESCE(@OutputServerName, @OutputDatabaseName, @OutputSchemaName, @OutputTableName) IS NOT NULL) - BEGIN - IF NOT @SchemaExists = 1 - BEGIN - RAISERROR (N'Invalid schema name, data could not be saved.', 16, 0); - RETURN; - END - - IF @TableExists = 0 - BEGIN - SET @StringToExecute = - N'CREATE TABLE @@@OutputDatabaseName@@@.@@@OutputSchemaName@@@.@@@OutputTableName@@@ - ( - [id] INT IDENTITY(1,1) NOT NULL, - [run_id] UNIQUEIDENTIFIER, - [run_datetime] DATETIME, - [server_name] NVARCHAR(128), - [priority] INT, - [finding] NVARCHAR(4000), - [database_name] NVARCHAR(128), - [details] NVARCHAR(MAX), - [index_definition] NVARCHAR(MAX), - [secret_columns] NVARCHAR(MAX), - [index_usage_summary] NVARCHAR(MAX), - [index_size_summary] NVARCHAR(MAX), - [more_info] NVARCHAR(MAX), - [url] NVARCHAR(MAX), - [create_tsql] NVARCHAR(MAX), - [sample_query_plan] XML, - CONSTRAINT [PK_ID_@@@RunID@@@] PRIMARY KEY CLUSTERED ([id] ASC) - );'; - - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputSchemaName@@@', @OutputSchemaName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@RunID@@@', @RunID); - - IF @ValidOutputServer = 1 - BEGIN - SET @StringToExecute = REPLACE(@StringToExecute,'''',''''''); - EXEC('EXEC('''+@StringToExecute+''') AT ' + @OutputServerName); - END; - ELSE - BEGIN - EXEC(@StringToExecute); - END; - END; /* @TableExists = 0 */ - - -- Re-check that table now exists (if not we failed creating it) - SET @TableExists = NULL; - EXEC sp_executesql @TableExistsSql, N'@TableExists BIT OUTPUT', @TableExists OUTPUT; - - IF NOT @TableExists = 1 - BEGIN - RAISERROR('Creation of the output table failed.', 16, 0); - RETURN; - END; - - SET @StringToExecute = - N'INSERT @@@OutputServerName@@@.@@@OutputDatabaseName@@@.@@@OutputSchemaName@@@.@@@OutputTableName@@@ - ( - [run_id], - [run_datetime], - [server_name], - [priority], - [finding], - [database_name], - [details], - [index_definition], - [secret_columns], - [index_usage_summary], - [index_size_summary], - [more_info], - [url], - [create_tsql], - [sample_query_plan] - ) - SELECT - ''@@@RunID@@@'', - ''@@@GETDATE@@@'', - ''@@@LocalServerName@@@'', - -- Below should be a copy/paste of the real query - -- Make sure all quotes are escaped - Priority, ISNULL(br.findings_group,N'''') + - CASE WHEN ISNULL(br.finding,N'''') <> N'''' THEN N'': '' ELSE N'''' END - + br.finding AS [Finding], - br.[database_name] AS [Database Name], - br.details AS [Details: schema.table.index(indexid)], - br.index_definition AS [Definition: [Property]] ColumnName {datatype maxbytes}], - ISNULL(br.secret_columns,'''') AS [Secret Columns], - br.index_usage_summary AS [Usage], - br.index_size_summary AS [Size], - COALESCE(br.more_info,sn.more_info,'''') AS [More Info], - br.URL, - COALESCE(br.create_tsql,ts.create_tsql,'''') AS [Create TSQL], - br.sample_query_plan AS [Sample Query Plan] - FROM #BlitzIndexResults br - LEFT JOIN #IndexSanity sn ON - br.index_sanity_id=sn.index_sanity_id - LEFT JOIN #IndexCreateTsql ts ON - br.index_sanity_id=ts.index_sanity_id - ORDER BY br.Priority ASC, br.check_id ASC, br.blitz_result_id ASC, br.findings_group ASC - OPTION (RECOMPILE);'; - - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputServerName@@@', @OutputServerName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputSchemaName@@@', @OutputSchemaName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@RunID@@@', @RunID); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@GETDATE@@@', GETDATE()); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@LocalServerName@@@', CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128))); - EXEC(@StringToExecute); - - END - ELSE - BEGIN - IF(@OutputType <> 'NONE') - BEGIN - SELECT Priority, ISNULL(br.findings_group,N'') + - CASE WHEN ISNULL(br.finding,N'') <> N'' THEN N': ' ELSE N'' END - + br.finding AS [Finding], - br.[database_name] AS [Database Name], - br.details AS [Details: schema.table.index(indexid)], - br.index_definition AS [Definition: [Property]] ColumnName {datatype maxbytes}], - ISNULL(br.secret_columns,'') AS [Secret Columns], - br.index_usage_summary AS [Usage], - br.index_size_summary AS [Size], - COALESCE(br.more_info,sn.more_info,'') AS [More Info], - br.URL, - COALESCE(br.create_tsql,ts.create_tsql,'') AS [Create TSQL], - br.sample_query_plan AS [Sample Query Plan] - FROM #BlitzIndexResults br - LEFT JOIN #IndexSanity sn ON - br.index_sanity_id=sn.index_sanity_id - LEFT JOIN #IndexCreateTsql ts ON - br.index_sanity_id=ts.index_sanity_id - ORDER BY br.Priority ASC, br.check_id ASC, br.blitz_result_id ASC, br.findings_group ASC - OPTION (RECOMPILE); - END; - - END; - - END /* End @Mode=0 or 4 (diagnose)*/ - - - - - - - - - ELSE IF (@Mode=1) /*Summarize*/ - BEGIN - --This mode is to give some overall stats on the database. - IF (@ValidOutputLocation = 1 AND COALESCE(@OutputServerName, @OutputDatabaseName, @OutputSchemaName, @OutputTableName) IS NOT NULL) - BEGIN - - IF NOT @SchemaExists = 1 - BEGIN - RAISERROR (N'Invalid schema name, data could not be saved.', 16, 0); - RETURN; - END - - IF @TableExists = 0 - BEGIN - SET @StringToExecute = - N'CREATE TABLE @@@OutputDatabaseName@@@.@@@OutputSchemaName@@@.@@@OutputTableName@@@ - ( - [id] INT IDENTITY(1,1) NOT NULL, - [run_id] UNIQUEIDENTIFIER, - [run_datetime] DATETIME, - [server_name] NVARCHAR(128), - [database_name] NVARCHAR(128), - [object_count] INT, - [reserved_gb] NUMERIC(29,1), - [reserved_lob_gb] NUMERIC(29,1), - [reserved_row_overflow_gb] NUMERIC(29,1), - [clustered_table_count] INT, - [clustered_table_gb] NUMERIC(29,1), - [nc_index_count] INT, - [nc_index_gb] NUMERIC(29,1), - [table_nc_index_ratio] NUMERIC(29,1), - [heap_count] INT, - [heap_gb] NUMERIC(29,1), - [partioned_table_count] INT, - [partioned_nc_count] INT, - [partioned_gb] NUMERIC(29,1), - [filtered_index_count] INT, - [indexed_view_count] INT, - [max_table_row_count] INT, - [max_table_gb] NUMERIC(29,1), - [max_nc_index_gb] NUMERIC(29,1), - [table_count_over_1gb] INT, - [table_count_over_10gb] INT, - [table_count_over_100gb] INT, - [nc_index_count_over_1gb] INT, - [nc_index_count_over_10gb] INT, - [nc_index_count_over_100gb] INT, - [min_create_date] DATETIME, - [max_create_date] DATETIME, - [max_modify_date] DATETIME, - [display_order] INT, - CONSTRAINT [PK_ID_@@@RunID@@@] PRIMARY KEY CLUSTERED ([id] ASC) - );'; - - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputSchemaName@@@', @OutputSchemaName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@RunID@@@', @RunID); - - IF @ValidOutputServer = 1 - BEGIN - SET @StringToExecute = REPLACE(@StringToExecute,'''',''''''); - EXEC('EXEC('''+@StringToExecute+''') AT ' + @OutputServerName); - END; - ELSE - BEGIN - EXEC(@StringToExecute); - END; - END; /* @TableExists = 0 */ - - -- Re-check that table now exists (if not we failed creating it) - SET @TableExists = NULL; - EXEC sp_executesql @TableExistsSql, N'@TableExists BIT OUTPUT', @TableExists OUTPUT; - - IF NOT @TableExists = 1 - BEGIN - RAISERROR('Creation of the output table failed.', 16, 0); - RETURN; - END; - - SET @StringToExecute = - N'INSERT @@@OutputServerName@@@.@@@OutputDatabaseName@@@.@@@OutputSchemaName@@@.@@@OutputTableName@@@ - ( - [run_id], - [run_datetime], - [server_name], - [database_name], - [object_count], - [reserved_gb], - [reserved_lob_gb], - [reserved_row_overflow_gb], - [clustered_table_count], - [clustered_table_gb], - [nc_index_count], - [nc_index_gb], - [table_nc_index_ratio], - [heap_count], - [heap_gb], - [partioned_table_count], - [partioned_nc_count], - [partioned_gb], - [filtered_index_count], - [indexed_view_count], - [max_table_row_count], - [max_table_gb], - [max_nc_index_gb], - [table_count_over_1gb], - [table_count_over_10gb], - [table_count_over_100gb], - [nc_index_count_over_1gb], - [nc_index_count_over_10gb], - [nc_index_count_over_100gb], - [min_create_date], - [max_create_date], - [max_modify_date], - [display_order] - ) - SELECT ''@@@RunID@@@'', - ''@@@GETDATE@@@'', - ''@@@LocalServerName@@@'', - -- Below should be a copy/paste of the real query - -- Make sure all quotes are escaped - -- NOTE! information line is skipped from output and the query below - -- NOTE! initial columns are not casted to nvarchar due to not outputing informational line - DB_NAME(i.database_id) AS [Database Name], - COUNT(*) AS [Number Objects], - CAST(SUM(sz.total_reserved_MB)/ - 1024. AS NUMERIC(29,1)) AS [All GB], - CAST(SUM(sz.total_reserved_LOB_MB)/ - 1024. AS NUMERIC(29,1)) AS [LOB GB], - CAST(SUM(sz.total_reserved_row_overflow_MB)/ - 1024. AS NUMERIC(29,1)) AS [Row Overflow GB], - SUM(CASE WHEN index_id=1 THEN 1 ELSE 0 END) AS [Clustered Tables], - CAST(SUM(CASE WHEN index_id=1 THEN sz.total_reserved_MB ELSE 0 END) - /1024. AS NUMERIC(29,1)) AS [Clustered Tables GB], - SUM(CASE WHEN index_id NOT IN (0,1) THEN 1 ELSE 0 END) AS [NC Indexes], - CAST(SUM(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) - /1024. AS NUMERIC(29,1)) AS [NC Indexes GB], - CASE WHEN SUM(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) > 0 THEN - CAST(SUM(CASE WHEN index_id IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) - / SUM(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) AS NUMERIC(29,1)) - ELSE 0 END AS [ratio table: NC Indexes], - SUM(CASE WHEN index_id=0 THEN 1 ELSE 0 END) AS [Heaps], - CAST(SUM(CASE WHEN index_id=0 THEN sz.total_reserved_MB ELSE 0 END) - /1024. AS NUMERIC(29,1)) AS [Heaps GB], - SUM(CASE WHEN index_id IN (0,1) AND partition_key_column_name IS NOT NULL THEN 1 ELSE 0 END) AS [Partitioned Tables], - SUM(CASE WHEN index_id NOT IN (0,1) AND partition_key_column_name IS NOT NULL THEN 1 ELSE 0 END) AS [Partitioned NCs], - CAST(SUM(CASE WHEN partition_key_column_name IS NOT NULL THEN sz.total_reserved_MB ELSE 0 END)/1024. AS NUMERIC(29,1)) AS [Partitioned GB], - SUM(CASE WHEN filter_definition <> '''' THEN 1 ELSE 0 END) AS [Filtered Indexes], - SUM(CASE WHEN is_indexed_view=1 THEN 1 ELSE 0 END) AS [Indexed Views], - MAX(total_rows) AS [Max Row Count], - CAST(MAX(CASE WHEN index_id IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) - /1024. AS NUMERIC(29,1)) AS [Max Table GB], - CAST(MAX(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) - /1024. AS NUMERIC(29,1)) AS [Max NC Index GB], - SUM(CASE WHEN index_id IN (0,1) AND sz.total_reserved_MB > 1024 THEN 1 ELSE 0 END) AS [Count Tables > 1GB], - SUM(CASE WHEN index_id IN (0,1) AND sz.total_reserved_MB > 10240 THEN 1 ELSE 0 END) AS [Count Tables > 10GB], - SUM(CASE WHEN index_id IN (0,1) AND sz.total_reserved_MB > 102400 THEN 1 ELSE 0 END) AS [Count Tables > 100GB], - SUM(CASE WHEN index_id NOT IN (0,1) AND sz.total_reserved_MB > 1024 THEN 1 ELSE 0 END) AS [Count NCs > 1GB], - SUM(CASE WHEN index_id NOT IN (0,1) AND sz.total_reserved_MB > 10240 THEN 1 ELSE 0 END) AS [Count NCs > 10GB], - SUM(CASE WHEN index_id NOT IN (0,1) AND sz.total_reserved_MB > 102400 THEN 1 ELSE 0 END) AS [Count NCs > 100GB], - MIN(create_date) AS [Oldest Create Date], - MAX(create_date) AS [Most Recent Create Date], - MAX(modify_date) AS [Most Recent Modify Date], - 1 AS [Display Order] - FROM #IndexSanity AS i - --left join here so we don''t lose disabled nc indexes - LEFT JOIN #IndexSanitySize AS sz - ON i.index_sanity_id=sz.index_sanity_id - GROUP BY DB_NAME(i.database_id) - ORDER BY [Display Order] ASC - OPTION (RECOMPILE);'; - - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputServerName@@@', @OutputServerName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputSchemaName@@@', @OutputSchemaName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@RunID@@@', @RunID); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@GETDATE@@@', GETDATE()); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@LocalServerName@@@', CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128))); - EXEC(@StringToExecute); - - END; /* @ValidOutputLocation = 1 */ - ELSE - BEGIN - IF(@OutputType <> 'NONE') - BEGIN - - RAISERROR(N'@Mode=1, we are summarizing.', 0,1) WITH NOWAIT; - - SELECT DB_NAME(i.database_id) AS [Database Name], - CAST((COUNT(*)) AS NVARCHAR(256)) AS [Number Objects], - CAST(CAST(SUM(sz.total_reserved_MB)/ - 1024. AS NUMERIC(29,1)) AS NVARCHAR(500)) AS [All GB], - CAST(CAST(SUM(sz.total_reserved_LOB_MB)/ - 1024. AS NUMERIC(29,1)) AS NVARCHAR(500)) AS [LOB GB], - CAST(CAST(SUM(sz.total_reserved_row_overflow_MB)/ - 1024. AS NUMERIC(29,1)) AS NVARCHAR(500)) AS [Row Overflow GB], - CAST(SUM(CASE WHEN index_id=1 THEN 1 ELSE 0 END)AS NVARCHAR(50)) AS [Clustered Tables], - CAST(SUM(CASE WHEN index_id=1 THEN sz.total_reserved_MB ELSE 0 END) - /1024. AS NUMERIC(29,1)) AS [Clustered Tables GB], - SUM(CASE WHEN index_id NOT IN (0,1) THEN 1 ELSE 0 END) AS [NC Indexes], - CAST(SUM(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) - /1024. AS NUMERIC(29,1)) AS [NC Indexes GB], - CASE WHEN SUM(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) > 0 THEN - CAST(SUM(CASE WHEN index_id IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) - / SUM(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) AS NUMERIC(29,1)) - ELSE 0 END AS [ratio table: NC Indexes], - SUM(CASE WHEN index_id=0 THEN 1 ELSE 0 END) AS [Heaps], - CAST(SUM(CASE WHEN index_id=0 THEN sz.total_reserved_MB ELSE 0 END) - /1024. AS NUMERIC(29,1)) AS [Heaps GB], - SUM(CASE WHEN index_id IN (0,1) AND partition_key_column_name IS NOT NULL THEN 1 ELSE 0 END) AS [Partitioned Tables], - SUM(CASE WHEN index_id NOT IN (0,1) AND partition_key_column_name IS NOT NULL THEN 1 ELSE 0 END) AS [Partitioned NCs], - CAST(SUM(CASE WHEN partition_key_column_name IS NOT NULL THEN sz.total_reserved_MB ELSE 0 END)/1024. AS NUMERIC(29,1)) AS [Partitioned GB], - SUM(CASE WHEN filter_definition <> '' THEN 1 ELSE 0 END) AS [Filtered Indexes], - SUM(CASE WHEN is_indexed_view=1 THEN 1 ELSE 0 END) AS [Indexed Views], - MAX(total_rows) AS [Max Row Count], - CAST(MAX(CASE WHEN index_id IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) - /1024. AS NUMERIC(29,1)) AS [Max Table GB], - CAST(MAX(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) - /1024. AS NUMERIC(29,1)) AS [Max NC Index GB], - SUM(CASE WHEN index_id IN (0,1) AND sz.total_reserved_MB > 1024 THEN 1 ELSE 0 END) AS [Count Tables > 1GB], - SUM(CASE WHEN index_id IN (0,1) AND sz.total_reserved_MB > 10240 THEN 1 ELSE 0 END) AS [Count Tables > 10GB], - SUM(CASE WHEN index_id IN (0,1) AND sz.total_reserved_MB > 102400 THEN 1 ELSE 0 END) AS [Count Tables > 100GB], - SUM(CASE WHEN index_id NOT IN (0,1) AND sz.total_reserved_MB > 1024 THEN 1 ELSE 0 END) AS [Count NCs > 1GB], - SUM(CASE WHEN index_id NOT IN (0,1) AND sz.total_reserved_MB > 10240 THEN 1 ELSE 0 END) AS [Count NCs > 10GB], - SUM(CASE WHEN index_id NOT IN (0,1) AND sz.total_reserved_MB > 102400 THEN 1 ELSE 0 END) AS [Count NCs > 100GB], - MIN(create_date) AS [Oldest Create Date], - MAX(create_date) AS [Most Recent Create Date], - MAX(modify_date) AS [Most Recent Modify Date], - 1 AS [Display Order] - FROM #IndexSanity AS i - --left join here so we don't lose disabled nc indexes - LEFT JOIN #IndexSanitySize AS sz - ON i.index_sanity_id=sz.index_sanity_id - GROUP BY DB_NAME(i.database_id) - UNION ALL - SELECT CASE WHEN @GetAllDatabases = 1 THEN N'All Databases' ELSE N'Database ' + N' as of ' + CONVERT(NVARCHAR(16),GETDATE(),121) END, - @ScriptVersionName, - N'From Your Community Volunteers' , - N'http://FirstResponderKit.org' , - @DaysUptimeInsertValue, - NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL, - NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL, - NULL,NULL,0 AS display_order - ORDER BY [Display Order] ASC - OPTION (RECOMPILE); - END; - END; - - END; /* End @Mode=1 (summarize)*/ - - - - - - - - - ELSE IF (@Mode=2) /*Index Detail*/ - BEGIN - --This mode just spits out all the detail without filters. - --This supports slicing AND dicing in Excel - RAISERROR(N'@Mode=2, here''s the details on existing indexes.', 0,1) WITH NOWAIT; - - IF (@ValidOutputLocation = 1 AND COALESCE(@OutputServerName, @OutputDatabaseName, @OutputSchemaName, @OutputTableName) IS NOT NULL) - BEGIN - IF @SchemaExists = 1 - BEGIN - IF @TableExists = 0 - BEGIN - SET @StringToExecute = - N'CREATE TABLE @@@OutputDatabaseName@@@.@@@OutputSchemaName@@@.@@@OutputTableName@@@ - ( - [id] INT IDENTITY(1,1) NOT NULL, - [run_id] UNIQUEIDENTIFIER, - [run_datetime] DATETIME, - [server_name] NVARCHAR(128), - [database_name] NVARCHAR(128), - [schema_name] NVARCHAR(128), - [table_name] NVARCHAR(128), - [index_name] NVARCHAR(128), - [Drop_Tsql] NVARCHAR(MAX), - [Create_Tsql] NVARCHAR(MAX), - [index_id] INT, - [db_schema_object_indexid] NVARCHAR(500), - [object_type] NVARCHAR(15), - [index_definition] NVARCHAR(MAX), - [key_column_names_with_sort_order] NVARCHAR(MAX), - [count_key_columns] INT, - [include_column_names] NVARCHAR(MAX), - [count_included_columns] INT, - [secret_columns] NVARCHAR(MAX), - [count_secret_columns] INT, - [partition_key_column_name] NVARCHAR(MAX), - [filter_definition] NVARCHAR(MAX), - [is_indexed_view] BIT, - [is_primary_key] BIT, - [is_unique_constraint] BIT, - [is_XML] BIT, - [is_spatial] BIT, - [is_NC_columnstore] BIT, - [is_CX_columnstore] BIT, - [is_in_memory_oltp] BIT, - [is_disabled] BIT, - [is_hypothetical] BIT, - [is_padded] BIT, - [fill_factor] INT, - [is_referenced_by_foreign_key] BIT, - [last_user_seek] DATETIME, - [last_user_scan] DATETIME, - [last_user_lookup] DATETIME, - [last_user_update] DATETIME, - [total_reads] BIGINT, - [user_updates] BIGINT, - [reads_per_write] MONEY, - [index_usage_summary] NVARCHAR(200), - [total_singleton_lookup_count] BIGINT, - [total_range_scan_count] BIGINT, - [total_leaf_delete_count] BIGINT, - [total_leaf_update_count] BIGINT, - [index_op_stats] NVARCHAR(200), - [partition_count] INT, - [total_rows] BIGINT, - [total_reserved_MB] NUMERIC(29,2), - [total_reserved_LOB_MB] NUMERIC(29,2), - [total_reserved_row_overflow_MB] NUMERIC(29,2), - [index_size_summary] NVARCHAR(300), - [total_row_lock_count] BIGINT, - [total_row_lock_wait_count] BIGINT, - [total_row_lock_wait_in_ms] BIGINT, - [avg_row_lock_wait_in_ms] BIGINT, - [total_page_lock_count] BIGINT, - [total_page_lock_wait_count] BIGINT, - [total_page_lock_wait_in_ms] BIGINT, - [avg_page_lock_wait_in_ms] BIGINT, - [total_index_lock_promotion_attempt_count] BIGINT, - [total_index_lock_promotion_count] BIGINT, - [total_forwarded_fetch_count] BIGINT, - [data_compression_desc] NVARCHAR(4000), - [page_latch_wait_count] BIGINT, - [page_latch_wait_in_ms] BIGINT, - [page_io_latch_wait_count] BIGINT, - [page_io_latch_wait_in_ms] BIGINT, - [create_date] DATETIME, - [modify_date] DATETIME, - [more_info] NVARCHAR(500), - [display_order] INT, - CONSTRAINT [PK_ID_@@@RunID@@@] PRIMARY KEY CLUSTERED ([id] ASC) - );'; - - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputSchemaName@@@', @OutputSchemaName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@RunID@@@', @RunID); - - IF @ValidOutputServer = 1 - BEGIN - SET @StringToExecute = REPLACE(@StringToExecute,'''',''''''); - EXEC('EXEC('''+@StringToExecute+''') AT ' + @OutputServerName); - END; - ELSE - BEGIN - EXEC(@StringToExecute); - END; - END; /* @TableExists = 0 */ - - - -- Re-check that table now exists (if not we failed creating it) - SET @TableExists = NULL; - EXEC sp_executesql @TableExistsSql, N'@TableExists BIT OUTPUT', @TableExists OUTPUT; - - IF @TableExists = 1 - BEGIN - SET @StringToExecute = - N'INSERT @@@OutputServerName@@@.@@@OutputDatabaseName@@@.@@@OutputSchemaName@@@.@@@OutputTableName@@@ - ( - [run_id], - [run_datetime], - [server_name], - [database_name], - [schema_name], - [table_name], - [index_name], - [Drop_Tsql], - [Create_Tsql], - [index_id], - [db_schema_object_indexid], - [object_type], - [index_definition], - [key_column_names_with_sort_order], - [count_key_columns], - [include_column_names], - [count_included_columns], - [secret_columns], - [count_secret_columns], - [partition_key_column_name], - [filter_definition], - [is_indexed_view], - [is_primary_key], - [is_unique_constraint], - [is_XML], - [is_spatial], - [is_NC_columnstore], - [is_CX_columnstore], - [is_in_memory_oltp], - [is_disabled], - [is_hypothetical], - [is_padded], - [fill_factor], - [is_referenced_by_foreign_key], - [last_user_seek], - [last_user_scan], - [last_user_lookup], - [last_user_update], - [total_reads], - [user_updates], - [reads_per_write], - [index_usage_summary], - [total_singleton_lookup_count], - [total_range_scan_count], - [total_leaf_delete_count], - [total_leaf_update_count], - [index_op_stats], - [partition_count], - [total_rows], - [total_reserved_MB], - [total_reserved_LOB_MB], - [total_reserved_row_overflow_MB], - [index_size_summary], - [total_row_lock_count], - [total_row_lock_wait_count], - [total_row_lock_wait_in_ms], - [avg_row_lock_wait_in_ms], - [total_page_lock_count], - [total_page_lock_wait_count], - [total_page_lock_wait_in_ms], - [avg_page_lock_wait_in_ms], - [total_index_lock_promotion_attempt_count], - [total_index_lock_promotion_count], - [total_forwarded_fetch_count], - [data_compression_desc], - [page_latch_wait_count], - [page_latch_wait_in_ms], - [page_io_latch_wait_count], - [page_io_latch_wait_in_ms], - [create_date], - [modify_date], - [more_info], - [display_order] - ) - SELECT ''@@@RunID@@@'', - ''@@@GETDATE@@@'', - ''@@@LocalServerName@@@'', - -- Below should be a copy/paste of the real query - -- Make sure all quotes are escaped - i.[database_name] AS [Database Name], - i.[schema_name] AS [Schema Name], - i.[object_name] AS [Object Name], - ISNULL(i.index_name, '''') AS [Index Name], - CASE - WHEN i.is_primary_key = 1 AND i.index_definition <> ''[HEAP]'' - THEN N''--ALTER TABLE '' + QUOTENAME(i.[database_name]) + N''.'' + QUOTENAME(i.[schema_name]) + N''.'' + QUOTENAME(i.[object_name]) + - N'' DROP CONSTRAINT '' + QUOTENAME(i.index_name) + N'';'' - WHEN i.is_primary_key = 0 AND i.is_unique_constraint = 1 AND i.index_definition <> ''[HEAP]'' - THEN N''--ALTER TABLE '' + QUOTENAME(i.[database_name]) + N''.'' + QUOTENAME(i.[schema_name]) + N''.'' + QUOTENAME(i.[object_name]) + - N'' DROP CONSTRAINT '' + QUOTENAME(i.index_name) + N'';'' - WHEN i.is_primary_key = 0 AND i.index_definition <> ''[HEAP]'' - THEN N''--DROP INDEX ''+ QUOTENAME(i.index_name) + N'' ON '' + QUOTENAME(i.[database_name]) + N''.'' + - QUOTENAME(i.[schema_name]) + N''.'' + QUOTENAME(i.[object_name]) + N'';'' - ELSE N'''' - END AS [Drop TSQL], - CASE - WHEN i.index_definition = ''[HEAP]'' THEN N'''' - ELSE N''--'' + ict.create_tsql END AS [Create TSQL], - CAST(i.index_id AS NVARCHAR(10))AS [Index ID], - db_schema_object_indexid AS [Details: schema.table.index(indexid)], - CASE WHEN index_id IN ( 1, 0 ) THEN ''TABLE'' - ELSE ''NonClustered'' - END AS [Object Type], - LEFT(index_definition,4000) AS [Definition: [Property]] ColumnName {datatype maxbytes}], - ISNULL(LTRIM(key_column_names_with_sort_order), '''') AS [Key Column Names With Sort], - ISNULL(count_key_columns, 0) AS [Count Key Columns], - ISNULL(include_column_names, '''') AS [Include Column Names], - ISNULL(count_included_columns,0) AS [Count Included Columns], - ISNULL(secret_columns,'''') AS [Secret Column Names], - ISNULL(count_secret_columns,0) AS [Count Secret Columns], - ISNULL(partition_key_column_name, '''') AS [Partition Key Column Name], - ISNULL(filter_definition, '''') AS [Filter Definition], - is_indexed_view AS [Is Indexed View], - is_primary_key AS [Is Primary Key], - is_unique_constraint AS [Is Unique Constraint], - is_XML AS [Is XML], - is_spatial AS [Is Spatial], - is_NC_columnstore AS [Is NC Columnstore], - is_CX_columnstore AS [Is CX Columnstore], - is_in_memory_oltp AS [Is In-Memory OLTP], - is_disabled AS [Is Disabled], - is_hypothetical AS [Is Hypothetical], - is_padded AS [Is Padded], - fill_factor AS [Fill Factor], - is_referenced_by_foreign_key AS [Is Reference by Foreign Key], - last_user_seek AS [Last User Seek], - last_user_scan AS [Last User Scan], - last_user_lookup AS [Last User Lookup], - last_user_update AS [Last User Update], - total_reads AS [Total Reads], - user_updates AS [User Updates], - reads_per_write AS [Reads Per Write], - index_usage_summary AS [Index Usage], - sz.total_singleton_lookup_count AS [Singleton Lookups], - sz.total_range_scan_count AS [Range Scans], - sz.total_leaf_delete_count AS [Leaf Deletes], - sz.total_leaf_update_count AS [Leaf Updates], - sz.index_op_stats AS [Index Op Stats], - sz.partition_count AS [Partition Count], - sz.total_rows AS [Rows], - sz.total_reserved_MB AS [Reserved MB], - sz.total_reserved_LOB_MB AS [Reserved LOB MB], - sz.total_reserved_row_overflow_MB AS [Reserved Row Overflow MB], - sz.index_size_summary AS [Index Size], - sz.total_row_lock_count AS [Row Lock Count], - sz.total_row_lock_wait_count AS [Row Lock Wait Count], - sz.total_row_lock_wait_in_ms AS [Row Lock Wait ms], - sz.avg_row_lock_wait_in_ms AS [Avg Row Lock Wait ms], - sz.total_page_lock_count AS [Page Lock Count], - sz.total_page_lock_wait_count AS [Page Lock Wait Count], - sz.total_page_lock_wait_in_ms AS [Page Lock Wait ms], - sz.avg_page_lock_wait_in_ms AS [Avg Page Lock Wait ms], - sz.total_index_lock_promotion_attempt_count AS [Lock Escalation Attempts], - sz.total_index_lock_promotion_count AS [Lock Escalations], - sz.total_forwarded_fetch_count AS [Forwarded Fetches], - sz.data_compression_desc AS [Data Compression], - sz.page_latch_wait_count, - sz.page_latch_wait_in_ms, - sz.page_io_latch_wait_count, - sz.page_io_latch_wait_in_ms, - i.create_date AS [Create Date], - i.modify_date AS [Modify Date], - more_info AS [More Info], - 1 AS [Display Order] - FROM #IndexSanity AS i - LEFT JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id - LEFT JOIN #IndexCreateTsql AS ict ON i.index_sanity_id = ict.index_sanity_id - ORDER BY [Database Name], [Schema Name], [Object Name], [Index ID] - OPTION (RECOMPILE);'; - - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputServerName@@@', @OutputServerName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputSchemaName@@@', @OutputSchemaName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@RunID@@@', @RunID); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@GETDATE@@@', GETDATE()); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@LocalServerName@@@', CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128))); - EXEC(@StringToExecute); - END; /* @TableExists = 1 */ - ELSE - RAISERROR('Creation of the output table failed.', 16, 0); - END; /* @TableExists = 0 */ - ELSE - RAISERROR (N'Invalid schema name, data could not be saved.', 16, 0); - END; /* @ValidOutputLocation = 1 */ - ELSE - - IF(@OutputType <> 'NONE') - BEGIN - SELECT i.[database_name] AS [Database Name], - i.[schema_name] AS [Schema Name], - i.[object_name] AS [Object Name], - ISNULL(i.index_name, '') AS [Index Name], - CAST(i.index_id AS NVARCHAR(10))AS [Index ID], - db_schema_object_indexid AS [Details: schema.table.index(indexid)], - CASE WHEN index_id IN ( 1, 0 ) THEN 'TABLE' - ELSE 'NonClustered' - END AS [Object Type], - index_definition AS [Definition: [Property]] ColumnName {datatype maxbytes}], - ISNULL(LTRIM(key_column_names_with_sort_order), '') AS [Key Column Names With Sort], - ISNULL(count_key_columns, 0) AS [Count Key Columns], - ISNULL(include_column_names, '') AS [Include Column Names], - ISNULL(count_included_columns,0) AS [Count Included Columns], - ISNULL(secret_columns,'') AS [Secret Column Names], - ISNULL(count_secret_columns,0) AS [Count Secret Columns], - ISNULL(partition_key_column_name, '') AS [Partition Key Column Name], - ISNULL(filter_definition, '') AS [Filter Definition], - is_indexed_view AS [Is Indexed View], - is_primary_key AS [Is Primary Key], - is_unique_constraint AS [Is Unique Constraint] , - is_XML AS [Is XML], - is_spatial AS [Is Spatial], - is_NC_columnstore AS [Is NC Columnstore], - is_CX_columnstore AS [Is CX Columnstore], - is_in_memory_oltp AS [Is In-Memory OLTP], - is_disabled AS [Is Disabled], - is_hypothetical AS [Is Hypothetical], - is_padded AS [Is Padded], - fill_factor AS [Fill Factor], - is_referenced_by_foreign_key AS [Is Reference by Foreign Key], - last_user_seek AS [Last User Seek], - last_user_scan AS [Last User Scan], - last_user_lookup AS [Last User Lookup], - last_user_update AS [Last User Update], - total_reads AS [Total Reads], - user_updates AS [User Updates], - reads_per_write AS [Reads Per Write], - index_usage_summary AS [Index Usage], - sz.total_singleton_lookup_count AS [Singleton Lookups], - sz.total_range_scan_count AS [Range Scans], - sz.total_leaf_delete_count AS [Leaf Deletes], - sz.total_leaf_update_count AS [Leaf Updates], - sz.index_op_stats AS [Index Op Stats], - sz.partition_count AS [Partition Count], - sz.total_rows AS [Rows], - sz.total_reserved_MB AS [Reserved MB], - sz.total_reserved_LOB_MB AS [Reserved LOB MB], - sz.total_reserved_row_overflow_MB AS [Reserved Row Overflow MB], - sz.index_size_summary AS [Index Size], - sz.total_row_lock_count AS [Row Lock Count], - sz.total_row_lock_wait_count AS [Row Lock Wait Count], - sz.total_row_lock_wait_in_ms AS [Row Lock Wait ms], - sz.avg_row_lock_wait_in_ms AS [Avg Row Lock Wait ms], - sz.total_page_lock_count AS [Page Lock Count], - sz.total_page_lock_wait_count AS [Page Lock Wait Count], - sz.total_page_lock_wait_in_ms AS [Page Lock Wait ms], - sz.avg_page_lock_wait_in_ms AS [Avg Page Lock Wait ms], - sz.total_index_lock_promotion_attempt_count AS [Lock Escalation Attempts], - sz.total_index_lock_promotion_count AS [Lock Escalations], - sz.page_latch_wait_count AS [Page Latch Wait Count], - sz.page_latch_wait_in_ms AS [Page Latch Wait ms], - sz.page_io_latch_wait_count AS [Page IO Latch Wait Count], - sz.page_io_latch_wait_in_ms as [Page IO Latch Wait ms], - sz.total_forwarded_fetch_count AS [Forwarded Fetches], - sz.data_compression_desc AS [Data Compression], - i.create_date AS [Create Date], - i.modify_date AS [Modify Date], - more_info AS [More Info], - CASE - WHEN i.is_primary_key = 1 AND i.index_definition <> '[HEAP]' - THEN N'--ALTER TABLE ' + QUOTENAME(i.[database_name]) + N'.' + QUOTENAME(i.[schema_name]) + N'.' + QUOTENAME(i.[object_name]) - + N' DROP CONSTRAINT ' + QUOTENAME(i.index_name) + N';' - WHEN i.is_primary_key = 0 AND i.is_unique_constraint = 1 AND i.index_definition <> '[HEAP]' - THEN N'--ALTER TABLE ' + QUOTENAME(i.[database_name]) + N'.' + QUOTENAME(i.[schema_name]) + N'.' + QUOTENAME(i.[object_name]) - + N' DROP CONSTRAINT ' + QUOTENAME(i.index_name) + N';' - WHEN i.is_primary_key = 0 AND i.index_definition <> '[HEAP]' - THEN N'--DROP INDEX '+ QUOTENAME(i.index_name) + N' ON ' + QUOTENAME(i.[database_name]) + N'.' + - QUOTENAME(i.[schema_name]) + N'.' + QUOTENAME(i.[object_name]) + N';' - ELSE N'' - END AS [Drop TSQL], - CASE - WHEN i.index_definition = '[HEAP]' THEN N'' - ELSE N'--' + ict.create_tsql END AS [Create TSQL], - 1 AS [Display Order] - FROM #IndexSanity AS i --left join here so we don't lose disabled nc indexes - LEFT JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id - LEFT JOIN #IndexCreateTsql AS ict ON i.index_sanity_id = ict.index_sanity_id - ORDER BY /* Shout out to DHutmacher */ - /*DESC*/ - CASE WHEN @SortOrder = N'rows' AND @SortDirection = N'desc' THEN sz.total_rows ELSE NULL END DESC, - CASE WHEN @SortOrder = N'reserved_mb' AND @SortDirection = N'desc' THEN sz.total_reserved_MB ELSE NULL END DESC, - CASE WHEN @SortOrder = N'size' AND @SortDirection = N'desc' THEN sz.total_reserved_MB ELSE NULL END DESC, - CASE WHEN @SortOrder = N'reserved_lob_mb' AND @SortDirection = N'desc' THEN sz.total_reserved_LOB_MB ELSE NULL END DESC, - CASE WHEN @SortOrder = N'lob' AND @SortDirection = N'desc' THEN sz.total_reserved_LOB_MB ELSE NULL END DESC, - CASE WHEN @SortOrder = N'total_row_lock_wait_in_ms' AND @SortDirection = N'desc' THEN COALESCE(sz.total_row_lock_wait_in_ms,0) ELSE NULL END DESC, - CASE WHEN @SortOrder = N'total_page_lock_wait_in_ms' AND @SortDirection = N'desc' THEN COALESCE(sz.total_page_lock_wait_in_ms,0) ELSE NULL END DESC, - CASE WHEN @SortOrder = N'lock_time' AND @SortDirection = N'desc' THEN (COALESCE(sz.total_row_lock_wait_in_ms,0) + COALESCE(sz.total_page_lock_wait_in_ms,0)) ELSE NULL END DESC, - CASE WHEN @SortOrder = N'total_reads' AND @SortDirection = N'desc' THEN total_reads ELSE NULL END DESC, - CASE WHEN @SortOrder = N'reads' AND @SortDirection = N'desc' THEN total_reads ELSE NULL END DESC, - CASE WHEN @SortOrder = N'user_updates' AND @SortDirection = N'desc' THEN user_updates ELSE NULL END DESC, - CASE WHEN @SortOrder = N'writes' AND @SortDirection = N'desc' THEN user_updates ELSE NULL END DESC, - CASE WHEN @SortOrder = N'reads_per_write' AND @SortDirection = N'desc' THEN reads_per_write ELSE NULL END DESC, - CASE WHEN @SortOrder = N'ratio' AND @SortDirection = N'desc' THEN reads_per_write ELSE NULL END DESC, - CASE WHEN @SortOrder = N'forward_fetches' AND @SortDirection = N'desc' THEN sz.total_forwarded_fetch_count ELSE NULL END DESC, - CASE WHEN @SortOrder = N'fetches' AND @SortDirection = N'desc' THEN sz.total_forwarded_fetch_count ELSE NULL END DESC, - CASE WHEN @SortOrder = N'create_date' AND @SortDirection = N'desc' THEN CONVERT(DATETIME, i.create_date) ELSE NULL END DESC, - CASE WHEN @SortOrder = N'modify_date' AND @SortDirection = N'desc' THEN CONVERT(DATETIME, i.modify_date) ELSE NULL END DESC, - /*ASC*/ - CASE WHEN @SortOrder = N'rows' AND @SortDirection = N'asc' THEN sz.total_rows ELSE NULL END ASC, - CASE WHEN @SortOrder = N'reserved_mb' AND @SortDirection = N'asc' THEN sz.total_reserved_MB ELSE NULL END ASC, - CASE WHEN @SortOrder = N'size' AND @SortDirection = N'asc' THEN sz.total_reserved_MB ELSE NULL END ASC, - CASE WHEN @SortOrder = N'reserved_lob_mb' AND @SortDirection = N'asc' THEN sz.total_reserved_LOB_MB ELSE NULL END ASC, - CASE WHEN @SortOrder = N'lob' AND @SortDirection = N'asc' THEN sz.total_reserved_LOB_MB ELSE NULL END ASC, - CASE WHEN @SortOrder = N'total_row_lock_wait_in_ms' AND @SortDirection = N'asc' THEN COALESCE(sz.total_row_lock_wait_in_ms,0) ELSE NULL END ASC, - CASE WHEN @SortOrder = N'total_page_lock_wait_in_ms' AND @SortDirection = N'asc' THEN COALESCE(sz.total_page_lock_wait_in_ms,0) ELSE NULL END ASC, - CASE WHEN @SortOrder = N'lock_time' AND @SortDirection = N'asc' THEN (COALESCE(sz.total_row_lock_wait_in_ms,0) + COALESCE(sz.total_page_lock_wait_in_ms,0)) ELSE NULL END ASC, - CASE WHEN @SortOrder = N'total_reads' AND @SortDirection = N'asc' THEN total_reads ELSE NULL END ASC, - CASE WHEN @SortOrder = N'reads' AND @SortDirection = N'asc' THEN total_reads ELSE NULL END ASC, - CASE WHEN @SortOrder = N'user_updates' AND @SortDirection = N'asc' THEN user_updates ELSE NULL END ASC, - CASE WHEN @SortOrder = N'writes' AND @SortDirection = N'asc' THEN user_updates ELSE NULL END ASC, - CASE WHEN @SortOrder = N'reads_per_write' AND @SortDirection = N'asc' THEN reads_per_write ELSE NULL END ASC, - CASE WHEN @SortOrder = N'ratio' AND @SortDirection = N'asc' THEN reads_per_write ELSE NULL END ASC, - CASE WHEN @SortOrder = N'forward_fetches' AND @SortDirection = N'asc' THEN sz.total_forwarded_fetch_count ELSE NULL END ASC, - CASE WHEN @SortOrder = N'fetches' AND @SortDirection = N'asc' THEN sz.total_forwarded_fetch_count ELSE NULL END ASC, - CASE WHEN @SortOrder = N'create_date' AND @SortDirection = N'asc' THEN CONVERT(DATETIME, i.create_date) ELSE NULL END ASC, - CASE WHEN @SortOrder = N'modify_date' AND @SortDirection = N'asc' THEN CONVERT(DATETIME, i.modify_date) ELSE NULL END ASC, - i.[database_name], [Schema Name], [Object Name], [Index ID] - OPTION (RECOMPILE); - END; - - END; /* End @Mode=2 (index detail)*/ - - - - - - - - - ELSE IF (@Mode=3) /*Missing index Detail*/ - BEGIN - IF (@ValidOutputLocation = 1 AND COALESCE(@OutputServerName, @OutputDatabaseName, @OutputSchemaName, @OutputTableName) IS NOT NULL) - BEGIN - - IF NOT @SchemaExists = 1 - BEGIN - RAISERROR (N'Invalid schema name, data could not be saved.', 16, 0); - RETURN; - END - - IF @TableExists = 0 - BEGIN - SET @StringToExecute = - N'CREATE TABLE @@@OutputDatabaseName@@@.@@@OutputSchemaName@@@.@@@OutputTableName@@@ - ( - [id] INT IDENTITY(1,1) NOT NULL, - [run_id] UNIQUEIDENTIFIER, - [run_datetime] DATETIME, - [server_name] NVARCHAR(128), - [database_name] NVARCHAR(128), - [schema_name] NVARCHAR(128), - [table_name] NVARCHAR(128), - [magic_benefit_number] BIGINT, - [missing_index_details] NVARCHAR(MAX), - [avg_total_user_cost] NUMERIC(29,4), - [avg_user_impact] NUMERIC(29,1), - [user_seeks] BIGINT, - [user_scans] BIGINT, - [unique_compiles] BIGINT, - [equality_columns_with_data_type] NVARCHAR(MAX), - [inequality_columns_with_data_type] NVARCHAR(MAX), - [included_columns_with_data_type] NVARCHAR(MAX), - [index_estimated_impact] NVARCHAR(256), - [create_tsql] NVARCHAR(MAX), - [more_info] NVARCHAR(600), - [display_order] INT, - [is_low] BIT, - [sample_query_plan] XML, - CONSTRAINT [PK_ID_@@@RunID@@@] PRIMARY KEY CLUSTERED ([id] ASC) - );'; - - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputSchemaName@@@', @OutputSchemaName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@RunID@@@', @RunID); - - IF @ValidOutputServer = 1 - BEGIN - SET @StringToExecute = REPLACE(@StringToExecute,'''',''''''); - EXEC('EXEC('''+@StringToExecute+''') AT ' + @OutputServerName); - END; - ELSE - BEGIN - EXEC(@StringToExecute); - END; - END; /* @TableExists = 0 */ - - -- Re-check that table now exists (if not we failed creating it) - SET @TableExists = NULL; - EXEC sp_executesql @TableExistsSql, N'@TableExists BIT OUTPUT', @TableExists OUTPUT; - - IF NOT @TableExists = 1 - BEGIN - RAISERROR('Creation of the output table failed.', 16, 0); - RETURN; - END; - SET @StringToExecute = - N'WITH create_date AS ( - SELECT i.database_id, - i.schema_name, - i.[object_id], - ISNULL(NULLIF(MAX(DATEDIFF(DAY, i.create_date, SYSDATETIME())), 0), 1) AS create_days - FROM #IndexSanity AS i - GROUP BY i.database_id, i.schema_name, i.object_id - ) - INSERT @@@OutputServerName@@@.@@@OutputDatabaseName@@@.@@@OutputSchemaName@@@.@@@OutputTableName@@@ - ( - [run_id], - [run_datetime], - [server_name], - [database_name], - [schema_name], - [table_name], - [magic_benefit_number], - [missing_index_details], - [avg_total_user_cost], - [avg_user_impact], - [user_seeks], - [user_scans], - [unique_compiles], - [equality_columns_with_data_type], - [inequality_columns_with_data_type], - [included_columns_with_data_type], - [index_estimated_impact], - [create_tsql], - [more_info], - [display_order], - [is_low], - [sample_query_plan] - ) - SELECT ''@@@RunID@@@'', - ''@@@GETDATE@@@'', - ''@@@LocalServerName@@@'', - -- Below should be a copy/paste of the real query - -- Make sure all quotes are escaped - -- NOTE! information line is skipped from output and the query below - -- NOTE! CTE block is above insert in the copied SQL - mi.database_name AS [Database Name], - mi.[schema_name] AS [Schema], - mi.table_name AS [Table], - CAST((mi.magic_benefit_number / CASE WHEN cd.create_days < @DaysUptime THEN cd.create_days ELSE @DaysUptime END) AS BIGINT) - AS [Magic Benefit Number], - mi.missing_index_details AS [Missing Index Details], - mi.avg_total_user_cost AS [Avg Query Cost], - mi.avg_user_impact AS [Est Index Improvement], - mi.user_seeks AS [Seeks], - mi.user_scans AS [Scans], - mi.unique_compiles AS [Compiles], - mi.equality_columns_with_data_type AS [Equality Columns], - mi.inequality_columns_with_data_type AS [Inequality Columns], - mi.included_columns_with_data_type AS [Included Columns], - mi.index_estimated_impact AS [Estimated Impact], - mi.create_tsql AS [Create TSQL], - mi.more_info AS [More Info], - 1 AS [Display Order], - mi.is_low, - mi.sample_query_plan AS [Sample Query Plan] - FROM #MissingIndexes AS mi - LEFT JOIN create_date AS cd - ON mi.[object_id] = cd.object_id - AND mi.database_id = cd.database_id - AND mi.schema_name = cd.schema_name - /* Minimum benefit threshold = 100k/day of uptime OR since table creation date, whichever is lower*/ - WHERE @ShowAllMissingIndexRequests=1 - OR (mi.magic_benefit_number / CASE WHEN cd.create_days < @DaysUptime THEN cd.create_days ELSE @DaysUptime END) >= 100000 - ORDER BY [Display Order] ASC, [Magic Benefit Number] DESC - OPTION (RECOMPILE);'; - - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputServerName@@@', @OutputServerName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputSchemaName@@@', @OutputSchemaName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@RunID@@@', @RunID); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@GETDATE@@@', GETDATE()); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@LocalServerName@@@', CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128))); - EXEC sp_executesql @StringToExecute, N'@DaysUptime NUMERIC(23,2), @ShowAllMissingIndexRequests BIT', @DaysUptime = @DaysUptime, @ShowAllMissingIndexRequests = @ShowAllMissingIndexRequests; - - END; /* @ValidOutputLocation = 1 */ - ELSE - BEGIN - IF(@OutputType <> 'NONE') - BEGIN - WITH create_date AS ( - SELECT i.database_id, - i.schema_name, - i.[object_id], - ISNULL(NULLIF(MAX(DATEDIFF(DAY, i.create_date, SYSDATETIME())), 0), 1) AS create_days - FROM #IndexSanity AS i - GROUP BY i.database_id, i.schema_name, i.object_id - ) - SELECT - mi.database_name AS [Database Name], - mi.[schema_name] AS [Schema], - mi.table_name AS [Table], - CAST((mi.magic_benefit_number / CASE WHEN cd.create_days < @DaysUptime THEN cd.create_days ELSE @DaysUptime END) AS BIGINT) - AS [Magic Benefit Number], - mi.missing_index_details AS [Missing Index Details], - mi.avg_total_user_cost AS [Avg Query Cost], - mi.avg_user_impact AS [Est Index Improvement], - mi.user_seeks AS [Seeks], - mi.user_scans AS [Scans], - mi.unique_compiles AS [Compiles], - mi.equality_columns_with_data_type AS [Equality Columns], - mi.inequality_columns_with_data_type AS [Inequality Columns], - mi.included_columns_with_data_type AS [Included Columns], - mi.index_estimated_impact AS [Estimated Impact], - mi.create_tsql AS [Create TSQL], - mi.more_info AS [More Info], - 1 AS [Display Order], - mi.is_low, - mi.sample_query_plan AS [Sample Query Plan] - FROM #MissingIndexes AS mi - LEFT JOIN create_date AS cd - ON mi.[object_id] = cd.object_id - AND mi.database_id = cd.database_id - AND mi.schema_name = cd.schema_name - /* Minimum benefit threshold = 100k/day of uptime OR since table creation date, whichever is lower*/ - WHERE @ShowAllMissingIndexRequests=1 - OR (mi.magic_benefit_number / CASE WHEN cd.create_days < @DaysUptime THEN cd.create_days ELSE @DaysUptime END) >= 100000 - UNION ALL - SELECT - @ScriptVersionName, - N'From Your Community Volunteers' , - N'http://FirstResponderKit.org' , - 100000000000, - @DaysUptimeInsertValue, - NULL,NULL,NULL,NULL,NULL,NULL,NULL, - NULL, NULL, NULL, NULL, 0 AS [Display Order], NULL AS is_low, NULL - ORDER BY [Display Order] ASC, [Magic Benefit Number] DESC - OPTION (RECOMPILE); - END; - - - IF (@BringThePain = 1 - AND @DatabaseName IS NOT NULL - AND @GetAllDatabases = 0) - - BEGIN - EXEC sp_BlitzCache @SortOrder = 'sp_BlitzIndex', @DatabaseName = @DatabaseName, @BringThePain = 1, @QueryFilter = 'statement', @HideSummary = 1; - END; - - END; - - - - - - END; /* End @Mode=3 (index detail)*/ - SET @d = CONVERT(VARCHAR(19), GETDATE(), 121); - RAISERROR (N'finishing at %s',0,1, @d) WITH NOWAIT; -END /* End @TableName IS NULL (mode 0/1/2/3/4) */ -END TRY - -BEGIN CATCH - RAISERROR (N'Failure analyzing temp tables.', 0,1) WITH NOWAIT; - - SELECT @msg = ERROR_MESSAGE(), @ErrorSeverity = ERROR_SEVERITY(), @ErrorState = ERROR_STATE(); - - RAISERROR (@msg, - @ErrorSeverity, - @ErrorState - ); - - WHILE @@trancount > 0 - ROLLBACK; - - RETURN; - END CATCH; -GO -IF OBJECT_ID('dbo.sp_BlitzLock') IS NULL -BEGIN - EXEC ('CREATE PROCEDURE dbo.sp_BlitzLock AS RETURN 0;'); -END; -GO - -ALTER PROCEDURE - dbo.sp_BlitzLock -( - @DatabaseName sysname = NULL, - @StartDate datetime = NULL, - @EndDate datetime = NULL, - @ObjectName nvarchar(1024) = NULL, - @StoredProcName nvarchar(1024) = NULL, - @AppName sysname = NULL, - @HostName sysname = NULL, - @LoginName sysname = NULL, - @EventSessionName sysname = N'system_health', - @TargetSessionType sysname = NULL, - @VictimsOnly bit = 0, - @Debug bit = 0, - @Help bit = 0, - @Version varchar(30) = NULL OUTPUT, - @VersionDate datetime = NULL OUTPUT, - @VersionCheckMode bit = 0, - @OutputDatabaseName sysname = NULL, - @OutputSchemaName sysname = N'dbo', /*ditto as below*/ - @OutputTableName sysname = N'BlitzLock', /*put a standard here no need to check later in the script*/ - @ExportToExcel bit = 0 -) -WITH RECOMPILE -AS -BEGIN - SET STATISTICS XML OFF; - SET NOCOUNT ON; - SET XACT_ABORT OFF; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - - SELECT @Version = '8.19', @VersionDate = '20240222'; - - IF @VersionCheckMode = 1 - BEGIN - RETURN; - END; - - IF @Help = 1 - BEGIN - PRINT N' - /* - sp_BlitzLock from http://FirstResponderKit.org - - This script checks for and analyzes deadlocks from the system health session or a custom extended event path - - Variables you can use: - - @DatabaseName: If you want to filter to a specific database - - @StartDate: The date you want to start searching on, defaults to last 7 days - - @EndDate: The date you want to stop searching on, defaults to current date - - @ObjectName: If you want to filter to a specific able. - The object name has to be fully qualified ''Database.Schema.Table'' - - @StoredProcName: If you want to search for a single stored proc - The proc name has to be fully qualified ''Database.Schema.Sproc'' - - @AppName: If you want to filter to a specific application - - @HostName: If you want to filter to a specific host - - @LoginName: If you want to filter to a specific login - - @EventSessionName: If you want to point this at an XE session rather than the system health session. - - @TargetSessionType: Can be ''ring_buffer'' or ''event_file''. Leave NULL to auto-detect. - - @OutputDatabaseName: If you want to output information to a specific database - - @OutputSchemaName: Specify a schema name to output information to a specific Schema - - @OutputTableName: Specify table name to to output information to a specific table - - To learn more, visit http://FirstResponderKit.org where you can download new - versions for free, watch training videos on how it works, get more info on - the findings, contribute your own code, and more. - - Known limitations of this version: - - Only SQL Server 2012 and newer is supported - - If your tables have weird characters in them (https://en.wikipedia.org/wiki/List_of_xml_and_HTML_character_entity_references) you may get errors trying to parse the xml. - I took a long look at this one, and: - 1) Trying to account for all the weird places these could crop up is a losing effort. - 2) Replace is slow af on lots of xml. - - Unknown limitations of this version: - - None. (If we knew them, they would be known. Duh.) - - MIT License - - Copyright (c) Brent Ozar Unlimited - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE. - - */'; - - RETURN; - END; /* @Help = 1 */ - - /*Declare local variables used in the procudure*/ - DECLARE - @DatabaseId int = - DB_ID(@DatabaseName), - @ProductVersion nvarchar(128) = - CAST(SERVERPROPERTY('ProductVersion') AS nvarchar(128)), - @ProductVersionMajor float = - SUBSTRING - ( - CAST(SERVERPROPERTY('ProductVersion') AS nvarchar(128)), - 1, - CHARINDEX('.', CAST(SERVERPROPERTY('ProductVersion') AS nvarchar(128))) + 1 - ), - @ProductVersionMinor int = - PARSENAME - ( - CONVERT - ( - varchar(32), - CAST(SERVERPROPERTY('ProductVersion') AS nvarchar(128)) - ), - 2 - ), - @ObjectFullName nvarchar(MAX) = N'', - @Azure bit = - CASE - WHEN - ( - SELECT - CONVERT - ( - integer, - SERVERPROPERTY('EngineEdition') - ) - ) = 5 - THEN 1 - ELSE 0 - END, - @MI bit = - CASE - WHEN - ( - SELECT - CONVERT - ( - integer, - SERVERPROPERTY('EngineEdition') - ) - ) = 8 - THEN 1 - ELSE 0 - END, - @RDS bit = - CASE - WHEN LEFT(CAST(SERVERPROPERTY('ComputerNamePhysicalNetBIOS') AS varchar(8000)), 8) <> 'EC2AMAZ-' - AND LEFT(CAST(SERVERPROPERTY('MachineName') AS varchar(8000)), 8) <> 'EC2AMAZ-' - AND DB_ID('rdsadmin') IS NULL - THEN 0 - ELSE 1 - END, - @d varchar(40) = '', - @StringToExecute nvarchar(4000) = N'', - @StringToExecuteParams nvarchar(500) = N'', - @r sysname = NULL, - @OutputTableFindings nvarchar(100) = N'[BlitzLockFindings]', - @DeadlockCount int = 0, - @ServerName sysname = @@SERVERNAME, - @OutputDatabaseCheck bit = -1, - @SessionId int = 0, - @TargetSessionId int = 0, - @FileName nvarchar(4000) = N'', - @inputbuf_bom nvarchar(1) = CONVERT(nvarchar(1), 0x0a00, 0), - @deadlock_result nvarchar(MAX) = N'', - @StartDateOriginal datetime = @StartDate, - @EndDateOriginal datetime = @EndDate, - @StartDateUTC datetime, - @EndDateUTC datetime; - - /*Temporary objects used in the procedure*/ - DECLARE - @sysAssObjId AS table - ( - database_id int, - partition_id bigint, - schema_name sysname, - table_name sysname - ); - - CREATE TABLE - #x - ( - x xml NOT NULL - DEFAULT N'x' - ); - - CREATE TABLE - #deadlock_data - ( - deadlock_xml xml NOT NULL - DEFAULT N'x' - ); - - CREATE TABLE - #t - ( - id int NOT NULL - ); - - CREATE TABLE - #deadlock_findings - ( - id int IDENTITY PRIMARY KEY, - check_id int NOT NULL, - database_name nvarchar(256), - object_name nvarchar(1000), - finding_group nvarchar(100), - finding nvarchar(4000), - sort_order bigint - ); - - /*Set these to some sane defaults if NULLs are passed in*/ - /*Normally I'd hate this, but we RECOMPILE everything*/ - - SELECT - @StartDate = - CASE - WHEN @StartDate IS NULL - THEN - DATEADD - ( - MINUTE, - DATEDIFF - ( - MINUTE, - SYSDATETIME(), - GETUTCDATE() - ), - DATEADD - ( - DAY, - -7, - SYSDATETIME() - ) - ) - ELSE - DATEADD - ( - MINUTE, - DATEDIFF - ( - MINUTE, - SYSDATETIME(), - GETUTCDATE() - ), - @StartDate - ) - END, - @EndDate = - CASE - WHEN @EndDate IS NULL - THEN - DATEADD - ( - MINUTE, - DATEDIFF - ( - MINUTE, - SYSDATETIME(), - GETUTCDATE() - ), - SYSDATETIME() - ) - ELSE - DATEADD - ( - MINUTE, - DATEDIFF - ( - MINUTE, - SYSDATETIME(), - GETUTCDATE() - ), - @EndDate - ) - END; - - SELECT - @StartDateUTC = @StartDate, - @EndDateUTC = @EndDate; - - IF - ( - @MI = 1 - AND @EventSessionName = N'system_health' - AND @TargetSessionType IS NULL - ) - BEGIN - SET - @TargetSessionType = N'ring_buffer'; - END; - - IF @Azure = 0 - BEGIN - IF NOT EXISTS - ( - SELECT - 1/0 - FROM sys.server_event_sessions AS ses - JOIN sys.dm_xe_sessions AS dxs - ON dxs.name = ses.name - WHERE ses.name = @EventSessionName - AND dxs.create_time IS NOT NULL - ) - BEGIN - RAISERROR('A session with the name %s does not exist or is not currently active.', 11, 1, @EventSessionName) WITH NOWAIT; - RETURN; - END; - END; - - IF @Azure = 1 - BEGIN - IF NOT EXISTS - ( - SELECT - 1/0 - FROM sys.database_event_sessions AS ses - JOIN sys.dm_xe_database_sessions AS dxs - ON dxs.name = ses.name - WHERE ses.name = @EventSessionName - AND dxs.create_time IS NOT NULL - ) - BEGIN - RAISERROR('A session with the name %s does not exist or is not currently active.', 11, 1, @EventSessionName) WITH NOWAIT; - RETURN; - END; - END; - - IF @OutputDatabaseName IS NOT NULL - BEGIN /*IF databaseName is set, do some sanity checks and put [] around def.*/ - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('@OutputDatabaseName set to %s, checking validity at %s', 0, 1, @OutputDatabaseName, @d) WITH NOWAIT; - - IF NOT EXISTS - ( - SELECT - 1/0 - FROM sys.databases AS d - WHERE d.name = @OutputDatabaseName - ) /*If database is invalid raiserror and set bitcheck*/ - BEGIN - RAISERROR('Database Name (%s) for output of table is invalid please, Output to Table will not be performed', 0, 1, @OutputDatabaseName) WITH NOWAIT; - SET @OutputDatabaseCheck = -1; /* -1 invalid/false, 0 = good/true */ - END; - ELSE - BEGIN - SET @OutputDatabaseCheck = 0; - - SELECT - @StringToExecute = - N'SELECT @r = o.name FROM ' + - @OutputDatabaseName + - N'.sys.objects AS o WHERE o.type_desc = N''USER_TABLE'' AND o.name = ' + - QUOTENAME - ( - @OutputTableName, - N'''' - ) + - N' AND o.schema_id = SCHEMA_ID(' + - QUOTENAME - ( - @OutputSchemaName, - N'''' - ) + - N');', - @StringToExecuteParams = - N'@r sysname OUTPUT'; - - IF @Debug = 1 BEGIN PRINT @StringToExecute; END; - EXEC sys.sp_executesql - @StringToExecute, - @StringToExecuteParams, - @r OUTPUT; - - IF @Debug = 1 - BEGIN - RAISERROR('@r is set to: %s for schema name %s and table name %s', 0, 1, @r, @OutputSchemaName, @OutputTableName) WITH NOWAIT; - END; - - /*protection spells*/ - SELECT - @ObjectFullName = - QUOTENAME(@OutputDatabaseName) + - N'.' + - QUOTENAME(@OutputSchemaName) + - N'.' + - QUOTENAME(@OutputTableName), - @OutputDatabaseName = - QUOTENAME(@OutputDatabaseName), - @OutputTableName = - QUOTENAME(@OutputTableName), - @OutputSchemaName = - QUOTENAME(@OutputSchemaName); - - IF (@r IS NOT NULL) /*if it is not null, there is a table, so check for newly added columns*/ - BEGIN - /* If the table doesn't have the new spid column, add it. See Github #3101. */ - SET @StringToExecute = - N'IF NOT EXISTS (SELECT 1/0 FROM ' + - @OutputDatabaseName + - N'.sys.all_columns AS o WHERE o.object_id = (OBJECT_ID(''' + - @ObjectFullName + - N''')) AND o.name = N''spid'') - /*Add spid column*/ - ALTER TABLE ' + - @ObjectFullName + - N' ADD spid smallint NULL;'; - - IF @Debug = 1 BEGIN PRINT @StringToExecute; END; - EXEC sys.sp_executesql - @StringToExecute; - - /* If the table doesn't have the new wait_resource column, add it. See Github #3101. */ - SET @StringToExecute = - N'IF NOT EXISTS (SELECT 1/0 FROM ' + - @OutputDatabaseName + - N'.sys.all_columns AS o WHERE o.object_id = (OBJECT_ID(''' + - @ObjectFullName + - N''')) AND o.name = N''wait_resource'') - /*Add wait_resource column*/ - ALTER TABLE ' + - @ObjectFullName + - N' ADD wait_resource nvarchar(MAX) NULL;'; - - IF @Debug = 1 BEGIN PRINT @StringToExecute; END; - EXEC sys.sp_executesql - @StringToExecute; - - /* If the table doesn't have the new client option column, add it. See Github #3101. */ - SET @StringToExecute = - N'IF NOT EXISTS (SELECT 1/0 FROM ' + - @OutputDatabaseName + - N'.sys.all_columns AS o WHERE o.object_id = (OBJECT_ID(''' + - @ObjectFullName + - N''')) AND o.name = N''client_option_1'') - /*Add wait_resource column*/ - ALTER TABLE ' + - @ObjectFullName + - N' ADD client_option_1 varchar(500) NULL;'; - - IF @Debug = 1 BEGIN PRINT @StringToExecute; END; - EXEC sys.sp_executesql - @StringToExecute; - - /* If the table doesn't have the new client option column, add it. See Github #3101. */ - SET @StringToExecute = - N'IF NOT EXISTS (SELECT 1/0 FROM ' + - @OutputDatabaseName + - N'.sys.all_columns AS o WHERE o.object_id = (OBJECT_ID(''' + - @ObjectFullName + - N''')) AND o.name = N''client_option_2'') - /*Add wait_resource column*/ - ALTER TABLE ' + - @ObjectFullName + - N' ADD client_option_2 varchar(500) NULL;'; - - IF @Debug = 1 BEGIN PRINT @StringToExecute; END; - EXEC sys.sp_executesql - @StringToExecute; - - /* If the table doesn't have the new lock mode column, add it. See Github #3101. */ - SET @StringToExecute = - N'IF NOT EXISTS (SELECT 1/0 FROM ' + - @OutputDatabaseName + - N'.sys.all_columns AS o WHERE o.object_id = (OBJECT_ID(''' + - @ObjectFullName + - N''')) AND o.name = N''lock_mode'') - /*Add wait_resource column*/ - ALTER TABLE ' + - @ObjectFullName + - N' ADD lock_mode nvarchar(256) NULL;'; - - IF @Debug = 1 BEGIN PRINT @StringToExecute; END; - EXEC sys.sp_executesql - @StringToExecute; - - /* If the table doesn't have the new status column, add it. See Github #3101. */ - SET @StringToExecute = - N'IF NOT EXISTS (SELECT 1/0 FROM ' + - @OutputDatabaseName + - N'.sys.all_columns AS o WHERE o.object_id = (OBJECT_ID(''' + - @ObjectFullName + - N''')) AND o.name = N''status'') - /*Add wait_resource column*/ - ALTER TABLE ' + - @ObjectFullName + - N' ADD status nvarchar(256) NULL;'; - - IF @Debug = 1 BEGIN PRINT @StringToExecute; END; - EXEC sys.sp_executesql - @StringToExecute; - END; - ELSE /* end if @r is not null. if it is null there is no table, create it from above execution */ - BEGIN - SELECT - @StringToExecute = - N'USE ' + - @OutputDatabaseName + - N'; - CREATE TABLE ' + - @OutputSchemaName + - N'.' + - @OutputTableName + - N' ( - ServerName nvarchar(256), - deadlock_type nvarchar(256), - event_date datetime, - database_name nvarchar(256), - spid smallint, - deadlock_group nvarchar(256), - query xml, - object_names xml, - isolation_level nvarchar(256), - owner_mode nvarchar(256), - waiter_mode nvarchar(256), - lock_mode nvarchar(256), - transaction_count bigint, - client_option_1 varchar(500), - client_option_2 varchar(500), - login_name nvarchar(256), - host_name nvarchar(256), - client_app nvarchar(1024), - wait_time bigint, - wait_resource nvarchar(max), - priority smallint, - log_used bigint, - last_tran_started datetime, - last_batch_started datetime, - last_batch_completed datetime, - transaction_name nvarchar(256), - status nvarchar(256), - owner_waiter_type nvarchar(256), - owner_activity nvarchar(256), - owner_waiter_activity nvarchar(256), - owner_merging nvarchar(256), - owner_spilling nvarchar(256), - owner_waiting_to_close nvarchar(256), - waiter_waiter_type nvarchar(256), - waiter_owner_activity nvarchar(256), - waiter_waiter_activity nvarchar(256), - waiter_merging nvarchar(256), - waiter_spilling nvarchar(256), - waiter_waiting_to_close nvarchar(256), - deadlock_graph xml - )'; - - IF @Debug = 1 BEGIN PRINT @StringToExecute; END; - EXEC sys.sp_executesql - @StringToExecute; - - /*table created.*/ - SELECT - @StringToExecute = - N'SELECT @r = o.name FROM ' + - @OutputDatabaseName + - N'.sys.objects AS o - WHERE o.type_desc = N''USER_TABLE'' - AND o.name = N''BlitzLockFindings''', - @StringToExecuteParams = - N'@r sysname OUTPUT'; - - IF @Debug = 1 BEGIN PRINT @StringToExecute; END; - EXEC sys.sp_executesql - @StringToExecute, - @StringToExecuteParams, - @r OUTPUT; - - IF (@r IS NULL) /*if table does not exist*/ - BEGIN - SELECT - @OutputTableFindings = - QUOTENAME(N'BlitzLockFindings'), - @StringToExecute = - N'USE ' + - @OutputDatabaseName + - N'; - CREATE TABLE ' + - @OutputSchemaName + - N'.' + - @OutputTableFindings + - N' ( - ServerName nvarchar(256), - check_id INT, - database_name nvarchar(256), - object_name nvarchar(1000), - finding_group nvarchar(100), - finding nvarchar(4000) - );'; - - IF @Debug = 1 BEGIN PRINT @StringToExecute; END; - EXEC sys.sp_executesql - @StringToExecute; - END; - END; - - /*create synonym for deadlockfindings.*/ - IF EXISTS - ( - SELECT - 1/0 - FROM sys.objects AS o - WHERE o.name = N'DeadlockFindings' - AND o.type_desc = N'SYNONYM' - ) - BEGIN - RAISERROR('Found synonym DeadlockFindings, dropping', 0, 1) WITH NOWAIT; - DROP SYNONYM DeadlockFindings; - END; - - RAISERROR('Creating synonym DeadlockFindings', 0, 1) WITH NOWAIT; - SET @StringToExecute = - N'CREATE SYNONYM DeadlockFindings FOR ' + - @OutputDatabaseName + - N'.' + - @OutputSchemaName + - N'.' + - @OutputTableFindings; - - IF @Debug = 1 BEGIN PRINT @StringToExecute; END; - EXEC sys.sp_executesql - @StringToExecute; - - /*create synonym for deadlock table.*/ - IF EXISTS - ( - SELECT - 1/0 - FROM sys.objects AS o - WHERE o.name = N'DeadLockTbl' - AND o.type_desc = N'SYNONYM' - ) - BEGIN - RAISERROR('Found synonym DeadLockTbl, dropping', 0, 1) WITH NOWAIT; - DROP SYNONYM DeadLockTbl; - END; - - RAISERROR('Creating synonym DeadLockTbl', 0, 1) WITH NOWAIT; - SET @StringToExecute = - N'CREATE SYNONYM DeadLockTbl FOR ' + - @OutputDatabaseName + - N'.' + - @OutputSchemaName + - N'.' + - @OutputTableName; - - IF @Debug = 1 BEGIN PRINT @StringToExecute; END; - EXEC sys.sp_executesql - @StringToExecute; - END; - END; - - /* WITH ROWCOUNT doesn't work on Amazon RDS - see: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2037 */ - IF @RDS = 0 - BEGIN; - BEGIN TRY; - RAISERROR('@RDS = 0, updating #t with high row and page counts', 0, 1) WITH NOWAIT; - UPDATE STATISTICS - #t - WITH - ROWCOUNT = 9223372036854775807, - PAGECOUNT = 9223372036854775807; - END TRY - BEGIN CATCH; - /* Misleading error returned, if run without permissions to update statistics the error returned is "Cannot find object". - Catching specific error, and returning message with better info. If any other error is returned, then throw as normal */ - IF (ERROR_NUMBER() = 1088) - BEGIN; - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Cannot run UPDATE STATISTICS on a #temp table without db_owner or sysadmin permissions', 0, 1) WITH NOWAIT; - END; - ELSE - BEGIN; - THROW; - END; - END CATCH; - END; - - /*If @TargetSessionType, we need to figure out if it's ring buffer or event file*/ - /*Azure has differently named views, so we need to separate. Thanks, Azure.*/ - - IF - ( - @Azure = 0 - AND @TargetSessionType IS NULL - ) - BEGIN - RAISERROR('@TargetSessionType is NULL, assigning for non-Azure instance', 0, 1) WITH NOWAIT; - - SELECT TOP (1) - @TargetSessionType = t.target_name - FROM sys.dm_xe_sessions AS s - JOIN sys.dm_xe_session_targets AS t - ON s.address = t.event_session_address - WHERE s.name = @EventSessionName - AND t.target_name IN (N'event_file', N'ring_buffer') - ORDER BY t.target_name - OPTION(RECOMPILE); - - RAISERROR('@TargetSessionType assigned as %s for non-Azure', 0, 1, @TargetSessionType) WITH NOWAIT; - END; - - IF - ( - @Azure = 1 - AND @TargetSessionType IS NULL - ) - BEGIN - RAISERROR('@TargetSessionType is NULL, assigning for Azure instance', 0, 1) WITH NOWAIT; - - SELECT TOP (1) - @TargetSessionType = t.target_name - FROM sys.dm_xe_database_sessions AS s - JOIN sys.dm_xe_database_session_targets AS t - ON s.address = t.event_session_address - WHERE s.name = @EventSessionName - AND t.target_name IN (N'event_file', N'ring_buffer') - ORDER BY t.target_name - OPTION(RECOMPILE); - - RAISERROR('@TargetSessionType assigned as %s for Azure', 0, 1, @TargetSessionType) WITH NOWAIT; - END; - - - /*The system health stuff gets handled different from user extended events.*/ - /*These next sections deal with user events, dependent on target.*/ - - /*If ring buffers*/ - IF - ( - @TargetSessionType LIKE N'ring%' - AND @EventSessionName NOT LIKE N'system_health%' - ) - BEGIN - IF @Azure = 0 - BEGIN - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('@TargetSessionType is ring_buffer, inserting XML for non-Azure at %s', 0, 1, @d) WITH NOWAIT; - - INSERT - #x WITH(TABLOCKX) - ( - x - ) - SELECT - x = TRY_CAST(t.target_data AS xml) - FROM sys.dm_xe_session_targets AS t - JOIN sys.dm_xe_sessions AS s - ON s.address = t.event_session_address - WHERE s.name = @EventSessionName - AND t.target_name = N'ring_buffer' - OPTION(RECOMPILE); - - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - END; - - IF @Azure = 1 - BEGIN - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('@TargetSessionType is ring_buffer, inserting XML for Azure at %s', 0, 1, @d) WITH NOWAIT; - - INSERT - #x WITH(TABLOCKX) - ( - x - ) - SELECT - x = TRY_CAST(t.target_data AS xml) - FROM sys.dm_xe_database_session_targets AS t - JOIN sys.dm_xe_database_sessions AS s - ON s.address = t.event_session_address - WHERE s.name = @EventSessionName - AND t.target_name = N'ring_buffer' - OPTION(RECOMPILE); - - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - END; - END; - - - /*If event file*/ - IF - ( - @TargetSessionType LIKE N'event%' - AND @EventSessionName NOT LIKE N'system_health%' - ) - BEGIN - IF @Azure = 0 - BEGIN - RAISERROR('@TargetSessionType is event_file, assigning XML for non-Azure', 0, 1) WITH NOWAIT; - - SELECT - @SessionId = t.event_session_id, - @TargetSessionId = t.target_id - FROM sys.server_event_session_targets AS t - JOIN sys.server_event_sessions AS s - ON s.event_session_id = t.event_session_id - WHERE t.name = @TargetSessionType - AND s.name = @EventSessionName - OPTION(RECOMPILE); - - /*We get the file name automatically, here*/ - RAISERROR('Assigning @FileName...', 0, 1) WITH NOWAIT; - SELECT - @FileName = - CASE - WHEN f.file_name LIKE N'%.xel' - THEN REPLACE(f.file_name, N'.xel', N'*.xel') - ELSE f.file_name + N'*.xel' - END - FROM - ( - SELECT - file_name = - CONVERT(nvarchar(4000), f.value) - FROM sys.server_event_session_fields AS f - WHERE f.event_session_id = @SessionId - AND f.object_id = @TargetSessionId - AND f.name = N'filename' - ) AS f - OPTION(RECOMPILE); - END; - - IF @Azure = 1 - BEGIN - RAISERROR('@TargetSessionType is event_file, assigning XML for Azure', 0, 1) WITH NOWAIT; - SELECT - @SessionId = - t.event_session_address, - @TargetSessionId = - t.target_name - FROM sys.dm_xe_database_session_targets t - JOIN sys.dm_xe_database_sessions s - ON s.address = t.event_session_address - WHERE t.target_name = @TargetSessionType - AND s.name = @EventSessionName - OPTION(RECOMPILE); - - /*We get the file name automatically, here*/ - RAISERROR('Assigning @FileName...', 0, 1) WITH NOWAIT; - SELECT - @FileName = - CASE - WHEN f.file_name LIKE N'%.xel' - THEN REPLACE(f.file_name, N'.xel', N'*.xel') - ELSE f.file_name + N'*.xel' - END - FROM - ( - SELECT - file_name = - CONVERT(nvarchar(4000), f.value) - FROM sys.server_event_session_fields AS f - WHERE f.event_session_id = @SessionId - AND f.object_id = @TargetSessionId - AND f.name = N'filename' - ) AS f - OPTION(RECOMPILE); - END; - - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Reading for event_file %s', 0, 1, @FileName) WITH NOWAIT; - - INSERT - #x WITH(TABLOCKX) - ( - x - ) - SELECT - x = TRY_CAST(f.event_data AS xml) - FROM sys.fn_xe_file_target_read_file(@FileName, NULL, NULL, NULL) AS f - LEFT JOIN #t AS t - ON 1 = 1 - OPTION(RECOMPILE); - - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - END; - - /*The XML is parsed differently if it comes from the event file or ring buffer*/ - - /*If ring buffers*/ - IF - ( - @TargetSessionType LIKE N'ring%' - AND @EventSessionName NOT LIKE N'system_health%' - ) - BEGIN - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Inserting to #deadlock_data for ring buffer data', 0, 1) WITH NOWAIT; - - INSERT - #deadlock_data WITH(TABLOCKX) - ( - deadlock_xml - ) - SELECT - deadlock_xml = - e.x.query(N'.') - FROM #x AS x - LEFT JOIN #t AS t - ON 1 = 1 - CROSS APPLY x.x.nodes('/RingBufferTarget/event') AS e(x) - WHERE - ( - e.x.exist('@name[ .= "xml_deadlock_report"]') = 1 - OR e.x.exist('@name[ .= "database_xml_deadlock_report"]') = 1 - OR e.x.exist('@name[ .= "xml_deadlock_report_filtered"]') = 1 - ) - AND e.x.exist('@timestamp[. >= sql:variable("@StartDate") and .< sql:variable("@EndDate")]') = 1 - OPTION(RECOMPILE); - - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - END; - - /*If event file*/ - IF - ( - @TargetSessionType LIKE N'event_file%' - AND @EventSessionName NOT LIKE N'system_health%' - ) - BEGIN - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Inserting to #deadlock_data for event file data', 0, 1) WITH NOWAIT; - - IF @Debug = 1 BEGIN SET STATISTICS XML ON; END; - - INSERT - #deadlock_data WITH(TABLOCKX) - ( - deadlock_xml - ) - SELECT - deadlock_xml = - e.x.query('.') - FROM #x AS x - LEFT JOIN #t AS t - ON 1 = 1 - CROSS APPLY x.x.nodes('/event') AS e(x) - WHERE - ( - e.x.exist('@name[ .= "xml_deadlock_report"]') = 1 - OR e.x.exist('@name[ .= "database_xml_deadlock_report"]') = 1 - OR e.x.exist('@name[ .= "xml_deadlock_report_filtered"]') = 1 - ) - AND e.x.exist('@timestamp[. >= sql:variable("@StartDate") and .< sql:variable("@EndDate")]') = 1 - OPTION(RECOMPILE); - - IF @Debug = 1 BEGIN SET STATISTICS XML OFF; END; - - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - END; - - /*This section deals with event file*/ - IF - ( - @TargetSessionType LIKE N'event%' - AND @EventSessionName LIKE N'system_health%' - ) - BEGIN - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Grab the initial set of xml to parse at %s', 0, 1, @d) WITH NOWAIT; - - IF @Debug = 1 BEGIN SET STATISTICS XML ON; END; - - SELECT - xml.deadlock_xml - INTO #xml - FROM - ( - SELECT - deadlock_xml = - TRY_CAST(fx.event_data AS xml) - FROM sys.fn_xe_file_target_read_file(N'system_health*.xel', NULL, NULL, NULL) AS fx - LEFT JOIN #t AS t - ON 1 = 1 - WHERE fx.object_name = N'xml_deadlock_report' - ) AS xml - CROSS APPLY xml.deadlock_xml.nodes('/event') AS e(x) - WHERE 1 = 1 - AND e.x.exist('@timestamp[. >= sql:variable("@StartDate") and .< sql:variable("@EndDate")]') = 1 - OPTION(RECOMPILE); - - INSERT - #deadlock_data WITH(TABLOCKX) - SELECT - deadlock_xml = - xml.deadlock_xml - FROM #xml AS xml - LEFT JOIN #t AS t - ON 1 = 1 - WHERE xml.deadlock_xml IS NOT NULL - OPTION(RECOMPILE); - - IF @Debug = 1 BEGIN SET STATISTICS XML OFF; END; - - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - END; - - /*Parse process and input buffer xml*/ - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Initial Parse process and input buffer xml %s', 0, 1, @d) WITH NOWAIT; - - SELECT - d1.deadlock_xml, - event_date = d1.deadlock_xml.value('(event/@timestamp)[1]', 'datetime2'), - victim_id = d1.deadlock_xml.value('(//deadlock/victim-list/victimProcess/@id)[1]', 'nvarchar(256)'), - is_parallel = d1.deadlock_xml.exist('//deadlock/resource-list/exchangeEvent'), - is_parallel_batch = d1.deadlock_xml.exist('//deadlock/resource-list/SyncPoint'), - deadlock_graph = d1.deadlock_xml.query('/event/data/value/deadlock') - INTO #dd - FROM #deadlock_data AS d1 - LEFT JOIN #t AS t - ON 1 = 1 - OPTION(RECOMPILE); - - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Final Parse process and input buffer xml %s', 0, 1, @d) WITH NOWAIT; - - SELECT - q.event_date, - q.victim_id, - is_parallel = - CONVERT(bit, q.is_parallel), - q.deadlock_graph, - q.id, - q.spid, - q.database_id, - database_name = - ISNULL - ( - DB_NAME(q.database_id), - N'UNKNOWN' - ), - q.current_database_name, - q.priority, - q.log_used, - q.wait_resource, - q.wait_time, - q.transaction_name, - q.last_tran_started, - q.last_batch_started, - q.last_batch_completed, - q.lock_mode, - q.status, - q.transaction_count, - q.client_app, - q.host_name, - q.login_name, - q.isolation_level, - client_option_1 = - SUBSTRING - ( - CASE WHEN q.clientoption1 & 1 = 1 THEN ', DISABLE_DEF_CNST_CHECK' ELSE '' END + - CASE WHEN q.clientoption1 & 2 = 2 THEN ', IMPLICIT_TRANSACTIONS' ELSE '' END + - CASE WHEN q.clientoption1 & 4 = 4 THEN ', CURSOR_CLOSE_ON_COMMIT' ELSE '' END + - CASE WHEN q.clientoption1 & 8 = 8 THEN ', ANSI_WARNINGS' ELSE '' END + - CASE WHEN q.clientoption1 & 16 = 16 THEN ', ANSI_PADDING' ELSE '' END + - CASE WHEN q.clientoption1 & 32 = 32 THEN ', ANSI_NULLS' ELSE '' END + - CASE WHEN q.clientoption1 & 64 = 64 THEN ', ARITHABORT' ELSE '' END + - CASE WHEN q.clientoption1 & 128 = 128 THEN ', ARITHIGNORE' ELSE '' END + - CASE WHEN q.clientoption1 & 256 = 256 THEN ', QUOTED_IDENTIFIER' ELSE '' END + - CASE WHEN q.clientoption1 & 512 = 512 THEN ', NOCOUNT' ELSE '' END + - CASE WHEN q.clientoption1 & 1024 = 1024 THEN ', ANSI_NULL_DFLT_ON' ELSE '' END + - CASE WHEN q.clientoption1 & 2048 = 2048 THEN ', ANSI_NULL_DFLT_OFF' ELSE '' END + - CASE WHEN q.clientoption1 & 4096 = 4096 THEN ', CONCAT_NULL_YIELDS_NULL' ELSE '' END + - CASE WHEN q.clientoption1 & 8192 = 8192 THEN ', NUMERIC_ROUNDABORT' ELSE '' END + - CASE WHEN q.clientoption1 & 16384 = 16384 THEN ', XACT_ABORT' ELSE '' END, - 3, - 500 - ), - client_option_2 = - SUBSTRING - ( - CASE WHEN q.clientoption2 & 1024 = 1024 THEN ', DB CHAINING' ELSE '' END + - CASE WHEN q.clientoption2 & 2048 = 2048 THEN ', NUMERIC ROUNDABORT' ELSE '' END + - CASE WHEN q.clientoption2 & 4096 = 4096 THEN ', ARITHABORT' ELSE '' END + - CASE WHEN q.clientoption2 & 8192 = 8192 THEN ', ANSI PADDING' ELSE '' END + - CASE WHEN q.clientoption2 & 16384 = 16384 THEN ', ANSI NULL DEFAULT' ELSE '' END + - CASE WHEN q.clientoption2 & 65536 = 65536 THEN ', CONCAT NULL YIELDS NULL' ELSE '' END + - CASE WHEN q.clientoption2 & 131072 = 131072 THEN ', RECURSIVE TRIGGERS' ELSE '' END + - CASE WHEN q.clientoption2 & 1048576 = 1048576 THEN ', DEFAULT TO LOCAL CURSOR' ELSE '' END + - CASE WHEN q.clientoption2 & 8388608 = 8388608 THEN ', QUOTED IDENTIFIER' ELSE '' END + - CASE WHEN q.clientoption2 & 16777216 = 16777216 THEN ', AUTO CREATE STATISTICS' ELSE '' END + - CASE WHEN q.clientoption2 & 33554432 = 33554432 THEN ', CURSOR CLOSE ON COMMIT' ELSE '' END + - CASE WHEN q.clientoption2 & 67108864 = 67108864 THEN ', ANSI NULLS' ELSE '' END + - CASE WHEN q.clientoption2 & 268435456 = 268435456 THEN ', ANSI WARNINGS' ELSE '' END + - CASE WHEN q.clientoption2 & 536870912 = 536870912 THEN ', FULL TEXT ENABLED' ELSE '' END + - CASE WHEN q.clientoption2 & 1073741824 = 1073741824 THEN ', AUTO UPDATE STATISTICS' ELSE '' END + - CASE WHEN q.clientoption2 & 1469283328 = 1469283328 THEN ', ALL SETTABLE OPTIONS' ELSE '' END, - 3, - 500 - ), - q.process_xml - INTO #deadlock_process - FROM - ( - SELECT - dd.deadlock_xml, - event_date = - DATEADD - ( - MINUTE, - DATEDIFF - ( - MINUTE, - GETUTCDATE(), - SYSDATETIME() - ), - dd.event_date - ), - dd.victim_id, - is_parallel = - CONVERT(tinyint, dd.is_parallel) + - CONVERT(tinyint, dd.is_parallel_batch), - dd.deadlock_graph, - id = ca.dp.value('@id', 'nvarchar(256)'), - spid = ca.dp.value('@spid', 'smallint'), - database_id = ca.dp.value('@currentdb', 'bigint'), - current_database_name = ca.dp.value('@currentdbname', 'nvarchar(256)'), - priority = ca.dp.value('@priority', 'smallint'), - log_used = ca.dp.value('@logused', 'bigint'), - wait_resource = ca.dp.value('@waitresource', 'nvarchar(256)'), - wait_time = ca.dp.value('@waittime', 'bigint'), - transaction_name = ca.dp.value('@transactionname', 'nvarchar(256)'), - last_tran_started = ca.dp.value('@lasttranstarted', 'datetime'), - last_batch_started = ca.dp.value('@lastbatchstarted', 'datetime'), - last_batch_completed = ca.dp.value('@lastbatchcompleted', 'datetime'), - lock_mode = ca.dp.value('@lockMode', 'nvarchar(256)'), - status = ca.dp.value('@status', 'nvarchar(256)'), - transaction_count = ca.dp.value('@trancount', 'bigint'), - client_app = ca.dp.value('@clientapp', 'nvarchar(1024)'), - host_name = ca.dp.value('@hostname', 'nvarchar(256)'), - login_name = ca.dp.value('@loginname', 'nvarchar(256)'), - isolation_level = ca.dp.value('@isolationlevel', 'nvarchar(256)'), - clientoption1 = ca.dp.value('@clientoption1', 'bigint'), - clientoption2 = ca.dp.value('@clientoption2', 'bigint'), - process_xml = ISNULL(ca.dp.query(N'.'), N'') - FROM #dd AS dd - CROSS APPLY dd.deadlock_xml.nodes('//deadlock/process-list/process') AS ca(dp) - WHERE (ca.dp.exist('@currentdb[. = sql:variable("@DatabaseId")]') = 1 OR @DatabaseName IS NULL) - AND (ca.dp.exist('@clientapp[. = sql:variable("@AppName")]') = 1 OR @AppName IS NULL) - AND (ca.dp.exist('@hostname[. = sql:variable("@HostName")]') = 1 OR @HostName IS NULL) - AND (ca.dp.exist('@loginname[. = sql:variable("@LoginName")]') = 1 OR @LoginName IS NULL) - ) AS q - LEFT JOIN #t AS t - ON 1 = 1 - OPTION(RECOMPILE); - - - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - /*Parse execution stack xml*/ - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Parse execution stack xml %s', 0, 1, @d) WITH NOWAIT; - - SELECT DISTINCT - dp.id, - dp.event_date, - proc_name = ca.dp.value('@procname', 'nvarchar(1024)'), - sql_handle = ca.dp.value('@sqlhandle', 'nvarchar(131)') - INTO #deadlock_stack - FROM #deadlock_process AS dp - CROSS APPLY dp.process_xml.nodes('//executionStack/frame') AS ca(dp) - WHERE (ca.dp.exist('@procname[. = sql:variable("@StoredProcName")]') = 1 OR @StoredProcName IS NULL) - AND ca.dp.exist('@sqlhandle[ .= "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"]') = 0 - OPTION(RECOMPILE); - - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - /*Grab the full resource list*/ - SET @d = CONVERT(varchar(40), GETDATE(), 109); - - RAISERROR('Grab the full resource list %s', 0, 1, @d) WITH NOWAIT; - - SELECT - event_date = - DATEADD - ( - MINUTE, - DATEDIFF - ( - MINUTE, - GETUTCDATE(), - SYSDATETIME() - ), - dr.event_date - ), - dr.victim_id, - dr.resource_xml - INTO - #deadlock_resource - FROM - ( - SELECT - event_date = dd.deadlock_xml.value('(event/@timestamp)[1]', 'datetime2'), - victim_id = dd.deadlock_xml.value('(//deadlock/victim-list/victimProcess/@id)[1]', 'nvarchar(256)'), - resource_xml = ISNULL(ca.dp.query(N'.'), N'') - FROM #deadlock_data AS dd - CROSS APPLY dd.deadlock_xml.nodes('//deadlock/resource-list') AS ca(dp) - ) AS dr - OPTION(RECOMPILE); - - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - /*Parse object locks*/ - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Parse object locks %s', 0, 1, @d) WITH NOWAIT; - - SELECT DISTINCT - ca.event_date, - ca.database_id, - database_name = - ISNULL - ( - DB_NAME(ca.database_id), - N'UNKNOWN' - ), - object_name = - REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE( - REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE( - REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE( - ca.object_name COLLATE Latin1_General_BIN2, - NCHAR(31), N'?'), NCHAR(30), N'?'), NCHAR(29), N'?'), NCHAR(28), N'?'), NCHAR(27), N'?'), - NCHAR(26), N'?'), NCHAR(25), N'?'), NCHAR(24), N'?'), NCHAR(23), N'?'), NCHAR(22), N'?'), - NCHAR(21), N'?'), NCHAR(20), N'?'), NCHAR(19), N'?'), NCHAR(18), N'?'), NCHAR(17), N'?'), - NCHAR(16), N'?'), NCHAR(15), N'?'), NCHAR(14), N'?'), NCHAR(12), N'?'), NCHAR(11), N'?'), - NCHAR(8), N'?'), NCHAR(7), N'?'), NCHAR(6), N'?'), NCHAR(5), N'?'), NCHAR(4), N'?'), - NCHAR(3), N'?'), NCHAR(2), N'?'), NCHAR(1), N'?'), NCHAR(0), N'?'), - ca.lock_mode, - ca.index_name, - ca.associatedObjectId, - waiter_id = w.l.value('@id', 'nvarchar(256)'), - waiter_mode = w.l.value('@mode', 'nvarchar(256)'), - owner_id = o.l.value('@id', 'nvarchar(256)'), - owner_mode = o.l.value('@mode', 'nvarchar(256)'), - lock_type = CAST(N'OBJECT' AS nvarchar(100)) - INTO #deadlock_owner_waiter - FROM - ( - SELECT - dr.event_date, - database_id = ca.dr.value('@dbid', 'bigint'), - object_name = ca.dr.value('@objectname', 'nvarchar(1024)'), - lock_mode = ca.dr.value('@mode', 'nvarchar(256)'), - index_name = ca.dr.value('@indexname', 'nvarchar(256)'), - associatedObjectId = ca.dr.value('@associatedObjectId', 'bigint'), - dr = ca.dr.query('.') - FROM #deadlock_resource AS dr - CROSS APPLY dr.resource_xml.nodes('//resource-list/objectlock') AS ca(dr) - WHERE (ca.dr.exist('@objectname[. = sql:variable("@ObjectName")]') = 1 OR @ObjectName IS NULL) - ) AS ca - CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l) - CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l) - OPTION(RECOMPILE); - - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - /*Parse page locks*/ - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Parse page locks %s', 0, 1, @d) WITH NOWAIT; - - INSERT - #deadlock_owner_waiter WITH(TABLOCKX) - SELECT DISTINCT - ca.event_date, - ca.database_id, - database_name = - ISNULL - ( - DB_NAME(ca.database_id), - N'UNKNOWN' - ), - ca.object_name, - ca.lock_mode, - ca.index_name, - ca.associatedObjectId, - waiter_id = w.l.value('@id', 'nvarchar(256)'), - waiter_mode = w.l.value('@mode', 'nvarchar(256)'), - owner_id = o.l.value('@id', 'nvarchar(256)'), - owner_mode = o.l.value('@mode', 'nvarchar(256)'), - lock_type = N'PAGE' - FROM - ( - SELECT - dr.event_date, - database_id = ca.dr.value('@dbid', 'bigint'), - object_name = ca.dr.value('@objectname', 'nvarchar(1024)'), - lock_mode = ca.dr.value('@mode', 'nvarchar(256)'), - index_name = ca.dr.value('@indexname', 'nvarchar(256)'), - associatedObjectId = ca.dr.value('@associatedObjectId', 'bigint'), - dr = ca.dr.query('.') - FROM #deadlock_resource AS dr - CROSS APPLY dr.resource_xml.nodes('//resource-list/pagelock') AS ca(dr) - WHERE (ca.dr.exist('@objectname[. = sql:variable("@ObjectName")]') = 1 OR @ObjectName IS NULL) - ) AS ca - CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l) - CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l) - OPTION(RECOMPILE); - - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - /*Parse key locks*/ - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Parse key locks %s', 0, 1, @d) WITH NOWAIT; - - INSERT - #deadlock_owner_waiter WITH(TABLOCKX) - SELECT DISTINCT - ca.event_date, - ca.database_id, - database_name = - ISNULL - ( - DB_NAME(ca.database_id), - N'UNKNOWN' - ), - ca.object_name, - ca.lock_mode, - ca.index_name, - ca.associatedObjectId, - waiter_id = w.l.value('@id', 'nvarchar(256)'), - waiter_mode = w.l.value('@mode', 'nvarchar(256)'), - owner_id = o.l.value('@id', 'nvarchar(256)'), - owner_mode = o.l.value('@mode', 'nvarchar(256)'), - lock_type = N'KEY' - FROM - ( - SELECT - dr.event_date, - database_id = ca.dr.value('@dbid', 'bigint'), - object_name = ca.dr.value('@objectname', 'nvarchar(1024)'), - lock_mode = ca.dr.value('@mode', 'nvarchar(256)'), - index_name = ca.dr.value('@indexname', 'nvarchar(256)'), - associatedObjectId = ca.dr.value('@associatedObjectId', 'bigint'), - dr = ca.dr.query('.') - FROM #deadlock_resource AS dr - CROSS APPLY dr.resource_xml.nodes('//resource-list/keylock') AS ca(dr) - WHERE (ca.dr.exist('@objectname[. = sql:variable("@ObjectName")]') = 1 OR @ObjectName IS NULL) - ) AS ca - CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l) - CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l) - OPTION(RECOMPILE); - - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - /*Parse RID locks*/ - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Parse RID locks %s', 0, 1, @d) WITH NOWAIT; - - INSERT - #deadlock_owner_waiter WITH(TABLOCKX) - SELECT DISTINCT - ca.event_date, - ca.database_id, - database_name = - ISNULL - ( - DB_NAME(ca.database_id), - N'UNKNOWN' - ), - ca.object_name, - ca.lock_mode, - ca.index_name, - ca.associatedObjectId, - waiter_id = w.l.value('@id', 'nvarchar(256)'), - waiter_mode = w.l.value('@mode', 'nvarchar(256)'), - owner_id = o.l.value('@id', 'nvarchar(256)'), - owner_mode = o.l.value('@mode', 'nvarchar(256)'), - lock_type = N'RID' - FROM - ( - SELECT - dr.event_date, - database_id = ca.dr.value('@dbid', 'bigint'), - object_name = ca.dr.value('@objectname', 'nvarchar(1024)'), - lock_mode = ca.dr.value('@mode', 'nvarchar(256)'), - index_name = ca.dr.value('@indexname', 'nvarchar(256)'), - associatedObjectId = ca.dr.value('@associatedObjectId', 'bigint'), - dr = ca.dr.query('.') - FROM #deadlock_resource AS dr - CROSS APPLY dr.resource_xml.nodes('//resource-list/ridlock') AS ca(dr) - WHERE (ca.dr.exist('@objectname[. = sql:variable("@ObjectName")]') = 1 OR @ObjectName IS NULL) - ) AS ca - CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l) - CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l) - OPTION(RECOMPILE); - - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - /*Parse row group locks*/ - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Parse row group locks %s', 0, 1, @d) WITH NOWAIT; - - INSERT - #deadlock_owner_waiter WITH(TABLOCKX) - SELECT DISTINCT - ca.event_date, - ca.database_id, - database_name = - ISNULL - ( - DB_NAME(ca.database_id), - N'UNKNOWN' - ), - ca.object_name, - ca.lock_mode, - ca.index_name, - ca.associatedObjectId, - waiter_id = w.l.value('@id', 'nvarchar(256)'), - waiter_mode = w.l.value('@mode', 'nvarchar(256)'), - owner_id = o.l.value('@id', 'nvarchar(256)'), - owner_mode = o.l.value('@mode', 'nvarchar(256)'), - lock_type = N'ROWGROUP' - FROM - ( - SELECT - dr.event_date, - database_id = ca.dr.value('@dbid', 'bigint'), - object_name = ca.dr.value('@objectname', 'nvarchar(1024)'), - lock_mode = ca.dr.value('@mode', 'nvarchar(256)'), - index_name = ca.dr.value('@indexname', 'nvarchar(256)'), - associatedObjectId = ca.dr.value('@associatedObjectId', 'bigint'), - dr = ca.dr.query('.') - FROM #deadlock_resource AS dr - CROSS APPLY dr.resource_xml.nodes('//resource-list/rowgrouplock') AS ca(dr) - WHERE (ca.dr.exist('@objectname[. = sql:variable("@ObjectName")]') = 1 OR @ObjectName IS NULL) - ) AS ca - CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l) - CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l) - OPTION(RECOMPILE); - - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Fixing heaps in #deadlock_owner_waiter %s', 0, 1, @d) WITH NOWAIT; - - UPDATE - d - SET - d.index_name = - d.object_name + N'.HEAP' - FROM #deadlock_owner_waiter AS d - WHERE d.lock_type IN - ( - N'HEAP', - N'RID' - ) - OPTION(RECOMPILE); - - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - /*Parse parallel deadlocks*/ - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Parse parallel deadlocks %s', 0, 1, @d) WITH NOWAIT; - - SELECT DISTINCT - ca.id, - ca.event_date, - ca.wait_type, - ca.node_id, - ca.waiter_type, - ca.owner_activity, - ca.waiter_activity, - ca.merging, - ca.spilling, - ca.waiting_to_close, - waiter_id = w.l.value('@id', 'nvarchar(256)'), - owner_id = o.l.value('@id', 'nvarchar(256)') - INTO #deadlock_resource_parallel - FROM - ( - SELECT - dr.event_date, - id = ca.dr.value('@id', 'nvarchar(256)'), - wait_type = ca.dr.value('@WaitType', 'nvarchar(256)'), - node_id = ca.dr.value('@nodeId', 'bigint'), - /* These columns are in 2017 CU5+ ONLY */ - waiter_type = ca.dr.value('@waiterType', 'nvarchar(256)'), - owner_activity = ca.dr.value('@ownerActivity', 'nvarchar(256)'), - waiter_activity = ca.dr.value('@waiterActivity', 'nvarchar(256)'), - merging = ca.dr.value('@merging', 'nvarchar(256)'), - spilling = ca.dr.value('@spilling', 'nvarchar(256)'), - waiting_to_close = ca.dr.value('@waitingToClose', 'nvarchar(256)'), - /* These columns are in 2017 CU5+ ONLY */ - dr = ca.dr.query('.') - FROM #deadlock_resource AS dr - CROSS APPLY dr.resource_xml.nodes('//resource-list/exchangeEvent') AS ca(dr) - ) AS ca - CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l) - CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l) - OPTION(RECOMPILE); - - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - /*Get rid of parallel noise*/ - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Get rid of parallel noise %s', 0, 1, @d) WITH NOWAIT; - - WITH - c AS - ( - SELECT - *, - rn = - ROW_NUMBER() OVER - ( - PARTITION BY - drp.owner_id, - drp.waiter_id - ORDER BY - drp.event_date - ) - FROM #deadlock_resource_parallel AS drp - ) - DELETE - FROM c - WHERE c.rn > 1 - OPTION(RECOMPILE); - - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - /*Get rid of nonsense*/ - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Get rid of nonsense %s', 0, 1, @d) WITH NOWAIT; - - DELETE dow - FROM #deadlock_owner_waiter AS dow - WHERE dow.owner_id = dow.waiter_id - OPTION(RECOMPILE); - - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - /*Add some nonsense*/ - ALTER TABLE - #deadlock_process - ADD - waiter_mode nvarchar(256), - owner_mode nvarchar(256), - is_victim AS - CONVERT - ( - bit, - CASE - WHEN id = victim_id - THEN 1 - ELSE 0 - END - ) PERSISTED; - - /*Update some nonsense*/ - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Update some nonsense part 1 %s', 0, 1, @d) WITH NOWAIT; - - UPDATE - dp - SET - dp.owner_mode = dow.owner_mode - FROM #deadlock_process AS dp - JOIN #deadlock_owner_waiter AS dow - ON dp.id = dow.owner_id - AND dp.event_date = dow.event_date - WHERE dp.is_victim = 0 - OPTION(RECOMPILE); - - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Update some nonsense part 2 %s', 0, 1, @d) WITH NOWAIT; - - UPDATE - dp - SET - dp.waiter_mode = dow.waiter_mode - FROM #deadlock_process AS dp - JOIN #deadlock_owner_waiter AS dow - ON dp.victim_id = dow.waiter_id - AND dp.event_date = dow.event_date - WHERE dp.is_victim = 1 - OPTION(RECOMPILE); - - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - /*Get Agent Job and Step names*/ - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Get Agent Job and Step names %s', 0, 1, @d) WITH NOWAIT; - - SELECT - x.event_date, - x.victim_id, - x.id, - x.database_id, - x.client_app, - x.job_id, - x.step_id, - job_id_guid = - CONVERT - ( - uniqueidentifier, - TRY_CAST - ( - N'' - AS xml - ).value('xs:hexBinary(substring(sql:column("x.job_id"), 0))', 'binary(16)') - ) - INTO #agent_job - FROM - ( - SELECT - dp.event_date, - dp.victim_id, - dp.id, - dp.database_id, - dp.client_app, - job_id = - SUBSTRING - ( - dp.client_app, - CHARINDEX(N'0x', dp.client_app) + LEN(N'0x'), - 32 - ), - step_id = - CASE - WHEN CHARINDEX(N': Step ', dp.client_app) > 0 - AND CHARINDEX(N')', dp.client_app, CHARINDEX(N': Step ', dp.client_app)) > 0 - THEN - SUBSTRING - ( - dp.client_app, - CHARINDEX(N': Step ', dp.client_app) + LEN(N': Step '), - CHARINDEX(N')', dp.client_app, CHARINDEX(N': Step ', dp.client_app)) - - (CHARINDEX(N': Step ', dp.client_app) + LEN(N': Step ')) - ) - ELSE dp.client_app - END - FROM #deadlock_process AS dp - WHERE dp.client_app LIKE N'SQLAgent - %' - AND dp.client_app <> N'SQLAgent - Initial Boot Probe' - ) AS x - OPTION(RECOMPILE); - - ALTER TABLE - #agent_job - ADD - job_name nvarchar(256), - step_name nvarchar(256); - - IF - ( - @Azure = 0 - AND @RDS = 0 - ) - BEGIN - SET @StringToExecute = - N' - UPDATE - aj - SET - aj.job_name = j.name, - aj.step_name = s.step_name - FROM msdb.dbo.sysjobs AS j - JOIN msdb.dbo.sysjobsteps AS s - ON j.job_id = s.job_id - JOIN #agent_job AS aj - ON aj.job_id_guid = j.job_id - AND aj.step_id = s.step_id - OPTION(RECOMPILE); - '; - - IF @Debug = 1 BEGIN PRINT @StringToExecute; END; - EXEC sys.sp_executesql - @StringToExecute; - - END; - - UPDATE - dp - SET - dp.client_app = - CASE - WHEN dp.client_app LIKE N'SQLAgent - %' - THEN N'SQLAgent - Job: ' + - aj.job_name + - N' Step: ' + - aj.step_name - ELSE dp.client_app - END - FROM #deadlock_process AS dp - JOIN #agent_job AS aj - ON dp.event_date = aj.event_date - AND dp.victim_id = aj.victim_id - AND dp.id = aj.id - OPTION(RECOMPILE); - - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - /*Get each and every table of all databases*/ - IF @Azure = 0 - BEGIN - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Inserting to @sysAssObjId %s', 0, 1, @d) WITH NOWAIT; - - INSERT INTO - @sysAssObjId - ( - database_id, - partition_id, - schema_name, - table_name - ) - EXECUTE sys.sp_MSforeachdb - N' - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - - USE [?]; - - IF EXISTS - ( - SELECT - 1/0 - FROM #deadlock_process AS dp - WHERE dp.database_id = DB_ID() - ) - BEGIN - SELECT - database_id = - DB_ID(), - p.partition_id, - schema_name = - s.name, - table_name = - t.name - FROM sys.partitions p - JOIN sys.tables t - ON t.object_id = p.object_id - JOIN sys.schemas s - ON s.schema_id = t.schema_id - WHERE s.name IS NOT NULL - AND t.name IS NOT NULL - OPTION(RECOMPILE); - END; - '; - - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - END; - - IF @Azure = 1 - BEGIN - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Inserting to @sysAssObjId at %s', 0, 1, @d) WITH NOWAIT; - - INSERT INTO - @sysAssObjId - ( - database_id, - partition_id, - schema_name, - table_name - ) - SELECT - database_id = - DB_ID(), - p.partition_id, - schema_name = - s.name, - table_name = - t.name - FROM sys.partitions p - JOIN sys.tables t - ON t.object_id = p.object_id - JOIN sys.schemas s - ON s.schema_id = t.schema_id - WHERE s.name IS NOT NULL - AND t.name IS NOT NULL - OPTION(RECOMPILE); - - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - END; - - /*Begin checks based on parsed values*/ - - /* - First, revert these back since we already converted the event data to local time, - and searches will break if we use the times converted over to UTC for the event data - */ - SELECT - @StartDate = @StartDateOriginal, - @EndDate = @EndDateOriginal; - - /*Check 1 is deadlocks by database*/ - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Check 1 database deadlocks %s', 0, 1, @d) WITH NOWAIT; - - INSERT - #deadlock_findings WITH(TABLOCKX) - ( - check_id, - database_name, - object_name, - finding_group, - finding, - sort_order - ) - SELECT - check_id = 1, - dp.database_name, - object_name = N'-', - finding_group = N'Total Database Deadlocks', - finding = - N'This database had ' + - CONVERT - ( - nvarchar(20), - COUNT_BIG(DISTINCT dp.event_date) - ) + - N' deadlocks.', - sort_order = - ROW_NUMBER() - OVER (ORDER BY COUNT_BIG(DISTINCT dp.event_date) DESC) - FROM #deadlock_process AS dp - WHERE 1 = 1 - AND (dp.database_name = @DatabaseName OR @DatabaseName IS NULL) - AND (dp.event_date >= @StartDate OR @StartDate IS NULL) - AND (dp.event_date < @EndDate OR @EndDate IS NULL) - AND (dp.client_app = @AppName OR @AppName IS NULL) - AND (dp.host_name = @HostName OR @HostName IS NULL) - AND (dp.login_name = @LoginName OR @LoginName IS NULL) - GROUP BY dp.database_name - OPTION(RECOMPILE); - - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - /*Check 2 is deadlocks with selects*/ - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Check 2 select deadlocks %s', 0, 1, @d) WITH NOWAIT; - - INSERT - #deadlock_findings WITH(TABLOCKX) - ( - check_id, - database_name, - object_name, - finding_group, - finding, - sort_order - ) - SELECT - check_id = 2, - dow.database_name, - object_name = - CASE - WHEN EXISTS - ( - SELECT - 1/0 - FROM sys.databases AS d - WHERE d.name COLLATE DATABASE_DEFAULT = dow.database_name COLLATE DATABASE_DEFAULT - AND d.is_read_committed_snapshot_on = 1 - ) - THEN N'You already enabled RCSI, but...' - ELSE N'You Might Need RCSI' - END, - finding_group = N'Total Deadlocks Involving Selects', - finding = - N'There have been ' + - CONVERT - ( - nvarchar(20), - COUNT_BIG(DISTINCT dow.event_date) - ) + - N' deadlock(s) between read queries and modification queries.', - sort_order = - ROW_NUMBER() - OVER (ORDER BY COUNT_BIG(DISTINCT dow.event_date) DESC) - FROM #deadlock_owner_waiter AS dow - WHERE 1 = 1 - AND dow.lock_mode IN - ( - N'S', - N'IS' - ) - OR dow.owner_mode IN - ( - N'S', - N'IS' - ) - OR dow.waiter_mode IN - ( - N'S', - N'IS' - ) - AND (dow.database_id = @DatabaseId OR @DatabaseName IS NULL) - AND (dow.event_date >= @StartDate OR @StartDate IS NULL) - AND (dow.event_date < @EndDate OR @EndDate IS NULL) - AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) - GROUP BY - dow.database_name - OPTION(RECOMPILE); - - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - /*Check 3 is deadlocks by object*/ - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Check 3 object deadlocks %s', 0, 1, @d) WITH NOWAIT; - - INSERT - #deadlock_findings WITH(TABLOCKX) - ( - check_id, - database_name, - object_name, - finding_group, - finding, - sort_order - ) - SELECT - check_id = 3, - dow.database_name, - object_name = - ISNULL - ( - dow.object_name, - N'UNKNOWN' - ), - finding_group = N'Total Object Deadlocks', - finding = - N'This object was involved in ' + - CONVERT - ( - nvarchar(20), - COUNT_BIG(DISTINCT dow.event_date) - ) + - N' deadlock(s).', - sort_order = - ROW_NUMBER() - OVER (ORDER BY COUNT_BIG(DISTINCT dow.event_date) DESC) - FROM #deadlock_owner_waiter AS dow - WHERE 1 = 1 - AND (dow.database_id = @DatabaseId OR @DatabaseName IS NULL) - AND (dow.event_date >= @StartDate OR @StartDate IS NULL) - AND (dow.event_date < @EndDate OR @EndDate IS NULL) - AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) - GROUP BY - dow.database_name, - dow.object_name - OPTION(RECOMPILE); - - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - /*Check 3 continuation, number of deadlocks per index*/ - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Check 3 (continued) index deadlocks %s', 0, 1, @d) WITH NOWAIT; - - INSERT - #deadlock_findings WITH(TABLOCKX) - ( - check_id, - database_name, - object_name, - finding_group, - finding, - sort_order - ) - SELECT - check_id = 3, - dow.database_name, - index_name = dow.index_name, - finding_group = N'Total Index Deadlocks', - finding = - N'This index was involved in ' + - CONVERT - ( - nvarchar(20), - COUNT_BIG(DISTINCT dow.event_date) - ) + - N' deadlock(s).', - sort_order = - ROW_NUMBER() - OVER (ORDER BY COUNT_BIG(DISTINCT dow.event_date) DESC) - FROM #deadlock_owner_waiter AS dow - WHERE 1 = 1 - AND (dow.database_id = @DatabaseId OR @DatabaseName IS NULL) - AND (dow.event_date >= @StartDate OR @StartDate IS NULL) - AND (dow.event_date < @EndDate OR @EndDate IS NULL) - AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) - AND dow.lock_type NOT IN - ( - N'HEAP', - N'RID' - ) - AND dow.index_name IS NOT NULL - GROUP BY - dow.database_name, - dow.index_name - OPTION(RECOMPILE); - - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - /*Check 3 continuation, number of deadlocks per heap*/ - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Check 3 (continued) heap deadlocks %s', 0, 1, @d) WITH NOWAIT; - - INSERT - #deadlock_findings WITH(TABLOCKX) - ( - check_id, - database_name, - object_name, - finding_group, - finding, - sort_order - ) - SELECT - check_id = 3, - dow.database_name, - index_name = dow.index_name, - finding_group = N'Total Heap Deadlocks', - finding = - N'This heap was involved in ' + - CONVERT - ( - nvarchar(20), - COUNT_BIG(DISTINCT dow.event_date) - ) + - N' deadlock(s).', - sort_order = - ROW_NUMBER() - OVER (ORDER BY COUNT_BIG(DISTINCT dow.event_date) DESC) - FROM #deadlock_owner_waiter AS dow - WHERE 1 = 1 - AND (dow.database_id = @DatabaseId OR @DatabaseName IS NULL) - AND (dow.event_date >= @StartDate OR @StartDate IS NULL) - AND (dow.event_date < @EndDate OR @EndDate IS NULL) - AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) - AND dow.lock_type IN - ( - N'HEAP', - N'RID' - ) - GROUP BY - dow.database_name, - dow.index_name - OPTION(RECOMPILE); - - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - /*Check 4 looks for Serializable deadlocks*/ - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Check 4 serializable deadlocks %s', 0, 1, @d) WITH NOWAIT; - - INSERT - #deadlock_findings WITH(TABLOCKX) - ( - check_id, - database_name, - object_name, - finding_group, - finding, - sort_order - ) - SELECT - check_id = 4, - database_name = - dp.database_name, - object_name = N'-', - finding_group = N'Serializable Deadlocking', - finding = - N'This database has had ' + - CONVERT - ( - nvarchar(20), - COUNT_BIG(DISTINCT dp.event_date) - ) + - N' instances of Serializable deadlocks.', - sort_order = - ROW_NUMBER() - OVER (ORDER BY COUNT_BIG(DISTINCT dp.event_date) DESC) - FROM #deadlock_process AS dp - WHERE dp.isolation_level LIKE N'serializable%' - AND (dp.database_name = @DatabaseName OR @DatabaseName IS NULL) - AND (dp.event_date >= @StartDate OR @StartDate IS NULL) - AND (dp.event_date < @EndDate OR @EndDate IS NULL) - AND (dp.client_app = @AppName OR @AppName IS NULL) - AND (dp.host_name = @HostName OR @HostName IS NULL) - AND (dp.login_name = @LoginName OR @LoginName IS NULL) - GROUP BY dp.database_name - OPTION(RECOMPILE); - - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - /*Check 5 looks for Repeatable Read deadlocks*/ - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Check 5 repeatable read deadlocks %s', 0, 1, @d) WITH NOWAIT; - - INSERT - #deadlock_findings WITH(TABLOCKX) - ( - check_id, - database_name, - object_name, - finding_group, - finding, - sort_order - ) - SELECT - check_id = 5, - dp.database_name, - object_name = N'-', - finding_group = N'Repeatable Read Deadlocking', - finding = - N'This database has had ' + - CONVERT - ( - nvarchar(20), - COUNT_BIG(DISTINCT dp.event_date) - ) + - N' instances of Repeatable Read deadlocks.', - sort_order = - ROW_NUMBER() - OVER (ORDER BY COUNT_BIG(DISTINCT dp.event_date) DESC) - FROM #deadlock_process AS dp - WHERE dp.isolation_level LIKE N'repeatable%' - AND (dp.database_name = @DatabaseName OR @DatabaseName IS NULL) - AND (dp.event_date >= @StartDate OR @StartDate IS NULL) - AND (dp.event_date < @EndDate OR @EndDate IS NULL) - AND (dp.client_app = @AppName OR @AppName IS NULL) - AND (dp.host_name = @HostName OR @HostName IS NULL) - AND (dp.login_name = @LoginName OR @LoginName IS NULL) - GROUP BY dp.database_name - OPTION(RECOMPILE); - - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - /*Check 6 breaks down app, host, and login information*/ - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Check 6 app/host/login deadlocks %s', 0, 1, @d) WITH NOWAIT; - - INSERT - #deadlock_findings WITH(TABLOCKX) - ( - check_id, - database_name, - object_name, - finding_group, - finding, - sort_order - ) - SELECT - check_id = 6, - database_name = - dp.database_name, - object_name = N'-', - finding_group = N'Login, App, and Host deadlocks', - finding = - N'This database has had ' + - CONVERT - ( - nvarchar(20), - COUNT_BIG(DISTINCT dp.event_date) - ) + - N' instances of deadlocks involving the login ' + - ISNULL - ( - dp.login_name, - N'UNKNOWN' - ) + - N' from the application ' + - ISNULL - ( - dp.client_app, - N'UNKNOWN' - ) + - N' on host ' + - ISNULL - ( - dp.host_name, - N'UNKNOWN' - ) + - N'.', - sort_order = - ROW_NUMBER() - OVER (ORDER BY COUNT_BIG(DISTINCT dp.event_date) DESC) - FROM #deadlock_process AS dp - WHERE 1 = 1 - AND (dp.database_name = @DatabaseName OR @DatabaseName IS NULL) - AND (dp.event_date >= @StartDate OR @StartDate IS NULL) - AND (dp.event_date < @EndDate OR @EndDate IS NULL) - AND (dp.client_app = @AppName OR @AppName IS NULL) - AND (dp.host_name = @HostName OR @HostName IS NULL) - AND (dp.login_name = @LoginName OR @LoginName IS NULL) - GROUP BY - dp.database_name, - dp.login_name, - dp.client_app, - dp.host_name - OPTION(RECOMPILE); - - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - /*Check 7 breaks down the types of deadlocks (object, page, key, etc.)*/ - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Check 7 types of deadlocks %s', 0, 1, @d) WITH NOWAIT; - - WITH - lock_types AS - ( - SELECT - database_name = - dp.database_name, - dow.object_name, - lock = - CASE - WHEN CHARINDEX(N':', dp.wait_resource) > 0 - THEN SUBSTRING - ( - dp.wait_resource, - 1, - CHARINDEX(N':', dp.wait_resource) - 1 - ) - ELSE dp.wait_resource - END, - lock_count = - CONVERT - ( - nvarchar(20), - COUNT_BIG(DISTINCT dp.event_date) - ) - FROM #deadlock_process AS dp - JOIN #deadlock_owner_waiter AS dow - ON (dp.id = dow.owner_id - OR dp.victim_id = dow.waiter_id) - AND dp.event_date = dow.event_date - WHERE 1 = 1 - AND (dp.database_name = @DatabaseName OR @DatabaseName IS NULL) - AND (dp.event_date >= @StartDate OR @StartDate IS NULL) - AND (dp.event_date < @EndDate OR @EndDate IS NULL) - AND (dp.client_app = @AppName OR @AppName IS NULL) - AND (dp.host_name = @HostName OR @HostName IS NULL) - AND (dp.login_name = @LoginName OR @LoginName IS NULL) - AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) - AND dow.object_name IS NOT NULL - GROUP BY - dp.database_name, - CASE - WHEN CHARINDEX(N':', dp.wait_resource) > 0 - THEN SUBSTRING - ( - dp.wait_resource, - 1, - CHARINDEX(N':', dp.wait_resource) - 1 - ) - ELSE dp.wait_resource - END, - dow.object_name - ) - INSERT - #deadlock_findings WITH(TABLOCKX) - ( - check_id, - database_name, - object_name, - finding_group, - finding, - sort_order - ) - SELECT - check_id = 7, - lt.database_name, - lt.object_name, - finding_group = N'Types of locks by object', - finding = - N'This object has had ' + - STUFF - ( - ( - SELECT - N', ' + - lt2.lock_count + - N' ' + - lt2.lock - FROM lock_types AS lt2 - WHERE lt2.database_name = lt.database_name - AND lt2.object_name = lt.object_name - FOR XML - PATH(N''), - TYPE - ).value(N'.[1]', N'nvarchar(MAX)'), - 1, - 1, - N'' - ) + N' locks.', - sort_order = - ROW_NUMBER() - OVER (ORDER BY CONVERT(bigint, lt.lock_count) DESC) - FROM lock_types AS lt - OPTION(RECOMPILE); - - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - /*Check 8 gives you more info queries for sp_BlitzCache & BlitzQueryStore*/ - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Check 8 more info part 1 BlitzCache %s', 0, 1, @d) WITH NOWAIT; - - WITH - deadlock_stack AS - ( - SELECT DISTINCT - ds.id, - ds.event_date, - ds.proc_name, - database_name = - PARSENAME(ds.proc_name, 3), - schema_name = - PARSENAME(ds.proc_name, 2), - proc_only_name = - PARSENAME(ds.proc_name, 1), - sql_handle_csv = - N'''' + - STUFF - ( - ( - SELECT DISTINCT - N',' + - ds2.sql_handle - FROM #deadlock_stack AS ds2 - WHERE ds2.id = ds.id - AND ds2.event_date = ds.event_date - AND ds2.sql_handle <> 0x - FOR XML - PATH(N''), - TYPE - ).value(N'.[1]', N'nvarchar(MAX)'), - 1, - 1, - N'' - ) + - N'''' - FROM #deadlock_stack AS ds - WHERE ds.sql_handle <> 0x - GROUP BY - PARSENAME(ds.proc_name, 3), - PARSENAME(ds.proc_name, 2), - PARSENAME(ds.proc_name, 1), - ds.proc_name, - ds.id, - ds.event_date - ) - INSERT - #deadlock_findings WITH(TABLOCKX) - ( - check_id, - database_name, - object_name, - finding_group, - finding - ) - SELECT DISTINCT - check_id = 8, - dow.database_name, - object_name = ds.proc_name, - finding_group = N'More Info - Query', - finding = N'EXEC sp_BlitzCache ' + - CASE - WHEN ds.proc_name = N'adhoc' - THEN N'@OnlySqlHandles = ' + ds.sql_handle_csv - ELSE N'@StoredProcName = ' + QUOTENAME(ds.proc_only_name, N'''') - END + N';' - FROM deadlock_stack AS ds - JOIN #deadlock_owner_waiter AS dow - ON dow.owner_id = ds.id - AND dow.event_date = ds.event_date - WHERE 1 = 1 - AND (dow.database_id = @DatabaseId OR @DatabaseName IS NULL) - AND (dow.event_date >= @StartDate OR @StartDate IS NULL) - AND (dow.event_date < @EndDate OR @EndDate IS NULL) - AND (dow.object_name = @StoredProcName OR @StoredProcName IS NULL) - AND ds.proc_name NOT LIKE 'Unknown%' - OPTION(RECOMPILE); - - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - IF (@ProductVersionMajor >= 13 OR @Azure = 1) - BEGIN - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Check 8 more info part 2 BlitzQueryStore %s', 0, 1, @d) WITH NOWAIT; - - WITH - deadlock_stack AS - ( - SELECT DISTINCT - ds.id, - ds.sql_handle, - ds.proc_name, - ds.event_date, - database_name = - PARSENAME(ds.proc_name, 3), - schema_name = - PARSENAME(ds.proc_name, 2), - proc_only_name = - PARSENAME(ds.proc_name, 1) - FROM #deadlock_stack AS ds - ) - INSERT - #deadlock_findings WITH(TABLOCKX) - ( - check_id, - database_name, - object_name, - finding_group, - finding - ) - SELECT DISTINCT - check_id = 8, - dow.database_name, - object_name = ds.proc_name, - finding_group = N'More Info - Query', - finding = - N'EXEC sp_BlitzQueryStore ' + - N'@DatabaseName = ' + - QUOTENAME(ds.database_name, N'''') + - N', ' + - N'@StoredProcName = ' + - QUOTENAME(ds.proc_only_name, N'''') + - N';' - FROM deadlock_stack AS ds - JOIN #deadlock_owner_waiter AS dow - ON dow.owner_id = ds.id - AND dow.event_date = ds.event_date - WHERE ds.proc_name <> N'adhoc' - AND (dow.database_id = @DatabaseId OR @DatabaseName IS NULL) - AND (dow.event_date >= @StartDate OR @StartDate IS NULL) - AND (dow.event_date < @EndDate OR @EndDate IS NULL) - AND (dow.object_name = @StoredProcName OR @StoredProcName IS NULL) - OPTION(RECOMPILE); - - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - END; - - /*Check 9 gives you stored procedure deadlock counts*/ - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Check 9 stored procedure deadlocks %s', 0, 1, @d) WITH NOWAIT; - - INSERT - #deadlock_findings WITH(TABLOCKX) - ( - check_id, - database_name, - object_name, - finding_group, - finding, - sort_order - ) - SELECT - check_id = 9, - database_name = - dp.database_name, - object_name = ds.proc_name, - finding_group = N'Stored Procedure Deadlocks', - finding = - N'The stored procedure ' + - PARSENAME(ds.proc_name, 2) + - N'.' + - PARSENAME(ds.proc_name, 1) + - N' has been involved in ' + - CONVERT - ( - nvarchar(10), - COUNT_BIG(DISTINCT ds.id) - ) + - N' deadlocks.', - sort_order = - ROW_NUMBER() - OVER (ORDER BY COUNT_BIG(DISTINCT ds.id) DESC) - FROM #deadlock_stack AS ds - JOIN #deadlock_process AS dp - ON dp.id = ds.id - AND ds.event_date = dp.event_date - WHERE ds.proc_name <> N'adhoc' - AND (ds.proc_name = @StoredProcName OR @StoredProcName IS NULL) - AND (dp.database_name = @DatabaseName OR @DatabaseName IS NULL) - AND (dp.event_date >= @StartDate OR @StartDate IS NULL) - AND (dp.event_date < @EndDate OR @EndDate IS NULL) - AND (dp.client_app = @AppName OR @AppName IS NULL) - AND (dp.host_name = @HostName OR @HostName IS NULL) - AND (dp.login_name = @LoginName OR @LoginName IS NULL) - GROUP BY - dp.database_name, - ds.proc_name - OPTION(RECOMPILE); - - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - /*Check 10 gives you more info queries for sp_BlitzIndex */ - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Check 10 more info, BlitzIndex %s', 0, 1, @d) WITH NOWAIT; - - WITH - bi AS - ( - SELECT DISTINCT - dow.object_name, - dow.database_name, - schema_name = s.schema_name, - table_name = s.table_name - FROM #deadlock_owner_waiter AS dow - JOIN @sysAssObjId AS s - ON s.database_id = dow.database_id - AND s.partition_id = dow.associatedObjectId - WHERE 1 = 1 - AND (dow.database_id = @DatabaseId OR @DatabaseName IS NULL) - AND (dow.event_date >= @StartDate OR @StartDate IS NULL) - AND (dow.event_date < @EndDate OR @EndDate IS NULL) - AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) - AND dow.object_name IS NOT NULL - ) - INSERT - #deadlock_findings WITH(TABLOCKX) - ( - check_id, - database_name, - object_name, - finding_group, - finding - ) - SELECT DISTINCT - check_id = 10, - bi.database_name, - bi.object_name, - finding_group = N'More Info - Table', - finding = - N'EXEC sp_BlitzIndex ' + - N'@DatabaseName = ' + - QUOTENAME(bi.database_name, N'''') + - N', @SchemaName = ' + - QUOTENAME(bi.schema_name, N'''') + - N', @TableName = ' + - QUOTENAME(bi.table_name, N'''') + - N';' - FROM bi - OPTION(RECOMPILE); - - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - /*Check 11 gets total deadlock wait time per object*/ - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Check 11 deadlock wait time per object %s', 0, 1, @d) WITH NOWAIT; - - WITH - chopsuey AS - ( - - SELECT - database_name = - dp.database_name, - dow.object_name, - wait_days = - CONVERT - ( - nvarchar(30), - ( - SUM - ( - CONVERT - ( - bigint, - dp.wait_time - ) - ) / 1000 / 86400 - ) - ), - wait_time_hms = - /*the more wait time you rack up the less accurate this gets, - it's either that or erroring out*/ - CASE - WHEN - SUM - ( - CONVERT - ( - bigint, - dp.wait_time - ) - )/1000 > 2147483647 - THEN - CONVERT - ( - nvarchar(30), - DATEADD - ( - MINUTE, - ( - ( - SUM - ( - CONVERT - ( - bigint, - dp.wait_time - ) - ) - )/ - 60000 - ), - 0 - ), - 14 - ) - WHEN - SUM - ( - CONVERT - ( - bigint, - dp.wait_time - ) - ) BETWEEN 2147483648 AND 2147483647000 - THEN - CONVERT - ( - nvarchar(30), - DATEADD - ( - SECOND, - ( - ( - SUM - ( - CONVERT - ( - bigint, - dp.wait_time - ) - ) - )/ - 1000 - ), - 0 - ), - 14 - ) - ELSE - CONVERT - ( - nvarchar(30), - DATEADD - ( - MILLISECOND, - ( - SUM - ( - CONVERT - ( - bigint, - dp.wait_time - ) - ) - ), - 0 - ), - 14 - ) - END, - total_waits = - SUM(CONVERT(bigint, dp.wait_time)) - FROM #deadlock_owner_waiter AS dow - JOIN #deadlock_process AS dp - ON (dp.id = dow.owner_id - OR dp.victim_id = dow.waiter_id) - AND dp.event_date = dow.event_date - WHERE 1 = 1 - AND (dp.database_name = @DatabaseName OR @DatabaseName IS NULL) - AND (dp.event_date >= @StartDate OR @StartDate IS NULL) - AND (dp.event_date < @EndDate OR @EndDate IS NULL) - AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) - AND (dp.client_app = @AppName OR @AppName IS NULL) - AND (dp.host_name = @HostName OR @HostName IS NULL) - AND (dp.login_name = @LoginName OR @LoginName IS NULL) - GROUP BY - dp.database_name, - dow.object_name - ) - INSERT - #deadlock_findings WITH(TABLOCKX) - ( - check_id, - database_name, - object_name, - finding_group, - finding, - sort_order - ) - SELECT - check_id = 11, - cs.database_name, - cs.object_name, - finding_group = N'Total object deadlock wait time', - finding = - N'This object has had ' + - CONVERT - ( - nvarchar(30), - cs.wait_days - ) + - N' ' + - CONVERT - ( - nvarchar(30), - cs.wait_time_hms, - 14 - ) + - N' [dd hh:mm:ss:ms] of deadlock wait time.', - sort_order = - ROW_NUMBER() - OVER (ORDER BY cs.total_waits DESC) - FROM chopsuey AS cs - WHERE cs.object_name IS NOT NULL - OPTION(RECOMPILE); - - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - /*Check 12 gets total deadlock wait time per database*/ - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Check 12 deadlock wait time per database %s', 0, 1, @d) WITH NOWAIT; - - WITH - wait_time AS - ( - SELECT - database_name = - dp.database_name, - total_wait_time_ms = - SUM - ( - CONVERT - ( - bigint, - dp.wait_time - ) - ) - FROM #deadlock_owner_waiter AS dow - JOIN #deadlock_process AS dp - ON (dp.id = dow.owner_id - OR dp.victim_id = dow.waiter_id) - AND dp.event_date = dow.event_date - WHERE 1 = 1 - AND (dp.database_name = @DatabaseName OR @DatabaseName IS NULL) - AND (dp.event_date >= @StartDate OR @StartDate IS NULL) - AND (dp.event_date < @EndDate OR @EndDate IS NULL) - AND (dp.client_app = @AppName OR @AppName IS NULL) - AND (dp.host_name = @HostName OR @HostName IS NULL) - AND (dp.login_name = @LoginName OR @LoginName IS NULL) - GROUP BY - dp.database_name - ) - INSERT - #deadlock_findings WITH(TABLOCKX) - ( - check_id, - database_name, - object_name, - finding_group, - finding, - sort_order - ) - SELECT - check_id = 12, - wt.database_name, - object_name = N'-', - finding_group = N'Total database deadlock wait time', - N'This database has had ' + - CONVERT - ( - nvarchar(30), - ( - SUM - ( - CONVERT - ( - bigint, - wt.total_wait_time_ms - ) - ) / 1000 / 86400 - ) - ) + - N' ' + - /*the more wait time you rack up the less accurate this gets, - it's either that or erroring out*/ - CASE - WHEN - SUM - ( - CONVERT - ( - bigint, - wt.total_wait_time_ms - ) - )/1000 > 2147483647 - THEN - CONVERT - ( - nvarchar(30), - DATEADD - ( - MINUTE, - ( - ( - SUM - ( - CONVERT - ( - bigint, - wt.total_wait_time_ms - ) - ) - )/ - 60000 - ), - 0 - ), - 14 - ) - WHEN - SUM - ( - CONVERT - ( - bigint, - wt.total_wait_time_ms - ) - ) BETWEEN 2147483648 AND 2147483647000 - THEN - CONVERT - ( - nvarchar(30), - DATEADD - ( - SECOND, - ( - ( - SUM - ( - CONVERT - ( - bigint, - wt.total_wait_time_ms - ) - ) - )/ - 1000 - ), - 0 - ), - 14 - ) - ELSE - CONVERT - ( - nvarchar(30), - DATEADD - ( - MILLISECOND, - ( - SUM - ( - CONVERT - ( - bigint, - wt.total_wait_time_ms - ) - ) - ), - 0 - ), - 14 - ) END + - N' [dd hh:mm:ss:ms] of deadlock wait time.', - sort_order = - ROW_NUMBER() - OVER (ORDER BY SUM(CONVERT(bigint, wt.total_wait_time_ms)) DESC) - FROM wait_time AS wt - GROUP BY - wt.database_name - OPTION(RECOMPILE); - - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - /*Check 13 gets total deadlock wait time for SQL Agent*/ - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Check 13 deadlock count for SQL Agent %s', 0, 1, @d) WITH NOWAIT; - - INSERT - #deadlock_findings WITH(TABLOCKX) - ( - check_id, - database_name, - object_name, - finding_group, - finding, - sort_order - ) - SELECT - check_id = 13, - database_name = - DB_NAME(aj.database_id), - object_name = - N'SQLAgent - Job: ' + - aj.job_name + - N' Step: ' + - aj.step_name, - finding_group = N'Agent Job Deadlocks', - finding = - N'There have been ' + - RTRIM(COUNT_BIG(DISTINCT aj.event_date)) + - N' deadlocks from this Agent Job and Step.', - sort_order = - ROW_NUMBER() - OVER (ORDER BY COUNT_BIG(DISTINCT aj.event_date) DESC) - FROM #agent_job AS aj - GROUP BY - DB_NAME(aj.database_id), - aj.job_name, - aj.step_name - OPTION(RECOMPILE); - - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - /*Check 14 is total parallel deadlocks*/ - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Check 14 parallel deadlocks %s', 0, 1, @d) WITH NOWAIT; - - INSERT - #deadlock_findings WITH(TABLOCKX) - ( - check_id, - database_name, - object_name, - finding_group, - finding - ) - SELECT - check_id = 14, - database_name = N'-', - object_name = N'-', - finding_group = N'Total parallel deadlocks', - finding = - N'There have been ' + - CONVERT - ( - nvarchar(20), - COUNT_BIG(DISTINCT drp.event_date) - ) + - N' parallel deadlocks.' - FROM #deadlock_resource_parallel AS drp - HAVING COUNT_BIG(DISTINCT drp.event_date) > 0 - OPTION(RECOMPILE); - - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - /*Check 15 is total deadlocks involving sleeping sessions*/ - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Check 15 sleeping and background deadlocks %s', 0, 1, @d) WITH NOWAIT; - - INSERT - #deadlock_findings WITH(TABLOCKX) - ( - check_id, - database_name, - object_name, - finding_group, - finding - ) - SELECT - check_id = 15, - database_name = N'-', - object_name = N'-', - finding_group = N'Total deadlocks involving sleeping sessions', - finding = - N'There have been ' + - CONVERT - ( - nvarchar(20), - COUNT_BIG(DISTINCT dp.event_date) - ) + - N' sleepy deadlocks.' - FROM #deadlock_process AS dp - WHERE dp.status = N'sleeping' - HAVING COUNT_BIG(DISTINCT dp.event_date) > 0 - OPTION(RECOMPILE); - - INSERT - #deadlock_findings WITH(TABLOCKX) - ( - check_id, - database_name, - object_name, - finding_group, - finding - ) - SELECT - check_id = 15, - database_name = N'-', - object_name = N'-', - finding_group = N'Total deadlocks involving background processes', - finding = - N'There have been ' + - CONVERT - ( - nvarchar(20), - COUNT_BIG(DISTINCT dp.event_date) - ) + - N' deadlocks with background task.' - FROM #deadlock_process AS dp - WHERE dp.status = N'background' - HAVING COUNT_BIG(DISTINCT dp.event_date) > 0 - OPTION(RECOMPILE); - - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - /*Check 16 is total deadlocks involving implicit transactions*/ - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Check 16 implicit transaction deadlocks %s', 0, 1, @d) WITH NOWAIT; - - INSERT - #deadlock_findings WITH(TABLOCKX) - ( - check_id, - database_name, - object_name, - finding_group, - finding - ) - SELECT - check_id = 14, - database_name = N'-', - object_name = N'-', - finding_group = N'Total implicit transaction deadlocks', - finding = - N'There have been ' + - CONVERT - ( - nvarchar(20), - COUNT_BIG(DISTINCT dp.event_date) - ) + - N' implicit transaction deadlocks.' - FROM #deadlock_process AS dp - WHERE dp.transaction_name = N'implicit_transaction' - HAVING COUNT_BIG(DISTINCT dp.event_date) > 0 - OPTION(RECOMPILE); - - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - /*Thank you goodnight*/ - INSERT - #deadlock_findings WITH(TABLOCKX) - ( - check_id, - database_name, - object_name, - finding_group, - finding - ) - VALUES - ( - -1, - N'sp_BlitzLock version ' + CONVERT(nvarchar(10), @Version), - N'Results for ' + CONVERT(nvarchar(10), @StartDate, 23) + N' through ' + CONVERT(nvarchar(10), @EndDate, 23), - N'http://FirstResponderKit.org/', - N'To get help or add your own contributions to the SQL Server First Responder Kit, join us at http://FirstResponderKit.org.' - ); - - RAISERROR('Finished rollup at %s', 0, 1, @d) WITH NOWAIT; - - /*Results*/ - BEGIN - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Results 1 %s', 0, 1, @d) WITH NOWAIT; - - CREATE CLUSTERED INDEX cx_whatever_dp ON #deadlock_process (event_date, id); - CREATE CLUSTERED INDEX cx_whatever_drp ON #deadlock_resource_parallel (event_date, owner_id); - CREATE CLUSTERED INDEX cx_whatever_dow ON #deadlock_owner_waiter (event_date, owner_id, waiter_id); - - IF @Debug = 1 BEGIN SET STATISTICS XML ON; END; - - WITH - deadlocks AS - ( - SELECT - deadlock_type = - N'Regular Deadlock', - dp.event_date, - dp.id, - dp.victim_id, - dp.spid, - dp.database_id, - dp.database_name, - dp.current_database_name, - dp.priority, - dp.log_used, - wait_resource = - dp.wait_resource COLLATE DATABASE_DEFAULT, - object_names = - CONVERT - ( - xml, - STUFF - ( - ( - SELECT DISTINCT - object_name = - NCHAR(10) + - N' ' + - ISNULL(c.object_name, N'') + - N' ' COLLATE DATABASE_DEFAULT - FROM #deadlock_owner_waiter AS c - WHERE (dp.id = c.owner_id - OR dp.victim_id = c.waiter_id) - AND dp.event_date = c.event_date - FOR XML - PATH(N''), - TYPE - ).value(N'.[1]', N'nvarchar(4000)'), - 1, - 1, - N'' - ) - ), - dp.wait_time, - dp.transaction_name, - dp.status, - dp.last_tran_started, - dp.last_batch_started, - dp.last_batch_completed, - dp.lock_mode, - dp.transaction_count, - dp.client_app, - dp.host_name, - dp.login_name, - dp.isolation_level, - dp.client_option_1, - dp.client_option_2, - inputbuf = - dp.process_xml.value('(//process/inputbuf/text())[1]', 'nvarchar(MAX)'), - en = - DENSE_RANK() OVER (ORDER BY dp.event_date), - qn = - ROW_NUMBER() OVER (PARTITION BY dp.event_date ORDER BY dp.event_date) - 1, - dn = - ROW_NUMBER() OVER (PARTITION BY dp.event_date, dp.id ORDER BY dp.event_date), - dp.is_victim, - owner_mode = - ISNULL(dp.owner_mode, N'-'), - owner_waiter_type = NULL, - owner_activity = NULL, - owner_waiter_activity = NULL, - owner_merging = NULL, - owner_spilling = NULL, - owner_waiting_to_close = NULL, - waiter_mode = - ISNULL(dp.waiter_mode, N'-'), - waiter_waiter_type = NULL, - waiter_owner_activity = NULL, - waiter_waiter_activity = NULL, - waiter_merging = NULL, - waiter_spilling = NULL, - waiter_waiting_to_close = NULL, - dp.deadlock_graph - FROM #deadlock_process AS dp - WHERE dp.victim_id IS NOT NULL - AND dp.is_parallel = 0 - - UNION ALL - - SELECT - deadlock_type = - N'Parallel Deadlock', - dp.event_date, - dp.id, - dp.victim_id, - dp.spid, - dp.database_id, - dp.database_name, - dp.current_database_name, - dp.priority, - dp.log_used, - dp.wait_resource COLLATE DATABASE_DEFAULT, - object_names = - CONVERT - ( - xml, - STUFF - ( - ( - SELECT DISTINCT - object_name = - NCHAR(10) + - N' ' + - ISNULL(c.object_name, N'') + - N' ' COLLATE DATABASE_DEFAULT - FROM #deadlock_owner_waiter AS c - WHERE (dp.id = c.owner_id - OR dp.victim_id = c.waiter_id) - AND dp.event_date = c.event_date - FOR XML - PATH(N''), - TYPE - ).value(N'.[1]', N'nvarchar(4000)'), - 1, - 1, - N'' - ) - ), - dp.wait_time, - dp.transaction_name, - dp.status, - dp.last_tran_started, - dp.last_batch_started, - dp.last_batch_completed, - dp.lock_mode, - dp.transaction_count, - dp.client_app, - dp.host_name, - dp.login_name, - dp.isolation_level, - dp.client_option_1, - dp.client_option_2, - inputbuf = - dp.process_xml.value('(//process/inputbuf/text())[1]', 'nvarchar(MAX)'), - en = - DENSE_RANK() OVER (ORDER BY dp.event_date), - qn = - ROW_NUMBER() OVER (PARTITION BY dp.event_date ORDER BY dp.event_date) - 1, - dn = - ROW_NUMBER() OVER (PARTITION BY dp.event_date, dp.id ORDER BY dp.event_date), - is_victim = 1, - owner_mode = cao.wait_type COLLATE DATABASE_DEFAULT, - owner_waiter_type = cao.waiter_type, - owner_activity = cao.owner_activity, - owner_waiter_activity = cao.waiter_activity, - owner_merging = cao.merging, - owner_spilling = cao.spilling, - owner_waiting_to_close = cao.waiting_to_close, - waiter_mode = caw.wait_type COLLATE DATABASE_DEFAULT, - waiter_waiter_type = caw.waiter_type, - waiter_owner_activity = caw.owner_activity, - waiter_waiter_activity = caw.waiter_activity, - waiter_merging = caw.merging, - waiter_spilling = caw.spilling, - waiter_waiting_to_close = caw.waiting_to_close, - dp.deadlock_graph - FROM #deadlock_process AS dp - OUTER APPLY - ( - SELECT TOP (1) - drp.* - FROM #deadlock_resource_parallel AS drp - WHERE drp.owner_id = dp.id - AND drp.wait_type IN - ( - N'e_waitPortOpen', - N'e_waitPipeNewRow' - ) - ORDER BY drp.event_date - ) AS cao - OUTER APPLY - ( - SELECT TOP (1) - drp.* - FROM #deadlock_resource_parallel AS drp - WHERE drp.owner_id = dp.id - AND drp.wait_type IN - ( - N'e_waitPortOpen', - N'e_waitPipeGetRow' - ) - ORDER BY drp.event_date - ) AS caw - WHERE dp.is_parallel = 1 - ) - SELECT - d.deadlock_type, - d.event_date, - d.id, - d.victim_id, - d.spid, - deadlock_group = - N'Deadlock #' + - CONVERT - ( - nvarchar(10), - d.en - ) + - N', Query #' - + CASE - WHEN d.qn = 0 - THEN N'1' - ELSE CONVERT(nvarchar(10), d.qn) - END + CASE - WHEN d.is_victim = 1 - THEN N' - VICTIM' - ELSE N'' - END, - d.database_id, - d.database_name, - d.current_database_name, - d.priority, - d.log_used, - d.wait_resource, - d.object_names, - d.wait_time, - d.transaction_name, - d.status, - d.last_tran_started, - d.last_batch_started, - d.last_batch_completed, - d.lock_mode, - d.transaction_count, - d.client_app, - d.host_name, - d.login_name, - d.isolation_level, - d.client_option_1, - d.client_option_2, - inputbuf = - CASE - WHEN d.inputbuf - LIKE @inputbuf_bom + N'Proc |[Database Id = %' ESCAPE N'|' - THEN - OBJECT_SCHEMA_NAME - ( - SUBSTRING - ( - d.inputbuf, - CHARINDEX(N'Object Id = ', d.inputbuf) + 12, - LEN(d.inputbuf) - (CHARINDEX(N'Object Id = ', d.inputbuf) + 12) - ) - , - SUBSTRING - ( - d.inputbuf, - CHARINDEX(N'Database Id = ', d.inputbuf) + 14, - CHARINDEX(N'Object Id', d.inputbuf) - (CHARINDEX(N'Database Id = ', d.inputbuf) + 14) - ) - ) + - N'.' + - OBJECT_NAME - ( - SUBSTRING - ( - d.inputbuf, - CHARINDEX(N'Object Id = ', d.inputbuf) + 12, - LEN(d.inputbuf) - (CHARINDEX(N'Object Id = ', d.inputbuf) + 12) - ) - , - SUBSTRING - ( - d.inputbuf, - CHARINDEX(N'Database Id = ', d.inputbuf) + 14, - CHARINDEX(N'Object Id', d.inputbuf) - (CHARINDEX(N'Database Id = ', d.inputbuf) + 14) - ) - ) - ELSE d.inputbuf - END COLLATE Latin1_General_BIN2, - d.owner_mode, - d.owner_waiter_type, - d.owner_activity, - d.owner_waiter_activity, - d.owner_merging, - d.owner_spilling, - d.owner_waiting_to_close, - d.waiter_mode, - d.waiter_waiter_type, - d.waiter_owner_activity, - d.waiter_waiter_activity, - d.waiter_merging, - d.waiter_spilling, - d.waiter_waiting_to_close, - d.deadlock_graph, - d.is_victim - INTO #deadlocks - FROM deadlocks AS d - WHERE d.dn = 1 - AND (d.is_victim = @VictimsOnly - OR @VictimsOnly = 0) - AND d.qn < CASE - WHEN d.deadlock_type = N'Parallel Deadlock' - THEN 2 - ELSE 2147483647 - END - AND (DB_NAME(d.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (d.event_date >= @StartDate OR @StartDate IS NULL) - AND (d.event_date < @EndDate OR @EndDate IS NULL) - AND (CONVERT(nvarchar(MAX), d.object_names) COLLATE Latin1_General_BIN2 LIKE N'%' + @ObjectName + N'%' OR @ObjectName IS NULL) - AND (d.client_app = @AppName OR @AppName IS NULL) - AND (d.host_name = @HostName OR @HostName IS NULL) - AND (d.login_name = @LoginName OR @LoginName IS NULL) - OPTION (RECOMPILE, LOOP JOIN, HASH JOIN); - - UPDATE d - SET d.inputbuf = - REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE( - REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE( - REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE( - d.inputbuf, - NCHAR(31), N'?'), NCHAR(30), N'?'), NCHAR(29), N'?'), NCHAR(28), N'?'), NCHAR(27), N'?'), - NCHAR(26), N'?'), NCHAR(25), N'?'), NCHAR(24), N'?'), NCHAR(23), N'?'), NCHAR(22), N'?'), - NCHAR(21), N'?'), NCHAR(20), N'?'), NCHAR(19), N'?'), NCHAR(18), N'?'), NCHAR(17), N'?'), - NCHAR(16), N'?'), NCHAR(15), N'?'), NCHAR(14), N'?'), NCHAR(12), N'?'), NCHAR(11), N'?'), - NCHAR(8), N'?'), NCHAR(7), N'?'), NCHAR(6), N'?'), NCHAR(5), N'?'), NCHAR(4), N'?'), - NCHAR(3), N'?'), NCHAR(2), N'?'), NCHAR(1), N'?'), NCHAR(0), N'?') - FROM #deadlocks AS d - OPTION(RECOMPILE); - - SELECT - d.deadlock_type, - d.event_date, - database_name = - DB_NAME(d.database_id), - database_name_x = - d.database_name, - d.current_database_name, - d.spid, - d.deadlock_group, - d.client_option_1, - d.client_option_2, - d.lock_mode, - query_xml = - ( - SELECT - [processing-instruction(query)] = - d.inputbuf - FOR XML - PATH(N''), - TYPE - ), - query_string = - d.inputbuf, - d.object_names, - d.isolation_level, - d.owner_mode, - d.waiter_mode, - d.transaction_count, - d.login_name, - d.host_name, - d.client_app, - d.wait_time, - d.wait_resource, - d.priority, - d.log_used, - d.last_tran_started, - d.last_batch_started, - d.last_batch_completed, - d.transaction_name, - d.status, - /*These columns will be NULL for regular (non-parallel) deadlocks*/ - parallel_deadlock_details = - ( - SELECT - d.owner_waiter_type, - d.owner_activity, - d.owner_waiter_activity, - d.owner_merging, - d.owner_spilling, - d.owner_waiting_to_close, - d.waiter_waiter_type, - d.waiter_owner_activity, - d.waiter_waiter_activity, - d.waiter_merging, - d.waiter_spilling, - d.waiter_waiting_to_close - FOR XML - PATH('parallel_deadlock_details'), - TYPE - ), - d.owner_waiter_type, - d.owner_activity, - d.owner_waiter_activity, - d.owner_merging, - d.owner_spilling, - d.owner_waiting_to_close, - d.waiter_waiter_type, - d.waiter_owner_activity, - d.waiter_waiter_activity, - d.waiter_merging, - d.waiter_spilling, - d.waiter_waiting_to_close, - /*end parallel deadlock columns*/ - d.deadlock_graph, - d.is_victim, - d.id - INTO #deadlock_results - FROM #deadlocks AS d; - - IF @Debug = 1 BEGIN SET STATISTICS XML OFF; END; - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - /*There's too much risk of errors sending the*/ - IF @OutputDatabaseCheck = 0 - BEGIN - SET @ExportToExcel = 0; - END; - - SET @deadlock_result += N' - SELECT - server_name = - @@SERVERNAME, - dr.deadlock_type, - dr.event_date, - database_name = - COALESCE - ( - dr.database_name, - dr.database_name_x, - dr.current_database_name - ), - dr.spid, - dr.deadlock_group, - ' + CASE @ExportToExcel - WHEN 1 - THEN N' - query = dr.query_string, - object_names = - REPLACE( - REPLACE( - CONVERT - ( - nvarchar(MAX), - dr.object_names - ) COLLATE Latin1_General_BIN2, - '''', ''''), - '''', ''''),' - ELSE N'query = dr.query_xml, - dr.object_names,' - END + N' - dr.isolation_level, - dr.owner_mode, - dr.waiter_mode, - dr.lock_mode, - dr.transaction_count, - dr.client_option_1, - dr.client_option_2, - dr.login_name, - dr.host_name, - dr.client_app, - dr.wait_time, - dr.wait_resource, - dr.priority, - dr.log_used, - dr.last_tran_started, - dr.last_batch_started, - dr.last_batch_completed, - dr.transaction_name, - dr.status,' + - CASE - WHEN (@ExportToExcel = 1 - OR @OutputDatabaseCheck = 0) - THEN N' - dr.owner_waiter_type, - dr.owner_activity, - dr.owner_waiter_activity, - dr.owner_merging, - dr.owner_spilling, - dr.owner_waiting_to_close, - dr.waiter_waiter_type, - dr.waiter_owner_activity, - dr.waiter_waiter_activity, - dr.waiter_merging, - dr.waiter_spilling, - dr.waiter_waiting_to_close,' - ELSE N' - dr.parallel_deadlock_details,' - END + - CASE - @ExportToExcel - WHEN 1 - THEN N' - deadlock_graph = - REPLACE(REPLACE( - REPLACE(REPLACE( - CONVERT - ( - nvarchar(MAX), - dr.deadlock_graph - ) COLLATE Latin1_General_BIN2, - ''NCHAR(10)'', ''''), ''NCHAR(13)'', ''''), - ''CHAR(10)'', ''''), ''CHAR(13)'', '''')' - ELSE N' - dr.deadlock_graph' - END + N' - FROM #deadlock_results AS dr - ORDER BY - dr.event_date, - dr.is_victim DESC - OPTION(RECOMPILE, LOOP JOIN, HASH JOIN); - '; - - IF (@OutputDatabaseCheck = 0) - BEGIN - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Results to table %s', 0, 1, @d) WITH NOWAIT; - - IF @Debug = 1 - BEGIN - PRINT @deadlock_result; - SET STATISTICS XML ON; - END; - - INSERT INTO - DeadLockTbl - ( - ServerName, - deadlock_type, - event_date, - database_name, - spid, - deadlock_group, - query, - object_names, - isolation_level, - owner_mode, - waiter_mode, - lock_mode, - transaction_count, - client_option_1, - client_option_2, - login_name, - host_name, - client_app, - wait_time, - wait_resource, - priority, - log_used, - last_tran_started, - last_batch_started, - last_batch_completed, - transaction_name, - status, - owner_waiter_type, - owner_activity, - owner_waiter_activity, - owner_merging, - owner_spilling, - owner_waiting_to_close, - waiter_waiter_type, - waiter_owner_activity, - waiter_waiter_activity, - waiter_merging, - waiter_spilling, - waiter_waiting_to_close, - deadlock_graph - ) - EXEC sys.sp_executesql - @deadlock_result; - - IF @Debug = 1 - BEGIN - SET STATISTICS XML OFF; - END; - - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - DROP SYNONYM DeadLockTbl; - - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Findings to table %s', 0, 1, @d) WITH NOWAIT; - - INSERT INTO - DeadlockFindings - ( - ServerName, - check_id, - database_name, - object_name, - finding_group, - finding - ) - SELECT - @@SERVERNAME, - df.check_id, - df.database_name, - df.object_name, - df.finding_group, - df.finding - FROM #deadlock_findings AS df - ORDER BY df.check_id - OPTION(RECOMPILE); - - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - DROP SYNONYM DeadlockFindings; /*done with inserting.*/ - END; - ELSE /*Output to database is not set output to client app*/ - BEGIN - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Results to client %s', 0, 1, @d) WITH NOWAIT; - - IF @Debug = 1 BEGIN SET STATISTICS XML ON; END; - - EXEC sys.sp_executesql - @deadlock_result; - - IF @Debug = 1 - BEGIN - SET STATISTICS XML OFF; - PRINT @deadlock_result; - END; - - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Getting available execution plans for deadlocks %s', 0, 1, @d) WITH NOWAIT; - - SELECT DISTINCT - available_plans = - 'available_plans', - ds.proc_name, - sql_handle = - CONVERT(varbinary(64), ds.sql_handle, 1), - dow.database_name, - dow.database_id, - dow.object_name, - query_xml = - TRY_CAST(dr.query_xml AS nvarchar(MAX)) - INTO #available_plans - FROM #deadlock_stack AS ds - JOIN #deadlock_owner_waiter AS dow - ON dow.owner_id = ds.id - AND dow.event_date = ds.event_date - JOIN #deadlock_results AS dr - ON dr.id = ds.id - AND dr.event_date = ds.event_date - OPTION(RECOMPILE); - - SELECT - deqs.sql_handle, - deqs.plan_handle, - deqs.statement_start_offset, - deqs.statement_end_offset, - deqs.creation_time, - deqs.last_execution_time, - deqs.execution_count, - total_worker_time_ms = - deqs.total_worker_time / 1000., - avg_worker_time_ms = - CONVERT(decimal(38, 6), deqs.total_worker_time / 1000. / deqs.execution_count), - total_elapsed_time_ms = - deqs.total_elapsed_time / 1000., - avg_elapsed_time_ms = - CONVERT(decimal(38, 6), deqs.total_elapsed_time / 1000. / deqs.execution_count), - executions_per_second = - ISNULL - ( - deqs.execution_count / - NULLIF - ( - DATEDIFF - ( - SECOND, - deqs.creation_time, - NULLIF(deqs.last_execution_time, '1900-01-01 00:00:00.000') - ), - 0 - ), - 0 - ), - total_physical_reads_mb = - deqs.total_physical_reads * 8. / 1024., - total_logical_writes_mb = - deqs.total_logical_writes * 8. / 1024., - total_logical_reads_mb = - deqs.total_logical_reads * 8. / 1024., - min_grant_mb = - deqs.min_grant_kb * 8. / 1024., - max_grant_mb = - deqs.max_grant_kb * 8. / 1024., - min_used_grant_mb = - deqs.min_used_grant_kb * 8. / 1024., - max_used_grant_mb = - deqs.max_used_grant_kb * 8. / 1024., - deqs.min_reserved_threads, - deqs.max_reserved_threads, - deqs.min_used_threads, - deqs.max_used_threads, - deqs.total_rows - INTO #dm_exec_query_stats - FROM sys.dm_exec_query_stats AS deqs - WHERE EXISTS - ( - SELECT - 1/0 - FROM #available_plans AS ap - WHERE ap.sql_handle = deqs.sql_handle - ) - AND deqs.query_hash IS NOT NULL; - - CREATE CLUSTERED INDEX - deqs - ON #dm_exec_query_stats - ( - sql_handle, - plan_handle - ); - - SELECT - ap.available_plans, - ap.database_name, - query_text = - TRY_CAST(ap.query_xml AS xml), - ap.query_plan, - ap.creation_time, - ap.last_execution_time, - ap.execution_count, - ap.executions_per_second, - ap.total_worker_time_ms, - ap.avg_worker_time_ms, - ap.total_elapsed_time_ms, - ap.avg_elapsed_time_ms, - ap.total_logical_reads_mb, - ap.total_physical_reads_mb, - ap.total_logical_writes_mb, - ap.min_grant_mb, - ap.max_grant_mb, - ap.min_used_grant_mb, - ap.max_used_grant_mb, - ap.min_reserved_threads, - ap.max_reserved_threads, - ap.min_used_threads, - ap.max_used_threads, - ap.total_rows, - ap.sql_handle, - ap.statement_start_offset, - ap.statement_end_offset - FROM - ( - - SELECT - ap.*, - c.statement_start_offset, - c.statement_end_offset, - c.creation_time, - c.last_execution_time, - c.execution_count, - c.total_worker_time_ms, - c.avg_worker_time_ms, - c.total_elapsed_time_ms, - c.avg_elapsed_time_ms, - c.executions_per_second, - c.total_physical_reads_mb, - c.total_logical_writes_mb, - c.total_logical_reads_mb, - c.min_grant_mb, - c.max_grant_mb, - c.min_used_grant_mb, - c.max_used_grant_mb, - c.min_reserved_threads, - c.max_reserved_threads, - c.min_used_threads, - c.max_used_threads, - c.total_rows, - c.query_plan - FROM #available_plans AS ap - OUTER APPLY - ( - SELECT - deqs.*, - query_plan = - TRY_CAST(deps.query_plan AS xml) - FROM #dm_exec_query_stats deqs - OUTER APPLY sys.dm_exec_text_query_plan - ( - deqs.plan_handle, - deqs.statement_start_offset, - deqs.statement_end_offset - ) AS deps - WHERE deqs.sql_handle = ap.sql_handle - AND deps.dbid = ap.database_id - ) AS c - ) AS ap - WHERE ap.query_plan IS NOT NULL - ORDER BY - ap.avg_worker_time_ms DESC - OPTION(RECOMPILE, LOOP JOIN, HASH JOIN); - - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Returning findings %s', 0, 1, @d) WITH NOWAIT; - - SELECT - df.check_id, - df.database_name, - df.object_name, - df.finding_group, - df.finding - FROM #deadlock_findings AS df - ORDER BY - df.check_id, - df.sort_order - OPTION(RECOMPILE); - - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - END; /*done with output to client app.*/ - END; - - IF @Debug = 1 - BEGIN - SELECT - table_name = N'#deadlock_data', - * - FROM #deadlock_data AS dd - OPTION(RECOMPILE); - - SELECT - table_name = N'#dd', - * - FROM #dd AS d - OPTION(RECOMPILE); - - SELECT - table_name = N'#deadlock_resource', - * - FROM #deadlock_resource AS dr - OPTION(RECOMPILE); - - SELECT - table_name = N'#deadlock_resource_parallel', - * - FROM #deadlock_resource_parallel AS drp - OPTION(RECOMPILE); - - SELECT - table_name = N'#deadlock_owner_waiter', - * - FROM #deadlock_owner_waiter AS dow - OPTION(RECOMPILE); - - SELECT - table_name = N'#deadlock_process', - * - FROM #deadlock_process AS dp - OPTION(RECOMPILE); - - SELECT - table_name = N'#deadlock_stack', - * - FROM #deadlock_stack AS ds - OPTION(RECOMPILE); - - SELECT - table_name = N'#deadlocks', - * - FROM #deadlocks AS d - OPTION(RECOMPILE); - - SELECT - table_name = N'#deadlock_results', - * - FROM #deadlock_results AS dr - OPTION(RECOMPILE); - - SELECT - table_name = N'#x', - * - FROM #x AS x - OPTION(RECOMPILE); - - SELECT - table_name = N'@sysAssObjId', - * - FROM @sysAssObjId AS s - OPTION(RECOMPILE); - - SELECT - table_name = N'#available_plans', - * - FROM #available_plans AS ap - OPTION(RECOMPILE); - - SELECT - table_name = N'#dm_exec_query_stats', - * - FROM #dm_exec_query_stats - OPTION(RECOMPILE); - - SELECT - procedure_parameters = - 'procedure_parameters', - DatabaseName = - @DatabaseName, - StartDate = - @StartDate, - EndDate = - @EndDate, - ObjectName = - @ObjectName, - StoredProcName = - @StoredProcName, - AppName = - @AppName, - HostName = - @HostName, - LoginName = - @LoginName, - EventSessionName = - @EventSessionName, - TargetSessionType = - @TargetSessionType, - VictimsOnly = - @VictimsOnly, - Debug = - @Debug, - Help = - @Help, - Version = - @Version, - VersionDate = - @VersionDate, - VersionCheckMode = - @VersionCheckMode, - OutputDatabaseName = - @OutputDatabaseName, - OutputSchemaName = - @OutputSchemaName, - OutputTableName = - @OutputTableName, - ExportToExcel = - @ExportToExcel; - - SELECT - declared_variables = - 'declared_variables', - DatabaseId = - @DatabaseId, - StartDateUTC = - @StartDateUTC, - EndDateUTC = - @EndDateUTC, - ProductVersion = - @ProductVersion, - ProductVersionMajor = - @ProductVersionMajor, - ProductVersionMinor = - @ProductVersionMinor, - ObjectFullName = - @ObjectFullName, - Azure = - @Azure, - RDS = - @RDS, - d = - @d, - StringToExecute = - @StringToExecute, - StringToExecuteParams = - @StringToExecuteParams, - r = - @r, - OutputTableFindings = - @OutputTableFindings, - DeadlockCount = - @DeadlockCount, - ServerName = - @ServerName, - OutputDatabaseCheck = - @OutputDatabaseCheck, - SessionId = - @SessionId, - TargetSessionId = - @TargetSessionId, - FileName = - @FileName, - inputbuf_bom = - @inputbuf_bom, - deadlock_result = - @deadlock_result; - END; /*End debug*/ - END; /*Final End*/ -GO -IF OBJECT_ID('dbo.sp_BlitzWho') IS NULL - EXEC ('CREATE PROCEDURE dbo.sp_BlitzWho AS RETURN 0;') -GO - -ALTER PROCEDURE dbo.sp_BlitzWho - @Help TINYINT = 0 , - @ShowSleepingSPIDs TINYINT = 0, - @ExpertMode BIT = 0, - @Debug BIT = 0, - @OutputDatabaseName NVARCHAR(256) = NULL , - @OutputSchemaName NVARCHAR(256) = NULL , - @OutputTableName NVARCHAR(256) = NULL , - @OutputTableRetentionDays TINYINT = 3 , - @MinElapsedSeconds INT = 0 , - @MinCPUTime INT = 0 , - @MinLogicalReads INT = 0 , - @MinPhysicalReads INT = 0 , - @MinWrites INT = 0 , - @MinTempdbMB INT = 0 , - @MinRequestedMemoryKB INT = 0 , - @MinBlockingSeconds INT = 0 , - @CheckDateOverride DATETIMEOFFSET = NULL, - @ShowActualParameters BIT = 0, - @GetOuterCommand BIT = 0, - @GetLiveQueryPlan BIT = 0, - @Version VARCHAR(30) = NULL OUTPUT, - @VersionDate DATETIME = NULL OUTPUT, - @VersionCheckMode BIT = 0, - @SortOrder NVARCHAR(256) = N'elapsed time' -AS -BEGIN - SET NOCOUNT ON; - SET STATISTICS XML OFF; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - - SELECT @Version = '8.19', @VersionDate = '20240222'; - - IF(@VersionCheckMode = 1) - BEGIN - RETURN; - END; - - - - IF @Help = 1 - BEGIN - PRINT ' -sp_BlitzWho from http://FirstResponderKit.org - -This script gives you a snapshot of everything currently executing on your SQL Server. - -To learn more, visit http://FirstResponderKit.org where you can download new -versions for free, watch training videos on how it works, get more info on -the findings, contribute your own code, and more. - -Known limitations of this version: - - Only Microsoft-supported versions of SQL Server. Sorry, 2005 and 2000. - - Outputting to table is only supported with SQL Server 2012 and higher. - - If @OutputDatabaseName and @OutputSchemaName are populated, the database and - schema must already exist. We will not create them, only the table. - -MIT License - -Copyright (c) Brent Ozar Unlimited - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -'; -RETURN; -END; /* @Help = 1 */ - -/* Get the major and minor build numbers */ -DECLARE @ProductVersion NVARCHAR(128) - ,@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]) - ,@EnhanceFlag BIT = 0 - ,@BlockingCheck NVARCHAR(MAX) - ,@StringToSelect NVARCHAR(MAX) - ,@StringToExecute NVARCHAR(MAX) - ,@OutputTableCleanupDate DATE - ,@SessionWaits BIT = 0 - ,@SessionWaitsSQL NVARCHAR(MAX) = - N'LEFT JOIN ( SELECT DISTINCT - wait.session_id , - ( SELECT TOP 5 waitwait.wait_type + N'' ('' - + CAST(MAX(waitwait.wait_time_ms) AS NVARCHAR(128)) - + N'' ms), '' - FROM sys.dm_exec_session_wait_stats AS waitwait - WHERE waitwait.session_id = wait.session_id - GROUP BY waitwait.wait_type - HAVING SUM(waitwait.wait_time_ms) > 5 - ORDER BY 1 - FOR - XML PATH('''') ) AS session_wait_info - FROM sys.dm_exec_session_wait_stats AS wait ) AS wt2 - ON s.session_id = wt2.session_id - LEFT JOIN sys.dm_exec_query_stats AS session_stats - ON r.sql_handle = session_stats.sql_handle - AND r.plan_handle = session_stats.plan_handle - AND r.statement_start_offset = session_stats.statement_start_offset - AND r.statement_end_offset = session_stats.statement_end_offset' - ,@ObjectFullName NVARCHAR(2000) - ,@OutputTableNameQueryStats_View NVARCHAR(256) - ,@LineFeed NVARCHAR(MAX) /* Had to set as MAX up from 10 as it was truncating the view creation*/; - -/* 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) - -SELECT - @OutputTableNameQueryStats_View = QUOTENAME(@OutputTableName + '_Deltas'), - @OutputDatabaseName = QUOTENAME(@OutputDatabaseName), - @OutputSchemaName = QUOTENAME(@OutputSchemaName), - @OutputTableName = QUOTENAME(@OutputTableName), - @LineFeed = CHAR(13) + CHAR(10); - -IF @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @OutputTableName IS NOT NULL - AND EXISTS ( SELECT * - FROM sys.databases - WHERE QUOTENAME([name]) = @OutputDatabaseName) - BEGIN - SET @ExpertMode = 1; /* Force ExpertMode when we're logging to table */ - - /* Create the table if it doesn't exist */ - SET @StringToExecute = N'USE ' - + @OutputDatabaseName - + N'; IF EXISTS(SELECT * FROM ' - + @OutputDatabaseName - + N'.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' - + @OutputSchemaName - + N''') AND NOT EXISTS (SELECT * FROM ' - + @OutputDatabaseName - + N'.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''' - + @OutputSchemaName + N''' AND QUOTENAME(TABLE_NAME) = ''' - + @OutputTableName + N''') CREATE TABLE ' - + @OutputSchemaName + N'.' - + @OutputTableName - + N'('; - SET @StringToExecute = @StringToExecute + N' - ID INT IDENTITY(1,1) NOT NULL, - ServerName NVARCHAR(128) NOT NULL, - CheckDate DATETIMEOFFSET NOT NULL, - [elapsed_time] [varchar](41) NULL, - [session_id] [smallint] NOT NULL, - [database_name] [nvarchar](128) NULL, - [query_text] [nvarchar](max) NULL, - [outer_command] NVARCHAR(4000) NULL, - [query_plan] [xml] NULL, - [live_query_plan] [xml] NULL, - [cached_parameter_info] [nvarchar](max) NULL, - [live_parameter_info] [nvarchar](max) NULL, - [query_cost] [float] NULL, - [status] [nvarchar](30) NOT NULL, - [wait_info] [nvarchar](max) NULL, - [wait_resource] [nvarchar](max) NULL, - [top_session_waits] [nvarchar](max) NULL, - [blocking_session_id] [smallint] NULL, - [open_transaction_count] [int] NULL, - [is_implicit_transaction] [int] NOT NULL, - [nt_domain] [nvarchar](128) NULL, - [host_name] [nvarchar](128) NULL, - [login_name] [nvarchar](128) NOT NULL, - [nt_user_name] [nvarchar](128) NULL, - [program_name] [nvarchar](128) NULL, - [fix_parameter_sniffing] [nvarchar](150) NULL, - [client_interface_name] [nvarchar](32) NULL, - [login_time] [datetime] NOT NULL, - [start_time] [datetime] NULL, - [request_time] [datetime] NULL, - [request_cpu_time] [int] NULL, - [request_logical_reads] [bigint] NULL, - [request_writes] [bigint] NULL, - [request_physical_reads] [bigint] NULL, - [session_cpu] [int] NOT NULL, - [session_logical_reads] [bigint] NOT NULL, - [session_physical_reads] [bigint] NOT NULL, - [session_writes] [bigint] NOT NULL, - [tempdb_allocations_mb] [decimal](38, 2) NULL, - [memory_usage] [int] NOT NULL, - [estimated_completion_time] [bigint] NULL, - [percent_complete] [real] NULL, - [deadlock_priority] [int] NULL, - [transaction_isolation_level] [varchar](33) NOT NULL, - [degree_of_parallelism] [smallint] NULL, - [last_dop] [bigint] NULL, - [min_dop] [bigint] NULL, - [max_dop] [bigint] NULL, - [last_grant_kb] [bigint] NULL, - [min_grant_kb] [bigint] NULL, - [max_grant_kb] [bigint] NULL, - [last_used_grant_kb] [bigint] NULL, - [min_used_grant_kb] [bigint] NULL, - [max_used_grant_kb] [bigint] NULL, - [last_ideal_grant_kb] [bigint] NULL, - [min_ideal_grant_kb] [bigint] NULL, - [max_ideal_grant_kb] [bigint] NULL, - [last_reserved_threads] [bigint] NULL, - [min_reserved_threads] [bigint] NULL, - [max_reserved_threads] [bigint] NULL, - [last_used_threads] [bigint] NULL, - [min_used_threads] [bigint] NULL, - [max_used_threads] [bigint] NULL, - [grant_time] [varchar](20) NULL, - [requested_memory_kb] [bigint] NULL, - [grant_memory_kb] [bigint] NULL, - [is_request_granted] [varchar](39) NOT NULL, - [required_memory_kb] [bigint] NULL, - [query_memory_grant_used_memory_kb] [bigint] NULL, - [ideal_memory_kb] [bigint] NULL, - [is_small] [bit] NULL, - [timeout_sec] [int] NULL, - [resource_semaphore_id] [smallint] NULL, - [wait_order] [varchar](20) NULL, - [wait_time_ms] [varchar](20) NULL, - [next_candidate_for_memory_grant] [varchar](3) NOT NULL, - [target_memory_kb] [bigint] NULL, - [max_target_memory_kb] [varchar](30) NULL, - [total_memory_kb] [bigint] NULL, - [available_memory_kb] [bigint] NULL, - [granted_memory_kb] [bigint] NULL, - [query_resource_semaphore_used_memory_kb] [bigint] NULL, - [grantee_count] [int] NULL, - [waiter_count] [int] NULL, - [timeout_error_count] [bigint] NULL, - [forced_grant_count] [varchar](30) NULL, - [workload_group_name] [sysname] NULL, - [resource_pool_name] [sysname] NULL, - [context_info] [varchar](128) NULL, - [query_hash] [binary](8) NULL, - [query_plan_hash] [binary](8) NULL, - [sql_handle] [varbinary] (64) NULL, - [plan_handle] [varbinary] (64) NULL, - [statement_start_offset] INT NULL, - [statement_end_offset] INT NULL, - JoinKey AS ServerName + CAST(CheckDate AS NVARCHAR(50)), - PRIMARY KEY CLUSTERED (ID ASC));'; - IF @Debug = 1 - BEGIN - PRINT CONVERT(VARCHAR(8000), SUBSTRING(@StringToExecute, 0, 8000)) - PRINT CONVERT(VARCHAR(8000), SUBSTRING(@StringToExecute, 8000, 16000)) - END - EXEC(@StringToExecute); - - /* If the table doesn't have the new JoinKey computed column, add it. See Github #2162. */ - SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableName; - SET @StringToExecute = N'IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns - WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''JoinKey'') - ALTER TABLE ' + @ObjectFullName + N' ADD JoinKey AS ServerName + CAST(CheckDate AS NVARCHAR(50));'; - EXEC(@StringToExecute); - - /* If the table doesn't have the new cached_parameter_info computed column, add it. See Github #2842. */ - SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableName; - SET @StringToExecute = N'IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns - WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''cached_parameter_info'') - ALTER TABLE ' + @ObjectFullName + N' ADD cached_parameter_info NVARCHAR(MAX) NULL;'; - EXEC(@StringToExecute); - - /* If the table doesn't have the new live_parameter_info computed column, add it. See Github #2842. */ - SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableName; - SET @StringToExecute = N'IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns - WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''live_parameter_info'') - ALTER TABLE ' + @ObjectFullName + N' ADD live_parameter_info NVARCHAR(MAX) NULL;'; - EXEC(@StringToExecute); - - /* If the table doesn't have the new outer_command column, add it. See Github #2887. */ - SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableName; - SET @StringToExecute = N'IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns - WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''outer_command'') - ALTER TABLE ' + @ObjectFullName + N' ADD outer_command NVARCHAR(4000) NULL;'; - EXEC(@StringToExecute); - - /* If the table doesn't have the new wait_resource column, add it. See Github #2970. */ - SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableName; - SET @StringToExecute = N'IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns - WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''wait_resource'') - ALTER TABLE ' + @ObjectFullName + N' ADD wait_resource NVARCHAR(MAX) NULL;'; - EXEC(@StringToExecute); - - /* Delete history older than @OutputTableRetentionDays */ - SET @OutputTableCleanupDate = CAST( (DATEADD(DAY, -1 * @OutputTableRetentionDays, GETDATE() ) ) AS DATE); - SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' - + @OutputDatabaseName - + N'.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' - + @OutputSchemaName + N''') DELETE ' - + @OutputDatabaseName + '.' - + @OutputSchemaName + '.' - + @OutputTableName - + N' WHERE ServerName = @SrvName AND CheckDate < @CheckDate;'; - IF @Debug = 1 - BEGIN - PRINT CONVERT(VARCHAR(8000), SUBSTRING(@StringToExecute, 0, 8000)) - PRINT CONVERT(VARCHAR(8000), SUBSTRING(@StringToExecute, 8000, 16000)) - END - EXEC sp_executesql @StringToExecute, - N'@SrvName NVARCHAR(128), @CheckDate date', - @@SERVERNAME, @OutputTableCleanupDate; - - SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableNameQueryStats_View; - - /* Create the view */ - IF OBJECT_ID(@ObjectFullName) IS NULL - BEGIN - SET @StringToExecute = N'USE ' - + @OutputDatabaseName - + N'; EXEC (''CREATE VIEW ' - + @OutputSchemaName + '.' - + @OutputTableNameQueryStats_View + N' AS ' + @LineFeed - + N'WITH MaxQueryDuration AS ' + @LineFeed - + N'( ' + @LineFeed - + N' SELECT ' + @LineFeed - + N' MIN([ID]) AS [MinID], ' + @LineFeed - + N' MAX([ID]) AS [MaxID] ' + @LineFeed - + N' FROM ' + @OutputSchemaName + '.' + @OutputTableName + '' + @LineFeed - + N' GROUP BY [ServerName], ' + @LineFeed - + N' [session_id], ' + @LineFeed - + N' [database_name], ' + @LineFeed - + N' [request_time], ' + @LineFeed - + N' [start_time], ' + @LineFeed - + N' [sql_handle] ' + @LineFeed - + N') ' + @LineFeed - + N'SELECT ' + @LineFeed - + N' [ID], ' + @LineFeed - + N' [ServerName], ' + @LineFeed - + N' [CheckDate], ' + @LineFeed - + N' [elapsed_time], ' + @LineFeed - + N' [session_id], ' + @LineFeed - + N' [database_name], ' + @LineFeed - + N' [query_text_snippet], ' + @LineFeed - + N' [query_plan], ' + @LineFeed - + N' [live_query_plan], ' + @LineFeed - + N' [query_cost], ' + @LineFeed - + N' [status], ' + @LineFeed - + N' [wait_info], ' + @LineFeed - + N' [wait_resource], ' + @LineFeed - + N' [top_session_waits], ' + @LineFeed - + N' [blocking_session_id], ' + @LineFeed - + N' [open_transaction_count], ' + @LineFeed - + N' [is_implicit_transaction], ' + @LineFeed - + N' [nt_domain], ' + @LineFeed - + N' [host_name], ' + @LineFeed - + N' [login_name], ' + @LineFeed - + N' [nt_user_name], ' + @LineFeed - + N' [program_name], ' + @LineFeed - + N' [fix_parameter_sniffing], ' + @LineFeed - + N' [client_interface_name], ' + @LineFeed - + N' [login_time], ' + @LineFeed - + N' [start_time], ' + @LineFeed - + N' [request_time], ' + @LineFeed - + N' [request_cpu_time], ' + @LineFeed - + N' [degree_of_parallelism], ' + @LineFeed - + N' [request_logical_reads], ' + @LineFeed - + N' [Logical_Reads_MB], ' + @LineFeed - + N' [request_writes], ' + @LineFeed - + N' [Logical_Writes_MB], ' + @LineFeed - + N' [request_physical_reads], ' + @LineFeed - + N' [Physical_reads_MB], ' + @LineFeed - + N' [session_cpu], ' + @LineFeed - + N' [session_logical_reads], ' + @LineFeed - + N' [session_logical_reads_MB], ' + @LineFeed - + N' [session_physical_reads], ' + @LineFeed - + N' [session_physical_reads_MB], ' + @LineFeed - + N' [session_writes], ' + @LineFeed - + N' [session_writes_MB], ' + @LineFeed - + N' [tempdb_allocations_mb], ' + @LineFeed - + N' [memory_usage], ' + @LineFeed - + N' [estimated_completion_time], ' + @LineFeed - + N' [percent_complete], ' + @LineFeed - + N' [deadlock_priority], ' + @LineFeed - + N' [transaction_isolation_level], ' + @LineFeed - + N' [last_dop], ' + @LineFeed - + N' [min_dop], ' + @LineFeed - + N' [max_dop], ' + @LineFeed - + N' [last_grant_kb], ' + @LineFeed - + N' [min_grant_kb], ' + @LineFeed - + N' [max_grant_kb], ' + @LineFeed - + N' [last_used_grant_kb], ' + @LineFeed - + N' [min_used_grant_kb], ' + @LineFeed - + N' [max_used_grant_kb], ' + @LineFeed - + N' [last_ideal_grant_kb], ' + @LineFeed - + N' [min_ideal_grant_kb], ' + @LineFeed - + N' [max_ideal_grant_kb], ' + @LineFeed - + N' [last_reserved_threads], ' + @LineFeed - + N' [min_reserved_threads], ' + @LineFeed - + N' [max_reserved_threads], ' + @LineFeed - + N' [last_used_threads], ' + @LineFeed - + N' [min_used_threads], ' + @LineFeed - + N' [max_used_threads], ' + @LineFeed - + N' [grant_time], ' + @LineFeed - + N' [requested_memory_kb], ' + @LineFeed - + N' [grant_memory_kb], ' + @LineFeed - + N' [is_request_granted], ' + @LineFeed - + N' [required_memory_kb], ' + @LineFeed - + N' [query_memory_grant_used_memory_kb], ' + @LineFeed - + N' [ideal_memory_kb], ' + @LineFeed - + N' [is_small], ' + @LineFeed - + N' [timeout_sec], ' + @LineFeed - + N' [resource_semaphore_id], ' + @LineFeed - + N' [wait_order], ' + @LineFeed - + N' [wait_time_ms], ' + @LineFeed - + N' [next_candidate_for_memory_grant], ' + @LineFeed - + N' [target_memory_kb], ' + @LineFeed - + N' [max_target_memory_kb], ' + @LineFeed - + N' [total_memory_kb], ' + @LineFeed - + N' [available_memory_kb], ' + @LineFeed - + N' [granted_memory_kb], ' + @LineFeed - + N' [query_resource_semaphore_used_memory_kb], ' + @LineFeed - + N' [grantee_count], ' + @LineFeed - + N' [waiter_count], ' + @LineFeed - + N' [timeout_error_count], ' + @LineFeed - + N' [forced_grant_count], ' + @LineFeed - + N' [workload_group_name], ' + @LineFeed - + N' [resource_pool_name], ' + @LineFeed - + N' [context_info], ' + @LineFeed - + N' [query_hash], ' + @LineFeed - + N' [query_plan_hash], ' + @LineFeed - + N' [sql_handle], ' + @LineFeed - + N' [plan_handle], ' + @LineFeed - + N' [statement_start_offset], ' + @LineFeed - + N' [statement_end_offset] ' + @LineFeed - + N' FROM ' + @LineFeed - + N' ( ' + @LineFeed - + N' SELECT ' + @LineFeed - + N' [ID], ' + @LineFeed - + N' [ServerName], ' + @LineFeed - + N' [CheckDate], ' + @LineFeed - + N' [elapsed_time], ' + @LineFeed - + N' [session_id], ' + @LineFeed - + N' [database_name], ' + @LineFeed - + N' /* Truncate the query text to aid performance of painting the rows in SSMS */ ' + @LineFeed - + N' CAST([query_text] AS NVARCHAR(1000)) AS [query_text_snippet], ' + @LineFeed - + N' [query_plan], ' + @LineFeed - + N' [live_query_plan], ' + @LineFeed - + N' [query_cost], ' + @LineFeed - + N' [status], ' + @LineFeed - + N' [wait_info], ' + @LineFeed - + N' [wait_resource], ' + @LineFeed - + N' [top_session_waits], ' + @LineFeed - + N' [blocking_session_id], ' + @LineFeed - + N' [open_transaction_count], ' + @LineFeed - + N' [is_implicit_transaction], ' + @LineFeed - + N' [nt_domain], ' + @LineFeed - + N' [host_name], ' + @LineFeed - + N' [login_name], ' + @LineFeed - + N' [nt_user_name], ' + @LineFeed - + N' [program_name], ' + @LineFeed - + N' [fix_parameter_sniffing], ' + @LineFeed - + N' [client_interface_name], ' + @LineFeed - + N' [login_time], ' + @LineFeed - + N' [start_time], ' + @LineFeed - + N' [request_time], ' + @LineFeed - + N' [request_cpu_time], ' + @LineFeed - + N' [degree_of_parallelism], ' + @LineFeed - + N' [request_logical_reads], ' + @LineFeed - + N' ((CAST([request_logical_reads] AS DECIMAL(38,2))* 8)/ 1024) [Logical_Reads_MB], ' + @LineFeed - + N' [request_writes], ' + @LineFeed - + N' ((CAST([request_writes] AS DECIMAL(38,2))* 8)/ 1024) [Logical_Writes_MB], ' + @LineFeed - + N' [request_physical_reads], ' + @LineFeed - + N' ((CAST([request_physical_reads] AS DECIMAL(38,2))* 8)/ 1024) [Physical_reads_MB], ' + @LineFeed - + N' [session_cpu], ' + @LineFeed - + N' [session_logical_reads], ' + @LineFeed - + N' ((CAST([session_logical_reads] AS DECIMAL(38,2))* 8)/ 1024) [session_logical_reads_MB], ' + @LineFeed - + N' [session_physical_reads], ' + @LineFeed - + N' ((CAST([session_physical_reads] AS DECIMAL(38,2))* 8)/ 1024) [session_physical_reads_MB], ' + @LineFeed - + N' [session_writes], ' + @LineFeed - + N' ((CAST([session_writes] AS DECIMAL(38,2))* 8)/ 1024) [session_writes_MB], ' + @LineFeed - + N' [tempdb_allocations_mb], ' + @LineFeed - + N' [memory_usage], ' + @LineFeed - + N' [estimated_completion_time], ' + @LineFeed - + N' [percent_complete], ' + @LineFeed - + N' [deadlock_priority], ' + @LineFeed - + N' [transaction_isolation_level], ' + @LineFeed - + N' [last_dop], ' + @LineFeed - + N' [min_dop], ' + @LineFeed - + N' [max_dop], ' + @LineFeed - + N' [last_grant_kb], ' + @LineFeed - + N' [min_grant_kb], ' + @LineFeed - + N' [max_grant_kb], ' + @LineFeed - + N' [last_used_grant_kb], ' + @LineFeed - + N' [min_used_grant_kb], ' + @LineFeed - + N' [max_used_grant_kb], ' + @LineFeed - + N' [last_ideal_grant_kb], ' + @LineFeed - + N' [min_ideal_grant_kb], ' + @LineFeed - + N' [max_ideal_grant_kb], ' + @LineFeed - + N' [last_reserved_threads], ' + @LineFeed - + N' [min_reserved_threads], ' + @LineFeed - + N' [max_reserved_threads], ' + @LineFeed - + N' [last_used_threads], ' + @LineFeed - + N' [min_used_threads], ' + @LineFeed - + N' [max_used_threads], ' + @LineFeed - + N' [grant_time], ' + @LineFeed - + N' [requested_memory_kb], ' + @LineFeed - + N' [grant_memory_kb], ' + @LineFeed - + N' [is_request_granted], ' + @LineFeed - + N' [required_memory_kb], ' + @LineFeed - + N' [query_memory_grant_used_memory_kb], ' + @LineFeed - + N' [ideal_memory_kb], ' + @LineFeed - + N' [is_small], ' + @LineFeed - + N' [timeout_sec], ' + @LineFeed - + N' [resource_semaphore_id], ' + @LineFeed - + N' [wait_order], ' + @LineFeed - + N' [wait_time_ms], ' + @LineFeed - + N' [next_candidate_for_memory_grant], ' + @LineFeed - + N' [target_memory_kb], ' + @LineFeed - + N' [max_target_memory_kb], ' + @LineFeed - + N' [total_memory_kb], ' + @LineFeed - + N' [available_memory_kb], ' + @LineFeed - + N' [granted_memory_kb], ' + @LineFeed - + N' [query_resource_semaphore_used_memory_kb], ' + @LineFeed - + N' [grantee_count], ' + @LineFeed - + N' [waiter_count], ' + @LineFeed - + N' [timeout_error_count], ' + @LineFeed - + N' [forced_grant_count], ' + @LineFeed - + N' [workload_group_name], ' + @LineFeed - + N' [resource_pool_name], ' + @LineFeed - + N' [context_info], ' + @LineFeed - + N' [query_hash], ' + @LineFeed - + N' [query_plan_hash], ' + @LineFeed - + N' [sql_handle], ' + @LineFeed - + N' [plan_handle], ' + @LineFeed - + N' [statement_start_offset], ' + @LineFeed - + N' [statement_end_offset] ' + @LineFeed - + N' FROM ' + @OutputSchemaName + '.' + @OutputTableName + '' + @LineFeed - + N' ) AS [BlitzWho] ' + @LineFeed - + N'INNER JOIN [MaxQueryDuration] ON [BlitzWho].[ID] = [MaxQueryDuration].[MaxID]; ' + @LineFeed - + N''');' - - IF @Debug = 1 - BEGIN - PRINT CONVERT(VARCHAR(8000), SUBSTRING(@StringToExecute, 0, 8000)) - PRINT CONVERT(VARCHAR(8000), SUBSTRING(@StringToExecute, 8000, 16000)) - END - - EXEC(@StringToExecute); - END; - - END - - IF OBJECT_ID('tempdb..#WhoReadableDBs') IS NOT NULL - DROP TABLE #WhoReadableDBs; - -CREATE TABLE #WhoReadableDBs -( -database_id INT -); - -IF EXISTS (SELECT * FROM sys.all_objects o WHERE o.name = 'dm_hadr_database_replica_states') -BEGIN - RAISERROR('Checking for Read intent databases to exclude',0,0) WITH NOWAIT; - - EXEC('INSERT INTO #WhoReadableDBs (database_id) SELECT DBs.database_id FROM sys.databases DBs INNER JOIN sys.availability_replicas Replicas ON DBs.replica_id = Replicas.replica_id WHERE replica_server_name NOT IN (SELECT DISTINCT primary_replica FROM sys.dm_hadr_availability_group_states States) AND Replicas.secondary_role_allow_connections_desc = ''READ_ONLY'' AND replica_server_name = @@SERVERNAME;'); -END - -SELECT @BlockingCheck = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - - SET LOCK_TIMEOUT 1000; /* To avoid blocking on live query plans. See Github issue #2907. */ - DECLARE @blocked TABLE - ( - dbid SMALLINT NOT NULL, - last_batch DATETIME NOT NULL, - open_tran SMALLINT NOT NULL, - sql_handle BINARY(20) NOT NULL, - session_id SMALLINT NOT NULL, - blocking_session_id SMALLINT NOT NULL, - lastwaittype NCHAR(32) NOT NULL, - waittime BIGINT NOT NULL, - cpu INT NOT NULL, - physical_io BIGINT NOT NULL, - memusage INT NOT NULL - ); - - INSERT @blocked ( dbid, last_batch, open_tran, sql_handle, session_id, blocking_session_id, lastwaittype, waittime, cpu, physical_io, memusage ) - SELECT - sys1.dbid, sys1.last_batch, sys1.open_tran, sys1.sql_handle, - sys2.spid AS session_id, sys2.blocked AS blocking_session_id, sys2.lastwaittype, sys2.waittime, sys2.cpu, sys2.physical_io, sys2.memusage - FROM sys.sysprocesses AS sys1 - JOIN sys.sysprocesses AS sys2 - ON sys1.spid = sys2.blocked; - '+CASE - WHEN (@GetOuterCommand = 1 AND (NOT EXISTS(SELECT 1 FROM sys.all_objects WHERE [name] = N'dm_exec_input_buffer'))) THEN N' - DECLARE @session_id SMALLINT; - DECLARE @Sessions TABLE - ( - session_id INT - ); - - DECLARE @inputbuffer TABLE - ( - ID INT IDENTITY(1,1), - session_id INT, - event_type NVARCHAR(30), - parameters SMALLINT, - event_info NVARCHAR(4000) - ); - - DECLARE inputbuffer_cursor - - CURSOR LOCAL FAST_FORWARD - FOR - SELECT session_id - FROM sys.dm_exec_sessions - WHERE session_id <> @@SPID - AND is_user_process = 1; - - OPEN inputbuffer_cursor; - - FETCH NEXT FROM inputbuffer_cursor INTO @session_id; - - WHILE (@@FETCH_STATUS = 0) - BEGIN; - BEGIN TRY; - - INSERT INTO @inputbuffer ([event_type],[parameters],[event_info]) - EXEC sp_executesql - N''DBCC INPUTBUFFER(@session_id) WITH NO_INFOMSGS;'', - N''@session_id SMALLINT'', - @session_id; - - UPDATE @inputbuffer - SET session_id = @session_id - WHERE ID = SCOPE_IDENTITY(); - - END TRY - BEGIN CATCH - RAISERROR(''DBCC inputbuffer failed for session %d'',0,0,@session_id) WITH NOWAIT; - END CATCH; - - FETCH NEXT FROM inputbuffer_cursor INTO @session_id - - END; - - CLOSE inputbuffer_cursor; - DEALLOCATE inputbuffer_cursor;' - ELSE N'' - END+ - N' - - DECLARE @LiveQueryPlans TABLE - ( - Session_Id INT NOT NULL, - Query_Plan XML NOT NULL - ); - - ' -IF EXISTS (SELECT * FROM sys.all_columns WHERE object_id = OBJECT_ID('sys.dm_exec_query_statistics_xml') AND name = 'query_plan' AND @GetLiveQueryPlan=1) -BEGIN - SET @BlockingCheck = @BlockingCheck + N' - INSERT INTO @LiveQueryPlans - SELECT s.session_id, query_plan - FROM sys.dm_exec_sessions AS s - CROSS APPLY sys.dm_exec_query_statistics_xml(s.session_id) - WHERE s.session_id <> @@SPID;'; -END - - -IF @ProductVersionMajor > 9 and @ProductVersionMajor < 11 -BEGIN - /* Think of the StringToExecute as starting with this, but we'll set this up later depending on whether we're doing an insert or a select: - SELECT @StringToExecute = N'SELECT GETDATE() AS run_date , - */ - SET @StringToExecute = N' CASE WHEN YEAR(s.last_request_start_time) = 1900 THEN NULL ELSE COALESCE( RIGHT(''00'' + CONVERT(VARCHAR(20), (ABS(r.total_elapsed_time) / 1000) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), (DATEADD(SECOND, (r.total_elapsed_time / 1000), 0) + DATEADD(MILLISECOND, (r.total_elapsed_time % 1000), 0)), 114), RIGHT(''00'' + CONVERT(VARCHAR(20), DATEDIFF(SECOND, s.last_request_start_time, GETDATE()) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), DATEADD(SECOND, DATEDIFF(SECOND, s.last_request_start_time, GETDATE()), 0), 114) ) END AS [elapsed_time] , - s.session_id , - CASE WHEN r.blocking_session_id <> 0 AND blocked.session_id IS NULL - THEN r.blocking_session_id - WHEN r.blocking_session_id <> 0 AND s.session_id <> blocked.blocking_session_id - THEN blocked.blocking_session_id - WHEN r.blocking_session_id = 0 AND s.session_id = blocked.session_id - THEN blocked.blocking_session_id - WHEN r.blocking_session_id <> 0 AND s.session_id = blocked.blocking_session_id - THEN r.blocking_session_id - ELSE NULL - END AS blocking_session_id, - COALESCE(DB_NAME(r.database_id), DB_NAME(blocked.dbid), ''N/A'') AS database_name, - ISNULL(SUBSTRING(dest.text, - ( r.statement_start_offset / 2 ) + 1, - ( ( CASE r.statement_end_offset - WHEN -1 THEN DATALENGTH(dest.text) - ELSE r.statement_end_offset - END - r.statement_start_offset ) - / 2 ) + 1), dest.text) AS query_text , - '+CASE - WHEN @GetOuterCommand = 1 THEN N'CAST(event_info AS NVARCHAR(4000)) AS outer_command,' - ELSE N'' - END+N' - derp.query_plan , - qmg.query_cost , - s.status , - CASE - WHEN s.status <> ''sleeping'' THEN COALESCE(wt.wait_info, RTRIM(blocked.lastwaittype) + '' ('' + CONVERT(VARCHAR(10), blocked.waittime) + '')'' ) - ELSE NULL - END AS wait_info , - r.wait_resource , - COALESCE(r.open_transaction_count, blocked.open_tran) AS open_transaction_count , - CASE WHEN EXISTS ( SELECT 1 - FROM sys.dm_tran_active_transactions AS tat - JOIN sys.dm_tran_session_transactions AS tst - ON tst.transaction_id = tat.transaction_id - WHERE tat.name = ''implicit_transaction'' - AND s.session_id = tst.session_id - ) THEN 1 - ELSE 0 - END AS is_implicit_transaction , - s.nt_domain , - s.host_name , - s.login_name , - s.nt_user_name ,' - IF @Platform = 'NonAzure' - BEGIN - SET @StringToExecute += - N'program_name = COALESCE(( - SELECT REPLACE(program_name,Substring(program_name,30,34),''"''+j.name+''"'') - FROM msdb.dbo.sysjobs j WHERE Substring(program_name,32,32) = CONVERT(char(32),CAST(j.job_id AS binary(16)),2) - ),s.program_name)' - END - ELSE - BEGIN - SET @StringToExecute += N's.program_name' - END - - IF @ExpertMode = 1 - BEGIN - SET @StringToExecute += - N', - ''DBCC FREEPROCCACHE ('' + CONVERT(NVARCHAR(128), r.plan_handle, 1) + '');'' AS fix_parameter_sniffing, - s.client_interface_name , - s.login_time , - r.start_time , - qmg.request_time , - COALESCE(r.cpu_time, s.cpu_time) AS request_cpu_time, - COALESCE(r.logical_reads, s.logical_reads) AS request_logical_reads, - COALESCE(r.writes, s.writes) AS request_writes, - COALESCE(r.reads, s.reads) AS request_physical_reads , - s.cpu_time AS session_cpu, - s.logical_reads AS session_logical_reads, - s.reads AS session_physical_reads , - s.writes AS session_writes, - tempdb_allocations.tempdb_allocations_mb, - s.memory_usage , - r.estimated_completion_time , - r.percent_complete , - r.deadlock_priority , - CASE - WHEN s.transaction_isolation_level = 0 THEN ''Unspecified'' - WHEN s.transaction_isolation_level = 1 THEN ''Read Uncommitted'' - WHEN s.transaction_isolation_level = 2 AND EXISTS (SELECT 1 FROM sys.databases WHERE name = DB_NAME(r.database_id) AND is_read_committed_snapshot_on = 1) THEN ''Read Committed Snapshot Isolation'' - WHEN s.transaction_isolation_level = 2 THEN ''Read Committed'' - WHEN s.transaction_isolation_level = 3 THEN ''Repeatable Read'' - WHEN s.transaction_isolation_level = 4 THEN ''Serializable'' - WHEN s.transaction_isolation_level = 5 THEN ''Snapshot'' - ELSE ''WHAT HAVE YOU DONE?'' - END AS transaction_isolation_level , - qmg.dop AS degree_of_parallelism , - COALESCE(CAST(qmg.grant_time AS VARCHAR(20)), ''N/A'') AS grant_time , - qmg.requested_memory_kb , - qmg.granted_memory_kb AS grant_memory_kb, - CASE WHEN qmg.grant_time IS NULL THEN ''N/A'' - WHEN qmg.requested_memory_kb < qmg.granted_memory_kb - THEN ''Query Granted Less Than Query Requested'' - ELSE ''Memory Request Granted'' - END AS is_request_granted , - qmg.required_memory_kb , - qmg.used_memory_kb AS query_memory_grant_used_memory_kb, - qmg.ideal_memory_kb , - qmg.is_small , - qmg.timeout_sec , - qmg.resource_semaphore_id , - COALESCE(CAST(qmg.wait_order AS VARCHAR(20)), ''N/A'') AS wait_order , - COALESCE(CAST(qmg.wait_time_ms AS VARCHAR(20)), - ''N/A'') AS wait_time_ms , - CASE qmg.is_next_candidate - WHEN 0 THEN ''No'' - WHEN 1 THEN ''Yes'' - ELSE ''N/A'' - END AS next_candidate_for_memory_grant , - qrs.target_memory_kb , - COALESCE(CAST(qrs.max_target_memory_kb AS VARCHAR(20)), - ''Small Query Resource Semaphore'') AS max_target_memory_kb , - qrs.total_memory_kb , - qrs.available_memory_kb , - qrs.granted_memory_kb , - qrs.used_memory_kb AS query_resource_semaphore_used_memory_kb, - qrs.grantee_count , - qrs.waiter_count , - qrs.timeout_error_count , - COALESCE(CAST(qrs.forced_grant_count AS VARCHAR(20)), - ''Small Query Resource Semaphore'') AS forced_grant_count, - wg.name AS workload_group_name , - rp.name AS resource_pool_name, - CONVERT(VARCHAR(128), r.context_info) AS context_info - ' - END /* IF @ExpertMode = 1 */ - - SET @StringToExecute += - N'FROM sys.dm_exec_sessions AS s - '+ - CASE - WHEN @GetOuterCommand = 1 THEN CASE - WHEN EXISTS(SELECT 1 FROM sys.all_objects WHERE [name] = N'dm_exec_input_buffer') THEN N'OUTER APPLY sys.dm_exec_input_buffer (s.session_id, 0) AS ib' - ELSE N'LEFT JOIN @inputbuffer ib ON s.session_id = ib.session_id' - END - ELSE N'' - END+N' - LEFT JOIN sys.dm_exec_requests AS r - ON r.session_id = s.session_id - LEFT JOIN ( SELECT DISTINCT - wait.session_id , - ( SELECT waitwait.wait_type + N'' ('' - + CAST(MAX(waitwait.wait_duration_ms) AS NVARCHAR(128)) - + N'' ms) '' - FROM sys.dm_os_waiting_tasks AS waitwait - WHERE waitwait.session_id = wait.session_id - GROUP BY waitwait.wait_type - ORDER BY SUM(waitwait.wait_duration_ms) DESC - FOR - XML PATH('''') ) AS wait_info - FROM sys.dm_os_waiting_tasks AS wait ) AS wt - ON s.session_id = wt.session_id - LEFT JOIN sys.dm_exec_query_stats AS query_stats - ON r.sql_handle = query_stats.sql_handle - AND r.plan_handle = query_stats.plan_handle - AND r.statement_start_offset = query_stats.statement_start_offset - AND r.statement_end_offset = query_stats.statement_end_offset - LEFT JOIN sys.dm_exec_query_memory_grants qmg - ON r.session_id = qmg.session_id - AND r.request_id = qmg.request_id - LEFT JOIN sys.dm_exec_query_resource_semaphores qrs - ON qmg.resource_semaphore_id = qrs.resource_semaphore_id - AND qmg.pool_id = qrs.pool_id - LEFT JOIN sys.resource_governor_workload_groups wg - ON s.group_id = wg.group_id - LEFT JOIN sys.resource_governor_resource_pools rp - ON wg.pool_id = rp.pool_id - OUTER APPLY ( - SELECT TOP 1 - b.dbid, b.last_batch, b.open_tran, b.sql_handle, - b.session_id, b.blocking_session_id, b.lastwaittype, b.waittime - FROM @blocked b - WHERE (s.session_id = b.session_id - OR s.session_id = b.blocking_session_id) - ) AS blocked - OUTER APPLY sys.dm_exec_sql_text(COALESCE(r.sql_handle, blocked.sql_handle)) AS dest - OUTER APPLY sys.dm_exec_query_plan(r.plan_handle) AS derp - OUTER APPLY ( - SELECT CONVERT(DECIMAL(38,2), SUM( ((((tsu.user_objects_alloc_page_count - user_objects_dealloc_page_count) + (tsu.internal_objects_alloc_page_count - internal_objects_dealloc_page_count)) * 8) / 1024.)) ) AS tempdb_allocations_mb - FROM sys.dm_db_task_space_usage tsu - WHERE tsu.request_id = r.request_id - AND tsu.session_id = r.session_id - AND tsu.session_id = s.session_id - ) as tempdb_allocations - WHERE s.session_id <> @@SPID - AND s.host_name IS NOT NULL - ' - + CASE WHEN @ShowSleepingSPIDs = 0 THEN - N' AND COALESCE(DB_NAME(r.database_id), DB_NAME(blocked.dbid)) IS NOT NULL' - WHEN @ShowSleepingSPIDs = 1 THEN - N' OR COALESCE(r.open_transaction_count, blocked.open_tran) >= 1' - ELSE N'' END; -END /* IF @ProductVersionMajor > 9 and @ProductVersionMajor < 11 */ - -IF @ProductVersionMajor >= 11 - BEGIN - SELECT @EnhanceFlag = - CASE WHEN @ProductVersionMajor = 11 AND @ProductVersionMinor >= 6020 THEN 1 - WHEN @ProductVersionMajor = 12 AND @ProductVersionMinor >= 5000 THEN 1 - WHEN @ProductVersionMajor = 13 AND @ProductVersionMinor >= 1601 THEN 1 - WHEN @ProductVersionMajor > 13 THEN 1 - ELSE 0 - END - - - IF OBJECT_ID('sys.dm_exec_session_wait_stats') IS NOT NULL - BEGIN - SET @SessionWaits = 1 - END - - /* Think of the StringToExecute as starting with this, but we'll set this up later depending on whether we're doing an insert or a select: - SELECT @StringToExecute = N'SELECT GETDATE() AS run_date , - */ - SELECT @StringToExecute = N' CASE WHEN YEAR(s.last_request_start_time) = 1900 THEN NULL ELSE COALESCE( RIGHT(''00'' + CONVERT(VARCHAR(20), (ABS(r.total_elapsed_time) / 1000) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), (DATEADD(SECOND, (r.total_elapsed_time / 1000), 0) + DATEADD(MILLISECOND, (r.total_elapsed_time % 1000), 0)), 114), RIGHT(''00'' + CONVERT(VARCHAR(20), DATEDIFF(SECOND, s.last_request_start_time, GETDATE()) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), DATEADD(SECOND, DATEDIFF(SECOND, s.last_request_start_time, GETDATE()), 0), 114) ) END AS [elapsed_time] , - s.session_id , - CASE WHEN r.blocking_session_id <> 0 AND blocked.session_id IS NULL - THEN r.blocking_session_id - WHEN r.blocking_session_id <> 0 AND s.session_id <> blocked.blocking_session_id - THEN blocked.blocking_session_id - WHEN r.blocking_session_id = 0 AND s.session_id = blocked.session_id - THEN blocked.blocking_session_id - WHEN r.blocking_session_id <> 0 AND s.session_id = blocked.blocking_session_id - THEN r.blocking_session_id - ELSE NULL - END AS blocking_session_id, - COALESCE(DB_NAME(r.database_id), DB_NAME(blocked.dbid), ''N/A'') AS database_name, - ISNULL(SUBSTRING(dest.text, - ( r.statement_start_offset / 2 ) + 1, - ( ( CASE r.statement_end_offset - WHEN -1 THEN DATALENGTH(dest.text) - ELSE r.statement_end_offset - END - r.statement_start_offset ) - / 2 ) + 1), dest.text) AS query_text , - '+CASE - WHEN @GetOuterCommand = 1 THEN N'CAST(event_info AS NVARCHAR(4000)) AS outer_command,' - ELSE N'' - END+N' - derp.query_plan , - CAST(COALESCE(qs_live.Query_Plan, ' + CASE WHEN @GetLiveQueryPlan=1 - THEN '''''' - ELSE '''''' - END - +') AS XML - - - ) AS live_query_plan , - STUFF((SELECT DISTINCT N'', '' + Node.Data.value(''(@Column)[1]'', ''NVARCHAR(4000)'') + N'' {'' + Node.Data.value(''(@ParameterDataType)[1]'', ''NVARCHAR(4000)'') + N''}: '' + Node.Data.value(''(@ParameterCompiledValue)[1]'', ''NVARCHAR(4000)'') - FROM derp.query_plan.nodes(''/*:ShowPlanXML/*:BatchSequence/*:Batch/*:Statements/*:StmtSimple/*:QueryPlan/*:ParameterList/*:ColumnReference'') AS Node(Data) - FOR XML PATH('''')), 1,2,'''') - AS Cached_Parameter_Info, - ' - IF @ShowActualParameters = 1 - BEGIN - SELECT @StringToExecute = @StringToExecute + N'qs_live.Live_Parameter_Info as Live_Parameter_Info,' - END - - SELECT @StringToExecute = @StringToExecute + N' - qmg.query_cost , - s.status , - CASE - WHEN s.status <> ''sleeping'' THEN COALESCE(wt.wait_info, RTRIM(blocked.lastwaittype) + '' ('' + CONVERT(VARCHAR(10), blocked.waittime) + '')'' ) - ELSE NULL - END AS wait_info , - r.wait_resource ,' - + - CASE @SessionWaits - WHEN 1 THEN + N'SUBSTRING(wt2.session_wait_info, 0, LEN(wt2.session_wait_info) ) AS top_session_waits ,' - ELSE N' NULL AS top_session_waits ,' - END - + - N'COALESCE(r.open_transaction_count, blocked.open_tran) AS open_transaction_count , - CASE WHEN EXISTS ( SELECT 1 - FROM sys.dm_tran_active_transactions AS tat - JOIN sys.dm_tran_session_transactions AS tst - ON tst.transaction_id = tat.transaction_id - WHERE tat.name = ''implicit_transaction'' - AND s.session_id = tst.session_id - ) THEN 1 - ELSE 0 - END AS is_implicit_transaction , - s.nt_domain , - s.host_name , - s.login_name , - s.nt_user_name ,' - IF @Platform = 'NonAzure' - BEGIN - SET @StringToExecute += - N'program_name = COALESCE(( - SELECT REPLACE(program_name,Substring(program_name,30,34),''"''+j.name+''"'') - FROM msdb.dbo.sysjobs j WHERE Substring(program_name,32,32) = CONVERT(char(32),CAST(j.job_id AS binary(16)),2) - ),s.program_name)' - END - ELSE - BEGIN - SET @StringToExecute += N's.program_name' - END - - IF @ExpertMode = 1 /* We show more columns in expert mode, so the SELECT gets longer */ - BEGIN - SET @StringToExecute += - N', ''DBCC FREEPROCCACHE ('' + CONVERT(NVARCHAR(128), r.plan_handle, 1) + '');'' AS fix_parameter_sniffing, - s.client_interface_name , - s.login_time , - r.start_time , - qmg.request_time , - COALESCE(r.cpu_time, s.cpu_time) AS request_cpu_time, - COALESCE(r.logical_reads, s.logical_reads) AS request_logical_reads, - COALESCE(r.writes, s.writes) AS request_writes, - COALESCE(r.reads, s.reads) AS request_physical_reads , - s.cpu_time AS session_cpu, - s.logical_reads AS session_logical_reads, - s.reads AS session_physical_reads , - s.writes AS session_writes, - tempdb_allocations.tempdb_allocations_mb, - s.memory_usage , - r.estimated_completion_time , - r.percent_complete , - r.deadlock_priority , - CASE - WHEN s.transaction_isolation_level = 0 THEN ''Unspecified'' - WHEN s.transaction_isolation_level = 1 THEN ''Read Uncommitted'' - WHEN s.transaction_isolation_level = 2 AND EXISTS (SELECT 1 FROM sys.databases WHERE name = DB_NAME(r.database_id) AND is_read_committed_snapshot_on = 1) THEN ''Read Committed Snapshot Isolation'' - WHEN s.transaction_isolation_level = 2 THEN ''Read Committed'' - WHEN s.transaction_isolation_level = 3 THEN ''Repeatable Read'' - WHEN s.transaction_isolation_level = 4 THEN ''Serializable'' - WHEN s.transaction_isolation_level = 5 THEN ''Snapshot'' - ELSE ''WHAT HAVE YOU DONE?'' - END AS transaction_isolation_level , - qmg.dop AS degree_of_parallelism , ' - + - CASE @EnhanceFlag - WHEN 1 THEN N'query_stats.last_dop, - query_stats.min_dop, - query_stats.max_dop, - query_stats.last_grant_kb, - query_stats.min_grant_kb, - query_stats.max_grant_kb, - query_stats.last_used_grant_kb, - query_stats.min_used_grant_kb, - query_stats.max_used_grant_kb, - query_stats.last_ideal_grant_kb, - query_stats.min_ideal_grant_kb, - query_stats.max_ideal_grant_kb, - query_stats.last_reserved_threads, - query_stats.min_reserved_threads, - query_stats.max_reserved_threads, - query_stats.last_used_threads, - query_stats.min_used_threads, - query_stats.max_used_threads,' - ELSE N' NULL AS last_dop, - NULL AS min_dop, - NULL AS max_dop, - NULL AS last_grant_kb, - NULL AS min_grant_kb, - NULL AS max_grant_kb, - NULL AS last_used_grant_kb, - NULL AS min_used_grant_kb, - NULL AS max_used_grant_kb, - NULL AS last_ideal_grant_kb, - NULL AS min_ideal_grant_kb, - NULL AS max_ideal_grant_kb, - NULL AS last_reserved_threads, - NULL AS min_reserved_threads, - NULL AS max_reserved_threads, - NULL AS last_used_threads, - NULL AS min_used_threads, - NULL AS max_used_threads,' - END - - SET @StringToExecute += - N' - COALESCE(CAST(qmg.grant_time AS VARCHAR(20)), ''Memory Not Granted'') AS grant_time , - qmg.requested_memory_kb , - qmg.granted_memory_kb AS grant_memory_kb, - CASE WHEN qmg.grant_time IS NULL THEN ''N/A'' - WHEN qmg.requested_memory_kb < qmg.granted_memory_kb - THEN ''Query Granted Less Than Query Requested'' - ELSE ''Memory Request Granted'' - END AS is_request_granted , - qmg.required_memory_kb , - qmg.used_memory_kb AS query_memory_grant_used_memory_kb, - qmg.ideal_memory_kb , - qmg.is_small , - qmg.timeout_sec , - qmg.resource_semaphore_id , - COALESCE(CAST(qmg.wait_order AS VARCHAR(20)), ''N/A'') AS wait_order , - COALESCE(CAST(qmg.wait_time_ms AS VARCHAR(20)), - ''N/A'') AS wait_time_ms , - CASE qmg.is_next_candidate - WHEN 0 THEN ''No'' - WHEN 1 THEN ''Yes'' - ELSE ''N/A'' - END AS next_candidate_for_memory_grant , - qrs.target_memory_kb , - COALESCE(CAST(qrs.max_target_memory_kb AS VARCHAR(20)), - ''Small Query Resource Semaphore'') AS max_target_memory_kb , - qrs.total_memory_kb , - qrs.available_memory_kb , - qrs.granted_memory_kb , - qrs.used_memory_kb AS query_resource_semaphore_used_memory_kb, - qrs.grantee_count , - qrs.waiter_count , - qrs.timeout_error_count , - COALESCE(CAST(qrs.forced_grant_count AS VARCHAR(20)), - ''Small Query Resource Semaphore'') AS forced_grant_count, - wg.name AS workload_group_name, - rp.name AS resource_pool_name, - CONVERT(VARCHAR(128), r.context_info) AS context_info, - r.query_hash, r.query_plan_hash, r.sql_handle, r.plan_handle, r.statement_start_offset, r.statement_end_offset ' - END /* IF @ExpertMode = 1 */ - - SET @StringToExecute += - N' FROM sys.dm_exec_sessions AS s'+ - CASE - WHEN @GetOuterCommand = 1 THEN CASE - WHEN EXISTS(SELECT 1 FROM sys.all_objects WHERE [name] = N'dm_exec_input_buffer') THEN N' - OUTER APPLY sys.dm_exec_input_buffer (s.session_id, 0) AS ib' - ELSE N' - LEFT JOIN @inputbuffer ib ON s.session_id = ib.session_id' - END - ELSE N'' - END+N' - LEFT JOIN sys.dm_exec_requests AS r - ON r.session_id = s.session_id - LEFT JOIN ( SELECT DISTINCT - wait.session_id , - ( SELECT waitwait.wait_type + N'' ('' - + CAST(MAX(waitwait.wait_duration_ms) AS NVARCHAR(128)) - + N'' ms) '' - FROM sys.dm_os_waiting_tasks AS waitwait - WHERE waitwait.session_id = wait.session_id - GROUP BY waitwait.wait_type - ORDER BY SUM(waitwait.wait_duration_ms) DESC - FOR - XML PATH('''') ) AS wait_info - FROM sys.dm_os_waiting_tasks AS wait ) AS wt - ON s.session_id = wt.session_id - LEFT JOIN sys.dm_exec_query_stats AS query_stats - ON r.sql_handle = query_stats.sql_handle - AND r.plan_handle = query_stats.plan_handle - AND r.statement_start_offset = query_stats.statement_start_offset - AND r.statement_end_offset = query_stats.statement_end_offset - ' - + - CASE @SessionWaits - WHEN 1 THEN @SessionWaitsSQL - ELSE N'' - END - + - N' - LEFT JOIN sys.dm_exec_query_memory_grants qmg - ON r.session_id = qmg.session_id - AND r.request_id = qmg.request_id - LEFT JOIN sys.dm_exec_query_resource_semaphores qrs - ON qmg.resource_semaphore_id = qrs.resource_semaphore_id - AND qmg.pool_id = qrs.pool_id - LEFT JOIN sys.resource_governor_workload_groups wg - ON s.group_id = wg.group_id - LEFT JOIN sys.resource_governor_resource_pools rp - ON wg.pool_id = rp.pool_id - OUTER APPLY ( - SELECT TOP 1 - b.dbid, b.last_batch, b.open_tran, b.sql_handle, - b.session_id, b.blocking_session_id, b.lastwaittype, b.waittime - FROM @blocked b - WHERE (s.session_id = b.session_id - OR s.session_id = b.blocking_session_id) - ) AS blocked - OUTER APPLY sys.dm_exec_sql_text(COALESCE(r.sql_handle, blocked.sql_handle)) AS dest - OUTER APPLY sys.dm_exec_query_plan(r.plan_handle) AS derp - OUTER APPLY ( - SELECT CONVERT(DECIMAL(38,2), SUM( ((((tsu.user_objects_alloc_page_count - user_objects_dealloc_page_count) + (tsu.internal_objects_alloc_page_count - internal_objects_dealloc_page_count)) * 8) / 1024.)) ) AS tempdb_allocations_mb - FROM sys.dm_db_task_space_usage tsu - WHERE tsu.request_id = r.request_id - AND tsu.session_id = r.session_id - AND tsu.session_id = s.session_id - ) as tempdb_allocations - - OUTER APPLY ( - SELECT TOP 1 Query_Plan, - STUFF((SELECT DISTINCT N'', '' + Node.Data.value(''(@Column)[1]'', ''NVARCHAR(4000)'') + N'' {'' + Node.Data.value(''(@ParameterDataType)[1]'', ''NVARCHAR(4000)'') + N''}: '' + Node.Data.value(''(@ParameterCompiledValue)[1]'', ''NVARCHAR(4000)'') + N'' (Actual: '' + Node.Data.value(''(@ParameterRuntimeValue)[1]'', ''NVARCHAR(4000)'') + N'')'' - FROM q.Query_Plan.nodes(''/*:ShowPlanXML/*:BatchSequence/*:Batch/*:Statements/*:StmtSimple/*:QueryPlan/*:ParameterList/*:ColumnReference'') AS Node(Data) - FOR XML PATH('''')), 1,2,'''') - AS Live_Parameter_Info - FROM @LiveQueryPlans q - WHERE (s.session_id = q.Session_Id) - - ) AS qs_live - - WHERE s.session_id <> @@SPID - AND s.host_name IS NOT NULL - AND r.database_id NOT IN (SELECT database_id FROM #WhoReadableDBs) - ' - + CASE WHEN @ShowSleepingSPIDs = 0 THEN - N' AND COALESCE(DB_NAME(r.database_id), DB_NAME(blocked.dbid)) IS NOT NULL' - WHEN @ShowSleepingSPIDs = 1 THEN - N' OR COALESCE(r.open_transaction_count, blocked.open_tran) >= 1' - ELSE N'' END; - - -END /* IF @ProductVersionMajor >= 11 */ - -IF (@MinElapsedSeconds + @MinCPUTime + @MinLogicalReads + @MinPhysicalReads + @MinWrites + @MinTempdbMB + @MinRequestedMemoryKB + @MinBlockingSeconds) > 0 - BEGIN - /* They're filtering for something, so set up a where clause that will let any (not all combined) of the min triggers work: */ - SET @StringToExecute += N' AND (1 = 0 '; - IF @MinElapsedSeconds > 0 - SET @StringToExecute += N' OR ABS(COALESCE(r.total_elapsed_time,0)) / 1000 >= ' + CAST(@MinElapsedSeconds AS NVARCHAR(20)); - IF @MinCPUTime > 0 - SET @StringToExecute += N' OR COALESCE(r.cpu_time, s.cpu_time,0) / 1000 >= ' + CAST(@MinCPUTime AS NVARCHAR(20)); - IF @MinLogicalReads > 0 - SET @StringToExecute += N' OR COALESCE(r.logical_reads, s.logical_reads,0) >= ' + CAST(@MinLogicalReads AS NVARCHAR(20)); - IF @MinPhysicalReads > 0 - SET @StringToExecute += N' OR COALESCE(s.reads,0) >= ' + CAST(@MinPhysicalReads AS NVARCHAR(20)); - IF @MinWrites > 0 - SET @StringToExecute += N' OR COALESCE(r.writes, s.writes,0) >= ' + CAST(@MinWrites AS NVARCHAR(20)); - IF @MinTempdbMB > 0 - SET @StringToExecute += N' OR COALESCE(tempdb_allocations.tempdb_allocations_mb,0) >= ' + CAST(@MinTempdbMB AS NVARCHAR(20)); - IF @MinRequestedMemoryKB > 0 - SET @StringToExecute += N' OR COALESCE(qmg.requested_memory_kb,0) >= ' + CAST(@MinRequestedMemoryKB AS NVARCHAR(20)); - /* Blocking is a little different - we're going to return ALL of the queries if we meet the blocking threshold. */ - IF @MinBlockingSeconds > 0 - SET @StringToExecute += N' OR (SELECT SUM(waittime / 1000) FROM @blocked) >= ' + CAST(@MinBlockingSeconds AS NVARCHAR(20)); - SET @StringToExecute += N' ) '; - END - -SET @StringToExecute += - N' ORDER BY ' + CASE WHEN @SortOrder = 'session_id' THEN '[session_id] DESC' - WHEN @SortOrder = 'query_cost' THEN '[query_cost] DESC' - WHEN @SortOrder = 'database_name' THEN '[database_name] ASC' - WHEN @SortOrder = 'open_transaction_count' THEN '[open_transaction_count] DESC' - WHEN @SortOrder = 'is_implicit_transaction' THEN '[is_implicit_transaction] DESC' - WHEN @SortOrder = 'login_name' THEN '[login_name] ASC' - WHEN @SortOrder = 'program_name' THEN '[program_name] ASC' - WHEN @SortOrder = 'client_interface_name' THEN '[client_interface_name] ASC' - WHEN @SortOrder = 'request_cpu_time' THEN 'COALESCE(r.cpu_time, s.cpu_time) DESC' - WHEN @SortOrder = 'request_logical_reads' THEN 'COALESCE(r.logical_reads, s.logical_reads) DESC' - WHEN @SortOrder = 'request_writes' THEN 'COALESCE(r.writes, s.writes) DESC' - WHEN @SortOrder = 'request_physical_reads' THEN 'COALESCE(r.reads, s.reads) DESC ' - WHEN @SortOrder = 'session_cpu' THEN 's.cpu_time DESC' - WHEN @SortOrder = 'session_logical_reads' THEN 's.logical_reads DESC' - WHEN @SortOrder = 'session_physical_reads' THEN 's.reads DESC' - WHEN @SortOrder = 'session_writes' THEN 's.writes DESC' - WHEN @SortOrder = 'tempdb_allocations_mb' THEN '[tempdb_allocations_mb] DESC' - WHEN @SortOrder = 'memory_usage' THEN '[memory_usage] DESC' - WHEN @SortOrder = 'deadlock_priority' THEN 'r.deadlock_priority DESC' - WHEN @SortOrder = 'transaction_isolation_level' THEN 'r.[transaction_isolation_level] DESC' - WHEN @SortOrder = 'requested_memory_kb' THEN '[requested_memory_kb] DESC' - WHEN @SortOrder = 'grant_memory_kb' THEN 'qmg.granted_memory_kb DESC' - WHEN @SortOrder = 'grant' THEN 'qmg.granted_memory_kb DESC' - WHEN @SortOrder = 'query_memory_grant_used_memory_kb' THEN 'qmg.used_memory_kb DESC' - WHEN @SortOrder = 'ideal_memory_kb' THEN '[ideal_memory_kb] DESC' - WHEN @SortOrder = 'workload_group_name' THEN 'wg.name ASC' - WHEN @SortOrder = 'resource_pool_name' THEN 'rp.name ASC' - ELSE '[elapsed_time] DESC' - END + ' - '; - - -IF @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @OutputTableName IS NOT NULL - AND EXISTS ( SELECT * - FROM sys.databases - WHERE QUOTENAME([name]) = @OutputDatabaseName) - BEGIN - SET @StringToExecute = N'USE ' - + @OutputDatabaseName + N'; ' - + @BlockingCheck + - + ' INSERT INTO ' - + @OutputSchemaName + N'.' - + @OutputTableName - + N'(ServerName - ,CheckDate - ,[elapsed_time] - ,[session_id] - ,[blocking_session_id] - ,[database_name] - ,[query_text]' - + CASE WHEN @GetOuterCommand = 1 THEN N',[outer_command]' ELSE N'' END + N' - ,[query_plan]' - + CASE WHEN @ProductVersionMajor >= 11 THEN N',[live_query_plan]' ELSE N'' END - + CASE WHEN @ProductVersionMajor >= 11 THEN N',[cached_parameter_info]' ELSE N'' END - + CASE WHEN @ProductVersionMajor >= 11 AND @ShowActualParameters = 1 THEN N',[Live_Parameter_Info]' ELSE N'' END + N' - ,[query_cost] - ,[status] - ,[wait_info] - ,[wait_resource]' - + CASE WHEN @ProductVersionMajor >= 11 THEN N',[top_session_waits]' ELSE N'' END + N' - ,[open_transaction_count] - ,[is_implicit_transaction] - ,[nt_domain] - ,[host_name] - ,[login_name] - ,[nt_user_name] - ,[program_name] - ,[fix_parameter_sniffing] - ,[client_interface_name] - ,[login_time] - ,[start_time] - ,[request_time] - ,[request_cpu_time] - ,[request_logical_reads] - ,[request_writes] - ,[request_physical_reads] - ,[session_cpu] - ,[session_logical_reads] - ,[session_physical_reads] - ,[session_writes] - ,[tempdb_allocations_mb] - ,[memory_usage] - ,[estimated_completion_time] - ,[percent_complete] - ,[deadlock_priority] - ,[transaction_isolation_level] - ,[degree_of_parallelism]' - + CASE WHEN @ProductVersionMajor >= 11 THEN N' - ,[last_dop] - ,[min_dop] - ,[max_dop] - ,[last_grant_kb] - ,[min_grant_kb] - ,[max_grant_kb] - ,[last_used_grant_kb] - ,[min_used_grant_kb] - ,[max_used_grant_kb] - ,[last_ideal_grant_kb] - ,[min_ideal_grant_kb] - ,[max_ideal_grant_kb] - ,[last_reserved_threads] - ,[min_reserved_threads] - ,[max_reserved_threads] - ,[last_used_threads] - ,[min_used_threads] - ,[max_used_threads]' ELSE N'' END + N' - ,[grant_time] - ,[requested_memory_kb] - ,[grant_memory_kb] - ,[is_request_granted] - ,[required_memory_kb] - ,[query_memory_grant_used_memory_kb] - ,[ideal_memory_kb] - ,[is_small] - ,[timeout_sec] - ,[resource_semaphore_id] - ,[wait_order] - ,[wait_time_ms] - ,[next_candidate_for_memory_grant] - ,[target_memory_kb] - ,[max_target_memory_kb] - ,[total_memory_kb] - ,[available_memory_kb] - ,[granted_memory_kb] - ,[query_resource_semaphore_used_memory_kb] - ,[grantee_count] - ,[waiter_count] - ,[timeout_error_count] - ,[forced_grant_count] - ,[workload_group_name] - ,[resource_pool_name] - ,[context_info]' - + CASE WHEN @ProductVersionMajor >= 11 THEN N' - ,[query_hash] - ,[query_plan_hash] - ,[sql_handle] - ,[plan_handle] - ,[statement_start_offset] - ,[statement_end_offset]' ELSE N'' END + N' -) - SELECT @@SERVERNAME, COALESCE(@CheckDateOverride, SYSDATETIMEOFFSET()) AS CheckDate , ' - + @StringToExecute; - END -ELSE - SET @StringToExecute = @BlockingCheck + N' SELECT GETDATE() AS run_date , ' + @StringToExecute; - -/* If the server has > 50GB of memory, add a max grant hint to avoid getting a giant grant */ -IF (@ProductVersionMajor = 11 AND @ProductVersionMinor >= 6020) - OR (@ProductVersionMajor = 12 AND @ProductVersionMinor >= 5000 ) - OR (@ProductVersionMajor >= 13 ) - AND 50000000 < (SELECT cntr_value - FROM sys.dm_os_performance_counters - WHERE object_name LIKE '%:Memory Manager%' - AND counter_name LIKE 'Target Server Memory (KB)%') - BEGIN - SET @StringToExecute = @StringToExecute + N' OPTION (MAX_GRANT_PERCENT = 1, RECOMPILE) '; - END -ELSE - BEGIN - SET @StringToExecute = @StringToExecute + N' OPTION (RECOMPILE) '; - END - -/* Be good: */ -SET @StringToExecute = @StringToExecute + N' ; '; - - -IF @Debug = 1 - BEGIN - PRINT CONVERT(VARCHAR(8000), SUBSTRING(@StringToExecute, 0, 8000)) - PRINT CONVERT(VARCHAR(8000), SUBSTRING(@StringToExecute, 8000, 16000)) - END - -EXEC sp_executesql @StringToExecute, - N'@CheckDateOverride DATETIMEOFFSET', - @CheckDateOverride; - -END -GO - -IF (OBJECT_ID('dbo.SqlServerVersions') IS NULL) -BEGIN - - CREATE TABLE dbo.SqlServerVersions - ( - MajorVersionNumber tinyint not null, - MinorVersionNumber smallint not null, - Branch varchar(34) not null, - [Url] varchar(99) not null, - ReleaseDate date not null, - MainstreamSupportEndDate date not null, - ExtendedSupportEndDate date not null, - MajorVersionName varchar(19) not null, - MinorVersionName varchar(67) not null, - - CONSTRAINT PK_SqlServerVersions PRIMARY KEY CLUSTERED - ( - MajorVersionNumber ASC, - MinorVersionNumber ASC, - ReleaseDate ASC - ) - ); - - EXEC sys.sp_addextendedproperty @name=N'Description', @value=N'The major version number.' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'SqlServerVersions', @level2type=N'COLUMN',@level2name=N'MajorVersionNumber' - EXEC sys.sp_addextendedproperty @name=N'Description', @value=N'The minor version number.' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'SqlServerVersions', @level2type=N'COLUMN',@level2name=N'MinorVersionNumber' - EXEC sys.sp_addextendedproperty @name=N'Description', @value=N'The update level of the build. CU indicates a cumulative update. SP indicates a service pack. RTM indicates Release To Manufacturer. GDR indicates a General Distribution Release. QFE indicates Quick Fix Engineering (aka hotfix).' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'SqlServerVersions', @level2type=N'COLUMN',@level2name=N'Branch' - EXEC sys.sp_addextendedproperty @name=N'Description', @value=N'A link to the KB article for a version.' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'SqlServerVersions', @level2type=N'COLUMN',@level2name=N'Url' - EXEC sys.sp_addextendedproperty @name=N'Description', @value=N'The date the version was publicly released.' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'SqlServerVersions', @level2type=N'COLUMN',@level2name=N'ReleaseDate' - EXEC sys.sp_addextendedproperty @name=N'Description', @value=N'The date main stream Microsoft support ends for the version.' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'SqlServerVersions', @level2type=N'COLUMN',@level2name=N'MainstreamSupportEndDate' - EXEC sys.sp_addextendedproperty @name=N'Description', @value=N'The date extended Microsoft support ends for the version.' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'SqlServerVersions', @level2type=N'COLUMN',@level2name=N'ExtendedSupportEndDate' - EXEC sys.sp_addextendedproperty @name=N'Description', @value=N'The major version name.' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'SqlServerVersions', @level2type=N'COLUMN',@level2name=N'MajorVersionName' - EXEC sys.sp_addextendedproperty @name=N'Description', @value=N'The minor version name.' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'SqlServerVersions', @level2type=N'COLUMN',@level2name=N'MinorVersionName' - EXEC sys.sp_addextendedproperty @name=N'Description', @value=N'A reference for SQL Server major and minor versions.' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'SqlServerVersions' - -END; -GO - -DELETE FROM dbo.SqlServerVersions; - -INSERT INTO dbo.SqlServerVersions - (MajorVersionNumber, MinorVersionNumber, Branch, [Url], ReleaseDate, MainstreamSupportEndDate, ExtendedSupportEndDate, MajorVersionName, MinorVersionName) -VALUES - (16, 4105, 'CU11', 'https://support.microsoft.com/en-us/help/5032679', '2024-01-11', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 11'), - (16, 4100, 'CU10 GDR', 'https://support.microsoft.com/en-us/help/5033592', '2024-01-09', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 10 GDR'), - (16, 4095, 'CU10', 'https://support.microsoft.com/en-us/help/5031778', '2023-11-16', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 10'), - (16, 4085, 'CU9', 'https://support.microsoft.com/en-us/help/5030731', '2023-10-12', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 9'), - (16, 4075, 'CU8', 'https://support.microsoft.com/en-us/help/5029666', '2023-09-14', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 8'), - (16, 4065, 'CU7', 'https://support.microsoft.com/en-us/help/5028743', '2023-08-10', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 7'), - (16, 4055, 'CU6', 'https://support.microsoft.com/en-us/help/5027505', '2023-07-13', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 6'), - (16, 4045, 'CU5', 'https://support.microsoft.com/en-us/help/5026806', '2023-06-15', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 5'), - (16, 4035, 'CU4', 'https://support.microsoft.com/en-us/help/5026717', '2023-05-11', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 4'), - (16, 4025, 'CU3', 'https://support.microsoft.com/en-us/help/5024396', '2023-04-13', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 3'), - (16, 4015, 'CU2', 'https://support.microsoft.com/en-us/help/5023127', '2023-03-15', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 2'), - (16, 4003, 'CU1', 'https://support.microsoft.com/en-us/help/5022375', '2023-02-16', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 1'), - (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'), - (15, 4355, 'CU25', 'https://support.microsoft.com/kb/5033688', '2023-02-15', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 25'), - (15, 4345, 'CU24', 'https://support.microsoft.com/kb/5031908', '2023-12-14', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 24'), - (15, 4335, 'CU23', 'https://support.microsoft.com/kb/5030333', '2023-10-12', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 23'), - (15, 4322, 'CU22', 'https://support.microsoft.com/kb/5027702', '2023-08-14', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 22'), - (15, 4316, 'CU21', 'https://support.microsoft.com/kb/5025808', '2023-06-15', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 21'), - (15, 4312, 'CU20', 'https://support.microsoft.com/kb/5024276', '2023-04-13', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 20'), - (15, 4298, 'CU19', 'https://support.microsoft.com/kb/5023049', '2023-02-16', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 19'), - (15, 4280, 'CU18 GDR', 'https://support.microsoft.com/kb/5021124', '2023-02-14', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 18 GDR'), - (15, 4261, 'CU18', 'https://support.microsoft.com/en-us/help/5017593', '2022-09-28', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 18'), - (15, 4249, 'CU17', 'https://support.microsoft.com/en-us/help/5016394', '2022-08-11', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 17'), - (15, 4236, 'CU16 GDR', 'https://support.microsoft.com/en-us/help/5014353', '2022-06-14', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 16 GDR'), - (15, 4223, 'CU16', 'https://support.microsoft.com/en-us/help/5011644', '2022-04-18', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 16'), - (15, 4198, 'CU15', 'https://support.microsoft.com/en-us/help/5008996', '2022-01-07', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 15'), - (15, 4188, 'CU14', 'https://support.microsoft.com/en-us/help/5007182', '2021-11-22', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 14'), - (15, 4178, 'CU13', 'https://support.microsoft.com/en-us/help/5005679', '2021-10-05', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 13'), - (15, 4153, 'CU12', 'https://support.microsoft.com/en-us/help/5004524', '2021-08-04', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 12'), - (15, 4138, 'CU11', 'https://support.microsoft.com/en-us/help/5003249', '2021-06-10', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 11'), - (15, 4123, 'CU10', 'https://support.microsoft.com/en-us/help/5001090', '2021-04-06', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 10'), - (15, 4102, 'CU9', 'https://support.microsoft.com/en-us/help/5000642', '2021-02-11', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 9 '), - (15, 4073, 'CU8 GDR', 'https://support.microsoft.com/en-us/help/4583459', '2021-01-12', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 8 GDR '), - (15, 4073, 'CU8', 'https://support.microsoft.com/en-us/help/4577194', '2020-10-01', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 8 '), - (15, 4063, 'CU7', 'https://support.microsoft.com/en-us/help/4570012', '2020-09-02', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 7 '), - (15, 4053, 'CU6', 'https://support.microsoft.com/en-us/help/4563110', '2020-08-04', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 6 '), - (15, 4043, 'CU5', 'https://support.microsoft.com/en-us/help/4548597', '2020-06-22', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 5 '), - (15, 4033, 'CU4', 'https://support.microsoft.com/en-us/help/4548597', '2020-03-31', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 4 '), - (15, 4023, 'CU3', 'https://support.microsoft.com/en-us/help/4538853', '2020-03-12', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 3 '), - (15, 4013, 'CU2', 'https://support.microsoft.com/en-us/help/4536075', '2020-02-13', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 2 '), - (15, 4003, 'CU1', 'https://support.microsoft.com/en-us/help/4527376', '2020-01-07', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 1 '), - (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 '), - (14, 3465, 'RTM CU31 GDR', 'https://support.microsoft.com/kb/5029376', '2023-10-12', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 31 GDR'), - (14, 3460, 'RTM CU31 GDR', 'https://support.microsoft.com/kb/5021126', '2023-02-14', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 31 GDR'), - (14, 3456, 'RTM CU31', 'https://support.microsoft.com/en-us/help/5016884', '2022-09-20', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 31'), - (14, 3451, 'RTM CU30', 'https://support.microsoft.com/en-us/help/5013756', '2022-07-13', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 30'), - (14, 3445, 'RTM CU29 GDR', 'https://support.microsoft.com/en-us/help/5014553', '2022-06-14', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 29 GDR'), - (14, 3436, 'RTM CU29', 'https://support.microsoft.com/en-us/help/5010786', '2022-03-31', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 29'), - (14, 3430, 'RTM CU28', 'https://support.microsoft.com/en-us/help/5006944', '2022-01-13', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 28'), - (14, 3421, 'RTM CU27', 'https://support.microsoft.com/en-us/help/5006944', '2021-10-27', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 27'), - (14, 3411, 'RTM CU26', 'https://support.microsoft.com/en-us/help/5005226', '2021-09-14', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 26'), - (14, 3401, 'RTM CU25', 'https://support.microsoft.com/en-us/help/5003830', '2021-07-12', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 25'), - (14, 3391, 'RTM CU24', 'https://support.microsoft.com/en-us/help/5001228', '2021-05-10', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 24'), - (14, 3381, 'RTM CU23', 'https://support.microsoft.com/en-us/help/5000685', '2021-02-25', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 23'), - (14, 3370, 'RTM CU22 GDR', 'https://support.microsoft.com/en-us/help/4583457', '2021-01-12', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 22 GDR'), - (14, 3356, 'RTM CU22', 'https://support.microsoft.com/en-us/help/4577467', '2020-09-10', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 22'), - (14, 3335, 'RTM CU21', 'https://support.microsoft.com/en-us/help/4557397', '2020-07-01', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 21'), - (14, 3294, 'RTM CU20', 'https://support.microsoft.com/en-us/help/4541283', '2020-04-07', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 20'), - (14, 3257, 'RTM CU19', 'https://support.microsoft.com/en-us/help/4535007', '2020-02-05', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 19'), - (14, 3257, 'RTM CU18', 'https://support.microsoft.com/en-us/help/4527377', '2019-12-09', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 18'), - (14, 3238, 'RTM CU17', 'https://support.microsoft.com/en-us/help/4515579', '2019-10-08', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 17'), - (14, 3223, 'RTM CU16', 'https://support.microsoft.com/en-us/help/4508218', '2019-08-01', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 16'), - (14, 3162, 'RTM CU15', 'https://support.microsoft.com/en-us/help/4498951', '2019-05-24', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 15'), - (14, 3076, 'RTM CU14', 'https://support.microsoft.com/en-us/help/4484710', '2019-03-25', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 14'), - (14, 3048, 'RTM CU13', 'https://support.microsoft.com/en-us/help/4466404', '2018-12-18', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 13'), - (14, 3045, 'RTM CU12', 'https://support.microsoft.com/en-us/help/4464082', '2018-10-24', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 12'), - (14, 3038, 'RTM CU11', 'https://support.microsoft.com/en-us/help/4462262', '2018-09-20', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 11'), - (14, 3037, 'RTM CU10', 'https://support.microsoft.com/en-us/help/4524334', '2018-08-27', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 10'), - (14, 3030, 'RTM CU9', 'https://support.microsoft.com/en-us/help/4515435', '2018-07-18', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 9'), - (14, 3029, 'RTM CU8', 'https://support.microsoft.com/en-us/help/4338363', '2018-06-21', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 8'), - (14, 3026, 'RTM CU7', 'https://support.microsoft.com/en-us/help/4229789', '2018-05-23', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 7'), - (14, 3025, 'RTM CU6', 'https://support.microsoft.com/en-us/help/4101464', '2018-04-17', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 6'), - (14, 3023, 'RTM CU5', 'https://support.microsoft.com/en-us/help/4092643', '2018-03-20', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 5'), - (14, 3022, 'RTM CU4', 'https://support.microsoft.com/en-us/help/4056498', '2018-02-20', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 4'), - (14, 3015, 'RTM CU3', 'https://support.microsoft.com/en-us/help/4052987', '2018-01-04', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 3'), - (14, 3008, 'RTM CU2', 'https://support.microsoft.com/en-us/help/4052574', '2017-11-28', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 2'), - (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 '), - (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, 6430, 'SP3 GDR', 'https://support.microsoft.com/kb/5021129', '2023-02-14', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3 GDR'), - (13, 6419, 'SP3 GDR', 'https://support.microsoft.com/en-us/help/5014355', '2022-06-14', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3 GDR'), - (13, 6404, 'SP3 GDR', 'https://support.microsoft.com/en-us/help/5006943', '2021-10-27', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3 GDR'), - (13, 6300, 'SP3 ', 'https://support.microsoft.com/en-us/help/5003279', '2021-09-15', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3'), - (13, 5888, 'SP2 CU17', 'https://support.microsoft.com/en-us/help/5001092', '2021-03-29', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 17'), - (13, 5882, 'SP2 CU16', 'https://support.microsoft.com/en-us/help/5000645', '2021-02-11', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 16'), - (13, 5865, 'SP2 CU15 GDR', 'https://support.microsoft.com/en-us/help/4583461', '2021-01-12', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 15 GDR'), - (13, 5850, 'SP2 CU15', 'https://support.microsoft.com/en-us/help/4577775', '2020-09-28', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 15'), - (13, 5830, 'SP2 CU14', 'https://support.microsoft.com/en-us/help/4564903', '2020-08-06', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 14'), - (13, 5820, 'SP2 CU13', 'https://support.microsoft.com/en-us/help/4549825', '2020-05-28', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 13'), - (13, 5698, 'SP2 CU12', 'https://support.microsoft.com/en-us/help/4536648', '2020-02-25', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 12'), - (13, 5598, 'SP2 CU11', 'https://support.microsoft.com/en-us/help/4527378', '2019-12-09', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 11'), - (13, 5492, 'SP2 CU10', 'https://support.microsoft.com/en-us/help/4505830', '2019-10-08', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 10'), - (13, 5479, 'SP2 CU9', 'https://support.microsoft.com/en-us/help/4505830', '2019-09-30', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 9'), - (13, 5426, 'SP2 CU8', 'https://support.microsoft.com/en-us/help/4505830', '2019-07-31', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 8'), - (13, 5337, 'SP2 CU7', 'https://support.microsoft.com/en-us/help/4495256', '2019-05-23', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 7'), - (13, 5292, 'SP2 CU6', 'https://support.microsoft.com/en-us/help/4488536', '2019-03-19', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 6'), - (13, 5264, 'SP2 CU5', 'https://support.microsoft.com/en-us/help/4475776', '2019-01-23', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 5'), - (13, 5233, 'SP2 CU4', 'https://support.microsoft.com/en-us/help/4464106', '2018-11-13', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 4'), - (13, 5216, 'SP2 CU3', 'https://support.microsoft.com/en-us/help/4458871', '2018-09-20', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 3'), - (13, 5201, 'SP2 CU2 + Security Update', 'https://support.microsoft.com/en-us/help/4458621', '2018-08-21', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 2 + Security Update'), - (13, 5153, 'SP2 CU2', 'https://support.microsoft.com/en-us/help/4340355', '2018-07-16', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 2'), - (13, 5149, 'SP2 CU1', 'https://support.microsoft.com/en-us/help/4135048', '2018-05-30', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 1'), - (13, 5103, 'SP2 GDR', 'https://support.microsoft.com/en-us/help/4583460', '2021-01-12', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 GDR'), - (13, 5026, 'SP2 ', 'https://support.microsoft.com/en-us/help/4052908', '2018-04-24', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 '), - (13, 4574, 'SP1 CU15', 'https://support.microsoft.com/en-us/help/4495257', '2019-05-16', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 15'), - (13, 4560, 'SP1 CU14', 'https://support.microsoft.com/en-us/help/4488535', '2019-03-19', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 14'), - (13, 4550, 'SP1 CU13', 'https://support.microsoft.com/en-us/help/4475775', '2019-01-23', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 13'), - (13, 4541, 'SP1 CU12', 'https://support.microsoft.com/en-us/help/4464343', '2018-11-13', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 12'), - (13, 4528, 'SP1 CU11', 'https://support.microsoft.com/en-us/help/4459676', '2018-09-17', '2019-07-09', '2019-07-09', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 11'), - (13, 4514, 'SP1 CU10', 'https://support.microsoft.com/en-us/help/4341569', '2018-07-16', '2019-07-09', '2019-07-09', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 10'), - (13, 4502, 'SP1 CU9', 'https://support.microsoft.com/en-us/help/4100997', '2018-05-30', '2019-07-09', '2019-07-09', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 9'), - (13, 4474, 'SP1 CU8', 'https://support.microsoft.com/en-us/help/4077064', '2018-03-19', '2019-07-09', '2019-07-09', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 8'), - (13, 4466, 'SP1 CU7', 'https://support.microsoft.com/en-us/help/4057119', '2018-01-04', '2019-07-09', '2019-07-09', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 7'), - (13, 4457, 'SP1 CU6', 'https://support.microsoft.com/en-us/help/4037354', '2017-11-20', '2019-07-09', '2019-07-09', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 6'), - (13, 4451, 'SP1 CU5', 'https://support.microsoft.com/en-us/help/4024305', '2017-09-18', '2019-07-09', '2019-07-09', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 5'), - (13, 4446, 'SP1 CU4', 'https://support.microsoft.com/en-us/help/4024305', '2017-08-08', '2019-07-09', '2019-07-09', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 4'), - (13, 4435, 'SP1 CU3', 'https://support.microsoft.com/en-us/help/4019916', '2017-05-15', '2019-07-09', '2019-07-09', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 3'), - (13, 4422, 'SP1 CU2', 'https://support.microsoft.com/en-us/help/4013106', '2017-03-20', '2019-07-09', '2019-07-09', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 2'), - (13, 4411, 'SP1 CU1', 'https://support.microsoft.com/en-us/help/3208177', '2017-01-17', '2019-07-09', '2019-07-09', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 1'), - (13, 4224, 'SP1 CU10 + Security Update', 'https://support.microsoft.com/en-us/help/4458842', '2018-08-22', '2019-07-09', '2019-07-09', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 10 + Security Update'), - (13, 4001, 'SP1 ', 'https://support.microsoft.com/en-us/help/3182545 ', '2016-11-16', '2019-07-09', '2019-07-09', 'SQL Server 2016', 'Service Pack 1 '), - (13, 2216, 'RTM CU9', 'https://support.microsoft.com/en-us/help/4037357', '2017-11-20', '2018-01-09', '2018-01-09', 'SQL Server 2016', 'RTM Cumulative Update 9'), - (13, 2213, 'RTM CU8', 'https://support.microsoft.com/en-us/help/4024304', '2017-09-18', '2018-01-09', '2018-01-09', 'SQL Server 2016', 'RTM Cumulative Update 8'), - (13, 2210, 'RTM CU7', 'https://support.microsoft.com/en-us/help/4024304', '2017-08-08', '2018-01-09', '2018-01-09', 'SQL Server 2016', 'RTM Cumulative Update 7'), - (13, 2204, 'RTM CU6', 'https://support.microsoft.com/en-us/help/4019914', '2017-05-15', '2018-01-09', '2018-01-09', 'SQL Server 2016', 'RTM Cumulative Update 6'), - (13, 2197, 'RTM CU5', 'https://support.microsoft.com/en-us/help/4013105', '2017-03-20', '2018-01-09', '2018-01-09', 'SQL Server 2016', 'RTM Cumulative Update 5'), - (13, 2193, 'RTM CU4', 'https://support.microsoft.com/en-us/help/3205052 ', '2017-01-17', '2018-01-09', '2018-01-09', 'SQL Server 2016', 'RTM Cumulative Update 4'), - (13, 2186, 'RTM CU3', 'https://support.microsoft.com/en-us/help/3205413 ', '2016-11-16', '2018-01-09', '2018-01-09', 'SQL Server 2016', 'RTM Cumulative Update 3'), - (13, 2164, 'RTM CU2', 'https://support.microsoft.com/en-us/help/3182270 ', '2016-09-22', '2018-01-09', '2018-01-09', 'SQL Server 2016', 'RTM Cumulative Update 2'), - (13, 2149, 'RTM CU1', 'https://support.microsoft.com/en-us/help/3164674 ', '2016-07-25', '2018-01-09', '2018-01-09', 'SQL Server 2016', 'RTM Cumulative Update 1'), - (13, 1601, 'RTM ', '', '2016-06-01', '2019-01-09', '2019-01-09', 'SQL Server 2016', 'RTM '), - (12, 6444, 'SP3 CU4 GDR', 'https://support.microsoft.com/kb/5021045', '2023-02-14', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 4 GDR'), - (12, 6439, 'SP3 CU4 GDR', 'https://support.microsoft.com/en-us/help/5014164', '2022-06-14', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 4 GDR'), - (12, 6433, 'SP3 CU4 GDR', 'https://support.microsoft.com/en-us/help/4583462', '2021-01-12', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 4 GDR'), - (12, 6372, 'SP3 CU4 GDR', 'https://support.microsoft.com/en-us/help/4535288', '2020-02-11', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 4 GDR'), - (12, 6329, 'SP3 CU4', 'https://support.microsoft.com/en-us/help/4500181', '2019-07-29', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 4'), - (12, 6259, 'SP3 CU3', 'https://support.microsoft.com/en-us/help/4491539', '2019-04-16', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 3'), - (12, 6214, 'SP3 CU2', 'https://support.microsoft.com/en-us/help/4482960', '2019-02-19', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 2'), - (12, 6205, 'SP3 CU1', 'https://support.microsoft.com/en-us/help/4470220', '2018-12-12', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 1'), - (12, 6164, 'SP3 GDR', 'https://support.microsoft.com/en-us/help/4583463', '2021-01-12', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 GDR'), - (12, 6024, 'SP3 ', 'https://support.microsoft.com/en-us/help/4022619', '2018-10-30', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 '), - (12, 5687, 'SP2 CU18', 'https://support.microsoft.com/en-us/help/4500180', '2019-07-29', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 18'), - (12, 5632, 'SP2 CU17', 'https://support.microsoft.com/en-us/help/4491540', '2019-04-16', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 17'), - (12, 5626, 'SP2 CU16', 'https://support.microsoft.com/en-us/help/4482967', '2019-02-19', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 16'), - (12, 5605, 'SP2 CU15', 'https://support.microsoft.com/en-us/help/4469137', '2018-12-12', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 15'), - (12, 5600, 'SP2 CU14', 'https://support.microsoft.com/en-us/help/4459860', '2018-10-15', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 14'), - (12, 5590, 'SP2 CU13', 'https://support.microsoft.com/en-us/help/4456287', '2018-08-27', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 13'), - (12, 5589, 'SP2 CU12', 'https://support.microsoft.com/en-us/help/4130489', '2018-06-18', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 12'), - (12, 5579, 'SP2 CU11', 'https://support.microsoft.com/en-us/help/4077063', '2018-03-19', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 11'), - (12, 5571, 'SP2 CU10', 'https://support.microsoft.com/en-us/help/4052725', '2018-01-16', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 10'), - (12, 5563, 'SP2 CU9', 'https://support.microsoft.com/en-us/help/4055557', '2017-12-18', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 9'), - (12, 5557, 'SP2 CU8', 'https://support.microsoft.com/en-us/help/4037356', '2017-10-16', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 8'), - (12, 5556, 'SP2 CU7', 'https://support.microsoft.com/en-us/help/4032541', '2017-08-28', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 7'), - (12, 5553, 'SP2 CU6', 'https://support.microsoft.com/en-us/help/4019094', '2017-08-08', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 6'), - (12, 5546, 'SP2 CU5', 'https://support.microsoft.com/en-us/help/4013098', '2017-04-17', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 5'), - (12, 5540, 'SP2 CU4', 'https://support.microsoft.com/en-us/help/4010394', '2017-02-21', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 4'), - (12, 5538, 'SP2 CU3', 'https://support.microsoft.com/en-us/help/3204388 ', '2016-12-19', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 3'), - (12, 5522, 'SP2 CU2', 'https://support.microsoft.com/en-us/help/3188778 ', '2016-10-17', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 2'), - (12, 5511, 'SP2 CU1', 'https://support.microsoft.com/en-us/help/3178925 ', '2016-08-25', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 1'), - (12, 5000, 'SP2 ', 'https://support.microsoft.com/en-us/help/3171021 ', '2016-07-11', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 '), - (12, 4522, 'SP1 CU13', 'https://support.microsoft.com/en-us/help/4019099', '2017-08-08', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 Cumulative Update 13'), - (12, 4511, 'SP1 CU12', 'https://support.microsoft.com/en-us/help/4017793', '2017-04-17', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 Cumulative Update 12'), - (12, 4502, 'SP1 CU11', 'https://support.microsoft.com/en-us/help/4010392', '2017-02-21', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 Cumulative Update 11'), - (12, 4491, 'SP1 CU10', 'https://support.microsoft.com/en-us/help/3204399 ', '2016-12-19', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 Cumulative Update 10'), - (12, 4474, 'SP1 CU9', 'https://support.microsoft.com/en-us/help/3186964 ', '2016-10-17', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 Cumulative Update 9'), - (12, 4468, 'SP1 CU8', 'https://support.microsoft.com/en-us/help/3174038 ', '2016-08-15', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 Cumulative Update 8'), - (12, 4459, 'SP1 CU7', 'https://support.microsoft.com/en-us/help/3162659 ', '2016-06-20', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 Cumulative Update 7'), - (12, 4457, 'SP1 CU6', 'https://support.microsoft.com/en-us/help/3167392 ', '2016-05-30', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 Cumulative Update 6'), - (12, 4449, 'SP1 CU6', 'https://support.microsoft.com/en-us/help/3144524', '2016-04-18', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 Cumulative Update 6'), - (12, 4438, 'SP1 CU5', 'https://support.microsoft.com/en-us/help/3130926', '2016-02-22', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 Cumulative Update 5'), - (12, 4436, 'SP1 CU4', 'https://support.microsoft.com/en-us/help/3106660', '2015-12-21', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 Cumulative Update 4'), - (12, 4427, 'SP1 CU3', 'https://support.microsoft.com/en-us/help/3094221', '2015-10-19', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 Cumulative Update 3'), - (12, 4422, 'SP1 CU2', 'https://support.microsoft.com/en-us/help/3075950', '2015-08-17', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 Cumulative Update 2'), - (12, 4416, 'SP1 CU1', 'https://support.microsoft.com/en-us/help/3067839', '2015-06-19', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 Cumulative Update 1'), - (12, 4213, 'SP1 MS15-058: GDR Security Update', 'https://support.microsoft.com/en-us/help/3070446', '2015-07-14', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 MS15-058: GDR Security Update'), - (12, 4100, 'SP1 ', 'https://support.microsoft.com/en-us/help/3058865', '2015-05-04', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 '), - (12, 2569, 'RTM CU14', 'https://support.microsoft.com/en-us/help/3158271 ', '2016-06-20', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM Cumulative Update 14'), - (12, 2568, 'RTM CU13', 'https://support.microsoft.com/en-us/help/3144517', '2016-04-18', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM Cumulative Update 13'), - (12, 2564, 'RTM CU12', 'https://support.microsoft.com/en-us/help/3130923', '2016-02-22', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM Cumulative Update 12'), - (12, 2560, 'RTM CU11', 'https://support.microsoft.com/en-us/help/3106659', '2015-12-21', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM Cumulative Update 11'), - (12, 2556, 'RTM CU10', 'https://support.microsoft.com/en-us/help/3094220', '2015-10-19', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM Cumulative Update 10'), - (12, 2553, 'RTM CU9', 'https://support.microsoft.com/en-us/help/3075949', '2015-08-17', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM Cumulative Update 9'), - (12, 2548, 'RTM MS15-058: QFE Security Update', 'https://support.microsoft.com/en-us/help/3045323', '2015-07-14', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM MS15-058: QFE Security Update'), - (12, 2546, 'RTM CU8', 'https://support.microsoft.com/en-us/help/3067836', '2015-06-19', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM Cumulative Update 8'), - (12, 2495, 'RTM CU7', 'https://support.microsoft.com/en-us/help/3046038', '2015-04-20', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM Cumulative Update 7'), - (12, 2480, 'RTM CU6', 'https://support.microsoft.com/en-us/help/3031047', '2015-02-16', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM Cumulative Update 6'), - (12, 2456, 'RTM CU5', 'https://support.microsoft.com/en-us/help/3011055', '2014-12-17', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM Cumulative Update 5'), - (12, 2430, 'RTM CU4', 'https://support.microsoft.com/en-us/help/2999197', '2014-10-21', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM Cumulative Update 4'), - (12, 2402, 'RTM CU3', 'https://support.microsoft.com/en-us/help/2984923', '2014-08-18', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM Cumulative Update 3'), - (12, 2381, 'RTM MS14-044: QFE Security Update', 'https://support.microsoft.com/en-us/help/2977316', '2014-08-12', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM MS14-044: QFE Security Update'), - (12, 2370, 'RTM CU2', 'https://support.microsoft.com/en-us/help/2967546', '2014-06-27', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM Cumulative Update 2'), - (12, 2342, 'RTM CU1', 'https://support.microsoft.com/en-us/help/2931693', '2014-04-21', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM Cumulative Update 1'), - (12, 2269, 'RTM MS15-058: GDR Security Update ', 'https://support.microsoft.com/en-us/help/3045324', '2015-07-14', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM MS15-058: GDR Security Update '), - (12, 2254, 'RTM MS14-044: GDR Security Update', 'https://support.microsoft.com/en-us/help/2977315', '2014-08-12', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM MS14-044: GDR Security Update'), - (12, 2000, 'RTM ', '', '2014-04-01', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM '), - (11, 7507, 'SP4 GDR Security Update', 'https://support.microsoft.com/en-us/help/4583465', '2021-01-12', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'Service Pack 4 GDR Security Update for CVE-2021-1636'), - (11, 7493, 'SP4 GDR Security Update', 'https://support.microsoft.com/en-us/help/4532098', '2020-02-11', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'Service Pack 4 GDR Security Update for CVE-2020-0618'), - (11, 7469, 'SP4 On-Demand Hotfix Update', 'https://support.microsoft.com/en-us/help/4091266', '2018-03-28', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'Service Pack 4 SP4 On-Demand Hotfix Update'), - (11, 7462, 'SP4 ADV180002: GDR Security Update', 'https://support.microsoft.com/en-us/help/4057116', '2018-01-12', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'Service Pack 4 ADV180002: GDR Security Update'), - (11, 7001, 'SP4 ', 'https://support.microsoft.com/en-us/help/4018073', '2017-10-02', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'Service Pack 4 '), - (11, 6607, 'SP3 CU10', 'https://support.microsoft.com/en-us/help/4025925', '2017-08-08', '2018-10-09', '2018-10-09', 'SQL Server 2012', 'Service Pack 3 Cumulative Update 10'), - (11, 6598, 'SP3 CU9', 'https://support.microsoft.com/en-us/help/4016762', '2017-05-15', '2018-10-09', '2018-10-09', 'SQL Server 2012', 'Service Pack 3 Cumulative Update 9'), - (11, 6594, 'SP3 CU8', 'https://support.microsoft.com/en-us/help/3205051 ', '2017-03-20', '2018-10-09', '2018-10-09', 'SQL Server 2012', 'Service Pack 3 Cumulative Update 8'), - (11, 6579, 'SP3 CU7', 'https://support.microsoft.com/en-us/help/3205051 ', '2017-01-17', '2018-10-09', '2018-10-09', 'SQL Server 2012', 'Service Pack 3 Cumulative Update 7'), - (11, 6567, 'SP3 CU6', 'https://support.microsoft.com/en-us/help/3194992 ', '2016-11-17', '2018-10-09', '2018-10-09', 'SQL Server 2012', 'Service Pack 3 Cumulative Update 6'), - (11, 6544, 'SP3 CU5', 'https://support.microsoft.com/en-us/help/3180915 ', '2016-09-19', '2018-10-09', '2018-10-09', 'SQL Server 2012', 'Service Pack 3 Cumulative Update 5'), - (11, 6540, 'SP3 CU4', 'https://support.microsoft.com/en-us/help/3165264 ', '2016-07-18', '2018-10-09', '2018-10-09', 'SQL Server 2012', 'Service Pack 3 Cumulative Update 4'), - (11, 6537, 'SP3 CU3', 'https://support.microsoft.com/en-us/help/3152635 ', '2016-05-16', '2018-10-09', '2018-10-09', 'SQL Server 2012', 'Service Pack 3 Cumulative Update 3'), - (11, 6523, 'SP3 CU2', 'https://support.microsoft.com/en-us/help/3137746', '2016-03-21', '2018-10-09', '2018-10-09', 'SQL Server 2012', 'Service Pack 3 Cumulative Update 2'), - (11, 6518, 'SP3 CU1', 'https://support.microsoft.com/en-us/help/3123299', '2016-01-19', '2018-10-09', '2018-10-09', 'SQL Server 2012', 'Service Pack 3 Cumulative Update 1'), - (11, 6020, 'SP3 ', 'https://support.microsoft.com/en-us/help/3072779', '2015-11-20', '2018-10-09', '2018-10-09', 'SQL Server 2012', 'Service Pack 3 '), - (11, 5678, 'SP2 CU16', 'https://support.microsoft.com/en-us/help/3205416 ', '2016-11-17', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 16'), - (11, 5676, 'SP2 CU15', 'https://support.microsoft.com/en-us/help/3205416 ', '2016-11-17', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 15'), - (11, 5657, 'SP2 CU14', 'https://support.microsoft.com/en-us/help/3180914 ', '2016-09-19', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 14'), - (11, 5655, 'SP2 CU13', 'https://support.microsoft.com/en-us/help/3165266 ', '2016-07-18', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 13'), - (11, 5649, 'SP2 CU12', 'https://support.microsoft.com/en-us/help/3152637 ', '2016-05-16', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 12'), - (11, 5646, 'SP2 CU11', 'https://support.microsoft.com/en-us/help/3137745', '2016-03-21', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 11'), - (11, 5644, 'SP2 CU10', 'https://support.microsoft.com/en-us/help/3120313', '2016-01-19', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 10'), - (11, 5641, 'SP2 CU9', 'https://support.microsoft.com/en-us/help/3098512', '2015-11-16', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 9'), - (11, 5634, 'SP2 CU8', 'https://support.microsoft.com/en-us/help/3082561', '2015-09-21', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 8'), - (11, 5623, 'SP2 CU7', 'https://support.microsoft.com/en-us/help/3072100', '2015-07-20', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 7'), - (11, 5613, 'SP2 MS15-058: QFE Security Update', 'https://support.microsoft.com/en-us/help/3045319', '2015-07-14', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 MS15-058: QFE Security Update'), - (11, 5592, 'SP2 CU6', 'https://support.microsoft.com/en-us/help/3052468', '2015-05-18', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 6'), - (11, 5582, 'SP2 CU5', 'https://support.microsoft.com/en-us/help/3037255', '2015-03-16', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 5'), - (11, 5569, 'SP2 CU4', 'https://support.microsoft.com/en-us/help/3007556', '2015-01-19', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 4'), - (11, 5556, 'SP2 CU3', 'https://support.microsoft.com/en-us/help/3002049', '2014-11-17', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 3'), - (11, 5548, 'SP2 CU2', 'https://support.microsoft.com/en-us/help/2983175', '2014-09-15', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 2'), - (11, 5532, 'SP2 CU1', 'https://support.microsoft.com/en-us/help/2976982', '2014-07-23', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 1'), - (11, 5343, 'SP2 MS15-058: GDR Security Update', 'https://support.microsoft.com/en-us/help/3045321', '2015-07-14', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 MS15-058: GDR Security Update'), - (11, 5058, 'SP2 ', 'https://support.microsoft.com/en-us/help/2958429', '2014-06-10', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 '), - (11, 3513, 'SP1 MS15-058: QFE Security Update', 'https://support.microsoft.com/en-us/help/3045317', '2015-07-14', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 MS15-058: QFE Security Update'), - (11, 3482, 'SP1 CU13', 'https://support.microsoft.com/en-us/help/3002044', '2014-11-17', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 Cumulative Update 13'), - (11, 3470, 'SP1 CU12', 'https://support.microsoft.com/en-us/help/2991533', '2014-09-15', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 Cumulative Update 12'), - (11, 3460, 'SP1 MS14-044: QFE Security Update ', 'https://support.microsoft.com/en-us/help/2977325', '2014-08-12', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 MS14-044: QFE Security Update '), - (11, 3449, 'SP1 CU11', 'https://support.microsoft.com/en-us/help/2975396', '2014-07-21', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 Cumulative Update 11'), - (11, 3431, 'SP1 CU10', 'https://support.microsoft.com/en-us/help/2954099', '2014-05-19', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 Cumulative Update 10'), - (11, 3412, 'SP1 CU9', 'https://support.microsoft.com/en-us/help/2931078', '2014-03-17', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 Cumulative Update 9'), - (11, 3401, 'SP1 CU8', 'https://support.microsoft.com/en-us/help/2917531', '2014-01-20', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 Cumulative Update 8'), - (11, 3393, 'SP1 CU7', 'https://support.microsoft.com/en-us/help/2894115', '2013-11-18', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 Cumulative Update 7'), - (11, 3381, 'SP1 CU6', 'https://support.microsoft.com/en-us/help/2874879', '2013-09-16', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 Cumulative Update 6'), - (11, 3373, 'SP1 CU5', 'https://support.microsoft.com/en-us/help/2861107', '2013-07-15', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 Cumulative Update 5'), - (11, 3368, 'SP1 CU4', 'https://support.microsoft.com/en-us/help/2833645', '2013-05-30', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 Cumulative Update 4'), - (11, 3349, 'SP1 CU3', 'https://support.microsoft.com/en-us/help/2812412', '2013-03-18', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 Cumulative Update 3'), - (11, 3339, 'SP1 CU2', 'https://support.microsoft.com/en-us/help/2790947', '2013-01-21', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 Cumulative Update 2'), - (11, 3321, 'SP1 CU1', 'https://support.microsoft.com/en-us/help/2765331', '2012-11-20', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 Cumulative Update 1'), - (11, 3156, 'SP1 MS15-058: GDR Security Update', 'https://support.microsoft.com/en-us/help/3045318', '2015-07-14', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 MS15-058: GDR Security Update'), - (11, 3153, 'SP1 MS14-044: GDR Security Update', 'https://support.microsoft.com/en-us/help/2977326', '2014-08-12', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 MS14-044: GDR Security Update'), - (11, 3000, 'SP1 ', 'https://support.microsoft.com/en-us/help/2674319', '2012-11-07', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 '), - (11, 2424, 'RTM CU11', 'https://support.microsoft.com/en-us/help/2908007', '2013-12-16', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'RTM Cumulative Update 11'), - (11, 2420, 'RTM CU10', 'https://support.microsoft.com/en-us/help/2891666', '2013-10-21', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'RTM Cumulative Update 10'), - (11, 2419, 'RTM CU9', 'https://support.microsoft.com/en-us/help/2867319', '2013-08-20', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'RTM Cumulative Update 9'), - (11, 2410, 'RTM CU8', 'https://support.microsoft.com/en-us/help/2844205', '2013-06-17', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'RTM Cumulative Update 8'), - (11, 2405, 'RTM CU7', 'https://support.microsoft.com/en-us/help/2823247', '2013-04-15', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'RTM Cumulative Update 7'), - (11, 2401, 'RTM CU6', 'https://support.microsoft.com/en-us/help/2728897', '2013-02-18', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'RTM Cumulative Update 6'), - (11, 2395, 'RTM CU5', 'https://support.microsoft.com/en-us/help/2777772', '2012-12-17', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'RTM Cumulative Update 5'), - (11, 2383, 'RTM CU4', 'https://support.microsoft.com/en-us/help/2758687', '2012-10-15', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'RTM Cumulative Update 4'), - (11, 2376, 'RTM MS12-070: QFE Security Update', 'https://support.microsoft.com/en-us/help/2716441', '2012-10-09', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'RTM MS12-070: QFE Security Update'), - (11, 2332, 'RTM CU3', 'https://support.microsoft.com/en-us/help/2723749', '2012-08-31', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'RTM Cumulative Update 3'), - (11, 2325, 'RTM CU2', 'https://support.microsoft.com/en-us/help/2703275', '2012-06-18', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'RTM Cumulative Update 2'), - (11, 2316, 'RTM CU1', 'https://support.microsoft.com/en-us/help/2679368', '2012-04-12', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'RTM Cumulative Update 1'), - (11, 2218, 'RTM MS12-070: GDR Security Update', 'https://support.microsoft.com/en-us/help/2716442', '2012-10-09', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'RTM MS12-070: GDR Security Update'), - (11, 2100, 'RTM ', '', '2012-03-06', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'RTM '), - (10, 6529, 'SP3 MS15-058: QFE Security Update', 'https://support.microsoft.com/en-us/help/3045314', '2015-07-14', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'Service Pack 3 MS15-058: QFE Security Update'), - (10, 6220, 'SP3 MS15-058: QFE Security Update', 'https://support.microsoft.com/en-us/help/3045316', '2015-07-14', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'Service Pack 3 MS15-058: QFE Security Update'), - (10, 6000, 'SP3 ', 'https://support.microsoft.com/en-us/help/2979597', '2014-09-26', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'Service Pack 3 '), - (10, 4339, 'SP2 MS15-058: QFE Security Update', 'https://support.microsoft.com/en-us/help/3045312', '2015-07-14', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 MS15-058: QFE Security Update'), - (10, 4321, 'SP2 MS14-044: QFE Security Update', 'https://support.microsoft.com/en-us/help/2977319', '2014-08-14', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 MS14-044: QFE Security Update'), - (10, 4319, 'SP2 CU13', 'https://support.microsoft.com/en-us/help/2967540', '2014-06-30', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 Cumulative Update 13'), - (10, 4305, 'SP2 CU12', 'https://support.microsoft.com/en-us/help/2938478', '2014-04-21', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 Cumulative Update 12'), - (10, 4302, 'SP2 CU11', 'https://support.microsoft.com/en-us/help/2926028', '2014-02-18', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 Cumulative Update 11'), - (10, 4297, 'SP2 CU10', 'https://support.microsoft.com/en-us/help/2908087', '2013-12-17', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 Cumulative Update 10'), - (10, 4295, 'SP2 CU9', 'https://support.microsoft.com/en-us/help/2887606', '2013-10-28', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 Cumulative Update 9'), - (10, 4290, 'SP2 CU8', 'https://support.microsoft.com/en-us/help/2871401', '2013-08-22', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 Cumulative Update 8'), - (10, 4285, 'SP2 CU7', 'https://support.microsoft.com/en-us/help/2844090', '2013-06-17', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 Cumulative Update 7'), - (10, 4279, 'SP2 CU6', 'https://support.microsoft.com/en-us/help/2830140', '2013-04-15', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 Cumulative Update 6'), - (10, 4276, 'SP2 CU5', 'https://support.microsoft.com/en-us/help/2797460', '2013-02-18', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 Cumulative Update 5'), - (10, 4270, 'SP2 CU4', 'https://support.microsoft.com/en-us/help/2777358', '2012-12-17', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 Cumulative Update 4'), - (10, 4266, 'SP2 CU3', 'https://support.microsoft.com/en-us/help/2754552', '2012-10-15', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 Cumulative Update 3'), - (10, 4263, 'SP2 CU2', 'https://support.microsoft.com/en-us/help/2740411', '2012-08-31', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 Cumulative Update 2'), - (10, 4260, 'SP2 CU1', 'https://support.microsoft.com/en-us/help/2720425', '2012-07-24', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 Cumulative Update 1'), - (10, 4042, 'SP2 MS15-058: GDR Security Update', 'https://support.microsoft.com/en-us/help/3045313', '2015-07-14', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 MS15-058: GDR Security Update'), - (10, 4033, 'SP2 MS14-044: GDR Security Update', 'https://support.microsoft.com/en-us/help/2977320', '2014-08-12', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 MS14-044: GDR Security Update'), - (10, 4000, 'SP2 ', 'https://support.microsoft.com/en-us/help/2630458', '2012-07-26', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 '), - (10, 2881, 'SP1 CU14', 'https://support.microsoft.com/en-us/help/2868244', '2013-08-08', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 Cumulative Update 14'), - (10, 2876, 'SP1 CU13', 'https://support.microsoft.com/en-us/help/2855792', '2013-06-17', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 Cumulative Update 13'), - (10, 2874, 'SP1 CU12', 'https://support.microsoft.com/en-us/help/2828727', '2013-04-15', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 Cumulative Update 12'), - (10, 2869, 'SP1 CU11', 'https://support.microsoft.com/en-us/help/2812683', '2013-02-18', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 Cumulative Update 11'), - (10, 2868, 'SP1 CU10', 'https://support.microsoft.com/en-us/help/2783135', '2012-12-17', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 Cumulative Update 10'), - (10, 2866, 'SP1 CU9', 'https://support.microsoft.com/en-us/help/2756574', '2012-10-15', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 Cumulative Update 9'), - (10, 2861, 'SP1 MS12-070: QFE Security Update', 'https://support.microsoft.com/en-us/help/2716439', '2012-10-09', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 MS12-070: QFE Security Update'), - (10, 2822, 'SP1 CU8', 'https://support.microsoft.com/en-us/help/2723743', '2012-08-31', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 Cumulative Update 8'), - (10, 2817, 'SP1 CU7', 'https://support.microsoft.com/en-us/help/2703282', '2012-06-18', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 Cumulative Update 7'), - (10, 2811, 'SP1 CU6', 'https://support.microsoft.com/en-us/help/2679367', '2012-04-16', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 Cumulative Update 6'), - (10, 2806, 'SP1 CU5', 'https://support.microsoft.com/en-us/help/2659694', '2012-02-22', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 Cumulative Update 5'), - (10, 2796, 'SP1 CU4', 'https://support.microsoft.com/en-us/help/2633146', '2011-12-19', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 Cumulative Update 4'), - (10, 2789, 'SP1 CU3', 'https://support.microsoft.com/en-us/help/2591748', '2011-10-17', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 Cumulative Update 3'), - (10, 2772, 'SP1 CU2', 'https://support.microsoft.com/en-us/help/2567714', '2011-08-15', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 Cumulative Update 2'), - (10, 2769, 'SP1 CU1', 'https://support.microsoft.com/en-us/help/2544793', '2011-07-18', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 Cumulative Update 1'), - (10, 2550, 'SP1 MS12-070: GDR Security Update', 'https://support.microsoft.com/en-us/help/2754849', '2012-10-09', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 MS12-070: GDR Security Update'), - (10, 2500, 'SP1 ', 'https://support.microsoft.com/en-us/help/2528583', '2011-07-12', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 '), - (10, 1815, 'RTM CU13', 'https://support.microsoft.com/en-us/help/2679366', '2012-04-16', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM Cumulative Update 13'), - (10, 1810, 'RTM CU12', 'https://support.microsoft.com/en-us/help/2659692', '2012-02-21', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM Cumulative Update 12'), - (10, 1809, 'RTM CU11', 'https://support.microsoft.com/en-us/help/2633145', '2011-12-19', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM Cumulative Update 11'), - (10, 1807, 'RTM CU10', 'https://support.microsoft.com/en-us/help/2591746', '2011-10-17', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM Cumulative Update 10'), - (10, 1804, 'RTM CU9', 'https://support.microsoft.com/en-us/help/2567713', '2011-08-15', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM Cumulative Update 9'), - (10, 1797, 'RTM CU8', 'https://support.microsoft.com/en-us/help/2534352', '2011-06-20', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM Cumulative Update 8'), - (10, 1790, 'RTM MS11-049: QFE Security Update', 'https://support.microsoft.com/en-us/help/2494086', '2011-06-14', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM MS11-049: QFE Security Update'), - (10, 1777, 'RTM CU7', 'https://support.microsoft.com/en-us/help/2507770', '2011-04-18', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM Cumulative Update 7'), - (10, 1765, 'RTM CU6', 'https://support.microsoft.com/en-us/help/2489376', '2011-02-21', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM Cumulative Update 6'), - (10, 1753, 'RTM CU5', 'https://support.microsoft.com/en-us/help/2438347', '2010-12-20', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM Cumulative Update 5'), - (10, 1746, 'RTM CU4', 'https://support.microsoft.com/en-us/help/2345451', '2010-10-18', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM Cumulative Update 4'), - (10, 1734, 'RTM CU3', 'https://support.microsoft.com/en-us/help/2261464', '2010-08-16', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM Cumulative Update 3'), - (10, 1720, 'RTM CU2', 'https://support.microsoft.com/en-us/help/2072493', '2010-06-21', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM Cumulative Update 2'), - (10, 1702, 'RTM CU1', 'https://support.microsoft.com/en-us/help/981355', '2010-05-18', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM Cumulative Update 1'), - (10, 1617, 'RTM MS11-049: GDR Security Update', 'https://support.microsoft.com/en-us/help/2494088', '2011-06-14', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM MS11-049: GDR Security Update'), - (10, 1600, 'RTM ', '', '2010-05-10', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM '), - (10, 6535, 'SP3 MS15-058: QFE Security Update', 'https://support.microsoft.com/en-us/help/3045308', '2015-07-14', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 MS15-058: QFE Security Update'), - (10, 6241, 'SP3 MS15-058: GDR Security Update', 'https://support.microsoft.com/en-us/help/3045311', '2015-07-14', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 MS15-058: GDR Security Update'), - (10, 5890, 'SP3 MS15-058: QFE Security Update', 'https://support.microsoft.com/en-us/help/3045303', '2015-07-14', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 MS15-058: QFE Security Update'), - (10, 5869, 'SP3 MS14-044: QFE Security Update', 'https://support.microsoft.com/en-us/help/2984340, https://support.microsoft.com/en-us/help/2977322', '2014-08-12', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 MS14-044: QFE Security Update'), - (10, 5861, 'SP3 CU17', 'https://support.microsoft.com/en-us/help/2958696', '2014-05-19', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 17'), - (10, 5852, 'SP3 CU16', 'https://support.microsoft.com/en-us/help/2936421', '2014-03-17', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 16'), - (10, 5850, 'SP3 CU15', 'https://support.microsoft.com/en-us/help/2923520', '2014-01-20', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 15'), - (10, 5848, 'SP3 CU14', 'https://support.microsoft.com/en-us/help/2893410', '2013-11-18', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 14'), - (10, 5846, 'SP3 CU13', 'https://support.microsoft.com/en-us/help/2880350', '2013-09-16', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 13'), - (10, 5844, 'SP3 CU12', 'https://support.microsoft.com/en-us/help/2863205', '2013-07-15', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 12'), - (10, 5840, 'SP3 CU11', 'https://support.microsoft.com/en-us/help/2834048', '2013-05-20', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 11'), - (10, 5835, 'SP3 CU10', 'https://support.microsoft.com/en-us/help/2814783', '2013-03-18', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 10'), - (10, 5829, 'SP3 CU9', 'https://support.microsoft.com/en-us/help/2799883', '2013-01-21', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 9'), - (10, 5828, 'SP3 CU8', 'https://support.microsoft.com/en-us/help/2771833', '2012-11-19', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 8'), - (10, 5826, 'SP3 MS12-070: QFE Security Update', 'https://support.microsoft.com/en-us/help/2716435', '2012-10-09', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 MS12-070: QFE Security Update'), - (10, 5794, 'SP3 CU7', 'https://support.microsoft.com/en-us/help/2738350', '2012-09-17', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 7'), - (10, 5788, 'SP3 CU6', 'https://support.microsoft.com/en-us/help/2715953', '2012-07-16', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 6'), - (10, 5785, 'SP3 CU5', 'https://support.microsoft.com/en-us/help/2696626', '2012-05-21', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 5'), - (10, 5775, 'SP3 CU4', 'https://support.microsoft.com/en-us/help/2673383', '2012-03-19', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 4'), - (10, 5770, 'SP3 CU3', 'https://support.microsoft.com/en-us/help/2648098', '2012-01-16', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 3'), - (10, 5768, 'SP3 CU2', 'https://support.microsoft.com/en-us/help/2633143', '2011-11-21', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 2'), - (10, 5766, 'SP3 CU1', 'https://support.microsoft.com/en-us/help/2617146', '2011-10-17', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 1'), - (10, 5538, 'SP3 MS15-058: GDR Security Update', 'https://support.microsoft.com/en-us/help/3045305', '2015-07-14', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 MS15-058: GDR Security Update'), - (10, 5520, 'SP3 MS14-044: GDR Security Update', 'https://support.microsoft.com/en-us/help/2977321', '2014-08-12', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 MS14-044: GDR Security Update'), - (10, 5512, 'SP3 MS12-070: GDR Security Update', 'https://support.microsoft.com/en-us/help/2716436', '2012-10-09', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 MS12-070: GDR Security Update'), - (10, 5500, 'SP3 ', 'https://support.microsoft.com/en-us/help/2546951', '2011-10-06', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 '), - (10, 4371, 'SP2 MS12-070: QFE Security Update', 'https://support.microsoft.com/en-us/help/2716433', '2012-10-09', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 MS12-070: QFE Security Update'), - (10, 4333, 'SP2 CU11', 'https://support.microsoft.com/en-us/help/2715951', '2012-07-16', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 Cumulative Update 11'), - (10, 4332, 'SP2 CU10', 'https://support.microsoft.com/en-us/help/2696625', '2012-05-21', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 Cumulative Update 10'), - (10, 4330, 'SP2 CU9', 'https://support.microsoft.com/en-us/help/2673382', '2012-03-19', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 Cumulative Update 9'), - (10, 4326, 'SP2 CU8', 'https://support.microsoft.com/en-us/help/2648096', '2012-01-16', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 Cumulative Update 8'), - (10, 4323, 'SP2 CU7', 'https://support.microsoft.com/en-us/help/2617148', '2011-11-21', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 Cumulative Update 7'), - (10, 4321, 'SP2 CU6', 'https://support.microsoft.com/en-us/help/2582285', '2011-09-19', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 Cumulative Update 6'), - (10, 4316, 'SP2 CU5', 'https://support.microsoft.com/en-us/help/2555408', '2011-07-18', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 Cumulative Update 5'), - (10, 4311, 'SP2 MS11-049: QFE Security Update', 'https://support.microsoft.com/en-us/help/2494094', '2011-06-14', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 MS11-049: QFE Security Update'), - (10, 4285, 'SP2 CU4', 'https://support.microsoft.com/en-us/help/2527180', '2011-05-16', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 Cumulative Update 4'), - (10, 4279, 'SP2 CU3', 'https://support.microsoft.com/en-us/help/2498535', '2011-03-17', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 Cumulative Update 3'), - (10, 4272, 'SP2 CU2', 'https://support.microsoft.com/en-us/help/2467239', '2011-01-17', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 Cumulative Update 2'), - (10, 4266, 'SP2 CU1', 'https://support.microsoft.com/en-us/help/2289254', '2010-11-15', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 Cumulative Update 1'), - (10, 4067, 'SP2 MS12-070: GDR Security Update', 'https://support.microsoft.com/en-us/help/2716434', '2012-10-09', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 MS12-070: GDR Security Update'), - (10, 4064, 'SP2 MS11-049: GDR Security Update', 'https://support.microsoft.com/en-us/help/2494089', '2011-06-14', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 MS11-049: GDR Security Update'), - (10, 4000, 'SP2 ', 'https://support.microsoft.com/en-us/help/2285068', '2010-09-29', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 '), - (10, 2850, 'SP1 CU16', 'https://support.microsoft.com/en-us/help/2582282', '2011-09-19', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 16'), - (10, 2847, 'SP1 CU15', 'https://support.microsoft.com/en-us/help/2555406', '2011-07-18', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 15'), - (10, 2841, 'SP1 MS11-049: QFE Security Update', 'https://support.microsoft.com/en-us/help/2494100', '2011-06-14', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 MS11-049: QFE Security Update'), - (10, 2821, 'SP1 CU14', 'https://support.microsoft.com/en-us/help/2527187', '2011-05-16', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 14'), - (10, 2816, 'SP1 CU13', 'https://support.microsoft.com/en-us/help/2497673', '2011-03-17', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 13'), - (10, 2808, 'SP1 CU12', 'https://support.microsoft.com/en-us/help/2467236', '2011-01-17', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 12'), - (10, 2804, 'SP1 CU11', 'https://support.microsoft.com/en-us/help/2413738', '2010-11-15', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 11'), - (10, 2799, 'SP1 CU10', 'https://support.microsoft.com/en-us/help/2279604', '2010-09-20', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 10'), - (10, 2789, 'SP1 CU9', 'https://support.microsoft.com/en-us/help/2083921', '2010-07-19', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 9'), - (10, 2775, 'SP1 CU8', 'https://support.microsoft.com/en-us/help/981702', '2010-05-17', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 8'), - (10, 2766, 'SP1 CU7', 'https://support.microsoft.com/en-us/help/979065', '2010-03-26', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 7'), - (10, 2757, 'SP1 CU6', 'https://support.microsoft.com/en-us/help/977443', '2010-01-18', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 6'), - (10, 2746, 'SP1 CU5', 'https://support.microsoft.com/en-us/help/975977', '2009-11-16', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 5'), - (10, 2734, 'SP1 CU4', 'https://support.microsoft.com/en-us/help/973602', '2009-09-21', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 4'), - (10, 2723, 'SP1 CU3', 'https://support.microsoft.com/en-us/help/971491', '2009-07-20', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 3'), - (10, 2714, 'SP1 CU2', 'https://support.microsoft.com/en-us/help/970315', '2009-05-18', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 2'), - (10, 2710, 'SP1 CU1', 'https://support.microsoft.com/en-us/help/969099', '2009-04-16', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 1'), - (10, 2573, 'SP1 MS11-049: GDR Security update', 'https://support.microsoft.com/en-us/help/2494096', '2011-06-14', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 MS11-049: GDR Security update'), - (10, 2531, 'SP1 ', '', '2009-04-01', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 '), - (10, 1835, 'RTM CU10', 'https://support.microsoft.com/en-us/help/979064', '2010-03-15', '2014-07-08', '2019-07-09', 'SQL Server 2008', 'RTM Cumulative Update 10'), - (10, 1828, 'RTM CU9', 'https://support.microsoft.com/en-us/help/977444', '2010-01-18', '2014-07-08', '2019-07-09', 'SQL Server 2008', 'RTM Cumulative Update 9'), - (10, 1823, 'RTM CU8', 'https://support.microsoft.com/en-us/help/975976', '2009-11-16', '2014-07-08', '2019-07-09', 'SQL Server 2008', 'RTM Cumulative Update 8'), - (10, 1818, 'RTM CU7', 'https://support.microsoft.com/en-us/help/973601', '2009-09-21', '2014-07-08', '2019-07-09', 'SQL Server 2008', 'RTM Cumulative Update 7'), - (10, 1812, 'RTM CU6', 'https://support.microsoft.com/en-us/help/971490', '2009-07-20', '2014-07-08', '2019-07-09', 'SQL Server 2008', 'RTM Cumulative Update 6'), - (10, 1806, 'RTM CU5', 'https://support.microsoft.com/en-us/help/969531', '2009-05-18', '2014-07-08', '2019-07-09', 'SQL Server 2008', 'RTM Cumulative Update 5'), - (10, 1798, 'RTM CU4', 'https://support.microsoft.com/en-us/help/963036', '2009-03-16', '2014-07-08', '2019-07-09', 'SQL Server 2008', 'RTM Cumulative Update 4'), - (10, 1787, 'RTM CU3', 'https://support.microsoft.com/en-us/help/960484', '2009-01-19', '2014-07-08', '2019-07-09', 'SQL Server 2008', 'RTM Cumulative Update 3'), - (10, 1779, 'RTM CU2', 'https://support.microsoft.com/en-us/help/958186', '2008-11-19', '2014-07-08', '2019-07-09', 'SQL Server 2008', 'RTM Cumulative Update 2'), - (10, 1763, 'RTM CU1', 'https://support.microsoft.com/en-us/help/956717', '2008-09-22', '2014-07-08', '2019-07-09', 'SQL Server 2008', 'RTM Cumulative Update 1'), - (10, 1600, 'RTM ', '', '2008-08-06', '2014-07-08', '2019-07-09', 'SQL Server 2008', 'RTM ') -; -GO -IF OBJECT_ID('dbo.sp_BlitzFirst') IS NULL - EXEC ('CREATE PROCEDURE dbo.sp_BlitzFirst AS RETURN 0;'); -GO - - -ALTER PROCEDURE [dbo].[sp_BlitzFirst] - @LogMessage NVARCHAR(4000) = NULL , - @Help TINYINT = 0 , - @AsOf DATETIMEOFFSET = NULL , - @ExpertMode TINYINT = 0 , - @Seconds INT = 5 , - @OutputType VARCHAR(20) = 'TABLE' , - @OutputServerName NVARCHAR(256) = NULL , - @OutputDatabaseName NVARCHAR(256) = NULL , - @OutputSchemaName NVARCHAR(256) = NULL , - @OutputTableName NVARCHAR(256) = NULL , - @OutputTableNameFileStats NVARCHAR(256) = NULL , - @OutputTableNamePerfmonStats NVARCHAR(256) = NULL , - @OutputTableNameWaitStats NVARCHAR(256) = NULL , - @OutputTableNameBlitzCache NVARCHAR(256) = NULL , - @OutputTableNameBlitzWho NVARCHAR(256) = NULL , - @OutputResultSets NVARCHAR(500) = N'BlitzWho_Start|Findings|FileStats|PerfmonStats|WaitStats|BlitzCache|BlitzWho_End' , - @OutputTableRetentionDays TINYINT = 7 , - @OutputXMLasNVARCHAR TINYINT = 0 , - @FilterPlansByDatabase VARCHAR(MAX) = NULL , - @CheckProcedureCache TINYINT = 0 , - @CheckServerInfo TINYINT = 1 , - @FileLatencyThresholdMS INT = 100 , - @SinceStartup TINYINT = 0 , - @ShowSleepingSPIDs TINYINT = 0 , - @BlitzCacheSkipAnalysis BIT = 1 , - @MemoryGrantThresholdPct DECIMAL(5,2) = 15.00, - @LogMessageCheckID INT = 38, - @LogMessagePriority TINYINT = 1, - @LogMessageFindingsGroup VARCHAR(50) = 'Logged Message', - @LogMessageFinding VARCHAR(200) = 'Logged from sp_BlitzFirst', - @LogMessageURL VARCHAR(200) = '', - @LogMessageCheckDate DATETIMEOFFSET = NULL, - @Debug BIT = 0, - @Version VARCHAR(30) = NULL OUTPUT, - @VersionDate DATETIME = NULL OUTPUT, - @VersionCheckMode BIT = 0 - WITH EXECUTE AS CALLER, RECOMPILE -AS -BEGIN -SET NOCOUNT ON; -SET STATISTICS XML OFF; -SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - -SELECT @Version = '8.19', @VersionDate = '20240222'; - -IF(@VersionCheckMode = 1) -BEGIN - RETURN; -END; - -IF @Help = 1 -BEGIN -PRINT ' -sp_BlitzFirst from http://FirstResponderKit.org - -This script gives you a prioritized list of why your SQL Server is slow right now. - -This is not an overall health check - for that, check out sp_Blitz. - -To learn more, visit http://FirstResponderKit.org where you can download new -versions for free, watch training videos on how it works, get more info on -the findings, contribute your own code, and more. - -Known limitations of this version: - - Only Microsoft-supported versions of SQL Server. Sorry, 2005 and 2000. It - may work just fine on 2005, and if it does, hug your parents. Just don''t - file support issues if it breaks. - - If a temp table called #CustomPerfmonCounters exists for any other session, - but not our session, this stored proc will fail with an error saying the - temp table #CustomPerfmonCounters does not exist. - - @OutputServerName is not functional yet. - - If @OutputDatabaseName, SchemaName, TableName, etc are quoted with brackets, - the write to table may silently fail. Look, I never said I was good at this. - -Unknown limitations of this version: - - None. Like Zombo.com, the only limit is yourself. - -Changes - for the full list of improvements and fixes in this version, see: -https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/ - - -MIT License - -Copyright (c) Brent Ozar Unlimited - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - -'; -RETURN; -END; /* @Help = 1 */ - -RAISERROR('Setting up configuration variables',10,1) WITH NOWAIT; -DECLARE @StringToExecute NVARCHAR(MAX), - @ParmDefinitions NVARCHAR(4000), - @Parm1 NVARCHAR(4000), - @OurSessionID INT, - @LineFeed NVARCHAR(10), - @StockWarningHeader NVARCHAR(MAX) = N'', - @StockWarningFooter NVARCHAR(MAX) = N'', - @StockDetailsHeader NVARCHAR(MAX) = N'', - @StockDetailsFooter NVARCHAR(MAX) = N'', - @StartSampleTime DATETIMEOFFSET, - @FinishSampleTime DATETIMEOFFSET, - @FinishSampleTimeWaitFor DATETIME, - @AsOf1 DATETIMEOFFSET, - @AsOf2 DATETIMEOFFSET, - @ServiceName sysname, - @OutputTableNameFileStats_View NVARCHAR(256), - @OutputTableNamePerfmonStats_View NVARCHAR(256), - @OutputTableNamePerfmonStatsActuals_View NVARCHAR(256), - @OutputTableNameWaitStats_View NVARCHAR(256), - @OutputTableNameWaitStats_Categories NVARCHAR(256), - @OutputTableCleanupDate DATE, - @ObjectFullName NVARCHAR(2000), - @BlitzWho NVARCHAR(MAX) = N'EXEC dbo.sp_BlitzWho @ShowSleepingSPIDs = ' + CONVERT(NVARCHAR(1), @ShowSleepingSPIDs) + N';', - @BlitzCacheMinutesBack INT, - @UnquotedOutputServerName NVARCHAR(256) = @OutputServerName , - @UnquotedOutputDatabaseName NVARCHAR(256) = @OutputDatabaseName , - @UnquotedOutputSchemaName NVARCHAR(256) = @OutputSchemaName , - @LocalServerName NVARCHAR(128) = CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128)), - @dm_exec_query_statistics_xml BIT = 0, - @total_cpu_usage BIT = 0, - @get_thread_time_ms NVARCHAR(MAX) = N'', - @thread_time_ms FLOAT = 0; - -/* Sanitize our inputs */ -SELECT - @OutputTableNameFileStats_View = QUOTENAME(@OutputTableNameFileStats + '_Deltas'), - @OutputTableNamePerfmonStats_View = QUOTENAME(@OutputTableNamePerfmonStats + '_Deltas'), - @OutputTableNamePerfmonStatsActuals_View = QUOTENAME(@OutputTableNamePerfmonStats + '_Actuals'), - @OutputTableNameWaitStats_View = QUOTENAME(@OutputTableNameWaitStats + '_Deltas'), - @OutputTableNameWaitStats_Categories = QUOTENAME(@OutputTableNameWaitStats + '_Categories'); - -SELECT - @OutputDatabaseName = QUOTENAME(@OutputDatabaseName), - @OutputSchemaName = QUOTENAME(@OutputSchemaName), - @OutputTableName = QUOTENAME(@OutputTableName), - @OutputTableNameFileStats = QUOTENAME(@OutputTableNameFileStats), - @OutputTableNamePerfmonStats = QUOTENAME(@OutputTableNamePerfmonStats), - @OutputTableNameWaitStats = QUOTENAME(@OutputTableNameWaitStats), - @OutputTableCleanupDate = CAST( (DATEADD(DAY, -1 * @OutputTableRetentionDays, GETDATE() ) ) AS DATE), - /* @OutputTableNameBlitzCache = QUOTENAME(@OutputTableNameBlitzCache), We purposely don't sanitize this because sp_BlitzCache will */ - /* @OutputTableNameBlitzWho = QUOTENAME(@OutputTableNameBlitzWho), We purposely don't sanitize this because sp_BlitzWho will */ - @LineFeed = CHAR(13) + CHAR(10), - @OurSessionID = @@SPID, - @OutputType = UPPER(@OutputType); - -IF(@OutputType = 'NONE' AND (@OutputTableName IS NULL OR @OutputSchemaName IS NULL OR @OutputDatabaseName IS NULL)) -BEGIN - RAISERROR('This procedure should be called with a value for all @Output* parameters, as @OutputType is set to NONE',12,1); - RETURN; -END; - -IF UPPER(@OutputType) LIKE 'TOP 10%' SET @OutputType = 'Top10'; -IF @OutputType = 'Top10' SET @SinceStartup = 1; - -/* Logged Message - CheckID 38 */ -IF @LogMessage IS NOT NULL - BEGIN - - RAISERROR('Saving LogMessage to table',10,1) WITH NOWAIT; - - /* Try to set the output table parameters if they don't exist */ - IF @OutputSchemaName IS NULL AND @OutputTableName IS NULL AND @OutputDatabaseName IS NULL - BEGIN - SET @OutputSchemaName = N'[dbo]'; - SET @OutputTableName = N'[BlitzFirst]'; - - /* Look for the table in the current database */ - SELECT TOP 1 @OutputDatabaseName = QUOTENAME(TABLE_CATALOG) - FROM INFORMATION_SCHEMA.TABLES - WHERE TABLE_SCHEMA = 'dbo' AND TABLE_NAME = 'BlitzFirst'; - - IF @OutputDatabaseName IS NULL AND EXISTS (SELECT * FROM sys.databases WHERE name = 'DBAtools') - SET @OutputDatabaseName = '[DBAtools]'; - - END; - - IF @OutputDatabaseName IS NULL OR @OutputSchemaName IS NULL OR @OutputTableName IS NULL - OR NOT EXISTS ( SELECT * - FROM sys.databases - WHERE QUOTENAME([name]) = @OutputDatabaseName) - BEGIN - RAISERROR('We have a hard time logging a message without a valid @OutputDatabaseName, @OutputSchemaName, and @OutputTableName to log it to.', 0, 1) WITH NOWAIT; - RETURN; - END; - IF @LogMessageCheckDate IS NULL - SET @LogMessageCheckDate = SYSDATETIMEOFFSET(); - SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' - + @OutputSchemaName + ''') INSERT ' - + @OutputDatabaseName + '.' - + @OutputSchemaName + '.' - + @OutputTableName - + ' (ServerName, CheckDate, CheckID, Priority, FindingsGroup, Finding, Details, URL) VALUES( ' - + ' @SrvName, @LogMessageCheckDate, @LogMessageCheckID, @LogMessagePriority, @LogMessageFindingsGroup, @LogMessageFinding, @LogMessage, @LogMessageURL)'; - - EXECUTE sp_executesql @StringToExecute, - N'@SrvName NVARCHAR(128), @LogMessageCheckID INT, @LogMessagePriority TINYINT, @LogMessageFindingsGroup VARCHAR(50), @LogMessageFinding VARCHAR(200), @LogMessage NVARCHAR(4000), @LogMessageCheckDate DATETIMEOFFSET, @LogMessageURL VARCHAR(200)', - @LocalServerName, @LogMessageCheckID, @LogMessagePriority, @LogMessageFindingsGroup, @LogMessageFinding, @LogMessage, @LogMessageCheckDate, @LogMessageURL; - - RAISERROR('LogMessage saved to table. We have made a note of your activity. Keep up the good work.',10,1) WITH NOWAIT; - - RETURN; - END; - -IF @SinceStartup = 1 - SELECT @Seconds = 0, @ExpertMode = 1; - - -IF @OutputType = 'SCHEMA' -BEGIN - SELECT FieldList = '[Priority] TINYINT, [FindingsGroup] VARCHAR(50), [Finding] VARCHAR(200), [URL] VARCHAR(200), [Details] NVARCHAR(4000), [HowToStopIt] NVARCHAR(MAX), [QueryPlan] XML, [QueryText] NVARCHAR(MAX)'; - -END; -ELSE IF @AsOf IS NOT NULL AND @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @OutputTableName IS NOT NULL -BEGIN - /* They want to look into the past. */ - SET @AsOf1= DATEADD(mi, -15, @AsOf); - SET @AsOf2= DATEADD(mi, +15, @AsOf); - - SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' - + @OutputSchemaName + ''') SELECT CheckDate, [Priority], [FindingsGroup], [Finding], [URL], CAST([Details] AS [XML]) AS Details,' - + '[HowToStopIt], [CheckID], [StartTime], [LoginName], [NTUserName], [OriginalLoginName], [ProgramName], [HostName], [DatabaseID],' - + '[DatabaseName], [OpenTransactionCount], [QueryPlan], [QueryText] FROM ' - + @OutputDatabaseName + '.' - + @OutputSchemaName + '.' - + @OutputTableName - + ' WHERE CheckDate >= @AsOf1' - + ' AND CheckDate <= @AsOf2' - + ' /*ORDER BY CheckDate, Priority , FindingsGroup , Finding , Details*/;'; - EXEC sp_executesql @StringToExecute, - N'@AsOf1 DATETIMEOFFSET, @AsOf2 DATETIMEOFFSET', - @AsOf1, @AsOf2 - - -END; /* IF @AsOf IS NOT NULL AND @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @OutputTableName IS NOT NULL */ -ELSE IF @LogMessage IS NULL /* IF @OutputType = 'SCHEMA' */ -BEGIN - /* What's running right now? This is the first and last result set. */ - IF @SinceStartup = 0 AND @Seconds > 0 AND @ExpertMode = 1 AND @OutputType <> 'NONE' AND @OutputResultSets LIKE N'%BlitzWho_Start%' - BEGIN - IF OBJECT_ID('master.dbo.sp_BlitzWho') IS NULL AND OBJECT_ID('dbo.sp_BlitzWho') IS NULL - BEGIN - PRINT N'sp_BlitzWho is not installed in the current database_files. You can get a copy from http://FirstResponderKit.org'; - END; - ELSE - BEGIN - EXEC (@BlitzWho); - END; - END; /* IF @SinceStartup = 0 AND @Seconds > 0 AND @ExpertMode = 1 AND @OutputType <> 'NONE' - What's running right now? This is the first and last result set. */ - - /* Set start/finish times AFTER sp_BlitzWho runs. For more info: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2244 */ - IF @Seconds = 0 AND SERVERPROPERTY('EngineEdition') = 5 /*SERVERPROPERTY('Edition') = 'SQL Azure'*/ - WITH WaitTimes AS ( - SELECT wait_type, wait_time_ms, - NTILE(3) OVER(ORDER BY wait_time_ms) AS grouper - FROM sys.dm_os_wait_stats w - WHERE wait_type IN ('DIRTY_PAGE_POLL','HADR_FILESTREAM_IOMGR_IOCOMPLETION','LAZYWRITER_SLEEP', - 'LOGMGR_QUEUE','REQUEST_FOR_DEADLOCK_SEARCH','XE_TIMER_EVENT') - ) - SELECT @StartSampleTime = DATEADD(mi, AVG(-wait_time_ms / 1000 / 60), SYSDATETIMEOFFSET()), @FinishSampleTime = SYSDATETIMEOFFSET() - FROM WaitTimes - WHERE grouper = 2; - ELSE IF @Seconds = 0 AND SERVERPROPERTY('EngineEdition') <> 5 /*SERVERPROPERTY('Edition') <> 'SQL Azure'*/ - SELECT @StartSampleTime = DATEADD(MINUTE,DATEDIFF(MINUTE, GETDATE(), GETUTCDATE()),create_date) , @FinishSampleTime = SYSDATETIMEOFFSET() - FROM sys.databases - WHERE database_id = 2; - ELSE - SELECT @StartSampleTime = SYSDATETIMEOFFSET(), - @FinishSampleTime = DATEADD(ss, @Seconds, SYSDATETIMEOFFSET()), - @FinishSampleTimeWaitFor = DATEADD(ss, @Seconds, GETDATE()); - - - IF EXISTS - ( - - SELECT - 1/0 - FROM sys.all_columns AS ac - WHERE ac.object_id = OBJECT_ID('sys.dm_os_schedulers') - AND ac.name = 'total_cpu_usage_ms' - - ) - BEGIN - - SELECT - @total_cpu_usage = 1, - @get_thread_time_ms += - N' - SELECT - @thread_time_ms = - CONVERT - ( - FLOAT, - SUM(s.total_cpu_usage_ms) - ) - FROM sys.dm_os_schedulers AS s - WHERE s.status = ''VISIBLE ONLINE'' - AND s.is_online = 1 - OPTION(RECOMPILE); - '; - - END - ELSE - BEGIN - SELECT - @total_cpu_usage = 0, - @get_thread_time_ms += - N' - SELECT - @thread_time_ms = - CONVERT - ( - FLOAT, - SUM(s.total_worker_time / 1000.) - ) - FROM sys.dm_exec_query_stats AS s - OPTION(RECOMPILE); - '; - END - - RAISERROR('Now starting diagnostic analysis',10,1) WITH NOWAIT; - - /* - We start by creating #BlitzFirstResults. It's a temp table that will store - the results from our checks. Throughout the rest of this stored procedure, - we're running a series of checks looking for dangerous things inside the SQL - Server. When we find a problem, we insert rows into the temp table. At the - end, we return these results to the end user. - - #BlitzFirstResults has a CheckID field, but there's no Check table. As we do - checks, we insert data into this table, and we manually put in the CheckID. - We (Brent Ozar Unlimited) maintain a list of the checks by ID#. You can - download that from http://FirstResponderKit.org if you want to build - a tool that relies on the output of sp_BlitzFirst. - */ - - - IF OBJECT_ID('tempdb..#BlitzFirstResults') IS NOT NULL - DROP TABLE #BlitzFirstResults; - CREATE TABLE #BlitzFirstResults - ( - ID INT IDENTITY(1, 1) PRIMARY KEY CLUSTERED, - CheckID INT NOT NULL, - Priority TINYINT NOT NULL, - FindingsGroup VARCHAR(50) NOT NULL, - Finding VARCHAR(200) NOT NULL, - URL VARCHAR(200) NULL, - Details NVARCHAR(MAX) NULL, - HowToStopIt NVARCHAR(MAX) NULL, - QueryPlan [XML] NULL, - QueryText NVARCHAR(MAX) NULL, - StartTime DATETIMEOFFSET NULL, - LoginName NVARCHAR(128) NULL, - NTUserName NVARCHAR(128) NULL, - OriginalLoginName NVARCHAR(128) NULL, - ProgramName NVARCHAR(128) NULL, - HostName NVARCHAR(128) NULL, - DatabaseID INT NULL, - DatabaseName NVARCHAR(128) NULL, - OpenTransactionCount INT NULL, - QueryStatsNowID INT NULL, - QueryStatsFirstID INT NULL, - PlanHandle VARBINARY(64) NULL, - DetailsInt INT NULL, - QueryHash BINARY(8) - ); - - IF OBJECT_ID('tempdb..#WaitStats') IS NOT NULL - DROP TABLE #WaitStats; - CREATE TABLE #WaitStats ( - Pass TINYINT NOT NULL, - wait_type NVARCHAR(60), - wait_time_ms BIGINT, - thread_time_ms FLOAT, - signal_wait_time_ms BIGINT, - waiting_tasks_count BIGINT, - SampleTime datetimeoffset - ); - - IF OBJECT_ID('tempdb..#FileStats') IS NOT NULL - DROP TABLE #FileStats; - CREATE TABLE #FileStats ( - ID INT IDENTITY(1, 1) PRIMARY KEY CLUSTERED, - Pass TINYINT NOT NULL, - SampleTime DATETIMEOFFSET NOT NULL, - DatabaseID INT NOT NULL, - FileID INT NOT NULL, - DatabaseName NVARCHAR(256) , - FileLogicalName NVARCHAR(256) , - TypeDesc NVARCHAR(60) , - SizeOnDiskMB BIGINT , - io_stall_read_ms BIGINT , - num_of_reads BIGINT , - bytes_read BIGINT , - io_stall_write_ms BIGINT , - num_of_writes BIGINT , - bytes_written BIGINT, - PhysicalName NVARCHAR(520) , - avg_stall_read_ms INT , - avg_stall_write_ms INT - ); - - IF OBJECT_ID('tempdb..#QueryStats') IS NOT NULL - DROP TABLE #QueryStats; - CREATE TABLE #QueryStats ( - ID INT IDENTITY(1, 1) PRIMARY KEY CLUSTERED, - Pass INT NOT NULL, - SampleTime DATETIMEOFFSET NOT NULL, - [sql_handle] VARBINARY(64), - statement_start_offset INT, - statement_end_offset INT, - plan_generation_num BIGINT, - plan_handle VARBINARY(64), - execution_count BIGINT, - total_worker_time BIGINT, - total_physical_reads BIGINT, - total_logical_writes BIGINT, - total_logical_reads BIGINT, - total_clr_time BIGINT, - total_elapsed_time BIGINT, - creation_time DATETIMEOFFSET, - query_hash BINARY(8), - query_plan_hash BINARY(8), - Points TINYINT - ); - - IF OBJECT_ID('tempdb..#PerfmonStats') IS NOT NULL - DROP TABLE #PerfmonStats; - CREATE TABLE #PerfmonStats ( - ID INT IDENTITY(1, 1) PRIMARY KEY CLUSTERED, - Pass TINYINT NOT NULL, - SampleTime DATETIMEOFFSET NOT NULL, - [object_name] NVARCHAR(128) NOT NULL, - [counter_name] NVARCHAR(128) NOT NULL, - [instance_name] NVARCHAR(128) NULL, - [cntr_value] BIGINT NULL, - [cntr_type] INT NOT NULL, - [value_delta] BIGINT NULL, - [value_per_second] DECIMAL(18,2) NULL - ); - - IF OBJECT_ID('tempdb..#PerfmonCounters') IS NOT NULL - DROP TABLE #PerfmonCounters; - CREATE TABLE #PerfmonCounters ( - ID INT IDENTITY(1, 1) PRIMARY KEY CLUSTERED, - [object_name] NVARCHAR(128) NOT NULL, - [counter_name] NVARCHAR(128) NOT NULL, - [instance_name] NVARCHAR(128) NULL - ); - - IF OBJECT_ID('tempdb..#FilterPlansByDatabase') IS NOT NULL - DROP TABLE #FilterPlansByDatabase; - CREATE TABLE #FilterPlansByDatabase (DatabaseID INT PRIMARY KEY CLUSTERED); - - IF OBJECT_ID ('tempdb..#checkversion') IS NOT NULL - DROP TABLE #checkversion; - CREATE TABLE #checkversion ( - version NVARCHAR(128), - common_version AS SUBSTRING(version, 1, CHARINDEX('.', version) + 1 ), - major AS PARSENAME(CONVERT(VARCHAR(32), version), 4), - minor AS PARSENAME(CONVERT(VARCHAR(32), version), 3), - build AS PARSENAME(CONVERT(VARCHAR(32), version), 2), - revision AS PARSENAME(CONVERT(VARCHAR(32), version), 1) - ); - - IF OBJECT_ID('tempdb..##WaitCategories') IS NULL - BEGIN - /* We reuse this one by default rather than recreate it every time. */ - CREATE TABLE ##WaitCategories - ( - WaitType NVARCHAR(60) PRIMARY KEY CLUSTERED, - WaitCategory NVARCHAR(128) NOT NULL, - Ignorable BIT DEFAULT 0 - ); - END; /* IF OBJECT_ID('tempdb..##WaitCategories') IS NULL */ - - IF 527 > (SELECT COALESCE(SUM(1),0) FROM ##WaitCategories) + IF 527 > (SELECT COALESCE(SUM(1),0) FROM ##WaitCategories) BEGIN TRUNCATE TABLE ##WaitCategories; INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('ASYNC_IO_COMPLETION','Other Disk IO',0); @@ -33421,3925 +11063,15949 @@ BEGIN - IF OBJECT_ID('tempdb..#MasterFiles') IS NOT NULL - DROP TABLE #MasterFiles; - CREATE TABLE #MasterFiles (database_id INT, file_id INT, type_desc NVARCHAR(50), name NVARCHAR(255), physical_name NVARCHAR(255), size BIGINT); - /* Azure SQL Database doesn't have sys.master_files, so we have to build our own. */ - IF (SERVERPROPERTY('EngineEdition') = 5 /*(SERVERPROPERTY('Edition')) = 'SQL Azure' */ - AND (OBJECT_ID('sys.master_files') IS NULL)) - SET @StringToExecute = 'INSERT INTO #MasterFiles (database_id, file_id, type_desc, name, physical_name, size) SELECT DB_ID(), file_id, type_desc, name, physical_name, size FROM sys.database_files;'; - ELSE - SET @StringToExecute = 'INSERT INTO #MasterFiles (database_id, file_id, type_desc, name, physical_name, size) SELECT database_id, file_id, type_desc, name, physical_name, size FROM sys.master_files;'; - EXEC(@StringToExecute); + IF OBJECT_ID('tempdb..#MasterFiles') IS NOT NULL + DROP TABLE #MasterFiles; + CREATE TABLE #MasterFiles (database_id INT, file_id INT, type_desc NVARCHAR(50), name NVARCHAR(255), physical_name NVARCHAR(255), size BIGINT); + /* Azure SQL Database doesn't have sys.master_files, so we have to build our own. */ + IF (SERVERPROPERTY('EngineEdition') = 5 /*(SERVERPROPERTY('Edition')) = 'SQL Azure' */ + AND (OBJECT_ID('sys.master_files') IS NULL)) + SET @StringToExecute = 'INSERT INTO #MasterFiles (database_id, file_id, type_desc, name, physical_name, size) SELECT DB_ID(), file_id, type_desc, name, physical_name, size FROM sys.database_files;'; + ELSE + SET @StringToExecute = 'INSERT INTO #MasterFiles (database_id, file_id, type_desc, name, physical_name, size) SELECT database_id, file_id, type_desc, name, physical_name, size FROM sys.master_files;'; + EXEC(@StringToExecute); + + IF @FilterPlansByDatabase IS NOT NULL + BEGIN + IF UPPER(LEFT(@FilterPlansByDatabase,4)) = 'USER' + BEGIN + INSERT INTO #FilterPlansByDatabase (DatabaseID) + SELECT database_id + FROM sys.databases + WHERE [name] NOT IN ('master', 'model', 'msdb', 'tempdb'); + END; + ELSE + BEGIN + SET @FilterPlansByDatabase = @FilterPlansByDatabase + ',' + ;WITH a AS + ( + SELECT CAST(1 AS BIGINT) f, CHARINDEX(',', @FilterPlansByDatabase) t, 1 SEQ + UNION ALL + SELECT t + 1, CHARINDEX(',', @FilterPlansByDatabase, t + 1), SEQ + 1 + FROM a + WHERE CHARINDEX(',', @FilterPlansByDatabase, t + 1) > 0 + ) + INSERT #FilterPlansByDatabase (DatabaseID) + SELECT DISTINCT db.database_id + FROM a + INNER JOIN sys.databases db ON LTRIM(RTRIM(SUBSTRING(@FilterPlansByDatabase, a.f, a.t - a.f))) = db.name + WHERE SUBSTRING(@FilterPlansByDatabase, f, t - f) IS NOT NULL + OPTION (MAXRECURSION 0); + END; + END; + + IF OBJECT_ID('tempdb..#ReadableDBs') IS NOT NULL + DROP TABLE #ReadableDBs; + CREATE TABLE #ReadableDBs ( + database_id INT + ); + + IF EXISTS (SELECT * FROM sys.all_objects o WHERE o.name = 'dm_hadr_database_replica_states') + BEGIN + RAISERROR('Checking for Read intent databases to exclude',0,0) WITH NOWAIT; + + SET @StringToExecute = 'INSERT INTO #ReadableDBs (database_id) SELECT DBs.database_id FROM sys.databases DBs INNER JOIN sys.availability_replicas Replicas ON DBs.replica_id = Replicas.replica_id WHERE replica_server_name NOT IN (SELECT DISTINCT primary_replica FROM sys.dm_hadr_availability_group_states States) AND Replicas.secondary_role_allow_connections_desc = ''READ_ONLY'' AND replica_server_name = @@SERVERNAME;'; + EXEC(@StringToExecute); + + END + + DECLARE @v DECIMAL(6,2), + @build INT, + @memGrantSortSupported BIT = 1; + + RAISERROR (N'Determining SQL Server version.',0,1) WITH NOWAIT; + + INSERT INTO #checkversion (version) + SELECT CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)) + OPTION (RECOMPILE); + + + SELECT @v = common_version , + @build = build + FROM #checkversion + OPTION (RECOMPILE); + + IF (@v < 11) + OR (@v = 11 AND @build < 6020) + OR (@v = 12 AND @build < 5000) + OR (@v = 13 AND @build < 1601) + SET @memGrantSortSupported = 0; + + IF EXISTS (SELECT * FROM sys.all_objects WHERE name = 'dm_exec_query_statistics_xml') + AND ((@v = 13 AND @build >= 5337) /* This DMF causes assertion errors: https://support.microsoft.com/en-us/help/4490136/fix-assertion-error-occurs-when-you-use-sys-dm-exec-query-statistics-x */ + OR (@v = 14 AND @build >= 3162) + OR (@v >= 15) + OR (@v <= 12)) /* Azure */ + SET @dm_exec_query_statistics_xml = 1; + + + SET @StockWarningHeader = '', + @StockDetailsHeader = @StockDetailsHeader + ''; + + /* Get the instance name to use as a Perfmon counter prefix. */ + IF SERVERPROPERTY('EngineEdition') IN (5, 8) /*CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) = 'SQL Azure'*/ + SELECT TOP 1 @ServiceName = LEFT(object_name, (CHARINDEX(':', object_name) - 1)) + FROM sys.dm_os_performance_counters; + ELSE + BEGIN + SET @StringToExecute = 'INSERT INTO #PerfmonStats(object_name, Pass, SampleTime, counter_name, cntr_type) SELECT CASE WHEN @@SERVICENAME = ''MSSQLSERVER'' THEN ''SQLServer'' ELSE ''MSSQL$'' + @@SERVICENAME END, 0, SYSDATETIMEOFFSET(), ''stuffing'', 0 ;'; + EXEC(@StringToExecute); + SELECT @ServiceName = object_name FROM #PerfmonStats; + DELETE #PerfmonStats; + END; + + /* Build a list of queries that were run in the last 10 seconds. + We're looking for the death-by-a-thousand-small-cuts scenario + where a query is constantly running, and it doesn't have that + big of an impact individually, but it has a ton of impact + overall. We're going to build this list, and then after we + finish our @Seconds sample, we'll compare our plan cache to + this list to see what ran the most. */ + + /* Populate #QueryStats. SQL 2005 doesn't have query hash or query plan hash. */ + IF @CheckProcedureCache = 1 + BEGIN + RAISERROR('@CheckProcedureCache = 1, capturing first pass of plan cache',10,1) WITH NOWAIT; + IF @@VERSION LIKE 'Microsoft SQL Server 2005%' + BEGIN + IF @FilterPlansByDatabase IS NULL + BEGIN + SET @StringToExecute = N'INSERT INTO #QueryStats ([sql_handle], Pass, SampleTime, statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, Points) + SELECT [sql_handle], 1 AS Pass, SYSDATETIMEOFFSET(), statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, NULL AS query_hash, NULL AS query_plan_hash, 0 + FROM sys.dm_exec_query_stats qs + WHERE qs.last_execution_time >= (DATEADD(ss, -10, SYSDATETIMEOFFSET()));'; + END; + ELSE + BEGIN + SET @StringToExecute = N'INSERT INTO #QueryStats ([sql_handle], Pass, SampleTime, statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, Points) + SELECT [sql_handle], 1 AS Pass, SYSDATETIMEOFFSET(), statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, NULL AS query_hash, NULL AS query_plan_hash, 0 + FROM sys.dm_exec_query_stats qs + CROSS APPLY sys.dm_exec_plan_attributes(qs.plan_handle) AS attr + INNER JOIN #FilterPlansByDatabase dbs ON CAST(attr.value AS INT) = dbs.DatabaseID + WHERE qs.last_execution_time >= (DATEADD(ss, -10, SYSDATETIMEOFFSET())) + AND attr.attribute = ''dbid'';'; + END; + END; + ELSE + BEGIN + IF @FilterPlansByDatabase IS NULL + BEGIN + SET @StringToExecute = N'INSERT INTO #QueryStats ([sql_handle], Pass, SampleTime, statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, Points) + SELECT [sql_handle], 1 AS Pass, SYSDATETIMEOFFSET(), statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, 0 + FROM sys.dm_exec_query_stats qs + WHERE qs.last_execution_time >= (DATEADD(ss, -10, SYSDATETIMEOFFSET()));'; + END; + ELSE + BEGIN + SET @StringToExecute = N'INSERT INTO #QueryStats ([sql_handle], Pass, SampleTime, statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, Points) + SELECT [sql_handle], 1 AS Pass, SYSDATETIMEOFFSET(), statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, 0 + FROM sys.dm_exec_query_stats qs + CROSS APPLY sys.dm_exec_plan_attributes(qs.plan_handle) AS attr + INNER JOIN #FilterPlansByDatabase dbs ON CAST(attr.value AS INT) = dbs.DatabaseID + WHERE qs.last_execution_time >= (DATEADD(ss, -10, SYSDATETIMEOFFSET())) + AND attr.attribute = ''dbid'';'; + END; + END; + EXEC(@StringToExecute); + + /* Get the totals for the entire plan cache */ + INSERT INTO #QueryStats (Pass, SampleTime, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time) + SELECT -1 AS Pass, SYSDATETIMEOFFSET(), SUM(execution_count), SUM(total_worker_time), SUM(total_physical_reads), SUM(total_logical_writes), SUM(total_logical_reads), SUM(total_clr_time), SUM(total_elapsed_time), MIN(creation_time) + FROM sys.dm_exec_query_stats qs; + END; /*IF @CheckProcedureCache = 1 */ + + + IF EXISTS (SELECT * + FROM tempdb.sys.all_objects obj + INNER JOIN tempdb.sys.all_columns col1 ON obj.object_id = col1.object_id AND col1.name = 'object_name' + INNER JOIN tempdb.sys.all_columns col2 ON obj.object_id = col2.object_id AND col2.name = 'counter_name' + INNER JOIN tempdb.sys.all_columns col3 ON obj.object_id = col3.object_id AND col3.name = 'instance_name' + WHERE obj.name LIKE '%CustomPerfmonCounters%') + BEGIN + SET @StringToExecute = 'INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) SELECT [object_name],[counter_name],[instance_name] FROM #CustomPerfmonCounters'; + EXEC(@StringToExecute); + END; + ELSE + BEGIN + /* Add our default Perfmon counters */ + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Forwarded Records/sec', NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Page compression attempts/sec', NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Page Splits/sec', NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Skipped Ghosted Records/sec', NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Table Lock Escalations/sec', NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Worktables Created/sec', NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Availability Group','Active Hadr Threads','_Total'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Availability Replica','Bytes Received from Replica/sec','_Total'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Availability Replica','Bytes Sent to Replica/sec','_Total'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Availability Replica','Bytes Sent to Transport/sec','_Total'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Availability Replica','Flow Control Time (ms/sec)','_Total'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Availability Replica','Flow Control/sec','_Total'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Availability Replica','Resent Messages/sec','_Total'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Availability Replica','Sends to Replica/sec','_Total'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Page life expectancy', NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Page reads/sec', NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Page writes/sec', NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Readahead pages/sec', NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Target pages', NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Total pages', NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Databases','', NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Active Transactions','_Total'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Database Flow Control Delay', '_Total'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Database Flow Controls/sec', '_Total'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Group Commit Time', '_Total'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Group Commits/Sec', '_Total'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Log Apply Pending Queue', '_Total'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Log Apply Ready Queue', '_Total'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Log Compression Cache misses/sec', '_Total'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Log remaining for undo', '_Total'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Log Send Queue', '_Total'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Recovery Queue', '_Total'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Redo blocked/sec', '_Total'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Redo Bytes Remaining', '_Total'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Redone Bytes/sec', '_Total'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Databases','Log Bytes Flushed/sec', '_Total'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Databases','Log Growths', '_Total'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Databases','Log Pool LogWriter Pushes/sec', '_Total'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Databases','Log Shrinks', '_Total'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Databases','Transactions/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Databases','Write Transactions/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Databases','XTP Memory Used (KB)',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Exec Statistics','Distributed Query', 'Execs in progress'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Exec Statistics','DTC calls', 'Execs in progress'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Exec Statistics','Extended Procedures', 'Execs in progress'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Exec Statistics','OLEDB calls', 'Execs in progress'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':General Statistics','Active Temp Tables', NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':General Statistics','Logins/sec', NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':General Statistics','Logouts/sec', NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':General Statistics','Mars Deadlocks', NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':General Statistics','Processes blocked', NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Locks','Number of Deadlocks/sec', NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Memory Manager','Memory Grants Pending', NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Errors','Errors/sec', '_Total'); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','Batch Requests/sec', NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','Forced Parameterizations/sec', NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','Guided plan executions/sec', NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','SQL Attention rate', NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','SQL Compilations/sec', NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','SQL Re-Compilations/sec', NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Workload Group Stats','Query optimizations/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Workload Group Stats','Suboptimal plans/sec',NULL); + /* Below counters added by Jefferson Elias */ + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Worktables From Cache Base',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Worktables From Cache Ratio',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Database pages',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Free pages',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Stolen pages',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Memory Manager','Granted Workspace Memory (KB)',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Memory Manager','Maximum Workspace Memory (KB)',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Memory Manager','Target Server Memory (KB)',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Memory Manager','Total Server Memory (KB)',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Buffer cache hit ratio',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Buffer cache hit ratio base',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Checkpoint pages/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Free list stalls/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Lazy writes/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','Auto-Param Attempts/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','Failed Auto-Params/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','Safe Auto-Params/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','Unsafe Auto-Params/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Workfiles Created/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':General Statistics','User Connections',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Latches','Average Latch Wait Time (ms)',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Latches','Average Latch Wait Time Base',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Latches','Latch Waits/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Latches','Total Latch Wait Time (ms)',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Locks','Average Wait Time (ms)',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Locks','Average Wait Time Base',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Locks','Lock Requests/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Locks','Lock Timeouts/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Locks','Lock Wait Time (ms)',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Locks','Lock Waits/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Transactions','Longest Transaction Running Time',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Full Scans/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Index Searches/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Page lookups/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Cursor Manager by Type','Active cursors',NULL); + /* Below counters are for In-Memory OLTP (Hekaton), which have a different naming convention. + And yes, they actually hard-coded the version numbers into the counters, and SQL 2019 still says 2017, oddly. + For why, see: https://connect.microsoft.com/SQLServer/feedback/details/817216/xtp-perfmon-counters-should-appear-under-sql-server-perfmon-counter-group + */ + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP Cursors','Expired rows removed/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP Cursors','Expired rows touched/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP Garbage Collection','Rows processed/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP IO Governor','Io Issued/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP Phantom Processor','Phantom expired rows touched/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP Phantom Processor','Phantom rows touched/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP Transaction Log','Log bytes written/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP Transaction Log','Log records written/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP Transactions','Transactions aborted by user/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP Transactions','Transactions aborted/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP Transactions','Transactions created/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP Cursors','Expired rows removed/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP Cursors','Expired rows touched/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP Garbage Collection','Rows processed/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP IO Governor','Io Issued/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP Phantom Processor','Phantom expired rows touched/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP Phantom Processor','Phantom rows touched/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP Transaction Log','Log bytes written/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP Transaction Log','Log records written/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP Transactions','Transactions aborted by user/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP Transactions','Transactions aborted/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP Transactions','Transactions created/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP Cursors','Expired rows removed/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP Cursors','Expired rows touched/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP Garbage Collection','Rows processed/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP IO Governor','Io Issued/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP Phantom Processor','Phantom expired rows touched/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP Phantom Processor','Phantom rows touched/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP Transaction Log','Log bytes written/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP Transaction Log','Log records written/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP Transactions','Transactions aborted by user/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP Transactions','Transactions aborted/sec',NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP Transactions','Transactions created/sec',NULL); + END; + + + IF @total_cpu_usage IN (0, 1) + BEGIN + EXEC sys.sp_executesql + @get_thread_time_ms, + N'@thread_time_ms FLOAT OUTPUT', + @thread_time_ms OUTPUT; + END + + /* Populate #FileStats, #PerfmonStats, #WaitStats with DMV data. + After we finish doing our checks, we'll take another sample and compare them. */ + RAISERROR('Capturing first pass of wait stats, perfmon counters, file stats',10,1) WITH NOWAIT; + SET @StringToExecute = N' + INSERT #WaitStats(Pass, SampleTime, wait_type, wait_time_ms, thread_time_ms, signal_wait_time_ms, waiting_tasks_count) + SELECT + x.Pass, + x.SampleTime, + x.wait_type, + SUM(x.sum_wait_time_ms) AS sum_wait_time_ms, + CASE @Seconds + WHEN 0 + THEN 0 + ELSE @thread_time_ms + END AS thread_time_ms, + SUM(x.sum_signal_wait_time_ms) AS sum_signal_wait_time_ms, + SUM(x.sum_waiting_tasks) AS sum_waiting_tasks + FROM ( + SELECT + 1 AS Pass, + CASE @Seconds WHEN 0 THEN @StartSampleTime ELSE SYSDATETIMEOFFSET() END AS SampleTime, + owt.wait_type, + CASE @Seconds WHEN 0 THEN 0 ELSE SUM(owt.wait_duration_ms) OVER (PARTITION BY owt.wait_type, owt.session_id) + - CASE WHEN @Seconds = 0 THEN 0 ELSE (@Seconds * 1000) END END AS sum_wait_time_ms, + 0 AS sum_signal_wait_time_ms, + 0 AS sum_waiting_tasks + FROM sys.dm_os_waiting_tasks owt + WHERE owt.session_id > 50 + AND owt.wait_duration_ms >= CASE @Seconds WHEN 0 THEN 0 ELSE @Seconds * 1000 END + UNION ALL + SELECT + 1 AS Pass, + CASE @Seconds WHEN 0 THEN @StartSampleTime ELSE SYSDATETIMEOFFSET() END AS SampleTime, + os.wait_type, + CASE @Seconds WHEN 0 THEN 0 ELSE SUM(os.wait_time_ms) OVER (PARTITION BY os.wait_type) END AS sum_wait_time_ms, + CASE @Seconds WHEN 0 THEN 0 ELSE SUM(os.signal_wait_time_ms) OVER (PARTITION BY os.wait_type ) END AS sum_signal_wait_time_ms, + CASE @Seconds WHEN 0 THEN 0 ELSE SUM(os.waiting_tasks_count) OVER (PARTITION BY os.wait_type) END AS sum_waiting_tasks '; + + IF SERVERPROPERTY('EngineEdition') = 5 /*SERVERPROPERTY('Edition') = 'SQL Azure'*/ + SET @StringToExecute = @StringToExecute + N' FROM sys.dm_db_wait_stats os '; + ELSE + SET @StringToExecute = @StringToExecute + N' FROM sys.dm_os_wait_stats os '; + + SET @StringToExecute = @StringToExecute + N' + ) x + WHERE NOT EXISTS + ( + SELECT * + FROM ##WaitCategories AS wc + WHERE wc.WaitType = x.wait_type + AND wc.Ignorable = 1 + ) + GROUP BY x.Pass, x.SampleTime, x.wait_type + ORDER BY sum_wait_time_ms DESC;' + + EXEC sys.sp_executesql + @StringToExecute, + N'@StartSampleTime DATETIMEOFFSET, + @Seconds INT, + @thread_time_ms FLOAT', + @StartSampleTime, + @Seconds, + @thread_time_ms; + + WITH w AS + ( + SELECT + total_waits = + CONVERT + ( + FLOAT, + SUM(ws.wait_time_ms) + ) + FROM #WaitStats AS ws + WHERE Pass = 1 + ) + UPDATE ws + SET ws.thread_time_ms += w.total_waits + FROM #WaitStats AS ws + CROSS JOIN w + WHERE ws.Pass = 1 + OPTION(RECOMPILE); + + INSERT INTO #FileStats (Pass, SampleTime, DatabaseID, FileID, DatabaseName, FileLogicalName, SizeOnDiskMB, io_stall_read_ms , + num_of_reads, [bytes_read] , io_stall_write_ms,num_of_writes, [bytes_written], PhysicalName, TypeDesc) + SELECT + 1 AS Pass, + CASE @Seconds WHEN 0 THEN @StartSampleTime ELSE SYSDATETIMEOFFSET() END AS SampleTime, + mf.[database_id], + mf.[file_id], + DB_NAME(vfs.database_id) AS [db_name], + mf.name + N' [' + mf.type_desc COLLATE SQL_Latin1_General_CP1_CI_AS + N']' AS file_logical_name , + CAST(( ( vfs.size_on_disk_bytes / 1024.0 ) / 1024.0 ) AS INT) AS size_on_disk_mb , + CASE @Seconds WHEN 0 THEN 0 ELSE vfs.io_stall_read_ms END , + CASE @Seconds WHEN 0 THEN 0 ELSE vfs.num_of_reads END , + CASE @Seconds WHEN 0 THEN 0 ELSE vfs.[num_of_bytes_read] END , + CASE @Seconds WHEN 0 THEN 0 ELSE vfs.io_stall_write_ms END , + CASE @Seconds WHEN 0 THEN 0 ELSE vfs.num_of_writes END , + CASE @Seconds WHEN 0 THEN 0 ELSE vfs.[num_of_bytes_written] END , + mf.physical_name, + mf.type_desc + FROM sys.dm_io_virtual_file_stats (NULL, NULL) AS vfs + INNER JOIN #MasterFiles AS mf ON vfs.file_id = mf.file_id + AND vfs.database_id = mf.database_id + WHERE vfs.num_of_reads > 0 + OR vfs.num_of_writes > 0; + + INSERT INTO #PerfmonStats (Pass, SampleTime, [object_name],[counter_name],[instance_name],[cntr_value],[cntr_type]) + SELECT 1 AS Pass, + CASE @Seconds WHEN 0 THEN @StartSampleTime ELSE SYSDATETIMEOFFSET() END AS SampleTime, RTRIM(dmv.object_name), RTRIM(dmv.counter_name), RTRIM(dmv.instance_name), CASE @Seconds WHEN 0 THEN 0 ELSE dmv.cntr_value END, dmv.cntr_type + FROM #PerfmonCounters counters + INNER JOIN sys.dm_os_performance_counters dmv ON counters.counter_name COLLATE SQL_Latin1_General_CP1_CI_AS = RTRIM(dmv.counter_name) COLLATE SQL_Latin1_General_CP1_CI_AS + AND counters.[object_name] COLLATE SQL_Latin1_General_CP1_CI_AS = RTRIM(dmv.[object_name]) COLLATE SQL_Latin1_General_CP1_CI_AS + AND (counters.[instance_name] IS NULL OR counters.[instance_name] COLLATE SQL_Latin1_General_CP1_CI_AS = RTRIM(dmv.[instance_name]) COLLATE SQL_Latin1_General_CP1_CI_AS); + + /* For Github #2743: */ + CREATE TABLE #TempdbOperationalStats (object_id BIGINT PRIMARY KEY CLUSTERED, + forwarded_fetch_count BIGINT); + INSERT INTO #TempdbOperationalStats (object_id, forwarded_fetch_count) + SELECT object_id, forwarded_fetch_count + FROM tempdb.sys.dm_db_index_operational_stats(DB_ID('tempdb'), NULL, NULL, NULL) os + WHERE os.database_id = DB_ID('tempdb') + AND os.forwarded_fetch_count > 100; + + + /* If they want to run sp_BlitzWho and export to table, go for it. */ + IF @OutputTableNameBlitzWho IS NOT NULL + AND @OutputDatabaseName IS NOT NULL + AND @OutputSchemaName IS NOT NULL + AND EXISTS ( SELECT * + FROM sys.databases + WHERE QUOTENAME([name]) = @OutputDatabaseName) + BEGIN + RAISERROR('Logging sp_BlitzWho to table',10,1) WITH NOWAIT; + EXEC sp_BlitzWho @OutputDatabaseName = @UnquotedOutputDatabaseName, @OutputSchemaName = @UnquotedOutputSchemaName, @OutputTableName = @OutputTableNameBlitzWho, @CheckDateOverride = @StartSampleTime, @OutputTableRetentionDays = @OutputTableRetentionDays; + END + + RAISERROR('Beginning investigatory queries',10,1) WITH NOWAIT; + + + /* Maintenance Tasks Running - Backup Running - CheckID 1 */ + IF @Seconds > 0 + BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 1',10,1) WITH NOWAIT; + END + + IF EXISTS(SELECT * FROM sys.dm_exec_requests WHERE total_elapsed_time > 300000 AND command LIKE 'BACKUP%') + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, QueryHash) + SELECT 1 AS CheckID, + 1 AS Priority, + 'Maintenance Tasks Running' AS FindingGroup, + 'Backup Running' AS Finding, + 'https://www.brentozar.com/askbrent/backups/' AS URL, + 'Backup of ' + DB_NAME(db.resource_database_id) + ' database (' + (SELECT CAST(CAST(SUM(size * 8.0 / 1024 / 1024) AS BIGINT) AS NVARCHAR) FROM #MasterFiles WHERE database_id = db.resource_database_id) + 'GB) ' + @LineFeed + + CAST(r.percent_complete AS NVARCHAR(100)) + '% complete, has been running since ' + CAST(r.start_time AS NVARCHAR(100)) + '. ' + @LineFeed + + CASE WHEN COALESCE(s.nt_username, s.loginame) IS NOT NULL THEN (' Login: ' + COALESCE(s.nt_username, s.loginame) + ' ') ELSE '' END AS Details, + 'KILL ' + CAST(r.session_id AS NVARCHAR(100)) + ';' AS HowToStopIt, + pl.query_plan AS QueryPlan, + r.start_time AS StartTime, + s.loginame AS LoginName, + s.nt_username AS NTUserName, + s.[program_name] AS ProgramName, + s.[hostname] AS HostName, + db.[resource_database_id] AS DatabaseID, + DB_NAME(db.resource_database_id) AS DatabaseName, + 0 AS OpenTransactionCount, + r.query_hash + FROM sys.dm_exec_requests r + INNER JOIN sys.dm_exec_connections c ON r.session_id = c.session_id + INNER JOIN sys.sysprocesses AS s ON r.session_id = s.spid AND s.ecid = 0 + INNER JOIN + ( + SELECT DISTINCT + t.request_session_id, + t.resource_database_id + FROM sys.dm_tran_locks AS t + WHERE t.resource_type = N'DATABASE' + AND t.request_mode = N'S' + AND t.request_status = N'GRANT' + AND t.request_owner_type = N'SHARED_TRANSACTION_WORKSPACE' + ) AS db ON s.spid = db.request_session_id AND s.dbid = db.resource_database_id + CROSS APPLY sys.dm_exec_query_plan(r.plan_handle) pl + WHERE r.command LIKE 'BACKUP%' + AND r.start_time <= DATEADD(minute, -5, GETDATE()) + AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs); + END + + /* If there's a backup running, add details explaining how long full backup has been taking in the last month. */ + IF @Seconds > 0 AND SERVERPROPERTY('EngineEdition') <> 5 /*CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) <> 'SQL Azure'*/ + BEGIN + SET @StringToExecute = 'UPDATE #BlitzFirstResults SET Details = Details + '' Over the last 60 days, the full backup usually takes '' + CAST((SELECT AVG(DATEDIFF(mi, bs.backup_start_date, bs.backup_finish_date)) FROM msdb.dbo.backupset bs WHERE abr.DatabaseName = bs.database_name AND bs.type = ''D'' AND bs.backup_start_date > DATEADD(dd, -60, SYSDATETIMEOFFSET()) AND bs.backup_finish_date IS NOT NULL) AS NVARCHAR(100)) + '' minutes.'' FROM #BlitzFirstResults abr WHERE abr.CheckID = 1 AND EXISTS (SELECT * FROM msdb.dbo.backupset bs WHERE bs.type = ''D'' AND bs.backup_start_date > DATEADD(dd, -60, SYSDATETIMEOFFSET()) AND bs.backup_finish_date IS NOT NULL AND abr.DatabaseName = bs.database_name AND DATEDIFF(mi, bs.backup_start_date, bs.backup_finish_date) > 1)'; + EXEC(@StringToExecute); + END; + + + /* Maintenance Tasks Running - DBCC CHECK* Running - CheckID 2 */ + IF @Seconds > 0 + BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 2',10,1) WITH NOWAIT; + END + + IF EXISTS (SELECT * FROM sys.dm_exec_requests WHERE command LIKE 'DBCC%' AND total_elapsed_time > 5000) + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, QueryHash) + SELECT 2 AS CheckID, + 1 AS Priority, + 'Maintenance Tasks Running' AS FindingGroup, + 'DBCC CHECK* Running' AS Finding, + 'https://www.brentozar.com/askbrent/dbcc/' AS URL, + 'Corruption check of ' + DB_NAME(db.resource_database_id) + ' database (' + (SELECT CAST(CAST(SUM(size * 8.0 / 1024 / 1024) AS BIGINT) AS NVARCHAR) FROM #MasterFiles WHERE database_id = db.resource_database_id) + 'GB) 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, + s.login_name AS LoginName, + s.nt_user_name AS NTUserName, + s.[program_name] AS ProgramName, + s.[host_name] AS HostName, + db.[resource_database_id] AS DatabaseID, + DB_NAME(db.resource_database_id) AS DatabaseName, + 0 AS OpenTransactionCount, + r.query_hash + FROM sys.dm_exec_requests r + INNER JOIN sys.dm_exec_connections c ON r.session_id = c.session_id + INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id + INNER JOIN (SELECT DISTINCT l.request_session_id, l.resource_database_id + FROM sys.dm_tran_locks l + INNER JOIN sys.databases d ON l.resource_database_id = d.database_id + WHERE l.resource_type = N'DATABASE' + AND l.request_mode = N'S' + AND l.request_status = N'GRANT' + AND l.request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.session_id = db.request_session_id + OUTER APPLY sys.dm_exec_query_plan(r.plan_handle) pl + OUTER APPLY sys.dm_exec_sql_text(r.sql_handle) AS t + WHERE r.command LIKE 'DBCC%' + AND CAST(t.text AS NVARCHAR(4000)) NOT LIKE '%dm_db_index_physical_stats%' + AND CAST(t.text AS NVARCHAR(4000)) NOT LIKE '%ALTER INDEX%' + AND CAST(t.text AS NVARCHAR(4000)) NOT LIKE '%fileproperty%' + AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs); + END + + /* Maintenance Tasks Running - Restore Running - CheckID 3 */ + IF @Seconds > 0 + BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 3',10,1) WITH NOWAIT; + END + + IF EXISTS (SELECT * FROM sys.dm_exec_requests WHERE command LIKE 'RESTORE%' AND total_elapsed_time > 5000) + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, QueryHash) + SELECT 3 AS CheckID, + 1 AS Priority, + 'Maintenance Tasks Running' AS FindingGroup, + 'Restore Running' AS Finding, + 'https://www.brentozar.com/askbrent/backups/' AS URL, + 'Restore of ' + DB_NAME(db.resource_database_id) + ' database (' + (SELECT CAST(CAST(SUM(size * 8.0 / 1024 / 1024) AS BIGINT) AS NVARCHAR) FROM #MasterFiles WHERE database_id = db.resource_database_id) + '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, + s.login_name AS LoginName, + s.nt_user_name AS NTUserName, + s.[program_name] AS ProgramName, + s.[host_name] AS HostName, + db.[resource_database_id] AS DatabaseID, + DB_NAME(db.resource_database_id) AS DatabaseName, + 0 AS OpenTransactionCount, + r.query_hash + FROM sys.dm_exec_requests r + INNER JOIN sys.dm_exec_connections c ON r.session_id = c.session_id + INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id + INNER JOIN ( + SELECT DISTINCT request_session_id, resource_database_id + FROM sys.dm_tran_locks + WHERE resource_type = N'DATABASE' + AND request_mode = N'S' + AND request_status = N'GRANT') AS db ON s.session_id = db.request_session_id + CROSS APPLY sys.dm_exec_query_plan(r.plan_handle) pl + WHERE r.command LIKE 'RESTORE%' + AND s.program_name <> 'SQL Server Log Shipping' + AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs); + END + + /* SQL Server Internal Maintenance - Database File Growing - CheckID 4 */ + IF @Seconds > 0 + BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 4',10,1) WITH NOWAIT; + END + + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount) + SELECT 4 AS CheckID, + 1 AS Priority, + 'SQL Server Internal Maintenance' AS FindingGroup, + 'Database File Growing' AS Finding, + 'https://www.brentozar.com/go/instant' AS URL, + 'SQL Server is waiting for Windows to provide storage space for a database restore, a data file growth, or a log file growth. This task has been running since ' + CAST(r.start_time AS NVARCHAR(100)) + '.' + @LineFeed + 'Check the query plan (expert mode) to identify the database involved.' AS Details, + 'Unfortunately, you can''t stop this, but you can prevent it next time. Check out https://www.brentozar.com/go/instant for details.' AS HowToStopIt, + pl.query_plan AS QueryPlan, + r.start_time AS StartTime, + s.login_name AS LoginName, + s.nt_user_name AS NTUserName, + s.[program_name] AS ProgramName, + s.[host_name] AS HostName, + NULL AS DatabaseID, + NULL AS DatabaseName, + 0 AS OpenTransactionCount + FROM sys.dm_os_waiting_tasks t + INNER JOIN sys.dm_exec_connections c ON t.session_id = c.session_id + INNER JOIN sys.dm_exec_requests r ON t.session_id = r.session_id + INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id + CROSS APPLY sys.dm_exec_query_plan(r.plan_handle) pl + WHERE t.wait_type = 'PREEMPTIVE_OS_WRITEFILEGATHER' + AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs); + END + + /* Query Problems - Long-Running Query Blocking Others - CheckID 5 */ + IF SERVERPROPERTY('EngineEdition') <> 5 /*SERVERPROPERTY('Edition') <> 'SQL Azure'*/ AND @Seconds > 0 AND EXISTS(SELECT * FROM sys.dm_os_waiting_tasks WHERE wait_type LIKE 'LCK%' AND wait_duration_ms > 30000) + BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 5',10,1) WITH NOWAIT; + END + + SET @StringToExecute = N'INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, QueryText, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, QueryHash) + SELECT 5 AS CheckID, + 1 AS Priority, + ''Query Problems'' AS FindingGroup, + ''Long-Running Query Blocking Others'' AS Finding, + ''https://www.brentozar.com/go/blocking'' AS URL, + ''Query in '' + COALESCE(DB_NAME(COALESCE((SELECT TOP 1 dbid FROM sys.dm_exec_sql_text(r.sql_handle)), + (SELECT TOP 1 t.dbid FROM master..sysprocesses spBlocker CROSS APPLY sys.dm_exec_sql_text(spBlocker.sql_handle) t WHERE spBlocker.spid = tBlocked.blocking_session_id))), ''(Unknown)'') + '' has a last request start time of '' + CAST(s.last_request_start_time AS NVARCHAR(100)) + ''. Query follows: ' + + @LineFeed + @LineFeed + + '''+ CAST(COALESCE((SELECT TOP 1 [text] FROM sys.dm_exec_sql_text(r.sql_handle)), + (SELECT TOP 1 [text] FROM master..sysprocesses spBlocker CROSS APPLY sys.dm_exec_sql_text(spBlocker.sql_handle) WHERE spBlocker.spid = tBlocked.blocking_session_id), '''') AS NVARCHAR(2000)) AS Details, + ''KILL '' + CAST(tBlocked.blocking_session_id AS NVARCHAR(100)) + '';'' AS HowToStopIt, + (SELECT TOP 1 query_plan FROM sys.dm_exec_query_plan(r.plan_handle)) AS QueryPlan, + COALESCE((SELECT TOP 1 [text] FROM sys.dm_exec_sql_text(r.sql_handle)), + (SELECT TOP 1 [text] FROM master..sysprocesses spBlocker CROSS APPLY sys.dm_exec_sql_text(spBlocker.sql_handle) WHERE spBlocker.spid = tBlocked.blocking_session_id)) AS QueryText, + r.start_time AS StartTime, + s.login_name AS LoginName, + s.nt_user_name AS NTUserName, + s.[program_name] AS ProgramName, + s.[host_name] AS HostName, + r.[database_id] AS DatabaseID, + DB_NAME(r.database_id) AS DatabaseName, + 0 AS OpenTransactionCount, + r.query_hash + FROM sys.dm_os_waiting_tasks tBlocked + INNER JOIN sys.dm_exec_sessions s ON tBlocked.blocking_session_id = s.session_id + LEFT OUTER JOIN sys.dm_exec_requests r ON s.session_id = r.session_id + INNER JOIN sys.dm_exec_connections c ON s.session_id = c.session_id + WHERE tBlocked.wait_type LIKE ''LCK%'' AND tBlocked.wait_duration_ms > 30000 + /* And the blocking session ID is not blocked by anyone else: */ + AND NOT EXISTS(SELECT * FROM sys.dm_os_waiting_tasks tBlocking WHERE s.session_id = tBlocking.session_id AND tBlocking.session_id <> tBlocking.blocking_session_id AND tBlocking.blocking_session_id IS NOT NULL) + AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs);'; + EXECUTE sp_executesql @StringToExecute; + END; + + /* Query Problems - Plan Cache Erased Recently - CheckID 7 */ + IF DATEADD(mi, -15, SYSDATETIME()) < (SELECT TOP 1 creation_time FROM sys.dm_exec_query_stats ORDER BY creation_time) + BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 7',10,1) WITH NOWAIT; + END + + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) + SELECT TOP 1 7 AS CheckID, + 50 AS Priority, + 'Query Problems' AS FindingGroup, + 'Plan Cache Erased Recently' AS Finding, + 'https://www.brentozar.com/askbrent/plan-cache-erased-recently/' AS URL, + 'The oldest query in the plan cache was created at ' + CAST(creation_time AS NVARCHAR(50)) + '. ' + @LineFeed + @LineFeed + + 'This indicates that someone ran DBCC FREEPROCCACHE at that time,' + @LineFeed + + 'Giving SQL Server temporary amnesia. Now, as queries come in,' + @LineFeed + + 'SQL Server has to use a lot of CPU power in order to build execution' + @LineFeed + + 'plans and put them in cache again. This causes high CPU loads.' AS Details, + 'Find who did that, and stop them from doing it again.' AS HowToStopIt + FROM sys.dm_exec_query_stats + ORDER BY creation_time; + END; + + + /* Query Problems - Sleeping Query with Open Transactions - CheckID 8 */ + IF @Seconds > 0 + BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 8',10,1) WITH NOWAIT; + END + + IF EXISTS (SELECT * FROM sys.dm_exec_requests WHERE total_elapsed_time > 5000 AND request_id > 0) + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, OpenTransactionCount) + SELECT 8 AS CheckID, + 50 AS Priority, + 'Query Problems' AS FindingGroup, + 'Sleeping Query with Open Transactions' AS Finding, + 'https://www.brentozar.com/askbrent/sleeping-query-with-open-transactions/' AS URL, + 'Database: ' + DB_NAME(db.resource_database_id) + @LineFeed + 'Host: ' + s.hostname + @LineFeed + 'Program: ' + s.[program_name] + @LineFeed + 'Asleep with open transactions and locks since ' + CAST(s.last_batch AS NVARCHAR(100)) + '. ' AS Details, + 'KILL ' + CAST(s.spid AS NVARCHAR(100)) + ';' AS HowToStopIt, + s.last_batch AS StartTime, + s.loginame AS LoginName, + s.nt_username AS NTUserName, + s.[program_name] AS ProgramName, + s.hostname AS HostName, + db.[resource_database_id] AS DatabaseID, + DB_NAME(db.resource_database_id) AS DatabaseName, + (SELECT TOP 1 [text] FROM sys.dm_exec_sql_text(c.most_recent_sql_handle)) AS QueryText, + s.open_tran AS OpenTransactionCount + FROM sys.sysprocesses s + INNER JOIN sys.dm_exec_connections c ON s.spid = c.session_id + INNER JOIN ( + SELECT DISTINCT request_session_id, resource_database_id + FROM sys.dm_tran_locks + WHERE resource_type = N'DATABASE' + AND request_mode = N'S' + AND request_status = N'GRANT' + AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.spid = db.request_session_id + WHERE s.status = 'sleeping' + AND s.open_tran > 0 + AND s.last_batch < DATEADD(ss, -10, SYSDATETIME()) + AND EXISTS(SELECT * FROM sys.dm_tran_locks WHERE request_session_id = s.spid + AND NOT (resource_type = N'DATABASE' AND request_mode = N'S' AND request_status = N'GRANT' AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE')); + END + + /*Query Problems - Clients using implicit transactions - CheckID 37 */ + IF @Seconds > 0 + AND ( @@VERSION NOT LIKE 'Microsoft SQL Server 2005%' + AND @@VERSION NOT LIKE 'Microsoft SQL Server 2008%' + AND @@VERSION NOT LIKE 'Microsoft SQL Server 2008 R2%' ) + BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 37',10,1) WITH NOWAIT; + END + + SET @StringToExecute = N'INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, OpenTransactionCount) + SELECT 37 AS CheckId, + 50 AS Priority, + ''Query Problems'' AS FindingsGroup, + ''Implicit Transactions'', + ''https://www.brentozar.com/go/ImplicitTransactions/'' AS URL, + ''Database: '' + DB_NAME(s.database_id) + '' '' + CHAR(13) + CHAR(10) + + ''Host: '' + s.[host_name] + '' '' + CHAR(13) + CHAR(10) + + ''Program: '' + s.[program_name] + '' '' + CHAR(13) + CHAR(10) + + CONVERT(NVARCHAR(10), s.open_transaction_count) + + '' open transactions since: '' + + CONVERT(NVARCHAR(30), tat.transaction_begin_time) + ''. '' + AS Details, + ''Run sp_BlitzWho and check the is_implicit_transaction column to spot the culprits. +If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, + tat.transaction_begin_time, + s.login_name, + s.nt_user_name, + s.program_name, + s.host_name, + s.database_id, + DB_NAME(s.database_id) AS DatabaseName, + NULL AS Querytext, + s.open_transaction_count AS OpenTransactionCount + FROM sys.dm_tran_active_transactions AS tat + LEFT JOIN sys.dm_tran_session_transactions AS tst + ON tst.transaction_id = tat.transaction_id + LEFT JOIN sys.dm_exec_sessions AS s + ON s.session_id = tst.session_id + WHERE tat.name = ''implicit_transaction''; + ' + EXECUTE sp_executesql @StringToExecute; + END; + + /* Query Problems - Query Rolling Back - CheckID 9 */ + IF @Seconds > 0 + BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 9',10,1) WITH NOWAIT; + END + + IF EXISTS (SELECT * FROM sys.dm_exec_requests WHERE total_elapsed_time > 5000 AND request_id > 0) + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, QueryHash) + SELECT 9 AS CheckID, + 1 AS Priority, + 'Query Problems' AS FindingGroup, + 'Query Rolling Back' AS Finding, + 'https://www.brentozar.com/askbrent/rollback/' AS URL, + 'Rollback started at ' + CAST(r.start_time AS NVARCHAR(100)) + ', is ' + CAST(r.percent_complete AS NVARCHAR(100)) + '% complete.' AS Details, + 'Unfortunately, you can''t stop this. Whatever you do, don''t restart the server in an attempt to fix it - SQL Server will keep rolling back.' AS HowToStopIt, + r.start_time AS StartTime, + s.login_name AS LoginName, + s.nt_user_name AS NTUserName, + s.[program_name] AS ProgramName, + s.[host_name] AS HostName, + db.[resource_database_id] AS DatabaseID, + DB_NAME(db.resource_database_id) AS DatabaseName, + (SELECT TOP 1 [text] FROM sys.dm_exec_sql_text(c.most_recent_sql_handle)) AS QueryText, + r.query_hash + FROM sys.dm_exec_sessions s + INNER JOIN sys.dm_exec_connections c ON s.session_id = c.session_id + INNER JOIN sys.dm_exec_requests r ON s.session_id = r.session_id + LEFT OUTER JOIN ( + SELECT DISTINCT request_session_id, resource_database_id + FROM sys.dm_tran_locks + WHERE resource_type = N'DATABASE' + AND request_mode = N'S' + AND request_status = N'GRANT' + AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.session_id = db.request_session_id + WHERE r.status = 'rollback'; + END + + IF @Seconds > 0 + BEGIN + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) + SELECT + 47 AS CheckId, + 50 AS Priority, + 'Query Problems' AS FindingsGroup, + 'High Percentage Of Runnable Queries' AS Finding, + 'https://erikdarlingdata.com/go/RunnableQueue/' AS URL, + 'On the ' + + CASE WHEN y.pass = 1 + THEN '1st' + ELSE '2nd' + END + + ' pass, ' + + RTRIM(y.runnable_pct) + + '% of your queries were waiting to get on a CPU to run. ' + + ' This can indicate CPU pressure.' + FROM + ( + SELECT + 1 AS pass, + x.total, + x.runnable, + CONVERT(decimal(5,2), + ( + x.runnable / + (1. * NULLIF(x.total, 0)) + ) + ) * 100. AS runnable_pct + FROM + ( + SELECT + COUNT_BIG(*) AS total, + SUM(CASE WHEN status = 'runnable' + THEN 1 + ELSE 0 + END) AS runnable + FROM sys.dm_exec_requests + WHERE session_id > 50 + ) AS x + ) AS y + WHERE y.runnable_pct > 20.; + END + + /* Server Performance - Too Much Free Memory - CheckID 34 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 34',10,1) WITH NOWAIT; + END + + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) + SELECT 34 AS CheckID, + 50 AS Priority, + 'Server Performance' AS FindingGroup, + 'Too Much Free Memory' AS Finding, + 'https://www.brentozar.com/go/freememory' AS URL, + CAST((CAST(cFree.cntr_value AS BIGINT) / 1024 / 1024 ) AS NVARCHAR(100)) + N'GB of free memory inside SQL Server''s buffer pool,' + @LineFeed + ' which is ' + CAST((CAST(cTotal.cntr_value AS BIGINT) / 1024 / 1024) AS NVARCHAR(100)) + N'GB. You would think lots of free memory would be good, but check out the URL for more information.' AS Details, + 'Run sp_BlitzCache @SortOrder = ''memory grant'' to find queries with huge memory grants and tune them.' AS HowToStopIt + FROM sys.dm_os_performance_counters cFree + INNER JOIN sys.dm_os_performance_counters cTotal ON cTotal.object_name LIKE N'%Memory Manager%' + AND cTotal.counter_name = N'Total Server Memory (KB) ' + WHERE cFree.object_name LIKE N'%Memory Manager%' + AND cFree.counter_name = N'Free Memory (KB) ' + AND CAST(cFree.cntr_value AS BIGINT) > 20480000000 + AND CAST(cTotal.cntr_value AS BIGINT) * .3 <= CAST(cFree.cntr_value AS BIGINT) + AND CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) NOT LIKE '%Standard%'; + + /* Server Performance - Target Memory Lower Than Max - CheckID 35 */ + IF SERVERPROPERTY('EngineEdition') <> 5 /*SERVERPROPERTY('Edition') <> 'SQL Azure'*/ + BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 35',10,1) WITH NOWAIT; + END + + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) + SELECT 35 AS CheckID, + 10 AS Priority, + 'Server Performance' AS FindingGroup, + 'Target Memory Lower Than Max' AS Finding, + 'https://www.brentozar.com/go/target' AS URL, + N'Max server memory is ' + CAST(cMax.value_in_use AS NVARCHAR(50)) + N' MB but target server memory is only ' + CAST((CAST(cTarget.cntr_value AS BIGINT) / 1024) AS NVARCHAR(50)) + N' MB,' + @LineFeed + + N'indicating that SQL Server may be under external memory pressure or max server memory may be set too high.' AS Details, + 'Investigate what OS processes are using memory, and double-check the max server memory setting.' AS HowToStopIt + FROM sys.configurations cMax + INNER JOIN sys.dm_os_performance_counters cTarget ON cTarget.object_name LIKE N'%Memory Manager%' + AND cTarget.counter_name = N'Target Server Memory (KB) ' + WHERE cMax.name = 'max server memory (MB)' + AND CAST(cMax.value_in_use AS BIGINT) >= 1.5 * (CAST(cTarget.cntr_value AS BIGINT) / 1024) + AND CAST(cMax.value_in_use AS BIGINT) < 2147483647 /* Not set to default of unlimited */ + AND CAST(cTarget.cntr_value AS BIGINT) < .8 * (SELECT available_physical_memory_kb FROM sys.dm_os_sys_memory); /* Target memory less than 80% of physical memory (in case they set max too high) */ + END + + /* Server Info - Database Size, Total GB - CheckID 21 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 21',10,1) WITH NOWAIT; + END + + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) + SELECT 21 AS CheckID, + 251 AS Priority, + 'Server Info' AS FindingGroup, + 'Database Size, Total GB' AS Finding, + CAST(SUM (CAST(size AS BIGINT)*8./1024./1024.) AS VARCHAR(100)) AS Details, + SUM (CAST(size AS BIGINT))*8./1024./1024. AS DetailsInt, + 'https://www.brentozar.com/askbrent/' AS URL + FROM #MasterFiles + WHERE database_id > 4; + + /* Server Info - Database Count - CheckID 22 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 22',10,1) WITH NOWAIT; + END + + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) + SELECT 22 AS CheckID, + 251 AS Priority, + 'Server Info' AS FindingGroup, + 'Database Count' AS Finding, + CAST(SUM(1) AS VARCHAR(100)) AS Details, + SUM (1) AS DetailsInt, + 'https://www.brentozar.com/askbrent/' AS URL + FROM sys.databases + WHERE database_id > 4; + + /* Server Info - Memory Grants pending - CheckID 39 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 39',10,1) WITH NOWAIT; + END + + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) + SELECT 39 AS CheckID, + 50 AS Priority, + 'Server Performance' AS FindingGroup, + 'Memory Grants Pending' AS Finding, + CAST(PendingGrants.Details AS NVARCHAR(50)) AS Details, + PendingGrants.DetailsInt, + 'https://www.brentozar.com/blitz/memory-grants/' AS URL + FROM + ( + SELECT + COUNT(1) AS Details, + COUNT(1) AS DetailsInt + FROM sys.dm_exec_query_memory_grants AS Grants + WHERE queue_id IS NOT NULL + ) AS PendingGrants + WHERE PendingGrants.Details > 0; + + /* Server Info - Memory Grant/Workspace info - CheckID 40 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 40',10,1) WITH NOWAIT; + END + + DECLARE @MaxWorkspace BIGINT + SET @MaxWorkspace = (SELECT CAST(cntr_value AS BIGINT)/1024 FROM #PerfmonStats WHERE counter_name = N'Maximum Workspace Memory (KB)') + + IF (@MaxWorkspace IS NULL + OR @MaxWorkspace = 0) + BEGIN + SET @MaxWorkspace = 1 + END + + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) + SELECT 40 AS CheckID, + 251 AS Priority, + 'Server Info' AS FindingGroup, + 'Memory Grant/Workspace info' AS Finding, + + 'Grants Outstanding: ' + CAST((SELECT COUNT(*) FROM sys.dm_exec_query_memory_grants WHERE queue_id IS NULL) AS NVARCHAR(50)) + @LineFeed + + 'Total Granted(MB): ' + CAST(ISNULL(SUM(Grants.granted_memory_kb) / 1024, 0) AS NVARCHAR(50)) + @LineFeed + + 'Total WorkSpace(MB): ' + CAST(ISNULL(@MaxWorkspace, 0) AS NVARCHAR(50)) + @LineFeed + + 'Granted workspace: ' + CAST(ISNULL((CAST(SUM(Grants.granted_memory_kb) / 1024 AS MONEY) + / CAST(@MaxWorkspace AS MONEY)) * 100, 0) AS NVARCHAR(50)) + '%' + @LineFeed + + 'Oldest Grant in seconds: ' + CAST(ISNULL(DATEDIFF(SECOND, MIN(Grants.request_time), GETDATE()), 0) AS NVARCHAR(50)) AS Details, + (SELECT COUNT(*) FROM sys.dm_exec_query_memory_grants WHERE queue_id IS NULL) AS DetailsInt, + 'https://www.brentozar.com/askbrent/' AS URL + FROM sys.dm_exec_query_memory_grants AS Grants; + + /* Query Problems - Queries with high memory grants - CheckID 46 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 46',10,1) WITH NOWAIT; + END + + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, URL, QueryText, QueryPlan) + SELECT 46 AS CheckID, + 100 AS Priority, + 'Query Problems' AS FindingGroup, + 'Query with a memory grant exceeding ' + +CAST(@MemoryGrantThresholdPct AS NVARCHAR(15)) + +'%' AS Finding, + 'Granted size: '+ CAST(CAST(Grants.granted_memory_kb / 1024 AS INT) AS NVARCHAR(50)) + +N'MB ' + + @LineFeed + +N'Granted pct of max workspace: ' + + CAST(ISNULL((CAST(Grants.granted_memory_kb / 1024 AS MONEY) + / CAST(@MaxWorkspace AS MONEY)) * 100, 0) AS NVARCHAR(50)) + '%' + + @LineFeed + +N'SQLHandle: ' + +CONVERT(NVARCHAR(128),Grants.[sql_handle],1), + 'https://www.brentozar.com/memory-grants-sql-servers-public-toilet/' AS URL, + SQLText.[text], + QueryPlan.query_plan + FROM sys.dm_exec_query_memory_grants AS Grants + OUTER APPLY sys.dm_exec_sql_text(Grants.[sql_handle]) AS SQLText + OUTER APPLY sys.dm_exec_query_plan(Grants.[plan_handle]) AS QueryPlan + WHERE Grants.granted_memory_kb > ((@MemoryGrantThresholdPct/100.00)*(@MaxWorkspace*1024)); + + /* Query Problems - Memory Leak in USERSTORE_TOKENPERM Cache - CheckID 45 */ + IF EXISTS (SELECT * FROM sys.all_columns WHERE object_id = OBJECT_ID('sys.dm_os_memory_clerks') AND name = 'pages_kb') + BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 45',10,1) WITH NOWAIT; + END + + /* SQL 2012+ version */ + SET @StringToExecute = N' + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, URL) + SELECT 45 AS CheckID, + 50 AS Priority, + ''Query Problems'' AS FindingsGroup, + ''Memory Leak in USERSTORE_TOKENPERM Cache'' AS Finding, + N''UserStore_TokenPerm clerk is using '' + CAST(CAST(SUM(CASE WHEN type = ''USERSTORE_TOKENPERM'' AND name = ''TokenAndPermUserStore'' THEN pages_kb * 1.0 ELSE 0.0 END) / 1024.0 / 1024.0 AS INT) AS NVARCHAR(100)) + + N''GB RAM, total buffer pool is '' + CAST(CAST(SUM(pages_kb) / 1024.0 / 1024.0 AS INT) AS NVARCHAR(100)) + N''GB.'' + AS details, + ''https://www.BrentOzar.com/go/userstore'' AS URL + FROM sys.dm_os_memory_clerks + HAVING SUM(CASE WHEN type = ''USERSTORE_TOKENPERM'' AND name = ''TokenAndPermUserStore'' THEN pages_kb * 1.0 ELSE 0.0 END) / SUM(pages_kb) >= 0.1 + AND SUM(pages_kb) / 1024.0 / 1024.0 >= 1; /* At least 1GB RAM overall */'; + EXEC sp_executesql @StringToExecute; + END + ELSE + BEGIN + /* Antiques Roadshow SQL 2008R2 - version */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 45 (Legacy version)',10,1) WITH NOWAIT; + END + + SET @StringToExecute = N' + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, URL) + SELECT 45 AS CheckID, + 50 AS Priority, + ''Performance'' AS FindingsGroup, + ''Memory Leak in USERSTORE_TOKENPERM Cache'' AS Finding, + N''UserStore_TokenPerm clerk is using '' + CAST(CAST(SUM(CASE WHEN type = ''USERSTORE_TOKENPERM'' AND name = ''TokenAndPermUserStore'' THEN single_pages_kb + multi_pages_kb * 1.0 ELSE 0.0 END) / 1024.0 / 1024.0 AS INT) AS NVARCHAR(100)) + + N''GB RAM, total buffer pool is '' + CAST(CAST(SUM(single_pages_kb + multi_pages_kb) / 1024.0 / 1024.0 AS INT) AS NVARCHAR(100)) + N''GB.'' + AS details, + ''https://www.BrentOzar.com/go/userstore'' AS URL + FROM sys.dm_os_memory_clerks + HAVING SUM(CASE WHEN type = ''USERSTORE_TOKENPERM'' AND name = ''TokenAndPermUserStore'' THEN single_pages_kb + multi_pages_kb * 1.0 ELSE 0.0 END) / SUM(single_pages_kb + multi_pages_kb) >= 0.1 + AND SUM(single_pages_kb + multi_pages_kb) / 1024.0 / 1024.0 >= 1; /* At least 1GB RAM overall */'; + EXEC sp_executesql @StringToExecute; + END + + + + IF @Seconds > 0 + BEGIN + + IF EXISTS ( SELECT 1/0 + FROM sys.all_objects AS ao + WHERE ao.name = 'dm_exec_query_profiles' ) + BEGIN + + IF EXISTS( SELECT 1/0 + FROM sys.dm_exec_requests AS r + JOIN sys.dm_exec_sessions AS s + ON r.session_id = s.session_id + WHERE s.host_name IS NOT NULL + AND r.total_elapsed_time > 5000 + AND r.request_id > 0 ) + BEGIN + + SET @StringToExecute = N' + DECLARE @bad_estimate TABLE + ( + session_id INT, + request_id INT, + estimate_inaccuracy BIT + ); + + INSERT @bad_estimate ( session_id, request_id, estimate_inaccuracy ) + SELECT x.session_id, + x.request_id, + x.estimate_inaccuracy + FROM ( + SELECT deqp.session_id, + deqp.request_id, + CASE WHEN (deqp.row_count/10000) > deqp.estimate_row_count + THEN 1 + ELSE 0 + END AS estimate_inaccuracy + FROM sys.dm_exec_query_profiles AS deqp + INNER JOIN sys.dm_exec_requests r ON deqp.session_id = r.session_id AND deqp.request_id = r.request_id + WHERE deqp.session_id <> @@SPID + AND r.total_elapsed_time > 5000 + ) AS x + WHERE x.estimate_inaccuracy = 1 + GROUP BY x.session_id, + x.request_id, + x.estimate_inaccuracy; + + DECLARE @parallelism_skew TABLE + ( + session_id INT, + request_id INT, + parallelism_skew BIT + ); + + INSERT @parallelism_skew ( session_id, request_id, parallelism_skew ) + SELECT y.session_id, + y.request_id, + y.parallelism_skew + FROM ( + SELECT x.session_id, + x.request_id, + x.node_id, + x.thread_id, + x.row_count, + x.sum_node_rows, + x.node_dop, + x.sum_node_rows / x.node_dop AS even_distribution, + x.row_count / (1. * ISNULL(NULLIF(x.sum_node_rows / x.node_dop, 0), 1)) AS skew_percent, + CASE + WHEN x.row_count > 10000 + AND x.row_count / (1. * ISNULL(NULLIF(x.sum_node_rows / x.node_dop, 0), 1)) > 2. + THEN 1 + WHEN x.row_count > 10000 + AND x.row_count / (1. * ISNULL(NULLIF(x.sum_node_rows / x.node_dop, 0), 1)) < 0.5 + THEN 1 + ELSE 0 + END AS parallelism_skew + FROM ( + SELECT deqp.session_id, + deqp.request_id, + deqp.node_id, + deqp.thread_id, + deqp.row_count, + SUM(deqp.row_count) + OVER ( PARTITION BY deqp.session_id, + deqp.request_id, + deqp.node_id + ORDER BY deqp.row_count + ROWS BETWEEN UNBOUNDED PRECEDING + AND UNBOUNDED FOLLOWING ) + AS sum_node_rows, + COUNT(*) + OVER ( PARTITION BY deqp.session_id, + deqp.request_id, + deqp.node_id + ORDER BY deqp.row_count + ROWS BETWEEN UNBOUNDED PRECEDING + AND UNBOUNDED FOLLOWING ) + AS node_dop + FROM sys.dm_exec_query_profiles AS deqp + WHERE deqp.thread_id > 0 + AND deqp.session_id <> @@SPID + AND EXISTS + ( + SELECT 1/0 + FROM sys.dm_exec_query_profiles AS deqp2 + WHERE deqp.session_id = deqp2.session_id + AND deqp.node_id = deqp2.node_id + AND deqp2.thread_id > 0 + GROUP BY deqp2.session_id, deqp2.node_id + HAVING COUNT(deqp2.node_id) > 1 + ) + ) AS x + ) AS y + WHERE y.parallelism_skew = 1 + GROUP BY y.session_id, + y.request_id, + y.parallelism_skew; + + /* Queries in dm_exec_query_profiles showing signs of poor cardinality estimates - CheckID 42 */ + IF (@Debug = 1) + BEGIN + RAISERROR(''Running CheckID 42'',10,1) WITH NOWAIT; + END + + INSERT INTO #BlitzFirstResults + (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, OpenTransactionCount, QueryHash, QueryPlan) + SELECT 42 AS CheckID, + 100 AS Priority, + ''Query Performance'' AS FindingsGroup, + ''Queries with 10000x cardinality misestimations'' AS Findings, + ''https://www.brentozar.com/go/skewedup'' AS URL, + ''The query on SPID '' + + RTRIM(b.session_id) + + '' has been running for '' + + RTRIM(r.total_elapsed_time / 1000) + + '' seconds, with a large cardinality misestimate'' AS Details, + ''No quick fix here: time to dig into the actual execution plan. '' AS HowToStopIt, + r.start_time, + s.login_name, + s.nt_user_name, + s.program_name, + s.host_name, + r.database_id, + DB_NAME(r.database_id), + dest.text, + s.open_transaction_count, + r.query_hash, '; + + IF @dm_exec_query_statistics_xml = 1 + SET @StringToExecute = @StringToExecute + N' COALESCE(qs_live.query_plan, qp.query_plan) AS query_plan '; + ELSE + SET @StringToExecute = @StringToExecute + N' qp.query_plan '; + + SET @StringToExecute = @StringToExecute + N' + FROM @bad_estimate AS b + JOIN sys.dm_exec_requests AS r + ON r.session_id = b.session_id + AND r.request_id = b.request_id + JOIN sys.dm_exec_sessions AS s + ON s.session_id = b.session_id + CROSS APPLY sys.dm_exec_sql_text(r.sql_handle) AS dest + CROSS APPLY sys.dm_exec_query_plan(r.plan_handle) AS qp '; + + IF EXISTS (SELECT * FROM sys.all_objects WHERE name = 'dm_exec_query_statistics_xml') + /* GitHub #3210 */ + SET @StringToExecute = N' + SET LOCK_TIMEOUT 1000 ' + @StringToExecute + N' OUTER APPLY sys.dm_exec_query_statistics_xml(s.session_id) qs_live '; + + SET @StringToExecute = @StringToExecute + N'; + + /* Queries in dm_exec_query_profiles showing signs of unbalanced parallelism - CheckID 43 */ + IF (@Debug = 1) + BEGIN + RAISERROR(''Running CheckID 43'',10,1) WITH NOWAIT; + END + + INSERT INTO #BlitzFirstResults + (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, OpenTransactionCount, QueryHash, QueryPlan) + SELECT 43 AS CheckID, + 100 AS Priority, + ''Query Performance'' AS FindingsGroup, + ''Queries with 10000x skewed parallelism'' AS Findings, + ''https://www.brentozar.com/go/skewedup'' AS URL, + ''The query on SPID '' + + RTRIM(p.session_id) + + '' has been running for '' + + RTRIM(r.total_elapsed_time / 1000) + + '' seconds, with a parallel threads doing uneven work.'' AS Details, + ''No quick fix here: time to dig into the actual execution plan. '' AS HowToStopIt, + r.start_time, + s.login_name, + s.nt_user_name, + s.program_name, + s.host_name, + r.database_id, + DB_NAME(r.database_id), + dest.text, + s.open_transaction_count, + r.query_hash, '; + + IF @dm_exec_query_statistics_xml = 1 + SET @StringToExecute = @StringToExecute + N' COALESCE(qs_live.query_plan, qp.query_plan) AS query_plan '; + ELSE + SET @StringToExecute = @StringToExecute + N' qp.query_plan '; + + SET @StringToExecute = @StringToExecute + N' + FROM @parallelism_skew AS p + JOIN sys.dm_exec_requests AS r + ON r.session_id = p.session_id + AND r.request_id = p.request_id + JOIN sys.dm_exec_sessions AS s + ON s.session_id = p.session_id + CROSS APPLY sys.dm_exec_sql_text(r.sql_handle) AS dest + CROSS APPLY sys.dm_exec_query_plan(r.plan_handle) AS qp '; + + IF EXISTS (SELECT * FROM sys.all_objects WHERE name = 'dm_exec_query_statistics_xml') + SET @StringToExecute = @StringToExecute + N' OUTER APPLY sys.dm_exec_query_statistics_xml(s.session_id) qs_live '; + + + SET @StringToExecute = @StringToExecute + N';'; + + EXECUTE sp_executesql @StringToExecute, N'@Debug BIT',@Debug = @Debug; + END + + END + END + + /* Server Performance - High CPU Utilization - CheckID 24 */ + IF @Seconds < 30 + BEGIN + /* If we're waiting less than 30 seconds, run this check now rather than wait til the end. + We get this data from the ring buffers, and it's only updated once per minute, so might + as well get it now - whereas if we're checking 30+ seconds, it might get updated by the + end of our sp_BlitzFirst session. */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 24',10,1) WITH NOWAIT; + END + + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) + SELECT 24, 50, 'Server Performance', 'High CPU Utilization', CAST(100 - SystemIdle AS NVARCHAR(20)) + N'%.', 100 - SystemIdle, 'https://www.brentozar.com/go/cpu' + FROM ( + SELECT record, + record.value('(./Record/SchedulerMonitorEvent/SystemHealth/SystemIdle)[1]', 'int') AS SystemIdle + FROM ( + SELECT TOP 1 CONVERT(XML, record) AS record + FROM sys.dm_os_ring_buffers + WHERE ring_buffer_type = N'RING_BUFFER_SCHEDULER_MONITOR' + AND record LIKE '%%' + ORDER BY timestamp DESC) AS rb + ) AS y + WHERE 100 - SystemIdle >= 50; + + /* CPU Utilization - CheckID 23 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 23',10,1) WITH NOWAIT; + END + + IF SERVERPROPERTY('EngineEdition') <> 5 /*SERVERPROPERTY('Edition') <> 'SQL Azure'*/ + WITH y + AS + ( + SELECT CONVERT(VARCHAR(5), 100 - ca.c.value('.', 'INT')) AS system_idle, + CONVERT(VARCHAR(30), rb.event_date) AS event_date, + CONVERT(VARCHAR(8000), rb.record) AS record, + event_date as event_date_raw + FROM + ( SELECT CONVERT(XML, dorb.record) AS record, + DATEADD(ms, -( ts.ms_ticks - dorb.timestamp ), GETDATE()) AS event_date + FROM sys.dm_os_ring_buffers AS dorb + CROSS JOIN + ( SELECT dosi.ms_ticks FROM sys.dm_os_sys_info AS dosi ) AS ts + WHERE dorb.ring_buffer_type = N'RING_BUFFER_SCHEDULER_MONITOR' + AND record LIKE '%%' ) AS rb + CROSS APPLY rb.record.nodes('/Record/SchedulerMonitorEvent/SystemHealth/SystemIdle') AS ca(c) + ) + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL, HowToStopIt) + SELECT TOP 1 + 23, + 250, + 'Server Info', + 'CPU Utilization', + y.system_idle + N'%. Ring buffer details: ' + CAST(y.record AS NVARCHAR(4000)), + y.system_idle , + 'https://www.brentozar.com/go/cpu', + STUFF(( SELECT TOP 2147483647 + CHAR(10) + CHAR(13) + + y2.system_idle + + '% ON ' + + y2.event_date + + ' Ring buffer details: ' + + y2.record + FROM y AS y2 + ORDER BY y2.event_date_raw DESC + FOR XML PATH(N''), TYPE ).value(N'.[1]', N'VARCHAR(MAX)'), 1, 1, N'') AS query + FROM y + ORDER BY y.event_date_raw DESC; + + + /* Highlight if non SQL processes are using >25% CPU - CheckID 28 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 28',10,1) WITH NOWAIT; + END + + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) + SELECT 28, 50, 'Server Performance', 'High CPU Utilization - Not SQL', CONVERT(NVARCHAR(100),100 - (y.SQLUsage + y.SystemIdle)) + N'% - Other Processes (not SQL Server) are using this much CPU. This may impact on the performance of your SQL Server instance', 100 - (y.SQLUsage + y.SystemIdle), 'https://www.brentozar.com/go/cpu' + FROM ( + SELECT record, + record.value('(./Record/SchedulerMonitorEvent/SystemHealth/SystemIdle)[1]', 'int') AS SystemIdle + ,record.value('(./Record/SchedulerMonitorEvent/SystemHealth/ProcessUtilization)[1]', 'int') AS SQLUsage + FROM ( + SELECT TOP 1 CONVERT(XML, record) AS record + FROM sys.dm_os_ring_buffers + WHERE ring_buffer_type = N'RING_BUFFER_SCHEDULER_MONITOR' + AND record LIKE '%%' + ORDER BY timestamp DESC) AS rb + ) AS y + WHERE 100 - (y.SQLUsage + y.SystemIdle) >= 25; + + END; /* IF @Seconds < 30 */ + + /* Query Problems - Statistics Updated Recently - CheckID 44 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 44',10,1) WITH NOWAIT; + END + + IF 20 >= (SELECT COUNT(*) FROM sys.databases WHERE name NOT IN ('master', 'model', 'msdb', 'tempdb')) + AND @Seconds > 0 + BEGIN + CREATE TABLE #UpdatedStats (HowToStopIt NVARCHAR(4000), RowsForSorting BIGINT); + IF EXISTS(SELECT * FROM sys.all_objects WHERE name = 'dm_db_stats_properties') + BEGIN + /* We don't want to hang around to obtain locks */ + SET LOCK_TIMEOUT 0; + + IF SERVERPROPERTY('EngineEdition') <> 5 /*SERVERPROPERTY('Edition') <> 'SQL Azure'*/ + SET @StringToExecute = N'USE [?];' + @LineFeed; + ELSE + SET @StringToExecute = N''; + + SET @StringToExecute = @StringToExecute + 'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; SET LOCK_TIMEOUT 1000;' + @LineFeed + + 'BEGIN TRY' + @LineFeed + + ' INSERT INTO #UpdatedStats(HowToStopIt, RowsForSorting)' + @LineFeed + + ' SELECT HowToStopIt = ' + @LineFeed + + ' QUOTENAME(DB_NAME()) + N''.'' +' + @LineFeed + + ' QUOTENAME(SCHEMA_NAME(obj.schema_id)) + N''.'' +' + @LineFeed + + ' QUOTENAME(obj.name) +' + @LineFeed + + ' N'' statistic '' + QUOTENAME(stat.name) + ' + @LineFeed + + ' N'' was updated on '' + CONVERT(NVARCHAR(50), sp.last_updated, 121) + N'','' + ' + @LineFeed + + ' N'' had '' + CAST(sp.rows AS NVARCHAR(50)) + N'' rows, with '' +' + @LineFeed + + ' CAST(sp.rows_sampled AS NVARCHAR(50)) + N'' rows sampled,'' + ' + @LineFeed + + ' N'' producing '' + CAST(sp.steps AS NVARCHAR(50)) + N'' steps in the histogram.'',' + @LineFeed + + ' sp.rows' + @LineFeed + + ' FROM sys.objects AS obj WITH (NOLOCK)' + @LineFeed + + ' INNER JOIN sys.stats AS stat WITH (NOLOCK) ON stat.object_id = obj.object_id ' + @LineFeed + + ' CROSS APPLY sys.dm_db_stats_properties(stat.object_id, stat.stats_id) AS sp ' + @LineFeed + + ' WHERE sp.last_updated > DATEADD(MI, -15, GETDATE())' + @LineFeed + + ' AND obj.is_ms_shipped = 0' + @LineFeed + + ' AND ''[?]'' <> ''[tempdb]'';' + @LineFeed + + 'END TRY' + @LineFeed + + 'BEGIN CATCH' + @LineFeed + + ' IF (ERROR_NUMBER() = 1222)' + @LineFeed + + ' BEGIN ' + @LineFeed + + ' INSERT INTO #UpdatedStats(HowToStopIt, RowsForSorting)' + @LineFeed + + ' SELECT HowToStopIt = ' + @LineFeed + + ' QUOTENAME(DB_NAME()) +' + @LineFeed + + ' N'' No information could be retrieved as the lock timeout was exceeded,''+' + @LineFeed + + ' N'' this is likely due to an Index operation in Progress'',' + @LineFeed + + ' -1' + @LineFeed + + ' END' + @LineFeed + + ' ELSE' + @LineFeed + + ' BEGIN' + @LineFeed + + ' INSERT INTO #UpdatedStats(HowToStopIt, RowsForSorting)' + @LineFeed + + ' SELECT HowToStopIt = ' + @LineFeed + + ' QUOTENAME(DB_NAME()) +' + @LineFeed + + ' N'' No information could be retrieved as a result of error: ''+' + @LineFeed + + ' CAST(ERROR_NUMBER() AS NVARCHAR(10)) +' + @LineFeed + + ' N'' with message: ''+' + @LineFeed + + ' CAST(ERROR_MESSAGE() AS NVARCHAR(128)),' + @LineFeed + + ' -1' + @LineFeed + + ' END' + @LineFeed + + 'END CATCH' + ; + + IF SERVERPROPERTY('EngineEdition') <> 5 /*SERVERPROPERTY('Edition') <> 'SQL Azure'*/ + BEGIN + BEGIN TRY + EXEC sp_MSforeachdb @StringToExecute; + END TRY + BEGIN CATCH + IF (ERROR_NUMBER() = 1222) + BEGIN + INSERT INTO #UpdatedStats(HowToStopIt, RowsForSorting) + SELECT HowToStopIt = N'No information could be retrieved as the lock timeout was exceeded while iterating databases,' + + N' this is likely due to an Index operation in Progress', -1; + END + ELSE + BEGIN + THROW; + END + END CATCH + END + ELSE + EXEC(@StringToExecute); + + /* Set timeout back to a default value of -1 */ + SET LOCK_TIMEOUT -1; + END; + + /* We mark timeout exceeded with a -1 so only show these IF there is statistics info that succeeded */ + IF EXISTS (SELECT * FROM #UpdatedStats WHERE RowsForSorting > -1) + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) + SELECT 44 AS CheckId, + 50 AS Priority, + 'Query Problems' AS FindingGroup, + 'Statistics Updated Recently' AS Finding, + 'https://www.brentozar.com/go/stats' AS URL, + 'In the last 15 minutes, statistics were updated. To see which ones, click the HowToStopIt column.' + @LineFeed + @LineFeed + + 'This effectively clears the plan cache for queries that involve these tables,' + @LineFeed + + 'which thereby causes parameter sniffing: those queries are now getting brand new' + @LineFeed + + 'query plans based on whatever parameters happen to call them next.' + @LineFeed + @LineFeed + + 'Be on the lookout for sudden parameter sniffing issues after this time range.', + HowToStopIt = (SELECT (SELECT HowToStopIt + NCHAR(10)) + FROM #UpdatedStats + ORDER BY RowsForSorting DESC + FOR XML PATH('')); + + END + + RAISERROR('Finished running investigatory queries',10,1) WITH NOWAIT; + + + /* End of checks. If we haven't waited @Seconds seconds, wait. */ + IF DATEADD(SECOND,1,SYSDATETIMEOFFSET()) < @FinishSampleTime + BEGIN + RAISERROR('Waiting to match @Seconds parameter',10,1) WITH NOWAIT; + WAITFOR TIME @FinishSampleTimeWaitFor; + END; + + IF @total_cpu_usage IN (0, 1) + BEGIN + EXEC sys.sp_executesql + @get_thread_time_ms, + N'@thread_time_ms FLOAT OUTPUT', + @thread_time_ms OUTPUT; + END + + RAISERROR('Capturing second pass of wait stats, perfmon counters, file stats',10,1) WITH NOWAIT; + /* Populate #FileStats, #PerfmonStats, #WaitStats with DMV data. In a second, we'll compare these. */ + SET @StringToExecute = N' + INSERT #WaitStats(Pass, SampleTime, wait_type, wait_time_ms, thread_time_ms, signal_wait_time_ms, waiting_tasks_count) + SELECT + x.Pass, + x.SampleTime, + x.wait_type, + SUM(x.sum_wait_time_ms) AS sum_wait_time_ms, + @thread_time_ms AS thread_time_ms, + SUM(x.sum_signal_wait_time_ms) AS sum_signal_wait_time_ms, + SUM(x.sum_waiting_tasks) AS sum_waiting_tasks + FROM ( + SELECT + 2 AS Pass, + SYSDATETIMEOFFSET() AS SampleTime, + owt.wait_type, + SUM(owt.wait_duration_ms) OVER (PARTITION BY owt.wait_type, owt.session_id) + - CASE WHEN @Seconds = 0 THEN 0 ELSE (@Seconds * 1000) END AS sum_wait_time_ms, + 0 AS sum_signal_wait_time_ms, + CASE @Seconds WHEN 0 THEN 0 ELSE 1 END AS sum_waiting_tasks + FROM sys.dm_os_waiting_tasks owt + WHERE owt.session_id > 50 + AND owt.wait_duration_ms >= CASE @Seconds WHEN 0 THEN 0 ELSE @Seconds * 1000 END + UNION ALL + SELECT + 2 AS Pass, + SYSDATETIMEOFFSET() AS SampleTime, + os.wait_type, + SUM(os.wait_time_ms) OVER (PARTITION BY os.wait_type) AS sum_wait_time_ms, + SUM(os.signal_wait_time_ms) OVER (PARTITION BY os.wait_type ) AS sum_signal_wait_time_ms, + SUM(os.waiting_tasks_count) OVER (PARTITION BY os.wait_type) AS sum_waiting_tasks '; + + IF SERVERPROPERTY('EngineEdition') = 5 /*SERVERPROPERTY('Edition') = 'SQL Azure'*/ + SET @StringToExecute = @StringToExecute + N' FROM sys.dm_db_wait_stats os '; + ELSE + SET @StringToExecute = @StringToExecute + N' FROM sys.dm_os_wait_stats os '; + + SET @StringToExecute = @StringToExecute + N' + ) x + WHERE NOT EXISTS + ( + SELECT * + FROM ##WaitCategories AS wc + WHERE wc.WaitType = x.wait_type + AND wc.Ignorable = 1 + ) + GROUP BY x.Pass, x.SampleTime, x.wait_type + ORDER BY sum_wait_time_ms DESC;'; + + EXEC sys.sp_executesql + @StringToExecute, + N'@StartSampleTime DATETIMEOFFSET, + @Seconds INT, + @thread_time_ms FLOAT', + @StartSampleTime, + @Seconds, + @thread_time_ms; + + WITH w AS + ( + SELECT + total_waits = + CONVERT + ( + FLOAT, + SUM(ws.wait_time_ms) + ) + FROM #WaitStats AS ws + WHERE Pass = 2 + ) + UPDATE ws + SET ws.thread_time_ms += w.total_waits + FROM #WaitStats AS ws + CROSS JOIN w + WHERE ws.Pass = 2 + OPTION(RECOMPILE); + + + INSERT INTO #FileStats (Pass, SampleTime, DatabaseID, FileID, DatabaseName, FileLogicalName, SizeOnDiskMB, io_stall_read_ms , + num_of_reads, [bytes_read] , io_stall_write_ms,num_of_writes, [bytes_written], PhysicalName, TypeDesc, avg_stall_read_ms, avg_stall_write_ms) + SELECT 2 AS Pass, + SYSDATETIMEOFFSET() AS SampleTime, + mf.[database_id], + mf.[file_id], + DB_NAME(vfs.database_id) AS [db_name], + mf.name + N' [' + mf.type_desc COLLATE SQL_Latin1_General_CP1_CI_AS + N']' AS file_logical_name , + CAST(( ( vfs.size_on_disk_bytes / 1024.0 ) / 1024.0 ) AS INT) AS size_on_disk_mb , + vfs.io_stall_read_ms , + vfs.num_of_reads , + vfs.[num_of_bytes_read], + vfs.io_stall_write_ms , + vfs.num_of_writes , + vfs.[num_of_bytes_written], + mf.physical_name, + mf.type_desc, + 0, + 0 + FROM sys.dm_io_virtual_file_stats (NULL, NULL) AS vfs + INNER JOIN #MasterFiles AS mf ON vfs.file_id = mf.file_id + AND vfs.database_id = mf.database_id + WHERE vfs.num_of_reads > 0 + OR vfs.num_of_writes > 0; + + INSERT INTO #PerfmonStats (Pass, SampleTime, [object_name],[counter_name],[instance_name],[cntr_value],[cntr_type]) + SELECT 2 AS Pass, + SYSDATETIMEOFFSET() AS SampleTime, + RTRIM(dmv.object_name), RTRIM(dmv.counter_name), RTRIM(dmv.instance_name), dmv.cntr_value, dmv.cntr_type + FROM #PerfmonCounters counters + INNER JOIN sys.dm_os_performance_counters dmv ON counters.counter_name COLLATE SQL_Latin1_General_CP1_CI_AS = RTRIM(dmv.counter_name) COLLATE SQL_Latin1_General_CP1_CI_AS + AND counters.[object_name] COLLATE SQL_Latin1_General_CP1_CI_AS = RTRIM(dmv.[object_name]) COLLATE SQL_Latin1_General_CP1_CI_AS + AND (counters.[instance_name] IS NULL OR counters.[instance_name] COLLATE SQL_Latin1_General_CP1_CI_AS = RTRIM(dmv.[instance_name]) COLLATE SQL_Latin1_General_CP1_CI_AS); + + /* Set the latencies and averages. We could do this with a CTE, but we're not ambitious today. */ + UPDATE fNow + SET avg_stall_read_ms = ((fNow.io_stall_read_ms - fBase.io_stall_read_ms) / (fNow.num_of_reads - fBase.num_of_reads)) + FROM #FileStats fNow + INNER JOIN #FileStats fBase ON fNow.DatabaseID = fBase.DatabaseID AND fNow.FileID = fBase.FileID AND fNow.SampleTime > fBase.SampleTime AND fNow.num_of_reads > fBase.num_of_reads AND fNow.io_stall_read_ms > fBase.io_stall_read_ms + WHERE (fNow.num_of_reads - fBase.num_of_reads) > 0; + + UPDATE fNow + SET avg_stall_write_ms = ((fNow.io_stall_write_ms - fBase.io_stall_write_ms) / (fNow.num_of_writes - fBase.num_of_writes)) + FROM #FileStats fNow + INNER JOIN #FileStats fBase ON fNow.DatabaseID = fBase.DatabaseID AND fNow.FileID = fBase.FileID AND fNow.SampleTime > fBase.SampleTime AND fNow.num_of_writes > fBase.num_of_writes AND fNow.io_stall_write_ms > fBase.io_stall_write_ms + WHERE (fNow.num_of_writes - fBase.num_of_writes) > 0; + + UPDATE pNow + SET [value_delta] = pNow.cntr_value - pFirst.cntr_value, + [value_per_second] = ((1.0 * pNow.cntr_value - pFirst.cntr_value) / DATEDIFF(ss, pFirst.SampleTime, pNow.SampleTime)) + FROM #PerfmonStats pNow + INNER JOIN #PerfmonStats pFirst ON pFirst.[object_name] = pNow.[object_name] AND pFirst.counter_name = pNow.counter_name AND (pFirst.instance_name = pNow.instance_name OR (pFirst.instance_name IS NULL AND pNow.instance_name IS NULL)) + AND pNow.ID > pFirst.ID + WHERE DATEDIFF(ss, pFirst.SampleTime, pNow.SampleTime) > 0; + + + /* Query Stats - If we're within 10 seconds of our projected finish time, do the plan cache analysis. - CheckID 18 */ + IF DATEDIFF(ss, @FinishSampleTime, SYSDATETIMEOFFSET()) > 10 AND @CheckProcedureCache = 1 + BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 18',10,1) WITH NOWAIT; + END + + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (18, 210, 'Query Stats', 'Plan Cache Analysis Skipped', 'https://www.brentozar.com/go/topqueries', + 'Due to excessive load, the plan cache analysis was skipped. To override this, use @ExpertMode = 1.'); + + END; + ELSE IF @CheckProcedureCache = 1 + BEGIN + + + RAISERROR('@CheckProcedureCache = 1, capturing second pass of plan cache',10,1) WITH NOWAIT; + + /* Populate #QueryStats. SQL 2005 doesn't have query hash or query plan hash. */ + IF @@VERSION LIKE 'Microsoft SQL Server 2005%' + BEGIN + IF @FilterPlansByDatabase IS NULL + BEGIN + SET @StringToExecute = N'INSERT INTO #QueryStats ([sql_handle], Pass, SampleTime, statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, Points) + SELECT [sql_handle], 2 AS Pass, SYSDATETIMEOFFSET(), statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, NULL AS query_hash, NULL AS query_plan_hash, 0 + FROM sys.dm_exec_query_stats qs + WHERE qs.last_execution_time >= @StartSampleTimeText;'; + END; + ELSE + BEGIN + SET @StringToExecute = N'INSERT INTO #QueryStats ([sql_handle], Pass, SampleTime, statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, Points) + SELECT [sql_handle], 2 AS Pass, SYSDATETIMEOFFSET(), statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, NULL AS query_hash, NULL AS query_plan_hash, 0 + FROM sys.dm_exec_query_stats qs + CROSS APPLY sys.dm_exec_plan_attributes(qs.plan_handle) AS attr + INNER JOIN #FilterPlansByDatabase dbs ON CAST(attr.value AS INT) = dbs.DatabaseID + WHERE qs.last_execution_time >= @StartSampleTimeText + AND attr.attribute = ''dbid'';'; + END; + END; + ELSE + BEGIN + IF @FilterPlansByDatabase IS NULL + BEGIN + SET @StringToExecute = N'INSERT INTO #QueryStats ([sql_handle], Pass, SampleTime, statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, Points) + SELECT [sql_handle], 2 AS Pass, SYSDATETIMEOFFSET(), statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, 0 + FROM sys.dm_exec_query_stats qs + WHERE qs.last_execution_time >= @StartSampleTimeText'; + END; + ELSE + BEGIN + SET @StringToExecute = N'INSERT INTO #QueryStats ([sql_handle], Pass, SampleTime, statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, Points) + SELECT [sql_handle], 2 AS Pass, SYSDATETIMEOFFSET(), statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, 0 + FROM sys.dm_exec_query_stats qs + CROSS APPLY sys.dm_exec_plan_attributes(qs.plan_handle) AS attr + INNER JOIN #FilterPlansByDatabase dbs ON CAST(attr.value AS INT) = dbs.DatabaseID + WHERE qs.last_execution_time >= @StartSampleTimeText + AND attr.attribute = ''dbid'';'; + END; + END; + /* Old version pre-2016/06/13: + IF @@VERSION LIKE 'Microsoft SQL Server 2005%' + SET @StringToExecute = N'INSERT INTO #QueryStats ([sql_handle], Pass, SampleTime, statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, Points) + SELECT [sql_handle], 2 AS Pass, SYSDATETIMEOFFSET(), statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, NULL AS query_hash, NULL AS query_plan_hash, 0 + FROM sys.dm_exec_query_stats qs + WHERE qs.last_execution_time >= @StartSampleTimeText;'; + ELSE + SET @StringToExecute = N'INSERT INTO #QueryStats ([sql_handle], Pass, SampleTime, statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, Points) + SELECT [sql_handle], 2 AS Pass, SYSDATETIMEOFFSET(), statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, 0 + FROM sys.dm_exec_query_stats qs + WHERE qs.last_execution_time >= @StartSampleTimeText;'; + */ + SET @ParmDefinitions = N'@StartSampleTimeText NVARCHAR(100)'; + SET @Parm1 = CONVERT(NVARCHAR(100), CAST(@StartSampleTime AS DATETIME), 127); + + EXECUTE sp_executesql @StringToExecute, @ParmDefinitions, @StartSampleTimeText = @Parm1; + + RAISERROR('@CheckProcedureCache = 1, totaling up plan cache metrics',10,1) WITH NOWAIT; + + /* Get the totals for the entire plan cache */ + INSERT INTO #QueryStats (Pass, SampleTime, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time) + SELECT 0 AS Pass, SYSDATETIMEOFFSET(), SUM(execution_count), SUM(total_worker_time), SUM(total_physical_reads), SUM(total_logical_writes), SUM(total_logical_reads), SUM(total_clr_time), SUM(total_elapsed_time), MIN(creation_time) + FROM sys.dm_exec_query_stats qs; + + + RAISERROR('@CheckProcedureCache = 1, so analyzing execution plans',10,1) WITH NOWAIT; + /* + Pick the most resource-intensive queries to review. Update the Points field + in #QueryStats - if a query is in the top 10 for logical reads, CPU time, + duration, or execution, add 1 to its points. + */ + WITH qsTop AS ( + SELECT TOP 10 qsNow.ID + FROM #QueryStats qsNow + INNER JOIN #QueryStats qsFirst ON qsNow.[sql_handle] = qsFirst.[sql_handle] AND qsNow.statement_start_offset = qsFirst.statement_start_offset AND qsNow.statement_end_offset = qsFirst.statement_end_offset AND qsNow.plan_generation_num = qsFirst.plan_generation_num AND qsNow.plan_handle = qsFirst.plan_handle AND qsFirst.Pass = 1 + WHERE qsNow.total_elapsed_time > qsFirst.total_elapsed_time + AND qsNow.Pass = 2 + AND qsNow.total_elapsed_time - qsFirst.total_elapsed_time > 1000000 /* Only queries with over 1 second of runtime */ + ORDER BY (qsNow.total_elapsed_time - COALESCE(qsFirst.total_elapsed_time, 0)) DESC) + UPDATE #QueryStats + SET Points = Points + 1 + FROM #QueryStats qs + INNER JOIN qsTop ON qs.ID = qsTop.ID; + + WITH qsTop AS ( + SELECT TOP 10 qsNow.ID + FROM #QueryStats qsNow + INNER JOIN #QueryStats qsFirst ON qsNow.[sql_handle] = qsFirst.[sql_handle] AND qsNow.statement_start_offset = qsFirst.statement_start_offset AND qsNow.statement_end_offset = qsFirst.statement_end_offset AND qsNow.plan_generation_num = qsFirst.plan_generation_num AND qsNow.plan_handle = qsFirst.plan_handle AND qsFirst.Pass = 1 + WHERE qsNow.total_logical_reads > qsFirst.total_logical_reads + AND qsNow.Pass = 2 + AND qsNow.total_logical_reads - qsFirst.total_logical_reads > 1000 /* Only queries with over 1000 reads */ + ORDER BY (qsNow.total_logical_reads - COALESCE(qsFirst.total_logical_reads, 0)) DESC) + UPDATE #QueryStats + SET Points = Points + 1 + FROM #QueryStats qs + INNER JOIN qsTop ON qs.ID = qsTop.ID; + + WITH qsTop AS ( + SELECT TOP 10 qsNow.ID + FROM #QueryStats qsNow + INNER JOIN #QueryStats qsFirst ON qsNow.[sql_handle] = qsFirst.[sql_handle] AND qsNow.statement_start_offset = qsFirst.statement_start_offset AND qsNow.statement_end_offset = qsFirst.statement_end_offset AND qsNow.plan_generation_num = qsFirst.plan_generation_num AND qsNow.plan_handle = qsFirst.plan_handle AND qsFirst.Pass = 1 + WHERE qsNow.total_worker_time > qsFirst.total_worker_time + AND qsNow.Pass = 2 + AND qsNow.total_worker_time - qsFirst.total_worker_time > 1000000 /* Only queries with over 1 second of worker time */ + ORDER BY (qsNow.total_worker_time - COALESCE(qsFirst.total_worker_time, 0)) DESC) + UPDATE #QueryStats + SET Points = Points + 1 + FROM #QueryStats qs + INNER JOIN qsTop ON qs.ID = qsTop.ID; + + WITH qsTop AS ( + SELECT TOP 10 qsNow.ID + FROM #QueryStats qsNow + INNER JOIN #QueryStats qsFirst ON qsNow.[sql_handle] = qsFirst.[sql_handle] AND qsNow.statement_start_offset = qsFirst.statement_start_offset AND qsNow.statement_end_offset = qsFirst.statement_end_offset AND qsNow.plan_generation_num = qsFirst.plan_generation_num AND qsNow.plan_handle = qsFirst.plan_handle AND qsFirst.Pass = 1 + WHERE qsNow.execution_count > qsFirst.execution_count + AND qsNow.Pass = 2 + AND (qsNow.total_elapsed_time - qsFirst.total_elapsed_time > 1000000 /* Only queries with over 1 second of runtime */ + OR qsNow.total_logical_reads - qsFirst.total_logical_reads > 1000 /* Only queries with over 1000 reads */ + OR qsNow.total_worker_time - qsFirst.total_worker_time > 1000000 /* Only queries with over 1 second of worker time */) + ORDER BY (qsNow.execution_count - COALESCE(qsFirst.execution_count, 0)) DESC) + UPDATE #QueryStats + SET Points = Points + 1 + FROM #QueryStats qs + INNER JOIN qsTop ON qs.ID = qsTop.ID; + + /* Query Stats - Most Resource-Intensive Queries - CheckID 17 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 17',10,1) WITH NOWAIT; + END + + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, QueryText, QueryStatsNowID, QueryStatsFirstID, PlanHandle, QueryHash) + SELECT 17, 210, 'Query Stats', 'Most Resource-Intensive Queries', 'https://www.brentozar.com/go/topqueries', + 'Query stats during the sample:' + @LineFeed + + 'Executions: ' + CAST(qsNow.execution_count - (COALESCE(qsFirst.execution_count, 0)) AS NVARCHAR(100)) + @LineFeed + + 'Elapsed Time: ' + CAST(qsNow.total_elapsed_time - (COALESCE(qsFirst.total_elapsed_time, 0)) AS NVARCHAR(100)) + @LineFeed + + 'CPU Time: ' + CAST(qsNow.total_worker_time - (COALESCE(qsFirst.total_worker_time, 0)) AS NVARCHAR(100)) + @LineFeed + + 'Logical Reads: ' + CAST(qsNow.total_logical_reads - (COALESCE(qsFirst.total_logical_reads, 0)) AS NVARCHAR(100)) + @LineFeed + + 'Logical Writes: ' + CAST(qsNow.total_logical_writes - (COALESCE(qsFirst.total_logical_writes, 0)) AS NVARCHAR(100)) + @LineFeed + + 'CLR Time: ' + CAST(qsNow.total_clr_time - (COALESCE(qsFirst.total_clr_time, 0)) AS NVARCHAR(100)) + @LineFeed + + @LineFeed + @LineFeed + 'Query stats since ' + CONVERT(NVARCHAR(100), qsNow.creation_time ,121) + @LineFeed + + 'Executions: ' + CAST(qsNow.execution_count AS NVARCHAR(100)) + + CASE qsTotal.execution_count WHEN 0 THEN '' ELSE (' - Percent of Server Total: ' + CAST(CAST(100.0 * qsNow.execution_count / qsTotal.execution_count AS DECIMAL(6,2)) AS NVARCHAR(100)) + '%') END + @LineFeed + + 'Elapsed Time: ' + CAST(qsNow.total_elapsed_time AS NVARCHAR(100)) + + CASE qsTotal.total_elapsed_time WHEN 0 THEN '' ELSE (' - Percent of Server Total: ' + CAST(CAST(100.0 * qsNow.total_elapsed_time / qsTotal.total_elapsed_time AS DECIMAL(6,2)) AS NVARCHAR(100)) + '%') END + @LineFeed + + 'CPU Time: ' + CAST(qsNow.total_worker_time AS NVARCHAR(100)) + + CASE qsTotal.total_worker_time WHEN 0 THEN '' ELSE (' - Percent of Server Total: ' + CAST(CAST(100.0 * qsNow.total_worker_time / qsTotal.total_worker_time AS DECIMAL(6,2)) AS NVARCHAR(100)) + '%') END + @LineFeed + + 'Logical Reads: ' + CAST(qsNow.total_logical_reads AS NVARCHAR(100)) + + CASE qsTotal.total_logical_reads WHEN 0 THEN '' ELSE (' - Percent of Server Total: ' + CAST(CAST(100.0 * qsNow.total_logical_reads / qsTotal.total_logical_reads AS DECIMAL(6,2)) AS NVARCHAR(100)) + '%') END + @LineFeed + + 'Logical Writes: ' + CAST(qsNow.total_logical_writes AS NVARCHAR(100)) + + CASE qsTotal.total_logical_writes WHEN 0 THEN '' ELSE (' - Percent of Server Total: ' + CAST(CAST(100.0 * qsNow.total_logical_writes / qsTotal.total_logical_writes AS DECIMAL(6,2)) AS NVARCHAR(100)) + '%') END + @LineFeed + + 'CLR Time: ' + CAST(qsNow.total_clr_time AS NVARCHAR(100)) + + CASE qsTotal.total_clr_time WHEN 0 THEN '' ELSE (' - Percent of Server Total: ' + CAST(CAST(100.0 * qsNow.total_clr_time / qsTotal.total_clr_time AS DECIMAL(6,2)) AS NVARCHAR(100)) + '%') END + @LineFeed + + --@LineFeed + @LineFeed + 'Query hash: ' + CAST(qsNow.query_hash AS NVARCHAR(100)) + @LineFeed + + --@LineFeed + @LineFeed + 'Query plan hash: ' + CAST(qsNow.query_plan_hash AS NVARCHAR(100)) + + @LineFeed AS Details, + 'See the URL for tuning tips on why this query may be consuming resources.' AS HowToStopIt, + qp.query_plan, + QueryText = SUBSTRING(st.text, + (qsNow.statement_start_offset / 2) + 1, + ((CASE qsNow.statement_end_offset + WHEN -1 THEN DATALENGTH(st.text) + ELSE qsNow.statement_end_offset + END - qsNow.statement_start_offset) / 2) + 1), + qsNow.ID AS QueryStatsNowID, + qsFirst.ID AS QueryStatsFirstID, + qsNow.plan_handle AS PlanHandle, + qsNow.query_hash + FROM #QueryStats qsNow + INNER JOIN #QueryStats qsTotal ON qsTotal.Pass = 0 + LEFT OUTER JOIN #QueryStats qsFirst ON qsNow.[sql_handle] = qsFirst.[sql_handle] AND qsNow.statement_start_offset = qsFirst.statement_start_offset AND qsNow.statement_end_offset = qsFirst.statement_end_offset AND qsNow.plan_generation_num = qsFirst.plan_generation_num AND qsNow.plan_handle = qsFirst.plan_handle AND qsFirst.Pass = 1 + CROSS APPLY sys.dm_exec_sql_text(qsNow.sql_handle) AS st + CROSS APPLY sys.dm_exec_query_plan(qsNow.plan_handle) AS qp + WHERE qsNow.Points > 0 AND st.text IS NOT NULL AND qp.query_plan IS NOT NULL; + + UPDATE #BlitzFirstResults + SET DatabaseID = CAST(attr.value AS INT), + DatabaseName = DB_NAME(CAST(attr.value AS INT)) + FROM #BlitzFirstResults + CROSS APPLY sys.dm_exec_plan_attributes(#BlitzFirstResults.PlanHandle) AS attr + WHERE attr.attribute = 'dbid'; + + + END; /* IF DATEDIFF(ss, @FinishSampleTime, SYSDATETIMEOFFSET()) > 10 AND @CheckProcedureCache = 1 */ + + + RAISERROR('Analyzing changes between first and second passes of DMVs',10,1) WITH NOWAIT; + + /* Wait Stats - CheckID 6 */ + /* Compare the current wait stats to the sample we took at the start, and insert the top 10 waits. */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 6',10,1) WITH NOWAIT; + END + + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, DetailsInt) + SELECT TOP 10 6 AS CheckID, + 200 AS Priority, + 'Wait Stats' AS FindingGroup, + wNow.wait_type AS Finding, /* IF YOU CHANGE THIS, STUFF WILL BREAK. Other checks look for wait type names in the Finding field. See checks 11, 12 as example. */ + N'https://www.sqlskills.com/help/waits/' + LOWER(wNow.wait_type) + '/' AS URL, + 'For ' + CAST(((wNow.wait_time_ms - COALESCE(wBase.wait_time_ms,0)) / 1000) AS NVARCHAR(100)) + ' seconds over the last ' + CASE @Seconds WHEN 0 THEN (CAST(DATEDIFF(dd,@StartSampleTime,@FinishSampleTime) AS NVARCHAR(10)) + ' days') ELSE (CAST(@Seconds AS NVARCHAR(10)) + ' seconds') END + ', SQL Server was waiting on this particular bottleneck.' + @LineFeed + @LineFeed AS Details, + 'See the URL for more details on how to mitigate this wait type.' AS HowToStopIt, + ((wNow.wait_time_ms - COALESCE(wBase.wait_time_ms,0)) / 1000) AS DetailsInt + FROM #WaitStats wNow + LEFT OUTER JOIN #WaitStats wBase ON wNow.wait_type = wBase.wait_type AND wNow.SampleTime > wBase.SampleTime + WHERE wNow.wait_time_ms > (wBase.wait_time_ms + (.5 * (DATEDIFF(ss,@StartSampleTime,@FinishSampleTime)) * 1000)) /* Only look for things we've actually waited on for half of the time or more */ + ORDER BY (wNow.wait_time_ms - COALESCE(wBase.wait_time_ms,0)) DESC; + + /* Server Performance - Poison Wait Detected - CheckID 30 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 30',10,1) WITH NOWAIT; + END + + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, DetailsInt) + SELECT 30 AS CheckID, + 10 AS Priority, + 'Server Performance' AS FindingGroup, + 'Poison Wait Detected: ' + wNow.wait_type AS Finding, + N'https://www.brentozar.com/go/poison/#' + wNow.wait_type AS URL, + 'For ' + CAST(((wNow.wait_time_ms - COALESCE(wBase.wait_time_ms,0)) / 1000) AS NVARCHAR(100)) + ' seconds over the last ' + CASE @Seconds WHEN 0 THEN (CAST(DATEDIFF(dd,@StartSampleTime,@FinishSampleTime) AS NVARCHAR(10)) + ' days') ELSE (CAST(@Seconds AS NVARCHAR(10)) + ' seconds') END + ', SQL Server was waiting on this particular bottleneck.' + @LineFeed + @LineFeed AS Details, + 'See the URL for more details on how to mitigate this wait type.' AS HowToStopIt, + ((wNow.wait_time_ms - COALESCE(wBase.wait_time_ms,0)) / 1000) AS DetailsInt + FROM #WaitStats wNow + LEFT OUTER JOIN #WaitStats wBase ON wNow.wait_type = wBase.wait_type AND wNow.SampleTime > wBase.SampleTime + WHERE wNow.wait_type IN ('IO_QUEUE_LIMIT', 'IO_RETRY', 'LOG_RATE_GOVERNOR', 'POOL_LOG_RATE_GOVERNOR', 'PREEMPTIVE_DEBUG', 'RESMGR_THROTTLED', 'RESOURCE_SEMAPHORE', 'RESOURCE_SEMAPHORE_QUERY_COMPILE','SE_REPL_CATCHUP_THROTTLE','SE_REPL_COMMIT_ACK','SE_REPL_COMMIT_TURN','SE_REPL_ROLLBACK_ACK','SE_REPL_SLOW_SECONDARY_THROTTLE','THREADPOOL') + AND wNow.wait_time_ms > (wBase.wait_time_ms + 1000); + + + /* Server Performance - Slow Data File Reads - CheckID 11 */ + IF EXISTS (SELECT * FROM #BlitzFirstResults WHERE Finding LIKE 'PAGEIOLATCH%') + BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 11',10,1) WITH NOWAIT; + END + + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, DatabaseID, DatabaseName) + SELECT TOP 10 11 AS CheckID, + 50 AS Priority, + 'Server Performance' AS FindingGroup, + 'Slow Data File Reads' AS Finding, + 'https://www.brentozar.com/blitz/slow-storage-reads-writes/' AS URL, + 'Your server is experiencing PAGEIOLATCH% waits due to slow data file reads. This file is one of the reasons why.' + @LineFeed + + 'File: ' + fNow.PhysicalName + @LineFeed + + 'Number of reads during the sample: ' + CAST((fNow.num_of_reads - fBase.num_of_reads) AS NVARCHAR(20)) + @LineFeed + + 'Seconds spent waiting on storage for these reads: ' + CAST(((fNow.io_stall_read_ms - fBase.io_stall_read_ms) / 1000.0) AS NVARCHAR(20)) + @LineFeed + + 'Average read latency during the sample: ' + CAST(((fNow.io_stall_read_ms - fBase.io_stall_read_ms) / (fNow.num_of_reads - fBase.num_of_reads) ) AS NVARCHAR(20)) + ' milliseconds' + @LineFeed + + 'Microsoft guidance for data file read speed: 20ms or less.' + @LineFeed + @LineFeed AS Details, + 'See the URL for more details on how to mitigate this wait type.' AS HowToStopIt, + fNow.DatabaseID, + fNow.DatabaseName + FROM #FileStats fNow + INNER JOIN #FileStats fBase ON fNow.DatabaseID = fBase.DatabaseID AND fNow.FileID = fBase.FileID AND fNow.SampleTime > fBase.SampleTime AND fNow.num_of_reads > fBase.num_of_reads AND fNow.io_stall_read_ms > (fBase.io_stall_read_ms + 1000) + WHERE (fNow.io_stall_read_ms - fBase.io_stall_read_ms) / (fNow.num_of_reads - fBase.num_of_reads) >= @FileLatencyThresholdMS + AND fNow.TypeDesc = 'ROWS' + ORDER BY (fNow.io_stall_read_ms - fBase.io_stall_read_ms) / (fNow.num_of_reads - fBase.num_of_reads) DESC; + END; + + /* Server Performance - Slow Log File Writes - CheckID 12 */ + IF EXISTS (SELECT * FROM #BlitzFirstResults WHERE Finding LIKE 'WRITELOG%') + BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 12',10,1) WITH NOWAIT; + END + + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, DatabaseID, DatabaseName) + SELECT TOP 10 12 AS CheckID, + 50 AS Priority, + 'Server Performance' AS FindingGroup, + 'Slow Log File Writes' AS Finding, + 'https://www.brentozar.com/blitz/slow-storage-reads-writes/' AS URL, + 'Your server is experiencing WRITELOG waits due to slow log file writes. This file is one of the reasons why.' + @LineFeed + + 'File: ' + fNow.PhysicalName + @LineFeed + + 'Number of writes during the sample: ' + CAST((fNow.num_of_writes - fBase.num_of_writes) AS NVARCHAR(20)) + @LineFeed + + 'Seconds spent waiting on storage for these writes: ' + CAST(((fNow.io_stall_write_ms - fBase.io_stall_write_ms) / 1000.0) AS NVARCHAR(20)) + @LineFeed + + 'Average write latency during the sample: ' + CAST(((fNow.io_stall_write_ms - fBase.io_stall_write_ms) / (fNow.num_of_writes - fBase.num_of_writes) ) AS NVARCHAR(20)) + ' milliseconds' + @LineFeed + + 'Microsoft guidance for log file write speed: 3ms or less.' + @LineFeed + @LineFeed AS Details, + 'See the URL for more details on how to mitigate this wait type.' AS HowToStopIt, + fNow.DatabaseID, + fNow.DatabaseName + FROM #FileStats fNow + INNER JOIN #FileStats fBase ON fNow.DatabaseID = fBase.DatabaseID AND fNow.FileID = fBase.FileID AND fNow.SampleTime > fBase.SampleTime AND fNow.num_of_writes > fBase.num_of_writes AND fNow.io_stall_write_ms > (fBase.io_stall_write_ms + 1000) + WHERE (fNow.io_stall_write_ms - fBase.io_stall_write_ms) / (fNow.num_of_writes - fBase.num_of_writes) >= @FileLatencyThresholdMS + AND fNow.TypeDesc = 'LOG' + ORDER BY (fNow.io_stall_write_ms - fBase.io_stall_write_ms) / (fNow.num_of_writes - fBase.num_of_writes) DESC; + END; + + + /* SQL Server Internal Maintenance - Log File Growing - CheckID 13 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 13',10,1) WITH NOWAIT; + END + + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) + SELECT 13 AS CheckID, + 1 AS Priority, + 'SQL Server Internal Maintenance' AS FindingGroup, + 'Log File Growing' AS Finding, + 'https://www.brentozar.com/askbrent/file-growing/' AS URL, + 'Number of growths during the sample: ' + CAST(ps.value_delta AS NVARCHAR(20)) + @LineFeed + + 'Determined by sampling Perfmon counter ' + ps.object_name + ' - ' + ps.counter_name + @LineFeed AS Details, + 'Pre-grow data and log files during maintenance windows so that they do not grow during production loads. See the URL for more details.' AS HowToStopIt + FROM #PerfmonStats ps + WHERE ps.Pass = 2 + AND object_name = @ServiceName + ':Databases' + AND counter_name = 'Log Growths' + AND value_delta > 0; + + + /* SQL Server Internal Maintenance - Log File Shrinking - CheckID 14 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 14',10,1) WITH NOWAIT; + END + + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) + SELECT 14 AS CheckID, + 1 AS Priority, + 'SQL Server Internal Maintenance' AS FindingGroup, + 'Log File Shrinking' AS Finding, + 'https://www.brentozar.com/askbrent/file-shrinking/' AS URL, + 'Number of shrinks during the sample: ' + CAST(ps.value_delta AS NVARCHAR(20)) + @LineFeed + + 'Determined by sampling Perfmon counter ' + ps.object_name + ' - ' + ps.counter_name + @LineFeed AS Details, + 'Pre-grow data and log files during maintenance windows so that they do not grow during production loads. See the URL for more details.' AS HowToStopIt + FROM #PerfmonStats ps + WHERE ps.Pass = 2 + AND object_name = @ServiceName + ':Databases' + AND counter_name = 'Log Shrinks' + AND value_delta > 0; + + /* Query Problems - Compilations/Sec High - CheckID 15 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 15',10,1) WITH NOWAIT; + END + + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) + SELECT 15 AS CheckID, + 50 AS Priority, + 'Query Problems' AS FindingGroup, + 'Compilations/Sec High' AS Finding, + 'https://www.brentozar.com/askbrent/compilations/' AS URL, + 'Number of batch requests during the sample: ' + CAST(ps.value_delta AS NVARCHAR(20)) + @LineFeed + + 'Number of compilations during the sample: ' + CAST(psComp.value_delta AS NVARCHAR(20)) + @LineFeed + + 'For OLTP environments, Microsoft recommends that 90% of batch requests should hit the plan cache, and not be compiled from scratch. We are exceeding that threshold.' + @LineFeed AS Details, + 'To find the queries that are compiling, start with:' + @LineFeed + + 'sp_BlitzCache @SortOrder = ''recent compilations''' + @LineFeed + + 'If dynamic SQL or non-parameterized strings are involved, consider enabling Forced Parameterization. See the URL for more details.' AS HowToStopIt + FROM #PerfmonStats ps + INNER JOIN #PerfmonStats psComp ON psComp.Pass = 2 AND psComp.object_name = @ServiceName + ':SQL Statistics' AND psComp.counter_name = 'SQL Compilations/sec' AND psComp.value_delta > 0 + WHERE ps.Pass = 2 + AND ps.object_name = @ServiceName + ':SQL Statistics' + AND ps.counter_name = 'Batch Requests/sec' + AND psComp.value_delta > 75 /* Because sp_BlitzFirst does around 50 compilations and re-compilations */ + AND (psComp.value_delta > (10 * @Seconds) OR psComp.value_delta > ps.value_delta) /* Either doing 10 compilations per second, or more compilations than queries */ + AND (psComp.value_delta * 10) > ps.value_delta; /* Compilations are more than 10% of batch requests per second */ + + /* Query Problems - Re-Compilations/Sec High - CheckID 16 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 16',10,1) WITH NOWAIT; + END + + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) + SELECT 16 AS CheckID, + 50 AS Priority, + 'Query Problems' AS FindingGroup, + 'Re-Compilations/Sec High' AS Finding, + 'https://www.brentozar.com/askbrent/recompilations/' AS URL, + 'Number of batch requests during the sample: ' + CAST(ps.value_delta AS NVARCHAR(20)) + @LineFeed + + 'Number of recompilations during the sample: ' + CAST(psComp.value_delta AS NVARCHAR(20)) + @LineFeed + + 'More than 10% of our queries are being recompiled. This is typically due to statistics changing on objects.' + @LineFeed AS Details, + 'To find the queries that are being forced to recompile, start with:' + @LineFeed + + 'sp_BlitzCache @SortOrder = ''recent compilations''' + @LineFeed + + 'Examine those plans to find out which objects are changing so quickly that they hit the stats update threshold. See the URL for more details.' AS HowToStopIt + FROM #PerfmonStats ps + INNER JOIN #PerfmonStats psComp ON psComp.Pass = 2 AND psComp.object_name = @ServiceName + ':SQL Statistics' AND psComp.counter_name = 'SQL Re-Compilations/sec' AND psComp.value_delta > 0 + WHERE ps.Pass = 2 + AND ps.object_name = @ServiceName + ':SQL Statistics' + AND ps.counter_name = 'Batch Requests/sec' + AND psComp.value_delta > 75 /* Because sp_BlitzFirst does around 50 compilations and re-compilations */ + AND (psComp.value_delta > (10 * @Seconds) OR psComp.value_delta > ps.value_delta) /* Either doing 10 recompilations per second, or more recompilations than queries */ + AND (psComp.value_delta * 10) > ps.value_delta; /* Recompilations are more than 10% of batch requests per second */ + + /* Table Problems - Forwarded Fetches/Sec High - CheckID 29 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 29',10,1) WITH NOWAIT; + END + + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) + SELECT 29 AS CheckID, + 40 AS Priority, + 'Table Problems' AS FindingGroup, + 'Forwarded Fetches/Sec High' AS Finding, + 'https://www.brentozar.com/go/fetch/' AS URL, + CAST(ps.value_delta AS NVARCHAR(20)) + ' forwarded fetches (from SQLServer:Access Methods counter)' + @LineFeed + + 'Check your heaps: they need to be rebuilt, or they need a clustered index applied.' + @LineFeed AS Details, + 'Rebuild your heaps. If you use Ola Hallengren maintenance scripts, those do not rebuild heaps by default: https://www.brentozar.com/archive/2016/07/fix-forwarded-records/' AS HowToStopIt + FROM #PerfmonStats ps + INNER JOIN #PerfmonStats psComp ON psComp.Pass = 2 AND psComp.object_name = @ServiceName + ':Access Methods' AND psComp.counter_name = 'Forwarded Records/sec' AND psComp.value_delta > 100 + WHERE ps.Pass = 2 + AND ps.object_name = @ServiceName + ':Access Methods' + AND ps.counter_name = 'Forwarded Records/sec' + AND ps.value_delta > (100 * @Seconds); /* Ignore servers sitting idle */ + + /* Check for temp objects with high forwarded fetches. + This has to be done as dynamic SQL because we have to execute OBJECT_NAME inside TempDB. */ + IF EXISTS (SELECT * FROM #BlitzFirstResults WHERE CheckID = 29) + BEGIN + SET @StringToExecute = N' + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) + SELECT TOP 10 29 AS CheckID, + 40 AS Priority, + ''Table Problems'' AS FindingGroup, + ''Forwarded Fetches/Sec High: TempDB Object'' AS Finding, + ''https://www.brentozar.com/go/fetch/'' AS URL, + CAST(COALESCE(os.forwarded_fetch_count,0) - COALESCE(os_prior.forwarded_fetch_count,0) AS NVARCHAR(20)) + '' forwarded fetches on '' + + CASE WHEN OBJECT_NAME(os.object_id) IS NULL THEN ''an unknown table '' + WHEN LEN(OBJECT_NAME(os.object_id)) < 50 THEN ''a table variable, internal identifier '' + OBJECT_NAME(os.object_id) + ELSE ''a temp table '' + OBJECT_NAME(os.object_id) + END AS Details, + ''Look through your source code to find the object creating these objects, and tune the creation and population to reduce fetches. See the URL for details.'' AS HowToStopIt + FROM tempdb.sys.dm_db_index_operational_stats(DB_ID(''tempdb''), NULL, NULL, NULL) os + LEFT OUTER JOIN #TempdbOperationalStats os_prior ON os.object_id = os_prior.object_id + AND os.forwarded_fetch_count > os_prior.forwarded_fetch_count + WHERE os.database_id = DB_ID(''tempdb'') + AND os.forwarded_fetch_count - COALESCE(os_prior.forwarded_fetch_count,0) > 100 + ORDER BY os.forwarded_fetch_count DESC;' + + EXECUTE sp_executesql @StringToExecute; + END + + /* In-Memory OLTP - Garbage Collection in Progress - CheckID 31 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 31',10,1) WITH NOWAIT; + END + + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) + SELECT 31 AS CheckID, + 50 AS Priority, + 'In-Memory OLTP' AS FindingGroup, + 'Garbage Collection in Progress' AS Finding, + 'https://www.brentozar.com/go/garbage/' AS URL, + CAST(ps.value_delta AS NVARCHAR(50)) + ' rows processed (from SQL Server YYYY XTP Garbage Collection:Rows processed/sec counter)' + @LineFeed + + 'This can happen for a few reasons: ' + @LineFeed + + 'Memory-Optimized TempDB, or ' + @LineFeed + + 'transactional workloads that constantly insert/delete data in In-Memory OLTP tables, or ' + @LineFeed + + 'memory pressure (causing In-Memory OLTP to shrink its footprint) or' AS Details, + 'Sadly, you cannot choose when garbage collection occurs. This is one of the many gotchas of Hekaton. Learn more: http://nedotter.com/archive/2016/04/row-version-lifecycle-for-in-memory-oltp/' AS HowToStopIt + FROM #PerfmonStats ps + INNER JOIN #PerfmonStats psComp ON psComp.Pass = 2 AND psComp.object_name LIKE '%XTP Garbage Collection' AND psComp.counter_name = 'Rows processed/sec' AND psComp.value_delta > 100 + WHERE ps.Pass = 2 + AND ps.object_name LIKE '%XTP Garbage Collection' + AND ps.counter_name = 'Rows processed/sec' + AND ps.value_delta > (100 * @Seconds); /* Ignore servers sitting idle */ + + /* In-Memory OLTP - Transactions Aborted - CheckID 32 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 32',10,1) WITH NOWAIT; + END + + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) + SELECT 32 AS CheckID, + 100 AS Priority, + 'In-Memory OLTP' AS FindingGroup, + 'Transactions Aborted' AS Finding, + 'https://www.brentozar.com/go/aborted/' AS URL, + CAST(ps.value_delta AS NVARCHAR(50)) + ' transactions aborted (from SQL Server YYYY XTP Transactions:Transactions aborted/sec counter)' + @LineFeed + + 'This may indicate that data is changing, or causing folks to retry their transactions, thereby increasing load.' AS Details, + 'Dig into your In-Memory OLTP transactions to figure out which ones are failing and being retried.' AS HowToStopIt + FROM #PerfmonStats ps + INNER JOIN #PerfmonStats psComp ON psComp.Pass = 2 AND psComp.object_name LIKE '%XTP Transactions' AND psComp.counter_name = 'Transactions aborted/sec' AND psComp.value_delta > 100 + WHERE ps.Pass = 2 + AND ps.object_name LIKE '%XTP Transactions' + AND ps.counter_name = 'Transactions aborted/sec' + AND ps.value_delta > (10 * @Seconds); /* Ignore servers sitting idle */ + + /* Query Problems - Suboptimal Plans/Sec High - CheckID 33 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 33',10,1) WITH NOWAIT; + END + + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) + SELECT 32 AS CheckID, + 100 AS Priority, + 'Query Problems' AS FindingGroup, + 'Suboptimal Plans/Sec High' AS Finding, + 'https://www.brentozar.com/go/suboptimal/' AS URL, + CAST(ps.value_delta AS NVARCHAR(50)) + ' plans reported in the ' + CAST(ps.instance_name AS NVARCHAR(100)) + ' workload group (from Workload GroupStats:Suboptimal plans/sec counter)' + @LineFeed + + 'Even if you are not using Resource Governor, it still tracks information about user queries, memory grants, etc.' AS Details, + 'Check out sp_BlitzCache to get more information about recent queries, or try sp_BlitzWho to see currently running queries.' AS HowToStopIt + FROM #PerfmonStats ps + INNER JOIN #PerfmonStats psComp ON psComp.Pass = 2 AND psComp.object_name = @ServiceName + ':Workload GroupStats' AND psComp.counter_name = 'Suboptimal plans/sec' AND psComp.value_delta > 100 + WHERE ps.Pass = 2 + AND ps.object_name = @ServiceName + ':Workload GroupStats' + AND ps.counter_name = 'Suboptimal plans/sec' + AND ps.value_delta > (10 * @Seconds); /* Ignore servers sitting idle */ + + /* Azure Performance - Database is Maxed Out - CheckID 41 */ + IF SERVERPROPERTY('EngineEdition') = 5 /*SERVERPROPERTY('Edition') = 'SQL Azure'*/ + BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 41',10,1) WITH NOWAIT; + END + + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) + SELECT 41 AS CheckID, + 10 AS Priority, + 'Azure Performance' AS FindingGroup, + 'Database is Maxed Out' AS Finding, + 'https://www.brentozar.com/go/maxedout' AS URL, + N'At ' + CONVERT(NVARCHAR(100), s.end_time ,121) + N', your database approached (or hit) your DTU limits:' + @LineFeed + + N'Average CPU percent: ' + CAST(avg_cpu_percent AS NVARCHAR(50)) + @LineFeed + + N'Average data IO percent: ' + CAST(avg_data_io_percent AS NVARCHAR(50)) + @LineFeed + + N'Average log write percent: ' + CAST(avg_log_write_percent AS NVARCHAR(50)) + @LineFeed + + N'Max worker percent: ' + CAST(max_worker_percent AS NVARCHAR(50)) + @LineFeed + + N'Max session percent: ' + CAST(max_session_percent AS NVARCHAR(50)) AS Details, + 'Tune your queries or indexes with sp_BlitzCache or sp_BlitzIndex, or consider upgrading to a higher DTU level.' AS HowToStopIt + FROM sys.dm_db_resource_stats s + WHERE s.end_time >= DATEADD(MI, -5, GETDATE()) + AND (avg_cpu_percent > 90 + OR avg_data_io_percent >= 90 + OR avg_log_write_percent >=90 + OR max_worker_percent >= 90 + OR max_session_percent >= 90); + END + + /* Server Info - Batch Requests per Sec - CheckID 19 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 19',10,1) WITH NOWAIT; + END + + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, DetailsInt) + SELECT 19 AS CheckID, + 250 AS Priority, + 'Server Info' AS FindingGroup, + 'Batch Requests per Sec' AS Finding, + 'https://www.brentozar.com/go/measure' AS URL, + CAST(CAST(ps.value_delta AS MONEY) / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS NVARCHAR(20)) AS Details, + ps.value_delta / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS DetailsInt + FROM #PerfmonStats ps + INNER JOIN #PerfmonStats ps1 ON ps.object_name = ps1.object_name AND ps.counter_name = ps1.counter_name AND ps1.Pass = 1 + WHERE ps.Pass = 2 + AND ps.object_name = @ServiceName + ':SQL Statistics' + AND ps.counter_name = 'Batch Requests/sec'; + + + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','SQL Compilations/sec', NULL); + INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','SQL Re-Compilations/sec', NULL); + + /* Server Info - SQL Compilations/sec - CheckID 25 */ + IF @ExpertMode >= 1 + BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 25',10,1) WITH NOWAIT; + END + + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, DetailsInt) + SELECT 25 AS CheckID, + 250 AS Priority, + 'Server Info' AS FindingGroup, + 'SQL Compilations per Sec' AS Finding, + 'https://www.brentozar.com/go/measure' AS URL, + CAST(ps.value_delta / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS NVARCHAR(20)) AS Details, + ps.value_delta / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS DetailsInt + FROM #PerfmonStats ps + INNER JOIN #PerfmonStats ps1 ON ps.object_name = ps1.object_name AND ps.counter_name = ps1.counter_name AND ps1.Pass = 1 + WHERE ps.Pass = 2 + AND ps.object_name = @ServiceName + ':SQL Statistics' + AND ps.counter_name = 'SQL Compilations/sec'; + END + + /* Server Info - SQL Re-Compilations/sec - CheckID 26 */ + IF @ExpertMode >= 1 + BEGIN + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 26',10,1) WITH NOWAIT; + END + + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, DetailsInt) + SELECT 26 AS CheckID, + 250 AS Priority, + 'Server Info' AS FindingGroup, + 'SQL Re-Compilations per Sec' AS Finding, + 'https://www.brentozar.com/go/measure' AS URL, + CAST(ps.value_delta / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS NVARCHAR(20)) AS Details, + ps.value_delta / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS DetailsInt + FROM #PerfmonStats ps + INNER JOIN #PerfmonStats ps1 ON ps.object_name = ps1.object_name AND ps.counter_name = ps1.counter_name AND ps1.Pass = 1 + WHERE ps.Pass = 2 + AND ps.object_name = @ServiceName + ':SQL Statistics' + AND ps.counter_name = 'SQL Re-Compilations/sec'; + END + + /* Server Info - Wait Time per Core per Sec - CheckID 20 */ + IF @Seconds > 0 + BEGIN; + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 20',10,1) WITH NOWAIT; + END; + + WITH waits1(SampleTime, waits_ms) AS (SELECT SampleTime, SUM(ws1.wait_time_ms) FROM #WaitStats ws1 WHERE ws1.Pass = 1 GROUP BY SampleTime), + waits2(SampleTime, waits_ms) AS (SELECT SampleTime, SUM(ws2.wait_time_ms) FROM #WaitStats ws2 WHERE ws2.Pass = 2 GROUP BY SampleTime), + cores(cpu_count) AS (SELECT SUM(1) FROM sys.dm_os_schedulers WHERE status = 'VISIBLE ONLINE' AND is_online = 1) + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, DetailsInt) + SELECT 20 AS CheckID, + 250 AS Priority, + 'Server Info' AS FindingGroup, + 'Wait Time per Core per Sec' AS Finding, + 'https://www.brentozar.com/go/measure' AS URL, + CAST((CAST(waits2.waits_ms - waits1.waits_ms AS MONEY)) / 1000 / i.cpu_count / ISNULL(NULLIF(DATEDIFF(ss, waits1.SampleTime, waits2.SampleTime), 0), 1) AS NVARCHAR(20)) AS Details, + (waits2.waits_ms - waits1.waits_ms) / 1000 / i.cpu_count / ISNULL(NULLIF(DATEDIFF(ss, waits1.SampleTime, waits2.SampleTime), 0), 1) AS DetailsInt + FROM cores i + CROSS JOIN waits1 + CROSS JOIN waits2; + END; + + IF @Seconds > 0 + BEGIN + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) + SELECT + 47 AS CheckId, + 50 AS Priority, + 'Query Problems' AS FindingsGroup, + 'High Percentage Of Runnable Queries' AS Finding, + 'https://erikdarlingdata.com/go/RunnableQueue/' AS URL, + 'On the ' + + CASE WHEN y.pass = 1 + THEN '1st' + ELSE '2nd' + END + + ' pass, ' + + RTRIM(y.runnable_pct) + + '% of your queries were waiting to get on a CPU to run. ' + + ' This can indicate CPU pressure.' + FROM + ( + SELECT + 2 AS pass, + x.total, + x.runnable, + CONVERT(decimal(5,2), + ( + x.runnable / + (1. * NULLIF(x.total, 0)) + ) + ) * 100. AS runnable_pct + FROM + ( + SELECT + COUNT_BIG(*) AS total, + SUM(CASE WHEN status = 'runnable' + THEN 1 + ELSE 0 + END) AS runnable + FROM sys.dm_exec_requests + WHERE session_id > 50 + ) AS x + ) AS y + WHERE y.runnable_pct > 20.; + END + + /* If we're waiting 30+ seconds, run these checks at the end. + We get this data from the ring buffers, and it's only updated once per minute, so might + as well get it now - whereas if we're checking 30+ seconds, it might get updated by the + end of our sp_BlitzFirst session. */ + IF @Seconds >= 30 + BEGIN + /* Server Performance - High CPU Utilization CheckID 24 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 24',10,1) WITH NOWAIT; + END + + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) + SELECT 24, 50, 'Server Performance', 'High CPU Utilization', CAST(100 - SystemIdle AS NVARCHAR(20)) + N'%. Ring buffer details: ' + CAST(record AS NVARCHAR(4000)), 100 - SystemIdle, 'https://www.brentozar.com/go/cpu' + FROM ( + SELECT record, + record.value('(./Record/SchedulerMonitorEvent/SystemHealth/SystemIdle)[1]', 'int') AS SystemIdle + FROM ( + SELECT TOP 1 CONVERT(XML, record) AS record + FROM sys.dm_os_ring_buffers + WHERE ring_buffer_type = N'RING_BUFFER_SCHEDULER_MONITOR' + AND record LIKE '%%' + ORDER BY timestamp DESC) AS rb + ) AS y + WHERE 100 - SystemIdle >= 50; + + /* Server Performance - CPU Utilization CheckID 23 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 23',10,1) WITH NOWAIT; + END + + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) + SELECT 23, 250, 'Server Info', 'CPU Utilization', CAST(100 - SystemIdle AS NVARCHAR(20)) + N'%. Ring buffer details: ' + CAST(record AS NVARCHAR(4000)), 100 - SystemIdle, 'https://www.brentozar.com/go/cpu' + FROM ( + SELECT record, + record.value('(./Record/SchedulerMonitorEvent/SystemHealth/SystemIdle)[1]', 'int') AS SystemIdle + FROM ( + SELECT TOP 1 CONVERT(XML, record) AS record + FROM sys.dm_os_ring_buffers + WHERE ring_buffer_type = N'RING_BUFFER_SCHEDULER_MONITOR' + AND record LIKE '%%' + ORDER BY timestamp DESC) AS rb + ) AS y; + + END; /* IF @Seconds >= 30 */ + + IF /* Let people on <2016 know about the thread time column */ + ( + @Seconds > 0 + AND @total_cpu_usage = 0 + ) + BEGIN + INSERT INTO + #BlitzFirstResults + ( + CheckID, + Priority, + FindingsGroup, + Finding, + Details, + URL + ) + SELECT + 48, + 254, + N'Informational', + N'Thread Time comes from the plan cache in versions earlier than 2016, and is not as reliable', + N'The oldest plan in your cache is from ' + + CONVERT(nvarchar(30), MIN(s.creation_time)) + + N' and your server was last restarted on ' + + CONVERT(nvarchar(30), MAX(o.sqlserver_start_time)), + N'https://docs.microsoft.com/en-us/sql/relational-databases/system-dynamic-management-views/sys-dm-os-schedulers-transact-sql' + FROM sys.dm_exec_query_stats AS s + CROSS JOIN sys.dm_os_sys_info AS o + OPTION(RECOMPILE); + END /* Let people on <2016 know about the thread time column */ + + /* If we didn't find anything, apologize. */ + IF NOT EXISTS (SELECT * FROM #BlitzFirstResults WHERE Priority < 250) + BEGIN + + INSERT INTO #BlitzFirstResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + VALUES ( -1 , + 1 , + 'No Problems Found' , + 'From Your Community Volunteers' , + 'http://FirstResponderKit.org/' , + 'Try running our more in-depth checks with sp_Blitz, or there may not be an unusual SQL Server performance problem. ' + ); + + END; /*IF NOT EXISTS (SELECT * FROM #BlitzFirstResults) */ + + /* Add credits for the nice folks who put so much time into building and maintaining this for free: */ + INSERT INTO #BlitzFirstResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + VALUES ( -1 , + 255 , + 'Thanks!' , + 'From Your Community Volunteers' , + 'http://FirstResponderKit.org/' , + 'To get help or add your own contributions, join us at http://FirstResponderKit.org.' + ); + + INSERT INTO #BlitzFirstResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + + ) + VALUES ( -1 , + 0 , + 'sp_BlitzFirst ' + CAST(CONVERT(DATETIMEOFFSET, @VersionDate, 102) AS VARCHAR(100)), + 'From Your Community Volunteers' , + 'http://FirstResponderKit.org/' , + 'We hope you found this tool useful.' + ); + + /* Outdated sp_BlitzFirst - sp_BlitzFirst is Over 6 Months Old - CheckID 27 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 27',10,1) WITH NOWAIT; + END + + IF DATEDIFF(MM, @VersionDate, SYSDATETIMEOFFSET()) > 6 + BEGIN + INSERT INTO #BlitzFirstResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT 27 AS CheckID , + 0 AS Priority , + 'Outdated sp_BlitzFirst' AS FindingsGroup , + 'sp_BlitzFirst is Over 6 Months Old' AS Finding , + 'http://FirstResponderKit.org/' AS URL , + 'Some things get better with age, like fine wine and your T-SQL. However, sp_BlitzFirst is not one of those things - time to go download the current one.' AS Details; + END; + + IF @CheckServerInfo = 0 /* Github #1680 */ + BEGIN + DELETE #BlitzFirstResults + WHERE FindingsGroup = 'Server Info'; + END + + RAISERROR('Analysis finished, outputting results',10,1) WITH NOWAIT; + + + /* If they want to run sp_BlitzCache and export to table, go for it. */ + IF @OutputTableNameBlitzCache IS NOT NULL + AND @OutputDatabaseName IS NOT NULL + AND @OutputSchemaName IS NOT NULL + AND EXISTS ( SELECT * + FROM sys.databases + WHERE QUOTENAME([name]) = @OutputDatabaseName) + BEGIN + + + RAISERROR('Calling sp_BlitzCache',10,1) WITH NOWAIT; + + + /* If they have an newer version of sp_BlitzCache that supports @MinutesBack and @CheckDateOverride */ + IF EXISTS (SELECT * FROM sys.objects o + INNER JOIN sys.parameters pMB ON o.object_id = pMB.object_id AND pMB.name = '@MinutesBack' + INNER JOIN sys.parameters pCDO ON o.object_id = pCDO.object_id AND pCDO.name = '@CheckDateOverride' + WHERE o.name = 'sp_BlitzCache') + BEGIN + /* Get the most recent sp_BlitzCache execution before this one - don't use sp_BlitzFirst because user logs are added in there at any time */ + SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' + + @OutputDatabaseName + + '.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''' + + @OutputSchemaName + ''' AND QUOTENAME(TABLE_NAME) = ''' + + QUOTENAME(@OutputTableNameBlitzCache) + ''') SELECT TOP 1 @BlitzCacheMinutesBack = DATEDIFF(MI,CheckDate,SYSDATETIMEOFFSET()) FROM ' + + @OutputDatabaseName + '.' + + @OutputSchemaName + '.' + + QUOTENAME(@OutputTableNameBlitzCache) + + ' WHERE ServerName = ''' + CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128)) + ''' ORDER BY CheckDate DESC;'; + EXEC sp_executesql @StringToExecute, N'@BlitzCacheMinutesBack INT OUTPUT', @BlitzCacheMinutesBack OUTPUT; + + /* If there's no data, let's just analyze the last 15 minutes of the plan cache */ + IF @BlitzCacheMinutesBack IS NULL OR @BlitzCacheMinutesBack < 1 OR @BlitzCacheMinutesBack > 60 + SET @BlitzCacheMinutesBack = 15; + + IF(@OutputType = 'NONE') + BEGIN + EXEC sp_BlitzCache + @OutputDatabaseName = @UnquotedOutputDatabaseName, + @OutputSchemaName = @UnquotedOutputSchemaName, + @OutputTableName = @OutputTableNameBlitzCache, + @CheckDateOverride = @StartSampleTime, + @SortOrder = 'all', + @SkipAnalysis = @BlitzCacheSkipAnalysis, + @MinutesBack = @BlitzCacheMinutesBack, + @Debug = @Debug, + @OutputType = @OutputType + ; + END; + ELSE + BEGIN + + EXEC sp_BlitzCache + @OutputDatabaseName = @UnquotedOutputDatabaseName, + @OutputSchemaName = @UnquotedOutputSchemaName, + @OutputTableName = @OutputTableNameBlitzCache, + @CheckDateOverride = @StartSampleTime, + @SortOrder = 'all', + @SkipAnalysis = @BlitzCacheSkipAnalysis, + @MinutesBack = @BlitzCacheMinutesBack, + @Debug = @Debug + ; + END; + + /* Delete history older than @OutputTableRetentionDays */ + SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' + + @OutputDatabaseName + + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' + + @OutputSchemaName + ''') DELETE ' + + @OutputDatabaseName + '.' + + @OutputSchemaName + '.' + + QUOTENAME(@OutputTableNameBlitzCache) + + ' WHERE ServerName = @SrvName AND CheckDate < @CheckDate;'; + EXEC sp_executesql @StringToExecute, + N'@SrvName NVARCHAR(128), @CheckDate date', + @LocalServerName, @OutputTableCleanupDate; + + + END; + + ELSE + BEGIN + /* No sp_BlitzCache found, or it's outdated - CheckID 36 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 36',10,1) WITH NOWAIT; + END + + INSERT INTO #BlitzFirstResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT 36 AS CheckID , + 0 AS Priority , + 'Outdated or Missing sp_BlitzCache' AS FindingsGroup , + 'Update Your sp_BlitzCache' AS Finding , + 'http://FirstResponderKit.org/' AS URL , + 'You passed in @OutputTableNameBlitzCache, but we need a newer version of sp_BlitzCache in master or the current database.' AS Details; + END; + + RAISERROR('sp_BlitzCache Finished',10,1) WITH NOWAIT; + + END; /* End running sp_BlitzCache */ + + /* @OutputTableName lets us export the results to a permanent table */ + IF @OutputDatabaseName IS NOT NULL + AND @OutputSchemaName IS NOT NULL + AND @OutputTableName IS NOT NULL + AND @OutputTableName NOT LIKE '#%' + AND EXISTS ( SELECT * + FROM sys.databases + WHERE QUOTENAME([name]) = @OutputDatabaseName) + BEGIN + SET @StringToExecute = 'USE ' + + @OutputDatabaseName + + '; IF EXISTS(SELECT * FROM ' + + @OutputDatabaseName + + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' + + @OutputSchemaName + + ''') AND NOT EXISTS (SELECT * FROM ' + + @OutputDatabaseName + + '.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''' + + @OutputSchemaName + ''' AND QUOTENAME(TABLE_NAME) = ''' + + @OutputTableName + ''') CREATE TABLE ' + + @OutputSchemaName + '.' + + @OutputTableName + + ' (ID INT IDENTITY(1,1) NOT NULL, + ServerName NVARCHAR(128), + CheckDate DATETIMEOFFSET, + CheckID INT NOT NULL, + Priority TINYINT NOT NULL, + FindingsGroup VARCHAR(50) NOT NULL, + Finding VARCHAR(200) NOT NULL, + URL VARCHAR(200) NOT NULL, + Details NVARCHAR(4000) NULL, + HowToStopIt [XML] NULL, + QueryPlan [XML] NULL, + QueryText NVARCHAR(MAX) NULL, + StartTime DATETIMEOFFSET NULL, + LoginName NVARCHAR(128) NULL, + NTUserName NVARCHAR(128) NULL, + OriginalLoginName NVARCHAR(128) NULL, + ProgramName NVARCHAR(128) NULL, + HostName NVARCHAR(128) NULL, + DatabaseID INT NULL, + DatabaseName NVARCHAR(128) NULL, + OpenTransactionCount INT NULL, + DetailsInt INT NULL, + QueryHash BINARY(8) NULL, + JoinKey AS ServerName + CAST(CheckDate AS NVARCHAR(50)), + PRIMARY KEY CLUSTERED (ID ASC));'; + + EXEC(@StringToExecute); + + /* If the table doesn't have the new QueryHash column, add it. See Github #2162. */ + SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableName; + SET @StringToExecute = N'IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns + WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''QueryHash'') + ALTER TABLE ' + @ObjectFullName + N' ADD QueryHash BINARY(8) NULL;'; + EXEC(@StringToExecute); + + /* If the table doesn't have the new JoinKey computed column, add it. See Github #2164. */ + SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableName; + SET @StringToExecute = N'IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns + WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''JoinKey'') + ALTER TABLE ' + @ObjectFullName + N' ADD JoinKey AS ServerName + CAST(CheckDate AS NVARCHAR(50));'; + EXEC(@StringToExecute); + + SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' + + @OutputDatabaseName + + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' + + @OutputSchemaName + ''') INSERT ' + + @OutputDatabaseName + '.' + + @OutputSchemaName + '.' + + @OutputTableName + + ' (ServerName, CheckDate, CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, QueryText, StartTime, LoginName, NTUserName, OriginalLoginName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, DetailsInt, QueryHash) SELECT ' + + ' @SrvName, @CheckDate, CheckID, Priority, FindingsGroup, Finding, URL, LEFT(Details,4000), HowToStopIt, QueryPlan, QueryText, StartTime, LoginName, NTUserName, OriginalLoginName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, DetailsInt, QueryHash FROM #BlitzFirstResults ORDER BY Priority , FindingsGroup , Finding , Details'; + + EXEC sp_executesql @StringToExecute, + N'@SrvName NVARCHAR(128), @CheckDate datetimeoffset', + @LocalServerName, @StartSampleTime; + + /* Delete history older than @OutputTableRetentionDays */ + SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' + + @OutputDatabaseName + + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' + + @OutputSchemaName + ''') DELETE ' + + @OutputDatabaseName + '.' + + @OutputSchemaName + '.' + + @OutputTableName + + ' WHERE ServerName = @SrvName AND CheckDate < @CheckDate ;'; + + EXEC sp_executesql @StringToExecute, + N'@SrvName NVARCHAR(128), @CheckDate date', + @LocalServerName, @OutputTableCleanupDate; + + END; + ELSE IF (SUBSTRING(@OutputTableName, 2, 2) = '##') + BEGIN + SET @StringToExecute = N' IF (OBJECT_ID(''tempdb..' + + @OutputTableName + + ''') IS NULL) CREATE TABLE ' + + @OutputTableName + + ' (ID INT IDENTITY(1,1) NOT NULL, + ServerName NVARCHAR(128), + CheckDate DATETIMEOFFSET, + CheckID INT NOT NULL, + Priority TINYINT NOT NULL, + FindingsGroup VARCHAR(50) NOT NULL, + Finding VARCHAR(200) NOT NULL, + URL VARCHAR(200) NOT NULL, + Details NVARCHAR(4000) NULL, + HowToStopIt [XML] NULL, + QueryPlan [XML] NULL, + QueryText NVARCHAR(MAX) NULL, + StartTime DATETIMEOFFSET NULL, + LoginName NVARCHAR(128) NULL, + NTUserName NVARCHAR(128) NULL, + OriginalLoginName NVARCHAR(128) NULL, + ProgramName NVARCHAR(128) NULL, + HostName NVARCHAR(128) NULL, + DatabaseID INT NULL, + DatabaseName NVARCHAR(128) NULL, + OpenTransactionCount INT NULL, + DetailsInt INT NULL, + QueryHash BINARY(8) NULL, + JoinKey AS ServerName + CAST(CheckDate AS NVARCHAR(50)), + PRIMARY KEY CLUSTERED (ID ASC));' + + ' INSERT ' + + @OutputTableName + + ' (ServerName, CheckDate, CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, QueryText, StartTime, LoginName, NTUserName, OriginalLoginName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, DetailsInt) SELECT ' + + ' @SrvName, @CheckDate, CheckID, Priority, FindingsGroup, Finding, URL, LEFT(Details,4000), HowToStopIt, QueryPlan, QueryText, StartTime, LoginName, NTUserName, OriginalLoginName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, DetailsInt FROM #BlitzFirstResults ORDER BY Priority , FindingsGroup , Finding , Details'; + + EXEC sp_executesql @StringToExecute, + N'@SrvName NVARCHAR(128), @CheckDate datetimeoffset', + @LocalServerName, @StartSampleTime; + END; + ELSE IF (SUBSTRING(@OutputTableName, 2, 1) = '#') + BEGIN + RAISERROR('Due to the nature of Dymamic SQL, only global (i.e. double pound (##)) temp tables are supported for @OutputTableName', 16, 0); + END; + + /* @OutputTableNameFileStats lets us export the results to a permanent table */ + IF @OutputDatabaseName IS NOT NULL + AND @OutputSchemaName IS NOT NULL + AND @OutputTableNameFileStats IS NOT NULL + AND @OutputTableNameFileStats NOT LIKE '#%' + AND EXISTS ( SELECT * + FROM sys.databases + WHERE QUOTENAME([name]) = @OutputDatabaseName) + BEGIN + /* Create the table */ + SET @StringToExecute = 'USE ' + + @OutputDatabaseName + + '; IF EXISTS(SELECT * FROM ' + + @OutputDatabaseName + + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' + + @OutputSchemaName + + ''') AND NOT EXISTS (SELECT * FROM ' + + @OutputDatabaseName + + '.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''' + + @OutputSchemaName + ''' AND QUOTENAME(TABLE_NAME) = ''' + + @OutputTableNameFileStats + ''') CREATE TABLE ' + + @OutputSchemaName + '.' + + @OutputTableNameFileStats + + ' (ID INT IDENTITY(1,1) NOT NULL, + ServerName NVARCHAR(128), + CheckDate DATETIMEOFFSET, + DatabaseID INT NOT NULL, + FileID INT NOT NULL, + DatabaseName NVARCHAR(256) , + FileLogicalName NVARCHAR(256) , + TypeDesc NVARCHAR(60) , + SizeOnDiskMB BIGINT , + io_stall_read_ms BIGINT , + num_of_reads BIGINT , + bytes_read BIGINT , + io_stall_write_ms BIGINT , + num_of_writes BIGINT , + bytes_written BIGINT, + PhysicalName NVARCHAR(520) , + PRIMARY KEY CLUSTERED (ID ASC));'; + + EXEC(@StringToExecute); + + SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableNameFileStats_View; + + /* If the view exists without the most recently added columns, drop it. See Github #2162. */ + IF OBJECT_ID(@ObjectFullName) IS NOT NULL + BEGIN + SET @StringToExecute = N'USE ' + @OutputDatabaseName + N'; IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns + WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''JoinKey'') + DROP VIEW ' + @OutputSchemaName + N'.' + @OutputTableNameFileStats_View + N';'; + + EXEC(@StringToExecute); + END + + /* Create the view */ + IF OBJECT_ID(@ObjectFullName) IS NULL + BEGIN + SET @StringToExecute = 'USE ' + + @OutputDatabaseName + + '; EXEC (''CREATE VIEW ' + + @OutputSchemaName + '.' + + @OutputTableNameFileStats_View + ' AS ' + @LineFeed + + 'WITH RowDates as' + @LineFeed + + '(' + @LineFeed + + ' SELECT ' + @LineFeed + + ' ROW_NUMBER() OVER (ORDER BY [ServerName], [CheckDate]) ID,' + @LineFeed + + ' [CheckDate]' + @LineFeed + + ' FROM ' + @OutputSchemaName + '.' + @OutputTableNameFileStats + '' + @LineFeed + + ' GROUP BY [ServerName], [CheckDate]' + @LineFeed + + '),' + @LineFeed + + 'CheckDates as' + @LineFeed + + '(' + @LineFeed + + ' SELECT ThisDate.CheckDate,' + @LineFeed + + ' LastDate.CheckDate as PreviousCheckDate' + @LineFeed + + ' FROM RowDates ThisDate' + @LineFeed + + ' JOIN RowDates LastDate' + @LineFeed + + ' ON ThisDate.ID = LastDate.ID + 1' + @LineFeed + + ')' + @LineFeed + + ' SELECT f.ServerName,' + @LineFeed + + ' f.CheckDate,' + @LineFeed + + ' f.DatabaseID,' + @LineFeed + + ' f.DatabaseName,' + @LineFeed + + ' f.FileID,' + @LineFeed + + ' f.FileLogicalName,' + @LineFeed + + ' f.TypeDesc,' + @LineFeed + + ' f.PhysicalName,' + @LineFeed + + ' f.SizeOnDiskMB,' + @LineFeed + + ' DATEDIFF(ss, fPrior.CheckDate, f.CheckDate) AS ElapsedSeconds,' + @LineFeed + + ' (f.SizeOnDiskMB - fPrior.SizeOnDiskMB) AS SizeOnDiskMBgrowth,' + @LineFeed + + ' (f.io_stall_read_ms - fPrior.io_stall_read_ms) AS io_stall_read_ms,' + @LineFeed + + ' io_stall_read_ms_average = CASE' + @LineFeed + + ' WHEN(f.num_of_reads - fPrior.num_of_reads) = 0' + @LineFeed + + ' THEN 0' + @LineFeed + + ' ELSE(f.io_stall_read_ms - fPrior.io_stall_read_ms) / (f.num_of_reads - fPrior.num_of_reads)' + @LineFeed + + ' END,' + @LineFeed + + ' (f.num_of_reads - fPrior.num_of_reads) AS num_of_reads,' + @LineFeed + + ' (f.bytes_read - fPrior.bytes_read) / 1024.0 / 1024.0 AS megabytes_read,' + @LineFeed + + ' (f.io_stall_write_ms - fPrior.io_stall_write_ms) AS io_stall_write_ms,' + @LineFeed + + ' io_stall_write_ms_average = CASE' + @LineFeed + + ' WHEN(f.num_of_writes - fPrior.num_of_writes) = 0' + @LineFeed + + ' THEN 0' + @LineFeed + + ' ELSE(f.io_stall_write_ms - fPrior.io_stall_write_ms) / (f.num_of_writes - fPrior.num_of_writes)' + @LineFeed + + ' END,' + @LineFeed + + ' (f.num_of_writes - fPrior.num_of_writes) AS num_of_writes,' + @LineFeed + + ' (f.bytes_written - fPrior.bytes_written) / 1024.0 / 1024.0 AS megabytes_written, ' + @LineFeed + + ' f.ServerName + CAST(f.CheckDate AS NVARCHAR(50)) AS JoinKey' + @LineFeed + + ' FROM ' + @OutputSchemaName + '.' + @OutputTableNameFileStats + ' f' + @LineFeed + + ' INNER HASH JOIN CheckDates DATES ON f.CheckDate = DATES.CheckDate' + @LineFeed + + ' INNER JOIN ' + @OutputSchemaName + '.' + @OutputTableNameFileStats + ' fPrior ON f.ServerName = fPrior.ServerName' + @LineFeed + + ' AND f.DatabaseID = fPrior.DatabaseID' + @LineFeed + + ' AND f.FileID = fPrior.FileID' + @LineFeed + + ' AND fPrior.CheckDate = DATES.PreviousCheckDate' + @LineFeed + + '' + @LineFeed + + ' WHERE f.num_of_reads >= fPrior.num_of_reads' + @LineFeed + + ' AND f.num_of_writes >= fPrior.num_of_writes' + @LineFeed + + ' AND DATEDIFF(MI, fPrior.CheckDate, f.CheckDate) BETWEEN 1 AND 60;'')' + + EXEC(@StringToExecute); + END; + + + SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' + + @OutputDatabaseName + + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' + + @OutputSchemaName + ''') INSERT ' + + @OutputDatabaseName + '.' + + @OutputSchemaName + '.' + + @OutputTableNameFileStats + + ' (ServerName, CheckDate, DatabaseID, FileID, DatabaseName, FileLogicalName, TypeDesc, SizeOnDiskMB, io_stall_read_ms, num_of_reads, bytes_read, io_stall_write_ms, num_of_writes, bytes_written, PhysicalName) SELECT ' + + ' @SrvName, @CheckDate, DatabaseID, FileID, DatabaseName, FileLogicalName, TypeDesc, SizeOnDiskMB, io_stall_read_ms, num_of_reads, bytes_read, io_stall_write_ms, num_of_writes, bytes_written, PhysicalName FROM #FileStats WHERE Pass = 2'; + + EXEC sp_executesql @StringToExecute, + N'@SrvName NVARCHAR(128), @CheckDate datetimeoffset', + @LocalServerName, @StartSampleTime; + + /* Delete history older than @OutputTableRetentionDays */ + SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' + + @OutputDatabaseName + + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' + + @OutputSchemaName + ''') DELETE ' + + @OutputDatabaseName + '.' + + @OutputSchemaName + '.' + + @OutputTableNameFileStats + + ' WHERE ServerName = @SrvName AND CheckDate < @CheckDate ;'; + + EXEC sp_executesql @StringToExecute, + N'@SrvName NVARCHAR(128), @CheckDate date', + @LocalServerName, @OutputTableCleanupDate; + + END; + ELSE IF (SUBSTRING(@OutputTableNameFileStats, 2, 2) = '##') + BEGIN + SET @StringToExecute = N' IF (OBJECT_ID(''tempdb..' + + @OutputTableNameFileStats + + ''') IS NULL) CREATE TABLE ' + + @OutputTableNameFileStats + + ' (ID INT IDENTITY(1,1) NOT NULL, + ServerName NVARCHAR(128), + CheckDate DATETIMEOFFSET, + DatabaseID INT NOT NULL, + FileID INT NOT NULL, + DatabaseName NVARCHAR(256) , + FileLogicalName NVARCHAR(256) , + TypeDesc NVARCHAR(60) , + SizeOnDiskMB BIGINT , + io_stall_read_ms BIGINT , + num_of_reads BIGINT , + bytes_read BIGINT , + io_stall_write_ms BIGINT , + num_of_writes BIGINT , + bytes_written BIGINT, + PhysicalName NVARCHAR(520) , + DetailsInt INT NULL, + PRIMARY KEY CLUSTERED (ID ASC));' + + ' INSERT ' + + @OutputTableNameFileStats + + ' (ServerName, CheckDate, DatabaseID, FileID, DatabaseName, FileLogicalName, TypeDesc, SizeOnDiskMB, io_stall_read_ms, num_of_reads, bytes_read, io_stall_write_ms, num_of_writes, bytes_written, PhysicalName) SELECT ' + + ' @SrvName, @CheckDate, DatabaseID, FileID, DatabaseName, FileLogicalName, TypeDesc, SizeOnDiskMB, io_stall_read_ms, num_of_reads, bytes_read, io_stall_write_ms, num_of_writes, bytes_written, PhysicalName FROM #FileStats WHERE Pass = 2'; + + EXEC sp_executesql @StringToExecute, + N'@SrvName NVARCHAR(128), @CheckDate datetimeoffset', + @LocalServerName, @StartSampleTime; + END; + ELSE IF (SUBSTRING(@OutputTableNameFileStats, 2, 1) = '#') + BEGIN + RAISERROR('Due to the nature of Dymamic SQL, only global (i.e. double pound (##)) temp tables are supported for @OutputTableName', 16, 0); + END; + + + /* @OutputTableNamePerfmonStats lets us export the results to a permanent table */ + IF @OutputDatabaseName IS NOT NULL + AND @OutputSchemaName IS NOT NULL + AND @OutputTableNamePerfmonStats IS NOT NULL + AND @OutputTableNamePerfmonStats NOT LIKE '#%' + AND EXISTS ( SELECT * + FROM sys.databases + WHERE QUOTENAME([name]) = @OutputDatabaseName) + BEGIN + /* Create the table */ + SET @StringToExecute = 'USE ' + + @OutputDatabaseName + + '; IF EXISTS(SELECT * FROM ' + + @OutputDatabaseName + + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' + + @OutputSchemaName + + ''') AND NOT EXISTS (SELECT * FROM ' + + @OutputDatabaseName + + '.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''' + + @OutputSchemaName + ''' AND QUOTENAME(TABLE_NAME) = ''' + + @OutputTableNamePerfmonStats + ''') CREATE TABLE ' + + @OutputSchemaName + '.' + + @OutputTableNamePerfmonStats + + ' (ID INT IDENTITY(1,1) NOT NULL, + ServerName NVARCHAR(128), + CheckDate DATETIMEOFFSET, + [object_name] NVARCHAR(128) NOT NULL, + [counter_name] NVARCHAR(128) NOT NULL, + [instance_name] NVARCHAR(128) NULL, + [cntr_value] BIGINT NULL, + [cntr_type] INT NOT NULL, + [value_delta] BIGINT NULL, + [value_per_second] DECIMAL(18,2) NULL, + PRIMARY KEY CLUSTERED (ID ASC));'; + + EXEC(@StringToExecute); + + SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableNamePerfmonStats_View; + + /* If the view exists without the most recently added columns, drop it. See Github #2162. */ + IF OBJECT_ID(@ObjectFullName) IS NOT NULL + BEGIN + SET @StringToExecute = N'USE ' + @OutputDatabaseName + N'; IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns + WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''JoinKey'') + DROP VIEW ' + @OutputSchemaName + N'.' + @OutputTableNamePerfmonStats_View + N';'; + + EXEC(@StringToExecute); + END + + /* Create the view */ + IF OBJECT_ID(@ObjectFullName) IS NULL + BEGIN + SET @StringToExecute = 'USE ' + + @OutputDatabaseName + + '; EXEC (''CREATE VIEW ' + + @OutputSchemaName + '.' + + @OutputTableNamePerfmonStats_View + ' AS ' + @LineFeed + + 'WITH RowDates as' + @LineFeed + + '(' + @LineFeed + + ' SELECT ' + @LineFeed + + ' ROW_NUMBER() OVER (ORDER BY [ServerName], [CheckDate]) ID,' + @LineFeed + + ' [CheckDate]' + @LineFeed + + ' FROM ' + @OutputSchemaName + '.' +@OutputTableNamePerfmonStats + '' + @LineFeed + + ' GROUP BY [ServerName], [CheckDate]' + @LineFeed + + '),' + @LineFeed + + 'CheckDates as' + @LineFeed + + '(' + @LineFeed + + ' SELECT ThisDate.CheckDate,' + @LineFeed + + ' LastDate.CheckDate as PreviousCheckDate' + @LineFeed + + ' FROM RowDates ThisDate' + @LineFeed + + ' JOIN RowDates LastDate' + @LineFeed + + ' ON ThisDate.ID = LastDate.ID + 1' + @LineFeed + + ')' + @LineFeed + + 'SELECT' + @LineFeed + + ' pMon.[ServerName]' + @LineFeed + + ' ,pMon.[CheckDate]' + @LineFeed + + ' ,pMon.[object_name]' + @LineFeed + + ' ,pMon.[counter_name]' + @LineFeed + + ' ,pMon.[instance_name]' + @LineFeed + + ' ,DATEDIFF(SECOND,pMonPrior.[CheckDate],pMon.[CheckDate]) AS ElapsedSeconds' + @LineFeed + + ' ,pMon.[cntr_value]' + @LineFeed + + ' ,pMon.[cntr_type]' + @LineFeed + + ' ,(pMon.[cntr_value] - pMonPrior.[cntr_value]) AS cntr_delta' + @LineFeed + + ' ,(pMon.cntr_value - pMonPrior.cntr_value) * 1.0 / DATEDIFF(ss, pMonPrior.CheckDate, pMon.CheckDate) AS cntr_delta_per_second' + @LineFeed + + ' ,pMon.ServerName + CAST(pMon.CheckDate AS NVARCHAR(50)) AS JoinKey' + @LineFeed + + ' FROM ' + @OutputSchemaName + '.' +@OutputTableNamePerfmonStats + ' pMon' + @LineFeed + + ' INNER HASH JOIN CheckDates Dates' + @LineFeed + + ' ON Dates.CheckDate = pMon.CheckDate' + @LineFeed + + ' JOIN ' + @OutputSchemaName + '.' +@OutputTableNamePerfmonStats + ' pMonPrior' + @LineFeed + + ' ON Dates.PreviousCheckDate = pMonPrior.CheckDate' + @LineFeed + + ' AND pMon.[ServerName] = pMonPrior.[ServerName] ' + @LineFeed + + ' AND pMon.[object_name] = pMonPrior.[object_name] ' + @LineFeed + + ' AND pMon.[counter_name] = pMonPrior.[counter_name] ' + @LineFeed + + ' AND pMon.[instance_name] = pMonPrior.[instance_name]' + @LineFeed + + ' WHERE DATEDIFF(MI, pMonPrior.CheckDate, pMon.CheckDate) BETWEEN 1 AND 60;'')' + + EXEC(@StringToExecute); + END + + SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableNamePerfmonStatsActuals_View; + + /* If the view exists without the most recently added columns, drop it. See Github #2162. */ + IF OBJECT_ID(@ObjectFullName) IS NOT NULL + BEGIN + SET @StringToExecute = N'USE ' + @OutputDatabaseName + N'; IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns + WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''JoinKey'') + DROP VIEW ' + @OutputSchemaName + N'.' + @OutputTableNamePerfmonStatsActuals_View + N';'; + + EXEC(@StringToExecute); + END + + /* Create the second view */ + IF OBJECT_ID(@ObjectFullName) IS NULL + BEGIN + SET @StringToExecute = 'USE ' + + @OutputDatabaseName + + '; EXEC (''CREATE VIEW ' + + @OutputSchemaName + '.' + + @OutputTableNamePerfmonStatsActuals_View + ' AS ' + @LineFeed + + 'WITH PERF_AVERAGE_BULK AS' + @LineFeed + + '(' + @LineFeed + + ' SELECT ServerName,' + @LineFeed + + ' object_name,' + @LineFeed + + ' instance_name,' + @LineFeed + + ' counter_name,' + @LineFeed + + ' CASE WHEN CHARINDEX(''''('''', counter_name) = 0 THEN counter_name ELSE LEFT (counter_name, CHARINDEX(''''('''',counter_name)-1) END AS counter_join,' + @LineFeed + + ' CheckDate,' + @LineFeed + + ' cntr_delta' + @LineFeed + + ' FROM ' + @OutputSchemaName + '.' + @OutputTableNamePerfmonStats_View + @LineFeed + + ' WHERE cntr_type IN(1073874176)' + @LineFeed + + ' AND cntr_delta <> 0' + @LineFeed + + '),' + @LineFeed + + 'PERF_LARGE_RAW_BASE AS' + @LineFeed + + '(' + @LineFeed + + ' SELECT ServerName,' + @LineFeed + + ' object_name,' + @LineFeed + + ' instance_name,' + @LineFeed + + ' LEFT(counter_name, CHARINDEX(''''BASE'''', UPPER(counter_name))-1) AS counter_join,' + @LineFeed + + ' CheckDate,' + @LineFeed + + ' cntr_delta' + @LineFeed + + ' FROM ' + @OutputSchemaName + '.' + @OutputTableNamePerfmonStats_View + '' + @LineFeed + + ' WHERE cntr_type IN(1073939712)' + @LineFeed + + ' AND cntr_delta <> 0' + @LineFeed + + '),' + @LineFeed + + 'PERF_AVERAGE_FRACTION AS' + @LineFeed + + '(' + @LineFeed + + ' SELECT ServerName,' + @LineFeed + + ' object_name,' + @LineFeed + + ' instance_name,' + @LineFeed + + ' counter_name,' + @LineFeed + + ' counter_name AS counter_join,' + @LineFeed + + ' CheckDate,' + @LineFeed + + ' cntr_delta' + @LineFeed + + ' FROM ' + @OutputSchemaName + '.' + @OutputTableNamePerfmonStats_View + '' + @LineFeed + + ' WHERE cntr_type IN(537003264)' + @LineFeed + + ' AND cntr_delta <> 0' + @LineFeed + + '),' + @LineFeed + + 'PERF_COUNTER_BULK_COUNT AS' + @LineFeed + + '(' + @LineFeed + + ' SELECT ServerName,' + @LineFeed + + ' object_name,' + @LineFeed + + ' instance_name,' + @LineFeed + + ' counter_name,' + @LineFeed + + ' CheckDate,' + @LineFeed + + ' cntr_delta / ElapsedSeconds AS cntr_value' + @LineFeed + + ' FROM ' + @OutputSchemaName + '.' + @OutputTableNamePerfmonStats_View + '' + @LineFeed + + ' WHERE cntr_type IN(272696576, 272696320)' + @LineFeed + + ' AND cntr_delta <> 0' + @LineFeed + + '),' + @LineFeed + + 'PERF_COUNTER_RAWCOUNT AS' + @LineFeed + + '(' + @LineFeed + + ' SELECT ServerName,' + @LineFeed + + ' object_name,' + @LineFeed + + ' instance_name,' + @LineFeed + + ' counter_name,' + @LineFeed + + ' CheckDate,' + @LineFeed + + ' cntr_value' + @LineFeed + + ' FROM ' + @OutputSchemaName + '.' + @OutputTableNamePerfmonStats_View + '' + @LineFeed + + ' WHERE cntr_type IN(65792, 65536)' + @LineFeed + + ')' + @LineFeed + + '' + @LineFeed + + 'SELECT NUM.ServerName,' + @LineFeed + + ' NUM.object_name,' + @LineFeed + + ' NUM.counter_name,' + @LineFeed + + ' NUM.instance_name,' + @LineFeed + + ' NUM.CheckDate,' + @LineFeed + + ' NUM.cntr_delta / DEN.cntr_delta AS cntr_value,' + @LineFeed + + ' NUM.ServerName + CAST(NUM.CheckDate AS NVARCHAR(50)) AS JoinKey' + @LineFeed + + ' ' + @LineFeed + + 'FROM PERF_AVERAGE_BULK AS NUM' + @LineFeed + + ' JOIN PERF_LARGE_RAW_BASE AS DEN ON NUM.counter_join = DEN.counter_join' + @LineFeed + + ' AND NUM.CheckDate = DEN.CheckDate' + @LineFeed + + ' AND NUM.ServerName = DEN.ServerName' + @LineFeed + + ' AND NUM.object_name = DEN.object_name' + @LineFeed + + ' AND NUM.instance_name = DEN.instance_name' + @LineFeed + + ' AND DEN.cntr_delta <> 0' + @LineFeed + + '' + @LineFeed + + 'UNION ALL' + @LineFeed + + '' + @LineFeed + + 'SELECT NUM.ServerName,' + @LineFeed + + ' NUM.object_name,' + @LineFeed + + ' NUM.counter_name,' + @LineFeed + + ' NUM.instance_name,' + @LineFeed + + ' NUM.CheckDate,' + @LineFeed + + ' CAST((CAST(NUM.cntr_delta as DECIMAL(19)) / DEN.cntr_delta) as decimal(23,3)) AS cntr_value,' + @LineFeed + + ' NUM.ServerName + CAST(NUM.CheckDate AS NVARCHAR(50)) AS JoinKey' + @LineFeed + + 'FROM PERF_AVERAGE_FRACTION AS NUM' + @LineFeed + + ' JOIN PERF_LARGE_RAW_BASE AS DEN ON NUM.counter_join = DEN.counter_join' + @LineFeed + + ' AND NUM.CheckDate = DEN.CheckDate' + @LineFeed + + ' AND NUM.ServerName = DEN.ServerName' + @LineFeed + + ' AND NUM.object_name = DEN.object_name' + @LineFeed + + ' AND NUM.instance_name = DEN.instance_name' + @LineFeed + + ' AND DEN.cntr_delta <> 0' + @LineFeed + + 'UNION ALL' + @LineFeed + + '' + @LineFeed + + 'SELECT ServerName,' + @LineFeed + + ' object_name,' + @LineFeed + + ' counter_name,' + @LineFeed + + ' instance_name,' + @LineFeed + + ' CheckDate,' + @LineFeed + + ' cntr_value,' + @LineFeed + + ' ServerName + CAST(CheckDate AS NVARCHAR(50)) AS JoinKey' + @LineFeed + + 'FROM PERF_COUNTER_BULK_COUNT' + @LineFeed + + '' + @LineFeed + + 'UNION ALL' + @LineFeed + + '' + @LineFeed + + 'SELECT ServerName,' + @LineFeed + + ' object_name,' + @LineFeed + + ' counter_name,' + @LineFeed + + ' instance_name,' + @LineFeed + + ' CheckDate,' + @LineFeed + + ' cntr_value,' + @LineFeed + + ' ServerName + CAST(CheckDate AS NVARCHAR(50)) AS JoinKey' + @LineFeed + + 'FROM PERF_COUNTER_RAWCOUNT;'')'; + + EXEC(@StringToExecute); + END; + + + SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' + + @OutputDatabaseName + + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' + + @OutputSchemaName + ''') INSERT ' + + @OutputDatabaseName + '.' + + @OutputSchemaName + '.' + + @OutputTableNamePerfmonStats + + ' (ServerName, CheckDate, object_name, counter_name, instance_name, cntr_value, cntr_type, value_delta, value_per_second) SELECT ' + + ' @SrvName, @CheckDate, object_name, counter_name, instance_name, cntr_value, cntr_type, value_delta, value_per_second FROM #PerfmonStats WHERE Pass = 2'; + + EXEC sp_executesql @StringToExecute, + N'@SrvName NVARCHAR(128), @CheckDate datetimeoffset', + @LocalServerName, @StartSampleTime; + + /* Delete history older than @OutputTableRetentionDays */ + SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' + + @OutputDatabaseName + + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' + + @OutputSchemaName + ''') DELETE ' + + @OutputDatabaseName + '.' + + @OutputSchemaName + '.' + + @OutputTableNamePerfmonStats + + ' WHERE ServerName = @SrvName AND CheckDate < @CheckDate ;'; + + EXEC sp_executesql @StringToExecute, + N'@SrvName NVARCHAR(128), @CheckDate date', + @LocalServerName, @OutputTableCleanupDate; + + + + END; + ELSE IF (SUBSTRING(@OutputTableNamePerfmonStats, 2, 2) = '##') + BEGIN + SET @StringToExecute = N' IF (OBJECT_ID(''tempdb..' + + @OutputTableNamePerfmonStats + + ''') IS NULL) CREATE TABLE ' + + @OutputTableNamePerfmonStats + + ' (ID INT IDENTITY(1,1) NOT NULL, + ServerName NVARCHAR(128), + CheckDate DATETIMEOFFSET, + [object_name] NVARCHAR(128) NOT NULL, + [counter_name] NVARCHAR(128) NOT NULL, + [instance_name] NVARCHAR(128) NULL, + [cntr_value] BIGINT NULL, + [cntr_type] INT NOT NULL, + [value_delta] BIGINT NULL, + [value_per_second] DECIMAL(18,2) NULL, + PRIMARY KEY CLUSTERED (ID ASC));' + + ' INSERT ' + + @OutputTableNamePerfmonStats + + ' (ServerName, CheckDate, object_name, counter_name, instance_name, cntr_value, cntr_type, value_delta, value_per_second) SELECT ' + + CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128)) + + ' @SrvName, @CheckDate, object_name, counter_name, instance_name, cntr_value, cntr_type, value_delta, value_per_second FROM #PerfmonStats WHERE Pass = 2'; + + EXEC sp_executesql @StringToExecute, + N'@SrvName NVARCHAR(128), @CheckDate datetimeoffset', + @LocalServerName, @StartSampleTime; + END; + ELSE IF (SUBSTRING(@OutputTableNamePerfmonStats, 2, 1) = '#') + BEGIN + RAISERROR('Due to the nature of Dymamic SQL, only global (i.e. double pound (##)) temp tables are supported for @OutputTableName', 16, 0); + END; + + + /* @OutputTableNameWaitStats lets us export the results to a permanent table */ + IF @OutputDatabaseName IS NOT NULL + AND @OutputSchemaName IS NOT NULL + AND @OutputTableNameWaitStats IS NOT NULL + AND @OutputTableNameWaitStats NOT LIKE '#%' + AND EXISTS ( SELECT * + FROM sys.databases + WHERE QUOTENAME([name]) = @OutputDatabaseName) + BEGIN + /* Create the table */ + SET @StringToExecute = 'USE ' + + @OutputDatabaseName + + '; IF EXISTS(SELECT * FROM ' + + @OutputDatabaseName + + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' + + @OutputSchemaName + + ''') AND NOT EXISTS (SELECT * FROM ' + + @OutputDatabaseName + + '.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''' + + @OutputSchemaName + ''' AND QUOTENAME(TABLE_NAME) = ''' + + @OutputTableNameWaitStats + ''') ' + @LineFeed + + 'BEGIN' + @LineFeed + + 'CREATE TABLE ' + + @OutputSchemaName + '.' + + @OutputTableNameWaitStats + + ' (ID INT IDENTITY(1,1) NOT NULL, + ServerName NVARCHAR(128), + CheckDate DATETIMEOFFSET, + wait_type NVARCHAR(60), + wait_time_ms BIGINT, + signal_wait_time_ms BIGINT, + waiting_tasks_count BIGINT , + PRIMARY KEY CLUSTERED (ID));' + @LineFeed + + 'CREATE NONCLUSTERED INDEX IX_ServerName_wait_type_CheckDate_Includes ON ' + @OutputSchemaName + '.' + @OutputTableNameWaitStats + @LineFeed + + '(ServerName, wait_type, CheckDate) INCLUDE (wait_time_ms, signal_wait_time_ms, waiting_tasks_count);' + @LineFeed + + 'END'; + + EXEC(@StringToExecute); + + /* Create the wait stats category table */ + SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableNameWaitStats_Categories; + IF OBJECT_ID(@ObjectFullName) IS NULL + BEGIN + SET @StringToExecute = 'USE ' + + @OutputDatabaseName + + '; EXEC (''CREATE TABLE ' + + @OutputSchemaName + '.' + + @OutputTableNameWaitStats_Categories + ' (WaitType NVARCHAR(60) PRIMARY KEY CLUSTERED, WaitCategory NVARCHAR(128) NOT NULL, Ignorable BIT DEFAULT 0);'')'; + + EXEC(@StringToExecute); + END; + + /* Make sure the wait stats category table has the current number of rows */ + SET @StringToExecute = 'USE ' + + @OutputDatabaseName + + '; EXEC (''IF (SELECT COALESCE(SUM(1),0) FROM ' + @OutputSchemaName + '.' + @OutputTableNameWaitStats_Categories + ') <> (SELECT COALESCE(SUM(1),0) FROM ##WaitCategories)' + @LineFeed + + 'BEGIN ' + @LineFeed + + 'TRUNCATE TABLE ' + @OutputSchemaName + '.' + @OutputTableNameWaitStats_Categories + @LineFeed + + 'INSERT INTO ' + @OutputSchemaName + '.' + @OutputTableNameWaitStats_Categories + ' (WaitType, WaitCategory, Ignorable) SELECT WaitType, WaitCategory, Ignorable FROM ##WaitCategories;' + @LineFeed + + 'END'')'; + + EXEC(@StringToExecute); + + + SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableNameWaitStats_View; + + /* If the view exists without the most recently added columns, drop it. See Github #2162. */ + IF OBJECT_ID(@ObjectFullName) IS NOT NULL + BEGIN + SET @StringToExecute = N'USE ' + @OutputDatabaseName + N'; IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns + WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''JoinKey'') + DROP VIEW ' + @OutputSchemaName + N'.' + @OutputTableNameWaitStats_View + N';'; + + EXEC(@StringToExecute); + END + + + /* Create the wait stats view */ + IF OBJECT_ID(@ObjectFullName) IS NULL + BEGIN + SET @StringToExecute = 'USE ' + + @OutputDatabaseName + + '; EXEC (''CREATE VIEW ' + + @OutputSchemaName + '.' + + @OutputTableNameWaitStats_View + ' AS ' + @LineFeed + + 'WITH RowDates as' + @LineFeed + + '(' + @LineFeed + + ' SELECT ' + @LineFeed + + ' ROW_NUMBER() OVER (ORDER BY [ServerName], [CheckDate]) ID,' + @LineFeed + + ' [CheckDate]' + @LineFeed + + ' FROM ' + @OutputSchemaName + '.' + @OutputTableNameWaitStats + @LineFeed + + ' GROUP BY [ServerName], [CheckDate]' + @LineFeed + + '),' + @LineFeed + + 'CheckDates as' + @LineFeed + + '(' + @LineFeed + + ' SELECT ThisDate.CheckDate,' + @LineFeed + + ' LastDate.CheckDate as PreviousCheckDate' + @LineFeed + + ' FROM RowDates ThisDate' + @LineFeed + + ' JOIN RowDates LastDate' + @LineFeed + + ' ON ThisDate.ID = LastDate.ID + 1' + @LineFeed + + ')' + @LineFeed + + 'SELECT w.ServerName, w.CheckDate, w.wait_type, COALESCE(wc.WaitCategory, ''''Other'''') AS WaitCategory, COALESCE(wc.Ignorable,0) AS Ignorable' + @LineFeed + + ', DATEDIFF(ss, wPrior.CheckDate, w.CheckDate) AS ElapsedSeconds' + @LineFeed + + ', (w.wait_time_ms - wPrior.wait_time_ms) AS wait_time_ms_delta' + @LineFeed + + ', (w.wait_time_ms - wPrior.wait_time_ms) / 60000.0 AS wait_time_minutes_delta' + @LineFeed + + ', (w.wait_time_ms - wPrior.wait_time_ms) / 1000.0 / DATEDIFF(ss, wPrior.CheckDate, w.CheckDate) AS wait_time_minutes_per_minute' + @LineFeed + + ', (w.signal_wait_time_ms - wPrior.signal_wait_time_ms) AS signal_wait_time_ms_delta' + @LineFeed + + ', (w.waiting_tasks_count - wPrior.waiting_tasks_count) AS waiting_tasks_count_delta' + @LineFeed + + ', w.ServerName + CAST(w.CheckDate AS NVARCHAR(50)) AS JoinKey' + @LineFeed + + 'FROM ' + @OutputSchemaName + '.' + @OutputTableNameWaitStats + ' w' + @LineFeed + + 'INNER HASH JOIN CheckDates Dates' + @LineFeed + + 'ON Dates.CheckDate = w.CheckDate' + @LineFeed + + 'INNER JOIN ' + @OutputSchemaName + '.' + @OutputTableNameWaitStats + ' wPrior ON w.ServerName = wPrior.ServerName AND w.wait_type = wPrior.wait_type AND Dates.PreviousCheckDate = wPrior.CheckDate' + @LineFeed + + 'LEFT OUTER JOIN ' + @OutputSchemaName + '.' + @OutputTableNameWaitStats_Categories + ' wc ON w.wait_type = wc.WaitType' + @LineFeed + + 'WHERE DATEDIFF(MI, wPrior.CheckDate, w.CheckDate) BETWEEN 1 AND 60' + @LineFeed + + 'AND [w].[wait_time_ms] >= [wPrior].[wait_time_ms];'')' + + EXEC(@StringToExecute); + END; + + + SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' + + @OutputDatabaseName + + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' + + @OutputSchemaName + ''') INSERT ' + + @OutputDatabaseName + '.' + + @OutputSchemaName + '.' + + @OutputTableNameWaitStats + + ' (ServerName, CheckDate, wait_type, wait_time_ms, signal_wait_time_ms, waiting_tasks_count) SELECT ' + + ' @SrvName, @CheckDate, wait_type, wait_time_ms, signal_wait_time_ms, waiting_tasks_count FROM #WaitStats WHERE Pass = 2 AND wait_time_ms > 0 AND waiting_tasks_count > 0'; + + EXEC sp_executesql @StringToExecute, + N'@SrvName NVARCHAR(128), @CheckDate datetimeoffset', + @LocalServerName, @StartSampleTime; + + /* Delete history older than @OutputTableRetentionDays */ + SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' + + @OutputDatabaseName + + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' + + @OutputSchemaName + ''') DELETE ' + + @OutputDatabaseName + '.' + + @OutputSchemaName + '.' + + @OutputTableNameWaitStats + + ' WHERE ServerName = @SrvName AND CheckDate < @CheckDate ;'; + + EXEC sp_executesql @StringToExecute, + N'@SrvName NVARCHAR(128), @CheckDate date', + @LocalServerName, @OutputTableCleanupDate; + + END; + ELSE IF (SUBSTRING(@OutputTableNameWaitStats, 2, 2) = '##') + BEGIN + SET @StringToExecute = N' IF (OBJECT_ID(''tempdb..' + + @OutputTableNameWaitStats + + ''') IS NULL) CREATE TABLE ' + + @OutputTableNameWaitStats + + ' (ID INT IDENTITY(1,1) NOT NULL, + ServerName NVARCHAR(128), + CheckDate DATETIMEOFFSET, + wait_type NVARCHAR(60), + wait_time_ms BIGINT, + signal_wait_time_ms BIGINT, + waiting_tasks_count BIGINT , + PRIMARY KEY CLUSTERED (ID ASC));' + + ' INSERT ' + + @OutputTableNameWaitStats + + ' (ServerName, CheckDate, wait_type, wait_time_ms, signal_wait_time_ms, waiting_tasks_count) SELECT ' + + ' @SrvName, @CheckDate, wait_type, wait_time_ms, signal_wait_time_ms, waiting_tasks_count FROM #WaitStats WHERE Pass = 2 AND wait_time_ms > 0 AND waiting_tasks_count > 0'; + + EXEC sp_executesql @StringToExecute, + N'@SrvName NVARCHAR(128), @CheckDate datetimeoffset', + @LocalServerName, @StartSampleTime; + END; + ELSE IF (SUBSTRING(@OutputTableNameWaitStats, 2, 1) = '#') + BEGIN + RAISERROR('Due to the nature of Dymamic SQL, only global (i.e. double pound (##)) temp tables are supported for @OutputTableName', 16, 0); + END; + + + + + DECLARE @separator AS VARCHAR(1); + IF @OutputType = 'RSV' + SET @separator = CHAR(31); + ELSE + SET @separator = ','; + + IF @OutputType = 'COUNT' AND @SinceStartup = 0 + BEGIN + SELECT COUNT(*) AS Warnings + FROM #BlitzFirstResults; + END; + ELSE + IF @OutputType = 'Opserver1' AND @SinceStartup = 0 AND @OutputResultSets LIKE N'%Findings%' + BEGIN + + SELECT r.[Priority] , + r.[FindingsGroup] , + r.[Finding] , + r.[URL] , + r.[Details], + r.[HowToStopIt] , + r.[CheckID] , + r.[StartTime], + r.[LoginName], + r.[NTUserName], + r.[OriginalLoginName], + r.[ProgramName], + r.[HostName], + r.[DatabaseID], + r.[DatabaseName], + r.[OpenTransactionCount], + r.[QueryPlan], + r.[QueryText], + qsNow.plan_handle AS PlanHandle, + qsNow.sql_handle AS SqlHandle, + qsNow.statement_start_offset AS StatementStartOffset, + qsNow.statement_end_offset AS StatementEndOffset, + [Executions] = qsNow.execution_count - (COALESCE(qsFirst.execution_count, 0)), + [ExecutionsPercent] = CAST(100.0 * (qsNow.execution_count - (COALESCE(qsFirst.execution_count, 0))) / (qsTotal.execution_count - qsTotalFirst.execution_count) AS DECIMAL(6,2)), + [Duration] = qsNow.total_elapsed_time - (COALESCE(qsFirst.total_elapsed_time, 0)), + [DurationPercent] = CAST(100.0 * (qsNow.total_elapsed_time - (COALESCE(qsFirst.total_elapsed_time, 0))) / (qsTotal.total_elapsed_time - qsTotalFirst.total_elapsed_time) AS DECIMAL(6,2)), + [CPU] = qsNow.total_worker_time - (COALESCE(qsFirst.total_worker_time, 0)), + [CPUPercent] = CAST(100.0 * (qsNow.total_worker_time - (COALESCE(qsFirst.total_worker_time, 0))) / (qsTotal.total_worker_time - qsTotalFirst.total_worker_time) AS DECIMAL(6,2)), + [Reads] = qsNow.total_logical_reads - (COALESCE(qsFirst.total_logical_reads, 0)), + [ReadsPercent] = CAST(100.0 * (qsNow.total_logical_reads - (COALESCE(qsFirst.total_logical_reads, 0))) / (qsTotal.total_logical_reads - qsTotalFirst.total_logical_reads) AS DECIMAL(6,2)), + [PlanCreationTime] = CONVERT(NVARCHAR(100), qsNow.creation_time ,121), + [TotalExecutions] = qsNow.execution_count, + [TotalExecutionsPercent] = CAST(100.0 * qsNow.execution_count / qsTotal.execution_count AS DECIMAL(6,2)), + [TotalDuration] = qsNow.total_elapsed_time, + [TotalDurationPercent] = CAST(100.0 * qsNow.total_elapsed_time / qsTotal.total_elapsed_time AS DECIMAL(6,2)), + [TotalCPU] = qsNow.total_worker_time, + [TotalCPUPercent] = CAST(100.0 * qsNow.total_worker_time / qsTotal.total_worker_time AS DECIMAL(6,2)), + [TotalReads] = qsNow.total_logical_reads, + [TotalReadsPercent] = CAST(100.0 * qsNow.total_logical_reads / qsTotal.total_logical_reads AS DECIMAL(6,2)), + r.[DetailsInt] + FROM #BlitzFirstResults r + LEFT OUTER JOIN #QueryStats qsTotal ON qsTotal.Pass = 0 + LEFT OUTER JOIN #QueryStats qsTotalFirst ON qsTotalFirst.Pass = -1 + LEFT OUTER JOIN #QueryStats qsNow ON r.QueryStatsNowID = qsNow.ID + LEFT OUTER JOIN #QueryStats qsFirst ON r.QueryStatsFirstID = qsFirst.ID + ORDER BY r.Priority , + r.FindingsGroup , + CASE + WHEN r.CheckID = 6 THEN DetailsInt + ELSE 0 + END DESC, + r.Finding, + r.ID; + END; + ELSE IF @OutputType IN ( 'CSV', 'RSV' ) AND @SinceStartup = 0 AND @OutputResultSets LIKE N'%Findings%' + BEGIN + + SELECT Result = CAST([Priority] AS NVARCHAR(100)) + + @separator + CAST(CheckID AS NVARCHAR(100)) + + @separator + COALESCE([FindingsGroup], + '(N/A)') + @separator + + COALESCE([Finding], '(N/A)') + @separator + + COALESCE(DatabaseName, '(N/A)') + @separator + + COALESCE([URL], '(N/A)') + @separator + + COALESCE([Details], '(N/A)') + FROM #BlitzFirstResults + ORDER BY Priority , + FindingsGroup , + CASE + WHEN CheckID = 6 THEN DetailsInt + ELSE 0 + END DESC, + Finding, + Details; + END; + ELSE IF @OutputType = 'Top10' AND @OutputResultSets LIKE N'%WaitStats%' + BEGIN + /* Measure waits in hours */ + ;WITH max_batch AS ( + SELECT MAX(SampleTime) AS SampleTime + FROM #WaitStats + ) + SELECT TOP 10 + CAST(DATEDIFF(mi,wd1.SampleTime, wd2.SampleTime) / 60.0 AS DECIMAL(18,1)) AS [Hours Sample], + CAST(c.[Total Thread Time (Seconds)] / 60. / 60. AS DECIMAL(18,1)) AS [Thread Time (Hours)], + wd1.wait_type, + COALESCE(wcat.WaitCategory, 'Other') AS wait_category, + CAST(c.[Wait Time (Seconds)] / 60. / 60. AS DECIMAL(18,1)) AS [Wait Time (Hours)], + CAST((wd2.wait_time_ms - wd1.wait_time_ms) / 1000.0 / cores.cpu_count / DATEDIFF(ss, wd1.SampleTime, wd2.SampleTime) AS DECIMAL(18,1)) AS [Per Core Per Hour], + (wd2.waiting_tasks_count - wd1.waiting_tasks_count) AS [Number of Waits], + CASE WHEN (wd2.waiting_tasks_count - wd1.waiting_tasks_count) > 0 + THEN + CAST((wd2.wait_time_ms-wd1.wait_time_ms)/ + (1.0*(wd2.waiting_tasks_count - wd1.waiting_tasks_count)) AS NUMERIC(12,1)) + ELSE 0 END AS [Avg ms Per Wait] + FROM max_batch b + JOIN #WaitStats wd2 ON + wd2.SampleTime =b.SampleTime + JOIN #WaitStats wd1 ON + wd1.wait_type=wd2.wait_type AND + wd2.SampleTime > wd1.SampleTime + CROSS APPLY (SELECT SUM(1) AS cpu_count FROM sys.dm_os_schedulers WHERE status = 'VISIBLE ONLINE' AND is_online = 1) AS cores + CROSS APPLY (SELECT + CAST((wd2.wait_time_ms-wd1.wait_time_ms)/1000. AS DECIMAL(18,1)) AS [Wait Time (Seconds)], + CAST((wd2.signal_wait_time_ms - wd1.signal_wait_time_ms)/1000. AS DECIMAL(18,1)) AS [Signal Wait Time (Seconds)], + CAST((wd2.thread_time_ms)/1000. AS DECIMAL(18,1)) AS [Total Thread Time (Seconds)] + ) AS c + LEFT OUTER JOIN ##WaitCategories wcat ON wd1.wait_type = wcat.WaitType + WHERE (wd2.waiting_tasks_count - wd1.waiting_tasks_count) > 0 + AND wd2.wait_time_ms-wd1.wait_time_ms > 0 + ORDER BY [Wait Time (Seconds)] DESC; + END; + ELSE IF @ExpertMode = 0 AND @OutputType <> 'NONE' AND @OutputXMLasNVARCHAR = 0 AND @SinceStartup = 0 AND @OutputResultSets LIKE N'%Findings%' + BEGIN + SELECT [Priority] , + [FindingsGroup] , + [Finding] , + [URL] , + CAST(@StockDetailsHeader + [Details] + @StockDetailsFooter AS XML) AS Details, + CAST(@StockWarningHeader + HowToStopIt + @StockWarningFooter AS XML) AS HowToStopIt, + [QueryText], + [QueryPlan] + FROM #BlitzFirstResults + WHERE (@Seconds > 0 OR (Priority IN (0, 250, 251, 255))) /* For @Seconds = 0, filter out broken checks for now */ + ORDER BY Priority , + FindingsGroup , + CASE + WHEN CheckID = 6 THEN DetailsInt + ELSE 0 + END DESC, + Finding, + ID, + CAST(Details AS NVARCHAR(4000)); + END; + ELSE IF @OutputType <> 'NONE' AND @OutputXMLasNVARCHAR = 1 AND @SinceStartup = 0 AND @OutputResultSets LIKE N'%Findings%' + BEGIN + SELECT [Priority] , + [FindingsGroup] , + [Finding] , + [URL] , + CAST(LEFT(@StockDetailsHeader + [Details] + @StockDetailsFooter,32000) AS TEXT) AS Details, + CAST(LEFT([HowToStopIt],32000) AS TEXT) AS HowToStopIt, + CAST([QueryText] AS NVARCHAR(MAX)) AS QueryText, + CAST([QueryPlan] AS NVARCHAR(MAX)) AS QueryPlan + FROM #BlitzFirstResults + WHERE (@Seconds > 0 OR (Priority IN (0, 250, 251, 255))) /* For @Seconds = 0, filter out broken checks for now */ + ORDER BY Priority , + FindingsGroup , + CASE + WHEN CheckID = 6 THEN DetailsInt + ELSE 0 + END DESC, + Finding, + ID, + CAST(Details AS NVARCHAR(4000)); + END; + ELSE IF @ExpertMode >= 1 AND @OutputType <> 'NONE' AND @OutputResultSets LIKE N'%Findings%' + BEGIN + IF @SinceStartup = 0 + SELECT r.[Priority] , + r.[FindingsGroup] , + r.[Finding] , + r.[URL] , + CAST(@StockDetailsHeader + r.[Details] + @StockDetailsFooter AS XML) AS Details, + CAST(@StockWarningHeader + r.HowToStopIt + @StockWarningFooter AS XML) AS HowToStopIt, + r.[CheckID] , + r.[StartTime], + r.[LoginName], + r.[NTUserName], + r.[OriginalLoginName], + r.[ProgramName], + r.[HostName], + r.[DatabaseID], + r.[DatabaseName], + r.[OpenTransactionCount], + r.[QueryPlan], + r.[QueryText], + qsNow.plan_handle AS PlanHandle, + qsNow.sql_handle AS SqlHandle, + qsNow.statement_start_offset AS StatementStartOffset, + qsNow.statement_end_offset AS StatementEndOffset, + [Executions] = qsNow.execution_count - (COALESCE(qsFirst.execution_count, 0)), + [ExecutionsPercent] = CAST(100.0 * (qsNow.execution_count - (COALESCE(qsFirst.execution_count, 0))) / (qsTotal.execution_count - qsTotalFirst.execution_count) AS DECIMAL(6,2)), + [Duration] = qsNow.total_elapsed_time - (COALESCE(qsFirst.total_elapsed_time, 0)), + [DurationPercent] = CAST(100.0 * (qsNow.total_elapsed_time - (COALESCE(qsFirst.total_elapsed_time, 0))) / (qsTotal.total_elapsed_time - qsTotalFirst.total_elapsed_time) AS DECIMAL(6,2)), + [CPU] = qsNow.total_worker_time - (COALESCE(qsFirst.total_worker_time, 0)), + [CPUPercent] = CAST(100.0 * (qsNow.total_worker_time - (COALESCE(qsFirst.total_worker_time, 0))) / (qsTotal.total_worker_time - qsTotalFirst.total_worker_time) AS DECIMAL(6,2)), + [Reads] = qsNow.total_logical_reads - (COALESCE(qsFirst.total_logical_reads, 0)), + [ReadsPercent] = CAST(100.0 * (qsNow.total_logical_reads - (COALESCE(qsFirst.total_logical_reads, 0))) / (qsTotal.total_logical_reads - qsTotalFirst.total_logical_reads) AS DECIMAL(6,2)), + [PlanCreationTime] = CONVERT(NVARCHAR(100), qsNow.creation_time ,121), + [TotalExecutions] = qsNow.execution_count, + [TotalExecutionsPercent] = CAST(100.0 * qsNow.execution_count / qsTotal.execution_count AS DECIMAL(6,2)), + [TotalDuration] = qsNow.total_elapsed_time, + [TotalDurationPercent] = CAST(100.0 * qsNow.total_elapsed_time / qsTotal.total_elapsed_time AS DECIMAL(6,2)), + [TotalCPU] = qsNow.total_worker_time, + [TotalCPUPercent] = CAST(100.0 * qsNow.total_worker_time / qsTotal.total_worker_time AS DECIMAL(6,2)), + [TotalReads] = qsNow.total_logical_reads, + [TotalReadsPercent] = CAST(100.0 * qsNow.total_logical_reads / qsTotal.total_logical_reads AS DECIMAL(6,2)), + r.[DetailsInt] + FROM #BlitzFirstResults r + LEFT OUTER JOIN #QueryStats qsTotal ON qsTotal.Pass = 0 + LEFT OUTER JOIN #QueryStats qsTotalFirst ON qsTotalFirst.Pass = -1 + LEFT OUTER JOIN #QueryStats qsNow ON r.QueryStatsNowID = qsNow.ID + LEFT OUTER JOIN #QueryStats qsFirst ON r.QueryStatsFirstID = qsFirst.ID + WHERE (@Seconds > 0 OR (Priority IN (0, 250, 251, 255))) /* For @Seconds = 0, filter out broken checks for now */ + ORDER BY r.Priority , + r.FindingsGroup , + CASE + WHEN r.CheckID = 6 THEN DetailsInt + ELSE 0 + END DESC, + r.Finding, + r.ID, + CAST(r.Details AS NVARCHAR(4000)); + + ------------------------- + --What happened: #WaitStats + ------------------------- + IF @Seconds = 0 AND @OutputResultSets LIKE N'%WaitStats%' + BEGIN + /* Measure waits in hours */ + ;WITH max_batch AS ( + SELECT MAX(SampleTime) AS SampleTime + FROM #WaitStats + ) + SELECT + 'WAIT STATS' AS Pattern, + b.SampleTime AS [Sample Ended], + CAST(DATEDIFF(mi,wd1.SampleTime, wd2.SampleTime) / 60. AS DECIMAL(18,1)) AS [Hours Sample], + CAST(c.[Total Thread Time (Seconds)] / 60. / 60. AS DECIMAL(18,1)) AS [Thread Time (Hours)], + wd1.wait_type, + COALESCE(wcat.WaitCategory, 'Other') AS wait_category, + CAST(c.[Wait Time (Seconds)] / 60. / 60. AS DECIMAL(18,1)) AS [Wait Time (Hours)], + CAST((wd2.wait_time_ms - wd1.wait_time_ms) / 1000.0 / cores.cpu_count / DATEDIFF(ss, wd1.SampleTime, wd2.SampleTime) AS DECIMAL(18,1)) AS [Per Core Per Hour], + CAST(c.[Signal Wait Time (Seconds)] / 60.0 / 60 AS DECIMAL(18,1)) AS [Signal Wait Time (Hours)], + CASE WHEN c.[Wait Time (Seconds)] > 0 + THEN CAST(100.*(c.[Signal Wait Time (Seconds)]/c.[Wait Time (Seconds)]) AS NUMERIC(4,1)) + ELSE 0 END AS [Percent Signal Waits], + (wd2.waiting_tasks_count - wd1.waiting_tasks_count) AS [Number of Waits], + CASE WHEN (wd2.waiting_tasks_count - wd1.waiting_tasks_count) > 0 + THEN + CAST((wd2.wait_time_ms-wd1.wait_time_ms)/ + (1.0*(wd2.waiting_tasks_count - wd1.waiting_tasks_count)) AS NUMERIC(12,1)) + ELSE 0 END AS [Avg ms Per Wait], + N'https://www.sqlskills.com/help/waits/' + LOWER(wd1.wait_type) + '/' AS URL + FROM max_batch b + JOIN #WaitStats wd2 ON + wd2.SampleTime =b.SampleTime + JOIN #WaitStats wd1 ON + wd1.wait_type=wd2.wait_type AND + wd2.SampleTime > wd1.SampleTime + CROSS APPLY (SELECT SUM(1) AS cpu_count FROM sys.dm_os_schedulers WHERE status = 'VISIBLE ONLINE' AND is_online = 1) AS cores + CROSS APPLY (SELECT + CAST((wd2.wait_time_ms-wd1.wait_time_ms)/1000. AS DECIMAL(18,1)) AS [Wait Time (Seconds)], + CAST((wd2.signal_wait_time_ms - wd1.signal_wait_time_ms)/1000. AS DECIMAL(18,1)) AS [Signal Wait Time (Seconds)], + CAST((wd2.thread_time_ms)/1000. AS DECIMAL(18,1)) AS [Total Thread Time (Seconds)] + ) AS c + LEFT OUTER JOIN ##WaitCategories wcat ON wd1.wait_type = wcat.WaitType + WHERE (wd2.waiting_tasks_count - wd1.waiting_tasks_count) > 0 + AND wd2.wait_time_ms-wd1.wait_time_ms > 0 + ORDER BY [Wait Time (Seconds)] DESC; + END; + ELSE IF @OutputResultSets LIKE N'%WaitStats%' + BEGIN + /* Measure waits in seconds */ + ;WITH max_batch AS ( + SELECT MAX(SampleTime) AS SampleTime + FROM #WaitStats + ) + SELECT + 'WAIT STATS' AS Pattern, + b.SampleTime AS [Sample Ended], + DATEDIFF(ss,wd1.SampleTime, wd2.SampleTime) AS [Seconds Sample], + c.[Total Thread Time (Seconds)], + wd1.wait_type, + COALESCE(wcat.WaitCategory, 'Other') AS wait_category, + c.[Wait Time (Seconds)], + CAST((CAST(wd2.wait_time_ms - wd1.wait_time_ms AS MONEY)) / 1000.0 / cores.cpu_count / DATEDIFF(ss, wd1.SampleTime, wd2.SampleTime) AS DECIMAL(18,1)) AS [Per Core Per Second], + c.[Signal Wait Time (Seconds)], + CASE WHEN c.[Wait Time (Seconds)] > 0 + THEN CAST(100.*(c.[Signal Wait Time (Seconds)]/c.[Wait Time (Seconds)]) AS NUMERIC(4,1)) + ELSE 0 END AS [Percent Signal Waits], + (wd2.waiting_tasks_count - wd1.waiting_tasks_count) AS [Number of Waits], + CASE WHEN (wd2.waiting_tasks_count - wd1.waiting_tasks_count) > 0 + THEN + CAST((wd2.wait_time_ms-wd1.wait_time_ms)/ + (1.0*(wd2.waiting_tasks_count - wd1.waiting_tasks_count)) AS NUMERIC(12,1)) + ELSE 0 END AS [Avg ms Per Wait], + N'https://www.sqlskills.com/help/waits/' + LOWER(wd1.wait_type) + '/' AS URL + FROM max_batch b + JOIN #WaitStats wd2 ON + wd2.SampleTime =b.SampleTime + JOIN #WaitStats wd1 ON + wd1.wait_type=wd2.wait_type AND + wd2.SampleTime > wd1.SampleTime + CROSS APPLY (SELECT SUM(1) AS cpu_count FROM sys.dm_os_schedulers WHERE status = 'VISIBLE ONLINE' AND is_online = 1) AS cores + CROSS APPLY (SELECT + CAST((wd2.wait_time_ms-wd1.wait_time_ms)/1000. AS DECIMAL(18,1)) AS [Wait Time (Seconds)], + CAST((wd2.signal_wait_time_ms - wd1.signal_wait_time_ms)/1000. AS DECIMAL(18,1)) AS [Signal Wait Time (Seconds)], + CAST((wd2.thread_time_ms - wd1.thread_time_ms)/1000. AS DECIMAL(18,1)) AS [Total Thread Time (Seconds)] + ) AS c + LEFT OUTER JOIN ##WaitCategories wcat ON wd1.wait_type = wcat.WaitType + WHERE (wd2.waiting_tasks_count - wd1.waiting_tasks_count) > 0 + AND wd2.wait_time_ms-wd1.wait_time_ms > 0 + ORDER BY [Wait Time (Seconds)] DESC; + END; + + ------------------------- + --What happened: #FileStats + ------------------------- + IF @OutputResultSets LIKE N'%FileStats%' + WITH readstats AS ( + SELECT 'PHYSICAL READS' AS Pattern, + ROW_NUMBER() OVER (ORDER BY wd2.avg_stall_read_ms DESC) AS StallRank, + wd2.SampleTime AS [Sample Time], + DATEDIFF(ss,wd1.SampleTime, wd2.SampleTime) AS [Sample (seconds)], + wd1.DatabaseName , + wd1.FileLogicalName AS [File Name], + UPPER(SUBSTRING(wd1.PhysicalName, 1, 2)) AS [Drive] , + wd1.SizeOnDiskMB , + ( wd2.num_of_reads - wd1.num_of_reads ) AS [# Reads/Writes], + CASE WHEN wd2.num_of_reads - wd1.num_of_reads > 0 + THEN CAST(( wd2.bytes_read - wd1.bytes_read)/1024./1024. AS NUMERIC(21,1)) + ELSE 0 + END AS [MB Read/Written], + wd2.avg_stall_read_ms AS [Avg Stall (ms)], + wd1.PhysicalName AS [file physical name] + FROM #FileStats wd2 + JOIN #FileStats wd1 ON wd2.SampleTime > wd1.SampleTime + AND wd1.DatabaseID = wd2.DatabaseID + AND wd1.FileID = wd2.FileID + ), + writestats AS ( + SELECT + 'PHYSICAL WRITES' AS Pattern, + ROW_NUMBER() OVER (ORDER BY wd2.avg_stall_write_ms DESC) AS StallRank, + wd2.SampleTime AS [Sample Time], + DATEDIFF(ss,wd1.SampleTime, wd2.SampleTime) AS [Sample (seconds)], + wd1.DatabaseName , + wd1.FileLogicalName AS [File Name], + UPPER(SUBSTRING(wd1.PhysicalName, 1, 2)) AS [Drive] , + wd1.SizeOnDiskMB , + ( wd2.num_of_writes - wd1.num_of_writes ) AS [# Reads/Writes], + CASE WHEN wd2.num_of_writes - wd1.num_of_writes > 0 + THEN CAST(( wd2.bytes_written - wd1.bytes_written)/1024./1024. AS NUMERIC(21,1)) + ELSE 0 + END AS [MB Read/Written], + wd2.avg_stall_write_ms AS [Avg Stall (ms)], + wd1.PhysicalName AS [file physical name] + FROM #FileStats wd2 + JOIN #FileStats wd1 ON wd2.SampleTime > wd1.SampleTime + AND wd1.DatabaseID = wd2.DatabaseID + AND wd1.FileID = wd2.FileID + ) + SELECT + Pattern, [Sample Time], [Sample (seconds)], [File Name], [Drive], [# Reads/Writes],[MB Read/Written],[Avg Stall (ms)], [file physical name], [DatabaseName], [StallRank] + FROM readstats + WHERE StallRank <=20 AND [MB Read/Written] > 0 + UNION ALL + SELECT Pattern, [Sample Time], [Sample (seconds)], [File Name], [Drive], [# Reads/Writes],[MB Read/Written],[Avg Stall (ms)], [file physical name], [DatabaseName], [StallRank] + FROM writestats + WHERE StallRank <=20 AND [MB Read/Written] > 0 + ORDER BY Pattern, StallRank; + + + ------------------------- + --What happened: #PerfmonStats + ------------------------- + + IF @OutputResultSets LIKE N'%PerfmonStats%' + SELECT 'PERFMON' AS Pattern, pLast.[object_name], pLast.counter_name, pLast.instance_name, + pFirst.SampleTime AS FirstSampleTime, pFirst.cntr_value AS FirstSampleValue, + pLast.SampleTime AS LastSampleTime, pLast.cntr_value AS LastSampleValue, + pLast.cntr_value - pFirst.cntr_value AS ValueDelta, + ((1.0 * pLast.cntr_value - pFirst.cntr_value) / DATEDIFF(ss, pFirst.SampleTime, pLast.SampleTime)) AS ValuePerSecond + FROM #PerfmonStats pLast + INNER JOIN #PerfmonStats pFirst ON pFirst.[object_name] = pLast.[object_name] AND pFirst.counter_name = pLast.counter_name AND (pFirst.instance_name = pLast.instance_name OR (pFirst.instance_name IS NULL AND pLast.instance_name IS NULL)) + AND pLast.ID > pFirst.ID + WHERE pLast.cntr_value <> pFirst.cntr_value + ORDER BY Pattern, pLast.[object_name], pLast.counter_name, pLast.instance_name; + + + ------------------------- + --What happened: #QueryStats + ------------------------- + IF @CheckProcedureCache = 1 AND @OutputResultSets LIKE N'%BlitzCache%' + BEGIN + + SELECT qsNow.*, qsFirst.* + FROM #QueryStats qsNow + INNER JOIN #QueryStats qsFirst ON qsNow.[sql_handle] = qsFirst.[sql_handle] AND qsNow.statement_start_offset = qsFirst.statement_start_offset AND qsNow.statement_end_offset = qsFirst.statement_end_offset AND qsNow.plan_generation_num = qsFirst.plan_generation_num AND qsNow.plan_handle = qsFirst.plan_handle AND qsFirst.Pass = 1 + WHERE qsNow.Pass = 2; + END; + ELSE IF @OutputResultSets LIKE N'%BlitzCache%' + BEGIN + SELECT 'Plan Cache' AS [Pattern], 'Plan cache not analyzed' AS [Finding], 'Use @CheckProcedureCache = 1 or run sp_BlitzCache for more analysis' AS [More Info], CONVERT(XML, @StockDetailsHeader + 'firstresponderkit.org' + @StockDetailsFooter) AS [Details]; + END; + END; + + DROP TABLE #BlitzFirstResults; + + /* What's running right now? This is the first and last result set. */ + IF @SinceStartup = 0 AND @Seconds > 0 AND @ExpertMode = 1 AND @OutputType <> 'NONE' AND @OutputResultSets LIKE N'%BlitzWho_End%' + BEGIN + IF OBJECT_ID('master.dbo.sp_BlitzWho') IS NULL AND OBJECT_ID('dbo.sp_BlitzWho') IS NULL + BEGIN + PRINT N'sp_BlitzWho is not installed in the current database_files. You can get a copy from http://FirstResponderKit.org'; + END; + ELSE + BEGIN + EXEC (@BlitzWho); + END; + END; /* IF @SinceStartup = 0 AND @Seconds > 0 AND @ExpertMode = 1 AND @OutputType <> 'NONE' - What's running right now? This is the first and last result set. */ + +END; /* IF @LogMessage IS NULL */ +END; /* ELSE IF @OutputType = 'SCHEMA' */ + +SET NOCOUNT OFF; +GO + + + +/* How to run it: +EXEC dbo.sp_BlitzFirst + +With extra diagnostic info: +EXEC dbo.sp_BlitzFirst @ExpertMode = 1; + +Saving output to tables: +EXEC sp_BlitzFirst + @OutputDatabaseName = 'DBAtools' +, @OutputSchemaName = 'dbo' +, @OutputTableName = 'BlitzFirst' +, @OutputTableNameFileStats = 'BlitzFirst_FileStats' +, @OutputTableNamePerfmonStats = 'BlitzFirst_PerfmonStats' +, @OutputTableNameWaitStats = 'BlitzFirst_WaitStats' +, @OutputTableNameBlitzCache = 'BlitzCache' +, @OutputTableNameBlitzWho = 'BlitzWho' +, @OutputType = 'none' +*/ +SET ANSI_NULLS ON; +SET ANSI_PADDING ON; +SET ANSI_WARNINGS ON; +SET ARITHABORT ON; +SET CONCAT_NULL_YIELDS_NULL ON; +SET QUOTED_IDENTIFIER ON; +SET STATISTICS IO OFF; +SET STATISTICS TIME OFF; +GO + +IF OBJECT_ID('dbo.sp_BlitzIndex') IS NULL + EXEC ('CREATE PROCEDURE dbo.sp_BlitzIndex AS RETURN 0;'); +GO + +ALTER PROCEDURE dbo.sp_BlitzIndex + @DatabaseName NVARCHAR(128) = NULL, /*Defaults to current DB if not specified*/ + @SchemaName NVARCHAR(128) = NULL, /*Requires table_name as well.*/ + @TableName NVARCHAR(128) = NULL, /*Requires schema_name as well.*/ + @Mode TINYINT=0, /*0=Diagnose, 1=Summarize, 2=Index Usage Detail, 3=Missing Index Detail, 4=Diagnose Details*/ + /*Note:@Mode doesn't matter if you're specifying schema_name and @TableName.*/ + @Filter TINYINT = 0, /* 0=no filter (default). 1=No low-usage warnings for objects with 0 reads. 2=Only warn for objects >= 500MB */ + /*Note:@Filter doesn't do anything unless @Mode=0*/ + @SkipPartitions BIT = 0, + @SkipStatistics BIT = 1, + @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, + @IgnoreDatabases NVARCHAR(MAX) = NULL, /* Comma-delimited list of databases you want to skip */ + @ThresholdMB INT = 250 /* Number of megabytes that an object must be before we include it in basic results */, + @OutputType VARCHAR(20) = 'TABLE' , + @OutputServerName NVARCHAR(256) = NULL , + @OutputDatabaseName NVARCHAR(256) = NULL , + @OutputSchemaName NVARCHAR(256) = NULL , + @OutputTableName NVARCHAR(256) = NULL , + @IncludeInactiveIndexes BIT = 0 /* Will skip indexes with no reads or writes */, + @ShowAllMissingIndexRequests BIT = 0 /*Will make all missing index requests show up*/, + @ShowPartitionRanges BIT = 0 /* Will add partition range values column to columnstore visualization */, + @SortOrder NVARCHAR(50) = NULL, /* Only affects @Mode = 2. */ + @SortDirection NVARCHAR(4) = 'DESC', /* Only affects @Mode = 2. */ + @Help TINYINT = 0, + @Debug BIT = 0, + @Version VARCHAR(30) = NULL OUTPUT, + @VersionDate DATETIME = NULL OUTPUT, + @VersionCheckMode BIT = 0 +WITH RECOMPILE +AS +SET NOCOUNT ON; +SET STATISTICS XML OFF; +SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + +SELECT @Version = '8.19', @VersionDate = '20240222'; +SET @OutputType = UPPER(@OutputType); + +IF(@VersionCheckMode = 1) +BEGIN + RETURN; +END; + +IF @Help = 1 +BEGIN +PRINT ' +/* +sp_BlitzIndex from http://FirstResponderKit.org + +This script analyzes the design and performance of your indexes. + +To learn more, visit http://FirstResponderKit.org where you can download new +versions for free, watch training videos on how it works, get more info on +the findings, contribute your own code, and more. + +Known limitations of this version: + - Only Microsoft-supported versions of SQL Server. Sorry, 2005 and 2000. + - Index create statements are just to give you a rough idea of the syntax. It includes filters and fillfactor. + -- Example 1: index creates use ONLINE=? instead of ONLINE=ON / ONLINE=OFF. This is because it is important + for the user to understand if it is going to be offline and not just run a script. + -- Example 2: they do not include all the options the index may have been created with (padding, compression + filegroup/partition scheme etc.) + -- (The compression and filegroup index create syntax is not trivial because it is set at the partition + level and is not trivial to code.) + - Does not advise you about data modeling for clustered indexes and primary keys (primarily looks for signs of problems.) + +Unknown limitations of this version: + - We knew them once, but we forgot. + + +MIT License + +Copyright (c) Brent Ozar Unlimited + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +'; +RETURN; +END; /* @Help = 1 */ + +DECLARE @ScriptVersionName NVARCHAR(50); +DECLARE @DaysUptime NUMERIC(23,2); +DECLARE @DatabaseID INT; +DECLARE @ObjectID INT; +DECLARE @dsql NVARCHAR(MAX); +DECLARE @params NVARCHAR(MAX); +DECLARE @msg NVARCHAR(4000); +DECLARE @ErrorSeverity INT; +DECLARE @ErrorState INT; +DECLARE @Rowcount BIGINT; +DECLARE @SQLServerProductVersion NVARCHAR(128); +DECLARE @SQLServerEdition INT; +DECLARE @FilterMB INT; +DECLARE @collation NVARCHAR(256); +DECLARE @NumDatabases INT; +DECLARE @LineFeed NVARCHAR(5); +DECLARE @DaysUptimeInsertValue NVARCHAR(256); +DECLARE @DatabaseToIgnore NVARCHAR(MAX); +DECLARE @ColumnList NVARCHAR(MAX); +DECLARE @ColumnListWithApostrophes NVARCHAR(MAX); +DECLARE @PartitionCount INT; +DECLARE @OptimizeForSequentialKey BIT = 0; +DECLARE @StringToExecute NVARCHAR(MAX); + + +/* Let's get @SortOrder set to lower case here for comparisons later */ +SET @SortOrder = REPLACE(LOWER(@SortOrder), N' ', N'_'); +SET @SortDirection = LOWER(@SortDirection); + +SET @LineFeed = CHAR(13) + CHAR(10); +SELECT @SQLServerProductVersion = CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)); +SELECT @SQLServerEdition =CAST(SERVERPROPERTY('EngineEdition') AS INT); /* We default to online index creates where EngineEdition=3*/ +SET @FilterMB=250; +SELECT @ScriptVersionName = 'sp_BlitzIndex(TM) v' + @Version + ' - ' + DATENAME(MM, @VersionDate) + ' ' + RIGHT('0'+DATENAME(DD, @VersionDate),2) + ', ' + DATENAME(YY, @VersionDate); +SET @IgnoreDatabases = REPLACE(REPLACE(LTRIM(RTRIM(@IgnoreDatabases)), CHAR(10), ''), CHAR(13), ''); + +SELECT + @OptimizeForSequentialKey = + CASE WHEN EXISTS + ( + SELECT + 1/0 + FROM sys.all_columns AS ac + WHERE ac.object_id = OBJECT_ID('sys.indexes') + AND ac.name = N'optimize_for_sequential_key' + ) + THEN 1 + ELSE 0 + END; + +RAISERROR(N'Starting run. %s', 0,1, @ScriptVersionName) WITH NOWAIT; + + +IF(@OutputType NOT IN ('TABLE','NONE')) +BEGIN + RAISERROR('Invalid value for parameter @OutputType. Expected: (TABLE;NONE)',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; + SET @OutputType = 'NONE' +END; + +IF(@OutputType = 'NONE') +BEGIN + + IF ((@OutputServerName IS NOT NULL) AND (@OutputTableName IS NULL OR @OutputSchemaName IS NULL OR @OutputDatabaseName IS NULL)) + BEGIN + RAISERROR('Parameter @OutputServerName is specified, rest of @Output* parameters needs to also be specified',12,1); + RETURN; + END; + + IF(@OutputTableName IS NULL OR @OutputSchemaName IS NULL OR @OutputDatabaseName IS NULL) + BEGIN + RAISERROR('This procedure should be called with a value for @OutputTableName, @OutputSchemaName and @OutputDatabaseName parameters, as @OutputType is set to NONE',12,1); + RETURN; + END; + /* Output is supported for all modes, no reason to not bring pain and output + IF(@BringThePain = 1) + BEGIN + RAISERROR('Incompatible Parameters: @BringThePain set to 1 and @OutputType set to NONE',12,1); + RETURN; + END; + */ + /* Eventually limit by mode + IF(@Mode not in (0,4)) + BEGIN + RAISERROR('Incompatible Parameters: @Mode set to %d and @OutputType set to NONE',12,1,@Mode); + RETURN; + END; + */ +END; + +IF OBJECT_ID('tempdb..#IndexSanity') IS NOT NULL + DROP TABLE #IndexSanity; + +IF OBJECT_ID('tempdb..#IndexPartitionSanity') IS NOT NULL + DROP TABLE #IndexPartitionSanity; + +IF OBJECT_ID('tempdb..#IndexSanitySize') IS NOT NULL + DROP TABLE #IndexSanitySize; + +IF OBJECT_ID('tempdb..#IndexColumns') IS NOT NULL + DROP TABLE #IndexColumns; + +IF OBJECT_ID('tempdb..#MissingIndexes') IS NOT NULL + DROP TABLE #MissingIndexes; + +IF OBJECT_ID('tempdb..#ForeignKeys') IS NOT NULL + DROP TABLE #ForeignKeys; + +IF OBJECT_ID('tempdb..#UnindexedForeignKeys') IS NOT NULL + DROP TABLE #UnindexedForeignKeys; + +IF OBJECT_ID('tempdb..#BlitzIndexResults') IS NOT NULL + DROP TABLE #BlitzIndexResults; + +IF OBJECT_ID('tempdb..#IndexCreateTsql') IS NOT NULL + DROP TABLE #IndexCreateTsql; + +IF OBJECT_ID('tempdb..#DatabaseList') IS NOT NULL + DROP TABLE #DatabaseList; + +IF OBJECT_ID('tempdb..#Statistics') IS NOT NULL + DROP TABLE #Statistics; + +IF OBJECT_ID('tempdb..#PartitionCompressionInfo') IS NOT NULL + DROP TABLE #PartitionCompressionInfo; + +IF OBJECT_ID('tempdb..#ComputedColumns') IS NOT NULL + DROP TABLE #ComputedColumns; + +IF OBJECT_ID('tempdb..#TraceStatus') IS NOT NULL + DROP TABLE #TraceStatus; + +IF OBJECT_ID('tempdb..#TemporalTables') IS NOT NULL + DROP TABLE #TemporalTables; + +IF OBJECT_ID('tempdb..#CheckConstraints') IS NOT NULL + DROP TABLE #CheckConstraints; + +IF OBJECT_ID('tempdb..#FilteredIndexes') IS NOT NULL + DROP TABLE #FilteredIndexes; + +IF OBJECT_ID('tempdb..#Ignore_Databases') IS NOT NULL + DROP TABLE #Ignore_Databases + +IF OBJECT_ID('tempdb..#dm_db_partition_stats_etc') IS NOT NULL + DROP TABLE #dm_db_partition_stats_etc +IF OBJECT_ID('tempdb..#dm_db_index_operational_stats') IS NOT NULL + DROP TABLE #dm_db_index_operational_stats + + RAISERROR (N'Create temp tables.',0,1) WITH NOWAIT; + CREATE TABLE #BlitzIndexResults + ( + blitz_result_id INT IDENTITY PRIMARY KEY, + check_id INT NOT NULL, + index_sanity_id INT NULL, + Priority INT NULL, + findings_group NVARCHAR(4000) NOT NULL, + finding NVARCHAR(200) NOT NULL, + [database_name] NVARCHAR(128) NULL, + URL NVARCHAR(200) NOT NULL, + details NVARCHAR(MAX) NOT NULL, + index_definition NVARCHAR(MAX) NOT NULL, + secret_columns NVARCHAR(MAX) NULL, + index_usage_summary NVARCHAR(MAX) NULL, + index_size_summary NVARCHAR(MAX) NULL, + create_tsql NVARCHAR(MAX) NULL, + more_info NVARCHAR(MAX) NULL, + sample_query_plan XML NULL + ); + + CREATE TABLE #IndexSanity + ( + [index_sanity_id] INT IDENTITY PRIMARY KEY CLUSTERED, + [database_id] SMALLINT NOT NULL , + [object_id] INT NOT NULL , + [index_id] INT NOT NULL , + [index_type] TINYINT NOT NULL, + [database_name] NVARCHAR(128) NOT NULL , + [schema_name] NVARCHAR(128) NOT NULL , + [object_name] NVARCHAR(128) NOT NULL , + index_name NVARCHAR(128) NULL , + key_column_names NVARCHAR(MAX) NULL , + key_column_names_with_sort_order NVARCHAR(MAX) NULL , + key_column_names_with_sort_order_no_types NVARCHAR(MAX) NULL , + count_key_columns INT NULL , + include_column_names NVARCHAR(MAX) NULL , + include_column_names_no_types NVARCHAR(MAX) NULL , + count_included_columns INT NULL , + partition_key_column_name NVARCHAR(MAX) NULL, + filter_definition NVARCHAR(MAX) NOT NULL , + optimize_for_sequential_key BIT NULL, + is_indexed_view BIT NOT NULL , + is_unique BIT NOT NULL , + is_primary_key BIT NOT NULL , + is_unique_constraint BIT NOT NULL , + is_XML bit NOT NULL, + is_spatial BIT NOT NULL, + is_NC_columnstore BIT NOT NULL, + is_CX_columnstore BIT NOT NULL, + is_in_memory_oltp BIT NOT NULL , + is_disabled BIT NOT NULL , + is_hypothetical BIT NOT NULL , + is_padded BIT NOT NULL , + fill_factor SMALLINT NOT NULL , + user_seeks BIGINT NOT NULL , + user_scans BIGINT NOT NULL , + user_lookups BIGINT NOT NULL , + user_updates BIGINT NULL , + last_user_seek DATETIME NULL , + last_user_scan DATETIME NULL , + last_user_lookup DATETIME NULL , + last_user_update DATETIME NULL , + is_referenced_by_foreign_key BIT DEFAULT(0), + secret_columns NVARCHAR(MAX) NULL, + count_secret_columns INT NULL, + create_date DATETIME NOT NULL, + modify_date DATETIME NOT NULL, + filter_columns_not_in_index NVARCHAR(MAX), + [db_schema_object_name] AS [schema_name] + N'.' + [object_name] , + [db_schema_object_indexid] AS [schema_name] + N'.' + [object_name] + + CASE WHEN [index_name] IS NOT NULL THEN N'.' + index_name + ELSE N'' + END + N' (' + CAST(index_id AS NVARCHAR(20)) + N')' , + first_key_column_name AS CASE WHEN count_key_columns > 1 + THEN LEFT(key_column_names, CHARINDEX(',', key_column_names, 0) - 1) + ELSE key_column_names + END , + index_definition AS + CASE WHEN partition_key_column_name IS NOT NULL + THEN N'[PARTITIONED BY:' + partition_key_column_name + N']' + ELSE '' + END + + CASE index_id + WHEN 0 THEN N'[HEAP] ' + WHEN 1 THEN N'[CX] ' + ELSE N'' END + CASE WHEN is_indexed_view = 1 THEN N'[VIEW] ' + ELSE N'' END + CASE WHEN is_primary_key = 1 THEN N'[PK] ' + ELSE N'' END + CASE WHEN is_XML = 1 THEN N'[XML] ' + ELSE N'' END + CASE WHEN is_spatial = 1 THEN N'[SPATIAL] ' + ELSE N'' END + CASE WHEN is_NC_columnstore = 1 THEN N'[COLUMNSTORE] ' + ELSE N'' END + CASE WHEN is_in_memory_oltp = 1 THEN N'[IN-MEMORY] ' + ELSE N'' END + CASE WHEN is_disabled = 1 THEN N'[DISABLED] ' + ELSE N'' END + CASE WHEN is_hypothetical = 1 THEN N'[HYPOTHETICAL] ' + ELSE N'' END + CASE WHEN is_unique = 1 AND is_primary_key = 0 AND is_unique_constraint = 0 THEN N'[UNIQUE] ' + ELSE N'' END + CASE WHEN is_unique_constraint = 1 AND is_primary_key = 0 THEN N'[UNIQUE CONSTRAINT] ' + ELSE N'' END + CASE WHEN count_key_columns > 0 THEN + N'[' + CAST(count_key_columns AS NVARCHAR(10)) + N' KEY' + + CASE WHEN count_key_columns > 1 THEN N'S' ELSE N'' END + + N'] ' + LTRIM(key_column_names_with_sort_order) + ELSE N'' END + CASE WHEN count_included_columns > 0 THEN + N' [' + CAST(count_included_columns AS NVARCHAR(10)) + N' INCLUDE' + + + CASE WHEN count_included_columns > 1 THEN N'S' ELSE N'' END + + N'] ' + include_column_names + ELSE N'' END + CASE WHEN filter_definition <> N'' THEN N' [FILTER] ' + filter_definition + ELSE N'' END , + [total_reads] AS user_seeks + user_scans + user_lookups, + [reads_per_write] AS CAST(CASE WHEN user_updates > 0 + THEN ( user_seeks + user_scans + user_lookups ) / (1.0 * user_updates) + ELSE 0 END AS MONEY) , + [index_usage_summary] AS + CASE WHEN is_spatial = 1 THEN N'Not Tracked' + WHEN is_disabled = 1 THEN N'Disabled' + ELSE N'Reads: ' + + REPLACE(CONVERT(NVARCHAR(30),CAST((user_seeks + user_scans + user_lookups) AS MONEY), 1), N'.00', N'') + + CASE WHEN user_seeks + user_scans + user_lookups > 0 THEN + N' (' + + RTRIM( + CASE WHEN user_seeks > 0 THEN REPLACE(CONVERT(NVARCHAR(30),CAST((user_seeks) AS MONEY), 1), N'.00', N'') + N' seek ' ELSE N'' END + + CASE WHEN user_scans > 0 THEN REPLACE(CONVERT(NVARCHAR(30),CAST((user_scans) AS MONEY), 1), N'.00', N'') + N' scan ' ELSE N'' END + + CASE WHEN user_lookups > 0 THEN REPLACE(CONVERT(NVARCHAR(30),CAST((user_lookups) AS MONEY), 1), N'.00', N'') + N' lookup' ELSE N'' END + ) + + N') ' + ELSE N' ' + END + + N'Writes: ' + + REPLACE(CONVERT(NVARCHAR(30),CAST(user_updates AS MONEY), 1), N'.00', N'') + END /* First "end" is about is_spatial */, + [more_info] AS + CASE WHEN is_in_memory_oltp = 1 + THEN N'EXEC dbo.sp_BlitzInMemoryOLTP @dbName=' + QUOTENAME([database_name],N'''') + + N', @tableName=' + QUOTENAME([object_name],N'''') + N';' + ELSE N'EXEC dbo.sp_BlitzIndex @DatabaseName=' + QUOTENAME([database_name],N'''') + + N', @SchemaName=' + QUOTENAME([schema_name],N'''') + N', @TableName=' + QUOTENAME([object_name],N'''') + N';' + END + ); + RAISERROR (N'Adding UQ index on #IndexSanity (database_id, object_id, index_id)',0,1) WITH NOWAIT; + IF NOT EXISTS(SELECT 1 FROM tempdb.sys.indexes WHERE name='uq_database_id_object_id_index_id') + CREATE UNIQUE INDEX uq_database_id_object_id_index_id ON #IndexSanity (database_id, object_id, index_id); + + + CREATE TABLE #IndexPartitionSanity + ( + [index_partition_sanity_id] INT IDENTITY, + [index_sanity_id] INT NULL , + [database_id] INT NOT NULL , + [object_id] INT NOT NULL , + [schema_name] NVARCHAR(128) NOT NULL, + [index_id] INT NOT NULL , + [partition_number] INT NOT NULL , + row_count BIGINT NOT NULL , + reserved_MB NUMERIC(29,2) NOT NULL , + reserved_LOB_MB NUMERIC(29,2) NOT NULL , + reserved_row_overflow_MB NUMERIC(29,2) NOT NULL , + reserved_dictionary_MB NUMERIC(29,2) NOT NULL , + leaf_insert_count BIGINT NULL , + leaf_delete_count BIGINT NULL , + leaf_update_count BIGINT NULL , + range_scan_count BIGINT NULL , + singleton_lookup_count BIGINT NULL , + forwarded_fetch_count BIGINT NULL , + lob_fetch_in_pages BIGINT NULL , + lob_fetch_in_bytes BIGINT NULL , + row_overflow_fetch_in_pages BIGINT NULL , + row_overflow_fetch_in_bytes BIGINT NULL , + row_lock_count BIGINT NULL , + row_lock_wait_count BIGINT NULL , + row_lock_wait_in_ms BIGINT NULL , + page_lock_count BIGINT NULL , + page_lock_wait_count BIGINT NULL , + page_lock_wait_in_ms BIGINT NULL , + index_lock_promotion_attempt_count BIGINT NULL , + index_lock_promotion_count BIGINT NULL, + data_compression_desc NVARCHAR(60) NULL, + page_latch_wait_count BIGINT NULL, + page_latch_wait_in_ms BIGINT NULL, + page_io_latch_wait_count BIGINT NULL, + page_io_latch_wait_in_ms BIGINT NULL, + lock_escalation_desc nvarchar(60) NULL + ); + + CREATE TABLE #IndexSanitySize + ( + [index_sanity_size_id] INT IDENTITY NOT NULL , + [index_sanity_id] INT NULL , + [database_id] INT NOT NULL, + [schema_name] NVARCHAR(128) NOT NULL, + partition_count INT NOT NULL , + total_rows BIGINT NOT NULL , + total_reserved_MB NUMERIC(29,2) NOT NULL , + total_reserved_LOB_MB NUMERIC(29,2) NOT NULL , + total_reserved_row_overflow_MB NUMERIC(29,2) NOT NULL , + total_reserved_dictionary_MB NUMERIC(29,2) NOT NULL , + total_leaf_delete_count BIGINT NULL, + total_leaf_update_count BIGINT NULL, + total_range_scan_count BIGINT NULL, + total_singleton_lookup_count BIGINT NULL, + total_forwarded_fetch_count BIGINT NULL, + total_row_lock_count BIGINT NULL , + total_row_lock_wait_count BIGINT NULL , + total_row_lock_wait_in_ms BIGINT NULL , + avg_row_lock_wait_in_ms BIGINT NULL , + total_page_lock_count BIGINT NULL , + total_page_lock_wait_count BIGINT NULL , + total_page_lock_wait_in_ms BIGINT NULL , + avg_page_lock_wait_in_ms BIGINT NULL , + total_index_lock_promotion_attempt_count BIGINT NULL , + total_index_lock_promotion_count BIGINT NULL , + data_compression_desc NVARCHAR(4000) NULL, + page_latch_wait_count BIGINT NULL, + page_latch_wait_in_ms BIGINT NULL, + page_io_latch_wait_count BIGINT NULL, + page_io_latch_wait_in_ms BIGINT NULL, + lock_escalation_desc nvarchar(60) NULL, + index_size_summary AS ISNULL( + CASE WHEN partition_count > 1 + THEN N'[' + CAST(partition_count AS NVARCHAR(10)) + N' PARTITIONS] ' + ELSE N'' + END + REPLACE(CONVERT(NVARCHAR(30),CAST([total_rows] AS MONEY), 1), N'.00', N'') + N' rows; ' + + CASE WHEN total_reserved_MB > 1024 THEN + CAST(CAST(total_reserved_MB/1024. AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'GB' + ELSE + CAST(CAST(total_reserved_MB AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'MB' + END + + CASE WHEN total_reserved_LOB_MB > 1024 THEN + N'; ' + CAST(CAST(total_reserved_LOB_MB/1024. AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'GB ' + CASE WHEN total_reserved_dictionary_MB = 0 THEN N'LOB' ELSE N'Columnstore' END + WHEN total_reserved_LOB_MB > 0 THEN + N'; ' + CAST(CAST(total_reserved_LOB_MB AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'MB ' + CASE WHEN total_reserved_dictionary_MB = 0 THEN N'LOB' ELSE N'Columnstore' END + ELSE '' + END + + CASE WHEN total_reserved_row_overflow_MB > 1024 THEN + N'; ' + CAST(CAST(total_reserved_row_overflow_MB/1024. AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'GB Row Overflow' + WHEN total_reserved_row_overflow_MB > 0 THEN + N'; ' + CAST(CAST(total_reserved_row_overflow_MB AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'MB Row Overflow' + ELSE '' + END + + CASE WHEN total_reserved_dictionary_MB > 1024 THEN + N'; ' + CAST(CAST(total_reserved_dictionary_MB/1024. AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'GB Dictionaries' + WHEN total_reserved_dictionary_MB > 0 THEN + N'; ' + CAST(CAST(total_reserved_dictionary_MB AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'MB Dictionaries' + ELSE '' + END , + N'Error- NULL in computed column'), + index_op_stats AS ISNULL( + ( + REPLACE(CONVERT(NVARCHAR(30),CAST(total_singleton_lookup_count AS MONEY), 1),N'.00',N'') + N' singleton lookups; ' + + REPLACE(CONVERT(NVARCHAR(30),CAST(total_range_scan_count AS MONEY), 1),N'.00',N'') + N' scans/seeks; ' + + REPLACE(CONVERT(NVARCHAR(30),CAST(total_leaf_delete_count AS MONEY), 1),N'.00',N'') + N' deletes; ' + + REPLACE(CONVERT(NVARCHAR(30),CAST(total_leaf_update_count AS MONEY), 1),N'.00',N'') + N' updates; ' + + CASE WHEN ISNULL(total_forwarded_fetch_count,0) >0 THEN + REPLACE(CONVERT(NVARCHAR(30),CAST(total_forwarded_fetch_count AS MONEY), 1),N'.00',N'') + N' forward records fetched; ' + ELSE N'' END + + /* rows will only be in this dmv when data is in memory for the table */ + ), N'Table metadata not in memory'), + index_lock_wait_summary AS ISNULL( + CASE WHEN total_row_lock_wait_count = 0 AND total_page_lock_wait_count = 0 AND + total_index_lock_promotion_attempt_count = 0 THEN N'0 lock waits; ' + + CASE WHEN lock_escalation_desc = N'DISABLE' THEN N'Lock escalation DISABLE.' + ELSE N'' + END + ELSE + CASE WHEN total_row_lock_wait_count > 0 THEN + N'Row lock waits: ' + REPLACE(CONVERT(NVARCHAR(30),CAST(total_row_lock_wait_count AS MONEY), 1), N'.00', N'') + + N'; total duration: ' + + CASE WHEN total_row_lock_wait_in_ms >= 60000 THEN /*More than 1 min*/ + REPLACE(CONVERT(NVARCHAR(30),CAST((total_row_lock_wait_in_ms/60000) AS MONEY), 1), N'.00', N'') + N' minutes; ' + ELSE + REPLACE(CONVERT(NVARCHAR(30),CAST(ISNULL(total_row_lock_wait_in_ms/1000,0) AS MONEY), 1), N'.00', N'') + N' seconds; ' + END + + N'avg duration: ' + + CASE WHEN avg_row_lock_wait_in_ms >= 60000 THEN /*More than 1 min*/ + REPLACE(CONVERT(NVARCHAR(30),CAST((avg_row_lock_wait_in_ms/60000) AS MONEY), 1), N'.00', N'') + N' minutes; ' + ELSE + REPLACE(CONVERT(NVARCHAR(30),CAST(ISNULL(avg_row_lock_wait_in_ms/1000,0) AS MONEY), 1), N'.00', N'') + N' seconds; ' + END + ELSE N'' + END + + CASE WHEN total_page_lock_wait_count > 0 THEN + N'Page lock waits: ' + REPLACE(CONVERT(NVARCHAR(30),CAST(total_page_lock_wait_count AS MONEY), 1), N'.00', N'') + + N'; total duration: ' + + CASE WHEN total_page_lock_wait_in_ms >= 60000 THEN /*More than 1 min*/ + REPLACE(CONVERT(NVARCHAR(30),CAST((total_page_lock_wait_in_ms/60000) AS MONEY), 1), N'.00', N'') + N' minutes; ' + ELSE + REPLACE(CONVERT(NVARCHAR(30),CAST(ISNULL(total_page_lock_wait_in_ms/1000,0) AS MONEY), 1), N'.00', N'') + N' seconds; ' + END + + N'avg duration: ' + + CASE WHEN avg_page_lock_wait_in_ms >= 60000 THEN /*More than 1 min*/ + REPLACE(CONVERT(NVARCHAR(30),CAST((avg_page_lock_wait_in_ms/60000) AS MONEY), 1), N'.00', N'') + N' minutes; ' + ELSE + REPLACE(CONVERT(NVARCHAR(30),CAST(ISNULL(avg_page_lock_wait_in_ms/1000,0) AS MONEY), 1), N'.00', N'') + N' seconds; ' + END + ELSE N'' + END + + CASE WHEN total_index_lock_promotion_attempt_count > 0 THEN + N'Lock escalation attempts: ' + REPLACE(CONVERT(NVARCHAR(30),CAST(total_index_lock_promotion_attempt_count AS MONEY), 1), N'.00', N'') + + N'; Actual Escalations: ' + REPLACE(CONVERT(NVARCHAR(30),CAST(ISNULL(total_index_lock_promotion_count,0) AS MONEY), 1), N'.00', N'') +N'; ' + ELSE N'' + END + + CASE WHEN lock_escalation_desc = N'DISABLE' THEN + N'Lock escalation is disabled.' + ELSE N'' + END + END + ,'Error- NULL in computed column') + ); + + CREATE TABLE #IndexColumns + ( + [database_id] INT NOT NULL, + [schema_name] NVARCHAR(128), + [object_id] INT NOT NULL , + [index_id] INT NOT NULL , + [key_ordinal] INT NULL , + is_included_column BIT NULL , + is_descending_key BIT NULL , + [partition_ordinal] INT NULL , + column_name NVARCHAR(256) NOT NULL , + system_type_name NVARCHAR(256) NOT NULL, + max_length SMALLINT NOT NULL, + [precision] TINYINT NOT NULL, + [scale] TINYINT NOT NULL, + collation_name NVARCHAR(256) NULL, + is_nullable BIT NULL, + is_identity BIT NULL, + is_computed BIT NULL, + is_replicated BIT NULL, + is_sparse BIT NULL, + is_filestream BIT NULL, + seed_value DECIMAL(38,0) NULL, + increment_value DECIMAL(38,0) NULL , + last_value DECIMAL(38,0) NULL, + is_not_for_replication BIT NULL + ); + CREATE CLUSTERED INDEX CLIX_database_id_object_id_index_id ON #IndexColumns + (database_id, object_id, index_id); + + CREATE TABLE #MissingIndexes + ([database_id] INT NOT NULL, + [object_id] INT NOT NULL, + [database_name] NVARCHAR(128) NOT NULL , + [schema_name] NVARCHAR(128) NOT NULL , + [table_name] NVARCHAR(128), + [statement] NVARCHAR(512) NOT NULL, + magic_benefit_number AS (( user_seeks + user_scans ) * avg_total_user_cost * avg_user_impact), + avg_total_user_cost NUMERIC(29,4) NOT NULL, + avg_user_impact NUMERIC(29,1) NOT NULL, + user_seeks BIGINT NOT NULL, + user_scans BIGINT NOT NULL, + unique_compiles BIGINT NULL, + equality_columns NVARCHAR(MAX), + equality_columns_with_data_type NVARCHAR(MAX), + inequality_columns NVARCHAR(MAX), + inequality_columns_with_data_type NVARCHAR(MAX), + included_columns NVARCHAR(MAX), + included_columns_with_data_type NVARCHAR(MAX), + is_low BIT, + [index_estimated_impact] AS + REPLACE(CONVERT(NVARCHAR(256),CAST(CAST( + (user_seeks + user_scans) + AS BIGINT) AS MONEY), 1), '.00', '') + N' use' + + CASE WHEN (user_seeks + user_scans) > 1 THEN N's' ELSE N'' END + +N'; Impact: ' + CAST(avg_user_impact AS NVARCHAR(30)) + + N'%; Avg query cost: ' + + CAST(avg_total_user_cost AS NVARCHAR(30)), + [missing_index_details] AS + CASE WHEN COALESCE(equality_columns_with_data_type,equality_columns) IS NOT NULL + THEN N'EQUALITY: ' + COALESCE(CAST(equality_columns_with_data_type AS NVARCHAR(MAX)), CAST(equality_columns AS NVARCHAR(MAX))) + N' ' + ELSE N'' END + + + CASE WHEN COALESCE(inequality_columns_with_data_type,inequality_columns) IS NOT NULL + THEN N'INEQUALITY: ' + COALESCE(CAST(inequality_columns_with_data_type AS NVARCHAR(MAX)), CAST(inequality_columns AS NVARCHAR(MAX))) + N' ' + ELSE N'' END + + + CASE WHEN COALESCE(included_columns_with_data_type,included_columns) IS NOT NULL + THEN N'INCLUDE: ' + COALESCE(CAST(included_columns_with_data_type AS NVARCHAR(MAX)), CAST(included_columns AS NVARCHAR(MAX))) + N' ' + ELSE N'' END, + [create_tsql] AS N'CREATE INDEX [' + + LEFT(REPLACE(REPLACE(REPLACE(REPLACE( + ISNULL(equality_columns,N'')+ + CASE WHEN equality_columns IS NOT NULL AND inequality_columns IS NOT NULL THEN N'_' ELSE N'' END + + ISNULL(inequality_columns,''),',','') + ,'[',''),']',''),' ','_') + + CASE WHEN included_columns IS NOT NULL THEN N'_Includes' ELSE N'' END, 128) + N'] ON ' + + [statement] + N' (' + ISNULL(equality_columns,N'') + + CASE WHEN equality_columns IS NOT NULL AND inequality_columns IS NOT NULL THEN N', ' ELSE N'' END + + CASE WHEN inequality_columns IS NOT NULL THEN inequality_columns ELSE N'' END + + ') ' + CASE WHEN included_columns IS NOT NULL THEN N' INCLUDE (' + included_columns + N')' ELSE N'' END + + N' WITH (' + + N'FILLFACTOR=100, ONLINE=?, SORT_IN_TEMPDB=?, DATA_COMPRESSION=?' + + N')' + + N';' + , + [more_info] AS N'EXEC dbo.sp_BlitzIndex @DatabaseName=' + QUOTENAME([database_name],'''') + + N', @SchemaName=' + QUOTENAME([schema_name],'''') + N', @TableName=' + QUOTENAME([table_name],'''') + N';', + [sample_query_plan] XML NULL + ); + + CREATE TABLE #ForeignKeys ( + [database_id] INT NOT NULL, + [database_name] NVARCHAR(128) NOT NULL , + [schema_name] NVARCHAR(128) NOT NULL , + foreign_key_name NVARCHAR(256), + parent_object_id INT, + parent_object_name NVARCHAR(256), + referenced_object_id INT, + referenced_object_name NVARCHAR(256), + is_disabled BIT, + is_not_trusted BIT, + is_not_for_replication BIT, + parent_fk_columns NVARCHAR(MAX), + referenced_fk_columns NVARCHAR(MAX), + update_referential_action_desc NVARCHAR(16), + delete_referential_action_desc NVARCHAR(60) + ); + + CREATE TABLE #UnindexedForeignKeys + ( + [database_id] INT NOT NULL, + [database_name] NVARCHAR(128) NOT NULL , + [schema_name] NVARCHAR(128) NOT NULL , + foreign_key_name NVARCHAR(256), + parent_object_name NVARCHAR(256), + parent_object_id INT, + referenced_object_name NVARCHAR(256), + referenced_object_id INT + ); + + CREATE TABLE #IndexCreateTsql ( + index_sanity_id INT NOT NULL, + create_tsql NVARCHAR(MAX) NOT NULL + ); + + CREATE TABLE #DatabaseList ( + DatabaseName NVARCHAR(256), + secondary_role_allow_connections_desc NVARCHAR(50) + + ); + + CREATE TABLE #PartitionCompressionInfo ( + [index_sanity_id] INT NULL, + [partition_compression_detail] NVARCHAR(4000) NULL + ); + + CREATE TABLE #Statistics ( + database_id INT NOT NULL, + database_name NVARCHAR(256) NOT NULL, + table_name NVARCHAR(128) NULL, + schema_name NVARCHAR(128) NULL, + index_name NVARCHAR(128) NULL, + column_names NVARCHAR(MAX) NULL, + statistics_name NVARCHAR(128) NULL, + last_statistics_update DATETIME NULL, + days_since_last_stats_update INT NULL, + rows BIGINT NULL, + rows_sampled BIGINT NULL, + percent_sampled DECIMAL(18, 1) NULL, + histogram_steps INT NULL, + modification_counter BIGINT NULL, + percent_modifications DECIMAL(18, 1) NULL, + modifications_before_auto_update INT 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 + ); + + CREATE TABLE #ComputedColumns + ( + index_sanity_id INT IDENTITY(1, 1) NOT NULL, + database_name NVARCHAR(128) NULL, + database_id INT NOT NULL, + table_name NVARCHAR(128) NOT NULL, + schema_name NVARCHAR(128) NOT NULL, + column_name NVARCHAR(128) NULL, + is_nullable BIT NULL, + definition NVARCHAR(MAX) NULL, + uses_database_collation BIT NOT NULL, + is_persisted BIT NOT NULL, + is_computed BIT NOT NULL, + is_function INT NOT NULL, + column_definition NVARCHAR(MAX) NULL + ); + + CREATE TABLE #TraceStatus + ( + TraceFlag NVARCHAR(10) , + status BIT , + Global BIT , + Session BIT + ); + + CREATE TABLE #TemporalTables + ( + index_sanity_id INT IDENTITY(1, 1) NOT NULL, + database_name NVARCHAR(128) NOT NULL, + database_id INT NOT NULL, + schema_name NVARCHAR(128) NOT NULL, + table_name NVARCHAR(128) NOT NULL, + history_table_name NVARCHAR(128) NOT NULL, + history_schema_name NVARCHAR(128) NOT NULL, + start_column_name NVARCHAR(128) NOT NULL, + end_column_name NVARCHAR(128) NOT NULL, + period_name NVARCHAR(128) NOT NULL + ); + + CREATE TABLE #CheckConstraints + ( + index_sanity_id INT IDENTITY(1, 1) NOT NULL, + database_name NVARCHAR(128) NULL, + database_id INT NOT NULL, + table_name NVARCHAR(128) NOT NULL, + schema_name NVARCHAR(128) NOT NULL, + constraint_name NVARCHAR(128) NULL, + is_disabled BIT NULL, + definition NVARCHAR(MAX) NULL, + uses_database_collation BIT NOT NULL, + is_not_trusted BIT NOT NULL, + is_function INT NOT NULL, + column_definition NVARCHAR(MAX) NULL + ); + + CREATE TABLE #FilteredIndexes + ( + index_sanity_id INT IDENTITY(1, 1) NOT NULL, + database_name NVARCHAR(128) NULL, + database_id INT NOT NULL, + schema_name NVARCHAR(128) NOT NULL, + table_name NVARCHAR(128) NOT NULL, + index_name NVARCHAR(128) NULL, + column_name NVARCHAR(128) NULL + ); + + CREATE TABLE #Ignore_Databases + ( + DatabaseName NVARCHAR(128), + Reason NVARCHAR(100) + ); + +/* Sanitize our inputs */ +SELECT + @OutputServerName = QUOTENAME(@OutputServerName), + @OutputDatabaseName = QUOTENAME(@OutputDatabaseName), + @OutputSchemaName = QUOTENAME(@OutputSchemaName), + @OutputTableName = QUOTENAME(@OutputTableName); + + +IF @GetAllDatabases = 1 + BEGIN + INSERT INTO #DatabaseList (DatabaseName) + SELECT DB_NAME(database_id) + FROM sys.databases + WHERE user_access_desc = 'MULTI_USER' + AND state_desc = 'ONLINE' + AND database_id > 4 + AND DB_NAME(database_id) NOT LIKE 'ReportServer%' + AND DB_NAME(database_id) NOT LIKE 'rdsadmin%' + AND LOWER(name) NOT IN('dbatools', 'dbadmin', 'dbmaintenance') + AND is_distributor = 0 + OPTION ( RECOMPILE ); + + /* Skip non-readable databases in an AG - see Github issue #1160 */ + IF EXISTS (SELECT * FROM sys.all_objects o INNER JOIN sys.all_columns c ON o.object_id = c.object_id AND o.name = 'dm_hadr_availability_replica_states' AND c.name = 'role_desc') + BEGIN + SET @dsql = N'UPDATE #DatabaseList SET secondary_role_allow_connections_desc = ''NO'' WHERE DatabaseName IN ( + SELECT d.name + FROM sys.dm_hadr_availability_replica_states rs + INNER JOIN sys.databases d ON rs.replica_id = d.replica_id + INNER JOIN sys.availability_replicas r ON rs.replica_id = r.replica_id + WHERE rs.role_desc = ''SECONDARY'' + AND r.secondary_role_allow_connections_desc = ''NO'') + OPTION ( RECOMPILE );'; + EXEC sp_executesql @dsql; + + IF EXISTS (SELECT * FROM #DatabaseList WHERE secondary_role_allow_connections_desc = 'NO') + BEGIN + INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, database_name, URL, details, index_definition, + index_usage_summary, index_size_summary ) + VALUES ( 1, + 0, + N'Skipped non-readable AG secondary databases.', + N'You are running this on an AG secondary, and some of your databases are configured as non-readable when this is a secondary node.', + N'To analyze those databases, run sp_BlitzIndex on the primary, or on a readable secondary.', + 'http://FirstResponderKit.org', '', '', '', '' + ); + END; + END; + + IF @IgnoreDatabases IS NOT NULL + AND LEN(@IgnoreDatabases) > 0 + BEGIN + RAISERROR(N'Setting up filter to ignore databases', 0, 1) WITH NOWAIT; + SET @DatabaseToIgnore = ''; + + WHILE LEN(@IgnoreDatabases) > 0 + BEGIN + IF PATINDEX('%,%', @IgnoreDatabases) > 0 + BEGIN + SET @DatabaseToIgnore = SUBSTRING(@IgnoreDatabases, 0, PATINDEX('%,%',@IgnoreDatabases)) ; + + INSERT INTO #Ignore_Databases (DatabaseName, Reason) + SELECT LTRIM(RTRIM(@DatabaseToIgnore)), 'Specified in the @IgnoreDatabases parameter' + OPTION (RECOMPILE) ; + + SET @IgnoreDatabases = SUBSTRING(@IgnoreDatabases, LEN(@DatabaseToIgnore + ',') + 1, LEN(@IgnoreDatabases)) ; + END; + ELSE + BEGIN + SET @DatabaseToIgnore = @IgnoreDatabases ; + SET @IgnoreDatabases = NULL ; + + INSERT INTO #Ignore_Databases (DatabaseName, Reason) + SELECT LTRIM(RTRIM(@DatabaseToIgnore)), 'Specified in the @IgnoreDatabases parameter' + OPTION (RECOMPILE) ; + END; + END; + + END + + END; +ELSE + BEGIN + INSERT INTO #DatabaseList + ( DatabaseName ) + SELECT CASE + WHEN @DatabaseName IS NULL OR @DatabaseName = N'' + THEN DB_NAME() + ELSE @DatabaseName END; + END; + +SET @NumDatabases = (SELECT COUNT(*) FROM #DatabaseList AS D LEFT OUTER JOIN #Ignore_Databases AS I ON D.DatabaseName = I.DatabaseName WHERE I.DatabaseName IS NULL); +SET @msg = N'Number of databases to examine: ' + CAST(@NumDatabases AS NVARCHAR(50)); +RAISERROR (@msg,0,1) WITH NOWAIT; + + + +/* Running on 50+ databases can take a reaaallly long time, so we want explicit permission to do so (and only after warning about it) */ + + +BEGIN TRY + IF @NumDatabases >= 50 AND @BringThePain != 1 AND @TableName IS NULL + BEGIN + + INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, URL, details, index_definition, + index_usage_summary, index_size_summary ) + VALUES ( -1, + 0 , + @ScriptVersionName, + CASE WHEN @GetAllDatabases = 1 THEN N'All Databases' ELSE N'Database ' + QUOTENAME(@DatabaseName) + N' as of ' + CONVERT(NVARCHAR(16), GETDATE(), 121) END, + N'From Your Community Volunteers', + N'http://FirstResponderKit.org', + N'', + N'', + N'' + ); + INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, database_name, URL, details, index_definition, + index_usage_summary, index_size_summary ) + VALUES ( 1, + 0, + N'You''re trying to run sp_BlitzIndex on a server with ' + CAST(@NumDatabases AS NVARCHAR(8)) + N' databases. ', + N'Running sp_BlitzIndex on a server with 50+ databases may cause temporary problems for the server and/or user.', + N'If you''re sure you want to do this, run again with the parameter @BringThePain = 1.', + 'http://FirstResponderKit.org', + '', + '', + '', + '' + ); + + if(@OutputType <> 'NONE') + BEGIN + SELECT bir.blitz_result_id, + bir.check_id, + bir.index_sanity_id, + bir.Priority, + bir.findings_group, + bir.finding, + bir.database_name, + bir.URL, + bir.details, + bir.index_definition, + bir.secret_columns, + bir.index_usage_summary, + bir.index_size_summary, + bir.create_tsql, + bir.more_info + FROM #BlitzIndexResults AS bir; + RAISERROR('Running sp_BlitzIndex on a server with 50+ databases may cause temporary problems for the server', 12, 1); + END; + + RETURN; + + END; +END TRY +BEGIN CATCH + RAISERROR (N'Failure to execute due to number of databases.', 0,1) WITH NOWAIT; + + SELECT @msg = ERROR_MESSAGE(), + @ErrorSeverity = ERROR_SEVERITY(), + @ErrorState = ERROR_STATE(); + + RAISERROR (@msg, @ErrorSeverity, @ErrorState); + + WHILE @@trancount > 0 + ROLLBACK; + + RETURN; + END CATCH; + + +RAISERROR (N'Checking partition counts to exclude databases with over 100 partitions',0,1) WITH NOWAIT; +IF @BringThePain = 0 AND @SkipPartitions = 0 AND @TableName IS NULL + BEGIN + DECLARE partition_cursor CURSOR FOR + SELECT dl.DatabaseName + FROM #DatabaseList dl + LEFT OUTER JOIN #Ignore_Databases i ON dl.DatabaseName = i.DatabaseName + WHERE COALESCE(dl.secondary_role_allow_connections_desc, 'OK') <> 'NO' + AND i.DatabaseName IS NULL + + OPEN partition_cursor + FETCH NEXT FROM partition_cursor INTO @DatabaseName + + WHILE @@FETCH_STATUS = 0 + BEGIN + /* Count the total number of partitions */ + SET @dsql = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + SELECT @RowcountOUT = SUM(1) FROM ' + QUOTENAME(@DatabaseName) + '.sys.partitions WHERE partition_number > 1 OPTION ( RECOMPILE );'; + EXEC sp_executesql @dsql, N'@RowcountOUT BIGINT OUTPUT', @RowcountOUT = @Rowcount OUTPUT; + IF @Rowcount > 100 + BEGIN + RAISERROR (N'Skipping database %s because > 100 partitions were found. To check this database, you must set @BringThePain = 1.',0,1,@DatabaseName) WITH NOWAIT; + INSERT INTO #Ignore_Databases (DatabaseName, Reason) + SELECT @DatabaseName, 'Over 100 partitions found - use @BringThePain = 1 to analyze' + END; + FETCH NEXT FROM partition_cursor INTO @DatabaseName + END; + CLOSE partition_cursor + DEALLOCATE partition_cursor + + END; + +INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, URL, details, index_definition, + index_usage_summary, index_size_summary ) +SELECT 1, 0 , + 'Database Skipped', + i.DatabaseName, + 'http://FirstResponderKit.org', + i.Reason, '', '', '' +FROM #Ignore_Databases i; + + +/* Last startup */ +IF COLUMNPROPERTY(OBJECT_ID('sys.dm_os_sys_info'),'sqlserver_start_time','ColumnID') IS NOT NULL +BEGIN + SELECT @DaysUptime = CAST(DATEDIFF(HOUR, sqlserver_start_time, GETDATE()) / 24. AS NUMERIC (23,2)) + FROM sys.dm_os_sys_info; +END +ELSE +BEGIN + SELECT @DaysUptime = CAST(DATEDIFF(HOUR, create_date, GETDATE()) / 24. AS NUMERIC (23,2)) + FROM sys.databases + WHERE database_id = 2; +END + +IF @DaysUptime = 0 OR @DaysUptime IS NULL + SET @DaysUptime = .01; + +SELECT @DaysUptimeInsertValue = 'Server: ' + (CONVERT(VARCHAR(256), (SERVERPROPERTY('ServerName')))) + ' Days Uptime: ' + RTRIM(@DaysUptime); + + +/* Permission granted or unnecessary? Ok, let's go! */ + +RAISERROR (N'Starting loop through databases',0,1) WITH NOWAIT; +DECLARE c1 CURSOR +LOCAL FAST_FORWARD +FOR +SELECT dl.DatabaseName +FROM #DatabaseList dl +LEFT OUTER JOIN #Ignore_Databases i ON dl.DatabaseName = i.DatabaseName +WHERE COALESCE(dl.secondary_role_allow_connections_desc, 'OK') <> 'NO' + AND i.DatabaseName IS NULL +ORDER BY dl.DatabaseName; + +OPEN c1; +FETCH NEXT FROM c1 INTO @DatabaseName; + WHILE @@FETCH_STATUS = 0 + +BEGIN + + RAISERROR (@LineFeed, 0, 1) WITH NOWAIT; + RAISERROR (@LineFeed, 0, 1) WITH NOWAIT; + RAISERROR (@DatabaseName, 0, 1) WITH NOWAIT; + +SELECT @DatabaseID = [database_id] +FROM sys.databases + WHERE [name] = @DatabaseName + AND user_access_desc='MULTI_USER' + AND state_desc = 'ONLINE'; + +---------------------------------------- +--STEP 1: OBSERVE THE PATIENT +--This step puts index information into temp tables. +---------------------------------------- +BEGIN TRY + BEGIN + DECLARE @d VARCHAR(19) = CONVERT(VARCHAR(19), GETDATE(), 121); + RAISERROR (N'starting at %s',0,1, @d) WITH NOWAIT; + + --Validate SQL Server Version + + IF (SELECT LEFT(@SQLServerProductVersion, + CHARINDEX('.',@SQLServerProductVersion,0)-1 + )) <= 9 + BEGIN + SET @msg=N'sp_BlitzIndex is only supported on SQL Server 2008 and higher. The version of this instance is: ' + @SQLServerProductVersion; + RAISERROR(@msg,16,1); + END; + + --Short circuit here if database name does not exist. + IF @DatabaseName IS NULL OR @DatabaseID IS NULL + BEGIN + SET @msg='Database does not exist or is not online/multi-user: cannot proceed.'; + RAISERROR(@msg,16,1); + END; + + --Validate parameters. + IF (@Mode NOT IN (0,1,2,3,4)) + BEGIN + SET @msg=N'Invalid @Mode parameter. 0=diagnose, 1=summarize, 2=index detail, 3=missing index detail, 4=diagnose detail'; + RAISERROR(@msg,16,1); + END; + + IF (@Mode <> 0 AND @TableName IS NOT NULL) + BEGIN + SET @msg=N'Setting the @Mode doesn''t change behavior if you supply @TableName. Use default @Mode=0 to see table detail.'; + RAISERROR(@msg,16,1); + END; + + IF ((@Mode <> 0 OR @TableName IS NOT NULL) AND @Filter <> 0) + BEGIN + SET @msg=N'@Filter only applies when @Mode=0 and @TableName is not specified. Please try again.'; + RAISERROR(@msg,16,1); + END; + + IF (@SchemaName IS NOT NULL AND @TableName IS NULL) + BEGIN + SET @msg='We can''t run against a whole schema! Specify a @TableName, or leave both NULL for diagnosis.'; + RAISERROR(@msg,16,1); + END; + + + IF (@TableName IS NOT NULL AND @SchemaName IS NULL) + BEGIN + SET @SchemaName=N'dbo'; + SET @msg='@SchemaName wasn''t specified-- assuming schema=dbo.'; + RAISERROR(@msg,1,1) WITH NOWAIT; + END; + + --If a table is specified, grab the object id. + --Short circuit if it doesn't exist. + IF @TableName IS NOT NULL + BEGIN + SET @dsql = N' + SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + SELECT @ObjectID= OBJECT_ID + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.objects AS so + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS sc on + so.schema_id=sc.schema_id + where so.type in (''U'', ''V'') + and so.name=' + QUOTENAME(@TableName,'''')+ N' + and sc.name=' + QUOTENAME(@SchemaName,'''')+ N' + /*Has a row in sys.indexes. This lets us get indexed views.*/ + and exists ( + SELECT si.name + FROM ' + QUOTENAME(@DatabaseName) + '.sys.indexes AS si + WHERE so.object_id=si.object_id) + OPTION (RECOMPILE);'; + + SET @params='@ObjectID INT OUTPUT'; + + IF @dsql IS NULL + RAISERROR('@dsql is null',16,1); + + EXEC sp_executesql @dsql, @params, @ObjectID=@ObjectID OUTPUT; + + IF @ObjectID IS NULL + BEGIN + SET @msg=N'Oh, this is awkward. I can''t find the table or indexed view you''re looking for in that database.' + CHAR(10) + + N'Please check your parameters.'; + RAISERROR(@msg,1,1); + RETURN; + END; + END; + + --set @collation + SELECT @collation=collation_name + FROM sys.databases + WHERE database_id=@DatabaseID; + + --insert columns for clustered indexes and heaps + --collect info on identity columns for this one + SET @dsql = N'/* sp_BlitzIndex */ + SET LOCK_TIMEOUT 1000; /* To fix locking bug in sys.identity_columns. See Github issue #2176. */ + SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + SELECT ' + CAST(@DatabaseID AS NVARCHAR(16)) + ', + s.name, + si.object_id, + si.index_id, + sc.key_ordinal, + sc.is_included_column, + sc.is_descending_key, + sc.partition_ordinal, + c.name as column_name, + st.name as system_type_name, + c.max_length, + c.[precision], + c.[scale], + c.collation_name, + c.is_nullable, + c.is_identity, + c.is_computed, + c.is_replicated, + ' + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N'c.is_sparse' ELSE N'NULL as is_sparse' END + N', + ' + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N'c.is_filestream' ELSE N'NULL as is_filestream' END + N', + CAST(ic.seed_value AS DECIMAL(38,0)), + CAST(ic.increment_value AS DECIMAL(38,0)), + CAST(ic.last_value AS DECIMAL(38,0)), + ic.is_not_for_replication + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.indexes si + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns c ON + si.object_id=c.object_id + LEFT JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.index_columns sc ON + sc.object_id = si.object_id + and sc.index_id=si.index_id + AND sc.column_id=c.column_id + LEFT JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.identity_columns ic ON + c.object_id=ic.object_id and + c.column_id=ic.column_id + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.types st ON + c.system_type_id=st.system_type_id + AND c.user_type_id=st.user_type_id + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects AS so ON si.object_id = so.object_id + AND so.is_ms_shipped = 0 + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s ON s.schema_id = so.schema_id + WHERE si.index_id in (0,1) ' + + CASE WHEN @ObjectID IS NOT NULL + THEN N' AND si.object_id=' + CAST(@ObjectID AS NVARCHAR(30)) + ELSE N'' END + + N'OPTION (RECOMPILE);'; + + IF @dsql IS NULL + RAISERROR('@dsql is null',16,1); + + RAISERROR (N'Inserting data into #IndexColumns for clustered indexes and heaps',0,1) WITH NOWAIT; + IF @Debug = 1 + BEGIN + PRINT SUBSTRING(@dsql, 1, 4000); + PRINT SUBSTRING(@dsql, 4001, 4000); + PRINT SUBSTRING(@dsql, 8001, 4000); + PRINT SUBSTRING(@dsql, 12001, 4000); + PRINT SUBSTRING(@dsql, 16001, 4000); + PRINT SUBSTRING(@dsql, 20001, 4000); + PRINT SUBSTRING(@dsql, 24001, 4000); + PRINT SUBSTRING(@dsql, 28001, 4000); + PRINT SUBSTRING(@dsql, 32001, 4000); + PRINT SUBSTRING(@dsql, 36001, 4000); + END; + BEGIN TRY + INSERT #IndexColumns ( database_id, [schema_name], [object_id], index_id, key_ordinal, is_included_column, is_descending_key, partition_ordinal, + column_name, system_type_name, max_length, precision, scale, collation_name, is_nullable, is_identity, is_computed, + is_replicated, is_sparse, is_filestream, seed_value, increment_value, last_value, is_not_for_replication ) + EXEC sp_executesql @dsql; + END TRY + BEGIN CATCH + RAISERROR (N'Failure inserting data into #IndexColumns for clustered indexes and heaps.', 0,1) WITH NOWAIT; + + IF @dsql IS NOT NULL + BEGIN + SET @msg= 'Last @dsql: ' + @dsql; + RAISERROR(@msg, 0, 1) WITH NOWAIT; + END; + + SELECT @msg = @DatabaseName + N' database failed to process. ' + ERROR_MESSAGE(), + @ErrorSeverity = 0, @ErrorState = ERROR_STATE(); + RAISERROR (@msg,@ErrorSeverity, @ErrorState )WITH NOWAIT; + + WHILE @@trancount > 0 + ROLLBACK; + + RETURN; + END CATCH; + + + --insert columns for nonclustered indexes + --this uses a full join to sys.index_columns + --We don't collect info on identity columns here. They may be in NC indexes, but we just analyze identities in the base table. + SET @dsql = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + SELECT ' + CAST(@DatabaseID AS NVARCHAR(16)) + ', + s.name, + si.object_id, + si.index_id, + sc.key_ordinal, + sc.is_included_column, + sc.is_descending_key, + sc.partition_ordinal, + c.name as column_name, + st.name as system_type_name, + c.max_length, + c.[precision], + c.[scale], + c.collation_name, + c.is_nullable, + c.is_identity, + c.is_computed, + c.is_replicated, + ' + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N'c.is_sparse' ELSE N'NULL AS is_sparse' END + N', + ' + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N'c.is_filestream' ELSE N'NULL AS is_filestream' END + N' + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.indexes AS si + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns AS c ON + si.object_id=c.object_id + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.index_columns AS sc ON + sc.object_id = si.object_id + and sc.index_id=si.index_id + AND sc.column_id=c.column_id + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.types AS st ON + c.system_type_id=st.system_type_id + AND c.user_type_id=st.user_type_id + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects AS so ON si.object_id = so.object_id + AND so.is_ms_shipped = 0 + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s ON s.schema_id = so.schema_id + WHERE si.index_id not in (0,1) ' + + CASE WHEN @ObjectID IS NOT NULL + THEN N' AND si.object_id=' + CAST(@ObjectID AS NVARCHAR(30)) + ELSE N'' END + + N'OPTION (RECOMPILE);'; + + IF @dsql IS NULL + RAISERROR('@dsql is null',16,1); + + RAISERROR (N'Inserting data into #IndexColumns for nonclustered indexes',0,1) WITH NOWAIT; + IF @Debug = 1 + BEGIN + PRINT SUBSTRING(@dsql, 0, 4000); + PRINT SUBSTRING(@dsql, 4000, 8000); + PRINT SUBSTRING(@dsql, 8000, 12000); + PRINT SUBSTRING(@dsql, 12000, 16000); + PRINT SUBSTRING(@dsql, 16000, 20000); + PRINT SUBSTRING(@dsql, 20000, 24000); + PRINT SUBSTRING(@dsql, 24000, 28000); + PRINT SUBSTRING(@dsql, 28000, 32000); + PRINT SUBSTRING(@dsql, 32000, 36000); + PRINT SUBSTRING(@dsql, 36000, 40000); + END; + INSERT #IndexColumns ( database_id, [schema_name], [object_id], index_id, key_ordinal, is_included_column, is_descending_key, partition_ordinal, + column_name, system_type_name, max_length, precision, scale, collation_name, is_nullable, is_identity, is_computed, + is_replicated, is_sparse, is_filestream ) + EXEC sp_executesql @dsql; + + SET @dsql = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + SELECT ' + CAST(@DatabaseID AS NVARCHAR(10)) + N' AS database_id, + so.object_id, + si.index_id, + si.type, + @i_DatabaseName AS database_name, + COALESCE(sc.NAME, ''Unknown'') AS [schema_name], + COALESCE(so.name, ''Unknown'') AS [object_name], + COALESCE(si.name, ''Unknown'') AS [index_name], + CASE WHEN so.[type] = CAST(''V'' AS CHAR(2)) THEN 1 ELSE 0 END, + si.is_unique, + si.is_primary_key, + si.is_unique_constraint, + CASE when si.type = 3 THEN 1 ELSE 0 END AS is_XML, + CASE when si.type = 4 THEN 1 ELSE 0 END AS is_spatial, + CASE when si.type = 6 THEN 1 ELSE 0 END AS is_NC_columnstore, + CASE when si.type = 5 then 1 else 0 end as is_CX_columnstore, + CASE when si.data_space_id = 0 then 1 else 0 end as is_in_memory_oltp, + si.is_disabled, + si.is_hypothetical, + si.is_padded, + si.fill_factor,' + + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N' + CASE WHEN si.filter_definition IS NOT NULL THEN si.filter_definition + ELSE N'''' + END AS filter_definition' ELSE N''''' AS filter_definition' END + + CASE + WHEN @OptimizeForSequentialKey = 1 + THEN N', si.optimize_for_sequential_key' + ELSE N', CONVERT(BIT, NULL) AS optimize_for_sequential_key' + END + + N', + ISNULL(us.user_seeks, 0), + ISNULL(us.user_scans, 0), + ISNULL(us.user_lookups, 0), + ISNULL(us.user_updates, 0), + us.last_user_seek, + us.last_user_scan, + us.last_user_lookup, + us.last_user_update, + so.create_date, + so.modify_date + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.indexes AS si WITH (NOLOCK) + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects AS so WITH (NOLOCK) ON si.object_id = so.object_id + AND so.is_ms_shipped = 0 /*Exclude objects shipped by Microsoft*/ + AND so.type <> ''TF'' /*Exclude table valued functions*/ + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas sc ON so.schema_id = sc.schema_id + LEFT JOIN sys.dm_db_index_usage_stats AS us WITH (NOLOCK) ON si.[object_id] = us.[object_id] + AND si.index_id = us.index_id + AND us.database_id = ' + CAST(@DatabaseID AS NVARCHAR(10)) + N' + WHERE si.[type] IN ( 0, 1, 2, 3, 4, 5, 6 ) + /* Heaps, clustered, nonclustered, XML, spatial, Cluster Columnstore, NC Columnstore */ ' + + CASE WHEN @TableName IS NOT NULL THEN N' and so.name=' + QUOTENAME(@TableName,N'''') + N' ' ELSE N'' END + + CASE WHEN ( @IncludeInactiveIndexes = 0 + AND @Mode IN (0, 4) + AND @TableName IS NULL ) + THEN N'AND ( us.user_seeks + us.user_scans + us.user_lookups + us.user_updates ) > 0' + ELSE N'' + END + + N'OPTION ( RECOMPILE ); + '; + IF @dsql IS NULL + RAISERROR('@dsql is null',16,1); + + RAISERROR (N'Inserting data into #IndexSanity',0,1) WITH NOWAIT; + IF @Debug = 1 + BEGIN + PRINT SUBSTRING(@dsql, 0, 4000); + PRINT SUBSTRING(@dsql, 4000, 8000); + PRINT SUBSTRING(@dsql, 8000, 12000); + PRINT SUBSTRING(@dsql, 12000, 16000); + PRINT SUBSTRING(@dsql, 16000, 20000); + PRINT SUBSTRING(@dsql, 20000, 24000); + PRINT SUBSTRING(@dsql, 24000, 28000); + PRINT SUBSTRING(@dsql, 28000, 32000); + PRINT SUBSTRING(@dsql, 32000, 36000); + PRINT SUBSTRING(@dsql, 36000, 40000); + END; + INSERT #IndexSanity ( [database_id], [object_id], [index_id], [index_type], [database_name], [schema_name], [object_name], + index_name, is_indexed_view, is_unique, is_primary_key, is_unique_constraint, is_XML, is_spatial, is_NC_columnstore, is_CX_columnstore, is_in_memory_oltp, + is_disabled, is_hypothetical, is_padded, fill_factor, filter_definition, [optimize_for_sequential_key], user_seeks, user_scans, + user_lookups, user_updates, last_user_seek, last_user_scan, last_user_lookup, last_user_update, + create_date, modify_date ) + EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; + + + RAISERROR (N'Checking partition count',0,1) WITH NOWAIT; + IF @BringThePain = 0 AND @SkipPartitions = 0 AND @TableName IS NULL + BEGIN + /* Count the total number of partitions */ + SET @dsql = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + SELECT @RowcountOUT = SUM(1) FROM ' + QUOTENAME(@DatabaseName) + '.sys.partitions WHERE partition_number > 1 OPTION ( RECOMPILE );'; + EXEC sp_executesql @dsql, N'@RowcountOUT BIGINT OUTPUT', @RowcountOUT = @Rowcount OUTPUT; + IF @Rowcount > 100 + BEGIN + RAISERROR (N'Setting @SkipPartitions = 1 because > 100 partitions were found. To check them, you must set @BringThePain = 1.',0,1) WITH NOWAIT; + SET @SkipPartitions = 1; + INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, URL, details, index_definition, + index_usage_summary, index_size_summary ) + VALUES ( 1, 0 , + 'Some Checks Were Skipped', + '@SkipPartitions Forced to 1', + 'http://FirstResponderKit.org', CAST(@Rowcount AS NVARCHAR(50)) + ' partitions found. To analyze them, use @BringThePain = 1.', 'We try to keep things quick - and warning, running @BringThePain = 1 can take tens of minutes.', '', '' + ); + END; + END; + + + + IF (@SkipPartitions = 0) + BEGIN + IF (SELECT LEFT(@SQLServerProductVersion, + CHARINDEX('.',@SQLServerProductVersion,0)-1 )) <= 2147483647 --Make change here + BEGIN + + RAISERROR (N'Preferring non-2012 syntax with LEFT JOIN to sys.dm_db_index_operational_stats',0,1) WITH NOWAIT; + + --NOTE: If you want to use the newer syntax for 2012+, you'll have to change 2147483647 to 11 on line ~819 + --This change was made because on a table with lots of paritions, the OUTER APPLY was crazy slow. + + -- get relevant columns from sys.dm_db_partition_stats, sys.partitions and sys.objects + IF OBJECT_ID('tempdb..#dm_db_partition_stats_etc') IS NOT NULL + DROP TABLE #dm_db_partition_stats_etc; + + create table #dm_db_partition_stats_etc + ( + database_id smallint not null + , object_id int not null + , sname sysname NULL + , index_id int + , partition_number int + , partition_id bigint + , row_count bigint + , reserved_MB bigint + , reserved_LOB_MB bigint + , reserved_row_overflow_MB bigint + , lock_escalation_desc nvarchar(60) + , data_compression_desc nvarchar(60) + ) + + -- get relevant info from sys.dm_db_index_operational_stats + IF OBJECT_ID('tempdb..#dm_db_index_operational_stats') IS NOT NULL + DROP TABLE #dm_db_index_operational_stats; + create table #dm_db_index_operational_stats + ( + database_id smallint not null + , object_id int not null + , index_id int + , partition_number int + , hobt_id bigint + , leaf_insert_count bigint + , leaf_delete_count bigint + , leaf_update_count bigint + , range_scan_count bigint + , singleton_lookup_count bigint + , forwarded_fetch_count bigint + , lob_fetch_in_pages bigint + , lob_fetch_in_bytes bigint + , row_overflow_fetch_in_pages bigint + , row_overflow_fetch_in_bytes bigint + , row_lock_count bigint + , row_lock_wait_count bigint + , row_lock_wait_in_ms bigint + , page_lock_count bigint + , page_lock_wait_count bigint + , page_lock_wait_in_ms bigint + , index_lock_promotion_attempt_count bigint + , index_lock_promotion_count bigint + , page_latch_wait_count bigint + , page_latch_wait_in_ms bigint + , page_io_latch_wait_count bigint + , page_io_latch_wait_in_ms bigint + ) + + SET @dsql = N' + DECLARE @d VARCHAR(19) = CONVERT(VARCHAR(19), GETDATE(), 121) + RAISERROR (N''start getting data into #dm_db_partition_stats_etc at %s'',0,1, @d) WITH NOWAIT; + SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + INSERT INTO #dm_db_partition_stats_etc + ( + database_id, object_id, sname, index_id, partition_number, partition_id, row_count, reserved_MB, reserved_LOB_MB, reserved_row_overflow_MB, lock_escalation_desc, data_compression_desc + ) + SELECT ' + CAST(@DatabaseID AS NVARCHAR(10)) + N' AS database_id, + ps.object_id, + s.name as sname, + ps.index_id, + ps.partition_number, + ps.partition_id, + ps.row_count, + ps.reserved_page_count * 8. / 1024. AS reserved_MB, + ps.lob_reserved_page_count * 8. / 1024. AS reserved_LOB_MB, + ps.row_overflow_reserved_page_count * 8. / 1024. AS reserved_row_overflow_MB, + le.lock_escalation_desc, + ' + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N'par.data_compression_desc ' ELSE N'null as data_compression_desc ' END + N' +'; + + SET @dsql = @dsql + N' + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_partition_stats AS ps + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partitions AS par on ps.partition_id=par.partition_id + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects AS so ON ps.object_id = so.object_id + AND so.is_ms_shipped = 0 /*Exclude objects shipped by Microsoft*/ + AND so.type <> ''TF'' /*Exclude table valued functions*/ + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s ON s.schema_id = so.schema_id + OUTER APPLY (SELECT st.lock_escalation_desc + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.tables st + WHERE st.object_id = ps.object_id + AND ps.index_id < 2 ) le + WHERE 1=1 + ' + CASE WHEN @ObjectID IS NOT NULL THEN N'AND so.object_id=' + CAST(@ObjectID AS NVARCHAR(30)) + N' ' ELSE N' ' END + N' + ' + CASE WHEN @Filter = 2 THEN N'AND ps.reserved_page_count * 8./1024. > ' + CAST(@FilterMB AS NVARCHAR(5)) + N' ' ELSE N' ' END + N' + GROUP BY ps.object_id, + s.name, + ps.index_id, + ps.partition_number, + ps.partition_id, + ps.row_count, + ps.reserved_page_count, + ps.lob_reserved_page_count, + ps.row_overflow_reserved_page_count, + le.lock_escalation_desc, + ' + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N'par.data_compression_desc ' ELSE N'null as data_compression_desc ' END + N' + ORDER BY ps.object_id, ps.index_id, ps.partition_number + OPTION ( RECOMPILE ' + + CASE WHEN (PARSENAME(@SQLServerProductVersion, 4) ) > 12 THEN N', min_grant_percent = 1 ' + ELSE N' ' + END + N'); + + SET @d = CONVERT(VARCHAR(19), GETDATE(), 121) + RAISERROR (N''start getting data into #dm_db_index_operational_stats at %s.'',0,1, @d) WITH NOWAIT; + + insert into #dm_db_index_operational_stats + ( + database_id + , object_id + , index_id + , partition_number + , hobt_id + , leaf_insert_count + , leaf_delete_count + , leaf_update_count + , range_scan_count + , singleton_lookup_count + , forwarded_fetch_count + , lob_fetch_in_pages + , lob_fetch_in_bytes + , row_overflow_fetch_in_pages + , row_overflow_fetch_in_bytes + , row_lock_count + , row_lock_wait_count + , row_lock_wait_in_ms + , page_lock_count + , page_lock_wait_count + , page_lock_wait_in_ms + , index_lock_promotion_attempt_count + , index_lock_promotion_count + , page_latch_wait_count + , page_latch_wait_in_ms + , page_io_latch_wait_count + , page_io_latch_wait_in_ms + ) + + select os.database_id + , os.object_id + , os.index_id + , os.partition_number ' + + CASE WHEN (PARSENAME(@SQLServerProductVersion, 4) ) > 12 THEN N', os.hobt_id ' + ELSE N', NULL AS hobt_id ' + END + N' + , os.leaf_insert_count + , os.leaf_delete_count + , os.leaf_update_count + , os.range_scan_count + , os.singleton_lookup_count + , os.forwarded_fetch_count + , os.lob_fetch_in_pages + , os.lob_fetch_in_bytes + , os.row_overflow_fetch_in_pages + , os.row_overflow_fetch_in_bytes + , os.row_lock_count + , os.row_lock_wait_count + , os.row_lock_wait_in_ms + , os.page_lock_count + , os.page_lock_wait_count + , os.page_lock_wait_in_ms + , os.index_lock_promotion_attempt_count + , os.index_lock_promotion_count + , os.page_latch_wait_count + , os.page_latch_wait_in_ms + , os.page_io_latch_wait_count + , os.page_io_latch_wait_in_ms + from ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_index_operational_stats('+ CAST(@DatabaseID AS NVARCHAR(10)) +', NULL, NULL,NULL) AS os + OPTION ( RECOMPILE ' + + CASE WHEN (PARSENAME(@SQLServerProductVersion, 4) ) > 12 THEN N', min_grant_percent = 1 ' + ELSE N' ' + END + N'); + + SET @d = CONVERT(VARCHAR(19), GETDATE(), 121) + RAISERROR (N''finished getting data into #dm_db_index_operational_stats at %s.'',0,1, @d) WITH NOWAIT; + '; + END; + ELSE + BEGIN + RAISERROR (N'Using 2012 syntax to query sys.dm_db_index_operational_stats',0,1) WITH NOWAIT; + --This is the syntax that will be used if you change 2147483647 to 11 on line ~819. + --If you have a lot of paritions and this suddenly starts running for a long time, change it back. + SET @dsql = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + SELECT ' + CAST(@DatabaseID AS NVARCHAR(10)) + N' AS database_id, + ps.object_id, + s.name, + ps.index_id, + ps.partition_number, + ps.row_count, + ps.reserved_page_count * 8. / 1024. AS reserved_MB, + ps.lob_reserved_page_count * 8. / 1024. AS reserved_LOB_MB, + ps.row_overflow_reserved_page_count * 8. / 1024. AS reserved_row_overflow_MB, + le.lock_escalation_desc, + ' + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N'par.data_compression_desc ' ELSE N'null as data_compression_desc' END + N', + SUM(os.leaf_insert_count), + SUM(os.leaf_delete_count), + SUM(os.leaf_update_count), + SUM(os.range_scan_count), + SUM(os.singleton_lookup_count), + SUM(os.forwarded_fetch_count), + SUM(os.lob_fetch_in_pages), + SUM(os.lob_fetch_in_bytes), + SUM(os.row_overflow_fetch_in_pages), + SUM(os.row_overflow_fetch_in_bytes), + SUM(os.row_lock_count), + SUM(os.row_lock_wait_count), + SUM(os.row_lock_wait_in_ms), + SUM(os.page_lock_count), + SUM(os.page_lock_wait_count), + SUM(os.page_lock_wait_in_ms), + SUM(os.index_lock_promotion_attempt_count), + SUM(os.index_lock_promotion_count), + SUM(os.page_latch_wait_count), + SUM(os.page_latch_wait_in_ms), + SUM(os.page_io_latch_wait_count), + SUM(os.page_io_latch_wait_in_ms)'; + + /* Get columnstore dictionary size - more info: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2585 */ + IF EXISTS (SELECT * FROM sys.all_objects WHERE name = 'column_store_dictionaries') + SET @dsql = @dsql + N' COALESCE((SELECT SUM (on_disk_size / 1024.0 / 1024) FROM ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_dictionaries dict WHERE dict.partition_id = ps.partition_id),0) AS reserved_dictionary_MB '; + ELSE + SET @dsql = @dsql + N' 0 AS reserved_dictionary_MB '; + + + SET @dsql = @dsql + N' + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_partition_stats AS ps + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partitions AS par on ps.partition_id=par.partition_id + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects AS so ON ps.object_id = so.object_id + AND so.is_ms_shipped = 0 /*Exclude objects shipped by Microsoft*/ + AND so.type <> ''TF'' /*Exclude table valued functions*/ + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s ON s.schema_id = so.schema_id + OUTER APPLY ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_index_operational_stats(' + + CAST(@DatabaseID AS NVARCHAR(10)) + N', ps.object_id, ps.index_id,ps.partition_number) AS os + OUTER APPLY (SELECT st.lock_escalation_desc + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.tables st + WHERE st.object_id = ps.object_id + AND ps.index_id < 2 ) le + WHERE 1=1 + ' + CASE WHEN @ObjectID IS NOT NULL THEN N'AND so.object_id=' + CAST(@ObjectID AS NVARCHAR(30)) + N' ' ELSE N' ' END + N' + ' + CASE WHEN @Filter = 2 THEN N'AND ps.reserved_page_count * 8./1024. > ' + CAST(@FilterMB AS NVARCHAR(5)) + N' ' ELSE N' ' END + ' + GROUP BY ps.object_id, + s.name, + ps.index_id, + ps.partition_number, + ps.partition_id, + ps.row_count, + ps.reserved_page_count, + ps.lob_reserved_page_count, + ps.row_overflow_reserved_page_count, + le.lock_escalation_desc, + ' + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N'par.data_compression_desc ' ELSE N'null as data_compression_desc ' END + N' + ORDER BY ps.object_id, ps.index_id, ps.partition_number + OPTION ( RECOMPILE ); + '; + END; + + IF @dsql IS NULL + RAISERROR('@dsql is null',16,1); + + RAISERROR (N'Inserting data into #IndexPartitionSanity',0,1) WITH NOWAIT; + IF @Debug = 1 + BEGIN + PRINT SUBSTRING(@dsql, 0, 4000); + PRINT SUBSTRING(@dsql, 4000, 8000); + PRINT SUBSTRING(@dsql, 8000, 12000); + PRINT SUBSTRING(@dsql, 12000, 16000); + PRINT SUBSTRING(@dsql, 16000, 20000); + PRINT SUBSTRING(@dsql, 20000, 24000); + PRINT SUBSTRING(@dsql, 24000, 28000); + PRINT SUBSTRING(@dsql, 28000, 32000); + PRINT SUBSTRING(@dsql, 32000, 36000); + PRINT SUBSTRING(@dsql, 36000, 40000); + END; + EXEC sp_executesql @dsql; + INSERT #IndexPartitionSanity ( [database_id], + [object_id], + [schema_name], + index_id, + partition_number, + row_count, + reserved_MB, + reserved_LOB_MB, + reserved_row_overflow_MB, + lock_escalation_desc, + data_compression_desc, + leaf_insert_count, + leaf_delete_count, + leaf_update_count, + range_scan_count, + singleton_lookup_count, + forwarded_fetch_count, + lob_fetch_in_pages, + lob_fetch_in_bytes, + row_overflow_fetch_in_pages, + row_overflow_fetch_in_bytes, + row_lock_count, + row_lock_wait_count, + row_lock_wait_in_ms, + page_lock_count, + page_lock_wait_count, + page_lock_wait_in_ms, + index_lock_promotion_attempt_count, + index_lock_promotion_count, + page_latch_wait_count, + page_latch_wait_in_ms, + page_io_latch_wait_count, + page_io_latch_wait_in_ms, + reserved_dictionary_MB) + select h.database_id, h.object_id, h.sname, h.index_id, h.partition_number, h.row_count, h.reserved_MB, h.reserved_LOB_MB, h.reserved_row_overflow_MB, h.lock_escalation_desc, h.data_compression_desc, + SUM(os.leaf_insert_count), + SUM(os.leaf_delete_count), + SUM(os.leaf_update_count), + SUM(os.range_scan_count), + SUM(os.singleton_lookup_count), + SUM(os.forwarded_fetch_count), + SUM(os.lob_fetch_in_pages), + SUM(os.lob_fetch_in_bytes), + SUM(os.row_overflow_fetch_in_pages), + SUM(os.row_overflow_fetch_in_bytes), + SUM(os.row_lock_count), + SUM(os.row_lock_wait_count), + SUM(os.row_lock_wait_in_ms), + SUM(os.page_lock_count), + SUM(os.page_lock_wait_count), + SUM(os.page_lock_wait_in_ms), + SUM(os.index_lock_promotion_attempt_count), + SUM(os.index_lock_promotion_count), + SUM(os.page_latch_wait_count), + SUM(os.page_latch_wait_in_ms), + SUM(os.page_io_latch_wait_count), + SUM(os.page_io_latch_wait_in_ms) + ,COALESCE((SELECT SUM (dict.on_disk_size / 1024.0 / 1024) FROM sys.column_store_dictionaries dict WHERE dict.partition_id = h.partition_id),0) AS reserved_dictionary_MB + from #dm_db_partition_stats_etc h + left JOIN #dm_db_index_operational_stats as os ON + h.object_id=os.object_id and h.index_id=os.index_id and h.partition_number=os.partition_number + group by h.database_id, h.object_id, h.sname, h.index_id, h.partition_number, h.partition_id, h.row_count, h.reserved_MB, h.reserved_LOB_MB, h.reserved_row_overflow_MB, h.lock_escalation_desc, h.data_compression_desc + + END; --End Check For @SkipPartitions = 0 + + + IF @Mode NOT IN(1, 2) + BEGIN + RAISERROR (N'Inserting data into #MissingIndexes',0,1) WITH NOWAIT; + SET @dsql=N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + + + SET @dsql = @dsql + ' +WITH + ColumnNamesWithDataTypes AS +( + SELECT + id.index_handle, + id.object_id, + cn.IndexColumnType, + STUFF + ( + ( + SELECT + '', '' + + cn_inner.ColumnName + + '' '' + + N'' {'' + + CASE + WHEN ty.name IN (''varchar'', ''char'') + THEN ty.name + + ''('' + + CASE + WHEN co.max_length = -1 + THEN ''max'' + ELSE CAST(co.max_length AS VARCHAR(25)) + END + + '')'' + WHEN ty.name IN (''nvarchar'', ''nchar'') + THEN ty.name + + ''('' + + CASE + WHEN co.max_length = -1 + THEN ''max'' + ELSE CAST(co.max_length / 2 AS VARCHAR(25)) + END + + '')'' + WHEN ty.name IN (''decimal'', ''numeric'') + THEN ty.name + + ''('' + + CAST(co.precision AS VARCHAR(25)) + + '', '' + + CAST(co.scale AS VARCHAR(25)) + + '')'' + WHEN ty.name IN (''datetime2'') + THEN ty.name + + ''('' + + CAST(co.scale AS VARCHAR(25)) + + '')'' + ELSE ty.name END + ''}'' + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_details AS id_inner + CROSS APPLY + ( + SELECT + LTRIM(RTRIM(v.value(''(./text())[1]'', ''varchar(max)''))) AS ColumnName, + ''Equality'' AS IndexColumnType + FROM + ( + VALUES + (CONVERT(XML, N'''' + REPLACE((SELECT CAST(id_inner.equality_columns AS nvarchar(max)) FOR XML PATH('''')), N'','', N'''') + N'''')) + ) x (n) + CROSS APPLY n.nodes(''x'') node(v) + UNION ALL + SELECT + LTRIM(RTRIM(v.value(N''(./text())[1]'', ''varchar(max)''))) AS ColumnName, + ''Inequality'' AS IndexColumnType + FROM + ( + VALUES + (CONVERT(XML, N'''' + REPLACE((SELECT CAST(id_inner.inequality_columns AS nvarchar(max)) FOR XML PATH('''')), N'','', N'''') + N'''')) + ) x (n) + CROSS APPLY n.nodes(''x'') node(v) + UNION ALL + SELECT + LTRIM(RTRIM(v.value(''(./text())[1]'', ''varchar(max)''))) AS ColumnName, + ''Included'' AS IndexColumnType + FROM + ( + VALUES (CONVERT(XML, N'''' + REPLACE((SELECT CAST(id_inner.included_columns AS nvarchar(max)) FOR XML PATH('''')), N'','', N'''') + N'''')) + ) x (n) + CROSS APPLY n.nodes(''x'') node(v) + ) AS cn_inner + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns AS co + ON co.object_id = id_inner.object_id + AND ''['' + co.name + '']'' = cn_inner.ColumnName + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.types AS ty + ON ty.user_type_id = co.user_type_id + WHERE id_inner.index_handle = id.index_handle + AND id_inner.object_id = id.object_id + AND cn_inner.IndexColumnType = cn.IndexColumnType + FOR XML PATH('''') + ), + 1, + 1, + '''' + ) AS ReplaceColumnNames + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_details AS id + CROSS APPLY + ( + SELECT + LTRIM(RTRIM(v.value(''(./text())[1]'', ''varchar(max)''))) AS ColumnName, + ''Equality'' AS IndexColumnType + FROM + ( + VALUES (CONVERT(XML, N'''' + REPLACE((SELECT CAST(id.equality_columns AS nvarchar(max)) FOR XML PATH('''')), N'','', N'''') + N'''')) + ) x (n) + CROSS APPLY n.nodes(''x'') node(v) + UNION ALL + SELECT + LTRIM(RTRIM(v.value(''(./text())[1]'', ''varchar(max)''))) AS ColumnName, + ''Inequality'' AS IndexColumnType + FROM + ( + VALUES (CONVERT(XML, N'''' + REPLACE((SELECT CAST(id.inequality_columns AS nvarchar(max)) FOR XML PATH('''')), N'','', N'''') + N'''')) + ) x (n) + CROSS APPLY n.nodes(''x'') node(v) + UNION ALL + SELECT + LTRIM(RTRIM(v.value(''(./text())[1]'', ''varchar(max)''))) AS ColumnName, + ''Included'' AS IndexColumnType + FROM + ( + VALUES (CONVERT(XML, N'''' + REPLACE((SELECT CAST(id.included_columns AS nvarchar(max)) FOR XML PATH('''')), N'','', N'''') + N'''')) + ) x (n) + CROSS APPLY n.nodes(''x'') node(v) + )AS cn + GROUP BY + id.index_handle, + id.object_id, + cn.IndexColumnType +) +SELECT + * +INTO #ColumnNamesWithDataTypes +FROM ColumnNamesWithDataTypes +OPTION(RECOMPILE); + +SELECT + id.database_id, + id.object_id, + @i_DatabaseName, + sc.[name], + so.[name], + id.statement, + gs.avg_total_user_cost, + gs.avg_user_impact, + gs.user_seeks, + gs.user_scans, + gs.unique_compiles, + id.equality_columns, + id.inequality_columns, + id.included_columns, + ( + SELECT + ColumnNamesWithDataTypes.ReplaceColumnNames + FROM #ColumnNamesWithDataTypes ColumnNamesWithDataTypes + WHERE ColumnNamesWithDataTypes.index_handle = id.index_handle + AND ColumnNamesWithDataTypes.object_id = id.object_id + AND ColumnNamesWithDataTypes.IndexColumnType = ''Equality'' + ) AS equality_columns_with_data_type, + ( + SELECT + ColumnNamesWithDataTypes.ReplaceColumnNames + FROM #ColumnNamesWithDataTypes ColumnNamesWithDataTypes + WHERE ColumnNamesWithDataTypes.index_handle = id.index_handle + AND ColumnNamesWithDataTypes.object_id = id.object_id + AND ColumnNamesWithDataTypes.IndexColumnType = ''Inequality'' + ) AS inequality_columns_with_data_type, + ( + SELECT ColumnNamesWithDataTypes.ReplaceColumnNames + FROM #ColumnNamesWithDataTypes ColumnNamesWithDataTypes + WHERE ColumnNamesWithDataTypes.index_handle = id.index_handle + AND ColumnNamesWithDataTypes.object_id = id.object_id + AND ColumnNamesWithDataTypes.IndexColumnType = ''Included'' + ) AS included_columns_with_data_type,'; + + /* Get the sample query plan if it's available, and if there are less than 1,000 rows in the DMV: */ + IF NOT EXISTS + ( + SELECT + 1/0 + FROM sys.all_objects AS o + WHERE o.name = 'dm_db_missing_index_group_stats_query' + ) + SELECT + @dsql += N' + NULL AS sample_query_plan' + ELSE + BEGIN + /* The DMV is only supposed to have 600 rows in it. If it's got more, + they could see performance slowdowns - see Github #3085. */ + DECLARE @MissingIndexPlans BIGINT; + SET @StringToExecute = N'SELECT @MissingIndexPlans = COUNT(*) FROM ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_group_stats_query;' + EXEC sp_executesql @StringToExecute, N'@MissingIndexPlans BIGINT OUT', @MissingIndexPlans OUT; + + IF @MissingIndexPlans > 1000 + BEGIN + SELECT @dsql += N' + NULL AS sample_query_plan /* Over 1000 plans found, skipping */'; + RAISERROR (N'Over 1000 plans found in sys.dm_db_missing_index_group_stats_query - your SQL Server is hitting a bug: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/3085',0,1) WITH NOWAIT; + END + ELSE + SELECT + @dsql += N' + sample_query_plan = + ( + SELECT TOP (1) + p.query_plan + FROM sys.dm_db_missing_index_group_stats gs + CROSS APPLY + ( + SELECT TOP (1) + s.plan_handle + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_group_stats_query q + INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.dm_exec_query_stats s + ON q.query_plan_hash = s.query_plan_hash + WHERE gs.group_handle = q.group_handle + ORDER BY + (q.user_seeks + q.user_scans) DESC, + s.total_logical_reads DESC + ) q2 + CROSS APPLY sys.dm_exec_query_plan(q2.plan_handle) p + WHERE ig.index_group_handle = gs.group_handle + )' + END + + + + SET @dsql = @dsql + N' +FROM ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_groups ig +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_details id + ON ig.index_handle = id.index_handle +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_group_stats gs + ON ig.index_group_handle = gs.group_handle +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects so + ON id.object_id=so.object_id +JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas sc + ON so.schema_id=sc.schema_id +WHERE id.database_id = ' + CAST(@DatabaseID AS NVARCHAR(30)) + +CASE + WHEN @ObjectID IS NULL + THEN N'' + ELSE N' +AND id.object_id = ' + CAST(@ObjectID AS NVARCHAR(30)) +END + +N' +OPTION (RECOMPILE);'; + + IF @dsql IS NULL + RAISERROR('@dsql is null',16,1); + IF @Debug = 1 + BEGIN + PRINT SUBSTRING(@dsql, 0, 4000); + PRINT SUBSTRING(@dsql, 4000, 8000); + PRINT SUBSTRING(@dsql, 8000, 12000); + PRINT SUBSTRING(@dsql, 12000, 16000); + PRINT SUBSTRING(@dsql, 16000, 20000); + PRINT SUBSTRING(@dsql, 20000, 24000); + PRINT SUBSTRING(@dsql, 24000, 28000); + PRINT SUBSTRING(@dsql, 28000, 32000); + PRINT SUBSTRING(@dsql, 32000, 36000); + PRINT SUBSTRING(@dsql, 36000, 40000); + END; + INSERT #MissingIndexes ( [database_id], [object_id], [database_name], [schema_name], [table_name], [statement], avg_total_user_cost, + avg_user_impact, user_seeks, user_scans, unique_compiles, equality_columns, + inequality_columns, included_columns, equality_columns_with_data_type, inequality_columns_with_data_type, + included_columns_with_data_type, sample_query_plan) + EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; + END; + + SET @dsql = N' + SELECT DB_ID(N' + QUOTENAME(@DatabaseName,'''') + N') AS [database_id], + @i_DatabaseName AS database_name, + s.name, + fk_object.name AS foreign_key_name, + parent_object.[object_id] AS parent_object_id, + parent_object.name AS parent_object_name, + referenced_object.[object_id] AS referenced_object_id, + referenced_object.name AS referenced_object_name, + fk.is_disabled, + fk.is_not_trusted, + fk.is_not_for_replication, + parent.fk_columns, + referenced.fk_columns, + [update_referential_action_desc], + [delete_referential_action_desc] + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.foreign_keys fk + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects fk_object ON fk.object_id=fk_object.object_id + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects parent_object ON fk.parent_object_id=parent_object.object_id + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects referenced_object ON fk.referenced_object_id=referenced_object.object_id + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s ON fk.schema_id=s.schema_id + CROSS APPLY ( SELECT STUFF( (SELECT N'', '' + c_parent.name AS fk_columns + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.foreign_key_columns fkc + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns c_parent ON fkc.parent_object_id=c_parent.[object_id] + AND fkc.parent_column_id=c_parent.column_id + WHERE fk.parent_object_id=fkc.parent_object_id + AND fk.[object_id]=fkc.constraint_object_id + ORDER BY fkc.constraint_column_id + FOR XML PATH('''') , + TYPE).value(''.'', ''nvarchar(max)''), 1, 1, '''')/*This is how we remove the first comma*/ ) parent ( fk_columns ) + CROSS APPLY ( SELECT STUFF( (SELECT N'', '' + c_referenced.name AS fk_columns + FROM ' + QUOTENAME(@DatabaseName) + N'.sys. foreign_key_columns fkc + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns c_referenced ON fkc.referenced_object_id=c_referenced.[object_id] + AND fkc.referenced_column_id=c_referenced.column_id + WHERE fk.referenced_object_id=fkc.referenced_object_id + and fk.[object_id]=fkc.constraint_object_id + ORDER BY fkc.constraint_column_id /*order by col name, we don''t have anything better*/ + FOR XML PATH('''') , + TYPE).value(''.'', ''nvarchar(max)''), 1, 1, '''') ) referenced ( fk_columns ) + ' + CASE WHEN @ObjectID IS NOT NULL THEN + 'WHERE fk.parent_object_id=' + CAST(@ObjectID AS NVARCHAR(30)) + N' OR fk.referenced_object_id=' + CAST(@ObjectID AS NVARCHAR(30)) + N' ' + ELSE N' ' END + ' + ORDER BY parent_object_name, foreign_key_name + OPTION (RECOMPILE);'; + IF @dsql IS NULL + RAISERROR('@dsql is null',16,1); + + RAISERROR (N'Inserting data into #ForeignKeys',0,1) WITH NOWAIT; + IF @Debug = 1 + BEGIN + PRINT SUBSTRING(@dsql, 0, 4000); + PRINT SUBSTRING(@dsql, 4000, 8000); + PRINT SUBSTRING(@dsql, 8000, 12000); + PRINT SUBSTRING(@dsql, 12000, 16000); + PRINT SUBSTRING(@dsql, 16000, 20000); + PRINT SUBSTRING(@dsql, 20000, 24000); + PRINT SUBSTRING(@dsql, 24000, 28000); + PRINT SUBSTRING(@dsql, 28000, 32000); + PRINT SUBSTRING(@dsql, 32000, 36000); + PRINT SUBSTRING(@dsql, 36000, 40000); + END; + INSERT #ForeignKeys ( [database_id], [database_name], [schema_name], foreign_key_name, parent_object_id,parent_object_name, referenced_object_id, referenced_object_name, + is_disabled, is_not_trusted, is_not_for_replication, parent_fk_columns, referenced_fk_columns, + [update_referential_action_desc], [delete_referential_action_desc] ) + EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; + + IF @Mode NOT IN(1, 2) + BEGIN + SET @dsql = N' + SELECT + DB_ID(N' + QUOTENAME(@DatabaseName,'''') + N') AS [database_id], + @i_DatabaseName AS database_name, + foreign_key_schema = + s.name, + foreign_key_name = + fk.name, + foreign_key_table = + OBJECT_NAME(fk.parent_object_id, DB_ID(N' + QUOTENAME(@DatabaseName,'''') + N')), + fk.parent_object_id, + foreign_key_referenced_table = + OBJECT_NAME(fk.referenced_object_id, DB_ID(N' + QUOTENAME(@DatabaseName,'''') + N')), + fk.referenced_object_id + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.foreign_keys fk + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s + ON s.schema_id = fk.schema_id + WHERE fk.is_disabled = 0 + AND EXISTS + ( + SELECT + 1/0 + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.foreign_key_columns fkc + WHERE fkc.constraint_object_id = fk.object_id + AND NOT EXISTS + ( + SELECT + 1/0 + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.index_columns ic + WHERE ic.object_id = fkc.parent_object_id + AND ic.column_id = fkc.parent_column_id + AND ic.index_column_id = fkc.constraint_column_id + ) + ) + OPTION (RECOMPILE);' + IF @dsql IS NULL + RAISERROR('@dsql is null',16,1); + + RAISERROR (N'Inserting data into #UnindexedForeignKeys',0,1) WITH NOWAIT; + IF @Debug = 1 + BEGIN + PRINT SUBSTRING(@dsql, 0, 4000); + PRINT SUBSTRING(@dsql, 4000, 8000); + PRINT SUBSTRING(@dsql, 8000, 12000); + PRINT SUBSTRING(@dsql, 12000, 16000); + PRINT SUBSTRING(@dsql, 16000, 20000); + PRINT SUBSTRING(@dsql, 20000, 24000); + PRINT SUBSTRING(@dsql, 24000, 28000); + PRINT SUBSTRING(@dsql, 28000, 32000); + PRINT SUBSTRING(@dsql, 32000, 36000); + PRINT SUBSTRING(@dsql, 36000, 40000); + END; + + INSERT + #UnindexedForeignKeys + ( + database_id, + database_name, + schema_name, + foreign_key_name, + parent_object_name, + parent_object_id, + referenced_object_name, + referenced_object_id + ) + EXEC sys.sp_executesql + @dsql, + N'@i_DatabaseName sysname', + @DatabaseName; + END; + + + IF @Mode NOT IN(1, 2) + BEGIN + IF @SkipStatistics = 0 /* AND DB_NAME() = @DatabaseName /* Can only get stats in the current database - see https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/1947 */ */ + BEGIN + IF ((PARSENAME(@SQLServerProductVersion, 4) >= 12) + OR (PARSENAME(@SQLServerProductVersion, 4) = 11 AND PARSENAME(@SQLServerProductVersion, 2) >= 3000) + OR (PARSENAME(@SQLServerProductVersion, 4) = 10 AND PARSENAME(@SQLServerProductVersion, 3) = 50 AND PARSENAME(@SQLServerProductVersion, 2) >= 2500)) + 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, + 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) + 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, + ISNULL(i.name, ''System Or User Statistic'') AS index_name, + ca.column_names AS column_names, + s.name AS statistics_name, + CONVERT(DATETIME, ddsp.last_updated) AS last_statistics_update, + DATEDIFF(DAY, ddsp.last_updated, GETDATE()) AS days_since_last_stats_update, + ddsp.rows, + ddsp.rows_sampled, + CAST(ddsp.rows_sampled / ( 1. * NULLIF(ddsp.rows, 0) ) * 100 AS DECIMAL(18, 1)) AS percent_sampled, + ddsp.steps AS histogram_steps, + ddsp.modification_counter, + CASE WHEN ddsp.modification_counter > 0 + THEN CAST(ddsp.modification_counter / ( 1. * NULLIF(ddsp.rows, 0) ) * 100 AS DECIMAL(18, 1)) + ELSE ddsp.modification_counter + END AS percent_modifications, + CASE WHEN ddsp.rows < 500 THEN 500 + ELSE CAST(( ddsp.rows * .20 ) + 500 AS INT) + 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 + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.stats AS s + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects obj + ON s.object_id = obj.object_id + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas sch + ON sch.schema_id = obj.schema_id + LEFT JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.indexes AS i + ON i.object_id = s.object_id + AND i.index_id = s.stats_id + OUTER APPLY ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_stats_properties(s.object_id, s.stats_id) AS ddsp + CROSS APPLY ( SELECT STUFF((SELECT '', '' + c.name + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.stats_columns AS sc + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns AS c + ON sc.column_id = c.column_id AND sc.object_id = c.object_id + WHERE sc.stats_id = s.stats_id AND sc.object_id = s.object_id + ORDER BY sc.stats_column_id + FOR XML PATH(''''), TYPE).value(''.'', ''nvarchar(max)''), 1, 2, '''') + ) ca (column_names) + WHERE obj.is_ms_shipped = 0 + OPTION (RECOMPILE);'; + + IF @dsql IS NULL + RAISERROR('@dsql is null',16,1); + + RAISERROR (N'Inserting data into #Statistics',0,1) WITH NOWAIT; + IF @Debug = 1 + BEGIN + PRINT SUBSTRING(@dsql, 0, 4000); + PRINT SUBSTRING(@dsql, 4000, 8000); + PRINT SUBSTRING(@dsql, 8000, 12000); + PRINT SUBSTRING(@dsql, 12000, 16000); + PRINT SUBSTRING(@dsql, 16000, 20000); + PRINT SUBSTRING(@dsql, 20000, 24000); + PRINT SUBSTRING(@dsql, 24000, 28000); + PRINT SUBSTRING(@dsql, 28000, 32000); + PRINT SUBSTRING(@dsql, 32000, 36000); + PRINT SUBSTRING(@dsql, 36000, 40000); + END; + + EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; + END; + ELSE + 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, + 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) + 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, + ISNULL(i.name, ''System Or User Statistic'') AS index_name, + ca.column_names AS column_names, + s.name AS statistics_name, + CONVERT(DATETIME, STATS_DATE(s.object_id, s.stats_id)) AS last_statistics_update, + DATEDIFF(DAY, STATS_DATE(s.object_id, s.stats_id), GETDATE()) AS days_since_last_stats_update, + si.rowcnt, + si.rowmodctr, + CASE WHEN si.rowmodctr > 0 THEN CAST(si.rowmodctr / ( 1. * NULLIF(si.rowcnt, 0) ) * 100 AS DECIMAL(18, 1)) + ELSE si.rowmodctr + END AS percent_modifications, + CASE WHEN si.rowcnt < 500 THEN 500 + ELSE CAST(( si.rowcnt * .20 ) + 500 AS INT) + 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, + ' + + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' + THEN N's.has_filter, + s.filter_definition' + ELSE N'NULL AS has_filter, + NULL AS filter_definition' END + + N' + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.stats AS s + INNER HASH JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.sysindexes si + ON si.name = s.name AND s.object_id = si.id + INNER HASH JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects obj + ON s.object_id = obj.object_id + INNER HASH JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas sch + ON sch.schema_id = obj.schema_id + LEFT HASH JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.indexes AS i + ON i.object_id = s.object_id + AND i.index_id = s.stats_id + CROSS APPLY ( SELECT STUFF((SELECT '', '' + c.name + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.stats_columns AS sc + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns AS c + ON sc.column_id = c.column_id AND sc.object_id = c.object_id + WHERE sc.stats_id = s.stats_id AND sc.object_id = s.object_id + ORDER BY sc.stats_column_id + FOR XML PATH(''''), TYPE).value(''.'', ''nvarchar(max)''), 1, 2, '''') + ) ca (column_names) + WHERE obj.is_ms_shipped = 0 + AND si.rowcnt > 0 + OPTION (RECOMPILE);'; + + IF @dsql IS NULL + RAISERROR('@dsql is null',16,1); + + RAISERROR (N'Inserting data into #Statistics',0,1) WITH NOWAIT; + IF @Debug = 1 + BEGIN + PRINT SUBSTRING(@dsql, 0, 4000); + PRINT SUBSTRING(@dsql, 4000, 8000); + PRINT SUBSTRING(@dsql, 8000, 12000); + PRINT SUBSTRING(@dsql, 12000, 16000); + PRINT SUBSTRING(@dsql, 16000, 20000); + PRINT SUBSTRING(@dsql, 20000, 24000); + PRINT SUBSTRING(@dsql, 24000, 28000); + PRINT SUBSTRING(@dsql, 28000, 32000); + PRINT SUBSTRING(@dsql, 32000, 36000); + PRINT SUBSTRING(@dsql, 36000, 40000); + END; + + EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; + END; + END; + END; + + IF @Mode NOT IN(1, 2) + BEGIN + IF (PARSENAME(@SQLServerProductVersion, 4) >= 10) + BEGIN + RAISERROR (N'Gathering Computed Column Info.',0,1) WITH NOWAIT; + SET @dsql=N'SELECT DB_ID(@i_DatabaseName) AS [database_id], + @i_DatabaseName AS database_name, + t.name AS table_name, + s.name AS schema_name, + c.name AS column_name, + cc.is_nullable, + cc.definition, + cc.uses_database_collation, + cc.is_persisted, + cc.is_computed, + CASE WHEN cc.definition LIKE ''%|].|[%'' ESCAPE ''|'' THEN 1 ELSE 0 END AS is_function, + ''ALTER TABLE '' + QUOTENAME(s.name) + ''.'' + QUOTENAME(t.name) + + '' ADD '' + QUOTENAME(c.name) + '' AS '' + cc.definition + + CASE WHEN is_persisted = 1 THEN '' PERSISTED'' ELSE '''' END + '';'' COLLATE DATABASE_DEFAULT AS [column_definition] + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.computed_columns AS cc + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns AS c + ON cc.object_id = c.object_id + AND cc.column_id = c.column_id + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.tables AS t + ON t.object_id = cc.object_id + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s + ON s.schema_id = t.schema_id + OPTION (RECOMPILE);'; + + IF @dsql IS NULL RAISERROR('@dsql is null',16,1); + + INSERT #ComputedColumns + ( database_id, [database_name], table_name, schema_name, column_name, is_nullable, definition, + uses_database_collation, is_persisted, is_computed, is_function, column_definition ) + EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; + END; + END; + + IF @Mode NOT IN(1, 2) + BEGIN + RAISERROR (N'Gathering Trace Flag Information',0,1) WITH NOWAIT; + INSERT #TraceStatus + EXEC ('DBCC TRACESTATUS(-1) WITH NO_INFOMSGS'); + + IF (PARSENAME(@SQLServerProductVersion, 4) >= 13) + BEGIN + RAISERROR (N'Gathering Temporal Table Info',0,1) WITH NOWAIT; + SET @dsql=N'SELECT ' + QUOTENAME(@DatabaseName,'''') + N' AS database_name, + DB_ID(N' + QUOTENAME(@DatabaseName,'''') + N') AS [database_id], + s.name AS schema_name, + t.name AS table_name, + oa.hsn as history_schema_name, + oa.htn AS history_table_name, + c1.name AS start_column_name, + c2.name AS end_column_name, + p.name AS period_name + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.periods AS p + INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.tables AS t + ON p.object_id = t.object_id + INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns AS c1 + ON t.object_id = c1.object_id + AND p.start_column_id = c1.column_id + INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns AS c2 + ON t.object_id = c2.object_id + AND p.end_column_id = c2.column_id + INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s + ON t.schema_id = s.schema_id + CROSS APPLY ( SELECT s2.name as hsn, t2.name htn + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.tables AS t2 + INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s2 + ON t2.schema_id = s2.schema_id + WHERE t2.object_id = t.history_table_id + AND t2.temporal_type = 1 /*History table*/ ) AS oa + WHERE t.temporal_type IN ( 2, 4 ) /*BOL currently points to these types, but has no definition for 4*/ + OPTION (RECOMPILE); + '; + + IF @dsql IS NULL + RAISERROR('@dsql is null',16,1); + + INSERT #TemporalTables ( database_name, database_id, schema_name, table_name, history_schema_name, + history_table_name, start_column_name, end_column_name, period_name ) + + EXEC sp_executesql @dsql; + END; + + SET @dsql=N'SELECT DB_ID(@i_DatabaseName) AS [database_id], + @i_DatabaseName AS database_name, + t.name AS table_name, + s.name AS schema_name, + cc.name AS constraint_name, + cc.is_disabled, + cc.definition, + cc.uses_database_collation, + cc.is_not_trusted, + CASE WHEN cc.definition LIKE ''%|].|[%'' ESCAPE ''|'' THEN 1 ELSE 0 END AS is_function, + ''ALTER TABLE '' + QUOTENAME(s.name) + ''.'' + QUOTENAME(t.name) + + '' ADD CONSTRAINT '' + QUOTENAME(cc.name) + '' CHECK '' + cc.definition + '';'' COLLATE DATABASE_DEFAULT AS [column_definition] + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.check_constraints AS cc + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.tables AS t + ON t.object_id = cc.parent_object_id + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s + ON s.schema_id = t.schema_id + OPTION (RECOMPILE);'; + + INSERT #CheckConstraints + ( database_id, [database_name], table_name, schema_name, constraint_name, is_disabled, definition, + uses_database_collation, is_not_trusted, is_function, column_definition ) + EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; + + + IF @Mode NOT IN(1, 2) + BEGIN + SET @dsql=N'SELECT DB_ID(@i_DatabaseName) AS [database_id], + @i_DatabaseName AS database_name, + s.name AS missing_schema_name, + t.name AS missing_table_name, + i.name AS missing_index_name, + c.name AS missing_column_name + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.sql_expression_dependencies AS sed + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.tables AS t + ON t.object_id = sed.referenced_id + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s + ON t.schema_id = s.schema_id + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.indexes AS i + ON i.object_id = sed.referenced_id + AND i.index_id = sed.referencing_minor_id + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns AS c + ON c.object_id = sed.referenced_id + AND c.column_id = sed.referenced_minor_id + WHERE sed.referencing_class = 7 + AND sed.referenced_class = 1 + AND i.has_filter = 1 + AND NOT EXISTS ( SELECT 1/0 + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.index_columns AS ic + WHERE ic.index_id = sed.referencing_minor_id + AND ic.column_id = sed.referenced_minor_id + AND ic.object_id = sed.referenced_id ) + OPTION(RECOMPILE);' + + INSERT #FilteredIndexes ( database_id, database_name, schema_name, table_name, index_name, column_name ) + EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; + END; + END; + +END; +END TRY +BEGIN CATCH + RAISERROR (N'Failure populating temp tables.', 0,1) WITH NOWAIT; + + IF @dsql IS NOT NULL + BEGIN + SET @msg= 'Last @dsql: ' + @dsql; + RAISERROR(@msg, 0, 1) WITH NOWAIT; + END; + + SELECT @msg = @DatabaseName + N' database failed to process. ' + ERROR_MESSAGE(), @ErrorSeverity = ERROR_SEVERITY(), @ErrorState = ERROR_STATE(); + RAISERROR (@msg,@ErrorSeverity, @ErrorState )WITH NOWAIT; + + + WHILE @@trancount > 0 + ROLLBACK; + + RETURN; +END CATCH; + FETCH NEXT FROM c1 INTO @DatabaseName; +END; +DEALLOCATE c1; + + + + + + +---------------------------------------- +--STEP 2: PREP THE TEMP TABLES +--EVERY QUERY AFTER THIS GOES AGAINST TEMP TABLES ONLY. +---------------------------------------- + +RAISERROR (N'Updating #IndexSanity.key_column_names',0,1) WITH NOWAIT; +UPDATE #IndexSanity +SET key_column_names = D1.key_column_names +FROM #IndexSanity si + CROSS APPLY ( SELECT RTRIM(STUFF( (SELECT N', ' + c.column_name + + N' {' + system_type_name + + CASE max_length WHEN -1 THEN N' (max)' ELSE + CASE + WHEN system_type_name IN (N'char',N'varchar',N'binary',N'varbinary') THEN N' (' + CAST(max_length AS NVARCHAR(20)) + N')' + WHEN system_type_name IN (N'nchar',N'nvarchar') THEN N' (' + CAST(max_length/2 AS NVARCHAR(20)) + N')' + ELSE N' ' + CAST(max_length AS NVARCHAR(50)) + END + END + + N'}' + AS col_definition + FROM #IndexColumns c + WHERE c.database_id= si.database_id + AND c.schema_name = si.schema_name + AND c.object_id = si.object_id + AND c.index_id = si.index_id + AND c.is_included_column = 0 /*Just Keys*/ + AND c.key_ordinal > 0 /*Ignore non-key columns, such as partitioning keys*/ + ORDER BY c.object_id, c.index_id, c.key_ordinal + FOR XML PATH('') ,TYPE).value('.', 'nvarchar(max)'), 1, 1, '')) + ) D1 ( key_column_names ); + +RAISERROR (N'Updating #IndexSanity.partition_key_column_name',0,1) WITH NOWAIT; +UPDATE #IndexSanity +SET partition_key_column_name = D1.partition_key_column_name +FROM #IndexSanity si + CROSS APPLY ( SELECT RTRIM(STUFF( (SELECT N', ' + c.column_name AS col_definition + FROM #IndexColumns c + WHERE c.database_id= si.database_id + AND c.schema_name = si.schema_name + AND c.object_id = si.object_id + AND c.index_id = si.index_id + AND c.partition_ordinal <> 0 /*Just Partitioned Keys*/ + ORDER BY c.object_id, c.index_id, c.key_ordinal + FOR XML PATH('') , TYPE).value('.', 'nvarchar(max)'), 1, 1,''))) D1 + ( partition_key_column_name ); + +RAISERROR (N'Updating #IndexSanity.key_column_names_with_sort_order',0,1) WITH NOWAIT; +UPDATE #IndexSanity +SET key_column_names_with_sort_order = D2.key_column_names_with_sort_order +FROM #IndexSanity si + CROSS APPLY ( SELECT RTRIM(STUFF( (SELECT N', ' + c.column_name + CASE c.is_descending_key + WHEN 1 THEN N' DESC' + ELSE N'' + END + + N' {' + system_type_name + + CASE max_length WHEN -1 THEN N' (max)' ELSE + CASE + WHEN system_type_name IN (N'char',N'varchar',N'binary',N'varbinary') THEN N' (' + CAST(max_length AS NVARCHAR(20)) + N')' + WHEN system_type_name IN (N'nchar',N'nvarchar') THEN N' (' + CAST(max_length/2 AS NVARCHAR(20)) + N')' + ELSE N' ' + CAST(max_length AS NVARCHAR(50)) + END + END + + N'}' + AS col_definition + FROM #IndexColumns c + WHERE c.database_id= si.database_id + AND c.schema_name = si.schema_name + AND c.object_id = si.object_id + AND c.index_id = si.index_id + AND c.is_included_column = 0 /*Just Keys*/ + AND c.key_ordinal > 0 /*Ignore non-key columns, such as partitioning keys*/ + ORDER BY c.object_id, c.index_id, c.key_ordinal + FOR XML PATH('') , TYPE).value('.', 'nvarchar(max)'), 1, 1, '')) + ) D2 ( key_column_names_with_sort_order ); + +RAISERROR (N'Updating #IndexSanity.key_column_names_with_sort_order_no_types (for create tsql)',0,1) WITH NOWAIT; +UPDATE #IndexSanity +SET key_column_names_with_sort_order_no_types = D2.key_column_names_with_sort_order_no_types +FROM #IndexSanity si + CROSS APPLY ( SELECT RTRIM(STUFF( (SELECT N', ' + QUOTENAME(c.column_name) + CASE c.is_descending_key + WHEN 1 THEN N' DESC' + ELSE N'' + END AS col_definition + FROM #IndexColumns c + WHERE c.database_id= si.database_id + AND c.schema_name = si.schema_name + AND c.object_id = si.object_id + AND c.index_id = si.index_id + AND c.is_included_column = 0 /*Just Keys*/ + AND c.key_ordinal > 0 /*Ignore non-key columns, such as partitioning keys*/ + ORDER BY c.object_id, c.index_id, c.key_ordinal + FOR XML PATH('') , TYPE).value('.', 'nvarchar(max)'), 1, 1, '')) + ) D2 ( key_column_names_with_sort_order_no_types ); + +RAISERROR (N'Updating #IndexSanity.include_column_names',0,1) WITH NOWAIT; +UPDATE #IndexSanity +SET include_column_names = D3.include_column_names +FROM #IndexSanity si + CROSS APPLY ( SELECT RTRIM(STUFF( (SELECT N', ' + c.column_name + + N' {' + system_type_name + + CASE max_length WHEN -1 THEN N' (max)' ELSE + CASE + WHEN system_type_name IN (N'char',N'varchar',N'binary',N'varbinary') THEN N' (' + CAST(max_length AS NVARCHAR(20)) + N')' + WHEN system_type_name IN (N'nchar',N'nvarchar') THEN N' (' + CAST(max_length/2 AS NVARCHAR(20)) + N')' + ELSE N' ' + CAST(max_length AS NVARCHAR(50)) + END + END + + N'}' + FROM #IndexColumns c + WHERE c.database_id= si.database_id + AND c.schema_name = si.schema_name + AND c.object_id = si.object_id + AND c.index_id = si.index_id + AND c.is_included_column = 1 /*Just includes*/ + ORDER BY c.column_name /*Order doesn't matter in includes, + this is here to make rows easy to compare.*/ + FOR XML PATH('') , TYPE).value('.', 'nvarchar(max)'), 1, 1, '')) + ) D3 ( include_column_names ); + +RAISERROR (N'Updating #IndexSanity.include_column_names_no_types (for create tsql)',0,1) WITH NOWAIT; +UPDATE #IndexSanity +SET include_column_names_no_types = D3.include_column_names_no_types +FROM #IndexSanity si + CROSS APPLY ( SELECT RTRIM(STUFF( (SELECT N', ' + QUOTENAME(c.column_name) + FROM #IndexColumns c + WHERE c.database_id= si.database_id + AND c.schema_name = si.schema_name + AND c.object_id = si.object_id + AND c.index_id = si.index_id + AND c.is_included_column = 1 /*Just includes*/ + ORDER BY c.column_name /*Order doesn't matter in includes, + this is here to make rows easy to compare.*/ + FOR XML PATH('') , TYPE).value('.', 'nvarchar(max)'), 1, 1, '')) + ) D3 ( include_column_names_no_types ); + +RAISERROR (N'Updating #IndexSanity.count_key_columns and count_include_columns',0,1) WITH NOWAIT; +UPDATE #IndexSanity +SET count_included_columns = D4.count_included_columns, + count_key_columns = D4.count_key_columns +FROM #IndexSanity si + CROSS APPLY ( SELECT SUM(CASE WHEN is_included_column = 'true' THEN 1 + ELSE 0 + END) AS count_included_columns, + SUM(CASE WHEN is_included_column = 'false' AND c.key_ordinal > 0 THEN 1 + ELSE 0 + END) AS count_key_columns + FROM #IndexColumns c + WHERE c.database_id= si.database_id + AND c.schema_name = si.schema_name + AND c.object_id = si.object_id + AND c.index_id = si.index_id + ) AS D4 ( count_included_columns, count_key_columns ); + +RAISERROR (N'Updating index_sanity_id on #IndexPartitionSanity',0,1) WITH NOWAIT; +UPDATE #IndexPartitionSanity +SET index_sanity_id = i.index_sanity_id +FROM #IndexPartitionSanity ps + JOIN #IndexSanity i ON ps.[object_id] = i.[object_id] + AND ps.index_id = i.index_id + AND i.database_id = ps.database_id + AND i.schema_name = ps.schema_name; + + +RAISERROR (N'Inserting data into #IndexSanitySize',0,1) WITH NOWAIT; +INSERT #IndexSanitySize ( [index_sanity_id], [database_id], [schema_name], [lock_escalation_desc], partition_count, total_rows, total_reserved_MB, + total_reserved_LOB_MB, total_reserved_row_overflow_MB, total_reserved_dictionary_MB, total_range_scan_count, + total_singleton_lookup_count, total_leaf_delete_count, total_leaf_update_count, + total_forwarded_fetch_count,total_row_lock_count, + total_row_lock_wait_count, total_row_lock_wait_in_ms, avg_row_lock_wait_in_ms, + total_page_lock_count, total_page_lock_wait_count, total_page_lock_wait_in_ms, + avg_page_lock_wait_in_ms, total_index_lock_promotion_attempt_count, + total_index_lock_promotion_count, data_compression_desc, + page_latch_wait_count, page_latch_wait_in_ms, page_io_latch_wait_count, page_io_latch_wait_in_ms) + SELECT index_sanity_id, ipp.database_id, ipp.schema_name, ipp.lock_escalation_desc, + COUNT(*), SUM(row_count), SUM(reserved_MB), + SUM(reserved_LOB_MB) - SUM(reserved_dictionary_MB), /* Subtract columnstore dictionaries from LOB data */ + SUM(reserved_row_overflow_MB), + SUM(reserved_dictionary_MB), + SUM(range_scan_count), + SUM(singleton_lookup_count), + SUM(leaf_delete_count), + SUM(leaf_update_count), + SUM(forwarded_fetch_count), + SUM(row_lock_count), + SUM(row_lock_wait_count), + SUM(row_lock_wait_in_ms), + CASE WHEN SUM(row_lock_wait_in_ms) > 0 THEN + SUM(row_lock_wait_in_ms)/(1.*SUM(row_lock_wait_count)) + ELSE 0 END AS avg_row_lock_wait_in_ms, + SUM(page_lock_count), + SUM(page_lock_wait_count), + SUM(page_lock_wait_in_ms), + CASE WHEN SUM(page_lock_wait_in_ms) > 0 THEN + SUM(page_lock_wait_in_ms)/(1.*SUM(page_lock_wait_count)) + ELSE 0 END AS avg_page_lock_wait_in_ms, + SUM(index_lock_promotion_attempt_count), + SUM(index_lock_promotion_count), + LEFT(MAX(data_compression_info.data_compression_rollup),4000), + SUM(page_latch_wait_count), + SUM(page_latch_wait_in_ms), + SUM(page_io_latch_wait_count), + SUM(page_io_latch_wait_in_ms) + FROM #IndexPartitionSanity ipp + /* individual partitions can have distinct compression settings, just roll them into a list here*/ + OUTER APPLY (SELECT STUFF(( + SELECT N', ' + data_compression_desc + FROM #IndexPartitionSanity ipp2 + WHERE ipp.[object_id]=ipp2.[object_id] + AND ipp.[index_id]=ipp2.[index_id] + AND ipp.database_id = ipp2.database_id + AND ipp.schema_name = ipp2.schema_name + ORDER BY ipp2.partition_number + FOR XML PATH(''),TYPE).value('.', 'nvarchar(max)'), 1, 1, '')) + data_compression_info(data_compression_rollup) + GROUP BY index_sanity_id, ipp.database_id, ipp.schema_name, ipp.lock_escalation_desc + ORDER BY index_sanity_id +OPTION ( RECOMPILE ); + +RAISERROR (N'Determining index usefulness',0,1) WITH NOWAIT; +UPDATE #MissingIndexes +SET is_low = CASE WHEN (user_seeks + user_scans) < 5000 + OR unique_compiles = 1 + THEN 1 + ELSE 0 + END; + +RAISERROR (N'Updating #IndexSanity.referenced_by_foreign_key',0,1) WITH NOWAIT; +UPDATE #IndexSanity + SET is_referenced_by_foreign_key=1 +FROM #IndexSanity s +JOIN #ForeignKeys fk ON + s.object_id=fk.referenced_object_id + AND s.database_id=fk.database_id + AND LEFT(s.key_column_names,LEN(fk.referenced_fk_columns)) = fk.referenced_fk_columns; + +RAISERROR (N'Update index_secret on #IndexSanity for NC indexes.',0,1) WITH NOWAIT; +UPDATE nc +SET secret_columns= + N'[' + + CASE tb.count_key_columns WHEN 0 THEN '1' ELSE CAST(tb.count_key_columns AS NVARCHAR(10)) END + + CASE nc.is_unique WHEN 1 THEN N' INCLUDE' ELSE N' KEY' END + + CASE WHEN tb.count_key_columns > 1 THEN N'S] ' ELSE N'] ' END + + CASE tb.index_id WHEN 0 THEN '[RID]' ELSE LTRIM(tb.key_column_names) + + /* Uniquifiers only needed on non-unique clustereds-- not heaps */ + CASE tb.is_unique WHEN 0 THEN ' [UNIQUIFIER]' ELSE N'' END + END + , count_secret_columns= + CASE tb.index_id WHEN 0 THEN 1 ELSE + tb.count_key_columns + + CASE tb.is_unique WHEN 0 THEN 1 ELSE 0 END + END +FROM #IndexSanity AS nc +JOIN #IndexSanity AS tb ON nc.object_id=tb.object_id + AND nc.database_id = tb.database_id + AND nc.schema_name = tb.schema_name + AND tb.index_id IN (0,1) +WHERE nc.index_id > 1; + +RAISERROR (N'Update index_secret on #IndexSanity for heaps and non-unique clustered.',0,1) WITH NOWAIT; +UPDATE tb +SET secret_columns= CASE tb.index_id WHEN 0 THEN '[RID]' ELSE '[UNIQUIFIER]' END + , count_secret_columns = 1 +FROM #IndexSanity AS tb +WHERE tb.index_id = 0 /*Heaps-- these have the RID */ + OR (tb.index_id=1 AND tb.is_unique=0); /* Non-unique CX: has uniquifer (when needed) */ + + +RAISERROR (N'Populate #IndexCreateTsql.',0,1) WITH NOWAIT; +INSERT #IndexCreateTsql (index_sanity_id, create_tsql) +SELECT + index_sanity_id, + ISNULL ( + CASE index_id WHEN 0 THEN N'ALTER TABLE ' + QUOTENAME([database_name]) + N'.' + QUOTENAME([schema_name]) + N'.' + QUOTENAME([object_name]) + ' REBUILD;' + ELSE + CASE WHEN is_XML = 1 OR is_spatial = 1 OR is_in_memory_oltp = 1 THEN N'' /* Not even trying for these just yet...*/ + ELSE + CASE WHEN is_primary_key=1 THEN + N'ALTER TABLE ' + QUOTENAME([database_name]) + N'.' + QUOTENAME([schema_name]) + + N'.' + QUOTENAME([object_name]) + + N' ADD CONSTRAINT [' + + index_name + + N'] PRIMARY KEY ' + + CASE WHEN index_id=1 THEN N'CLUSTERED (' ELSE N'(' END + + key_column_names_with_sort_order_no_types + N' )' + WHEN is_unique_constraint = 1 AND is_primary_key = 0 + THEN + N'ALTER TABLE ' + QUOTENAME([database_name]) + N'.' + QUOTENAME([schema_name]) + + N'.' + QUOTENAME([object_name]) + + N' ADD CONSTRAINT [' + + index_name + + N'] UNIQUE ' + + CASE WHEN index_id=1 THEN N'CLUSTERED (' ELSE N'(' END + + key_column_names_with_sort_order_no_types + N' )' + WHEN is_CX_columnstore= 1 THEN + N'CREATE CLUSTERED COLUMNSTORE INDEX ' + QUOTENAME(index_name) + N' on ' + QUOTENAME([database_name]) + N'.' + QUOTENAME([schema_name]) + N'.' + QUOTENAME([object_name]) + ELSE /*Else not a PK or cx columnstore */ + N'CREATE ' + + CASE WHEN is_unique=1 THEN N'UNIQUE ' ELSE N'' END + + CASE WHEN index_id=1 THEN N'CLUSTERED ' ELSE N'' END + + CASE WHEN is_NC_columnstore=1 THEN N'NONCLUSTERED COLUMNSTORE ' + ELSE N'' END + + N'INDEX [' + + index_name + N'] ON ' + + QUOTENAME([database_name]) + N'.' + + QUOTENAME([schema_name]) + N'.' + QUOTENAME([object_name]) + + CASE WHEN is_NC_columnstore=1 THEN + N' (' + ISNULL(include_column_names_no_types,'') + N' )' + ELSE /*Else not columnstore */ + N' (' + ISNULL(key_column_names_with_sort_order_no_types,'') + N' )' + + CASE WHEN include_column_names_no_types IS NOT NULL THEN + N' INCLUDE (' + include_column_names_no_types + N')' + ELSE N'' + END + END /*End non-columnstore case */ + + CASE WHEN filter_definition <> N'' THEN N' WHERE ' + filter_definition ELSE N'' END + END /*End Non-PK index CASE */ + + CASE WHEN is_NC_columnstore=0 AND is_CX_columnstore=0 THEN + N' WITH (' + + N'FILLFACTOR=' + CASE fill_factor WHEN 0 THEN N'100' ELSE CAST(fill_factor AS NVARCHAR(5)) END + ', ' + + N'ONLINE=?, SORT_IN_TEMPDB=?, DATA_COMPRESSION=?' + + N')' + ELSE N'' END + + N';' + END /*End non-spatial and non-xml CASE */ + END, '[Unknown Error]') + AS create_tsql +FROM #IndexSanity; + +RAISERROR (N'Populate #PartitionCompressionInfo.',0,1) WITH NOWAIT; +IF OBJECT_ID('tempdb..#maps') IS NOT NULL DROP TABLE #maps; +WITH maps + AS + ( + SELECT ips.index_sanity_id, + ips.partition_number, + ips.data_compression_desc, + ips.partition_number - ROW_NUMBER() OVER ( PARTITION BY ips.index_sanity_id, ips.data_compression_desc + ORDER BY ips.partition_number ) AS rn + FROM #IndexPartitionSanity AS ips + ) +SELECT * +INTO #maps +FROM maps; + +IF OBJECT_ID('tempdb..#grps') IS NOT NULL DROP TABLE #grps; +WITH grps + AS + ( + SELECT MIN(maps.partition_number) AS MinKey, + MAX(maps.partition_number) AS MaxKey, + maps.index_sanity_id, + maps.data_compression_desc + FROM #maps AS maps + GROUP BY maps.rn, maps.index_sanity_id, maps.data_compression_desc + ) +SELECT * +INTO #grps +FROM grps; + +INSERT #PartitionCompressionInfo ( index_sanity_id, partition_compression_detail ) +SELECT DISTINCT + grps.index_sanity_id, + SUBSTRING( + ( STUFF( + ( SELECT N', ' + N' Partition' + + CASE + WHEN grps2.MinKey < grps2.MaxKey + THEN + + N's ' + CAST(grps2.MinKey AS NVARCHAR(10)) + N' - ' + + CAST(grps2.MaxKey AS NVARCHAR(10)) + N' use ' + grps2.data_compression_desc + ELSE + N' ' + CAST(grps2.MinKey AS NVARCHAR(10)) + N' uses ' + grps2.data_compression_desc + END AS Partitions + FROM #grps AS grps2 + WHERE grps2.index_sanity_id = grps.index_sanity_id + ORDER BY grps2.MinKey, grps2.MaxKey + FOR XML PATH(''), TYPE ).value('.', 'NVARCHAR(MAX)'), 1, 1, '')), 0, 8000) AS partition_compression_detail +FROM #grps AS grps; + +RAISERROR (N'Update #PartitionCompressionInfo.',0,1) WITH NOWAIT; +UPDATE sz +SET sz.data_compression_desc = pci.partition_compression_detail +FROM #IndexSanitySize sz +JOIN #PartitionCompressionInfo AS pci +ON pci.index_sanity_id = sz.index_sanity_id; + +RAISERROR (N'Update #IndexSanity for filtered indexes with columns not in the index definition.',0,1) WITH NOWAIT; +UPDATE #IndexSanity +SET filter_columns_not_in_index = D1.filter_columns_not_in_index +FROM #IndexSanity si + CROSS APPLY ( SELECT RTRIM(STUFF( (SELECT N', ' + c.column_name AS col_definition + FROM #FilteredIndexes AS c + WHERE c.database_id= si.database_id + AND c.schema_name = si.schema_name + AND c.table_name = si.object_name + AND c.index_name = si.index_name + ORDER BY c.index_sanity_id + FOR XML PATH('') , TYPE).value('.', 'nvarchar(max)'), 1, 1,''))) D1 + ( filter_columns_not_in_index ); + + +IF @Debug = 1 +BEGIN + SELECT '#BlitzIndexResults' AS table_name, * FROM #BlitzIndexResults AS bir; + SELECT '#IndexSanity' AS table_name, * FROM #IndexSanity; + SELECT '#IndexPartitionSanity' AS table_name, * FROM #IndexPartitionSanity; + SELECT '#IndexSanitySize' AS table_name, * FROM #IndexSanitySize; + SELECT '#IndexColumns' AS table_name, * FROM #IndexColumns; + SELECT '#MissingIndexes' AS table_name, * FROM #MissingIndexes; + SELECT '#ForeignKeys' AS table_name, * FROM #ForeignKeys; + SELECT '#UnindexedForeignKeys' AS table_name, * FROM #UnindexedForeignKeys; + SELECT '#BlitzIndexResults' AS table_name, * FROM #BlitzIndexResults; + SELECT '#IndexCreateTsql' AS table_name, * FROM #IndexCreateTsql; + SELECT '#DatabaseList' AS table_name, * FROM #DatabaseList; + SELECT '#Statistics' AS table_name, * FROM #Statistics; + SELECT '#PartitionCompressionInfo' AS table_name, * FROM #PartitionCompressionInfo; + SELECT '#ComputedColumns' AS table_name, * FROM #ComputedColumns; + SELECT '#TraceStatus' AS table_name, * FROM #TraceStatus; + SELECT '#CheckConstraints' AS table_name, * FROM #CheckConstraints; + SELECT '#FilteredIndexes' AS table_name, * FROM #FilteredIndexes; +END + + +---------------------------------------- +--STEP 3: DIAGNOSE THE PATIENT +---------------------------------------- + + +BEGIN TRY +---------------------------------------- +--If @TableName is specified, just return information for that table. +--The @Mode parameter doesn't matter if you're looking at a specific table. +---------------------------------------- +IF @TableName IS NOT NULL +BEGIN + RAISERROR(N'@TableName specified, giving detail only on that table.', 0,1) WITH NOWAIT; + + --We do a left join here in case this is a disabled NC. + --In that case, it won't have any size info/pages allocated. + + IF (@ShowColumnstoreOnly = 0) + BEGIN + WITH table_mode_cte AS ( + SELECT + s.db_schema_object_indexid, + s.key_column_names, + s.index_definition, + ISNULL(s.secret_columns,N'') AS secret_columns, + s.fill_factor, + s.index_usage_summary, + sz.index_op_stats, + ISNULL(sz.index_size_summary,'') /*disabled NCs will be null*/ AS index_size_summary, + partition_compression_detail , + ISNULL(sz.index_lock_wait_summary,'') AS index_lock_wait_summary, + s.is_referenced_by_foreign_key, + (SELECT COUNT(*) + FROM #ForeignKeys fk WHERE fk.parent_object_id=s.object_id + AND PATINDEX (fk.parent_fk_columns, s.key_column_names)=1) AS FKs_covered_by_index, + s.last_user_seek, + s.last_user_scan, + s.last_user_lookup, + s.last_user_update, + s.create_date, + s.modify_date, + sz.page_latch_wait_count, + CONVERT(VARCHAR(10), (sz.page_latch_wait_in_ms / 1000) / 86400) + ':' + CONVERT(VARCHAR(20), DATEADD(s, (sz.page_latch_wait_in_ms / 1000), 0), 108) AS page_latch_wait_time, + sz.page_io_latch_wait_count, + CONVERT(VARCHAR(10), (sz.page_io_latch_wait_in_ms / 1000) / 86400) + ':' + CONVERT(VARCHAR(20), DATEADD(s, (sz.page_io_latch_wait_in_ms / 1000), 0), 108) AS page_io_latch_wait_time, + ct.create_tsql, + CASE + WHEN s.is_primary_key = 1 AND s.index_definition <> '[HEAP]' + THEN N'--ALTER TABLE ' + QUOTENAME(s.[database_name]) + N'.' + QUOTENAME(s.[schema_name]) + N'.' + QUOTENAME(s.[object_name]) + + N' DROP CONSTRAINT ' + QUOTENAME(s.index_name) + N';' + WHEN s.is_primary_key = 0 AND is_unique_constraint = 1 AND s.index_definition <> '[HEAP]' + THEN N'--ALTER TABLE ' + QUOTENAME(s.[database_name]) + N'.' + QUOTENAME(s.[schema_name]) + N'.' + QUOTENAME(s.[object_name]) + + N' DROP CONSTRAINT ' + QUOTENAME(s.index_name) + N';' + WHEN s.is_primary_key = 0 AND s.index_definition <> '[HEAP]' + THEN N'--DROP INDEX '+ QUOTENAME(s.index_name) + N' ON ' + QUOTENAME(s.[database_name]) + N'.' + + QUOTENAME(s.[schema_name]) + N'.' + QUOTENAME(s.[object_name]) + N';' + ELSE N'' + END AS drop_tsql, + 1 AS display_order + FROM #IndexSanity s + LEFT JOIN #IndexSanitySize sz ON + s.index_sanity_id=sz.index_sanity_id + LEFT JOIN #IndexCreateTsql ct ON + s.index_sanity_id=ct.index_sanity_id + LEFT JOIN #PartitionCompressionInfo pci ON + pci.index_sanity_id = s.index_sanity_id + WHERE s.[object_id]=@ObjectID + UNION ALL + SELECT N'Database ' + QUOTENAME(@DatabaseName) + N' as of ' + CONVERT(NVARCHAR(16),GETDATE(),121) + + N' (' + @ScriptVersionName + ')' , + N'SQL Server First Responder Kit' , + N'http://FirstResponderKit.org' , + N'From Your Community Volunteers', + NULL,@DaysUptimeInsertValue,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL, + 0 AS display_order + ) + SELECT + db_schema_object_indexid AS [Details: db_schema.table.index(indexid)], + index_definition AS [Definition: [Property]] ColumnName {datatype maxbytes}], + secret_columns AS [Secret Columns], + fill_factor AS [Fillfactor], + index_usage_summary AS [Usage Stats], + index_op_stats AS [Op Stats], + index_size_summary AS [Size], + partition_compression_detail AS [Compression Type], + index_lock_wait_summary AS [Lock Waits], + is_referenced_by_foreign_key AS [Referenced by FK?], + FKs_covered_by_index AS [FK Covered by Index?], + last_user_seek AS [Last User Seek], + last_user_scan AS [Last User Scan], + last_user_lookup AS [Last User Lookup], + last_user_update AS [Last User Write], + create_date AS [Created], + modify_date AS [Last Modified], + page_latch_wait_count AS [Page Latch Wait Count], + page_latch_wait_time as [Page Latch Wait Time (D:H:M:S)], + page_io_latch_wait_count AS [Page IO Latch Wait Count], + page_io_latch_wait_time as [Page IO Latch Wait Time (D:H:M:S)], + create_tsql AS [Create TSQL], + drop_tsql AS [Drop TSQL] + FROM table_mode_cte + ORDER BY display_order ASC, key_column_names ASC + OPTION ( RECOMPILE ); + + IF (SELECT TOP 1 [object_id] FROM #MissingIndexes mi) IS NOT NULL + BEGIN; + + WITH create_date AS ( + SELECT i.database_id, + i.schema_name, + i.[object_id], + ISNULL(NULLIF(MAX(DATEDIFF(DAY, i.create_date, SYSDATETIME())), 0), 1) AS create_days + FROM #IndexSanity AS i + GROUP BY i.database_id, i.schema_name, i.object_id + ) + SELECT N'Missing index.' AS Finding , + N'https://www.brentozar.com/go/Indexaphobia' AS URL , + mi.[statement] + + ' Est. Benefit: ' + + CASE WHEN magic_benefit_number >= 922337203685477 THEN '>= 922,337,203,685,477' + ELSE REPLACE(CONVERT(NVARCHAR(256),CAST(CAST( + (magic_benefit_number / CASE WHEN cd.create_days < @DaysUptime THEN cd.create_days ELSE @DaysUptime END) + AS BIGINT) AS MONEY), 1), '.00', '') + END AS [Estimated Benefit], + missing_index_details AS [Missing Index Request] , + index_estimated_impact AS [Estimated Impact], + create_tsql AS [Create TSQL], + sample_query_plan AS [Sample Query Plan] + FROM #MissingIndexes mi + LEFT JOIN create_date AS cd + ON mi.[object_id] = cd.object_id + AND mi.database_id = cd.database_id + AND mi.schema_name = cd.schema_name + WHERE mi.[object_id] = @ObjectID + AND (@ShowAllMissingIndexRequests=1 + /* Minimum benefit threshold = 100k/day of uptime OR since table creation date, whichever is lower*/ + OR (magic_benefit_number / CASE WHEN cd.create_days < @DaysUptime THEN cd.create_days ELSE @DaysUptime END) >= 100000) + ORDER BY magic_benefit_number DESC + OPTION ( RECOMPILE ); + END; + ELSE + SELECT 'No missing indexes.' AS finding; + + SELECT + column_name AS [Column Name], + (SELECT COUNT(*) + FROM #IndexColumns c2 + WHERE c2.column_name=c.column_name + AND c2.key_ordinal IS NOT NULL) + + CASE WHEN c.index_id = 1 AND c.key_ordinal IS NOT NULL THEN + -1+ (SELECT COUNT(DISTINCT index_id) + FROM #IndexColumns c3 + WHERE c3.index_id NOT IN (0,1)) + ELSE 0 END + AS [Found In], + system_type_name + + CASE max_length WHEN -1 THEN N' (max)' ELSE + CASE + WHEN system_type_name IN (N'char',N'varchar',N'binary',N'varbinary') THEN N' (' + CAST(max_length AS NVARCHAR(20)) + N')' + WHEN system_type_name IN (N'nchar',N'nvarchar') THEN N' (' + CAST(max_length/2 AS NVARCHAR(20)) + N')' + ELSE '' + END + END + AS [Type], + CASE is_computed WHEN 1 THEN 'yes' ELSE '' END AS [Computed?], + max_length AS [Length (max bytes)], + [precision] AS [Prec], + [scale] AS [Scale], + CASE is_nullable WHEN 1 THEN 'yes' ELSE '' END AS [Nullable?], + CASE is_identity WHEN 1 THEN 'yes' ELSE '' END AS [Identity?], + CASE is_replicated WHEN 1 THEN 'yes' ELSE '' END AS [Replicated?], + CASE is_sparse WHEN 1 THEN 'yes' ELSE '' END AS [Sparse?], + CASE is_filestream WHEN 1 THEN 'yes' ELSE '' END AS [Filestream?], + collation_name AS [Collation] + FROM #IndexColumns AS c + WHERE index_id IN (0,1); + + IF (SELECT TOP 1 parent_object_id FROM #ForeignKeys) IS NOT NULL + BEGIN + SELECT [database_name] + N':' + parent_object_name + N': ' + foreign_key_name AS [Foreign Key], + parent_fk_columns AS [Foreign Key Columns], + referenced_object_name AS [Referenced Table], + referenced_fk_columns AS [Referenced Table Columns], + is_disabled AS [Is Disabled?], + is_not_trusted AS [Not Trusted?], + is_not_for_replication [Not for Replication?], + [update_referential_action_desc] AS [Cascading Updates?], + [delete_referential_action_desc] AS [Cascading Deletes?] + FROM #ForeignKeys + ORDER BY [Foreign Key] + OPTION ( RECOMPILE ); + END; + ELSE + SELECT 'No foreign keys.' AS finding; + + /* Show histograms for all stats on this table. More info: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/1900 */ + IF EXISTS (SELECT * FROM sys.all_objects WHERE name = 'dm_db_stats_histogram') + BEGIN + SET @dsql = N'USE ' + QUOTENAME(@DatabaseName) + N'; + SELECT s.name AS [Stat Name], c.name AS [Leading Column Name], hist.step_number AS [Step Number], + hist.range_high_key AS [Range High Key], hist.range_rows AS [Range Rows], + hist.equal_rows AS [Equal Rows], hist.distinct_range_rows AS [Distinct Range Rows], hist.average_range_rows AS [Average Range Rows], + s.auto_created AS [Auto-Created], s.user_created AS [User-Created], + props.last_updated AS [Last Updated], props.modification_counter AS [Modification Counter], props.rows AS [Table Rows], + props.rows_sampled AS [Rows Sampled], s.stats_id AS [StatsID] + FROM sys.stats AS s + INNER JOIN sys.stats_columns sc ON s.object_id = sc.object_id AND s.stats_id = sc.stats_id AND sc.stats_column_id = 1 + INNER JOIN sys.columns c ON sc.object_id = c.object_id AND sc.column_id = c.column_id + CROSS APPLY sys.dm_db_stats_properties(s.object_id, s.stats_id) AS props + CROSS APPLY sys.dm_db_stats_histogram(s.[object_id], s.stats_id) AS hist + WHERE s.object_id = @ObjectID + ORDER BY s.auto_created, s.user_created, s.name, hist.step_number;'; + EXEC sp_executesql @dsql, N'@ObjectID INT', @ObjectID; + END + END + + /* Visualize columnstore index contents. More info: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2584 */ + IF 2 = (SELECT SUM(1) FROM sys.all_objects WHERE name IN ('column_store_row_groups','column_store_segments')) + BEGIN + RAISERROR(N'Visualizing columnstore index contents.', 0,1) WITH NOWAIT; + + SET @dsql = N'USE ' + QUOTENAME(@DatabaseName) + N'; + IF EXISTS(SELECT * FROM ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_row_groups WHERE object_id = @ObjectID) + BEGIN + SELECT @ColumnList = N'''', @ColumnListWithApostrophes = N''''; + WITH DistinctColumns AS ( + SELECT DISTINCT QUOTENAME(c.name) AS column_name, QUOTENAME(c.name,'''''''') AS ColumnNameWithApostrophes, c.column_id + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.partitions p + INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns c ON p.object_id = c.object_id + INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.index_columns ic on ic.column_id = c.column_id and ic.object_id = c.object_id AND ic.index_id = p.index_id + INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.types t ON c.system_type_id = t.system_type_id AND c.user_type_id = t.user_type_id + WHERE p.object_id = @ObjectID + AND EXISTS (SELECT * FROM ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_segments seg WHERE p.partition_id = seg.partition_id AND seg.column_id = ic.index_column_id) + AND p.data_compression IN (3,4) + ) + SELECT @ColumnList = @ColumnList + column_name + N'', '', + @ColumnListWithApostrophes = @ColumnListWithApostrophes + ColumnNameWithApostrophes + N'', '' + FROM DistinctColumns + ORDER BY column_id; + SELECT @PartitionCount = COUNT(1) FROM ' + QUOTENAME(@DatabaseName) + N'.sys.partitions WHERE object_id = @ObjectID AND data_compression IN (3,4); + END'; + + IF @Debug = 1 + BEGIN + PRINT SUBSTRING(@dsql, 0, 4000); + PRINT SUBSTRING(@dsql, 4000, 8000); + PRINT SUBSTRING(@dsql, 8000, 12000); + PRINT SUBSTRING(@dsql, 12000, 16000); + PRINT SUBSTRING(@dsql, 16000, 20000); + PRINT SUBSTRING(@dsql, 20000, 24000); + PRINT SUBSTRING(@dsql, 24000, 28000); + PRINT SUBSTRING(@dsql, 28000, 32000); + PRINT SUBSTRING(@dsql, 32000, 36000); + PRINT SUBSTRING(@dsql, 36000, 40000); + END; + + EXEC sp_executesql @dsql, N'@ObjectID INT, @ColumnList NVARCHAR(MAX) OUTPUT, @ColumnListWithApostrophes NVARCHAR(MAX) OUTPUT, @PartitionCount INT OUTPUT', @ObjectID, @ColumnList OUTPUT, @ColumnListWithApostrophes OUTPUT, @PartitionCount OUTPUT; + + IF @PartitionCount < 2 + SET @ShowPartitionRanges = 0; + + IF @Debug = 1 + SELECT @ColumnList AS ColumnstoreColumnList, @ColumnListWithApostrophes AS ColumnstoreColumnListWithApostrophes, @PartitionCount AS PartitionCount, @ShowPartitionRanges AS ShowPartitionRanges; + + IF @ColumnList <> '' + BEGIN + /* Remove the trailing comma */ + SET @ColumnList = LEFT(@ColumnList, LEN(@ColumnList) - 1); + SET @ColumnListWithApostrophes = LEFT(@ColumnListWithApostrophes, LEN(@ColumnListWithApostrophes) - 1); + + SET @dsql = N'USE ' + QUOTENAME(@DatabaseName) + N'; + SELECT partition_number, ' + + CASE WHEN @ShowPartitionRanges = 1 THEN N' COALESCE(range_start_op + '' '' + range_start + '' '', '''') + COALESCE(range_end_op + '' '' + range_end, '''') AS partition_range, ' ELSE N' ' END + + N' row_group_id, total_rows, deleted_rows, ' + + @ColumnList + + CASE WHEN @ShowPartitionRanges = 1 THEN N' , + state_desc, trim_reason_desc, transition_to_compressed_state_desc, has_vertipaq_optimization + FROM ( + SELECT column_name, partition_number, row_group_id, total_rows, deleted_rows, details, + range_start_op, + CASE + WHEN format_type IS NULL THEN CAST(range_start_value AS NVARCHAR(4000)) + ELSE CONVERT(NVARCHAR(4000), range_start_value, format_type) END range_start, + range_end_op, + CASE + WHEN format_type IS NULL THEN CAST(range_end_value AS NVARCHAR(4000)) + ELSE CONVERT(NVARCHAR(4000), range_end_value, format_type) END range_end' ELSE N' ' END + N', + state_desc, trim_reason_desc, transition_to_compressed_state_desc, has_vertipaq_optimization + FROM ( + SELECT c.name AS column_name, p.partition_number, rg.row_group_id, rg.total_rows, rg.deleted_rows, + phys.state_desc, phys.trim_reason_desc, phys.transition_to_compressed_state_desc, phys.has_vertipaq_optimization, + details = CAST(seg.min_data_id AS VARCHAR(20)) + '' to '' + CAST(seg.max_data_id AS VARCHAR(20)) + '', '' + CAST(CAST(((COALESCE(d.on_disk_size,0) + COALESCE(seg.on_disk_size,0)) / 1024.0 / 1024) AS DECIMAL(18,0)) AS VARCHAR(20)) + '' MB''' + + CASE WHEN @ShowPartitionRanges = 1 THEN N', + CASE + WHEN pp.system_type_id IN (40, 41, 42, 43, 58, 61) THEN 126 + WHEN pp.system_type_id IN (59, 62) THEN 3 + WHEN pp.system_type_id IN (60, 122) THEN 2 + ELSE NULL END format_type, + CASE WHEN pf.boundary_value_on_right = 0 THEN ''>'' ELSE ''>='' END range_start_op, + prvs.value range_start_value, + CASE WHEN pf.boundary_value_on_right = 0 THEN ''<='' ELSE ''<'' END range_end_op, + prve.value range_end_value ' ELSE N' ' END + N' + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_row_groups rg + INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns c ON rg.object_id = c.object_id + INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partitions p ON rg.object_id = p.object_id AND rg.partition_number = p.partition_number + INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.index_columns ic on ic.column_id = c.column_id AND ic.object_id = c.object_id AND ic.index_id = p.index_id + LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_column_store_row_group_physical_stats phys ON rg.row_group_id = phys.row_group_id AND rg.object_id = phys.object_id AND rg.partition_number = phys.partition_number AND rg.index_id = phys.index_id ' + CASE WHEN @ShowPartitionRanges = 1 THEN N' + LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.indexes i ON i.object_id = rg.object_id AND i.index_id = rg.index_id + LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partition_schemes ps ON ps.data_space_id = i.data_space_id + LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partition_functions pf ON pf.function_id = ps.function_id + LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partition_parameters pp ON pp.function_id = pf.function_id + LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partition_range_values prvs ON prvs.function_id = pf.function_id AND prvs.boundary_id = p.partition_number - 1 + LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partition_range_values prve ON prve.function_id = pf.function_id AND prve.boundary_id = p.partition_number ' ELSE N' ' END + + N' LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_segments seg ON p.partition_id = seg.partition_id AND ic.index_column_id = seg.column_id AND rg.row_group_id = seg.segment_id + LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_dictionaries d ON p.hobt_id = d.hobt_id AND c.column_id = d.column_id AND seg.secondary_dictionary_id = d.dictionary_id + WHERE rg.object_id = @ObjectID + AND rg.state IN (1, 2, 3) + AND c.name IN ( ' + @ColumnListWithApostrophes + N')' + + CASE WHEN @ShowPartitionRanges = 1 THEN N' + ) AS y ' ELSE N' ' END + N' + ) AS x + PIVOT (MAX(details) FOR column_name IN ( ' + @ColumnList + N')) AS pivot1 + ORDER BY partition_number, row_group_id;'; + + IF @Debug = 1 + BEGIN + PRINT SUBSTRING(@dsql, 0, 4000); + PRINT SUBSTRING(@dsql, 4000, 8000); + PRINT SUBSTRING(@dsql, 8000, 12000); + PRINT SUBSTRING(@dsql, 12000, 16000); + PRINT SUBSTRING(@dsql, 16000, 20000); + PRINT SUBSTRING(@dsql, 20000, 24000); + PRINT SUBSTRING(@dsql, 24000, 28000); + PRINT SUBSTRING(@dsql, 28000, 32000); + PRINT SUBSTRING(@dsql, 32000, 36000); + PRINT SUBSTRING(@dsql, 36000, 40000); + END; + + IF @dsql IS NULL + RAISERROR('@dsql is null',16,1); + ELSE + EXEC sp_executesql @dsql, N'@ObjectID INT', @ObjectID; + END + ELSE /* No columns were found for this object */ + BEGIN + SELECT N'No compressed columnstore rowgroups were found for this object.' AS Columnstore_Visualization + UNION ALL + SELECT N'SELECT * FROM ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_row_groups WHERE object_id = ' + CAST(@ObjectID AS NVARCHAR(100)); + END + RAISERROR(N'Done visualizing columnstore index contents.', 0,1) WITH NOWAIT; + END + + IF @ShowColumnstoreOnly = 1 + RETURN; + +END; /* IF @TableName IS NOT NULL */ + + + + + + + + +ELSE /* @TableName IS NULL, so we operate in normal mode 0/1/2/3/4 */ +BEGIN + +/* Validate and check table output params */ + + + /* Checks if @OutputServerName is populated with a valid linked server, and that the database name specified is valid */ + DECLARE @ValidOutputServer BIT; + DECLARE @ValidOutputLocation BIT; + DECLARE @LinkedServerDBCheck NVARCHAR(2000); + DECLARE @ValidLinkedServerDB INT; + DECLARE @tmpdbchk TABLE (cnt INT); + + IF @OutputServerName IS NOT NULL + BEGIN + IF (SUBSTRING(@OutputTableName, 2, 1) = '#') + BEGIN + RAISERROR('Due to the nature of temporary tables, outputting to a linked server requires a permanent table.', 16, 0); + END; + ELSE IF EXISTS (SELECT server_id FROM sys.servers WHERE QUOTENAME([name]) = @OutputServerName) + BEGIN + SET @LinkedServerDBCheck = 'SELECT 1 WHERE EXISTS (SELECT * FROM '+@OutputServerName+'.master.sys.databases WHERE QUOTENAME([name]) = '''+@OutputDatabaseName+''')'; + INSERT INTO @tmpdbchk EXEC sys.sp_executesql @LinkedServerDBCheck; + SET @ValidLinkedServerDB = (SELECT COUNT(*) FROM @tmpdbchk); + IF (@ValidLinkedServerDB > 0) + BEGIN + SET @ValidOutputServer = 1; + SET @ValidOutputLocation = 1; + END; + ELSE + RAISERROR('The specified database was not found on the output server', 16, 0); + END; + ELSE + BEGIN + RAISERROR('The specified output server was not found', 16, 0); + END; + END; + ELSE + BEGIN + IF (SUBSTRING(@OutputTableName, 2, 2) = '##') + BEGIN + SET @StringToExecute = N' IF (OBJECT_ID(''[tempdb].[dbo].@@@OutputTableName@@@'') IS NOT NULL) DROP TABLE @@@OutputTableName@@@'; + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); + EXEC(@StringToExecute); + + SET @OutputServerName = QUOTENAME(CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128))); + SET @OutputDatabaseName = '[tempdb]'; + SET @OutputSchemaName = '[dbo]'; + SET @ValidOutputLocation = 1; + END; + ELSE IF (SUBSTRING(@OutputTableName, 2, 1) = '#') + BEGIN + RAISERROR('Due to the nature of Dymamic SQL, only global (i.e. double pound (##)) temp tables are supported for @OutputTableName', 16, 0); + END; + ELSE IF @OutputDatabaseName IS NOT NULL + AND @OutputSchemaName IS NOT NULL + AND @OutputTableName IS NOT NULL + AND EXISTS ( SELECT * + FROM sys.databases + WHERE QUOTENAME([name]) = @OutputDatabaseName) + BEGIN + SET @ValidOutputLocation = 1; + SET @OutputServerName = QUOTENAME(CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128))); + END; + ELSE IF @OutputDatabaseName IS NOT NULL + AND @OutputSchemaName IS NOT NULL + AND @OutputTableName IS NOT NULL + AND NOT EXISTS ( SELECT * + FROM sys.databases + WHERE QUOTENAME([name]) = @OutputDatabaseName) + BEGIN + RAISERROR('The specified output database was not found on this server', 16, 0); + END; + ELSE + BEGIN + SET @ValidOutputLocation = 0; + END; + END; + + IF (@ValidOutputLocation = 0 AND @OutputType = 'NONE') + BEGIN + RAISERROR('Invalid output location and no output asked',12,1); + RETURN; + END; + + /* @OutputTableName lets us export the results to a permanent table */ + DECLARE @RunID UNIQUEIDENTIFIER; + SET @RunID = NEWID(); + + DECLARE @TableExists BIT; + DECLARE @SchemaExists BIT; + + DECLARE @TableExistsSql NVARCHAR(MAX); + + IF (@ValidOutputLocation = 1 AND COALESCE(@OutputServerName, @OutputDatabaseName, @OutputSchemaName, @OutputTableName) IS NOT NULL) + BEGIN + SET @StringToExecute = + N'SET @SchemaExists = 0; + SET @TableExists = 0; + IF EXISTS(SELECT * FROM @@@OutputServerName@@@.@@@OutputDatabaseName@@@.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''@@@OutputSchemaName@@@'') + SET @SchemaExists = 1 + IF EXISTS (SELECT * FROM @@@OutputServerName@@@.@@@OutputDatabaseName@@@.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''@@@OutputSchemaName@@@'' AND QUOTENAME(TABLE_NAME) = ''@@@OutputTableName@@@'') + BEGIN + SET @TableExists = 1 + IF NOT EXISTS(SELECT * FROM @@@OutputServerName@@@.@@@OutputDatabaseName@@@.INFORMATION_SCHEMA.COLUMNS WHERE QUOTENAME(TABLE_SCHEMA) = ''@@@OutputSchemaName@@@'' + AND QUOTENAME(TABLE_NAME) = ''@@@OutputTableName@@@'' AND QUOTENAME(COLUMN_NAME) = ''[total_forwarded_fetch_count]'') + EXEC @@@OutputServerName@@@.@@@OutputDatabaseName@@@.dbo.sp_executesql N''ALTER TABLE @@@OutputSchemaName@@@.@@@OutputTableName@@@ ADD [total_forwarded_fetch_count] BIGINT'' + END'; + + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputServerName@@@', @OutputServerName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputSchemaName@@@', @OutputSchemaName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); + + EXEC sp_executesql @StringToExecute, N'@TableExists BIT OUTPUT, @SchemaExists BIT OUTPUT', @TableExists OUTPUT, @SchemaExists OUTPUT; + + + SET @TableExistsSql = + N'IF EXISTS(SELECT * FROM @@@OutputServerName@@@.@@@OutputDatabaseName@@@.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''@@@OutputSchemaName@@@'') + AND NOT EXISTS (SELECT * FROM @@@OutputServerName@@@.@@@OutputDatabaseName@@@.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''@@@OutputSchemaName@@@'' AND QUOTENAME(TABLE_NAME) = ''@@@OutputTableName@@@'') + SET @TableExists = 0 + ELSE + SET @TableExists = 1'; + + SET @TableExistsSql = REPLACE(@TableExistsSql, '@@@OutputServerName@@@', @OutputServerName); + SET @TableExistsSql = REPLACE(@TableExistsSql, '@@@OutputDatabaseName@@@', @OutputDatabaseName); + SET @TableExistsSql = REPLACE(@TableExistsSql, '@@@OutputSchemaName@@@', @OutputSchemaName); + SET @TableExistsSql = REPLACE(@TableExistsSql, '@@@OutputTableName@@@', @OutputTableName); + + END + + + + + IF @Mode IN (0, 4) /* DIAGNOSE */ + BEGIN; + IF @Mode IN (0, 4) /* DIAGNOSE priorities 1-100 */ + BEGIN; + RAISERROR(N'@Mode=0 or 4, running rules for priorities 1-100.', 0,1) WITH NOWAIT; + + ---------------------------------------- + --Multiple Index Personalities: Check_id 0-10 + ---------------------------------------- + RAISERROR('check_id 1: Duplicate keys', 0,1) WITH NOWAIT; + WITH duplicate_indexes + AS ( SELECT [object_id], key_column_names, database_id, [schema_name] + FROM #IndexSanity AS ip + WHERE index_type IN (1,2) /* Clustered, NC only*/ + AND is_hypothetical = 0 + AND is_disabled = 0 + AND is_primary_key = 0 + AND EXISTS ( + SELECT 1/0 + FROM #IndexSanitySize ips + WHERE ip.index_sanity_id = ips.index_sanity_id + AND ip.database_id = ips.database_id + AND ip.schema_name = ips.schema_name + AND ips.total_reserved_MB >= CASE + WHEN (@GetAllDatabases = 1 OR @Mode = 0) + THEN @ThresholdMB + ELSE ips.total_reserved_MB + END + ) + GROUP BY [object_id], key_column_names, database_id, [schema_name] + HAVING COUNT(*) > 1) + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 1 AS check_id, + ip.index_sanity_id, + 20 AS Priority, + 'Multiple Index Personalities' AS findings_group, + 'Duplicate keys' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/duplicateindex' AS URL, + N'Index Name: ' + ip.index_name + N' Table Name: ' + ip.db_schema_object_name AS details, + ip.index_definition, + ip.secret_columns, + ip.index_usage_summary, + ips.index_size_summary + FROM duplicate_indexes di + JOIN #IndexSanity ip ON di.[object_id] = ip.[object_id] + AND ip.database_id = di.database_id + AND ip.[schema_name] = di.[schema_name] + AND di.key_column_names = ip.key_column_names + JOIN #IndexSanitySize ips ON ip.index_sanity_id = ips.index_sanity_id + AND ip.database_id = ips.database_id + AND ip.schema_name = ips.schema_name + /* WHERE clause limits to only @ThresholdMB or larger duplicate indexes when getting all databases or using PainRelief mode */ + WHERE ips.total_reserved_MB >= CASE WHEN (@GetAllDatabases = 1 OR @Mode = 0) THEN @ThresholdMB ELSE ips.total_reserved_MB END + AND ip.is_primary_key = 0 + ORDER BY ips.total_rows DESC, ip.[schema_name], ip.[object_name], ip.key_column_names_with_sort_order + OPTION ( RECOMPILE ); + + RAISERROR('check_id 2: Keys w/ identical leading columns.', 0,1) WITH NOWAIT; + WITH borderline_duplicate_indexes + AS ( SELECT DISTINCT database_id, [object_id], first_key_column_name, key_column_names, + COUNT([object_id]) OVER ( PARTITION BY database_id, [object_id], first_key_column_name ) AS number_dupes + FROM #IndexSanity + WHERE index_type IN (1,2) /* Clustered, NC only*/ + AND is_hypothetical=0 + AND is_disabled=0 + AND is_primary_key = 0) + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 2 AS check_id, + ip.index_sanity_id, + 30 AS Priority, + 'Multiple Index Personalities' AS findings_group, + 'Borderline duplicate keys' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/duplicateindex' AS URL, + ip.db_schema_object_indexid AS details, + ip.index_definition, + ip.secret_columns, + ip.index_usage_summary, + ips.index_size_summary + FROM #IndexSanity AS ip + JOIN #IndexSanitySize ips ON ip.index_sanity_id = ips.index_sanity_id + WHERE EXISTS ( + SELECT di.[object_id] + FROM borderline_duplicate_indexes AS di + WHERE di.[object_id] = ip.[object_id] AND + di.database_id = ip.database_id AND + di.first_key_column_name = ip.first_key_column_name AND + di.key_column_names <> ip.key_column_names AND + di.number_dupes > 1 + ) + AND ip.is_primary_key = 0 + ORDER BY ips.total_rows DESC, ip.[schema_name], ip.[object_name], ip.key_column_names, ip.include_column_names + OPTION ( RECOMPILE ); + + ---------------------------------------- + --Aggressive Indexes: Check_id 10-19 + ---------------------------------------- + + RAISERROR(N'check_id 11: Total lock wait time > 5 minutes (row + page)', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 11 AS check_id, + i.index_sanity_id, + 70 AS Priority, + N'Aggressive ' + + CASE COALESCE((SELECT SUM(1) + FROM #IndexSanity iMe + INNER JOIN #IndexSanity iOthers + ON iMe.database_id = iOthers.database_id + AND iMe.object_id = iOthers.object_id + AND iOthers.index_id > 1 + WHERE i.index_sanity_id = iMe.index_sanity_id + AND iOthers.is_hypothetical = 0 + AND iOthers.is_disabled = 0 + ), 0) + WHEN 0 THEN N'Under-Indexing' + WHEN 1 THEN N'Under-Indexing' + WHEN 2 THEN N'Under-Indexing' + WHEN 3 THEN N'Under-Indexing' + WHEN 4 THEN N'Indexes' + WHEN 5 THEN N'Indexes' + WHEN 6 THEN N'Indexes' + WHEN 7 THEN N'Indexes' + WHEN 8 THEN N'Indexes' + WHEN 9 THEN N'Indexes' + ELSE N'Over-Indexing' + END AS findings_group, + N'Total lock wait time > 5 minutes (row + page)' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/AggressiveIndexes' AS URL, + (i.db_schema_object_indexid + N': ' + + sz.index_lock_wait_summary + N' NC indexes on table: ') COLLATE DATABASE_DEFAULT + + CAST(COALESCE((SELECT SUM(1) + FROM #IndexSanity iMe + INNER JOIN #IndexSanity iOthers + ON iMe.database_id = iOthers.database_id + AND iMe.object_id = iOthers.object_id + AND iOthers.index_id > 1 + WHERE i.index_sanity_id = iMe.index_sanity_id + AND iOthers.is_hypothetical = 0 + AND iOthers.is_disabled = 0 + ), 0) + AS NVARCHAR(30)) AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + sz.index_size_summary + FROM #IndexSanity AS i + JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id + WHERE (total_row_lock_wait_in_ms + total_page_lock_wait_in_ms) > 300000 + GROUP BY i.index_sanity_id, [database_name], i.db_schema_object_indexid, sz.index_lock_wait_summary, i.index_definition, i.secret_columns, i.index_usage_summary, sz.index_size_summary, sz.index_sanity_id + ORDER BY SUM(total_row_lock_wait_in_ms + total_page_lock_wait_in_ms) DESC, 4, [database_name], 8 + OPTION ( RECOMPILE ); + + + + ---------------------------------------- + --Index Hoarder: Check_id 20-29 + ---------------------------------------- + RAISERROR(N'check_id 20: >= 10 NC indexes on any given table. Yes, 10 is an arbitrary number.', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 20 AS check_id, + MAX(i.index_sanity_id) AS index_sanity_id, + 10 AS Priority, + 'Index Hoarder' AS findings_group, + 'Many NC Indexes on a Single Table' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/IndexHoarder' AS URL, + CAST (COUNT(*) AS NVARCHAR(30)) + ' NC indexes on ' + i.db_schema_object_name AS details, + i.db_schema_object_name + ' (' + CAST (COUNT(*) AS NVARCHAR(30)) + ' indexes)' AS index_definition, + '' AS secret_columns, + REPLACE(CONVERT(NVARCHAR(30),CAST(SUM(total_reads) AS MONEY), 1), N'.00', N'') + N' reads (ALL); ' + + REPLACE(CONVERT(NVARCHAR(30),CAST(SUM(user_updates) AS MONEY), 1), N'.00', N'') + N' writes (ALL); ', + REPLACE(CONVERT(NVARCHAR(30),CAST(MAX(total_rows) AS MONEY), 1), N'.00', N'') + N' rows (MAX)' + + CASE WHEN SUM(total_reserved_MB) > 1024 THEN + N'; ' + CAST(CAST(SUM(total_reserved_MB)/1024. AS NUMERIC(29,1)) AS NVARCHAR(30)) + 'GB (ALL)' + WHEN SUM(total_reserved_MB) > 0 THEN + N'; ' + CAST(CAST(SUM(total_reserved_MB) AS NUMERIC(29,1)) AS NVARCHAR(30)) + 'MB (ALL)' + ELSE '' + END AS index_size_summary + FROM #IndexSanity i + JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id + WHERE index_id NOT IN ( 0, 1 ) + GROUP BY db_schema_object_name, [i].[database_name] + HAVING COUNT(*) >= 10 + ORDER BY i.db_schema_object_name DESC + OPTION ( RECOMPILE ); + + RAISERROR(N'check_id 22: NC indexes with 0 reads. (Borderline) and >= 10,000 writes', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 22 AS check_id, + i.index_sanity_id, + 10 AS Priority, + N'Index Hoarder' AS findings_group, + N'Unused NC Index with High Writes' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/IndexHoarder' AS URL, + N'Reads: 0,' + + N' Writes: ' + + REPLACE(CONVERT(NVARCHAR(30), CAST((i.user_updates) AS MONEY), 1), N'.00', N'') + + N' on: ' + + i.db_schema_object_indexid + AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + sz.index_size_summary + FROM #IndexSanity AS i + JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id + WHERE i.total_reads=0 + AND i.user_updates >= 10000 + 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 + AND @Filter <> 1 /* 1 = "ignore unused */ + ORDER BY i.db_schema_object_indexid + OPTION ( RECOMPILE ); + + + RAISERROR(N'check_id 34: Filtered index definition columns not in index definition', 0,1) WITH NOWAIT; + + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 34 AS check_id, + i.index_sanity_id, + 80 AS Priority, + N'Abnormal Psychology' AS findings_group, + N'Filter Columns Not In Index Definition' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/IndexFeatures' AS URL, + N'The index ' + + QUOTENAME(i.index_name) + + N' on [' + + i.db_schema_object_name + + N'] has a filter on [' + + i.filter_definition + + N'] but is missing [' + + LTRIM(i.filter_columns_not_in_index) + + N'] from the index definition.' + AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + sz.index_size_summary + FROM #IndexSanity i + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + WHERE i.filter_columns_not_in_index IS NOT NULL + ORDER BY i.db_schema_object_indexid + OPTION ( RECOMPILE ); + + ---------------------------------------- + --Self Loathing Indexes : Check_id 40-49 + ---------------------------------------- + + RAISERROR(N'check_id 40: Fillfactor in nonclustered 80 percent or less', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 40 AS check_id, + i.index_sanity_id, + 100 AS Priority, + N'Self Loathing Indexes' AS findings_group, + N'Low Fill Factor on Nonclustered Index' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/SelfLoathing' AS URL, + CAST(fill_factor AS NVARCHAR(10)) + N'% fill factor on ' + db_schema_object_indexid + N'. '+ + CASE WHEN (last_user_update IS NULL OR user_updates < 1) + THEN N'No writes have been made.' + ELSE + N'Last write was ' + CONVERT(NVARCHAR(16),last_user_update,121) + N' and ' + + CAST(user_updates AS NVARCHAR(25)) + N' updates have been made.' + END + AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + sz.index_size_summary + FROM #IndexSanity AS i + JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id + WHERE index_id > 1 + AND fill_factor BETWEEN 1 AND 80 OPTION ( RECOMPILE ); + + RAISERROR(N'check_id 40: Fillfactor in clustered 80 percent or less', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 40 AS check_id, + i.index_sanity_id, + 100 AS Priority, + N'Self Loathing Indexes' AS findings_group, + N'Low Fill Factor on Clustered Index' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/SelfLoathing' AS URL, + N'Fill factor on ' + db_schema_object_indexid + N' is ' + CAST(fill_factor AS NVARCHAR(10)) + N'%. '+ + CASE WHEN (last_user_update IS NULL OR user_updates < 1) + THEN N'No writes have been made.' + ELSE + N'Last write was ' + CONVERT(NVARCHAR(16),last_user_update,121) + N' and ' + + CAST(user_updates AS NVARCHAR(25)) + N' updates have been made.' + END + AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + sz.index_size_summary + FROM #IndexSanity AS i + JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id + WHERE index_id = 1 + AND fill_factor BETWEEN 1 AND 80 OPTION ( RECOMPILE ); + + + RAISERROR(N'check_id 43: Heaps with forwarded records', 0,1) WITH NOWAIT; + WITH heaps_cte + AS ( SELECT [object_id], + [database_id], + [schema_name], + SUM(forwarded_fetch_count) AS forwarded_fetch_count, + SUM(leaf_delete_count) AS leaf_delete_count + FROM #IndexPartitionSanity + GROUP BY [object_id], + [database_id], + [schema_name] + HAVING SUM(forwarded_fetch_count) > 0) + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 43 AS check_id, + i.index_sanity_id, + 100 AS Priority, + N'Self Loathing Indexes' AS findings_group, + N'Heaps with Forwarded Fetches' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/SelfLoathing' AS URL, + CASE WHEN h.forwarded_fetch_count >= 922337203685477 THEN '>= 922,337,203,685,477' + WHEN @DaysUptime < 1 THEN CAST(h.forwarded_fetch_count AS NVARCHAR(256)) + N' forwarded fetches against heap: ' + db_schema_object_indexid + ELSE REPLACE(CONVERT(NVARCHAR(256),CAST(CAST( + (h.forwarded_fetch_count /*/@DaysUptime */) + AS BIGINT) AS MONEY), 1), '.00', '') + END + N' forwarded fetches per day against heap: ' + + db_schema_object_indexid AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + sz.index_size_summary + FROM #IndexSanity i + JOIN heaps_cte h ON i.[object_id] = h.[object_id] + AND i.[database_id] = h.[database_id] + AND i.[schema_name] = h.[schema_name] + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + WHERE i.index_id = 0 + AND h.forwarded_fetch_count / @DaysUptime > 1000 + AND sz.total_reserved_MB >= CASE WHEN NOT (@GetAllDatabases = 1 OR @Mode = 4) THEN @ThresholdMB ELSE sz.total_reserved_MB END + OPTION ( RECOMPILE ); + + RAISERROR(N'check_id 44: Large Heaps with reads or writes.', 0,1) WITH NOWAIT; + WITH heaps_cte + AS ( SELECT [object_id], + [database_id], + [schema_name], + SUM(forwarded_fetch_count) AS forwarded_fetch_count, + SUM(leaf_delete_count) AS leaf_delete_count + FROM #IndexPartitionSanity + GROUP BY [object_id], + [database_id], + [schema_name] + HAVING SUM(forwarded_fetch_count) > 0 + OR SUM(leaf_delete_count) > 0) + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 44 AS check_id, + i.index_sanity_id, + 100 AS Priority, + N'Self Loathing Indexes' AS findings_group, + N'Large Active Heap' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/SelfLoathing' AS URL, + N'Should this table be a heap? ' + db_schema_object_indexid AS details, + i.index_definition, + 'N/A' AS secret_columns, + i.index_usage_summary, + sz.index_size_summary + FROM #IndexSanity i + LEFT JOIN heaps_cte h ON i.[object_id] = h.[object_id] + AND i.[database_id] = h.[database_id] + AND i.[schema_name] = h.[schema_name] + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + WHERE i.index_id = 0 + AND (i.total_reads > 0 OR i.user_updates > 0) + AND sz.total_rows >= 100000 + AND h.[object_id] IS NULL /*don't duplicate the prior check.*/ + OPTION ( RECOMPILE ); + + RAISERROR(N'check_id 45: Medium Heaps with reads or writes.', 0,1) WITH NOWAIT; + WITH heaps_cte + AS ( SELECT [object_id], + [database_id], + [schema_name], + SUM(forwarded_fetch_count) AS forwarded_fetch_count, + SUM(leaf_delete_count) AS leaf_delete_count + FROM #IndexPartitionSanity + GROUP BY [object_id], + [database_id], + [schema_name] + HAVING SUM(forwarded_fetch_count) > 0 + OR SUM(leaf_delete_count) > 0) + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 45 AS check_id, + i.index_sanity_id, + 100 AS Priority, + N'Self Loathing Indexes' AS findings_group, + N'Medium Active heap' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/SelfLoathing' AS URL, + N'Should this table be a heap? ' + db_schema_object_indexid AS details, + i.index_definition, + 'N/A' AS secret_columns, + i.index_usage_summary, + sz.index_size_summary + FROM #IndexSanity i + LEFT JOIN heaps_cte h ON i.[object_id] = h.[object_id] + AND i.[database_id] = h.[database_id] + AND i.[schema_name] = h.[schema_name] + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + WHERE i.index_id = 0 + AND + (i.total_reads > 0 OR i.user_updates > 0) + AND sz.total_rows >= 10000 AND sz.total_rows < 100000 + AND h.[object_id] IS NULL /*don't duplicate the prior check.*/ + OPTION ( RECOMPILE ); + + RAISERROR(N'check_id 46: Small Heaps with reads or writes.', 0,1) WITH NOWAIT; + WITH heaps_cte + AS ( SELECT [object_id], + [database_id], + [schema_name], + SUM(forwarded_fetch_count) AS forwarded_fetch_count, + SUM(leaf_delete_count) AS leaf_delete_count + FROM #IndexPartitionSanity + GROUP BY [object_id], + [database_id], + [schema_name] + HAVING SUM(forwarded_fetch_count) > 0 + OR SUM(leaf_delete_count) > 0) + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 46 AS check_id, + i.index_sanity_id, + 100 AS Priority, + N'Self Loathing Indexes' AS findings_group, + N'Small Active heap' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/SelfLoathing' AS URL, + N'Should this table be a heap? ' + db_schema_object_indexid AS details, + i.index_definition, + 'N/A' AS secret_columns, + i.index_usage_summary, + sz.index_size_summary + FROM #IndexSanity i + LEFT JOIN heaps_cte h ON i.[object_id] = h.[object_id] + AND i.[database_id] = h.[database_id] + AND i.[schema_name] = h.[schema_name] + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + WHERE i.index_id = 0 + AND + (i.total_reads > 0 OR i.user_updates > 0) + AND sz.total_rows < 10000 + AND h.[object_id] IS NULL /*don't duplicate the prior check.*/ + OPTION ( RECOMPILE ); + + RAISERROR(N'check_id 47: Heap with a Nonclustered Primary Key', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 47 AS check_id, + i.index_sanity_id, + 100 AS Priority, + N'Self Loathing Indexes' AS findings_group, + N'Heap with a Nonclustered Primary Key' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/SelfLoathing' AS URL, + db_schema_object_indexid + N' is a HEAP with a Nonclustered Primary Key' AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + sz.index_size_summary + FROM #IndexSanity i + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + WHERE i.index_type = 2 AND i.is_primary_key = 1 + AND EXISTS + ( + SELECT 1/0 + FROM #IndexSanity AS isa + WHERE i.database_id = isa.database_id + AND i.object_id = isa.object_id + AND isa.index_id = 0 + ) + OPTION ( RECOMPILE ); + + RAISERROR(N'check_id 48: Nonclustered indexes with a bad read to write ratio', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 48 AS check_id, + i.index_sanity_id, + 100 AS Priority, + N'Index Hoarder' AS findings_group, + N'NC index with High Writes:Reads' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/IndexHoarder' AS URL, + N'Reads: ' + + REPLACE(CONVERT(NVARCHAR(30), CAST((i.total_reads) AS MONEY), 1), N'.00', N'') + + N' Writes: ' + + REPLACE(CONVERT(NVARCHAR(30), CAST((i.user_updates) AS MONEY), 1), N'.00', N'') + + N' on: ' + + i.db_schema_object_indexid AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + sz.index_size_summary + FROM #IndexSanity i + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + WHERE i.total_reads > 0 /*Not totally unused*/ + AND i.user_updates >= 10000 /*Decent write activity*/ + AND i.total_reads < 10000 + AND ((i.total_reads * 10) < i.user_updates) /*10x more writes than reads*/ + 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 + ORDER BY i.db_schema_object_indexid + OPTION ( RECOMPILE ); + + ---------------------------------------- + --Indexaphobia + --Missing indexes with value >= 5 million: : Check_id 50-59 + ---------------------------------------- + RAISERROR(N'check_id 50: Indexaphobia.', 0,1) WITH NOWAIT; + WITH index_size_cte + AS ( SELECT i.database_id, + i.schema_name, + i.[object_id], + MAX(i.index_sanity_id) AS index_sanity_id, + ISNULL(NULLIF(MAX(DATEDIFF(DAY, i.create_date, SYSDATETIME())), 0), 1) AS create_days, + ISNULL ( + CAST(SUM(CASE WHEN index_id NOT IN (0,1) THEN 1 ELSE 0 END) + AS NVARCHAR(30))+ N' NC indexes exist (' + + CASE WHEN SUM(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) > 1024 + THEN CAST(CAST(SUM(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END )/1024. + + AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'GB); ' + ELSE CAST(SUM(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) + AS NVARCHAR(30)) + N'MB); ' + END + + CASE WHEN MAX(sz.[total_rows]) >= 922337203685477 THEN '>= 922,337,203,685,477' + ELSE REPLACE(CONVERT(NVARCHAR(30),CAST(MAX(sz.[total_rows]) AS MONEY), 1), '.00', '') + END + + + N' Estimated Rows;' + ,N'') AS index_size_summary + FROM #IndexSanity AS i + LEFT JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id AND i.database_id = sz.database_id + WHERE i.is_hypothetical = 0 + AND i.is_disabled = 0 + GROUP BY i.database_id, i.schema_name, i.[object_id]) + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + index_usage_summary, index_size_summary, create_tsql, more_info, sample_query_plan ) + + SELECT check_id, t.index_sanity_id, t.check_id, t.findings_group, t.finding, t.[Database Name], t.URL, t.details, t.[definition], + index_estimated_impact, t.index_size_summary, create_tsql, more_info, sample_query_plan + FROM + ( + SELECT ROW_NUMBER() OVER (ORDER BY magic_benefit_number DESC) AS rownum, + 50 AS check_id, + sz.index_sanity_id, + 40 AS Priority, + N'Indexaphobia' AS findings_group, + N'High Value Missing Index' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/Indexaphobia' AS URL, + mi.[statement] + + N' Est. benefit per day: ' + + CASE WHEN magic_benefit_number >= 922337203685477 THEN '>= 922,337,203,685,477' + ELSE REPLACE(CONVERT(NVARCHAR(256),CAST(CAST( + (magic_benefit_number/@DaysUptime) + AS BIGINT) AS MONEY), 1), '.00', '') + END AS details, + missing_index_details AS [definition], + index_estimated_impact, + sz.index_size_summary, + mi.create_tsql, + mi.more_info, + magic_benefit_number, + mi.is_low, + mi.sample_query_plan + FROM #MissingIndexes mi + LEFT JOIN index_size_cte sz ON mi.[object_id] = sz.object_id + AND mi.database_id = sz.database_id + AND mi.schema_name = sz.schema_name + /* Minimum benefit threshold = 100k/day of uptime OR since table creation date, whichever is lower*/ + WHERE @ShowAllMissingIndexRequests=1 + OR ( @Mode = 4 AND (magic_benefit_number / CASE WHEN sz.create_days < @DaysUptime THEN sz.create_days ELSE @DaysUptime END) >= 100000 ) + OR (magic_benefit_number / CASE WHEN sz.create_days < @DaysUptime THEN sz.create_days ELSE @DaysUptime END) >= 100000 + ) AS t + WHERE t.rownum <= CASE WHEN (@Mode <> 4) THEN 20 ELSE t.rownum END + ORDER BY magic_benefit_number DESC + OPTION ( RECOMPILE ); + + + + RAISERROR(N'check_id 68: Identity columns within 30 percent of the end of range', 0,1) WITH NOWAIT; + -- Allowed Ranges: + --int -2,147,483,648 to 2,147,483,647 + --smallint -32,768 to 32,768 + --tinyint 0 to 255 + + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 68 AS check_id, + i.index_sanity_id, + 80 AS Priority, + N'Abnormal Psychology' AS findings_group, + N'Identity Column Within ' + + CAST (calc1.percent_remaining AS NVARCHAR(256)) + + N' Percent End of Range' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, + i.db_schema_object_name + N'.' + QUOTENAME(ic.column_name) + + N' is an identity with type ' + ic.system_type_name + + N', last value of ' + + ISNULL((CONVERT(NVARCHAR(256),CAST(ic.last_value AS DECIMAL(38,0)), 1)),N'NULL') + + N', seed of ' + + ISNULL((CONVERT(NVARCHAR(256),CAST(ic.seed_value AS DECIMAL(38,0)), 1)),N'NULL') + + N', increment of ' + CAST(ic.increment_value AS NVARCHAR(256)) + + N', and range of ' + + CASE ic.system_type_name WHEN 'int' THEN N'+/- 2,147,483,647' + WHEN 'smallint' THEN N'+/- 32,768' + WHEN 'tinyint' THEN N'0 to 255' + ELSE 'unknown' + END + AS details, + i.index_definition, + secret_columns, + ISNULL(i.index_usage_summary,''), + ISNULL(ip.index_size_summary,'') + FROM #IndexSanity i + JOIN #IndexColumns ic ON + i.object_id=ic.object_id + AND i.database_id = ic.database_id + AND i.schema_name = ic.schema_name + AND i.index_id IN (0,1) /* heaps and cx only */ + AND ic.is_identity=1 + AND ic.system_type_name IN ('tinyint', 'smallint', 'int') + JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id + CROSS APPLY ( + SELECT CAST(CASE WHEN ic.increment_value >= 0 + THEN + CASE ic.system_type_name + WHEN 'int' THEN (2147483647 - (ISNULL(ic.last_value,ic.seed_value) + ic.increment_value)) / 2147483647.*100 + WHEN 'smallint' THEN (32768 - (ISNULL(ic.last_value,ic.seed_value) + ic.increment_value)) / 32768.*100 + WHEN 'tinyint' THEN ( 255 - (ISNULL(ic.last_value,ic.seed_value) + ic.increment_value)) / 255.*100 + ELSE 999 + END + ELSE --ic.increment_value is negative + CASE ic.system_type_name + WHEN 'int' THEN ABS(-2147483647 - (ISNULL(ic.last_value,ic.seed_value) + ic.increment_value)) / 2147483647.*100 + WHEN 'smallint' THEN ABS(-32768 - (ISNULL(ic.last_value,ic.seed_value) + ic.increment_value)) / 32768.*100 + WHEN 'tinyint' THEN ABS( 0 - (ISNULL(ic.last_value,ic.seed_value) + ic.increment_value)) / 255.*100 + ELSE -1 + END + END AS NUMERIC(5,1)) AS percent_remaining + ) AS calc1 + WHERE i.index_id IN (1,0) + AND calc1.percent_remaining <= 30 + OPTION (RECOMPILE); + + + RAISERROR(N'check_id 72: Columnstore indexes with Trace Flag 834', 0,1) WITH NOWAIT; + IF EXISTS (SELECT * FROM #IndexSanity WHERE index_type IN (5,6)) + AND EXISTS (SELECT * FROM #TraceStatus WHERE TraceFlag = 834 AND status = 1) + BEGIN + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 72 AS check_id, + i.index_sanity_id, + 80 AS Priority, + N'Abnormal Psychology' AS findings_group, + 'Columnstore Indexes with Trace Flag 834' AS finding, + [database_name] AS [Database Name], + N'https://support.microsoft.com/en-us/kb/3210239' AS URL, + i.db_schema_object_indexid AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + ISNULL(sz.index_size_summary,'') AS index_size_summary + FROM #IndexSanity AS i + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + WHERE i.index_type IN (5,6) + OPTION ( RECOMPILE ); + END; + + ---------------------------------------- + --Statistics Info: Check_id 90-99 + ---------------------------------------- + + RAISERROR(N'check_id 90: Outdated 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 ) + SELECT 90 AS check_id, + 90 AS Priority, + 'Functioning Statistaholics' AS findings_group, + 'Statistics Not Updated Recently', + s.database_name, + 'https://www.brentozar.com/go/stats' AS URL, + 'Statistics on this table were last updated ' + + CASE WHEN s.last_statistics_update IS NULL THEN N' NEVER ' + ELSE CONVERT(NVARCHAR(20), s.last_statistics_update) + + ' have had ' + CONVERT(NVARCHAR(100), s.modification_counter) + + ' modifications in that time, which is ' + + CONVERT(NVARCHAR(100), s.percent_modifications) + + '% of the table.' + END 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.last_statistics_update <= CONVERT(DATETIME, GETDATE() - 30) + AND s.percent_modifications >= 10. + AND s.rows >= 10000 + OPTION ( RECOMPILE ); + + RAISERROR(N'check_id 91: Statistics with a low 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 91 AS check_id, + 90 AS Priority, + 'Functioning Statistaholics' AS findings_group, + 'Low Sampling Rates', + s.database_name, + 'https://www.brentozar.com/go/stats' AS URL, + 'Only ' + CONVERT(NVARCHAR(100), s.percent_sampled) + '% of the rows were sampled during the last statistics update. This may lead to poor cardinality estimates.' 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.rows BETWEEN 10000 AND 1000000 AND s.percent_sampled < 10) + OR (s.rows > 1000000 AND s.percent_sampled < 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 ) + SELECT 92 AS check_id, + 90 AS Priority, + 'Functioning Statistaholics' AS findings_group, + 'Statistics With NO RECOMPUTE', + s.database_name, + 'https://www.brentozar.com/go/stats' AS URL, + 'The statistic ' + QUOTENAME(s.statistics_name) + ' is set to not recompute. This can be helpful if data is really skewed, but harmful if you expect automatic statistics updates.' 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.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 ) + SELECT 94 AS check_id, + 100 AS Priority, + 'Serial Forcer' AS findings_group, + 'Check Constraint with Scalar UDF' AS finding, + cc.database_name, + 'https://www.brentozar.com/go/computedscalar' AS URL, + 'The check constraint ' + QUOTENAME(cc.constraint_name) + ' on ' + QUOTENAME(cc.schema_name) + '.' + QUOTENAME(cc.table_name) + ' is based on ' + cc.definition + + '. That indicates it may reference a scalar function, or a CLR function with data access, which can cause all queries and maintenance to run serially.' AS details, + cc.column_definition, + 'N/A' AS secret_columns, + 'N/A' AS index_usage_summary, + 'N/A' AS index_size_summary + FROM #CheckConstraints AS cc + WHERE cc.is_function = 1 + OPTION ( RECOMPILE ); + + RAISERROR(N'check_id 99: Computed Columns 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 ) + SELECT 99 AS check_id, + 100 AS Priority, + 'Serial Forcer' AS findings_group, + 'Computed Column with Scalar UDF' AS finding, + cc.database_name, + 'https://www.brentozar.com/go/serialudf' AS URL, + 'The computed column ' + QUOTENAME(cc.column_name) + ' on ' + QUOTENAME(cc.schema_name) + '.' + QUOTENAME(cc.table_name) + ' is based on ' + cc.definition + + '. That indicates it may reference a scalar function, or a CLR function with data access, which can cause all queries and maintenance to run serially.' AS details, + cc.column_definition, + 'N/A' AS secret_columns, + 'N/A' AS index_usage_summary, + 'N/A' AS index_size_summary + FROM #ComputedColumns AS cc + WHERE cc.is_function = 1 + OPTION ( RECOMPILE ); + + + + END /* IF @Mode IN (0, 4) DIAGNOSE priorities 1-100 */ + + + + + + + + + IF @Mode = 4 /* DIAGNOSE*/ + BEGIN; + RAISERROR(N'@Mode=4, running rules for priorities 101+.', 0,1) WITH NOWAIT; + + RAISERROR(N'check_id 21: More Than 5 Percent NC Indexes Are Unused', 0,1) WITH NOWAIT; + DECLARE @percent_NC_indexes_unused NUMERIC(29,1); + DECLARE @NC_indexes_unused_reserved_MB NUMERIC(29,1); + + SELECT @percent_NC_indexes_unused = ( 100.00 * SUM(CASE + WHEN total_reads = 0 + THEN 1 + ELSE 0 + END) ) / COUNT(*), + @NC_indexes_unused_reserved_MB = SUM(CASE + WHEN total_reads = 0 + THEN sz.total_reserved_MB + ELSE 0 + END) + FROM #IndexSanity i + 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()) + 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, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 21 AS check_id, + MAX(i.index_sanity_id) AS index_sanity_id, + 150 AS Priority, + N'Index Hoarder' AS findings_group, + N'More Than 5 Percent NC Indexes Are Unused' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/IndexHoarder' AS URL, + CAST (@percent_NC_indexes_unused AS NVARCHAR(30)) + N' percent NC indexes (' + CAST(COUNT(*) AS NVARCHAR(10)) + N') unused. ' + + N'These take up ' + CAST (@NC_indexes_unused_reserved_MB AS NVARCHAR(30)) + N'MB of space.' AS details, + i.database_name + ' (' + CAST (COUNT(*) AS NVARCHAR(30)) + N' indexes)' AS index_definition, + '' AS secret_columns, + CAST(SUM(total_reads) AS NVARCHAR(256)) + N' reads (ALL); ' + + CAST(SUM([user_updates]) AS NVARCHAR(256)) + N' writes (ALL)' AS index_usage_summary, + + REPLACE(CONVERT(NVARCHAR(30),CAST(MAX([total_rows]) AS MONEY), 1), '.00', '') + N' rows (MAX)' + + CASE WHEN SUM(total_reserved_MB) > 1024 THEN + N'; ' + CAST(CAST(SUM(total_reserved_MB)/1024. AS NUMERIC(29,1)) AS NVARCHAR(30)) + 'GB (ALL)' + WHEN SUM(total_reserved_MB) > 0 THEN + N'; ' + CAST(CAST(SUM(total_reserved_MB) AS NUMERIC(29,1)) AS NVARCHAR(30)) + 'MB (ALL)' + ELSE '' + END AS index_size_summary + FROM #IndexSanity i + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + 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()) + GROUP BY i.database_name + OPTION ( RECOMPILE ); + + RAISERROR(N'check_id 23: Indexes with 7 or more columns. (Borderline)', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 23 AS check_id, + i.index_sanity_id, + 150 AS Priority, + N'Index Hoarder' AS findings_group, + N'Borderline: Wide Indexes (7 or More Columns)' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/IndexHoarder' AS URL, + CAST(count_key_columns + count_included_columns AS NVARCHAR(10)) + ' columns on ' + + i.db_schema_object_indexid AS details, i.index_definition, + i.secret_columns, + i.index_usage_summary, + sz.index_size_summary + FROM #IndexSanity AS i + JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id + WHERE ( count_key_columns + count_included_columns ) >= 7 + OPTION ( RECOMPILE ); + + RAISERROR(N'check_id 24: Wide clustered indexes (> 3 columns or > 16 bytes).', 0,1) WITH NOWAIT; + WITH count_columns AS ( + SELECT database_id, [object_id], + SUM(CASE max_length WHEN -1 THEN 0 ELSE max_length END) AS sum_max_length + FROM #IndexColumns ic + WHERE index_id IN (1,0) /*Heap or clustered only*/ + AND key_ordinal > 0 + GROUP BY database_id, object_id + ) + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 24 AS check_id, + i.index_sanity_id, + 150 AS Priority, + N'Index Hoarder' AS findings_group, + N'Wide Clustered Index (> 3 columns OR > 16 bytes)' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/IndexHoarder' AS URL, + CAST (i.count_key_columns AS NVARCHAR(10)) + N' columns with potential size of ' + + CAST(cc.sum_max_length AS NVARCHAR(10)) + + N' bytes in clustered index:' + i.db_schema_object_name + + N'. ' + + (SELECT CAST(COUNT(*) AS NVARCHAR(23)) + FROM #IndexSanity i2 + WHERE i2.[object_id]=i.[object_id] + AND i2.database_id = i.database_id + AND i2.index_id <> 1 + AND i2.is_disabled=0 + AND i2.is_hypothetical=0) + + N' NC indexes on the table.' + AS details, + i.index_definition, + secret_columns, + i.index_usage_summary, + ip.index_size_summary + FROM #IndexSanity i + JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id + JOIN count_columns AS cc ON i.[object_id]=cc.[object_id] + AND i.database_id = cc.database_id + WHERE index_id = 1 /* clustered only */ + AND + (count_key_columns > 3 /*More than three key columns.*/ + OR cc.sum_max_length > 16 /*More than 16 bytes in key */) + AND i.is_CX_columnstore = 0 + ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); + + RAISERROR(N'check_id 25: Addicted to nullable columns.', 0,1) WITH NOWAIT; + WITH count_columns AS ( + SELECT [object_id], + [database_id], + [schema_name], + SUM(CASE is_nullable WHEN 1 THEN 0 ELSE 1 END) AS non_nullable_columns, + COUNT(*) AS total_columns + FROM #IndexColumns ic + WHERE index_id IN (1,0) /*Heap or clustered only*/ + GROUP BY [object_id], + [database_id], + [schema_name] + ) + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 25 AS check_id, + i.index_sanity_id, + 200 AS Priority, + N'Index Hoarder' AS findings_group, + N'Addicted to Nulls' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/IndexHoarder' AS URL, + i.db_schema_object_name + + N' allows null in ' + CAST((total_columns-non_nullable_columns) AS NVARCHAR(10)) + + N' of ' + CAST(total_columns AS NVARCHAR(10)) + + N' columns.' AS details, + i.index_definition, + secret_columns, + ISNULL(i.index_usage_summary,''), + ISNULL(ip.index_size_summary,'') + FROM #IndexSanity i + JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id + JOIN count_columns AS cc ON i.[object_id]=cc.[object_id] + AND cc.database_id = ip.database_id + AND cc.[schema_name] = ip.[schema_name] + WHERE i.index_id IN (1,0) + AND cc.non_nullable_columns < 2 + AND cc.total_columns > 3 + ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); + + RAISERROR(N'check_id 26: Wide tables (35+ cols or > 2000 non-LOB bytes).', 0,1) WITH NOWAIT; + WITH count_columns AS ( + SELECT [object_id], + [database_id], + [schema_name], + SUM(CASE max_length WHEN -1 THEN 1 ELSE 0 END) AS count_lob_columns, + SUM(CASE max_length WHEN -1 THEN 0 ELSE max_length END) AS sum_max_length, + COUNT(*) AS total_columns + FROM #IndexColumns ic + WHERE index_id IN (1,0) /*Heap or clustered only*/ + GROUP BY [object_id], + [database_id], + [schema_name] + ) + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 26 AS check_id, + i.index_sanity_id, + 150 AS Priority, + N'Index Hoarder' AS findings_group, + N'Wide Tables: 35+ cols or > 2000 non-LOB bytes' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/IndexHoarder' AS URL, + i.db_schema_object_name + + N' has ' + CAST((total_columns) AS NVARCHAR(10)) + + N' total columns with a max possible width of ' + CAST(sum_max_length AS NVARCHAR(10)) + + N' bytes.' + + CASE WHEN count_lob_columns > 0 THEN CAST((count_lob_columns) AS NVARCHAR(10)) + + ' columns are LOB types.' ELSE '' + END + AS details, + i.index_definition, + secret_columns, + ISNULL(i.index_usage_summary,''), + ISNULL(ip.index_size_summary,'') + FROM #IndexSanity i + JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id + JOIN count_columns AS cc ON i.[object_id]=cc.[object_id] + AND cc.database_id = i.database_id + AND cc.[schema_name] = i.[schema_name] + WHERE i.index_id IN (1,0) + AND + (cc.total_columns >= 35 OR + cc.sum_max_length >= 2000) + ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); + + RAISERROR(N'check_id 27: Addicted to strings.', 0,1) WITH NOWAIT; + WITH count_columns AS ( + SELECT [object_id], + [database_id], + [schema_name], + SUM(CASE WHEN system_type_name IN ('varchar','nvarchar','char') OR max_length=-1 THEN 1 ELSE 0 END) AS string_or_LOB_columns, + COUNT(*) AS total_columns + FROM #IndexColumns ic + WHERE index_id IN (1,0) /*Heap or clustered only*/ + GROUP BY [object_id], + [database_id], + [schema_name] + ) + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 27 AS check_id, + i.index_sanity_id, + 200 AS Priority, + N'Index Hoarder' AS findings_group, + N'Addicted to strings' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/IndexHoarder' AS URL, + i.db_schema_object_name + + N' uses string or LOB types for ' + CAST((string_or_LOB_columns) AS NVARCHAR(10)) + + N' of ' + CAST(total_columns AS NVARCHAR(10)) + + N' columns. Check if data types are valid.' AS details, + i.index_definition, + secret_columns, + ISNULL(i.index_usage_summary,''), + ISNULL(ip.index_size_summary,'') + FROM #IndexSanity i + JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id + JOIN count_columns AS cc ON i.[object_id]=cc.[object_id] + AND cc.database_id = i.database_id + AND cc.[schema_name] = i.[schema_name] + CROSS APPLY (SELECT cc.total_columns - string_or_LOB_columns AS non_string_or_lob_columns) AS calc1 + WHERE i.index_id IN (1,0) + AND calc1.non_string_or_lob_columns <= 1 + AND cc.total_columns > 3 + ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); + + RAISERROR(N'check_id 28: Non-unique clustered index.', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 28 AS check_id, + i.index_sanity_id, + 150 AS Priority, + N'Index Hoarder' AS findings_group, + N'Non-Unique Clustered Index' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/IndexHoarder' AS URL, + N'Uniquifiers will be required! Clustered index: ' + i.db_schema_object_name + + N' and all NC indexes. ' + + (SELECT CAST(COUNT(*) AS NVARCHAR(23)) + FROM #IndexSanity i2 + WHERE i2.[object_id]=i.[object_id] + AND i2.database_id = i.database_id + AND i2.index_id <> 1 + AND i2.is_disabled=0 + AND i2.is_hypothetical=0) + + N' NC indexes on the table.' + AS details, + i.index_definition, + secret_columns, + i.index_usage_summary, + ip.index_size_summary + FROM #IndexSanity i + JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id + WHERE index_id = 1 /* clustered only */ + AND is_unique=0 /* not unique */ + AND is_CX_columnstore=0 /* not a clustered columnstore-- no unique option on those */ + ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); + + RAISERROR(N'check_id 29: NC indexes with 0 reads. (Borderline) and < 10,000 writes', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 29 AS check_id, + i.index_sanity_id, + 150 AS Priority, + N'Index Hoarder' AS findings_group, + N'Unused NC index with Low Writes' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/IndexHoarder' AS URL, + N'0 reads: ' + i.db_schema_object_indexid AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + sz.index_size_summary + FROM #IndexSanity AS i + JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id + WHERE i.total_reads=0 + AND i.user_updates < 10000 + 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()) + ORDER BY i.db_schema_object_indexid + OPTION ( RECOMPILE ); + + + ---------------------------------------- + --Feature-Phobic Indexes: Check_id 30-39 + ---------------------------------------- + RAISERROR(N'check_id 30: No indexes with includes', 0,1) WITH NOWAIT; + /* This does not work the way you'd expect with @GetAllDatabases = 1. For details: + https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/825 + */ + + SELECT database_name, + SUM(CASE WHEN count_included_columns > 0 THEN 1 ELSE 0 END) AS number_indexes_with_includes, + 100.* SUM(CASE WHEN count_included_columns > 0 THEN 1 ELSE 0 END) / ( 1.0 * COUNT(*) ) AS percent_indexes_with_includes + INTO #index_includes + FROM #IndexSanity + WHERE is_hypothetical = 0 + AND is_disabled = 0 + AND NOT (@GetAllDatabases = 1 OR @Mode = 0) + GROUP BY database_name; + + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 30 AS check_id, + NULL AS index_sanity_id, + 250 AS Priority, + N'Feature-Phobic Indexes' AS findings_group, + database_name AS [Database Name], + N'No Indexes Use Includes' AS finding, 'https://www.brentozar.com/go/IndexFeatures' AS URL, + N'No Indexes Use Includes' AS details, + database_name + N' (Entire database)' AS index_definition, + N'' AS secret_columns, + N'N/A' AS index_usage_summary, + N'N/A' AS index_size_summary + FROM #index_includes + WHERE number_indexes_with_includes = 0 + OPTION ( RECOMPILE ); + + RAISERROR(N'check_id 31: < 3 percent of indexes have includes', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 31 AS check_id, + NULL AS index_sanity_id, + 250 AS Priority, + N'Feature-Phobic Indexes' AS findings_group, + N'Few Indexes Use Includes' AS findings, + database_name AS [Database Name], + N'https://www.brentozar.com/go/IndexFeatures' AS URL, + N'Only ' + CAST(percent_indexes_with_includes AS NVARCHAR(20)) + '% of indexes have includes' AS details, + N'Entire database' AS index_definition, + N'' AS secret_columns, + N'N/A' AS index_usage_summary, + N'N/A' AS index_size_summary + FROM #index_includes + WHERE number_indexes_with_includes > 0 AND percent_indexes_with_includes <= 3 + OPTION ( RECOMPILE ); + + RAISERROR(N'check_id 32: filtered indexes and indexed views', 0,1) WITH NOWAIT; + + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT DISTINCT + 32 AS check_id, + NULL AS index_sanity_id, + 250 AS Priority, + N'Feature-Phobic Indexes' AS findings_group, + N'No Filtered Indexes or Indexed Views' AS finding, + i.database_name AS [Database Name], + N'https://www.brentozar.com/go/IndexFeatures' AS URL, + N'These are NOT always needed-- but do you know when you would use them?' AS details, + i.database_name + N' (Entire database)' AS index_definition, + N'' AS secret_columns, + N'N/A' AS index_usage_summary, + N'N/A' AS index_size_summary + FROM #IndexSanity i + WHERE i.database_name NOT IN ( + SELECT database_name + FROM #IndexSanity + WHERE filter_definition <> '' ) + AND i.database_name NOT IN ( + SELECT database_name + FROM #IndexSanity + WHERE is_indexed_view = 1 ) + OPTION ( RECOMPILE ); + + RAISERROR(N'check_id 33: Potential filtered indexes based on column names.', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 33 AS check_id, + i.index_sanity_id AS index_sanity_id, + 250 AS Priority, + N'Feature-Phobic Indexes' AS findings_group, + N'Potential Filtered Index (Based on Column Name)' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/IndexFeatures' AS URL, + N'A column name in this index suggests it might be a candidate for filtering (is%, %archive%, %active%, %flag%)' AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + sz.index_size_summary + FROM #IndexColumns ic + JOIN #IndexSanity i ON ic.[object_id]=i.[object_id] + AND ic.database_id =i.database_id + AND ic.schema_name = i.schema_name + AND ic.[index_id]=i.[index_id] + AND i.[index_id] > 1 /* non-clustered index */ + JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id + WHERE (column_name LIKE 'is%' + OR column_name LIKE '%archive%' + OR column_name LIKE '%active%' + OR column_name LIKE '%flag%') + OPTION ( RECOMPILE ); + + RAISERROR(N'check_id 41: Hypothetical indexes ', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 41 AS check_id, + i.index_sanity_id, + 150 AS Priority, + N'Self Loathing Indexes' AS findings_group, + N'Hypothetical Index' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/SelfLoathing' AS URL, + N'Hypothetical Index: ' + db_schema_object_indexid AS details, + i.index_definition, + i.secret_columns, + N'' AS index_usage_summary, + N'' AS index_size_summary + FROM #IndexSanity AS i + WHERE is_hypothetical = 1 + OPTION ( RECOMPILE ); + + + RAISERROR(N'check_id 42: Disabled indexes', 0,1) WITH NOWAIT; + --Note: disabled NC indexes will have O rows in #IndexSanitySize! + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 42 AS check_id, + index_sanity_id, + 150 AS Priority, + N'Self Loathing Indexes' AS findings_group, + N'Disabled Index' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/SelfLoathing' AS URL, + N'Disabled Index:' + db_schema_object_indexid AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + 'DISABLED' AS index_size_summary + FROM #IndexSanity AS i + WHERE is_disabled = 1 + OPTION ( RECOMPILE ); + + RAISERROR(N'check_id 49: Heaps with deletes', 0,1) WITH NOWAIT; + WITH heaps_cte + AS ( SELECT [object_id], + [database_id], + [schema_name], + SUM(leaf_delete_count) AS leaf_delete_count + FROM #IndexPartitionSanity + GROUP BY [object_id], + [database_id], + [schema_name] + HAVING SUM(forwarded_fetch_count) < 1000 * @DaysUptime /* Only alert about indexes with no forwarded fetches - we already alerted about those in check_id 43 */ + AND SUM(leaf_delete_count) > 0) + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 49 AS check_id, + i.index_sanity_id, + 200 AS Priority, + N'Self Loathing Indexes' AS findings_group, + N'Heaps with Deletes' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/SelfLoathing' AS URL, + CAST(h.leaf_delete_count AS NVARCHAR(256)) + N' deletes against heap:' + + db_schema_object_indexid AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + sz.index_size_summary + FROM #IndexSanity i + JOIN heaps_cte h ON i.[object_id] = h.[object_id] + AND i.[database_id] = h.[database_id] + AND i.[schema_name] = h.[schema_name] + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + WHERE i.index_id = 0 + AND sz.total_reserved_MB >= CASE WHEN NOT (@GetAllDatabases = 1 OR @Mode = 4) THEN @ThresholdMB ELSE sz.total_reserved_MB END + OPTION ( RECOMPILE ); + + ---------------------------------------- + --Abnormal Psychology : Check_id 60-79 + ---------------------------------------- + RAISERROR(N'check_id 60: XML indexes', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 60 AS check_id, + i.index_sanity_id, + 150 AS Priority, + N'Abnormal Psychology' AS findings_group, + N'XML Index' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, + i.db_schema_object_indexid AS details, + i.index_definition, + i.secret_columns, + N'' AS index_usage_summary, + ISNULL(sz.index_size_summary,'') AS index_size_summary + FROM #IndexSanity AS i + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + WHERE i.is_XML = 1 + OPTION ( RECOMPILE ); + + RAISERROR(N'check_id 61: Columnstore indexes', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 61 AS check_id, + i.index_sanity_id, + 150 AS Priority, + N'Abnormal Psychology' AS findings_group, + CASE WHEN i.is_NC_columnstore=1 + THEN N'NC Columnstore Index' + ELSE N'Clustered Columnstore Index' + END AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, + i.db_schema_object_indexid AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + ISNULL(sz.index_size_summary,'') AS index_size_summary + FROM #IndexSanity AS i + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + WHERE i.is_NC_columnstore = 1 OR i.is_CX_columnstore=1 + OPTION ( RECOMPILE ); + + + RAISERROR(N'check_id 62: Spatial indexes', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 62 AS check_id, + i.index_sanity_id, + 150 AS Priority, + N'Abnormal Psychology' AS findings_group, + N'Spatial Index' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, + i.db_schema_object_indexid AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + ISNULL(sz.index_size_summary,'') AS index_size_summary + FROM #IndexSanity AS i + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + WHERE i.is_spatial = 1 + OPTION ( RECOMPILE ); + + RAISERROR(N'check_id 63: Compressed indexes', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 63 AS check_id, + i.index_sanity_id, + 150 AS Priority, + N'Abnormal Psychology' AS findings_group, + N'Compressed Index' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, + i.db_schema_object_indexid + N'. COMPRESSION: ' + sz.data_compression_desc AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + ISNULL(sz.index_size_summary,'') AS index_size_summary + FROM #IndexSanity AS i + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + WHERE sz.data_compression_desc LIKE '%PAGE%' OR sz.data_compression_desc LIKE '%ROW%' + OPTION ( RECOMPILE ); + + RAISERROR(N'check_id 64: Partitioned', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 64 AS check_id, + i.index_sanity_id, + 150 AS Priority, + N'Abnormal Psychology' AS findings_group, + N'Partitioned Index' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, + i.db_schema_object_indexid AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + ISNULL(sz.index_size_summary,'') AS index_size_summary + FROM #IndexSanity AS i + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + WHERE i.partition_key_column_name IS NOT NULL + OPTION ( RECOMPILE ); + + RAISERROR(N'check_id 65: Non-Aligned Partitioned', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 65 AS check_id, + i.index_sanity_id, + 150 AS Priority, + N'Abnormal Psychology' AS findings_group, + N'Non-Aligned Index on a Partitioned Table' AS finding, + i.[database_name] AS [Database Name], + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, + i.db_schema_object_indexid AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + ISNULL(sz.index_size_summary,'') AS index_size_summary + FROM #IndexSanity AS i + JOIN #IndexSanity AS iParent ON + i.[object_id]=iParent.[object_id] + AND i.database_id = iParent.database_id + AND i.schema_name = iParent.schema_name + AND iParent.index_id IN (0,1) /* could be a partitioned heap or clustered table */ + AND iParent.partition_key_column_name IS NOT NULL /* parent is partitioned*/ + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + WHERE i.partition_key_column_name IS NULL + OPTION ( RECOMPILE ); + + RAISERROR(N'check_id 66: Recently created tables/indexes (1 week)', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 66 AS check_id, + i.index_sanity_id, + 200 AS Priority, + N'Abnormal Psychology' AS findings_group, + N'Recently Created Tables/Indexes (1 week)' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, + i.db_schema_object_indexid + N' was created on ' + + CONVERT(NVARCHAR(16),i.create_date,121) + + N'. Tables/indexes which are dropped/created regularly require special methods for index tuning.' + AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + ISNULL(sz.index_size_summary,'') AS index_size_summary + FROM #IndexSanity AS i + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + WHERE i.create_date >= DATEADD(dd,-7,GETDATE()) + OPTION ( RECOMPILE ); + + RAISERROR(N'check_id 67: Recently modified tables/indexes (2 days)', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 67 AS check_id, + i.index_sanity_id, + 200 AS Priority, + N'Abnormal Psychology' AS findings_group, + N'Recently Modified Tables/Indexes (2 days)' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, + i.db_schema_object_indexid + N' was modified on ' + + CONVERT(NVARCHAR(16),i.modify_date,121) + + N'. A large amount of recently modified indexes may mean a lot of rebuilds are occurring each night.' + AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + ISNULL(sz.index_size_summary,'') AS index_size_summary + FROM #IndexSanity AS i + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + WHERE i.modify_date > DATEADD(dd,-2,GETDATE()) + AND /*Exclude recently created tables.*/ + i.create_date < DATEADD(dd,-7,GETDATE()) + OPTION ( RECOMPILE ); + + RAISERROR(N'check_id 69: Column collation does not match database collation', 0,1) WITH NOWAIT; + WITH count_columns AS ( + SELECT [object_id], + database_id, + schema_name, + COUNT(*) AS column_count + FROM #IndexColumns ic + WHERE index_id IN (1,0) /*Heap or clustered only*/ + AND collation_name <> @collation + GROUP BY [object_id], + database_id, + schema_name + ) + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 69 AS check_id, + i.index_sanity_id, + 150 AS Priority, + N'Abnormal Psychology' AS findings_group, + N'Column Collation Does Not Match Database Collation' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, + i.db_schema_object_name + + N' has ' + CAST(column_count AS NVARCHAR(20)) + + N' column' + CASE WHEN column_count > 1 THEN 's' ELSE '' END + + N' with a different collation than the db collation of ' + + @collation AS details, + i.index_definition, + secret_columns, + ISNULL(i.index_usage_summary,''), + ISNULL(ip.index_size_summary,'') + FROM #IndexSanity i + JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id + JOIN count_columns AS cc ON i.[object_id]=cc.[object_id] + AND cc.database_id = i.database_id + AND cc.schema_name = i.schema_name + WHERE i.index_id IN (1,0) + ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); + + RAISERROR(N'check_id 70: Replicated columns', 0,1) WITH NOWAIT; + WITH count_columns AS ( + SELECT [object_id], + database_id, + schema_name, + COUNT(*) AS column_count, + SUM(CASE is_replicated WHEN 1 THEN 1 ELSE 0 END) AS replicated_column_count + FROM #IndexColumns ic + WHERE index_id IN (1,0) /*Heap or clustered only*/ + GROUP BY object_id, + database_id, + schema_name + ) + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 70 AS check_id, + i.index_sanity_id, + 200 AS Priority, + N'Abnormal Psychology' AS findings_group, + N'Replicated Columns' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, + i.db_schema_object_name + + N' has ' + CAST(replicated_column_count AS NVARCHAR(20)) + + N' out of ' + CAST(column_count AS NVARCHAR(20)) + + N' column' + CASE WHEN column_count > 1 THEN 's' ELSE '' END + + N' in one or more publications.' + AS details, + i.index_definition, + secret_columns, + ISNULL(i.index_usage_summary,''), + ISNULL(ip.index_size_summary,'') + FROM #IndexSanity i + JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id + JOIN count_columns AS cc ON i.[object_id]=cc.[object_id] + AND i.database_id = cc.database_id + AND i.schema_name = cc.schema_name + WHERE i.index_id IN (1,0) + AND replicated_column_count > 0 + ORDER BY i.db_schema_object_name DESC + OPTION ( RECOMPILE ); + + RAISERROR(N'check_id 71: Cascading updates or cascading deletes.', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary, more_info ) + SELECT 71 AS check_id, + NULL AS index_sanity_id, + 150 AS Priority, + N'Abnormal Psychology' AS findings_group, + N'Cascading Updates or Deletes' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, + N'Foreign Key ' + QUOTENAME(foreign_key_name) + + N' on ' + QUOTENAME(parent_object_name) + N'(' + LTRIM(parent_fk_columns) + N')' + + N' referencing ' + QUOTENAME(referenced_object_name) + N'(' + LTRIM(referenced_fk_columns) + N')' + + N' has settings:' + + CASE [delete_referential_action_desc] WHEN N'NO_ACTION' THEN N'' ELSE N' ON DELETE ' +[delete_referential_action_desc] END + + CASE [update_referential_action_desc] WHEN N'NO_ACTION' THEN N'' ELSE N' ON UPDATE ' + [update_referential_action_desc] END + AS details, + [fk].[database_name] AS index_definition, + N'N/A' AS secret_columns, + N'N/A' AS index_usage_summary, + N'N/A' AS index_size_summary, + (SELECT TOP 1 more_info FROM #IndexSanity i WHERE i.object_id=fk.parent_object_id AND i.database_id = fk.database_id AND i.schema_name = fk.schema_name) + AS more_info + FROM #ForeignKeys fk + WHERE ([delete_referential_action_desc] <> N'NO_ACTION' + OR [update_referential_action_desc] <> N'NO_ACTION') + OPTION ( RECOMPILE ); + + RAISERROR(N'check_id 72: Unindexed foreign keys.', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary, more_info ) + SELECT 72 AS check_id, + NULL AS index_sanity_id, + 150 AS Priority, + N'Abnormal Psychology' AS findings_group, + N'Unindexed Foreign Keys' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, + N'Foreign Key ' + QUOTENAME(foreign_key_name) + + N' on ' + QUOTENAME(parent_object_name) + N'' + + N' referencing ' + QUOTENAME(referenced_object_name) + N'' + + N' does not appear to have a supporting index.' AS details, + N'N/A' AS index_definition, + N'N/A' AS secret_columns, + N'N/A' AS index_usage_summary, + N'N/A' AS index_size_summary, + (SELECT TOP 1 more_info FROM #IndexSanity i WHERE i.object_id=fk.parent_object_id AND i.database_id = fk.database_id AND i.schema_name = fk.schema_name) + AS more_info + FROM #UnindexedForeignKeys AS fk + OPTION ( RECOMPILE ); + + + RAISERROR(N'check_id 73: In-Memory OLTP', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 73 AS check_id, + i.index_sanity_id, + 150 AS Priority, + N'Abnormal Psychology' AS findings_group, + N'In-Memory OLTP' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, + i.db_schema_object_indexid AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + ISNULL(sz.index_size_summary,'') AS index_size_summary + FROM #IndexSanity AS i + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + WHERE i.is_in_memory_oltp = 1 + OPTION ( RECOMPILE ); + + RAISERROR(N'check_id 74: Identity column with unusual seed', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 74 AS check_id, + i.index_sanity_id, + 200 AS Priority, + N'Abnormal Psychology' AS findings_group, + N'Identity Column Using a Negative Seed or Increment Other Than 1' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, + i.db_schema_object_name + N'.' + QUOTENAME(ic.column_name) + + N' is an identity with type ' + ic.system_type_name + + N', last value of ' + + ISNULL((CONVERT(NVARCHAR(256),CAST(ic.last_value AS DECIMAL(38,0)), 1)),N'NULL') + + N', seed of ' + + ISNULL((CONVERT(NVARCHAR(256),CAST(ic.seed_value AS DECIMAL(38,0)), 1)),N'NULL') + + N', increment of ' + CAST(ic.increment_value AS NVARCHAR(256)) + + N', and range of ' + + CASE ic.system_type_name WHEN 'int' THEN N'+/- 2,147,483,647' + WHEN 'smallint' THEN N'+/- 32,768' + WHEN 'tinyint' THEN N'0 to 255' + ELSE 'unknown' + END + AS details, + i.index_definition, + secret_columns, + ISNULL(i.index_usage_summary,''), + ISNULL(ip.index_size_summary,'') + FROM #IndexSanity i + JOIN #IndexColumns ic ON + i.object_id=ic.object_id + AND i.database_id = ic.database_id + AND i.schema_name = ic.schema_name + AND i.index_id IN (0,1) /* heaps and cx only */ + AND ic.is_identity=1 + AND ic.system_type_name IN ('tinyint', 'smallint', 'int') + JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id + WHERE i.index_id IN (1,0) + AND (ic.seed_value < 0 OR ic.increment_value <> 1) + ORDER BY finding, details DESC + OPTION ( RECOMPILE ); + + ---------------------------------------- + --Workaholics: Check_id 80-89 + ---------------------------------------- + + RAISERROR(N'check_id 80: Most scanned indexes (index_usage_stats)', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + + --Workaholics according to index_usage_stats + --This isn't perfect: it mentions the number of scans present in a plan + --A "scan" isn't necessarily a full scan, but hey, we gotta do the best with what we've got. + --in the case of things like indexed views, the operator might be in the plan but never executed + SELECT TOP 5 + 80 AS check_id, + i.index_sanity_id AS index_sanity_id, + 200 AS Priority, + N'Workaholics' AS findings_group, + N'Scan-a-lots (index-usage-stats)' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/Workaholics' AS URL, + REPLACE(CONVERT( NVARCHAR(50),CAST(i.user_scans AS MONEY),1),'.00','') + + N' scans against ' + i.db_schema_object_indexid + + N'. Latest scan: ' + ISNULL(CAST(i.last_user_scan AS NVARCHAR(128)),'?') + N'. ' + + N'ScanFactor=' + CAST(((i.user_scans * iss.total_reserved_MB)/1000000.) AS NVARCHAR(256)) AS details, + ISNULL(i.key_column_names_with_sort_order,'N/A') AS index_definition, + ISNULL(i.secret_columns,'') AS secret_columns, + i.index_usage_summary AS index_usage_summary, + iss.index_size_summary AS index_size_summary + FROM #IndexSanity i + JOIN #IndexSanitySize iss ON i.index_sanity_id=iss.index_sanity_id + WHERE ISNULL(i.user_scans,0) > 0 + ORDER BY i.user_scans * iss.total_reserved_MB DESC + OPTION ( RECOMPILE ); + + RAISERROR(N'check_id 81: Top recent accesses (op stats)', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + --Workaholics according to index_operational_stats + --This isn't perfect either: range_scan_count contains full scans, partial scans, even seeks in nested loop ops + --But this can help bubble up some most-accessed tables + SELECT TOP 5 + 81 AS check_id, + i.index_sanity_id AS index_sanity_id, + 200 AS Priority, + N'Workaholics' AS findings_group, + N'Top Recent Accesses (index-op-stats)' AS finding, + [database_name] AS [Database Name], + N'https://www.brentozar.com/go/Workaholics' AS URL, + ISNULL(REPLACE( + CONVERT(NVARCHAR(50),CAST((iss.total_range_scan_count + iss.total_singleton_lookup_count) AS MONEY),1), + N'.00',N'') + + N' uses of ' + i.db_schema_object_indexid + N'. ' + + REPLACE(CONVERT(NVARCHAR(50), CAST(iss.total_range_scan_count AS MONEY),1),N'.00',N'') + N' scans or seeks. ' + + REPLACE(CONVERT(NVARCHAR(50), CAST(iss.total_singleton_lookup_count AS MONEY), 1),N'.00',N'') + N' singleton lookups. ' + + N'OpStatsFactor=' + CAST(((((iss.total_range_scan_count + iss.total_singleton_lookup_count) * iss.total_reserved_MB))/1000000.) AS VARCHAR(256)),'') AS details, + ISNULL(i.key_column_names_with_sort_order,'N/A') AS index_definition, + ISNULL(i.secret_columns,'') AS secret_columns, + i.index_usage_summary AS index_usage_summary, + iss.index_size_summary AS index_size_summary + FROM #IndexSanity i + JOIN #IndexSanitySize iss ON i.index_sanity_id=iss.index_sanity_id + WHERE (ISNULL(iss.total_range_scan_count,0) > 0 OR ISNULL(iss.total_singleton_lookup_count,0) > 0) + ORDER BY ((iss.total_range_scan_count + iss.total_singleton_lookup_count) * iss.total_reserved_MB) DESC + OPTION ( RECOMPILE ); + + + + RAISERROR(N'check_id 93: Statistics with filters', 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 93 AS check_id, + 200 AS Priority, + 'Functioning Statistaholics' AS findings_group, + 'Filter Fixation', + s.database_name, + 'https://www.brentozar.com/go/stats' AS URL, + 'The statistic ' + QUOTENAME(s.statistics_name) + ' is filtered on [' + s.filter_definition + ']. It could be part of a filtered index, or just a filtered statistic. This is purely informational.' 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_filter = 1 + OPTION ( RECOMPILE ); + + + RAISERROR(N'check_id 100: Computed Columns that are not Persisted.', 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 100 AS check_id, + 200 AS Priority, + 'Cold Calculators' AS findings_group, + 'Definition Defeatists' AS finding, + cc.database_name, + '' AS URL, + 'The computed column ' + QUOTENAME(cc.column_name) + ' on ' + QUOTENAME(cc.schema_name) + '.' + QUOTENAME(cc.table_name) + ' is not persisted, which means it will be calculated when a query runs.' + + 'You can change this with the following command, if the definition is deterministic: ALTER TABLE ' + QUOTENAME(cc.schema_name) + '.' + QUOTENAME(cc.table_name) + ' ALTER COLUMN ' + cc.column_name + + ' ADD PERSISTED' AS details, + cc.column_definition, + 'N/A' AS secret_columns, + 'N/A' AS index_usage_summary, + 'N/A' AS index_size_summary + FROM #ComputedColumns AS cc + WHERE cc.is_persisted = 0 + OPTION ( RECOMPILE ); + + RAISERROR(N'check_id 110: Temporal Tables.', 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 110 AS check_id, + 200 AS Priority, + 'Abnormal Psychology' AS findings_group, + 'Temporal Tables', + t.database_name, + '' AS URL, + 'The table ' + QUOTENAME(t.schema_name) + '.' + QUOTENAME(t.table_name) + ' is a temporal table, with rows versioned in ' + + QUOTENAME(t.history_schema_name) + '.' + QUOTENAME(t.history_table_name) + ' on History columns ' + QUOTENAME(t.start_column_name) + ' and ' + QUOTENAME(t.end_column_name) + '.' + AS details, + '' AS index_definition, + 'N/A' AS secret_columns, + 'N/A' AS index_usage_summary, + 'N/A' AS index_size_summary + FROM #TemporalTables AS t + OPTION ( RECOMPILE ); + + RAISERROR(N'check_id 121: Optimized For Sequental Keys.', 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 121 AS check_id, + 200 AS Priority, + 'Medicated Indexes' AS findings_group, + 'Optimized For Sequential Keys', + i.database_name, + '' AS URL, + 'The table ' + QUOTENAME(i.schema_name) + '.' + QUOTENAME(i.object_name) + ' is optimized for sequential keys.' AS details, + '' AS index_definition, + 'N/A' AS secret_columns, + 'N/A' AS index_usage_summary, + 'N/A' AS index_size_summary + FROM #IndexSanity AS i + WHERE i.optimize_for_sequential_key = 1 + OPTION ( RECOMPILE ); + + + + END /* IF @Mode = 4 */ + + + + + + + + + RAISERROR(N'Insert a row to help people find help', 0,1) WITH NOWAIT; + IF DATEDIFF(MM, @VersionDate, GETDATE()) > 6 + BEGIN + INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, URL, details, index_definition, + index_usage_summary, index_size_summary ) + VALUES ( -1, 0 , + 'Outdated sp_BlitzIndex', 'sp_BlitzIndex is Over 6 Months Old', 'http://FirstResponderKit.org/', + 'Fine wine gets better with age, but this ' + @ScriptVersionName + ' is more like bad cheese. Time to get a new one.', + @DaysUptimeInsertValue,N'',N'' + ); + END; + + IF EXISTS(SELECT * FROM #BlitzIndexResults) + BEGIN + INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, URL, details, index_definition, + index_usage_summary, index_size_summary ) + VALUES ( -1, 0 , + @ScriptVersionName, + CASE WHEN @GetAllDatabases = 1 THEN N'All Databases' ELSE N'Database ' + QUOTENAME(@DatabaseName) + N' as of ' + CONVERT(NVARCHAR(16),GETDATE(),121) END, + N'From Your Community Volunteers' , N'http://FirstResponderKit.org' , + @DaysUptimeInsertValue,N'',N'' + ); + END; + ELSE IF @Mode = 0 OR (@GetAllDatabases = 1 AND @Mode <> 4) + BEGIN + INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, URL, details, index_definition, + index_usage_summary, index_size_summary ) + VALUES ( -1, 0 , + @ScriptVersionName, + CASE WHEN @GetAllDatabases = 1 THEN N'All Databases' ELSE N'Database ' + QUOTENAME(@DatabaseName) + N' as of ' + CONVERT(NVARCHAR(16),GETDATE(),121) END, + N'From Your Community Volunteers' , N'http://FirstResponderKit.org' , + @DaysUptimeInsertValue, N'',N'' + ); + INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, URL, details, index_definition, + index_usage_summary, index_size_summary ) + VALUES ( 1, 0 , + N'No Major Problems Found', + N'Nice Work!', + N'http://FirstResponderKit.org', + N'Consider running with @Mode = 4 in individual databases (not all) for more detailed diagnostics.', + N'The default Mode 0 only looks for very serious index issues.', + @DaysUptimeInsertValue, N'' + ); + + END; + ELSE + BEGIN + INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, URL, details, index_definition, + index_usage_summary, index_size_summary ) + VALUES ( -1, 0 , + @ScriptVersionName, + CASE WHEN @GetAllDatabases = 1 THEN N'All Databases' ELSE N'Database ' + QUOTENAME(@DatabaseName) + N' as of ' + CONVERT(NVARCHAR(16),GETDATE(),121) END, + N'From Your Community Volunteers' , N'http://FirstResponderKit.org' , + @DaysUptimeInsertValue, N'',N'' + ); + INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, URL, details, index_definition, + index_usage_summary, index_size_summary ) + VALUES ( 1, 0 , + N'No Problems Found', + N'Nice job! Or more likely, you have a nearly empty database.', + N'http://FirstResponderKit.org', 'Time to go read some blog posts.', + @DaysUptimeInsertValue, N'', N'' + ); + + END; + + RAISERROR(N'Returning results.', 0,1) WITH NOWAIT; + + /*Return results.*/ + IF (@ValidOutputLocation = 1 AND COALESCE(@OutputServerName, @OutputDatabaseName, @OutputSchemaName, @OutputTableName) IS NOT NULL) + BEGIN + IF NOT @SchemaExists = 1 + BEGIN + RAISERROR (N'Invalid schema name, data could not be saved.', 16, 0); + RETURN; + END + + IF @TableExists = 0 + BEGIN + SET @StringToExecute = + N'CREATE TABLE @@@OutputDatabaseName@@@.@@@OutputSchemaName@@@.@@@OutputTableName@@@ + ( + [id] INT IDENTITY(1,1) NOT NULL, + [run_id] UNIQUEIDENTIFIER, + [run_datetime] DATETIME, + [server_name] NVARCHAR(128), + [priority] INT, + [finding] NVARCHAR(4000), + [database_name] NVARCHAR(128), + [details] NVARCHAR(MAX), + [index_definition] NVARCHAR(MAX), + [secret_columns] NVARCHAR(MAX), + [index_usage_summary] NVARCHAR(MAX), + [index_size_summary] NVARCHAR(MAX), + [more_info] NVARCHAR(MAX), + [url] NVARCHAR(MAX), + [create_tsql] NVARCHAR(MAX), + [sample_query_plan] XML, + CONSTRAINT [PK_ID_@@@RunID@@@] PRIMARY KEY CLUSTERED ([id] ASC) + );'; + + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputSchemaName@@@', @OutputSchemaName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@RunID@@@', @RunID); + + IF @ValidOutputServer = 1 + BEGIN + SET @StringToExecute = REPLACE(@StringToExecute,'''',''''''); + EXEC('EXEC('''+@StringToExecute+''') AT ' + @OutputServerName); + END; + ELSE + BEGIN + EXEC(@StringToExecute); + END; + END; /* @TableExists = 0 */ + + -- Re-check that table now exists (if not we failed creating it) + SET @TableExists = NULL; + EXEC sp_executesql @TableExistsSql, N'@TableExists BIT OUTPUT', @TableExists OUTPUT; + + IF NOT @TableExists = 1 + BEGIN + RAISERROR('Creation of the output table failed.', 16, 0); + RETURN; + END; + + SET @StringToExecute = + N'INSERT @@@OutputServerName@@@.@@@OutputDatabaseName@@@.@@@OutputSchemaName@@@.@@@OutputTableName@@@ + ( + [run_id], + [run_datetime], + [server_name], + [priority], + [finding], + [database_name], + [details], + [index_definition], + [secret_columns], + [index_usage_summary], + [index_size_summary], + [more_info], + [url], + [create_tsql], + [sample_query_plan] + ) + SELECT + ''@@@RunID@@@'', + ''@@@GETDATE@@@'', + ''@@@LocalServerName@@@'', + -- Below should be a copy/paste of the real query + -- Make sure all quotes are escaped + Priority, ISNULL(br.findings_group,N'''') + + CASE WHEN ISNULL(br.finding,N'''') <> N'''' THEN N'': '' ELSE N'''' END + + br.finding AS [Finding], + br.[database_name] AS [Database Name], + br.details AS [Details: schema.table.index(indexid)], + br.index_definition AS [Definition: [Property]] ColumnName {datatype maxbytes}], + ISNULL(br.secret_columns,'''') AS [Secret Columns], + br.index_usage_summary AS [Usage], + br.index_size_summary AS [Size], + COALESCE(br.more_info,sn.more_info,'''') AS [More Info], + br.URL, + COALESCE(br.create_tsql,ts.create_tsql,'''') AS [Create TSQL], + br.sample_query_plan AS [Sample Query Plan] + FROM #BlitzIndexResults br + LEFT JOIN #IndexSanity sn ON + br.index_sanity_id=sn.index_sanity_id + LEFT JOIN #IndexCreateTsql ts ON + br.index_sanity_id=ts.index_sanity_id + ORDER BY br.Priority ASC, br.check_id ASC, br.blitz_result_id ASC, br.findings_group ASC + OPTION (RECOMPILE);'; + + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputServerName@@@', @OutputServerName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputSchemaName@@@', @OutputSchemaName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@RunID@@@', @RunID); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@GETDATE@@@', GETDATE()); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@LocalServerName@@@', CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128))); + EXEC(@StringToExecute); + + END + ELSE + BEGIN + IF(@OutputType <> 'NONE') + BEGIN + SELECT Priority, ISNULL(br.findings_group,N'') + + CASE WHEN ISNULL(br.finding,N'') <> N'' THEN N': ' ELSE N'' END + + br.finding AS [Finding], + br.[database_name] AS [Database Name], + br.details AS [Details: schema.table.index(indexid)], + br.index_definition AS [Definition: [Property]] ColumnName {datatype maxbytes}], + ISNULL(br.secret_columns,'') AS [Secret Columns], + br.index_usage_summary AS [Usage], + br.index_size_summary AS [Size], + COALESCE(br.more_info,sn.more_info,'') AS [More Info], + br.URL, + COALESCE(br.create_tsql,ts.create_tsql,'') AS [Create TSQL], + br.sample_query_plan AS [Sample Query Plan] + FROM #BlitzIndexResults br + LEFT JOIN #IndexSanity sn ON + br.index_sanity_id=sn.index_sanity_id + LEFT JOIN #IndexCreateTsql ts ON + br.index_sanity_id=ts.index_sanity_id + ORDER BY br.Priority ASC, br.check_id ASC, br.blitz_result_id ASC, br.findings_group ASC + OPTION (RECOMPILE); + END; + + END; + + END /* End @Mode=0 or 4 (diagnose)*/ + + + + + + + + + ELSE IF (@Mode=1) /*Summarize*/ + BEGIN + --This mode is to give some overall stats on the database. + IF (@ValidOutputLocation = 1 AND COALESCE(@OutputServerName, @OutputDatabaseName, @OutputSchemaName, @OutputTableName) IS NOT NULL) + BEGIN + + IF NOT @SchemaExists = 1 + BEGIN + RAISERROR (N'Invalid schema name, data could not be saved.', 16, 0); + RETURN; + END + + IF @TableExists = 0 + BEGIN + SET @StringToExecute = + N'CREATE TABLE @@@OutputDatabaseName@@@.@@@OutputSchemaName@@@.@@@OutputTableName@@@ + ( + [id] INT IDENTITY(1,1) NOT NULL, + [run_id] UNIQUEIDENTIFIER, + [run_datetime] DATETIME, + [server_name] NVARCHAR(128), + [database_name] NVARCHAR(128), + [object_count] INT, + [reserved_gb] NUMERIC(29,1), + [reserved_lob_gb] NUMERIC(29,1), + [reserved_row_overflow_gb] NUMERIC(29,1), + [clustered_table_count] INT, + [clustered_table_gb] NUMERIC(29,1), + [nc_index_count] INT, + [nc_index_gb] NUMERIC(29,1), + [table_nc_index_ratio] NUMERIC(29,1), + [heap_count] INT, + [heap_gb] NUMERIC(29,1), + [partioned_table_count] INT, + [partioned_nc_count] INT, + [partioned_gb] NUMERIC(29,1), + [filtered_index_count] INT, + [indexed_view_count] INT, + [max_table_row_count] INT, + [max_table_gb] NUMERIC(29,1), + [max_nc_index_gb] NUMERIC(29,1), + [table_count_over_1gb] INT, + [table_count_over_10gb] INT, + [table_count_over_100gb] INT, + [nc_index_count_over_1gb] INT, + [nc_index_count_over_10gb] INT, + [nc_index_count_over_100gb] INT, + [min_create_date] DATETIME, + [max_create_date] DATETIME, + [max_modify_date] DATETIME, + [display_order] INT, + CONSTRAINT [PK_ID_@@@RunID@@@] PRIMARY KEY CLUSTERED ([id] ASC) + );'; + + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputSchemaName@@@', @OutputSchemaName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@RunID@@@', @RunID); + + IF @ValidOutputServer = 1 + BEGIN + SET @StringToExecute = REPLACE(@StringToExecute,'''',''''''); + EXEC('EXEC('''+@StringToExecute+''') AT ' + @OutputServerName); + END; + ELSE + BEGIN + EXEC(@StringToExecute); + END; + END; /* @TableExists = 0 */ + + -- Re-check that table now exists (if not we failed creating it) + SET @TableExists = NULL; + EXEC sp_executesql @TableExistsSql, N'@TableExists BIT OUTPUT', @TableExists OUTPUT; + + IF NOT @TableExists = 1 + BEGIN + RAISERROR('Creation of the output table failed.', 16, 0); + RETURN; + END; + + SET @StringToExecute = + N'INSERT @@@OutputServerName@@@.@@@OutputDatabaseName@@@.@@@OutputSchemaName@@@.@@@OutputTableName@@@ + ( + [run_id], + [run_datetime], + [server_name], + [database_name], + [object_count], + [reserved_gb], + [reserved_lob_gb], + [reserved_row_overflow_gb], + [clustered_table_count], + [clustered_table_gb], + [nc_index_count], + [nc_index_gb], + [table_nc_index_ratio], + [heap_count], + [heap_gb], + [partioned_table_count], + [partioned_nc_count], + [partioned_gb], + [filtered_index_count], + [indexed_view_count], + [max_table_row_count], + [max_table_gb], + [max_nc_index_gb], + [table_count_over_1gb], + [table_count_over_10gb], + [table_count_over_100gb], + [nc_index_count_over_1gb], + [nc_index_count_over_10gb], + [nc_index_count_over_100gb], + [min_create_date], + [max_create_date], + [max_modify_date], + [display_order] + ) + SELECT ''@@@RunID@@@'', + ''@@@GETDATE@@@'', + ''@@@LocalServerName@@@'', + -- Below should be a copy/paste of the real query + -- Make sure all quotes are escaped + -- NOTE! information line is skipped from output and the query below + -- NOTE! initial columns are not casted to nvarchar due to not outputing informational line + DB_NAME(i.database_id) AS [Database Name], + COUNT(*) AS [Number Objects], + CAST(SUM(sz.total_reserved_MB)/ + 1024. AS NUMERIC(29,1)) AS [All GB], + CAST(SUM(sz.total_reserved_LOB_MB)/ + 1024. AS NUMERIC(29,1)) AS [LOB GB], + CAST(SUM(sz.total_reserved_row_overflow_MB)/ + 1024. AS NUMERIC(29,1)) AS [Row Overflow GB], + SUM(CASE WHEN index_id=1 THEN 1 ELSE 0 END) AS [Clustered Tables], + CAST(SUM(CASE WHEN index_id=1 THEN sz.total_reserved_MB ELSE 0 END) + /1024. AS NUMERIC(29,1)) AS [Clustered Tables GB], + SUM(CASE WHEN index_id NOT IN (0,1) THEN 1 ELSE 0 END) AS [NC Indexes], + CAST(SUM(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) + /1024. AS NUMERIC(29,1)) AS [NC Indexes GB], + CASE WHEN SUM(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) > 0 THEN + CAST(SUM(CASE WHEN index_id IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) + / SUM(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) AS NUMERIC(29,1)) + ELSE 0 END AS [ratio table: NC Indexes], + SUM(CASE WHEN index_id=0 THEN 1 ELSE 0 END) AS [Heaps], + CAST(SUM(CASE WHEN index_id=0 THEN sz.total_reserved_MB ELSE 0 END) + /1024. AS NUMERIC(29,1)) AS [Heaps GB], + SUM(CASE WHEN index_id IN (0,1) AND partition_key_column_name IS NOT NULL THEN 1 ELSE 0 END) AS [Partitioned Tables], + SUM(CASE WHEN index_id NOT IN (0,1) AND partition_key_column_name IS NOT NULL THEN 1 ELSE 0 END) AS [Partitioned NCs], + CAST(SUM(CASE WHEN partition_key_column_name IS NOT NULL THEN sz.total_reserved_MB ELSE 0 END)/1024. AS NUMERIC(29,1)) AS [Partitioned GB], + SUM(CASE WHEN filter_definition <> '''' THEN 1 ELSE 0 END) AS [Filtered Indexes], + SUM(CASE WHEN is_indexed_view=1 THEN 1 ELSE 0 END) AS [Indexed Views], + MAX(total_rows) AS [Max Row Count], + CAST(MAX(CASE WHEN index_id IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) + /1024. AS NUMERIC(29,1)) AS [Max Table GB], + CAST(MAX(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) + /1024. AS NUMERIC(29,1)) AS [Max NC Index GB], + SUM(CASE WHEN index_id IN (0,1) AND sz.total_reserved_MB > 1024 THEN 1 ELSE 0 END) AS [Count Tables > 1GB], + SUM(CASE WHEN index_id IN (0,1) AND sz.total_reserved_MB > 10240 THEN 1 ELSE 0 END) AS [Count Tables > 10GB], + SUM(CASE WHEN index_id IN (0,1) AND sz.total_reserved_MB > 102400 THEN 1 ELSE 0 END) AS [Count Tables > 100GB], + SUM(CASE WHEN index_id NOT IN (0,1) AND sz.total_reserved_MB > 1024 THEN 1 ELSE 0 END) AS [Count NCs > 1GB], + SUM(CASE WHEN index_id NOT IN (0,1) AND sz.total_reserved_MB > 10240 THEN 1 ELSE 0 END) AS [Count NCs > 10GB], + SUM(CASE WHEN index_id NOT IN (0,1) AND sz.total_reserved_MB > 102400 THEN 1 ELSE 0 END) AS [Count NCs > 100GB], + MIN(create_date) AS [Oldest Create Date], + MAX(create_date) AS [Most Recent Create Date], + MAX(modify_date) AS [Most Recent Modify Date], + 1 AS [Display Order] + FROM #IndexSanity AS i + --left join here so we don''t lose disabled nc indexes + LEFT JOIN #IndexSanitySize AS sz + ON i.index_sanity_id=sz.index_sanity_id + GROUP BY DB_NAME(i.database_id) + ORDER BY [Display Order] ASC + OPTION (RECOMPILE);'; + + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputServerName@@@', @OutputServerName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputSchemaName@@@', @OutputSchemaName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@RunID@@@', @RunID); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@GETDATE@@@', GETDATE()); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@LocalServerName@@@', CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128))); + EXEC(@StringToExecute); + + END; /* @ValidOutputLocation = 1 */ + ELSE + BEGIN + IF(@OutputType <> 'NONE') + BEGIN + + RAISERROR(N'@Mode=1, we are summarizing.', 0,1) WITH NOWAIT; + + SELECT DB_NAME(i.database_id) AS [Database Name], + CAST((COUNT(*)) AS NVARCHAR(256)) AS [Number Objects], + CAST(CAST(SUM(sz.total_reserved_MB)/ + 1024. AS NUMERIC(29,1)) AS NVARCHAR(500)) AS [All GB], + CAST(CAST(SUM(sz.total_reserved_LOB_MB)/ + 1024. AS NUMERIC(29,1)) AS NVARCHAR(500)) AS [LOB GB], + CAST(CAST(SUM(sz.total_reserved_row_overflow_MB)/ + 1024. AS NUMERIC(29,1)) AS NVARCHAR(500)) AS [Row Overflow GB], + CAST(SUM(CASE WHEN index_id=1 THEN 1 ELSE 0 END)AS NVARCHAR(50)) AS [Clustered Tables], + CAST(SUM(CASE WHEN index_id=1 THEN sz.total_reserved_MB ELSE 0 END) + /1024. AS NUMERIC(29,1)) AS [Clustered Tables GB], + SUM(CASE WHEN index_id NOT IN (0,1) THEN 1 ELSE 0 END) AS [NC Indexes], + CAST(SUM(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) + /1024. AS NUMERIC(29,1)) AS [NC Indexes GB], + CASE WHEN SUM(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) > 0 THEN + CAST(SUM(CASE WHEN index_id IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) + / SUM(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) AS NUMERIC(29,1)) + ELSE 0 END AS [ratio table: NC Indexes], + SUM(CASE WHEN index_id=0 THEN 1 ELSE 0 END) AS [Heaps], + CAST(SUM(CASE WHEN index_id=0 THEN sz.total_reserved_MB ELSE 0 END) + /1024. AS NUMERIC(29,1)) AS [Heaps GB], + SUM(CASE WHEN index_id IN (0,1) AND partition_key_column_name IS NOT NULL THEN 1 ELSE 0 END) AS [Partitioned Tables], + SUM(CASE WHEN index_id NOT IN (0,1) AND partition_key_column_name IS NOT NULL THEN 1 ELSE 0 END) AS [Partitioned NCs], + CAST(SUM(CASE WHEN partition_key_column_name IS NOT NULL THEN sz.total_reserved_MB ELSE 0 END)/1024. AS NUMERIC(29,1)) AS [Partitioned GB], + SUM(CASE WHEN filter_definition <> '' THEN 1 ELSE 0 END) AS [Filtered Indexes], + SUM(CASE WHEN is_indexed_view=1 THEN 1 ELSE 0 END) AS [Indexed Views], + MAX(total_rows) AS [Max Row Count], + CAST(MAX(CASE WHEN index_id IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) + /1024. AS NUMERIC(29,1)) AS [Max Table GB], + CAST(MAX(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) + /1024. AS NUMERIC(29,1)) AS [Max NC Index GB], + SUM(CASE WHEN index_id IN (0,1) AND sz.total_reserved_MB > 1024 THEN 1 ELSE 0 END) AS [Count Tables > 1GB], + SUM(CASE WHEN index_id IN (0,1) AND sz.total_reserved_MB > 10240 THEN 1 ELSE 0 END) AS [Count Tables > 10GB], + SUM(CASE WHEN index_id IN (0,1) AND sz.total_reserved_MB > 102400 THEN 1 ELSE 0 END) AS [Count Tables > 100GB], + SUM(CASE WHEN index_id NOT IN (0,1) AND sz.total_reserved_MB > 1024 THEN 1 ELSE 0 END) AS [Count NCs > 1GB], + SUM(CASE WHEN index_id NOT IN (0,1) AND sz.total_reserved_MB > 10240 THEN 1 ELSE 0 END) AS [Count NCs > 10GB], + SUM(CASE WHEN index_id NOT IN (0,1) AND sz.total_reserved_MB > 102400 THEN 1 ELSE 0 END) AS [Count NCs > 100GB], + MIN(create_date) AS [Oldest Create Date], + MAX(create_date) AS [Most Recent Create Date], + MAX(modify_date) AS [Most Recent Modify Date], + 1 AS [Display Order] + FROM #IndexSanity AS i + --left join here so we don't lose disabled nc indexes + LEFT JOIN #IndexSanitySize AS sz + ON i.index_sanity_id=sz.index_sanity_id + GROUP BY DB_NAME(i.database_id) + UNION ALL + SELECT CASE WHEN @GetAllDatabases = 1 THEN N'All Databases' ELSE N'Database ' + N' as of ' + CONVERT(NVARCHAR(16),GETDATE(),121) END, + @ScriptVersionName, + N'From Your Community Volunteers' , + N'http://FirstResponderKit.org' , + @DaysUptimeInsertValue, + NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL, + NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL, + NULL,NULL,0 AS display_order + ORDER BY [Display Order] ASC + OPTION (RECOMPILE); + END; + END; + + END; /* End @Mode=1 (summarize)*/ + + + + + + + + + ELSE IF (@Mode=2) /*Index Detail*/ + BEGIN + --This mode just spits out all the detail without filters. + --This supports slicing AND dicing in Excel + RAISERROR(N'@Mode=2, here''s the details on existing indexes.', 0,1) WITH NOWAIT; + + IF (@ValidOutputLocation = 1 AND COALESCE(@OutputServerName, @OutputDatabaseName, @OutputSchemaName, @OutputTableName) IS NOT NULL) + BEGIN + IF @SchemaExists = 1 + BEGIN + IF @TableExists = 0 + BEGIN + SET @StringToExecute = + N'CREATE TABLE @@@OutputDatabaseName@@@.@@@OutputSchemaName@@@.@@@OutputTableName@@@ + ( + [id] INT IDENTITY(1,1) NOT NULL, + [run_id] UNIQUEIDENTIFIER, + [run_datetime] DATETIME, + [server_name] NVARCHAR(128), + [database_name] NVARCHAR(128), + [schema_name] NVARCHAR(128), + [table_name] NVARCHAR(128), + [index_name] NVARCHAR(128), + [Drop_Tsql] NVARCHAR(MAX), + [Create_Tsql] NVARCHAR(MAX), + [index_id] INT, + [db_schema_object_indexid] NVARCHAR(500), + [object_type] NVARCHAR(15), + [index_definition] NVARCHAR(MAX), + [key_column_names_with_sort_order] NVARCHAR(MAX), + [count_key_columns] INT, + [include_column_names] NVARCHAR(MAX), + [count_included_columns] INT, + [secret_columns] NVARCHAR(MAX), + [count_secret_columns] INT, + [partition_key_column_name] NVARCHAR(MAX), + [filter_definition] NVARCHAR(MAX), + [is_indexed_view] BIT, + [is_primary_key] BIT, + [is_unique_constraint] BIT, + [is_XML] BIT, + [is_spatial] BIT, + [is_NC_columnstore] BIT, + [is_CX_columnstore] BIT, + [is_in_memory_oltp] BIT, + [is_disabled] BIT, + [is_hypothetical] BIT, + [is_padded] BIT, + [fill_factor] INT, + [is_referenced_by_foreign_key] BIT, + [last_user_seek] DATETIME, + [last_user_scan] DATETIME, + [last_user_lookup] DATETIME, + [last_user_update] DATETIME, + [total_reads] BIGINT, + [user_updates] BIGINT, + [reads_per_write] MONEY, + [index_usage_summary] NVARCHAR(200), + [total_singleton_lookup_count] BIGINT, + [total_range_scan_count] BIGINT, + [total_leaf_delete_count] BIGINT, + [total_leaf_update_count] BIGINT, + [index_op_stats] NVARCHAR(200), + [partition_count] INT, + [total_rows] BIGINT, + [total_reserved_MB] NUMERIC(29,2), + [total_reserved_LOB_MB] NUMERIC(29,2), + [total_reserved_row_overflow_MB] NUMERIC(29,2), + [index_size_summary] NVARCHAR(300), + [total_row_lock_count] BIGINT, + [total_row_lock_wait_count] BIGINT, + [total_row_lock_wait_in_ms] BIGINT, + [avg_row_lock_wait_in_ms] BIGINT, + [total_page_lock_count] BIGINT, + [total_page_lock_wait_count] BIGINT, + [total_page_lock_wait_in_ms] BIGINT, + [avg_page_lock_wait_in_ms] BIGINT, + [total_index_lock_promotion_attempt_count] BIGINT, + [total_index_lock_promotion_count] BIGINT, + [total_forwarded_fetch_count] BIGINT, + [data_compression_desc] NVARCHAR(4000), + [page_latch_wait_count] BIGINT, + [page_latch_wait_in_ms] BIGINT, + [page_io_latch_wait_count] BIGINT, + [page_io_latch_wait_in_ms] BIGINT, + [create_date] DATETIME, + [modify_date] DATETIME, + [more_info] NVARCHAR(500), + [display_order] INT, + CONSTRAINT [PK_ID_@@@RunID@@@] PRIMARY KEY CLUSTERED ([id] ASC) + );'; + + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputSchemaName@@@', @OutputSchemaName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@RunID@@@', @RunID); + + IF @ValidOutputServer = 1 + BEGIN + SET @StringToExecute = REPLACE(@StringToExecute,'''',''''''); + EXEC('EXEC('''+@StringToExecute+''') AT ' + @OutputServerName); + END; + ELSE + BEGIN + EXEC(@StringToExecute); + END; + END; /* @TableExists = 0 */ + + + -- Re-check that table now exists (if not we failed creating it) + SET @TableExists = NULL; + EXEC sp_executesql @TableExistsSql, N'@TableExists BIT OUTPUT', @TableExists OUTPUT; + + IF @TableExists = 1 + BEGIN + SET @StringToExecute = + N'INSERT @@@OutputServerName@@@.@@@OutputDatabaseName@@@.@@@OutputSchemaName@@@.@@@OutputTableName@@@ + ( + [run_id], + [run_datetime], + [server_name], + [database_name], + [schema_name], + [table_name], + [index_name], + [Drop_Tsql], + [Create_Tsql], + [index_id], + [db_schema_object_indexid], + [object_type], + [index_definition], + [key_column_names_with_sort_order], + [count_key_columns], + [include_column_names], + [count_included_columns], + [secret_columns], + [count_secret_columns], + [partition_key_column_name], + [filter_definition], + [is_indexed_view], + [is_primary_key], + [is_unique_constraint], + [is_XML], + [is_spatial], + [is_NC_columnstore], + [is_CX_columnstore], + [is_in_memory_oltp], + [is_disabled], + [is_hypothetical], + [is_padded], + [fill_factor], + [is_referenced_by_foreign_key], + [last_user_seek], + [last_user_scan], + [last_user_lookup], + [last_user_update], + [total_reads], + [user_updates], + [reads_per_write], + [index_usage_summary], + [total_singleton_lookup_count], + [total_range_scan_count], + [total_leaf_delete_count], + [total_leaf_update_count], + [index_op_stats], + [partition_count], + [total_rows], + [total_reserved_MB], + [total_reserved_LOB_MB], + [total_reserved_row_overflow_MB], + [index_size_summary], + [total_row_lock_count], + [total_row_lock_wait_count], + [total_row_lock_wait_in_ms], + [avg_row_lock_wait_in_ms], + [total_page_lock_count], + [total_page_lock_wait_count], + [total_page_lock_wait_in_ms], + [avg_page_lock_wait_in_ms], + [total_index_lock_promotion_attempt_count], + [total_index_lock_promotion_count], + [total_forwarded_fetch_count], + [data_compression_desc], + [page_latch_wait_count], + [page_latch_wait_in_ms], + [page_io_latch_wait_count], + [page_io_latch_wait_in_ms], + [create_date], + [modify_date], + [more_info], + [display_order] + ) + SELECT ''@@@RunID@@@'', + ''@@@GETDATE@@@'', + ''@@@LocalServerName@@@'', + -- Below should be a copy/paste of the real query + -- Make sure all quotes are escaped + i.[database_name] AS [Database Name], + i.[schema_name] AS [Schema Name], + i.[object_name] AS [Object Name], + ISNULL(i.index_name, '''') AS [Index Name], + CASE + WHEN i.is_primary_key = 1 AND i.index_definition <> ''[HEAP]'' + THEN N''--ALTER TABLE '' + QUOTENAME(i.[database_name]) + N''.'' + QUOTENAME(i.[schema_name]) + N''.'' + QUOTENAME(i.[object_name]) + + N'' DROP CONSTRAINT '' + QUOTENAME(i.index_name) + N'';'' + WHEN i.is_primary_key = 0 AND i.is_unique_constraint = 1 AND i.index_definition <> ''[HEAP]'' + THEN N''--ALTER TABLE '' + QUOTENAME(i.[database_name]) + N''.'' + QUOTENAME(i.[schema_name]) + N''.'' + QUOTENAME(i.[object_name]) + + N'' DROP CONSTRAINT '' + QUOTENAME(i.index_name) + N'';'' + WHEN i.is_primary_key = 0 AND i.index_definition <> ''[HEAP]'' + THEN N''--DROP INDEX ''+ QUOTENAME(i.index_name) + N'' ON '' + QUOTENAME(i.[database_name]) + N''.'' + + QUOTENAME(i.[schema_name]) + N''.'' + QUOTENAME(i.[object_name]) + N'';'' + ELSE N'''' + END AS [Drop TSQL], + CASE + WHEN i.index_definition = ''[HEAP]'' THEN N'''' + ELSE N''--'' + ict.create_tsql END AS [Create TSQL], + CAST(i.index_id AS NVARCHAR(10))AS [Index ID], + db_schema_object_indexid AS [Details: schema.table.index(indexid)], + CASE WHEN index_id IN ( 1, 0 ) THEN ''TABLE'' + ELSE ''NonClustered'' + END AS [Object Type], + LEFT(index_definition,4000) AS [Definition: [Property]] ColumnName {datatype maxbytes}], + ISNULL(LTRIM(key_column_names_with_sort_order), '''') AS [Key Column Names With Sort], + ISNULL(count_key_columns, 0) AS [Count Key Columns], + ISNULL(include_column_names, '''') AS [Include Column Names], + ISNULL(count_included_columns,0) AS [Count Included Columns], + ISNULL(secret_columns,'''') AS [Secret Column Names], + ISNULL(count_secret_columns,0) AS [Count Secret Columns], + ISNULL(partition_key_column_name, '''') AS [Partition Key Column Name], + ISNULL(filter_definition, '''') AS [Filter Definition], + is_indexed_view AS [Is Indexed View], + is_primary_key AS [Is Primary Key], + is_unique_constraint AS [Is Unique Constraint], + is_XML AS [Is XML], + is_spatial AS [Is Spatial], + is_NC_columnstore AS [Is NC Columnstore], + is_CX_columnstore AS [Is CX Columnstore], + is_in_memory_oltp AS [Is In-Memory OLTP], + is_disabled AS [Is Disabled], + is_hypothetical AS [Is Hypothetical], + is_padded AS [Is Padded], + fill_factor AS [Fill Factor], + is_referenced_by_foreign_key AS [Is Reference by Foreign Key], + last_user_seek AS [Last User Seek], + last_user_scan AS [Last User Scan], + last_user_lookup AS [Last User Lookup], + last_user_update AS [Last User Update], + total_reads AS [Total Reads], + user_updates AS [User Updates], + reads_per_write AS [Reads Per Write], + index_usage_summary AS [Index Usage], + sz.total_singleton_lookup_count AS [Singleton Lookups], + sz.total_range_scan_count AS [Range Scans], + sz.total_leaf_delete_count AS [Leaf Deletes], + sz.total_leaf_update_count AS [Leaf Updates], + sz.index_op_stats AS [Index Op Stats], + sz.partition_count AS [Partition Count], + sz.total_rows AS [Rows], + sz.total_reserved_MB AS [Reserved MB], + sz.total_reserved_LOB_MB AS [Reserved LOB MB], + sz.total_reserved_row_overflow_MB AS [Reserved Row Overflow MB], + sz.index_size_summary AS [Index Size], + sz.total_row_lock_count AS [Row Lock Count], + sz.total_row_lock_wait_count AS [Row Lock Wait Count], + sz.total_row_lock_wait_in_ms AS [Row Lock Wait ms], + sz.avg_row_lock_wait_in_ms AS [Avg Row Lock Wait ms], + sz.total_page_lock_count AS [Page Lock Count], + sz.total_page_lock_wait_count AS [Page Lock Wait Count], + sz.total_page_lock_wait_in_ms AS [Page Lock Wait ms], + sz.avg_page_lock_wait_in_ms AS [Avg Page Lock Wait ms], + sz.total_index_lock_promotion_attempt_count AS [Lock Escalation Attempts], + sz.total_index_lock_promotion_count AS [Lock Escalations], + sz.total_forwarded_fetch_count AS [Forwarded Fetches], + sz.data_compression_desc AS [Data Compression], + sz.page_latch_wait_count, + sz.page_latch_wait_in_ms, + sz.page_io_latch_wait_count, + sz.page_io_latch_wait_in_ms, + i.create_date AS [Create Date], + i.modify_date AS [Modify Date], + more_info AS [More Info], + 1 AS [Display Order] + FROM #IndexSanity AS i + LEFT JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id + LEFT JOIN #IndexCreateTsql AS ict ON i.index_sanity_id = ict.index_sanity_id + ORDER BY [Database Name], [Schema Name], [Object Name], [Index ID] + OPTION (RECOMPILE);'; + + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputServerName@@@', @OutputServerName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputSchemaName@@@', @OutputSchemaName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@RunID@@@', @RunID); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@GETDATE@@@', GETDATE()); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@LocalServerName@@@', CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128))); + EXEC(@StringToExecute); + END; /* @TableExists = 1 */ + ELSE + RAISERROR('Creation of the output table failed.', 16, 0); + END; /* @TableExists = 0 */ + ELSE + RAISERROR (N'Invalid schema name, data could not be saved.', 16, 0); + END; /* @ValidOutputLocation = 1 */ + ELSE + + IF(@OutputType <> 'NONE') + BEGIN + SELECT i.[database_name] AS [Database Name], + i.[schema_name] AS [Schema Name], + i.[object_name] AS [Object Name], + ISNULL(i.index_name, '') AS [Index Name], + CAST(i.index_id AS NVARCHAR(10))AS [Index ID], + db_schema_object_indexid AS [Details: schema.table.index(indexid)], + CASE WHEN index_id IN ( 1, 0 ) THEN 'TABLE' + ELSE 'NonClustered' + END AS [Object Type], + index_definition AS [Definition: [Property]] ColumnName {datatype maxbytes}], + ISNULL(LTRIM(key_column_names_with_sort_order), '') AS [Key Column Names With Sort], + ISNULL(count_key_columns, 0) AS [Count Key Columns], + ISNULL(include_column_names, '') AS [Include Column Names], + ISNULL(count_included_columns,0) AS [Count Included Columns], + ISNULL(secret_columns,'') AS [Secret Column Names], + ISNULL(count_secret_columns,0) AS [Count Secret Columns], + ISNULL(partition_key_column_name, '') AS [Partition Key Column Name], + ISNULL(filter_definition, '') AS [Filter Definition], + is_indexed_view AS [Is Indexed View], + is_primary_key AS [Is Primary Key], + is_unique_constraint AS [Is Unique Constraint] , + is_XML AS [Is XML], + is_spatial AS [Is Spatial], + is_NC_columnstore AS [Is NC Columnstore], + is_CX_columnstore AS [Is CX Columnstore], + is_in_memory_oltp AS [Is In-Memory OLTP], + is_disabled AS [Is Disabled], + is_hypothetical AS [Is Hypothetical], + is_padded AS [Is Padded], + fill_factor AS [Fill Factor], + is_referenced_by_foreign_key AS [Is Reference by Foreign Key], + last_user_seek AS [Last User Seek], + last_user_scan AS [Last User Scan], + last_user_lookup AS [Last User Lookup], + last_user_update AS [Last User Update], + total_reads AS [Total Reads], + user_updates AS [User Updates], + reads_per_write AS [Reads Per Write], + index_usage_summary AS [Index Usage], + sz.total_singleton_lookup_count AS [Singleton Lookups], + sz.total_range_scan_count AS [Range Scans], + sz.total_leaf_delete_count AS [Leaf Deletes], + sz.total_leaf_update_count AS [Leaf Updates], + sz.index_op_stats AS [Index Op Stats], + sz.partition_count AS [Partition Count], + sz.total_rows AS [Rows], + sz.total_reserved_MB AS [Reserved MB], + sz.total_reserved_LOB_MB AS [Reserved LOB MB], + sz.total_reserved_row_overflow_MB AS [Reserved Row Overflow MB], + sz.index_size_summary AS [Index Size], + sz.total_row_lock_count AS [Row Lock Count], + sz.total_row_lock_wait_count AS [Row Lock Wait Count], + sz.total_row_lock_wait_in_ms AS [Row Lock Wait ms], + sz.avg_row_lock_wait_in_ms AS [Avg Row Lock Wait ms], + sz.total_page_lock_count AS [Page Lock Count], + sz.total_page_lock_wait_count AS [Page Lock Wait Count], + sz.total_page_lock_wait_in_ms AS [Page Lock Wait ms], + sz.avg_page_lock_wait_in_ms AS [Avg Page Lock Wait ms], + sz.total_index_lock_promotion_attempt_count AS [Lock Escalation Attempts], + sz.total_index_lock_promotion_count AS [Lock Escalations], + sz.page_latch_wait_count AS [Page Latch Wait Count], + sz.page_latch_wait_in_ms AS [Page Latch Wait ms], + sz.page_io_latch_wait_count AS [Page IO Latch Wait Count], + sz.page_io_latch_wait_in_ms as [Page IO Latch Wait ms], + sz.total_forwarded_fetch_count AS [Forwarded Fetches], + sz.data_compression_desc AS [Data Compression], + i.create_date AS [Create Date], + i.modify_date AS [Modify Date], + more_info AS [More Info], + CASE + WHEN i.is_primary_key = 1 AND i.index_definition <> '[HEAP]' + THEN N'--ALTER TABLE ' + QUOTENAME(i.[database_name]) + N'.' + QUOTENAME(i.[schema_name]) + N'.' + QUOTENAME(i.[object_name]) + + N' DROP CONSTRAINT ' + QUOTENAME(i.index_name) + N';' + WHEN i.is_primary_key = 0 AND i.is_unique_constraint = 1 AND i.index_definition <> '[HEAP]' + THEN N'--ALTER TABLE ' + QUOTENAME(i.[database_name]) + N'.' + QUOTENAME(i.[schema_name]) + N'.' + QUOTENAME(i.[object_name]) + + N' DROP CONSTRAINT ' + QUOTENAME(i.index_name) + N';' + WHEN i.is_primary_key = 0 AND i.index_definition <> '[HEAP]' + THEN N'--DROP INDEX '+ QUOTENAME(i.index_name) + N' ON ' + QUOTENAME(i.[database_name]) + N'.' + + QUOTENAME(i.[schema_name]) + N'.' + QUOTENAME(i.[object_name]) + N';' + ELSE N'' + END AS [Drop TSQL], + CASE + WHEN i.index_definition = '[HEAP]' THEN N'' + ELSE N'--' + ict.create_tsql END AS [Create TSQL], + 1 AS [Display Order] + INTO #Mode2Temp + FROM #IndexSanity AS i --left join here so we don't lose disabled nc indexes + LEFT JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id + LEFT JOIN #IndexCreateTsql AS ict ON i.index_sanity_id = ict.index_sanity_id + OPTION(RECOMPILE); + + IF @@ROWCOUNT > 0 + BEGIN + SELECT + sz.* + FROM #Mode2Temp AS sz + ORDER BY /* Shout out to DHutmacher */ + /*DESC*/ + CASE WHEN @SortOrder = N'rows' AND @SortDirection = N'desc' THEN sz.[Rows] ELSE NULL END DESC, + CASE WHEN @SortOrder = N'reserved_mb' AND @SortDirection = N'desc' THEN sz.[Reserved MB] ELSE NULL END DESC, + CASE WHEN @SortOrder = N'size' AND @SortDirection = N'desc' THEN sz.[Reserved MB] ELSE NULL END DESC, + CASE WHEN @SortOrder = N'reserved_lob_mb' AND @SortDirection = N'desc' THEN sz.[Reserved LOB MB] ELSE NULL END DESC, + CASE WHEN @SortOrder = N'lob' AND @SortDirection = N'desc' THEN sz.[Reserved LOB MB] ELSE NULL END DESC, + CASE WHEN @SortOrder = N'total_row_lock_wait_in_ms' AND @SortDirection = N'desc' THEN COALESCE(sz.[Row Lock Wait ms],0) ELSE NULL END DESC, + CASE WHEN @SortOrder = N'total_page_lock_wait_in_ms' AND @SortDirection = N'desc' THEN COALESCE(sz.[Page Lock Wait ms],0) ELSE NULL END DESC, + CASE WHEN @SortOrder = N'lock_time' AND @SortDirection = N'desc' THEN (COALESCE(sz.[Row Lock Wait ms],0) + COALESCE(sz.[Page Lock Wait ms],0)) ELSE NULL END DESC, + CASE WHEN @SortOrder = N'total_reads' AND @SortDirection = N'desc' THEN [Total Reads] ELSE NULL END DESC, + CASE WHEN @SortOrder = N'reads' AND @SortDirection = N'desc' THEN [Total Reads] ELSE NULL END DESC, + CASE WHEN @SortOrder = N'user_updates' AND @SortDirection = N'desc' THEN [User Updates] ELSE NULL END DESC, + CASE WHEN @SortOrder = N'writes' AND @SortDirection = N'desc' THEN [User Updates] ELSE NULL END DESC, + CASE WHEN @SortOrder = N'reads_per_write' AND @SortDirection = N'desc' THEN [Reads Per Write] ELSE NULL END DESC, + CASE WHEN @SortOrder = N'ratio' AND @SortDirection = N'desc' THEN [Reads Per Write] ELSE NULL END DESC, + CASE WHEN @SortOrder = N'forward_fetches' AND @SortDirection = N'desc' THEN sz.[Forwarded Fetches] ELSE NULL END DESC, + CASE WHEN @SortOrder = N'fetches' AND @SortDirection = N'desc' THEN sz.[Forwarded Fetches] ELSE NULL END DESC, + CASE WHEN @SortOrder = N'create_date' AND @SortDirection = N'desc' THEN CONVERT(DATETIME, sz.[Create Date]) ELSE NULL END DESC, + CASE WHEN @SortOrder = N'modify_date' AND @SortDirection = N'desc' THEN CONVERT(DATETIME, sz.[Modify Date]) ELSE NULL END DESC, + /*ASC*/ + CASE WHEN @SortOrder = N'rows' AND @SortDirection = N'asc' THEN sz.[Rows] ELSE NULL END ASC, + CASE WHEN @SortOrder = N'reserved_mb' AND @SortDirection = N'asc' THEN sz.[Reserved MB] ELSE NULL END ASC, + CASE WHEN @SortOrder = N'size' AND @SortDirection = N'asc' THEN sz.[Reserved MB] ELSE NULL END ASC, + CASE WHEN @SortOrder = N'reserved_lob_mb' AND @SortDirection = N'asc' THEN sz.[Reserved LOB MB] ELSE NULL END ASC, + CASE WHEN @SortOrder = N'lob' AND @SortDirection = N'asc' THEN sz.[Reserved LOB MB] ELSE NULL END ASC, + CASE WHEN @SortOrder = N'total_row_lock_wait_in_ms' AND @SortDirection = N'asc' THEN COALESCE(sz.[Row Lock Wait ms],0) ELSE NULL END ASC, + CASE WHEN @SortOrder = N'total_page_lock_wait_in_ms' AND @SortDirection = N'asc' THEN COALESCE(sz.[Page Lock Wait ms],0) ELSE NULL END ASC, + CASE WHEN @SortOrder = N'lock_time' AND @SortDirection = N'asc' THEN (COALESCE(sz.[Row Lock Wait ms],0) + COALESCE(sz.[Page Lock Wait ms],0)) ELSE NULL END ASC, + CASE WHEN @SortOrder = N'total_reads' AND @SortDirection = N'asc' THEN [Total Reads] ELSE NULL END ASC, + CASE WHEN @SortOrder = N'reads' AND @SortDirection = N'asc' THEN [Total Reads] ELSE NULL END ASC, + CASE WHEN @SortOrder = N'user_updates' AND @SortDirection = N'asc' THEN [User Updates] ELSE NULL END ASC, + CASE WHEN @SortOrder = N'writes' AND @SortDirection = N'asc' THEN [User Updates] ELSE NULL END ASC, + CASE WHEN @SortOrder = N'reads_per_write' AND @SortDirection = N'asc' THEN [Reads Per Write] ELSE NULL END ASC, + CASE WHEN @SortOrder = N'ratio' AND @SortDirection = N'asc' THEN [Reads Per Write] ELSE NULL END ASC, + CASE WHEN @SortOrder = N'forward_fetches' AND @SortDirection = N'asc' THEN sz.[Forwarded Fetches] ELSE NULL END ASC, + CASE WHEN @SortOrder = N'fetches' AND @SortDirection = N'asc' THEN sz.[Forwarded Fetches] ELSE NULL END ASC, + CASE WHEN @SortOrder = N'create_date' AND @SortDirection = N'asc' THEN CONVERT(DATETIME, sz.[Create Date]) ELSE NULL END ASC, + CASE WHEN @SortOrder = N'modify_date' AND @SortDirection = N'asc' THEN CONVERT(DATETIME, sz.[Modify Date]) ELSE NULL END ASC, + sz.[Database Name], [Schema Name], [Object Name], [Index ID] + OPTION (RECOMPILE); + END + ELSE + BEGIN + SELECT + DatabaseDetails = + N'Database ' + + ISNULL(@DatabaseName, DB_NAME()) + + N' has ' + + ISNULL(RTRIM(@Rowcount), 0) + + N' partitions.', + BringThePain = + CASE + WHEN @BringThePain IN (0, 1) AND ISNULL(@Rowcount, 0) = 0 + THEN N'Check the database name, it looks like nothing is here.' + WHEN @BringThePain = 0 AND ISNULL(@Rowcount, 0) > 0 + THEN N'Please re-run with @BringThePain = 1' + END; + END + END; + END; /* End @Mode=2 (index detail)*/ + + + + + + + + + ELSE IF (@Mode=3) /*Missing index Detail*/ + BEGIN + IF (@ValidOutputLocation = 1 AND COALESCE(@OutputServerName, @OutputDatabaseName, @OutputSchemaName, @OutputTableName) IS NOT NULL) + BEGIN + + IF NOT @SchemaExists = 1 + BEGIN + RAISERROR (N'Invalid schema name, data could not be saved.', 16, 0); + RETURN; + END + + IF @TableExists = 0 + BEGIN + SET @StringToExecute = + N'CREATE TABLE @@@OutputDatabaseName@@@.@@@OutputSchemaName@@@.@@@OutputTableName@@@ + ( + [id] INT IDENTITY(1,1) NOT NULL, + [run_id] UNIQUEIDENTIFIER, + [run_datetime] DATETIME, + [server_name] NVARCHAR(128), + [database_name] NVARCHAR(128), + [schema_name] NVARCHAR(128), + [table_name] NVARCHAR(128), + [magic_benefit_number] BIGINT, + [missing_index_details] NVARCHAR(MAX), + [avg_total_user_cost] NUMERIC(29,4), + [avg_user_impact] NUMERIC(29,1), + [user_seeks] BIGINT, + [user_scans] BIGINT, + [unique_compiles] BIGINT, + [equality_columns_with_data_type] NVARCHAR(MAX), + [inequality_columns_with_data_type] NVARCHAR(MAX), + [included_columns_with_data_type] NVARCHAR(MAX), + [index_estimated_impact] NVARCHAR(256), + [create_tsql] NVARCHAR(MAX), + [more_info] NVARCHAR(600), + [display_order] INT, + [is_low] BIT, + [sample_query_plan] XML, + CONSTRAINT [PK_ID_@@@RunID@@@] PRIMARY KEY CLUSTERED ([id] ASC) + );'; + + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputSchemaName@@@', @OutputSchemaName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@RunID@@@', @RunID); + + IF @ValidOutputServer = 1 + BEGIN + SET @StringToExecute = REPLACE(@StringToExecute,'''',''''''); + EXEC('EXEC('''+@StringToExecute+''') AT ' + @OutputServerName); + END; + ELSE + BEGIN + EXEC(@StringToExecute); + END; + END; /* @TableExists = 0 */ + + -- Re-check that table now exists (if not we failed creating it) + SET @TableExists = NULL; + EXEC sp_executesql @TableExistsSql, N'@TableExists BIT OUTPUT', @TableExists OUTPUT; + + IF NOT @TableExists = 1 + BEGIN + RAISERROR('Creation of the output table failed.', 16, 0); + RETURN; + END; + SET @StringToExecute = + N'WITH create_date AS ( + SELECT i.database_id, + i.schema_name, + i.[object_id], + ISNULL(NULLIF(MAX(DATEDIFF(DAY, i.create_date, SYSDATETIME())), 0), 1) AS create_days + FROM #IndexSanity AS i + GROUP BY i.database_id, i.schema_name, i.object_id + ) + INSERT @@@OutputServerName@@@.@@@OutputDatabaseName@@@.@@@OutputSchemaName@@@.@@@OutputTableName@@@ + ( + [run_id], + [run_datetime], + [server_name], + [database_name], + [schema_name], + [table_name], + [magic_benefit_number], + [missing_index_details], + [avg_total_user_cost], + [avg_user_impact], + [user_seeks], + [user_scans], + [unique_compiles], + [equality_columns_with_data_type], + [inequality_columns_with_data_type], + [included_columns_with_data_type], + [index_estimated_impact], + [create_tsql], + [more_info], + [display_order], + [is_low], + [sample_query_plan] + ) + SELECT ''@@@RunID@@@'', + ''@@@GETDATE@@@'', + ''@@@LocalServerName@@@'', + -- Below should be a copy/paste of the real query + -- Make sure all quotes are escaped + -- NOTE! information line is skipped from output and the query below + -- NOTE! CTE block is above insert in the copied SQL + mi.database_name AS [Database Name], + mi.[schema_name] AS [Schema], + mi.table_name AS [Table], + CAST((mi.magic_benefit_number / CASE WHEN cd.create_days < @DaysUptime THEN cd.create_days ELSE @DaysUptime END) AS BIGINT) + AS [Magic Benefit Number], + mi.missing_index_details AS [Missing Index Details], + mi.avg_total_user_cost AS [Avg Query Cost], + mi.avg_user_impact AS [Est Index Improvement], + mi.user_seeks AS [Seeks], + mi.user_scans AS [Scans], + mi.unique_compiles AS [Compiles], + mi.equality_columns_with_data_type AS [Equality Columns], + mi.inequality_columns_with_data_type AS [Inequality Columns], + mi.included_columns_with_data_type AS [Included Columns], + mi.index_estimated_impact AS [Estimated Impact], + mi.create_tsql AS [Create TSQL], + mi.more_info AS [More Info], + 1 AS [Display Order], + mi.is_low, + mi.sample_query_plan AS [Sample Query Plan] + FROM #MissingIndexes AS mi + LEFT JOIN create_date AS cd + ON mi.[object_id] = cd.object_id + AND mi.database_id = cd.database_id + AND mi.schema_name = cd.schema_name + /* Minimum benefit threshold = 100k/day of uptime OR since table creation date, whichever is lower*/ + WHERE @ShowAllMissingIndexRequests=1 + OR (mi.magic_benefit_number / CASE WHEN cd.create_days < @DaysUptime THEN cd.create_days ELSE @DaysUptime END) >= 100000 + ORDER BY [Display Order] ASC, [Magic Benefit Number] DESC + OPTION (RECOMPILE);'; + + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputServerName@@@', @OutputServerName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputSchemaName@@@', @OutputSchemaName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@RunID@@@', @RunID); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@GETDATE@@@', GETDATE()); + SET @StringToExecute = REPLACE(@StringToExecute, '@@@LocalServerName@@@', CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128))); + EXEC sp_executesql @StringToExecute, N'@DaysUptime NUMERIC(23,2), @ShowAllMissingIndexRequests BIT', @DaysUptime = @DaysUptime, @ShowAllMissingIndexRequests = @ShowAllMissingIndexRequests; + + END; /* @ValidOutputLocation = 1 */ + ELSE + BEGIN + IF(@OutputType <> 'NONE') + BEGIN + WITH create_date AS ( + SELECT i.database_id, + i.schema_name, + i.[object_id], + ISNULL(NULLIF(MAX(DATEDIFF(DAY, i.create_date, SYSDATETIME())), 0), 1) AS create_days + FROM #IndexSanity AS i + GROUP BY i.database_id, i.schema_name, i.object_id + ) + SELECT + mi.database_name AS [Database Name], + mi.[schema_name] AS [Schema], + mi.table_name AS [Table], + CAST((mi.magic_benefit_number / CASE WHEN cd.create_days < @DaysUptime THEN cd.create_days ELSE @DaysUptime END) AS BIGINT) + AS [Magic Benefit Number], + mi.missing_index_details AS [Missing Index Details], + mi.avg_total_user_cost AS [Avg Query Cost], + mi.avg_user_impact AS [Est Index Improvement], + mi.user_seeks AS [Seeks], + mi.user_scans AS [Scans], + mi.unique_compiles AS [Compiles], + mi.equality_columns_with_data_type AS [Equality Columns], + mi.inequality_columns_with_data_type AS [Inequality Columns], + mi.included_columns_with_data_type AS [Included Columns], + mi.index_estimated_impact AS [Estimated Impact], + mi.create_tsql AS [Create TSQL], + mi.more_info AS [More Info], + 1 AS [Display Order], + mi.is_low, + mi.sample_query_plan AS [Sample Query Plan] + FROM #MissingIndexes AS mi + LEFT JOIN create_date AS cd + ON mi.[object_id] = cd.object_id + AND mi.database_id = cd.database_id + AND mi.schema_name = cd.schema_name + /* Minimum benefit threshold = 100k/day of uptime OR since table creation date, whichever is lower*/ + WHERE @ShowAllMissingIndexRequests=1 + OR (mi.magic_benefit_number / CASE WHEN cd.create_days < @DaysUptime THEN cd.create_days ELSE @DaysUptime END) >= 100000 + UNION ALL + SELECT + @ScriptVersionName, + N'From Your Community Volunteers' , + N'http://FirstResponderKit.org' , + 100000000000, + @DaysUptimeInsertValue, + NULL,NULL,NULL,NULL,NULL,NULL,NULL, + NULL, NULL, NULL, NULL, 0 AS [Display Order], NULL AS is_low, NULL + ORDER BY [Display Order] ASC, [Magic Benefit Number] DESC + OPTION (RECOMPILE); + END; + + + IF (@BringThePain = 1 + AND @DatabaseName IS NOT NULL + AND @GetAllDatabases = 0) - IF @FilterPlansByDatabase IS NOT NULL - BEGIN - IF UPPER(LEFT(@FilterPlansByDatabase,4)) = 'USER' - BEGIN - INSERT INTO #FilterPlansByDatabase (DatabaseID) - SELECT database_id - FROM sys.databases - WHERE [name] NOT IN ('master', 'model', 'msdb', 'tempdb'); - END; - ELSE - BEGIN - SET @FilterPlansByDatabase = @FilterPlansByDatabase + ',' - ;WITH a AS - ( - SELECT CAST(1 AS BIGINT) f, CHARINDEX(',', @FilterPlansByDatabase) t, 1 SEQ - UNION ALL - SELECT t + 1, CHARINDEX(',', @FilterPlansByDatabase, t + 1), SEQ + 1 - FROM a - WHERE CHARINDEX(',', @FilterPlansByDatabase, t + 1) > 0 - ) - INSERT #FilterPlansByDatabase (DatabaseID) - SELECT DISTINCT db.database_id - FROM a - INNER JOIN sys.databases db ON LTRIM(RTRIM(SUBSTRING(@FilterPlansByDatabase, a.f, a.t - a.f))) = db.name - WHERE SUBSTRING(@FilterPlansByDatabase, f, t - f) IS NOT NULL - OPTION (MAXRECURSION 0); - END; - END; + BEGIN + EXEC sp_BlitzCache @SortOrder = 'sp_BlitzIndex', @DatabaseName = @DatabaseName, @BringThePain = 1, @QueryFilter = 'statement', @HideSummary = 1; + END; - IF OBJECT_ID('tempdb..#ReadableDBs') IS NOT NULL - DROP TABLE #ReadableDBs; - CREATE TABLE #ReadableDBs ( - database_id INT - ); + END; - IF EXISTS (SELECT * FROM sys.all_objects o WHERE o.name = 'dm_hadr_database_replica_states') - BEGIN - RAISERROR('Checking for Read intent databases to exclude',0,0) WITH NOWAIT; - SET @StringToExecute = 'INSERT INTO #ReadableDBs (database_id) SELECT DBs.database_id FROM sys.databases DBs INNER JOIN sys.availability_replicas Replicas ON DBs.replica_id = Replicas.replica_id WHERE replica_server_name NOT IN (SELECT DISTINCT primary_replica FROM sys.dm_hadr_availability_group_states States) AND Replicas.secondary_role_allow_connections_desc = ''READ_ONLY'' AND replica_server_name = @@SERVERNAME;'; - EXEC(@StringToExecute); - - END - DECLARE @v DECIMAL(6,2), - @build INT, - @memGrantSortSupported BIT = 1; - RAISERROR (N'Determining SQL Server version.',0,1) WITH NOWAIT; - INSERT INTO #checkversion (version) - SELECT CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)) - OPTION (RECOMPILE); + END; /* End @Mode=3 (index detail)*/ + SET @d = CONVERT(VARCHAR(19), GETDATE(), 121); + RAISERROR (N'finishing at %s',0,1, @d) WITH NOWAIT; +END /* End @TableName IS NULL (mode 0/1/2/3/4) */ +END TRY +BEGIN CATCH + RAISERROR (N'Failure analyzing temp tables.', 0,1) WITH NOWAIT; - SELECT @v = common_version , - @build = build - FROM #checkversion - OPTION (RECOMPILE); + SELECT @msg = ERROR_MESSAGE(), @ErrorSeverity = ERROR_SEVERITY(), @ErrorState = ERROR_STATE(); - IF (@v < 11) - OR (@v = 11 AND @build < 6020) - OR (@v = 12 AND @build < 5000) - OR (@v = 13 AND @build < 1601) - SET @memGrantSortSupported = 0; + RAISERROR (@msg, + @ErrorSeverity, + @ErrorState + ); + + WHILE @@trancount > 0 + ROLLBACK; - IF EXISTS (SELECT * FROM sys.all_objects WHERE name = 'dm_exec_query_statistics_xml') - AND ((@v = 13 AND @build >= 5337) /* This DMF causes assertion errors: https://support.microsoft.com/en-us/help/4490136/fix-assertion-error-occurs-when-you-use-sys-dm-exec-query-statistics-x */ - OR (@v = 14 AND @build >= 3162) - OR (@v >= 15) - OR (@v <= 12)) /* Azure */ - SET @dm_exec_query_statistics_xml = 1; + RETURN; + END CATCH; +GO +IF OBJECT_ID('dbo.sp_BlitzLock') IS NULL +BEGIN + EXEC ('CREATE PROCEDURE dbo.sp_BlitzLock AS RETURN 0;'); +END; +GO + +ALTER PROCEDURE + dbo.sp_BlitzLock +( + @DatabaseName sysname = NULL, + @StartDate datetime = NULL, + @EndDate datetime = NULL, + @ObjectName nvarchar(1024) = NULL, + @StoredProcName nvarchar(1024) = NULL, + @AppName sysname = NULL, + @HostName sysname = NULL, + @LoginName sysname = NULL, + @EventSessionName sysname = N'system_health', + @TargetSessionType sysname = NULL, + @VictimsOnly bit = 0, + @Debug bit = 0, + @Help bit = 0, + @Version varchar(30) = NULL OUTPUT, + @VersionDate datetime = NULL OUTPUT, + @VersionCheckMode bit = 0, + @OutputDatabaseName sysname = NULL, + @OutputSchemaName sysname = N'dbo', /*ditto as below*/ + @OutputTableName sysname = N'BlitzLock', /*put a standard here no need to check later in the script*/ + @ExportToExcel bit = 0 +) +WITH RECOMPILE +AS +BEGIN + SET STATISTICS XML OFF; + SET NOCOUNT ON; + SET XACT_ABORT OFF; + SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + SELECT @Version = '8.19', @VersionDate = '20240222'; - SET @StockWarningHeader = '', - @StockDetailsHeader = @StockDetailsHeader + ''; + IF @Help = 1 + BEGIN + PRINT N' + /* + sp_BlitzLock from http://FirstResponderKit.org - /* Get the instance name to use as a Perfmon counter prefix. */ - IF SERVERPROPERTY('EngineEdition') IN (5, 8) /*CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) = 'SQL Azure'*/ - SELECT TOP 1 @ServiceName = LEFT(object_name, (CHARINDEX(':', object_name) - 1)) - FROM sys.dm_os_performance_counters; - ELSE - BEGIN - SET @StringToExecute = 'INSERT INTO #PerfmonStats(object_name, Pass, SampleTime, counter_name, cntr_type) SELECT CASE WHEN @@SERVICENAME = ''MSSQLSERVER'' THEN ''SQLServer'' ELSE ''MSSQL$'' + @@SERVICENAME END, 0, SYSDATETIMEOFFSET(), ''stuffing'', 0 ;'; - EXEC(@StringToExecute); - SELECT @ServiceName = object_name FROM #PerfmonStats; - DELETE #PerfmonStats; - END; + This script checks for and analyzes deadlocks from the system health session or a custom extended event path - /* Build a list of queries that were run in the last 10 seconds. - We're looking for the death-by-a-thousand-small-cuts scenario - where a query is constantly running, and it doesn't have that - big of an impact individually, but it has a ton of impact - overall. We're going to build this list, and then after we - finish our @Seconds sample, we'll compare our plan cache to - this list to see what ran the most. */ + Variables you can use: + + @DatabaseName: If you want to filter to a specific database - /* Populate #QueryStats. SQL 2005 doesn't have query hash or query plan hash. */ - IF @CheckProcedureCache = 1 - BEGIN - RAISERROR('@CheckProcedureCache = 1, capturing first pass of plan cache',10,1) WITH NOWAIT; - IF @@VERSION LIKE 'Microsoft SQL Server 2005%' - BEGIN - IF @FilterPlansByDatabase IS NULL - BEGIN - SET @StringToExecute = N'INSERT INTO #QueryStats ([sql_handle], Pass, SampleTime, statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, Points) - SELECT [sql_handle], 1 AS Pass, SYSDATETIMEOFFSET(), statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, NULL AS query_hash, NULL AS query_plan_hash, 0 - FROM sys.dm_exec_query_stats qs - WHERE qs.last_execution_time >= (DATEADD(ss, -10, SYSDATETIMEOFFSET()));'; - END; - ELSE - BEGIN - SET @StringToExecute = N'INSERT INTO #QueryStats ([sql_handle], Pass, SampleTime, statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, Points) - SELECT [sql_handle], 1 AS Pass, SYSDATETIMEOFFSET(), statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, NULL AS query_hash, NULL AS query_plan_hash, 0 - FROM sys.dm_exec_query_stats qs - CROSS APPLY sys.dm_exec_plan_attributes(qs.plan_handle) AS attr - INNER JOIN #FilterPlansByDatabase dbs ON CAST(attr.value AS INT) = dbs.DatabaseID - WHERE qs.last_execution_time >= (DATEADD(ss, -10, SYSDATETIMEOFFSET())) - AND attr.attribute = ''dbid'';'; - END; - END; - ELSE - BEGIN - IF @FilterPlansByDatabase IS NULL - BEGIN - SET @StringToExecute = N'INSERT INTO #QueryStats ([sql_handle], Pass, SampleTime, statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, Points) - SELECT [sql_handle], 1 AS Pass, SYSDATETIMEOFFSET(), statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, 0 - FROM sys.dm_exec_query_stats qs - WHERE qs.last_execution_time >= (DATEADD(ss, -10, SYSDATETIMEOFFSET()));'; - END; - ELSE - BEGIN - SET @StringToExecute = N'INSERT INTO #QueryStats ([sql_handle], Pass, SampleTime, statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, Points) - SELECT [sql_handle], 1 AS Pass, SYSDATETIMEOFFSET(), statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, 0 - FROM sys.dm_exec_query_stats qs - CROSS APPLY sys.dm_exec_plan_attributes(qs.plan_handle) AS attr - INNER JOIN #FilterPlansByDatabase dbs ON CAST(attr.value AS INT) = dbs.DatabaseID - WHERE qs.last_execution_time >= (DATEADD(ss, -10, SYSDATETIMEOFFSET())) - AND attr.attribute = ''dbid'';'; - END; - END; - EXEC(@StringToExecute); + @StartDate: The date you want to start searching on, defaults to last 7 days - /* Get the totals for the entire plan cache */ - INSERT INTO #QueryStats (Pass, SampleTime, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time) - SELECT -1 AS Pass, SYSDATETIMEOFFSET(), SUM(execution_count), SUM(total_worker_time), SUM(total_physical_reads), SUM(total_logical_writes), SUM(total_logical_reads), SUM(total_clr_time), SUM(total_elapsed_time), MIN(creation_time) - FROM sys.dm_exec_query_stats qs; - END; /*IF @CheckProcedureCache = 1 */ + @EndDate: The date you want to stop searching on, defaults to current date + @ObjectName: If you want to filter to a specific able. + The object name has to be fully qualified ''Database.Schema.Table'' - IF EXISTS (SELECT * - FROM tempdb.sys.all_objects obj - INNER JOIN tempdb.sys.all_columns col1 ON obj.object_id = col1.object_id AND col1.name = 'object_name' - INNER JOIN tempdb.sys.all_columns col2 ON obj.object_id = col2.object_id AND col2.name = 'counter_name' - INNER JOIN tempdb.sys.all_columns col3 ON obj.object_id = col3.object_id AND col3.name = 'instance_name' - WHERE obj.name LIKE '%CustomPerfmonCounters%') - BEGIN - SET @StringToExecute = 'INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) SELECT [object_name],[counter_name],[instance_name] FROM #CustomPerfmonCounters'; - EXEC(@StringToExecute); - END; - ELSE - BEGIN - /* Add our default Perfmon counters */ - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Forwarded Records/sec', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Page compression attempts/sec', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Page Splits/sec', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Skipped Ghosted Records/sec', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Table Lock Escalations/sec', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Worktables Created/sec', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Availability Group','Active Hadr Threads','_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Availability Replica','Bytes Received from Replica/sec','_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Availability Replica','Bytes Sent to Replica/sec','_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Availability Replica','Bytes Sent to Transport/sec','_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Availability Replica','Flow Control Time (ms/sec)','_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Availability Replica','Flow Control/sec','_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Availability Replica','Resent Messages/sec','_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Availability Replica','Sends to Replica/sec','_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Page life expectancy', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Page reads/sec', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Page writes/sec', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Readahead pages/sec', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Target pages', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Total pages', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Databases','', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Active Transactions','_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Database Flow Control Delay', '_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Database Flow Controls/sec', '_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Group Commit Time', '_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Group Commits/Sec', '_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Log Apply Pending Queue', '_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Log Apply Ready Queue', '_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Log Compression Cache misses/sec', '_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Log remaining for undo', '_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Log Send Queue', '_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Recovery Queue', '_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Redo blocked/sec', '_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Redo Bytes Remaining', '_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Redone Bytes/sec', '_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Databases','Log Bytes Flushed/sec', '_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Databases','Log Growths', '_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Databases','Log Pool LogWriter Pushes/sec', '_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Databases','Log Shrinks', '_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Databases','Transactions/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Databases','Write Transactions/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Databases','XTP Memory Used (KB)',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Exec Statistics','Distributed Query', 'Execs in progress'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Exec Statistics','DTC calls', 'Execs in progress'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Exec Statistics','Extended Procedures', 'Execs in progress'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Exec Statistics','OLEDB calls', 'Execs in progress'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':General Statistics','Active Temp Tables', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':General Statistics','Logins/sec', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':General Statistics','Logouts/sec', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':General Statistics','Mars Deadlocks', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':General Statistics','Processes blocked', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Locks','Number of Deadlocks/sec', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Memory Manager','Memory Grants Pending', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Errors','Errors/sec', '_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','Batch Requests/sec', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','Forced Parameterizations/sec', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','Guided plan executions/sec', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','SQL Attention rate', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','SQL Compilations/sec', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','SQL Re-Compilations/sec', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Workload Group Stats','Query optimizations/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Workload Group Stats','Suboptimal plans/sec',NULL); - /* Below counters added by Jefferson Elias */ - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Worktables From Cache Base',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Worktables From Cache Ratio',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Database pages',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Free pages',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Stolen pages',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Memory Manager','Granted Workspace Memory (KB)',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Memory Manager','Maximum Workspace Memory (KB)',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Memory Manager','Target Server Memory (KB)',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Memory Manager','Total Server Memory (KB)',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Buffer cache hit ratio',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Buffer cache hit ratio base',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Checkpoint pages/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Free list stalls/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Lazy writes/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','Auto-Param Attempts/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','Failed Auto-Params/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','Safe Auto-Params/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','Unsafe Auto-Params/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Workfiles Created/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':General Statistics','User Connections',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Latches','Average Latch Wait Time (ms)',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Latches','Average Latch Wait Time Base',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Latches','Latch Waits/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Latches','Total Latch Wait Time (ms)',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Locks','Average Wait Time (ms)',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Locks','Average Wait Time Base',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Locks','Lock Requests/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Locks','Lock Timeouts/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Locks','Lock Wait Time (ms)',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Locks','Lock Waits/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Transactions','Longest Transaction Running Time',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Full Scans/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Index Searches/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Page lookups/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Cursor Manager by Type','Active cursors',NULL); - /* Below counters are for In-Memory OLTP (Hekaton), which have a different naming convention. - And yes, they actually hard-coded the version numbers into the counters, and SQL 2019 still says 2017, oddly. - For why, see: https://connect.microsoft.com/SQLServer/feedback/details/817216/xtp-perfmon-counters-should-appear-under-sql-server-perfmon-counter-group - */ - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP Cursors','Expired rows removed/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP Cursors','Expired rows touched/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP Garbage Collection','Rows processed/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP IO Governor','Io Issued/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP Phantom Processor','Phantom expired rows touched/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP Phantom Processor','Phantom rows touched/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP Transaction Log','Log bytes written/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP Transaction Log','Log records written/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP Transactions','Transactions aborted by user/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP Transactions','Transactions aborted/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP Transactions','Transactions created/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP Cursors','Expired rows removed/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP Cursors','Expired rows touched/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP Garbage Collection','Rows processed/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP IO Governor','Io Issued/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP Phantom Processor','Phantom expired rows touched/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP Phantom Processor','Phantom rows touched/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP Transaction Log','Log bytes written/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP Transaction Log','Log records written/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP Transactions','Transactions aborted by user/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP Transactions','Transactions aborted/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP Transactions','Transactions created/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP Cursors','Expired rows removed/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP Cursors','Expired rows touched/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP Garbage Collection','Rows processed/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP IO Governor','Io Issued/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP Phantom Processor','Phantom expired rows touched/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP Phantom Processor','Phantom rows touched/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP Transaction Log','Log bytes written/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP Transaction Log','Log records written/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP Transactions','Transactions aborted by user/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP Transactions','Transactions aborted/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP Transactions','Transactions created/sec',NULL); - END; + @StoredProcName: If you want to search for a single stored proc + The proc name has to be fully qualified ''Database.Schema.Sproc'' + @AppName: If you want to filter to a specific application - IF @total_cpu_usage IN (0, 1) - BEGIN - EXEC sys.sp_executesql - @get_thread_time_ms, - N'@thread_time_ms FLOAT OUTPUT', - @thread_time_ms OUTPUT; - END + @HostName: If you want to filter to a specific host - /* Populate #FileStats, #PerfmonStats, #WaitStats with DMV data. - After we finish doing our checks, we'll take another sample and compare them. */ - RAISERROR('Capturing first pass of wait stats, perfmon counters, file stats',10,1) WITH NOWAIT; - SET @StringToExecute = N' - INSERT #WaitStats(Pass, SampleTime, wait_type, wait_time_ms, thread_time_ms, signal_wait_time_ms, waiting_tasks_count) - SELECT - x.Pass, - x.SampleTime, - x.wait_type, - SUM(x.sum_wait_time_ms) AS sum_wait_time_ms, - CASE @Seconds - WHEN 0 - THEN 0 - ELSE @thread_time_ms - END AS thread_time_ms, - SUM(x.sum_signal_wait_time_ms) AS sum_signal_wait_time_ms, - SUM(x.sum_waiting_tasks) AS sum_waiting_tasks - FROM ( - SELECT - 1 AS Pass, - CASE @Seconds WHEN 0 THEN @StartSampleTime ELSE SYSDATETIMEOFFSET() END AS SampleTime, - owt.wait_type, - CASE @Seconds WHEN 0 THEN 0 ELSE SUM(owt.wait_duration_ms) OVER (PARTITION BY owt.wait_type, owt.session_id) - - CASE WHEN @Seconds = 0 THEN 0 ELSE (@Seconds * 1000) END END AS sum_wait_time_ms, - 0 AS sum_signal_wait_time_ms, - 0 AS sum_waiting_tasks - FROM sys.dm_os_waiting_tasks owt - WHERE owt.session_id > 50 - AND owt.wait_duration_ms >= CASE @Seconds WHEN 0 THEN 0 ELSE @Seconds * 1000 END - UNION ALL - SELECT - 1 AS Pass, - CASE @Seconds WHEN 0 THEN @StartSampleTime ELSE SYSDATETIMEOFFSET() END AS SampleTime, - os.wait_type, - CASE @Seconds WHEN 0 THEN 0 ELSE SUM(os.wait_time_ms) OVER (PARTITION BY os.wait_type) END AS sum_wait_time_ms, - CASE @Seconds WHEN 0 THEN 0 ELSE SUM(os.signal_wait_time_ms) OVER (PARTITION BY os.wait_type ) END AS sum_signal_wait_time_ms, - CASE @Seconds WHEN 0 THEN 0 ELSE SUM(os.waiting_tasks_count) OVER (PARTITION BY os.wait_type) END AS sum_waiting_tasks '; + @LoginName: If you want to filter to a specific login + + @EventSessionName: If you want to point this at an XE session rather than the system health session. + + @TargetSessionType: Can be ''ring_buffer'' or ''event_file''. Leave NULL to auto-detect. - IF SERVERPROPERTY('EngineEdition') = 5 /*SERVERPROPERTY('Edition') = 'SQL Azure'*/ - SET @StringToExecute = @StringToExecute + N' FROM sys.dm_db_wait_stats os '; - ELSE - SET @StringToExecute = @StringToExecute + N' FROM sys.dm_os_wait_stats os '; + @OutputDatabaseName: If you want to output information to a specific database - SET @StringToExecute = @StringToExecute + N' - ) x - WHERE NOT EXISTS - ( - SELECT * - FROM ##WaitCategories AS wc - WHERE wc.WaitType = x.wait_type - AND wc.Ignorable = 1 - ) - GROUP BY x.Pass, x.SampleTime, x.wait_type - ORDER BY sum_wait_time_ms DESC;' - - EXEC sys.sp_executesql - @StringToExecute, - N'@StartSampleTime DATETIMEOFFSET, - @Seconds INT, - @thread_time_ms FLOAT', - @StartSampleTime, - @Seconds, - @thread_time_ms; - - WITH w AS - ( - SELECT - total_waits = - CONVERT - ( - FLOAT, - SUM(ws.wait_time_ms) - ) - FROM #WaitStats AS ws - WHERE Pass = 1 - ) - UPDATE ws - SET ws.thread_time_ms += w.total_waits - FROM #WaitStats AS ws - CROSS JOIN w - WHERE ws.Pass = 1 - OPTION(RECOMPILE); - - INSERT INTO #FileStats (Pass, SampleTime, DatabaseID, FileID, DatabaseName, FileLogicalName, SizeOnDiskMB, io_stall_read_ms , - num_of_reads, [bytes_read] , io_stall_write_ms,num_of_writes, [bytes_written], PhysicalName, TypeDesc) - SELECT - 1 AS Pass, - CASE @Seconds WHEN 0 THEN @StartSampleTime ELSE SYSDATETIMEOFFSET() END AS SampleTime, - mf.[database_id], - mf.[file_id], - DB_NAME(vfs.database_id) AS [db_name], - mf.name + N' [' + mf.type_desc COLLATE SQL_Latin1_General_CP1_CI_AS + N']' AS file_logical_name , - CAST(( ( vfs.size_on_disk_bytes / 1024.0 ) / 1024.0 ) AS INT) AS size_on_disk_mb , - CASE @Seconds WHEN 0 THEN 0 ELSE vfs.io_stall_read_ms END , - CASE @Seconds WHEN 0 THEN 0 ELSE vfs.num_of_reads END , - CASE @Seconds WHEN 0 THEN 0 ELSE vfs.[num_of_bytes_read] END , - CASE @Seconds WHEN 0 THEN 0 ELSE vfs.io_stall_write_ms END , - CASE @Seconds WHEN 0 THEN 0 ELSE vfs.num_of_writes END , - CASE @Seconds WHEN 0 THEN 0 ELSE vfs.[num_of_bytes_written] END , - mf.physical_name, - mf.type_desc - FROM sys.dm_io_virtual_file_stats (NULL, NULL) AS vfs - INNER JOIN #MasterFiles AS mf ON vfs.file_id = mf.file_id - AND vfs.database_id = mf.database_id - WHERE vfs.num_of_reads > 0 - OR vfs.num_of_writes > 0; + @OutputSchemaName: Specify a schema name to output information to a specific Schema - INSERT INTO #PerfmonStats (Pass, SampleTime, [object_name],[counter_name],[instance_name],[cntr_value],[cntr_type]) - SELECT 1 AS Pass, - CASE @Seconds WHEN 0 THEN @StartSampleTime ELSE SYSDATETIMEOFFSET() END AS SampleTime, RTRIM(dmv.object_name), RTRIM(dmv.counter_name), RTRIM(dmv.instance_name), CASE @Seconds WHEN 0 THEN 0 ELSE dmv.cntr_value END, dmv.cntr_type - FROM #PerfmonCounters counters - INNER JOIN sys.dm_os_performance_counters dmv ON counters.counter_name COLLATE SQL_Latin1_General_CP1_CI_AS = RTRIM(dmv.counter_name) COLLATE SQL_Latin1_General_CP1_CI_AS - AND counters.[object_name] COLLATE SQL_Latin1_General_CP1_CI_AS = RTRIM(dmv.[object_name]) COLLATE SQL_Latin1_General_CP1_CI_AS - AND (counters.[instance_name] IS NULL OR counters.[instance_name] COLLATE SQL_Latin1_General_CP1_CI_AS = RTRIM(dmv.[instance_name]) COLLATE SQL_Latin1_General_CP1_CI_AS); + @OutputTableName: Specify table name to to output information to a specific table - /* For Github #2743: */ - CREATE TABLE #TempdbOperationalStats (object_id BIGINT PRIMARY KEY CLUSTERED, - forwarded_fetch_count BIGINT); - INSERT INTO #TempdbOperationalStats (object_id, forwarded_fetch_count) - SELECT object_id, forwarded_fetch_count - FROM tempdb.sys.dm_db_index_operational_stats(DB_ID('tempdb'), NULL, NULL, NULL) os - WHERE os.database_id = DB_ID('tempdb') - AND os.forwarded_fetch_count > 100; + To learn more, visit http://FirstResponderKit.org where you can download new + versions for free, watch training videos on how it works, get more info on + the findings, contribute your own code, and more. + Known limitations of this version: + - Only SQL Server 2012 and newer is supported + - If your tables have weird characters in them (https://en.wikipedia.org/wiki/List_of_xml_and_HTML_character_entity_references) you may get errors trying to parse the xml. + I took a long look at this one, and: + 1) Trying to account for all the weird places these could crop up is a losing effort. + 2) Replace is slow af on lots of xml. - /* If they want to run sp_BlitzWho and export to table, go for it. */ - IF @OutputTableNameBlitzWho IS NOT NULL - AND @OutputDatabaseName IS NOT NULL - AND @OutputSchemaName IS NOT NULL - AND EXISTS ( SELECT * - FROM sys.databases - WHERE QUOTENAME([name]) = @OutputDatabaseName) - BEGIN - RAISERROR('Logging sp_BlitzWho to table',10,1) WITH NOWAIT; - EXEC sp_BlitzWho @OutputDatabaseName = @UnquotedOutputDatabaseName, @OutputSchemaName = @UnquotedOutputSchemaName, @OutputTableName = @OutputTableNameBlitzWho, @CheckDateOverride = @StartSampleTime, @OutputTableRetentionDays = @OutputTableRetentionDays; - END + Unknown limitations of this version: + - None. (If we knew them, they would be known. Duh.) - RAISERROR('Beginning investigatory queries',10,1) WITH NOWAIT; + MIT License + Copyright (c) Brent Ozar Unlimited - /* Maintenance Tasks Running - Backup Running - CheckID 1 */ - IF @Seconds > 0 - BEGIN - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 1',10,1) WITH NOWAIT; - END + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: - IF EXISTS(SELECT * FROM sys.dm_exec_requests WHERE total_elapsed_time > 300000 AND command LIKE 'BACKUP%') - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, QueryHash) - SELECT 1 AS CheckID, - 1 AS Priority, - 'Maintenance Tasks Running' AS FindingGroup, - 'Backup Running' AS Finding, - 'https://www.brentozar.com/askbrent/backups/' AS URL, - 'Backup of ' + DB_NAME(db.resource_database_id) + ' database (' + (SELECT CAST(CAST(SUM(size * 8.0 / 1024 / 1024) AS BIGINT) AS NVARCHAR) FROM #MasterFiles WHERE database_id = db.resource_database_id) + 'GB) ' + @LineFeed - + CAST(r.percent_complete AS NVARCHAR(100)) + '% complete, has been running since ' + CAST(r.start_time AS NVARCHAR(100)) + '. ' + @LineFeed - + CASE WHEN COALESCE(s.nt_username, s.loginame) IS NOT NULL THEN (' Login: ' + COALESCE(s.nt_username, s.loginame) + ' ') ELSE '' END AS Details, - 'KILL ' + CAST(r.session_id AS NVARCHAR(100)) + ';' AS HowToStopIt, - pl.query_plan AS QueryPlan, - r.start_time AS StartTime, - s.loginame AS LoginName, - s.nt_username AS NTUserName, - s.[program_name] AS ProgramName, - s.[hostname] AS HostName, - db.[resource_database_id] AS DatabaseID, - DB_NAME(db.resource_database_id) AS DatabaseName, - 0 AS OpenTransactionCount, - r.query_hash - FROM sys.dm_exec_requests r - INNER JOIN sys.dm_exec_connections c ON r.session_id = c.session_id - INNER JOIN sys.sysprocesses AS s ON r.session_id = s.spid AND s.ecid = 0 - INNER JOIN - ( - SELECT DISTINCT - t.request_session_id, - t.resource_database_id - FROM sys.dm_tran_locks AS t - WHERE t.resource_type = N'DATABASE' - AND t.request_mode = N'S' - AND t.request_status = N'GRANT' - AND t.request_owner_type = N'SHARED_TRANSACTION_WORKSPACE' - ) AS db ON s.spid = db.request_session_id AND s.dbid = db.resource_database_id - CROSS APPLY sys.dm_exec_query_plan(r.plan_handle) pl - WHERE r.command LIKE 'BACKUP%' - AND r.start_time <= DATEADD(minute, -5, GETDATE()) - AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs); - END + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. - /* If there's a backup running, add details explaining how long full backup has been taking in the last month. */ - IF @Seconds > 0 AND SERVERPROPERTY('EngineEdition') <> 5 /*CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) <> 'SQL Azure'*/ - BEGIN - SET @StringToExecute = 'UPDATE #BlitzFirstResults SET Details = Details + '' Over the last 60 days, the full backup usually takes '' + CAST((SELECT AVG(DATEDIFF(mi, bs.backup_start_date, bs.backup_finish_date)) FROM msdb.dbo.backupset bs WHERE abr.DatabaseName = bs.database_name AND bs.type = ''D'' AND bs.backup_start_date > DATEADD(dd, -60, SYSDATETIMEOFFSET()) AND bs.backup_finish_date IS NOT NULL) AS NVARCHAR(100)) + '' minutes.'' FROM #BlitzFirstResults abr WHERE abr.CheckID = 1 AND EXISTS (SELECT * FROM msdb.dbo.backupset bs WHERE bs.type = ''D'' AND bs.backup_start_date > DATEADD(dd, -60, SYSDATETIMEOFFSET()) AND bs.backup_finish_date IS NOT NULL AND abr.DatabaseName = bs.database_name AND DATEDIFF(mi, bs.backup_start_date, bs.backup_finish_date) > 1)'; - EXEC(@StringToExecute); - END; + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + */'; - /* Maintenance Tasks Running - DBCC CHECK* Running - CheckID 2 */ - IF @Seconds > 0 - BEGIN - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 2',10,1) WITH NOWAIT; - END + RETURN; + END; /* @Help = 1 */ + + /*Declare local variables used in the procudure*/ + DECLARE + @DatabaseId int = + DB_ID(@DatabaseName), + @ProductVersion nvarchar(128) = + CAST(SERVERPROPERTY('ProductVersion') AS nvarchar(128)), + @ProductVersionMajor float = + SUBSTRING + ( + CAST(SERVERPROPERTY('ProductVersion') AS nvarchar(128)), + 1, + CHARINDEX('.', CAST(SERVERPROPERTY('ProductVersion') AS nvarchar(128))) + 1 + ), + @ProductVersionMinor int = + PARSENAME + ( + CONVERT + ( + varchar(32), + CAST(SERVERPROPERTY('ProductVersion') AS nvarchar(128)) + ), + 2 + ), + @ObjectFullName nvarchar(MAX) = N'', + @Azure bit = + CASE + WHEN + ( + SELECT + CONVERT + ( + integer, + SERVERPROPERTY('EngineEdition') + ) + ) = 5 + THEN 1 + ELSE 0 + END, + @MI bit = + CASE + WHEN + ( + SELECT + CONVERT + ( + integer, + SERVERPROPERTY('EngineEdition') + ) + ) = 8 + THEN 1 + ELSE 0 + END, + @RDS bit = + CASE + WHEN LEFT(CAST(SERVERPROPERTY('ComputerNamePhysicalNetBIOS') AS varchar(8000)), 8) <> 'EC2AMAZ-' + AND LEFT(CAST(SERVERPROPERTY('MachineName') AS varchar(8000)), 8) <> 'EC2AMAZ-' + AND DB_ID('rdsadmin') IS NULL + THEN 0 + ELSE 1 + END, + @d varchar(40) = '', + @StringToExecute nvarchar(4000) = N'', + @StringToExecuteParams nvarchar(500) = N'', + @r sysname = NULL, + @OutputTableFindings nvarchar(100) = N'[BlitzLockFindings]', + @DeadlockCount int = 0, + @ServerName sysname = @@SERVERNAME, + @OutputDatabaseCheck bit = -1, + @SessionId int = 0, + @TargetSessionId int = 0, + @FileName nvarchar(4000) = N'', + @inputbuf_bom nvarchar(1) = CONVERT(nvarchar(1), 0x0a00, 0), + @deadlock_result nvarchar(MAX) = N'', + @StartDateOriginal datetime = @StartDate, + @EndDateOriginal datetime = @EndDate, + @StartDateUTC datetime, + @EndDateUTC datetime; - IF EXISTS (SELECT * FROM sys.dm_exec_requests WHERE command LIKE 'DBCC%' AND total_elapsed_time > 5000) - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, QueryHash) - SELECT 2 AS CheckID, - 1 AS Priority, - 'Maintenance Tasks Running' AS FindingGroup, - 'DBCC CHECK* Running' AS Finding, - 'https://www.brentozar.com/askbrent/dbcc/' AS URL, - 'Corruption check of ' + DB_NAME(db.resource_database_id) + ' database (' + (SELECT CAST(CAST(SUM(size * 8.0 / 1024 / 1024) AS BIGINT) AS NVARCHAR) FROM #MasterFiles WHERE database_id = db.resource_database_id) + 'GB) 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, - s.login_name AS LoginName, - s.nt_user_name AS NTUserName, - s.[program_name] AS ProgramName, - s.[host_name] AS HostName, - db.[resource_database_id] AS DatabaseID, - DB_NAME(db.resource_database_id) AS DatabaseName, - 0 AS OpenTransactionCount, - r.query_hash - FROM sys.dm_exec_requests r - INNER JOIN sys.dm_exec_connections c ON r.session_id = c.session_id - INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id - INNER JOIN (SELECT DISTINCT l.request_session_id, l.resource_database_id - FROM sys.dm_tran_locks l - INNER JOIN sys.databases d ON l.resource_database_id = d.database_id - WHERE l.resource_type = N'DATABASE' - AND l.request_mode = N'S' - AND l.request_status = N'GRANT' - AND l.request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.session_id = db.request_session_id - OUTER APPLY sys.dm_exec_query_plan(r.plan_handle) pl - OUTER APPLY sys.dm_exec_sql_text(r.sql_handle) AS t - WHERE r.command LIKE 'DBCC%' - AND CAST(t.text AS NVARCHAR(4000)) NOT LIKE '%dm_db_index_physical_stats%' - AND CAST(t.text AS NVARCHAR(4000)) NOT LIKE '%ALTER INDEX%' - AND CAST(t.text AS NVARCHAR(4000)) NOT LIKE '%fileproperty%' - AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs); - END + /*Temporary objects used in the procedure*/ + DECLARE + @sysAssObjId AS table + ( + database_id int, + partition_id bigint, + schema_name sysname, + table_name sysname + ); - /* Maintenance Tasks Running - Restore Running - CheckID 3 */ - IF @Seconds > 0 - BEGIN - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 3',10,1) WITH NOWAIT; - END + CREATE TABLE + #x + ( + x xml NOT NULL + DEFAULT N'x' + ); - IF EXISTS (SELECT * FROM sys.dm_exec_requests WHERE command LIKE 'RESTORE%' AND total_elapsed_time > 5000) - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, QueryHash) - SELECT 3 AS CheckID, - 1 AS Priority, - 'Maintenance Tasks Running' AS FindingGroup, - 'Restore Running' AS Finding, - 'https://www.brentozar.com/askbrent/backups/' AS URL, - 'Restore of ' + DB_NAME(db.resource_database_id) + ' database (' + (SELECT CAST(CAST(SUM(size * 8.0 / 1024 / 1024) AS BIGINT) AS NVARCHAR) FROM #MasterFiles WHERE database_id = db.resource_database_id) + '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, - s.login_name AS LoginName, - s.nt_user_name AS NTUserName, - s.[program_name] AS ProgramName, - s.[host_name] AS HostName, - db.[resource_database_id] AS DatabaseID, - DB_NAME(db.resource_database_id) AS DatabaseName, - 0 AS OpenTransactionCount, - r.query_hash - FROM sys.dm_exec_requests r - INNER JOIN sys.dm_exec_connections c ON r.session_id = c.session_id - INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id - INNER JOIN ( - SELECT DISTINCT request_session_id, resource_database_id - FROM sys.dm_tran_locks - WHERE resource_type = N'DATABASE' - AND request_mode = N'S' - AND request_status = N'GRANT') AS db ON s.session_id = db.request_session_id - CROSS APPLY sys.dm_exec_query_plan(r.plan_handle) pl - WHERE r.command LIKE 'RESTORE%' - AND s.program_name <> 'SQL Server Log Shipping' - AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs); - END + CREATE TABLE + #deadlock_data + ( + deadlock_xml xml NOT NULL + DEFAULT N'x' + ); - /* SQL Server Internal Maintenance - Database File Growing - CheckID 4 */ - IF @Seconds > 0 - BEGIN - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 4',10,1) WITH NOWAIT; - END + CREATE TABLE + #t + ( + id int NOT NULL + ); - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount) - SELECT 4 AS CheckID, - 1 AS Priority, - 'SQL Server Internal Maintenance' AS FindingGroup, - 'Database File Growing' AS Finding, - 'https://www.brentozar.com/go/instant' AS URL, - 'SQL Server is waiting for Windows to provide storage space for a database restore, a data file growth, or a log file growth. This task has been running since ' + CAST(r.start_time AS NVARCHAR(100)) + '.' + @LineFeed + 'Check the query plan (expert mode) to identify the database involved.' AS Details, - 'Unfortunately, you can''t stop this, but you can prevent it next time. Check out https://www.brentozar.com/go/instant for details.' AS HowToStopIt, - pl.query_plan AS QueryPlan, - r.start_time AS StartTime, - s.login_name AS LoginName, - s.nt_user_name AS NTUserName, - s.[program_name] AS ProgramName, - s.[host_name] AS HostName, - NULL AS DatabaseID, - NULL AS DatabaseName, - 0 AS OpenTransactionCount - FROM sys.dm_os_waiting_tasks t - INNER JOIN sys.dm_exec_connections c ON t.session_id = c.session_id - INNER JOIN sys.dm_exec_requests r ON t.session_id = r.session_id - INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id - CROSS APPLY sys.dm_exec_query_plan(r.plan_handle) pl - WHERE t.wait_type = 'PREEMPTIVE_OS_WRITEFILEGATHER' - AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs); - END + CREATE TABLE + #deadlock_findings + ( + id int IDENTITY PRIMARY KEY, + check_id int NOT NULL, + database_name nvarchar(256), + object_name nvarchar(1000), + finding_group nvarchar(100), + finding nvarchar(4000), + sort_order bigint + ); - /* Query Problems - Long-Running Query Blocking Others - CheckID 5 */ - IF SERVERPROPERTY('EngineEdition') <> 5 /*SERVERPROPERTY('Edition') <> 'SQL Azure'*/ AND @Seconds > 0 AND EXISTS(SELECT * FROM sys.dm_os_waiting_tasks WHERE wait_type LIKE 'LCK%' AND wait_duration_ms > 30000) - BEGIN - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 5',10,1) WITH NOWAIT; - END + /*Set these to some sane defaults if NULLs are passed in*/ + /*Normally I'd hate this, but we RECOMPILE everything*/ - SET @StringToExecute = N'INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, QueryText, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, QueryHash) - SELECT 5 AS CheckID, - 1 AS Priority, - ''Query Problems'' AS FindingGroup, - ''Long-Running Query Blocking Others'' AS Finding, - ''https://www.brentozar.com/go/blocking'' AS URL, - ''Query in '' + COALESCE(DB_NAME(COALESCE((SELECT TOP 1 dbid FROM sys.dm_exec_sql_text(r.sql_handle)), - (SELECT TOP 1 t.dbid FROM master..sysprocesses spBlocker CROSS APPLY sys.dm_exec_sql_text(spBlocker.sql_handle) t WHERE spBlocker.spid = tBlocked.blocking_session_id))), ''(Unknown)'') + '' has a last request start time of '' + CAST(s.last_request_start_time AS NVARCHAR(100)) + ''. Query follows: ' - + @LineFeed + @LineFeed + - '''+ CAST(COALESCE((SELECT TOP 1 [text] FROM sys.dm_exec_sql_text(r.sql_handle)), - (SELECT TOP 1 [text] FROM master..sysprocesses spBlocker CROSS APPLY sys.dm_exec_sql_text(spBlocker.sql_handle) WHERE spBlocker.spid = tBlocked.blocking_session_id), '''') AS NVARCHAR(2000)) AS Details, - ''KILL '' + CAST(tBlocked.blocking_session_id AS NVARCHAR(100)) + '';'' AS HowToStopIt, - (SELECT TOP 1 query_plan FROM sys.dm_exec_query_plan(r.plan_handle)) AS QueryPlan, - COALESCE((SELECT TOP 1 [text] FROM sys.dm_exec_sql_text(r.sql_handle)), - (SELECT TOP 1 [text] FROM master..sysprocesses spBlocker CROSS APPLY sys.dm_exec_sql_text(spBlocker.sql_handle) WHERE spBlocker.spid = tBlocked.blocking_session_id)) AS QueryText, - r.start_time AS StartTime, - s.login_name AS LoginName, - s.nt_user_name AS NTUserName, - s.[program_name] AS ProgramName, - s.[host_name] AS HostName, - r.[database_id] AS DatabaseID, - DB_NAME(r.database_id) AS DatabaseName, - 0 AS OpenTransactionCount, - r.query_hash - FROM sys.dm_os_waiting_tasks tBlocked - INNER JOIN sys.dm_exec_sessions s ON tBlocked.blocking_session_id = s.session_id - LEFT OUTER JOIN sys.dm_exec_requests r ON s.session_id = r.session_id - INNER JOIN sys.dm_exec_connections c ON s.session_id = c.session_id - WHERE tBlocked.wait_type LIKE ''LCK%'' AND tBlocked.wait_duration_ms > 30000 - /* And the blocking session ID is not blocked by anyone else: */ - AND NOT EXISTS(SELECT * FROM sys.dm_os_waiting_tasks tBlocking WHERE s.session_id = tBlocking.session_id AND tBlocking.session_id <> tBlocking.blocking_session_id AND tBlocking.blocking_session_id IS NOT NULL) - AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs);'; - EXECUTE sp_executesql @StringToExecute; - END; - - /* Query Problems - Plan Cache Erased Recently - CheckID 7 */ - IF DATEADD(mi, -15, SYSDATETIME()) < (SELECT TOP 1 creation_time FROM sys.dm_exec_query_stats ORDER BY creation_time) - BEGIN - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 7',10,1) WITH NOWAIT; - END + SELECT + @StartDate = + CASE + WHEN @StartDate IS NULL + THEN + DATEADD + ( + MINUTE, + DATEDIFF + ( + MINUTE, + SYSDATETIME(), + GETUTCDATE() + ), + DATEADD + ( + DAY, + -7, + SYSDATETIME() + ) + ) + ELSE + DATEADD + ( + MINUTE, + DATEDIFF + ( + MINUTE, + SYSDATETIME(), + GETUTCDATE() + ), + @StartDate + ) + END, + @EndDate = + CASE + WHEN @EndDate IS NULL + THEN + DATEADD + ( + MINUTE, + DATEDIFF + ( + MINUTE, + SYSDATETIME(), + GETUTCDATE() + ), + SYSDATETIME() + ) + ELSE + DATEADD + ( + MINUTE, + DATEDIFF + ( + MINUTE, + SYSDATETIME(), + GETUTCDATE() + ), + @EndDate + ) + END; - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) - SELECT TOP 1 7 AS CheckID, - 50 AS Priority, - 'Query Problems' AS FindingGroup, - 'Plan Cache Erased Recently' AS Finding, - 'https://www.brentozar.com/askbrent/plan-cache-erased-recently/' AS URL, - 'The oldest query in the plan cache was created at ' + CAST(creation_time AS NVARCHAR(50)) + '. ' + @LineFeed + @LineFeed - + 'This indicates that someone ran DBCC FREEPROCCACHE at that time,' + @LineFeed - + 'Giving SQL Server temporary amnesia. Now, as queries come in,' + @LineFeed - + 'SQL Server has to use a lot of CPU power in order to build execution' + @LineFeed - + 'plans and put them in cache again. This causes high CPU loads.' AS Details, - 'Find who did that, and stop them from doing it again.' AS HowToStopIt - FROM sys.dm_exec_query_stats - ORDER BY creation_time; + SELECT + @StartDateUTC = @StartDate, + @EndDateUTC = @EndDate; + + IF + ( + @MI = 1 + AND @EventSessionName = N'system_health' + AND @TargetSessionType IS NULL + ) + BEGIN + SET + @TargetSessionType = N'ring_buffer'; END; + IF @Azure = 0 + BEGIN + IF NOT EXISTS + ( + SELECT + 1/0 + FROM sys.server_event_sessions AS ses + JOIN sys.dm_xe_sessions AS dxs + ON dxs.name = ses.name + WHERE ses.name = @EventSessionName + AND dxs.create_time IS NOT NULL + ) + BEGIN + RAISERROR('A session with the name %s does not exist or is not currently active.', 11, 1, @EventSessionName) WITH NOWAIT; + RETURN; + END; + END; + + IF @Azure = 1 + BEGIN + IF NOT EXISTS + ( + SELECT + 1/0 + FROM sys.database_event_sessions AS ses + JOIN sys.dm_xe_database_sessions AS dxs + ON dxs.name = ses.name + WHERE ses.name = @EventSessionName + AND dxs.create_time IS NOT NULL + ) + BEGIN + RAISERROR('A session with the name %s does not exist or is not currently active.', 11, 1, @EventSessionName) WITH NOWAIT; + RETURN; + END; + END; - /* Query Problems - Sleeping Query with Open Transactions - CheckID 8 */ - IF @Seconds > 0 - BEGIN - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 8',10,1) WITH NOWAIT; - END + IF @OutputDatabaseName IS NOT NULL + BEGIN /*IF databaseName is set, do some sanity checks and put [] around def.*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('@OutputDatabaseName set to %s, checking validity at %s', 0, 1, @OutputDatabaseName, @d) WITH NOWAIT; - IF EXISTS (SELECT * FROM sys.dm_exec_requests WHERE total_elapsed_time > 5000 AND request_id > 0) - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, OpenTransactionCount) - SELECT 8 AS CheckID, - 50 AS Priority, - 'Query Problems' AS FindingGroup, - 'Sleeping Query with Open Transactions' AS Finding, - 'https://www.brentozar.com/askbrent/sleeping-query-with-open-transactions/' AS URL, - 'Database: ' + DB_NAME(db.resource_database_id) + @LineFeed + 'Host: ' + s.hostname + @LineFeed + 'Program: ' + s.[program_name] + @LineFeed + 'Asleep with open transactions and locks since ' + CAST(s.last_batch AS NVARCHAR(100)) + '. ' AS Details, - 'KILL ' + CAST(s.spid AS NVARCHAR(100)) + ';' AS HowToStopIt, - s.last_batch AS StartTime, - s.loginame AS LoginName, - s.nt_username AS NTUserName, - s.[program_name] AS ProgramName, - s.hostname AS HostName, - db.[resource_database_id] AS DatabaseID, - DB_NAME(db.resource_database_id) AS DatabaseName, - (SELECT TOP 1 [text] FROM sys.dm_exec_sql_text(c.most_recent_sql_handle)) AS QueryText, - s.open_tran AS OpenTransactionCount - FROM sys.sysprocesses s - INNER JOIN sys.dm_exec_connections c ON s.spid = c.session_id - INNER JOIN ( - SELECT DISTINCT request_session_id, resource_database_id - FROM sys.dm_tran_locks - WHERE resource_type = N'DATABASE' - AND request_mode = N'S' - AND request_status = N'GRANT' - AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.spid = db.request_session_id - WHERE s.status = 'sleeping' - AND s.open_tran > 0 - AND s.last_batch < DATEADD(ss, -10, SYSDATETIME()) - AND EXISTS(SELECT * FROM sys.dm_tran_locks WHERE request_session_id = s.spid - AND NOT (resource_type = N'DATABASE' AND request_mode = N'S' AND request_status = N'GRANT' AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE')); - END + IF NOT EXISTS + ( + SELECT + 1/0 + FROM sys.databases AS d + WHERE d.name = @OutputDatabaseName + ) /*If database is invalid raiserror and set bitcheck*/ + BEGIN + RAISERROR('Database Name (%s) for output of table is invalid please, Output to Table will not be performed', 0, 1, @OutputDatabaseName) WITH NOWAIT; + SET @OutputDatabaseCheck = -1; /* -1 invalid/false, 0 = good/true */ + END; + ELSE + BEGIN + SET @OutputDatabaseCheck = 0; - /*Query Problems - Clients using implicit transactions - CheckID 37 */ - IF @Seconds > 0 - AND ( @@VERSION NOT LIKE 'Microsoft SQL Server 2005%' - AND @@VERSION NOT LIKE 'Microsoft SQL Server 2008%' - AND @@VERSION NOT LIKE 'Microsoft SQL Server 2008 R2%' ) - BEGIN - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 37',10,1) WITH NOWAIT; - END + SELECT + @StringToExecute = + N'SELECT @r = o.name FROM ' + + @OutputDatabaseName + + N'.sys.objects AS o WHERE o.type_desc = N''USER_TABLE'' AND o.name = ' + + QUOTENAME + ( + @OutputTableName, + N'''' + ) + + N' AND o.schema_id = SCHEMA_ID(' + + QUOTENAME + ( + @OutputSchemaName, + N'''' + ) + + N');', + @StringToExecuteParams = + N'@r sysname OUTPUT'; - SET @StringToExecute = N'INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, OpenTransactionCount) - SELECT 37 AS CheckId, - 50 AS Priority, - ''Query Problems'' AS FindingsGroup, - ''Implicit Transactions'', - ''https://www.brentozar.com/go/ImplicitTransactions/'' AS URL, - ''Database: '' + DB_NAME(s.database_id) + '' '' + CHAR(13) + CHAR(10) + - ''Host: '' + s.[host_name] + '' '' + CHAR(13) + CHAR(10) + - ''Program: '' + s.[program_name] + '' '' + CHAR(13) + CHAR(10) + - CONVERT(NVARCHAR(10), s.open_transaction_count) + - '' open transactions since: '' + - CONVERT(NVARCHAR(30), tat.transaction_begin_time) + ''. '' - AS Details, - ''Run sp_BlitzWho and check the is_implicit_transaction column to spot the culprits. -If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, - tat.transaction_begin_time, - s.login_name, - s.nt_user_name, - s.program_name, - s.host_name, - s.database_id, - DB_NAME(s.database_id) AS DatabaseName, - NULL AS Querytext, - s.open_transaction_count AS OpenTransactionCount - FROM sys.dm_tran_active_transactions AS tat - LEFT JOIN sys.dm_tran_session_transactions AS tst - ON tst.transaction_id = tat.transaction_id - LEFT JOIN sys.dm_exec_sessions AS s - ON s.session_id = tst.session_id - WHERE tat.name = ''implicit_transaction''; - ' - EXECUTE sp_executesql @StringToExecute; - END; + IF @Debug = 1 BEGIN PRINT @StringToExecute; END; + EXEC sys.sp_executesql + @StringToExecute, + @StringToExecuteParams, + @r OUTPUT; - /* Query Problems - Query Rolling Back - CheckID 9 */ - IF @Seconds > 0 - BEGIN - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 9',10,1) WITH NOWAIT; - END + IF @Debug = 1 + BEGIN + RAISERROR('@r is set to: %s for schema name %s and table name %s', 0, 1, @r, @OutputSchemaName, @OutputTableName) WITH NOWAIT; + END; - IF EXISTS (SELECT * FROM sys.dm_exec_requests WHERE total_elapsed_time > 5000 AND request_id > 0) - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, QueryHash) - SELECT 9 AS CheckID, - 1 AS Priority, - 'Query Problems' AS FindingGroup, - 'Query Rolling Back' AS Finding, - 'https://www.brentozar.com/askbrent/rollback/' AS URL, - 'Rollback started at ' + CAST(r.start_time AS NVARCHAR(100)) + ', is ' + CAST(r.percent_complete AS NVARCHAR(100)) + '% complete.' AS Details, - 'Unfortunately, you can''t stop this. Whatever you do, don''t restart the server in an attempt to fix it - SQL Server will keep rolling back.' AS HowToStopIt, - r.start_time AS StartTime, - s.login_name AS LoginName, - s.nt_user_name AS NTUserName, - s.[program_name] AS ProgramName, - s.[host_name] AS HostName, - db.[resource_database_id] AS DatabaseID, - DB_NAME(db.resource_database_id) AS DatabaseName, - (SELECT TOP 1 [text] FROM sys.dm_exec_sql_text(c.most_recent_sql_handle)) AS QueryText, - r.query_hash - FROM sys.dm_exec_sessions s - INNER JOIN sys.dm_exec_connections c ON s.session_id = c.session_id - INNER JOIN sys.dm_exec_requests r ON s.session_id = r.session_id - LEFT OUTER JOIN ( - SELECT DISTINCT request_session_id, resource_database_id - FROM sys.dm_tran_locks - WHERE resource_type = N'DATABASE' - AND request_mode = N'S' - AND request_status = N'GRANT' - AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.session_id = db.request_session_id - WHERE r.status = 'rollback'; - END + /*protection spells*/ + SELECT + @ObjectFullName = + QUOTENAME(@OutputDatabaseName) + + N'.' + + QUOTENAME(@OutputSchemaName) + + N'.' + + QUOTENAME(@OutputTableName), + @OutputDatabaseName = + QUOTENAME(@OutputDatabaseName), + @OutputTableName = + QUOTENAME(@OutputTableName), + @OutputSchemaName = + QUOTENAME(@OutputSchemaName); - IF @Seconds > 0 - BEGIN - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) - SELECT - 47 AS CheckId, - 50 AS Priority, - 'Query Problems' AS FindingsGroup, - 'High Percentage Of Runnable Queries' AS Finding, - 'https://erikdarlingdata.com/go/RunnableQueue/' AS URL, - 'On the ' - + CASE WHEN y.pass = 1 - THEN '1st' - ELSE '2nd' - END - + ' pass, ' - + RTRIM(y.runnable_pct) - + '% of your queries were waiting to get on a CPU to run. ' - + ' This can indicate CPU pressure.' - FROM - ( - SELECT - 1 AS pass, - x.total, - x.runnable, - CONVERT(decimal(5,2), - ( - x.runnable / - (1. * NULLIF(x.total, 0)) - ) - ) * 100. AS runnable_pct - FROM - ( - SELECT - COUNT_BIG(*) AS total, - SUM(CASE WHEN status = 'runnable' - THEN 1 - ELSE 0 - END) AS runnable - FROM sys.dm_exec_requests - WHERE session_id > 50 - ) AS x - ) AS y - WHERE y.runnable_pct > 20.; - END + IF (@r IS NOT NULL) /*if it is not null, there is a table, so check for newly added columns*/ + BEGIN + /* If the table doesn't have the new spid column, add it. See Github #3101. */ + SET @StringToExecute = + N'IF NOT EXISTS (SELECT 1/0 FROM ' + + @OutputDatabaseName + + N'.sys.all_columns AS o WHERE o.object_id = (OBJECT_ID(''' + + @ObjectFullName + + N''')) AND o.name = N''spid'') + /*Add spid column*/ + ALTER TABLE ' + + @ObjectFullName + + N' ADD spid smallint NULL;'; - /* Server Performance - Too Much Free Memory - CheckID 34 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 34',10,1) WITH NOWAIT; - END + IF @Debug = 1 BEGIN PRINT @StringToExecute; END; + EXEC sys.sp_executesql + @StringToExecute; - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) - SELECT 34 AS CheckID, - 50 AS Priority, - 'Server Performance' AS FindingGroup, - 'Too Much Free Memory' AS Finding, - 'https://www.brentozar.com/go/freememory' AS URL, - CAST((CAST(cFree.cntr_value AS BIGINT) / 1024 / 1024 ) AS NVARCHAR(100)) + N'GB of free memory inside SQL Server''s buffer pool,' + @LineFeed + ' which is ' + CAST((CAST(cTotal.cntr_value AS BIGINT) / 1024 / 1024) AS NVARCHAR(100)) + N'GB. You would think lots of free memory would be good, but check out the URL for more information.' AS Details, - 'Run sp_BlitzCache @SortOrder = ''memory grant'' to find queries with huge memory grants and tune them.' AS HowToStopIt - FROM sys.dm_os_performance_counters cFree - INNER JOIN sys.dm_os_performance_counters cTotal ON cTotal.object_name LIKE N'%Memory Manager%' - AND cTotal.counter_name = N'Total Server Memory (KB) ' - WHERE cFree.object_name LIKE N'%Memory Manager%' - AND cFree.counter_name = N'Free Memory (KB) ' - AND CAST(cFree.cntr_value AS BIGINT) > 20480000000 - AND CAST(cTotal.cntr_value AS BIGINT) * .3 <= CAST(cFree.cntr_value AS BIGINT) - AND CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) NOT LIKE '%Standard%'; + /* If the table doesn't have the new wait_resource column, add it. See Github #3101. */ + SET @StringToExecute = + N'IF NOT EXISTS (SELECT 1/0 FROM ' + + @OutputDatabaseName + + N'.sys.all_columns AS o WHERE o.object_id = (OBJECT_ID(''' + + @ObjectFullName + + N''')) AND o.name = N''wait_resource'') + /*Add wait_resource column*/ + ALTER TABLE ' + + @ObjectFullName + + N' ADD wait_resource nvarchar(MAX) NULL;'; - /* Server Performance - Target Memory Lower Than Max - CheckID 35 */ - IF SERVERPROPERTY('EngineEdition') <> 5 /*SERVERPROPERTY('Edition') <> 'SQL Azure'*/ - BEGIN - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 35',10,1) WITH NOWAIT; - END + IF @Debug = 1 BEGIN PRINT @StringToExecute; END; + EXEC sys.sp_executesql + @StringToExecute; - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) - SELECT 35 AS CheckID, - 10 AS Priority, - 'Server Performance' AS FindingGroup, - 'Target Memory Lower Than Max' AS Finding, - 'https://www.brentozar.com/go/target' AS URL, - N'Max server memory is ' + CAST(cMax.value_in_use AS NVARCHAR(50)) + N' MB but target server memory is only ' + CAST((CAST(cTarget.cntr_value AS BIGINT) / 1024) AS NVARCHAR(50)) + N' MB,' + @LineFeed - + N'indicating that SQL Server may be under external memory pressure or max server memory may be set too high.' AS Details, - 'Investigate what OS processes are using memory, and double-check the max server memory setting.' AS HowToStopIt - FROM sys.configurations cMax - INNER JOIN sys.dm_os_performance_counters cTarget ON cTarget.object_name LIKE N'%Memory Manager%' - AND cTarget.counter_name = N'Target Server Memory (KB) ' - WHERE cMax.name = 'max server memory (MB)' - AND CAST(cMax.value_in_use AS BIGINT) >= 1.5 * (CAST(cTarget.cntr_value AS BIGINT) / 1024) - AND CAST(cMax.value_in_use AS BIGINT) < 2147483647 /* Not set to default of unlimited */ - AND CAST(cTarget.cntr_value AS BIGINT) < .8 * (SELECT available_physical_memory_kb FROM sys.dm_os_sys_memory); /* Target memory less than 80% of physical memory (in case they set max too high) */ - END + /* If the table doesn't have the new client option column, add it. See Github #3101. */ + SET @StringToExecute = + N'IF NOT EXISTS (SELECT 1/0 FROM ' + + @OutputDatabaseName + + N'.sys.all_columns AS o WHERE o.object_id = (OBJECT_ID(''' + + @ObjectFullName + + N''')) AND o.name = N''client_option_1'') + /*Add wait_resource column*/ + ALTER TABLE ' + + @ObjectFullName + + N' ADD client_option_1 varchar(500) NULL;'; - /* Server Info - Database Size, Total GB - CheckID 21 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 21',10,1) WITH NOWAIT; - END + IF @Debug = 1 BEGIN PRINT @StringToExecute; END; + EXEC sys.sp_executesql + @StringToExecute; - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) - SELECT 21 AS CheckID, - 251 AS Priority, - 'Server Info' AS FindingGroup, - 'Database Size, Total GB' AS Finding, - CAST(SUM (CAST(size AS BIGINT)*8./1024./1024.) AS VARCHAR(100)) AS Details, - SUM (CAST(size AS BIGINT))*8./1024./1024. AS DetailsInt, - 'https://www.brentozar.com/askbrent/' AS URL - FROM #MasterFiles - WHERE database_id > 4; + /* If the table doesn't have the new client option column, add it. See Github #3101. */ + SET @StringToExecute = + N'IF NOT EXISTS (SELECT 1/0 FROM ' + + @OutputDatabaseName + + N'.sys.all_columns AS o WHERE o.object_id = (OBJECT_ID(''' + + @ObjectFullName + + N''')) AND o.name = N''client_option_2'') + /*Add wait_resource column*/ + ALTER TABLE ' + + @ObjectFullName + + N' ADD client_option_2 varchar(500) NULL;'; - /* Server Info - Database Count - CheckID 22 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 22',10,1) WITH NOWAIT; - END + IF @Debug = 1 BEGIN PRINT @StringToExecute; END; + EXEC sys.sp_executesql + @StringToExecute; - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) - SELECT 22 AS CheckID, - 251 AS Priority, - 'Server Info' AS FindingGroup, - 'Database Count' AS Finding, - CAST(SUM(1) AS VARCHAR(100)) AS Details, - SUM (1) AS DetailsInt, - 'https://www.brentozar.com/askbrent/' AS URL - FROM sys.databases - WHERE database_id > 4; + /* If the table doesn't have the new lock mode column, add it. See Github #3101. */ + SET @StringToExecute = + N'IF NOT EXISTS (SELECT 1/0 FROM ' + + @OutputDatabaseName + + N'.sys.all_columns AS o WHERE o.object_id = (OBJECT_ID(''' + + @ObjectFullName + + N''')) AND o.name = N''lock_mode'') + /*Add wait_resource column*/ + ALTER TABLE ' + + @ObjectFullName + + N' ADD lock_mode nvarchar(256) NULL;'; - /* Server Info - Memory Grants pending - CheckID 39 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 39',10,1) WITH NOWAIT; - END + IF @Debug = 1 BEGIN PRINT @StringToExecute; END; + EXEC sys.sp_executesql + @StringToExecute; - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) - SELECT 39 AS CheckID, - 50 AS Priority, - 'Server Performance' AS FindingGroup, - 'Memory Grants Pending' AS Finding, - CAST(PendingGrants.Details AS NVARCHAR(50)) AS Details, - PendingGrants.DetailsInt, - 'https://www.brentozar.com/blitz/memory-grants/' AS URL - FROM - ( - SELECT - COUNT(1) AS Details, - COUNT(1) AS DetailsInt - FROM sys.dm_exec_query_memory_grants AS Grants - WHERE queue_id IS NOT NULL - ) AS PendingGrants - WHERE PendingGrants.Details > 0; + /* If the table doesn't have the new status column, add it. See Github #3101. */ + SET @StringToExecute = + N'IF NOT EXISTS (SELECT 1/0 FROM ' + + @OutputDatabaseName + + N'.sys.all_columns AS o WHERE o.object_id = (OBJECT_ID(''' + + @ObjectFullName + + N''')) AND o.name = N''status'') + /*Add wait_resource column*/ + ALTER TABLE ' + + @ObjectFullName + + N' ADD status nvarchar(256) NULL;'; - /* Server Info - Memory Grant/Workspace info - CheckID 40 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 40',10,1) WITH NOWAIT; - END + IF @Debug = 1 BEGIN PRINT @StringToExecute; END; + EXEC sys.sp_executesql + @StringToExecute; + END; + ELSE /* end if @r is not null. if it is null there is no table, create it from above execution */ + BEGIN + SELECT + @StringToExecute = + N'USE ' + + @OutputDatabaseName + + N'; + CREATE TABLE ' + + @OutputSchemaName + + N'.' + + @OutputTableName + + N' ( + ServerName nvarchar(256), + deadlock_type nvarchar(256), + event_date datetime, + database_name nvarchar(256), + spid smallint, + deadlock_group nvarchar(256), + query xml, + object_names xml, + isolation_level nvarchar(256), + owner_mode nvarchar(256), + waiter_mode nvarchar(256), + lock_mode nvarchar(256), + transaction_count bigint, + client_option_1 varchar(500), + client_option_2 varchar(500), + login_name nvarchar(256), + host_name nvarchar(256), + client_app nvarchar(1024), + wait_time bigint, + wait_resource nvarchar(max), + priority smallint, + log_used bigint, + last_tran_started datetime, + last_batch_started datetime, + last_batch_completed datetime, + transaction_name nvarchar(256), + status nvarchar(256), + owner_waiter_type nvarchar(256), + owner_activity nvarchar(256), + owner_waiter_activity nvarchar(256), + owner_merging nvarchar(256), + owner_spilling nvarchar(256), + owner_waiting_to_close nvarchar(256), + waiter_waiter_type nvarchar(256), + waiter_owner_activity nvarchar(256), + waiter_waiter_activity nvarchar(256), + waiter_merging nvarchar(256), + waiter_spilling nvarchar(256), + waiter_waiting_to_close nvarchar(256), + deadlock_graph xml + )'; - DECLARE @MaxWorkspace BIGINT - SET @MaxWorkspace = (SELECT CAST(cntr_value AS BIGINT)/1024 FROM #PerfmonStats WHERE counter_name = N'Maximum Workspace Memory (KB)') - - IF (@MaxWorkspace IS NULL - OR @MaxWorkspace = 0) - BEGIN - SET @MaxWorkspace = 1 - END + IF @Debug = 1 BEGIN PRINT @StringToExecute; END; + EXEC sys.sp_executesql + @StringToExecute; - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) - SELECT 40 AS CheckID, - 251 AS Priority, - 'Server Info' AS FindingGroup, - 'Memory Grant/Workspace info' AS Finding, - + 'Grants Outstanding: ' + CAST((SELECT COUNT(*) FROM sys.dm_exec_query_memory_grants WHERE queue_id IS NULL) AS NVARCHAR(50)) + @LineFeed - + 'Total Granted(MB): ' + CAST(ISNULL(SUM(Grants.granted_memory_kb) / 1024, 0) AS NVARCHAR(50)) + @LineFeed - + 'Total WorkSpace(MB): ' + CAST(ISNULL(@MaxWorkspace, 0) AS NVARCHAR(50)) + @LineFeed - + 'Granted workspace: ' + CAST(ISNULL((CAST(SUM(Grants.granted_memory_kb) / 1024 AS MONEY) - / CAST(@MaxWorkspace AS MONEY)) * 100, 0) AS NVARCHAR(50)) + '%' + @LineFeed - + 'Oldest Grant in seconds: ' + CAST(ISNULL(DATEDIFF(SECOND, MIN(Grants.request_time), GETDATE()), 0) AS NVARCHAR(50)) AS Details, - (SELECT COUNT(*) FROM sys.dm_exec_query_memory_grants WHERE queue_id IS NULL) AS DetailsInt, - 'https://www.brentozar.com/askbrent/' AS URL - FROM sys.dm_exec_query_memory_grants AS Grants; + /*table created.*/ + SELECT + @StringToExecute = + N'SELECT @r = o.name FROM ' + + @OutputDatabaseName + + N'.sys.objects AS o + WHERE o.type_desc = N''USER_TABLE'' + AND o.name = N''BlitzLockFindings''', + @StringToExecuteParams = + N'@r sysname OUTPUT'; - /* Query Problems - Queries with high memory grants - CheckID 46 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 46',10,1) WITH NOWAIT; - END + IF @Debug = 1 BEGIN PRINT @StringToExecute; END; + EXEC sys.sp_executesql + @StringToExecute, + @StringToExecuteParams, + @r OUTPUT; - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, URL, QueryText, QueryPlan) - SELECT 46 AS CheckID, - 100 AS Priority, - 'Query Problems' AS FindingGroup, - 'Query with a memory grant exceeding ' - +CAST(@MemoryGrantThresholdPct AS NVARCHAR(15)) - +'%' AS Finding, - 'Granted size: '+ CAST(CAST(Grants.granted_memory_kb / 1024 AS INT) AS NVARCHAR(50)) - +N'MB ' - + @LineFeed - +N'Granted pct of max workspace: ' - + CAST(ISNULL((CAST(Grants.granted_memory_kb / 1024 AS MONEY) - / CAST(@MaxWorkspace AS MONEY)) * 100, 0) AS NVARCHAR(50)) + '%' - + @LineFeed - +N'SQLHandle: ' - +CONVERT(NVARCHAR(128),Grants.[sql_handle],1), - 'https://www.brentozar.com/memory-grants-sql-servers-public-toilet/' AS URL, - SQLText.[text], - QueryPlan.query_plan - FROM sys.dm_exec_query_memory_grants AS Grants - OUTER APPLY sys.dm_exec_sql_text(Grants.[sql_handle]) AS SQLText - OUTER APPLY sys.dm_exec_query_plan(Grants.[plan_handle]) AS QueryPlan - WHERE Grants.granted_memory_kb > ((@MemoryGrantThresholdPct/100.00)*(@MaxWorkspace*1024)); + IF (@r IS NULL) /*if table does not exist*/ + BEGIN + SELECT + @OutputTableFindings = + QUOTENAME(N'BlitzLockFindings'), + @StringToExecute = + N'USE ' + + @OutputDatabaseName + + N'; + CREATE TABLE ' + + @OutputSchemaName + + N'.' + + @OutputTableFindings + + N' ( + ServerName nvarchar(256), + check_id INT, + database_name nvarchar(256), + object_name nvarchar(1000), + finding_group nvarchar(100), + finding nvarchar(4000) + );'; - /* Query Problems - Memory Leak in USERSTORE_TOKENPERM Cache - CheckID 45 */ - IF EXISTS (SELECT * FROM sys.all_columns WHERE object_id = OBJECT_ID('sys.dm_os_memory_clerks') AND name = 'pages_kb') - BEGIN - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 45',10,1) WITH NOWAIT; - END + IF @Debug = 1 BEGIN PRINT @StringToExecute; END; + EXEC sys.sp_executesql + @StringToExecute; + END; + END; - /* SQL 2012+ version */ - SET @StringToExecute = N' - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, URL) - SELECT 45 AS CheckID, - 50 AS Priority, - ''Query Problems'' AS FindingsGroup, - ''Memory Leak in USERSTORE_TOKENPERM Cache'' AS Finding, - N''UserStore_TokenPerm clerk is using '' + CAST(CAST(SUM(CASE WHEN type = ''USERSTORE_TOKENPERM'' AND name = ''TokenAndPermUserStore'' THEN pages_kb * 1.0 ELSE 0.0 END) / 1024.0 / 1024.0 AS INT) AS NVARCHAR(100)) - + N''GB RAM, total buffer pool is '' + CAST(CAST(SUM(pages_kb) / 1024.0 / 1024.0 AS INT) AS NVARCHAR(100)) + N''GB.'' - AS details, - ''https://www.BrentOzar.com/go/userstore'' AS URL - FROM sys.dm_os_memory_clerks - HAVING SUM(CASE WHEN type = ''USERSTORE_TOKENPERM'' AND name = ''TokenAndPermUserStore'' THEN pages_kb * 1.0 ELSE 0.0 END) / SUM(pages_kb) >= 0.1 - AND SUM(pages_kb) / 1024.0 / 1024.0 >= 1; /* At least 1GB RAM overall */'; - EXEC sp_executesql @StringToExecute; - END - ELSE - BEGIN - /* Antiques Roadshow SQL 2008R2 - version */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 45 (Legacy version)',10,1) WITH NOWAIT; - END + /*create synonym for deadlockfindings.*/ + IF EXISTS + ( + SELECT + 1/0 + FROM sys.objects AS o + WHERE o.name = N'DeadlockFindings' + AND o.type_desc = N'SYNONYM' + ) + BEGIN + RAISERROR('Found synonym DeadlockFindings, dropping', 0, 1) WITH NOWAIT; + DROP SYNONYM DeadlockFindings; + END; - SET @StringToExecute = N' - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, URL) - SELECT 45 AS CheckID, - 50 AS Priority, - ''Performance'' AS FindingsGroup, - ''Memory Leak in USERSTORE_TOKENPERM Cache'' AS Finding, - N''UserStore_TokenPerm clerk is using '' + CAST(CAST(SUM(CASE WHEN type = ''USERSTORE_TOKENPERM'' AND name = ''TokenAndPermUserStore'' THEN single_pages_kb + multi_pages_kb * 1.0 ELSE 0.0 END) / 1024.0 / 1024.0 AS INT) AS NVARCHAR(100)) - + N''GB RAM, total buffer pool is '' + CAST(CAST(SUM(single_pages_kb + multi_pages_kb) / 1024.0 / 1024.0 AS INT) AS NVARCHAR(100)) + N''GB.'' - AS details, - ''https://www.BrentOzar.com/go/userstore'' AS URL - FROM sys.dm_os_memory_clerks - HAVING SUM(CASE WHEN type = ''USERSTORE_TOKENPERM'' AND name = ''TokenAndPermUserStore'' THEN single_pages_kb + multi_pages_kb * 1.0 ELSE 0.0 END) / SUM(single_pages_kb + multi_pages_kb) >= 0.1 - AND SUM(single_pages_kb + multi_pages_kb) / 1024.0 / 1024.0 >= 1; /* At least 1GB RAM overall */'; - EXEC sp_executesql @StringToExecute; - END + RAISERROR('Creating synonym DeadlockFindings', 0, 1) WITH NOWAIT; + SET @StringToExecute = + N'CREATE SYNONYM DeadlockFindings FOR ' + + @OutputDatabaseName + + N'.' + + @OutputSchemaName + + N'.' + + @OutputTableFindings; + IF @Debug = 1 BEGIN PRINT @StringToExecute; END; + EXEC sys.sp_executesql + @StringToExecute; + /*create synonym for deadlock table.*/ + IF EXISTS + ( + SELECT + 1/0 + FROM sys.objects AS o + WHERE o.name = N'DeadLockTbl' + AND o.type_desc = N'SYNONYM' + ) + BEGIN + RAISERROR('Found synonym DeadLockTbl, dropping', 0, 1) WITH NOWAIT; + DROP SYNONYM DeadLockTbl; + END; - IF @Seconds > 0 - BEGIN + RAISERROR('Creating synonym DeadLockTbl', 0, 1) WITH NOWAIT; + SET @StringToExecute = + N'CREATE SYNONYM DeadLockTbl FOR ' + + @OutputDatabaseName + + N'.' + + @OutputSchemaName + + N'.' + + @OutputTableName; - IF EXISTS ( SELECT 1/0 - FROM sys.all_objects AS ao - WHERE ao.name = 'dm_exec_query_profiles' ) - BEGIN + IF @Debug = 1 BEGIN PRINT @StringToExecute; END; + EXEC sys.sp_executesql + @StringToExecute; + END; + END; - IF EXISTS( SELECT 1/0 - FROM sys.dm_exec_requests AS r - JOIN sys.dm_exec_sessions AS s - ON r.session_id = s.session_id - WHERE s.host_name IS NOT NULL - AND r.total_elapsed_time > 5000 - AND r.request_id > 0 ) - BEGIN + /* WITH ROWCOUNT doesn't work on Amazon RDS - see: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2037 */ + IF @RDS = 0 + BEGIN; + BEGIN TRY; + RAISERROR('@RDS = 0, updating #t with high row and page counts', 0, 1) WITH NOWAIT; + UPDATE STATISTICS + #t + WITH + ROWCOUNT = 9223372036854775807, + PAGECOUNT = 9223372036854775807; + END TRY + BEGIN CATCH; + /* Misleading error returned, if run without permissions to update statistics the error returned is "Cannot find object". + Catching specific error, and returning message with better info. If any other error is returned, then throw as normal */ + IF (ERROR_NUMBER() = 1088) + BEGIN; + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Cannot run UPDATE STATISTICS on a #temp table without db_owner or sysadmin permissions', 0, 1) WITH NOWAIT; + END; + ELSE + BEGIN; + THROW; + END; + END CATCH; + END; - SET @StringToExecute = N' - DECLARE @bad_estimate TABLE - ( - session_id INT, - request_id INT, - estimate_inaccuracy BIT - ); - - INSERT @bad_estimate ( session_id, request_id, estimate_inaccuracy ) - SELECT x.session_id, - x.request_id, - x.estimate_inaccuracy - FROM ( - SELECT deqp.session_id, - deqp.request_id, - CASE WHEN (deqp.row_count/10000) > deqp.estimate_row_count - THEN 1 - ELSE 0 - END AS estimate_inaccuracy - FROM sys.dm_exec_query_profiles AS deqp - INNER JOIN sys.dm_exec_requests r ON deqp.session_id = r.session_id AND deqp.request_id = r.request_id - WHERE deqp.session_id <> @@SPID - AND r.total_elapsed_time > 5000 - ) AS x - WHERE x.estimate_inaccuracy = 1 - GROUP BY x.session_id, - x.request_id, - x.estimate_inaccuracy; - - DECLARE @parallelism_skew TABLE - ( - session_id INT, - request_id INT, - parallelism_skew BIT - ); - - INSERT @parallelism_skew ( session_id, request_id, parallelism_skew ) - SELECT y.session_id, - y.request_id, - y.parallelism_skew - FROM ( - SELECT x.session_id, - x.request_id, - x.node_id, - x.thread_id, - x.row_count, - x.sum_node_rows, - x.node_dop, - x.sum_node_rows / x.node_dop AS even_distribution, - x.row_count / (1. * ISNULL(NULLIF(x.sum_node_rows / x.node_dop, 0), 1)) AS skew_percent, - CASE - WHEN x.row_count > 10000 - AND x.row_count / (1. * ISNULL(NULLIF(x.sum_node_rows / x.node_dop, 0), 1)) > 2. - THEN 1 - WHEN x.row_count > 10000 - AND x.row_count / (1. * ISNULL(NULLIF(x.sum_node_rows / x.node_dop, 0), 1)) < 0.5 - THEN 1 - ELSE 0 - END AS parallelism_skew - FROM ( - SELECT deqp.session_id, - deqp.request_id, - deqp.node_id, - deqp.thread_id, - deqp.row_count, - SUM(deqp.row_count) - OVER ( PARTITION BY deqp.session_id, - deqp.request_id, - deqp.node_id - ORDER BY deqp.row_count - ROWS BETWEEN UNBOUNDED PRECEDING - AND UNBOUNDED FOLLOWING ) - AS sum_node_rows, - COUNT(*) - OVER ( PARTITION BY deqp.session_id, - deqp.request_id, - deqp.node_id - ORDER BY deqp.row_count - ROWS BETWEEN UNBOUNDED PRECEDING - AND UNBOUNDED FOLLOWING ) - AS node_dop - FROM sys.dm_exec_query_profiles AS deqp - WHERE deqp.thread_id > 0 - AND deqp.session_id <> @@SPID - AND EXISTS - ( - SELECT 1/0 - FROM sys.dm_exec_query_profiles AS deqp2 - WHERE deqp.session_id = deqp2.session_id - AND deqp.node_id = deqp2.node_id - AND deqp2.thread_id > 0 - GROUP BY deqp2.session_id, deqp2.node_id - HAVING COUNT(deqp2.node_id) > 1 - ) - ) AS x - ) AS y - WHERE y.parallelism_skew = 1 - GROUP BY y.session_id, - y.request_id, - y.parallelism_skew; - - /* Queries in dm_exec_query_profiles showing signs of poor cardinality estimates - CheckID 42 */ - IF (@Debug = 1) - BEGIN - RAISERROR(''Running CheckID 42'',10,1) WITH NOWAIT; - END + /*If @TargetSessionType, we need to figure out if it's ring buffer or event file*/ + /*Azure has differently named views, so we need to separate. Thanks, Azure.*/ - INSERT INTO #BlitzFirstResults - (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, OpenTransactionCount, QueryHash, QueryPlan) - SELECT 42 AS CheckID, - 100 AS Priority, - ''Query Performance'' AS FindingsGroup, - ''Queries with 10000x cardinality misestimations'' AS Findings, - ''https://www.brentozar.com/go/skewedup'' AS URL, - ''The query on SPID '' - + RTRIM(b.session_id) - + '' has been running for '' - + RTRIM(r.total_elapsed_time / 1000) - + '' seconds, with a large cardinality misestimate'' AS Details, - ''No quick fix here: time to dig into the actual execution plan. '' AS HowToStopIt, - r.start_time, - s.login_name, - s.nt_user_name, - s.program_name, - s.host_name, - r.database_id, - DB_NAME(r.database_id), - dest.text, - s.open_transaction_count, - r.query_hash, '; + IF + ( + @Azure = 0 + AND @TargetSessionType IS NULL + ) + BEGIN + RAISERROR('@TargetSessionType is NULL, assigning for non-Azure instance', 0, 1) WITH NOWAIT; - IF @dm_exec_query_statistics_xml = 1 - SET @StringToExecute = @StringToExecute + N' COALESCE(qs_live.query_plan, qp.query_plan) AS query_plan '; - ELSE - SET @StringToExecute = @StringToExecute + N' qp.query_plan '; + SELECT TOP (1) + @TargetSessionType = t.target_name + FROM sys.dm_xe_sessions AS s + JOIN sys.dm_xe_session_targets AS t + ON s.address = t.event_session_address + WHERE s.name = @EventSessionName + AND t.target_name IN (N'event_file', N'ring_buffer') + ORDER BY t.target_name + OPTION(RECOMPILE); - SET @StringToExecute = @StringToExecute + N' - FROM @bad_estimate AS b - JOIN sys.dm_exec_requests AS r - ON r.session_id = b.session_id - AND r.request_id = b.request_id - JOIN sys.dm_exec_sessions AS s - ON s.session_id = b.session_id - CROSS APPLY sys.dm_exec_sql_text(r.sql_handle) AS dest - CROSS APPLY sys.dm_exec_query_plan(r.plan_handle) AS qp '; + RAISERROR('@TargetSessionType assigned as %s for non-Azure', 0, 1, @TargetSessionType) WITH NOWAIT; + END; - IF EXISTS (SELECT * FROM sys.all_objects WHERE name = 'dm_exec_query_statistics_xml') - /* GitHub #3210 */ - SET @StringToExecute = N' - SET LOCK_TIMEOUT 1000 ' + @StringToExecute + N' OUTER APPLY sys.dm_exec_query_statistics_xml(s.session_id) qs_live '; - - SET @StringToExecute = @StringToExecute + N'; + IF + ( + @Azure = 1 + AND @TargetSessionType IS NULL + ) + BEGIN + RAISERROR('@TargetSessionType is NULL, assigning for Azure instance', 0, 1) WITH NOWAIT; - /* Queries in dm_exec_query_profiles showing signs of unbalanced parallelism - CheckID 43 */ - IF (@Debug = 1) - BEGIN - RAISERROR(''Running CheckID 43'',10,1) WITH NOWAIT; - END + SELECT TOP (1) + @TargetSessionType = t.target_name + FROM sys.dm_xe_database_sessions AS s + JOIN sys.dm_xe_database_session_targets AS t + ON s.address = t.event_session_address + WHERE s.name = @EventSessionName + AND t.target_name IN (N'event_file', N'ring_buffer') + ORDER BY t.target_name + OPTION(RECOMPILE); - INSERT INTO #BlitzFirstResults - (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, OpenTransactionCount, QueryHash, QueryPlan) - SELECT 43 AS CheckID, - 100 AS Priority, - ''Query Performance'' AS FindingsGroup, - ''Queries with 10000x skewed parallelism'' AS Findings, - ''https://www.brentozar.com/go/skewedup'' AS URL, - ''The query on SPID '' - + RTRIM(p.session_id) - + '' has been running for '' - + RTRIM(r.total_elapsed_time / 1000) - + '' seconds, with a parallel threads doing uneven work.'' AS Details, - ''No quick fix here: time to dig into the actual execution plan. '' AS HowToStopIt, - r.start_time, - s.login_name, - s.nt_user_name, - s.program_name, - s.host_name, - r.database_id, - DB_NAME(r.database_id), - dest.text, - s.open_transaction_count, - r.query_hash, '; + RAISERROR('@TargetSessionType assigned as %s for Azure', 0, 1, @TargetSessionType) WITH NOWAIT; + END; - IF @dm_exec_query_statistics_xml = 1 - SET @StringToExecute = @StringToExecute + N' COALESCE(qs_live.query_plan, qp.query_plan) AS query_plan '; - ELSE - SET @StringToExecute = @StringToExecute + N' qp.query_plan '; - SET @StringToExecute = @StringToExecute + N' - FROM @parallelism_skew AS p - JOIN sys.dm_exec_requests AS r - ON r.session_id = p.session_id - AND r.request_id = p.request_id - JOIN sys.dm_exec_sessions AS s - ON s.session_id = p.session_id - CROSS APPLY sys.dm_exec_sql_text(r.sql_handle) AS dest - CROSS APPLY sys.dm_exec_query_plan(r.plan_handle) AS qp '; + /*The system health stuff gets handled different from user extended events.*/ + /*These next sections deal with user events, dependent on target.*/ - IF EXISTS (SELECT * FROM sys.all_objects WHERE name = 'dm_exec_query_statistics_xml') - SET @StringToExecute = @StringToExecute + N' OUTER APPLY sys.dm_exec_query_statistics_xml(s.session_id) qs_live '; - - - SET @StringToExecute = @StringToExecute + N';'; + /*If ring buffers*/ + IF + ( + @TargetSessionType LIKE N'ring%' + AND @EventSessionName NOT LIKE N'system_health%' + ) + BEGIN + IF @Azure = 0 + BEGIN + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('@TargetSessionType is ring_buffer, inserting XML for non-Azure at %s', 0, 1, @d) WITH NOWAIT; + + INSERT + #x WITH(TABLOCKX) + ( + x + ) + SELECT + x = TRY_CAST(t.target_data AS xml) + FROM sys.dm_xe_session_targets AS t + JOIN sys.dm_xe_sessions AS s + ON s.address = t.event_session_address + WHERE s.name = @EventSessionName + AND t.target_name = N'ring_buffer' + OPTION(RECOMPILE); - EXECUTE sp_executesql @StringToExecute, N'@Debug BIT',@Debug = @Debug; - END - - END - END + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + END; - /* Server Performance - High CPU Utilization - CheckID 24 */ - IF @Seconds < 30 + IF @Azure = 1 BEGIN - /* If we're waiting less than 30 seconds, run this check now rather than wait til the end. - We get this data from the ring buffers, and it's only updated once per minute, so might - as well get it now - whereas if we're checking 30+ seconds, it might get updated by the - end of our sp_BlitzFirst session. */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 24',10,1) WITH NOWAIT; - END - - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) - SELECT 24, 50, 'Server Performance', 'High CPU Utilization', CAST(100 - SystemIdle AS NVARCHAR(20)) + N'%.', 100 - SystemIdle, 'https://www.brentozar.com/go/cpu' - FROM ( - SELECT record, - record.value('(./Record/SchedulerMonitorEvent/SystemHealth/SystemIdle)[1]', 'int') AS SystemIdle - FROM ( - SELECT TOP 1 CONVERT(XML, record) AS record - FROM sys.dm_os_ring_buffers - WHERE ring_buffer_type = N'RING_BUFFER_SCHEDULER_MONITOR' - AND record LIKE '%%' - ORDER BY timestamp DESC) AS rb - ) AS y - WHERE 100 - SystemIdle >= 50; + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('@TargetSessionType is ring_buffer, inserting XML for Azure at %s', 0, 1, @d) WITH NOWAIT; - /* CPU Utilization - CheckID 23 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 23',10,1) WITH NOWAIT; - END + INSERT + #x WITH(TABLOCKX) + ( + x + ) + SELECT + x = TRY_CAST(t.target_data AS xml) + FROM sys.dm_xe_database_session_targets AS t + JOIN sys.dm_xe_database_sessions AS s + ON s.address = t.event_session_address + WHERE s.name = @EventSessionName + AND t.target_name = N'ring_buffer' + OPTION(RECOMPILE); - IF SERVERPROPERTY('EngineEdition') <> 5 /*SERVERPROPERTY('Edition') <> 'SQL Azure'*/ - WITH y - AS - ( - SELECT CONVERT(VARCHAR(5), 100 - ca.c.value('.', 'INT')) AS system_idle, - CONVERT(VARCHAR(30), rb.event_date) AS event_date, - CONVERT(VARCHAR(8000), rb.record) AS record, - event_date as event_date_raw - FROM - ( SELECT CONVERT(XML, dorb.record) AS record, - DATEADD(ms, -( ts.ms_ticks - dorb.timestamp ), GETDATE()) AS event_date - FROM sys.dm_os_ring_buffers AS dorb - CROSS JOIN - ( SELECT dosi.ms_ticks FROM sys.dm_os_sys_info AS dosi ) AS ts - WHERE dorb.ring_buffer_type = N'RING_BUFFER_SCHEDULER_MONITOR' - AND record LIKE '%%' ) AS rb - CROSS APPLY rb.record.nodes('/Record/SchedulerMonitorEvent/SystemHealth/SystemIdle') AS ca(c) - ) - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL, HowToStopIt) - SELECT TOP 1 - 23, - 250, - 'Server Info', - 'CPU Utilization', - y.system_idle + N'%. Ring buffer details: ' + CAST(y.record AS NVARCHAR(4000)), - y.system_idle , - 'https://www.brentozar.com/go/cpu', - STUFF(( SELECT TOP 2147483647 - CHAR(10) + CHAR(13) - + y2.system_idle - + '% ON ' - + y2.event_date - + ' Ring buffer details: ' - + y2.record - FROM y AS y2 - ORDER BY y2.event_date_raw DESC - FOR XML PATH(N''), TYPE ).value(N'.[1]', N'VARCHAR(MAX)'), 1, 1, N'') AS query - FROM y - ORDER BY y.event_date_raw DESC; + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + END; + END; - - /* Highlight if non SQL processes are using >25% CPU - CheckID 28 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 28',10,1) WITH NOWAIT; - END - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) - SELECT 28, 50, 'Server Performance', 'High CPU Utilization - Not SQL', CONVERT(NVARCHAR(100),100 - (y.SQLUsage + y.SystemIdle)) + N'% - Other Processes (not SQL Server) are using this much CPU. This may impact on the performance of your SQL Server instance', 100 - (y.SQLUsage + y.SystemIdle), 'https://www.brentozar.com/go/cpu' - FROM ( - SELECT record, - record.value('(./Record/SchedulerMonitorEvent/SystemHealth/SystemIdle)[1]', 'int') AS SystemIdle - ,record.value('(./Record/SchedulerMonitorEvent/SystemHealth/ProcessUtilization)[1]', 'int') AS SQLUsage - FROM ( - SELECT TOP 1 CONVERT(XML, record) AS record - FROM sys.dm_os_ring_buffers - WHERE ring_buffer_type = N'RING_BUFFER_SCHEDULER_MONITOR' - AND record LIKE '%%' - ORDER BY timestamp DESC) AS rb - ) AS y - WHERE 100 - (y.SQLUsage + y.SystemIdle) >= 25; - - END; /* IF @Seconds < 30 */ + /*If event file*/ + IF + ( + @TargetSessionType LIKE N'event%' + AND @EventSessionName NOT LIKE N'system_health%' + ) + BEGIN + IF @Azure = 0 + BEGIN + RAISERROR('@TargetSessionType is event_file, assigning XML for non-Azure', 0, 1) WITH NOWAIT; - /* Query Problems - Statistics Updated Recently - CheckID 44 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 44',10,1) WITH NOWAIT; - END + SELECT + @SessionId = t.event_session_id, + @TargetSessionId = t.target_id + FROM sys.server_event_session_targets AS t + JOIN sys.server_event_sessions AS s + ON s.event_session_id = t.event_session_id + WHERE t.name = @TargetSessionType + AND s.name = @EventSessionName + OPTION(RECOMPILE); - IF 20 >= (SELECT COUNT(*) FROM sys.databases WHERE name NOT IN ('master', 'model', 'msdb', 'tempdb')) - AND @Seconds > 0 - BEGIN - CREATE TABLE #UpdatedStats (HowToStopIt NVARCHAR(4000), RowsForSorting BIGINT); - IF EXISTS(SELECT * FROM sys.all_objects WHERE name = 'dm_db_stats_properties') - BEGIN - /* We don't want to hang around to obtain locks */ - SET LOCK_TIMEOUT 0; + /*We get the file name automatically, here*/ + RAISERROR('Assigning @FileName...', 0, 1) WITH NOWAIT; + SELECT + @FileName = + CASE + WHEN f.file_name LIKE N'%.xel' + THEN REPLACE(f.file_name, N'.xel', N'*.xel') + ELSE f.file_name + N'*.xel' + END + FROM + ( + SELECT + file_name = + CONVERT(nvarchar(4000), f.value) + FROM sys.server_event_session_fields AS f + WHERE f.event_session_id = @SessionId + AND f.object_id = @TargetSessionId + AND f.name = N'filename' + ) AS f + OPTION(RECOMPILE); + END; - IF SERVERPROPERTY('EngineEdition') <> 5 /*SERVERPROPERTY('Edition') <> 'SQL Azure'*/ - SET @StringToExecute = N'USE [?];' + @LineFeed; - ELSE - SET @StringToExecute = N''; + IF @Azure = 1 + BEGIN + RAISERROR('@TargetSessionType is event_file, assigning XML for Azure', 0, 1) WITH NOWAIT; + SELECT + @SessionId = + t.event_session_address, + @TargetSessionId = + t.target_name + FROM sys.dm_xe_database_session_targets t + JOIN sys.dm_xe_database_sessions s + ON s.address = t.event_session_address + WHERE t.target_name = @TargetSessionType + AND s.name = @EventSessionName + OPTION(RECOMPILE); - SET @StringToExecute = @StringToExecute + 'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; SET LOCK_TIMEOUT 1000;' + @LineFeed + - 'BEGIN TRY' + @LineFeed + - ' INSERT INTO #UpdatedStats(HowToStopIt, RowsForSorting)' + @LineFeed + - ' SELECT HowToStopIt = ' + @LineFeed + - ' QUOTENAME(DB_NAME()) + N''.'' +' + @LineFeed + - ' QUOTENAME(SCHEMA_NAME(obj.schema_id)) + N''.'' +' + @LineFeed + - ' QUOTENAME(obj.name) +' + @LineFeed + - ' N'' statistic '' + QUOTENAME(stat.name) + ' + @LineFeed + - ' N'' was updated on '' + CONVERT(NVARCHAR(50), sp.last_updated, 121) + N'','' + ' + @LineFeed + - ' N'' had '' + CAST(sp.rows AS NVARCHAR(50)) + N'' rows, with '' +' + @LineFeed + - ' CAST(sp.rows_sampled AS NVARCHAR(50)) + N'' rows sampled,'' + ' + @LineFeed + - ' N'' producing '' + CAST(sp.steps AS NVARCHAR(50)) + N'' steps in the histogram.'',' + @LineFeed + - ' sp.rows' + @LineFeed + - ' FROM sys.objects AS obj WITH (NOLOCK)' + @LineFeed + - ' INNER JOIN sys.stats AS stat WITH (NOLOCK) ON stat.object_id = obj.object_id ' + @LineFeed + - ' CROSS APPLY sys.dm_db_stats_properties(stat.object_id, stat.stats_id) AS sp ' + @LineFeed + - ' WHERE sp.last_updated > DATEADD(MI, -15, GETDATE())' + @LineFeed + - ' AND obj.is_ms_shipped = 0' + @LineFeed + - ' AND ''[?]'' <> ''[tempdb]'';' + @LineFeed + - 'END TRY' + @LineFeed + - 'BEGIN CATCH' + @LineFeed + - ' IF (ERROR_NUMBER() = 1222)' + @LineFeed + - ' BEGIN ' + @LineFeed + - ' INSERT INTO #UpdatedStats(HowToStopIt, RowsForSorting)' + @LineFeed + - ' SELECT HowToStopIt = ' + @LineFeed + - ' QUOTENAME(DB_NAME()) +' + @LineFeed + - ' N'' No information could be retrieved as the lock timeout was exceeded,''+' + @LineFeed + - ' N'' this is likely due to an Index operation in Progress'',' + @LineFeed + - ' -1' + @LineFeed + - ' END' + @LineFeed + - ' ELSE' + @LineFeed + - ' BEGIN' + @LineFeed + - ' INSERT INTO #UpdatedStats(HowToStopIt, RowsForSorting)' + @LineFeed + - ' SELECT HowToStopIt = ' + @LineFeed + - ' QUOTENAME(DB_NAME()) +' + @LineFeed + - ' N'' No information could be retrieved as a result of error: ''+' + @LineFeed + - ' CAST(ERROR_NUMBER() AS NVARCHAR(10)) +' + @LineFeed + - ' N'' with message: ''+' + @LineFeed + - ' CAST(ERROR_MESSAGE() AS NVARCHAR(128)),' + @LineFeed + - ' -1' + @LineFeed + - ' END' + @LineFeed + - 'END CATCH' - ; + /*We get the file name automatically, here*/ + RAISERROR('Assigning @FileName...', 0, 1) WITH NOWAIT; + SELECT + @FileName = + CASE + WHEN f.file_name LIKE N'%.xel' + THEN REPLACE(f.file_name, N'.xel', N'*.xel') + ELSE f.file_name + N'*.xel' + END + FROM + ( + SELECT + file_name = + CONVERT(nvarchar(4000), f.value) + FROM sys.server_event_session_fields AS f + WHERE f.event_session_id = @SessionId + AND f.object_id = @TargetSessionId + AND f.name = N'filename' + ) AS f + OPTION(RECOMPILE); + END; - IF SERVERPROPERTY('EngineEdition') <> 5 /*SERVERPROPERTY('Edition') <> 'SQL Azure'*/ - BEGIN - BEGIN TRY - EXEC sp_MSforeachdb @StringToExecute; - END TRY - BEGIN CATCH - IF (ERROR_NUMBER() = 1222) - BEGIN - INSERT INTO #UpdatedStats(HowToStopIt, RowsForSorting) - SELECT HowToStopIt = N'No information could be retrieved as the lock timeout was exceeded while iterating databases,' + - N' this is likely due to an Index operation in Progress', -1; - END - ELSE - BEGIN - THROW; - END - END CATCH - END - ELSE - EXEC(@StringToExecute); + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Reading for event_file %s', 0, 1, @FileName) WITH NOWAIT; - /* Set timeout back to a default value of -1 */ - SET LOCK_TIMEOUT -1; - END; - - /* We mark timeout exceeded with a -1 so only show these IF there is statistics info that succeeded */ - IF EXISTS (SELECT * FROM #UpdatedStats WHERE RowsForSorting > -1) - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) - SELECT 44 AS CheckId, - 50 AS Priority, - 'Query Problems' AS FindingGroup, - 'Statistics Updated Recently' AS Finding, - 'https://www.brentozar.com/go/stats' AS URL, - 'In the last 15 minutes, statistics were updated. To see which ones, click the HowToStopIt column.' + @LineFeed + @LineFeed - + 'This effectively clears the plan cache for queries that involve these tables,' + @LineFeed - + 'which thereby causes parameter sniffing: those queries are now getting brand new' + @LineFeed - + 'query plans based on whatever parameters happen to call them next.' + @LineFeed + @LineFeed - + 'Be on the lookout for sudden parameter sniffing issues after this time range.', - HowToStopIt = (SELECT (SELECT HowToStopIt + NCHAR(10)) - FROM #UpdatedStats - ORDER BY RowsForSorting DESC - FOR XML PATH('')); + INSERT + #x WITH(TABLOCKX) + ( + x + ) + SELECT + x = TRY_CAST(f.event_data AS xml) + FROM sys.fn_xe_file_target_read_file(@FileName, NULL, NULL, NULL) AS f + LEFT JOIN #t AS t + ON 1 = 1 + OPTION(RECOMPILE); - END + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + END; - RAISERROR('Finished running investigatory queries',10,1) WITH NOWAIT; + /*The XML is parsed differently if it comes from the event file or ring buffer*/ + /*If ring buffers*/ + IF + ( + @TargetSessionType LIKE N'ring%' + AND @EventSessionName NOT LIKE N'system_health%' + ) + BEGIN + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Inserting to #deadlock_data for ring buffer data', 0, 1) WITH NOWAIT; - /* End of checks. If we haven't waited @Seconds seconds, wait. */ - IF DATEADD(SECOND,1,SYSDATETIMEOFFSET()) < @FinishSampleTime - BEGIN - RAISERROR('Waiting to match @Seconds parameter',10,1) WITH NOWAIT; - WAITFOR TIME @FinishSampleTimeWaitFor; - END; + INSERT + #deadlock_data WITH(TABLOCKX) + ( + deadlock_xml + ) + SELECT + deadlock_xml = + e.x.query(N'.') + FROM #x AS x + LEFT JOIN #t AS t + ON 1 = 1 + CROSS APPLY x.x.nodes('/RingBufferTarget/event') AS e(x) + WHERE + ( + e.x.exist('@name[ .= "xml_deadlock_report"]') = 1 + OR e.x.exist('@name[ .= "database_xml_deadlock_report"]') = 1 + OR e.x.exist('@name[ .= "xml_deadlock_report_filtered"]') = 1 + ) + AND e.x.exist('@timestamp[. >= sql:variable("@StartDate") and .< sql:variable("@EndDate")]') = 1 + OPTION(RECOMPILE); - IF @total_cpu_usage IN (0, 1) - BEGIN - EXEC sys.sp_executesql - @get_thread_time_ms, - N'@thread_time_ms FLOAT OUTPUT', - @thread_time_ms OUTPUT; - END + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + END; - RAISERROR('Capturing second pass of wait stats, perfmon counters, file stats',10,1) WITH NOWAIT; - /* Populate #FileStats, #PerfmonStats, #WaitStats with DMV data. In a second, we'll compare these. */ - SET @StringToExecute = N' - INSERT #WaitStats(Pass, SampleTime, wait_type, wait_time_ms, thread_time_ms, signal_wait_time_ms, waiting_tasks_count) - SELECT - x.Pass, - x.SampleTime, - x.wait_type, - SUM(x.sum_wait_time_ms) AS sum_wait_time_ms, - @thread_time_ms AS thread_time_ms, - SUM(x.sum_signal_wait_time_ms) AS sum_signal_wait_time_ms, - SUM(x.sum_waiting_tasks) AS sum_waiting_tasks - FROM ( - SELECT - 2 AS Pass, - SYSDATETIMEOFFSET() AS SampleTime, - owt.wait_type, - SUM(owt.wait_duration_ms) OVER (PARTITION BY owt.wait_type, owt.session_id) - - CASE WHEN @Seconds = 0 THEN 0 ELSE (@Seconds * 1000) END AS sum_wait_time_ms, - 0 AS sum_signal_wait_time_ms, - CASE @Seconds WHEN 0 THEN 0 ELSE 1 END AS sum_waiting_tasks - FROM sys.dm_os_waiting_tasks owt - WHERE owt.session_id > 50 - AND owt.wait_duration_ms >= CASE @Seconds WHEN 0 THEN 0 ELSE @Seconds * 1000 END - UNION ALL - SELECT - 2 AS Pass, - SYSDATETIMEOFFSET() AS SampleTime, - os.wait_type, - SUM(os.wait_time_ms) OVER (PARTITION BY os.wait_type) AS sum_wait_time_ms, - SUM(os.signal_wait_time_ms) OVER (PARTITION BY os.wait_type ) AS sum_signal_wait_time_ms, - SUM(os.waiting_tasks_count) OVER (PARTITION BY os.wait_type) AS sum_waiting_tasks '; + /*If event file*/ + IF + ( + @TargetSessionType LIKE N'event_file%' + AND @EventSessionName NOT LIKE N'system_health%' + ) + BEGIN + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Inserting to #deadlock_data for event file data', 0, 1) WITH NOWAIT; - IF SERVERPROPERTY('EngineEdition') = 5 /*SERVERPROPERTY('Edition') = 'SQL Azure'*/ - SET @StringToExecute = @StringToExecute + N' FROM sys.dm_db_wait_stats os '; - ELSE - SET @StringToExecute = @StringToExecute + N' FROM sys.dm_os_wait_stats os '; + IF @Debug = 1 BEGIN SET STATISTICS XML ON; END; - SET @StringToExecute = @StringToExecute + N' - ) x - WHERE NOT EXISTS - ( - SELECT * - FROM ##WaitCategories AS wc - WHERE wc.WaitType = x.wait_type - AND wc.Ignorable = 1 - ) - GROUP BY x.Pass, x.SampleTime, x.wait_type - ORDER BY sum_wait_time_ms DESC;'; - - EXEC sys.sp_executesql - @StringToExecute, - N'@StartSampleTime DATETIMEOFFSET, - @Seconds INT, - @thread_time_ms FLOAT', - @StartSampleTime, - @Seconds, - @thread_time_ms; + INSERT + #deadlock_data WITH(TABLOCKX) + ( + deadlock_xml + ) + SELECT + deadlock_xml = + e.x.query('.') + FROM #x AS x + LEFT JOIN #t AS t + ON 1 = 1 + CROSS APPLY x.x.nodes('/event') AS e(x) + WHERE + ( + e.x.exist('@name[ .= "xml_deadlock_report"]') = 1 + OR e.x.exist('@name[ .= "database_xml_deadlock_report"]') = 1 + OR e.x.exist('@name[ .= "xml_deadlock_report_filtered"]') = 1 + ) + AND e.x.exist('@timestamp[. >= sql:variable("@StartDate") and .< sql:variable("@EndDate")]') = 1 + OPTION(RECOMPILE); - WITH w AS - ( - SELECT - total_waits = - CONVERT - ( - FLOAT, - SUM(ws.wait_time_ms) - ) - FROM #WaitStats AS ws - WHERE Pass = 2 - ) - UPDATE ws - SET ws.thread_time_ms += w.total_waits - FROM #WaitStats AS ws - CROSS JOIN w - WHERE ws.Pass = 2 - OPTION(RECOMPILE); - + IF @Debug = 1 BEGIN SET STATISTICS XML OFF; END; + + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + END; + + /*This section deals with event file*/ + IF + ( + @TargetSessionType LIKE N'event%' + AND @EventSessionName LIKE N'system_health%' + ) + BEGIN + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Grab the initial set of xml to parse at %s', 0, 1, @d) WITH NOWAIT; + + IF @Debug = 1 BEGIN SET STATISTICS XML ON; END; + + SELECT + xml.deadlock_xml + INTO #xml + FROM + ( + SELECT + deadlock_xml = + TRY_CAST(fx.event_data AS xml) + FROM sys.fn_xe_file_target_read_file(N'system_health*.xel', NULL, NULL, NULL) AS fx + LEFT JOIN #t AS t + ON 1 = 1 + WHERE fx.object_name = N'xml_deadlock_report' + ) AS xml + CROSS APPLY xml.deadlock_xml.nodes('/event') AS e(x) + WHERE 1 = 1 + AND e.x.exist('@timestamp[. >= sql:variable("@StartDate") and .< sql:variable("@EndDate")]') = 1 + OPTION(RECOMPILE); + + INSERT + #deadlock_data WITH(TABLOCKX) + SELECT + deadlock_xml = + xml.deadlock_xml + FROM #xml AS xml + LEFT JOIN #t AS t + ON 1 = 1 + WHERE xml.deadlock_xml IS NOT NULL + OPTION(RECOMPILE); - INSERT INTO #FileStats (Pass, SampleTime, DatabaseID, FileID, DatabaseName, FileLogicalName, SizeOnDiskMB, io_stall_read_ms , - num_of_reads, [bytes_read] , io_stall_write_ms,num_of_writes, [bytes_written], PhysicalName, TypeDesc, avg_stall_read_ms, avg_stall_write_ms) - SELECT 2 AS Pass, - SYSDATETIMEOFFSET() AS SampleTime, - mf.[database_id], - mf.[file_id], - DB_NAME(vfs.database_id) AS [db_name], - mf.name + N' [' + mf.type_desc COLLATE SQL_Latin1_General_CP1_CI_AS + N']' AS file_logical_name , - CAST(( ( vfs.size_on_disk_bytes / 1024.0 ) / 1024.0 ) AS INT) AS size_on_disk_mb , - vfs.io_stall_read_ms , - vfs.num_of_reads , - vfs.[num_of_bytes_read], - vfs.io_stall_write_ms , - vfs.num_of_writes , - vfs.[num_of_bytes_written], - mf.physical_name, - mf.type_desc, - 0, - 0 - FROM sys.dm_io_virtual_file_stats (NULL, NULL) AS vfs - INNER JOIN #MasterFiles AS mf ON vfs.file_id = mf.file_id - AND vfs.database_id = mf.database_id - WHERE vfs.num_of_reads > 0 - OR vfs.num_of_writes > 0; + IF @Debug = 1 BEGIN SET STATISTICS XML OFF; END; - INSERT INTO #PerfmonStats (Pass, SampleTime, [object_name],[counter_name],[instance_name],[cntr_value],[cntr_type]) - SELECT 2 AS Pass, - SYSDATETIMEOFFSET() AS SampleTime, - RTRIM(dmv.object_name), RTRIM(dmv.counter_name), RTRIM(dmv.instance_name), dmv.cntr_value, dmv.cntr_type - FROM #PerfmonCounters counters - INNER JOIN sys.dm_os_performance_counters dmv ON counters.counter_name COLLATE SQL_Latin1_General_CP1_CI_AS = RTRIM(dmv.counter_name) COLLATE SQL_Latin1_General_CP1_CI_AS - AND counters.[object_name] COLLATE SQL_Latin1_General_CP1_CI_AS = RTRIM(dmv.[object_name]) COLLATE SQL_Latin1_General_CP1_CI_AS - AND (counters.[instance_name] IS NULL OR counters.[instance_name] COLLATE SQL_Latin1_General_CP1_CI_AS = RTRIM(dmv.[instance_name]) COLLATE SQL_Latin1_General_CP1_CI_AS); + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + END; - /* Set the latencies and averages. We could do this with a CTE, but we're not ambitious today. */ - UPDATE fNow - SET avg_stall_read_ms = ((fNow.io_stall_read_ms - fBase.io_stall_read_ms) / (fNow.num_of_reads - fBase.num_of_reads)) - FROM #FileStats fNow - INNER JOIN #FileStats fBase ON fNow.DatabaseID = fBase.DatabaseID AND fNow.FileID = fBase.FileID AND fNow.SampleTime > fBase.SampleTime AND fNow.num_of_reads > fBase.num_of_reads AND fNow.io_stall_read_ms > fBase.io_stall_read_ms - WHERE (fNow.num_of_reads - fBase.num_of_reads) > 0; + /*Parse process and input buffer xml*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Initial Parse process and input buffer xml %s', 0, 1, @d) WITH NOWAIT; - UPDATE fNow - SET avg_stall_write_ms = ((fNow.io_stall_write_ms - fBase.io_stall_write_ms) / (fNow.num_of_writes - fBase.num_of_writes)) - FROM #FileStats fNow - INNER JOIN #FileStats fBase ON fNow.DatabaseID = fBase.DatabaseID AND fNow.FileID = fBase.FileID AND fNow.SampleTime > fBase.SampleTime AND fNow.num_of_writes > fBase.num_of_writes AND fNow.io_stall_write_ms > fBase.io_stall_write_ms - WHERE (fNow.num_of_writes - fBase.num_of_writes) > 0; + SELECT + d1.deadlock_xml, + event_date = d1.deadlock_xml.value('(event/@timestamp)[1]', 'datetime2'), + victim_id = d1.deadlock_xml.value('(//deadlock/victim-list/victimProcess/@id)[1]', 'nvarchar(256)'), + is_parallel = d1.deadlock_xml.exist('//deadlock/resource-list/exchangeEvent'), + is_parallel_batch = d1.deadlock_xml.exist('//deadlock/resource-list/SyncPoint'), + deadlock_graph = d1.deadlock_xml.query('/event/data/value/deadlock') + INTO #dd + FROM #deadlock_data AS d1 + LEFT JOIN #t AS t + ON 1 = 1 + OPTION(RECOMPILE); - UPDATE pNow - SET [value_delta] = pNow.cntr_value - pFirst.cntr_value, - [value_per_second] = ((1.0 * pNow.cntr_value - pFirst.cntr_value) / DATEDIFF(ss, pFirst.SampleTime, pNow.SampleTime)) - FROM #PerfmonStats pNow - INNER JOIN #PerfmonStats pFirst ON pFirst.[object_name] = pNow.[object_name] AND pFirst.counter_name = pNow.counter_name AND (pFirst.instance_name = pNow.instance_name OR (pFirst.instance_name IS NULL AND pNow.instance_name IS NULL)) - AND pNow.ID > pFirst.ID - WHERE DATEDIFF(ss, pFirst.SampleTime, pNow.SampleTime) > 0; + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Final Parse process and input buffer xml %s', 0, 1, @d) WITH NOWAIT; - /* Query Stats - If we're within 10 seconds of our projected finish time, do the plan cache analysis. - CheckID 18 */ - IF DATEDIFF(ss, @FinishSampleTime, SYSDATETIMEOFFSET()) > 10 AND @CheckProcedureCache = 1 - BEGIN - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 18',10,1) WITH NOWAIT; - END + SELECT + q.event_date, + q.victim_id, + is_parallel = + CONVERT(bit, q.is_parallel), + q.deadlock_graph, + q.id, + q.spid, + q.database_id, + database_name = + ISNULL + ( + DB_NAME(q.database_id), + N'UNKNOWN' + ), + q.current_database_name, + q.priority, + q.log_used, + q.wait_resource, + q.wait_time, + q.transaction_name, + q.last_tran_started, + q.last_batch_started, + q.last_batch_completed, + q.lock_mode, + q.status, + q.transaction_count, + q.client_app, + q.host_name, + q.login_name, + q.isolation_level, + client_option_1 = + SUBSTRING + ( + CASE WHEN q.clientoption1 & 1 = 1 THEN ', DISABLE_DEF_CNST_CHECK' ELSE '' END + + CASE WHEN q.clientoption1 & 2 = 2 THEN ', IMPLICIT_TRANSACTIONS' ELSE '' END + + CASE WHEN q.clientoption1 & 4 = 4 THEN ', CURSOR_CLOSE_ON_COMMIT' ELSE '' END + + CASE WHEN q.clientoption1 & 8 = 8 THEN ', ANSI_WARNINGS' ELSE '' END + + CASE WHEN q.clientoption1 & 16 = 16 THEN ', ANSI_PADDING' ELSE '' END + + CASE WHEN q.clientoption1 & 32 = 32 THEN ', ANSI_NULLS' ELSE '' END + + CASE WHEN q.clientoption1 & 64 = 64 THEN ', ARITHABORT' ELSE '' END + + CASE WHEN q.clientoption1 & 128 = 128 THEN ', ARITHIGNORE' ELSE '' END + + CASE WHEN q.clientoption1 & 256 = 256 THEN ', QUOTED_IDENTIFIER' ELSE '' END + + CASE WHEN q.clientoption1 & 512 = 512 THEN ', NOCOUNT' ELSE '' END + + CASE WHEN q.clientoption1 & 1024 = 1024 THEN ', ANSI_NULL_DFLT_ON' ELSE '' END + + CASE WHEN q.clientoption1 & 2048 = 2048 THEN ', ANSI_NULL_DFLT_OFF' ELSE '' END + + CASE WHEN q.clientoption1 & 4096 = 4096 THEN ', CONCAT_NULL_YIELDS_NULL' ELSE '' END + + CASE WHEN q.clientoption1 & 8192 = 8192 THEN ', NUMERIC_ROUNDABORT' ELSE '' END + + CASE WHEN q.clientoption1 & 16384 = 16384 THEN ', XACT_ABORT' ELSE '' END, + 3, + 500 + ), + client_option_2 = + SUBSTRING + ( + CASE WHEN q.clientoption2 & 1024 = 1024 THEN ', DB CHAINING' ELSE '' END + + CASE WHEN q.clientoption2 & 2048 = 2048 THEN ', NUMERIC ROUNDABORT' ELSE '' END + + CASE WHEN q.clientoption2 & 4096 = 4096 THEN ', ARITHABORT' ELSE '' END + + CASE WHEN q.clientoption2 & 8192 = 8192 THEN ', ANSI PADDING' ELSE '' END + + CASE WHEN q.clientoption2 & 16384 = 16384 THEN ', ANSI NULL DEFAULT' ELSE '' END + + CASE WHEN q.clientoption2 & 65536 = 65536 THEN ', CONCAT NULL YIELDS NULL' ELSE '' END + + CASE WHEN q.clientoption2 & 131072 = 131072 THEN ', RECURSIVE TRIGGERS' ELSE '' END + + CASE WHEN q.clientoption2 & 1048576 = 1048576 THEN ', DEFAULT TO LOCAL CURSOR' ELSE '' END + + CASE WHEN q.clientoption2 & 8388608 = 8388608 THEN ', QUOTED IDENTIFIER' ELSE '' END + + CASE WHEN q.clientoption2 & 16777216 = 16777216 THEN ', AUTO CREATE STATISTICS' ELSE '' END + + CASE WHEN q.clientoption2 & 33554432 = 33554432 THEN ', CURSOR CLOSE ON COMMIT' ELSE '' END + + CASE WHEN q.clientoption2 & 67108864 = 67108864 THEN ', ANSI NULLS' ELSE '' END + + CASE WHEN q.clientoption2 & 268435456 = 268435456 THEN ', ANSI WARNINGS' ELSE '' END + + CASE WHEN q.clientoption2 & 536870912 = 536870912 THEN ', FULL TEXT ENABLED' ELSE '' END + + CASE WHEN q.clientoption2 & 1073741824 = 1073741824 THEN ', AUTO UPDATE STATISTICS' ELSE '' END + + CASE WHEN q.clientoption2 & 1469283328 = 1469283328 THEN ', ALL SETTABLE OPTIONS' ELSE '' END, + 3, + 500 + ), + q.process_xml + INTO #deadlock_process + FROM + ( + SELECT + dd.deadlock_xml, + event_date = + DATEADD + ( + MINUTE, + DATEDIFF + ( + MINUTE, + GETUTCDATE(), + SYSDATETIME() + ), + dd.event_date + ), + dd.victim_id, + is_parallel = + CONVERT(tinyint, dd.is_parallel) + + CONVERT(tinyint, dd.is_parallel_batch), + dd.deadlock_graph, + id = ca.dp.value('@id', 'nvarchar(256)'), + spid = ca.dp.value('@spid', 'smallint'), + database_id = ca.dp.value('@currentdb', 'bigint'), + current_database_name = ca.dp.value('@currentdbname', 'nvarchar(256)'), + priority = ca.dp.value('@priority', 'smallint'), + log_used = ca.dp.value('@logused', 'bigint'), + wait_resource = ca.dp.value('@waitresource', 'nvarchar(256)'), + wait_time = ca.dp.value('@waittime', 'bigint'), + transaction_name = ca.dp.value('@transactionname', 'nvarchar(256)'), + last_tran_started = ca.dp.value('@lasttranstarted', 'datetime'), + last_batch_started = ca.dp.value('@lastbatchstarted', 'datetime'), + last_batch_completed = ca.dp.value('@lastbatchcompleted', 'datetime'), + lock_mode = ca.dp.value('@lockMode', 'nvarchar(256)'), + status = ca.dp.value('@status', 'nvarchar(256)'), + transaction_count = ca.dp.value('@trancount', 'bigint'), + client_app = ca.dp.value('@clientapp', 'nvarchar(1024)'), + host_name = ca.dp.value('@hostname', 'nvarchar(256)'), + login_name = ca.dp.value('@loginname', 'nvarchar(256)'), + isolation_level = ca.dp.value('@isolationlevel', 'nvarchar(256)'), + clientoption1 = ca.dp.value('@clientoption1', 'bigint'), + clientoption2 = ca.dp.value('@clientoption2', 'bigint'), + process_xml = ISNULL(ca.dp.query(N'.'), N'') + FROM #dd AS dd + CROSS APPLY dd.deadlock_xml.nodes('//deadlock/process-list/process') AS ca(dp) + WHERE (ca.dp.exist('@currentdb[. = sql:variable("@DatabaseId")]') = 1 OR @DatabaseName IS NULL) + AND (ca.dp.exist('@clientapp[. = sql:variable("@AppName")]') = 1 OR @AppName IS NULL) + AND (ca.dp.exist('@hostname[. = sql:variable("@HostName")]') = 1 OR @HostName IS NULL) + AND (ca.dp.exist('@loginname[. = sql:variable("@LoginName")]') = 1 OR @LoginName IS NULL) + ) AS q + LEFT JOIN #t AS t + ON 1 = 1 + OPTION(RECOMPILE); - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (18, 210, 'Query Stats', 'Plan Cache Analysis Skipped', 'https://www.brentozar.com/go/topqueries', - 'Due to excessive load, the plan cache analysis was skipped. To override this, use @ExpertMode = 1.'); - END; - ELSE IF @CheckProcedureCache = 1 - BEGIN + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + /*Parse execution stack xml*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Parse execution stack xml %s', 0, 1, @d) WITH NOWAIT; - RAISERROR('@CheckProcedureCache = 1, capturing second pass of plan cache',10,1) WITH NOWAIT; + SELECT DISTINCT + dp.id, + dp.event_date, + proc_name = ca.dp.value('@procname', 'nvarchar(1024)'), + sql_handle = ca.dp.value('@sqlhandle', 'nvarchar(131)') + INTO #deadlock_stack + FROM #deadlock_process AS dp + CROSS APPLY dp.process_xml.nodes('//executionStack/frame') AS ca(dp) + WHERE (ca.dp.exist('@procname[. = sql:variable("@StoredProcName")]') = 1 OR @StoredProcName IS NULL) + AND ca.dp.exist('@sqlhandle[ .= "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"]') = 0 + OPTION(RECOMPILE); + + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + + /*Grab the full resource list*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + + RAISERROR('Grab the full resource list %s', 0, 1, @d) WITH NOWAIT; + + SELECT + event_date = + DATEADD + ( + MINUTE, + DATEDIFF + ( + MINUTE, + GETUTCDATE(), + SYSDATETIME() + ), + dr.event_date + ), + dr.victim_id, + dr.resource_xml + INTO + #deadlock_resource + FROM + ( + SELECT + event_date = dd.deadlock_xml.value('(event/@timestamp)[1]', 'datetime2'), + victim_id = dd.deadlock_xml.value('(//deadlock/victim-list/victimProcess/@id)[1]', 'nvarchar(256)'), + resource_xml = ISNULL(ca.dp.query(N'.'), N'') + FROM #deadlock_data AS dd + CROSS APPLY dd.deadlock_xml.nodes('//deadlock/resource-list') AS ca(dp) + ) AS dr + OPTION(RECOMPILE); - /* Populate #QueryStats. SQL 2005 doesn't have query hash or query plan hash. */ - IF @@VERSION LIKE 'Microsoft SQL Server 2005%' - BEGIN - IF @FilterPlansByDatabase IS NULL - BEGIN - SET @StringToExecute = N'INSERT INTO #QueryStats ([sql_handle], Pass, SampleTime, statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, Points) - SELECT [sql_handle], 2 AS Pass, SYSDATETIMEOFFSET(), statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, NULL AS query_hash, NULL AS query_plan_hash, 0 - FROM sys.dm_exec_query_stats qs - WHERE qs.last_execution_time >= @StartSampleTimeText;'; - END; - ELSE - BEGIN - SET @StringToExecute = N'INSERT INTO #QueryStats ([sql_handle], Pass, SampleTime, statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, Points) - SELECT [sql_handle], 2 AS Pass, SYSDATETIMEOFFSET(), statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, NULL AS query_hash, NULL AS query_plan_hash, 0 - FROM sys.dm_exec_query_stats qs - CROSS APPLY sys.dm_exec_plan_attributes(qs.plan_handle) AS attr - INNER JOIN #FilterPlansByDatabase dbs ON CAST(attr.value AS INT) = dbs.DatabaseID - WHERE qs.last_execution_time >= @StartSampleTimeText - AND attr.attribute = ''dbid'';'; - END; - END; - ELSE - BEGIN - IF @FilterPlansByDatabase IS NULL - BEGIN - SET @StringToExecute = N'INSERT INTO #QueryStats ([sql_handle], Pass, SampleTime, statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, Points) - SELECT [sql_handle], 2 AS Pass, SYSDATETIMEOFFSET(), statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, 0 - FROM sys.dm_exec_query_stats qs - WHERE qs.last_execution_time >= @StartSampleTimeText'; - END; - ELSE - BEGIN - SET @StringToExecute = N'INSERT INTO #QueryStats ([sql_handle], Pass, SampleTime, statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, Points) - SELECT [sql_handle], 2 AS Pass, SYSDATETIMEOFFSET(), statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, 0 - FROM sys.dm_exec_query_stats qs - CROSS APPLY sys.dm_exec_plan_attributes(qs.plan_handle) AS attr - INNER JOIN #FilterPlansByDatabase dbs ON CAST(attr.value AS INT) = dbs.DatabaseID - WHERE qs.last_execution_time >= @StartSampleTimeText - AND attr.attribute = ''dbid'';'; - END; - END; - /* Old version pre-2016/06/13: - IF @@VERSION LIKE 'Microsoft SQL Server 2005%' - SET @StringToExecute = N'INSERT INTO #QueryStats ([sql_handle], Pass, SampleTime, statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, Points) - SELECT [sql_handle], 2 AS Pass, SYSDATETIMEOFFSET(), statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, NULL AS query_hash, NULL AS query_plan_hash, 0 - FROM sys.dm_exec_query_stats qs - WHERE qs.last_execution_time >= @StartSampleTimeText;'; - ELSE - SET @StringToExecute = N'INSERT INTO #QueryStats ([sql_handle], Pass, SampleTime, statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, Points) - SELECT [sql_handle], 2 AS Pass, SYSDATETIMEOFFSET(), statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, 0 - FROM sys.dm_exec_query_stats qs - WHERE qs.last_execution_time >= @StartSampleTimeText;'; - */ - SET @ParmDefinitions = N'@StartSampleTimeText NVARCHAR(100)'; - SET @Parm1 = CONVERT(NVARCHAR(100), CAST(@StartSampleTime AS DATETIME), 127); + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - EXECUTE sp_executesql @StringToExecute, @ParmDefinitions, @StartSampleTimeText = @Parm1; + /*Parse object locks*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Parse object locks %s', 0, 1, @d) WITH NOWAIT; - RAISERROR('@CheckProcedureCache = 1, totaling up plan cache metrics',10,1) WITH NOWAIT; + SELECT DISTINCT + ca.event_date, + ca.database_id, + database_name = + ISNULL + ( + DB_NAME(ca.database_id), + N'UNKNOWN' + ), + object_name = + REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE( + REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE( + REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE( + ca.object_name COLLATE Latin1_General_BIN2, + NCHAR(31), N'?'), NCHAR(30), N'?'), NCHAR(29), N'?'), NCHAR(28), N'?'), NCHAR(27), N'?'), + NCHAR(26), N'?'), NCHAR(25), N'?'), NCHAR(24), N'?'), NCHAR(23), N'?'), NCHAR(22), N'?'), + NCHAR(21), N'?'), NCHAR(20), N'?'), NCHAR(19), N'?'), NCHAR(18), N'?'), NCHAR(17), N'?'), + NCHAR(16), N'?'), NCHAR(15), N'?'), NCHAR(14), N'?'), NCHAR(12), N'?'), NCHAR(11), N'?'), + NCHAR(8), N'?'), NCHAR(7), N'?'), NCHAR(6), N'?'), NCHAR(5), N'?'), NCHAR(4), N'?'), + NCHAR(3), N'?'), NCHAR(2), N'?'), NCHAR(1), N'?'), NCHAR(0), N'?'), + ca.lock_mode, + ca.index_name, + ca.associatedObjectId, + waiter_id = w.l.value('@id', 'nvarchar(256)'), + waiter_mode = w.l.value('@mode', 'nvarchar(256)'), + owner_id = o.l.value('@id', 'nvarchar(256)'), + owner_mode = o.l.value('@mode', 'nvarchar(256)'), + lock_type = CAST(N'OBJECT' AS nvarchar(100)) + INTO #deadlock_owner_waiter + FROM + ( + SELECT + dr.event_date, + database_id = ca.dr.value('@dbid', 'bigint'), + object_name = ca.dr.value('@objectname', 'nvarchar(1024)'), + lock_mode = ca.dr.value('@mode', 'nvarchar(256)'), + index_name = ca.dr.value('@indexname', 'nvarchar(256)'), + associatedObjectId = ca.dr.value('@associatedObjectId', 'bigint'), + dr = ca.dr.query('.') + FROM #deadlock_resource AS dr + CROSS APPLY dr.resource_xml.nodes('//resource-list/objectlock') AS ca(dr) + WHERE (ca.dr.exist('@objectname[. = sql:variable("@ObjectName")]') = 1 OR @ObjectName IS NULL) + ) AS ca + CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l) + CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l) + OPTION(RECOMPILE); - /* Get the totals for the entire plan cache */ - INSERT INTO #QueryStats (Pass, SampleTime, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time) - SELECT 0 AS Pass, SYSDATETIMEOFFSET(), SUM(execution_count), SUM(total_worker_time), SUM(total_physical_reads), SUM(total_logical_writes), SUM(total_logical_reads), SUM(total_clr_time), SUM(total_elapsed_time), MIN(creation_time) - FROM sys.dm_exec_query_stats qs; + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + /*Parse page locks*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Parse page locks %s', 0, 1, @d) WITH NOWAIT; - RAISERROR('@CheckProcedureCache = 1, so analyzing execution plans',10,1) WITH NOWAIT; - /* - Pick the most resource-intensive queries to review. Update the Points field - in #QueryStats - if a query is in the top 10 for logical reads, CPU time, - duration, or execution, add 1 to its points. - */ - WITH qsTop AS ( - SELECT TOP 10 qsNow.ID - FROM #QueryStats qsNow - INNER JOIN #QueryStats qsFirst ON qsNow.[sql_handle] = qsFirst.[sql_handle] AND qsNow.statement_start_offset = qsFirst.statement_start_offset AND qsNow.statement_end_offset = qsFirst.statement_end_offset AND qsNow.plan_generation_num = qsFirst.plan_generation_num AND qsNow.plan_handle = qsFirst.plan_handle AND qsFirst.Pass = 1 - WHERE qsNow.total_elapsed_time > qsFirst.total_elapsed_time - AND qsNow.Pass = 2 - AND qsNow.total_elapsed_time - qsFirst.total_elapsed_time > 1000000 /* Only queries with over 1 second of runtime */ - ORDER BY (qsNow.total_elapsed_time - COALESCE(qsFirst.total_elapsed_time, 0)) DESC) - UPDATE #QueryStats - SET Points = Points + 1 - FROM #QueryStats qs - INNER JOIN qsTop ON qs.ID = qsTop.ID; + INSERT + #deadlock_owner_waiter WITH(TABLOCKX) + SELECT DISTINCT + ca.event_date, + ca.database_id, + database_name = + ISNULL + ( + DB_NAME(ca.database_id), + N'UNKNOWN' + ), + ca.object_name, + ca.lock_mode, + ca.index_name, + ca.associatedObjectId, + waiter_id = w.l.value('@id', 'nvarchar(256)'), + waiter_mode = w.l.value('@mode', 'nvarchar(256)'), + owner_id = o.l.value('@id', 'nvarchar(256)'), + owner_mode = o.l.value('@mode', 'nvarchar(256)'), + lock_type = N'PAGE' + FROM + ( + SELECT + dr.event_date, + database_id = ca.dr.value('@dbid', 'bigint'), + object_name = ca.dr.value('@objectname', 'nvarchar(1024)'), + lock_mode = ca.dr.value('@mode', 'nvarchar(256)'), + index_name = ca.dr.value('@indexname', 'nvarchar(256)'), + associatedObjectId = ca.dr.value('@associatedObjectId', 'bigint'), + dr = ca.dr.query('.') + FROM #deadlock_resource AS dr + CROSS APPLY dr.resource_xml.nodes('//resource-list/pagelock') AS ca(dr) + WHERE (ca.dr.exist('@objectname[. = sql:variable("@ObjectName")]') = 1 OR @ObjectName IS NULL) + ) AS ca + CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l) + CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l) + OPTION(RECOMPILE); - WITH qsTop AS ( - SELECT TOP 10 qsNow.ID - FROM #QueryStats qsNow - INNER JOIN #QueryStats qsFirst ON qsNow.[sql_handle] = qsFirst.[sql_handle] AND qsNow.statement_start_offset = qsFirst.statement_start_offset AND qsNow.statement_end_offset = qsFirst.statement_end_offset AND qsNow.plan_generation_num = qsFirst.plan_generation_num AND qsNow.plan_handle = qsFirst.plan_handle AND qsFirst.Pass = 1 - WHERE qsNow.total_logical_reads > qsFirst.total_logical_reads - AND qsNow.Pass = 2 - AND qsNow.total_logical_reads - qsFirst.total_logical_reads > 1000 /* Only queries with over 1000 reads */ - ORDER BY (qsNow.total_logical_reads - COALESCE(qsFirst.total_logical_reads, 0)) DESC) - UPDATE #QueryStats - SET Points = Points + 1 - FROM #QueryStats qs - INNER JOIN qsTop ON qs.ID = qsTop.ID; + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - WITH qsTop AS ( - SELECT TOP 10 qsNow.ID - FROM #QueryStats qsNow - INNER JOIN #QueryStats qsFirst ON qsNow.[sql_handle] = qsFirst.[sql_handle] AND qsNow.statement_start_offset = qsFirst.statement_start_offset AND qsNow.statement_end_offset = qsFirst.statement_end_offset AND qsNow.plan_generation_num = qsFirst.plan_generation_num AND qsNow.plan_handle = qsFirst.plan_handle AND qsFirst.Pass = 1 - WHERE qsNow.total_worker_time > qsFirst.total_worker_time - AND qsNow.Pass = 2 - AND qsNow.total_worker_time - qsFirst.total_worker_time > 1000000 /* Only queries with over 1 second of worker time */ - ORDER BY (qsNow.total_worker_time - COALESCE(qsFirst.total_worker_time, 0)) DESC) - UPDATE #QueryStats - SET Points = Points + 1 - FROM #QueryStats qs - INNER JOIN qsTop ON qs.ID = qsTop.ID; + /*Parse key locks*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Parse key locks %s', 0, 1, @d) WITH NOWAIT; - WITH qsTop AS ( - SELECT TOP 10 qsNow.ID - FROM #QueryStats qsNow - INNER JOIN #QueryStats qsFirst ON qsNow.[sql_handle] = qsFirst.[sql_handle] AND qsNow.statement_start_offset = qsFirst.statement_start_offset AND qsNow.statement_end_offset = qsFirst.statement_end_offset AND qsNow.plan_generation_num = qsFirst.plan_generation_num AND qsNow.plan_handle = qsFirst.plan_handle AND qsFirst.Pass = 1 - WHERE qsNow.execution_count > qsFirst.execution_count - AND qsNow.Pass = 2 - AND (qsNow.total_elapsed_time - qsFirst.total_elapsed_time > 1000000 /* Only queries with over 1 second of runtime */ - OR qsNow.total_logical_reads - qsFirst.total_logical_reads > 1000 /* Only queries with over 1000 reads */ - OR qsNow.total_worker_time - qsFirst.total_worker_time > 1000000 /* Only queries with over 1 second of worker time */) - ORDER BY (qsNow.execution_count - COALESCE(qsFirst.execution_count, 0)) DESC) - UPDATE #QueryStats - SET Points = Points + 1 - FROM #QueryStats qs - INNER JOIN qsTop ON qs.ID = qsTop.ID; + INSERT + #deadlock_owner_waiter WITH(TABLOCKX) + SELECT DISTINCT + ca.event_date, + ca.database_id, + database_name = + ISNULL + ( + DB_NAME(ca.database_id), + N'UNKNOWN' + ), + ca.object_name, + ca.lock_mode, + ca.index_name, + ca.associatedObjectId, + waiter_id = w.l.value('@id', 'nvarchar(256)'), + waiter_mode = w.l.value('@mode', 'nvarchar(256)'), + owner_id = o.l.value('@id', 'nvarchar(256)'), + owner_mode = o.l.value('@mode', 'nvarchar(256)'), + lock_type = N'KEY' + FROM + ( + SELECT + dr.event_date, + database_id = ca.dr.value('@dbid', 'bigint'), + object_name = ca.dr.value('@objectname', 'nvarchar(1024)'), + lock_mode = ca.dr.value('@mode', 'nvarchar(256)'), + index_name = ca.dr.value('@indexname', 'nvarchar(256)'), + associatedObjectId = ca.dr.value('@associatedObjectId', 'bigint'), + dr = ca.dr.query('.') + FROM #deadlock_resource AS dr + CROSS APPLY dr.resource_xml.nodes('//resource-list/keylock') AS ca(dr) + WHERE (ca.dr.exist('@objectname[. = sql:variable("@ObjectName")]') = 1 OR @ObjectName IS NULL) + ) AS ca + CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l) + CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l) + OPTION(RECOMPILE); - /* Query Stats - Most Resource-Intensive Queries - CheckID 17 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 17',10,1) WITH NOWAIT; - END + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, QueryText, QueryStatsNowID, QueryStatsFirstID, PlanHandle, QueryHash) - SELECT 17, 210, 'Query Stats', 'Most Resource-Intensive Queries', 'https://www.brentozar.com/go/topqueries', - 'Query stats during the sample:' + @LineFeed + - 'Executions: ' + CAST(qsNow.execution_count - (COALESCE(qsFirst.execution_count, 0)) AS NVARCHAR(100)) + @LineFeed + - 'Elapsed Time: ' + CAST(qsNow.total_elapsed_time - (COALESCE(qsFirst.total_elapsed_time, 0)) AS NVARCHAR(100)) + @LineFeed + - 'CPU Time: ' + CAST(qsNow.total_worker_time - (COALESCE(qsFirst.total_worker_time, 0)) AS NVARCHAR(100)) + @LineFeed + - 'Logical Reads: ' + CAST(qsNow.total_logical_reads - (COALESCE(qsFirst.total_logical_reads, 0)) AS NVARCHAR(100)) + @LineFeed + - 'Logical Writes: ' + CAST(qsNow.total_logical_writes - (COALESCE(qsFirst.total_logical_writes, 0)) AS NVARCHAR(100)) + @LineFeed + - 'CLR Time: ' + CAST(qsNow.total_clr_time - (COALESCE(qsFirst.total_clr_time, 0)) AS NVARCHAR(100)) + @LineFeed + - @LineFeed + @LineFeed + 'Query stats since ' + CONVERT(NVARCHAR(100), qsNow.creation_time ,121) + @LineFeed + - 'Executions: ' + CAST(qsNow.execution_count AS NVARCHAR(100)) + - CASE qsTotal.execution_count WHEN 0 THEN '' ELSE (' - Percent of Server Total: ' + CAST(CAST(100.0 * qsNow.execution_count / qsTotal.execution_count AS DECIMAL(6,2)) AS NVARCHAR(100)) + '%') END + @LineFeed + - 'Elapsed Time: ' + CAST(qsNow.total_elapsed_time AS NVARCHAR(100)) + - CASE qsTotal.total_elapsed_time WHEN 0 THEN '' ELSE (' - Percent of Server Total: ' + CAST(CAST(100.0 * qsNow.total_elapsed_time / qsTotal.total_elapsed_time AS DECIMAL(6,2)) AS NVARCHAR(100)) + '%') END + @LineFeed + - 'CPU Time: ' + CAST(qsNow.total_worker_time AS NVARCHAR(100)) + - CASE qsTotal.total_worker_time WHEN 0 THEN '' ELSE (' - Percent of Server Total: ' + CAST(CAST(100.0 * qsNow.total_worker_time / qsTotal.total_worker_time AS DECIMAL(6,2)) AS NVARCHAR(100)) + '%') END + @LineFeed + - 'Logical Reads: ' + CAST(qsNow.total_logical_reads AS NVARCHAR(100)) + - CASE qsTotal.total_logical_reads WHEN 0 THEN '' ELSE (' - Percent of Server Total: ' + CAST(CAST(100.0 * qsNow.total_logical_reads / qsTotal.total_logical_reads AS DECIMAL(6,2)) AS NVARCHAR(100)) + '%') END + @LineFeed + - 'Logical Writes: ' + CAST(qsNow.total_logical_writes AS NVARCHAR(100)) + - CASE qsTotal.total_logical_writes WHEN 0 THEN '' ELSE (' - Percent of Server Total: ' + CAST(CAST(100.0 * qsNow.total_logical_writes / qsTotal.total_logical_writes AS DECIMAL(6,2)) AS NVARCHAR(100)) + '%') END + @LineFeed + - 'CLR Time: ' + CAST(qsNow.total_clr_time AS NVARCHAR(100)) + - CASE qsTotal.total_clr_time WHEN 0 THEN '' ELSE (' - Percent of Server Total: ' + CAST(CAST(100.0 * qsNow.total_clr_time / qsTotal.total_clr_time AS DECIMAL(6,2)) AS NVARCHAR(100)) + '%') END + @LineFeed + - --@LineFeed + @LineFeed + 'Query hash: ' + CAST(qsNow.query_hash AS NVARCHAR(100)) + @LineFeed + - --@LineFeed + @LineFeed + 'Query plan hash: ' + CAST(qsNow.query_plan_hash AS NVARCHAR(100)) + - @LineFeed AS Details, - 'See the URL for tuning tips on why this query may be consuming resources.' AS HowToStopIt, - qp.query_plan, - QueryText = SUBSTRING(st.text, - (qsNow.statement_start_offset / 2) + 1, - ((CASE qsNow.statement_end_offset - WHEN -1 THEN DATALENGTH(st.text) - ELSE qsNow.statement_end_offset - END - qsNow.statement_start_offset) / 2) + 1), - qsNow.ID AS QueryStatsNowID, - qsFirst.ID AS QueryStatsFirstID, - qsNow.plan_handle AS PlanHandle, - qsNow.query_hash - FROM #QueryStats qsNow - INNER JOIN #QueryStats qsTotal ON qsTotal.Pass = 0 - LEFT OUTER JOIN #QueryStats qsFirst ON qsNow.[sql_handle] = qsFirst.[sql_handle] AND qsNow.statement_start_offset = qsFirst.statement_start_offset AND qsNow.statement_end_offset = qsFirst.statement_end_offset AND qsNow.plan_generation_num = qsFirst.plan_generation_num AND qsNow.plan_handle = qsFirst.plan_handle AND qsFirst.Pass = 1 - CROSS APPLY sys.dm_exec_sql_text(qsNow.sql_handle) AS st - CROSS APPLY sys.dm_exec_query_plan(qsNow.plan_handle) AS qp - WHERE qsNow.Points > 0 AND st.text IS NOT NULL AND qp.query_plan IS NOT NULL; + /*Parse RID locks*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Parse RID locks %s', 0, 1, @d) WITH NOWAIT; - UPDATE #BlitzFirstResults - SET DatabaseID = CAST(attr.value AS INT), - DatabaseName = DB_NAME(CAST(attr.value AS INT)) - FROM #BlitzFirstResults - CROSS APPLY sys.dm_exec_plan_attributes(#BlitzFirstResults.PlanHandle) AS attr - WHERE attr.attribute = 'dbid'; + INSERT + #deadlock_owner_waiter WITH(TABLOCKX) + SELECT DISTINCT + ca.event_date, + ca.database_id, + database_name = + ISNULL + ( + DB_NAME(ca.database_id), + N'UNKNOWN' + ), + ca.object_name, + ca.lock_mode, + ca.index_name, + ca.associatedObjectId, + waiter_id = w.l.value('@id', 'nvarchar(256)'), + waiter_mode = w.l.value('@mode', 'nvarchar(256)'), + owner_id = o.l.value('@id', 'nvarchar(256)'), + owner_mode = o.l.value('@mode', 'nvarchar(256)'), + lock_type = N'RID' + FROM + ( + SELECT + dr.event_date, + database_id = ca.dr.value('@dbid', 'bigint'), + object_name = ca.dr.value('@objectname', 'nvarchar(1024)'), + lock_mode = ca.dr.value('@mode', 'nvarchar(256)'), + index_name = ca.dr.value('@indexname', 'nvarchar(256)'), + associatedObjectId = ca.dr.value('@associatedObjectId', 'bigint'), + dr = ca.dr.query('.') + FROM #deadlock_resource AS dr + CROSS APPLY dr.resource_xml.nodes('//resource-list/ridlock') AS ca(dr) + WHERE (ca.dr.exist('@objectname[. = sql:variable("@ObjectName")]') = 1 OR @ObjectName IS NULL) + ) AS ca + CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l) + CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l) + OPTION(RECOMPILE); + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - END; /* IF DATEDIFF(ss, @FinishSampleTime, SYSDATETIMEOFFSET()) > 10 AND @CheckProcedureCache = 1 */ + /*Parse row group locks*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Parse row group locks %s', 0, 1, @d) WITH NOWAIT; + + INSERT + #deadlock_owner_waiter WITH(TABLOCKX) + SELECT DISTINCT + ca.event_date, + ca.database_id, + database_name = + ISNULL + ( + DB_NAME(ca.database_id), + N'UNKNOWN' + ), + ca.object_name, + ca.lock_mode, + ca.index_name, + ca.associatedObjectId, + waiter_id = w.l.value('@id', 'nvarchar(256)'), + waiter_mode = w.l.value('@mode', 'nvarchar(256)'), + owner_id = o.l.value('@id', 'nvarchar(256)'), + owner_mode = o.l.value('@mode', 'nvarchar(256)'), + lock_type = N'ROWGROUP' + FROM + ( + SELECT + dr.event_date, + database_id = ca.dr.value('@dbid', 'bigint'), + object_name = ca.dr.value('@objectname', 'nvarchar(1024)'), + lock_mode = ca.dr.value('@mode', 'nvarchar(256)'), + index_name = ca.dr.value('@indexname', 'nvarchar(256)'), + associatedObjectId = ca.dr.value('@associatedObjectId', 'bigint'), + dr = ca.dr.query('.') + FROM #deadlock_resource AS dr + CROSS APPLY dr.resource_xml.nodes('//resource-list/rowgrouplock') AS ca(dr) + WHERE (ca.dr.exist('@objectname[. = sql:variable("@ObjectName")]') = 1 OR @ObjectName IS NULL) + ) AS ca + CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l) + CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l) + OPTION(RECOMPILE); + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - RAISERROR('Analyzing changes between first and second passes of DMVs',10,1) WITH NOWAIT; + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Fixing heaps in #deadlock_owner_waiter %s', 0, 1, @d) WITH NOWAIT; - /* Wait Stats - CheckID 6 */ - /* Compare the current wait stats to the sample we took at the start, and insert the top 10 waits. */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 6',10,1) WITH NOWAIT; - END + UPDATE + d + SET + d.index_name = + d.object_name + N'.HEAP' + FROM #deadlock_owner_waiter AS d + WHERE d.lock_type IN + ( + N'HEAP', + N'RID' + ) + OPTION(RECOMPILE); - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, DetailsInt) - SELECT TOP 10 6 AS CheckID, - 200 AS Priority, - 'Wait Stats' AS FindingGroup, - wNow.wait_type AS Finding, /* IF YOU CHANGE THIS, STUFF WILL BREAK. Other checks look for wait type names in the Finding field. See checks 11, 12 as example. */ - N'https://www.sqlskills.com/help/waits/' + LOWER(wNow.wait_type) + '/' AS URL, - 'For ' + CAST(((wNow.wait_time_ms - COALESCE(wBase.wait_time_ms,0)) / 1000) AS NVARCHAR(100)) + ' seconds over the last ' + CASE @Seconds WHEN 0 THEN (CAST(DATEDIFF(dd,@StartSampleTime,@FinishSampleTime) AS NVARCHAR(10)) + ' days') ELSE (CAST(@Seconds AS NVARCHAR(10)) + ' seconds') END + ', SQL Server was waiting on this particular bottleneck.' + @LineFeed + @LineFeed AS Details, - 'See the URL for more details on how to mitigate this wait type.' AS HowToStopIt, - ((wNow.wait_time_ms - COALESCE(wBase.wait_time_ms,0)) / 1000) AS DetailsInt - FROM #WaitStats wNow - LEFT OUTER JOIN #WaitStats wBase ON wNow.wait_type = wBase.wait_type AND wNow.SampleTime > wBase.SampleTime - WHERE wNow.wait_time_ms > (wBase.wait_time_ms + (.5 * (DATEDIFF(ss,@StartSampleTime,@FinishSampleTime)) * 1000)) /* Only look for things we've actually waited on for half of the time or more */ - ORDER BY (wNow.wait_time_ms - COALESCE(wBase.wait_time_ms,0)) DESC; + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - /* Server Performance - Poison Wait Detected - CheckID 30 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 30',10,1) WITH NOWAIT; - END + /*Parse parallel deadlocks*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Parse parallel deadlocks %s', 0, 1, @d) WITH NOWAIT; - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, DetailsInt) - SELECT 30 AS CheckID, - 10 AS Priority, - 'Server Performance' AS FindingGroup, - 'Poison Wait Detected: ' + wNow.wait_type AS Finding, - N'https://www.brentozar.com/go/poison/#' + wNow.wait_type AS URL, - 'For ' + CAST(((wNow.wait_time_ms - COALESCE(wBase.wait_time_ms,0)) / 1000) AS NVARCHAR(100)) + ' seconds over the last ' + CASE @Seconds WHEN 0 THEN (CAST(DATEDIFF(dd,@StartSampleTime,@FinishSampleTime) AS NVARCHAR(10)) + ' days') ELSE (CAST(@Seconds AS NVARCHAR(10)) + ' seconds') END + ', SQL Server was waiting on this particular bottleneck.' + @LineFeed + @LineFeed AS Details, - 'See the URL for more details on how to mitigate this wait type.' AS HowToStopIt, - ((wNow.wait_time_ms - COALESCE(wBase.wait_time_ms,0)) / 1000) AS DetailsInt - FROM #WaitStats wNow - LEFT OUTER JOIN #WaitStats wBase ON wNow.wait_type = wBase.wait_type AND wNow.SampleTime > wBase.SampleTime - WHERE wNow.wait_type IN ('IO_QUEUE_LIMIT', 'IO_RETRY', 'LOG_RATE_GOVERNOR', 'POOL_LOG_RATE_GOVERNOR', 'PREEMPTIVE_DEBUG', 'RESMGR_THROTTLED', 'RESOURCE_SEMAPHORE', 'RESOURCE_SEMAPHORE_QUERY_COMPILE','SE_REPL_CATCHUP_THROTTLE','SE_REPL_COMMIT_ACK','SE_REPL_COMMIT_TURN','SE_REPL_ROLLBACK_ACK','SE_REPL_SLOW_SECONDARY_THROTTLE','THREADPOOL') - AND wNow.wait_time_ms > (wBase.wait_time_ms + 1000); + SELECT DISTINCT + ca.id, + ca.event_date, + ca.wait_type, + ca.node_id, + ca.waiter_type, + ca.owner_activity, + ca.waiter_activity, + ca.merging, + ca.spilling, + ca.waiting_to_close, + waiter_id = w.l.value('@id', 'nvarchar(256)'), + owner_id = o.l.value('@id', 'nvarchar(256)') + INTO #deadlock_resource_parallel + FROM + ( + SELECT + dr.event_date, + id = ca.dr.value('@id', 'nvarchar(256)'), + wait_type = ca.dr.value('@WaitType', 'nvarchar(256)'), + node_id = ca.dr.value('@nodeId', 'bigint'), + /* These columns are in 2017 CU5+ ONLY */ + waiter_type = ca.dr.value('@waiterType', 'nvarchar(256)'), + owner_activity = ca.dr.value('@ownerActivity', 'nvarchar(256)'), + waiter_activity = ca.dr.value('@waiterActivity', 'nvarchar(256)'), + merging = ca.dr.value('@merging', 'nvarchar(256)'), + spilling = ca.dr.value('@spilling', 'nvarchar(256)'), + waiting_to_close = ca.dr.value('@waitingToClose', 'nvarchar(256)'), + /* These columns are in 2017 CU5+ ONLY */ + dr = ca.dr.query('.') + FROM #deadlock_resource AS dr + CROSS APPLY dr.resource_xml.nodes('//resource-list/exchangeEvent') AS ca(dr) + ) AS ca + CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l) + CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l) + OPTION(RECOMPILE); + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - /* Server Performance - Slow Data File Reads - CheckID 11 */ - IF EXISTS (SELECT * FROM #BlitzFirstResults WHERE Finding LIKE 'PAGEIOLATCH%') - BEGIN - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 11',10,1) WITH NOWAIT; - END + /*Get rid of parallel noise*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Get rid of parallel noise %s', 0, 1, @d) WITH NOWAIT; - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, DatabaseID, DatabaseName) - SELECT TOP 10 11 AS CheckID, - 50 AS Priority, - 'Server Performance' AS FindingGroup, - 'Slow Data File Reads' AS Finding, - 'https://www.brentozar.com/go/slow/' AS URL, - 'Your server is experiencing PAGEIOLATCH% waits due to slow data file reads. This file is one of the reasons why.' + @LineFeed - + 'File: ' + fNow.PhysicalName + @LineFeed - + 'Number of reads during the sample: ' + CAST((fNow.num_of_reads - fBase.num_of_reads) AS NVARCHAR(20)) + @LineFeed - + 'Seconds spent waiting on storage for these reads: ' + CAST(((fNow.io_stall_read_ms - fBase.io_stall_read_ms) / 1000.0) AS NVARCHAR(20)) + @LineFeed - + 'Average read latency during the sample: ' + CAST(((fNow.io_stall_read_ms - fBase.io_stall_read_ms) / (fNow.num_of_reads - fBase.num_of_reads) ) AS NVARCHAR(20)) + ' milliseconds' + @LineFeed - + 'Microsoft guidance for data file read speed: 20ms or less.' + @LineFeed + @LineFeed AS Details, - 'See the URL for more details on how to mitigate this wait type.' AS HowToStopIt, - fNow.DatabaseID, - fNow.DatabaseName - FROM #FileStats fNow - INNER JOIN #FileStats fBase ON fNow.DatabaseID = fBase.DatabaseID AND fNow.FileID = fBase.FileID AND fNow.SampleTime > fBase.SampleTime AND fNow.num_of_reads > fBase.num_of_reads AND fNow.io_stall_read_ms > (fBase.io_stall_read_ms + 1000) - WHERE (fNow.io_stall_read_ms - fBase.io_stall_read_ms) / (fNow.num_of_reads - fBase.num_of_reads) >= @FileLatencyThresholdMS - AND fNow.TypeDesc = 'ROWS' - ORDER BY (fNow.io_stall_read_ms - fBase.io_stall_read_ms) / (fNow.num_of_reads - fBase.num_of_reads) DESC; - END; + WITH + c AS + ( + SELECT + *, + rn = + ROW_NUMBER() OVER + ( + PARTITION BY + drp.owner_id, + drp.waiter_id + ORDER BY + drp.event_date + ) + FROM #deadlock_resource_parallel AS drp + ) + DELETE + FROM c + WHERE c.rn > 1 + OPTION(RECOMPILE); - /* Server Performance - Slow Log File Writes - CheckID 12 */ - IF EXISTS (SELECT * FROM #BlitzFirstResults WHERE Finding LIKE 'WRITELOG%') - BEGIN - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 12',10,1) WITH NOWAIT; - END + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, DatabaseID, DatabaseName) - SELECT TOP 10 12 AS CheckID, - 50 AS Priority, - 'Server Performance' AS FindingGroup, - 'Slow Log File Writes' AS Finding, - 'https://www.brentozar.com/go/slow/' AS URL, - 'Your server is experiencing WRITELOG waits due to slow log file writes. This file is one of the reasons why.' + @LineFeed - + 'File: ' + fNow.PhysicalName + @LineFeed - + 'Number of writes during the sample: ' + CAST((fNow.num_of_writes - fBase.num_of_writes) AS NVARCHAR(20)) + @LineFeed - + 'Seconds spent waiting on storage for these writes: ' + CAST(((fNow.io_stall_write_ms - fBase.io_stall_write_ms) / 1000.0) AS NVARCHAR(20)) + @LineFeed - + 'Average write latency during the sample: ' + CAST(((fNow.io_stall_write_ms - fBase.io_stall_write_ms) / (fNow.num_of_writes - fBase.num_of_writes) ) AS NVARCHAR(20)) + ' milliseconds' + @LineFeed - + 'Microsoft guidance for log file write speed: 3ms or less.' + @LineFeed + @LineFeed AS Details, - 'See the URL for more details on how to mitigate this wait type.' AS HowToStopIt, - fNow.DatabaseID, - fNow.DatabaseName - FROM #FileStats fNow - INNER JOIN #FileStats fBase ON fNow.DatabaseID = fBase.DatabaseID AND fNow.FileID = fBase.FileID AND fNow.SampleTime > fBase.SampleTime AND fNow.num_of_writes > fBase.num_of_writes AND fNow.io_stall_write_ms > (fBase.io_stall_write_ms + 1000) - WHERE (fNow.io_stall_write_ms - fBase.io_stall_write_ms) / (fNow.num_of_writes - fBase.num_of_writes) >= @FileLatencyThresholdMS - AND fNow.TypeDesc = 'LOG' - ORDER BY (fNow.io_stall_write_ms - fBase.io_stall_write_ms) / (fNow.num_of_writes - fBase.num_of_writes) DESC; - END; + /*Get rid of nonsense*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Get rid of nonsense %s', 0, 1, @d) WITH NOWAIT; + DELETE dow + FROM #deadlock_owner_waiter AS dow + WHERE dow.owner_id = dow.waiter_id + OPTION(RECOMPILE); - /* SQL Server Internal Maintenance - Log File Growing - CheckID 13 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 13',10,1) WITH NOWAIT; - END + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) - SELECT 13 AS CheckID, - 1 AS Priority, - 'SQL Server Internal Maintenance' AS FindingGroup, - 'Log File Growing' AS Finding, - 'https://www.brentozar.com/askbrent/file-growing/' AS URL, - 'Number of growths during the sample: ' + CAST(ps.value_delta AS NVARCHAR(20)) + @LineFeed - + 'Determined by sampling Perfmon counter ' + ps.object_name + ' - ' + ps.counter_name + @LineFeed AS Details, - 'Pre-grow data and log files during maintenance windows so that they do not grow during production loads. See the URL for more details.' AS HowToStopIt - FROM #PerfmonStats ps - WHERE ps.Pass = 2 - AND object_name = @ServiceName + ':Databases' - AND counter_name = 'Log Growths' - AND value_delta > 0; + /*Add some nonsense*/ + ALTER TABLE + #deadlock_process + ADD + waiter_mode nvarchar(256), + owner_mode nvarchar(256), + is_victim AS + CONVERT + ( + bit, + CASE + WHEN id = victim_id + THEN 1 + ELSE 0 + END + ) PERSISTED; + /*Update some nonsense*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Update some nonsense part 1 %s', 0, 1, @d) WITH NOWAIT; - /* SQL Server Internal Maintenance - Log File Shrinking - CheckID 14 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 14',10,1) WITH NOWAIT; - END + UPDATE + dp + SET + dp.owner_mode = dow.owner_mode + FROM #deadlock_process AS dp + JOIN #deadlock_owner_waiter AS dow + ON dp.id = dow.owner_id + AND dp.event_date = dow.event_date + WHERE dp.is_victim = 0 + OPTION(RECOMPILE); - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) - SELECT 14 AS CheckID, - 1 AS Priority, - 'SQL Server Internal Maintenance' AS FindingGroup, - 'Log File Shrinking' AS Finding, - 'https://www.brentozar.com/askbrent/file-shrinking/' AS URL, - 'Number of shrinks during the sample: ' + CAST(ps.value_delta AS NVARCHAR(20)) + @LineFeed - + 'Determined by sampling Perfmon counter ' + ps.object_name + ' - ' + ps.counter_name + @LineFeed AS Details, - 'Pre-grow data and log files during maintenance windows so that they do not grow during production loads. See the URL for more details.' AS HowToStopIt - FROM #PerfmonStats ps - WHERE ps.Pass = 2 - AND object_name = @ServiceName + ':Databases' - AND counter_name = 'Log Shrinks' - AND value_delta > 0; + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - /* Query Problems - Compilations/Sec High - CheckID 15 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 15',10,1) WITH NOWAIT; - END + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Update some nonsense part 2 %s', 0, 1, @d) WITH NOWAIT; - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) - SELECT 15 AS CheckID, - 50 AS Priority, - 'Query Problems' AS FindingGroup, - 'Compilations/Sec High' AS Finding, - 'https://www.brentozar.com/askbrent/compilations/' AS URL, - 'Number of batch requests during the sample: ' + CAST(ps.value_delta AS NVARCHAR(20)) + @LineFeed - + 'Number of compilations during the sample: ' + CAST(psComp.value_delta AS NVARCHAR(20)) + @LineFeed - + 'For OLTP environments, Microsoft recommends that 90% of batch requests should hit the plan cache, and not be compiled from scratch. We are exceeding that threshold.' + @LineFeed AS Details, - 'To find the queries that are compiling, start with:' + @LineFeed - + 'sp_BlitzCache @SortOrder = ''recent compilations''' + @LineFeed - + 'If dynamic SQL or non-parameterized strings are involved, consider enabling Forced Parameterization. See the URL for more details.' AS HowToStopIt - FROM #PerfmonStats ps - INNER JOIN #PerfmonStats psComp ON psComp.Pass = 2 AND psComp.object_name = @ServiceName + ':SQL Statistics' AND psComp.counter_name = 'SQL Compilations/sec' AND psComp.value_delta > 0 - WHERE ps.Pass = 2 - AND ps.object_name = @ServiceName + ':SQL Statistics' - AND ps.counter_name = 'Batch Requests/sec' - AND psComp.value_delta > 75 /* Because sp_BlitzFirst does around 50 compilations and re-compilations */ - AND (psComp.value_delta > (10 * @Seconds) OR psComp.value_delta > ps.value_delta) /* Either doing 10 compilations per second, or more compilations than queries */ - AND (psComp.value_delta * 10) > ps.value_delta; /* Compilations are more than 10% of batch requests per second */ + UPDATE + dp + SET + dp.waiter_mode = dow.waiter_mode + FROM #deadlock_process AS dp + JOIN #deadlock_owner_waiter AS dow + ON dp.victim_id = dow.waiter_id + AND dp.event_date = dow.event_date + WHERE dp.is_victim = 1 + OPTION(RECOMPILE); - /* Query Problems - Re-Compilations/Sec High - CheckID 16 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 16',10,1) WITH NOWAIT; - END + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) - SELECT 16 AS CheckID, - 50 AS Priority, - 'Query Problems' AS FindingGroup, - 'Re-Compilations/Sec High' AS Finding, - 'https://www.brentozar.com/askbrent/recompilations/' AS URL, - 'Number of batch requests during the sample: ' + CAST(ps.value_delta AS NVARCHAR(20)) + @LineFeed - + 'Number of recompilations during the sample: ' + CAST(psComp.value_delta AS NVARCHAR(20)) + @LineFeed - + 'More than 10% of our queries are being recompiled. This is typically due to statistics changing on objects.' + @LineFeed AS Details, - 'To find the queries that are being forced to recompile, start with:' + @LineFeed - + 'sp_BlitzCache @SortOrder = ''recent compilations''' + @LineFeed - + 'Examine those plans to find out which objects are changing so quickly that they hit the stats update threshold. See the URL for more details.' AS HowToStopIt - FROM #PerfmonStats ps - INNER JOIN #PerfmonStats psComp ON psComp.Pass = 2 AND psComp.object_name = @ServiceName + ':SQL Statistics' AND psComp.counter_name = 'SQL Re-Compilations/sec' AND psComp.value_delta > 0 - WHERE ps.Pass = 2 - AND ps.object_name = @ServiceName + ':SQL Statistics' - AND ps.counter_name = 'Batch Requests/sec' - AND psComp.value_delta > 75 /* Because sp_BlitzFirst does around 50 compilations and re-compilations */ - AND (psComp.value_delta > (10 * @Seconds) OR psComp.value_delta > ps.value_delta) /* Either doing 10 recompilations per second, or more recompilations than queries */ - AND (psComp.value_delta * 10) > ps.value_delta; /* Recompilations are more than 10% of batch requests per second */ + /*Get Agent Job and Step names*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Get Agent Job and Step names %s', 0, 1, @d) WITH NOWAIT; + + SELECT + x.event_date, + x.victim_id, + x.id, + x.database_id, + x.client_app, + x.job_id, + x.step_id, + job_id_guid = + CONVERT + ( + uniqueidentifier, + TRY_CAST + ( + N'' + AS xml + ).value('xs:hexBinary(substring(sql:column("x.job_id"), 0))', 'binary(16)') + ) + INTO #agent_job + FROM + ( + SELECT + dp.event_date, + dp.victim_id, + dp.id, + dp.database_id, + dp.client_app, + job_id = + SUBSTRING + ( + dp.client_app, + CHARINDEX(N'0x', dp.client_app) + LEN(N'0x'), + 32 + ), + step_id = + CASE + WHEN CHARINDEX(N': Step ', dp.client_app) > 0 + AND CHARINDEX(N')', dp.client_app, CHARINDEX(N': Step ', dp.client_app)) > 0 + THEN + SUBSTRING + ( + dp.client_app, + CHARINDEX(N': Step ', dp.client_app) + LEN(N': Step '), + CHARINDEX(N')', dp.client_app, CHARINDEX(N': Step ', dp.client_app)) - + (CHARINDEX(N': Step ', dp.client_app) + LEN(N': Step ')) + ) + ELSE dp.client_app + END + FROM #deadlock_process AS dp + WHERE dp.client_app LIKE N'SQLAgent - %' + AND dp.client_app <> N'SQLAgent - Initial Boot Probe' + ) AS x + OPTION(RECOMPILE); - /* Table Problems - Forwarded Fetches/Sec High - CheckID 29 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 29',10,1) WITH NOWAIT; - END + ALTER TABLE + #agent_job + ADD + job_name nvarchar(256), + step_name nvarchar(256); - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) - SELECT 29 AS CheckID, - 40 AS Priority, - 'Table Problems' AS FindingGroup, - 'Forwarded Fetches/Sec High' AS Finding, - 'https://www.brentozar.com/go/fetch/' AS URL, - CAST(ps.value_delta AS NVARCHAR(20)) + ' forwarded fetches (from SQLServer:Access Methods counter)' + @LineFeed - + 'Check your heaps: they need to be rebuilt, or they need a clustered index applied.' + @LineFeed AS Details, - 'Rebuild your heaps. If you use Ola Hallengren maintenance scripts, those do not rebuild heaps by default: https://www.brentozar.com/archive/2016/07/fix-forwarded-records/' AS HowToStopIt - FROM #PerfmonStats ps - INNER JOIN #PerfmonStats psComp ON psComp.Pass = 2 AND psComp.object_name = @ServiceName + ':Access Methods' AND psComp.counter_name = 'Forwarded Records/sec' AND psComp.value_delta > 100 - WHERE ps.Pass = 2 - AND ps.object_name = @ServiceName + ':Access Methods' - AND ps.counter_name = 'Forwarded Records/sec' - AND ps.value_delta > (100 * @Seconds); /* Ignore servers sitting idle */ + IF + ( + @Azure = 0 + AND @RDS = 0 + ) + BEGIN + SET @StringToExecute = + N' + UPDATE + aj + SET + aj.job_name = j.name, + aj.step_name = s.step_name + FROM msdb.dbo.sysjobs AS j + JOIN msdb.dbo.sysjobsteps AS s + ON j.job_id = s.job_id + JOIN #agent_job AS aj + ON aj.job_id_guid = j.job_id + AND aj.step_id = s.step_id + OPTION(RECOMPILE); + '; - /* Check for temp objects with high forwarded fetches. - This has to be done as dynamic SQL because we have to execute OBJECT_NAME inside TempDB. */ - IF EXISTS (SELECT * FROM #BlitzFirstResults WHERE CheckID = 29) - BEGIN - SET @StringToExecute = N' - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) - SELECT TOP 10 29 AS CheckID, - 40 AS Priority, - ''Table Problems'' AS FindingGroup, - ''Forwarded Fetches/Sec High: TempDB Object'' AS Finding, - ''https://www.brentozar.com/go/fetch/'' AS URL, - CAST(COALESCE(os.forwarded_fetch_count,0) - COALESCE(os_prior.forwarded_fetch_count,0) AS NVARCHAR(20)) + '' forwarded fetches on '' + - CASE WHEN OBJECT_NAME(os.object_id) IS NULL THEN ''an unknown table '' - WHEN LEN(OBJECT_NAME(os.object_id)) < 50 THEN ''a table variable, internal identifier '' + OBJECT_NAME(os.object_id) - ELSE ''a temp table '' + OBJECT_NAME(os.object_id) - END AS Details, - ''Look through your source code to find the object creating these objects, and tune the creation and population to reduce fetches. See the URL for details.'' AS HowToStopIt - FROM tempdb.sys.dm_db_index_operational_stats(DB_ID(''tempdb''), NULL, NULL, NULL) os - LEFT OUTER JOIN #TempdbOperationalStats os_prior ON os.object_id = os_prior.object_id - AND os.forwarded_fetch_count > os_prior.forwarded_fetch_count - WHERE os.database_id = DB_ID(''tempdb'') - AND os.forwarded_fetch_count - COALESCE(os_prior.forwarded_fetch_count,0) > 100 - ORDER BY os.forwarded_fetch_count DESC;' + IF @Debug = 1 BEGIN PRINT @StringToExecute; END; + EXEC sys.sp_executesql + @StringToExecute; - EXECUTE sp_executesql @StringToExecute; - END + END; - /* In-Memory OLTP - Garbage Collection in Progress - CheckID 31 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 31',10,1) WITH NOWAIT; - END + UPDATE + dp + SET + dp.client_app = + CASE + WHEN dp.client_app LIKE N'SQLAgent - %' + THEN N'SQLAgent - Job: ' + + aj.job_name + + N' Step: ' + + aj.step_name + ELSE dp.client_app + END + FROM #deadlock_process AS dp + JOIN #agent_job AS aj + ON dp.event_date = aj.event_date + AND dp.victim_id = aj.victim_id + AND dp.id = aj.id + OPTION(RECOMPILE); - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) - SELECT 31 AS CheckID, - 50 AS Priority, - 'In-Memory OLTP' AS FindingGroup, - 'Garbage Collection in Progress' AS Finding, - 'https://www.brentozar.com/go/garbage/' AS URL, - CAST(ps.value_delta AS NVARCHAR(50)) + ' rows processed (from SQL Server YYYY XTP Garbage Collection:Rows processed/sec counter)' + @LineFeed - + 'This can happen due to memory pressure (causing In-Memory OLTP to shrink its footprint) or' + @LineFeed - + 'due to transactional workloads that constantly insert/delete data.' AS Details, - 'Sadly, you cannot choose when garbage collection occurs. This is one of the many gotchas of Hekaton. Learn more: http://nedotter.com/archive/2016/04/row-version-lifecycle-for-in-memory-oltp/' AS HowToStopIt - FROM #PerfmonStats ps - INNER JOIN #PerfmonStats psComp ON psComp.Pass = 2 AND psComp.object_name LIKE '%XTP Garbage Collection' AND psComp.counter_name = 'Rows processed/sec' AND psComp.value_delta > 100 - WHERE ps.Pass = 2 - AND ps.object_name LIKE '%XTP Garbage Collection' - AND ps.counter_name = 'Rows processed/sec' - AND ps.value_delta > (100 * @Seconds); /* Ignore servers sitting idle */ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - /* In-Memory OLTP - Transactions Aborted - CheckID 32 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 32',10,1) WITH NOWAIT; - END + /*Get each and every table of all databases*/ + IF @Azure = 0 + BEGIN + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Inserting to @sysAssObjId %s', 0, 1, @d) WITH NOWAIT; - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) - SELECT 32 AS CheckID, - 100 AS Priority, - 'In-Memory OLTP' AS FindingGroup, - 'Transactions Aborted' AS Finding, - 'https://www.brentozar.com/go/aborted/' AS URL, - CAST(ps.value_delta AS NVARCHAR(50)) + ' transactions aborted (from SQL Server YYYY XTP Transactions:Transactions aborted/sec counter)' + @LineFeed - + 'This may indicate that data is changing, or causing folks to retry their transactions, thereby increasing load.' AS Details, - 'Dig into your In-Memory OLTP transactions to figure out which ones are failing and being retried.' AS HowToStopIt - FROM #PerfmonStats ps - INNER JOIN #PerfmonStats psComp ON psComp.Pass = 2 AND psComp.object_name LIKE '%XTP Transactions' AND psComp.counter_name = 'Transactions aborted/sec' AND psComp.value_delta > 100 - WHERE ps.Pass = 2 - AND ps.object_name LIKE '%XTP Transactions' - AND ps.counter_name = 'Transactions aborted/sec' - AND ps.value_delta > (10 * @Seconds); /* Ignore servers sitting idle */ + INSERT INTO + @sysAssObjId + ( + database_id, + partition_id, + schema_name, + table_name + ) + EXECUTE sys.sp_MSforeachdb + N' + SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - /* Query Problems - Suboptimal Plans/Sec High - CheckID 33 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 33',10,1) WITH NOWAIT; - END + USE [?]; - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) - SELECT 32 AS CheckID, - 100 AS Priority, - 'Query Problems' AS FindingGroup, - 'Suboptimal Plans/Sec High' AS Finding, - 'https://www.brentozar.com/go/suboptimal/' AS URL, - CAST(ps.value_delta AS NVARCHAR(50)) + ' plans reported in the ' + CAST(ps.instance_name AS NVARCHAR(100)) + ' workload group (from Workload GroupStats:Suboptimal plans/sec counter)' + @LineFeed - + 'Even if you are not using Resource Governor, it still tracks information about user queries, memory grants, etc.' AS Details, - 'Check out sp_BlitzCache to get more information about recent queries, or try sp_BlitzWho to see currently running queries.' AS HowToStopIt - FROM #PerfmonStats ps - INNER JOIN #PerfmonStats psComp ON psComp.Pass = 2 AND psComp.object_name = @ServiceName + ':Workload GroupStats' AND psComp.counter_name = 'Suboptimal plans/sec' AND psComp.value_delta > 100 - WHERE ps.Pass = 2 - AND ps.object_name = @ServiceName + ':Workload GroupStats' - AND ps.counter_name = 'Suboptimal plans/sec' - AND ps.value_delta > (10 * @Seconds); /* Ignore servers sitting idle */ + IF EXISTS + ( + SELECT + 1/0 + FROM #deadlock_process AS dp + WHERE dp.database_id = DB_ID() + ) + BEGIN + SELECT + database_id = + DB_ID(), + p.partition_id, + schema_name = + s.name, + table_name = + t.name + FROM sys.partitions p + JOIN sys.tables t + ON t.object_id = p.object_id + JOIN sys.schemas s + ON s.schema_id = t.schema_id + WHERE s.name IS NOT NULL + AND t.name IS NOT NULL + OPTION(RECOMPILE); + END; + '; - /* Azure Performance - Database is Maxed Out - CheckID 41 */ - IF SERVERPROPERTY('EngineEdition') = 5 /*SERVERPROPERTY('Edition') = 'SQL Azure'*/ - BEGIN - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 41',10,1) WITH NOWAIT; - END + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + END; - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) - SELECT 41 AS CheckID, - 10 AS Priority, - 'Azure Performance' AS FindingGroup, - 'Database is Maxed Out' AS Finding, - 'https://www.brentozar.com/go/maxedout' AS URL, - N'At ' + CONVERT(NVARCHAR(100), s.end_time ,121) + N', your database approached (or hit) your DTU limits:' + @LineFeed - + N'Average CPU percent: ' + CAST(avg_cpu_percent AS NVARCHAR(50)) + @LineFeed - + N'Average data IO percent: ' + CAST(avg_data_io_percent AS NVARCHAR(50)) + @LineFeed - + N'Average log write percent: ' + CAST(avg_log_write_percent AS NVARCHAR(50)) + @LineFeed - + N'Max worker percent: ' + CAST(max_worker_percent AS NVARCHAR(50)) + @LineFeed - + N'Max session percent: ' + CAST(max_session_percent AS NVARCHAR(50)) AS Details, - 'Tune your queries or indexes with sp_BlitzCache or sp_BlitzIndex, or consider upgrading to a higher DTU level.' AS HowToStopIt - FROM sys.dm_db_resource_stats s - WHERE s.end_time >= DATEADD(MI, -5, GETDATE()) - AND (avg_cpu_percent > 90 - OR avg_data_io_percent >= 90 - OR avg_log_write_percent >=90 - OR max_worker_percent >= 90 - OR max_session_percent >= 90); - END + IF @Azure = 1 + BEGIN + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Inserting to @sysAssObjId at %s', 0, 1, @d) WITH NOWAIT; + + INSERT INTO + @sysAssObjId + ( + database_id, + partition_id, + schema_name, + table_name + ) + SELECT + database_id = + DB_ID(), + p.partition_id, + schema_name = + s.name, + table_name = + t.name + FROM sys.partitions p + JOIN sys.tables t + ON t.object_id = p.object_id + JOIN sys.schemas s + ON s.schema_id = t.schema_id + WHERE s.name IS NOT NULL + AND t.name IS NOT NULL + OPTION(RECOMPILE); - /* Server Info - Batch Requests per Sec - CheckID 19 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 19',10,1) WITH NOWAIT; - END + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + END; - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, DetailsInt) - SELECT 19 AS CheckID, - 250 AS Priority, - 'Server Info' AS FindingGroup, - 'Batch Requests per Sec' AS Finding, - 'https://www.brentozar.com/go/measure' AS URL, - CAST(CAST(ps.value_delta AS MONEY) / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS NVARCHAR(20)) AS Details, - ps.value_delta / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS DetailsInt - FROM #PerfmonStats ps - INNER JOIN #PerfmonStats ps1 ON ps.object_name = ps1.object_name AND ps.counter_name = ps1.counter_name AND ps1.Pass = 1 - WHERE ps.Pass = 2 - AND ps.object_name = @ServiceName + ':SQL Statistics' - AND ps.counter_name = 'Batch Requests/sec'; + /*Begin checks based on parsed values*/ + /* + First, revert these back since we already converted the event data to local time, + and searches will break if we use the times converted over to UTC for the event data + */ + SELECT + @StartDate = @StartDateOriginal, + @EndDate = @EndDateOriginal; - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','SQL Compilations/sec', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','SQL Re-Compilations/sec', NULL); + /*Check 1 is deadlocks by database*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Check 1 database deadlocks %s', 0, 1, @d) WITH NOWAIT; - /* Server Info - SQL Compilations/sec - CheckID 25 */ - IF @ExpertMode = 1 - BEGIN - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 25',10,1) WITH NOWAIT; - END + INSERT + #deadlock_findings WITH(TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding, + sort_order + ) + SELECT + check_id = 1, + dp.database_name, + object_name = N'-', + finding_group = N'Total Database Deadlocks', + finding = + N'This database had ' + + CONVERT + ( + nvarchar(20), + COUNT_BIG(DISTINCT dp.event_date) + ) + + N' deadlocks.', + sort_order = + ROW_NUMBER() + OVER (ORDER BY COUNT_BIG(DISTINCT dp.event_date) DESC) + FROM #deadlock_process AS dp + WHERE 1 = 1 + AND (dp.database_name = @DatabaseName OR @DatabaseName IS NULL) + AND (dp.event_date >= @StartDate OR @StartDate IS NULL) + AND (dp.event_date < @EndDate OR @EndDate IS NULL) + AND (dp.client_app = @AppName OR @AppName IS NULL) + AND (dp.host_name = @HostName OR @HostName IS NULL) + AND (dp.login_name = @LoginName OR @LoginName IS NULL) + GROUP BY dp.database_name + OPTION(RECOMPILE); - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, DetailsInt) - SELECT 25 AS CheckID, - 250 AS Priority, - 'Server Info' AS FindingGroup, - 'SQL Compilations per Sec' AS Finding, - 'https://www.brentozar.com/go/measure' AS URL, - CAST(ps.value_delta / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS NVARCHAR(20)) AS Details, - ps.value_delta / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS DetailsInt - FROM #PerfmonStats ps - INNER JOIN #PerfmonStats ps1 ON ps.object_name = ps1.object_name AND ps.counter_name = ps1.counter_name AND ps1.Pass = 1 - WHERE ps.Pass = 2 - AND ps.object_name = @ServiceName + ':SQL Statistics' - AND ps.counter_name = 'SQL Compilations/sec'; - END + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - /* Server Info - SQL Re-Compilations/sec - CheckID 26 */ - IF @ExpertMode = 1 - BEGIN - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 26',10,1) WITH NOWAIT; - END + /*Check 2 is deadlocks with selects*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Check 2 select deadlocks %s', 0, 1, @d) WITH NOWAIT; - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, DetailsInt) - SELECT 26 AS CheckID, - 250 AS Priority, - 'Server Info' AS FindingGroup, - 'SQL Re-Compilations per Sec' AS Finding, - 'https://www.brentozar.com/go/measure' AS URL, - CAST(ps.value_delta / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS NVARCHAR(20)) AS Details, - ps.value_delta / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS DetailsInt - FROM #PerfmonStats ps - INNER JOIN #PerfmonStats ps1 ON ps.object_name = ps1.object_name AND ps.counter_name = ps1.counter_name AND ps1.Pass = 1 - WHERE ps.Pass = 2 - AND ps.object_name = @ServiceName + ':SQL Statistics' - AND ps.counter_name = 'SQL Re-Compilations/sec'; - END + INSERT + #deadlock_findings WITH(TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding, + sort_order + ) + SELECT + check_id = 2, + dow.database_name, + object_name = + CASE + WHEN EXISTS + ( + SELECT + 1/0 + FROM sys.databases AS d + WHERE d.name COLLATE DATABASE_DEFAULT = dow.database_name COLLATE DATABASE_DEFAULT + AND d.is_read_committed_snapshot_on = 1 + ) + THEN N'You already enabled RCSI, but...' + ELSE N'You Might Need RCSI' + END, + finding_group = N'Total Deadlocks Involving Selects', + finding = + N'There have been ' + + CONVERT + ( + nvarchar(20), + COUNT_BIG(DISTINCT dow.event_date) + ) + + N' deadlock(s) between read queries and modification queries.', + sort_order = + ROW_NUMBER() + OVER (ORDER BY COUNT_BIG(DISTINCT dow.event_date) DESC) + FROM #deadlock_owner_waiter AS dow + WHERE 1 = 1 + AND dow.lock_mode IN + ( + N'S', + N'IS' + ) + OR dow.owner_mode IN + ( + N'S', + N'IS' + ) + OR dow.waiter_mode IN + ( + N'S', + N'IS' + ) + AND (dow.database_id = @DatabaseId OR @DatabaseName IS NULL) + AND (dow.event_date >= @StartDate OR @StartDate IS NULL) + AND (dow.event_date < @EndDate OR @EndDate IS NULL) + AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) + GROUP BY + dow.database_name + OPTION(RECOMPILE); - /* Server Info - Wait Time per Core per Sec - CheckID 20 */ - IF @Seconds > 0 - BEGIN; - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 20',10,1) WITH NOWAIT; - END; + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - WITH waits1(SampleTime, waits_ms) AS (SELECT SampleTime, SUM(ws1.wait_time_ms) FROM #WaitStats ws1 WHERE ws1.Pass = 1 GROUP BY SampleTime), - waits2(SampleTime, waits_ms) AS (SELECT SampleTime, SUM(ws2.wait_time_ms) FROM #WaitStats ws2 WHERE ws2.Pass = 2 GROUP BY SampleTime), - cores(cpu_count) AS (SELECT SUM(1) FROM sys.dm_os_schedulers WHERE status = 'VISIBLE ONLINE' AND is_online = 1) - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, DetailsInt) - SELECT 20 AS CheckID, - 250 AS Priority, - 'Server Info' AS FindingGroup, - 'Wait Time per Core per Sec' AS Finding, - 'https://www.brentozar.com/go/measure' AS URL, - CAST((CAST(waits2.waits_ms - waits1.waits_ms AS MONEY)) / 1000 / i.cpu_count / ISNULL(NULLIF(DATEDIFF(ss, waits1.SampleTime, waits2.SampleTime), 0), 1) AS NVARCHAR(20)) AS Details, - (waits2.waits_ms - waits1.waits_ms) / 1000 / i.cpu_count / ISNULL(NULLIF(DATEDIFF(ss, waits1.SampleTime, waits2.SampleTime), 0), 1) AS DetailsInt - FROM cores i - CROSS JOIN waits1 - CROSS JOIN waits2; - END; + /*Check 3 is deadlocks by object*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Check 3 object deadlocks %s', 0, 1, @d) WITH NOWAIT; - IF @Seconds > 0 - BEGIN - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) - SELECT - 47 AS CheckId, - 50 AS Priority, - 'Query Problems' AS FindingsGroup, - 'High Percentage Of Runnable Queries' AS Finding, - 'https://erikdarlingdata.com/go/RunnableQueue/' AS URL, - 'On the ' - + CASE WHEN y.pass = 1 - THEN '1st' - ELSE '2nd' - END - + ' pass, ' - + RTRIM(y.runnable_pct) - + '% of your queries were waiting to get on a CPU to run. ' - + ' This can indicate CPU pressure.' - FROM - ( - SELECT - 2 AS pass, - x.total, - x.runnable, - CONVERT(decimal(5,2), + INSERT + #deadlock_findings WITH(TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding, + sort_order + ) + SELECT + check_id = 3, + dow.database_name, + object_name = + ISNULL ( - x.runnable / - (1. * NULLIF(x.total, 0)) - ) - ) * 100. AS runnable_pct - FROM - ( - SELECT - COUNT_BIG(*) AS total, - SUM(CASE WHEN status = 'runnable' - THEN 1 - ELSE 0 - END) AS runnable - FROM sys.dm_exec_requests - WHERE session_id > 50 - ) AS x - ) AS y - WHERE y.runnable_pct > 20.; - END + dow.object_name, + N'UNKNOWN' + ), + finding_group = N'Total Object Deadlocks', + finding = + N'This object was involved in ' + + CONVERT + ( + nvarchar(20), + COUNT_BIG(DISTINCT dow.event_date) + ) + + N' deadlock(s).', + sort_order = + ROW_NUMBER() + OVER (ORDER BY COUNT_BIG(DISTINCT dow.event_date) DESC) + FROM #deadlock_owner_waiter AS dow + WHERE 1 = 1 + AND (dow.database_id = @DatabaseId OR @DatabaseName IS NULL) + AND (dow.event_date >= @StartDate OR @StartDate IS NULL) + AND (dow.event_date < @EndDate OR @EndDate IS NULL) + AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) + GROUP BY + dow.database_name, + dow.object_name + OPTION(RECOMPILE); - /* If we're waiting 30+ seconds, run these checks at the end. - We get this data from the ring buffers, and it's only updated once per minute, so might - as well get it now - whereas if we're checking 30+ seconds, it might get updated by the - end of our sp_BlitzFirst session. */ - IF @Seconds >= 30 - BEGIN - /* Server Performance - High CPU Utilization CheckID 24 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 24',10,1) WITH NOWAIT; - END + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) - SELECT 24, 50, 'Server Performance', 'High CPU Utilization', CAST(100 - SystemIdle AS NVARCHAR(20)) + N'%. Ring buffer details: ' + CAST(record AS NVARCHAR(4000)), 100 - SystemIdle, 'https://www.brentozar.com/go/cpu' - FROM ( - SELECT record, - record.value('(./Record/SchedulerMonitorEvent/SystemHealth/SystemIdle)[1]', 'int') AS SystemIdle - FROM ( - SELECT TOP 1 CONVERT(XML, record) AS record - FROM sys.dm_os_ring_buffers - WHERE ring_buffer_type = N'RING_BUFFER_SCHEDULER_MONITOR' - AND record LIKE '%%' - ORDER BY timestamp DESC) AS rb - ) AS y - WHERE 100 - SystemIdle >= 50; + /*Check 3 continuation, number of deadlocks per index*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Check 3 (continued) index deadlocks %s', 0, 1, @d) WITH NOWAIT; - /* Server Performance - CPU Utilization CheckID 23 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 23',10,1) WITH NOWAIT; - END + INSERT + #deadlock_findings WITH(TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding, + sort_order + ) + SELECT + check_id = 3, + dow.database_name, + index_name = dow.index_name, + finding_group = N'Total Index Deadlocks', + finding = + N'This index was involved in ' + + CONVERT + ( + nvarchar(20), + COUNT_BIG(DISTINCT dow.event_date) + ) + + N' deadlock(s).', + sort_order = + ROW_NUMBER() + OVER (ORDER BY COUNT_BIG(DISTINCT dow.event_date) DESC) + FROM #deadlock_owner_waiter AS dow + WHERE 1 = 1 + AND (dow.database_id = @DatabaseId OR @DatabaseName IS NULL) + AND (dow.event_date >= @StartDate OR @StartDate IS NULL) + AND (dow.event_date < @EndDate OR @EndDate IS NULL) + AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) + AND dow.lock_type NOT IN + ( + N'HEAP', + N'RID' + ) + AND dow.index_name IS NOT NULL + GROUP BY + dow.database_name, + dow.index_name + OPTION(RECOMPILE); - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) - SELECT 23, 250, 'Server Info', 'CPU Utilization', CAST(100 - SystemIdle AS NVARCHAR(20)) + N'%. Ring buffer details: ' + CAST(record AS NVARCHAR(4000)), 100 - SystemIdle, 'https://www.brentozar.com/go/cpu' - FROM ( - SELECT record, - record.value('(./Record/SchedulerMonitorEvent/SystemHealth/SystemIdle)[1]', 'int') AS SystemIdle - FROM ( - SELECT TOP 1 CONVERT(XML, record) AS record - FROM sys.dm_os_ring_buffers - WHERE ring_buffer_type = N'RING_BUFFER_SCHEDULER_MONITOR' - AND record LIKE '%%' - ORDER BY timestamp DESC) AS rb - ) AS y; + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - END; /* IF @Seconds >= 30 */ + /*Check 3 continuation, number of deadlocks per heap*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Check 3 (continued) heap deadlocks %s', 0, 1, @d) WITH NOWAIT; - IF /* Let people on <2016 know about the thread time column */ - ( - @Seconds > 0 - AND @total_cpu_usage = 0 - ) - BEGIN - INSERT INTO - #BlitzFirstResults - ( - CheckID, - Priority, - FindingsGroup, - Finding, - Details, - URL - ) - SELECT - 48, - 254, - N'Informational', - N'Thread Time comes from the plan cache in versions earlier than 2016, and is not as reliable', - N'The oldest plan in your cache is from ' + - CONVERT(nvarchar(30), MIN(s.creation_time)) + - N' and your server was last restarted on ' + - CONVERT(nvarchar(30), MAX(o.sqlserver_start_time)), - N'https://docs.microsoft.com/en-us/sql/relational-databases/system-dynamic-management-views/sys-dm-os-schedulers-transact-sql' - FROM sys.dm_exec_query_stats AS s - CROSS JOIN sys.dm_os_sys_info AS o + INSERT + #deadlock_findings WITH(TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding, + sort_order + ) + SELECT + check_id = 3, + dow.database_name, + index_name = dow.index_name, + finding_group = N'Total Heap Deadlocks', + finding = + N'This heap was involved in ' + + CONVERT + ( + nvarchar(20), + COUNT_BIG(DISTINCT dow.event_date) + ) + + N' deadlock(s).', + sort_order = + ROW_NUMBER() + OVER (ORDER BY COUNT_BIG(DISTINCT dow.event_date) DESC) + FROM #deadlock_owner_waiter AS dow + WHERE 1 = 1 + AND (dow.database_id = @DatabaseId OR @DatabaseName IS NULL) + AND (dow.event_date >= @StartDate OR @StartDate IS NULL) + AND (dow.event_date < @EndDate OR @EndDate IS NULL) + AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) + AND dow.lock_type IN + ( + N'HEAP', + N'RID' + ) + GROUP BY + dow.database_name, + dow.index_name OPTION(RECOMPILE); - END /* Let people on <2016 know about the thread time column */ - /* If we didn't find anything, apologize. */ - IF NOT EXISTS (SELECT * FROM #BlitzFirstResults WHERE Priority < 250) - BEGIN + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - INSERT INTO #BlitzFirstResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - VALUES ( -1 , - 1 , - 'No Problems Found' , - 'From Your Community Volunteers' , - 'http://FirstResponderKit.org/' , - 'Try running our more in-depth checks with sp_Blitz, or there may not be an unusual SQL Server performance problem. ' - ); + /*Check 4 looks for Serializable deadlocks*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Check 4 serializable deadlocks %s', 0, 1, @d) WITH NOWAIT; + + INSERT + #deadlock_findings WITH(TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding, + sort_order + ) + SELECT + check_id = 4, + database_name = + dp.database_name, + object_name = N'-', + finding_group = N'Serializable Deadlocking', + finding = + N'This database has had ' + + CONVERT + ( + nvarchar(20), + COUNT_BIG(DISTINCT dp.event_date) + ) + + N' instances of Serializable deadlocks.', + sort_order = + ROW_NUMBER() + OVER (ORDER BY COUNT_BIG(DISTINCT dp.event_date) DESC) + FROM #deadlock_process AS dp + WHERE dp.isolation_level LIKE N'serializable%' + AND (dp.database_name = @DatabaseName OR @DatabaseName IS NULL) + AND (dp.event_date >= @StartDate OR @StartDate IS NULL) + AND (dp.event_date < @EndDate OR @EndDate IS NULL) + AND (dp.client_app = @AppName OR @AppName IS NULL) + AND (dp.host_name = @HostName OR @HostName IS NULL) + AND (dp.login_name = @LoginName OR @LoginName IS NULL) + GROUP BY dp.database_name + OPTION(RECOMPILE); - END; /*IF NOT EXISTS (SELECT * FROM #BlitzFirstResults) */ + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - /* Add credits for the nice folks who put so much time into building and maintaining this for free: */ - INSERT INTO #BlitzFirstResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - VALUES ( -1 , - 255 , - 'Thanks!' , - 'From Your Community Volunteers' , - 'http://FirstResponderKit.org/' , - 'To get help or add your own contributions, join us at http://FirstResponderKit.org.' - ); + /*Check 5 looks for Repeatable Read deadlocks*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Check 5 repeatable read deadlocks %s', 0, 1, @d) WITH NOWAIT; - INSERT INTO #BlitzFirstResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details + INSERT + #deadlock_findings WITH(TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding, + sort_order + ) + SELECT + check_id = 5, + dp.database_name, + object_name = N'-', + finding_group = N'Repeatable Read Deadlocking', + finding = + N'This database has had ' + + CONVERT + ( + nvarchar(20), + COUNT_BIG(DISTINCT dp.event_date) + ) + + N' instances of Repeatable Read deadlocks.', + sort_order = + ROW_NUMBER() + OVER (ORDER BY COUNT_BIG(DISTINCT dp.event_date) DESC) + FROM #deadlock_process AS dp + WHERE dp.isolation_level LIKE N'repeatable%' + AND (dp.database_name = @DatabaseName OR @DatabaseName IS NULL) + AND (dp.event_date >= @StartDate OR @StartDate IS NULL) + AND (dp.event_date < @EndDate OR @EndDate IS NULL) + AND (dp.client_app = @AppName OR @AppName IS NULL) + AND (dp.host_name = @HostName OR @HostName IS NULL) + AND (dp.login_name = @LoginName OR @LoginName IS NULL) + GROUP BY dp.database_name + OPTION(RECOMPILE); - ) - VALUES ( -1 , - 0 , - 'sp_BlitzFirst ' + CAST(CONVERT(DATETIMEOFFSET, @VersionDate, 102) AS VARCHAR(100)), - 'From Your Community Volunteers' , - 'http://FirstResponderKit.org/' , - 'We hope you found this tool useful.' - ); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - /* Outdated sp_BlitzFirst - sp_BlitzFirst is Over 6 Months Old - CheckID 27 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 27',10,1) WITH NOWAIT; - END + /*Check 6 breaks down app, host, and login information*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Check 6 app/host/login deadlocks %s', 0, 1, @d) WITH NOWAIT; - IF DATEDIFF(MM, @VersionDate, SYSDATETIMEOFFSET()) > 6 - BEGIN - INSERT INTO #BlitzFirstResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 27 AS CheckID , - 0 AS Priority , - 'Outdated sp_BlitzFirst' AS FindingsGroup , - 'sp_BlitzFirst is Over 6 Months Old' AS Finding , - 'http://FirstResponderKit.org/' AS URL , - 'Some things get better with age, like fine wine and your T-SQL. However, sp_BlitzFirst is not one of those things - time to go download the current one.' AS Details; - END; + INSERT + #deadlock_findings WITH(TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding, + sort_order + ) + SELECT + check_id = 6, + database_name = + dp.database_name, + object_name = N'-', + finding_group = N'Login, App, and Host deadlocks', + finding = + N'This database has had ' + + CONVERT + ( + nvarchar(20), + COUNT_BIG(DISTINCT dp.event_date) + ) + + N' instances of deadlocks involving the login ' + + ISNULL + ( + dp.login_name, + N'UNKNOWN' + ) + + N' from the application ' + + ISNULL + ( + dp.client_app, + N'UNKNOWN' + ) + + N' on host ' + + ISNULL + ( + dp.host_name, + N'UNKNOWN' + ) + + N'.', + sort_order = + ROW_NUMBER() + OVER (ORDER BY COUNT_BIG(DISTINCT dp.event_date) DESC) + FROM #deadlock_process AS dp + WHERE 1 = 1 + AND (dp.database_name = @DatabaseName OR @DatabaseName IS NULL) + AND (dp.event_date >= @StartDate OR @StartDate IS NULL) + AND (dp.event_date < @EndDate OR @EndDate IS NULL) + AND (dp.client_app = @AppName OR @AppName IS NULL) + AND (dp.host_name = @HostName OR @HostName IS NULL) + AND (dp.login_name = @LoginName OR @LoginName IS NULL) + GROUP BY + dp.database_name, + dp.login_name, + dp.client_app, + dp.host_name + OPTION(RECOMPILE); - IF @CheckServerInfo = 0 /* Github #1680 */ - BEGIN - DELETE #BlitzFirstResults - WHERE FindingsGroup = 'Server Info'; - END + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + + /*Check 7 breaks down the types of deadlocks (object, page, key, etc.)*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Check 7 types of deadlocks %s', 0, 1, @d) WITH NOWAIT; + + WITH + lock_types AS + ( + SELECT + database_name = + dp.database_name, + dow.object_name, + lock = + CASE + WHEN CHARINDEX(N':', dp.wait_resource) > 0 + THEN SUBSTRING + ( + dp.wait_resource, + 1, + CHARINDEX(N':', dp.wait_resource) - 1 + ) + ELSE dp.wait_resource + END, + lock_count = + CONVERT + ( + nvarchar(20), + COUNT_BIG(DISTINCT dp.event_date) + ) + FROM #deadlock_process AS dp + JOIN #deadlock_owner_waiter AS dow + ON (dp.id = dow.owner_id + OR dp.victim_id = dow.waiter_id) + AND dp.event_date = dow.event_date + WHERE 1 = 1 + AND (dp.database_name = @DatabaseName OR @DatabaseName IS NULL) + AND (dp.event_date >= @StartDate OR @StartDate IS NULL) + AND (dp.event_date < @EndDate OR @EndDate IS NULL) + AND (dp.client_app = @AppName OR @AppName IS NULL) + AND (dp.host_name = @HostName OR @HostName IS NULL) + AND (dp.login_name = @LoginName OR @LoginName IS NULL) + AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) + AND dow.object_name IS NOT NULL + GROUP BY + dp.database_name, + CASE + WHEN CHARINDEX(N':', dp.wait_resource) > 0 + THEN SUBSTRING + ( + dp.wait_resource, + 1, + CHARINDEX(N':', dp.wait_resource) - 1 + ) + ELSE dp.wait_resource + END, + dow.object_name + ) + INSERT + #deadlock_findings WITH(TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding, + sort_order + ) + SELECT + check_id = 7, + lt.database_name, + lt.object_name, + finding_group = N'Types of locks by object', + finding = + N'This object has had ' + + STUFF + ( + ( + SELECT + N', ' + + lt2.lock_count + + N' ' + + lt2.lock + FROM lock_types AS lt2 + WHERE lt2.database_name = lt.database_name + AND lt2.object_name = lt.object_name + FOR XML + PATH(N''), + TYPE + ).value(N'.[1]', N'nvarchar(MAX)'), + 1, + 1, + N'' + ) + N' locks.', + sort_order = + ROW_NUMBER() + OVER (ORDER BY CONVERT(bigint, lt.lock_count) DESC) + FROM lock_types AS lt + OPTION(RECOMPILE); - RAISERROR('Analysis finished, outputting results',10,1) WITH NOWAIT; + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + /*Check 8 gives you more info queries for sp_BlitzCache & BlitzQueryStore*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Check 8 more info part 1 BlitzCache %s', 0, 1, @d) WITH NOWAIT; - /* If they want to run sp_BlitzCache and export to table, go for it. */ - IF @OutputTableNameBlitzCache IS NOT NULL - AND @OutputDatabaseName IS NOT NULL - AND @OutputSchemaName IS NOT NULL - AND EXISTS ( SELECT * - FROM sys.databases - WHERE QUOTENAME([name]) = @OutputDatabaseName) - BEGIN + WITH + deadlock_stack AS + ( + SELECT DISTINCT + ds.id, + ds.event_date, + ds.proc_name, + database_name = + PARSENAME(ds.proc_name, 3), + schema_name = + PARSENAME(ds.proc_name, 2), + proc_only_name = + PARSENAME(ds.proc_name, 1), + sql_handle_csv = + N'''' + + STUFF + ( + ( + SELECT DISTINCT + N',' + + ds2.sql_handle + FROM #deadlock_stack AS ds2 + WHERE ds2.id = ds.id + AND ds2.event_date = ds.event_date + AND ds2.sql_handle <> 0x + FOR XML + PATH(N''), + TYPE + ).value(N'.[1]', N'nvarchar(MAX)'), + 1, + 1, + N'' + ) + + N'''' + FROM #deadlock_stack AS ds + WHERE ds.sql_handle <> 0x + GROUP BY + PARSENAME(ds.proc_name, 3), + PARSENAME(ds.proc_name, 2), + PARSENAME(ds.proc_name, 1), + ds.proc_name, + ds.id, + ds.event_date + ) + INSERT + #deadlock_findings WITH(TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding + ) + SELECT DISTINCT + check_id = 8, + dow.database_name, + object_name = ds.proc_name, + finding_group = N'More Info - Query', + finding = N'EXEC sp_BlitzCache ' + + CASE + WHEN ds.proc_name = N'adhoc' + THEN N'@OnlySqlHandles = ' + ds.sql_handle_csv + ELSE N'@StoredProcName = ' + QUOTENAME(ds.proc_only_name, N'''') + END + N';' + FROM deadlock_stack AS ds + JOIN #deadlock_owner_waiter AS dow + ON dow.owner_id = ds.id + AND dow.event_date = ds.event_date + WHERE 1 = 1 + AND (dow.database_id = @DatabaseId OR @DatabaseName IS NULL) + AND (dow.event_date >= @StartDate OR @StartDate IS NULL) + AND (dow.event_date < @EndDate OR @EndDate IS NULL) + AND (dow.object_name = @StoredProcName OR @StoredProcName IS NULL) + AND ds.proc_name NOT LIKE 'Unknown%' + OPTION(RECOMPILE); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - RAISERROR('Calling sp_BlitzCache',10,1) WITH NOWAIT; + IF (@ProductVersionMajor >= 13 OR @Azure = 1) + BEGIN + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Check 8 more info part 2 BlitzQueryStore %s', 0, 1, @d) WITH NOWAIT; + WITH + deadlock_stack AS + ( + SELECT DISTINCT + ds.id, + ds.sql_handle, + ds.proc_name, + ds.event_date, + database_name = + PARSENAME(ds.proc_name, 3), + schema_name = + PARSENAME(ds.proc_name, 2), + proc_only_name = + PARSENAME(ds.proc_name, 1) + FROM #deadlock_stack AS ds + ) + INSERT + #deadlock_findings WITH(TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding + ) + SELECT DISTINCT + check_id = 8, + dow.database_name, + object_name = ds.proc_name, + finding_group = N'More Info - Query', + finding = + N'EXEC sp_BlitzQueryStore ' + + N'@DatabaseName = ' + + QUOTENAME(ds.database_name, N'''') + + N', ' + + N'@StoredProcName = ' + + QUOTENAME(ds.proc_only_name, N'''') + + N';' + FROM deadlock_stack AS ds + JOIN #deadlock_owner_waiter AS dow + ON dow.owner_id = ds.id + AND dow.event_date = ds.event_date + WHERE ds.proc_name <> N'adhoc' + AND (dow.database_id = @DatabaseId OR @DatabaseName IS NULL) + AND (dow.event_date >= @StartDate OR @StartDate IS NULL) + AND (dow.event_date < @EndDate OR @EndDate IS NULL) + AND (dow.object_name = @StoredProcName OR @StoredProcName IS NULL) + OPTION(RECOMPILE); - /* If they have an newer version of sp_BlitzCache that supports @MinutesBack and @CheckDateOverride */ - IF EXISTS (SELECT * FROM sys.objects o - INNER JOIN sys.parameters pMB ON o.object_id = pMB.object_id AND pMB.name = '@MinutesBack' - INNER JOIN sys.parameters pCDO ON o.object_id = pCDO.object_id AND pCDO.name = '@CheckDateOverride' - WHERE o.name = 'sp_BlitzCache') - BEGIN - /* Get the most recent sp_BlitzCache execution before this one - don't use sp_BlitzFirst because user logs are added in there at any time */ - SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''' - + @OutputSchemaName + ''' AND QUOTENAME(TABLE_NAME) = ''' - + QUOTENAME(@OutputTableNameBlitzCache) + ''') SELECT TOP 1 @BlitzCacheMinutesBack = DATEDIFF(MI,CheckDate,SYSDATETIMEOFFSET()) FROM ' - + @OutputDatabaseName + '.' - + @OutputSchemaName + '.' - + QUOTENAME(@OutputTableNameBlitzCache) - + ' WHERE ServerName = ''' + CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128)) + ''' ORDER BY CheckDate DESC;'; - EXEC sp_executesql @StringToExecute, N'@BlitzCacheMinutesBack INT OUTPUT', @BlitzCacheMinutesBack OUTPUT; + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + END; - /* If there's no data, let's just analyze the last 15 minutes of the plan cache */ - IF @BlitzCacheMinutesBack IS NULL OR @BlitzCacheMinutesBack < 1 OR @BlitzCacheMinutesBack > 60 - SET @BlitzCacheMinutesBack = 15; + /*Check 9 gives you stored procedure deadlock counts*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Check 9 stored procedure deadlocks %s', 0, 1, @d) WITH NOWAIT; - IF(@OutputType = 'NONE') - BEGIN - EXEC sp_BlitzCache - @OutputDatabaseName = @UnquotedOutputDatabaseName, - @OutputSchemaName = @UnquotedOutputSchemaName, - @OutputTableName = @OutputTableNameBlitzCache, - @CheckDateOverride = @StartSampleTime, - @SortOrder = 'all', - @SkipAnalysis = @BlitzCacheSkipAnalysis, - @MinutesBack = @BlitzCacheMinutesBack, - @Debug = @Debug, - @OutputType = @OutputType - ; - END; - ELSE - BEGIN - - EXEC sp_BlitzCache - @OutputDatabaseName = @UnquotedOutputDatabaseName, - @OutputSchemaName = @UnquotedOutputSchemaName, - @OutputTableName = @OutputTableNameBlitzCache, - @CheckDateOverride = @StartSampleTime, - @SortOrder = 'all', - @SkipAnalysis = @BlitzCacheSkipAnalysis, - @MinutesBack = @BlitzCacheMinutesBack, - @Debug = @Debug - ; - END; + INSERT + #deadlock_findings WITH(TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding, + sort_order + ) + SELECT + check_id = 9, + database_name = + dp.database_name, + object_name = ds.proc_name, + finding_group = N'Stored Procedure Deadlocks', + finding = + N'The stored procedure ' + + PARSENAME(ds.proc_name, 2) + + N'.' + + PARSENAME(ds.proc_name, 1) + + N' has been involved in ' + + CONVERT + ( + nvarchar(10), + COUNT_BIG(DISTINCT ds.id) + ) + + N' deadlocks.', + sort_order = + ROW_NUMBER() + OVER (ORDER BY COUNT_BIG(DISTINCT ds.id) DESC) + FROM #deadlock_stack AS ds + JOIN #deadlock_process AS dp + ON dp.id = ds.id + AND ds.event_date = dp.event_date + WHERE ds.proc_name <> N'adhoc' + AND (ds.proc_name = @StoredProcName OR @StoredProcName IS NULL) + AND (dp.database_name = @DatabaseName OR @DatabaseName IS NULL) + AND (dp.event_date >= @StartDate OR @StartDate IS NULL) + AND (dp.event_date < @EndDate OR @EndDate IS NULL) + AND (dp.client_app = @AppName OR @AppName IS NULL) + AND (dp.host_name = @HostName OR @HostName IS NULL) + AND (dp.login_name = @LoginName OR @LoginName IS NULL) + GROUP BY + dp.database_name, + ds.proc_name + OPTION(RECOMPILE); - /* Delete history older than @OutputTableRetentionDays */ - SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' - + @OutputSchemaName + ''') DELETE ' - + @OutputDatabaseName + '.' - + @OutputSchemaName + '.' - + QUOTENAME(@OutputTableNameBlitzCache) - + ' WHERE ServerName = @SrvName AND CheckDate < @CheckDate;'; - EXEC sp_executesql @StringToExecute, - N'@SrvName NVARCHAR(128), @CheckDate date', - @LocalServerName, @OutputTableCleanupDate; + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + /*Check 10 gives you more info queries for sp_BlitzIndex */ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Check 10 more info, BlitzIndex %s', 0, 1, @d) WITH NOWAIT; - END; + WITH + bi AS + ( + SELECT DISTINCT + dow.object_name, + dow.database_name, + schema_name = s.schema_name, + table_name = s.table_name + FROM #deadlock_owner_waiter AS dow + JOIN @sysAssObjId AS s + ON s.database_id = dow.database_id + AND s.partition_id = dow.associatedObjectId + WHERE 1 = 1 + AND (dow.database_id = @DatabaseId OR @DatabaseName IS NULL) + AND (dow.event_date >= @StartDate OR @StartDate IS NULL) + AND (dow.event_date < @EndDate OR @EndDate IS NULL) + AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) + AND dow.object_name IS NOT NULL + ) + INSERT + #deadlock_findings WITH(TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding + ) + SELECT DISTINCT + check_id = 10, + bi.database_name, + bi.object_name, + finding_group = N'More Info - Table', + finding = + N'EXEC sp_BlitzIndex ' + + N'@DatabaseName = ' + + QUOTENAME(bi.database_name, N'''') + + N', @SchemaName = ' + + QUOTENAME(bi.schema_name, N'''') + + N', @TableName = ' + + QUOTENAME(bi.table_name, N'''') + + N';' + FROM bi + OPTION(RECOMPILE); - ELSE - BEGIN - /* No sp_BlitzCache found, or it's outdated - CheckID 36 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 36',10,1) WITH NOWAIT; - END + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - INSERT INTO #BlitzFirstResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 36 AS CheckID , - 0 AS Priority , - 'Outdated or Missing sp_BlitzCache' AS FindingsGroup , - 'Update Your sp_BlitzCache' AS Finding , - 'http://FirstResponderKit.org/' AS URL , - 'You passed in @OutputTableNameBlitzCache, but we need a newer version of sp_BlitzCache in master or the current database.' AS Details; - END; + /*Check 11 gets total deadlock wait time per object*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Check 11 deadlock wait time per object %s', 0, 1, @d) WITH NOWAIT; - RAISERROR('sp_BlitzCache Finished',10,1) WITH NOWAIT; + WITH + chopsuey AS + ( - END; /* End running sp_BlitzCache */ + SELECT + database_name = + dp.database_name, + dow.object_name, + wait_days = + CONVERT + ( + nvarchar(30), + ( + SUM + ( + CONVERT + ( + bigint, + dp.wait_time + ) + ) / 1000 / 86400 + ) + ), + wait_time_hms = + /*the more wait time you rack up the less accurate this gets, + it's either that or erroring out*/ + CASE + WHEN + SUM + ( + CONVERT + ( + bigint, + dp.wait_time + ) + )/1000 > 2147483647 + THEN + CONVERT + ( + nvarchar(30), + DATEADD + ( + MINUTE, + ( + ( + SUM + ( + CONVERT + ( + bigint, + dp.wait_time + ) + ) + )/ + 60000 + ), + 0 + ), + 14 + ) + WHEN + SUM + ( + CONVERT + ( + bigint, + dp.wait_time + ) + ) BETWEEN 2147483648 AND 2147483647000 + THEN + CONVERT + ( + nvarchar(30), + DATEADD + ( + SECOND, + ( + ( + SUM + ( + CONVERT + ( + bigint, + dp.wait_time + ) + ) + )/ + 1000 + ), + 0 + ), + 14 + ) + ELSE + CONVERT + ( + nvarchar(30), + DATEADD + ( + MILLISECOND, + ( + SUM + ( + CONVERT + ( + bigint, + dp.wait_time + ) + ) + ), + 0 + ), + 14 + ) + END, + total_waits = + SUM(CONVERT(bigint, dp.wait_time)) + FROM #deadlock_owner_waiter AS dow + JOIN #deadlock_process AS dp + ON (dp.id = dow.owner_id + OR dp.victim_id = dow.waiter_id) + AND dp.event_date = dow.event_date + WHERE 1 = 1 + AND (dp.database_name = @DatabaseName OR @DatabaseName IS NULL) + AND (dp.event_date >= @StartDate OR @StartDate IS NULL) + AND (dp.event_date < @EndDate OR @EndDate IS NULL) + AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) + AND (dp.client_app = @AppName OR @AppName IS NULL) + AND (dp.host_name = @HostName OR @HostName IS NULL) + AND (dp.login_name = @LoginName OR @LoginName IS NULL) + GROUP BY + dp.database_name, + dow.object_name + ) + INSERT + #deadlock_findings WITH(TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding, + sort_order + ) + SELECT + check_id = 11, + cs.database_name, + cs.object_name, + finding_group = N'Total object deadlock wait time', + finding = + N'This object has had ' + + CONVERT + ( + nvarchar(30), + cs.wait_days + ) + + N' ' + + CONVERT + ( + nvarchar(30), + cs.wait_time_hms, + 14 + ) + + N' [dd hh:mm:ss:ms] of deadlock wait time.', + sort_order = + ROW_NUMBER() + OVER (ORDER BY cs.total_waits DESC) + FROM chopsuey AS cs + WHERE cs.object_name IS NOT NULL + OPTION(RECOMPILE); - /* @OutputTableName lets us export the results to a permanent table */ - IF @OutputDatabaseName IS NOT NULL - AND @OutputSchemaName IS NOT NULL - AND @OutputTableName IS NOT NULL - AND @OutputTableName NOT LIKE '#%' - AND EXISTS ( SELECT * - FROM sys.databases - WHERE QUOTENAME([name]) = @OutputDatabaseName) - BEGIN - SET @StringToExecute = 'USE ' - + @OutputDatabaseName - + '; IF EXISTS(SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' - + @OutputSchemaName - + ''') AND NOT EXISTS (SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''' - + @OutputSchemaName + ''' AND QUOTENAME(TABLE_NAME) = ''' - + @OutputTableName + ''') CREATE TABLE ' - + @OutputSchemaName + '.' - + @OutputTableName - + ' (ID INT IDENTITY(1,1) NOT NULL, - ServerName NVARCHAR(128), - CheckDate DATETIMEOFFSET, - CheckID INT NOT NULL, - Priority TINYINT NOT NULL, - FindingsGroup VARCHAR(50) NOT NULL, - Finding VARCHAR(200) NOT NULL, - URL VARCHAR(200) NOT NULL, - Details NVARCHAR(4000) NULL, - HowToStopIt [XML] NULL, - QueryPlan [XML] NULL, - QueryText NVARCHAR(MAX) NULL, - StartTime DATETIMEOFFSET NULL, - LoginName NVARCHAR(128) NULL, - NTUserName NVARCHAR(128) NULL, - OriginalLoginName NVARCHAR(128) NULL, - ProgramName NVARCHAR(128) NULL, - HostName NVARCHAR(128) NULL, - DatabaseID INT NULL, - DatabaseName NVARCHAR(128) NULL, - OpenTransactionCount INT NULL, - DetailsInt INT NULL, - QueryHash BINARY(8) NULL, - JoinKey AS ServerName + CAST(CheckDate AS NVARCHAR(50)), - PRIMARY KEY CLUSTERED (ID ASC));'; + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - EXEC(@StringToExecute); + /*Check 12 gets total deadlock wait time per database*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Check 12 deadlock wait time per database %s', 0, 1, @d) WITH NOWAIT; - /* If the table doesn't have the new QueryHash column, add it. See Github #2162. */ - SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableName; - SET @StringToExecute = N'IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns - WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''QueryHash'') - ALTER TABLE ' + @ObjectFullName + N' ADD QueryHash BINARY(8) NULL;'; - EXEC(@StringToExecute); + WITH + wait_time AS + ( + SELECT + database_name = + dp.database_name, + total_wait_time_ms = + SUM + ( + CONVERT + ( + bigint, + dp.wait_time + ) + ) + FROM #deadlock_owner_waiter AS dow + JOIN #deadlock_process AS dp + ON (dp.id = dow.owner_id + OR dp.victim_id = dow.waiter_id) + AND dp.event_date = dow.event_date + WHERE 1 = 1 + AND (dp.database_name = @DatabaseName OR @DatabaseName IS NULL) + AND (dp.event_date >= @StartDate OR @StartDate IS NULL) + AND (dp.event_date < @EndDate OR @EndDate IS NULL) + AND (dp.client_app = @AppName OR @AppName IS NULL) + AND (dp.host_name = @HostName OR @HostName IS NULL) + AND (dp.login_name = @LoginName OR @LoginName IS NULL) + GROUP BY + dp.database_name + ) + INSERT + #deadlock_findings WITH(TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding, + sort_order + ) + SELECT + check_id = 12, + wt.database_name, + object_name = N'-', + finding_group = N'Total database deadlock wait time', + N'This database has had ' + + CONVERT + ( + nvarchar(30), + ( + SUM + ( + CONVERT + ( + bigint, + wt.total_wait_time_ms + ) + ) / 1000 / 86400 + ) + ) + + N' ' + + /*the more wait time you rack up the less accurate this gets, + it's either that or erroring out*/ + CASE + WHEN + SUM + ( + CONVERT + ( + bigint, + wt.total_wait_time_ms + ) + )/1000 > 2147483647 + THEN + CONVERT + ( + nvarchar(30), + DATEADD + ( + MINUTE, + ( + ( + SUM + ( + CONVERT + ( + bigint, + wt.total_wait_time_ms + ) + ) + )/ + 60000 + ), + 0 + ), + 14 + ) + WHEN + SUM + ( + CONVERT + ( + bigint, + wt.total_wait_time_ms + ) + ) BETWEEN 2147483648 AND 2147483647000 + THEN + CONVERT + ( + nvarchar(30), + DATEADD + ( + SECOND, + ( + ( + SUM + ( + CONVERT + ( + bigint, + wt.total_wait_time_ms + ) + ) + )/ + 1000 + ), + 0 + ), + 14 + ) + ELSE + CONVERT + ( + nvarchar(30), + DATEADD + ( + MILLISECOND, + ( + SUM + ( + CONVERT + ( + bigint, + wt.total_wait_time_ms + ) + ) + ), + 0 + ), + 14 + ) END + + N' [dd hh:mm:ss:ms] of deadlock wait time.', + sort_order = + ROW_NUMBER() + OVER (ORDER BY SUM(CONVERT(bigint, wt.total_wait_time_ms)) DESC) + FROM wait_time AS wt + GROUP BY + wt.database_name + OPTION(RECOMPILE); - /* If the table doesn't have the new JoinKey computed column, add it. See Github #2164. */ - SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableName; - SET @StringToExecute = N'IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns - WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''JoinKey'') - ALTER TABLE ' + @ObjectFullName + N' ADD JoinKey AS ServerName + CAST(CheckDate AS NVARCHAR(50));'; - EXEC(@StringToExecute); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' - + @OutputSchemaName + ''') INSERT ' - + @OutputDatabaseName + '.' - + @OutputSchemaName + '.' - + @OutputTableName - + ' (ServerName, CheckDate, CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, QueryText, StartTime, LoginName, NTUserName, OriginalLoginName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, DetailsInt, QueryHash) SELECT ' - + ' @SrvName, @CheckDate, CheckID, Priority, FindingsGroup, Finding, URL, LEFT(Details,4000), HowToStopIt, QueryPlan, QueryText, StartTime, LoginName, NTUserName, OriginalLoginName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, DetailsInt, QueryHash FROM #BlitzFirstResults ORDER BY Priority , FindingsGroup , Finding , Details'; - - EXEC sp_executesql @StringToExecute, - N'@SrvName NVARCHAR(128), @CheckDate datetimeoffset', - @LocalServerName, @StartSampleTime; + /*Check 13 gets total deadlock wait time for SQL Agent*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Check 13 deadlock count for SQL Agent %s', 0, 1, @d) WITH NOWAIT; - /* Delete history older than @OutputTableRetentionDays */ - SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' - + @OutputSchemaName + ''') DELETE ' - + @OutputDatabaseName + '.' - + @OutputSchemaName + '.' - + @OutputTableName - + ' WHERE ServerName = @SrvName AND CheckDate < @CheckDate ;'; - - EXEC sp_executesql @StringToExecute, - N'@SrvName NVARCHAR(128), @CheckDate date', - @LocalServerName, @OutputTableCleanupDate; + INSERT + #deadlock_findings WITH(TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding, + sort_order + ) + SELECT + check_id = 13, + database_name = + DB_NAME(aj.database_id), + object_name = + N'SQLAgent - Job: ' + + aj.job_name + + N' Step: ' + + aj.step_name, + finding_group = N'Agent Job Deadlocks', + finding = + N'There have been ' + + RTRIM(COUNT_BIG(DISTINCT aj.event_date)) + + N' deadlocks from this Agent Job and Step.', + sort_order = + ROW_NUMBER() + OVER (ORDER BY COUNT_BIG(DISTINCT aj.event_date) DESC) + FROM #agent_job AS aj + GROUP BY + DB_NAME(aj.database_id), + aj.job_name, + aj.step_name + OPTION(RECOMPILE); - END; - ELSE IF (SUBSTRING(@OutputTableName, 2, 2) = '##') - BEGIN - SET @StringToExecute = N' IF (OBJECT_ID(''tempdb..' - + @OutputTableName - + ''') IS NULL) CREATE TABLE ' - + @OutputTableName - + ' (ID INT IDENTITY(1,1) NOT NULL, - ServerName NVARCHAR(128), - CheckDate DATETIMEOFFSET, - CheckID INT NOT NULL, - Priority TINYINT NOT NULL, - FindingsGroup VARCHAR(50) NOT NULL, - Finding VARCHAR(200) NOT NULL, - URL VARCHAR(200) NOT NULL, - Details NVARCHAR(4000) NULL, - HowToStopIt [XML] NULL, - QueryPlan [XML] NULL, - QueryText NVARCHAR(MAX) NULL, - StartTime DATETIMEOFFSET NULL, - LoginName NVARCHAR(128) NULL, - NTUserName NVARCHAR(128) NULL, - OriginalLoginName NVARCHAR(128) NULL, - ProgramName NVARCHAR(128) NULL, - HostName NVARCHAR(128) NULL, - DatabaseID INT NULL, - DatabaseName NVARCHAR(128) NULL, - OpenTransactionCount INT NULL, - DetailsInt INT NULL, - QueryHash BINARY(8) NULL, - JoinKey AS ServerName + CAST(CheckDate AS NVARCHAR(50)), - PRIMARY KEY CLUSTERED (ID ASC));' - + ' INSERT ' - + @OutputTableName - + ' (ServerName, CheckDate, CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, QueryText, StartTime, LoginName, NTUserName, OriginalLoginName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, DetailsInt) SELECT ' - + ' @SrvName, @CheckDate, CheckID, Priority, FindingsGroup, Finding, URL, LEFT(Details,4000), HowToStopIt, QueryPlan, QueryText, StartTime, LoginName, NTUserName, OriginalLoginName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, DetailsInt FROM #BlitzFirstResults ORDER BY Priority , FindingsGroup , Finding , Details'; - - EXEC sp_executesql @StringToExecute, - N'@SrvName NVARCHAR(128), @CheckDate datetimeoffset', - @LocalServerName, @StartSampleTime; - END; - ELSE IF (SUBSTRING(@OutputTableName, 2, 1) = '#') - BEGIN - RAISERROR('Due to the nature of Dymamic SQL, only global (i.e. double pound (##)) temp tables are supported for @OutputTableName', 16, 0); - END; + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - /* @OutputTableNameFileStats lets us export the results to a permanent table */ - IF @OutputDatabaseName IS NOT NULL - AND @OutputSchemaName IS NOT NULL - AND @OutputTableNameFileStats IS NOT NULL - AND @OutputTableNameFileStats NOT LIKE '#%' - AND EXISTS ( SELECT * - FROM sys.databases - WHERE QUOTENAME([name]) = @OutputDatabaseName) - BEGIN - /* Create the table */ - SET @StringToExecute = 'USE ' - + @OutputDatabaseName - + '; IF EXISTS(SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' - + @OutputSchemaName - + ''') AND NOT EXISTS (SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''' - + @OutputSchemaName + ''' AND QUOTENAME(TABLE_NAME) = ''' - + @OutputTableNameFileStats + ''') CREATE TABLE ' - + @OutputSchemaName + '.' - + @OutputTableNameFileStats - + ' (ID INT IDENTITY(1,1) NOT NULL, - ServerName NVARCHAR(128), - CheckDate DATETIMEOFFSET, - DatabaseID INT NOT NULL, - FileID INT NOT NULL, - DatabaseName NVARCHAR(256) , - FileLogicalName NVARCHAR(256) , - TypeDesc NVARCHAR(60) , - SizeOnDiskMB BIGINT , - io_stall_read_ms BIGINT , - num_of_reads BIGINT , - bytes_read BIGINT , - io_stall_write_ms BIGINT , - num_of_writes BIGINT , - bytes_written BIGINT, - PhysicalName NVARCHAR(520) , - PRIMARY KEY CLUSTERED (ID ASC));'; + /*Check 14 is total parallel deadlocks*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Check 14 parallel deadlocks %s', 0, 1, @d) WITH NOWAIT; - EXEC(@StringToExecute); + INSERT + #deadlock_findings WITH(TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding + ) + SELECT + check_id = 14, + database_name = N'-', + object_name = N'-', + finding_group = N'Total parallel deadlocks', + finding = + N'There have been ' + + CONVERT + ( + nvarchar(20), + COUNT_BIG(DISTINCT drp.event_date) + ) + + N' parallel deadlocks.' + FROM #deadlock_resource_parallel AS drp + HAVING COUNT_BIG(DISTINCT drp.event_date) > 0 + OPTION(RECOMPILE); - SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableNameFileStats_View; + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - /* If the view exists without the most recently added columns, drop it. See Github #2162. */ - IF OBJECT_ID(@ObjectFullName) IS NOT NULL - BEGIN - SET @StringToExecute = N'USE ' + @OutputDatabaseName + N'; IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns - WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''JoinKey'') - DROP VIEW ' + @OutputSchemaName + N'.' + @OutputTableNameFileStats_View + N';'; + /*Check 15 is total deadlocks involving sleeping sessions*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Check 15 sleeping and background deadlocks %s', 0, 1, @d) WITH NOWAIT; - EXEC(@StringToExecute); - END + INSERT + #deadlock_findings WITH(TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding + ) + SELECT + check_id = 15, + database_name = N'-', + object_name = N'-', + finding_group = N'Total deadlocks involving sleeping sessions', + finding = + N'There have been ' + + CONVERT + ( + nvarchar(20), + COUNT_BIG(DISTINCT dp.event_date) + ) + + N' sleepy deadlocks.' + FROM #deadlock_process AS dp + WHERE dp.status = N'sleeping' + HAVING COUNT_BIG(DISTINCT dp.event_date) > 0 + OPTION(RECOMPILE); - /* Create the view */ - IF OBJECT_ID(@ObjectFullName) IS NULL - BEGIN - SET @StringToExecute = 'USE ' - + @OutputDatabaseName - + '; EXEC (''CREATE VIEW ' - + @OutputSchemaName + '.' - + @OutputTableNameFileStats_View + ' AS ' + @LineFeed - + 'WITH RowDates as' + @LineFeed - + '(' + @LineFeed - + ' SELECT ' + @LineFeed - + ' ROW_NUMBER() OVER (ORDER BY [ServerName], [CheckDate]) ID,' + @LineFeed - + ' [CheckDate]' + @LineFeed - + ' FROM ' + @OutputSchemaName + '.' + @OutputTableNameFileStats + '' + @LineFeed - + ' GROUP BY [ServerName], [CheckDate]' + @LineFeed - + '),' + @LineFeed - + 'CheckDates as' + @LineFeed - + '(' + @LineFeed - + ' SELECT ThisDate.CheckDate,' + @LineFeed - + ' LastDate.CheckDate as PreviousCheckDate' + @LineFeed - + ' FROM RowDates ThisDate' + @LineFeed - + ' JOIN RowDates LastDate' + @LineFeed - + ' ON ThisDate.ID = LastDate.ID + 1' + @LineFeed - + ')' + @LineFeed - + ' SELECT f.ServerName,' + @LineFeed - + ' f.CheckDate,' + @LineFeed - + ' f.DatabaseID,' + @LineFeed - + ' f.DatabaseName,' + @LineFeed - + ' f.FileID,' + @LineFeed - + ' f.FileLogicalName,' + @LineFeed - + ' f.TypeDesc,' + @LineFeed - + ' f.PhysicalName,' + @LineFeed - + ' f.SizeOnDiskMB,' + @LineFeed - + ' DATEDIFF(ss, fPrior.CheckDate, f.CheckDate) AS ElapsedSeconds,' + @LineFeed - + ' (f.SizeOnDiskMB - fPrior.SizeOnDiskMB) AS SizeOnDiskMBgrowth,' + @LineFeed - + ' (f.io_stall_read_ms - fPrior.io_stall_read_ms) AS io_stall_read_ms,' + @LineFeed - + ' io_stall_read_ms_average = CASE' + @LineFeed - + ' WHEN(f.num_of_reads - fPrior.num_of_reads) = 0' + @LineFeed - + ' THEN 0' + @LineFeed - + ' ELSE(f.io_stall_read_ms - fPrior.io_stall_read_ms) / (f.num_of_reads - fPrior.num_of_reads)' + @LineFeed - + ' END,' + @LineFeed - + ' (f.num_of_reads - fPrior.num_of_reads) AS num_of_reads,' + @LineFeed - + ' (f.bytes_read - fPrior.bytes_read) / 1024.0 / 1024.0 AS megabytes_read,' + @LineFeed - + ' (f.io_stall_write_ms - fPrior.io_stall_write_ms) AS io_stall_write_ms,' + @LineFeed - + ' io_stall_write_ms_average = CASE' + @LineFeed - + ' WHEN(f.num_of_writes - fPrior.num_of_writes) = 0' + @LineFeed - + ' THEN 0' + @LineFeed - + ' ELSE(f.io_stall_write_ms - fPrior.io_stall_write_ms) / (f.num_of_writes - fPrior.num_of_writes)' + @LineFeed - + ' END,' + @LineFeed - + ' (f.num_of_writes - fPrior.num_of_writes) AS num_of_writes,' + @LineFeed - + ' (f.bytes_written - fPrior.bytes_written) / 1024.0 / 1024.0 AS megabytes_written, ' + @LineFeed - + ' f.ServerName + CAST(f.CheckDate AS NVARCHAR(50)) AS JoinKey' + @LineFeed - + ' FROM ' + @OutputSchemaName + '.' + @OutputTableNameFileStats + ' f' + @LineFeed - + ' INNER HASH JOIN CheckDates DATES ON f.CheckDate = DATES.CheckDate' + @LineFeed - + ' INNER JOIN ' + @OutputSchemaName + '.' + @OutputTableNameFileStats + ' fPrior ON f.ServerName = fPrior.ServerName' + @LineFeed - + ' AND f.DatabaseID = fPrior.DatabaseID' + @LineFeed - + ' AND f.FileID = fPrior.FileID' + @LineFeed - + ' AND fPrior.CheckDate = DATES.PreviousCheckDate' + @LineFeed - + '' + @LineFeed - + ' WHERE f.num_of_reads >= fPrior.num_of_reads' + @LineFeed - + ' AND f.num_of_writes >= fPrior.num_of_writes' + @LineFeed - + ' AND DATEDIFF(MI, fPrior.CheckDate, f.CheckDate) BETWEEN 1 AND 60;'')' + INSERT + #deadlock_findings WITH(TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding + ) + SELECT + check_id = 15, + database_name = N'-', + object_name = N'-', + finding_group = N'Total deadlocks involving background processes', + finding = + N'There have been ' + + CONVERT + ( + nvarchar(20), + COUNT_BIG(DISTINCT dp.event_date) + ) + + N' deadlocks with background task.' + FROM #deadlock_process AS dp + WHERE dp.status = N'background' + HAVING COUNT_BIG(DISTINCT dp.event_date) > 0 + OPTION(RECOMPILE); - EXEC(@StringToExecute); - END; + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + /*Check 16 is total deadlocks involving implicit transactions*/ + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Check 16 implicit transaction deadlocks %s', 0, 1, @d) WITH NOWAIT; - SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' - + @OutputSchemaName + ''') INSERT ' - + @OutputDatabaseName + '.' - + @OutputSchemaName + '.' - + @OutputTableNameFileStats - + ' (ServerName, CheckDate, DatabaseID, FileID, DatabaseName, FileLogicalName, TypeDesc, SizeOnDiskMB, io_stall_read_ms, num_of_reads, bytes_read, io_stall_write_ms, num_of_writes, bytes_written, PhysicalName) SELECT ' - + ' @SrvName, @CheckDate, DatabaseID, FileID, DatabaseName, FileLogicalName, TypeDesc, SizeOnDiskMB, io_stall_read_ms, num_of_reads, bytes_read, io_stall_write_ms, num_of_writes, bytes_written, PhysicalName FROM #FileStats WHERE Pass = 2'; + INSERT + #deadlock_findings WITH(TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding + ) + SELECT + check_id = 14, + database_name = N'-', + object_name = N'-', + finding_group = N'Total implicit transaction deadlocks', + finding = + N'There have been ' + + CONVERT + ( + nvarchar(20), + COUNT_BIG(DISTINCT dp.event_date) + ) + + N' implicit transaction deadlocks.' + FROM #deadlock_process AS dp + WHERE dp.transaction_name = N'implicit_transaction' + HAVING COUNT_BIG(DISTINCT dp.event_date) > 0 + OPTION(RECOMPILE); - EXEC sp_executesql @StringToExecute, - N'@SrvName NVARCHAR(128), @CheckDate datetimeoffset', - @LocalServerName, @StartSampleTime; + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - /* Delete history older than @OutputTableRetentionDays */ - SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' - + @OutputSchemaName + ''') DELETE ' - + @OutputDatabaseName + '.' - + @OutputSchemaName + '.' - + @OutputTableNameFileStats - + ' WHERE ServerName = @SrvName AND CheckDate < @CheckDate ;'; + /*Thank you goodnight*/ + INSERT + #deadlock_findings WITH(TABLOCKX) + ( + check_id, + database_name, + object_name, + finding_group, + finding + ) + VALUES + ( + -1, + N'sp_BlitzLock version ' + CONVERT(nvarchar(10), @Version), + N'Results for ' + CONVERT(nvarchar(10), @StartDate, 23) + N' through ' + CONVERT(nvarchar(10), @EndDate, 23), + N'http://FirstResponderKit.org/', + N'To get help or add your own contributions to the SQL Server First Responder Kit, join us at http://FirstResponderKit.org.' + ); - EXEC sp_executesql @StringToExecute, - N'@SrvName NVARCHAR(128), @CheckDate date', - @LocalServerName, @OutputTableCleanupDate; + RAISERROR('Finished rollup at %s', 0, 1, @d) WITH NOWAIT; - END; - ELSE IF (SUBSTRING(@OutputTableNameFileStats, 2, 2) = '##') - BEGIN - SET @StringToExecute = N' IF (OBJECT_ID(''tempdb..' - + @OutputTableNameFileStats - + ''') IS NULL) CREATE TABLE ' - + @OutputTableNameFileStats - + ' (ID INT IDENTITY(1,1) NOT NULL, - ServerName NVARCHAR(128), - CheckDate DATETIMEOFFSET, - DatabaseID INT NOT NULL, - FileID INT NOT NULL, - DatabaseName NVARCHAR(256) , - FileLogicalName NVARCHAR(256) , - TypeDesc NVARCHAR(60) , - SizeOnDiskMB BIGINT , - io_stall_read_ms BIGINT , - num_of_reads BIGINT , - bytes_read BIGINT , - io_stall_write_ms BIGINT , - num_of_writes BIGINT , - bytes_written BIGINT, - PhysicalName NVARCHAR(520) , - DetailsInt INT NULL, - PRIMARY KEY CLUSTERED (ID ASC));' - + ' INSERT ' - + @OutputTableNameFileStats - + ' (ServerName, CheckDate, DatabaseID, FileID, DatabaseName, FileLogicalName, TypeDesc, SizeOnDiskMB, io_stall_read_ms, num_of_reads, bytes_read, io_stall_write_ms, num_of_writes, bytes_written, PhysicalName) SELECT ' - + ' @SrvName, @CheckDate, DatabaseID, FileID, DatabaseName, FileLogicalName, TypeDesc, SizeOnDiskMB, io_stall_read_ms, num_of_reads, bytes_read, io_stall_write_ms, num_of_writes, bytes_written, PhysicalName FROM #FileStats WHERE Pass = 2'; + /*Results*/ + BEGIN + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Results 1 %s', 0, 1, @d) WITH NOWAIT; - EXEC sp_executesql @StringToExecute, - N'@SrvName NVARCHAR(128), @CheckDate datetimeoffset', - @LocalServerName, @StartSampleTime; - END; - ELSE IF (SUBSTRING(@OutputTableNameFileStats, 2, 1) = '#') - BEGIN - RAISERROR('Due to the nature of Dymamic SQL, only global (i.e. double pound (##)) temp tables are supported for @OutputTableName', 16, 0); - END; + CREATE CLUSTERED INDEX cx_whatever_dp ON #deadlock_process (event_date, id); + CREATE CLUSTERED INDEX cx_whatever_drp ON #deadlock_resource_parallel (event_date, owner_id); + CREATE CLUSTERED INDEX cx_whatever_dow ON #deadlock_owner_waiter (event_date, owner_id, waiter_id); + + IF @Debug = 1 BEGIN SET STATISTICS XML ON; END; + + WITH + deadlocks AS + ( + SELECT + deadlock_type = + N'Regular Deadlock', + dp.event_date, + dp.id, + dp.victim_id, + dp.spid, + dp.database_id, + dp.database_name, + dp.current_database_name, + dp.priority, + dp.log_used, + wait_resource = + dp.wait_resource COLLATE DATABASE_DEFAULT, + object_names = + CONVERT + ( + xml, + STUFF + ( + ( + SELECT DISTINCT + object_name = + NCHAR(10) + + N' ' + + ISNULL(c.object_name, N'') + + N' ' COLLATE DATABASE_DEFAULT + FROM #deadlock_owner_waiter AS c + WHERE (dp.id = c.owner_id + OR dp.victim_id = c.waiter_id) + AND dp.event_date = c.event_date + FOR XML + PATH(N''), + TYPE + ).value(N'.[1]', N'nvarchar(4000)'), + 1, + 1, + N'' + ) + ), + dp.wait_time, + dp.transaction_name, + dp.status, + dp.last_tran_started, + dp.last_batch_started, + dp.last_batch_completed, + dp.lock_mode, + dp.transaction_count, + dp.client_app, + dp.host_name, + dp.login_name, + dp.isolation_level, + dp.client_option_1, + dp.client_option_2, + inputbuf = + dp.process_xml.value('(//process/inputbuf/text())[1]', 'nvarchar(MAX)'), + en = + DENSE_RANK() OVER (ORDER BY dp.event_date), + qn = + ROW_NUMBER() OVER (PARTITION BY dp.event_date ORDER BY dp.event_date) - 1, + dn = + ROW_NUMBER() OVER (PARTITION BY dp.event_date, dp.id ORDER BY dp.event_date), + dp.is_victim, + owner_mode = + ISNULL(dp.owner_mode, N'-'), + owner_waiter_type = NULL, + owner_activity = NULL, + owner_waiter_activity = NULL, + owner_merging = NULL, + owner_spilling = NULL, + owner_waiting_to_close = NULL, + waiter_mode = + ISNULL(dp.waiter_mode, N'-'), + waiter_waiter_type = NULL, + waiter_owner_activity = NULL, + waiter_waiter_activity = NULL, + waiter_merging = NULL, + waiter_spilling = NULL, + waiter_waiting_to_close = NULL, + dp.deadlock_graph + FROM #deadlock_process AS dp + WHERE dp.victim_id IS NOT NULL + AND dp.is_parallel = 0 + + UNION ALL + SELECT + deadlock_type = + N'Parallel Deadlock', + dp.event_date, + dp.id, + dp.victim_id, + dp.spid, + dp.database_id, + dp.database_name, + dp.current_database_name, + dp.priority, + dp.log_used, + dp.wait_resource COLLATE DATABASE_DEFAULT, + object_names = + CONVERT + ( + xml, + STUFF + ( + ( + SELECT DISTINCT + object_name = + NCHAR(10) + + N' ' + + ISNULL(c.object_name, N'') + + N' ' COLLATE DATABASE_DEFAULT + FROM #deadlock_owner_waiter AS c + WHERE (dp.id = c.owner_id + OR dp.victim_id = c.waiter_id) + AND dp.event_date = c.event_date + FOR XML + PATH(N''), + TYPE + ).value(N'.[1]', N'nvarchar(4000)'), + 1, + 1, + N'' + ) + ), + dp.wait_time, + dp.transaction_name, + dp.status, + dp.last_tran_started, + dp.last_batch_started, + dp.last_batch_completed, + dp.lock_mode, + dp.transaction_count, + dp.client_app, + dp.host_name, + dp.login_name, + dp.isolation_level, + dp.client_option_1, + dp.client_option_2, + inputbuf = + dp.process_xml.value('(//process/inputbuf/text())[1]', 'nvarchar(MAX)'), + en = + DENSE_RANK() OVER (ORDER BY dp.event_date), + qn = + ROW_NUMBER() OVER (PARTITION BY dp.event_date ORDER BY dp.event_date) - 1, + dn = + ROW_NUMBER() OVER (PARTITION BY dp.event_date, dp.id ORDER BY dp.event_date), + is_victim = 1, + owner_mode = cao.wait_type COLLATE DATABASE_DEFAULT, + owner_waiter_type = cao.waiter_type, + owner_activity = cao.owner_activity, + owner_waiter_activity = cao.waiter_activity, + owner_merging = cao.merging, + owner_spilling = cao.spilling, + owner_waiting_to_close = cao.waiting_to_close, + waiter_mode = caw.wait_type COLLATE DATABASE_DEFAULT, + waiter_waiter_type = caw.waiter_type, + waiter_owner_activity = caw.owner_activity, + waiter_waiter_activity = caw.waiter_activity, + waiter_merging = caw.merging, + waiter_spilling = caw.spilling, + waiter_waiting_to_close = caw.waiting_to_close, + dp.deadlock_graph + FROM #deadlock_process AS dp + OUTER APPLY + ( + SELECT TOP (1) + drp.* + FROM #deadlock_resource_parallel AS drp + WHERE drp.owner_id = dp.id + AND drp.wait_type IN + ( + N'e_waitPortOpen', + N'e_waitPipeNewRow' + ) + ORDER BY drp.event_date + ) AS cao + OUTER APPLY + ( + SELECT TOP (1) + drp.* + FROM #deadlock_resource_parallel AS drp + WHERE drp.owner_id = dp.id + AND drp.wait_type IN + ( + N'e_waitPortOpen', + N'e_waitPipeGetRow' + ) + ORDER BY drp.event_date + ) AS caw + WHERE dp.is_parallel = 1 + ) + SELECT + d.deadlock_type, + d.event_date, + d.id, + d.victim_id, + d.spid, + deadlock_group = + N'Deadlock #' + + CONVERT + ( + nvarchar(10), + d.en + ) + + N', Query #' + + CASE + WHEN d.qn = 0 + THEN N'1' + ELSE CONVERT(nvarchar(10), d.qn) + END + CASE + WHEN d.is_victim = 1 + THEN N' - VICTIM' + ELSE N'' + END, + d.database_id, + d.database_name, + d.current_database_name, + d.priority, + d.log_used, + d.wait_resource, + d.object_names, + d.wait_time, + d.transaction_name, + d.status, + d.last_tran_started, + d.last_batch_started, + d.last_batch_completed, + d.lock_mode, + d.transaction_count, + d.client_app, + d.host_name, + d.login_name, + d.isolation_level, + d.client_option_1, + d.client_option_2, + inputbuf = + CASE + WHEN d.inputbuf + LIKE @inputbuf_bom + N'Proc |[Database Id = %' ESCAPE N'|' + THEN + OBJECT_SCHEMA_NAME + ( + SUBSTRING + ( + d.inputbuf, + CHARINDEX(N'Object Id = ', d.inputbuf) + 12, + LEN(d.inputbuf) - (CHARINDEX(N'Object Id = ', d.inputbuf) + 12) + ) + , + SUBSTRING + ( + d.inputbuf, + CHARINDEX(N'Database Id = ', d.inputbuf) + 14, + CHARINDEX(N'Object Id', d.inputbuf) - (CHARINDEX(N'Database Id = ', d.inputbuf) + 14) + ) + ) + + N'.' + + OBJECT_NAME + ( + SUBSTRING + ( + d.inputbuf, + CHARINDEX(N'Object Id = ', d.inputbuf) + 12, + LEN(d.inputbuf) - (CHARINDEX(N'Object Id = ', d.inputbuf) + 12) + ) + , + SUBSTRING + ( + d.inputbuf, + CHARINDEX(N'Database Id = ', d.inputbuf) + 14, + CHARINDEX(N'Object Id', d.inputbuf) - (CHARINDEX(N'Database Id = ', d.inputbuf) + 14) + ) + ) + ELSE d.inputbuf + END COLLATE Latin1_General_BIN2, + d.owner_mode, + d.owner_waiter_type, + d.owner_activity, + d.owner_waiter_activity, + d.owner_merging, + d.owner_spilling, + d.owner_waiting_to_close, + d.waiter_mode, + d.waiter_waiter_type, + d.waiter_owner_activity, + d.waiter_waiter_activity, + d.waiter_merging, + d.waiter_spilling, + d.waiter_waiting_to_close, + d.deadlock_graph, + d.is_victim + INTO #deadlocks + FROM deadlocks AS d + WHERE d.dn = 1 + AND (d.is_victim = @VictimsOnly + OR @VictimsOnly = 0) + AND d.qn < CASE + WHEN d.deadlock_type = N'Parallel Deadlock' + THEN 2 + ELSE 2147483647 + END + AND (DB_NAME(d.database_id) = @DatabaseName OR @DatabaseName IS NULL) + AND (d.event_date >= @StartDate OR @StartDate IS NULL) + AND (d.event_date < @EndDate OR @EndDate IS NULL) + AND (CONVERT(nvarchar(MAX), d.object_names) COLLATE Latin1_General_BIN2 LIKE N'%' + @ObjectName + N'%' OR @ObjectName IS NULL) + AND (d.client_app = @AppName OR @AppName IS NULL) + AND (d.host_name = @HostName OR @HostName IS NULL) + AND (d.login_name = @LoginName OR @LoginName IS NULL) + OPTION (RECOMPILE, LOOP JOIN, HASH JOIN); - /* @OutputTableNamePerfmonStats lets us export the results to a permanent table */ - IF @OutputDatabaseName IS NOT NULL - AND @OutputSchemaName IS NOT NULL - AND @OutputTableNamePerfmonStats IS NOT NULL - AND @OutputTableNamePerfmonStats NOT LIKE '#%' - AND EXISTS ( SELECT * - FROM sys.databases - WHERE QUOTENAME([name]) = @OutputDatabaseName) - BEGIN - /* Create the table */ - SET @StringToExecute = 'USE ' - + @OutputDatabaseName - + '; IF EXISTS(SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' - + @OutputSchemaName - + ''') AND NOT EXISTS (SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''' - + @OutputSchemaName + ''' AND QUOTENAME(TABLE_NAME) = ''' - + @OutputTableNamePerfmonStats + ''') CREATE TABLE ' - + @OutputSchemaName + '.' - + @OutputTableNamePerfmonStats - + ' (ID INT IDENTITY(1,1) NOT NULL, - ServerName NVARCHAR(128), - CheckDate DATETIMEOFFSET, - [object_name] NVARCHAR(128) NOT NULL, - [counter_name] NVARCHAR(128) NOT NULL, - [instance_name] NVARCHAR(128) NULL, - [cntr_value] BIGINT NULL, - [cntr_type] INT NOT NULL, - [value_delta] BIGINT NULL, - [value_per_second] DECIMAL(18,2) NULL, - PRIMARY KEY CLUSTERED (ID ASC));'; + UPDATE d + SET d.inputbuf = + REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE( + REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE( + REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE( + d.inputbuf, + NCHAR(31), N'?'), NCHAR(30), N'?'), NCHAR(29), N'?'), NCHAR(28), N'?'), NCHAR(27), N'?'), + NCHAR(26), N'?'), NCHAR(25), N'?'), NCHAR(24), N'?'), NCHAR(23), N'?'), NCHAR(22), N'?'), + NCHAR(21), N'?'), NCHAR(20), N'?'), NCHAR(19), N'?'), NCHAR(18), N'?'), NCHAR(17), N'?'), + NCHAR(16), N'?'), NCHAR(15), N'?'), NCHAR(14), N'?'), NCHAR(12), N'?'), NCHAR(11), N'?'), + NCHAR(8), N'?'), NCHAR(7), N'?'), NCHAR(6), N'?'), NCHAR(5), N'?'), NCHAR(4), N'?'), + NCHAR(3), N'?'), NCHAR(2), N'?'), NCHAR(1), N'?'), NCHAR(0), N'?') + FROM #deadlocks AS d + OPTION(RECOMPILE); - EXEC(@StringToExecute); + SELECT + d.deadlock_type, + d.event_date, + database_name = + DB_NAME(d.database_id), + database_name_x = + d.database_name, + d.current_database_name, + d.spid, + d.deadlock_group, + d.client_option_1, + d.client_option_2, + d.lock_mode, + query_xml = + ( + SELECT + [processing-instruction(query)] = + d.inputbuf + FOR XML + PATH(N''), + TYPE + ), + query_string = + d.inputbuf, + d.object_names, + d.isolation_level, + d.owner_mode, + d.waiter_mode, + d.transaction_count, + d.login_name, + d.host_name, + d.client_app, + d.wait_time, + d.wait_resource, + d.priority, + d.log_used, + d.last_tran_started, + d.last_batch_started, + d.last_batch_completed, + d.transaction_name, + d.status, + /*These columns will be NULL for regular (non-parallel) deadlocks*/ + parallel_deadlock_details = + ( + SELECT + d.owner_waiter_type, + d.owner_activity, + d.owner_waiter_activity, + d.owner_merging, + d.owner_spilling, + d.owner_waiting_to_close, + d.waiter_waiter_type, + d.waiter_owner_activity, + d.waiter_waiter_activity, + d.waiter_merging, + d.waiter_spilling, + d.waiter_waiting_to_close + FOR XML + PATH('parallel_deadlock_details'), + TYPE + ), + d.owner_waiter_type, + d.owner_activity, + d.owner_waiter_activity, + d.owner_merging, + d.owner_spilling, + d.owner_waiting_to_close, + d.waiter_waiter_type, + d.waiter_owner_activity, + d.waiter_waiter_activity, + d.waiter_merging, + d.waiter_spilling, + d.waiter_waiting_to_close, + /*end parallel deadlock columns*/ + d.deadlock_graph, + d.is_victim, + d.id + INTO #deadlock_results + FROM #deadlocks AS d; - SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableNamePerfmonStats_View; + IF @Debug = 1 BEGIN SET STATISTICS XML OFF; END; + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - /* If the view exists without the most recently added columns, drop it. See Github #2162. */ - IF OBJECT_ID(@ObjectFullName) IS NOT NULL + /*There's too much risk of errors sending the*/ + IF @OutputDatabaseCheck = 0 BEGIN - SET @StringToExecute = N'USE ' + @OutputDatabaseName + N'; IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns - WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''JoinKey'') - DROP VIEW ' + @OutputSchemaName + N'.' + @OutputTableNamePerfmonStats_View + N';'; + SET @ExportToExcel = 0; + END; - EXEC(@StringToExecute); - END + SET @deadlock_result += N' + SELECT + server_name = + @@SERVERNAME, + dr.deadlock_type, + dr.event_date, + database_name = + COALESCE + ( + dr.database_name, + dr.database_name_x, + dr.current_database_name + ), + dr.spid, + dr.deadlock_group, + ' + CASE @ExportToExcel + WHEN 1 + THEN N' + query = dr.query_string, + object_names = + REPLACE( + REPLACE( + CONVERT + ( + nvarchar(MAX), + dr.object_names + ) COLLATE Latin1_General_BIN2, + '''', ''''), + '''', ''''),' + ELSE N'query = dr.query_xml, + dr.object_names,' + END + N' + dr.isolation_level, + dr.owner_mode, + dr.waiter_mode, + dr.lock_mode, + dr.transaction_count, + dr.client_option_1, + dr.client_option_2, + dr.login_name, + dr.host_name, + dr.client_app, + dr.wait_time, + dr.wait_resource, + dr.priority, + dr.log_used, + dr.last_tran_started, + dr.last_batch_started, + dr.last_batch_completed, + dr.transaction_name, + dr.status,' + + CASE + WHEN (@ExportToExcel = 1 + OR @OutputDatabaseCheck = 0) + THEN N' + dr.owner_waiter_type, + dr.owner_activity, + dr.owner_waiter_activity, + dr.owner_merging, + dr.owner_spilling, + dr.owner_waiting_to_close, + dr.waiter_waiter_type, + dr.waiter_owner_activity, + dr.waiter_waiter_activity, + dr.waiter_merging, + dr.waiter_spilling, + dr.waiter_waiting_to_close,' + ELSE N' + dr.parallel_deadlock_details,' + END + + CASE + @ExportToExcel + WHEN 1 + THEN N' + deadlock_graph = + REPLACE(REPLACE( + REPLACE(REPLACE( + CONVERT + ( + nvarchar(MAX), + dr.deadlock_graph + ) COLLATE Latin1_General_BIN2, + ''NCHAR(10)'', ''''), ''NCHAR(13)'', ''''), + ''CHAR(10)'', ''''), ''CHAR(13)'', '''')' + ELSE N' + dr.deadlock_graph' + END + N' + FROM #deadlock_results AS dr + ORDER BY + dr.event_date, + dr.is_victim DESC + OPTION(RECOMPILE, LOOP JOIN, HASH JOIN); + '; - /* Create the view */ - IF OBJECT_ID(@ObjectFullName) IS NULL - BEGIN - SET @StringToExecute = 'USE ' - + @OutputDatabaseName - + '; EXEC (''CREATE VIEW ' - + @OutputSchemaName + '.' - + @OutputTableNamePerfmonStats_View + ' AS ' + @LineFeed - + 'WITH RowDates as' + @LineFeed - + '(' + @LineFeed - + ' SELECT ' + @LineFeed - + ' ROW_NUMBER() OVER (ORDER BY [ServerName], [CheckDate]) ID,' + @LineFeed - + ' [CheckDate]' + @LineFeed - + ' FROM ' + @OutputSchemaName + '.' +@OutputTableNamePerfmonStats + '' + @LineFeed - + ' GROUP BY [ServerName], [CheckDate]' + @LineFeed - + '),' + @LineFeed - + 'CheckDates as' + @LineFeed - + '(' + @LineFeed - + ' SELECT ThisDate.CheckDate,' + @LineFeed - + ' LastDate.CheckDate as PreviousCheckDate' + @LineFeed - + ' FROM RowDates ThisDate' + @LineFeed - + ' JOIN RowDates LastDate' + @LineFeed - + ' ON ThisDate.ID = LastDate.ID + 1' + @LineFeed - + ')' + @LineFeed - + 'SELECT' + @LineFeed - + ' pMon.[ServerName]' + @LineFeed - + ' ,pMon.[CheckDate]' + @LineFeed - + ' ,pMon.[object_name]' + @LineFeed - + ' ,pMon.[counter_name]' + @LineFeed - + ' ,pMon.[instance_name]' + @LineFeed - + ' ,DATEDIFF(SECOND,pMonPrior.[CheckDate],pMon.[CheckDate]) AS ElapsedSeconds' + @LineFeed - + ' ,pMon.[cntr_value]' + @LineFeed - + ' ,pMon.[cntr_type]' + @LineFeed - + ' ,(pMon.[cntr_value] - pMonPrior.[cntr_value]) AS cntr_delta' + @LineFeed - + ' ,(pMon.cntr_value - pMonPrior.cntr_value) * 1.0 / DATEDIFF(ss, pMonPrior.CheckDate, pMon.CheckDate) AS cntr_delta_per_second' + @LineFeed - + ' ,pMon.ServerName + CAST(pMon.CheckDate AS NVARCHAR(50)) AS JoinKey' + @LineFeed - + ' FROM ' + @OutputSchemaName + '.' +@OutputTableNamePerfmonStats + ' pMon' + @LineFeed - + ' INNER HASH JOIN CheckDates Dates' + @LineFeed - + ' ON Dates.CheckDate = pMon.CheckDate' + @LineFeed - + ' JOIN ' + @OutputSchemaName + '.' +@OutputTableNamePerfmonStats + ' pMonPrior' + @LineFeed - + ' ON Dates.PreviousCheckDate = pMonPrior.CheckDate' + @LineFeed - + ' AND pMon.[ServerName] = pMonPrior.[ServerName] ' + @LineFeed - + ' AND pMon.[object_name] = pMonPrior.[object_name] ' + @LineFeed - + ' AND pMon.[counter_name] = pMonPrior.[counter_name] ' + @LineFeed - + ' AND pMon.[instance_name] = pMonPrior.[instance_name]' + @LineFeed - + ' WHERE DATEDIFF(MI, pMonPrior.CheckDate, pMon.CheckDate) BETWEEN 1 AND 60;'')' + IF (@OutputDatabaseCheck = 0) + BEGIN + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Results to table %s', 0, 1, @d) WITH NOWAIT; - EXEC(@StringToExecute); - END + IF @Debug = 1 + BEGIN + PRINT @deadlock_result; + SET STATISTICS XML ON; + END; - SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableNamePerfmonStatsActuals_View; + INSERT INTO + DeadLockTbl + ( + ServerName, + deadlock_type, + event_date, + database_name, + spid, + deadlock_group, + query, + object_names, + isolation_level, + owner_mode, + waiter_mode, + lock_mode, + transaction_count, + client_option_1, + client_option_2, + login_name, + host_name, + client_app, + wait_time, + wait_resource, + priority, + log_used, + last_tran_started, + last_batch_started, + last_batch_completed, + transaction_name, + status, + owner_waiter_type, + owner_activity, + owner_waiter_activity, + owner_merging, + owner_spilling, + owner_waiting_to_close, + waiter_waiter_type, + waiter_owner_activity, + waiter_waiter_activity, + waiter_merging, + waiter_spilling, + waiter_waiting_to_close, + deadlock_graph + ) + EXEC sys.sp_executesql + @deadlock_result; - /* If the view exists without the most recently added columns, drop it. See Github #2162. */ - IF OBJECT_ID(@ObjectFullName) IS NOT NULL + IF @Debug = 1 BEGIN - SET @StringToExecute = N'USE ' + @OutputDatabaseName + N'; IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns - WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''JoinKey'') - DROP VIEW ' + @OutputSchemaName + N'.' + @OutputTableNamePerfmonStatsActuals_View + N';'; + SET STATISTICS XML OFF; + END; - EXEC(@StringToExecute); - END + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - /* Create the second view */ - IF OBJECT_ID(@ObjectFullName) IS NULL - BEGIN - SET @StringToExecute = 'USE ' - + @OutputDatabaseName - + '; EXEC (''CREATE VIEW ' - + @OutputSchemaName + '.' - + @OutputTableNamePerfmonStatsActuals_View + ' AS ' + @LineFeed - + 'WITH PERF_AVERAGE_BULK AS' + @LineFeed - + '(' + @LineFeed - + ' SELECT ServerName,' + @LineFeed - + ' object_name,' + @LineFeed - + ' instance_name,' + @LineFeed - + ' counter_name,' + @LineFeed - + ' CASE WHEN CHARINDEX(''''('''', counter_name) = 0 THEN counter_name ELSE LEFT (counter_name, CHARINDEX(''''('''',counter_name)-1) END AS counter_join,' + @LineFeed - + ' CheckDate,' + @LineFeed - + ' cntr_delta' + @LineFeed - + ' FROM ' + @OutputSchemaName + '.' + @OutputTableNamePerfmonStats_View + @LineFeed - + ' WHERE cntr_type IN(1073874176)' + @LineFeed - + ' AND cntr_delta <> 0' + @LineFeed - + '),' + @LineFeed - + 'PERF_LARGE_RAW_BASE AS' + @LineFeed - + '(' + @LineFeed - + ' SELECT ServerName,' + @LineFeed - + ' object_name,' + @LineFeed - + ' instance_name,' + @LineFeed - + ' LEFT(counter_name, CHARINDEX(''''BASE'''', UPPER(counter_name))-1) AS counter_join,' + @LineFeed - + ' CheckDate,' + @LineFeed - + ' cntr_delta' + @LineFeed - + ' FROM ' + @OutputSchemaName + '.' + @OutputTableNamePerfmonStats_View + '' + @LineFeed - + ' WHERE cntr_type IN(1073939712)' + @LineFeed - + ' AND cntr_delta <> 0' + @LineFeed - + '),' + @LineFeed - + 'PERF_AVERAGE_FRACTION AS' + @LineFeed - + '(' + @LineFeed - + ' SELECT ServerName,' + @LineFeed - + ' object_name,' + @LineFeed - + ' instance_name,' + @LineFeed - + ' counter_name,' + @LineFeed - + ' counter_name AS counter_join,' + @LineFeed - + ' CheckDate,' + @LineFeed - + ' cntr_delta' + @LineFeed - + ' FROM ' + @OutputSchemaName + '.' + @OutputTableNamePerfmonStats_View + '' + @LineFeed - + ' WHERE cntr_type IN(537003264)' + @LineFeed - + ' AND cntr_delta <> 0' + @LineFeed - + '),' + @LineFeed - + 'PERF_COUNTER_BULK_COUNT AS' + @LineFeed - + '(' + @LineFeed - + ' SELECT ServerName,' + @LineFeed - + ' object_name,' + @LineFeed - + ' instance_name,' + @LineFeed - + ' counter_name,' + @LineFeed - + ' CheckDate,' + @LineFeed - + ' cntr_delta / ElapsedSeconds AS cntr_value' + @LineFeed - + ' FROM ' + @OutputSchemaName + '.' + @OutputTableNamePerfmonStats_View + '' + @LineFeed - + ' WHERE cntr_type IN(272696576, 272696320)' + @LineFeed - + ' AND cntr_delta <> 0' + @LineFeed - + '),' + @LineFeed - + 'PERF_COUNTER_RAWCOUNT AS' + @LineFeed - + '(' + @LineFeed - + ' SELECT ServerName,' + @LineFeed - + ' object_name,' + @LineFeed - + ' instance_name,' + @LineFeed - + ' counter_name,' + @LineFeed - + ' CheckDate,' + @LineFeed - + ' cntr_value' + @LineFeed - + ' FROM ' + @OutputSchemaName + '.' + @OutputTableNamePerfmonStats_View + '' + @LineFeed - + ' WHERE cntr_type IN(65792, 65536)' + @LineFeed - + ')' + @LineFeed - + '' + @LineFeed - + 'SELECT NUM.ServerName,' + @LineFeed - + ' NUM.object_name,' + @LineFeed - + ' NUM.counter_name,' + @LineFeed - + ' NUM.instance_name,' + @LineFeed - + ' NUM.CheckDate,' + @LineFeed - + ' NUM.cntr_delta / DEN.cntr_delta AS cntr_value,' + @LineFeed - + ' NUM.ServerName + CAST(NUM.CheckDate AS NVARCHAR(50)) AS JoinKey' + @LineFeed - + ' ' + @LineFeed - + 'FROM PERF_AVERAGE_BULK AS NUM' + @LineFeed - + ' JOIN PERF_LARGE_RAW_BASE AS DEN ON NUM.counter_join = DEN.counter_join' + @LineFeed - + ' AND NUM.CheckDate = DEN.CheckDate' + @LineFeed - + ' AND NUM.ServerName = DEN.ServerName' + @LineFeed - + ' AND NUM.object_name = DEN.object_name' + @LineFeed - + ' AND NUM.instance_name = DEN.instance_name' + @LineFeed - + ' AND DEN.cntr_delta <> 0' + @LineFeed - + '' + @LineFeed - + 'UNION ALL' + @LineFeed - + '' + @LineFeed - + 'SELECT NUM.ServerName,' + @LineFeed - + ' NUM.object_name,' + @LineFeed - + ' NUM.counter_name,' + @LineFeed - + ' NUM.instance_name,' + @LineFeed - + ' NUM.CheckDate,' + @LineFeed - + ' CAST((CAST(NUM.cntr_delta as DECIMAL(19)) / DEN.cntr_delta) as decimal(23,3)) AS cntr_value,' + @LineFeed - + ' NUM.ServerName + CAST(NUM.CheckDate AS NVARCHAR(50)) AS JoinKey' + @LineFeed - + 'FROM PERF_AVERAGE_FRACTION AS NUM' + @LineFeed - + ' JOIN PERF_LARGE_RAW_BASE AS DEN ON NUM.counter_join = DEN.counter_join' + @LineFeed - + ' AND NUM.CheckDate = DEN.CheckDate' + @LineFeed - + ' AND NUM.ServerName = DEN.ServerName' + @LineFeed - + ' AND NUM.object_name = DEN.object_name' + @LineFeed - + ' AND NUM.instance_name = DEN.instance_name' + @LineFeed - + ' AND DEN.cntr_delta <> 0' + @LineFeed - + 'UNION ALL' + @LineFeed - + '' + @LineFeed - + 'SELECT ServerName,' + @LineFeed - + ' object_name,' + @LineFeed - + ' counter_name,' + @LineFeed - + ' instance_name,' + @LineFeed - + ' CheckDate,' + @LineFeed - + ' cntr_value,' + @LineFeed - + ' ServerName + CAST(CheckDate AS NVARCHAR(50)) AS JoinKey' + @LineFeed - + 'FROM PERF_COUNTER_BULK_COUNT' + @LineFeed - + '' + @LineFeed - + 'UNION ALL' + @LineFeed - + '' + @LineFeed - + 'SELECT ServerName,' + @LineFeed - + ' object_name,' + @LineFeed - + ' counter_name,' + @LineFeed - + ' instance_name,' + @LineFeed - + ' CheckDate,' + @LineFeed - + ' cntr_value,' + @LineFeed - + ' ServerName + CAST(CheckDate AS NVARCHAR(50)) AS JoinKey' + @LineFeed - + 'FROM PERF_COUNTER_RAWCOUNT;'')'; + DROP SYNONYM DeadLockTbl; - EXEC(@StringToExecute); - END; + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Findings to table %s', 0, 1, @d) WITH NOWAIT; + INSERT INTO + DeadlockFindings + ( + ServerName, + check_id, + database_name, + object_name, + finding_group, + finding + ) + SELECT + @@SERVERNAME, + df.check_id, + df.database_name, + df.object_name, + df.finding_group, + df.finding + FROM #deadlock_findings AS df + ORDER BY df.check_id + OPTION(RECOMPILE); - SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' - + @OutputSchemaName + ''') INSERT ' - + @OutputDatabaseName + '.' - + @OutputSchemaName + '.' - + @OutputTableNamePerfmonStats - + ' (ServerName, CheckDate, object_name, counter_name, instance_name, cntr_value, cntr_type, value_delta, value_per_second) SELECT ' - + ' @SrvName, @CheckDate, object_name, counter_name, instance_name, cntr_value, cntr_type, value_delta, value_per_second FROM #PerfmonStats WHERE Pass = 2'; + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - EXEC sp_executesql @StringToExecute, - N'@SrvName NVARCHAR(128), @CheckDate datetimeoffset', - @LocalServerName, @StartSampleTime; + DROP SYNONYM DeadlockFindings; /*done with inserting.*/ + END; + ELSE /*Output to database is not set output to client app*/ + BEGIN + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Results to client %s', 0, 1, @d) WITH NOWAIT; + + IF @Debug = 1 BEGIN SET STATISTICS XML ON; END; + + EXEC sys.sp_executesql + @deadlock_result; + + IF @Debug = 1 + BEGIN + SET STATISTICS XML OFF; + PRINT @deadlock_result; + END; + + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - /* Delete history older than @OutputTableRetentionDays */ - SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' - + @OutputSchemaName + ''') DELETE ' - + @OutputDatabaseName + '.' - + @OutputSchemaName + '.' - + @OutputTableNamePerfmonStats - + ' WHERE ServerName = @SrvName AND CheckDate < @CheckDate ;'; + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Getting available execution plans for deadlocks %s', 0, 1, @d) WITH NOWAIT; + + SELECT DISTINCT + available_plans = + 'available_plans', + ds.proc_name, + sql_handle = + CONVERT(varbinary(64), ds.sql_handle, 1), + dow.database_name, + dow.database_id, + dow.object_name, + query_xml = + TRY_CAST(dr.query_xml AS nvarchar(MAX)) + INTO #available_plans + FROM #deadlock_stack AS ds + JOIN #deadlock_owner_waiter AS dow + ON dow.owner_id = ds.id + AND dow.event_date = ds.event_date + JOIN #deadlock_results AS dr + ON dr.id = ds.id + AND dr.event_date = ds.event_date + OPTION(RECOMPILE); - EXEC sp_executesql @StringToExecute, - N'@SrvName NVARCHAR(128), @CheckDate date', - @LocalServerName, @OutputTableCleanupDate; + SELECT + deqs.sql_handle, + deqs.plan_handle, + deqs.statement_start_offset, + deqs.statement_end_offset, + deqs.creation_time, + deqs.last_execution_time, + deqs.execution_count, + total_worker_time_ms = + deqs.total_worker_time / 1000., + avg_worker_time_ms = + CONVERT(decimal(38, 6), deqs.total_worker_time / 1000. / deqs.execution_count), + total_elapsed_time_ms = + deqs.total_elapsed_time / 1000., + avg_elapsed_time_ms = + CONVERT(decimal(38, 6), deqs.total_elapsed_time / 1000. / deqs.execution_count), + executions_per_second = + ISNULL + ( + deqs.execution_count / + NULLIF + ( + DATEDIFF + ( + SECOND, + deqs.creation_time, + NULLIF(deqs.last_execution_time, '1900-01-01 00:00:00.000') + ), + 0 + ), + 0 + ), + total_physical_reads_mb = + deqs.total_physical_reads * 8. / 1024., + total_logical_writes_mb = + deqs.total_logical_writes * 8. / 1024., + total_logical_reads_mb = + deqs.total_logical_reads * 8. / 1024., + min_grant_mb = + deqs.min_grant_kb * 8. / 1024., + max_grant_mb = + deqs.max_grant_kb * 8. / 1024., + min_used_grant_mb = + deqs.min_used_grant_kb * 8. / 1024., + max_used_grant_mb = + deqs.max_used_grant_kb * 8. / 1024., + deqs.min_reserved_threads, + deqs.max_reserved_threads, + deqs.min_used_threads, + deqs.max_used_threads, + deqs.total_rows + INTO #dm_exec_query_stats + FROM sys.dm_exec_query_stats AS deqs + WHERE EXISTS + ( + SELECT + 1/0 + FROM #available_plans AS ap + WHERE ap.sql_handle = deqs.sql_handle + ) + AND deqs.query_hash IS NOT NULL; + + CREATE CLUSTERED INDEX + deqs + ON #dm_exec_query_stats + ( + sql_handle, + plan_handle + ); + + SELECT + ap.available_plans, + ap.database_name, + query_text = + TRY_CAST(ap.query_xml AS xml), + ap.query_plan, + ap.creation_time, + ap.last_execution_time, + ap.execution_count, + ap.executions_per_second, + ap.total_worker_time_ms, + ap.avg_worker_time_ms, + ap.total_elapsed_time_ms, + ap.avg_elapsed_time_ms, + ap.total_logical_reads_mb, + ap.total_physical_reads_mb, + ap.total_logical_writes_mb, + ap.min_grant_mb, + ap.max_grant_mb, + ap.min_used_grant_mb, + ap.max_used_grant_mb, + ap.min_reserved_threads, + ap.max_reserved_threads, + ap.min_used_threads, + ap.max_used_threads, + ap.total_rows, + ap.sql_handle, + ap.statement_start_offset, + ap.statement_end_offset + FROM + ( + + SELECT + ap.*, + c.statement_start_offset, + c.statement_end_offset, + c.creation_time, + c.last_execution_time, + c.execution_count, + c.total_worker_time_ms, + c.avg_worker_time_ms, + c.total_elapsed_time_ms, + c.avg_elapsed_time_ms, + c.executions_per_second, + c.total_physical_reads_mb, + c.total_logical_writes_mb, + c.total_logical_reads_mb, + c.min_grant_mb, + c.max_grant_mb, + c.min_used_grant_mb, + c.max_used_grant_mb, + c.min_reserved_threads, + c.max_reserved_threads, + c.min_used_threads, + c.max_used_threads, + c.total_rows, + c.query_plan + FROM #available_plans AS ap + OUTER APPLY + ( + SELECT + deqs.*, + query_plan = + TRY_CAST(deps.query_plan AS xml) + FROM #dm_exec_query_stats deqs + OUTER APPLY sys.dm_exec_text_query_plan + ( + deqs.plan_handle, + deqs.statement_start_offset, + deqs.statement_end_offset + ) AS deps + WHERE deqs.sql_handle = ap.sql_handle + AND deps.dbid = ap.database_id + ) AS c + ) AS ap + WHERE ap.query_plan IS NOT NULL + ORDER BY + ap.avg_worker_time_ms DESC + OPTION(RECOMPILE, LOOP JOIN, HASH JOIN); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Returning findings %s', 0, 1, @d) WITH NOWAIT; + + SELECT + df.check_id, + df.database_name, + df.object_name, + df.finding_group, + df.finding + FROM #deadlock_findings AS df + ORDER BY + df.check_id, + df.sort_order + OPTION(RECOMPILE); + + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + END; /*done with output to client app.*/ + END; + IF @Debug = 1 + BEGIN + SELECT + table_name = N'#deadlock_data', + * + FROM #deadlock_data AS dd + OPTION(RECOMPILE); - END; - ELSE IF (SUBSTRING(@OutputTableNamePerfmonStats, 2, 2) = '##') - BEGIN - SET @StringToExecute = N' IF (OBJECT_ID(''tempdb..' - + @OutputTableNamePerfmonStats - + ''') IS NULL) CREATE TABLE ' - + @OutputTableNamePerfmonStats - + ' (ID INT IDENTITY(1,1) NOT NULL, - ServerName NVARCHAR(128), - CheckDate DATETIMEOFFSET, - [object_name] NVARCHAR(128) NOT NULL, - [counter_name] NVARCHAR(128) NOT NULL, - [instance_name] NVARCHAR(128) NULL, - [cntr_value] BIGINT NULL, - [cntr_type] INT NOT NULL, - [value_delta] BIGINT NULL, - [value_per_second] DECIMAL(18,2) NULL, - PRIMARY KEY CLUSTERED (ID ASC));' - + ' INSERT ' - + @OutputTableNamePerfmonStats - + ' (ServerName, CheckDate, object_name, counter_name, instance_name, cntr_value, cntr_type, value_delta, value_per_second) SELECT ' - + CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128)) - + ' @SrvName, @CheckDate, object_name, counter_name, instance_name, cntr_value, cntr_type, value_delta, value_per_second FROM #PerfmonStats WHERE Pass = 2'; + SELECT + table_name = N'#dd', + * + FROM #dd AS d + OPTION(RECOMPILE); - EXEC sp_executesql @StringToExecute, - N'@SrvName NVARCHAR(128), @CheckDate datetimeoffset', - @LocalServerName, @StartSampleTime; - END; - ELSE IF (SUBSTRING(@OutputTableNamePerfmonStats, 2, 1) = '#') - BEGIN - RAISERROR('Due to the nature of Dymamic SQL, only global (i.e. double pound (##)) temp tables are supported for @OutputTableName', 16, 0); - END; + SELECT + table_name = N'#deadlock_resource', + * + FROM #deadlock_resource AS dr + OPTION(RECOMPILE); + SELECT + table_name = N'#deadlock_resource_parallel', + * + FROM #deadlock_resource_parallel AS drp + OPTION(RECOMPILE); - /* @OutputTableNameWaitStats lets us export the results to a permanent table */ - IF @OutputDatabaseName IS NOT NULL - AND @OutputSchemaName IS NOT NULL - AND @OutputTableNameWaitStats IS NOT NULL - AND @OutputTableNameWaitStats NOT LIKE '#%' - AND EXISTS ( SELECT * - FROM sys.databases - WHERE QUOTENAME([name]) = @OutputDatabaseName) - BEGIN - /* Create the table */ - SET @StringToExecute = 'USE ' - + @OutputDatabaseName - + '; IF EXISTS(SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' - + @OutputSchemaName - + ''') AND NOT EXISTS (SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''' - + @OutputSchemaName + ''' AND QUOTENAME(TABLE_NAME) = ''' - + @OutputTableNameWaitStats + ''') ' + @LineFeed - + 'BEGIN' + @LineFeed - + 'CREATE TABLE ' - + @OutputSchemaName + '.' - + @OutputTableNameWaitStats - + ' (ID INT IDENTITY(1,1) NOT NULL, - ServerName NVARCHAR(128), - CheckDate DATETIMEOFFSET, - wait_type NVARCHAR(60), - wait_time_ms BIGINT, - signal_wait_time_ms BIGINT, - waiting_tasks_count BIGINT , - PRIMARY KEY CLUSTERED (ID));' + @LineFeed - + 'CREATE NONCLUSTERED INDEX IX_ServerName_wait_type_CheckDate_Includes ON ' + @OutputSchemaName + '.' + @OutputTableNameWaitStats + @LineFeed - + '(ServerName, wait_type, CheckDate) INCLUDE (wait_time_ms, signal_wait_time_ms, waiting_tasks_count);' + @LineFeed - + 'END'; + SELECT + table_name = N'#deadlock_owner_waiter', + * + FROM #deadlock_owner_waiter AS dow + OPTION(RECOMPILE); - EXEC(@StringToExecute); + SELECT + table_name = N'#deadlock_process', + * + FROM #deadlock_process AS dp + OPTION(RECOMPILE); - /* Create the wait stats category table */ - SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableNameWaitStats_Categories; - IF OBJECT_ID(@ObjectFullName) IS NULL - BEGIN - SET @StringToExecute = 'USE ' - + @OutputDatabaseName - + '; EXEC (''CREATE TABLE ' - + @OutputSchemaName + '.' - + @OutputTableNameWaitStats_Categories + ' (WaitType NVARCHAR(60) PRIMARY KEY CLUSTERED, WaitCategory NVARCHAR(128) NOT NULL, Ignorable BIT DEFAULT 0);'')'; + SELECT + table_name = N'#deadlock_stack', + * + FROM #deadlock_stack AS ds + OPTION(RECOMPILE); - EXEC(@StringToExecute); - END; + SELECT + table_name = N'#deadlocks', + * + FROM #deadlocks AS d + OPTION(RECOMPILE); - /* Make sure the wait stats category table has the current number of rows */ - SET @StringToExecute = 'USE ' - + @OutputDatabaseName - + '; EXEC (''IF (SELECT COALESCE(SUM(1),0) FROM ' + @OutputSchemaName + '.' + @OutputTableNameWaitStats_Categories + ') <> (SELECT COALESCE(SUM(1),0) FROM ##WaitCategories)' + @LineFeed - + 'BEGIN ' + @LineFeed - + 'TRUNCATE TABLE ' + @OutputSchemaName + '.' + @OutputTableNameWaitStats_Categories + @LineFeed - + 'INSERT INTO ' + @OutputSchemaName + '.' + @OutputTableNameWaitStats_Categories + ' (WaitType, WaitCategory, Ignorable) SELECT WaitType, WaitCategory, Ignorable FROM ##WaitCategories;' + @LineFeed - + 'END'')'; + SELECT + table_name = N'#deadlock_results', + * + FROM #deadlock_results AS dr + OPTION(RECOMPILE); - EXEC(@StringToExecute); + SELECT + table_name = N'#x', + * + FROM #x AS x + OPTION(RECOMPILE); + SELECT + table_name = N'@sysAssObjId', + * + FROM @sysAssObjId AS s + OPTION(RECOMPILE); - SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableNameWaitStats_View; + SELECT + table_name = N'#available_plans', + * + FROM #available_plans AS ap + OPTION(RECOMPILE); - /* If the view exists without the most recently added columns, drop it. See Github #2162. */ - IF OBJECT_ID(@ObjectFullName) IS NOT NULL - BEGIN - SET @StringToExecute = N'USE ' + @OutputDatabaseName + N'; IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns - WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''JoinKey'') - DROP VIEW ' + @OutputSchemaName + N'.' + @OutputTableNameWaitStats_View + N';'; + SELECT + table_name = N'#dm_exec_query_stats', + * + FROM #dm_exec_query_stats + OPTION(RECOMPILE); - EXEC(@StringToExecute); - END + SELECT + procedure_parameters = + 'procedure_parameters', + DatabaseName = + @DatabaseName, + StartDate = + @StartDate, + EndDate = + @EndDate, + ObjectName = + @ObjectName, + StoredProcName = + @StoredProcName, + AppName = + @AppName, + HostName = + @HostName, + LoginName = + @LoginName, + EventSessionName = + @EventSessionName, + TargetSessionType = + @TargetSessionType, + VictimsOnly = + @VictimsOnly, + Debug = + @Debug, + Help = + @Help, + Version = + @Version, + VersionDate = + @VersionDate, + VersionCheckMode = + @VersionCheckMode, + OutputDatabaseName = + @OutputDatabaseName, + OutputSchemaName = + @OutputSchemaName, + OutputTableName = + @OutputTableName, + ExportToExcel = + @ExportToExcel; + + SELECT + declared_variables = + 'declared_variables', + DatabaseId = + @DatabaseId, + StartDateUTC = + @StartDateUTC, + EndDateUTC = + @EndDateUTC, + ProductVersion = + @ProductVersion, + ProductVersionMajor = + @ProductVersionMajor, + ProductVersionMinor = + @ProductVersionMinor, + ObjectFullName = + @ObjectFullName, + Azure = + @Azure, + RDS = + @RDS, + d = + @d, + StringToExecute = + @StringToExecute, + StringToExecuteParams = + @StringToExecuteParams, + r = + @r, + OutputTableFindings = + @OutputTableFindings, + DeadlockCount = + @DeadlockCount, + ServerName = + @ServerName, + OutputDatabaseCheck = + @OutputDatabaseCheck, + SessionId = + @SessionId, + TargetSessionId = + @TargetSessionId, + FileName = + @FileName, + inputbuf_bom = + @inputbuf_bom, + deadlock_result = + @deadlock_result; + END; /*End debug*/ + END; /*Final End*/ +GO +IF OBJECT_ID('dbo.sp_BlitzWho') IS NULL + EXEC ('CREATE PROCEDURE dbo.sp_BlitzWho AS RETURN 0;') +GO + +ALTER PROCEDURE dbo.sp_BlitzWho + @Help TINYINT = 0 , + @ShowSleepingSPIDs TINYINT = 0, + @ExpertMode BIT = 0, + @Debug BIT = 0, + @OutputDatabaseName NVARCHAR(256) = NULL , + @OutputSchemaName NVARCHAR(256) = NULL , + @OutputTableName NVARCHAR(256) = NULL , + @OutputTableRetentionDays TINYINT = 3 , + @MinElapsedSeconds INT = 0 , + @MinCPUTime INT = 0 , + @MinLogicalReads INT = 0 , + @MinPhysicalReads INT = 0 , + @MinWrites INT = 0 , + @MinTempdbMB INT = 0 , + @MinRequestedMemoryKB INT = 0 , + @MinBlockingSeconds INT = 0 , + @CheckDateOverride DATETIMEOFFSET = NULL, + @ShowActualParameters BIT = 0, + @GetOuterCommand BIT = 0, + @GetLiveQueryPlan BIT = 0, + @Version VARCHAR(30) = NULL OUTPUT, + @VersionDate DATETIME = NULL OUTPUT, + @VersionCheckMode BIT = 0, + @SortOrder NVARCHAR(256) = N'elapsed time' +AS +BEGIN + SET NOCOUNT ON; + SET STATISTICS XML OFF; + SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + + SELECT @Version = '8.19', @VersionDate = '20240222'; + + IF(@VersionCheckMode = 1) + BEGIN + RETURN; + END; - /* Create the wait stats view */ - IF OBJECT_ID(@ObjectFullName) IS NULL - BEGIN - SET @StringToExecute = 'USE ' - + @OutputDatabaseName - + '; EXEC (''CREATE VIEW ' - + @OutputSchemaName + '.' - + @OutputTableNameWaitStats_View + ' AS ' + @LineFeed - + 'WITH RowDates as' + @LineFeed - + '(' + @LineFeed - + ' SELECT ' + @LineFeed - + ' ROW_NUMBER() OVER (ORDER BY [ServerName], [CheckDate]) ID,' + @LineFeed - + ' [CheckDate]' + @LineFeed - + ' FROM ' + @OutputSchemaName + '.' + @OutputTableNameWaitStats + @LineFeed - + ' GROUP BY [ServerName], [CheckDate]' + @LineFeed - + '),' + @LineFeed - + 'CheckDates as' + @LineFeed - + '(' + @LineFeed - + ' SELECT ThisDate.CheckDate,' + @LineFeed - + ' LastDate.CheckDate as PreviousCheckDate' + @LineFeed - + ' FROM RowDates ThisDate' + @LineFeed - + ' JOIN RowDates LastDate' + @LineFeed - + ' ON ThisDate.ID = LastDate.ID + 1' + @LineFeed - + ')' + @LineFeed - + 'SELECT w.ServerName, w.CheckDate, w.wait_type, COALESCE(wc.WaitCategory, ''''Other'''') AS WaitCategory, COALESCE(wc.Ignorable,0) AS Ignorable' + @LineFeed - + ', DATEDIFF(ss, wPrior.CheckDate, w.CheckDate) AS ElapsedSeconds' + @LineFeed - + ', (w.wait_time_ms - wPrior.wait_time_ms) AS wait_time_ms_delta' + @LineFeed - + ', (w.wait_time_ms - wPrior.wait_time_ms) / 60000.0 AS wait_time_minutes_delta' + @LineFeed - + ', (w.wait_time_ms - wPrior.wait_time_ms) / 1000.0 / DATEDIFF(ss, wPrior.CheckDate, w.CheckDate) AS wait_time_minutes_per_minute' + @LineFeed - + ', (w.signal_wait_time_ms - wPrior.signal_wait_time_ms) AS signal_wait_time_ms_delta' + @LineFeed - + ', (w.waiting_tasks_count - wPrior.waiting_tasks_count) AS waiting_tasks_count_delta' + @LineFeed - + ', w.ServerName + CAST(w.CheckDate AS NVARCHAR(50)) AS JoinKey' + @LineFeed - + 'FROM ' + @OutputSchemaName + '.' + @OutputTableNameWaitStats + ' w' + @LineFeed - + 'INNER HASH JOIN CheckDates Dates' + @LineFeed - + 'ON Dates.CheckDate = w.CheckDate' + @LineFeed - + 'INNER JOIN ' + @OutputSchemaName + '.' + @OutputTableNameWaitStats + ' wPrior ON w.ServerName = wPrior.ServerName AND w.wait_type = wPrior.wait_type AND Dates.PreviousCheckDate = wPrior.CheckDate' + @LineFeed - + 'LEFT OUTER JOIN ' + @OutputSchemaName + '.' + @OutputTableNameWaitStats_Categories + ' wc ON w.wait_type = wc.WaitType' + @LineFeed - + 'WHERE DATEDIFF(MI, wPrior.CheckDate, w.CheckDate) BETWEEN 1 AND 60' + @LineFeed - + 'AND [w].[wait_time_ms] >= [wPrior].[wait_time_ms];'')' - EXEC(@StringToExecute); - END; + IF @Help = 1 + BEGIN + PRINT ' +sp_BlitzWho from http://FirstResponderKit.org + +This script gives you a snapshot of everything currently executing on your SQL Server. + +To learn more, visit http://FirstResponderKit.org where you can download new +versions for free, watch training videos on how it works, get more info on +the findings, contribute your own code, and more. + +Known limitations of this version: + - Only Microsoft-supported versions of SQL Server. Sorry, 2005 and 2000. + - Outputting to table is only supported with SQL Server 2012 and higher. + - If @OutputDatabaseName and @OutputSchemaName are populated, the database and + schema must already exist. We will not create them, only the table. + +MIT License +Copyright (c) Brent Ozar Unlimited - SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' - + @OutputSchemaName + ''') INSERT ' - + @OutputDatabaseName + '.' - + @OutputSchemaName + '.' - + @OutputTableNameWaitStats - + ' (ServerName, CheckDate, wait_type, wait_time_ms, signal_wait_time_ms, waiting_tasks_count) SELECT ' - + ' @SrvName, @CheckDate, wait_type, wait_time_ms, signal_wait_time_ms, waiting_tasks_count FROM #WaitStats WHERE Pass = 2 AND wait_time_ms > 0 AND waiting_tasks_count > 0'; +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: - EXEC sp_executesql @StringToExecute, - N'@SrvName NVARCHAR(128), @CheckDate datetimeoffset', - @LocalServerName, @StartSampleTime; +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. - /* Delete history older than @OutputTableRetentionDays */ - SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' - + @OutputSchemaName + ''') DELETE ' - + @OutputDatabaseName + '.' - + @OutputSchemaName + '.' - + @OutputTableNameWaitStats - + ' WHERE ServerName = @SrvName AND CheckDate < @CheckDate ;'; +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +'; +RETURN; +END; /* @Help = 1 */ - EXEC sp_executesql @StringToExecute, - N'@SrvName NVARCHAR(128), @CheckDate date', - @LocalServerName, @OutputTableCleanupDate; +/* Get the major and minor build numbers */ +DECLARE @ProductVersion NVARCHAR(128) + ,@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]) + ,@EnhanceFlag BIT = 0 + ,@BlockingCheck NVARCHAR(MAX) + ,@StringToSelect NVARCHAR(MAX) + ,@StringToExecute NVARCHAR(MAX) + ,@OutputTableCleanupDate DATE + ,@SessionWaits BIT = 0 + ,@SessionWaitsSQL NVARCHAR(MAX) = + N'LEFT JOIN ( SELECT DISTINCT + wait.session_id , + ( SELECT TOP 5 waitwait.wait_type + N'' ('' + + CAST(MAX(waitwait.wait_time_ms) AS NVARCHAR(128)) + + N'' ms), '' + FROM sys.dm_exec_session_wait_stats AS waitwait + WHERE waitwait.session_id = wait.session_id + GROUP BY waitwait.wait_type + HAVING SUM(waitwait.wait_time_ms) > 5 + ORDER BY 1 + FOR + XML PATH('''') ) AS session_wait_info + FROM sys.dm_exec_session_wait_stats AS wait ) AS wt2 + ON s.session_id = wt2.session_id + LEFT JOIN sys.dm_exec_query_stats AS session_stats + ON r.sql_handle = session_stats.sql_handle + AND r.plan_handle = session_stats.plan_handle + AND r.statement_start_offset = session_stats.statement_start_offset + AND r.statement_end_offset = session_stats.statement_end_offset' + ,@ObjectFullName NVARCHAR(2000) + ,@OutputTableNameQueryStats_View NVARCHAR(256) + ,@LineFeed NVARCHAR(MAX) /* Had to set as MAX up from 10 as it was truncating the view creation*/; - END; - ELSE IF (SUBSTRING(@OutputTableNameWaitStats, 2, 2) = '##') - BEGIN - SET @StringToExecute = N' IF (OBJECT_ID(''tempdb..' - + @OutputTableNameWaitStats - + ''') IS NULL) CREATE TABLE ' - + @OutputTableNameWaitStats - + ' (ID INT IDENTITY(1,1) NOT NULL, - ServerName NVARCHAR(128), - CheckDate DATETIMEOFFSET, - wait_type NVARCHAR(60), - wait_time_ms BIGINT, - signal_wait_time_ms BIGINT, - waiting_tasks_count BIGINT , - PRIMARY KEY CLUSTERED (ID ASC));' - + ' INSERT ' - + @OutputTableNameWaitStats - + ' (ServerName, CheckDate, wait_type, wait_time_ms, signal_wait_time_ms, waiting_tasks_count) SELECT ' - + ' @SrvName, @CheckDate, wait_type, wait_time_ms, signal_wait_time_ms, waiting_tasks_count FROM #WaitStats WHERE Pass = 2 AND wait_time_ms > 0 AND waiting_tasks_count > 0'; +/* 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) + +SELECT + @OutputTableNameQueryStats_View = QUOTENAME(@OutputTableName + '_Deltas'), + @OutputDatabaseName = QUOTENAME(@OutputDatabaseName), + @OutputSchemaName = QUOTENAME(@OutputSchemaName), + @OutputTableName = QUOTENAME(@OutputTableName), + @LineFeed = CHAR(13) + CHAR(10); + +IF @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @OutputTableName IS NOT NULL + AND EXISTS ( SELECT * + FROM sys.databases + WHERE QUOTENAME([name]) = @OutputDatabaseName) + BEGIN + SET @ExpertMode = 1; /* Force ExpertMode when we're logging to table */ + + /* Create the table if it doesn't exist */ + SET @StringToExecute = N'USE ' + + @OutputDatabaseName + + N'; IF EXISTS(SELECT * FROM ' + + @OutputDatabaseName + + N'.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' + + @OutputSchemaName + + N''') AND NOT EXISTS (SELECT * FROM ' + + @OutputDatabaseName + + N'.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''' + + @OutputSchemaName + N''' AND QUOTENAME(TABLE_NAME) = ''' + + @OutputTableName + N''') CREATE TABLE ' + + @OutputSchemaName + N'.' + + @OutputTableName + + N'('; + SET @StringToExecute = @StringToExecute + N' + ID INT IDENTITY(1,1) NOT NULL, + ServerName NVARCHAR(128) NOT NULL, + CheckDate DATETIMEOFFSET NOT NULL, + [elapsed_time] [varchar](41) NULL, + [session_id] [smallint] NOT NULL, + [database_name] [nvarchar](128) NULL, + [query_text] [nvarchar](max) NULL, + [outer_command] NVARCHAR(4000) NULL, + [query_plan] [xml] NULL, + [live_query_plan] [xml] NULL, + [cached_parameter_info] [nvarchar](max) NULL, + [live_parameter_info] [nvarchar](max) NULL, + [query_cost] [float] NULL, + [status] [nvarchar](30) NOT NULL, + [wait_info] [nvarchar](max) NULL, + [wait_resource] [nvarchar](max) NULL, + [top_session_waits] [nvarchar](max) NULL, + [blocking_session_id] [smallint] NULL, + [open_transaction_count] [int] NULL, + [is_implicit_transaction] [int] NOT NULL, + [nt_domain] [nvarchar](128) NULL, + [host_name] [nvarchar](128) NULL, + [login_name] [nvarchar](128) NOT NULL, + [nt_user_name] [nvarchar](128) NULL, + [program_name] [nvarchar](128) NULL, + [fix_parameter_sniffing] [nvarchar](150) NULL, + [client_interface_name] [nvarchar](32) NULL, + [login_time] [datetime] NOT NULL, + [start_time] [datetime] NULL, + [request_time] [datetime] NULL, + [request_cpu_time] [int] NULL, + [request_logical_reads] [bigint] NULL, + [request_writes] [bigint] NULL, + [request_physical_reads] [bigint] NULL, + [session_cpu] [int] NOT NULL, + [session_logical_reads] [bigint] NOT NULL, + [session_physical_reads] [bigint] NOT NULL, + [session_writes] [bigint] NOT NULL, + [tempdb_allocations_mb] [decimal](38, 2) NULL, + [memory_usage] [int] NOT NULL, + [estimated_completion_time] [bigint] NULL, + [percent_complete] [real] NULL, + [deadlock_priority] [int] NULL, + [transaction_isolation_level] [varchar](33) NOT NULL, + [degree_of_parallelism] [smallint] NULL, + [last_dop] [bigint] NULL, + [min_dop] [bigint] NULL, + [max_dop] [bigint] NULL, + [last_grant_kb] [bigint] NULL, + [min_grant_kb] [bigint] NULL, + [max_grant_kb] [bigint] NULL, + [last_used_grant_kb] [bigint] NULL, + [min_used_grant_kb] [bigint] NULL, + [max_used_grant_kb] [bigint] NULL, + [last_ideal_grant_kb] [bigint] NULL, + [min_ideal_grant_kb] [bigint] NULL, + [max_ideal_grant_kb] [bigint] NULL, + [last_reserved_threads] [bigint] NULL, + [min_reserved_threads] [bigint] NULL, + [max_reserved_threads] [bigint] NULL, + [last_used_threads] [bigint] NULL, + [min_used_threads] [bigint] NULL, + [max_used_threads] [bigint] NULL, + [grant_time] [varchar](20) NULL, + [requested_memory_kb] [bigint] NULL, + [grant_memory_kb] [bigint] NULL, + [is_request_granted] [varchar](39) NOT NULL, + [required_memory_kb] [bigint] NULL, + [query_memory_grant_used_memory_kb] [bigint] NULL, + [ideal_memory_kb] [bigint] NULL, + [is_small] [bit] NULL, + [timeout_sec] [int] NULL, + [resource_semaphore_id] [smallint] NULL, + [wait_order] [varchar](20) NULL, + [wait_time_ms] [varchar](20) NULL, + [next_candidate_for_memory_grant] [varchar](3) NOT NULL, + [target_memory_kb] [bigint] NULL, + [max_target_memory_kb] [varchar](30) NULL, + [total_memory_kb] [bigint] NULL, + [available_memory_kb] [bigint] NULL, + [granted_memory_kb] [bigint] NULL, + [query_resource_semaphore_used_memory_kb] [bigint] NULL, + [grantee_count] [int] NULL, + [waiter_count] [int] NULL, + [timeout_error_count] [bigint] NULL, + [forced_grant_count] [varchar](30) NULL, + [workload_group_name] [sysname] NULL, + [resource_pool_name] [sysname] NULL, + [context_info] [varchar](128) NULL, + [query_hash] [binary](8) NULL, + [query_plan_hash] [binary](8) NULL, + [sql_handle] [varbinary] (64) NULL, + [plan_handle] [varbinary] (64) NULL, + [statement_start_offset] INT NULL, + [statement_end_offset] INT NULL, + JoinKey AS ServerName + CAST(CheckDate AS NVARCHAR(50)), + PRIMARY KEY CLUSTERED (ID ASC));'; + IF @Debug = 1 + BEGIN + PRINT CONVERT(VARCHAR(8000), SUBSTRING(@StringToExecute, 0, 8000)) + PRINT CONVERT(VARCHAR(8000), SUBSTRING(@StringToExecute, 8000, 16000)) + END + EXEC(@StringToExecute); + + /* If the table doesn't have the new JoinKey computed column, add it. See Github #2162. */ + SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableName; + SET @StringToExecute = N'IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns + WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''JoinKey'') + ALTER TABLE ' + @ObjectFullName + N' ADD JoinKey AS ServerName + CAST(CheckDate AS NVARCHAR(50));'; + EXEC(@StringToExecute); + + /* If the table doesn't have the new cached_parameter_info computed column, add it. See Github #2842. */ + SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableName; + SET @StringToExecute = N'IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns + WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''cached_parameter_info'') + ALTER TABLE ' + @ObjectFullName + N' ADD cached_parameter_info NVARCHAR(MAX) NULL;'; + EXEC(@StringToExecute); - EXEC sp_executesql @StringToExecute, - N'@SrvName NVARCHAR(128), @CheckDate datetimeoffset', - @LocalServerName, @StartSampleTime; - END; - ELSE IF (SUBSTRING(@OutputTableNameWaitStats, 2, 1) = '#') - BEGIN - RAISERROR('Due to the nature of Dymamic SQL, only global (i.e. double pound (##)) temp tables are supported for @OutputTableName', 16, 0); - END; + /* If the table doesn't have the new live_parameter_info computed column, add it. See Github #2842. */ + SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableName; + SET @StringToExecute = N'IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns + WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''live_parameter_info'') + ALTER TABLE ' + @ObjectFullName + N' ADD live_parameter_info NVARCHAR(MAX) NULL;'; + EXEC(@StringToExecute); + + /* If the table doesn't have the new outer_command column, add it. See Github #2887. */ + SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableName; + SET @StringToExecute = N'IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns + WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''outer_command'') + ALTER TABLE ' + @ObjectFullName + N' ADD outer_command NVARCHAR(4000) NULL;'; + EXEC(@StringToExecute); + + /* If the table doesn't have the new wait_resource column, add it. See Github #2970. */ + SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableName; + SET @StringToExecute = N'IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns + WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''wait_resource'') + ALTER TABLE ' + @ObjectFullName + N' ADD wait_resource NVARCHAR(MAX) NULL;'; + EXEC(@StringToExecute); + /* Delete history older than @OutputTableRetentionDays */ + SET @OutputTableCleanupDate = CAST( (DATEADD(DAY, -1 * @OutputTableRetentionDays, GETDATE() ) ) AS DATE); + SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' + + @OutputDatabaseName + + N'.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' + + @OutputSchemaName + N''') DELETE ' + + @OutputDatabaseName + '.' + + @OutputSchemaName + '.' + + @OutputTableName + + N' WHERE ServerName = @SrvName AND CheckDate < @CheckDate;'; + IF @Debug = 1 + BEGIN + PRINT CONVERT(VARCHAR(8000), SUBSTRING(@StringToExecute, 0, 8000)) + PRINT CONVERT(VARCHAR(8000), SUBSTRING(@StringToExecute, 8000, 16000)) + END + EXEC sp_executesql @StringToExecute, + N'@SrvName NVARCHAR(128), @CheckDate date', + @@SERVERNAME, @OutputTableCleanupDate; + SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableNameQueryStats_View; + /* Create the view */ + IF OBJECT_ID(@ObjectFullName) IS NULL + BEGIN + SET @StringToExecute = N'USE ' + + @OutputDatabaseName + + N'; EXEC (''CREATE VIEW ' + + @OutputSchemaName + '.' + + @OutputTableNameQueryStats_View + N' AS ' + @LineFeed + + N'WITH MaxQueryDuration AS ' + @LineFeed + + N'( ' + @LineFeed + + N' SELECT ' + @LineFeed + + N' MIN([ID]) AS [MinID], ' + @LineFeed + + N' MAX([ID]) AS [MaxID] ' + @LineFeed + + N' FROM ' + @OutputSchemaName + '.' + @OutputTableName + '' + @LineFeed + + N' GROUP BY [ServerName], ' + @LineFeed + + N' [session_id], ' + @LineFeed + + N' [database_name], ' + @LineFeed + + N' [request_time], ' + @LineFeed + + N' [start_time], ' + @LineFeed + + N' [sql_handle] ' + @LineFeed + + N') ' + @LineFeed + + N'SELECT ' + @LineFeed + + N' [ID], ' + @LineFeed + + N' [ServerName], ' + @LineFeed + + N' [CheckDate], ' + @LineFeed + + N' [elapsed_time], ' + @LineFeed + + N' [session_id], ' + @LineFeed + + N' [database_name], ' + @LineFeed + + N' [query_text_snippet], ' + @LineFeed + + N' [query_plan], ' + @LineFeed + + N' [live_query_plan], ' + @LineFeed + + N' [query_cost], ' + @LineFeed + + N' [status], ' + @LineFeed + + N' [wait_info], ' + @LineFeed + + N' [wait_resource], ' + @LineFeed + + N' [top_session_waits], ' + @LineFeed + + N' [blocking_session_id], ' + @LineFeed + + N' [open_transaction_count], ' + @LineFeed + + N' [is_implicit_transaction], ' + @LineFeed + + N' [nt_domain], ' + @LineFeed + + N' [host_name], ' + @LineFeed + + N' [login_name], ' + @LineFeed + + N' [nt_user_name], ' + @LineFeed + + N' [program_name], ' + @LineFeed + + N' [fix_parameter_sniffing], ' + @LineFeed + + N' [client_interface_name], ' + @LineFeed + + N' [login_time], ' + @LineFeed + + N' [start_time], ' + @LineFeed + + N' [request_time], ' + @LineFeed + + N' [request_cpu_time], ' + @LineFeed + + N' [degree_of_parallelism], ' + @LineFeed + + N' [request_logical_reads], ' + @LineFeed + + N' [Logical_Reads_MB], ' + @LineFeed + + N' [request_writes], ' + @LineFeed + + N' [Logical_Writes_MB], ' + @LineFeed + + N' [request_physical_reads], ' + @LineFeed + + N' [Physical_reads_MB], ' + @LineFeed + + N' [session_cpu], ' + @LineFeed + + N' [session_logical_reads], ' + @LineFeed + + N' [session_logical_reads_MB], ' + @LineFeed + + N' [session_physical_reads], ' + @LineFeed + + N' [session_physical_reads_MB], ' + @LineFeed + + N' [session_writes], ' + @LineFeed + + N' [session_writes_MB], ' + @LineFeed + + N' [tempdb_allocations_mb], ' + @LineFeed + + N' [memory_usage], ' + @LineFeed + + N' [estimated_completion_time], ' + @LineFeed + + N' [percent_complete], ' + @LineFeed + + N' [deadlock_priority], ' + @LineFeed + + N' [transaction_isolation_level], ' + @LineFeed + + N' [last_dop], ' + @LineFeed + + N' [min_dop], ' + @LineFeed + + N' [max_dop], ' + @LineFeed + + N' [last_grant_kb], ' + @LineFeed + + N' [min_grant_kb], ' + @LineFeed + + N' [max_grant_kb], ' + @LineFeed + + N' [last_used_grant_kb], ' + @LineFeed + + N' [min_used_grant_kb], ' + @LineFeed + + N' [max_used_grant_kb], ' + @LineFeed + + N' [last_ideal_grant_kb], ' + @LineFeed + + N' [min_ideal_grant_kb], ' + @LineFeed + + N' [max_ideal_grant_kb], ' + @LineFeed + + N' [last_reserved_threads], ' + @LineFeed + + N' [min_reserved_threads], ' + @LineFeed + + N' [max_reserved_threads], ' + @LineFeed + + N' [last_used_threads], ' + @LineFeed + + N' [min_used_threads], ' + @LineFeed + + N' [max_used_threads], ' + @LineFeed + + N' [grant_time], ' + @LineFeed + + N' [requested_memory_kb], ' + @LineFeed + + N' [grant_memory_kb], ' + @LineFeed + + N' [is_request_granted], ' + @LineFeed + + N' [required_memory_kb], ' + @LineFeed + + N' [query_memory_grant_used_memory_kb], ' + @LineFeed + + N' [ideal_memory_kb], ' + @LineFeed + + N' [is_small], ' + @LineFeed + + N' [timeout_sec], ' + @LineFeed + + N' [resource_semaphore_id], ' + @LineFeed + + N' [wait_order], ' + @LineFeed + + N' [wait_time_ms], ' + @LineFeed + + N' [next_candidate_for_memory_grant], ' + @LineFeed + + N' [target_memory_kb], ' + @LineFeed + + N' [max_target_memory_kb], ' + @LineFeed + + N' [total_memory_kb], ' + @LineFeed + + N' [available_memory_kb], ' + @LineFeed + + N' [granted_memory_kb], ' + @LineFeed + + N' [query_resource_semaphore_used_memory_kb], ' + @LineFeed + + N' [grantee_count], ' + @LineFeed + + N' [waiter_count], ' + @LineFeed + + N' [timeout_error_count], ' + @LineFeed + + N' [forced_grant_count], ' + @LineFeed + + N' [workload_group_name], ' + @LineFeed + + N' [resource_pool_name], ' + @LineFeed + + N' [context_info], ' + @LineFeed + + N' [query_hash], ' + @LineFeed + + N' [query_plan_hash], ' + @LineFeed + + N' [sql_handle], ' + @LineFeed + + N' [plan_handle], ' + @LineFeed + + N' [statement_start_offset], ' + @LineFeed + + N' [statement_end_offset] ' + @LineFeed + + N' FROM ' + @LineFeed + + N' ( ' + @LineFeed + + N' SELECT ' + @LineFeed + + N' [ID], ' + @LineFeed + + N' [ServerName], ' + @LineFeed + + N' [CheckDate], ' + @LineFeed + + N' [elapsed_time], ' + @LineFeed + + N' [session_id], ' + @LineFeed + + N' [database_name], ' + @LineFeed + + N' /* Truncate the query text to aid performance of painting the rows in SSMS */ ' + @LineFeed + + N' CAST([query_text] AS NVARCHAR(1000)) AS [query_text_snippet], ' + @LineFeed + + N' [query_plan], ' + @LineFeed + + N' [live_query_plan], ' + @LineFeed + + N' [query_cost], ' + @LineFeed + + N' [status], ' + @LineFeed + + N' [wait_info], ' + @LineFeed + + N' [wait_resource], ' + @LineFeed + + N' [top_session_waits], ' + @LineFeed + + N' [blocking_session_id], ' + @LineFeed + + N' [open_transaction_count], ' + @LineFeed + + N' [is_implicit_transaction], ' + @LineFeed + + N' [nt_domain], ' + @LineFeed + + N' [host_name], ' + @LineFeed + + N' [login_name], ' + @LineFeed + + N' [nt_user_name], ' + @LineFeed + + N' [program_name], ' + @LineFeed + + N' [fix_parameter_sniffing], ' + @LineFeed + + N' [client_interface_name], ' + @LineFeed + + N' [login_time], ' + @LineFeed + + N' [start_time], ' + @LineFeed + + N' [request_time], ' + @LineFeed + + N' [request_cpu_time], ' + @LineFeed + + N' [degree_of_parallelism], ' + @LineFeed + + N' [request_logical_reads], ' + @LineFeed + + N' ((CAST([request_logical_reads] AS DECIMAL(38,2))* 8)/ 1024) [Logical_Reads_MB], ' + @LineFeed + + N' [request_writes], ' + @LineFeed + + N' ((CAST([request_writes] AS DECIMAL(38,2))* 8)/ 1024) [Logical_Writes_MB], ' + @LineFeed + + N' [request_physical_reads], ' + @LineFeed + + N' ((CAST([request_physical_reads] AS DECIMAL(38,2))* 8)/ 1024) [Physical_reads_MB], ' + @LineFeed + + N' [session_cpu], ' + @LineFeed + + N' [session_logical_reads], ' + @LineFeed + + N' ((CAST([session_logical_reads] AS DECIMAL(38,2))* 8)/ 1024) [session_logical_reads_MB], ' + @LineFeed + + N' [session_physical_reads], ' + @LineFeed + + N' ((CAST([session_physical_reads] AS DECIMAL(38,2))* 8)/ 1024) [session_physical_reads_MB], ' + @LineFeed + + N' [session_writes], ' + @LineFeed + + N' ((CAST([session_writes] AS DECIMAL(38,2))* 8)/ 1024) [session_writes_MB], ' + @LineFeed + + N' [tempdb_allocations_mb], ' + @LineFeed + + N' [memory_usage], ' + @LineFeed + + N' [estimated_completion_time], ' + @LineFeed + + N' [percent_complete], ' + @LineFeed + + N' [deadlock_priority], ' + @LineFeed + + N' [transaction_isolation_level], ' + @LineFeed + + N' [last_dop], ' + @LineFeed + + N' [min_dop], ' + @LineFeed + + N' [max_dop], ' + @LineFeed + + N' [last_grant_kb], ' + @LineFeed + + N' [min_grant_kb], ' + @LineFeed + + N' [max_grant_kb], ' + @LineFeed + + N' [last_used_grant_kb], ' + @LineFeed + + N' [min_used_grant_kb], ' + @LineFeed + + N' [max_used_grant_kb], ' + @LineFeed + + N' [last_ideal_grant_kb], ' + @LineFeed + + N' [min_ideal_grant_kb], ' + @LineFeed + + N' [max_ideal_grant_kb], ' + @LineFeed + + N' [last_reserved_threads], ' + @LineFeed + + N' [min_reserved_threads], ' + @LineFeed + + N' [max_reserved_threads], ' + @LineFeed + + N' [last_used_threads], ' + @LineFeed + + N' [min_used_threads], ' + @LineFeed + + N' [max_used_threads], ' + @LineFeed + + N' [grant_time], ' + @LineFeed + + N' [requested_memory_kb], ' + @LineFeed + + N' [grant_memory_kb], ' + @LineFeed + + N' [is_request_granted], ' + @LineFeed + + N' [required_memory_kb], ' + @LineFeed + + N' [query_memory_grant_used_memory_kb], ' + @LineFeed + + N' [ideal_memory_kb], ' + @LineFeed + + N' [is_small], ' + @LineFeed + + N' [timeout_sec], ' + @LineFeed + + N' [resource_semaphore_id], ' + @LineFeed + + N' [wait_order], ' + @LineFeed + + N' [wait_time_ms], ' + @LineFeed + + N' [next_candidate_for_memory_grant], ' + @LineFeed + + N' [target_memory_kb], ' + @LineFeed + + N' [max_target_memory_kb], ' + @LineFeed + + N' [total_memory_kb], ' + @LineFeed + + N' [available_memory_kb], ' + @LineFeed + + N' [granted_memory_kb], ' + @LineFeed + + N' [query_resource_semaphore_used_memory_kb], ' + @LineFeed + + N' [grantee_count], ' + @LineFeed + + N' [waiter_count], ' + @LineFeed + + N' [timeout_error_count], ' + @LineFeed + + N' [forced_grant_count], ' + @LineFeed + + N' [workload_group_name], ' + @LineFeed + + N' [resource_pool_name], ' + @LineFeed + + N' [context_info], ' + @LineFeed + + N' [query_hash], ' + @LineFeed + + N' [query_plan_hash], ' + @LineFeed + + N' [sql_handle], ' + @LineFeed + + N' [plan_handle], ' + @LineFeed + + N' [statement_start_offset], ' + @LineFeed + + N' [statement_end_offset] ' + @LineFeed + + N' FROM ' + @OutputSchemaName + '.' + @OutputTableName + '' + @LineFeed + + N' ) AS [BlitzWho] ' + @LineFeed + + N'INNER JOIN [MaxQueryDuration] ON [BlitzWho].[ID] = [MaxQueryDuration].[MaxID]; ' + @LineFeed + + N''');' - DECLARE @separator AS VARCHAR(1); - IF @OutputType = 'RSV' - SET @separator = CHAR(31); - ELSE - SET @separator = ','; + IF @Debug = 1 + BEGIN + PRINT CONVERT(VARCHAR(8000), SUBSTRING(@StringToExecute, 0, 8000)) + PRINT CONVERT(VARCHAR(8000), SUBSTRING(@StringToExecute, 8000, 16000)) + END - IF @OutputType = 'COUNT' AND @SinceStartup = 0 - BEGIN - SELECT COUNT(*) AS Warnings - FROM #BlitzFirstResults; - END; - ELSE - IF @OutputType = 'Opserver1' AND @SinceStartup = 0 AND @OutputResultSets LIKE N'%Findings%' - BEGIN + EXEC(@StringToExecute); + END; - SELECT r.[Priority] , - r.[FindingsGroup] , - r.[Finding] , - r.[URL] , - r.[Details], - r.[HowToStopIt] , - r.[CheckID] , - r.[StartTime], - r.[LoginName], - r.[NTUserName], - r.[OriginalLoginName], - r.[ProgramName], - r.[HostName], - r.[DatabaseID], - r.[DatabaseName], - r.[OpenTransactionCount], - r.[QueryPlan], - r.[QueryText], - qsNow.plan_handle AS PlanHandle, - qsNow.sql_handle AS SqlHandle, - qsNow.statement_start_offset AS StatementStartOffset, - qsNow.statement_end_offset AS StatementEndOffset, - [Executions] = qsNow.execution_count - (COALESCE(qsFirst.execution_count, 0)), - [ExecutionsPercent] = CAST(100.0 * (qsNow.execution_count - (COALESCE(qsFirst.execution_count, 0))) / (qsTotal.execution_count - qsTotalFirst.execution_count) AS DECIMAL(6,2)), - [Duration] = qsNow.total_elapsed_time - (COALESCE(qsFirst.total_elapsed_time, 0)), - [DurationPercent] = CAST(100.0 * (qsNow.total_elapsed_time - (COALESCE(qsFirst.total_elapsed_time, 0))) / (qsTotal.total_elapsed_time - qsTotalFirst.total_elapsed_time) AS DECIMAL(6,2)), - [CPU] = qsNow.total_worker_time - (COALESCE(qsFirst.total_worker_time, 0)), - [CPUPercent] = CAST(100.0 * (qsNow.total_worker_time - (COALESCE(qsFirst.total_worker_time, 0))) / (qsTotal.total_worker_time - qsTotalFirst.total_worker_time) AS DECIMAL(6,2)), - [Reads] = qsNow.total_logical_reads - (COALESCE(qsFirst.total_logical_reads, 0)), - [ReadsPercent] = CAST(100.0 * (qsNow.total_logical_reads - (COALESCE(qsFirst.total_logical_reads, 0))) / (qsTotal.total_logical_reads - qsTotalFirst.total_logical_reads) AS DECIMAL(6,2)), - [PlanCreationTime] = CONVERT(NVARCHAR(100), qsNow.creation_time ,121), - [TotalExecutions] = qsNow.execution_count, - [TotalExecutionsPercent] = CAST(100.0 * qsNow.execution_count / qsTotal.execution_count AS DECIMAL(6,2)), - [TotalDuration] = qsNow.total_elapsed_time, - [TotalDurationPercent] = CAST(100.0 * qsNow.total_elapsed_time / qsTotal.total_elapsed_time AS DECIMAL(6,2)), - [TotalCPU] = qsNow.total_worker_time, - [TotalCPUPercent] = CAST(100.0 * qsNow.total_worker_time / qsTotal.total_worker_time AS DECIMAL(6,2)), - [TotalReads] = qsNow.total_logical_reads, - [TotalReadsPercent] = CAST(100.0 * qsNow.total_logical_reads / qsTotal.total_logical_reads AS DECIMAL(6,2)), - r.[DetailsInt] - FROM #BlitzFirstResults r - LEFT OUTER JOIN #QueryStats qsTotal ON qsTotal.Pass = 0 - LEFT OUTER JOIN #QueryStats qsTotalFirst ON qsTotalFirst.Pass = -1 - LEFT OUTER JOIN #QueryStats qsNow ON r.QueryStatsNowID = qsNow.ID - LEFT OUTER JOIN #QueryStats qsFirst ON r.QueryStatsFirstID = qsFirst.ID - ORDER BY r.Priority , - r.FindingsGroup , - CASE - WHEN r.CheckID = 6 THEN DetailsInt - ELSE 0 - END DESC, - r.Finding, - r.ID; - END; - ELSE IF @OutputType IN ( 'CSV', 'RSV' ) AND @SinceStartup = 0 AND @OutputResultSets LIKE N'%Findings%' - BEGIN + END - SELECT Result = CAST([Priority] AS NVARCHAR(100)) - + @separator + CAST(CheckID AS NVARCHAR(100)) - + @separator + COALESCE([FindingsGroup], - '(N/A)') + @separator - + COALESCE([Finding], '(N/A)') + @separator - + COALESCE(DatabaseName, '(N/A)') + @separator - + COALESCE([URL], '(N/A)') + @separator - + COALESCE([Details], '(N/A)') - FROM #BlitzFirstResults - ORDER BY Priority , - FindingsGroup , - CASE - WHEN CheckID = 6 THEN DetailsInt - ELSE 0 - END DESC, - Finding, - Details; - END; - ELSE IF @OutputType = 'Top10' AND @OutputResultSets LIKE N'%WaitStats%' - BEGIN - /* Measure waits in hours */ - ;WITH max_batch AS ( - SELECT MAX(SampleTime) AS SampleTime - FROM #WaitStats - ) - SELECT TOP 10 - CAST(DATEDIFF(mi,wd1.SampleTime, wd2.SampleTime) / 60.0 AS DECIMAL(18,1)) AS [Hours Sample], - CAST(c.[Total Thread Time (Seconds)] / 60. / 60. AS DECIMAL(18,1)) AS [Thread Time (Hours)], - wd1.wait_type, - COALESCE(wcat.WaitCategory, 'Other') AS wait_category, - CAST(c.[Wait Time (Seconds)] / 60. / 60. AS DECIMAL(18,1)) AS [Wait Time (Hours)], - CAST((wd2.wait_time_ms - wd1.wait_time_ms) / 1000.0 / cores.cpu_count / DATEDIFF(ss, wd1.SampleTime, wd2.SampleTime) AS DECIMAL(18,1)) AS [Per Core Per Hour], - (wd2.waiting_tasks_count - wd1.waiting_tasks_count) AS [Number of Waits], - CASE WHEN (wd2.waiting_tasks_count - wd1.waiting_tasks_count) > 0 - THEN - CAST((wd2.wait_time_ms-wd1.wait_time_ms)/ - (1.0*(wd2.waiting_tasks_count - wd1.waiting_tasks_count)) AS NUMERIC(12,1)) - ELSE 0 END AS [Avg ms Per Wait] - FROM max_batch b - JOIN #WaitStats wd2 ON - wd2.SampleTime =b.SampleTime - JOIN #WaitStats wd1 ON - wd1.wait_type=wd2.wait_type AND - wd2.SampleTime > wd1.SampleTime - CROSS APPLY (SELECT SUM(1) AS cpu_count FROM sys.dm_os_schedulers WHERE status = 'VISIBLE ONLINE' AND is_online = 1) AS cores - CROSS APPLY (SELECT - CAST((wd2.wait_time_ms-wd1.wait_time_ms)/1000. AS DECIMAL(18,1)) AS [Wait Time (Seconds)], - CAST((wd2.signal_wait_time_ms - wd1.signal_wait_time_ms)/1000. AS DECIMAL(18,1)) AS [Signal Wait Time (Seconds)], - CAST((wd2.thread_time_ms)/1000. AS DECIMAL(18,1)) AS [Total Thread Time (Seconds)] - ) AS c - LEFT OUTER JOIN ##WaitCategories wcat ON wd1.wait_type = wcat.WaitType - WHERE (wd2.waiting_tasks_count - wd1.waiting_tasks_count) > 0 - AND wd2.wait_time_ms-wd1.wait_time_ms > 0 - ORDER BY [Wait Time (Seconds)] DESC; - END; - ELSE IF @ExpertMode = 0 AND @OutputType <> 'NONE' AND @OutputXMLasNVARCHAR = 0 AND @SinceStartup = 0 AND @OutputResultSets LIKE N'%Findings%' - BEGIN - SELECT [Priority] , - [FindingsGroup] , - [Finding] , - [URL] , - CAST(@StockDetailsHeader + [Details] + @StockDetailsFooter AS XML) AS Details, - CAST(@StockWarningHeader + HowToStopIt + @StockWarningFooter AS XML) AS HowToStopIt, - [QueryText], - [QueryPlan] - FROM #BlitzFirstResults - WHERE (@Seconds > 0 OR (Priority IN (0, 250, 251, 255))) /* For @Seconds = 0, filter out broken checks for now */ - ORDER BY Priority , - FindingsGroup , - CASE - WHEN CheckID = 6 THEN DetailsInt - ELSE 0 - END DESC, - Finding, - ID, - CAST(Details AS NVARCHAR(4000)); - END; - ELSE IF @OutputType <> 'NONE' AND @OutputXMLasNVARCHAR = 1 AND @SinceStartup = 0 AND @OutputResultSets LIKE N'%Findings%' - BEGIN - SELECT [Priority] , - [FindingsGroup] , - [Finding] , - [URL] , - CAST(LEFT(@StockDetailsHeader + [Details] + @StockDetailsFooter,32000) AS TEXT) AS Details, - CAST(LEFT([HowToStopIt],32000) AS TEXT) AS HowToStopIt, - CAST([QueryText] AS NVARCHAR(MAX)) AS QueryText, - CAST([QueryPlan] AS NVARCHAR(MAX)) AS QueryPlan - FROM #BlitzFirstResults - WHERE (@Seconds > 0 OR (Priority IN (0, 250, 251, 255))) /* For @Seconds = 0, filter out broken checks for now */ - ORDER BY Priority , - FindingsGroup , - CASE - WHEN CheckID = 6 THEN DetailsInt - ELSE 0 - END DESC, - Finding, - ID, - CAST(Details AS NVARCHAR(4000)); - END; - ELSE IF @ExpertMode = 1 AND @OutputType <> 'NONE' AND @OutputResultSets LIKE N'%Findings%' - BEGIN - IF @SinceStartup = 0 - SELECT r.[Priority] , - r.[FindingsGroup] , - r.[Finding] , - r.[URL] , - CAST(@StockDetailsHeader + r.[Details] + @StockDetailsFooter AS XML) AS Details, - CAST(@StockWarningHeader + r.HowToStopIt + @StockWarningFooter AS XML) AS HowToStopIt, - r.[CheckID] , - r.[StartTime], - r.[LoginName], - r.[NTUserName], - r.[OriginalLoginName], - r.[ProgramName], - r.[HostName], - r.[DatabaseID], - r.[DatabaseName], - r.[OpenTransactionCount], - r.[QueryPlan], - r.[QueryText], - qsNow.plan_handle AS PlanHandle, - qsNow.sql_handle AS SqlHandle, - qsNow.statement_start_offset AS StatementStartOffset, - qsNow.statement_end_offset AS StatementEndOffset, - [Executions] = qsNow.execution_count - (COALESCE(qsFirst.execution_count, 0)), - [ExecutionsPercent] = CAST(100.0 * (qsNow.execution_count - (COALESCE(qsFirst.execution_count, 0))) / (qsTotal.execution_count - qsTotalFirst.execution_count) AS DECIMAL(6,2)), - [Duration] = qsNow.total_elapsed_time - (COALESCE(qsFirst.total_elapsed_time, 0)), - [DurationPercent] = CAST(100.0 * (qsNow.total_elapsed_time - (COALESCE(qsFirst.total_elapsed_time, 0))) / (qsTotal.total_elapsed_time - qsTotalFirst.total_elapsed_time) AS DECIMAL(6,2)), - [CPU] = qsNow.total_worker_time - (COALESCE(qsFirst.total_worker_time, 0)), - [CPUPercent] = CAST(100.0 * (qsNow.total_worker_time - (COALESCE(qsFirst.total_worker_time, 0))) / (qsTotal.total_worker_time - qsTotalFirst.total_worker_time) AS DECIMAL(6,2)), - [Reads] = qsNow.total_logical_reads - (COALESCE(qsFirst.total_logical_reads, 0)), - [ReadsPercent] = CAST(100.0 * (qsNow.total_logical_reads - (COALESCE(qsFirst.total_logical_reads, 0))) / (qsTotal.total_logical_reads - qsTotalFirst.total_logical_reads) AS DECIMAL(6,2)), - [PlanCreationTime] = CONVERT(NVARCHAR(100), qsNow.creation_time ,121), - [TotalExecutions] = qsNow.execution_count, - [TotalExecutionsPercent] = CAST(100.0 * qsNow.execution_count / qsTotal.execution_count AS DECIMAL(6,2)), - [TotalDuration] = qsNow.total_elapsed_time, - [TotalDurationPercent] = CAST(100.0 * qsNow.total_elapsed_time / qsTotal.total_elapsed_time AS DECIMAL(6,2)), - [TotalCPU] = qsNow.total_worker_time, - [TotalCPUPercent] = CAST(100.0 * qsNow.total_worker_time / qsTotal.total_worker_time AS DECIMAL(6,2)), - [TotalReads] = qsNow.total_logical_reads, - [TotalReadsPercent] = CAST(100.0 * qsNow.total_logical_reads / qsTotal.total_logical_reads AS DECIMAL(6,2)), - r.[DetailsInt] - FROM #BlitzFirstResults r - LEFT OUTER JOIN #QueryStats qsTotal ON qsTotal.Pass = 0 - LEFT OUTER JOIN #QueryStats qsTotalFirst ON qsTotalFirst.Pass = -1 - LEFT OUTER JOIN #QueryStats qsNow ON r.QueryStatsNowID = qsNow.ID - LEFT OUTER JOIN #QueryStats qsFirst ON r.QueryStatsFirstID = qsFirst.ID - WHERE (@Seconds > 0 OR (Priority IN (0, 250, 251, 255))) /* For @Seconds = 0, filter out broken checks for now */ - ORDER BY r.Priority , - r.FindingsGroup , - CASE - WHEN r.CheckID = 6 THEN DetailsInt - ELSE 0 - END DESC, - r.Finding, - r.ID, - CAST(r.Details AS NVARCHAR(4000)); + IF OBJECT_ID('tempdb..#WhoReadableDBs') IS NOT NULL + DROP TABLE #WhoReadableDBs; + +CREATE TABLE #WhoReadableDBs +( +database_id INT +); + +IF EXISTS (SELECT * FROM sys.all_objects o WHERE o.name = 'dm_hadr_database_replica_states') +BEGIN + RAISERROR('Checking for Read intent databases to exclude',0,0) WITH NOWAIT; + + EXEC('INSERT INTO #WhoReadableDBs (database_id) SELECT DBs.database_id FROM sys.databases DBs INNER JOIN sys.availability_replicas Replicas ON DBs.replica_id = Replicas.replica_id WHERE replica_server_name NOT IN (SELECT DISTINCT primary_replica FROM sys.dm_hadr_availability_group_states States) AND Replicas.secondary_role_allow_connections_desc = ''READ_ONLY'' AND replica_server_name = @@SERVERNAME;'); +END + +SELECT @BlockingCheck = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + + SET LOCK_TIMEOUT 1000; /* To avoid blocking on live query plans. See Github issue #2907. */ + DECLARE @blocked TABLE + ( + dbid SMALLINT NOT NULL, + last_batch DATETIME NOT NULL, + open_tran SMALLINT NOT NULL, + sql_handle BINARY(20) NOT NULL, + session_id SMALLINT NOT NULL, + blocking_session_id SMALLINT NOT NULL, + lastwaittype NCHAR(32) NOT NULL, + waittime BIGINT NOT NULL, + cpu INT NOT NULL, + physical_io BIGINT NOT NULL, + memusage INT NOT NULL + ); + + INSERT @blocked ( dbid, last_batch, open_tran, sql_handle, session_id, blocking_session_id, lastwaittype, waittime, cpu, physical_io, memusage ) + SELECT + sys1.dbid, sys1.last_batch, sys1.open_tran, sys1.sql_handle, + sys2.spid AS session_id, sys2.blocked AS blocking_session_id, sys2.lastwaittype, sys2.waittime, sys2.cpu, sys2.physical_io, sys2.memusage + FROM sys.sysprocesses AS sys1 + JOIN sys.sysprocesses AS sys2 + ON sys1.spid = sys2.blocked; + '+CASE + WHEN (@GetOuterCommand = 1 AND (NOT EXISTS(SELECT 1 FROM sys.all_objects WHERE [name] = N'dm_exec_input_buffer'))) THEN N' + DECLARE @session_id SMALLINT; + DECLARE @Sessions TABLE + ( + session_id INT + ); + + DECLARE @inputbuffer TABLE + ( + ID INT IDENTITY(1,1), + session_id INT, + event_type NVARCHAR(30), + parameters SMALLINT, + event_info NVARCHAR(4000) + ); + + DECLARE inputbuffer_cursor + + CURSOR LOCAL FAST_FORWARD + FOR + SELECT session_id + FROM sys.dm_exec_sessions + WHERE session_id <> @@SPID + AND is_user_process = 1; + + OPEN inputbuffer_cursor; + + FETCH NEXT FROM inputbuffer_cursor INTO @session_id; + + WHILE (@@FETCH_STATUS = 0) + BEGIN; + BEGIN TRY; + + INSERT INTO @inputbuffer ([event_type],[parameters],[event_info]) + EXEC sp_executesql + N''DBCC INPUTBUFFER(@session_id) WITH NO_INFOMSGS;'', + N''@session_id SMALLINT'', + @session_id; + + UPDATE @inputbuffer + SET session_id = @session_id + WHERE ID = SCOPE_IDENTITY(); + + END TRY + BEGIN CATCH + RAISERROR(''DBCC inputbuffer failed for session %d'',0,0,@session_id) WITH NOWAIT; + END CATCH; + + FETCH NEXT FROM inputbuffer_cursor INTO @session_id + + END; + + CLOSE inputbuffer_cursor; + DEALLOCATE inputbuffer_cursor;' + ELSE N'' + END+ + N' + + DECLARE @LiveQueryPlans TABLE + ( + Session_Id INT NOT NULL, + Query_Plan XML NOT NULL + ); + + ' +IF EXISTS (SELECT * FROM sys.all_columns WHERE object_id = OBJECT_ID('sys.dm_exec_query_statistics_xml') AND name = 'query_plan' AND @GetLiveQueryPlan=1) +BEGIN + SET @BlockingCheck = @BlockingCheck + N' + INSERT INTO @LiveQueryPlans + SELECT s.session_id, query_plan + FROM sys.dm_exec_sessions AS s + CROSS APPLY sys.dm_exec_query_statistics_xml(s.session_id) + WHERE s.session_id <> @@SPID;'; +END + + +IF @ProductVersionMajor > 9 and @ProductVersionMajor < 11 +BEGIN + /* Think of the StringToExecute as starting with this, but we'll set this up later depending on whether we're doing an insert or a select: + SELECT @StringToExecute = N'SELECT GETDATE() AS run_date , + */ + SET @StringToExecute = N' CASE WHEN YEAR(s.last_request_start_time) = 1900 THEN NULL ELSE COALESCE( RIGHT(''00'' + CONVERT(VARCHAR(20), (ABS(r.total_elapsed_time) / 1000) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), (DATEADD(SECOND, (r.total_elapsed_time / 1000), 0) + DATEADD(MILLISECOND, (r.total_elapsed_time % 1000), 0)), 114), RIGHT(''00'' + CONVERT(VARCHAR(20), DATEDIFF(SECOND, s.last_request_start_time, GETDATE()) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), DATEADD(SECOND, DATEDIFF(SECOND, s.last_request_start_time, GETDATE()), 0), 114) ) END AS [elapsed_time] , + s.session_id , + CASE WHEN r.blocking_session_id <> 0 AND blocked.session_id IS NULL + THEN r.blocking_session_id + WHEN r.blocking_session_id <> 0 AND s.session_id <> blocked.blocking_session_id + THEN blocked.blocking_session_id + WHEN r.blocking_session_id = 0 AND s.session_id = blocked.session_id + THEN blocked.blocking_session_id + WHEN r.blocking_session_id <> 0 AND s.session_id = blocked.blocking_session_id + THEN r.blocking_session_id + ELSE NULL + END AS blocking_session_id, + COALESCE(DB_NAME(r.database_id), DB_NAME(blocked.dbid), ''N/A'') AS database_name, + ISNULL(SUBSTRING(dest.text, + ( r.statement_start_offset / 2 ) + 1, + ( ( CASE r.statement_end_offset + WHEN -1 THEN DATALENGTH(dest.text) + ELSE r.statement_end_offset + END - r.statement_start_offset ) + / 2 ) + 1), dest.text) AS query_text , + '+CASE + WHEN @GetOuterCommand = 1 THEN N'CAST(event_info AS NVARCHAR(4000)) AS outer_command,' + ELSE N'' + END+N' + derp.query_plan , + qmg.query_cost , + s.status , + CASE + WHEN s.status <> ''sleeping'' THEN COALESCE(wt.wait_info, RTRIM(blocked.lastwaittype) + '' ('' + CONVERT(VARCHAR(10), blocked.waittime) + '')'' ) + ELSE NULL + END AS wait_info , + r.wait_resource , + COALESCE(r.open_transaction_count, blocked.open_tran) AS open_transaction_count , + CASE WHEN EXISTS ( SELECT 1 + FROM sys.dm_tran_active_transactions AS tat + JOIN sys.dm_tran_session_transactions AS tst + ON tst.transaction_id = tat.transaction_id + WHERE tat.name = ''implicit_transaction'' + AND s.session_id = tst.session_id + ) THEN 1 + ELSE 0 + END AS is_implicit_transaction , + s.nt_domain , + s.host_name , + s.login_name , + s.nt_user_name ,' + IF @Platform = 'NonAzure' + BEGIN + SET @StringToExecute += + N'program_name = COALESCE(( + SELECT REPLACE(program_name,Substring(program_name,30,34),''"''+j.name+''"'') + FROM msdb.dbo.sysjobs j WHERE Substring(program_name,32,32) = CONVERT(char(32),CAST(j.job_id AS binary(16)),2) + ),s.program_name)' + END + ELSE + BEGIN + SET @StringToExecute += N's.program_name' + END + + IF @ExpertMode = 1 + BEGIN + SET @StringToExecute += + N', + ''DBCC FREEPROCCACHE ('' + CONVERT(NVARCHAR(128), r.plan_handle, 1) + '');'' AS fix_parameter_sniffing, + s.client_interface_name , + s.login_time , + r.start_time , + qmg.request_time , + COALESCE(r.cpu_time, s.cpu_time) AS request_cpu_time, + COALESCE(r.logical_reads, s.logical_reads) AS request_logical_reads, + COALESCE(r.writes, s.writes) AS request_writes, + COALESCE(r.reads, s.reads) AS request_physical_reads , + s.cpu_time AS session_cpu, + s.logical_reads AS session_logical_reads, + s.reads AS session_physical_reads , + s.writes AS session_writes, + tempdb_allocations.tempdb_allocations_mb, + s.memory_usage , + r.estimated_completion_time , + r.percent_complete , + r.deadlock_priority , + CASE + WHEN s.transaction_isolation_level = 0 THEN ''Unspecified'' + WHEN s.transaction_isolation_level = 1 THEN ''Read Uncommitted'' + WHEN s.transaction_isolation_level = 2 AND EXISTS (SELECT 1 FROM sys.databases WHERE name = DB_NAME(r.database_id) AND is_read_committed_snapshot_on = 1) THEN ''Read Committed Snapshot Isolation'' + WHEN s.transaction_isolation_level = 2 THEN ''Read Committed'' + WHEN s.transaction_isolation_level = 3 THEN ''Repeatable Read'' + WHEN s.transaction_isolation_level = 4 THEN ''Serializable'' + WHEN s.transaction_isolation_level = 5 THEN ''Snapshot'' + ELSE ''WHAT HAVE YOU DONE?'' + END AS transaction_isolation_level , + qmg.dop AS degree_of_parallelism , + COALESCE(CAST(qmg.grant_time AS VARCHAR(20)), ''N/A'') AS grant_time , + qmg.requested_memory_kb , + qmg.granted_memory_kb AS grant_memory_kb, + CASE WHEN qmg.grant_time IS NULL THEN ''N/A'' + WHEN qmg.requested_memory_kb < qmg.granted_memory_kb + THEN ''Query Granted Less Than Query Requested'' + ELSE ''Memory Request Granted'' + END AS is_request_granted , + qmg.required_memory_kb , + qmg.used_memory_kb AS query_memory_grant_used_memory_kb, + qmg.ideal_memory_kb , + qmg.is_small , + qmg.timeout_sec , + qmg.resource_semaphore_id , + COALESCE(CAST(qmg.wait_order AS VARCHAR(20)), ''N/A'') AS wait_order , + COALESCE(CAST(qmg.wait_time_ms AS VARCHAR(20)), + ''N/A'') AS wait_time_ms , + CASE qmg.is_next_candidate + WHEN 0 THEN ''No'' + WHEN 1 THEN ''Yes'' + ELSE ''N/A'' + END AS next_candidate_for_memory_grant , + qrs.target_memory_kb , + COALESCE(CAST(qrs.max_target_memory_kb AS VARCHAR(20)), + ''Small Query Resource Semaphore'') AS max_target_memory_kb , + qrs.total_memory_kb , + qrs.available_memory_kb , + qrs.granted_memory_kb , + qrs.used_memory_kb AS query_resource_semaphore_used_memory_kb, + qrs.grantee_count , + qrs.waiter_count , + qrs.timeout_error_count , + COALESCE(CAST(qrs.forced_grant_count AS VARCHAR(20)), + ''Small Query Resource Semaphore'') AS forced_grant_count, + wg.name AS workload_group_name , + rp.name AS resource_pool_name, + CONVERT(VARCHAR(128), r.context_info) AS context_info + ' + END /* IF @ExpertMode = 1 */ + + SET @StringToExecute += + N'FROM sys.dm_exec_sessions AS s + '+ + CASE + WHEN @GetOuterCommand = 1 THEN CASE + WHEN EXISTS(SELECT 1 FROM sys.all_objects WHERE [name] = N'dm_exec_input_buffer') THEN N'OUTER APPLY sys.dm_exec_input_buffer (s.session_id, 0) AS ib' + ELSE N'LEFT JOIN @inputbuffer ib ON s.session_id = ib.session_id' + END + ELSE N'' + END+N' + LEFT JOIN sys.dm_exec_requests AS r + ON r.session_id = s.session_id + LEFT JOIN ( SELECT DISTINCT + wait.session_id , + ( SELECT waitwait.wait_type + N'' ('' + + CAST(MAX(waitwait.wait_duration_ms) AS NVARCHAR(128)) + + N'' ms) '' + FROM sys.dm_os_waiting_tasks AS waitwait + WHERE waitwait.session_id = wait.session_id + GROUP BY waitwait.wait_type + ORDER BY SUM(waitwait.wait_duration_ms) DESC + FOR + XML PATH('''') ) AS wait_info + FROM sys.dm_os_waiting_tasks AS wait ) AS wt + ON s.session_id = wt.session_id + LEFT JOIN sys.dm_exec_query_stats AS query_stats + ON r.sql_handle = query_stats.sql_handle + AND r.plan_handle = query_stats.plan_handle + AND r.statement_start_offset = query_stats.statement_start_offset + AND r.statement_end_offset = query_stats.statement_end_offset + LEFT JOIN sys.dm_exec_query_memory_grants qmg + ON r.session_id = qmg.session_id + AND r.request_id = qmg.request_id + LEFT JOIN sys.dm_exec_query_resource_semaphores qrs + ON qmg.resource_semaphore_id = qrs.resource_semaphore_id + AND qmg.pool_id = qrs.pool_id + LEFT JOIN sys.resource_governor_workload_groups wg + ON s.group_id = wg.group_id + LEFT JOIN sys.resource_governor_resource_pools rp + ON wg.pool_id = rp.pool_id + OUTER APPLY ( + SELECT TOP 1 + b.dbid, b.last_batch, b.open_tran, b.sql_handle, + b.session_id, b.blocking_session_id, b.lastwaittype, b.waittime + FROM @blocked b + WHERE (s.session_id = b.session_id + OR s.session_id = b.blocking_session_id) + ) AS blocked + OUTER APPLY sys.dm_exec_sql_text(COALESCE(r.sql_handle, blocked.sql_handle)) AS dest + OUTER APPLY sys.dm_exec_query_plan(r.plan_handle) AS derp + OUTER APPLY ( + SELECT CONVERT(DECIMAL(38,2), SUM( ((((tsu.user_objects_alloc_page_count - user_objects_dealloc_page_count) + (tsu.internal_objects_alloc_page_count - internal_objects_dealloc_page_count)) * 8) / 1024.)) ) AS tempdb_allocations_mb + FROM sys.dm_db_task_space_usage tsu + WHERE tsu.request_id = r.request_id + AND tsu.session_id = r.session_id + AND tsu.session_id = s.session_id + ) as tempdb_allocations + WHERE s.session_id <> @@SPID + AND s.host_name IS NOT NULL + ' + + CASE WHEN @ShowSleepingSPIDs = 0 THEN + N' AND COALESCE(DB_NAME(r.database_id), DB_NAME(blocked.dbid)) IS NOT NULL' + WHEN @ShowSleepingSPIDs = 1 THEN + N' OR COALESCE(r.open_transaction_count, blocked.open_tran) >= 1' + ELSE N'' END; +END /* IF @ProductVersionMajor > 9 and @ProductVersionMajor < 11 */ - ------------------------- - --What happened: #WaitStats - ------------------------- - IF @Seconds = 0 AND @OutputResultSets LIKE N'%WaitStats%' - BEGIN - /* Measure waits in hours */ - ;WITH max_batch AS ( - SELECT MAX(SampleTime) AS SampleTime - FROM #WaitStats - ) - SELECT - 'WAIT STATS' AS Pattern, - b.SampleTime AS [Sample Ended], - CAST(DATEDIFF(mi,wd1.SampleTime, wd2.SampleTime) / 60. AS DECIMAL(18,1)) AS [Hours Sample], - CAST(c.[Total Thread Time (Seconds)] / 60. / 60. AS DECIMAL(18,1)) AS [Thread Time (Hours)], - wd1.wait_type, - COALESCE(wcat.WaitCategory, 'Other') AS wait_category, - CAST(c.[Wait Time (Seconds)] / 60. / 60. AS DECIMAL(18,1)) AS [Wait Time (Hours)], - CAST((wd2.wait_time_ms - wd1.wait_time_ms) / 1000.0 / cores.cpu_count / DATEDIFF(ss, wd1.SampleTime, wd2.SampleTime) AS DECIMAL(18,1)) AS [Per Core Per Hour], - CAST(c.[Signal Wait Time (Seconds)] / 60.0 / 60 AS DECIMAL(18,1)) AS [Signal Wait Time (Hours)], - CASE WHEN c.[Wait Time (Seconds)] > 0 - THEN CAST(100.*(c.[Signal Wait Time (Seconds)]/c.[Wait Time (Seconds)]) AS NUMERIC(4,1)) - ELSE 0 END AS [Percent Signal Waits], - (wd2.waiting_tasks_count - wd1.waiting_tasks_count) AS [Number of Waits], - CASE WHEN (wd2.waiting_tasks_count - wd1.waiting_tasks_count) > 0 - THEN - CAST((wd2.wait_time_ms-wd1.wait_time_ms)/ - (1.0*(wd2.waiting_tasks_count - wd1.waiting_tasks_count)) AS NUMERIC(12,1)) - ELSE 0 END AS [Avg ms Per Wait], - N'https://www.sqlskills.com/help/waits/' + LOWER(wd1.wait_type) + '/' AS URL - FROM max_batch b - JOIN #WaitStats wd2 ON - wd2.SampleTime =b.SampleTime - JOIN #WaitStats wd1 ON - wd1.wait_type=wd2.wait_type AND - wd2.SampleTime > wd1.SampleTime - CROSS APPLY (SELECT SUM(1) AS cpu_count FROM sys.dm_os_schedulers WHERE status = 'VISIBLE ONLINE' AND is_online = 1) AS cores - CROSS APPLY (SELECT - CAST((wd2.wait_time_ms-wd1.wait_time_ms)/1000. AS DECIMAL(18,1)) AS [Wait Time (Seconds)], - CAST((wd2.signal_wait_time_ms - wd1.signal_wait_time_ms)/1000. AS DECIMAL(18,1)) AS [Signal Wait Time (Seconds)], - CAST((wd2.thread_time_ms)/1000. AS DECIMAL(18,1)) AS [Total Thread Time (Seconds)] - ) AS c - LEFT OUTER JOIN ##WaitCategories wcat ON wd1.wait_type = wcat.WaitType - WHERE (wd2.waiting_tasks_count - wd1.waiting_tasks_count) > 0 - AND wd2.wait_time_ms-wd1.wait_time_ms > 0 - ORDER BY [Wait Time (Seconds)] DESC; - END; - ELSE IF @OutputResultSets LIKE N'%WaitStats%' - BEGIN - /* Measure waits in seconds */ - ;WITH max_batch AS ( - SELECT MAX(SampleTime) AS SampleTime - FROM #WaitStats - ) - SELECT - 'WAIT STATS' AS Pattern, - b.SampleTime AS [Sample Ended], - DATEDIFF(ss,wd1.SampleTime, wd2.SampleTime) AS [Seconds Sample], - c.[Total Thread Time (Seconds)], - wd1.wait_type, - COALESCE(wcat.WaitCategory, 'Other') AS wait_category, - c.[Wait Time (Seconds)], - CAST((CAST(wd2.wait_time_ms - wd1.wait_time_ms AS MONEY)) / 1000.0 / cores.cpu_count / DATEDIFF(ss, wd1.SampleTime, wd2.SampleTime) AS DECIMAL(18,1)) AS [Per Core Per Second], - c.[Signal Wait Time (Seconds)], - CASE WHEN c.[Wait Time (Seconds)] > 0 - THEN CAST(100.*(c.[Signal Wait Time (Seconds)]/c.[Wait Time (Seconds)]) AS NUMERIC(4,1)) - ELSE 0 END AS [Percent Signal Waits], - (wd2.waiting_tasks_count - wd1.waiting_tasks_count) AS [Number of Waits], - CASE WHEN (wd2.waiting_tasks_count - wd1.waiting_tasks_count) > 0 - THEN - CAST((wd2.wait_time_ms-wd1.wait_time_ms)/ - (1.0*(wd2.waiting_tasks_count - wd1.waiting_tasks_count)) AS NUMERIC(12,1)) - ELSE 0 END AS [Avg ms Per Wait], - N'https://www.sqlskills.com/help/waits/' + LOWER(wd1.wait_type) + '/' AS URL - FROM max_batch b - JOIN #WaitStats wd2 ON - wd2.SampleTime =b.SampleTime - JOIN #WaitStats wd1 ON - wd1.wait_type=wd2.wait_type AND - wd2.SampleTime > wd1.SampleTime - CROSS APPLY (SELECT SUM(1) AS cpu_count FROM sys.dm_os_schedulers WHERE status = 'VISIBLE ONLINE' AND is_online = 1) AS cores - CROSS APPLY (SELECT - CAST((wd2.wait_time_ms-wd1.wait_time_ms)/1000. AS DECIMAL(18,1)) AS [Wait Time (Seconds)], - CAST((wd2.signal_wait_time_ms - wd1.signal_wait_time_ms)/1000. AS DECIMAL(18,1)) AS [Signal Wait Time (Seconds)], - CAST((wd2.thread_time_ms - wd1.thread_time_ms)/1000. AS DECIMAL(18,1)) AS [Total Thread Time (Seconds)] - ) AS c - LEFT OUTER JOIN ##WaitCategories wcat ON wd1.wait_type = wcat.WaitType - WHERE (wd2.waiting_tasks_count - wd1.waiting_tasks_count) > 0 - AND wd2.wait_time_ms-wd1.wait_time_ms > 0 - ORDER BY [Wait Time (Seconds)] DESC; - END; +IF @ProductVersionMajor >= 11 + BEGIN + SELECT @EnhanceFlag = + CASE WHEN @ProductVersionMajor = 11 AND @ProductVersionMinor >= 6020 THEN 1 + WHEN @ProductVersionMajor = 12 AND @ProductVersionMinor >= 5000 THEN 1 + WHEN @ProductVersionMajor = 13 AND @ProductVersionMinor >= 1601 THEN 1 + WHEN @ProductVersionMajor > 13 THEN 1 + ELSE 0 + END + + + IF OBJECT_ID('sys.dm_exec_session_wait_stats') IS NOT NULL + BEGIN + SET @SessionWaits = 1 + END + + /* Think of the StringToExecute as starting with this, but we'll set this up later depending on whether we're doing an insert or a select: + SELECT @StringToExecute = N'SELECT GETDATE() AS run_date , + */ + SELECT @StringToExecute = N' CASE WHEN YEAR(s.last_request_start_time) = 1900 THEN NULL ELSE COALESCE( RIGHT(''00'' + CONVERT(VARCHAR(20), (ABS(r.total_elapsed_time) / 1000) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), (DATEADD(SECOND, (r.total_elapsed_time / 1000), 0) + DATEADD(MILLISECOND, (r.total_elapsed_time % 1000), 0)), 114), RIGHT(''00'' + CONVERT(VARCHAR(20), DATEDIFF(SECOND, s.last_request_start_time, GETDATE()) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), DATEADD(SECOND, DATEDIFF(SECOND, s.last_request_start_time, GETDATE()), 0), 114) ) END AS [elapsed_time] , + s.session_id , + CASE WHEN r.blocking_session_id <> 0 AND blocked.session_id IS NULL + THEN r.blocking_session_id + WHEN r.blocking_session_id <> 0 AND s.session_id <> blocked.blocking_session_id + THEN blocked.blocking_session_id + WHEN r.blocking_session_id = 0 AND s.session_id = blocked.session_id + THEN blocked.blocking_session_id + WHEN r.blocking_session_id <> 0 AND s.session_id = blocked.blocking_session_id + THEN r.blocking_session_id + ELSE NULL + END AS blocking_session_id, + COALESCE(DB_NAME(r.database_id), DB_NAME(blocked.dbid), ''N/A'') AS database_name, + ISNULL(SUBSTRING(dest.text, + ( r.statement_start_offset / 2 ) + 1, + ( ( CASE r.statement_end_offset + WHEN -1 THEN DATALENGTH(dest.text) + ELSE r.statement_end_offset + END - r.statement_start_offset ) + / 2 ) + 1), dest.text) AS query_text , + '+CASE + WHEN @GetOuterCommand = 1 THEN N'CAST(event_info AS NVARCHAR(4000)) AS outer_command,' + ELSE N'' + END+N' + derp.query_plan , + CAST(COALESCE(qs_live.Query_Plan, ' + CASE WHEN @GetLiveQueryPlan=1 + THEN '''''' + ELSE '''''' + END + +') AS XML + + + ) AS live_query_plan , + STUFF((SELECT DISTINCT N'', '' + Node.Data.value(''(@Column)[1]'', ''NVARCHAR(4000)'') + N'' {'' + Node.Data.value(''(@ParameterDataType)[1]'', ''NVARCHAR(4000)'') + N''}: '' + Node.Data.value(''(@ParameterCompiledValue)[1]'', ''NVARCHAR(4000)'') + FROM derp.query_plan.nodes(''/*:ShowPlanXML/*:BatchSequence/*:Batch/*:Statements/*:StmtSimple/*:QueryPlan/*:ParameterList/*:ColumnReference'') AS Node(Data) + FOR XML PATH('''')), 1,2,'''') + AS Cached_Parameter_Info, + ' + IF @ShowActualParameters = 1 + BEGIN + SELECT @StringToExecute = @StringToExecute + N'qs_live.Live_Parameter_Info as Live_Parameter_Info,' + END + + SELECT @StringToExecute = @StringToExecute + N' + qmg.query_cost , + s.status , + CASE + WHEN s.status <> ''sleeping'' THEN COALESCE(wt.wait_info, RTRIM(blocked.lastwaittype) + '' ('' + CONVERT(VARCHAR(10), blocked.waittime) + '')'' ) + ELSE NULL + END AS wait_info , + r.wait_resource ,' + + + CASE @SessionWaits + WHEN 1 THEN + N'SUBSTRING(wt2.session_wait_info, 0, LEN(wt2.session_wait_info) ) AS top_session_waits ,' + ELSE N' NULL AS top_session_waits ,' + END + + + N'COALESCE(r.open_transaction_count, blocked.open_tran) AS open_transaction_count , + CASE WHEN EXISTS ( SELECT 1 + FROM sys.dm_tran_active_transactions AS tat + JOIN sys.dm_tran_session_transactions AS tst + ON tst.transaction_id = tat.transaction_id + WHERE tat.name = ''implicit_transaction'' + AND s.session_id = tst.session_id + ) THEN 1 + ELSE 0 + END AS is_implicit_transaction , + s.nt_domain , + s.host_name , + s.login_name , + s.nt_user_name ,' + IF @Platform = 'NonAzure' + BEGIN + SET @StringToExecute += + N'program_name = COALESCE(( + SELECT REPLACE(program_name,Substring(program_name,30,34),''"''+j.name+''"'') + FROM msdb.dbo.sysjobs j WHERE Substring(program_name,32,32) = CONVERT(char(32),CAST(j.job_id AS binary(16)),2) + ),s.program_name)' + END + ELSE + BEGIN + SET @StringToExecute += N's.program_name' + END + + IF @ExpertMode = 1 /* We show more columns in expert mode, so the SELECT gets longer */ + BEGIN + SET @StringToExecute += + N', ''DBCC FREEPROCCACHE ('' + CONVERT(NVARCHAR(128), r.plan_handle, 1) + '');'' AS fix_parameter_sniffing, + s.client_interface_name , + s.login_time , + r.start_time , + qmg.request_time , + COALESCE(r.cpu_time, s.cpu_time) AS request_cpu_time, + COALESCE(r.logical_reads, s.logical_reads) AS request_logical_reads, + COALESCE(r.writes, s.writes) AS request_writes, + COALESCE(r.reads, s.reads) AS request_physical_reads , + s.cpu_time AS session_cpu, + s.logical_reads AS session_logical_reads, + s.reads AS session_physical_reads , + s.writes AS session_writes, + tempdb_allocations.tempdb_allocations_mb, + s.memory_usage , + r.estimated_completion_time , + r.percent_complete , + r.deadlock_priority , + CASE + WHEN s.transaction_isolation_level = 0 THEN ''Unspecified'' + WHEN s.transaction_isolation_level = 1 THEN ''Read Uncommitted'' + WHEN s.transaction_isolation_level = 2 AND EXISTS (SELECT 1 FROM sys.databases WHERE name = DB_NAME(r.database_id) AND is_read_committed_snapshot_on = 1) THEN ''Read Committed Snapshot Isolation'' + WHEN s.transaction_isolation_level = 2 THEN ''Read Committed'' + WHEN s.transaction_isolation_level = 3 THEN ''Repeatable Read'' + WHEN s.transaction_isolation_level = 4 THEN ''Serializable'' + WHEN s.transaction_isolation_level = 5 THEN ''Snapshot'' + ELSE ''WHAT HAVE YOU DONE?'' + END AS transaction_isolation_level , + qmg.dop AS degree_of_parallelism , ' + + + CASE @EnhanceFlag + WHEN 1 THEN N'query_stats.last_dop, + query_stats.min_dop, + query_stats.max_dop, + query_stats.last_grant_kb, + query_stats.min_grant_kb, + query_stats.max_grant_kb, + query_stats.last_used_grant_kb, + query_stats.min_used_grant_kb, + query_stats.max_used_grant_kb, + query_stats.last_ideal_grant_kb, + query_stats.min_ideal_grant_kb, + query_stats.max_ideal_grant_kb, + query_stats.last_reserved_threads, + query_stats.min_reserved_threads, + query_stats.max_reserved_threads, + query_stats.last_used_threads, + query_stats.min_used_threads, + query_stats.max_used_threads,' + ELSE N' NULL AS last_dop, + NULL AS min_dop, + NULL AS max_dop, + NULL AS last_grant_kb, + NULL AS min_grant_kb, + NULL AS max_grant_kb, + NULL AS last_used_grant_kb, + NULL AS min_used_grant_kb, + NULL AS max_used_grant_kb, + NULL AS last_ideal_grant_kb, + NULL AS min_ideal_grant_kb, + NULL AS max_ideal_grant_kb, + NULL AS last_reserved_threads, + NULL AS min_reserved_threads, + NULL AS max_reserved_threads, + NULL AS last_used_threads, + NULL AS min_used_threads, + NULL AS max_used_threads,' + END - ------------------------- - --What happened: #FileStats - ------------------------- - IF @OutputResultSets LIKE N'%FileStats%' - WITH readstats AS ( - SELECT 'PHYSICAL READS' AS Pattern, - ROW_NUMBER() OVER (ORDER BY wd2.avg_stall_read_ms DESC) AS StallRank, - wd2.SampleTime AS [Sample Time], - DATEDIFF(ss,wd1.SampleTime, wd2.SampleTime) AS [Sample (seconds)], - wd1.DatabaseName , - wd1.FileLogicalName AS [File Name], - UPPER(SUBSTRING(wd1.PhysicalName, 1, 2)) AS [Drive] , - wd1.SizeOnDiskMB , - ( wd2.num_of_reads - wd1.num_of_reads ) AS [# Reads/Writes], - CASE WHEN wd2.num_of_reads - wd1.num_of_reads > 0 - THEN CAST(( wd2.bytes_read - wd1.bytes_read)/1024./1024. AS NUMERIC(21,1)) - ELSE 0 - END AS [MB Read/Written], - wd2.avg_stall_read_ms AS [Avg Stall (ms)], - wd1.PhysicalName AS [file physical name] - FROM #FileStats wd2 - JOIN #FileStats wd1 ON wd2.SampleTime > wd1.SampleTime - AND wd1.DatabaseID = wd2.DatabaseID - AND wd1.FileID = wd2.FileID - ), - writestats AS ( - SELECT - 'PHYSICAL WRITES' AS Pattern, - ROW_NUMBER() OVER (ORDER BY wd2.avg_stall_write_ms DESC) AS StallRank, - wd2.SampleTime AS [Sample Time], - DATEDIFF(ss,wd1.SampleTime, wd2.SampleTime) AS [Sample (seconds)], - wd1.DatabaseName , - wd1.FileLogicalName AS [File Name], - UPPER(SUBSTRING(wd1.PhysicalName, 1, 2)) AS [Drive] , - wd1.SizeOnDiskMB , - ( wd2.num_of_writes - wd1.num_of_writes ) AS [# Reads/Writes], - CASE WHEN wd2.num_of_writes - wd1.num_of_writes > 0 - THEN CAST(( wd2.bytes_written - wd1.bytes_written)/1024./1024. AS NUMERIC(21,1)) - ELSE 0 - END AS [MB Read/Written], - wd2.avg_stall_write_ms AS [Avg Stall (ms)], - wd1.PhysicalName AS [file physical name] - FROM #FileStats wd2 - JOIN #FileStats wd1 ON wd2.SampleTime > wd1.SampleTime - AND wd1.DatabaseID = wd2.DatabaseID - AND wd1.FileID = wd2.FileID - ) - SELECT - Pattern, [Sample Time], [Sample (seconds)], [File Name], [Drive], [# Reads/Writes],[MB Read/Written],[Avg Stall (ms)], [file physical name], [DatabaseName], [StallRank] - FROM readstats - WHERE StallRank <=20 AND [MB Read/Written] > 0 - UNION ALL - SELECT Pattern, [Sample Time], [Sample (seconds)], [File Name], [Drive], [# Reads/Writes],[MB Read/Written],[Avg Stall (ms)], [file physical name], [DatabaseName], [StallRank] - FROM writestats - WHERE StallRank <=20 AND [MB Read/Written] > 0 - ORDER BY Pattern, StallRank; + SET @StringToExecute += + N' + COALESCE(CAST(qmg.grant_time AS VARCHAR(20)), ''Memory Not Granted'') AS grant_time , + qmg.requested_memory_kb , + qmg.granted_memory_kb AS grant_memory_kb, + CASE WHEN qmg.grant_time IS NULL THEN ''N/A'' + WHEN qmg.requested_memory_kb < qmg.granted_memory_kb + THEN ''Query Granted Less Than Query Requested'' + ELSE ''Memory Request Granted'' + END AS is_request_granted , + qmg.required_memory_kb , + qmg.used_memory_kb AS query_memory_grant_used_memory_kb, + qmg.ideal_memory_kb , + qmg.is_small , + qmg.timeout_sec , + qmg.resource_semaphore_id , + COALESCE(CAST(qmg.wait_order AS VARCHAR(20)), ''N/A'') AS wait_order , + COALESCE(CAST(qmg.wait_time_ms AS VARCHAR(20)), + ''N/A'') AS wait_time_ms , + CASE qmg.is_next_candidate + WHEN 0 THEN ''No'' + WHEN 1 THEN ''Yes'' + ELSE ''N/A'' + END AS next_candidate_for_memory_grant , + qrs.target_memory_kb , + COALESCE(CAST(qrs.max_target_memory_kb AS VARCHAR(20)), + ''Small Query Resource Semaphore'') AS max_target_memory_kb , + qrs.total_memory_kb , + qrs.available_memory_kb , + qrs.granted_memory_kb , + qrs.used_memory_kb AS query_resource_semaphore_used_memory_kb, + qrs.grantee_count , + qrs.waiter_count , + qrs.timeout_error_count , + COALESCE(CAST(qrs.forced_grant_count AS VARCHAR(20)), + ''Small Query Resource Semaphore'') AS forced_grant_count, + wg.name AS workload_group_name, + rp.name AS resource_pool_name, + CONVERT(VARCHAR(128), r.context_info) AS context_info, + r.query_hash, r.query_plan_hash, r.sql_handle, r.plan_handle, r.statement_start_offset, r.statement_end_offset ' + END /* IF @ExpertMode = 1 */ + + SET @StringToExecute += + N' FROM sys.dm_exec_sessions AS s'+ + CASE + WHEN @GetOuterCommand = 1 THEN CASE + WHEN EXISTS(SELECT 1 FROM sys.all_objects WHERE [name] = N'dm_exec_input_buffer') THEN N' + OUTER APPLY sys.dm_exec_input_buffer (s.session_id, 0) AS ib' + ELSE N' + LEFT JOIN @inputbuffer ib ON s.session_id = ib.session_id' + END + ELSE N'' + END+N' + LEFT JOIN sys.dm_exec_requests AS r + ON r.session_id = s.session_id + LEFT JOIN ( SELECT DISTINCT + wait.session_id , + ( SELECT waitwait.wait_type + N'' ('' + + CAST(MAX(waitwait.wait_duration_ms) AS NVARCHAR(128)) + + N'' ms) '' + FROM sys.dm_os_waiting_tasks AS waitwait + WHERE waitwait.session_id = wait.session_id + GROUP BY waitwait.wait_type + ORDER BY SUM(waitwait.wait_duration_ms) DESC + FOR + XML PATH('''') ) AS wait_info + FROM sys.dm_os_waiting_tasks AS wait ) AS wt + ON s.session_id = wt.session_id + LEFT JOIN sys.dm_exec_query_stats AS query_stats + ON r.sql_handle = query_stats.sql_handle + AND r.plan_handle = query_stats.plan_handle + AND r.statement_start_offset = query_stats.statement_start_offset + AND r.statement_end_offset = query_stats.statement_end_offset + ' + + + CASE @SessionWaits + WHEN 1 THEN @SessionWaitsSQL + ELSE N'' + END + + + N' + LEFT JOIN sys.dm_exec_query_memory_grants qmg + ON r.session_id = qmg.session_id + AND r.request_id = qmg.request_id + LEFT JOIN sys.dm_exec_query_resource_semaphores qrs + ON qmg.resource_semaphore_id = qrs.resource_semaphore_id + AND qmg.pool_id = qrs.pool_id + LEFT JOIN sys.resource_governor_workload_groups wg + ON s.group_id = wg.group_id + LEFT JOIN sys.resource_governor_resource_pools rp + ON wg.pool_id = rp.pool_id + OUTER APPLY ( + SELECT TOP 1 + b.dbid, b.last_batch, b.open_tran, b.sql_handle, + b.session_id, b.blocking_session_id, b.lastwaittype, b.waittime + FROM @blocked b + WHERE (s.session_id = b.session_id + OR s.session_id = b.blocking_session_id) + ) AS blocked + OUTER APPLY sys.dm_exec_sql_text(COALESCE(r.sql_handle, blocked.sql_handle)) AS dest + OUTER APPLY sys.dm_exec_query_plan(r.plan_handle) AS derp + OUTER APPLY ( + SELECT CONVERT(DECIMAL(38,2), SUM( ((((tsu.user_objects_alloc_page_count - user_objects_dealloc_page_count) + (tsu.internal_objects_alloc_page_count - internal_objects_dealloc_page_count)) * 8) / 1024.)) ) AS tempdb_allocations_mb + FROM sys.dm_db_task_space_usage tsu + WHERE tsu.request_id = r.request_id + AND tsu.session_id = r.session_id + AND tsu.session_id = s.session_id + ) as tempdb_allocations + + OUTER APPLY ( + SELECT TOP 1 Query_Plan, + STUFF((SELECT DISTINCT N'', '' + Node.Data.value(''(@Column)[1]'', ''NVARCHAR(4000)'') + N'' {'' + Node.Data.value(''(@ParameterDataType)[1]'', ''NVARCHAR(4000)'') + N''}: '' + Node.Data.value(''(@ParameterCompiledValue)[1]'', ''NVARCHAR(4000)'') + N'' (Actual: '' + Node.Data.value(''(@ParameterRuntimeValue)[1]'', ''NVARCHAR(4000)'') + N'')'' + FROM q.Query_Plan.nodes(''/*:ShowPlanXML/*:BatchSequence/*:Batch/*:Statements/*:StmtSimple/*:QueryPlan/*:ParameterList/*:ColumnReference'') AS Node(Data) + FOR XML PATH('''')), 1,2,'''') + AS Live_Parameter_Info + FROM @LiveQueryPlans q + WHERE (s.session_id = q.Session_Id) + ) AS qs_live - ------------------------- - --What happened: #PerfmonStats - ------------------------- + WHERE s.session_id <> @@SPID + AND s.host_name IS NOT NULL + AND r.database_id NOT IN (SELECT database_id FROM #WhoReadableDBs) + ' + + CASE WHEN @ShowSleepingSPIDs = 0 THEN + N' AND COALESCE(DB_NAME(r.database_id), DB_NAME(blocked.dbid)) IS NOT NULL' + WHEN @ShowSleepingSPIDs = 1 THEN + N' OR COALESCE(r.open_transaction_count, blocked.open_tran) >= 1' + ELSE N'' END; - IF @OutputResultSets LIKE N'%PerfmonStats%' - SELECT 'PERFMON' AS Pattern, pLast.[object_name], pLast.counter_name, pLast.instance_name, - pFirst.SampleTime AS FirstSampleTime, pFirst.cntr_value AS FirstSampleValue, - pLast.SampleTime AS LastSampleTime, pLast.cntr_value AS LastSampleValue, - pLast.cntr_value - pFirst.cntr_value AS ValueDelta, - ((1.0 * pLast.cntr_value - pFirst.cntr_value) / DATEDIFF(ss, pFirst.SampleTime, pLast.SampleTime)) AS ValuePerSecond - FROM #PerfmonStats pLast - INNER JOIN #PerfmonStats pFirst ON pFirst.[object_name] = pLast.[object_name] AND pFirst.counter_name = pLast.counter_name AND (pFirst.instance_name = pLast.instance_name OR (pFirst.instance_name IS NULL AND pLast.instance_name IS NULL)) - AND pLast.ID > pFirst.ID - WHERE pLast.cntr_value <> pFirst.cntr_value - ORDER BY Pattern, pLast.[object_name], pLast.counter_name, pLast.instance_name; +END /* IF @ProductVersionMajor >= 11 */ - ------------------------- - --What happened: #QueryStats - ------------------------- - IF @CheckProcedureCache = 1 AND @OutputResultSets LIKE N'%BlitzCache%' - BEGIN - - SELECT qsNow.*, qsFirst.* - FROM #QueryStats qsNow - INNER JOIN #QueryStats qsFirst ON qsNow.[sql_handle] = qsFirst.[sql_handle] AND qsNow.statement_start_offset = qsFirst.statement_start_offset AND qsNow.statement_end_offset = qsFirst.statement_end_offset AND qsNow.plan_generation_num = qsFirst.plan_generation_num AND qsNow.plan_handle = qsFirst.plan_handle AND qsFirst.Pass = 1 - WHERE qsNow.Pass = 2; - END; - ELSE - BEGIN - SELECT 'Plan Cache' AS [Pattern], 'Plan cache not analyzed' AS [Finding], 'Use @CheckProcedureCache = 1 or run sp_BlitzCache for more analysis' AS [More Info], CONVERT(XML, @StockDetailsHeader + 'firstresponderkit.org' + @StockDetailsFooter) AS [Details]; - END; - END; +IF (@MinElapsedSeconds + @MinCPUTime + @MinLogicalReads + @MinPhysicalReads + @MinWrites + @MinTempdbMB + @MinRequestedMemoryKB + @MinBlockingSeconds) > 0 + BEGIN + /* They're filtering for something, so set up a where clause that will let any (not all combined) of the min triggers work: */ + SET @StringToExecute += N' AND (1 = 0 '; + IF @MinElapsedSeconds > 0 + SET @StringToExecute += N' OR ABS(COALESCE(r.total_elapsed_time,0)) / 1000 >= ' + CAST(@MinElapsedSeconds AS NVARCHAR(20)); + IF @MinCPUTime > 0 + SET @StringToExecute += N' OR COALESCE(r.cpu_time, s.cpu_time,0) / 1000 >= ' + CAST(@MinCPUTime AS NVARCHAR(20)); + IF @MinLogicalReads > 0 + SET @StringToExecute += N' OR COALESCE(r.logical_reads, s.logical_reads,0) >= ' + CAST(@MinLogicalReads AS NVARCHAR(20)); + IF @MinPhysicalReads > 0 + SET @StringToExecute += N' OR COALESCE(s.reads,0) >= ' + CAST(@MinPhysicalReads AS NVARCHAR(20)); + IF @MinWrites > 0 + SET @StringToExecute += N' OR COALESCE(r.writes, s.writes,0) >= ' + CAST(@MinWrites AS NVARCHAR(20)); + IF @MinTempdbMB > 0 + SET @StringToExecute += N' OR COALESCE(tempdb_allocations.tempdb_allocations_mb,0) >= ' + CAST(@MinTempdbMB AS NVARCHAR(20)); + IF @MinRequestedMemoryKB > 0 + SET @StringToExecute += N' OR COALESCE(qmg.requested_memory_kb,0) >= ' + CAST(@MinRequestedMemoryKB AS NVARCHAR(20)); + /* Blocking is a little different - we're going to return ALL of the queries if we meet the blocking threshold. */ + IF @MinBlockingSeconds > 0 + SET @StringToExecute += N' OR (SELECT SUM(waittime / 1000) FROM @blocked) >= ' + CAST(@MinBlockingSeconds AS NVARCHAR(20)); + SET @StringToExecute += N' ) '; + END - DROP TABLE #BlitzFirstResults; +SET @StringToExecute += + N' ORDER BY ' + CASE WHEN @SortOrder = 'session_id' THEN '[session_id] DESC' + WHEN @SortOrder = 'query_cost' THEN '[query_cost] DESC' + WHEN @SortOrder = 'database_name' THEN '[database_name] ASC' + WHEN @SortOrder = 'open_transaction_count' THEN '[open_transaction_count] DESC' + WHEN @SortOrder = 'is_implicit_transaction' THEN '[is_implicit_transaction] DESC' + WHEN @SortOrder = 'login_name' THEN '[login_name] ASC' + WHEN @SortOrder = 'program_name' THEN '[program_name] ASC' + WHEN @SortOrder = 'client_interface_name' THEN '[client_interface_name] ASC' + WHEN @SortOrder = 'request_cpu_time' THEN 'COALESCE(r.cpu_time, s.cpu_time) DESC' + WHEN @SortOrder = 'request_logical_reads' THEN 'COALESCE(r.logical_reads, s.logical_reads) DESC' + WHEN @SortOrder = 'request_writes' THEN 'COALESCE(r.writes, s.writes) DESC' + WHEN @SortOrder = 'request_physical_reads' THEN 'COALESCE(r.reads, s.reads) DESC ' + WHEN @SortOrder = 'session_cpu' THEN 's.cpu_time DESC' + WHEN @SortOrder = 'session_logical_reads' THEN 's.logical_reads DESC' + WHEN @SortOrder = 'session_physical_reads' THEN 's.reads DESC' + WHEN @SortOrder = 'session_writes' THEN 's.writes DESC' + WHEN @SortOrder = 'tempdb_allocations_mb' THEN '[tempdb_allocations_mb] DESC' + WHEN @SortOrder = 'memory_usage' THEN '[memory_usage] DESC' + WHEN @SortOrder = 'deadlock_priority' THEN 'r.deadlock_priority DESC' + WHEN @SortOrder = 'transaction_isolation_level' THEN 'r.[transaction_isolation_level] DESC' + WHEN @SortOrder = 'requested_memory_kb' THEN '[requested_memory_kb] DESC' + WHEN @SortOrder = 'grant_memory_kb' THEN 'qmg.granted_memory_kb DESC' + WHEN @SortOrder = 'grant' THEN 'qmg.granted_memory_kb DESC' + WHEN @SortOrder = 'query_memory_grant_used_memory_kb' THEN 'qmg.used_memory_kb DESC' + WHEN @SortOrder = 'ideal_memory_kb' THEN '[ideal_memory_kb] DESC' + WHEN @SortOrder = 'workload_group_name' THEN 'wg.name ASC' + WHEN @SortOrder = 'resource_pool_name' THEN 'rp.name ASC' + ELSE '[elapsed_time] DESC' + END + ' + '; - /* What's running right now? This is the first and last result set. */ - IF @SinceStartup = 0 AND @Seconds > 0 AND @ExpertMode = 1 AND @OutputType <> 'NONE' AND @OutputResultSets LIKE N'%BlitzWho_End%' - BEGIN - IF OBJECT_ID('master.dbo.sp_BlitzWho') IS NULL AND OBJECT_ID('dbo.sp_BlitzWho') IS NULL - BEGIN - PRINT N'sp_BlitzWho is not installed in the current database_files. You can get a copy from http://FirstResponderKit.org'; - END; - ELSE - BEGIN - EXEC (@BlitzWho); - END; - END; /* IF @SinceStartup = 0 AND @Seconds > 0 AND @ExpertMode = 1 AND @OutputType <> 'NONE' - What's running right now? This is the first and last result set. */ -END; /* IF @LogMessage IS NULL */ -END; /* ELSE IF @OutputType = 'SCHEMA' */ +IF @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @OutputTableName IS NOT NULL + AND EXISTS ( SELECT * + FROM sys.databases + WHERE QUOTENAME([name]) = @OutputDatabaseName) + BEGIN + SET @StringToExecute = N'USE ' + + @OutputDatabaseName + N'; ' + + @BlockingCheck + + + ' INSERT INTO ' + + @OutputSchemaName + N'.' + + @OutputTableName + + N'(ServerName + ,CheckDate + ,[elapsed_time] + ,[session_id] + ,[blocking_session_id] + ,[database_name] + ,[query_text]' + + CASE WHEN @GetOuterCommand = 1 THEN N',[outer_command]' ELSE N'' END + N' + ,[query_plan]' + + CASE WHEN @ProductVersionMajor >= 11 THEN N',[live_query_plan]' ELSE N'' END + + CASE WHEN @ProductVersionMajor >= 11 THEN N',[cached_parameter_info]' ELSE N'' END + + CASE WHEN @ProductVersionMajor >= 11 AND @ShowActualParameters = 1 THEN N',[Live_Parameter_Info]' ELSE N'' END + N' + ,[query_cost] + ,[status] + ,[wait_info] + ,[wait_resource]' + + CASE WHEN @ProductVersionMajor >= 11 THEN N',[top_session_waits]' ELSE N'' END + N' + ,[open_transaction_count] + ,[is_implicit_transaction] + ,[nt_domain] + ,[host_name] + ,[login_name] + ,[nt_user_name] + ,[program_name] + ,[fix_parameter_sniffing] + ,[client_interface_name] + ,[login_time] + ,[start_time] + ,[request_time] + ,[request_cpu_time] + ,[request_logical_reads] + ,[request_writes] + ,[request_physical_reads] + ,[session_cpu] + ,[session_logical_reads] + ,[session_physical_reads] + ,[session_writes] + ,[tempdb_allocations_mb] + ,[memory_usage] + ,[estimated_completion_time] + ,[percent_complete] + ,[deadlock_priority] + ,[transaction_isolation_level] + ,[degree_of_parallelism]' + + CASE WHEN @ProductVersionMajor >= 11 THEN N' + ,[last_dop] + ,[min_dop] + ,[max_dop] + ,[last_grant_kb] + ,[min_grant_kb] + ,[max_grant_kb] + ,[last_used_grant_kb] + ,[min_used_grant_kb] + ,[max_used_grant_kb] + ,[last_ideal_grant_kb] + ,[min_ideal_grant_kb] + ,[max_ideal_grant_kb] + ,[last_reserved_threads] + ,[min_reserved_threads] + ,[max_reserved_threads] + ,[last_used_threads] + ,[min_used_threads] + ,[max_used_threads]' ELSE N'' END + N' + ,[grant_time] + ,[requested_memory_kb] + ,[grant_memory_kb] + ,[is_request_granted] + ,[required_memory_kb] + ,[query_memory_grant_used_memory_kb] + ,[ideal_memory_kb] + ,[is_small] + ,[timeout_sec] + ,[resource_semaphore_id] + ,[wait_order] + ,[wait_time_ms] + ,[next_candidate_for_memory_grant] + ,[target_memory_kb] + ,[max_target_memory_kb] + ,[total_memory_kb] + ,[available_memory_kb] + ,[granted_memory_kb] + ,[query_resource_semaphore_used_memory_kb] + ,[grantee_count] + ,[waiter_count] + ,[timeout_error_count] + ,[forced_grant_count] + ,[workload_group_name] + ,[resource_pool_name] + ,[context_info]' + + CASE WHEN @ProductVersionMajor >= 11 THEN N' + ,[query_hash] + ,[query_plan_hash] + ,[sql_handle] + ,[plan_handle] + ,[statement_start_offset] + ,[statement_end_offset]' ELSE N'' END + N' +) + SELECT @@SERVERNAME, COALESCE(@CheckDateOverride, SYSDATETIMEOFFSET()) AS CheckDate , ' + + @StringToExecute; + END +ELSE + SET @StringToExecute = @BlockingCheck + N' SELECT GETDATE() AS run_date , ' + @StringToExecute; -SET NOCOUNT OFF; -GO +/* If the server has > 50GB of memory, add a max grant hint to avoid getting a giant grant */ +IF (@ProductVersionMajor = 11 AND @ProductVersionMinor >= 6020) + OR (@ProductVersionMajor = 12 AND @ProductVersionMinor >= 5000 ) + OR (@ProductVersionMajor >= 13 ) + AND 50000000 < (SELECT cntr_value + FROM sys.dm_os_performance_counters + WHERE object_name LIKE '%:Memory Manager%' + AND counter_name LIKE 'Target Server Memory (KB)%') + BEGIN + SET @StringToExecute = @StringToExecute + N' OPTION (MAX_GRANT_PERCENT = 1, RECOMPILE) '; + END +ELSE + BEGIN + SET @StringToExecute = @StringToExecute + N' OPTION (RECOMPILE) '; + END +/* Be good: */ +SET @StringToExecute = @StringToExecute + N' ; '; -/* How to run it: -EXEC dbo.sp_BlitzFirst +IF @Debug = 1 + BEGIN + PRINT CONVERT(VARCHAR(8000), SUBSTRING(@StringToExecute, 0, 8000)) + PRINT CONVERT(VARCHAR(8000), SUBSTRING(@StringToExecute, 8000, 16000)) + END -With extra diagnostic info: -EXEC dbo.sp_BlitzFirst @ExpertMode = 1; +EXEC sp_executesql @StringToExecute, + N'@CheckDateOverride DATETIMEOFFSET', + @CheckDateOverride; -Saving output to tables: -EXEC sp_BlitzFirst - @OutputDatabaseName = 'DBAtools' -, @OutputSchemaName = 'dbo' -, @OutputTableName = 'BlitzFirst' -, @OutputTableNameFileStats = 'BlitzFirst_FileStats' -, @OutputTableNamePerfmonStats = 'BlitzFirst_PerfmonStats' -, @OutputTableNameWaitStats = 'BlitzFirst_WaitStats' -, @OutputTableNameBlitzCache = 'BlitzCache' -, @OutputTableNameBlitzWho = 'BlitzWho' -, @OutputType = 'none' -*/ +END +GO diff --git a/Install-Core-Blitz-With-Query-Store.sql b/Install-Core-Blitz-With-Query-Store.sql deleted file mode 100644 index 44c08d66c..000000000 --- a/Install-Core-Blitz-With-Query-Store.sql +++ /dev/null @@ -1,43418 +0,0 @@ -IF OBJECT_ID('dbo.sp_Blitz') IS NULL - EXEC ('CREATE PROCEDURE dbo.sp_Blitz AS RETURN 0;'); -GO - -ALTER PROCEDURE [dbo].[sp_Blitz] - @Help TINYINT = 0 , - @CheckUserDatabaseObjects TINYINT = 1 , - @CheckProcedureCache TINYINT = 0 , - @OutputType VARCHAR(20) = 'TABLE' , - @OutputProcedureCache TINYINT = 0 , - @CheckProcedureCacheFilter VARCHAR(10) = NULL , - @CheckServerInfo TINYINT = 0 , - @SkipChecksServer NVARCHAR(256) = NULL , - @SkipChecksDatabase NVARCHAR(256) = NULL , - @SkipChecksSchema NVARCHAR(256) = NULL , - @SkipChecksTable NVARCHAR(256) = NULL , - @IgnorePrioritiesBelow INT = NULL , - @IgnorePrioritiesAbove INT = NULL , - @OutputServerName NVARCHAR(256) = NULL , - @OutputDatabaseName NVARCHAR(256) = NULL , - @OutputSchemaName NVARCHAR(256) = NULL , - @OutputTableName NVARCHAR(256) = NULL , - @OutputXMLasNVARCHAR TINYINT = 0 , - @EmailRecipients VARCHAR(MAX) = NULL , - @EmailProfile sysname = NULL , - @SummaryMode TINYINT = 0 , - @BringThePain TINYINT = 0 , - @UsualDBOwner sysname = NULL , - @SkipBlockingChecks TINYINT = 1 , - @Debug TINYINT = 0 , - @Version VARCHAR(30) = NULL OUTPUT, - @VersionDate DATETIME = NULL OUTPUT, - @VersionCheckMode BIT = 0 -WITH RECOMPILE -AS - SET NOCOUNT ON; - SET STATISTICS XML OFF; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - - - SELECT @Version = '8.19', @VersionDate = '20240222'; - SET @OutputType = UPPER(@OutputType); - - IF(@VersionCheckMode = 1) - BEGIN - RETURN; - END; - - IF @Help = 1 - BEGIN - PRINT ' - /* - sp_Blitz from http://FirstResponderKit.org - - This script checks the health of your SQL Server and gives you a prioritized - to-do list of the most urgent things you should consider fixing. - - To learn more, visit http://FirstResponderKit.org where you can download new - versions for free, watch training videos on how it works, get more info on - the findings, contribute your own code, and more. - - Known limitations of this version: - - Only Microsoft-supported versions of SQL Server. Sorry, 2005 and 2000. - - If a database name has a question mark in it, some tests will fail. Gotta - love that unsupported sp_MSforeachdb. - - If you have offline databases, sp_Blitz fails the first time you run it, - but does work the second time. (Hoo, boy, this will be fun to debug.) - - @OutputServerName will output QueryPlans as NVARCHAR(MAX) since Microsoft - has refused to support XML columns in Linked Server queries. The bug is now - 16 years old! *~ \o/ ~* - - Unknown limitations of this version: - - None. (If we knew them, they would be known. Duh.) - - Changes - for the full list of improvements and fixes in this version, see: - https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/ - - Parameter explanations: - - @CheckUserDatabaseObjects 1=review user databases for triggers, heaps, etc. Takes more time for more databases and objects. - @CheckServerInfo 1=show server info like CPUs, memory, virtualization - @CheckProcedureCache 1=top 20-50 resource-intensive cache plans and analyze them for common performance issues. - @OutputProcedureCache 1=output the top 20-50 resource-intensive plans even if they did not trigger an alarm - @CheckProcedureCacheFilter ''CPU'' | ''Reads'' | ''Duration'' | ''ExecCount'' - @OutputType ''TABLE''=table | ''COUNT''=row with number found | ''MARKDOWN''=bulleted list (including server info, excluding security findings) | ''SCHEMA''=version and field list | ''XML'' =table output as XML | ''NONE'' = none - @IgnorePrioritiesBelow 50=ignore priorities below 50 - @IgnorePrioritiesAbove 50=ignore priorities above 50 - @Debug 0=silent (Default) | 1=messages per step | 2=outputs dynamic queries - For the rest of the parameters, see https://www.BrentOzar.com/blitz/documentation for details. - - MIT License - - Copyright for portions of sp_Blitz are held by Microsoft as part of project - tigertoolbox and are provided under the MIT license: - https://github.com/Microsoft/tigertoolbox - - All other copyrights for sp_Blitz are held by Brent Ozar Unlimited. - - Copyright (c) Brent Ozar Unlimited - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE. - - */'; - RETURN; - END; /* @Help = 1 */ - - ELSE IF @OutputType = 'SCHEMA' - BEGIN - SELECT FieldList = '[Priority] TINYINT, [FindingsGroup] VARCHAR(50), [Finding] VARCHAR(200), [DatabaseName] NVARCHAR(128), [URL] VARCHAR(200), [Details] NVARCHAR(4000), [QueryPlan] NVARCHAR(MAX), [QueryPlanFiltered] NVARCHAR(MAX), [CheckID] INT'; - - END;/* IF @OutputType = 'SCHEMA' */ - ELSE - BEGIN - - DECLARE @StringToExecute NVARCHAR(4000) - ,@curr_tracefilename NVARCHAR(500) - ,@base_tracefilename NVARCHAR(500) - ,@indx int - ,@query_result_separator CHAR(1) - ,@EmailSubject NVARCHAR(255) - ,@EmailBody NVARCHAR(MAX) - ,@EmailAttachmentFilename NVARCHAR(255) - ,@ProductVersion NVARCHAR(128) - ,@ProductVersionMajor DECIMAL(10,2) - ,@ProductVersionMinor DECIMAL(10,2) - ,@CurrentName NVARCHAR(128) - ,@CurrentDefaultValue NVARCHAR(200) - ,@CurrentCheckID INT - ,@CurrentPriority INT - ,@CurrentFinding VARCHAR(200) - ,@CurrentURL VARCHAR(200) - ,@CurrentDetails NVARCHAR(4000) - ,@MsSinceWaitsCleared DECIMAL(38,0) - ,@CpuMsSinceWaitsCleared DECIMAL(38,0) - ,@ResultText NVARCHAR(MAX) - ,@crlf NVARCHAR(2) - ,@Processors int - ,@NUMANodes int - ,@MinServerMemory bigint - ,@MaxServerMemory bigint - ,@ColumnStoreIndexesInUse bit - ,@TraceFileIssue bit - -- Flag for Windows OS to help with Linux support - ,@IsWindowsOperatingSystem BIT - ,@DaysUptime NUMERIC(23,2) - /* For First Responder Kit consistency check:*/ - ,@spBlitzFullName VARCHAR(1024) - ,@BlitzIsOutdatedComparedToOthers BIT - ,@tsql NVARCHAR(MAX) - ,@VersionCheckModeExistsTSQL NVARCHAR(MAX) - ,@BlitzProcDbName VARCHAR(256) - ,@ExecRet INT - ,@InnerExecRet INT - ,@TmpCnt INT - ,@PreviousComponentName VARCHAR(256) - ,@PreviousComponentFullPath VARCHAR(1024) - ,@CurrentStatementId INT - ,@CurrentComponentSchema VARCHAR(256) - ,@CurrentComponentName VARCHAR(256) - ,@CurrentComponentType VARCHAR(256) - ,@CurrentComponentVersionDate DATETIME2 - ,@CurrentComponentFullName VARCHAR(1024) - ,@CurrentComponentMandatory BIT - ,@MaximumVersionDate DATETIME - ,@StatementCheckName VARCHAR(256) - ,@StatementOutputsCounter BIT - ,@OutputCounterExpectedValue INT - ,@StatementOutputsExecRet BIT - ,@StatementOutputsDateTime BIT - ,@CurrentComponentMandatoryCheckOK BIT - ,@CurrentComponentVersionCheckModeOK BIT - ,@canExitLoop BIT - ,@frkIsConsistent BIT - ,@NeedToTurnNumericRoundabortBackOn BIT - ,@sa bit = 1 - ,@SUSER_NAME sysname = SUSER_SNAME() - ,@SkipDBCC bit = 0 - ,@SkipTrace bit = 0 - ,@SkipXPRegRead bit = 0 - ,@SkipXPFixedDrives bit = 0 - ,@SkipXPCMDShell bit = 0 - ,@SkipMaster bit = 0 - ,@SkipMSDB_objs bit = 0 - ,@SkipMSDB_jobs bit = 0 - ,@SkipModel bit = 0 - ,@SkipTempDB bit = 0 - ,@SkipValidateLogins bit = 0 - ,@SkipGetAlertInfo bit = 0 - - DECLARE - @db_perms table - ( - database_name sysname, - permission_name sysname - ); - - INSERT - @db_perms - ( - database_name, - permission_name - ) - SELECT - database_name = - DB_NAME(d.database_id), - fmp.permission_name - FROM sys.databases AS d - CROSS APPLY fn_my_permissions(d.name, 'DATABASE') AS fmp - WHERE fmp.permission_name = N'SELECT'; /*Databases where we don't have read permissions*/ - - /* End of declarations for First Responder Kit consistency check:*/ - ; - - /* Create temp table for check 73 */ - IF OBJECT_ID('tempdb..#AlertInfo') IS NOT NULL - EXEC sp_executesql N'DROP TABLE #AlertInfo;'; - - CREATE TABLE #AlertInfo - ( - FailSafeOperator NVARCHAR(255) , - NotificationMethod INT , - ForwardingServer NVARCHAR(255) , - ForwardingSeverity INT , - PagerToTemplate NVARCHAR(255) , - PagerCCTemplate NVARCHAR(255) , - PagerSubjectTemplate NVARCHAR(255) , - PagerSendSubjectOnly NVARCHAR(255) , - ForwardAlways INT - ); - - /* Create temp table for check 2301 */ - IF OBJECT_ID('tempdb..#InvalidLogins') IS NOT NULL - EXEC sp_executesql N'DROP TABLE #InvalidLogins;'; - - CREATE TABLE #InvalidLogins - ( - LoginSID varbinary(85), - LoginName VARCHAR(256) - ); - - /*Starting permissions checks here, but only if we're not a sysadmin*/ - IF - ( - SELECT - sa = - ISNULL - ( - IS_SRVROLEMEMBER(N'sysadmin'), - 0 - ) - ) = 0 - BEGIN - IF @Debug IN (1, 2) RAISERROR('User not SA, checking permissions', 0, 1) WITH NOWAIT; - - SET @sa = 0; /*Setting this to 0 to skip DBCC COMMANDS*/ - - IF NOT EXISTS - ( - SELECT - 1/0 - FROM sys.fn_my_permissions(NULL, NULL) AS fmp - WHERE fmp.permission_name = N'VIEW SERVER STATE' - ) - BEGIN - RAISERROR('The user %s does not have VIEW SERVER STATE permissions.', 0, 11, @SUSER_NAME) WITH NOWAIT; - RETURN; - END; /*If we don't have this, we can't do anything at all.*/ - - IF NOT EXISTS - ( - SELECT - 1/0 - FROM fn_my_permissions(N'sys.traces', N'OBJECT') AS fmp - WHERE fmp.permission_name = N'ALTER' - ) - BEGIN - SET @SkipTrace = 1; - END; /*We need this permission to execute trace stuff, apparently*/ - - IF NOT EXISTS - ( - SELECT - 1/0 - FROM fn_my_permissions(N'xp_fixeddrives', N'OBJECT') AS fmp - WHERE fmp.permission_name = N'EXECUTE' - ) - BEGIN - SET @SkipXPFixedDrives = 1; - END; /*Need execute on xp_fixeddrives*/ - - IF NOT EXISTS - ( - SELECT - 1/0 - FROM fn_my_permissions(N'xp_cmdshell', N'OBJECT') AS fmp - WHERE fmp.permission_name = N'EXECUTE' - ) - BEGIN - SET @SkipXPCMDShell = 1; - END; /*Need execute on xp_cmdshell*/ - - IF ISNULL(@SkipValidateLogins, 0) != 1 /*If @SkipValidateLogins hasn't been set to 1 by the caller*/ - BEGIN - BEGIN TRY - /* Try to fill the table for check 2301 */ - INSERT INTO #InvalidLogins - ( - [LoginSID] - ,[LoginName] - ) - EXEC sp_validatelogins; - - SET @SkipValidateLogins = 0; /*We can execute sp_validatelogins*/ - END TRY - BEGIN CATCH - SET @SkipValidateLogins = 1; /*We have don't have execute rights or sp_validatelogins throws an error so skip it*/ - END CATCH; - END; /*Need execute on sp_validatelogins*/ - - IF ISNULL(@SkipGetAlertInfo, 0) != 1 /*If @SkipGetAlertInfo hasn't been set to 1 by the caller*/ - BEGIN - BEGIN TRY - /* Try to fill the table for check 73 */ - INSERT INTO #AlertInfo - EXEC [master].[dbo].[sp_MSgetalertinfo] @includeaddresses = 0; - - SET @SkipGetAlertInfo = 0; /*We can execute sp_MSgetalertinfo*/ - END TRY - BEGIN CATCH - SET @SkipGetAlertInfo = 1; /*We have don't have execute rights or sp_MSgetalertinfo throws an error so skip it*/ - END CATCH; - END; /*Need execute on sp_MSgetalertinfo*/ - - IF ISNULL(@SkipModel, 0) != 1 /*If @SkipModel hasn't been set to 1 by the caller*/ - BEGIN - IF EXISTS - ( - SELECT 1/0 - FROM @db_perms - WHERE database_name = N'model' - ) - BEGIN - BEGIN TRY - IF EXISTS - ( - SELECT 1/0 - FROM model.sys.objects - ) - BEGIN - SET @SkipModel = 0; /*We have read permissions in the model database, and can view the objects*/ - END; - END TRY - BEGIN CATCH - SET @SkipModel = 1; /*We have read permissions in the model database ... oh wait we got tricked, we can't view the objects*/ - END CATCH; - END; - ELSE - BEGIN - SET @SkipModel = 1; /*We don't have read permissions in the model database*/ - END; - END; - - IF ISNULL(@SkipMSDB_objs, 0) != 1 /*If @SkipMSDB_objs hasn't been set to 1 by the caller*/ - BEGIN - IF EXISTS - ( - SELECT 1/0 - FROM @db_perms - WHERE database_name = N'msdb' - ) - BEGIN - BEGIN TRY - IF EXISTS - ( - SELECT 1/0 - FROM msdb.sys.objects - ) - BEGIN - SET @SkipMSDB_objs = 0; /*We have read permissions in the msdb database, and can view the objects*/ - END; - END TRY - BEGIN CATCH - SET @SkipMSDB_objs = 1; /*We have read permissions in the msdb database ... oh wait we got tricked, we can't view the objects*/ - END CATCH; - END; - ELSE - BEGIN - SET @SkipMSDB_objs = 1; /*We don't have read permissions in the msdb database*/ - END; - END; - - IF ISNULL(@SkipMSDB_jobs, 0) != 1 /*If @SkipMSDB_jobs hasn't been set to 1 by the caller*/ - BEGIN - IF EXISTS - ( - SELECT 1/0 - FROM @db_perms - WHERE database_name = N'msdb' - ) - BEGIN - BEGIN TRY - IF EXISTS - ( - SELECT 1/0 - FROM msdb.dbo.sysjobs - ) - BEGIN - SET @SkipMSDB_jobs = 0; /*We have read permissions in the msdb database, and can view the objects*/ - END; - END TRY - BEGIN CATCH - SET @SkipMSDB_jobs = 1; /*We have read permissions in the msdb database ... oh wait we got tricked, we can't view the objects*/ - END CATCH; - END; - ELSE - BEGIN - SET @SkipMSDB_jobs = 1; /*We don't have read permissions in the msdb database*/ - END; - END; - END; - - SET @crlf = NCHAR(13) + NCHAR(10); - SET @ResultText = 'sp_Blitz Results: ' + @crlf; - - /* Last startup */ - SELECT @DaysUptime = CAST(DATEDIFF(HOUR, create_date, GETDATE()) / 24. AS NUMERIC(23, 2)) - FROM sys.databases - WHERE database_id = 2; - - IF @DaysUptime = 0 - SET @DaysUptime = .01; - - /* - Set the session state of Numeric_RoundAbort to off if any databases have Numeric Round-Abort enabled. - Stops arithmetic overflow errors during data conversion. See Github issue #2302 for more info. - */ - IF ( (8192 & @@OPTIONS) = 8192 ) /* Numeric RoundAbort is currently on, so we may need to turn it off temporarily */ - BEGIN - IF EXISTS (SELECT 1 - FROM sys.databases - WHERE is_numeric_roundabort_on = 1) /* A database has it turned on */ - BEGIN - SET @NeedToTurnNumericRoundabortBackOn = 1; - SET NUMERIC_ROUNDABORT OFF; - END; - END; - - - - - /* - --TOURSTOP01-- - See https://www.BrentOzar.com/go/blitztour for a guided tour. - - We start by creating #BlitzResults. It's a temp table that will store all of - the results from our checks. Throughout the rest of this stored procedure, - we're running a series of checks looking for dangerous things inside the SQL - Server. When we find a problem, we insert rows into #BlitzResults. At the - end, we return these results to the end user. - - #BlitzResults has a CheckID field, but there's no Check table. As we do - checks, we insert data into this table, and we manually put in the CheckID. - For a list of checks, visit http://FirstResponderKit.org. - */ - IF OBJECT_ID('tempdb..#BlitzResults') IS NOT NULL - DROP TABLE #BlitzResults; - CREATE TABLE #BlitzResults - ( - ID INT IDENTITY(1, 1) , - CheckID INT , - DatabaseName NVARCHAR(128) , - Priority TINYINT , - FindingsGroup VARCHAR(50) , - Finding VARCHAR(200) , - URL VARCHAR(200) , - Details NVARCHAR(4000) , - QueryPlan [XML] NULL , - QueryPlanFiltered [NVARCHAR](MAX) NULL - ); - - IF OBJECT_ID('tempdb..#TemporaryDatabaseResults') IS NOT NULL - DROP TABLE #TemporaryDatabaseResults; - CREATE TABLE #TemporaryDatabaseResults - ( - DatabaseName NVARCHAR(128) , - Finding NVARCHAR(128) - ); - - /* First Responder Kit consistency (temporary tables) */ - - IF(OBJECT_ID('tempdb..#FRKObjects') IS NOT NULL) - BEGIN - EXEC sp_executesql N'DROP TABLE #FRKObjects;'; - END; - - -- this one represents FRK objects - CREATE TABLE #FRKObjects ( - DatabaseName VARCHAR(256) NOT NULL, - ObjectSchemaName VARCHAR(256) NULL, - ObjectName VARCHAR(256) NOT NULL, - ObjectType VARCHAR(256) NOT NULL, - MandatoryComponent BIT NOT NULL - ); - - - IF(OBJECT_ID('tempdb..#StatementsToRun4FRKVersionCheck') IS NOT NULL) - BEGIN - EXEC sp_executesql N'DROP TABLE #StatementsToRun4FRKVersionCheck;'; - END; - - - -- This one will contain the statements to be executed - -- order: 1- Mandatory, 2- VersionCheckMode, 3- VersionCheck - - CREATE TABLE #StatementsToRun4FRKVersionCheck ( - StatementId INT IDENTITY(1,1), - CheckName VARCHAR(256), - SubjectName VARCHAR(256), - SubjectFullPath VARCHAR(1024), - StatementText NVARCHAR(MAX), - StatementOutputsCounter BIT, - OutputCounterExpectedValue INT, - StatementOutputsExecRet BIT, - StatementOutputsDateTime BIT - ); - - /* End of First Responder Kit consistency (temporary tables) */ - - - /* - You can build your own table with a list of checks to skip. For example, you - might have some databases that you don't care about, or some checks you don't - want to run. Then, when you run sp_Blitz, you can specify these parameters: - @SkipChecksDatabase = 'DBAtools', - @SkipChecksSchema = 'dbo', - @SkipChecksTable = 'BlitzChecksToSkip' - Pass in the database, schema, and table that contains the list of checks you - want to skip. This part of the code checks those parameters, gets the list, - and then saves those in a temp table. As we run each check, we'll see if we - need to skip it. - */ - /* --TOURSTOP07-- */ - IF OBJECT_ID('tempdb..#SkipChecks') IS NOT NULL - DROP TABLE #SkipChecks; - CREATE TABLE #SkipChecks - ( - DatabaseName NVARCHAR(128) , - CheckID INT , - ServerName NVARCHAR(128) - ); - CREATE CLUSTERED INDEX IX_CheckID_DatabaseName ON #SkipChecks(CheckID, DatabaseName); - - INSERT INTO #SkipChecks - (DatabaseName) - SELECT - DB_NAME(d.database_id) - FROM sys.databases AS d - WHERE (DB_NAME(d.database_id) LIKE 'rdsadmin%' - OR LOWER(d.name) IN ('dbatools', 'dbadmin', 'dbmaintenance')) - OPTION(RECOMPILE); - - /*Skip checks for database where we don't have read permissions*/ - INSERT INTO - #SkipChecks - ( - DatabaseName - ) - SELECT - DB_NAME(d.database_id) - FROM sys.databases AS d - WHERE NOT EXISTS - ( - SELECT - 1/0 - FROM @db_perms AS dp - WHERE dp.database_name = DB_NAME(d.database_id) - ); - - /*Skip individial checks where we don't have permissions*/ - INSERT #SkipChecks (DatabaseName, CheckID, ServerName) - SELECT - v.* - FROM (VALUES(NULL, 29, NULL)) AS v (DatabaseName, CheckID, ServerName) /*Looks for user tables in model*/ - WHERE @SkipModel = 1; - - INSERT #SkipChecks (DatabaseName, CheckID, ServerName) - SELECT - v.* - FROM (VALUES(NULL, 28, NULL)) AS v (DatabaseName, CheckID, ServerName) /*Tables in the MSDB Database*/ - WHERE @SkipMSDB_objs = 1; - - INSERT #SkipChecks (DatabaseName, CheckID, ServerName) - SELECT - v.* - FROM (VALUES - /*sysjobs checks*/ - (NULL, 6, NULL), /*Jobs Owned By Users*/ - (NULL, 57, NULL), /*SQL Agent Job Runs at Startup*/ - (NULL, 79, NULL), /*Shrink Database Job*/ - (NULL, 94, NULL), /*Agent Jobs Without Failure Emails*/ - (NULL, 123, NULL), /*Agent Jobs Starting Simultaneously*/ - (NULL, 180, NULL), /*Shrink Database Step In Maintenance Plan*/ - (NULL, 181, NULL), /*Repetitive Maintenance Tasks*/ - - /*sysalerts checks*/ - (NULL, 30, NULL), /*Not All Alerts Configured*/ - (NULL, 59, NULL), /*Alerts Configured without Follow Up*/ - (NULL, 61, NULL), /*No Alerts for Sev 19-25*/ - (NULL, 96, NULL), /*No Alerts for Corruption*/ - (NULL, 98, NULL), /*Alerts Disabled*/ - (NULL, 219, NULL), /*Alerts Without Event Descriptions*/ - - /*sysoperators*/ - (NULL, 31, NULL) /*No Operators Configured/Enabled*/ - ) AS v (DatabaseName, CheckID, ServerName) - WHERE @SkipMSDB_jobs = 1; - - INSERT #SkipChecks (DatabaseName, CheckID, ServerName) - SELECT - v.* - FROM (VALUES(NULL, 68, NULL)) AS v (DatabaseName, CheckID, ServerName) /*DBCC command*/ - WHERE @sa = 0; - - INSERT #SkipChecks (DatabaseName, CheckID, ServerName) - SELECT - v.* - FROM (VALUES(NULL, 69, NULL)) AS v (DatabaseName, CheckID, ServerName) /*DBCC command*/ - WHERE @sa = 0; - - INSERT #SkipChecks (DatabaseName, CheckID, ServerName) - SELECT - v.* - FROM (VALUES(NULL, 92, NULL)) AS v (DatabaseName, CheckID, ServerName) /*xp_fixeddrives*/ - WHERE @SkipXPFixedDrives = 1; - - INSERT #SkipChecks (DatabaseName, CheckID, ServerName) - SELECT - v.* - FROM (VALUES(NULL, 106, NULL)) AS v (DatabaseName, CheckID, ServerName) /*alter trace*/ - WHERE @SkipTrace = 1; - - INSERT #SkipChecks (DatabaseName, CheckID, ServerName) - SELECT - v.* - FROM (VALUES(NULL, 211, NULL)) AS v (DatabaseName, CheckID, ServerName) /*xp_regread*/ - WHERE @sa = 0; - - INSERT #SkipChecks (DatabaseName, CheckID, ServerName) - SELECT - v.* - FROM (VALUES(NULL, 212, NULL)) AS v (DatabaseName, CheckID, ServerName) /*xp_regread*/ - WHERE @SkipXPCMDShell = 1; - - INSERT #SkipChecks (DatabaseName, CheckID, ServerName) - SELECT - v.* - FROM (VALUES(NULL, 2301, NULL)) AS v (DatabaseName, CheckID, ServerName) /*sp_validatelogins*/ - WHERE @SkipValidateLogins = 1; - - INSERT #SkipChecks (DatabaseName, CheckID, ServerName) - SELECT - v.* - FROM (VALUES(NULL, 73, NULL)) AS v (DatabaseName, CheckID, ServerName) /*sp_validatelogins*/ - WHERE @SkipGetAlertInfo = 1; - - IF @sa = 0 - BEGIN - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 223 AS CheckID , - 0 AS Priority , - 'Informational' AS FindingsGroup , - 'Some Checks Skipped' AS Finding , - '' AS URL , - 'User ''' + @SUSER_NAME + ''' is not part of the sysadmin role, so we skipped some checks that are not possible due to lack of permissions.' AS Details; - END; - /*End of SkipsChecks added due to permissions*/ - - IF @SkipChecksTable IS NOT NULL - AND @SkipChecksSchema IS NOT NULL - AND @SkipChecksDatabase IS NOT NULL - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Inserting SkipChecks', 0, 1) WITH NOWAIT; - - SET @StringToExecute = N'INSERT INTO #SkipChecks(DatabaseName, CheckID, ServerName ) - SELECT DISTINCT DatabaseName, CheckID, ServerName - FROM ' - IF LTRIM(RTRIM(@SkipChecksServer)) <> '' - BEGIN - SET @StringToExecute += QUOTENAME(@SkipChecksServer) + N'.'; - END - SET @StringToExecute += QUOTENAME(@SkipChecksDatabase) + N'.' + QUOTENAME(@SkipChecksSchema) + N'.' + QUOTENAME(@SkipChecksTable) - + N' WHERE ServerName IS NULL OR ServerName = CAST(SERVERPROPERTY(''ServerName'') AS NVARCHAR(128)) OPTION (RECOMPILE);'; - EXEC(@StringToExecute); - END; - - -- Flag for Windows OS to help with Linux support - IF EXISTS ( SELECT 1 - FROM sys.all_objects - WHERE name = 'dm_os_host_info' ) - BEGIN - SELECT @IsWindowsOperatingSystem = CASE WHEN host_platform = 'Windows' THEN 1 ELSE 0 END FROM sys.dm_os_host_info ; - END; - ELSE - BEGIN - SELECT @IsWindowsOperatingSystem = 1 ; - END; - - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 106 ) - AND (select convert(int,value_in_use) from sys.configurations where name = 'default trace enabled' ) = 1 - BEGIN - - select @curr_tracefilename = [path] from sys.traces where is_default = 1 ; - set @curr_tracefilename = reverse(@curr_tracefilename); - - -- Set the trace file path separator based on underlying OS - IF (@IsWindowsOperatingSystem = 1) AND @curr_tracefilename IS NOT NULL - BEGIN - select @indx = patindex('%\%', @curr_tracefilename) ; - set @curr_tracefilename = reverse(@curr_tracefilename) ; - set @base_tracefilename = left( @curr_tracefilename,len(@curr_tracefilename) - @indx) + '\log.trc' ; - END; - ELSE - BEGIN - select @indx = patindex('%/%', @curr_tracefilename) ; - set @curr_tracefilename = reverse(@curr_tracefilename) ; - set @base_tracefilename = left( @curr_tracefilename,len(@curr_tracefilename) - @indx) + '/log.trc' ; - END; - - END; - - /* If the server has any databases on Antiques Roadshow, skip the checks that would break due to CTEs. */ - IF @CheckUserDatabaseObjects = 1 AND EXISTS(SELECT * FROM sys.databases WHERE compatibility_level < 90) - BEGIN - SET @CheckUserDatabaseObjects = 0; - PRINT 'Databases with compatibility level < 90 found, so setting @CheckUserDatabaseObjects = 0.'; - PRINT 'The database-level checks rely on CTEs, which are not supported in SQL 2000 compat level databases.'; - PRINT 'Get with the cool kids and switch to a current compatibility level, Grandpa. To find the problems, run:'; - PRINT 'SELECT * FROM sys.databases WHERE compatibility_level < 90;'; - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 204 AS CheckID , - 0 AS Priority , - 'Informational' AS FindingsGroup , - '@CheckUserDatabaseObjects Disabled' AS Finding , - 'https://www.BrentOzar.com/blitz/' AS URL , - 'Since you have databases with compatibility_level < 90, we can''t run @CheckUserDatabaseObjects = 1. To find them: SELECT * FROM sys.databases WHERE compatibility_level < 90' AS Details; - END; - - /* --TOURSTOP08-- */ - /* If the server is Amazon RDS, skip checks that it doesn't allow */ - IF LEFT(CAST(SERVERPROPERTY('ComputerNamePhysicalNetBIOS') AS VARCHAR(8000)), 8) = 'EC2AMAZ-' - AND LEFT(CAST(SERVERPROPERTY('MachineName') AS VARCHAR(8000)), 8) = 'EC2AMAZ-' - AND db_id('rdsadmin') IS NOT NULL - AND EXISTS(SELECT * FROM master.sys.all_objects WHERE name IN ('rds_startup_tasks', 'rds_help_revlogin', 'rds_hexadecimal', 'rds_failover_tracking', 'rds_database_tracking', 'rds_track_change')) - BEGIN - 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 (29); /* tables in model database created by users - not allowed */ - INSERT INTO #SkipChecks (CheckID) VALUES (40); /* TempDB only has one data file in RDS */ - INSERT INTO #SkipChecks (CheckID) VALUES (62); /* Database compatibility level - cannot change in RDS */ - INSERT INTO #SkipChecks (CheckID) VALUES (68); /*Check for the last good DBCC CHECKDB date - can't run DBCC DBINFO() */ - INSERT INTO #SkipChecks (CheckID) VALUES (69); /* High VLF check - requires DBCC LOGINFO permission */ - INSERT INTO #SkipChecks (CheckID) VALUES (73); /* No Failsafe Operator Configured check */ - INSERT INTO #SkipChecks (CheckID) VALUES (92); /* Drive info check - requires xp_Fixeddrives permission */ - INSERT INTO #SkipChecks (CheckID) VALUES (100); /* Remote DAC disabled */ - INSERT INTO #SkipChecks (CheckID) VALUES (177); /* Disabled Internal Monitoring Features check - requires dm_server_registry access */ - INSERT INTO #SkipChecks (CheckID) VALUES (180); /* 180/181 are maintenance plans checks - Maint plans not available in RDS*/ - INSERT INTO #SkipChecks (CheckID) VALUES (181); /*Find repetitive maintenance tasks*/ - - -- can check errorlog using rdsadmin.dbo.rds_read_error_log, so allow this check - --INSERT INTO #SkipChecks (CheckID) VALUES (193); /* xp_readerrorlog checking for IFI */ - - INSERT INTO #SkipChecks (CheckID) VALUES (211); /* xp_regread not allowed - checking for power saving */ - INSERT INTO #SkipChecks (CheckID) VALUES (212); /* xp_regread not allowed - checking for additional instances */ - INSERT INTO #SkipChecks (CheckID) VALUES (2301); /* sp_validatelogins called by Invalid login defined with Windows Authentication */ - - -- Following are skipped due to limited permissions in msdb/SQLAgent in RDS - INSERT INTO #SkipChecks (CheckID) VALUES (30); /* SQL Server Agent alerts not configured */ - INSERT INTO #SkipChecks (CheckID) VALUES (31); /* check whether we have NO ENABLED operators */ - INSERT INTO #SkipChecks (CheckID) VALUES (57); /* SQL Agent Job Runs at Startup */ - INSERT INTO #SkipChecks (CheckID) VALUES (59); /* Alerts Configured without Follow Up */ - INSERT INTO #SkipChecks (CheckID) VALUES (61); /*SQL Server Agent alerts do not exist for severity levels 19 through 25*/ - INSERT INTO #SkipChecks (CheckID) VALUES (79); /* Shrink Database Job check */ - INSERT INTO #SkipChecks (CheckID) VALUES (94); /* job failure without operator notification check */ - INSERT INTO #SkipChecks (CheckID) VALUES (96); /* Agent alerts for corruption */ - INSERT INTO #SkipChecks (CheckID) VALUES (98); /* check for disabled alerts */ - INSERT INTO #SkipChecks (CheckID) VALUES (123); /* Agent Jobs Starting Simultaneously */ - INSERT INTO #SkipChecks (CheckID) VALUES (219); /* check for alerts that do NOT include event descriptions in their outputs via email/pager/net-send */ - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 223 AS CheckID , - 0 AS Priority , - 'Informational' AS FindingsGroup , - 'Some Checks Skipped' AS Finding , - 'https://aws.amazon.com/rds/sqlserver/' AS URL , - 'Amazon RDS detected, so we skipped some checks that are not currently possible, relevant, or practical there.' AS Details; - END; /* Amazon RDS skipped checks */ - - /* If the server is ExpressEdition, skip checks that it doesn't allow */ - IF CAST(SERVERPROPERTY('Edition') AS NVARCHAR(1000)) LIKE N'%Express%' - BEGIN - INSERT INTO #SkipChecks (CheckID) VALUES (30); /* Alerts not configured */ - INSERT INTO #SkipChecks (CheckID) VALUES (31); /* Operators not configured */ - INSERT INTO #SkipChecks (CheckID) VALUES (61); /* Agent alerts 19-25 */ - INSERT INTO #SkipChecks (CheckID) VALUES (73); /* Failsafe operator */ - INSERT INTO #SkipChecks (CheckID) VALUES (96); /* Agent alerts for corruption */ - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 223 AS CheckID , - 0 AS Priority , - 'Informational' AS FindingsGroup , - 'Some Checks Skipped' AS Finding , - 'https://stackoverflow.com/questions/1169634/limitations-of-sql-server-express' AS URL , - 'Express Edition detected, so we skipped some checks that are not currently possible, relevant, or practical there.' AS Details; - END; /* Express Edition skipped checks */ - - /* If the server is an Azure Managed Instance, skip checks that it doesn't allow */ - IF SERVERPROPERTY('EngineEdition') = 8 - BEGIN - INSERT INTO #SkipChecks (CheckID) VALUES (1); /* Full backups - because of the MI GUID name bug mentioned here: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/1481 */ - INSERT INTO #SkipChecks (CheckID) VALUES (2); /* Log backups - because of the MI GUID name bug mentioned here: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/1481 */ - 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 (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 (74); /* TraceFlag On - because Azure Managed Instances go wild and crazy with the trace flags */ - 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 (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 */ - INSERT INTO #SkipChecks (CheckID, DatabaseName) VALUES (80, 'model'); /* Max file size set */ - INSERT INTO #SkipChecks (CheckID, DatabaseName) VALUES (80, 'msdb'); /* Max file size set */ - INSERT INTO #SkipChecks (CheckID, DatabaseName) VALUES (80, 'tempdb'); /* Max file size set */ - INSERT INTO #SkipChecks (CheckID) VALUES (224); /* CheckID 224 - Performance - SSRS/SSAS/SSIS Installed */ - INSERT INTO #SkipChecks (CheckID) VALUES (92); /* CheckID 92 - drive space */ - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 223 AS CheckID , - 0 AS Priority , - 'Informational' AS FindingsGroup , - 'Some Checks Skipped' AS Finding , - 'https://docs.microsoft.com/en-us/azure/sql-database/sql-database-managed-instance-index' AS URL , - 'Managed Instance detected, so we skipped some checks that are not currently possible, relevant, or practical there.' AS Details; - END; /* Azure Managed Instance skipped checks */ - - /* - That's the end of the SkipChecks stuff. - The next several tables are used by various checks later. - */ - IF OBJECT_ID('tempdb..#ConfigurationDefaults') IS NOT NULL - DROP TABLE #ConfigurationDefaults; - CREATE TABLE #ConfigurationDefaults - ( - name NVARCHAR(128) , - DefaultValue BIGINT, - CheckID INT - ); - - IF OBJECT_ID ('tempdb..#Recompile') IS NOT NULL - DROP TABLE #Recompile; - CREATE TABLE #Recompile( - DBName varchar(200), - ProcName varchar(300), - RecompileFlag varchar(1), - SPSchema varchar(50) - ); - - IF OBJECT_ID('tempdb..#DatabaseDefaults') IS NOT NULL - DROP TABLE #DatabaseDefaults; - CREATE TABLE #DatabaseDefaults - ( - name NVARCHAR(128) , - DefaultValue NVARCHAR(200), - CheckID INT, - Priority INT, - Finding VARCHAR(200), - URL VARCHAR(200), - Details NVARCHAR(4000) - ); - - IF OBJECT_ID('tempdb..#DatabaseScopedConfigurationDefaults') IS NOT NULL - DROP TABLE #DatabaseScopedConfigurationDefaults; - CREATE TABLE #DatabaseScopedConfigurationDefaults - (ID INT IDENTITY(1,1), configuration_id INT, [name] NVARCHAR(60), default_value sql_variant, default_value_for_secondary sql_variant, CheckID INT, ); - - IF OBJECT_ID('tempdb..#DBCCs') IS NOT NULL - DROP TABLE #DBCCs; - CREATE TABLE #DBCCs - ( - ID INT IDENTITY(1, 1) - PRIMARY KEY , - ParentObject VARCHAR(255) , - Object VARCHAR(255) , - Field VARCHAR(255) , - Value VARCHAR(255) , - DbName NVARCHAR(128) NULL - ); - - IF OBJECT_ID('tempdb..#LogInfo2012') IS NOT NULL - DROP TABLE #LogInfo2012; - CREATE TABLE #LogInfo2012 - ( - recoveryunitid INT , - FileID SMALLINT , - FileSize BIGINT , - StartOffset BIGINT , - FSeqNo BIGINT , - [Status] TINYINT , - Parity TINYINT , - CreateLSN NUMERIC(38) - ); - - IF OBJECT_ID('tempdb..#LogInfo') IS NOT NULL - DROP TABLE #LogInfo; - CREATE TABLE #LogInfo - ( - FileID SMALLINT , - FileSize BIGINT , - StartOffset BIGINT , - FSeqNo BIGINT , - [Status] TINYINT , - Parity TINYINT , - CreateLSN NUMERIC(38) - ); - - IF OBJECT_ID('tempdb..#partdb') IS NOT NULL - DROP TABLE #partdb; - CREATE TABLE #partdb - ( - dbname NVARCHAR(128) , - objectname NVARCHAR(200) , - type_desc NVARCHAR(128) - ); - - IF OBJECT_ID('tempdb..#TraceStatus') IS NOT NULL - DROP TABLE #TraceStatus; - CREATE TABLE #TraceStatus - ( - TraceFlag VARCHAR(10) , - status BIT , - Global BIT , - Session BIT - ); - - IF OBJECT_ID('tempdb..#driveInfo') IS NOT NULL - DROP TABLE #driveInfo; - CREATE TABLE #driveInfo - ( - drive NVARCHAR(2), - logical_volume_name NVARCHAR(36), --Limit is 32 for NTFS, 11 for FAT - available_MB DECIMAL(18, 0), - total_MB DECIMAL(18, 0), - used_percent DECIMAL(18, 2) - ); - - IF OBJECT_ID('tempdb..#dm_exec_query_stats') IS NOT NULL - DROP TABLE #dm_exec_query_stats; - CREATE TABLE #dm_exec_query_stats - ( - [id] [int] NOT NULL - IDENTITY(1, 1) , - [sql_handle] [varbinary](64) NOT NULL , - [statement_start_offset] [int] NOT NULL , - [statement_end_offset] [int] NOT NULL , - [plan_generation_num] [bigint] NOT NULL , - [plan_handle] [varbinary](64) NOT NULL , - [creation_time] [datetime] NOT NULL , - [last_execution_time] [datetime] NOT NULL , - [execution_count] [bigint] NOT NULL , - [total_worker_time] [bigint] NOT NULL , - [last_worker_time] [bigint] NOT NULL , - [min_worker_time] [bigint] NOT NULL , - [max_worker_time] [bigint] NOT NULL , - [total_physical_reads] [bigint] NOT NULL , - [last_physical_reads] [bigint] NOT NULL , - [min_physical_reads] [bigint] NOT NULL , - [max_physical_reads] [bigint] NOT NULL , - [total_logical_writes] [bigint] NOT NULL , - [last_logical_writes] [bigint] NOT NULL , - [min_logical_writes] [bigint] NOT NULL , - [max_logical_writes] [bigint] NOT NULL , - [total_logical_reads] [bigint] NOT NULL , - [last_logical_reads] [bigint] NOT NULL , - [min_logical_reads] [bigint] NOT NULL , - [max_logical_reads] [bigint] NOT NULL , - [total_clr_time] [bigint] NOT NULL , - [last_clr_time] [bigint] NOT NULL , - [min_clr_time] [bigint] NOT NULL , - [max_clr_time] [bigint] NOT NULL , - [total_elapsed_time] [bigint] NOT NULL , - [last_elapsed_time] [bigint] NOT NULL , - [min_elapsed_time] [bigint] NOT NULL , - [max_elapsed_time] [bigint] NOT NULL , - [query_hash] [binary](8) NULL , - [query_plan_hash] [binary](8) NULL , - [query_plan] [xml] NULL , - [query_plan_filtered] [nvarchar](MAX) NULL , - [text] [nvarchar](MAX) COLLATE SQL_Latin1_General_CP1_CI_AS - NULL , - [text_filtered] [nvarchar](MAX) COLLATE SQL_Latin1_General_CP1_CI_AS - NULL - ); - - IF OBJECT_ID('tempdb..#ErrorLog') IS NOT NULL - DROP TABLE #ErrorLog; - CREATE TABLE #ErrorLog - ( - LogDate DATETIME , - ProcessInfo NVARCHAR(20) , - [Text] NVARCHAR(1000) - ); - - IF OBJECT_ID('tempdb..#fnTraceGettable') IS NOT NULL - DROP TABLE #fnTraceGettable; - CREATE TABLE #fnTraceGettable - ( - TextData NVARCHAR(4000) , - DatabaseName NVARCHAR(256) , - EventClass INT , - Severity INT , - StartTime DATETIME , - EndTime DATETIME , - Duration BIGINT , - NTUserName NVARCHAR(256) , - NTDomainName NVARCHAR(256) , - HostName NVARCHAR(256) , - ApplicationName NVARCHAR(256) , - LoginName NVARCHAR(256) , - DBUserName NVARCHAR(256) - ); - - IF OBJECT_ID('tempdb..#Instances') IS NOT NULL - DROP TABLE #Instances; - CREATE TABLE #Instances - ( - Instance_Number NVARCHAR(MAX) , - Instance_Name NVARCHAR(MAX) , - Data_Field NVARCHAR(MAX) - ); - - IF OBJECT_ID('tempdb..#IgnorableWaits') IS NOT NULL - DROP TABLE #IgnorableWaits; - CREATE TABLE #IgnorableWaits (wait_type NVARCHAR(60)); - INSERT INTO #IgnorableWaits VALUES ('BROKER_EVENTHANDLER'); - INSERT INTO #IgnorableWaits VALUES ('BROKER_RECEIVE_WAITFOR'); - INSERT INTO #IgnorableWaits VALUES ('BROKER_TASK_STOP'); - INSERT INTO #IgnorableWaits VALUES ('BROKER_TO_FLUSH'); - INSERT INTO #IgnorableWaits VALUES ('BROKER_TRANSMITTER'); - INSERT INTO #IgnorableWaits VALUES ('CHECKPOINT_QUEUE'); - INSERT INTO #IgnorableWaits VALUES ('CLR_AUTO_EVENT'); - INSERT INTO #IgnorableWaits VALUES ('CLR_MANUAL_EVENT'); - INSERT INTO #IgnorableWaits VALUES ('CLR_SEMAPHORE'); - INSERT INTO #IgnorableWaits VALUES ('DBMIRROR_DBM_EVENT'); - INSERT INTO #IgnorableWaits VALUES ('DBMIRROR_DBM_MUTEX'); - INSERT INTO #IgnorableWaits VALUES ('DBMIRROR_EVENTS_QUEUE'); - INSERT INTO #IgnorableWaits VALUES ('DBMIRROR_WORKER_QUEUE'); - INSERT INTO #IgnorableWaits VALUES ('DBMIRRORING_CMD'); - INSERT INTO #IgnorableWaits VALUES ('DIRTY_PAGE_POLL'); - INSERT INTO #IgnorableWaits VALUES ('DISPATCHER_QUEUE_SEMAPHORE'); - INSERT INTO #IgnorableWaits VALUES ('FT_IFTS_SCHEDULER_IDLE_WAIT'); - INSERT INTO #IgnorableWaits VALUES ('FT_IFTSHC_MUTEX'); - INSERT INTO #IgnorableWaits VALUES ('HADR_CLUSAPI_CALL'); - INSERT INTO #IgnorableWaits VALUES ('HADR_FABRIC_CALLBACK'); - INSERT INTO #IgnorableWaits VALUES ('HADR_FILESTREAM_IOMGR_IOCOMPLETION'); - INSERT INTO #IgnorableWaits VALUES ('HADR_LOGCAPTURE_WAIT'); - INSERT INTO #IgnorableWaits VALUES ('HADR_NOTIFICATION_DEQUEUE'); - INSERT INTO #IgnorableWaits VALUES ('HADR_TIMER_TASK'); - INSERT INTO #IgnorableWaits VALUES ('HADR_WORK_QUEUE'); - INSERT INTO #IgnorableWaits VALUES ('LAZYWRITER_SLEEP'); - INSERT INTO #IgnorableWaits VALUES ('LOGMGR_QUEUE'); - INSERT INTO #IgnorableWaits VALUES ('ONDEMAND_TASK_QUEUE'); - INSERT INTO #IgnorableWaits VALUES ('PARALLEL_REDO_DRAIN_WORKER'); - INSERT INTO #IgnorableWaits VALUES ('PARALLEL_REDO_LOG_CACHE'); - INSERT INTO #IgnorableWaits VALUES ('PARALLEL_REDO_TRAN_LIST'); - INSERT INTO #IgnorableWaits VALUES ('PARALLEL_REDO_WORKER_SYNC'); - INSERT INTO #IgnorableWaits VALUES ('PARALLEL_REDO_WORKER_WAIT_WORK'); - INSERT INTO #IgnorableWaits VALUES ('POPULATE_LOCK_ORDINALS'); - INSERT INTO #IgnorableWaits VALUES ('PREEMPTIVE_HADR_LEASE_MECHANISM'); - INSERT INTO #IgnorableWaits VALUES ('PREEMPTIVE_SP_SERVER_DIAGNOSTICS'); - INSERT INTO #IgnorableWaits VALUES ('QDS_ASYNC_QUEUE'); - INSERT INTO #IgnorableWaits VALUES ('QDS_CLEANUP_STALE_QUERIES_TASK_MAIN_LOOP_SLEEP'); - INSERT INTO #IgnorableWaits VALUES ('QDS_PERSIST_TASK_MAIN_LOOP_SLEEP'); - INSERT INTO #IgnorableWaits VALUES ('QDS_SHUTDOWN_QUEUE'); - INSERT INTO #IgnorableWaits VALUES ('REDO_THREAD_PENDING_WORK'); - INSERT INTO #IgnorableWaits VALUES ('REQUEST_FOR_DEADLOCK_SEARCH'); - INSERT INTO #IgnorableWaits VALUES ('SLEEP_SYSTEMTASK'); - INSERT INTO #IgnorableWaits VALUES ('SLEEP_TASK'); - INSERT INTO #IgnorableWaits VALUES ('SOS_WORK_DISPATCHER'); - INSERT INTO #IgnorableWaits VALUES ('SP_SERVER_DIAGNOSTICS_SLEEP'); - INSERT INTO #IgnorableWaits VALUES ('SQLTRACE_BUFFER_FLUSH'); - INSERT INTO #IgnorableWaits VALUES ('SQLTRACE_INCREMENTAL_FLUSH_SLEEP'); - INSERT INTO #IgnorableWaits VALUES ('UCS_SESSION_REGISTRATION'); - INSERT INTO #IgnorableWaits VALUES ('WAIT_XTP_OFFLINE_CKPT_NEW_LOG'); - INSERT INTO #IgnorableWaits VALUES ('WAITFOR'); - INSERT INTO #IgnorableWaits VALUES ('XE_DISPATCHER_WAIT'); - INSERT INTO #IgnorableWaits VALUES ('XE_LIVE_TARGET_TVF'); - INSERT INTO #IgnorableWaits VALUES ('XE_TIMER_EVENT'); - - IF @Debug IN (1, 2) RAISERROR('Setting @MsSinceWaitsCleared', 0, 1) WITH NOWAIT; - - SELECT @MsSinceWaitsCleared = DATEDIFF(MINUTE, create_date, CURRENT_TIMESTAMP) * 60000.0 - FROM sys.databases - WHERE name = 'tempdb'; - - /* Have they cleared wait stats? Using a 10% fudge factor */ - IF @MsSinceWaitsCleared * .9 > (SELECT MAX(wait_time_ms) FROM sys.dm_os_wait_stats WHERE wait_type IN ('SP_SERVER_DIAGNOSTICS_SLEEP', 'QDS_PERSIST_TASK_MAIN_LOOP_SLEEP', 'REQUEST_FOR_DEADLOCK_SEARCH', 'HADR_FILESTREAM_IOMGR_IOCOMPLETION', 'LAZYWRITER_SLEEP', 'SQLTRACE_INCREMENTAL_FLUSH_SLEEP', 'DIRTY_PAGE_POLL', 'LOGMGR_QUEUE')) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 185) WITH NOWAIT; - - SET @MsSinceWaitsCleared = (SELECT MAX(wait_time_ms) FROM sys.dm_os_wait_stats WHERE wait_type IN ('SP_SERVER_DIAGNOSTICS_SLEEP', 'QDS_PERSIST_TASK_MAIN_LOOP_SLEEP', 'REQUEST_FOR_DEADLOCK_SEARCH', 'HADR_FILESTREAM_IOMGR_IOCOMPLETION', 'LAZYWRITER_SLEEP', 'SQLTRACE_INCREMENTAL_FLUSH_SLEEP', 'DIRTY_PAGE_POLL', 'LOGMGR_QUEUE')); - IF @MsSinceWaitsCleared = 0 SET @MsSinceWaitsCleared = 1; - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - VALUES( 185, - 240, - 'Wait Stats', - 'Wait Stats Have Been Cleared', - 'https://www.brentozar.com/go/waits', - 'Someone ran DBCC SQLPERF to clear sys.dm_os_wait_stats at approximately: ' - + CONVERT(NVARCHAR(100), - DATEADD(MINUTE, (-1. * (@MsSinceWaitsCleared) / 1000. / 60.), GETDATE()), 120)); - END; - - /* @CpuMsSinceWaitsCleared is used for waits stats calculations */ - - IF @Debug IN (1, 2) RAISERROR('Setting @CpuMsSinceWaitsCleared', 0, 1) WITH NOWAIT; - - SELECT @CpuMsSinceWaitsCleared = @MsSinceWaitsCleared * scheduler_count - FROM sys.dm_os_sys_info; - - /* If we're outputting CSV or Markdown, don't bother checking the plan cache because we cannot export plans. */ - IF @OutputType = 'CSV' OR @OutputType = 'MARKDOWN' - SET @CheckProcedureCache = 0; - - /* If we're posting a question on Stack, include background info on the server */ - IF @OutputType = 'MARKDOWN' - SET @CheckServerInfo = 1; - - /* Only run CheckUserDatabaseObjects if there are less than 50 databases. */ - IF @BringThePain = 0 AND 50 <= (SELECT COUNT(*) FROM sys.databases) AND @CheckUserDatabaseObjects = 1 - BEGIN - SET @CheckUserDatabaseObjects = 0; - PRINT 'Running sp_Blitz @CheckUserDatabaseObjects = 1 on a server with 50+ databases may cause temporary problems for the server and/or user.'; - PRINT 'If you''re sure you want to do this, run again with the parameter @BringThePain = 1.'; - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 201 AS CheckID , - 0 AS Priority , - 'Informational' AS FindingsGroup , - '@CheckUserDatabaseObjects Disabled' AS Finding , - 'https://www.BrentOzar.com/blitz/' AS URL , - 'If you want to check 50+ databases, you have to also use @BringThePain = 1.' AS Details; - END; - - /* Sanitize our inputs */ - SELECT - @OutputServerName = QUOTENAME(@OutputServerName), - @OutputDatabaseName = QUOTENAME(@OutputDatabaseName), - @OutputSchemaName = QUOTENAME(@OutputSchemaName), - @OutputTableName = QUOTENAME(@OutputTableName); - - /* Get the major and minor build numbers */ - - IF @Debug IN (1, 2) RAISERROR('Getting version information.', 0, 1) WITH NOWAIT; - - SET @ProductVersion = CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)); - SELECT @ProductVersionMajor = SUBSTRING(@ProductVersion, 1,CHARINDEX('.', @ProductVersion) + 1 ), - @ProductVersionMinor = PARSENAME(CONVERT(varchar(32), @ProductVersion), 2); - - /* - Whew! we're finally done with the setup, and we can start doing checks. - First, let's make sure we're actually supposed to do checks on this server. - The user could have passed in a SkipChecks table that specified to skip ALL - checks on this server, so let's check for that: - */ - IF ( ( SERVERPROPERTY('ServerName') NOT IN ( SELECT ServerName - FROM #SkipChecks - WHERE DatabaseName IS NULL - AND CheckID IS NULL ) ) - OR ( @SkipChecksTable IS NULL ) - ) - BEGIN - - /* - Extract DBCC DBINFO data from the server. This data is used for check 2 using - the dbi_LastLogBackupTime field and check 68 using the dbi_LastKnownGood field. - NB: DBCC DBINFO is not available on AWS RDS databases so if the server is RDS - (which will have previously triggered inserting a checkID 223 record) and at - least one of the relevant checks is not being skipped then we can extract the - dbinfo information. - */ - IF NOT EXISTS - ( - SELECT 1/0 - FROM #BlitzResults - WHERE CheckID = 223 AND URL = 'https://aws.amazon.com/rds/sqlserver/' - ) AND NOT EXISTS - ( - SELECT 1/0 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID IN (2, 68) - ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Extracting DBCC DBINFO data (used in checks 2 and 68).', 0, 1, 68) WITH NOWAIT; - - EXEC sp_MSforeachdb N'USE [?]; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - INSERT #DBCCs - (ParentObject, - Object, - Field, - Value) - EXEC (''DBCC DBInfo() With TableResults, NO_INFOMSGS''); - UPDATE #DBCCs SET DbName = N''?'' WHERE DbName IS NULL OPTION (RECOMPILE);'; - END - - /* - Our very first check! We'll put more comments in this one just to - explain exactly how it works. First, we check to see if we're - supposed to skip CheckID 1 (that's the check we're working on.) - */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 1 ) - BEGIN - - /* - Below, we check master.sys.databases looking for databases - that haven't had a backup in the last week. If we find any, - we insert them into #BlitzResults, the temp table that - tracks our server's problems. Note that if the check does - NOT find any problems, we don't save that. We're only - saving the problems, not the successful checks. - */ - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 1) WITH NOWAIT; - - IF SERVERPROPERTY('EngineEdition') <> 8 /* Azure Managed Instances need a special query */ - BEGIN - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 1 AS CheckID , - d.[name] AS DatabaseName , - 1 AS Priority , - 'Backup' AS FindingsGroup , - 'Backups Not Performed Recently' AS Finding , - 'https://www.brentozar.com/go/nobak' AS URL , - 'Last backed up: ' - + COALESCE(CAST(MAX(b.backup_finish_date) AS VARCHAR(25)),'never') AS Details - FROM master.sys.databases d - LEFT OUTER JOIN msdb.dbo.backupset b ON d.name COLLATE SQL_Latin1_General_CP1_CI_AS = b.database_name COLLATE SQL_Latin1_General_CP1_CI_AS - AND b.type = 'D' - AND b.server_name = SERVERPROPERTY('ServerName') /*Backupset ran on current server */ - WHERE d.database_id <> 2 /* Bonus points if you know what that means */ - AND d.state NOT IN(1, 6, 10) /* Not currently offline or restoring, like log shipping databases */ - AND d.is_in_standby = 0 /* Not a log shipping target database */ - AND d.source_database_id IS NULL /* Excludes database snapshots */ - AND d.name NOT IN ( SELECT DISTINCT - DatabaseName - FROM #SkipChecks - WHERE CheckID IS NULL OR CheckID = 1) - /* - The above NOT IN filters out the databases we're not supposed to check. - */ - GROUP BY d.name - HAVING MAX(b.backup_finish_date) <= DATEADD(dd, - -7, GETDATE()) - OR MAX(b.backup_finish_date) IS NULL; - END; - - ELSE /* SERVERPROPERTY('EngineName') must be 8, Azure Managed Instances */ - BEGIN - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 1 AS CheckID , - d.[name] AS DatabaseName , - 1 AS Priority , - 'Backup' AS FindingsGroup , - 'Backups Not Performed Recently' AS Finding , - 'https://www.brentozar.com/go/nobak' AS URL , - 'Last backed up: ' - + COALESCE(CAST(MAX(b.backup_finish_date) AS VARCHAR(25)),'never') AS Details - FROM master.sys.databases d - LEFT OUTER JOIN msdb.dbo.backupset b ON d.name COLLATE SQL_Latin1_General_CP1_CI_AS = b.database_name COLLATE SQL_Latin1_General_CP1_CI_AS - AND b.type = 'D' - WHERE d.database_id <> 2 /* Bonus points if you know what that means */ - AND d.state NOT IN(1, 6, 10) /* Not currently offline or restoring, like log shipping databases */ - AND d.is_in_standby = 0 /* Not a log shipping target database */ - AND d.source_database_id IS NULL /* Excludes database snapshots */ - AND d.name NOT IN ( SELECT DISTINCT - DatabaseName - FROM #SkipChecks - WHERE CheckID IS NULL OR CheckID = 1) - /* - The above NOT IN filters out the databases we're not supposed to check. - */ - GROUP BY d.name - HAVING MAX(b.backup_finish_date) <= DATEADD(dd, - -7, GETDATE()) - OR MAX(b.backup_finish_date) IS NULL; - END; - - - - /* - And there you have it. The rest of this stored procedure works the same - way: it asks: - - Should I skip this check? - - If not, do I find problems? - - Insert the results into #BlitzResults - */ - - END; - - /* - And that's the end of CheckID #1. - - CheckID #2 is a little simpler because it only involves one query, and it's - more typical for queries that people contribute. But keep reading, because - the next check gets more complex again. - */ - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 2 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 2) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT DISTINCT - 2 AS CheckID , - d.name AS DatabaseName , - 1 AS Priority , - 'Backup' AS FindingsGroup , - 'Full Recovery Model w/o Log Backups' AS Finding , - 'https://www.brentozar.com/go/biglogs' AS URL , - ( 'The ' + CAST(CAST((SELECT ((SUM([mf].[size]) * 8.) / 1024.) FROM sys.[master_files] AS [mf] WHERE [mf].[database_id] = d.[database_id] AND [mf].[type_desc] = 'LOG') AS DECIMAL(18,2)) AS VARCHAR(30)) + 'MB log file has not been backed up in the last week.' ) AS Details - FROM master.sys.databases d - LEFT JOIN #DBCCs ll On ll.DbName = d.name And ll.Field = 'dbi_LastLogBackupTime' - WHERE d.recovery_model IN ( 1, 2 ) - AND d.database_id NOT IN ( 2, 3 ) - AND d.source_database_id IS NULL - AND d.state NOT IN(1, 6, 10) /* Not currently offline or restoring, like log shipping databases */ - AND d.is_in_standby = 0 /* Not a log shipping target database */ - AND d.source_database_id IS NULL /* Excludes database snapshots */ - AND d.name NOT IN ( SELECT DISTINCT - DatabaseName - FROM #SkipChecks - WHERE CheckID IS NULL OR CheckID = 2) - AND ( - ( - /* We couldn't get a value from the DBCC DBINFO data so let's check the msdb backup history information */ - [ll].[Value] Is Null - AND NOT EXISTS ( SELECT * - FROM msdb.dbo.backupset b - WHERE d.name COLLATE SQL_Latin1_General_CP1_CI_AS = b.database_name COLLATE SQL_Latin1_General_CP1_CI_AS - AND b.type = 'L' - AND b.backup_finish_date >= DATEADD(dd,-7, GETDATE()) - ) - ) - OR - ( - Convert(datetime,ll.Value,21) < DATEADD(dd,-7, GETDATE()) - ) - - ); - END; - - /* - CheckID #256 is searching for backups to NUL. - */ - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 256 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 2) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT DISTINCT - 256 AS CheckID , - d.name AS DatabaseName, - 1 AS Priority , - 'Backup' AS FindingsGroup , - 'Log Backups to NUL' AS Finding , - 'https://www.brentozar.com/go/nul' AS URL , - N'The transaction log file has been backed up ' + CAST((SELECT count(*) - FROM msdb.dbo.backupset AS b INNER JOIN - msdb.dbo.backupmediafamily AS bmf - ON b.media_set_id = bmf.media_set_id - WHERE b.database_name COLLATE SQL_Latin1_General_CP1_CI_AS = d.name COLLATE SQL_Latin1_General_CP1_CI_AS - AND bmf.physical_device_name = 'NUL' - AND b.type = 'L' - AND b.backup_finish_date >= DATEADD(dd, - -7, GETDATE())) AS NVARCHAR(8)) + ' time(s) to ''NUL'' in the last week, which means the backup does not exist. This breaks point-in-time recovery.' AS Details - FROM master.sys.databases AS d - WHERE d.recovery_model IN ( 1, 2 ) - AND d.database_id NOT IN ( 2, 3 ) - AND d.source_database_id IS NULL - AND d.state NOT IN(1, 6, 10) /* Not currently offline or restoring, like log shipping databases */ - AND d.is_in_standby = 0 /* Not a log shipping target database */ - AND d.source_database_id IS NULL /* Excludes database snapshots */ - --AND d.name NOT IN ( SELECT DISTINCT - -- DatabaseName - -- FROM #SkipChecks - -- WHERE CheckID IS NULL OR CheckID = 2) - AND EXISTS ( SELECT * - FROM msdb.dbo.backupset AS b INNER JOIN - msdb.dbo.backupmediafamily AS bmf - ON b.media_set_id = bmf.media_set_id - WHERE d.name COLLATE SQL_Latin1_General_CP1_CI_AS = b.database_name COLLATE SQL_Latin1_General_CP1_CI_AS - AND bmf.physical_device_name = 'NUL' - AND b.type = 'L' - AND b.backup_finish_date >= DATEADD(dd, - -7, GETDATE()) ); - END; - - /* - Next up, we've got CheckID 8. (These don't have to go in order.) This one - won't work on SQL Server 2005 because it relies on a new DMV that didn't - exist prior to SQL Server 2008. This means we have to check the SQL Server - version first, then build a dynamic string with the query we want to run: - */ - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 8 ) - BEGIN - IF @@VERSION NOT LIKE '%Microsoft SQL Server 2000%' - AND @@VERSION NOT LIKE '%Microsoft SQL Server 2005%' - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 8) WITH NOWAIT; - - SET @StringToExecute = 'INSERT INTO #BlitzResults - (CheckID, Priority, - FindingsGroup, - Finding, URL, - Details) - SELECT 8 AS CheckID, - 230 AS Priority, - ''Security'' AS FindingsGroup, - ''Server Audits Running'' AS Finding, - ''https://www.brentozar.com/go/audits'' AS URL, - (''SQL Server built-in audit functionality is being used by server audit: '' + [name]) AS Details FROM sys.dm_server_audit_status OPTION (RECOMPILE);'; - - IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; - IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; - - EXECUTE(@StringToExecute); - END; - END; - - /* - But what if you need to run a query in every individual database? - Hop down to the @CheckUserDatabaseObjects section. - - And that's the basic idea! You can read through the rest of the - checks if you like - some more exciting stuff happens closer to the - end of the stored proc, where we start doing things like checking - the plan cache, but those aren't as cleanly commented. - - If you'd like to contribute your own check, use one of the check - formats shown above and email it to Help@BrentOzar.com. You don't - have to pick a CheckID or a link - we'll take care of that when we - test and publish the code. Thanks! - */ - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 93 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 93) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT - 93 AS CheckID , - 1 AS Priority , - 'Backup' AS FindingsGroup , - 'Backing Up to Same Drive Where Databases Reside' AS Finding , - 'https://www.brentozar.com/go/backup' AS URL , - CAST(COUNT(1) AS VARCHAR(50)) + ' backups done on drive ' - + UPPER(LEFT(bmf.physical_device_name, 3)) - + ' in the last two weeks, where database files also live. This represents a serious risk if that array fails.' Details - FROM msdb.dbo.backupmediafamily AS bmf - INNER JOIN msdb.dbo.backupset AS bs ON bmf.media_set_id = bs.media_set_id - AND bs.backup_start_date >= ( DATEADD(dd, - -14, GETDATE()) ) - /* Filter out databases that were recently restored: */ - LEFT OUTER JOIN msdb.dbo.restorehistory rh ON bs.database_name = rh.destination_database_name AND rh.restore_date > DATEADD(dd, -14, GETDATE()) - WHERE UPPER(LEFT(bmf.physical_device_name, 3)) <> 'HTT' AND - bmf.physical_device_name NOT LIKE '\\%' AND -- GitHub Issue #2141 - @IsWindowsOperatingSystem = 1 AND -- GitHub Issue #1995 - UPPER(LEFT(bmf.physical_device_name COLLATE SQL_Latin1_General_CP1_CI_AS, 3)) IN ( - SELECT DISTINCT - UPPER(LEFT(mf.physical_name COLLATE SQL_Latin1_General_CP1_CI_AS, 3)) - FROM sys.master_files AS mf - WHERE mf.database_id <> 2 ) - AND rh.destination_database_name IS NULL - GROUP BY UPPER(LEFT(bmf.physical_device_name, 3)); - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 119 ) - AND EXISTS ( SELECT * - FROM sys.all_objects o - WHERE o.name = 'dm_database_encryption_keys' ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 119) WITH NOWAIT; - - SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, DatabaseName, URL, Details) - SELECT 119 AS CheckID, - 1 AS Priority, - ''Backup'' AS FindingsGroup, - ''TDE Certificate Not Backed Up Recently'' AS Finding, - db_name(dek.database_id) AS DatabaseName, - ''https://www.brentozar.com/go/tde'' AS URL, - ''The certificate '' + c.name + '' is used to encrypt database '' + db_name(dek.database_id) + ''. Last backup date: '' + COALESCE(CAST(c.pvt_key_last_backup_date AS VARCHAR(100)), ''Never'') AS Details - FROM master.sys.certificates c INNER JOIN sys.dm_database_encryption_keys dek ON c.thumbprint = dek.encryptor_thumbprint - WHERE pvt_key_last_backup_date IS NULL OR pvt_key_last_backup_date <= DATEADD(dd, -30, GETDATE()) OPTION (RECOMPILE);'; - - IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; - IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; - - EXECUTE(@StringToExecute); - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 202 ) - AND EXISTS ( SELECT * - FROM sys.all_columns c - WHERE c.name = 'pvt_key_last_backup_date' ) - AND EXISTS ( SELECT * - FROM msdb.INFORMATION_SCHEMA.COLUMNS c - WHERE c.TABLE_NAME = 'backupset' AND c.COLUMN_NAME = 'encryptor_thumbprint' ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 202) WITH NOWAIT; - - SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) - SELECT DISTINCT 202 AS CheckID, - 1 AS Priority, - ''Backup'' AS FindingsGroup, - ''Encryption Certificate Not Backed Up Recently'' AS Finding, - ''https://www.brentozar.com/go/tde'' AS URL, - ''The certificate '' + c.name + '' is used to encrypt database backups. Last backup date: '' + COALESCE(CAST(c.pvt_key_last_backup_date AS VARCHAR(100)), ''Never'') AS Details - FROM sys.certificates c - INNER JOIN msdb.dbo.backupset bs ON c.thumbprint = bs.encryptor_thumbprint - WHERE pvt_key_last_backup_date IS NULL OR pvt_key_last_backup_date <= DATEADD(dd, -30, GETDATE()) OPTION (RECOMPILE);'; - - IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; - IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; - - EXECUTE(@StringToExecute); - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 3 ) - BEGIN - IF DATEADD(dd, -60, GETDATE()) > (SELECT TOP 1 backup_start_date FROM msdb.dbo.backupset ORDER BY backup_start_date) - - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 3) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT TOP 1 - 3 AS CheckID , - 'msdb' , - 200 AS Priority , - 'Backup' AS FindingsGroup , - 'MSDB Backup History Not Purged' AS Finding , - 'https://www.brentozar.com/go/history' AS URL , - ( 'Database backup history retained back to ' - + CAST(bs.backup_start_date AS VARCHAR(20)) ) AS Details - FROM msdb.dbo.backupset bs - LEFT OUTER JOIN msdb.dbo.restorehistory rh ON bs.database_name = rh.destination_database_name - WHERE rh.destination_database_name IS NULL - ORDER BY bs.backup_start_date ASC; - END; - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 186 ) - BEGIN - IF DATEADD(dd, -2, GETDATE()) < (SELECT TOP 1 backup_start_date FROM msdb.dbo.backupset ORDER BY backup_start_date) - - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 186) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT TOP 1 - 186 AS CheckID , - 'msdb' , - 200 AS Priority , - 'Backup' AS FindingsGroup , - 'MSDB Backup History Purged Too Frequently' AS Finding , - 'https://www.brentozar.com/go/history' AS URL , - ( 'Database backup history only retained back to ' - + CAST(bs.backup_start_date AS VARCHAR(20)) ) AS Details - FROM msdb.dbo.backupset bs - ORDER BY backup_start_date ASC; - END; - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 178 ) - AND EXISTS (SELECT * - FROM msdb.dbo.backupset bs - WHERE bs.type = 'D' - AND bs.backup_size >= 50000000000 /* At least 50GB */ - AND DATEDIFF(SECOND, bs.backup_start_date, bs.backup_finish_date) <= 60 /* Backup took less than 60 seconds */ - AND bs.backup_finish_date >= DATEADD(DAY, -14, GETDATE()) /* In the last 2 weeks */) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 178) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 178 AS CheckID , - 200 AS Priority , - 'Performance' AS FindingsGroup , - 'Snapshot Backups Occurring' AS Finding , - 'https://www.brentozar.com/go/snaps' AS URL , - ( CAST(COUNT(*) AS VARCHAR(20)) + ' snapshot-looking backups have occurred in the last two weeks, indicating that IO may be freezing up.') AS Details - FROM msdb.dbo.backupset bs - WHERE bs.type = 'D' - AND bs.backup_size >= 50000000000 /* At least 50GB */ - AND DATEDIFF(SECOND, bs.backup_start_date, bs.backup_finish_date) <= 60 /* Backup took less than 60 seconds */ - AND bs.backup_finish_date >= DATEADD(DAY, -14, GETDATE()); /* In the last 2 weeks */ - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 236 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 236) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT TOP 1 236 AS CheckID , - 50 AS Priority , - 'Performance' AS FindingsGroup , - 'Snapshotting Too Many Databases' AS Finding , - 'https://www.brentozar.com/go/toomanysnaps' AS URL , - ( CAST(SUM(1) AS VARCHAR(20)) + ' databases snapshotted at once in the last two weeks, indicating that IO may be freezing up. Microsoft does not recommend VSS snaps for 35 or more databases.') AS Details - FROM msdb.dbo.backupset bs - WHERE bs.type = 'D' - AND bs.backup_finish_date >= DATEADD(DAY, -14, GETDATE()) /* In the last 2 weeks */ - GROUP BY bs.backup_finish_date - HAVING SUM(1) >= 35 - ORDER BY SUM(1) DESC; - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 4 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 4) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 4 AS CheckID , - 230 AS Priority , - 'Security' AS FindingsGroup , - 'Sysadmins' AS Finding , - 'https://www.brentozar.com/go/sa' AS URL , - ( 'Login [' + l.name - + '] is a sysadmin - meaning they can do absolutely anything in SQL Server, including dropping databases or hiding their tracks.' ) AS Details - FROM master.sys.syslogins l - WHERE l.sysadmin = 1 - AND l.name <> SUSER_SNAME(0x01) - AND l.denylogin = 0 - AND l.name NOT LIKE 'NT SERVICE\%' - AND l.name <> 'l_certSignSmDetach'; /* Added in SQL 2016 */ - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE CheckID = 2301 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 2301) WITH NOWAIT; - - /* - #InvalidLogins is filled at the start during the permissions check - */ - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 2301 AS CheckID , - 230 AS Priority , - 'Security' AS FindingsGroup , - 'Invalid login defined with Windows Authentication' AS Finding , - 'https://docs.microsoft.com/en-us/sql/relational-databases/system-stored-procedures/sp-validatelogins-transact-sql' AS URL , - ( 'Windows user or group ' + QUOTENAME(LoginName) + ' is mapped to a SQL Server principal but no longer exists in the Windows environment.') AS Details - FROM #InvalidLogins - ; - END; - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 5 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 5) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 5 AS CheckID , - 230 AS Priority , - 'Security' AS FindingsGroup , - 'Security Admins' AS Finding , - 'https://www.brentozar.com/go/sa' AS URL , - ( 'Login [' + l.name - + '] is a security admin - meaning they can give themselves permission to do absolutely anything in SQL Server, including dropping databases or hiding their tracks.' ) AS Details - FROM master.sys.syslogins l - WHERE l.securityadmin = 1 - AND l.name <> SUSER_SNAME(0x01) - AND l.denylogin = 0; - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 104 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 104) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( [CheckID] , - [Priority] , - [FindingsGroup] , - [Finding] , - [URL] , - [Details] - ) - SELECT 104 AS [CheckID] , - 230 AS [Priority] , - 'Security' AS [FindingsGroup] , - 'Login Can Control Server' AS [Finding] , - 'https://www.brentozar.com/go/sa' AS [URL] , - 'Login [' + pri.[name] - + '] has the CONTROL SERVER permission - meaning they can do absolutely anything in SQL Server, including dropping databases or hiding their tracks.' AS [Details] - FROM sys.server_principals AS pri - WHERE pri.[principal_id] IN ( - SELECT p.[grantee_principal_id] - FROM sys.server_permissions AS p - WHERE p.[state] IN ( 'G', 'W' ) - AND p.[class] = 100 - AND p.[type] = 'CL' ) - AND pri.[name] NOT LIKE '##%##'; - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 6 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 6) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 6 AS CheckID , - 230 AS Priority , - 'Security' AS FindingsGroup , - 'Jobs Owned By Users' AS Finding , - 'https://www.brentozar.com/go/owners' AS URL , - ( 'Job [' + j.name + '] is owned by [' - + SUSER_SNAME(j.owner_sid) - + '] - 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); - END; - - /* --TOURSTOP06-- */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 7 ) - BEGIN - /* --TOURSTOP02-- */ - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 7) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 7 AS CheckID , - 230 AS Priority , - 'Security' AS FindingsGroup , - 'Stored Procedure Runs at Startup' AS Finding , - 'https://www.brentozar.com/go/startup' AS URL , - ( 'Stored procedure [master].[' - + r.SPECIFIC_SCHEMA + '].[' - + r.SPECIFIC_NAME - + '] runs automatically when SQL Server starts up. Make sure you know exactly what this stored procedure is doing, because it could pose a security risk.' ) AS Details - FROM master.INFORMATION_SCHEMA.ROUTINES r - WHERE OBJECTPROPERTY(OBJECT_ID(ROUTINE_NAME), - 'ExecIsStartup') = 1; - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 10 ) - BEGIN - IF @@VERSION NOT LIKE '%Microsoft SQL Server 2000%' - AND @@VERSION NOT LIKE '%Microsoft SQL Server 2005%' - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 10) WITH NOWAIT; - - SET @StringToExecute = 'INSERT INTO #BlitzResults - (CheckID, - Priority, - FindingsGroup, - Finding, - URL, - Details) - SELECT 10 AS CheckID, - 100 AS Priority, - ''Performance'' AS FindingsGroup, - ''Resource Governor Enabled'' AS Finding, - ''https://www.brentozar.com/go/rg'' AS URL, - (''Resource Governor is enabled. Queries may be throttled. Make sure you understand how the Classifier Function is configured.'') AS Details FROM sys.resource_governor_configuration WHERE is_enabled = 1 OPTION (RECOMPILE);'; - - IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; - IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; - - EXECUTE(@StringToExecute); - END; - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 11 ) - BEGIN - IF @@VERSION NOT LIKE '%Microsoft SQL Server 2000%' - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 11) WITH NOWAIT; - - SET @StringToExecute = 'INSERT INTO #BlitzResults - (CheckID, - Priority, - FindingsGroup, - Finding, - URL, - Details) - SELECT 11 AS CheckID, - 100 AS Priority, - ''Performance'' AS FindingsGroup, - ''Server Triggers Enabled'' AS Finding, - ''https://www.brentozar.com/go/logontriggers/'' AS URL, - (''Server Trigger ['' + [name] ++ ''] is enabled. Make sure you understand what that trigger is doing - the less work it does, the better.'') AS Details FROM sys.server_triggers WHERE is_disabled = 0 AND is_ms_shipped = 0 OPTION (RECOMPILE);'; - - IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; - IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; - - EXECUTE(@StringToExecute); - END; - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 12 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 12) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 12 AS CheckID , - [name] AS DatabaseName , - 10 AS Priority , - 'Performance' AS FindingsGroup , - 'Auto-Close Enabled' AS Finding , - 'https://www.brentozar.com/go/autoclose' AS URL , - ( 'Database [' + [name] - + '] has auto-close enabled. This setting can dramatically decrease performance.' ) AS Details - FROM sys.databases - WHERE is_auto_close_on = 1 - AND name NOT IN ( SELECT DISTINCT - DatabaseName - FROM #SkipChecks - WHERE CheckID IS NULL OR CheckID = 12); - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 13 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 13) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 13 AS CheckID , - [name] AS DatabaseName , - 10 AS Priority , - 'Performance' AS FindingsGroup , - 'Auto-Shrink Enabled' AS Finding , - 'https://www.brentozar.com/go/autoshrink' AS URL , - ( 'Database [' + [name] - + '] has auto-shrink enabled. This setting can dramatically decrease performance.' ) AS Details - FROM sys.databases - WHERE is_auto_shrink_on = 1 - AND state <> 6 /* Offline */ - AND name NOT IN ( SELECT DISTINCT - DatabaseName - FROM #SkipChecks - WHERE CheckID IS NULL OR CheckID = 13); - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 14 ) - BEGIN - IF @@VERSION NOT LIKE '%Microsoft SQL Server 2000%' - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 14) WITH NOWAIT; - - SET @StringToExecute = 'INSERT INTO #BlitzResults - (CheckID, - DatabaseName, - Priority, - FindingsGroup, - Finding, - URL, - Details) - SELECT 14 AS CheckID, - [name] as DatabaseName, - 50 AS Priority, - ''Reliability'' AS FindingsGroup, - ''Page Verification Not Optimal'' AS Finding, - ''https://www.brentozar.com/go/torn'' AS URL, - (''Database ['' + [name] + ''] has '' + [page_verify_option_desc] + '' for page verification. SQL Server may have a harder time recognizing and recovering from storage corruption. Consider using CHECKSUM instead.'') COLLATE database_default AS Details - FROM sys.databases - WHERE page_verify_option < 2 - AND name <> ''tempdb'' - AND state NOT IN (1, 6) /* Restoring, Offline */ - and name not in (select distinct DatabaseName from #SkipChecks WHERE CheckID IS NULL OR CheckID = 14) OPTION (RECOMPILE);'; - - IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; - IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; - - EXECUTE(@StringToExecute); - END; - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 15 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 15) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 15 AS CheckID , - [name] AS DatabaseName , - 110 AS Priority , - 'Performance' AS FindingsGroup , - 'Auto-Create Stats Disabled' AS Finding , - 'https://www.brentozar.com/go/acs' AS URL , - ( 'Database [' + [name] - + '] has auto-create-stats disabled. SQL Server uses statistics to build better execution plans, and without the ability to automatically create more, performance may suffer.' ) AS Details - FROM sys.databases - WHERE is_auto_create_stats_on = 0 - AND name NOT IN ( SELECT DISTINCT - DatabaseName - FROM #SkipChecks - WHERE CheckID IS NULL OR CheckID = 15); - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 16 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 16) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 16 AS CheckID , - [name] AS DatabaseName , - 110 AS Priority , - 'Performance' AS FindingsGroup , - 'Auto-Update Stats Disabled' AS Finding , - 'https://www.brentozar.com/go/aus' AS URL , - ( 'Database [' + [name] - + '] has auto-update-stats disabled. SQL Server uses statistics to build better execution plans, and without the ability to automatically update them, performance may suffer.' ) AS Details - FROM sys.databases - WHERE is_auto_update_stats_on = 0 - AND name NOT IN ( SELECT DISTINCT - DatabaseName - FROM #SkipChecks - WHERE CheckID IS NULL OR CheckID = 16); - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 17 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 17) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 17 AS CheckID , - [name] AS DatabaseName , - 150 AS Priority , - 'Performance' AS FindingsGroup , - 'Stats Updated Asynchronously' AS Finding , - 'https://www.brentozar.com/go/asyncstats' AS URL , - ( 'Database [' + [name] - + '] has auto-update-stats-async enabled. When SQL Server gets a query for a table with out-of-date statistics, it will run the query with the stats it has - while updating stats to make later queries better. The initial run of the query may suffer, though.' ) AS Details - FROM sys.databases - WHERE is_auto_update_stats_async_on = 1 - AND name NOT IN ( SELECT DISTINCT - DatabaseName - FROM #SkipChecks - WHERE CheckID IS NULL OR CheckID = 17); - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 20 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 20) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 20 AS CheckID , - [name] AS DatabaseName , - 200 AS Priority , - 'Informational' AS FindingsGroup , - 'Date Correlation On' AS Finding , - 'https://www.brentozar.com/go/corr' AS URL , - ( 'Database [' + [name] - + '] has date correlation enabled. This is not a default setting, and it has some performance overhead. It tells SQL Server that date fields in two tables are related, and SQL Server maintains statistics showing that relation.' ) AS Details - FROM sys.databases - WHERE is_date_correlation_on = 1 - AND name NOT IN ( SELECT DISTINCT - DatabaseName - FROM #SkipChecks - WHERE CheckID IS NULL OR CheckID = 20); - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 21 ) - BEGIN - /* --TOURSTOP04-- */ - IF @@VERSION NOT LIKE '%Microsoft SQL Server 2000%' - AND @@VERSION NOT LIKE '%Microsoft SQL Server 2005%' - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 21) WITH NOWAIT; - - SET @StringToExecute = 'INSERT INTO #BlitzResults - (CheckID, - DatabaseName, - Priority, - FindingsGroup, - Finding, - URL, - Details) - SELECT 21 AS CheckID, - [name] as DatabaseName, - 200 AS Priority, - ''Informational'' AS FindingsGroup, - ''Database Encrypted'' AS Finding, - ''https://www.brentozar.com/go/tde'' AS URL, - (''Database ['' + [name] + ''] has Transparent Data Encryption enabled. Make absolutely sure you have backed up the certificate and private key, or else you will not be able to restore this database.'') AS Details - FROM sys.databases - WHERE is_encrypted = 1 - and name not in (select distinct DatabaseName from #SkipChecks WHERE CheckID IS NULL OR CheckID = 21) OPTION (RECOMPILE);'; - - IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; - IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; - - EXECUTE(@StringToExecute); - END; - END; - - /* - Believe it or not, SQL Server doesn't track the default values - for sp_configure options! We'll make our own list here. - */ - - IF @Debug IN (1, 2) RAISERROR('Generating default configuration values', 0, 1) WITH NOWAIT; - - INSERT INTO #ConfigurationDefaults - VALUES ( 'access check cache bucket count', 0, 1001 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'access check cache quota', 0, 1002 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'Ad Hoc Distributed Queries', 0, 1003 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'affinity I/O mask', 0, 1004 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'affinity mask', 0, 1005 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'affinity64 mask', 0, 1066 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'affinity64 I/O mask', 0, 1067 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'Agent XPs', 0, 1071 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'allow updates', 0, 1007 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'awe enabled', 0, 1008 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'backup checksum default', 0, 1070 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'backup compression default', 0, 1073 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'blocked process threshold', 0, 1009 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'blocked process threshold (s)', 0, 1009 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'c2 audit mode', 0, 1010 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'clr enabled', 0, 1011 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'common criteria compliance enabled', 0, 1074 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'contained database authentication', 0, 1068 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'cost threshold for parallelism', 5, 1012 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'cross db ownership chaining', 0, 1013 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'cursor threshold', -1, 1014 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'Database Mail XPs', 0, 1072 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'default full-text language', 1033, 1016 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'default language', 0, 1017 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'default trace enabled', 1, 1018 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'disallow results from triggers', 0, 1019 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'EKM provider enabled', 0, 1075 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'filestream access level', 0, 1076 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'fill factor (%)', 0, 1020 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'ft crawl bandwidth (max)', 100, 1021 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'ft crawl bandwidth (min)', 0, 1022 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'ft notify bandwidth (max)', 100, 1023 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'ft notify bandwidth (min)', 0, 1024 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'index create memory (KB)', 0, 1025 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'in-doubt xact resolution', 0, 1026 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'lightweight pooling', 0, 1027 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'locks', 0, 1028 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'max degree of parallelism', 0, 1029 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'max full-text crawl range', 4, 1030 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'max server memory (MB)', 2147483647, 1031 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'max text repl size (B)', 65536, 1032 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'max worker threads', 0, 1033 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'media retention', 0, 1034 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'min memory per query (KB)', 1024, 1035 ); - /* Accepting both 0 and 16 below because both have been seen in the wild as defaults. */ - IF EXISTS ( SELECT * - FROM sys.configurations - WHERE name = 'min server memory (MB)' - AND value_in_use IN ( 0, 16 ) ) - INSERT INTO #ConfigurationDefaults - SELECT 'min server memory (MB)' , - CAST(value_in_use AS BIGINT), 1036 - FROM sys.configurations - WHERE name = 'min server memory (MB)'; - ELSE - INSERT INTO #ConfigurationDefaults - VALUES ( 'min server memory (MB)', 0, 1036 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'nested triggers', 1, 1037 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'network packet size (B)', 4096, 1038 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'Ole Automation Procedures', 0, 1039 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'open objects', 0, 1040 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'optimize for ad hoc workloads', 0, 1041 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'PH timeout (s)', 60, 1042 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'precompute rank', 0, 1043 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'priority boost', 0, 1044 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'query governor cost limit', 0, 1045 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'query wait (s)', -1, 1046 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'recovery interval (min)', 0, 1047 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'remote access', 1, 1048 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'remote admin connections', 0, 1049 ); - /* SQL Server 2012 changes a configuration default */ - IF @@VERSION LIKE '%Microsoft SQL Server 2005%' - OR @@VERSION LIKE '%Microsoft SQL Server 2008%' - BEGIN - INSERT INTO #ConfigurationDefaults - VALUES ( 'remote login timeout (s)', 20, 1069 ); - END; - ELSE - BEGIN - INSERT INTO #ConfigurationDefaults - VALUES ( 'remote login timeout (s)', 10, 1069 ); - END; - INSERT INTO #ConfigurationDefaults - VALUES ( 'remote proc trans', 0, 1050 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'remote query timeout (s)', 600, 1051 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'Replication XPs', 0, 1052 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'RPC parameter data validation', 0, 1053 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'scan for startup procs', 0, 1054 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'server trigger recursion', 1, 1055 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'set working set size', 0, 1056 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'show advanced options', 0, 1057 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'SMO and DMO XPs', 1, 1058 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'SQL Mail XPs', 0, 1059 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'transform noise words', 0, 1060 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'two digit year cutoff', 2049, 1061 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'user connections', 0, 1062 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'user options', 0, 1063 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'Web Assistant Procedures', 0, 1064 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'xp_cmdshell', 0, 1065 ); - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 22 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 22) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT cd.CheckID , - 200 AS Priority , - 'Non-Default Server Config' AS FindingsGroup , - cr.name AS Finding , - 'https://www.brentozar.com/go/conf' AS URL , - ( 'This sp_configure option has been changed. Its default value is ' - + COALESCE(CAST(cd.[DefaultValue] AS VARCHAR(100)), - '(unknown)') - + ' and it has been set to ' - + CAST(cr.value_in_use AS VARCHAR(100)) - + '.' ) AS Details - FROM sys.configurations cr - INNER JOIN #ConfigurationDefaults cd ON cd.name = cr.name - LEFT OUTER JOIN #ConfigurationDefaults cdUsed ON cdUsed.name = cr.name - AND cdUsed.DefaultValue = cr.value_in_use - WHERE cdUsed.name IS NULL; - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 190 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Setting @MinServerMemory and @MaxServerMemory', 0, 1) WITH NOWAIT; - - SELECT @MinServerMemory = CAST(value_in_use as BIGINT) FROM sys.configurations WHERE name = 'min server memory (MB)'; - SELECT @MaxServerMemory = CAST(value_in_use as BIGINT) FROM sys.configurations WHERE name = 'max server memory (MB)'; - - IF (@MinServerMemory = @MaxServerMemory) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 190) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - VALUES - ( 190, - 200, - 'Performance', - 'Non-Dynamic Memory', - 'https://www.brentozar.com/go/memory', - 'Minimum Server Memory setting is the same as the Maximum (both set to ' + CAST(@MinServerMemory AS NVARCHAR(50)) + '). This will not allow dynamic memory. Please revise memory settings' - ); - END; - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 188 ) - BEGIN - - /* Let's set variables so that our query is still SARGable */ - - IF @Debug IN (1, 2) RAISERROR('Setting @Processors.', 0, 1) WITH NOWAIT; - - SET @Processors = (SELECT cpu_count FROM sys.dm_os_sys_info); - - IF @Debug IN (1, 2) RAISERROR('Setting @NUMANodes', 0, 1) WITH NOWAIT; - - SET @NUMANodes = (SELECT COUNT(1) - FROM sys.dm_os_performance_counters pc - WHERE pc.object_name LIKE '%Buffer Node%' - AND counter_name = 'Page life expectancy'); - /* If Cost Threshold for Parallelism is default then flag as a potential issue */ - /* If MAXDOP is default and processors > 8 or NUMA nodes > 1 then flag as potential issue */ - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 188) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 188 AS CheckID , - 200 AS Priority , - 'Performance' AS FindingsGroup , - cr.name AS Finding , - 'https://www.brentozar.com/go/cxpacket' AS URL , - ( 'Set to ' + CAST(cr.value_in_use AS NVARCHAR(50)) + ', its default value. Changing this sp_configure setting may reduce CXPACKET waits.') - FROM sys.configurations cr - INNER JOIN #ConfigurationDefaults cd ON cd.name = cr.name - AND cr.value_in_use = cd.DefaultValue - WHERE cr.name = 'cost threshold for parallelism' - OR (cr.name = 'max degree of parallelism' AND (@NUMANodes > 1 OR @Processors > 8)); - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 24 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 24) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT DISTINCT - 24 AS CheckID , - DB_NAME(database_id) AS DatabaseName , - 170 AS Priority , - 'File Configuration' AS FindingsGroup , - 'System Database on C Drive' AS Finding , - 'https://www.brentozar.com/go/cdrive' AS URL , - ( 'The ' + DB_NAME(database_id) - + ' database has a file on the C drive. Putting system databases on the C drive runs the risk of crashing the server when it runs out of space.' ) AS Details - FROM sys.master_files - WHERE UPPER(LEFT(physical_name, 1)) = 'C' - AND DB_NAME(database_id) IN ( 'master', - 'model', 'msdb' ); - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 25 ) - AND SERVERPROPERTY('EngineEdition') <> 8 /* Azure Managed Instances */ - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 25) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT TOP 1 - 25 AS CheckID , - 'tempdb' , - 20 AS Priority , - 'File Configuration' AS FindingsGroup , - 'TempDB on C Drive' AS Finding , - 'https://www.brentozar.com/go/cdrive' AS URL , - CASE WHEN growth > 0 - THEN ( 'The tempdb database has files on the C drive. TempDB frequently grows unpredictably, putting your server at risk of running out of C drive space and crashing hard. C is also often much slower than other drives, so performance may be suffering.' ) - ELSE ( 'The tempdb database has files on the C drive. TempDB is not set to Autogrow, hopefully it is big enough. C is also often much slower than other drives, so performance may be suffering.' ) - END AS Details - FROM sys.master_files - WHERE UPPER(LEFT(physical_name, 1)) = 'C' - AND DB_NAME(database_id) = 'tempdb'; - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 26 ) - AND SERVERPROPERTY('EngineEdition') <> 8 /* Azure Managed Instances */ - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 26) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT DISTINCT - 26 AS CheckID , - DB_NAME(database_id) AS DatabaseName , - 20 AS Priority , - 'Reliability' AS FindingsGroup , - 'User Databases on C Drive' AS Finding , - 'https://www.brentozar.com/go/cdrive' AS URL , - ( 'The ' + DB_NAME(database_id) - + ' database has a file on the C drive. Putting databases on the C drive runs the risk of crashing the server when it runs out of space.' ) AS Details - FROM sys.master_files - WHERE UPPER(LEFT(physical_name, 1)) = 'C' - AND DB_NAME(database_id) NOT IN ( 'master', - 'model', 'msdb', - 'tempdb' ) - AND DB_NAME(database_id) NOT IN ( - SELECT DISTINCT - DatabaseName - FROM #SkipChecks - WHERE CheckID IS NULL OR CheckID = 26 ); - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 27 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 27) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 27 AS CheckID , - 'master' AS DatabaseName , - 200 AS Priority , - 'Informational' AS FindingsGroup , - 'Tables in the Master Database' AS Finding , - 'https://www.brentozar.com/go/mastuser' AS URL , - ( 'The ' + name - + ' table in the master database was created by end users on ' - + CAST(create_date AS VARCHAR(20)) - + '. Tables in the master database may not be restored in the event of a disaster.' ) AS Details - FROM master.sys.tables - WHERE is_ms_shipped = 0 - AND name NOT IN ('CommandLog','SqlServerVersions','$ndo$srvproperty'); - /* That last one is the Dynamics NAV licensing table: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2426 */ - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 28 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 28) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 28 AS CheckID , - 'msdb' AS DatabaseName , - 200 AS Priority , - 'Informational' AS FindingsGroup , - 'Tables in the MSDB Database' AS Finding , - 'https://www.brentozar.com/go/msdbuser' AS URL , - ( 'The ' + name - + ' table in the msdb database was created by end users on ' - + CAST(create_date AS VARCHAR(20)) - + '. Tables in the msdb database may not be restored in the event of a disaster.' ) AS Details - FROM msdb.sys.tables - WHERE is_ms_shipped = 0 AND name NOT LIKE '%DTA_%'; - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 29 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 29) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 29 AS CheckID , - 'model' AS DatabaseName , - 200 AS Priority , - 'Informational' AS FindingsGroup , - 'Tables in the Model Database' AS Finding , - 'https://www.brentozar.com/go/model' AS URL , - ( 'The ' + name - + ' table in the model database was created by end users on ' - + CAST(create_date AS VARCHAR(20)) - + '. Tables in the model database are automatically copied into all new databases.' ) AS Details - FROM model.sys.tables - WHERE is_ms_shipped = 0; - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 30 ) - BEGIN - IF ( SELECT COUNT(*) - FROM msdb.dbo.sysalerts - WHERE severity BETWEEN 19 AND 25 - ) < 7 - - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 30) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 30 AS CheckID , - 200 AS Priority , - 'Monitoring' AS FindingsGroup , - 'Not All Alerts Configured' AS Finding , - 'https://www.brentozar.com/go/alert' AS URL , - ( 'Not all SQL Server Agent alerts have been configured. This is a free, easy way to get notified of corruption, job failures, or major outages even before monitoring systems pick it up.' ) AS Details; - END; - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 59 ) - BEGIN - IF EXISTS ( SELECT * - FROM msdb.dbo.sysalerts - WHERE enabled = 1 - AND COALESCE(has_notification, 0) = 0 - AND (job_id IS NULL OR job_id = 0x)) - - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 59) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 59 AS CheckID , - 200 AS Priority , - 'Monitoring' AS FindingsGroup , - 'Alerts Configured without Follow Up' AS Finding , - 'https://www.brentozar.com/go/alert' AS URL , - ( 'SQL Server Agent alerts have been configured but they either do not notify anyone or else they do not take any action. This is a free, easy way to get notified of corruption, job failures, or major outages even before monitoring systems pick it up.' ) AS Details; - - END; - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 96 ) - BEGIN - IF NOT EXISTS ( SELECT * - FROM msdb.dbo.sysalerts - WHERE message_id IN ( 823, 824, 825 ) ) - - BEGIN; - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 96) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 96 AS CheckID , - 200 AS Priority , - 'Monitoring' AS FindingsGroup , - 'No Alerts for Corruption' AS Finding , - 'https://www.brentozar.com/go/alert' AS URL , - ( 'SQL Server Agent alerts do not exist for errors 823, 824, and 825. These three errors can give you notification about early hardware failure. Enabling them can prevent you a lot of heartbreak.' ) AS Details; - - END; - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 61 ) - BEGIN - IF NOT EXISTS ( SELECT * - FROM msdb.dbo.sysalerts - WHERE severity BETWEEN 19 AND 25 ) - - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 61) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 61 AS CheckID , - 200 AS Priority , - 'Monitoring' AS FindingsGroup , - 'No Alerts for Sev 19-25' AS Finding , - 'https://www.brentozar.com/go/alert' AS URL , - ( 'SQL Server Agent alerts do not exist for severity levels 19 through 25. These are some very severe SQL Server errors. Knowing that these are happening may let you recover from errors faster.' ) AS Details; - - END; - - END; - - --check for disabled alerts - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 98 ) - BEGIN - IF EXISTS ( SELECT name - FROM msdb.dbo.sysalerts - WHERE enabled = 0 ) - - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 98) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 98 AS CheckID , - 200 AS Priority , - 'Monitoring' AS FindingsGroup , - 'Alerts Disabled' AS Finding , - 'https://www.brentozar.com/go/alert' AS URL , - ( 'The following Alert is disabled, please review and enable if desired: ' - + name ) AS Details - FROM msdb.dbo.sysalerts - WHERE enabled = 0; - END; - END; - - --check for alerts that do NOT include event descriptions in their outputs via email/pager/net-send - IF NOT EXISTS ( - SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL - AND CheckID = 219 - ) - BEGIN; - IF @Debug IN (1, 2) - BEGIN; - RAISERROR ('Running CheckId [%d].', 0, 1, 219) WITH NOWAIT; - END; - - INSERT INTO #BlitzResults ( - CheckID - ,[Priority] - ,FindingsGroup - ,Finding - ,[URL] - ,Details - ) - SELECT 219 AS CheckID - ,200 AS [Priority] - ,'Monitoring' AS FindingsGroup - ,'Alerts Without Event Descriptions' AS Finding - ,'https://www.brentozar.com/go/alert' AS [URL] - ,('The following Alert is not including detailed event descriptions in its output messages: ' + QUOTENAME([name]) - + '. You can fix it by ticking the relevant boxes in its Properties --> Options page.') AS Details - FROM msdb.dbo.sysalerts - WHERE [enabled] = 1 - AND include_event_description = 0 --bitmask: 1 = email, 2 = pager, 4 = net send - ; - END; - - --check whether we have NO ENABLED operators! - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 31 ) - BEGIN; - IF NOT EXISTS ( SELECT * - FROM msdb.dbo.sysoperators - WHERE enabled = 1 ) - - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 31) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 31 AS CheckID , - 200 AS Priority , - 'Monitoring' AS FindingsGroup , - 'No Operators Configured/Enabled' AS Finding , - 'https://www.brentozar.com/go/op' AS URL , - ( 'No SQL Server Agent operators (emails) have been configured. This is a free, easy way to get notified of corruption, job failures, or major outages even before monitoring systems pick it up.' ) AS Details; - - END; - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 34 ) - BEGIN - IF EXISTS ( SELECT * - FROM sys.all_objects - WHERE name = 'dm_db_mirroring_auto_page_repair' ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 34) WITH NOWAIT; - - SET @StringToExecute = 'INSERT INTO #BlitzResults - (CheckID, - DatabaseName, - Priority, - FindingsGroup, - Finding, - URL, - Details) - SELECT DISTINCT - 34 AS CheckID , - db.name , - 1 AS Priority , - ''Corruption'' AS FindingsGroup , - ''Database Corruption Detected'' AS Finding , - ''https://www.brentozar.com/go/repair'' AS URL , - ( ''Database mirroring has automatically repaired at least one corrupt page in the last 30 days. For more information, query the DMV sys.dm_db_mirroring_auto_page_repair.'' ) AS Details - FROM (SELECT rp2.database_id, rp2.modification_time - FROM sys.dm_db_mirroring_auto_page_repair rp2 - WHERE rp2.[database_id] not in ( - SELECT db2.[database_id] - FROM sys.databases as db2 - WHERE db2.[state] = 1 - ) ) as rp - INNER JOIN master.sys.databases db ON rp.database_id = db.database_id - WHERE rp.modification_time >= DATEADD(dd, -30, GETDATE()) OPTION (RECOMPILE);'; - - IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; - IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; - - EXECUTE(@StringToExecute); - END; - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 89 ) - BEGIN - IF EXISTS ( SELECT * - FROM sys.all_objects - WHERE name = 'dm_hadr_auto_page_repair' ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 89) WITH NOWAIT; - - SET @StringToExecute = 'INSERT INTO #BlitzResults - (CheckID, - DatabaseName, - Priority, - FindingsGroup, - Finding, - URL, - Details) - SELECT DISTINCT - 89 AS CheckID , - db.name , - 1 AS Priority , - ''Corruption'' AS FindingsGroup , - ''Database Corruption Detected'' AS Finding , - ''https://www.brentozar.com/go/repair'' AS URL , - ( ''Availability Groups has automatically repaired at least one corrupt page in the last 30 days. For more information, query the DMV sys.dm_hadr_auto_page_repair.'' ) AS Details - FROM sys.dm_hadr_auto_page_repair rp - INNER JOIN master.sys.databases db ON rp.database_id = db.database_id - WHERE rp.modification_time >= DATEADD(dd, -30, GETDATE()) OPTION (RECOMPILE) ;'; - - IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; - IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; - - EXECUTE(@StringToExecute); - END; - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 90 ) - BEGIN - IF EXISTS ( SELECT * - FROM msdb.sys.all_objects - WHERE name = 'suspect_pages' ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 90) WITH NOWAIT; - - SET @StringToExecute = 'INSERT INTO #BlitzResults - (CheckID, - DatabaseName, - Priority, - FindingsGroup, - Finding, - URL, - Details) - SELECT DISTINCT - 90 AS CheckID , - db.name , - 1 AS Priority , - ''Corruption'' AS FindingsGroup , - ''Database Corruption Detected'' AS Finding , - ''https://www.brentozar.com/go/repair'' AS URL , - ( ''SQL Server has detected at least one corrupt page in the last 30 days. For more information, query the system table msdb.dbo.suspect_pages.'' ) AS Details - FROM msdb.dbo.suspect_pages sp - INNER JOIN master.sys.databases db ON sp.database_id = db.database_id - WHERE sp.last_update_date >= DATEADD(dd, -30, GETDATE()) OPTION (RECOMPILE);'; - - IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; - IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; - - EXECUTE(@StringToExecute); - END; - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 36 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 36) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT DISTINCT - 36 AS CheckID , - 150 AS Priority , - 'Performance' AS FindingsGroup , - 'Slow Storage Reads on Drive ' - + UPPER(LEFT(mf.physical_name, 1)) AS Finding , - 'https://www.brentozar.com/go/slow' AS URL , - 'Reads are averaging longer than 200ms for at least one database on this drive. For specific database file speeds, run the query from the information link.' AS Details - FROM sys.dm_io_virtual_file_stats(NULL, NULL) - AS fs - INNER JOIN sys.master_files AS mf ON fs.database_id = mf.database_id - AND fs.[file_id] = mf.[file_id] - WHERE ( io_stall_read_ms / ( 1.0 + num_of_reads ) ) > 200 - AND num_of_reads > 100000; - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 37 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 37) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT DISTINCT - 37 AS CheckID , - 150 AS Priority , - 'Performance' AS FindingsGroup , - 'Slow Storage Writes on Drive ' - + UPPER(LEFT(mf.physical_name, 1)) AS Finding , - 'https://www.brentozar.com/go/slow' AS URL , - 'Writes are averaging longer than 100ms for at least one database on this drive. For specific database file speeds, run the query from the information link.' AS Details - FROM sys.dm_io_virtual_file_stats(NULL, NULL) - AS fs - INNER JOIN sys.master_files AS mf ON fs.database_id = mf.database_id - AND fs.[file_id] = mf.[file_id] - WHERE ( io_stall_write_ms / ( 1.0 - + num_of_writes ) ) > 100 - AND num_of_writes > 100000; - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 40 ) - BEGIN - IF ( SELECT COUNT(*) - FROM tempdb.sys.database_files - WHERE type_desc = 'ROWS' - ) = 1 - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 40) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - VALUES ( 40 , - 'tempdb' , - 170 , - 'File Configuration' , - 'TempDB Only Has 1 Data File' , - 'https://www.brentozar.com/go/tempdb' , - 'TempDB is only configured with one data file. More data files are usually required to alleviate SGAM contention.' - ); - END; - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 183 ) - - BEGIN - - IF ( SELECT COUNT (distinct [size]) - FROM tempdb.sys.database_files - WHERE type_desc = 'ROWS' - 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; - - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - VALUES ( 183 , - 'tempdb' , - 170 , - 'File Configuration' , - 'TempDB Unevenly Sized Data Files' , - 'https://www.brentozar.com/go/tempdb' , - 'TempDB data files are not configured with the same size. Unevenly sized tempdb data files will result in unevenly sized workloads.' - ); - END; - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 44 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 44) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 44 AS CheckID , - 150 AS Priority , - 'Performance' AS FindingsGroup , - 'Queries Forcing Order Hints' AS Finding , - 'https://www.brentozar.com/go/hints' AS URL , - CAST(occurrence AS VARCHAR(10)) - + ' instances of order hinting have been recorded since restart. This means queries are bossing the SQL Server optimizer around, and if they don''t know what they''re doing, this can cause more harm than good. This can also explain why DBA tuning efforts aren''t working.' AS Details - FROM sys.dm_exec_query_optimizer_info - WHERE counter = 'order hint' - AND occurrence > 1000; - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 45 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 45) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 45 AS CheckID , - 150 AS Priority , - 'Performance' AS FindingsGroup , - 'Queries Forcing Join Hints' AS Finding , - 'https://www.brentozar.com/go/hints' AS URL , - CAST(occurrence AS VARCHAR(10)) - + ' instances of join hinting have been recorded since restart. This means queries are bossing the SQL Server optimizer around, and if they don''t know what they''re doing, this can cause more harm than good. This can also explain why DBA tuning efforts aren''t working.' AS Details - FROM sys.dm_exec_query_optimizer_info - WHERE counter = 'join hint' - AND occurrence > 1000; - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 49 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 49) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT DISTINCT - 49 AS CheckID , - 200 AS Priority , - 'Informational' AS FindingsGroup , - 'Linked Server Configured' AS Finding , - 'https://www.brentozar.com/go/link' AS URL , - +CASE WHEN l.remote_name = 'sa' - THEN COALESCE(s.data_source, s.provider) - + ' is configured as a linked server. Check its security configuration as it is connecting with sa, because any user who queries it will get admin-level permissions.' - ELSE COALESCE(s.data_source, s.provider) - + ' is configured as a linked server. Check its security configuration to make sure it isn''t connecting with SA or some other bone-headed administrative login, because any user who queries it might get admin-level permissions.' - END AS Details - FROM sys.servers s - INNER JOIN sys.linked_logins l ON s.server_id = l.server_id - WHERE s.is_linked = 1; - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 50 ) - BEGIN - IF @@VERSION NOT LIKE '%Microsoft SQL Server 2000%' - AND @@VERSION NOT LIKE '%Microsoft SQL Server 2005%' - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 50) WITH NOWAIT; - - SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) - SELECT 50 AS CheckID , - 100 AS Priority , - ''Performance'' AS FindingsGroup , - ''Max Memory Set Too High'' AS Finding , - ''https://www.brentozar.com/go/max'' AS URL , - ''SQL Server max memory is set to '' - + CAST(c.value_in_use AS VARCHAR(20)) - + '' megabytes, but the server only has '' - + CAST(( CAST(m.total_physical_memory_kb AS BIGINT) / 1024 ) AS VARCHAR(20)) - + '' megabytes. SQL Server may drain the system dry of memory, and under certain conditions, this can cause Windows to swap to disk.'' AS Details - FROM sys.dm_os_sys_memory m - INNER JOIN sys.configurations c ON c.name = ''max server memory (MB)'' - WHERE CAST(m.total_physical_memory_kb AS BIGINT) < ( CAST(c.value_in_use AS BIGINT) * 1024 ) OPTION (RECOMPILE);'; - - IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; - IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; - - EXECUTE(@StringToExecute); - END; - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 51 ) - BEGIN - IF @@VERSION NOT LIKE '%Microsoft SQL Server 2000%' - AND @@VERSION NOT LIKE '%Microsoft SQL Server 2005%' - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 51) WITH NOWAIT - - SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) - SELECT 51 AS CheckID , - 1 AS Priority , - ''Performance'' AS FindingsGroup , - ''Memory Dangerously Low'' AS Finding , - ''https://www.brentozar.com/go/max'' AS URL , - ''The server has '' + CAST(( CAST(m.total_physical_memory_kb AS BIGINT) / 1024 ) AS VARCHAR(20)) + '' megabytes of physical memory, but only '' + CAST(( CAST(m.available_physical_memory_kb AS BIGINT) / 1024 ) AS VARCHAR(20)) - + '' megabytes are available. As the server runs out of memory, there is danger of swapping to disk, which will kill performance.'' AS Details - FROM sys.dm_os_sys_memory m - WHERE CAST(m.available_physical_memory_kb AS BIGINT) < 262144 OPTION (RECOMPILE);'; - - IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; - IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; - - EXECUTE(@StringToExecute); - END; - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 159 ) - BEGIN - IF @@VERSION NOT LIKE '%Microsoft SQL Server 2000%' - AND @@VERSION NOT LIKE '%Microsoft SQL Server 2005%' - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 159) WITH NOWAIT; - - SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) - SELECT DISTINCT 159 AS CheckID , - 1 AS Priority , - ''Performance'' AS FindingsGroup , - ''Memory Dangerously Low in NUMA Nodes'' AS Finding , - ''https://www.brentozar.com/go/max'' AS URL , - ''At least one NUMA node is reporting THREAD_RESOURCES_LOW in sys.dm_os_nodes and can no longer create threads.'' AS Details - FROM sys.dm_os_nodes m - WHERE node_state_desc LIKE ''%THREAD_RESOURCES_LOW%'' OPTION (RECOMPILE);'; - - IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; - IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; - - EXECUTE(@StringToExecute); - END; - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 53 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 53) WITH NOWAIT; - - DECLARE @AOFCI AS INT, @AOAG AS INT, @HAType AS VARCHAR(10), @errmsg AS VARCHAR(200) - - SELECT @AOAG = CAST(SERVERPROPERTY('IsHadrEnabled') AS INT) - SELECT @AOFCI = CAST(SERVERPROPERTY('IsClustered') AS INT) - - IF (SELECT COUNT(DISTINCT join_state_desc) FROM sys.dm_hadr_availability_replica_cluster_states) = 2 - BEGIN --if count is 2 both JOINED_STANDALONE and JOINED_FCI is configured - SET @AOFCI = 1 - END - - SELECT @HAType = - CASE - WHEN @AOFCI = 1 AND @AOAG =1 THEN 'FCIAG' - WHEN @AOFCI = 1 AND @AOAG =0 THEN 'FCI' - WHEN @AOFCI = 0 AND @AOAG =1 THEN 'AG' - ELSE 'STANDALONE' - END - - IF (@HAType IN ('FCIAG','FCI','AG')) - BEGIN - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - - SELECT DISTINCT 53 AS CheckID , - 200 AS Priority , - 'Informational' AS FindingsGroup , - 'Cluster Node' AS Finding , - 'https://BrentOzar.com/go/node' AS URL , - 'This is a node in a cluster.' AS Details - - IF @HAType = 'AG' - BEGIN - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT DISTINCT 53 AS CheckID , - 200 AS Priority , - 'Informational' AS FindingsGroup , - 'Cluster Node Info' AS Finding , - 'https://BrentOzar.com/go/node' AS URL, - 'The cluster nodes are: ' + STUFF((SELECT ', ' + CASE ar.replica_server_name WHEN dhags.primary_replica THEN 'PRIMARY' - ELSE 'SECONDARY' - END + '=' + UPPER(ar.replica_server_name) - FROM sys.availability_groups AS ag - LEFT OUTER JOIN sys.availability_replicas AS ar ON ag.group_id = ar.group_id - LEFT OUTER JOIN sys.dm_hadr_availability_group_states as dhags ON ag.group_id = dhags.group_id - ORDER BY 1 - FOR XML PATH ('') - ), 1, 1, '') + ' - High Availability solution used is AlwaysOn Availability Group (AG)' As Details - END - - IF @HAType = 'FCI' - BEGIN - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT DISTINCT 53 AS CheckID , - 200 AS Priority , - 'Informational' AS FindingsGroup , - 'Cluster Node Info' AS Finding , - 'https://BrentOzar.com/go/node' AS URL, - 'The cluster nodes are: ' + STUFF((SELECT ', ' + CASE is_current_owner - WHEN 1 THEN 'PRIMARY' - ELSE 'SECONDARY' - END + '=' + UPPER(NodeName) - FROM sys.dm_os_cluster_nodes - ORDER BY 1 - FOR XML PATH ('') - ), 1, 1, '') + ' - High Availability solution used is AlwaysOn Failover Cluster Instance (FCI)' As Details - END - - IF @HAType = 'FCIAG' - BEGIN - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT DISTINCT 53 AS CheckID , - 200 AS Priority , - 'Informational' AS FindingsGroup , - 'Cluster Node Info' AS Finding , - 'https://BrentOzar.com/go/node' AS URL, - 'The cluster nodes are: ' + STUFF((SELECT ', ' + HighAvailabilityRoleDesc + '=' + ServerName - FROM (SELECT UPPER(ar.replica_server_name) AS ServerName - ,CASE ar.replica_server_name WHEN dhags.primary_replica THEN 'PRIMARY' - ELSE 'SECONDARY' - END AS HighAvailabilityRoleDesc - FROM sys.availability_groups AS ag - LEFT OUTER JOIN sys.availability_replicas AS ar ON ag.group_id = ar.group_id - LEFT OUTER JOIN sys.dm_hadr_availability_group_states as dhags ON ag.group_id = dhags.group_id - WHERE UPPER(CAST(SERVERPROPERTY('ServerName') AS VARCHAR)) <> ar.replica_server_name - UNION ALL - SELECT UPPER(NodeName) AS ServerName - ,CASE is_current_owner - WHEN 1 THEN 'PRIMARY' - ELSE 'SECONDARY' - END AS HighAvailabilityRoleDesc - FROM sys.dm_os_cluster_nodes) AS Z - ORDER BY 1 - FOR XML PATH ('') - ), 1, 1, '') + ' - High Availability solution used is AlwaysOn FCI with AG (Failover Cluster Instance with Availability Group).'As Details - END - END - - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 55 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 55) WITH NOWAIT; - - IF @UsualDBOwner IS NULL - SET @UsualDBOwner = SUSER_SNAME(0x01); - - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 55 AS CheckID , - [name] AS DatabaseName , - 230 AS Priority , - 'Security' AS FindingsGroup , - 'Database Owner <> ' + @UsualDBOwner AS Finding , - 'https://www.brentozar.com/go/owndb' AS URL , - ( 'Database name: ' + [name] + ' ' - + 'Owner name: ' + SUSER_SNAME(owner_sid) ) AS Details - FROM sys.databases - WHERE (((SUSER_SNAME(owner_sid) <> SUSER_SNAME(0x01)) AND (name IN (N'master', N'model', N'msdb', N'tempdb'))) - OR ((SUSER_SNAME(owner_sid) <> @UsualDBOwner) AND (name NOT IN (N'master', N'model', N'msdb', N'tempdb'))) - ) - AND name NOT IN ( SELECT DISTINCT DatabaseName - FROM #SkipChecks - WHERE CheckID IS NULL OR CheckID = 55); - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 213 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 213) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 213 AS CheckID , - [name] AS DatabaseName , - 230 AS Priority , - 'Security' AS FindingsGroup , - 'Database Owner is Unknown' AS Finding , - '' AS URL , - ( 'Database name: ' + [name] + ' ' - + 'Owner name: ' + ISNULL(SUSER_SNAME(owner_sid),'~~ UNKNOWN ~~') ) AS Details - FROM sys.databases - WHERE SUSER_SNAME(owner_sid) is NULL - AND name NOT IN ( SELECT DISTINCT DatabaseName - FROM #SkipChecks - WHERE CheckID IS NULL OR CheckID = 213); - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 57 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 57) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 57 AS CheckID , - 230 AS Priority , - 'Security' AS FindingsGroup , - 'SQL Agent Job Runs at Startup' AS Finding , - 'https://www.brentozar.com/go/startup' AS URL , - ( 'Job [' + j.name - + '] runs automatically when SQL Server Agent starts up. Make sure you know exactly what this job is doing, because it could pose a security risk.' ) AS Details - FROM msdb.dbo.sysschedules sched - JOIN msdb.dbo.sysjobschedules jsched ON sched.schedule_id = jsched.schedule_id - JOIN msdb.dbo.sysjobs j ON jsched.job_id = j.job_id - WHERE sched.freq_type = 64 - AND sched.enabled = 1; - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 97 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 97) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 97 AS CheckID , - 100 AS Priority , - 'Performance' AS FindingsGroup , - 'Unusual SQL Server Edition' AS Finding , - 'https://www.brentozar.com/go/workgroup' AS URL , - ( 'This server is using ' - + CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) - + ', which is capped at low amounts of CPU and memory.' ) AS Details - WHERE CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) NOT LIKE '%Standard%' - AND CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) NOT LIKE '%Enterprise%' - AND CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) NOT LIKE '%Data Center%' - AND CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) NOT LIKE '%Developer%' - AND CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) NOT LIKE '%Business Intelligence%'; - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 154 ) - AND SERVERPROPERTY('EngineEdition') <> 8 - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 154) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 154 AS CheckID , - 10 AS Priority , - 'Performance' AS FindingsGroup , - '32-bit SQL Server Installed' AS Finding , - 'https://www.brentozar.com/go/32bit' AS URL , - ( 'This server uses the 32-bit x86 binaries for SQL Server instead of the 64-bit x64 binaries. The amount of memory available for query workspace and execution plans is heavily limited.' ) AS Details - WHERE CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) NOT LIKE '%64%'; - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 62 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 62) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 62 AS CheckID , - [name] AS DatabaseName , - 200 AS Priority , - 'Performance' AS FindingsGroup , - 'Old Compatibility Level' AS Finding , - 'https://www.brentozar.com/go/compatlevel' AS URL , - ( 'Database ' + [name] - + ' is compatibility level ' - + CAST(compatibility_level AS VARCHAR(20)) - + ', which may cause unwanted results when trying to run queries that have newer T-SQL features.' ) AS Details - FROM sys.databases - WHERE name NOT IN ( SELECT DISTINCT - DatabaseName - FROM #SkipChecks - WHERE CheckID IS NULL OR CheckID = 62) - AND compatibility_level <= 90; - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 94 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 94) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 94 AS CheckID , - 200 AS [Priority] , - 'Monitoring' AS FindingsGroup , - 'Agent Jobs Without Failure Emails' AS Finding , - 'https://www.brentozar.com/go/alerts' AS URL , - 'The job ' + [name] - + ' has not been set up to notify an operator if it fails.' AS Details - FROM msdb.[dbo].[sysjobs] j - WHERE j.enabled = 1 - AND j.notify_email_operator_id = 0 - AND j.notify_netsend_operator_id = 0 - AND j.notify_page_operator_id = 0 - AND j.category_id <> 100; /* Exclude SSRS category */ - END; - - IF EXISTS ( SELECT 1 - FROM sys.configurations - WHERE name = 'remote admin connections' - AND value_in_use = 0 ) - AND NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 100 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 100) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 100 AS CheckID , - 170 AS Priority , - 'Reliability' AS FindingGroup , - 'Remote DAC Disabled' AS Finding , - 'https://www.brentozar.com/go/dac' AS URL , - 'Remote access to the Dedicated Admin Connection (DAC) is not enabled. The DAC can make remote troubleshooting much easier when SQL Server is unresponsive.'; - END; - - IF EXISTS ( SELECT * - FROM sys.dm_os_schedulers - WHERE is_online = 0 ) - AND NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 101 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 101) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 101 AS CheckID , - 50 AS Priority , - 'Performance' AS FindingGroup , - 'CPU Schedulers Offline' AS Finding , - 'https://www.brentozar.com/go/schedulers' AS URL , - 'Some CPU cores are not accessible to SQL Server due to affinity masking or licensing problems.'; - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 110 ) - AND EXISTS (SELECT * FROM master.sys.all_objects WHERE name = 'dm_os_memory_nodes') - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 110) WITH NOWAIT; - - SET @StringToExecute = 'IF EXISTS (SELECT * - FROM sys.dm_os_nodes n - INNER JOIN sys.dm_os_memory_nodes m ON n.memory_node_id = m.memory_node_id - WHERE n.node_state_desc = ''OFFLINE'') - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 110 AS CheckID , - 50 AS Priority , - ''Performance'' AS FindingGroup , - ''Memory Nodes Offline'' AS Finding , - ''https://www.brentozar.com/go/schedulers'' AS URL , - ''Due to affinity masking or licensing problems, some of the memory may not be available.'' OPTION (RECOMPILE)'; - - IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; - IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; - - EXECUTE(@StringToExecute); - END; - - IF EXISTS ( SELECT * - FROM sys.databases - WHERE state > 1 ) - AND NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 102 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 102) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 102 AS CheckID , - [name] , - 20 AS Priority , - 'Reliability' AS FindingGroup , - 'Unusual Database State: ' + [state_desc] AS Finding , - 'https://www.brentozar.com/go/repair' AS URL , - 'This database may not be online.' - FROM sys.databases - WHERE state > 1; - END; - - IF EXISTS ( SELECT * - FROM master.sys.extended_procedures ) - AND NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 105 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 105) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 105 AS CheckID , - 'master' , - 200 AS Priority , - 'Reliability' AS FindingGroup , - 'Extended Stored Procedures in Master' AS Finding , - 'https://www.brentozar.com/go/clr' AS URL , - 'The [' + name - + '] extended stored procedure is in the master database. CLR may be in use, and the master database now needs to be part of your backup/recovery planning.' - FROM master.sys.extended_procedures; - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 107 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 107) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 107 AS CheckID , - 50 AS Priority , - 'Performance' AS FindingGroup , - 'Poison Wait Detected: ' + wait_type AS Finding , - 'https://www.brentozar.com/go/poison/#' + wait_type AS URL , - CONVERT(VARCHAR(10), (SUM([wait_time_ms]) / 1000) / 86400) + ':' + CONVERT(VARCHAR(20), DATEADD(s, (SUM([wait_time_ms]) / 1000), 0), 108) + ' of this wait have been recorded. This wait often indicates killer performance problems.' - FROM sys.[dm_os_wait_stats] - WHERE wait_type IN('IO_QUEUE_LIMIT', 'IO_RETRY', 'LOG_RATE_GOVERNOR', 'POOL_LOG_RATE_GOVERNOR', 'PREEMPTIVE_DEBUG', 'RESMGR_THROTTLED', 'RESOURCE_SEMAPHORE', 'RESOURCE_SEMAPHORE_QUERY_COMPILE','SE_REPL_CATCHUP_THROTTLE','SE_REPL_COMMIT_ACK','SE_REPL_COMMIT_TURN','SE_REPL_ROLLBACK_ACK','SE_REPL_SLOW_SECONDARY_THROTTLE','THREADPOOL') - GROUP BY wait_type - HAVING SUM([wait_time_ms]) > (SELECT 5000 * datediff(HH,create_date,CURRENT_TIMESTAMP) AS hours_since_startup FROM sys.databases WHERE name='tempdb') - AND SUM([wait_time_ms]) > 60000; - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 121 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 121) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 121 AS CheckID , - 50 AS Priority , - 'Performance' AS FindingGroup , - 'Poison Wait Detected: Serializable Locking' AS Finding , - 'https://www.brentozar.com/go/serializable' AS URL , - CONVERT(VARCHAR(10), (SUM([wait_time_ms]) / 1000) / 86400) + ':' + CONVERT(VARCHAR(20), DATEADD(s, (SUM([wait_time_ms]) / 1000), 0), 108) + ' of LCK_M_R% waits have been recorded. This wait often indicates killer performance problems.' - FROM sys.[dm_os_wait_stats] - WHERE wait_type IN ('LCK_M_RS_S', 'LCK_M_RS_U', 'LCK_M_RIn_NL','LCK_M_RIn_S', 'LCK_M_RIn_U','LCK_M_RIn_X', 'LCK_M_RX_S', 'LCK_M_RX_U','LCK_M_RX_X') - HAVING SUM([wait_time_ms]) > (SELECT 5000 * datediff(HH,create_date,CURRENT_TIMESTAMP) AS hours_since_startup FROM sys.databases WHERE name='tempdb') - AND SUM([wait_time_ms]) > 60000; - END; - - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 111 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 111) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - DatabaseName , - URL , - Details - ) - SELECT 111 AS CheckID , - 50 AS Priority , - 'Reliability' AS FindingGroup , - 'Possibly Broken Log Shipping' AS Finding , - d.[name] , - 'https://www.brentozar.com/go/shipping' AS URL , - d.[name] + ' is in a restoring state, but has not had a backup applied in the last two days. This is a possible indication of a broken transaction log shipping setup.' - FROM [master].sys.databases d - INNER JOIN [master].sys.database_mirroring dm ON d.database_id = dm.database_id - AND dm.mirroring_role IS NULL - WHERE ( d.[state] = 1 - OR (d.[state] = 0 AND d.[is_in_standby] = 1) ) - AND NOT EXISTS(SELECT * FROM msdb.dbo.restorehistory rh - INNER JOIN msdb.dbo.backupset bs ON rh.backup_set_id = bs.backup_set_id - WHERE d.[name] COLLATE SQL_Latin1_General_CP1_CI_AS = rh.destination_database_name COLLATE SQL_Latin1_General_CP1_CI_AS - AND rh.restore_date >= DATEADD(dd, -2, GETDATE())); - - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 112 ) - AND EXISTS (SELECT * FROM master.sys.all_objects WHERE name = 'change_tracking_databases') - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 112) WITH NOWAIT; - - SET @StringToExecute = 'INSERT INTO #BlitzResults - (CheckID, - Priority, - FindingsGroup, - Finding, - DatabaseName, - URL, - Details) - SELECT 112 AS CheckID, - 100 AS Priority, - ''Performance'' AS FindingsGroup, - ''Change Tracking Enabled'' AS Finding, - d.[name], - ''https://www.brentozar.com/go/tracking'' AS URL, - ( d.[name] + '' has change tracking enabled. This is not a default setting, and it has some performance overhead. It keeps track of changes to rows in tables that have change tracking turned on.'' ) AS Details FROM sys.change_tracking_databases AS ctd INNER JOIN sys.databases AS d ON ctd.database_id = d.database_id OPTION (RECOMPILE)'; - - IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; - IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; - - EXECUTE(@StringToExecute); - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 116 ) - AND EXISTS (SELECT * FROM msdb.sys.all_columns WHERE name = 'compressed_backup_size') - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 116) WITH NOWAIT - - SET @StringToExecute = 'INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 116 AS CheckID , - 200 AS Priority , - ''Informational'' AS FindingGroup , - ''Backup Compression Default Off'' AS Finding , - ''https://www.brentozar.com/go/backup'' AS URL , - ''Uncompressed full backups have happened recently, and backup compression is not turned on at the server level. Backup compression is included with SQL Server 2008R2 & newer, even in Standard Edition. We recommend turning backup compression on by default so that ad-hoc backups will get compressed.'' - FROM sys.configurations - WHERE configuration_id = 1579 AND CAST(value_in_use AS INT) = 0 - AND EXISTS (SELECT * FROM msdb.dbo.backupset WHERE backup_size = compressed_backup_size AND type = ''D'' AND backup_finish_date >= DATEADD(DD, -14, GETDATE())) OPTION (RECOMPILE);'; - - IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; - IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; - - EXECUTE(@StringToExecute); - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 117 ) - AND EXISTS (SELECT * FROM master.sys.all_objects WHERE name = 'dm_exec_query_resource_semaphores') - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 117) WITH NOWAIT; - - SET @StringToExecute = 'IF 0 < (SELECT SUM([forced_grant_count]) FROM sys.dm_exec_query_resource_semaphores WHERE [forced_grant_count] IS NOT NULL) - INSERT INTO #BlitzResults - (CheckID, - Priority, - FindingsGroup, - Finding, - URL, - Details) - SELECT 117 AS CheckID, - 100 AS Priority, - ''Performance'' AS FindingsGroup, - ''Memory Pressure Affecting Queries'' AS Finding, - ''https://www.brentozar.com/go/grants'' AS URL, - CAST(SUM(forced_grant_count) AS NVARCHAR(100)) + '' forced grants reported in the DMV sys.dm_exec_query_resource_semaphores, indicating memory pressure has affected query runtimes.'' - FROM sys.dm_exec_query_resource_semaphores WHERE [forced_grant_count] IS NOT NULL OPTION (RECOMPILE);'; - - IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; - IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; - - EXECUTE(@StringToExecute); - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 124 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 124) WITH NOWAIT; - - INSERT INTO #BlitzResults - (CheckID, - Priority, - FindingsGroup, - Finding, - URL, - Details) - SELECT 124, - 150, - 'Performance', - 'Deadlocks Happening Daily', - 'https://www.brentozar.com/go/deadlocks', - CAST(CAST(p.cntr_value / @DaysUptime AS BIGINT) AS NVARCHAR(100)) + ' average deadlocks per day. To find them, run sp_BlitzLock.' AS Details - FROM sys.dm_os_performance_counters p - INNER JOIN sys.databases d ON d.name = 'tempdb' - WHERE RTRIM(p.counter_name) = 'Number of Deadlocks/sec' - AND RTRIM(p.instance_name) = '_Total' - AND p.cntr_value > 0 - AND (1.0 * p.cntr_value / NULLIF(datediff(DD,create_date,CURRENT_TIMESTAMP),0)) > 10; - END; - - IF DATEADD(mi, -15, GETDATE()) < (SELECT TOP 1 creation_time FROM sys.dm_exec_query_stats ORDER BY creation_time) - AND NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 125 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 125) WITH NOWAIT; - - DECLARE @user_perm_sql NVARCHAR(MAX) = N''; - DECLARE @user_perm_gb_out DECIMAL(38,2); - - IF @ProductVersionMajor >= 11 - - BEGIN - - SET @user_perm_sql += N' - SELECT @user_perm_gb = CASE WHEN (pages_kb / 1024. / 1024.) >= 2. - THEN CONVERT(DECIMAL(38, 2), (pages_kb / 1024. / 1024.)) - ELSE NULL - END - FROM sys.dm_os_memory_clerks - WHERE type = ''USERSTORE_TOKENPERM'' - AND name = ''TokenAndPermUserStore'' - '; - - END - - IF @ProductVersionMajor < 11 - - BEGIN - SET @user_perm_sql += N' - SELECT @user_perm_gb = CASE WHEN ((single_pages_kb + multi_pages_kb) / 1024.0 / 1024.) >= 2. - THEN CONVERT(DECIMAL(38, 2), ((single_pages_kb + multi_pages_kb) / 1024.0 / 1024.)) - ELSE NULL - END - FROM sys.dm_os_memory_clerks - WHERE type = ''USERSTORE_TOKENPERM'' - AND name = ''TokenAndPermUserStore'' - '; - - END - - EXEC sys.sp_executesql @user_perm_sql, - N'@user_perm_gb DECIMAL(38,2) OUTPUT', - @user_perm_gb = @user_perm_gb_out OUTPUT - - INSERT INTO #BlitzResults - (CheckID, - Priority, - FindingsGroup, - Finding, - URL, - Details) - SELECT TOP 1 125, 10, 'Performance', 'Plan Cache Erased Recently', 'https://www.brentozar.com/askbrent/plan-cache-erased-recently/', - 'The oldest query in the plan cache was created at ' + CAST(creation_time AS NVARCHAR(50)) - + CASE WHEN @user_perm_gb_out IS NULL - THEN '. Someone ran DBCC FREEPROCCACHE, restarted SQL Server, or it is under horrific memory pressure.' - ELSE '. You also have ' + CONVERT(NVARCHAR(20), @user_perm_gb_out) + ' GB of USERSTORE_TOKENPERM, which could indicate unusual memory consumption.' - END - FROM sys.dm_exec_query_stats WITH (NOLOCK) - ORDER BY creation_time; - END; - - IF EXISTS (SELECT * FROM sys.configurations WHERE name = 'priority boost' AND (value = 1 OR value_in_use = 1)) - AND NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 126 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 126) WITH NOWAIT; - - INSERT INTO #BlitzResults - (CheckID, - Priority, - FindingsGroup, - Finding, - URL, - Details) - VALUES(126, 5, 'Reliability', 'Priority Boost Enabled', 'https://www.brentozar.com/go/priorityboost/', - 'Priority Boost sounds awesome, but it can actually cause your SQL Server to crash.'); - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 128 ) - AND SERVERPROPERTY('EngineEdition') <> 8 /* Azure Managed Instances */ - BEGIN - - IF (@ProductVersionMajor = 15 AND @ProductVersionMinor < 2000) OR - (@ProductVersionMajor = 14 AND @ProductVersionMinor < 1000) OR - (@ProductVersionMajor = 13 AND @ProductVersionMinor < 6300) OR - (@ProductVersionMajor = 12 AND @ProductVersionMinor < 6024) OR - (@ProductVersionMajor = 11 /*AND @ProductVersionMinor < 7001)*/) OR - (@ProductVersionMajor = 10.5 /*AND @ProductVersionMinor < 6000*/) OR - (@ProductVersionMajor = 10 /*AND @ProductVersionMinor < 6000*/) OR - (@ProductVersionMajor = 9 /*AND @ProductVersionMinor <= 5000*/) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 128) WITH NOWAIT; - - INSERT INTO #BlitzResults(CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES(128, 20, 'Reliability', 'Unsupported Build of SQL Server', 'https://www.brentozar.com/go/unsupported', - 'Version ' + CAST(@ProductVersionMajor AS VARCHAR(100)) + - CASE WHEN @ProductVersionMajor >= 12 THEN - '.' + CAST(@ProductVersionMinor AS VARCHAR(100)) + ' is no longer supported by Microsoft. You need to apply a service pack.' - ELSE ' is no longer supported by Microsoft. You should be making plans to upgrade to a modern version of SQL Server.' END); - END; - - END; - - /* Reliability - Dangerous Build of SQL Server (Corruption) */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 129 ) - AND SERVERPROPERTY('EngineEdition') <> 8 /* Azure Managed Instances */ - BEGIN - IF (@ProductVersionMajor = 11 AND @ProductVersionMinor >= 3000 AND @ProductVersionMinor <= 3436) OR - (@ProductVersionMajor = 11 AND @ProductVersionMinor = 5058) OR - (@ProductVersionMajor = 12 AND @ProductVersionMinor >= 2000 AND @ProductVersionMinor <= 2342) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 129) WITH NOWAIT; - - INSERT INTO #BlitzResults(CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES(129, 20, 'Reliability', 'Dangerous Build of SQL Server (Corruption)', 'http://sqlperformance.com/2014/06/sql-indexes/hotfix-sql-2012-rebuilds', - 'There are dangerous known bugs with version ' + CAST(@ProductVersionMajor AS VARCHAR(100)) + '.' + CAST(@ProductVersionMinor AS VARCHAR(100)) + '. Check the URL for details and apply the right service pack or hotfix.'); - END; - - END; - - /* Reliability - Dangerous Build of SQL Server (Security) */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 157 ) - AND SERVERPROPERTY('EngineEdition') <> 8 /* Azure Managed Instances */ - BEGIN - IF (@ProductVersionMajor = 10 AND @ProductVersionMinor >= 5500 AND @ProductVersionMinor <= 5512) OR - (@ProductVersionMajor = 10 AND @ProductVersionMinor >= 5750 AND @ProductVersionMinor <= 5867) OR - (@ProductVersionMajor = 10.5 AND @ProductVersionMinor >= 4000 AND @ProductVersionMinor <= 4017) OR - (@ProductVersionMajor = 10.5 AND @ProductVersionMinor >= 4251 AND @ProductVersionMinor <= 4319) OR - (@ProductVersionMajor = 11 AND @ProductVersionMinor >= 3000 AND @ProductVersionMinor <= 3129) OR - (@ProductVersionMajor = 11 AND @ProductVersionMinor >= 3300 AND @ProductVersionMinor <= 3447) OR - (@ProductVersionMajor = 12 AND @ProductVersionMinor >= 2000 AND @ProductVersionMinor <= 2253) OR - (@ProductVersionMajor = 12 AND @ProductVersionMinor >= 2300 AND @ProductVersionMinor <= 2370) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 157) WITH NOWAIT; - - INSERT INTO #BlitzResults(CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES(157, 20, 'Reliability', 'Dangerous Build of SQL Server (Security)', 'https://technet.microsoft.com/en-us/library/security/MS14-044', - 'There are dangerous known bugs with version ' + CAST(@ProductVersionMajor AS VARCHAR(100)) + '.' + CAST(@ProductVersionMinor AS VARCHAR(100)) + '. Check the URL for details and apply the right service pack or hotfix.'); - END; - - END; - - /* Check if SQL 2016 Standard Edition but not SP1 */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 189 ) - AND SERVERPROPERTY('EngineEdition') <> 8 /* Azure Managed Instances */ - BEGIN - IF (@ProductVersionMajor = 13 AND @ProductVersionMinor < 4001 AND @@VERSION LIKE '%Standard Edition%') - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 189) WITH NOWAIT; - - INSERT INTO #BlitzResults(CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES(189, 100, 'Features', 'Missing Features', 'https://blogs.msdn.microsoft.com/sqlreleaseservices/sql-server-2016-service-pack-1-sp1-released/', - 'SQL 2016 Standard Edition is being used but not Service Pack 1. Check the URL for a list of Enterprise Features that are included in Standard Edition as of SP1.'); - END; - - END; - - /* Check if SQL 2017 but not CU3 */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 216 ) - AND SERVERPROPERTY('EngineEdition') <> 8 /* Azure Managed Instances */ - BEGIN - IF (@ProductVersionMajor = 14 AND @ProductVersionMinor < 3015) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 216) WITH NOWAIT; - - INSERT INTO #BlitzResults(CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES(216, 100, 'Features', 'Missing Features', 'https://support.microsoft.com/en-us/help/4041814', - 'SQL 2017 is being used but not Cumulative Update 3. We''d recommend patching to take advantage of increased analytics when running BlitzCache.'); - END; - - END; - - /* Cumulative Update Available */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 217 ) - AND SERVERPROPERTY('EngineEdition') NOT IN (5,8) /* Azure Managed Instances and Azure SQL DB*/ - AND EXISTS (SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 'SqlServerVersions' AND TABLE_TYPE = 'BASE TABLE') - AND NOT EXISTS (SELECT * FROM #BlitzResults WHERE CheckID IN (128, 129, 157, 189, 216)) /* Other version checks */ - BEGIN - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 217) WITH NOWAIT; - - INSERT INTO #BlitzResults(CheckID, Priority, FindingsGroup, Finding, URL, Details) - SELECT TOP 1 217, 100, 'Reliability', 'Cumulative Update Available', COALESCE(v.Url, 'https://SQLServerUpdates.com/'), - v.MinorVersionName + ' was released on ' + CAST(CONVERT(DATETIME, v.ReleaseDate, 112) AS VARCHAR(100)) - FROM dbo.SqlServerVersions v - WHERE v.MajorVersionNumber = @ProductVersionMajor - AND v.MinorVersionNumber > @ProductVersionMinor - ORDER BY v.MinorVersionNumber DESC; - END; - - /* Performance - High Memory Use for In-Memory OLTP (Hekaton) */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 145 ) - AND EXISTS ( SELECT * - FROM sys.all_objects o - WHERE o.name = 'dm_db_xtp_table_memory_stats' ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 145) WITH NOWAIT; - - SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) - SELECT 145 AS CheckID, - 10 AS Priority, - ''Performance'' AS FindingsGroup, - ''High Memory Use for In-Memory OLTP (Hekaton)'' AS Finding, - ''https://www.brentozar.com/go/hekaton'' AS URL, - CAST(CAST((SUM(mem.pages_kb / 1024.0) / CAST(value_in_use AS INT) * 100) AS INT) AS NVARCHAR(100)) + ''% of your '' + CAST(CAST((CAST(value_in_use AS DECIMAL(38,1)) / 1024) AS MONEY) AS NVARCHAR(100)) + ''GB of your max server memory is being used for in-memory OLTP tables (Hekaton). Microsoft recommends having 2X your Hekaton table space available in memory just for Hekaton, with a max of 250GB of in-memory data regardless of your server memory capacity.'' AS Details - FROM sys.configurations c INNER JOIN sys.dm_os_memory_clerks mem ON mem.type = ''MEMORYCLERK_XTP'' - WHERE c.name = ''max server memory (MB)'' - GROUP BY c.value_in_use - HAVING CAST(value_in_use AS DECIMAL(38,2)) * .25 < SUM(mem.pages_kb / 1024.0) - OR SUM(mem.pages_kb / 1024.0) > 250000 OPTION (RECOMPILE)'; - - IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; - IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; - - EXECUTE(@StringToExecute); - END; - - /* Performance - In-Memory OLTP (Hekaton) In Use */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 146 ) - AND EXISTS ( SELECT * - FROM sys.all_objects o - WHERE o.name = 'dm_db_xtp_table_memory_stats' ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 146) WITH NOWAIT; - - SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) - SELECT 146 AS CheckID, - 200 AS Priority, - ''Performance'' AS FindingsGroup, - ''In-Memory OLTP (Hekaton) In Use'' AS Finding, - ''https://www.brentozar.com/go/hekaton'' AS URL, - CAST(CAST((SUM(mem.pages_kb / 1024.0) / CAST(value_in_use AS INT) * 100) AS DECIMAL(6,1)) AS NVARCHAR(100)) + ''% of your '' + CAST(CAST((CAST(value_in_use AS DECIMAL(38,1)) / 1024) AS MONEY) AS NVARCHAR(100)) + ''GB of your max server memory is being used for in-memory OLTP tables (Hekaton).'' AS Details - FROM sys.configurations c INNER JOIN sys.dm_os_memory_clerks mem ON mem.type = ''MEMORYCLERK_XTP'' - WHERE c.name = ''max server memory (MB)'' - GROUP BY c.value_in_use - HAVING SUM(mem.pages_kb / 1024.0) > 1000 OPTION (RECOMPILE)'; - - IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; - IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; - - EXECUTE(@StringToExecute); - END; - - /* In-Memory OLTP (Hekaton) - Transaction Errors */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 147 ) - AND EXISTS ( SELECT * - FROM sys.all_objects o - WHERE o.name = 'dm_xtp_transaction_stats' ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 147) WITH NOWAIT - - SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) - SELECT 147 AS CheckID, - 100 AS Priority, - ''In-Memory OLTP (Hekaton)'' AS FindingsGroup, - ''Transaction Errors'' AS Finding, - ''https://www.brentozar.com/go/hekaton'' AS URL, - ''Since restart: '' + CAST(validation_failures AS NVARCHAR(100)) + '' validation failures, '' + CAST(dependencies_failed AS NVARCHAR(100)) + '' dependency failures, '' + CAST(write_conflicts AS NVARCHAR(100)) + '' write conflicts, '' + CAST(unique_constraint_violations AS NVARCHAR(100)) + '' unique constraint violations.'' AS Details - FROM sys.dm_xtp_transaction_stats - WHERE validation_failures <> 0 - OR dependencies_failed <> 0 - OR write_conflicts <> 0 - OR unique_constraint_violations <> 0 OPTION (RECOMPILE);'; - - IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; - IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; - - EXECUTE(@StringToExecute); - END; - - /* Reliability - Database Files on Network File Shares */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 148 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 148) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT DISTINCT 148 AS CheckID , - d.[name] AS DatabaseName , - 170 AS Priority , - 'Reliability' AS FindingsGroup , - 'Database Files on Network File Shares' AS Finding , - 'https://www.brentozar.com/go/nas' AS URL , - ( 'Files for this database are on: ' + LEFT(mf.physical_name, 30)) AS Details - FROM sys.databases d - INNER JOIN sys.master_files mf ON d.database_id = mf.database_id - WHERE mf.physical_name LIKE '\\%' - AND d.name NOT IN ( SELECT DISTINCT - DatabaseName - FROM #SkipChecks - WHERE CheckID IS NULL OR CheckID = 148); - END; - - /* Reliability - Database Files Stored in Azure */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 149 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 149) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT DISTINCT 149 AS CheckID , - d.[name] AS DatabaseName , - 170 AS Priority , - 'Reliability' AS FindingsGroup , - 'Database Files Stored in Azure' AS Finding , - 'https://www.brentozar.com/go/azurefiles' AS URL , - ( 'Files for this database are on: ' + LEFT(mf.physical_name, 30)) AS Details - FROM sys.databases d - INNER JOIN sys.master_files mf ON d.database_id = mf.database_id - WHERE mf.physical_name LIKE 'http://%' - AND d.name NOT IN ( SELECT DISTINCT - DatabaseName - FROM #SkipChecks - WHERE CheckID IS NULL OR CheckID = 149); - END; - - /* Reliability - Errors Logged Recently in the Default Trace */ - - /* First, let's check that there aren't any issues with the trace files */ - BEGIN TRY - - IF @SkipTrace = 0 - BEGIN - INSERT INTO #fnTraceGettable - ( TextData , - DatabaseName , - EventClass , - Severity , - StartTime , - EndTime , - Duration , - NTUserName , - NTDomainName , - HostName , - ApplicationName , - LoginName , - DBUserName - ) - SELECT TOP 20000 - CONVERT(NVARCHAR(4000),t.TextData) , - t.DatabaseName , - t.EventClass , - t.Severity , - t.StartTime , - t.EndTime , - t.Duration , - t.NTUserName , - t.NTDomainName , - t.HostName , - t.ApplicationName , - t.LoginName , - t.DBUserName - FROM sys.fn_trace_gettable(@base_tracefilename, DEFAULT) t - WHERE - ( - t.EventClass = 22 - AND t.Severity >= 17 - AND t.StartTime > DATEADD(dd, -30, GETDATE()) - ) - OR - ( - t.EventClass IN (92, 93) - AND t.StartTime > DATEADD(dd, -30, GETDATE()) - AND t.Duration > 15000000 - ) - OR - ( - t.EventClass IN (94, 95, 116) - ) - END; - - SET @TraceFileIssue = 0 - - END TRY - BEGIN CATCH - - SET @TraceFileIssue = 1 - - END CATCH - - IF @TraceFileIssue = 1 - BEGIN - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 199 ) - - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT - '199' AS CheckID , - '' AS DatabaseName , - 50 AS Priority , - 'Reliability' AS FindingsGroup , - 'There Is An Error With The Default Trace' AS Finding , - 'https://www.brentozar.com/go/defaulttrace' AS URL , - 'Somebody has been messing with your trace files. Check the files are present at ' + @base_tracefilename AS Details - END - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 150 ) - AND @base_tracefilename IS NOT NULL - AND @TraceFileIssue = 0 - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 150) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT DISTINCT 150 AS CheckID , - t.DatabaseName, - 170 AS Priority , - 'Reliability' AS FindingsGroup , - 'Errors Logged Recently in the Default Trace' AS Finding , - 'https://www.brentozar.com/go/defaulttrace' AS URL , - CAST(t.TextData AS NVARCHAR(4000)) AS Details - FROM #fnTraceGettable t - WHERE t.EventClass = 22 - /* Removed these as they're unnecessary, we filter this when inserting data into #fnTraceGettable */ - --AND t.Severity >= 17 - --AND t.StartTime > DATEADD(dd, -30, GETDATE()); - END; - - /* Performance - File Growths Slow */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 151 ) - AND @base_tracefilename IS NOT NULL - AND @TraceFileIssue = 0 - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 151) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT DISTINCT 151 AS CheckID , - t.DatabaseName, - 50 AS Priority , - 'Performance' AS FindingsGroup , - 'File Growths Slow' AS Finding , - 'https://www.brentozar.com/go/filegrowth' AS URL , - CAST(COUNT(*) AS NVARCHAR(100)) + ' growths took more than 15 seconds each. Consider setting file autogrowth to a smaller increment.' AS Details - FROM #fnTraceGettable t - WHERE t.EventClass IN (92, 93) - /* Removed these as they're unnecessary, we filter this when inserting data into #fnTraceGettable */ - --AND t.StartTime > DATEADD(dd, -30, GETDATE()) - --AND t.Duration > 15000000 - GROUP BY t.DatabaseName - HAVING COUNT(*) > 1; - END; - - /* Performance - Many Plans for One Query */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 160 ) - AND EXISTS (SELECT * FROM sys.all_columns WHERE name = 'query_hash') - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 160) WITH NOWAIT; - - SET @StringToExecute = N'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) - SELECT TOP 1 160 AS CheckID, - 100 AS Priority, - ''Performance'' AS FindingsGroup, - ''Many Plans for One Query'' AS Finding, - ''https://www.brentozar.com/go/parameterization'' AS URL, - CAST(COUNT(DISTINCT plan_handle) AS NVARCHAR(50)) + '' plans are present for a single query in the plan cache - meaning we probably have parameterization issues.'' AS Details - FROM sys.dm_exec_query_stats qs - CROSS APPLY sys.dm_exec_plan_attributes(qs.plan_handle) pa - WHERE pa.attribute = ''dbid'' - GROUP BY qs.query_hash, pa.value - HAVING COUNT(DISTINCT plan_handle) > '; - - IF 50 > (SELECT COUNT(*) FROM sys.databases) - SET @StringToExecute = @StringToExecute + N' 50 '; - ELSE - SELECT @StringToExecute = @StringToExecute + CAST(COUNT(*) * 2 AS NVARCHAR(50)) FROM sys.databases; - - SET @StringToExecute = @StringToExecute + N' ORDER BY COUNT(DISTINCT plan_handle) DESC OPTION (RECOMPILE);'; - - IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; - IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; - - EXECUTE(@StringToExecute); - END; - - /* Performance - High Number of Cached Plans */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 161 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 161) WITH NOWAIT; - - SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) - SELECT TOP 1 161 AS CheckID, - 100 AS Priority, - ''Performance'' AS FindingsGroup, - ''High Number of Cached Plans'' AS Finding, - ''https://www.brentozar.com/go/planlimits'' AS URL, - ''Your server configuration is limited to '' + CAST(ht.buckets_count * 4 AS VARCHAR(20)) + '' '' + ht.name + '', and you are currently caching '' + CAST(cc.entries_count AS VARCHAR(20)) + ''.'' AS Details - FROM sys.dm_os_memory_cache_hash_tables ht - INNER JOIN sys.dm_os_memory_cache_counters cc ON ht.name = cc.name AND ht.type = cc.type - where ht.name IN ( ''SQL Plans'' , ''Object Plans'' , ''Bound Trees'' ) - AND cc.entries_count >= (3 * ht.buckets_count) OPTION (RECOMPILE)'; - - IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; - IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; - - EXECUTE(@StringToExecute); - END; - - /* Performance - Too Much Free Memory */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 165 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 165) WITH NOWAIT; - - INSERT INTO #BlitzResults - (CheckID, - Priority, - FindingsGroup, - Finding, - URL, - Details) - SELECT 165, 50, 'Performance', 'Too Much Free Memory', 'https://www.brentozar.com/go/freememory', - CAST((CAST(cFree.cntr_value AS BIGINT) / 1024 / 1024 ) AS NVARCHAR(100)) + N'GB of free memory inside SQL Server''s buffer pool, which is ' + CAST((CAST(cTotal.cntr_value AS BIGINT) / 1024 / 1024) AS NVARCHAR(100)) + N'GB. You would think lots of free memory would be good, but check out the URL for more information.' AS Details - FROM sys.dm_os_performance_counters cFree - INNER JOIN sys.dm_os_performance_counters cTotal ON cTotal.object_name LIKE N'%Memory Manager%' - AND cTotal.counter_name = N'Total Server Memory (KB) ' - WHERE cFree.object_name LIKE N'%Memory Manager%' - AND cFree.counter_name = N'Free Memory (KB) ' - AND CAST(cTotal.cntr_value AS BIGINT) > 20480000000 - AND CAST(cTotal.cntr_value AS BIGINT) * .3 <= CAST(cFree.cntr_value AS BIGINT) - AND CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) NOT LIKE '%Standard%'; - - END; - - /* Outdated sp_Blitz - sp_Blitz is Over 6 Months Old */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 155 ) - AND DATEDIFF(MM, @VersionDate, GETDATE()) > 6 - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 155) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 155 AS CheckID , - 0 AS Priority , - 'Outdated sp_Blitz' AS FindingsGroup , - 'sp_Blitz is Over 6 Months Old' AS Finding , - 'http://FirstResponderKit.org/' AS URL , - 'Some things get better with age, like fine wine and your T-SQL. However, sp_Blitz is not one of those things - time to go download the current one.' AS Details; - END; - - /* Populate a list of database defaults. I'm doing this kind of oddly - - it reads like a lot of work, but this way it compiles & runs on all - versions of SQL Server. - */ - - IF @Debug IN (1, 2) RAISERROR('Generating database defaults.', 0, 1) WITH NOWAIT; - - INSERT INTO #DatabaseDefaults - SELECT 'is_supplemental_logging_enabled', 0, 131, 210, 'Supplemental Logging Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL - FROM sys.all_columns - WHERE name = 'is_supplemental_logging_enabled' AND object_id = OBJECT_ID('sys.databases'); - INSERT INTO #DatabaseDefaults - SELECT 'snapshot_isolation_state', 0, 132, 210, 'Snapshot Isolation Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL - FROM sys.all_columns - WHERE name = 'snapshot_isolation_state' AND object_id = OBJECT_ID('sys.databases'); - INSERT INTO #DatabaseDefaults - SELECT 'is_read_committed_snapshot_on', - CASE WHEN SERVERPROPERTY('EngineEdition') = 5 THEN 1 ELSE 0 END, /* RCSI is always enabled in Azure SQL DB per https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/1919 */ - 133, 210, CASE WHEN SERVERPROPERTY('EngineEdition') = 5 THEN 'Read Committed Snapshot Isolation Disabled' ELSE 'Read Committed Snapshot Isolation Enabled' END, 'https://www.brentozar.com/go/dbdefaults', NULL - FROM sys.all_columns - WHERE name = 'is_read_committed_snapshot_on' AND object_id = OBJECT_ID('sys.databases'); - INSERT INTO #DatabaseDefaults - SELECT 'is_auto_create_stats_incremental_on', 0, 134, 210, 'Auto Create Stats Incremental Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL - FROM sys.all_columns - WHERE name = 'is_auto_create_stats_incremental_on' AND object_id = OBJECT_ID('sys.databases'); - INSERT INTO #DatabaseDefaults - SELECT 'is_ansi_null_default_on', 0, 135, 210, 'ANSI NULL Default Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL - FROM sys.all_columns - WHERE name = 'is_ansi_null_default_on' AND object_id = OBJECT_ID('sys.databases'); - INSERT INTO #DatabaseDefaults - SELECT 'is_recursive_triggers_on', 0, 136, 210, 'Recursive Triggers Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL - FROM sys.all_columns - WHERE name = 'is_recursive_triggers_on' AND object_id = OBJECT_ID('sys.databases'); - INSERT INTO #DatabaseDefaults - SELECT 'is_trustworthy_on', 0, 137, 210, 'Trustworthy Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL - FROM sys.all_columns - WHERE name = 'is_trustworthy_on' AND object_id = OBJECT_ID('sys.databases'); - INSERT INTO #DatabaseDefaults - SELECT 'is_broker_enabled', 0, 230, 210, 'Broker Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL - FROM sys.all_columns - WHERE name = 'is_broker_enabled' AND object_id = OBJECT_ID('sys.databases'); - INSERT INTO #DatabaseDefaults - SELECT 'is_honor_broker_priority_on', 0, 231, 210, 'Honor Broker Priority Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL - FROM sys.all_columns - WHERE name = 'is_honor_broker_priority_on' AND object_id = OBJECT_ID('sys.databases'); - INSERT INTO #DatabaseDefaults - SELECT 'is_parameterization_forced', 0, 138, 210, 'Forced Parameterization Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL - FROM sys.all_columns - WHERE name = 'is_parameterization_forced' AND object_id = OBJECT_ID('sys.databases'); - /* Not alerting for this since we actually want it and we have a separate check for it: - INSERT INTO #DatabaseDefaults - SELECT 'is_query_store_on', 0, 139, 210, 'Query Store Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL - FROM sys.all_columns - WHERE name = 'is_query_store_on' AND object_id = OBJECT_ID('sys.databases'); - */ - INSERT INTO #DatabaseDefaults - SELECT 'is_cdc_enabled', 0, 140, 210, 'Change Data Capture Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL - FROM sys.all_columns - WHERE name = 'is_cdc_enabled' AND object_id = OBJECT_ID('sys.databases'); - INSERT INTO #DatabaseDefaults - SELECT 'containment', 0, 141, 210, 'Containment Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL - FROM sys.all_columns - WHERE name = 'containment' AND object_id = OBJECT_ID('sys.databases'); - --INSERT INTO #DatabaseDefaults - -- SELECT 'target_recovery_time_in_seconds', 0, 142, 210, 'Target Recovery Time Changed', 'https://www.brentozar.com/go/dbdefaults', NULL - -- FROM sys.all_columns - -- WHERE name = 'target_recovery_time_in_seconds' AND object_id = OBJECT_ID('sys.databases'); - INSERT INTO #DatabaseDefaults - SELECT 'delayed_durability', 0, 143, 210, 'Delayed Durability Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL - FROM sys.all_columns - WHERE name = 'delayed_durability' AND object_id = OBJECT_ID('sys.databases'); - INSERT INTO #DatabaseDefaults - SELECT 'is_memory_optimized_elevate_to_snapshot_on', 0, 144, 210, 'Memory Optimized Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL - FROM sys.all_columns - WHERE name = 'is_memory_optimized_elevate_to_snapshot_on' AND object_id = OBJECT_ID('sys.databases') - AND SERVERPROPERTY('EngineEdition') <> 8; /* Hekaton is always enabled in Managed Instances per https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/1919 */ - INSERT INTO #DatabaseDefaults - SELECT 'is_accelerated_database_recovery_on', 0, 145, 210, 'Acclerated Database Recovery Enabled', 'https://www.brentozar.com/go/dbdefaults', NULL - FROM sys.all_columns - WHERE name = 'is_accelerated_database_recovery_on' AND object_id = OBJECT_ID('sys.databases') AND SERVERPROPERTY('EngineEdition') NOT IN (5, 8) ; - - DECLARE DatabaseDefaultsLoop CURSOR FOR - SELECT name, DefaultValue, CheckID, Priority, Finding, URL, Details - FROM #DatabaseDefaults; - - OPEN DatabaseDefaultsLoop; - FETCH NEXT FROM DatabaseDefaultsLoop into @CurrentName, @CurrentDefaultValue, @CurrentCheckID, @CurrentPriority, @CurrentFinding, @CurrentURL, @CurrentDetails; - WHILE @@FETCH_STATUS = 0 - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, @CurrentCheckID) WITH NOWAIT; - - /* Target Recovery Time (142) can be either 0 or 60 due to a number of bugs */ - IF @CurrentCheckID = 142 - SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details) - SELECT ' + CAST(@CurrentCheckID AS NVARCHAR(200)) + ', d.[name], ' + CAST(@CurrentPriority AS NVARCHAR(200)) + ', ''Non-Default Database Config'', ''' + @CurrentFinding + ''',''' + @CurrentURL + ''',''' + COALESCE(@CurrentDetails, 'This database setting is not the default.') + ''' - FROM sys.databases d - WHERE d.database_id > 4 AND d.state = 0 AND (d.[' + @CurrentName + '] NOT IN (0, 60) OR d.[' + @CurrentName + '] IS NULL) OPTION (RECOMPILE);'; - ELSE - SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details) - SELECT ' + CAST(@CurrentCheckID AS NVARCHAR(200)) + ', d.[name], ' + CAST(@CurrentPriority AS NVARCHAR(200)) + ', ''Non-Default Database Config'', ''' + @CurrentFinding + ''',''' + @CurrentURL + ''',''' + COALESCE(@CurrentDetails, 'This database setting is not the default.') + ''' - FROM sys.databases d - WHERE d.database_id > 4 AND d.state = 0 AND (d.[' + @CurrentName + '] <> ' + @CurrentDefaultValue + ' OR d.[' + @CurrentName + '] IS NULL) OPTION (RECOMPILE);'; - - IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; - IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; - - EXEC (@StringToExecute); - - FETCH NEXT FROM DatabaseDefaultsLoop into @CurrentName, @CurrentDefaultValue, @CurrentCheckID, @CurrentPriority, @CurrentFinding, @CurrentURL, @CurrentDetails; - END; - - CLOSE DatabaseDefaultsLoop; - DEALLOCATE DatabaseDefaultsLoop; - -/* Check if target recovery interval <> 60 */ -IF - @ProductVersionMajor >= 10 - AND NOT EXISTS - ( - SELECT - 1/0 - FROM #SkipChecks AS sc - WHERE sc.DatabaseName IS NULL - AND sc.CheckID = 257 - ) - BEGIN - IF EXISTS - ( - SELECT - 1/0 - FROM sys.all_columns AS ac - WHERE ac.name = 'target_recovery_time_in_seconds' - AND ac.object_id = OBJECT_ID('sys.databases') - ) - BEGIN - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 257) WITH NOWAIT; - - DECLARE - @tri nvarchar(max) = N' - SELECT - DatabaseName = - d.name, - CheckId = - 257, - Priority = - 50, - FindingsGroup = - N''Performance'', - Finding = - N''Recovery Interval Not Optimal'', - URL = - N''https://sqlperformance.com/2020/05/system-configuration/0-to-60-switching-to-indirect-checkpoints'', - Details = - N''The database '' + - QUOTENAME(d.name) + - N'' has a target recovery interval of '' + - RTRIM(d.target_recovery_time_in_seconds) + - CASE - WHEN d.target_recovery_time_in_seconds = 0 - THEN N'', which is a legacy default, and should be changed to 60.'' - WHEN d.target_recovery_time_in_seconds <> 0 - THEN N'', which is probably a mistake, and should be changed to 60.'' - END - FROM sys.databases AS d - WHERE d.database_id > 4 - AND d.is_read_only = 0 - AND d.is_in_standby = 0 - AND d.target_recovery_time_in_seconds <> 60; - '; - - INSERT INTO - #BlitzResults - ( - DatabaseName, - CheckID, - Priority, - FindingsGroup, - Finding, - URL, - Details - ) - EXEC sys.sp_executesql - @tri; - - END; - END; - - -/*This checks to see if Agent is Offline*/ -IF @ProductVersionMajor >= 10 - AND NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 167 ) - BEGIN - IF EXISTS ( SELECT 1 - FROM sys.all_objects - WHERE name = 'dm_server_services' ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 167) WITH NOWAIT; - - INSERT INTO [#BlitzResults] - ( [CheckID] , - [Priority] , - [FindingsGroup] , - [Finding] , - [URL] , - [Details] ) - - SELECT - 167 AS [CheckID] , - 250 AS [Priority] , - 'Server Info' AS [FindingsGroup] , - 'Agent is Currently Offline' AS [Finding] , - '' AS [URL] , - ( 'Oops! It looks like the ' + [servicename] + ' service is ' + [status_desc] + '. The startup type is ' + [startup_type_desc] + '.' - ) AS [Details] - FROM - [sys].[dm_server_services] - WHERE [status_desc] <> 'Running' - AND [servicename] LIKE 'SQL Server Agent%' - AND CAST(SERVERPROPERTY('Edition') AS VARCHAR(1000)) NOT LIKE '%xpress%'; - - END; - END; - -/*This checks to see if the Full Text thingy is offline*/ -IF @ProductVersionMajor >= 10 - AND NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 168 ) - BEGIN - IF EXISTS ( SELECT 1 - FROM sys.all_objects - WHERE name = 'dm_server_services' ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 168) WITH NOWAIT; - - INSERT INTO [#BlitzResults] - ( [CheckID] , - [Priority] , - [FindingsGroup] , - [Finding] , - [URL] , - [Details] ) - - SELECT - 168 AS [CheckID] , - 250 AS [Priority] , - 'Server Info' AS [FindingsGroup] , - 'Full-text Filter Daemon Launcher is Currently Offline' AS [Finding] , - '' AS [URL] , - ( 'Oops! It looks like the ' + [servicename] + ' service is ' + [status_desc] + '. The startup type is ' + [startup_type_desc] + '.' - ) AS [Details] - FROM - [sys].[dm_server_services] - WHERE [status_desc] <> 'Running' - AND [servicename] LIKE 'SQL Full-text Filter Daemon Launcher%'; - - END; - END; - -/*This checks which service account SQL Server is running as.*/ -IF @ProductVersionMajor >= 10 - AND NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 169 ) - - BEGIN - IF EXISTS ( SELECT 1 - FROM sys.all_objects - WHERE name = 'dm_server_services' ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 169) WITH NOWAIT; - - INSERT INTO [#BlitzResults] - ( [CheckID] , - [Priority] , - [FindingsGroup] , - [Finding] , - [URL] , - [Details] ) - - SELECT - 169 AS [CheckID] , - 250 AS [Priority] , - 'Informational' AS [FindingsGroup] , - 'SQL Server is running under an NT Service account' AS [Finding] , - 'https://www.brentozar.com/go/setup' AS [URL] , - ( 'I''m running as ' + [service_account] + '.' - ) AS [Details] - FROM - [sys].[dm_server_services] - WHERE [service_account] LIKE 'NT Service%' - AND [servicename] LIKE 'SQL Server%' - AND [servicename] NOT LIKE 'SQL Server Agent%' - AND [servicename] NOT LIKE 'SQL Server Launchpad%'; - - END; - END; - -/*This checks which service account SQL Agent is running as.*/ -IF @ProductVersionMajor >= 10 - AND NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 170 ) - - BEGIN - IF EXISTS ( SELECT 1 - FROM sys.all_objects - WHERE name = 'dm_server_services' ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 170) WITH NOWAIT; - - INSERT INTO [#BlitzResults] - ( [CheckID] , - [Priority] , - [FindingsGroup] , - [Finding] , - [URL] , - [Details] ) - - SELECT - 170 AS [CheckID] , - 250 AS [Priority] , - 'Informational' AS [FindingsGroup] , - 'SQL Server Agent is running under an NT Service account' AS [Finding] , - 'https://www.brentozar.com/go/setup' AS [URL] , - ( 'I''m running as ' + [service_account] + '.' - ) AS [Details] - FROM - [sys].[dm_server_services] - WHERE [service_account] LIKE 'NT Service%' - AND [servicename] LIKE 'SQL Server Agent%'; - - END; - END; - -/*This checks that First Responder Kit is consistent. -It assumes that all the objects of the kit resides in the same database, the one in which this SP is stored -It also is ready to check for installation in another schema. -*/ -IF( - NOT EXISTS ( - SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 226 - ) -) -BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running check with id %d',0,1,2000); - - SET @spBlitzFullName = QUOTENAME(DB_NAME()) + '.' +QUOTENAME(OBJECT_SCHEMA_NAME(@@PROCID)) + '.' + QUOTENAME(OBJECT_NAME(@@PROCID)); - SET @BlitzIsOutdatedComparedToOthers = 0; - SET @tsql = NULL; - SET @VersionCheckModeExistsTSQL = NULL; - SET @BlitzProcDbName = DB_NAME(); - SET @ExecRet = NULL; - SET @InnerExecRet = NULL; - SET @TmpCnt = NULL; - - SET @PreviousComponentName = NULL; - SET @PreviousComponentFullPath = NULL; - SET @CurrentStatementId = NULL; - SET @CurrentComponentSchema = NULL; - SET @CurrentComponentName = NULL; - SET @CurrentComponentType = NULL; - SET @CurrentComponentVersionDate = NULL; - SET @CurrentComponentFullName = NULL; - SET @CurrentComponentMandatory = NULL; - SET @MaximumVersionDate = NULL; - - SET @StatementCheckName = NULL; - SET @StatementOutputsCounter = NULL; - SET @OutputCounterExpectedValue = NULL; - SET @StatementOutputsExecRet = NULL; - SET @StatementOutputsDateTime = NULL; - - SET @CurrentComponentMandatoryCheckOK = NULL; - SET @CurrentComponentVersionCheckModeOK = NULL; - - SET @canExitLoop = 0; - SET @frkIsConsistent = 0; - - - SET @tsql = 'USE ' + QUOTENAME(@BlitzProcDbName) + ';' + @crlf + - 'WITH FRKComponents (' + @crlf + - ' ObjectName,' + @crlf + - ' ObjectType,' + @crlf + - ' MandatoryComponent' + @crlf + - ')' + @crlf + - 'AS (' + @crlf + - ' SELECT ''sp_AllNightLog'',''P'' ,0' + @crlf + - ' UNION ALL' + @crlf + - ' SELECT ''sp_AllNightLog_Setup'', ''P'',0' + @crlf + - ' UNION ALL ' + @crlf + - ' SELECT ''sp_Blitz'',''P'',0' + @crlf + - ' UNION ALL ' + @crlf + - ' SELECT ''sp_BlitzBackups'',''P'',0' + @crlf + - ' UNION ALL ' + @crlf + - ' SELECT ''sp_BlitzCache'',''P'',0' + @crlf + - ' UNION ALL ' + @crlf + - ' SELECT ''sp_BlitzFirst'',''P'',0' + @crlf + - ' UNION ALL' + @crlf + - ' SELECT ''sp_BlitzIndex'',''P'',0' + @crlf + - ' UNION ALL ' + @crlf + - ' SELECT ''sp_BlitzLock'',''P'',0' + @crlf + - ' UNION ALL ' + @crlf + - ' SELECT ''sp_BlitzQueryStore'',''P'',0' + @crlf + - ' UNION ALL ' + @crlf + - ' SELECT ''sp_BlitzWho'',''P'',0' + @crlf + - ' UNION ALL ' + @crlf + - ' SELECT ''sp_DatabaseRestore'',''P'',0' + @crlf + - ' UNION ALL ' + @crlf + - ' SELECT ''sp_ineachdb'',''P'',0' + @crlf + - ' UNION ALL' + @crlf + - ' SELECT ''SqlServerVersions'',''U'',0' + @crlf + - ')' + @crlf + - 'INSERT INTO #FRKObjects (' + @crlf + - ' DatabaseName,ObjectSchemaName,ObjectName, ObjectType,MandatoryComponent' + @crlf + - ')' + @crlf + - 'SELECT DB_NAME(),SCHEMA_NAME(o.schema_id), c.ObjectName,c.ObjectType,c.MandatoryComponent' + @crlf + - 'FROM ' + @crlf + - ' FRKComponents c' + @crlf + - 'LEFT JOIN ' + @crlf + - ' sys.objects o' + @crlf + - 'ON c.ObjectName = o.[name]' + @crlf + - 'AND c.ObjectType = o.[type]' + @crlf + - --'WHERE o.schema_id IS NOT NULL' + @crlf + - ';' - ; - - EXEC @ExecRet = sp_executesql @tsql ; - - -- TODO: add check for statement success - - -- TODO: based on SP requirements and presence (SchemaName is not null) ==> update MandatoryComponent column - - -- Filling #StatementsToRun4FRKVersionCheck - INSERT INTO #StatementsToRun4FRKVersionCheck ( - CheckName,StatementText,SubjectName,SubjectFullPath, StatementOutputsCounter,OutputCounterExpectedValue,StatementOutputsExecRet,StatementOutputsDateTime - ) - SELECT - 'Mandatory', - 'SELECT @cnt = COUNT(*) FROM #FRKObjects WHERE ObjectSchemaName IS NULL AND ObjectName = ''' + ObjectName + ''' AND MandatoryComponent = 1;', - ObjectName, - QUOTENAME(DatabaseName) + '.' + QUOTENAME(ObjectSchemaName) + '.' + QUOTENAME(ObjectName), - 1, - 0, - 0, - 0 - FROM #FRKObjects - UNION ALL - SELECT - 'VersionCheckMode', - 'SELECT @cnt = COUNT(*) FROM ' + - QUOTENAME(DatabaseName) + '.sys.all_parameters ' + - 'where object_id = OBJECT_ID(''' + QUOTENAME(DatabaseName) + '.' + QUOTENAME(ObjectSchemaName) + '.' + QUOTENAME(ObjectName) + ''') AND [name] = ''@VersionCheckMode'';', - ObjectName, - QUOTENAME(DatabaseName) + '.' + QUOTENAME(ObjectSchemaName) + '.' + QUOTENAME(ObjectName), - 1, - 1, - 0, - 0 - FROM #FRKObjects - WHERE ObjectType = 'P' - AND ObjectSchemaName IS NOT NULL - UNION ALL - SELECT - 'VersionCheck', - 'EXEC @ExecRet = ' + QUOTENAME(DatabaseName) + '.' + QUOTENAME(ObjectSchemaName) + '.' + QUOTENAME(ObjectName) + ' @VersionCheckMode = 1 , @VersionDate = @ObjDate OUTPUT;', - ObjectName, - QUOTENAME(DatabaseName) + '.' + QUOTENAME(ObjectSchemaName) + '.' + QUOTENAME(ObjectName), - 0, - 0, - 1, - 1 - FROM #FRKObjects - WHERE ObjectType = 'P' - AND ObjectSchemaName IS NOT NULL - ; - IF(@Debug in (1,2)) - BEGIN - SELECT * - FROM #StatementsToRun4FRKVersionCheck ORDER BY SubjectName,SubjectFullPath,StatementId -- in case of schema change ; - END; - - - -- loop on queries... - WHILE(@canExitLoop = 0) - BEGIN - SET @CurrentStatementId = NULL; - - SELECT TOP 1 - @StatementCheckName = CheckName, - @CurrentStatementId = StatementId , - @CurrentComponentName = SubjectName, - @CurrentComponentFullName = SubjectFullPath, - @tsql = StatementText, - @StatementOutputsCounter = StatementOutputsCounter, - @OutputCounterExpectedValue = OutputCounterExpectedValue , - @StatementOutputsExecRet = StatementOutputsExecRet, - @StatementOutputsDateTime = StatementOutputsDateTime - FROM #StatementsToRun4FRKVersionCheck - ORDER BY SubjectName, SubjectFullPath,StatementId /* in case of schema change */ - ; - - -- loop exit condition - IF(@CurrentStatementId IS NULL) - BEGIN - BREAK; - END; - - IF @Debug IN (1, 2) RAISERROR(' Statement: %s',0,1,@tsql); - - -- we start a new component - IF(@PreviousComponentName IS NULL OR - (@PreviousComponentName IS NOT NULL AND @PreviousComponentName <> @CurrentComponentName) OR - (@PreviousComponentName IS NOT NULL AND @PreviousComponentName = @CurrentComponentName AND @PreviousComponentFullPath <> @CurrentComponentFullName) - ) - BEGIN - -- reset variables - SET @CurrentComponentMandatoryCheckOK = 0; - SET @CurrentComponentVersionCheckModeOK = 0; - SET @PreviousComponentName = @CurrentComponentName; - SET @PreviousComponentFullPath = @CurrentComponentFullName ; - END; - - IF(@StatementCheckName NOT IN ('Mandatory','VersionCheckMode','VersionCheck')) - BEGIN - INSERT INTO #BlitzResults( - CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT - 226 AS CheckID , - 253 AS Priority , - 'First Responder Kit' AS FindingsGroup , - 'Version Check Failed (code generator changed)' AS Finding , - 'http://FirstResponderKit.org' AS URL , - 'Download an updated First Responder Kit. Your version check failed because a change has been made to the version check code generator.' + @crlf + - 'Error: No handler for check with name "' + ISNULL(@StatementCheckName,'') + '"' AS Details - ; - - -- we will stop the test because it's possible to get the same message for other components - SET @canExitLoop = 1; - CONTINUE; - END; - - IF(@StatementCheckName = 'Mandatory') - BEGIN - -- outputs counter - EXEC @ExecRet = sp_executesql @tsql, N'@cnt INT OUTPUT',@cnt = @TmpCnt OUTPUT; - - IF(@ExecRet <> 0) - BEGIN - - INSERT INTO #BlitzResults( - CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT - 226 AS CheckID , - 253 AS Priority , - 'First Responder Kit' AS FindingsGroup , - 'Version Check Failed (dynamic query failure)' AS Finding , - 'http://FirstResponderKit.org' AS URL , - 'Download an updated First Responder Kit. Your version check failed due to dynamic query failure.' + @crlf + - 'Error: following query failed at execution (check if component [' + ISNULL(@CurrentComponentName,@CurrentComponentName) + '] is mandatory and missing)' + @crlf + - @tsql AS Details - ; - - -- we will stop the test because it's possible to get the same message for other components - SET @canExitLoop = 1; - CONTINUE; - END; - - IF(@TmpCnt <> @OutputCounterExpectedValue) - BEGIN - INSERT INTO #BlitzResults( - CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT - 227 AS CheckID , - 253 AS Priority , - 'First Responder Kit' AS FindingsGroup , - 'Component Missing: ' + @CurrentComponentName AS Finding , - 'http://FirstResponderKit.org' AS URL , - 'Download an updated version of the First Responder Kit to install it.' AS Details - ; - - -- as it's missing, no value for SubjectFullPath - DELETE FROM #StatementsToRun4FRKVersionCheck WHERE SubjectName = @CurrentComponentName ; - CONTINUE; - END; - - SET @CurrentComponentMandatoryCheckOK = 1; - END; - - IF(@StatementCheckName = 'VersionCheckMode') - BEGIN - IF(@CurrentComponentMandatoryCheckOK = 0) - BEGIN - INSERT INTO #BlitzResults( - CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT - 226 AS CheckID , - 253 AS Priority , - 'First Responder Kit' AS FindingsGroup , - 'Version Check Failed (unexpectedly modified checks ordering)' AS Finding , - 'http://FirstResponderKit.org' AS URL , - 'Download an updated First Responder Kit. Version check failed because "Mandatory" check has not been completed before for current component' + @crlf + - 'Error: version check mode happenned before "Mandatory" check for component called "' + @CurrentComponentFullName + '"' - ; - - -- we will stop the test because it's possible to get the same message for other components - SET @canExitLoop = 1; - CONTINUE; - END; - - -- outputs counter - EXEC @ExecRet = sp_executesql @tsql, N'@cnt INT OUTPUT',@cnt = @TmpCnt OUTPUT; - - IF(@ExecRet <> 0) - BEGIN - INSERT INTO #BlitzResults( - CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT - 226 AS CheckID , - 253 AS Priority , - 'First Responder Kit' AS FindingsGroup , - 'Version Check Failed (dynamic query failure)' AS Finding , - 'http://FirstResponderKit.org' AS URL , - 'Download an updated First Responder Kit. Version check failed because a change has been made to the code generator.' + @crlf + - 'Error: following query failed at execution (check if component [' + @CurrentComponentFullName + '] can run in VersionCheckMode)' + @crlf + - @tsql AS Details - ; - - -- we will stop the test because it's possible to get the same message for other components - SET @canExitLoop = 1; - CONTINUE; - END; - - IF(@TmpCnt <> @OutputCounterExpectedValue) - BEGIN - INSERT INTO #BlitzResults( - CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT - 228 AS CheckID , - 253 AS Priority , - 'First Responder Kit' AS FindingsGroup , - 'Component Outdated: ' + @CurrentComponentFullName AS Finding , - 'http://FirstResponderKit.org' AS URL , - 'Download an updated First Responder Kit. Component ' + @CurrentComponentFullName + ' is not at the minimum version required to run this procedure' + @crlf + - 'VersionCheckMode has been introduced in component version date after "20190320". This means its version is lower than or equal to that date.' AS Details; - ; - - DELETE FROM #StatementsToRun4FRKVersionCheck WHERE SubjectFullPath = @CurrentComponentFullName ; - CONTINUE; - END; - - SET @CurrentComponentVersionCheckModeOK = 1; - END; - - IF(@StatementCheckName = 'VersionCheck') - BEGIN - IF(@CurrentComponentMandatoryCheckOK = 0 OR @CurrentComponentVersionCheckModeOK = 0) - BEGIN - INSERT INTO #BlitzResults( - CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT - 226 AS CheckID , - 253 AS Priority , - 'First Responder Kit' AS FindingsGroup , - 'Version Check Failed (unexpectedly modified checks ordering)' AS Finding , - 'http://FirstResponderKit.org' AS URL , - 'Download an updated First Responder Kit. Version check failed because "VersionCheckMode" check has not been completed before for component called "' + @CurrentComponentFullName + '"' + @crlf + - 'Error: VersionCheck happenned before "VersionCheckMode" check for component called "' + @CurrentComponentFullName + '"' - ; - - -- we will stop the test because it's possible to get the same message for other components - SET @canExitLoop = 1; - CONTINUE; - END; - - EXEC @ExecRet = sp_executesql @tsql , N'@ExecRet INT OUTPUT, @ObjDate DATETIME OUTPUT', @ExecRet = @InnerExecRet OUTPUT, @ObjDate = @CurrentComponentVersionDate OUTPUT; - - IF(@ExecRet <> 0) - BEGIN - INSERT INTO #BlitzResults( - CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT - 226 AS CheckID , - 253 AS Priority , - 'First Responder Kit' AS FindingsGroup , - 'Version Check Failed (dynamic query failure)' AS Finding , - 'http://FirstResponderKit.org' AS URL , - 'Download an updated First Responder Kit. The version check failed because a change has been made to the code generator.' + @crlf + - 'Error: following query failed at execution (check if component [' + @CurrentComponentFullName + '] is at the expected version)' + @crlf + - @tsql AS Details - ; - - -- we will stop the test because it's possible to get the same message for other components - SET @canExitLoop = 1; - CONTINUE; - END; - - - IF(@InnerExecRet <> 0) - BEGIN - INSERT INTO #BlitzResults( - CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT - 226 AS CheckID , - 253 AS Priority , - 'First Responder Kit' AS FindingsGroup , - 'Version Check Failed (Failed dynamic SP call to ' + @CurrentComponentFullName + ')' AS Finding , - 'http://FirstResponderKit.org' AS URL , - 'Download an updated First Responder Kit. Error: following query failed at execution (check if component [' + @CurrentComponentFullName + '] is at the expected version)' + @crlf + - 'Return code: ' + CONVERT(VARCHAR(10),@InnerExecRet) + @crlf + - 'T-SQL Query: ' + @crlf + - @tsql AS Details - ; - - -- advance to next component - DELETE FROM #StatementsToRun4FRKVersionCheck WHERE SubjectFullPath = @CurrentComponentFullName ; - CONTINUE; - END; - - IF(@CurrentComponentVersionDate < @VersionDate) - BEGIN - - INSERT INTO #BlitzResults( - CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT - 228 AS CheckID , - 253 AS Priority , - 'First Responder Kit' AS FindingsGroup , - 'Component Outdated: ' + @CurrentComponentFullName AS Finding , - 'http://FirstResponderKit.org' AS URL , - 'Download and install the latest First Responder Kit - you''re running some older code, and it doesn''t get better with age.' AS Details - ; - - RAISERROR('Component %s is outdated',10,1,@CurrentComponentFullName); - -- advance to next component - DELETE FROM #StatementsToRun4FRKVersionCheck WHERE SubjectFullPath = @CurrentComponentFullName ; - CONTINUE; - END; - - ELSE IF(@CurrentComponentVersionDate > @VersionDate AND @BlitzIsOutdatedComparedToOthers = 0) - BEGIN - SET @BlitzIsOutdatedComparedToOthers = 1; - RAISERROR('Procedure %s is outdated',10,1,@spBlitzFullName); - IF(@MaximumVersionDate IS NULL OR @MaximumVersionDate < @CurrentComponentVersionDate) - BEGIN - SET @MaximumVersionDate = @CurrentComponentVersionDate; - END; - END; - /* Kept for debug purpose: - ELSE - BEGIN - INSERT INTO #BlitzResults( - CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT - 2000 AS CheckID , - 250 AS Priority , - 'Informational' AS FindingsGroup , - 'First Responder kit component ' + @CurrentComponentFullName + ' is at the expected version' AS Finding , - 'https://www.BrentOzar.com/blitz/' AS URL , - 'Version date is: ' + CONVERT(VARCHAR(32),@CurrentComponentVersionDate,121) AS Details - ; - END; - */ - END; - - -- could be performed differently to minimize computation - DELETE FROM #StatementsToRun4FRKVersionCheck WHERE StatementId = @CurrentStatementId ; - END; -END; - - -/*This counts memory dumps and gives min and max date of in view*/ -IF @ProductVersionMajor >= 10 - AND NOT (@ProductVersionMajor = 10.5 AND @ProductVersionMinor < 4297) /* Skip due to crash bug: https://support.microsoft.com/en-us/help/2908087 */ - AND NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 171 ) - BEGIN - IF EXISTS ( SELECT 1 - FROM sys.all_objects - WHERE name = 'dm_server_memory_dumps' ) - BEGIN - IF EXISTS (SELECT * FROM [sys].[dm_server_memory_dumps] WHERE [creation_time] >= DATEADD(YEAR, -1, GETDATE())) - - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 171) WITH NOWAIT; - - INSERT INTO [#BlitzResults] - ( [CheckID] , - [Priority] , - [FindingsGroup] , - [Finding] , - [URL] , - [Details] ) - - SELECT - 171 AS [CheckID] , - 20 AS [Priority] , - 'Reliability' AS [FindingsGroup] , - 'Memory Dumps Have Occurred' AS [Finding] , - 'https://www.brentozar.com/go/dump' AS [URL] , - ( 'That ain''t good. I''ve had ' + - CAST(COUNT(*) AS VARCHAR(100)) + ' memory dumps between ' + - CAST(CAST(MIN([creation_time]) AS DATETIME) AS VARCHAR(100)) + - ' and ' + - CAST(CAST(MAX([creation_time]) AS DATETIME) AS VARCHAR(100)) + - '!' - ) AS [Details] - FROM - [sys].[dm_server_memory_dumps] - WHERE [creation_time] >= DATEADD(year, -1, GETDATE()); - - END; - END; - END; - -/*Checks to see if you're on Developer or Evaluation*/ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 173 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 173) WITH NOWAIT; - - INSERT INTO [#BlitzResults] - ( [CheckID] , - [Priority] , - [FindingsGroup] , - [Finding] , - [URL] , - [Details] ) - - SELECT - 173 AS [CheckID] , - 200 AS [Priority] , - 'Licensing' AS [FindingsGroup] , - 'Non-Production License' AS [Finding] , - 'https://www.brentozar.com/go/licensing' AS [URL] , - ( 'We''re not the licensing police, but if this is supposed to be a production server, and you''re running ' + - CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) + - ' the good folks at Microsoft might get upset with you. Better start counting those cores.' - ) AS [Details] - WHERE CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) LIKE '%Developer%' - OR CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) LIKE '%Evaluation%'; - - END; - -/*Checks to see if Buffer Pool Extensions are in use*/ - IF @ProductVersionMajor >= 12 - AND NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 174 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 174) WITH NOWAIT; - - INSERT INTO [#BlitzResults] - ( [CheckID] , - [Priority] , - [FindingsGroup] , - [Finding] , - [URL] , - [Details] ) - - SELECT - 174 AS [CheckID] , - 200 AS [Priority] , - 'Performance' AS [FindingsGroup] , - 'Buffer Pool Extensions Enabled' AS [Finding] , - 'https://www.brentozar.com/go/bpe' AS [URL] , - ( 'You have Buffer Pool Extensions enabled, and one lives here: ' + - [path] + - '. It''s currently ' + - CASE WHEN [current_size_in_kb] / 1024. / 1024. > 0 - THEN CAST([current_size_in_kb] / 1024. / 1024. AS VARCHAR(100)) - + ' GB' - ELSE CAST([current_size_in_kb] / 1024. AS VARCHAR(100)) - + ' MB' - END + - '. Did you know that BPEs only provide single threaded access 8KB (one page) at a time?' - ) AS [Details] - FROM sys.dm_os_buffer_pool_extension_configuration - WHERE [state_description] <> 'BUFFER POOL EXTENSION DISABLED'; - - END; - -/*Check for too many tempdb files*/ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 175 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 175) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT DISTINCT - 175 AS CheckID , - 'TempDB' AS DatabaseName , - 170 AS Priority , - 'File Configuration' AS FindingsGroup , - 'TempDB Has >16 Data Files' AS Finding , - 'https://www.brentozar.com/go/tempdb' AS URL , - 'Woah, Nelly! TempDB has ' + CAST(COUNT_BIG(*) AS VARCHAR(30)) + '. Did you forget to terminate a loop somewhere?' AS Details - FROM sys.[master_files] AS [mf] - WHERE [mf].[database_id] = 2 AND [mf].[type] = 0 - HAVING COUNT_BIG(*) > 16; - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 176 ) - BEGIN - - IF EXISTS ( SELECT 1 - FROM sys.all_objects - WHERE name = 'dm_xe_sessions' ) - - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 176) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT DISTINCT - 176 AS CheckID , - '' AS DatabaseName , - 200 AS Priority , - 'Monitoring' AS FindingsGroup , - 'Extended Events Hyperextension' AS Finding , - 'https://www.brentozar.com/go/xe' AS URL , - 'Hey big spender, you have ' + CAST(COUNT_BIG(*) AS VARCHAR(30)) + ' Extended Events sessions running. You sure you meant to do that?' AS Details - FROM sys.dm_xe_sessions - WHERE [name] NOT IN - ( 'AlwaysOn_health', - 'system_health', - 'telemetry_xevents', - 'sp_server_diagnostics', - 'sp_server_diagnostics session', - 'hkenginexesession' ) - AND name NOT LIKE '%$A%' - HAVING COUNT_BIG(*) >= 2; - END; - END; - - /*Harmful startup parameter*/ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 177 ) - BEGIN - - IF EXISTS ( SELECT 1 - FROM sys.all_objects - WHERE name = 'dm_server_registry' ) - - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 177) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT DISTINCT - 177 AS CheckID , - '' AS DatabaseName , - 5 AS Priority , - 'Monitoring' AS FindingsGroup , - 'Disabled Internal Monitoring Features' AS Finding , - 'https://msdn.microsoft.com/en-us/library/ms190737.aspx' AS URL , - 'You have -x as a startup parameter. You should head to the URL and read more about what it does to your system.' AS Details - FROM - [sys].[dm_server_registry] AS [dsr] - WHERE - [dsr].[registry_key] LIKE N'%MSSQLServer\Parameters' - AND [dsr].[value_data] = '-x';; - END; - END; - - - /* Reliability - Dangerous Third Party Modules - 179 */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 179 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 179) WITH NOWAIT; - - INSERT INTO [#BlitzResults] - ( [CheckID] , - [Priority] , - [FindingsGroup] , - [Finding] , - [URL] , - [Details] ) - - SELECT - 179 AS [CheckID] , - 5 AS [Priority] , - 'Reliability' AS [FindingsGroup] , - 'Dangerous Third Party Modules' AS [Finding] , - 'https://support.microsoft.com/en-us/kb/2033238' AS [URL] , - ( COALESCE(company, '') + ' - ' + COALESCE(description, '') + ' - ' + COALESCE(name, '') + ' - suspected dangerous third party module is installed.') AS [Details] - FROM sys.dm_os_loaded_modules - WHERE UPPER(name) LIKE UPPER('%\ENTAPI.DLL') OR UPPER(name) LIKE '%MFEBOPK.SYS' /* McAfee VirusScan Enterprise */ - OR UPPER(name) LIKE UPPER('%\HIPI.DLL') OR UPPER(name) LIKE UPPER('%\HcSQL.dll') OR UPPER(name) LIKE UPPER('%\HcApi.dll') OR UPPER(name) LIKE UPPER('%\HcThe.dll') /* McAfee Host Intrusion */ - OR UPPER(name) LIKE UPPER('%\SOPHOS_DETOURED.DLL') OR UPPER(name) LIKE UPPER('%\SOPHOS_DETOURED_x64.DLL') OR UPPER(name) LIKE UPPER('%\SWI_IFSLSP_64.dll') OR UPPER(name) LIKE UPPER('%\SOPHOS~%.dll') /* Sophos AV */ - OR UPPER(name) LIKE UPPER('%\PIOLEDB.DLL') OR UPPER(name) LIKE UPPER('%\PISDK.DLL') /* OSISoft PI data access */ - OR UPPER(name) LIKE UPPER('%ScriptControl%.dll') OR UPPER(name) LIKE UPPER('%umppc%.dll') /* CrowdStrike */ - OR UPPER(name) LIKE UPPER('%perfiCrcPerfMonMgr.DLL') /* Trend Micro OfficeScan */ - OR UPPER(name) LIKE UPPER('%NLEMSQL.SYS') /* NetLib Encryptionizer-Software. */ - OR UPPER(name) LIKE UPPER('%MFETDIK.SYS') /* McAfee Anti-Virus Mini-Firewall */ - OR UPPER(name) LIKE UPPER('%ANTIVIRUS%'); /* To pick up sqlmaggieAntiVirus_64.dll (malware) or anything else labelled AntiVirus */ - /* MS docs link for blacklisted modules: https://learn.microsoft.com/en-us/troubleshoot/sql/performance/performance-consistency-issues-filter-drivers-modules */ - END; - - /*Find shrink database tasks*/ - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 180 ) - AND CONVERT(VARCHAR(128), SERVERPROPERTY ('productversion')) LIKE '1%' /* Only run on 2008+ */ - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 180) WITH NOWAIT; - - WITH XMLNAMESPACES ('www.microsoft.com/SqlServer/Dts' AS [dts]) - ,[maintenance_plan_steps] AS ( - SELECT [name] - , [id] -- ID required to link maintenace plan with jobs and jobhistory (sp_Blitz Issue #776) - , CAST(CAST([packagedata] AS VARBINARY(MAX)) AS XML) AS [maintenance_plan_xml] - FROM [msdb].[dbo].[sysssispackages] - WHERE [packagetype] = 6 - ) - INSERT INTO [#BlitzResults] - ( [CheckID] , - [Priority] , - [FindingsGroup] , - [Finding] , - [URL] , - [Details] ) - SELECT - 180 AS [CheckID] , - -- sp_Blitz Issue #776 - -- Job has history and was executed in the last 30 days - CASE WHEN (cast(datediff(dd, substring(cast(sjh.run_date as nvarchar(10)), 1, 4) + '-' + substring(cast(sjh.run_date as nvarchar(10)), 5, 2) + '-' + substring(cast(sjh.run_date as nvarchar(10)), 7, 2), GETDATE()) AS INT) < 30) OR (j.[enabled] = 1 AND ssc.[enabled] = 1 )THEN - 100 - ELSE -- no job history (implicit) AND job not run in the past 30 days AND (Job disabled OR Job Schedule disabled) - 200 - END AS Priority, - 'Performance' AS [FindingsGroup] , - 'Shrink Database Step In Maintenance Plan' AS [Finding] , - 'https://www.brentozar.com/go/autoshrink' AS [URL] , - 'The maintenance plan ' + [mps].[name] + ' has a step to shrink databases in it. Shrinking databases is as outdated as maintenance plans.' - + CASE WHEN COALESCE(ssc.name,'0') != '0' THEN + ' (Schedule: [' + ssc.name + '])' ELSE + '' END AS [Details] - FROM [maintenance_plan_steps] [mps] - CROSS APPLY [maintenance_plan_xml].[nodes]('//dts:Executables/dts:Executable') [t]([c]) - join msdb.dbo.sysmaintplan_subplans as sms - on mps.id = sms.plan_id - JOIN msdb.dbo.sysjobs j - on sms.job_id = j.job_id - LEFT OUTER JOIN msdb.dbo.sysjobsteps AS step - ON j.job_id = step.job_id - LEFT OUTER JOIN msdb.dbo.sysjobschedules AS sjsc - ON j.job_id = sjsc.job_id - LEFT OUTER JOIN msdb.dbo.sysschedules AS ssc - ON sjsc.schedule_id = ssc.schedule_id - AND sjsc.job_id = j.job_id - LEFT OUTER JOIN msdb.dbo.sysjobhistory AS sjh - ON j.job_id = sjh.job_id - AND step.step_id = sjh.step_id - AND sjh.run_date IN (SELECT max(sjh2.run_date) FROM msdb.dbo.sysjobhistory AS sjh2 WHERE sjh2.job_id = j.job_id) -- get the latest entry date - AND sjh.run_time IN (SELECT max(sjh3.run_time) FROM msdb.dbo.sysjobhistory AS sjh3 WHERE sjh3.job_id = j.job_id AND sjh3.run_date = sjh.run_date) -- get the latest entry time - WHERE [c].[value]('(@dts:ObjectName)', 'VARCHAR(128)') = 'Shrink Database Task'; - - END; - - /*Find repetitive maintenance tasks*/ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 181 ) - AND CONVERT(VARCHAR(128), SERVERPROPERTY ('productversion')) LIKE '1%' /* Only run on 2008+ */ - BEGIN - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 181) WITH NOWAIT; - - WITH XMLNAMESPACES ('www.microsoft.com/SqlServer/Dts' AS [dts]) - ,[maintenance_plan_steps] AS ( - SELECT [name] - , CAST(CAST([packagedata] AS VARBINARY(MAX)) AS XML) AS [maintenance_plan_xml] - FROM [msdb].[dbo].[sysssispackages] - WHERE [packagetype] = 6 - ), [maintenance_plan_table] AS ( - SELECT [mps].[name] - ,[c].[value]('(@dts:ObjectName)', 'NVARCHAR(128)') AS [step_name] - FROM [maintenance_plan_steps] [mps] - CROSS APPLY [maintenance_plan_xml].[nodes]('//dts:Executables/dts:Executable') [t]([c]) - ), [mp_steps_pretty] AS (SELECT DISTINCT [m1].[name] , - STUFF((SELECT N', ' + [m2].[step_name] FROM [maintenance_plan_table] AS [m2] WHERE [m1].[name] = [m2].[name] - FOR XML PATH(N'')), 1, 2, N'') AS [maintenance_plan_steps] - FROM [maintenance_plan_table] AS [m1]) - - INSERT INTO [#BlitzResults] - ( [CheckID] , - [Priority] , - [FindingsGroup] , - [Finding] , - [URL] , - [Details] ) - - SELECT - 181 AS [CheckID] , - 100 AS [Priority] , - 'Performance' AS [FindingsGroup] , - 'Repetitive Steps In Maintenance Plans' AS [Finding] , - 'https://ola.hallengren.com/' AS [URL] , - 'The maintenance plan ' + [m].[name] + ' is doing repetitive work on indexes and statistics. Perhaps it''s time to try something more modern?' AS [Details] - FROM [mp_steps_pretty] m - WHERE m.[maintenance_plan_steps] LIKE '%Rebuild%Reorganize%' - OR m.[maintenance_plan_steps] LIKE '%Rebuild%Update%'; - - END; - - - /* Reliability - No Failover Cluster Nodes Available - 184 */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 184 ) - AND CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)) NOT LIKE '10%' - AND CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)) NOT LIKE '9%' - BEGIN - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 184) WITH NOWAIT; - - SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) - SELECT TOP 1 - 184 AS CheckID , - 20 AS Priority , - ''Reliability'' AS FindingsGroup , - ''No Failover Cluster Nodes Available'' AS Finding , - ''https://www.brentozar.com/go/node'' AS URL , - ''There are no failover cluster nodes available if the active node fails'' AS Details - FROM ( - SELECT SUM(CASE WHEN [status] = 0 AND [is_current_owner] = 0 THEN 1 ELSE 0 END) AS [available_nodes] - FROM sys.dm_os_cluster_nodes - ) a - WHERE [available_nodes] < 1 OPTION (RECOMPILE)'; - - IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; - IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; - - EXECUTE(@StringToExecute); - END; - - /* Reliability - TempDB File Error */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 191 ) - AND (SELECT COUNT(*) FROM sys.master_files WHERE database_id = 2) <> (SELECT COUNT(*) FROM tempdb.sys.database_files) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 191) WITH NOWAIT - - INSERT INTO [#BlitzResults] - ( [CheckID] , - [Priority] , - [FindingsGroup] , - [Finding] , - [URL] , - [Details] ) - - SELECT - 191 AS [CheckID] , - 50 AS [Priority] , - 'Reliability' AS [FindingsGroup] , - 'TempDB File Error' AS [Finding] , - 'https://www.brentozar.com/go/tempdboops' AS [URL] , - 'Mismatch between the number of TempDB files in sys.master_files versus tempdb.sys.database_files' AS [Details]; - END; - -/*Perf - Odd number of cores in a socket*/ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL - AND CheckID = 198 ) - AND EXISTS ( SELECT 1 - FROM sys.dm_os_schedulers - WHERE is_online = 1 - AND scheduler_id < 255 - AND parent_node_id < 64 - GROUP BY parent_node_id, - is_online - HAVING ( COUNT(cpu_id) + 2 ) % 2 = 1 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 198) WITH NOWAIT - - INSERT INTO #BlitzResults - ( - CheckID, - DatabaseName, - Priority, - FindingsGroup, - Finding, - URL, - Details - ) - SELECT 198 AS CheckID, - NULL AS DatabaseName, - 10 AS Priority, - 'Performance' AS FindingsGroup, - 'CPU w/Odd Number of Cores' AS Finding, - 'https://www.brentozar.com/go/oddity' AS URL, - 'Node ' + CONVERT(VARCHAR(10), parent_node_id) + ' has ' + CONVERT(VARCHAR(10), COUNT(cpu_id)) - + CASE WHEN COUNT(cpu_id) = 1 THEN ' core assigned to it. This is a really bad NUMA configuration.' - ELSE ' cores assigned to it. This is a really bad NUMA configuration.' - END AS Details - FROM sys.dm_os_schedulers - WHERE is_online = 1 - AND scheduler_id < 255 - AND parent_node_id < 64 - AND EXISTS ( - SELECT 1 - FROM ( SELECT memory_node_id, SUM(online_scheduler_count) AS schedulers - FROM sys.dm_os_nodes - WHERE memory_node_id < 64 - GROUP BY memory_node_id ) AS nodes - HAVING MIN(nodes.schedulers) <> MAX(nodes.schedulers) - ) - GROUP BY parent_node_id, - is_online - HAVING ( COUNT(cpu_id) + 2 ) % 2 = 1; - - END; - -/*Begin: checking default trace for odd DBCC activity*/ - - --Grab relevant event data - IF @TraceFileIssue = 0 - BEGIN - SELECT UPPER( - REPLACE( - SUBSTRING(CONVERT(NVARCHAR(MAX), t.TextData), 0, - ISNULL( - NULLIF( - CHARINDEX('(', CONVERT(NVARCHAR(MAX), t.TextData)), - 0), - LEN(CONVERT(NVARCHAR(MAX), t.TextData)) + 1 )) --This replaces everything up to an open paren, if one exists. - , SUBSTRING(CONVERT(NVARCHAR(MAX), t.TextData), - ISNULL( - NULLIF( - CHARINDEX(' WITH ',CONVERT(NVARCHAR(MAX), t.TextData)) - , 0), - LEN(CONVERT(NVARCHAR(MAX), t.TextData)) + 1), - LEN(CONVERT(NVARCHAR(MAX), t.TextData)) + 1 ) - , '') --This replaces any optional WITH clause to a DBCC command, like tableresults. - ) AS [dbcc_event_trunc_upper], - UPPER( - REPLACE( - CONVERT(NVARCHAR(MAX), t.TextData), SUBSTRING(CONVERT(NVARCHAR(MAX), t.TextData), - ISNULL( - NULLIF( - CHARINDEX(' WITH ',CONVERT(NVARCHAR(MAX), t.TextData)) - , 0), - LEN(CONVERT(NVARCHAR(MAX), t.TextData)) + 1), - LEN(CONVERT(NVARCHAR(MAX), t.TextData)) + 1 ), '')) AS [dbcc_event_full_upper], - MIN(t.StartTime) OVER (PARTITION BY CONVERT(NVARCHAR(128), t.TextData)) AS min_start_time, - MAX(t.StartTime) OVER (PARTITION BY CONVERT(NVARCHAR(128), t.TextData)) AS max_start_time, - t.NTUserName AS [nt_user_name], - t.NTDomainName AS [nt_domain_name], - t.HostName AS [host_name], - t.ApplicationName AS [application_name], - t.LoginName [login_name], - t.DBUserName AS [db_user_name] - INTO #dbcc_events_from_trace - FROM #fnTraceGettable AS t - WHERE t.EventClass = 116 - OPTION(RECOMPILE) - END; - - /*Overall count of DBCC events excluding silly stuff*/ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 203 ) - AND @TraceFileIssue = 0 - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 203) WITH NOWAIT - - INSERT INTO [#BlitzResults] - ( [CheckID] , - [Priority] , - [FindingsGroup] , - [Finding] , - [URL] , - [Details] ) - SELECT 203 AS CheckID , - 50 AS Priority , - 'DBCC Events' AS FindingsGroup , - 'Overall Events' AS Finding , - 'https://www.BrentOzar.com/go/dbcc' AS URL , - CAST(COUNT(*) AS NVARCHAR(100)) + ' DBCC events have taken place between ' + CONVERT(NVARCHAR(30), MIN(d.min_start_time)) + ' and ' + CONVERT(NVARCHAR(30), MAX(d.max_start_time)) + - '. This does not include CHECKDB and other usually benign DBCC events.' - AS Details - FROM #dbcc_events_from_trace d - /* This WHERE clause below looks horrible, but it's because users can run stuff like - DBCC LOGINFO - with lots of spaces (or carriage returns, or comments) in between the DBCC and the - command they're trying to run. See Github issues 1062, 1074, 1075. - */ - WHERE d.dbcc_event_full_upper NOT LIKE '%DBCC%ADDINSTANCE%' - AND d.dbcc_event_full_upper NOT LIKE '%DBCC%AUTOPILOT%' - AND d.dbcc_event_full_upper NOT LIKE '%DBCC%CHECKALLOC%' - AND d.dbcc_event_full_upper NOT LIKE '%DBCC%CHECKCATALOG%' - AND d.dbcc_event_full_upper NOT LIKE '%DBCC%CHECKCONSTRAINTS%' - AND d.dbcc_event_full_upper NOT LIKE '%DBCC%CHECKDB%' - AND d.dbcc_event_full_upper NOT LIKE '%DBCC%CHECKFILEGROUP%' - AND d.dbcc_event_full_upper NOT LIKE '%DBCC%CHECKIDENT%' - AND d.dbcc_event_full_upper NOT LIKE '%DBCC%CHECKPRIMARYFILE%' - AND d.dbcc_event_full_upper NOT LIKE '%DBCC%CHECKTABLE%' - AND d.dbcc_event_full_upper NOT LIKE '%DBCC%CLEANTABLE%' - AND d.dbcc_event_full_upper NOT LIKE '%DBCC%DBINFO%' - AND d.dbcc_event_full_upper NOT LIKE '%DBCC%ERRORLOG%' - AND d.dbcc_event_full_upper NOT LIKE '%DBCC%INCREMENTINSTANCE%' - AND d.dbcc_event_full_upper NOT LIKE '%DBCC%INPUTBUFFER%' - AND d.dbcc_event_full_upper NOT LIKE '%DBCC%LOGINFO%' - AND d.dbcc_event_full_upper NOT LIKE '%DBCC%OPENTRAN%' - AND d.dbcc_event_full_upper NOT LIKE '%DBCC%SETINSTANCE%' - AND d.dbcc_event_full_upper NOT LIKE '%DBCC%SHOWFILESTATS%' - AND d.dbcc_event_full_upper NOT LIKE '%DBCC%SHOW_STATISTICS%' - AND d.dbcc_event_full_upper NOT LIKE '%DBCC%SQLPERF%NETSTATS%' - AND d.dbcc_event_full_upper NOT LIKE '%DBCC%SQLPERF%LOGSPACE%' - AND d.dbcc_event_full_upper NOT LIKE '%DBCC%TRACEON%' - AND d.dbcc_event_full_upper NOT LIKE '%DBCC%TRACEOFF%' - AND d.dbcc_event_full_upper NOT LIKE '%DBCC%TRACESTATUS%' - AND d.dbcc_event_full_upper NOT LIKE '%DBCC%USEROPTIONS%' - AND d.application_name NOT LIKE 'Critical Care(R) Collector' - AND d.application_name NOT LIKE '%Red Gate Software Ltd SQL Prompt%' - AND d.application_name NOT LIKE '%Spotlight Diagnostic Server%' - AND d.application_name NOT LIKE '%SQL Diagnostic Manager%' - AND d.application_name NOT LIKE 'SQL Server Checkup%' - AND d.application_name NOT LIKE '%Sentry%' - AND d.application_name NOT LIKE '%LiteSpeed%' - AND d.application_name NOT LIKE '%SQL Monitor - Monitoring%' - - - HAVING COUNT(*) > 0; - - END; - - /*Check for someone running drop clean buffers*/ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 207 ) - AND @TraceFileIssue = 0 - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 207) WITH NOWAIT - - INSERT INTO [#BlitzResults] - ( [CheckID] , - [Priority] , - [FindingsGroup] , - [Finding] , - [URL] , - [Details] ) - SELECT 207 AS CheckID , - 10 AS Priority , - 'Performance' AS FindingsGroup , - 'DBCC DROPCLEANBUFFERS Ran Recently' AS Finding , - 'https://www.BrentOzar.com/go/dbcc' AS URL , - 'The user ' + COALESCE(d.nt_user_name, d.login_name) + ' has run DBCC DROPCLEANBUFFERS ' + CAST(COUNT(*) AS NVARCHAR(100)) + ' times between ' + CONVERT(NVARCHAR(30), MIN(d.min_start_time)) + ' and ' + CONVERT(NVARCHAR(30), MAX(d.max_start_time)) + - '. If this is a production box, know that you''re clearing all data out of memory when this happens. What kind of monster would do that?' - AS Details - FROM #dbcc_events_from_trace d - WHERE d.dbcc_event_full_upper = N'DBCC DROPCLEANBUFFERS' - GROUP BY COALESCE(d.nt_user_name, d.login_name) - HAVING COUNT(*) > 0; - - END; - - /*Check for someone running free proc cache*/ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 208 ) - AND @TraceFileIssue = 0 - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 208) WITH NOWAIT - - INSERT INTO [#BlitzResults] - ( [CheckID] , - [Priority] , - [FindingsGroup] , - [Finding] , - [URL] , - [Details] ) - SELECT 208 AS CheckID , - 10 AS Priority , - 'DBCC Events' AS FindingsGroup , - 'DBCC FREEPROCCACHE Ran Recently' AS Finding , - 'https://www.BrentOzar.com/go/dbcc' AS URL , - 'The user ' + COALESCE(d.nt_user_name, d.login_name) + ' has run DBCC FREEPROCCACHE ' + CAST(COUNT(*) AS NVARCHAR(100)) + ' times between ' + CONVERT(NVARCHAR(30), MIN(d.min_start_time)) + ' and ' + CONVERT(NVARCHAR(30), MAX(d.max_start_time)) + - '. This has bad idea jeans written all over its butt, like most other bad idea jeans.' - AS Details - FROM #dbcc_events_from_trace d - WHERE d.dbcc_event_full_upper = N'DBCC FREEPROCCACHE' - GROUP BY COALESCE(d.nt_user_name, d.login_name) - HAVING COUNT(*) > 0; - - END; - - /*Check for someone clearing wait stats*/ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 205 ) - AND @TraceFileIssue = 0 - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 205) WITH NOWAIT - - INSERT INTO [#BlitzResults] - ( [CheckID] , - [Priority] , - [FindingsGroup] , - [Finding] , - [URL] , - [Details] ) - SELECT 205 AS CheckID , - 50 AS Priority , - 'Performance' AS FindingsGroup , - 'Wait Stats Cleared Recently' AS Finding , - 'https://www.BrentOzar.com/go/dbcc' AS URL , - 'The user ' + COALESCE(d.nt_user_name, d.login_name) + ' has run DBCC SQLPERF(''SYS.DM_OS_WAIT_STATS'',CLEAR) ' + CAST(COUNT(*) AS NVARCHAR(100)) + ' times between ' + CONVERT(NVARCHAR(30), MIN(d.min_start_time)) + ' and ' + CONVERT(NVARCHAR(30), MAX(d.max_start_time)) + - '. Why are you clearing wait stats? What are you hiding?' - AS Details - FROM #dbcc_events_from_trace d - WHERE d.dbcc_event_full_upper = N'DBCC SQLPERF(''SYS.DM_OS_WAIT_STATS'',CLEAR)' - GROUP BY COALESCE(d.nt_user_name, d.login_name) - HAVING COUNT(*) > 0; - - END; - - /*Check for someone writing to pages. Yeah, right?*/ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 209 ) - AND @TraceFileIssue = 0 - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 209) WITH NOWAIT - - INSERT INTO [#BlitzResults] - ( [CheckID] , - [Priority] , - [FindingsGroup] , - [Finding] , - [URL] , - [Details] ) - SELECT 209 AS CheckID , - 10 AS Priority , - 'Reliability' AS FindingsGroup , - 'DBCC WRITEPAGE Used Recently' AS Finding , - 'https://www.BrentOzar.com/go/dbcc' AS URL , - 'The user ' + COALESCE(d.nt_user_name, d.login_name) + ' has run DBCC WRITEPAGE ' + CAST(COUNT(*) AS NVARCHAR(100)) + ' times between ' + CONVERT(NVARCHAR(30), MIN(d.min_start_time)) + ' and ' + CONVERT(NVARCHAR(30), MAX(d.max_start_time)) + - '. So, uh, are they trying to fix corruption, or cause corruption?' - AS Details - FROM #dbcc_events_from_trace d - WHERE d.dbcc_event_trunc_upper = N'DBCC WRITEPAGE' - GROUP BY COALESCE(d.nt_user_name, d.login_name) - HAVING COUNT(*) > 0; - - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 210 ) - AND @TraceFileIssue = 0 - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 210) WITH NOWAIT - - INSERT INTO [#BlitzResults] - ( [CheckID] , - [Priority] , - [FindingsGroup] , - [Finding] , - [URL] , - [Details] ) - - SELECT 210 AS CheckID , - 10 AS Priority , - 'Performance' AS FindingsGroup , - 'DBCC SHRINK% Ran Recently' AS Finding , - 'https://www.BrentOzar.com/go/dbcc' AS URL , - 'The user ' + COALESCE(d.nt_user_name, d.login_name) + ' has run file shrinks ' + CAST(COUNT(*) AS NVARCHAR(100)) + ' times between ' + CONVERT(NVARCHAR(30), MIN(d.min_start_time)) + ' and ' + CONVERT(NVARCHAR(30), MAX(d.max_start_time)) + - '. So, uh, are they trying to cause bad performance on purpose?' - AS Details - FROM #dbcc_events_from_trace d - WHERE d.dbcc_event_trunc_upper LIKE N'DBCC SHRINK%' - GROUP BY COALESCE(d.nt_user_name, d.login_name) - HAVING COUNT(*) > 0; - - END; - -/*End: checking default trace for odd DBCC activity*/ - - /*Begin check for autoshrink events*/ - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 206 ) - AND @TraceFileIssue = 0 - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 206) WITH NOWAIT - - INSERT INTO [#BlitzResults] - ( [CheckID] , - [Priority] , - [FindingsGroup] , - [Finding] , - [URL] , - [Details] ) - - SELECT 206 AS CheckID , - 10 AS Priority , - 'Performance' AS FindingsGroup , - 'Auto-Shrink Ran Recently' AS Finding , - '' AS URL , - N'The database ' + QUOTENAME(t.DatabaseName) + N' has had ' - + CONVERT(NVARCHAR(10), COUNT(*)) - + N' auto shrink events between ' - + CONVERT(NVARCHAR(30), MIN(t.StartTime)) + ' and ' + CONVERT(NVARCHAR(30), MAX(t.StartTime)) - + ' that lasted on average ' - + CONVERT(NVARCHAR(10), AVG(DATEDIFF(SECOND, t.StartTime, t.EndTime))) - + ' seconds.' AS Details - FROM #fnTraceGettable AS t - WHERE t.EventClass IN (94, 95) - GROUP BY t.DatabaseName - HAVING AVG(DATEDIFF(SECOND, t.StartTime, t.EndTime)) > 5; - - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 215 ) - AND @TraceFileIssue = 0 - AND EXISTS (SELECT * FROM sys.all_columns WHERE name = 'database_id' AND object_id = OBJECT_ID('sys.dm_exec_sessions')) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 215) WITH NOWAIT - - SET @StringToExecute = 'INSERT INTO [#BlitzResults] - ( [CheckID] , - [Priority] , - [FindingsGroup] , - [Finding] , - [DatabaseName] , - [URL] , - [Details] ) - - SELECT 215 AS CheckID , - 100 AS Priority , - ''Performance'' AS FindingsGroup , - ''Implicit Transactions'' AS Finding , - DB_NAME(s.database_id) AS DatabaseName, - ''https://www.brentozar.com/go/ImplicitTransactions/'' AS URL , - N''The database '' + - DB_NAME(s.database_id) - + '' has '' - + CONVERT(NVARCHAR(20), COUNT_BIG(*)) - + '' open implicit transactions with an oldest begin time of '' - + CONVERT(NVARCHAR(30), MIN(tat.transaction_begin_time)) - + '' Run sp_BlitzWho and check the is_implicit_transaction column to see the culprits.'' AS details - FROM sys.dm_tran_active_transactions AS tat - LEFT JOIN sys.dm_tran_session_transactions AS tst - ON tst.transaction_id = tat.transaction_id - LEFT JOIN sys.dm_exec_sessions AS s - ON s.session_id = tst.session_id - WHERE tat.name = ''implicit_transaction'' - GROUP BY DB_NAME(s.database_id), transaction_type, transaction_state;'; - - - IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; - IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; - - EXECUTE(@StringToExecute); - - - - END; - - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 221 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 221) WITH NOWAIT; - - WITH reboot_airhorn - AS - ( - SELECT create_date - FROM sys.databases - WHERE database_id = 2 - UNION ALL - SELECT CAST(DATEADD(SECOND, ( ms_ticks / 1000 ) * ( -1 ), GETDATE()) AS DATETIME) - FROM sys.dm_os_sys_info - ) - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 221 AS CheckID, - 10 AS Priority, - 'Reliability' AS FindingsGroup, - 'Server restarted in last 24 hours' AS Finding, - '' AS URL, - 'Surprise! Your server was last restarted on: ' + CONVERT(VARCHAR(30), MAX(reboot_airhorn.create_date)) AS details - FROM reboot_airhorn - HAVING MAX(reboot_airhorn.create_date) >= DATEADD(HOUR, -24, GETDATE()); - - - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 229 ) - AND CAST(SERVERPROPERTY('Edition') AS NVARCHAR(4000)) LIKE '%Evaluation%' - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 229) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 229 AS CheckID, - 1 AS Priority, - 'Reliability' AS FindingsGroup, - 'Evaluation Edition' AS Finding, - 'https://www.BrentOzar.com/go/workgroup' AS URL, - 'This server will stop working on: ' + CAST(CONVERT(DATETIME, DATEADD(DD, 180, create_date), 102) AS VARCHAR(100)) AS details - FROM sys.server_principals - WHERE sid = 0x010100000000000512000000; - - END; - - - - - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 233 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 233) WITH NOWAIT; - - - IF EXISTS (SELECT * FROM sys.all_columns WHERE object_id = OBJECT_ID('sys.dm_os_memory_clerks') AND name = 'pages_kb') - BEGIN - /* SQL 2012+ version */ - SET @StringToExecute = N' - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 233 AS CheckID, - 50 AS Priority, - ''Performance'' AS FindingsGroup, - ''Memory Leak in USERSTORE_TOKENPERM Cache'' AS Finding, - ''https://www.BrentOzar.com/go/userstore'' AS URL, - N''UserStore_TokenPerm clerk is using '' + CAST(CAST(SUM(CASE WHEN type = ''USERSTORE_TOKENPERM'' AND name = ''TokenAndPermUserStore'' THEN pages_kb * 1.0 ELSE 0.0 END) / 1024.0 / 1024.0 AS INT) AS NVARCHAR(100)) - + N''GB RAM, total buffer pool is '' + CAST(CAST(SUM(pages_kb) / 1024.0 / 1024.0 AS INT) AS NVARCHAR(100)) + N''GB.'' - AS details - FROM sys.dm_os_memory_clerks - HAVING SUM(CASE WHEN type = ''USERSTORE_TOKENPERM'' AND name = ''TokenAndPermUserStore'' THEN pages_kb * 1.0 ELSE 0.0 END) / SUM(pages_kb) >= 0.1 - AND SUM(pages_kb) / 1024.0 / 1024.0 >= 1; /* At least 1GB RAM overall */'; - EXEC sp_executesql @StringToExecute; - END - ELSE - BEGIN - /* Antiques Roadshow SQL 2008R2 - version */ - SET @StringToExecute = N' - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 233 AS CheckID, - 50 AS Priority, - ''Performance'' AS FindingsGroup, - ''Memory Leak in USERSTORE_TOKENPERM Cache'' AS Finding, - ''https://www.BrentOzar.com/go/userstore'' AS URL, - N''UserStore_TokenPerm clerk is using '' + CAST(CAST(SUM(CASE WHEN type = ''USERSTORE_TOKENPERM'' AND name = ''TokenAndPermUserStore'' THEN single_pages_kb + multi_pages_kb * 1.0 ELSE 0.0 END) / 1024.0 / 1024.0 AS INT) AS NVARCHAR(100)) - + N''GB RAM, total buffer pool is '' + CAST(CAST(SUM(single_pages_kb + multi_pages_kb) / 1024.0 / 1024.0 AS INT) AS NVARCHAR(100)) + N''GB.'' - AS details - FROM sys.dm_os_memory_clerks - HAVING SUM(CASE WHEN type = ''USERSTORE_TOKENPERM'' AND name = ''TokenAndPermUserStore'' THEN single_pages_kb + multi_pages_kb * 1.0 ELSE 0.0 END) / SUM(single_pages_kb + multi_pages_kb) >= 0.1 - AND SUM(single_pages_kb + multi_pages_kb) / 1024.0 / 1024.0 >= 1; /* At least 1GB RAM overall */'; - EXEC sp_executesql @StringToExecute; - END - - END; - - - - - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 234 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 234) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - DatabaseName , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 234 AS CheckID, - 100 AS Priority, - db_name(f.database_id) AS DatabaseName, - 'Reliability' AS FindingsGroup, - 'SQL Server Update May Fail' AS Finding, - 'https://desertdba.com/failovers-cant-serve-two-masters/' AS URL, - 'This database has a file with a logical name of ''master'', which can break SQL Server updates. Rename it in SSMS by right-clicking on the database, go into Properties, and rename the file. Takes effect instantly.' AS details - FROM master.sys.master_files f - WHERE (f.name = N'master') - AND f.database_id > 4 - AND db_name(f.database_id) <> 'master'; /* Thanks Michaels3 for catching this */ - END; - - - - - IF @CheckUserDatabaseObjects = 1 - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Starting @CheckUserDatabaseObjects section.', 0, 1) WITH NOWAIT - - /* - But what if you need to run a query in every individual database? - Check out CheckID 99 below. Yes, it uses sp_MSforeachdb, and no, - we're not happy about that. sp_MSforeachdb is known to have a lot - of issues, like skipping databases sometimes. However, this is the - only built-in option that we have. If you're writing your own code - for database maintenance, consider Aaron Bertrand's alternative: - http://www.mssqltips.com/sqlservertip/2201/making-a-more-reliable-and-flexible-spmsforeachdb/ - We don't include that as part of sp_Blitz, of course, because - copying and distributing copyrighted code from others without their - written permission isn't a good idea. - */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 99 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 99) WITH NOWAIT; - - EXEC dbo.sp_MSforeachdb 'USE [?]; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; IF EXISTS (SELECT * FROM sys.tables WITH (NOLOCK) WHERE name = ''sysmergepublications'' ) IF EXISTS ( SELECT * FROM sysmergepublications WITH (NOLOCK) WHERE retention = 0) INSERT INTO #BlitzResults (CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details) SELECT DISTINCT 99, DB_NAME(), 110, ''Performance'', ''Infinite merge replication metadata retention period'', ''https://www.brentozar.com/go/merge'', (''The ['' + DB_NAME() + ''] database has merge replication metadata retention period set to infinite - this can be the case of significant performance issues.'')'; - END; - /* - Note that by using sp_MSforeachdb, we're running the query in all - databases. We're not checking #SkipChecks here for each database to - see if we should run the check in this database. That means we may - still run a skipped check if it involves sp_MSforeachdb. We just - don't output those results in the last step. - */ - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 163 ) - AND EXISTS(SELECT * FROM sys.all_objects WHERE name = 'database_query_store_options') - BEGIN - /* --TOURSTOP03-- */ - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 163) WITH NOWAIT; - - EXEC dbo.sp_MSforeachdb 'USE [?]; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - INSERT INTO #BlitzResults - (CheckID, - DatabaseName, - Priority, - FindingsGroup, - Finding, - URL, - Details) - SELECT TOP 1 163, - N''?'', - 200, - ''Performance'', - ''Query Store Disabled'', - ''https://www.brentozar.com/go/querystore'', - (''The new SQL Server 2016 Query Store feature has not been enabled on this database.'') - FROM [?].sys.database_query_store_options WHERE desired_state = 0 - AND N''?'' NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''DWConfiguration'', ''DWDiagnostics'', ''DWQueue'', ''ReportServer'', ''ReportServerTempDB'') OPTION (RECOMPILE)'; - END; - - - IF @ProductVersionMajor = 13 AND @ProductVersionMinor < 2149 --2016 CU1 has the fix in it - AND NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 182 ) - AND CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) NOT LIKE '%Enterprise%' - AND CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) NOT LIKE '%Developer%' - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 182) WITH NOWAIT; - - SET @StringToExecute = 'INSERT INTO #BlitzResults - (CheckID, - DatabaseName, - Priority, - FindingsGroup, - Finding, - URL, - Details) - SELECT TOP 1 - 182, - ''Server'', - 20, - ''Reliability'', - ''Query Store Cleanup Disabled'', - ''https://www.brentozar.com/go/cleanup'', - (''SQL 2016 RTM has a bug involving dumps that happen every time Query Store cleanup jobs run. This is fixed in CU1 and later: https://sqlserverupdates.com/sql-server-2016-updates/'') - FROM sys.databases AS d - WHERE d.is_query_store_on = 1 OPTION (RECOMPILE);'; - - IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; - IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; - - EXECUTE(@StringToExecute); - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 235 ) - AND EXISTS(SELECT * FROM sys.all_objects WHERE name = 'database_query_store_options') - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 235) WITH NOWAIT; - - EXEC dbo.sp_MSforeachdb 'USE [?]; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - INSERT INTO #BlitzResults - (CheckID, - DatabaseName, - Priority, - FindingsGroup, - Finding, - URL, - Details) - SELECT TOP 1 235, - N''?'', - 150, - ''Performance'', - ''Inconsistent Query Store metadata'', - '''', - (''Query store state in master metadata and database specific metadata not in sync.'') - FROM [?].sys.database_query_store_options dqso - join master.sys.databases D on D.name = N''?'' - WHERE ((dqso.actual_state = 0 AND D.is_query_store_on = 1) OR (dqso.actual_state <> 0 AND D.is_query_store_on = 0)) - AND N''?'' NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''DWConfiguration'', ''DWDiagnostics'', ''DWQueue'', ''ReportServer'', ''ReportServerTempDB'') OPTION (RECOMPILE)'; - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 41 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 41) WITH NOWAIT; - - EXEC dbo.sp_MSforeachdb 'use [?]; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - INSERT INTO #BlitzResults - (CheckID, - DatabaseName, - Priority, - FindingsGroup, - Finding, - URL, - Details) - SELECT 41, - N''?'', - 170, - ''File Configuration'', - ''Multiple Log Files on One Drive'', - ''https://www.brentozar.com/go/manylogs'', - (''The ['' + DB_NAME() + ''] database has multiple log files on the '' + LEFT(physical_name, 1) + '' drive. This is not a performance booster because log file access is sequential, not parallel.'') - FROM [?].sys.database_files WHERE type_desc = ''LOG'' - AND N''?'' <> ''[tempdb]'' - GROUP BY LEFT(physical_name, 1) - HAVING COUNT(*) > 1 - AND SUM(size) < 268435456 OPTION (RECOMPILE);'; - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 42 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 42) WITH NOWAIT; - - EXEC dbo.sp_MSforeachdb 'use [?]; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - INSERT INTO #BlitzResults - (CheckID, - DatabaseName, - Priority, - FindingsGroup, - Finding, - URL, - Details) - SELECT DISTINCT 42, - N''?'', - 170, - ''File Configuration'', - ''Uneven File Growth Settings in One Filegroup'', - ''https://www.brentozar.com/go/grow'', - (''The ['' + DB_NAME() + ''] database has multiple data files in one filegroup, but they are not all set up to grow in identical amounts. This can lead to uneven file activity inside the filegroup.'') - FROM [?].sys.database_files - WHERE type_desc = ''ROWS'' - GROUP BY data_space_id - HAVING COUNT(DISTINCT growth) > 1 OR COUNT(DISTINCT is_percent_growth) > 1 OPTION (RECOMPILE);'; - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 82 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 82) WITH NOWAIT; - - EXEC sp_MSforeachdb 'use [?]; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - INSERT INTO #BlitzResults - (CheckID, - DatabaseName, - Priority, - FindingsGroup, - Finding, - URL, Details) - SELECT DISTINCT 82 AS CheckID, - N''?'' as DatabaseName, - 170 AS Priority, - ''File Configuration'' AS FindingsGroup, - ''File growth set to percent'', - ''https://www.brentozar.com/go/percentgrowth'' AS URL, - ''The ['' + DB_NAME() + ''] database file '' + f.physical_name + '' has grown to '' + CONVERT(NVARCHAR(10), CONVERT(NUMERIC(38, 2), (f.size / 128.) / 1024.)) + '' GB, and is using percent filegrowth settings. This can lead to slow performance during growths if Instant File Initialization is not enabled.'' - FROM [?].sys.database_files f - WHERE is_percent_growth = 1 and size > 128000 OPTION (RECOMPILE);'; - END; - - /* addition by Henrik Staun Poulsen, Stovi Software */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 158 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 158) WITH NOWAIT; - - EXEC sp_MSforeachdb 'use [?]; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - INSERT INTO #BlitzResults - (CheckID, - DatabaseName, - Priority, - FindingsGroup, - Finding, - URL, Details) - SELECT DISTINCT 158 AS CheckID, - N''?'' as DatabaseName, - 170 AS Priority, - ''File Configuration'' AS FindingsGroup, - ''File growth set to 1MB'', - ''https://www.brentozar.com/go/percentgrowth'' AS URL, - ''The ['' + DB_NAME() + ''] database file '' + f.physical_name + '' is using 1MB filegrowth settings, but it has grown to '' + CAST((CAST(f.size AS BIGINT) * 8 / 1000000) AS NVARCHAR(10)) + '' GB. Time to up the growth amount.'' - FROM [?].sys.database_files f - WHERE is_percent_growth = 0 and growth=128 and size > 128000 OPTION (RECOMPILE);'; - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 33 ) - BEGIN - IF @@VERSION NOT LIKE '%Microsoft SQL Server 2000%' - AND @@VERSION NOT LIKE '%Microsoft SQL Server 2005%' - AND @SkipBlockingChecks = 0 - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 33) WITH NOWAIT; - - EXEC dbo.sp_MSforeachdb 'USE [?]; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - INSERT INTO #BlitzResults - (CheckID, - DatabaseName, - Priority, - FindingsGroup, - Finding, - URL, - Details) - SELECT DISTINCT 33, - db_name(), - 200, - ''Licensing'', - ''Enterprise Edition Features In Use'', - ''https://www.brentozar.com/go/ee'', - (''The ['' + DB_NAME() + ''] database is using '' + feature_name + ''. If this database is restored onto a Standard Edition server, the restore will fail on versions prior to 2016 SP1.'') - FROM [?].sys.dm_db_persisted_sku_features OPTION (RECOMPILE);'; - END; - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 19 ) - BEGIN - /* Method 1: Check sys.databases parameters */ - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 19) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - - SELECT 19 AS CheckID , - [name] AS DatabaseName , - 200 AS Priority , - 'Informational' AS FindingsGroup , - 'Replication In Use' AS Finding , - 'https://www.brentozar.com/go/repl' AS URL , - ( 'Database [' + [name] - + '] is a replication publisher, subscriber, or distributor.' ) AS Details - FROM sys.databases - WHERE name NOT IN ( SELECT DISTINCT - DatabaseName - FROM #SkipChecks - WHERE CheckID IS NULL OR CheckID = 19) - AND (is_published = 1 - OR is_subscribed = 1 - OR is_merge_published = 1 - OR is_distributor = 1); - - /* Method B: check subscribers for MSreplication_objects tables */ - EXEC dbo.sp_MSforeachdb 'USE [?]; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - INSERT INTO #BlitzResults - (CheckID, - DatabaseName, - Priority, - FindingsGroup, - Finding, - URL, - Details) - SELECT DISTINCT 19, - db_name(), - 200, - ''Informational'', - ''Replication In Use'', - ''https://www.brentozar.com/go/repl'', - (''['' + DB_NAME() + ''] has MSreplication_objects tables in it, indicating it is a replication subscriber.'') - FROM [?].sys.tables - WHERE name = ''dbo.MSreplication_objects'' AND ''?'' <> ''master'' OPTION (RECOMPILE)'; - - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 32 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 32) WITH NOWAIT; - - EXEC dbo.sp_MSforeachdb 'USE [?]; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - INSERT INTO #BlitzResults - (CheckID, - DatabaseName, - Priority, - FindingsGroup, - Finding, - URL, - Details) - SELECT 32, - N''?'', - 150, - ''Performance'', - ''Triggers on Tables'', - ''https://www.brentozar.com/go/trig'', - (''The ['' + DB_NAME() + ''] database has '' + CAST(SUM(1) AS NVARCHAR(50)) + '' triggers.'') - FROM [?].sys.triggers t INNER JOIN [?].sys.objects o ON t.parent_id = o.object_id - INNER JOIN [?].sys.schemas s ON o.schema_id = s.schema_id WHERE t.is_ms_shipped = 0 AND DB_NAME() != ''ReportServer'' - HAVING SUM(1) > 0 OPTION (RECOMPILE)'; - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 164 ) - AND EXISTS(SELECT * FROM sys.all_objects WHERE name = 'fn_validate_plan_guide') - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 164) WITH NOWAIT; - - EXEC dbo.sp_MSforeachdb 'USE [?]; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SET QUOTED_IDENTIFIER ON; - INSERT INTO #BlitzResults - (CheckID, - DatabaseName, - Priority, - FindingsGroup, - Finding, - URL, - Details) - SELECT DISTINCT 164, - N''?'', - 100, - ''Reliability'', - ''Plan Guides Failing'', - ''https://www.brentozar.com/go/misguided'', - (''The ['' + DB_NAME() + ''] database has plan guides that are no longer valid, so the queries involved may be failing silently.'') - FROM [?].sys.plan_guides g CROSS APPLY fn_validate_plan_guide(g.plan_guide_id) OPTION (RECOMPILE)'; - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 46 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 46) WITH NOWAIT; - - EXEC dbo.sp_MSforeachdb 'USE [?]; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - INSERT INTO #BlitzResults - (CheckID, - DatabaseName, - Priority, - FindingsGroup, - Finding, - URL, - Details) - SELECT 46, - N''?'', - 150, - ''Performance'', - ''Leftover Fake Indexes From Wizards'', - ''https://www.brentozar.com/go/hypo'', - (''The index ['' + DB_NAME() + ''].['' + s.name + ''].['' + o.name + ''].['' + i.name + ''] is a leftover hypothetical index from the Index Tuning Wizard or Database Tuning Advisor. This index is not actually helping performance and should be removed.'') - from [?].sys.indexes i INNER JOIN [?].sys.objects o ON i.object_id = o.object_id INNER JOIN [?].sys.schemas s ON o.schema_id = s.schema_id - WHERE i.is_hypothetical = 1 OPTION (RECOMPILE);'; - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 47 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 47) WITH NOWAIT; - - EXEC dbo.sp_MSforeachdb 'USE [?]; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - INSERT INTO #BlitzResults - (CheckID, - DatabaseName, - Priority, - FindingsGroup, - Finding, - URL, - Details) - SELECT 47, - N''?'', - 100, - ''Performance'', - ''Indexes Disabled'', - ''https://www.brentozar.com/go/ixoff'', - (''The index ['' + DB_NAME() + ''].['' + s.name + ''].['' + o.name + ''].['' + i.name + ''] is disabled. This index is not actually helping performance and should either be enabled or removed.'') - from [?].sys.indexes i INNER JOIN [?].sys.objects o ON i.object_id = o.object_id INNER JOIN [?].sys.schemas s ON o.schema_id = s.schema_id - WHERE i.is_disabled = 1 OPTION (RECOMPILE);'; - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 48 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 48) WITH NOWAIT; - - EXEC dbo.sp_MSforeachdb 'USE [?]; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - INSERT INTO #BlitzResults - (CheckID, - DatabaseName, - Priority, - FindingsGroup, - Finding, - URL, - Details) - SELECT DISTINCT 48, - N''?'', - 150, - ''Performance'', - ''Foreign Keys Not Trusted'', - ''https://www.brentozar.com/go/trust'', - (''The ['' + DB_NAME() + ''] database has foreign keys that were probably disabled, data was changed, and then the key was enabled again. Simply enabling the key is not enough for the optimizer to use this key - we have to alter the table using the WITH CHECK CHECK CONSTRAINT parameter.'') - from [?].sys.foreign_keys i INNER JOIN [?].sys.objects o ON i.parent_object_id = o.object_id INNER JOIN [?].sys.schemas s ON o.schema_id = s.schema_id - WHERE i.is_not_trusted = 1 AND i.is_not_for_replication = 0 AND i.is_disabled = 0 AND N''?'' NOT IN (''master'', ''model'', ''msdb'', ''ReportServer'', ''ReportServerTempDB'') OPTION (RECOMPILE);'; - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 56 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 56) WITH NOWAIT; - - EXEC dbo.sp_MSforeachdb 'USE [?]; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - INSERT INTO #BlitzResults - (CheckID, - DatabaseName, - Priority, - FindingsGroup, - Finding, - URL, - Details) - SELECT 56, - N''?'', - 150, - ''Performance'', - ''Check Constraint Not Trusted'', - ''https://www.brentozar.com/go/trust'', - (''The check constraint ['' + DB_NAME() + ''].['' + s.name + ''].['' + o.name + ''].['' + i.name + ''] is not trusted - meaning, it was disabled, data was changed, and then the constraint was enabled again. Simply enabling the constraint is not enough for the optimizer to use this constraint - we have to alter the table using the WITH CHECK CHECK CONSTRAINT parameter.'') - from [?].sys.check_constraints i INNER JOIN [?].sys.objects o ON i.parent_object_id = o.object_id - INNER JOIN [?].sys.schemas s ON o.schema_id = s.schema_id - WHERE i.is_not_trusted = 1 AND i.is_not_for_replication = 0 AND i.is_disabled = 0 OPTION (RECOMPILE);'; - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 95 ) - BEGIN - IF @@VERSION NOT LIKE '%Microsoft SQL Server 2000%' - AND @@VERSION NOT LIKE '%Microsoft SQL Server 2005%' - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 95) WITH NOWAIT; - - EXEC dbo.sp_MSforeachdb 'USE [?]; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - INSERT INTO #BlitzResults - (CheckID, - DatabaseName, - Priority, - FindingsGroup, - Finding, - URL, - Details) - SELECT TOP 1 95 AS CheckID, - N''?'' as DatabaseName, - 110 AS Priority, - ''Performance'' AS FindingsGroup, - ''Plan Guides Enabled'' AS Finding, - ''https://www.brentozar.com/go/guides'' AS URL, - (''Database ['' + DB_NAME() + ''] has query plan guides so a query will always get a specific execution plan. If you are having trouble getting query performance to improve, it might be due to a frozen plan. Review the DMV sys.plan_guides to learn more about the plan guides in place on this server.'') AS Details - FROM [?].sys.plan_guides WHERE is_disabled = 0 OPTION (RECOMPILE);'; - END; - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 60 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 60) WITH NOWAIT; - - EXEC sp_MSforeachdb 'USE [?]; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - INSERT INTO #BlitzResults - (CheckID, - DatabaseName, - Priority, - FindingsGroup, - Finding, - URL, - Details) - SELECT 60 AS CheckID, - N''?'' as DatabaseName, - 100 AS Priority, - ''Performance'' AS FindingsGroup, - ''Fill Factor Changed'', - ''https://www.brentozar.com/go/fillfactor'' AS URL, - ''The ['' + DB_NAME() + ''] database has '' + CAST(SUM(1) AS NVARCHAR(50)) + '' objects with fill factor = '' + CAST(fill_factor AS NVARCHAR(5)) + ''%. This can cause memory and storage performance problems, but may also prevent page splits.'' - FROM [?].sys.indexes - WHERE fill_factor <> 0 AND fill_factor < 80 AND is_disabled = 0 AND is_hypothetical = 0 - GROUP BY fill_factor OPTION (RECOMPILE);'; - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 78 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 78) WITH NOWAIT; - - EXECUTE master.sys.sp_MSforeachdb 'USE [?]; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - INSERT INTO #Recompile - SELECT DISTINCT DBName = DB_Name(), SPName = SO.name, SM.is_recompiled, ISR.SPECIFIC_SCHEMA - FROM sys.sql_modules AS SM - LEFT OUTER JOIN master.sys.databases AS sDB ON SM.object_id = DB_id() - LEFT OUTER JOIN dbo.sysobjects AS SO ON SM.object_id = SO.id and type = ''P'' - LEFT OUTER JOIN INFORMATION_SCHEMA.ROUTINES AS ISR on ISR.Routine_Name = SO.name AND ISR.SPECIFIC_CATALOG = DB_Name() - WHERE SM.is_recompiled=1 OPTION (RECOMPILE); /* oh the rich irony of recompile here */ - '; - INSERT INTO #BlitzResults - (Priority, - FindingsGroup, - Finding, - DatabaseName, - URL, - Details, - CheckID) - SELECT [Priority] = '100', - FindingsGroup = 'Performance', - Finding = 'Stored Procedure WITH RECOMPILE', - DatabaseName = DBName, - URL = 'https://www.brentozar.com/go/recompile', - Details = '[' + DBName + '].[' + SPSchema + '].[' + ProcName + '] has WITH RECOMPILE in the stored procedure code, which may cause increased CPU usage due to constant recompiles of the code.', - CheckID = '78' - FROM #Recompile AS TR - WHERE ProcName NOT LIKE 'sp_AllNightLog%' AND ProcName NOT LIKE 'sp_AskBrent%' AND ProcName NOT LIKE 'sp_Blitz%' AND ProcName NOT LIKE 'sp_PressureDetector' - AND DBName NOT IN ('master', 'model', 'msdb', 'tempdb'); - DROP TABLE #Recompile; - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 86 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 86) WITH NOWAIT; - - EXEC dbo.sp_MSforeachdb 'USE [?]; INSERT INTO #BlitzResults (CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details) SELECT DISTINCT 86, DB_NAME(), 230, ''Security'', ''Elevated Permissions on a Database'', ''https://www.brentozar.com/go/elevated'', (''In ['' + DB_NAME() + ''], user ['' + u.name + ''] has the role ['' + g.name + '']. This user can perform tasks beyond just reading and writing data.'') FROM (SELECT memberuid = convert(int, member_principal_id), groupuid = convert(int, role_principal_id) FROM [?].sys.database_role_members) m inner join [?].dbo.sysusers u on m.memberuid = u.uid inner join sysusers g on m.groupuid = g.uid where u.name <> ''dbo'' and g.name in (''db_owner'' , ''db_accessadmin'' , ''db_securityadmin'' , ''db_ddladmin'') OPTION (RECOMPILE);'; - END; - - /*Check for non-aligned indexes in partioned databases*/ - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 72 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 72) WITH NOWAIT; - - EXEC dbo.sp_MSforeachdb 'USE [?]; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - insert into #partdb(dbname, objectname, type_desc) - SELECT distinct db_name(DB_ID()) as DBName,o.name Object_Name,ds.type_desc - FROM sys.objects AS o JOIN sys.indexes AS i ON o.object_id = i.object_id - JOIN sys.data_spaces ds on ds.data_space_id = i.data_space_id - LEFT OUTER JOIN sys.dm_db_index_usage_stats AS s ON i.object_id = s.object_id AND i.index_id = s.index_id AND s.database_id = DB_ID() - WHERE o.type = ''u'' - -- Clustered and Non-Clustered indexes - AND i.type IN (1, 2) - AND o.object_id in - ( - SELECT a.object_id from - (SELECT ob.object_id, ds.type_desc from sys.objects ob JOIN sys.indexes ind on ind.object_id = ob.object_id join sys.data_spaces ds on ds.data_space_id = ind.data_space_id - GROUP BY ob.object_id, ds.type_desc ) a group by a.object_id having COUNT (*) > 1 - ) OPTION (RECOMPILE);'; - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT DISTINCT - 72 AS CheckID , - dbname AS DatabaseName , - 100 AS Priority , - 'Performance' AS FindingsGroup , - 'The partitioned database ' + dbname - + ' may have non-aligned indexes' AS Finding , - 'https://www.brentozar.com/go/aligned' AS URL , - 'Having non-aligned indexes on partitioned tables may cause inefficient query plans and CPU pressure' AS Details - FROM #partdb - WHERE dbname IS NOT NULL - AND dbname NOT IN ( SELECT DISTINCT - DatabaseName - FROM #SkipChecks - WHERE CheckID IS NULL OR CheckID = 72); - DROP TABLE #partdb; - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 113 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 113) WITH NOWAIT; - - EXEC dbo.sp_MSforeachdb 'USE [?]; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - INSERT INTO #BlitzResults - (CheckID, - DatabaseName, - Priority, - FindingsGroup, - Finding, - URL, - Details) - SELECT DISTINCT 113, - N''?'', - 50, - ''Reliability'', - ''Full Text Indexes Not Updating'', - ''https://www.brentozar.com/go/fulltext'', - (''At least one full text index in this database has not been crawled in the last week.'') - from [?].sys.fulltext_indexes i WHERE change_tracking_state_desc <> ''AUTO'' AND i.is_enabled = 1 AND i.crawl_end_date < DATEADD(dd, -7, GETDATE()) OPTION (RECOMPILE);'; - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 115 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 115) WITH NOWAIT; - - EXEC dbo.sp_MSforeachdb 'USE [?]; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - INSERT INTO #BlitzResults - (CheckID, - DatabaseName, - Priority, - FindingsGroup, - Finding, - URL, - Details) - SELECT 115, - N''?'', - 110, - ''Performance'', - ''Parallelism Rocket Surgery'', - ''https://www.brentozar.com/go/makeparallel'', - (''['' + DB_NAME() + ''] has a make_parallel function, indicating that an advanced developer may be manhandling SQL Server into forcing queries to go parallel.'') - from [?].INFORMATION_SCHEMA.ROUTINES WHERE ROUTINE_NAME = ''make_parallel'' AND ROUTINE_TYPE = ''FUNCTION'' OPTION (RECOMPILE);'; - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 122 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 122) WITH NOWAIT; - - /* SQL Server 2012 and newer uses temporary stats for Availability Groups, and those show up as user-created */ - IF EXISTS (SELECT * - FROM sys.all_columns c - INNER JOIN sys.all_objects o ON c.object_id = o.object_id - WHERE c.name = 'is_temporary' AND o.name = 'stats') - - EXEC dbo.sp_MSforeachdb 'USE [?]; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - INSERT INTO #BlitzResults - (CheckID, - DatabaseName, - Priority, - FindingsGroup, - Finding, - URL, - Details) - SELECT TOP 1 122, - N''?'', - 200, - ''Performance'', - ''User-Created Statistics In Place'', - ''https://www.brentozar.com/go/userstats'', - (''['' + DB_NAME() + ''] has '' + CAST(SUM(1) AS NVARCHAR(10)) + '' user-created statistics. This indicates that someone is being a rocket scientist with the stats, and might actually be slowing things down, especially during stats updates.'') - from [?].sys.stats WHERE user_created = 1 AND is_temporary = 0 - HAVING SUM(1) > 0 OPTION (RECOMPILE);'; - - ELSE - EXEC dbo.sp_MSforeachdb 'USE [?]; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - INSERT INTO #BlitzResults - (CheckID, - DatabaseName, - Priority, - FindingsGroup, - Finding, - URL, - Details) - SELECT 122, - N''?'', - 200, - ''Performance'', - ''User-Created Statistics In Place'', - ''https://www.brentozar.com/go/userstats'', - (''['' + DB_NAME() + ''] has '' + CAST(SUM(1) AS NVARCHAR(10)) + '' user-created statistics. This indicates that someone is being a rocket scientist with the stats, and might actually be slowing things down, especially during stats updates.'') - from [?].sys.stats WHERE user_created = 1 - HAVING SUM(1) > 0 OPTION (RECOMPILE);'; - - END; /* IF NOT EXISTS ( SELECT 1 */ - - /*Check for high VLF count: this will omit any database snapshots*/ - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 69 ) - BEGIN - IF @ProductVersionMajor >= 11 - - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d] (2012 version of Log Info).', 0, 1, 69) WITH NOWAIT; - - EXEC sp_MSforeachdb N'USE [?]; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - INSERT INTO #LogInfo2012 - EXEC sp_executesql N''DBCC LogInfo() WITH NO_INFOMSGS''; - IF @@ROWCOUNT > 999 - BEGIN - INSERT INTO #BlitzResults - ( CheckID - ,DatabaseName - ,Priority - ,FindingsGroup - ,Finding - ,URL - ,Details) - SELECT 69 - ,DB_NAME() - ,170 - ,''File Configuration'' - ,''High VLF Count'' - ,''https://www.brentozar.com/go/vlf'' - ,''The ['' + DB_NAME() + ''] database has '' + CAST(COUNT(*) as VARCHAR(20)) + '' virtual log files (VLFs). This may be slowing down startup, restores, and even inserts/updates/deletes.'' - FROM #LogInfo2012 - WHERE EXISTS (SELECT name FROM master.sys.databases - WHERE source_database_id is null) OPTION (RECOMPILE); - END - TRUNCATE TABLE #LogInfo2012;'; - DROP TABLE #LogInfo2012; - END; - ELSE - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d] (pre-2012 version of Log Info).', 0, 1, 69) WITH NOWAIT; - - EXEC sp_MSforeachdb N'USE [?]; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - INSERT INTO #LogInfo - EXEC sp_executesql N''DBCC LogInfo() WITH NO_INFOMSGS''; - IF @@ROWCOUNT > 999 - BEGIN - INSERT INTO #BlitzResults - ( CheckID - ,DatabaseName - ,Priority - ,FindingsGroup - ,Finding - ,URL - ,Details) - SELECT 69 - ,DB_NAME() - ,170 - ,''File Configuration'' - ,''High VLF Count'' - ,''https://www.brentozar.com/go/vlf'' - ,''The ['' + DB_NAME() + ''] database has '' + CAST(COUNT(*) as VARCHAR(20)) + '' virtual log files (VLFs). This may be slowing down startup, restores, and even inserts/updates/deletes.'' - FROM #LogInfo - WHERE EXISTS (SELECT name FROM master.sys.databases - WHERE source_database_id is null) OPTION (RECOMPILE); - END - TRUNCATE TABLE #LogInfo;'; - DROP TABLE #LogInfo; - END; - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 80 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 80) WITH NOWAIT; - - EXEC dbo.sp_MSforeachdb 'USE [?]; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - INSERT INTO #BlitzResults (CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details) - SELECT DISTINCT 80, DB_NAME(), 170, ''Reliability'', ''Max File Size Set'', ''https://www.brentozar.com/go/maxsize'', - (''The ['' + DB_NAME() + ''] database file '' + df.name + '' has a max file size set to '' - + CAST(CAST(df.max_size AS BIGINT) * 8 / 1024 AS VARCHAR(100)) - + ''MB. If it runs out of space, the database will stop working even though there may be drive space available.'') - FROM sys.database_files df - WHERE 0 = (SELECT is_read_only FROM sys.databases WHERE name = ''?'') - AND df.max_size <> 268435456 - AND df.max_size <> -1 - AND df.type <> 2 - AND df.growth > 0 - AND df.name <> ''DWDiagnostics'' OPTION (RECOMPILE);'; - - DELETE br - FROM #BlitzResults br - INNER JOIN #SkipChecks sc ON sc.CheckID = 80 AND br.DatabaseName = sc.DatabaseName; - END; - - - /* Check if columnstore indexes are in use - for Github issue #615 */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 74 ) /* Trace flags */ - BEGIN - TRUNCATE TABLE #TemporaryDatabaseResults; - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 74) WITH NOWAIT; - - EXEC dbo.sp_MSforeachdb 'USE [?]; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; IF EXISTS(SELECT * FROM sys.indexes WHERE type IN (5,6)) INSERT INTO #TemporaryDatabaseResults (DatabaseName, Finding) VALUES (DB_NAME(), ''Yup'') OPTION (RECOMPILE);'; - IF EXISTS (SELECT * FROM #TemporaryDatabaseResults) SET @ColumnStoreIndexesInUse = 1; - END; - - /* Non-Default Database Scoped Config - Github issue #598 */ - IF EXISTS ( SELECT * FROM sys.all_objects WHERE [name] = 'database_scoped_configurations' ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d] through [%d] and [%d] through [%d].', 0, 1, 194, 197, 237, 255) WITH NOWAIT; - - INSERT INTO #DatabaseScopedConfigurationDefaults (configuration_id, [name], default_value, default_value_for_secondary, CheckID) - SELECT 1, 'MAXDOP', '0', NULL, 194 - UNION ALL - SELECT 2, 'LEGACY_CARDINALITY_ESTIMATION', '0', NULL, 195 - UNION ALL - SELECT 3, 'PARAMETER_SNIFFING', '1', NULL, 196 - UNION ALL - SELECT 4, 'QUERY_OPTIMIZER_HOTFIXES', '0', NULL, 197 - UNION ALL - SELECT 6, 'IDENTITY_CACHE', '1', NULL, 237 - UNION ALL - SELECT 7, 'INTERLEAVED_EXECUTION_TVF', '1', NULL, 238 - UNION ALL - SELECT 8, 'BATCH_MODE_MEMORY_GRANT_FEEDBACK', '1', NULL, 239 - UNION ALL - SELECT 9, 'BATCH_MODE_ADAPTIVE_JOINS', '1', NULL, 240 - UNION ALL - SELECT 10, 'TSQL_SCALAR_UDF_INLINING', '1', NULL, 241 - UNION ALL - SELECT 11, 'ELEVATE_ONLINE', 'OFF', NULL, 242 - UNION ALL - SELECT 12, 'ELEVATE_RESUMABLE', 'OFF', NULL, 243 - UNION ALL - SELECT 13, 'OPTIMIZE_FOR_AD_HOC_WORKLOADS', '0', NULL, 244 - UNION ALL - SELECT 14, 'XTP_PROCEDURE_EXECUTION_STATISTICS', '0', NULL, 245 - UNION ALL - SELECT 15, 'XTP_QUERY_EXECUTION_STATISTICS', '0', NULL, 246 - UNION ALL - SELECT 16, 'ROW_MODE_MEMORY_GRANT_FEEDBACK', '1', NULL, 247 - UNION ALL - SELECT 17, 'ISOLATE_SECURITY_POLICY_CARDINALITY', '0', NULL, 248 - UNION ALL - SELECT 18, 'BATCH_MODE_ON_ROWSTORE', '1', NULL, 249 - UNION ALL - SELECT 19, 'DEFERRED_COMPILATION_TV', '1', NULL, 250 - UNION ALL - SELECT 20, 'ACCELERATED_PLAN_FORCING', '1', NULL, 251 - UNION ALL - SELECT 21, 'GLOBAL_TEMPORARY_TABLE_AUTO_DROP', '1', NULL, 252 - UNION ALL - SELECT 22, 'LIGHTWEIGHT_QUERY_PROFILING', '1', NULL, 253 - UNION ALL - SELECT 23, 'VERBOSE_TRUNCATION_WARNINGS', '1', NULL, 254 - UNION ALL - SELECT 24, 'LAST_QUERY_PLAN_STATS', '0', NULL, 255; - EXEC dbo.sp_MSforeachdb 'USE [?]; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; INSERT INTO #BlitzResults (CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details) - SELECT def1.CheckID, DB_NAME(), 210, ''Non-Default Database Scoped Config'', dsc.[name], ''https://www.brentozar.com/go/dbscope'', (''Set value: '' + COALESCE(CAST(dsc.value AS NVARCHAR(100)),''Empty'') + '' Default: '' + COALESCE(CAST(def1.default_value AS NVARCHAR(100)),''Empty'') + '' Set value for secondary: '' + COALESCE(CAST(dsc.value_for_secondary AS NVARCHAR(100)),''Empty'') + '' Default value for secondary: '' + COALESCE(CAST(def1.default_value_for_secondary AS NVARCHAR(100)),''Empty'')) - FROM [?].sys.database_scoped_configurations dsc - INNER JOIN #DatabaseScopedConfigurationDefaults def1 ON dsc.configuration_id = def1.configuration_id - LEFT OUTER JOIN #DatabaseScopedConfigurationDefaults def ON dsc.configuration_id = def.configuration_id AND (cast(dsc.value as nvarchar(100)) = cast(def.default_value as nvarchar(100)) OR dsc.value IS NULL) AND (dsc.value_for_secondary = def.default_value_for_secondary OR dsc.value_for_secondary IS NULL) - LEFT OUTER JOIN #SkipChecks sk ON (sk.CheckID IS NULL OR def.CheckID = sk.CheckID) AND (sk.DatabaseName IS NULL OR sk.DatabaseName = DB_NAME()) - WHERE def.configuration_id IS NULL AND sk.CheckID IS NULL ORDER BY 1 - OPTION (RECOMPILE);'; - END; - - /* Check 218 - Show me the dodgy SET Options */ - IF NOT EXISTS ( - SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL - AND CheckID = 218 - ) - BEGIN - IF @Debug IN (1,2) - BEGIN - RAISERROR ('Running CheckId [%d].',0,1,218) WITH NOWAIT; - END - - EXECUTE sp_MSforeachdb 'USE [?]; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - INSERT INTO #BlitzResults (CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details) - SELECT 218 AS CheckID - ,''?'' AS DatabaseName - ,150 AS Priority - ,''Performance'' AS FindingsGroup - ,''Objects created with dangerous SET Options'' AS Finding - ,''https://www.brentozar.com/go/badset'' AS URL - ,''The '' + QUOTENAME(DB_NAME()) - + '' database has '' + CONVERT(VARCHAR(20),COUNT(1)) - + '' objects that were created with dangerous ANSI_NULL or QUOTED_IDENTIFIER options.'' - + '' These objects can break when using filtered indexes, indexed views'' - + '' and other advanced SQL features.'' AS Details - FROM sys.sql_modules sm - JOIN sys.objects o ON o.[object_id] = sm.[object_id] - AND ( - sm.uses_ansi_nulls <> 1 - OR sm.uses_quoted_identifier <> 1 - ) - AND o.is_ms_shipped = 0 - HAVING COUNT(1) > 0;'; - END; --of Check 218. - - /* Check 225 - Reliability - Resumable Index Operation Paused */ - IF NOT EXISTS ( - SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL - AND CheckID = 225 - ) - AND EXISTS (SELECT * FROM sys.all_objects WHERE name = 'index_resumable_operations') - BEGIN - IF @Debug IN (1,2) - BEGIN - RAISERROR ('Running CheckId [%d].',0,1,218) WITH NOWAIT; - END - - EXECUTE sp_MSforeachdb 'USE [?]; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - INSERT INTO #BlitzResults (CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details) - SELECT 225 AS CheckID - ,''?'' AS DatabaseName - ,200 AS Priority - ,''Reliability'' AS FindingsGroup - ,''Resumable Index Operation Paused'' AS Finding - ,''https://www.brentozar.com/go/resumable'' AS URL - ,iro.state_desc + N'' since '' + CONVERT(NVARCHAR(50), last_pause_time, 120) + '', '' - + CAST(iro.percent_complete AS NVARCHAR(20)) + ''% complete: '' - + CAST(iro.sql_text AS NVARCHAR(1000)) AS Details - FROM sys.index_resumable_operations iro - JOIN sys.objects o ON iro.[object_id] = o.[object_id] - WHERE iro.state <> 0;'; - END; --of Check 225. - - --/* Check 220 - Statistics Without Histograms */ - --IF NOT EXISTS ( - -- SELECT 1 - -- FROM #SkipChecks - -- WHERE DatabaseName IS NULL - -- AND CheckID = 220 - -- ) - -- AND EXISTS (SELECT * FROM sys.all_objects WHERE name = 'dm_db_stats_histogram') - --BEGIN - -- IF @Debug IN (1,2) - -- BEGIN - -- RAISERROR ('Running CheckId [%d].',0,1,220) WITH NOWAIT; - -- END - - -- EXECUTE sp_MSforeachdb 'USE [?]; - -- SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - -- INSERT INTO #BlitzResults (CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details) - -- SELECT 220 AS CheckID - -- ,DB_NAME() AS DatabaseName - -- ,110 AS Priority - -- ,''Performance'' AS FindingsGroup - -- ,''Statistics Without Histograms'' AS Finding - -- ,''https://www.brentozar.com/go/brokenstats'' AS URL - -- ,CAST(COUNT(DISTINCT o.object_id) AS VARCHAR(100)) + '' tables have statistics that have not been updated since the database was restored or upgraded,'' - -- + '' and have no data in their histogram. See the More Info URL for a script to update them. '' AS Details - -- FROM sys.all_objects o - -- INNER JOIN sys.stats s ON o.object_id = s.object_id AND s.has_filter = 0 - -- OUTER APPLY sys.dm_db_stats_histogram(o.object_id, s.stats_id) h - -- WHERE o.is_ms_shipped = 0 AND o.type_desc = ''USER_TABLE'' - -- AND h.object_id IS NULL - -- AND 0 < (SELECT SUM(row_count) FROM sys.dm_db_partition_stats ps WHERE ps.object_id = o.object_id) - -- AND ''?'' NOT IN (''master'', ''model'', ''msdb'', ''tempdb'') - -- HAVING COUNT(DISTINCT o.object_id) > 0;'; - --END; --of Check 220. - - /*Check for the last good DBCC CHECKDB date */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 68 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 68) WITH NOWAIT; - - /* Removed as populating the #DBCCs table now done in advance as data is uses for multiple checks*/ - --EXEC sp_MSforeachdb N'USE [?]; - --SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - --INSERT #DBCCs - -- (ParentObject, - -- Object, - -- Field, - -- Value) - --EXEC (''DBCC DBInfo() With TableResults, NO_INFOMSGS''); - --UPDATE #DBCCs SET DbName = N''?'' WHERE DbName IS NULL OPTION (RECOMPILE);'; - - WITH DB2 - AS ( SELECT DISTINCT - Field , - Value , - DbName - FROM #DBCCs - INNER JOIN sys.databases d ON #DBCCs.DbName = d.name - WHERE Field = 'dbi_dbccLastKnownGood' - AND d.create_date < DATEADD(dd, -14, GETDATE()) - ) - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 68 AS CheckID , - DB2.DbName AS DatabaseName , - 1 AS PRIORITY , - 'Reliability' AS FindingsGroup , - 'Last good DBCC CHECKDB over 2 weeks old' AS Finding , - 'https://www.brentozar.com/go/checkdb' AS URL , - 'Last successful CHECKDB: ' - + CASE DB2.Value - WHEN '1900-01-01 00:00:00.000' - THEN ' never.' - ELSE DB2.Value - END AS Details - FROM DB2 - WHERE DB2.DbName <> 'tempdb' - AND DB2.DbName NOT IN ( SELECT DISTINCT - DatabaseName - FROM - #SkipChecks - WHERE CheckID IS NULL OR CheckID = 68) - AND DB2.DbName NOT IN ( SELECT name - FROM sys.databases - WHERE is_read_only = 1) - AND CONVERT(DATETIME, DB2.Value, 121) < DATEADD(DD, - -14, - CURRENT_TIMESTAMP); - END; - - END; /* IF @CheckUserDatabaseObjects = 1 */ - - IF @CheckProcedureCache = 1 - - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Begin checking procedure cache', 0, 1) WITH NOWAIT; - - BEGIN - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 35 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 35) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 35 AS CheckID , - 100 AS Priority , - 'Performance' AS FindingsGroup , - 'Single-Use Plans in Procedure Cache' AS Finding , - 'https://www.brentozar.com/go/single' AS URL , - ( CAST(COUNT(*) AS VARCHAR(10)) - + ' query plans are taking up memory in the procedure cache. This may be wasted memory if we cache plans for queries that never get called again. This may be a good use case for SQL Server 2008''s Optimize for Ad Hoc or for Forced Parameterization.' ) AS Details - FROM sys.dm_exec_cached_plans AS cp - WHERE cp.usecounts = 1 - AND cp.objtype = 'Adhoc' - AND EXISTS ( SELECT - 1 - FROM sys.configurations - WHERE - name = 'optimize for ad hoc workloads' - AND value_in_use = 0 ) - HAVING COUNT(*) > 1; - END; - - /* Set up the cache tables. Different on 2005 since it doesn't support query_hash, query_plan_hash. */ - IF @@VERSION LIKE '%Microsoft SQL Server 2005%' - BEGIN - IF @CheckProcedureCacheFilter = 'CPU' - OR @CheckProcedureCacheFilter IS NULL - BEGIN - SET @StringToExecute = 'WITH queries ([sql_handle],[statement_start_offset],[statement_end_offset],[plan_generation_num],[plan_handle],[creation_time],[last_execution_time],[execution_count],[total_worker_time],[last_worker_time],[min_worker_time],[max_worker_time],[total_physical_reads],[last_physical_reads],[min_physical_reads],[max_physical_reads],[total_logical_writes],[last_logical_writes],[min_logical_writes],[max_logical_writes],[total_logical_reads],[last_logical_reads],[min_logical_reads],[max_logical_reads],[total_clr_time],[last_clr_time],[min_clr_time],[max_clr_time],[total_elapsed_time],[last_elapsed_time],[min_elapsed_time],[max_elapsed_time]) - AS (SELECT TOP 20 qs.[sql_handle],qs.[statement_start_offset],qs.[statement_end_offset],qs.[plan_generation_num],qs.[plan_handle],qs.[creation_time],qs.[last_execution_time],qs.[execution_count],qs.[total_worker_time],qs.[last_worker_time],qs.[min_worker_time],qs.[max_worker_time],qs.[total_physical_reads],qs.[last_physical_reads],qs.[min_physical_reads],qs.[max_physical_reads],qs.[total_logical_writes],qs.[last_logical_writes],qs.[min_logical_writes],qs.[max_logical_writes],qs.[total_logical_reads],qs.[last_logical_reads],qs.[min_logical_reads],qs.[max_logical_reads],qs.[total_clr_time],qs.[last_clr_time],qs.[min_clr_time],qs.[max_clr_time],qs.[total_elapsed_time],qs.[last_elapsed_time],qs.[min_elapsed_time],qs.[max_elapsed_time] - FROM sys.dm_exec_query_stats qs - ORDER BY qs.total_worker_time DESC) - INSERT INTO #dm_exec_query_stats ([sql_handle],[statement_start_offset],[statement_end_offset],[plan_generation_num],[plan_handle],[creation_time],[last_execution_time],[execution_count],[total_worker_time],[last_worker_time],[min_worker_time],[max_worker_time],[total_physical_reads],[last_physical_reads],[min_physical_reads],[max_physical_reads],[total_logical_writes],[last_logical_writes],[min_logical_writes],[max_logical_writes],[total_logical_reads],[last_logical_reads],[min_logical_reads],[max_logical_reads],[total_clr_time],[last_clr_time],[min_clr_time],[max_clr_time],[total_elapsed_time],[last_elapsed_time],[min_elapsed_time],[max_elapsed_time]) - SELECT qs.[sql_handle],qs.[statement_start_offset],qs.[statement_end_offset],qs.[plan_generation_num],qs.[plan_handle],qs.[creation_time],qs.[last_execution_time],qs.[execution_count],qs.[total_worker_time],qs.[last_worker_time],qs.[min_worker_time],qs.[max_worker_time],qs.[total_physical_reads],qs.[last_physical_reads],qs.[min_physical_reads],qs.[max_physical_reads],qs.[total_logical_writes],qs.[last_logical_writes],qs.[min_logical_writes],qs.[max_logical_writes],qs.[total_logical_reads],qs.[last_logical_reads],qs.[min_logical_reads],qs.[max_logical_reads],qs.[total_clr_time],qs.[last_clr_time],qs.[min_clr_time],qs.[max_clr_time],qs.[total_elapsed_time],qs.[last_elapsed_time],qs.[min_elapsed_time],qs.[max_elapsed_time] - FROM queries qs - LEFT OUTER JOIN #dm_exec_query_stats qsCaught ON qs.sql_handle = qsCaught.sql_handle AND qs.plan_handle = qsCaught.plan_handle AND qs.statement_start_offset = qsCaught.statement_start_offset - WHERE qsCaught.sql_handle IS NULL OPTION (RECOMPILE);'; - EXECUTE(@StringToExecute); - END; - - IF @CheckProcedureCacheFilter = 'Reads' - OR @CheckProcedureCacheFilter IS NULL - BEGIN - SET @StringToExecute = 'WITH queries ([sql_handle],[statement_start_offset],[statement_end_offset],[plan_generation_num],[plan_handle],[creation_time],[last_execution_time],[execution_count],[total_worker_time],[last_worker_time],[min_worker_time],[max_worker_time],[total_physical_reads],[last_physical_reads],[min_physical_reads],[max_physical_reads],[total_logical_writes],[last_logical_writes],[min_logical_writes],[max_logical_writes],[total_logical_reads],[last_logical_reads],[min_logical_reads],[max_logical_reads],[total_clr_time],[last_clr_time],[min_clr_time],[max_clr_time],[total_elapsed_time],[last_elapsed_time],[min_elapsed_time],[max_elapsed_time]) - AS (SELECT TOP 20 qs.[sql_handle],qs.[statement_start_offset],qs.[statement_end_offset],qs.[plan_generation_num],qs.[plan_handle],qs.[creation_time],qs.[last_execution_time],qs.[execution_count],qs.[total_worker_time],qs.[last_worker_time],qs.[min_worker_time],qs.[max_worker_time],qs.[total_physical_reads],qs.[last_physical_reads],qs.[min_physical_reads],qs.[max_physical_reads],qs.[total_logical_writes],qs.[last_logical_writes],qs.[min_logical_writes],qs.[max_logical_writes],qs.[total_logical_reads],qs.[last_logical_reads],qs.[min_logical_reads],qs.[max_logical_reads],qs.[total_clr_time],qs.[last_clr_time],qs.[min_clr_time],qs.[max_clr_time],qs.[total_elapsed_time],qs.[last_elapsed_time],qs.[min_elapsed_time],qs.[max_elapsed_time] - FROM sys.dm_exec_query_stats qs - ORDER BY qs.total_logical_reads DESC) - INSERT INTO #dm_exec_query_stats ([sql_handle],[statement_start_offset],[statement_end_offset],[plan_generation_num],[plan_handle],[creation_time],[last_execution_time],[execution_count],[total_worker_time],[last_worker_time],[min_worker_time],[max_worker_time],[total_physical_reads],[last_physical_reads],[min_physical_reads],[max_physical_reads],[total_logical_writes],[last_logical_writes],[min_logical_writes],[max_logical_writes],[total_logical_reads],[last_logical_reads],[min_logical_reads],[max_logical_reads],[total_clr_time],[last_clr_time],[min_clr_time],[max_clr_time],[total_elapsed_time],[last_elapsed_time],[min_elapsed_time],[max_elapsed_time]) - SELECT qs.[sql_handle],qs.[statement_start_offset],qs.[statement_end_offset],qs.[plan_generation_num],qs.[plan_handle],qs.[creation_time],qs.[last_execution_time],qs.[execution_count],qs.[total_worker_time],qs.[last_worker_time],qs.[min_worker_time],qs.[max_worker_time],qs.[total_physical_reads],qs.[last_physical_reads],qs.[min_physical_reads],qs.[max_physical_reads],qs.[total_logical_writes],qs.[last_logical_writes],qs.[min_logical_writes],qs.[max_logical_writes],qs.[total_logical_reads],qs.[last_logical_reads],qs.[min_logical_reads],qs.[max_logical_reads],qs.[total_clr_time],qs.[last_clr_time],qs.[min_clr_time],qs.[max_clr_time],qs.[total_elapsed_time],qs.[last_elapsed_time],qs.[min_elapsed_time],qs.[max_elapsed_time] - FROM queries qs - LEFT OUTER JOIN #dm_exec_query_stats qsCaught ON qs.sql_handle = qsCaught.sql_handle AND qs.plan_handle = qsCaught.plan_handle AND qs.statement_start_offset = qsCaught.statement_start_offset - WHERE qsCaught.sql_handle IS NULL OPTION (RECOMPILE);'; - EXECUTE(@StringToExecute); - END; - - IF @CheckProcedureCacheFilter = 'ExecCount' - OR @CheckProcedureCacheFilter IS NULL - BEGIN - SET @StringToExecute = 'WITH queries ([sql_handle],[statement_start_offset],[statement_end_offset],[plan_generation_num],[plan_handle],[creation_time],[last_execution_time],[execution_count],[total_worker_time],[last_worker_time],[min_worker_time],[max_worker_time],[total_physical_reads],[last_physical_reads],[min_physical_reads],[max_physical_reads],[total_logical_writes],[last_logical_writes],[min_logical_writes],[max_logical_writes],[total_logical_reads],[last_logical_reads],[min_logical_reads],[max_logical_reads],[total_clr_time],[last_clr_time],[min_clr_time],[max_clr_time],[total_elapsed_time],[last_elapsed_time],[min_elapsed_time],[max_elapsed_time]) - AS (SELECT TOP 20 qs.[sql_handle],qs.[statement_start_offset],qs.[statement_end_offset],qs.[plan_generation_num],qs.[plan_handle],qs.[creation_time],qs.[last_execution_time],qs.[execution_count],qs.[total_worker_time],qs.[last_worker_time],qs.[min_worker_time],qs.[max_worker_time],qs.[total_physical_reads],qs.[last_physical_reads],qs.[min_physical_reads],qs.[max_physical_reads],qs.[total_logical_writes],qs.[last_logical_writes],qs.[min_logical_writes],qs.[max_logical_writes],qs.[total_logical_reads],qs.[last_logical_reads],qs.[min_logical_reads],qs.[max_logical_reads],qs.[total_clr_time],qs.[last_clr_time],qs.[min_clr_time],qs.[max_clr_time],qs.[total_elapsed_time],qs.[last_elapsed_time],qs.[min_elapsed_time],qs.[max_elapsed_time] - FROM sys.dm_exec_query_stats qs - ORDER BY qs.execution_count DESC) - INSERT INTO #dm_exec_query_stats ([sql_handle],[statement_start_offset],[statement_end_offset],[plan_generation_num],[plan_handle],[creation_time],[last_execution_time],[execution_count],[total_worker_time],[last_worker_time],[min_worker_time],[max_worker_time],[total_physical_reads],[last_physical_reads],[min_physical_reads],[max_physical_reads],[total_logical_writes],[last_logical_writes],[min_logical_writes],[max_logical_writes],[total_logical_reads],[last_logical_reads],[min_logical_reads],[max_logical_reads],[total_clr_time],[last_clr_time],[min_clr_time],[max_clr_time],[total_elapsed_time],[last_elapsed_time],[min_elapsed_time],[max_elapsed_time]) - SELECT qs.[sql_handle],qs.[statement_start_offset],qs.[statement_end_offset],qs.[plan_generation_num],qs.[plan_handle],qs.[creation_time],qs.[last_execution_time],qs.[execution_count],qs.[total_worker_time],qs.[last_worker_time],qs.[min_worker_time],qs.[max_worker_time],qs.[total_physical_reads],qs.[last_physical_reads],qs.[min_physical_reads],qs.[max_physical_reads],qs.[total_logical_writes],qs.[last_logical_writes],qs.[min_logical_writes],qs.[max_logical_writes],qs.[total_logical_reads],qs.[last_logical_reads],qs.[min_logical_reads],qs.[max_logical_reads],qs.[total_clr_time],qs.[last_clr_time],qs.[min_clr_time],qs.[max_clr_time],qs.[total_elapsed_time],qs.[last_elapsed_time],qs.[min_elapsed_time],qs.[max_elapsed_time] - FROM queries qs - LEFT OUTER JOIN #dm_exec_query_stats qsCaught ON qs.sql_handle = qsCaught.sql_handle AND qs.plan_handle = qsCaught.plan_handle AND qs.statement_start_offset = qsCaught.statement_start_offset - WHERE qsCaught.sql_handle IS NULL OPTION (RECOMPILE);'; - EXECUTE(@StringToExecute); - END; - - IF @CheckProcedureCacheFilter = 'Duration' - OR @CheckProcedureCacheFilter IS NULL - BEGIN - SET @StringToExecute = 'WITH queries ([sql_handle],[statement_start_offset],[statement_end_offset],[plan_generation_num],[plan_handle],[creation_time],[last_execution_time],[execution_count],[total_worker_time],[last_worker_time],[min_worker_time],[max_worker_time],[total_physical_reads],[last_physical_reads],[min_physical_reads],[max_physical_reads],[total_logical_writes],[last_logical_writes],[min_logical_writes],[max_logical_writes],[total_logical_reads],[last_logical_reads],[min_logical_reads],[max_logical_reads],[total_clr_time],[last_clr_time],[min_clr_time],[max_clr_time],[total_elapsed_time],[last_elapsed_time],[min_elapsed_time],[max_elapsed_time]) - AS (SELECT TOP 20 qs.[sql_handle],qs.[statement_start_offset],qs.[statement_end_offset],qs.[plan_generation_num],qs.[plan_handle],qs.[creation_time],qs.[last_execution_time],qs.[execution_count],qs.[total_worker_time],qs.[last_worker_time],qs.[min_worker_time],qs.[max_worker_time],qs.[total_physical_reads],qs.[last_physical_reads],qs.[min_physical_reads],qs.[max_physical_reads],qs.[total_logical_writes],qs.[last_logical_writes],qs.[min_logical_writes],qs.[max_logical_writes],qs.[total_logical_reads],qs.[last_logical_reads],qs.[min_logical_reads],qs.[max_logical_reads],qs.[total_clr_time],qs.[last_clr_time],qs.[min_clr_time],qs.[max_clr_time],qs.[total_elapsed_time],qs.[last_elapsed_time],qs.[min_elapsed_time],qs.[max_elapsed_time] - FROM sys.dm_exec_query_stats qs - ORDER BY qs.total_elapsed_time DESC) - INSERT INTO #dm_exec_query_stats ([sql_handle],[statement_start_offset],[statement_end_offset],[plan_generation_num],[plan_handle],[creation_time],[last_execution_time],[execution_count],[total_worker_time],[last_worker_time],[min_worker_time],[max_worker_time],[total_physical_reads],[last_physical_reads],[min_physical_reads],[max_physical_reads],[total_logical_writes],[last_logical_writes],[min_logical_writes],[max_logical_writes],[total_logical_reads],[last_logical_reads],[min_logical_reads],[max_logical_reads],[total_clr_time],[last_clr_time],[min_clr_time],[max_clr_time],[total_elapsed_time],[last_elapsed_time],[min_elapsed_time],[max_elapsed_time]) - SELECT qs.[sql_handle],qs.[statement_start_offset],qs.[statement_end_offset],qs.[plan_generation_num],qs.[plan_handle],qs.[creation_time],qs.[last_execution_time],qs.[execution_count],qs.[total_worker_time],qs.[last_worker_time],qs.[min_worker_time],qs.[max_worker_time],qs.[total_physical_reads],qs.[last_physical_reads],qs.[min_physical_reads],qs.[max_physical_reads],qs.[total_logical_writes],qs.[last_logical_writes],qs.[min_logical_writes],qs.[max_logical_writes],qs.[total_logical_reads],qs.[last_logical_reads],qs.[min_logical_reads],qs.[max_logical_reads],qs.[total_clr_time],qs.[last_clr_time],qs.[min_clr_time],qs.[max_clr_time],qs.[total_elapsed_time],qs.[last_elapsed_time],qs.[min_elapsed_time],qs.[max_elapsed_time] - FROM queries qs - LEFT OUTER JOIN #dm_exec_query_stats qsCaught ON qs.sql_handle = qsCaught.sql_handle AND qs.plan_handle = qsCaught.plan_handle AND qs.statement_start_offset = qsCaught.statement_start_offset - WHERE qsCaught.sql_handle IS NULL OPTION (RECOMPILE);'; - EXECUTE(@StringToExecute); - END; - - END; - IF @ProductVersionMajor >= 10 - BEGIN - IF @CheckProcedureCacheFilter = 'CPU' - OR @CheckProcedureCacheFilter IS NULL - BEGIN - SET @StringToExecute = 'WITH queries ([sql_handle],[statement_start_offset],[statement_end_offset],[plan_generation_num],[plan_handle],[creation_time],[last_execution_time],[execution_count],[total_worker_time],[last_worker_time],[min_worker_time],[max_worker_time],[total_physical_reads],[last_physical_reads],[min_physical_reads],[max_physical_reads],[total_logical_writes],[last_logical_writes],[min_logical_writes],[max_logical_writes],[total_logical_reads],[last_logical_reads],[min_logical_reads],[max_logical_reads],[total_clr_time],[last_clr_time],[min_clr_time],[max_clr_time],[total_elapsed_time],[last_elapsed_time],[min_elapsed_time],[max_elapsed_time],[query_hash],[query_plan_hash]) - AS (SELECT TOP 20 qs.[sql_handle],qs.[statement_start_offset],qs.[statement_end_offset],qs.[plan_generation_num],qs.[plan_handle],qs.[creation_time],qs.[last_execution_time],qs.[execution_count],qs.[total_worker_time],qs.[last_worker_time],qs.[min_worker_time],qs.[max_worker_time],qs.[total_physical_reads],qs.[last_physical_reads],qs.[min_physical_reads],qs.[max_physical_reads],qs.[total_logical_writes],qs.[last_logical_writes],qs.[min_logical_writes],qs.[max_logical_writes],qs.[total_logical_reads],qs.[last_logical_reads],qs.[min_logical_reads],qs.[max_logical_reads],qs.[total_clr_time],qs.[last_clr_time],qs.[min_clr_time],qs.[max_clr_time],qs.[total_elapsed_time],qs.[last_elapsed_time],qs.[min_elapsed_time],qs.[max_elapsed_time],qs.[query_hash],qs.[query_plan_hash] - FROM sys.dm_exec_query_stats qs - ORDER BY qs.total_worker_time DESC) - INSERT INTO #dm_exec_query_stats ([sql_handle],[statement_start_offset],[statement_end_offset],[plan_generation_num],[plan_handle],[creation_time],[last_execution_time],[execution_count],[total_worker_time],[last_worker_time],[min_worker_time],[max_worker_time],[total_physical_reads],[last_physical_reads],[min_physical_reads],[max_physical_reads],[total_logical_writes],[last_logical_writes],[min_logical_writes],[max_logical_writes],[total_logical_reads],[last_logical_reads],[min_logical_reads],[max_logical_reads],[total_clr_time],[last_clr_time],[min_clr_time],[max_clr_time],[total_elapsed_time],[last_elapsed_time],[min_elapsed_time],[max_elapsed_time],[query_hash],[query_plan_hash]) - SELECT qs.[sql_handle],qs.[statement_start_offset],qs.[statement_end_offset],qs.[plan_generation_num],qs.[plan_handle],qs.[creation_time],qs.[last_execution_time],qs.[execution_count],qs.[total_worker_time],qs.[last_worker_time],qs.[min_worker_time],qs.[max_worker_time],qs.[total_physical_reads],qs.[last_physical_reads],qs.[min_physical_reads],qs.[max_physical_reads],qs.[total_logical_writes],qs.[last_logical_writes],qs.[min_logical_writes],qs.[max_logical_writes],qs.[total_logical_reads],qs.[last_logical_reads],qs.[min_logical_reads],qs.[max_logical_reads],qs.[total_clr_time],qs.[last_clr_time],qs.[min_clr_time],qs.[max_clr_time],qs.[total_elapsed_time],qs.[last_elapsed_time],qs.[min_elapsed_time],qs.[max_elapsed_time],qs.[query_hash],qs.[query_plan_hash] - FROM queries qs - LEFT OUTER JOIN #dm_exec_query_stats qsCaught ON qs.sql_handle = qsCaught.sql_handle AND qs.plan_handle = qsCaught.plan_handle AND qs.statement_start_offset = qsCaught.statement_start_offset - WHERE qsCaught.sql_handle IS NULL OPTION (RECOMPILE);'; - EXECUTE(@StringToExecute); - END; - - IF @CheckProcedureCacheFilter = 'Reads' - OR @CheckProcedureCacheFilter IS NULL - BEGIN - SET @StringToExecute = 'WITH queries ([sql_handle],[statement_start_offset],[statement_end_offset],[plan_generation_num],[plan_handle],[creation_time],[last_execution_time],[execution_count],[total_worker_time],[last_worker_time],[min_worker_time],[max_worker_time],[total_physical_reads],[last_physical_reads],[min_physical_reads],[max_physical_reads],[total_logical_writes],[last_logical_writes],[min_logical_writes],[max_logical_writes],[total_logical_reads],[last_logical_reads],[min_logical_reads],[max_logical_reads],[total_clr_time],[last_clr_time],[min_clr_time],[max_clr_time],[total_elapsed_time],[last_elapsed_time],[min_elapsed_time],[max_elapsed_time],[query_hash],[query_plan_hash]) - AS (SELECT TOP 20 qs.[sql_handle],qs.[statement_start_offset],qs.[statement_end_offset],qs.[plan_generation_num],qs.[plan_handle],qs.[creation_time],qs.[last_execution_time],qs.[execution_count],qs.[total_worker_time],qs.[last_worker_time],qs.[min_worker_time],qs.[max_worker_time],qs.[total_physical_reads],qs.[last_physical_reads],qs.[min_physical_reads],qs.[max_physical_reads],qs.[total_logical_writes],qs.[last_logical_writes],qs.[min_logical_writes],qs.[max_logical_writes],qs.[total_logical_reads],qs.[last_logical_reads],qs.[min_logical_reads],qs.[max_logical_reads],qs.[total_clr_time],qs.[last_clr_time],qs.[min_clr_time],qs.[max_clr_time],qs.[total_elapsed_time],qs.[last_elapsed_time],qs.[min_elapsed_time],qs.[max_elapsed_time],qs.[query_hash],qs.[query_plan_hash] - FROM sys.dm_exec_query_stats qs - ORDER BY qs.total_logical_reads DESC) - INSERT INTO #dm_exec_query_stats ([sql_handle],[statement_start_offset],[statement_end_offset],[plan_generation_num],[plan_handle],[creation_time],[last_execution_time],[execution_count],[total_worker_time],[last_worker_time],[min_worker_time],[max_worker_time],[total_physical_reads],[last_physical_reads],[min_physical_reads],[max_physical_reads],[total_logical_writes],[last_logical_writes],[min_logical_writes],[max_logical_writes],[total_logical_reads],[last_logical_reads],[min_logical_reads],[max_logical_reads],[total_clr_time],[last_clr_time],[min_clr_time],[max_clr_time],[total_elapsed_time],[last_elapsed_time],[min_elapsed_time],[max_elapsed_time],[query_hash],[query_plan_hash]) - SELECT qs.[sql_handle],qs.[statement_start_offset],qs.[statement_end_offset],qs.[plan_generation_num],qs.[plan_handle],qs.[creation_time],qs.[last_execution_time],qs.[execution_count],qs.[total_worker_time],qs.[last_worker_time],qs.[min_worker_time],qs.[max_worker_time],qs.[total_physical_reads],qs.[last_physical_reads],qs.[min_physical_reads],qs.[max_physical_reads],qs.[total_logical_writes],qs.[last_logical_writes],qs.[min_logical_writes],qs.[max_logical_writes],qs.[total_logical_reads],qs.[last_logical_reads],qs.[min_logical_reads],qs.[max_logical_reads],qs.[total_clr_time],qs.[last_clr_time],qs.[min_clr_time],qs.[max_clr_time],qs.[total_elapsed_time],qs.[last_elapsed_time],qs.[min_elapsed_time],qs.[max_elapsed_time],qs.[query_hash],qs.[query_plan_hash] - FROM queries qs - LEFT OUTER JOIN #dm_exec_query_stats qsCaught ON qs.sql_handle = qsCaught.sql_handle AND qs.plan_handle = qsCaught.plan_handle AND qs.statement_start_offset = qsCaught.statement_start_offset - WHERE qsCaught.sql_handle IS NULL OPTION (RECOMPILE);'; - EXECUTE(@StringToExecute); - END; - - IF @CheckProcedureCacheFilter = 'ExecCount' - OR @CheckProcedureCacheFilter IS NULL - BEGIN - SET @StringToExecute = 'WITH queries ([sql_handle],[statement_start_offset],[statement_end_offset],[plan_generation_num],[plan_handle],[creation_time],[last_execution_time],[execution_count],[total_worker_time],[last_worker_time],[min_worker_time],[max_worker_time],[total_physical_reads],[last_physical_reads],[min_physical_reads],[max_physical_reads],[total_logical_writes],[last_logical_writes],[min_logical_writes],[max_logical_writes],[total_logical_reads],[last_logical_reads],[min_logical_reads],[max_logical_reads],[total_clr_time],[last_clr_time],[min_clr_time],[max_clr_time],[total_elapsed_time],[last_elapsed_time],[min_elapsed_time],[max_elapsed_time],[query_hash],[query_plan_hash]) - AS (SELECT TOP 20 qs.[sql_handle],qs.[statement_start_offset],qs.[statement_end_offset],qs.[plan_generation_num],qs.[plan_handle],qs.[creation_time],qs.[last_execution_time],qs.[execution_count],qs.[total_worker_time],qs.[last_worker_time],qs.[min_worker_time],qs.[max_worker_time],qs.[total_physical_reads],qs.[last_physical_reads],qs.[min_physical_reads],qs.[max_physical_reads],qs.[total_logical_writes],qs.[last_logical_writes],qs.[min_logical_writes],qs.[max_logical_writes],qs.[total_logical_reads],qs.[last_logical_reads],qs.[min_logical_reads],qs.[max_logical_reads],qs.[total_clr_time],qs.[last_clr_time],qs.[min_clr_time],qs.[max_clr_time],qs.[total_elapsed_time],qs.[last_elapsed_time],qs.[min_elapsed_time],qs.[max_elapsed_time],qs.[query_hash],qs.[query_plan_hash] - FROM sys.dm_exec_query_stats qs - ORDER BY qs.execution_count DESC) - INSERT INTO #dm_exec_query_stats ([sql_handle],[statement_start_offset],[statement_end_offset],[plan_generation_num],[plan_handle],[creation_time],[last_execution_time],[execution_count],[total_worker_time],[last_worker_time],[min_worker_time],[max_worker_time],[total_physical_reads],[last_physical_reads],[min_physical_reads],[max_physical_reads],[total_logical_writes],[last_logical_writes],[min_logical_writes],[max_logical_writes],[total_logical_reads],[last_logical_reads],[min_logical_reads],[max_logical_reads],[total_clr_time],[last_clr_time],[min_clr_time],[max_clr_time],[total_elapsed_time],[last_elapsed_time],[min_elapsed_time],[max_elapsed_time],[query_hash],[query_plan_hash]) - SELECT qs.[sql_handle],qs.[statement_start_offset],qs.[statement_end_offset],qs.[plan_generation_num],qs.[plan_handle],qs.[creation_time],qs.[last_execution_time],qs.[execution_count],qs.[total_worker_time],qs.[last_worker_time],qs.[min_worker_time],qs.[max_worker_time],qs.[total_physical_reads],qs.[last_physical_reads],qs.[min_physical_reads],qs.[max_physical_reads],qs.[total_logical_writes],qs.[last_logical_writes],qs.[min_logical_writes],qs.[max_logical_writes],qs.[total_logical_reads],qs.[last_logical_reads],qs.[min_logical_reads],qs.[max_logical_reads],qs.[total_clr_time],qs.[last_clr_time],qs.[min_clr_time],qs.[max_clr_time],qs.[total_elapsed_time],qs.[last_elapsed_time],qs.[min_elapsed_time],qs.[max_elapsed_time],qs.[query_hash],qs.[query_plan_hash] - FROM queries qs - LEFT OUTER JOIN #dm_exec_query_stats qsCaught ON qs.sql_handle = qsCaught.sql_handle AND qs.plan_handle = qsCaught.plan_handle AND qs.statement_start_offset = qsCaught.statement_start_offset - WHERE qsCaught.sql_handle IS NULL OPTION (RECOMPILE);'; - EXECUTE(@StringToExecute); - END; - - IF @CheckProcedureCacheFilter = 'Duration' - OR @CheckProcedureCacheFilter IS NULL - BEGIN - SET @StringToExecute = 'WITH queries ([sql_handle],[statement_start_offset],[statement_end_offset],[plan_generation_num],[plan_handle],[creation_time],[last_execution_time],[execution_count],[total_worker_time],[last_worker_time],[min_worker_time],[max_worker_time],[total_physical_reads],[last_physical_reads],[min_physical_reads],[max_physical_reads],[total_logical_writes],[last_logical_writes],[min_logical_writes],[max_logical_writes],[total_logical_reads],[last_logical_reads],[min_logical_reads],[max_logical_reads],[total_clr_time],[last_clr_time],[min_clr_time],[max_clr_time],[total_elapsed_time],[last_elapsed_time],[min_elapsed_time],[max_elapsed_time],[query_hash],[query_plan_hash]) - AS (SELECT TOP 20 qs.[sql_handle],qs.[statement_start_offset],qs.[statement_end_offset],qs.[plan_generation_num],qs.[plan_handle],qs.[creation_time],qs.[last_execution_time],qs.[execution_count],qs.[total_worker_time],qs.[last_worker_time],qs.[min_worker_time],qs.[max_worker_time],qs.[total_physical_reads],qs.[last_physical_reads],qs.[min_physical_reads],qs.[max_physical_reads],qs.[total_logical_writes],qs.[last_logical_writes],qs.[min_logical_writes],qs.[max_logical_writes],qs.[total_logical_reads],qs.[last_logical_reads],qs.[min_logical_reads],qs.[max_logical_reads],qs.[total_clr_time],qs.[last_clr_time],qs.[min_clr_time],qs.[max_clr_time],qs.[total_elapsed_time],qs.[last_elapsed_time],qs.[min_elapsed_time],qs.[max_elapsed_time],qs.[query_hash],qs.[query_plan_hash] - FROM sys.dm_exec_query_stats qs - ORDER BY qs.total_elapsed_time DESC) - INSERT INTO #dm_exec_query_stats ([sql_handle],[statement_start_offset],[statement_end_offset],[plan_generation_num],[plan_handle],[creation_time],[last_execution_time],[execution_count],[total_worker_time],[last_worker_time],[min_worker_time],[max_worker_time],[total_physical_reads],[last_physical_reads],[min_physical_reads],[max_physical_reads],[total_logical_writes],[last_logical_writes],[min_logical_writes],[max_logical_writes],[total_logical_reads],[last_logical_reads],[min_logical_reads],[max_logical_reads],[total_clr_time],[last_clr_time],[min_clr_time],[max_clr_time],[total_elapsed_time],[last_elapsed_time],[min_elapsed_time],[max_elapsed_time],[query_hash],[query_plan_hash]) - SELECT qs.[sql_handle],qs.[statement_start_offset],qs.[statement_end_offset],qs.[plan_generation_num],qs.[plan_handle],qs.[creation_time],qs.[last_execution_time],qs.[execution_count],qs.[total_worker_time],qs.[last_worker_time],qs.[min_worker_time],qs.[max_worker_time],qs.[total_physical_reads],qs.[last_physical_reads],qs.[min_physical_reads],qs.[max_physical_reads],qs.[total_logical_writes],qs.[last_logical_writes],qs.[min_logical_writes],qs.[max_logical_writes],qs.[total_logical_reads],qs.[last_logical_reads],qs.[min_logical_reads],qs.[max_logical_reads],qs.[total_clr_time],qs.[last_clr_time],qs.[min_clr_time],qs.[max_clr_time],qs.[total_elapsed_time],qs.[last_elapsed_time],qs.[min_elapsed_time],qs.[max_elapsed_time],qs.[query_hash],qs.[query_plan_hash] - FROM queries qs - LEFT OUTER JOIN #dm_exec_query_stats qsCaught ON qs.sql_handle = qsCaught.sql_handle AND qs.plan_handle = qsCaught.plan_handle AND qs.statement_start_offset = qsCaught.statement_start_offset - WHERE qsCaught.sql_handle IS NULL OPTION (RECOMPILE);'; - EXECUTE(@StringToExecute); - END; - - /* Populate the query_plan_filtered field. Only works in 2005SP2+, but we're just doing it in 2008 to be safe. */ - UPDATE #dm_exec_query_stats - SET query_plan_filtered = qp.query_plan - FROM #dm_exec_query_stats qs - CROSS APPLY sys.dm_exec_text_query_plan(qs.plan_handle, - qs.statement_start_offset, - qs.statement_end_offset) - AS qp; - - END; - - /* Populate the additional query_plan, text, and text_filtered fields */ - UPDATE #dm_exec_query_stats - SET query_plan = qp.query_plan , - [text] = st.[text] , - text_filtered = SUBSTRING(st.text, - ( qs.statement_start_offset - / 2 ) + 1, - ( ( CASE qs.statement_end_offset - WHEN -1 - THEN DATALENGTH(st.text) - ELSE qs.statement_end_offset - END - - qs.statement_start_offset ) - / 2 ) + 1) - FROM #dm_exec_query_stats qs - CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) AS st - CROSS APPLY sys.dm_exec_query_plan(qs.plan_handle) - AS qp; - - /* Dump instances of our own script. We're not trying to tune ourselves. */ - DELETE #dm_exec_query_stats - WHERE text LIKE '%sp_Blitz%' - OR text LIKE '%#BlitzResults%'; - - /* Look for implicit conversions */ - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 63 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 63) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details , - QueryPlan , - QueryPlanFiltered - ) - SELECT 63 AS CheckID , - 120 AS Priority , - 'Query Plans' AS FindingsGroup , - 'Implicit Conversion' AS Finding , - 'https://www.brentozar.com/go/implicit' AS URL , - ( 'One of the top resource-intensive queries is comparing two fields that are not the same datatype.' ) AS Details , - qs.query_plan , - qs.query_plan_filtered - FROM #dm_exec_query_stats qs - WHERE COALESCE(qs.query_plan_filtered, - CAST(qs.query_plan AS NVARCHAR(MAX))) LIKE '%CONVERT_IMPLICIT%' - AND COALESCE(qs.query_plan_filtered, - CAST(qs.query_plan AS NVARCHAR(MAX))) LIKE '%PhysicalOp="Index Scan"%'; - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 64 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 64) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details , - QueryPlan , - QueryPlanFiltered - ) - SELECT 64 AS CheckID , - 120 AS Priority , - 'Query Plans' AS FindingsGroup , - 'Implicit Conversion Affecting Cardinality' AS Finding , - 'https://www.brentozar.com/go/implicit' AS URL , - ( 'One of the top resource-intensive queries has an implicit conversion that is affecting cardinality estimation.' ) AS Details , - qs.query_plan , - qs.query_plan_filtered - FROM #dm_exec_query_stats qs - WHERE COALESCE(qs.query_plan_filtered, - CAST(qs.query_plan AS NVARCHAR(MAX))) LIKE '%= 10 - AND NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 187 ) - - IF SERVERPROPERTY('IsHadrEnabled') = 1 - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 187) WITH NOWAIT; - - INSERT INTO [#BlitzResults] - ( [CheckID] , - [Priority] , - [FindingsGroup] , - [Finding] , - [URL] , - [Details] ) - SELECT - 187 AS [CheckID] , - 230 AS [Priority] , - 'Security' AS [FindingsGroup] , - 'Endpoints Owned by Users' AS [Finding] , - 'https://www.brentozar.com/go/owners' AS [URL] , - ( 'Endpoint ' + ep.[name] + ' is owned by ' + SUSER_NAME(ep.principal_id) + '. If the endpoint owner login is disabled or not available due to Active Directory problems, the high availability will stop working.' - ) AS [Details] - FROM sys.database_mirroring_endpoints ep - LEFT OUTER JOIN sys.dm_server_services s ON SUSER_NAME(ep.principal_id) = s.service_account - WHERE s.service_account IS NULL AND ep.principal_id <> 1; - END; - - - /*Verify that the servername is set */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 70 ) - BEGIN - IF @@SERVERNAME IS NULL - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 70) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 70 AS CheckID , - 200 AS Priority , - 'Informational' AS FindingsGroup , - '@@Servername Not Set' AS Finding , - 'https://www.brentozar.com/go/servername' AS URL , - '@@Servername variable is null. You can fix it by executing: "sp_addserver '''', local"' AS Details; - END; - - IF /* @@SERVERNAME IS set */ - (@@SERVERNAME IS NOT NULL - AND - /* not a named instance */ - CHARINDEX(CHAR(92),CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128))) = 0 - AND - /* not clustered, when computername may be different than the servername */ - SERVERPROPERTY('IsClustered') = 0 - AND - /* @@SERVERNAME is different than the computer name */ - @@SERVERNAME <> CAST(ISNULL(SERVERPROPERTY('ComputerNamePhysicalNetBIOS'),@@SERVERNAME) AS NVARCHAR(128)) ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 70) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 70 AS CheckID , - 200 AS Priority , - 'Configuration' AS FindingsGroup , - '@@Servername Not Correct' AS Finding , - 'https://www.brentozar.com/go/servername' AS URL , - 'The @@Servername is different than the computer name, which may trigger certificate errors.' AS Details; - END; - - END; - /*Check to see if a failsafe operator has been configured*/ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 73 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 73) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 73 AS CheckID , - 200 AS Priority , - 'Monitoring' AS FindingsGroup , - 'No Failsafe Operator Configured' AS Finding , - 'https://www.brentozar.com/go/failsafe' AS URL , - ( 'No failsafe operator is configured on this server. This is a good idea just in-case there are issues with the [msdb] database that prevents alerting.' ) AS Details - FROM #AlertInfo - WHERE FailSafeOperator IS NULL; - END; - - /*Identify globally enabled trace flags*/ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 74 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 74) WITH NOWAIT; - - INSERT INTO #TraceStatus - EXEC ( ' DBCC TRACESTATUS(-1) WITH NO_INFOMSGS' - ); - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 74 AS CheckID , - 200 AS Priority , - 'Informational' AS FindingsGroup , - 'Trace Flag On' AS Finding , - CASE WHEN [T].[TraceFlag] = '834' AND @ColumnStoreIndexesInUse = 1 THEN 'https://support.microsoft.com/en-us/kb/3210239' - ELSE'https://www.BrentOzar.com/go/traceflags/' END AS URL , - 'Trace flag ' + - CASE WHEN [T].[TraceFlag] = '652' THEN '652 enabled globally, which disables pre-fetching during index scans. This is usually a very bad idea.' - WHEN [T].[TraceFlag] = '661' THEN '661 enabled globally, which disables ghost record removal, causing the database to grow in size. This is usually a very bad idea.' - WHEN [T].[TraceFlag] = '834' AND @ColumnStoreIndexesInUse = 1 THEN '834 is enabled globally, but you also have columnstore indexes. That combination is not recommended by Microsoft.' - WHEN [T].[TraceFlag] = '1117' THEN '1117 enabled globally, which grows all files in a filegroup at the same time.' - WHEN [T].[TraceFlag] = '1118' THEN '1118 enabled globally, which tries to reduce SGAM waits.' - WHEN [T].[TraceFlag] = '1211' THEN '1211 enabled globally, which disables lock escalation when you least expect it. This is usually a very bad idea.' - WHEN [T].[TraceFlag] = '1204' THEN '1204 enabled globally, which captures deadlock graphs in the error log.' - WHEN [T].[TraceFlag] = '1222' THEN '1222 enabled globally, which captures deadlock graphs in the error log.' - WHEN [T].[TraceFlag] = '1224' THEN '1224 enabled globally, which disables lock escalation until the server has memory pressure. This is usually a very bad idea.' - WHEN [T].[TraceFlag] = '1806' THEN '1806 enabled globally, which disables Instant File Initialization, causing restores and file growths to take longer. This is usually a very bad idea.' - WHEN [T].[TraceFlag] = '2330' THEN '2330 enabled globally, which disables missing index requests. This is usually a very bad idea.' - WHEN [T].[TraceFlag] = '2371' THEN '2371 enabled globally, which changes the auto update stats threshold.' - WHEN [T].[TraceFlag] = '3023' THEN '3023 enabled globally, which performs checksums by default when doing database backups.' - WHEN [T].[TraceFlag] = '3226' THEN '3226 enabled globally, which keeps the event log clean by not reporting successful backups.' - WHEN [T].[TraceFlag] = '3505' THEN '3505 enabled globally, which disables Checkpoints. This is usually a very bad idea.' - WHEN [T].[TraceFlag] = '4199' THEN '4199 enabled globally, which enables non-default Query Optimizer fixes, changing query plans from the default behaviors.' - WHEN [T].[TraceFlag] = '8048' THEN '8048 enabled globally, which tries to reduce CMEMTHREAD waits on servers with a lot of logical processors.' - WHEN [T].[TraceFlag] = '8017' AND (CAST(SERVERPROPERTY('Edition') AS NVARCHAR(1000)) LIKE N'%Express%') THEN '8017 is enabled globally, but this is the default for Express Edition.' - WHEN [T].[TraceFlag] = '8017' AND (CAST(SERVERPROPERTY('Edition') AS NVARCHAR(1000)) NOT LIKE N'%Express%') THEN '8017 is enabled globally, which disables the creation of schedulers for all logical processors.' - WHEN [T].[TraceFlag] = '8649' THEN '8649 enabled globally, which cost threshold for parallelism down to 0. This is usually a very bad idea.' - ELSE [T].[TraceFlag] + ' is enabled globally.' END - AS Details - FROM #TraceStatus T; - END; - - /* High CMEMTHREAD waits that could need trace flag 8048. - This check has to be run AFTER the globally enabled trace flag check, - since it uses the #TraceStatus table to know if flags are enabled. - */ - IF @ProductVersionMajor >= 11 AND NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 162 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 162) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 162 AS CheckID , - 50 AS Priority , - 'Performance' AS FindingGroup , - 'Poison Wait Detected: CMEMTHREAD & NUMA' AS Finding , - 'https://www.brentozar.com/go/poison' AS URL , - CONVERT(VARCHAR(10), (MAX([wait_time_ms]) / 1000) / 86400) + ':' + CONVERT(VARCHAR(20), DATEADD(s, (MAX([wait_time_ms]) / 1000), 0), 108) + ' of this wait have been recorded' - + CASE WHEN ts.status = 1 THEN ' despite enabling trace flag 8048 already.' - ELSE '. In servers with over 8 cores per NUMA node, when CMEMTHREAD waits are a bottleneck, trace flag 8048 may be needed.' - END - FROM sys.dm_os_nodes n - INNER JOIN sys.[dm_os_wait_stats] w ON w.wait_type = 'CMEMTHREAD' - LEFT OUTER JOIN #TraceStatus ts ON ts.TraceFlag = 8048 AND ts.status = 1 - WHERE n.node_id = 0 AND n.online_scheduler_count >= 8 - AND EXISTS (SELECT * FROM sys.dm_os_nodes WHERE node_id > 0 AND node_state_desc NOT LIKE '%DAC') - GROUP BY w.wait_type, ts.status - HAVING SUM([wait_time_ms]) > (SELECT 5000 * datediff(HH,create_date,CURRENT_TIMESTAMP) AS hours_since_startup FROM sys.databases WHERE name='tempdb') - AND SUM([wait_time_ms]) > 60000; - END; - - - /*Check for transaction log file larger than data file */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 75 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 75) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 75 AS CheckID , - DB_NAME(a.database_id) , - 50 AS Priority , - 'Reliability' AS FindingsGroup , - 'Transaction Log Larger than Data File' AS Finding , - 'https://www.brentozar.com/go/biglog' AS URL , - 'The database [' + DB_NAME(a.database_id) - + '] has a ' + CAST((CAST(a.size AS BIGINT) * 8 / 1000000) AS NVARCHAR(20)) + ' GB transaction log file, larger than the total data file sizes. This may indicate that transaction log backups are not being performed or not performed often enough.' AS Details - FROM sys.master_files a - WHERE a.type = 1 - AND DB_NAME(a.database_id) NOT IN ( - SELECT DISTINCT - DatabaseName - FROM #SkipChecks - WHERE CheckID = 75 OR CheckID IS NULL) - AND a.size > 125000 /* Size is measured in pages here, so this gets us log files over 1GB. */ - AND a.size > ( SELECT SUM(CAST(b.size AS BIGINT)) - FROM sys.master_files b - WHERE a.database_id = b.database_id - AND b.type = 0 - ) - AND a.database_id IN ( - SELECT database_id - FROM sys.databases - WHERE source_database_id IS NULL ); - END; - - /*Check for collation conflicts between user databases and tempdb */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 76 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 76) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 76 AS CheckID , - name AS DatabaseName , - 200 AS Priority , - 'Informational' AS FindingsGroup , - 'Collation is ' + collation_name AS Finding , - 'https://www.brentozar.com/go/collate' AS URL , - 'Collation differences between user databases and tempdb can cause conflicts especially when comparing string values' AS Details - FROM sys.databases - WHERE name NOT IN ( 'master', 'model', 'msdb') - AND name NOT LIKE 'ReportServer%' - AND name NOT IN ( SELECT DISTINCT - DatabaseName - FROM #SkipChecks - WHERE CheckID IS NULL OR CheckID = 76) - AND collation_name <> ( SELECT - collation_name - FROM - sys.databases - WHERE - name = 'tempdb' - ); - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 77 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 77) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - DatabaseName , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 77 AS CheckID , - dSnap.[name] AS DatabaseName , - 170 AS Priority , - 'Reliability' AS FindingsGroup , - 'Database Snapshot Online' AS Finding , - 'https://www.brentozar.com/go/snapshot' AS URL , - 'Database [' + dSnap.[name] - + '] is a snapshot of [' - + dOriginal.[name] - + ']. Make sure you have enough drive space to maintain the snapshot as the original database grows.' AS Details - FROM sys.databases dSnap - INNER JOIN sys.databases dOriginal ON dSnap.source_database_id = dOriginal.database_id - AND dSnap.name NOT IN ( - SELECT DISTINCT DatabaseName - FROM #SkipChecks - WHERE CheckID = 77 OR CheckID IS NULL); - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 79 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 79) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 79 AS CheckID , - -- sp_Blitz Issue #776 - -- Job has history and was executed in the last 30 days OR Job is enabled AND Job Schedule is enabled - CASE WHEN (cast(datediff(dd, substring(cast(sjh.run_date as nvarchar(10)), 1, 4) + '-' + substring(cast(sjh.run_date as nvarchar(10)), 5, 2) + '-' + substring(cast(sjh.run_date as nvarchar(10)), 7, 2), GETDATE()) AS INT) < 30) OR (j.[enabled] = 1 AND ssc.[enabled] = 1 )THEN - 100 - ELSE -- no job history (implicit) AND job not run in the past 30 days AND (Job disabled OR Job Schedule disabled) - 200 - END AS Priority, - 'Performance' AS FindingsGroup , - 'Shrink Database Job' AS Finding , - 'https://www.brentozar.com/go/autoshrink' AS URL , - 'In the [' + j.[name] + '] job, step [' - + step.[step_name] - + '] has SHRINKDATABASE or SHRINKFILE, which may be causing database fragmentation.' - + CASE WHEN COALESCE(ssc.name,'0') != '0' THEN + ' (Schedule: [' + ssc.name + '])' ELSE + '' END AS Details - FROM msdb.dbo.sysjobs j - INNER JOIN msdb.dbo.sysjobsteps step ON j.job_id = step.job_id - LEFT OUTER JOIN msdb.dbo.sysjobschedules AS sjsc - ON j.job_id = sjsc.job_id - LEFT OUTER JOIN msdb.dbo.sysschedules AS ssc - ON sjsc.schedule_id = ssc.schedule_id - AND sjsc.job_id = j.job_id - LEFT OUTER JOIN msdb.dbo.sysjobhistory AS sjh - ON j.job_id = sjh.job_id - AND step.step_id = sjh.step_id - AND sjh.run_date IN (SELECT max(sjh2.run_date) FROM msdb.dbo.sysjobhistory AS sjh2 WHERE sjh2.job_id = j.job_id) -- get the latest entry date - AND sjh.run_time IN (SELECT max(sjh3.run_time) FROM msdb.dbo.sysjobhistory AS sjh3 WHERE sjh3.job_id = j.job_id AND sjh3.run_date = sjh.run_date) -- get the latest entry time - WHERE step.command LIKE N'%SHRINKDATABASE%' - OR step.command LIKE N'%SHRINKFILE%'; - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 81 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 81) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 81 AS CheckID , - 200 AS Priority , - 'Non-Active Server Config' AS FindingsGroup , - cr.name AS Finding , - 'https://www.BrentOzar.com/blitz/sp_configure/' AS URL , - ( 'This sp_configure option isn''t running under its set value. Its set value is ' - + CAST(cr.[value] AS VARCHAR(100)) - + ' and its running value is ' - + CAST(cr.value_in_use AS VARCHAR(100)) - + '. When someone does a RECONFIGURE or restarts the instance, this setting will start taking effect.' ) AS Details - FROM sys.configurations cr - WHERE cr.value <> cr.value_in_use - AND NOT (cr.name = 'min server memory (MB)' AND cr.value IN (0,16) AND cr.value_in_use IN (0,16)); - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 123 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 123) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT TOP 1 123 AS CheckID , - 200 AS Priority , - 'Informational' AS FindingsGroup , - 'Agent Jobs Starting Simultaneously' AS Finding , - 'https://www.brentozar.com/go/busyagent/' AS URL , - ( 'Multiple SQL Server Agent jobs are configured to start simultaneously. For detailed schedule listings, see the query in the URL.' ) AS Details - FROM msdb.dbo.sysjobactivity - WHERE start_execution_date > DATEADD(dd, -14, GETDATE()) - GROUP BY start_execution_date HAVING COUNT(*) > 1; - END; - - IF @CheckServerInfo = 1 - BEGIN - -/*This checks Windows version. It would be better if Microsoft gave everything a separate build number, but whatever.*/ -IF @ProductVersionMajor >= 10 - AND NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 172 ) - BEGIN - -- sys.dm_os_host_info includes both Windows and Linux info - IF EXISTS (SELECT 1 - FROM sys.all_objects - WHERE name = 'dm_os_host_info' ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 172) WITH NOWAIT; - - INSERT INTO [#BlitzResults] - ( [CheckID] , - [Priority] , - [FindingsGroup] , - [Finding] , - [URL] , - [Details] ) - - SELECT - 172 AS [CheckID] , - 250 AS [Priority] , - 'Server Info' AS [FindingsGroup] , - 'Operating System Version' AS [Finding] , - ( CASE WHEN @IsWindowsOperatingSystem = 1 - THEN 'https://en.wikipedia.org/wiki/List_of_Microsoft_Windows_versions' - ELSE 'https://en.wikipedia.org/wiki/List_of_Linux_distributions' - END - ) AS [URL] , - ( CASE - WHEN [ohi].[host_platform] = 'Linux' THEN 'You''re running the ' + CAST([ohi].[host_distribution] AS VARCHAR(35)) + ' distribution of ' + CAST([ohi].[host_platform] AS VARCHAR(35)) + ', version ' + CAST([ohi].[host_release] AS VARCHAR(5)) - WHEN [ohi].[host_platform] = 'Windows' AND [ohi].[host_release] = '5' THEN 'You''re running Windows 2000, version ' + CAST([ohi].[host_release] AS VARCHAR(5)) - WHEN [ohi].[host_platform] = 'Windows' AND [ohi].[host_release] > '5' THEN 'You''re running ' + CAST([ohi].[host_distribution] AS VARCHAR(50)) + ', version ' + CAST([ohi].[host_release] AS VARCHAR(5)) - ELSE 'You''re running ' + CAST([ohi].[host_distribution] AS VARCHAR(35)) + ', version ' + CAST([ohi].[host_release] AS VARCHAR(5)) - END - ) AS [Details] - FROM [sys].[dm_os_host_info] [ohi]; - END; - ELSE - BEGIN - -- Otherwise, stick with Windows-only detection - - IF EXISTS ( SELECT 1 - FROM sys.all_objects - WHERE name = 'dm_os_windows_info' ) - - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 172) WITH NOWAIT; - - INSERT INTO [#BlitzResults] - ( [CheckID] , - [Priority] , - [FindingsGroup] , - [Finding] , - [URL] , - [Details] ) - - SELECT - 172 AS [CheckID] , - 250 AS [Priority] , - 'Server Info' AS [FindingsGroup] , - 'Windows Version' AS [Finding] , - 'https://en.wikipedia.org/wiki/List_of_Microsoft_Windows_versions' AS [URL] , - ( CASE - WHEN [owi].[windows_release] = '5' THEN 'You''re running Windows 2000, version ' + CAST([owi].[windows_release] AS VARCHAR(5)) - WHEN [owi].[windows_release] > '5' AND [owi].[windows_release] < '6' THEN 'You''re running Windows Server 2003/2003R2 era, version ' + CAST([owi].[windows_release] AS VARCHAR(5)) - WHEN [owi].[windows_release] >= '6' AND [owi].[windows_release] <= '6.1' THEN 'You''re running Windows Server 2008/2008R2 era, version ' + CAST([owi].[windows_release] AS VARCHAR(5)) - WHEN [owi].[windows_release] >= '6.2' AND [owi].[windows_release] <= '6.3' THEN 'You''re running Windows Server 2012/2012R2 era, version ' + CAST([owi].[windows_release] AS VARCHAR(5)) - WHEN [owi].[windows_release] = '10.0' THEN 'You''re running Windows Server 2016/2019 era, version ' + CAST([owi].[windows_release] AS VARCHAR(5)) - ELSE 'You''re running Windows Server, version ' + CAST([owi].[windows_release] AS VARCHAR(5)) - END - ) AS [Details] - FROM [sys].[dm_os_windows_info] [owi]; - - END; - END; - END; - -/* -This check hits the dm_os_process_memory system view -to see if locked_page_allocations_kb is > 0, -which could indicate that locked pages in memory is enabled. -*/ -IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 166 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 166) WITH NOWAIT; - - INSERT INTO [#BlitzResults] - ( [CheckID] , - [Priority] , - [FindingsGroup] , - [Finding] , - [URL] , - [Details] ) - SELECT - 166 AS [CheckID] , - 250 AS [Priority] , - 'Server Info' AS [FindingsGroup] , - 'Locked Pages In Memory Enabled' AS [Finding] , - 'https://www.brentozar.com/go/lpim' AS [URL] , - ( 'You currently have ' - + CASE WHEN [dopm].[locked_page_allocations_kb] / 1024. / 1024. > 0 - THEN CAST([dopm].[locked_page_allocations_kb] / 1024 / 1024 AS VARCHAR(100)) - + ' GB' - ELSE CAST([dopm].[locked_page_allocations_kb] / 1024 AS VARCHAR(100)) - + ' MB' - END + ' of pages locked in memory.' ) AS [Details] - FROM - [sys].[dm_os_process_memory] AS [dopm] - WHERE - [dopm].[locked_page_allocations_kb] > 0; - END; - - /* Server Info - Locked Pages In Memory Enabled - Check 166 - SQL Server 2016 SP1 and newer */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 166 ) - AND EXISTS ( SELECT * - FROM sys.all_objects o - INNER JOIN sys.all_columns c ON o.object_id = c.object_id - WHERE o.name = 'dm_os_sys_info' - AND c.name = 'sql_memory_model' ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 166) WITH NOWAIT; - - SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) - SELECT 166 AS CheckID , - 250 AS Priority , - ''Server Info'' AS FindingsGroup , - ''Memory Model Unconventional'' AS Finding , - ''https://www.brentozar.com/go/lpim'' AS URL , - ''Memory Model: '' + CAST(sql_memory_model_desc AS NVARCHAR(100)) - FROM sys.dm_os_sys_info WHERE sql_memory_model <> 1 OPTION (RECOMPILE);'; - - IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; - IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; - - EXECUTE(@StringToExecute); - END; - - /* Performance - Instant File Initialization Not Enabled - Check 192 */ - /* Server Info - Instant File Initialization Enabled - Check 193 */ - IF NOT EXISTS ( SELECT 1/0 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 192 /* IFI disabled check disabled */ - ) OR NOT EXISTS - ( SELECT 1/0 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 193 /* IFI enabled check disabled */ - ) - BEGIN - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d] and CheckId [%d].', 0, 1, 192, 193) WITH NOWAIT; - - DECLARE @IFISetting varchar(1) = N'N' - ,@IFIReadDMVFailed bit = 0 - ,@IFIAllFailed bit = 0; - - /* See if we can get the instant_file_initialization_enabled column from sys.dm_server_services */ - IF EXISTS - ( - SELECT 1/0 - FROM sys.all_columns - WHERE [object_id] = OBJECT_ID(N'[sys].[dm_server_services]') - AND [name] = N'instant_file_initialization_enabled' - ) - BEGIN - /* This needs to be a "dynamic" SQL statement because if the 'instant_file_initialization_enabled' column doesn't exist the procedure might fail on a bind error */ - SET @StringToExecute = N'SELECT @IFISetting = instant_file_initialization_enabled' + @crlf + - N'FROM sys.dm_server_services' + @crlf + - N'WHERE filename LIKE ''%sqlservr.exe%''' + @crlf + - N'OPTION (RECOMPILE);'; - - IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; - IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; - - EXEC dbo.sp_executesql - @StringToExecute - ,N'@IFISetting varchar(1) OUTPUT' - ,@IFISetting = @IFISetting OUTPUT - - SET @IFIReadDMVFailed = 0; - END - ELSE - /* We couldn't get the instant_file_initialization_enabled column from sys.dm_server_services, fall back to read error log */ - BEGIN - SET @IFIReadDMVFailed = 1; - /* If this is Amazon RDS, we'll use the rdsadmin.dbo.rds_read_error_log */ - IF LEFT(CAST(SERVERPROPERTY('ComputerNamePhysicalNetBIOS') AS VARCHAR(8000)), 8) = 'EC2AMAZ-' - AND LEFT(CAST(SERVERPROPERTY('MachineName') AS VARCHAR(8000)), 8) = 'EC2AMAZ-' - AND db_id('rdsadmin') IS NOT NULL - AND EXISTS ( SELECT 1/0 - FROM master.sys.all_objects - WHERE name IN ('rds_startup_tasks', 'rds_help_revlogin', 'rds_hexadecimal', 'rds_failover_tracking', 'rds_database_tracking', 'rds_track_change') - ) - BEGIN - /* Amazon RDS detected, read rdsadmin.dbo.rds_read_error_log */ - INSERT INTO #ErrorLog - EXEC rdsadmin.dbo.rds_read_error_log 0, 1, N'Database Instant File Initialization: enabled'; - END - ELSE - BEGIN - /* Try to read the error log, this might fail due to permissions */ - BEGIN TRY - INSERT INTO #ErrorLog - EXEC sys.xp_readerrorlog 0, 1, N'Database Instant File Initialization: enabled'; - END TRY - BEGIN CATCH - IF @Debug IN (1, 2) RAISERROR('No permissions to execute xp_readerrorlog.', 0, 1) WITH NOWAIT; - SET @IFIAllFailed = 1; - END CATCH - END; - END; - - IF @IFIAllFailed = 0 - BEGIN - IF @IFIReadDMVFailed = 1 - /* We couldn't read the DMV so set the @IFISetting variable using the error log */ - BEGIN - IF EXISTS ( SELECT 1/0 - FROM #ErrorLog - WHERE LEFT([Text], 45) = N'Database Instant File Initialization: enabled' - ) - BEGIN - SET @IFISetting = 'Y'; - END - ELSE - BEGIN - SET @IFISetting = 'N'; - END; - END; - - IF NOT EXISTS ( SELECT 1/0 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 192 /* IFI disabled check disabled */ - ) AND @IFISetting = 'N' - BEGIN - INSERT INTO #BlitzResults - ( - CheckID , - [Priority] , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT - 192 AS [CheckID] , - 50 AS [Priority] , - 'Performance' AS [FindingsGroup] , - 'Instant File Initialization Not Enabled' AS [Finding] , - 'https://www.brentozar.com/go/instant' AS [URL] , - 'Consider enabling IFI for faster restores and data file growths.' AS [Details] - END; - - IF NOT EXISTS ( SELECT 1/0 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 193 /* IFI enabled check disabled */ - ) AND @IFISetting = 'Y' - BEGIN - INSERT INTO #BlitzResults - ( - CheckID , - [Priority] , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT - 193 AS [CheckID] , - 250 AS [Priority] , - 'Server Info' AS [FindingsGroup] , - 'Instant File Initialization Enabled' AS [Finding] , - 'https://www.brentozar.com/go/instant' AS [URL] , - 'The service account has the Perform Volume Maintenance Tasks permission.' AS [Details] - END; - END; - END; - - /* End of checkId 192 */ - /* End of checkId 193 */ - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 130 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 130) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 130 AS CheckID , - 250 AS Priority , - 'Server Info' AS FindingsGroup , - 'Server Name' AS Finding , - 'https://www.brentozar.com/go/servername' AS URL , - @@SERVERNAME AS Details - WHERE @@SERVERNAME IS NOT NULL; - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 83 ) - BEGIN - IF EXISTS ( SELECT * - FROM sys.all_objects - WHERE name = 'dm_server_services' ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 83) WITH NOWAIT; - - -- DATETIMEOFFSET and DATETIME have different minimum values, so there's - -- a small workaround here to force 1753-01-01 if the minimum is detected - SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) - SELECT 83 AS CheckID , - 250 AS Priority , - ''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 + ''.'' - FROM sys.dm_server_services OPTION (RECOMPILE);'; - - IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; - IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; - - EXECUTE(@StringToExecute); - END; - END; - - /* Check 84 - SQL Server 2012 */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 84 ) - BEGIN - IF EXISTS ( SELECT * - FROM sys.all_objects o - INNER JOIN sys.all_columns c ON o.object_id = c.object_id - WHERE o.name = 'dm_os_sys_info' - AND c.name = 'physical_memory_kb' ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 84) WITH NOWAIT; - - SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) - SELECT 84 AS CheckID , - 250 AS Priority , - ''Server Info'' AS FindingsGroup , - ''Hardware'' AS Finding , - '''' AS URL , - ''Logical processors: '' + CAST(cpu_count AS VARCHAR(50)) + ''. Physical memory: '' + CAST( CAST(ROUND((physical_memory_kb / 1024.0 / 1024), 1) AS INT) AS VARCHAR(50)) + ''GB.'' - FROM sys.dm_os_sys_info OPTION (RECOMPILE);'; - - IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; - IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; - - EXECUTE(@StringToExecute); - END; - - /* Check 84 - SQL Server 2008 */ - IF EXISTS ( SELECT * - FROM sys.all_objects o - INNER JOIN sys.all_columns c ON o.object_id = c.object_id - WHERE o.name = 'dm_os_sys_info' - AND c.name = 'physical_memory_in_bytes' ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 84) WITH NOWAIT; - - SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) - SELECT 84 AS CheckID , - 250 AS Priority , - ''Server Info'' AS FindingsGroup , - ''Hardware'' AS Finding , - '''' AS URL , - ''Logical processors: '' + CAST(cpu_count AS VARCHAR(50)) + ''. Physical memory: '' + CAST( CAST(ROUND((physical_memory_in_bytes / 1024.0 / 1024 / 1024), 1) AS INT) AS VARCHAR(50)) + ''GB.'' - FROM sys.dm_os_sys_info OPTION (RECOMPILE);'; - - IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; - IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; - - EXECUTE(@StringToExecute); - END; - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 85 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 85) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 85 AS CheckID , - 250 AS Priority , - 'Server Info' AS FindingsGroup , - 'SQL Server Service' AS Finding , - '' AS URL , - N'Version: ' - + CAST(SERVERPROPERTY('productversion') AS NVARCHAR(100)) - + N'. Patch Level: ' - + CAST(SERVERPROPERTY('productlevel') AS NVARCHAR(100)) - + CASE WHEN SERVERPROPERTY('ProductUpdateLevel') IS NULL - THEN N'' - ELSE N'. Cumulative Update: ' - + CAST(SERVERPROPERTY('ProductUpdateLevel') AS NVARCHAR(100)) - END - + N'. Edition: ' - + CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) - + N'. Availability Groups Enabled: ' - + CAST(COALESCE(SERVERPROPERTY('IsHadrEnabled'), - 0) AS VARCHAR(100)) - + N'. Availability Groups Manager Status: ' - + CAST(COALESCE(SERVERPROPERTY('HadrManagerStatus'), - 0) AS VARCHAR(100)); - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 88 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 88) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 88 AS CheckID , - 250 AS Priority , - 'Server Info' AS FindingsGroup , - 'SQL Server Last Restart' AS Finding , - '' AS URL , - CAST(create_date AS VARCHAR(100)) - FROM sys.databases - WHERE database_id = 2; - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 91 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 91) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 91 AS CheckID , - 250 AS Priority , - 'Server Info' AS FindingsGroup , - 'Server Last Restart' AS Finding , - '' AS URL , - CAST(DATEADD(SECOND, (ms_ticks/1000)*(-1), GETDATE()) AS nvarchar(25)) - FROM sys.dm_os_sys_info; - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 92 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 92) WITH NOWAIT; - - INSERT INTO #driveInfo - ( drive, available_MB ) - EXEC master..xp_fixeddrives; - - IF EXISTS (SELECT * FROM sys.all_objects WHERE name = 'dm_os_volume_stats') - BEGIN - SET @StringToExecute = 'Update #driveInfo - SET - logical_volume_name = v.logical_volume_name, - total_MB = v.total_MB, - used_percent = v.used_percent - FROM - #driveInfo - inner join ( - SELECT DISTINCT - SUBSTRING(volume_mount_point, 1, 1) AS volume_mount_point - ,CASE WHEN ISNULL(logical_volume_name,'''') = '''' THEN '''' ELSE ''('' + logical_volume_name + '')'' END AS logical_volume_name - ,total_bytes/1024/1024 AS total_MB - ,available_bytes/1024/1024 AS available_MB - ,(CONVERT(DECIMAL(5,2),(total_bytes/1.0 - available_bytes)/total_bytes * 100)) AS used_percent - FROM - (SELECT TOP 1 WITH TIES - database_id - ,file_id - ,SUBSTRING(physical_name,1,1) AS Drive - FROM sys.master_files - ORDER BY ROW_NUMBER() OVER(PARTITION BY SUBSTRING(physical_name,1,1) ORDER BY database_id) - ) f - CROSS APPLY - sys.dm_os_volume_stats(f.database_id, f.file_id) - ) as v on #driveInfo.drive = v.volume_mount_point;'; - EXECUTE(@StringToExecute); - END; - - SET @StringToExecute ='INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 92 AS CheckID , - 250 AS Priority , - ''Server Info'' AS FindingsGroup , - ''Drive '' + i.drive + '' Space'' AS Finding , - '''' AS URL , - CASE WHEN i.total_MB IS NULL THEN - CAST(CAST(i.available_MB/1024 AS NUMERIC(18,2)) AS VARCHAR(30)) - + '' GB free on '' + i.drive - + '' drive'' - ELSE CAST(CAST(i.available_MB/1024 AS NUMERIC(18,2)) AS VARCHAR(30)) - + '' GB free on '' + i.drive - + '' drive '' + i.logical_volume_name - + '' out of '' + CAST(CAST(i.total_MB/1024 AS NUMERIC(18,2)) AS VARCHAR(30)) - + '' GB total ('' + CAST(i.used_percent AS VARCHAR(30)) + ''% used)'' END - AS Details - FROM #driveInfo AS i;' - - IF (@ProductVersionMajor >= 11) - BEGIN - SET @StringToExecute=REPLACE(@StringToExecute,'CAST(i.available_MB/1024 AS NUMERIC(18,2))','FORMAT(i.available_MB/1024,''N2'')'); - SET @StringToExecute=REPLACE(@StringToExecute,'CAST(i.total_MB/1024 AS NUMERIC(18,2))','FORMAT(i.total_MB/1024,''N2'')'); - END; - - EXECUTE(@StringToExecute); - - DROP TABLE #driveInfo; - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 103 ) - AND EXISTS ( SELECT * - FROM sys.all_objects o - INNER JOIN sys.all_columns c ON o.object_id = c.object_id - WHERE o.name = 'dm_os_sys_info' - AND c.name = 'virtual_machine_type_desc' ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 103) WITH NOWAIT; - - SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) - SELECT 103 AS CheckID, - 250 AS Priority, - ''Server Info'' AS FindingsGroup, - ''Virtual Server'' AS Finding, - ''https://www.brentozar.com/go/virtual'' AS URL, - ''Type: ('' + virtual_machine_type_desc + '')'' AS Details - FROM sys.dm_os_sys_info - WHERE virtual_machine_type <> 0 OPTION (RECOMPILE);'; - - IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; - IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; - - EXECUTE(@StringToExecute); - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 214 ) - AND EXISTS ( SELECT * - FROM sys.all_objects o - INNER JOIN sys.all_columns c ON o.object_id = c.object_id - WHERE o.name = 'dm_os_sys_info' - AND c.name = 'container_type_desc' ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 214) WITH NOWAIT; - - SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) - SELECT 214 AS CheckID, - 250 AS Priority, - ''Server Info'' AS FindingsGroup, - ''Container'' AS Finding, - ''https://www.brentozar.com/go/virtual'' AS URL, - ''Type: ('' + container_type_desc + '')'' AS Details - FROM sys.dm_os_sys_info - WHERE container_type_desc <> ''NONE'' OPTION (RECOMPILE);'; - - IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; - IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; - - EXECUTE(@StringToExecute); - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 114 ) - AND EXISTS ( SELECT * - FROM sys.all_objects o - WHERE o.name = 'dm_os_memory_nodes' ) - AND EXISTS ( SELECT * - FROM sys.all_objects o - INNER JOIN sys.all_columns c ON o.object_id = c.object_id - WHERE o.name = 'dm_os_nodes' - AND c.name = 'processor_group' ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 114) WITH NOWAIT; - - SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) - SELECT 114 AS CheckID , - 250 AS Priority , - ''Server Info'' AS FindingsGroup , - ''Hardware - NUMA Config'' AS Finding , - '''' AS URL , - ''Node: '' + CAST(n.node_id AS NVARCHAR(10)) + '' State: '' + node_state_desc - + '' Online schedulers: '' + CAST(n.online_scheduler_count AS NVARCHAR(10)) + '' Offline schedulers: '' + CAST(oac.offline_schedulers AS VARCHAR(100)) + '' Processor Group: '' + CAST(n.processor_group AS NVARCHAR(10)) - + '' Memory node: '' + CAST(n.memory_node_id AS NVARCHAR(10)) + '' Memory VAS Reserved GB: '' + CAST(CAST((m.virtual_address_space_reserved_kb / 1024.0 / 1024) AS INT) AS NVARCHAR(100)) - FROM sys.dm_os_nodes n - INNER JOIN sys.dm_os_memory_nodes m ON n.memory_node_id = m.memory_node_id - OUTER APPLY (SELECT - COUNT(*) AS [offline_schedulers] - FROM sys.dm_os_schedulers dos - WHERE n.node_id = dos.parent_node_id - AND dos.status = ''VISIBLE OFFLINE'' - ) oac - WHERE n.node_state_desc NOT LIKE ''%DAC%'' - ORDER BY n.node_id OPTION (RECOMPILE);'; - - IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; - IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; - - EXECUTE(@StringToExecute); - END; - - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 211 ) - BEGIN - - /* Variables for check 211: */ - DECLARE - @powerScheme varchar(36) - ,@cpu_speed_mhz int - ,@cpu_speed_ghz decimal(18,2) - ,@ExecResult int; - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 211) WITH NOWAIT; - IF @sa = 0 RAISERROR('The errors: ''xp_regread() returned error 5, ''Access is denied.'''' can be safely ignored', 0, 1) WITH NOWAIT; - - /* Get power plan if set by group policy [Git Hub Issue #1620] */ - EXEC xp_regread @rootkey = N'HKEY_LOCAL_MACHINE', - @key = N'SOFTWARE\Policies\Microsoft\Power\PowerSettings', - @value_name = N'ActivePowerScheme', - @value = @powerScheme OUTPUT, - @no_output = N'no_output'; - - IF @powerScheme IS NULL /* If power plan was not set by group policy, get local value [Git Hub Issue #1620]*/ - EXEC xp_regread @rootkey = N'HKEY_LOCAL_MACHINE', - @key = N'SYSTEM\CurrentControlSet\Control\Power\User\PowerSchemes', - @value_name = N'ActivePowerScheme', - @value = @powerScheme OUTPUT; - - /* Get the cpu speed*/ - EXEC @ExecResult = xp_regread @rootkey = N'HKEY_LOCAL_MACHINE', - @key = N'HARDWARE\DESCRIPTION\System\CentralProcessor\0', - @value_name = N'~MHz', - @value = @cpu_speed_mhz OUTPUT; - - /* Convert the Megahertz to Gigahertz */ - IF @ExecResult != 0 RAISERROR('We couldn''t retrieve the CPU speed, you will see Unknown in the results', 0, 1) - - SET @cpu_speed_ghz = CAST(CAST(@cpu_speed_mhz AS decimal) / 1000 AS decimal(18,2)); - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 211 AS CheckId, - 250 AS Priority, - 'Server Info' AS FindingsGroup, - 'Power Plan' AS Finding, - 'https://www.brentozar.com/blitz/power-mode/' AS URL, - 'Your server has ' - + ISNULL(CAST(@cpu_speed_ghz as VARCHAR(8)), 'Unknown ') - + 'GHz CPUs, and is in ' - + CASE @powerScheme - WHEN 'a1841308-3541-4fab-bc81-f71556f20b4a' - THEN 'power saving mode -- are you sure this is a production SQL Server?' - WHEN '381b4222-f694-41f0-9685-ff5bb260df2e' - THEN 'balanced power mode -- Uh... you want your CPUs to run at full speed, right?' - WHEN '8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c' - THEN 'high performance power mode' - WHEN 'e9a42b02-d5df-448d-aa00-03f14749eb61' - THEN 'ultimate performance power mode' - ELSE 'an unknown power mode.' - END AS Details - - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 212 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 212) WITH NOWAIT; - - INSERT INTO #Instances (Instance_Number, Instance_Name, Data_Field) - EXEC master.sys.xp_regread @rootkey = 'HKEY_LOCAL_MACHINE', - @key = 'SOFTWARE\Microsoft\Microsoft SQL Server', - @value_name = 'InstalledInstances' - - IF (SELECT COUNT(*) FROM #Instances) > 1 - BEGIN - - DECLARE @InstanceCount NVARCHAR(MAX) - SELECT @InstanceCount = COUNT(*) FROM #Instances - - INSERT INTO #BlitzResults - ( - CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT - 212 AS CheckId , - 250 AS Priority , - 'Server Info' AS FindingsGroup , - 'Instance Stacking' AS Finding , - 'https://www.brentozar.com/go/babygotstacked/' AS URL , - 'Your Server has ' + @InstanceCount + ' Instances of SQL Server installed. More than one is usually a bad idea. Read the URL for more info.' - END; - END; - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 106 ) - AND (select convert(int,value_in_use) from sys.configurations where name = 'default trace enabled' ) = 1 - AND DATALENGTH( COALESCE( @base_tracefilename, '' ) ) > DATALENGTH('.TRC') - AND @TraceFileIssue = 0 - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 106) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT - 106 AS CheckID - ,250 AS Priority - ,'Server Info' AS FindingsGroup - ,'Default Trace Contents' AS Finding - ,'https://www.brentozar.com/go/trace' AS URL - ,'The default trace holds '+cast(DATEDIFF(hour,MIN(StartTime),GETDATE())as VARCHAR(30))+' hours of data' - +' between '+cast(Min(StartTime) as VARCHAR(30))+' and '+cast(GETDATE()as VARCHAR(30)) - +('. The default trace files are located in: '+left( @curr_tracefilename,len(@curr_tracefilename) - @indx) - ) as Details - FROM ::fn_trace_gettable( @base_tracefilename, default ) - WHERE EventClass BETWEEN 65500 and 65600; - END; /* CheckID 106 */ - - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 152 ) - BEGIN - IF EXISTS (SELECT * FROM sys.dm_os_wait_stats ws - LEFT OUTER JOIN #IgnorableWaits i ON ws.wait_type = i.wait_type - WHERE wait_time_ms > .1 * @CpuMsSinceWaitsCleared AND waiting_tasks_count > 0 - AND i.wait_type IS NULL) - BEGIN - /* Check for waits that have had more than 10% of the server's wait time */ - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 152) WITH NOWAIT; - - WITH os(wait_type, waiting_tasks_count, wait_time_ms, max_wait_time_ms, signal_wait_time_ms) - AS - (SELECT ws.wait_type, waiting_tasks_count, wait_time_ms, max_wait_time_ms, signal_wait_time_ms - FROM sys.dm_os_wait_stats ws - LEFT OUTER JOIN #IgnorableWaits i ON ws.wait_type = i.wait_type - WHERE i.wait_type IS NULL - AND wait_time_ms > .1 * @CpuMsSinceWaitsCleared - AND waiting_tasks_count > 0) - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT TOP 9 - 152 AS CheckID - ,240 AS Priority - ,'Wait Stats' AS FindingsGroup - , CAST(ROW_NUMBER() OVER(ORDER BY os.wait_time_ms DESC) AS NVARCHAR(10)) + N' - ' + os.wait_type AS Finding - ,'https://www.sqlskills.com/help/waits/' + LOWER(os.wait_type) + '/' AS URL - , Details = CAST(CAST(SUM(os.wait_time_ms / 1000.0 / 60 / 60) OVER (PARTITION BY os.wait_type) AS NUMERIC(18,1)) AS NVARCHAR(20)) + N' hours of waits, ' + - CAST(CAST((SUM(60.0 * os.wait_time_ms) OVER (PARTITION BY os.wait_type) ) / @MsSinceWaitsCleared AS NUMERIC(18,1)) AS NVARCHAR(20)) + N' minutes average wait time per hour, ' + - /* CAST(CAST( - 100.* SUM(os.wait_time_ms) OVER (PARTITION BY os.wait_type) - / (1. * SUM(os.wait_time_ms) OVER () ) - AS NUMERIC(18,1)) AS NVARCHAR(40)) + N'% of waits, ' + */ - CAST(CAST( - 100. * SUM(os.signal_wait_time_ms) OVER (PARTITION BY os.wait_type) - / (1. * SUM(os.wait_time_ms) OVER ()) - AS NUMERIC(18,1)) AS NVARCHAR(40)) + N'% signal wait, ' + - CAST(SUM(os.waiting_tasks_count) OVER (PARTITION BY os.wait_type) AS NVARCHAR(40)) + N' waiting tasks, ' + - CAST(CASE WHEN SUM(os.waiting_tasks_count) OVER (PARTITION BY os.wait_type) > 0 - THEN - CAST( - SUM(os.wait_time_ms) OVER (PARTITION BY os.wait_type) - / (1. * SUM(os.waiting_tasks_count) OVER (PARTITION BY os.wait_type)) - AS NUMERIC(18,1)) - ELSE 0 END AS NVARCHAR(40)) + N' ms average wait time.' - FROM os - ORDER BY SUM(os.wait_time_ms / 1000.0 / 60 / 60) OVER (PARTITION BY os.wait_type) DESC; - END; /* IF EXISTS (SELECT * FROM sys.dm_os_wait_stats WHERE wait_time_ms > 0 AND waiting_tasks_count > 0) */ - - /* If no waits were found, add a note about that */ - IF NOT EXISTS (SELECT * FROM #BlitzResults WHERE CheckID IN (107, 108, 109, 121, 152, 162)) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 153) WITH NOWAIT; - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - VALUES (153, 240, 'Wait Stats', 'No Significant Waits Detected', 'https://www.brentozar.com/go/waits', 'This server might be just sitting around idle, or someone may have cleared wait stats recently.'); - END; - END; /* CheckID 152 */ - - /* CheckID 222 - Server Info - Azure Managed Instance */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 222 ) - AND 4 = ( SELECT COUNT(*) - FROM sys.all_objects o - INNER JOIN sys.all_columns c ON o.object_id = c.object_id - WHERE o.name = 'dm_os_job_object' - AND c.name IN ('cpu_rate', 'memory_limit_mb', 'process_memory_limit_mb', 'workingset_limit_mb' )) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 222) WITH NOWAIT; - - SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) - SELECT 222 AS CheckID , - 250 AS Priority , - ''Server Info'' AS FindingsGroup , - ''Azure Managed Instance'' AS Finding , - ''https://www.BrentOzar.com/go/azurevm'' AS URL , - ''cpu_rate: '' + CAST(COALESCE(cpu_rate, 0) AS VARCHAR(20)) + - '', memory_limit_mb: '' + CAST(COALESCE(memory_limit_mb, 0) AS NVARCHAR(20)) + - '', process_memory_limit_mb: '' + CAST(COALESCE(process_memory_limit_mb, 0) AS NVARCHAR(20)) + - '', workingset_limit_mb: '' + CAST(COALESCE(workingset_limit_mb, 0) AS NVARCHAR(20)) - FROM sys.dm_os_job_object OPTION (RECOMPILE);'; - - IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; - IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.'; - - EXECUTE(@StringToExecute); - END; - - /* CheckID 224 - Performance - SSRS/SSAS/SSIS Installed */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 224 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 224) WITH NOWAIT; - - IF (SELECT value_in_use FROM sys.configurations WHERE [name] = 'xp_cmdshell') = 1 - BEGIN - - IF OBJECT_ID('tempdb..#services') IS NOT NULL DROP TABLE #services; - CREATE TABLE #services (cmdshell_output varchar(max)); - - INSERT INTO #services - EXEC /**/xp_cmdshell/**/ 'net start' /* added comments around command since some firewalls block this string TL 20210221 */ - - IF EXISTS (SELECT 1 - FROM #services - WHERE cmdshell_output LIKE '%SQL Server Reporting Services%' - OR cmdshell_output LIKE '%SQL Server Integration Services%' - OR cmdshell_output LIKE '%SQL Server Analysis Services%') - BEGIN - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT - 224 AS CheckID - ,200 AS Priority - ,'Performance' AS FindingsGroup - ,'SSAS/SSIS/SSRS Installed' AS Finding - ,'https://www.BrentOzar.com/go/services' AS URL - ,'Did you know you have other SQL Server services installed on this box other than the engine? It can be a real performance pain.' as Details - - END; - - END; - END; - - /* CheckID 232 - Server Info - Data Size */ - IF NOT EXISTS ( SELECT 1 - FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 232 ) - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 232) WITH NOWAIT; - - IF OBJECT_ID('tempdb..#MasterFiles') IS NOT NULL - DROP TABLE #MasterFiles; - CREATE TABLE #MasterFiles (database_id INT, file_id INT, type_desc NVARCHAR(50), name NVARCHAR(255), physical_name NVARCHAR(255), size BIGINT); - /* Azure SQL Database doesn't have sys.master_files, so we have to build our own. */ - IF ((SERVERPROPERTY('Edition')) = 'SQL Azure' - AND (OBJECT_ID('sys.master_files') IS NULL)) - SET @StringToExecute = 'INSERT INTO #MasterFiles (database_id, file_id, type_desc, name, physical_name, size) SELECT DB_ID(), file_id, type_desc, name, physical_name, size FROM sys.database_files;'; - ELSE - SET @StringToExecute = 'INSERT INTO #MasterFiles (database_id, file_id, type_desc, name, physical_name, size) SELECT database_id, file_id, type_desc, name, physical_name, size FROM sys.master_files;'; - EXEC(@StringToExecute); - - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 232 AS CheckID - ,250 AS Priority - ,'Server Info' AS FindingsGroup - ,'Data Size' AS Finding - ,'' AS URL - ,CAST(COUNT(DISTINCT database_id) AS NVARCHAR(100)) + N' databases, ' + CAST(CAST(SUM (CAST(size AS BIGINT)*8./1024./1024.) AS MONEY) AS VARCHAR(100)) + ' GB total file size' as Details - FROM #MasterFiles - WHERE database_id > 4; - - END; - - - END; /* IF @CheckServerInfo = 1 */ - END; /* IF ( ( SERVERPROPERTY('ServerName') NOT IN ( SELECT ServerName */ - - /* Delete priorites they wanted to skip. */ - IF @IgnorePrioritiesAbove IS NOT NULL - DELETE #BlitzResults - WHERE [Priority] > @IgnorePrioritiesAbove AND CheckID <> -1; - - IF @IgnorePrioritiesBelow IS NOT NULL - DELETE #BlitzResults - WHERE [Priority] < @IgnorePrioritiesBelow AND CheckID <> -1; - - /* Delete checks they wanted to skip. */ - IF @SkipChecksTable IS NOT NULL - BEGIN - DELETE FROM #BlitzResults - WHERE DatabaseName IN ( SELECT DatabaseName - FROM #SkipChecks - WHERE CheckID IS NULL - AND (ServerName IS NULL OR ServerName = SERVERPROPERTY('ServerName'))); - DELETE FROM #BlitzResults - WHERE CheckID IN ( SELECT CheckID - FROM #SkipChecks - WHERE DatabaseName IS NULL - AND (ServerName IS NULL OR ServerName = SERVERPROPERTY('ServerName'))); - DELETE r FROM #BlitzResults r - INNER JOIN #SkipChecks c ON r.DatabaseName = c.DatabaseName and r.CheckID = c.CheckID - AND (ServerName IS NULL OR ServerName = SERVERPROPERTY('ServerName')); - END; - - /* Add summary mode */ - IF @SummaryMode > 0 - BEGIN - UPDATE #BlitzResults - SET Finding = br.Finding + ' (' + CAST(brTotals.recs AS NVARCHAR(20)) + ')' - FROM #BlitzResults br - INNER JOIN (SELECT FindingsGroup, Finding, Priority, COUNT(*) AS recs FROM #BlitzResults GROUP BY FindingsGroup, Finding, Priority) brTotals ON br.FindingsGroup = brTotals.FindingsGroup AND br.Finding = brTotals.Finding AND br.Priority = brTotals.Priority - WHERE brTotals.recs > 1; - - DELETE br - FROM #BlitzResults br - WHERE EXISTS (SELECT * FROM #BlitzResults brLower WHERE br.FindingsGroup = brLower.FindingsGroup AND br.Finding = brLower.Finding AND br.Priority = brLower.Priority AND br.ID > brLower.ID); - - END; - - /* Add credits for the nice folks who put so much time into building and maintaining this for free: */ - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - VALUES ( -1 , - 255 , - 'Thanks!' , - 'From Your Community Volunteers' , - 'http://FirstResponderKit.org' , - 'We hope you found this tool useful.' - ); - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - - ) - VALUES ( -1 , - 0 , - 'sp_Blitz ' + CAST(CONVERT(DATETIME, @VersionDate, 102) AS VARCHAR(100)), - 'SQL Server First Responder Kit' , - 'http://FirstResponderKit.org/' , - 'To get help or add your own contributions, join us at http://FirstResponderKit.org.' - - ); - - INSERT INTO #BlitzResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - - ) - SELECT 156 , - 254 , - 'Rundate' , - GETDATE() , - 'http://FirstResponderKit.org/' , - 'Captain''s log: stardate something and something...'; - - IF @EmailRecipients IS NOT NULL - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Sending an email.', 0, 1) WITH NOWAIT; - - /* Database mail won't work off a local temp table. I'm not happy about this hacky workaround either. */ - IF (OBJECT_ID('tempdb..##BlitzResults', 'U') IS NOT NULL) DROP TABLE ##BlitzResults; - SELECT * INTO ##BlitzResults FROM #BlitzResults; - SET @query_result_separator = char(9); - SET @StringToExecute = 'SET NOCOUNT ON;SELECT [Priority] , [FindingsGroup] , [Finding] , [DatabaseName] , [URL] , [Details] , CheckID FROM ##BlitzResults ORDER BY Priority , FindingsGroup , Finding , DatabaseName , Details; SET NOCOUNT OFF;'; - SET @EmailSubject = 'sp_Blitz Results for ' + @@SERVERNAME; - SET @EmailBody = 'sp_Blitz ' + CAST(CONVERT(DATETIME, @VersionDate, 102) AS VARCHAR(100)) + '. http://FirstResponderKit.org'; - IF @EmailProfile IS NULL - EXEC msdb.dbo.sp_send_dbmail - @recipients = @EmailRecipients, - @subject = @EmailSubject, - @body = @EmailBody, - @query_attachment_filename = 'sp_Blitz-Results.csv', - @attach_query_result_as_file = 1, - @query_result_header = 1, - @query_result_width = 32767, - @append_query_error = 1, - @query_result_no_padding = 1, - @query_result_separator = @query_result_separator, - @query = @StringToExecute; - ELSE - EXEC msdb.dbo.sp_send_dbmail - @profile_name = @EmailProfile, - @recipients = @EmailRecipients, - @subject = @EmailSubject, - @body = @EmailBody, - @query_attachment_filename = 'sp_Blitz-Results.csv', - @attach_query_result_as_file = 1, - @query_result_header = 1, - @query_result_width = 32767, - @append_query_error = 1, - @query_result_no_padding = 1, - @query_result_separator = @query_result_separator, - @query = @StringToExecute; - IF (OBJECT_ID('tempdb..##BlitzResults', 'U') IS NOT NULL) DROP TABLE ##BlitzResults; - END; - - /* Checks if @OutputServerName is populated with a valid linked server, and that the database name specified is valid */ - DECLARE @ValidOutputServer BIT; - DECLARE @ValidOutputLocation BIT; - DECLARE @LinkedServerDBCheck NVARCHAR(2000); - DECLARE @ValidLinkedServerDB INT; - DECLARE @tmpdbchk table (cnt int); - IF @OutputServerName IS NOT NULL - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Outputting to a remote server.', 0, 1) WITH NOWAIT; - - IF EXISTS (SELECT server_id FROM sys.servers WHERE QUOTENAME([name]) = @OutputServerName) - BEGIN - SET @LinkedServerDBCheck = 'SELECT 1 WHERE EXISTS (SELECT * FROM '+@OutputServerName+'.master.sys.databases WHERE QUOTENAME([name]) = '''+@OutputDatabaseName+''')'; - INSERT INTO @tmpdbchk EXEC sys.sp_executesql @LinkedServerDBCheck; - SET @ValidLinkedServerDB = (SELECT COUNT(*) FROM @tmpdbchk); - IF (@ValidLinkedServerDB > 0) - BEGIN - SET @ValidOutputServer = 1; - SET @ValidOutputLocation = 1; - END; - ELSE - RAISERROR('The specified database was not found on the output server', 16, 0); - END; - ELSE - BEGIN - RAISERROR('The specified output server was not found', 16, 0); - END; - END; - ELSE - BEGIN - IF @OutputDatabaseName IS NOT NULL - AND @OutputSchemaName IS NOT NULL - AND @OutputTableName IS NOT NULL - AND EXISTS ( SELECT * - FROM sys.databases - WHERE QUOTENAME([name]) = @OutputDatabaseName) - BEGIN - SET @ValidOutputLocation = 1; - END; - ELSE IF @OutputDatabaseName IS NOT NULL - AND @OutputSchemaName IS NOT NULL - AND @OutputTableName IS NOT NULL - AND NOT EXISTS ( SELECT * - FROM sys.databases - WHERE QUOTENAME([name]) = @OutputDatabaseName) - BEGIN - RAISERROR('The specified output database was not found on this server', 16, 0); - END; - ELSE - BEGIN - SET @ValidOutputLocation = 0; - END; - END; - - /* @OutputTableName lets us export the results to a permanent table */ - IF @ValidOutputLocation = 1 - BEGIN - SET @StringToExecute = 'USE ' - + @OutputDatabaseName - + '; IF EXISTS(SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' - + @OutputSchemaName - + ''') AND NOT EXISTS (SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''' - + @OutputSchemaName + ''' AND QUOTENAME(TABLE_NAME) = ''' - + @OutputTableName + ''') CREATE TABLE ' - + @OutputSchemaName + '.' - + @OutputTableName - + ' (ID INT IDENTITY(1,1) NOT NULL, - ServerName NVARCHAR(128), - CheckDate DATETIMEOFFSET, - Priority TINYINT , - FindingsGroup VARCHAR(50) , - Finding VARCHAR(200) , - DatabaseName NVARCHAR(128), - URL VARCHAR(200) , - Details NVARCHAR(4000) , - QueryPlan [XML] NULL , - QueryPlanFiltered [NVARCHAR](MAX) NULL, - CheckID INT , - CONSTRAINT [PK_' + CAST(NEWID() AS CHAR(36)) + '] PRIMARY KEY CLUSTERED (ID ASC));'; - IF @ValidOutputServer = 1 - BEGIN - SET @StringToExecute = REPLACE(@StringToExecute,''''+@OutputSchemaName+'''',''''''+@OutputSchemaName+''''''); - SET @StringToExecute = REPLACE(@StringToExecute,''''+@OutputTableName+'''',''''''+@OutputTableName+''''''); - SET @StringToExecute = REPLACE(@StringToExecute,'[XML]','[NVARCHAR](MAX)'); - EXEC('EXEC('''+@StringToExecute+''') AT ' + @OutputServerName); - END; - ELSE - BEGIN - IF @OutputXMLasNVARCHAR = 1 - BEGIN - SET @StringToExecute = REPLACE(@StringToExecute,'[XML]','[NVARCHAR](MAX)'); - END; - EXEC(@StringToExecute); - END; - IF @ValidOutputServer = 1 - BEGIN - SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' - + @OutputServerName + '.' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' - + @OutputSchemaName + ''') INSERT ' - + @OutputServerName + '.' - + @OutputDatabaseName + '.' - + @OutputSchemaName + '.' - + @OutputTableName - + ' (ServerName, CheckDate, CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details, QueryPlan, QueryPlanFiltered) SELECT ''' - + CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128)) - + ''', SYSDATETIMEOFFSET(), CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details, CAST(QueryPlan AS NVARCHAR(MAX)), QueryPlanFiltered FROM #BlitzResults ORDER BY Priority , FindingsGroup , Finding , DatabaseName , Details'; - - EXEC(@StringToExecute); - END; - ELSE - BEGIN - IF @OutputXMLasNVARCHAR = 1 - BEGIN - SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' - + @OutputSchemaName + ''') INSERT ' - + @OutputDatabaseName + '.' - + @OutputSchemaName + '.' - + @OutputTableName - + ' (ServerName, CheckDate, CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details, QueryPlan, QueryPlanFiltered) SELECT ''' - + CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128)) - + ''', SYSDATETIMEOFFSET(), CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details, CAST(QueryPlan AS NVARCHAR(MAX)), QueryPlanFiltered FROM #BlitzResults ORDER BY Priority , FindingsGroup , Finding , DatabaseName , Details'; - END; - ELSE - begin - SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' - + @OutputSchemaName + ''') INSERT ' - + @OutputDatabaseName + '.' - + @OutputSchemaName + '.' - + @OutputTableName - + ' (ServerName, CheckDate, CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details, QueryPlan, QueryPlanFiltered) SELECT ''' - + CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128)) - + ''', SYSDATETIMEOFFSET(), CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details, QueryPlan, QueryPlanFiltered FROM #BlitzResults ORDER BY Priority , FindingsGroup , Finding , DatabaseName , Details'; - END; - EXEC(@StringToExecute); - - END; - END; - ELSE IF (SUBSTRING(@OutputTableName, 2, 2) = '##') - BEGIN - IF @ValidOutputServer = 1 - BEGIN - RAISERROR('Due to the nature of temporary tables, outputting to a linked server requires a permanent table.', 16, 0); - END; - ELSE - BEGIN - SET @StringToExecute = N' IF (OBJECT_ID(''tempdb..' - + @OutputTableName - + ''') IS NOT NULL) DROP TABLE ' + @OutputTableName + ';' - + 'CREATE TABLE ' - + @OutputTableName - + ' (ID INT IDENTITY(1,1) NOT NULL, - ServerName NVARCHAR(128), - CheckDate DATETIMEOFFSET, - Priority TINYINT , - FindingsGroup VARCHAR(50) , - Finding VARCHAR(200) , - DatabaseName NVARCHAR(128), - URL VARCHAR(200) , - Details NVARCHAR(4000) , - QueryPlan [XML] NULL , - QueryPlanFiltered [NVARCHAR](MAX) NULL, - CheckID INT , - CONSTRAINT [PK_' + CAST(NEWID() AS CHAR(36)) + '] PRIMARY KEY CLUSTERED (ID ASC));' - + ' INSERT ' - + @OutputTableName - + ' (ServerName, CheckDate, CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details, QueryPlan, QueryPlanFiltered) SELECT ''' - + CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128)) - + ''', SYSDATETIMEOFFSET(), CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details, QueryPlan, QueryPlanFiltered FROM #BlitzResults ORDER BY Priority , FindingsGroup , Finding , DatabaseName , Details'; - - EXEC(@StringToExecute); - END; - END; - ELSE IF (SUBSTRING(@OutputTableName, 2, 1) = '#') - BEGIN - RAISERROR('Due to the nature of Dymamic SQL, only global (i.e. double pound (##)) temp tables are supported for @OutputTableName', 16, 0); - END; - - DECLARE @separator AS VARCHAR(1); - IF @OutputType = 'RSV' - SET @separator = CHAR(31); - ELSE - SET @separator = ','; - - IF @OutputType = 'COUNT' - BEGIN - SELECT COUNT(*) AS Warnings - FROM #BlitzResults; - END; - ELSE - IF @OutputType IN ( 'CSV', 'RSV' ) - BEGIN - - SELECT Result = CAST([Priority] AS NVARCHAR(100)) - + @separator + CAST(CheckID AS NVARCHAR(100)) - + @separator + COALESCE([FindingsGroup], - '(N/A)') + @separator - + COALESCE([Finding], '(N/A)') + @separator - + COALESCE(DatabaseName, '(N/A)') + @separator - + COALESCE([URL], '(N/A)') + @separator - + COALESCE([Details], '(N/A)') - FROM #BlitzResults - ORDER BY Priority , - FindingsGroup , - Finding , - DatabaseName , - Details; - END; - ELSE IF @OutputXMLasNVARCHAR = 1 AND @OutputType <> 'NONE' - BEGIN - SELECT [Priority] , - [FindingsGroup] , - [Finding] , - [DatabaseName] , - [URL] , - [Details] , - CAST([QueryPlan] AS NVARCHAR(MAX)) AS QueryPlan, - [QueryPlanFiltered] , - CheckID - FROM #BlitzResults - ORDER BY Priority , - FindingsGroup , - Finding , - DatabaseName , - Details; - END; - ELSE IF @OutputType = 'MARKDOWN' - BEGIN - WITH Results AS (SELECT row_number() OVER (ORDER BY Priority, FindingsGroup, Finding, DatabaseName, Details) AS rownum, * - FROM #BlitzResults - WHERE Priority > 0 AND Priority < 255 AND FindingsGroup IS NOT NULL AND Finding IS NOT NULL - AND FindingsGroup <> 'Security' /* Specifically excluding security checks for public exports */) - SELECT - Markdown = CONVERT(XML, STUFF((SELECT - CASE - WHEN r.Priority <> COALESCE(rPrior.Priority, 0) OR r.FindingsGroup <> rPrior.FindingsGroup THEN @crlf + N'**Priority ' + CAST(COALESCE(r.Priority,N'') AS NVARCHAR(5)) + N': ' + COALESCE(r.FindingsGroup,N'') + N'**:' + @crlf + @crlf - ELSE N'' - END - + CASE WHEN r.Finding <> COALESCE(rPrior.Finding,N'') AND r.Finding <> COALESCE(rNext.Finding,N'') THEN N'- ' + COALESCE(r.Finding,N'') + N' ' + COALESCE(r.DatabaseName, N'') + N' - ' + COALESCE(r.Details,N'') + @crlf - WHEN r.Finding <> COALESCE(rPrior.Finding,N'') AND r.Finding = rNext.Finding AND r.Details = rNext.Details THEN N'- ' + COALESCE(r.Finding,N'') + N' - ' + COALESCE(r.Details,N'') + @crlf + @crlf + N' * ' + COALESCE(r.DatabaseName, N'') + @crlf - WHEN r.Finding <> COALESCE(rPrior.Finding,N'') AND r.Finding = rNext.Finding THEN N'- ' + COALESCE(r.Finding,N'') + @crlf + @crlf + CASE WHEN r.DatabaseName IS NULL THEN N'' ELSE N' * ' + COALESCE(r.DatabaseName,N'') END + CASE WHEN r.Details <> rPrior.Details THEN N' - ' + COALESCE(r.Details,N'') + @crlf ELSE '' END - ELSE CASE WHEN r.DatabaseName IS NULL THEN N'' ELSE N' * ' + COALESCE(r.DatabaseName,N'') END + CASE WHEN r.Details <> rPrior.Details THEN N' - ' + COALESCE(r.Details,N'') + @crlf ELSE N'' + @crlf END - END + @crlf - FROM Results r - LEFT OUTER JOIN Results rPrior ON r.rownum = rPrior.rownum + 1 - LEFT OUTER JOIN Results rNext ON r.rownum = rNext.rownum - 1 - ORDER BY r.rownum FOR XML PATH(N''), ROOT('Markdown'), TYPE).value('/Markdown[1]','VARCHAR(MAX)'), 1, 2, '') - + ''); - END; - ELSE IF @OutputType = 'XML' - BEGIN - /* --TOURSTOP05-- */ - SELECT [Priority] , - [FindingsGroup] , - [Finding] , - [DatabaseName] , - [URL] , - [Details] , - [QueryPlanFiltered] , - CheckID - FROM #BlitzResults - ORDER BY Priority , - FindingsGroup , - Finding , - DatabaseName , - Details - FOR XML PATH('Result'), ROOT('sp_Blitz_Output'); - END; - ELSE IF @OutputType <> 'NONE' - BEGIN - /* --TOURSTOP05-- */ - SELECT [Priority] , - [FindingsGroup] , - [Finding] , - [DatabaseName] , - [URL] , - [Details] , - [QueryPlan] , - [QueryPlanFiltered] , - CheckID - FROM #BlitzResults - ORDER BY Priority , - FindingsGroup , - Finding , - DatabaseName , - Details; - END; - - DROP TABLE #BlitzResults; - - IF @OutputProcedureCache = 1 - AND @CheckProcedureCache = 1 - SELECT TOP 20 - total_worker_time / execution_count AS AvgCPU , - total_worker_time AS TotalCPU , - CAST(ROUND(100.00 * total_worker_time - / ( SELECT SUM(total_worker_time) - FROM sys.dm_exec_query_stats - ), 2) AS MONEY) AS PercentCPU , - total_elapsed_time / execution_count AS AvgDuration , - total_elapsed_time AS TotalDuration , - CAST(ROUND(100.00 * total_elapsed_time - / ( SELECT SUM(total_elapsed_time) - FROM sys.dm_exec_query_stats - ), 2) AS MONEY) AS PercentDuration , - total_logical_reads / execution_count AS AvgReads , - total_logical_reads AS TotalReads , - CAST(ROUND(100.00 * total_logical_reads - / ( SELECT SUM(total_logical_reads) - FROM sys.dm_exec_query_stats - ), 2) AS MONEY) AS PercentReads , - execution_count , - CAST(ROUND(100.00 * execution_count - / ( SELECT SUM(execution_count) - FROM sys.dm_exec_query_stats - ), 2) AS MONEY) AS PercentExecutions , - CASE WHEN DATEDIFF(mi, creation_time, - qs.last_execution_time) = 0 THEN 0 - ELSE CAST(( 1.00 * execution_count / DATEDIFF(mi, - creation_time, - qs.last_execution_time) ) AS MONEY) - END AS executions_per_minute , - qs.creation_time AS plan_creation_time , - qs.last_execution_time , - text , - text_filtered , - query_plan , - query_plan_filtered , - sql_handle , - query_hash , - plan_handle , - query_plan_hash - FROM #dm_exec_query_stats qs - ORDER BY CASE UPPER(@CheckProcedureCacheFilter) - WHEN 'CPU' THEN total_worker_time - WHEN 'READS' THEN total_logical_reads - WHEN 'EXECCOUNT' THEN execution_count - WHEN 'DURATION' THEN total_elapsed_time - ELSE total_worker_time - END DESC; - - END; /* ELSE -- IF @OutputType = 'SCHEMA' */ - - /* - Cleanups - drop temporary tables that have been created by this SP. - */ - - IF(OBJECT_ID('tempdb..#InvalidLogins') IS NOT NULL) - BEGIN - EXEC sp_executesql N'DROP TABLE #InvalidLogins;'; - END; - - IF OBJECT_ID('tempdb..#AlertInfo') IS NOT NULL - BEGIN - EXEC sp_executesql N'DROP TABLE #AlertInfo;'; - END; - - /* - Reset the Nmumeric_RoundAbort session state back to enabled if it was disabled earlier. - See Github issue #2302 for more info. - */ - IF @NeedToTurnNumericRoundabortBackOn = 1 - SET NUMERIC_ROUNDABORT ON; - - SET NOCOUNT OFF; -GO - -/* ---Sample execution call with the most common parameters: -EXEC [dbo].[sp_Blitz] - @CheckUserDatabaseObjects = 1 , - @CheckProcedureCache = 0 , - @OutputType = 'TABLE' , - @OutputProcedureCache = 0 , - @CheckProcedureCacheFilter = NULL, - @CheckServerInfo = 1 -*/ -SET ANSI_NULLS ON; -SET QUOTED_IDENTIFIER ON - -IF NOT EXISTS (SELECT * FROM sys.objects WHERE [object_id] = OBJECT_ID(N'[dbo].[sp_BlitzAnalysis]') AND [type] in (N'P', N'PC')) -BEGIN -EXEC dbo.sp_executesql @statement = N'CREATE PROCEDURE [dbo].[sp_BlitzAnalysis] AS' -END -GO - -ALTER PROCEDURE [dbo].[sp_BlitzAnalysis] ( -@Help TINYINT = 0, -@StartDate DATETIMEOFFSET(7) = NULL, -@EndDate DATETIMEOFFSET(7) = NULL, -@OutputDatabaseName NVARCHAR(256) = 'DBAtools', -@OutputSchemaName NVARCHAR(256) = N'dbo', -@OutputTableNameBlitzFirst NVARCHAR(256) = N'BlitzFirst', -@OutputTableNameFileStats NVARCHAR(256) = N'BlitzFirst_FileStats', -@OutputTableNamePerfmonStats NVARCHAR(256) = N'BlitzFirst_PerfmonStats', -@OutputTableNameWaitStats NVARCHAR(256) = N'BlitzFirst_WaitStats', -@OutputTableNameBlitzCache NVARCHAR(256) = N'BlitzCache', -@OutputTableNameBlitzWho NVARCHAR(256) = N'BlitzWho', -@Servername NVARCHAR(128) = @@SERVERNAME, -@Databasename NVARCHAR(128) = NULL, -@BlitzCacheSortorder NVARCHAR(20) = N'cpu', -@MaxBlitzFirstPriority INT = 249, -@ReadLatencyThreshold INT = 100, -@WriteLatencyThreshold INT = 100, -@WaitStatsTop TINYINT = 10, -@Version VARCHAR(30) = NULL OUTPUT, -@VersionDate DATETIME = NULL OUTPUT, -@VersionCheckMode BIT = 0, -@BringThePain BIT = 0, -@Maxdop INT = 1, -@Debug BIT = 0 -) -AS -SET NOCOUNT ON; -SET STATISTICS XML OFF; - -SELECT @Version = '8.19', @VersionDate = '20240222'; - -IF(@VersionCheckMode = 1) -BEGIN - RETURN; -END; - -IF (@Help = 1) -BEGIN - PRINT 'EXEC sp_BlitzAnalysis -@StartDate = NULL, /* Specify a datetime or NULL will get an hour ago */ -@EndDate = NULL, /* Specify a datetime or NULL will get an hour of data since @StartDate */ -@OutputDatabaseName = N''DBA'', /* Specify the database name where where we can find your logged blitz data */ -@OutputSchemaName = N''dbo'', /* Specify the schema */ -@OutputTableNameBlitzFirst = N''BlitzFirst'', /* Table name where you are storing sp_BlitzFirst output, Set to NULL to ignore */ -@OutputTableNameFileStats = N''BlitzFirst_FileStats'', /* Table name where you are storing sp_BlitzFirst filestats output, Set to NULL to ignore */ -@OutputTableNamePerfmonStats = N''BlitzFirst_PerfmonStats'', /* Table name where you are storing sp_BlitzFirst Perfmon output, Set to NULL to ignore */ -@OutputTableNameWaitStats = N''BlitzFirst_WaitStats'', /* Table name where you are storing sp_BlitzFirst Wait stats output, Set to NULL to ignore */ -@OutputTableNameBlitzCache = N''BlitzCache'', /* Table name where you are storing sp_BlitzCache output, Set to NULL to ignore */ -@OutputTableNameBlitzWho = N''BlitzWho'', /* Table name where you are storing sp_BlitzWho output, Set to NULL to ignore */ -@Databasename = NULL, /* Filters results for BlitzCache, FileStats (will also include tempdb), BlitzWho. Leave as NULL for all databases */ -@MaxBlitzFirstPriority = 249, /* Max priority to include in the results */ -@BlitzCacheSortorder = ''cpu'', /* Accepted values ''all'' ''cpu'' ''reads'' ''writes'' ''duration'' ''executions'' ''memory grant'' ''spills'' */ -@WaitStatsTop = 3, /* Controls the top for wait stats only */ -@Maxdop = 1, /* Control the degree of parallelism that the queries within this proc can use if they want to*/ -@Debug = 0; /* Show sp_BlitzAnalysis SQL commands in the messages tab as they execute */ - -/* -Additional parameters: -@ReadLatencyThreshold INT /* Default: 100 - Sets the threshold in ms to compare against io_stall_read_average_ms in your filestats table */ -@WriteLatencyThreshold INT /* Default: 100 - Sets the threshold in ms to compare against io_stall_write_average_ms in your filestats table */ -@BringThePain BIT /* Default: 0 - If you are getting more than 4 hours of data with blitzcachesortorder set to ''all'' you will need to set BringThePain to 1 */ -*/'; - RETURN; -END - -/* Declare all local variables required */ -DECLARE @FullOutputTableNameBlitzFirst NVARCHAR(1000); -DECLARE @FullOutputTableNameFileStats NVARCHAR(1000); -DECLARE @FullOutputTableNamePerfmonStats NVARCHAR(1000); -DECLARE @FullOutputTableNameWaitStats NVARCHAR(1000); -DECLARE @FullOutputTableNameBlitzCache NVARCHAR(1000); -DECLARE @FullOutputTableNameBlitzWho NVARCHAR(1000); -DECLARE @Sql NVARCHAR(MAX); -DECLARE @NewLine NVARCHAR(2) = CHAR(13); -DECLARE @IncludeMemoryGrants BIT; -DECLARE @IncludeSpills BIT; - -/* Validate the database name */ -IF (DB_ID(@OutputDatabaseName) IS NULL) -BEGIN - RAISERROR('Invalid database name provided for parameter @OutputDatabaseName: %s',11,0,@OutputDatabaseName); - RETURN; -END - -/* Set fully qualified table names */ -SET @FullOutputTableNameBlitzFirst = QUOTENAME(@OutputDatabaseName)+N'.'+QUOTENAME(@OutputSchemaName)+N'.'+QUOTENAME(@OutputTableNameBlitzFirst); -SET @FullOutputTableNameFileStats = QUOTENAME(@OutputDatabaseName)+N'.'+QUOTENAME(@OutputSchemaName)+N'.'+QUOTENAME(@OutputTableNameFileStats+N'_Deltas'); -SET @FullOutputTableNamePerfmonStats = QUOTENAME(@OutputDatabaseName)+N'.'+QUOTENAME(@OutputSchemaName)+N'.'+QUOTENAME(@OutputTableNamePerfmonStats+N'_Actuals'); -SET @FullOutputTableNameWaitStats = QUOTENAME(@OutputDatabaseName)+N'.'+QUOTENAME(@OutputSchemaName)+N'.'+QUOTENAME(@OutputTableNameWaitStats+N'_Deltas'); -SET @FullOutputTableNameBlitzCache = QUOTENAME(@OutputDatabaseName)+N'.'+QUOTENAME(@OutputSchemaName)+N'.'+QUOTENAME(@OutputTableNameBlitzCache); -SET @FullOutputTableNameBlitzWho = QUOTENAME(@OutputDatabaseName)+N'.'+QUOTENAME(@OutputSchemaName)+N'.'+QUOTENAME(@OutputTableNameBlitzWho+N'_Deltas'); - -IF OBJECT_ID('tempdb.dbo.#BlitzFirstCounts') IS NOT NULL -BEGIN - DROP TABLE #BlitzFirstCounts; -END - -CREATE TABLE #BlitzFirstCounts ( - [Priority] TINYINT NOT NULL, - [FindingsGroup] VARCHAR(50) NOT NULL, - [Finding] VARCHAR(200) NOT NULL, - [TotalOccurrences] INT NULL, - [FirstOccurrence] DATETIMEOFFSET(7) NULL, - [LastOccurrence] DATETIMEOFFSET(7) NULL -); - -/* Validate variables and set defaults as required */ -IF (@BlitzCacheSortorder IS NULL) -BEGIN - SET @BlitzCacheSortorder = N'cpu'; -END - -SET @BlitzCacheSortorder = LOWER(@BlitzCacheSortorder); - -IF (@OutputTableNameBlitzCache IS NOT NULL AND @BlitzCacheSortorder NOT IN (N'all',N'cpu',N'reads',N'writes',N'duration',N'executions',N'memory grant',N'spills')) -BEGIN - RAISERROR('Invalid sort option specified for @BlitzCacheSortorder, supported values are ''all'', ''cpu'', ''reads'', ''writes'', ''duration'', ''executions'', ''memory grant'', ''spills''',11,0) WITH NOWAIT; - RETURN; -END - -/* Set @Maxdop to 1 if NULL was passed in */ -IF (@Maxdop IS NULL) -BEGIN - SET @Maxdop = 1; -END - -/* iF @Maxdop is set higher than the core count just set it to 0 */ -IF (@Maxdop > (SELECT CAST(cpu_count AS INT) FROM sys.dm_os_sys_info)) -BEGIN - SET @Maxdop = 0; -END - -/* We need to check if your SQL version has memory grant and spills columns in sys.dm_exec_query_stats */ -SELECT @IncludeMemoryGrants = - CASE - WHEN (EXISTS(SELECT * FROM sys.all_columns WHERE [object_id] = OBJECT_ID('sys.dm_exec_query_stats') AND name = 'max_grant_kb')) THEN 1 - ELSE 0 - END; - -SELECT @IncludeSpills = - CASE - WHEN (EXISTS(SELECT * FROM sys.all_columns WHERE [object_id] = OBJECT_ID('sys.dm_exec_query_stats') AND name = 'max_spills')) THEN 1 - ELSE 0 - END; - - -IF (@StartDate IS NULL) -BEGIN - RAISERROR('Setting @StartDate to: 1 hour ago',0,0) WITH NOWAIT; - /* Set StartDate to be an hour ago */ - SET @StartDate = DATEADD(HOUR,-1,SYSDATETIMEOFFSET()); - - IF (@EndDate IS NULL) - BEGIN - RAISERROR('Setting @EndDate to: Now',0,0) WITH NOWAIT; - /* Get data right up to now */ - SET @EndDate = SYSDATETIMEOFFSET(); - END -END - -IF (@EndDate IS NULL) -BEGIN - /* Default to an hour of data or SYSDATETIMEOFFSET() if now is earlier than the hour added to @StartDate */ - IF(DATEADD(HOUR,1,@StartDate) < SYSDATETIMEOFFSET()) - BEGIN - RAISERROR('@EndDate was NULL - Setting to return 1 hour of information, if you want more then set @EndDate aswell',0,0) WITH NOWAIT; - SET @EndDate = DATEADD(HOUR,1,@StartDate); - END - ELSE - BEGIN - RAISERROR('@EndDate was NULL - Setting to SYSDATETIMEOFFSET()',0,0) WITH NOWAIT; - SET @EndDate = SYSDATETIMEOFFSET(); - END -END - -/* Default to dbo schema if NULL is passed in */ -IF (@OutputSchemaName IS NULL) -BEGIN - SET @OutputSchemaName = 'dbo'; -END - -/* Prompt the user for @BringThePain = 1 if they are searching a timeframe greater than 4 hours and they are using BlitzCacheSortorder = 'all' */ -IF(@BlitzCacheSortorder = 'all' AND DATEDIFF(HOUR,@StartDate,@EndDate) > 4 AND @BringThePain = 0) -BEGIN - RAISERROR('Wow! hold up now, are you sure you wanna do this? Are sure you want to query over 4 hours of data with @BlitzCacheSortorder set to ''all''? IF you do then set @BringThePain = 1 but I gotta warn you this might hurt a bit!',11,1) WITH NOWAIT; - RETURN; -END - -/* Output report window information */ -SELECT - @Servername AS [ServerToReportOn], - CAST(1 AS NVARCHAR(20)) + N' - '+ CAST(@MaxBlitzFirstPriority AS NVARCHAR(20)) AS [PrioritesToInclude], - @StartDate AS [StartDatetime], - @EndDate AS [EndDatetime];; - - -/* BlitzFirst data */ -SET @Sql = N' -INSERT INTO #BlitzFirstCounts ([Priority],[FindingsGroup],[Finding],[TotalOccurrences],[FirstOccurrence],[LastOccurrence]) -SELECT -[Priority], -[FindingsGroup], -[Finding], -COUNT(*) AS [TotalOccurrences], -MIN(CheckDate) AS [FirstOccurrence], -MAX(CheckDate) AS [LastOccurrence] -FROM '+@FullOutputTableNameBlitzFirst+N' -WHERE [ServerName] = @Servername -AND [Priority] BETWEEN 1 AND @MaxBlitzFirstPriority -AND CheckDate BETWEEN @StartDate AND @EndDate -AND [CheckID] > -1 -GROUP BY [Priority],[FindingsGroup],[Finding]; - -IF EXISTS(SELECT 1 FROM #BlitzFirstCounts) -BEGIN - SELECT - [Priority], - [FindingsGroup], - [Finding], - [TotalOccurrences], - [FirstOccurrence], - [LastOccurrence] - FROM #BlitzFirstCounts - ORDER BY [Priority] ASC,[TotalOccurrences] DESC; -END -ELSE -BEGIN - SELECT N''No findings with a priority between 1 and ''+CAST(@MaxBlitzFirstPriority AS NVARCHAR(10))+N'' found for this period''; -END -SELECT - [ServerName] -,[CheckDate] -,[CheckID] -,[Priority] -,[Finding] -,[URL] -,[Details] -,[HowToStopIt] -,[QueryPlan] -,[QueryText] -FROM '+@FullOutputTableNameBlitzFirst+N' Findings -WHERE [ServerName] = @Servername -AND [Priority] BETWEEN 1 AND @MaxBlitzFirstPriority -AND [CheckDate] BETWEEN @StartDate AND @EndDate -AND [CheckID] > -1 -ORDER BY CheckDate ASC,[Priority] ASC -OPTION (RECOMPILE, MAXDOP '+CAST(@Maxdop AS NVARCHAR(2))+N');'; - - -RAISERROR('Getting BlitzFirst info from %s',0,0,@FullOutputTableNameBlitzFirst) WITH NOWAIT; - -IF (@Debug = 1) -BEGIN - PRINT @Sql; -END - -IF (OBJECT_ID(@FullOutputTableNameBlitzFirst) IS NULL) -BEGIN - IF (@OutputTableNameBlitzFirst IS NULL) - BEGIN - RAISERROR('BlitzFirst data skipped',10,0); - SELECT N'Skipped logged BlitzFirst data as NULL was passed to parameter @OutputTableNameBlitzFirst'; - END - ELSE - BEGIN - RAISERROR('Table provided for BlitzFirst data: %s does not exist',10,0,@FullOutputTableNameBlitzFirst); - SELECT N'No BlitzFirst data available as the table cannot be found'; - END - -END -ELSE /* Table exists then run the query */ -BEGIN - EXEC sp_executesql @Sql, - N'@StartDate DATETIMEOFFSET(7), - @EndDate DATETIMEOFFSET(7), - @Servername NVARCHAR(128), - @MaxBlitzFirstPriority INT', - @StartDate=@StartDate, - @EndDate=@EndDate, - @Servername=@Servername, - @MaxBlitzFirstPriority = @MaxBlitzFirstPriority; -END - -/* Blitz WaitStats data */ -SET @Sql = N'SELECT -[ServerName], -[CheckDate], -[wait_type], -[WaitsRank], -[WaitCategory], -[Ignorable], -[ElapsedSeconds], -[wait_time_ms_delta], -[wait_time_minutes_delta], -[wait_time_minutes_per_minute], -[signal_wait_time_ms_delta], -[waiting_tasks_count_delta], -ISNULL((CAST([wait_time_ms_delta] AS DECIMAL(38,2))/NULLIF(CAST([waiting_tasks_count_delta] AS DECIMAL(38,2)),0)),0) AS [wait_time_ms_per_wait] -FROM -( - SELECT - [ServerName], - [CheckDate], - [wait_type], - [WaitCategory], - [Ignorable], - [ElapsedSeconds], - [wait_time_ms_delta], - [wait_time_minutes_delta], - [wait_time_minutes_per_minute], - [signal_wait_time_ms_delta], - [waiting_tasks_count_delta], - ROW_NUMBER() OVER(PARTITION BY [CheckDate] ORDER BY [CheckDate] ASC,[wait_time_ms_delta] DESC) AS [WaitsRank] - FROM '+@FullOutputTableNameWaitStats+N' AS [Waits] - WHERE [ServerName] = @Servername - AND [CheckDate] BETWEEN @StartDate AND @EndDate -) TopWaits -WHERE [WaitsRank] <= @WaitStatsTop -ORDER BY -[CheckDate] ASC, -[wait_time_ms_delta] DESC -OPTION(RECOMPILE, MAXDOP '+CAST(@Maxdop AS NVARCHAR(2))+N');' - -RAISERROR('Getting wait stats info from %s',0,0,@FullOutputTableNameWaitStats) WITH NOWAIT; - -IF (@Debug = 1) -BEGIN - PRINT @Sql; -END - -IF (OBJECT_ID(@FullOutputTableNameWaitStats) IS NULL) -BEGIN - IF (@OutputTableNameWaitStats IS NULL) - BEGIN - RAISERROR('Wait stats data skipped',10,0); - SELECT N'Skipped logged wait stats data as NULL was passed to parameter @OutputTableNameWaitStats'; - END - ELSE - BEGIN - RAISERROR('Table provided for wait stats data: %s does not exist',10,0,@FullOutputTableNameWaitStats); - SELECT N'No wait stats data available as the table cannot be found'; - END -END -ELSE /* Table exists then run the query */ -BEGIN - EXEC sp_executesql @Sql, - N'@StartDate DATETIMEOFFSET(7), - @EndDate DATETIMEOFFSET(7), - @Servername NVARCHAR(128), - @WaitStatsTop TINYINT', - @StartDate=@StartDate, - @EndDate=@EndDate, - @Servername=@Servername, - @WaitStatsTop=@WaitStatsTop; -END - -/* BlitzFileStats info */ -SET @Sql = N' -SELECT -[ServerName], -[CheckDate], -CASE - WHEN MAX([io_stall_read_ms_average]) > @ReadLatencyThreshold THEN ''Yes'' - WHEN MAX([io_stall_write_ms_average]) > @WriteLatencyThreshold THEN ''Yes'' - ELSE ''No'' -END AS [io_stall_ms_breached], -LEFT([PhysicalName],LEN([PhysicalName])-CHARINDEX(''\'',REVERSE([PhysicalName]))+1) AS [PhysicalPath], -SUM([SizeOnDiskMB]) AS [SizeOnDiskMB], -SUM([SizeOnDiskMBgrowth]) AS [SizeOnDiskMBgrowth], -MAX([io_stall_read_ms]) AS [max_io_stall_read_ms], -MAX([io_stall_read_ms_average]) AS [max_io_stall_read_ms_average], -@ReadLatencyThreshold AS [is_stall_read_ms_threshold], -SUM([num_of_reads]) AS [num_of_reads], -SUM([megabytes_read]) AS [megabytes_read], -MAX([io_stall_write_ms]) AS [max_io_stall_write_ms], -MAX([io_stall_write_ms_average]) AS [max_io_stall_write_ms_average], -@WriteLatencyThreshold AS [io_stall_write_ms_average], -SUM([num_of_writes]) AS [num_of_writes], -SUM([megabytes_written]) AS [megabytes_written] -FROM '+@FullOutputTableNameFileStats+N' -WHERE [ServerName] = @Servername -AND [CheckDate] BETWEEN @StartDate AND @EndDate -' -+CASE - WHEN @Databasename IS NOT NULL THEN N'AND [DatabaseName] IN (N''tempdb'',@Databasename) -' - ELSE N'' -END -+N'GROUP BY -[ServerName], -[CheckDate], -LEFT([PhysicalName],LEN([PhysicalName])-CHARINDEX(''\'',REVERSE([PhysicalName]))+1) -ORDER BY -[CheckDate] ASC -OPTION (RECOMPILE, MAXDOP '+CAST(@Maxdop AS NVARCHAR(2))+N');' - -RAISERROR('Getting FileStats info from %s',0,0,@FullOutputTableNameFileStats) WITH NOWAIT; - -IF (@Debug = 1) -BEGIN - PRINT @Sql; -END - -IF (OBJECT_ID(@FullOutputTableNameFileStats) IS NULL) -BEGIN - IF (@OutputTableNameFileStats IS NULL) - BEGIN - RAISERROR('File stats data skipped',10,0); - SELECT N'Skipped logged File stats data as NULL was passed to parameter @OutputTableNameFileStats'; - END - ELSE - BEGIN - RAISERROR('Table provided for FileStats data: %s does not exist',10,0,@FullOutputTableNameFileStats); - SELECT N'No File stats data available as the table cannot be found'; - END -END -ELSE /* Table exists then run the query */ -BEGIN - EXEC sp_executesql @Sql, - N'@StartDate DATETIMEOFFSET(7), - @EndDate DATETIMEOFFSET(7), - @Servername NVARCHAR(128), - @Databasename NVARCHAR(128), - @ReadLatencyThreshold INT, - @WriteLatencyThreshold INT', - @StartDate=@StartDate, - @EndDate=@EndDate, - @Servername=@Servername, - @Databasename = @Databasename, - @ReadLatencyThreshold = @ReadLatencyThreshold, - @WriteLatencyThreshold = @WriteLatencyThreshold; -END - -/* Blitz Perfmon stats*/ -SET @Sql = N' -SELECT - [ServerName] - ,[CheckDate] - ,[counter_name] - ,[object_name] - ,[instance_name] - ,[cntr_value] -FROM '+@FullOutputTableNamePerfmonStats+N' -WHERE [ServerName] = @Servername -AND CheckDate BETWEEN @StartDate AND @EndDate -ORDER BY - [CheckDate] ASC, - [counter_name] ASC -OPTION (RECOMPILE, MAXDOP '+CAST(@Maxdop AS NVARCHAR(2))+N');' - -RAISERROR('Getting Perfmon info from %s',0,0,@FullOutputTableNamePerfmonStats) WITH NOWAIT; - -IF (@Debug = 1) -BEGIN - PRINT @Sql; -END - -IF (OBJECT_ID(@FullOutputTableNamePerfmonStats) IS NULL) -BEGIN - IF (@OutputTableNamePerfmonStats IS NULL) - BEGIN - RAISERROR('Perfmon stats data skipped',10,0); - SELECT N'Skipped logged Perfmon stats data as NULL was passed to parameter @OutputTableNamePerfmonStats'; - END - ELSE - BEGIN - RAISERROR('Table provided for Perfmon stats data: %s does not exist',10,0,@FullOutputTableNamePerfmonStats); - SELECT N'No Perfmon data available as the table cannot be found'; - END -END -ELSE /* Table exists then run the query */ -BEGIN - EXEC sp_executesql @Sql, - N'@StartDate DATETIMEOFFSET(7), - @EndDate DATETIMEOFFSET(7), - @Servername NVARCHAR(128)', - @StartDate=@StartDate, - @EndDate=@EndDate, - @Servername=@Servername; -END - -/* Blitz cache data */ -RAISERROR('Sortorder for BlitzCache data: %s',0,0,@BlitzCacheSortorder) WITH NOWAIT; - -/* Set intial CTE */ -SET @Sql = N'WITH CheckDates AS ( -SELECT DISTINCT CheckDate -FROM ' -+@FullOutputTableNameBlitzCache -+N' -WHERE [ServerName] = @Servername -AND [CheckDate] BETWEEN @StartDate AND @EndDate' -+@NewLine -+CASE - WHEN @Databasename IS NOT NULL THEN N'AND [DatabaseName] = @Databasename'+@NewLine - ELSE N'' -END -+N')' -; - -SET @Sql += @NewLine; - -/* Append additional CTEs based on sortorder */ -SET @Sql += ( -SELECT CAST(N',' AS NVARCHAR(MAX)) -+[SortOptions].[Aliasname]+N' AS ( -SELECT - [ServerName] - ,'+[SortOptions].[Aliasname]+N'.[CheckDate] - ,[Sortorder] - ,[TimeFrameRank] - ,ROW_NUMBER() OVER(ORDER BY ['+[SortOptions].[Columnname]+N'] DESC) AS [OverallRank] - ,'+[SortOptions].[Aliasname]+N'.[QueryType] - ,'+[SortOptions].[Aliasname]+N'.[QueryText] - ,'+[SortOptions].[Aliasname]+N'.[DatabaseName] - ,'+[SortOptions].[Aliasname]+N'.[AverageCPU] - ,'+[SortOptions].[Aliasname]+N'.[TotalCPU] - ,'+[SortOptions].[Aliasname]+N'.[PercentCPUByType] - ,'+[SortOptions].[Aliasname]+N'.[AverageDuration] - ,'+[SortOptions].[Aliasname]+N'.[TotalDuration] - ,'+[SortOptions].[Aliasname]+N'.[PercentDurationByType] - ,'+[SortOptions].[Aliasname]+N'.[AverageReads] - ,'+[SortOptions].[Aliasname]+N'.[TotalReads] - ,'+[SortOptions].[Aliasname]+N'.[PercentReadsByType] - ,'+[SortOptions].[Aliasname]+N'.[AverageWrites] - ,'+[SortOptions].[Aliasname]+N'.[TotalWrites] - ,'+[SortOptions].[Aliasname]+N'.[PercentWritesByType] - ,'+[SortOptions].[Aliasname]+N'.[ExecutionCount] - ,'+[SortOptions].[Aliasname]+N'.[ExecutionWeight] - ,'+[SortOptions].[Aliasname]+N'.[PercentExecutionsByType] - ,'+[SortOptions].[Aliasname]+N'.[ExecutionsPerMinute] - ,'+[SortOptions].[Aliasname]+N'.[PlanCreationTime] - ,'+[SortOptions].[Aliasname]+N'.[PlanCreationTimeHours] - ,'+[SortOptions].[Aliasname]+N'.[LastExecutionTime] - ,'+[SortOptions].[Aliasname]+N'.[PlanHandle] - ,'+[SortOptions].[Aliasname]+N'.[SqlHandle] - ,'+[SortOptions].[Aliasname]+N'.[SQL Handle More Info] - ,'+[SortOptions].[Aliasname]+N'.[QueryHash] - ,'+[SortOptions].[Aliasname]+N'.[Query Hash More Info] - ,'+[SortOptions].[Aliasname]+N'.[QueryPlanHash] - ,'+[SortOptions].[Aliasname]+N'.[StatementStartOffset] - ,'+[SortOptions].[Aliasname]+N'.[StatementEndOffset] - ,'+[SortOptions].[Aliasname]+N'.[MinReturnedRows] - ,'+[SortOptions].[Aliasname]+N'.[MaxReturnedRows] - ,'+[SortOptions].[Aliasname]+N'.[AverageReturnedRows] - ,'+[SortOptions].[Aliasname]+N'.[TotalReturnedRows] - ,'+[SortOptions].[Aliasname]+N'.[QueryPlan] - ,'+[SortOptions].[Aliasname]+N'.[NumberOfPlans] - ,'+[SortOptions].[Aliasname]+N'.[NumberOfDistinctPlans] - ,'+[SortOptions].[Aliasname]+N'.[MinGrantKB] - ,'+[SortOptions].[Aliasname]+N'.[MaxGrantKB] - ,'+[SortOptions].[Aliasname]+N'.[MinUsedGrantKB] - ,'+[SortOptions].[Aliasname]+N'.[MaxUsedGrantKB] - ,'+[SortOptions].[Aliasname]+N'.[PercentMemoryGrantUsed] - ,'+[SortOptions].[Aliasname]+N'.[AvgMaxMemoryGrant] - ,'+[SortOptions].[Aliasname]+N'.[MinSpills] - ,'+[SortOptions].[Aliasname]+N'.[MaxSpills] - ,'+[SortOptions].[Aliasname]+N'.[TotalSpills] - ,'+[SortOptions].[Aliasname]+N'.[AvgSpills] - ,'+[SortOptions].[Aliasname]+N'.[QueryPlanCost] -FROM CheckDates -CROSS APPLY ( - SELECT TOP (5) - [ServerName] - ,'+[SortOptions].[Aliasname]+N'.[CheckDate] - ,'+QUOTENAME(UPPER([SortOptions].[Sortorder]),N'''')+N' AS [Sortorder] - ,ROW_NUMBER() OVER(ORDER BY ['+[SortOptions].[Columnname]+N'] DESC) AS [TimeFrameRank] - ,'+[SortOptions].[Aliasname]+N'.[QueryType] - ,'+[SortOptions].[Aliasname]+N'.[QueryText] - ,'+[SortOptions].[Aliasname]+N'.[DatabaseName] - ,'+[SortOptions].[Aliasname]+N'.[AverageCPU] - ,'+[SortOptions].[Aliasname]+N'.[TotalCPU] - ,'+[SortOptions].[Aliasname]+N'.[PercentCPUByType] - ,'+[SortOptions].[Aliasname]+N'.[AverageDuration] - ,'+[SortOptions].[Aliasname]+N'.[TotalDuration] - ,'+[SortOptions].[Aliasname]+N'.[PercentDurationByType] - ,'+[SortOptions].[Aliasname]+N'.[AverageReads] - ,'+[SortOptions].[Aliasname]+N'.[TotalReads] - ,'+[SortOptions].[Aliasname]+N'.[PercentReadsByType] - ,'+[SortOptions].[Aliasname]+N'.[AverageWrites] - ,'+[SortOptions].[Aliasname]+N'.[TotalWrites] - ,'+[SortOptions].[Aliasname]+N'.[PercentWritesByType] - ,'+[SortOptions].[Aliasname]+N'.[ExecutionCount] - ,'+[SortOptions].[Aliasname]+N'.[ExecutionWeight] - ,'+[SortOptions].[Aliasname]+N'.[PercentExecutionsByType] - ,'+[SortOptions].[Aliasname]+N'.[ExecutionsPerMinute] - ,'+[SortOptions].[Aliasname]+N'.[PlanCreationTime] - ,'+[SortOptions].[Aliasname]+N'.[PlanCreationTimeHours] - ,'+[SortOptions].[Aliasname]+N'.[LastExecutionTime] - ,'+[SortOptions].[Aliasname]+N'.[PlanHandle] - ,'+[SortOptions].[Aliasname]+N'.[SqlHandle] - ,'+[SortOptions].[Aliasname]+N'.[SQL Handle More Info] - ,'+[SortOptions].[Aliasname]+N'.[QueryHash] - ,'+[SortOptions].[Aliasname]+N'.[Query Hash More Info] - ,'+[SortOptions].[Aliasname]+N'.[QueryPlanHash] - ,'+[SortOptions].[Aliasname]+N'.[StatementStartOffset] - ,'+[SortOptions].[Aliasname]+N'.[StatementEndOffset] - ,'+[SortOptions].[Aliasname]+N'.[MinReturnedRows] - ,'+[SortOptions].[Aliasname]+N'.[MaxReturnedRows] - ,'+[SortOptions].[Aliasname]+N'.[AverageReturnedRows] - ,'+[SortOptions].[Aliasname]+N'.[TotalReturnedRows] - ,'+[SortOptions].[Aliasname]+N'.[QueryPlan] - ,'+[SortOptions].[Aliasname]+N'.[NumberOfPlans] - ,'+[SortOptions].[Aliasname]+N'.[NumberOfDistinctPlans] - ,'+[SortOptions].[Aliasname]+N'.[MinGrantKB] - ,'+[SortOptions].[Aliasname]+N'.[MaxGrantKB] - ,'+[SortOptions].[Aliasname]+N'.[MinUsedGrantKB] - ,'+[SortOptions].[Aliasname]+N'.[MaxUsedGrantKB] - ,'+[SortOptions].[Aliasname]+N'.[PercentMemoryGrantUsed] - ,'+[SortOptions].[Aliasname]+N'.[AvgMaxMemoryGrant] - ,'+[SortOptions].[Aliasname]+N'.[MinSpills] - ,'+[SortOptions].[Aliasname]+N'.[MaxSpills] - ,'+[SortOptions].[Aliasname]+N'.[TotalSpills] - ,'+[SortOptions].[Aliasname]+N'.[AvgSpills] - ,'+[SortOptions].[Aliasname]+N'.[QueryPlanCost] - FROM '+@FullOutputTableNameBlitzCache+N' AS '+[SortOptions].[Aliasname]+N' - WHERE [ServerName] = @Servername - AND [CheckDate] BETWEEN @StartDate AND @EndDate - AND ['+[SortOptions].[Aliasname]+N'].[CheckDate] = [CheckDates].[CheckDate]' - +@NewLine - +CASE - WHEN @Databasename IS NOT NULL THEN N'AND ['+[SortOptions].[Aliasname]+N'].[DatabaseName] = @Databasename'+@NewLine - ELSE N'' - END - +CASE - WHEN [Sortorder] = N'cpu' THEN N'AND [TotalCPU] > 0' - WHEN [Sortorder] = N'reads' THEN N'AND [TotalReads] > 0' - WHEN [Sortorder] = N'writes' THEN N'AND [TotalWrites] > 0' - WHEN [Sortorder] = N'duration' THEN N'AND [TotalDuration] > 0' - WHEN [Sortorder] = N'executions' THEN N'AND [ExecutionCount] > 0' - WHEN [Sortorder] = N'memory grant' THEN N'AND [MaxGrantKB] > 0' - WHEN [Sortorder] = N'spills' THEN N'AND [MaxSpills] > 0' - ELSE N'' - END - +N' - ORDER BY ['+[SortOptions].[Columnname]+N'] DESC) '+[SortOptions].[Aliasname]+N' -)' -FROM (VALUES - (N'cpu',N'TopCPU',N'TotalCPU'), - (N'reads',N'TopReads',N'TotalReads'), - (N'writes',N'TopWrites',N'TotalWrites'), - (N'duration',N'TopDuration',N'TotalDuration'), - (N'executions',N'TopExecutions',N'ExecutionCount'), - (N'memory grant',N'TopMemoryGrants',N'MaxGrantKB'), - (N'spills',N'TopSpills',N'MaxSpills') - ) SortOptions(Sortorder,Aliasname,Columnname) -WHERE - CASE /* for spills and memory grant sorts make sure the underlying columns exist in the DMV otherwise do not include them */ - WHEN (@IncludeMemoryGrants = 0 OR @IncludeMemoryGrants IS NULL) AND ([SortOptions].[Sortorder] = N'memory grant' OR [SortOptions].[Sortorder] = N'all') THEN NULL - WHEN (@IncludeSpills = 0 OR @IncludeSpills IS NULL) AND ([SortOptions].[Sortorder] = N'spills' OR [SortOptions].[Sortorder] = N'all') THEN NULL - ELSE [SortOptions].[Sortorder] - END = ISNULL(NULLIF(@BlitzCacheSortorder,N'all'),[SortOptions].[Sortorder]) -FOR XML PATH(N''), TYPE).value(N'.[1]', N'NVARCHAR(MAX)'); - -SET @Sql += @NewLine; - -/* Build the select statements to return the data after CTE declarations */ -SET @Sql += ( -SELECT STUFF(( -SELECT @NewLine -+N'UNION ALL' -+@NewLine -+N'SELECT * -FROM '+[SortOptions].[Aliasname] -FROM (VALUES - (N'cpu',N'TopCPU',N'TotalCPU'), - (N'reads',N'TopReads',N'TotalReads'), - (N'writes',N'TopWrites',N'TotalWrites'), - (N'duration',N'TopDuration',N'TotalDuration'), - (N'executions',N'TopExecutions',N'ExecutionCount'), - (N'memory grant',N'TopMemoryGrants',N'MaxGrantKB'), - (N'spills',N'TopSpills',N'MaxSpills') - ) SortOptions(Sortorder,Aliasname,Columnname) -WHERE - CASE /* for spills and memory grant sorts make sure the underlying columns exist in the DMV otherwise do not include them */ - WHEN (@IncludeMemoryGrants = 0 OR @IncludeMemoryGrants IS NULL) AND ([SortOptions].[Sortorder] = N'memory grant' OR [SortOptions].[Sortorder] = N'all') THEN NULL - WHEN (@IncludeSpills = 0 OR @IncludeSpills IS NULL) AND ([SortOptions].[Sortorder] = N'spills' OR [SortOptions].[Sortorder] = N'all') THEN NULL - ELSE [SortOptions].[Sortorder] - END = ISNULL(NULLIF(@BlitzCacheSortorder,N'all'),[SortOptions].[Sortorder]) -FOR XML PATH(N''), TYPE).value(N'.[1]', N'NVARCHAR(MAX)'),1,11,N'') -); - -/* Append Order By */ -SET @Sql += @NewLine -+N'ORDER BY - [Sortorder] ASC, - [CheckDate] ASC, - [TimeFrameRank] ASC'; - -/* Append OPTION(RECOMPILE, MAXDOP) to complete the statement */ -SET @Sql += @NewLine -+N'OPTION(RECOMPILE, MAXDOP '+CAST(@Maxdop AS NVARCHAR(2))+N');'; - -RAISERROR('Getting BlitzCache info from %s',0,0,@FullOutputTableNameBlitzCache) WITH NOWAIT; - -IF (@Debug = 1) -BEGIN - PRINT SUBSTRING(@Sql, 0, 4000); - PRINT SUBSTRING(@Sql, 4000, 8000); - PRINT SUBSTRING(@Sql, 8000, 12000); - PRINT SUBSTRING(@Sql, 12000, 16000); - PRINT SUBSTRING(@Sql, 16000, 20000); - PRINT SUBSTRING(@Sql, 20000, 24000); - PRINT SUBSTRING(@Sql, 24000, 28000); -END - -IF (OBJECT_ID(@FullOutputTableNameBlitzCache) IS NULL) -BEGIN - IF (@OutputTableNameBlitzCache IS NULL) - BEGIN - RAISERROR('BlitzCache data skipped',10,0); - SELECT N'Skipped logged BlitzCache data as NULL was passed to parameter @OutputTableNameBlitzCache'; - END - ELSE - BEGIN - RAISERROR('Table provided for BlitzCache data: %s does not exist',10,0,@FullOutputTableNameBlitzCache); - SELECT N'No BlitzCache data available as the table cannot be found'; - END -END -ELSE /* Table exists then run the query */ -BEGIN - EXEC sp_executesql @Sql, - N'@Servername NVARCHAR(128), - @Databasename NVARCHAR(128), - @BlitzCacheSortorder NVARCHAR(20), - @StartDate DATETIMEOFFSET(7), - @EndDate DATETIMEOFFSET(7)', - @Servername = @Servername, - @Databasename = @Databasename, - @BlitzCacheSortorder = @BlitzCacheSortorder, - @StartDate = @StartDate, - @EndDate = @EndDate; -END - - - -/* BlitzWho data */ -SET @Sql = N' -SELECT [ServerName] - ,[CheckDate] - ,[elapsed_time] - ,[session_id] - ,[database_name] - ,[query_text_snippet] - ,[query_plan] - ,[live_query_plan] - ,[query_cost] - ,[status] - ,[wait_info] - ,[top_session_waits] - ,[blocking_session_id] - ,[open_transaction_count] - ,[is_implicit_transaction] - ,[nt_domain] - ,[host_name] - ,[login_name] - ,[nt_user_name] - ,[program_name] - ,[fix_parameter_sniffing] - ,[client_interface_name] - ,[login_time] - ,[start_time] - ,[request_time] - ,[request_cpu_time] - ,[degree_of_parallelism] - ,[request_logical_reads] - ,[Logical_Reads_MB] - ,[request_writes] - ,[Logical_Writes_MB] - ,[request_physical_reads] - ,[Physical_reads_MB] - ,[session_cpu] - ,[session_logical_reads] - ,[session_logical_reads_MB] - ,[session_physical_reads] - ,[session_physical_reads_MB] - ,[session_writes] - ,[session_writes_MB] - ,[tempdb_allocations_mb] - ,[memory_usage] - ,[estimated_completion_time] - ,[percent_complete] - ,[deadlock_priority] - ,[transaction_isolation_level] - ,[last_dop] - ,[min_dop] - ,[max_dop] - ,[last_grant_kb] - ,[min_grant_kb] - ,[max_grant_kb] - ,[last_used_grant_kb] - ,[min_used_grant_kb] - ,[max_used_grant_kb] - ,[last_ideal_grant_kb] - ,[min_ideal_grant_kb] - ,[max_ideal_grant_kb] - ,[last_reserved_threads] - ,[min_reserved_threads] - ,[max_reserved_threads] - ,[last_used_threads] - ,[min_used_threads] - ,[max_used_threads] - ,[grant_time] - ,[requested_memory_kb] - ,[grant_memory_kb] - ,[is_request_granted] - ,[required_memory_kb] - ,[query_memory_grant_used_memory_kb] - ,[ideal_memory_kb] - ,[is_small] - ,[timeout_sec] - ,[resource_semaphore_id] - ,[wait_order] - ,[wait_time_ms] - ,[next_candidate_for_memory_grant] - ,[target_memory_kb] - ,[max_target_memory_kb] - ,[total_memory_kb] - ,[available_memory_kb] - ,[granted_memory_kb] - ,[query_resource_semaphore_used_memory_kb] - ,[grantee_count] - ,[waiter_count] - ,[timeout_error_count] - ,[forced_grant_count] - ,[workload_group_name] - ,[resource_pool_name] - ,[context_info] - ,[query_hash] - ,[query_plan_hash] - ,[sql_handle] - ,[plan_handle] - ,[statement_start_offset] - ,[statement_end_offset] - FROM '+@FullOutputTableNameBlitzWho+N' - WHERE [ServerName] = @Servername - AND ([CheckDate] BETWEEN @StartDate AND @EndDate OR [start_time] BETWEEN CAST(@StartDate AS DATETIME) AND CAST(@EndDate AS DATETIME)) - ' - +CASE - WHEN @Databasename IS NOT NULL THEN N'AND [database_name] = @Databasename - ' - ELSE N'' - END -+N'ORDER BY [CheckDate] ASC - OPTION (RECOMPILE, MAXDOP '+CAST(@Maxdop AS NVARCHAR(2))+N');'; - -RAISERROR('Getting BlitzWho info from %s',0,0,@FullOutputTableNameBlitzWho) WITH NOWAIT; - -IF (@Debug = 1) -BEGIN - PRINT @Sql; -END - -IF (OBJECT_ID(@FullOutputTableNameBlitzWho) IS NULL) -BEGIN - IF (@OutputTableNameBlitzWho IS NULL) - BEGIN - RAISERROR('BlitzWho data skipped',10,0); - SELECT N'Skipped logged BlitzWho data as NULL was passed to parameter @OutputTableNameBlitzWho'; - END - ELSE - BEGIN - RAISERROR('Table provided for BlitzWho data: %s does not exist',10,0,@FullOutputTableNameBlitzWho); - SELECT N'No BlitzWho data available as the table cannot be found'; - END -END -ELSE -BEGIN - EXEC sp_executesql @Sql, - N'@StartDate DATETIMEOFFSET(7), - @EndDate DATETIMEOFFSET(7), - @Servername NVARCHAR(128), - @Databasename NVARCHAR(128)', - @StartDate=@StartDate, - @EndDate=@EndDate, - @Servername=@Servername, - @Databasename = @Databasename; -END - - -GO -IF OBJECT_ID('dbo.sp_BlitzBackups') IS NULL - EXEC ('CREATE PROCEDURE dbo.sp_BlitzBackups AS RETURN 0;'); -GO -ALTER PROCEDURE [dbo].[sp_BlitzBackups] - @Help TINYINT = 0 , - @HoursBack INT = 168, - @MSDBName NVARCHAR(256) = 'msdb', - @AGName NVARCHAR(256) = NULL, - @RestoreSpeedFullMBps INT = NULL, - @RestoreSpeedDiffMBps INT = NULL, - @RestoreSpeedLogMBps INT = NULL, - @Debug TINYINT = 0, - @PushBackupHistoryToListener BIT = 0, - @WriteBackupsToListenerName NVARCHAR(256) = NULL, - @WriteBackupsToDatabaseName NVARCHAR(256) = NULL, - @WriteBackupsLastHours INT = 168, - @Version VARCHAR(30) = NULL OUTPUT, - @VersionDate DATETIME = NULL OUTPUT, - @VersionCheckMode BIT = 0 -WITH RECOMPILE -AS - BEGIN - SET NOCOUNT ON; - SET STATISTICS XML OFF; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - - SELECT @Version = '8.19', @VersionDate = '20240222'; - - IF(@VersionCheckMode = 1) - BEGIN - RETURN; - END; - - IF @Help = 1 PRINT ' - /* - sp_BlitzBackups from http://FirstResponderKit.org - - This script checks your backups to see how much data you might lose when - this server fails, and how long it might take to recover. - - To learn more, visit http://FirstResponderKit.org where you can download new - versions for free, watch training videos on how it works, get more info on - the findings, contribute your own code, and more. - - Known limitations of this version: - - Only Microsoft-supported versions of SQL Server. Sorry, 2005 and 2000. - - Unknown limitations of this version: - - None. (If we knew them, they would be known. Duh.) - - Changes - for the full list of improvements and fixes in this version, see: - https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/ - - - Parameter explanations: - - @HoursBack INT = 168 How many hours of history to examine, back from now. - You can check just the last 24 hours of backups, for example. - @MSDBName NVARCHAR(255) You can restore MSDB from different servers and check them - centrally. Also useful if you create a DBA utility database - and merge data from several servers in an AG into one DB. - @RestoreSpeedFullMBps INT By default, we use the backup speed from MSDB to guesstimate - how fast your restores will go. If you have done performance - tuning and testing of your backups (or if they horribly go even - slower in your DR environment, and you want to account for - that), then you can pass in different numbers here. - @RestoreSpeedDiffMBps INT See above. - @RestoreSpeedLogMBps INT See above. - - For more documentation: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/ - - MIT License - - Copyright (c) Brent Ozar Unlimited - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE. - - - - - */'; -ELSE -BEGIN -DECLARE @StringToExecute NVARCHAR(MAX) = N'', - @InnerStringToExecute NVARCHAR(MAX) = N'', - @ProductVersion NVARCHAR(128), - @ProductVersionMajor DECIMAL(10, 2), - @ProductVersionMinor DECIMAL(10, 2), - @StartTime DATETIME2, @ResultText NVARCHAR(MAX), - @crlf NVARCHAR(2), - @MoreInfoHeader NVARCHAR(100), - @MoreInfoFooter NVARCHAR(100); - -IF @HoursBack > 0 - SET @HoursBack = @HoursBack * -1; - -IF @WriteBackupsLastHours > 0 - SET @WriteBackupsLastHours = @WriteBackupsLastHours * -1; - -SELECT @crlf = NCHAR(13) + NCHAR(10), - @StartTime = DATEADD(hh, @HoursBack, GETDATE()), - @MoreInfoHeader = N''; - -SET @ProductVersion = CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)); -SELECT @ProductVersionMajor = SUBSTRING(@ProductVersion, 1, CHARINDEX('.', @ProductVersion) + 1), - @ProductVersionMinor = PARSENAME(CONVERT(VARCHAR(32), @ProductVersion), 2); - -CREATE TABLE #Backups -( - id INT IDENTITY(1, 1), - database_name NVARCHAR(128), - database_guid UNIQUEIDENTIFIER, - RPOWorstCaseMinutes DECIMAL(18, 1), - RTOWorstCaseMinutes DECIMAL(18, 1), - RPOWorstCaseBackupSetID INT, - RPOWorstCaseBackupSetFinishTime DATETIME, - RPOWorstCaseBackupSetIDPrior INT, - RPOWorstCaseBackupSetPriorFinishTime DATETIME, - RPOWorstCaseMoreInfoQuery XML, - RTOWorstCaseBackupFileSizeMB DECIMAL(18, 2), - RTOWorstCaseMoreInfoQuery XML, - FullMBpsAvg DECIMAL(18, 2), - FullMBpsMin DECIMAL(18, 2), - FullMBpsMax DECIMAL(18, 2), - FullSizeMBAvg DECIMAL(18, 2), - FullSizeMBMin DECIMAL(18, 2), - FullSizeMBMax DECIMAL(18, 2), - FullCompressedSizeMBAvg DECIMAL(18, 2), - FullCompressedSizeMBMin DECIMAL(18, 2), - FullCompressedSizeMBMax DECIMAL(18, 2), - DiffMBpsAvg DECIMAL(18, 2), - DiffMBpsMin DECIMAL(18, 2), - DiffMBpsMax DECIMAL(18, 2), - DiffSizeMBAvg DECIMAL(18, 2), - DiffSizeMBMin DECIMAL(18, 2), - DiffSizeMBMax DECIMAL(18, 2), - DiffCompressedSizeMBAvg DECIMAL(18, 2), - DiffCompressedSizeMBMin DECIMAL(18, 2), - DiffCompressedSizeMBMax DECIMAL(18, 2), - LogMBpsAvg DECIMAL(18, 2), - LogMBpsMin DECIMAL(18, 2), - LogMBpsMax DECIMAL(18, 2), - LogSizeMBAvg DECIMAL(18, 2), - LogSizeMBMin DECIMAL(18, 2), - LogSizeMBMax DECIMAL(18, 2), - LogCompressedSizeMBAvg DECIMAL(18, 2), - LogCompressedSizeMBMin DECIMAL(18, 2), - LogCompressedSizeMBMax DECIMAL(18, 2) -); - -CREATE TABLE #RTORecoveryPoints -( - id INT IDENTITY(1, 1), - database_name NVARCHAR(128), - database_guid UNIQUEIDENTIFIER, - rto_worst_case_size_mb AS - ( COALESCE(log_file_size_mb, 0) + COALESCE(diff_file_size_mb, 0) + COALESCE(full_file_size_mb, 0)), - rto_worst_case_time_seconds AS - ( COALESCE(log_time_seconds, 0) + COALESCE(diff_time_seconds, 0) + COALESCE(full_time_seconds, 0)), - full_backup_set_id INT, - full_last_lsn NUMERIC(25, 0), - full_backup_set_uuid UNIQUEIDENTIFIER, - full_time_seconds BIGINT, - full_file_size_mb DECIMAL(18, 2), - diff_backup_set_id INT, - diff_last_lsn NUMERIC(25, 0), - diff_time_seconds BIGINT, - diff_file_size_mb DECIMAL(18, 2), - log_backup_set_id INT, - log_last_lsn NUMERIC(25, 0), - log_time_seconds BIGINT, - log_file_size_mb DECIMAL(18, 2), - log_backups INT -); - -CREATE TABLE #Recoverability - ( - Id INT IDENTITY , - DatabaseName NVARCHAR(128), - DatabaseGUID UNIQUEIDENTIFIER, - LastBackupRecoveryModel NVARCHAR(60), - FirstFullBackupSizeMB DECIMAL (18,2), - FirstFullBackupDate DATETIME, - LastFullBackupSizeMB DECIMAL (18,2), - LastFullBackupDate DATETIME, - AvgFullBackupThroughputMB DECIMAL (18,2), - AvgFullBackupDurationSeconds INT, - AvgDiffBackupThroughputMB DECIMAL (18,2), - AvgDiffBackupDurationSeconds INT, - AvgLogBackupThroughputMB DECIMAL (18,2), - AvgLogBackupDurationSeconds INT, - AvgFullSizeMB DECIMAL (18,2), - AvgDiffSizeMB DECIMAL (18,2), - AvgLogSizeMB DECIMAL (18,2), - IsBigDiff AS CASE WHEN (AvgFullSizeMB > 10240. AND ((AvgDiffSizeMB * 100.) / AvgFullSizeMB >= 40.)) THEN 1 ELSE 0 END, - IsBigLog AS CASE WHEN (AvgFullSizeMB > 10240. AND ((AvgLogSizeMB * 100.) / AvgFullSizeMB >= 20.)) THEN 1 ELSE 0 END - ); - -CREATE TABLE #Trending -( - DatabaseName NVARCHAR(128), - DatabaseGUID UNIQUEIDENTIFIER, - [0] DECIMAL(18, 2), - [-1] DECIMAL(18, 2), - [-2] DECIMAL(18, 2), - [-3] DECIMAL(18, 2), - [-4] DECIMAL(18, 2), - [-5] DECIMAL(18, 2), - [-6] DECIMAL(18, 2), - [-7] DECIMAL(18, 2), - [-8] DECIMAL(18, 2), - [-9] DECIMAL(18, 2), - [-10] DECIMAL(18, 2), - [-11] DECIMAL(18, 2), - [-12] DECIMAL(18, 2) -); - - -CREATE TABLE #Warnings -( - Id INT IDENTITY(1, 1) PRIMARY KEY CLUSTERED, - CheckId INT, - Priority INT, - DatabaseName VARCHAR(128), - Finding VARCHAR(256), - Warning VARCHAR(8000) -); - -IF NOT EXISTS(SELECT * FROM sys.databases WHERE name = @MSDBName) - BEGIN - RAISERROR('@MSDBName was specified, but the database does not exist.', 16, 1) WITH NOWAIT; - RETURN; - END - -IF @PushBackupHistoryToListener = 1 -GOTO PushBackupHistoryToListener - - - RAISERROR('Inserting to #Backups', 0, 1) WITH NOWAIT; - - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - - SET @StringToExecute += N'WITH Backups AS (SELECT bs.database_name, bs.database_guid, bs.type AS backup_type ' + @crlf - + ' , MBpsAvg = CAST(AVG(( bs.backup_size / ( CASE WHEN DATEDIFF(ss, bs.backup_start_date, bs.backup_finish_date) = 0 THEN 1 ELSE DATEDIFF(ss, bs.backup_start_date, bs.backup_finish_date) END ) / 1048576 )) AS INT) ' + @crlf - + ' , MBpsMin = CAST(MIN(( bs.backup_size / ( CASE WHEN DATEDIFF(ss, bs.backup_start_date, bs.backup_finish_date) = 0 THEN 1 ELSE DATEDIFF(ss, bs.backup_start_date, bs.backup_finish_date) END ) / 1048576 )) AS INT) ' + @crlf - + ' , MBpsMax = CAST(MAX(( bs.backup_size / ( CASE WHEN DATEDIFF(ss, bs.backup_start_date, bs.backup_finish_date) = 0 THEN 1 ELSE DATEDIFF(ss, bs.backup_start_date, bs.backup_finish_date) END ) / 1048576 )) AS INT) ' + @crlf - + ' , SizeMBAvg = AVG(backup_size / 1048576.0) ' + @crlf - + ' , SizeMBMin = MIN(backup_size / 1048576.0) ' + @crlf - + ' , SizeMBMax = MAX(backup_size / 1048576.0) ' + @crlf - + ' , CompressedSizeMBAvg = AVG(compressed_backup_size / 1048576.0) ' + @crlf - + ' , CompressedSizeMBMin = MIN(compressed_backup_size / 1048576.0) ' + @crlf - + ' , CompressedSizeMBMax = MAX(compressed_backup_size / 1048576.0) ' + @crlf; - - - SET @StringToExecute += N' FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset bs ' + @crlf - + N' WHERE bs.backup_finish_date >= @StartTime AND bs.is_damaged = 0 ' + @crlf - + N' GROUP BY bs.database_name, bs.database_guid, bs.type)' + @crlf; - - SET @StringToExecute += + N'INSERT INTO #Backups(database_name, database_guid, ' + @crlf - + N' FullMBpsAvg, FullMBpsMin, FullMBpsMax, FullSizeMBAvg, FullSizeMBMin, FullSizeMBMax, FullCompressedSizeMBAvg, FullCompressedSizeMBMin, FullCompressedSizeMBMax, ' + @crlf - + N' DiffMBpsAvg, DiffMBpsMin, DiffMBpsMax, DiffSizeMBAvg, DiffSizeMBMin, DiffSizeMBMax, DiffCompressedSizeMBAvg, DiffCompressedSizeMBMin, DiffCompressedSizeMBMax, ' + @crlf - + N' LogMBpsAvg, LogMBpsMin, LogMBpsMax, LogSizeMBAvg, LogSizeMBMin, LogSizeMBMax, LogCompressedSizeMBAvg, LogCompressedSizeMBMin, LogCompressedSizeMBMax ) ' + @crlf - + N'SELECT bF.database_name, bF.database_guid ' + @crlf - + N' , bF.MBpsAvg AS FullMBpsAvg ' + @crlf - + N' , bF.MBpsMin AS FullMBpsMin ' + @crlf - + N' , bF.MBpsMax AS FullMBpsMax ' + @crlf - + N' , bF.SizeMBAvg AS FullSizeMBAvg ' + @crlf - + N' , bF.SizeMBMin AS FullSizeMBMin ' + @crlf - + N' , bF.SizeMBMax AS FullSizeMBMax ' + @crlf - + N' , bF.CompressedSizeMBAvg AS FullCompressedSizeMBAvg ' + @crlf - + N' , bF.CompressedSizeMBMin AS FullCompressedSizeMBMin ' + @crlf - + N' , bF.CompressedSizeMBMax AS FullCompressedSizeMBMax ' + @crlf - + N' , bD.MBpsAvg AS DiffMBpsAvg ' + @crlf - + N' , bD.MBpsMin AS DiffMBpsMin ' + @crlf - + N' , bD.MBpsMax AS DiffMBpsMax ' + @crlf - + N' , bD.SizeMBAvg AS DiffSizeMBAvg ' + @crlf - + N' , bD.SizeMBMin AS DiffSizeMBMin ' + @crlf - + N' , bD.SizeMBMax AS DiffSizeMBMax ' + @crlf - + N' , bD.CompressedSizeMBAvg AS DiffCompressedSizeMBAvg ' + @crlf - + N' , bD.CompressedSizeMBMin AS DiffCompressedSizeMBMin ' + @crlf - + N' , bD.CompressedSizeMBMax AS DiffCompressedSizeMBMax ' + @crlf - + N' , bL.MBpsAvg AS LogMBpsAvg ' + @crlf - + N' , bL.MBpsMin AS LogMBpsMin ' + @crlf - + N' , bL.MBpsMax AS LogMBpsMax ' + @crlf - + N' , bL.SizeMBAvg AS LogSizeMBAvg ' + @crlf - + N' , bL.SizeMBMin AS LogSizeMBMin ' + @crlf - + N' , bL.SizeMBMax AS LogSizeMBMax ' + @crlf - + N' , bL.CompressedSizeMBAvg AS LogCompressedSizeMBAvg ' + @crlf - + N' , bL.CompressedSizeMBMin AS LogCompressedSizeMBMin ' + @crlf - + N' , bL.CompressedSizeMBMax AS LogCompressedSizeMBMax ' + @crlf - + N' FROM Backups bF ' + @crlf - + N' LEFT OUTER JOIN Backups bD ON bF.database_name = bD.database_name AND bF.database_guid = bD.database_guid AND bD.backup_type = ''I''' + @crlf - + N' LEFT OUTER JOIN Backups bL ON bF.database_name = bL.database_name AND bF.database_guid = bL.database_guid AND bL.backup_type = ''L''' + @crlf - + N' WHERE bF.backup_type = ''D''; ' + @crlf; - - IF @Debug = 1 - PRINT @StringToExecute; - - EXEC sys.sp_executesql @StringToExecute, N'@StartTime DATETIME2', @StartTime; - - - RAISERROR('Updating #Backups with worst RPO case', 0, 1) WITH NOWAIT; - - SET @StringToExecute =N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - - SET @StringToExecute += N' - SELECT bs.database_name, bs.database_guid, bs.backup_set_id, bsPrior.backup_set_id AS backup_set_id_prior, - bs.backup_finish_date, bsPrior.backup_finish_date AS backup_finish_date_prior, - DATEDIFF(ss, bsPrior.backup_finish_date, bs.backup_finish_date) AS backup_gap_seconds - INTO #backup_gaps - FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS bs - CROSS APPLY ( - SELECT TOP 1 bs1.backup_set_id, bs1.backup_finish_date - FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS bs1 - WHERE bs.database_name = bs1.database_name - AND bs.database_guid = bs1.database_guid - AND bs.backup_finish_date > bs1.backup_finish_date - AND bs.backup_set_id > bs1.backup_set_id - ORDER BY bs1.backup_finish_date DESC, bs1.backup_set_id DESC - ) bsPrior - WHERE bs.backup_finish_date > @StartTime - - CREATE CLUSTERED INDEX cx_backup_gaps ON #backup_gaps (database_name, database_guid, backup_set_id, backup_finish_date, backup_gap_seconds); - - WITH max_gaps AS ( - SELECT g.database_name, g.database_guid, g.backup_set_id, g.backup_set_id_prior, g.backup_finish_date_prior, - g.backup_finish_date, MAX(g.backup_gap_seconds) AS max_backup_gap_seconds - FROM #backup_gaps AS g - GROUP BY g.database_name, g.database_guid, g.backup_set_id, g.backup_set_id_prior, g.backup_finish_date_prior, g.backup_finish_date - ) - UPDATE #Backups - SET RPOWorstCaseMinutes = bg.max_backup_gap_seconds / 60.0 - , RPOWorstCaseBackupSetID = bg.backup_set_id - , RPOWorstCaseBackupSetFinishTime = bg.backup_finish_date - , RPOWorstCaseBackupSetIDPrior = bg.backup_set_id_prior - , RPOWorstCaseBackupSetPriorFinishTime = bg.backup_finish_date_prior - FROM #Backups b - INNER HASH JOIN max_gaps bg ON b.database_name = bg.database_name AND b.database_guid = bg.database_guid - LEFT OUTER HASH JOIN max_gaps bgBigger ON bg.database_name = bgBigger.database_name AND bg.database_guid = bgBigger.database_guid AND bg.max_backup_gap_seconds < bgBigger.max_backup_gap_seconds - WHERE bgBigger.backup_set_id IS NULL; - '; - IF @Debug = 1 - PRINT @StringToExecute; - - EXEC sys.sp_executesql @StringToExecute, N'@StartTime DATETIME2', @StartTime; - - RAISERROR('Updating #Backups with worst RPO case queries', 0, 1) WITH NOWAIT; - - UPDATE #Backups - SET RPOWorstCaseMoreInfoQuery = @MoreInfoHeader + N'SELECT * ' + @crlf - + N' FROM ' + QUOTENAME(@MSDBName) + '.dbo.backupset ' + @crlf - + N' WHERE database_name = ''' + database_name + ''' ' + @crlf - + N' AND database_guid = ''' + CAST(database_guid AS NVARCHAR(50)) + ''' ' + @crlf - + N' AND backup_finish_date >= DATEADD(hh, -2, ''' + CAST(CONVERT(DATETIME, RPOWorstCaseBackupSetPriorFinishTime, 102) AS NVARCHAR(100)) + ''') ' + @crlf - + N' AND backup_finish_date <= DATEADD(hh, 2, ''' + CAST(CONVERT(DATETIME, RPOWorstCaseBackupSetPriorFinishTime, 102) AS NVARCHAR(100)) + ''') ' + @crlf - + N' ORDER BY backup_finish_date;' - + @MoreInfoFooter; - - -/* RTO */ - -RAISERROR('Gathering RTO information', 0, 1) WITH NOWAIT; - - - SET @StringToExecute =N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - - SET @StringToExecute += N' - INSERT INTO #RTORecoveryPoints(database_name, database_guid, log_last_lsn) - SELECT database_name, database_guid, MAX(last_lsn) AS log_last_lsn - FROM ' + QUOTENAME(@MSDBName) + '.dbo.backupset bLastLog - WHERE type = ''L'' - AND bLastLog.backup_finish_date >= @StartTime - GROUP BY database_name, database_guid; - '; - - IF @Debug = 1 - PRINT @StringToExecute; - - EXEC sys.sp_executesql @StringToExecute, N'@StartTime DATETIME2', @StartTime; - -/* Find the most recent full backups for those logs */ - -RAISERROR('Updating #RTORecoveryPoints', 0, 1) WITH NOWAIT; - - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - - SET @StringToExecute += N' - UPDATE #RTORecoveryPoints - SET log_backup_set_id = bLasted.backup_set_id - ,full_backup_set_id = bLasted.backup_set_id - ,full_last_lsn = bLasted.last_lsn - ,full_backup_set_uuid = bLasted.backup_set_uuid - FROM #RTORecoveryPoints rp - CROSS APPLY ( - SELECT TOP 1 bLog.backup_set_id AS backup_set_id_log, bLastFull.backup_set_id, bLastFull.last_lsn, bLastFull.backup_set_uuid, bLastFull.database_guid, bLastFull.database_name - FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset bLog - INNER JOIN ' + QUOTENAME(@MSDBName) + N'.dbo.backupset bLastFull - ON bLog.database_guid = bLastFull.database_guid - AND bLog.database_name = bLastFull.database_name - AND bLog.first_lsn > bLastFull.last_lsn - AND bLastFull.type = ''D'' - WHERE rp.database_guid = bLog.database_guid - AND rp.database_name = bLog.database_name - ) bLasted - LEFT OUTER JOIN ' + QUOTENAME(@MSDBName) + N'.dbo.backupset bLaterFulls ON bLasted.database_guid = bLaterFulls.database_guid AND bLasted.database_name = bLaterFulls.database_name - AND bLasted.last_lsn < bLaterFulls.last_lsn - AND bLaterFulls.first_lsn < bLasted.last_lsn - AND bLaterFulls.type = ''D'' - WHERE bLaterFulls.backup_set_id IS NULL; - '; - - IF @Debug = 1 - PRINT @StringToExecute; - - EXEC sys.sp_executesql @StringToExecute; - -/* Add any full backups in the StartDate range that weren't part of the above log backup chain */ - -RAISERROR('Add any full backups in the StartDate range that weren''t part of the above log backup chain', 0, 1) WITH NOWAIT; - - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - - SET @StringToExecute += N' - INSERT INTO #RTORecoveryPoints(database_name, database_guid, full_backup_set_id, full_last_lsn, full_backup_set_uuid) - SELECT bFull.database_name, bFull.database_guid, bFull.backup_set_id, bFull.last_lsn, bFull.backup_set_uuid - FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset bFull - LEFT OUTER JOIN #RTORecoveryPoints rp ON bFull.backup_set_uuid = rp.full_backup_set_uuid - WHERE bFull.type = ''D'' - AND bFull.backup_finish_date IS NOT NULL - AND rp.full_backup_set_uuid IS NULL - AND bFull.backup_finish_date >= @StartTime; - '; - - IF @Debug = 1 - PRINT @StringToExecute; - - EXEC sys.sp_executesql @StringToExecute, N'@StartTime DATETIME2', @StartTime; - -/* Fill out the most recent log for that full, but before the next full */ - -RAISERROR('Fill out the most recent log for that full, but before the next full', 0, 1) WITH NOWAIT; - - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - - SET @StringToExecute += N' - UPDATE rp - SET log_last_lsn = (SELECT MAX(last_lsn) FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset bLog WHERE bLog.first_lsn >= rp.full_last_lsn AND bLog.first_lsn <= rpNextFull.full_last_lsn AND bLog.type = ''L'') - FROM #RTORecoveryPoints rp - INNER JOIN #RTORecoveryPoints rpNextFull ON rp.database_guid = rpNextFull.database_guid AND rp.database_name = rpNextFull.database_name - AND rp.full_last_lsn < rpNextFull.full_last_lsn - LEFT OUTER JOIN #RTORecoveryPoints rpEarlierFull ON rp.database_guid = rpEarlierFull.database_guid AND rp.database_name = rpEarlierFull.database_name - AND rp.full_last_lsn < rpEarlierFull.full_last_lsn - AND rpNextFull.full_last_lsn > rpEarlierFull.full_last_lsn - WHERE rpEarlierFull.full_backup_set_id IS NULL; - '; - - IF @Debug = 1 - PRINT @StringToExecute; - - EXEC sys.sp_executesql @StringToExecute; - -/* Fill out a diff in that range */ - -RAISERROR('Fill out a diff in that range', 0, 1) WITH NOWAIT; - - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - - SET @StringToExecute += N' - UPDATE #RTORecoveryPoints - SET diff_last_lsn = (SELECT TOP 1 bDiff.last_lsn FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset bDiff - WHERE rp.database_guid = bDiff.database_guid AND rp.database_name = bDiff.database_name - AND bDiff.type = ''I'' - AND bDiff.last_lsn < rp.log_last_lsn - AND rp.full_backup_set_uuid = bDiff.differential_base_guid - ORDER BY bDiff.last_lsn DESC) - FROM #RTORecoveryPoints rp - WHERE diff_last_lsn IS NULL; - '; - - IF @Debug = 1 - PRINT @StringToExecute; - - EXEC sys.sp_executesql @StringToExecute; - -/* Get time & size totals for full & diff */ - -RAISERROR('Get time & size totals for full & diff', 0, 1) WITH NOWAIT; - - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - - SET @StringToExecute += N' - UPDATE #RTORecoveryPoints - SET full_time_seconds = DATEDIFF(ss,bFull.backup_start_date, bFull.backup_finish_date) - , full_file_size_mb = bFull.backup_size / 1048576.0 - , diff_backup_set_id = bDiff.backup_set_id - , diff_time_seconds = DATEDIFF(ss,bDiff.backup_start_date, bDiff.backup_finish_date) - , diff_file_size_mb = bDiff.backup_size / 1048576.0 - FROM #RTORecoveryPoints rp - INNER JOIN ' + QUOTENAME(@MSDBName) + N'.dbo.backupset bFull ON rp.database_guid = bFull.database_guid AND rp.database_name = bFull.database_name AND rp.full_last_lsn = bFull.last_lsn - LEFT OUTER JOIN ' + QUOTENAME(@MSDBName) + N'.dbo.backupset bDiff ON rp.database_guid = bDiff.database_guid AND rp.database_name = bDiff.database_name AND rp.diff_last_lsn = bDiff.last_lsn AND bDiff.last_lsn IS NOT NULL; - '; - - IF @Debug = 1 - PRINT @StringToExecute; - - EXEC sys.sp_executesql @StringToExecute; - - -/* Get time & size totals for logs */ - -RAISERROR('Get time & size totals for logs', 0, 1) WITH NOWAIT; - - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - - SET @StringToExecute += N' - WITH LogTotals AS ( - SELECT rp.id, log_time_seconds = SUM(DATEDIFF(ss,bLog.backup_start_date, bLog.backup_finish_date)) - , log_file_size = SUM(bLog.backup_size) - , SUM(1) AS log_backups - FROM #RTORecoveryPoints rp - INNER JOIN ' + QUOTENAME(@MSDBName) + N'.dbo.backupset bLog ON rp.database_guid = bLog.database_guid AND rp.database_name = bLog.database_name AND bLog.type = ''L'' - AND bLog.first_lsn > COALESCE(rp.diff_last_lsn, rp.full_last_lsn) - AND bLog.first_lsn <= rp.log_last_lsn - GROUP BY rp.id - ) - UPDATE #RTORecoveryPoints - SET log_time_seconds = lt.log_time_seconds - , log_file_size_mb = lt.log_file_size / 1048576.0 - , log_backups = lt.log_backups - FROM #RTORecoveryPoints rp - INNER JOIN LogTotals lt ON rp.id = lt.id; - '; - - IF @Debug = 1 - PRINT @StringToExecute; - - EXEC sys.sp_executesql @StringToExecute; - -RAISERROR('Gathering RTO worst cases', 0, 1) WITH NOWAIT; - - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - - SET @StringToExecute += N' - WITH WorstCases AS ( - SELECT rp.* - FROM #RTORecoveryPoints rp - LEFT OUTER JOIN #RTORecoveryPoints rpNewer - ON rp.database_guid = rpNewer.database_guid - AND rp.database_name = rpNewer.database_name - AND rp.full_last_lsn < rpNewer.full_last_lsn - AND rpNewer.rto_worst_case_size_mb = (SELECT TOP 1 rto_worst_case_size_mb FROM #RTORecoveryPoints s WHERE rp.database_guid = s.database_guid AND rp.database_name = s.database_name ORDER BY rto_worst_case_size_mb DESC) - WHERE rp.rto_worst_case_size_mb = (SELECT TOP 1 rto_worst_case_size_mb FROM #RTORecoveryPoints s WHERE rp.database_guid = s.database_guid AND rp.database_name = s.database_name ORDER BY rto_worst_case_size_mb DESC) - /* OR rp.rto_worst_case_time_seconds = (SELECT TOP 1 rto_worst_case_time_seconds FROM #RTORecoveryPoints s WHERE rp.database_guid = s.database_guid AND rp.database_name = s.database_name ORDER BY rto_worst_case_time_seconds DESC) */ - AND rpNewer.database_guid IS NULL - ) - UPDATE #Backups - SET RTOWorstCaseMinutes = - /* Fulls */ - (CASE WHEN @RestoreSpeedFullMBps IS NULL - THEN wc.full_time_seconds / 60.0 - ELSE @RestoreSpeedFullMBps / wc.full_file_size_mb - END) - - /* Diffs, which might not have been taken */ - + (CASE WHEN @RestoreSpeedDiffMBps IS NOT NULL AND wc.diff_file_size_mb IS NOT NULL - THEN @RestoreSpeedDiffMBps / wc.diff_file_size_mb - ELSE COALESCE(wc.diff_time_seconds,0) / 60.0 - END) - - /* Logs, which might not have been taken */ - + (CASE WHEN @RestoreSpeedLogMBps IS NOT NULL AND wc.log_file_size_mb IS NOT NULL - THEN @RestoreSpeedLogMBps / wc.log_file_size_mb - ELSE COALESCE(wc.log_time_seconds,0) / 60.0 - END) - , RTOWorstCaseBackupFileSizeMB = wc.rto_worst_case_size_mb - FROM #Backups b - INNER JOIN WorstCases wc - ON b.database_guid = wc.database_guid - AND b.database_name = wc.database_name; - '; - - IF @Debug = 1 - PRINT @StringToExecute; - - EXEC sys.sp_executesql @StringToExecute, N'@RestoreSpeedFullMBps INT, @RestoreSpeedDiffMBps INT, @RestoreSpeedLogMBps INT', @RestoreSpeedFullMBps, @RestoreSpeedDiffMBps, @RestoreSpeedLogMBps; - - - -/*Populating Recoverability*/ - - - /*Get distinct list of databases*/ - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - - SET @StringToExecute += N' - SELECT DISTINCT b.database_name, database_guid - FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b;' - - IF @Debug = 1 - PRINT @StringToExecute; - - INSERT #Recoverability ( DatabaseName, DatabaseGUID ) - EXEC sys.sp_executesql @StringToExecute; - - - /*Find most recent recovery model, backup size, and backup date*/ - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - - SET @StringToExecute += N' - UPDATE r - SET r.LastBackupRecoveryModel = ca.recovery_model, - r.LastFullBackupSizeMB = ca.compressed_backup_size, - r.LastFullBackupDate = ca.backup_finish_date - FROM #Recoverability r - CROSS APPLY ( - SELECT TOP 1 b.recovery_model, (b.compressed_backup_size / 1048576.0) AS compressed_backup_size, b.backup_finish_date - FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b - WHERE r.DatabaseName = b.database_name - AND r.DatabaseGUID = b.database_guid - AND b.type = ''D'' - AND b.backup_finish_date > @StartTime - ORDER BY b.backup_finish_date DESC - ) ca;' - - IF @Debug = 1 - PRINT @StringToExecute; - - EXEC sys.sp_executesql @StringToExecute, N'@StartTime DATETIME2', @StartTime; - - /*Find first backup size and date*/ - SET @StringToExecute =N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - - SET @StringToExecute += N' - UPDATE r - SET r.FirstFullBackupSizeMB = ca.compressed_backup_size, - r.FirstFullBackupDate = ca.backup_finish_date - FROM #Recoverability r - CROSS APPLY ( - SELECT TOP 1 (b.compressed_backup_size / 1048576.0) AS compressed_backup_size, b.backup_finish_date - FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b - WHERE r.DatabaseName = b.database_name - AND r.DatabaseGUID = b.database_guid - AND b.type = ''D'' - AND b.backup_finish_date > @StartTime - ORDER BY b.backup_finish_date ASC - ) ca;' - - IF @Debug = 1 - PRINT @StringToExecute; - - EXEC sys.sp_executesql @StringToExecute, N'@StartTime DATETIME2', @StartTime; - - - /*Find average backup throughputs for full, diff, and log*/ - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - - SET @StringToExecute += N' - UPDATE r - SET r.AvgFullBackupThroughputMB = ca_full.AvgFullSpeed, - r.AvgDiffBackupThroughputMB = ca_diff.AvgDiffSpeed, - r.AvgLogBackupThroughputMB = ca_log.AvgLogSpeed, - r.AvgFullBackupDurationSeconds = AvgFullDuration, - r.AvgDiffBackupDurationSeconds = AvgDiffDuration, - r.AvgLogBackupDurationSeconds = AvgLogDuration - FROM #Recoverability AS r - OUTER APPLY ( - SELECT b.database_name, - AVG( b.compressed_backup_size / ( DATEDIFF(ss, b.backup_start_date, b.backup_finish_date) ) / 1048576.0 ) AS AvgFullSpeed, - AVG( DATEDIFF(ss, b.backup_start_date, b.backup_finish_date) ) AS AvgFullDuration - FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset b - WHERE r.DatabaseName = b.database_name - AND r.DatabaseGUID = b.database_guid - AND b.type = ''D'' - AND DATEDIFF(SECOND, b.backup_start_date, b.backup_finish_date) > 0 - AND b.backup_finish_date > @StartTime - GROUP BY b.database_name - ) ca_full - OUTER APPLY ( - SELECT b.database_name, - AVG( b.compressed_backup_size / ( DATEDIFF(ss, b.backup_start_date, b.backup_finish_date) ) / 1048576.0 ) AS AvgDiffSpeed, - AVG( DATEDIFF(ss, b.backup_start_date, b.backup_finish_date) ) AS AvgDiffDuration - FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset b - WHERE r.DatabaseName = b.database_name - AND r.DatabaseGUID = b.database_guid - AND b.type = ''I'' - AND DATEDIFF(SECOND, b.backup_start_date, b.backup_finish_date) > 0 - AND b.backup_finish_date > @StartTime - GROUP BY b.database_name - ) ca_diff - OUTER APPLY ( - SELECT b.database_name, - AVG( b.compressed_backup_size / ( DATEDIFF(ss, b.backup_start_date, b.backup_finish_date) ) / 1048576.0 ) AS AvgLogSpeed, - AVG( DATEDIFF(ss, b.backup_start_date, b.backup_finish_date) ) AS AvgLogDuration - FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset b - WHERE r.DatabaseName = b.database_name - AND r.DatabaseGUID = b.database_guid - AND b.type = ''L'' - AND DATEDIFF(SECOND, b.backup_start_date, b.backup_finish_date) > 0 - AND b.backup_finish_date > @StartTime - GROUP BY b.database_name - ) ca_log;' - - IF @Debug = 1 - PRINT @StringToExecute; - - EXEC sys.sp_executesql @StringToExecute, N'@StartTime DATETIME2', @StartTime; - - - /*Find max and avg diff and log sizes*/ - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - - SET @StringToExecute += N' - UPDATE r - SET r.AvgFullSizeMB = fulls.avg_full_size, - r.AvgDiffSizeMB = diffs.avg_diff_size, - r.AvgLogSizeMB = logs.avg_log_size - FROM #Recoverability AS r - OUTER APPLY ( - SELECT b.database_name, AVG(b.compressed_backup_size / 1048576.0) AS avg_full_size - FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b - WHERE r.DatabaseName = b.database_name - AND r.DatabaseGUID = b.database_guid - AND b.type = ''D'' - AND b.backup_finish_date > @StartTime - GROUP BY b.database_name - ) AS fulls - OUTER APPLY ( - SELECT b.database_name, AVG(b.compressed_backup_size / 1048576.0) AS avg_diff_size - FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b - WHERE r.DatabaseName = b.database_name - AND r.DatabaseGUID = b.database_guid - AND b.type = ''I'' - AND b.backup_finish_date > @StartTime - GROUP BY b.database_name - ) AS diffs - OUTER APPLY ( - SELECT b.database_name, AVG(b.compressed_backup_size / 1048576.0) AS avg_log_size - FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b - WHERE r.DatabaseName = b.database_name - AND r.DatabaseGUID = b.database_guid - AND b.type = ''L'' - AND b.backup_finish_date > @StartTime - GROUP BY b.database_name - ) AS logs;' - - IF @Debug = 1 - PRINT @StringToExecute; - - EXEC sys.sp_executesql @StringToExecute, N'@StartTime DATETIME2', @StartTime; - -/*Trending - only works if backupfile is populated, which means in msdb */ -IF @MSDBName = N'msdb' -BEGIN - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' --+ @crlf; - - SET @StringToExecute += N' - SELECT p.DatabaseName, - p.DatabaseGUID, - p.[0], - p.[-1], - p.[-2], - p.[-3], - p.[-4], - p.[-5], - p.[-6], - p.[-7], - p.[-8], - p.[-9], - p.[-10], - p.[-11], - p.[-12] - FROM ( SELECT b.database_name AS DatabaseName, - b.database_guid AS DatabaseGUID, - DATEDIFF(MONTH, @StartTime, b.backup_start_date) AS MonthsAgo , - CONVERT(DECIMAL(18, 2), AVG(bf.file_size / 1048576.0)) AS AvgSizeMB - FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b - INNER JOIN ' + QUOTENAME(@MSDBName) + N'.dbo.backupfile AS bf - ON b.backup_set_id = bf.backup_set_id - WHERE b.database_name NOT IN ( ''master'', ''msdb'', ''model'', ''tempdb'' ) - AND bf.file_type = ''D'' - AND b.backup_start_date >= DATEADD(YEAR, -1, @StartTime) - AND b.backup_start_date <= SYSDATETIME() - GROUP BY b.database_name, - b.database_guid, - DATEDIFF(mm, @StartTime, b.backup_start_date) - ) AS bckstat PIVOT ( SUM(bckstat.AvgSizeMB) FOR bckstat.MonthsAgo IN ( [0], [-1], [-2], [-3], [-4], [-5], [-6], [-7], [-8], [-9], [-10], [-11], [-12] ) ) AS p - ORDER BY p.DatabaseName; - ' - - IF @Debug = 1 - PRINT @StringToExecute; - - INSERT #Trending ( DatabaseName, DatabaseGUID, [0], [-1], [-2], [-3], [-4], [-5], [-6], [-7], [-8], [-9], [-10], [-11], [-12] ) - EXEC sys.sp_executesql @StringToExecute, N'@StartTime DATETIME2', @StartTime; - -END - -/*End Trending*/ - -/*End populating Recoverability*/ - -RAISERROR('Returning data', 0, 1) WITH NOWAIT; - - SELECT b.* - FROM #Backups AS b - ORDER BY b.database_name; - - SELECT r.*, - t.[0], t.[-1], t.[-2], t.[-3], t.[-4], t.[-5], t.[-6], t.[-7], t.[-8], t.[-9], t.[-10], t.[-11], t.[-12] - FROM #Recoverability AS r - LEFT JOIN #Trending t - ON r.DatabaseName = t.DatabaseName - AND r.DatabaseGUID = t.DatabaseGUID - WHERE r.LastBackupRecoveryModel IS NOT NULL - ORDER BY r.DatabaseName - - -RAISERROR('Rules analysis starting', 0, 1) WITH NOWAIT; - -/*Looking for out of band backups by finding most common backup operator user_name and noting backups taken by other user_names*/ - - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - - SET @StringToExecute += N' - WITH common_people AS ( - SELECT TOP 1 b.user_name, COUNT_BIG(*) AS Records - FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b - GROUP BY b.user_name - ORDER BY Records DESC - ) - SELECT - 1 AS CheckId, - 100 AS [Priority], - b.database_name AS [Database Name], - ''Non-Agent backups taken'' AS [Finding], - ''The database '' + QUOTENAME(b.database_name) + '' has been backed up by '' + QUOTENAME(b.user_name) + '' '' + CONVERT(VARCHAR(10), COUNT(*)) + '' times.'' AS [Warning] - FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b - WHERE b.user_name NOT LIKE ''%Agent%'' AND b.user_name NOT LIKE ''%AGENT%'' - AND NOT EXISTS ( - SELECT 1 - FROM common_people AS cp - WHERE cp.user_name = b.user_name - ) - GROUP BY b.database_name, b.user_name - HAVING COUNT(*) > 1;' + @crlf; - - IF @Debug = 1 - PRINT @StringToExecute; - - INSERT #Warnings (CheckId, Priority, DatabaseName, Finding, Warning ) - EXEC sys.sp_executesql @StringToExecute; - - /*Looking for compatibility level changing. Only looking for databases that have changed more than twice (It''s possible someone may have changed up, had CE problems, and then changed back)*/ - - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - - SET @StringToExecute += N'SELECT - 2 AS CheckId, - 100 AS [Priority], - b.database_name AS [Database Name], - ''Compatibility level changing'' AS [Finding], - ''The database '' + QUOTENAME(b.database_name) + '' has changed compatibility levels '' + CONVERT(VARCHAR(10), COUNT(DISTINCT b.compatibility_level)) + '' times.'' AS [Warning] - FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b - GROUP BY b.database_name - HAVING COUNT(DISTINCT b.compatibility_level) > 2;' + @crlf; - - IF @Debug = 1 - PRINT @StringToExecute; - - INSERT #Warnings ( CheckId, Priority, DatabaseName, Finding, Warning ) - EXEC sys.sp_executesql @StringToExecute; - - /*Looking for password protected backups. This hasn''t been a popular option ever, and was largely replaced by encrypted backups, but it''s simple to check for.*/ - - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - - SET @StringToExecute += N'SELECT - 3 AS CheckId, - 100 AS [Priority], - b.database_name AS [Database Name], - ''Password backups'' AS [Finding], - ''The database '' + QUOTENAME(b.database_name) + '' has been backed up with a password '' + CONVERT(VARCHAR(10), COUNT(*)) + '' times. Who has the password?'' AS [Warning] - FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b - WHERE b.is_password_protected = 1 - GROUP BY b.database_name;' + @crlf; - - IF @Debug = 1 - PRINT @StringToExecute; - - INSERT #Warnings ( CheckId, Priority, DatabaseName, Finding, Warning ) - EXEC sys.sp_executesql @StringToExecute; - - /*Looking for snapshot backups. There are legit reasons for these, but we should flag them so the questions get asked. What questions? Good question.*/ - - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - - SET @StringToExecute += N'SELECT - 4 AS CheckId, - 100 AS [Priority], - b.database_name AS [Database Name], - ''Snapshot backups'' AS [Finding], - ''The database '' + QUOTENAME(b.database_name) + '' has had '' + CONVERT(VARCHAR(10), COUNT(*)) + '' snapshot backups. This message is purely informational.'' AS [Warning] - FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b - WHERE b.is_snapshot = 1 - GROUP BY b.database_name;' + @crlf; - - IF @Debug = 1 - PRINT @StringToExecute; - - INSERT #Warnings ( CheckId, Priority, DatabaseName, Finding, Warning ) - EXEC sys.sp_executesql @StringToExecute; - - /*It''s fine to take backups of read only databases, but it''s not always necessary (there''s no new data, after all).*/ - - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - - SET @StringToExecute += N'SELECT - 5 AS CheckId, - 100 AS [Priority], - b.database_name AS [Database Name], - ''Read only state backups'' AS [Finding], - ''The database '' + QUOTENAME(b.database_name) + '' has been backed up '' + CONVERT(VARCHAR(10), COUNT(*)) + '' times while in a read-only state. This can be normal if it''''s a secondary, but a bit odd otherwise.'' AS [Warning] - FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b - WHERE b.is_readonly = 1 - GROUP BY b.database_name;' + @crlf; - - IF @Debug = 1 - PRINT @StringToExecute; - - INSERT #Warnings ( CheckId, Priority, DatabaseName, Finding, Warning ) - EXEC sys.sp_executesql @StringToExecute; - - /*So, I''ve come across people who think they need to change their database to single user mode to take a backup. Or that doing that will help something. I just need to know, here.*/ - - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - - SET @StringToExecute += N'SELECT - 6 AS CheckId, - 100 AS [Priority], - b.database_name AS [Database Name], - ''Single user mode backups'' AS [Finding], - ''The database '' + QUOTENAME(b.database_name) + '' has been backed up '' + CONVERT(VARCHAR(10), COUNT(*)) + '' times while in single-user mode. This is really weird! Make sure your backup process doesn''''t include a mode change anywhere.'' AS [Warning] - FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b - WHERE b.is_single_user = 1 - GROUP BY b.database_name;' + @crlf; - - IF @Debug = 1 - PRINT @StringToExecute; - - INSERT #Warnings ( CheckId, Priority, DatabaseName, Finding, Warning ) - EXEC sys.sp_executesql @StringToExecute; - - /*C''mon, it''s 2017. Take your backups with CHECKSUMS, people.*/ - - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - - SET @StringToExecute += N'SELECT - 7 AS CheckId, - 100 AS [Priority], - b.database_name AS [Database Name], - ''No CHECKSUMS'' AS [Finding], - ''The database '' + QUOTENAME(b.database_name) + '' has been backed up '' + CONVERT(VARCHAR(10), COUNT(*)) + '' times without CHECKSUMS in the past 30 days. CHECKSUMS can help alert you to corruption errors.'' AS [Warning] - FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b - WHERE b.has_backup_checksums = 0 - AND b.backup_finish_date >= DATEADD(DAY, -30, SYSDATETIME()) - GROUP BY b.database_name;' + @crlf; - - IF @Debug = 1 - PRINT @StringToExecute; - - INSERT #Warnings ( CheckId, Priority, DatabaseName, Finding, Warning ) - EXEC sys.sp_executesql @StringToExecute; - - /*Damaged is a Black Flag album. You don''t want your backups to be like a Black Flag album. */ - - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - - SET @StringToExecute += N'SELECT - 8 AS CheckId, - 100 AS [Priority], - b.database_name AS [Database Name], - ''Damaged backups'' AS [Finding], - ''The database '' + QUOTENAME(b.database_name) + '' has had '' + CONVERT(VARCHAR(10), COUNT(*)) + '' damaged backups taken without stopping to throw an error. This is done by specifying CONTINUE_AFTER_ERROR in your BACKUP commands.'' AS [Warning] - FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b - WHERE b.is_damaged = 1 - GROUP BY b.database_name;' + @crlf; - - IF @Debug = 1 - PRINT @StringToExecute; - - INSERT #Warnings ( CheckId, Priority, DatabaseName, Finding, Warning ) - EXEC sys.sp_executesql @StringToExecute; - - /*Checking for encrypted backups and the last backup of the encryption key.*/ - - /*2014 ONLY*/ - -IF @ProductVersionMajor >= 12 - BEGIN - - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - - SET @StringToExecute += N'SELECT - 9 AS CheckId, - 100 AS [Priority], - b.database_name AS [Database Name], - ''Encrypted backups'' AS [Finding], - ''The database '' + QUOTENAME(b.database_name) + '' has had '' + CONVERT(VARCHAR(10), COUNT(*)) + '' '' + b.encryptor_type + '' backups, and the last time a certificate was backed up is ' - + CASE WHEN LOWER(@MSDBName) <> N'msdb' - THEN + N'...well, that information is on another server, anyway.'' AS [Warning]' - ELSE + CONVERT(VARCHAR(30), (SELECT MAX(c.pvt_key_last_backup_date) FROM sys.certificates AS c WHERE c.name NOT LIKE '##%')) + N'.'' AS [Warning]' - END + - N' - FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b - WHERE b.encryptor_type IS NOT NULL - GROUP BY b.database_name, b.encryptor_type;' + @crlf; - - IF @Debug = 1 - PRINT @StringToExecute; - - INSERT #Warnings ( CheckId, Priority, DatabaseName, Finding, Warning ) - EXEC sys.sp_executesql @StringToExecute; - - END - - /*Looking for backups that have BULK LOGGED data in them -- this can screw up point in time LOG recovery.*/ - - SET @StringToExecute =N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - - SET @StringToExecute += N'SELECT - 10 AS CheckId, - 100 AS [Priority], - b.database_name AS [Database Name], - ''Bulk logged backups'' AS [Finding], - ''The database '' + QUOTENAME(b.database_name) + '' has had '' + CONVERT(VARCHAR(10), COUNT(*)) + '' backups with bulk logged data. This can make point in time recovery awkward. '' AS [Warning] - FROM ' + QUOTENAME(@MSDBName) + '.dbo.backupset AS b - WHERE b.has_bulk_logged_data = 1 - GROUP BY b.database_name;' + @crlf; - - IF @Debug = 1 - PRINT @StringToExecute; - - INSERT #Warnings ( CheckId, Priority, DatabaseName, Finding, Warning ) - EXEC sys.sp_executesql @StringToExecute; - - /*Looking for recovery model being switched between FULL and SIMPLE, because it''s a bad practice.*/ - - SET @StringToExecute =N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - - SET @StringToExecute += N'SELECT - 11 AS CheckId, - 100 AS [Priority], - b.database_name AS [Database Name], - ''Recovery model switched'' AS [Finding], - ''The database '' + QUOTENAME(b.database_name) + '' has changed recovery models from between FULL and SIMPLE '' + CONVERT(VARCHAR(10), COUNT(DISTINCT b.recovery_model)) + '' times. This breaks the log chain and is generally a bad idea.'' AS [Warning] - FROM ' + QUOTENAME(@MSDBName) + '.dbo.backupset AS b - WHERE b.recovery_model <> ''BULK-LOGGED'' - GROUP BY b.database_name - HAVING COUNT(DISTINCT b.recovery_model) > 4;' + @crlf; - - IF @Debug = 1 - PRINT @StringToExecute; - - INSERT #Warnings ( CheckId, Priority, DatabaseName, Finding, Warning ) - EXEC sys.sp_executesql @StringToExecute; - - /*Looking for uncompressed backups.*/ - - SET @StringToExecute =N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - - SET @StringToExecute += N'SELECT - 12 AS CheckId, - 100 AS [Priority], - b.database_name AS [Database Name], - ''Uncompressed backups'' AS [Finding], - ''The database '' + QUOTENAME(b.database_name) + '' has had '' + CONVERT(VARCHAR(10), COUNT(*)) + '' uncompressed backups in the last 30 days. This is a free way to save time and space. And SPACETIME. If your version of SQL supports it.'' AS [Warning] - FROM ' + QUOTENAME(@MSDBName) + '.dbo.backupset AS b - WHERE backup_size = compressed_backup_size AND type = ''D'' - AND b.backup_finish_date >= DATEADD(DAY, -30, SYSDATETIME()) - GROUP BY b.database_name;' + @crlf; - - IF @Debug = 1 - PRINT @StringToExecute; - - INSERT #Warnings ( CheckId, Priority, DatabaseName, Finding, Warning ) - EXEC sys.sp_executesql @StringToExecute; - -RAISERROR('Rules analysis starting on temp tables', 0, 1) WITH NOWAIT; - - INSERT #Warnings ( CheckId, Priority, DatabaseName, Finding, Warning ) - SELECT - 13 AS CheckId, - 100 AS Priority, - r.DatabaseName as [DatabaseName], - 'Big Diffs' AS [Finding], - 'On average, Differential backups for this database are >=40% of the size of the average Full backup.' AS [Warning] - FROM #Recoverability AS r - WHERE r.IsBigDiff = 1 - - INSERT #Warnings ( CheckId, Priority, DatabaseName, Finding, Warning ) - SELECT - 13 AS CheckId, - 100 AS Priority, - r.DatabaseName as [DatabaseName], - 'Big Logs' AS [Finding], - 'On average, Log backups for this database are >=20% of the size of the average Full backup.' AS [Warning] - FROM #Recoverability AS r - WHERE r.IsBigLog = 1 - - - -/*Insert thank you stuff last*/ - INSERT #Warnings ( CheckId, Priority, DatabaseName, Finding, Warning ) - - SELECT - 2147483647 AS [CheckId], - 2147483647 AS [Priority], - 'From Your Community Volunteers' AS [DatabaseName], - 'sp_BlitzBackups Version: ' + @Version + ', Version Date: ' + CONVERT(VARCHAR(30), @VersionDate) + '.' AS [Finding], - 'Thanks for using our stored procedure. We hope you find it useful! Check out our other free SQL Server scripts at firstresponderkit.org!' AS [Warning]; - -RAISERROR('Rules analysis finished', 0, 1) WITH NOWAIT; - -SELECT w.CheckId, w.Priority, w.DatabaseName, w.Finding, w.Warning -FROM #Warnings AS w -ORDER BY w.Priority, w.CheckId; - -DROP TABLE #Backups, #Warnings, #Recoverability, #RTORecoveryPoints - - -RETURN; - -PushBackupHistoryToListener: - -RAISERROR('Pushing backup history to listener', 0, 1) WITH NOWAIT; - -DECLARE @msg NVARCHAR(4000) = N''; -DECLARE @RemoteCheck TABLE (c INT NULL); - - -IF @WriteBackupsToDatabaseName IS NULL - BEGIN - RAISERROR('@WriteBackupsToDatabaseName can''t be NULL.', 16, 1) WITH NOWAIT - RETURN; - END - -IF LOWER(@WriteBackupsToDatabaseName) = N'msdb' - BEGIN - RAISERROR('We can''t write to the real msdb, we have to write to a fake msdb.', 16, 1) WITH NOWAIT - RETURN; - END - -IF @WriteBackupsToListenerName IS NULL -BEGIN - IF @AGName IS NULL - BEGIN - RAISERROR('@WriteBackupsToListenerName and @AGName can''t both be NULL.', 16, 1) WITH NOWAIT; - RETURN; - END - ELSE - BEGIN - SELECT @WriteBackupsToListenerName = dns_name - FROM sys.availability_groups AS ag - JOIN sys.availability_group_listeners AS agl - ON ag.group_id = agl.group_id - WHERE name = @AGName; - END - -END - -IF @WriteBackupsToListenerName IS NOT NULL -BEGIN - IF NOT EXISTS - ( - SELECT * - FROM sys.servers s - WHERE name = @WriteBackupsToListenerName - ) - BEGIN - SET @msg = N'We need a linked server to write data across. Please set one up for ' + @WriteBackupsToListenerName + N'.'; - RAISERROR(@msg, 16, 1) WITH NOWAIT; - RETURN; - END -END - - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - - SET @StringToExecute += N'SELECT TOP 1 1 FROM ' - + QUOTENAME(@WriteBackupsToListenerName) + N'.master.sys.databases d WHERE d.name = @i_WriteBackupsToDatabaseName;' - - IF @Debug = 1 - PRINT @StringToExecute; - - INSERT @RemoteCheck (c) - EXEC sp_executesql @StringToExecute, N'@i_WriteBackupsToDatabaseName NVARCHAR(256)', @i_WriteBackupsToDatabaseName = @WriteBackupsToDatabaseName; - - IF @@ROWCOUNT = 0 - BEGIN - SET @msg = N'The database ' + @WriteBackupsToDatabaseName + N' doesn''t appear to exist on that server.' - RAISERROR(@msg, 16, 1) WITH NOWAIT - RETURN; - END - - - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - - SET @StringToExecute += N'SELECT TOP 1 1 FROM ' - + QUOTENAME(@WriteBackupsToListenerName) + '.' + QUOTENAME(@WriteBackupsToDatabaseName) + '.sys.tables WHERE name = ''backupset'' AND SCHEMA_NAME(schema_id) = ''dbo''; - ' + @crlf; - - IF @Debug = 1 - PRINT @StringToExecute; - - INSERT @RemoteCheck (c) - EXEC sp_executesql @StringToExecute; - - IF @@ROWCOUNT = 0 - BEGIN - - SET @msg = N'The database ' + @WriteBackupsToDatabaseName + N' doesn''t appear to have a table called dbo.backupset in it.' - RAISERROR(@msg, 0, 1) WITH NOWAIT - RAISERROR('Don''t worry, we''ll create it for you!', 0, 1) WITH NOWAIT - - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - - SET @StringToExecute += N'CREATE TABLE ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.dbo.backupset - ( backup_set_id INT IDENTITY(1, 1), backup_set_uuid UNIQUEIDENTIFIER, media_set_id INT, first_family_number TINYINT, first_media_number SMALLINT, - last_family_number TINYINT, last_media_number SMALLINT, catalog_family_number TINYINT, catalog_media_number SMALLINT, position INT, expiration_date DATETIME, - software_vendor_id INT, name NVARCHAR(128), description NVARCHAR(255), user_name NVARCHAR(128), software_major_version TINYINT, software_minor_version TINYINT, - software_build_version SMALLINT, time_zone SMALLINT, mtf_minor_version TINYINT, first_lsn NUMERIC(25, 0), last_lsn NUMERIC(25, 0), checkpoint_lsn NUMERIC(25, 0), - database_backup_lsn NUMERIC(25, 0), database_creation_date DATETIME, backup_start_date DATETIME, backup_finish_date DATETIME, type CHAR(1), sort_order SMALLINT, - code_page SMALLINT, compatibility_level TINYINT, database_version INT, backup_size NUMERIC(20, 0), database_name NVARCHAR(128), server_name NVARCHAR(128), - machine_name NVARCHAR(128), flags INT, unicode_locale INT, unicode_compare_style INT, collation_name NVARCHAR(128), is_password_protected BIT, recovery_model NVARCHAR(60), - has_bulk_logged_data BIT, is_snapshot BIT, is_readonly BIT, is_single_user BIT, has_backup_checksums BIT, is_damaged BIT, begins_log_chain BIT, has_incomplete_metadata BIT, - is_force_offline BIT, is_copy_only BIT, first_recovery_fork_guid UNIQUEIDENTIFIER, last_recovery_fork_guid UNIQUEIDENTIFIER, fork_point_lsn NUMERIC(25, 0), database_guid UNIQUEIDENTIFIER, - family_guid UNIQUEIDENTIFIER, differential_base_lsn NUMERIC(25, 0), differential_base_guid UNIQUEIDENTIFIER, compressed_backup_size NUMERIC(20, 0), key_algorithm NVARCHAR(32), - encryptor_thumbprint VARBINARY(20) , encryptor_type NVARCHAR(32) - ); - ' + @crlf; - - SET @InnerStringToExecute = N'EXEC( ''' + @StringToExecute + ''' ) AT ' + QUOTENAME(@WriteBackupsToListenerName) + N';' - - IF @Debug = 1 - PRINT @InnerStringToExecute; - - EXEC sp_executesql @InnerStringToExecute - - - RAISERROR('We''ll even make the indexes!', 0, 1) WITH NOWAIT - - /*Checking for and creating the PK/CX*/ - - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - - SET @StringToExecute += N' - - IF NOT EXISTS ( - SELECT t.name, i.name - FROM ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.sys.tables AS t - JOIN ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.sys.indexes AS i - ON t.object_id = i.object_id - WHERE t.name = ? - AND i.name LIKE ? - ) - - BEGIN - ALTER TABLE ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.[dbo].[backupset] ADD PRIMARY KEY CLUSTERED ([backup_set_id] ASC) - END - ' - - SET @InnerStringToExecute = N'EXEC( ''' + @StringToExecute + ''', ''backupset'', ''PK[_][_]%'' ) AT ' + QUOTENAME(@WriteBackupsToListenerName) + N';' - - IF @Debug = 1 - PRINT @InnerStringToExecute; - - EXEC sp_executesql @InnerStringToExecute - - - - /*Checking for and creating index on backup_set_uuid*/ - - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - - SET @StringToExecute += N'IF NOT EXISTS ( - SELECT t.name, i.name - FROM ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.sys.tables AS t - JOIN ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.sys.indexes AS i - ON t.object_id = i.object_id - WHERE t.name = ? - AND i.name = ? - ) - - BEGIN - CREATE NONCLUSTERED INDEX [backupsetuuid] ON ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.[dbo].[backupset] ([backup_set_uuid] ASC) - END - ' - - SET @InnerStringToExecute = N'EXEC( ''' + @StringToExecute + ''', ''backupset'', ''backupsetuuid'' ) AT ' + QUOTENAME(@WriteBackupsToListenerName) + N';' - - IF @Debug = 1 - PRINT @InnerStringToExecute; - - EXEC sp_executesql @InnerStringToExecute - - - - /*Checking for and creating index on media_set_id*/ - - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - - SET @StringToExecute += 'IF NOT EXISTS ( - SELECT t.name, i.name - FROM ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.sys.tables AS t - JOIN ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.sys.indexes AS i - ON t.object_id = i.object_id - WHERE t.name = ? - AND i.name = ? - ) - - BEGIN - CREATE NONCLUSTERED INDEX [backupsetMediaSetId] ON ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.[dbo].[backupset] ([media_set_id] ASC) - END - ' - - SET @InnerStringToExecute = N'EXEC( ''' + @StringToExecute + ''', ''backupset'', ''backupsetMediaSetId'' ) AT ' + QUOTENAME(@WriteBackupsToListenerName) + N';' - - IF @Debug = 1 - PRINT @InnerStringToExecute; - - EXEC sp_executesql @InnerStringToExecute - - - - /*Checking for and creating index on backup_finish_date*/ - - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - - SET @StringToExecute += N'IF NOT EXISTS ( - SELECT t.name, i.name - FROM ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.sys.tables AS t - JOIN ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.sys.indexes AS i - ON t.object_id = i.object_id - WHERE t.name = ? - AND i.name = ? - ) - - BEGIN - CREATE NONCLUSTERED INDEX [backupsetDate] ON ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.[dbo].[backupset] ([backup_finish_date] ASC) - END - ' - - SET @InnerStringToExecute = N'EXEC( ''' + @StringToExecute + ''', ''backupset'', ''backupsetDate'' ) AT ' + QUOTENAME(@WriteBackupsToListenerName) + N';' - - IF @Debug = 1 - PRINT @InnerStringToExecute; - - EXEC sp_executesql @InnerStringToExecute - - - - /*Checking for and creating index on database_name*/ - - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - - SET @StringToExecute += N'IF NOT EXISTS ( - SELECT t.name, i.name - FROM ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.sys.tables AS t - JOIN ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.sys.indexes AS i - ON t.object_id = i.object_id - WHERE t.name = ? - AND i.name = ? - ) - - BEGIN - CREATE NONCLUSTERED INDEX [backupsetDatabaseName] ON ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.[dbo].[backupset] ([database_name] ASC ) INCLUDE ([backup_set_id], [media_set_id]) - END - - ' - - SET @InnerStringToExecute = N'EXEC( ''' + @StringToExecute + ''', ''backupset'', ''backupsetDatabaseName'' ) AT ' + QUOTENAME(@WriteBackupsToListenerName) + N';' - - IF @Debug = 1 - PRINT @InnerStringToExecute; - - EXEC sp_executesql @InnerStringToExecute - - RAISERROR('Table and indexes created! You''re welcome!', 0, 1) WITH NOWAIT - END - - - RAISERROR('Beginning inserts', 0, 1) WITH NOWAIT; - RAISERROR(@crlf, 0, 1) WITH NOWAIT; - - /* - Batching code comes from the lovely and talented Michael J. Swart - http://michaeljswart.com/2014/09/take-care-when-scripting-batches/ - If you're ever in Canada, he says you can stay at his house, too. - */ - - - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - - SET @StringToExecute += N' - DECLARE - @StartDate DATETIME = DATEADD(HOUR, @i_WriteBackupsLastHours, SYSDATETIME()), - @StartDateNext DATETIME, - @RC INT = 1, - @msg NVARCHAR(4000) = N''''; - - SELECT @StartDate = MIN(b.backup_start_date) - FROM msdb.dbo.backupset b - WHERE b.backup_start_date >= @StartDate - AND NOT EXISTS ( - SELECT 1 - FROM ' + QUOTENAME(@WriteBackupsToListenerName) + N'.' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.dbo.backupset b2 - WHERE b.backup_set_uuid = b2.backup_set_uuid - AND b2.backup_start_date >= @StartDate - ) - - SET @StartDateNext = DATEADD(MINUTE, 10, @StartDate); - - IF - ( @StartDate IS NULL ) - BEGIN - SET @msg = N''No data to move, exiting.'' - RAISERROR(@msg, 0, 1) WITH NOWAIT - - RETURN; - END - - RAISERROR(''Starting insert loop'', 0, 1) WITH NOWAIT; - - WHILE EXISTS ( - SELECT 1 - FROM msdb.dbo.backupset b - WHERE NOT EXISTS ( - SELECT 1 - FROM ' + QUOTENAME(@WriteBackupsToListenerName) + N'.' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.dbo.backupset b2 - WHERE b.backup_set_uuid = b2.backup_set_uuid - AND b2.backup_start_date >= @StartDate - ) - ) - BEGIN - - SET @msg = N''Inserting data for '' + CONVERT(NVARCHAR(30), @StartDate) + '' through '' + + CONVERT(NVARCHAR(30), @StartDateNext) + ''.'' - RAISERROR(@msg, 0, 1) WITH NOWAIT - - ' - - SET @StringToExecute += N'INSERT ' + QUOTENAME(@WriteBackupsToListenerName) + N'.' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.dbo.backupset - ' - SET @StringToExecute += N' (database_name, database_guid, backup_set_uuid, type, backup_size, backup_start_date, backup_finish_date, media_set_id, time_zone, - compressed_backup_size, recovery_model, server_name, machine_name, first_lsn, last_lsn, user_name, compatibility_level, - is_password_protected, is_snapshot, is_readonly, is_single_user, has_backup_checksums, is_damaged, ' + CASE WHEN @ProductVersionMajor >= 12 - THEN + N'encryptor_type, has_bulk_logged_data)' + @crlf - ELSE + N'has_bulk_logged_data)' + @crlf - END - - SET @StringToExecute +=N' - SELECT database_name, database_guid, backup_set_uuid, type, backup_size, backup_start_date, backup_finish_date, media_set_id, time_zone, - compressed_backup_size, recovery_model, server_name, machine_name, first_lsn, last_lsn, user_name, compatibility_level, - is_password_protected, is_snapshot, is_readonly, is_single_user, has_backup_checksums, is_damaged, ' + CASE WHEN @ProductVersionMajor >= 12 - THEN + N'encryptor_type, has_bulk_logged_data' + @crlf - ELSE + N'has_bulk_logged_data' + @crlf - END - SET @StringToExecute +=N' - FROM msdb.dbo.backupset b - WHERE 1=1 - AND b.backup_start_date >= @StartDate - AND b.backup_start_date < @StartDateNext - AND NOT EXISTS ( - SELECT 1 - FROM ' + QUOTENAME(@WriteBackupsToListenerName) + N'.' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.dbo.backupset b2 - WHERE b.backup_set_uuid = b2.backup_set_uuid - AND b2.backup_start_date >= @StartDate - )' + @crlf; - - - SET @StringToExecute +=N' - SET @RC = @@ROWCOUNT; - - SET @msg = N''Inserted '' + CONVERT(NVARCHAR(30), @RC) + '' rows for ''+ CONVERT(NVARCHAR(30), @StartDate) + '' through '' + CONVERT(NVARCHAR(30), @StartDateNext) + ''.'' - RAISERROR(@msg, 0, 1) WITH NOWAIT - - SET @StartDate = @StartDateNext; - SET @StartDateNext = DATEADD(MINUTE, 10, @StartDate); - - IF - ( @StartDate > SYSDATETIME() ) - BEGIN - - SET @msg = N''No more data to move, exiting.'' - RAISERROR(@msg, 0, 1) WITH NOWAIT - - BREAK; - - END - END' + @crlf; - - IF @Debug = 1 - PRINT @StringToExecute; - - EXEC sp_executesql @StringToExecute, N'@i_WriteBackupsLastHours INT', @i_WriteBackupsLastHours = @WriteBackupsLastHours; - -END; - -END; - -GO -SET ANSI_NULLS ON; -SET ANSI_PADDING ON; -SET ANSI_WARNINGS ON; -SET ARITHABORT ON; -SET CONCAT_NULL_YIELDS_NULL ON; -SET QUOTED_IDENTIFIER ON; -SET STATISTICS IO OFF; -SET STATISTICS TIME OFF; -GO - -IF ( -SELECT - CASE - WHEN CONVERT(NVARCHAR(128), SERVERPROPERTY ('PRODUCTVERSION')) LIKE '8%' THEN 0 - WHEN CONVERT(NVARCHAR(128), SERVERPROPERTY ('PRODUCTVERSION')) LIKE '9%' THEN 0 - ELSE 1 - END -) = 0 -BEGIN - DECLARE @msg VARCHAR(8000); - SELECT @msg = 'Sorry, sp_BlitzCache doesn''t work on versions of SQL prior to 2008.' + REPLICATE(CHAR(13), 7933); - PRINT @msg; - RETURN; -END; - -IF OBJECT_ID('dbo.sp_BlitzCache') IS NULL - EXEC ('CREATE PROCEDURE dbo.sp_BlitzCache AS RETURN 0;'); -GO - -IF OBJECT_ID('dbo.sp_BlitzCache') IS NOT NULL AND OBJECT_ID('tempdb.dbo.##BlitzCacheProcs', 'U') IS NOT NULL - EXEC ('DROP TABLE ##BlitzCacheProcs;'); -GO - -IF OBJECT_ID('dbo.sp_BlitzCache') IS NOT NULL AND OBJECT_ID('tempdb.dbo.##BlitzCacheResults', 'U') IS NOT NULL - EXEC ('DROP TABLE ##BlitzCacheResults;'); -GO - -CREATE TABLE ##BlitzCacheResults ( - SPID INT, - ID INT IDENTITY(1,1), - CheckID INT, - Priority TINYINT, - FindingsGroup VARCHAR(50), - Finding VARCHAR(500), - URL VARCHAR(200), - Details VARCHAR(4000) -); - -CREATE TABLE ##BlitzCacheProcs ( - SPID INT , - QueryType NVARCHAR(258), - DatabaseName sysname, - AverageCPU DECIMAL(38,4), - AverageCPUPerMinute DECIMAL(38,4), - TotalCPU DECIMAL(38,4), - PercentCPUByType MONEY, - PercentCPU MONEY, - AverageDuration DECIMAL(38,4), - TotalDuration DECIMAL(38,4), - PercentDuration MONEY, - PercentDurationByType MONEY, - AverageReads BIGINT, - TotalReads BIGINT, - PercentReads MONEY, - PercentReadsByType MONEY, - ExecutionCount BIGINT, - PercentExecutions MONEY, - PercentExecutionsByType MONEY, - ExecutionsPerMinute MONEY, - TotalWrites BIGINT, - AverageWrites MONEY, - PercentWrites MONEY, - PercentWritesByType MONEY, - WritesPerMinute MONEY, - PlanCreationTime DATETIME, - PlanCreationTimeHours AS DATEDIFF(HOUR, PlanCreationTime, SYSDATETIME()), - LastExecutionTime DATETIME, - LastCompletionTime DATETIME, - PlanHandle VARBINARY(64), - [Remove Plan Handle From Cache] AS - CASE WHEN [PlanHandle] IS NOT NULL - THEN 'DBCC FREEPROCCACHE (' + CONVERT(VARCHAR(128), [PlanHandle], 1) + ');' - ELSE 'N/A' END, - SqlHandle VARBINARY(64), - [Remove SQL Handle From Cache] AS - CASE WHEN [SqlHandle] IS NOT NULL - THEN 'DBCC FREEPROCCACHE (' + CONVERT(VARCHAR(128), [SqlHandle], 1) + ');' - ELSE 'N/A' END, - [SQL Handle More Info] AS - CASE WHEN [SqlHandle] IS NOT NULL - THEN 'EXEC sp_BlitzCache @OnlySqlHandles = ''' + CONVERT(VARCHAR(128), [SqlHandle], 1) + '''; ' - ELSE 'N/A' END, - QueryHash BINARY(8), - [Query Hash More Info] AS - CASE WHEN [QueryHash] IS NOT NULL - THEN 'EXEC sp_BlitzCache @OnlyQueryHashes = ''' + CONVERT(VARCHAR(32), [QueryHash], 1) + '''; ' - ELSE 'N/A' END, - QueryPlanHash BINARY(8), - StatementStartOffset INT, - StatementEndOffset INT, - PlanGenerationNum BIGINT, - MinReturnedRows BIGINT, - MaxReturnedRows BIGINT, - AverageReturnedRows MONEY, - TotalReturnedRows BIGINT, - LastReturnedRows BIGINT, - /*The Memory Grant columns are only supported - in certain versions, giggle giggle. - */ - MinGrantKB BIGINT, - MaxGrantKB BIGINT, - MinUsedGrantKB BIGINT, - MaxUsedGrantKB BIGINT, - PercentMemoryGrantUsed MONEY, - AvgMaxMemoryGrant MONEY, - MinSpills BIGINT, - MaxSpills BIGINT, - TotalSpills BIGINT, - AvgSpills MONEY, - QueryText NVARCHAR(MAX), - QueryPlan XML, - /* these next four columns are the total for the type of query. - don't actually use them for anything apart from math by type. - */ - TotalWorkerTimeForType BIGINT, - TotalElapsedTimeForType BIGINT, - TotalReadsForType DECIMAL(30), - TotalExecutionCountForType BIGINT, - TotalWritesForType DECIMAL(30), - NumberOfPlans INT, - NumberOfDistinctPlans INT, - SerialDesiredMemory FLOAT, - SerialRequiredMemory FLOAT, - CachedPlanSize FLOAT, - CompileTime FLOAT, - CompileCPU FLOAT , - CompileMemory FLOAT , - MaxCompileMemory FLOAT , - min_worker_time BIGINT, - max_worker_time BIGINT, - is_forced_plan BIT, - is_forced_parameterized BIT, - is_cursor BIT, - is_optimistic_cursor BIT, - is_forward_only_cursor BIT, - is_fast_forward_cursor BIT, - is_cursor_dynamic BIT, - is_parallel BIT, - is_forced_serial BIT, - is_key_lookup_expensive BIT, - key_lookup_cost FLOAT, - is_remote_query_expensive BIT, - remote_query_cost FLOAT, - frequent_execution BIT, - parameter_sniffing BIT, - unparameterized_query BIT, - near_parallel BIT, - plan_warnings BIT, - plan_multiple_plans INT, - long_running BIT, - downlevel_estimator BIT, - implicit_conversions BIT, - busy_loops BIT, - tvf_join BIT, - tvf_estimate BIT, - compile_timeout BIT, - compile_memory_limit_exceeded BIT, - warning_no_join_predicate BIT, - QueryPlanCost FLOAT, - missing_index_count INT, - unmatched_index_count INT, - min_elapsed_time BIGINT, - max_elapsed_time BIGINT, - age_minutes MONEY, - age_minutes_lifetime MONEY, - is_trivial BIT, - trace_flags_session VARCHAR(1000), - is_unused_grant BIT, - function_count INT, - clr_function_count INT, - is_table_variable BIT, - no_stats_warning BIT, - relop_warnings BIT, - is_table_scan BIT, - backwards_scan BIT, - forced_index BIT, - forced_seek BIT, - forced_scan BIT, - columnstore_row_mode BIT, - is_computed_scalar BIT , - is_sort_expensive BIT, - sort_cost FLOAT, - is_computed_filter BIT, - op_name VARCHAR(100) NULL, - index_insert_count INT NULL, - index_update_count INT NULL, - index_delete_count INT NULL, - cx_insert_count INT NULL, - cx_update_count INT NULL, - cx_delete_count INT NULL, - table_insert_count INT NULL, - table_update_count INT NULL, - table_delete_count INT NULL, - index_ops AS (index_insert_count + index_update_count + index_delete_count + - cx_insert_count + cx_update_count + cx_delete_count + - table_insert_count + table_update_count + table_delete_count), - is_row_level BIT, - is_spatial BIT, - index_dml BIT, - table_dml BIT, - long_running_low_cpu BIT, - low_cost_high_cpu BIT, - stale_stats BIT, - is_adaptive BIT, - index_spool_cost FLOAT, - index_spool_rows FLOAT, - table_spool_cost FLOAT, - table_spool_rows FLOAT, - is_spool_expensive BIT, - is_spool_more_rows BIT, - is_table_spool_expensive BIT, - is_table_spool_more_rows BIT, - estimated_rows FLOAT, - is_bad_estimate BIT, - is_paul_white_electric BIT, - is_row_goal BIT, - is_big_spills BIT, - is_mstvf BIT, - is_mm_join BIT, - is_nonsargable BIT, - select_with_writes BIT, - implicit_conversion_info XML, - cached_execution_parameters XML, - missing_indexes XML, - SetOptions VARCHAR(MAX), - Warnings VARCHAR(MAX), - Pattern NVARCHAR(20) - ); -GO - -ALTER PROCEDURE dbo.sp_BlitzCache - @Help BIT = 0, - @Top INT = NULL, - @SortOrder VARCHAR(50) = 'CPU', - @UseTriggersAnyway BIT = NULL, - @ExportToExcel BIT = 0, - @ExpertMode TINYINT = 0, - @OutputType VARCHAR(20) = 'TABLE' , - @OutputServerName NVARCHAR(258) = NULL , - @OutputDatabaseName NVARCHAR(258) = NULL , - @OutputSchemaName NVARCHAR(258) = NULL , - @OutputTableName NVARCHAR(258) = NULL , -- do NOT use ##BlitzCacheResults or ##BlitzCacheProcs as they are used as work tables in this procedure - @ConfigurationDatabaseName NVARCHAR(128) = NULL , - @ConfigurationSchemaName NVARCHAR(258) = NULL , - @ConfigurationTableName NVARCHAR(258) = NULL , - @DurationFilter DECIMAL(38,4) = NULL , - @HideSummary BIT = 0 , - @IgnoreSystemDBs BIT = 1 , - @OnlyQueryHashes VARCHAR(MAX) = NULL , - @IgnoreQueryHashes VARCHAR(MAX) = NULL , - @OnlySqlHandles VARCHAR(MAX) = NULL , - @IgnoreSqlHandles VARCHAR(MAX) = NULL , - @QueryFilter VARCHAR(10) = 'ALL' , - @DatabaseName NVARCHAR(128) = NULL , - @StoredProcName NVARCHAR(128) = NULL, - @SlowlySearchPlansFor NVARCHAR(4000) = NULL, - @Reanalyze BIT = 0 , - @SkipAnalysis BIT = 0 , - @BringThePain BIT = 0 , - @MinimumExecutionCount INT = 0, - @Debug BIT = 0, - @CheckDateOverride DATETIMEOFFSET = NULL, - @MinutesBack INT = NULL, - @Version VARCHAR(30) = NULL OUTPUT, - @VersionDate DATETIME = NULL OUTPUT, - @VersionCheckMode BIT = 0 -WITH RECOMPILE -AS -BEGIN -SET NOCOUNT ON; -SET STATISTICS XML OFF; -SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - -SELECT @Version = '8.19', @VersionDate = '20240222'; -SET @OutputType = UPPER(@OutputType); - -IF(@VersionCheckMode = 1) -BEGIN - RETURN; -END; - -DECLARE @nl NVARCHAR(2) = NCHAR(13) + NCHAR(10) ; - -IF @Help = 1 - BEGIN - PRINT ' - sp_BlitzCache from http://FirstResponderKit.org - - This script displays your most resource-intensive queries from the plan cache, - and points to ways you can tune these queries to make them faster. - - - To learn more, visit http://FirstResponderKit.org where you can download new - versions for free, watch training videos on how it works, get more info on - the findings, contribute your own code, and more. - - Known limitations of this version: - - SQL Server 2008 and 2008R2 have a bug in trigger stats, so that output is - excluded by default. - - @IgnoreQueryHashes and @OnlyQueryHashes require a CSV list of hashes - with no spaces between the hash values. - - Unknown limitations of this version: - - May or may not be vulnerable to the wick effect. - - Changes - for the full list of improvements and fixes in this version, see: - https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/ - - - - MIT License - - Copyright (c) Brent Ozar Unlimited - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE. - '; - - - SELECT N'@Help' AS [Parameter Name] , - N'BIT' AS [Data Type] , - N'Displays this help message.' AS [Parameter Description] - - UNION ALL - SELECT N'@Top', - N'INT', - N'The number of records to retrieve and analyze from the plan cache. The following DMVs are used as the plan cache: dm_exec_query_stats, dm_exec_procedure_stats, dm_exec_trigger_stats.' - - UNION ALL - SELECT N'@SortOrder', - N'VARCHAR(10)', - N'Data processing and display order. @SortOrder will still be used, even when preparing output for a table or for excel. Possible values are: "CPU", "Reads", "Writes", "Duration", "Executions", "Recent Compilations", "Memory Grant", "Unused Grant", "Spills", "Query Hash", "Duplicate". Additionally, the word "Average" or "Avg" can be used to sort on averages rather than total. "Executions per minute" and "Executions / minute" can be used to sort by execution per minute. For the truly lazy, "xpm" can also be used. Note that when you use all or all avg, the only parameters you can use are @Top and @DatabaseName. All others will be ignored.' - - UNION ALL - SELECT N'@UseTriggersAnyway', - N'BIT', - N'On SQL Server 2008R2 and earlier, trigger execution count is incorrect - trigger execution count is incremented once per execution of a SQL agent job. If you still want to see relative execution count of triggers, then you can force sp_BlitzCache to include this information.' - - UNION ALL - SELECT N'@ExportToExcel', - N'BIT', - N'Prepare output for exporting to Excel. Newlines and additional whitespace are removed from query text and the execution plan is not displayed.' - - UNION ALL - SELECT N'@ExpertMode', - N'TINYINT', - N'Default 0. When set to 1, results include more columns. When 2, mode is optimized for Opserver, the open source dashboard.' - UNION ALL - SELECT N'@OutputType', - N'NVARCHAR(258)', - N'If set to "NONE", this will tell this procedure not to run any query leading to a results set thrown to caller.' - - UNION ALL - SELECT N'@OutputDatabaseName', - N'NVARCHAR(128)', - N'The output database. If this does not exist SQL Server will divide by zero and everything will fall apart.' - - UNION ALL - SELECT N'@OutputSchemaName', - N'NVARCHAR(258)', - N'The output schema. If this does not exist SQL Server will divide by zero and everything will fall apart.' - - UNION ALL - SELECT N'@OutputTableName', - N'NVARCHAR(258)', - N'The output table. If this does not exist, it will be created for you.' - - UNION ALL - SELECT N'@DurationFilter', - N'DECIMAL(38,4)', - N'Excludes queries with an average duration (in seconds) less than @DurationFilter.' - - UNION ALL - SELECT N'@HideSummary', - N'BIT', - N'Hides the findings summary result set.' - - UNION ALL - SELECT N'@IgnoreSystemDBs', - N'BIT', - N'Ignores plans found in the system databases (master, model, msdb, tempdb, dbmaintenance, dbadmin, dbatools and resourcedb)' - - UNION ALL - SELECT N'@OnlyQueryHashes', - N'VARCHAR(MAX)', - N'A list of query hashes to query. All other query hashes will be ignored. Stored procedures and triggers will be ignored.' - - UNION ALL - SELECT N'@IgnoreQueryHashes', - N'VARCHAR(MAX)', - N'A list of query hashes to ignore.' - - UNION ALL - SELECT N'@OnlySqlHandles', - N'VARCHAR(MAX)', - N'One or more sql_handles to use for filtering results.' - - UNION ALL - SELECT N'@IgnoreSqlHandles', - N'VARCHAR(MAX)', - N'One or more sql_handles to ignore.' - - UNION ALL - SELECT N'@DatabaseName', - N'NVARCHAR(128)', - N'A database name which is used for filtering results.' - - UNION ALL - SELECT N'@StoredProcName', - N'NVARCHAR(128)', - N'Name of stored procedure you want to find plans for.' - - UNION ALL - SELECT N'@SlowlySearchPlansFor', - N'NVARCHAR(4000)', - N'String to search for in plan text. % wildcards allowed.' - - UNION ALL - SELECT N'@BringThePain', - N'BIT', - N'When using @SortOrder = ''all'' and @Top > 10, we require you to set @BringThePain = 1 so you understand that sp_BlitzCache will take a while to run.' - - UNION ALL - SELECT N'@QueryFilter', - N'VARCHAR(10)', - N'Filter out stored procedures or statements. The default value is ''ALL''. Allowed values are ''procedures'', ''statements'', ''functions'', or ''all'' (any variation in capitalization is acceptable).' - - UNION ALL - SELECT N'@Reanalyze', - N'BIT', - N'The default is 0. When set to 0, sp_BlitzCache will re-evalute the plan cache. Set this to 1 to reanalyze existing results' - - UNION ALL - SELECT N'@MinimumExecutionCount', - N'INT', - N'Queries with fewer than this number of executions will be omitted from results.' - - UNION ALL - SELECT N'@Debug', - N'BIT', - N'Setting this to 1 will print dynamic SQL and select data from all tables used.' - - UNION ALL - SELECT N'@MinutesBack', - N'INT', - N'How many minutes back to begin plan cache analysis. If you put in a positive number, we''ll flip it to negative.'; - - - /* Column definitions */ - SELECT N'# Executions' AS [Column Name], - N'BIGINT' AS [Data Type], - N'The number of executions of this particular query. This is computed across statements, procedures, and triggers and aggregated by the SQL handle.' AS [Column Description] - - UNION ALL - SELECT N'Executions / Minute', - N'MONEY', - N'Number of executions per minute - calculated for the life of the current plan. Plan life is the last execution time minus the plan creation time.' - - UNION ALL - SELECT N'Execution Weight', - N'MONEY', - N'An arbitrary metric of total "execution-ness". A weight of 2 is "one more" than a weight of 1.' - - UNION ALL - SELECT N'Database', - N'sysname', - N'The name of the database where the plan was encountered. If the database name cannot be determined for some reason, a value of NA will be substituted. A value of 32767 indicates the plan comes from ResourceDB.' - - UNION ALL - SELECT N'Total CPU', - N'BIGINT', - N'Total CPU time, reported in milliseconds, that was consumed by all executions of this query since the last compilation.' - - UNION ALL - SELECT N'Avg CPU', - N'BIGINT', - N'Average CPU time, reported in milliseconds, consumed by each execution of this query since the last compilation.' - - UNION ALL - SELECT N'CPU Weight', - N'MONEY', - N'An arbitrary metric of total "CPU-ness". A weight of 2 is "one more" than a weight of 1.' - - UNION ALL - SELECT N'Total Duration', - N'BIGINT', - N'Total elapsed time, reported in milliseconds, consumed by all executions of this query since last compilation.' - - UNION ALL - SELECT N'Avg Duration', - N'BIGINT', - N'Average elapsed time, reported in milliseconds, consumed by each execution of this query since the last compilation.' - - UNION ALL - SELECT N'Duration Weight', - N'MONEY', - N'An arbitrary metric of total "Duration-ness". A weight of 2 is "one more" than a weight of 1.' - - UNION ALL - SELECT N'Total Reads', - N'BIGINT', - N'Total logical reads performed by this query since last compilation.' - - UNION ALL - SELECT N'Average Reads', - N'BIGINT', - N'Average logical reads performed by each execution of this query since the last compilation.' - - UNION ALL - SELECT N'Read Weight', - N'MONEY', - N'An arbitrary metric of "Read-ness". A weight of 2 is "one more" than a weight of 1.' - - UNION ALL - SELECT N'Total Writes', - N'BIGINT', - N'Total logical writes performed by this query since last compilation.' - - UNION ALL - SELECT N'Average Writes', - N'BIGINT', - N'Average logical writes performed by each execution this query since last compilation.' - - UNION ALL - SELECT N'Write Weight', - N'MONEY', - N'An arbitrary metric of "Write-ness". A weight of 2 is "one more" than a weight of 1.' - - UNION ALL - SELECT N'Query Type', - N'NVARCHAR(258)', - N'The type of query being examined. This can be "Procedure", "Statement", or "Trigger".' - - UNION ALL - SELECT N'Query Text', - N'NVARCHAR(4000)', - N'The text of the query. This may be truncated by either SQL Server or by sp_BlitzCache(tm) for display purposes.' - - UNION ALL - SELECT N'% Executions (Type)', - N'MONEY', - N'Percent of executions relative to the type of query - e.g. 17.2% of all stored procedure executions.' - - UNION ALL - SELECT N'% CPU (Type)', - N'MONEY', - N'Percent of CPU time consumed by this query for a given type of query - e.g. 22% of CPU of all stored procedures executed.' - - UNION ALL - SELECT N'% Duration (Type)', - N'MONEY', - N'Percent of elapsed time consumed by this query for a given type of query - e.g. 12% of all statements executed.' - - UNION ALL - SELECT N'% Reads (Type)', - N'MONEY', - N'Percent of reads consumed by this query for a given type of query - e.g. 34.2% of all stored procedures executed.' - - UNION ALL - SELECT N'% Writes (Type)', - N'MONEY', - N'Percent of writes performed by this query for a given type of query - e.g. 43.2% of all statements executed.' - - UNION ALL - SELECT N'Total Rows', - N'BIGINT', - N'Total number of rows returned for all executions of this query. This only applies to query level stats, not stored procedures or triggers.' - - UNION ALL - SELECT N'Average Rows', - N'MONEY', - N'Average number of rows returned by each execution of the query.' - - UNION ALL - SELECT N'Min Rows', - N'BIGINT', - N'The minimum number of rows returned by any execution of this query.' - - UNION ALL - SELECT N'Max Rows', - N'BIGINT', - N'The maximum number of rows returned by any execution of this query.' - - UNION ALL - SELECT N'MinGrantKB', - N'BIGINT', - N'The minimum memory grant the query received in kb.' - - UNION ALL - SELECT N'MaxGrantKB', - N'BIGINT', - N'The maximum memory grant the query received in kb.' - - UNION ALL - SELECT N'MinUsedGrantKB', - N'BIGINT', - N'The minimum used memory grant the query received in kb.' - - UNION ALL - SELECT N'MaxUsedGrantKB', - N'BIGINT', - N'The maximum used memory grant the query received in kb.' - - UNION ALL - SELECT N'MinSpills', - N'BIGINT', - N'The minimum amount this query has spilled to tempdb in 8k pages.' - - UNION ALL - SELECT N'MaxSpills', - N'BIGINT', - N'The maximum amount this query has spilled to tempdb in 8k pages.' - - UNION ALL - SELECT N'TotalSpills', - N'BIGINT', - N'The total amount this query has spilled to tempdb in 8k pages.' - - UNION ALL - SELECT N'AvgSpills', - N'BIGINT', - N'The average amount this query has spilled to tempdb in 8k pages.' - - UNION ALL - SELECT N'PercentMemoryGrantUsed', - N'MONEY', - N'Result of dividing the maximum grant used by the minimum granted.' - - UNION ALL - SELECT N'AvgMaxMemoryGrant', - N'MONEY', - N'The average maximum memory grant for a query.' - - UNION ALL - SELECT N'# Plans', - N'INT', - N'The total number of execution plans found that match a given query.' - - UNION ALL - SELECT N'# Distinct Plans', - N'INT', - N'The number of distinct execution plans that match a given query. ' - + NCHAR(13) + NCHAR(10) - + N'This may be caused by running the same query across multiple databases or because of a lack of proper parameterization in the database.' - - UNION ALL - SELECT N'Created At', - N'DATETIME', - N'Time that the execution plan was last compiled.' - - UNION ALL - SELECT N'Last Execution', - N'DATETIME', - N'The last time that this query was executed.' - - UNION ALL - SELECT N'Query Plan', - N'XML', - N'The query plan. Click to display a graphical plan or, if you need to patch SSMS, a pile of XML.' - - UNION ALL - SELECT N'Plan Handle', - N'VARBINARY(64)', - N'An arbitrary identifier referring to the compiled plan this query is a part of.' - - UNION ALL - SELECT N'SQL Handle', - N'VARBINARY(64)', - N'An arbitrary identifier referring to a batch or stored procedure that this query is a part of.' - - UNION ALL - SELECT N'Query Hash', - N'BINARY(8)', - N'A hash of the query. Queries with the same query hash have similar logic but only differ by literal values or database.' - - UNION ALL - SELECT N'Warnings', - N'VARCHAR(MAX)', - N'A list of individual warnings generated by this query.' ; - - - - /* Configuration table description */ - SELECT N'Frequent Execution Threshold' AS [Configuration Parameter] , - N'100' AS [Default Value] , - N'Executions / Minute' AS [Unit of Measure] , - N'Executions / Minute before a "Frequent Execution Threshold" warning is triggered.' AS [Description] - - UNION ALL - SELECT N'Parameter Sniffing Variance Percent' , - N'30' , - N'Percent' , - N'Variance required between min/max values and average values before a "Parameter Sniffing" warning is triggered. Applies to worker time and returned rows.' - - UNION ALL - SELECT N'Parameter Sniffing IO Threshold' , - N'100,000' , - N'Logical reads' , - N'Minimum number of average logical reads before parameter sniffing checks are evaluated.' - - UNION ALL - SELECT N'Cost Threshold for Parallelism Warning' AS [Configuration Parameter] , - N'10' , - N'Percent' , - N'Trigger a "Nearly Parallel" warning when a query''s cost is within X percent of the cost threshold for parallelism.' - - UNION ALL - SELECT N'Long Running Query Warning' AS [Configuration Parameter] , - N'300' , - N'Seconds' , - N'Triggers a "Long Running Query Warning" when average duration, max CPU time, or max clock time is higher than this number.' - - UNION ALL - SELECT N'Unused Memory Grant Warning' AS [Configuration Parameter] , - N'10' , - N'Percent' , - N'Triggers an "Unused Memory Grant Warning" when a query uses >= X percent of its memory grant.'; - RETURN; - END; /* IF @Help = 1 */ - - - -/*Validate version*/ -IF ( -SELECT - CASE - WHEN CONVERT(NVARCHAR(128), SERVERPROPERTY ('PRODUCTVERSION')) LIKE '8%' THEN 0 - WHEN CONVERT(NVARCHAR(128), SERVERPROPERTY ('PRODUCTVERSION')) LIKE '9%' THEN 0 - ELSE 1 - END -) = 0 -BEGIN - DECLARE @version_msg VARCHAR(8000); - SELECT @version_msg = 'Sorry, sp_BlitzCache doesn''t work on versions of SQL prior to 2008.' + REPLICATE(CHAR(13), 7933); - PRINT @version_msg; - RETURN; -END; - -IF(@OutputType = 'NONE' AND (@OutputTableName IS NULL OR @OutputSchemaName IS NULL OR @OutputDatabaseName IS NULL)) -BEGIN - RAISERROR('This procedure should be called with a value for all @Output* parameters, as @OutputType is set to NONE',12,1); - RETURN; -END; - -IF(@OutputType = 'NONE') -BEGIN - SET @HideSummary = 1; -END; - - -/* Lets get @SortOrder set to lower case here for comparisons later */ -SET @SortOrder = LOWER(@SortOrder); - -/* Set @Top based on sort */ -IF ( - @Top IS NULL - AND @SortOrder IN ( 'all', 'all sort' ) - ) - BEGIN - SET @Top = 5; - END; - -IF ( - @Top IS NULL - AND @SortOrder NOT IN ( 'all', 'all sort' ) - ) - BEGIN - SET @Top = 10; - END; - - -/* If they want to sort by query hash, populate the @OnlyQueryHashes list for them */ -IF @SortOrder LIKE 'query hash%' - BEGIN - RAISERROR('Beginning query hash sort', 0, 1) WITH NOWAIT; - - SELECT TOP(@Top) qs.query_hash, - MAX(qs.max_worker_time) AS max_worker_time, - COUNT_BIG(*) AS records - INTO #query_hash_grouped - FROM sys.dm_exec_query_stats AS qs - CROSS APPLY ( SELECT pa.value - FROM sys.dm_exec_plan_attributes(qs.plan_handle) AS pa - WHERE pa.attribute = 'dbid' ) AS ca - GROUP BY qs.query_hash, ca.value - HAVING COUNT_BIG(*) > 1 - ORDER BY max_worker_time DESC, - records DESC; - - SELECT TOP (1) - @OnlyQueryHashes = STUFF((SELECT DISTINCT N',' + CONVERT(NVARCHAR(MAX), qhg.query_hash, 1) - FROM #query_hash_grouped AS qhg - WHERE qhg.query_hash <> 0x00 - FOR XML PATH(N''), TYPE).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 1, N'') - OPTION(RECOMPILE); - - /* When they ran it, @SortOrder probably looked like 'query hash, cpu', so strip the first sort order out: */ - SELECT @SortOrder = LTRIM(REPLACE(REPLACE(@SortOrder,'query hash', ''), ',', '')); - - /* If they just called it with @SortOrder = 'query hash', set it to 'cpu' for backwards compatibility: */ - IF @SortOrder = '' SET @SortOrder = 'cpu'; - - END - - -/* If they want to sort by duplicate, create a table with the worst offenders - issue #3345 */ -IF @SortOrder LIKE 'duplicate%' - BEGIN - RAISERROR('Beginning duplicate query hash sort', 0, 1) WITH NOWAIT; - - /* Find the query hashes that are the most duplicated */ - WITH MostCommonQueries AS ( - SELECT TOP(@Top) qs.query_hash, - COUNT_BIG(*) AS plans - FROM sys.dm_exec_query_stats AS qs - GROUP BY qs.query_hash - HAVING COUNT_BIG(*) > 100 - ORDER BY COUNT_BIG(*) DESC - ) - SELECT mcq_recent.sql_handle, mcq_recent.plan_handle, mcq_recent.creation_time AS duplicate_creation_time, mcq.plans - INTO #duplicate_query_filter - FROM MostCommonQueries mcq - CROSS APPLY ( SELECT TOP 1 qs.sql_handle, qs.plan_handle, qs.creation_time - FROM sys.dm_exec_query_stats qs - WHERE qs.query_hash = mcq.query_hash - ORDER BY qs.creation_time DESC) AS mcq_recent - OPTION (RECOMPILE); - - SET @MinimumExecutionCount = 0; - END - - -/* validate user inputs */ -IF @Top IS NULL - OR @SortOrder IS NULL - OR @QueryFilter IS NULL - OR @Reanalyze IS NULL -BEGIN - RAISERROR(N'Several parameters (@Top, @SortOrder, @QueryFilter, @renalyze) are required. Do not set them to NULL. Please try again.', 16, 1) WITH NOWAIT; - RETURN; -END; - -RAISERROR(N'Checking @MinutesBack validity.', 0, 1) WITH NOWAIT; -IF @MinutesBack IS NOT NULL - BEGIN - IF @MinutesBack > 0 - BEGIN - RAISERROR(N'Setting @MinutesBack to a negative number', 0, 1) WITH NOWAIT; - SET @MinutesBack *=-1; - END; - IF @MinutesBack = 0 - BEGIN - RAISERROR(N'@MinutesBack can''t be 0, setting to -1', 0, 1) WITH NOWAIT; - SET @MinutesBack = -1; - END; - END; - - - -DECLARE @DurationFilter_i INT, - @MinMemoryPerQuery INT, - @msg NVARCHAR(4000), - @NoobSaibot BIT = 0, - @VersionShowsAirQuoteActualPlans BIT, - @ObjectFullName NVARCHAR(2000), - @user_perm_sql NVARCHAR(MAX) = N'', - @user_perm_gb_out DECIMAL(10,2), - @common_version DECIMAL(10,2), - @buffer_pool_memory_gb DECIMAL(10,2), - @user_perm_percent DECIMAL(10,2), - @is_tokenstore_big BIT = 0, - @sort NVARCHAR(MAX) = N'', - @sort_filter NVARCHAR(MAX) = N''; - - -IF @SortOrder = 'sp_BlitzIndex' -BEGIN - RAISERROR(N'OUTSTANDING!', 0, 1) WITH NOWAIT; - SET @SortOrder = 'reads'; - SET @NoobSaibot = 1; - -END - - -/* Change duration from seconds to milliseconds */ -IF @DurationFilter IS NOT NULL - BEGIN - RAISERROR(N'Converting Duration Filter to milliseconds', 0, 1) WITH NOWAIT; - SET @DurationFilter_i = CAST((@DurationFilter * 1000.0) AS INT); - END; - -RAISERROR(N'Checking database validity', 0, 1) WITH NOWAIT; -SET @DatabaseName = LTRIM(RTRIM(@DatabaseName)) ; - -IF SERVERPROPERTY('EngineEdition') IN (5, 6) AND DB_NAME() <> @DatabaseName -BEGIN - RAISERROR('You specified a database name other than the current database, but Azure SQL DB does not allow you to change databases. Execute sp_BlitzCache from the database you want to analyze.', 16, 1); - RETURN; -END; -IF (DB_ID(@DatabaseName)) IS NULL AND @DatabaseName <> N'' -BEGIN - RAISERROR('The database you specified does not exist. Please check the name and try again.', 16, 1); - RETURN; -END; -IF (SELECT DATABASEPROPERTYEX(ISNULL(@DatabaseName, 'master'), 'Collation')) IS NULL AND SERVERPROPERTY('EngineEdition') NOT IN (5, 6, 8) -BEGIN - RAISERROR('The database you specified is not readable. Please check the name and try again. Better yet, check your server.', 16, 1); - RETURN; -END; - -SELECT @MinMemoryPerQuery = CONVERT(INT, c.value) FROM sys.configurations AS c WHERE c.name = 'min memory per query (KB)'; - -SET @SortOrder = REPLACE(REPLACE(@SortOrder, 'average', 'avg'), '.', ''); - -SET @SortOrder = CASE - WHEN @SortOrder IN ('executions per minute','execution per minute','executions / minute','execution / minute','xpm') THEN 'avg executions' - WHEN @SortOrder IN ('recent compilations','recent compilation','compile') THEN 'compiles' - WHEN @SortOrder IN ('read') THEN 'reads' - WHEN @SortOrder IN ('avg read') THEN 'avg reads' - WHEN @SortOrder IN ('write') THEN 'writes' - WHEN @SortOrder IN ('avg write') THEN 'avg writes' - WHEN @SortOrder IN ('memory grants') THEN 'memory grant' - WHEN @SortOrder IN ('avg memory grants') THEN 'avg memory grant' - WHEN @SortOrder IN ('unused grants','unused memory', 'unused memory grant', 'unused memory grants') THEN 'unused grant' - WHEN @SortOrder IN ('spill') THEN 'spills' - WHEN @SortOrder IN ('avg spill') THEN 'avg spills' - WHEN @SortOrder IN ('execution') THEN 'executions' - WHEN @SortOrder IN ('duplicates') THEN 'duplicate' - ELSE @SortOrder END - -RAISERROR(N'Checking sort order', 0, 1) WITH NOWAIT; -IF @SortOrder NOT IN ('cpu', 'avg cpu', 'reads', 'avg reads', 'writes', 'avg writes', - 'duration', 'avg duration', 'executions', 'avg executions', - 'compiles', 'memory grant', 'avg memory grant', 'unused grant', - 'spills', 'avg spills', 'all', 'all avg', 'sp_BlitzIndex', - 'query hash', 'duplicate') - BEGIN - RAISERROR(N'Invalid sort order chosen, reverting to cpu', 16, 1) WITH NOWAIT; - SET @SortOrder = 'cpu'; - END; - -SET @QueryFilter = LOWER(@QueryFilter); - -IF LEFT(@QueryFilter, 3) NOT IN ('all', 'sta', 'pro', 'fun') - BEGIN - RAISERROR(N'Invalid query filter chosen. Reverting to all.', 0, 1) WITH NOWAIT; - SET @QueryFilter = 'all'; - END; - -IF @SkipAnalysis = 1 - BEGIN - RAISERROR(N'Skip Analysis set to 1, hiding Summary', 0, 1) WITH NOWAIT; - SET @HideSummary = 1; - END; - -DECLARE @AllSortSql NVARCHAR(MAX) = N''; -DECLARE @VersionShowsMemoryGrants BIT; -IF EXISTS(SELECT * FROM sys.all_columns WHERE object_id = OBJECT_ID('sys.dm_exec_query_stats') AND name = 'max_grant_kb') - SET @VersionShowsMemoryGrants = 1; -ELSE - SET @VersionShowsMemoryGrants = 0; - -DECLARE @VersionShowsSpills BIT; -IF EXISTS(SELECT * FROM sys.all_columns WHERE object_id = OBJECT_ID('sys.dm_exec_query_stats') AND name = 'max_spills') - SET @VersionShowsSpills = 1; -ELSE - SET @VersionShowsSpills = 0; - -IF EXISTS(SELECT * FROM sys.all_columns WHERE object_id = OBJECT_ID('sys.dm_exec_query_plan_stats') AND name = 'query_plan') - SET @VersionShowsAirQuoteActualPlans = 1; -ELSE - SET @VersionShowsAirQuoteActualPlans = 0; - -IF @Reanalyze = 1 - BEGIN - IF OBJECT_ID('tempdb..##BlitzCacheResults') IS NULL - BEGIN - RAISERROR(N'##BlitzCacheResults does not exist, can''t reanalyze', 0, 1) WITH NOWAIT; - SET @Reanalyze = 0; - END - ELSE - BEGIN - RAISERROR(N'Reanalyzing current data, skipping to results', 0, 1) WITH NOWAIT; - GOTO Results; - END; - END; - - -IF @SortOrder IN ('all', 'all avg') - BEGIN - RAISERROR(N'Checking all sort orders, please be patient', 0, 1) WITH NOWAIT; - GOTO AllSorts; - END; - -RAISERROR(N'Creating temp tables for internal processing', 0, 1) WITH NOWAIT; -IF OBJECT_ID('tempdb..#only_query_hashes') IS NOT NULL - DROP TABLE #only_query_hashes ; - -IF OBJECT_ID('tempdb..#ignore_query_hashes') IS NOT NULL - DROP TABLE #ignore_query_hashes ; - -IF OBJECT_ID('tempdb..#only_sql_handles') IS NOT NULL - DROP TABLE #only_sql_handles ; - -IF OBJECT_ID('tempdb..#ignore_sql_handles') IS NOT NULL - DROP TABLE #ignore_sql_handles ; - -IF OBJECT_ID('tempdb..#p') IS NOT NULL - DROP TABLE #p; - -IF OBJECT_ID ('tempdb..#checkversion') IS NOT NULL - DROP TABLE #checkversion; - -IF OBJECT_ID ('tempdb..#configuration') IS NOT NULL - DROP TABLE #configuration; - -IF OBJECT_ID ('tempdb..#stored_proc_info') IS NOT NULL - DROP TABLE #stored_proc_info; - -IF OBJECT_ID ('tempdb..#plan_creation') IS NOT NULL - DROP TABLE #plan_creation; - -IF OBJECT_ID ('tempdb..#est_rows') IS NOT NULL - DROP TABLE #est_rows; - -IF OBJECT_ID ('tempdb..#plan_cost') IS NOT NULL - DROP TABLE #plan_cost; - -IF OBJECT_ID ('tempdb..#proc_costs') IS NOT NULL - DROP TABLE #proc_costs; - -IF OBJECT_ID ('tempdb..#stats_agg') IS NOT NULL - DROP TABLE #stats_agg; - -IF OBJECT_ID ('tempdb..#trace_flags') IS NOT NULL - DROP TABLE #trace_flags; - -IF OBJECT_ID('tempdb..#variable_info') IS NOT NULL - DROP TABLE #variable_info; - -IF OBJECT_ID('tempdb..#conversion_info') IS NOT NULL - DROP TABLE #conversion_info; - -IF OBJECT_ID('tempdb..#missing_index_xml') IS NOT NULL - DROP TABLE #missing_index_xml; - -IF OBJECT_ID('tempdb..#missing_index_schema') IS NOT NULL - DROP TABLE #missing_index_schema; - -IF OBJECT_ID('tempdb..#missing_index_usage') IS NOT NULL - DROP TABLE #missing_index_usage; - -IF OBJECT_ID('tempdb..#missing_index_detail') IS NOT NULL - DROP TABLE #missing_index_detail; - -IF OBJECT_ID('tempdb..#missing_index_pretty') IS NOT NULL - DROP TABLE #missing_index_pretty; - -IF OBJECT_ID('tempdb..#index_spool_ugly') IS NOT NULL - DROP TABLE #index_spool_ugly; - -IF OBJECT_ID('tempdb..#ReadableDBs') IS NOT NULL - DROP TABLE #ReadableDBs; - -IF OBJECT_ID('tempdb..#plan_usage') IS NOT NULL - DROP TABLE #plan_usage; - -CREATE TABLE #only_query_hashes ( - query_hash BINARY(8) -); - -CREATE TABLE #ignore_query_hashes ( - query_hash BINARY(8) -); - -CREATE TABLE #only_sql_handles ( - sql_handle VARBINARY(64) -); - -CREATE TABLE #ignore_sql_handles ( - sql_handle VARBINARY(64) -); - -CREATE TABLE #p ( - SqlHandle VARBINARY(64), - TotalCPU BIGINT, - TotalDuration BIGINT, - TotalReads BIGINT, - TotalWrites BIGINT, - ExecutionCount BIGINT -); - -CREATE TABLE #checkversion ( - version NVARCHAR(128), - common_version AS SUBSTRING(version, 1, CHARINDEX('.', version) + 1 ), - major AS PARSENAME(CONVERT(VARCHAR(32), version), 4), - minor AS PARSENAME(CONVERT(VARCHAR(32), version), 3), - build AS PARSENAME(CONVERT(VARCHAR(32), version), 2), - revision AS PARSENAME(CONVERT(VARCHAR(32), version), 1) -); - -CREATE TABLE #configuration ( - parameter_name VARCHAR(100), - value DECIMAL(38,0) -); - -CREATE TABLE #plan_creation -( - percent_24 DECIMAL(5, 2), - percent_4 DECIMAL(5, 2), - percent_1 DECIMAL(5, 2), - total_plans INT, - SPID INT -); - -CREATE TABLE #est_rows -( - QueryHash BINARY(8), - estimated_rows FLOAT -); - -CREATE TABLE #plan_cost -( - QueryPlanCost FLOAT, - SqlHandle VARBINARY(64), - PlanHandle VARBINARY(64), - QueryHash BINARY(8), - QueryPlanHash BINARY(8) -); - -CREATE TABLE #proc_costs -( - PlanTotalQuery FLOAT, - PlanHandle VARBINARY(64), - SqlHandle VARBINARY(64) -); - -CREATE TABLE #stats_agg -( - SqlHandle VARBINARY(64), - LastUpdate DATETIME2(7), - ModificationCount BIGINT, - SamplingPercent FLOAT, - [Statistics] NVARCHAR(258), - [Table] NVARCHAR(258), - [Schema] NVARCHAR(258), - [Database] NVARCHAR(258), -); - -CREATE TABLE #trace_flags -( - SqlHandle VARBINARY(64), - QueryHash BINARY(8), - global_trace_flags VARCHAR(1000), - session_trace_flags VARCHAR(1000) -); - -CREATE TABLE #stored_proc_info -( - SPID INT, - SqlHandle VARBINARY(64), - QueryHash BINARY(8), - variable_name NVARCHAR(258), - variable_datatype NVARCHAR(258), - converted_column_name NVARCHAR(258), - compile_time_value NVARCHAR(258), - proc_name NVARCHAR(1000), - column_name NVARCHAR(4000), - converted_to NVARCHAR(258), - set_options NVARCHAR(1000) -); - -CREATE TABLE #variable_info -( - SPID INT, - QueryHash BINARY(8), - SqlHandle VARBINARY(64), - proc_name NVARCHAR(1000), - variable_name NVARCHAR(258), - variable_datatype NVARCHAR(258), - compile_time_value NVARCHAR(258) -); - -CREATE TABLE #conversion_info -( - SPID INT, - QueryHash BINARY(8), - SqlHandle VARBINARY(64), - proc_name NVARCHAR(258), - expression NVARCHAR(4000), - at_charindex AS CHARINDEX('@', expression), - bracket_charindex AS CHARINDEX(']', expression, CHARINDEX('@', expression)) - CHARINDEX('@', expression), - comma_charindex AS CHARINDEX(',', expression) + 1, - second_comma_charindex AS - CHARINDEX(',', expression, CHARINDEX(',', expression) + 1) - CHARINDEX(',', expression) - 1, - equal_charindex AS CHARINDEX('=', expression) + 1, - paren_charindex AS CHARINDEX('(', expression) + 1, - comma_paren_charindex AS - CHARINDEX(',', expression, CHARINDEX('(', expression) + 1) - CHARINDEX('(', expression) - 1, - convert_implicit_charindex AS CHARINDEX('=CONVERT_IMPLICIT', expression) -); - - -CREATE TABLE #missing_index_xml -( - QueryHash BINARY(8), - SqlHandle VARBINARY(64), - impact FLOAT, - index_xml XML -); - - -CREATE TABLE #missing_index_schema -( - QueryHash BINARY(8), - SqlHandle VARBINARY(64), - impact FLOAT, - database_name NVARCHAR(128), - schema_name NVARCHAR(128), - table_name NVARCHAR(128), - index_xml XML -); - - -CREATE TABLE #missing_index_usage -( - QueryHash BINARY(8), - SqlHandle VARBINARY(64), - impact FLOAT, - database_name NVARCHAR(128), - schema_name NVARCHAR(128), - table_name NVARCHAR(128), - usage NVARCHAR(128), - index_xml XML -); - - -CREATE TABLE #missing_index_detail -( - QueryHash BINARY(8), - SqlHandle VARBINARY(64), - impact FLOAT, - database_name NVARCHAR(128), - schema_name NVARCHAR(128), - table_name NVARCHAR(128), - usage NVARCHAR(128), - column_name NVARCHAR(128) -); - - -CREATE TABLE #missing_index_pretty -( - QueryHash BINARY(8), - SqlHandle VARBINARY(64), - impact FLOAT, - database_name NVARCHAR(128), - schema_name NVARCHAR(128), - table_name NVARCHAR(128), - equality NVARCHAR(MAX), - inequality NVARCHAR(MAX), - [include] NVARCHAR(MAX), - executions NVARCHAR(128), - query_cost NVARCHAR(128), - creation_hours NVARCHAR(128), - is_spool BIT, - details AS N'/* ' - + CHAR(10) - + CASE is_spool - WHEN 0 - THEN N'The Query Processor estimates that implementing the ' - ELSE N'We estimate that implementing the ' - END - + N'following index could improve query cost (' + query_cost + N')' - + CHAR(10) - + N'by ' - + CONVERT(NVARCHAR(30), impact) - + N'% for ' + executions + N' executions of the query' - + N' over the last ' + - CASE WHEN creation_hours < 24 - THEN creation_hours + N' hours.' - WHEN creation_hours = 24 - THEN ' 1 day.' - WHEN creation_hours > 24 - THEN (CONVERT(NVARCHAR(128), creation_hours / 24)) + N' days.' - ELSE N'' - END - + CHAR(10) - + N'*/' - + CHAR(10) + CHAR(13) - + N'/* ' - + CHAR(10) - + N'USE ' - + database_name - + CHAR(10) - + N'GO' - + CHAR(10) + CHAR(13) - + N'CREATE NONCLUSTERED INDEX ix_' - + ISNULL(REPLACE(REPLACE(REPLACE(equality,'[', ''), ']', ''), ', ', '_'), '') - + ISNULL(REPLACE(REPLACE(REPLACE(inequality,'[', ''), ']', ''), ', ', '_'), '') - + CASE WHEN [include] IS NOT NULL THEN + N'_Includes' ELSE N'' END - + CHAR(10) - + N' ON ' - + schema_name - + N'.' - + table_name - + N' (' + - + CASE WHEN equality IS NOT NULL - THEN equality - + CASE WHEN inequality IS NOT NULL - THEN N', ' + inequality - ELSE N'' - END - ELSE inequality - END - + N')' - + CHAR(10) - + CASE WHEN include IS NOT NULL - THEN N'INCLUDE (' + include + N') WITH (FILLFACTOR=100, ONLINE=?, SORT_IN_TEMPDB=?, DATA_COMPRESSION=?);' - ELSE N' WITH (FILLFACTOR=100, ONLINE=?, SORT_IN_TEMPDB=?, DATA_COMPRESSION=?);' - END - + CHAR(10) - + N'GO' - + CHAR(10) - + N'*/' -); - - -CREATE TABLE #index_spool_ugly -( - QueryHash BINARY(8), - SqlHandle VARBINARY(64), - impact FLOAT, - database_name NVARCHAR(128), - schema_name NVARCHAR(128), - table_name NVARCHAR(128), - equality NVARCHAR(MAX), - inequality NVARCHAR(MAX), - [include] NVARCHAR(MAX), - executions NVARCHAR(128), - query_cost NVARCHAR(128), - creation_hours NVARCHAR(128) -); - - -CREATE TABLE #ReadableDBs -( -database_id INT -); - - -CREATE TABLE #plan_usage -( - duplicate_plan_hashes BIGINT NULL, - percent_duplicate DECIMAL(9, 2) NULL, - single_use_plan_count BIGINT NULL, - percent_single DECIMAL(9, 2) NULL, - total_plans BIGINT NULL, - spid INT -); - - -IF EXISTS (SELECT * FROM sys.all_objects o WHERE o.name = 'dm_hadr_database_replica_states') -BEGIN - RAISERROR('Checking for Read intent databases to exclude',0,0) WITH NOWAIT; - - EXEC('INSERT INTO #ReadableDBs (database_id) SELECT DBs.database_id FROM sys.databases DBs INNER JOIN sys.availability_replicas Replicas ON DBs.replica_id = Replicas.replica_id WHERE replica_server_name NOT IN (SELECT DISTINCT primary_replica FROM sys.dm_hadr_availability_group_states States) AND Replicas.secondary_role_allow_connections_desc = ''READ_ONLY'' AND replica_server_name = @@SERVERNAME OPTION (RECOMPILE);'); - EXEC('INSERT INTO #ReadableDBs VALUES (32767) ;'); -- Exclude internal resource database as well -END - -RAISERROR(N'Checking plan cache age', 0, 1) WITH NOWAIT; -WITH x AS ( -SELECT SUM(CASE WHEN DATEDIFF(HOUR, deqs.creation_time, SYSDATETIME()) <= 24 THEN 1 ELSE 0 END) AS [plans_24], - SUM(CASE WHEN DATEDIFF(HOUR, deqs.creation_time, SYSDATETIME()) <= 4 THEN 1 ELSE 0 END) AS [plans_4], - SUM(CASE WHEN DATEDIFF(HOUR, deqs.creation_time, SYSDATETIME()) <= 1 THEN 1 ELSE 0 END) AS [plans_1], - COUNT(deqs.creation_time) AS [total_plans] -FROM sys.dm_exec_query_stats AS deqs -) -INSERT INTO #plan_creation ( percent_24, percent_4, percent_1, total_plans, SPID ) -SELECT CONVERT(DECIMAL(5,2), NULLIF(x.plans_24, 0) / (1. * NULLIF(x.total_plans, 0))) * 100 AS [percent_24], - CONVERT(DECIMAL(5,2), NULLIF(x.plans_4 , 0) / (1. * NULLIF(x.total_plans, 0))) * 100 AS [percent_4], - CONVERT(DECIMAL(5,2), NULLIF(x.plans_1 , 0) / (1. * NULLIF(x.total_plans, 0))) * 100 AS [percent_1], - x.total_plans, - @@SPID AS SPID -FROM x -OPTION (RECOMPILE); - - -RAISERROR(N'Checking for single use plans and plans with many queries', 0, 1) WITH NOWAIT; -WITH total_plans AS -( - SELECT - COUNT_BIG(deqs.query_plan_hash) AS total_plans - FROM sys.dm_exec_query_stats AS deqs -), - many_plans AS -( - SELECT - SUM(x.duplicate_plan_hashes) AS duplicate_plan_hashes - FROM - ( - SELECT - COUNT_BIG(qs.query_plan_hash) AS duplicate_plan_hashes - FROM sys.dm_exec_query_stats qs - LEFT JOIN sys.dm_exec_procedure_stats ps ON qs.plan_handle = ps.plan_handle - CROSS APPLY sys.dm_exec_plan_attributes(qs.plan_handle) pa - WHERE pa.attribute = N'dbid' - AND pa.value <> 32767 /*Omit Resource database-based queries, we're not going to "fix" them no matter what. Addresses #3314*/ - AND qs.query_plan_hash <> 0x0000000000000000 - GROUP BY - /* qs.query_plan_hash, BGO 20210524 commenting this out to fix #2909 */ - qs.query_hash, - ps.object_id, - pa.value - HAVING COUNT_BIG(qs.query_plan_hash) > 5 - ) AS x -), - single_use_plans AS -( - SELECT - COUNT_BIG(*) AS single_use_plan_count - FROM sys.dm_exec_query_stats AS s - WHERE s.execution_count = 1 -) -INSERT - #plan_usage -( - duplicate_plan_hashes, - percent_duplicate, - single_use_plan_count, - percent_single, - total_plans, - spid -) -SELECT - m.duplicate_plan_hashes, - CONVERT - ( - decimal(5,2), - m.duplicate_plan_hashes - / (1. * NULLIF(t.total_plans, 0)) - ) * 100. AS percent_duplicate, - s.single_use_plan_count, - CONVERT - ( - decimal(5,2), - s.single_use_plan_count - / (1. * NULLIF(t.total_plans, 0)) - ) * 100. AS percent_single, - t.total_plans, - @@SPID -FROM many_plans AS m -CROSS JOIN single_use_plans AS s -CROSS JOIN total_plans AS t; - - -/* -Erik Darling: - Quoting this out to see if the above query fixes the issue - 2021-05-17, Issue #2909 - -UPDATE #plan_usage - SET percent_duplicate = CASE WHEN percent_duplicate > 100 THEN 100 ELSE percent_duplicate END, - percent_single = CASE WHEN percent_duplicate > 100 THEN 100 ELSE percent_duplicate END; -*/ - -SET @OnlySqlHandles = LTRIM(RTRIM(@OnlySqlHandles)) ; -SET @OnlyQueryHashes = LTRIM(RTRIM(@OnlyQueryHashes)) ; -SET @IgnoreQueryHashes = LTRIM(RTRIM(@IgnoreQueryHashes)) ; - -DECLARE @individual VARCHAR(100) ; - -IF (@OnlySqlHandles IS NOT NULL AND @IgnoreSqlHandles IS NOT NULL) -BEGIN -RAISERROR('You shouldn''t need to ignore and filter on SqlHandle at the same time.', 0, 1) WITH NOWAIT; -RETURN; -END; - -IF (@StoredProcName IS NOT NULL AND (@OnlySqlHandles IS NOT NULL OR @IgnoreSqlHandles IS NOT NULL)) -BEGIN -RAISERROR('You can''t filter on stored procedure name and SQL Handle.', 0, 1) WITH NOWAIT; -RETURN; -END; - -IF @OnlySqlHandles IS NOT NULL - AND LEN(@OnlySqlHandles) > 0 -BEGIN - RAISERROR(N'Processing SQL Handles', 0, 1) WITH NOWAIT; - SET @individual = ''; - - WHILE LEN(@OnlySqlHandles) > 0 - BEGIN - IF PATINDEX('%,%', @OnlySqlHandles) > 0 - BEGIN - SET @individual = SUBSTRING(@OnlySqlHandles, 0, PATINDEX('%,%',@OnlySqlHandles)) ; - - INSERT INTO #only_sql_handles - SELECT CAST('' AS XML).value('xs:hexBinary( substring(sql:variable("@individual"), sql:column("t.pos")) )', 'varbinary(max)') - FROM (SELECT CASE SUBSTRING(@individual, 1, 2) WHEN '0x' THEN 3 ELSE 0 END) AS t(pos) - OPTION (RECOMPILE) ; - - --SELECT CAST(SUBSTRING(@individual, 1, 2) AS BINARY(8)); - - SET @OnlySqlHandles = SUBSTRING(@OnlySqlHandles, LEN(@individual + ',') + 1, LEN(@OnlySqlHandles)) ; - END; - ELSE - BEGIN - SET @individual = @OnlySqlHandles; - SET @OnlySqlHandles = NULL; - - INSERT INTO #only_sql_handles - SELECT CAST('' AS XML).value('xs:hexBinary( substring(sql:variable("@individual"), sql:column("t.pos")) )', 'varbinary(max)') - FROM (SELECT CASE SUBSTRING(@individual, 1, 2) WHEN '0x' THEN 3 ELSE 0 END) AS t(pos) - OPTION (RECOMPILE) ; - - --SELECT CAST(SUBSTRING(@individual, 1, 2) AS VARBINARY(MAX)) ; - END; - END; -END; - -IF @IgnoreSqlHandles IS NOT NULL - AND LEN(@IgnoreSqlHandles) > 0 -BEGIN - RAISERROR(N'Processing SQL Handles To Ignore', 0, 1) WITH NOWAIT; - SET @individual = ''; - - WHILE LEN(@IgnoreSqlHandles) > 0 - BEGIN - IF PATINDEX('%,%', @IgnoreSqlHandles) > 0 - BEGIN - SET @individual = SUBSTRING(@IgnoreSqlHandles, 0, PATINDEX('%,%',@IgnoreSqlHandles)) ; - - INSERT INTO #ignore_sql_handles - SELECT CAST('' AS XML).value('xs:hexBinary( substring(sql:variable("@individual"), sql:column("t.pos")) )', 'varbinary(max)') - FROM (SELECT CASE SUBSTRING(@individual, 1, 2) WHEN '0x' THEN 3 ELSE 0 END) AS t(pos) - OPTION (RECOMPILE) ; - - --SELECT CAST(SUBSTRING(@individual, 1, 2) AS BINARY(8)); - - SET @IgnoreSqlHandles = SUBSTRING(@IgnoreSqlHandles, LEN(@individual + ',') + 1, LEN(@IgnoreSqlHandles)) ; - END; - ELSE - BEGIN - SET @individual = @IgnoreSqlHandles; - SET @IgnoreSqlHandles = NULL; - - INSERT INTO #ignore_sql_handles - SELECT CAST('' AS XML).value('xs:hexBinary( substring(sql:variable("@individual"), sql:column("t.pos")) )', 'varbinary(max)') - FROM (SELECT CASE SUBSTRING(@individual, 1, 2) WHEN '0x' THEN 3 ELSE 0 END) AS t(pos) - OPTION (RECOMPILE) ; - - --SELECT CAST(SUBSTRING(@individual, 1, 2) AS VARBINARY(MAX)) ; - END; - END; -END; - -IF @StoredProcName IS NOT NULL AND @StoredProcName <> N'' - -BEGIN - RAISERROR(N'Setting up filter for stored procedure name', 0, 1) WITH NOWAIT; - - DECLARE @function_search_sql NVARCHAR(MAX) = N'' - - INSERT #only_sql_handles - ( sql_handle ) - SELECT ISNULL(deps.sql_handle, CONVERT(VARBINARY(64),'0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000')) - FROM sys.dm_exec_procedure_stats AS deps - WHERE OBJECT_NAME(deps.object_id, deps.database_id) = @StoredProcName - - UNION ALL - - SELECT ISNULL(dets.sql_handle, CONVERT(VARBINARY(64),'0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000')) - FROM sys.dm_exec_trigger_stats AS dets - WHERE OBJECT_NAME(dets.object_id, dets.database_id) = @StoredProcName - OPTION (RECOMPILE); - - IF EXISTS (SELECT 1/0 FROM sys.all_objects AS o WHERE o.name = 'dm_exec_function_stats') - BEGIN - SET @function_search_sql = @function_search_sql + N' - SELECT ISNULL(defs.sql_handle, CONVERT(VARBINARY(64),''0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'')) - FROM sys.dm_exec_function_stats AS defs - WHERE OBJECT_NAME(defs.object_id, defs.database_id) = @i_StoredProcName - OPTION (RECOMPILE); - ' - INSERT #only_sql_handles ( sql_handle ) - EXEC sys.sp_executesql @function_search_sql, N'@i_StoredProcName NVARCHAR(128)', @StoredProcName - END - - IF (SELECT COUNT(*) FROM #only_sql_handles) = 0 - BEGIN - RAISERROR(N'No information for that stored procedure was found.', 0, 1) WITH NOWAIT; - RETURN; - END; - -END; - - - -IF ((@OnlyQueryHashes IS NOT NULL AND LEN(@OnlyQueryHashes) > 0) - OR (@IgnoreQueryHashes IS NOT NULL AND LEN(@IgnoreQueryHashes) > 0)) - AND LEFT(@QueryFilter, 3) IN ('pro', 'fun') -BEGIN - RAISERROR('You cannot limit by query hash and filter by stored procedure', 16, 1); - RETURN; -END; - -/* If the user is attempting to limit by query hash, set up the - #only_query_hashes temp table. This will be used to narrow down - results. - - Just a reminder: Using @OnlyQueryHashes will ignore stored - procedures and triggers. - */ -IF @OnlyQueryHashes IS NOT NULL - AND LEN(@OnlyQueryHashes) > 0 -BEGIN - RAISERROR(N'Setting up filter for Query Hashes', 0, 1) WITH NOWAIT; - SET @individual = ''; - - WHILE LEN(@OnlyQueryHashes) > 0 - BEGIN - IF PATINDEX('%,%', @OnlyQueryHashes) > 0 - BEGIN - SET @individual = SUBSTRING(@OnlyQueryHashes, 0, PATINDEX('%,%',@OnlyQueryHashes)) ; - - INSERT INTO #only_query_hashes - SELECT CAST('' AS XML).value('xs:hexBinary( substring(sql:variable("@individual"), sql:column("t.pos")) )', 'varbinary(max)') - FROM (SELECT CASE SUBSTRING(@individual, 1, 2) WHEN '0x' THEN 3 ELSE 0 END) AS t(pos) - OPTION (RECOMPILE) ; - - --SELECT CAST(SUBSTRING(@individual, 1, 2) AS BINARY(8)); - - SET @OnlyQueryHashes = SUBSTRING(@OnlyQueryHashes, LEN(@individual + ',') + 1, LEN(@OnlyQueryHashes)) ; - END; - ELSE - BEGIN - SET @individual = @OnlyQueryHashes; - SET @OnlyQueryHashes = NULL; - - INSERT INTO #only_query_hashes - SELECT CAST('' AS XML).value('xs:hexBinary( substring(sql:variable("@individual"), sql:column("t.pos")) )', 'varbinary(max)') - FROM (SELECT CASE SUBSTRING(@individual, 1, 2) WHEN '0x' THEN 3 ELSE 0 END) AS t(pos) - OPTION (RECOMPILE) ; - - --SELECT CAST(SUBSTRING(@individual, 1, 2) AS VARBINARY(MAX)) ; - END; - END; -END; - -/* If the user is setting up a list of query hashes to ignore, those - values will be inserted into #ignore_query_hashes. This is used to - exclude values from query results. - - Just a reminder: Using @IgnoreQueryHashes will ignore stored - procedures and triggers. - */ -IF @IgnoreQueryHashes IS NOT NULL - AND LEN(@IgnoreQueryHashes) > 0 -BEGIN - RAISERROR(N'Setting up filter to ignore query hashes', 0, 1) WITH NOWAIT; - SET @individual = '' ; - - WHILE LEN(@IgnoreQueryHashes) > 0 - BEGIN - IF PATINDEX('%,%', @IgnoreQueryHashes) > 0 - BEGIN - SET @individual = SUBSTRING(@IgnoreQueryHashes, 0, PATINDEX('%,%',@IgnoreQueryHashes)) ; - - INSERT INTO #ignore_query_hashes - SELECT CAST('' AS XML).value('xs:hexBinary( substring(sql:variable("@individual"), sql:column("t.pos")) )', 'varbinary(max)') - FROM (SELECT CASE SUBSTRING(@individual, 1, 2) WHEN '0x' THEN 3 ELSE 0 END) AS t(pos) - OPTION (RECOMPILE) ; - - SET @IgnoreQueryHashes = SUBSTRING(@IgnoreQueryHashes, LEN(@individual + ',') + 1, LEN(@IgnoreQueryHashes)) ; - END; - ELSE - BEGIN - SET @individual = @IgnoreQueryHashes ; - SET @IgnoreQueryHashes = NULL ; - - INSERT INTO #ignore_query_hashes - SELECT CAST('' AS XML).value('xs:hexBinary( substring(sql:variable("@individual"), sql:column("t.pos")) )', 'varbinary(max)') - FROM (SELECT CASE SUBSTRING(@individual, 1, 2) WHEN '0x' THEN 3 ELSE 0 END) AS t(pos) - OPTION (RECOMPILE) ; - END; - END; -END; - -IF @ConfigurationDatabaseName IS NOT NULL -BEGIN - RAISERROR(N'Reading values from Configuration Database', 0, 1) WITH NOWAIT; - DECLARE @config_sql NVARCHAR(MAX) = N'INSERT INTO #configuration SELECT parameter_name, value FROM ' - + QUOTENAME(@ConfigurationDatabaseName) - + '.' + QUOTENAME(@ConfigurationSchemaName) - + '.' + QUOTENAME(@ConfigurationTableName) - + ' ; ' ; - EXEC(@config_sql); -END; - -RAISERROR(N'Setting up variables', 0, 1) WITH NOWAIT; -DECLARE @sql NVARCHAR(MAX) = N'', - @insert_list NVARCHAR(MAX) = N'', - @plans_triggers_select_list NVARCHAR(MAX) = N'', - @body NVARCHAR(MAX) = N'', - @body_where NVARCHAR(MAX) = N'WHERE 1 = 1 ' + @nl, - @body_order NVARCHAR(MAX) = N'ORDER BY #sortable# DESC OPTION (RECOMPILE) ', - - @q NVARCHAR(1) = N'''', - @pv VARCHAR(20), - @pos TINYINT, - @v DECIMAL(6,2), - @build INT; - - -RAISERROR (N'Determining SQL Server version.',0,1) WITH NOWAIT; - -INSERT INTO #checkversion (version) -SELECT CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)) -OPTION (RECOMPILE); - - -SELECT @v = common_version , - @build = build -FROM #checkversion -OPTION (RECOMPILE); - -IF (@SortOrder IN ('memory grant', 'avg memory grant')) AND @VersionShowsMemoryGrants = 0 -BEGIN - RAISERROR('Your version of SQL does not support sorting by memory grant or average memory grant. Please use another sort order.', 16, 1); - RETURN; -END; - -IF (@SortOrder IN ('spills', 'avg spills') AND @VersionShowsSpills = 0) -BEGIN - RAISERROR('Your version of SQL does not support sorting by spills. Please use another sort order.', 16, 1); - RETURN; -END; - -IF ((LEFT(@QueryFilter, 3) = 'fun') AND (@v < 13)) -BEGIN - RAISERROR('Your version of SQL does not support filtering by functions. Please use another filter.', 16, 1); - RETURN; -END; - -RAISERROR (N'Creating dynamic SQL based on SQL Server version.',0,1) WITH NOWAIT; - -SET @insert_list += N' -SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -INSERT INTO ##BlitzCacheProcs (SPID, QueryType, DatabaseName, AverageCPU, TotalCPU, AverageCPUPerMinute, PercentCPUByType, PercentDurationByType, - PercentReadsByType, PercentExecutionsByType, AverageDuration, TotalDuration, AverageReads, TotalReads, ExecutionCount, - ExecutionsPerMinute, TotalWrites, AverageWrites, PercentWritesByType, WritesPerMinute, PlanCreationTime, - LastExecutionTime, LastCompletionTime, StatementStartOffset, StatementEndOffset, PlanGenerationNum, MinReturnedRows, MaxReturnedRows, AverageReturnedRows, TotalReturnedRows, - LastReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, MaxUsedGrantKB, PercentMemoryGrantUsed, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, - QueryText, QueryPlan, TotalWorkerTimeForType, TotalElapsedTimeForType, TotalReadsForType, - TotalExecutionCountForType, TotalWritesForType, SqlHandle, PlanHandle, QueryHash, QueryPlanHash, - min_worker_time, max_worker_time, is_parallel, min_elapsed_time, max_elapsed_time, age_minutes, age_minutes_lifetime, Pattern) ' ; - -SET @body += N' -FROM (SELECT TOP (@Top) x.*, xpa.*, - CAST((CASE WHEN DATEDIFF(mi, cached_time, GETDATE()) > 0 AND execution_count > 1 - THEN DATEDIFF(mi, cached_time, GETDATE()) - ELSE NULL END) as MONEY) as age_minutes, - CAST((CASE WHEN DATEDIFF(mi, cached_time, last_execution_time) > 0 AND execution_count > 1 - THEN DATEDIFF(mi, cached_time, last_execution_time) - ELSE Null END) as MONEY) as age_minutes_lifetime - FROM sys.#view# x - CROSS APPLY (SELECT * FROM sys.dm_exec_plan_attributes(x.plan_handle) AS ixpa - WHERE ixpa.attribute = ''dbid'') AS xpa ' + @nl ; - -IF @SortOrder = 'duplicate' /* Issue #3345 */ - BEGIN - SET @body += N' INNER JOIN #duplicate_query_filter AS dqf ON x.sql_handle = dqf.sql_handle AND x.plan_handle = dqf.plan_handle AND x.creation_time = dqf.duplicate_creation_time ' + @nl ; - END - -IF @VersionShowsAirQuoteActualPlans = 1 - BEGIN - SET @body += N' CROSS APPLY sys.dm_exec_query_plan_stats(x.plan_handle) AS deqps ' + @nl ; - END - -SET @body += N' WHERE 1 = 1 ' + @nl ; - - IF EXISTS (SELECT * FROM sys.all_objects o WHERE o.name = 'dm_hadr_database_replica_states') - BEGIN - RAISERROR(N'Ignoring readable secondaries databases by default', 0, 1) WITH NOWAIT; - SET @body += N' AND CAST(xpa.value AS INT) NOT IN (SELECT database_id FROM #ReadableDBs)' + @nl ; - END - -IF @IgnoreSystemDBs = 1 - BEGIN - RAISERROR(N'Ignoring system databases by default', 0, 1) WITH NOWAIT; - SET @body += N' AND COALESCE(LOWER(DB_NAME(CAST(xpa.value AS INT))), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'', ''dbmaintenance'', ''dbadmin'', ''dbatools'') AND COALESCE(DB_NAME(CAST(xpa.value AS INT)), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ; - END; - -IF @DatabaseName IS NOT NULL OR @DatabaseName <> N'' - BEGIN - RAISERROR(N'Filtering database name chosen', 0, 1) WITH NOWAIT; - SET @body += N' AND CAST(xpa.value AS BIGINT) = DB_ID(N' - + QUOTENAME(@DatabaseName, N'''') - + N') ' + @nl; - END; - -IF (SELECT COUNT(*) FROM #only_sql_handles) > 0 -BEGIN - RAISERROR(N'Including only chosen SQL Handles', 0, 1) WITH NOWAIT; - SET @body += N' AND EXISTS(SELECT 1/0 FROM #only_sql_handles q WHERE q.sql_handle = x.sql_handle) ' + @nl ; -END; - -IF (SELECT COUNT(*) FROM #ignore_sql_handles) > 0 -BEGIN - RAISERROR(N'Including only chosen SQL Handles', 0, 1) WITH NOWAIT; - SET @body += N' AND NOT EXISTS(SELECT 1/0 FROM #ignore_sql_handles q WHERE q.sql_handle = x.sql_handle) ' + @nl ; -END; - -IF (SELECT COUNT(*) FROM #only_query_hashes) > 0 - AND (SELECT COUNT(*) FROM #ignore_query_hashes) = 0 - AND (SELECT COUNT(*) FROM #only_sql_handles) = 0 - AND (SELECT COUNT(*) FROM #ignore_sql_handles) = 0 -BEGIN - RAISERROR(N'Including only chosen Query Hashes', 0, 1) WITH NOWAIT; - SET @body += N' AND EXISTS(SELECT 1/0 FROM #only_query_hashes q WHERE q.query_hash = x.query_hash) ' + @nl ; -END; - -/* filtering for query hashes */ -IF (SELECT COUNT(*) FROM #ignore_query_hashes) > 0 - AND (SELECT COUNT(*) FROM #only_query_hashes) = 0 -BEGIN - RAISERROR(N'Excluding chosen Query Hashes', 0, 1) WITH NOWAIT; - SET @body += N' AND NOT EXISTS(SELECT 1/0 FROM #ignore_query_hashes iq WHERE iq.query_hash = x.query_hash) ' + @nl ; -END; -/* end filtering for query hashes */ - -IF @DurationFilter IS NOT NULL - BEGIN - RAISERROR(N'Setting duration filter', 0, 1) WITH NOWAIT; - SET @body += N' AND (total_elapsed_time / 1000.0) / execution_count > @min_duration ' + @nl ; - END; - -IF @MinutesBack IS NOT NULL - BEGIN - RAISERROR(N'Setting minutes back filter', 0, 1) WITH NOWAIT; - SET @body += N' AND DATEADD(SECOND, (x.last_elapsed_time / 1000000.), x.last_execution_time) >= DATEADD(MINUTE, @min_back, GETDATE()) ' + @nl ; - END; - -IF @SlowlySearchPlansFor IS NOT NULL - BEGIN - RAISERROR(N'Setting string search for @SlowlySearchPlansFor, so remember, this is gonna be slow', 0, 1) WITH NOWAIT; - SET @SlowlySearchPlansFor = REPLACE((REPLACE((REPLACE((REPLACE(@SlowlySearchPlansFor, N'[', N'_')), N']', N'_')), N'^', N'_')), N'''', N''''''); - SET @body_where += N' AND CAST(qp.query_plan AS NVARCHAR(MAX)) LIKE N''%' + @SlowlySearchPlansFor + N'%'' ' + @nl; - END - - -/* Apply the sort order here to only grab relevant plans. - This should make it faster to process since we'll be pulling back fewer - plans for processing. - */ -RAISERROR(N'Applying chosen sort order', 0, 1) WITH NOWAIT; -SELECT @body += N' ORDER BY ' + - CASE @SortOrder WHEN N'cpu' THEN N'total_worker_time' - WHEN N'reads' THEN N'total_logical_reads' - WHEN N'writes' THEN N'total_logical_writes' - WHEN N'duration' THEN N'total_elapsed_time' - WHEN N'executions' THEN N'execution_count' - WHEN N'compiles' THEN N'cached_time' - WHEN N'memory grant' THEN N'max_grant_kb' - WHEN N'unused grant' THEN N'max_grant_kb - max_used_grant_kb' - WHEN N'spills' THEN N'max_spills' - WHEN N'duplicate' THEN N'total_worker_time' /* Issue #3345 */ - /* And now the averages */ - WHEN N'avg cpu' THEN N'total_worker_time / execution_count' - WHEN N'avg reads' THEN N'total_logical_reads / execution_count' - WHEN N'avg writes' THEN N'total_logical_writes / execution_count' - WHEN N'avg duration' THEN N'total_elapsed_time / execution_count' - WHEN N'avg memory grant' THEN N'CASE WHEN max_grant_kb = 0 THEN 0 ELSE max_grant_kb / execution_count END' - WHEN N'avg spills' THEN N'CASE WHEN total_spills = 0 THEN 0 ELSE total_spills / execution_count END' - WHEN N'avg executions' THEN 'CASE WHEN execution_count = 0 THEN 0 - WHEN COALESCE(CAST((CASE WHEN DATEDIFF(mi, cached_time, GETDATE()) > 0 AND execution_count > 1 - THEN DATEDIFF(mi, cached_time, GETDATE()) - ELSE NULL END) as MONEY), CAST((CASE WHEN DATEDIFF(mi, cached_time, last_execution_time) > 0 AND execution_count > 1 - THEN DATEDIFF(mi, cached_time, last_execution_time) - ELSE Null END) as MONEY), 0) = 0 THEN 0 - ELSE CAST((1.00 * execution_count / COALESCE(CAST((CASE WHEN DATEDIFF(mi, cached_time, GETDATE()) > 0 AND execution_count > 1 - THEN DATEDIFF(mi, cached_time, GETDATE()) - ELSE NULL END) as MONEY), CAST((CASE WHEN DATEDIFF(mi, cached_time, last_execution_time) > 0 AND execution_count > 1 - THEN DATEDIFF(mi, cached_time, last_execution_time) - ELSE Null END) as MONEY))) AS money) - END ' - END + N' DESC ' + @nl ; - - - -SET @body += N') AS qs - CROSS JOIN(SELECT SUM(execution_count) AS t_TotalExecs, - SUM(CAST(total_elapsed_time AS BIGINT) / 1000.0) AS t_TotalElapsed, - SUM(CAST(total_worker_time AS BIGINT) / 1000.0) AS t_TotalWorker, - SUM(CAST(total_logical_reads AS DECIMAL(30))) AS t_TotalReads, - SUM(CAST(total_logical_writes AS DECIMAL(30))) AS t_TotalWrites - FROM sys.#view#) AS t - CROSS APPLY sys.dm_exec_plan_attributes(qs.plan_handle) AS pa - CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) AS st - CROSS APPLY sys.dm_exec_query_plan(qs.plan_handle) AS qp ' + @nl ; - -IF @VersionShowsAirQuoteActualPlans = 1 - BEGIN - SET @body += N' CROSS APPLY sys.dm_exec_query_plan_stats(qs.plan_handle) AS deqps ' + @nl ; - END - -SET @body_where += N' AND pa.attribute = ' + QUOTENAME('dbid', @q ) + @nl ; - -IF @NoobSaibot = 1 -BEGIN - SET @body_where += N' AND qp.query_plan.exist(''declare namespace p="http://schemas.microsoft.com/sqlserver/2004/07/showplan";//p:StmtSimple//p:MissingIndex'') = 1' + @nl ; -END - -SET @plans_triggers_select_list += N' -SELECT TOP (@Top) - @@SPID , - ''Procedure or Function: '' - + QUOTENAME(COALESCE(OBJECT_SCHEMA_NAME(qs.object_id, qs.database_id),'''')) - + ''.'' - + QUOTENAME(COALESCE(OBJECT_NAME(qs.object_id, qs.database_id),'''')) AS QueryType, - COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), N''-- N/A --'') AS DatabaseName, - (total_worker_time / 1000.0) / execution_count AS AvgCPU , - (total_worker_time / 1000.0) AS TotalCPU , - CASE WHEN total_worker_time = 0 THEN 0 - WHEN COALESCE(age_minutes, DATEDIFF(mi, qs.cached_time, qs.last_execution_time), 0) = 0 THEN 0 - ELSE CAST((total_worker_time / 1000.0) / COALESCE(age_minutes, DATEDIFF(mi, qs.cached_time, qs.last_execution_time)) AS MONEY) - END AS AverageCPUPerMinute , - CASE WHEN t.t_TotalWorker = 0 THEN 0 - ELSE CAST(ROUND(100.00 * (total_worker_time / 1000.0) / t.t_TotalWorker, 2) AS MONEY) - END AS PercentCPUByType, - CASE WHEN t.t_TotalElapsed = 0 THEN 0 - ELSE CAST(ROUND(100.00 * (total_elapsed_time / 1000.0) / t.t_TotalElapsed, 2) AS MONEY) - END AS PercentDurationByType, - CASE WHEN t.t_TotalReads = 0 THEN 0 - ELSE CAST(ROUND(100.00 * total_logical_reads / t.t_TotalReads, 2) AS MONEY) - END AS PercentReadsByType, - CASE WHEN t.t_TotalExecs = 0 THEN 0 - ELSE CAST(ROUND(100.00 * execution_count / t.t_TotalExecs, 2) AS MONEY) - END AS PercentExecutionsByType, - (total_elapsed_time / 1000.0) / execution_count AS AvgDuration , - (total_elapsed_time / 1000.0) AS TotalDuration , - total_logical_reads / execution_count AS AvgReads , - total_logical_reads AS TotalReads , - execution_count AS ExecutionCount , - CASE WHEN execution_count = 0 THEN 0 - WHEN COALESCE(age_minutes, DATEDIFF(mi, qs.cached_time, qs.last_execution_time), 0) = 0 THEN 0 - ELSE CAST((1.00 * execution_count / COALESCE(age_minutes, DATEDIFF(mi, qs.cached_time, qs.last_execution_time))) AS money) - END AS ExecutionsPerMinute , - total_logical_writes AS TotalWrites , - total_logical_writes / execution_count AS AverageWrites , - CASE WHEN t.t_TotalWrites = 0 THEN 0 - ELSE CAST(ROUND(100.00 * total_logical_writes / t.t_TotalWrites, 2) AS MONEY) - END AS PercentWritesByType, - CASE WHEN total_logical_writes = 0 THEN 0 - WHEN COALESCE(age_minutes, DATEDIFF(mi, qs.cached_time, qs.last_execution_time), 0) = 0 THEN 0 - ELSE CAST((1.00 * total_logical_writes / COALESCE(age_minutes, DATEDIFF(mi, qs.cached_time, qs.last_execution_time), 0)) AS money) - END AS WritesPerMinute, - qs.cached_time AS PlanCreationTime, - qs.last_execution_time AS LastExecutionTime, - DATEADD(SECOND, (qs.last_elapsed_time / 1000000.), qs.last_execution_time) AS LastCompletionTime, - NULL AS StatementStartOffset, - NULL AS StatementEndOffset, - NULL AS PlanGenerationNum, - NULL AS MinReturnedRows, - NULL AS MaxReturnedRows, - NULL AS AvgReturnedRows, - NULL AS TotalReturnedRows, - NULL AS LastReturnedRows, - NULL AS MinGrantKB, - NULL AS MaxGrantKB, - NULL AS MinUsedGrantKB, - NULL AS MaxUsedGrantKB, - NULL AS PercentMemoryGrantUsed, - NULL AS AvgMaxMemoryGrant,'; - - IF @VersionShowsSpills = 1 - BEGIN - RAISERROR(N'Getting spill information for newer versions of SQL', 0, 1) WITH NOWAIT; - SET @plans_triggers_select_list += N' - min_spills AS MinSpills, - max_spills AS MaxSpills, - total_spills AS TotalSpills, - CAST(ISNULL(NULLIF(( total_spills * 1. ), 0) / NULLIF(execution_count, 0), 0) AS MONEY) AS AvgSpills, '; - END; - ELSE - BEGIN - RAISERROR(N'Substituting NULLs for spill columns in older versions of SQL', 0, 1) WITH NOWAIT; - SET @plans_triggers_select_list += N' - NULL AS MinSpills, - NULL AS MaxSpills, - NULL AS TotalSpills, - NULL AS AvgSpills, ' ; - END; - - SET @plans_triggers_select_list += - N'st.text AS QueryText ,'; - - IF @VersionShowsAirQuoteActualPlans = 1 - BEGIN - SET @plans_triggers_select_list += N' CASE WHEN DATALENGTH(COALESCE(deqps.query_plan,'''')) > DATALENGTH(COALESCE(qp.query_plan,'''')) THEN deqps.query_plan ELSE qp.query_plan END AS QueryPlan, ' + @nl ; - END; - ELSE - BEGIN - SET @plans_triggers_select_list += N' qp.query_plan AS QueryPlan, ' + @nl ; - END; - - SET @plans_triggers_select_list += - N't.t_TotalWorker, - t.t_TotalElapsed, - t.t_TotalReads, - t.t_TotalExecs, - t.t_TotalWrites, - qs.sql_handle AS SqlHandle, - qs.plan_handle AS PlanHandle, - NULL AS QueryHash, - NULL AS QueryPlanHash, - qs.min_worker_time / 1000.0, - qs.max_worker_time / 1000.0, - CASE WHEN qp.query_plan.value(''declare namespace p="http://schemas.microsoft.com/sqlserver/2004/07/showplan";max(//p:RelOp/@Parallel)'', ''float'') > 0 THEN 1 ELSE 0 END, - qs.min_elapsed_time / 1000.0, - qs.max_elapsed_time / 1000.0, - age_minutes, - age_minutes_lifetime, - @SortOrder '; - - -IF LEFT(@QueryFilter, 3) IN ('all', 'sta') -BEGIN - SET @sql += @insert_list; - - SET @sql += N' - SELECT TOP (@Top) - @@SPID , - ''Statement'' AS QueryType, - COALESCE(DB_NAME(CAST(pa.value AS INT)), N''-- N/A --'') AS DatabaseName, - (total_worker_time / 1000.0) / execution_count AS AvgCPU , - (total_worker_time / 1000.0) AS TotalCPU , - CASE WHEN total_worker_time = 0 THEN 0 - WHEN COALESCE(age_minutes, DATEDIFF(mi, qs.creation_time, qs.last_execution_time), 0) = 0 THEN 0 - ELSE CAST((total_worker_time / 1000.0) / COALESCE(age_minutes, DATEDIFF(mi, qs.creation_time, qs.last_execution_time)) AS MONEY) - END AS AverageCPUPerMinute , - CASE WHEN t.t_TotalWorker = 0 THEN 0 - ELSE CAST(ROUND(100.00 * total_worker_time / t.t_TotalWorker, 2) AS MONEY) - END AS PercentCPUByType, - CASE WHEN t.t_TotalElapsed = 0 THEN 0 - ELSE CAST(ROUND(100.00 * total_elapsed_time / t.t_TotalElapsed, 2) AS MONEY) - END AS PercentDurationByType, - CASE WHEN t.t_TotalReads = 0 THEN 0 - ELSE CAST(ROUND(100.00 * total_logical_reads / t.t_TotalReads, 2) AS MONEY) - END AS PercentReadsByType, - CAST(ROUND(100.00 * execution_count / t.t_TotalExecs, 2) AS MONEY) AS PercentExecutionsByType, - (total_elapsed_time / 1000.0) / execution_count AS AvgDuration , - (total_elapsed_time / 1000.0) AS TotalDuration , - total_logical_reads / execution_count AS AvgReads , - total_logical_reads AS TotalReads , - execution_count AS ExecutionCount , - CASE WHEN execution_count = 0 THEN 0 - WHEN COALESCE(age_minutes, DATEDIFF(mi, qs.creation_time, qs.last_execution_time), 0) = 0 THEN 0 - ELSE CAST((1.00 * execution_count / COALESCE(age_minutes, DATEDIFF(mi, qs.creation_time, qs.last_execution_time))) AS money) - END AS ExecutionsPerMinute , - total_logical_writes AS TotalWrites , - total_logical_writes / execution_count AS AverageWrites , - CASE WHEN t.t_TotalWrites = 0 THEN 0 - ELSE CAST(ROUND(100.00 * total_logical_writes / t.t_TotalWrites, 2) AS MONEY) - END AS PercentWritesByType, - CASE WHEN total_logical_writes = 0 THEN 0 - WHEN COALESCE(age_minutes, DATEDIFF(mi, qs.creation_time, qs.last_execution_time), 0) = 0 THEN 0 - ELSE CAST((1.00 * total_logical_writes / COALESCE(age_minutes, DATEDIFF(mi, qs.creation_time, qs.last_execution_time), 0)) AS money) - END AS WritesPerMinute, - qs.creation_time AS PlanCreationTime, - qs.last_execution_time AS LastExecutionTime, - DATEADD(SECOND, (qs.last_elapsed_time / 1000000.), qs.last_execution_time) AS LastCompletionTime, - qs.statement_start_offset AS StatementStartOffset, - qs.statement_end_offset AS StatementEndOffset, - qs.plan_generation_num AS PlanGenerationNum, '; - - IF (@v >= 11) OR (@v >= 10.5 AND @build >= 2500) - BEGIN - RAISERROR(N'Adding additional info columns for newer versions of SQL', 0, 1) WITH NOWAIT; - SET @sql += N' - qs.min_rows AS MinReturnedRows, - qs.max_rows AS MaxReturnedRows, - CAST(qs.total_rows as MONEY) / execution_count AS AvgReturnedRows, - qs.total_rows AS TotalReturnedRows, - qs.last_rows AS LastReturnedRows, ' ; - END; - ELSE - BEGIN - RAISERROR(N'Substituting NULLs for more info columns in older versions of SQL', 0, 1) WITH NOWAIT; - SET @sql += N' - NULL AS MinReturnedRows, - NULL AS MaxReturnedRows, - NULL AS AvgReturnedRows, - NULL AS TotalReturnedRows, - NULL AS LastReturnedRows, ' ; - END; - - IF @VersionShowsMemoryGrants = 1 - BEGIN - RAISERROR(N'Getting memory grant information for newer versions of SQL', 0, 1) WITH NOWAIT; - SET @sql += N' - min_grant_kb AS MinGrantKB, - max_grant_kb AS MaxGrantKB, - min_used_grant_kb AS MinUsedGrantKB, - max_used_grant_kb AS MaxUsedGrantKB, - CAST(ISNULL(NULLIF(( total_used_grant_kb * 1.00 ), 0) / NULLIF(total_grant_kb, 0), 0) * 100. AS MONEY) AS PercentMemoryGrantUsed, - CAST(ISNULL(NULLIF(( total_grant_kb * 1. ), 0) / NULLIF(execution_count, 0), 0) AS MONEY) AS AvgMaxMemoryGrant, '; - END; - ELSE - BEGIN - RAISERROR(N'Substituting NULLs for memory grant columns in older versions of SQL', 0, 1) WITH NOWAIT; - SET @sql += N' - NULL AS MinGrantKB, - NULL AS MaxGrantKB, - NULL AS MinUsedGrantKB, - NULL AS MaxUsedGrantKB, - NULL AS PercentMemoryGrantUsed, - NULL AS AvgMaxMemoryGrant, ' ; - END; - - IF @VersionShowsSpills = 1 - BEGIN - RAISERROR(N'Getting spill information for newer versions of SQL', 0, 1) WITH NOWAIT; - SET @sql += N' - min_spills AS MinSpills, - max_spills AS MaxSpills, - total_spills AS TotalSpills, - CAST(ISNULL(NULLIF(( total_spills * 1. ), 0) / NULLIF(execution_count, 0), 0) AS MONEY) AS AvgSpills,'; - END; - ELSE - BEGIN - RAISERROR(N'Substituting NULLs for spill columns in older versions of SQL', 0, 1) WITH NOWAIT; - SET @sql += N' - NULL AS MinSpills, - NULL AS MaxSpills, - NULL AS TotalSpills, - NULL AS AvgSpills, ' ; - END; - - SET @sql += N' - SUBSTRING(st.text, ( qs.statement_start_offset / 2 ) + 1, ( ( CASE qs.statement_end_offset - WHEN -1 THEN DATALENGTH(st.text) - ELSE qs.statement_end_offset - END - qs.statement_start_offset ) / 2 ) + 1) AS QueryText , ' + @nl ; - - - IF @VersionShowsAirQuoteActualPlans = 1 - BEGIN - SET @sql += N' CASE WHEN DATALENGTH(COALESCE(deqps.query_plan,'''')) > DATALENGTH(COALESCE(qp.query_plan,'''')) THEN deqps.query_plan ELSE qp.query_plan END AS QueryPlan, ' + @nl ; - END - ELSE - BEGIN - SET @sql += N' query_plan AS QueryPlan, ' + @nl ; - END - - SET @sql += N' - t.t_TotalWorker, - t.t_TotalElapsed, - t.t_TotalReads, - t.t_TotalExecs, - t.t_TotalWrites, - qs.sql_handle AS SqlHandle, - qs.plan_handle AS PlanHandle, - qs.query_hash AS QueryHash, - qs.query_plan_hash AS QueryPlanHash, - qs.min_worker_time / 1000.0, - qs.max_worker_time / 1000.0, - CASE WHEN qp.query_plan.value(''declare namespace p="http://schemas.microsoft.com/sqlserver/2004/07/showplan";max(//p:RelOp/@Parallel)'', ''float'') > 0 THEN 1 ELSE 0 END, - qs.min_elapsed_time / 1000.0, - qs.max_worker_time / 1000.0, - age_minutes, - age_minutes_lifetime, - @SortOrder '; - - SET @sql += REPLACE(REPLACE(@body, '#view#', 'dm_exec_query_stats'), 'cached_time', 'creation_time') ; - - SET @sort_filter += CASE @SortOrder WHEN N'cpu' THEN N'AND total_worker_time > 0' - WHEN N'reads' THEN N'AND total_logical_reads > 0' - WHEN N'writes' THEN N'AND total_logical_writes > 0' - WHEN N'duration' THEN N'AND total_elapsed_time > 0' - WHEN N'executions' THEN N'AND execution_count > 0' - /* WHEN N'compiles' THEN N'AND (age_minutes + age_minutes_lifetime) > 0' BGO 2021-01-24 commenting out for https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2772 */ - WHEN N'memory grant' THEN N'AND max_grant_kb > 0' - WHEN N'unused grant' THEN N'AND max_grant_kb > 0' - WHEN N'spills' THEN N'AND max_spills > 0' - /* And now the averages */ - WHEN N'avg cpu' THEN N'AND (total_worker_time / execution_count) > 0' - WHEN N'avg reads' THEN N'AND (total_logical_reads / execution_count) > 0' - WHEN N'avg writes' THEN N'AND (total_logical_writes / execution_count) > 0' - WHEN N'avg duration' THEN N'AND (total_elapsed_time / execution_count) > 0' - WHEN N'avg memory grant' THEN N'AND CASE WHEN max_grant_kb = 0 THEN 0 ELSE (max_grant_kb / execution_count) END > 0' - WHEN N'avg spills' THEN N'AND CASE WHEN total_spills = 0 THEN 0 ELSE (total_spills / execution_count) END > 0' - WHEN N'avg executions' THEN N'AND CASE WHEN execution_count = 0 THEN 0 - WHEN COALESCE(age_minutes, age_minutes_lifetime, 0) = 0 THEN 0 - ELSE CAST((1.00 * execution_count / COALESCE(age_minutes, age_minutes_lifetime)) AS money) - END > 0' - ELSE N' /* No minimum threshold set */ ' - END; - - SET @sql += REPLACE(@body_where, 'cached_time', 'creation_time') ; - - SET @sql += @sort_filter + @nl; - - SET @sql += @body_order + @nl + @nl + @nl; - - IF @SortOrder = 'compiles' - BEGIN - RAISERROR(N'Sorting by compiles', 0, 1) WITH NOWAIT; - SET @sql = REPLACE(@sql, '#sortable#', 'creation_time'); - END; -END; - - -IF (@QueryFilter = 'all' - AND (SELECT COUNT(*) FROM #only_query_hashes) = 0 - AND (SELECT COUNT(*) FROM #ignore_query_hashes) = 0) - AND (@SortOrder NOT IN ('memory grant', 'avg memory grant', 'unused grant', 'duplicate')) /* Issue #3345 added 'duplicate' */ - OR (LEFT(@QueryFilter, 3) = 'pro') -BEGIN - SET @sql += @insert_list; - SET @sql += REPLACE(@plans_triggers_select_list, '#query_type#', 'Stored Procedure') ; - - SET @sql += REPLACE(@body, '#view#', 'dm_exec_procedure_stats') ; - SET @sql += @body_where ; - - IF @IgnoreSystemDBs = 1 - SET @sql += N' AND COALESCE(LOWER(DB_NAME(database_id)), LOWER(CAST(pa.value AS sysname)), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'', ''dbmaintenance'', ''dbadmin'', ''dbatools'') AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ; - - SET @sql += @sort_filter + @nl; - - SET @sql += @body_order + @nl + @nl + @nl ; -END; - -IF (@v >= 13 - AND @QueryFilter = 'all' - AND (SELECT COUNT(*) FROM #only_query_hashes) = 0 - AND (SELECT COUNT(*) FROM #ignore_query_hashes) = 0) - AND (@SortOrder NOT IN ('memory grant', 'avg memory grant', 'unused grant', 'duplicate')) /* Issue #3345 added 'duplicate' */ - AND (@SortOrder NOT IN ('spills', 'avg spills')) - OR (LEFT(@QueryFilter, 3) = 'fun') -BEGIN - SET @sql += @insert_list; - SET @sql += REPLACE(REPLACE(@plans_triggers_select_list, '#query_type#', 'Function') - , N' - min_spills AS MinSpills, - max_spills AS MaxSpills, - total_spills AS TotalSpills, - CAST(ISNULL(NULLIF(( total_spills * 1. ), 0) / NULLIF(execution_count, 0), 0) AS MONEY) AS AvgSpills, ', - N' - NULL AS MinSpills, - NULL AS MaxSpills, - NULL AS TotalSpills, - NULL AS AvgSpills, ') ; - - SET @sql += REPLACE(@body, '#view#', 'dm_exec_function_stats') ; - SET @sql += @body_where ; - - IF @IgnoreSystemDBs = 1 - SET @sql += N' AND COALESCE(LOWER(DB_NAME(database_id)), LOWER(CAST(pa.value AS sysname)), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'', ''dbmaintenance'', ''dbadmin'', ''dbatools'') AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ; - - SET @sql += @sort_filter + @nl; - - SET @sql += @body_order + @nl + @nl + @nl ; -END; - -/******************************************************************************* - * - * Because the trigger execution count in SQL Server 2008R2 and earlier is not - * correct, we ignore triggers for these versions of SQL Server. If you'd like - * to include trigger numbers, just know that the ExecutionCount, - * PercentExecutions, and ExecutionsPerMinute are wildly inaccurate for - * triggers on these versions of SQL Server. - * - * This is why we can't have nice things. - * - ******************************************************************************/ -IF (@UseTriggersAnyway = 1 OR @v >= 11) - AND (SELECT COUNT(*) FROM #only_query_hashes) = 0 - AND (SELECT COUNT(*) FROM #ignore_query_hashes) = 0 - AND (@QueryFilter = 'all') - AND (@SortOrder NOT IN ('memory grant', 'avg memory grant', 'unused grant', 'duplicate')) /* Issue #3345 added 'duplicate' */ -BEGIN - RAISERROR (N'Adding SQL to collect trigger stats.',0,1) WITH NOWAIT; - - /* Trigger level information from the plan cache */ - SET @sql += @insert_list ; - - SET @sql += REPLACE(@plans_triggers_select_list, '#query_type#', 'Trigger') ; - - SET @sql += REPLACE(@body, '#view#', 'dm_exec_trigger_stats') ; - - SET @sql += @body_where ; - - IF @IgnoreSystemDBs = 1 - SET @sql += N' AND COALESCE(LOWER(DB_NAME(database_id)), LOWER(CAST(pa.value AS sysname)), '''') NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''32767'', ''dbmaintenance'', ''dbadmin'', ''dbatools'') AND COALESCE(DB_NAME(database_id), CAST(pa.value AS sysname), '''') NOT IN (SELECT name FROM sys.databases WHERE is_distributor = 1)' + @nl ; - - SET @sql += @sort_filter + @nl; - - SET @sql += @body_order + @nl + @nl + @nl ; -END; - - - -SELECT @sort = CASE @SortOrder WHEN N'cpu' THEN N'total_worker_time' - WHEN N'reads' THEN N'total_logical_reads' - WHEN N'writes' THEN N'total_logical_writes' - WHEN N'duration' THEN N'total_elapsed_time' - WHEN N'executions' THEN N'execution_count' - WHEN N'compiles' THEN N'cached_time' - WHEN N'memory grant' THEN N'max_grant_kb' - WHEN N'unused grant' THEN N'max_grant_kb - max_used_grant_kb' - WHEN N'spills' THEN N'max_spills' - WHEN N'duplicate' THEN N'total_worker_time' /* Issue #3345 */ - /* And now the averages */ - WHEN N'avg cpu' THEN N'total_worker_time / execution_count' - WHEN N'avg reads' THEN N'total_logical_reads / execution_count' - WHEN N'avg writes' THEN N'total_logical_writes / execution_count' - WHEN N'avg duration' THEN N'total_elapsed_time / execution_count' - WHEN N'avg memory grant' THEN N'CASE WHEN max_grant_kb = 0 THEN 0 ELSE max_grant_kb / execution_count END' - WHEN N'avg spills' THEN N'CASE WHEN total_spills = 0 THEN 0 ELSE total_spills / execution_count END' - WHEN N'avg executions' THEN N'CASE WHEN execution_count = 0 THEN 0 - WHEN COALESCE(age_minutes, age_minutes_lifetime, 0) = 0 THEN 0 - ELSE CAST((1.00 * execution_count / COALESCE(age_minutes, age_minutes_lifetime)) AS money) - END' - END ; - -SELECT @sql = REPLACE(@sql, '#sortable#', @sort); - -SET @sql += N' -SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -INSERT INTO #p (SqlHandle, TotalCPU, TotalReads, TotalDuration, TotalWrites, ExecutionCount) -SELECT SqlHandle, - TotalCPU, - TotalReads, - TotalDuration, - TotalWrites, - ExecutionCount -FROM (SELECT SqlHandle, - TotalCPU, - TotalReads, - TotalDuration, - TotalWrites, - ExecutionCount, - ROW_NUMBER() OVER (PARTITION BY SqlHandle ORDER BY #sortable# DESC) AS rn - FROM ##BlitzCacheProcs - WHERE SPID = @@SPID) AS x -WHERE x.rn = 1 -OPTION (RECOMPILE); - -/* - This block was used to delete duplicate queries, but has been removed. - For more info: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2026 -WITH d AS ( -SELECT SPID, - ROW_NUMBER() OVER (PARTITION BY SqlHandle, QueryHash ORDER BY #sortable# DESC) AS rn -FROM ##BlitzCacheProcs -WHERE SPID = @@SPID -) -DELETE d -WHERE d.rn > 1 -AND SPID = @@SPID -OPTION (RECOMPILE); -*/ -'; - -SELECT @sort = CASE @SortOrder WHEN N'cpu' THEN N'TotalCPU' - WHEN N'reads' THEN N'TotalReads' - WHEN N'writes' THEN N'TotalWrites' - WHEN N'duration' THEN N'TotalDuration' - WHEN N'executions' THEN N'ExecutionCount' - WHEN N'compiles' THEN N'PlanCreationTime' - WHEN N'memory grant' THEN N'MaxGrantKB' - WHEN N'unused grant' THEN N'MaxGrantKB - MaxUsedGrantKB' - WHEN N'spills' THEN N'MaxSpills' - WHEN N'duplicate' THEN N'TotalCPU' /* Issue #3345 */ - /* And now the averages */ - WHEN N'avg cpu' THEN N'TotalCPU / ExecutionCount' - WHEN N'avg reads' THEN N'TotalReads / ExecutionCount' - WHEN N'avg writes' THEN N'TotalWrites / ExecutionCount' - WHEN N'avg duration' THEN N'TotalDuration / ExecutionCount' - WHEN N'avg memory grant' THEN N'AvgMaxMemoryGrant' - WHEN N'avg spills' THEN N'AvgSpills' - WHEN N'avg executions' THEN N'CASE WHEN ExecutionCount = 0 THEN 0 - WHEN COALESCE(age_minutes, age_minutes_lifetime, 0) = 0 THEN 0 - ELSE CAST((1.00 * ExecutionCount / COALESCE(age_minutes, age_minutes_lifetime)) AS money) - END' - END ; - -SELECT @sql = REPLACE(@sql, '#sortable#', @sort); - - -IF @Debug = 1 - BEGIN - PRINT N'Printing dynamic SQL stored in @sql: '; - PRINT SUBSTRING(@sql, 0, 4000); - PRINT SUBSTRING(@sql, 4000, 8000); - PRINT SUBSTRING(@sql, 8000, 12000); - PRINT SUBSTRING(@sql, 12000, 16000); - PRINT SUBSTRING(@sql, 16000, 20000); - PRINT SUBSTRING(@sql, 20000, 24000); - PRINT SUBSTRING(@sql, 24000, 28000); - PRINT SUBSTRING(@sql, 28000, 32000); - PRINT SUBSTRING(@sql, 32000, 36000); - PRINT SUBSTRING(@sql, 36000, 40000); - END; - -RAISERROR(N'Creating temp tables for results and warnings.', 0, 1) WITH NOWAIT; - - -IF OBJECT_ID('tempdb.dbo.##BlitzCacheResults') IS NULL -BEGIN - CREATE TABLE ##BlitzCacheResults ( - SPID INT, - ID INT IDENTITY(1,1), - CheckID INT, - Priority TINYINT, - FindingsGroup VARCHAR(50), - Finding VARCHAR(500), - URL VARCHAR(200), - Details VARCHAR(4000) - ); -END; -ELSE -BEGIN - RAISERROR(N'Cleaning up old warnings for your SPID', 0, 1) WITH NOWAIT; - DELETE ##BlitzCacheResults - WHERE SPID = @@SPID - OPTION (RECOMPILE) ; -END - - -IF OBJECT_ID('tempdb.dbo.##BlitzCacheProcs') IS NULL -BEGIN - CREATE TABLE ##BlitzCacheProcs ( - SPID INT , - QueryType NVARCHAR(258), - DatabaseName sysname, - AverageCPU DECIMAL(38,4), - AverageCPUPerMinute DECIMAL(38,4), - TotalCPU DECIMAL(38,4), - PercentCPUByType MONEY, - PercentCPU MONEY, - AverageDuration DECIMAL(38,4), - TotalDuration DECIMAL(38,4), - PercentDuration MONEY, - PercentDurationByType MONEY, - AverageReads BIGINT, - TotalReads BIGINT, - PercentReads MONEY, - PercentReadsByType MONEY, - ExecutionCount BIGINT, - PercentExecutions MONEY, - PercentExecutionsByType MONEY, - ExecutionsPerMinute MONEY, - TotalWrites BIGINT, - AverageWrites MONEY, - PercentWrites MONEY, - PercentWritesByType MONEY, - WritesPerMinute MONEY, - PlanCreationTime DATETIME, - PlanCreationTimeHours AS DATEDIFF(HOUR, PlanCreationTime, SYSDATETIME()), - LastExecutionTime DATETIME, - LastCompletionTime DATETIME, - PlanHandle VARBINARY(64), - [Remove Plan Handle From Cache] AS - CASE WHEN [PlanHandle] IS NOT NULL - THEN 'DBCC FREEPROCCACHE (' + CONVERT(VARCHAR(128), [PlanHandle], 1) + ');' - ELSE 'N/A' END, - SqlHandle VARBINARY(64), - [Remove SQL Handle From Cache] AS - CASE WHEN [SqlHandle] IS NOT NULL - THEN 'DBCC FREEPROCCACHE (' + CONVERT(VARCHAR(128), [SqlHandle], 1) + ');' - ELSE 'N/A' END, - [SQL Handle More Info] AS - CASE WHEN [SqlHandle] IS NOT NULL - THEN 'EXEC sp_BlitzCache @OnlySqlHandles = ''' + CONVERT(VARCHAR(128), [SqlHandle], 1) + '''; ' - ELSE 'N/A' END, - QueryHash BINARY(8), - [Query Hash More Info] AS - CASE WHEN [QueryHash] IS NOT NULL - THEN 'EXEC sp_BlitzCache @OnlyQueryHashes = ''' + CONVERT(VARCHAR(32), [QueryHash], 1) + '''; ' - ELSE 'N/A' END, - QueryPlanHash BINARY(8), - StatementStartOffset INT, - StatementEndOffset INT, - PlanGenerationNum BIGINT, - MinReturnedRows BIGINT, - MaxReturnedRows BIGINT, - AverageReturnedRows MONEY, - TotalReturnedRows BIGINT, - LastReturnedRows BIGINT, - MinGrantKB BIGINT, - MaxGrantKB BIGINT, - MinUsedGrantKB BIGINT, - MaxUsedGrantKB BIGINT, - PercentMemoryGrantUsed MONEY, - AvgMaxMemoryGrant MONEY, - MinSpills BIGINT, - MaxSpills BIGINT, - TotalSpills BIGINT, - AvgSpills MONEY, - QueryText NVARCHAR(MAX), - QueryPlan XML, - /* these next four columns are the total for the type of query. - don't actually use them for anything apart from math by type. - */ - TotalWorkerTimeForType BIGINT, - TotalElapsedTimeForType BIGINT, - TotalReadsForType BIGINT, - TotalExecutionCountForType BIGINT, - TotalWritesForType BIGINT, - NumberOfPlans INT, - NumberOfDistinctPlans INT, - SerialDesiredMemory FLOAT, - SerialRequiredMemory FLOAT, - CachedPlanSize FLOAT, - CompileTime FLOAT, - CompileCPU FLOAT , - CompileMemory FLOAT , - MaxCompileMemory FLOAT , - min_worker_time BIGINT, - max_worker_time BIGINT, - is_forced_plan BIT, - is_forced_parameterized BIT, - is_cursor BIT, - is_optimistic_cursor BIT, - is_forward_only_cursor BIT, - is_fast_forward_cursor BIT, - is_cursor_dynamic BIT, - is_parallel BIT, - is_forced_serial BIT, - is_key_lookup_expensive BIT, - key_lookup_cost FLOAT, - is_remote_query_expensive BIT, - remote_query_cost FLOAT, - frequent_execution BIT, - parameter_sniffing BIT, - unparameterized_query BIT, - near_parallel BIT, - plan_warnings BIT, - plan_multiple_plans INT, - long_running BIT, - downlevel_estimator BIT, - implicit_conversions BIT, - busy_loops BIT, - tvf_join BIT, - tvf_estimate BIT, - compile_timeout BIT, - compile_memory_limit_exceeded BIT, - warning_no_join_predicate BIT, - QueryPlanCost FLOAT, - missing_index_count INT, - unmatched_index_count INT, - min_elapsed_time BIGINT, - max_elapsed_time BIGINT, - age_minutes MONEY, - age_minutes_lifetime MONEY, - is_trivial BIT, - trace_flags_session VARCHAR(1000), - is_unused_grant BIT, - function_count INT, - clr_function_count INT, - is_table_variable BIT, - no_stats_warning BIT, - relop_warnings BIT, - is_table_scan BIT, - backwards_scan BIT, - forced_index BIT, - forced_seek BIT, - forced_scan BIT, - columnstore_row_mode BIT, - is_computed_scalar BIT , - is_sort_expensive BIT, - sort_cost FLOAT, - is_computed_filter BIT, - op_name VARCHAR(100) NULL, - index_insert_count INT NULL, - index_update_count INT NULL, - index_delete_count INT NULL, - cx_insert_count INT NULL, - cx_update_count INT NULL, - cx_delete_count INT NULL, - table_insert_count INT NULL, - table_update_count INT NULL, - table_delete_count INT NULL, - index_ops AS (index_insert_count + index_update_count + index_delete_count + - cx_insert_count + cx_update_count + cx_delete_count + - table_insert_count + table_update_count + table_delete_count), - is_row_level BIT, - is_spatial BIT, - index_dml BIT, - table_dml BIT, - long_running_low_cpu BIT, - low_cost_high_cpu BIT, - stale_stats BIT, - is_adaptive BIT, - index_spool_cost FLOAT, - index_spool_rows FLOAT, - table_spool_cost FLOAT, - table_spool_rows FLOAT, - is_spool_expensive BIT, - is_spool_more_rows BIT, - is_table_spool_expensive BIT, - is_table_spool_more_rows BIT, - estimated_rows FLOAT, - is_bad_estimate BIT, - is_paul_white_electric BIT, - is_row_goal BIT, - is_big_spills BIT, - is_mstvf BIT, - is_mm_join BIT, - is_nonsargable BIT, - select_with_writes BIT, - implicit_conversion_info XML, - cached_execution_parameters XML, - missing_indexes XML, - SetOptions VARCHAR(MAX), - Warnings VARCHAR(MAX), - Pattern NVARCHAR(20) - ); -END; -ELSE -BEGIN - RAISERROR(N'Cleaning up old plans for your SPID', 0, 1) WITH NOWAIT; - DELETE ##BlitzCacheProcs - WHERE SPID = @@SPID - OPTION (RECOMPILE) ; -END - -IF @Reanalyze = 0 -BEGIN - RAISERROR('Collecting execution plan information.', 0, 1) WITH NOWAIT; - - EXEC sp_executesql @sql, N'@Top INT, @min_duration INT, @min_back INT, @SortOrder NVARCHAR(20)', @Top, @DurationFilter_i, @MinutesBack, @SortOrder; -END; - -IF @SkipAnalysis = 1 - BEGIN - RAISERROR(N'Skipping analysis, going to results', 0, 1) WITH NOWAIT; - GOTO Results ; - END; - - -/* Update ##BlitzCacheProcs to get Stored Proc info - * This should get totals for all statements in a Stored Proc - */ -RAISERROR(N'Attempting to aggregate stored proc info from separate statements', 0, 1) WITH NOWAIT; -;WITH agg AS ( - SELECT - b.SqlHandle, - SUM(b.MinReturnedRows) AS MinReturnedRows, - SUM(b.MaxReturnedRows) AS MaxReturnedRows, - SUM(b.AverageReturnedRows) AS AverageReturnedRows, - SUM(b.TotalReturnedRows) AS TotalReturnedRows, - SUM(b.LastReturnedRows) AS LastReturnedRows, - SUM(b.MinGrantKB) AS MinGrantKB, - SUM(b.MaxGrantKB) AS MaxGrantKB, - SUM(b.MinUsedGrantKB) AS MinUsedGrantKB, - SUM(b.MaxUsedGrantKB) AS MaxUsedGrantKB, - SUM(b.MinSpills) AS MinSpills, - SUM(b.MaxSpills) AS MaxSpills, - SUM(b.TotalSpills) AS TotalSpills - FROM ##BlitzCacheProcs b - WHERE b.SPID = @@SPID - AND b.QueryHash IS NOT NULL - GROUP BY b.SqlHandle -) -UPDATE b - SET - b.MinReturnedRows = b2.MinReturnedRows, - b.MaxReturnedRows = b2.MaxReturnedRows, - b.AverageReturnedRows = b2.AverageReturnedRows, - b.TotalReturnedRows = b2.TotalReturnedRows, - b.LastReturnedRows = b2.LastReturnedRows, - b.MinGrantKB = b2.MinGrantKB, - b.MaxGrantKB = b2.MaxGrantKB, - b.MinUsedGrantKB = b2.MinUsedGrantKB, - b.MaxUsedGrantKB = b2.MaxUsedGrantKB, - b.MinSpills = b2.MinSpills, - b.MaxSpills = b2.MaxSpills, - b.TotalSpills = b2.TotalSpills -FROM ##BlitzCacheProcs b -JOIN agg b2 -ON b2.SqlHandle = b.SqlHandle -WHERE b.QueryHash IS NULL -AND b.SPID = @@SPID -OPTION (RECOMPILE) ; - -/* Compute the total CPU, etc across our active set of the plan cache. - * Yes, there's a flaw - this doesn't include anything outside of our @Top - * metric. - */ -RAISERROR('Computing CPU, duration, read, and write metrics', 0, 1) WITH NOWAIT; -DECLARE @total_duration BIGINT, - @total_cpu BIGINT, - @total_reads BIGINT, - @total_writes BIGINT, - @total_execution_count BIGINT; - -SELECT @total_cpu = SUM(TotalCPU), - @total_duration = SUM(TotalDuration), - @total_reads = SUM(TotalReads), - @total_writes = SUM(TotalWrites), - @total_execution_count = SUM(ExecutionCount) -FROM #p -OPTION (RECOMPILE) ; - -DECLARE @cr NVARCHAR(1) = NCHAR(13); -DECLARE @lf NVARCHAR(1) = NCHAR(10); -DECLARE @tab NVARCHAR(1) = NCHAR(9); - -/* Update CPU percentage for stored procedures */ -RAISERROR(N'Update CPU percentage for stored procedures', 0, 1) WITH NOWAIT; -UPDATE ##BlitzCacheProcs -SET PercentCPU = y.PercentCPU, - PercentDuration = y.PercentDuration, - PercentReads = y.PercentReads, - PercentWrites = y.PercentWrites, - PercentExecutions = y.PercentExecutions, - ExecutionsPerMinute = y.ExecutionsPerMinute, - /* Strip newlines and tabs. Tabs are replaced with multiple spaces - so that the later whitespace trim will completely eliminate them - */ - QueryText = REPLACE(REPLACE(REPLACE(QueryText, @cr, ' '), @lf, ' '), @tab, ' ') -FROM ( - SELECT PlanHandle, - CASE @total_cpu WHEN 0 THEN 0 - ELSE CAST((100. * TotalCPU) / @total_cpu AS MONEY) END AS PercentCPU, - CASE @total_duration WHEN 0 THEN 0 - ELSE CAST((100. * TotalDuration) / @total_duration AS MONEY) END AS PercentDuration, - CASE @total_reads WHEN 0 THEN 0 - ELSE CAST((100. * TotalReads) / @total_reads AS MONEY) END AS PercentReads, - CASE @total_writes WHEN 0 THEN 0 - ELSE CAST((100. * TotalWrites) / @total_writes AS MONEY) END AS PercentWrites, - CASE @total_execution_count WHEN 0 THEN 0 - ELSE CAST((100. * ExecutionCount) / @total_execution_count AS MONEY) END AS PercentExecutions, - CASE DATEDIFF(mi, PlanCreationTime, LastExecutionTime) - WHEN 0 THEN 0 - ELSE CAST((1.00 * ExecutionCount / DATEDIFF(mi, PlanCreationTime, LastExecutionTime)) AS MONEY) - END AS ExecutionsPerMinute - FROM ( - SELECT PlanHandle, - TotalCPU, - TotalDuration, - TotalReads, - TotalWrites, - ExecutionCount, - PlanCreationTime, - LastExecutionTime - FROM ##BlitzCacheProcs - WHERE PlanHandle IS NOT NULL - AND SPID = @@SPID - GROUP BY PlanHandle, - TotalCPU, - TotalDuration, - TotalReads, - TotalWrites, - ExecutionCount, - PlanCreationTime, - LastExecutionTime - ) AS x -) AS y -WHERE ##BlitzCacheProcs.PlanHandle = y.PlanHandle - AND ##BlitzCacheProcs.PlanHandle IS NOT NULL - AND ##BlitzCacheProcs.SPID = @@SPID -OPTION (RECOMPILE) ; - - -RAISERROR(N'Gather percentage information from grouped results', 0, 1) WITH NOWAIT; -UPDATE ##BlitzCacheProcs -SET PercentCPU = y.PercentCPU, - PercentDuration = y.PercentDuration, - PercentReads = y.PercentReads, - PercentWrites = y.PercentWrites, - PercentExecutions = y.PercentExecutions, - ExecutionsPerMinute = y.ExecutionsPerMinute, - /* Strip newlines and tabs. Tabs are replaced with multiple spaces - so that the later whitespace trim will completely eliminate them - */ - QueryText = REPLACE(REPLACE(REPLACE(QueryText, @cr, ' '), @lf, ' '), @tab, ' ') -FROM ( - SELECT DatabaseName, - SqlHandle, - QueryHash, - CASE @total_cpu WHEN 0 THEN 0 - ELSE CAST((100. * TotalCPU) / @total_cpu AS MONEY) END AS PercentCPU, - CASE @total_duration WHEN 0 THEN 0 - ELSE CAST((100. * TotalDuration) / @total_duration AS MONEY) END AS PercentDuration, - CASE @total_reads WHEN 0 THEN 0 - ELSE CAST((100. * TotalReads) / @total_reads AS MONEY) END AS PercentReads, - CASE @total_writes WHEN 0 THEN 0 - ELSE CAST((100. * TotalWrites) / @total_writes AS MONEY) END AS PercentWrites, - CASE @total_execution_count WHEN 0 THEN 0 - ELSE CAST((100. * ExecutionCount) / @total_execution_count AS MONEY) END AS PercentExecutions, - CASE DATEDIFF(mi, PlanCreationTime, LastExecutionTime) - WHEN 0 THEN 0 - ELSE CAST((1.00 * ExecutionCount / DATEDIFF(mi, PlanCreationTime, LastExecutionTime)) AS MONEY) - END AS ExecutionsPerMinute - FROM ( - SELECT DatabaseName, - SqlHandle, - QueryHash, - TotalCPU, - TotalDuration, - TotalReads, - TotalWrites, - ExecutionCount, - PlanCreationTime, - LastExecutionTime - FROM ##BlitzCacheProcs - WHERE SPID = @@SPID - GROUP BY DatabaseName, - SqlHandle, - QueryHash, - TotalCPU, - TotalDuration, - TotalReads, - TotalWrites, - ExecutionCount, - PlanCreationTime, - LastExecutionTime - ) AS x -) AS y -WHERE ##BlitzCacheProcs.SqlHandle = y.SqlHandle - AND ##BlitzCacheProcs.QueryHash = y.QueryHash - AND ##BlitzCacheProcs.DatabaseName = y.DatabaseName - AND ##BlitzCacheProcs.PlanHandle IS NULL -OPTION (RECOMPILE) ; - - - -/* Testing using XML nodes to speed up processing */ -RAISERROR(N'Begin XML nodes processing', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -SELECT QueryHash , - SqlHandle , - PlanHandle, - q.n.query('.') AS statement, - 0 AS is_cursor -INTO #statements -FROM ##BlitzCacheProcs p - CROSS APPLY p.QueryPlan.nodes('//p:StmtSimple') AS q(n) -WHERE p.SPID = @@SPID -OPTION (RECOMPILE) ; - -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -INSERT #statements -SELECT QueryHash , - SqlHandle , - PlanHandle, - q.n.query('.') AS statement, - 1 AS is_cursor -FROM ##BlitzCacheProcs p - CROSS APPLY p.QueryPlan.nodes('//p:StmtCursor') AS q(n) -WHERE p.SPID = @@SPID -OPTION (RECOMPILE) ; - -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -SELECT QueryHash , - SqlHandle , - q.n.query('.') AS query_plan -INTO #query_plan -FROM #statements p - CROSS APPLY p.statement.nodes('//p:QueryPlan') AS q(n) -OPTION (RECOMPILE) ; - -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -SELECT QueryHash , - SqlHandle , - q.n.query('.') AS relop -INTO #relop -FROM #query_plan p - CROSS APPLY p.query_plan.nodes('//p:RelOp') AS q(n) -OPTION (RECOMPILE) ; - --- high level plan stuff -RAISERROR(N'Gathering high level plan information', 0, 1) WITH NOWAIT; -UPDATE ##BlitzCacheProcs -SET NumberOfDistinctPlans = distinct_plan_count, - NumberOfPlans = number_of_plans , - plan_multiple_plans = CASE WHEN distinct_plan_count < number_of_plans THEN number_of_plans END -FROM - ( - SELECT - DatabaseName = - DB_NAME(CONVERT(int, pa.value)), - QueryHash = - qs.query_hash, - number_of_plans = - COUNT_BIG(qs.query_plan_hash), - distinct_plan_count = - COUNT_BIG(DISTINCT qs.query_plan_hash) - FROM sys.dm_exec_query_stats AS qs - CROSS APPLY sys.dm_exec_plan_attributes(qs.plan_handle) pa - WHERE pa.attribute = 'dbid' - GROUP BY - DB_NAME(CONVERT(int, pa.value)), - qs.query_hash -) AS x -WHERE ##BlitzCacheProcs.QueryHash = x.QueryHash -AND ##BlitzCacheProcs.DatabaseName = x.DatabaseName -OPTION (RECOMPILE) ; - --- query level checks -RAISERROR(N'Performing query level checks', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE ##BlitzCacheProcs -SET missing_index_count = query_plan.value('count(//p:QueryPlan/p:MissingIndexes/p:MissingIndexGroup)', 'int') , - unmatched_index_count = CASE WHEN is_trivial <> 1 THEN query_plan.value('count(//p:QueryPlan/p:UnmatchedIndexes/p:Parameterization/p:Object)', 'int') END , - SerialDesiredMemory = query_plan.value('sum(//p:QueryPlan/p:MemoryGrantInfo/@SerialDesiredMemory)', 'float') , - SerialRequiredMemory = query_plan.value('sum(//p:QueryPlan/p:MemoryGrantInfo/@SerialRequiredMemory)', 'float'), - CachedPlanSize = query_plan.value('sum(//p:QueryPlan/@CachedPlanSize)', 'float') , - CompileTime = query_plan.value('sum(//p:QueryPlan/@CompileTime)', 'float') , - CompileCPU = query_plan.value('sum(//p:QueryPlan/@CompileCPU)', 'float') , - CompileMemory = query_plan.value('sum(//p:QueryPlan/@CompileMemory)', 'float'), - MaxCompileMemory = query_plan.value('sum(//p:QueryPlan/p:OptimizerHardwareDependentProperties/@MaxCompileMemory)', 'float') -FROM #query_plan qp -WHERE qp.QueryHash = ##BlitzCacheProcs.QueryHash -AND qp.SqlHandle = ##BlitzCacheProcs.SqlHandle -AND SPID = @@SPID -OPTION (RECOMPILE); - --- statement level checks -RAISERROR(N'Performing compile timeout checks', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET compile_timeout = 1 -FROM #statements s -JOIN ##BlitzCacheProcs b -ON s.QueryHash = b.QueryHash -AND SPID = @@SPID -WHERE statement.exist('/p:StmtSimple/@StatementOptmEarlyAbortReason[.="TimeOut"]') = 1 -OPTION (RECOMPILE); - -RAISERROR(N'Performing compile memory limit exceeded checks', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET compile_memory_limit_exceeded = 1 -FROM #statements s -JOIN ##BlitzCacheProcs b -ON s.QueryHash = b.QueryHash -AND SPID = @@SPID -WHERE statement.exist('/p:StmtSimple/@StatementOptmEarlyAbortReason[.="MemoryLimitExceeded"]') = 1 -OPTION (RECOMPILE); - -IF @ExpertMode > 0 -BEGIN -RAISERROR(N'Performing unparameterized query checks', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p), -unparameterized_query AS ( - SELECT s.QueryHash, - unparameterized_query = CASE WHEN statement.exist('//p:StmtSimple[@StatementOptmLevel[.="FULL"]]/p:QueryPlan/p:ParameterList') = 1 AND - statement.exist('//p:StmtSimple[@StatementOptmLevel[.="FULL"]]/p:QueryPlan/p:ParameterList/p:ColumnReference') = 0 THEN 1 - WHEN statement.exist('//p:StmtSimple[@StatementOptmLevel[.="FULL"]]/p:QueryPlan/p:ParameterList') = 0 AND - statement.exist('//p:StmtSimple[@StatementOptmLevel[.="FULL"]]/*/p:RelOp/descendant::p:ScalarOperator/p:Identifier/p:ColumnReference[contains(@Column, "@")]') = 1 THEN 1 - END - FROM #statements AS s - ) -UPDATE b -SET b.unparameterized_query = u.unparameterized_query -FROM ##BlitzCacheProcs b -JOIN unparameterized_query u -ON u.QueryHash = b.QueryHash -AND SPID = @@SPID -WHERE u.unparameterized_query = 1 -OPTION (RECOMPILE); -END; - - -IF @ExpertMode > 0 -BEGIN -RAISERROR(N'Performing index DML checks', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p), -index_dml AS ( - SELECT s.QueryHash, - index_dml = CASE WHEN statement.exist('//p:StmtSimple/@StatementType[.="CREATE INDEX"]') = 1 THEN 1 - WHEN statement.exist('//p:StmtSimple/@StatementType[.="DROP INDEX"]') = 1 THEN 1 - END - FROM #statements s - ) - UPDATE b - SET b.index_dml = i.index_dml - FROM ##BlitzCacheProcs AS b - JOIN index_dml i - ON i.QueryHash = b.QueryHash - WHERE i.index_dml = 1 - AND b.SPID = @@SPID - OPTION (RECOMPILE); -END; - - -IF @ExpertMode > 0 -BEGIN -RAISERROR(N'Performing table DML checks', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p), -table_dml AS ( - SELECT s.QueryHash, - table_dml = CASE WHEN statement.exist('//p:StmtSimple/@StatementType[.="CREATE TABLE"]') = 1 THEN 1 - WHEN statement.exist('//p:StmtSimple/@StatementType[.="DROP OBJECT"]') = 1 THEN 1 - END - FROM #statements AS s - ) - UPDATE b - SET b.table_dml = t.table_dml - FROM ##BlitzCacheProcs AS b - JOIN table_dml t - ON t.QueryHash = b.QueryHash - WHERE t.table_dml = 1 - AND b.SPID = @@SPID - OPTION (RECOMPILE); -END; - - -IF @ExpertMode > 0 -BEGIN -RAISERROR(N'Gathering row estimates', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES ('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) -INSERT INTO #est_rows -SELECT DISTINCT - CONVERT(BINARY(8), RIGHT('0000000000000000' + SUBSTRING(c.n.value('@QueryHash', 'VARCHAR(18)'), 3, 18), 16), 2) AS QueryHash, - c.n.value('(/p:StmtSimple/@StatementEstRows)[1]', 'FLOAT') AS estimated_rows -FROM #statements AS s -CROSS APPLY s.statement.nodes('/p:StmtSimple') AS c(n) -WHERE c.n.exist('/p:StmtSimple[@StatementEstRows > 0]') = 1; - - UPDATE b - SET b.estimated_rows = er.estimated_rows - FROM ##BlitzCacheProcs AS b - JOIN #est_rows er - ON er.QueryHash = b.QueryHash - WHERE b.SPID = @@SPID - AND b.QueryType = 'Statement' - OPTION (RECOMPILE); -END; - -RAISERROR(N'Gathering trivial plans', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES ( 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) -UPDATE b -SET b.is_trivial = 1 -FROM ##BlitzCacheProcs AS b -JOIN ( -SELECT s.SqlHandle -FROM #statements AS s -JOIN ( SELECT r.SqlHandle - FROM #relop AS r - WHERE r.relop.exist('//p:RelOp[contains(@LogicalOp, "Scan")]') = 1 ) AS r - ON r.SqlHandle = s.SqlHandle -WHERE s.statement.exist('//p:StmtSimple[@StatementOptmLevel[.="TRIVIAL"]]/p:QueryPlan/p:ParameterList') = 1 -) AS s -ON b.SqlHandle = s.SqlHandle -OPTION (RECOMPILE); - - ---Gather costs -RAISERROR(N'Gathering statement costs', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -INSERT INTO #plan_cost ( QueryPlanCost, SqlHandle, PlanHandle, QueryHash, QueryPlanHash ) -SELECT DISTINCT - statement.value('sum(/p:StmtSimple/@StatementSubTreeCost)', 'float') QueryPlanCost, - s.SqlHandle, - s.PlanHandle, - CONVERT(BINARY(8), RIGHT('0000000000000000' + SUBSTRING(q.n.value('@QueryHash', 'VARCHAR(18)'), 3, 18), 16), 2) AS QueryHash, - CONVERT(BINARY(8), RIGHT('0000000000000000' + SUBSTRING(q.n.value('@QueryPlanHash', 'VARCHAR(18)'), 3, 18), 16), 2) AS QueryPlanHash -FROM #statements s -CROSS APPLY s.statement.nodes('/p:StmtSimple') AS q(n) -WHERE statement.value('sum(/p:StmtSimple/@StatementSubTreeCost)', 'float') > 0 -OPTION (RECOMPILE); - -RAISERROR(N'Updating statement costs', 0, 1) WITH NOWAIT; -WITH pc AS ( - SELECT SUM(DISTINCT pc.QueryPlanCost) AS QueryPlanCostSum, pc.QueryHash, pc.QueryPlanHash, pc.SqlHandle, pc.PlanHandle - FROM #plan_cost AS pc - GROUP BY pc.QueryHash, pc.QueryPlanHash, pc.SqlHandle, pc.PlanHandle -) - UPDATE b - SET b.QueryPlanCost = ISNULL(pc.QueryPlanCostSum, 0) - FROM pc - JOIN ##BlitzCacheProcs b - ON b.SqlHandle = pc.SqlHandle - AND b.QueryHash = pc.QueryHash - WHERE b.QueryType NOT LIKE '%Procedure%' - OPTION (RECOMPILE); - -IF EXISTS ( -SELECT 1 -FROM ##BlitzCacheProcs AS b -WHERE b.QueryType LIKE 'Procedure%' -) - -BEGIN - -RAISERROR(N'Gathering stored procedure costs', 0, 1) WITH NOWAIT; -;WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -, QueryCost AS ( - SELECT - DISTINCT - statement.value('sum(/p:StmtSimple/@StatementSubTreeCost)', 'float') AS SubTreeCost, - s.PlanHandle, - s.SqlHandle - FROM #statements AS s - WHERE PlanHandle IS NOT NULL -) -, QueryCostUpdate AS ( - SELECT - SUM(qc.SubTreeCost) OVER (PARTITION BY SqlHandle, PlanHandle) PlanTotalQuery, - qc.PlanHandle, - qc.SqlHandle - FROM QueryCost qc -) -INSERT INTO #proc_costs -SELECT qcu.PlanTotalQuery, PlanHandle, SqlHandle -FROM QueryCostUpdate AS qcu -OPTION (RECOMPILE); - - -UPDATE b - SET b.QueryPlanCost = ca.PlanTotalQuery -FROM ##BlitzCacheProcs AS b -CROSS APPLY ( - SELECT TOP 1 PlanTotalQuery - FROM #proc_costs qcu - WHERE qcu.PlanHandle = b.PlanHandle - ORDER BY PlanTotalQuery DESC -) ca -WHERE b.QueryType LIKE 'Procedure%' -AND b.SPID = @@SPID -OPTION (RECOMPILE); - -END; - -UPDATE b -SET b.QueryPlanCost = 0.0 -FROM ##BlitzCacheProcs b -WHERE b.QueryPlanCost IS NULL -AND b.SPID = @@SPID -OPTION (RECOMPILE); - -RAISERROR(N'Checking for plan warnings', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE ##BlitzCacheProcs -SET plan_warnings = 1 -FROM #query_plan qp -WHERE qp.SqlHandle = ##BlitzCacheProcs.SqlHandle -AND SPID = @@SPID -AND query_plan.exist('/p:QueryPlan/p:Warnings') = 1 -OPTION (RECOMPILE); - -RAISERROR(N'Checking for implicit conversion', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE ##BlitzCacheProcs -SET implicit_conversions = 1 -FROM #query_plan qp -WHERE qp.SqlHandle = ##BlitzCacheProcs.SqlHandle -AND SPID = @@SPID -AND query_plan.exist('/p:QueryPlan/p:Warnings/p:PlanAffectingConvert/@Expression[contains(., "CONVERT_IMPLICIT")]') = 1 -OPTION (RECOMPILE); - --- operator level checks -IF @ExpertMode > 0 -BEGIN -RAISERROR(N'Performing busy loops checks', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE p -SET busy_loops = CASE WHEN (x.estimated_executions / 100.0) > x.estimated_rows THEN 1 END -FROM ##BlitzCacheProcs p - JOIN ( - SELECT qs.SqlHandle, - relop.value('sum(/p:RelOp/@EstimateRows)', 'float') AS estimated_rows , - relop.value('sum(/p:RelOp/@EstimateRewinds)', 'float') + relop.value('sum(/p:RelOp/@EstimateRebinds)', 'float') + 1.0 AS estimated_executions - FROM #relop qs - ) AS x ON p.SqlHandle = x.SqlHandle -WHERE SPID = @@SPID -OPTION (RECOMPILE); -END; - - -RAISERROR(N'Performing TVF join check', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE p -SET p.tvf_join = CASE WHEN x.tvf_join = 1 THEN 1 END -FROM ##BlitzCacheProcs p - JOIN ( - SELECT r.SqlHandle, - 1 AS tvf_join - FROM #relop AS r - WHERE r.relop.exist('//p:RelOp[(@LogicalOp[.="Table-valued function"])]') = 1 - AND r.relop.exist('//p:RelOp[contains(@LogicalOp, "Join")]') = 1 - ) AS x ON p.SqlHandle = x.SqlHandle -WHERE SPID = @@SPID -OPTION (RECOMPILE); - -IF @ExpertMode > 0 -BEGIN -RAISERROR(N'Checking for operator warnings', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -, x AS ( -SELECT r.SqlHandle, - c.n.exist('//p:Warnings[(@NoJoinPredicate[.="1"])]') AS warning_no_join_predicate, - c.n.exist('//p:ColumnsWithNoStatistics') AS no_stats_warning , - c.n.exist('//p:Warnings') AS relop_warnings -FROM #relop AS r -CROSS APPLY r.relop.nodes('/p:RelOp/p:Warnings') AS c(n) -) -UPDATE p -SET p.warning_no_join_predicate = x.warning_no_join_predicate, - p.no_stats_warning = x.no_stats_warning, - p.relop_warnings = x.relop_warnings -FROM ##BlitzCacheProcs AS p -JOIN x ON x.SqlHandle = p.SqlHandle -AND SPID = @@SPID -OPTION (RECOMPILE); -END; - - -RAISERROR(N'Checking for table variables', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -, x AS ( -SELECT r.SqlHandle, - c.n.value('substring(@Table, 2, 1)','VARCHAR(100)') AS first_char -FROM #relop r -CROSS APPLY r.relop.nodes('//p:Object') AS c(n) -) -UPDATE p -SET is_table_variable = 1 -FROM ##BlitzCacheProcs AS p -JOIN x ON x.SqlHandle = p.SqlHandle -AND SPID = @@SPID -WHERE x.first_char = '@' -OPTION (RECOMPILE); - -IF @ExpertMode > 0 -BEGIN -RAISERROR(N'Checking for functions', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -, x AS ( -SELECT qs.SqlHandle, - n.fn.value('count(distinct-values(//p:UserDefinedFunction[not(@IsClrFunction)]))', 'INT') AS function_count, - n.fn.value('count(distinct-values(//p:UserDefinedFunction[@IsClrFunction = "1"]))', 'INT') AS clr_function_count -FROM #relop qs -CROSS APPLY relop.nodes('/p:RelOp/p:ComputeScalar/p:DefinedValues/p:DefinedValue/p:ScalarOperator') n(fn) -) -UPDATE p -SET p.function_count = x.function_count, - p.clr_function_count = x.clr_function_count -FROM ##BlitzCacheProcs AS p -JOIN x ON x.SqlHandle = p.SqlHandle -AND SPID = @@SPID -OPTION (RECOMPILE); -END; - - -RAISERROR(N'Checking for expensive key lookups', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE ##BlitzCacheProcs -SET key_lookup_cost = x.key_lookup_cost -FROM ( -SELECT - qs.SqlHandle, - MAX(relop.value('sum(/p:RelOp/@EstimatedTotalSubtreeCost)', 'float')) AS key_lookup_cost -FROM #relop qs -WHERE [relop].exist('/p:RelOp/p:IndexScan[(@Lookup[.="1"])]') = 1 -GROUP BY qs.SqlHandle -) AS x -WHERE ##BlitzCacheProcs.SqlHandle = x.SqlHandle -AND SPID = @@SPID -OPTION (RECOMPILE); - - -RAISERROR(N'Checking for expensive remote queries', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE ##BlitzCacheProcs -SET remote_query_cost = x.remote_query_cost -FROM ( -SELECT - qs.SqlHandle, - MAX(relop.value('sum(/p:RelOp/@EstimatedTotalSubtreeCost)', 'float')) AS remote_query_cost -FROM #relop qs -WHERE [relop].exist('/p:RelOp[(@PhysicalOp[contains(., "Remote")])]') = 1 -GROUP BY qs.SqlHandle -) AS x -WHERE ##BlitzCacheProcs.SqlHandle = x.SqlHandle -AND SPID = @@SPID -OPTION (RECOMPILE); - -RAISERROR(N'Checking for expensive sorts', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE ##BlitzCacheProcs -SET sort_cost = y.max_sort_cost -FROM ( - SELECT x.SqlHandle, MAX((x.sort_io + x.sort_cpu)) AS max_sort_cost - FROM ( - SELECT - qs.SqlHandle, - relop.value('sum(/p:RelOp/@EstimateIO)', 'float') AS sort_io, - relop.value('sum(/p:RelOp/@EstimateCPU)', 'float') AS sort_cpu - FROM #relop qs - WHERE [relop].exist('/p:RelOp[(@PhysicalOp[.="Sort"])]') = 1 - ) AS x - GROUP BY x.SqlHandle - ) AS y -WHERE ##BlitzCacheProcs.SqlHandle = y.SqlHandle -AND SPID = @@SPID -OPTION (RECOMPILE); - -IF NOT EXISTS(SELECT 1/0 FROM #statements AS s WHERE s.is_cursor = 1) -BEGIN - -RAISERROR(N'No cursor plans found, skipping', 0, 1) WITH NOWAIT; - -END - -IF EXISTS(SELECT 1/0 FROM #statements AS s WHERE s.is_cursor = 1) -BEGIN - -RAISERROR(N'Cursor plans found, investigating', 0, 1) WITH NOWAIT; - -RAISERROR(N'Checking for Optimistic cursors', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET b.is_optimistic_cursor = 1 -FROM ##BlitzCacheProcs b -JOIN #statements AS qs -ON b.SqlHandle = qs.SqlHandle -CROSS APPLY qs.statement.nodes('/p:StmtCursor') AS n1(fn) -WHERE SPID = @@SPID -AND n1.fn.exist('//p:CursorPlan/@CursorConcurrency[.="Optimistic"]') = 1 -AND qs.is_cursor = 1 -OPTION (RECOMPILE); - - -RAISERROR(N'Checking if cursor is Forward Only', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET b.is_forward_only_cursor = 1 -FROM ##BlitzCacheProcs b -JOIN #statements AS qs -ON b.SqlHandle = qs.SqlHandle -CROSS APPLY qs.statement.nodes('/p:StmtCursor') AS n1(fn) -WHERE SPID = @@SPID -AND n1.fn.exist('//p:CursorPlan/@ForwardOnly[.="true"]') = 1 -AND qs.is_cursor = 1 -OPTION (RECOMPILE); - -RAISERROR(N'Checking if cursor is Fast Forward', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET b.is_fast_forward_cursor = 1 -FROM ##BlitzCacheProcs b -JOIN #statements AS qs -ON b.SqlHandle = qs.SqlHandle -CROSS APPLY qs.statement.nodes('/p:StmtCursor') AS n1(fn) -WHERE SPID = @@SPID -AND n1.fn.exist('//p:CursorPlan/@CursorActualType[.="FastForward"]') = 1 -AND qs.is_cursor = 1 -OPTION (RECOMPILE); - - -RAISERROR(N'Checking for Dynamic cursors', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET b.is_cursor_dynamic = 1 -FROM ##BlitzCacheProcs b -JOIN #statements AS qs -ON b.SqlHandle = qs.SqlHandle -CROSS APPLY qs.statement.nodes('/p:StmtCursor') AS n1(fn) -WHERE SPID = @@SPID -AND n1.fn.exist('//p:CursorPlan/@CursorActualType[.="Dynamic"]') = 1 -AND qs.is_cursor = 1 -OPTION (RECOMPILE); - -END - -IF @ExpertMode > 0 -BEGIN -RAISERROR(N'Checking for bad scans and plan forcing', 0, 1) WITH NOWAIT; -;WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET -b.is_table_scan = x.is_table_scan, -b.backwards_scan = x.backwards_scan, -b.forced_index = x.forced_index, -b.forced_seek = x.forced_seek, -b.forced_scan = x.forced_scan -FROM ##BlitzCacheProcs b -JOIN ( -SELECT - qs.SqlHandle, - 0 AS is_table_scan, - q.n.exist('@ScanDirection[.="BACKWARD"]') AS backwards_scan, - q.n.value('@ForcedIndex', 'bit') AS forced_index, - q.n.value('@ForceSeek', 'bit') AS forced_seek, - q.n.value('@ForceScan', 'bit') AS forced_scan -FROM #relop qs -CROSS APPLY qs.relop.nodes('//p:IndexScan') AS q(n) -UNION ALL -SELECT - qs.SqlHandle, - 1 AS is_table_scan, - q.n.exist('@ScanDirection[.="BACKWARD"]') AS backwards_scan, - q.n.value('@ForcedIndex', 'bit') AS forced_index, - q.n.value('@ForceSeek', 'bit') AS forced_seek, - q.n.value('@ForceScan', 'bit') AS forced_scan -FROM #relop qs -CROSS APPLY qs.relop.nodes('//p:TableScan') AS q(n) -) AS x ON b.SqlHandle = x.SqlHandle -WHERE SPID = @@SPID -OPTION (RECOMPILE); -END; - - -IF @ExpertMode > 0 -BEGIN -RAISERROR(N'Checking for computed columns that reference scalar UDFs', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE ##BlitzCacheProcs -SET is_computed_scalar = x.computed_column_function -FROM ( -SELECT qs.SqlHandle, - n.fn.value('count(distinct-values(//p:UserDefinedFunction[not(@IsClrFunction)]))', 'INT') AS computed_column_function -FROM #relop qs -CROSS APPLY relop.nodes('/p:RelOp/p:ComputeScalar/p:DefinedValues/p:DefinedValue/p:ScalarOperator') n(fn) -WHERE n.fn.exist('/p:RelOp/p:ComputeScalar/p:DefinedValues/p:DefinedValue/p:ColumnReference[(@ComputedColumn[.="1"])]') = 1 -) AS x -WHERE ##BlitzCacheProcs.SqlHandle = x.SqlHandle -AND SPID = @@SPID -OPTION (RECOMPILE); -END; - - -RAISERROR(N'Checking for filters that reference scalar UDFs', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE ##BlitzCacheProcs -SET is_computed_filter = x.filter_function -FROM ( -SELECT -r.SqlHandle, -c.n.value('count(distinct-values(//p:UserDefinedFunction[not(@IsClrFunction)]))', 'INT') AS filter_function -FROM #relop AS r -CROSS APPLY r.relop.nodes('/p:RelOp/p:Filter/p:Predicate/p:ScalarOperator/p:Compare/p:ScalarOperator/p:UserDefinedFunction') c(n) -) x -WHERE ##BlitzCacheProcs.SqlHandle = x.SqlHandle -AND SPID = @@SPID -OPTION (RECOMPILE); - -IF @ExpertMode > 0 -BEGIN -RAISERROR(N'Checking modification queries that hit lots of indexes', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p), -IndexOps AS -( - SELECT - r.QueryHash, - c.n.value('@PhysicalOp', 'VARCHAR(100)') AS op_name, - c.n.exist('@PhysicalOp[.="Index Insert"]') AS ii, - c.n.exist('@PhysicalOp[.="Index Update"]') AS iu, - c.n.exist('@PhysicalOp[.="Index Delete"]') AS id, - c.n.exist('@PhysicalOp[.="Clustered Index Insert"]') AS cii, - c.n.exist('@PhysicalOp[.="Clustered Index Update"]') AS ciu, - c.n.exist('@PhysicalOp[.="Clustered Index Delete"]') AS cid, - c.n.exist('@PhysicalOp[.="Table Insert"]') AS ti, - c.n.exist('@PhysicalOp[.="Table Update"]') AS tu, - c.n.exist('@PhysicalOp[.="Table Delete"]') AS td - FROM #relop AS r - CROSS APPLY r.relop.nodes('/p:RelOp') c(n) - OUTER APPLY r.relop.nodes('/p:RelOp/p:ScalarInsert/p:Object') q(n) - OUTER APPLY r.relop.nodes('/p:RelOp/p:Update/p:Object') o2(n) - OUTER APPLY r.relop.nodes('/p:RelOp/p:SimpleUpdate/p:Object') o3(n) -), iops AS -( - SELECT ios.QueryHash, - SUM(CONVERT(TINYINT, ios.ii)) AS index_insert_count, - SUM(CONVERT(TINYINT, ios.iu)) AS index_update_count, - SUM(CONVERT(TINYINT, ios.id)) AS index_delete_count, - SUM(CONVERT(TINYINT, ios.cii)) AS cx_insert_count, - SUM(CONVERT(TINYINT, ios.ciu)) AS cx_update_count, - SUM(CONVERT(TINYINT, ios.cid)) AS cx_delete_count, - SUM(CONVERT(TINYINT, ios.ti)) AS table_insert_count, - SUM(CONVERT(TINYINT, ios.tu)) AS table_update_count, - SUM(CONVERT(TINYINT, ios.td)) AS table_delete_count - FROM IndexOps AS ios - WHERE ios.op_name IN ('Index Insert', 'Index Delete', 'Index Update', - 'Clustered Index Insert', 'Clustered Index Delete', 'Clustered Index Update', - 'Table Insert', 'Table Delete', 'Table Update') - GROUP BY ios.QueryHash) -UPDATE b -SET b.index_insert_count = iops.index_insert_count, - b.index_update_count = iops.index_update_count, - b.index_delete_count = iops.index_delete_count, - b.cx_insert_count = iops.cx_insert_count, - b.cx_update_count = iops.cx_update_count, - b.cx_delete_count = iops.cx_delete_count, - b.table_insert_count = iops.table_insert_count, - b.table_update_count = iops.table_update_count, - b.table_delete_count = iops.table_delete_count -FROM ##BlitzCacheProcs AS b -JOIN iops ON iops.QueryHash = b.QueryHash -WHERE SPID = @@SPID -OPTION (RECOMPILE); -END; - - -IF @ExpertMode > 0 -BEGIN -RAISERROR(N'Checking for Spatial index use', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE ##BlitzCacheProcs -SET is_spatial = x.is_spatial -FROM ( -SELECT qs.SqlHandle, - 1 AS is_spatial -FROM #relop qs -CROSS APPLY relop.nodes('/p:RelOp//p:Object') n(fn) -WHERE n.fn.exist('(@IndexKind[.="Spatial"])') = 1 -) AS x -WHERE ##BlitzCacheProcs.SqlHandle = x.SqlHandle -AND SPID = @@SPID -OPTION (RECOMPILE); -END; - - -RAISERROR('Checking for wonky Index Spools', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES ( - 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) -, selects -AS ( SELECT s.QueryHash - FROM #statements AS s - WHERE s.statement.exist('/p:StmtSimple/@StatementType[.="SELECT"]') = 1 ) -, spools -AS ( SELECT DISTINCT r.QueryHash, - c.n.value('@EstimateRows', 'FLOAT') AS estimated_rows, - c.n.value('@EstimateIO', 'FLOAT') AS estimated_io, - c.n.value('@EstimateCPU', 'FLOAT') AS estimated_cpu, - c.n.value('@EstimateRebinds', 'FLOAT') AS estimated_rebinds -FROM #relop AS r -JOIN selects AS s -ON s.QueryHash = r.QueryHash -CROSS APPLY r.relop.nodes('/p:RelOp') AS c(n) -WHERE r.relop.exist('/p:RelOp[@PhysicalOp="Index Spool" and @LogicalOp="Eager Spool"]') = 1 -) -UPDATE b - SET b.index_spool_rows = sp.estimated_rows, - b.index_spool_cost = ((sp.estimated_io * sp.estimated_cpu) * CASE WHEN sp.estimated_rebinds < 1 THEN 1 ELSE sp.estimated_rebinds END) -FROM ##BlitzCacheProcs b -JOIN spools sp -ON sp.QueryHash = b.QueryHash -OPTION (RECOMPILE); - -RAISERROR('Checking for wonky Table Spools', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES ( - 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) -, selects -AS ( SELECT s.QueryHash - FROM #statements AS s - WHERE s.statement.exist('/p:StmtSimple/@StatementType[.="SELECT"]') = 1 ) -, spools -AS ( SELECT DISTINCT r.QueryHash, - c.n.value('@EstimateRows', 'FLOAT') AS estimated_rows, - c.n.value('@EstimateIO', 'FLOAT') AS estimated_io, - c.n.value('@EstimateCPU', 'FLOAT') AS estimated_cpu, - c.n.value('@EstimateRebinds', 'FLOAT') AS estimated_rebinds -FROM #relop AS r -JOIN selects AS s -ON s.QueryHash = r.QueryHash -CROSS APPLY r.relop.nodes('/p:RelOp') AS c(n) -WHERE r.relop.exist('/p:RelOp[@PhysicalOp="Table Spool" and @LogicalOp="Lazy Spool"]') = 1 -) -UPDATE b - SET b.table_spool_rows = (sp.estimated_rows * sp.estimated_rebinds), - b.table_spool_cost = ((sp.estimated_io * sp.estimated_cpu * sp.estimated_rows) * CASE WHEN sp.estimated_rebinds < 1 THEN 1 ELSE sp.estimated_rebinds END) -FROM ##BlitzCacheProcs b -JOIN spools sp -ON sp.QueryHash = b.QueryHash -OPTION (RECOMPILE); - - -RAISERROR('Checking for selects that cause non-spill and index spool writes', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES ( - 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) -, selects -AS ( SELECT CONVERT(BINARY(8), - RIGHT('0000000000000000' - + SUBSTRING(s.statement.value('(/p:StmtSimple/@QueryHash)[1]', 'VARCHAR(18)'), - 3, 18), 16), 2) AS QueryHash - FROM #statements AS s - JOIN ##BlitzCacheProcs b - ON s.QueryHash = b.QueryHash - WHERE b.index_spool_rows IS NULL - AND b.index_spool_cost IS NULL - AND b.table_spool_cost IS NULL - AND b.table_spool_rows IS NULL - AND b.is_big_spills IS NULL - AND b.AverageWrites > 1024. - AND s.statement.exist('/p:StmtSimple/@StatementType[.="SELECT"]') = 1 -) -UPDATE b - SET b.select_with_writes = 1 -FROM ##BlitzCacheProcs b -JOIN selects AS s -ON s.QueryHash = b.QueryHash -AND b.AverageWrites > 1024.; - -/* 2012+ only */ -IF @v >= 11 -BEGIN - - RAISERROR(N'Checking for forced serialization', 0, 1) WITH NOWAIT; - WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) - UPDATE ##BlitzCacheProcs - SET is_forced_serial = 1 - FROM #query_plan qp - WHERE qp.SqlHandle = ##BlitzCacheProcs.SqlHandle - AND SPID = @@SPID - AND query_plan.exist('/p:QueryPlan/@NonParallelPlanReason') = 1 - AND (##BlitzCacheProcs.is_parallel = 0 OR ##BlitzCacheProcs.is_parallel IS NULL) - OPTION (RECOMPILE); - - IF @ExpertMode > 0 - BEGIN - RAISERROR(N'Checking for ColumnStore queries operating in Row Mode instead of Batch Mode', 0, 1) WITH NOWAIT; - WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) - UPDATE ##BlitzCacheProcs - SET columnstore_row_mode = x.is_row_mode - FROM ( - SELECT - qs.SqlHandle, - relop.exist('/p:RelOp[(@EstimatedExecutionMode[.="Row"])]') AS is_row_mode - FROM #relop qs - WHERE [relop].exist('/p:RelOp/p:IndexScan[(@Storage[.="ColumnStore"])]') = 1 - ) AS x - WHERE ##BlitzCacheProcs.SqlHandle = x.SqlHandle - AND SPID = @@SPID - OPTION (RECOMPILE); - END; - -END; - -/* 2014+ only */ -IF @v >= 12 -BEGIN - RAISERROR('Checking for downlevel cardinality estimators being used on SQL Server 2014.', 0, 1) WITH NOWAIT; - - WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) - UPDATE p - SET downlevel_estimator = CASE WHEN statement.value('min(//p:StmtSimple/@CardinalityEstimationModelVersion)', 'int') < (@v * 10) THEN 1 END - FROM ##BlitzCacheProcs p - JOIN #statements s ON p.QueryHash = s.QueryHash - WHERE SPID = @@SPID - OPTION (RECOMPILE); -END ; - -/* 2016+ only */ -IF @v >= 13 AND @ExpertMode > 0 -BEGIN - RAISERROR('Checking for row level security in 2016 only', 0, 1) WITH NOWAIT; - - WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) - UPDATE p - SET p.is_row_level = 1 - FROM ##BlitzCacheProcs p - JOIN #statements s ON p.QueryHash = s.QueryHash - WHERE SPID = @@SPID - AND statement.exist('/p:StmtSimple/@SecurityPolicyApplied[.="true"]') = 1 - OPTION (RECOMPILE); -END ; - -/* 2017+ only */ -IF @v >= 14 OR (@v = 13 AND @build >= 5026) -BEGIN - -IF @ExpertMode > 0 -BEGIN -RAISERROR('Gathering stats information', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -INSERT INTO #stats_agg -SELECT qp.SqlHandle, - x.c.value('@LastUpdate', 'DATETIME2(7)') AS LastUpdate, - x.c.value('@ModificationCount', 'BIGINT') AS ModificationCount, - x.c.value('@SamplingPercent', 'FLOAT') AS SamplingPercent, - x.c.value('@Statistics', 'NVARCHAR(258)') AS [Statistics], - x.c.value('@Table', 'NVARCHAR(258)') AS [Table], - x.c.value('@Schema', 'NVARCHAR(258)') AS [Schema], - x.c.value('@Database', 'NVARCHAR(258)') AS [Database] -FROM #query_plan AS qp -CROSS APPLY qp.query_plan.nodes('//p:OptimizerStatsUsage/p:StatisticsInfo') x (c) -OPTION (RECOMPILE); - - -RAISERROR('Checking for stale stats', 0, 1) WITH NOWAIT; -WITH stale_stats AS ( - SELECT sa.SqlHandle - FROM #stats_agg AS sa - GROUP BY sa.SqlHandle - HAVING MAX(sa.LastUpdate) <= DATEADD(DAY, -7, SYSDATETIME()) - AND AVG(sa.ModificationCount) >= 100000 -) -UPDATE b -SET stale_stats = 1 -FROM ##BlitzCacheProcs b -JOIN stale_stats os -ON b.SqlHandle = os.SqlHandle -AND b.SPID = @@SPID -OPTION (RECOMPILE); -END; - -IF @v >= 14 AND @ExpertMode > 0 -BEGIN -RAISERROR('Checking for adaptive joins', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p), -aj AS ( - SELECT - SqlHandle - FROM #relop AS r - CROSS APPLY r.relop.nodes('//p:RelOp') x(c) - WHERE x.c.exist('@IsAdaptive[.=1]') = 1 -) -UPDATE b -SET b.is_adaptive = 1 -FROM ##BlitzCacheProcs b -JOIN aj -ON b.SqlHandle = aj.SqlHandle -AND b.SPID = @@SPID -OPTION (RECOMPILE); -END; - -IF ((@v >= 14 - OR (@v = 13 AND @build >= 5026) - OR (@v = 12 AND @build >= 6024)) - AND @ExpertMode > 0) - -BEGIN; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p), -row_goals AS( -SELECT qs.QueryHash -FROM #relop qs -WHERE relop.value('sum(/p:RelOp/@EstimateRowsWithoutRowGoal)', 'float') > 0 -) -UPDATE b -SET b.is_row_goal = 1 -FROM ##BlitzCacheProcs b -JOIN row_goals -ON b.QueryHash = row_goals.QueryHash -AND b.SPID = @@SPID -OPTION (RECOMPILE); -END ; - -END; - - -/* END Testing using XML nodes to speed up processing */ - - -/* Update to grab stored procedure name for individual statements */ -RAISERROR(N'Attempting to get stored procedure name for individual statements', 0, 1) WITH NOWAIT; -UPDATE p -SET QueryType = QueryType + ' (parent ' + - + QUOTENAME(OBJECT_SCHEMA_NAME(s.object_id, s.database_id)) - + '.' - + QUOTENAME(OBJECT_NAME(s.object_id, s.database_id)) + ')' -FROM ##BlitzCacheProcs p - JOIN sys.dm_exec_procedure_stats s ON p.SqlHandle = s.sql_handle -WHERE QueryType = 'Statement' -AND SPID = @@SPID -OPTION (RECOMPILE); - -/* Update to grab stored procedure name for individual statements when PSPO is detected */ -UPDATE p -SET QueryType = QueryType + ' (parent ' + - + QUOTENAME(OBJECT_SCHEMA_NAME(s.object_id, s.database_id)) - + '.' - + QUOTENAME(OBJECT_NAME(s.object_id, s.database_id)) + ')' -FROM ##BlitzCacheProcs p - OUTER APPLY ( - SELECT REPLACE(REPLACE(REPLACE(REPLACE(p.QueryText, ' (', '('), '( ', '('), ' =', '='), '= ', '=') AS NormalizedQueryText - ) a - OUTER APPLY ( - SELECT CHARINDEX('option(PLAN PER VALUE(ObjectID=', a.NormalizedQueryText) AS OptionStart - ) b - OUTER APPLY ( - SELECT SUBSTRING(a.NormalizedQueryText, b.OptionStart + 31, LEN(a.NormalizedQueryText) - b.OptionStart - 30) AS OptionSubstring - WHERE b.OptionStart > 0 - ) c - OUTER APPLY ( - SELECT PATINDEX('%[^0-9]%', c.OptionSubstring) AS ObjectLength - ) d - OUTER APPLY ( - SELECT TRY_CAST(SUBSTRING(OptionSubstring, 1, d.ObjectLength - 1) AS INT) AS ObjectId - ) e - JOIN sys.dm_exec_procedure_stats s ON DB_ID(p.DatabaseName) = s.database_id AND e.ObjectId = s.object_id -WHERE p.QueryType = 'Statement' -AND p.SPID = @@SPID -AND s.object_id IS NOT NULL -OPTION (RECOMPILE); - -RAISERROR(N'Attempting to get function name for individual statements', 0, 1) WITH NOWAIT; -DECLARE @function_update_sql NVARCHAR(MAX) = N'' -IF EXISTS (SELECT 1/0 FROM sys.all_objects AS o WHERE o.name = 'dm_exec_function_stats') - BEGIN - SET @function_update_sql = @function_update_sql + N' - UPDATE p - SET QueryType = QueryType + '' (parent '' + - + QUOTENAME(OBJECT_SCHEMA_NAME(s.object_id, s.database_id)) - + ''.'' - + QUOTENAME(OBJECT_NAME(s.object_id, s.database_id)) + '')'' - FROM ##BlitzCacheProcs p - JOIN sys.dm_exec_function_stats s ON p.SqlHandle = s.sql_handle - WHERE QueryType = ''Statement'' - AND SPID = @@SPID - OPTION (RECOMPILE); - ' - EXEC sys.sp_executesql @function_update_sql - END - - -/* Trace Flag Checks 2012 SP3, 2014 SP2 and 2016 SP1 only)*/ -IF @v >= 11 -BEGIN - -RAISERROR(N'Trace flag checks', 0, 1) WITH NOWAIT; -;WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -, tf_pretty AS ( -SELECT qp.QueryHash, - qp.SqlHandle, - q.n.value('@Value', 'INT') AS trace_flag, - q.n.value('@Scope', 'VARCHAR(10)') AS scope -FROM #query_plan qp -CROSS APPLY qp.query_plan.nodes('/p:QueryPlan/p:TraceFlags/p:TraceFlag') AS q(n) -) -INSERT INTO #trace_flags -SELECT DISTINCT tf1.SqlHandle , tf1.QueryHash, - STUFF(( - SELECT DISTINCT ', ' + CONVERT(VARCHAR(5), tf2.trace_flag) - FROM tf_pretty AS tf2 - WHERE tf1.SqlHandle = tf2.SqlHandle - AND tf1.QueryHash = tf2.QueryHash - AND tf2.scope = 'Global' - FOR XML PATH(N'')), 1, 2, N'' - ) AS global_trace_flags, - STUFF(( - SELECT DISTINCT ', ' + CONVERT(VARCHAR(5), tf2.trace_flag) - FROM tf_pretty AS tf2 - WHERE tf1.SqlHandle = tf2.SqlHandle - AND tf1.QueryHash = tf2.QueryHash - AND tf2.scope = 'Session' - FOR XML PATH(N'')), 1, 2, N'' - ) AS session_trace_flags -FROM tf_pretty AS tf1 -OPTION (RECOMPILE); - -UPDATE p -SET p.trace_flags_session = tf.session_trace_flags -FROM ##BlitzCacheProcs p -JOIN #trace_flags tf ON tf.QueryHash = p.QueryHash -WHERE SPID = @@SPID -OPTION (RECOMPILE); - -END; - - -RAISERROR(N'Checking for MSTVFs', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET b.is_mstvf = 1 -FROM #relop AS r -JOIN ##BlitzCacheProcs AS b -ON b.SqlHandle = r.SqlHandle -WHERE r.relop.exist('/p:RelOp[(@EstimateRows="100" or @EstimateRows="1") and @LogicalOp="Table-valued function"]') = 1 -OPTION (RECOMPILE); - - -IF @ExpertMode > 0 -BEGIN -RAISERROR(N'Checking for many to many merge joins', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET b.is_mm_join = 1 -FROM #relop AS r -JOIN ##BlitzCacheProcs AS b -ON b.SqlHandle = r.SqlHandle -WHERE r.relop.exist('/p:RelOp/p:Merge/@ManyToMany[.="1"]') = 1 -OPTION (RECOMPILE); -END ; - - -IF @ExpertMode > 0 -BEGIN -RAISERROR(N'Is Paul White Electric?', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p), -is_paul_white_electric AS ( -SELECT 1 AS [is_paul_white_electric], -r.SqlHandle -FROM #relop AS r -CROSS APPLY r.relop.nodes('//p:RelOp') c(n) -WHERE c.n.exist('@PhysicalOp[.="Switch"]') = 1 -) -UPDATE b -SET b.is_paul_white_electric = ipwe.is_paul_white_electric -FROM ##BlitzCacheProcs AS b -JOIN is_paul_white_electric ipwe -ON ipwe.SqlHandle = b.SqlHandle -WHERE b.SPID = @@SPID -OPTION (RECOMPILE); -END ; - - -RAISERROR(N'Checking for non-sargable predicates', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES ( 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) -, nsarg - AS ( SELECT r.QueryHash, 1 AS fn, 0 AS jo, 0 AS lk - FROM #relop AS r - CROSS APPLY r.relop.nodes('/p:RelOp/p:IndexScan/p:Predicate/p:ScalarOperator/p:Compare/p:ScalarOperator') AS ca(x) - WHERE ( ca.x.exist('//p:ScalarOperator/p:Intrinsic/@FunctionName') = 1 - OR ca.x.exist('//p:ScalarOperator/p:IF') = 1 ) - UNION ALL - SELECT r.QueryHash, 0 AS fn, 1 AS jo, 0 AS lk - FROM #relop AS r - CROSS APPLY r.relop.nodes('/p:RelOp//p:ScalarOperator') AS ca(x) - WHERE r.relop.exist('/p:RelOp[contains(@LogicalOp, "Join")]') = 1 - AND ca.x.exist('//p:ScalarOperator[contains(@ScalarString, "Expr")]') = 1 - UNION ALL - SELECT r.QueryHash, 0 AS fn, 0 AS jo, 1 AS lk - FROM #relop AS r - CROSS APPLY r.relop.nodes('/p:RelOp/p:IndexScan/p:Predicate/p:ScalarOperator') AS ca(x) - CROSS APPLY ca.x.nodes('//p:Const') AS co(x) - WHERE ca.x.exist('//p:ScalarOperator/p:Intrinsic/@FunctionName[.="like"]') = 1 - AND ( ( co.x.value('substring(@ConstValue, 1, 1)', 'VARCHAR(100)') <> 'N' - AND co.x.value('substring(@ConstValue, 2, 1)', 'VARCHAR(100)') = '%' ) - OR ( co.x.value('substring(@ConstValue, 1, 1)', 'VARCHAR(100)') = 'N' - AND co.x.value('substring(@ConstValue, 3, 1)', 'VARCHAR(100)') = '%' ))), - d_nsarg - AS ( SELECT DISTINCT - nsarg.QueryHash - FROM nsarg - WHERE nsarg.fn = 1 - OR nsarg.jo = 1 - OR nsarg.lk = 1 ) -UPDATE b -SET b.is_nonsargable = 1 -FROM d_nsarg AS d -JOIN ##BlitzCacheProcs AS b - ON b.QueryHash = d.QueryHash -WHERE b.SPID = @@SPID -OPTION ( RECOMPILE ); - -/*Begin implicit conversion and parameter info */ - -RAISERROR(N'Getting information about implicit conversions and stored proc parameters', 0, 1) WITH NOWAIT; - -RAISERROR(N'Getting variable info', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES ( 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) -INSERT #variable_info ( SPID, QueryHash, SqlHandle, proc_name, variable_name, variable_datatype, compile_time_value ) -SELECT DISTINCT @@SPID, - qp.QueryHash, - qp.SqlHandle, - b.QueryType AS proc_name, - q.n.value('@Column', 'NVARCHAR(258)') AS variable_name, - q.n.value('@ParameterDataType', 'NVARCHAR(258)') AS variable_datatype, - q.n.value('@ParameterCompiledValue', 'NVARCHAR(258)') AS compile_time_value -FROM #query_plan AS qp -JOIN ##BlitzCacheProcs AS b -ON (b.QueryType = 'adhoc' AND b.QueryHash = qp.QueryHash) -OR (b.QueryType <> 'adhoc' AND b.SqlHandle = qp.SqlHandle) -CROSS APPLY qp.query_plan.nodes('//p:QueryPlan/p:ParameterList/p:ColumnReference') AS q(n) -WHERE b.SPID = @@SPID -OPTION (RECOMPILE); - - -RAISERROR(N'Getting conversion info', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES ( 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) -INSERT #conversion_info ( SPID, QueryHash, SqlHandle, proc_name, expression ) -SELECT DISTINCT @@SPID, - qp.QueryHash, - qp.SqlHandle, - b.QueryType AS proc_name, - qq.c.value('@Expression', 'NVARCHAR(4000)') AS expression -FROM #query_plan AS qp -JOIN ##BlitzCacheProcs AS b -ON (b.QueryType = 'adhoc' AND b.QueryHash = qp.QueryHash) -OR (b.QueryType <> 'adhoc' AND b.SqlHandle = qp.SqlHandle) -CROSS APPLY qp.query_plan.nodes('//p:QueryPlan/p:Warnings/p:PlanAffectingConvert') AS qq(c) -WHERE qq.c.exist('@ConvertIssue[.="Seek Plan"]') = 1 - AND qp.QueryHash IS NOT NULL - AND b.implicit_conversions = 1 -AND b.SPID = @@SPID -OPTION (RECOMPILE); - - -RAISERROR(N'Parsing conversion info', 0, 1) WITH NOWAIT; -INSERT #stored_proc_info ( SPID, SqlHandle, QueryHash, proc_name, variable_name, variable_datatype, converted_column_name, column_name, converted_to, compile_time_value ) -SELECT @@SPID AS SPID, - ci.SqlHandle, - ci.QueryHash, - REPLACE(REPLACE(REPLACE(ci.proc_name, ')', ''), 'Statement (parent ', ''), 'Procedure or Function: ', '') AS proc_name, - CASE WHEN ci.at_charindex > 0 - AND ci.bracket_charindex > 0 - THEN SUBSTRING(ci.expression, ci.at_charindex, ci.bracket_charindex) - ELSE N'**no_variable**' - END AS variable_name, - N'**no_variable**' AS variable_datatype, - CASE WHEN ci.at_charindex = 0 - AND ci.comma_charindex > 0 - AND ci.second_comma_charindex > 0 - THEN SUBSTRING(ci.expression, ci.comma_charindex, ci.second_comma_charindex) - ELSE N'**no_column**' - END AS converted_column_name, - CASE WHEN ci.at_charindex = 0 - AND ci.equal_charindex > 0 - AND ci.convert_implicit_charindex = 0 - THEN SUBSTRING(ci.expression, ci.equal_charindex, 4000) - WHEN ci.at_charindex = 0 - AND (ci.equal_charindex -1) > 0 - AND ci.convert_implicit_charindex > 0 - THEN SUBSTRING(ci.expression, 0, ci.equal_charindex -1) - WHEN ci.at_charindex > 0 - AND ci.comma_charindex > 0 - AND ci.second_comma_charindex > 0 - THEN SUBSTRING(ci.expression, ci.comma_charindex, ci.second_comma_charindex) - ELSE N'**no_column **' - END AS column_name, - CASE WHEN ci.paren_charindex > 0 - 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 - 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 -FROM #conversion_info AS ci -OPTION (RECOMPILE); - - -RAISERROR(N'Updating variables for inserted procs', 0, 1) WITH NOWAIT; -UPDATE sp -SET sp.variable_datatype = vi.variable_datatype, - sp.compile_time_value = vi.compile_time_value -FROM #stored_proc_info AS sp -JOIN #variable_info AS vi -ON (sp.proc_name = 'adhoc' AND sp.QueryHash = vi.QueryHash) -OR (sp.proc_name <> 'adhoc' AND sp.SqlHandle = vi.SqlHandle) -AND sp.variable_name = vi.variable_name -OPTION (RECOMPILE); - - -RAISERROR(N'Inserting variables for other procs', 0, 1) WITH NOWAIT; -INSERT #stored_proc_info - ( SPID, SqlHandle, QueryHash, variable_name, variable_datatype, compile_time_value, proc_name ) -SELECT vi.SPID, vi.SqlHandle, vi.QueryHash, vi.variable_name, vi.variable_datatype, vi.compile_time_value, REPLACE(REPLACE(REPLACE(vi.proc_name, ')', ''), 'Statement (parent ', ''), 'Procedure or Function: ', '') AS proc_name -FROM #variable_info AS vi -WHERE NOT EXISTS -( - SELECT * - FROM #stored_proc_info AS sp - WHERE (sp.proc_name = 'adhoc' AND sp.QueryHash = vi.QueryHash) - OR (sp.proc_name <> 'adhoc' AND sp.SqlHandle = vi.SqlHandle) -) -OPTION (RECOMPILE); - - -RAISERROR(N'Updating procs', 0, 1) WITH NOWAIT; -UPDATE s -SET s.variable_datatype = CASE WHEN s.variable_datatype LIKE '%(%)%' - THEN LEFT(s.variable_datatype, CHARINDEX('(', s.variable_datatype) - 1) - ELSE s.variable_datatype - END, - s.converted_to = CASE WHEN s.converted_to LIKE '%(%)%' - THEN LEFT(s.converted_to, CHARINDEX('(', s.converted_to) - 1) - ELSE s.converted_to - END, - s.compile_time_value = CASE WHEN s.compile_time_value LIKE '%(%)%' - THEN SUBSTRING(s.compile_time_value, - CHARINDEX('(', s.compile_time_value) + 1, - CHARINDEX(')', s.compile_time_value, CHARINDEX('(', s.compile_time_value) + 1) - 1 - CHARINDEX('(', s.compile_time_value) - ) - WHEN variable_datatype NOT IN ('bit', 'tinyint', 'smallint', 'int', 'bigint') - AND s.variable_datatype NOT LIKE '%binary%' - AND s.compile_time_value NOT LIKE 'N''%''' - AND s.compile_time_value NOT LIKE '''%''' - AND s.compile_time_value <> s.column_name - AND s.compile_time_value <> '**idk_man**' - THEN QUOTENAME(compile_time_value, '''') - ELSE s.compile_time_value - END -FROM #stored_proc_info AS s -OPTION (RECOMPILE); - - -RAISERROR(N'Updating SET options', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE s -SET set_options = set_options.ansi_set_options -FROM #stored_proc_info AS s -JOIN ( - SELECT x.SqlHandle, - N'SET ANSI_NULLS ' + CASE WHEN [ANSI_NULLS] = 'true' THEN N'ON ' ELSE N'OFF ' END + NCHAR(10) + - N'SET ANSI_PADDING ' + CASE WHEN [ANSI_PADDING] = 'true' THEN N'ON ' ELSE N'OFF ' END + NCHAR(10) + - N'SET ANSI_WARNINGS ' + CASE WHEN [ANSI_WARNINGS] = 'true' THEN N'ON ' ELSE N'OFF ' END + NCHAR(10) + - N'SET ARITHABORT ' + CASE WHEN [ARITHABORT] = 'true' THEN N'ON ' ELSE N' OFF ' END + NCHAR(10) + - N'SET CONCAT_NULL_YIELDS_NULL ' + CASE WHEN [CONCAT_NULL_YIELDS_NULL] = 'true' THEN N'ON ' ELSE N'OFF ' END + NCHAR(10) + - N'SET NUMERIC_ROUNDABORT ' + CASE WHEN [NUMERIC_ROUNDABORT] = 'true' THEN N'ON ' ELSE N'OFF ' END + NCHAR(10) + - N'SET QUOTED_IDENTIFIER ' + CASE WHEN [QUOTED_IDENTIFIER] = 'true' THEN N'ON ' ELSE N'OFF ' + NCHAR(10) END AS [ansi_set_options] - FROM ( - SELECT - s.SqlHandle, - so.o.value('@ANSI_NULLS', 'NVARCHAR(20)') AS [ANSI_NULLS], - so.o.value('@ANSI_PADDING', 'NVARCHAR(20)') AS [ANSI_PADDING], - so.o.value('@ANSI_WARNINGS', 'NVARCHAR(20)') AS [ANSI_WARNINGS], - so.o.value('@ARITHABORT', 'NVARCHAR(20)') AS [ARITHABORT], - so.o.value('@CONCAT_NULL_YIELDS_NULL', 'NVARCHAR(20)') AS [CONCAT_NULL_YIELDS_NULL], - so.o.value('@NUMERIC_ROUNDABORT', 'NVARCHAR(20)') AS [NUMERIC_ROUNDABORT], - so.o.value('@QUOTED_IDENTIFIER', 'NVARCHAR(20)') AS [QUOTED_IDENTIFIER] - FROM #statements AS s - CROSS APPLY s.statement.nodes('//p:StatementSetOptions') AS so(o) - ) AS x -) AS set_options ON set_options.SqlHandle = s.SqlHandle -OPTION(RECOMPILE); - - -RAISERROR(N'Updating conversion XML', 0, 1) WITH NOWAIT; -WITH precheck AS ( -SELECT spi.SPID, - spi.SqlHandle, - spi.proc_name, - (SELECT - CASE WHEN spi.proc_name <> 'Statement' - THEN N'The stored procedure ' + spi.proc_name - ELSE N'This ad hoc statement' - END - + N' had the following implicit conversions: ' - + CHAR(10) - + STUFF(( - SELECT DISTINCT - @nl - + CASE WHEN spi2.variable_name <> N'**no_variable**' - THEN N'The variable ' - WHEN spi2.variable_name = N'**no_variable**' AND (spi2.column_name = spi2.converted_column_name OR spi2.column_name LIKE '%CONVERT_IMPLICIT%') - THEN N'The compiled value ' - WHEN spi2.column_name LIKE '%Expr%' - THEN 'The expression ' - ELSE N'The column ' - END - + CASE WHEN spi2.variable_name <> N'**no_variable**' - THEN spi2.variable_name - WHEN spi2.variable_name = N'**no_variable**' AND (spi2.column_name = spi2.converted_column_name OR spi2.column_name LIKE '%CONVERT_IMPLICIT%') - THEN spi2.compile_time_value - ELSE spi2.column_name - END - + N' has a data type of ' - + CASE WHEN spi2.variable_datatype = N'**no_variable**' THEN spi2.converted_to - ELSE spi2.variable_datatype - END - + N' which caused implicit conversion on the column ' - + CASE WHEN spi2.column_name LIKE N'%CONVERT_IMPLICIT%' - THEN spi2.converted_column_name - WHEN spi2.column_name = N'**no_column**' - THEN spi2.converted_column_name - WHEN spi2.converted_column_name = N'**no_column**' - THEN spi2.column_name - WHEN spi2.column_name <> spi2.converted_column_name - THEN spi2.converted_column_name - ELSE spi2.column_name - END - + CASE WHEN spi2.variable_name = N'**no_variable**' AND (spi2.column_name = spi2.converted_column_name OR spi2.column_name LIKE '%CONVERT_IMPLICIT%') - THEN N'' - WHEN spi2.column_name LIKE '%Expr%' - THEN N'' - WHEN spi2.compile_time_value NOT IN ('**declared in proc**', '**idk_man**') - AND spi2.compile_time_value <> spi2.column_name - THEN ' with the value ' + RTRIM(spi2.compile_time_value) - ELSE N'' - END - + '.' - FROM #stored_proc_info AS spi2 - WHERE spi.SqlHandle = spi2.SqlHandle - FOR XML PATH(N''), TYPE).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 1, N'') - AS [processing-instruction(ClickMe)] FOR XML PATH(''), TYPE ) - AS implicit_conversion_info -FROM #stored_proc_info AS spi -GROUP BY spi.SPID, spi.SqlHandle, spi.proc_name -) -UPDATE b -SET b.implicit_conversion_info = pk.implicit_conversion_info -FROM ##BlitzCacheProcs AS b -JOIN precheck pk -ON pk.SqlHandle = b.SqlHandle -AND pk.SPID = b.SPID -OPTION (RECOMPILE); - - -RAISERROR(N'Updating cached parameter XML for stored procs', 0, 1) WITH NOWAIT; -WITH precheck AS ( -SELECT spi.SPID, - spi.SqlHandle, - spi.proc_name, - (SELECT - set_options - + @nl - + @nl - + N'EXEC ' - + spi.proc_name - + N' ' - + STUFF(( - SELECT DISTINCT N', ' - + CASE WHEN spi2.variable_name <> N'**no_variable**' AND spi2.compile_time_value <> N'**idk_man**' - THEN spi2.variable_name + N' = ' - ELSE @nl + N' We could not find any cached parameter values for this stored proc. ' - END - + CASE WHEN spi2.variable_name = N'**no_variable**' OR spi2.compile_time_value = N'**idk_man**' - THEN @nl + N'More info on possible reasons: https://www.brentozar.com/go/noplans ' - WHEN spi2.compile_time_value = N'NULL' - THEN spi2.compile_time_value - ELSE RTRIM(spi2.compile_time_value) - END - FROM #stored_proc_info AS spi2 - WHERE spi.SqlHandle = spi2.SqlHandle - AND spi2.proc_name <> N'Statement' - FOR XML PATH(N''), TYPE).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 1, N'') - AS [processing-instruction(ClickMe)] FOR XML PATH(''), TYPE ) - AS cached_execution_parameters -FROM #stored_proc_info AS spi -GROUP BY spi.SPID, spi.SqlHandle, spi.proc_name, spi.set_options -) -UPDATE b -SET b.cached_execution_parameters = pk.cached_execution_parameters -FROM ##BlitzCacheProcs AS b -JOIN precheck pk -ON pk.SqlHandle = b.SqlHandle -AND pk.SPID = b.SPID -WHERE b.QueryType <> N'Statement' -OPTION (RECOMPILE); - - -RAISERROR(N'Updating cached parameter XML for statements', 0, 1) WITH NOWAIT; -WITH precheck AS ( -SELECT spi.SPID, - spi.SqlHandle, - spi.proc_name, - (SELECT - set_options - + @nl - + @nl - + N' See QueryText column for full query text' - + @nl - + @nl - + STUFF(( - SELECT DISTINCT N', ' - + CASE WHEN spi2.variable_name <> N'**no_variable**' AND spi2.compile_time_value <> N'**idk_man**' - THEN spi2.variable_name + N' = ' - ELSE @nl + N' We could not find any cached parameter values for this stored proc. ' - END - + CASE WHEN spi2.variable_name = N'**no_variable**' OR spi2.compile_time_value = N'**idk_man**' - THEN @nl + N' More info on possible reasons: https://www.brentozar.com/go/noplans ' - WHEN spi2.compile_time_value = N'NULL' - THEN spi2.compile_time_value - ELSE RTRIM(spi2.compile_time_value) - END - FROM #stored_proc_info AS spi2 - WHERE spi.SqlHandle = spi2.SqlHandle - AND spi2.proc_name = N'Statement' - AND spi2.variable_name NOT LIKE N'%msparam%' - FOR XML PATH(N''), TYPE).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 1, N'') - AS [processing-instruction(ClickMe)] FOR XML PATH(''), TYPE ) - AS cached_execution_parameters -FROM #stored_proc_info AS spi -GROUP BY spi.SPID, spi.SqlHandle, spi.proc_name, spi.set_options -) -UPDATE b -SET b.cached_execution_parameters = pk.cached_execution_parameters -FROM ##BlitzCacheProcs AS b -JOIN precheck pk -ON pk.SqlHandle = b.SqlHandle -AND pk.SPID = b.SPID -WHERE b.QueryType = N'Statement' -OPTION (RECOMPILE); - -RAISERROR(N'Filling in implicit conversion and cached plan parameter info', 0, 1) WITH NOWAIT; -UPDATE b -SET b.implicit_conversion_info = CASE WHEN b.implicit_conversion_info IS NULL - OR CONVERT(NVARCHAR(MAX), b.implicit_conversion_info) = N'' - THEN '' - ELSE b.implicit_conversion_info END, - b.cached_execution_parameters = CASE WHEN b.cached_execution_parameters IS NULL - OR CONVERT(NVARCHAR(MAX), b.cached_execution_parameters) = N'' - THEN '' - ELSE b.cached_execution_parameters END -FROM ##BlitzCacheProcs AS b -WHERE b.SPID = @@SPID -OPTION (RECOMPILE); - -/*End implicit conversion and parameter info*/ - -/*Begin Missing Index*/ -IF EXISTS ( SELECT 1/0 - FROM ##BlitzCacheProcs AS bbcp - WHERE bbcp.missing_index_count > 0 - OR bbcp.index_spool_cost > 0 - OR bbcp.index_spool_rows > 0 - AND bbcp.SPID = @@SPID ) - - BEGIN - RAISERROR(N'Inserting to #missing_index_xml', 0, 1) WITH NOWAIT; - WITH XMLNAMESPACES ( 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) - INSERT #missing_index_xml - SELECT qp.QueryHash, - qp.SqlHandle, - c.mg.value('@Impact', 'FLOAT') AS Impact, - c.mg.query('.') AS cmg - FROM #query_plan AS qp - CROSS APPLY qp.query_plan.nodes('//p:MissingIndexes/p:MissingIndexGroup') AS c(mg) - WHERE qp.QueryHash IS NOT NULL - OPTION(RECOMPILE); - - RAISERROR(N'Inserting to #missing_index_schema', 0, 1) WITH NOWAIT; - WITH XMLNAMESPACES ( 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) - INSERT #missing_index_schema - SELECT mix.QueryHash, mix.SqlHandle, mix.impact, - c.mi.value('@Database', 'NVARCHAR(128)'), - c.mi.value('@Schema', 'NVARCHAR(128)'), - c.mi.value('@Table', 'NVARCHAR(128)'), - c.mi.query('.') - FROM #missing_index_xml AS mix - CROSS APPLY mix.index_xml.nodes('//p:MissingIndex') AS c(mi) - OPTION(RECOMPILE); - - RAISERROR(N'Inserting to #missing_index_usage', 0, 1) WITH NOWAIT; - WITH XMLNAMESPACES ( 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) - INSERT #missing_index_usage - SELECT ms.QueryHash, ms.SqlHandle, ms.impact, ms.database_name, ms.schema_name, ms.table_name, - c.cg.value('@Usage', 'NVARCHAR(128)'), - c.cg.query('.') - FROM #missing_index_schema ms - CROSS APPLY ms.index_xml.nodes('//p:ColumnGroup') AS c(cg) - OPTION(RECOMPILE); - - RAISERROR(N'Inserting to #missing_index_detail', 0, 1) WITH NOWAIT; - WITH XMLNAMESPACES ( 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) - INSERT #missing_index_detail - SELECT miu.QueryHash, - miu.SqlHandle, - miu.impact, - miu.database_name, - miu.schema_name, - miu.table_name, - miu.usage, - c.c.value('@Name', 'NVARCHAR(128)') - FROM #missing_index_usage AS miu - CROSS APPLY miu.index_xml.nodes('//p:Column') AS c(c) - OPTION (RECOMPILE); - - RAISERROR(N'Inserting to missing indexes to #missing_index_pretty', 0, 1) WITH NOWAIT; - INSERT #missing_index_pretty - ( QueryHash, SqlHandle, impact, database_name, schema_name, table_name, equality, inequality, include, executions, query_cost, creation_hours, is_spool ) - SELECT DISTINCT m.QueryHash, m.SqlHandle, m.impact, m.database_name, m.schema_name, m.table_name - , STUFF(( SELECT DISTINCT N', ' + ISNULL(m2.column_name, '') AS column_name - FROM #missing_index_detail AS m2 - WHERE m2.usage = 'EQUALITY' - AND m.QueryHash = m2.QueryHash - AND m.SqlHandle = m2.SqlHandle - AND m.impact = m2.impact - AND m.database_name = m2.database_name - AND m.schema_name = m2.schema_name - AND m.table_name = m2.table_name - FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 2, N'') AS equality - , STUFF(( SELECT DISTINCT N', ' + ISNULL(m2.column_name, '') AS column_name - FROM #missing_index_detail AS m2 - WHERE m2.usage = 'INEQUALITY' - AND m.QueryHash = m2.QueryHash - AND m.SqlHandle = m2.SqlHandle - AND m.impact = m2.impact - AND m.database_name = m2.database_name - AND m.schema_name = m2.schema_name - AND m.table_name = m2.table_name - FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 2, N'') AS inequality - , STUFF(( SELECT DISTINCT N', ' + ISNULL(m2.column_name, '') AS column_name - FROM #missing_index_detail AS m2 - WHERE m2.usage = 'INCLUDE' - AND m.QueryHash = m2.QueryHash - AND m.SqlHandle = m2.SqlHandle - AND m.impact = m2.impact - AND m.database_name = m2.database_name - AND m.schema_name = m2.schema_name - AND m.table_name = m2.table_name - FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 2, N'') AS [include], - bbcp.ExecutionCount, - bbcp.QueryPlanCost, - bbcp.PlanCreationTimeHours, - 0 as is_spool - FROM #missing_index_detail AS m - JOIN ##BlitzCacheProcs AS bbcp - ON m.SqlHandle = bbcp.SqlHandle - AND m.QueryHash = bbcp.QueryHash - OPTION (RECOMPILE); - - RAISERROR(N'Inserting to #index_spool_ugly', 0, 1) WITH NOWAIT; - WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) - INSERT #index_spool_ugly - (QueryHash, SqlHandle, impact, database_name, schema_name, table_name, equality, inequality, include, executions, query_cost, creation_hours) - SELECT p.QueryHash, - p.SqlHandle, - (c.n.value('@EstimateIO', 'FLOAT') + (c.n.value('@EstimateCPU', 'FLOAT'))) - / ( 1 * NULLIF(p.QueryPlanCost, 0)) * 100 AS impact, - o.n.value('@Database', 'NVARCHAR(128)') AS output_database, - o.n.value('@Schema', 'NVARCHAR(128)') AS output_schema, - o.n.value('@Table', 'NVARCHAR(128)') AS output_table, - k.n.value('@Column', 'NVARCHAR(128)') AS range_column, - e.n.value('@Column', 'NVARCHAR(128)') AS expression_column, - o.n.value('@Column', 'NVARCHAR(128)') AS output_column, - p.ExecutionCount, - p.QueryPlanCost, - p.PlanCreationTimeHours - FROM #relop AS r - JOIN ##BlitzCacheProcs p - ON p.QueryHash = r.QueryHash - CROSS APPLY r.relop.nodes('/p:RelOp') AS c(n) - CROSS APPLY r.relop.nodes('/p:RelOp/p:OutputList/p:ColumnReference') AS o(n) - OUTER APPLY r.relop.nodes('/p:RelOp/p:Spool/p:SeekPredicateNew/p:SeekKeys/p:Prefix/p:RangeColumns/p:ColumnReference') AS k(n) - OUTER APPLY r.relop.nodes('/p:RelOp/p:Spool/p:SeekPredicateNew/p:SeekKeys/p:Prefix/p:RangeExpressions/p:ColumnReference') AS e(n) - WHERE r.relop.exist('/p:RelOp[@PhysicalOp="Index Spool" and @LogicalOp="Eager Spool"]') = 1 - - RAISERROR(N'Inserting to spools to #missing_index_pretty', 0, 1) WITH NOWAIT; - INSERT #missing_index_pretty - (QueryHash, SqlHandle, impact, database_name, schema_name, table_name, equality, inequality, include, executions, query_cost, creation_hours, is_spool) - SELECT DISTINCT - isu.QueryHash, - isu.SqlHandle, - isu.impact, - isu.database_name, - isu.schema_name, - isu.table_name - , STUFF(( SELECT DISTINCT N', ' + ISNULL(isu2.equality, '') AS column_name - FROM #index_spool_ugly AS isu2 - WHERE isu2.equality IS NOT NULL - AND isu.QueryHash = isu2.QueryHash - AND isu.SqlHandle = isu2.SqlHandle - AND isu.impact = isu2.impact - AND isu.database_name = isu2.database_name - AND isu.schema_name = isu2.schema_name - AND isu.table_name = isu2.table_name - FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 2, N'') AS equality - , STUFF(( SELECT DISTINCT N', ' + ISNULL(isu2.inequality, '') AS column_name - FROM #index_spool_ugly AS isu2 - WHERE isu2.inequality IS NOT NULL - AND isu.QueryHash = isu2.QueryHash - AND isu.SqlHandle = isu2.SqlHandle - AND isu.impact = isu2.impact - AND isu.database_name = isu2.database_name - AND isu.schema_name = isu2.schema_name - AND isu.table_name = isu2.table_name - FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 2, N'') AS inequality - , STUFF(( SELECT DISTINCT N', ' + ISNULL(isu2.include, '') AS column_name - FROM #index_spool_ugly AS isu2 - WHERE isu2.include IS NOT NULL - AND isu.QueryHash = isu2.QueryHash - AND isu.SqlHandle = isu2.SqlHandle - AND isu.impact = isu2.impact - AND isu.database_name = isu2.database_name - AND isu.schema_name = isu2.schema_name - AND isu.table_name = isu2.table_name - FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 2, N'') AS include, - isu.executions, - isu.query_cost, - isu.creation_hours, - 1 AS is_spool - FROM #index_spool_ugly AS isu - - - RAISERROR(N'Updating missing index information', 0, 1) WITH NOWAIT; - WITH missing AS ( - SELECT DISTINCT - mip.QueryHash, - mip.SqlHandle, - mip.executions, - N'' - AS full_details - FROM #missing_index_pretty AS mip - ) - UPDATE bbcp - SET bbcp.missing_indexes = m.full_details - FROM ##BlitzCacheProcs AS bbcp - JOIN missing AS m - ON m.SqlHandle = bbcp.SqlHandle - AND m.QueryHash = bbcp.QueryHash - AND m.executions = bbcp.ExecutionCount - AND SPID = @@SPID - OPTION (RECOMPILE); - - END; - - RAISERROR(N'Filling in missing index blanks', 0, 1) WITH NOWAIT; - UPDATE b - SET b.missing_indexes = - CASE WHEN b.missing_indexes IS NULL - THEN '' - ELSE b.missing_indexes - END - FROM ##BlitzCacheProcs AS b - WHERE b.SPID = @@SPID - OPTION (RECOMPILE); - -/*End Missing Index*/ - - -/* Set configuration values */ -RAISERROR(N'Setting configuration values', 0, 1) WITH NOWAIT; -DECLARE @execution_threshold INT = 1000 , - @parameter_sniffing_warning_pct TINYINT = 30, - /* This is in average reads */ - @parameter_sniffing_io_threshold BIGINT = 100000 , - @ctp_threshold_pct TINYINT = 10, - @long_running_query_warning_seconds BIGINT = 300 * 1000 , - @memory_grant_warning_percent INT = 10; - -IF EXISTS (SELECT 1/0 FROM #configuration WHERE 'frequent execution threshold' = LOWER(parameter_name)) -BEGIN - SELECT @execution_threshold = CAST(value AS INT) - FROM #configuration - WHERE 'frequent execution threshold' = LOWER(parameter_name) ; - - SET @msg = ' Setting "frequent execution threshold" to ' + CAST(@execution_threshold AS VARCHAR(10)) ; - - RAISERROR(@msg, 0, 1) WITH NOWAIT; -END; - -IF EXISTS (SELECT 1/0 FROM #configuration WHERE 'parameter sniffing variance percent' = LOWER(parameter_name)) -BEGIN - SELECT @parameter_sniffing_warning_pct = CAST(value AS TINYINT) - FROM #configuration - WHERE 'parameter sniffing variance percent' = LOWER(parameter_name) ; - - SET @msg = ' Setting "parameter sniffing variance percent" to ' + CAST(@parameter_sniffing_warning_pct AS VARCHAR(3)) ; - - RAISERROR(@msg, 0, 1) WITH NOWAIT; -END; - -IF EXISTS (SELECT 1/0 FROM #configuration WHERE 'parameter sniffing io threshold' = LOWER(parameter_name)) -BEGIN - SELECT @parameter_sniffing_io_threshold = CAST(value AS BIGINT) - FROM #configuration - WHERE 'parameter sniffing io threshold' = LOWER(parameter_name) ; - - SET @msg = ' Setting "parameter sniffing io threshold" to ' + CAST(@parameter_sniffing_io_threshold AS VARCHAR(10)); - - RAISERROR(@msg, 0, 1) WITH NOWAIT; -END; - -IF EXISTS (SELECT 1/0 FROM #configuration WHERE 'cost threshold for parallelism warning' = LOWER(parameter_name)) -BEGIN - SELECT @ctp_threshold_pct = CAST(value AS TINYINT) - FROM #configuration - WHERE 'cost threshold for parallelism warning' = LOWER(parameter_name) ; - - SET @msg = ' Setting "cost threshold for parallelism warning" to ' + CAST(@ctp_threshold_pct AS VARCHAR(3)); - - RAISERROR(@msg, 0, 1) WITH NOWAIT; -END; - -IF EXISTS (SELECT 1/0 FROM #configuration WHERE 'long running query warning (seconds)' = LOWER(parameter_name)) -BEGIN - SELECT @long_running_query_warning_seconds = CAST(value * 1000 AS BIGINT) - FROM #configuration - WHERE 'long running query warning (seconds)' = LOWER(parameter_name) ; - - SET @msg = ' Setting "long running query warning (seconds)" to ' + CAST(@long_running_query_warning_seconds AS VARCHAR(10)); - - RAISERROR(@msg, 0, 1) WITH NOWAIT; -END; - -IF EXISTS (SELECT 1/0 FROM #configuration WHERE 'unused memory grant' = LOWER(parameter_name)) -BEGIN - SELECT @memory_grant_warning_percent = CAST(value AS INT) - FROM #configuration - WHERE 'unused memory grant' = LOWER(parameter_name) ; - - SET @msg = ' Setting "unused memory grant" to ' + CAST(@memory_grant_warning_percent AS VARCHAR(10)); - - RAISERROR(@msg, 0, 1) WITH NOWAIT; -END; - -DECLARE @ctp INT ; - -SELECT @ctp = NULLIF(CAST(value AS INT), 0) -FROM sys.configurations -WHERE name = 'cost threshold for parallelism' -OPTION (RECOMPILE); - - -/* Update to populate checks columns */ -RAISERROR('Checking for query level SQL Server issues.', 0, 1) WITH NOWAIT; - -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE ##BlitzCacheProcs -SET frequent_execution = CASE WHEN ExecutionsPerMinute > @execution_threshold THEN 1 END , - parameter_sniffing = CASE WHEN ExecutionCount > 3 AND AverageReads > @parameter_sniffing_io_threshold - AND min_worker_time < ((1.0 - (@parameter_sniffing_warning_pct / 100.0)) * AverageCPU) THEN 1 - WHEN ExecutionCount > 3 AND AverageReads > @parameter_sniffing_io_threshold - AND max_worker_time > ((1.0 + (@parameter_sniffing_warning_pct / 100.0)) * AverageCPU) THEN 1 - WHEN ExecutionCount > 3 AND AverageReads > @parameter_sniffing_io_threshold - AND MinReturnedRows < ((1.0 - (@parameter_sniffing_warning_pct / 100.0)) * AverageReturnedRows) THEN 1 - WHEN ExecutionCount > 3 AND AverageReads > @parameter_sniffing_io_threshold - AND MaxReturnedRows > ((1.0 + (@parameter_sniffing_warning_pct / 100.0)) * AverageReturnedRows) THEN 1 END , - near_parallel = CASE WHEN is_parallel <> 1 AND QueryPlanCost BETWEEN @ctp * (1 - (@ctp_threshold_pct / 100.0)) AND @ctp THEN 1 END, - long_running = CASE WHEN AverageDuration > @long_running_query_warning_seconds THEN 1 - WHEN max_worker_time > @long_running_query_warning_seconds THEN 1 - WHEN max_elapsed_time > @long_running_query_warning_seconds THEN 1 END, - is_key_lookup_expensive = CASE WHEN QueryPlanCost >= (@ctp / 2) AND key_lookup_cost >= QueryPlanCost * .5 THEN 1 END, - is_sort_expensive = CASE WHEN QueryPlanCost >= (@ctp / 2) AND sort_cost >= QueryPlanCost * .5 THEN 1 END, - is_remote_query_expensive = CASE WHEN remote_query_cost >= QueryPlanCost * .05 THEN 1 END, - is_unused_grant = CASE WHEN PercentMemoryGrantUsed <= @memory_grant_warning_percent AND MinGrantKB > @MinMemoryPerQuery THEN 1 END, - long_running_low_cpu = CASE WHEN AverageDuration > AverageCPU * 4 AND AverageCPU < 500. THEN 1 END, - low_cost_high_cpu = CASE WHEN QueryPlanCost <= 10 AND AverageCPU > 5000. THEN 1 END, - is_spool_expensive = CASE WHEN QueryPlanCost > (@ctp / 5) AND index_spool_cost >= QueryPlanCost * .1 THEN 1 END, - is_spool_more_rows = CASE WHEN index_spool_rows >= (AverageReturnedRows / ISNULL(NULLIF(ExecutionCount, 0), 1)) THEN 1 END, - is_table_spool_expensive = CASE WHEN QueryPlanCost > (@ctp / 5) AND table_spool_cost >= QueryPlanCost / 4 THEN 1 END, - is_table_spool_more_rows = CASE WHEN table_spool_rows >= (AverageReturnedRows / ISNULL(NULLIF(ExecutionCount, 0), 1)) THEN 1 END, - is_bad_estimate = CASE WHEN AverageReturnedRows > 0 AND (estimated_rows * 1000 < AverageReturnedRows OR estimated_rows > AverageReturnedRows * 1000) THEN 1 END, - is_big_spills = CASE WHEN (AvgSpills / 128.) > 499. THEN 1 END -WHERE SPID = @@SPID -OPTION (RECOMPILE); - - - -RAISERROR('Checking for forced parameterization and cursors.', 0, 1) WITH NOWAIT; - -/* Set options checks */ -UPDATE p - SET is_forced_parameterized = CASE WHEN (CAST(pa.value AS INT) & 131072 = 131072) THEN 1 END , - is_forced_plan = CASE WHEN (CAST(pa.value AS INT) & 4 = 4) THEN 1 END , - SetOptions = SUBSTRING( - CASE WHEN (CAST(pa.value AS INT) & 1 = 1) THEN ', ANSI_PADDING' ELSE '' END + - CASE WHEN (CAST(pa.value AS INT) & 8 = 8) THEN ', CONCAT_NULL_YIELDS_NULL' ELSE '' END + - CASE WHEN (CAST(pa.value AS INT) & 16 = 16) THEN ', ANSI_WARNINGS' ELSE '' END + - CASE WHEN (CAST(pa.value AS INT) & 32 = 32) THEN ', ANSI_NULLS' ELSE '' END + - CASE WHEN (CAST(pa.value AS INT) & 64 = 64) THEN ', QUOTED_IDENTIFIER' ELSE '' END + - CASE WHEN (CAST(pa.value AS INT) & 4096 = 4096) THEN ', ARITH_ABORT' ELSE '' END + - CASE WHEN (CAST(pa.value AS INT) & 8192 = 8191) THEN ', NUMERIC_ROUNDABORT' ELSE '' END - , 2, 200000) -FROM ##BlitzCacheProcs p - CROSS APPLY sys.dm_exec_plan_attributes(p.PlanHandle) pa -WHERE pa.attribute = 'set_options' -AND SPID = @@SPID -OPTION (RECOMPILE); - - -/* Cursor checks */ -UPDATE p -SET is_cursor = CASE WHEN CAST(pa.value AS INT) <> 0 THEN 1 END -FROM ##BlitzCacheProcs p - CROSS APPLY sys.dm_exec_plan_attributes(p.PlanHandle) pa -WHERE pa.attribute LIKE '%cursor%' -AND SPID = @@SPID -OPTION (RECOMPILE); - -UPDATE p -SET is_cursor = 1 -FROM ##BlitzCacheProcs p -WHERE QueryHash = 0x0000000000000000 -OR QueryPlanHash = 0x0000000000000000 -AND SPID = @@SPID -OPTION (RECOMPILE); - - - -RAISERROR('Populating Warnings column', 0, 1) WITH NOWAIT; -/* Populate warnings */ -UPDATE ##BlitzCacheProcs -SET Warnings = SUBSTRING( - CASE WHEN warning_no_join_predicate = 1 THEN ', No Join Predicate' ELSE '' END + - CASE WHEN compile_timeout = 1 THEN ', Compilation Timeout' ELSE '' END + - CASE WHEN compile_memory_limit_exceeded = 1 THEN ', Compile Memory Limit Exceeded' ELSE '' END + - CASE WHEN busy_loops = 1 THEN ', Busy Loops' ELSE '' END + - CASE WHEN is_forced_plan = 1 THEN ', Forced Plan' ELSE '' END + - CASE WHEN is_forced_parameterized = 1 THEN ', Forced Parameterization' ELSE '' END + - CASE WHEN unparameterized_query = 1 THEN ', Unparameterized Query' ELSE '' END + - CASE WHEN missing_index_count > 0 THEN ', Missing Indexes (' + CAST(missing_index_count AS VARCHAR(3)) + ')' ELSE '' END + - CASE WHEN unmatched_index_count > 0 THEN ', Unmatched Indexes (' + CAST(unmatched_index_count AS VARCHAR(3)) + ')' ELSE '' END + - CASE WHEN is_cursor = 1 THEN ', Cursor' - + CASE WHEN is_optimistic_cursor = 1 THEN '; optimistic' ELSE '' END - + CASE WHEN is_forward_only_cursor = 0 THEN '; not forward only' ELSE '' END - + CASE WHEN is_cursor_dynamic = 1 THEN '; dynamic' ELSE '' END - + CASE WHEN is_fast_forward_cursor = 1 THEN '; fast forward' ELSE '' END - ELSE '' END + - CASE WHEN is_parallel = 1 THEN ', Parallel' ELSE '' END + - CASE WHEN near_parallel = 1 THEN ', Nearly Parallel' ELSE '' END + - CASE WHEN frequent_execution = 1 THEN ', Frequent Execution' ELSE '' END + - CASE WHEN plan_warnings = 1 THEN ', Plan Warnings' ELSE '' END + - CASE WHEN parameter_sniffing = 1 THEN ', Parameter Sniffing' ELSE '' END + - CASE WHEN long_running = 1 THEN ', Long Running Query' ELSE '' END + - CASE WHEN downlevel_estimator = 1 THEN ', Downlevel CE' ELSE '' END + - CASE WHEN implicit_conversions = 1 THEN ', Implicit Conversions' ELSE '' END + - CASE WHEN tvf_join = 1 THEN ', Function Join' ELSE '' END + - CASE WHEN plan_multiple_plans > 0 THEN ', Multiple Plans' + COALESCE(' (' + CAST(plan_multiple_plans AS VARCHAR(10)) + ')', '') ELSE '' END + - CASE WHEN is_trivial = 1 THEN ', Trivial Plans' ELSE '' END + - CASE WHEN is_forced_serial = 1 THEN ', Forced Serialization' ELSE '' END + - CASE WHEN is_key_lookup_expensive = 1 THEN ', Expensive Key Lookup' ELSE '' END + - CASE WHEN is_remote_query_expensive = 1 THEN ', Expensive Remote Query' ELSE '' END + - CASE WHEN trace_flags_session IS NOT NULL THEN ', Session Level Trace Flag(s) Enabled: ' + trace_flags_session ELSE '' END + - CASE WHEN is_unused_grant = 1 THEN ', Unused Memory Grant' ELSE '' END + - CASE WHEN function_count > 0 THEN ', Calls ' + CONVERT(VARCHAR(10), function_count) + ' Function(s)' ELSE '' END + - CASE WHEN clr_function_count > 0 THEN ', Calls ' + CONVERT(VARCHAR(10), clr_function_count) + ' CLR Function(s)' ELSE '' END + - CASE WHEN PlanCreationTimeHours <= 4 THEN ', Plan created last 4hrs' ELSE '' END + - CASE WHEN is_table_variable = 1 THEN ', Table Variables' ELSE '' END + - CASE WHEN no_stats_warning = 1 THEN ', Columns With No Statistics' ELSE '' END + - CASE WHEN relop_warnings = 1 THEN ', Operator Warnings' ELSE '' END + - CASE WHEN is_table_scan = 1 THEN ', Table Scans (Heaps)' ELSE '' END + - CASE WHEN backwards_scan = 1 THEN ', Backwards Scans' ELSE '' END + - CASE WHEN forced_index = 1 THEN ', Forced Indexes' ELSE '' END + - CASE WHEN forced_seek = 1 THEN ', Forced Seeks' ELSE '' END + - CASE WHEN forced_scan = 1 THEN ', Forced Scans' ELSE '' END + - CASE WHEN columnstore_row_mode = 1 THEN ', ColumnStore Row Mode ' ELSE '' END + - CASE WHEN is_computed_scalar = 1 THEN ', Computed Column UDF ' ELSE '' END + - CASE WHEN is_sort_expensive = 1 THEN ', Expensive Sort' ELSE '' END + - CASE WHEN is_computed_filter = 1 THEN ', Filter UDF' ELSE '' END + - CASE WHEN index_ops >= 5 THEN ', >= 5 Indexes Modified' ELSE '' END + - CASE WHEN is_row_level = 1 THEN ', Row Level Security' ELSE '' END + - CASE WHEN is_spatial = 1 THEN ', Spatial Index' ELSE '' END + - CASE WHEN index_dml = 1 THEN ', Index DML' ELSE '' END + - CASE WHEN table_dml = 1 THEN ', Table DML' ELSE '' END + - CASE WHEN low_cost_high_cpu = 1 THEN ', Low Cost High CPU' ELSE '' END + - CASE WHEN long_running_low_cpu = 1 THEN + ', Long Running With Low CPU' ELSE '' END + - CASE WHEN stale_stats = 1 THEN + ', Statistics used have > 100k modifications in the last 7 days' ELSE '' END + - CASE WHEN is_adaptive = 1 THEN + ', Adaptive Joins' ELSE '' END + - CASE WHEN is_spool_expensive = 1 THEN + ', Expensive Index Spool' ELSE '' END + - CASE WHEN is_spool_more_rows = 1 THEN + ', Large Index Row Spool' ELSE '' END + - CASE WHEN is_table_spool_expensive = 1 THEN + ', Expensive Table Spool' ELSE '' END + - CASE WHEN is_table_spool_more_rows = 1 THEN + ', Many Rows Table Spool' ELSE '' END + - CASE WHEN is_bad_estimate = 1 THEN + ', Row Estimate Mismatch' ELSE '' END + - CASE WHEN is_paul_white_electric = 1 THEN ', SWITCH!' ELSE '' END + - CASE WHEN is_row_goal = 1 THEN ', Row Goals' ELSE '' END + - CASE WHEN is_big_spills = 1 THEN ', >500mb Spills' ELSE '' END + - CASE WHEN is_mstvf = 1 THEN ', MSTVFs' ELSE '' END + - CASE WHEN is_mm_join = 1 THEN ', Many to Many Merge' ELSE '' END + - CASE WHEN is_nonsargable = 1 THEN ', non-SARGables' ELSE '' END + - CASE WHEN CompileTime > 5000 THEN ', Long Compile Time' ELSE '' END + - CASE WHEN CompileCPU > 5000 THEN ', High Compile CPU' ELSE '' END + - CASE WHEN CompileMemory > 1024 AND ((CompileMemory) / (1 * CASE WHEN MaxCompileMemory = 0 THEN 1 ELSE MaxCompileMemory END) * 100.) >= 10. THEN ', High Compile Memory' ELSE '' END + - CASE WHEN select_with_writes > 0 THEN ', Select w/ Writes' ELSE '' END - , 3, 200000) -WHERE SPID = @@SPID -OPTION (RECOMPILE); - - -RAISERROR('Populating Warnings column for stored procedures', 0, 1) WITH NOWAIT; -WITH statement_warnings AS - ( -SELECT DISTINCT - SqlHandle, - Warnings = SUBSTRING( - CASE WHEN warning_no_join_predicate = 1 THEN ', No Join Predicate' ELSE '' END + - CASE WHEN compile_timeout = 1 THEN ', Compilation Timeout' ELSE '' END + - CASE WHEN compile_memory_limit_exceeded = 1 THEN ', Compile Memory Limit Exceeded' ELSE '' END + - CASE WHEN busy_loops = 1 THEN ', Busy Loops' ELSE '' END + - CASE WHEN is_forced_plan = 1 THEN ', Forced Plan' ELSE '' END + - CASE WHEN is_forced_parameterized = 1 THEN ', Forced Parameterization' ELSE '' END + - --CASE WHEN unparameterized_query = 1 THEN ', Unparameterized Query' ELSE '' END + - CASE WHEN missing_index_count > 0 THEN ', Missing Indexes (' + CONVERT(VARCHAR(10), (SELECT SUM(b2.missing_index_count) FROM ##BlitzCacheProcs AS b2 WHERE b2.SqlHandle = b.SqlHandle AND b2.QueryHash IS NOT NULL AND SPID = @@SPID) ) + ')' ELSE '' END + - CASE WHEN unmatched_index_count > 0 THEN ', Unmatched Indexes (' + CONVERT(VARCHAR(10), (SELECT SUM(b2.unmatched_index_count) FROM ##BlitzCacheProcs AS b2 WHERE b2.SqlHandle = b.SqlHandle AND b2.QueryHash IS NOT NULL AND SPID = @@SPID) ) + ')' ELSE '' END + - CASE WHEN is_cursor = 1 THEN ', Cursor' - + CASE WHEN is_optimistic_cursor = 1 THEN '; optimistic' ELSE '' END - + CASE WHEN is_forward_only_cursor = 0 THEN '; not forward only' ELSE '' END - + CASE WHEN is_cursor_dynamic = 1 THEN '; dynamic' ELSE '' END - + CASE WHEN is_fast_forward_cursor = 1 THEN '; fast forward' ELSE '' END - ELSE '' END + - CASE WHEN is_parallel = 1 THEN ', Parallel' ELSE '' END + - CASE WHEN near_parallel = 1 THEN ', Nearly Parallel' ELSE '' END + - CASE WHEN frequent_execution = 1 THEN ', Frequent Execution' ELSE '' END + - CASE WHEN plan_warnings = 1 THEN ', Plan Warnings' ELSE '' END + - CASE WHEN parameter_sniffing = 1 THEN ', Parameter Sniffing' ELSE '' END + - CASE WHEN long_running = 1 THEN ', Long Running Query' ELSE '' END + - CASE WHEN downlevel_estimator = 1 THEN ', Downlevel CE' ELSE '' END + - CASE WHEN implicit_conversions = 1 THEN ', Implicit Conversions' ELSE '' END + - CASE WHEN tvf_join = 1 THEN ', Function Join' ELSE '' END + - CASE WHEN plan_multiple_plans > 0 THEN ', Multiple Plans' + COALESCE(' (' + CAST(plan_multiple_plans AS VARCHAR(10)) + ')', '') ELSE '' END + - CASE WHEN is_trivial = 1 THEN ', Trivial Plans' ELSE '' END + - CASE WHEN is_forced_serial = 1 THEN ', Forced Serialization' ELSE '' END + - CASE WHEN is_key_lookup_expensive = 1 THEN ', Expensive Key Lookup' ELSE '' END + - CASE WHEN is_remote_query_expensive = 1 THEN ', Expensive Remote Query' ELSE '' END + - CASE WHEN trace_flags_session IS NOT NULL THEN ', Session Level Trace Flag(s) Enabled: ' + trace_flags_session ELSE '' END + - CASE WHEN is_unused_grant = 1 THEN ', Unused Memory Grant' ELSE '' END + - CASE WHEN function_count > 0 THEN ', Calls ' + CONVERT(VARCHAR(10), (SELECT SUM(b2.function_count) FROM ##BlitzCacheProcs AS b2 WHERE b2.SqlHandle = b.SqlHandle AND b2.QueryHash IS NOT NULL AND SPID = @@SPID) ) + ' Function(s)' ELSE '' END + - CASE WHEN clr_function_count > 0 THEN ', Calls ' + CONVERT(VARCHAR(10), (SELECT SUM(b2.clr_function_count) FROM ##BlitzCacheProcs AS b2 WHERE b2.SqlHandle = b.SqlHandle AND b2.QueryHash IS NOT NULL AND SPID = @@SPID) ) + ' CLR Function(s)' ELSE '' END + - CASE WHEN PlanCreationTimeHours <= 4 THEN ', Plan created last 4hrs' ELSE '' END + - CASE WHEN is_table_variable = 1 THEN ', Table Variables' ELSE '' END + - CASE WHEN no_stats_warning = 1 THEN ', Columns With No Statistics' ELSE '' END + - CASE WHEN relop_warnings = 1 THEN ', Operator Warnings' ELSE '' END + - CASE WHEN is_table_scan = 1 THEN ', Table Scans' ELSE '' END + - CASE WHEN backwards_scan = 1 THEN ', Backwards Scans' ELSE '' END + - CASE WHEN forced_index = 1 THEN ', Forced Indexes' ELSE '' END + - CASE WHEN forced_seek = 1 THEN ', Forced Seeks' ELSE '' END + - CASE WHEN forced_scan = 1 THEN ', Forced Scans' ELSE '' END + - CASE WHEN columnstore_row_mode = 1 THEN ', ColumnStore Row Mode ' ELSE '' END + - CASE WHEN is_computed_scalar = 1 THEN ', Computed Column UDF ' ELSE '' END + - CASE WHEN is_sort_expensive = 1 THEN ', Expensive Sort' ELSE '' END + - CASE WHEN is_computed_filter = 1 THEN ', Filter UDF' ELSE '' END + - CASE WHEN index_ops >= 5 THEN ', >= 5 Indexes Modified' ELSE '' END + - CASE WHEN is_row_level = 1 THEN ', Row Level Security' ELSE '' END + - CASE WHEN is_spatial = 1 THEN ', Spatial Index' ELSE '' END + - CASE WHEN index_dml = 1 THEN ', Index DML' ELSE '' END + - CASE WHEN table_dml = 1 THEN ', Table DML' ELSE '' END + - CASE WHEN low_cost_high_cpu = 1 THEN ', Low Cost High CPU' ELSE '' END + - CASE WHEN long_running_low_cpu = 1 THEN + ', Long Running With Low CPU' ELSE '' END + - CASE WHEN stale_stats = 1 THEN + ', Statistics used have > 100k modifications in the last 7 days' ELSE '' END + - CASE WHEN is_adaptive = 1 THEN + ', Adaptive Joins' ELSE '' END + - CASE WHEN is_spool_expensive = 1 THEN + ', Expensive Index Spool' ELSE '' END + - CASE WHEN is_spool_more_rows = 1 THEN + ', Large Index Row Spool' ELSE '' END + - CASE WHEN is_table_spool_expensive = 1 THEN + ', Expensive Table Spool' ELSE '' END + - CASE WHEN is_table_spool_more_rows = 1 THEN + ', Many Rows Table Spool' ELSE '' END + - CASE WHEN is_bad_estimate = 1 THEN + ', Row Estimate Mismatch' ELSE '' END + - CASE WHEN is_paul_white_electric = 1 THEN ', SWITCH!' ELSE '' END + - CASE WHEN is_row_goal = 1 THEN ', Row Goals' ELSE '' END + - CASE WHEN is_big_spills = 1 THEN ', >500mb spills' ELSE '' END + - CASE WHEN is_mstvf = 1 THEN ', MSTVFs' ELSE '' END + - CASE WHEN is_mm_join = 1 THEN ', Many to Many Merge' ELSE '' END + - CASE WHEN is_nonsargable = 1 THEN ', non-SARGables' ELSE '' END + - CASE WHEN CompileTime > 5000 THEN ', Long Compile Time' ELSE '' END + - CASE WHEN CompileCPU > 5000 THEN ', High Compile CPU' ELSE '' END + - CASE WHEN CompileMemory > 1024 AND ((CompileMemory) / (1 * CASE WHEN MaxCompileMemory = 0 THEN 1 ELSE MaxCompileMemory END) * 100.) >= 10. THEN ', High Compile Memory' ELSE '' END + - CASE WHEN select_with_writes > 0 THEN ', Select w/ Writes' ELSE '' END - , 3, 200000) -FROM ##BlitzCacheProcs b -WHERE SPID = @@SPID -AND QueryType LIKE 'Statement (parent%' - ) -UPDATE b -SET b.Warnings = s.Warnings -FROM ##BlitzCacheProcs AS b -JOIN statement_warnings s -ON b.SqlHandle = s.SqlHandle -WHERE QueryType LIKE 'Procedure or Function%' -AND SPID = @@SPID -OPTION (RECOMPILE); - -RAISERROR('Checking for plans with >128 levels of nesting', 0, 1) WITH NOWAIT; -WITH plan_handle AS ( -SELECT b.PlanHandle -FROM ##BlitzCacheProcs b - CROSS APPLY sys.dm_exec_text_query_plan(b.PlanHandle, 0, -1) tqp - CROSS APPLY sys.dm_exec_query_plan(b.PlanHandle) qp - WHERE tqp.encrypted = 0 - AND b.SPID = @@SPID - AND (qp.query_plan IS NULL - AND tqp.query_plan IS NOT NULL) -) -UPDATE b -SET Warnings = ISNULL('Your query plan is >128 levels of nested nodes, and can''t be converted to XML. Use SELECT * FROM sys.dm_exec_text_query_plan('+ CONVERT(VARCHAR(128), ph.PlanHandle, 1) + ', 0, -1) to get more information' - , 'We couldn''t find a plan for this query. More info on possible reasons: https://www.brentozar.com/go/noplans') -FROM ##BlitzCacheProcs b -LEFT JOIN plan_handle ph ON -b.PlanHandle = ph.PlanHandle -WHERE b.QueryPlan IS NULL -AND b.SPID = @@SPID -OPTION (RECOMPILE); - -RAISERROR('Checking for plans with no warnings', 0, 1) WITH NOWAIT; -UPDATE ##BlitzCacheProcs -SET Warnings = 'No warnings detected. ' + CASE @ExpertMode - WHEN 0 - THEN ' Try running sp_BlitzCache with @ExpertMode = 1 to find more advanced problems.' - ELSE '' - END -WHERE Warnings = '' OR Warnings IS NULL -AND SPID = @@SPID -OPTION (RECOMPILE); - - -Results: -IF @ExportToExcel = 1 -BEGIN - RAISERROR('Displaying results with Excel formatting (no plans).', 0, 1) WITH NOWAIT; - - /* excel output */ - UPDATE ##BlitzCacheProcs - SET QueryText = SUBSTRING(REPLACE(REPLACE(REPLACE(LTRIM(RTRIM(QueryText)),' ','<>'),'><',''),'<>',' '), 1, 32000) - OPTION(RECOMPILE); - - SET @sql = N' - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT TOP (@Top) - DatabaseName AS [Database Name], - QueryPlanCost AS [Cost], - QueryText, - QueryType AS [Query Type], - Warnings, - ExecutionCount, - ExecutionsPerMinute AS [Executions / Minute], - PercentExecutions AS [Execution Weight], - PercentExecutionsByType AS [% Executions (Type)], - SerialDesiredMemory AS [Serial Desired Memory], - SerialRequiredMemory AS [Serial Required Memory], - TotalCPU AS [Total CPU (ms)], - AverageCPU AS [Avg CPU (ms)], - PercentCPU AS [CPU Weight], - PercentCPUByType AS [% CPU (Type)], - TotalDuration AS [Total Duration (ms)], - AverageDuration AS [Avg Duration (ms)], - PercentDuration AS [Duration Weight], - PercentDurationByType AS [% Duration (Type)], - TotalReads AS [Total Reads], - AverageReads AS [Average Reads], - PercentReads AS [Read Weight], - PercentReadsByType AS [% Reads (Type)], - TotalWrites AS [Total Writes], - AverageWrites AS [Average Writes], - PercentWrites AS [Write Weight], - PercentWritesByType AS [% Writes (Type)], - TotalReturnedRows, - AverageReturnedRows, - MinReturnedRows, - MaxReturnedRows, - MinGrantKB, - MaxGrantKB, - MinUsedGrantKB, - MaxUsedGrantKB, - PercentMemoryGrantUsed, - AvgMaxMemoryGrant, - MinSpills, - MaxSpills, - TotalSpills, - AvgSpills, - NumberOfPlans, - NumberOfDistinctPlans, - PlanCreationTime AS [Created At], - LastExecutionTime AS [Last Execution], - StatementStartOffset, - StatementEndOffset, - PlanGenerationNum, - PlanHandle AS [Plan Handle], - SqlHandle AS [SQL Handle], - QueryHash, - QueryPlanHash, - COALESCE(SetOptions, '''') AS [SET Options] - FROM ##BlitzCacheProcs - WHERE 1 = 1 - AND SPID = @@SPID ' + @nl; - - IF @MinimumExecutionCount IS NOT NULL - BEGIN - SET @sql += N' AND ExecutionCount >= @MinimumExecutionCount '; - END; - - IF @MinutesBack IS NOT NULL - BEGIN - SET @sql += N' AND LastCompletionTime >= DATEADD(MINUTE, @min_back, GETDATE() ) '; - END; - - SELECT @sql += N' ORDER BY ' + CASE @SortOrder WHEN N'cpu' THEN N' TotalCPU ' - WHEN N'reads' THEN N' TotalReads ' - WHEN N'writes' THEN N' TotalWrites ' - WHEN N'duration' THEN N' TotalDuration ' - WHEN N'executions' THEN N' ExecutionCount ' - WHEN N'compiles' THEN N' PlanCreationTime ' - WHEN N'memory grant' THEN N' MaxGrantKB' - WHEN N'unused grant' THEN N' MaxGrantKB - MaxUsedGrantKB' - WHEN N'spills' THEN N' MaxSpills' - WHEN N'duplicate' THEN N' plan_multiple_plans ' /* Issue #3345 */ - WHEN N'avg cpu' THEN N' AverageCPU' - WHEN N'avg reads' THEN N' AverageReads' - WHEN N'avg writes' THEN N' AverageWrites' - WHEN N'avg duration' THEN N' AverageDuration' - WHEN N'avg executions' THEN N' ExecutionsPerMinute' - WHEN N'avg memory grant' THEN N' AvgMaxMemoryGrant' - WHEN N'avg spills' THEN N' AvgSpills' - END + N' DESC '; - - SET @sql += N' OPTION (RECOMPILE) ; '; - - IF @sql IS NULL - BEGIN - RAISERROR('@sql is null, which means dynamic SQL generation went terribly wrong', 0, 1) WITH NOWAIT; - END - - IF @Debug = 1 - BEGIN - RAISERROR('Printing @sql, the dynamic SQL we generated:', 0, 1) WITH NOWAIT; - PRINT SUBSTRING(@sql, 0, 4000); - PRINT SUBSTRING(@sql, 4000, 8000); - PRINT SUBSTRING(@sql, 8000, 12000); - PRINT SUBSTRING(@sql, 12000, 16000); - PRINT SUBSTRING(@sql, 16000, 20000); - PRINT SUBSTRING(@sql, 20000, 24000); - PRINT SUBSTRING(@sql, 24000, 28000); - PRINT SUBSTRING(@sql, 28000, 32000); - PRINT SUBSTRING(@sql, 32000, 36000); - PRINT SUBSTRING(@sql, 36000, 40000); - END; - - EXEC sp_executesql @sql, N'@Top INT, @min_duration INT, @min_back INT, @MinimumExecutionCount INT', @Top, @DurationFilter_i, @MinutesBack, @MinimumExecutionCount; -END; - - -RAISERROR('Displaying analysis of plan cache.', 0, 1) WITH NOWAIT; - -DECLARE @columns NVARCHAR(MAX) = N'' ; - -IF @ExpertMode = 0 -BEGIN - RAISERROR(N'Returning ExpertMode = 0', 0, 1) WITH NOWAIT; - SET @columns = N' DatabaseName AS [Database], - QueryPlanCost AS [Cost], - QueryText AS [Query Text], - QueryType AS [Query Type], - Warnings AS [Warnings], - QueryPlan AS [Query Plan], - missing_indexes AS [Missing Indexes], - implicit_conversion_info AS [Implicit Conversion Info], - cached_execution_parameters AS [Cached Execution Parameters], - CONVERT(NVARCHAR(30), CAST((ExecutionCount) AS BIGINT), 1) AS [# Executions], - CONVERT(NVARCHAR(30), CAST((ExecutionsPerMinute) AS BIGINT), 1) AS [Executions / Minute], - CONVERT(NVARCHAR(30), CAST((PercentExecutions) AS BIGINT), 1) AS [Execution Weight], - CONVERT(NVARCHAR(30), CAST((TotalCPU) AS BIGINT), 1) AS [Total CPU (ms)], - CONVERT(NVARCHAR(30), CAST((AverageCPU) AS BIGINT), 1) AS [Avg CPU (ms)], - CONVERT(NVARCHAR(30), CAST((PercentCPU) AS BIGINT), 1) AS [CPU Weight], - CONVERT(NVARCHAR(30), CAST((TotalDuration) AS BIGINT), 1) AS [Total Duration (ms)], - CONVERT(NVARCHAR(30), CAST((AverageDuration) AS BIGINT), 1) AS [Avg Duration (ms)], - CONVERT(NVARCHAR(30), CAST((PercentDuration) AS BIGINT), 1) AS [Duration Weight], - CONVERT(NVARCHAR(30), CAST((TotalReads) AS BIGINT), 1) AS [Total Reads], - CONVERT(NVARCHAR(30), CAST((AverageReads) AS BIGINT), 1) AS [Avg Reads], - CONVERT(NVARCHAR(30), CAST((PercentReads) AS BIGINT), 1) AS [Read Weight], - CONVERT(NVARCHAR(30), CAST((TotalWrites) AS BIGINT), 1) AS [Total Writes], - CONVERT(NVARCHAR(30), CAST((AverageWrites) AS BIGINT), 1) AS [Avg Writes], - CONVERT(NVARCHAR(30), CAST((PercentWrites) AS BIGINT), 1) AS [Write Weight], - CONVERT(NVARCHAR(30), CAST((AverageReturnedRows) AS BIGINT), 1) AS [Average Rows], - CONVERT(NVARCHAR(30), CAST((MinGrantKB) AS BIGINT), 1) AS [Minimum Memory Grant KB], - CONVERT(NVARCHAR(30), CAST((MaxGrantKB) AS BIGINT), 1) AS [Maximum Memory Grant KB], - CONVERT(NVARCHAR(30), CAST((MinUsedGrantKB) AS BIGINT), 1) AS [Minimum Used Grant KB], - CONVERT(NVARCHAR(30), CAST((MaxUsedGrantKB) AS BIGINT), 1) AS [Maximum Used Grant KB], - CONVERT(NVARCHAR(30), CAST((AvgMaxMemoryGrant) AS BIGINT), 1) AS [Average Max Memory Grant], - CONVERT(NVARCHAR(30), CAST((MinSpills) AS BIGINT), 1) AS [Min Spills], - CONVERT(NVARCHAR(30), CAST((MaxSpills) AS BIGINT), 1) AS [Max Spills], - CONVERT(NVARCHAR(30), CAST((TotalSpills) AS BIGINT), 1) AS [Total Spills], - CONVERT(NVARCHAR(30), CAST((AvgSpills) AS MONEY), 1) AS [Avg Spills], - PlanCreationTime AS [Created At], - LastExecutionTime AS [Last Execution], - LastCompletionTime AS [Last Completion], - PlanHandle AS [Plan Handle], - SqlHandle AS [SQL Handle], - COALESCE(SetOptions, '''') AS [SET Options], - QueryHash AS [Query Hash], - PlanGenerationNum, - [Remove Plan Handle From Cache]'; -END; -ELSE -BEGIN - SET @columns = N' DatabaseName AS [Database], - QueryPlanCost AS [Cost], - QueryText AS [Query Text], - QueryType AS [Query Type], - Warnings AS [Warnings], - QueryPlan AS [Query Plan], - missing_indexes AS [Missing Indexes], - implicit_conversion_info AS [Implicit Conversion Info], - cached_execution_parameters AS [Cached Execution Parameters], ' + @nl; - - IF @ExpertMode = 2 /* Opserver */ - BEGIN - RAISERROR(N'Returning Expert Mode = 2', 0, 1) WITH NOWAIT; - SET @columns += N' - SUBSTRING( - CASE WHEN warning_no_join_predicate = 1 THEN '', 20'' ELSE '''' END + - CASE WHEN compile_timeout = 1 THEN '', 18'' ELSE '''' END + - CASE WHEN compile_memory_limit_exceeded = 1 THEN '', 19'' ELSE '''' END + - CASE WHEN busy_loops = 1 THEN '', 16'' ELSE '''' END + - CASE WHEN is_forced_plan = 1 THEN '', 3'' ELSE '''' END + - CASE WHEN is_forced_parameterized > 0 THEN '', 5'' ELSE '''' END + - CASE WHEN unparameterized_query = 1 THEN '', 23'' ELSE '''' END + - CASE WHEN missing_index_count > 0 THEN '', 10'' ELSE '''' END + - CASE WHEN unmatched_index_count > 0 THEN '', 22'' ELSE '''' END + - CASE WHEN is_cursor = 1 THEN '', 4'' ELSE '''' END + - CASE WHEN is_parallel = 1 THEN '', 6'' ELSE '''' END + - CASE WHEN near_parallel = 1 THEN '', 7'' ELSE '''' END + - CASE WHEN frequent_execution = 1 THEN '', 1'' ELSE '''' END + - CASE WHEN plan_warnings = 1 THEN '', 8'' ELSE '''' END + - CASE WHEN parameter_sniffing = 1 THEN '', 2'' ELSE '''' END + - CASE WHEN long_running = 1 THEN '', 9'' ELSE '''' END + - CASE WHEN downlevel_estimator = 1 THEN '', 13'' ELSE '''' END + - CASE WHEN implicit_conversions = 1 THEN '', 14'' ELSE '''' END + - CASE WHEN tvf_join = 1 THEN '', 17'' ELSE '''' END + - CASE WHEN plan_multiple_plans > 0 THEN '', 21'' ELSE '''' END + - CASE WHEN unmatched_index_count > 0 THEN '', 22'' ELSE '''' END + - CASE WHEN is_trivial = 1 THEN '', 24'' ELSE '''' END + - CASE WHEN is_forced_serial = 1 THEN '', 25'' ELSE '''' END + - CASE WHEN is_key_lookup_expensive = 1 THEN '', 26'' ELSE '''' END + - CASE WHEN is_remote_query_expensive = 1 THEN '', 28'' ELSE '''' END + - CASE WHEN trace_flags_session IS NOT NULL THEN '', 29'' ELSE '''' END + - CASE WHEN is_unused_grant = 1 THEN '', 30'' ELSE '''' END + - CASE WHEN function_count > 0 THEN '', 31'' ELSE '''' END + - CASE WHEN clr_function_count > 0 THEN '', 32'' ELSE '''' END + - CASE WHEN PlanCreationTimeHours <= 4 THEN '', 33'' ELSE '''' END + - CASE WHEN is_table_variable = 1 THEN '', 34'' ELSE '''' END + - CASE WHEN no_stats_warning = 1 THEN '', 35'' ELSE '''' END + - CASE WHEN relop_warnings = 1 THEN '', 36'' ELSE '''' END + - CASE WHEN is_table_scan = 1 THEN '', 37'' ELSE '''' END + - CASE WHEN backwards_scan = 1 THEN '', 38'' ELSE '''' END + - CASE WHEN forced_index = 1 THEN '', 39'' ELSE '''' END + - CASE WHEN forced_seek = 1 OR forced_scan = 1 THEN '', 40'' ELSE '''' END + - CASE WHEN columnstore_row_mode = 1 THEN '', 41'' ELSE '''' END + - CASE WHEN is_computed_scalar = 1 THEN '', 42'' ELSE '''' END + - CASE WHEN is_sort_expensive = 1 THEN '', 43'' ELSE '''' END + - CASE WHEN is_computed_filter = 1 THEN '', 44'' ELSE '''' END + - CASE WHEN index_ops >= 5 THEN '', 45'' ELSE '''' END + - CASE WHEN is_row_level = 1 THEN '', 46'' ELSE '''' END + - CASE WHEN is_spatial = 1 THEN '', 47'' ELSE '''' END + - CASE WHEN index_dml = 1 THEN '', 48'' ELSE '''' END + - CASE WHEN table_dml = 1 THEN '', 49'' ELSE '''' END + - CASE WHEN long_running_low_cpu = 1 THEN '', 50'' ELSE '''' END + - CASE WHEN low_cost_high_cpu = 1 THEN '', 51'' ELSE '''' END + - CASE WHEN stale_stats = 1 THEN '', 52'' ELSE '''' END + - CASE WHEN is_adaptive = 1 THEN '', 53'' ELSE '''' END + - CASE WHEN is_spool_expensive = 1 THEN + '', 54'' ELSE '''' END + - CASE WHEN is_spool_more_rows = 1 THEN + '', 55'' ELSE '''' END + - CASE WHEN is_table_spool_expensive = 1 THEN + '', 67'' ELSE '''' END + - CASE WHEN is_table_spool_more_rows = 1 THEN + '', 68'' ELSE '''' END + - CASE WHEN is_bad_estimate = 1 THEN + '', 56'' ELSE '''' END + - CASE WHEN is_paul_white_electric = 1 THEN '', 57'' ELSE '''' END + - CASE WHEN is_row_goal = 1 THEN '', 58'' ELSE '''' END + - CASE WHEN is_big_spills = 1 THEN '', 59'' ELSE '''' END + - CASE WHEN is_mstvf = 1 THEN '', 60'' ELSE '''' END + - CASE WHEN is_mm_join = 1 THEN '', 61'' ELSE '''' END + - CASE WHEN is_nonsargable = 1 THEN '', 62'' ELSE '''' END + - CASE WHEN CompileTime > 5000 THEN '', 63 '' ELSE '''' END + - CASE WHEN CompileCPU > 5000 THEN '', 64 '' ELSE '''' END + - CASE WHEN CompileMemory > 1024 AND ((CompileMemory) / (1 * CASE WHEN MaxCompileMemory = 0 THEN 1 ELSE MaxCompileMemory END) * 100.) >= 10. THEN '', 65 '' ELSE '''' END + - CASE WHEN select_with_writes > 0 THEN '', 66'' ELSE '''' END - , 3, 200000) AS opserver_warning , ' + @nl ; - END; - - SET @columns += N' - CONVERT(NVARCHAR(30), CAST((ExecutionCount) AS BIGINT), 1) AS [# Executions], - CONVERT(NVARCHAR(30), CAST((ExecutionsPerMinute) AS BIGINT), 1) AS [Executions / Minute], - CONVERT(NVARCHAR(30), CAST((PercentExecutions) AS BIGINT), 1) AS [Execution Weight], - CONVERT(NVARCHAR(30), CAST((SerialDesiredMemory) AS BIGINT), 1) AS [Serial Desired Memory], - CONVERT(NVARCHAR(30), CAST((SerialRequiredMemory) AS BIGINT), 1) AS [Serial Required Memory], - CONVERT(NVARCHAR(30), CAST((TotalCPU) AS BIGINT), 1) AS [Total CPU (ms)], - CONVERT(NVARCHAR(30), CAST((AverageCPU) AS BIGINT), 1) AS [Avg CPU (ms)], - CONVERT(NVARCHAR(30), CAST((PercentCPU) AS BIGINT), 1) AS [CPU Weight], - CONVERT(NVARCHAR(30), CAST((TotalDuration) AS BIGINT), 1) AS [Total Duration (ms)], - CONVERT(NVARCHAR(30), CAST((AverageDuration) AS BIGINT), 1) AS [Avg Duration (ms)], - CONVERT(NVARCHAR(30), CAST((PercentDuration) AS BIGINT), 1) AS [Duration Weight], - CONVERT(NVARCHAR(30), CAST((TotalReads) AS BIGINT), 1) AS [Total Reads], - CONVERT(NVARCHAR(30), CAST((AverageReads) AS BIGINT), 1) AS [Average Reads], - CONVERT(NVARCHAR(30), CAST((PercentReads) AS BIGINT), 1) AS [Read Weight], - CONVERT(NVARCHAR(30), CAST((TotalWrites) AS BIGINT), 1) AS [Total Writes], - CONVERT(NVARCHAR(30), CAST((AverageWrites) AS BIGINT), 1) AS [Average Writes], - CONVERT(NVARCHAR(30), CAST((PercentWrites) AS BIGINT), 1) AS [Write Weight], - CONVERT(NVARCHAR(30), CAST((PercentExecutionsByType) AS BIGINT), 1) AS [% Executions (Type)], - CONVERT(NVARCHAR(30), CAST((PercentCPUByType) AS BIGINT), 1) AS [% CPU (Type)], - CONVERT(NVARCHAR(30), CAST((PercentDurationByType) AS BIGINT), 1) AS [% Duration (Type)], - CONVERT(NVARCHAR(30), CAST((PercentReadsByType) AS BIGINT), 1) AS [% Reads (Type)], - CONVERT(NVARCHAR(30), CAST((PercentWritesByType) AS BIGINT), 1) AS [% Writes (Type)], - CONVERT(NVARCHAR(30), CAST((TotalReturnedRows) AS BIGINT), 1) AS [Total Rows], - CONVERT(NVARCHAR(30), CAST((AverageReturnedRows) AS BIGINT), 1) AS [Avg Rows], - CONVERT(NVARCHAR(30), CAST((MinReturnedRows) AS BIGINT), 1) AS [Min Rows], - CONVERT(NVARCHAR(30), CAST((MaxReturnedRows) AS BIGINT), 1) AS [Max Rows], - CONVERT(NVARCHAR(30), CAST((MinGrantKB) AS BIGINT), 1) AS [Minimum Memory Grant KB], - CONVERT(NVARCHAR(30), CAST((MaxGrantKB) AS BIGINT), 1) AS [Maximum Memory Grant KB], - CONVERT(NVARCHAR(30), CAST((MinUsedGrantKB) AS BIGINT), 1) AS [Minimum Used Grant KB], - CONVERT(NVARCHAR(30), CAST((MaxUsedGrantKB) AS BIGINT), 1) AS [Maximum Used Grant KB], - CONVERT(NVARCHAR(30), CAST((AvgMaxMemoryGrant) AS BIGINT), 1) AS [Average Max Memory Grant], - CONVERT(NVARCHAR(30), CAST((MinSpills) AS BIGINT), 1) AS [Min Spills], - CONVERT(NVARCHAR(30), CAST((MaxSpills) AS BIGINT), 1) AS [Max Spills], - CONVERT(NVARCHAR(30), CAST((TotalSpills) AS BIGINT), 1) AS [Total Spills], - CONVERT(NVARCHAR(30), CAST((AvgSpills) AS MONEY), 1) AS [Avg Spills], - CONVERT(NVARCHAR(30), CAST((NumberOfPlans) AS BIGINT), 1) AS [# Plans], - CONVERT(NVARCHAR(30), CAST((NumberOfDistinctPlans) AS BIGINT), 1) AS [# Distinct Plans], - PlanCreationTime AS [Created At], - LastExecutionTime AS [Last Execution], - LastCompletionTime AS [Last Completion], - CONVERT(NVARCHAR(30), CAST((CachedPlanSize) AS BIGINT), 1) AS [Cached Plan Size (KB)], - CONVERT(NVARCHAR(30), CAST((CompileTime) AS BIGINT), 1) AS [Compile Time (ms)], - CONVERT(NVARCHAR(30), CAST((CompileCPU) AS BIGINT), 1) AS [Compile CPU (ms)], - CONVERT(NVARCHAR(30), CAST((CompileMemory) AS BIGINT), 1) AS [Compile memory (KB)], - COALESCE(SetOptions, '''') AS [SET Options], - PlanHandle AS [Plan Handle], - SqlHandle AS [SQL Handle], - [SQL Handle More Info], - QueryHash AS [Query Hash], - [Query Hash More Info], - QueryPlanHash AS [Query Plan Hash], - StatementStartOffset, - StatementEndOffset, - PlanGenerationNum, - [Remove Plan Handle From Cache], - [Remove SQL Handle From Cache]'; -END; - -SET @sql = N' -SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT TOP (@Top) ' + @columns + @nl + N' -FROM ##BlitzCacheProcs -WHERE SPID = @spid ' + @nl; - -IF @MinimumExecutionCount IS NOT NULL - BEGIN - SET @sql += N' AND ExecutionCount >= @MinimumExecutionCount ' + @nl; - END; - -IF @MinutesBack IS NOT NULL - BEGIN - SET @sql += N' AND LastCompletionTime >= DATEADD(MINUTE, @min_back, GETDATE() ) ' + @nl; - END; - -SELECT @sql += N' ORDER BY ' + CASE @SortOrder WHEN N'cpu' THEN N' TotalCPU ' - WHEN N'reads' THEN N' TotalReads ' - WHEN N'writes' THEN N' TotalWrites ' - WHEN N'duration' THEN N' TotalDuration ' - WHEN N'executions' THEN N' ExecutionCount ' - WHEN N'compiles' THEN N' PlanCreationTime ' - WHEN N'memory grant' THEN N' MaxGrantKB' - WHEN N'unused grant' THEN N' MaxGrantKB - MaxUsedGrantKB ' - WHEN N'duplicate' THEN N' plan_multiple_plans ' - WHEN N'spills' THEN N' MaxSpills ' - WHEN N'avg cpu' THEN N' AverageCPU' - WHEN N'avg reads' THEN N' AverageReads' - WHEN N'avg writes' THEN N' AverageWrites' - WHEN N'avg duration' THEN N' AverageDuration' - WHEN N'avg executions' THEN N' ExecutionsPerMinute' - WHEN N'avg memory grant' THEN N' AvgMaxMemoryGrant' - WHEN N'avg spills' THEN N' AvgSpills' - END + N' DESC '; -SET @sql += N' OPTION (RECOMPILE) ; '; - -IF @Debug = 1 - BEGIN - PRINT SUBSTRING(@sql, 0, 4000); - PRINT SUBSTRING(@sql, 4000, 8000); - PRINT SUBSTRING(@sql, 8000, 12000); - PRINT SUBSTRING(@sql, 12000, 16000); - PRINT SUBSTRING(@sql, 16000, 20000); - PRINT SUBSTRING(@sql, 20000, 24000); - PRINT SUBSTRING(@sql, 24000, 28000); - PRINT SUBSTRING(@sql, 28000, 32000); - PRINT SUBSTRING(@sql, 32000, 36000); - PRINT SUBSTRING(@sql, 36000, 40000); - END; -IF(@OutputType <> 'NONE') -BEGIN - EXEC sp_executesql @sql, N'@Top INT, @spid INT, @MinimumExecutionCount INT, @min_back INT', @Top, @@SPID, @MinimumExecutionCount, @MinutesBack; -END; - -/* - -This section will check if: - * >= 30% of plans were created in the last hour - * Check on the memory_clerks DMV for space used by TokenAndPermUserStore - * Compare that to the size of the buffer pool - * If it's >10%, -*/ -IF EXISTS -( - SELECT 1/0 - FROM #plan_creation AS pc - WHERE pc.percent_1 >= 30 -) -BEGIN - -SELECT @common_version = - CONVERT(DECIMAL(10,2), c.common_version) -FROM #checkversion AS c; - -IF @common_version >= 11 - SET @user_perm_sql = N' - SET @buffer_pool_memory_gb = 0; - SELECT @buffer_pool_memory_gb = SUM(pages_kb)/ 1024. / 1024. - FROM sys.dm_os_memory_clerks - WHERE type = ''MEMORYCLERK_SQLBUFFERPOOL'';' -ELSE - SET @user_perm_sql = N' - SET @buffer_pool_memory_gb = 0; - SELECT @buffer_pool_memory_gb = SUM(single_pages_kb + multi_pages_kb)/ 1024. / 1024. - FROM sys.dm_os_memory_clerks - WHERE type = ''MEMORYCLERK_SQLBUFFERPOOL'';' - -EXEC sys.sp_executesql @user_perm_sql, - N'@buffer_pool_memory_gb DECIMAL(10,2) OUTPUT', - @buffer_pool_memory_gb = @buffer_pool_memory_gb OUTPUT; - -IF @common_version >= 11 -BEGIN - SET @user_perm_sql = N' - SELECT @user_perm_gb = CASE WHEN (pages_kb / 1024.0 / 1024.) >= 2. - THEN CONVERT(DECIMAL(38, 2), (pages_kb / 1024.0 / 1024.)) - ELSE 0 - END - FROM sys.dm_os_memory_clerks - WHERE type = ''USERSTORE_TOKENPERM'' - AND name = ''TokenAndPermUserStore'';'; -END; - -IF @common_version < 11 -BEGIN - SET @user_perm_sql = N' - SELECT @user_perm_gb = CASE WHEN ((single_pages_kb + multi_pages_kb) / 1024.0 / 1024.) >= 2. - THEN CONVERT(DECIMAL(38, 2), ((single_pages_kb + multi_pages_kb) / 1024.0 / 1024.)) - ELSE 0 - END - FROM sys.dm_os_memory_clerks - WHERE type = ''USERSTORE_TOKENPERM'' - AND name = ''TokenAndPermUserStore'';'; -END; - -EXEC sys.sp_executesql @user_perm_sql, - N'@user_perm_gb DECIMAL(10,2) OUTPUT', - @user_perm_gb = @user_perm_gb_out OUTPUT; - -IF @buffer_pool_memory_gb > 0 - BEGIN - IF (@user_perm_gb_out / (1. * @buffer_pool_memory_gb)) * 100. >= 10 - BEGIN - SET @is_tokenstore_big = 1; - SET @user_perm_percent = (@user_perm_gb_out / (1. * @buffer_pool_memory_gb)) * 100.; - END - END - -END - - - -IF @HideSummary = 0 AND @ExportToExcel = 0 -BEGIN - IF @Reanalyze = 0 - BEGIN - RAISERROR('Building query plan summary data.', 0, 1) WITH NOWAIT; - - /* Build summary data */ - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs - WHERE frequent_execution = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 1, - 100, - 'Execution Pattern', - 'Frequent Execution', - 'https://www.brentozar.com/blitzcache/frequently-executed-queries/', - 'Queries are being executed more than ' - + CAST (@execution_threshold AS VARCHAR(5)) - + ' times per minute. This can put additional load on the server, even when queries are lightweight.') ; - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs - WHERE parameter_sniffing = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 2, - 50, - 'Parameterization', - 'Parameter Sniffing', - 'https://www.brentozar.com/blitzcache/parameter-sniffing/', - 'There are signs of parameter sniffing (wide variance in rows return or time to execute). Investigate query patterns and tune code appropriately.') ; - - /* Forced execution plans */ - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs - WHERE is_forced_plan = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 3, - 50, - 'Parameterization', - 'Forced Plan', - 'https://www.brentozar.com/blitzcache/forced-plans/', - 'Execution plans have been compiled with forced plans, either through FORCEPLAN, plan guides, or forced parameterization. This will make general tuning efforts less effective.'); - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs - WHERE is_cursor = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 4, - 200, - 'Cursors', - 'Cursor', - 'https://www.brentozar.com/blitzcache/cursors-found-slow-queries/', - 'There are cursors in the plan cache. This is neither good nor bad, but it is a thing. Cursors are weird in SQL Server.'); - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs - WHERE is_cursor = 1 - AND is_optimistic_cursor = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 4, - 200, - 'Cursors', - 'Optimistic Cursors', - 'https://www.brentozar.com/blitzcache/cursors-found-slow-queries/', - 'There are optimistic cursors in the plan cache, which can harm performance.'); - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs - WHERE is_cursor = 1 - AND is_forward_only_cursor = 0 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 4, - 200, - 'Cursors', - 'Non-forward Only Cursors', - 'https://www.brentozar.com/blitzcache/cursors-found-slow-queries/', - 'There are non-forward only cursors in the plan cache, which can harm performance.'); - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs - WHERE is_cursor = 1 - AND is_cursor_dynamic = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 4, - 200, - 'Cursors', - 'Dynamic Cursors', - 'https://www.brentozar.com/blitzcache/cursors-found-slow-queries/', - 'Dynamic Cursors inhibit parallelism!.'); - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs - WHERE is_cursor = 1 - AND is_fast_forward_cursor = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 4, - 200, - 'Cursors', - 'Fast Forward Cursors', - 'https://www.brentozar.com/blitzcache/cursors-found-slow-queries/', - 'Fast forward cursors inhibit parallelism!.'); - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs - WHERE is_forced_parameterized = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 5, - 50, - 'Parameterization', - 'Forced Parameterization', - 'https://www.brentozar.com/blitzcache/forced-parameterization/', - 'Execution plans have been compiled with forced parameterization.') ; - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.is_parallel = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 6, - 200, - 'Execution Plans', - 'Parallel', - 'https://www.brentozar.com/blitzcache/parallel-plans-detected/', - 'Parallel plans detected. These warrant investigation, but are neither good nor bad.') ; - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE near_parallel = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 7, - 200, - 'Execution Plans', - 'Nearly Parallel', - 'https://www.brentozar.com/blitzcache/query-cost-near-cost-threshold-parallelism/', - 'Queries near the cost threshold for parallelism. These may go parallel when you least expect it.') ; - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE plan_warnings = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 8, - 50, - 'Execution Plans', - 'Plan Warnings', - 'https://www.brentozar.com/blitzcache/query-plan-warnings/', - 'Warnings detected in execution plans. SQL Server is telling you that something bad is going on that requires your attention.') ; - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE long_running = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 9, - 50, - 'Performance', - 'Long Running Query', - 'https://www.brentozar.com/blitzcache/long-running-queries/', - 'Long running queries have been found. These are queries with an average duration longer than ' - + CAST(@long_running_query_warning_seconds / 1000 / 1000 AS VARCHAR(5)) - + ' second(s). These queries should be investigated for additional tuning options.') ; - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.missing_index_count > 0 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 10, - 50, - 'Performance', - 'Missing Indexes', - 'https://www.brentozar.com/blitzcache/missing-index-request/', - 'Queries found with missing indexes.'); - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.downlevel_estimator = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 13, - 200, - 'Cardinality', - 'Downlevel CE', - 'https://www.brentozar.com/blitzcache/legacy-cardinality-estimator/', - 'A legacy cardinality estimator is being used by one or more queries. Investigate whether you need to be using this cardinality estimator. This may be caused by compatibility levels, global trace flags, or query level trace flags.'); - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE implicit_conversions = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 14, - 50, - 'Performance', - 'Implicit Conversions', - 'https://www.brentozar.com/go/implicit', - 'One or more queries are comparing two fields that are not of the same data type.') ; - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs - WHERE busy_loops = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 16, - 100, - 'Performance', - 'Busy Loops', - 'https://www.brentozar.com/blitzcache/busy-loops/', - 'Operations have been found that are executed 100 times more often than the number of rows returned by each iteration. This is an indicator that something is off in query execution.'); - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs - WHERE tvf_join = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 17, - 50, - 'Performance', - 'Function Join', - 'https://www.brentozar.com/blitzcache/tvf-join/', - 'Execution plans have been found that join to table valued functions (TVFs). TVFs produce inaccurate estimates of the number of rows returned and can lead to any number of query plan problems.'); - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs - WHERE compile_timeout = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 18, - 50, - 'Execution Plans', - 'Compilation Timeout', - 'https://www.brentozar.com/blitzcache/compilation-timeout/', - 'Query compilation timed out for one or more queries. SQL Server did not find a plan that meets acceptable performance criteria in the time allotted so the best guess was returned. There is a very good chance that this plan isn''t even below average - it''s probably terrible.'); - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs - WHERE compile_memory_limit_exceeded = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 19, - 50, - 'Execution Plans', - 'Compile Memory Limit Exceeded', - 'https://www.brentozar.com/blitzcache/compile-memory-limit-exceeded/', - 'The optimizer has a limited amount of memory available. One or more queries are complex enough that SQL Server was unable to allocate enough memory to fully optimize the query. A best fit plan was found, and it''s probably terrible.'); - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs - WHERE warning_no_join_predicate = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 20, - 50, - 'Execution Plans', - 'No Join Predicate', - 'https://www.brentozar.com/blitzcache/no-join-predicate/', - 'Operators in a query have no join predicate. This means that all rows from one table will be matched with all rows from anther table producing a Cartesian product. That''s a whole lot of rows. This may be your goal, but it''s important to investigate why this is happening.'); - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs - WHERE plan_multiple_plans > 0 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 21, - 200, - 'Execution Plans', - 'Multiple Plans', - 'https://www.brentozar.com/blitzcache/multiple-plans/', - 'Queries exist with multiple execution plans (as determined by query_plan_hash). Investigate possible ways to parameterize these queries or otherwise reduce the plan count.'); - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs - WHERE unmatched_index_count > 0 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 22, - 100, - 'Performance', - 'Unmatched Indexes', - 'https://www.brentozar.com/blitzcache/unmatched-indexes', - 'An index could have been used, but SQL Server chose not to use it - likely due to parameterization and filtered indexes.'); - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs - WHERE unparameterized_query = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 23, - 100, - 'Parameterization', - 'Unparameterized Query', - 'https://www.brentozar.com/blitzcache/unparameterized-queries', - 'Unparameterized queries found. These could be ad hoc queries, data exploration, or queries using "OPTIMIZE FOR UNKNOWN".'); - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs - WHERE is_trivial = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 24, - 100, - 'Execution Plans', - 'Trivial Plans', - 'https://www.brentozar.com/blitzcache/trivial-plans', - 'Trivial plans get almost no optimization. If you''re finding these in the top worst queries, something may be going wrong.'); - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.is_forced_serial= 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 25, - 10, - 'Execution Plans', - 'Forced Serialization', - 'https://www.brentozar.com/blitzcache/forced-serialization/', - 'Something in your plan is forcing a serial query. Further investigation is needed if this is not by design.') ; - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.is_key_lookup_expensive= 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 26, - 100, - 'Execution Plans', - 'Expensive Key Lookup', - 'https://www.brentozar.com/blitzcache/expensive-key-lookups/', - 'There''s a key lookup in your plan that costs >=50% of the total plan cost.') ; - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.is_remote_query_expensive= 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 28, - 100, - 'Execution Plans', - 'Expensive Remote Query', - 'https://www.brentozar.com/blitzcache/expensive-remote-query/', - 'There''s a remote query in your plan that costs >=50% of the total plan cost.') ; - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.trace_flags_session IS NOT NULL - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 29, - 200, - 'Trace Flags', - 'Session Level Trace Flags Enabled', - 'https://www.brentozar.com/blitz/trace-flags-enabled-globally/', - 'Someone is enabling session level Trace Flags in a query.') ; - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.is_unused_grant IS NOT NULL - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 30, - 100, - 'Memory Grant', - 'Unused Memory Grant', - 'https://www.brentozar.com/blitzcache/unused-memory-grants/', - 'Queries have large unused memory grants. This can cause concurrency issues, if queries are waiting a long time to get memory to run.') ; - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.function_count > 0 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 31, - 100, - 'Compute Scalar That References A Function', - 'Calls Functions', - 'https://www.brentozar.com/blitzcache/compute-scalar-functions/', - 'Both of these will force queries to run serially, run at least once per row, and may result in poor cardinality estimates.') ; - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.clr_function_count > 0 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 32, - 100, - 'Compute Scalar That References A CLR Function', - 'Calls CLR Functions', - 'https://www.brentozar.com/blitzcache/compute-scalar-functions/', - 'May force queries to run serially, run at least once per row, and may result in poor cardinality estimates.') ; - - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.is_table_variable = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 33, - 100, - 'Table Variables detected', - 'Table Variables', - 'https://www.brentozar.com/blitzcache/table-variables/', - 'All modifications are single threaded, and selects have really low row estimates.') ; - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.no_stats_warning = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 35, - 100, - 'Statistics', - 'Columns With No Statistics', - 'https://www.brentozar.com/blitzcache/columns-no-statistics/', - 'Sometimes this happens with indexed views, other times because auto create stats is turned off.') ; - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.relop_warnings = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 36, - 100, - 'Warnings', - 'Operator Warnings', - 'https://www.brentozar.com/blitzcache/query-plan-warnings/', - 'Check the plan for more details.') ; - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.is_table_scan = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 37, - 100, - 'Indexes', - 'Table Scans (Heaps)', - 'https://www.brentozar.com/archive/2012/05/video-heaps/', - 'This may not be a problem. Run sp_BlitzIndex for more information.') ; - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.backwards_scan = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 38, - 200, - 'Indexes', - 'Backwards Scans', - 'https://www.brentozar.com/blitzcache/backwards-scans/', - 'This isn''t always a problem. They can cause serial zones in plans, and may need an index to match sort order.') ; - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.forced_index = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 39, - 100, - 'Indexes', - 'Forced Indexes', - 'https://www.brentozar.com/blitzcache/optimizer-forcing/', - 'This can cause inefficient plans, and will prevent missing index requests.') ; - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.forced_seek = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 40, - 100, - 'Indexes', - 'Forced Seeks', - 'https://www.brentozar.com/blitzcache/optimizer-forcing/', - 'This can cause inefficient plans by taking seek vs scan choice away from the optimizer.') ; - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.forced_scan = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 40, - 100, - 'Indexes', - 'Forced Scans', - 'https://www.brentozar.com/blitzcache/optimizer-forcing/', - 'This can cause inefficient plans by taking seek vs scan choice away from the optimizer.') ; - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.columnstore_row_mode = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 41, - 100, - 'Indexes', - 'ColumnStore Row Mode', - 'https://www.brentozar.com/blitzcache/columnstore-indexes-operating-row-mode/', - 'ColumnStore indexes operating in Row Mode indicate really poor query choices.') ; - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.is_computed_scalar = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 42, - 50, - 'Functions', - 'Computed Column UDF', - 'https://www.brentozar.com/blitzcache/computed-columns-referencing-functions/', - 'This can cause a whole mess of bad serializartion problems.') ; - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.is_sort_expensive = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 43, - 100, - 'Execution Plans', - 'Expensive Sort', - 'https://www.brentozar.com/blitzcache/expensive-sorts/', - 'There''s a sort in your plan that costs >=50% of the total plan cost.') ; - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.is_computed_filter = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 44, - 50, - 'Functions', - 'Filter UDF', - 'https://www.brentozar.com/blitzcache/compute-scalar-functions/', - 'Someone put a Scalar UDF in the WHERE clause!') ; - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.index_ops >= 5 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 45, - 100, - 'Indexes', - '>= 5 Indexes Modified', - 'https://www.brentozar.com/blitzcache/many-indexes-modified/', - 'This can cause lots of hidden I/O -- Run sp_BlitzIndex for more information.') ; - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.is_row_level = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 46, - 200, - 'Complexity', - 'Row Level Security', - 'https://www.brentozar.com/blitzcache/row-level-security/', - 'You may see a lot of confusing junk in your query plan.') ; - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.is_spatial = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 47, - 200, - 'Complexity', - 'Spatial Index', - 'https://www.brentozar.com/blitzcache/spatial-indexes/', - 'Purely informational.') ; - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.index_dml = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 48, - 150, - 'Complexity', - 'Index DML', - 'https://www.brentozar.com/blitzcache/index-dml/', - 'This can cause recompiles and stuff.') ; - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.table_dml = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 49, - 150, - 'Complexity', - 'Table DML', - 'https://www.brentozar.com/blitzcache/table-dml/', - 'This can cause recompiles and stuff.') ; - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.long_running_low_cpu = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 50, - 150, - 'Blocking', - 'Long Running Low CPU', - 'https://www.brentozar.com/blitzcache/long-running-low-cpu/', - 'This can be a sign of blocking, linked servers, or poor client application code (ASYNC_NETWORK_IO).') ; - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.low_cost_high_cpu = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 51, - 150, - 'Complexity', - 'Low Cost Query With High CPU', - 'https://www.brentozar.com/blitzcache/low-cost-high-cpu/', - 'This can be a sign of functions or Dynamic SQL that calls black-box code.') ; - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.stale_stats = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 52, - 150, - 'Statistics', - 'Statistics used have > 100k modifications in the last 7 days', - 'https://www.brentozar.com/blitzcache/stale-statistics/', - 'Ever heard of updating statistics?') ; - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.is_adaptive = 1 - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 53, - 200, - 'Complexity', - 'Adaptive joins', - 'https://www.brentozar.com/blitzcache/adaptive-joins/', - 'This join will sometimes do seeks, and sometimes do scans.') ; - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.is_spool_expensive = 1 - ) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 54, - 150, - 'Indexes', - 'Expensive Index Spool', - 'https://www.brentozar.com/blitzcache/eager-index-spools/', - 'Check operator predicates and output for index definition guidance') ; - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.is_spool_more_rows = 1 - ) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 55, - 150, - 'Indexes', - 'Large Index Row Spool', - 'https://www.brentozar.com/blitzcache/eager-index-spools/', - 'Check operator predicates and output for index definition guidance') ; - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.is_bad_estimate = 1 - ) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 56, - 100, - 'Complexity', - 'Row Estimate Mismatch', - 'https://www.brentozar.com/blitzcache/bad-estimates/', - 'Estimated rows are different from average rows by a factor of 10000. This may indicate a performance problem if mismatches occur regularly') ; - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.is_paul_white_electric = 1 - ) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 57, - 200, - 'Is Paul White Electric?', - 'This query has a Switch operator in it!', - 'https://www.sql.kiwi/2013/06/hello-operator-my-switch-is-bored.html', - 'You should email this query plan to Paul: SQLkiwi at gmail dot com') ; - - IF @v >= 14 OR (@v = 13 AND @build >= 5026) - BEGIN - - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - SELECT - @@SPID, - 997, - 200, - 'Database Level Statistics', - 'The database ' + sa.[Database] + ' last had a stats update on ' + CONVERT(NVARCHAR(10), CONVERT(DATE, MAX(sa.LastUpdate))) + ' and has ' + CONVERT(NVARCHAR(10), AVG(sa.ModificationCount)) + ' modifications on average.' AS [Finding], - 'https://www.brentozar.com/blitzcache/stale-statistics/' AS URL, - 'Consider updating statistics more frequently,' AS [Details] - FROM #stats_agg AS sa - GROUP BY sa.[Database] - HAVING MAX(sa.LastUpdate) <= DATEADD(DAY, -7, SYSDATETIME()) - AND AVG(sa.ModificationCount) >= 100000; - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.is_row_goal = 1 - ) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 58, - 200, - 'Complexity', - 'Row Goals', - 'https://www.brentozar.com/go/rowgoals/', - 'This query had row goals introduced, which can be good or bad, and should be investigated for high read queries.') ; - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.is_big_spills = 1 - ) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 59, - 100, - 'TempDB', - '>500mb Spills', - 'https://www.brentozar.com/blitzcache/tempdb-spills/', - 'This query spills >500mb to tempdb on average. One way or another, this query didn''t get enough memory') ; - - - END; - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.is_mstvf = 1 - ) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 60, - 100, - 'Functions', - 'MSTVFs', - 'https://www.brentozar.com/blitzcache/tvf-join/', - 'Execution plans have been found that join to table valued functions (TVFs). TVFs produce inaccurate estimates of the number of rows returned and can lead to any number of query plan problems.'); - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.is_mm_join = 1 - ) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 61, - 100, - 'Complexity', - 'Many to Many Merge', - 'https://www.brentozar.com/archive/2018/04/many-mysteries-merge-joins/', - 'These use secret worktables that could be doing lots of reads. Occurs when join inputs aren''t known to be unique. Can be really bad when parallel.'); - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.is_nonsargable = 1 - ) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 62, - 50, - 'Non-SARGable queries', - 'non-SARGables', - 'https://www.brentozar.com/blitzcache/non-sargable-predicates/', - 'Looks for intrinsic functions and expressions as predicates, and leading wildcard LIKE searches.'); - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE CompileTime > 5000 - ) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 63, - 100, - 'Complexity', - 'Long Compile Time', - 'https://www.brentozar.com/blitzcache/high-compilers/', - 'Queries are taking >5 seconds to compile. This can be normal for large plans, but be careful if they compile frequently'); - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE CompileCPU > 5000 - ) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 64, - 50, - 'Complexity', - 'High Compile CPU', - 'https://www.brentozar.com/blitzcache/high-compilers/', - 'Queries taking >5 seconds of CPU to compile. If CPU is high and plans like this compile frequently, they may be related'); - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE CompileMemory > 1024 - AND ((CompileMemory) / (1 * CASE WHEN MaxCompileMemory = 0 THEN 1 ELSE MaxCompileMemory END) * 100.) >= 10. - ) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 65, - 50, - 'Complexity', - 'High Compile Memory', - 'https://www.brentozar.com/blitzcache/high-compilers/', - 'Queries taking 10% of Max Compile Memory. If you see high RESOURCE_SEMAPHORE_QUERY_COMPILE waits, these may be related'); - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.select_with_writes = 1 - ) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 66, - 50, - 'Complexity', - 'Selects w/ Writes', - 'https://dba.stackexchange.com/questions/191825/', - 'This is thrown when reads cause writes that are not already flagged as big spills (2016+) or index spools.'); - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.is_table_spool_expensive = 1 - ) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 67, - 150, - 'Expensive Table Spool', - 'You have a table spool, this is usually a sign that queries are doing unnecessary work', - 'https://sqlperformance.com/2019/09/sql-performance/nested-loops-joins-performance-spools', - 'Check for non-SARGable predicates, or a lot of work being done inside a nested loops join') ; - - IF EXISTS (SELECT 1/0 - FROM ##BlitzCacheProcs p - WHERE p.is_table_spool_more_rows = 1 - ) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 68, - 150, - 'Table Spools Many Rows', - 'You have a table spool that spools more rows than the query returns', - 'https://sqlperformance.com/2019/09/sql-performance/nested-loops-joins-performance-spools', - 'Check for non-SARGable predicates, or a lot of work being done inside a nested loops join'); - - IF EXISTS (SELECT 1/0 - FROM #plan_creation p - WHERE (p.percent_24 > 0) - AND SPID = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - SELECT SPID, - 999, - CASE WHEN ISNULL(p.percent_24, 0) > 75 THEN 1 ELSE 254 END AS Priority, - 'Plan Cache Information', - CASE WHEN ISNULL(p.percent_24, 0) > 75 THEN 'Plan Cache Instability' ELSE 'Plan Cache Stability' END AS Finding, - 'https://www.brentozar.com/archive/2018/07/tsql2sday-how-much-plan-cache-history-do-you-have/', - 'You have ' + CONVERT(NVARCHAR(10), ISNULL(p.total_plans, 0)) - + ' total plans in your cache, with ' - + CONVERT(NVARCHAR(10), ISNULL(p.percent_24, 0)) - + '% plans created in the past 24 hours, ' - + CONVERT(NVARCHAR(10), ISNULL(p.percent_4, 0)) - + '% created in the past 4 hours, and ' - + CONVERT(NVARCHAR(10), ISNULL(p.percent_1, 0)) - + '% created in the past 1 hour. ' - + 'When these percentages are high, it may be a sign of memory pressure or plan cache instability.' - FROM #plan_creation p ; - - IF EXISTS (SELECT 1/0 - FROM #plan_usage p - WHERE p.percent_duplicate > 5 - AND spid = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - SELECT spid, - 999, - CASE WHEN ISNULL(p.percent_duplicate, 0) > 75 THEN 1 ELSE 254 END AS Priority, - 'Plan Cache Information', - CASE WHEN ISNULL(p.percent_duplicate, 0) > 75 THEN 'Many Duplicate Plans' ELSE 'Duplicate Plans' END AS Finding, - 'https://www.brentozar.com/archive/2018/03/why-multiple-plans-for-one-query-are-bad/', - 'You have ' + CONVERT(NVARCHAR(10), p.total_plans) - + ' plans in your cache, and ' - + CONVERT(NVARCHAR(10), p.percent_duplicate) - + '% are duplicates with more than 5 entries' - + ', meaning similar queries are generating the same plan repeatedly.' - + ' Forced Parameterization may fix the issue. To find troublemakers, use: EXEC sp_BlitzCache @SortOrder = ''query hash''; ' - FROM #plan_usage AS p ; - - IF EXISTS (SELECT 1/0 - FROM #plan_usage p - WHERE p.percent_single > 5 - AND spid = @@SPID) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - SELECT spid, - 999, - CASE WHEN ISNULL(p.percent_single, 0) > 75 THEN 1 ELSE 254 END AS Priority, - 'Plan Cache Information', - CASE WHEN ISNULL(p.percent_single, 0) > 75 THEN 'Many Single-Use Plans' ELSE 'Single-Use Plans' END AS Finding, - 'https://www.brentozar.com/blitz/single-use-plans-procedure-cache/', - 'You have ' + CONVERT(NVARCHAR(10), p.total_plans) - + ' plans in your cache, and ' - + CONVERT(NVARCHAR(10), p.percent_single) - + '% are single use plans' - + ', meaning SQL Server thinks it''s seeing a lot of "new" queries and creating plans for them.' - + ' Forced Parameterization and/or Optimize For Ad Hoc Workloads may fix the issue.' - + 'To find troublemakers, use: EXEC sp_BlitzCache @SortOrder = ''query hash''; ' - FROM #plan_usage AS p ; - - IF @is_tokenstore_big = 1 - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - SELECT @@SPID, - 69, - 10, - N'Large USERSTORE_TOKENPERM cache: ' + CONVERT(NVARCHAR(11), @user_perm_gb_out) + N'GB', - N'The USERSTORE_TOKENPERM is taking up ' + CONVERT(NVARCHAR(11), @user_perm_percent) - + N'% of the buffer pool, and your plan cache seems to be unstable', - N'https://www.brentozar.com/go/userstore', - N'A growing USERSTORE_TOKENPERM cache can cause the plan cache to clear out' - - IF @v >= 11 - BEGIN - IF EXISTS (SELECT 1/0 - FROM #trace_flags AS tf - WHERE tf.global_trace_flags IS NOT NULL - ) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 1000, - 255, - 'Global Trace Flags Enabled', - 'You have Global Trace Flags enabled on your server', - 'https://www.brentozar.com/blitz/trace-flags-enabled-globally/', - 'You have the following Global Trace Flags enabled: ' + (SELECT TOP 1 tf.global_trace_flags FROM #trace_flags AS tf WHERE tf.global_trace_flags IS NOT NULL)) ; - END; - - IF NOT EXISTS (SELECT 1/0 - FROM ##BlitzCacheResults AS bcr - WHERE bcr.Priority = 2147483646 - ) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 2147483646, - 255, - 'Need more help?' , - 'Paste your plan on the internet!', - 'http://pastetheplan.com', - 'This makes it easy to share plans and post them to Q&A sites like https://dba.stackexchange.com/!') ; - - - - IF NOT EXISTS (SELECT 1/0 - FROM ##BlitzCacheResults AS bcr - WHERE bcr.Priority = 2147483647 - ) - INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (@@SPID, - 2147483647, - 255, - 'Thanks for using sp_BlitzCache!' , - 'From Your Community Volunteers', - 'http://FirstResponderKit.org', - 'We hope you found this tool useful. Current version: ' + @Version + ' released on ' + CONVERT(NVARCHAR(30), @VersionDate) + '.') ; - - END; - - - SELECT Priority, - FindingsGroup, - Finding, - URL, - Details, - CheckID - FROM ##BlitzCacheResults - WHERE SPID = @@SPID - GROUP BY Priority, - FindingsGroup, - Finding, - URL, - Details, - CheckID - ORDER BY Priority ASC, FindingsGroup, Finding, CheckID ASC - OPTION (RECOMPILE); -END; - -IF @Debug = 1 - BEGIN - - SELECT '##BlitzCacheResults' AS table_name, * - FROM ##BlitzCacheResults - OPTION ( RECOMPILE ); - - SELECT '##BlitzCacheProcs' AS table_name, * - FROM ##BlitzCacheProcs - OPTION ( RECOMPILE ); - - SELECT '#statements' AS table_name, * - FROM #statements AS s - OPTION (RECOMPILE); - - SELECT '#query_plan' AS table_name, * - FROM #query_plan AS qp - OPTION (RECOMPILE); - - SELECT '#relop' AS table_name, * - FROM #relop AS r - OPTION (RECOMPILE); - - SELECT '#only_query_hashes' AS table_name, * - FROM #only_query_hashes - OPTION ( RECOMPILE ); - - SELECT '#ignore_query_hashes' AS table_name, * - FROM #ignore_query_hashes - OPTION ( RECOMPILE ); - - SELECT '#only_sql_handles' AS table_name, * - FROM #only_sql_handles - OPTION ( RECOMPILE ); - - SELECT '#ignore_sql_handles' AS table_name, * - FROM #ignore_sql_handles - OPTION ( RECOMPILE ); - - SELECT '#p' AS table_name, * - FROM #p - OPTION ( RECOMPILE ); - - SELECT '#checkversion' AS table_name, * - FROM #checkversion - OPTION ( RECOMPILE ); - - SELECT '#configuration' AS table_name, * - FROM #configuration - OPTION ( RECOMPILE ); - - SELECT '#stored_proc_info' AS table_name, * - FROM #stored_proc_info - OPTION ( RECOMPILE ); - - SELECT '#conversion_info' AS table_name, * - FROM #conversion_info AS ci - OPTION ( RECOMPILE ); - - SELECT '#variable_info' AS table_name, * - FROM #variable_info AS vi - OPTION ( RECOMPILE ); - - SELECT '#missing_index_xml' AS table_name, * - FROM #missing_index_xml AS mix - OPTION ( RECOMPILE ); - - SELECT '#missing_index_schema' AS table_name, * - FROM #missing_index_schema AS mis - OPTION ( RECOMPILE ); - - SELECT '#missing_index_usage' AS table_name, * - FROM #missing_index_usage AS miu - OPTION ( RECOMPILE ); - - SELECT '#missing_index_detail' AS table_name, * - FROM #missing_index_detail AS mid - OPTION ( RECOMPILE ); - - SELECT '#missing_index_pretty' AS table_name, * - FROM #missing_index_pretty AS mip - OPTION ( RECOMPILE ); - - SELECT '#plan_creation' AS table_name, * - FROM #plan_creation - OPTION ( RECOMPILE ); - - SELECT '#plan_cost' AS table_name, * - FROM #plan_cost - OPTION ( RECOMPILE ); - - SELECT '#proc_costs' AS table_name, * - FROM #proc_costs - OPTION ( RECOMPILE ); - - SELECT '#stats_agg' AS table_name, * - FROM #stats_agg - OPTION ( RECOMPILE ); - - SELECT '#trace_flags' AS table_name, * - FROM #trace_flags - OPTION ( RECOMPILE ); - - SELECT '#plan_usage' AS table_name, * - FROM #plan_usage - OPTION ( RECOMPILE ); - - END; - - IF @OutputTableName IS NOT NULL - --Allow for output to ##DB so don't check for DB or schema name here - GOTO OutputResultsToTable; -RETURN; --Avoid going into the AllSort GOTO - -/*Begin code to sort by all*/ -AllSorts: -RAISERROR('Beginning all sort loop', 0, 1) WITH NOWAIT; - - -IF ( - @Top > 10 - AND @SkipAnalysis = 0 - AND @BringThePain = 0 - ) - BEGIN - RAISERROR( - ' - You''ve chosen a value greater than 10 to sort the whole plan cache by. - That can take a long time and harm performance. - Please choose a number <= 10, or set @BringThePain = 1 to signify you understand this might be a bad idea. - ', 0, 1) WITH NOWAIT; - RETURN; - END; - - -IF OBJECT_ID('tempdb..#checkversion_allsort') IS NULL - BEGIN - CREATE TABLE #checkversion_allsort - ( - version NVARCHAR(128), - common_version AS SUBSTRING(version, 1, CHARINDEX('.', version) + 1), - major AS PARSENAME(CONVERT(VARCHAR(32), version), 4), - minor AS PARSENAME(CONVERT(VARCHAR(32), version), 3), - build AS PARSENAME(CONVERT(VARCHAR(32), version), 2), - revision AS PARSENAME(CONVERT(VARCHAR(32), version), 1) - ); - - INSERT INTO #checkversion_allsort - (version) - SELECT CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)) - OPTION ( RECOMPILE ); - END; - - -SELECT @v = common_version, - @build = build -FROM #checkversion_allsort -OPTION ( RECOMPILE ); - -IF OBJECT_ID('tempdb.. #bou_allsort') IS NULL - BEGIN - CREATE TABLE #bou_allsort - ( - Id INT IDENTITY(1, 1), - DatabaseName NVARCHAR(128), - Cost FLOAT, - QueryText NVARCHAR(MAX), - QueryType NVARCHAR(258), - Warnings VARCHAR(MAX), - QueryPlan XML, - missing_indexes XML, - implicit_conversion_info XML, - cached_execution_parameters XML, - ExecutionCount NVARCHAR(30), - ExecutionsPerMinute MONEY, - ExecutionWeight MONEY, - TotalCPU NVARCHAR(30), - AverageCPU NVARCHAR(30), - CPUWeight MONEY, - TotalDuration NVARCHAR(30), - AverageDuration NVARCHAR(30), - DurationWeight MONEY, - TotalReads NVARCHAR(30), - AverageReads NVARCHAR(30), - ReadWeight MONEY, - TotalWrites NVARCHAR(30), - AverageWrites NVARCHAR(30), - WriteWeight MONEY, - AverageReturnedRows MONEY, - MinGrantKB NVARCHAR(30), - MaxGrantKB NVARCHAR(30), - MinUsedGrantKB NVARCHAR(30), - MaxUsedGrantKB NVARCHAR(30), - AvgMaxMemoryGrant MONEY, - MinSpills NVARCHAR(30), - MaxSpills NVARCHAR(30), - TotalSpills NVARCHAR(30), - AvgSpills MONEY, - PlanCreationTime DATETIME, - LastExecutionTime DATETIME, - LastCompletionTime DATETIME, - PlanHandle VARBINARY(64), - SqlHandle VARBINARY(64), - SetOptions VARCHAR(MAX), - QueryHash BINARY(8), - PlanGenerationNum NVARCHAR(30), - RemovePlanHandleFromCache NVARCHAR(200), - Pattern NVARCHAR(20) - ); - END; - - -IF @SortOrder = 'all' -BEGIN -RAISERROR('Beginning for ALL', 0, 1) WITH NOWAIT; -SET @AllSortSql += N' - DECLARE @ISH NVARCHAR(MAX) = N'''' - - INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, - TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, - ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) - - EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''cpu'', - @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; - - UPDATE #bou_allsort SET Pattern = ''cpu'' WHERE Pattern IS NULL OPTION(RECOMPILE); - - SELECT TOP 1 @ISH = STUFF((SELECT DISTINCT N'','' + CONVERT(NVARCHAR(MAX),b2.SqlHandle, 1) FROM #bou_allsort AS b2 FOR XML PATH(N''''), TYPE).value(N''.[1]'', N''NVARCHAR(MAX)''), 1, 1, N'''') OPTION(RECOMPILE); - - INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, - TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, - ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) - - EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''reads'', @IgnoreSqlHandles = @ISH, - @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; - - UPDATE #bou_allsort SET Pattern = ''reads'' WHERE Pattern IS NULL OPTION(RECOMPILE); - - SELECT TOP 1 @ISH = STUFF((SELECT DISTINCT N'','' + CONVERT(NVARCHAR(MAX),b2.SqlHandle, 1) FROM #bou_allsort AS b2 FOR XML PATH(N''''), TYPE).value(N''.[1]'', N''NVARCHAR(MAX)''), 1, 1, N'''') OPTION(RECOMPILE); - - INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, - TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, - ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) - - EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''writes'', @IgnoreSqlHandles = @ISH, - @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; - - UPDATE #bou_allsort SET Pattern = ''writes'' WHERE Pattern IS NULL OPTION(RECOMPILE); - - SELECT TOP 1 @ISH = STUFF((SELECT DISTINCT N'','' + CONVERT(NVARCHAR(MAX),b2.SqlHandle, 1) FROM #bou_allsort AS b2 FOR XML PATH(N''''), TYPE).value(N''.[1]'', N''NVARCHAR(MAX)''), 1, 1, N'''') OPTION(RECOMPILE); - - INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, - TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, - ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) - - EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''duration'', @IgnoreSqlHandles = @ISH, - @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; - - UPDATE #bou_allsort SET Pattern = ''duration'' WHERE Pattern IS NULL OPTION(RECOMPILE); - - SELECT TOP 1 @ISH = STUFF((SELECT DISTINCT N'','' + CONVERT(NVARCHAR(MAX),b2.SqlHandle, 1) FROM #bou_allsort AS b2 FOR XML PATH(N''''), TYPE).value(N''.[1]'', N''NVARCHAR(MAX)''), 1, 1, N'''') OPTION(RECOMPILE); - - INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, - TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, - ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) - - EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''executions'', @IgnoreSqlHandles = @ISH, - @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; - - UPDATE #bou_allsort SET Pattern = ''executions'' WHERE Pattern IS NULL OPTION(RECOMPILE); - - '; - - IF @VersionShowsMemoryGrants = 0 - BEGIN - IF @ExportToExcel = 1 - BEGIN - SET @AllSortSql += N' UPDATE #bou_allsort - SET - QueryPlan = NULL, - implicit_conversion_info = NULL, - cached_execution_parameters = NULL, - missing_indexes = NULL - OPTION (RECOMPILE); - - UPDATE ##BlitzCacheProcs - SET QueryText = SUBSTRING(REPLACE(REPLACE(REPLACE(LTRIM(RTRIM(QueryText)),'' '',''<>''),''><'',''''),''<>'','' ''), 1, 32000) - OPTION(RECOMPILE);'; - END; - - END; - - IF @VersionShowsMemoryGrants = 1 - BEGIN - SET @AllSortSql += N' SELECT TOP 1 @ISH = STUFF((SELECT DISTINCT N'','' + CONVERT(NVARCHAR(MAX),b2.SqlHandle, 1) FROM #bou_allsort AS b2 FOR XML PATH(N''''), TYPE).value(N''.[1]'', N''NVARCHAR(MAX)''), 1, 1, N'''') OPTION(RECOMPILE); - - INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, - TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, - ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) - - EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''memory grant'', @IgnoreSqlHandles = @ISH, - @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; - - UPDATE #bou_allsort SET Pattern = ''memory grant'' WHERE Pattern IS NULL OPTION(RECOMPILE);'; - IF @ExportToExcel = 1 - BEGIN - SET @AllSortSql += N' UPDATE #bou_allsort - SET - QueryPlan = NULL, - implicit_conversion_info = NULL, - cached_execution_parameters = NULL, - missing_indexes = NULL - OPTION (RECOMPILE); - - UPDATE ##BlitzCacheProcs - SET QueryText = SUBSTRING(REPLACE(REPLACE(REPLACE(LTRIM(RTRIM(QueryText)),'' '',''<>''),''><'',''''),''<>'','' ''), 1, 32000) - OPTION(RECOMPILE);'; - END; - - END; - - IF @VersionShowsSpills = 0 - BEGIN - IF @ExportToExcel = 1 - BEGIN - SET @AllSortSql += N' UPDATE #bou_allsort - SET - QueryPlan = NULL, - implicit_conversion_info = NULL, - cached_execution_parameters = NULL, - missing_indexes = NULL - OPTION (RECOMPILE); - - UPDATE ##BlitzCacheProcs - SET QueryText = SUBSTRING(REPLACE(REPLACE(REPLACE(LTRIM(RTRIM(QueryText)),'' '',''<>''),''><'',''''),''<>'','' ''), 1, 32000) - OPTION(RECOMPILE);'; - END; - - END; - - IF @VersionShowsSpills = 1 - BEGIN - SET @AllSortSql += N' SELECT TOP 1 @ISH = STUFF((SELECT DISTINCT N'','' + CONVERT(NVARCHAR(MAX),b2.SqlHandle, 1) FROM #bou_allsort AS b2 FOR XML PATH(N''''), TYPE).value(N''.[1]'', N''NVARCHAR(MAX)''), 1, 1, N'''') OPTION(RECOMPILE); - - INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, - TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, - ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) - - EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''spills'', @IgnoreSqlHandles = @ISH, - @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; - - UPDATE #bou_allsort SET Pattern = ''spills'' WHERE Pattern IS NULL OPTION(RECOMPILE);'; - IF @ExportToExcel = 1 - BEGIN - SET @AllSortSql += N' UPDATE #bou_allsort - SET - QueryPlan = NULL, - implicit_conversion_info = NULL, - cached_execution_parameters = NULL, - missing_indexes = NULL - OPTION (RECOMPILE); - - UPDATE ##BlitzCacheProcs - SET QueryText = SUBSTRING(REPLACE(REPLACE(REPLACE(LTRIM(RTRIM(QueryText)),'' '',''<>''),''><'',''''),''<>'','' ''), 1, 32000) - OPTION(RECOMPILE);'; - END; - - END; - - IF(@OutputType <> 'NONE') - BEGIN - SET @AllSortSql += N' SELECT DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters,ExecutionCount, ExecutionsPerMinute, ExecutionWeight, - TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, - ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache, Pattern - FROM #bou_allsort - ORDER BY Id - OPTION(RECOMPILE); '; - END; -END; - - -IF @SortOrder = 'all avg' -BEGIN -RAISERROR('Beginning for ALL AVG', 0, 1) WITH NOWAIT; -SET @AllSortSql += N' - DECLARE @ISH NVARCHAR(MAX) = N'''' - - INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, - TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, - ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) - - EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''avg cpu'', - @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; - - UPDATE #bou_allsort SET Pattern = ''avg cpu'' WHERE Pattern IS NULL OPTION(RECOMPILE); - - SELECT TOP 1 @ISH = STUFF((SELECT DISTINCT N'','' + CONVERT(NVARCHAR(MAX),b2.SqlHandle, 1) FROM #bou_allsort AS b2 FOR XML PATH(N''''), TYPE).value(N''.[1]'', N''NVARCHAR(MAX)''), 1, 1, N'''') OPTION(RECOMPILE); - - INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, - TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, - ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) - - EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''avg reads'', @IgnoreSqlHandles = @ISH, - @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; - - UPDATE #bou_allsort SET Pattern = ''avg reads'' WHERE Pattern IS NULL OPTION(RECOMPILE); - - SELECT TOP 1 @ISH = STUFF((SELECT DISTINCT N'','' + CONVERT(NVARCHAR(MAX),b2.SqlHandle, 1) FROM #bou_allsort AS b2 FOR XML PATH(N''''), TYPE).value(N''.[1]'', N''NVARCHAR(MAX)''), 1, 1, N'''') OPTION(RECOMPILE); - - INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, - TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, - ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) - - EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''avg writes'', @IgnoreSqlHandles = @ISH, - @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; - - UPDATE #bou_allsort SET Pattern = ''avg writes'' WHERE Pattern IS NULL OPTION(RECOMPILE); - - SELECT TOP 1 @ISH = STUFF((SELECT DISTINCT N'','' + CONVERT(NVARCHAR(MAX),b2.SqlHandle, 1) FROM #bou_allsort AS b2 FOR XML PATH(N''''), TYPE).value(N''.[1]'', N''NVARCHAR(MAX)''), 1, 1, N'''') OPTION(RECOMPILE); - - INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, - TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, - ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) - - EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''avg duration'', @IgnoreSqlHandles = @ISH, - @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; - - UPDATE #bou_allsort SET Pattern = ''avg duration'' WHERE Pattern IS NULL OPTION(RECOMPILE); - - SELECT TOP 1 @ISH = STUFF((SELECT DISTINCT N'','' + CONVERT(NVARCHAR(MAX),b2.SqlHandle, 1) FROM #bou_allsort AS b2 FOR XML PATH(N''''), TYPE).value(N''.[1]'', N''NVARCHAR(MAX)''), 1, 1, N'''') OPTION(RECOMPILE); - - INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, - TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, - ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) - - EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''avg executions'', @IgnoreSqlHandles = @ISH, - @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; - - UPDATE #bou_allsort SET Pattern = ''avg executions'' WHERE Pattern IS NULL OPTION(RECOMPILE); - - '; - - IF @VersionShowsMemoryGrants = 0 - BEGIN - IF @ExportToExcel = 1 - BEGIN - SET @AllSortSql += N' UPDATE #bou_allsort - SET - QueryPlan = NULL, - implicit_conversion_info = NULL, - cached_execution_parameters = NULL, - missing_indexes = NULL - OPTION (RECOMPILE); - - UPDATE ##BlitzCacheProcs - SET QueryText = SUBSTRING(REPLACE(REPLACE(REPLACE(LTRIM(RTRIM(QueryText)),'' '',''<>''),''><'',''''),''<>'','' ''), 1, 32000) - OPTION(RECOMPILE);'; - END; - - END; - - IF @VersionShowsMemoryGrants = 1 - BEGIN - SET @AllSortSql += N' SELECT TOP 1 @ISH = STUFF((SELECT DISTINCT N'','' + CONVERT(NVARCHAR(MAX),b2.SqlHandle, 1) FROM #bou_allsort AS b2 FOR XML PATH(N''''), TYPE).value(N''.[1]'', N''NVARCHAR(MAX)''), 1, 1, N'''') OPTION(RECOMPILE); - - INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, - TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, - ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) - - EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''avg memory grant'', @IgnoreSqlHandles = @ISH, - @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; - - UPDATE #bou_allsort SET Pattern = ''avg memory grant'' WHERE Pattern IS NULL OPTION(RECOMPILE);'; - IF @ExportToExcel = 1 - BEGIN - SET @AllSortSql += N' UPDATE #bou_allsort - SET - QueryPlan = NULL, - implicit_conversion_info = NULL, - cached_execution_parameters = NULL, - missing_indexes = NULL - OPTION (RECOMPILE); - - UPDATE ##BlitzCacheProcs - SET QueryText = SUBSTRING(REPLACE(REPLACE(REPLACE(LTRIM(RTRIM(QueryText)),'' '',''<>''),''><'',''''),''<>'','' ''), 1, 32000) - OPTION(RECOMPILE);'; - END; - - END; - - IF @VersionShowsSpills = 0 - BEGIN - IF @ExportToExcel = 1 - BEGIN - SET @AllSortSql += N' UPDATE #bou_allsort - SET - QueryPlan = NULL, - implicit_conversion_info = NULL, - cached_execution_parameters = NULL, - missing_indexes = NULL - OPTION (RECOMPILE); - - UPDATE ##BlitzCacheProcs - SET QueryText = SUBSTRING(REPLACE(REPLACE(REPLACE(LTRIM(RTRIM(QueryText)),'' '',''<>''),''><'',''''),''<>'','' ''), 1, 32000) - OPTION(RECOMPILE);'; - END; - - END; - - IF @VersionShowsSpills = 1 - BEGIN - SET @AllSortSql += N' SELECT TOP 1 @ISH = STUFF((SELECT DISTINCT N'','' + CONVERT(NVARCHAR(MAX),b2.SqlHandle, 1) FROM #bou_allsort AS b2 FOR XML PATH(N''''), TYPE).value(N''.[1]'', N''NVARCHAR(MAX)''), 1, 1, N'''') OPTION(RECOMPILE); - - INSERT #bou_allsort ( DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters, ExecutionCount, ExecutionsPerMinute, ExecutionWeight, - TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, - ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache ) - - EXEC sp_BlitzCache @ExpertMode = 0, @HideSummary = 1, @Top = @i_Top, @SortOrder = ''avg spills'', @IgnoreSqlHandles = @ISH, - @DatabaseName = @i_DatabaseName, @SkipAnalysis = @i_SkipAnalysis, @OutputDatabaseName = @i_OutputDatabaseName, @OutputSchemaName = @i_OutputSchemaName, @OutputTableName = @i_OutputTableName, @CheckDateOverride = @i_CheckDateOverride, @MinutesBack = @i_MinutesBack WITH RECOMPILE; - - UPDATE #bou_allsort SET Pattern = ''avg spills'' WHERE Pattern IS NULL OPTION(RECOMPILE);'; - IF @ExportToExcel = 1 - BEGIN - SET @AllSortSql += N' UPDATE #bou_allsort - SET - QueryPlan = NULL, - implicit_conversion_info = NULL, - cached_execution_parameters = NULL, - missing_indexes = NULL - OPTION (RECOMPILE); - - UPDATE ##BlitzCacheProcs - SET QueryText = SUBSTRING(REPLACE(REPLACE(REPLACE(LTRIM(RTRIM(QueryText)),'' '',''<>''),''><'',''''),''<>'','' ''), 1, 32000) - OPTION(RECOMPILE);'; - END; - - END; - - IF(@OutputType <> 'NONE') - BEGIN - SET @AllSortSql += N' SELECT DatabaseName, Cost, QueryText, QueryType, Warnings, QueryPlan, missing_indexes, implicit_conversion_info, cached_execution_parameters,ExecutionCount, ExecutionsPerMinute, ExecutionWeight, - TotalCPU, AverageCPU, CPUWeight, TotalDuration, AverageDuration, DurationWeight, TotalReads, AverageReads, - ReadWeight, TotalWrites, AverageWrites, WriteWeight, AverageReturnedRows, MinGrantKB, MaxGrantKB, MinUsedGrantKB, - MaxUsedGrantKB, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, SetOptions, QueryHash, PlanGenerationNum, RemovePlanHandleFromCache, Pattern - FROM #bou_allsort - ORDER BY Id - OPTION(RECOMPILE); '; - END; -END; - - IF @Debug = 1 - BEGIN - PRINT SUBSTRING(@AllSortSql, 0, 4000); - PRINT SUBSTRING(@AllSortSql, 4000, 8000); - PRINT SUBSTRING(@AllSortSql, 8000, 12000); - PRINT SUBSTRING(@AllSortSql, 12000, 16000); - PRINT SUBSTRING(@AllSortSql, 16000, 20000); - PRINT SUBSTRING(@AllSortSql, 20000, 24000); - PRINT SUBSTRING(@AllSortSql, 24000, 28000); - PRINT SUBSTRING(@AllSortSql, 28000, 32000); - PRINT SUBSTRING(@AllSortSql, 32000, 36000); - PRINT SUBSTRING(@AllSortSql, 36000, 40000); - END; - - EXEC sys.sp_executesql @stmt = @AllSortSql, @params = N'@i_DatabaseName NVARCHAR(128), @i_Top INT, @i_SkipAnalysis BIT, @i_OutputDatabaseName NVARCHAR(258), @i_OutputSchemaName NVARCHAR(258), @i_OutputTableName NVARCHAR(258), @i_CheckDateOverride DATETIMEOFFSET, @i_MinutesBack INT ', - @i_DatabaseName = @DatabaseName, @i_Top = @Top, @i_SkipAnalysis = @SkipAnalysis, @i_OutputDatabaseName = @OutputDatabaseName, @i_OutputSchemaName = @OutputSchemaName, @i_OutputTableName = @OutputTableName, @i_CheckDateOverride = @CheckDateOverride, @i_MinutesBack = @MinutesBack; - -/* Avoid going into OutputResultsToTable - ... otherwise the last result (e.g. spills) would be recorded twice into the output table. -*/ -RETURN; - -/*End of AllSort section*/ - - -/*Begin code to write results to table */ -OutputResultsToTable: - -RAISERROR('Writing results to table.', 0, 1) WITH NOWAIT; - -SELECT @OutputServerName = QUOTENAME(@OutputServerName), - @OutputDatabaseName = QUOTENAME(@OutputDatabaseName), - @OutputSchemaName = QUOTENAME(@OutputSchemaName), - @OutputTableName = QUOTENAME(@OutputTableName); - -/* Checks if @OutputServerName is populated with a valid linked server, and that the database name specified is valid */ -DECLARE @ValidOutputServer BIT; -DECLARE @ValidOutputLocation BIT; -DECLARE @LinkedServerDBCheck NVARCHAR(2000); -DECLARE @ValidLinkedServerDB INT; -DECLARE @tmpdbchk table (cnt int); -IF @OutputServerName IS NOT NULL - BEGIN - - IF @Debug IN (1, 2) RAISERROR('Outputting to a remote server.', 0, 1) WITH NOWAIT; - - IF EXISTS (SELECT server_id FROM sys.servers WHERE QUOTENAME([name]) = @OutputServerName) - BEGIN - SET @LinkedServerDBCheck = 'SELECT 1 WHERE EXISTS (SELECT * FROM '+@OutputServerName+'.master.sys.databases WHERE QUOTENAME([name]) = '''+@OutputDatabaseName+''')'; - INSERT INTO @tmpdbchk EXEC sys.sp_executesql @LinkedServerDBCheck; - SET @ValidLinkedServerDB = (SELECT COUNT(*) FROM @tmpdbchk); - IF (@ValidLinkedServerDB > 0) - BEGIN - SET @ValidOutputServer = 1; - SET @ValidOutputLocation = 1; - END; - ELSE - RAISERROR('The specified database was not found on the output server', 16, 0); - END; - ELSE - BEGIN - RAISERROR('The specified output server was not found', 16, 0); - END; - END; -ELSE - BEGIN - IF @OutputDatabaseName IS NOT NULL - AND @OutputSchemaName IS NOT NULL - AND @OutputTableName IS NOT NULL - AND EXISTS ( SELECT * - FROM sys.databases - WHERE QUOTENAME([name]) = @OutputDatabaseName) - BEGIN - SET @ValidOutputLocation = 1; - END; - ELSE IF @OutputDatabaseName IS NOT NULL - AND @OutputSchemaName IS NOT NULL - AND @OutputTableName IS NOT NULL - AND NOT EXISTS ( SELECT * - FROM sys.databases - WHERE QUOTENAME([name]) = @OutputDatabaseName) - BEGIN - RAISERROR('The specified output database was not found on this server', 16, 0); - END; - ELSE - BEGIN - SET @ValidOutputLocation = 0; - END; - END; - - /* @OutputTableName lets us export the results to a permanent table */ - DECLARE @StringToExecute NVARCHAR(MAX) = N'' ; - - IF @ValidOutputLocation = 1 - BEGIN - SET @StringToExecute = N'USE ' - + @OutputDatabaseName - + N'; IF EXISTS(SELECT * FROM ' - + @OutputDatabaseName - + N'.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' - + @OutputSchemaName - + N''') AND NOT EXISTS (SELECT * FROM ' - + @OutputDatabaseName - + N'.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''' - + @OutputSchemaName + N''' AND QUOTENAME(TABLE_NAME) = ''' - + @OutputTableName + N''') CREATE TABLE ' - + @OutputSchemaName + N'.' - + @OutputTableName - + CONVERT - ( - nvarchar(MAX), - N'(ID bigint NOT NULL IDENTITY(1,1), - ServerName NVARCHAR(258), - CheckDate DATETIMEOFFSET, - Version NVARCHAR(258), - QueryType NVARCHAR(258), - Warnings varchar(max), - DatabaseName sysname, - SerialDesiredMemory float, - SerialRequiredMemory float, - AverageCPU bigint, - TotalCPU bigint, - PercentCPUByType money, - CPUWeight money, - AverageDuration bigint, - TotalDuration bigint, - DurationWeight money, - PercentDurationByType money, - AverageReads bigint, - TotalReads bigint, - ReadWeight money, - PercentReadsByType money, - AverageWrites bigint, - TotalWrites bigint, - WriteWeight money, - PercentWritesByType money, - ExecutionCount bigint, - ExecutionWeight money, - PercentExecutionsByType money, - ExecutionsPerMinute money, - PlanCreationTime datetime,' + N' - PlanCreationTimeHours AS DATEDIFF(HOUR,CONVERT(DATETIMEOFFSET(7),[PlanCreationTime]),[CheckDate]), - LastExecutionTime datetime, - LastCompletionTime datetime, - PlanHandle varbinary(64), - [Remove Plan Handle From Cache] AS - CASE WHEN [PlanHandle] IS NOT NULL - THEN ''DBCC FREEPROCCACHE ('' + CONVERT(VARCHAR(128), [PlanHandle], 1) + '');'' - ELSE ''N/A'' END, - SqlHandle varbinary(64), - [Remove SQL Handle From Cache] AS - CASE WHEN [SqlHandle] IS NOT NULL - THEN ''DBCC FREEPROCCACHE ('' + CONVERT(VARCHAR(128), [SqlHandle], 1) + '');'' - ELSE ''N/A'' END, - [SQL Handle More Info] AS - CASE WHEN [SqlHandle] IS NOT NULL - THEN ''EXEC sp_BlitzCache @OnlySqlHandles = '''''' + CONVERT(VARCHAR(128), [SqlHandle], 1) + ''''''; '' - ELSE ''N/A'' END, - QueryHash binary(8), - [Query Hash More Info] AS - CASE WHEN [QueryHash] IS NOT NULL - THEN ''EXEC sp_BlitzCache @OnlyQueryHashes = '''''' + CONVERT(VARCHAR(32), [QueryHash], 1) + ''''''; '' - ELSE ''N/A'' END, - QueryPlanHash binary(8), - StatementStartOffset int, - StatementEndOffset int, - PlanGenerationNum bigint, - MinReturnedRows bigint, - MaxReturnedRows bigint, - AverageReturnedRows money, - TotalReturnedRows bigint, - QueryText nvarchar(max), - QueryPlan xml, - NumberOfPlans int, - NumberOfDistinctPlans int, - MinGrantKB BIGINT, - MaxGrantKB BIGINT, - MinUsedGrantKB BIGINT, - MaxUsedGrantKB BIGINT, - PercentMemoryGrantUsed MONEY, - AvgMaxMemoryGrant MONEY, - MinSpills BIGINT, - MaxSpills BIGINT, - TotalSpills BIGINT, - AvgSpills MONEY, - QueryPlanCost FLOAT, - Pattern NVARCHAR(20), - JoinKey AS ServerName + Cast(CheckDate AS NVARCHAR(50)), - CONSTRAINT [PK_' + REPLACE(REPLACE(@OutputTableName,N'[',N''),N']',N'') + N'] PRIMARY KEY CLUSTERED(ID ASC));' - ); - - SET @StringToExecute += N'IF EXISTS(SELECT * FROM ' - +@OutputDatabaseName - +N'.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' - +@OutputSchemaName - +N''') AND EXISTS (SELECT * FROM ' - +@OutputDatabaseName+ - N'.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''' - +@OutputSchemaName - +N''' AND QUOTENAME(TABLE_NAME) = ''' - +@OutputTableName - +N''') AND EXISTS (SELECT * FROM ' - +@OutputDatabaseName+ - N'.sys.computed_columns WHERE [name] = N''PlanCreationTimeHours'' AND QUOTENAME(OBJECT_NAME(object_id)) = N''' - +@OutputTableName - +N''' AND [definition] = N''(datediff(hour,[PlanCreationTime],sysdatetime()))'') -BEGIN - RAISERROR(''We noticed that you are running an old computed column definition for PlanCreationTimeHours, fixing that now'',0,0) WITH NOWAIT; - ALTER TABLE '+@OutputDatabaseName+N'.'+@OutputSchemaName+N'.'+@OutputTableName+N' DROP COLUMN [PlanCreationTimeHours]; - ALTER TABLE '+@OutputDatabaseName+N'.'+@OutputSchemaName+N'.'+@OutputTableName+N' ADD [PlanCreationTimeHours] AS DATEDIFF(HOUR,CONVERT(DATETIMEOFFSET(7),[PlanCreationTime]),[CheckDate]); -END '; - - IF @ValidOutputServer = 1 - BEGIN - SET @StringToExecute = REPLACE(@StringToExecute,''''+@OutputSchemaName+'''',''''''+@OutputSchemaName+''''''); - SET @StringToExecute = REPLACE(@StringToExecute,''''+@OutputTableName+'''',''''''+@OutputTableName+''''''); - SET @StringToExecute = REPLACE(@StringToExecute,'xml','nvarchar(max)'); - SET @StringToExecute = REPLACE(@StringToExecute,'''DBCC FREEPROCCACHE ('' + CONVERT(VARCHAR(128), [PlanHandle], 1) + '');''','''''DBCC FREEPROCCACHE ('''' + CONVERT(VARCHAR(128), [PlanHandle], 1) + '''');'''''); - SET @StringToExecute = REPLACE(@StringToExecute,'''DBCC FREEPROCCACHE ('' + CONVERT(VARCHAR(128), [SqlHandle], 1) + '');''','''''DBCC FREEPROCCACHE ('''' + CONVERT(VARCHAR(128), [SqlHandle], 1) + '''');'''''); - SET @StringToExecute = REPLACE(@StringToExecute,'''EXEC sp_BlitzCache @OnlySqlHandles = '''''' + CONVERT(VARCHAR(128), [SqlHandle], 1) + ''''''; ''','''''EXEC sp_BlitzCache @OnlySqlHandles = '''''''' + CONVERT(VARCHAR(128), [SqlHandle], 1) + ''''''''; '''''); - SET @StringToExecute = REPLACE(@StringToExecute,'''EXEC sp_BlitzCache @OnlyQueryHashes = '''''' + CONVERT(VARCHAR(32), [QueryHash], 1) + ''''''; ''','''''EXEC sp_BlitzCache @OnlyQueryHashes = '''''''' + CONVERT(VARCHAR(32), [QueryHash], 1) + ''''''''; '''''); - SET @StringToExecute = REPLACE(@StringToExecute,'''N/A''','''''N/A'''''); - - IF @Debug = 1 - BEGIN - PRINT SUBSTRING(@StringToExecute, 0, 4000); - PRINT SUBSTRING(@StringToExecute, 4000, 8000); - PRINT SUBSTRING(@StringToExecute, 8000, 12000); - PRINT SUBSTRING(@StringToExecute, 12000, 16000); - PRINT SUBSTRING(@StringToExecute, 16000, 20000); - PRINT SUBSTRING(@StringToExecute, 20000, 24000); - PRINT SUBSTRING(@StringToExecute, 24000, 28000); - PRINT SUBSTRING(@StringToExecute, 28000, 32000); - PRINT SUBSTRING(@StringToExecute, 32000, 36000); - PRINT SUBSTRING(@StringToExecute, 36000, 40000); - END; - EXEC('EXEC('''+@StringToExecute+''') AT ' + @OutputServerName); - END; - ELSE - BEGIN - IF @Debug = 1 - BEGIN - PRINT SUBSTRING(@StringToExecute, 0, 4000); - PRINT SUBSTRING(@StringToExecute, 4000, 8000); - PRINT SUBSTRING(@StringToExecute, 8000, 12000); - PRINT SUBSTRING(@StringToExecute, 12000, 16000); - PRINT SUBSTRING(@StringToExecute, 16000, 20000); - PRINT SUBSTRING(@StringToExecute, 20000, 24000); - PRINT SUBSTRING(@StringToExecute, 24000, 28000); - PRINT SUBSTRING(@StringToExecute, 28000, 32000); - PRINT SUBSTRING(@StringToExecute, 32000, 36000); - PRINT SUBSTRING(@StringToExecute, 36000, 40000); - END; - EXEC(@StringToExecute); - END; - - /* If the table doesn't have the new LastCompletionTime column, add it. See Github #2377. */ - SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableName; - SET @StringToExecute = N'IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns - WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + ''')) AND name = ''LastCompletionTime'') - ALTER TABLE ' + @ObjectFullName + N' ADD LastCompletionTime DATETIME NULL;'; - IF @ValidOutputServer = 1 - BEGIN - SET @StringToExecute = REPLACE(@StringToExecute,'''LastCompletionTime''','''''LastCompletionTime'''''); - SET @StringToExecute = REPLACE(@StringToExecute,'''' + @ObjectFullName + '''','''''' + @ObjectFullName + ''''''); - EXEC('EXEC('''+@StringToExecute+''') AT ' + @OutputServerName); - END; - ELSE - BEGIN - EXEC(@StringToExecute); - END; - - /* If the table doesn't have the new PlanGenerationNum column, add it. See Github #2514. */ - SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableName; - SET @StringToExecute = N'IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns - WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''PlanGenerationNum'') - ALTER TABLE ' + @ObjectFullName + N' ADD PlanGenerationNum BIGINT NULL;'; - IF @ValidOutputServer = 1 - BEGIN - SET @StringToExecute = REPLACE(@StringToExecute,'''PlanGenerationNum''','''''PlanGenerationNum'''''); - SET @StringToExecute = REPLACE(@StringToExecute,'''' + @ObjectFullName + '''','''''' + @ObjectFullName + ''''''); - EXEC('EXEC('''+@StringToExecute+''') AT ' + @OutputServerName); - END; - ELSE - BEGIN - EXEC(@StringToExecute); - END; - - /* If the table doesn't have the new Pattern column, add it */ - SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableName; - SET @StringToExecute = N'IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns - WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''Pattern'') - ALTER TABLE ' + @ObjectFullName + N' ADD Pattern NVARCHAR(20) NULL;'; - IF @ValidOutputServer = 1 - BEGIN - SET @StringToExecute = REPLACE(@StringToExecute,'''Pattern''','''''Pattern'''''); - SET @StringToExecute = REPLACE(@StringToExecute,'''' + @ObjectFullName + '''','''''' + @ObjectFullName + ''''''); - EXEC('EXEC('''+@StringToExecute+''') AT ' + @OutputServerName); - END; - ELSE - BEGIN - EXEC(@StringToExecute); - END - - IF @CheckDateOverride IS NULL - BEGIN - SET @CheckDateOverride = SYSDATETIMEOFFSET(); - END; - - IF @ValidOutputServer = 1 - BEGIN - SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' - + @OutputServerName + '.' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' - + @OutputSchemaName + ''') INSERT ' - + @OutputServerName + '.' - + @OutputDatabaseName + '.' - + @OutputSchemaName + '.' - + @OutputTableName - + ' (ServerName, CheckDate, Version, QueryType, DatabaseName, AverageCPU, TotalCPU, PercentCPUByType, CPUWeight, AverageDuration, TotalDuration, DurationWeight, PercentDurationByType, AverageReads, TotalReads, ReadWeight, PercentReadsByType, ' - + ' AverageWrites, TotalWrites, WriteWeight, PercentWritesByType, ExecutionCount, ExecutionWeight, PercentExecutionsByType, ' - + ' ExecutionsPerMinute, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, QueryHash, QueryPlanHash, StatementStartOffset, StatementEndOffset, PlanGenerationNum, MinReturnedRows, MaxReturnedRows, AverageReturnedRows, TotalReturnedRows, QueryText, QueryPlan, NumberOfPlans, NumberOfDistinctPlans, Warnings, ' - + ' SerialRequiredMemory, SerialDesiredMemory, MinGrantKB, MaxGrantKB, MinUsedGrantKB, MaxUsedGrantKB, PercentMemoryGrantUsed, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, QueryPlanCost, Pattern ) ' - + 'SELECT TOP (@Top) ' - + QUOTENAME(CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128)), '''') + ', @CheckDateOverride, ' - + QUOTENAME(CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)), '''') + ', ' - + ' QueryType, DatabaseName, AverageCPU, TotalCPU, PercentCPUByType, PercentCPU, AverageDuration, TotalDuration, PercentDuration, PercentDurationByType, AverageReads, TotalReads, PercentReads, PercentReadsByType, ' - + ' AverageWrites, TotalWrites, PercentWrites, PercentWritesByType, ExecutionCount, PercentExecutions, PercentExecutionsByType, ' - + ' ExecutionsPerMinute, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, QueryHash, QueryPlanHash, StatementStartOffset, StatementEndOffset, PlanGenerationNum, MinReturnedRows, MaxReturnedRows, AverageReturnedRows, TotalReturnedRows, QueryText, CAST(QueryPlan AS NVARCHAR(MAX)), NumberOfPlans, NumberOfDistinctPlans, Warnings, ' - + ' SerialRequiredMemory, SerialDesiredMemory, MinGrantKB, MaxGrantKB, MinUsedGrantKB, MaxUsedGrantKB, PercentMemoryGrantUsed, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, QueryPlanCost, Pattern ' - + ' FROM ##BlitzCacheProcs ' - + ' WHERE 1=1 '; - - IF @MinimumExecutionCount IS NOT NULL - BEGIN - SET @StringToExecute += N' AND ExecutionCount >= @MinimumExecutionCount '; - END; - IF @MinutesBack IS NOT NULL - BEGIN - SET @StringToExecute += N' AND LastCompletionTime >= DATEADD(MINUTE, @min_back, GETDATE() ) '; - END; - SET @StringToExecute += N' AND SPID = @@SPID '; - SELECT @StringToExecute += N' ORDER BY ' + CASE @SortOrder WHEN 'cpu' THEN N' TotalCPU ' - WHEN N'reads' THEN N' TotalReads ' - WHEN N'writes' THEN N' TotalWrites ' - WHEN N'duration' THEN N' TotalDuration ' - WHEN N'executions' THEN N' ExecutionCount ' - WHEN N'compiles' THEN N' PlanCreationTime ' - WHEN N'memory grant' THEN N' MaxGrantKB' - WHEN N'spills' THEN N' MaxSpills' - WHEN N'avg cpu' THEN N' AverageCPU' - WHEN N'avg reads' THEN N' AverageReads' - WHEN N'avg writes' THEN N' AverageWrites' - WHEN N'avg duration' THEN N' AverageDuration' - WHEN N'avg executions' THEN N' ExecutionsPerMinute' - WHEN N'avg memory grant' THEN N' AvgMaxMemoryGrant' - WHEN N'avg spills' THEN N' AvgSpills' - WHEN N'unused grant' THEN N' MaxGrantKB - MaxUsedGrantKB' - ELSE N' TotalCPU ' - END + N' DESC '; - SET @StringToExecute += N' OPTION (RECOMPILE) ; '; - - IF @Debug = 1 - BEGIN - PRINT SUBSTRING(@StringToExecute, 1, 4000); - PRINT SUBSTRING(@StringToExecute, 4001, 4000); - PRINT SUBSTRING(@StringToExecute, 8001, 4000); - PRINT SUBSTRING(@StringToExecute, 12001, 4000); - PRINT SUBSTRING(@StringToExecute, 16001, 4000); - PRINT SUBSTRING(@StringToExecute, 20001, 4000); - PRINT SUBSTRING(@StringToExecute, 24001, 4000); - PRINT SUBSTRING(@StringToExecute, 28001, 4000); - PRINT SUBSTRING(@StringToExecute, 32001, 4000); - PRINT SUBSTRING(@StringToExecute, 36001, 4000); - END; - - EXEC sp_executesql @StringToExecute, N'@Top INT, @min_duration INT, @min_back INT, @CheckDateOverride DATETIMEOFFSET, @MinimumExecutionCount INT', @Top, @DurationFilter_i, @MinutesBack, @CheckDateOverride, @MinimumExecutionCount; - END; - ELSE - BEGIN - SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' - + @OutputSchemaName + ''') INSERT ' - + @OutputDatabaseName + '.' - + @OutputSchemaName + '.' - + @OutputTableName - + ' (ServerName, CheckDate, Version, QueryType, DatabaseName, AverageCPU, TotalCPU, PercentCPUByType, CPUWeight, AverageDuration, TotalDuration, DurationWeight, PercentDurationByType, AverageReads, TotalReads, ReadWeight, PercentReadsByType, ' - + ' AverageWrites, TotalWrites, WriteWeight, PercentWritesByType, ExecutionCount, ExecutionWeight, PercentExecutionsByType, ' - + ' ExecutionsPerMinute, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, QueryHash, QueryPlanHash, StatementStartOffset, StatementEndOffset, PlanGenerationNum, MinReturnedRows, MaxReturnedRows, AverageReturnedRows, TotalReturnedRows, QueryText, QueryPlan, NumberOfPlans, NumberOfDistinctPlans, Warnings, ' - + ' SerialRequiredMemory, SerialDesiredMemory, MinGrantKB, MaxGrantKB, MinUsedGrantKB, MaxUsedGrantKB, PercentMemoryGrantUsed, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, QueryPlanCost, Pattern ) ' - + 'SELECT TOP (@Top) ' - + QUOTENAME(CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128)), '''') + ', @CheckDateOverride, ' - + QUOTENAME(CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)), '''') + ', ' - + ' QueryType, DatabaseName, AverageCPU, TotalCPU, PercentCPUByType, PercentCPU, AverageDuration, TotalDuration, PercentDuration, PercentDurationByType, AverageReads, TotalReads, PercentReads, PercentReadsByType, ' - + ' AverageWrites, TotalWrites, PercentWrites, PercentWritesByType, ExecutionCount, PercentExecutions, PercentExecutionsByType, ' - + ' ExecutionsPerMinute, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, QueryHash, QueryPlanHash, StatementStartOffset, StatementEndOffset, PlanGenerationNum, MinReturnedRows, MaxReturnedRows, AverageReturnedRows, TotalReturnedRows, QueryText, QueryPlan, NumberOfPlans, NumberOfDistinctPlans, Warnings, ' - + ' SerialRequiredMemory, SerialDesiredMemory, MinGrantKB, MaxGrantKB, MinUsedGrantKB, MaxUsedGrantKB, PercentMemoryGrantUsed, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, QueryPlanCost, Pattern ' - + ' FROM ##BlitzCacheProcs ' - + ' WHERE 1=1 '; - - IF @MinimumExecutionCount IS NOT NULL - BEGIN - SET @StringToExecute += N' AND ExecutionCount >= @MinimumExecutionCount '; - END; - IF @MinutesBack IS NOT NULL - BEGIN - SET @StringToExecute += N' AND LastCompletionTime >= DATEADD(MINUTE, @min_back, GETDATE() ) '; - END; - SET @StringToExecute += N' AND SPID = @@SPID '; - SELECT @StringToExecute += N' ORDER BY ' + CASE @SortOrder WHEN 'cpu' THEN N' TotalCPU ' - WHEN N'reads' THEN N' TotalReads ' - WHEN N'writes' THEN N' TotalWrites ' - WHEN N'duration' THEN N' TotalDuration ' - WHEN N'executions' THEN N' ExecutionCount ' - WHEN N'compiles' THEN N' PlanCreationTime ' - WHEN N'memory grant' THEN N' MaxGrantKB' - WHEN N'spills' THEN N' MaxSpills' - WHEN N'avg cpu' THEN N' AverageCPU' - WHEN N'avg reads' THEN N' AverageReads' - WHEN N'avg writes' THEN N' AverageWrites' - WHEN N'avg duration' THEN N' AverageDuration' - WHEN N'avg executions' THEN N' ExecutionsPerMinute' - WHEN N'avg memory grant' THEN N' AvgMaxMemoryGrant' - WHEN N'avg spills' THEN N' AvgSpills' - WHEN N'unused grant' THEN N' MaxGrantKB - MaxUsedGrantKB' - ELSE N' TotalCPU ' - END + N' DESC '; - SET @StringToExecute += N' OPTION (RECOMPILE) ; '; - - IF @Debug = 1 - BEGIN - PRINT SUBSTRING(@StringToExecute, 0, 4000); - PRINT SUBSTRING(@StringToExecute, 4000, 8000); - PRINT SUBSTRING(@StringToExecute, 8000, 12000); - PRINT SUBSTRING(@StringToExecute, 12000, 16000); - PRINT SUBSTRING(@StringToExecute, 16000, 20000); - PRINT SUBSTRING(@StringToExecute, 20000, 24000); - PRINT SUBSTRING(@StringToExecute, 24000, 28000); - PRINT SUBSTRING(@StringToExecute, 28000, 32000); - PRINT SUBSTRING(@StringToExecute, 32000, 36000); - PRINT SUBSTRING(@StringToExecute, 36000, 40000); - END; - - EXEC sp_executesql @StringToExecute, N'@Top INT, @min_duration INT, @min_back INT, @CheckDateOverride DATETIMEOFFSET, @MinimumExecutionCount INT', @Top, @DurationFilter_i, @MinutesBack, @CheckDateOverride, @MinimumExecutionCount; - END; - END; - ELSE IF (SUBSTRING(@OutputTableName, 2, 2) = '##') - BEGIN - IF @ValidOutputServer = 1 - BEGIN - RAISERROR('Due to the nature of temporary tables, outputting to a linked server requires a permanent table.', 16, 0); - END; - ELSE IF @OutputTableName IN ('##BlitzCacheProcs','##BlitzCacheResults') - BEGIN - RAISERROR('OutputTableName is a reserved name for this procedure. We only use ##BlitzCacheProcs and ##BlitzCacheResults, please choose another table name.', 16, 0); - END; - ELSE - BEGIN - SET @StringToExecute = N' IF (OBJECT_ID(''tempdb..' - + @OutputTableName - + ''') IS NOT NULL) DROP TABLE ' + @OutputTableName + ';' - + 'CREATE TABLE ' - + @OutputTableName - + ' (ID bigint NOT NULL IDENTITY(1,1), - ServerName NVARCHAR(258), - CheckDate DATETIMEOFFSET, - Version NVARCHAR(258), - QueryType NVARCHAR(258), - Warnings varchar(max), - DatabaseName sysname, - SerialDesiredMemory float, - SerialRequiredMemory float, - AverageCPU bigint, - TotalCPU bigint, - PercentCPUByType money, - CPUWeight money, - AverageDuration bigint, - TotalDuration bigint, - DurationWeight money, - PercentDurationByType money, - AverageReads bigint, - TotalReads bigint, - ReadWeight money, - PercentReadsByType money, - AverageWrites bigint, - TotalWrites bigint, - WriteWeight money, - PercentWritesByType money, - ExecutionCount bigint, - ExecutionWeight money, - PercentExecutionsByType money, - ExecutionsPerMinute money, - PlanCreationTime datetime,' + N' - PlanCreationTimeHours AS DATEDIFF(HOUR, PlanCreationTime, SYSDATETIME()), - LastExecutionTime datetime, - LastCompletionTime datetime, - PlanHandle varbinary(64), - [Remove Plan Handle From Cache] AS - CASE WHEN [PlanHandle] IS NOT NULL - THEN ''DBCC FREEPROCCACHE ('' + CONVERT(VARCHAR(128), [PlanHandle], 1) + '');'' - ELSE ''N/A'' END, - SqlHandle varbinary(64), - [Remove SQL Handle From Cache] AS - CASE WHEN [SqlHandle] IS NOT NULL - THEN ''DBCC FREEPROCCACHE ('' + CONVERT(VARCHAR(128), [SqlHandle], 1) + '');'' - ELSE ''N/A'' END, - [SQL Handle More Info] AS - CASE WHEN [SqlHandle] IS NOT NULL - THEN ''EXEC sp_BlitzCache @OnlySqlHandles = '''''' + CONVERT(VARCHAR(128), [SqlHandle], 1) + ''''''; '' - ELSE ''N/A'' END, - QueryHash binary(8), - [Query Hash More Info] AS - CASE WHEN [QueryHash] IS NOT NULL - THEN ''EXEC sp_BlitzCache @OnlyQueryHashes = '''''' + CONVERT(VARCHAR(32), [QueryHash], 1) + ''''''; '' - ELSE ''N/A'' END, - QueryPlanHash binary(8), - StatementStartOffset int, - StatementEndOffset int, - PlanGenerationNum bigint, - MinReturnedRows bigint, - MaxReturnedRows bigint, - AverageReturnedRows money, - TotalReturnedRows bigint, - QueryText nvarchar(max), - QueryPlan xml, - NumberOfPlans int, - NumberOfDistinctPlans int, - MinGrantKB BIGINT, - MaxGrantKB BIGINT, - MinUsedGrantKB BIGINT, - MaxUsedGrantKB BIGINT, - PercentMemoryGrantUsed MONEY, - AvgMaxMemoryGrant MONEY, - MinSpills BIGINT, - MaxSpills BIGINT, - TotalSpills BIGINT, - AvgSpills MONEY, - QueryPlanCost FLOAT, - Pattern NVARCHAR(20), - JoinKey AS ServerName + Cast(CheckDate AS NVARCHAR(50)), - CONSTRAINT [PK_' + REPLACE(REPLACE(@OutputTableName,'[',''),']','') + '] PRIMARY KEY CLUSTERED(ID ASC));'; - SET @StringToExecute += N' INSERT ' - + @OutputTableName - + ' (ServerName, CheckDate, Version, QueryType, DatabaseName, AverageCPU, TotalCPU, PercentCPUByType, CPUWeight, AverageDuration, TotalDuration, DurationWeight, PercentDurationByType, AverageReads, TotalReads, ReadWeight, PercentReadsByType, ' - + ' AverageWrites, TotalWrites, WriteWeight, PercentWritesByType, ExecutionCount, ExecutionWeight, PercentExecutionsByType, ' - + ' ExecutionsPerMinute, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, QueryHash, QueryPlanHash, StatementStartOffset, StatementEndOffset, PlanGenerationNum, MinReturnedRows, MaxReturnedRows, AverageReturnedRows, TotalReturnedRows, QueryText, QueryPlan, NumberOfPlans, NumberOfDistinctPlans, Warnings, ' - + ' SerialRequiredMemory, SerialDesiredMemory, MinGrantKB, MaxGrantKB, MinUsedGrantKB, MaxUsedGrantKB, PercentMemoryGrantUsed, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, QueryPlanCost, Pattern ) ' - + 'SELECT TOP (@Top) ' - + QUOTENAME(CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128)), '''') + ', @CheckDateOverride, ' - + QUOTENAME(CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)), '''') + ', ' - + ' QueryType, DatabaseName, AverageCPU, TotalCPU, PercentCPUByType, PercentCPU, AverageDuration, TotalDuration, PercentDuration, PercentDurationByType, AverageReads, TotalReads, PercentReads, PercentReadsByType, ' - + ' AverageWrites, TotalWrites, PercentWrites, PercentWritesByType, ExecutionCount, PercentExecutions, PercentExecutionsByType, ' - + ' ExecutionsPerMinute, PlanCreationTime, LastExecutionTime, LastCompletionTime, PlanHandle, SqlHandle, QueryHash, QueryPlanHash, StatementStartOffset, StatementEndOffset, PlanGenerationNum, MinReturnedRows, MaxReturnedRows, AverageReturnedRows, TotalReturnedRows, QueryText, QueryPlan, NumberOfPlans, NumberOfDistinctPlans, Warnings, ' - + ' SerialRequiredMemory, SerialDesiredMemory, MinGrantKB, MaxGrantKB, MinUsedGrantKB, MaxUsedGrantKB, PercentMemoryGrantUsed, AvgMaxMemoryGrant, MinSpills, MaxSpills, TotalSpills, AvgSpills, QueryPlanCost, Pattern ' - + ' FROM ##BlitzCacheProcs ' - + ' WHERE 1=1 '; - - IF @MinimumExecutionCount IS NOT NULL - BEGIN - SET @StringToExecute += N' AND ExecutionCount >= @MinimumExecutionCount '; - END; - - IF @MinutesBack IS NOT NULL - BEGIN - SET @StringToExecute += N' AND LastCompletionTime >= DATEADD(MINUTE, @min_back, GETDATE() ) '; - END; - - SET @StringToExecute += N' AND SPID = @@SPID '; - - SELECT @StringToExecute += N' ORDER BY ' + CASE @SortOrder WHEN 'cpu' THEN N' TotalCPU ' - WHEN N'reads' THEN N' TotalReads ' - WHEN N'writes' THEN N' TotalWrites ' - WHEN N'duration' THEN N' TotalDuration ' - WHEN N'executions' THEN N' ExecutionCount ' - WHEN N'compiles' THEN N' PlanCreationTime ' - WHEN N'memory grant' THEN N' MaxGrantKB' - WHEN N'spills' THEN N' MaxSpills' - WHEN N'avg cpu' THEN N' AverageCPU' - WHEN N'avg reads' THEN N' AverageReads' - WHEN N'avg writes' THEN N' AverageWrites' - WHEN N'avg duration' THEN N' AverageDuration' - WHEN N'avg executions' THEN N' ExecutionsPerMinute' - WHEN N'avg memory grant' THEN N' AvgMaxMemoryGrant' - WHEN N'avg spills' THEN N' AvgSpills' - WHEN N'unused grant' THEN N' MaxGrantKB - MaxUsedGrantKB' - ELSE N' TotalCPU ' - END + N' DESC '; - - SET @StringToExecute += N' OPTION (RECOMPILE) ; '; - - IF @Debug = 1 - BEGIN - PRINT SUBSTRING(@StringToExecute, 0, 4000); - PRINT SUBSTRING(@StringToExecute, 4000, 8000); - PRINT SUBSTRING(@StringToExecute, 8000, 12000); - PRINT SUBSTRING(@StringToExecute, 12000, 16000); - PRINT SUBSTRING(@StringToExecute, 16000, 20000); - PRINT SUBSTRING(@StringToExecute, 20000, 24000); - PRINT SUBSTRING(@StringToExecute, 24000, 28000); - PRINT SUBSTRING(@StringToExecute, 28000, 32000); - PRINT SUBSTRING(@StringToExecute, 32000, 36000); - PRINT SUBSTRING(@StringToExecute, 36000, 40000); - PRINT SUBSTRING(@StringToExecute, 34000, 40000); - END; - EXEC sp_executesql @StringToExecute, N'@Top INT, @min_duration INT, @min_back INT, @CheckDateOverride DATETIMEOFFSET, @MinimumExecutionCount INT', @Top, @DurationFilter_i, @MinutesBack, @CheckDateOverride, @MinimumExecutionCount; - END; - END; - ELSE IF (SUBSTRING(@OutputTableName, 2, 1) = '#') - BEGIN - RAISERROR('Due to the nature of Dymamic SQL, only global (i.e. double pound (##)) temp tables are supported for @OutputTableName', 16, 0); -END; /* End of writing results to table */ - -END; /*Final End*/ - -GO -SET ANSI_NULLS ON; -SET ANSI_PADDING ON; -SET ANSI_WARNINGS ON; -SET ARITHABORT ON; -SET CONCAT_NULL_YIELDS_NULL ON; -SET QUOTED_IDENTIFIER ON; -SET STATISTICS IO OFF; -SET STATISTICS TIME OFF; -GO - -IF OBJECT_ID('dbo.sp_BlitzIndex') IS NULL - EXEC ('CREATE PROCEDURE dbo.sp_BlitzIndex AS RETURN 0;'); -GO - -ALTER PROCEDURE dbo.sp_BlitzIndex - @DatabaseName NVARCHAR(128) = NULL, /*Defaults to current DB if not specified*/ - @SchemaName NVARCHAR(128) = NULL, /*Requires table_name as well.*/ - @TableName NVARCHAR(128) = NULL, /*Requires schema_name as well.*/ - @Mode TINYINT=0, /*0=Diagnose, 1=Summarize, 2=Index Usage Detail, 3=Missing Index Detail, 4=Diagnose Details*/ - /*Note:@Mode doesn't matter if you're specifying schema_name and @TableName.*/ - @Filter TINYINT = 0, /* 0=no filter (default). 1=No low-usage warnings for objects with 0 reads. 2=Only warn for objects >= 500MB */ - /*Note:@Filter doesn't do anything unless @Mode=0*/ - @SkipPartitions BIT = 0, - @SkipStatistics BIT = 1, - @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, - @IgnoreDatabases NVARCHAR(MAX) = NULL, /* Comma-delimited list of databases you want to skip */ - @ThresholdMB INT = 250 /* Number of megabytes that an object must be before we include it in basic results */, - @OutputType VARCHAR(20) = 'TABLE' , - @OutputServerName NVARCHAR(256) = NULL , - @OutputDatabaseName NVARCHAR(256) = NULL , - @OutputSchemaName NVARCHAR(256) = NULL , - @OutputTableName NVARCHAR(256) = NULL , - @IncludeInactiveIndexes BIT = 0 /* Will skip indexes with no reads or writes */, - @ShowAllMissingIndexRequests BIT = 0 /*Will make all missing index requests show up*/, - @ShowPartitionRanges BIT = 0 /* Will add partition range values column to columnstore visualization */, - @SortOrder NVARCHAR(50) = NULL, /* Only affects @Mode = 2. */ - @SortDirection NVARCHAR(4) = 'DESC', /* Only affects @Mode = 2. */ - @Help TINYINT = 0, - @Debug BIT = 0, - @Version VARCHAR(30) = NULL OUTPUT, - @VersionDate DATETIME = NULL OUTPUT, - @VersionCheckMode BIT = 0 -WITH RECOMPILE -AS -SET NOCOUNT ON; -SET STATISTICS XML OFF; -SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - -SELECT @Version = '8.19', @VersionDate = '20240222'; -SET @OutputType = UPPER(@OutputType); - -IF(@VersionCheckMode = 1) -BEGIN - RETURN; -END; - -IF @Help = 1 -BEGIN -PRINT ' -/* -sp_BlitzIndex from http://FirstResponderKit.org - -This script analyzes the design and performance of your indexes. - -To learn more, visit http://FirstResponderKit.org where you can download new -versions for free, watch training videos on how it works, get more info on -the findings, contribute your own code, and more. - -Known limitations of this version: - - Only Microsoft-supported versions of SQL Server. Sorry, 2005 and 2000. - - Index create statements are just to give you a rough idea of the syntax. It includes filters and fillfactor. - -- Example 1: index creates use ONLINE=? instead of ONLINE=ON / ONLINE=OFF. This is because it is important - for the user to understand if it is going to be offline and not just run a script. - -- Example 2: they do not include all the options the index may have been created with (padding, compression - filegroup/partition scheme etc.) - -- (The compression and filegroup index create syntax is not trivial because it is set at the partition - level and is not trivial to code.) - - Does not advise you about data modeling for clustered indexes and primary keys (primarily looks for signs of problems.) - -Unknown limitations of this version: - - We knew them once, but we forgot. - - -MIT License - -Copyright (c) Brent Ozar Unlimited - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -'; -RETURN; -END; /* @Help = 1 */ - -DECLARE @ScriptVersionName NVARCHAR(50); -DECLARE @DaysUptime NUMERIC(23,2); -DECLARE @DatabaseID INT; -DECLARE @ObjectID INT; -DECLARE @dsql NVARCHAR(MAX); -DECLARE @params NVARCHAR(MAX); -DECLARE @msg NVARCHAR(4000); -DECLARE @ErrorSeverity INT; -DECLARE @ErrorState INT; -DECLARE @Rowcount BIGINT; -DECLARE @SQLServerProductVersion NVARCHAR(128); -DECLARE @SQLServerEdition INT; -DECLARE @FilterMB INT; -DECLARE @collation NVARCHAR(256); -DECLARE @NumDatabases INT; -DECLARE @LineFeed NVARCHAR(5); -DECLARE @DaysUptimeInsertValue NVARCHAR(256); -DECLARE @DatabaseToIgnore NVARCHAR(MAX); -DECLARE @ColumnList NVARCHAR(MAX); -DECLARE @ColumnListWithApostrophes NVARCHAR(MAX); -DECLARE @PartitionCount INT; -DECLARE @OptimizeForSequentialKey BIT = 0; -DECLARE @StringToExecute NVARCHAR(MAX); - - -/* Let's get @SortOrder set to lower case here for comparisons later */ -SET @SortOrder = REPLACE(LOWER(@SortOrder), N' ', N'_'); -SET @SortDirection = LOWER(@SortDirection); - -SET @LineFeed = CHAR(13) + CHAR(10); -SELECT @SQLServerProductVersion = CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)); -SELECT @SQLServerEdition =CAST(SERVERPROPERTY('EngineEdition') AS INT); /* We default to online index creates where EngineEdition=3*/ -SET @FilterMB=250; -SELECT @ScriptVersionName = 'sp_BlitzIndex(TM) v' + @Version + ' - ' + DATENAME(MM, @VersionDate) + ' ' + RIGHT('0'+DATENAME(DD, @VersionDate),2) + ', ' + DATENAME(YY, @VersionDate); -SET @IgnoreDatabases = REPLACE(REPLACE(LTRIM(RTRIM(@IgnoreDatabases)), CHAR(10), ''), CHAR(13), ''); - -SELECT - @OptimizeForSequentialKey = - CASE WHEN EXISTS - ( - SELECT - 1/0 - FROM sys.all_columns AS ac - WHERE ac.object_id = OBJECT_ID('sys.indexes') - AND ac.name = N'optimize_for_sequential_key' - ) - THEN 1 - ELSE 0 - END; - -RAISERROR(N'Starting run. %s', 0,1, @ScriptVersionName) WITH NOWAIT; - - -IF(@OutputType NOT IN ('TABLE','NONE')) -BEGIN - RAISERROR('Invalid value for parameter @OutputType. Expected: (TABLE;NONE)',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; - SET @OutputType = 'NONE' -END; - -IF(@OutputType = 'NONE') -BEGIN - - IF ((@OutputServerName IS NOT NULL) AND (@OutputTableName IS NULL OR @OutputSchemaName IS NULL OR @OutputDatabaseName IS NULL)) - BEGIN - RAISERROR('Parameter @OutputServerName is specified, rest of @Output* parameters needs to also be specified',12,1); - RETURN; - END; - - IF(@OutputTableName IS NULL OR @OutputSchemaName IS NULL OR @OutputDatabaseName IS NULL) - BEGIN - RAISERROR('This procedure should be called with a value for @OutputTableName, @OutputSchemaName and @OutputDatabaseName parameters, as @OutputType is set to NONE',12,1); - RETURN; - END; - /* Output is supported for all modes, no reason to not bring pain and output - IF(@BringThePain = 1) - BEGIN - RAISERROR('Incompatible Parameters: @BringThePain set to 1 and @OutputType set to NONE',12,1); - RETURN; - END; - */ - /* Eventually limit by mode - IF(@Mode not in (0,4)) - BEGIN - RAISERROR('Incompatible Parameters: @Mode set to %d and @OutputType set to NONE',12,1,@Mode); - RETURN; - END; - */ -END; - -IF OBJECT_ID('tempdb..#IndexSanity') IS NOT NULL - DROP TABLE #IndexSanity; - -IF OBJECT_ID('tempdb..#IndexPartitionSanity') IS NOT NULL - DROP TABLE #IndexPartitionSanity; - -IF OBJECT_ID('tempdb..#IndexSanitySize') IS NOT NULL - DROP TABLE #IndexSanitySize; - -IF OBJECT_ID('tempdb..#IndexColumns') IS NOT NULL - DROP TABLE #IndexColumns; - -IF OBJECT_ID('tempdb..#MissingIndexes') IS NOT NULL - DROP TABLE #MissingIndexes; - -IF OBJECT_ID('tempdb..#ForeignKeys') IS NOT NULL - DROP TABLE #ForeignKeys; - -IF OBJECT_ID('tempdb..#UnindexedForeignKeys') IS NOT NULL - DROP TABLE #UnindexedForeignKeys; - -IF OBJECT_ID('tempdb..#BlitzIndexResults') IS NOT NULL - DROP TABLE #BlitzIndexResults; - -IF OBJECT_ID('tempdb..#IndexCreateTsql') IS NOT NULL - DROP TABLE #IndexCreateTsql; - -IF OBJECT_ID('tempdb..#DatabaseList') IS NOT NULL - DROP TABLE #DatabaseList; - -IF OBJECT_ID('tempdb..#Statistics') IS NOT NULL - DROP TABLE #Statistics; - -IF OBJECT_ID('tempdb..#PartitionCompressionInfo') IS NOT NULL - DROP TABLE #PartitionCompressionInfo; - -IF OBJECT_ID('tempdb..#ComputedColumns') IS NOT NULL - DROP TABLE #ComputedColumns; - -IF OBJECT_ID('tempdb..#TraceStatus') IS NOT NULL - DROP TABLE #TraceStatus; - -IF OBJECT_ID('tempdb..#TemporalTables') IS NOT NULL - DROP TABLE #TemporalTables; - -IF OBJECT_ID('tempdb..#CheckConstraints') IS NOT NULL - DROP TABLE #CheckConstraints; - -IF OBJECT_ID('tempdb..#FilteredIndexes') IS NOT NULL - DROP TABLE #FilteredIndexes; - -IF OBJECT_ID('tempdb..#Ignore_Databases') IS NOT NULL - DROP TABLE #Ignore_Databases - -IF OBJECT_ID('tempdb..#dm_db_partition_stats_etc') IS NOT NULL - DROP TABLE #dm_db_partition_stats_etc -IF OBJECT_ID('tempdb..#dm_db_index_operational_stats') IS NOT NULL - DROP TABLE #dm_db_index_operational_stats - - RAISERROR (N'Create temp tables.',0,1) WITH NOWAIT; - CREATE TABLE #BlitzIndexResults - ( - blitz_result_id INT IDENTITY PRIMARY KEY, - check_id INT NOT NULL, - index_sanity_id INT NULL, - Priority INT NULL, - findings_group NVARCHAR(4000) NOT NULL, - finding NVARCHAR(200) NOT NULL, - [database_name] NVARCHAR(128) NULL, - URL NVARCHAR(200) NOT NULL, - details NVARCHAR(MAX) NOT NULL, - index_definition NVARCHAR(MAX) NOT NULL, - secret_columns NVARCHAR(MAX) NULL, - index_usage_summary NVARCHAR(MAX) NULL, - index_size_summary NVARCHAR(MAX) NULL, - create_tsql NVARCHAR(MAX) NULL, - more_info NVARCHAR(MAX) NULL, - sample_query_plan XML NULL - ); - - CREATE TABLE #IndexSanity - ( - [index_sanity_id] INT IDENTITY PRIMARY KEY CLUSTERED, - [database_id] SMALLINT NOT NULL , - [object_id] INT NOT NULL , - [index_id] INT NOT NULL , - [index_type] TINYINT NOT NULL, - [database_name] NVARCHAR(128) NOT NULL , - [schema_name] NVARCHAR(128) NOT NULL , - [object_name] NVARCHAR(128) NOT NULL , - index_name NVARCHAR(128) NULL , - key_column_names NVARCHAR(MAX) NULL , - key_column_names_with_sort_order NVARCHAR(MAX) NULL , - key_column_names_with_sort_order_no_types NVARCHAR(MAX) NULL , - count_key_columns INT NULL , - include_column_names NVARCHAR(MAX) NULL , - include_column_names_no_types NVARCHAR(MAX) NULL , - count_included_columns INT NULL , - partition_key_column_name NVARCHAR(MAX) NULL, - filter_definition NVARCHAR(MAX) NOT NULL , - optimize_for_sequential_key BIT NULL, - is_indexed_view BIT NOT NULL , - is_unique BIT NOT NULL , - is_primary_key BIT NOT NULL , - is_unique_constraint BIT NOT NULL , - is_XML bit NOT NULL, - is_spatial BIT NOT NULL, - is_NC_columnstore BIT NOT NULL, - is_CX_columnstore BIT NOT NULL, - is_in_memory_oltp BIT NOT NULL , - is_disabled BIT NOT NULL , - is_hypothetical BIT NOT NULL , - is_padded BIT NOT NULL , - fill_factor SMALLINT NOT NULL , - user_seeks BIGINT NOT NULL , - user_scans BIGINT NOT NULL , - user_lookups BIGINT NOT NULL , - user_updates BIGINT NULL , - last_user_seek DATETIME NULL , - last_user_scan DATETIME NULL , - last_user_lookup DATETIME NULL , - last_user_update DATETIME NULL , - is_referenced_by_foreign_key BIT DEFAULT(0), - secret_columns NVARCHAR(MAX) NULL, - count_secret_columns INT NULL, - create_date DATETIME NOT NULL, - modify_date DATETIME NOT NULL, - filter_columns_not_in_index NVARCHAR(MAX), - [db_schema_object_name] AS [schema_name] + N'.' + [object_name] , - [db_schema_object_indexid] AS [schema_name] + N'.' + [object_name] - + CASE WHEN [index_name] IS NOT NULL THEN N'.' + index_name - ELSE N'' - END + N' (' + CAST(index_id AS NVARCHAR(20)) + N')' , - first_key_column_name AS CASE WHEN count_key_columns > 1 - THEN LEFT(key_column_names, CHARINDEX(',', key_column_names, 0) - 1) - ELSE key_column_names - END , - index_definition AS - CASE WHEN partition_key_column_name IS NOT NULL - THEN N'[PARTITIONED BY:' + partition_key_column_name + N']' - ELSE '' - END + - CASE index_id - WHEN 0 THEN N'[HEAP] ' - WHEN 1 THEN N'[CX] ' - ELSE N'' END + CASE WHEN is_indexed_view = 1 THEN N'[VIEW] ' - ELSE N'' END + CASE WHEN is_primary_key = 1 THEN N'[PK] ' - ELSE N'' END + CASE WHEN is_XML = 1 THEN N'[XML] ' - ELSE N'' END + CASE WHEN is_spatial = 1 THEN N'[SPATIAL] ' - ELSE N'' END + CASE WHEN is_NC_columnstore = 1 THEN N'[COLUMNSTORE] ' - ELSE N'' END + CASE WHEN is_in_memory_oltp = 1 THEN N'[IN-MEMORY] ' - ELSE N'' END + CASE WHEN is_disabled = 1 THEN N'[DISABLED] ' - ELSE N'' END + CASE WHEN is_hypothetical = 1 THEN N'[HYPOTHETICAL] ' - ELSE N'' END + CASE WHEN is_unique = 1 AND is_primary_key = 0 AND is_unique_constraint = 0 THEN N'[UNIQUE] ' - ELSE N'' END + CASE WHEN is_unique_constraint = 1 AND is_primary_key = 0 THEN N'[UNIQUE CONSTRAINT] ' - ELSE N'' END + CASE WHEN count_key_columns > 0 THEN - N'[' + CAST(count_key_columns AS NVARCHAR(10)) + N' KEY' - + CASE WHEN count_key_columns > 1 THEN N'S' ELSE N'' END - + N'] ' + LTRIM(key_column_names_with_sort_order) - ELSE N'' END + CASE WHEN count_included_columns > 0 THEN - N' [' + CAST(count_included_columns AS NVARCHAR(10)) + N' INCLUDE' + - + CASE WHEN count_included_columns > 1 THEN N'S' ELSE N'' END - + N'] ' + include_column_names - ELSE N'' END + CASE WHEN filter_definition <> N'' THEN N' [FILTER] ' + filter_definition - ELSE N'' END , - [total_reads] AS user_seeks + user_scans + user_lookups, - [reads_per_write] AS CAST(CASE WHEN user_updates > 0 - THEN ( user_seeks + user_scans + user_lookups ) / (1.0 * user_updates) - ELSE 0 END AS MONEY) , - [index_usage_summary] AS - CASE WHEN is_spatial = 1 THEN N'Not Tracked' - WHEN is_disabled = 1 THEN N'Disabled' - ELSE N'Reads: ' + - REPLACE(CONVERT(NVARCHAR(30),CAST((user_seeks + user_scans + user_lookups) AS MONEY), 1), N'.00', N'') - + CASE WHEN user_seeks + user_scans + user_lookups > 0 THEN - N' (' - + RTRIM( - CASE WHEN user_seeks > 0 THEN REPLACE(CONVERT(NVARCHAR(30),CAST((user_seeks) AS MONEY), 1), N'.00', N'') + N' seek ' ELSE N'' END - + CASE WHEN user_scans > 0 THEN REPLACE(CONVERT(NVARCHAR(30),CAST((user_scans) AS MONEY), 1), N'.00', N'') + N' scan ' ELSE N'' END - + CASE WHEN user_lookups > 0 THEN REPLACE(CONVERT(NVARCHAR(30),CAST((user_lookups) AS MONEY), 1), N'.00', N'') + N' lookup' ELSE N'' END - ) - + N') ' - ELSE N' ' - END - + N'Writes: ' + - REPLACE(CONVERT(NVARCHAR(30),CAST(user_updates AS MONEY), 1), N'.00', N'') - END /* First "end" is about is_spatial */, - [more_info] AS - CASE WHEN is_in_memory_oltp = 1 - THEN N'EXEC dbo.sp_BlitzInMemoryOLTP @dbName=' + QUOTENAME([database_name],N'''') + - N', @tableName=' + QUOTENAME([object_name],N'''') + N';' - ELSE N'EXEC dbo.sp_BlitzIndex @DatabaseName=' + QUOTENAME([database_name],N'''') + - N', @SchemaName=' + QUOTENAME([schema_name],N'''') + N', @TableName=' + QUOTENAME([object_name],N'''') + N';' - END - ); - RAISERROR (N'Adding UQ index on #IndexSanity (database_id, object_id, index_id)',0,1) WITH NOWAIT; - IF NOT EXISTS(SELECT 1 FROM tempdb.sys.indexes WHERE name='uq_database_id_object_id_index_id') - CREATE UNIQUE INDEX uq_database_id_object_id_index_id ON #IndexSanity (database_id, object_id, index_id); - - - CREATE TABLE #IndexPartitionSanity - ( - [index_partition_sanity_id] INT IDENTITY, - [index_sanity_id] INT NULL , - [database_id] INT NOT NULL , - [object_id] INT NOT NULL , - [schema_name] NVARCHAR(128) NOT NULL, - [index_id] INT NOT NULL , - [partition_number] INT NOT NULL , - row_count BIGINT NOT NULL , - reserved_MB NUMERIC(29,2) NOT NULL , - reserved_LOB_MB NUMERIC(29,2) NOT NULL , - reserved_row_overflow_MB NUMERIC(29,2) NOT NULL , - reserved_dictionary_MB NUMERIC(29,2) NOT NULL , - leaf_insert_count BIGINT NULL , - leaf_delete_count BIGINT NULL , - leaf_update_count BIGINT NULL , - range_scan_count BIGINT NULL , - singleton_lookup_count BIGINT NULL , - forwarded_fetch_count BIGINT NULL , - lob_fetch_in_pages BIGINT NULL , - lob_fetch_in_bytes BIGINT NULL , - row_overflow_fetch_in_pages BIGINT NULL , - row_overflow_fetch_in_bytes BIGINT NULL , - row_lock_count BIGINT NULL , - row_lock_wait_count BIGINT NULL , - row_lock_wait_in_ms BIGINT NULL , - page_lock_count BIGINT NULL , - page_lock_wait_count BIGINT NULL , - page_lock_wait_in_ms BIGINT NULL , - index_lock_promotion_attempt_count BIGINT NULL , - index_lock_promotion_count BIGINT NULL, - data_compression_desc NVARCHAR(60) NULL, - page_latch_wait_count BIGINT NULL, - page_latch_wait_in_ms BIGINT NULL, - page_io_latch_wait_count BIGINT NULL, - page_io_latch_wait_in_ms BIGINT NULL, - lock_escalation_desc nvarchar(60) NULL - ); - - CREATE TABLE #IndexSanitySize - ( - [index_sanity_size_id] INT IDENTITY NOT NULL , - [index_sanity_id] INT NULL , - [database_id] INT NOT NULL, - [schema_name] NVARCHAR(128) NOT NULL, - partition_count INT NOT NULL , - total_rows BIGINT NOT NULL , - total_reserved_MB NUMERIC(29,2) NOT NULL , - total_reserved_LOB_MB NUMERIC(29,2) NOT NULL , - total_reserved_row_overflow_MB NUMERIC(29,2) NOT NULL , - total_reserved_dictionary_MB NUMERIC(29,2) NOT NULL , - total_leaf_delete_count BIGINT NULL, - total_leaf_update_count BIGINT NULL, - total_range_scan_count BIGINT NULL, - total_singleton_lookup_count BIGINT NULL, - total_forwarded_fetch_count BIGINT NULL, - total_row_lock_count BIGINT NULL , - total_row_lock_wait_count BIGINT NULL , - total_row_lock_wait_in_ms BIGINT NULL , - avg_row_lock_wait_in_ms BIGINT NULL , - total_page_lock_count BIGINT NULL , - total_page_lock_wait_count BIGINT NULL , - total_page_lock_wait_in_ms BIGINT NULL , - avg_page_lock_wait_in_ms BIGINT NULL , - total_index_lock_promotion_attempt_count BIGINT NULL , - total_index_lock_promotion_count BIGINT NULL , - data_compression_desc NVARCHAR(4000) NULL, - page_latch_wait_count BIGINT NULL, - page_latch_wait_in_ms BIGINT NULL, - page_io_latch_wait_count BIGINT NULL, - page_io_latch_wait_in_ms BIGINT NULL, - lock_escalation_desc nvarchar(60) NULL, - index_size_summary AS ISNULL( - CASE WHEN partition_count > 1 - THEN N'[' + CAST(partition_count AS NVARCHAR(10)) + N' PARTITIONS] ' - ELSE N'' - END + REPLACE(CONVERT(NVARCHAR(30),CAST([total_rows] AS MONEY), 1), N'.00', N'') + N' rows; ' - + CASE WHEN total_reserved_MB > 1024 THEN - CAST(CAST(total_reserved_MB/1024. AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'GB' - ELSE - CAST(CAST(total_reserved_MB AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'MB' - END - + CASE WHEN total_reserved_LOB_MB > 1024 THEN - N'; ' + CAST(CAST(total_reserved_LOB_MB/1024. AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'GB ' + CASE WHEN total_reserved_dictionary_MB = 0 THEN N'LOB' ELSE N'Columnstore' END - WHEN total_reserved_LOB_MB > 0 THEN - N'; ' + CAST(CAST(total_reserved_LOB_MB AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'MB ' + CASE WHEN total_reserved_dictionary_MB = 0 THEN N'LOB' ELSE N'Columnstore' END - ELSE '' - END - + CASE WHEN total_reserved_row_overflow_MB > 1024 THEN - N'; ' + CAST(CAST(total_reserved_row_overflow_MB/1024. AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'GB Row Overflow' - WHEN total_reserved_row_overflow_MB > 0 THEN - N'; ' + CAST(CAST(total_reserved_row_overflow_MB AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'MB Row Overflow' - ELSE '' - END - + CASE WHEN total_reserved_dictionary_MB > 1024 THEN - N'; ' + CAST(CAST(total_reserved_dictionary_MB/1024. AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'GB Dictionaries' - WHEN total_reserved_dictionary_MB > 0 THEN - N'; ' + CAST(CAST(total_reserved_dictionary_MB AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'MB Dictionaries' - ELSE '' - END , - N'Error- NULL in computed column'), - index_op_stats AS ISNULL( - ( - REPLACE(CONVERT(NVARCHAR(30),CAST(total_singleton_lookup_count AS MONEY), 1),N'.00',N'') + N' singleton lookups; ' - + REPLACE(CONVERT(NVARCHAR(30),CAST(total_range_scan_count AS MONEY), 1),N'.00',N'') + N' scans/seeks; ' - + REPLACE(CONVERT(NVARCHAR(30),CAST(total_leaf_delete_count AS MONEY), 1),N'.00',N'') + N' deletes; ' - + REPLACE(CONVERT(NVARCHAR(30),CAST(total_leaf_update_count AS MONEY), 1),N'.00',N'') + N' updates; ' - + CASE WHEN ISNULL(total_forwarded_fetch_count,0) >0 THEN - REPLACE(CONVERT(NVARCHAR(30),CAST(total_forwarded_fetch_count AS MONEY), 1),N'.00',N'') + N' forward records fetched; ' - ELSE N'' END - - /* rows will only be in this dmv when data is in memory for the table */ - ), N'Table metadata not in memory'), - index_lock_wait_summary AS ISNULL( - CASE WHEN total_row_lock_wait_count = 0 AND total_page_lock_wait_count = 0 AND - total_index_lock_promotion_attempt_count = 0 THEN N'0 lock waits; ' - + CASE WHEN lock_escalation_desc = N'DISABLE' THEN N'Lock escalation DISABLE.' - ELSE N'' - END - ELSE - CASE WHEN total_row_lock_wait_count > 0 THEN - N'Row lock waits: ' + REPLACE(CONVERT(NVARCHAR(30),CAST(total_row_lock_wait_count AS MONEY), 1), N'.00', N'') - + N'; total duration: ' + - CASE WHEN total_row_lock_wait_in_ms >= 60000 THEN /*More than 1 min*/ - REPLACE(CONVERT(NVARCHAR(30),CAST((total_row_lock_wait_in_ms/60000) AS MONEY), 1), N'.00', N'') + N' minutes; ' - ELSE - REPLACE(CONVERT(NVARCHAR(30),CAST(ISNULL(total_row_lock_wait_in_ms/1000,0) AS MONEY), 1), N'.00', N'') + N' seconds; ' - END - + N'avg duration: ' + - CASE WHEN avg_row_lock_wait_in_ms >= 60000 THEN /*More than 1 min*/ - REPLACE(CONVERT(NVARCHAR(30),CAST((avg_row_lock_wait_in_ms/60000) AS MONEY), 1), N'.00', N'') + N' minutes; ' - ELSE - REPLACE(CONVERT(NVARCHAR(30),CAST(ISNULL(avg_row_lock_wait_in_ms/1000,0) AS MONEY), 1), N'.00', N'') + N' seconds; ' - END - ELSE N'' - END + - CASE WHEN total_page_lock_wait_count > 0 THEN - N'Page lock waits: ' + REPLACE(CONVERT(NVARCHAR(30),CAST(total_page_lock_wait_count AS MONEY), 1), N'.00', N'') - + N'; total duration: ' + - CASE WHEN total_page_lock_wait_in_ms >= 60000 THEN /*More than 1 min*/ - REPLACE(CONVERT(NVARCHAR(30),CAST((total_page_lock_wait_in_ms/60000) AS MONEY), 1), N'.00', N'') + N' minutes; ' - ELSE - REPLACE(CONVERT(NVARCHAR(30),CAST(ISNULL(total_page_lock_wait_in_ms/1000,0) AS MONEY), 1), N'.00', N'') + N' seconds; ' - END - + N'avg duration: ' + - CASE WHEN avg_page_lock_wait_in_ms >= 60000 THEN /*More than 1 min*/ - REPLACE(CONVERT(NVARCHAR(30),CAST((avg_page_lock_wait_in_ms/60000) AS MONEY), 1), N'.00', N'') + N' minutes; ' - ELSE - REPLACE(CONVERT(NVARCHAR(30),CAST(ISNULL(avg_page_lock_wait_in_ms/1000,0) AS MONEY), 1), N'.00', N'') + N' seconds; ' - END - ELSE N'' - END + - CASE WHEN total_index_lock_promotion_attempt_count > 0 THEN - N'Lock escalation attempts: ' + REPLACE(CONVERT(NVARCHAR(30),CAST(total_index_lock_promotion_attempt_count AS MONEY), 1), N'.00', N'') - + N'; Actual Escalations: ' + REPLACE(CONVERT(NVARCHAR(30),CAST(ISNULL(total_index_lock_promotion_count,0) AS MONEY), 1), N'.00', N'') +N'; ' - ELSE N'' - END + - CASE WHEN lock_escalation_desc = N'DISABLE' THEN - N'Lock escalation is disabled.' - ELSE N'' - END - END - ,'Error- NULL in computed column') - ); - - CREATE TABLE #IndexColumns - ( - [database_id] INT NOT NULL, - [schema_name] NVARCHAR(128), - [object_id] INT NOT NULL , - [index_id] INT NOT NULL , - [key_ordinal] INT NULL , - is_included_column BIT NULL , - is_descending_key BIT NULL , - [partition_ordinal] INT NULL , - column_name NVARCHAR(256) NOT NULL , - system_type_name NVARCHAR(256) NOT NULL, - max_length SMALLINT NOT NULL, - [precision] TINYINT NOT NULL, - [scale] TINYINT NOT NULL, - collation_name NVARCHAR(256) NULL, - is_nullable BIT NULL, - is_identity BIT NULL, - is_computed BIT NULL, - is_replicated BIT NULL, - is_sparse BIT NULL, - is_filestream BIT NULL, - seed_value DECIMAL(38,0) NULL, - increment_value DECIMAL(38,0) NULL , - last_value DECIMAL(38,0) NULL, - is_not_for_replication BIT NULL - ); - CREATE CLUSTERED INDEX CLIX_database_id_object_id_index_id ON #IndexColumns - (database_id, object_id, index_id); - - CREATE TABLE #MissingIndexes - ([database_id] INT NOT NULL, - [object_id] INT NOT NULL, - [database_name] NVARCHAR(128) NOT NULL , - [schema_name] NVARCHAR(128) NOT NULL , - [table_name] NVARCHAR(128), - [statement] NVARCHAR(512) NOT NULL, - magic_benefit_number AS (( user_seeks + user_scans ) * avg_total_user_cost * avg_user_impact), - avg_total_user_cost NUMERIC(29,4) NOT NULL, - avg_user_impact NUMERIC(29,1) NOT NULL, - user_seeks BIGINT NOT NULL, - user_scans BIGINT NOT NULL, - unique_compiles BIGINT NULL, - equality_columns NVARCHAR(MAX), - equality_columns_with_data_type NVARCHAR(MAX), - inequality_columns NVARCHAR(MAX), - inequality_columns_with_data_type NVARCHAR(MAX), - included_columns NVARCHAR(MAX), - included_columns_with_data_type NVARCHAR(MAX), - is_low BIT, - [index_estimated_impact] AS - REPLACE(CONVERT(NVARCHAR(256),CAST(CAST( - (user_seeks + user_scans) - AS BIGINT) AS MONEY), 1), '.00', '') + N' use' - + CASE WHEN (user_seeks + user_scans) > 1 THEN N's' ELSE N'' END - +N'; Impact: ' + CAST(avg_user_impact AS NVARCHAR(30)) - + N'%; Avg query cost: ' - + CAST(avg_total_user_cost AS NVARCHAR(30)), - [missing_index_details] AS - CASE WHEN COALESCE(equality_columns_with_data_type,equality_columns) IS NOT NULL - THEN N'EQUALITY: ' + COALESCE(CAST(equality_columns_with_data_type AS NVARCHAR(MAX)), CAST(equality_columns AS NVARCHAR(MAX))) + N' ' - ELSE N'' END + - - CASE WHEN COALESCE(inequality_columns_with_data_type,inequality_columns) IS NOT NULL - THEN N'INEQUALITY: ' + COALESCE(CAST(inequality_columns_with_data_type AS NVARCHAR(MAX)), CAST(inequality_columns AS NVARCHAR(MAX))) + N' ' - ELSE N'' END + - - CASE WHEN COALESCE(included_columns_with_data_type,included_columns) IS NOT NULL - THEN N'INCLUDE: ' + COALESCE(CAST(included_columns_with_data_type AS NVARCHAR(MAX)), CAST(included_columns AS NVARCHAR(MAX))) + N' ' - ELSE N'' END, - [create_tsql] AS N'CREATE INDEX [' - + LEFT(REPLACE(REPLACE(REPLACE(REPLACE( - ISNULL(equality_columns,N'')+ - CASE WHEN equality_columns IS NOT NULL AND inequality_columns IS NOT NULL THEN N'_' ELSE N'' END - + ISNULL(inequality_columns,''),',','') - ,'[',''),']',''),' ','_') - + CASE WHEN included_columns IS NOT NULL THEN N'_Includes' ELSE N'' END, 128) + N'] ON ' - + [statement] + N' (' + ISNULL(equality_columns,N'') - + CASE WHEN equality_columns IS NOT NULL AND inequality_columns IS NOT NULL THEN N', ' ELSE N'' END - + CASE WHEN inequality_columns IS NOT NULL THEN inequality_columns ELSE N'' END + - ') ' + CASE WHEN included_columns IS NOT NULL THEN N' INCLUDE (' + included_columns + N')' ELSE N'' END - + N' WITH (' - + N'FILLFACTOR=100, ONLINE=?, SORT_IN_TEMPDB=?, DATA_COMPRESSION=?' - + N')' - + N';' - , - [more_info] AS N'EXEC dbo.sp_BlitzIndex @DatabaseName=' + QUOTENAME([database_name],'''') + - N', @SchemaName=' + QUOTENAME([schema_name],'''') + N', @TableName=' + QUOTENAME([table_name],'''') + N';', - [sample_query_plan] XML NULL - ); - - CREATE TABLE #ForeignKeys ( - [database_id] INT NOT NULL, - [database_name] NVARCHAR(128) NOT NULL , - [schema_name] NVARCHAR(128) NOT NULL , - foreign_key_name NVARCHAR(256), - parent_object_id INT, - parent_object_name NVARCHAR(256), - referenced_object_id INT, - referenced_object_name NVARCHAR(256), - is_disabled BIT, - is_not_trusted BIT, - is_not_for_replication BIT, - parent_fk_columns NVARCHAR(MAX), - referenced_fk_columns NVARCHAR(MAX), - update_referential_action_desc NVARCHAR(16), - delete_referential_action_desc NVARCHAR(60) - ); - - CREATE TABLE #UnindexedForeignKeys - ( - [database_id] INT NOT NULL, - [database_name] NVARCHAR(128) NOT NULL , - [schema_name] NVARCHAR(128) NOT NULL , - foreign_key_name NVARCHAR(256), - parent_object_name NVARCHAR(256), - parent_object_id INT, - referenced_object_name NVARCHAR(256), - referenced_object_id INT - ); - - CREATE TABLE #IndexCreateTsql ( - index_sanity_id INT NOT NULL, - create_tsql NVARCHAR(MAX) NOT NULL - ); - - CREATE TABLE #DatabaseList ( - DatabaseName NVARCHAR(256), - secondary_role_allow_connections_desc NVARCHAR(50) - - ); - - CREATE TABLE #PartitionCompressionInfo ( - [index_sanity_id] INT NULL, - [partition_compression_detail] NVARCHAR(4000) NULL - ); - - CREATE TABLE #Statistics ( - database_id INT NOT NULL, - database_name NVARCHAR(256) NOT NULL, - table_name NVARCHAR(128) NULL, - schema_name NVARCHAR(128) NULL, - index_name NVARCHAR(128) NULL, - column_names NVARCHAR(MAX) NULL, - statistics_name NVARCHAR(128) NULL, - last_statistics_update DATETIME NULL, - days_since_last_stats_update INT NULL, - rows BIGINT NULL, - rows_sampled BIGINT NULL, - percent_sampled DECIMAL(18, 1) NULL, - histogram_steps INT NULL, - modification_counter BIGINT NULL, - percent_modifications DECIMAL(18, 1) NULL, - modifications_before_auto_update INT 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 - ); - - CREATE TABLE #ComputedColumns - ( - index_sanity_id INT IDENTITY(1, 1) NOT NULL, - database_name NVARCHAR(128) NULL, - database_id INT NOT NULL, - table_name NVARCHAR(128) NOT NULL, - schema_name NVARCHAR(128) NOT NULL, - column_name NVARCHAR(128) NULL, - is_nullable BIT NULL, - definition NVARCHAR(MAX) NULL, - uses_database_collation BIT NOT NULL, - is_persisted BIT NOT NULL, - is_computed BIT NOT NULL, - is_function INT NOT NULL, - column_definition NVARCHAR(MAX) NULL - ); - - CREATE TABLE #TraceStatus - ( - TraceFlag NVARCHAR(10) , - status BIT , - Global BIT , - Session BIT - ); - - CREATE TABLE #TemporalTables - ( - index_sanity_id INT IDENTITY(1, 1) NOT NULL, - database_name NVARCHAR(128) NOT NULL, - database_id INT NOT NULL, - schema_name NVARCHAR(128) NOT NULL, - table_name NVARCHAR(128) NOT NULL, - history_table_name NVARCHAR(128) NOT NULL, - history_schema_name NVARCHAR(128) NOT NULL, - start_column_name NVARCHAR(128) NOT NULL, - end_column_name NVARCHAR(128) NOT NULL, - period_name NVARCHAR(128) NOT NULL - ); - - CREATE TABLE #CheckConstraints - ( - index_sanity_id INT IDENTITY(1, 1) NOT NULL, - database_name NVARCHAR(128) NULL, - database_id INT NOT NULL, - table_name NVARCHAR(128) NOT NULL, - schema_name NVARCHAR(128) NOT NULL, - constraint_name NVARCHAR(128) NULL, - is_disabled BIT NULL, - definition NVARCHAR(MAX) NULL, - uses_database_collation BIT NOT NULL, - is_not_trusted BIT NOT NULL, - is_function INT NOT NULL, - column_definition NVARCHAR(MAX) NULL - ); - - CREATE TABLE #FilteredIndexes - ( - index_sanity_id INT IDENTITY(1, 1) NOT NULL, - database_name NVARCHAR(128) NULL, - database_id INT NOT NULL, - schema_name NVARCHAR(128) NOT NULL, - table_name NVARCHAR(128) NOT NULL, - index_name NVARCHAR(128) NULL, - column_name NVARCHAR(128) NULL - ); - - CREATE TABLE #Ignore_Databases - ( - DatabaseName NVARCHAR(128), - Reason NVARCHAR(100) - ); - -/* Sanitize our inputs */ -SELECT - @OutputServerName = QUOTENAME(@OutputServerName), - @OutputDatabaseName = QUOTENAME(@OutputDatabaseName), - @OutputSchemaName = QUOTENAME(@OutputSchemaName), - @OutputTableName = QUOTENAME(@OutputTableName); - - -IF @GetAllDatabases = 1 - BEGIN - INSERT INTO #DatabaseList (DatabaseName) - SELECT DB_NAME(database_id) - FROM sys.databases - WHERE user_access_desc = 'MULTI_USER' - AND state_desc = 'ONLINE' - AND database_id > 4 - AND DB_NAME(database_id) NOT LIKE 'ReportServer%' - AND DB_NAME(database_id) NOT LIKE 'rdsadmin%' - AND LOWER(name) NOT IN('dbatools', 'dbadmin', 'dbmaintenance') - AND is_distributor = 0 - OPTION ( RECOMPILE ); - - /* Skip non-readable databases in an AG - see Github issue #1160 */ - IF EXISTS (SELECT * FROM sys.all_objects o INNER JOIN sys.all_columns c ON o.object_id = c.object_id AND o.name = 'dm_hadr_availability_replica_states' AND c.name = 'role_desc') - BEGIN - SET @dsql = N'UPDATE #DatabaseList SET secondary_role_allow_connections_desc = ''NO'' WHERE DatabaseName IN ( - SELECT d.name - FROM sys.dm_hadr_availability_replica_states rs - INNER JOIN sys.databases d ON rs.replica_id = d.replica_id - INNER JOIN sys.availability_replicas r ON rs.replica_id = r.replica_id - WHERE rs.role_desc = ''SECONDARY'' - AND r.secondary_role_allow_connections_desc = ''NO'') - OPTION ( RECOMPILE );'; - EXEC sp_executesql @dsql; - - IF EXISTS (SELECT * FROM #DatabaseList WHERE secondary_role_allow_connections_desc = 'NO') - BEGIN - INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, database_name, URL, details, index_definition, - index_usage_summary, index_size_summary ) - VALUES ( 1, - 0, - N'Skipped non-readable AG secondary databases.', - N'You are running this on an AG secondary, and some of your databases are configured as non-readable when this is a secondary node.', - N'To analyze those databases, run sp_BlitzIndex on the primary, or on a readable secondary.', - 'http://FirstResponderKit.org', '', '', '', '' - ); - END; - END; - - IF @IgnoreDatabases IS NOT NULL - AND LEN(@IgnoreDatabases) > 0 - BEGIN - RAISERROR(N'Setting up filter to ignore databases', 0, 1) WITH NOWAIT; - SET @DatabaseToIgnore = ''; - - WHILE LEN(@IgnoreDatabases) > 0 - BEGIN - IF PATINDEX('%,%', @IgnoreDatabases) > 0 - BEGIN - SET @DatabaseToIgnore = SUBSTRING(@IgnoreDatabases, 0, PATINDEX('%,%',@IgnoreDatabases)) ; - - INSERT INTO #Ignore_Databases (DatabaseName, Reason) - SELECT LTRIM(RTRIM(@DatabaseToIgnore)), 'Specified in the @IgnoreDatabases parameter' - OPTION (RECOMPILE) ; - - SET @IgnoreDatabases = SUBSTRING(@IgnoreDatabases, LEN(@DatabaseToIgnore + ',') + 1, LEN(@IgnoreDatabases)) ; - END; - ELSE - BEGIN - SET @DatabaseToIgnore = @IgnoreDatabases ; - SET @IgnoreDatabases = NULL ; - - INSERT INTO #Ignore_Databases (DatabaseName, Reason) - SELECT LTRIM(RTRIM(@DatabaseToIgnore)), 'Specified in the @IgnoreDatabases parameter' - OPTION (RECOMPILE) ; - END; - END; - - END - - END; -ELSE - BEGIN - INSERT INTO #DatabaseList - ( DatabaseName ) - SELECT CASE - WHEN @DatabaseName IS NULL OR @DatabaseName = N'' - THEN DB_NAME() - ELSE @DatabaseName END; - END; - -SET @NumDatabases = (SELECT COUNT(*) FROM #DatabaseList AS D LEFT OUTER JOIN #Ignore_Databases AS I ON D.DatabaseName = I.DatabaseName WHERE I.DatabaseName IS NULL); -SET @msg = N'Number of databases to examine: ' + CAST(@NumDatabases AS NVARCHAR(50)); -RAISERROR (@msg,0,1) WITH NOWAIT; - - - -/* Running on 50+ databases can take a reaaallly long time, so we want explicit permission to do so (and only after warning about it) */ - - -BEGIN TRY - IF @NumDatabases >= 50 AND @BringThePain != 1 AND @TableName IS NULL - BEGIN - - INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, URL, details, index_definition, - index_usage_summary, index_size_summary ) - VALUES ( -1, - 0 , - @ScriptVersionName, - CASE WHEN @GetAllDatabases = 1 THEN N'All Databases' ELSE N'Database ' + QUOTENAME(@DatabaseName) + N' as of ' + CONVERT(NVARCHAR(16), GETDATE(), 121) END, - N'From Your Community Volunteers', - N'http://FirstResponderKit.org', - N'', - N'', - N'' - ); - INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, database_name, URL, details, index_definition, - index_usage_summary, index_size_summary ) - VALUES ( 1, - 0, - N'You''re trying to run sp_BlitzIndex on a server with ' + CAST(@NumDatabases AS NVARCHAR(8)) + N' databases. ', - N'Running sp_BlitzIndex on a server with 50+ databases may cause temporary problems for the server and/or user.', - N'If you''re sure you want to do this, run again with the parameter @BringThePain = 1.', - 'http://FirstResponderKit.org', - '', - '', - '', - '' - ); - - if(@OutputType <> 'NONE') - BEGIN - SELECT bir.blitz_result_id, - bir.check_id, - bir.index_sanity_id, - bir.Priority, - bir.findings_group, - bir.finding, - bir.database_name, - bir.URL, - bir.details, - bir.index_definition, - bir.secret_columns, - bir.index_usage_summary, - bir.index_size_summary, - bir.create_tsql, - bir.more_info - FROM #BlitzIndexResults AS bir; - RAISERROR('Running sp_BlitzIndex on a server with 50+ databases may cause temporary problems for the server', 12, 1); - END; - - RETURN; - - END; -END TRY -BEGIN CATCH - RAISERROR (N'Failure to execute due to number of databases.', 0,1) WITH NOWAIT; - - SELECT @msg = ERROR_MESSAGE(), - @ErrorSeverity = ERROR_SEVERITY(), - @ErrorState = ERROR_STATE(); - - RAISERROR (@msg, @ErrorSeverity, @ErrorState); - - WHILE @@trancount > 0 - ROLLBACK; - - RETURN; - END CATCH; - - -RAISERROR (N'Checking partition counts to exclude databases with over 100 partitions',0,1) WITH NOWAIT; -IF @BringThePain = 0 AND @SkipPartitions = 0 AND @TableName IS NULL - BEGIN - DECLARE partition_cursor CURSOR FOR - SELECT dl.DatabaseName - FROM #DatabaseList dl - LEFT OUTER JOIN #Ignore_Databases i ON dl.DatabaseName = i.DatabaseName - WHERE COALESCE(dl.secondary_role_allow_connections_desc, 'OK') <> 'NO' - AND i.DatabaseName IS NULL - - OPEN partition_cursor - FETCH NEXT FROM partition_cursor INTO @DatabaseName - - WHILE @@FETCH_STATUS = 0 - BEGIN - /* Count the total number of partitions */ - SET @dsql = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @RowcountOUT = SUM(1) FROM ' + QUOTENAME(@DatabaseName) + '.sys.partitions WHERE partition_number > 1 OPTION ( RECOMPILE );'; - EXEC sp_executesql @dsql, N'@RowcountOUT BIGINT OUTPUT', @RowcountOUT = @Rowcount OUTPUT; - IF @Rowcount > 100 - BEGIN - RAISERROR (N'Skipping database %s because > 100 partitions were found. To check this database, you must set @BringThePain = 1.',0,1,@DatabaseName) WITH NOWAIT; - INSERT INTO #Ignore_Databases (DatabaseName, Reason) - SELECT @DatabaseName, 'Over 100 partitions found - use @BringThePain = 1 to analyze' - END; - FETCH NEXT FROM partition_cursor INTO @DatabaseName - END; - CLOSE partition_cursor - DEALLOCATE partition_cursor - - END; - -INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, URL, details, index_definition, - index_usage_summary, index_size_summary ) -SELECT 1, 0 , - 'Database Skipped', - i.DatabaseName, - 'http://FirstResponderKit.org', - i.Reason, '', '', '' -FROM #Ignore_Databases i; - - -/* Last startup */ -IF COLUMNPROPERTY(OBJECT_ID('sys.dm_os_sys_info'),'sqlserver_start_time','ColumnID') IS NOT NULL -BEGIN - SELECT @DaysUptime = CAST(DATEDIFF(HOUR, sqlserver_start_time, GETDATE()) / 24. AS NUMERIC (23,2)) - FROM sys.dm_os_sys_info; -END -ELSE -BEGIN - SELECT @DaysUptime = CAST(DATEDIFF(HOUR, create_date, GETDATE()) / 24. AS NUMERIC (23,2)) - FROM sys.databases - WHERE database_id = 2; -END - -IF @DaysUptime = 0 OR @DaysUptime IS NULL - SET @DaysUptime = .01; - -SELECT @DaysUptimeInsertValue = 'Server: ' + (CONVERT(VARCHAR(256), (SERVERPROPERTY('ServerName')))) + ' Days Uptime: ' + RTRIM(@DaysUptime); - - -/* Permission granted or unnecessary? Ok, let's go! */ - -RAISERROR (N'Starting loop through databases',0,1) WITH NOWAIT; -DECLARE c1 CURSOR -LOCAL FAST_FORWARD -FOR -SELECT dl.DatabaseName -FROM #DatabaseList dl -LEFT OUTER JOIN #Ignore_Databases i ON dl.DatabaseName = i.DatabaseName -WHERE COALESCE(dl.secondary_role_allow_connections_desc, 'OK') <> 'NO' - AND i.DatabaseName IS NULL -ORDER BY dl.DatabaseName; - -OPEN c1; -FETCH NEXT FROM c1 INTO @DatabaseName; - WHILE @@FETCH_STATUS = 0 - -BEGIN - - RAISERROR (@LineFeed, 0, 1) WITH NOWAIT; - RAISERROR (@LineFeed, 0, 1) WITH NOWAIT; - RAISERROR (@DatabaseName, 0, 1) WITH NOWAIT; - -SELECT @DatabaseID = [database_id] -FROM sys.databases - WHERE [name] = @DatabaseName - AND user_access_desc='MULTI_USER' - AND state_desc = 'ONLINE'; - ----------------------------------------- ---STEP 1: OBSERVE THE PATIENT ---This step puts index information into temp tables. ----------------------------------------- -BEGIN TRY - BEGIN - DECLARE @d VARCHAR(19) = CONVERT(VARCHAR(19), GETDATE(), 121); - RAISERROR (N'starting at %s',0,1, @d) WITH NOWAIT; - - --Validate SQL Server Version - - IF (SELECT LEFT(@SQLServerProductVersion, - CHARINDEX('.',@SQLServerProductVersion,0)-1 - )) <= 9 - BEGIN - SET @msg=N'sp_BlitzIndex is only supported on SQL Server 2008 and higher. The version of this instance is: ' + @SQLServerProductVersion; - RAISERROR(@msg,16,1); - END; - - --Short circuit here if database name does not exist. - IF @DatabaseName IS NULL OR @DatabaseID IS NULL - BEGIN - SET @msg='Database does not exist or is not online/multi-user: cannot proceed.'; - RAISERROR(@msg,16,1); - END; - - --Validate parameters. - IF (@Mode NOT IN (0,1,2,3,4)) - BEGIN - SET @msg=N'Invalid @Mode parameter. 0=diagnose, 1=summarize, 2=index detail, 3=missing index detail, 4=diagnose detail'; - RAISERROR(@msg,16,1); - END; - - IF (@Mode <> 0 AND @TableName IS NOT NULL) - BEGIN - SET @msg=N'Setting the @Mode doesn''t change behavior if you supply @TableName. Use default @Mode=0 to see table detail.'; - RAISERROR(@msg,16,1); - END; - - IF ((@Mode <> 0 OR @TableName IS NOT NULL) AND @Filter <> 0) - BEGIN - SET @msg=N'@Filter only applies when @Mode=0 and @TableName is not specified. Please try again.'; - RAISERROR(@msg,16,1); - END; - - IF (@SchemaName IS NOT NULL AND @TableName IS NULL) - BEGIN - SET @msg='We can''t run against a whole schema! Specify a @TableName, or leave both NULL for diagnosis.'; - RAISERROR(@msg,16,1); - END; - - - IF (@TableName IS NOT NULL AND @SchemaName IS NULL) - BEGIN - SET @SchemaName=N'dbo'; - SET @msg='@SchemaName wasn''t specified-- assuming schema=dbo.'; - RAISERROR(@msg,1,1) WITH NOWAIT; - END; - - --If a table is specified, grab the object id. - --Short circuit if it doesn't exist. - IF @TableName IS NOT NULL - BEGIN - SET @dsql = N' - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @ObjectID= OBJECT_ID - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.objects AS so - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS sc on - so.schema_id=sc.schema_id - where so.type in (''U'', ''V'') - and so.name=' + QUOTENAME(@TableName,'''')+ N' - and sc.name=' + QUOTENAME(@SchemaName,'''')+ N' - /*Has a row in sys.indexes. This lets us get indexed views.*/ - and exists ( - SELECT si.name - FROM ' + QUOTENAME(@DatabaseName) + '.sys.indexes AS si - WHERE so.object_id=si.object_id) - OPTION (RECOMPILE);'; - - SET @params='@ObjectID INT OUTPUT'; - - IF @dsql IS NULL - RAISERROR('@dsql is null',16,1); - - EXEC sp_executesql @dsql, @params, @ObjectID=@ObjectID OUTPUT; - - IF @ObjectID IS NULL - BEGIN - SET @msg=N'Oh, this is awkward. I can''t find the table or indexed view you''re looking for in that database.' + CHAR(10) + - N'Please check your parameters.'; - RAISERROR(@msg,1,1); - RETURN; - END; - END; - - --set @collation - SELECT @collation=collation_name - FROM sys.databases - WHERE database_id=@DatabaseID; - - --insert columns for clustered indexes and heaps - --collect info on identity columns for this one - SET @dsql = N'/* sp_BlitzIndex */ - SET LOCK_TIMEOUT 1000; /* To fix locking bug in sys.identity_columns. See Github issue #2176. */ - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT ' + CAST(@DatabaseID AS NVARCHAR(16)) + ', - s.name, - si.object_id, - si.index_id, - sc.key_ordinal, - sc.is_included_column, - sc.is_descending_key, - sc.partition_ordinal, - c.name as column_name, - st.name as system_type_name, - c.max_length, - c.[precision], - c.[scale], - c.collation_name, - c.is_nullable, - c.is_identity, - c.is_computed, - c.is_replicated, - ' + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N'c.is_sparse' ELSE N'NULL as is_sparse' END + N', - ' + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N'c.is_filestream' ELSE N'NULL as is_filestream' END + N', - CAST(ic.seed_value AS DECIMAL(38,0)), - CAST(ic.increment_value AS DECIMAL(38,0)), - CAST(ic.last_value AS DECIMAL(38,0)), - ic.is_not_for_replication - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.indexes si - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns c ON - si.object_id=c.object_id - LEFT JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.index_columns sc ON - sc.object_id = si.object_id - and sc.index_id=si.index_id - AND sc.column_id=c.column_id - LEFT JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.identity_columns ic ON - c.object_id=ic.object_id and - c.column_id=ic.column_id - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.types st ON - c.system_type_id=st.system_type_id - AND c.user_type_id=st.user_type_id - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects AS so ON si.object_id = so.object_id - AND so.is_ms_shipped = 0 - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s ON s.schema_id = so.schema_id - WHERE si.index_id in (0,1) ' - + CASE WHEN @ObjectID IS NOT NULL - THEN N' AND si.object_id=' + CAST(@ObjectID AS NVARCHAR(30)) - ELSE N'' END - + N'OPTION (RECOMPILE);'; - - IF @dsql IS NULL - RAISERROR('@dsql is null',16,1); - - RAISERROR (N'Inserting data into #IndexColumns for clustered indexes and heaps',0,1) WITH NOWAIT; - IF @Debug = 1 - BEGIN - PRINT SUBSTRING(@dsql, 1, 4000); - PRINT SUBSTRING(@dsql, 4001, 4000); - PRINT SUBSTRING(@dsql, 8001, 4000); - PRINT SUBSTRING(@dsql, 12001, 4000); - PRINT SUBSTRING(@dsql, 16001, 4000); - PRINT SUBSTRING(@dsql, 20001, 4000); - PRINT SUBSTRING(@dsql, 24001, 4000); - PRINT SUBSTRING(@dsql, 28001, 4000); - PRINT SUBSTRING(@dsql, 32001, 4000); - PRINT SUBSTRING(@dsql, 36001, 4000); - END; - BEGIN TRY - INSERT #IndexColumns ( database_id, [schema_name], [object_id], index_id, key_ordinal, is_included_column, is_descending_key, partition_ordinal, - column_name, system_type_name, max_length, precision, scale, collation_name, is_nullable, is_identity, is_computed, - is_replicated, is_sparse, is_filestream, seed_value, increment_value, last_value, is_not_for_replication ) - EXEC sp_executesql @dsql; - END TRY - BEGIN CATCH - RAISERROR (N'Failure inserting data into #IndexColumns for clustered indexes and heaps.', 0,1) WITH NOWAIT; - - IF @dsql IS NOT NULL - BEGIN - SET @msg= 'Last @dsql: ' + @dsql; - RAISERROR(@msg, 0, 1) WITH NOWAIT; - END; - - SELECT @msg = @DatabaseName + N' database failed to process. ' + ERROR_MESSAGE(), - @ErrorSeverity = 0, @ErrorState = ERROR_STATE(); - RAISERROR (@msg,@ErrorSeverity, @ErrorState )WITH NOWAIT; - - WHILE @@trancount > 0 - ROLLBACK; - - RETURN; - END CATCH; - - - --insert columns for nonclustered indexes - --this uses a full join to sys.index_columns - --We don't collect info on identity columns here. They may be in NC indexes, but we just analyze identities in the base table. - SET @dsql = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT ' + CAST(@DatabaseID AS NVARCHAR(16)) + ', - s.name, - si.object_id, - si.index_id, - sc.key_ordinal, - sc.is_included_column, - sc.is_descending_key, - sc.partition_ordinal, - c.name as column_name, - st.name as system_type_name, - c.max_length, - c.[precision], - c.[scale], - c.collation_name, - c.is_nullable, - c.is_identity, - c.is_computed, - c.is_replicated, - ' + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N'c.is_sparse' ELSE N'NULL AS is_sparse' END + N', - ' + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N'c.is_filestream' ELSE N'NULL AS is_filestream' END + N' - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.indexes AS si - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns AS c ON - si.object_id=c.object_id - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.index_columns AS sc ON - sc.object_id = si.object_id - and sc.index_id=si.index_id - AND sc.column_id=c.column_id - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.types AS st ON - c.system_type_id=st.system_type_id - AND c.user_type_id=st.user_type_id - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects AS so ON si.object_id = so.object_id - AND so.is_ms_shipped = 0 - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s ON s.schema_id = so.schema_id - WHERE si.index_id not in (0,1) ' - + CASE WHEN @ObjectID IS NOT NULL - THEN N' AND si.object_id=' + CAST(@ObjectID AS NVARCHAR(30)) - ELSE N'' END - + N'OPTION (RECOMPILE);'; - - IF @dsql IS NULL - RAISERROR('@dsql is null',16,1); - - RAISERROR (N'Inserting data into #IndexColumns for nonclustered indexes',0,1) WITH NOWAIT; - IF @Debug = 1 - BEGIN - PRINT SUBSTRING(@dsql, 0, 4000); - PRINT SUBSTRING(@dsql, 4000, 8000); - PRINT SUBSTRING(@dsql, 8000, 12000); - PRINT SUBSTRING(@dsql, 12000, 16000); - PRINT SUBSTRING(@dsql, 16000, 20000); - PRINT SUBSTRING(@dsql, 20000, 24000); - PRINT SUBSTRING(@dsql, 24000, 28000); - PRINT SUBSTRING(@dsql, 28000, 32000); - PRINT SUBSTRING(@dsql, 32000, 36000); - PRINT SUBSTRING(@dsql, 36000, 40000); - END; - INSERT #IndexColumns ( database_id, [schema_name], [object_id], index_id, key_ordinal, is_included_column, is_descending_key, partition_ordinal, - column_name, system_type_name, max_length, precision, scale, collation_name, is_nullable, is_identity, is_computed, - is_replicated, is_sparse, is_filestream ) - EXEC sp_executesql @dsql; - - SET @dsql = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT ' + CAST(@DatabaseID AS NVARCHAR(10)) + N' AS database_id, - so.object_id, - si.index_id, - si.type, - @i_DatabaseName AS database_name, - COALESCE(sc.NAME, ''Unknown'') AS [schema_name], - COALESCE(so.name, ''Unknown'') AS [object_name], - COALESCE(si.name, ''Unknown'') AS [index_name], - CASE WHEN so.[type] = CAST(''V'' AS CHAR(2)) THEN 1 ELSE 0 END, - si.is_unique, - si.is_primary_key, - si.is_unique_constraint, - CASE when si.type = 3 THEN 1 ELSE 0 END AS is_XML, - CASE when si.type = 4 THEN 1 ELSE 0 END AS is_spatial, - CASE when si.type = 6 THEN 1 ELSE 0 END AS is_NC_columnstore, - CASE when si.type = 5 then 1 else 0 end as is_CX_columnstore, - CASE when si.data_space_id = 0 then 1 else 0 end as is_in_memory_oltp, - si.is_disabled, - si.is_hypothetical, - si.is_padded, - si.fill_factor,' - + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N' - CASE WHEN si.filter_definition IS NOT NULL THEN si.filter_definition - ELSE N'''' - END AS filter_definition' ELSE N''''' AS filter_definition' END - + CASE - WHEN @OptimizeForSequentialKey = 1 - THEN N', si.optimize_for_sequential_key' - ELSE N', CONVERT(BIT, NULL) AS optimize_for_sequential_key' - END - + N', - ISNULL(us.user_seeks, 0), - ISNULL(us.user_scans, 0), - ISNULL(us.user_lookups, 0), - ISNULL(us.user_updates, 0), - us.last_user_seek, - us.last_user_scan, - us.last_user_lookup, - us.last_user_update, - so.create_date, - so.modify_date - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.indexes AS si WITH (NOLOCK) - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects AS so WITH (NOLOCK) ON si.object_id = so.object_id - AND so.is_ms_shipped = 0 /*Exclude objects shipped by Microsoft*/ - AND so.type <> ''TF'' /*Exclude table valued functions*/ - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas sc ON so.schema_id = sc.schema_id - LEFT JOIN sys.dm_db_index_usage_stats AS us WITH (NOLOCK) ON si.[object_id] = us.[object_id] - AND si.index_id = us.index_id - AND us.database_id = ' + CAST(@DatabaseID AS NVARCHAR(10)) + N' - WHERE si.[type] IN ( 0, 1, 2, 3, 4, 5, 6 ) - /* Heaps, clustered, nonclustered, XML, spatial, Cluster Columnstore, NC Columnstore */ ' + - CASE WHEN @TableName IS NOT NULL THEN N' and so.name=' + QUOTENAME(@TableName,N'''') + N' ' ELSE N'' END + - CASE WHEN ( @IncludeInactiveIndexes = 0 - AND @Mode IN (0, 4) - AND @TableName IS NULL ) - THEN N'AND ( us.user_seeks + us.user_scans + us.user_lookups + us.user_updates ) > 0' - ELSE N'' - END - + N'OPTION ( RECOMPILE ); - '; - IF @dsql IS NULL - RAISERROR('@dsql is null',16,1); - - RAISERROR (N'Inserting data into #IndexSanity',0,1) WITH NOWAIT; - IF @Debug = 1 - BEGIN - PRINT SUBSTRING(@dsql, 0, 4000); - PRINT SUBSTRING(@dsql, 4000, 8000); - PRINT SUBSTRING(@dsql, 8000, 12000); - PRINT SUBSTRING(@dsql, 12000, 16000); - PRINT SUBSTRING(@dsql, 16000, 20000); - PRINT SUBSTRING(@dsql, 20000, 24000); - PRINT SUBSTRING(@dsql, 24000, 28000); - PRINT SUBSTRING(@dsql, 28000, 32000); - PRINT SUBSTRING(@dsql, 32000, 36000); - PRINT SUBSTRING(@dsql, 36000, 40000); - END; - INSERT #IndexSanity ( [database_id], [object_id], [index_id], [index_type], [database_name], [schema_name], [object_name], - index_name, is_indexed_view, is_unique, is_primary_key, is_unique_constraint, is_XML, is_spatial, is_NC_columnstore, is_CX_columnstore, is_in_memory_oltp, - is_disabled, is_hypothetical, is_padded, fill_factor, filter_definition, [optimize_for_sequential_key], user_seeks, user_scans, - user_lookups, user_updates, last_user_seek, last_user_scan, last_user_lookup, last_user_update, - create_date, modify_date ) - EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; - - - RAISERROR (N'Checking partition count',0,1) WITH NOWAIT; - IF @BringThePain = 0 AND @SkipPartitions = 0 AND @TableName IS NULL - BEGIN - /* Count the total number of partitions */ - SET @dsql = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @RowcountOUT = SUM(1) FROM ' + QUOTENAME(@DatabaseName) + '.sys.partitions WHERE partition_number > 1 OPTION ( RECOMPILE );'; - EXEC sp_executesql @dsql, N'@RowcountOUT BIGINT OUTPUT', @RowcountOUT = @Rowcount OUTPUT; - IF @Rowcount > 100 - BEGIN - RAISERROR (N'Setting @SkipPartitions = 1 because > 100 partitions were found. To check them, you must set @BringThePain = 1.',0,1) WITH NOWAIT; - SET @SkipPartitions = 1; - INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, URL, details, index_definition, - index_usage_summary, index_size_summary ) - VALUES ( 1, 0 , - 'Some Checks Were Skipped', - '@SkipPartitions Forced to 1', - 'http://FirstResponderKit.org', CAST(@Rowcount AS NVARCHAR(50)) + ' partitions found. To analyze them, use @BringThePain = 1.', 'We try to keep things quick - and warning, running @BringThePain = 1 can take tens of minutes.', '', '' - ); - END; - END; - - - - IF (@SkipPartitions = 0) - BEGIN - IF (SELECT LEFT(@SQLServerProductVersion, - CHARINDEX('.',@SQLServerProductVersion,0)-1 )) <= 2147483647 --Make change here - BEGIN - - RAISERROR (N'Preferring non-2012 syntax with LEFT JOIN to sys.dm_db_index_operational_stats',0,1) WITH NOWAIT; - - --NOTE: If you want to use the newer syntax for 2012+, you'll have to change 2147483647 to 11 on line ~819 - --This change was made because on a table with lots of paritions, the OUTER APPLY was crazy slow. - - -- get relevant columns from sys.dm_db_partition_stats, sys.partitions and sys.objects - DROP TABLE if exists #dm_db_partition_stats_etc - create table #dm_db_partition_stats_etc - ( - database_id smallint not null - , object_id int not null - , sname sysname NULL - , index_id int - , partition_number int - , partition_id bigint - , row_count bigint - , reserved_MB bigint - , reserved_LOB_MB bigint - , reserved_row_overflow_MB bigint - , lock_escalation_desc nvarchar(60) - , data_compression_desc nvarchar(60) - ) - - -- get relevant info from sys.dm_db_index_operational_stats - drop TABLE if exists #dm_db_index_operational_stats - create table #dm_db_index_operational_stats - ( - database_id smallint not null - , object_id int not null - , index_id int - , partition_number int - , hobt_id bigint - , leaf_insert_count bigint - , leaf_delete_count bigint - , leaf_update_count bigint - , range_scan_count bigint - , singleton_lookup_count bigint - , forwarded_fetch_count bigint - , lob_fetch_in_pages bigint - , lob_fetch_in_bytes bigint - , row_overflow_fetch_in_pages bigint - , row_overflow_fetch_in_bytes bigint - , row_lock_count bigint - , row_lock_wait_count bigint - , row_lock_wait_in_ms bigint - , page_lock_count bigint - , page_lock_wait_count bigint - , page_lock_wait_in_ms bigint - , index_lock_promotion_attempt_count bigint - , index_lock_promotion_count bigint - , page_latch_wait_count bigint - , page_latch_wait_in_ms bigint - , page_io_latch_wait_count bigint - , page_io_latch_wait_in_ms bigint - ) - - SET @dsql = N' - DECLARE @d VARCHAR(19) = CONVERT(VARCHAR(19), GETDATE(), 121) - RAISERROR (N''start getting data into #dm_db_partition_stats_etc at %s'',0,1, @d) WITH NOWAIT; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - INSERT INTO #dm_db_partition_stats_etc - ( - database_id, object_id, sname, index_id, partition_number, partition_id, row_count, reserved_MB, reserved_LOB_MB, reserved_row_overflow_MB, lock_escalation_desc, data_compression_desc - ) - SELECT ' + CAST(@DatabaseID AS NVARCHAR(10)) + N' AS database_id, - ps.object_id, - s.name as sname, - ps.index_id, - ps.partition_number, - ps.partition_id, - ps.row_count, - ps.reserved_page_count * 8. / 1024. AS reserved_MB, - ps.lob_reserved_page_count * 8. / 1024. AS reserved_LOB_MB, - ps.row_overflow_reserved_page_count * 8. / 1024. AS reserved_row_overflow_MB, - le.lock_escalation_desc, - ' + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N'par.data_compression_desc ' ELSE N'null as data_compression_desc ' END + N' -'; - - SET @dsql = @dsql + N' - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_partition_stats AS ps - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partitions AS par on ps.partition_id=par.partition_id - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects AS so ON ps.object_id = so.object_id - AND so.is_ms_shipped = 0 /*Exclude objects shipped by Microsoft*/ - AND so.type <> ''TF'' /*Exclude table valued functions*/ - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s ON s.schema_id = so.schema_id - OUTER APPLY (SELECT st.lock_escalation_desc - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.tables st - WHERE st.object_id = ps.object_id - AND ps.index_id < 2 ) le - WHERE 1=1 - ' + CASE WHEN @ObjectID IS NOT NULL THEN N'AND so.object_id=' + CAST(@ObjectID AS NVARCHAR(30)) + N' ' ELSE N' ' END + N' - ' + CASE WHEN @Filter = 2 THEN N'AND ps.reserved_page_count * 8./1024. > ' + CAST(@FilterMB AS NVARCHAR(5)) + N' ' ELSE N' ' END + N' - GROUP BY ps.object_id, - s.name, - ps.index_id, - ps.partition_number, - ps.partition_id, - ps.row_count, - ps.reserved_page_count, - ps.lob_reserved_page_count, - ps.row_overflow_reserved_page_count, - le.lock_escalation_desc, - ' + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N'par.data_compression_desc ' ELSE N'null as data_compression_desc ' END + N' - ORDER BY ps.object_id, ps.index_id, ps.partition_number - /*OPTION ( RECOMPILE );*/ - OPTION ( RECOMPILE , min_grant_percent = 1); - - SET @d = CONVERT(VARCHAR(19), GETDATE(), 121) - RAISERROR (N''start getting data into #dm_db_index_operational_stats at %s.'',0,1, @d) WITH NOWAIT; - - insert into #dm_db_index_operational_stats - ( - database_id - , object_id - , index_id - , partition_number - , hobt_id - , leaf_insert_count - , leaf_delete_count - , leaf_update_count - , range_scan_count - , singleton_lookup_count - , forwarded_fetch_count - , lob_fetch_in_pages - , lob_fetch_in_bytes - , row_overflow_fetch_in_pages - , row_overflow_fetch_in_bytes - , row_lock_count - , row_lock_wait_count - , row_lock_wait_in_ms - , page_lock_count - , page_lock_wait_count - , page_lock_wait_in_ms - , index_lock_promotion_attempt_count - , index_lock_promotion_count - , page_latch_wait_count - , page_latch_wait_in_ms - , page_io_latch_wait_count - , page_io_latch_wait_in_ms - ) - - select os.database_id - , os.object_id - , os.index_id - , os.partition_number - , os.hobt_id - , os.leaf_insert_count - , os.leaf_delete_count - , os.leaf_update_count - , os.range_scan_count - , os.singleton_lookup_count - , os.forwarded_fetch_count - , os.lob_fetch_in_pages - , os.lob_fetch_in_bytes - , os.row_overflow_fetch_in_pages - , os.row_overflow_fetch_in_bytes - , os.row_lock_count - , os.row_lock_wait_count - , os.row_lock_wait_in_ms - , os.page_lock_count - , os.page_lock_wait_count - , os.page_lock_wait_in_ms - , os.index_lock_promotion_attempt_count - , os.index_lock_promotion_count - , os.page_latch_wait_count - , os.page_latch_wait_in_ms - , os.page_io_latch_wait_count - , os.page_io_latch_wait_in_ms - from ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_index_operational_stats('+ CAST(@DatabaseID AS NVARCHAR(10)) +', NULL, NULL,NULL) AS os - OPTION ( RECOMPILE , min_grant_percent = 1); - - SET @d = CONVERT(VARCHAR(19), GETDATE(), 121) - RAISERROR (N''finished getting data into #dm_db_index_operational_stats at %s.'',0,1, @d) WITH NOWAIT; - '; - END; - ELSE - BEGIN - RAISERROR (N'Using 2012 syntax to query sys.dm_db_index_operational_stats',0,1) WITH NOWAIT; - --This is the syntax that will be used if you change 2147483647 to 11 on line ~819. - --If you have a lot of paritions and this suddenly starts running for a long time, change it back. - SET @dsql = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT ' + CAST(@DatabaseID AS NVARCHAR(10)) + N' AS database_id, - ps.object_id, - s.name, - ps.index_id, - ps.partition_number, - ps.row_count, - ps.reserved_page_count * 8. / 1024. AS reserved_MB, - ps.lob_reserved_page_count * 8. / 1024. AS reserved_LOB_MB, - ps.row_overflow_reserved_page_count * 8. / 1024. AS reserved_row_overflow_MB, - le.lock_escalation_desc, - ' + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N'par.data_compression_desc ' ELSE N'null as data_compression_desc' END + N', - SUM(os.leaf_insert_count), - SUM(os.leaf_delete_count), - SUM(os.leaf_update_count), - SUM(os.range_scan_count), - SUM(os.singleton_lookup_count), - SUM(os.forwarded_fetch_count), - SUM(os.lob_fetch_in_pages), - SUM(os.lob_fetch_in_bytes), - SUM(os.row_overflow_fetch_in_pages), - SUM(os.row_overflow_fetch_in_bytes), - SUM(os.row_lock_count), - SUM(os.row_lock_wait_count), - SUM(os.row_lock_wait_in_ms), - SUM(os.page_lock_count), - SUM(os.page_lock_wait_count), - SUM(os.page_lock_wait_in_ms), - SUM(os.index_lock_promotion_attempt_count), - SUM(os.index_lock_promotion_count), - SUM(os.page_latch_wait_count), - SUM(os.page_latch_wait_in_ms), - SUM(os.page_io_latch_wait_count), - SUM(os.page_io_latch_wait_in_ms)'; - - /* Get columnstore dictionary size - more info: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2585 */ - IF EXISTS (SELECT * FROM sys.all_objects WHERE name = 'column_store_dictionaries') - SET @dsql = @dsql + N' COALESCE((SELECT SUM (on_disk_size / 1024.0 / 1024) FROM ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_dictionaries dict WHERE dict.partition_id = ps.partition_id),0) AS reserved_dictionary_MB '; - ELSE - SET @dsql = @dsql + N' 0 AS reserved_dictionary_MB '; - - - SET @dsql = @dsql + N' - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_partition_stats AS ps - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partitions AS par on ps.partition_id=par.partition_id - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects AS so ON ps.object_id = so.object_id - AND so.is_ms_shipped = 0 /*Exclude objects shipped by Microsoft*/ - AND so.type <> ''TF'' /*Exclude table valued functions*/ - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s ON s.schema_id = so.schema_id - OUTER APPLY ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_index_operational_stats(' - + CAST(@DatabaseID AS NVARCHAR(10)) + N', ps.object_id, ps.index_id,ps.partition_number) AS os - OUTER APPLY (SELECT st.lock_escalation_desc - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.tables st - WHERE st.object_id = ps.object_id - AND ps.index_id < 2 ) le - WHERE 1=1 - ' + CASE WHEN @ObjectID IS NOT NULL THEN N'AND so.object_id=' + CAST(@ObjectID AS NVARCHAR(30)) + N' ' ELSE N' ' END + N' - ' + CASE WHEN @Filter = 2 THEN N'AND ps.reserved_page_count * 8./1024. > ' + CAST(@FilterMB AS NVARCHAR(5)) + N' ' ELSE N' ' END + ' - GROUP BY ps.object_id, - s.name, - ps.index_id, - ps.partition_number, - ps.partition_id, - ps.row_count, - ps.reserved_page_count, - ps.lob_reserved_page_count, - ps.row_overflow_reserved_page_count, - le.lock_escalation_desc, - ' + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N'par.data_compression_desc ' ELSE N'null as data_compression_desc ' END + N' - ORDER BY ps.object_id, ps.index_id, ps.partition_number - OPTION ( RECOMPILE ); - '; - END; - - IF @dsql IS NULL - RAISERROR('@dsql is null',16,1); - - RAISERROR (N'Inserting data into #IndexPartitionSanity',0,1) WITH NOWAIT; - IF @Debug = 1 - BEGIN - PRINT SUBSTRING(@dsql, 0, 4000); - PRINT SUBSTRING(@dsql, 4000, 8000); - PRINT SUBSTRING(@dsql, 8000, 12000); - PRINT SUBSTRING(@dsql, 12000, 16000); - PRINT SUBSTRING(@dsql, 16000, 20000); - PRINT SUBSTRING(@dsql, 20000, 24000); - PRINT SUBSTRING(@dsql, 24000, 28000); - PRINT SUBSTRING(@dsql, 28000, 32000); - PRINT SUBSTRING(@dsql, 32000, 36000); - PRINT SUBSTRING(@dsql, 36000, 40000); - END; - EXEC sp_executesql @dsql; - INSERT #IndexPartitionSanity ( [database_id], - [object_id], - [schema_name], - index_id, - partition_number, - row_count, - reserved_MB, - reserved_LOB_MB, - reserved_row_overflow_MB, - lock_escalation_desc, - data_compression_desc, - leaf_insert_count, - leaf_delete_count, - leaf_update_count, - range_scan_count, - singleton_lookup_count, - forwarded_fetch_count, - lob_fetch_in_pages, - lob_fetch_in_bytes, - row_overflow_fetch_in_pages, - row_overflow_fetch_in_bytes, - row_lock_count, - row_lock_wait_count, - row_lock_wait_in_ms, - page_lock_count, - page_lock_wait_count, - page_lock_wait_in_ms, - index_lock_promotion_attempt_count, - index_lock_promotion_count, - page_latch_wait_count, - page_latch_wait_in_ms, - page_io_latch_wait_count, - page_io_latch_wait_in_ms, - reserved_dictionary_MB) - select h.database_id, h.object_id, h.sname, h.index_id, h.partition_number, h.row_count, h.reserved_MB, h.reserved_LOB_MB, h.reserved_row_overflow_MB, h.lock_escalation_desc, h.data_compression_desc, - SUM(os.leaf_insert_count), - SUM(os.leaf_delete_count), - SUM(os.leaf_update_count), - SUM(os.range_scan_count), - SUM(os.singleton_lookup_count), - SUM(os.forwarded_fetch_count), - SUM(os.lob_fetch_in_pages), - SUM(os.lob_fetch_in_bytes), - SUM(os.row_overflow_fetch_in_pages), - SUM(os.row_overflow_fetch_in_bytes), - SUM(os.row_lock_count), - SUM(os.row_lock_wait_count), - SUM(os.row_lock_wait_in_ms), - SUM(os.page_lock_count), - SUM(os.page_lock_wait_count), - SUM(os.page_lock_wait_in_ms), - SUM(os.index_lock_promotion_attempt_count), - SUM(os.index_lock_promotion_count), - SUM(os.page_latch_wait_count), - SUM(os.page_latch_wait_in_ms), - SUM(os.page_io_latch_wait_count), - SUM(os.page_io_latch_wait_in_ms) - ,COALESCE((SELECT SUM (dict.on_disk_size / 1024.0 / 1024) FROM sys.column_store_dictionaries dict WHERE dict.partition_id = h.partition_id),0) AS reserved_dictionary_MB - from #dm_db_partition_stats_etc h - left JOIN #dm_db_index_operational_stats as os ON - h.object_id=os.object_id and h.index_id=os.index_id and h.partition_number=os.partition_number - group by h.database_id, h.object_id, h.sname, h.index_id, h.partition_number, h.partition_id, h.row_count, h.reserved_MB, h.reserved_LOB_MB, h.reserved_row_overflow_MB, h.lock_escalation_desc, h.data_compression_desc - - END; --End Check For @SkipPartitions = 0 - - - - RAISERROR (N'Inserting data into #MissingIndexes',0,1) WITH NOWAIT; - SET @dsql=N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' - - - SET @dsql = @dsql + 'WITH ColumnNamesWithDataTypes AS(SELECT id.index_handle,id.object_id,cn.IndexColumnType,STUFF((SELECT '', '' + cn_inner.ColumnName + '' '' + - N'' {'' + CASE WHEN ty.name IN ( ''varchar'', ''char'' ) THEN ty.name + ''('' + CASE WHEN co.max_length = -1 THEN ''max'' ELSE CAST(co.max_length AS VARCHAR(25)) END + '')'' - WHEN ty.name IN ( ''nvarchar'', ''nchar'' ) THEN ty.name + ''('' + CASE WHEN co.max_length = -1 THEN ''max'' ELSE CAST(co.max_length / 2 AS VARCHAR(25)) END + '')'' - WHEN ty.name IN ( ''decimal'', ''numeric'' ) THEN ty.name + ''('' + CAST(co.precision AS VARCHAR(25)) + '', '' + CAST(co.scale AS VARCHAR(25)) + '')'' - WHEN ty.name IN ( ''datetime2'' ) THEN ty.name + ''('' + CAST(co.scale AS VARCHAR(25)) + '')'' - ELSE ty.name END + ''}'' - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_details AS id_inner - CROSS APPLY( - SELECT LTRIM(RTRIM(v.value(''(./text())[1]'', ''varchar(max)''))) AS ColumnName, ''Equality'' AS IndexColumnType - FROM (VALUES (CONVERT(XML, N'''' + REPLACE((SELECT CAST(id_inner.equality_columns AS nvarchar(max)) FOR XML PATH('''')), N'','', N'''') + N''''))) x(n) - CROSS APPLY n.nodes(''x'') node(v) - UNION ALL - SELECT LTRIM(RTRIM(v.value(N''(./text())[1]'', ''varchar(max)''))) AS ColumnName, ''Inequality'' AS IndexColumnType - FROM (VALUES (CONVERT(XML, N'''' + REPLACE((SELECT CAST(id_inner.inequality_columns AS nvarchar(max)) FOR XML PATH('''')), N'','', N'''') + N''''))) x(n) - CROSS APPLY n.nodes(''x'') node(v) - UNION ALL - SELECT LTRIM(RTRIM(v.value(''(./text())[1]'', ''varchar(max)''))) AS ColumnName, ''Included'' AS IndexColumnType - FROM (VALUES (CONVERT(XML, N'''' + REPLACE((SELECT CAST(id_inner.included_columns AS nvarchar(max)) FOR XML PATH('''')), N'','', N'''') + N''''))) x(n) - CROSS APPLY n.nodes(''x'') node(v) - )AS cn_inner' - + /*split the string otherwise dsql cuts some of it out*/ - ' JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns AS co ON co.object_id = id_inner.object_id AND ''['' + co.name + '']'' = cn_inner.ColumnName - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.types AS ty ON ty.user_type_id = co.user_type_id - WHERE id_inner.index_handle = id.index_handle - AND id_inner.object_id = id.object_id - AND cn_inner.IndexColumnType = cn.IndexColumnType - FOR XML PATH('''') - ),1,1,'''') AS ReplaceColumnNames - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_details AS id - CROSS APPLY( - SELECT LTRIM(RTRIM(v.value(''(./text())[1]'', ''varchar(max)''))) AS ColumnName, ''Equality'' AS IndexColumnType - FROM (VALUES (CONVERT(XML, N'''' + REPLACE((SELECT CAST(id.equality_columns AS nvarchar(max)) FOR XML PATH('''')), N'','', N'''') + N''''))) x(n) - CROSS APPLY n.nodes(''x'') node(v) - UNION ALL - SELECT LTRIM(RTRIM(v.value(''(./text())[1]'', ''varchar(max)''))) AS ColumnName, ''Inequality'' AS IndexColumnType - FROM (VALUES (CONVERT(XML, N'''' + REPLACE((SELECT CAST(id.inequality_columns AS nvarchar(max)) FOR XML PATH('''')), N'','', N'''') + N''''))) x(n) - CROSS APPLY n.nodes(''x'') node(v) - UNION ALL - SELECT LTRIM(RTRIM(v.value(''(./text())[1]'', ''varchar(max)''))) AS ColumnName, ''Included'' AS IndexColumnType - FROM (VALUES (CONVERT(XML, N'''' + REPLACE((SELECT CAST(id.included_columns AS nvarchar(max)) FOR XML PATH('''')), N'','', N'''') + N''''))) x(n) - CROSS APPLY n.nodes(''x'') node(v) - )AS cn - GROUP BY id.index_handle,id.object_id,cn.IndexColumnType - ) - SELECT id.database_id, id.object_id, @i_DatabaseName, sc.[name], so.[name], id.statement , gs.avg_total_user_cost, - gs.avg_user_impact, gs.user_seeks, gs.user_scans, gs.unique_compiles, id.equality_columns, id.inequality_columns, id.included_columns, - ( - SELECT ColumnNamesWithDataTypes.ReplaceColumnNames - FROM ColumnNamesWithDataTypes WHERE ColumnNamesWithDataTypes.index_handle = id.index_handle - AND ColumnNamesWithDataTypes.object_id = id.object_id - AND ColumnNamesWithDataTypes.IndexColumnType = ''Equality'' - ) AS equality_columns_with_data_type - ,( - SELECT ColumnNamesWithDataTypes.ReplaceColumnNames - FROM ColumnNamesWithDataTypes WHERE ColumnNamesWithDataTypes.index_handle = id.index_handle - AND ColumnNamesWithDataTypes.object_id = id.object_id - AND ColumnNamesWithDataTypes.IndexColumnType = ''Inequality'' - ) AS inequality_columns_with_data_type - ,( - SELECT ColumnNamesWithDataTypes.ReplaceColumnNames - FROM ColumnNamesWithDataTypes WHERE ColumnNamesWithDataTypes.index_handle = id.index_handle - AND ColumnNamesWithDataTypes.object_id = id.object_id - AND ColumnNamesWithDataTypes.IndexColumnType = ''Included'' - ) AS included_columns_with_data_type ' - - /* Get the sample query plan if it's available, and if there are less than 1,000 rows in the DMV: */ - IF NOT EXISTS - ( - SELECT - 1/0 - FROM sys.all_objects AS o - WHERE o.name = 'dm_db_missing_index_group_stats_query' - ) - SELECT - @dsql += N' , NULL AS sample_query_plan ' - ELSE - BEGIN - /* The DMV is only supposed to have 600 rows in it. If it's got more, - they could see performance slowdowns - see Github #3085. */ - DECLARE @MissingIndexPlans BIGINT; - SET @StringToExecute = N'SELECT @MissingIndexPlans = COUNT(*) FROM ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_group_stats_query;' - EXEC sp_executesql @StringToExecute, N'@MissingIndexPlans BIGINT OUT', @MissingIndexPlans OUT; - - IF @MissingIndexPlans > 1000 - BEGIN - SELECT @dsql += N' , NULL AS sample_query_plan /* Over 1000 plans found, skipping */ '; - RAISERROR (N'Over 1000 plans found in sys.dm_db_missing_index_group_stats_query - your SQL Server is hitting a bug: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/3085',0,1) WITH NOWAIT; - END - ELSE - SELECT - @dsql += N' - , sample_query_plan = - ( - SELECT TOP (1) - p.query_plan - FROM sys.dm_db_missing_index_group_stats gs - CROSS APPLY - ( - SELECT TOP (1) - s.plan_handle - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_group_stats_query q - INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.dm_exec_query_stats s - ON q.query_plan_hash = s.query_plan_hash - WHERE gs.group_handle = q.group_handle - ORDER BY (q.user_seeks + q.user_scans) DESC, s.total_logical_reads DESC - ) q2 - CROSS APPLY sys.dm_exec_query_plan(q2.plan_handle) p - WHERE ig.index_group_handle = gs.group_handle - ) ' - END - - - - SET @dsql = @dsql + N'FROM ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_groups ig - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_details id ON ig.index_handle = id.index_handle - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_missing_index_group_stats gs ON ig.index_group_handle = gs.group_handle - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects so on - id.object_id=so.object_id - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas sc on - so.schema_id=sc.schema_id - WHERE id.database_id = ' + CAST(@DatabaseID AS NVARCHAR(30)) + ' - ' + CASE WHEN @ObjectID IS NULL THEN N'' - ELSE N'and id.object_id=' + CAST(@ObjectID AS NVARCHAR(30)) - END + - N'OPTION (RECOMPILE);'; - - IF @dsql IS NULL - RAISERROR('@dsql is null',16,1); - IF @Debug = 1 - BEGIN - PRINT SUBSTRING(@dsql, 0, 4000); - PRINT SUBSTRING(@dsql, 4000, 8000); - PRINT SUBSTRING(@dsql, 8000, 12000); - PRINT SUBSTRING(@dsql, 12000, 16000); - PRINT SUBSTRING(@dsql, 16000, 20000); - PRINT SUBSTRING(@dsql, 20000, 24000); - PRINT SUBSTRING(@dsql, 24000, 28000); - PRINT SUBSTRING(@dsql, 28000, 32000); - PRINT SUBSTRING(@dsql, 32000, 36000); - PRINT SUBSTRING(@dsql, 36000, 40000); - END; - INSERT #MissingIndexes ( [database_id], [object_id], [database_name], [schema_name], [table_name], [statement], avg_total_user_cost, - avg_user_impact, user_seeks, user_scans, unique_compiles, equality_columns, - inequality_columns, included_columns, equality_columns_with_data_type, inequality_columns_with_data_type, - included_columns_with_data_type, sample_query_plan) - EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; - - SET @dsql = N' - SELECT DB_ID(N' + QUOTENAME(@DatabaseName,'''') + N') AS [database_id], - @i_DatabaseName AS database_name, - s.name, - fk_object.name AS foreign_key_name, - parent_object.[object_id] AS parent_object_id, - parent_object.name AS parent_object_name, - referenced_object.[object_id] AS referenced_object_id, - referenced_object.name AS referenced_object_name, - fk.is_disabled, - fk.is_not_trusted, - fk.is_not_for_replication, - parent.fk_columns, - referenced.fk_columns, - [update_referential_action_desc], - [delete_referential_action_desc] - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.foreign_keys fk - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects fk_object ON fk.object_id=fk_object.object_id - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects parent_object ON fk.parent_object_id=parent_object.object_id - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects referenced_object ON fk.referenced_object_id=referenced_object.object_id - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s ON fk.schema_id=s.schema_id - CROSS APPLY ( SELECT STUFF( (SELECT N'', '' + c_parent.name AS fk_columns - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.foreign_key_columns fkc - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns c_parent ON fkc.parent_object_id=c_parent.[object_id] - AND fkc.parent_column_id=c_parent.column_id - WHERE fk.parent_object_id=fkc.parent_object_id - AND fk.[object_id]=fkc.constraint_object_id - ORDER BY fkc.constraint_column_id - FOR XML PATH('''') , - TYPE).value(''.'', ''nvarchar(max)''), 1, 1, '''')/*This is how we remove the first comma*/ ) parent ( fk_columns ) - CROSS APPLY ( SELECT STUFF( (SELECT N'', '' + c_referenced.name AS fk_columns - FROM ' + QUOTENAME(@DatabaseName) + N'.sys. foreign_key_columns fkc - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns c_referenced ON fkc.referenced_object_id=c_referenced.[object_id] - AND fkc.referenced_column_id=c_referenced.column_id - WHERE fk.referenced_object_id=fkc.referenced_object_id - and fk.[object_id]=fkc.constraint_object_id - ORDER BY fkc.constraint_column_id /*order by col name, we don''t have anything better*/ - FOR XML PATH('''') , - TYPE).value(''.'', ''nvarchar(max)''), 1, 1, '''') ) referenced ( fk_columns ) - ' + CASE WHEN @ObjectID IS NOT NULL THEN - 'WHERE fk.parent_object_id=' + CAST(@ObjectID AS NVARCHAR(30)) + N' OR fk.referenced_object_id=' + CAST(@ObjectID AS NVARCHAR(30)) + N' ' - ELSE N' ' END + ' - ORDER BY parent_object_name, foreign_key_name - OPTION (RECOMPILE);'; - IF @dsql IS NULL - RAISERROR('@dsql is null',16,1); - - RAISERROR (N'Inserting data into #ForeignKeys',0,1) WITH NOWAIT; - IF @Debug = 1 - BEGIN - PRINT SUBSTRING(@dsql, 0, 4000); - PRINT SUBSTRING(@dsql, 4000, 8000); - PRINT SUBSTRING(@dsql, 8000, 12000); - PRINT SUBSTRING(@dsql, 12000, 16000); - PRINT SUBSTRING(@dsql, 16000, 20000); - PRINT SUBSTRING(@dsql, 20000, 24000); - PRINT SUBSTRING(@dsql, 24000, 28000); - PRINT SUBSTRING(@dsql, 28000, 32000); - PRINT SUBSTRING(@dsql, 32000, 36000); - PRINT SUBSTRING(@dsql, 36000, 40000); - END; - INSERT #ForeignKeys ( [database_id], [database_name], [schema_name], foreign_key_name, parent_object_id,parent_object_name, referenced_object_id, referenced_object_name, - is_disabled, is_not_trusted, is_not_for_replication, parent_fk_columns, referenced_fk_columns, - [update_referential_action_desc], [delete_referential_action_desc] ) - EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; - - - SET @dsql = N' - SELECT - DB_ID(N' + QUOTENAME(@DatabaseName,'''') + N') AS [database_id], - @i_DatabaseName AS database_name, - foreign_key_schema = - s.name, - foreign_key_name = - fk.name, - foreign_key_table = - OBJECT_NAME(fk.parent_object_id, DB_ID(N' + QUOTENAME(@DatabaseName,'''') + N')), - fk.parent_object_id, - foreign_key_referenced_table = - OBJECT_NAME(fk.referenced_object_id, DB_ID(N' + QUOTENAME(@DatabaseName,'''') + N')), - fk.referenced_object_id - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.foreign_keys fk - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s - ON s.schema_id = fk.schema_id - WHERE fk.is_disabled = 0 - AND EXISTS - ( - SELECT - 1/0 - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.foreign_key_columns fkc - WHERE fkc.constraint_object_id = fk.object_id - AND NOT EXISTS - ( - SELECT - 1/0 - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.index_columns ic - WHERE ic.object_id = fkc.parent_object_id - AND ic.column_id = fkc.parent_column_id - AND ic.index_column_id = fkc.constraint_column_id - ) - ) - OPTION (RECOMPILE);' - IF @dsql IS NULL - RAISERROR('@dsql is null',16,1); - - RAISERROR (N'Inserting data into #ForeignKeys',0,1) WITH NOWAIT; - IF @Debug = 1 - BEGIN - PRINT SUBSTRING(@dsql, 0, 4000); - PRINT SUBSTRING(@dsql, 4000, 8000); - PRINT SUBSTRING(@dsql, 8000, 12000); - PRINT SUBSTRING(@dsql, 12000, 16000); - PRINT SUBSTRING(@dsql, 16000, 20000); - PRINT SUBSTRING(@dsql, 20000, 24000); - PRINT SUBSTRING(@dsql, 24000, 28000); - PRINT SUBSTRING(@dsql, 28000, 32000); - PRINT SUBSTRING(@dsql, 32000, 36000); - PRINT SUBSTRING(@dsql, 36000, 40000); - END; - - INSERT - #UnindexedForeignKeys - ( - database_id, - database_name, - schema_name, - foreign_key_name, - parent_object_name, - parent_object_id, - referenced_object_name, - referenced_object_id - ) - EXEC sys.sp_executesql - @dsql, - N'@i_DatabaseName sysname', - @DatabaseName; - - - IF @SkipStatistics = 0 /* AND DB_NAME() = @DatabaseName /* Can only get stats in the current database - see https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/1947 */ */ - BEGIN - IF ((PARSENAME(@SQLServerProductVersion, 4) >= 12) - OR (PARSENAME(@SQLServerProductVersion, 4) = 11 AND PARSENAME(@SQLServerProductVersion, 2) >= 3000) - OR (PARSENAME(@SQLServerProductVersion, 4) = 10 AND PARSENAME(@SQLServerProductVersion, 3) = 50 AND PARSENAME(@SQLServerProductVersion, 2) >= 2500)) - 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, - 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) - 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, - ISNULL(i.name, ''System Or User Statistic'') AS index_name, - ca.column_names AS column_names, - s.name AS statistics_name, - CONVERT(DATETIME, ddsp.last_updated) AS last_statistics_update, - DATEDIFF(DAY, ddsp.last_updated, GETDATE()) AS days_since_last_stats_update, - ddsp.rows, - ddsp.rows_sampled, - CAST(ddsp.rows_sampled / ( 1. * NULLIF(ddsp.rows, 0) ) * 100 AS DECIMAL(18, 1)) AS percent_sampled, - ddsp.steps AS histogram_steps, - ddsp.modification_counter, - CASE WHEN ddsp.modification_counter > 0 - THEN CAST(ddsp.modification_counter / ( 1. * NULLIF(ddsp.rows, 0) ) * 100 AS DECIMAL(18, 1)) - ELSE ddsp.modification_counter - END AS percent_modifications, - CASE WHEN ddsp.rows < 500 THEN 500 - ELSE CAST(( ddsp.rows * .20 ) + 500 AS INT) - 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 - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.stats AS s - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects obj - ON s.object_id = obj.object_id - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas sch - ON sch.schema_id = obj.schema_id - LEFT JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.indexes AS i - ON i.object_id = s.object_id - AND i.index_id = s.stats_id - OUTER APPLY ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_stats_properties(s.object_id, s.stats_id) AS ddsp - CROSS APPLY ( SELECT STUFF((SELECT '', '' + c.name - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.stats_columns AS sc - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns AS c - ON sc.column_id = c.column_id AND sc.object_id = c.object_id - WHERE sc.stats_id = s.stats_id AND sc.object_id = s.object_id - ORDER BY sc.stats_column_id - FOR XML PATH(''''), TYPE).value(''.'', ''nvarchar(max)''), 1, 2, '''') - ) ca (column_names) - WHERE obj.is_ms_shipped = 0 - OPTION (RECOMPILE);'; - - IF @dsql IS NULL - RAISERROR('@dsql is null',16,1); - - RAISERROR (N'Inserting data into #Statistics',0,1) WITH NOWAIT; - IF @Debug = 1 - BEGIN - PRINT SUBSTRING(@dsql, 0, 4000); - PRINT SUBSTRING(@dsql, 4000, 8000); - PRINT SUBSTRING(@dsql, 8000, 12000); - PRINT SUBSTRING(@dsql, 12000, 16000); - PRINT SUBSTRING(@dsql, 16000, 20000); - PRINT SUBSTRING(@dsql, 20000, 24000); - PRINT SUBSTRING(@dsql, 24000, 28000); - PRINT SUBSTRING(@dsql, 28000, 32000); - PRINT SUBSTRING(@dsql, 32000, 36000); - PRINT SUBSTRING(@dsql, 36000, 40000); - END; - - EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; - END; - ELSE - 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, - 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) - 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, - ISNULL(i.name, ''System Or User Statistic'') AS index_name, - ca.column_names AS column_names, - s.name AS statistics_name, - CONVERT(DATETIME, STATS_DATE(s.object_id, s.stats_id)) AS last_statistics_update, - DATEDIFF(DAY, STATS_DATE(s.object_id, s.stats_id), GETDATE()) AS days_since_last_stats_update, - si.rowcnt, - si.rowmodctr, - CASE WHEN si.rowmodctr > 0 THEN CAST(si.rowmodctr / ( 1. * NULLIF(si.rowcnt, 0) ) * 100 AS DECIMAL(18, 1)) - ELSE si.rowmodctr - END AS percent_modifications, - CASE WHEN si.rowcnt < 500 THEN 500 - ELSE CAST(( si.rowcnt * .20 ) + 500 AS INT) - 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, - ' - + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' - THEN N's.has_filter, - s.filter_definition' - ELSE N'NULL AS has_filter, - NULL AS filter_definition' END - + N' - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.stats AS s - INNER HASH JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.sysindexes si - ON si.name = s.name AND s.object_id = si.id - INNER HASH JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects obj - ON s.object_id = obj.object_id - INNER HASH JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas sch - ON sch.schema_id = obj.schema_id - LEFT HASH JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.indexes AS i - ON i.object_id = s.object_id - AND i.index_id = s.stats_id - CROSS APPLY ( SELECT STUFF((SELECT '', '' + c.name - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.stats_columns AS sc - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns AS c - ON sc.column_id = c.column_id AND sc.object_id = c.object_id - WHERE sc.stats_id = s.stats_id AND sc.object_id = s.object_id - ORDER BY sc.stats_column_id - FOR XML PATH(''''), TYPE).value(''.'', ''nvarchar(max)''), 1, 2, '''') - ) ca (column_names) - WHERE obj.is_ms_shipped = 0 - AND si.rowcnt > 0 - OPTION (RECOMPILE);'; - - IF @dsql IS NULL - RAISERROR('@dsql is null',16,1); - - RAISERROR (N'Inserting data into #Statistics',0,1) WITH NOWAIT; - IF @Debug = 1 - BEGIN - PRINT SUBSTRING(@dsql, 0, 4000); - PRINT SUBSTRING(@dsql, 4000, 8000); - PRINT SUBSTRING(@dsql, 8000, 12000); - PRINT SUBSTRING(@dsql, 12000, 16000); - PRINT SUBSTRING(@dsql, 16000, 20000); - PRINT SUBSTRING(@dsql, 20000, 24000); - PRINT SUBSTRING(@dsql, 24000, 28000); - PRINT SUBSTRING(@dsql, 28000, 32000); - PRINT SUBSTRING(@dsql, 32000, 36000); - PRINT SUBSTRING(@dsql, 36000, 40000); - END; - - EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; - END; - - END; - - IF (PARSENAME(@SQLServerProductVersion, 4) >= 10) - BEGIN - RAISERROR (N'Gathering Computed Column Info.',0,1) WITH NOWAIT; - SET @dsql=N'SELECT DB_ID(@i_DatabaseName) AS [database_id], - @i_DatabaseName AS database_name, - t.name AS table_name, - s.name AS schema_name, - c.name AS column_name, - cc.is_nullable, - cc.definition, - cc.uses_database_collation, - cc.is_persisted, - cc.is_computed, - CASE WHEN cc.definition LIKE ''%|].|[%'' ESCAPE ''|'' THEN 1 ELSE 0 END AS is_function, - ''ALTER TABLE '' + QUOTENAME(s.name) + ''.'' + QUOTENAME(t.name) + - '' ADD '' + QUOTENAME(c.name) + '' AS '' + cc.definition + - CASE WHEN is_persisted = 1 THEN '' PERSISTED'' ELSE '''' END + '';'' COLLATE DATABASE_DEFAULT AS [column_definition] - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.computed_columns AS cc - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns AS c - ON cc.object_id = c.object_id - AND cc.column_id = c.column_id - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.tables AS t - ON t.object_id = cc.object_id - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s - ON s.schema_id = t.schema_id - OPTION (RECOMPILE);'; - - IF @dsql IS NULL RAISERROR('@dsql is null',16,1); - - INSERT #ComputedColumns - ( database_id, [database_name], table_name, schema_name, column_name, is_nullable, definition, - uses_database_collation, is_persisted, is_computed, is_function, column_definition ) - EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; - - END; - - RAISERROR (N'Gathering Trace Flag Information',0,1) WITH NOWAIT; - INSERT #TraceStatus - EXEC ('DBCC TRACESTATUS(-1) WITH NO_INFOMSGS'); - - IF (PARSENAME(@SQLServerProductVersion, 4) >= 13) - BEGIN - RAISERROR (N'Gathering Temporal Table Info',0,1) WITH NOWAIT; - SET @dsql=N'SELECT ' + QUOTENAME(@DatabaseName,'''') + N' AS database_name, - DB_ID(N' + QUOTENAME(@DatabaseName,'''') + N') AS [database_id], - s.name AS schema_name, - t.name AS table_name, - oa.hsn as history_schema_name, - oa.htn AS history_table_name, - c1.name AS start_column_name, - c2.name AS end_column_name, - p.name AS period_name - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.periods AS p - INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.tables AS t - ON p.object_id = t.object_id - INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns AS c1 - ON t.object_id = c1.object_id - AND p.start_column_id = c1.column_id - INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns AS c2 - ON t.object_id = c2.object_id - AND p.end_column_id = c2.column_id - INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s - ON t.schema_id = s.schema_id - CROSS APPLY ( SELECT s2.name as hsn, t2.name htn - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.tables AS t2 - INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s2 - ON t2.schema_id = s2.schema_id - WHERE t2.object_id = t.history_table_id - AND t2.temporal_type = 1 /*History table*/ ) AS oa - WHERE t.temporal_type IN ( 2, 4 ) /*BOL currently points to these types, but has no definition for 4*/ - OPTION (RECOMPILE); - '; - - IF @dsql IS NULL - RAISERROR('@dsql is null',16,1); - - INSERT #TemporalTables ( database_name, database_id, schema_name, table_name, history_schema_name, - history_table_name, start_column_name, end_column_name, period_name ) - - EXEC sp_executesql @dsql; - - SET @dsql=N'SELECT DB_ID(@i_DatabaseName) AS [database_id], - @i_DatabaseName AS database_name, - t.name AS table_name, - s.name AS schema_name, - cc.name AS constraint_name, - cc.is_disabled, - cc.definition, - cc.uses_database_collation, - cc.is_not_trusted, - CASE WHEN cc.definition LIKE ''%|].|[%'' ESCAPE ''|'' THEN 1 ELSE 0 END AS is_function, - ''ALTER TABLE '' + QUOTENAME(s.name) + ''.'' + QUOTENAME(t.name) + - '' ADD CONSTRAINT '' + QUOTENAME(cc.name) + '' CHECK '' + cc.definition + '';'' COLLATE DATABASE_DEFAULT AS [column_definition] - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.check_constraints AS cc - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.tables AS t - ON t.object_id = cc.parent_object_id - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s - ON s.schema_id = t.schema_id - OPTION (RECOMPILE);'; - - INSERT #CheckConstraints - ( database_id, [database_name], table_name, schema_name, constraint_name, is_disabled, definition, - uses_database_collation, is_not_trusted, is_function, column_definition ) - EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; - - - SET @dsql=N'SELECT DB_ID(@i_DatabaseName) AS [database_id], - @i_DatabaseName AS database_name, - s.name AS missing_schema_name, - t.name AS missing_table_name, - i.name AS missing_index_name, - c.name AS missing_column_name - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.sql_expression_dependencies AS sed - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.tables AS t - ON t.object_id = sed.referenced_id - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s - ON t.schema_id = s.schema_id - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.indexes AS i - ON i.object_id = sed.referenced_id - AND i.index_id = sed.referencing_minor_id - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns AS c - ON c.object_id = sed.referenced_id - AND c.column_id = sed.referenced_minor_id - WHERE sed.referencing_class = 7 - AND sed.referenced_class = 1 - AND i.has_filter = 1 - AND NOT EXISTS ( SELECT 1/0 - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.index_columns AS ic - WHERE ic.index_id = sed.referencing_minor_id - AND ic.column_id = sed.referenced_minor_id - AND ic.object_id = sed.referenced_id ) - OPTION(RECOMPILE);' - - INSERT #FilteredIndexes ( database_id, database_name, schema_name, table_name, index_name, column_name ) - EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; - - - END; - -END; -END TRY -BEGIN CATCH - RAISERROR (N'Failure populating temp tables.', 0,1) WITH NOWAIT; - - IF @dsql IS NOT NULL - BEGIN - SET @msg= 'Last @dsql: ' + @dsql; - RAISERROR(@msg, 0, 1) WITH NOWAIT; - END; - - SELECT @msg = @DatabaseName + N' database failed to process. ' + ERROR_MESSAGE(), @ErrorSeverity = ERROR_SEVERITY(), @ErrorState = ERROR_STATE(); - RAISERROR (@msg,@ErrorSeverity, @ErrorState )WITH NOWAIT; - - - WHILE @@trancount > 0 - ROLLBACK; - - RETURN; -END CATCH; - FETCH NEXT FROM c1 INTO @DatabaseName; -END; -DEALLOCATE c1; - - - - - - ----------------------------------------- ---STEP 2: PREP THE TEMP TABLES ---EVERY QUERY AFTER THIS GOES AGAINST TEMP TABLES ONLY. ----------------------------------------- - -RAISERROR (N'Updating #IndexSanity.key_column_names',0,1) WITH NOWAIT; -UPDATE #IndexSanity -SET key_column_names = D1.key_column_names -FROM #IndexSanity si - CROSS APPLY ( SELECT RTRIM(STUFF( (SELECT N', ' + c.column_name - + N' {' + system_type_name + - CASE max_length WHEN -1 THEN N' (max)' ELSE - CASE - WHEN system_type_name IN (N'char',N'varchar',N'binary',N'varbinary') THEN N' (' + CAST(max_length AS NVARCHAR(20)) + N')' - WHEN system_type_name IN (N'nchar',N'nvarchar') THEN N' (' + CAST(max_length/2 AS NVARCHAR(20)) + N')' - ELSE N' ' + CAST(max_length AS NVARCHAR(50)) - END - END - + N'}' - AS col_definition - FROM #IndexColumns c - WHERE c.database_id= si.database_id - AND c.schema_name = si.schema_name - AND c.object_id = si.object_id - AND c.index_id = si.index_id - AND c.is_included_column = 0 /*Just Keys*/ - AND c.key_ordinal > 0 /*Ignore non-key columns, such as partitioning keys*/ - ORDER BY c.object_id, c.index_id, c.key_ordinal - FOR XML PATH('') ,TYPE).value('.', 'nvarchar(max)'), 1, 1, '')) - ) D1 ( key_column_names ); - -RAISERROR (N'Updating #IndexSanity.partition_key_column_name',0,1) WITH NOWAIT; -UPDATE #IndexSanity -SET partition_key_column_name = D1.partition_key_column_name -FROM #IndexSanity si - CROSS APPLY ( SELECT RTRIM(STUFF( (SELECT N', ' + c.column_name AS col_definition - FROM #IndexColumns c - WHERE c.database_id= si.database_id - AND c.schema_name = si.schema_name - AND c.object_id = si.object_id - AND c.index_id = si.index_id - AND c.partition_ordinal <> 0 /*Just Partitioned Keys*/ - ORDER BY c.object_id, c.index_id, c.key_ordinal - FOR XML PATH('') , TYPE).value('.', 'nvarchar(max)'), 1, 1,''))) D1 - ( partition_key_column_name ); - -RAISERROR (N'Updating #IndexSanity.key_column_names_with_sort_order',0,1) WITH NOWAIT; -UPDATE #IndexSanity -SET key_column_names_with_sort_order = D2.key_column_names_with_sort_order -FROM #IndexSanity si - CROSS APPLY ( SELECT RTRIM(STUFF( (SELECT N', ' + c.column_name + CASE c.is_descending_key - WHEN 1 THEN N' DESC' - ELSE N'' - END - + N' {' + system_type_name + - CASE max_length WHEN -1 THEN N' (max)' ELSE - CASE - WHEN system_type_name IN (N'char',N'varchar',N'binary',N'varbinary') THEN N' (' + CAST(max_length AS NVARCHAR(20)) + N')' - WHEN system_type_name IN (N'nchar',N'nvarchar') THEN N' (' + CAST(max_length/2 AS NVARCHAR(20)) + N')' - ELSE N' ' + CAST(max_length AS NVARCHAR(50)) - END - END - + N'}' - AS col_definition - FROM #IndexColumns c - WHERE c.database_id= si.database_id - AND c.schema_name = si.schema_name - AND c.object_id = si.object_id - AND c.index_id = si.index_id - AND c.is_included_column = 0 /*Just Keys*/ - AND c.key_ordinal > 0 /*Ignore non-key columns, such as partitioning keys*/ - ORDER BY c.object_id, c.index_id, c.key_ordinal - FOR XML PATH('') , TYPE).value('.', 'nvarchar(max)'), 1, 1, '')) - ) D2 ( key_column_names_with_sort_order ); - -RAISERROR (N'Updating #IndexSanity.key_column_names_with_sort_order_no_types (for create tsql)',0,1) WITH NOWAIT; -UPDATE #IndexSanity -SET key_column_names_with_sort_order_no_types = D2.key_column_names_with_sort_order_no_types -FROM #IndexSanity si - CROSS APPLY ( SELECT RTRIM(STUFF( (SELECT N', ' + QUOTENAME(c.column_name) + CASE c.is_descending_key - WHEN 1 THEN N' DESC' - ELSE N'' - END AS col_definition - FROM #IndexColumns c - WHERE c.database_id= si.database_id - AND c.schema_name = si.schema_name - AND c.object_id = si.object_id - AND c.index_id = si.index_id - AND c.is_included_column = 0 /*Just Keys*/ - AND c.key_ordinal > 0 /*Ignore non-key columns, such as partitioning keys*/ - ORDER BY c.object_id, c.index_id, c.key_ordinal - FOR XML PATH('') , TYPE).value('.', 'nvarchar(max)'), 1, 1, '')) - ) D2 ( key_column_names_with_sort_order_no_types ); - -RAISERROR (N'Updating #IndexSanity.include_column_names',0,1) WITH NOWAIT; -UPDATE #IndexSanity -SET include_column_names = D3.include_column_names -FROM #IndexSanity si - CROSS APPLY ( SELECT RTRIM(STUFF( (SELECT N', ' + c.column_name - + N' {' + system_type_name + - CASE max_length WHEN -1 THEN N' (max)' ELSE - CASE - WHEN system_type_name IN (N'char',N'varchar',N'binary',N'varbinary') THEN N' (' + CAST(max_length AS NVARCHAR(20)) + N')' - WHEN system_type_name IN (N'nchar',N'nvarchar') THEN N' (' + CAST(max_length/2 AS NVARCHAR(20)) + N')' - ELSE N' ' + CAST(max_length AS NVARCHAR(50)) - END - END - + N'}' - FROM #IndexColumns c - WHERE c.database_id= si.database_id - AND c.schema_name = si.schema_name - AND c.object_id = si.object_id - AND c.index_id = si.index_id - AND c.is_included_column = 1 /*Just includes*/ - ORDER BY c.column_name /*Order doesn't matter in includes, - this is here to make rows easy to compare.*/ - FOR XML PATH('') , TYPE).value('.', 'nvarchar(max)'), 1, 1, '')) - ) D3 ( include_column_names ); - -RAISERROR (N'Updating #IndexSanity.include_column_names_no_types (for create tsql)',0,1) WITH NOWAIT; -UPDATE #IndexSanity -SET include_column_names_no_types = D3.include_column_names_no_types -FROM #IndexSanity si - CROSS APPLY ( SELECT RTRIM(STUFF( (SELECT N', ' + QUOTENAME(c.column_name) - FROM #IndexColumns c - WHERE c.database_id= si.database_id - AND c.schema_name = si.schema_name - AND c.object_id = si.object_id - AND c.index_id = si.index_id - AND c.is_included_column = 1 /*Just includes*/ - ORDER BY c.column_name /*Order doesn't matter in includes, - this is here to make rows easy to compare.*/ - FOR XML PATH('') , TYPE).value('.', 'nvarchar(max)'), 1, 1, '')) - ) D3 ( include_column_names_no_types ); - -RAISERROR (N'Updating #IndexSanity.count_key_columns and count_include_columns',0,1) WITH NOWAIT; -UPDATE #IndexSanity -SET count_included_columns = D4.count_included_columns, - count_key_columns = D4.count_key_columns -FROM #IndexSanity si - CROSS APPLY ( SELECT SUM(CASE WHEN is_included_column = 'true' THEN 1 - ELSE 0 - END) AS count_included_columns, - SUM(CASE WHEN is_included_column = 'false' AND c.key_ordinal > 0 THEN 1 - ELSE 0 - END) AS count_key_columns - FROM #IndexColumns c - WHERE c.database_id= si.database_id - AND c.schema_name = si.schema_name - AND c.object_id = si.object_id - AND c.index_id = si.index_id - ) AS D4 ( count_included_columns, count_key_columns ); - -RAISERROR (N'Updating index_sanity_id on #IndexPartitionSanity',0,1) WITH NOWAIT; -UPDATE #IndexPartitionSanity -SET index_sanity_id = i.index_sanity_id -FROM #IndexPartitionSanity ps - JOIN #IndexSanity i ON ps.[object_id] = i.[object_id] - AND ps.index_id = i.index_id - AND i.database_id = ps.database_id - AND i.schema_name = ps.schema_name; - - -RAISERROR (N'Inserting data into #IndexSanitySize',0,1) WITH NOWAIT; -INSERT #IndexSanitySize ( [index_sanity_id], [database_id], [schema_name], [lock_escalation_desc], partition_count, total_rows, total_reserved_MB, - total_reserved_LOB_MB, total_reserved_row_overflow_MB, total_reserved_dictionary_MB, total_range_scan_count, - total_singleton_lookup_count, total_leaf_delete_count, total_leaf_update_count, - total_forwarded_fetch_count,total_row_lock_count, - total_row_lock_wait_count, total_row_lock_wait_in_ms, avg_row_lock_wait_in_ms, - total_page_lock_count, total_page_lock_wait_count, total_page_lock_wait_in_ms, - avg_page_lock_wait_in_ms, total_index_lock_promotion_attempt_count, - total_index_lock_promotion_count, data_compression_desc, - page_latch_wait_count, page_latch_wait_in_ms, page_io_latch_wait_count, page_io_latch_wait_in_ms) - SELECT index_sanity_id, ipp.database_id, ipp.schema_name, ipp.lock_escalation_desc, - COUNT(*), SUM(row_count), SUM(reserved_MB), - SUM(reserved_LOB_MB) - SUM(reserved_dictionary_MB), /* Subtract columnstore dictionaries from LOB data */ - SUM(reserved_row_overflow_MB), - SUM(reserved_dictionary_MB), - SUM(range_scan_count), - SUM(singleton_lookup_count), - SUM(leaf_delete_count), - SUM(leaf_update_count), - SUM(forwarded_fetch_count), - SUM(row_lock_count), - SUM(row_lock_wait_count), - SUM(row_lock_wait_in_ms), - CASE WHEN SUM(row_lock_wait_in_ms) > 0 THEN - SUM(row_lock_wait_in_ms)/(1.*SUM(row_lock_wait_count)) - ELSE 0 END AS avg_row_lock_wait_in_ms, - SUM(page_lock_count), - SUM(page_lock_wait_count), - SUM(page_lock_wait_in_ms), - CASE WHEN SUM(page_lock_wait_in_ms) > 0 THEN - SUM(page_lock_wait_in_ms)/(1.*SUM(page_lock_wait_count)) - ELSE 0 END AS avg_page_lock_wait_in_ms, - SUM(index_lock_promotion_attempt_count), - SUM(index_lock_promotion_count), - LEFT(MAX(data_compression_info.data_compression_rollup),4000), - SUM(page_latch_wait_count), - SUM(page_latch_wait_in_ms), - SUM(page_io_latch_wait_count), - SUM(page_io_latch_wait_in_ms) - FROM #IndexPartitionSanity ipp - /* individual partitions can have distinct compression settings, just roll them into a list here*/ - OUTER APPLY (SELECT STUFF(( - SELECT N', ' + data_compression_desc - FROM #IndexPartitionSanity ipp2 - WHERE ipp.[object_id]=ipp2.[object_id] - AND ipp.[index_id]=ipp2.[index_id] - AND ipp.database_id = ipp2.database_id - AND ipp.schema_name = ipp2.schema_name - ORDER BY ipp2.partition_number - FOR XML PATH(''),TYPE).value('.', 'nvarchar(max)'), 1, 1, '')) - data_compression_info(data_compression_rollup) - GROUP BY index_sanity_id, ipp.database_id, ipp.schema_name, ipp.lock_escalation_desc - ORDER BY index_sanity_id -OPTION ( RECOMPILE ); - -RAISERROR (N'Determining index usefulness',0,1) WITH NOWAIT; -UPDATE #MissingIndexes -SET is_low = CASE WHEN (user_seeks + user_scans) < 5000 - OR unique_compiles = 1 - THEN 1 - ELSE 0 - END; - -RAISERROR (N'Updating #IndexSanity.referenced_by_foreign_key',0,1) WITH NOWAIT; -UPDATE #IndexSanity - SET is_referenced_by_foreign_key=1 -FROM #IndexSanity s -JOIN #ForeignKeys fk ON - s.object_id=fk.referenced_object_id - AND s.database_id=fk.database_id - AND LEFT(s.key_column_names,LEN(fk.referenced_fk_columns)) = fk.referenced_fk_columns; - -RAISERROR (N'Update index_secret on #IndexSanity for NC indexes.',0,1) WITH NOWAIT; -UPDATE nc -SET secret_columns= - N'[' + - CASE tb.count_key_columns WHEN 0 THEN '1' ELSE CAST(tb.count_key_columns AS NVARCHAR(10)) END + - CASE nc.is_unique WHEN 1 THEN N' INCLUDE' ELSE N' KEY' END + - CASE WHEN tb.count_key_columns > 1 THEN N'S] ' ELSE N'] ' END + - CASE tb.index_id WHEN 0 THEN '[RID]' ELSE LTRIM(tb.key_column_names) + - /* Uniquifiers only needed on non-unique clustereds-- not heaps */ - CASE tb.is_unique WHEN 0 THEN ' [UNIQUIFIER]' ELSE N'' END - END - , count_secret_columns= - CASE tb.index_id WHEN 0 THEN 1 ELSE - tb.count_key_columns + - CASE tb.is_unique WHEN 0 THEN 1 ELSE 0 END - END -FROM #IndexSanity AS nc -JOIN #IndexSanity AS tb ON nc.object_id=tb.object_id - AND nc.database_id = tb.database_id - AND nc.schema_name = tb.schema_name - AND tb.index_id IN (0,1) -WHERE nc.index_id > 1; - -RAISERROR (N'Update index_secret on #IndexSanity for heaps and non-unique clustered.',0,1) WITH NOWAIT; -UPDATE tb -SET secret_columns= CASE tb.index_id WHEN 0 THEN '[RID]' ELSE '[UNIQUIFIER]' END - , count_secret_columns = 1 -FROM #IndexSanity AS tb -WHERE tb.index_id = 0 /*Heaps-- these have the RID */ - OR (tb.index_id=1 AND tb.is_unique=0); /* Non-unique CX: has uniquifer (when needed) */ - - -RAISERROR (N'Populate #IndexCreateTsql.',0,1) WITH NOWAIT; -INSERT #IndexCreateTsql (index_sanity_id, create_tsql) -SELECT - index_sanity_id, - ISNULL ( - CASE index_id WHEN 0 THEN N'ALTER TABLE ' + QUOTENAME([database_name]) + N'.' + QUOTENAME([schema_name]) + N'.' + QUOTENAME([object_name]) + ' REBUILD;' - ELSE - CASE WHEN is_XML = 1 OR is_spatial = 1 OR is_in_memory_oltp = 1 THEN N'' /* Not even trying for these just yet...*/ - ELSE - CASE WHEN is_primary_key=1 THEN - N'ALTER TABLE ' + QUOTENAME([database_name]) + N'.' + QUOTENAME([schema_name]) + - N'.' + QUOTENAME([object_name]) + - N' ADD CONSTRAINT [' + - index_name + - N'] PRIMARY KEY ' + - CASE WHEN index_id=1 THEN N'CLUSTERED (' ELSE N'(' END + - key_column_names_with_sort_order_no_types + N' )' - WHEN is_unique_constraint = 1 AND is_primary_key = 0 - THEN - N'ALTER TABLE ' + QUOTENAME([database_name]) + N'.' + QUOTENAME([schema_name]) + - N'.' + QUOTENAME([object_name]) + - N' ADD CONSTRAINT [' + - index_name + - N'] UNIQUE ' + - CASE WHEN index_id=1 THEN N'CLUSTERED (' ELSE N'(' END + - key_column_names_with_sort_order_no_types + N' )' - WHEN is_CX_columnstore= 1 THEN - N'CREATE CLUSTERED COLUMNSTORE INDEX ' + QUOTENAME(index_name) + N' on ' + QUOTENAME([database_name]) + N'.' + QUOTENAME([schema_name]) + N'.' + QUOTENAME([object_name]) - ELSE /*Else not a PK or cx columnstore */ - N'CREATE ' + - CASE WHEN is_unique=1 THEN N'UNIQUE ' ELSE N'' END + - CASE WHEN index_id=1 THEN N'CLUSTERED ' ELSE N'' END + - CASE WHEN is_NC_columnstore=1 THEN N'NONCLUSTERED COLUMNSTORE ' - ELSE N'' END + - N'INDEX [' - + index_name + N'] ON ' + - QUOTENAME([database_name]) + N'.' + - QUOTENAME([schema_name]) + N'.' + QUOTENAME([object_name]) + - CASE WHEN is_NC_columnstore=1 THEN - N' (' + ISNULL(include_column_names_no_types,'') + N' )' - ELSE /*Else not columnstore */ - N' (' + ISNULL(key_column_names_with_sort_order_no_types,'') + N' )' - + CASE WHEN include_column_names_no_types IS NOT NULL THEN - N' INCLUDE (' + include_column_names_no_types + N')' - ELSE N'' - END - END /*End non-columnstore case */ - + CASE WHEN filter_definition <> N'' THEN N' WHERE ' + filter_definition ELSE N'' END - END /*End Non-PK index CASE */ - + CASE WHEN is_NC_columnstore=0 AND is_CX_columnstore=0 THEN - N' WITH (' - + N'FILLFACTOR=' + CASE fill_factor WHEN 0 THEN N'100' ELSE CAST(fill_factor AS NVARCHAR(5)) END + ', ' - + N'ONLINE=?, SORT_IN_TEMPDB=?, DATA_COMPRESSION=?' - + N')' - ELSE N'' END - + N';' - END /*End non-spatial and non-xml CASE */ - END, '[Unknown Error]') - AS create_tsql -FROM #IndexSanity; - -RAISERROR (N'Populate #PartitionCompressionInfo.',0,1) WITH NOWAIT; -IF OBJECT_ID('tempdb..#maps') IS NOT NULL DROP TABLE #maps; -WITH maps - AS - ( - SELECT ips.index_sanity_id, - ips.partition_number, - ips.data_compression_desc, - ips.partition_number - ROW_NUMBER() OVER ( PARTITION BY ips.index_sanity_id, ips.data_compression_desc - ORDER BY ips.partition_number ) AS rn - FROM #IndexPartitionSanity AS ips - ) -SELECT * -INTO #maps -FROM maps; - -IF OBJECT_ID('tempdb..#grps') IS NOT NULL DROP TABLE #grps; -WITH grps - AS - ( - SELECT MIN(maps.partition_number) AS MinKey, - MAX(maps.partition_number) AS MaxKey, - maps.index_sanity_id, - maps.data_compression_desc - FROM #maps AS maps - GROUP BY maps.rn, maps.index_sanity_id, maps.data_compression_desc - ) -SELECT * -INTO #grps -FROM grps; - -INSERT #PartitionCompressionInfo ( index_sanity_id, partition_compression_detail ) -SELECT DISTINCT - grps.index_sanity_id, - SUBSTRING( - ( STUFF( - ( SELECT N', ' + N' Partition' - + CASE - WHEN grps2.MinKey < grps2.MaxKey - THEN - + N's ' + CAST(grps2.MinKey AS NVARCHAR(10)) + N' - ' - + CAST(grps2.MaxKey AS NVARCHAR(10)) + N' use ' + grps2.data_compression_desc - ELSE - N' ' + CAST(grps2.MinKey AS NVARCHAR(10)) + N' uses ' + grps2.data_compression_desc - END AS Partitions - FROM #grps AS grps2 - WHERE grps2.index_sanity_id = grps.index_sanity_id - ORDER BY grps2.MinKey, grps2.MaxKey - FOR XML PATH(''), TYPE ).value('.', 'NVARCHAR(MAX)'), 1, 1, '')), 0, 8000) AS partition_compression_detail -FROM #grps AS grps; - -RAISERROR (N'Update #PartitionCompressionInfo.',0,1) WITH NOWAIT; -UPDATE sz -SET sz.data_compression_desc = pci.partition_compression_detail -FROM #IndexSanitySize sz -JOIN #PartitionCompressionInfo AS pci -ON pci.index_sanity_id = sz.index_sanity_id; - -RAISERROR (N'Update #IndexSanity for filtered indexes with columns not in the index definition.',0,1) WITH NOWAIT; -UPDATE #IndexSanity -SET filter_columns_not_in_index = D1.filter_columns_not_in_index -FROM #IndexSanity si - CROSS APPLY ( SELECT RTRIM(STUFF( (SELECT N', ' + c.column_name AS col_definition - FROM #FilteredIndexes AS c - WHERE c.database_id= si.database_id - AND c.schema_name = si.schema_name - AND c.table_name = si.object_name - AND c.index_name = si.index_name - ORDER BY c.index_sanity_id - FOR XML PATH('') , TYPE).value('.', 'nvarchar(max)'), 1, 1,''))) D1 - ( filter_columns_not_in_index ); - - -IF @Debug = 1 -BEGIN - SELECT '#BlitzIndexResults' AS table_name, * FROM #BlitzIndexResults AS bir; - SELECT '#IndexSanity' AS table_name, * FROM #IndexSanity; - SELECT '#IndexPartitionSanity' AS table_name, * FROM #IndexPartitionSanity; - SELECT '#IndexSanitySize' AS table_name, * FROM #IndexSanitySize; - SELECT '#IndexColumns' AS table_name, * FROM #IndexColumns; - SELECT '#MissingIndexes' AS table_name, * FROM #MissingIndexes; - SELECT '#ForeignKeys' AS table_name, * FROM #ForeignKeys; - SELECT '#UnindexedForeignKeys' AS table_name, * FROM #UnindexedForeignKeys; - SELECT '#BlitzIndexResults' AS table_name, * FROM #BlitzIndexResults; - SELECT '#IndexCreateTsql' AS table_name, * FROM #IndexCreateTsql; - SELECT '#DatabaseList' AS table_name, * FROM #DatabaseList; - SELECT '#Statistics' AS table_name, * FROM #Statistics; - SELECT '#PartitionCompressionInfo' AS table_name, * FROM #PartitionCompressionInfo; - SELECT '#ComputedColumns' AS table_name, * FROM #ComputedColumns; - SELECT '#TraceStatus' AS table_name, * FROM #TraceStatus; - SELECT '#CheckConstraints' AS table_name, * FROM #CheckConstraints; - SELECT '#FilteredIndexes' AS table_name, * FROM #FilteredIndexes; -END - - ----------------------------------------- ---STEP 3: DIAGNOSE THE PATIENT ----------------------------------------- - - -BEGIN TRY ----------------------------------------- ---If @TableName is specified, just return information for that table. ---The @Mode parameter doesn't matter if you're looking at a specific table. ----------------------------------------- -IF @TableName IS NOT NULL -BEGIN - RAISERROR(N'@TableName specified, giving detail only on that table.', 0,1) WITH NOWAIT; - - --We do a left join here in case this is a disabled NC. - --In that case, it won't have any size info/pages allocated. - - IF (@ShowColumnstoreOnly = 0) - BEGIN - WITH table_mode_cte AS ( - SELECT - s.db_schema_object_indexid, - s.key_column_names, - s.index_definition, - ISNULL(s.secret_columns,N'') AS secret_columns, - s.fill_factor, - s.index_usage_summary, - sz.index_op_stats, - ISNULL(sz.index_size_summary,'') /*disabled NCs will be null*/ AS index_size_summary, - partition_compression_detail , - ISNULL(sz.index_lock_wait_summary,'') AS index_lock_wait_summary, - s.is_referenced_by_foreign_key, - (SELECT COUNT(*) - FROM #ForeignKeys fk WHERE fk.parent_object_id=s.object_id - AND PATINDEX (fk.parent_fk_columns, s.key_column_names)=1) AS FKs_covered_by_index, - s.last_user_seek, - s.last_user_scan, - s.last_user_lookup, - s.last_user_update, - s.create_date, - s.modify_date, - sz.page_latch_wait_count, - CONVERT(VARCHAR(10), (sz.page_latch_wait_in_ms / 1000) / 86400) + ':' + CONVERT(VARCHAR(20), DATEADD(s, (sz.page_latch_wait_in_ms / 1000), 0), 108) AS page_latch_wait_time, - sz.page_io_latch_wait_count, - CONVERT(VARCHAR(10), (sz.page_io_latch_wait_in_ms / 1000) / 86400) + ':' + CONVERT(VARCHAR(20), DATEADD(s, (sz.page_io_latch_wait_in_ms / 1000), 0), 108) AS page_io_latch_wait_time, - ct.create_tsql, - CASE - WHEN s.is_primary_key = 1 AND s.index_definition <> '[HEAP]' - THEN N'--ALTER TABLE ' + QUOTENAME(s.[database_name]) + N'.' + QUOTENAME(s.[schema_name]) + N'.' + QUOTENAME(s.[object_name]) - + N' DROP CONSTRAINT ' + QUOTENAME(s.index_name) + N';' - WHEN s.is_primary_key = 0 AND is_unique_constraint = 1 AND s.index_definition <> '[HEAP]' - THEN N'--ALTER TABLE ' + QUOTENAME(s.[database_name]) + N'.' + QUOTENAME(s.[schema_name]) + N'.' + QUOTENAME(s.[object_name]) - + N' DROP CONSTRAINT ' + QUOTENAME(s.index_name) + N';' - WHEN s.is_primary_key = 0 AND s.index_definition <> '[HEAP]' - THEN N'--DROP INDEX '+ QUOTENAME(s.index_name) + N' ON ' + QUOTENAME(s.[database_name]) + N'.' + - QUOTENAME(s.[schema_name]) + N'.' + QUOTENAME(s.[object_name]) + N';' - ELSE N'' - END AS drop_tsql, - 1 AS display_order - FROM #IndexSanity s - LEFT JOIN #IndexSanitySize sz ON - s.index_sanity_id=sz.index_sanity_id - LEFT JOIN #IndexCreateTsql ct ON - s.index_sanity_id=ct.index_sanity_id - LEFT JOIN #PartitionCompressionInfo pci ON - pci.index_sanity_id = s.index_sanity_id - WHERE s.[object_id]=@ObjectID - UNION ALL - SELECT N'Database ' + QUOTENAME(@DatabaseName) + N' as of ' + CONVERT(NVARCHAR(16),GETDATE(),121) + - N' (' + @ScriptVersionName + ')' , - N'SQL Server First Responder Kit' , - N'http://FirstResponderKit.org' , - N'From Your Community Volunteers', - NULL,@DaysUptimeInsertValue,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL, - 0 AS display_order - ) - SELECT - db_schema_object_indexid AS [Details: db_schema.table.index(indexid)], - index_definition AS [Definition: [Property]] ColumnName {datatype maxbytes}], - secret_columns AS [Secret Columns], - fill_factor AS [Fillfactor], - index_usage_summary AS [Usage Stats], - index_op_stats AS [Op Stats], - index_size_summary AS [Size], - partition_compression_detail AS [Compression Type], - index_lock_wait_summary AS [Lock Waits], - is_referenced_by_foreign_key AS [Referenced by FK?], - FKs_covered_by_index AS [FK Covered by Index?], - last_user_seek AS [Last User Seek], - last_user_scan AS [Last User Scan], - last_user_lookup AS [Last User Lookup], - last_user_update AS [Last User Write], - create_date AS [Created], - modify_date AS [Last Modified], - page_latch_wait_count AS [Page Latch Wait Count], - page_latch_wait_time as [Page Latch Wait Time (D:H:M:S)], - page_io_latch_wait_count AS [Page IO Latch Wait Count], - page_io_latch_wait_time as [Page IO Latch Wait Time (D:H:M:S)], - create_tsql AS [Create TSQL], - drop_tsql AS [Drop TSQL] - FROM table_mode_cte - ORDER BY display_order ASC, key_column_names ASC - OPTION ( RECOMPILE ); - - IF (SELECT TOP 1 [object_id] FROM #MissingIndexes mi) IS NOT NULL - BEGIN; - - WITH create_date AS ( - SELECT i.database_id, - i.schema_name, - i.[object_id], - ISNULL(NULLIF(MAX(DATEDIFF(DAY, i.create_date, SYSDATETIME())), 0), 1) AS create_days - FROM #IndexSanity AS i - GROUP BY i.database_id, i.schema_name, i.object_id - ) - SELECT N'Missing index.' AS Finding , - N'https://www.brentozar.com/go/Indexaphobia' AS URL , - mi.[statement] + - ' Est. Benefit: ' - + CASE WHEN magic_benefit_number >= 922337203685477 THEN '>= 922,337,203,685,477' - ELSE REPLACE(CONVERT(NVARCHAR(256),CAST(CAST( - (magic_benefit_number / CASE WHEN cd.create_days < @DaysUptime THEN cd.create_days ELSE @DaysUptime END) - AS BIGINT) AS MONEY), 1), '.00', '') - END AS [Estimated Benefit], - missing_index_details AS [Missing Index Request] , - index_estimated_impact AS [Estimated Impact], - create_tsql AS [Create TSQL], - sample_query_plan AS [Sample Query Plan] - FROM #MissingIndexes mi - LEFT JOIN create_date AS cd - ON mi.[object_id] = cd.object_id - AND mi.database_id = cd.database_id - AND mi.schema_name = cd.schema_name - WHERE mi.[object_id] = @ObjectID - AND (@ShowAllMissingIndexRequests=1 - /* Minimum benefit threshold = 100k/day of uptime OR since table creation date, whichever is lower*/ - OR (magic_benefit_number / CASE WHEN cd.create_days < @DaysUptime THEN cd.create_days ELSE @DaysUptime END) >= 100000) - ORDER BY magic_benefit_number DESC - OPTION ( RECOMPILE ); - END; - ELSE - SELECT 'No missing indexes.' AS finding; - - SELECT - column_name AS [Column Name], - (SELECT COUNT(*) - FROM #IndexColumns c2 - WHERE c2.column_name=c.column_name - AND c2.key_ordinal IS NOT NULL) - + CASE WHEN c.index_id = 1 AND c.key_ordinal IS NOT NULL THEN - -1+ (SELECT COUNT(DISTINCT index_id) - FROM #IndexColumns c3 - WHERE c3.index_id NOT IN (0,1)) - ELSE 0 END - AS [Found In], - system_type_name + - CASE max_length WHEN -1 THEN N' (max)' ELSE - CASE - WHEN system_type_name IN (N'char',N'varchar',N'binary',N'varbinary') THEN N' (' + CAST(max_length AS NVARCHAR(20)) + N')' - WHEN system_type_name IN (N'nchar',N'nvarchar') THEN N' (' + CAST(max_length/2 AS NVARCHAR(20)) + N')' - ELSE '' - END - END - AS [Type], - CASE is_computed WHEN 1 THEN 'yes' ELSE '' END AS [Computed?], - max_length AS [Length (max bytes)], - [precision] AS [Prec], - [scale] AS [Scale], - CASE is_nullable WHEN 1 THEN 'yes' ELSE '' END AS [Nullable?], - CASE is_identity WHEN 1 THEN 'yes' ELSE '' END AS [Identity?], - CASE is_replicated WHEN 1 THEN 'yes' ELSE '' END AS [Replicated?], - CASE is_sparse WHEN 1 THEN 'yes' ELSE '' END AS [Sparse?], - CASE is_filestream WHEN 1 THEN 'yes' ELSE '' END AS [Filestream?], - collation_name AS [Collation] - FROM #IndexColumns AS c - WHERE index_id IN (0,1); - - IF (SELECT TOP 1 parent_object_id FROM #ForeignKeys) IS NOT NULL - BEGIN - SELECT [database_name] + N':' + parent_object_name + N': ' + foreign_key_name AS [Foreign Key], - parent_fk_columns AS [Foreign Key Columns], - referenced_object_name AS [Referenced Table], - referenced_fk_columns AS [Referenced Table Columns], - is_disabled AS [Is Disabled?], - is_not_trusted AS [Not Trusted?], - is_not_for_replication [Not for Replication?], - [update_referential_action_desc] AS [Cascading Updates?], - [delete_referential_action_desc] AS [Cascading Deletes?] - FROM #ForeignKeys - ORDER BY [Foreign Key] - OPTION ( RECOMPILE ); - END; - ELSE - SELECT 'No foreign keys.' AS finding; - - /* Show histograms for all stats on this table. More info: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/1900 */ - IF EXISTS (SELECT * FROM sys.all_objects WHERE name = 'dm_db_stats_histogram') - BEGIN - SET @dsql = N'USE ' + QUOTENAME(@DatabaseName) + N'; - SELECT s.name AS [Stat Name], c.name AS [Leading Column Name], hist.step_number AS [Step Number], - hist.range_high_key AS [Range High Key], hist.range_rows AS [Range Rows], - hist.equal_rows AS [Equal Rows], hist.distinct_range_rows AS [Distinct Range Rows], hist.average_range_rows AS [Average Range Rows], - s.auto_created AS [Auto-Created], s.user_created AS [User-Created], - props.last_updated AS [Last Updated], props.modification_counter AS [Modification Counter], props.rows AS [Table Rows], - props.rows_sampled AS [Rows Sampled], s.stats_id AS [StatsID] - FROM sys.stats AS s - INNER JOIN sys.stats_columns sc ON s.object_id = sc.object_id AND s.stats_id = sc.stats_id AND sc.stats_column_id = 1 - INNER JOIN sys.columns c ON sc.object_id = c.object_id AND sc.column_id = c.column_id - CROSS APPLY sys.dm_db_stats_properties(s.object_id, s.stats_id) AS props - CROSS APPLY sys.dm_db_stats_histogram(s.[object_id], s.stats_id) AS hist - WHERE s.object_id = @ObjectID - ORDER BY s.auto_created, s.user_created, s.name, hist.step_number;'; - EXEC sp_executesql @dsql, N'@ObjectID INT', @ObjectID; - END - END - - /* Visualize columnstore index contents. More info: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2584 */ - IF 2 = (SELECT SUM(1) FROM sys.all_objects WHERE name IN ('column_store_row_groups','column_store_segments')) - BEGIN - RAISERROR(N'Visualizing columnstore index contents.', 0,1) WITH NOWAIT; - - SET @dsql = N'USE ' + QUOTENAME(@DatabaseName) + N'; - IF EXISTS(SELECT * FROM ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_row_groups WHERE object_id = @ObjectID) - BEGIN - SELECT @ColumnList = N'''', @ColumnListWithApostrophes = N''''; - WITH DistinctColumns AS ( - SELECT DISTINCT QUOTENAME(c.name) AS column_name, QUOTENAME(c.name,'''''''') AS ColumnNameWithApostrophes, c.column_id - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.partitions p - INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns c ON p.object_id = c.object_id - INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.index_columns ic on ic.column_id = c.column_id and ic.object_id = c.object_id AND ic.index_id = p.index_id - INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.types t ON c.system_type_id = t.system_type_id AND c.user_type_id = t.user_type_id - WHERE p.object_id = @ObjectID - AND EXISTS (SELECT * FROM ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_segments seg WHERE p.partition_id = seg.partition_id AND seg.column_id = ic.index_column_id) - AND p.data_compression IN (3,4) - ) - SELECT @ColumnList = @ColumnList + column_name + N'', '', - @ColumnListWithApostrophes = @ColumnListWithApostrophes + ColumnNameWithApostrophes + N'', '' - FROM DistinctColumns - ORDER BY column_id; - SELECT @PartitionCount = COUNT(1) FROM ' + QUOTENAME(@DatabaseName) + N'.sys.partitions WHERE object_id = @ObjectID AND data_compression IN (3,4); - END'; - - IF @Debug = 1 - BEGIN - PRINT SUBSTRING(@dsql, 0, 4000); - PRINT SUBSTRING(@dsql, 4000, 8000); - PRINT SUBSTRING(@dsql, 8000, 12000); - PRINT SUBSTRING(@dsql, 12000, 16000); - PRINT SUBSTRING(@dsql, 16000, 20000); - PRINT SUBSTRING(@dsql, 20000, 24000); - PRINT SUBSTRING(@dsql, 24000, 28000); - PRINT SUBSTRING(@dsql, 28000, 32000); - PRINT SUBSTRING(@dsql, 32000, 36000); - PRINT SUBSTRING(@dsql, 36000, 40000); - END; - - EXEC sp_executesql @dsql, N'@ObjectID INT, @ColumnList NVARCHAR(MAX) OUTPUT, @ColumnListWithApostrophes NVARCHAR(MAX) OUTPUT, @PartitionCount INT OUTPUT', @ObjectID, @ColumnList OUTPUT, @ColumnListWithApostrophes OUTPUT, @PartitionCount OUTPUT; - - IF @PartitionCount < 2 - SET @ShowPartitionRanges = 0; - - IF @Debug = 1 - SELECT @ColumnList AS ColumnstoreColumnList, @ColumnListWithApostrophes AS ColumnstoreColumnListWithApostrophes, @PartitionCount AS PartitionCount, @ShowPartitionRanges AS ShowPartitionRanges; - - IF @ColumnList <> '' - BEGIN - /* Remove the trailing comma */ - SET @ColumnList = LEFT(@ColumnList, LEN(@ColumnList) - 1); - SET @ColumnListWithApostrophes = LEFT(@ColumnListWithApostrophes, LEN(@ColumnListWithApostrophes) - 1); - - SET @dsql = N'USE ' + QUOTENAME(@DatabaseName) + N'; - SELECT partition_number, ' - + CASE WHEN @ShowPartitionRanges = 1 THEN N' COALESCE(range_start_op + '' '' + range_start + '' '', '''') + COALESCE(range_end_op + '' '' + range_end, '''') AS partition_range, ' ELSE N' ' END - + N' row_group_id, total_rows, deleted_rows, ' - + @ColumnList - + CASE WHEN @ShowPartitionRanges = 1 THEN N' , - state_desc, trim_reason_desc, transition_to_compressed_state_desc, has_vertipaq_optimization - FROM ( - SELECT column_name, partition_number, row_group_id, total_rows, deleted_rows, details, - range_start_op, - CASE - WHEN format_type IS NULL THEN CAST(range_start_value AS NVARCHAR(4000)) - ELSE CONVERT(NVARCHAR(4000), range_start_value, format_type) END range_start, - range_end_op, - CASE - WHEN format_type IS NULL THEN CAST(range_end_value AS NVARCHAR(4000)) - ELSE CONVERT(NVARCHAR(4000), range_end_value, format_type) END range_end' ELSE N' ' END + N', - state_desc, trim_reason_desc, transition_to_compressed_state_desc, has_vertipaq_optimization - FROM ( - SELECT c.name AS column_name, p.partition_number, rg.row_group_id, rg.total_rows, rg.deleted_rows, - phys.state_desc, phys.trim_reason_desc, phys.transition_to_compressed_state_desc, phys.has_vertipaq_optimization, - details = CAST(seg.min_data_id AS VARCHAR(20)) + '' to '' + CAST(seg.max_data_id AS VARCHAR(20)) + '', '' + CAST(CAST(((COALESCE(d.on_disk_size,0) + COALESCE(seg.on_disk_size,0)) / 1024.0 / 1024) AS DECIMAL(18,0)) AS VARCHAR(20)) + '' MB''' - + CASE WHEN @ShowPartitionRanges = 1 THEN N', - CASE - WHEN pp.system_type_id IN (40, 41, 42, 43, 58, 61) THEN 126 - WHEN pp.system_type_id IN (59, 62) THEN 3 - WHEN pp.system_type_id IN (60, 122) THEN 2 - ELSE NULL END format_type, - CASE WHEN pf.boundary_value_on_right = 0 THEN ''>'' ELSE ''>='' END range_start_op, - prvs.value range_start_value, - CASE WHEN pf.boundary_value_on_right = 0 THEN ''<='' ELSE ''<'' END range_end_op, - prve.value range_end_value ' ELSE N' ' END + N' - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_row_groups rg - INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns c ON rg.object_id = c.object_id - INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partitions p ON rg.object_id = p.object_id AND rg.partition_number = p.partition_number - INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.index_columns ic on ic.column_id = c.column_id AND ic.object_id = c.object_id AND ic.index_id = p.index_id - LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_column_store_row_group_physical_stats phys ON rg.row_group_id = phys.row_group_id AND rg.object_id = phys.object_id AND rg.partition_number = phys.partition_number AND rg.index_id = phys.index_id ' + CASE WHEN @ShowPartitionRanges = 1 THEN N' - LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.indexes i ON i.object_id = rg.object_id AND i.index_id = rg.index_id - LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partition_schemes ps ON ps.data_space_id = i.data_space_id - LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partition_functions pf ON pf.function_id = ps.function_id - LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partition_parameters pp ON pp.function_id = pf.function_id - LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partition_range_values prvs ON prvs.function_id = pf.function_id AND prvs.boundary_id = p.partition_number - 1 - LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.partition_range_values prve ON prve.function_id = pf.function_id AND prve.boundary_id = p.partition_number ' ELSE N' ' END - + N' LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_segments seg ON p.partition_id = seg.partition_id AND ic.index_column_id = seg.column_id AND rg.row_group_id = seg.segment_id - LEFT OUTER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_dictionaries d ON p.hobt_id = d.hobt_id AND c.column_id = d.column_id AND seg.secondary_dictionary_id = d.dictionary_id - WHERE rg.object_id = @ObjectID - AND rg.state IN (1, 2, 3) - AND c.name IN ( ' + @ColumnListWithApostrophes + N')' - + CASE WHEN @ShowPartitionRanges = 1 THEN N' - ) AS y ' ELSE N' ' END + N' - ) AS x - PIVOT (MAX(details) FOR column_name IN ( ' + @ColumnList + N')) AS pivot1 - ORDER BY partition_number, row_group_id;'; - - IF @Debug = 1 - BEGIN - PRINT SUBSTRING(@dsql, 0, 4000); - PRINT SUBSTRING(@dsql, 4000, 8000); - PRINT SUBSTRING(@dsql, 8000, 12000); - PRINT SUBSTRING(@dsql, 12000, 16000); - PRINT SUBSTRING(@dsql, 16000, 20000); - PRINT SUBSTRING(@dsql, 20000, 24000); - PRINT SUBSTRING(@dsql, 24000, 28000); - PRINT SUBSTRING(@dsql, 28000, 32000); - PRINT SUBSTRING(@dsql, 32000, 36000); - PRINT SUBSTRING(@dsql, 36000, 40000); - END; - - IF @dsql IS NULL - RAISERROR('@dsql is null',16,1); - ELSE - EXEC sp_executesql @dsql, N'@ObjectID INT', @ObjectID; - END - ELSE /* No columns were found for this object */ - BEGIN - SELECT N'No compressed columnstore rowgroups were found for this object.' AS Columnstore_Visualization - UNION ALL - SELECT N'SELECT * FROM ' + QUOTENAME(@DatabaseName) + N'.sys.column_store_row_groups WHERE object_id = ' + CAST(@ObjectID AS NVARCHAR(100)); - END - RAISERROR(N'Done visualizing columnstore index contents.', 0,1) WITH NOWAIT; - END - - IF @ShowColumnstoreOnly = 1 - RETURN; - -END; /* IF @TableName IS NOT NULL */ - - - - - - - - -ELSE /* @TableName IS NULL, so we operate in normal mode 0/1/2/3/4 */ -BEGIN - -/* Validate and check table output params */ - - - /* Checks if @OutputServerName is populated with a valid linked server, and that the database name specified is valid */ - DECLARE @ValidOutputServer BIT; - DECLARE @ValidOutputLocation BIT; - DECLARE @LinkedServerDBCheck NVARCHAR(2000); - DECLARE @ValidLinkedServerDB INT; - DECLARE @tmpdbchk TABLE (cnt INT); - - IF @OutputServerName IS NOT NULL - BEGIN - IF (SUBSTRING(@OutputTableName, 2, 1) = '#') - BEGIN - RAISERROR('Due to the nature of temporary tables, outputting to a linked server requires a permanent table.', 16, 0); - END; - ELSE IF EXISTS (SELECT server_id FROM sys.servers WHERE QUOTENAME([name]) = @OutputServerName) - BEGIN - SET @LinkedServerDBCheck = 'SELECT 1 WHERE EXISTS (SELECT * FROM '+@OutputServerName+'.master.sys.databases WHERE QUOTENAME([name]) = '''+@OutputDatabaseName+''')'; - INSERT INTO @tmpdbchk EXEC sys.sp_executesql @LinkedServerDBCheck; - SET @ValidLinkedServerDB = (SELECT COUNT(*) FROM @tmpdbchk); - IF (@ValidLinkedServerDB > 0) - BEGIN - SET @ValidOutputServer = 1; - SET @ValidOutputLocation = 1; - END; - ELSE - RAISERROR('The specified database was not found on the output server', 16, 0); - END; - ELSE - BEGIN - RAISERROR('The specified output server was not found', 16, 0); - END; - END; - ELSE - BEGIN - IF (SUBSTRING(@OutputTableName, 2, 2) = '##') - BEGIN - SET @StringToExecute = N' IF (OBJECT_ID(''[tempdb].[dbo].@@@OutputTableName@@@'') IS NOT NULL) DROP TABLE @@@OutputTableName@@@'; - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); - EXEC(@StringToExecute); - - SET @OutputServerName = QUOTENAME(CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128))); - SET @OutputDatabaseName = '[tempdb]'; - SET @OutputSchemaName = '[dbo]'; - SET @ValidOutputLocation = 1; - END; - ELSE IF (SUBSTRING(@OutputTableName, 2, 1) = '#') - BEGIN - RAISERROR('Due to the nature of Dymamic SQL, only global (i.e. double pound (##)) temp tables are supported for @OutputTableName', 16, 0); - END; - ELSE IF @OutputDatabaseName IS NOT NULL - AND @OutputSchemaName IS NOT NULL - AND @OutputTableName IS NOT NULL - AND EXISTS ( SELECT * - FROM sys.databases - WHERE QUOTENAME([name]) = @OutputDatabaseName) - BEGIN - SET @ValidOutputLocation = 1; - SET @OutputServerName = QUOTENAME(CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128))); - END; - ELSE IF @OutputDatabaseName IS NOT NULL - AND @OutputSchemaName IS NOT NULL - AND @OutputTableName IS NOT NULL - AND NOT EXISTS ( SELECT * - FROM sys.databases - WHERE QUOTENAME([name]) = @OutputDatabaseName) - BEGIN - RAISERROR('The specified output database was not found on this server', 16, 0); - END; - ELSE - BEGIN - SET @ValidOutputLocation = 0; - END; - END; - - IF (@ValidOutputLocation = 0 AND @OutputType = 'NONE') - BEGIN - RAISERROR('Invalid output location and no output asked',12,1); - RETURN; - END; - - /* @OutputTableName lets us export the results to a permanent table */ - DECLARE @RunID UNIQUEIDENTIFIER; - SET @RunID = NEWID(); - - DECLARE @TableExists BIT; - DECLARE @SchemaExists BIT; - - DECLARE @TableExistsSql NVARCHAR(MAX); - - IF (@ValidOutputLocation = 1 AND COALESCE(@OutputServerName, @OutputDatabaseName, @OutputSchemaName, @OutputTableName) IS NOT NULL) - BEGIN - SET @StringToExecute = - N'SET @SchemaExists = 0; - SET @TableExists = 0; - IF EXISTS(SELECT * FROM @@@OutputServerName@@@.@@@OutputDatabaseName@@@.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''@@@OutputSchemaName@@@'') - SET @SchemaExists = 1 - IF EXISTS (SELECT * FROM @@@OutputServerName@@@.@@@OutputDatabaseName@@@.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''@@@OutputSchemaName@@@'' AND QUOTENAME(TABLE_NAME) = ''@@@OutputTableName@@@'') - BEGIN - SET @TableExists = 1 - IF NOT EXISTS(SELECT * FROM @@@OutputServerName@@@.@@@OutputDatabaseName@@@.INFORMATION_SCHEMA.COLUMNS WHERE QUOTENAME(TABLE_SCHEMA) = ''@@@OutputSchemaName@@@'' - AND QUOTENAME(TABLE_NAME) = ''@@@OutputTableName@@@'' AND QUOTENAME(COLUMN_NAME) = ''[total_forwarded_fetch_count]'') - EXEC @@@OutputServerName@@@.@@@OutputDatabaseName@@@.dbo.sp_executesql N''ALTER TABLE @@@OutputSchemaName@@@.@@@OutputTableName@@@ ADD [total_forwarded_fetch_count] BIGINT'' - END'; - - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputServerName@@@', @OutputServerName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputSchemaName@@@', @OutputSchemaName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); - - EXEC sp_executesql @StringToExecute, N'@TableExists BIT OUTPUT, @SchemaExists BIT OUTPUT', @TableExists OUTPUT, @SchemaExists OUTPUT; - - - SET @TableExistsSql = - N'IF EXISTS(SELECT * FROM @@@OutputServerName@@@.@@@OutputDatabaseName@@@.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''@@@OutputSchemaName@@@'') - AND NOT EXISTS (SELECT * FROM @@@OutputServerName@@@.@@@OutputDatabaseName@@@.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''@@@OutputSchemaName@@@'' AND QUOTENAME(TABLE_NAME) = ''@@@OutputTableName@@@'') - SET @TableExists = 0 - ELSE - SET @TableExists = 1'; - - SET @TableExistsSql = REPLACE(@TableExistsSql, '@@@OutputServerName@@@', @OutputServerName); - SET @TableExistsSql = REPLACE(@TableExistsSql, '@@@OutputDatabaseName@@@', @OutputDatabaseName); - SET @TableExistsSql = REPLACE(@TableExistsSql, '@@@OutputSchemaName@@@', @OutputSchemaName); - SET @TableExistsSql = REPLACE(@TableExistsSql, '@@@OutputTableName@@@', @OutputTableName); - - END - - - - - IF @Mode IN (0, 4) /* DIAGNOSE */ - BEGIN; - IF @Mode IN (0, 4) /* DIAGNOSE priorities 1-100 */ - BEGIN; - RAISERROR(N'@Mode=0 or 4, running rules for priorities 1-100.', 0,1) WITH NOWAIT; - - ---------------------------------------- - --Multiple Index Personalities: Check_id 0-10 - ---------------------------------------- - RAISERROR('check_id 1: Duplicate keys', 0,1) WITH NOWAIT; - WITH duplicate_indexes - AS ( SELECT [object_id], key_column_names, database_id, [schema_name] - FROM #IndexSanity AS ip - WHERE index_type IN (1,2) /* Clustered, NC only*/ - AND is_hypothetical = 0 - AND is_disabled = 0 - AND is_primary_key = 0 - AND EXISTS ( - SELECT 1/0 - FROM #IndexSanitySize ips - WHERE ip.index_sanity_id = ips.index_sanity_id - AND ip.database_id = ips.database_id - AND ip.schema_name = ips.schema_name - AND ips.total_reserved_MB >= CASE - WHEN (@GetAllDatabases = 1 OR @Mode = 0) - THEN @ThresholdMB - ELSE ips.total_reserved_MB - END - ) - GROUP BY [object_id], key_column_names, database_id, [schema_name] - HAVING COUNT(*) > 1) - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 1 AS check_id, - ip.index_sanity_id, - 20 AS Priority, - 'Multiple Index Personalities' AS findings_group, - 'Duplicate keys' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/duplicateindex' AS URL, - N'Index Name: ' + ip.index_name + N' Table Name: ' + ip.db_schema_object_name AS details, - ip.index_definition, - ip.secret_columns, - ip.index_usage_summary, - ips.index_size_summary - FROM duplicate_indexes di - JOIN #IndexSanity ip ON di.[object_id] = ip.[object_id] - AND ip.database_id = di.database_id - AND ip.[schema_name] = di.[schema_name] - AND di.key_column_names = ip.key_column_names - JOIN #IndexSanitySize ips ON ip.index_sanity_id = ips.index_sanity_id - AND ip.database_id = ips.database_id - AND ip.schema_name = ips.schema_name - /* WHERE clause limits to only @ThresholdMB or larger duplicate indexes when getting all databases or using PainRelief mode */ - WHERE ips.total_reserved_MB >= CASE WHEN (@GetAllDatabases = 1 OR @Mode = 0) THEN @ThresholdMB ELSE ips.total_reserved_MB END - AND ip.is_primary_key = 0 - ORDER BY ips.total_rows DESC, ip.[schema_name], ip.[object_name], ip.key_column_names_with_sort_order - OPTION ( RECOMPILE ); - - RAISERROR('check_id 2: Keys w/ identical leading columns.', 0,1) WITH NOWAIT; - WITH borderline_duplicate_indexes - AS ( SELECT DISTINCT database_id, [object_id], first_key_column_name, key_column_names, - COUNT([object_id]) OVER ( PARTITION BY database_id, [object_id], first_key_column_name ) AS number_dupes - FROM #IndexSanity - WHERE index_type IN (1,2) /* Clustered, NC only*/ - AND is_hypothetical=0 - AND is_disabled=0 - AND is_primary_key = 0) - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 2 AS check_id, - ip.index_sanity_id, - 30 AS Priority, - 'Multiple Index Personalities' AS findings_group, - 'Borderline duplicate keys' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/duplicateindex' AS URL, - ip.db_schema_object_indexid AS details, - ip.index_definition, - ip.secret_columns, - ip.index_usage_summary, - ips.index_size_summary - FROM #IndexSanity AS ip - JOIN #IndexSanitySize ips ON ip.index_sanity_id = ips.index_sanity_id - WHERE EXISTS ( - SELECT di.[object_id] - FROM borderline_duplicate_indexes AS di - WHERE di.[object_id] = ip.[object_id] AND - di.database_id = ip.database_id AND - di.first_key_column_name = ip.first_key_column_name AND - di.key_column_names <> ip.key_column_names AND - di.number_dupes > 1 - ) - AND ip.is_primary_key = 0 - ORDER BY ips.total_rows DESC, ip.[schema_name], ip.[object_name], ip.key_column_names, ip.include_column_names - OPTION ( RECOMPILE ); - - ---------------------------------------- - --Aggressive Indexes: Check_id 10-19 - ---------------------------------------- - - RAISERROR(N'check_id 11: Total lock wait time > 5 minutes (row + page)', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 11 AS check_id, - i.index_sanity_id, - 70 AS Priority, - N'Aggressive ' - + CASE COALESCE((SELECT SUM(1) - FROM #IndexSanity iMe - INNER JOIN #IndexSanity iOthers - ON iMe.database_id = iOthers.database_id - AND iMe.object_id = iOthers.object_id - AND iOthers.index_id > 1 - WHERE i.index_sanity_id = iMe.index_sanity_id - AND iOthers.is_hypothetical = 0 - AND iOthers.is_disabled = 0 - ), 0) - WHEN 0 THEN N'Under-Indexing' - WHEN 1 THEN N'Under-Indexing' - WHEN 2 THEN N'Under-Indexing' - WHEN 3 THEN N'Under-Indexing' - WHEN 4 THEN N'Indexes' - WHEN 5 THEN N'Indexes' - WHEN 6 THEN N'Indexes' - WHEN 7 THEN N'Indexes' - WHEN 8 THEN N'Indexes' - WHEN 9 THEN N'Indexes' - ELSE N'Over-Indexing' - END AS findings_group, - N'Total lock wait time > 5 minutes (row + page)' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/AggressiveIndexes' AS URL, - (i.db_schema_object_indexid + N': ' + - sz.index_lock_wait_summary + N' NC indexes on table: ') COLLATE DATABASE_DEFAULT + - CAST(COALESCE((SELECT SUM(1) - FROM #IndexSanity iMe - INNER JOIN #IndexSanity iOthers - ON iMe.database_id = iOthers.database_id - AND iMe.object_id = iOthers.object_id - AND iOthers.index_id > 1 - WHERE i.index_sanity_id = iMe.index_sanity_id - AND iOthers.is_hypothetical = 0 - AND iOthers.is_disabled = 0 - ), 0) - AS NVARCHAR(30)) AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - sz.index_size_summary - FROM #IndexSanity AS i - JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id - WHERE (total_row_lock_wait_in_ms + total_page_lock_wait_in_ms) > 300000 - GROUP BY i.index_sanity_id, [database_name], i.db_schema_object_indexid, sz.index_lock_wait_summary, i.index_definition, i.secret_columns, i.index_usage_summary, sz.index_size_summary, sz.index_sanity_id - ORDER BY SUM(total_row_lock_wait_in_ms + total_page_lock_wait_in_ms) DESC, 4, [database_name], 8 - OPTION ( RECOMPILE ); - - - - ---------------------------------------- - --Index Hoarder: Check_id 20-29 - ---------------------------------------- - RAISERROR(N'check_id 20: >= 10 NC indexes on any given table. Yes, 10 is an arbitrary number.', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 20 AS check_id, - MAX(i.index_sanity_id) AS index_sanity_id, - 10 AS Priority, - 'Index Hoarder' AS findings_group, - 'Many NC Indexes on a Single Table' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/IndexHoarder' AS URL, - CAST (COUNT(*) AS NVARCHAR(30)) + ' NC indexes on ' + i.db_schema_object_name AS details, - i.db_schema_object_name + ' (' + CAST (COUNT(*) AS NVARCHAR(30)) + ' indexes)' AS index_definition, - '' AS secret_columns, - REPLACE(CONVERT(NVARCHAR(30),CAST(SUM(total_reads) AS MONEY), 1), N'.00', N'') + N' reads (ALL); ' - + REPLACE(CONVERT(NVARCHAR(30),CAST(SUM(user_updates) AS MONEY), 1), N'.00', N'') + N' writes (ALL); ', - REPLACE(CONVERT(NVARCHAR(30),CAST(MAX(total_rows) AS MONEY), 1), N'.00', N'') + N' rows (MAX)' - + CASE WHEN SUM(total_reserved_MB) > 1024 THEN - N'; ' + CAST(CAST(SUM(total_reserved_MB)/1024. AS NUMERIC(29,1)) AS NVARCHAR(30)) + 'GB (ALL)' - WHEN SUM(total_reserved_MB) > 0 THEN - N'; ' + CAST(CAST(SUM(total_reserved_MB) AS NUMERIC(29,1)) AS NVARCHAR(30)) + 'MB (ALL)' - ELSE '' - END AS index_size_summary - FROM #IndexSanity i - JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id - WHERE index_id NOT IN ( 0, 1 ) - GROUP BY db_schema_object_name, [i].[database_name] - HAVING COUNT(*) >= 10 - ORDER BY i.db_schema_object_name DESC - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 22: NC indexes with 0 reads. (Borderline) and >= 10,000 writes', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 22 AS check_id, - i.index_sanity_id, - 10 AS Priority, - N'Index Hoarder' AS findings_group, - N'Unused NC Index with High Writes' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/IndexHoarder' AS URL, - N'Reads: 0,' - + N' Writes: ' - + REPLACE(CONVERT(NVARCHAR(30), CAST((i.user_updates) AS MONEY), 1), N'.00', N'') - + N' on: ' - + i.db_schema_object_indexid - AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - sz.index_size_summary - FROM #IndexSanity AS i - JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.total_reads=0 - AND i.user_updates >= 10000 - 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 - AND @Filter <> 1 /* 1 = "ignore unused */ - ORDER BY i.db_schema_object_indexid - OPTION ( RECOMPILE ); - - - RAISERROR(N'check_id 34: Filtered index definition columns not in index definition', 0,1) WITH NOWAIT; - - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 34 AS check_id, - i.index_sanity_id, - 80 AS Priority, - N'Abnormal Psychology' AS findings_group, - N'Filter Columns Not In Index Definition' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/IndexFeatures' AS URL, - N'The index ' - + QUOTENAME(i.index_name) - + N' on [' - + i.db_schema_object_name - + N'] has a filter on [' - + i.filter_definition - + N'] but is missing [' - + LTRIM(i.filter_columns_not_in_index) - + N'] from the index definition.' - AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - sz.index_size_summary - FROM #IndexSanity i - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.filter_columns_not_in_index IS NOT NULL - ORDER BY i.db_schema_object_indexid - OPTION ( RECOMPILE ); - - ---------------------------------------- - --Self Loathing Indexes : Check_id 40-49 - ---------------------------------------- - - RAISERROR(N'check_id 40: Fillfactor in nonclustered 80 percent or less', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 40 AS check_id, - i.index_sanity_id, - 100 AS Priority, - N'Self Loathing Indexes' AS findings_group, - N'Low Fill Factor on Nonclustered Index' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/SelfLoathing' AS URL, - CAST(fill_factor AS NVARCHAR(10)) + N'% fill factor on ' + db_schema_object_indexid + N'. '+ - CASE WHEN (last_user_update IS NULL OR user_updates < 1) - THEN N'No writes have been made.' - ELSE - N'Last write was ' + CONVERT(NVARCHAR(16),last_user_update,121) + N' and ' + - CAST(user_updates AS NVARCHAR(25)) + N' updates have been made.' - END - AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - sz.index_size_summary - FROM #IndexSanity AS i - JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id - WHERE index_id > 1 - AND fill_factor BETWEEN 1 AND 80 OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 40: Fillfactor in clustered 80 percent or less', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 40 AS check_id, - i.index_sanity_id, - 100 AS Priority, - N'Self Loathing Indexes' AS findings_group, - N'Low Fill Factor on Clustered Index' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/SelfLoathing' AS URL, - N'Fill factor on ' + db_schema_object_indexid + N' is ' + CAST(fill_factor AS NVARCHAR(10)) + N'%. '+ - CASE WHEN (last_user_update IS NULL OR user_updates < 1) - THEN N'No writes have been made.' - ELSE - N'Last write was ' + CONVERT(NVARCHAR(16),last_user_update,121) + N' and ' + - CAST(user_updates AS NVARCHAR(25)) + N' updates have been made.' - END - AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - sz.index_size_summary - FROM #IndexSanity AS i - JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id - WHERE index_id = 1 - AND fill_factor BETWEEN 1 AND 80 OPTION ( RECOMPILE ); - - - RAISERROR(N'check_id 43: Heaps with forwarded records', 0,1) WITH NOWAIT; - WITH heaps_cte - AS ( SELECT [object_id], - [database_id], - [schema_name], - SUM(forwarded_fetch_count) AS forwarded_fetch_count, - SUM(leaf_delete_count) AS leaf_delete_count - FROM #IndexPartitionSanity - GROUP BY [object_id], - [database_id], - [schema_name] - HAVING SUM(forwarded_fetch_count) > 0) - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 43 AS check_id, - i.index_sanity_id, - 100 AS Priority, - N'Self Loathing Indexes' AS findings_group, - N'Heaps with Forwarded Fetches' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/SelfLoathing' AS URL, - CASE WHEN h.forwarded_fetch_count >= 922337203685477 THEN '>= 922,337,203,685,477' - WHEN @DaysUptime < 1 THEN CAST(h.forwarded_fetch_count AS NVARCHAR(256)) + N' forwarded fetches against heap: ' + db_schema_object_indexid - ELSE REPLACE(CONVERT(NVARCHAR(256),CAST(CAST( - (h.forwarded_fetch_count /*/@DaysUptime */) - AS BIGINT) AS MONEY), 1), '.00', '') - END + N' forwarded fetches per day against heap: ' - + db_schema_object_indexid AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - sz.index_size_summary - FROM #IndexSanity i - JOIN heaps_cte h ON i.[object_id] = h.[object_id] - AND i.[database_id] = h.[database_id] - AND i.[schema_name] = h.[schema_name] - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.index_id = 0 - AND h.forwarded_fetch_count / @DaysUptime > 1000 - AND sz.total_reserved_MB >= CASE WHEN NOT (@GetAllDatabases = 1 OR @Mode = 4) THEN @ThresholdMB ELSE sz.total_reserved_MB END - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 44: Large Heaps with reads or writes.', 0,1) WITH NOWAIT; - WITH heaps_cte - AS ( SELECT [object_id], - [database_id], - [schema_name], - SUM(forwarded_fetch_count) AS forwarded_fetch_count, - SUM(leaf_delete_count) AS leaf_delete_count - FROM #IndexPartitionSanity - GROUP BY [object_id], - [database_id], - [schema_name] - HAVING SUM(forwarded_fetch_count) > 0 - OR SUM(leaf_delete_count) > 0) - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 44 AS check_id, - i.index_sanity_id, - 100 AS Priority, - N'Self Loathing Indexes' AS findings_group, - N'Large Active Heap' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/SelfLoathing' AS URL, - N'Should this table be a heap? ' + db_schema_object_indexid AS details, - i.index_definition, - 'N/A' AS secret_columns, - i.index_usage_summary, - sz.index_size_summary - FROM #IndexSanity i - LEFT JOIN heaps_cte h ON i.[object_id] = h.[object_id] - AND i.[database_id] = h.[database_id] - AND i.[schema_name] = h.[schema_name] - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.index_id = 0 - AND (i.total_reads > 0 OR i.user_updates > 0) - AND sz.total_rows >= 100000 - AND h.[object_id] IS NULL /*don't duplicate the prior check.*/ - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 45: Medium Heaps with reads or writes.', 0,1) WITH NOWAIT; - WITH heaps_cte - AS ( SELECT [object_id], - [database_id], - [schema_name], - SUM(forwarded_fetch_count) AS forwarded_fetch_count, - SUM(leaf_delete_count) AS leaf_delete_count - FROM #IndexPartitionSanity - GROUP BY [object_id], - [database_id], - [schema_name] - HAVING SUM(forwarded_fetch_count) > 0 - OR SUM(leaf_delete_count) > 0) - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 45 AS check_id, - i.index_sanity_id, - 100 AS Priority, - N'Self Loathing Indexes' AS findings_group, - N'Medium Active heap' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/SelfLoathing' AS URL, - N'Should this table be a heap? ' + db_schema_object_indexid AS details, - i.index_definition, - 'N/A' AS secret_columns, - i.index_usage_summary, - sz.index_size_summary - FROM #IndexSanity i - LEFT JOIN heaps_cte h ON i.[object_id] = h.[object_id] - AND i.[database_id] = h.[database_id] - AND i.[schema_name] = h.[schema_name] - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.index_id = 0 - AND - (i.total_reads > 0 OR i.user_updates > 0) - AND sz.total_rows >= 10000 AND sz.total_rows < 100000 - AND h.[object_id] IS NULL /*don't duplicate the prior check.*/ - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 46: Small Heaps with reads or writes.', 0,1) WITH NOWAIT; - WITH heaps_cte - AS ( SELECT [object_id], - [database_id], - [schema_name], - SUM(forwarded_fetch_count) AS forwarded_fetch_count, - SUM(leaf_delete_count) AS leaf_delete_count - FROM #IndexPartitionSanity - GROUP BY [object_id], - [database_id], - [schema_name] - HAVING SUM(forwarded_fetch_count) > 0 - OR SUM(leaf_delete_count) > 0) - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 46 AS check_id, - i.index_sanity_id, - 100 AS Priority, - N'Self Loathing Indexes' AS findings_group, - N'Small Active heap' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/SelfLoathing' AS URL, - N'Should this table be a heap? ' + db_schema_object_indexid AS details, - i.index_definition, - 'N/A' AS secret_columns, - i.index_usage_summary, - sz.index_size_summary - FROM #IndexSanity i - LEFT JOIN heaps_cte h ON i.[object_id] = h.[object_id] - AND i.[database_id] = h.[database_id] - AND i.[schema_name] = h.[schema_name] - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.index_id = 0 - AND - (i.total_reads > 0 OR i.user_updates > 0) - AND sz.total_rows < 10000 - AND h.[object_id] IS NULL /*don't duplicate the prior check.*/ - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 47: Heap with a Nonclustered Primary Key', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 47 AS check_id, - i.index_sanity_id, - 100 AS Priority, - N'Self Loathing Indexes' AS findings_group, - N'Heap with a Nonclustered Primary Key' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/SelfLoathing' AS URL, - db_schema_object_indexid + N' is a HEAP with a Nonclustered Primary Key' AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - sz.index_size_summary - FROM #IndexSanity i - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.index_type = 2 AND i.is_primary_key = 1 - AND EXISTS - ( - SELECT 1/0 - FROM #IndexSanity AS isa - WHERE i.database_id = isa.database_id - AND i.object_id = isa.object_id - AND isa.index_id = 0 - ) - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 48: Nonclustered indexes with a bad read to write ratio', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 48 AS check_id, - i.index_sanity_id, - 100 AS Priority, - N'Index Hoarder' AS findings_group, - N'NC index with High Writes:Reads' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/IndexHoarder' AS URL, - N'Reads: ' - + REPLACE(CONVERT(NVARCHAR(30), CAST((i.total_reads) AS MONEY), 1), N'.00', N'') - + N' Writes: ' - + REPLACE(CONVERT(NVARCHAR(30), CAST((i.user_updates) AS MONEY), 1), N'.00', N'') - + N' on: ' - + i.db_schema_object_indexid AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - sz.index_size_summary - FROM #IndexSanity i - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.total_reads > 0 /*Not totally unused*/ - AND i.user_updates >= 10000 /*Decent write activity*/ - AND i.total_reads < 10000 - AND ((i.total_reads * 10) < i.user_updates) /*10x more writes than reads*/ - 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 - ORDER BY i.db_schema_object_indexid - OPTION ( RECOMPILE ); - - ---------------------------------------- - --Indexaphobia - --Missing indexes with value >= 5 million: : Check_id 50-59 - ---------------------------------------- - RAISERROR(N'check_id 50: Indexaphobia.', 0,1) WITH NOWAIT; - WITH index_size_cte - AS ( SELECT i.database_id, - i.schema_name, - i.[object_id], - MAX(i.index_sanity_id) AS index_sanity_id, - ISNULL(NULLIF(MAX(DATEDIFF(DAY, i.create_date, SYSDATETIME())), 0), 1) AS create_days, - ISNULL ( - CAST(SUM(CASE WHEN index_id NOT IN (0,1) THEN 1 ELSE 0 END) - AS NVARCHAR(30))+ N' NC indexes exist (' + - CASE WHEN SUM(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) > 1024 - THEN CAST(CAST(SUM(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END )/1024. - - AS NUMERIC(29,1)) AS NVARCHAR(30)) + N'GB); ' - ELSE CAST(SUM(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) - AS NVARCHAR(30)) + N'MB); ' - END + - CASE WHEN MAX(sz.[total_rows]) >= 922337203685477 THEN '>= 922,337,203,685,477' - ELSE REPLACE(CONVERT(NVARCHAR(30),CAST(MAX(sz.[total_rows]) AS MONEY), 1), '.00', '') - END + - + N' Estimated Rows;' - ,N'') AS index_size_summary - FROM #IndexSanity AS i - LEFT JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id AND i.database_id = sz.database_id - WHERE i.is_hypothetical = 0 - AND i.is_disabled = 0 - GROUP BY i.database_id, i.schema_name, i.[object_id]) - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - index_usage_summary, index_size_summary, create_tsql, more_info, sample_query_plan ) - - SELECT check_id, t.index_sanity_id, t.check_id, t.findings_group, t.finding, t.[Database Name], t.URL, t.details, t.[definition], - index_estimated_impact, t.index_size_summary, create_tsql, more_info, sample_query_plan - FROM - ( - SELECT ROW_NUMBER() OVER (ORDER BY magic_benefit_number DESC) AS rownum, - 50 AS check_id, - sz.index_sanity_id, - 40 AS Priority, - N'Indexaphobia' AS findings_group, - N'High Value Missing Index' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/Indexaphobia' AS URL, - mi.[statement] + - N' Est. benefit per day: ' + - CASE WHEN magic_benefit_number >= 922337203685477 THEN '>= 922,337,203,685,477' - ELSE REPLACE(CONVERT(NVARCHAR(256),CAST(CAST( - (magic_benefit_number/@DaysUptime) - AS BIGINT) AS MONEY), 1), '.00', '') - END AS details, - missing_index_details AS [definition], - index_estimated_impact, - sz.index_size_summary, - mi.create_tsql, - mi.more_info, - magic_benefit_number, - mi.is_low, - mi.sample_query_plan - FROM #MissingIndexes mi - LEFT JOIN index_size_cte sz ON mi.[object_id] = sz.object_id - AND mi.database_id = sz.database_id - AND mi.schema_name = sz.schema_name - /* Minimum benefit threshold = 100k/day of uptime OR since table creation date, whichever is lower*/ - WHERE @ShowAllMissingIndexRequests=1 - OR ( @Mode = 4 AND (magic_benefit_number / CASE WHEN sz.create_days < @DaysUptime THEN sz.create_days ELSE @DaysUptime END) >= 100000 ) - OR (magic_benefit_number / CASE WHEN sz.create_days < @DaysUptime THEN sz.create_days ELSE @DaysUptime END) >= 100000 - ) AS t - WHERE t.rownum <= CASE WHEN (@Mode <> 4) THEN 20 ELSE t.rownum END - ORDER BY magic_benefit_number DESC - OPTION ( RECOMPILE ); - - - - RAISERROR(N'check_id 68: Identity columns within 30 percent of the end of range', 0,1) WITH NOWAIT; - -- Allowed Ranges: - --int -2,147,483,648 to 2,147,483,647 - --smallint -32,768 to 32,768 - --tinyint 0 to 255 - - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 68 AS check_id, - i.index_sanity_id, - 80 AS Priority, - N'Abnormal Psychology' AS findings_group, - N'Identity Column Within ' + - CAST (calc1.percent_remaining AS NVARCHAR(256)) - + N' Percent End of Range' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, - i.db_schema_object_name + N'.' + QUOTENAME(ic.column_name) - + N' is an identity with type ' + ic.system_type_name - + N', last value of ' - + ISNULL((CONVERT(NVARCHAR(256),CAST(ic.last_value AS DECIMAL(38,0)), 1)),N'NULL') - + N', seed of ' - + ISNULL((CONVERT(NVARCHAR(256),CAST(ic.seed_value AS DECIMAL(38,0)), 1)),N'NULL') - + N', increment of ' + CAST(ic.increment_value AS NVARCHAR(256)) - + N', and range of ' + - CASE ic.system_type_name WHEN 'int' THEN N'+/- 2,147,483,647' - WHEN 'smallint' THEN N'+/- 32,768' - WHEN 'tinyint' THEN N'0 to 255' - ELSE 'unknown' - END - AS details, - i.index_definition, - secret_columns, - ISNULL(i.index_usage_summary,''), - ISNULL(ip.index_size_summary,'') - FROM #IndexSanity i - JOIN #IndexColumns ic ON - i.object_id=ic.object_id - AND i.database_id = ic.database_id - AND i.schema_name = ic.schema_name - AND i.index_id IN (0,1) /* heaps and cx only */ - AND ic.is_identity=1 - AND ic.system_type_name IN ('tinyint', 'smallint', 'int') - JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id - CROSS APPLY ( - SELECT CAST(CASE WHEN ic.increment_value >= 0 - THEN - CASE ic.system_type_name - WHEN 'int' THEN (2147483647 - (ISNULL(ic.last_value,ic.seed_value) + ic.increment_value)) / 2147483647.*100 - WHEN 'smallint' THEN (32768 - (ISNULL(ic.last_value,ic.seed_value) + ic.increment_value)) / 32768.*100 - WHEN 'tinyint' THEN ( 255 - (ISNULL(ic.last_value,ic.seed_value) + ic.increment_value)) / 255.*100 - ELSE 999 - END - ELSE --ic.increment_value is negative - CASE ic.system_type_name - WHEN 'int' THEN ABS(-2147483647 - (ISNULL(ic.last_value,ic.seed_value) + ic.increment_value)) / 2147483647.*100 - WHEN 'smallint' THEN ABS(-32768 - (ISNULL(ic.last_value,ic.seed_value) + ic.increment_value)) / 32768.*100 - WHEN 'tinyint' THEN ABS( 0 - (ISNULL(ic.last_value,ic.seed_value) + ic.increment_value)) / 255.*100 - ELSE -1 - END - END AS NUMERIC(5,1)) AS percent_remaining - ) AS calc1 - WHERE i.index_id IN (1,0) - AND calc1.percent_remaining <= 30 - OPTION (RECOMPILE); - - - RAISERROR(N'check_id 72: Columnstore indexes with Trace Flag 834', 0,1) WITH NOWAIT; - IF EXISTS (SELECT * FROM #IndexSanity WHERE index_type IN (5,6)) - AND EXISTS (SELECT * FROM #TraceStatus WHERE TraceFlag = 834 AND status = 1) - BEGIN - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 72 AS check_id, - i.index_sanity_id, - 80 AS Priority, - N'Abnormal Psychology' AS findings_group, - 'Columnstore Indexes with Trace Flag 834' AS finding, - [database_name] AS [Database Name], - N'https://support.microsoft.com/en-us/kb/3210239' AS URL, - i.db_schema_object_indexid AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - ISNULL(sz.index_size_summary,'') AS index_size_summary - FROM #IndexSanity AS i - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.index_type IN (5,6) - OPTION ( RECOMPILE ); - END; - - ---------------------------------------- - --Statistics Info: Check_id 90-99 - ---------------------------------------- - - RAISERROR(N'check_id 90: Outdated 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 ) - SELECT 90 AS check_id, - 90 AS Priority, - 'Functioning Statistaholics' AS findings_group, - 'Statistics Not Updated Recently', - s.database_name, - 'https://www.brentozar.com/go/stats' AS URL, - 'Statistics on this table were last updated ' + - CASE WHEN s.last_statistics_update IS NULL THEN N' NEVER ' - ELSE CONVERT(NVARCHAR(20), s.last_statistics_update) + - ' have had ' + CONVERT(NVARCHAR(100), s.modification_counter) + - ' modifications in that time, which is ' + - CONVERT(NVARCHAR(100), s.percent_modifications) + - '% of the table.' - END 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.last_statistics_update <= CONVERT(DATETIME, GETDATE() - 30) - AND s.percent_modifications >= 10. - AND s.rows >= 10000 - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 91: Statistics with a low 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 91 AS check_id, - 90 AS Priority, - 'Functioning Statistaholics' AS findings_group, - 'Low Sampling Rates', - s.database_name, - 'https://www.brentozar.com/go/stats' AS URL, - 'Only ' + CONVERT(NVARCHAR(100), s.percent_sampled) + '% of the rows were sampled during the last statistics update. This may lead to poor cardinality estimates.' 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.rows BETWEEN 10000 AND 1000000 AND s.percent_sampled < 10) - OR (s.rows > 1000000 AND s.percent_sampled < 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 ) - SELECT 92 AS check_id, - 90 AS Priority, - 'Functioning Statistaholics' AS findings_group, - 'Statistics With NO RECOMPUTE', - s.database_name, - 'https://www.brentozar.com/go/stats' AS URL, - 'The statistic ' + QUOTENAME(s.statistics_name) + ' is set to not recompute. This can be helpful if data is really skewed, but harmful if you expect automatic statistics updates.' 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.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 ) - SELECT 94 AS check_id, - 100 AS Priority, - 'Serial Forcer' AS findings_group, - 'Check Constraint with Scalar UDF' AS finding, - cc.database_name, - 'https://www.brentozar.com/go/computedscalar' AS URL, - 'The check constraint ' + QUOTENAME(cc.constraint_name) + ' on ' + QUOTENAME(cc.schema_name) + '.' + QUOTENAME(cc.table_name) + ' is based on ' + cc.definition - + '. That indicates it may reference a scalar function, or a CLR function with data access, which can cause all queries and maintenance to run serially.' AS details, - cc.column_definition, - 'N/A' AS secret_columns, - 'N/A' AS index_usage_summary, - 'N/A' AS index_size_summary - FROM #CheckConstraints AS cc - WHERE cc.is_function = 1 - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 99: Computed Columns 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 ) - SELECT 99 AS check_id, - 100 AS Priority, - 'Serial Forcer' AS findings_group, - 'Computed Column with Scalar UDF' AS finding, - cc.database_name, - 'https://www.brentozar.com/go/serialudf' AS URL, - 'The computed column ' + QUOTENAME(cc.column_name) + ' on ' + QUOTENAME(cc.schema_name) + '.' + QUOTENAME(cc.table_name) + ' is based on ' + cc.definition - + '. That indicates it may reference a scalar function, or a CLR function with data access, which can cause all queries and maintenance to run serially.' AS details, - cc.column_definition, - 'N/A' AS secret_columns, - 'N/A' AS index_usage_summary, - 'N/A' AS index_size_summary - FROM #ComputedColumns AS cc - WHERE cc.is_function = 1 - OPTION ( RECOMPILE ); - - - - END /* IF @Mode IN (0, 4) DIAGNOSE priorities 1-100 */ - - - - - - - - - IF @Mode = 4 /* DIAGNOSE*/ - BEGIN; - RAISERROR(N'@Mode=4, running rules for priorities 101+.', 0,1) WITH NOWAIT; - - RAISERROR(N'check_id 21: More Than 5 Percent NC Indexes Are Unused', 0,1) WITH NOWAIT; - DECLARE @percent_NC_indexes_unused NUMERIC(29,1); - DECLARE @NC_indexes_unused_reserved_MB NUMERIC(29,1); - - SELECT @percent_NC_indexes_unused = ( 100.00 * SUM(CASE - WHEN total_reads = 0 - THEN 1 - ELSE 0 - END) ) / COUNT(*), - @NC_indexes_unused_reserved_MB = SUM(CASE - WHEN total_reads = 0 - THEN sz.total_reserved_MB - ELSE 0 - END) - FROM #IndexSanity i - 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()) - 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, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 21 AS check_id, - MAX(i.index_sanity_id) AS index_sanity_id, - 150 AS Priority, - N'Index Hoarder' AS findings_group, - N'More Than 5 Percent NC Indexes Are Unused' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/IndexHoarder' AS URL, - CAST (@percent_NC_indexes_unused AS NVARCHAR(30)) + N' percent NC indexes (' + CAST(COUNT(*) AS NVARCHAR(10)) + N') unused. ' + - N'These take up ' + CAST (@NC_indexes_unused_reserved_MB AS NVARCHAR(30)) + N'MB of space.' AS details, - i.database_name + ' (' + CAST (COUNT(*) AS NVARCHAR(30)) + N' indexes)' AS index_definition, - '' AS secret_columns, - CAST(SUM(total_reads) AS NVARCHAR(256)) + N' reads (ALL); ' - + CAST(SUM([user_updates]) AS NVARCHAR(256)) + N' writes (ALL)' AS index_usage_summary, - - REPLACE(CONVERT(NVARCHAR(30),CAST(MAX([total_rows]) AS MONEY), 1), '.00', '') + N' rows (MAX)' - + CASE WHEN SUM(total_reserved_MB) > 1024 THEN - N'; ' + CAST(CAST(SUM(total_reserved_MB)/1024. AS NUMERIC(29,1)) AS NVARCHAR(30)) + 'GB (ALL)' - WHEN SUM(total_reserved_MB) > 0 THEN - N'; ' + CAST(CAST(SUM(total_reserved_MB) AS NUMERIC(29,1)) AS NVARCHAR(30)) + 'MB (ALL)' - ELSE '' - END AS index_size_summary - FROM #IndexSanity i - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - 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()) - GROUP BY i.database_name - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 23: Indexes with 7 or more columns. (Borderline)', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 23 AS check_id, - i.index_sanity_id, - 150 AS Priority, - N'Index Hoarder' AS findings_group, - N'Borderline: Wide Indexes (7 or More Columns)' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/IndexHoarder' AS URL, - CAST(count_key_columns + count_included_columns AS NVARCHAR(10)) + ' columns on ' - + i.db_schema_object_indexid AS details, i.index_definition, - i.secret_columns, - i.index_usage_summary, - sz.index_size_summary - FROM #IndexSanity AS i - JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id - WHERE ( count_key_columns + count_included_columns ) >= 7 - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 24: Wide clustered indexes (> 3 columns or > 16 bytes).', 0,1) WITH NOWAIT; - WITH count_columns AS ( - SELECT database_id, [object_id], - SUM(CASE max_length WHEN -1 THEN 0 ELSE max_length END) AS sum_max_length - FROM #IndexColumns ic - WHERE index_id IN (1,0) /*Heap or clustered only*/ - AND key_ordinal > 0 - GROUP BY database_id, object_id - ) - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 24 AS check_id, - i.index_sanity_id, - 150 AS Priority, - N'Index Hoarder' AS findings_group, - N'Wide Clustered Index (> 3 columns OR > 16 bytes)' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/IndexHoarder' AS URL, - CAST (i.count_key_columns AS NVARCHAR(10)) + N' columns with potential size of ' - + CAST(cc.sum_max_length AS NVARCHAR(10)) - + N' bytes in clustered index:' + i.db_schema_object_name - + N'. ' + - (SELECT CAST(COUNT(*) AS NVARCHAR(23)) - FROM #IndexSanity i2 - WHERE i2.[object_id]=i.[object_id] - AND i2.database_id = i.database_id - AND i2.index_id <> 1 - AND i2.is_disabled=0 - AND i2.is_hypothetical=0) - + N' NC indexes on the table.' - AS details, - i.index_definition, - secret_columns, - i.index_usage_summary, - ip.index_size_summary - FROM #IndexSanity i - JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id - JOIN count_columns AS cc ON i.[object_id]=cc.[object_id] - AND i.database_id = cc.database_id - WHERE index_id = 1 /* clustered only */ - AND - (count_key_columns > 3 /*More than three key columns.*/ - OR cc.sum_max_length > 16 /*More than 16 bytes in key */) - AND i.is_CX_columnstore = 0 - ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 25: Addicted to nullable columns.', 0,1) WITH NOWAIT; - WITH count_columns AS ( - SELECT [object_id], - [database_id], - [schema_name], - SUM(CASE is_nullable WHEN 1 THEN 0 ELSE 1 END) AS non_nullable_columns, - COUNT(*) AS total_columns - FROM #IndexColumns ic - WHERE index_id IN (1,0) /*Heap or clustered only*/ - GROUP BY [object_id], - [database_id], - [schema_name] - ) - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 25 AS check_id, - i.index_sanity_id, - 200 AS Priority, - N'Index Hoarder' AS findings_group, - N'Addicted to Nulls' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/IndexHoarder' AS URL, - i.db_schema_object_name - + N' allows null in ' + CAST((total_columns-non_nullable_columns) AS NVARCHAR(10)) - + N' of ' + CAST(total_columns AS NVARCHAR(10)) - + N' columns.' AS details, - i.index_definition, - secret_columns, - ISNULL(i.index_usage_summary,''), - ISNULL(ip.index_size_summary,'') - FROM #IndexSanity i - JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id - JOIN count_columns AS cc ON i.[object_id]=cc.[object_id] - AND cc.database_id = ip.database_id - AND cc.[schema_name] = ip.[schema_name] - WHERE i.index_id IN (1,0) - AND cc.non_nullable_columns < 2 - AND cc.total_columns > 3 - ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 26: Wide tables (35+ cols or > 2000 non-LOB bytes).', 0,1) WITH NOWAIT; - WITH count_columns AS ( - SELECT [object_id], - [database_id], - [schema_name], - SUM(CASE max_length WHEN -1 THEN 1 ELSE 0 END) AS count_lob_columns, - SUM(CASE max_length WHEN -1 THEN 0 ELSE max_length END) AS sum_max_length, - COUNT(*) AS total_columns - FROM #IndexColumns ic - WHERE index_id IN (1,0) /*Heap or clustered only*/ - GROUP BY [object_id], - [database_id], - [schema_name] - ) - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 26 AS check_id, - i.index_sanity_id, - 150 AS Priority, - N'Index Hoarder' AS findings_group, - N'Wide Tables: 35+ cols or > 2000 non-LOB bytes' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/IndexHoarder' AS URL, - i.db_schema_object_name - + N' has ' + CAST((total_columns) AS NVARCHAR(10)) - + N' total columns with a max possible width of ' + CAST(sum_max_length AS NVARCHAR(10)) - + N' bytes.' + - CASE WHEN count_lob_columns > 0 THEN CAST((count_lob_columns) AS NVARCHAR(10)) - + ' columns are LOB types.' ELSE '' - END - AS details, - i.index_definition, - secret_columns, - ISNULL(i.index_usage_summary,''), - ISNULL(ip.index_size_summary,'') - FROM #IndexSanity i - JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id - JOIN count_columns AS cc ON i.[object_id]=cc.[object_id] - AND cc.database_id = i.database_id - AND cc.[schema_name] = i.[schema_name] - WHERE i.index_id IN (1,0) - AND - (cc.total_columns >= 35 OR - cc.sum_max_length >= 2000) - ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 27: Addicted to strings.', 0,1) WITH NOWAIT; - WITH count_columns AS ( - SELECT [object_id], - [database_id], - [schema_name], - SUM(CASE WHEN system_type_name IN ('varchar','nvarchar','char') OR max_length=-1 THEN 1 ELSE 0 END) AS string_or_LOB_columns, - COUNT(*) AS total_columns - FROM #IndexColumns ic - WHERE index_id IN (1,0) /*Heap or clustered only*/ - GROUP BY [object_id], - [database_id], - [schema_name] - ) - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 27 AS check_id, - i.index_sanity_id, - 200 AS Priority, - N'Index Hoarder' AS findings_group, - N'Addicted to strings' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/IndexHoarder' AS URL, - i.db_schema_object_name - + N' uses string or LOB types for ' + CAST((string_or_LOB_columns) AS NVARCHAR(10)) - + N' of ' + CAST(total_columns AS NVARCHAR(10)) - + N' columns. Check if data types are valid.' AS details, - i.index_definition, - secret_columns, - ISNULL(i.index_usage_summary,''), - ISNULL(ip.index_size_summary,'') - FROM #IndexSanity i - JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id - JOIN count_columns AS cc ON i.[object_id]=cc.[object_id] - AND cc.database_id = i.database_id - AND cc.[schema_name] = i.[schema_name] - CROSS APPLY (SELECT cc.total_columns - string_or_LOB_columns AS non_string_or_lob_columns) AS calc1 - WHERE i.index_id IN (1,0) - AND calc1.non_string_or_lob_columns <= 1 - AND cc.total_columns > 3 - ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 28: Non-unique clustered index.', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 28 AS check_id, - i.index_sanity_id, - 150 AS Priority, - N'Index Hoarder' AS findings_group, - N'Non-Unique Clustered Index' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/IndexHoarder' AS URL, - N'Uniquifiers will be required! Clustered index: ' + i.db_schema_object_name - + N' and all NC indexes. ' + - (SELECT CAST(COUNT(*) AS NVARCHAR(23)) - FROM #IndexSanity i2 - WHERE i2.[object_id]=i.[object_id] - AND i2.database_id = i.database_id - AND i2.index_id <> 1 - AND i2.is_disabled=0 - AND i2.is_hypothetical=0) - + N' NC indexes on the table.' - AS details, - i.index_definition, - secret_columns, - i.index_usage_summary, - ip.index_size_summary - FROM #IndexSanity i - JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id - WHERE index_id = 1 /* clustered only */ - AND is_unique=0 /* not unique */ - AND is_CX_columnstore=0 /* not a clustered columnstore-- no unique option on those */ - ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 29: NC indexes with 0 reads. (Borderline) and < 10,000 writes', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 29 AS check_id, - i.index_sanity_id, - 150 AS Priority, - N'Index Hoarder' AS findings_group, - N'Unused NC index with Low Writes' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/IndexHoarder' AS URL, - N'0 reads: ' + i.db_schema_object_indexid AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - sz.index_size_summary - FROM #IndexSanity AS i - JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.total_reads=0 - AND i.user_updates < 10000 - 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()) - ORDER BY i.db_schema_object_indexid - OPTION ( RECOMPILE ); - - - ---------------------------------------- - --Feature-Phobic Indexes: Check_id 30-39 - ---------------------------------------- - RAISERROR(N'check_id 30: No indexes with includes', 0,1) WITH NOWAIT; - /* This does not work the way you'd expect with @GetAllDatabases = 1. For details: - https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/825 - */ - - SELECT database_name, - SUM(CASE WHEN count_included_columns > 0 THEN 1 ELSE 0 END) AS number_indexes_with_includes, - 100.* SUM(CASE WHEN count_included_columns > 0 THEN 1 ELSE 0 END) / ( 1.0 * COUNT(*) ) AS percent_indexes_with_includes - INTO #index_includes - FROM #IndexSanity - WHERE is_hypothetical = 0 - AND is_disabled = 0 - AND NOT (@GetAllDatabases = 1 OR @Mode = 0) - GROUP BY database_name; - - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 30 AS check_id, - NULL AS index_sanity_id, - 250 AS Priority, - N'Feature-Phobic Indexes' AS findings_group, - database_name AS [Database Name], - N'No Indexes Use Includes' AS finding, 'https://www.brentozar.com/go/IndexFeatures' AS URL, - N'No Indexes Use Includes' AS details, - database_name + N' (Entire database)' AS index_definition, - N'' AS secret_columns, - N'N/A' AS index_usage_summary, - N'N/A' AS index_size_summary - FROM #index_includes - WHERE number_indexes_with_includes = 0 - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 31: < 3 percent of indexes have includes', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 31 AS check_id, - NULL AS index_sanity_id, - 250 AS Priority, - N'Feature-Phobic Indexes' AS findings_group, - N'Few Indexes Use Includes' AS findings, - database_name AS [Database Name], - N'https://www.brentozar.com/go/IndexFeatures' AS URL, - N'Only ' + CAST(percent_indexes_with_includes AS NVARCHAR(20)) + '% of indexes have includes' AS details, - N'Entire database' AS index_definition, - N'' AS secret_columns, - N'N/A' AS index_usage_summary, - N'N/A' AS index_size_summary - FROM #index_includes - WHERE number_indexes_with_includes > 0 AND percent_indexes_with_includes <= 3 - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 32: filtered indexes and indexed views', 0,1) WITH NOWAIT; - - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT DISTINCT - 32 AS check_id, - NULL AS index_sanity_id, - 250 AS Priority, - N'Feature-Phobic Indexes' AS findings_group, - N'No Filtered Indexes or Indexed Views' AS finding, - i.database_name AS [Database Name], - N'https://www.brentozar.com/go/IndexFeatures' AS URL, - N'These are NOT always needed-- but do you know when you would use them?' AS details, - i.database_name + N' (Entire database)' AS index_definition, - N'' AS secret_columns, - N'N/A' AS index_usage_summary, - N'N/A' AS index_size_summary - FROM #IndexSanity i - WHERE i.database_name NOT IN ( - SELECT database_name - FROM #IndexSanity - WHERE filter_definition <> '' ) - AND i.database_name NOT IN ( - SELECT database_name - FROM #IndexSanity - WHERE is_indexed_view = 1 ) - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 33: Potential filtered indexes based on column names.', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 33 AS check_id, - i.index_sanity_id AS index_sanity_id, - 250 AS Priority, - N'Feature-Phobic Indexes' AS findings_group, - N'Potential Filtered Index (Based on Column Name)' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/IndexFeatures' AS URL, - N'A column name in this index suggests it might be a candidate for filtering (is%, %archive%, %active%, %flag%)' AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - sz.index_size_summary - FROM #IndexColumns ic - JOIN #IndexSanity i ON ic.[object_id]=i.[object_id] - AND ic.database_id =i.database_id - AND ic.schema_name = i.schema_name - AND ic.[index_id]=i.[index_id] - AND i.[index_id] > 1 /* non-clustered index */ - JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id - WHERE (column_name LIKE 'is%' - OR column_name LIKE '%archive%' - OR column_name LIKE '%active%' - OR column_name LIKE '%flag%') - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 41: Hypothetical indexes ', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 41 AS check_id, - i.index_sanity_id, - 150 AS Priority, - N'Self Loathing Indexes' AS findings_group, - N'Hypothetical Index' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/SelfLoathing' AS URL, - N'Hypothetical Index: ' + db_schema_object_indexid AS details, - i.index_definition, - i.secret_columns, - N'' AS index_usage_summary, - N'' AS index_size_summary - FROM #IndexSanity AS i - WHERE is_hypothetical = 1 - OPTION ( RECOMPILE ); - - - RAISERROR(N'check_id 42: Disabled indexes', 0,1) WITH NOWAIT; - --Note: disabled NC indexes will have O rows in #IndexSanitySize! - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 42 AS check_id, - index_sanity_id, - 150 AS Priority, - N'Self Loathing Indexes' AS findings_group, - N'Disabled Index' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/SelfLoathing' AS URL, - N'Disabled Index:' + db_schema_object_indexid AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - 'DISABLED' AS index_size_summary - FROM #IndexSanity AS i - WHERE is_disabled = 1 - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 49: Heaps with deletes', 0,1) WITH NOWAIT; - WITH heaps_cte - AS ( SELECT [object_id], - [database_id], - [schema_name], - SUM(leaf_delete_count) AS leaf_delete_count - FROM #IndexPartitionSanity - GROUP BY [object_id], - [database_id], - [schema_name] - HAVING SUM(forwarded_fetch_count) < 1000 * @DaysUptime /* Only alert about indexes with no forwarded fetches - we already alerted about those in check_id 43 */ - AND SUM(leaf_delete_count) > 0) - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 49 AS check_id, - i.index_sanity_id, - 200 AS Priority, - N'Self Loathing Indexes' AS findings_group, - N'Heaps with Deletes' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/SelfLoathing' AS URL, - CAST(h.leaf_delete_count AS NVARCHAR(256)) + N' deletes against heap:' - + db_schema_object_indexid AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - sz.index_size_summary - FROM #IndexSanity i - JOIN heaps_cte h ON i.[object_id] = h.[object_id] - AND i.[database_id] = h.[database_id] - AND i.[schema_name] = h.[schema_name] - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.index_id = 0 - AND sz.total_reserved_MB >= CASE WHEN NOT (@GetAllDatabases = 1 OR @Mode = 4) THEN @ThresholdMB ELSE sz.total_reserved_MB END - OPTION ( RECOMPILE ); - - ---------------------------------------- - --Abnormal Psychology : Check_id 60-79 - ---------------------------------------- - RAISERROR(N'check_id 60: XML indexes', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 60 AS check_id, - i.index_sanity_id, - 150 AS Priority, - N'Abnormal Psychology' AS findings_group, - N'XML Index' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, - i.db_schema_object_indexid AS details, - i.index_definition, - i.secret_columns, - N'' AS index_usage_summary, - ISNULL(sz.index_size_summary,'') AS index_size_summary - FROM #IndexSanity AS i - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.is_XML = 1 - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 61: Columnstore indexes', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 61 AS check_id, - i.index_sanity_id, - 150 AS Priority, - N'Abnormal Psychology' AS findings_group, - CASE WHEN i.is_NC_columnstore=1 - THEN N'NC Columnstore Index' - ELSE N'Clustered Columnstore Index' - END AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, - i.db_schema_object_indexid AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - ISNULL(sz.index_size_summary,'') AS index_size_summary - FROM #IndexSanity AS i - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.is_NC_columnstore = 1 OR i.is_CX_columnstore=1 - OPTION ( RECOMPILE ); - - - RAISERROR(N'check_id 62: Spatial indexes', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 62 AS check_id, - i.index_sanity_id, - 150 AS Priority, - N'Abnormal Psychology' AS findings_group, - N'Spatial Index' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, - i.db_schema_object_indexid AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - ISNULL(sz.index_size_summary,'') AS index_size_summary - FROM #IndexSanity AS i - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.is_spatial = 1 - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 63: Compressed indexes', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 63 AS check_id, - i.index_sanity_id, - 150 AS Priority, - N'Abnormal Psychology' AS findings_group, - N'Compressed Index' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, - i.db_schema_object_indexid + N'. COMPRESSION: ' + sz.data_compression_desc AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - ISNULL(sz.index_size_summary,'') AS index_size_summary - FROM #IndexSanity AS i - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE sz.data_compression_desc LIKE '%PAGE%' OR sz.data_compression_desc LIKE '%ROW%' - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 64: Partitioned', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 64 AS check_id, - i.index_sanity_id, - 150 AS Priority, - N'Abnormal Psychology' AS findings_group, - N'Partitioned Index' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, - i.db_schema_object_indexid AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - ISNULL(sz.index_size_summary,'') AS index_size_summary - FROM #IndexSanity AS i - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.partition_key_column_name IS NOT NULL - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 65: Non-Aligned Partitioned', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 65 AS check_id, - i.index_sanity_id, - 150 AS Priority, - N'Abnormal Psychology' AS findings_group, - N'Non-Aligned Index on a Partitioned Table' AS finding, - i.[database_name] AS [Database Name], - N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, - i.db_schema_object_indexid AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - ISNULL(sz.index_size_summary,'') AS index_size_summary - FROM #IndexSanity AS i - JOIN #IndexSanity AS iParent ON - i.[object_id]=iParent.[object_id] - AND i.database_id = iParent.database_id - AND i.schema_name = iParent.schema_name - AND iParent.index_id IN (0,1) /* could be a partitioned heap or clustered table */ - AND iParent.partition_key_column_name IS NOT NULL /* parent is partitioned*/ - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.partition_key_column_name IS NULL - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 66: Recently created tables/indexes (1 week)', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 66 AS check_id, - i.index_sanity_id, - 200 AS Priority, - N'Abnormal Psychology' AS findings_group, - N'Recently Created Tables/Indexes (1 week)' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, - i.db_schema_object_indexid + N' was created on ' + - CONVERT(NVARCHAR(16),i.create_date,121) + - N'. Tables/indexes which are dropped/created regularly require special methods for index tuning.' - AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - ISNULL(sz.index_size_summary,'') AS index_size_summary - FROM #IndexSanity AS i - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.create_date >= DATEADD(dd,-7,GETDATE()) - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 67: Recently modified tables/indexes (2 days)', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 67 AS check_id, - i.index_sanity_id, - 200 AS Priority, - N'Abnormal Psychology' AS findings_group, - N'Recently Modified Tables/Indexes (2 days)' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, - i.db_schema_object_indexid + N' was modified on ' + - CONVERT(NVARCHAR(16),i.modify_date,121) + - N'. A large amount of recently modified indexes may mean a lot of rebuilds are occurring each night.' - AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - ISNULL(sz.index_size_summary,'') AS index_size_summary - FROM #IndexSanity AS i - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.modify_date > DATEADD(dd,-2,GETDATE()) - AND /*Exclude recently created tables.*/ - i.create_date < DATEADD(dd,-7,GETDATE()) - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 69: Column collation does not match database collation', 0,1) WITH NOWAIT; - WITH count_columns AS ( - SELECT [object_id], - database_id, - schema_name, - COUNT(*) AS column_count - FROM #IndexColumns ic - WHERE index_id IN (1,0) /*Heap or clustered only*/ - AND collation_name <> @collation - GROUP BY [object_id], - database_id, - schema_name - ) - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 69 AS check_id, - i.index_sanity_id, - 150 AS Priority, - N'Abnormal Psychology' AS findings_group, - N'Column Collation Does Not Match Database Collation' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, - i.db_schema_object_name - + N' has ' + CAST(column_count AS NVARCHAR(20)) - + N' column' + CASE WHEN column_count > 1 THEN 's' ELSE '' END - + N' with a different collation than the db collation of ' - + @collation AS details, - i.index_definition, - secret_columns, - ISNULL(i.index_usage_summary,''), - ISNULL(ip.index_size_summary,'') - FROM #IndexSanity i - JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id - JOIN count_columns AS cc ON i.[object_id]=cc.[object_id] - AND cc.database_id = i.database_id - AND cc.schema_name = i.schema_name - WHERE i.index_id IN (1,0) - ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 70: Replicated columns', 0,1) WITH NOWAIT; - WITH count_columns AS ( - SELECT [object_id], - database_id, - schema_name, - COUNT(*) AS column_count, - SUM(CASE is_replicated WHEN 1 THEN 1 ELSE 0 END) AS replicated_column_count - FROM #IndexColumns ic - WHERE index_id IN (1,0) /*Heap or clustered only*/ - GROUP BY object_id, - database_id, - schema_name - ) - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 70 AS check_id, - i.index_sanity_id, - 200 AS Priority, - N'Abnormal Psychology' AS findings_group, - N'Replicated Columns' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, - i.db_schema_object_name - + N' has ' + CAST(replicated_column_count AS NVARCHAR(20)) - + N' out of ' + CAST(column_count AS NVARCHAR(20)) - + N' column' + CASE WHEN column_count > 1 THEN 's' ELSE '' END - + N' in one or more publications.' - AS details, - i.index_definition, - secret_columns, - ISNULL(i.index_usage_summary,''), - ISNULL(ip.index_size_summary,'') - FROM #IndexSanity i - JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id - JOIN count_columns AS cc ON i.[object_id]=cc.[object_id] - AND i.database_id = cc.database_id - AND i.schema_name = cc.schema_name - WHERE i.index_id IN (1,0) - AND replicated_column_count > 0 - ORDER BY i.db_schema_object_name DESC - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 71: Cascading updates or cascading deletes.', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary, more_info ) - SELECT 71 AS check_id, - NULL AS index_sanity_id, - 150 AS Priority, - N'Abnormal Psychology' AS findings_group, - N'Cascading Updates or Deletes' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, - N'Foreign Key ' + QUOTENAME(foreign_key_name) + - N' on ' + QUOTENAME(parent_object_name) + N'(' + LTRIM(parent_fk_columns) + N')' - + N' referencing ' + QUOTENAME(referenced_object_name) + N'(' + LTRIM(referenced_fk_columns) + N')' - + N' has settings:' - + CASE [delete_referential_action_desc] WHEN N'NO_ACTION' THEN N'' ELSE N' ON DELETE ' +[delete_referential_action_desc] END - + CASE [update_referential_action_desc] WHEN N'NO_ACTION' THEN N'' ELSE N' ON UPDATE ' + [update_referential_action_desc] END - AS details, - [fk].[database_name] AS index_definition, - N'N/A' AS secret_columns, - N'N/A' AS index_usage_summary, - N'N/A' AS index_size_summary, - (SELECT TOP 1 more_info FROM #IndexSanity i WHERE i.object_id=fk.parent_object_id AND i.database_id = fk.database_id AND i.schema_name = fk.schema_name) - AS more_info - FROM #ForeignKeys fk - WHERE ([delete_referential_action_desc] <> N'NO_ACTION' - OR [update_referential_action_desc] <> N'NO_ACTION') - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 72: Unindexed foreign keys.', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary, more_info ) - SELECT 72 AS check_id, - NULL AS index_sanity_id, - 150 AS Priority, - N'Abnormal Psychology' AS findings_group, - N'Unindexed Foreign Keys' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, - N'Foreign Key ' + QUOTENAME(foreign_key_name) + - N' on ' + QUOTENAME(parent_object_name) + N'' - + N' referencing ' + QUOTENAME(referenced_object_name) + N'' - + N' does not appear to have a supporting index.' AS details, - N'N/A' AS index_definition, - N'N/A' AS secret_columns, - N'N/A' AS index_usage_summary, - N'N/A' AS index_size_summary, - (SELECT TOP 1 more_info FROM #IndexSanity i WHERE i.object_id=fk.parent_object_id AND i.database_id = fk.database_id AND i.schema_name = fk.schema_name) - AS more_info - FROM #UnindexedForeignKeys AS fk - OPTION ( RECOMPILE ); - - - RAISERROR(N'check_id 73: In-Memory OLTP', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 73 AS check_id, - i.index_sanity_id, - 150 AS Priority, - N'Abnormal Psychology' AS findings_group, - N'In-Memory OLTP' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, - i.db_schema_object_indexid AS details, - i.index_definition, - i.secret_columns, - i.index_usage_summary, - ISNULL(sz.index_size_summary,'') AS index_size_summary - FROM #IndexSanity AS i - JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id - WHERE i.is_in_memory_oltp = 1 - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 74: Identity column with unusual seed', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 74 AS check_id, - i.index_sanity_id, - 200 AS Priority, - N'Abnormal Psychology' AS findings_group, - N'Identity Column Using a Negative Seed or Increment Other Than 1' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, - i.db_schema_object_name + N'.' + QUOTENAME(ic.column_name) - + N' is an identity with type ' + ic.system_type_name - + N', last value of ' - + ISNULL((CONVERT(NVARCHAR(256),CAST(ic.last_value AS DECIMAL(38,0)), 1)),N'NULL') - + N', seed of ' - + ISNULL((CONVERT(NVARCHAR(256),CAST(ic.seed_value AS DECIMAL(38,0)), 1)),N'NULL') - + N', increment of ' + CAST(ic.increment_value AS NVARCHAR(256)) - + N', and range of ' + - CASE ic.system_type_name WHEN 'int' THEN N'+/- 2,147,483,647' - WHEN 'smallint' THEN N'+/- 32,768' - WHEN 'tinyint' THEN N'0 to 255' - ELSE 'unknown' - END - AS details, - i.index_definition, - secret_columns, - ISNULL(i.index_usage_summary,''), - ISNULL(ip.index_size_summary,'') - FROM #IndexSanity i - JOIN #IndexColumns ic ON - i.object_id=ic.object_id - AND i.database_id = ic.database_id - AND i.schema_name = ic.schema_name - AND i.index_id IN (0,1) /* heaps and cx only */ - AND ic.is_identity=1 - AND ic.system_type_name IN ('tinyint', 'smallint', 'int') - JOIN #IndexSanitySize ip ON i.index_sanity_id = ip.index_sanity_id - WHERE i.index_id IN (1,0) - AND (ic.seed_value < 0 OR ic.increment_value <> 1) - ORDER BY finding, details DESC - OPTION ( RECOMPILE ); - - ---------------------------------------- - --Workaholics: Check_id 80-89 - ---------------------------------------- - - RAISERROR(N'check_id 80: Most scanned indexes (index_usage_stats)', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - - --Workaholics according to index_usage_stats - --This isn't perfect: it mentions the number of scans present in a plan - --A "scan" isn't necessarily a full scan, but hey, we gotta do the best with what we've got. - --in the case of things like indexed views, the operator might be in the plan but never executed - SELECT TOP 5 - 80 AS check_id, - i.index_sanity_id AS index_sanity_id, - 200 AS Priority, - N'Workaholics' AS findings_group, - N'Scan-a-lots (index-usage-stats)' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/Workaholics' AS URL, - REPLACE(CONVERT( NVARCHAR(50),CAST(i.user_scans AS MONEY),1),'.00','') - + N' scans against ' + i.db_schema_object_indexid - + N'. Latest scan: ' + ISNULL(CAST(i.last_user_scan AS NVARCHAR(128)),'?') + N'. ' - + N'ScanFactor=' + CAST(((i.user_scans * iss.total_reserved_MB)/1000000.) AS NVARCHAR(256)) AS details, - ISNULL(i.key_column_names_with_sort_order,'N/A') AS index_definition, - ISNULL(i.secret_columns,'') AS secret_columns, - i.index_usage_summary AS index_usage_summary, - iss.index_size_summary AS index_size_summary - FROM #IndexSanity i - JOIN #IndexSanitySize iss ON i.index_sanity_id=iss.index_sanity_id - WHERE ISNULL(i.user_scans,0) > 0 - ORDER BY i.user_scans * iss.total_reserved_MB DESC - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 81: Top recent accesses (op stats)', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - --Workaholics according to index_operational_stats - --This isn't perfect either: range_scan_count contains full scans, partial scans, even seeks in nested loop ops - --But this can help bubble up some most-accessed tables - SELECT TOP 5 - 81 AS check_id, - i.index_sanity_id AS index_sanity_id, - 200 AS Priority, - N'Workaholics' AS findings_group, - N'Top Recent Accesses (index-op-stats)' AS finding, - [database_name] AS [Database Name], - N'https://www.brentozar.com/go/Workaholics' AS URL, - ISNULL(REPLACE( - CONVERT(NVARCHAR(50),CAST((iss.total_range_scan_count + iss.total_singleton_lookup_count) AS MONEY),1), - N'.00',N'') - + N' uses of ' + i.db_schema_object_indexid + N'. ' - + REPLACE(CONVERT(NVARCHAR(50), CAST(iss.total_range_scan_count AS MONEY),1),N'.00',N'') + N' scans or seeks. ' - + REPLACE(CONVERT(NVARCHAR(50), CAST(iss.total_singleton_lookup_count AS MONEY), 1),N'.00',N'') + N' singleton lookups. ' - + N'OpStatsFactor=' + CAST(((((iss.total_range_scan_count + iss.total_singleton_lookup_count) * iss.total_reserved_MB))/1000000.) AS VARCHAR(256)),'') AS details, - ISNULL(i.key_column_names_with_sort_order,'N/A') AS index_definition, - ISNULL(i.secret_columns,'') AS secret_columns, - i.index_usage_summary AS index_usage_summary, - iss.index_size_summary AS index_size_summary - FROM #IndexSanity i - JOIN #IndexSanitySize iss ON i.index_sanity_id=iss.index_sanity_id - WHERE (ISNULL(iss.total_range_scan_count,0) > 0 OR ISNULL(iss.total_singleton_lookup_count,0) > 0) - ORDER BY ((iss.total_range_scan_count + iss.total_singleton_lookup_count) * iss.total_reserved_MB) DESC - OPTION ( RECOMPILE ); - - - - RAISERROR(N'check_id 93: Statistics with filters', 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 93 AS check_id, - 200 AS Priority, - 'Functioning Statistaholics' AS findings_group, - 'Filter Fixation', - s.database_name, - 'https://www.brentozar.com/go/stats' AS URL, - 'The statistic ' + QUOTENAME(s.statistics_name) + ' is filtered on [' + s.filter_definition + ']. It could be part of a filtered index, or just a filtered statistic. This is purely informational.' 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_filter = 1 - OPTION ( RECOMPILE ); - - - RAISERROR(N'check_id 100: Computed Columns that are not Persisted.', 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 100 AS check_id, - 200 AS Priority, - 'Cold Calculators' AS findings_group, - 'Definition Defeatists' AS finding, - cc.database_name, - '' AS URL, - 'The computed column ' + QUOTENAME(cc.column_name) + ' on ' + QUOTENAME(cc.schema_name) + '.' + QUOTENAME(cc.table_name) + ' is not persisted, which means it will be calculated when a query runs.' + - 'You can change this with the following command, if the definition is deterministic: ALTER TABLE ' + QUOTENAME(cc.schema_name) + '.' + QUOTENAME(cc.table_name) + ' ALTER COLUMN ' + cc.column_name + - ' ADD PERSISTED' AS details, - cc.column_definition, - 'N/A' AS secret_columns, - 'N/A' AS index_usage_summary, - 'N/A' AS index_size_summary - FROM #ComputedColumns AS cc - WHERE cc.is_persisted = 0 - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 110: Temporal Tables.', 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 110 AS check_id, - 200 AS Priority, - 'Abnormal Psychology' AS findings_group, - 'Temporal Tables', - t.database_name, - '' AS URL, - 'The table ' + QUOTENAME(t.schema_name) + '.' + QUOTENAME(t.table_name) + ' is a temporal table, with rows versioned in ' - + QUOTENAME(t.history_schema_name) + '.' + QUOTENAME(t.history_table_name) + ' on History columns ' + QUOTENAME(t.start_column_name) + ' and ' + QUOTENAME(t.end_column_name) + '.' - AS details, - '' AS index_definition, - 'N/A' AS secret_columns, - 'N/A' AS index_usage_summary, - 'N/A' AS index_size_summary - FROM #TemporalTables AS t - OPTION ( RECOMPILE ); - - RAISERROR(N'check_id 121: Optimized For Sequental Keys.', 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 121 AS check_id, - 200 AS Priority, - 'Medicated Indexes' AS findings_group, - 'Optimized For Sequential Keys', - i.database_name, - '' AS URL, - 'The table ' + QUOTENAME(i.schema_name) + '.' + QUOTENAME(i.object_name) + ' is optimized for sequential keys.' AS details, - '' AS index_definition, - 'N/A' AS secret_columns, - 'N/A' AS index_usage_summary, - 'N/A' AS index_size_summary - FROM #IndexSanity AS i - WHERE i.optimize_for_sequential_key = 1 - OPTION ( RECOMPILE ); - - - - END /* IF @Mode = 4 */ - - - - - - - - - RAISERROR(N'Insert a row to help people find help', 0,1) WITH NOWAIT; - IF DATEDIFF(MM, @VersionDate, GETDATE()) > 6 - BEGIN - INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, URL, details, index_definition, - index_usage_summary, index_size_summary ) - VALUES ( -1, 0 , - 'Outdated sp_BlitzIndex', 'sp_BlitzIndex is Over 6 Months Old', 'http://FirstResponderKit.org/', - 'Fine wine gets better with age, but this ' + @ScriptVersionName + ' is more like bad cheese. Time to get a new one.', - @DaysUptimeInsertValue,N'',N'' - ); - END; - - IF EXISTS(SELECT * FROM #BlitzIndexResults) - BEGIN - INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, URL, details, index_definition, - index_usage_summary, index_size_summary ) - VALUES ( -1, 0 , - @ScriptVersionName, - CASE WHEN @GetAllDatabases = 1 THEN N'All Databases' ELSE N'Database ' + QUOTENAME(@DatabaseName) + N' as of ' + CONVERT(NVARCHAR(16),GETDATE(),121) END, - N'From Your Community Volunteers' , N'http://FirstResponderKit.org' , - @DaysUptimeInsertValue,N'',N'' - ); - END; - ELSE IF @Mode = 0 OR (@GetAllDatabases = 1 AND @Mode <> 4) - BEGIN - INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, URL, details, index_definition, - index_usage_summary, index_size_summary ) - VALUES ( -1, 0 , - @ScriptVersionName, - CASE WHEN @GetAllDatabases = 1 THEN N'All Databases' ELSE N'Database ' + QUOTENAME(@DatabaseName) + N' as of ' + CONVERT(NVARCHAR(16),GETDATE(),121) END, - N'From Your Community Volunteers' , N'http://FirstResponderKit.org' , - @DaysUptimeInsertValue, N'',N'' - ); - INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, URL, details, index_definition, - index_usage_summary, index_size_summary ) - VALUES ( 1, 0 , - N'No Major Problems Found', - N'Nice Work!', - N'http://FirstResponderKit.org', - N'Consider running with @Mode = 4 in individual databases (not all) for more detailed diagnostics.', - N'The default Mode 0 only looks for very serious index issues.', - @DaysUptimeInsertValue, N'' - ); - - END; - ELSE - BEGIN - INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, URL, details, index_definition, - index_usage_summary, index_size_summary ) - VALUES ( -1, 0 , - @ScriptVersionName, - CASE WHEN @GetAllDatabases = 1 THEN N'All Databases' ELSE N'Database ' + QUOTENAME(@DatabaseName) + N' as of ' + CONVERT(NVARCHAR(16),GETDATE(),121) END, - N'From Your Community Volunteers' , N'http://FirstResponderKit.org' , - @DaysUptimeInsertValue, N'',N'' - ); - INSERT #BlitzIndexResults ( Priority, check_id, findings_group, finding, URL, details, index_definition, - index_usage_summary, index_size_summary ) - VALUES ( 1, 0 , - N'No Problems Found', - N'Nice job! Or more likely, you have a nearly empty database.', - N'http://FirstResponderKit.org', 'Time to go read some blog posts.', - @DaysUptimeInsertValue, N'', N'' - ); - - END; - - RAISERROR(N'Returning results.', 0,1) WITH NOWAIT; - - /*Return results.*/ - IF (@ValidOutputLocation = 1 AND COALESCE(@OutputServerName, @OutputDatabaseName, @OutputSchemaName, @OutputTableName) IS NOT NULL) - BEGIN - IF NOT @SchemaExists = 1 - BEGIN - RAISERROR (N'Invalid schema name, data could not be saved.', 16, 0); - RETURN; - END - - IF @TableExists = 0 - BEGIN - SET @StringToExecute = - N'CREATE TABLE @@@OutputDatabaseName@@@.@@@OutputSchemaName@@@.@@@OutputTableName@@@ - ( - [id] INT IDENTITY(1,1) NOT NULL, - [run_id] UNIQUEIDENTIFIER, - [run_datetime] DATETIME, - [server_name] NVARCHAR(128), - [priority] INT, - [finding] NVARCHAR(4000), - [database_name] NVARCHAR(128), - [details] NVARCHAR(MAX), - [index_definition] NVARCHAR(MAX), - [secret_columns] NVARCHAR(MAX), - [index_usage_summary] NVARCHAR(MAX), - [index_size_summary] NVARCHAR(MAX), - [more_info] NVARCHAR(MAX), - [url] NVARCHAR(MAX), - [create_tsql] NVARCHAR(MAX), - [sample_query_plan] XML, - CONSTRAINT [PK_ID_@@@RunID@@@] PRIMARY KEY CLUSTERED ([id] ASC) - );'; - - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputSchemaName@@@', @OutputSchemaName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@RunID@@@', @RunID); - - IF @ValidOutputServer = 1 - BEGIN - SET @StringToExecute = REPLACE(@StringToExecute,'''',''''''); - EXEC('EXEC('''+@StringToExecute+''') AT ' + @OutputServerName); - END; - ELSE - BEGIN - EXEC(@StringToExecute); - END; - END; /* @TableExists = 0 */ - - -- Re-check that table now exists (if not we failed creating it) - SET @TableExists = NULL; - EXEC sp_executesql @TableExistsSql, N'@TableExists BIT OUTPUT', @TableExists OUTPUT; - - IF NOT @TableExists = 1 - BEGIN - RAISERROR('Creation of the output table failed.', 16, 0); - RETURN; - END; - - SET @StringToExecute = - N'INSERT @@@OutputServerName@@@.@@@OutputDatabaseName@@@.@@@OutputSchemaName@@@.@@@OutputTableName@@@ - ( - [run_id], - [run_datetime], - [server_name], - [priority], - [finding], - [database_name], - [details], - [index_definition], - [secret_columns], - [index_usage_summary], - [index_size_summary], - [more_info], - [url], - [create_tsql], - [sample_query_plan] - ) - SELECT - ''@@@RunID@@@'', - ''@@@GETDATE@@@'', - ''@@@LocalServerName@@@'', - -- Below should be a copy/paste of the real query - -- Make sure all quotes are escaped - Priority, ISNULL(br.findings_group,N'''') + - CASE WHEN ISNULL(br.finding,N'''') <> N'''' THEN N'': '' ELSE N'''' END - + br.finding AS [Finding], - br.[database_name] AS [Database Name], - br.details AS [Details: schema.table.index(indexid)], - br.index_definition AS [Definition: [Property]] ColumnName {datatype maxbytes}], - ISNULL(br.secret_columns,'''') AS [Secret Columns], - br.index_usage_summary AS [Usage], - br.index_size_summary AS [Size], - COALESCE(br.more_info,sn.more_info,'''') AS [More Info], - br.URL, - COALESCE(br.create_tsql,ts.create_tsql,'''') AS [Create TSQL], - br.sample_query_plan AS [Sample Query Plan] - FROM #BlitzIndexResults br - LEFT JOIN #IndexSanity sn ON - br.index_sanity_id=sn.index_sanity_id - LEFT JOIN #IndexCreateTsql ts ON - br.index_sanity_id=ts.index_sanity_id - ORDER BY br.Priority ASC, br.check_id ASC, br.blitz_result_id ASC, br.findings_group ASC - OPTION (RECOMPILE);'; - - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputServerName@@@', @OutputServerName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputSchemaName@@@', @OutputSchemaName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@RunID@@@', @RunID); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@GETDATE@@@', GETDATE()); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@LocalServerName@@@', CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128))); - EXEC(@StringToExecute); - - END - ELSE - BEGIN - IF(@OutputType <> 'NONE') - BEGIN - SELECT Priority, ISNULL(br.findings_group,N'') + - CASE WHEN ISNULL(br.finding,N'') <> N'' THEN N': ' ELSE N'' END - + br.finding AS [Finding], - br.[database_name] AS [Database Name], - br.details AS [Details: schema.table.index(indexid)], - br.index_definition AS [Definition: [Property]] ColumnName {datatype maxbytes}], - ISNULL(br.secret_columns,'') AS [Secret Columns], - br.index_usage_summary AS [Usage], - br.index_size_summary AS [Size], - COALESCE(br.more_info,sn.more_info,'') AS [More Info], - br.URL, - COALESCE(br.create_tsql,ts.create_tsql,'') AS [Create TSQL], - br.sample_query_plan AS [Sample Query Plan] - FROM #BlitzIndexResults br - LEFT JOIN #IndexSanity sn ON - br.index_sanity_id=sn.index_sanity_id - LEFT JOIN #IndexCreateTsql ts ON - br.index_sanity_id=ts.index_sanity_id - ORDER BY br.Priority ASC, br.check_id ASC, br.blitz_result_id ASC, br.findings_group ASC - OPTION (RECOMPILE); - END; - - END; - - END /* End @Mode=0 or 4 (diagnose)*/ - - - - - - - - - ELSE IF (@Mode=1) /*Summarize*/ - BEGIN - --This mode is to give some overall stats on the database. - IF (@ValidOutputLocation = 1 AND COALESCE(@OutputServerName, @OutputDatabaseName, @OutputSchemaName, @OutputTableName) IS NOT NULL) - BEGIN - - IF NOT @SchemaExists = 1 - BEGIN - RAISERROR (N'Invalid schema name, data could not be saved.', 16, 0); - RETURN; - END - - IF @TableExists = 0 - BEGIN - SET @StringToExecute = - N'CREATE TABLE @@@OutputDatabaseName@@@.@@@OutputSchemaName@@@.@@@OutputTableName@@@ - ( - [id] INT IDENTITY(1,1) NOT NULL, - [run_id] UNIQUEIDENTIFIER, - [run_datetime] DATETIME, - [server_name] NVARCHAR(128), - [database_name] NVARCHAR(128), - [object_count] INT, - [reserved_gb] NUMERIC(29,1), - [reserved_lob_gb] NUMERIC(29,1), - [reserved_row_overflow_gb] NUMERIC(29,1), - [clustered_table_count] INT, - [clustered_table_gb] NUMERIC(29,1), - [nc_index_count] INT, - [nc_index_gb] NUMERIC(29,1), - [table_nc_index_ratio] NUMERIC(29,1), - [heap_count] INT, - [heap_gb] NUMERIC(29,1), - [partioned_table_count] INT, - [partioned_nc_count] INT, - [partioned_gb] NUMERIC(29,1), - [filtered_index_count] INT, - [indexed_view_count] INT, - [max_table_row_count] INT, - [max_table_gb] NUMERIC(29,1), - [max_nc_index_gb] NUMERIC(29,1), - [table_count_over_1gb] INT, - [table_count_over_10gb] INT, - [table_count_over_100gb] INT, - [nc_index_count_over_1gb] INT, - [nc_index_count_over_10gb] INT, - [nc_index_count_over_100gb] INT, - [min_create_date] DATETIME, - [max_create_date] DATETIME, - [max_modify_date] DATETIME, - [display_order] INT, - CONSTRAINT [PK_ID_@@@RunID@@@] PRIMARY KEY CLUSTERED ([id] ASC) - );'; - - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputSchemaName@@@', @OutputSchemaName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@RunID@@@', @RunID); - - IF @ValidOutputServer = 1 - BEGIN - SET @StringToExecute = REPLACE(@StringToExecute,'''',''''''); - EXEC('EXEC('''+@StringToExecute+''') AT ' + @OutputServerName); - END; - ELSE - BEGIN - EXEC(@StringToExecute); - END; - END; /* @TableExists = 0 */ - - -- Re-check that table now exists (if not we failed creating it) - SET @TableExists = NULL; - EXEC sp_executesql @TableExistsSql, N'@TableExists BIT OUTPUT', @TableExists OUTPUT; - - IF NOT @TableExists = 1 - BEGIN - RAISERROR('Creation of the output table failed.', 16, 0); - RETURN; - END; - - SET @StringToExecute = - N'INSERT @@@OutputServerName@@@.@@@OutputDatabaseName@@@.@@@OutputSchemaName@@@.@@@OutputTableName@@@ - ( - [run_id], - [run_datetime], - [server_name], - [database_name], - [object_count], - [reserved_gb], - [reserved_lob_gb], - [reserved_row_overflow_gb], - [clustered_table_count], - [clustered_table_gb], - [nc_index_count], - [nc_index_gb], - [table_nc_index_ratio], - [heap_count], - [heap_gb], - [partioned_table_count], - [partioned_nc_count], - [partioned_gb], - [filtered_index_count], - [indexed_view_count], - [max_table_row_count], - [max_table_gb], - [max_nc_index_gb], - [table_count_over_1gb], - [table_count_over_10gb], - [table_count_over_100gb], - [nc_index_count_over_1gb], - [nc_index_count_over_10gb], - [nc_index_count_over_100gb], - [min_create_date], - [max_create_date], - [max_modify_date], - [display_order] - ) - SELECT ''@@@RunID@@@'', - ''@@@GETDATE@@@'', - ''@@@LocalServerName@@@'', - -- Below should be a copy/paste of the real query - -- Make sure all quotes are escaped - -- NOTE! information line is skipped from output and the query below - -- NOTE! initial columns are not casted to nvarchar due to not outputing informational line - DB_NAME(i.database_id) AS [Database Name], - COUNT(*) AS [Number Objects], - CAST(SUM(sz.total_reserved_MB)/ - 1024. AS NUMERIC(29,1)) AS [All GB], - CAST(SUM(sz.total_reserved_LOB_MB)/ - 1024. AS NUMERIC(29,1)) AS [LOB GB], - CAST(SUM(sz.total_reserved_row_overflow_MB)/ - 1024. AS NUMERIC(29,1)) AS [Row Overflow GB], - SUM(CASE WHEN index_id=1 THEN 1 ELSE 0 END) AS [Clustered Tables], - CAST(SUM(CASE WHEN index_id=1 THEN sz.total_reserved_MB ELSE 0 END) - /1024. AS NUMERIC(29,1)) AS [Clustered Tables GB], - SUM(CASE WHEN index_id NOT IN (0,1) THEN 1 ELSE 0 END) AS [NC Indexes], - CAST(SUM(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) - /1024. AS NUMERIC(29,1)) AS [NC Indexes GB], - CASE WHEN SUM(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) > 0 THEN - CAST(SUM(CASE WHEN index_id IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) - / SUM(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) AS NUMERIC(29,1)) - ELSE 0 END AS [ratio table: NC Indexes], - SUM(CASE WHEN index_id=0 THEN 1 ELSE 0 END) AS [Heaps], - CAST(SUM(CASE WHEN index_id=0 THEN sz.total_reserved_MB ELSE 0 END) - /1024. AS NUMERIC(29,1)) AS [Heaps GB], - SUM(CASE WHEN index_id IN (0,1) AND partition_key_column_name IS NOT NULL THEN 1 ELSE 0 END) AS [Partitioned Tables], - SUM(CASE WHEN index_id NOT IN (0,1) AND partition_key_column_name IS NOT NULL THEN 1 ELSE 0 END) AS [Partitioned NCs], - CAST(SUM(CASE WHEN partition_key_column_name IS NOT NULL THEN sz.total_reserved_MB ELSE 0 END)/1024. AS NUMERIC(29,1)) AS [Partitioned GB], - SUM(CASE WHEN filter_definition <> '''' THEN 1 ELSE 0 END) AS [Filtered Indexes], - SUM(CASE WHEN is_indexed_view=1 THEN 1 ELSE 0 END) AS [Indexed Views], - MAX(total_rows) AS [Max Row Count], - CAST(MAX(CASE WHEN index_id IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) - /1024. AS NUMERIC(29,1)) AS [Max Table GB], - CAST(MAX(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) - /1024. AS NUMERIC(29,1)) AS [Max NC Index GB], - SUM(CASE WHEN index_id IN (0,1) AND sz.total_reserved_MB > 1024 THEN 1 ELSE 0 END) AS [Count Tables > 1GB], - SUM(CASE WHEN index_id IN (0,1) AND sz.total_reserved_MB > 10240 THEN 1 ELSE 0 END) AS [Count Tables > 10GB], - SUM(CASE WHEN index_id IN (0,1) AND sz.total_reserved_MB > 102400 THEN 1 ELSE 0 END) AS [Count Tables > 100GB], - SUM(CASE WHEN index_id NOT IN (0,1) AND sz.total_reserved_MB > 1024 THEN 1 ELSE 0 END) AS [Count NCs > 1GB], - SUM(CASE WHEN index_id NOT IN (0,1) AND sz.total_reserved_MB > 10240 THEN 1 ELSE 0 END) AS [Count NCs > 10GB], - SUM(CASE WHEN index_id NOT IN (0,1) AND sz.total_reserved_MB > 102400 THEN 1 ELSE 0 END) AS [Count NCs > 100GB], - MIN(create_date) AS [Oldest Create Date], - MAX(create_date) AS [Most Recent Create Date], - MAX(modify_date) AS [Most Recent Modify Date], - 1 AS [Display Order] - FROM #IndexSanity AS i - --left join here so we don''t lose disabled nc indexes - LEFT JOIN #IndexSanitySize AS sz - ON i.index_sanity_id=sz.index_sanity_id - GROUP BY DB_NAME(i.database_id) - ORDER BY [Display Order] ASC - OPTION (RECOMPILE);'; - - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputServerName@@@', @OutputServerName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputSchemaName@@@', @OutputSchemaName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@RunID@@@', @RunID); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@GETDATE@@@', GETDATE()); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@LocalServerName@@@', CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128))); - EXEC(@StringToExecute); - - END; /* @ValidOutputLocation = 1 */ - ELSE - BEGIN - IF(@OutputType <> 'NONE') - BEGIN - - RAISERROR(N'@Mode=1, we are summarizing.', 0,1) WITH NOWAIT; - - SELECT DB_NAME(i.database_id) AS [Database Name], - CAST((COUNT(*)) AS NVARCHAR(256)) AS [Number Objects], - CAST(CAST(SUM(sz.total_reserved_MB)/ - 1024. AS NUMERIC(29,1)) AS NVARCHAR(500)) AS [All GB], - CAST(CAST(SUM(sz.total_reserved_LOB_MB)/ - 1024. AS NUMERIC(29,1)) AS NVARCHAR(500)) AS [LOB GB], - CAST(CAST(SUM(sz.total_reserved_row_overflow_MB)/ - 1024. AS NUMERIC(29,1)) AS NVARCHAR(500)) AS [Row Overflow GB], - CAST(SUM(CASE WHEN index_id=1 THEN 1 ELSE 0 END)AS NVARCHAR(50)) AS [Clustered Tables], - CAST(SUM(CASE WHEN index_id=1 THEN sz.total_reserved_MB ELSE 0 END) - /1024. AS NUMERIC(29,1)) AS [Clustered Tables GB], - SUM(CASE WHEN index_id NOT IN (0,1) THEN 1 ELSE 0 END) AS [NC Indexes], - CAST(SUM(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) - /1024. AS NUMERIC(29,1)) AS [NC Indexes GB], - CASE WHEN SUM(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) > 0 THEN - CAST(SUM(CASE WHEN index_id IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) - / SUM(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) AS NUMERIC(29,1)) - ELSE 0 END AS [ratio table: NC Indexes], - SUM(CASE WHEN index_id=0 THEN 1 ELSE 0 END) AS [Heaps], - CAST(SUM(CASE WHEN index_id=0 THEN sz.total_reserved_MB ELSE 0 END) - /1024. AS NUMERIC(29,1)) AS [Heaps GB], - SUM(CASE WHEN index_id IN (0,1) AND partition_key_column_name IS NOT NULL THEN 1 ELSE 0 END) AS [Partitioned Tables], - SUM(CASE WHEN index_id NOT IN (0,1) AND partition_key_column_name IS NOT NULL THEN 1 ELSE 0 END) AS [Partitioned NCs], - CAST(SUM(CASE WHEN partition_key_column_name IS NOT NULL THEN sz.total_reserved_MB ELSE 0 END)/1024. AS NUMERIC(29,1)) AS [Partitioned GB], - SUM(CASE WHEN filter_definition <> '' THEN 1 ELSE 0 END) AS [Filtered Indexes], - SUM(CASE WHEN is_indexed_view=1 THEN 1 ELSE 0 END) AS [Indexed Views], - MAX(total_rows) AS [Max Row Count], - CAST(MAX(CASE WHEN index_id IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) - /1024. AS NUMERIC(29,1)) AS [Max Table GB], - CAST(MAX(CASE WHEN index_id NOT IN (0,1) THEN sz.total_reserved_MB ELSE 0 END) - /1024. AS NUMERIC(29,1)) AS [Max NC Index GB], - SUM(CASE WHEN index_id IN (0,1) AND sz.total_reserved_MB > 1024 THEN 1 ELSE 0 END) AS [Count Tables > 1GB], - SUM(CASE WHEN index_id IN (0,1) AND sz.total_reserved_MB > 10240 THEN 1 ELSE 0 END) AS [Count Tables > 10GB], - SUM(CASE WHEN index_id IN (0,1) AND sz.total_reserved_MB > 102400 THEN 1 ELSE 0 END) AS [Count Tables > 100GB], - SUM(CASE WHEN index_id NOT IN (0,1) AND sz.total_reserved_MB > 1024 THEN 1 ELSE 0 END) AS [Count NCs > 1GB], - SUM(CASE WHEN index_id NOT IN (0,1) AND sz.total_reserved_MB > 10240 THEN 1 ELSE 0 END) AS [Count NCs > 10GB], - SUM(CASE WHEN index_id NOT IN (0,1) AND sz.total_reserved_MB > 102400 THEN 1 ELSE 0 END) AS [Count NCs > 100GB], - MIN(create_date) AS [Oldest Create Date], - MAX(create_date) AS [Most Recent Create Date], - MAX(modify_date) AS [Most Recent Modify Date], - 1 AS [Display Order] - FROM #IndexSanity AS i - --left join here so we don't lose disabled nc indexes - LEFT JOIN #IndexSanitySize AS sz - ON i.index_sanity_id=sz.index_sanity_id - GROUP BY DB_NAME(i.database_id) - UNION ALL - SELECT CASE WHEN @GetAllDatabases = 1 THEN N'All Databases' ELSE N'Database ' + N' as of ' + CONVERT(NVARCHAR(16),GETDATE(),121) END, - @ScriptVersionName, - N'From Your Community Volunteers' , - N'http://FirstResponderKit.org' , - @DaysUptimeInsertValue, - NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL, - NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL, - NULL,NULL,0 AS display_order - ORDER BY [Display Order] ASC - OPTION (RECOMPILE); - END; - END; - - END; /* End @Mode=1 (summarize)*/ - - - - - - - - - ELSE IF (@Mode=2) /*Index Detail*/ - BEGIN - --This mode just spits out all the detail without filters. - --This supports slicing AND dicing in Excel - RAISERROR(N'@Mode=2, here''s the details on existing indexes.', 0,1) WITH NOWAIT; - - IF (@ValidOutputLocation = 1 AND COALESCE(@OutputServerName, @OutputDatabaseName, @OutputSchemaName, @OutputTableName) IS NOT NULL) - BEGIN - IF @SchemaExists = 1 - BEGIN - IF @TableExists = 0 - BEGIN - SET @StringToExecute = - N'CREATE TABLE @@@OutputDatabaseName@@@.@@@OutputSchemaName@@@.@@@OutputTableName@@@ - ( - [id] INT IDENTITY(1,1) NOT NULL, - [run_id] UNIQUEIDENTIFIER, - [run_datetime] DATETIME, - [server_name] NVARCHAR(128), - [database_name] NVARCHAR(128), - [schema_name] NVARCHAR(128), - [table_name] NVARCHAR(128), - [index_name] NVARCHAR(128), - [Drop_Tsql] NVARCHAR(MAX), - [Create_Tsql] NVARCHAR(MAX), - [index_id] INT, - [db_schema_object_indexid] NVARCHAR(500), - [object_type] NVARCHAR(15), - [index_definition] NVARCHAR(MAX), - [key_column_names_with_sort_order] NVARCHAR(MAX), - [count_key_columns] INT, - [include_column_names] NVARCHAR(MAX), - [count_included_columns] INT, - [secret_columns] NVARCHAR(MAX), - [count_secret_columns] INT, - [partition_key_column_name] NVARCHAR(MAX), - [filter_definition] NVARCHAR(MAX), - [is_indexed_view] BIT, - [is_primary_key] BIT, - [is_unique_constraint] BIT, - [is_XML] BIT, - [is_spatial] BIT, - [is_NC_columnstore] BIT, - [is_CX_columnstore] BIT, - [is_in_memory_oltp] BIT, - [is_disabled] BIT, - [is_hypothetical] BIT, - [is_padded] BIT, - [fill_factor] INT, - [is_referenced_by_foreign_key] BIT, - [last_user_seek] DATETIME, - [last_user_scan] DATETIME, - [last_user_lookup] DATETIME, - [last_user_update] DATETIME, - [total_reads] BIGINT, - [user_updates] BIGINT, - [reads_per_write] MONEY, - [index_usage_summary] NVARCHAR(200), - [total_singleton_lookup_count] BIGINT, - [total_range_scan_count] BIGINT, - [total_leaf_delete_count] BIGINT, - [total_leaf_update_count] BIGINT, - [index_op_stats] NVARCHAR(200), - [partition_count] INT, - [total_rows] BIGINT, - [total_reserved_MB] NUMERIC(29,2), - [total_reserved_LOB_MB] NUMERIC(29,2), - [total_reserved_row_overflow_MB] NUMERIC(29,2), - [index_size_summary] NVARCHAR(300), - [total_row_lock_count] BIGINT, - [total_row_lock_wait_count] BIGINT, - [total_row_lock_wait_in_ms] BIGINT, - [avg_row_lock_wait_in_ms] BIGINT, - [total_page_lock_count] BIGINT, - [total_page_lock_wait_count] BIGINT, - [total_page_lock_wait_in_ms] BIGINT, - [avg_page_lock_wait_in_ms] BIGINT, - [total_index_lock_promotion_attempt_count] BIGINT, - [total_index_lock_promotion_count] BIGINT, - [total_forwarded_fetch_count] BIGINT, - [data_compression_desc] NVARCHAR(4000), - [page_latch_wait_count] BIGINT, - [page_latch_wait_in_ms] BIGINT, - [page_io_latch_wait_count] BIGINT, - [page_io_latch_wait_in_ms] BIGINT, - [create_date] DATETIME, - [modify_date] DATETIME, - [more_info] NVARCHAR(500), - [display_order] INT, - CONSTRAINT [PK_ID_@@@RunID@@@] PRIMARY KEY CLUSTERED ([id] ASC) - );'; - - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputSchemaName@@@', @OutputSchemaName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@RunID@@@', @RunID); - - IF @ValidOutputServer = 1 - BEGIN - SET @StringToExecute = REPLACE(@StringToExecute,'''',''''''); - EXEC('EXEC('''+@StringToExecute+''') AT ' + @OutputServerName); - END; - ELSE - BEGIN - EXEC(@StringToExecute); - END; - END; /* @TableExists = 0 */ - - - -- Re-check that table now exists (if not we failed creating it) - SET @TableExists = NULL; - EXEC sp_executesql @TableExistsSql, N'@TableExists BIT OUTPUT', @TableExists OUTPUT; - - IF @TableExists = 1 - BEGIN - SET @StringToExecute = - N'INSERT @@@OutputServerName@@@.@@@OutputDatabaseName@@@.@@@OutputSchemaName@@@.@@@OutputTableName@@@ - ( - [run_id], - [run_datetime], - [server_name], - [database_name], - [schema_name], - [table_name], - [index_name], - [Drop_Tsql], - [Create_Tsql], - [index_id], - [db_schema_object_indexid], - [object_type], - [index_definition], - [key_column_names_with_sort_order], - [count_key_columns], - [include_column_names], - [count_included_columns], - [secret_columns], - [count_secret_columns], - [partition_key_column_name], - [filter_definition], - [is_indexed_view], - [is_primary_key], - [is_unique_constraint], - [is_XML], - [is_spatial], - [is_NC_columnstore], - [is_CX_columnstore], - [is_in_memory_oltp], - [is_disabled], - [is_hypothetical], - [is_padded], - [fill_factor], - [is_referenced_by_foreign_key], - [last_user_seek], - [last_user_scan], - [last_user_lookup], - [last_user_update], - [total_reads], - [user_updates], - [reads_per_write], - [index_usage_summary], - [total_singleton_lookup_count], - [total_range_scan_count], - [total_leaf_delete_count], - [total_leaf_update_count], - [index_op_stats], - [partition_count], - [total_rows], - [total_reserved_MB], - [total_reserved_LOB_MB], - [total_reserved_row_overflow_MB], - [index_size_summary], - [total_row_lock_count], - [total_row_lock_wait_count], - [total_row_lock_wait_in_ms], - [avg_row_lock_wait_in_ms], - [total_page_lock_count], - [total_page_lock_wait_count], - [total_page_lock_wait_in_ms], - [avg_page_lock_wait_in_ms], - [total_index_lock_promotion_attempt_count], - [total_index_lock_promotion_count], - [total_forwarded_fetch_count], - [data_compression_desc], - [page_latch_wait_count], - [page_latch_wait_in_ms], - [page_io_latch_wait_count], - [page_io_latch_wait_in_ms], - [create_date], - [modify_date], - [more_info], - [display_order] - ) - SELECT ''@@@RunID@@@'', - ''@@@GETDATE@@@'', - ''@@@LocalServerName@@@'', - -- Below should be a copy/paste of the real query - -- Make sure all quotes are escaped - i.[database_name] AS [Database Name], - i.[schema_name] AS [Schema Name], - i.[object_name] AS [Object Name], - ISNULL(i.index_name, '''') AS [Index Name], - CASE - WHEN i.is_primary_key = 1 AND i.index_definition <> ''[HEAP]'' - THEN N''--ALTER TABLE '' + QUOTENAME(i.[database_name]) + N''.'' + QUOTENAME(i.[schema_name]) + N''.'' + QUOTENAME(i.[object_name]) + - N'' DROP CONSTRAINT '' + QUOTENAME(i.index_name) + N'';'' - WHEN i.is_primary_key = 0 AND i.is_unique_constraint = 1 AND i.index_definition <> ''[HEAP]'' - THEN N''--ALTER TABLE '' + QUOTENAME(i.[database_name]) + N''.'' + QUOTENAME(i.[schema_name]) + N''.'' + QUOTENAME(i.[object_name]) + - N'' DROP CONSTRAINT '' + QUOTENAME(i.index_name) + N'';'' - WHEN i.is_primary_key = 0 AND i.index_definition <> ''[HEAP]'' - THEN N''--DROP INDEX ''+ QUOTENAME(i.index_name) + N'' ON '' + QUOTENAME(i.[database_name]) + N''.'' + - QUOTENAME(i.[schema_name]) + N''.'' + QUOTENAME(i.[object_name]) + N'';'' - ELSE N'''' - END AS [Drop TSQL], - CASE - WHEN i.index_definition = ''[HEAP]'' THEN N'''' - ELSE N''--'' + ict.create_tsql END AS [Create TSQL], - CAST(i.index_id AS NVARCHAR(10))AS [Index ID], - db_schema_object_indexid AS [Details: schema.table.index(indexid)], - CASE WHEN index_id IN ( 1, 0 ) THEN ''TABLE'' - ELSE ''NonClustered'' - END AS [Object Type], - LEFT(index_definition,4000) AS [Definition: [Property]] ColumnName {datatype maxbytes}], - ISNULL(LTRIM(key_column_names_with_sort_order), '''') AS [Key Column Names With Sort], - ISNULL(count_key_columns, 0) AS [Count Key Columns], - ISNULL(include_column_names, '''') AS [Include Column Names], - ISNULL(count_included_columns,0) AS [Count Included Columns], - ISNULL(secret_columns,'''') AS [Secret Column Names], - ISNULL(count_secret_columns,0) AS [Count Secret Columns], - ISNULL(partition_key_column_name, '''') AS [Partition Key Column Name], - ISNULL(filter_definition, '''') AS [Filter Definition], - is_indexed_view AS [Is Indexed View], - is_primary_key AS [Is Primary Key], - is_unique_constraint AS [Is Unique Constraint], - is_XML AS [Is XML], - is_spatial AS [Is Spatial], - is_NC_columnstore AS [Is NC Columnstore], - is_CX_columnstore AS [Is CX Columnstore], - is_in_memory_oltp AS [Is In-Memory OLTP], - is_disabled AS [Is Disabled], - is_hypothetical AS [Is Hypothetical], - is_padded AS [Is Padded], - fill_factor AS [Fill Factor], - is_referenced_by_foreign_key AS [Is Reference by Foreign Key], - last_user_seek AS [Last User Seek], - last_user_scan AS [Last User Scan], - last_user_lookup AS [Last User Lookup], - last_user_update AS [Last User Update], - total_reads AS [Total Reads], - user_updates AS [User Updates], - reads_per_write AS [Reads Per Write], - index_usage_summary AS [Index Usage], - sz.total_singleton_lookup_count AS [Singleton Lookups], - sz.total_range_scan_count AS [Range Scans], - sz.total_leaf_delete_count AS [Leaf Deletes], - sz.total_leaf_update_count AS [Leaf Updates], - sz.index_op_stats AS [Index Op Stats], - sz.partition_count AS [Partition Count], - sz.total_rows AS [Rows], - sz.total_reserved_MB AS [Reserved MB], - sz.total_reserved_LOB_MB AS [Reserved LOB MB], - sz.total_reserved_row_overflow_MB AS [Reserved Row Overflow MB], - sz.index_size_summary AS [Index Size], - sz.total_row_lock_count AS [Row Lock Count], - sz.total_row_lock_wait_count AS [Row Lock Wait Count], - sz.total_row_lock_wait_in_ms AS [Row Lock Wait ms], - sz.avg_row_lock_wait_in_ms AS [Avg Row Lock Wait ms], - sz.total_page_lock_count AS [Page Lock Count], - sz.total_page_lock_wait_count AS [Page Lock Wait Count], - sz.total_page_lock_wait_in_ms AS [Page Lock Wait ms], - sz.avg_page_lock_wait_in_ms AS [Avg Page Lock Wait ms], - sz.total_index_lock_promotion_attempt_count AS [Lock Escalation Attempts], - sz.total_index_lock_promotion_count AS [Lock Escalations], - sz.total_forwarded_fetch_count AS [Forwarded Fetches], - sz.data_compression_desc AS [Data Compression], - sz.page_latch_wait_count, - sz.page_latch_wait_in_ms, - sz.page_io_latch_wait_count, - sz.page_io_latch_wait_in_ms, - i.create_date AS [Create Date], - i.modify_date AS [Modify Date], - more_info AS [More Info], - 1 AS [Display Order] - FROM #IndexSanity AS i - LEFT JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id - LEFT JOIN #IndexCreateTsql AS ict ON i.index_sanity_id = ict.index_sanity_id - ORDER BY [Database Name], [Schema Name], [Object Name], [Index ID] - OPTION (RECOMPILE);'; - - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputServerName@@@', @OutputServerName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputSchemaName@@@', @OutputSchemaName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@RunID@@@', @RunID); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@GETDATE@@@', GETDATE()); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@LocalServerName@@@', CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128))); - EXEC(@StringToExecute); - END; /* @TableExists = 1 */ - ELSE - RAISERROR('Creation of the output table failed.', 16, 0); - END; /* @TableExists = 0 */ - ELSE - RAISERROR (N'Invalid schema name, data could not be saved.', 16, 0); - END; /* @ValidOutputLocation = 1 */ - ELSE - - IF(@OutputType <> 'NONE') - BEGIN - SELECT i.[database_name] AS [Database Name], - i.[schema_name] AS [Schema Name], - i.[object_name] AS [Object Name], - ISNULL(i.index_name, '') AS [Index Name], - CAST(i.index_id AS NVARCHAR(10))AS [Index ID], - db_schema_object_indexid AS [Details: schema.table.index(indexid)], - CASE WHEN index_id IN ( 1, 0 ) THEN 'TABLE' - ELSE 'NonClustered' - END AS [Object Type], - index_definition AS [Definition: [Property]] ColumnName {datatype maxbytes}], - ISNULL(LTRIM(key_column_names_with_sort_order), '') AS [Key Column Names With Sort], - ISNULL(count_key_columns, 0) AS [Count Key Columns], - ISNULL(include_column_names, '') AS [Include Column Names], - ISNULL(count_included_columns,0) AS [Count Included Columns], - ISNULL(secret_columns,'') AS [Secret Column Names], - ISNULL(count_secret_columns,0) AS [Count Secret Columns], - ISNULL(partition_key_column_name, '') AS [Partition Key Column Name], - ISNULL(filter_definition, '') AS [Filter Definition], - is_indexed_view AS [Is Indexed View], - is_primary_key AS [Is Primary Key], - is_unique_constraint AS [Is Unique Constraint] , - is_XML AS [Is XML], - is_spatial AS [Is Spatial], - is_NC_columnstore AS [Is NC Columnstore], - is_CX_columnstore AS [Is CX Columnstore], - is_in_memory_oltp AS [Is In-Memory OLTP], - is_disabled AS [Is Disabled], - is_hypothetical AS [Is Hypothetical], - is_padded AS [Is Padded], - fill_factor AS [Fill Factor], - is_referenced_by_foreign_key AS [Is Reference by Foreign Key], - last_user_seek AS [Last User Seek], - last_user_scan AS [Last User Scan], - last_user_lookup AS [Last User Lookup], - last_user_update AS [Last User Update], - total_reads AS [Total Reads], - user_updates AS [User Updates], - reads_per_write AS [Reads Per Write], - index_usage_summary AS [Index Usage], - sz.total_singleton_lookup_count AS [Singleton Lookups], - sz.total_range_scan_count AS [Range Scans], - sz.total_leaf_delete_count AS [Leaf Deletes], - sz.total_leaf_update_count AS [Leaf Updates], - sz.index_op_stats AS [Index Op Stats], - sz.partition_count AS [Partition Count], - sz.total_rows AS [Rows], - sz.total_reserved_MB AS [Reserved MB], - sz.total_reserved_LOB_MB AS [Reserved LOB MB], - sz.total_reserved_row_overflow_MB AS [Reserved Row Overflow MB], - sz.index_size_summary AS [Index Size], - sz.total_row_lock_count AS [Row Lock Count], - sz.total_row_lock_wait_count AS [Row Lock Wait Count], - sz.total_row_lock_wait_in_ms AS [Row Lock Wait ms], - sz.avg_row_lock_wait_in_ms AS [Avg Row Lock Wait ms], - sz.total_page_lock_count AS [Page Lock Count], - sz.total_page_lock_wait_count AS [Page Lock Wait Count], - sz.total_page_lock_wait_in_ms AS [Page Lock Wait ms], - sz.avg_page_lock_wait_in_ms AS [Avg Page Lock Wait ms], - sz.total_index_lock_promotion_attempt_count AS [Lock Escalation Attempts], - sz.total_index_lock_promotion_count AS [Lock Escalations], - sz.page_latch_wait_count AS [Page Latch Wait Count], - sz.page_latch_wait_in_ms AS [Page Latch Wait ms], - sz.page_io_latch_wait_count AS [Page IO Latch Wait Count], - sz.page_io_latch_wait_in_ms as [Page IO Latch Wait ms], - sz.total_forwarded_fetch_count AS [Forwarded Fetches], - sz.data_compression_desc AS [Data Compression], - i.create_date AS [Create Date], - i.modify_date AS [Modify Date], - more_info AS [More Info], - CASE - WHEN i.is_primary_key = 1 AND i.index_definition <> '[HEAP]' - THEN N'--ALTER TABLE ' + QUOTENAME(i.[database_name]) + N'.' + QUOTENAME(i.[schema_name]) + N'.' + QUOTENAME(i.[object_name]) - + N' DROP CONSTRAINT ' + QUOTENAME(i.index_name) + N';' - WHEN i.is_primary_key = 0 AND i.is_unique_constraint = 1 AND i.index_definition <> '[HEAP]' - THEN N'--ALTER TABLE ' + QUOTENAME(i.[database_name]) + N'.' + QUOTENAME(i.[schema_name]) + N'.' + QUOTENAME(i.[object_name]) - + N' DROP CONSTRAINT ' + QUOTENAME(i.index_name) + N';' - WHEN i.is_primary_key = 0 AND i.index_definition <> '[HEAP]' - THEN N'--DROP INDEX '+ QUOTENAME(i.index_name) + N' ON ' + QUOTENAME(i.[database_name]) + N'.' + - QUOTENAME(i.[schema_name]) + N'.' + QUOTENAME(i.[object_name]) + N';' - ELSE N'' - END AS [Drop TSQL], - CASE - WHEN i.index_definition = '[HEAP]' THEN N'' - ELSE N'--' + ict.create_tsql END AS [Create TSQL], - 1 AS [Display Order] - FROM #IndexSanity AS i --left join here so we don't lose disabled nc indexes - LEFT JOIN #IndexSanitySize AS sz ON i.index_sanity_id = sz.index_sanity_id - LEFT JOIN #IndexCreateTsql AS ict ON i.index_sanity_id = ict.index_sanity_id - ORDER BY /* Shout out to DHutmacher */ - /*DESC*/ - CASE WHEN @SortOrder = N'rows' AND @SortDirection = N'desc' THEN sz.total_rows ELSE NULL END DESC, - CASE WHEN @SortOrder = N'reserved_mb' AND @SortDirection = N'desc' THEN sz.total_reserved_MB ELSE NULL END DESC, - CASE WHEN @SortOrder = N'size' AND @SortDirection = N'desc' THEN sz.total_reserved_MB ELSE NULL END DESC, - CASE WHEN @SortOrder = N'reserved_lob_mb' AND @SortDirection = N'desc' THEN sz.total_reserved_LOB_MB ELSE NULL END DESC, - CASE WHEN @SortOrder = N'lob' AND @SortDirection = N'desc' THEN sz.total_reserved_LOB_MB ELSE NULL END DESC, - CASE WHEN @SortOrder = N'total_row_lock_wait_in_ms' AND @SortDirection = N'desc' THEN COALESCE(sz.total_row_lock_wait_in_ms,0) ELSE NULL END DESC, - CASE WHEN @SortOrder = N'total_page_lock_wait_in_ms' AND @SortDirection = N'desc' THEN COALESCE(sz.total_page_lock_wait_in_ms,0) ELSE NULL END DESC, - CASE WHEN @SortOrder = N'lock_time' AND @SortDirection = N'desc' THEN (COALESCE(sz.total_row_lock_wait_in_ms,0) + COALESCE(sz.total_page_lock_wait_in_ms,0)) ELSE NULL END DESC, - CASE WHEN @SortOrder = N'total_reads' AND @SortDirection = N'desc' THEN total_reads ELSE NULL END DESC, - CASE WHEN @SortOrder = N'reads' AND @SortDirection = N'desc' THEN total_reads ELSE NULL END DESC, - CASE WHEN @SortOrder = N'user_updates' AND @SortDirection = N'desc' THEN user_updates ELSE NULL END DESC, - CASE WHEN @SortOrder = N'writes' AND @SortDirection = N'desc' THEN user_updates ELSE NULL END DESC, - CASE WHEN @SortOrder = N'reads_per_write' AND @SortDirection = N'desc' THEN reads_per_write ELSE NULL END DESC, - CASE WHEN @SortOrder = N'ratio' AND @SortDirection = N'desc' THEN reads_per_write ELSE NULL END DESC, - CASE WHEN @SortOrder = N'forward_fetches' AND @SortDirection = N'desc' THEN sz.total_forwarded_fetch_count ELSE NULL END DESC, - CASE WHEN @SortOrder = N'fetches' AND @SortDirection = N'desc' THEN sz.total_forwarded_fetch_count ELSE NULL END DESC, - CASE WHEN @SortOrder = N'create_date' AND @SortDirection = N'desc' THEN CONVERT(DATETIME, i.create_date) ELSE NULL END DESC, - CASE WHEN @SortOrder = N'modify_date' AND @SortDirection = N'desc' THEN CONVERT(DATETIME, i.modify_date) ELSE NULL END DESC, - /*ASC*/ - CASE WHEN @SortOrder = N'rows' AND @SortDirection = N'asc' THEN sz.total_rows ELSE NULL END ASC, - CASE WHEN @SortOrder = N'reserved_mb' AND @SortDirection = N'asc' THEN sz.total_reserved_MB ELSE NULL END ASC, - CASE WHEN @SortOrder = N'size' AND @SortDirection = N'asc' THEN sz.total_reserved_MB ELSE NULL END ASC, - CASE WHEN @SortOrder = N'reserved_lob_mb' AND @SortDirection = N'asc' THEN sz.total_reserved_LOB_MB ELSE NULL END ASC, - CASE WHEN @SortOrder = N'lob' AND @SortDirection = N'asc' THEN sz.total_reserved_LOB_MB ELSE NULL END ASC, - CASE WHEN @SortOrder = N'total_row_lock_wait_in_ms' AND @SortDirection = N'asc' THEN COALESCE(sz.total_row_lock_wait_in_ms,0) ELSE NULL END ASC, - CASE WHEN @SortOrder = N'total_page_lock_wait_in_ms' AND @SortDirection = N'asc' THEN COALESCE(sz.total_page_lock_wait_in_ms,0) ELSE NULL END ASC, - CASE WHEN @SortOrder = N'lock_time' AND @SortDirection = N'asc' THEN (COALESCE(sz.total_row_lock_wait_in_ms,0) + COALESCE(sz.total_page_lock_wait_in_ms,0)) ELSE NULL END ASC, - CASE WHEN @SortOrder = N'total_reads' AND @SortDirection = N'asc' THEN total_reads ELSE NULL END ASC, - CASE WHEN @SortOrder = N'reads' AND @SortDirection = N'asc' THEN total_reads ELSE NULL END ASC, - CASE WHEN @SortOrder = N'user_updates' AND @SortDirection = N'asc' THEN user_updates ELSE NULL END ASC, - CASE WHEN @SortOrder = N'writes' AND @SortDirection = N'asc' THEN user_updates ELSE NULL END ASC, - CASE WHEN @SortOrder = N'reads_per_write' AND @SortDirection = N'asc' THEN reads_per_write ELSE NULL END ASC, - CASE WHEN @SortOrder = N'ratio' AND @SortDirection = N'asc' THEN reads_per_write ELSE NULL END ASC, - CASE WHEN @SortOrder = N'forward_fetches' AND @SortDirection = N'asc' THEN sz.total_forwarded_fetch_count ELSE NULL END ASC, - CASE WHEN @SortOrder = N'fetches' AND @SortDirection = N'asc' THEN sz.total_forwarded_fetch_count ELSE NULL END ASC, - CASE WHEN @SortOrder = N'create_date' AND @SortDirection = N'asc' THEN CONVERT(DATETIME, i.create_date) ELSE NULL END ASC, - CASE WHEN @SortOrder = N'modify_date' AND @SortDirection = N'asc' THEN CONVERT(DATETIME, i.modify_date) ELSE NULL END ASC, - i.[database_name], [Schema Name], [Object Name], [Index ID] - OPTION (RECOMPILE); - END; - - END; /* End @Mode=2 (index detail)*/ - - - - - - - - - ELSE IF (@Mode=3) /*Missing index Detail*/ - BEGIN - IF (@ValidOutputLocation = 1 AND COALESCE(@OutputServerName, @OutputDatabaseName, @OutputSchemaName, @OutputTableName) IS NOT NULL) - BEGIN - - IF NOT @SchemaExists = 1 - BEGIN - RAISERROR (N'Invalid schema name, data could not be saved.', 16, 0); - RETURN; - END - - IF @TableExists = 0 - BEGIN - SET @StringToExecute = - N'CREATE TABLE @@@OutputDatabaseName@@@.@@@OutputSchemaName@@@.@@@OutputTableName@@@ - ( - [id] INT IDENTITY(1,1) NOT NULL, - [run_id] UNIQUEIDENTIFIER, - [run_datetime] DATETIME, - [server_name] NVARCHAR(128), - [database_name] NVARCHAR(128), - [schema_name] NVARCHAR(128), - [table_name] NVARCHAR(128), - [magic_benefit_number] BIGINT, - [missing_index_details] NVARCHAR(MAX), - [avg_total_user_cost] NUMERIC(29,4), - [avg_user_impact] NUMERIC(29,1), - [user_seeks] BIGINT, - [user_scans] BIGINT, - [unique_compiles] BIGINT, - [equality_columns_with_data_type] NVARCHAR(MAX), - [inequality_columns_with_data_type] NVARCHAR(MAX), - [included_columns_with_data_type] NVARCHAR(MAX), - [index_estimated_impact] NVARCHAR(256), - [create_tsql] NVARCHAR(MAX), - [more_info] NVARCHAR(600), - [display_order] INT, - [is_low] BIT, - [sample_query_plan] XML, - CONSTRAINT [PK_ID_@@@RunID@@@] PRIMARY KEY CLUSTERED ([id] ASC) - );'; - - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputSchemaName@@@', @OutputSchemaName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@RunID@@@', @RunID); - - IF @ValidOutputServer = 1 - BEGIN - SET @StringToExecute = REPLACE(@StringToExecute,'''',''''''); - EXEC('EXEC('''+@StringToExecute+''') AT ' + @OutputServerName); - END; - ELSE - BEGIN - EXEC(@StringToExecute); - END; - END; /* @TableExists = 0 */ - - -- Re-check that table now exists (if not we failed creating it) - SET @TableExists = NULL; - EXEC sp_executesql @TableExistsSql, N'@TableExists BIT OUTPUT', @TableExists OUTPUT; - - IF NOT @TableExists = 1 - BEGIN - RAISERROR('Creation of the output table failed.', 16, 0); - RETURN; - END; - SET @StringToExecute = - N'WITH create_date AS ( - SELECT i.database_id, - i.schema_name, - i.[object_id], - ISNULL(NULLIF(MAX(DATEDIFF(DAY, i.create_date, SYSDATETIME())), 0), 1) AS create_days - FROM #IndexSanity AS i - GROUP BY i.database_id, i.schema_name, i.object_id - ) - INSERT @@@OutputServerName@@@.@@@OutputDatabaseName@@@.@@@OutputSchemaName@@@.@@@OutputTableName@@@ - ( - [run_id], - [run_datetime], - [server_name], - [database_name], - [schema_name], - [table_name], - [magic_benefit_number], - [missing_index_details], - [avg_total_user_cost], - [avg_user_impact], - [user_seeks], - [user_scans], - [unique_compiles], - [equality_columns_with_data_type], - [inequality_columns_with_data_type], - [included_columns_with_data_type], - [index_estimated_impact], - [create_tsql], - [more_info], - [display_order], - [is_low], - [sample_query_plan] - ) - SELECT ''@@@RunID@@@'', - ''@@@GETDATE@@@'', - ''@@@LocalServerName@@@'', - -- Below should be a copy/paste of the real query - -- Make sure all quotes are escaped - -- NOTE! information line is skipped from output and the query below - -- NOTE! CTE block is above insert in the copied SQL - mi.database_name AS [Database Name], - mi.[schema_name] AS [Schema], - mi.table_name AS [Table], - CAST((mi.magic_benefit_number / CASE WHEN cd.create_days < @DaysUptime THEN cd.create_days ELSE @DaysUptime END) AS BIGINT) - AS [Magic Benefit Number], - mi.missing_index_details AS [Missing Index Details], - mi.avg_total_user_cost AS [Avg Query Cost], - mi.avg_user_impact AS [Est Index Improvement], - mi.user_seeks AS [Seeks], - mi.user_scans AS [Scans], - mi.unique_compiles AS [Compiles], - mi.equality_columns_with_data_type AS [Equality Columns], - mi.inequality_columns_with_data_type AS [Inequality Columns], - mi.included_columns_with_data_type AS [Included Columns], - mi.index_estimated_impact AS [Estimated Impact], - mi.create_tsql AS [Create TSQL], - mi.more_info AS [More Info], - 1 AS [Display Order], - mi.is_low, - mi.sample_query_plan AS [Sample Query Plan] - FROM #MissingIndexes AS mi - LEFT JOIN create_date AS cd - ON mi.[object_id] = cd.object_id - AND mi.database_id = cd.database_id - AND mi.schema_name = cd.schema_name - /* Minimum benefit threshold = 100k/day of uptime OR since table creation date, whichever is lower*/ - WHERE @ShowAllMissingIndexRequests=1 - OR (mi.magic_benefit_number / CASE WHEN cd.create_days < @DaysUptime THEN cd.create_days ELSE @DaysUptime END) >= 100000 - ORDER BY [Display Order] ASC, [Magic Benefit Number] DESC - OPTION (RECOMPILE);'; - - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputServerName@@@', @OutputServerName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputDatabaseName@@@', @OutputDatabaseName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputSchemaName@@@', @OutputSchemaName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@OutputTableName@@@', @OutputTableName); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@RunID@@@', @RunID); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@GETDATE@@@', GETDATE()); - SET @StringToExecute = REPLACE(@StringToExecute, '@@@LocalServerName@@@', CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128))); - EXEC sp_executesql @StringToExecute, N'@DaysUptime NUMERIC(23,2), @ShowAllMissingIndexRequests BIT', @DaysUptime = @DaysUptime, @ShowAllMissingIndexRequests = @ShowAllMissingIndexRequests; - - END; /* @ValidOutputLocation = 1 */ - ELSE - BEGIN - IF(@OutputType <> 'NONE') - BEGIN - WITH create_date AS ( - SELECT i.database_id, - i.schema_name, - i.[object_id], - ISNULL(NULLIF(MAX(DATEDIFF(DAY, i.create_date, SYSDATETIME())), 0), 1) AS create_days - FROM #IndexSanity AS i - GROUP BY i.database_id, i.schema_name, i.object_id - ) - SELECT - mi.database_name AS [Database Name], - mi.[schema_name] AS [Schema], - mi.table_name AS [Table], - CAST((mi.magic_benefit_number / CASE WHEN cd.create_days < @DaysUptime THEN cd.create_days ELSE @DaysUptime END) AS BIGINT) - AS [Magic Benefit Number], - mi.missing_index_details AS [Missing Index Details], - mi.avg_total_user_cost AS [Avg Query Cost], - mi.avg_user_impact AS [Est Index Improvement], - mi.user_seeks AS [Seeks], - mi.user_scans AS [Scans], - mi.unique_compiles AS [Compiles], - mi.equality_columns_with_data_type AS [Equality Columns], - mi.inequality_columns_with_data_type AS [Inequality Columns], - mi.included_columns_with_data_type AS [Included Columns], - mi.index_estimated_impact AS [Estimated Impact], - mi.create_tsql AS [Create TSQL], - mi.more_info AS [More Info], - 1 AS [Display Order], - mi.is_low, - mi.sample_query_plan AS [Sample Query Plan] - FROM #MissingIndexes AS mi - LEFT JOIN create_date AS cd - ON mi.[object_id] = cd.object_id - AND mi.database_id = cd.database_id - AND mi.schema_name = cd.schema_name - /* Minimum benefit threshold = 100k/day of uptime OR since table creation date, whichever is lower*/ - WHERE @ShowAllMissingIndexRequests=1 - OR (mi.magic_benefit_number / CASE WHEN cd.create_days < @DaysUptime THEN cd.create_days ELSE @DaysUptime END) >= 100000 - UNION ALL - SELECT - @ScriptVersionName, - N'From Your Community Volunteers' , - N'http://FirstResponderKit.org' , - 100000000000, - @DaysUptimeInsertValue, - NULL,NULL,NULL,NULL,NULL,NULL,NULL, - NULL, NULL, NULL, NULL, 0 AS [Display Order], NULL AS is_low, NULL - ORDER BY [Display Order] ASC, [Magic Benefit Number] DESC - OPTION (RECOMPILE); - END; - - - IF (@BringThePain = 1 - AND @DatabaseName IS NOT NULL - AND @GetAllDatabases = 0) - - BEGIN - EXEC sp_BlitzCache @SortOrder = 'sp_BlitzIndex', @DatabaseName = @DatabaseName, @BringThePain = 1, @QueryFilter = 'statement', @HideSummary = 1; - END; - - END; - - - - - - END; /* End @Mode=3 (index detail)*/ - SET @d = CONVERT(VARCHAR(19), GETDATE(), 121); - RAISERROR (N'finishing at %s',0,1, @d) WITH NOWAIT; -END /* End @TableName IS NULL (mode 0/1/2/3/4) */ -END TRY - -BEGIN CATCH - RAISERROR (N'Failure analyzing temp tables.', 0,1) WITH NOWAIT; - - SELECT @msg = ERROR_MESSAGE(), @ErrorSeverity = ERROR_SEVERITY(), @ErrorState = ERROR_STATE(); - - RAISERROR (@msg, - @ErrorSeverity, - @ErrorState - ); - - WHILE @@trancount > 0 - ROLLBACK; - - RETURN; - END CATCH; -GO -IF OBJECT_ID('dbo.sp_BlitzLock') IS NULL -BEGIN - EXEC ('CREATE PROCEDURE dbo.sp_BlitzLock AS RETURN 0;'); -END; -GO - -ALTER PROCEDURE - dbo.sp_BlitzLock -( - @DatabaseName sysname = NULL, - @StartDate datetime = NULL, - @EndDate datetime = NULL, - @ObjectName nvarchar(1024) = NULL, - @StoredProcName nvarchar(1024) = NULL, - @AppName sysname = NULL, - @HostName sysname = NULL, - @LoginName sysname = NULL, - @EventSessionName sysname = N'system_health', - @TargetSessionType sysname = NULL, - @VictimsOnly bit = 0, - @Debug bit = 0, - @Help bit = 0, - @Version varchar(30) = NULL OUTPUT, - @VersionDate datetime = NULL OUTPUT, - @VersionCheckMode bit = 0, - @OutputDatabaseName sysname = NULL, - @OutputSchemaName sysname = N'dbo', /*ditto as below*/ - @OutputTableName sysname = N'BlitzLock', /*put a standard here no need to check later in the script*/ - @ExportToExcel bit = 0 -) -WITH RECOMPILE -AS -BEGIN - SET STATISTICS XML OFF; - SET NOCOUNT ON; - SET XACT_ABORT OFF; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - - SELECT @Version = '8.19', @VersionDate = '20240222'; - - IF @VersionCheckMode = 1 - BEGIN - RETURN; - END; - - IF @Help = 1 - BEGIN - PRINT N' - /* - sp_BlitzLock from http://FirstResponderKit.org - - This script checks for and analyzes deadlocks from the system health session or a custom extended event path - - Variables you can use: - - @DatabaseName: If you want to filter to a specific database - - @StartDate: The date you want to start searching on, defaults to last 7 days - - @EndDate: The date you want to stop searching on, defaults to current date - - @ObjectName: If you want to filter to a specific able. - The object name has to be fully qualified ''Database.Schema.Table'' - - @StoredProcName: If you want to search for a single stored proc - The proc name has to be fully qualified ''Database.Schema.Sproc'' - - @AppName: If you want to filter to a specific application - - @HostName: If you want to filter to a specific host - - @LoginName: If you want to filter to a specific login - - @EventSessionName: If you want to point this at an XE session rather than the system health session. - - @TargetSessionType: Can be ''ring_buffer'' or ''event_file''. Leave NULL to auto-detect. - - @OutputDatabaseName: If you want to output information to a specific database - - @OutputSchemaName: Specify a schema name to output information to a specific Schema - - @OutputTableName: Specify table name to to output information to a specific table - - To learn more, visit http://FirstResponderKit.org where you can download new - versions for free, watch training videos on how it works, get more info on - the findings, contribute your own code, and more. - - Known limitations of this version: - - Only SQL Server 2012 and newer is supported - - If your tables have weird characters in them (https://en.wikipedia.org/wiki/List_of_xml_and_HTML_character_entity_references) you may get errors trying to parse the xml. - I took a long look at this one, and: - 1) Trying to account for all the weird places these could crop up is a losing effort. - 2) Replace is slow af on lots of xml. - - Unknown limitations of this version: - - None. (If we knew them, they would be known. Duh.) - - MIT License - - Copyright (c) Brent Ozar Unlimited - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE. - - */'; - - RETURN; - END; /* @Help = 1 */ - - /*Declare local variables used in the procudure*/ - DECLARE - @DatabaseId int = - DB_ID(@DatabaseName), - @ProductVersion nvarchar(128) = - CAST(SERVERPROPERTY('ProductVersion') AS nvarchar(128)), - @ProductVersionMajor float = - SUBSTRING - ( - CAST(SERVERPROPERTY('ProductVersion') AS nvarchar(128)), - 1, - CHARINDEX('.', CAST(SERVERPROPERTY('ProductVersion') AS nvarchar(128))) + 1 - ), - @ProductVersionMinor int = - PARSENAME - ( - CONVERT - ( - varchar(32), - CAST(SERVERPROPERTY('ProductVersion') AS nvarchar(128)) - ), - 2 - ), - @ObjectFullName nvarchar(MAX) = N'', - @Azure bit = - CASE - WHEN - ( - SELECT - CONVERT - ( - integer, - SERVERPROPERTY('EngineEdition') - ) - ) = 5 - THEN 1 - ELSE 0 - END, - @MI bit = - CASE - WHEN - ( - SELECT - CONVERT - ( - integer, - SERVERPROPERTY('EngineEdition') - ) - ) = 8 - THEN 1 - ELSE 0 - END, - @RDS bit = - CASE - WHEN LEFT(CAST(SERVERPROPERTY('ComputerNamePhysicalNetBIOS') AS varchar(8000)), 8) <> 'EC2AMAZ-' - AND LEFT(CAST(SERVERPROPERTY('MachineName') AS varchar(8000)), 8) <> 'EC2AMAZ-' - AND DB_ID('rdsadmin') IS NULL - THEN 0 - ELSE 1 - END, - @d varchar(40) = '', - @StringToExecute nvarchar(4000) = N'', - @StringToExecuteParams nvarchar(500) = N'', - @r sysname = NULL, - @OutputTableFindings nvarchar(100) = N'[BlitzLockFindings]', - @DeadlockCount int = 0, - @ServerName sysname = @@SERVERNAME, - @OutputDatabaseCheck bit = -1, - @SessionId int = 0, - @TargetSessionId int = 0, - @FileName nvarchar(4000) = N'', - @inputbuf_bom nvarchar(1) = CONVERT(nvarchar(1), 0x0a00, 0), - @deadlock_result nvarchar(MAX) = N'', - @StartDateOriginal datetime = @StartDate, - @EndDateOriginal datetime = @EndDate, - @StartDateUTC datetime, - @EndDateUTC datetime; - - /*Temporary objects used in the procedure*/ - DECLARE - @sysAssObjId AS table - ( - database_id int, - partition_id bigint, - schema_name sysname, - table_name sysname - ); - - CREATE TABLE - #x - ( - x xml NOT NULL - DEFAULT N'x' - ); - - CREATE TABLE - #deadlock_data - ( - deadlock_xml xml NOT NULL - DEFAULT N'x' - ); - - CREATE TABLE - #t - ( - id int NOT NULL - ); - - CREATE TABLE - #deadlock_findings - ( - id int IDENTITY PRIMARY KEY, - check_id int NOT NULL, - database_name nvarchar(256), - object_name nvarchar(1000), - finding_group nvarchar(100), - finding nvarchar(4000), - sort_order bigint - ); - - /*Set these to some sane defaults if NULLs are passed in*/ - /*Normally I'd hate this, but we RECOMPILE everything*/ - - SELECT - @StartDate = - CASE - WHEN @StartDate IS NULL - THEN - DATEADD - ( - MINUTE, - DATEDIFF - ( - MINUTE, - SYSDATETIME(), - GETUTCDATE() - ), - DATEADD - ( - DAY, - -7, - SYSDATETIME() - ) - ) - ELSE - DATEADD - ( - MINUTE, - DATEDIFF - ( - MINUTE, - SYSDATETIME(), - GETUTCDATE() - ), - @StartDate - ) - END, - @EndDate = - CASE - WHEN @EndDate IS NULL - THEN - DATEADD - ( - MINUTE, - DATEDIFF - ( - MINUTE, - SYSDATETIME(), - GETUTCDATE() - ), - SYSDATETIME() - ) - ELSE - DATEADD - ( - MINUTE, - DATEDIFF - ( - MINUTE, - SYSDATETIME(), - GETUTCDATE() - ), - @EndDate - ) - END; - - SELECT - @StartDateUTC = @StartDate, - @EndDateUTC = @EndDate; - - IF - ( - @MI = 1 - AND @EventSessionName = N'system_health' - AND @TargetSessionType IS NULL - ) - BEGIN - SET - @TargetSessionType = N'ring_buffer'; - END; - - IF @Azure = 0 - BEGIN - IF NOT EXISTS - ( - SELECT - 1/0 - FROM sys.server_event_sessions AS ses - JOIN sys.dm_xe_sessions AS dxs - ON dxs.name = ses.name - WHERE ses.name = @EventSessionName - AND dxs.create_time IS NOT NULL - ) - BEGIN - RAISERROR('A session with the name %s does not exist or is not currently active.', 11, 1, @EventSessionName) WITH NOWAIT; - RETURN; - END; - END; - - IF @Azure = 1 - BEGIN - IF NOT EXISTS - ( - SELECT - 1/0 - FROM sys.database_event_sessions AS ses - JOIN sys.dm_xe_database_sessions AS dxs - ON dxs.name = ses.name - WHERE ses.name = @EventSessionName - AND dxs.create_time IS NOT NULL - ) - BEGIN - RAISERROR('A session with the name %s does not exist or is not currently active.', 11, 1, @EventSessionName) WITH NOWAIT; - RETURN; - END; - END; - - IF @OutputDatabaseName IS NOT NULL - BEGIN /*IF databaseName is set, do some sanity checks and put [] around def.*/ - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('@OutputDatabaseName set to %s, checking validity at %s', 0, 1, @OutputDatabaseName, @d) WITH NOWAIT; - - IF NOT EXISTS - ( - SELECT - 1/0 - FROM sys.databases AS d - WHERE d.name = @OutputDatabaseName - ) /*If database is invalid raiserror and set bitcheck*/ - BEGIN - RAISERROR('Database Name (%s) for output of table is invalid please, Output to Table will not be performed', 0, 1, @OutputDatabaseName) WITH NOWAIT; - SET @OutputDatabaseCheck = -1; /* -1 invalid/false, 0 = good/true */ - END; - ELSE - BEGIN - SET @OutputDatabaseCheck = 0; - - SELECT - @StringToExecute = - N'SELECT @r = o.name FROM ' + - @OutputDatabaseName + - N'.sys.objects AS o WHERE o.type_desc = N''USER_TABLE'' AND o.name = ' + - QUOTENAME - ( - @OutputTableName, - N'''' - ) + - N' AND o.schema_id = SCHEMA_ID(' + - QUOTENAME - ( - @OutputSchemaName, - N'''' - ) + - N');', - @StringToExecuteParams = - N'@r sysname OUTPUT'; - - IF @Debug = 1 BEGIN PRINT @StringToExecute; END; - EXEC sys.sp_executesql - @StringToExecute, - @StringToExecuteParams, - @r OUTPUT; - - IF @Debug = 1 - BEGIN - RAISERROR('@r is set to: %s for schema name %s and table name %s', 0, 1, @r, @OutputSchemaName, @OutputTableName) WITH NOWAIT; - END; - - /*protection spells*/ - SELECT - @ObjectFullName = - QUOTENAME(@OutputDatabaseName) + - N'.' + - QUOTENAME(@OutputSchemaName) + - N'.' + - QUOTENAME(@OutputTableName), - @OutputDatabaseName = - QUOTENAME(@OutputDatabaseName), - @OutputTableName = - QUOTENAME(@OutputTableName), - @OutputSchemaName = - QUOTENAME(@OutputSchemaName); - - IF (@r IS NOT NULL) /*if it is not null, there is a table, so check for newly added columns*/ - BEGIN - /* If the table doesn't have the new spid column, add it. See Github #3101. */ - SET @StringToExecute = - N'IF NOT EXISTS (SELECT 1/0 FROM ' + - @OutputDatabaseName + - N'.sys.all_columns AS o WHERE o.object_id = (OBJECT_ID(''' + - @ObjectFullName + - N''')) AND o.name = N''spid'') - /*Add spid column*/ - ALTER TABLE ' + - @ObjectFullName + - N' ADD spid smallint NULL;'; - - IF @Debug = 1 BEGIN PRINT @StringToExecute; END; - EXEC sys.sp_executesql - @StringToExecute; - - /* If the table doesn't have the new wait_resource column, add it. See Github #3101. */ - SET @StringToExecute = - N'IF NOT EXISTS (SELECT 1/0 FROM ' + - @OutputDatabaseName + - N'.sys.all_columns AS o WHERE o.object_id = (OBJECT_ID(''' + - @ObjectFullName + - N''')) AND o.name = N''wait_resource'') - /*Add wait_resource column*/ - ALTER TABLE ' + - @ObjectFullName + - N' ADD wait_resource nvarchar(MAX) NULL;'; - - IF @Debug = 1 BEGIN PRINT @StringToExecute; END; - EXEC sys.sp_executesql - @StringToExecute; - - /* If the table doesn't have the new client option column, add it. See Github #3101. */ - SET @StringToExecute = - N'IF NOT EXISTS (SELECT 1/0 FROM ' + - @OutputDatabaseName + - N'.sys.all_columns AS o WHERE o.object_id = (OBJECT_ID(''' + - @ObjectFullName + - N''')) AND o.name = N''client_option_1'') - /*Add wait_resource column*/ - ALTER TABLE ' + - @ObjectFullName + - N' ADD client_option_1 varchar(500) NULL;'; - - IF @Debug = 1 BEGIN PRINT @StringToExecute; END; - EXEC sys.sp_executesql - @StringToExecute; - - /* If the table doesn't have the new client option column, add it. See Github #3101. */ - SET @StringToExecute = - N'IF NOT EXISTS (SELECT 1/0 FROM ' + - @OutputDatabaseName + - N'.sys.all_columns AS o WHERE o.object_id = (OBJECT_ID(''' + - @ObjectFullName + - N''')) AND o.name = N''client_option_2'') - /*Add wait_resource column*/ - ALTER TABLE ' + - @ObjectFullName + - N' ADD client_option_2 varchar(500) NULL;'; - - IF @Debug = 1 BEGIN PRINT @StringToExecute; END; - EXEC sys.sp_executesql - @StringToExecute; - - /* If the table doesn't have the new lock mode column, add it. See Github #3101. */ - SET @StringToExecute = - N'IF NOT EXISTS (SELECT 1/0 FROM ' + - @OutputDatabaseName + - N'.sys.all_columns AS o WHERE o.object_id = (OBJECT_ID(''' + - @ObjectFullName + - N''')) AND o.name = N''lock_mode'') - /*Add wait_resource column*/ - ALTER TABLE ' + - @ObjectFullName + - N' ADD lock_mode nvarchar(256) NULL;'; - - IF @Debug = 1 BEGIN PRINT @StringToExecute; END; - EXEC sys.sp_executesql - @StringToExecute; - - /* If the table doesn't have the new status column, add it. See Github #3101. */ - SET @StringToExecute = - N'IF NOT EXISTS (SELECT 1/0 FROM ' + - @OutputDatabaseName + - N'.sys.all_columns AS o WHERE o.object_id = (OBJECT_ID(''' + - @ObjectFullName + - N''')) AND o.name = N''status'') - /*Add wait_resource column*/ - ALTER TABLE ' + - @ObjectFullName + - N' ADD status nvarchar(256) NULL;'; - - IF @Debug = 1 BEGIN PRINT @StringToExecute; END; - EXEC sys.sp_executesql - @StringToExecute; - END; - ELSE /* end if @r is not null. if it is null there is no table, create it from above execution */ - BEGIN - SELECT - @StringToExecute = - N'USE ' + - @OutputDatabaseName + - N'; - CREATE TABLE ' + - @OutputSchemaName + - N'.' + - @OutputTableName + - N' ( - ServerName nvarchar(256), - deadlock_type nvarchar(256), - event_date datetime, - database_name nvarchar(256), - spid smallint, - deadlock_group nvarchar(256), - query xml, - object_names xml, - isolation_level nvarchar(256), - owner_mode nvarchar(256), - waiter_mode nvarchar(256), - lock_mode nvarchar(256), - transaction_count bigint, - client_option_1 varchar(500), - client_option_2 varchar(500), - login_name nvarchar(256), - host_name nvarchar(256), - client_app nvarchar(1024), - wait_time bigint, - wait_resource nvarchar(max), - priority smallint, - log_used bigint, - last_tran_started datetime, - last_batch_started datetime, - last_batch_completed datetime, - transaction_name nvarchar(256), - status nvarchar(256), - owner_waiter_type nvarchar(256), - owner_activity nvarchar(256), - owner_waiter_activity nvarchar(256), - owner_merging nvarchar(256), - owner_spilling nvarchar(256), - owner_waiting_to_close nvarchar(256), - waiter_waiter_type nvarchar(256), - waiter_owner_activity nvarchar(256), - waiter_waiter_activity nvarchar(256), - waiter_merging nvarchar(256), - waiter_spilling nvarchar(256), - waiter_waiting_to_close nvarchar(256), - deadlock_graph xml - )'; - - IF @Debug = 1 BEGIN PRINT @StringToExecute; END; - EXEC sys.sp_executesql - @StringToExecute; - - /*table created.*/ - SELECT - @StringToExecute = - N'SELECT @r = o.name FROM ' + - @OutputDatabaseName + - N'.sys.objects AS o - WHERE o.type_desc = N''USER_TABLE'' - AND o.name = N''BlitzLockFindings''', - @StringToExecuteParams = - N'@r sysname OUTPUT'; - - IF @Debug = 1 BEGIN PRINT @StringToExecute; END; - EXEC sys.sp_executesql - @StringToExecute, - @StringToExecuteParams, - @r OUTPUT; - - IF (@r IS NULL) /*if table does not exist*/ - BEGIN - SELECT - @OutputTableFindings = - QUOTENAME(N'BlitzLockFindings'), - @StringToExecute = - N'USE ' + - @OutputDatabaseName + - N'; - CREATE TABLE ' + - @OutputSchemaName + - N'.' + - @OutputTableFindings + - N' ( - ServerName nvarchar(256), - check_id INT, - database_name nvarchar(256), - object_name nvarchar(1000), - finding_group nvarchar(100), - finding nvarchar(4000) - );'; - - IF @Debug = 1 BEGIN PRINT @StringToExecute; END; - EXEC sys.sp_executesql - @StringToExecute; - END; - END; - - /*create synonym for deadlockfindings.*/ - IF EXISTS - ( - SELECT - 1/0 - FROM sys.objects AS o - WHERE o.name = N'DeadlockFindings' - AND o.type_desc = N'SYNONYM' - ) - BEGIN - RAISERROR('Found synonym DeadlockFindings, dropping', 0, 1) WITH NOWAIT; - DROP SYNONYM DeadlockFindings; - END; - - RAISERROR('Creating synonym DeadlockFindings', 0, 1) WITH NOWAIT; - SET @StringToExecute = - N'CREATE SYNONYM DeadlockFindings FOR ' + - @OutputDatabaseName + - N'.' + - @OutputSchemaName + - N'.' + - @OutputTableFindings; - - IF @Debug = 1 BEGIN PRINT @StringToExecute; END; - EXEC sys.sp_executesql - @StringToExecute; - - /*create synonym for deadlock table.*/ - IF EXISTS - ( - SELECT - 1/0 - FROM sys.objects AS o - WHERE o.name = N'DeadLockTbl' - AND o.type_desc = N'SYNONYM' - ) - BEGIN - RAISERROR('Found synonym DeadLockTbl, dropping', 0, 1) WITH NOWAIT; - DROP SYNONYM DeadLockTbl; - END; - - RAISERROR('Creating synonym DeadLockTbl', 0, 1) WITH NOWAIT; - SET @StringToExecute = - N'CREATE SYNONYM DeadLockTbl FOR ' + - @OutputDatabaseName + - N'.' + - @OutputSchemaName + - N'.' + - @OutputTableName; - - IF @Debug = 1 BEGIN PRINT @StringToExecute; END; - EXEC sys.sp_executesql - @StringToExecute; - END; - END; - - /* WITH ROWCOUNT doesn't work on Amazon RDS - see: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2037 */ - IF @RDS = 0 - BEGIN; - BEGIN TRY; - RAISERROR('@RDS = 0, updating #t with high row and page counts', 0, 1) WITH NOWAIT; - UPDATE STATISTICS - #t - WITH - ROWCOUNT = 9223372036854775807, - PAGECOUNT = 9223372036854775807; - END TRY - BEGIN CATCH; - /* Misleading error returned, if run without permissions to update statistics the error returned is "Cannot find object". - Catching specific error, and returning message with better info. If any other error is returned, then throw as normal */ - IF (ERROR_NUMBER() = 1088) - BEGIN; - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Cannot run UPDATE STATISTICS on a #temp table without db_owner or sysadmin permissions', 0, 1) WITH NOWAIT; - END; - ELSE - BEGIN; - THROW; - END; - END CATCH; - END; - - /*If @TargetSessionType, we need to figure out if it's ring buffer or event file*/ - /*Azure has differently named views, so we need to separate. Thanks, Azure.*/ - - IF - ( - @Azure = 0 - AND @TargetSessionType IS NULL - ) - BEGIN - RAISERROR('@TargetSessionType is NULL, assigning for non-Azure instance', 0, 1) WITH NOWAIT; - - SELECT TOP (1) - @TargetSessionType = t.target_name - FROM sys.dm_xe_sessions AS s - JOIN sys.dm_xe_session_targets AS t - ON s.address = t.event_session_address - WHERE s.name = @EventSessionName - AND t.target_name IN (N'event_file', N'ring_buffer') - ORDER BY t.target_name - OPTION(RECOMPILE); - - RAISERROR('@TargetSessionType assigned as %s for non-Azure', 0, 1, @TargetSessionType) WITH NOWAIT; - END; - - IF - ( - @Azure = 1 - AND @TargetSessionType IS NULL - ) - BEGIN - RAISERROR('@TargetSessionType is NULL, assigning for Azure instance', 0, 1) WITH NOWAIT; - - SELECT TOP (1) - @TargetSessionType = t.target_name - FROM sys.dm_xe_database_sessions AS s - JOIN sys.dm_xe_database_session_targets AS t - ON s.address = t.event_session_address - WHERE s.name = @EventSessionName - AND t.target_name IN (N'event_file', N'ring_buffer') - ORDER BY t.target_name - OPTION(RECOMPILE); - - RAISERROR('@TargetSessionType assigned as %s for Azure', 0, 1, @TargetSessionType) WITH NOWAIT; - END; - - - /*The system health stuff gets handled different from user extended events.*/ - /*These next sections deal with user events, dependent on target.*/ - - /*If ring buffers*/ - IF - ( - @TargetSessionType LIKE N'ring%' - AND @EventSessionName NOT LIKE N'system_health%' - ) - BEGIN - IF @Azure = 0 - BEGIN - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('@TargetSessionType is ring_buffer, inserting XML for non-Azure at %s', 0, 1, @d) WITH NOWAIT; - - INSERT - #x WITH(TABLOCKX) - ( - x - ) - SELECT - x = TRY_CAST(t.target_data AS xml) - FROM sys.dm_xe_session_targets AS t - JOIN sys.dm_xe_sessions AS s - ON s.address = t.event_session_address - WHERE s.name = @EventSessionName - AND t.target_name = N'ring_buffer' - OPTION(RECOMPILE); - - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - END; - - IF @Azure = 1 - BEGIN - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('@TargetSessionType is ring_buffer, inserting XML for Azure at %s', 0, 1, @d) WITH NOWAIT; - - INSERT - #x WITH(TABLOCKX) - ( - x - ) - SELECT - x = TRY_CAST(t.target_data AS xml) - FROM sys.dm_xe_database_session_targets AS t - JOIN sys.dm_xe_database_sessions AS s - ON s.address = t.event_session_address - WHERE s.name = @EventSessionName - AND t.target_name = N'ring_buffer' - OPTION(RECOMPILE); - - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - END; - END; - - - /*If event file*/ - IF - ( - @TargetSessionType LIKE N'event%' - AND @EventSessionName NOT LIKE N'system_health%' - ) - BEGIN - IF @Azure = 0 - BEGIN - RAISERROR('@TargetSessionType is event_file, assigning XML for non-Azure', 0, 1) WITH NOWAIT; - - SELECT - @SessionId = t.event_session_id, - @TargetSessionId = t.target_id - FROM sys.server_event_session_targets AS t - JOIN sys.server_event_sessions AS s - ON s.event_session_id = t.event_session_id - WHERE t.name = @TargetSessionType - AND s.name = @EventSessionName - OPTION(RECOMPILE); - - /*We get the file name automatically, here*/ - RAISERROR('Assigning @FileName...', 0, 1) WITH NOWAIT; - SELECT - @FileName = - CASE - WHEN f.file_name LIKE N'%.xel' - THEN REPLACE(f.file_name, N'.xel', N'*.xel') - ELSE f.file_name + N'*.xel' - END - FROM - ( - SELECT - file_name = - CONVERT(nvarchar(4000), f.value) - FROM sys.server_event_session_fields AS f - WHERE f.event_session_id = @SessionId - AND f.object_id = @TargetSessionId - AND f.name = N'filename' - ) AS f - OPTION(RECOMPILE); - END; - - IF @Azure = 1 - BEGIN - RAISERROR('@TargetSessionType is event_file, assigning XML for Azure', 0, 1) WITH NOWAIT; - SELECT - @SessionId = - t.event_session_address, - @TargetSessionId = - t.target_name - FROM sys.dm_xe_database_session_targets t - JOIN sys.dm_xe_database_sessions s - ON s.address = t.event_session_address - WHERE t.target_name = @TargetSessionType - AND s.name = @EventSessionName - OPTION(RECOMPILE); - - /*We get the file name automatically, here*/ - RAISERROR('Assigning @FileName...', 0, 1) WITH NOWAIT; - SELECT - @FileName = - CASE - WHEN f.file_name LIKE N'%.xel' - THEN REPLACE(f.file_name, N'.xel', N'*.xel') - ELSE f.file_name + N'*.xel' - END - FROM - ( - SELECT - file_name = - CONVERT(nvarchar(4000), f.value) - FROM sys.server_event_session_fields AS f - WHERE f.event_session_id = @SessionId - AND f.object_id = @TargetSessionId - AND f.name = N'filename' - ) AS f - OPTION(RECOMPILE); - END; - - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Reading for event_file %s', 0, 1, @FileName) WITH NOWAIT; - - INSERT - #x WITH(TABLOCKX) - ( - x - ) - SELECT - x = TRY_CAST(f.event_data AS xml) - FROM sys.fn_xe_file_target_read_file(@FileName, NULL, NULL, NULL) AS f - LEFT JOIN #t AS t - ON 1 = 1 - OPTION(RECOMPILE); - - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - END; - - /*The XML is parsed differently if it comes from the event file or ring buffer*/ - - /*If ring buffers*/ - IF - ( - @TargetSessionType LIKE N'ring%' - AND @EventSessionName NOT LIKE N'system_health%' - ) - BEGIN - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Inserting to #deadlock_data for ring buffer data', 0, 1) WITH NOWAIT; - - INSERT - #deadlock_data WITH(TABLOCKX) - ( - deadlock_xml - ) - SELECT - deadlock_xml = - e.x.query(N'.') - FROM #x AS x - LEFT JOIN #t AS t - ON 1 = 1 - CROSS APPLY x.x.nodes('/RingBufferTarget/event') AS e(x) - WHERE - ( - e.x.exist('@name[ .= "xml_deadlock_report"]') = 1 - OR e.x.exist('@name[ .= "database_xml_deadlock_report"]') = 1 - OR e.x.exist('@name[ .= "xml_deadlock_report_filtered"]') = 1 - ) - AND e.x.exist('@timestamp[. >= sql:variable("@StartDate") and .< sql:variable("@EndDate")]') = 1 - OPTION(RECOMPILE); - - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - END; - - /*If event file*/ - IF - ( - @TargetSessionType LIKE N'event_file%' - AND @EventSessionName NOT LIKE N'system_health%' - ) - BEGIN - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Inserting to #deadlock_data for event file data', 0, 1) WITH NOWAIT; - - IF @Debug = 1 BEGIN SET STATISTICS XML ON; END; - - INSERT - #deadlock_data WITH(TABLOCKX) - ( - deadlock_xml - ) - SELECT - deadlock_xml = - e.x.query('.') - FROM #x AS x - LEFT JOIN #t AS t - ON 1 = 1 - CROSS APPLY x.x.nodes('/event') AS e(x) - WHERE - ( - e.x.exist('@name[ .= "xml_deadlock_report"]') = 1 - OR e.x.exist('@name[ .= "database_xml_deadlock_report"]') = 1 - OR e.x.exist('@name[ .= "xml_deadlock_report_filtered"]') = 1 - ) - AND e.x.exist('@timestamp[. >= sql:variable("@StartDate") and .< sql:variable("@EndDate")]') = 1 - OPTION(RECOMPILE); - - IF @Debug = 1 BEGIN SET STATISTICS XML OFF; END; - - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - END; - - /*This section deals with event file*/ - IF - ( - @TargetSessionType LIKE N'event%' - AND @EventSessionName LIKE N'system_health%' - ) - BEGIN - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Grab the initial set of xml to parse at %s', 0, 1, @d) WITH NOWAIT; - - IF @Debug = 1 BEGIN SET STATISTICS XML ON; END; - - SELECT - xml.deadlock_xml - INTO #xml - FROM - ( - SELECT - deadlock_xml = - TRY_CAST(fx.event_data AS xml) - FROM sys.fn_xe_file_target_read_file(N'system_health*.xel', NULL, NULL, NULL) AS fx - LEFT JOIN #t AS t - ON 1 = 1 - WHERE fx.object_name = N'xml_deadlock_report' - ) AS xml - CROSS APPLY xml.deadlock_xml.nodes('/event') AS e(x) - WHERE 1 = 1 - AND e.x.exist('@timestamp[. >= sql:variable("@StartDate") and .< sql:variable("@EndDate")]') = 1 - OPTION(RECOMPILE); - - INSERT - #deadlock_data WITH(TABLOCKX) - SELECT - deadlock_xml = - xml.deadlock_xml - FROM #xml AS xml - LEFT JOIN #t AS t - ON 1 = 1 - WHERE xml.deadlock_xml IS NOT NULL - OPTION(RECOMPILE); - - IF @Debug = 1 BEGIN SET STATISTICS XML OFF; END; - - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - END; - - /*Parse process and input buffer xml*/ - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Initial Parse process and input buffer xml %s', 0, 1, @d) WITH NOWAIT; - - SELECT - d1.deadlock_xml, - event_date = d1.deadlock_xml.value('(event/@timestamp)[1]', 'datetime2'), - victim_id = d1.deadlock_xml.value('(//deadlock/victim-list/victimProcess/@id)[1]', 'nvarchar(256)'), - is_parallel = d1.deadlock_xml.exist('//deadlock/resource-list/exchangeEvent'), - is_parallel_batch = d1.deadlock_xml.exist('//deadlock/resource-list/SyncPoint'), - deadlock_graph = d1.deadlock_xml.query('/event/data/value/deadlock') - INTO #dd - FROM #deadlock_data AS d1 - LEFT JOIN #t AS t - ON 1 = 1 - OPTION(RECOMPILE); - - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Final Parse process and input buffer xml %s', 0, 1, @d) WITH NOWAIT; - - SELECT - q.event_date, - q.victim_id, - is_parallel = - CONVERT(bit, q.is_parallel), - q.deadlock_graph, - q.id, - q.spid, - q.database_id, - database_name = - ISNULL - ( - DB_NAME(q.database_id), - N'UNKNOWN' - ), - q.current_database_name, - q.priority, - q.log_used, - q.wait_resource, - q.wait_time, - q.transaction_name, - q.last_tran_started, - q.last_batch_started, - q.last_batch_completed, - q.lock_mode, - q.status, - q.transaction_count, - q.client_app, - q.host_name, - q.login_name, - q.isolation_level, - client_option_1 = - SUBSTRING - ( - CASE WHEN q.clientoption1 & 1 = 1 THEN ', DISABLE_DEF_CNST_CHECK' ELSE '' END + - CASE WHEN q.clientoption1 & 2 = 2 THEN ', IMPLICIT_TRANSACTIONS' ELSE '' END + - CASE WHEN q.clientoption1 & 4 = 4 THEN ', CURSOR_CLOSE_ON_COMMIT' ELSE '' END + - CASE WHEN q.clientoption1 & 8 = 8 THEN ', ANSI_WARNINGS' ELSE '' END + - CASE WHEN q.clientoption1 & 16 = 16 THEN ', ANSI_PADDING' ELSE '' END + - CASE WHEN q.clientoption1 & 32 = 32 THEN ', ANSI_NULLS' ELSE '' END + - CASE WHEN q.clientoption1 & 64 = 64 THEN ', ARITHABORT' ELSE '' END + - CASE WHEN q.clientoption1 & 128 = 128 THEN ', ARITHIGNORE' ELSE '' END + - CASE WHEN q.clientoption1 & 256 = 256 THEN ', QUOTED_IDENTIFIER' ELSE '' END + - CASE WHEN q.clientoption1 & 512 = 512 THEN ', NOCOUNT' ELSE '' END + - CASE WHEN q.clientoption1 & 1024 = 1024 THEN ', ANSI_NULL_DFLT_ON' ELSE '' END + - CASE WHEN q.clientoption1 & 2048 = 2048 THEN ', ANSI_NULL_DFLT_OFF' ELSE '' END + - CASE WHEN q.clientoption1 & 4096 = 4096 THEN ', CONCAT_NULL_YIELDS_NULL' ELSE '' END + - CASE WHEN q.clientoption1 & 8192 = 8192 THEN ', NUMERIC_ROUNDABORT' ELSE '' END + - CASE WHEN q.clientoption1 & 16384 = 16384 THEN ', XACT_ABORT' ELSE '' END, - 3, - 500 - ), - client_option_2 = - SUBSTRING - ( - CASE WHEN q.clientoption2 & 1024 = 1024 THEN ', DB CHAINING' ELSE '' END + - CASE WHEN q.clientoption2 & 2048 = 2048 THEN ', NUMERIC ROUNDABORT' ELSE '' END + - CASE WHEN q.clientoption2 & 4096 = 4096 THEN ', ARITHABORT' ELSE '' END + - CASE WHEN q.clientoption2 & 8192 = 8192 THEN ', ANSI PADDING' ELSE '' END + - CASE WHEN q.clientoption2 & 16384 = 16384 THEN ', ANSI NULL DEFAULT' ELSE '' END + - CASE WHEN q.clientoption2 & 65536 = 65536 THEN ', CONCAT NULL YIELDS NULL' ELSE '' END + - CASE WHEN q.clientoption2 & 131072 = 131072 THEN ', RECURSIVE TRIGGERS' ELSE '' END + - CASE WHEN q.clientoption2 & 1048576 = 1048576 THEN ', DEFAULT TO LOCAL CURSOR' ELSE '' END + - CASE WHEN q.clientoption2 & 8388608 = 8388608 THEN ', QUOTED IDENTIFIER' ELSE '' END + - CASE WHEN q.clientoption2 & 16777216 = 16777216 THEN ', AUTO CREATE STATISTICS' ELSE '' END + - CASE WHEN q.clientoption2 & 33554432 = 33554432 THEN ', CURSOR CLOSE ON COMMIT' ELSE '' END + - CASE WHEN q.clientoption2 & 67108864 = 67108864 THEN ', ANSI NULLS' ELSE '' END + - CASE WHEN q.clientoption2 & 268435456 = 268435456 THEN ', ANSI WARNINGS' ELSE '' END + - CASE WHEN q.clientoption2 & 536870912 = 536870912 THEN ', FULL TEXT ENABLED' ELSE '' END + - CASE WHEN q.clientoption2 & 1073741824 = 1073741824 THEN ', AUTO UPDATE STATISTICS' ELSE '' END + - CASE WHEN q.clientoption2 & 1469283328 = 1469283328 THEN ', ALL SETTABLE OPTIONS' ELSE '' END, - 3, - 500 - ), - q.process_xml - INTO #deadlock_process - FROM - ( - SELECT - dd.deadlock_xml, - event_date = - DATEADD - ( - MINUTE, - DATEDIFF - ( - MINUTE, - GETUTCDATE(), - SYSDATETIME() - ), - dd.event_date - ), - dd.victim_id, - is_parallel = - CONVERT(tinyint, dd.is_parallel) + - CONVERT(tinyint, dd.is_parallel_batch), - dd.deadlock_graph, - id = ca.dp.value('@id', 'nvarchar(256)'), - spid = ca.dp.value('@spid', 'smallint'), - database_id = ca.dp.value('@currentdb', 'bigint'), - current_database_name = ca.dp.value('@currentdbname', 'nvarchar(256)'), - priority = ca.dp.value('@priority', 'smallint'), - log_used = ca.dp.value('@logused', 'bigint'), - wait_resource = ca.dp.value('@waitresource', 'nvarchar(256)'), - wait_time = ca.dp.value('@waittime', 'bigint'), - transaction_name = ca.dp.value('@transactionname', 'nvarchar(256)'), - last_tran_started = ca.dp.value('@lasttranstarted', 'datetime'), - last_batch_started = ca.dp.value('@lastbatchstarted', 'datetime'), - last_batch_completed = ca.dp.value('@lastbatchcompleted', 'datetime'), - lock_mode = ca.dp.value('@lockMode', 'nvarchar(256)'), - status = ca.dp.value('@status', 'nvarchar(256)'), - transaction_count = ca.dp.value('@trancount', 'bigint'), - client_app = ca.dp.value('@clientapp', 'nvarchar(1024)'), - host_name = ca.dp.value('@hostname', 'nvarchar(256)'), - login_name = ca.dp.value('@loginname', 'nvarchar(256)'), - isolation_level = ca.dp.value('@isolationlevel', 'nvarchar(256)'), - clientoption1 = ca.dp.value('@clientoption1', 'bigint'), - clientoption2 = ca.dp.value('@clientoption2', 'bigint'), - process_xml = ISNULL(ca.dp.query(N'.'), N'') - FROM #dd AS dd - CROSS APPLY dd.deadlock_xml.nodes('//deadlock/process-list/process') AS ca(dp) - WHERE (ca.dp.exist('@currentdb[. = sql:variable("@DatabaseId")]') = 1 OR @DatabaseName IS NULL) - AND (ca.dp.exist('@clientapp[. = sql:variable("@AppName")]') = 1 OR @AppName IS NULL) - AND (ca.dp.exist('@hostname[. = sql:variable("@HostName")]') = 1 OR @HostName IS NULL) - AND (ca.dp.exist('@loginname[. = sql:variable("@LoginName")]') = 1 OR @LoginName IS NULL) - ) AS q - LEFT JOIN #t AS t - ON 1 = 1 - OPTION(RECOMPILE); - - - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - /*Parse execution stack xml*/ - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Parse execution stack xml %s', 0, 1, @d) WITH NOWAIT; - - SELECT DISTINCT - dp.id, - dp.event_date, - proc_name = ca.dp.value('@procname', 'nvarchar(1024)'), - sql_handle = ca.dp.value('@sqlhandle', 'nvarchar(131)') - INTO #deadlock_stack - FROM #deadlock_process AS dp - CROSS APPLY dp.process_xml.nodes('//executionStack/frame') AS ca(dp) - WHERE (ca.dp.exist('@procname[. = sql:variable("@StoredProcName")]') = 1 OR @StoredProcName IS NULL) - AND ca.dp.exist('@sqlhandle[ .= "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"]') = 0 - OPTION(RECOMPILE); - - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - /*Grab the full resource list*/ - SET @d = CONVERT(varchar(40), GETDATE(), 109); - - RAISERROR('Grab the full resource list %s', 0, 1, @d) WITH NOWAIT; - - SELECT - event_date = - DATEADD - ( - MINUTE, - DATEDIFF - ( - MINUTE, - GETUTCDATE(), - SYSDATETIME() - ), - dr.event_date - ), - dr.victim_id, - dr.resource_xml - INTO - #deadlock_resource - FROM - ( - SELECT - event_date = dd.deadlock_xml.value('(event/@timestamp)[1]', 'datetime2'), - victim_id = dd.deadlock_xml.value('(//deadlock/victim-list/victimProcess/@id)[1]', 'nvarchar(256)'), - resource_xml = ISNULL(ca.dp.query(N'.'), N'') - FROM #deadlock_data AS dd - CROSS APPLY dd.deadlock_xml.nodes('//deadlock/resource-list') AS ca(dp) - ) AS dr - OPTION(RECOMPILE); - - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - /*Parse object locks*/ - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Parse object locks %s', 0, 1, @d) WITH NOWAIT; - - SELECT DISTINCT - ca.event_date, - ca.database_id, - database_name = - ISNULL - ( - DB_NAME(ca.database_id), - N'UNKNOWN' - ), - object_name = - REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE( - REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE( - REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE( - ca.object_name COLLATE Latin1_General_BIN2, - NCHAR(31), N'?'), NCHAR(30), N'?'), NCHAR(29), N'?'), NCHAR(28), N'?'), NCHAR(27), N'?'), - NCHAR(26), N'?'), NCHAR(25), N'?'), NCHAR(24), N'?'), NCHAR(23), N'?'), NCHAR(22), N'?'), - NCHAR(21), N'?'), NCHAR(20), N'?'), NCHAR(19), N'?'), NCHAR(18), N'?'), NCHAR(17), N'?'), - NCHAR(16), N'?'), NCHAR(15), N'?'), NCHAR(14), N'?'), NCHAR(12), N'?'), NCHAR(11), N'?'), - NCHAR(8), N'?'), NCHAR(7), N'?'), NCHAR(6), N'?'), NCHAR(5), N'?'), NCHAR(4), N'?'), - NCHAR(3), N'?'), NCHAR(2), N'?'), NCHAR(1), N'?'), NCHAR(0), N'?'), - ca.lock_mode, - ca.index_name, - ca.associatedObjectId, - waiter_id = w.l.value('@id', 'nvarchar(256)'), - waiter_mode = w.l.value('@mode', 'nvarchar(256)'), - owner_id = o.l.value('@id', 'nvarchar(256)'), - owner_mode = o.l.value('@mode', 'nvarchar(256)'), - lock_type = CAST(N'OBJECT' AS nvarchar(100)) - INTO #deadlock_owner_waiter - FROM - ( - SELECT - dr.event_date, - database_id = ca.dr.value('@dbid', 'bigint'), - object_name = ca.dr.value('@objectname', 'nvarchar(1024)'), - lock_mode = ca.dr.value('@mode', 'nvarchar(256)'), - index_name = ca.dr.value('@indexname', 'nvarchar(256)'), - associatedObjectId = ca.dr.value('@associatedObjectId', 'bigint'), - dr = ca.dr.query('.') - FROM #deadlock_resource AS dr - CROSS APPLY dr.resource_xml.nodes('//resource-list/objectlock') AS ca(dr) - WHERE (ca.dr.exist('@objectname[. = sql:variable("@ObjectName")]') = 1 OR @ObjectName IS NULL) - ) AS ca - CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l) - CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l) - OPTION(RECOMPILE); - - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - /*Parse page locks*/ - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Parse page locks %s', 0, 1, @d) WITH NOWAIT; - - INSERT - #deadlock_owner_waiter WITH(TABLOCKX) - SELECT DISTINCT - ca.event_date, - ca.database_id, - database_name = - ISNULL - ( - DB_NAME(ca.database_id), - N'UNKNOWN' - ), - ca.object_name, - ca.lock_mode, - ca.index_name, - ca.associatedObjectId, - waiter_id = w.l.value('@id', 'nvarchar(256)'), - waiter_mode = w.l.value('@mode', 'nvarchar(256)'), - owner_id = o.l.value('@id', 'nvarchar(256)'), - owner_mode = o.l.value('@mode', 'nvarchar(256)'), - lock_type = N'PAGE' - FROM - ( - SELECT - dr.event_date, - database_id = ca.dr.value('@dbid', 'bigint'), - object_name = ca.dr.value('@objectname', 'nvarchar(1024)'), - lock_mode = ca.dr.value('@mode', 'nvarchar(256)'), - index_name = ca.dr.value('@indexname', 'nvarchar(256)'), - associatedObjectId = ca.dr.value('@associatedObjectId', 'bigint'), - dr = ca.dr.query('.') - FROM #deadlock_resource AS dr - CROSS APPLY dr.resource_xml.nodes('//resource-list/pagelock') AS ca(dr) - WHERE (ca.dr.exist('@objectname[. = sql:variable("@ObjectName")]') = 1 OR @ObjectName IS NULL) - ) AS ca - CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l) - CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l) - OPTION(RECOMPILE); - - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - /*Parse key locks*/ - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Parse key locks %s', 0, 1, @d) WITH NOWAIT; - - INSERT - #deadlock_owner_waiter WITH(TABLOCKX) - SELECT DISTINCT - ca.event_date, - ca.database_id, - database_name = - ISNULL - ( - DB_NAME(ca.database_id), - N'UNKNOWN' - ), - ca.object_name, - ca.lock_mode, - ca.index_name, - ca.associatedObjectId, - waiter_id = w.l.value('@id', 'nvarchar(256)'), - waiter_mode = w.l.value('@mode', 'nvarchar(256)'), - owner_id = o.l.value('@id', 'nvarchar(256)'), - owner_mode = o.l.value('@mode', 'nvarchar(256)'), - lock_type = N'KEY' - FROM - ( - SELECT - dr.event_date, - database_id = ca.dr.value('@dbid', 'bigint'), - object_name = ca.dr.value('@objectname', 'nvarchar(1024)'), - lock_mode = ca.dr.value('@mode', 'nvarchar(256)'), - index_name = ca.dr.value('@indexname', 'nvarchar(256)'), - associatedObjectId = ca.dr.value('@associatedObjectId', 'bigint'), - dr = ca.dr.query('.') - FROM #deadlock_resource AS dr - CROSS APPLY dr.resource_xml.nodes('//resource-list/keylock') AS ca(dr) - WHERE (ca.dr.exist('@objectname[. = sql:variable("@ObjectName")]') = 1 OR @ObjectName IS NULL) - ) AS ca - CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l) - CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l) - OPTION(RECOMPILE); - - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - /*Parse RID locks*/ - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Parse RID locks %s', 0, 1, @d) WITH NOWAIT; - - INSERT - #deadlock_owner_waiter WITH(TABLOCKX) - SELECT DISTINCT - ca.event_date, - ca.database_id, - database_name = - ISNULL - ( - DB_NAME(ca.database_id), - N'UNKNOWN' - ), - ca.object_name, - ca.lock_mode, - ca.index_name, - ca.associatedObjectId, - waiter_id = w.l.value('@id', 'nvarchar(256)'), - waiter_mode = w.l.value('@mode', 'nvarchar(256)'), - owner_id = o.l.value('@id', 'nvarchar(256)'), - owner_mode = o.l.value('@mode', 'nvarchar(256)'), - lock_type = N'RID' - FROM - ( - SELECT - dr.event_date, - database_id = ca.dr.value('@dbid', 'bigint'), - object_name = ca.dr.value('@objectname', 'nvarchar(1024)'), - lock_mode = ca.dr.value('@mode', 'nvarchar(256)'), - index_name = ca.dr.value('@indexname', 'nvarchar(256)'), - associatedObjectId = ca.dr.value('@associatedObjectId', 'bigint'), - dr = ca.dr.query('.') - FROM #deadlock_resource AS dr - CROSS APPLY dr.resource_xml.nodes('//resource-list/ridlock') AS ca(dr) - WHERE (ca.dr.exist('@objectname[. = sql:variable("@ObjectName")]') = 1 OR @ObjectName IS NULL) - ) AS ca - CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l) - CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l) - OPTION(RECOMPILE); - - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - /*Parse row group locks*/ - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Parse row group locks %s', 0, 1, @d) WITH NOWAIT; - - INSERT - #deadlock_owner_waiter WITH(TABLOCKX) - SELECT DISTINCT - ca.event_date, - ca.database_id, - database_name = - ISNULL - ( - DB_NAME(ca.database_id), - N'UNKNOWN' - ), - ca.object_name, - ca.lock_mode, - ca.index_name, - ca.associatedObjectId, - waiter_id = w.l.value('@id', 'nvarchar(256)'), - waiter_mode = w.l.value('@mode', 'nvarchar(256)'), - owner_id = o.l.value('@id', 'nvarchar(256)'), - owner_mode = o.l.value('@mode', 'nvarchar(256)'), - lock_type = N'ROWGROUP' - FROM - ( - SELECT - dr.event_date, - database_id = ca.dr.value('@dbid', 'bigint'), - object_name = ca.dr.value('@objectname', 'nvarchar(1024)'), - lock_mode = ca.dr.value('@mode', 'nvarchar(256)'), - index_name = ca.dr.value('@indexname', 'nvarchar(256)'), - associatedObjectId = ca.dr.value('@associatedObjectId', 'bigint'), - dr = ca.dr.query('.') - FROM #deadlock_resource AS dr - CROSS APPLY dr.resource_xml.nodes('//resource-list/rowgrouplock') AS ca(dr) - WHERE (ca.dr.exist('@objectname[. = sql:variable("@ObjectName")]') = 1 OR @ObjectName IS NULL) - ) AS ca - CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l) - CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l) - OPTION(RECOMPILE); - - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Fixing heaps in #deadlock_owner_waiter %s', 0, 1, @d) WITH NOWAIT; - - UPDATE - d - SET - d.index_name = - d.object_name + N'.HEAP' - FROM #deadlock_owner_waiter AS d - WHERE d.lock_type IN - ( - N'HEAP', - N'RID' - ) - OPTION(RECOMPILE); - - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - /*Parse parallel deadlocks*/ - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Parse parallel deadlocks %s', 0, 1, @d) WITH NOWAIT; - - SELECT DISTINCT - ca.id, - ca.event_date, - ca.wait_type, - ca.node_id, - ca.waiter_type, - ca.owner_activity, - ca.waiter_activity, - ca.merging, - ca.spilling, - ca.waiting_to_close, - waiter_id = w.l.value('@id', 'nvarchar(256)'), - owner_id = o.l.value('@id', 'nvarchar(256)') - INTO #deadlock_resource_parallel - FROM - ( - SELECT - dr.event_date, - id = ca.dr.value('@id', 'nvarchar(256)'), - wait_type = ca.dr.value('@WaitType', 'nvarchar(256)'), - node_id = ca.dr.value('@nodeId', 'bigint'), - /* These columns are in 2017 CU5+ ONLY */ - waiter_type = ca.dr.value('@waiterType', 'nvarchar(256)'), - owner_activity = ca.dr.value('@ownerActivity', 'nvarchar(256)'), - waiter_activity = ca.dr.value('@waiterActivity', 'nvarchar(256)'), - merging = ca.dr.value('@merging', 'nvarchar(256)'), - spilling = ca.dr.value('@spilling', 'nvarchar(256)'), - waiting_to_close = ca.dr.value('@waitingToClose', 'nvarchar(256)'), - /* These columns are in 2017 CU5+ ONLY */ - dr = ca.dr.query('.') - FROM #deadlock_resource AS dr - CROSS APPLY dr.resource_xml.nodes('//resource-list/exchangeEvent') AS ca(dr) - ) AS ca - CROSS APPLY ca.dr.nodes('//waiter-list/waiter') AS w(l) - CROSS APPLY ca.dr.nodes('//owner-list/owner') AS o(l) - OPTION(RECOMPILE); - - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - /*Get rid of parallel noise*/ - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Get rid of parallel noise %s', 0, 1, @d) WITH NOWAIT; - - WITH - c AS - ( - SELECT - *, - rn = - ROW_NUMBER() OVER - ( - PARTITION BY - drp.owner_id, - drp.waiter_id - ORDER BY - drp.event_date - ) - FROM #deadlock_resource_parallel AS drp - ) - DELETE - FROM c - WHERE c.rn > 1 - OPTION(RECOMPILE); - - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - /*Get rid of nonsense*/ - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Get rid of nonsense %s', 0, 1, @d) WITH NOWAIT; - - DELETE dow - FROM #deadlock_owner_waiter AS dow - WHERE dow.owner_id = dow.waiter_id - OPTION(RECOMPILE); - - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - /*Add some nonsense*/ - ALTER TABLE - #deadlock_process - ADD - waiter_mode nvarchar(256), - owner_mode nvarchar(256), - is_victim AS - CONVERT - ( - bit, - CASE - WHEN id = victim_id - THEN 1 - ELSE 0 - END - ) PERSISTED; - - /*Update some nonsense*/ - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Update some nonsense part 1 %s', 0, 1, @d) WITH NOWAIT; - - UPDATE - dp - SET - dp.owner_mode = dow.owner_mode - FROM #deadlock_process AS dp - JOIN #deadlock_owner_waiter AS dow - ON dp.id = dow.owner_id - AND dp.event_date = dow.event_date - WHERE dp.is_victim = 0 - OPTION(RECOMPILE); - - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Update some nonsense part 2 %s', 0, 1, @d) WITH NOWAIT; - - UPDATE - dp - SET - dp.waiter_mode = dow.waiter_mode - FROM #deadlock_process AS dp - JOIN #deadlock_owner_waiter AS dow - ON dp.victim_id = dow.waiter_id - AND dp.event_date = dow.event_date - WHERE dp.is_victim = 1 - OPTION(RECOMPILE); - - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - /*Get Agent Job and Step names*/ - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Get Agent Job and Step names %s', 0, 1, @d) WITH NOWAIT; - - SELECT - x.event_date, - x.victim_id, - x.id, - x.database_id, - x.client_app, - x.job_id, - x.step_id, - job_id_guid = - CONVERT - ( - uniqueidentifier, - TRY_CAST - ( - N'' - AS xml - ).value('xs:hexBinary(substring(sql:column("x.job_id"), 0))', 'binary(16)') - ) - INTO #agent_job - FROM - ( - SELECT - dp.event_date, - dp.victim_id, - dp.id, - dp.database_id, - dp.client_app, - job_id = - SUBSTRING - ( - dp.client_app, - CHARINDEX(N'0x', dp.client_app) + LEN(N'0x'), - 32 - ), - step_id = - CASE - WHEN CHARINDEX(N': Step ', dp.client_app) > 0 - AND CHARINDEX(N')', dp.client_app, CHARINDEX(N': Step ', dp.client_app)) > 0 - THEN - SUBSTRING - ( - dp.client_app, - CHARINDEX(N': Step ', dp.client_app) + LEN(N': Step '), - CHARINDEX(N')', dp.client_app, CHARINDEX(N': Step ', dp.client_app)) - - (CHARINDEX(N': Step ', dp.client_app) + LEN(N': Step ')) - ) - ELSE dp.client_app - END - FROM #deadlock_process AS dp - WHERE dp.client_app LIKE N'SQLAgent - %' - AND dp.client_app <> N'SQLAgent - Initial Boot Probe' - ) AS x - OPTION(RECOMPILE); - - ALTER TABLE - #agent_job - ADD - job_name nvarchar(256), - step_name nvarchar(256); - - IF - ( - @Azure = 0 - AND @RDS = 0 - ) - BEGIN - SET @StringToExecute = - N' - UPDATE - aj - SET - aj.job_name = j.name, - aj.step_name = s.step_name - FROM msdb.dbo.sysjobs AS j - JOIN msdb.dbo.sysjobsteps AS s - ON j.job_id = s.job_id - JOIN #agent_job AS aj - ON aj.job_id_guid = j.job_id - AND aj.step_id = s.step_id - OPTION(RECOMPILE); - '; - - IF @Debug = 1 BEGIN PRINT @StringToExecute; END; - EXEC sys.sp_executesql - @StringToExecute; - - END; - - UPDATE - dp - SET - dp.client_app = - CASE - WHEN dp.client_app LIKE N'SQLAgent - %' - THEN N'SQLAgent - Job: ' + - aj.job_name + - N' Step: ' + - aj.step_name - ELSE dp.client_app - END - FROM #deadlock_process AS dp - JOIN #agent_job AS aj - ON dp.event_date = aj.event_date - AND dp.victim_id = aj.victim_id - AND dp.id = aj.id - OPTION(RECOMPILE); - - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - /*Get each and every table of all databases*/ - IF @Azure = 0 - BEGIN - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Inserting to @sysAssObjId %s', 0, 1, @d) WITH NOWAIT; - - INSERT INTO - @sysAssObjId - ( - database_id, - partition_id, - schema_name, - table_name - ) - EXECUTE sys.sp_MSforeachdb - N' - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - - USE [?]; - - IF EXISTS - ( - SELECT - 1/0 - FROM #deadlock_process AS dp - WHERE dp.database_id = DB_ID() - ) - BEGIN - SELECT - database_id = - DB_ID(), - p.partition_id, - schema_name = - s.name, - table_name = - t.name - FROM sys.partitions p - JOIN sys.tables t - ON t.object_id = p.object_id - JOIN sys.schemas s - ON s.schema_id = t.schema_id - WHERE s.name IS NOT NULL - AND t.name IS NOT NULL - OPTION(RECOMPILE); - END; - '; - - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - END; - - IF @Azure = 1 - BEGIN - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Inserting to @sysAssObjId at %s', 0, 1, @d) WITH NOWAIT; - - INSERT INTO - @sysAssObjId - ( - database_id, - partition_id, - schema_name, - table_name - ) - SELECT - database_id = - DB_ID(), - p.partition_id, - schema_name = - s.name, - table_name = - t.name - FROM sys.partitions p - JOIN sys.tables t - ON t.object_id = p.object_id - JOIN sys.schemas s - ON s.schema_id = t.schema_id - WHERE s.name IS NOT NULL - AND t.name IS NOT NULL - OPTION(RECOMPILE); - - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - END; - - /*Begin checks based on parsed values*/ - - /* - First, revert these back since we already converted the event data to local time, - and searches will break if we use the times converted over to UTC for the event data - */ - SELECT - @StartDate = @StartDateOriginal, - @EndDate = @EndDateOriginal; - - /*Check 1 is deadlocks by database*/ - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Check 1 database deadlocks %s', 0, 1, @d) WITH NOWAIT; - - INSERT - #deadlock_findings WITH(TABLOCKX) - ( - check_id, - database_name, - object_name, - finding_group, - finding, - sort_order - ) - SELECT - check_id = 1, - dp.database_name, - object_name = N'-', - finding_group = N'Total Database Deadlocks', - finding = - N'This database had ' + - CONVERT - ( - nvarchar(20), - COUNT_BIG(DISTINCT dp.event_date) - ) + - N' deadlocks.', - sort_order = - ROW_NUMBER() - OVER (ORDER BY COUNT_BIG(DISTINCT dp.event_date) DESC) - FROM #deadlock_process AS dp - WHERE 1 = 1 - AND (dp.database_name = @DatabaseName OR @DatabaseName IS NULL) - AND (dp.event_date >= @StartDate OR @StartDate IS NULL) - AND (dp.event_date < @EndDate OR @EndDate IS NULL) - AND (dp.client_app = @AppName OR @AppName IS NULL) - AND (dp.host_name = @HostName OR @HostName IS NULL) - AND (dp.login_name = @LoginName OR @LoginName IS NULL) - GROUP BY dp.database_name - OPTION(RECOMPILE); - - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - /*Check 2 is deadlocks with selects*/ - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Check 2 select deadlocks %s', 0, 1, @d) WITH NOWAIT; - - INSERT - #deadlock_findings WITH(TABLOCKX) - ( - check_id, - database_name, - object_name, - finding_group, - finding, - sort_order - ) - SELECT - check_id = 2, - dow.database_name, - object_name = - CASE - WHEN EXISTS - ( - SELECT - 1/0 - FROM sys.databases AS d - WHERE d.name COLLATE DATABASE_DEFAULT = dow.database_name COLLATE DATABASE_DEFAULT - AND d.is_read_committed_snapshot_on = 1 - ) - THEN N'You already enabled RCSI, but...' - ELSE N'You Might Need RCSI' - END, - finding_group = N'Total Deadlocks Involving Selects', - finding = - N'There have been ' + - CONVERT - ( - nvarchar(20), - COUNT_BIG(DISTINCT dow.event_date) - ) + - N' deadlock(s) between read queries and modification queries.', - sort_order = - ROW_NUMBER() - OVER (ORDER BY COUNT_BIG(DISTINCT dow.event_date) DESC) - FROM #deadlock_owner_waiter AS dow - WHERE 1 = 1 - AND dow.lock_mode IN - ( - N'S', - N'IS' - ) - OR dow.owner_mode IN - ( - N'S', - N'IS' - ) - OR dow.waiter_mode IN - ( - N'S', - N'IS' - ) - AND (dow.database_id = @DatabaseId OR @DatabaseName IS NULL) - AND (dow.event_date >= @StartDate OR @StartDate IS NULL) - AND (dow.event_date < @EndDate OR @EndDate IS NULL) - AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) - GROUP BY - dow.database_name - OPTION(RECOMPILE); - - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - /*Check 3 is deadlocks by object*/ - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Check 3 object deadlocks %s', 0, 1, @d) WITH NOWAIT; - - INSERT - #deadlock_findings WITH(TABLOCKX) - ( - check_id, - database_name, - object_name, - finding_group, - finding, - sort_order - ) - SELECT - check_id = 3, - dow.database_name, - object_name = - ISNULL - ( - dow.object_name, - N'UNKNOWN' - ), - finding_group = N'Total Object Deadlocks', - finding = - N'This object was involved in ' + - CONVERT - ( - nvarchar(20), - COUNT_BIG(DISTINCT dow.event_date) - ) + - N' deadlock(s).', - sort_order = - ROW_NUMBER() - OVER (ORDER BY COUNT_BIG(DISTINCT dow.event_date) DESC) - FROM #deadlock_owner_waiter AS dow - WHERE 1 = 1 - AND (dow.database_id = @DatabaseId OR @DatabaseName IS NULL) - AND (dow.event_date >= @StartDate OR @StartDate IS NULL) - AND (dow.event_date < @EndDate OR @EndDate IS NULL) - AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) - GROUP BY - dow.database_name, - dow.object_name - OPTION(RECOMPILE); - - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - /*Check 3 continuation, number of deadlocks per index*/ - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Check 3 (continued) index deadlocks %s', 0, 1, @d) WITH NOWAIT; - - INSERT - #deadlock_findings WITH(TABLOCKX) - ( - check_id, - database_name, - object_name, - finding_group, - finding, - sort_order - ) - SELECT - check_id = 3, - dow.database_name, - index_name = dow.index_name, - finding_group = N'Total Index Deadlocks', - finding = - N'This index was involved in ' + - CONVERT - ( - nvarchar(20), - COUNT_BIG(DISTINCT dow.event_date) - ) + - N' deadlock(s).', - sort_order = - ROW_NUMBER() - OVER (ORDER BY COUNT_BIG(DISTINCT dow.event_date) DESC) - FROM #deadlock_owner_waiter AS dow - WHERE 1 = 1 - AND (dow.database_id = @DatabaseId OR @DatabaseName IS NULL) - AND (dow.event_date >= @StartDate OR @StartDate IS NULL) - AND (dow.event_date < @EndDate OR @EndDate IS NULL) - AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) - AND dow.lock_type NOT IN - ( - N'HEAP', - N'RID' - ) - AND dow.index_name IS NOT NULL - GROUP BY - dow.database_name, - dow.index_name - OPTION(RECOMPILE); - - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - /*Check 3 continuation, number of deadlocks per heap*/ - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Check 3 (continued) heap deadlocks %s', 0, 1, @d) WITH NOWAIT; - - INSERT - #deadlock_findings WITH(TABLOCKX) - ( - check_id, - database_name, - object_name, - finding_group, - finding, - sort_order - ) - SELECT - check_id = 3, - dow.database_name, - index_name = dow.index_name, - finding_group = N'Total Heap Deadlocks', - finding = - N'This heap was involved in ' + - CONVERT - ( - nvarchar(20), - COUNT_BIG(DISTINCT dow.event_date) - ) + - N' deadlock(s).', - sort_order = - ROW_NUMBER() - OVER (ORDER BY COUNT_BIG(DISTINCT dow.event_date) DESC) - FROM #deadlock_owner_waiter AS dow - WHERE 1 = 1 - AND (dow.database_id = @DatabaseId OR @DatabaseName IS NULL) - AND (dow.event_date >= @StartDate OR @StartDate IS NULL) - AND (dow.event_date < @EndDate OR @EndDate IS NULL) - AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) - AND dow.lock_type IN - ( - N'HEAP', - N'RID' - ) - GROUP BY - dow.database_name, - dow.index_name - OPTION(RECOMPILE); - - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - /*Check 4 looks for Serializable deadlocks*/ - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Check 4 serializable deadlocks %s', 0, 1, @d) WITH NOWAIT; - - INSERT - #deadlock_findings WITH(TABLOCKX) - ( - check_id, - database_name, - object_name, - finding_group, - finding, - sort_order - ) - SELECT - check_id = 4, - database_name = - dp.database_name, - object_name = N'-', - finding_group = N'Serializable Deadlocking', - finding = - N'This database has had ' + - CONVERT - ( - nvarchar(20), - COUNT_BIG(DISTINCT dp.event_date) - ) + - N' instances of Serializable deadlocks.', - sort_order = - ROW_NUMBER() - OVER (ORDER BY COUNT_BIG(DISTINCT dp.event_date) DESC) - FROM #deadlock_process AS dp - WHERE dp.isolation_level LIKE N'serializable%' - AND (dp.database_name = @DatabaseName OR @DatabaseName IS NULL) - AND (dp.event_date >= @StartDate OR @StartDate IS NULL) - AND (dp.event_date < @EndDate OR @EndDate IS NULL) - AND (dp.client_app = @AppName OR @AppName IS NULL) - AND (dp.host_name = @HostName OR @HostName IS NULL) - AND (dp.login_name = @LoginName OR @LoginName IS NULL) - GROUP BY dp.database_name - OPTION(RECOMPILE); - - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - /*Check 5 looks for Repeatable Read deadlocks*/ - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Check 5 repeatable read deadlocks %s', 0, 1, @d) WITH NOWAIT; - - INSERT - #deadlock_findings WITH(TABLOCKX) - ( - check_id, - database_name, - object_name, - finding_group, - finding, - sort_order - ) - SELECT - check_id = 5, - dp.database_name, - object_name = N'-', - finding_group = N'Repeatable Read Deadlocking', - finding = - N'This database has had ' + - CONVERT - ( - nvarchar(20), - COUNT_BIG(DISTINCT dp.event_date) - ) + - N' instances of Repeatable Read deadlocks.', - sort_order = - ROW_NUMBER() - OVER (ORDER BY COUNT_BIG(DISTINCT dp.event_date) DESC) - FROM #deadlock_process AS dp - WHERE dp.isolation_level LIKE N'repeatable%' - AND (dp.database_name = @DatabaseName OR @DatabaseName IS NULL) - AND (dp.event_date >= @StartDate OR @StartDate IS NULL) - AND (dp.event_date < @EndDate OR @EndDate IS NULL) - AND (dp.client_app = @AppName OR @AppName IS NULL) - AND (dp.host_name = @HostName OR @HostName IS NULL) - AND (dp.login_name = @LoginName OR @LoginName IS NULL) - GROUP BY dp.database_name - OPTION(RECOMPILE); - - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - /*Check 6 breaks down app, host, and login information*/ - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Check 6 app/host/login deadlocks %s', 0, 1, @d) WITH NOWAIT; - - INSERT - #deadlock_findings WITH(TABLOCKX) - ( - check_id, - database_name, - object_name, - finding_group, - finding, - sort_order - ) - SELECT - check_id = 6, - database_name = - dp.database_name, - object_name = N'-', - finding_group = N'Login, App, and Host deadlocks', - finding = - N'This database has had ' + - CONVERT - ( - nvarchar(20), - COUNT_BIG(DISTINCT dp.event_date) - ) + - N' instances of deadlocks involving the login ' + - ISNULL - ( - dp.login_name, - N'UNKNOWN' - ) + - N' from the application ' + - ISNULL - ( - dp.client_app, - N'UNKNOWN' - ) + - N' on host ' + - ISNULL - ( - dp.host_name, - N'UNKNOWN' - ) + - N'.', - sort_order = - ROW_NUMBER() - OVER (ORDER BY COUNT_BIG(DISTINCT dp.event_date) DESC) - FROM #deadlock_process AS dp - WHERE 1 = 1 - AND (dp.database_name = @DatabaseName OR @DatabaseName IS NULL) - AND (dp.event_date >= @StartDate OR @StartDate IS NULL) - AND (dp.event_date < @EndDate OR @EndDate IS NULL) - AND (dp.client_app = @AppName OR @AppName IS NULL) - AND (dp.host_name = @HostName OR @HostName IS NULL) - AND (dp.login_name = @LoginName OR @LoginName IS NULL) - GROUP BY - dp.database_name, - dp.login_name, - dp.client_app, - dp.host_name - OPTION(RECOMPILE); - - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - /*Check 7 breaks down the types of deadlocks (object, page, key, etc.)*/ - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Check 7 types of deadlocks %s', 0, 1, @d) WITH NOWAIT; - - WITH - lock_types AS - ( - SELECT - database_name = - dp.database_name, - dow.object_name, - lock = - CASE - WHEN CHARINDEX(N':', dp.wait_resource) > 0 - THEN SUBSTRING - ( - dp.wait_resource, - 1, - CHARINDEX(N':', dp.wait_resource) - 1 - ) - ELSE dp.wait_resource - END, - lock_count = - CONVERT - ( - nvarchar(20), - COUNT_BIG(DISTINCT dp.event_date) - ) - FROM #deadlock_process AS dp - JOIN #deadlock_owner_waiter AS dow - ON (dp.id = dow.owner_id - OR dp.victim_id = dow.waiter_id) - AND dp.event_date = dow.event_date - WHERE 1 = 1 - AND (dp.database_name = @DatabaseName OR @DatabaseName IS NULL) - AND (dp.event_date >= @StartDate OR @StartDate IS NULL) - AND (dp.event_date < @EndDate OR @EndDate IS NULL) - AND (dp.client_app = @AppName OR @AppName IS NULL) - AND (dp.host_name = @HostName OR @HostName IS NULL) - AND (dp.login_name = @LoginName OR @LoginName IS NULL) - AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) - AND dow.object_name IS NOT NULL - GROUP BY - dp.database_name, - CASE - WHEN CHARINDEX(N':', dp.wait_resource) > 0 - THEN SUBSTRING - ( - dp.wait_resource, - 1, - CHARINDEX(N':', dp.wait_resource) - 1 - ) - ELSE dp.wait_resource - END, - dow.object_name - ) - INSERT - #deadlock_findings WITH(TABLOCKX) - ( - check_id, - database_name, - object_name, - finding_group, - finding, - sort_order - ) - SELECT - check_id = 7, - lt.database_name, - lt.object_name, - finding_group = N'Types of locks by object', - finding = - N'This object has had ' + - STUFF - ( - ( - SELECT - N', ' + - lt2.lock_count + - N' ' + - lt2.lock - FROM lock_types AS lt2 - WHERE lt2.database_name = lt.database_name - AND lt2.object_name = lt.object_name - FOR XML - PATH(N''), - TYPE - ).value(N'.[1]', N'nvarchar(MAX)'), - 1, - 1, - N'' - ) + N' locks.', - sort_order = - ROW_NUMBER() - OVER (ORDER BY CONVERT(bigint, lt.lock_count) DESC) - FROM lock_types AS lt - OPTION(RECOMPILE); - - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - /*Check 8 gives you more info queries for sp_BlitzCache & BlitzQueryStore*/ - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Check 8 more info part 1 BlitzCache %s', 0, 1, @d) WITH NOWAIT; - - WITH - deadlock_stack AS - ( - SELECT DISTINCT - ds.id, - ds.event_date, - ds.proc_name, - database_name = - PARSENAME(ds.proc_name, 3), - schema_name = - PARSENAME(ds.proc_name, 2), - proc_only_name = - PARSENAME(ds.proc_name, 1), - sql_handle_csv = - N'''' + - STUFF - ( - ( - SELECT DISTINCT - N',' + - ds2.sql_handle - FROM #deadlock_stack AS ds2 - WHERE ds2.id = ds.id - AND ds2.event_date = ds.event_date - AND ds2.sql_handle <> 0x - FOR XML - PATH(N''), - TYPE - ).value(N'.[1]', N'nvarchar(MAX)'), - 1, - 1, - N'' - ) + - N'''' - FROM #deadlock_stack AS ds - WHERE ds.sql_handle <> 0x - GROUP BY - PARSENAME(ds.proc_name, 3), - PARSENAME(ds.proc_name, 2), - PARSENAME(ds.proc_name, 1), - ds.proc_name, - ds.id, - ds.event_date - ) - INSERT - #deadlock_findings WITH(TABLOCKX) - ( - check_id, - database_name, - object_name, - finding_group, - finding - ) - SELECT DISTINCT - check_id = 8, - dow.database_name, - object_name = ds.proc_name, - finding_group = N'More Info - Query', - finding = N'EXEC sp_BlitzCache ' + - CASE - WHEN ds.proc_name = N'adhoc' - THEN N'@OnlySqlHandles = ' + ds.sql_handle_csv - ELSE N'@StoredProcName = ' + QUOTENAME(ds.proc_only_name, N'''') - END + N';' - FROM deadlock_stack AS ds - JOIN #deadlock_owner_waiter AS dow - ON dow.owner_id = ds.id - AND dow.event_date = ds.event_date - WHERE 1 = 1 - AND (dow.database_id = @DatabaseId OR @DatabaseName IS NULL) - AND (dow.event_date >= @StartDate OR @StartDate IS NULL) - AND (dow.event_date < @EndDate OR @EndDate IS NULL) - AND (dow.object_name = @StoredProcName OR @StoredProcName IS NULL) - AND ds.proc_name NOT LIKE 'Unknown%' - OPTION(RECOMPILE); - - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - IF (@ProductVersionMajor >= 13 OR @Azure = 1) - BEGIN - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Check 8 more info part 2 BlitzQueryStore %s', 0, 1, @d) WITH NOWAIT; - - WITH - deadlock_stack AS - ( - SELECT DISTINCT - ds.id, - ds.sql_handle, - ds.proc_name, - ds.event_date, - database_name = - PARSENAME(ds.proc_name, 3), - schema_name = - PARSENAME(ds.proc_name, 2), - proc_only_name = - PARSENAME(ds.proc_name, 1) - FROM #deadlock_stack AS ds - ) - INSERT - #deadlock_findings WITH(TABLOCKX) - ( - check_id, - database_name, - object_name, - finding_group, - finding - ) - SELECT DISTINCT - check_id = 8, - dow.database_name, - object_name = ds.proc_name, - finding_group = N'More Info - Query', - finding = - N'EXEC sp_BlitzQueryStore ' + - N'@DatabaseName = ' + - QUOTENAME(ds.database_name, N'''') + - N', ' + - N'@StoredProcName = ' + - QUOTENAME(ds.proc_only_name, N'''') + - N';' - FROM deadlock_stack AS ds - JOIN #deadlock_owner_waiter AS dow - ON dow.owner_id = ds.id - AND dow.event_date = ds.event_date - WHERE ds.proc_name <> N'adhoc' - AND (dow.database_id = @DatabaseId OR @DatabaseName IS NULL) - AND (dow.event_date >= @StartDate OR @StartDate IS NULL) - AND (dow.event_date < @EndDate OR @EndDate IS NULL) - AND (dow.object_name = @StoredProcName OR @StoredProcName IS NULL) - OPTION(RECOMPILE); - - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - END; - - /*Check 9 gives you stored procedure deadlock counts*/ - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Check 9 stored procedure deadlocks %s', 0, 1, @d) WITH NOWAIT; - - INSERT - #deadlock_findings WITH(TABLOCKX) - ( - check_id, - database_name, - object_name, - finding_group, - finding, - sort_order - ) - SELECT - check_id = 9, - database_name = - dp.database_name, - object_name = ds.proc_name, - finding_group = N'Stored Procedure Deadlocks', - finding = - N'The stored procedure ' + - PARSENAME(ds.proc_name, 2) + - N'.' + - PARSENAME(ds.proc_name, 1) + - N' has been involved in ' + - CONVERT - ( - nvarchar(10), - COUNT_BIG(DISTINCT ds.id) - ) + - N' deadlocks.', - sort_order = - ROW_NUMBER() - OVER (ORDER BY COUNT_BIG(DISTINCT ds.id) DESC) - FROM #deadlock_stack AS ds - JOIN #deadlock_process AS dp - ON dp.id = ds.id - AND ds.event_date = dp.event_date - WHERE ds.proc_name <> N'adhoc' - AND (ds.proc_name = @StoredProcName OR @StoredProcName IS NULL) - AND (dp.database_name = @DatabaseName OR @DatabaseName IS NULL) - AND (dp.event_date >= @StartDate OR @StartDate IS NULL) - AND (dp.event_date < @EndDate OR @EndDate IS NULL) - AND (dp.client_app = @AppName OR @AppName IS NULL) - AND (dp.host_name = @HostName OR @HostName IS NULL) - AND (dp.login_name = @LoginName OR @LoginName IS NULL) - GROUP BY - dp.database_name, - ds.proc_name - OPTION(RECOMPILE); - - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - /*Check 10 gives you more info queries for sp_BlitzIndex */ - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Check 10 more info, BlitzIndex %s', 0, 1, @d) WITH NOWAIT; - - WITH - bi AS - ( - SELECT DISTINCT - dow.object_name, - dow.database_name, - schema_name = s.schema_name, - table_name = s.table_name - FROM #deadlock_owner_waiter AS dow - JOIN @sysAssObjId AS s - ON s.database_id = dow.database_id - AND s.partition_id = dow.associatedObjectId - WHERE 1 = 1 - AND (dow.database_id = @DatabaseId OR @DatabaseName IS NULL) - AND (dow.event_date >= @StartDate OR @StartDate IS NULL) - AND (dow.event_date < @EndDate OR @EndDate IS NULL) - AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) - AND dow.object_name IS NOT NULL - ) - INSERT - #deadlock_findings WITH(TABLOCKX) - ( - check_id, - database_name, - object_name, - finding_group, - finding - ) - SELECT DISTINCT - check_id = 10, - bi.database_name, - bi.object_name, - finding_group = N'More Info - Table', - finding = - N'EXEC sp_BlitzIndex ' + - N'@DatabaseName = ' + - QUOTENAME(bi.database_name, N'''') + - N', @SchemaName = ' + - QUOTENAME(bi.schema_name, N'''') + - N', @TableName = ' + - QUOTENAME(bi.table_name, N'''') + - N';' - FROM bi - OPTION(RECOMPILE); - - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - /*Check 11 gets total deadlock wait time per object*/ - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Check 11 deadlock wait time per object %s', 0, 1, @d) WITH NOWAIT; - - WITH - chopsuey AS - ( - - SELECT - database_name = - dp.database_name, - dow.object_name, - wait_days = - CONVERT - ( - nvarchar(30), - ( - SUM - ( - CONVERT - ( - bigint, - dp.wait_time - ) - ) / 1000 / 86400 - ) - ), - wait_time_hms = - /*the more wait time you rack up the less accurate this gets, - it's either that or erroring out*/ - CASE - WHEN - SUM - ( - CONVERT - ( - bigint, - dp.wait_time - ) - )/1000 > 2147483647 - THEN - CONVERT - ( - nvarchar(30), - DATEADD - ( - MINUTE, - ( - ( - SUM - ( - CONVERT - ( - bigint, - dp.wait_time - ) - ) - )/ - 60000 - ), - 0 - ), - 14 - ) - WHEN - SUM - ( - CONVERT - ( - bigint, - dp.wait_time - ) - ) BETWEEN 2147483648 AND 2147483647000 - THEN - CONVERT - ( - nvarchar(30), - DATEADD - ( - SECOND, - ( - ( - SUM - ( - CONVERT - ( - bigint, - dp.wait_time - ) - ) - )/ - 1000 - ), - 0 - ), - 14 - ) - ELSE - CONVERT - ( - nvarchar(30), - DATEADD - ( - MILLISECOND, - ( - SUM - ( - CONVERT - ( - bigint, - dp.wait_time - ) - ) - ), - 0 - ), - 14 - ) - END, - total_waits = - SUM(CONVERT(bigint, dp.wait_time)) - FROM #deadlock_owner_waiter AS dow - JOIN #deadlock_process AS dp - ON (dp.id = dow.owner_id - OR dp.victim_id = dow.waiter_id) - AND dp.event_date = dow.event_date - WHERE 1 = 1 - AND (dp.database_name = @DatabaseName OR @DatabaseName IS NULL) - AND (dp.event_date >= @StartDate OR @StartDate IS NULL) - AND (dp.event_date < @EndDate OR @EndDate IS NULL) - AND (dow.object_name = @ObjectName OR @ObjectName IS NULL) - AND (dp.client_app = @AppName OR @AppName IS NULL) - AND (dp.host_name = @HostName OR @HostName IS NULL) - AND (dp.login_name = @LoginName OR @LoginName IS NULL) - GROUP BY - dp.database_name, - dow.object_name - ) - INSERT - #deadlock_findings WITH(TABLOCKX) - ( - check_id, - database_name, - object_name, - finding_group, - finding, - sort_order - ) - SELECT - check_id = 11, - cs.database_name, - cs.object_name, - finding_group = N'Total object deadlock wait time', - finding = - N'This object has had ' + - CONVERT - ( - nvarchar(30), - cs.wait_days - ) + - N' ' + - CONVERT - ( - nvarchar(30), - cs.wait_time_hms, - 14 - ) + - N' [dd hh:mm:ss:ms] of deadlock wait time.', - sort_order = - ROW_NUMBER() - OVER (ORDER BY cs.total_waits DESC) - FROM chopsuey AS cs - WHERE cs.object_name IS NOT NULL - OPTION(RECOMPILE); - - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - /*Check 12 gets total deadlock wait time per database*/ - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Check 12 deadlock wait time per database %s', 0, 1, @d) WITH NOWAIT; - - WITH - wait_time AS - ( - SELECT - database_name = - dp.database_name, - total_wait_time_ms = - SUM - ( - CONVERT - ( - bigint, - dp.wait_time - ) - ) - FROM #deadlock_owner_waiter AS dow - JOIN #deadlock_process AS dp - ON (dp.id = dow.owner_id - OR dp.victim_id = dow.waiter_id) - AND dp.event_date = dow.event_date - WHERE 1 = 1 - AND (dp.database_name = @DatabaseName OR @DatabaseName IS NULL) - AND (dp.event_date >= @StartDate OR @StartDate IS NULL) - AND (dp.event_date < @EndDate OR @EndDate IS NULL) - AND (dp.client_app = @AppName OR @AppName IS NULL) - AND (dp.host_name = @HostName OR @HostName IS NULL) - AND (dp.login_name = @LoginName OR @LoginName IS NULL) - GROUP BY - dp.database_name - ) - INSERT - #deadlock_findings WITH(TABLOCKX) - ( - check_id, - database_name, - object_name, - finding_group, - finding, - sort_order - ) - SELECT - check_id = 12, - wt.database_name, - object_name = N'-', - finding_group = N'Total database deadlock wait time', - N'This database has had ' + - CONVERT - ( - nvarchar(30), - ( - SUM - ( - CONVERT - ( - bigint, - wt.total_wait_time_ms - ) - ) / 1000 / 86400 - ) - ) + - N' ' + - /*the more wait time you rack up the less accurate this gets, - it's either that or erroring out*/ - CASE - WHEN - SUM - ( - CONVERT - ( - bigint, - wt.total_wait_time_ms - ) - )/1000 > 2147483647 - THEN - CONVERT - ( - nvarchar(30), - DATEADD - ( - MINUTE, - ( - ( - SUM - ( - CONVERT - ( - bigint, - wt.total_wait_time_ms - ) - ) - )/ - 60000 - ), - 0 - ), - 14 - ) - WHEN - SUM - ( - CONVERT - ( - bigint, - wt.total_wait_time_ms - ) - ) BETWEEN 2147483648 AND 2147483647000 - THEN - CONVERT - ( - nvarchar(30), - DATEADD - ( - SECOND, - ( - ( - SUM - ( - CONVERT - ( - bigint, - wt.total_wait_time_ms - ) - ) - )/ - 1000 - ), - 0 - ), - 14 - ) - ELSE - CONVERT - ( - nvarchar(30), - DATEADD - ( - MILLISECOND, - ( - SUM - ( - CONVERT - ( - bigint, - wt.total_wait_time_ms - ) - ) - ), - 0 - ), - 14 - ) END + - N' [dd hh:mm:ss:ms] of deadlock wait time.', - sort_order = - ROW_NUMBER() - OVER (ORDER BY SUM(CONVERT(bigint, wt.total_wait_time_ms)) DESC) - FROM wait_time AS wt - GROUP BY - wt.database_name - OPTION(RECOMPILE); - - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - /*Check 13 gets total deadlock wait time for SQL Agent*/ - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Check 13 deadlock count for SQL Agent %s', 0, 1, @d) WITH NOWAIT; - - INSERT - #deadlock_findings WITH(TABLOCKX) - ( - check_id, - database_name, - object_name, - finding_group, - finding, - sort_order - ) - SELECT - check_id = 13, - database_name = - DB_NAME(aj.database_id), - object_name = - N'SQLAgent - Job: ' + - aj.job_name + - N' Step: ' + - aj.step_name, - finding_group = N'Agent Job Deadlocks', - finding = - N'There have been ' + - RTRIM(COUNT_BIG(DISTINCT aj.event_date)) + - N' deadlocks from this Agent Job and Step.', - sort_order = - ROW_NUMBER() - OVER (ORDER BY COUNT_BIG(DISTINCT aj.event_date) DESC) - FROM #agent_job AS aj - GROUP BY - DB_NAME(aj.database_id), - aj.job_name, - aj.step_name - OPTION(RECOMPILE); - - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - /*Check 14 is total parallel deadlocks*/ - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Check 14 parallel deadlocks %s', 0, 1, @d) WITH NOWAIT; - - INSERT - #deadlock_findings WITH(TABLOCKX) - ( - check_id, - database_name, - object_name, - finding_group, - finding - ) - SELECT - check_id = 14, - database_name = N'-', - object_name = N'-', - finding_group = N'Total parallel deadlocks', - finding = - N'There have been ' + - CONVERT - ( - nvarchar(20), - COUNT_BIG(DISTINCT drp.event_date) - ) + - N' parallel deadlocks.' - FROM #deadlock_resource_parallel AS drp - HAVING COUNT_BIG(DISTINCT drp.event_date) > 0 - OPTION(RECOMPILE); - - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - /*Check 15 is total deadlocks involving sleeping sessions*/ - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Check 15 sleeping and background deadlocks %s', 0, 1, @d) WITH NOWAIT; - - INSERT - #deadlock_findings WITH(TABLOCKX) - ( - check_id, - database_name, - object_name, - finding_group, - finding - ) - SELECT - check_id = 15, - database_name = N'-', - object_name = N'-', - finding_group = N'Total deadlocks involving sleeping sessions', - finding = - N'There have been ' + - CONVERT - ( - nvarchar(20), - COUNT_BIG(DISTINCT dp.event_date) - ) + - N' sleepy deadlocks.' - FROM #deadlock_process AS dp - WHERE dp.status = N'sleeping' - HAVING COUNT_BIG(DISTINCT dp.event_date) > 0 - OPTION(RECOMPILE); - - INSERT - #deadlock_findings WITH(TABLOCKX) - ( - check_id, - database_name, - object_name, - finding_group, - finding - ) - SELECT - check_id = 15, - database_name = N'-', - object_name = N'-', - finding_group = N'Total deadlocks involving background processes', - finding = - N'There have been ' + - CONVERT - ( - nvarchar(20), - COUNT_BIG(DISTINCT dp.event_date) - ) + - N' deadlocks with background task.' - FROM #deadlock_process AS dp - WHERE dp.status = N'background' - HAVING COUNT_BIG(DISTINCT dp.event_date) > 0 - OPTION(RECOMPILE); - - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - /*Check 16 is total deadlocks involving implicit transactions*/ - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Check 16 implicit transaction deadlocks %s', 0, 1, @d) WITH NOWAIT; - - INSERT - #deadlock_findings WITH(TABLOCKX) - ( - check_id, - database_name, - object_name, - finding_group, - finding - ) - SELECT - check_id = 14, - database_name = N'-', - object_name = N'-', - finding_group = N'Total implicit transaction deadlocks', - finding = - N'There have been ' + - CONVERT - ( - nvarchar(20), - COUNT_BIG(DISTINCT dp.event_date) - ) + - N' implicit transaction deadlocks.' - FROM #deadlock_process AS dp - WHERE dp.transaction_name = N'implicit_transaction' - HAVING COUNT_BIG(DISTINCT dp.event_date) > 0 - OPTION(RECOMPILE); - - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - /*Thank you goodnight*/ - INSERT - #deadlock_findings WITH(TABLOCKX) - ( - check_id, - database_name, - object_name, - finding_group, - finding - ) - VALUES - ( - -1, - N'sp_BlitzLock version ' + CONVERT(nvarchar(10), @Version), - N'Results for ' + CONVERT(nvarchar(10), @StartDate, 23) + N' through ' + CONVERT(nvarchar(10), @EndDate, 23), - N'http://FirstResponderKit.org/', - N'To get help or add your own contributions to the SQL Server First Responder Kit, join us at http://FirstResponderKit.org.' - ); - - RAISERROR('Finished rollup at %s', 0, 1, @d) WITH NOWAIT; - - /*Results*/ - BEGIN - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Results 1 %s', 0, 1, @d) WITH NOWAIT; - - CREATE CLUSTERED INDEX cx_whatever_dp ON #deadlock_process (event_date, id); - CREATE CLUSTERED INDEX cx_whatever_drp ON #deadlock_resource_parallel (event_date, owner_id); - CREATE CLUSTERED INDEX cx_whatever_dow ON #deadlock_owner_waiter (event_date, owner_id, waiter_id); - - IF @Debug = 1 BEGIN SET STATISTICS XML ON; END; - - WITH - deadlocks AS - ( - SELECT - deadlock_type = - N'Regular Deadlock', - dp.event_date, - dp.id, - dp.victim_id, - dp.spid, - dp.database_id, - dp.database_name, - dp.current_database_name, - dp.priority, - dp.log_used, - wait_resource = - dp.wait_resource COLLATE DATABASE_DEFAULT, - object_names = - CONVERT - ( - xml, - STUFF - ( - ( - SELECT DISTINCT - object_name = - NCHAR(10) + - N' ' + - ISNULL(c.object_name, N'') + - N' ' COLLATE DATABASE_DEFAULT - FROM #deadlock_owner_waiter AS c - WHERE (dp.id = c.owner_id - OR dp.victim_id = c.waiter_id) - AND dp.event_date = c.event_date - FOR XML - PATH(N''), - TYPE - ).value(N'.[1]', N'nvarchar(4000)'), - 1, - 1, - N'' - ) - ), - dp.wait_time, - dp.transaction_name, - dp.status, - dp.last_tran_started, - dp.last_batch_started, - dp.last_batch_completed, - dp.lock_mode, - dp.transaction_count, - dp.client_app, - dp.host_name, - dp.login_name, - dp.isolation_level, - dp.client_option_1, - dp.client_option_2, - inputbuf = - dp.process_xml.value('(//process/inputbuf/text())[1]', 'nvarchar(MAX)'), - en = - DENSE_RANK() OVER (ORDER BY dp.event_date), - qn = - ROW_NUMBER() OVER (PARTITION BY dp.event_date ORDER BY dp.event_date) - 1, - dn = - ROW_NUMBER() OVER (PARTITION BY dp.event_date, dp.id ORDER BY dp.event_date), - dp.is_victim, - owner_mode = - ISNULL(dp.owner_mode, N'-'), - owner_waiter_type = NULL, - owner_activity = NULL, - owner_waiter_activity = NULL, - owner_merging = NULL, - owner_spilling = NULL, - owner_waiting_to_close = NULL, - waiter_mode = - ISNULL(dp.waiter_mode, N'-'), - waiter_waiter_type = NULL, - waiter_owner_activity = NULL, - waiter_waiter_activity = NULL, - waiter_merging = NULL, - waiter_spilling = NULL, - waiter_waiting_to_close = NULL, - dp.deadlock_graph - FROM #deadlock_process AS dp - WHERE dp.victim_id IS NOT NULL - AND dp.is_parallel = 0 - - UNION ALL - - SELECT - deadlock_type = - N'Parallel Deadlock', - dp.event_date, - dp.id, - dp.victim_id, - dp.spid, - dp.database_id, - dp.database_name, - dp.current_database_name, - dp.priority, - dp.log_used, - dp.wait_resource COLLATE DATABASE_DEFAULT, - object_names = - CONVERT - ( - xml, - STUFF - ( - ( - SELECT DISTINCT - object_name = - NCHAR(10) + - N' ' + - ISNULL(c.object_name, N'') + - N' ' COLLATE DATABASE_DEFAULT - FROM #deadlock_owner_waiter AS c - WHERE (dp.id = c.owner_id - OR dp.victim_id = c.waiter_id) - AND dp.event_date = c.event_date - FOR XML - PATH(N''), - TYPE - ).value(N'.[1]', N'nvarchar(4000)'), - 1, - 1, - N'' - ) - ), - dp.wait_time, - dp.transaction_name, - dp.status, - dp.last_tran_started, - dp.last_batch_started, - dp.last_batch_completed, - dp.lock_mode, - dp.transaction_count, - dp.client_app, - dp.host_name, - dp.login_name, - dp.isolation_level, - dp.client_option_1, - dp.client_option_2, - inputbuf = - dp.process_xml.value('(//process/inputbuf/text())[1]', 'nvarchar(MAX)'), - en = - DENSE_RANK() OVER (ORDER BY dp.event_date), - qn = - ROW_NUMBER() OVER (PARTITION BY dp.event_date ORDER BY dp.event_date) - 1, - dn = - ROW_NUMBER() OVER (PARTITION BY dp.event_date, dp.id ORDER BY dp.event_date), - is_victim = 1, - owner_mode = cao.wait_type COLLATE DATABASE_DEFAULT, - owner_waiter_type = cao.waiter_type, - owner_activity = cao.owner_activity, - owner_waiter_activity = cao.waiter_activity, - owner_merging = cao.merging, - owner_spilling = cao.spilling, - owner_waiting_to_close = cao.waiting_to_close, - waiter_mode = caw.wait_type COLLATE DATABASE_DEFAULT, - waiter_waiter_type = caw.waiter_type, - waiter_owner_activity = caw.owner_activity, - waiter_waiter_activity = caw.waiter_activity, - waiter_merging = caw.merging, - waiter_spilling = caw.spilling, - waiter_waiting_to_close = caw.waiting_to_close, - dp.deadlock_graph - FROM #deadlock_process AS dp - OUTER APPLY - ( - SELECT TOP (1) - drp.* - FROM #deadlock_resource_parallel AS drp - WHERE drp.owner_id = dp.id - AND drp.wait_type IN - ( - N'e_waitPortOpen', - N'e_waitPipeNewRow' - ) - ORDER BY drp.event_date - ) AS cao - OUTER APPLY - ( - SELECT TOP (1) - drp.* - FROM #deadlock_resource_parallel AS drp - WHERE drp.owner_id = dp.id - AND drp.wait_type IN - ( - N'e_waitPortOpen', - N'e_waitPipeGetRow' - ) - ORDER BY drp.event_date - ) AS caw - WHERE dp.is_parallel = 1 - ) - SELECT - d.deadlock_type, - d.event_date, - d.id, - d.victim_id, - d.spid, - deadlock_group = - N'Deadlock #' + - CONVERT - ( - nvarchar(10), - d.en - ) + - N', Query #' - + CASE - WHEN d.qn = 0 - THEN N'1' - ELSE CONVERT(nvarchar(10), d.qn) - END + CASE - WHEN d.is_victim = 1 - THEN N' - VICTIM' - ELSE N'' - END, - d.database_id, - d.database_name, - d.current_database_name, - d.priority, - d.log_used, - d.wait_resource, - d.object_names, - d.wait_time, - d.transaction_name, - d.status, - d.last_tran_started, - d.last_batch_started, - d.last_batch_completed, - d.lock_mode, - d.transaction_count, - d.client_app, - d.host_name, - d.login_name, - d.isolation_level, - d.client_option_1, - d.client_option_2, - inputbuf = - CASE - WHEN d.inputbuf - LIKE @inputbuf_bom + N'Proc |[Database Id = %' ESCAPE N'|' - THEN - OBJECT_SCHEMA_NAME - ( - SUBSTRING - ( - d.inputbuf, - CHARINDEX(N'Object Id = ', d.inputbuf) + 12, - LEN(d.inputbuf) - (CHARINDEX(N'Object Id = ', d.inputbuf) + 12) - ) - , - SUBSTRING - ( - d.inputbuf, - CHARINDEX(N'Database Id = ', d.inputbuf) + 14, - CHARINDEX(N'Object Id', d.inputbuf) - (CHARINDEX(N'Database Id = ', d.inputbuf) + 14) - ) - ) + - N'.' + - OBJECT_NAME - ( - SUBSTRING - ( - d.inputbuf, - CHARINDEX(N'Object Id = ', d.inputbuf) + 12, - LEN(d.inputbuf) - (CHARINDEX(N'Object Id = ', d.inputbuf) + 12) - ) - , - SUBSTRING - ( - d.inputbuf, - CHARINDEX(N'Database Id = ', d.inputbuf) + 14, - CHARINDEX(N'Object Id', d.inputbuf) - (CHARINDEX(N'Database Id = ', d.inputbuf) + 14) - ) - ) - ELSE d.inputbuf - END COLLATE Latin1_General_BIN2, - d.owner_mode, - d.owner_waiter_type, - d.owner_activity, - d.owner_waiter_activity, - d.owner_merging, - d.owner_spilling, - d.owner_waiting_to_close, - d.waiter_mode, - d.waiter_waiter_type, - d.waiter_owner_activity, - d.waiter_waiter_activity, - d.waiter_merging, - d.waiter_spilling, - d.waiter_waiting_to_close, - d.deadlock_graph, - d.is_victim - INTO #deadlocks - FROM deadlocks AS d - WHERE d.dn = 1 - AND (d.is_victim = @VictimsOnly - OR @VictimsOnly = 0) - AND d.qn < CASE - WHEN d.deadlock_type = N'Parallel Deadlock' - THEN 2 - ELSE 2147483647 - END - AND (DB_NAME(d.database_id) = @DatabaseName OR @DatabaseName IS NULL) - AND (d.event_date >= @StartDate OR @StartDate IS NULL) - AND (d.event_date < @EndDate OR @EndDate IS NULL) - AND (CONVERT(nvarchar(MAX), d.object_names) COLLATE Latin1_General_BIN2 LIKE N'%' + @ObjectName + N'%' OR @ObjectName IS NULL) - AND (d.client_app = @AppName OR @AppName IS NULL) - AND (d.host_name = @HostName OR @HostName IS NULL) - AND (d.login_name = @LoginName OR @LoginName IS NULL) - OPTION (RECOMPILE, LOOP JOIN, HASH JOIN); - - UPDATE d - SET d.inputbuf = - REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE( - REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE( - REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE( - d.inputbuf, - NCHAR(31), N'?'), NCHAR(30), N'?'), NCHAR(29), N'?'), NCHAR(28), N'?'), NCHAR(27), N'?'), - NCHAR(26), N'?'), NCHAR(25), N'?'), NCHAR(24), N'?'), NCHAR(23), N'?'), NCHAR(22), N'?'), - NCHAR(21), N'?'), NCHAR(20), N'?'), NCHAR(19), N'?'), NCHAR(18), N'?'), NCHAR(17), N'?'), - NCHAR(16), N'?'), NCHAR(15), N'?'), NCHAR(14), N'?'), NCHAR(12), N'?'), NCHAR(11), N'?'), - NCHAR(8), N'?'), NCHAR(7), N'?'), NCHAR(6), N'?'), NCHAR(5), N'?'), NCHAR(4), N'?'), - NCHAR(3), N'?'), NCHAR(2), N'?'), NCHAR(1), N'?'), NCHAR(0), N'?') - FROM #deadlocks AS d - OPTION(RECOMPILE); - - SELECT - d.deadlock_type, - d.event_date, - database_name = - DB_NAME(d.database_id), - database_name_x = - d.database_name, - d.current_database_name, - d.spid, - d.deadlock_group, - d.client_option_1, - d.client_option_2, - d.lock_mode, - query_xml = - ( - SELECT - [processing-instruction(query)] = - d.inputbuf - FOR XML - PATH(N''), - TYPE - ), - query_string = - d.inputbuf, - d.object_names, - d.isolation_level, - d.owner_mode, - d.waiter_mode, - d.transaction_count, - d.login_name, - d.host_name, - d.client_app, - d.wait_time, - d.wait_resource, - d.priority, - d.log_used, - d.last_tran_started, - d.last_batch_started, - d.last_batch_completed, - d.transaction_name, - d.status, - /*These columns will be NULL for regular (non-parallel) deadlocks*/ - parallel_deadlock_details = - ( - SELECT - d.owner_waiter_type, - d.owner_activity, - d.owner_waiter_activity, - d.owner_merging, - d.owner_spilling, - d.owner_waiting_to_close, - d.waiter_waiter_type, - d.waiter_owner_activity, - d.waiter_waiter_activity, - d.waiter_merging, - d.waiter_spilling, - d.waiter_waiting_to_close - FOR XML - PATH('parallel_deadlock_details'), - TYPE - ), - d.owner_waiter_type, - d.owner_activity, - d.owner_waiter_activity, - d.owner_merging, - d.owner_spilling, - d.owner_waiting_to_close, - d.waiter_waiter_type, - d.waiter_owner_activity, - d.waiter_waiter_activity, - d.waiter_merging, - d.waiter_spilling, - d.waiter_waiting_to_close, - /*end parallel deadlock columns*/ - d.deadlock_graph, - d.is_victim, - d.id - INTO #deadlock_results - FROM #deadlocks AS d; - - IF @Debug = 1 BEGIN SET STATISTICS XML OFF; END; - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - /*There's too much risk of errors sending the*/ - IF @OutputDatabaseCheck = 0 - BEGIN - SET @ExportToExcel = 0; - END; - - SET @deadlock_result += N' - SELECT - server_name = - @@SERVERNAME, - dr.deadlock_type, - dr.event_date, - database_name = - COALESCE - ( - dr.database_name, - dr.database_name_x, - dr.current_database_name - ), - dr.spid, - dr.deadlock_group, - ' + CASE @ExportToExcel - WHEN 1 - THEN N' - query = dr.query_string, - object_names = - REPLACE( - REPLACE( - CONVERT - ( - nvarchar(MAX), - dr.object_names - ) COLLATE Latin1_General_BIN2, - '''', ''''), - '''', ''''),' - ELSE N'query = dr.query_xml, - dr.object_names,' - END + N' - dr.isolation_level, - dr.owner_mode, - dr.waiter_mode, - dr.lock_mode, - dr.transaction_count, - dr.client_option_1, - dr.client_option_2, - dr.login_name, - dr.host_name, - dr.client_app, - dr.wait_time, - dr.wait_resource, - dr.priority, - dr.log_used, - dr.last_tran_started, - dr.last_batch_started, - dr.last_batch_completed, - dr.transaction_name, - dr.status,' + - CASE - WHEN (@ExportToExcel = 1 - OR @OutputDatabaseCheck = 0) - THEN N' - dr.owner_waiter_type, - dr.owner_activity, - dr.owner_waiter_activity, - dr.owner_merging, - dr.owner_spilling, - dr.owner_waiting_to_close, - dr.waiter_waiter_type, - dr.waiter_owner_activity, - dr.waiter_waiter_activity, - dr.waiter_merging, - dr.waiter_spilling, - dr.waiter_waiting_to_close,' - ELSE N' - dr.parallel_deadlock_details,' - END + - CASE - @ExportToExcel - WHEN 1 - THEN N' - deadlock_graph = - REPLACE(REPLACE( - REPLACE(REPLACE( - CONVERT - ( - nvarchar(MAX), - dr.deadlock_graph - ) COLLATE Latin1_General_BIN2, - ''NCHAR(10)'', ''''), ''NCHAR(13)'', ''''), - ''CHAR(10)'', ''''), ''CHAR(13)'', '''')' - ELSE N' - dr.deadlock_graph' - END + N' - FROM #deadlock_results AS dr - ORDER BY - dr.event_date, - dr.is_victim DESC - OPTION(RECOMPILE, LOOP JOIN, HASH JOIN); - '; - - IF (@OutputDatabaseCheck = 0) - BEGIN - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Results to table %s', 0, 1, @d) WITH NOWAIT; - - IF @Debug = 1 - BEGIN - PRINT @deadlock_result; - SET STATISTICS XML ON; - END; - - INSERT INTO - DeadLockTbl - ( - ServerName, - deadlock_type, - event_date, - database_name, - spid, - deadlock_group, - query, - object_names, - isolation_level, - owner_mode, - waiter_mode, - lock_mode, - transaction_count, - client_option_1, - client_option_2, - login_name, - host_name, - client_app, - wait_time, - wait_resource, - priority, - log_used, - last_tran_started, - last_batch_started, - last_batch_completed, - transaction_name, - status, - owner_waiter_type, - owner_activity, - owner_waiter_activity, - owner_merging, - owner_spilling, - owner_waiting_to_close, - waiter_waiter_type, - waiter_owner_activity, - waiter_waiter_activity, - waiter_merging, - waiter_spilling, - waiter_waiting_to_close, - deadlock_graph - ) - EXEC sys.sp_executesql - @deadlock_result; - - IF @Debug = 1 - BEGIN - SET STATISTICS XML OFF; - END; - - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - DROP SYNONYM DeadLockTbl; - - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Findings to table %s', 0, 1, @d) WITH NOWAIT; - - INSERT INTO - DeadlockFindings - ( - ServerName, - check_id, - database_name, - object_name, - finding_group, - finding - ) - SELECT - @@SERVERNAME, - df.check_id, - df.database_name, - df.object_name, - df.finding_group, - df.finding - FROM #deadlock_findings AS df - ORDER BY df.check_id - OPTION(RECOMPILE); - - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - DROP SYNONYM DeadlockFindings; /*done with inserting.*/ - END; - ELSE /*Output to database is not set output to client app*/ - BEGIN - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Results to client %s', 0, 1, @d) WITH NOWAIT; - - IF @Debug = 1 BEGIN SET STATISTICS XML ON; END; - - EXEC sys.sp_executesql - @deadlock_result; - - IF @Debug = 1 - BEGIN - SET STATISTICS XML OFF; - PRINT @deadlock_result; - END; - - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Getting available execution plans for deadlocks %s', 0, 1, @d) WITH NOWAIT; - - SELECT DISTINCT - available_plans = - 'available_plans', - ds.proc_name, - sql_handle = - CONVERT(varbinary(64), ds.sql_handle, 1), - dow.database_name, - dow.database_id, - dow.object_name, - query_xml = - TRY_CAST(dr.query_xml AS nvarchar(MAX)) - INTO #available_plans - FROM #deadlock_stack AS ds - JOIN #deadlock_owner_waiter AS dow - ON dow.owner_id = ds.id - AND dow.event_date = ds.event_date - JOIN #deadlock_results AS dr - ON dr.id = ds.id - AND dr.event_date = ds.event_date - OPTION(RECOMPILE); - - SELECT - deqs.sql_handle, - deqs.plan_handle, - deqs.statement_start_offset, - deqs.statement_end_offset, - deqs.creation_time, - deqs.last_execution_time, - deqs.execution_count, - total_worker_time_ms = - deqs.total_worker_time / 1000., - avg_worker_time_ms = - CONVERT(decimal(38, 6), deqs.total_worker_time / 1000. / deqs.execution_count), - total_elapsed_time_ms = - deqs.total_elapsed_time / 1000., - avg_elapsed_time_ms = - CONVERT(decimal(38, 6), deqs.total_elapsed_time / 1000. / deqs.execution_count), - executions_per_second = - ISNULL - ( - deqs.execution_count / - NULLIF - ( - DATEDIFF - ( - SECOND, - deqs.creation_time, - NULLIF(deqs.last_execution_time, '1900-01-01 00:00:00.000') - ), - 0 - ), - 0 - ), - total_physical_reads_mb = - deqs.total_physical_reads * 8. / 1024., - total_logical_writes_mb = - deqs.total_logical_writes * 8. / 1024., - total_logical_reads_mb = - deqs.total_logical_reads * 8. / 1024., - min_grant_mb = - deqs.min_grant_kb * 8. / 1024., - max_grant_mb = - deqs.max_grant_kb * 8. / 1024., - min_used_grant_mb = - deqs.min_used_grant_kb * 8. / 1024., - max_used_grant_mb = - deqs.max_used_grant_kb * 8. / 1024., - deqs.min_reserved_threads, - deqs.max_reserved_threads, - deqs.min_used_threads, - deqs.max_used_threads, - deqs.total_rows - INTO #dm_exec_query_stats - FROM sys.dm_exec_query_stats AS deqs - WHERE EXISTS - ( - SELECT - 1/0 - FROM #available_plans AS ap - WHERE ap.sql_handle = deqs.sql_handle - ) - AND deqs.query_hash IS NOT NULL; - - CREATE CLUSTERED INDEX - deqs - ON #dm_exec_query_stats - ( - sql_handle, - plan_handle - ); - - SELECT - ap.available_plans, - ap.database_name, - query_text = - TRY_CAST(ap.query_xml AS xml), - ap.query_plan, - ap.creation_time, - ap.last_execution_time, - ap.execution_count, - ap.executions_per_second, - ap.total_worker_time_ms, - ap.avg_worker_time_ms, - ap.total_elapsed_time_ms, - ap.avg_elapsed_time_ms, - ap.total_logical_reads_mb, - ap.total_physical_reads_mb, - ap.total_logical_writes_mb, - ap.min_grant_mb, - ap.max_grant_mb, - ap.min_used_grant_mb, - ap.max_used_grant_mb, - ap.min_reserved_threads, - ap.max_reserved_threads, - ap.min_used_threads, - ap.max_used_threads, - ap.total_rows, - ap.sql_handle, - ap.statement_start_offset, - ap.statement_end_offset - FROM - ( - - SELECT - ap.*, - c.statement_start_offset, - c.statement_end_offset, - c.creation_time, - c.last_execution_time, - c.execution_count, - c.total_worker_time_ms, - c.avg_worker_time_ms, - c.total_elapsed_time_ms, - c.avg_elapsed_time_ms, - c.executions_per_second, - c.total_physical_reads_mb, - c.total_logical_writes_mb, - c.total_logical_reads_mb, - c.min_grant_mb, - c.max_grant_mb, - c.min_used_grant_mb, - c.max_used_grant_mb, - c.min_reserved_threads, - c.max_reserved_threads, - c.min_used_threads, - c.max_used_threads, - c.total_rows, - c.query_plan - FROM #available_plans AS ap - OUTER APPLY - ( - SELECT - deqs.*, - query_plan = - TRY_CAST(deps.query_plan AS xml) - FROM #dm_exec_query_stats deqs - OUTER APPLY sys.dm_exec_text_query_plan - ( - deqs.plan_handle, - deqs.statement_start_offset, - deqs.statement_end_offset - ) AS deps - WHERE deqs.sql_handle = ap.sql_handle - AND deps.dbid = ap.database_id - ) AS c - ) AS ap - WHERE ap.query_plan IS NOT NULL - ORDER BY - ap.avg_worker_time_ms DESC - OPTION(RECOMPILE, LOOP JOIN, HASH JOIN); - - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Returning findings %s', 0, 1, @d) WITH NOWAIT; - - SELECT - df.check_id, - df.database_name, - df.object_name, - df.finding_group, - df.finding - FROM #deadlock_findings AS df - ORDER BY - df.check_id, - df.sort_order - OPTION(RECOMPILE); - - SET @d = CONVERT(varchar(40), GETDATE(), 109); - RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - END; /*done with output to client app.*/ - END; - - IF @Debug = 1 - BEGIN - SELECT - table_name = N'#deadlock_data', - * - FROM #deadlock_data AS dd - OPTION(RECOMPILE); - - SELECT - table_name = N'#dd', - * - FROM #dd AS d - OPTION(RECOMPILE); - - SELECT - table_name = N'#deadlock_resource', - * - FROM #deadlock_resource AS dr - OPTION(RECOMPILE); - - SELECT - table_name = N'#deadlock_resource_parallel', - * - FROM #deadlock_resource_parallel AS drp - OPTION(RECOMPILE); - - SELECT - table_name = N'#deadlock_owner_waiter', - * - FROM #deadlock_owner_waiter AS dow - OPTION(RECOMPILE); - - SELECT - table_name = N'#deadlock_process', - * - FROM #deadlock_process AS dp - OPTION(RECOMPILE); - - SELECT - table_name = N'#deadlock_stack', - * - FROM #deadlock_stack AS ds - OPTION(RECOMPILE); - - SELECT - table_name = N'#deadlocks', - * - FROM #deadlocks AS d - OPTION(RECOMPILE); - - SELECT - table_name = N'#deadlock_results', - * - FROM #deadlock_results AS dr - OPTION(RECOMPILE); - - SELECT - table_name = N'#x', - * - FROM #x AS x - OPTION(RECOMPILE); - - SELECT - table_name = N'@sysAssObjId', - * - FROM @sysAssObjId AS s - OPTION(RECOMPILE); - - SELECT - table_name = N'#available_plans', - * - FROM #available_plans AS ap - OPTION(RECOMPILE); - - SELECT - table_name = N'#dm_exec_query_stats', - * - FROM #dm_exec_query_stats - OPTION(RECOMPILE); - - SELECT - procedure_parameters = - 'procedure_parameters', - DatabaseName = - @DatabaseName, - StartDate = - @StartDate, - EndDate = - @EndDate, - ObjectName = - @ObjectName, - StoredProcName = - @StoredProcName, - AppName = - @AppName, - HostName = - @HostName, - LoginName = - @LoginName, - EventSessionName = - @EventSessionName, - TargetSessionType = - @TargetSessionType, - VictimsOnly = - @VictimsOnly, - Debug = - @Debug, - Help = - @Help, - Version = - @Version, - VersionDate = - @VersionDate, - VersionCheckMode = - @VersionCheckMode, - OutputDatabaseName = - @OutputDatabaseName, - OutputSchemaName = - @OutputSchemaName, - OutputTableName = - @OutputTableName, - ExportToExcel = - @ExportToExcel; - - SELECT - declared_variables = - 'declared_variables', - DatabaseId = - @DatabaseId, - StartDateUTC = - @StartDateUTC, - EndDateUTC = - @EndDateUTC, - ProductVersion = - @ProductVersion, - ProductVersionMajor = - @ProductVersionMajor, - ProductVersionMinor = - @ProductVersionMinor, - ObjectFullName = - @ObjectFullName, - Azure = - @Azure, - RDS = - @RDS, - d = - @d, - StringToExecute = - @StringToExecute, - StringToExecuteParams = - @StringToExecuteParams, - r = - @r, - OutputTableFindings = - @OutputTableFindings, - DeadlockCount = - @DeadlockCount, - ServerName = - @ServerName, - OutputDatabaseCheck = - @OutputDatabaseCheck, - SessionId = - @SessionId, - TargetSessionId = - @TargetSessionId, - FileName = - @FileName, - inputbuf_bom = - @inputbuf_bom, - deadlock_result = - @deadlock_result; - END; /*End debug*/ - END; /*Final End*/ -GO -SET ANSI_NULLS ON; -SET ANSI_PADDING ON; -SET ANSI_WARNINGS ON; -SET ARITHABORT ON; -SET CONCAT_NULL_YIELDS_NULL ON; -SET QUOTED_IDENTIFIER ON; -SET STATISTICS IO OFF; -SET STATISTICS TIME OFF; -GO - -DECLARE @msg NVARCHAR(MAX) = N''; - - -- Must be a compatible, on-prem version of SQL (2016+) -IF ( (SELECT CONVERT(NVARCHAR(128), SERVERPROPERTY ('EDITION'))) <> 'SQL Azure' - AND (SELECT PARSENAME(CONVERT(NVARCHAR(128), SERVERPROPERTY ('PRODUCTVERSION')), 4)) < 13 - ) - -- or Azure Database (not Azure Data Warehouse), running at database compat level 130+ -OR ( (SELECT CONVERT(NVARCHAR(128), SERVERPROPERTY ('EDITION'))) = 'SQL Azure' - AND (SELECT SERVERPROPERTY ('ENGINEEDITION')) NOT IN (5,8) - AND (SELECT [compatibility_level] FROM sys.databases WHERE [name] = DB_NAME()) < 130 - ) -BEGIN - SELECT @msg = N'Sorry, sp_BlitzQueryStore doesn''t work on versions of SQL prior to 2016, or Azure Database compatibility < 130.' + REPLICATE(CHAR(13), 7933); - PRINT @msg; - RETURN; -END; - -IF OBJECT_ID('dbo.sp_BlitzQueryStore') IS NULL - EXEC ('CREATE PROCEDURE dbo.sp_BlitzQueryStore AS RETURN 0;'); -GO - -ALTER PROCEDURE dbo.sp_BlitzQueryStore - @Help BIT = 0, - @DatabaseName NVARCHAR(128) = NULL , - @Top INT = 3, - @StartDate DATETIME2 = NULL, - @EndDate DATETIME2 = NULL, - @MinimumExecutionCount INT = NULL, - @DurationFilter DECIMAL(38,4) = NULL , - @StoredProcName NVARCHAR(128) = NULL, - @Failed BIT = 0, - @PlanIdFilter INT = NULL, - @QueryIdFilter INT = NULL, - @ExportToExcel BIT = 0, - @HideSummary BIT = 0 , - @SkipXML BIT = 0, - @Debug BIT = 0, - @ExpertMode BIT = 0, - @Version VARCHAR(30) = NULL OUTPUT, - @VersionDate DATETIME = NULL OUTPUT, - @VersionCheckMode BIT = 0 -WITH RECOMPILE -AS -BEGIN /*First BEGIN*/ - -SET NOCOUNT ON; -SET STATISTICS XML OFF; -SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - -SELECT @Version = '8.19', @VersionDate = '20240222'; -IF(@VersionCheckMode = 1) -BEGIN - RETURN; -END; - - -DECLARE /*Variables for the variable Gods*/ - @msg NVARCHAR(MAX) = N'', --Used to format RAISERROR messages in some places - @sql_select NVARCHAR(MAX) = N'', --Used to hold SELECT statements for dynamic SQL - @sql_where NVARCHAR(MAX) = N'', -- Used to hold WHERE clause for dynamic SQL - @duration_filter_ms DECIMAL(38,4) = (@DurationFilter * 1000.), --We accept Duration in seconds, but we filter in milliseconds (this is grandfathered from sp_BlitzCache) - @execution_threshold INT = 1000, --Threshold at which we consider a query to be frequently executed - @ctp_threshold_pct TINYINT = 10, --Percentage of CTFP at which we consider a query to be near parallel - @long_running_query_warning_seconds BIGINT = 300 * 1000 ,--Number of seconds (converted to milliseconds) at which a query is considered long running - @memory_grant_warning_percent INT = 10,--Percent of memory grant used compared to what's granted; used to trigger unused memory grant warning - @ctp INT,--Holds the CTFP value for the server - @min_memory_per_query INT,--Holds the server configuration value for min memory per query - @cr NVARCHAR(1) = NCHAR(13),--Special character - @lf NVARCHAR(1) = NCHAR(10),--Special character - @tab NVARCHAR(1) = NCHAR(9),--Special character - @error_severity INT,--Holds error info for try/catch blocks - @error_state INT,--Holds error info for try/catch blocks - @sp_params NVARCHAR(MAX) = N'@sp_Top INT, @sp_StartDate DATETIME2, @sp_EndDate DATETIME2, @sp_MinimumExecutionCount INT, @sp_MinDuration INT, @sp_StoredProcName NVARCHAR(128), @sp_PlanIdFilter INT, @sp_QueryIdFilter INT',--Holds parameters used in dynamic SQL - @is_azure_db BIT = 0, --Are we using Azure? I'm not. You might be. That's cool. - @compatibility_level TINYINT = 0, --Some functionality (T-SQL) isn't available in lower compat levels. We can use this to weed out those issues as we go. - @log_size_mb DECIMAL(38,2) = 0, - @avg_tempdb_data_file DECIMAL(38,2) = 0; - -/*Grabs CTFP setting*/ -SELECT @ctp = NULLIF(CAST(value AS INT), 0) -FROM sys.configurations -WHERE name = N'cost threshold for parallelism' -OPTION (RECOMPILE); - -/*Grabs min query memory setting*/ -SELECT @min_memory_per_query = CONVERT(INT, c.value) -FROM sys.configurations AS c -WHERE c.name = N'min memory per query (KB)' -OPTION (RECOMPILE); - -/*Check if this is Azure first*/ -IF (SELECT CONVERT(NVARCHAR(128), SERVERPROPERTY ('EDITION'))) <> 'SQL Azure' - BEGIN - /*Grabs log size for datbase*/ - SELECT @log_size_mb = AVG(((mf.size * 8) / 1024.)) - FROM sys.master_files AS mf - WHERE mf.database_id = DB_ID(@DatabaseName) - AND mf.type_desc = 'LOG'; - - /*Grab avg tempdb file size*/ - SELECT @avg_tempdb_data_file = AVG(((mf.size * 8) / 1024.)) - FROM sys.master_files AS mf - WHERE mf.database_id = DB_ID('tempdb') - AND mf.type_desc = 'ROWS'; - END; - -/*Help section*/ - -IF @Help = 1 - BEGIN - - PRINT N' - sp_BlitzQueryStore from http://FirstResponderKit.org - - This script displays your most resource-intensive queries from the Query Store, - and points to ways you can tune these queries to make them faster. - - - To learn more, visit http://FirstResponderKit.org where you can download new - versions for free, watch training videos on how it works, get more info on - the findings, contribute your own code, and more. - - Known limitations of this version: - - This query will not run on SQL Server versions less than 2016. - - This query will not run on Azure Databases with compatibility less than 130. - - This query will not run on Azure Data Warehouse. - - Unknown limitations of this version: - - Could be tickling - - - MIT License - - Copyright (c) Brent Ozar Unlimited - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE. - '; - /*Parameter info*/ - SELECT N'@Help' AS [Parameter Name] , - N'BIT' AS [Data Type] , - N'0' AS [Default Value], - N'Displays this help message.' AS [Parameter Description] - UNION ALL - SELECT N'@DatabaseName', - N'NVARCHAR(128)', - N'NULL', - N'The name of the database you want to check the query store for.' - UNION ALL - SELECT N'@Top', - N'INT', - N'3', - N'The number of records to retrieve and analyze from the query store. The following system views are used: query_store_query, query_context_settings, query_store_wait_stats, query_store_runtime_stats,query_store_plan.' - - UNION ALL - SELECT N'@StartDate', - N'DATETIME2(7)', - N'NULL', - N'Get query store info starting from this date. When not specified, sp_BlitzQueryStore gets info from the last 7 days' - UNION ALL - SELECT N'@EndDate', - N'DATETIME2(7)', - N'NULL', - N'Get query store info until this date. When not specified, sp_BlitzQueryStore gets info from the last 7 days' - UNION ALL - SELECT N'@MinimumExecutionCount', - N'INT', - N'NULL', - N'When a value is specified, sp_BlitzQueryStore gets info for queries where count_executions >= @MinimumExecutionCount' - UNION ALL - SELECT N'@DurationFilter', - N'DECIMAL(38,4)', - N'NULL', - N'Time unit - seconds. When a value is specified, sp_BlitzQueryStore gets info for queries where the average duration >= @DurationFilter' - UNION ALL - SELECT N'@StoredProcName', - N'NVARCHAR(128)', - N'NULL', - N'Get information for this specific stored procedure.' - UNION ALL - SELECT N'@Failed', - N'BIT', - N'0', - N'When set to 1, only information about failed queries is returned.' - UNION ALL - SELECT N'@PlanIdFilter', - N'INT', - N'NULL', - N'The ID of the plan you want to check for.' - UNION ALL - SELECT N'@QueryIdFilter', - N'INT', - N'NULL', - N'The ID of the query you want to check for.' - UNION ALL - SELECT N'@ExportToExcel', - N'BIT', - N'0', - N'When set to 1, prepares output for exporting to Excel. Newlines and additional whitespace are removed from query text and the execution plan is not displayed.' - UNION ALL - SELECT N'@HideSummary', - N'BIT', - N'0', - N'When set to 1, hides the findings summary result set.' - UNION ALL - SELECT N'@SkipXML', - N'BIT', - N'0', - N'When set to 1, missing_indexes, implicit_conversion_info, cached_execution_parameters, are not returned. Does not affect query_plan_xml' - UNION ALL - SELECT N'@Debug', - N'BIT', - N'0', - N'Setting this to 1 will print dynamic SQL and select data from all tables used.' - UNION ALL - SELECT N'@ExpertMode', - N'BIT', - N'0', - N'When set to 1, more checks are done. Examples: many to many merge joins, row goals, adaptive joins, stats info, bad scans and plan forcing, computed columns that reference scalar UDFs.' - UNION ALL - SELECT N'@VersionCheckMode', - N'BIT', - N'0', - N'Outputs the version number and date.' - - /* Column definitions */ - SELECT 'database_name' AS [Column Name], - 'NVARCHAR(258)' AS [Data Type], - 'The name of the database where the plan was encountered.' AS [Column Description] - UNION ALL - SELECT 'query_cost', - 'FLOAT', - 'The cost of the execution plan in query bucks.' - UNION ALL - SELECT 'plan_id', - 'BIGINT', - 'The ID of the plan from sys.query_store_plan.' - UNION ALL - SELECT 'query_id', - 'BIGINT', - 'The ID of the query from sys.query_store_query.' - UNION ALL - SELECT 'query_id_all_plan_ids', - 'VARCHAR(8000)', - 'Comma-separated list of all query plan IDs associated with this query.' - UNION ALL - SELECT 'query_sql_text', - 'NVARCHAR(MAX)', - 'The text of the query, as provided by the user/app. Includes whitespaces, hints and comments. Comments and spaces before and after the query text are ignored.' - UNION ALL - SELECT 'proc_or_function_name', - 'NVARCHAR(258)', - 'If the query is part of a function/stored procedure, you''ll see here the name of its parent object.' - UNION ALL - SELECT 'query_plan_xml', - ' XML', - 'The query plan. Click to display a graphical plan.' - UNION ALL - SELECT 'warnings', - 'VARCHAR(MAX)', - 'A list of individual warnings generated by this query.' - UNION ALL - SELECT 'pattern', - 'NVARCHAR(512)', - 'A list of performance related patterns identified for this query.' - UNION ALL - SELECT 'parameter_sniffing_symptoms', - 'NVARCHAR(4000)', - 'A list of all the identified symptoms that are usually indicators of parameter sniffing.' - UNION ALL - SELECT 'last_force_failure_reason_desc', - 'NVARCHAR(258)', - 'Reason why plan forcing failed. NONE if plan isn''t forced.' - UNION ALL - SELECT 'top_three_waits', - 'NVARCHAR(MAX)', - 'The top 3 wait types, and their times in milliseconds, recorded for this query.' - UNION ALL - SELECT 'missing_indexes', - 'XML', - 'Missing index recommendations retrieved from the query plan.' - UNION ALL - SELECT 'implicit_conversion_info', - 'XML', - 'Information about the implicit conversion warnings,if any, retrieved from the query plan.' - UNION ALL - SELECT 'cached_execution_parameters', - 'XML', - 'Names, data types, and values for the parameters used when the query plan was compiled.' - UNION ALL - SELECT 'count_executions ', - 'BIGINT', - 'The number of executions of this particular query.' - UNION ALL - SELECT 'count_compiles', - 'BIGINT', - 'The number of plan compilations for this particular query.' - UNION ALL - SELECT 'total_cpu_time', - 'BIGINT', - 'Total CPU time, reported in milliseconds, that was consumed by all executions of this query.' - UNION ALL - SELECT 'avg_cpu_time ', - 'BIGINT', - 'Average CPU time, reported in milliseconds, consumed by each execution of this query.' - UNION ALL - SELECT 'total_duration', - 'BIGINT', - 'Total elapsed time, reported in milliseconds, consumed by all executions of this query.' - UNION ALL - SELECT 'avg_duration', - 'BIGINT', - 'Average elapsed time, reported in milliseconds, consumed by each execution of this query.' - UNION ALL - SELECT 'total_logical_io_reads', - 'BIGINT', - 'Total logical reads, reported in MB, performed by this query.' - UNION ALL - SELECT 'avg_logical_io_reads', - 'BIGINT', - 'Average logical reads, reported in MB, performed by each execution of this query.' - UNION ALL - SELECT 'total_physical_io_reads', - 'BIGINT', - 'Total physical reads, reported in MB, performed by this query.' - UNION ALL - SELECT 'avg_physical_io_reads', - 'BIGINT', - 'Average physical reads, reported in MB, performed by each execution of this query.' - UNION ALL - SELECT 'total_logical_io_writes', - 'BIGINT', - 'Total logical writes, reported in MB, performed by this query.' - UNION ALL - SELECT 'avg_logical_io_writes', - 'BIGINT', - 'Average logical writes, reported in MB, performed by each execution of this query.' - UNION ALL - SELECT 'total_rowcount', - 'BIGINT', - 'Total number of rows returned for all executions of this query.' - UNION ALL - SELECT 'avg_rowcount', - 'BIGINT', - 'Average number of rows returned by each execution of this query.' - UNION ALL - SELECT 'total_query_max_used_memory', - 'DECIMAL(38,2)', - 'Total max memory grant, reported in MB, used by this query.' - UNION ALL - SELECT 'avg_query_max_used_memory', - 'DECIMAL(38,2)', - 'Average max memory grant, reported in MB, used by each execution of this query.' - UNION ALL - SELECT 'total_tempdb_space_used', - 'DECIMAL(38,2)', - 'Total tempdb space, reported in MB, used by this query.' - UNION ALL - SELECT 'avg_tempdb_space_used', - 'DECIMAL(38,2)', - 'Average tempdb space, reported in MB, used by each execution of this query.' - UNION ALL - SELECT 'total_log_bytes_used', - 'DECIMAL(38,2)', - 'Total number of bytes in the database log used by this query.' - UNION ALL - SELECT 'avg_log_bytes_used', - 'DECIMAL(38,2)', - 'Average number of bytes in the database log used by each execution of this query.' - UNION ALL - SELECT 'total_num_physical_io_reads', - 'DECIMAL(38,2)', - 'Total number of physical I/O reads performed by this query (expressed as a number of read I/O operations).' - UNION ALL - SELECT 'avg_num_physical_io_reads', - 'DECIMAL(38,2)', - 'Average number of physical I/O reads performed by each execution of this query (expressed as a number of read I/O operations).' - UNION ALL - SELECT 'first_execution_time', - 'DATETIME2', - 'First execution time for this query within the aggregation interval. This is the end time of the query execution.' - UNION ALL - SELECT 'last_execution_time', - 'DATETIME2', - 'Last execution time for this query within the aggregation interval. This is the end time of the query execution.' - UNION ALL - SELECT 'context_settings', - 'NVARCHAR(512)', - 'Contains information about context settings associated with this query.'; - RETURN; - -END; - -/*Making sure your version is copasetic*/ -IF ( (SELECT CONVERT(NVARCHAR(128), SERVERPROPERTY ('EDITION'))) = 'SQL Azure' ) - BEGIN - SET @is_azure_db = 1; - - IF ( (SELECT SERVERPROPERTY ('ENGINEEDITION')) NOT IN (5,8) - OR (SELECT [compatibility_level] FROM sys.databases WHERE [name] = DB_NAME()) < 130 - ) - BEGIN - SELECT @msg = N'Sorry, sp_BlitzQueryStore doesn''t work on Azure Data Warehouse, or Azure Databases with DB compatibility < 130.' + REPLICATE(CHAR(13), 7933); - PRINT @msg; - RETURN; - END; - END; -ELSE IF ( (SELECT PARSENAME(CONVERT(NVARCHAR(128), SERVERPROPERTY ('PRODUCTVERSION')), 4) ) < 13 ) - BEGIN - SELECT @msg = N'Sorry, sp_BlitzQueryStore doesn''t work on versions of SQL prior to 2016.' + REPLICATE(CHAR(13), 7933); - PRINT @msg; - RETURN; - END; - -/*Making sure at least one database uses QS*/ -IF ( SELECT COUNT(*) - FROM sys.databases AS d - WHERE d.is_query_store_on = 1 - AND d.user_access_desc='MULTI_USER' - AND d.state_desc = 'ONLINE' - AND d.name NOT IN ('master', 'model', 'msdb', 'tempdb', '32767') - AND d.is_distributor = 0 ) = 0 - BEGIN - SELECT @msg = N'You don''t currently have any databases with Query Store enabled.' + REPLICATE(CHAR(13), 7933); - PRINT @msg; - RETURN; - END; - -/*Making sure your databases are using QDS.*/ -RAISERROR('Checking database validity', 0, 1) WITH NOWAIT; - -IF (@is_azure_db = 1 AND SERVERPROPERTY ('ENGINEEDITION') <> 8) - SET @DatabaseName = DB_NAME(); -ELSE -BEGIN - - /*If we're on Azure SQL DB we don't need to check all this @DatabaseName stuff...*/ - - SET @DatabaseName = LTRIM(RTRIM(@DatabaseName)); - - /*Did you set @DatabaseName?*/ - RAISERROR('Making sure [%s] isn''t NULL', 0, 1, @DatabaseName) WITH NOWAIT; - IF (@DatabaseName IS NULL) - BEGIN - RAISERROR('@DatabaseName cannot be NULL', 0, 1) WITH NOWAIT; - RETURN; - END; - - /*Does the database exist?*/ - RAISERROR('Making sure [%s] exists', 0, 1, @DatabaseName) WITH NOWAIT; - IF ((DB_ID(@DatabaseName)) IS NULL) - BEGIN - RAISERROR('The @DatabaseName you specified ([%s]) does not exist. Please check the name and try again.', 0, 1, @DatabaseName) WITH NOWAIT; - RETURN; - END; - - /*Is it online?*/ - RAISERROR('Making sure [%s] is online', 0, 1, @DatabaseName) WITH NOWAIT; - IF (DATABASEPROPERTYEX(@DatabaseName, 'Collation')) IS NULL - BEGIN - RAISERROR('The @DatabaseName you specified ([%s]) is not readable. Please check the name and try again. Better yet, check your server.', 0, 1, @DatabaseName); - RETURN; - END; -END; - -/*Does it have Query Store enabled?*/ -RAISERROR('Making sure [%s] has Query Store enabled', 0, 1, @DatabaseName) WITH NOWAIT; -IF - - ( SELECT [d].[name] - FROM [sys].[databases] AS d - WHERE [d].[is_query_store_on] = 1 - AND [d].[user_access_desc]='MULTI_USER' - AND [d].[state_desc] = 'ONLINE' - AND [d].[database_id] = (SELECT database_id FROM sys.databases WHERE name = @DatabaseName) - ) IS NULL -BEGIN - RAISERROR('The @DatabaseName you specified ([%s]) does not have the Query Store enabled. Please check the name or settings, and try again.', 0, 1, @DatabaseName) WITH NOWAIT; - RETURN; -END; - -/*Check database compat level*/ - -RAISERROR('Checking database compatibility level', 0, 1) WITH NOWAIT; - -SELECT @compatibility_level = d.compatibility_level -FROM sys.databases AS d -WHERE d.name = @DatabaseName; - -RAISERROR('The @DatabaseName you specified ([%s])is running in compatibility level ([%d]).', 0, 1, @DatabaseName, @compatibility_level) WITH NOWAIT; - - -/*Making sure top is set to something if NULL*/ -IF ( @Top IS NULL ) - BEGIN - SET @Top = 3; - END; - -/* -This section determines if you have the Query Store wait stats DMV -*/ - -RAISERROR('Checking for query_store_wait_stats', 0, 1) WITH NOWAIT; - -DECLARE @ws_out INT, - @waitstats BIT, - @ws_sql NVARCHAR(MAX) = N'SELECT @i_out = COUNT(*) FROM ' + QUOTENAME(@DatabaseName) + N'.sys.all_objects WHERE name = ''query_store_wait_stats'' OPTION (RECOMPILE);', - @ws_params NVARCHAR(MAX) = N'@i_out INT OUTPUT'; - -EXEC sys.sp_executesql @ws_sql, @ws_params, @i_out = @ws_out OUTPUT; - -SELECT @waitstats = CASE @ws_out WHEN 0 THEN 0 ELSE 1 END; - -SET @msg = N'Wait stats DMV ' + CASE @waitstats - WHEN 0 THEN N' does not exist, skipping.' - WHEN 1 THEN N' exists, will analyze.' - END; -RAISERROR(@msg, 0, 1) WITH NOWAIT; - -/* -This section determines if you have some additional columns present in 2017, in case they get back ported. -*/ - -RAISERROR('Checking for new columns in query_store_runtime_stats', 0, 1) WITH NOWAIT; - -DECLARE @nc_out INT, - @new_columns BIT, - @nc_sql NVARCHAR(MAX) = N'SELECT @i_out = COUNT(*) - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.all_columns AS ac - WHERE OBJECT_NAME(object_id) = ''query_store_runtime_stats'' - AND ac.name IN ( - ''avg_num_physical_io_reads'', - ''last_num_physical_io_reads'', - ''min_num_physical_io_reads'', - ''max_num_physical_io_reads'', - ''avg_log_bytes_used'', - ''last_log_bytes_used'', - ''min_log_bytes_used'', - ''max_log_bytes_used'', - ''avg_tempdb_space_used'', - ''last_tempdb_space_used'', - ''min_tempdb_space_used'', - ''max_tempdb_space_used'' - ) OPTION (RECOMPILE);', - @nc_params NVARCHAR(MAX) = N'@i_out INT OUTPUT'; - -EXEC sys.sp_executesql @nc_sql, @ws_params, @i_out = @nc_out OUTPUT; - -SELECT @new_columns = CASE @nc_out WHEN 12 THEN 1 ELSE 0 END; - -SET @msg = N'New query_store_runtime_stats columns ' + CASE @new_columns - WHEN 0 THEN N' do not exist, skipping.' - WHEN 1 THEN N' exist, will analyze.' - END; -RAISERROR(@msg, 0, 1) WITH NOWAIT; - -/* -This section determines if Parameter Sensitive Plan Optimization is enabled on SQL Server 2022+. -*/ - -RAISERROR('Checking for Parameter Sensitive Plan Optimization ', 0, 1) WITH NOWAIT; - -DECLARE @pspo_out BIT, - @pspo_enabled BIT, - @pspo_sql NVARCHAR(MAX) = N'SELECT @i_out = CONVERT(bit,dsc.value) - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.database_scoped_configurations dsc - WHERE dsc.name = ''PARAMETER_SENSITIVE_PLAN_OPTIMIZATION'';', - @pspo_params NVARCHAR(MAX) = N'@i_out INT OUTPUT'; - -EXEC sys.sp_executesql @pspo_sql, @pspo_params, @i_out = @pspo_out OUTPUT; - -SET @pspo_enabled = CASE WHEN @pspo_out = 1 THEN 1 ELSE 0 END; - -SET @msg = N'Parameter Sensitive Plan Optimization ' + CASE @pspo_enabled - WHEN 0 THEN N' not enabled, skipping.' - WHEN 1 THEN N' enabled, will analyze.' - END; -RAISERROR(@msg, 0, 1) WITH NOWAIT; - -/* -These are the temp tables we use -*/ - - -/* -This one holds the grouped data that helps use figure out which periods to examine -*/ - -RAISERROR(N'Creating temp tables', 0, 1) WITH NOWAIT; - -DROP TABLE IF EXISTS #grouped_interval; - -CREATE TABLE #grouped_interval -( - flat_date DATE NULL, - start_range DATETIME NULL, - end_range DATETIME NULL, - total_avg_duration_ms DECIMAL(38, 2) NULL, - total_avg_cpu_time_ms DECIMAL(38, 2) NULL, - total_avg_logical_io_reads_mb DECIMAL(38, 2) NULL, - total_avg_physical_io_reads_mb DECIMAL(38, 2) NULL, - total_avg_logical_io_writes_mb DECIMAL(38, 2) NULL, - total_avg_query_max_used_memory_mb DECIMAL(38, 2) NULL, - total_rowcount DECIMAL(38, 2) NULL, - total_count_executions BIGINT NULL, - total_avg_log_bytes_mb DECIMAL(38, 2) NULL, - total_avg_tempdb_space DECIMAL(38, 2) NULL, - total_max_duration_ms DECIMAL(38, 2) NULL, - total_max_cpu_time_ms DECIMAL(38, 2) NULL, - total_max_logical_io_reads_mb DECIMAL(38, 2) NULL, - total_max_physical_io_reads_mb DECIMAL(38, 2) NULL, - total_max_logical_io_writes_mb DECIMAL(38, 2) NULL, - total_max_query_max_used_memory_mb DECIMAL(38, 2) NULL, - total_max_log_bytes_mb DECIMAL(38, 2) NULL, - total_max_tempdb_space DECIMAL(38, 2) NULL, - INDEX gi_ix_dates CLUSTERED (start_range, end_range) -); - - -/* -These are the plans we focus on based on what we find in the grouped intervals -*/ -DROP TABLE IF EXISTS #working_plans; - -CREATE TABLE #working_plans -( - plan_id BIGINT, - query_id BIGINT, - pattern NVARCHAR(258), - INDEX wp_ix_ids CLUSTERED (plan_id, query_id) -); - - -/* -These are the gathered metrics we get from query store to generate some warnings and help you find your worst offenders -*/ -DROP TABLE IF EXISTS #working_metrics; - -CREATE TABLE #working_metrics -( - database_name NVARCHAR(258), - plan_id BIGINT, - query_id BIGINT, - query_id_all_plan_ids VARCHAR(8000), - /*these columns are from query_store_query*/ - proc_or_function_name NVARCHAR(258), - batch_sql_handle VARBINARY(64), - query_hash BINARY(8), - query_parameterization_type_desc NVARCHAR(258), - parameter_sniffing_symptoms NVARCHAR(4000), - count_compiles BIGINT, - avg_compile_duration DECIMAL(38,2), - last_compile_duration DECIMAL(38,2), - avg_bind_duration DECIMAL(38,2), - last_bind_duration DECIMAL(38,2), - avg_bind_cpu_time DECIMAL(38,2), - last_bind_cpu_time DECIMAL(38,2), - avg_optimize_duration DECIMAL(38,2), - last_optimize_duration DECIMAL(38,2), - avg_optimize_cpu_time DECIMAL(38,2), - last_optimize_cpu_time DECIMAL(38,2), - avg_compile_memory_kb DECIMAL(38,2), - last_compile_memory_kb DECIMAL(38,2), - /*These come from query_store_runtime_stats*/ - execution_type_desc NVARCHAR(128), - first_execution_time DATETIME2, - last_execution_time DATETIME2, - count_executions BIGINT, - avg_duration DECIMAL(38,2) , - last_duration DECIMAL(38,2), - min_duration DECIMAL(38,2), - max_duration DECIMAL(38,2), - avg_cpu_time DECIMAL(38,2), - last_cpu_time DECIMAL(38,2), - min_cpu_time DECIMAL(38,2), - max_cpu_time DECIMAL(38,2), - avg_logical_io_reads DECIMAL(38,2), - last_logical_io_reads DECIMAL(38,2), - min_logical_io_reads DECIMAL(38,2), - max_logical_io_reads DECIMAL(38,2), - avg_logical_io_writes DECIMAL(38,2), - last_logical_io_writes DECIMAL(38,2), - min_logical_io_writes DECIMAL(38,2), - max_logical_io_writes DECIMAL(38,2), - avg_physical_io_reads DECIMAL(38,2), - last_physical_io_reads DECIMAL(38,2), - min_physical_io_reads DECIMAL(38,2), - max_physical_io_reads DECIMAL(38,2), - avg_clr_time DECIMAL(38,2), - last_clr_time DECIMAL(38,2), - min_clr_time DECIMAL(38,2), - max_clr_time DECIMAL(38,2), - avg_dop BIGINT, - last_dop BIGINT, - min_dop BIGINT, - max_dop BIGINT, - avg_query_max_used_memory DECIMAL(38,2), - last_query_max_used_memory DECIMAL(38,2), - min_query_max_used_memory DECIMAL(38,2), - max_query_max_used_memory DECIMAL(38,2), - avg_rowcount DECIMAL(38,2), - last_rowcount DECIMAL(38,2), - min_rowcount DECIMAL(38,2), - max_rowcount DECIMAL(38,2), - /*These are 2017 only, AFAIK*/ - avg_num_physical_io_reads DECIMAL(38,2), - last_num_physical_io_reads DECIMAL(38,2), - min_num_physical_io_reads DECIMAL(38,2), - max_num_physical_io_reads DECIMAL(38,2), - avg_log_bytes_used DECIMAL(38,2), - last_log_bytes_used DECIMAL(38,2), - min_log_bytes_used DECIMAL(38,2), - max_log_bytes_used DECIMAL(38,2), - avg_tempdb_space_used DECIMAL(38,2), - last_tempdb_space_used DECIMAL(38,2), - min_tempdb_space_used DECIMAL(38,2), - max_tempdb_space_used DECIMAL(38,2), - /*These are computed columns to make some stuff easier down the line*/ - total_compile_duration AS avg_compile_duration * count_compiles, - total_bind_duration AS avg_bind_duration * count_compiles, - total_bind_cpu_time AS avg_bind_cpu_time * count_compiles, - total_optimize_duration AS avg_optimize_duration * count_compiles, - total_optimize_cpu_time AS avg_optimize_cpu_time * count_compiles, - total_compile_memory_kb AS avg_compile_memory_kb * count_compiles, - total_duration AS avg_duration * count_executions, - total_cpu_time AS avg_cpu_time * count_executions, - total_logical_io_reads AS avg_logical_io_reads * count_executions, - total_logical_io_writes AS avg_logical_io_writes * count_executions, - total_physical_io_reads AS avg_physical_io_reads * count_executions, - total_clr_time AS avg_clr_time * count_executions, - total_query_max_used_memory AS avg_query_max_used_memory * count_executions, - total_rowcount AS avg_rowcount * count_executions, - total_num_physical_io_reads AS avg_num_physical_io_reads * count_executions, - total_log_bytes_used AS avg_log_bytes_used * count_executions, - total_tempdb_space_used AS avg_tempdb_space_used * count_executions, - xpm AS NULLIF(count_executions, 0) / NULLIF(DATEDIFF(MINUTE, first_execution_time, last_execution_time), 0), - percent_memory_grant_used AS CONVERT(MONEY, ISNULL(NULLIF(( max_query_max_used_memory * 1.00 ), 0) / NULLIF(min_query_max_used_memory, 0), 0) * 100.), - INDEX wm_ix_ids CLUSTERED (plan_id, query_id, query_hash) -); - - -/* -This is where we store some additional metrics, along with the query plan and text -*/ -DROP TABLE IF EXISTS #working_plan_text; - -CREATE TABLE #working_plan_text -( - database_name NVARCHAR(258), - plan_id BIGINT, - query_id BIGINT, - /*These are from query_store_plan*/ - plan_group_id BIGINT, - engine_version NVARCHAR(64), - compatibility_level INT, - query_plan_hash BINARY(8), - query_plan_xml XML, - is_online_index_plan BIT, - is_trivial_plan BIT, - is_parallel_plan BIT, - is_forced_plan BIT, - is_natively_compiled BIT, - force_failure_count BIGINT, - last_force_failure_reason_desc NVARCHAR(258), - count_compiles BIGINT, - initial_compile_start_time DATETIME2, - last_compile_start_time DATETIME2, - last_execution_time DATETIME2, - avg_compile_duration DECIMAL(38,2), - last_compile_duration BIGINT, - /*These are from query_store_query*/ - query_sql_text NVARCHAR(MAX), - statement_sql_handle VARBINARY(64), - is_part_of_encrypted_module BIT, - has_restricted_text BIT, - /*This is from query_context_settings*/ - context_settings NVARCHAR(512), - /*This is from #working_plans*/ - pattern NVARCHAR(512), - top_three_waits NVARCHAR(MAX), - INDEX wpt_ix_ids CLUSTERED (plan_id, query_id, query_plan_hash) -); - - -/* -This is where we store warnings that we generate from the XML and metrics -*/ -DROP TABLE IF EXISTS #working_warnings; - -CREATE TABLE #working_warnings -( - plan_id BIGINT, - query_id BIGINT, - query_hash BINARY(8), - sql_handle VARBINARY(64), - proc_or_function_name NVARCHAR(258), - plan_multiple_plans BIT, - is_forced_plan BIT, - is_forced_parameterized BIT, - is_cursor BIT, - is_optimistic_cursor BIT, - is_forward_only_cursor BIT, - is_fast_forward_cursor BIT, - is_cursor_dynamic BIT, - is_parallel BIT, - is_forced_serial BIT, - is_key_lookup_expensive BIT, - key_lookup_cost FLOAT, - is_remote_query_expensive BIT, - remote_query_cost FLOAT, - frequent_execution BIT, - parameter_sniffing BIT, - unparameterized_query BIT, - near_parallel BIT, - plan_warnings BIT, - long_running BIT, - downlevel_estimator BIT, - implicit_conversions BIT, - tvf_estimate BIT, - compile_timeout BIT, - compile_memory_limit_exceeded BIT, - warning_no_join_predicate BIT, - query_cost FLOAT, - missing_index_count INT, - unmatched_index_count INT, - is_trivial BIT, - trace_flags_session NVARCHAR(1000), - is_unused_grant BIT, - function_count INT, - clr_function_count INT, - is_table_variable BIT, - no_stats_warning BIT, - relop_warnings BIT, - is_table_scan BIT, - backwards_scan BIT, - forced_index BIT, - forced_seek BIT, - forced_scan BIT, - columnstore_row_mode BIT, - is_computed_scalar BIT , - is_sort_expensive BIT, - sort_cost FLOAT, - is_computed_filter BIT, - op_name NVARCHAR(100) NULL, - index_insert_count INT NULL, - index_update_count INT NULL, - index_delete_count INT NULL, - cx_insert_count INT NULL, - cx_update_count INT NULL, - cx_delete_count INT NULL, - table_insert_count INT NULL, - table_update_count INT NULL, - table_delete_count INT NULL, - index_ops AS (index_insert_count + index_update_count + index_delete_count + - cx_insert_count + cx_update_count + cx_delete_count + - table_insert_count + table_update_count + table_delete_count), - is_row_level BIT, - is_spatial BIT, - index_dml BIT, - table_dml BIT, - long_running_low_cpu BIT, - low_cost_high_cpu BIT, - stale_stats BIT, - is_adaptive BIT, - is_slow_plan BIT, - is_compile_more BIT, - index_spool_cost FLOAT, - index_spool_rows FLOAT, - is_spool_expensive BIT, - is_spool_more_rows BIT, - estimated_rows FLOAT, - is_bad_estimate BIT, - is_big_log BIT, - is_big_tempdb BIT, - is_paul_white_electric BIT, - is_row_goal BIT, - is_mstvf BIT, - is_mm_join BIT, - is_nonsargable BIT, - busy_loops BIT, - tvf_join BIT, - implicit_conversion_info XML, - cached_execution_parameters XML, - missing_indexes XML, - warnings NVARCHAR(4000) - INDEX ww_ix_ids CLUSTERED (plan_id, query_id, query_hash, sql_handle) -); - - -DROP TABLE IF EXISTS #working_wait_stats; - -CREATE TABLE #working_wait_stats -( - plan_id BIGINT, - wait_category TINYINT, - wait_category_desc NVARCHAR(258), - total_query_wait_time_ms BIGINT, - avg_query_wait_time_ms DECIMAL(38, 2), - last_query_wait_time_ms BIGINT, - min_query_wait_time_ms BIGINT, - max_query_wait_time_ms BIGINT, - wait_category_mapped AS CASE wait_category - WHEN 0 THEN N'UNKNOWN' - WHEN 1 THEN N'SOS_SCHEDULER_YIELD' - WHEN 2 THEN N'THREADPOOL' - WHEN 3 THEN N'LCK_M_%' - WHEN 4 THEN N'LATCH_%' - WHEN 5 THEN N'PAGELATCH_%' - WHEN 6 THEN N'PAGEIOLATCH_%' - WHEN 7 THEN N'RESOURCE_SEMAPHORE_QUERY_COMPILE' - WHEN 8 THEN N'CLR%, SQLCLR%' - WHEN 9 THEN N'DBMIRROR%' - WHEN 10 THEN N'XACT%, DTC%, TRAN_MARKLATCH_%, MSQL_XACT_%, TRANSACTION_MUTEX' - WHEN 11 THEN N'SLEEP_%, LAZYWRITER_SLEEP, SQLTRACE_BUFFER_FLUSH, SQLTRACE_INCREMENTAL_FLUSH_SLEEP, SQLTRACE_WAIT_ENTRIES, FT_IFTS_SCHEDULER_IDLE_WAIT, XE_DISPATCHER_WAIT, REQUEST_FOR_DEADLOCK_SEARCH, LOGMGR_QUEUE, ONDEMAND_TASK_QUEUE, CHECKPOINT_QUEUE, XE_TIMER_EVENT' - WHEN 12 THEN N'PREEMPTIVE_%' - WHEN 13 THEN N'BROKER_% (but not BROKER_RECEIVE_WAITFOR)' - WHEN 14 THEN N'LOGMGR, LOGBUFFER, LOGMGR_RESERVE_APPEND, LOGMGR_FLUSH, LOGMGR_PMM_LOG, CHKPT, WRITELOG' - WHEN 15 THEN N'ASYNC_NETWORK_IO, NET_WAITFOR_PACKET, PROXY_NETWORK_IO, EXTERNAL_SCRIPT_NETWORK_IOF' - WHEN 16 THEN N'CXPACKET, EXCHANGE, CXCONSUMER' - WHEN 17 THEN N'RESOURCE_SEMAPHORE, CMEMTHREAD, CMEMPARTITIONED, EE_PMOLOCK, MEMORY_ALLOCATION_EXT, RESERVED_MEMORY_ALLOCATION_EXT, MEMORY_GRANT_UPDATE' - WHEN 18 THEN N'WAITFOR, WAIT_FOR_RESULTS, BROKER_RECEIVE_WAITFOR' - WHEN 19 THEN N'TRACEWRITE, SQLTRACE_LOCK, SQLTRACE_FILE_BUFFER, SQLTRACE_FILE_WRITE_IO_COMPLETION, SQLTRACE_FILE_READ_IO_COMPLETION, SQLTRACE_PENDING_BUFFER_WRITERS, SQLTRACE_SHUTDOWN, QUERY_TRACEOUT, TRACE_EVTNOTIFF' - WHEN 20 THEN N'FT_RESTART_CRAWL, FULLTEXT GATHERER, MSSEARCH, FT_METADATA_MUTEX, FT_IFTSHC_MUTEX, FT_IFTSISM_MUTEX, FT_IFTS_RWLOCK, FT_COMPROWSET_RWLOCK, FT_MASTER_MERGE, FT_PROPERTYLIST_CACHE, FT_MASTER_MERGE_COORDINATOR, PWAIT_RESOURCE_SEMAPHORE_FT_PARALLEL_QUERY_SYNC' - WHEN 21 THEN N'ASYNC_IO_COMPLETION, IO_COMPLETION, BACKUPIO, WRITE_COMPLETION, IO_QUEUE_LIMIT, IO_RETRY' - WHEN 22 THEN N'SE_REPL_%, REPL_%, HADR_% (but not HADR_THROTTLE_LOG_RATE_GOVERNOR), PWAIT_HADR_%, REPLICA_WRITES, FCB_REPLICA_WRITE, FCB_REPLICA_READ, PWAIT_HADRSIM' - WHEN 23 THEN N'LOG_RATE_GOVERNOR, POOL_LOG_RATE_GOVERNOR, HADR_THROTTLE_LOG_RATE_GOVERNOR, INSTANCE_LOG_RATE_GOVERNOR' - END, - INDEX wws_ix_ids CLUSTERED ( plan_id) -); - - -/* -The next three tables hold plan XML parsed out to different degrees -*/ -DROP TABLE IF EXISTS #statements; - -CREATE TABLE #statements -( - plan_id BIGINT, - query_id BIGINT, - query_hash BINARY(8), - sql_handle VARBINARY(64), - statement XML, - is_cursor BIT - INDEX s_ix_ids CLUSTERED (plan_id, query_id, query_hash, sql_handle) -); - - -DROP TABLE IF EXISTS #query_plan; - -CREATE TABLE #query_plan -( - plan_id BIGINT, - query_id BIGINT, - query_hash BINARY(8), - sql_handle VARBINARY(64), - query_plan XML, - INDEX qp_ix_ids CLUSTERED (plan_id, query_id, query_hash, sql_handle) -); - - -DROP TABLE IF EXISTS #relop; - -CREATE TABLE #relop -( - plan_id BIGINT, - query_id BIGINT, - query_hash BINARY(8), - sql_handle VARBINARY(64), - relop XML, - INDEX ix_ids CLUSTERED (plan_id, query_id, query_hash, sql_handle) -); - - -DROP TABLE IF EXISTS #plan_cost; - -CREATE TABLE #plan_cost -( - query_plan_cost DECIMAL(38,2), - sql_handle VARBINARY(64), - plan_id INT, - INDEX px_ix_ids CLUSTERED (sql_handle, plan_id) -); - - -DROP TABLE IF EXISTS #est_rows; - -CREATE TABLE #est_rows -( - estimated_rows DECIMAL(38,2), - query_hash BINARY(8), - INDEX px_ix_ids CLUSTERED (query_hash) -); - - -DROP TABLE IF EXISTS #stats_agg; - -CREATE TABLE #stats_agg -( - sql_handle VARBINARY(64), - last_update DATETIME2, - modification_count BIGINT, - sampling_percent DECIMAL(38, 2), - [statistics] NVARCHAR(258), - [table] NVARCHAR(258), - [schema] NVARCHAR(258), - [database] NVARCHAR(258), - INDEX sa_ix_ids CLUSTERED (sql_handle) -); - - -DROP TABLE IF EXISTS #trace_flags; - -CREATE TABLE #trace_flags -( - sql_handle VARBINARY(54), - global_trace_flags NVARCHAR(4000), - session_trace_flags NVARCHAR(4000), - INDEX tf_ix_ids CLUSTERED (sql_handle) -); - - -DROP TABLE IF EXISTS #warning_results; - -CREATE TABLE #warning_results -( - ID INT IDENTITY(1,1) PRIMARY KEY CLUSTERED, - CheckID INT, - Priority TINYINT, - FindingsGroup NVARCHAR(50), - Finding NVARCHAR(200), - URL NVARCHAR(200), - Details NVARCHAR(4000) -); - -/*These next three tables hold information about implicit conversion and cached parameters */ -DROP TABLE IF EXISTS #stored_proc_info; - -CREATE TABLE #stored_proc_info -( - sql_handle VARBINARY(64), - query_hash BINARY(8), - variable_name NVARCHAR(258), - variable_datatype NVARCHAR(258), - converted_column_name NVARCHAR(258), - compile_time_value NVARCHAR(258), - proc_name NVARCHAR(1000), - column_name NVARCHAR(4000), - converted_to NVARCHAR(258), - set_options NVARCHAR(1000) - INDEX tf_ix_ids CLUSTERED (sql_handle, query_hash) -); - -DROP TABLE IF EXISTS #variable_info; - -CREATE TABLE #variable_info -( - query_hash BINARY(8), - sql_handle VARBINARY(64), - proc_name NVARCHAR(1000), - variable_name NVARCHAR(258), - variable_datatype NVARCHAR(258), - compile_time_value NVARCHAR(258), - INDEX vif_ix_ids CLUSTERED (sql_handle, query_hash) -); - -DROP TABLE IF EXISTS #conversion_info; - -CREATE TABLE #conversion_info -( - query_hash BINARY(8), - sql_handle VARBINARY(64), - proc_name NVARCHAR(128), - expression NVARCHAR(4000), - at_charindex AS CHARINDEX('@', expression), - bracket_charindex AS CHARINDEX(']', expression, CHARINDEX('@', expression)) - CHARINDEX('@', expression), - comma_charindex AS CHARINDEX(',', expression) + 1, - second_comma_charindex AS - CHARINDEX(',', expression, CHARINDEX(',', expression) + 1) - CHARINDEX(',', expression) - 1, - equal_charindex AS CHARINDEX('=', expression) + 1, - paren_charindex AS CHARINDEX('(', expression) + 1, - comma_paren_charindex AS - CHARINDEX(',', expression, CHARINDEX('(', expression) + 1) - CHARINDEX('(', expression) - 1, - convert_implicit_charindex AS CHARINDEX('=CONVERT_IMPLICIT', expression), - INDEX cif_ix_ids CLUSTERED (sql_handle, query_hash) -); - -/* These tables support the Missing Index details clickable*/ - - -DROP TABLE IF EXISTS #missing_index_xml; - -CREATE TABLE #missing_index_xml -( - query_hash BINARY(8), - sql_handle VARBINARY(64), - impact FLOAT, - index_xml XML, - INDEX mix_ix_ids CLUSTERED (sql_handle, query_hash) -); - -DROP TABLE IF EXISTS #missing_index_schema; - -CREATE TABLE #missing_index_schema -( - query_hash BINARY(8), - sql_handle VARBINARY(64), - impact FLOAT, - database_name NVARCHAR(128), - schema_name NVARCHAR(128), - table_name NVARCHAR(128), - index_xml XML, - INDEX mis_ix_ids CLUSTERED (sql_handle, query_hash) -); - - -DROP TABLE IF EXISTS #missing_index_usage; - -CREATE TABLE #missing_index_usage -( - query_hash BINARY(8), - sql_handle VARBINARY(64), - impact FLOAT, - database_name NVARCHAR(128), - schema_name NVARCHAR(128), - table_name NVARCHAR(128), - usage NVARCHAR(128), - index_xml XML, - INDEX miu_ix_ids CLUSTERED (sql_handle, query_hash) -); - -DROP TABLE IF EXISTS #missing_index_detail; - -CREATE TABLE #missing_index_detail -( - query_hash BINARY(8), - sql_handle VARBINARY(64), - impact FLOAT, - database_name NVARCHAR(128), - schema_name NVARCHAR(128), - table_name NVARCHAR(128), - usage NVARCHAR(128), - column_name NVARCHAR(128), - INDEX mid_ix_ids CLUSTERED (sql_handle, query_hash) -); - - -DROP TABLE IF EXISTS #missing_index_pretty; - -CREATE TABLE #missing_index_pretty -( - query_hash BINARY(8), - sql_handle VARBINARY(64), - impact FLOAT, - database_name NVARCHAR(128), - schema_name NVARCHAR(128), - table_name NVARCHAR(128), - equality NVARCHAR(MAX), - inequality NVARCHAR(MAX), - [include] NVARCHAR(MAX), - is_spool BIT, - details AS N'/* ' - + CHAR(10) - + CASE is_spool - WHEN 0 - THEN N'The Query Processor estimates that implementing the ' - ELSE N'We estimate that implementing the ' - END - + CONVERT(NVARCHAR(30), impact) - + '%.' - + CHAR(10) - + N'*/' - + CHAR(10) + CHAR(13) - + N'/* ' - + CHAR(10) - + N'USE ' - + database_name - + CHAR(10) - + N'GO' - + CHAR(10) + CHAR(13) - + N'CREATE NONCLUSTERED INDEX ix_' - + ISNULL(REPLACE(REPLACE(REPLACE(equality,'[', ''), ']', ''), ', ', '_'), '') - + ISNULL(REPLACE(REPLACE(REPLACE(inequality,'[', ''), ']', ''), ', ', '_'), '') - + CASE WHEN [include] IS NOT NULL THEN + N'_Includes' ELSE N'' END - + CHAR(10) - + N' ON ' - + schema_name - + N'.' - + table_name - + N' (' + - + CASE WHEN equality IS NOT NULL - THEN equality - + CASE WHEN inequality IS NOT NULL - THEN N', ' + inequality - ELSE N'' - END - ELSE inequality - END - + N')' - + CHAR(10) - + CASE WHEN include IS NOT NULL - THEN N'INCLUDE (' + include + N') WITH (FILLFACTOR=100, ONLINE=?, SORT_IN_TEMPDB=?, DATA_COMPRESSION=?);' - ELSE N' WITH (FILLFACTOR=100, ONLINE=?, SORT_IN_TEMPDB=?, DATA_COMPRESSION=?);' - END - + CHAR(10) - + N'GO' - + CHAR(10) - + N'*/', - INDEX mip_ix_ids CLUSTERED (sql_handle, query_hash) -); - -DROP TABLE IF EXISTS #index_spool_ugly; - -CREATE TABLE #index_spool_ugly -( - query_hash BINARY(8), - sql_handle VARBINARY(64), - impact FLOAT, - database_name NVARCHAR(128), - schema_name NVARCHAR(128), - table_name NVARCHAR(128), - equality NVARCHAR(MAX), - inequality NVARCHAR(MAX), - [include] NVARCHAR(MAX), - INDEX isu_ix_ids CLUSTERED (sql_handle, query_hash) -); - - -/*Sets up WHERE clause that gets used quite a bit*/ - ---Date stuff ---If they're both NULL, we'll just look at the last 7 days -IF (@StartDate IS NULL AND @EndDate IS NULL) - BEGIN - RAISERROR(N'@StartDate and @EndDate are NULL, checking last 7 days', 0, 1) WITH NOWAIT; - SET @sql_where += N' AND qsrs.last_execution_time >= DATEADD(DAY, -7, DATEDIFF(DAY, 0, SYSDATETIME() )) - '; - END; - ---Hey, that's nice of me -IF @StartDate IS NOT NULL - BEGIN - RAISERROR(N'Setting start date filter', 0, 1) WITH NOWAIT; - SET @sql_where += N' AND qsrs.last_execution_time >= @sp_StartDate - '; - END; - ---Alright, sensible -IF @EndDate IS NOT NULL - BEGIN - RAISERROR(N'Setting end date filter', 0, 1) WITH NOWAIT; - SET @sql_where += N' AND qsrs.last_execution_time < @sp_EndDate - '; - END; - ---C'mon, why would you do that? -IF (@StartDate IS NULL AND @EndDate IS NOT NULL) - BEGIN - RAISERROR(N'Setting reasonable start date filter', 0, 1) WITH NOWAIT; - SET @sql_where += N' AND qsrs.last_execution_time >= DATEADD(DAY, -7, @sp_EndDate) - '; - END; - ---Jeez, abusive -IF (@StartDate IS NOT NULL AND @EndDate IS NULL) - BEGIN - RAISERROR(N'Setting reasonable end date filter', 0, 1) WITH NOWAIT; - SET @sql_where += N' AND qsrs.last_execution_time < DATEADD(DAY, 7, @sp_StartDate) - '; - END; - ---I care about minimum execution counts -IF @MinimumExecutionCount IS NOT NULL - BEGIN - RAISERROR(N'Setting execution filter', 0, 1) WITH NOWAIT; - SET @sql_where += N' AND qsrs.count_executions >= @sp_MinimumExecutionCount - '; - END; - ---You care about stored proc names -IF @StoredProcName IS NOT NULL - BEGIN - - IF (@pspo_enabled = 1) - BEGIN - RAISERROR(N'Setting stored proc filter, PSPO enabled', 0, 1) WITH NOWAIT; - /* If PSPO is enabled, the object_id for a variant query would be 0. To include it, we check whether the object_id = 0 query - is a variant query, and whether it's parent query belongs to @sp_StoredProcName. */ - SET @sql_where += N' AND (object_name(qsq.object_id, DB_ID(' + QUOTENAME(@DatabaseName, '''') + N')) = @sp_StoredProcName - OR (qsq.object_id = 0 - AND EXISTS( - SELECT 1 - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_variant vr - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query pqsq - ON pqsq.query_id = vr.parent_query_id - WHERE - vr.query_variant_query_id = qsq.query_id - AND object_name(pqsq.object_id, DB_ID(' + QUOTENAME(@DatabaseName, '''') + N')) = @sp_StoredProcName - ) - )) - '; - END - ELSE - BEGIN - RAISERROR(N'Setting stored proc filter', 0, 1) WITH NOWAIT; - SET @sql_where += N' AND object_name(qsq.object_id, DB_ID(' + QUOTENAME(@DatabaseName, '''') + N')) = @sp_StoredProcName - '; - END - END; - ---I will always love you, but hopefully this query will eventually end -IF @DurationFilter IS NOT NULL - BEGIN - RAISERROR(N'Setting duration filter', 0, 1) WITH NOWAIT; - SET @sql_where += N' AND (qsrs.avg_duration / 1000.) >= @sp_MinDuration - '; - END; - ---I don't know why you'd go looking for failed queries, but hey -IF (@Failed = 0 OR @Failed IS NULL) - BEGIN - RAISERROR(N'Setting failed query filter to 0', 0, 1) WITH NOWAIT; - SET @sql_where += N' AND qsrs.execution_type = 0 - '; - END; -IF (@Failed = 1) - BEGIN - RAISERROR(N'Setting failed query filter to 3, 4', 0, 1) WITH NOWAIT; - SET @sql_where += N' AND qsrs.execution_type IN (3, 4) - '; - END; - -/*Filtering for plan_id or query_id*/ -IF (@PlanIdFilter IS NOT NULL) - BEGIN - RAISERROR(N'Setting plan_id filter', 0, 1) WITH NOWAIT; - SET @sql_where += N' AND qsp.plan_id = @sp_PlanIdFilter - '; - END; - -IF (@QueryIdFilter IS NOT NULL) - BEGIN - RAISERROR(N'Setting query_id filter', 0, 1) WITH NOWAIT; - SET @sql_where += N' AND qsq.query_id = @sp_QueryIdFilter - '; - END; - -IF @Debug = 1 - RAISERROR(N'Starting WHERE clause:', 0, 1) WITH NOWAIT; - PRINT @sql_where; - -IF @sql_where IS NULL - BEGIN - RAISERROR(N'@sql_where is NULL', 0, 1) WITH NOWAIT; - RETURN; - END; - -IF (@ExportToExcel = 1 OR @SkipXML = 1) - BEGIN - RAISERROR(N'Exporting to Excel or skipping XML, hiding summary', 0, 1) WITH NOWAIT; - SET @HideSummary = 1; - END; - -IF @StoredProcName IS NOT NULL - BEGIN - - DECLARE @sql NVARCHAR(MAX); - DECLARE @out INT; - DECLARE @proc_params NVARCHAR(MAX) = N'@sp_StartDate DATETIME2, @sp_EndDate DATETIME2, @sp_MinimumExecutionCount INT, @sp_MinDuration INT, @sp_StoredProcName NVARCHAR(128), @sp_PlanIdFilter INT, @sp_QueryIdFilter INT, @i_out INT OUTPUT'; - - - SET @sql = N'SELECT @i_out = COUNT(*) - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp - ON qsp.plan_id = qsrs.plan_id - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq - ON qsq.query_id = qsp.query_id - WHERE 1 = 1 - AND qsq.is_internal_query = 0 - AND qsp.query_plan IS NOT NULL - '; - - SET @sql += @sql_where; - - EXEC sys.sp_executesql @sql, - @proc_params, - @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter, @i_out = @out OUTPUT; - - IF @out = 0 - BEGIN - - SET @msg = N'We couldn''t find the Stored Procedure ' + QUOTENAME(@StoredProcName) + N' in the Query Store views for ' + QUOTENAME(@DatabaseName) + N' between ' + CONVERT(NVARCHAR(30), ISNULL(@StartDate, DATEADD(DAY, -7, DATEDIFF(DAY, 0, SYSDATETIME() ))) ) + N' and ' + CONVERT(NVARCHAR(30), ISNULL(@EndDate, SYSDATETIME())) + - '. Try removing schema prefixes or adjusting dates. If it was executed from a different database context, try searching there instead.'; - RAISERROR(@msg, 0, 1) WITH NOWAIT; - - SELECT @msg AS [Blue Flowers, Blue Flowers, Blue Flowers]; - - RETURN; - - END; - - END; - - - - -/* -This is our grouped interval query. - -By default, it looks at queries: - In the last 7 days - That aren't system queries - That have a query plan (some won't, if nested level is > 128, along with other reasons) - And haven't failed - This stuff, along with some other options, will be configurable in the stored proc - -*/ - -IF @sql_where IS NOT NULL -BEGIN TRY - BEGIN - - RAISERROR(N'Populating temp tables', 0, 1) WITH NOWAIT; - -RAISERROR(N'Gathering intervals', 0, 1) WITH NOWAIT; - -SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; -SET @sql_select += N' -SELECT CONVERT(DATE, qsrs.last_execution_time) AS flat_date, - MIN(DATEADD(HOUR, DATEDIFF(HOUR, 0, qsrs.last_execution_time), 0)) AS start_range, - MAX(DATEADD(HOUR, DATEDIFF(HOUR, 0, qsrs.last_execution_time) + 1, 0)) AS end_range, - SUM(qsrs.avg_duration / 1000.) / SUM(qsrs.count_executions) AS total_avg_duration_ms, - SUM(qsrs.avg_cpu_time / 1000.) / SUM(qsrs.count_executions) AS total_avg_cpu_time_ms, - SUM((qsrs.avg_logical_io_reads * 8 ) / 1024.) / SUM(qsrs.count_executions) AS total_avg_logical_io_reads_mb, - SUM((qsrs.avg_physical_io_reads* 8 ) / 1024.) / SUM(qsrs.count_executions) AS total_avg_physical_io_reads_mb, - SUM((qsrs.avg_logical_io_writes* 8 ) / 1024.) / SUM(qsrs.count_executions) AS total_avg_logical_io_writes_mb, - SUM((qsrs.avg_query_max_used_memory * 8 ) / 1024.) / SUM(qsrs.count_executions) AS total_avg_query_max_used_memory_mb, - SUM(qsrs.avg_rowcount) AS total_rowcount, - SUM(qsrs.count_executions) AS total_count_executions, - SUM(qsrs.max_duration / 1000.) AS total_max_duration_ms, - SUM(qsrs.max_cpu_time / 1000.) AS total_max_cpu_time_ms, - SUM((qsrs.max_logical_io_reads * 8 ) / 1024.) AS total_max_logical_io_reads_mb, - SUM((qsrs.max_physical_io_reads* 8 ) / 1024.) AS total_max_physical_io_reads_mb, - SUM((qsrs.max_logical_io_writes* 8 ) / 1024.) AS total_max_logical_io_writes_mb, - SUM((qsrs.max_query_max_used_memory * 8 ) / 1024.) AS total_max_query_max_used_memory_mb '; - IF @new_columns = 1 - BEGIN - SET @sql_select += N', - SUM((qsrs.avg_log_bytes_used) / 1048576.) / SUM(qsrs.count_executions) AS total_avg_log_bytes_mb, - SUM(qsrs.avg_tempdb_space_used) / SUM(qsrs.count_executions) AS total_avg_tempdb_space, - SUM((qsrs.max_log_bytes_used) / 1048576.) AS total_max_log_bytes_mb, - SUM(qsrs.max_tempdb_space_used) AS total_max_tempdb_space - '; - END; - IF @new_columns = 0 - BEGIN - SET @sql_select += N', - NULL AS total_avg_log_bytes_mb, - NULL AS total_avg_tempdb_space, - NULL AS total_max_log_bytes_mb, - NULL AS total_max_tempdb_space - '; - END; - - -SET @sql_select += N'FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp - ON qsp.plan_id = qsrs.plan_id - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq - ON qsq.query_id = qsp.query_id - WHERE 1 = 1 - AND qsq.is_internal_query = 0 - AND qsp.query_plan IS NOT NULL - '; - - -SET @sql_select += @sql_where; - -SET @sql_select += - N'GROUP BY CONVERT(DATE, qsrs.last_execution_time) - OPTION (RECOMPILE); - '; - -IF @Debug = 1 - PRINT @sql_select; - -IF @sql_select IS NULL - BEGIN - RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; - RETURN; - END; - -INSERT #grouped_interval WITH (TABLOCK) - ( flat_date, start_range, end_range, total_avg_duration_ms, - total_avg_cpu_time_ms, total_avg_logical_io_reads_mb, total_avg_physical_io_reads_mb, - total_avg_logical_io_writes_mb, total_avg_query_max_used_memory_mb, total_rowcount, - total_count_executions, total_max_duration_ms, total_max_cpu_time_ms, total_max_logical_io_reads_mb, - total_max_physical_io_reads_mb, total_max_logical_io_writes_mb, total_max_query_max_used_memory_mb, - total_avg_log_bytes_mb, total_avg_tempdb_space, total_max_log_bytes_mb, total_max_tempdb_space ) - -EXEC sys.sp_executesql @stmt = @sql_select, - @params = @sp_params, - @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; - - -/* -The next group of queries looks at plans in the ranges we found in the grouped interval query - -We take the highest value from each metric (duration, cpu, etc) and find the top plans by that metric in the range - -They insert into the #working_plans table -*/ - - - -/*Get longest duration plans*/ - -RAISERROR(N'Gathering longest duration plans', 0, 1) WITH NOWAIT; - -SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; -SET @sql_select += N' -WITH duration_max -AS ( SELECT TOP 1 - gi.start_range, - gi.end_range - FROM #grouped_interval AS gi - ORDER BY gi.total_avg_duration_ms DESC ) -INSERT #working_plans WITH (TABLOCK) - ( plan_id, query_id, pattern ) -SELECT TOP ( @sp_Top ) - qsp.plan_id, qsp.query_id, ''avg duration'' -FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp -JOIN duration_max AS dm -ON qsp.last_execution_time >= dm.start_range - AND qsp.last_execution_time < dm.end_range -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs -ON qsrs.plan_id = qsp.plan_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq -ON qsq.query_id = qsp.query_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt -ON qsqt.query_text_id = qsq.query_text_id -WHERE 1 = 1 - AND qsq.is_internal_query = 0 - AND qsp.query_plan IS NOT NULL - '; - -SET @sql_select += @sql_where; - -SET @sql_select += N'ORDER BY qsrs.avg_duration DESC - OPTION (RECOMPILE); - '; - -IF @Debug = 1 - PRINT @sql_select; - -IF @sql_select IS NULL - BEGIN - RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; - RETURN; - END; - -EXEC sys.sp_executesql @stmt = @sql_select, - @params = @sp_params, - @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; - - -SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; -SET @sql_select += N' -WITH duration_max -AS ( SELECT TOP 1 - gi.start_range, - gi.end_range - FROM #grouped_interval AS gi - ORDER BY gi.total_max_duration_ms DESC ) -INSERT #working_plans WITH (TABLOCK) - ( plan_id, query_id, pattern ) -SELECT TOP ( @sp_Top ) - qsp.plan_id, qsp.query_id, ''max duration'' -FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp -JOIN duration_max AS dm -ON qsp.last_execution_time >= dm.start_range - AND qsp.last_execution_time < dm.end_range -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs -ON qsrs.plan_id = qsp.plan_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq -ON qsq.query_id = qsp.query_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt -ON qsqt.query_text_id = qsq.query_text_id -WHERE 1 = 1 - AND qsq.is_internal_query = 0 - AND qsp.query_plan IS NOT NULL - '; - -SET @sql_select += @sql_where; - -SET @sql_select += N'ORDER BY qsrs.max_duration DESC - OPTION (RECOMPILE); - '; - -IF @Debug = 1 - PRINT @sql_select; - -IF @sql_select IS NULL - BEGIN - RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; - RETURN; - END; - -EXEC sys.sp_executesql @stmt = @sql_select, - @params = @sp_params, - @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; - - -/*Get longest cpu plans*/ - -RAISERROR(N'Gathering highest cpu plans', 0, 1) WITH NOWAIT; - -SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; -SET @sql_select += N' -WITH cpu_max -AS ( SELECT TOP 1 - gi.start_range, - gi.end_range - FROM #grouped_interval AS gi - ORDER BY gi.total_avg_cpu_time_ms DESC ) -INSERT #working_plans WITH (TABLOCK) - ( plan_id, query_id, pattern ) -SELECT TOP ( @sp_Top ) - qsp.plan_id, qsp.query_id, ''avg cpu'' -FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp -JOIN cpu_max AS dm -ON qsp.last_execution_time >= dm.start_range - AND qsp.last_execution_time < dm.end_range -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs -ON qsrs.plan_id = qsp.plan_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq -ON qsq.query_id = qsp.query_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt -ON qsqt.query_text_id = qsq.query_text_id -WHERE 1 = 1 - AND qsq.is_internal_query = 0 - AND qsp.query_plan IS NOT NULL - '; - -SET @sql_select += @sql_where; - -SET @sql_select += N'ORDER BY qsrs.avg_cpu_time DESC - OPTION (RECOMPILE); - '; - -IF @Debug = 1 - PRINT @sql_select; - -IF @sql_select IS NULL - BEGIN - RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; - RETURN; - END; - -EXEC sys.sp_executesql @stmt = @sql_select, - @params = @sp_params, - @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; - - -SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; -SET @sql_select += N' -WITH cpu_max -AS ( SELECT TOP 1 - gi.start_range, - gi.end_range - FROM #grouped_interval AS gi - ORDER BY gi.total_max_cpu_time_ms DESC ) -INSERT #working_plans WITH (TABLOCK) - ( plan_id, query_id, pattern ) -SELECT TOP ( @sp_Top ) - qsp.plan_id, qsp.query_id, ''max cpu'' -FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp -JOIN cpu_max AS dm -ON qsp.last_execution_time >= dm.start_range - AND qsp.last_execution_time < dm.end_range -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs -ON qsrs.plan_id = qsp.plan_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq -ON qsq.query_id = qsp.query_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt -ON qsqt.query_text_id = qsq.query_text_id -WHERE 1 = 1 - AND qsq.is_internal_query = 0 - AND qsp.query_plan IS NOT NULL - '; - -SET @sql_select += @sql_where; - -SET @sql_select += N'ORDER BY qsrs.max_cpu_time DESC - OPTION (RECOMPILE); - '; - -IF @Debug = 1 - PRINT @sql_select; - -IF @sql_select IS NULL - BEGIN - RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; - RETURN; - END; - -EXEC sys.sp_executesql @stmt = @sql_select, - @params = @sp_params, - @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; - - -/*Get highest logical read plans*/ - -RAISERROR(N'Gathering highest logical read plans', 0, 1) WITH NOWAIT; - -SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; -SET @sql_select += N' -WITH logical_reads_max -AS ( SELECT TOP 1 - gi.start_range, - gi.end_range - FROM #grouped_interval AS gi - ORDER BY gi.total_avg_logical_io_reads_mb DESC ) -INSERT #working_plans WITH (TABLOCK) - ( plan_id, query_id, pattern ) -SELECT TOP ( @sp_Top ) - qsp.plan_id, qsp.query_id, ''avg logical reads'' -FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp -JOIN logical_reads_max AS dm -ON qsp.last_execution_time >= dm.start_range - AND qsp.last_execution_time < dm.end_range -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs -ON qsrs.plan_id = qsp.plan_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq -ON qsq.query_id = qsp.query_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt -ON qsqt.query_text_id = qsq.query_text_id -WHERE 1 = 1 - AND qsq.is_internal_query = 0 - AND qsp.query_plan IS NOT NULL - '; - -SET @sql_select += @sql_where; - -SET @sql_select += N'ORDER BY qsrs.avg_logical_io_reads DESC - OPTION (RECOMPILE); - '; - -IF @Debug = 1 - PRINT @sql_select; - -IF @sql_select IS NULL - BEGIN - RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; - RETURN; - END; - -EXEC sys.sp_executesql @stmt = @sql_select, - @params = @sp_params, - @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; - - -SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; -SET @sql_select += N' -WITH logical_reads_max -AS ( SELECT TOP 1 - gi.start_range, - gi.end_range - FROM #grouped_interval AS gi - ORDER BY gi.total_max_logical_io_reads_mb DESC ) -INSERT #working_plans WITH (TABLOCK) - ( plan_id, query_id, pattern ) -SELECT TOP ( @sp_Top ) - qsp.plan_id, qsp.query_id, ''max logical reads'' -FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp -JOIN logical_reads_max AS dm -ON qsp.last_execution_time >= dm.start_range - AND qsp.last_execution_time < dm.end_range -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs -ON qsrs.plan_id = qsp.plan_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq -ON qsq.query_id = qsp.query_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt -ON qsqt.query_text_id = qsq.query_text_id -WHERE 1 = 1 - AND qsq.is_internal_query = 0 - AND qsp.query_plan IS NOT NULL - '; - -SET @sql_select += @sql_where; - -SET @sql_select += N'ORDER BY qsrs.max_logical_io_reads DESC - OPTION (RECOMPILE); - '; - -IF @Debug = 1 - PRINT @sql_select; - -IF @sql_select IS NULL - BEGIN - RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; - RETURN; - END; - -EXEC sys.sp_executesql @stmt = @sql_select, - @params = @sp_params, - @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; - - -/*Get highest physical read plans*/ - -RAISERROR(N'Gathering highest physical read plans', 0, 1) WITH NOWAIT; - -SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; -SET @sql_select += N' -WITH physical_read_max -AS ( SELECT TOP 1 - gi.start_range, - gi.end_range - FROM #grouped_interval AS gi - ORDER BY gi.total_avg_physical_io_reads_mb DESC ) -INSERT #working_plans WITH (TABLOCK) - ( plan_id, query_id, pattern ) -SELECT TOP ( @sp_Top ) - qsp.plan_id, qsp.query_id, ''avg physical reads'' -FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp -JOIN physical_read_max AS dm -ON qsp.last_execution_time >= dm.start_range - AND qsp.last_execution_time < dm.end_range -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs -ON qsrs.plan_id = qsp.plan_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq -ON qsq.query_id = qsp.query_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt -ON qsqt.query_text_id = qsq.query_text_id -WHERE 1 = 1 - AND qsq.is_internal_query = 0 - AND qsp.query_plan IS NOT NULL - '; - -SET @sql_select += @sql_where; - -SET @sql_select += N'ORDER BY qsrs.avg_physical_io_reads DESC - OPTION (RECOMPILE); - '; - -IF @Debug = 1 - PRINT @sql_select; - -IF @sql_select IS NULL - BEGIN - RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; - RETURN; - END; - -EXEC sys.sp_executesql @stmt = @sql_select, - @params = @sp_params, - @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; - - -SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; -SET @sql_select += N' -WITH physical_read_max -AS ( SELECT TOP 1 - gi.start_range, - gi.end_range - FROM #grouped_interval AS gi - ORDER BY gi.total_max_physical_io_reads_mb DESC ) -INSERT #working_plans WITH (TABLOCK) - ( plan_id, query_id, pattern ) -SELECT TOP ( @sp_Top ) - qsp.plan_id, qsp.query_id, ''max physical reads'' -FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp -JOIN physical_read_max AS dm -ON qsp.last_execution_time >= dm.start_range - AND qsp.last_execution_time < dm.end_range -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs -ON qsrs.plan_id = qsp.plan_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq -ON qsq.query_id = qsp.query_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt -ON qsqt.query_text_id = qsq.query_text_id -WHERE 1 = 1 - AND qsq.is_internal_query = 0 - AND qsp.query_plan IS NOT NULL - '; - -SET @sql_select += @sql_where; - -SET @sql_select += N'ORDER BY qsrs.max_physical_io_reads DESC - OPTION (RECOMPILE); - '; - -IF @Debug = 1 - PRINT @sql_select; - -IF @sql_select IS NULL - BEGIN - RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; - RETURN; - END; - -EXEC sys.sp_executesql @stmt = @sql_select, - @params = @sp_params, - @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; - - -/*Get highest logical write plans*/ - -RAISERROR(N'Gathering highest write plans', 0, 1) WITH NOWAIT; - -SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; -SET @sql_select += N' -WITH logical_writes_max -AS ( SELECT TOP 1 - gi.start_range, - gi.end_range - FROM #grouped_interval AS gi - ORDER BY gi.total_avg_logical_io_writes_mb DESC ) -INSERT #working_plans WITH (TABLOCK) - ( plan_id, query_id, pattern ) -SELECT TOP ( @sp_Top ) - qsp.plan_id, qsp.query_id, ''avg writes'' -FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp -JOIN logical_writes_max AS dm -ON qsp.last_execution_time >= dm.start_range - AND qsp.last_execution_time < dm.end_range -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs -ON qsrs.plan_id = qsp.plan_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq -ON qsq.query_id = qsp.query_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt -ON qsqt.query_text_id = qsq.query_text_id -WHERE 1 = 1 - AND qsq.is_internal_query = 0 - AND qsp.query_plan IS NOT NULL - '; - -SET @sql_select += @sql_where; - -SET @sql_select += N'ORDER BY qsrs.avg_logical_io_writes DESC - OPTION (RECOMPILE); - '; - -IF @Debug = 1 - PRINT @sql_select; - -IF @sql_select IS NULL - BEGIN - RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; - RETURN; - END; - -EXEC sys.sp_executesql @stmt = @sql_select, - @params = @sp_params, - @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; - - -SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; -SET @sql_select += N' -WITH logical_writes_max -AS ( SELECT TOP 1 - gi.start_range, - gi.end_range - FROM #grouped_interval AS gi - ORDER BY gi.total_max_logical_io_writes_mb DESC ) -INSERT #working_plans WITH (TABLOCK) - ( plan_id, query_id, pattern ) -SELECT TOP ( @sp_Top ) - qsp.plan_id, qsp.query_id, ''max writes'' -FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp -JOIN logical_writes_max AS dm -ON qsp.last_execution_time >= dm.start_range - AND qsp.last_execution_time < dm.end_range -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs -ON qsrs.plan_id = qsp.plan_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq -ON qsq.query_id = qsp.query_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt -ON qsqt.query_text_id = qsq.query_text_id -WHERE 1 = 1 - AND qsq.is_internal_query = 0 - AND qsp.query_plan IS NOT NULL - '; - -SET @sql_select += @sql_where; - -SET @sql_select += N'ORDER BY qsrs.max_logical_io_writes DESC - OPTION (RECOMPILE); - '; - -IF @Debug = 1 - PRINT @sql_select; - -IF @sql_select IS NULL - BEGIN - RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; - RETURN; - END; - -EXEC sys.sp_executesql @stmt = @sql_select, - @params = @sp_params, - @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; - - -/*Get highest memory use plans*/ - -RAISERROR(N'Gathering highest memory use plans', 0, 1) WITH NOWAIT; - -SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; -SET @sql_select += N' -WITH memory_max -AS ( SELECT TOP 1 - gi.start_range, - gi.end_range - FROM #grouped_interval AS gi - ORDER BY gi.total_avg_query_max_used_memory_mb DESC ) -INSERT #working_plans WITH (TABLOCK) - ( plan_id, query_id, pattern ) -SELECT TOP ( @sp_Top ) - qsp.plan_id, qsp.query_id, ''avg memory'' -FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp -JOIN memory_max AS dm -ON qsp.last_execution_time >= dm.start_range - AND qsp.last_execution_time < dm.end_range -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs -ON qsrs.plan_id = qsp.plan_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq -ON qsq.query_id = qsp.query_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt -ON qsqt.query_text_id = qsq.query_text_id -WHERE 1 = 1 - AND qsq.is_internal_query = 0 - AND qsp.query_plan IS NOT NULL - '; - -SET @sql_select += @sql_where; - -SET @sql_select += N'ORDER BY qsrs.avg_query_max_used_memory DESC - OPTION (RECOMPILE); - '; - -IF @Debug = 1 - PRINT @sql_select; - -IF @sql_select IS NULL - BEGIN - RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; - RETURN; - END; - -EXEC sys.sp_executesql @stmt = @sql_select, - @params = @sp_params, - @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; - -SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; -SET @sql_select += N' -WITH memory_max -AS ( SELECT TOP 1 - gi.start_range, - gi.end_range - FROM #grouped_interval AS gi - ORDER BY gi.total_max_query_max_used_memory_mb DESC ) -INSERT #working_plans WITH (TABLOCK) - ( plan_id, query_id, pattern ) -SELECT TOP ( @sp_Top ) - qsp.plan_id, qsp.query_id, ''max memory'' -FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp -JOIN memory_max AS dm -ON qsp.last_execution_time >= dm.start_range - AND qsp.last_execution_time < dm.end_range -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs -ON qsrs.plan_id = qsp.plan_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq -ON qsq.query_id = qsp.query_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt -ON qsqt.query_text_id = qsq.query_text_id -WHERE 1 = 1 - AND qsq.is_internal_query = 0 - AND qsp.query_plan IS NOT NULL - '; - -SET @sql_select += @sql_where; - -SET @sql_select += N'ORDER BY qsrs.max_query_max_used_memory DESC - OPTION (RECOMPILE); - '; - -IF @Debug = 1 - PRINT @sql_select; - -IF @sql_select IS NULL - BEGIN - RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; - RETURN; - END; - -EXEC sys.sp_executesql @stmt = @sql_select, - @params = @sp_params, - @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; - - -/*Get highest row count plans*/ - -RAISERROR(N'Gathering highest row count plans', 0, 1) WITH NOWAIT; - -SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; -SET @sql_select += N' -WITH rowcount_max -AS ( SELECT TOP 1 - gi.start_range, - gi.end_range - FROM #grouped_interval AS gi - ORDER BY gi.total_rowcount DESC ) -INSERT #working_plans WITH (TABLOCK) - ( plan_id, query_id, pattern ) -SELECT TOP ( @sp_Top ) - qsp.plan_id, qsp.query_id, ''avg rows'' -FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp -JOIN rowcount_max AS dm -ON qsp.last_execution_time >= dm.start_range - AND qsp.last_execution_time < dm.end_range -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs -ON qsrs.plan_id = qsp.plan_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq -ON qsq.query_id = qsp.query_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt -ON qsqt.query_text_id = qsq.query_text_id -WHERE 1 = 1 - AND qsq.is_internal_query = 0 - AND qsp.query_plan IS NOT NULL - '; - -SET @sql_select += @sql_where; - -SET @sql_select += N'ORDER BY qsrs.avg_rowcount DESC - OPTION (RECOMPILE); - '; - -IF @Debug = 1 - PRINT @sql_select; - -IF @sql_select IS NULL - BEGIN - RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; - RETURN; - END; - -EXEC sys.sp_executesql @stmt = @sql_select, - @params = @sp_params, - @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; - - -IF @new_columns = 1 -BEGIN - -RAISERROR(N'Gathering new 2017 new column info...', 0, 1) WITH NOWAIT; - -/*Get highest log byte count plans*/ - -RAISERROR(N'Gathering highest log byte use plans', 0, 1) WITH NOWAIT; - -SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; -SET @sql_select += N' -WITH rowcount_max -AS ( SELECT TOP 1 - gi.start_range, - gi.end_range - FROM #grouped_interval AS gi - ORDER BY gi.total_avg_log_bytes_mb DESC ) -INSERT #working_plans WITH (TABLOCK) - ( plan_id, query_id, pattern ) -SELECT TOP ( @sp_Top ) - qsp.plan_id, qsp.query_id, ''avg log bytes'' -FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp -JOIN rowcount_max AS dm -ON qsp.last_execution_time >= dm.start_range - AND qsp.last_execution_time < dm.end_range -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs -ON qsrs.plan_id = qsp.plan_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq -ON qsq.query_id = qsp.query_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt -ON qsqt.query_text_id = qsq.query_text_id -WHERE 1 = 1 - AND qsq.is_internal_query = 0 - AND qsp.query_plan IS NOT NULL - '; - -SET @sql_select += @sql_where; - -SET @sql_select += N'ORDER BY qsrs.avg_log_bytes_used DESC - OPTION (RECOMPILE); - '; - -IF @Debug = 1 - PRINT @sql_select; - -IF @sql_select IS NULL - BEGIN - RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; - RETURN; - END; - -EXEC sys.sp_executesql @stmt = @sql_select, - @params = @sp_params, - @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; - -RAISERROR(N'Gathering highest log byte use plans', 0, 1) WITH NOWAIT; - -SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; -SET @sql_select += N' -WITH rowcount_max -AS ( SELECT TOP 1 - gi.start_range, - gi.end_range - FROM #grouped_interval AS gi - ORDER BY gi.total_max_log_bytes_mb DESC ) -INSERT #working_plans WITH (TABLOCK) - ( plan_id, query_id, pattern ) -SELECT TOP ( @sp_Top ) - qsp.plan_id, qsp.query_id, ''max log bytes'' -FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp -JOIN rowcount_max AS dm -ON qsp.last_execution_time >= dm.start_range - AND qsp.last_execution_time < dm.end_range -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs -ON qsrs.plan_id = qsp.plan_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq -ON qsq.query_id = qsp.query_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt -ON qsqt.query_text_id = qsq.query_text_id -WHERE 1 = 1 - AND qsq.is_internal_query = 0 - AND qsp.query_plan IS NOT NULL - '; - -SET @sql_select += @sql_where; - -SET @sql_select += N'ORDER BY qsrs.max_log_bytes_used DESC - OPTION (RECOMPILE); - '; - -IF @Debug = 1 - PRINT @sql_select; - -IF @sql_select IS NULL - BEGIN - RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; - RETURN; - END; - -EXEC sys.sp_executesql @stmt = @sql_select, - @params = @sp_params, - @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; - - -/*Get highest tempdb use plans*/ - -RAISERROR(N'Gathering highest tempdb use plans', 0, 1) WITH NOWAIT; - -SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; -SET @sql_select += N' -WITH rowcount_max -AS ( SELECT TOP 1 - gi.start_range, - gi.end_range - FROM #grouped_interval AS gi - ORDER BY gi.total_avg_tempdb_space DESC ) -INSERT #working_plans WITH (TABLOCK) - ( plan_id, query_id, pattern ) -SELECT TOP ( @sp_Top ) - qsp.plan_id, qsp.query_id, ''avg tempdb space'' -FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp -JOIN rowcount_max AS dm -ON qsp.last_execution_time >= dm.start_range - AND qsp.last_execution_time < dm.end_range -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs -ON qsrs.plan_id = qsp.plan_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq -ON qsq.query_id = qsp.query_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt -ON qsqt.query_text_id = qsq.query_text_id -WHERE 1 = 1 - AND qsq.is_internal_query = 0 - AND qsp.query_plan IS NOT NULL - '; - -SET @sql_select += @sql_where; - -SET @sql_select += N'ORDER BY qsrs.avg_tempdb_space_used DESC - OPTION (RECOMPILE); - '; - -IF @Debug = 1 - PRINT @sql_select; - -IF @sql_select IS NULL - BEGIN - RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; - RETURN; - END; - -EXEC sys.sp_executesql @stmt = @sql_select, - @params = @sp_params, - @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; - -SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; -SET @sql_select += N' -WITH rowcount_max -AS ( SELECT TOP 1 - gi.start_range, - gi.end_range - FROM #grouped_interval AS gi - ORDER BY gi.total_max_tempdb_space DESC ) -INSERT #working_plans WITH (TABLOCK) - ( plan_id, query_id, pattern ) -SELECT TOP ( @sp_Top ) - qsp.plan_id, qsp.query_id, ''max tempdb space'' -FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp -JOIN rowcount_max AS dm -ON qsp.last_execution_time >= dm.start_range - AND qsp.last_execution_time < dm.end_range -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs -ON qsrs.plan_id = qsp.plan_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq -ON qsq.query_id = qsp.query_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt -ON qsqt.query_text_id = qsq.query_text_id -WHERE 1 = 1 - AND qsq.is_internal_query = 0 - AND qsp.query_plan IS NOT NULL - '; - -SET @sql_select += @sql_where; - -SET @sql_select += N'ORDER BY qsrs.max_tempdb_space_used DESC - OPTION (RECOMPILE); - '; - -IF @Debug = 1 - PRINT @sql_select; - -IF @sql_select IS NULL - BEGIN - RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; - RETURN; - END; - -EXEC sys.sp_executesql @stmt = @sql_select, - @params = @sp_params, - @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; - - -END; - - -/* -This rolls up the different patterns we find before deduplicating. - -The point of this is so we know if a query was gathered by one or more of the search queries - -*/ - -RAISERROR(N'Updating patterns', 0, 1) WITH NOWAIT; - -WITH patterns AS ( -SELECT wp.plan_id, wp.query_id, - pattern_path = STUFF((SELECT DISTINCT N', ' + wp2.pattern - FROM #working_plans AS wp2 - WHERE wp.plan_id = wp2.plan_id - AND wp.query_id = wp2.query_id - FOR XML PATH(N''), TYPE).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 2, N'') -FROM #working_plans AS wp -) -UPDATE wp -SET wp.pattern = patterns.pattern_path -FROM #working_plans AS wp -JOIN patterns -ON wp.plan_id = patterns.plan_id -AND wp.query_id = patterns.query_id -OPTION (RECOMPILE); - - -/* -This dedupes our results so we hopefully don't double-work the same plan -*/ - -RAISERROR(N'Deduplicating gathered plans', 0, 1) WITH NOWAIT; - -WITH dedupe AS ( -SELECT * , ROW_NUMBER() OVER (PARTITION BY wp.plan_id ORDER BY wp.plan_id) AS dupes -FROM #working_plans AS wp -) -DELETE dedupe -WHERE dedupe.dupes > 1 -OPTION (RECOMPILE); - -SET @msg = N'Removed ' + CONVERT(NVARCHAR(10), @@ROWCOUNT) + N' duplicate plan_ids.'; -RAISERROR(@msg, 0, 1) WITH NOWAIT; - - -/* -This gathers data for the #working_metrics table -*/ - - -RAISERROR(N'Collecting worker metrics', 0, 1) WITH NOWAIT; - -SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; -SET @sql_select += N' -SELECT ' + QUOTENAME(@DatabaseName, '''') + N' AS database_name, wp.plan_id, wp.query_id, - QUOTENAME(object_schema_name(qsq.object_id, DB_ID(' + QUOTENAME(@DatabaseName, '''') + N'))) + ''.'' + - QUOTENAME(object_name(qsq.object_id, DB_ID(' + QUOTENAME(@DatabaseName, '''') + N'))) AS proc_or_function_name, - qsq.batch_sql_handle, qsq.query_hash, qsq.query_parameterization_type_desc, qsq.count_compiles, - (qsq.avg_compile_duration / 1000.), - (qsq.last_compile_duration / 1000.), - (qsq.avg_bind_duration / 1000.), - (qsq.last_bind_duration / 1000.), - (qsq.avg_bind_cpu_time / 1000.), - (qsq.last_bind_cpu_time / 1000.), - (qsq.avg_optimize_duration / 1000.), - (qsq.last_optimize_duration / 1000.), - (qsq.avg_optimize_cpu_time / 1000.), - (qsq.last_optimize_cpu_time / 1000.), - (qsq.avg_compile_memory_kb / 1024.), - (qsq.last_compile_memory_kb / 1024.), - qsrs.execution_type_desc, qsrs.first_execution_time, qsrs.last_execution_time, qsrs.count_executions, - (qsrs.avg_duration / 1000.), - (qsrs.last_duration / 1000.), - (qsrs.min_duration / 1000.), - (qsrs.max_duration / 1000.), - (qsrs.avg_cpu_time / 1000.), - (qsrs.last_cpu_time / 1000.), - (qsrs.min_cpu_time / 1000.), - (qsrs.max_cpu_time / 1000.), - ((qsrs.avg_logical_io_reads * 8 ) / 1024.), - ((qsrs.last_logical_io_reads * 8 ) / 1024.), - ((qsrs.min_logical_io_reads * 8 ) / 1024.), - ((qsrs.max_logical_io_reads * 8 ) / 1024.), - ((qsrs.avg_logical_io_writes * 8 ) / 1024.), - ((qsrs.last_logical_io_writes * 8 ) / 1024.), - ((qsrs.min_logical_io_writes * 8 ) / 1024.), - ((qsrs.max_logical_io_writes * 8 ) / 1024.), - ((qsrs.avg_physical_io_reads * 8 ) / 1024.), - ((qsrs.last_physical_io_reads * 8 ) / 1024.), - ((qsrs.min_physical_io_reads * 8 ) / 1024.), - ((qsrs.max_physical_io_reads * 8 ) / 1024.), - (qsrs.avg_clr_time / 1000.), - (qsrs.last_clr_time / 1000.), - (qsrs.min_clr_time / 1000.), - (qsrs.max_clr_time / 1000.), - qsrs.avg_dop, qsrs.last_dop, qsrs.min_dop, qsrs.max_dop, - ((qsrs.avg_query_max_used_memory * 8 ) / 1024.), - ((qsrs.last_query_max_used_memory * 8 ) / 1024.), - ((qsrs.min_query_max_used_memory * 8 ) / 1024.), - ((qsrs.max_query_max_used_memory * 8 ) / 1024.), - qsrs.avg_rowcount, qsrs.last_rowcount, qsrs.min_rowcount, qsrs.max_rowcount,'; - - IF @new_columns = 1 - BEGIN - SET @sql_select += N' - qsrs.avg_num_physical_io_reads, qsrs.last_num_physical_io_reads, qsrs.min_num_physical_io_reads, qsrs.max_num_physical_io_reads, - (qsrs.avg_log_bytes_used / 100000000), - (qsrs.last_log_bytes_used / 100000000), - (qsrs.min_log_bytes_used / 100000000), - (qsrs.max_log_bytes_used / 100000000), - ((qsrs.avg_tempdb_space_used * 8 ) / 1024.), - ((qsrs.last_tempdb_space_used * 8 ) / 1024.), - ((qsrs.min_tempdb_space_used * 8 ) / 1024.), - ((qsrs.max_tempdb_space_used * 8 ) / 1024.) - '; - END; - IF @new_columns = 0 - BEGIN - SET @sql_select += N' - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL - '; - END; -SET @sql_select += -N'FROM #working_plans AS wp -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq -ON wp.query_id = qsq.query_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs -ON qsrs.plan_id = wp.plan_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp -ON qsp.plan_id = wp.plan_id -AND qsp.query_id = wp.query_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt -ON qsqt.query_text_id = qsq.query_text_id -WHERE 1 = 1 - AND qsq.is_internal_query = 0 - AND qsp.query_plan IS NOT NULL - '; - -SET @sql_select += @sql_where; - -SET @sql_select += N'OPTION (RECOMPILE); - '; - -IF @Debug = 1 - PRINT @sql_select; - -IF @sql_select IS NULL - BEGIN - RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; - RETURN; - END; - -INSERT #working_metrics WITH (TABLOCK) - ( database_name, plan_id, query_id, - proc_or_function_name, - batch_sql_handle, query_hash, query_parameterization_type_desc, count_compiles, - avg_compile_duration, last_compile_duration, avg_bind_duration, last_bind_duration, avg_bind_cpu_time, last_bind_cpu_time, avg_optimize_duration, - last_optimize_duration, avg_optimize_cpu_time, last_optimize_cpu_time, avg_compile_memory_kb, last_compile_memory_kb, execution_type_desc, - first_execution_time, last_execution_time, count_executions, avg_duration, last_duration, min_duration, max_duration, avg_cpu_time, last_cpu_time, - min_cpu_time, max_cpu_time, avg_logical_io_reads, last_logical_io_reads, min_logical_io_reads, max_logical_io_reads, avg_logical_io_writes, - last_logical_io_writes, min_logical_io_writes, max_logical_io_writes, avg_physical_io_reads, last_physical_io_reads, min_physical_io_reads, - max_physical_io_reads, avg_clr_time, last_clr_time, min_clr_time, max_clr_time, avg_dop, last_dop, min_dop, max_dop, avg_query_max_used_memory, - last_query_max_used_memory, min_query_max_used_memory, max_query_max_used_memory, avg_rowcount, last_rowcount, min_rowcount, max_rowcount, - /* 2017 only columns */ - avg_num_physical_io_reads, last_num_physical_io_reads, min_num_physical_io_reads, max_num_physical_io_reads, - avg_log_bytes_used, last_log_bytes_used, min_log_bytes_used, max_log_bytes_used, - avg_tempdb_space_used, last_tempdb_space_used, min_tempdb_space_used, max_tempdb_space_used ) - -EXEC sys.sp_executesql @stmt = @sql_select, - @params = @sp_params, - @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; - - -/*If PSPO is enabled, get procedure names for variant queries.*/ -IF (@pspo_enabled = 1) -BEGIN - DECLARE - @pspo_names NVARCHAR(MAX) = ''; - - SET @pspo_names = - 'UPDATE wm - SET - wm.proc_or_function_name = - QUOTENAME(object_schema_name(qsq.object_id, DB_ID(' + QUOTENAME(@DatabaseName, '''') + N'))) + ''.'' + - QUOTENAME(object_name(qsq.object_id, DB_ID(' + QUOTENAME(@DatabaseName, '''') + N'))) - FROM #working_metrics wm - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_variant AS vr - ON vr.query_variant_query_id = wm.query_id - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq - ON qsq.query_id = vr.parent_query_id - AND qsq.object_id > 0 - WHERE - wm.proc_or_function_name IS NULL;' - - EXEC sys.sp_executesql @pspo_names; -END; - - -/*This just helps us classify our queries*/ -UPDATE #working_metrics -SET proc_or_function_name = N'Statement' -WHERE proc_or_function_name IS NULL -OPTION(RECOMPILE); - -SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; -SET @sql_select += N' - WITH patterns AS ( - SELECT query_id, planid_path = STUFF((SELECT DISTINCT N'', '' + RTRIM(qsp2.plan_id) - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp2 - WHERE qsp.query_id = qsp2.query_id - FOR XML PATH(N''''), TYPE).value(N''.[1]'', N''NVARCHAR(MAX)''), 1, 2, N'''') - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp - ) - UPDATE wm - SET wm.query_id_all_plan_ids = patterns.planid_path - FROM #working_metrics AS wm - JOIN patterns - ON wm.query_id = patterns.query_id - OPTION (RECOMPILE); -' - -EXEC sys.sp_executesql @stmt = @sql_select; - -/* -This gathers data for the #working_plan_text table -*/ - - -RAISERROR(N'Gathering working plans', 0, 1) WITH NOWAIT; - -SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; -SET @sql_select += N' -SELECT ' + QUOTENAME(@DatabaseName, '''') + N' AS database_name, wp.plan_id, wp.query_id, - qsp.plan_group_id, qsp.engine_version, qsp.compatibility_level, qsp.query_plan_hash, TRY_CAST(qsp.query_plan AS XML), qsp.is_online_index_plan, qsp.is_trivial_plan, - qsp.is_parallel_plan, qsp.is_forced_plan, qsp.is_natively_compiled, qsp.force_failure_count, qsp.last_force_failure_reason_desc, qsp.count_compiles, - qsp.initial_compile_start_time, qsp.last_compile_start_time, qsp.last_execution_time, - (qsp.avg_compile_duration / 1000.), - (qsp.last_compile_duration / 1000.), - qsqt.query_sql_text, qsqt.statement_sql_handle, qsqt.is_part_of_encrypted_module, qsqt.has_restricted_text -FROM #working_plans AS wp -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp -ON qsp.plan_id = wp.plan_id - AND qsp.query_id = wp.query_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq -ON wp.query_id = qsq.query_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt -ON qsqt.query_text_id = qsq.query_text_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs -ON qsrs.plan_id = wp.plan_id -WHERE 1 = 1 - AND qsq.is_internal_query = 0 - AND qsp.query_plan IS NOT NULL - '; - -SET @sql_select += @sql_where; - -SET @sql_select += N'OPTION (RECOMPILE); - '; - -IF @Debug = 1 - PRINT @sql_select; - -IF @sql_select IS NULL - BEGIN - RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; - RETURN; - END; - -INSERT #working_plan_text WITH (TABLOCK) - ( database_name, plan_id, query_id, - plan_group_id, engine_version, compatibility_level, query_plan_hash, query_plan_xml, is_online_index_plan, is_trivial_plan, - is_parallel_plan, is_forced_plan, is_natively_compiled, force_failure_count, last_force_failure_reason_desc, count_compiles, - initial_compile_start_time, last_compile_start_time, last_execution_time, avg_compile_duration, last_compile_duration, - query_sql_text, statement_sql_handle, is_part_of_encrypted_module, has_restricted_text ) - -EXEC sys.sp_executesql @stmt = @sql_select, - @params = @sp_params, - @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; - - - -/* -This gets us context settings for our queries and adds it to the #working_plan_text table -*/ - -RAISERROR(N'Gathering context settings', 0, 1) WITH NOWAIT; - -SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; -SET @sql_select += N' -UPDATE wp -SET wp.context_settings = SUBSTRING( - CASE WHEN (CAST(qcs.set_options AS INT) & 1 = 1) THEN '', ANSI_PADDING'' ELSE '''' END + - CASE WHEN (CAST(qcs.set_options AS INT) & 8 = 8) THEN '', CONCAT_NULL_YIELDS_NULL'' ELSE '''' END + - CASE WHEN (CAST(qcs.set_options AS INT) & 16 = 16) THEN '', ANSI_WARNINGS'' ELSE '''' END + - CASE WHEN (CAST(qcs.set_options AS INT) & 32 = 32) THEN '', ANSI_NULLS'' ELSE '''' END + - CASE WHEN (CAST(qcs.set_options AS INT) & 64 = 64) THEN '', QUOTED_IDENTIFIER'' ELSE '''' END + - CASE WHEN (CAST(qcs.set_options AS INT) & 4096 = 4096) THEN '', ARITH_ABORT'' ELSE '''' END + - CASE WHEN (CAST(qcs.set_options AS INT) & 8192 = 8192) THEN '', NUMERIC_ROUNDABORT'' ELSE '''' END - , 2, 200000) -FROM #working_plan_text wp -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq -ON wp.query_id = qsq.query_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_context_settings AS qcs -ON qcs.context_settings_id = qsq.context_settings_id -OPTION (RECOMPILE); -'; - -IF @Debug = 1 - PRINT @sql_select; - -IF @sql_select IS NULL - BEGIN - RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; - RETURN; - END; - -EXEC sys.sp_executesql @stmt = @sql_select; - - -/*This adds the patterns we found from each interval to the #working_plan_text table*/ - -RAISERROR(N'Add patterns to working plans', 0, 1) WITH NOWAIT; - -UPDATE wpt -SET wpt.pattern = wp.pattern -FROM #working_plans AS wp -JOIN #working_plan_text AS wpt -ON wpt.plan_id = wp.plan_id -AND wpt.query_id = wp.query_id -OPTION (RECOMPILE); - -/*This cleans up query text a bit*/ - -RAISERROR(N'Clean awkward characters from query text', 0, 1) WITH NOWAIT; - -UPDATE b -SET b.query_sql_text = REPLACE(REPLACE(REPLACE(b.query_sql_text, @cr, ' '), @lf, ' '), @tab, ' ') -FROM #working_plan_text AS b -OPTION (RECOMPILE); - - -/*This populates #working_wait_stats when available*/ - -IF @waitstats = 1 - - BEGIN - - RAISERROR(N'Collecting wait stats info', 0, 1) WITH NOWAIT; - - - SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; - SET @sql_select += N' - SELECT qws.plan_id, - qws.wait_category, - qws.wait_category_desc, - SUM(qws.total_query_wait_time_ms) AS total_query_wait_time_ms, - SUM(qws.avg_query_wait_time_ms) AS avg_query_wait_time_ms, - SUM(qws.last_query_wait_time_ms) AS last_query_wait_time_ms, - SUM(qws.min_query_wait_time_ms) AS min_query_wait_time_ms, - SUM(qws.max_query_wait_time_ms) AS max_query_wait_time_ms - FROM ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_wait_stats qws - JOIN #working_plans AS wp - ON qws.plan_id = wp.plan_id - GROUP BY qws.plan_id, qws.wait_category, qws.wait_category_desc - HAVING SUM(qws.min_query_wait_time_ms) >= 5 - OPTION (RECOMPILE); - '; - - IF @Debug = 1 - PRINT @sql_select; - - IF @sql_select IS NULL - BEGIN - RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; - RETURN; - END; - - INSERT #working_wait_stats WITH (TABLOCK) - ( plan_id, wait_category, wait_category_desc, total_query_wait_time_ms, avg_query_wait_time_ms, last_query_wait_time_ms, min_query_wait_time_ms, max_query_wait_time_ms ) - - EXEC sys.sp_executesql @stmt = @sql_select; - - - /*This updates #working_plan_text with the top three waits from the wait stats DMV*/ - - RAISERROR(N'Update working_plan_text with top three waits', 0, 1) WITH NOWAIT; - - - UPDATE wpt - SET wpt.top_three_waits = x.top_three_waits - FROM #working_plan_text AS wpt - JOIN ( - SELECT wws.plan_id, - top_three_waits = STUFF((SELECT TOP 3 N', ' + wws2.wait_category_desc + N' (' + CONVERT(NVARCHAR(20), SUM(CONVERT(BIGINT, wws2.avg_query_wait_time_ms))) + N' ms) ' - FROM #working_wait_stats AS wws2 - WHERE wws.plan_id = wws2.plan_id - GROUP BY wws2.wait_category_desc - ORDER BY SUM(wws2.avg_query_wait_time_ms) DESC - FOR XML PATH(N''), TYPE).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 2, N'') - FROM #working_wait_stats AS wws - GROUP BY wws.plan_id - ) AS x - ON x.plan_id = wpt.plan_id - OPTION (RECOMPILE); - -END; - -/*End wait stats population*/ - -UPDATE #working_plan_text -SET top_three_waits = CASE - WHEN @waitstats = 0 - THEN N'The query store waits stats DMV is not available' - ELSE N'No Significant waits detected!' - END -WHERE top_three_waits IS NULL -OPTION(RECOMPILE); - -END; -END TRY -BEGIN CATCH - RAISERROR (N'Failure populating temp tables.', 0,1) WITH NOWAIT; - - IF @sql_select IS NOT NULL - BEGIN - SET @msg = N'Last @sql_select: ' + @sql_select; - RAISERROR(@msg, 0, 1) WITH NOWAIT; - END; - - SELECT @msg = @DatabaseName + N' database failed to process. ' + ERROR_MESSAGE(), @error_severity = ERROR_SEVERITY(), @error_state = ERROR_STATE(); - RAISERROR (@msg, @error_severity, @error_state) WITH NOWAIT; - - - WHILE @@TRANCOUNT > 0 - ROLLBACK; - - RETURN; -END CATCH; - -IF (@SkipXML = 0) -BEGIN TRY -BEGIN - -/* -This sets up the #working_warnings table with the IDs we're interested in so we can tie warnings back to them -*/ - -RAISERROR(N'Populate working warnings table with gathered plans', 0, 1) WITH NOWAIT; - - -SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; -SET @sql_select += N' -SELECT DISTINCT wp.plan_id, wp.query_id, qsq.query_hash, qsqt.statement_sql_handle -FROM #working_plans AS wp -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp -ON qsp.plan_id = wp.plan_id - AND qsp.query_id = wp.query_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq -ON wp.query_id = qsq.query_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt -ON qsqt.query_text_id = qsq.query_text_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs -ON qsrs.plan_id = wp.plan_id -WHERE 1 = 1 - AND qsq.is_internal_query = 0 - AND qsp.query_plan IS NOT NULL - '; - -SET @sql_select += @sql_where; - -SET @sql_select += N'OPTION (RECOMPILE); - '; - -IF @Debug = 1 - PRINT @sql_select; - -IF @sql_select IS NULL - BEGIN - RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; - RETURN; - END; - -INSERT #working_warnings WITH (TABLOCK) - ( plan_id, query_id, query_hash, sql_handle ) -EXEC sys.sp_executesql @stmt = @sql_select, - @params = @sp_params, - @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; - -/* -This looks for queries in the query stores that we picked up from an internal that have multiple plans in cache - -This and several of the following queries all replaced XML parsing to find plan attributes. Sweet. - -Thanks, Query Store -*/ - -RAISERROR(N'Populating object name in #working_warnings', 0, 1) WITH NOWAIT; -UPDATE w -SET w.proc_or_function_name = ISNULL(wm.proc_or_function_name, N'Statement') -FROM #working_warnings AS w -JOIN #working_metrics AS wm -ON w.plan_id = wm.plan_id - AND w.query_id = wm.query_id -OPTION (RECOMPILE); - - -RAISERROR(N'Checking for multiple plans', 0, 1) WITH NOWAIT; - -SET @sql_select = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;'; -SET @sql_select += N' -UPDATE ww -SET ww.plan_multiple_plans = 1 -FROM #working_warnings AS ww -JOIN -( -SELECT wp.query_id, COUNT(qsp.plan_id) AS plans -FROM #working_plans AS wp -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_plan AS qsp -ON qsp.plan_id = wp.plan_id - AND qsp.query_id = wp.query_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query AS qsq -ON wp.query_id = qsq.query_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_query_text AS qsqt -ON qsqt.query_text_id = qsq.query_text_id -JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.query_store_runtime_stats AS qsrs -ON qsrs.plan_id = wp.plan_id -WHERE 1 = 1 - AND qsq.is_internal_query = 0 - AND qsp.query_plan IS NOT NULL - '; - -SET @sql_select += @sql_where; - -SET @sql_select += -N'GROUP BY wp.query_id - HAVING COUNT(qsp.plan_id) > 1 -) AS x - ON ww.query_id = x.query_id -OPTION (RECOMPILE); -'; - -IF @Debug = 1 - PRINT @sql_select; - -IF @sql_select IS NULL - BEGIN - RAISERROR(N'@sql_select is NULL', 0, 1) WITH NOWAIT; - RETURN; - END; - -EXEC sys.sp_executesql @stmt = @sql_select, - @params = @sp_params, - @sp_Top = @Top, @sp_StartDate = @StartDate, @sp_EndDate = @EndDate, @sp_MinimumExecutionCount = @MinimumExecutionCount, @sp_MinDuration = @duration_filter_ms, @sp_StoredProcName = @StoredProcName, @sp_PlanIdFilter = @PlanIdFilter, @sp_QueryIdFilter = @QueryIdFilter; - -/* -This looks for forced plans -*/ - -RAISERROR(N'Checking for forced plans', 0, 1) WITH NOWAIT; - -UPDATE ww -SET ww.is_forced_plan = 1 -FROM #working_warnings AS ww -JOIN #working_plan_text AS wp -ON ww.plan_id = wp.plan_id - AND ww.query_id = wp.query_id - AND wp.is_forced_plan = 1 -OPTION (RECOMPILE); - - -/* -This looks for forced parameterization -*/ - -RAISERROR(N'Checking for forced parameterization', 0, 1) WITH NOWAIT; - -UPDATE ww -SET ww.is_forced_parameterized = 1 -FROM #working_warnings AS ww -JOIN #working_metrics AS wm -ON ww.plan_id = wm.plan_id - AND ww.query_id = wm.query_id - AND wm.query_parameterization_type_desc = 'Forced' -OPTION (RECOMPILE); - - -/* -This looks for unparameterized queries -*/ - -RAISERROR(N'Checking for unparameterized plans', 0, 1) WITH NOWAIT; - -UPDATE ww -SET ww.unparameterized_query = 1 -FROM #working_warnings AS ww -JOIN #working_metrics AS wm -ON ww.plan_id = wm.plan_id - AND ww.query_id = wm.query_id - AND wm.query_parameterization_type_desc = 'None' - AND ww.proc_or_function_name = 'Statement' -OPTION (RECOMPILE); - - -/* -This looks for cursors -*/ - -RAISERROR(N'Checking for cursors', 0, 1) WITH NOWAIT; -UPDATE ww -SET ww.is_cursor = 1 -FROM #working_warnings AS ww -JOIN #working_plan_text AS wp -ON ww.plan_id = wp.plan_id - AND ww.query_id = wp.query_id - AND wp.plan_group_id > 0 -OPTION (RECOMPILE); - - -UPDATE ww -SET ww.is_cursor = 1 -FROM #working_warnings AS ww -JOIN #working_plan_text AS wp -ON ww.plan_id = wp.plan_id - AND ww.query_id = wp.query_id -WHERE ww.query_hash = 0x0000000000000000 -OR wp.query_plan_hash = 0x0000000000000000 -OPTION (RECOMPILE); - -/* -This looks for parallel plans -*/ -UPDATE ww -SET ww.is_parallel = 1 -FROM #working_warnings AS ww -JOIN #working_plan_text AS wp -ON ww.plan_id = wp.plan_id - AND ww.query_id = wp.query_id - AND wp.is_parallel_plan = 1 -OPTION (RECOMPILE); - -/*This looks for old CE*/ - -RAISERROR(N'Checking for legacy CE', 0, 1) WITH NOWAIT; - -UPDATE w -SET w.downlevel_estimator = 1 -FROM #working_warnings AS w -JOIN #working_plan_text AS wpt -ON w.plan_id = wpt.plan_id -AND w.query_id = wpt.query_id -/*PLEASE DON'T TELL ANYONE I DID THIS*/ -WHERE PARSENAME(wpt.engine_version, 4) < PARSENAME(CONVERT(VARCHAR(128), SERVERPROPERTY ('PRODUCTVERSION')), 4) -OPTION (RECOMPILE); -/*NO SERIOUSLY THIS IS A HORRIBLE IDEA*/ - - -/*Plans that compile 2x more than they execute*/ - -RAISERROR(N'Checking for plans that compile 2x more than they execute', 0, 1) WITH NOWAIT; - -UPDATE ww -SET ww.is_compile_more = 1 -FROM #working_warnings AS ww -JOIN #working_metrics AS wm -ON ww.plan_id = wm.plan_id - AND ww.query_id = wm.query_id - AND wm.count_compiles > (wm.count_executions * 2) -OPTION (RECOMPILE); - -/*Plans that compile 2x more than they execute*/ - -RAISERROR(N'Checking for plans that take more than 5 seconds to bind, compile, or optimize', 0, 1) WITH NOWAIT; - -UPDATE ww -SET ww.is_slow_plan = 1 -FROM #working_warnings AS ww -JOIN #working_metrics AS wm -ON ww.plan_id = wm.plan_id - AND ww.query_id = wm.query_id - AND (wm.avg_bind_duration > 5000 - OR - wm.avg_compile_duration > 5000 - OR - wm.avg_optimize_duration > 5000 - OR - wm.avg_optimize_cpu_time > 5000) -OPTION (RECOMPILE); - - - -/* -This parses the XML from our top plans into smaller chunks for easier consumption -*/ - -RAISERROR(N'Begin XML nodes parsing', 0, 1) WITH NOWAIT; - -RAISERROR(N'Inserting #statements', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -INSERT #statements WITH (TABLOCK) ( plan_id, query_id, query_hash, sql_handle, statement, is_cursor ) - SELECT ww.plan_id, ww.query_id, ww.query_hash, ww.sql_handle, q.n.query('.') AS statement, 0 AS is_cursor - FROM #working_warnings AS ww - JOIN #working_plan_text AS wp - ON ww.plan_id = wp.plan_id - AND ww.query_id = wp.query_id - CROSS APPLY wp.query_plan_xml.nodes('//p:StmtSimple') AS q(n) -OPTION (RECOMPILE); - -RAISERROR(N'Inserting parsed cursor XML to #statements', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -INSERT #statements WITH (TABLOCK) ( plan_id, query_id, query_hash, sql_handle, statement, is_cursor ) - SELECT ww.plan_id, ww.query_id, ww.query_hash, ww.sql_handle, q.n.query('.') AS statement, 1 AS is_cursor - FROM #working_warnings AS ww - JOIN #working_plan_text AS wp - ON ww.plan_id = wp.plan_id - AND ww.query_id = wp.query_id - CROSS APPLY wp.query_plan_xml.nodes('//p:StmtCursor') AS q(n) -OPTION (RECOMPILE); - -RAISERROR(N'Inserting to #query_plan', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -INSERT #query_plan WITH (TABLOCK) ( plan_id, query_id, query_hash, sql_handle, query_plan ) -SELECT s.plan_id, s.query_id, s.query_hash, s.sql_handle, q.n.query('.') AS query_plan -FROM #statements AS s - CROSS APPLY s.statement.nodes('//p:QueryPlan') AS q(n) -OPTION (RECOMPILE); - -RAISERROR(N'Inserting to #relop', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -INSERT #relop WITH (TABLOCK) ( plan_id, query_id, query_hash, sql_handle, relop) -SELECT qp.plan_id, qp.query_id, qp.query_hash, qp.sql_handle, q.n.query('.') AS relop -FROM #query_plan qp - CROSS APPLY qp.query_plan.nodes('//p:RelOp') AS q(n) -OPTION (RECOMPILE); - - --- statement level checks - -RAISERROR(N'Performing compile timeout checks', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET b.compile_timeout = 1 -FROM #statements s -JOIN #working_warnings AS b -ON s.query_hash = b.query_hash -WHERE s.statement.exist('/p:StmtSimple/@StatementOptmEarlyAbortReason[.="TimeOut"]') = 1 -OPTION (RECOMPILE); - - -RAISERROR(N'Performing compile memory limit exceeded checks', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET b.compile_memory_limit_exceeded = 1 -FROM #statements s -JOIN #working_warnings AS b -ON s.query_hash = b.query_hash -WHERE s.statement.exist('/p:StmtSimple/@StatementOptmEarlyAbortReason[.="MemoryLimitExceeded"]') = 1 -OPTION (RECOMPILE); - -IF @ExpertMode > 0 -BEGIN -RAISERROR(N'Performing index DML checks', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p), -index_dml AS ( - SELECT s.query_hash, - index_dml = CASE WHEN s.statement.exist('//p:StmtSimple/@StatementType[.="CREATE INDEX"]') = 1 THEN 1 - WHEN s.statement.exist('//p:StmtSimple/@StatementType[.="DROP INDEX"]') = 1 THEN 1 - END - FROM #statements s - ) - UPDATE b - SET b.index_dml = i.index_dml - FROM #working_warnings AS b - JOIN index_dml i - ON i.query_hash = b.query_hash - WHERE i.index_dml = 1 -OPTION (RECOMPILE); - - -RAISERROR(N'Performing table DML checks', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p), -table_dml AS ( - SELECT s.query_hash, - table_dml = CASE WHEN s.statement.exist('//p:StmtSimple/@StatementType[.="CREATE TABLE"]') = 1 THEN 1 - WHEN s.statement.exist('//p:StmtSimple/@StatementType[.="DROP OBJECT"]') = 1 THEN 1 - END - FROM #statements AS s - ) - UPDATE b - SET b.table_dml = t.table_dml - FROM #working_warnings AS b - JOIN table_dml t - ON t.query_hash = b.query_hash - WHERE t.table_dml = 1 -OPTION (RECOMPILE); -END; - - -RAISERROR(N'Gathering trivial plans', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES ( 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) -UPDATE b -SET b.is_trivial = 1 -FROM #working_warnings AS b -JOIN ( -SELECT s.sql_handle -FROM #statements AS s -JOIN ( SELECT r.sql_handle - FROM #relop AS r - WHERE r.relop.exist('//p:RelOp[contains(@LogicalOp, "Scan")]') = 1 ) AS r - ON r.sql_handle = s.sql_handle -WHERE s.statement.exist('//p:StmtSimple[@StatementOptmLevel[.="TRIVIAL"]]/p:QueryPlan/p:ParameterList') = 1 -) AS s -ON b.sql_handle = s.sql_handle -OPTION (RECOMPILE); - -IF @ExpertMode > 0 -BEGIN -RAISERROR(N'Gathering row estimates', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES ('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) -INSERT #est_rows (query_hash, estimated_rows) -SELECT DISTINCT - CONVERT(BINARY(8), RIGHT('0000000000000000' + SUBSTRING(c.n.value('@QueryHash', 'VARCHAR(18)'), 3, 18), 16), 2) AS query_hash, - c.n.value('(/p:StmtSimple/@StatementEstRows)[1]', 'FLOAT') AS estimated_rows -FROM #statements AS s -CROSS APPLY s.statement.nodes('/p:StmtSimple') AS c(n) -WHERE c.n.exist('/p:StmtSimple[@StatementEstRows > 0]') = 1; - - UPDATE b - SET b.estimated_rows = er.estimated_rows - FROM #working_warnings AS b - JOIN #est_rows er - ON er.query_hash = b.query_hash - OPTION (RECOMPILE); -END; - - -/*Begin plan cost calculations*/ -RAISERROR(N'Gathering statement costs', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -INSERT #plan_cost WITH (TABLOCK) - ( query_plan_cost, sql_handle, plan_id ) -SELECT DISTINCT - s.statement.value('sum(/p:StmtSimple/@StatementSubTreeCost)', 'float') query_plan_cost, - s.sql_handle, - s.plan_id -FROM #statements s -OUTER APPLY s.statement.nodes('/p:StmtSimple') AS q(n) -WHERE s.statement.value('sum(/p:StmtSimple/@StatementSubTreeCost)', 'float') > 0 -OPTION (RECOMPILE); - - -RAISERROR(N'Updating statement costs', 0, 1) WITH NOWAIT; -WITH pc AS ( - SELECT SUM(DISTINCT pc.query_plan_cost) AS queryplancostsum, pc.sql_handle, pc.plan_id - FROM #plan_cost AS pc - GROUP BY pc.sql_handle, pc.plan_id - ) - UPDATE b - SET b.query_cost = ISNULL(pc.queryplancostsum, 0) - FROM #working_warnings AS b - JOIN pc - ON pc.sql_handle = b.sql_handle - AND pc.plan_id = b.plan_id -OPTION (RECOMPILE); - - -/*End plan cost calculations*/ - - -RAISERROR(N'Checking for plan warnings', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET b.plan_warnings = 1 -FROM #query_plan qp -JOIN #working_warnings b -ON qp.sql_handle = b.sql_handle -AND qp.query_plan.exist('/p:QueryPlan/p:Warnings') = 1 -OPTION (RECOMPILE); - - -RAISERROR(N'Checking for implicit conversion', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET b.implicit_conversions = 1 -FROM #query_plan qp -JOIN #working_warnings b -ON qp.sql_handle = b.sql_handle -AND qp.query_plan.exist('/p:QueryPlan/p:Warnings/p:PlanAffectingConvert/@Expression[contains(., "CONVERT_IMPLICIT")]') = 1 -OPTION (RECOMPILE); - -IF @ExpertMode > 0 -BEGIN -RAISERROR(N'Performing busy loops checks', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE p -SET busy_loops = CASE WHEN (x.estimated_executions / 100.0) > x.estimated_rows THEN 1 END -FROM #working_warnings p - JOIN ( - SELECT qs.sql_handle, - relop.value('sum(/p:RelOp/@EstimateRows)', 'float') AS estimated_rows , - relop.value('sum(/p:RelOp/@EstimateRewinds)', 'float') + relop.value('sum(/p:RelOp/@EstimateRebinds)', 'float') + 1.0 AS estimated_executions - FROM #relop qs - ) AS x ON p.sql_handle = x.sql_handle -OPTION (RECOMPILE); -END; - - -RAISERROR(N'Performing TVF join check', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE p -SET p.tvf_join = CASE WHEN x.tvf_join = 1 THEN 1 END -FROM #working_warnings p - JOIN ( - SELECT r.sql_handle, - 1 AS tvf_join - FROM #relop AS r - WHERE r.relop.exist('//p:RelOp[(@LogicalOp[.="Table-valued function"])]') = 1 - AND r.relop.exist('//p:RelOp[contains(@LogicalOp, "Join")]') = 1 - ) AS x ON p.sql_handle = x.sql_handle -OPTION (RECOMPILE); - -IF @ExpertMode > 0 -BEGIN -RAISERROR(N'Checking for operator warnings', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -, x AS ( -SELECT r.sql_handle, - c.n.exist('//p:Warnings[(@NoJoinPredicate[.="1"])]') AS warning_no_join_predicate, - c.n.exist('//p:ColumnsWithNoStatistics') AS no_stats_warning , - c.n.exist('//p:Warnings') AS relop_warnings -FROM #relop AS r -CROSS APPLY r.relop.nodes('/p:RelOp/p:Warnings') AS c(n) -) -UPDATE b -SET b.warning_no_join_predicate = x.warning_no_join_predicate, - b.no_stats_warning = x.no_stats_warning, - b.relop_warnings = x.relop_warnings -FROM #working_warnings b -JOIN x ON x.sql_handle = b.sql_handle -OPTION (RECOMPILE); -END; - - -RAISERROR(N'Checking for table variables', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -, x AS ( -SELECT r.sql_handle, - c.n.value('substring(@Table, 2, 1)','VARCHAR(100)') AS first_char -FROM #relop r -CROSS APPLY r.relop.nodes('//p:Object') AS c(n) -) -UPDATE b -SET b.is_table_variable = 1 -FROM #working_warnings b -JOIN x ON x.sql_handle = b.sql_handle -JOIN #working_metrics AS wm -ON b.plan_id = wm.plan_id -AND b.query_id = wm.query_id -AND wm.batch_sql_handle IS NOT NULL -WHERE x.first_char = '@' -OPTION (RECOMPILE); - - -IF @ExpertMode > 0 -BEGIN -RAISERROR(N'Checking for functions', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -, x AS ( -SELECT r.sql_handle, - n.fn.value('count(distinct-values(//p:UserDefinedFunction[not(@IsClrFunction)]))', 'INT') AS function_count, - n.fn.value('count(distinct-values(//p:UserDefinedFunction[@IsClrFunction = "1"]))', 'INT') AS clr_function_count -FROM #relop r -CROSS APPLY r.relop.nodes('/p:RelOp/p:ComputeScalar/p:DefinedValues/p:DefinedValue/p:ScalarOperator') n(fn) -) -UPDATE b -SET b.function_count = x.function_count, - b.clr_function_count = x.clr_function_count -FROM #working_warnings b -JOIN x ON x.sql_handle = b.sql_handle -OPTION (RECOMPILE); -END; - - -RAISERROR(N'Checking for expensive key lookups', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET b.key_lookup_cost = x.key_lookup_cost -FROM #working_warnings b -JOIN ( -SELECT - r.sql_handle, - MAX(r.relop.value('sum(/p:RelOp/@EstimatedTotalSubtreeCost)', 'float')) AS key_lookup_cost -FROM #relop r -WHERE r.relop.exist('/p:RelOp/p:IndexScan[(@Lookup[.="1"])]') = 1 -GROUP BY r.sql_handle -) AS x ON x.sql_handle = b.sql_handle -OPTION (RECOMPILE); - - -RAISERROR(N'Checking for expensive remote queries', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET b.remote_query_cost = x.remote_query_cost -FROM #working_warnings b -JOIN ( -SELECT - r.sql_handle, - MAX(r.relop.value('sum(/p:RelOp/@EstimatedTotalSubtreeCost)', 'float')) AS remote_query_cost -FROM #relop r -WHERE r.relop.exist('/p:RelOp[(@PhysicalOp[contains(., "Remote")])]') = 1 -GROUP BY r.sql_handle -) AS x ON x.sql_handle = b.sql_handle -OPTION (RECOMPILE); - - -RAISERROR(N'Checking for expensive sorts', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET sort_cost = y.max_sort_cost -FROM #working_warnings b -JOIN ( - SELECT x.sql_handle, MAX((x.sort_io + x.sort_cpu)) AS max_sort_cost - FROM ( - SELECT - qs.sql_handle, - relop.value('sum(/p:RelOp/@EstimateIO)', 'float') AS sort_io, - relop.value('sum(/p:RelOp/@EstimateCPU)', 'float') AS sort_cpu - FROM #relop qs - WHERE [relop].exist('/p:RelOp[(@PhysicalOp[.="Sort"])]') = 1 - ) AS x - GROUP BY x.sql_handle - ) AS y -ON b.sql_handle = y.sql_handle -OPTION (RECOMPILE); - -IF NOT EXISTS(SELECT 1/0 FROM #statements AS s WHERE s.is_cursor = 1) -BEGIN - -RAISERROR(N'No cursor plans found, skipping', 0, 1) WITH NOWAIT; - -END - -IF EXISTS(SELECT 1/0 FROM #statements AS s WHERE s.is_cursor = 1) -BEGIN - -RAISERROR(N'Cursor plans found, investigating', 0, 1) WITH NOWAIT; - -RAISERROR(N'Checking for Optimistic cursors', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET b.is_optimistic_cursor = 1 -FROM #working_warnings b -JOIN #statements AS s -ON b.sql_handle = s.sql_handle -CROSS APPLY s.statement.nodes('/p:StmtCursor') AS n1(fn) -WHERE n1.fn.exist('//p:CursorPlan/@CursorConcurrency[.="Optimistic"]') = 1 -AND s.is_cursor = 1 -OPTION (RECOMPILE); - - -RAISERROR(N'Checking if cursor is Forward Only', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET b.is_forward_only_cursor = 1 -FROM #working_warnings b -JOIN #statements AS s -ON b.sql_handle = s.sql_handle -CROSS APPLY s.statement.nodes('/p:StmtCursor') AS n1(fn) -WHERE n1.fn.exist('//p:CursorPlan/@ForwardOnly[.="true"]') = 1 -AND s.is_cursor = 1 -OPTION (RECOMPILE); - - -RAISERROR(N'Checking if cursor is Fast Forward', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET b.is_fast_forward_cursor = 1 -FROM #working_warnings b -JOIN #statements AS s -ON b.sql_handle = s.sql_handle -CROSS APPLY s.statement.nodes('/p:StmtCursor') AS n1(fn) -WHERE n1.fn.exist('//p:CursorPlan/@CursorActualType[.="FastForward"]') = 1 -AND s.is_cursor = 1 -OPTION (RECOMPILE); - - -RAISERROR(N'Checking for Dynamic cursors', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET b.is_cursor_dynamic = 1 -FROM #working_warnings b -JOIN #statements AS s -ON b.sql_handle = s.sql_handle -CROSS APPLY s.statement.nodes('/p:StmtCursor') AS n1(fn) -WHERE n1.fn.exist('//p:CursorPlan/@CursorActualType[.="Dynamic"]') = 1 -AND s.is_cursor = 1 -OPTION (RECOMPILE); - -END - -IF @ExpertMode > 0 -BEGIN -RAISERROR(N'Checking for bad scans and plan forcing', 0, 1) WITH NOWAIT; -;WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET -b.is_table_scan = x.is_table_scan, -b.backwards_scan = x.backwards_scan, -b.forced_index = x.forced_index, -b.forced_seek = x.forced_seek, -b.forced_scan = x.forced_scan -FROM #working_warnings b -JOIN ( -SELECT - r.sql_handle, - 0 AS is_table_scan, - q.n.exist('@ScanDirection[.="BACKWARD"]') AS backwards_scan, - q.n.value('@ForcedIndex', 'bit') AS forced_index, - q.n.value('@ForceSeek', 'bit') AS forced_seek, - q.n.value('@ForceScan', 'bit') AS forced_scan -FROM #relop r -CROSS APPLY r.relop.nodes('//p:IndexScan') AS q(n) -UNION ALL -SELECT - r.sql_handle, - 1 AS is_table_scan, - q.n.exist('@ScanDirection[.="BACKWARD"]') AS backwards_scan, - q.n.value('@ForcedIndex', 'bit') AS forced_index, - q.n.value('@ForceSeek', 'bit') AS forced_seek, - q.n.value('@ForceScan', 'bit') AS forced_scan -FROM #relop r -CROSS APPLY r.relop.nodes('//p:TableScan') AS q(n) -) AS x ON b.sql_handle = x.sql_handle -OPTION (RECOMPILE); -END; - - -IF @ExpertMode > 0 -BEGIN -RAISERROR(N'Checking for computed columns that reference scalar UDFs', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET b.is_computed_scalar = x.computed_column_function -FROM #working_warnings b -JOIN ( -SELECT r.sql_handle, - n.fn.value('count(distinct-values(//p:UserDefinedFunction[not(@IsClrFunction)]))', 'INT') AS computed_column_function -FROM #relop r -CROSS APPLY r.relop.nodes('/p:RelOp/p:ComputeScalar/p:DefinedValues/p:DefinedValue/p:ScalarOperator') n(fn) -WHERE n.fn.exist('/p:RelOp/p:ComputeScalar/p:DefinedValues/p:DefinedValue/p:ColumnReference[(@ComputedColumn[.="1"])]') = 1 -) AS x ON x.sql_handle = b.sql_handle -OPTION (RECOMPILE); -END; - - -RAISERROR(N'Checking for filters that reference scalar UDFs', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET b.is_computed_filter = x.filter_function -FROM #working_warnings b -JOIN ( -SELECT -r.sql_handle, -c.n.value('count(distinct-values(//p:UserDefinedFunction[not(@IsClrFunction)]))', 'INT') AS filter_function -FROM #relop AS r -CROSS APPLY r.relop.nodes('/p:RelOp/p:Filter/p:Predicate/p:ScalarOperator/p:Compare/p:ScalarOperator/p:UserDefinedFunction') c(n) -) x ON x.sql_handle = b.sql_handle -OPTION (RECOMPILE); - - -IF @ExpertMode > 0 -BEGIN -RAISERROR(N'Checking modification queries that hit lots of indexes', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p), -IndexOps AS -( - SELECT - r.query_hash, - c.n.value('@PhysicalOp', 'VARCHAR(100)') AS op_name, - c.n.exist('@PhysicalOp[.="Index Insert"]') AS ii, - c.n.exist('@PhysicalOp[.="Index Update"]') AS iu, - c.n.exist('@PhysicalOp[.="Index Delete"]') AS id, - c.n.exist('@PhysicalOp[.="Clustered Index Insert"]') AS cii, - c.n.exist('@PhysicalOp[.="Clustered Index Update"]') AS ciu, - c.n.exist('@PhysicalOp[.="Clustered Index Delete"]') AS cid, - c.n.exist('@PhysicalOp[.="Table Insert"]') AS ti, - c.n.exist('@PhysicalOp[.="Table Update"]') AS tu, - c.n.exist('@PhysicalOp[.="Table Delete"]') AS td - FROM #relop AS r - CROSS APPLY r.relop.nodes('/p:RelOp') c(n) - OUTER APPLY r.relop.nodes('/p:RelOp/p:ScalarInsert/p:Object') q(n) - OUTER APPLY r.relop.nodes('/p:RelOp/p:Update/p:Object') o2(n) - OUTER APPLY r.relop.nodes('/p:RelOp/p:SimpleUpdate/p:Object') o3(n) -), iops AS -( - SELECT ios.query_hash, - SUM(CONVERT(TINYINT, ios.ii)) AS index_insert_count, - SUM(CONVERT(TINYINT, ios.iu)) AS index_update_count, - SUM(CONVERT(TINYINT, ios.id)) AS index_delete_count, - SUM(CONVERT(TINYINT, ios.cii)) AS cx_insert_count, - SUM(CONVERT(TINYINT, ios.ciu)) AS cx_update_count, - SUM(CONVERT(TINYINT, ios.cid)) AS cx_delete_count, - SUM(CONVERT(TINYINT, ios.ti)) AS table_insert_count, - SUM(CONVERT(TINYINT, ios.tu)) AS table_update_count, - SUM(CONVERT(TINYINT, ios.td)) AS table_delete_count - FROM IndexOps AS ios - WHERE ios.op_name IN ('Index Insert', 'Index Delete', 'Index Update', - 'Clustered Index Insert', 'Clustered Index Delete', 'Clustered Index Update', - 'Table Insert', 'Table Delete', 'Table Update') - GROUP BY ios.query_hash) -UPDATE b -SET b.index_insert_count = iops.index_insert_count, - b.index_update_count = iops.index_update_count, - b.index_delete_count = iops.index_delete_count, - b.cx_insert_count = iops.cx_insert_count, - b.cx_update_count = iops.cx_update_count, - b.cx_delete_count = iops.cx_delete_count, - b.table_insert_count = iops.table_insert_count, - b.table_update_count = iops.table_update_count, - b.table_delete_count = iops.table_delete_count -FROM #working_warnings AS b -JOIN iops ON iops.query_hash = b.query_hash -OPTION (RECOMPILE); -END; - - -IF @ExpertMode > 0 -BEGIN -RAISERROR(N'Checking for Spatial index use', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET b.is_spatial = x.is_spatial -FROM #working_warnings AS b -JOIN ( -SELECT r.sql_handle, - 1 AS is_spatial -FROM #relop r -CROSS APPLY r.relop.nodes('/p:RelOp//p:Object') n(fn) -WHERE n.fn.exist('(@IndexKind[.="Spatial"])') = 1 -) AS x ON x.sql_handle = b.sql_handle -OPTION (RECOMPILE); -END; - -RAISERROR(N'Checking for forced serialization', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET b.is_forced_serial = 1 -FROM #query_plan qp -JOIN #working_warnings AS b -ON qp.sql_handle = b.sql_handle -AND b.is_parallel IS NULL -AND qp.query_plan.exist('/p:QueryPlan/@NonParallelPlanReason') = 1 -OPTION (RECOMPILE); - - -IF @ExpertMode > 0 -BEGIN -RAISERROR(N'Checking for ColumnStore queries operating in Row Mode instead of Batch Mode', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET b.columnstore_row_mode = x.is_row_mode -FROM #working_warnings AS b -JOIN ( -SELECT - r.sql_handle, - r.relop.exist('/p:RelOp[(@EstimatedExecutionMode[.="Row"])]') AS is_row_mode -FROM #relop r -WHERE r.relop.exist('/p:RelOp/p:IndexScan[(@Storage[.="ColumnStore"])]') = 1 -) AS x ON x.sql_handle = b.sql_handle -OPTION (RECOMPILE); -END; - - -IF @ExpertMode > 0 -BEGIN -RAISERROR('Checking for row level security only', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET b.is_row_level = 1 -FROM #working_warnings b -JOIN #statements s -ON s.query_hash = b.query_hash -WHERE s.statement.exist('/p:StmtSimple/@SecurityPolicyApplied[.="true"]') = 1 -OPTION (RECOMPILE); -END; - - -IF @ExpertMode > 0 -BEGIN -RAISERROR('Checking for wonky Index Spools', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES ( - 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) -, selects -AS ( SELECT s.plan_id, s.query_id - FROM #statements AS s - WHERE s.statement.exist('/p:StmtSimple/@StatementType[.="SELECT"]') = 1 ) -, spools -AS ( SELECT DISTINCT r.plan_id, - r.query_id, - c.n.value('@EstimateRows', 'FLOAT') AS estimated_rows, - c.n.value('@EstimateIO', 'FLOAT') AS estimated_io, - c.n.value('@EstimateCPU', 'FLOAT') AS estimated_cpu, - c.n.value('@EstimateRebinds', 'FLOAT') AS estimated_rebinds -FROM #relop AS r -JOIN selects AS s -ON s.plan_id = r.plan_id - AND s.query_id = r.query_id -CROSS APPLY r.relop.nodes('/p:RelOp') AS c(n) -WHERE r.relop.exist('/p:RelOp[@PhysicalOp="Index Spool" and @LogicalOp="Eager Spool"]') = 1 -) -UPDATE ww - SET ww.index_spool_rows = sp.estimated_rows, - ww.index_spool_cost = ((sp.estimated_io * sp.estimated_cpu) * CASE WHEN sp.estimated_rebinds < 1 THEN 1 ELSE sp.estimated_rebinds END) - -FROM #working_warnings ww -JOIN spools sp -ON ww.plan_id = sp.plan_id -AND ww.query_id = sp.query_id -OPTION (RECOMPILE); -END; - - -IF (PARSENAME(CONVERT(VARCHAR(128), SERVERPROPERTY ('PRODUCTVERSION')), 4)) >= 14 -OR ((PARSENAME(CONVERT(VARCHAR(128), SERVERPROPERTY ('PRODUCTVERSION')), 4)) = 13 - AND PARSENAME(CONVERT(VARCHAR(128), SERVERPROPERTY ('PRODUCTVERSION')), 2) >= 5026) - -BEGIN - -RAISERROR(N'Beginning 2017 and 2016 SP2 specfic checks', 0, 1) WITH NOWAIT; - -IF @ExpertMode > 0 -BEGIN -RAISERROR('Gathering stats information', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -INSERT #stats_agg WITH (TABLOCK) - (sql_handle, last_update, modification_count, sampling_percent, [statistics], [table], [schema], [database]) -SELECT qp.sql_handle, - x.c.value('@LastUpdate', 'DATETIME2(7)') AS LastUpdate, - x.c.value('@ModificationCount', 'BIGINT') AS ModificationCount, - x.c.value('@SamplingPercent', 'FLOAT') AS SamplingPercent, - x.c.value('@Statistics', 'NVARCHAR(258)') AS [Statistics], - x.c.value('@Table', 'NVARCHAR(258)') AS [Table], - x.c.value('@Schema', 'NVARCHAR(258)') AS [Schema], - x.c.value('@Database', 'NVARCHAR(258)') AS [Database] -FROM #query_plan AS qp -CROSS APPLY qp.query_plan.nodes('//p:OptimizerStatsUsage/p:StatisticsInfo') x (c) -OPTION (RECOMPILE); - - -RAISERROR('Checking for stale stats', 0, 1) WITH NOWAIT; -WITH stale_stats AS ( - SELECT sa.sql_handle - FROM #stats_agg AS sa - GROUP BY sa.sql_handle - HAVING MAX(sa.last_update) <= DATEADD(DAY, -7, SYSDATETIME()) - AND AVG(sa.modification_count) >= 100000 -) -UPDATE b -SET b.stale_stats = 1 -FROM #working_warnings AS b -JOIN stale_stats os -ON b.sql_handle = os.sql_handle -OPTION (RECOMPILE); -END; - - -IF (PARSENAME(CONVERT(VARCHAR(128), SERVERPROPERTY ('PRODUCTVERSION')), 4)) >= 14 - AND @ExpertMode > 0 -BEGIN -RAISERROR(N'Checking for Adaptive Joins', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p), -aj AS ( - SELECT r.sql_handle - FROM #relop AS r - CROSS APPLY r.relop.nodes('//p:RelOp') x(c) - WHERE x.c.exist('@IsAdaptive[.=1]') = 1 -) -UPDATE b -SET b.is_adaptive = 1 -FROM #working_warnings AS b -JOIN aj -ON b.sql_handle = aj.sql_handle -OPTION (RECOMPILE); -END; - - -IF @ExpertMode > 0 -BEGIN; -RAISERROR(N'Checking for Row Goals', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p), -row_goals AS( -SELECT qs.query_hash -FROM #relop qs -WHERE relop.value('sum(/p:RelOp/@EstimateRowsWithoutRowGoal)', 'float') > 0 -) -UPDATE b -SET b.is_row_goal = 1 -FROM #working_warnings b -JOIN row_goals -ON b.query_hash = row_goals.query_hash -OPTION (RECOMPILE); -END; - -END; - - -RAISERROR(N'Performing query level checks', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET b.missing_index_count = query_plan.value('count(//p:QueryPlan/p:MissingIndexes/p:MissingIndexGroup)', 'int') , - b.unmatched_index_count = CASE WHEN is_trivial <> 1 THEN query_plan.value('count(//p:QueryPlan/p:UnmatchedIndexes/p:Parameterization/p:Object)', 'int') END -FROM #query_plan qp -JOIN #working_warnings AS b -ON b.query_hash = qp.query_hash -OPTION (RECOMPILE); - - -RAISERROR(N'Trace flag checks', 0, 1) WITH NOWAIT; -;WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -, tf_pretty AS ( -SELECT qp.sql_handle, - q.n.value('@Value', 'INT') AS trace_flag, - q.n.value('@Scope', 'VARCHAR(10)') AS scope -FROM #query_plan qp -CROSS APPLY qp.query_plan.nodes('/p:QueryPlan/p:TraceFlags/p:TraceFlag') AS q(n) -) -INSERT #trace_flags WITH (TABLOCK) - (sql_handle, global_trace_flags, session_trace_flags ) -SELECT DISTINCT tf1.sql_handle , - STUFF(( - SELECT DISTINCT N', ' + CONVERT(NVARCHAR(5), tf2.trace_flag) - FROM tf_pretty AS tf2 - WHERE tf1.sql_handle = tf2.sql_handle - AND tf2.scope = 'Global' - FOR XML PATH(N'')), 1, 2, N'' - ) AS global_trace_flags, - STUFF(( - SELECT DISTINCT N', ' + CONVERT(NVARCHAR(5), tf2.trace_flag) - FROM tf_pretty AS tf2 - WHERE tf1.sql_handle = tf2.sql_handle - AND tf2.scope = 'Session' - FOR XML PATH(N'')), 1, 2, N'' - ) AS session_trace_flags -FROM tf_pretty AS tf1 -OPTION (RECOMPILE); - -UPDATE b -SET b.trace_flags_session = tf.session_trace_flags -FROM #working_warnings AS b -JOIN #trace_flags tf -ON tf.sql_handle = b.sql_handle -OPTION (RECOMPILE); - - -RAISERROR(N'Checking for MSTVFs', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET b.is_mstvf = 1 -FROM #relop AS r -JOIN #working_warnings AS b -ON b.sql_handle = r.sql_handle -WHERE r.relop.exist('/p:RelOp[(@EstimateRows="100" or @EstimateRows="1") and @LogicalOp="Table-valued function"]') = 1 -OPTION (RECOMPILE); - -IF @ExpertMode > 0 -BEGIN -RAISERROR(N'Checking for many to many merge joins', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET b.is_mm_join = 1 -FROM #relop AS r -JOIN #working_warnings AS b -ON b.sql_handle = r.sql_handle -WHERE r.relop.exist('/p:RelOp/p:Merge/@ManyToMany[.="1"]') = 1 -OPTION (RECOMPILE); -END; - - -IF @ExpertMode > 0 -BEGIN -RAISERROR(N'Is Paul White Electric?', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p), -is_paul_white_electric AS ( -SELECT 1 AS [is_paul_white_electric], -r.sql_handle -FROM #relop AS r -CROSS APPLY r.relop.nodes('//p:RelOp') c(n) -WHERE c.n.exist('@PhysicalOp[.="Switch"]') = 1 -) -UPDATE b -SET b.is_paul_white_electric = ipwe.is_paul_white_electric -FROM #working_warnings AS b -JOIN is_paul_white_electric ipwe -ON ipwe.sql_handle = b.sql_handle -OPTION (RECOMPILE); -END; - - - -RAISERROR(N'Checking for non-sargable predicates', 0, 1) WITH NOWAIT; -WITH XMLNAMESPACES ( 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) -, nsarg - AS ( SELECT r.query_hash, 1 AS fn, 0 AS jo, 0 AS lk - FROM #relop AS r - CROSS APPLY r.relop.nodes('/p:RelOp/p:IndexScan/p:Predicate/p:ScalarOperator/p:Compare/p:ScalarOperator') AS ca(x) - WHERE ( ca.x.exist('//p:ScalarOperator/p:Intrinsic/@FunctionName') = 1 - OR ca.x.exist('//p:ScalarOperator/p:IF') = 1 ) - UNION ALL - SELECT r.query_hash, 0 AS fn, 1 AS jo, 0 AS lk - FROM #relop AS r - CROSS APPLY r.relop.nodes('/p:RelOp//p:ScalarOperator') AS ca(x) - WHERE r.relop.exist('/p:RelOp[contains(@LogicalOp, "Join")]') = 1 - AND ca.x.exist('//p:ScalarOperator[contains(@ScalarString, "Expr")]') = 1 - UNION ALL - SELECT r.query_hash, 0 AS fn, 0 AS jo, 1 AS lk - FROM #relop AS r - CROSS APPLY r.relop.nodes('/p:RelOp/p:IndexScan/p:Predicate/p:ScalarOperator') AS ca(x) - CROSS APPLY ca.x.nodes('//p:Const') AS co(x) - WHERE ca.x.exist('//p:ScalarOperator/p:Intrinsic/@FunctionName[.="like"]') = 1 - AND ( ( co.x.value('substring(@ConstValue, 1, 1)', 'VARCHAR(100)') <> 'N' - AND co.x.value('substring(@ConstValue, 2, 1)', 'VARCHAR(100)') = '%' ) - OR ( co.x.value('substring(@ConstValue, 1, 1)', 'VARCHAR(100)') = 'N' - AND co.x.value('substring(@ConstValue, 3, 1)', 'VARCHAR(100)') = '%' ))), - d_nsarg - AS ( SELECT DISTINCT - nsarg.query_hash - FROM nsarg - WHERE nsarg.fn = 1 - OR nsarg.jo = 1 - OR nsarg.lk = 1 ) -UPDATE b -SET b.is_nonsargable = 1 -FROM d_nsarg AS d -JOIN #working_warnings AS b - ON b.query_hash = d.query_hash -OPTION ( RECOMPILE ); - - - RAISERROR(N'Getting information about implicit conversions and stored proc parameters', 0, 1) WITH NOWAIT; - - RAISERROR(N'Getting variable info', 0, 1) WITH NOWAIT; - WITH XMLNAMESPACES ( 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) - INSERT #variable_info ( query_hash, sql_handle, proc_name, variable_name, variable_datatype, compile_time_value ) - SELECT DISTINCT - qp.query_hash, - qp.sql_handle, - b.proc_or_function_name AS proc_name, - q.n.value('@Column', 'NVARCHAR(258)') AS variable_name, - q.n.value('@ParameterDataType', 'NVARCHAR(258)') AS variable_datatype, - q.n.value('@ParameterCompiledValue', 'NVARCHAR(258)') AS compile_time_value - FROM #query_plan AS qp - JOIN #working_warnings AS b - ON (b.query_hash = qp.query_hash AND b.proc_or_function_name = 'adhoc') - OR (b.sql_handle = qp.sql_handle AND b.proc_or_function_name <> 'adhoc') - CROSS APPLY qp.query_plan.nodes('//p:QueryPlan/p:ParameterList/p:ColumnReference') AS q(n) - OPTION (RECOMPILE); - - RAISERROR(N'Getting conversion info', 0, 1) WITH NOWAIT; - WITH XMLNAMESPACES ( 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) - INSERT #conversion_info ( query_hash, sql_handle, proc_name, expression ) - SELECT DISTINCT - qp.query_hash, - qp.sql_handle, - b.proc_or_function_name AS proc_name, - qq.c.value('@Expression', 'NVARCHAR(4000)') AS expression - FROM #query_plan AS qp - JOIN #working_warnings AS b - ON (b.query_hash = qp.query_hash AND b.proc_or_function_name = 'adhoc') - OR (b.sql_handle = qp.sql_handle AND b.proc_or_function_name <> 'adhoc') - CROSS APPLY qp.query_plan.nodes('//p:QueryPlan/p:Warnings/p:PlanAffectingConvert') AS qq(c) - WHERE qq.c.exist('@ConvertIssue[.="Seek Plan"]') = 1 - AND b.implicit_conversions = 1 - OPTION (RECOMPILE); - - RAISERROR(N'Parsing conversion info', 0, 1) WITH NOWAIT; - INSERT #stored_proc_info ( sql_handle, query_hash, proc_name, variable_name, variable_datatype, converted_column_name, column_name, converted_to, compile_time_value ) - SELECT ci.sql_handle, - ci.query_hash, - ci.proc_name, - CASE WHEN ci.at_charindex > 0 - AND ci.bracket_charindex > 0 - THEN SUBSTRING(ci.expression, ci.at_charindex, ci.bracket_charindex) - ELSE N'**no_variable**' - END AS variable_name, - N'**no_variable**' AS variable_datatype, - CASE WHEN ci.at_charindex = 0 - AND ci.comma_charindex > 0 - AND ci.second_comma_charindex > 0 - THEN SUBSTRING(ci.expression, ci.comma_charindex, ci.second_comma_charindex) - ELSE N'**no_column**' - END AS converted_column_name, - CASE WHEN ci.at_charindex = 0 - AND ci.equal_charindex > 0 - AND ci.convert_implicit_charindex = 0 - THEN SUBSTRING(ci.expression, ci.equal_charindex, 4000) - WHEN ci.at_charindex = 0 - AND (ci.equal_charindex -1) > 0 - AND ci.convert_implicit_charindex > 0 - THEN SUBSTRING(ci.expression, 0, ci.equal_charindex -1) - WHEN ci.at_charindex > 0 - AND ci.comma_charindex > 0 - AND ci.second_comma_charindex > 0 - THEN SUBSTRING(ci.expression, ci.comma_charindex, ci.second_comma_charindex) - ELSE N'**no_column **' - END AS column_name, - CASE WHEN ci.paren_charindex > 0 - 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 - 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 - FROM #conversion_info AS ci - OPTION (RECOMPILE); - - RAISERROR(N'Updating variables inserted procs', 0, 1) WITH NOWAIT; - UPDATE sp - SET sp.variable_datatype = vi.variable_datatype, - sp.compile_time_value = vi.compile_time_value - FROM #stored_proc_info AS sp - JOIN #variable_info AS vi - ON (sp.proc_name = 'adhoc' AND sp.query_hash = vi.query_hash) - OR (sp.proc_name <> 'adhoc' AND sp.sql_handle = vi.sql_handle) - AND sp.variable_name = vi.variable_name - OPTION (RECOMPILE); - - - RAISERROR(N'Inserting variables for other procs', 0, 1) WITH NOWAIT; - INSERT #stored_proc_info - ( sql_handle, query_hash, variable_name, variable_datatype, compile_time_value, proc_name ) - SELECT vi.sql_handle, vi.query_hash, vi.variable_name, vi.variable_datatype, vi.compile_time_value, vi.proc_name - FROM #variable_info AS vi - WHERE NOT EXISTS - ( - SELECT * - FROM #stored_proc_info AS sp - WHERE (sp.proc_name = 'adhoc' AND sp.query_hash = vi.query_hash) - OR (sp.proc_name <> 'adhoc' AND sp.sql_handle = vi.sql_handle) - ) - OPTION (RECOMPILE); - - RAISERROR(N'Updating procs', 0, 1) WITH NOWAIT; - UPDATE s - SET s.variable_datatype = CASE WHEN s.variable_datatype LIKE '%(%)%' - THEN LEFT(s.variable_datatype, CHARINDEX('(', s.variable_datatype) - 1) - ELSE s.variable_datatype - END, - s.converted_to = CASE WHEN s.converted_to LIKE '%(%)%' - THEN LEFT(s.converted_to, CHARINDEX('(', s.converted_to) - 1) - ELSE s.converted_to - END, - s.compile_time_value = CASE WHEN s.compile_time_value LIKE '%(%)%' - THEN SUBSTRING(s.compile_time_value, - CHARINDEX('(', s.compile_time_value) + 1, - CHARINDEX(')', s.compile_time_value) - 1 - CHARINDEX('(', s.compile_time_value) - ) - WHEN variable_datatype NOT IN ('bit', 'tinyint', 'smallint', 'int', 'bigint') - AND s.variable_datatype NOT LIKE '%binary%' - AND s.compile_time_value NOT LIKE 'N''%''' - AND s.compile_time_value NOT LIKE '''%''' - AND s.compile_time_value <> s.column_name - AND s.compile_time_value <> '**idk_man**' - THEN QUOTENAME(compile_time_value, '''') - ELSE s.compile_time_value - END - FROM #stored_proc_info AS s - OPTION (RECOMPILE); - - - RAISERROR(N'Updating SET options', 0, 1) WITH NOWAIT; - WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) - UPDATE s - SET set_options = set_options.ansi_set_options - FROM #stored_proc_info AS s - JOIN ( - SELECT x.sql_handle, - N'SET ANSI_NULLS ' + CASE WHEN [ANSI_NULLS] = 'true' THEN N'ON ' ELSE N'OFF ' END + NCHAR(10) + - N'SET ANSI_PADDING ' + CASE WHEN [ANSI_PADDING] = 'true' THEN N'ON ' ELSE N'OFF ' END + NCHAR(10) + - N'SET ANSI_WARNINGS ' + CASE WHEN [ANSI_WARNINGS] = 'true' THEN N'ON ' ELSE N'OFF ' END + NCHAR(10) + - N'SET ARITHABORT ' + CASE WHEN [ARITHABORT] = 'true' THEN N'ON ' ELSE N' OFF ' END + NCHAR(10) + - N'SET CONCAT_NULL_YIELDS_NULL ' + CASE WHEN [CONCAT_NULL_YIELDS_NULL] = 'true' THEN N'ON ' ELSE N'OFF ' END + NCHAR(10) + - N'SET NUMERIC_ROUNDABORT ' + CASE WHEN [NUMERIC_ROUNDABORT] = 'true' THEN N'ON ' ELSE N'OFF ' END + NCHAR(10) + - N'SET QUOTED_IDENTIFIER ' + CASE WHEN [QUOTED_IDENTIFIER] = 'true' THEN N'ON ' ELSE N'OFF ' + NCHAR(10) END AS [ansi_set_options] - FROM ( - SELECT - s.sql_handle, - so.o.value('@ANSI_NULLS', 'NVARCHAR(20)') AS [ANSI_NULLS], - so.o.value('@ANSI_PADDING', 'NVARCHAR(20)') AS [ANSI_PADDING], - so.o.value('@ANSI_WARNINGS', 'NVARCHAR(20)') AS [ANSI_WARNINGS], - so.o.value('@ARITHABORT', 'NVARCHAR(20)') AS [ARITHABORT], - so.o.value('@CONCAT_NULL_YIELDS_NULL', 'NVARCHAR(20)') AS [CONCAT_NULL_YIELDS_NULL], - so.o.value('@NUMERIC_ROUNDABORT', 'NVARCHAR(20)') AS [NUMERIC_ROUNDABORT], - so.o.value('@QUOTED_IDENTIFIER', 'NVARCHAR(20)') AS [QUOTED_IDENTIFIER] - FROM #statements AS s - CROSS APPLY s.statement.nodes('//p:StatementSetOptions') AS so(o) - ) AS x - ) AS set_options ON set_options.sql_handle = s.sql_handle - OPTION(RECOMPILE); - - - RAISERROR(N'Updating conversion XML', 0, 1) WITH NOWAIT; - WITH precheck AS ( - SELECT spi.sql_handle, - spi.proc_name, - (SELECT CASE WHEN spi.proc_name <> 'Statement' - THEN N'The stored procedure ' + spi.proc_name - ELSE N'This ad hoc statement' - END - + N' had the following implicit conversions: ' - + CHAR(10) - + STUFF(( - SELECT DISTINCT - @cr + @lf - + CASE WHEN spi2.variable_name <> N'**no_variable**' - THEN N'The variable ' - WHEN spi2.variable_name = N'**no_variable**' AND (spi2.column_name = spi2.converted_column_name OR spi2.column_name LIKE '%CONVERT_IMPLICIT%') - THEN N'The compiled value ' - WHEN spi2.column_name LIKE '%Expr%' - THEN 'The expression ' - ELSE N'The column ' - END - + CASE WHEN spi2.variable_name <> N'**no_variable**' - THEN spi2.variable_name - WHEN spi2.variable_name = N'**no_variable**' AND (spi2.column_name = spi2.converted_column_name OR spi2.column_name LIKE '%CONVERT_IMPLICIT%') - THEN spi2.compile_time_value - - ELSE spi2.column_name - END - + N' has a data type of ' - + CASE WHEN spi2.variable_datatype = N'**no_variable**' THEN spi2.converted_to - ELSE spi2.variable_datatype - END - + N' which caused implicit conversion on the column ' - + CASE WHEN spi2.column_name LIKE N'%CONVERT_IMPLICIT%' - THEN spi2.converted_column_name - WHEN spi2.column_name = N'**no_column**' - THEN spi2.converted_column_name - WHEN spi2.converted_column_name = N'**no_column**' - THEN spi2.column_name - WHEN spi2.column_name <> spi2.converted_column_name - THEN spi2.converted_column_name - ELSE spi2.column_name - END - + CASE WHEN spi2.variable_name = N'**no_variable**' AND (spi2.column_name = spi2.converted_column_name OR spi2.column_name LIKE '%CONVERT_IMPLICIT%') - THEN N'' - WHEN spi2.column_name LIKE '%Expr%' - THEN N'' - WHEN spi2.compile_time_value NOT IN ('**declared in proc**', '**idk_man**') - AND spi2.compile_time_value <> spi2.column_name - THEN ' with the value ' + RTRIM(spi2.compile_time_value) - ELSE N'' - END - + '.' - FROM #stored_proc_info AS spi2 - WHERE spi.sql_handle = spi2.sql_handle - FOR XML PATH(N''), TYPE).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 1, N'') - AS [processing-instruction(ClickMe)] FOR XML PATH(''), TYPE ) - AS implicit_conversion_info - FROM #stored_proc_info AS spi - GROUP BY spi.sql_handle, spi.proc_name - ) - UPDATE b - SET b.implicit_conversion_info = pk.implicit_conversion_info - FROM #working_warnings AS b - JOIN precheck AS pk - ON pk.sql_handle = b.sql_handle - OPTION (RECOMPILE); - - RAISERROR(N'Updating cached parameter XML for procs', 0, 1) WITH NOWAIT; - WITH precheck AS ( - SELECT spi.sql_handle, - spi.proc_name, - (SELECT set_options - + @cr + @lf - + @cr + @lf - + N'EXEC ' - + spi.proc_name - + N' ' - + STUFF(( - SELECT DISTINCT N', ' - + CASE WHEN spi2.variable_name <> N'**no_variable**' AND spi2.compile_time_value <> N'**idk_man**' - THEN spi2.variable_name + N' = ' - ELSE @cr + @lf + N' We could not find any cached parameter values for this stored proc. ' - END - + CASE WHEN spi2.variable_name = N'**no_variable**' OR spi2.compile_time_value = N'**idk_man**' - THEN @cr + @lf + N' Possible reasons include declared variables inside the procedure, recompile hints, etc. ' - WHEN spi2.compile_time_value = N'NULL' - THEN spi2.compile_time_value - ELSE RTRIM(spi2.compile_time_value) - END - FROM #stored_proc_info AS spi2 - WHERE spi.sql_handle = spi2.sql_handle - AND spi2.proc_name <> N'Statement' - FOR XML PATH(N''), TYPE).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 1, N'') - AS [processing-instruction(ClickMe)] FOR XML PATH(''), TYPE ) - AS cached_execution_parameters - FROM #stored_proc_info AS spi - GROUP BY spi.sql_handle, spi.proc_name, set_options - ) - UPDATE b - SET b.cached_execution_parameters = pk.cached_execution_parameters - FROM #working_warnings AS b - JOIN precheck AS pk - ON pk.sql_handle = b.sql_handle - WHERE b.proc_or_function_name <> N'Statement' - OPTION (RECOMPILE); - - - RAISERROR(N'Updating cached parameter XML for statements', 0, 1) WITH NOWAIT; - WITH precheck AS ( - SELECT spi.sql_handle, - spi.proc_name, - (SELECT - set_options - + @cr + @lf - + @cr + @lf - + N' See QueryText column for full query text' - + @cr + @lf - + @cr + @lf - + STUFF(( - SELECT DISTINCT N', ' - + CASE WHEN spi2.variable_name <> N'**no_variable**' AND spi2.compile_time_value <> N'**idk_man**' - THEN spi2.variable_name + N' = ' - ELSE + @cr + @lf + N' We could not find any cached parameter values for this stored proc. ' - END - + CASE WHEN spi2.variable_name = N'**no_variable**' OR spi2.compile_time_value = N'**idk_man**' - THEN + @cr + @lf + N' Possible reasons include declared variables inside the procedure, recompile hints, etc. ' - WHEN spi2.compile_time_value = N'NULL' - THEN spi2.compile_time_value - ELSE RTRIM(spi2.compile_time_value) - END - FROM #stored_proc_info AS spi2 - WHERE spi.sql_handle = spi2.sql_handle - AND spi2.proc_name = N'Statement' - AND spi2.variable_name NOT LIKE N'%msparam%' - FOR XML PATH(N''), TYPE).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 1, N'') - AS [processing-instruction(ClickMe)] FOR XML PATH(''), TYPE ) - AS cached_execution_parameters - FROM #stored_proc_info AS spi - GROUP BY spi.sql_handle, spi.proc_name, spi.set_options - ) - UPDATE b - SET b.cached_execution_parameters = pk.cached_execution_parameters - FROM #working_warnings AS b - JOIN precheck AS pk - ON pk.sql_handle = b.sql_handle - WHERE b.proc_or_function_name = N'Statement' - OPTION (RECOMPILE); - - -RAISERROR(N'Filling in implicit conversion info', 0, 1) WITH NOWAIT; -UPDATE b -SET b.implicit_conversion_info = CASE WHEN b.implicit_conversion_info IS NULL - OR CONVERT(NVARCHAR(MAX), b.implicit_conversion_info) = N'' - THEN N'' - ELSE b.implicit_conversion_info - END, - b.cached_execution_parameters = CASE WHEN b.cached_execution_parameters IS NULL - OR CONVERT(NVARCHAR(MAX), b.cached_execution_parameters) = N'' - THEN N'' - ELSE b.cached_execution_parameters - END -FROM #working_warnings AS b -OPTION (RECOMPILE); - -/*End implicit conversion and parameter info*/ - -/*Begin Missing Index*/ -IF EXISTS ( SELECT 1/0 - FROM #working_warnings AS ww - WHERE ww.missing_index_count > 0 - OR ww.index_spool_cost > 0 - OR ww.index_spool_rows > 0 ) - - BEGIN - - RAISERROR(N'Inserting to #missing_index_xml', 0, 1) WITH NOWAIT; - WITH XMLNAMESPACES ( 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) - INSERT #missing_index_xml - SELECT qp.query_hash, - qp.sql_handle, - c.mg.value('@Impact', 'FLOAT') AS Impact, - c.mg.query('.') AS cmg - FROM #query_plan AS qp - CROSS APPLY qp.query_plan.nodes('//p:MissingIndexes/p:MissingIndexGroup') AS c(mg) - WHERE qp.query_hash IS NOT NULL - AND c.mg.value('@Impact', 'FLOAT') > 70.0 - OPTION (RECOMPILE); - - RAISERROR(N'Inserting to #missing_index_schema', 0, 1) WITH NOWAIT; - WITH XMLNAMESPACES ( 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) - INSERT #missing_index_schema - SELECT mix.query_hash, mix.sql_handle, mix.impact, - c.mi.value('@Database', 'NVARCHAR(128)'), - c.mi.value('@Schema', 'NVARCHAR(128)'), - c.mi.value('@Table', 'NVARCHAR(128)'), - c.mi.query('.') - FROM #missing_index_xml AS mix - CROSS APPLY mix.index_xml.nodes('//p:MissingIndex') AS c(mi) - OPTION (RECOMPILE); - - RAISERROR(N'Inserting to #missing_index_usage', 0, 1) WITH NOWAIT; - WITH XMLNAMESPACES ( 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) - INSERT #missing_index_usage - SELECT ms.query_hash, ms.sql_handle, ms.impact, ms.database_name, ms.schema_name, ms.table_name, - c.cg.value('@Usage', 'NVARCHAR(128)'), - c.cg.query('.') - FROM #missing_index_schema ms - CROSS APPLY ms.index_xml.nodes('//p:ColumnGroup') AS c(cg) - OPTION (RECOMPILE); - - RAISERROR(N'Inserting to #missing_index_detail', 0, 1) WITH NOWAIT; - WITH XMLNAMESPACES ( 'http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p ) - INSERT #missing_index_detail - SELECT miu.query_hash, - miu.sql_handle, - miu.impact, - miu.database_name, - miu.schema_name, - miu.table_name, - miu.usage, - c.c.value('@Name', 'NVARCHAR(128)') - FROM #missing_index_usage AS miu - CROSS APPLY miu.index_xml.nodes('//p:Column') AS c(c) - OPTION (RECOMPILE); - - RAISERROR(N'Inserting to #missing_index_pretty', 0, 1) WITH NOWAIT; - INSERT #missing_index_pretty - SELECT DISTINCT m.query_hash, m.sql_handle, m.impact, m.database_name, m.schema_name, m.table_name - , STUFF(( SELECT DISTINCT N', ' + ISNULL(m2.column_name, '') AS column_name - FROM #missing_index_detail AS m2 - WHERE m2.usage = 'EQUALITY' - AND m.query_hash = m2.query_hash - AND m.sql_handle = m2.sql_handle - AND m.impact = m2.impact - AND m.database_name = m2.database_name - AND m.schema_name = m2.schema_name - AND m.table_name = m2.table_name - FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 2, N'') AS equality - , STUFF(( SELECT DISTINCT N', ' + ISNULL(m2.column_name, '') AS column_name - FROM #missing_index_detail AS m2 - WHERE m2.usage = 'INEQUALITY' - AND m.query_hash = m2.query_hash - AND m.sql_handle = m2.sql_handle - AND m.impact = m2.impact - AND m.database_name = m2.database_name - AND m.schema_name = m2.schema_name - AND m.table_name = m2.table_name - FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 2, N'') AS inequality - , STUFF(( SELECT DISTINCT N', ' + ISNULL(m2.column_name, '') AS column_name - FROM #missing_index_detail AS m2 - WHERE m2.usage = 'INCLUDE' - AND m.query_hash = m2.query_hash - AND m.sql_handle = m2.sql_handle - AND m.impact = m2.impact - AND m.database_name = m2.database_name - AND m.schema_name = m2.schema_name - AND m.table_name = m2.table_name - FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 2, N'') AS [include], - 0 AS is_spool - FROM #missing_index_detail AS m - GROUP BY m.query_hash, m.sql_handle, m.impact, m.database_name, m.schema_name, m.table_name - OPTION (RECOMPILE); - - RAISERROR(N'Inserting to #index_spool_ugly', 0, 1) WITH NOWAIT; - WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) - INSERT #index_spool_ugly - (query_hash, sql_handle, impact, database_name, schema_name, table_name, equality, inequality, include) - SELECT r.query_hash, - r.sql_handle, - (c.n.value('@EstimateIO', 'FLOAT') + (c.n.value('@EstimateCPU', 'FLOAT'))) - / ( 1 * NULLIF(ww.query_cost, 0)) * 100 AS impact, - o.n.value('@Database', 'NVARCHAR(128)') AS output_database, - o.n.value('@Schema', 'NVARCHAR(128)') AS output_schema, - o.n.value('@Table', 'NVARCHAR(128)') AS output_table, - k.n.value('@Column', 'NVARCHAR(128)') AS range_column, - e.n.value('@Column', 'NVARCHAR(128)') AS expression_column, - o.n.value('@Column', 'NVARCHAR(128)') AS output_column - FROM #relop AS r - JOIN #working_warnings AS ww - ON ww.query_hash = r.query_hash - CROSS APPLY r.relop.nodes('/p:RelOp') AS c(n) - CROSS APPLY r.relop.nodes('/p:RelOp/p:OutputList/p:ColumnReference') AS o(n) - OUTER APPLY r.relop.nodes('/p:RelOp/p:Spool/p:SeekPredicateNew/p:SeekKeys/p:Prefix/p:RangeColumns/p:ColumnReference') AS k(n) - OUTER APPLY r.relop.nodes('/p:RelOp/p:Spool/p:SeekPredicateNew/p:SeekKeys/p:Prefix/p:RangeExpressions/p:ColumnReference') AS e(n) - WHERE r.relop.exist('/p:RelOp[@PhysicalOp="Index Spool" and @LogicalOp="Eager Spool"]') = 1 - - RAISERROR(N'Inserting to spools to #missing_index_pretty', 0, 1) WITH NOWAIT; - INSERT #missing_index_pretty - (query_hash, sql_handle, impact, database_name, schema_name, table_name, equality, inequality, include, is_spool) - SELECT DISTINCT - isu.query_hash, - isu.sql_handle, - isu.impact, - isu.database_name, - isu.schema_name, - isu.table_name - , STUFF(( SELECT DISTINCT N', ' + ISNULL(isu2.equality, '') AS column_name - FROM #index_spool_ugly AS isu2 - WHERE isu2.equality IS NOT NULL - AND isu.query_hash = isu2.query_hash - AND isu.sql_handle = isu2.sql_handle - AND isu.impact = isu2.impact - AND isu.database_name = isu2.database_name - AND isu.schema_name = isu2.schema_name - AND isu.table_name = isu2.table_name - FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 2, N'') AS equality - , STUFF(( SELECT DISTINCT N', ' + ISNULL(isu2.inequality, '') AS column_name - FROM #index_spool_ugly AS isu2 - WHERE isu2.inequality IS NOT NULL - AND isu.query_hash = isu2.query_hash - AND isu.sql_handle = isu2.sql_handle - AND isu.impact = isu2.impact - AND isu.database_name = isu2.database_name - AND isu.schema_name = isu2.schema_name - AND isu.table_name = isu2.table_name - FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 2, N'') AS inequality - , STUFF(( SELECT DISTINCT N', ' + ISNULL(isu2.include, '') AS column_name - FROM #index_spool_ugly AS isu2 - WHERE isu2.include IS NOT NULL - AND isu.query_hash = isu2.query_hash - AND isu.sql_handle = isu2.sql_handle - AND isu.impact = isu2.impact - AND isu.database_name = isu2.database_name - AND isu.schema_name = isu2.schema_name - AND isu.table_name = isu2.table_name - FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 2, N'') AS include, - 1 AS is_spool - FROM #index_spool_ugly AS isu - - - RAISERROR(N'Updating missing index information', 0, 1) WITH NOWAIT; - WITH missing AS ( - SELECT DISTINCT - mip.query_hash, - mip.sql_handle, - N'' - AS full_details - FROM #missing_index_pretty AS mip - GROUP BY mip.query_hash, mip.sql_handle, mip.impact - ) - UPDATE ww - SET ww.missing_indexes = m.full_details - FROM #working_warnings AS ww - JOIN missing AS m - ON m.sql_handle = ww.sql_handle - OPTION (RECOMPILE); - - RAISERROR(N'Filling in missing index blanks', 0, 1) WITH NOWAIT; - UPDATE ww - SET ww.missing_indexes = - CASE WHEN ww.missing_indexes IS NULL - THEN '' - ELSE ww.missing_indexes - END - FROM #working_warnings AS ww - OPTION (RECOMPILE); - -END -/*End Missing Index*/ - -RAISERROR(N'General query dispositions: frequent executions, long running, etc.', 0, 1) WITH NOWAIT; - -WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS p) -UPDATE b -SET b.frequent_execution = CASE WHEN wm.xpm > @execution_threshold THEN 1 END , - b.near_parallel = CASE WHEN b.query_cost BETWEEN @ctp * (1 - (@ctp_threshold_pct / 100.0)) AND @ctp THEN 1 END, - b.long_running = CASE WHEN wm.avg_duration > @long_running_query_warning_seconds THEN 1 - WHEN wm.max_duration > @long_running_query_warning_seconds THEN 1 - WHEN wm.avg_cpu_time > @long_running_query_warning_seconds THEN 1 - WHEN wm.max_cpu_time > @long_running_query_warning_seconds THEN 1 END, - b.is_key_lookup_expensive = CASE WHEN b.query_cost >= (@ctp / 2) AND b.key_lookup_cost >= b.query_cost * .5 THEN 1 END, - b.is_sort_expensive = CASE WHEN b.query_cost >= (@ctp / 2) AND b.sort_cost >= b.query_cost * .5 THEN 1 END, - b.is_remote_query_expensive = CASE WHEN b.remote_query_cost >= b.query_cost * .05 THEN 1 END, - b.is_unused_grant = CASE WHEN percent_memory_grant_used <= @memory_grant_warning_percent AND min_query_max_used_memory > @min_memory_per_query THEN 1 END, - b.long_running_low_cpu = CASE WHEN wm.avg_duration > wm.avg_cpu_time * 4 AND avg_cpu_time < 500. THEN 1 END, - b.low_cost_high_cpu = CASE WHEN b.query_cost < 10 AND wm.avg_cpu_time > 5000. THEN 1 END, - b.is_spool_expensive = CASE WHEN b.query_cost > (@ctp / 2) AND b.index_spool_cost >= b.query_cost * .1 THEN 1 END, - b.is_spool_more_rows = CASE WHEN b.index_spool_rows >= wm.min_rowcount THEN 1 END, - b.is_bad_estimate = CASE WHEN wm.avg_rowcount > 0 AND (b.estimated_rows * 1000 < wm.avg_rowcount OR b.estimated_rows > wm.avg_rowcount * 1000) THEN 1 END, - b.is_big_log = CASE WHEN wm.avg_log_bytes_used >= (@log_size_mb / 2.) THEN 1 END, - b.is_big_tempdb = CASE WHEN wm.avg_tempdb_space_used >= (@avg_tempdb_data_file / 2.) THEN 1 END -FROM #working_warnings AS b -JOIN #working_metrics AS wm -ON b.plan_id = wm.plan_id -AND b.query_id = wm.query_id -JOIN #working_plan_text AS wpt -ON b.plan_id = wpt.plan_id -AND b.query_id = wpt.query_id -OPTION (RECOMPILE); - - -RAISERROR('Populating Warnings column', 0, 1) WITH NOWAIT; -/* Populate warnings */ -UPDATE b -SET b.warnings = SUBSTRING( - CASE WHEN b.warning_no_join_predicate = 1 THEN ', No Join Predicate' ELSE '' END + - CASE WHEN b.compile_timeout = 1 THEN ', Compilation Timeout' ELSE '' END + - CASE WHEN b.compile_memory_limit_exceeded = 1 THEN ', Compile Memory Limit Exceeded' ELSE '' END + - CASE WHEN b.is_forced_plan = 1 THEN ', Forced Plan' ELSE '' END + - CASE WHEN b.is_forced_parameterized = 1 THEN ', Forced Parameterization' ELSE '' END + - CASE WHEN b.unparameterized_query = 1 THEN ', Unparameterized Query' ELSE '' END + - CASE WHEN b.missing_index_count > 0 THEN ', Missing Indexes (' + CAST(b.missing_index_count AS NVARCHAR(3)) + ')' ELSE '' END + - CASE WHEN b.unmatched_index_count > 0 THEN ', Unmatched Indexes (' + CAST(b.unmatched_index_count AS NVARCHAR(3)) + ')' ELSE '' END + - CASE WHEN b.is_cursor = 1 THEN ', Cursor' - + CASE WHEN b.is_optimistic_cursor = 1 THEN '; optimistic' ELSE '' END - + CASE WHEN b.is_forward_only_cursor = 0 THEN '; not forward only' ELSE '' END - + CASE WHEN b.is_cursor_dynamic = 1 THEN '; dynamic' ELSE '' END - + CASE WHEN b.is_fast_forward_cursor = 1 THEN '; fast forward' ELSE '' END - ELSE '' END + - CASE WHEN b.is_parallel = 1 THEN ', Parallel' ELSE '' END + - CASE WHEN b.near_parallel = 1 THEN ', Nearly Parallel' ELSE '' END + - CASE WHEN b.frequent_execution = 1 THEN ', Frequent Execution' ELSE '' END + - CASE WHEN b.plan_warnings = 1 THEN ', Plan Warnings' ELSE '' END + - CASE WHEN b.parameter_sniffing = 1 THEN ', Parameter Sniffing' ELSE '' END + - CASE WHEN b.long_running = 1 THEN ', Long Running Query' ELSE '' END + - CASE WHEN b.downlevel_estimator = 1 THEN ', Downlevel CE' ELSE '' END + - CASE WHEN b.implicit_conversions = 1 THEN ', Implicit Conversions' ELSE '' END + - CASE WHEN b.plan_multiple_plans = 1 THEN ', Multiple Plans' ELSE '' END + - CASE WHEN b.is_trivial = 1 THEN ', Trivial Plans' ELSE '' END + - CASE WHEN b.is_forced_serial = 1 THEN ', Forced Serialization' ELSE '' END + - CASE WHEN b.is_key_lookup_expensive = 1 THEN ', Expensive Key Lookup' ELSE '' END + - CASE WHEN b.is_remote_query_expensive = 1 THEN ', Expensive Remote Query' ELSE '' END + - CASE WHEN b.trace_flags_session IS NOT NULL THEN ', Session Level Trace Flag(s) Enabled: ' + b.trace_flags_session ELSE '' END + - CASE WHEN b.is_unused_grant = 1 THEN ', Unused Memory Grant' ELSE '' END + - CASE WHEN b.function_count > 0 THEN ', Calls ' + CONVERT(VARCHAR(10), b.function_count) + ' function(s)' ELSE '' END + - CASE WHEN b.clr_function_count > 0 THEN ', Calls ' + CONVERT(VARCHAR(10), b.clr_function_count) + ' CLR function(s)' ELSE '' END + - CASE WHEN b.is_table_variable = 1 THEN ', Table Variables' ELSE '' END + - CASE WHEN b.no_stats_warning = 1 THEN ', Columns With No Statistics' ELSE '' END + - CASE WHEN b.relop_warnings = 1 THEN ', Operator Warnings' ELSE '' END + - CASE WHEN b.is_table_scan = 1 THEN ', Table Scans' ELSE '' END + - CASE WHEN b.backwards_scan = 1 THEN ', Backwards Scans' ELSE '' END + - CASE WHEN b.forced_index = 1 THEN ', Forced Indexes' ELSE '' END + - CASE WHEN b.forced_seek = 1 THEN ', Forced Seeks' ELSE '' END + - CASE WHEN b.forced_scan = 1 THEN ', Forced Scans' ELSE '' END + - CASE WHEN b.columnstore_row_mode = 1 THEN ', ColumnStore Row Mode ' ELSE '' END + - CASE WHEN b.is_computed_scalar = 1 THEN ', Computed Column UDF ' ELSE '' END + - CASE WHEN b.is_sort_expensive = 1 THEN ', Expensive Sort' ELSE '' END + - CASE WHEN b.is_computed_filter = 1 THEN ', Filter UDF' ELSE '' END + - CASE WHEN b.index_ops >= 5 THEN ', >= 5 Indexes Modified' ELSE '' END + - CASE WHEN b.is_row_level = 1 THEN ', Row Level Security' ELSE '' END + - CASE WHEN b.is_spatial = 1 THEN ', Spatial Index' ELSE '' END + - CASE WHEN b.index_dml = 1 THEN ', Index DML' ELSE '' END + - CASE WHEN b.table_dml = 1 THEN ', Table DML' ELSE '' END + - CASE WHEN b.low_cost_high_cpu = 1 THEN ', Low Cost High CPU' ELSE '' END + - CASE WHEN b.long_running_low_cpu = 1 THEN + ', Long Running With Low CPU' ELSE '' END + - CASE WHEN b.stale_stats = 1 THEN + ', Statistics used have > 100k modifications in the last 7 days' ELSE '' END + - CASE WHEN b.is_adaptive = 1 THEN + ', Adaptive Joins' ELSE '' END + - CASE WHEN b.is_spool_expensive = 1 THEN + ', Expensive Index Spool' ELSE '' END + - CASE WHEN b.is_spool_more_rows = 1 THEN + ', Large Index Row Spool' ELSE '' END + - CASE WHEN b.is_bad_estimate = 1 THEN + ', Row estimate mismatch' ELSE '' END + - CASE WHEN b.is_big_log = 1 THEN + ', High log use' ELSE '' END + - CASE WHEN b.is_big_tempdb = 1 THEN ', High tempdb use' ELSE '' END + - CASE WHEN b.is_paul_white_electric = 1 THEN ', SWITCH!' ELSE '' END + - CASE WHEN b.is_row_goal = 1 THEN ', Row Goals' ELSE '' END + - CASE WHEN b.is_mstvf = 1 THEN ', MSTVFs' ELSE '' END + - CASE WHEN b.is_mm_join = 1 THEN ', Many to Many Merge' ELSE '' END + - CASE WHEN b.is_nonsargable = 1 THEN ', non-SARGables' ELSE '' END - , 2, 200000) -FROM #working_warnings b -OPTION (RECOMPILE); - - -END; -END TRY -BEGIN CATCH - RAISERROR (N'Failure generating warnings.', 0,1) WITH NOWAIT; - - IF @sql_select IS NOT NULL - BEGIN - SET @msg = N'Last @sql_select: ' + @sql_select; - RAISERROR(@msg, 0, 1) WITH NOWAIT; - END; - - SELECT @msg = @DatabaseName + N' database failed to process. ' + ERROR_MESSAGE(), @error_severity = ERROR_SEVERITY(), @error_state = ERROR_STATE(); - RAISERROR (@msg, @error_severity, @error_state) WITH NOWAIT; - - - WHILE @@TRANCOUNT > 0 - ROLLBACK; - - RETURN; -END CATCH; - - -BEGIN TRY -BEGIN - -RAISERROR(N'Checking for parameter sniffing symptoms', 0, 1) WITH NOWAIT; - -UPDATE b -SET b.parameter_sniffing_symptoms = -CASE WHEN b.count_executions < 2 THEN 'Too few executions to compare (< 2).' - ELSE - SUBSTRING( - /*Duration*/ - CASE WHEN (b.min_duration * 100) < (b.avg_duration) THEN ', Fast sometimes' ELSE '' END + - CASE WHEN (b.max_duration) > (b.avg_duration * 100) THEN ', Slow sometimes' ELSE '' END + - CASE WHEN (b.last_duration * 100) < (b.avg_duration) THEN ', Fast last run' ELSE '' END + - CASE WHEN (b.last_duration) > (b.avg_duration * 100) THEN ', Slow last run' ELSE '' END + - /*CPU*/ - CASE WHEN (b.min_cpu_time / b.avg_dop) * 100 < (b.avg_cpu_time / b.avg_dop) THEN ', Low CPU sometimes' ELSE '' END + - CASE WHEN (b.max_cpu_time / b.max_dop) > (b.avg_cpu_time / b.avg_dop) * 100 THEN ', High CPU sometimes' ELSE '' END + - CASE WHEN (b.last_cpu_time / b.last_dop) * 100 < (b.avg_cpu_time / b.avg_dop) THEN ', Low CPU last run' ELSE '' END + - CASE WHEN (b.last_cpu_time / b.last_dop) > (b.avg_cpu_time / b.avg_dop) * 100 THEN ', High CPU last run' ELSE '' END + - /*Logical Reads*/ - CASE WHEN (b.min_logical_io_reads * 100) < (b.avg_logical_io_reads) THEN ', Low reads sometimes' ELSE '' END + - CASE WHEN (b.max_logical_io_reads) > (b.avg_logical_io_reads * 100) THEN ', High reads sometimes' ELSE '' END + - CASE WHEN (b.last_logical_io_reads * 100) < (b.avg_logical_io_reads) THEN ', Low reads last run' ELSE '' END + - CASE WHEN (b.last_logical_io_reads) > (b.avg_logical_io_reads * 100) THEN ', High reads last run' ELSE '' END + - /*Logical Writes*/ - CASE WHEN (b.min_logical_io_writes * 100) < (b.avg_logical_io_writes) THEN ', Low writes sometimes' ELSE '' END + - CASE WHEN (b.max_logical_io_writes) > (b.avg_logical_io_writes * 100) THEN ', High writes sometimes' ELSE '' END + - CASE WHEN (b.last_logical_io_writes * 100) < (b.avg_logical_io_writes) THEN ', Low writes last run' ELSE '' END + - CASE WHEN (b.last_logical_io_writes) > (b.avg_logical_io_writes * 100) THEN ', High writes last run' ELSE '' END + - /*Physical Reads*/ - CASE WHEN (b.min_physical_io_reads * 100) < (b.avg_physical_io_reads) THEN ', Low physical reads sometimes' ELSE '' END + - CASE WHEN (b.max_physical_io_reads) > (b.avg_physical_io_reads * 100) THEN ', High physical reads sometimes' ELSE '' END + - CASE WHEN (b.last_physical_io_reads * 100) < (b.avg_physical_io_reads) THEN ', Low physical reads last run' ELSE '' END + - CASE WHEN (b.last_physical_io_reads) > (b.avg_physical_io_reads * 100) THEN ', High physical reads last run' ELSE '' END + - /*Memory*/ - CASE WHEN (b.min_query_max_used_memory * 100) < (b.avg_query_max_used_memory) THEN ', Low memory sometimes' ELSE '' END + - CASE WHEN (b.max_query_max_used_memory) > (b.avg_query_max_used_memory * 100) THEN ', High memory sometimes' ELSE '' END + - CASE WHEN (b.last_query_max_used_memory * 100) < (b.avg_query_max_used_memory) THEN ', Low memory last run' ELSE '' END + - CASE WHEN (b.last_query_max_used_memory) > (b.avg_query_max_used_memory * 100) THEN ', High memory last run' ELSE '' END + - /*Duration*/ - CASE WHEN b.min_rowcount * 100 < b.avg_rowcount THEN ', Low row count sometimes' ELSE '' END + - CASE WHEN b.max_rowcount > b.avg_rowcount * 100 THEN ', High row count sometimes' ELSE '' END + - CASE WHEN b.last_rowcount * 100 < b.avg_rowcount THEN ', Low row count run' ELSE '' END + - CASE WHEN b.last_rowcount > b.avg_rowcount * 100 THEN ', High row count last run' ELSE '' END + - /*DOP*/ - CASE WHEN b.min_dop <> b.max_dop THEN ', Serial sometimes' ELSE '' END + - CASE WHEN b.min_dop <> b.max_dop AND b.last_dop = 1 THEN ', Serial last run' ELSE '' END + - CASE WHEN b.min_dop <> b.max_dop AND b.last_dop > 1 THEN ', Parallel last run' ELSE '' END + - /*tempdb*/ - CASE WHEN b.min_tempdb_space_used * 100 < b.avg_tempdb_space_used THEN ', Low tempdb sometimes' ELSE '' END + - CASE WHEN b.max_tempdb_space_used > b.avg_tempdb_space_used * 100 THEN ', High tempdb sometimes' ELSE '' END + - CASE WHEN b.last_tempdb_space_used * 100 < b.avg_tempdb_space_used THEN ', Low tempdb run' ELSE '' END + - CASE WHEN b.last_tempdb_space_used > b.avg_tempdb_space_used * 100 THEN ', High tempdb last run' ELSE '' END + - /*tlog*/ - CASE WHEN b.min_log_bytes_used * 100 < b.avg_log_bytes_used THEN ', Low log use sometimes' ELSE '' END + - CASE WHEN b.max_log_bytes_used > b.avg_log_bytes_used * 100 THEN ', High log use sometimes' ELSE '' END + - CASE WHEN b.last_log_bytes_used * 100 < b.avg_log_bytes_used THEN ', Low log use run' ELSE '' END + - CASE WHEN b.last_log_bytes_used > b.avg_log_bytes_used * 100 THEN ', High log use last run' ELSE '' END - , 2, 200000) - END -FROM #working_metrics AS b -OPTION (RECOMPILE); - -END; -END TRY -BEGIN CATCH - RAISERROR (N'Failure analyzing parameter sniffing', 0,1) WITH NOWAIT; - - IF @sql_select IS NOT NULL - BEGIN - SET @msg = N'Last @sql_select: ' + @sql_select; - RAISERROR(@msg, 0, 1) WITH NOWAIT; - END; - - SELECT @msg = @DatabaseName + N' database failed to process. ' + ERROR_MESSAGE(), @error_severity = ERROR_SEVERITY(), @error_state = ERROR_STATE(); - RAISERROR (@msg, @error_severity, @error_state) WITH NOWAIT; - - - WHILE @@TRANCOUNT > 0 - ROLLBACK; - - RETURN; -END CATCH; - -BEGIN TRY - -BEGIN - -IF (@Failed = 0 AND @ExportToExcel = 0 AND @SkipXML = 0) -BEGIN - -RAISERROR(N'Returning regular results', 0, 1) WITH NOWAIT; - -WITH x AS ( -SELECT wpt.database_name, ww.query_cost, wm.plan_id, wm.query_id, wm.query_id_all_plan_ids, wpt.query_sql_text, wm.proc_or_function_name, wpt.query_plan_xml, ww.warnings, wpt.pattern, - wm.parameter_sniffing_symptoms, wpt.top_three_waits, ww.missing_indexes, ww.implicit_conversion_info, ww.cached_execution_parameters, wm.count_executions, wm.count_compiles, wm.total_cpu_time, wm.avg_cpu_time, - wm.total_duration, wm.avg_duration, wm.total_logical_io_reads, wm.avg_logical_io_reads, - wm.total_physical_io_reads, wm.avg_physical_io_reads, wm.total_logical_io_writes, wm.avg_logical_io_writes, wm.total_rowcount, wm.avg_rowcount, - wm.total_query_max_used_memory, wm.avg_query_max_used_memory, wm.total_tempdb_space_used, wm.avg_tempdb_space_used, - wm.total_log_bytes_used, wm.avg_log_bytes_used, wm.total_num_physical_io_reads, wm.avg_num_physical_io_reads, - wm.first_execution_time, wm.last_execution_time, wpt.last_force_failure_reason_desc, wpt.context_settings, ROW_NUMBER() OVER (PARTITION BY wm.plan_id, wm.query_id, wm.last_execution_time ORDER BY wm.plan_id) AS rn -FROM #working_plan_text AS wpt -JOIN #working_warnings AS ww - ON wpt.plan_id = ww.plan_id - AND wpt.query_id = ww.query_id -JOIN #working_metrics AS wm - ON wpt.plan_id = wm.plan_id - AND wpt.query_id = wm.query_id -) -SELECT * -FROM x -WHERE x.rn = 1 -ORDER BY x.last_execution_time -OPTION (RECOMPILE); - -END; - -IF (@Failed = 1 AND @ExportToExcel = 0 AND @SkipXML = 0) -BEGIN - -RAISERROR(N'Returning results for failed queries', 0, 1) WITH NOWAIT; - -WITH x AS ( -SELECT wpt.database_name, ww.query_cost, wm.plan_id, wm.query_id, wm.query_id_all_plan_ids, wpt.query_sql_text, wm.proc_or_function_name, wpt.query_plan_xml, ww.warnings, wpt.pattern, - wm.parameter_sniffing_symptoms, wpt.last_force_failure_reason_desc, wpt.top_three_waits, ww.missing_indexes, ww.implicit_conversion_info, ww.cached_execution_parameters, - wm.count_executions, wm.count_compiles, wm.total_cpu_time, wm.avg_cpu_time, - wm.total_duration, wm.avg_duration, wm.total_logical_io_reads, wm.avg_logical_io_reads, - wm.total_physical_io_reads, wm.avg_physical_io_reads, wm.total_logical_io_writes, wm.avg_logical_io_writes, wm.total_rowcount, wm.avg_rowcount, - wm.total_query_max_used_memory, wm.avg_query_max_used_memory, wm.total_tempdb_space_used, wm.avg_tempdb_space_used, - wm.total_log_bytes_used, wm.avg_log_bytes_used, wm.total_num_physical_io_reads, wm.avg_num_physical_io_reads, - wm.first_execution_time, wm.last_execution_time, wpt.context_settings, ROW_NUMBER() OVER (PARTITION BY wm.plan_id, wm.query_id, wm.last_execution_time ORDER BY wm.plan_id) AS rn -FROM #working_plan_text AS wpt -JOIN #working_warnings AS ww - ON wpt.plan_id = ww.plan_id - AND wpt.query_id = ww.query_id -JOIN #working_metrics AS wm - ON wpt.plan_id = wm.plan_id - AND wpt.query_id = wm.query_id -) -SELECT * -FROM x -WHERE x.rn = 1 -ORDER BY x.last_execution_time -OPTION (RECOMPILE); - -END; - -IF (@ExportToExcel = 1 AND @SkipXML = 0) -BEGIN - -RAISERROR(N'Returning results for Excel export', 0, 1) WITH NOWAIT; - -UPDATE #working_plan_text -SET query_sql_text = SUBSTRING(REPLACE(REPLACE(REPLACE(LTRIM(RTRIM(query_sql_text)),' ','<>'),'><',''),'<>',' '), 1, 31000) -OPTION (RECOMPILE); - -WITH x AS ( -SELECT wpt.database_name, ww.query_cost, wm.plan_id, wm.query_id, wm.query_id_all_plan_ids, wpt.query_sql_text, wm.proc_or_function_name, ww.warnings, wpt.pattern, - wm.parameter_sniffing_symptoms, wpt.last_force_failure_reason_desc, wpt.top_three_waits, wm.count_executions, wm.count_compiles, wm.total_cpu_time, wm.avg_cpu_time, - wm.total_duration, wm.avg_duration, wm.total_logical_io_reads, wm.avg_logical_io_reads, - wm.total_physical_io_reads, wm.avg_physical_io_reads, wm.total_logical_io_writes, wm.avg_logical_io_writes, wm.total_rowcount, wm.avg_rowcount, - wm.total_query_max_used_memory, wm.avg_query_max_used_memory, wm.total_tempdb_space_used, wm.avg_tempdb_space_used, - wm.total_log_bytes_used, wm.avg_log_bytes_used, wm.total_num_physical_io_reads, wm.avg_num_physical_io_reads, - wm.first_execution_time, wm.last_execution_time, wpt.context_settings, ROW_NUMBER() OVER (PARTITION BY wm.plan_id, wm.query_id, wm.last_execution_time ORDER BY wm.plan_id) AS rn -FROM #working_plan_text AS wpt -JOIN #working_warnings AS ww - ON wpt.plan_id = ww.plan_id - AND wpt.query_id = ww.query_id -JOIN #working_metrics AS wm - ON wpt.plan_id = wm.plan_id - AND wpt.query_id = wm.query_id -) -SELECT * -FROM x -WHERE x.rn = 1 -ORDER BY x.last_execution_time -OPTION (RECOMPILE); - -END; - -IF (@ExportToExcel = 0 AND @SkipXML = 1) -BEGIN - -RAISERROR(N'Returning results for skipped XML', 0, 1) WITH NOWAIT; - -WITH x AS ( -SELECT wpt.database_name, wm.plan_id, wm.query_id, wm.query_id_all_plan_ids, wpt.query_sql_text, wpt.query_plan_xml, wpt.pattern, - wm.parameter_sniffing_symptoms, wpt.top_three_waits, wm.count_executions, wm.count_compiles, wm.total_cpu_time, wm.avg_cpu_time, - wm.total_duration, wm.avg_duration, wm.total_logical_io_reads, wm.avg_logical_io_reads, - wm.total_physical_io_reads, wm.avg_physical_io_reads, wm.total_logical_io_writes, wm.avg_logical_io_writes, wm.total_rowcount, wm.avg_rowcount, - wm.total_query_max_used_memory, wm.avg_query_max_used_memory, wm.total_tempdb_space_used, wm.avg_tempdb_space_used, - wm.total_log_bytes_used, wm.avg_log_bytes_used, wm.total_num_physical_io_reads, wm.avg_num_physical_io_reads, - wm.first_execution_time, wm.last_execution_time, wpt.last_force_failure_reason_desc, wpt.context_settings, ROW_NUMBER() OVER (PARTITION BY wm.plan_id, wm.query_id, wm.last_execution_time ORDER BY wm.plan_id) AS rn -FROM #working_plan_text AS wpt -JOIN #working_metrics AS wm - ON wpt.plan_id = wm.plan_id - AND wpt.query_id = wm.query_id -) -SELECT * -FROM x -WHERE x.rn = 1 -ORDER BY x.last_execution_time -OPTION (RECOMPILE); - -END; - -END; -END TRY -BEGIN CATCH - RAISERROR (N'Failure returning results', 0,1) WITH NOWAIT; - - IF @sql_select IS NOT NULL - BEGIN - SET @msg = N'Last @sql_select: ' + @sql_select; - RAISERROR(@msg, 0, 1) WITH NOWAIT; - END; - - SELECT @msg = @DatabaseName + N' database failed to process. ' + ERROR_MESSAGE(), @error_severity = ERROR_SEVERITY(), @error_state = ERROR_STATE(); - RAISERROR (@msg, @error_severity, @error_state) WITH NOWAIT; - - - WHILE @@TRANCOUNT > 0 - ROLLBACK; - - RETURN; -END CATCH; - -BEGIN TRY -BEGIN - -IF (@ExportToExcel = 0 AND @HideSummary = 0 AND @SkipXML = 0) -BEGIN - RAISERROR('Building query plan summary data.', 0, 1) WITH NOWAIT; - - /* Build summary data */ - IF EXISTS (SELECT 1/0 - FROM #working_warnings - WHERE frequent_execution = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 1, - 100, - 'Execution Pattern', - 'Frequently Executed Queries', - 'https://www.brentozar.com/blitzcache/frequently-executed-queries/', - 'Queries are being executed more than ' - + CAST (@execution_threshold AS VARCHAR(5)) - + ' times per minute. This can put additional load on the server, even when queries are lightweight.') ; - - IF EXISTS (SELECT 1/0 - FROM #working_warnings - WHERE parameter_sniffing = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 2, - 50, - 'Parameterization', - 'Parameter Sniffing', - 'https://www.brentozar.com/blitzcache/parameter-sniffing/', - 'There are signs of parameter sniffing (wide variance in rows return or time to execute). Investigate query patterns and tune code appropriately.') ; - - /* Forced execution plans */ - IF EXISTS (SELECT 1/0 - FROM #working_warnings - WHERE is_forced_plan = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 3, - 5, - 'Parameterization', - 'Forced Plans', - 'https://www.brentozar.com/blitzcache/forced-plans/', - 'Execution plans have been compiled with forced plans, either through FORCEPLAN, plan guides, or forced parameterization. This will make general tuning efforts less effective.'); - - IF EXISTS (SELECT 1/0 - FROM #working_warnings - WHERE is_cursor = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 4, - 200, - 'Cursors', - 'Cursors', - 'https://www.brentozar.com/blitzcache/cursors-found-slow-queries/', - 'There are cursors in the plan cache. This is neither good nor bad, but it is a thing. Cursors are weird in SQL Server.'); - - IF EXISTS (SELECT 1/0 - FROM #working_warnings - WHERE is_cursor = 1 - AND is_optimistic_cursor = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 4, - 200, - 'Cursors', - 'Optimistic Cursors', - 'https://www.brentozar.com/blitzcache/cursors-found-slow-queries/', - 'There are optimistic cursors in the plan cache, which can harm performance.'); - - IF EXISTS (SELECT 1/0 - FROM #working_warnings - WHERE is_cursor = 1 - AND is_forward_only_cursor = 0 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 4, - 200, - 'Cursors', - 'Non-forward Only Cursors', - 'https://www.brentozar.com/blitzcache/cursors-found-slow-queries/', - 'There are non-forward only cursors in the plan cache, which can harm performance.'); - - IF EXISTS (SELECT 1/0 - FROM #working_warnings - WHERE is_cursor = 1 - AND is_cursor_dynamic = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (4, - 200, - 'Cursors', - 'Dynamic Cursors', - 'https://www.brentozar.com/blitzcache/cursors-found-slow-queries/', - 'Dynamic Cursors inhibit parallelism!.'); - - IF EXISTS (SELECT 1/0 - FROM #working_warnings - WHERE is_cursor = 1 - AND is_fast_forward_cursor = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (4, - 200, - 'Cursors', - 'Fast Forward Cursors', - 'https://www.brentozar.com/blitzcache/cursors-found-slow-queries/', - 'Fast forward cursors inhibit parallelism!.'); - - IF EXISTS (SELECT 1/0 - FROM #working_warnings - WHERE is_forced_parameterized = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 5, - 50, - 'Parameterization', - 'Forced Parameterization', - 'https://www.brentozar.com/blitzcache/forced-parameterization/', - 'Execution plans have been compiled with forced parameterization.') ; - - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.is_parallel = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 6, - 200, - 'Execution Plans', - 'Parallelism', - 'https://www.brentozar.com/blitzcache/parallel-plans-detected/', - 'Parallel plans detected. These warrant investigation, but are neither good nor bad.') ; - - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.near_parallel = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 7, - 200, - 'Execution Plans', - 'Nearly Parallel', - 'https://www.brentozar.com/blitzcache/query-cost-near-cost-threshold-parallelism/', - 'Queries near the cost threshold for parallelism. These may go parallel when you least expect it.') ; - - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.plan_warnings = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 8, - 50, - 'Execution Plans', - 'Query Plan Warnings', - 'https://www.brentozar.com/blitzcache/query-plan-warnings/', - 'Warnings detected in execution plans. SQL Server is telling you that something bad is going on that requires your attention.') ; - - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.long_running = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 9, - 50, - 'Performance', - 'Long Running Queries', - 'https://www.brentozar.com/blitzcache/long-running-queries/', - 'Long running queries have been found. These are queries with an average duration longer than ' - + CAST(@long_running_query_warning_seconds / 1000 / 1000 AS VARCHAR(5)) - + ' second(s). These queries should be investigated for additional tuning options.') ; - - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.missing_index_count > 0 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 10, - 50, - 'Performance', - 'Missing Index Request', - 'https://www.brentozar.com/blitzcache/missing-index-request/', - 'Queries found with missing indexes.'); - - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.downlevel_estimator = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 13, - 200, - 'Cardinality', - 'Legacy Cardinality Estimator in Use', - 'https://www.brentozar.com/blitzcache/legacy-cardinality-estimator/', - 'A legacy cardinality estimator is being used by one or more queries. Investigate whether you need to be using this cardinality estimator. This may be caused by compatibility levels, global trace flags, or query level trace flags.'); - - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.implicit_conversions = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 14, - 50, - 'Performance', - 'Implicit Conversions', - 'https://www.brentozar.com/go/implicit', - 'One or more queries are comparing two fields that are not of the same data type.') ; - - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE busy_loops = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 16, - 100, - 'Performance', - 'Busy Loops', - 'https://www.brentozar.com/blitzcache/busy-loops/', - 'Operations have been found that are executed 100 times more often than the number of rows returned by each iteration. This is an indicator that something is off in query execution.'); - - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE tvf_join = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 17, - 50, - 'Performance', - 'Joining to table valued functions', - 'https://www.brentozar.com/blitzcache/tvf-join/', - 'Execution plans have been found that join to table valued functions (TVFs). TVFs produce inaccurate estimates of the number of rows returned and can lead to any number of query plan problems.'); - - IF EXISTS (SELECT 1/0 - FROM #working_warnings - WHERE compile_timeout = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 18, - 50, - 'Execution Plans', - 'Compilation timeout', - 'https://www.brentozar.com/blitzcache/compilation-timeout/', - 'Query compilation timed out for one or more queries. SQL Server did not find a plan that meets acceptable performance criteria in the time allotted so the best guess was returned. There is a very good chance that this plan isn''t even below average - it''s probably terrible.'); - - IF EXISTS (SELECT 1/0 - FROM #working_warnings - WHERE compile_memory_limit_exceeded = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 19, - 50, - 'Execution Plans', - 'Compilation memory limit exceeded', - 'https://www.brentozar.com/blitzcache/compile-memory-limit-exceeded/', - 'The optimizer has a limited amount of memory available. One or more queries are complex enough that SQL Server was unable to allocate enough memory to fully optimize the query. A best fit plan was found, and it''s probably terrible.'); - - IF EXISTS (SELECT 1/0 - FROM #working_warnings - WHERE warning_no_join_predicate = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 20, - 10, - 'Execution Plans', - 'No join predicate', - 'https://www.brentozar.com/blitzcache/no-join-predicate/', - 'Operators in a query have no join predicate. This means that all rows from one table will be matched with all rows from anther table producing a Cartesian product. That''s a whole lot of rows. This may be your goal, but it''s important to investigate why this is happening.'); - - IF EXISTS (SELECT 1/0 - FROM #working_warnings - WHERE plan_multiple_plans = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 21, - 200, - 'Execution Plans', - 'Multiple execution plans', - 'https://www.brentozar.com/blitzcache/multiple-plans/', - 'Queries exist with multiple execution plans (as determined by query_plan_hash). Investigate possible ways to parameterize these queries or otherwise reduce the plan count.'); - - IF EXISTS (SELECT 1/0 - FROM #working_warnings - WHERE unmatched_index_count > 0 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 22, - 100, - 'Performance', - 'Unmatched indexes', - 'https://www.brentozar.com/blitzcache/unmatched-indexes', - 'An index could have been used, but SQL Server chose not to use it - likely due to parameterization and filtered indexes.'); - - IF EXISTS (SELECT 1/0 - FROM #working_warnings - WHERE unparameterized_query = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 23, - 100, - 'Parameterization', - 'Unparameterized queries', - 'https://www.brentozar.com/blitzcache/unparameterized-queries', - 'Unparameterized queries found. These could be ad hoc queries, data exploration, or queries using "OPTIMIZE FOR UNKNOWN".'); - - IF EXISTS (SELECT 1/0 - FROM #working_warnings - WHERE is_trivial = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 24, - 100, - 'Execution Plans', - 'Trivial Plans', - 'https://www.brentozar.com/blitzcache/trivial-plans', - 'Trivial plans get almost no optimization. If you''re finding these in the top worst queries, something may be going wrong.'); - - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.is_forced_serial= 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 25, - 10, - 'Execution Plans', - 'Forced Serialization', - 'https://www.brentozar.com/blitzcache/forced-serialization/', - 'Something in your plan is forcing a serial query. Further investigation is needed if this is not by design.') ; - - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.is_key_lookup_expensive= 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 26, - 100, - 'Execution Plans', - 'Expensive Key Lookups', - 'https://www.brentozar.com/blitzcache/expensive-key-lookups/', - 'There''s a key lookup in your plan that costs >=50% of the total plan cost.') ; - - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.is_remote_query_expensive= 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 28, - 100, - 'Execution Plans', - 'Expensive Remote Query', - 'https://www.brentozar.com/blitzcache/expensive-remote-query/', - 'There''s a remote query in your plan that costs >=50% of the total plan cost.') ; - - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.trace_flags_session IS NOT NULL - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 29, - 100, - 'Trace Flags', - 'Session Level Trace Flags Enabled', - 'https://www.brentozar.com/blitz/trace-flags-enabled-globally/', - 'Someone is enabling session level Trace Flags in a query.') ; - - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.is_unused_grant IS NOT NULL - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 30, - 100, - 'Unused memory grants', - 'Queries are asking for more memory than they''re using', - 'https://www.brentozar.com/blitzcache/unused-memory-grants/', - 'Queries have large unused memory grants. This can cause concurrency issues, if queries are waiting a long time to get memory to run.') ; - - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.function_count > 0 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 31, - 100, - 'Compute Scalar That References A Function', - 'This could be trouble if you''re using Scalar Functions or MSTVFs', - 'https://www.brentozar.com/blitzcache/compute-scalar-functions/', - 'Both of these will force queries to run serially, run at least once per row, and may result in poor cardinality estimates.') ; - - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.clr_function_count > 0 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 32, - 100, - 'Compute Scalar That References A CLR Function', - 'This could be trouble if your CLR functions perform data access', - 'https://www.brentozar.com/blitzcache/compute-scalar-functions/', - 'May force queries to run serially, run at least once per row, and may result in poor cardinlity estimates.') ; - - - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.is_table_variable = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 33, - 100, - 'Table Variables detected', - 'Beware nasty side effects', - 'https://www.brentozar.com/blitzcache/table-variables/', - 'All modifications are single threaded, and selects have really low row estimates.') ; - - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.no_stats_warning = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 35, - 100, - 'Columns with no statistics', - 'Poor cardinality estimates may ensue', - 'https://www.brentozar.com/blitzcache/columns-no-statistics/', - 'Sometimes this happens with indexed views, other times because auto create stats is turned off.') ; - - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.relop_warnings = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 36, - 100, - 'Operator Warnings', - 'SQL is throwing operator level plan warnings', - 'https://www.brentozar.com/blitzcache/query-plan-warnings/', - 'Check the plan for more details.') ; - - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.is_table_scan = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 37, - 100, - 'Table Scans', - 'Your database has HEAPs', - 'https://www.brentozar.com/archive/2012/05/video-heaps/', - 'This may not be a problem. Run sp_BlitzIndex for more information.') ; - - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.backwards_scan = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 38, - 100, - 'Backwards Scans', - 'Indexes are being read backwards', - 'https://www.brentozar.com/blitzcache/backwards-scans/', - 'This isn''t always a problem. They can cause serial zones in plans, and may need an index to match sort order.') ; - - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.forced_index = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 39, - 100, - 'Index forcing', - 'Someone is using hints to force index usage', - 'https://www.brentozar.com/blitzcache/optimizer-forcing/', - 'This can cause inefficient plans, and will prevent missing index requests.') ; - - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.forced_seek = 1 - OR p.forced_scan = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 40, - 100, - 'Seek/Scan forcing', - 'Someone is using hints to force index seeks/scans', - 'https://www.brentozar.com/blitzcache/optimizer-forcing/', - 'This can cause inefficient plans by taking seek vs scan choice away from the optimizer.') ; - - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.columnstore_row_mode = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 41, - 100, - 'ColumnStore indexes operating in Row Mode', - 'Batch Mode is optimal for ColumnStore indexes', - 'https://www.brentozar.com/blitzcache/columnstore-indexes-operating-row-mode/', - 'ColumnStore indexes operating in Row Mode indicate really poor query choices.') ; - - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.is_computed_scalar = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 42, - 50, - 'Computed Columns Referencing Scalar UDFs', - 'This makes a whole lot of stuff run serially', - 'https://www.brentozar.com/blitzcache/computed-columns-referencing-functions/', - 'This can cause a whole mess of bad serializartion problems.') ; - - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.is_sort_expensive = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 43, - 100, - 'Execution Plans', - 'Expensive Sort', - 'https://www.brentozar.com/blitzcache/expensive-sorts/', - 'There''s a sort in your plan that costs >=50% of the total plan cost.') ; - - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.is_computed_filter = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 44, - 50, - 'Filters Referencing Scalar UDFs', - 'This forces serialization', - 'https://www.brentozar.com/blitzcache/compute-scalar-functions/', - 'Someone put a Scalar UDF in the WHERE clause!') ; - - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.index_ops >= 5 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 45, - 100, - 'Many Indexes Modified', - 'Write Queries Are Hitting >= 5 Indexes', - 'https://www.brentozar.com/blitzcache/many-indexes-modified/', - 'This can cause lots of hidden I/O -- Run sp_BlitzIndex for more information.') ; - - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.is_row_level = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 46, - 100, - 'Plan Confusion', - 'Row Level Security is in use', - 'https://www.brentozar.com/blitzcache/row-level-security/', - 'You may see a lot of confusing junk in your query plan.') ; - - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.is_spatial = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 47, - 200, - 'Spatial Abuse', - 'You hit a Spatial Index', - 'https://www.brentozar.com/blitzcache/spatial-indexes/', - 'Purely informational.') ; - - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.index_dml = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 48, - 150, - 'Index DML', - 'Indexes were created or dropped', - 'https://www.brentozar.com/blitzcache/index-dml/', - 'This can cause recompiles and stuff.') ; - - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.table_dml = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 49, - 150, - 'Table DML', - 'Tables were created or dropped', - 'https://www.brentozar.com/blitzcache/table-dml/', - 'This can cause recompiles and stuff.') ; - - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.long_running_low_cpu = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 50, - 150, - 'Long Running Low CPU', - 'You have a query that runs for much longer than it uses CPU', - 'https://www.brentozar.com/blitzcache/long-running-low-cpu/', - 'This can be a sign of blocking, linked servers, or poor client application code (ASYNC_NETWORK_IO).') ; - - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.low_cost_high_cpu = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 51, - 150, - 'Low Cost Query With High CPU', - 'You have a low cost query that uses a lot of CPU', - 'https://www.brentozar.com/blitzcache/low-cost-high-cpu/', - 'This can be a sign of functions or Dynamic SQL that calls black-box code.') ; - - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.stale_stats = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 52, - 150, - 'Biblical Statistics', - 'Statistics used in queries are >7 days old with >100k modifications', - 'https://www.brentozar.com/blitzcache/stale-statistics/', - 'Ever heard of updating statistics?') ; - - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.is_adaptive = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 53, - 150, - 'Adaptive joins', - 'This is pretty cool -- you''re living in the future.', - 'https://www.brentozar.com/blitzcache/adaptive-joins/', - 'Joe Sack rules.') ; - - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.is_spool_expensive = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 54, - 150, - 'Expensive Index Spool', - 'You have an index spool, this is usually a sign that there''s an index missing somewhere.', - 'https://www.brentozar.com/blitzcache/eager-index-spools/', - 'Check operator predicates and output for index definition guidance') ; - - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.is_spool_more_rows = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 55, - 150, - 'Index Spools Many Rows', - 'You have an index spool that spools more rows than the query returns', - 'https://www.brentozar.com/blitzcache/eager-index-spools/', - 'Check operator predicates and output for index definition guidance') ; - - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.is_bad_estimate = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 56, - 100, - 'Potentially bad cardinality estimates', - 'Estimated rows are different from average rows by a factor of 10000', - 'https://www.brentozar.com/blitzcache/bad-estimates/', - 'This may indicate a performance problem if mismatches occur regularly') ; - - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.is_big_log = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 57, - 100, - 'High transaction log use', - 'This query on average uses more than half of the transaction log', - 'http://michaeljswart.com/2014/09/take-care-when-scripting-batches/', - 'This is probably a sign that you need to start batching queries') ; - - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.is_big_tempdb = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 58, - 100, - 'High tempdb use', - 'This query uses more than half of a data file on average', - 'No URL yet', - 'You should take a look at tempdb waits to see if you''re having problems') ; - - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.is_row_goal = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (59, - 200, - 'Row Goals', - 'This query had row goals introduced', - 'https://www.brentozar.com/archive/2018/01/sql-server-2017-cu3-adds-optimizer-row-goal-information-query-plans/', - 'This can be good or bad, and should be investigated for high read queries') ; - - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.is_mstvf = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 60, - 100, - 'MSTVFs', - 'These have many of the same problems scalar UDFs have', - 'https://www.brentozar.com/blitzcache/tvf-join/', - 'Execution plans have been found that join to table valued functions (TVFs). TVFs produce inaccurate estimates of the number of rows returned and can lead to any number of query plan problems.'); - - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.is_mstvf = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (61, - 100, - 'Many to Many Merge', - 'These use secret worktables that could be doing lots of reads', - 'https://www.brentozar.com/archive/2018/04/many-mysteries-merge-joins/', - 'Occurs when join inputs aren''t known to be unique. Can be really bad when parallel.'); - - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.is_nonsargable = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (62, - 50, - 'Non-SARGable queries', - 'Queries may be using', - 'https://www.brentozar.com/blitzcache/non-sargable-predicates/', - 'Occurs when join inputs aren''t known to be unique. Can be really bad when parallel.'); - - IF EXISTS (SELECT 1/0 - FROM #working_warnings p - WHERE p.is_paul_white_electric = 1 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 998, - 200, - 'Is Paul White Electric?', - 'This query has a Switch operator in it!', - 'https://www.sql.kiwi/2013/06/hello-operator-my-switch-is-bored.html', - 'You should email this query plan to Paul: SQLkiwi at gmail dot com') ; - - - - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - SELECT - 999, - 200, - 'Database Level Statistics', - 'The database ' + sa.[database] + ' last had a stats update on ' + CONVERT(NVARCHAR(10), CONVERT(DATE, MAX(sa.last_update))) + ' and has ' + CONVERT(NVARCHAR(10), AVG(sa.modification_count)) + ' modifications on average.' AS Finding, - 'https://www.brentozar.com/blitzcache/stale-statistics/' AS URL, - 'Consider updating statistics more frequently,' AS Details - FROM #stats_agg AS sa - GROUP BY sa.[database] - HAVING MAX(sa.last_update) <= DATEADD(DAY, -7, SYSDATETIME()) - AND AVG(sa.modification_count) >= 100000; - - - IF EXISTS (SELECT 1/0 - FROM #trace_flags AS tf - WHERE tf.global_trace_flags IS NOT NULL - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 1000, - 255, - 'Global Trace Flags Enabled', - 'You have Global Trace Flags enabled on your server', - 'https://www.brentozar.com/blitz/trace-flags-enabled-globally/', - 'You have the following Global Trace Flags enabled: ' + (SELECT TOP 1 tf.global_trace_flags FROM #trace_flags AS tf WHERE tf.global_trace_flags IS NOT NULL)) ; - - - /* - Return worsts - */ - WITH worsts AS ( - SELECT gi.flat_date, - gi.start_range, - gi.end_range, - gi.total_avg_duration_ms, - gi.total_avg_cpu_time_ms, - gi.total_avg_logical_io_reads_mb, - gi.total_avg_physical_io_reads_mb, - gi.total_avg_logical_io_writes_mb, - gi.total_avg_query_max_used_memory_mb, - gi.total_rowcount, - gi.total_avg_log_bytes_mb, - gi.total_avg_tempdb_space, - gi.total_max_duration_ms, - gi.total_max_cpu_time_ms, - gi.total_max_logical_io_reads_mb, - gi.total_max_physical_io_reads_mb, - gi.total_max_logical_io_writes_mb, - gi.total_max_query_max_used_memory_mb, - gi.total_max_log_bytes_mb, - gi.total_max_tempdb_space, - CONVERT(NVARCHAR(20), gi.flat_date) AS worst_date, - CASE WHEN DATEPART(HOUR, gi.start_range) = 0 THEN ' midnight ' - WHEN DATEPART(HOUR, gi.start_range) <= 12 THEN CONVERT(NVARCHAR(3), DATEPART(HOUR, gi.start_range)) + 'am ' - WHEN DATEPART(HOUR, gi.start_range) > 12 THEN CONVERT(NVARCHAR(3), DATEPART(HOUR, gi.start_range) -12) + 'pm ' - END AS worst_start_time, - CASE WHEN DATEPART(HOUR, gi.end_range) = 0 THEN ' midnight ' - WHEN DATEPART(HOUR, gi.end_range) <= 12 THEN CONVERT(NVARCHAR(3), DATEPART(HOUR, gi.end_range)) + 'am ' - WHEN DATEPART(HOUR, gi.end_range) > 12 THEN CONVERT(NVARCHAR(3), DATEPART(HOUR, gi.end_range) -12) + 'pm ' - END AS worst_end_time - FROM #grouped_interval AS gi - ), /*averages*/ - duration_worst AS ( - SELECT TOP 1 'Your worst avg duration range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg - FROM worsts - ORDER BY worsts.total_avg_duration_ms DESC - ), - cpu_worst AS ( - SELECT TOP 1 'Your worst avg cpu range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg - FROM worsts - ORDER BY worsts.total_avg_cpu_time_ms DESC - ), - logical_reads_worst AS ( - SELECT TOP 1 'Your worst avg logical read range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg - FROM worsts - ORDER BY worsts.total_avg_logical_io_reads_mb DESC - ), - physical_reads_worst AS ( - SELECT TOP 1 'Your worst avg physical read range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg - FROM worsts - ORDER BY worsts.total_avg_physical_io_reads_mb DESC - ), - logical_writes_worst AS ( - SELECT TOP 1 'Your worst avg logical write range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg - FROM worsts - ORDER BY worsts.total_avg_logical_io_writes_mb DESC - ), - memory_worst AS ( - SELECT TOP 1 'Your worst avg memory range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg - FROM worsts - ORDER BY worsts.total_avg_query_max_used_memory_mb DESC - ), - rowcount_worst AS ( - SELECT TOP 1 'Your worst avg row count range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg - FROM worsts - ORDER BY worsts.total_rowcount DESC - ), - logbytes_worst AS ( - SELECT TOP 1 'Your worst avg log bytes range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg - FROM worsts - ORDER BY worsts.total_avg_log_bytes_mb DESC - ), - tempdb_worst AS ( - SELECT TOP 1 'Your worst avg tempdb range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg - FROM worsts - ORDER BY worsts.total_avg_tempdb_space DESC - )/*maxes*/, - max_duration_worst AS ( - SELECT TOP 1 'Your worst max duration range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg - FROM worsts - ORDER BY worsts.total_max_duration_ms DESC - ), - max_cpu_worst AS ( - SELECT TOP 1 'Your worst max cpu range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg - FROM worsts - ORDER BY worsts.total_max_cpu_time_ms DESC - ), - max_logical_reads_worst AS ( - SELECT TOP 1 'Your worst max logical read range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg - FROM worsts - ORDER BY worsts.total_max_logical_io_reads_mb DESC - ), - max_physical_reads_worst AS ( - SELECT TOP 1 'Your worst max physical read range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg - FROM worsts - ORDER BY worsts.total_max_physical_io_reads_mb DESC - ), - max_logical_writes_worst AS ( - SELECT TOP 1 'Your worst max logical write range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg - FROM worsts - ORDER BY worsts.total_max_logical_io_writes_mb DESC - ), - max_memory_worst AS ( - SELECT TOP 1 'Your worst max memory range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg - FROM worsts - ORDER BY worsts.total_max_query_max_used_memory_mb DESC - ), - max_logbytes_worst AS ( - SELECT TOP 1 'Your worst max log bytes range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg - FROM worsts - ORDER BY worsts.total_max_log_bytes_mb DESC - ), - max_tempdb_worst AS ( - SELECT TOP 1 'Your worst max tempdb range was on ' + worsts.worst_date + ' between ' + worsts.worst_start_time + ' and ' + worsts.worst_end_time + '.' AS msg - FROM worsts - ORDER BY worsts.total_max_tempdb_space DESC - ) - INSERT #warning_results ( CheckID, Priority, FindingsGroup, Finding, URL, Details ) - /*averages*/ - SELECT 1002, 255, 'Worsts', 'Worst Avg Duration', 'N/A', duration_worst.msg - FROM duration_worst - UNION ALL - SELECT 1002, 255, 'Worsts', 'Worst Avg CPU', 'N/A', cpu_worst.msg - FROM cpu_worst - UNION ALL - SELECT 1002, 255, 'Worsts', 'Worst Avg Logical Reads', 'N/A', logical_reads_worst.msg - FROM logical_reads_worst - UNION ALL - SELECT 1002, 255, 'Worsts', 'Worst Avg Physical Reads', 'N/A', physical_reads_worst.msg - FROM physical_reads_worst - UNION ALL - SELECT 1002, 255, 'Worsts', 'Worst Avg Logical Writes', 'N/A', logical_writes_worst.msg - FROM logical_writes_worst - UNION ALL - SELECT 1002, 255, 'Worsts', 'Worst Avg Memory', 'N/A', memory_worst.msg - FROM memory_worst - UNION ALL - SELECT 1002, 255, 'Worsts', 'Worst Row Counts', 'N/A', rowcount_worst.msg - FROM rowcount_worst - UNION ALL - SELECT 1002, 255, 'Worsts', 'Worst Avg Log Bytes', 'N/A', logbytes_worst.msg - FROM logbytes_worst - UNION ALL - SELECT 1002, 255, 'Worsts', 'Worst Avg tempdb', 'N/A', tempdb_worst.msg - FROM tempdb_worst - UNION ALL - /*maxes*/ - SELECT 1002, 255, 'Worsts', 'Worst Max Duration', 'N/A', max_duration_worst.msg - FROM max_duration_worst - UNION ALL - SELECT 1002, 255, 'Worsts', 'Worst Max CPU', 'N/A', max_cpu_worst.msg - FROM max_cpu_worst - UNION ALL - SELECT 1002, 255, 'Worsts', 'Worst Max Logical Reads', 'N/A', max_logical_reads_worst.msg - FROM max_logical_reads_worst - UNION ALL - SELECT 1002, 255, 'Worsts', 'Worst Max Physical Reads', 'N/A', max_physical_reads_worst.msg - FROM max_physical_reads_worst - UNION ALL - SELECT 1002, 255, 'Worsts', 'Worst Max Logical Writes', 'N/A', max_logical_writes_worst.msg - FROM max_logical_writes_worst - UNION ALL - SELECT 1002, 255, 'Worsts', 'Worst Max Memory', 'N/A', max_memory_worst.msg - FROM max_memory_worst - UNION ALL - SELECT 1002, 255, 'Worsts', 'Worst Max Log Bytes', 'N/A', max_logbytes_worst.msg - FROM max_logbytes_worst - UNION ALL - SELECT 1002, 255, 'Worsts', 'Worst Max tempdb', 'N/A', max_tempdb_worst.msg - FROM max_tempdb_worst - OPTION (RECOMPILE); - - - IF NOT EXISTS (SELECT 1/0 - FROM #warning_results AS bcr - WHERE bcr.Priority = 2147483646 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (2147483646, - 255, - 'Need more help?' , - 'Paste your plan on the internet!', - 'http://pastetheplan.com', - 'This makes it easy to share plans and post them to Q&A sites like https://dba.stackexchange.com/!') ; - - - IF NOT EXISTS (SELECT 1/0 - FROM #warning_results AS bcr - WHERE bcr.Priority = 2147483647 - ) - INSERT INTO #warning_results (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES ( - 2147483647, - 255, - 'Thanks for using sp_BlitzQueryStore!' , - 'From Your Community Volunteers', - 'http://FirstResponderKit.org', - 'We hope you found this tool useful. Current version: ' + @Version + ' released on ' + CONVERT(NVARCHAR(30), @VersionDate) + '.') ; - - - - SELECT Priority, - FindingsGroup, - Finding, - URL, - Details, - CheckID - FROM #warning_results - GROUP BY Priority, - FindingsGroup, - Finding, - URL, - Details, - CheckID - ORDER BY Priority ASC, FindingsGroup, Finding, CheckID ASC - OPTION (RECOMPILE); - - - -END; - -END; -END TRY -BEGIN CATCH - RAISERROR (N'Failure returning warnings', 0,1) WITH NOWAIT; - - IF @sql_select IS NOT NULL - BEGIN - SET @msg = N'Last @sql_select: ' + @sql_select; - RAISERROR(@msg, 0, 1) WITH NOWAIT; - END; - - SELECT @msg = @DatabaseName + N' database failed to process. ' + ERROR_MESSAGE(), @error_severity = ERROR_SEVERITY(), @error_state = ERROR_STATE(); - RAISERROR (@msg, @error_severity, @error_state) WITH NOWAIT; - - - WHILE @@TRANCOUNT > 0 - ROLLBACK; - - RETURN; -END CATCH; - -IF @Debug = 1 - -BEGIN TRY - -BEGIN - -RAISERROR(N'Returning debugging data from temp tables', 0, 1) WITH NOWAIT; - ---Table content debugging - -SELECT '#working_metrics' AS table_name, * -FROM #working_metrics AS wm -OPTION (RECOMPILE); - -SELECT '#working_plan_text' AS table_name, * -FROM #working_plan_text AS wpt -OPTION (RECOMPILE); - -SELECT '#working_warnings' AS table_name, * -FROM #working_warnings AS ww -OPTION (RECOMPILE); - -SELECT '#working_wait_stats' AS table_name, * -FROM #working_wait_stats wws -OPTION (RECOMPILE); - -SELECT '#grouped_interval' AS table_name, * -FROM #grouped_interval -OPTION (RECOMPILE); - -SELECT '#working_plans' AS table_name, * -FROM #working_plans -OPTION (RECOMPILE); - -SELECT '#stats_agg' AS table_name, * -FROM #stats_agg -OPTION (RECOMPILE); - -SELECT '#trace_flags' AS table_name, * -FROM #trace_flags -OPTION (RECOMPILE); - -SELECT '#statements' AS table_name, * -FROM #statements AS s -OPTION (RECOMPILE); - -SELECT '#query_plan' AS table_name, * -FROM #query_plan AS qp -OPTION (RECOMPILE); - -SELECT '#relop' AS table_name, * -FROM #relop AS r -OPTION (RECOMPILE); - -SELECT '#plan_cost' AS table_name, * -FROM #plan_cost AS pc -OPTION (RECOMPILE); - -SELECT '#est_rows' AS table_name, * -FROM #est_rows AS er -OPTION (RECOMPILE); - -SELECT '#stored_proc_info' AS table_name, * -FROM #stored_proc_info AS spi -OPTION(RECOMPILE); - -SELECT '#conversion_info' AS table_name, * -FROM #conversion_info AS ci -OPTION ( RECOMPILE ); - -SELECT '#variable_info' AS table_name, * -FROM #variable_info AS vi -OPTION ( RECOMPILE ); - -SELECT '#missing_index_xml' AS table_name, * -FROM #missing_index_xml -OPTION ( RECOMPILE ); - -SELECT '#missing_index_schema' AS table_name, * -FROM #missing_index_schema -OPTION ( RECOMPILE ); - -SELECT '#missing_index_usage' AS table_name, * -FROM #missing_index_usage -OPTION ( RECOMPILE ); - -SELECT '#missing_index_detail' AS table_name, * -FROM #missing_index_detail -OPTION ( RECOMPILE ); - -SELECT '#missing_index_pretty' AS table_name, * -FROM #missing_index_pretty -OPTION ( RECOMPILE ); - -END; - -END TRY -BEGIN CATCH - RAISERROR (N'Failure returning debug temp tables', 0,1) WITH NOWAIT; - - IF @sql_select IS NOT NULL - BEGIN - SET @msg = N'Last @sql_select: ' + @sql_select; - RAISERROR(@msg, 0, 1) WITH NOWAIT; - END; - - SELECT @msg = @DatabaseName + N' database failed to process. ' + ERROR_MESSAGE(), @error_severity = ERROR_SEVERITY(), @error_state = ERROR_STATE(); - RAISERROR (@msg, @error_severity, @error_state) WITH NOWAIT; - - - WHILE @@TRANCOUNT > 0 - ROLLBACK; - - RETURN; -END CATCH; - -/* -Ways to run this thing - ---Debug -EXEC sp_BlitzQueryStore @DatabaseName = 'StackOverflow', @Debug = 1 - ---Get the top 1 -EXEC sp_BlitzQueryStore @DatabaseName = 'StackOverflow', @Top = 1, @Debug = 1 - ---Use a StartDate -EXEC sp_BlitzQueryStore @DatabaseName = 'StackOverflow', @Top = 1, @StartDate = '20170527' - ---Use an EndDate -EXEC sp_BlitzQueryStore @DatabaseName = 'StackOverflow', @Top = 1, @EndDate = '20170527' - ---Use Both -EXEC sp_BlitzQueryStore @DatabaseName = 'StackOverflow', @Top = 1, @StartDate = '20170526', @EndDate = '20170527' - ---Set a minimum execution count -EXEC sp_BlitzQueryStore @DatabaseName = 'StackOverflow', @Top = 1, @MinimumExecutionCount = 10 - ---Set a duration minimum -EXEC sp_BlitzQueryStore @DatabaseName = 'StackOverflow', @Top = 1, @DurationFilter = 5 - ---Look for a stored procedure name (that doesn't exist!) -EXEC sp_BlitzQueryStore @DatabaseName = 'StackOverflow', @Top = 1, @StoredProcName = 'blah' - ---Look for a stored procedure name that does (at least On My Computer®) -EXEC sp_BlitzQueryStore @DatabaseName = 'StackOverflow', @Top = 1, @StoredProcName = 'UserReportExtended' - ---Look for failed queries -EXEC sp_BlitzQueryStore @DatabaseName = 'StackOverflow', @Top = 1, @Failed = 1 - ---Filter by plan_id -EXEC sp_BlitzQueryStore @DatabaseName = 'StackOverflow', @PlanIdFilter = 3356 - ---Filter by query_id -EXEC sp_BlitzQueryStore @DatabaseName = 'StackOverflow', @QueryIdFilter = 2958 - -*/ - -END; - -GO -IF OBJECT_ID('dbo.sp_BlitzWho') IS NULL - EXEC ('CREATE PROCEDURE dbo.sp_BlitzWho AS RETURN 0;') -GO - -ALTER PROCEDURE dbo.sp_BlitzWho - @Help TINYINT = 0 , - @ShowSleepingSPIDs TINYINT = 0, - @ExpertMode BIT = 0, - @Debug BIT = 0, - @OutputDatabaseName NVARCHAR(256) = NULL , - @OutputSchemaName NVARCHAR(256) = NULL , - @OutputTableName NVARCHAR(256) = NULL , - @OutputTableRetentionDays TINYINT = 3 , - @MinElapsedSeconds INT = 0 , - @MinCPUTime INT = 0 , - @MinLogicalReads INT = 0 , - @MinPhysicalReads INT = 0 , - @MinWrites INT = 0 , - @MinTempdbMB INT = 0 , - @MinRequestedMemoryKB INT = 0 , - @MinBlockingSeconds INT = 0 , - @CheckDateOverride DATETIMEOFFSET = NULL, - @ShowActualParameters BIT = 0, - @GetOuterCommand BIT = 0, - @GetLiveQueryPlan BIT = 0, - @Version VARCHAR(30) = NULL OUTPUT, - @VersionDate DATETIME = NULL OUTPUT, - @VersionCheckMode BIT = 0, - @SortOrder NVARCHAR(256) = N'elapsed time' -AS -BEGIN - SET NOCOUNT ON; - SET STATISTICS XML OFF; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - - SELECT @Version = '8.19', @VersionDate = '20240222'; - - IF(@VersionCheckMode = 1) - BEGIN - RETURN; - END; - - - - IF @Help = 1 - BEGIN - PRINT ' -sp_BlitzWho from http://FirstResponderKit.org - -This script gives you a snapshot of everything currently executing on your SQL Server. - -To learn more, visit http://FirstResponderKit.org where you can download new -versions for free, watch training videos on how it works, get more info on -the findings, contribute your own code, and more. - -Known limitations of this version: - - Only Microsoft-supported versions of SQL Server. Sorry, 2005 and 2000. - - Outputting to table is only supported with SQL Server 2012 and higher. - - If @OutputDatabaseName and @OutputSchemaName are populated, the database and - schema must already exist. We will not create them, only the table. - -MIT License - -Copyright (c) Brent Ozar Unlimited - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -'; -RETURN; -END; /* @Help = 1 */ - -/* Get the major and minor build numbers */ -DECLARE @ProductVersion NVARCHAR(128) - ,@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]) - ,@EnhanceFlag BIT = 0 - ,@BlockingCheck NVARCHAR(MAX) - ,@StringToSelect NVARCHAR(MAX) - ,@StringToExecute NVARCHAR(MAX) - ,@OutputTableCleanupDate DATE - ,@SessionWaits BIT = 0 - ,@SessionWaitsSQL NVARCHAR(MAX) = - N'LEFT JOIN ( SELECT DISTINCT - wait.session_id , - ( SELECT TOP 5 waitwait.wait_type + N'' ('' - + CAST(MAX(waitwait.wait_time_ms) AS NVARCHAR(128)) - + N'' ms), '' - FROM sys.dm_exec_session_wait_stats AS waitwait - WHERE waitwait.session_id = wait.session_id - GROUP BY waitwait.wait_type - HAVING SUM(waitwait.wait_time_ms) > 5 - ORDER BY 1 - FOR - XML PATH('''') ) AS session_wait_info - FROM sys.dm_exec_session_wait_stats AS wait ) AS wt2 - ON s.session_id = wt2.session_id - LEFT JOIN sys.dm_exec_query_stats AS session_stats - ON r.sql_handle = session_stats.sql_handle - AND r.plan_handle = session_stats.plan_handle - AND r.statement_start_offset = session_stats.statement_start_offset - AND r.statement_end_offset = session_stats.statement_end_offset' - ,@ObjectFullName NVARCHAR(2000) - ,@OutputTableNameQueryStats_View NVARCHAR(256) - ,@LineFeed NVARCHAR(MAX) /* Had to set as MAX up from 10 as it was truncating the view creation*/; - -/* 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) - -SELECT - @OutputTableNameQueryStats_View = QUOTENAME(@OutputTableName + '_Deltas'), - @OutputDatabaseName = QUOTENAME(@OutputDatabaseName), - @OutputSchemaName = QUOTENAME(@OutputSchemaName), - @OutputTableName = QUOTENAME(@OutputTableName), - @LineFeed = CHAR(13) + CHAR(10); - -IF @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @OutputTableName IS NOT NULL - AND EXISTS ( SELECT * - FROM sys.databases - WHERE QUOTENAME([name]) = @OutputDatabaseName) - BEGIN - SET @ExpertMode = 1; /* Force ExpertMode when we're logging to table */ - - /* Create the table if it doesn't exist */ - SET @StringToExecute = N'USE ' - + @OutputDatabaseName - + N'; IF EXISTS(SELECT * FROM ' - + @OutputDatabaseName - + N'.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' - + @OutputSchemaName - + N''') AND NOT EXISTS (SELECT * FROM ' - + @OutputDatabaseName - + N'.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''' - + @OutputSchemaName + N''' AND QUOTENAME(TABLE_NAME) = ''' - + @OutputTableName + N''') CREATE TABLE ' - + @OutputSchemaName + N'.' - + @OutputTableName - + N'('; - SET @StringToExecute = @StringToExecute + N' - ID INT IDENTITY(1,1) NOT NULL, - ServerName NVARCHAR(128) NOT NULL, - CheckDate DATETIMEOFFSET NOT NULL, - [elapsed_time] [varchar](41) NULL, - [session_id] [smallint] NOT NULL, - [database_name] [nvarchar](128) NULL, - [query_text] [nvarchar](max) NULL, - [outer_command] NVARCHAR(4000) NULL, - [query_plan] [xml] NULL, - [live_query_plan] [xml] NULL, - [cached_parameter_info] [nvarchar](max) NULL, - [live_parameter_info] [nvarchar](max) NULL, - [query_cost] [float] NULL, - [status] [nvarchar](30) NOT NULL, - [wait_info] [nvarchar](max) NULL, - [wait_resource] [nvarchar](max) NULL, - [top_session_waits] [nvarchar](max) NULL, - [blocking_session_id] [smallint] NULL, - [open_transaction_count] [int] NULL, - [is_implicit_transaction] [int] NOT NULL, - [nt_domain] [nvarchar](128) NULL, - [host_name] [nvarchar](128) NULL, - [login_name] [nvarchar](128) NOT NULL, - [nt_user_name] [nvarchar](128) NULL, - [program_name] [nvarchar](128) NULL, - [fix_parameter_sniffing] [nvarchar](150) NULL, - [client_interface_name] [nvarchar](32) NULL, - [login_time] [datetime] NOT NULL, - [start_time] [datetime] NULL, - [request_time] [datetime] NULL, - [request_cpu_time] [int] NULL, - [request_logical_reads] [bigint] NULL, - [request_writes] [bigint] NULL, - [request_physical_reads] [bigint] NULL, - [session_cpu] [int] NOT NULL, - [session_logical_reads] [bigint] NOT NULL, - [session_physical_reads] [bigint] NOT NULL, - [session_writes] [bigint] NOT NULL, - [tempdb_allocations_mb] [decimal](38, 2) NULL, - [memory_usage] [int] NOT NULL, - [estimated_completion_time] [bigint] NULL, - [percent_complete] [real] NULL, - [deadlock_priority] [int] NULL, - [transaction_isolation_level] [varchar](33) NOT NULL, - [degree_of_parallelism] [smallint] NULL, - [last_dop] [bigint] NULL, - [min_dop] [bigint] NULL, - [max_dop] [bigint] NULL, - [last_grant_kb] [bigint] NULL, - [min_grant_kb] [bigint] NULL, - [max_grant_kb] [bigint] NULL, - [last_used_grant_kb] [bigint] NULL, - [min_used_grant_kb] [bigint] NULL, - [max_used_grant_kb] [bigint] NULL, - [last_ideal_grant_kb] [bigint] NULL, - [min_ideal_grant_kb] [bigint] NULL, - [max_ideal_grant_kb] [bigint] NULL, - [last_reserved_threads] [bigint] NULL, - [min_reserved_threads] [bigint] NULL, - [max_reserved_threads] [bigint] NULL, - [last_used_threads] [bigint] NULL, - [min_used_threads] [bigint] NULL, - [max_used_threads] [bigint] NULL, - [grant_time] [varchar](20) NULL, - [requested_memory_kb] [bigint] NULL, - [grant_memory_kb] [bigint] NULL, - [is_request_granted] [varchar](39) NOT NULL, - [required_memory_kb] [bigint] NULL, - [query_memory_grant_used_memory_kb] [bigint] NULL, - [ideal_memory_kb] [bigint] NULL, - [is_small] [bit] NULL, - [timeout_sec] [int] NULL, - [resource_semaphore_id] [smallint] NULL, - [wait_order] [varchar](20) NULL, - [wait_time_ms] [varchar](20) NULL, - [next_candidate_for_memory_grant] [varchar](3) NOT NULL, - [target_memory_kb] [bigint] NULL, - [max_target_memory_kb] [varchar](30) NULL, - [total_memory_kb] [bigint] NULL, - [available_memory_kb] [bigint] NULL, - [granted_memory_kb] [bigint] NULL, - [query_resource_semaphore_used_memory_kb] [bigint] NULL, - [grantee_count] [int] NULL, - [waiter_count] [int] NULL, - [timeout_error_count] [bigint] NULL, - [forced_grant_count] [varchar](30) NULL, - [workload_group_name] [sysname] NULL, - [resource_pool_name] [sysname] NULL, - [context_info] [varchar](128) NULL, - [query_hash] [binary](8) NULL, - [query_plan_hash] [binary](8) NULL, - [sql_handle] [varbinary] (64) NULL, - [plan_handle] [varbinary] (64) NULL, - [statement_start_offset] INT NULL, - [statement_end_offset] INT NULL, - JoinKey AS ServerName + CAST(CheckDate AS NVARCHAR(50)), - PRIMARY KEY CLUSTERED (ID ASC));'; - IF @Debug = 1 - BEGIN - PRINT CONVERT(VARCHAR(8000), SUBSTRING(@StringToExecute, 0, 8000)) - PRINT CONVERT(VARCHAR(8000), SUBSTRING(@StringToExecute, 8000, 16000)) - END - EXEC(@StringToExecute); - - /* If the table doesn't have the new JoinKey computed column, add it. See Github #2162. */ - SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableName; - SET @StringToExecute = N'IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns - WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''JoinKey'') - ALTER TABLE ' + @ObjectFullName + N' ADD JoinKey AS ServerName + CAST(CheckDate AS NVARCHAR(50));'; - EXEC(@StringToExecute); - - /* If the table doesn't have the new cached_parameter_info computed column, add it. See Github #2842. */ - SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableName; - SET @StringToExecute = N'IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns - WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''cached_parameter_info'') - ALTER TABLE ' + @ObjectFullName + N' ADD cached_parameter_info NVARCHAR(MAX) NULL;'; - EXEC(@StringToExecute); - - /* If the table doesn't have the new live_parameter_info computed column, add it. See Github #2842. */ - SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableName; - SET @StringToExecute = N'IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns - WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''live_parameter_info'') - ALTER TABLE ' + @ObjectFullName + N' ADD live_parameter_info NVARCHAR(MAX) NULL;'; - EXEC(@StringToExecute); - - /* If the table doesn't have the new outer_command column, add it. See Github #2887. */ - SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableName; - SET @StringToExecute = N'IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns - WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''outer_command'') - ALTER TABLE ' + @ObjectFullName + N' ADD outer_command NVARCHAR(4000) NULL;'; - EXEC(@StringToExecute); - - /* If the table doesn't have the new wait_resource column, add it. See Github #2970. */ - SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableName; - SET @StringToExecute = N'IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns - WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''wait_resource'') - ALTER TABLE ' + @ObjectFullName + N' ADD wait_resource NVARCHAR(MAX) NULL;'; - EXEC(@StringToExecute); - - /* Delete history older than @OutputTableRetentionDays */ - SET @OutputTableCleanupDate = CAST( (DATEADD(DAY, -1 * @OutputTableRetentionDays, GETDATE() ) ) AS DATE); - SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' - + @OutputDatabaseName - + N'.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' - + @OutputSchemaName + N''') DELETE ' - + @OutputDatabaseName + '.' - + @OutputSchemaName + '.' - + @OutputTableName - + N' WHERE ServerName = @SrvName AND CheckDate < @CheckDate;'; - IF @Debug = 1 - BEGIN - PRINT CONVERT(VARCHAR(8000), SUBSTRING(@StringToExecute, 0, 8000)) - PRINT CONVERT(VARCHAR(8000), SUBSTRING(@StringToExecute, 8000, 16000)) - END - EXEC sp_executesql @StringToExecute, - N'@SrvName NVARCHAR(128), @CheckDate date', - @@SERVERNAME, @OutputTableCleanupDate; - - SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableNameQueryStats_View; - - /* Create the view */ - IF OBJECT_ID(@ObjectFullName) IS NULL - BEGIN - SET @StringToExecute = N'USE ' - + @OutputDatabaseName - + N'; EXEC (''CREATE VIEW ' - + @OutputSchemaName + '.' - + @OutputTableNameQueryStats_View + N' AS ' + @LineFeed - + N'WITH MaxQueryDuration AS ' + @LineFeed - + N'( ' + @LineFeed - + N' SELECT ' + @LineFeed - + N' MIN([ID]) AS [MinID], ' + @LineFeed - + N' MAX([ID]) AS [MaxID] ' + @LineFeed - + N' FROM ' + @OutputSchemaName + '.' + @OutputTableName + '' + @LineFeed - + N' GROUP BY [ServerName], ' + @LineFeed - + N' [session_id], ' + @LineFeed - + N' [database_name], ' + @LineFeed - + N' [request_time], ' + @LineFeed - + N' [start_time], ' + @LineFeed - + N' [sql_handle] ' + @LineFeed - + N') ' + @LineFeed - + N'SELECT ' + @LineFeed - + N' [ID], ' + @LineFeed - + N' [ServerName], ' + @LineFeed - + N' [CheckDate], ' + @LineFeed - + N' [elapsed_time], ' + @LineFeed - + N' [session_id], ' + @LineFeed - + N' [database_name], ' + @LineFeed - + N' [query_text_snippet], ' + @LineFeed - + N' [query_plan], ' + @LineFeed - + N' [live_query_plan], ' + @LineFeed - + N' [query_cost], ' + @LineFeed - + N' [status], ' + @LineFeed - + N' [wait_info], ' + @LineFeed - + N' [wait_resource], ' + @LineFeed - + N' [top_session_waits], ' + @LineFeed - + N' [blocking_session_id], ' + @LineFeed - + N' [open_transaction_count], ' + @LineFeed - + N' [is_implicit_transaction], ' + @LineFeed - + N' [nt_domain], ' + @LineFeed - + N' [host_name], ' + @LineFeed - + N' [login_name], ' + @LineFeed - + N' [nt_user_name], ' + @LineFeed - + N' [program_name], ' + @LineFeed - + N' [fix_parameter_sniffing], ' + @LineFeed - + N' [client_interface_name], ' + @LineFeed - + N' [login_time], ' + @LineFeed - + N' [start_time], ' + @LineFeed - + N' [request_time], ' + @LineFeed - + N' [request_cpu_time], ' + @LineFeed - + N' [degree_of_parallelism], ' + @LineFeed - + N' [request_logical_reads], ' + @LineFeed - + N' [Logical_Reads_MB], ' + @LineFeed - + N' [request_writes], ' + @LineFeed - + N' [Logical_Writes_MB], ' + @LineFeed - + N' [request_physical_reads], ' + @LineFeed - + N' [Physical_reads_MB], ' + @LineFeed - + N' [session_cpu], ' + @LineFeed - + N' [session_logical_reads], ' + @LineFeed - + N' [session_logical_reads_MB], ' + @LineFeed - + N' [session_physical_reads], ' + @LineFeed - + N' [session_physical_reads_MB], ' + @LineFeed - + N' [session_writes], ' + @LineFeed - + N' [session_writes_MB], ' + @LineFeed - + N' [tempdb_allocations_mb], ' + @LineFeed - + N' [memory_usage], ' + @LineFeed - + N' [estimated_completion_time], ' + @LineFeed - + N' [percent_complete], ' + @LineFeed - + N' [deadlock_priority], ' + @LineFeed - + N' [transaction_isolation_level], ' + @LineFeed - + N' [last_dop], ' + @LineFeed - + N' [min_dop], ' + @LineFeed - + N' [max_dop], ' + @LineFeed - + N' [last_grant_kb], ' + @LineFeed - + N' [min_grant_kb], ' + @LineFeed - + N' [max_grant_kb], ' + @LineFeed - + N' [last_used_grant_kb], ' + @LineFeed - + N' [min_used_grant_kb], ' + @LineFeed - + N' [max_used_grant_kb], ' + @LineFeed - + N' [last_ideal_grant_kb], ' + @LineFeed - + N' [min_ideal_grant_kb], ' + @LineFeed - + N' [max_ideal_grant_kb], ' + @LineFeed - + N' [last_reserved_threads], ' + @LineFeed - + N' [min_reserved_threads], ' + @LineFeed - + N' [max_reserved_threads], ' + @LineFeed - + N' [last_used_threads], ' + @LineFeed - + N' [min_used_threads], ' + @LineFeed - + N' [max_used_threads], ' + @LineFeed - + N' [grant_time], ' + @LineFeed - + N' [requested_memory_kb], ' + @LineFeed - + N' [grant_memory_kb], ' + @LineFeed - + N' [is_request_granted], ' + @LineFeed - + N' [required_memory_kb], ' + @LineFeed - + N' [query_memory_grant_used_memory_kb], ' + @LineFeed - + N' [ideal_memory_kb], ' + @LineFeed - + N' [is_small], ' + @LineFeed - + N' [timeout_sec], ' + @LineFeed - + N' [resource_semaphore_id], ' + @LineFeed - + N' [wait_order], ' + @LineFeed - + N' [wait_time_ms], ' + @LineFeed - + N' [next_candidate_for_memory_grant], ' + @LineFeed - + N' [target_memory_kb], ' + @LineFeed - + N' [max_target_memory_kb], ' + @LineFeed - + N' [total_memory_kb], ' + @LineFeed - + N' [available_memory_kb], ' + @LineFeed - + N' [granted_memory_kb], ' + @LineFeed - + N' [query_resource_semaphore_used_memory_kb], ' + @LineFeed - + N' [grantee_count], ' + @LineFeed - + N' [waiter_count], ' + @LineFeed - + N' [timeout_error_count], ' + @LineFeed - + N' [forced_grant_count], ' + @LineFeed - + N' [workload_group_name], ' + @LineFeed - + N' [resource_pool_name], ' + @LineFeed - + N' [context_info], ' + @LineFeed - + N' [query_hash], ' + @LineFeed - + N' [query_plan_hash], ' + @LineFeed - + N' [sql_handle], ' + @LineFeed - + N' [plan_handle], ' + @LineFeed - + N' [statement_start_offset], ' + @LineFeed - + N' [statement_end_offset] ' + @LineFeed - + N' FROM ' + @LineFeed - + N' ( ' + @LineFeed - + N' SELECT ' + @LineFeed - + N' [ID], ' + @LineFeed - + N' [ServerName], ' + @LineFeed - + N' [CheckDate], ' + @LineFeed - + N' [elapsed_time], ' + @LineFeed - + N' [session_id], ' + @LineFeed - + N' [database_name], ' + @LineFeed - + N' /* Truncate the query text to aid performance of painting the rows in SSMS */ ' + @LineFeed - + N' CAST([query_text] AS NVARCHAR(1000)) AS [query_text_snippet], ' + @LineFeed - + N' [query_plan], ' + @LineFeed - + N' [live_query_plan], ' + @LineFeed - + N' [query_cost], ' + @LineFeed - + N' [status], ' + @LineFeed - + N' [wait_info], ' + @LineFeed - + N' [wait_resource], ' + @LineFeed - + N' [top_session_waits], ' + @LineFeed - + N' [blocking_session_id], ' + @LineFeed - + N' [open_transaction_count], ' + @LineFeed - + N' [is_implicit_transaction], ' + @LineFeed - + N' [nt_domain], ' + @LineFeed - + N' [host_name], ' + @LineFeed - + N' [login_name], ' + @LineFeed - + N' [nt_user_name], ' + @LineFeed - + N' [program_name], ' + @LineFeed - + N' [fix_parameter_sniffing], ' + @LineFeed - + N' [client_interface_name], ' + @LineFeed - + N' [login_time], ' + @LineFeed - + N' [start_time], ' + @LineFeed - + N' [request_time], ' + @LineFeed - + N' [request_cpu_time], ' + @LineFeed - + N' [degree_of_parallelism], ' + @LineFeed - + N' [request_logical_reads], ' + @LineFeed - + N' ((CAST([request_logical_reads] AS DECIMAL(38,2))* 8)/ 1024) [Logical_Reads_MB], ' + @LineFeed - + N' [request_writes], ' + @LineFeed - + N' ((CAST([request_writes] AS DECIMAL(38,2))* 8)/ 1024) [Logical_Writes_MB], ' + @LineFeed - + N' [request_physical_reads], ' + @LineFeed - + N' ((CAST([request_physical_reads] AS DECIMAL(38,2))* 8)/ 1024) [Physical_reads_MB], ' + @LineFeed - + N' [session_cpu], ' + @LineFeed - + N' [session_logical_reads], ' + @LineFeed - + N' ((CAST([session_logical_reads] AS DECIMAL(38,2))* 8)/ 1024) [session_logical_reads_MB], ' + @LineFeed - + N' [session_physical_reads], ' + @LineFeed - + N' ((CAST([session_physical_reads] AS DECIMAL(38,2))* 8)/ 1024) [session_physical_reads_MB], ' + @LineFeed - + N' [session_writes], ' + @LineFeed - + N' ((CAST([session_writes] AS DECIMAL(38,2))* 8)/ 1024) [session_writes_MB], ' + @LineFeed - + N' [tempdb_allocations_mb], ' + @LineFeed - + N' [memory_usage], ' + @LineFeed - + N' [estimated_completion_time], ' + @LineFeed - + N' [percent_complete], ' + @LineFeed - + N' [deadlock_priority], ' + @LineFeed - + N' [transaction_isolation_level], ' + @LineFeed - + N' [last_dop], ' + @LineFeed - + N' [min_dop], ' + @LineFeed - + N' [max_dop], ' + @LineFeed - + N' [last_grant_kb], ' + @LineFeed - + N' [min_grant_kb], ' + @LineFeed - + N' [max_grant_kb], ' + @LineFeed - + N' [last_used_grant_kb], ' + @LineFeed - + N' [min_used_grant_kb], ' + @LineFeed - + N' [max_used_grant_kb], ' + @LineFeed - + N' [last_ideal_grant_kb], ' + @LineFeed - + N' [min_ideal_grant_kb], ' + @LineFeed - + N' [max_ideal_grant_kb], ' + @LineFeed - + N' [last_reserved_threads], ' + @LineFeed - + N' [min_reserved_threads], ' + @LineFeed - + N' [max_reserved_threads], ' + @LineFeed - + N' [last_used_threads], ' + @LineFeed - + N' [min_used_threads], ' + @LineFeed - + N' [max_used_threads], ' + @LineFeed - + N' [grant_time], ' + @LineFeed - + N' [requested_memory_kb], ' + @LineFeed - + N' [grant_memory_kb], ' + @LineFeed - + N' [is_request_granted], ' + @LineFeed - + N' [required_memory_kb], ' + @LineFeed - + N' [query_memory_grant_used_memory_kb], ' + @LineFeed - + N' [ideal_memory_kb], ' + @LineFeed - + N' [is_small], ' + @LineFeed - + N' [timeout_sec], ' + @LineFeed - + N' [resource_semaphore_id], ' + @LineFeed - + N' [wait_order], ' + @LineFeed - + N' [wait_time_ms], ' + @LineFeed - + N' [next_candidate_for_memory_grant], ' + @LineFeed - + N' [target_memory_kb], ' + @LineFeed - + N' [max_target_memory_kb], ' + @LineFeed - + N' [total_memory_kb], ' + @LineFeed - + N' [available_memory_kb], ' + @LineFeed - + N' [granted_memory_kb], ' + @LineFeed - + N' [query_resource_semaphore_used_memory_kb], ' + @LineFeed - + N' [grantee_count], ' + @LineFeed - + N' [waiter_count], ' + @LineFeed - + N' [timeout_error_count], ' + @LineFeed - + N' [forced_grant_count], ' + @LineFeed - + N' [workload_group_name], ' + @LineFeed - + N' [resource_pool_name], ' + @LineFeed - + N' [context_info], ' + @LineFeed - + N' [query_hash], ' + @LineFeed - + N' [query_plan_hash], ' + @LineFeed - + N' [sql_handle], ' + @LineFeed - + N' [plan_handle], ' + @LineFeed - + N' [statement_start_offset], ' + @LineFeed - + N' [statement_end_offset] ' + @LineFeed - + N' FROM ' + @OutputSchemaName + '.' + @OutputTableName + '' + @LineFeed - + N' ) AS [BlitzWho] ' + @LineFeed - + N'INNER JOIN [MaxQueryDuration] ON [BlitzWho].[ID] = [MaxQueryDuration].[MaxID]; ' + @LineFeed - + N''');' - - IF @Debug = 1 - BEGIN - PRINT CONVERT(VARCHAR(8000), SUBSTRING(@StringToExecute, 0, 8000)) - PRINT CONVERT(VARCHAR(8000), SUBSTRING(@StringToExecute, 8000, 16000)) - END - - EXEC(@StringToExecute); - END; - - END - - IF OBJECT_ID('tempdb..#WhoReadableDBs') IS NOT NULL - DROP TABLE #WhoReadableDBs; - -CREATE TABLE #WhoReadableDBs -( -database_id INT -); - -IF EXISTS (SELECT * FROM sys.all_objects o WHERE o.name = 'dm_hadr_database_replica_states') -BEGIN - RAISERROR('Checking for Read intent databases to exclude',0,0) WITH NOWAIT; - - EXEC('INSERT INTO #WhoReadableDBs (database_id) SELECT DBs.database_id FROM sys.databases DBs INNER JOIN sys.availability_replicas Replicas ON DBs.replica_id = Replicas.replica_id WHERE replica_server_name NOT IN (SELECT DISTINCT primary_replica FROM sys.dm_hadr_availability_group_states States) AND Replicas.secondary_role_allow_connections_desc = ''READ_ONLY'' AND replica_server_name = @@SERVERNAME;'); -END - -SELECT @BlockingCheck = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - - SET LOCK_TIMEOUT 1000; /* To avoid blocking on live query plans. See Github issue #2907. */ - DECLARE @blocked TABLE - ( - dbid SMALLINT NOT NULL, - last_batch DATETIME NOT NULL, - open_tran SMALLINT NOT NULL, - sql_handle BINARY(20) NOT NULL, - session_id SMALLINT NOT NULL, - blocking_session_id SMALLINT NOT NULL, - lastwaittype NCHAR(32) NOT NULL, - waittime BIGINT NOT NULL, - cpu INT NOT NULL, - physical_io BIGINT NOT NULL, - memusage INT NOT NULL - ); - - INSERT @blocked ( dbid, last_batch, open_tran, sql_handle, session_id, blocking_session_id, lastwaittype, waittime, cpu, physical_io, memusage ) - SELECT - sys1.dbid, sys1.last_batch, sys1.open_tran, sys1.sql_handle, - sys2.spid AS session_id, sys2.blocked AS blocking_session_id, sys2.lastwaittype, sys2.waittime, sys2.cpu, sys2.physical_io, sys2.memusage - FROM sys.sysprocesses AS sys1 - JOIN sys.sysprocesses AS sys2 - ON sys1.spid = sys2.blocked; - '+CASE - WHEN (@GetOuterCommand = 1 AND (NOT EXISTS(SELECT 1 FROM sys.all_objects WHERE [name] = N'dm_exec_input_buffer'))) THEN N' - DECLARE @session_id SMALLINT; - DECLARE @Sessions TABLE - ( - session_id INT - ); - - DECLARE @inputbuffer TABLE - ( - ID INT IDENTITY(1,1), - session_id INT, - event_type NVARCHAR(30), - parameters SMALLINT, - event_info NVARCHAR(4000) - ); - - DECLARE inputbuffer_cursor - - CURSOR LOCAL FAST_FORWARD - FOR - SELECT session_id - FROM sys.dm_exec_sessions - WHERE session_id <> @@SPID - AND is_user_process = 1; - - OPEN inputbuffer_cursor; - - FETCH NEXT FROM inputbuffer_cursor INTO @session_id; - - WHILE (@@FETCH_STATUS = 0) - BEGIN; - BEGIN TRY; - - INSERT INTO @inputbuffer ([event_type],[parameters],[event_info]) - EXEC sp_executesql - N''DBCC INPUTBUFFER(@session_id) WITH NO_INFOMSGS;'', - N''@session_id SMALLINT'', - @session_id; - - UPDATE @inputbuffer - SET session_id = @session_id - WHERE ID = SCOPE_IDENTITY(); - - END TRY - BEGIN CATCH - RAISERROR(''DBCC inputbuffer failed for session %d'',0,0,@session_id) WITH NOWAIT; - END CATCH; - - FETCH NEXT FROM inputbuffer_cursor INTO @session_id - - END; - - CLOSE inputbuffer_cursor; - DEALLOCATE inputbuffer_cursor;' - ELSE N'' - END+ - N' - - DECLARE @LiveQueryPlans TABLE - ( - Session_Id INT NOT NULL, - Query_Plan XML NOT NULL - ); - - ' -IF EXISTS (SELECT * FROM sys.all_columns WHERE object_id = OBJECT_ID('sys.dm_exec_query_statistics_xml') AND name = 'query_plan' AND @GetLiveQueryPlan=1) -BEGIN - SET @BlockingCheck = @BlockingCheck + N' - INSERT INTO @LiveQueryPlans - SELECT s.session_id, query_plan - FROM sys.dm_exec_sessions AS s - CROSS APPLY sys.dm_exec_query_statistics_xml(s.session_id) - WHERE s.session_id <> @@SPID;'; -END - - -IF @ProductVersionMajor > 9 and @ProductVersionMajor < 11 -BEGIN - /* Think of the StringToExecute as starting with this, but we'll set this up later depending on whether we're doing an insert or a select: - SELECT @StringToExecute = N'SELECT GETDATE() AS run_date , - */ - SET @StringToExecute = N' CASE WHEN YEAR(s.last_request_start_time) = 1900 THEN NULL ELSE COALESCE( RIGHT(''00'' + CONVERT(VARCHAR(20), (ABS(r.total_elapsed_time) / 1000) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), (DATEADD(SECOND, (r.total_elapsed_time / 1000), 0) + DATEADD(MILLISECOND, (r.total_elapsed_time % 1000), 0)), 114), RIGHT(''00'' + CONVERT(VARCHAR(20), DATEDIFF(SECOND, s.last_request_start_time, GETDATE()) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), DATEADD(SECOND, DATEDIFF(SECOND, s.last_request_start_time, GETDATE()), 0), 114) ) END AS [elapsed_time] , - s.session_id , - CASE WHEN r.blocking_session_id <> 0 AND blocked.session_id IS NULL - THEN r.blocking_session_id - WHEN r.blocking_session_id <> 0 AND s.session_id <> blocked.blocking_session_id - THEN blocked.blocking_session_id - WHEN r.blocking_session_id = 0 AND s.session_id = blocked.session_id - THEN blocked.blocking_session_id - WHEN r.blocking_session_id <> 0 AND s.session_id = blocked.blocking_session_id - THEN r.blocking_session_id - ELSE NULL - END AS blocking_session_id, - COALESCE(DB_NAME(r.database_id), DB_NAME(blocked.dbid), ''N/A'') AS database_name, - ISNULL(SUBSTRING(dest.text, - ( r.statement_start_offset / 2 ) + 1, - ( ( CASE r.statement_end_offset - WHEN -1 THEN DATALENGTH(dest.text) - ELSE r.statement_end_offset - END - r.statement_start_offset ) - / 2 ) + 1), dest.text) AS query_text , - '+CASE - WHEN @GetOuterCommand = 1 THEN N'CAST(event_info AS NVARCHAR(4000)) AS outer_command,' - ELSE N'' - END+N' - derp.query_plan , - qmg.query_cost , - s.status , - CASE - WHEN s.status <> ''sleeping'' THEN COALESCE(wt.wait_info, RTRIM(blocked.lastwaittype) + '' ('' + CONVERT(VARCHAR(10), blocked.waittime) + '')'' ) - ELSE NULL - END AS wait_info , - r.wait_resource , - COALESCE(r.open_transaction_count, blocked.open_tran) AS open_transaction_count , - CASE WHEN EXISTS ( SELECT 1 - FROM sys.dm_tran_active_transactions AS tat - JOIN sys.dm_tran_session_transactions AS tst - ON tst.transaction_id = tat.transaction_id - WHERE tat.name = ''implicit_transaction'' - AND s.session_id = tst.session_id - ) THEN 1 - ELSE 0 - END AS is_implicit_transaction , - s.nt_domain , - s.host_name , - s.login_name , - s.nt_user_name ,' - IF @Platform = 'NonAzure' - BEGIN - SET @StringToExecute += - N'program_name = COALESCE(( - SELECT REPLACE(program_name,Substring(program_name,30,34),''"''+j.name+''"'') - FROM msdb.dbo.sysjobs j WHERE Substring(program_name,32,32) = CONVERT(char(32),CAST(j.job_id AS binary(16)),2) - ),s.program_name)' - END - ELSE - BEGIN - SET @StringToExecute += N's.program_name' - END - - IF @ExpertMode = 1 - BEGIN - SET @StringToExecute += - N', - ''DBCC FREEPROCCACHE ('' + CONVERT(NVARCHAR(128), r.plan_handle, 1) + '');'' AS fix_parameter_sniffing, - s.client_interface_name , - s.login_time , - r.start_time , - qmg.request_time , - COALESCE(r.cpu_time, s.cpu_time) AS request_cpu_time, - COALESCE(r.logical_reads, s.logical_reads) AS request_logical_reads, - COALESCE(r.writes, s.writes) AS request_writes, - COALESCE(r.reads, s.reads) AS request_physical_reads , - s.cpu_time AS session_cpu, - s.logical_reads AS session_logical_reads, - s.reads AS session_physical_reads , - s.writes AS session_writes, - tempdb_allocations.tempdb_allocations_mb, - s.memory_usage , - r.estimated_completion_time , - r.percent_complete , - r.deadlock_priority , - CASE - WHEN s.transaction_isolation_level = 0 THEN ''Unspecified'' - WHEN s.transaction_isolation_level = 1 THEN ''Read Uncommitted'' - WHEN s.transaction_isolation_level = 2 AND EXISTS (SELECT 1 FROM sys.databases WHERE name = DB_NAME(r.database_id) AND is_read_committed_snapshot_on = 1) THEN ''Read Committed Snapshot Isolation'' - WHEN s.transaction_isolation_level = 2 THEN ''Read Committed'' - WHEN s.transaction_isolation_level = 3 THEN ''Repeatable Read'' - WHEN s.transaction_isolation_level = 4 THEN ''Serializable'' - WHEN s.transaction_isolation_level = 5 THEN ''Snapshot'' - ELSE ''WHAT HAVE YOU DONE?'' - END AS transaction_isolation_level , - qmg.dop AS degree_of_parallelism , - COALESCE(CAST(qmg.grant_time AS VARCHAR(20)), ''N/A'') AS grant_time , - qmg.requested_memory_kb , - qmg.granted_memory_kb AS grant_memory_kb, - CASE WHEN qmg.grant_time IS NULL THEN ''N/A'' - WHEN qmg.requested_memory_kb < qmg.granted_memory_kb - THEN ''Query Granted Less Than Query Requested'' - ELSE ''Memory Request Granted'' - END AS is_request_granted , - qmg.required_memory_kb , - qmg.used_memory_kb AS query_memory_grant_used_memory_kb, - qmg.ideal_memory_kb , - qmg.is_small , - qmg.timeout_sec , - qmg.resource_semaphore_id , - COALESCE(CAST(qmg.wait_order AS VARCHAR(20)), ''N/A'') AS wait_order , - COALESCE(CAST(qmg.wait_time_ms AS VARCHAR(20)), - ''N/A'') AS wait_time_ms , - CASE qmg.is_next_candidate - WHEN 0 THEN ''No'' - WHEN 1 THEN ''Yes'' - ELSE ''N/A'' - END AS next_candidate_for_memory_grant , - qrs.target_memory_kb , - COALESCE(CAST(qrs.max_target_memory_kb AS VARCHAR(20)), - ''Small Query Resource Semaphore'') AS max_target_memory_kb , - qrs.total_memory_kb , - qrs.available_memory_kb , - qrs.granted_memory_kb , - qrs.used_memory_kb AS query_resource_semaphore_used_memory_kb, - qrs.grantee_count , - qrs.waiter_count , - qrs.timeout_error_count , - COALESCE(CAST(qrs.forced_grant_count AS VARCHAR(20)), - ''Small Query Resource Semaphore'') AS forced_grant_count, - wg.name AS workload_group_name , - rp.name AS resource_pool_name, - CONVERT(VARCHAR(128), r.context_info) AS context_info - ' - END /* IF @ExpertMode = 1 */ - - SET @StringToExecute += - N'FROM sys.dm_exec_sessions AS s - '+ - CASE - WHEN @GetOuterCommand = 1 THEN CASE - WHEN EXISTS(SELECT 1 FROM sys.all_objects WHERE [name] = N'dm_exec_input_buffer') THEN N'OUTER APPLY sys.dm_exec_input_buffer (s.session_id, 0) AS ib' - ELSE N'LEFT JOIN @inputbuffer ib ON s.session_id = ib.session_id' - END - ELSE N'' - END+N' - LEFT JOIN sys.dm_exec_requests AS r - ON r.session_id = s.session_id - LEFT JOIN ( SELECT DISTINCT - wait.session_id , - ( SELECT waitwait.wait_type + N'' ('' - + CAST(MAX(waitwait.wait_duration_ms) AS NVARCHAR(128)) - + N'' ms) '' - FROM sys.dm_os_waiting_tasks AS waitwait - WHERE waitwait.session_id = wait.session_id - GROUP BY waitwait.wait_type - ORDER BY SUM(waitwait.wait_duration_ms) DESC - FOR - XML PATH('''') ) AS wait_info - FROM sys.dm_os_waiting_tasks AS wait ) AS wt - ON s.session_id = wt.session_id - LEFT JOIN sys.dm_exec_query_stats AS query_stats - ON r.sql_handle = query_stats.sql_handle - AND r.plan_handle = query_stats.plan_handle - AND r.statement_start_offset = query_stats.statement_start_offset - AND r.statement_end_offset = query_stats.statement_end_offset - LEFT JOIN sys.dm_exec_query_memory_grants qmg - ON r.session_id = qmg.session_id - AND r.request_id = qmg.request_id - LEFT JOIN sys.dm_exec_query_resource_semaphores qrs - ON qmg.resource_semaphore_id = qrs.resource_semaphore_id - AND qmg.pool_id = qrs.pool_id - LEFT JOIN sys.resource_governor_workload_groups wg - ON s.group_id = wg.group_id - LEFT JOIN sys.resource_governor_resource_pools rp - ON wg.pool_id = rp.pool_id - OUTER APPLY ( - SELECT TOP 1 - b.dbid, b.last_batch, b.open_tran, b.sql_handle, - b.session_id, b.blocking_session_id, b.lastwaittype, b.waittime - FROM @blocked b - WHERE (s.session_id = b.session_id - OR s.session_id = b.blocking_session_id) - ) AS blocked - OUTER APPLY sys.dm_exec_sql_text(COALESCE(r.sql_handle, blocked.sql_handle)) AS dest - OUTER APPLY sys.dm_exec_query_plan(r.plan_handle) AS derp - OUTER APPLY ( - SELECT CONVERT(DECIMAL(38,2), SUM( ((((tsu.user_objects_alloc_page_count - user_objects_dealloc_page_count) + (tsu.internal_objects_alloc_page_count - internal_objects_dealloc_page_count)) * 8) / 1024.)) ) AS tempdb_allocations_mb - FROM sys.dm_db_task_space_usage tsu - WHERE tsu.request_id = r.request_id - AND tsu.session_id = r.session_id - AND tsu.session_id = s.session_id - ) as tempdb_allocations - WHERE s.session_id <> @@SPID - AND s.host_name IS NOT NULL - ' - + CASE WHEN @ShowSleepingSPIDs = 0 THEN - N' AND COALESCE(DB_NAME(r.database_id), DB_NAME(blocked.dbid)) IS NOT NULL' - WHEN @ShowSleepingSPIDs = 1 THEN - N' OR COALESCE(r.open_transaction_count, blocked.open_tran) >= 1' - ELSE N'' END; -END /* IF @ProductVersionMajor > 9 and @ProductVersionMajor < 11 */ - -IF @ProductVersionMajor >= 11 - BEGIN - SELECT @EnhanceFlag = - CASE WHEN @ProductVersionMajor = 11 AND @ProductVersionMinor >= 6020 THEN 1 - WHEN @ProductVersionMajor = 12 AND @ProductVersionMinor >= 5000 THEN 1 - WHEN @ProductVersionMajor = 13 AND @ProductVersionMinor >= 1601 THEN 1 - WHEN @ProductVersionMajor > 13 THEN 1 - ELSE 0 - END - - - IF OBJECT_ID('sys.dm_exec_session_wait_stats') IS NOT NULL - BEGIN - SET @SessionWaits = 1 - END - - /* Think of the StringToExecute as starting with this, but we'll set this up later depending on whether we're doing an insert or a select: - SELECT @StringToExecute = N'SELECT GETDATE() AS run_date , - */ - SELECT @StringToExecute = N' CASE WHEN YEAR(s.last_request_start_time) = 1900 THEN NULL ELSE COALESCE( RIGHT(''00'' + CONVERT(VARCHAR(20), (ABS(r.total_elapsed_time) / 1000) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), (DATEADD(SECOND, (r.total_elapsed_time / 1000), 0) + DATEADD(MILLISECOND, (r.total_elapsed_time % 1000), 0)), 114), RIGHT(''00'' + CONVERT(VARCHAR(20), DATEDIFF(SECOND, s.last_request_start_time, GETDATE()) / 86400), 2) + '':'' + CONVERT(VARCHAR(20), DATEADD(SECOND, DATEDIFF(SECOND, s.last_request_start_time, GETDATE()), 0), 114) ) END AS [elapsed_time] , - s.session_id , - CASE WHEN r.blocking_session_id <> 0 AND blocked.session_id IS NULL - THEN r.blocking_session_id - WHEN r.blocking_session_id <> 0 AND s.session_id <> blocked.blocking_session_id - THEN blocked.blocking_session_id - WHEN r.blocking_session_id = 0 AND s.session_id = blocked.session_id - THEN blocked.blocking_session_id - WHEN r.blocking_session_id <> 0 AND s.session_id = blocked.blocking_session_id - THEN r.blocking_session_id - ELSE NULL - END AS blocking_session_id, - COALESCE(DB_NAME(r.database_id), DB_NAME(blocked.dbid), ''N/A'') AS database_name, - ISNULL(SUBSTRING(dest.text, - ( r.statement_start_offset / 2 ) + 1, - ( ( CASE r.statement_end_offset - WHEN -1 THEN DATALENGTH(dest.text) - ELSE r.statement_end_offset - END - r.statement_start_offset ) - / 2 ) + 1), dest.text) AS query_text , - '+CASE - WHEN @GetOuterCommand = 1 THEN N'CAST(event_info AS NVARCHAR(4000)) AS outer_command,' - ELSE N'' - END+N' - derp.query_plan , - CAST(COALESCE(qs_live.Query_Plan, ' + CASE WHEN @GetLiveQueryPlan=1 - THEN '''''' - ELSE '''''' - END - +') AS XML - - - ) AS live_query_plan , - STUFF((SELECT DISTINCT N'', '' + Node.Data.value(''(@Column)[1]'', ''NVARCHAR(4000)'') + N'' {'' + Node.Data.value(''(@ParameterDataType)[1]'', ''NVARCHAR(4000)'') + N''}: '' + Node.Data.value(''(@ParameterCompiledValue)[1]'', ''NVARCHAR(4000)'') - FROM derp.query_plan.nodes(''/*:ShowPlanXML/*:BatchSequence/*:Batch/*:Statements/*:StmtSimple/*:QueryPlan/*:ParameterList/*:ColumnReference'') AS Node(Data) - FOR XML PATH('''')), 1,2,'''') - AS Cached_Parameter_Info, - ' - IF @ShowActualParameters = 1 - BEGIN - SELECT @StringToExecute = @StringToExecute + N'qs_live.Live_Parameter_Info as Live_Parameter_Info,' - END - - SELECT @StringToExecute = @StringToExecute + N' - qmg.query_cost , - s.status , - CASE - WHEN s.status <> ''sleeping'' THEN COALESCE(wt.wait_info, RTRIM(blocked.lastwaittype) + '' ('' + CONVERT(VARCHAR(10), blocked.waittime) + '')'' ) - ELSE NULL - END AS wait_info , - r.wait_resource ,' - + - CASE @SessionWaits - WHEN 1 THEN + N'SUBSTRING(wt2.session_wait_info, 0, LEN(wt2.session_wait_info) ) AS top_session_waits ,' - ELSE N' NULL AS top_session_waits ,' - END - + - N'COALESCE(r.open_transaction_count, blocked.open_tran) AS open_transaction_count , - CASE WHEN EXISTS ( SELECT 1 - FROM sys.dm_tran_active_transactions AS tat - JOIN sys.dm_tran_session_transactions AS tst - ON tst.transaction_id = tat.transaction_id - WHERE tat.name = ''implicit_transaction'' - AND s.session_id = tst.session_id - ) THEN 1 - ELSE 0 - END AS is_implicit_transaction , - s.nt_domain , - s.host_name , - s.login_name , - s.nt_user_name ,' - IF @Platform = 'NonAzure' - BEGIN - SET @StringToExecute += - N'program_name = COALESCE(( - SELECT REPLACE(program_name,Substring(program_name,30,34),''"''+j.name+''"'') - FROM msdb.dbo.sysjobs j WHERE Substring(program_name,32,32) = CONVERT(char(32),CAST(j.job_id AS binary(16)),2) - ),s.program_name)' - END - ELSE - BEGIN - SET @StringToExecute += N's.program_name' - END - - IF @ExpertMode = 1 /* We show more columns in expert mode, so the SELECT gets longer */ - BEGIN - SET @StringToExecute += - N', ''DBCC FREEPROCCACHE ('' + CONVERT(NVARCHAR(128), r.plan_handle, 1) + '');'' AS fix_parameter_sniffing, - s.client_interface_name , - s.login_time , - r.start_time , - qmg.request_time , - COALESCE(r.cpu_time, s.cpu_time) AS request_cpu_time, - COALESCE(r.logical_reads, s.logical_reads) AS request_logical_reads, - COALESCE(r.writes, s.writes) AS request_writes, - COALESCE(r.reads, s.reads) AS request_physical_reads , - s.cpu_time AS session_cpu, - s.logical_reads AS session_logical_reads, - s.reads AS session_physical_reads , - s.writes AS session_writes, - tempdb_allocations.tempdb_allocations_mb, - s.memory_usage , - r.estimated_completion_time , - r.percent_complete , - r.deadlock_priority , - CASE - WHEN s.transaction_isolation_level = 0 THEN ''Unspecified'' - WHEN s.transaction_isolation_level = 1 THEN ''Read Uncommitted'' - WHEN s.transaction_isolation_level = 2 AND EXISTS (SELECT 1 FROM sys.databases WHERE name = DB_NAME(r.database_id) AND is_read_committed_snapshot_on = 1) THEN ''Read Committed Snapshot Isolation'' - WHEN s.transaction_isolation_level = 2 THEN ''Read Committed'' - WHEN s.transaction_isolation_level = 3 THEN ''Repeatable Read'' - WHEN s.transaction_isolation_level = 4 THEN ''Serializable'' - WHEN s.transaction_isolation_level = 5 THEN ''Snapshot'' - ELSE ''WHAT HAVE YOU DONE?'' - END AS transaction_isolation_level , - qmg.dop AS degree_of_parallelism , ' - + - CASE @EnhanceFlag - WHEN 1 THEN N'query_stats.last_dop, - query_stats.min_dop, - query_stats.max_dop, - query_stats.last_grant_kb, - query_stats.min_grant_kb, - query_stats.max_grant_kb, - query_stats.last_used_grant_kb, - query_stats.min_used_grant_kb, - query_stats.max_used_grant_kb, - query_stats.last_ideal_grant_kb, - query_stats.min_ideal_grant_kb, - query_stats.max_ideal_grant_kb, - query_stats.last_reserved_threads, - query_stats.min_reserved_threads, - query_stats.max_reserved_threads, - query_stats.last_used_threads, - query_stats.min_used_threads, - query_stats.max_used_threads,' - ELSE N' NULL AS last_dop, - NULL AS min_dop, - NULL AS max_dop, - NULL AS last_grant_kb, - NULL AS min_grant_kb, - NULL AS max_grant_kb, - NULL AS last_used_grant_kb, - NULL AS min_used_grant_kb, - NULL AS max_used_grant_kb, - NULL AS last_ideal_grant_kb, - NULL AS min_ideal_grant_kb, - NULL AS max_ideal_grant_kb, - NULL AS last_reserved_threads, - NULL AS min_reserved_threads, - NULL AS max_reserved_threads, - NULL AS last_used_threads, - NULL AS min_used_threads, - NULL AS max_used_threads,' - END - - SET @StringToExecute += - N' - COALESCE(CAST(qmg.grant_time AS VARCHAR(20)), ''Memory Not Granted'') AS grant_time , - qmg.requested_memory_kb , - qmg.granted_memory_kb AS grant_memory_kb, - CASE WHEN qmg.grant_time IS NULL THEN ''N/A'' - WHEN qmg.requested_memory_kb < qmg.granted_memory_kb - THEN ''Query Granted Less Than Query Requested'' - ELSE ''Memory Request Granted'' - END AS is_request_granted , - qmg.required_memory_kb , - qmg.used_memory_kb AS query_memory_grant_used_memory_kb, - qmg.ideal_memory_kb , - qmg.is_small , - qmg.timeout_sec , - qmg.resource_semaphore_id , - COALESCE(CAST(qmg.wait_order AS VARCHAR(20)), ''N/A'') AS wait_order , - COALESCE(CAST(qmg.wait_time_ms AS VARCHAR(20)), - ''N/A'') AS wait_time_ms , - CASE qmg.is_next_candidate - WHEN 0 THEN ''No'' - WHEN 1 THEN ''Yes'' - ELSE ''N/A'' - END AS next_candidate_for_memory_grant , - qrs.target_memory_kb , - COALESCE(CAST(qrs.max_target_memory_kb AS VARCHAR(20)), - ''Small Query Resource Semaphore'') AS max_target_memory_kb , - qrs.total_memory_kb , - qrs.available_memory_kb , - qrs.granted_memory_kb , - qrs.used_memory_kb AS query_resource_semaphore_used_memory_kb, - qrs.grantee_count , - qrs.waiter_count , - qrs.timeout_error_count , - COALESCE(CAST(qrs.forced_grant_count AS VARCHAR(20)), - ''Small Query Resource Semaphore'') AS forced_grant_count, - wg.name AS workload_group_name, - rp.name AS resource_pool_name, - CONVERT(VARCHAR(128), r.context_info) AS context_info, - r.query_hash, r.query_plan_hash, r.sql_handle, r.plan_handle, r.statement_start_offset, r.statement_end_offset ' - END /* IF @ExpertMode = 1 */ - - SET @StringToExecute += - N' FROM sys.dm_exec_sessions AS s'+ - CASE - WHEN @GetOuterCommand = 1 THEN CASE - WHEN EXISTS(SELECT 1 FROM sys.all_objects WHERE [name] = N'dm_exec_input_buffer') THEN N' - OUTER APPLY sys.dm_exec_input_buffer (s.session_id, 0) AS ib' - ELSE N' - LEFT JOIN @inputbuffer ib ON s.session_id = ib.session_id' - END - ELSE N'' - END+N' - LEFT JOIN sys.dm_exec_requests AS r - ON r.session_id = s.session_id - LEFT JOIN ( SELECT DISTINCT - wait.session_id , - ( SELECT waitwait.wait_type + N'' ('' - + CAST(MAX(waitwait.wait_duration_ms) AS NVARCHAR(128)) - + N'' ms) '' - FROM sys.dm_os_waiting_tasks AS waitwait - WHERE waitwait.session_id = wait.session_id - GROUP BY waitwait.wait_type - ORDER BY SUM(waitwait.wait_duration_ms) DESC - FOR - XML PATH('''') ) AS wait_info - FROM sys.dm_os_waiting_tasks AS wait ) AS wt - ON s.session_id = wt.session_id - LEFT JOIN sys.dm_exec_query_stats AS query_stats - ON r.sql_handle = query_stats.sql_handle - AND r.plan_handle = query_stats.plan_handle - AND r.statement_start_offset = query_stats.statement_start_offset - AND r.statement_end_offset = query_stats.statement_end_offset - ' - + - CASE @SessionWaits - WHEN 1 THEN @SessionWaitsSQL - ELSE N'' - END - + - N' - LEFT JOIN sys.dm_exec_query_memory_grants qmg - ON r.session_id = qmg.session_id - AND r.request_id = qmg.request_id - LEFT JOIN sys.dm_exec_query_resource_semaphores qrs - ON qmg.resource_semaphore_id = qrs.resource_semaphore_id - AND qmg.pool_id = qrs.pool_id - LEFT JOIN sys.resource_governor_workload_groups wg - ON s.group_id = wg.group_id - LEFT JOIN sys.resource_governor_resource_pools rp - ON wg.pool_id = rp.pool_id - OUTER APPLY ( - SELECT TOP 1 - b.dbid, b.last_batch, b.open_tran, b.sql_handle, - b.session_id, b.blocking_session_id, b.lastwaittype, b.waittime - FROM @blocked b - WHERE (s.session_id = b.session_id - OR s.session_id = b.blocking_session_id) - ) AS blocked - OUTER APPLY sys.dm_exec_sql_text(COALESCE(r.sql_handle, blocked.sql_handle)) AS dest - OUTER APPLY sys.dm_exec_query_plan(r.plan_handle) AS derp - OUTER APPLY ( - SELECT CONVERT(DECIMAL(38,2), SUM( ((((tsu.user_objects_alloc_page_count - user_objects_dealloc_page_count) + (tsu.internal_objects_alloc_page_count - internal_objects_dealloc_page_count)) * 8) / 1024.)) ) AS tempdb_allocations_mb - FROM sys.dm_db_task_space_usage tsu - WHERE tsu.request_id = r.request_id - AND tsu.session_id = r.session_id - AND tsu.session_id = s.session_id - ) as tempdb_allocations - - OUTER APPLY ( - SELECT TOP 1 Query_Plan, - STUFF((SELECT DISTINCT N'', '' + Node.Data.value(''(@Column)[1]'', ''NVARCHAR(4000)'') + N'' {'' + Node.Data.value(''(@ParameterDataType)[1]'', ''NVARCHAR(4000)'') + N''}: '' + Node.Data.value(''(@ParameterCompiledValue)[1]'', ''NVARCHAR(4000)'') + N'' (Actual: '' + Node.Data.value(''(@ParameterRuntimeValue)[1]'', ''NVARCHAR(4000)'') + N'')'' - FROM q.Query_Plan.nodes(''/*:ShowPlanXML/*:BatchSequence/*:Batch/*:Statements/*:StmtSimple/*:QueryPlan/*:ParameterList/*:ColumnReference'') AS Node(Data) - FOR XML PATH('''')), 1,2,'''') - AS Live_Parameter_Info - FROM @LiveQueryPlans q - WHERE (s.session_id = q.Session_Id) - - ) AS qs_live - - WHERE s.session_id <> @@SPID - AND s.host_name IS NOT NULL - AND r.database_id NOT IN (SELECT database_id FROM #WhoReadableDBs) - ' - + CASE WHEN @ShowSleepingSPIDs = 0 THEN - N' AND COALESCE(DB_NAME(r.database_id), DB_NAME(blocked.dbid)) IS NOT NULL' - WHEN @ShowSleepingSPIDs = 1 THEN - N' OR COALESCE(r.open_transaction_count, blocked.open_tran) >= 1' - ELSE N'' END; - - -END /* IF @ProductVersionMajor >= 11 */ - -IF (@MinElapsedSeconds + @MinCPUTime + @MinLogicalReads + @MinPhysicalReads + @MinWrites + @MinTempdbMB + @MinRequestedMemoryKB + @MinBlockingSeconds) > 0 - BEGIN - /* They're filtering for something, so set up a where clause that will let any (not all combined) of the min triggers work: */ - SET @StringToExecute += N' AND (1 = 0 '; - IF @MinElapsedSeconds > 0 - SET @StringToExecute += N' OR ABS(COALESCE(r.total_elapsed_time,0)) / 1000 >= ' + CAST(@MinElapsedSeconds AS NVARCHAR(20)); - IF @MinCPUTime > 0 - SET @StringToExecute += N' OR COALESCE(r.cpu_time, s.cpu_time,0) / 1000 >= ' + CAST(@MinCPUTime AS NVARCHAR(20)); - IF @MinLogicalReads > 0 - SET @StringToExecute += N' OR COALESCE(r.logical_reads, s.logical_reads,0) >= ' + CAST(@MinLogicalReads AS NVARCHAR(20)); - IF @MinPhysicalReads > 0 - SET @StringToExecute += N' OR COALESCE(s.reads,0) >= ' + CAST(@MinPhysicalReads AS NVARCHAR(20)); - IF @MinWrites > 0 - SET @StringToExecute += N' OR COALESCE(r.writes, s.writes,0) >= ' + CAST(@MinWrites AS NVARCHAR(20)); - IF @MinTempdbMB > 0 - SET @StringToExecute += N' OR COALESCE(tempdb_allocations.tempdb_allocations_mb,0) >= ' + CAST(@MinTempdbMB AS NVARCHAR(20)); - IF @MinRequestedMemoryKB > 0 - SET @StringToExecute += N' OR COALESCE(qmg.requested_memory_kb,0) >= ' + CAST(@MinRequestedMemoryKB AS NVARCHAR(20)); - /* Blocking is a little different - we're going to return ALL of the queries if we meet the blocking threshold. */ - IF @MinBlockingSeconds > 0 - SET @StringToExecute += N' OR (SELECT SUM(waittime / 1000) FROM @blocked) >= ' + CAST(@MinBlockingSeconds AS NVARCHAR(20)); - SET @StringToExecute += N' ) '; - END - -SET @StringToExecute += - N' ORDER BY ' + CASE WHEN @SortOrder = 'session_id' THEN '[session_id] DESC' - WHEN @SortOrder = 'query_cost' THEN '[query_cost] DESC' - WHEN @SortOrder = 'database_name' THEN '[database_name] ASC' - WHEN @SortOrder = 'open_transaction_count' THEN '[open_transaction_count] DESC' - WHEN @SortOrder = 'is_implicit_transaction' THEN '[is_implicit_transaction] DESC' - WHEN @SortOrder = 'login_name' THEN '[login_name] ASC' - WHEN @SortOrder = 'program_name' THEN '[program_name] ASC' - WHEN @SortOrder = 'client_interface_name' THEN '[client_interface_name] ASC' - WHEN @SortOrder = 'request_cpu_time' THEN 'COALESCE(r.cpu_time, s.cpu_time) DESC' - WHEN @SortOrder = 'request_logical_reads' THEN 'COALESCE(r.logical_reads, s.logical_reads) DESC' - WHEN @SortOrder = 'request_writes' THEN 'COALESCE(r.writes, s.writes) DESC' - WHEN @SortOrder = 'request_physical_reads' THEN 'COALESCE(r.reads, s.reads) DESC ' - WHEN @SortOrder = 'session_cpu' THEN 's.cpu_time DESC' - WHEN @SortOrder = 'session_logical_reads' THEN 's.logical_reads DESC' - WHEN @SortOrder = 'session_physical_reads' THEN 's.reads DESC' - WHEN @SortOrder = 'session_writes' THEN 's.writes DESC' - WHEN @SortOrder = 'tempdb_allocations_mb' THEN '[tempdb_allocations_mb] DESC' - WHEN @SortOrder = 'memory_usage' THEN '[memory_usage] DESC' - WHEN @SortOrder = 'deadlock_priority' THEN 'r.deadlock_priority DESC' - WHEN @SortOrder = 'transaction_isolation_level' THEN 'r.[transaction_isolation_level] DESC' - WHEN @SortOrder = 'requested_memory_kb' THEN '[requested_memory_kb] DESC' - WHEN @SortOrder = 'grant_memory_kb' THEN 'qmg.granted_memory_kb DESC' - WHEN @SortOrder = 'grant' THEN 'qmg.granted_memory_kb DESC' - WHEN @SortOrder = 'query_memory_grant_used_memory_kb' THEN 'qmg.used_memory_kb DESC' - WHEN @SortOrder = 'ideal_memory_kb' THEN '[ideal_memory_kb] DESC' - WHEN @SortOrder = 'workload_group_name' THEN 'wg.name ASC' - WHEN @SortOrder = 'resource_pool_name' THEN 'rp.name ASC' - ELSE '[elapsed_time] DESC' - END + ' - '; - - -IF @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @OutputTableName IS NOT NULL - AND EXISTS ( SELECT * - FROM sys.databases - WHERE QUOTENAME([name]) = @OutputDatabaseName) - BEGIN - SET @StringToExecute = N'USE ' - + @OutputDatabaseName + N'; ' - + @BlockingCheck + - + ' INSERT INTO ' - + @OutputSchemaName + N'.' - + @OutputTableName - + N'(ServerName - ,CheckDate - ,[elapsed_time] - ,[session_id] - ,[blocking_session_id] - ,[database_name] - ,[query_text]' - + CASE WHEN @GetOuterCommand = 1 THEN N',[outer_command]' ELSE N'' END + N' - ,[query_plan]' - + CASE WHEN @ProductVersionMajor >= 11 THEN N',[live_query_plan]' ELSE N'' END - + CASE WHEN @ProductVersionMajor >= 11 THEN N',[cached_parameter_info]' ELSE N'' END - + CASE WHEN @ProductVersionMajor >= 11 AND @ShowActualParameters = 1 THEN N',[Live_Parameter_Info]' ELSE N'' END + N' - ,[query_cost] - ,[status] - ,[wait_info] - ,[wait_resource]' - + CASE WHEN @ProductVersionMajor >= 11 THEN N',[top_session_waits]' ELSE N'' END + N' - ,[open_transaction_count] - ,[is_implicit_transaction] - ,[nt_domain] - ,[host_name] - ,[login_name] - ,[nt_user_name] - ,[program_name] - ,[fix_parameter_sniffing] - ,[client_interface_name] - ,[login_time] - ,[start_time] - ,[request_time] - ,[request_cpu_time] - ,[request_logical_reads] - ,[request_writes] - ,[request_physical_reads] - ,[session_cpu] - ,[session_logical_reads] - ,[session_physical_reads] - ,[session_writes] - ,[tempdb_allocations_mb] - ,[memory_usage] - ,[estimated_completion_time] - ,[percent_complete] - ,[deadlock_priority] - ,[transaction_isolation_level] - ,[degree_of_parallelism]' - + CASE WHEN @ProductVersionMajor >= 11 THEN N' - ,[last_dop] - ,[min_dop] - ,[max_dop] - ,[last_grant_kb] - ,[min_grant_kb] - ,[max_grant_kb] - ,[last_used_grant_kb] - ,[min_used_grant_kb] - ,[max_used_grant_kb] - ,[last_ideal_grant_kb] - ,[min_ideal_grant_kb] - ,[max_ideal_grant_kb] - ,[last_reserved_threads] - ,[min_reserved_threads] - ,[max_reserved_threads] - ,[last_used_threads] - ,[min_used_threads] - ,[max_used_threads]' ELSE N'' END + N' - ,[grant_time] - ,[requested_memory_kb] - ,[grant_memory_kb] - ,[is_request_granted] - ,[required_memory_kb] - ,[query_memory_grant_used_memory_kb] - ,[ideal_memory_kb] - ,[is_small] - ,[timeout_sec] - ,[resource_semaphore_id] - ,[wait_order] - ,[wait_time_ms] - ,[next_candidate_for_memory_grant] - ,[target_memory_kb] - ,[max_target_memory_kb] - ,[total_memory_kb] - ,[available_memory_kb] - ,[granted_memory_kb] - ,[query_resource_semaphore_used_memory_kb] - ,[grantee_count] - ,[waiter_count] - ,[timeout_error_count] - ,[forced_grant_count] - ,[workload_group_name] - ,[resource_pool_name] - ,[context_info]' - + CASE WHEN @ProductVersionMajor >= 11 THEN N' - ,[query_hash] - ,[query_plan_hash] - ,[sql_handle] - ,[plan_handle] - ,[statement_start_offset] - ,[statement_end_offset]' ELSE N'' END + N' -) - SELECT @@SERVERNAME, COALESCE(@CheckDateOverride, SYSDATETIMEOFFSET()) AS CheckDate , ' - + @StringToExecute; - END -ELSE - SET @StringToExecute = @BlockingCheck + N' SELECT GETDATE() AS run_date , ' + @StringToExecute; - -/* If the server has > 50GB of memory, add a max grant hint to avoid getting a giant grant */ -IF (@ProductVersionMajor = 11 AND @ProductVersionMinor >= 6020) - OR (@ProductVersionMajor = 12 AND @ProductVersionMinor >= 5000 ) - OR (@ProductVersionMajor >= 13 ) - AND 50000000 < (SELECT cntr_value - FROM sys.dm_os_performance_counters - WHERE object_name LIKE '%:Memory Manager%' - AND counter_name LIKE 'Target Server Memory (KB)%') - BEGIN - SET @StringToExecute = @StringToExecute + N' OPTION (MAX_GRANT_PERCENT = 1, RECOMPILE) '; - END -ELSE - BEGIN - SET @StringToExecute = @StringToExecute + N' OPTION (RECOMPILE) '; - END - -/* Be good: */ -SET @StringToExecute = @StringToExecute + N' ; '; - - -IF @Debug = 1 - BEGIN - PRINT CONVERT(VARCHAR(8000), SUBSTRING(@StringToExecute, 0, 8000)) - PRINT CONVERT(VARCHAR(8000), SUBSTRING(@StringToExecute, 8000, 16000)) - END - -EXEC sp_executesql @StringToExecute, - N'@CheckDateOverride DATETIMEOFFSET', - @CheckDateOverride; - -END -GO - -IF (OBJECT_ID('dbo.SqlServerVersions') IS NULL) -BEGIN - - CREATE TABLE dbo.SqlServerVersions - ( - MajorVersionNumber tinyint not null, - MinorVersionNumber smallint not null, - Branch varchar(34) not null, - [Url] varchar(99) not null, - ReleaseDate date not null, - MainstreamSupportEndDate date not null, - ExtendedSupportEndDate date not null, - MajorVersionName varchar(19) not null, - MinorVersionName varchar(67) not null, - - CONSTRAINT PK_SqlServerVersions PRIMARY KEY CLUSTERED - ( - MajorVersionNumber ASC, - MinorVersionNumber ASC, - ReleaseDate ASC - ) - ); - - EXEC sys.sp_addextendedproperty @name=N'Description', @value=N'The major version number.' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'SqlServerVersions', @level2type=N'COLUMN',@level2name=N'MajorVersionNumber' - EXEC sys.sp_addextendedproperty @name=N'Description', @value=N'The minor version number.' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'SqlServerVersions', @level2type=N'COLUMN',@level2name=N'MinorVersionNumber' - EXEC sys.sp_addextendedproperty @name=N'Description', @value=N'The update level of the build. CU indicates a cumulative update. SP indicates a service pack. RTM indicates Release To Manufacturer. GDR indicates a General Distribution Release. QFE indicates Quick Fix Engineering (aka hotfix).' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'SqlServerVersions', @level2type=N'COLUMN',@level2name=N'Branch' - EXEC sys.sp_addextendedproperty @name=N'Description', @value=N'A link to the KB article for a version.' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'SqlServerVersions', @level2type=N'COLUMN',@level2name=N'Url' - EXEC sys.sp_addextendedproperty @name=N'Description', @value=N'The date the version was publicly released.' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'SqlServerVersions', @level2type=N'COLUMN',@level2name=N'ReleaseDate' - EXEC sys.sp_addextendedproperty @name=N'Description', @value=N'The date main stream Microsoft support ends for the version.' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'SqlServerVersions', @level2type=N'COLUMN',@level2name=N'MainstreamSupportEndDate' - EXEC sys.sp_addextendedproperty @name=N'Description', @value=N'The date extended Microsoft support ends for the version.' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'SqlServerVersions', @level2type=N'COLUMN',@level2name=N'ExtendedSupportEndDate' - EXEC sys.sp_addextendedproperty @name=N'Description', @value=N'The major version name.' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'SqlServerVersions', @level2type=N'COLUMN',@level2name=N'MajorVersionName' - EXEC sys.sp_addextendedproperty @name=N'Description', @value=N'The minor version name.' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'SqlServerVersions', @level2type=N'COLUMN',@level2name=N'MinorVersionName' - EXEC sys.sp_addextendedproperty @name=N'Description', @value=N'A reference for SQL Server major and minor versions.' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'SqlServerVersions' - -END; -GO - -DELETE FROM dbo.SqlServerVersions; - -INSERT INTO dbo.SqlServerVersions - (MajorVersionNumber, MinorVersionNumber, Branch, [Url], ReleaseDate, MainstreamSupportEndDate, ExtendedSupportEndDate, MajorVersionName, MinorVersionName) -VALUES - (16, 4105, 'CU11', 'https://support.microsoft.com/en-us/help/5032679', '2024-01-11', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 11'), - (16, 4100, 'CU10 GDR', 'https://support.microsoft.com/en-us/help/5033592', '2024-01-09', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 10 GDR'), - (16, 4095, 'CU10', 'https://support.microsoft.com/en-us/help/5031778', '2023-11-16', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 10'), - (16, 4085, 'CU9', 'https://support.microsoft.com/en-us/help/5030731', '2023-10-12', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 9'), - (16, 4075, 'CU8', 'https://support.microsoft.com/en-us/help/5029666', '2023-09-14', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 8'), - (16, 4065, 'CU7', 'https://support.microsoft.com/en-us/help/5028743', '2023-08-10', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 7'), - (16, 4055, 'CU6', 'https://support.microsoft.com/en-us/help/5027505', '2023-07-13', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 6'), - (16, 4045, 'CU5', 'https://support.microsoft.com/en-us/help/5026806', '2023-06-15', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 5'), - (16, 4035, 'CU4', 'https://support.microsoft.com/en-us/help/5026717', '2023-05-11', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 4'), - (16, 4025, 'CU3', 'https://support.microsoft.com/en-us/help/5024396', '2023-04-13', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 3'), - (16, 4015, 'CU2', 'https://support.microsoft.com/en-us/help/5023127', '2023-03-15', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 2'), - (16, 4003, 'CU1', 'https://support.microsoft.com/en-us/help/5022375', '2023-02-16', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 1'), - (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'), - (15, 4355, 'CU25', 'https://support.microsoft.com/kb/5033688', '2023-02-15', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 25'), - (15, 4345, 'CU24', 'https://support.microsoft.com/kb/5031908', '2023-12-14', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 24'), - (15, 4335, 'CU23', 'https://support.microsoft.com/kb/5030333', '2023-10-12', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 23'), - (15, 4322, 'CU22', 'https://support.microsoft.com/kb/5027702', '2023-08-14', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 22'), - (15, 4316, 'CU21', 'https://support.microsoft.com/kb/5025808', '2023-06-15', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 21'), - (15, 4312, 'CU20', 'https://support.microsoft.com/kb/5024276', '2023-04-13', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 20'), - (15, 4298, 'CU19', 'https://support.microsoft.com/kb/5023049', '2023-02-16', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 19'), - (15, 4280, 'CU18 GDR', 'https://support.microsoft.com/kb/5021124', '2023-02-14', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 18 GDR'), - (15, 4261, 'CU18', 'https://support.microsoft.com/en-us/help/5017593', '2022-09-28', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 18'), - (15, 4249, 'CU17', 'https://support.microsoft.com/en-us/help/5016394', '2022-08-11', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 17'), - (15, 4236, 'CU16 GDR', 'https://support.microsoft.com/en-us/help/5014353', '2022-06-14', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 16 GDR'), - (15, 4223, 'CU16', 'https://support.microsoft.com/en-us/help/5011644', '2022-04-18', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 16'), - (15, 4198, 'CU15', 'https://support.microsoft.com/en-us/help/5008996', '2022-01-07', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 15'), - (15, 4188, 'CU14', 'https://support.microsoft.com/en-us/help/5007182', '2021-11-22', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 14'), - (15, 4178, 'CU13', 'https://support.microsoft.com/en-us/help/5005679', '2021-10-05', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 13'), - (15, 4153, 'CU12', 'https://support.microsoft.com/en-us/help/5004524', '2021-08-04', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 12'), - (15, 4138, 'CU11', 'https://support.microsoft.com/en-us/help/5003249', '2021-06-10', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 11'), - (15, 4123, 'CU10', 'https://support.microsoft.com/en-us/help/5001090', '2021-04-06', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 10'), - (15, 4102, 'CU9', 'https://support.microsoft.com/en-us/help/5000642', '2021-02-11', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 9 '), - (15, 4073, 'CU8 GDR', 'https://support.microsoft.com/en-us/help/4583459', '2021-01-12', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 8 GDR '), - (15, 4073, 'CU8', 'https://support.microsoft.com/en-us/help/4577194', '2020-10-01', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 8 '), - (15, 4063, 'CU7', 'https://support.microsoft.com/en-us/help/4570012', '2020-09-02', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 7 '), - (15, 4053, 'CU6', 'https://support.microsoft.com/en-us/help/4563110', '2020-08-04', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 6 '), - (15, 4043, 'CU5', 'https://support.microsoft.com/en-us/help/4548597', '2020-06-22', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 5 '), - (15, 4033, 'CU4', 'https://support.microsoft.com/en-us/help/4548597', '2020-03-31', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 4 '), - (15, 4023, 'CU3', 'https://support.microsoft.com/en-us/help/4538853', '2020-03-12', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 3 '), - (15, 4013, 'CU2', 'https://support.microsoft.com/en-us/help/4536075', '2020-02-13', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 2 '), - (15, 4003, 'CU1', 'https://support.microsoft.com/en-us/help/4527376', '2020-01-07', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 1 '), - (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 '), - (14, 3465, 'RTM CU31 GDR', 'https://support.microsoft.com/kb/5029376', '2023-10-12', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 31 GDR'), - (14, 3460, 'RTM CU31 GDR', 'https://support.microsoft.com/kb/5021126', '2023-02-14', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 31 GDR'), - (14, 3456, 'RTM CU31', 'https://support.microsoft.com/en-us/help/5016884', '2022-09-20', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 31'), - (14, 3451, 'RTM CU30', 'https://support.microsoft.com/en-us/help/5013756', '2022-07-13', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 30'), - (14, 3445, 'RTM CU29 GDR', 'https://support.microsoft.com/en-us/help/5014553', '2022-06-14', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 29 GDR'), - (14, 3436, 'RTM CU29', 'https://support.microsoft.com/en-us/help/5010786', '2022-03-31', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 29'), - (14, 3430, 'RTM CU28', 'https://support.microsoft.com/en-us/help/5006944', '2022-01-13', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 28'), - (14, 3421, 'RTM CU27', 'https://support.microsoft.com/en-us/help/5006944', '2021-10-27', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 27'), - (14, 3411, 'RTM CU26', 'https://support.microsoft.com/en-us/help/5005226', '2021-09-14', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 26'), - (14, 3401, 'RTM CU25', 'https://support.microsoft.com/en-us/help/5003830', '2021-07-12', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 25'), - (14, 3391, 'RTM CU24', 'https://support.microsoft.com/en-us/help/5001228', '2021-05-10', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 24'), - (14, 3381, 'RTM CU23', 'https://support.microsoft.com/en-us/help/5000685', '2021-02-25', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 23'), - (14, 3370, 'RTM CU22 GDR', 'https://support.microsoft.com/en-us/help/4583457', '2021-01-12', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 22 GDR'), - (14, 3356, 'RTM CU22', 'https://support.microsoft.com/en-us/help/4577467', '2020-09-10', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 22'), - (14, 3335, 'RTM CU21', 'https://support.microsoft.com/en-us/help/4557397', '2020-07-01', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 21'), - (14, 3294, 'RTM CU20', 'https://support.microsoft.com/en-us/help/4541283', '2020-04-07', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 20'), - (14, 3257, 'RTM CU19', 'https://support.microsoft.com/en-us/help/4535007', '2020-02-05', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 19'), - (14, 3257, 'RTM CU18', 'https://support.microsoft.com/en-us/help/4527377', '2019-12-09', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 18'), - (14, 3238, 'RTM CU17', 'https://support.microsoft.com/en-us/help/4515579', '2019-10-08', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 17'), - (14, 3223, 'RTM CU16', 'https://support.microsoft.com/en-us/help/4508218', '2019-08-01', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 16'), - (14, 3162, 'RTM CU15', 'https://support.microsoft.com/en-us/help/4498951', '2019-05-24', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 15'), - (14, 3076, 'RTM CU14', 'https://support.microsoft.com/en-us/help/4484710', '2019-03-25', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 14'), - (14, 3048, 'RTM CU13', 'https://support.microsoft.com/en-us/help/4466404', '2018-12-18', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 13'), - (14, 3045, 'RTM CU12', 'https://support.microsoft.com/en-us/help/4464082', '2018-10-24', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 12'), - (14, 3038, 'RTM CU11', 'https://support.microsoft.com/en-us/help/4462262', '2018-09-20', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 11'), - (14, 3037, 'RTM CU10', 'https://support.microsoft.com/en-us/help/4524334', '2018-08-27', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 10'), - (14, 3030, 'RTM CU9', 'https://support.microsoft.com/en-us/help/4515435', '2018-07-18', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 9'), - (14, 3029, 'RTM CU8', 'https://support.microsoft.com/en-us/help/4338363', '2018-06-21', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 8'), - (14, 3026, 'RTM CU7', 'https://support.microsoft.com/en-us/help/4229789', '2018-05-23', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 7'), - (14, 3025, 'RTM CU6', 'https://support.microsoft.com/en-us/help/4101464', '2018-04-17', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 6'), - (14, 3023, 'RTM CU5', 'https://support.microsoft.com/en-us/help/4092643', '2018-03-20', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 5'), - (14, 3022, 'RTM CU4', 'https://support.microsoft.com/en-us/help/4056498', '2018-02-20', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 4'), - (14, 3015, 'RTM CU3', 'https://support.microsoft.com/en-us/help/4052987', '2018-01-04', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 3'), - (14, 3008, 'RTM CU2', 'https://support.microsoft.com/en-us/help/4052574', '2017-11-28', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 2'), - (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 '), - (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, 6430, 'SP3 GDR', 'https://support.microsoft.com/kb/5021129', '2023-02-14', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3 GDR'), - (13, 6419, 'SP3 GDR', 'https://support.microsoft.com/en-us/help/5014355', '2022-06-14', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3 GDR'), - (13, 6404, 'SP3 GDR', 'https://support.microsoft.com/en-us/help/5006943', '2021-10-27', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3 GDR'), - (13, 6300, 'SP3 ', 'https://support.microsoft.com/en-us/help/5003279', '2021-09-15', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3'), - (13, 5888, 'SP2 CU17', 'https://support.microsoft.com/en-us/help/5001092', '2021-03-29', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 17'), - (13, 5882, 'SP2 CU16', 'https://support.microsoft.com/en-us/help/5000645', '2021-02-11', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 16'), - (13, 5865, 'SP2 CU15 GDR', 'https://support.microsoft.com/en-us/help/4583461', '2021-01-12', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 15 GDR'), - (13, 5850, 'SP2 CU15', 'https://support.microsoft.com/en-us/help/4577775', '2020-09-28', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 15'), - (13, 5830, 'SP2 CU14', 'https://support.microsoft.com/en-us/help/4564903', '2020-08-06', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 14'), - (13, 5820, 'SP2 CU13', 'https://support.microsoft.com/en-us/help/4549825', '2020-05-28', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 13'), - (13, 5698, 'SP2 CU12', 'https://support.microsoft.com/en-us/help/4536648', '2020-02-25', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 12'), - (13, 5598, 'SP2 CU11', 'https://support.microsoft.com/en-us/help/4527378', '2019-12-09', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 11'), - (13, 5492, 'SP2 CU10', 'https://support.microsoft.com/en-us/help/4505830', '2019-10-08', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 10'), - (13, 5479, 'SP2 CU9', 'https://support.microsoft.com/en-us/help/4505830', '2019-09-30', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 9'), - (13, 5426, 'SP2 CU8', 'https://support.microsoft.com/en-us/help/4505830', '2019-07-31', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 8'), - (13, 5337, 'SP2 CU7', 'https://support.microsoft.com/en-us/help/4495256', '2019-05-23', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 7'), - (13, 5292, 'SP2 CU6', 'https://support.microsoft.com/en-us/help/4488536', '2019-03-19', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 6'), - (13, 5264, 'SP2 CU5', 'https://support.microsoft.com/en-us/help/4475776', '2019-01-23', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 5'), - (13, 5233, 'SP2 CU4', 'https://support.microsoft.com/en-us/help/4464106', '2018-11-13', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 4'), - (13, 5216, 'SP2 CU3', 'https://support.microsoft.com/en-us/help/4458871', '2018-09-20', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 3'), - (13, 5201, 'SP2 CU2 + Security Update', 'https://support.microsoft.com/en-us/help/4458621', '2018-08-21', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 2 + Security Update'), - (13, 5153, 'SP2 CU2', 'https://support.microsoft.com/en-us/help/4340355', '2018-07-16', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 2'), - (13, 5149, 'SP2 CU1', 'https://support.microsoft.com/en-us/help/4135048', '2018-05-30', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 Cumulative Update 1'), - (13, 5103, 'SP2 GDR', 'https://support.microsoft.com/en-us/help/4583460', '2021-01-12', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 GDR'), - (13, 5026, 'SP2 ', 'https://support.microsoft.com/en-us/help/4052908', '2018-04-24', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 2 '), - (13, 4574, 'SP1 CU15', 'https://support.microsoft.com/en-us/help/4495257', '2019-05-16', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 15'), - (13, 4560, 'SP1 CU14', 'https://support.microsoft.com/en-us/help/4488535', '2019-03-19', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 14'), - (13, 4550, 'SP1 CU13', 'https://support.microsoft.com/en-us/help/4475775', '2019-01-23', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 13'), - (13, 4541, 'SP1 CU12', 'https://support.microsoft.com/en-us/help/4464343', '2018-11-13', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 12'), - (13, 4528, 'SP1 CU11', 'https://support.microsoft.com/en-us/help/4459676', '2018-09-17', '2019-07-09', '2019-07-09', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 11'), - (13, 4514, 'SP1 CU10', 'https://support.microsoft.com/en-us/help/4341569', '2018-07-16', '2019-07-09', '2019-07-09', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 10'), - (13, 4502, 'SP1 CU9', 'https://support.microsoft.com/en-us/help/4100997', '2018-05-30', '2019-07-09', '2019-07-09', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 9'), - (13, 4474, 'SP1 CU8', 'https://support.microsoft.com/en-us/help/4077064', '2018-03-19', '2019-07-09', '2019-07-09', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 8'), - (13, 4466, 'SP1 CU7', 'https://support.microsoft.com/en-us/help/4057119', '2018-01-04', '2019-07-09', '2019-07-09', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 7'), - (13, 4457, 'SP1 CU6', 'https://support.microsoft.com/en-us/help/4037354', '2017-11-20', '2019-07-09', '2019-07-09', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 6'), - (13, 4451, 'SP1 CU5', 'https://support.microsoft.com/en-us/help/4024305', '2017-09-18', '2019-07-09', '2019-07-09', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 5'), - (13, 4446, 'SP1 CU4', 'https://support.microsoft.com/en-us/help/4024305', '2017-08-08', '2019-07-09', '2019-07-09', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 4'), - (13, 4435, 'SP1 CU3', 'https://support.microsoft.com/en-us/help/4019916', '2017-05-15', '2019-07-09', '2019-07-09', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 3'), - (13, 4422, 'SP1 CU2', 'https://support.microsoft.com/en-us/help/4013106', '2017-03-20', '2019-07-09', '2019-07-09', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 2'), - (13, 4411, 'SP1 CU1', 'https://support.microsoft.com/en-us/help/3208177', '2017-01-17', '2019-07-09', '2019-07-09', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 1'), - (13, 4224, 'SP1 CU10 + Security Update', 'https://support.microsoft.com/en-us/help/4458842', '2018-08-22', '2019-07-09', '2019-07-09', 'SQL Server 2016', 'Service Pack 1 Cumulative Update 10 + Security Update'), - (13, 4001, 'SP1 ', 'https://support.microsoft.com/en-us/help/3182545 ', '2016-11-16', '2019-07-09', '2019-07-09', 'SQL Server 2016', 'Service Pack 1 '), - (13, 2216, 'RTM CU9', 'https://support.microsoft.com/en-us/help/4037357', '2017-11-20', '2018-01-09', '2018-01-09', 'SQL Server 2016', 'RTM Cumulative Update 9'), - (13, 2213, 'RTM CU8', 'https://support.microsoft.com/en-us/help/4024304', '2017-09-18', '2018-01-09', '2018-01-09', 'SQL Server 2016', 'RTM Cumulative Update 8'), - (13, 2210, 'RTM CU7', 'https://support.microsoft.com/en-us/help/4024304', '2017-08-08', '2018-01-09', '2018-01-09', 'SQL Server 2016', 'RTM Cumulative Update 7'), - (13, 2204, 'RTM CU6', 'https://support.microsoft.com/en-us/help/4019914', '2017-05-15', '2018-01-09', '2018-01-09', 'SQL Server 2016', 'RTM Cumulative Update 6'), - (13, 2197, 'RTM CU5', 'https://support.microsoft.com/en-us/help/4013105', '2017-03-20', '2018-01-09', '2018-01-09', 'SQL Server 2016', 'RTM Cumulative Update 5'), - (13, 2193, 'RTM CU4', 'https://support.microsoft.com/en-us/help/3205052 ', '2017-01-17', '2018-01-09', '2018-01-09', 'SQL Server 2016', 'RTM Cumulative Update 4'), - (13, 2186, 'RTM CU3', 'https://support.microsoft.com/en-us/help/3205413 ', '2016-11-16', '2018-01-09', '2018-01-09', 'SQL Server 2016', 'RTM Cumulative Update 3'), - (13, 2164, 'RTM CU2', 'https://support.microsoft.com/en-us/help/3182270 ', '2016-09-22', '2018-01-09', '2018-01-09', 'SQL Server 2016', 'RTM Cumulative Update 2'), - (13, 2149, 'RTM CU1', 'https://support.microsoft.com/en-us/help/3164674 ', '2016-07-25', '2018-01-09', '2018-01-09', 'SQL Server 2016', 'RTM Cumulative Update 1'), - (13, 1601, 'RTM ', '', '2016-06-01', '2019-01-09', '2019-01-09', 'SQL Server 2016', 'RTM '), - (12, 6444, 'SP3 CU4 GDR', 'https://support.microsoft.com/kb/5021045', '2023-02-14', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 4 GDR'), - (12, 6439, 'SP3 CU4 GDR', 'https://support.microsoft.com/en-us/help/5014164', '2022-06-14', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 4 GDR'), - (12, 6433, 'SP3 CU4 GDR', 'https://support.microsoft.com/en-us/help/4583462', '2021-01-12', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 4 GDR'), - (12, 6372, 'SP3 CU4 GDR', 'https://support.microsoft.com/en-us/help/4535288', '2020-02-11', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 4 GDR'), - (12, 6329, 'SP3 CU4', 'https://support.microsoft.com/en-us/help/4500181', '2019-07-29', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 4'), - (12, 6259, 'SP3 CU3', 'https://support.microsoft.com/en-us/help/4491539', '2019-04-16', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 3'), - (12, 6214, 'SP3 CU2', 'https://support.microsoft.com/en-us/help/4482960', '2019-02-19', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 2'), - (12, 6205, 'SP3 CU1', 'https://support.microsoft.com/en-us/help/4470220', '2018-12-12', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 1'), - (12, 6164, 'SP3 GDR', 'https://support.microsoft.com/en-us/help/4583463', '2021-01-12', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 GDR'), - (12, 6024, 'SP3 ', 'https://support.microsoft.com/en-us/help/4022619', '2018-10-30', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 '), - (12, 5687, 'SP2 CU18', 'https://support.microsoft.com/en-us/help/4500180', '2019-07-29', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 18'), - (12, 5632, 'SP2 CU17', 'https://support.microsoft.com/en-us/help/4491540', '2019-04-16', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 17'), - (12, 5626, 'SP2 CU16', 'https://support.microsoft.com/en-us/help/4482967', '2019-02-19', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 16'), - (12, 5605, 'SP2 CU15', 'https://support.microsoft.com/en-us/help/4469137', '2018-12-12', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 15'), - (12, 5600, 'SP2 CU14', 'https://support.microsoft.com/en-us/help/4459860', '2018-10-15', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 14'), - (12, 5590, 'SP2 CU13', 'https://support.microsoft.com/en-us/help/4456287', '2018-08-27', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 13'), - (12, 5589, 'SP2 CU12', 'https://support.microsoft.com/en-us/help/4130489', '2018-06-18', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 12'), - (12, 5579, 'SP2 CU11', 'https://support.microsoft.com/en-us/help/4077063', '2018-03-19', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 11'), - (12, 5571, 'SP2 CU10', 'https://support.microsoft.com/en-us/help/4052725', '2018-01-16', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 10'), - (12, 5563, 'SP2 CU9', 'https://support.microsoft.com/en-us/help/4055557', '2017-12-18', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 9'), - (12, 5557, 'SP2 CU8', 'https://support.microsoft.com/en-us/help/4037356', '2017-10-16', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 8'), - (12, 5556, 'SP2 CU7', 'https://support.microsoft.com/en-us/help/4032541', '2017-08-28', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 7'), - (12, 5553, 'SP2 CU6', 'https://support.microsoft.com/en-us/help/4019094', '2017-08-08', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 6'), - (12, 5546, 'SP2 CU5', 'https://support.microsoft.com/en-us/help/4013098', '2017-04-17', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 5'), - (12, 5540, 'SP2 CU4', 'https://support.microsoft.com/en-us/help/4010394', '2017-02-21', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 4'), - (12, 5538, 'SP2 CU3', 'https://support.microsoft.com/en-us/help/3204388 ', '2016-12-19', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 3'), - (12, 5522, 'SP2 CU2', 'https://support.microsoft.com/en-us/help/3188778 ', '2016-10-17', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 2'), - (12, 5511, 'SP2 CU1', 'https://support.microsoft.com/en-us/help/3178925 ', '2016-08-25', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 Cumulative Update 1'), - (12, 5000, 'SP2 ', 'https://support.microsoft.com/en-us/help/3171021 ', '2016-07-11', '2020-01-14', '2020-01-14', 'SQL Server 2014', 'Service Pack 2 '), - (12, 4522, 'SP1 CU13', 'https://support.microsoft.com/en-us/help/4019099', '2017-08-08', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 Cumulative Update 13'), - (12, 4511, 'SP1 CU12', 'https://support.microsoft.com/en-us/help/4017793', '2017-04-17', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 Cumulative Update 12'), - (12, 4502, 'SP1 CU11', 'https://support.microsoft.com/en-us/help/4010392', '2017-02-21', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 Cumulative Update 11'), - (12, 4491, 'SP1 CU10', 'https://support.microsoft.com/en-us/help/3204399 ', '2016-12-19', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 Cumulative Update 10'), - (12, 4474, 'SP1 CU9', 'https://support.microsoft.com/en-us/help/3186964 ', '2016-10-17', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 Cumulative Update 9'), - (12, 4468, 'SP1 CU8', 'https://support.microsoft.com/en-us/help/3174038 ', '2016-08-15', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 Cumulative Update 8'), - (12, 4459, 'SP1 CU7', 'https://support.microsoft.com/en-us/help/3162659 ', '2016-06-20', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 Cumulative Update 7'), - (12, 4457, 'SP1 CU6', 'https://support.microsoft.com/en-us/help/3167392 ', '2016-05-30', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 Cumulative Update 6'), - (12, 4449, 'SP1 CU6', 'https://support.microsoft.com/en-us/help/3144524', '2016-04-18', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 Cumulative Update 6'), - (12, 4438, 'SP1 CU5', 'https://support.microsoft.com/en-us/help/3130926', '2016-02-22', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 Cumulative Update 5'), - (12, 4436, 'SP1 CU4', 'https://support.microsoft.com/en-us/help/3106660', '2015-12-21', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 Cumulative Update 4'), - (12, 4427, 'SP1 CU3', 'https://support.microsoft.com/en-us/help/3094221', '2015-10-19', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 Cumulative Update 3'), - (12, 4422, 'SP1 CU2', 'https://support.microsoft.com/en-us/help/3075950', '2015-08-17', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 Cumulative Update 2'), - (12, 4416, 'SP1 CU1', 'https://support.microsoft.com/en-us/help/3067839', '2015-06-19', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 Cumulative Update 1'), - (12, 4213, 'SP1 MS15-058: GDR Security Update', 'https://support.microsoft.com/en-us/help/3070446', '2015-07-14', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 MS15-058: GDR Security Update'), - (12, 4100, 'SP1 ', 'https://support.microsoft.com/en-us/help/3058865', '2015-05-04', '2017-10-10', '2017-10-10', 'SQL Server 2014', 'Service Pack 1 '), - (12, 2569, 'RTM CU14', 'https://support.microsoft.com/en-us/help/3158271 ', '2016-06-20', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM Cumulative Update 14'), - (12, 2568, 'RTM CU13', 'https://support.microsoft.com/en-us/help/3144517', '2016-04-18', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM Cumulative Update 13'), - (12, 2564, 'RTM CU12', 'https://support.microsoft.com/en-us/help/3130923', '2016-02-22', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM Cumulative Update 12'), - (12, 2560, 'RTM CU11', 'https://support.microsoft.com/en-us/help/3106659', '2015-12-21', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM Cumulative Update 11'), - (12, 2556, 'RTM CU10', 'https://support.microsoft.com/en-us/help/3094220', '2015-10-19', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM Cumulative Update 10'), - (12, 2553, 'RTM CU9', 'https://support.microsoft.com/en-us/help/3075949', '2015-08-17', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM Cumulative Update 9'), - (12, 2548, 'RTM MS15-058: QFE Security Update', 'https://support.microsoft.com/en-us/help/3045323', '2015-07-14', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM MS15-058: QFE Security Update'), - (12, 2546, 'RTM CU8', 'https://support.microsoft.com/en-us/help/3067836', '2015-06-19', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM Cumulative Update 8'), - (12, 2495, 'RTM CU7', 'https://support.microsoft.com/en-us/help/3046038', '2015-04-20', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM Cumulative Update 7'), - (12, 2480, 'RTM CU6', 'https://support.microsoft.com/en-us/help/3031047', '2015-02-16', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM Cumulative Update 6'), - (12, 2456, 'RTM CU5', 'https://support.microsoft.com/en-us/help/3011055', '2014-12-17', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM Cumulative Update 5'), - (12, 2430, 'RTM CU4', 'https://support.microsoft.com/en-us/help/2999197', '2014-10-21', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM Cumulative Update 4'), - (12, 2402, 'RTM CU3', 'https://support.microsoft.com/en-us/help/2984923', '2014-08-18', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM Cumulative Update 3'), - (12, 2381, 'RTM MS14-044: QFE Security Update', 'https://support.microsoft.com/en-us/help/2977316', '2014-08-12', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM MS14-044: QFE Security Update'), - (12, 2370, 'RTM CU2', 'https://support.microsoft.com/en-us/help/2967546', '2014-06-27', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM Cumulative Update 2'), - (12, 2342, 'RTM CU1', 'https://support.microsoft.com/en-us/help/2931693', '2014-04-21', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM Cumulative Update 1'), - (12, 2269, 'RTM MS15-058: GDR Security Update ', 'https://support.microsoft.com/en-us/help/3045324', '2015-07-14', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM MS15-058: GDR Security Update '), - (12, 2254, 'RTM MS14-044: GDR Security Update', 'https://support.microsoft.com/en-us/help/2977315', '2014-08-12', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM MS14-044: GDR Security Update'), - (12, 2000, 'RTM ', '', '2014-04-01', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM '), - (11, 7507, 'SP4 GDR Security Update', 'https://support.microsoft.com/en-us/help/4583465', '2021-01-12', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'Service Pack 4 GDR Security Update for CVE-2021-1636'), - (11, 7493, 'SP4 GDR Security Update', 'https://support.microsoft.com/en-us/help/4532098', '2020-02-11', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'Service Pack 4 GDR Security Update for CVE-2020-0618'), - (11, 7469, 'SP4 On-Demand Hotfix Update', 'https://support.microsoft.com/en-us/help/4091266', '2018-03-28', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'Service Pack 4 SP4 On-Demand Hotfix Update'), - (11, 7462, 'SP4 ADV180002: GDR Security Update', 'https://support.microsoft.com/en-us/help/4057116', '2018-01-12', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'Service Pack 4 ADV180002: GDR Security Update'), - (11, 7001, 'SP4 ', 'https://support.microsoft.com/en-us/help/4018073', '2017-10-02', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'Service Pack 4 '), - (11, 6607, 'SP3 CU10', 'https://support.microsoft.com/en-us/help/4025925', '2017-08-08', '2018-10-09', '2018-10-09', 'SQL Server 2012', 'Service Pack 3 Cumulative Update 10'), - (11, 6598, 'SP3 CU9', 'https://support.microsoft.com/en-us/help/4016762', '2017-05-15', '2018-10-09', '2018-10-09', 'SQL Server 2012', 'Service Pack 3 Cumulative Update 9'), - (11, 6594, 'SP3 CU8', 'https://support.microsoft.com/en-us/help/3205051 ', '2017-03-20', '2018-10-09', '2018-10-09', 'SQL Server 2012', 'Service Pack 3 Cumulative Update 8'), - (11, 6579, 'SP3 CU7', 'https://support.microsoft.com/en-us/help/3205051 ', '2017-01-17', '2018-10-09', '2018-10-09', 'SQL Server 2012', 'Service Pack 3 Cumulative Update 7'), - (11, 6567, 'SP3 CU6', 'https://support.microsoft.com/en-us/help/3194992 ', '2016-11-17', '2018-10-09', '2018-10-09', 'SQL Server 2012', 'Service Pack 3 Cumulative Update 6'), - (11, 6544, 'SP3 CU5', 'https://support.microsoft.com/en-us/help/3180915 ', '2016-09-19', '2018-10-09', '2018-10-09', 'SQL Server 2012', 'Service Pack 3 Cumulative Update 5'), - (11, 6540, 'SP3 CU4', 'https://support.microsoft.com/en-us/help/3165264 ', '2016-07-18', '2018-10-09', '2018-10-09', 'SQL Server 2012', 'Service Pack 3 Cumulative Update 4'), - (11, 6537, 'SP3 CU3', 'https://support.microsoft.com/en-us/help/3152635 ', '2016-05-16', '2018-10-09', '2018-10-09', 'SQL Server 2012', 'Service Pack 3 Cumulative Update 3'), - (11, 6523, 'SP3 CU2', 'https://support.microsoft.com/en-us/help/3137746', '2016-03-21', '2018-10-09', '2018-10-09', 'SQL Server 2012', 'Service Pack 3 Cumulative Update 2'), - (11, 6518, 'SP3 CU1', 'https://support.microsoft.com/en-us/help/3123299', '2016-01-19', '2018-10-09', '2018-10-09', 'SQL Server 2012', 'Service Pack 3 Cumulative Update 1'), - (11, 6020, 'SP3 ', 'https://support.microsoft.com/en-us/help/3072779', '2015-11-20', '2018-10-09', '2018-10-09', 'SQL Server 2012', 'Service Pack 3 '), - (11, 5678, 'SP2 CU16', 'https://support.microsoft.com/en-us/help/3205416 ', '2016-11-17', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 16'), - (11, 5676, 'SP2 CU15', 'https://support.microsoft.com/en-us/help/3205416 ', '2016-11-17', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 15'), - (11, 5657, 'SP2 CU14', 'https://support.microsoft.com/en-us/help/3180914 ', '2016-09-19', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 14'), - (11, 5655, 'SP2 CU13', 'https://support.microsoft.com/en-us/help/3165266 ', '2016-07-18', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 13'), - (11, 5649, 'SP2 CU12', 'https://support.microsoft.com/en-us/help/3152637 ', '2016-05-16', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 12'), - (11, 5646, 'SP2 CU11', 'https://support.microsoft.com/en-us/help/3137745', '2016-03-21', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 11'), - (11, 5644, 'SP2 CU10', 'https://support.microsoft.com/en-us/help/3120313', '2016-01-19', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 10'), - (11, 5641, 'SP2 CU9', 'https://support.microsoft.com/en-us/help/3098512', '2015-11-16', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 9'), - (11, 5634, 'SP2 CU8', 'https://support.microsoft.com/en-us/help/3082561', '2015-09-21', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 8'), - (11, 5623, 'SP2 CU7', 'https://support.microsoft.com/en-us/help/3072100', '2015-07-20', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 7'), - (11, 5613, 'SP2 MS15-058: QFE Security Update', 'https://support.microsoft.com/en-us/help/3045319', '2015-07-14', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 MS15-058: QFE Security Update'), - (11, 5592, 'SP2 CU6', 'https://support.microsoft.com/en-us/help/3052468', '2015-05-18', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 6'), - (11, 5582, 'SP2 CU5', 'https://support.microsoft.com/en-us/help/3037255', '2015-03-16', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 5'), - (11, 5569, 'SP2 CU4', 'https://support.microsoft.com/en-us/help/3007556', '2015-01-19', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 4'), - (11, 5556, 'SP2 CU3', 'https://support.microsoft.com/en-us/help/3002049', '2014-11-17', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 3'), - (11, 5548, 'SP2 CU2', 'https://support.microsoft.com/en-us/help/2983175', '2014-09-15', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 2'), - (11, 5532, 'SP2 CU1', 'https://support.microsoft.com/en-us/help/2976982', '2014-07-23', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 Cumulative Update 1'), - (11, 5343, 'SP2 MS15-058: GDR Security Update', 'https://support.microsoft.com/en-us/help/3045321', '2015-07-14', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 MS15-058: GDR Security Update'), - (11, 5058, 'SP2 ', 'https://support.microsoft.com/en-us/help/2958429', '2014-06-10', '2017-01-10', '2017-01-10', 'SQL Server 2012', 'Service Pack 2 '), - (11, 3513, 'SP1 MS15-058: QFE Security Update', 'https://support.microsoft.com/en-us/help/3045317', '2015-07-14', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 MS15-058: QFE Security Update'), - (11, 3482, 'SP1 CU13', 'https://support.microsoft.com/en-us/help/3002044', '2014-11-17', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 Cumulative Update 13'), - (11, 3470, 'SP1 CU12', 'https://support.microsoft.com/en-us/help/2991533', '2014-09-15', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 Cumulative Update 12'), - (11, 3460, 'SP1 MS14-044: QFE Security Update ', 'https://support.microsoft.com/en-us/help/2977325', '2014-08-12', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 MS14-044: QFE Security Update '), - (11, 3449, 'SP1 CU11', 'https://support.microsoft.com/en-us/help/2975396', '2014-07-21', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 Cumulative Update 11'), - (11, 3431, 'SP1 CU10', 'https://support.microsoft.com/en-us/help/2954099', '2014-05-19', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 Cumulative Update 10'), - (11, 3412, 'SP1 CU9', 'https://support.microsoft.com/en-us/help/2931078', '2014-03-17', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 Cumulative Update 9'), - (11, 3401, 'SP1 CU8', 'https://support.microsoft.com/en-us/help/2917531', '2014-01-20', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 Cumulative Update 8'), - (11, 3393, 'SP1 CU7', 'https://support.microsoft.com/en-us/help/2894115', '2013-11-18', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 Cumulative Update 7'), - (11, 3381, 'SP1 CU6', 'https://support.microsoft.com/en-us/help/2874879', '2013-09-16', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 Cumulative Update 6'), - (11, 3373, 'SP1 CU5', 'https://support.microsoft.com/en-us/help/2861107', '2013-07-15', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 Cumulative Update 5'), - (11, 3368, 'SP1 CU4', 'https://support.microsoft.com/en-us/help/2833645', '2013-05-30', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 Cumulative Update 4'), - (11, 3349, 'SP1 CU3', 'https://support.microsoft.com/en-us/help/2812412', '2013-03-18', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 Cumulative Update 3'), - (11, 3339, 'SP1 CU2', 'https://support.microsoft.com/en-us/help/2790947', '2013-01-21', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 Cumulative Update 2'), - (11, 3321, 'SP1 CU1', 'https://support.microsoft.com/en-us/help/2765331', '2012-11-20', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 Cumulative Update 1'), - (11, 3156, 'SP1 MS15-058: GDR Security Update', 'https://support.microsoft.com/en-us/help/3045318', '2015-07-14', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 MS15-058: GDR Security Update'), - (11, 3153, 'SP1 MS14-044: GDR Security Update', 'https://support.microsoft.com/en-us/help/2977326', '2014-08-12', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 MS14-044: GDR Security Update'), - (11, 3000, 'SP1 ', 'https://support.microsoft.com/en-us/help/2674319', '2012-11-07', '2015-07-14', '2015-07-14', 'SQL Server 2012', 'Service Pack 1 '), - (11, 2424, 'RTM CU11', 'https://support.microsoft.com/en-us/help/2908007', '2013-12-16', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'RTM Cumulative Update 11'), - (11, 2420, 'RTM CU10', 'https://support.microsoft.com/en-us/help/2891666', '2013-10-21', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'RTM Cumulative Update 10'), - (11, 2419, 'RTM CU9', 'https://support.microsoft.com/en-us/help/2867319', '2013-08-20', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'RTM Cumulative Update 9'), - (11, 2410, 'RTM CU8', 'https://support.microsoft.com/en-us/help/2844205', '2013-06-17', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'RTM Cumulative Update 8'), - (11, 2405, 'RTM CU7', 'https://support.microsoft.com/en-us/help/2823247', '2013-04-15', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'RTM Cumulative Update 7'), - (11, 2401, 'RTM CU6', 'https://support.microsoft.com/en-us/help/2728897', '2013-02-18', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'RTM Cumulative Update 6'), - (11, 2395, 'RTM CU5', 'https://support.microsoft.com/en-us/help/2777772', '2012-12-17', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'RTM Cumulative Update 5'), - (11, 2383, 'RTM CU4', 'https://support.microsoft.com/en-us/help/2758687', '2012-10-15', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'RTM Cumulative Update 4'), - (11, 2376, 'RTM MS12-070: QFE Security Update', 'https://support.microsoft.com/en-us/help/2716441', '2012-10-09', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'RTM MS12-070: QFE Security Update'), - (11, 2332, 'RTM CU3', 'https://support.microsoft.com/en-us/help/2723749', '2012-08-31', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'RTM Cumulative Update 3'), - (11, 2325, 'RTM CU2', 'https://support.microsoft.com/en-us/help/2703275', '2012-06-18', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'RTM Cumulative Update 2'), - (11, 2316, 'RTM CU1', 'https://support.microsoft.com/en-us/help/2679368', '2012-04-12', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'RTM Cumulative Update 1'), - (11, 2218, 'RTM MS12-070: GDR Security Update', 'https://support.microsoft.com/en-us/help/2716442', '2012-10-09', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'RTM MS12-070: GDR Security Update'), - (11, 2100, 'RTM ', '', '2012-03-06', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'RTM '), - (10, 6529, 'SP3 MS15-058: QFE Security Update', 'https://support.microsoft.com/en-us/help/3045314', '2015-07-14', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'Service Pack 3 MS15-058: QFE Security Update'), - (10, 6220, 'SP3 MS15-058: QFE Security Update', 'https://support.microsoft.com/en-us/help/3045316', '2015-07-14', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'Service Pack 3 MS15-058: QFE Security Update'), - (10, 6000, 'SP3 ', 'https://support.microsoft.com/en-us/help/2979597', '2014-09-26', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'Service Pack 3 '), - (10, 4339, 'SP2 MS15-058: QFE Security Update', 'https://support.microsoft.com/en-us/help/3045312', '2015-07-14', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 MS15-058: QFE Security Update'), - (10, 4321, 'SP2 MS14-044: QFE Security Update', 'https://support.microsoft.com/en-us/help/2977319', '2014-08-14', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 MS14-044: QFE Security Update'), - (10, 4319, 'SP2 CU13', 'https://support.microsoft.com/en-us/help/2967540', '2014-06-30', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 Cumulative Update 13'), - (10, 4305, 'SP2 CU12', 'https://support.microsoft.com/en-us/help/2938478', '2014-04-21', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 Cumulative Update 12'), - (10, 4302, 'SP2 CU11', 'https://support.microsoft.com/en-us/help/2926028', '2014-02-18', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 Cumulative Update 11'), - (10, 4297, 'SP2 CU10', 'https://support.microsoft.com/en-us/help/2908087', '2013-12-17', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 Cumulative Update 10'), - (10, 4295, 'SP2 CU9', 'https://support.microsoft.com/en-us/help/2887606', '2013-10-28', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 Cumulative Update 9'), - (10, 4290, 'SP2 CU8', 'https://support.microsoft.com/en-us/help/2871401', '2013-08-22', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 Cumulative Update 8'), - (10, 4285, 'SP2 CU7', 'https://support.microsoft.com/en-us/help/2844090', '2013-06-17', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 Cumulative Update 7'), - (10, 4279, 'SP2 CU6', 'https://support.microsoft.com/en-us/help/2830140', '2013-04-15', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 Cumulative Update 6'), - (10, 4276, 'SP2 CU5', 'https://support.microsoft.com/en-us/help/2797460', '2013-02-18', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 Cumulative Update 5'), - (10, 4270, 'SP2 CU4', 'https://support.microsoft.com/en-us/help/2777358', '2012-12-17', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 Cumulative Update 4'), - (10, 4266, 'SP2 CU3', 'https://support.microsoft.com/en-us/help/2754552', '2012-10-15', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 Cumulative Update 3'), - (10, 4263, 'SP2 CU2', 'https://support.microsoft.com/en-us/help/2740411', '2012-08-31', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 Cumulative Update 2'), - (10, 4260, 'SP2 CU1', 'https://support.microsoft.com/en-us/help/2720425', '2012-07-24', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 Cumulative Update 1'), - (10, 4042, 'SP2 MS15-058: GDR Security Update', 'https://support.microsoft.com/en-us/help/3045313', '2015-07-14', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 MS15-058: GDR Security Update'), - (10, 4033, 'SP2 MS14-044: GDR Security Update', 'https://support.microsoft.com/en-us/help/2977320', '2014-08-12', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 MS14-044: GDR Security Update'), - (10, 4000, 'SP2 ', 'https://support.microsoft.com/en-us/help/2630458', '2012-07-26', '2015-10-13', '2015-10-13', 'SQL Server 2008 R2', 'Service Pack 2 '), - (10, 2881, 'SP1 CU14', 'https://support.microsoft.com/en-us/help/2868244', '2013-08-08', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 Cumulative Update 14'), - (10, 2876, 'SP1 CU13', 'https://support.microsoft.com/en-us/help/2855792', '2013-06-17', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 Cumulative Update 13'), - (10, 2874, 'SP1 CU12', 'https://support.microsoft.com/en-us/help/2828727', '2013-04-15', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 Cumulative Update 12'), - (10, 2869, 'SP1 CU11', 'https://support.microsoft.com/en-us/help/2812683', '2013-02-18', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 Cumulative Update 11'), - (10, 2868, 'SP1 CU10', 'https://support.microsoft.com/en-us/help/2783135', '2012-12-17', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 Cumulative Update 10'), - (10, 2866, 'SP1 CU9', 'https://support.microsoft.com/en-us/help/2756574', '2012-10-15', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 Cumulative Update 9'), - (10, 2861, 'SP1 MS12-070: QFE Security Update', 'https://support.microsoft.com/en-us/help/2716439', '2012-10-09', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 MS12-070: QFE Security Update'), - (10, 2822, 'SP1 CU8', 'https://support.microsoft.com/en-us/help/2723743', '2012-08-31', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 Cumulative Update 8'), - (10, 2817, 'SP1 CU7', 'https://support.microsoft.com/en-us/help/2703282', '2012-06-18', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 Cumulative Update 7'), - (10, 2811, 'SP1 CU6', 'https://support.microsoft.com/en-us/help/2679367', '2012-04-16', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 Cumulative Update 6'), - (10, 2806, 'SP1 CU5', 'https://support.microsoft.com/en-us/help/2659694', '2012-02-22', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 Cumulative Update 5'), - (10, 2796, 'SP1 CU4', 'https://support.microsoft.com/en-us/help/2633146', '2011-12-19', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 Cumulative Update 4'), - (10, 2789, 'SP1 CU3', 'https://support.microsoft.com/en-us/help/2591748', '2011-10-17', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 Cumulative Update 3'), - (10, 2772, 'SP1 CU2', 'https://support.microsoft.com/en-us/help/2567714', '2011-08-15', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 Cumulative Update 2'), - (10, 2769, 'SP1 CU1', 'https://support.microsoft.com/en-us/help/2544793', '2011-07-18', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 Cumulative Update 1'), - (10, 2550, 'SP1 MS12-070: GDR Security Update', 'https://support.microsoft.com/en-us/help/2754849', '2012-10-09', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 MS12-070: GDR Security Update'), - (10, 2500, 'SP1 ', 'https://support.microsoft.com/en-us/help/2528583', '2011-07-12', '2013-10-08', '2013-10-08', 'SQL Server 2008 R2', 'Service Pack 1 '), - (10, 1815, 'RTM CU13', 'https://support.microsoft.com/en-us/help/2679366', '2012-04-16', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM Cumulative Update 13'), - (10, 1810, 'RTM CU12', 'https://support.microsoft.com/en-us/help/2659692', '2012-02-21', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM Cumulative Update 12'), - (10, 1809, 'RTM CU11', 'https://support.microsoft.com/en-us/help/2633145', '2011-12-19', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM Cumulative Update 11'), - (10, 1807, 'RTM CU10', 'https://support.microsoft.com/en-us/help/2591746', '2011-10-17', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM Cumulative Update 10'), - (10, 1804, 'RTM CU9', 'https://support.microsoft.com/en-us/help/2567713', '2011-08-15', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM Cumulative Update 9'), - (10, 1797, 'RTM CU8', 'https://support.microsoft.com/en-us/help/2534352', '2011-06-20', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM Cumulative Update 8'), - (10, 1790, 'RTM MS11-049: QFE Security Update', 'https://support.microsoft.com/en-us/help/2494086', '2011-06-14', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM MS11-049: QFE Security Update'), - (10, 1777, 'RTM CU7', 'https://support.microsoft.com/en-us/help/2507770', '2011-04-18', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM Cumulative Update 7'), - (10, 1765, 'RTM CU6', 'https://support.microsoft.com/en-us/help/2489376', '2011-02-21', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM Cumulative Update 6'), - (10, 1753, 'RTM CU5', 'https://support.microsoft.com/en-us/help/2438347', '2010-12-20', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM Cumulative Update 5'), - (10, 1746, 'RTM CU4', 'https://support.microsoft.com/en-us/help/2345451', '2010-10-18', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM Cumulative Update 4'), - (10, 1734, 'RTM CU3', 'https://support.microsoft.com/en-us/help/2261464', '2010-08-16', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM Cumulative Update 3'), - (10, 1720, 'RTM CU2', 'https://support.microsoft.com/en-us/help/2072493', '2010-06-21', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM Cumulative Update 2'), - (10, 1702, 'RTM CU1', 'https://support.microsoft.com/en-us/help/981355', '2010-05-18', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM Cumulative Update 1'), - (10, 1617, 'RTM MS11-049: GDR Security Update', 'https://support.microsoft.com/en-us/help/2494088', '2011-06-14', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM MS11-049: GDR Security Update'), - (10, 1600, 'RTM ', '', '2010-05-10', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM '), - (10, 6535, 'SP3 MS15-058: QFE Security Update', 'https://support.microsoft.com/en-us/help/3045308', '2015-07-14', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 MS15-058: QFE Security Update'), - (10, 6241, 'SP3 MS15-058: GDR Security Update', 'https://support.microsoft.com/en-us/help/3045311', '2015-07-14', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 MS15-058: GDR Security Update'), - (10, 5890, 'SP3 MS15-058: QFE Security Update', 'https://support.microsoft.com/en-us/help/3045303', '2015-07-14', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 MS15-058: QFE Security Update'), - (10, 5869, 'SP3 MS14-044: QFE Security Update', 'https://support.microsoft.com/en-us/help/2984340, https://support.microsoft.com/en-us/help/2977322', '2014-08-12', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 MS14-044: QFE Security Update'), - (10, 5861, 'SP3 CU17', 'https://support.microsoft.com/en-us/help/2958696', '2014-05-19', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 17'), - (10, 5852, 'SP3 CU16', 'https://support.microsoft.com/en-us/help/2936421', '2014-03-17', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 16'), - (10, 5850, 'SP3 CU15', 'https://support.microsoft.com/en-us/help/2923520', '2014-01-20', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 15'), - (10, 5848, 'SP3 CU14', 'https://support.microsoft.com/en-us/help/2893410', '2013-11-18', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 14'), - (10, 5846, 'SP3 CU13', 'https://support.microsoft.com/en-us/help/2880350', '2013-09-16', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 13'), - (10, 5844, 'SP3 CU12', 'https://support.microsoft.com/en-us/help/2863205', '2013-07-15', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 12'), - (10, 5840, 'SP3 CU11', 'https://support.microsoft.com/en-us/help/2834048', '2013-05-20', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 11'), - (10, 5835, 'SP3 CU10', 'https://support.microsoft.com/en-us/help/2814783', '2013-03-18', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 10'), - (10, 5829, 'SP3 CU9', 'https://support.microsoft.com/en-us/help/2799883', '2013-01-21', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 9'), - (10, 5828, 'SP3 CU8', 'https://support.microsoft.com/en-us/help/2771833', '2012-11-19', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 8'), - (10, 5826, 'SP3 MS12-070: QFE Security Update', 'https://support.microsoft.com/en-us/help/2716435', '2012-10-09', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 MS12-070: QFE Security Update'), - (10, 5794, 'SP3 CU7', 'https://support.microsoft.com/en-us/help/2738350', '2012-09-17', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 7'), - (10, 5788, 'SP3 CU6', 'https://support.microsoft.com/en-us/help/2715953', '2012-07-16', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 6'), - (10, 5785, 'SP3 CU5', 'https://support.microsoft.com/en-us/help/2696626', '2012-05-21', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 5'), - (10, 5775, 'SP3 CU4', 'https://support.microsoft.com/en-us/help/2673383', '2012-03-19', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 4'), - (10, 5770, 'SP3 CU3', 'https://support.microsoft.com/en-us/help/2648098', '2012-01-16', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 3'), - (10, 5768, 'SP3 CU2', 'https://support.microsoft.com/en-us/help/2633143', '2011-11-21', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 2'), - (10, 5766, 'SP3 CU1', 'https://support.microsoft.com/en-us/help/2617146', '2011-10-17', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 Cumulative Update 1'), - (10, 5538, 'SP3 MS15-058: GDR Security Update', 'https://support.microsoft.com/en-us/help/3045305', '2015-07-14', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 MS15-058: GDR Security Update'), - (10, 5520, 'SP3 MS14-044: GDR Security Update', 'https://support.microsoft.com/en-us/help/2977321', '2014-08-12', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 MS14-044: GDR Security Update'), - (10, 5512, 'SP3 MS12-070: GDR Security Update', 'https://support.microsoft.com/en-us/help/2716436', '2012-10-09', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 MS12-070: GDR Security Update'), - (10, 5500, 'SP3 ', 'https://support.microsoft.com/en-us/help/2546951', '2011-10-06', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 '), - (10, 4371, 'SP2 MS12-070: QFE Security Update', 'https://support.microsoft.com/en-us/help/2716433', '2012-10-09', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 MS12-070: QFE Security Update'), - (10, 4333, 'SP2 CU11', 'https://support.microsoft.com/en-us/help/2715951', '2012-07-16', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 Cumulative Update 11'), - (10, 4332, 'SP2 CU10', 'https://support.microsoft.com/en-us/help/2696625', '2012-05-21', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 Cumulative Update 10'), - (10, 4330, 'SP2 CU9', 'https://support.microsoft.com/en-us/help/2673382', '2012-03-19', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 Cumulative Update 9'), - (10, 4326, 'SP2 CU8', 'https://support.microsoft.com/en-us/help/2648096', '2012-01-16', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 Cumulative Update 8'), - (10, 4323, 'SP2 CU7', 'https://support.microsoft.com/en-us/help/2617148', '2011-11-21', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 Cumulative Update 7'), - (10, 4321, 'SP2 CU6', 'https://support.microsoft.com/en-us/help/2582285', '2011-09-19', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 Cumulative Update 6'), - (10, 4316, 'SP2 CU5', 'https://support.microsoft.com/en-us/help/2555408', '2011-07-18', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 Cumulative Update 5'), - (10, 4311, 'SP2 MS11-049: QFE Security Update', 'https://support.microsoft.com/en-us/help/2494094', '2011-06-14', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 MS11-049: QFE Security Update'), - (10, 4285, 'SP2 CU4', 'https://support.microsoft.com/en-us/help/2527180', '2011-05-16', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 Cumulative Update 4'), - (10, 4279, 'SP2 CU3', 'https://support.microsoft.com/en-us/help/2498535', '2011-03-17', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 Cumulative Update 3'), - (10, 4272, 'SP2 CU2', 'https://support.microsoft.com/en-us/help/2467239', '2011-01-17', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 Cumulative Update 2'), - (10, 4266, 'SP2 CU1', 'https://support.microsoft.com/en-us/help/2289254', '2010-11-15', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 Cumulative Update 1'), - (10, 4067, 'SP2 MS12-070: GDR Security Update', 'https://support.microsoft.com/en-us/help/2716434', '2012-10-09', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 MS12-070: GDR Security Update'), - (10, 4064, 'SP2 MS11-049: GDR Security Update', 'https://support.microsoft.com/en-us/help/2494089', '2011-06-14', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 MS11-049: GDR Security Update'), - (10, 4000, 'SP2 ', 'https://support.microsoft.com/en-us/help/2285068', '2010-09-29', '2012-10-09', '2012-10-09', 'SQL Server 2008', 'Service Pack 2 '), - (10, 2850, 'SP1 CU16', 'https://support.microsoft.com/en-us/help/2582282', '2011-09-19', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 16'), - (10, 2847, 'SP1 CU15', 'https://support.microsoft.com/en-us/help/2555406', '2011-07-18', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 15'), - (10, 2841, 'SP1 MS11-049: QFE Security Update', 'https://support.microsoft.com/en-us/help/2494100', '2011-06-14', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 MS11-049: QFE Security Update'), - (10, 2821, 'SP1 CU14', 'https://support.microsoft.com/en-us/help/2527187', '2011-05-16', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 14'), - (10, 2816, 'SP1 CU13', 'https://support.microsoft.com/en-us/help/2497673', '2011-03-17', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 13'), - (10, 2808, 'SP1 CU12', 'https://support.microsoft.com/en-us/help/2467236', '2011-01-17', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 12'), - (10, 2804, 'SP1 CU11', 'https://support.microsoft.com/en-us/help/2413738', '2010-11-15', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 11'), - (10, 2799, 'SP1 CU10', 'https://support.microsoft.com/en-us/help/2279604', '2010-09-20', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 10'), - (10, 2789, 'SP1 CU9', 'https://support.microsoft.com/en-us/help/2083921', '2010-07-19', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 9'), - (10, 2775, 'SP1 CU8', 'https://support.microsoft.com/en-us/help/981702', '2010-05-17', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 8'), - (10, 2766, 'SP1 CU7', 'https://support.microsoft.com/en-us/help/979065', '2010-03-26', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 7'), - (10, 2757, 'SP1 CU6', 'https://support.microsoft.com/en-us/help/977443', '2010-01-18', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 6'), - (10, 2746, 'SP1 CU5', 'https://support.microsoft.com/en-us/help/975977', '2009-11-16', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 5'), - (10, 2734, 'SP1 CU4', 'https://support.microsoft.com/en-us/help/973602', '2009-09-21', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 4'), - (10, 2723, 'SP1 CU3', 'https://support.microsoft.com/en-us/help/971491', '2009-07-20', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 3'), - (10, 2714, 'SP1 CU2', 'https://support.microsoft.com/en-us/help/970315', '2009-05-18', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 2'), - (10, 2710, 'SP1 CU1', 'https://support.microsoft.com/en-us/help/969099', '2009-04-16', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 Cumulative Update 1'), - (10, 2573, 'SP1 MS11-049: GDR Security update', 'https://support.microsoft.com/en-us/help/2494096', '2011-06-14', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 MS11-049: GDR Security update'), - (10, 2531, 'SP1 ', '', '2009-04-01', '2011-10-11', '2011-10-11', 'SQL Server 2008', 'Service Pack 1 '), - (10, 1835, 'RTM CU10', 'https://support.microsoft.com/en-us/help/979064', '2010-03-15', '2014-07-08', '2019-07-09', 'SQL Server 2008', 'RTM Cumulative Update 10'), - (10, 1828, 'RTM CU9', 'https://support.microsoft.com/en-us/help/977444', '2010-01-18', '2014-07-08', '2019-07-09', 'SQL Server 2008', 'RTM Cumulative Update 9'), - (10, 1823, 'RTM CU8', 'https://support.microsoft.com/en-us/help/975976', '2009-11-16', '2014-07-08', '2019-07-09', 'SQL Server 2008', 'RTM Cumulative Update 8'), - (10, 1818, 'RTM CU7', 'https://support.microsoft.com/en-us/help/973601', '2009-09-21', '2014-07-08', '2019-07-09', 'SQL Server 2008', 'RTM Cumulative Update 7'), - (10, 1812, 'RTM CU6', 'https://support.microsoft.com/en-us/help/971490', '2009-07-20', '2014-07-08', '2019-07-09', 'SQL Server 2008', 'RTM Cumulative Update 6'), - (10, 1806, 'RTM CU5', 'https://support.microsoft.com/en-us/help/969531', '2009-05-18', '2014-07-08', '2019-07-09', 'SQL Server 2008', 'RTM Cumulative Update 5'), - (10, 1798, 'RTM CU4', 'https://support.microsoft.com/en-us/help/963036', '2009-03-16', '2014-07-08', '2019-07-09', 'SQL Server 2008', 'RTM Cumulative Update 4'), - (10, 1787, 'RTM CU3', 'https://support.microsoft.com/en-us/help/960484', '2009-01-19', '2014-07-08', '2019-07-09', 'SQL Server 2008', 'RTM Cumulative Update 3'), - (10, 1779, 'RTM CU2', 'https://support.microsoft.com/en-us/help/958186', '2008-11-19', '2014-07-08', '2019-07-09', 'SQL Server 2008', 'RTM Cumulative Update 2'), - (10, 1763, 'RTM CU1', 'https://support.microsoft.com/en-us/help/956717', '2008-09-22', '2014-07-08', '2019-07-09', 'SQL Server 2008', 'RTM Cumulative Update 1'), - (10, 1600, 'RTM ', '', '2008-08-06', '2014-07-08', '2019-07-09', 'SQL Server 2008', 'RTM ') -; -GO -IF OBJECT_ID('dbo.sp_BlitzFirst') IS NULL - EXEC ('CREATE PROCEDURE dbo.sp_BlitzFirst AS RETURN 0;'); -GO - - -ALTER PROCEDURE [dbo].[sp_BlitzFirst] - @LogMessage NVARCHAR(4000) = NULL , - @Help TINYINT = 0 , - @AsOf DATETIMEOFFSET = NULL , - @ExpertMode TINYINT = 0 , - @Seconds INT = 5 , - @OutputType VARCHAR(20) = 'TABLE' , - @OutputServerName NVARCHAR(256) = NULL , - @OutputDatabaseName NVARCHAR(256) = NULL , - @OutputSchemaName NVARCHAR(256) = NULL , - @OutputTableName NVARCHAR(256) = NULL , - @OutputTableNameFileStats NVARCHAR(256) = NULL , - @OutputTableNamePerfmonStats NVARCHAR(256) = NULL , - @OutputTableNameWaitStats NVARCHAR(256) = NULL , - @OutputTableNameBlitzCache NVARCHAR(256) = NULL , - @OutputTableNameBlitzWho NVARCHAR(256) = NULL , - @OutputResultSets NVARCHAR(500) = N'BlitzWho_Start|Findings|FileStats|PerfmonStats|WaitStats|BlitzCache|BlitzWho_End' , - @OutputTableRetentionDays TINYINT = 7 , - @OutputXMLasNVARCHAR TINYINT = 0 , - @FilterPlansByDatabase VARCHAR(MAX) = NULL , - @CheckProcedureCache TINYINT = 0 , - @CheckServerInfo TINYINT = 1 , - @FileLatencyThresholdMS INT = 100 , - @SinceStartup TINYINT = 0 , - @ShowSleepingSPIDs TINYINT = 0 , - @BlitzCacheSkipAnalysis BIT = 1 , - @MemoryGrantThresholdPct DECIMAL(5,2) = 15.00, - @LogMessageCheckID INT = 38, - @LogMessagePriority TINYINT = 1, - @LogMessageFindingsGroup VARCHAR(50) = 'Logged Message', - @LogMessageFinding VARCHAR(200) = 'Logged from sp_BlitzFirst', - @LogMessageURL VARCHAR(200) = '', - @LogMessageCheckDate DATETIMEOFFSET = NULL, - @Debug BIT = 0, - @Version VARCHAR(30) = NULL OUTPUT, - @VersionDate DATETIME = NULL OUTPUT, - @VersionCheckMode BIT = 0 - WITH EXECUTE AS CALLER, RECOMPILE -AS -BEGIN -SET NOCOUNT ON; -SET STATISTICS XML OFF; -SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - -SELECT @Version = '8.19', @VersionDate = '20240222'; - -IF(@VersionCheckMode = 1) -BEGIN - RETURN; -END; - -IF @Help = 1 -BEGIN -PRINT ' -sp_BlitzFirst from http://FirstResponderKit.org - -This script gives you a prioritized list of why your SQL Server is slow right now. - -This is not an overall health check - for that, check out sp_Blitz. - -To learn more, visit http://FirstResponderKit.org where you can download new -versions for free, watch training videos on how it works, get more info on -the findings, contribute your own code, and more. - -Known limitations of this version: - - Only Microsoft-supported versions of SQL Server. Sorry, 2005 and 2000. It - may work just fine on 2005, and if it does, hug your parents. Just don''t - file support issues if it breaks. - - If a temp table called #CustomPerfmonCounters exists for any other session, - but not our session, this stored proc will fail with an error saying the - temp table #CustomPerfmonCounters does not exist. - - @OutputServerName is not functional yet. - - If @OutputDatabaseName, SchemaName, TableName, etc are quoted with brackets, - the write to table may silently fail. Look, I never said I was good at this. - -Unknown limitations of this version: - - None. Like Zombo.com, the only limit is yourself. - -Changes - for the full list of improvements and fixes in this version, see: -https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/ - - -MIT License - -Copyright (c) Brent Ozar Unlimited - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - -'; -RETURN; -END; /* @Help = 1 */ - -RAISERROR('Setting up configuration variables',10,1) WITH NOWAIT; -DECLARE @StringToExecute NVARCHAR(MAX), - @ParmDefinitions NVARCHAR(4000), - @Parm1 NVARCHAR(4000), - @OurSessionID INT, - @LineFeed NVARCHAR(10), - @StockWarningHeader NVARCHAR(MAX) = N'', - @StockWarningFooter NVARCHAR(MAX) = N'', - @StockDetailsHeader NVARCHAR(MAX) = N'', - @StockDetailsFooter NVARCHAR(MAX) = N'', - @StartSampleTime DATETIMEOFFSET, - @FinishSampleTime DATETIMEOFFSET, - @FinishSampleTimeWaitFor DATETIME, - @AsOf1 DATETIMEOFFSET, - @AsOf2 DATETIMEOFFSET, - @ServiceName sysname, - @OutputTableNameFileStats_View NVARCHAR(256), - @OutputTableNamePerfmonStats_View NVARCHAR(256), - @OutputTableNamePerfmonStatsActuals_View NVARCHAR(256), - @OutputTableNameWaitStats_View NVARCHAR(256), - @OutputTableNameWaitStats_Categories NVARCHAR(256), - @OutputTableCleanupDate DATE, - @ObjectFullName NVARCHAR(2000), - @BlitzWho NVARCHAR(MAX) = N'EXEC dbo.sp_BlitzWho @ShowSleepingSPIDs = ' + CONVERT(NVARCHAR(1), @ShowSleepingSPIDs) + N';', - @BlitzCacheMinutesBack INT, - @UnquotedOutputServerName NVARCHAR(256) = @OutputServerName , - @UnquotedOutputDatabaseName NVARCHAR(256) = @OutputDatabaseName , - @UnquotedOutputSchemaName NVARCHAR(256) = @OutputSchemaName , - @LocalServerName NVARCHAR(128) = CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128)), - @dm_exec_query_statistics_xml BIT = 0, - @total_cpu_usage BIT = 0, - @get_thread_time_ms NVARCHAR(MAX) = N'', - @thread_time_ms FLOAT = 0; - -/* Sanitize our inputs */ -SELECT - @OutputTableNameFileStats_View = QUOTENAME(@OutputTableNameFileStats + '_Deltas'), - @OutputTableNamePerfmonStats_View = QUOTENAME(@OutputTableNamePerfmonStats + '_Deltas'), - @OutputTableNamePerfmonStatsActuals_View = QUOTENAME(@OutputTableNamePerfmonStats + '_Actuals'), - @OutputTableNameWaitStats_View = QUOTENAME(@OutputTableNameWaitStats + '_Deltas'), - @OutputTableNameWaitStats_Categories = QUOTENAME(@OutputTableNameWaitStats + '_Categories'); - -SELECT - @OutputDatabaseName = QUOTENAME(@OutputDatabaseName), - @OutputSchemaName = QUOTENAME(@OutputSchemaName), - @OutputTableName = QUOTENAME(@OutputTableName), - @OutputTableNameFileStats = QUOTENAME(@OutputTableNameFileStats), - @OutputTableNamePerfmonStats = QUOTENAME(@OutputTableNamePerfmonStats), - @OutputTableNameWaitStats = QUOTENAME(@OutputTableNameWaitStats), - @OutputTableCleanupDate = CAST( (DATEADD(DAY, -1 * @OutputTableRetentionDays, GETDATE() ) ) AS DATE), - /* @OutputTableNameBlitzCache = QUOTENAME(@OutputTableNameBlitzCache), We purposely don't sanitize this because sp_BlitzCache will */ - /* @OutputTableNameBlitzWho = QUOTENAME(@OutputTableNameBlitzWho), We purposely don't sanitize this because sp_BlitzWho will */ - @LineFeed = CHAR(13) + CHAR(10), - @OurSessionID = @@SPID, - @OutputType = UPPER(@OutputType); - -IF(@OutputType = 'NONE' AND (@OutputTableName IS NULL OR @OutputSchemaName IS NULL OR @OutputDatabaseName IS NULL)) -BEGIN - RAISERROR('This procedure should be called with a value for all @Output* parameters, as @OutputType is set to NONE',12,1); - RETURN; -END; - -IF UPPER(@OutputType) LIKE 'TOP 10%' SET @OutputType = 'Top10'; -IF @OutputType = 'Top10' SET @SinceStartup = 1; - -/* Logged Message - CheckID 38 */ -IF @LogMessage IS NOT NULL - BEGIN - - RAISERROR('Saving LogMessage to table',10,1) WITH NOWAIT; - - /* Try to set the output table parameters if they don't exist */ - IF @OutputSchemaName IS NULL AND @OutputTableName IS NULL AND @OutputDatabaseName IS NULL - BEGIN - SET @OutputSchemaName = N'[dbo]'; - SET @OutputTableName = N'[BlitzFirst]'; - - /* Look for the table in the current database */ - SELECT TOP 1 @OutputDatabaseName = QUOTENAME(TABLE_CATALOG) - FROM INFORMATION_SCHEMA.TABLES - WHERE TABLE_SCHEMA = 'dbo' AND TABLE_NAME = 'BlitzFirst'; - - IF @OutputDatabaseName IS NULL AND EXISTS (SELECT * FROM sys.databases WHERE name = 'DBAtools') - SET @OutputDatabaseName = '[DBAtools]'; - - END; - - IF @OutputDatabaseName IS NULL OR @OutputSchemaName IS NULL OR @OutputTableName IS NULL - OR NOT EXISTS ( SELECT * - FROM sys.databases - WHERE QUOTENAME([name]) = @OutputDatabaseName) - BEGIN - RAISERROR('We have a hard time logging a message without a valid @OutputDatabaseName, @OutputSchemaName, and @OutputTableName to log it to.', 0, 1) WITH NOWAIT; - RETURN; - END; - IF @LogMessageCheckDate IS NULL - SET @LogMessageCheckDate = SYSDATETIMEOFFSET(); - SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' - + @OutputSchemaName + ''') INSERT ' - + @OutputDatabaseName + '.' - + @OutputSchemaName + '.' - + @OutputTableName - + ' (ServerName, CheckDate, CheckID, Priority, FindingsGroup, Finding, Details, URL) VALUES( ' - + ' @SrvName, @LogMessageCheckDate, @LogMessageCheckID, @LogMessagePriority, @LogMessageFindingsGroup, @LogMessageFinding, @LogMessage, @LogMessageURL)'; - - EXECUTE sp_executesql @StringToExecute, - N'@SrvName NVARCHAR(128), @LogMessageCheckID INT, @LogMessagePriority TINYINT, @LogMessageFindingsGroup VARCHAR(50), @LogMessageFinding VARCHAR(200), @LogMessage NVARCHAR(4000), @LogMessageCheckDate DATETIMEOFFSET, @LogMessageURL VARCHAR(200)', - @LocalServerName, @LogMessageCheckID, @LogMessagePriority, @LogMessageFindingsGroup, @LogMessageFinding, @LogMessage, @LogMessageCheckDate, @LogMessageURL; - - RAISERROR('LogMessage saved to table. We have made a note of your activity. Keep up the good work.',10,1) WITH NOWAIT; - - RETURN; - END; - -IF @SinceStartup = 1 - SELECT @Seconds = 0, @ExpertMode = 1; - - -IF @OutputType = 'SCHEMA' -BEGIN - SELECT FieldList = '[Priority] TINYINT, [FindingsGroup] VARCHAR(50), [Finding] VARCHAR(200), [URL] VARCHAR(200), [Details] NVARCHAR(4000), [HowToStopIt] NVARCHAR(MAX), [QueryPlan] XML, [QueryText] NVARCHAR(MAX)'; - -END; -ELSE IF @AsOf IS NOT NULL AND @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @OutputTableName IS NOT NULL -BEGIN - /* They want to look into the past. */ - SET @AsOf1= DATEADD(mi, -15, @AsOf); - SET @AsOf2= DATEADD(mi, +15, @AsOf); - - SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' - + @OutputSchemaName + ''') SELECT CheckDate, [Priority], [FindingsGroup], [Finding], [URL], CAST([Details] AS [XML]) AS Details,' - + '[HowToStopIt], [CheckID], [StartTime], [LoginName], [NTUserName], [OriginalLoginName], [ProgramName], [HostName], [DatabaseID],' - + '[DatabaseName], [OpenTransactionCount], [QueryPlan], [QueryText] FROM ' - + @OutputDatabaseName + '.' - + @OutputSchemaName + '.' - + @OutputTableName - + ' WHERE CheckDate >= @AsOf1' - + ' AND CheckDate <= @AsOf2' - + ' /*ORDER BY CheckDate, Priority , FindingsGroup , Finding , Details*/;'; - EXEC sp_executesql @StringToExecute, - N'@AsOf1 DATETIMEOFFSET, @AsOf2 DATETIMEOFFSET', - @AsOf1, @AsOf2 - - -END; /* IF @AsOf IS NOT NULL AND @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @OutputTableName IS NOT NULL */ -ELSE IF @LogMessage IS NULL /* IF @OutputType = 'SCHEMA' */ -BEGIN - /* What's running right now? This is the first and last result set. */ - IF @SinceStartup = 0 AND @Seconds > 0 AND @ExpertMode = 1 AND @OutputType <> 'NONE' AND @OutputResultSets LIKE N'%BlitzWho_Start%' - BEGIN - IF OBJECT_ID('master.dbo.sp_BlitzWho') IS NULL AND OBJECT_ID('dbo.sp_BlitzWho') IS NULL - BEGIN - PRINT N'sp_BlitzWho is not installed in the current database_files. You can get a copy from http://FirstResponderKit.org'; - END; - ELSE - BEGIN - EXEC (@BlitzWho); - END; - END; /* IF @SinceStartup = 0 AND @Seconds > 0 AND @ExpertMode = 1 AND @OutputType <> 'NONE' - What's running right now? This is the first and last result set. */ - - /* Set start/finish times AFTER sp_BlitzWho runs. For more info: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2244 */ - IF @Seconds = 0 AND SERVERPROPERTY('EngineEdition') = 5 /*SERVERPROPERTY('Edition') = 'SQL Azure'*/ - WITH WaitTimes AS ( - SELECT wait_type, wait_time_ms, - NTILE(3) OVER(ORDER BY wait_time_ms) AS grouper - FROM sys.dm_os_wait_stats w - WHERE wait_type IN ('DIRTY_PAGE_POLL','HADR_FILESTREAM_IOMGR_IOCOMPLETION','LAZYWRITER_SLEEP', - 'LOGMGR_QUEUE','REQUEST_FOR_DEADLOCK_SEARCH','XE_TIMER_EVENT') - ) - SELECT @StartSampleTime = DATEADD(mi, AVG(-wait_time_ms / 1000 / 60), SYSDATETIMEOFFSET()), @FinishSampleTime = SYSDATETIMEOFFSET() - FROM WaitTimes - WHERE grouper = 2; - ELSE IF @Seconds = 0 AND SERVERPROPERTY('EngineEdition') <> 5 /*SERVERPROPERTY('Edition') <> 'SQL Azure'*/ - SELECT @StartSampleTime = DATEADD(MINUTE,DATEDIFF(MINUTE, GETDATE(), GETUTCDATE()),create_date) , @FinishSampleTime = SYSDATETIMEOFFSET() - FROM sys.databases - WHERE database_id = 2; - ELSE - SELECT @StartSampleTime = SYSDATETIMEOFFSET(), - @FinishSampleTime = DATEADD(ss, @Seconds, SYSDATETIMEOFFSET()), - @FinishSampleTimeWaitFor = DATEADD(ss, @Seconds, GETDATE()); - - - IF EXISTS - ( - - SELECT - 1/0 - FROM sys.all_columns AS ac - WHERE ac.object_id = OBJECT_ID('sys.dm_os_schedulers') - AND ac.name = 'total_cpu_usage_ms' - - ) - BEGIN - - SELECT - @total_cpu_usage = 1, - @get_thread_time_ms += - N' - SELECT - @thread_time_ms = - CONVERT - ( - FLOAT, - SUM(s.total_cpu_usage_ms) - ) - FROM sys.dm_os_schedulers AS s - WHERE s.status = ''VISIBLE ONLINE'' - AND s.is_online = 1 - OPTION(RECOMPILE); - '; - - END - ELSE - BEGIN - SELECT - @total_cpu_usage = 0, - @get_thread_time_ms += - N' - SELECT - @thread_time_ms = - CONVERT - ( - FLOAT, - SUM(s.total_worker_time / 1000.) - ) - FROM sys.dm_exec_query_stats AS s - OPTION(RECOMPILE); - '; - END - - RAISERROR('Now starting diagnostic analysis',10,1) WITH NOWAIT; - - /* - We start by creating #BlitzFirstResults. It's a temp table that will store - the results from our checks. Throughout the rest of this stored procedure, - we're running a series of checks looking for dangerous things inside the SQL - Server. When we find a problem, we insert rows into the temp table. At the - end, we return these results to the end user. - - #BlitzFirstResults has a CheckID field, but there's no Check table. As we do - checks, we insert data into this table, and we manually put in the CheckID. - We (Brent Ozar Unlimited) maintain a list of the checks by ID#. You can - download that from http://FirstResponderKit.org if you want to build - a tool that relies on the output of sp_BlitzFirst. - */ - - - IF OBJECT_ID('tempdb..#BlitzFirstResults') IS NOT NULL - DROP TABLE #BlitzFirstResults; - CREATE TABLE #BlitzFirstResults - ( - ID INT IDENTITY(1, 1) PRIMARY KEY CLUSTERED, - CheckID INT NOT NULL, - Priority TINYINT NOT NULL, - FindingsGroup VARCHAR(50) NOT NULL, - Finding VARCHAR(200) NOT NULL, - URL VARCHAR(200) NULL, - Details NVARCHAR(MAX) NULL, - HowToStopIt NVARCHAR(MAX) NULL, - QueryPlan [XML] NULL, - QueryText NVARCHAR(MAX) NULL, - StartTime DATETIMEOFFSET NULL, - LoginName NVARCHAR(128) NULL, - NTUserName NVARCHAR(128) NULL, - OriginalLoginName NVARCHAR(128) NULL, - ProgramName NVARCHAR(128) NULL, - HostName NVARCHAR(128) NULL, - DatabaseID INT NULL, - DatabaseName NVARCHAR(128) NULL, - OpenTransactionCount INT NULL, - QueryStatsNowID INT NULL, - QueryStatsFirstID INT NULL, - PlanHandle VARBINARY(64) NULL, - DetailsInt INT NULL, - QueryHash BINARY(8) - ); - - IF OBJECT_ID('tempdb..#WaitStats') IS NOT NULL - DROP TABLE #WaitStats; - CREATE TABLE #WaitStats ( - Pass TINYINT NOT NULL, - wait_type NVARCHAR(60), - wait_time_ms BIGINT, - thread_time_ms FLOAT, - signal_wait_time_ms BIGINT, - waiting_tasks_count BIGINT, - SampleTime datetimeoffset - ); - - IF OBJECT_ID('tempdb..#FileStats') IS NOT NULL - DROP TABLE #FileStats; - CREATE TABLE #FileStats ( - ID INT IDENTITY(1, 1) PRIMARY KEY CLUSTERED, - Pass TINYINT NOT NULL, - SampleTime DATETIMEOFFSET NOT NULL, - DatabaseID INT NOT NULL, - FileID INT NOT NULL, - DatabaseName NVARCHAR(256) , - FileLogicalName NVARCHAR(256) , - TypeDesc NVARCHAR(60) , - SizeOnDiskMB BIGINT , - io_stall_read_ms BIGINT , - num_of_reads BIGINT , - bytes_read BIGINT , - io_stall_write_ms BIGINT , - num_of_writes BIGINT , - bytes_written BIGINT, - PhysicalName NVARCHAR(520) , - avg_stall_read_ms INT , - avg_stall_write_ms INT - ); - - IF OBJECT_ID('tempdb..#QueryStats') IS NOT NULL - DROP TABLE #QueryStats; - CREATE TABLE #QueryStats ( - ID INT IDENTITY(1, 1) PRIMARY KEY CLUSTERED, - Pass INT NOT NULL, - SampleTime DATETIMEOFFSET NOT NULL, - [sql_handle] VARBINARY(64), - statement_start_offset INT, - statement_end_offset INT, - plan_generation_num BIGINT, - plan_handle VARBINARY(64), - execution_count BIGINT, - total_worker_time BIGINT, - total_physical_reads BIGINT, - total_logical_writes BIGINT, - total_logical_reads BIGINT, - total_clr_time BIGINT, - total_elapsed_time BIGINT, - creation_time DATETIMEOFFSET, - query_hash BINARY(8), - query_plan_hash BINARY(8), - Points TINYINT - ); - - IF OBJECT_ID('tempdb..#PerfmonStats') IS NOT NULL - DROP TABLE #PerfmonStats; - CREATE TABLE #PerfmonStats ( - ID INT IDENTITY(1, 1) PRIMARY KEY CLUSTERED, - Pass TINYINT NOT NULL, - SampleTime DATETIMEOFFSET NOT NULL, - [object_name] NVARCHAR(128) NOT NULL, - [counter_name] NVARCHAR(128) NOT NULL, - [instance_name] NVARCHAR(128) NULL, - [cntr_value] BIGINT NULL, - [cntr_type] INT NOT NULL, - [value_delta] BIGINT NULL, - [value_per_second] DECIMAL(18,2) NULL - ); - - IF OBJECT_ID('tempdb..#PerfmonCounters') IS NOT NULL - DROP TABLE #PerfmonCounters; - CREATE TABLE #PerfmonCounters ( - ID INT IDENTITY(1, 1) PRIMARY KEY CLUSTERED, - [object_name] NVARCHAR(128) NOT NULL, - [counter_name] NVARCHAR(128) NOT NULL, - [instance_name] NVARCHAR(128) NULL - ); - - IF OBJECT_ID('tempdb..#FilterPlansByDatabase') IS NOT NULL - DROP TABLE #FilterPlansByDatabase; - CREATE TABLE #FilterPlansByDatabase (DatabaseID INT PRIMARY KEY CLUSTERED); - - IF OBJECT_ID ('tempdb..#checkversion') IS NOT NULL - DROP TABLE #checkversion; - CREATE TABLE #checkversion ( - version NVARCHAR(128), - common_version AS SUBSTRING(version, 1, CHARINDEX('.', version) + 1 ), - major AS PARSENAME(CONVERT(VARCHAR(32), version), 4), - minor AS PARSENAME(CONVERT(VARCHAR(32), version), 3), - build AS PARSENAME(CONVERT(VARCHAR(32), version), 2), - revision AS PARSENAME(CONVERT(VARCHAR(32), version), 1) - ); - - IF OBJECT_ID('tempdb..##WaitCategories') IS NULL - BEGIN - /* We reuse this one by default rather than recreate it every time. */ - CREATE TABLE ##WaitCategories - ( - WaitType NVARCHAR(60) PRIMARY KEY CLUSTERED, - WaitCategory NVARCHAR(128) NOT NULL, - Ignorable BIT DEFAULT 0 - ); - END; /* IF OBJECT_ID('tempdb..##WaitCategories') IS NULL */ - - IF 527 > (SELECT COALESCE(SUM(1),0) FROM ##WaitCategories) - BEGIN - TRUNCATE TABLE ##WaitCategories; - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('ASYNC_IO_COMPLETION','Other Disk IO',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('ASYNC_NETWORK_IO','Network IO',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BACKUPIO','Other Disk IO',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_CONNECTION_RECEIVE_TASK','Service Broker',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_DISPATCHER','Service Broker',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_ENDPOINT_STATE_MUTEX','Service Broker',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_EVENTHANDLER','Service Broker',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_FORWARDER','Service Broker',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_INIT','Service Broker',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_MASTERSTART','Service Broker',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_RECEIVE_WAITFOR','User Wait',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_REGISTERALLENDPOINTS','Service Broker',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_SERVICE','Service Broker',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_SHUTDOWN','Service Broker',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_START','Service Broker',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_TASK_SHUTDOWN','Service Broker',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_TASK_STOP','Service Broker',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_TASK_SUBMIT','Service Broker',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_TO_FLUSH','Service Broker',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_TRANSMISSION_OBJECT','Service Broker',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_TRANSMISSION_TABLE','Service Broker',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_TRANSMISSION_WORK','Service Broker',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('BROKER_TRANSMITTER','Service Broker',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CHECKPOINT_QUEUE','Idle',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CHKPT','Tran Log IO',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CLR_AUTO_EVENT','SQL CLR',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CLR_CRST','SQL CLR',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CLR_JOIN','SQL CLR',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CLR_MANUAL_EVENT','SQL CLR',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CLR_MEMORY_SPY','SQL CLR',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CLR_MONITOR','SQL CLR',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CLR_RWLOCK_READER','SQL CLR',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CLR_RWLOCK_WRITER','SQL CLR',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CLR_SEMAPHORE','SQL CLR',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CLR_TASK_START','SQL CLR',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CLRHOST_STATE_ACCESS','SQL CLR',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CMEMPARTITIONED','Memory',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CMEMTHREAD','Memory',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CXPACKET','Parallelism',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('CXCONSUMER','Parallelism',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DBMIRROR_DBM_EVENT','Mirroring',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DBMIRROR_DBM_MUTEX','Mirroring',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DBMIRROR_EVENTS_QUEUE','Mirroring',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DBMIRROR_SEND','Mirroring',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DBMIRROR_WORKER_QUEUE','Mirroring',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DBMIRRORING_CMD','Mirroring',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DIRTY_PAGE_POLL','Other',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DIRTY_PAGE_TABLE_LOCK','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DISPATCHER_QUEUE_SEMAPHORE','Other',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DPT_ENTRY_LOCK','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DTC','Transaction',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DTC_ABORT_REQUEST','Transaction',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DTC_RESOLVE','Transaction',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DTC_STATE','Transaction',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DTC_TMDOWN_REQUEST','Transaction',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DTC_WAITFOR_OUTCOME','Transaction',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DTCNEW_ENLIST','Transaction',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DTCNEW_PREPARE','Transaction',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DTCNEW_RECOVERY','Transaction',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DTCNEW_TM','Transaction',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DTCNEW_TRANSACTION_ENLISTMENT','Transaction',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('DTCPNTSYNC','Transaction',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('EE_PMOLOCK','Memory',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('EXCHANGE','Parallelism',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('EXTERNAL_SCRIPT_NETWORK_IOF','Network IO',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FCB_REPLICA_READ','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FCB_REPLICA_WRITE','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_COMPROWSET_RWLOCK','Full Text Search',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_IFTS_RWLOCK','Full Text Search',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_IFTS_SCHEDULER_IDLE_WAIT','Idle',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_IFTSHC_MUTEX','Full Text Search',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_IFTSISM_MUTEX','Full Text Search',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_MASTER_MERGE','Full Text Search',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_MASTER_MERGE_COORDINATOR','Full Text Search',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_METADATA_MUTEX','Full Text Search',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_PROPERTYLIST_CACHE','Full Text Search',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FT_RESTART_CRAWL','Full Text Search',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('FULLTEXT GATHERER','Full Text Search',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_AG_MUTEX','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_AR_CRITICAL_SECTION_ENTRY','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_AR_MANAGER_MUTEX','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_AR_UNLOAD_COMPLETED','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_ARCONTROLLER_NOTIFICATIONS_SUBSCRIBER_LIST','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_BACKUP_BULK_LOCK','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_BACKUP_QUEUE','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_CLUSAPI_CALL','Replication',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_COMPRESSED_CACHE_SYNC','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_CONNECTIVITY_INFO','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_DATABASE_FLOW_CONTROL','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_DATABASE_VERSIONING_STATE','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_DATABASE_WAIT_FOR_RECOVERY','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_DATABASE_WAIT_FOR_RESTART','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_DATABASE_WAIT_FOR_TRANSITION_TO_VERSIONING','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_DB_COMMAND','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_DB_OP_COMPLETION_SYNC','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_DB_OP_START_SYNC','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_DBR_SUBSCRIBER','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_DBR_SUBSCRIBER_FILTER_LIST','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_DBSEEDING','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_DBSEEDING_LIST','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_DBSTATECHANGE_SYNC','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_FABRIC_CALLBACK','Replication',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_FILESTREAM_BLOCK_FLUSH','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_FILESTREAM_FILE_CLOSE','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_FILESTREAM_FILE_REQUEST','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_FILESTREAM_IOMGR','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_FILESTREAM_IOMGR_IOCOMPLETION','Replication',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_FILESTREAM_MANAGER','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_FILESTREAM_PREPROC','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_GROUP_COMMIT','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_LOGCAPTURE_SYNC','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_LOGCAPTURE_WAIT','Replication',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_LOGPROGRESS_SYNC','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_NOTIFICATION_DEQUEUE','Replication',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_NOTIFICATION_WORKER_EXCLUSIVE_ACCESS','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_NOTIFICATION_WORKER_STARTUP_SYNC','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_NOTIFICATION_WORKER_TERMINATION_SYNC','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_PARTNER_SYNC','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_READ_ALL_NETWORKS','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_RECOVERY_WAIT_FOR_CONNECTION','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_RECOVERY_WAIT_FOR_UNDO','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_REPLICAINFO_SYNC','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_SEEDING_CANCELLATION','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_SEEDING_FILE_LIST','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_SEEDING_LIMIT_BACKUPS','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_SEEDING_SYNC_COMPLETION','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_SEEDING_TIMEOUT_TASK','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_SEEDING_WAIT_FOR_COMPLETION','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_SYNC_COMMIT','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_SYNCHRONIZING_THROTTLE','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_TDS_LISTENER_SYNC','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_TDS_LISTENER_SYNC_PROCESSING','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_THROTTLE_LOG_RATE_GOVERNOR','Log Rate Governor',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_TIMER_TASK','Replication',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_TRANSPORT_DBRLIST','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_TRANSPORT_FLOW_CONTROL','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_TRANSPORT_SESSION','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_WORK_POOL','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_WORK_QUEUE','Replication',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('HADR_XRF_STACK_ACCESS','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('INSTANCE_LOG_RATE_GOVERNOR','Log Rate Governor',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('IO_COMPLETION','Other Disk IO',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('IO_QUEUE_LIMIT','Other Disk IO',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('IO_RETRY','Other Disk IO',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LATCH_DT','Latch',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LATCH_EX','Latch',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LATCH_KP','Latch',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LATCH_NL','Latch',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LATCH_SH','Latch',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LATCH_UP','Latch',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LAZYWRITER_SLEEP','Idle',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_BU','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_BU_ABORT_BLOCKERS','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_BU_LOW_PRIORITY','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_IS','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_IS_ABORT_BLOCKERS','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_IS_LOW_PRIORITY','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_IU','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_IU_ABORT_BLOCKERS','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_IU_LOW_PRIORITY','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_IX','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_IX_ABORT_BLOCKERS','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_IX_LOW_PRIORITY','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RIn_NL','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RIn_NL_ABORT_BLOCKERS','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RIn_NL_LOW_PRIORITY','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RIn_S','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RIn_S_ABORT_BLOCKERS','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RIn_S_LOW_PRIORITY','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RIn_U','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RIn_U_ABORT_BLOCKERS','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RIn_U_LOW_PRIORITY','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RIn_X','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RIn_X_ABORT_BLOCKERS','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RIn_X_LOW_PRIORITY','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RS_S','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RS_S_ABORT_BLOCKERS','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RS_S_LOW_PRIORITY','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RS_U','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RS_U_ABORT_BLOCKERS','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RS_U_LOW_PRIORITY','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RX_S','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RX_S_ABORT_BLOCKERS','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RX_S_LOW_PRIORITY','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RX_U','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RX_U_ABORT_BLOCKERS','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RX_U_LOW_PRIORITY','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RX_X','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RX_X_ABORT_BLOCKERS','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_RX_X_LOW_PRIORITY','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_S','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_S_ABORT_BLOCKERS','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_S_LOW_PRIORITY','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_SCH_M','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_SCH_M_ABORT_BLOCKERS','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_SCH_M_LOW_PRIORITY','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_SCH_S','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_SCH_S_ABORT_BLOCKERS','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_SCH_S_LOW_PRIORITY','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_SIU','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_SIU_ABORT_BLOCKERS','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_SIU_LOW_PRIORITY','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_SIX','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_SIX_ABORT_BLOCKERS','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_SIX_LOW_PRIORITY','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_U','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_U_ABORT_BLOCKERS','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_U_LOW_PRIORITY','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_UIX','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_UIX_ABORT_BLOCKERS','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_UIX_LOW_PRIORITY','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_X','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_X_ABORT_BLOCKERS','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LCK_M_X_LOW_PRIORITY','Lock',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LOG_RATE_GOVERNOR','Tran Log IO',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LOGBUFFER','Tran Log IO',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LOGMGR','Tran Log IO',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LOGMGR_FLUSH','Tran Log IO',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LOGMGR_PMM_LOG','Tran Log IO',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LOGMGR_QUEUE','Idle',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('LOGMGR_RESERVE_APPEND','Tran Log IO',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('MEMORY_ALLOCATION_EXT','Memory',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('MEMORY_GRANT_UPDATE','Memory',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('MSQL_XACT_MGR_MUTEX','Transaction',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('MSQL_XACT_MUTEX','Transaction',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('MSSEARCH','Full Text Search',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('NET_WAITFOR_PACKET','Network IO',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('ONDEMAND_TASK_QUEUE','Idle',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PAGEIOLATCH_DT','Buffer IO',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PAGEIOLATCH_EX','Buffer IO',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PAGEIOLATCH_KP','Buffer IO',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PAGEIOLATCH_NL','Buffer IO',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PAGEIOLATCH_SH','Buffer IO',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PAGEIOLATCH_UP','Buffer IO',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PAGELATCH_DT','Buffer Latch',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PAGELATCH_EX','Buffer Latch',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PAGELATCH_KP','Buffer Latch',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PAGELATCH_NL','Buffer Latch',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PAGELATCH_SH','Buffer Latch',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PAGELATCH_UP','Buffer Latch',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PARALLEL_REDO_DRAIN_WORKER','Replication',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PARALLEL_REDO_FLOW_CONTROL','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PARALLEL_REDO_LOG_CACHE','Replication',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PARALLEL_REDO_TRAN_LIST','Replication',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PARALLEL_REDO_TRAN_TURN','Replication',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PARALLEL_REDO_WORKER_SYNC','Replication',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PARALLEL_REDO_WORKER_WAIT_WORK','Replication',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('POOL_LOG_RATE_GOVERNOR','Log Rate Governor',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('POPULATE_LOCK_ORDINALS','Idle',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_ABR','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_CLOSEBACKUPMEDIA','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_CLOSEBACKUPTAPE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_CLOSEBACKUPVDIDEVICE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_CLUSAPI_CLUSTERRESOURCECONTROL','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_COCREATEINSTANCE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_COGETCLASSOBJECT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_CREATEACCESSOR','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_DELETEROWS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_GETCOMMANDTEXT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_GETDATA','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_GETNEXTROWS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_GETRESULT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_GETROWSBYBOOKMARK','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_LBFLUSH','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_LBLOCKREGION','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_LBREADAT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_LBSETSIZE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_LBSTAT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_LBUNLOCKREGION','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_LBWRITEAT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_QUERYINTERFACE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_RELEASE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_RELEASEACCESSOR','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_RELEASEROWS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_RELEASESESSION','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_RESTARTPOSITION','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_SEQSTRMREAD','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_SEQSTRMREADANDWRITE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_SETDATAFAILURE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_SETPARAMETERINFO','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_SETPARAMETERPROPERTIES','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_STRMLOCKREGION','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_STRMSEEKANDREAD','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_STRMSEEKANDWRITE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_STRMSETSIZE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_STRMSTAT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_COM_STRMUNLOCKREGION','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_CONSOLEWRITE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_CREATEPARAM','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DEBUG','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DFSADDLINK','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DFSLINKEXISTCHECK','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DFSLINKHEALTHCHECK','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DFSREMOVELINK','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DFSREMOVEROOT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DFSROOTFOLDERCHECK','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DFSROOTINIT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DFSROOTSHARECHECK','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DTC_ABORT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DTC_ABORTREQUESTDONE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DTC_BEGINTRANSACTION','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DTC_COMMITREQUESTDONE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DTC_ENLIST','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_DTC_PREPAREREQUESTDONE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_FILESIZEGET','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_FSAOLEDB_ABORTTRANSACTION','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_FSAOLEDB_COMMITTRANSACTION','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_FSAOLEDB_STARTTRANSACTION','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_FSRECOVER_UNCONDITIONALUNDO','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_GETRMINFO','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_HADR_LEASE_MECHANISM','Preemptive',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_HTTP_EVENT_WAIT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_HTTP_REQUEST','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_LOCKMONITOR','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_MSS_RELEASE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_ODBCOPS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OLE_UNINIT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OLEDB_ABORTORCOMMITTRAN','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OLEDB_ABORTTRAN','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OLEDB_GETDATASOURCE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OLEDB_GETLITERALINFO','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OLEDB_GETPROPERTIES','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OLEDB_GETPROPERTYINFO','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OLEDB_GETSCHEMALOCK','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OLEDB_JOINTRANSACTION','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OLEDB_RELEASE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OLEDB_SETPROPERTIES','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OLEDBOPS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_ACCEPTSECURITYCONTEXT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_ACQUIRECREDENTIALSHANDLE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_AUTHENTICATIONOPS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_AUTHORIZATIONOPS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_AUTHZGETINFORMATIONFROMCONTEXT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_AUTHZINITIALIZECONTEXTFROMSID','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_AUTHZINITIALIZERESOURCEMANAGER','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_BACKUPREAD','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_CLOSEHANDLE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_CLUSTEROPS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_COMOPS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_COMPLETEAUTHTOKEN','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_COPYFILE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_CREATEDIRECTORY','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_CREATEFILE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_CRYPTACQUIRECONTEXT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_CRYPTIMPORTKEY','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_CRYPTOPS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_DECRYPTMESSAGE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_DELETEFILE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_DELETESECURITYCONTEXT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_DEVICEIOCONTROL','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_DEVICEOPS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_DIRSVC_NETWORKOPS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_DISCONNECTNAMEDPIPE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_DOMAINSERVICESOPS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_DSGETDCNAME','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_DTCOPS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_ENCRYPTMESSAGE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_FILEOPS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_FINDFILE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_FLUSHFILEBUFFERS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_FORMATMESSAGE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_FREECREDENTIALSHANDLE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_FREELIBRARY','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_GENERICOPS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_GETADDRINFO','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_GETCOMPRESSEDFILESIZE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_GETDISKFREESPACE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_GETFILEATTRIBUTES','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_GETFILESIZE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_GETFINALFILEPATHBYHANDLE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_GETLONGPATHNAME','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_GETPROCADDRESS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_GETVOLUMENAMEFORVOLUMEMOUNTPOINT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_GETVOLUMEPATHNAME','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_INITIALIZESECURITYCONTEXT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_LIBRARYOPS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_LOADLIBRARY','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_LOGONUSER','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_LOOKUPACCOUNTSID','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_MESSAGEQUEUEOPS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_MOVEFILE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_NETGROUPGETUSERS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_NETLOCALGROUPGETMEMBERS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_NETUSERGETGROUPS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_NETUSERGETLOCALGROUPS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_NETUSERMODALSGET','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_NETVALIDATEPASSWORDPOLICY','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_NETVALIDATEPASSWORDPOLICYFREE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_OPENDIRECTORY','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_PDH_WMI_INIT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_PIPEOPS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_PROCESSOPS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_QUERYCONTEXTATTRIBUTES','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_QUERYREGISTRY','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_QUERYSECURITYCONTEXTTOKEN','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_REMOVEDIRECTORY','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_REPORTEVENT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_REVERTTOSELF','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_RSFXDEVICEOPS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_SECURITYOPS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_SERVICEOPS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_SETENDOFFILE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_SETFILEPOINTER','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_SETFILEVALIDDATA','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_SETNAMEDSECURITYINFO','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_SQLCLROPS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_SQMLAUNCH','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_VERIFYSIGNATURE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_VERIFYTRUST','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_VSSOPS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_WAITFORSINGLEOBJECT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_WINSOCKOPS','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_WRITEFILE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_WRITEFILEGATHER','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_OS_WSASETLASTERROR','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_REENLIST','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_RESIZELOG','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_ROLLFORWARDREDO','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_ROLLFORWARDUNDO','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_SB_STOPENDPOINT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_SERVER_STARTUP','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_SETRMINFO','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_SHAREDMEM_GETDATA','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_SNIOPEN','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_SOSHOST','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_SOSTESTING','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_SP_SERVER_DIAGNOSTICS','Preemptive',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_STARTRM','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_STREAMFCB_CHECKPOINT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_STREAMFCB_RECOVER','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_STRESSDRIVER','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_TESTING','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_TRANSIMPORT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_UNMARSHALPROPAGATIONTOKEN','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_VSS_CREATESNAPSHOT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_VSS_CREATEVOLUMESNAPSHOT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_XE_CALLBACKEXECUTE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_XE_CX_FILE_OPEN','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_XE_CX_HTTP_CALL','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_XE_DISPATCHER','Preemptive',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_XE_ENGINEINIT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_XE_GETTARGETSTATE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_XE_SESSIONCOMMIT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_XE_TARGETFINALIZE','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_XE_TARGETINIT','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_XE_TIMERRUN','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PREEMPTIVE_XETESTING','Preemptive',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PWAIT_HADR_ACTION_COMPLETED','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PWAIT_HADR_CHANGE_NOTIFIER_TERMINATION_SYNC','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PWAIT_HADR_CLUSTER_INTEGRATION','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PWAIT_HADR_FAILOVER_COMPLETED','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PWAIT_HADR_JOIN','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PWAIT_HADR_OFFLINE_COMPLETED','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PWAIT_HADR_ONLINE_COMPLETED','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PWAIT_HADR_POST_ONLINE_COMPLETED','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PWAIT_HADR_SERVER_READY_CONNECTIONS','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PWAIT_HADR_WORKITEM_COMPLETED','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PWAIT_HADRSIM','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('PWAIT_RESOURCE_SEMAPHORE_FT_PARALLEL_QUERY_SYNC','Full Text Search',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('QDS_ASYNC_QUEUE','Other',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('QDS_CLEANUP_STALE_QUERIES_TASK_MAIN_LOOP_SLEEP','Other',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('QDS_PERSIST_TASK_MAIN_LOOP_SLEEP','Other',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('QDS_SHUTDOWN_QUEUE','Other',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('QUERY_TRACEOUT','Tracing',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('REDO_THREAD_PENDING_WORK','Other',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('REPL_CACHE_ACCESS','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('REPL_HISTORYCACHE_ACCESS','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('REPL_SCHEMA_ACCESS','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('REPL_TRANFSINFO_ACCESS','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('REPL_TRANHASHTABLE_ACCESS','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('REPL_TRANTEXTINFO_ACCESS','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('REPLICA_WRITES','Replication',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('REQUEST_FOR_DEADLOCK_SEARCH','Idle',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('RESERVED_MEMORY_ALLOCATION_EXT','Memory',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('RESOURCE_SEMAPHORE','Memory',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('RESOURCE_SEMAPHORE_QUERY_COMPILE','Compilation',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_BPOOL_FLUSH','Idle',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_BUFFERPOOL_HELPLW','Idle',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_DBSTARTUP','Idle',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_DCOMSTARTUP','Idle',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_MASTERDBREADY','Idle',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_MASTERMDREADY','Idle',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_MASTERUPGRADED','Idle',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_MEMORYPOOL_ALLOCATEPAGES','Idle',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_MSDBSTARTUP','Idle',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_RETRY_VIRTUALALLOC','Idle',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_SYSTEMTASK','Idle',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_TASK','Idle',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_TEMPDBSTARTUP','Idle',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SLEEP_WORKSPACE_ALLOCATEPAGE','Idle',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SOS_SCHEDULER_YIELD','CPU',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SOS_WORK_DISPATCHER','Idle',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SP_SERVER_DIAGNOSTICS_SLEEP','Other',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SQLCLR_APPDOMAIN','SQL CLR',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SQLCLR_ASSEMBLY','SQL CLR',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SQLCLR_DEADLOCK_DETECTION','SQL CLR',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SQLCLR_QUANTUM_PUNISHMENT','SQL CLR',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SQLTRACE_BUFFER_FLUSH','Idle',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SQLTRACE_FILE_BUFFER','Tracing',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SQLTRACE_FILE_READ_IO_COMPLETION','Tracing',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SQLTRACE_FILE_WRITE_IO_COMPLETION','Tracing',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SQLTRACE_INCREMENTAL_FLUSH_SLEEP','Idle',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SQLTRACE_PENDING_BUFFER_WRITERS','Tracing',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SQLTRACE_SHUTDOWN','Tracing',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('SQLTRACE_WAIT_ENTRIES','Idle',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('THREADPOOL','Worker Thread',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('TRACE_EVTNOTIF','Tracing',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('TRACEWRITE','Tracing',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('TRAN_MARKLATCH_DT','Transaction',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('TRAN_MARKLATCH_EX','Transaction',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('TRAN_MARKLATCH_KP','Transaction',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('TRAN_MARKLATCH_NL','Transaction',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('TRAN_MARKLATCH_SH','Transaction',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('TRAN_MARKLATCH_UP','Transaction',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('TRANSACTION_MUTEX','Transaction',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('UCS_SESSION_REGISTRATION','Other',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('WAIT_FOR_RESULTS','User Wait',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('WAIT_XTP_OFFLINE_CKPT_NEW_LOG','Other',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('WAITFOR','User Wait',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('WRITE_COMPLETION','Other Disk IO',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('WRITELOG','Tran Log IO',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('XACT_OWN_TRANSACTION','Transaction',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('XACT_RECLAIM_SESSION','Transaction',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('XACTLOCKINFO','Transaction',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('XACTWORKSPACE_MUTEX','Transaction',0); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('XE_DISPATCHER_WAIT','Idle',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('XE_LIVE_TARGET_TVF','Other',1); - INSERT INTO ##WaitCategories(WaitType, WaitCategory, Ignorable) VALUES ('XE_TIMER_EVENT','Idle',1); - END; /* IF SELECT SUM(1) FROM ##WaitCategories <> 527 */ - - - - IF OBJECT_ID('tempdb..#MasterFiles') IS NOT NULL - DROP TABLE #MasterFiles; - CREATE TABLE #MasterFiles (database_id INT, file_id INT, type_desc NVARCHAR(50), name NVARCHAR(255), physical_name NVARCHAR(255), size BIGINT); - /* Azure SQL Database doesn't have sys.master_files, so we have to build our own. */ - IF (SERVERPROPERTY('EngineEdition') = 5 /*(SERVERPROPERTY('Edition')) = 'SQL Azure' */ - AND (OBJECT_ID('sys.master_files') IS NULL)) - SET @StringToExecute = 'INSERT INTO #MasterFiles (database_id, file_id, type_desc, name, physical_name, size) SELECT DB_ID(), file_id, type_desc, name, physical_name, size FROM sys.database_files;'; - ELSE - SET @StringToExecute = 'INSERT INTO #MasterFiles (database_id, file_id, type_desc, name, physical_name, size) SELECT database_id, file_id, type_desc, name, physical_name, size FROM sys.master_files;'; - EXEC(@StringToExecute); - - IF @FilterPlansByDatabase IS NOT NULL - BEGIN - IF UPPER(LEFT(@FilterPlansByDatabase,4)) = 'USER' - BEGIN - INSERT INTO #FilterPlansByDatabase (DatabaseID) - SELECT database_id - FROM sys.databases - WHERE [name] NOT IN ('master', 'model', 'msdb', 'tempdb'); - END; - ELSE - BEGIN - SET @FilterPlansByDatabase = @FilterPlansByDatabase + ',' - ;WITH a AS - ( - SELECT CAST(1 AS BIGINT) f, CHARINDEX(',', @FilterPlansByDatabase) t, 1 SEQ - UNION ALL - SELECT t + 1, CHARINDEX(',', @FilterPlansByDatabase, t + 1), SEQ + 1 - FROM a - WHERE CHARINDEX(',', @FilterPlansByDatabase, t + 1) > 0 - ) - INSERT #FilterPlansByDatabase (DatabaseID) - SELECT DISTINCT db.database_id - FROM a - INNER JOIN sys.databases db ON LTRIM(RTRIM(SUBSTRING(@FilterPlansByDatabase, a.f, a.t - a.f))) = db.name - WHERE SUBSTRING(@FilterPlansByDatabase, f, t - f) IS NOT NULL - OPTION (MAXRECURSION 0); - END; - END; - - IF OBJECT_ID('tempdb..#ReadableDBs') IS NOT NULL - DROP TABLE #ReadableDBs; - CREATE TABLE #ReadableDBs ( - database_id INT - ); - - IF EXISTS (SELECT * FROM sys.all_objects o WHERE o.name = 'dm_hadr_database_replica_states') - BEGIN - RAISERROR('Checking for Read intent databases to exclude',0,0) WITH NOWAIT; - - SET @StringToExecute = 'INSERT INTO #ReadableDBs (database_id) SELECT DBs.database_id FROM sys.databases DBs INNER JOIN sys.availability_replicas Replicas ON DBs.replica_id = Replicas.replica_id WHERE replica_server_name NOT IN (SELECT DISTINCT primary_replica FROM sys.dm_hadr_availability_group_states States) AND Replicas.secondary_role_allow_connections_desc = ''READ_ONLY'' AND replica_server_name = @@SERVERNAME;'; - EXEC(@StringToExecute); - - END - - DECLARE @v DECIMAL(6,2), - @build INT, - @memGrantSortSupported BIT = 1; - - RAISERROR (N'Determining SQL Server version.',0,1) WITH NOWAIT; - - INSERT INTO #checkversion (version) - SELECT CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)) - OPTION (RECOMPILE); - - - SELECT @v = common_version , - @build = build - FROM #checkversion - OPTION (RECOMPILE); - - IF (@v < 11) - OR (@v = 11 AND @build < 6020) - OR (@v = 12 AND @build < 5000) - OR (@v = 13 AND @build < 1601) - SET @memGrantSortSupported = 0; - - IF EXISTS (SELECT * FROM sys.all_objects WHERE name = 'dm_exec_query_statistics_xml') - AND ((@v = 13 AND @build >= 5337) /* This DMF causes assertion errors: https://support.microsoft.com/en-us/help/4490136/fix-assertion-error-occurs-when-you-use-sys-dm-exec-query-statistics-x */ - OR (@v = 14 AND @build >= 3162) - OR (@v >= 15) - OR (@v <= 12)) /* Azure */ - SET @dm_exec_query_statistics_xml = 1; - - - SET @StockWarningHeader = '', - @StockDetailsHeader = @StockDetailsHeader + ''; - - /* Get the instance name to use as a Perfmon counter prefix. */ - IF SERVERPROPERTY('EngineEdition') IN (5, 8) /*CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) = 'SQL Azure'*/ - SELECT TOP 1 @ServiceName = LEFT(object_name, (CHARINDEX(':', object_name) - 1)) - FROM sys.dm_os_performance_counters; - ELSE - BEGIN - SET @StringToExecute = 'INSERT INTO #PerfmonStats(object_name, Pass, SampleTime, counter_name, cntr_type) SELECT CASE WHEN @@SERVICENAME = ''MSSQLSERVER'' THEN ''SQLServer'' ELSE ''MSSQL$'' + @@SERVICENAME END, 0, SYSDATETIMEOFFSET(), ''stuffing'', 0 ;'; - EXEC(@StringToExecute); - SELECT @ServiceName = object_name FROM #PerfmonStats; - DELETE #PerfmonStats; - END; - - /* Build a list of queries that were run in the last 10 seconds. - We're looking for the death-by-a-thousand-small-cuts scenario - where a query is constantly running, and it doesn't have that - big of an impact individually, but it has a ton of impact - overall. We're going to build this list, and then after we - finish our @Seconds sample, we'll compare our plan cache to - this list to see what ran the most. */ - - /* Populate #QueryStats. SQL 2005 doesn't have query hash or query plan hash. */ - IF @CheckProcedureCache = 1 - BEGIN - RAISERROR('@CheckProcedureCache = 1, capturing first pass of plan cache',10,1) WITH NOWAIT; - IF @@VERSION LIKE 'Microsoft SQL Server 2005%' - BEGIN - IF @FilterPlansByDatabase IS NULL - BEGIN - SET @StringToExecute = N'INSERT INTO #QueryStats ([sql_handle], Pass, SampleTime, statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, Points) - SELECT [sql_handle], 1 AS Pass, SYSDATETIMEOFFSET(), statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, NULL AS query_hash, NULL AS query_plan_hash, 0 - FROM sys.dm_exec_query_stats qs - WHERE qs.last_execution_time >= (DATEADD(ss, -10, SYSDATETIMEOFFSET()));'; - END; - ELSE - BEGIN - SET @StringToExecute = N'INSERT INTO #QueryStats ([sql_handle], Pass, SampleTime, statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, Points) - SELECT [sql_handle], 1 AS Pass, SYSDATETIMEOFFSET(), statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, NULL AS query_hash, NULL AS query_plan_hash, 0 - FROM sys.dm_exec_query_stats qs - CROSS APPLY sys.dm_exec_plan_attributes(qs.plan_handle) AS attr - INNER JOIN #FilterPlansByDatabase dbs ON CAST(attr.value AS INT) = dbs.DatabaseID - WHERE qs.last_execution_time >= (DATEADD(ss, -10, SYSDATETIMEOFFSET())) - AND attr.attribute = ''dbid'';'; - END; - END; - ELSE - BEGIN - IF @FilterPlansByDatabase IS NULL - BEGIN - SET @StringToExecute = N'INSERT INTO #QueryStats ([sql_handle], Pass, SampleTime, statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, Points) - SELECT [sql_handle], 1 AS Pass, SYSDATETIMEOFFSET(), statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, 0 - FROM sys.dm_exec_query_stats qs - WHERE qs.last_execution_time >= (DATEADD(ss, -10, SYSDATETIMEOFFSET()));'; - END; - ELSE - BEGIN - SET @StringToExecute = N'INSERT INTO #QueryStats ([sql_handle], Pass, SampleTime, statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, Points) - SELECT [sql_handle], 1 AS Pass, SYSDATETIMEOFFSET(), statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, 0 - FROM sys.dm_exec_query_stats qs - CROSS APPLY sys.dm_exec_plan_attributes(qs.plan_handle) AS attr - INNER JOIN #FilterPlansByDatabase dbs ON CAST(attr.value AS INT) = dbs.DatabaseID - WHERE qs.last_execution_time >= (DATEADD(ss, -10, SYSDATETIMEOFFSET())) - AND attr.attribute = ''dbid'';'; - END; - END; - EXEC(@StringToExecute); - - /* Get the totals for the entire plan cache */ - INSERT INTO #QueryStats (Pass, SampleTime, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time) - SELECT -1 AS Pass, SYSDATETIMEOFFSET(), SUM(execution_count), SUM(total_worker_time), SUM(total_physical_reads), SUM(total_logical_writes), SUM(total_logical_reads), SUM(total_clr_time), SUM(total_elapsed_time), MIN(creation_time) - FROM sys.dm_exec_query_stats qs; - END; /*IF @CheckProcedureCache = 1 */ - - - IF EXISTS (SELECT * - FROM tempdb.sys.all_objects obj - INNER JOIN tempdb.sys.all_columns col1 ON obj.object_id = col1.object_id AND col1.name = 'object_name' - INNER JOIN tempdb.sys.all_columns col2 ON obj.object_id = col2.object_id AND col2.name = 'counter_name' - INNER JOIN tempdb.sys.all_columns col3 ON obj.object_id = col3.object_id AND col3.name = 'instance_name' - WHERE obj.name LIKE '%CustomPerfmonCounters%') - BEGIN - SET @StringToExecute = 'INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) SELECT [object_name],[counter_name],[instance_name] FROM #CustomPerfmonCounters'; - EXEC(@StringToExecute); - END; - ELSE - BEGIN - /* Add our default Perfmon counters */ - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Forwarded Records/sec', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Page compression attempts/sec', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Page Splits/sec', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Skipped Ghosted Records/sec', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Table Lock Escalations/sec', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Worktables Created/sec', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Availability Group','Active Hadr Threads','_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Availability Replica','Bytes Received from Replica/sec','_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Availability Replica','Bytes Sent to Replica/sec','_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Availability Replica','Bytes Sent to Transport/sec','_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Availability Replica','Flow Control Time (ms/sec)','_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Availability Replica','Flow Control/sec','_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Availability Replica','Resent Messages/sec','_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Availability Replica','Sends to Replica/sec','_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Page life expectancy', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Page reads/sec', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Page writes/sec', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Readahead pages/sec', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Target pages', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Total pages', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Databases','', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Active Transactions','_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Database Flow Control Delay', '_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Database Flow Controls/sec', '_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Group Commit Time', '_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Group Commits/Sec', '_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Log Apply Pending Queue', '_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Log Apply Ready Queue', '_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Log Compression Cache misses/sec', '_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Log remaining for undo', '_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Log Send Queue', '_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Recovery Queue', '_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Redo blocked/sec', '_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Redo Bytes Remaining', '_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Database Replica','Redone Bytes/sec', '_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Databases','Log Bytes Flushed/sec', '_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Databases','Log Growths', '_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Databases','Log Pool LogWriter Pushes/sec', '_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Databases','Log Shrinks', '_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Databases','Transactions/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Databases','Write Transactions/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Databases','XTP Memory Used (KB)',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Exec Statistics','Distributed Query', 'Execs in progress'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Exec Statistics','DTC calls', 'Execs in progress'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Exec Statistics','Extended Procedures', 'Execs in progress'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Exec Statistics','OLEDB calls', 'Execs in progress'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':General Statistics','Active Temp Tables', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':General Statistics','Logins/sec', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':General Statistics','Logouts/sec', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':General Statistics','Mars Deadlocks', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':General Statistics','Processes blocked', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Locks','Number of Deadlocks/sec', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Memory Manager','Memory Grants Pending', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Errors','Errors/sec', '_Total'); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','Batch Requests/sec', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','Forced Parameterizations/sec', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','Guided plan executions/sec', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','SQL Attention rate', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','SQL Compilations/sec', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','SQL Re-Compilations/sec', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Workload Group Stats','Query optimizations/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Workload Group Stats','Suboptimal plans/sec',NULL); - /* Below counters added by Jefferson Elias */ - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Worktables From Cache Base',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Worktables From Cache Ratio',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Database pages',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Free pages',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Stolen pages',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Memory Manager','Granted Workspace Memory (KB)',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Memory Manager','Maximum Workspace Memory (KB)',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Memory Manager','Target Server Memory (KB)',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Memory Manager','Total Server Memory (KB)',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Buffer cache hit ratio',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Buffer cache hit ratio base',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Checkpoint pages/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Free list stalls/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Lazy writes/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','Auto-Param Attempts/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','Failed Auto-Params/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','Safe Auto-Params/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','Unsafe Auto-Params/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Workfiles Created/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':General Statistics','User Connections',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Latches','Average Latch Wait Time (ms)',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Latches','Average Latch Wait Time Base',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Latches','Latch Waits/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Latches','Total Latch Wait Time (ms)',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Locks','Average Wait Time (ms)',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Locks','Average Wait Time Base',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Locks','Lock Requests/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Locks','Lock Timeouts/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Locks','Lock Wait Time (ms)',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Locks','Lock Waits/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Transactions','Longest Transaction Running Time',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Full Scans/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Access Methods','Index Searches/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Buffer Manager','Page lookups/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':Cursor Manager by Type','Active cursors',NULL); - /* Below counters are for In-Memory OLTP (Hekaton), which have a different naming convention. - And yes, they actually hard-coded the version numbers into the counters, and SQL 2019 still says 2017, oddly. - For why, see: https://connect.microsoft.com/SQLServer/feedback/details/817216/xtp-perfmon-counters-should-appear-under-sql-server-perfmon-counter-group - */ - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP Cursors','Expired rows removed/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP Cursors','Expired rows touched/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP Garbage Collection','Rows processed/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP IO Governor','Io Issued/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP Phantom Processor','Phantom expired rows touched/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP Phantom Processor','Phantom rows touched/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP Transaction Log','Log bytes written/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP Transaction Log','Log records written/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP Transactions','Transactions aborted by user/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP Transactions','Transactions aborted/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2014 XTP Transactions','Transactions created/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP Cursors','Expired rows removed/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP Cursors','Expired rows touched/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP Garbage Collection','Rows processed/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP IO Governor','Io Issued/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP Phantom Processor','Phantom expired rows touched/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP Phantom Processor','Phantom rows touched/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP Transaction Log','Log bytes written/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP Transaction Log','Log records written/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP Transactions','Transactions aborted by user/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP Transactions','Transactions aborted/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2016 XTP Transactions','Transactions created/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP Cursors','Expired rows removed/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP Cursors','Expired rows touched/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP Garbage Collection','Rows processed/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP IO Governor','Io Issued/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP Phantom Processor','Phantom expired rows touched/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP Phantom Processor','Phantom rows touched/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP Transaction Log','Log bytes written/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP Transaction Log','Log records written/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP Transactions','Transactions aborted by user/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP Transactions','Transactions aborted/sec',NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES ('SQL Server 2017 XTP Transactions','Transactions created/sec',NULL); - END; - - - IF @total_cpu_usage IN (0, 1) - BEGIN - EXEC sys.sp_executesql - @get_thread_time_ms, - N'@thread_time_ms FLOAT OUTPUT', - @thread_time_ms OUTPUT; - END - - /* Populate #FileStats, #PerfmonStats, #WaitStats with DMV data. - After we finish doing our checks, we'll take another sample and compare them. */ - RAISERROR('Capturing first pass of wait stats, perfmon counters, file stats',10,1) WITH NOWAIT; - SET @StringToExecute = N' - INSERT #WaitStats(Pass, SampleTime, wait_type, wait_time_ms, thread_time_ms, signal_wait_time_ms, waiting_tasks_count) - SELECT - x.Pass, - x.SampleTime, - x.wait_type, - SUM(x.sum_wait_time_ms) AS sum_wait_time_ms, - CASE @Seconds - WHEN 0 - THEN 0 - ELSE @thread_time_ms - END AS thread_time_ms, - SUM(x.sum_signal_wait_time_ms) AS sum_signal_wait_time_ms, - SUM(x.sum_waiting_tasks) AS sum_waiting_tasks - FROM ( - SELECT - 1 AS Pass, - CASE @Seconds WHEN 0 THEN @StartSampleTime ELSE SYSDATETIMEOFFSET() END AS SampleTime, - owt.wait_type, - CASE @Seconds WHEN 0 THEN 0 ELSE SUM(owt.wait_duration_ms) OVER (PARTITION BY owt.wait_type, owt.session_id) - - CASE WHEN @Seconds = 0 THEN 0 ELSE (@Seconds * 1000) END END AS sum_wait_time_ms, - 0 AS sum_signal_wait_time_ms, - 0 AS sum_waiting_tasks - FROM sys.dm_os_waiting_tasks owt - WHERE owt.session_id > 50 - AND owt.wait_duration_ms >= CASE @Seconds WHEN 0 THEN 0 ELSE @Seconds * 1000 END - UNION ALL - SELECT - 1 AS Pass, - CASE @Seconds WHEN 0 THEN @StartSampleTime ELSE SYSDATETIMEOFFSET() END AS SampleTime, - os.wait_type, - CASE @Seconds WHEN 0 THEN 0 ELSE SUM(os.wait_time_ms) OVER (PARTITION BY os.wait_type) END AS sum_wait_time_ms, - CASE @Seconds WHEN 0 THEN 0 ELSE SUM(os.signal_wait_time_ms) OVER (PARTITION BY os.wait_type ) END AS sum_signal_wait_time_ms, - CASE @Seconds WHEN 0 THEN 0 ELSE SUM(os.waiting_tasks_count) OVER (PARTITION BY os.wait_type) END AS sum_waiting_tasks '; - - IF SERVERPROPERTY('EngineEdition') = 5 /*SERVERPROPERTY('Edition') = 'SQL Azure'*/ - SET @StringToExecute = @StringToExecute + N' FROM sys.dm_db_wait_stats os '; - ELSE - SET @StringToExecute = @StringToExecute + N' FROM sys.dm_os_wait_stats os '; - - SET @StringToExecute = @StringToExecute + N' - ) x - WHERE NOT EXISTS - ( - SELECT * - FROM ##WaitCategories AS wc - WHERE wc.WaitType = x.wait_type - AND wc.Ignorable = 1 - ) - GROUP BY x.Pass, x.SampleTime, x.wait_type - ORDER BY sum_wait_time_ms DESC;' - - EXEC sys.sp_executesql - @StringToExecute, - N'@StartSampleTime DATETIMEOFFSET, - @Seconds INT, - @thread_time_ms FLOAT', - @StartSampleTime, - @Seconds, - @thread_time_ms; - - WITH w AS - ( - SELECT - total_waits = - CONVERT - ( - FLOAT, - SUM(ws.wait_time_ms) - ) - FROM #WaitStats AS ws - WHERE Pass = 1 - ) - UPDATE ws - SET ws.thread_time_ms += w.total_waits - FROM #WaitStats AS ws - CROSS JOIN w - WHERE ws.Pass = 1 - OPTION(RECOMPILE); - - INSERT INTO #FileStats (Pass, SampleTime, DatabaseID, FileID, DatabaseName, FileLogicalName, SizeOnDiskMB, io_stall_read_ms , - num_of_reads, [bytes_read] , io_stall_write_ms,num_of_writes, [bytes_written], PhysicalName, TypeDesc) - SELECT - 1 AS Pass, - CASE @Seconds WHEN 0 THEN @StartSampleTime ELSE SYSDATETIMEOFFSET() END AS SampleTime, - mf.[database_id], - mf.[file_id], - DB_NAME(vfs.database_id) AS [db_name], - mf.name + N' [' + mf.type_desc COLLATE SQL_Latin1_General_CP1_CI_AS + N']' AS file_logical_name , - CAST(( ( vfs.size_on_disk_bytes / 1024.0 ) / 1024.0 ) AS INT) AS size_on_disk_mb , - CASE @Seconds WHEN 0 THEN 0 ELSE vfs.io_stall_read_ms END , - CASE @Seconds WHEN 0 THEN 0 ELSE vfs.num_of_reads END , - CASE @Seconds WHEN 0 THEN 0 ELSE vfs.[num_of_bytes_read] END , - CASE @Seconds WHEN 0 THEN 0 ELSE vfs.io_stall_write_ms END , - CASE @Seconds WHEN 0 THEN 0 ELSE vfs.num_of_writes END , - CASE @Seconds WHEN 0 THEN 0 ELSE vfs.[num_of_bytes_written] END , - mf.physical_name, - mf.type_desc - FROM sys.dm_io_virtual_file_stats (NULL, NULL) AS vfs - INNER JOIN #MasterFiles AS mf ON vfs.file_id = mf.file_id - AND vfs.database_id = mf.database_id - WHERE vfs.num_of_reads > 0 - OR vfs.num_of_writes > 0; - - INSERT INTO #PerfmonStats (Pass, SampleTime, [object_name],[counter_name],[instance_name],[cntr_value],[cntr_type]) - SELECT 1 AS Pass, - CASE @Seconds WHEN 0 THEN @StartSampleTime ELSE SYSDATETIMEOFFSET() END AS SampleTime, RTRIM(dmv.object_name), RTRIM(dmv.counter_name), RTRIM(dmv.instance_name), CASE @Seconds WHEN 0 THEN 0 ELSE dmv.cntr_value END, dmv.cntr_type - FROM #PerfmonCounters counters - INNER JOIN sys.dm_os_performance_counters dmv ON counters.counter_name COLLATE SQL_Latin1_General_CP1_CI_AS = RTRIM(dmv.counter_name) COLLATE SQL_Latin1_General_CP1_CI_AS - AND counters.[object_name] COLLATE SQL_Latin1_General_CP1_CI_AS = RTRIM(dmv.[object_name]) COLLATE SQL_Latin1_General_CP1_CI_AS - AND (counters.[instance_name] IS NULL OR counters.[instance_name] COLLATE SQL_Latin1_General_CP1_CI_AS = RTRIM(dmv.[instance_name]) COLLATE SQL_Latin1_General_CP1_CI_AS); - - /* For Github #2743: */ - CREATE TABLE #TempdbOperationalStats (object_id BIGINT PRIMARY KEY CLUSTERED, - forwarded_fetch_count BIGINT); - INSERT INTO #TempdbOperationalStats (object_id, forwarded_fetch_count) - SELECT object_id, forwarded_fetch_count - FROM tempdb.sys.dm_db_index_operational_stats(DB_ID('tempdb'), NULL, NULL, NULL) os - WHERE os.database_id = DB_ID('tempdb') - AND os.forwarded_fetch_count > 100; - - - /* If they want to run sp_BlitzWho and export to table, go for it. */ - IF @OutputTableNameBlitzWho IS NOT NULL - AND @OutputDatabaseName IS NOT NULL - AND @OutputSchemaName IS NOT NULL - AND EXISTS ( SELECT * - FROM sys.databases - WHERE QUOTENAME([name]) = @OutputDatabaseName) - BEGIN - RAISERROR('Logging sp_BlitzWho to table',10,1) WITH NOWAIT; - EXEC sp_BlitzWho @OutputDatabaseName = @UnquotedOutputDatabaseName, @OutputSchemaName = @UnquotedOutputSchemaName, @OutputTableName = @OutputTableNameBlitzWho, @CheckDateOverride = @StartSampleTime, @OutputTableRetentionDays = @OutputTableRetentionDays; - END - - RAISERROR('Beginning investigatory queries',10,1) WITH NOWAIT; - - - /* Maintenance Tasks Running - Backup Running - CheckID 1 */ - IF @Seconds > 0 - BEGIN - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 1',10,1) WITH NOWAIT; - END - - IF EXISTS(SELECT * FROM sys.dm_exec_requests WHERE total_elapsed_time > 300000 AND command LIKE 'BACKUP%') - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, QueryHash) - SELECT 1 AS CheckID, - 1 AS Priority, - 'Maintenance Tasks Running' AS FindingGroup, - 'Backup Running' AS Finding, - 'https://www.brentozar.com/askbrent/backups/' AS URL, - 'Backup of ' + DB_NAME(db.resource_database_id) + ' database (' + (SELECT CAST(CAST(SUM(size * 8.0 / 1024 / 1024) AS BIGINT) AS NVARCHAR) FROM #MasterFiles WHERE database_id = db.resource_database_id) + 'GB) ' + @LineFeed - + CAST(r.percent_complete AS NVARCHAR(100)) + '% complete, has been running since ' + CAST(r.start_time AS NVARCHAR(100)) + '. ' + @LineFeed - + CASE WHEN COALESCE(s.nt_username, s.loginame) IS NOT NULL THEN (' Login: ' + COALESCE(s.nt_username, s.loginame) + ' ') ELSE '' END AS Details, - 'KILL ' + CAST(r.session_id AS NVARCHAR(100)) + ';' AS HowToStopIt, - pl.query_plan AS QueryPlan, - r.start_time AS StartTime, - s.loginame AS LoginName, - s.nt_username AS NTUserName, - s.[program_name] AS ProgramName, - s.[hostname] AS HostName, - db.[resource_database_id] AS DatabaseID, - DB_NAME(db.resource_database_id) AS DatabaseName, - 0 AS OpenTransactionCount, - r.query_hash - FROM sys.dm_exec_requests r - INNER JOIN sys.dm_exec_connections c ON r.session_id = c.session_id - INNER JOIN sys.sysprocesses AS s ON r.session_id = s.spid AND s.ecid = 0 - INNER JOIN - ( - SELECT DISTINCT - t.request_session_id, - t.resource_database_id - FROM sys.dm_tran_locks AS t - WHERE t.resource_type = N'DATABASE' - AND t.request_mode = N'S' - AND t.request_status = N'GRANT' - AND t.request_owner_type = N'SHARED_TRANSACTION_WORKSPACE' - ) AS db ON s.spid = db.request_session_id AND s.dbid = db.resource_database_id - CROSS APPLY sys.dm_exec_query_plan(r.plan_handle) pl - WHERE r.command LIKE 'BACKUP%' - AND r.start_time <= DATEADD(minute, -5, GETDATE()) - AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs); - END - - /* If there's a backup running, add details explaining how long full backup has been taking in the last month. */ - IF @Seconds > 0 AND SERVERPROPERTY('EngineEdition') <> 5 /*CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) <> 'SQL Azure'*/ - BEGIN - SET @StringToExecute = 'UPDATE #BlitzFirstResults SET Details = Details + '' Over the last 60 days, the full backup usually takes '' + CAST((SELECT AVG(DATEDIFF(mi, bs.backup_start_date, bs.backup_finish_date)) FROM msdb.dbo.backupset bs WHERE abr.DatabaseName = bs.database_name AND bs.type = ''D'' AND bs.backup_start_date > DATEADD(dd, -60, SYSDATETIMEOFFSET()) AND bs.backup_finish_date IS NOT NULL) AS NVARCHAR(100)) + '' minutes.'' FROM #BlitzFirstResults abr WHERE abr.CheckID = 1 AND EXISTS (SELECT * FROM msdb.dbo.backupset bs WHERE bs.type = ''D'' AND bs.backup_start_date > DATEADD(dd, -60, SYSDATETIMEOFFSET()) AND bs.backup_finish_date IS NOT NULL AND abr.DatabaseName = bs.database_name AND DATEDIFF(mi, bs.backup_start_date, bs.backup_finish_date) > 1)'; - EXEC(@StringToExecute); - END; - - - /* Maintenance Tasks Running - DBCC CHECK* Running - CheckID 2 */ - IF @Seconds > 0 - BEGIN - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 2',10,1) WITH NOWAIT; - END - - IF EXISTS (SELECT * FROM sys.dm_exec_requests WHERE command LIKE 'DBCC%' AND total_elapsed_time > 5000) - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, QueryHash) - SELECT 2 AS CheckID, - 1 AS Priority, - 'Maintenance Tasks Running' AS FindingGroup, - 'DBCC CHECK* Running' AS Finding, - 'https://www.brentozar.com/askbrent/dbcc/' AS URL, - 'Corruption check of ' + DB_NAME(db.resource_database_id) + ' database (' + (SELECT CAST(CAST(SUM(size * 8.0 / 1024 / 1024) AS BIGINT) AS NVARCHAR) FROM #MasterFiles WHERE database_id = db.resource_database_id) + 'GB) 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, - s.login_name AS LoginName, - s.nt_user_name AS NTUserName, - s.[program_name] AS ProgramName, - s.[host_name] AS HostName, - db.[resource_database_id] AS DatabaseID, - DB_NAME(db.resource_database_id) AS DatabaseName, - 0 AS OpenTransactionCount, - r.query_hash - FROM sys.dm_exec_requests r - INNER JOIN sys.dm_exec_connections c ON r.session_id = c.session_id - INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id - INNER JOIN (SELECT DISTINCT l.request_session_id, l.resource_database_id - FROM sys.dm_tran_locks l - INNER JOIN sys.databases d ON l.resource_database_id = d.database_id - WHERE l.resource_type = N'DATABASE' - AND l.request_mode = N'S' - AND l.request_status = N'GRANT' - AND l.request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.session_id = db.request_session_id - OUTER APPLY sys.dm_exec_query_plan(r.plan_handle) pl - OUTER APPLY sys.dm_exec_sql_text(r.sql_handle) AS t - WHERE r.command LIKE 'DBCC%' - AND CAST(t.text AS NVARCHAR(4000)) NOT LIKE '%dm_db_index_physical_stats%' - AND CAST(t.text AS NVARCHAR(4000)) NOT LIKE '%ALTER INDEX%' - AND CAST(t.text AS NVARCHAR(4000)) NOT LIKE '%fileproperty%' - AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs); - END - - /* Maintenance Tasks Running - Restore Running - CheckID 3 */ - IF @Seconds > 0 - BEGIN - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 3',10,1) WITH NOWAIT; - END - - IF EXISTS (SELECT * FROM sys.dm_exec_requests WHERE command LIKE 'RESTORE%' AND total_elapsed_time > 5000) - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, QueryHash) - SELECT 3 AS CheckID, - 1 AS Priority, - 'Maintenance Tasks Running' AS FindingGroup, - 'Restore Running' AS Finding, - 'https://www.brentozar.com/askbrent/backups/' AS URL, - 'Restore of ' + DB_NAME(db.resource_database_id) + ' database (' + (SELECT CAST(CAST(SUM(size * 8.0 / 1024 / 1024) AS BIGINT) AS NVARCHAR) FROM #MasterFiles WHERE database_id = db.resource_database_id) + '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, - s.login_name AS LoginName, - s.nt_user_name AS NTUserName, - s.[program_name] AS ProgramName, - s.[host_name] AS HostName, - db.[resource_database_id] AS DatabaseID, - DB_NAME(db.resource_database_id) AS DatabaseName, - 0 AS OpenTransactionCount, - r.query_hash - FROM sys.dm_exec_requests r - INNER JOIN sys.dm_exec_connections c ON r.session_id = c.session_id - INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id - INNER JOIN ( - SELECT DISTINCT request_session_id, resource_database_id - FROM sys.dm_tran_locks - WHERE resource_type = N'DATABASE' - AND request_mode = N'S' - AND request_status = N'GRANT') AS db ON s.session_id = db.request_session_id - CROSS APPLY sys.dm_exec_query_plan(r.plan_handle) pl - WHERE r.command LIKE 'RESTORE%' - AND s.program_name <> 'SQL Server Log Shipping' - AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs); - END - - /* SQL Server Internal Maintenance - Database File Growing - CheckID 4 */ - IF @Seconds > 0 - BEGIN - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 4',10,1) WITH NOWAIT; - END - - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount) - SELECT 4 AS CheckID, - 1 AS Priority, - 'SQL Server Internal Maintenance' AS FindingGroup, - 'Database File Growing' AS Finding, - 'https://www.brentozar.com/go/instant' AS URL, - 'SQL Server is waiting for Windows to provide storage space for a database restore, a data file growth, or a log file growth. This task has been running since ' + CAST(r.start_time AS NVARCHAR(100)) + '.' + @LineFeed + 'Check the query plan (expert mode) to identify the database involved.' AS Details, - 'Unfortunately, you can''t stop this, but you can prevent it next time. Check out https://www.brentozar.com/go/instant for details.' AS HowToStopIt, - pl.query_plan AS QueryPlan, - r.start_time AS StartTime, - s.login_name AS LoginName, - s.nt_user_name AS NTUserName, - s.[program_name] AS ProgramName, - s.[host_name] AS HostName, - NULL AS DatabaseID, - NULL AS DatabaseName, - 0 AS OpenTransactionCount - FROM sys.dm_os_waiting_tasks t - INNER JOIN sys.dm_exec_connections c ON t.session_id = c.session_id - INNER JOIN sys.dm_exec_requests r ON t.session_id = r.session_id - INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id - CROSS APPLY sys.dm_exec_query_plan(r.plan_handle) pl - WHERE t.wait_type = 'PREEMPTIVE_OS_WRITEFILEGATHER' - AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs); - END - - /* Query Problems - Long-Running Query Blocking Others - CheckID 5 */ - IF SERVERPROPERTY('EngineEdition') <> 5 /*SERVERPROPERTY('Edition') <> 'SQL Azure'*/ AND @Seconds > 0 AND EXISTS(SELECT * FROM sys.dm_os_waiting_tasks WHERE wait_type LIKE 'LCK%' AND wait_duration_ms > 30000) - BEGIN - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 5',10,1) WITH NOWAIT; - END - - SET @StringToExecute = N'INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, QueryText, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, QueryHash) - SELECT 5 AS CheckID, - 1 AS Priority, - ''Query Problems'' AS FindingGroup, - ''Long-Running Query Blocking Others'' AS Finding, - ''https://www.brentozar.com/go/blocking'' AS URL, - ''Query in '' + COALESCE(DB_NAME(COALESCE((SELECT TOP 1 dbid FROM sys.dm_exec_sql_text(r.sql_handle)), - (SELECT TOP 1 t.dbid FROM master..sysprocesses spBlocker CROSS APPLY sys.dm_exec_sql_text(spBlocker.sql_handle) t WHERE spBlocker.spid = tBlocked.blocking_session_id))), ''(Unknown)'') + '' has a last request start time of '' + CAST(s.last_request_start_time AS NVARCHAR(100)) + ''. Query follows: ' - + @LineFeed + @LineFeed + - '''+ CAST(COALESCE((SELECT TOP 1 [text] FROM sys.dm_exec_sql_text(r.sql_handle)), - (SELECT TOP 1 [text] FROM master..sysprocesses spBlocker CROSS APPLY sys.dm_exec_sql_text(spBlocker.sql_handle) WHERE spBlocker.spid = tBlocked.blocking_session_id), '''') AS NVARCHAR(2000)) AS Details, - ''KILL '' + CAST(tBlocked.blocking_session_id AS NVARCHAR(100)) + '';'' AS HowToStopIt, - (SELECT TOP 1 query_plan FROM sys.dm_exec_query_plan(r.plan_handle)) AS QueryPlan, - COALESCE((SELECT TOP 1 [text] FROM sys.dm_exec_sql_text(r.sql_handle)), - (SELECT TOP 1 [text] FROM master..sysprocesses spBlocker CROSS APPLY sys.dm_exec_sql_text(spBlocker.sql_handle) WHERE spBlocker.spid = tBlocked.blocking_session_id)) AS QueryText, - r.start_time AS StartTime, - s.login_name AS LoginName, - s.nt_user_name AS NTUserName, - s.[program_name] AS ProgramName, - s.[host_name] AS HostName, - r.[database_id] AS DatabaseID, - DB_NAME(r.database_id) AS DatabaseName, - 0 AS OpenTransactionCount, - r.query_hash - FROM sys.dm_os_waiting_tasks tBlocked - INNER JOIN sys.dm_exec_sessions s ON tBlocked.blocking_session_id = s.session_id - LEFT OUTER JOIN sys.dm_exec_requests r ON s.session_id = r.session_id - INNER JOIN sys.dm_exec_connections c ON s.session_id = c.session_id - WHERE tBlocked.wait_type LIKE ''LCK%'' AND tBlocked.wait_duration_ms > 30000 - /* And the blocking session ID is not blocked by anyone else: */ - AND NOT EXISTS(SELECT * FROM sys.dm_os_waiting_tasks tBlocking WHERE s.session_id = tBlocking.session_id AND tBlocking.session_id <> tBlocking.blocking_session_id AND tBlocking.blocking_session_id IS NOT NULL) - AND r.database_id NOT IN (SELECT database_id FROM #ReadableDBs);'; - EXECUTE sp_executesql @StringToExecute; - END; - - /* Query Problems - Plan Cache Erased Recently - CheckID 7 */ - IF DATEADD(mi, -15, SYSDATETIME()) < (SELECT TOP 1 creation_time FROM sys.dm_exec_query_stats ORDER BY creation_time) - BEGIN - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 7',10,1) WITH NOWAIT; - END - - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) - SELECT TOP 1 7 AS CheckID, - 50 AS Priority, - 'Query Problems' AS FindingGroup, - 'Plan Cache Erased Recently' AS Finding, - 'https://www.brentozar.com/askbrent/plan-cache-erased-recently/' AS URL, - 'The oldest query in the plan cache was created at ' + CAST(creation_time AS NVARCHAR(50)) + '. ' + @LineFeed + @LineFeed - + 'This indicates that someone ran DBCC FREEPROCCACHE at that time,' + @LineFeed - + 'Giving SQL Server temporary amnesia. Now, as queries come in,' + @LineFeed - + 'SQL Server has to use a lot of CPU power in order to build execution' + @LineFeed - + 'plans and put them in cache again. This causes high CPU loads.' AS Details, - 'Find who did that, and stop them from doing it again.' AS HowToStopIt - FROM sys.dm_exec_query_stats - ORDER BY creation_time; - END; - - - /* Query Problems - Sleeping Query with Open Transactions - CheckID 8 */ - IF @Seconds > 0 - BEGIN - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 8',10,1) WITH NOWAIT; - END - - IF EXISTS (SELECT * FROM sys.dm_exec_requests WHERE total_elapsed_time > 5000 AND request_id > 0) - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, OpenTransactionCount) - SELECT 8 AS CheckID, - 50 AS Priority, - 'Query Problems' AS FindingGroup, - 'Sleeping Query with Open Transactions' AS Finding, - 'https://www.brentozar.com/askbrent/sleeping-query-with-open-transactions/' AS URL, - 'Database: ' + DB_NAME(db.resource_database_id) + @LineFeed + 'Host: ' + s.hostname + @LineFeed + 'Program: ' + s.[program_name] + @LineFeed + 'Asleep with open transactions and locks since ' + CAST(s.last_batch AS NVARCHAR(100)) + '. ' AS Details, - 'KILL ' + CAST(s.spid AS NVARCHAR(100)) + ';' AS HowToStopIt, - s.last_batch AS StartTime, - s.loginame AS LoginName, - s.nt_username AS NTUserName, - s.[program_name] AS ProgramName, - s.hostname AS HostName, - db.[resource_database_id] AS DatabaseID, - DB_NAME(db.resource_database_id) AS DatabaseName, - (SELECT TOP 1 [text] FROM sys.dm_exec_sql_text(c.most_recent_sql_handle)) AS QueryText, - s.open_tran AS OpenTransactionCount - FROM sys.sysprocesses s - INNER JOIN sys.dm_exec_connections c ON s.spid = c.session_id - INNER JOIN ( - SELECT DISTINCT request_session_id, resource_database_id - FROM sys.dm_tran_locks - WHERE resource_type = N'DATABASE' - AND request_mode = N'S' - AND request_status = N'GRANT' - AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.spid = db.request_session_id - WHERE s.status = 'sleeping' - AND s.open_tran > 0 - AND s.last_batch < DATEADD(ss, -10, SYSDATETIME()) - AND EXISTS(SELECT * FROM sys.dm_tran_locks WHERE request_session_id = s.spid - AND NOT (resource_type = N'DATABASE' AND request_mode = N'S' AND request_status = N'GRANT' AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE')); - END - - /*Query Problems - Clients using implicit transactions - CheckID 37 */ - IF @Seconds > 0 - AND ( @@VERSION NOT LIKE 'Microsoft SQL Server 2005%' - AND @@VERSION NOT LIKE 'Microsoft SQL Server 2008%' - AND @@VERSION NOT LIKE 'Microsoft SQL Server 2008 R2%' ) - BEGIN - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 37',10,1) WITH NOWAIT; - END - - SET @StringToExecute = N'INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, OpenTransactionCount) - SELECT 37 AS CheckId, - 50 AS Priority, - ''Query Problems'' AS FindingsGroup, - ''Implicit Transactions'', - ''https://www.brentozar.com/go/ImplicitTransactions/'' AS URL, - ''Database: '' + DB_NAME(s.database_id) + '' '' + CHAR(13) + CHAR(10) + - ''Host: '' + s.[host_name] + '' '' + CHAR(13) + CHAR(10) + - ''Program: '' + s.[program_name] + '' '' + CHAR(13) + CHAR(10) + - CONVERT(NVARCHAR(10), s.open_transaction_count) + - '' open transactions since: '' + - CONVERT(NVARCHAR(30), tat.transaction_begin_time) + ''. '' - AS Details, - ''Run sp_BlitzWho and check the is_implicit_transaction column to spot the culprits. -If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, - tat.transaction_begin_time, - s.login_name, - s.nt_user_name, - s.program_name, - s.host_name, - s.database_id, - DB_NAME(s.database_id) AS DatabaseName, - NULL AS Querytext, - s.open_transaction_count AS OpenTransactionCount - FROM sys.dm_tran_active_transactions AS tat - LEFT JOIN sys.dm_tran_session_transactions AS tst - ON tst.transaction_id = tat.transaction_id - LEFT JOIN sys.dm_exec_sessions AS s - ON s.session_id = tst.session_id - WHERE tat.name = ''implicit_transaction''; - ' - EXECUTE sp_executesql @StringToExecute; - END; - - /* Query Problems - Query Rolling Back - CheckID 9 */ - IF @Seconds > 0 - BEGIN - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 9',10,1) WITH NOWAIT; - END - - IF EXISTS (SELECT * FROM sys.dm_exec_requests WHERE total_elapsed_time > 5000 AND request_id > 0) - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, QueryHash) - SELECT 9 AS CheckID, - 1 AS Priority, - 'Query Problems' AS FindingGroup, - 'Query Rolling Back' AS Finding, - 'https://www.brentozar.com/askbrent/rollback/' AS URL, - 'Rollback started at ' + CAST(r.start_time AS NVARCHAR(100)) + ', is ' + CAST(r.percent_complete AS NVARCHAR(100)) + '% complete.' AS Details, - 'Unfortunately, you can''t stop this. Whatever you do, don''t restart the server in an attempt to fix it - SQL Server will keep rolling back.' AS HowToStopIt, - r.start_time AS StartTime, - s.login_name AS LoginName, - s.nt_user_name AS NTUserName, - s.[program_name] AS ProgramName, - s.[host_name] AS HostName, - db.[resource_database_id] AS DatabaseID, - DB_NAME(db.resource_database_id) AS DatabaseName, - (SELECT TOP 1 [text] FROM sys.dm_exec_sql_text(c.most_recent_sql_handle)) AS QueryText, - r.query_hash - FROM sys.dm_exec_sessions s - INNER JOIN sys.dm_exec_connections c ON s.session_id = c.session_id - INNER JOIN sys.dm_exec_requests r ON s.session_id = r.session_id - LEFT OUTER JOIN ( - SELECT DISTINCT request_session_id, resource_database_id - FROM sys.dm_tran_locks - WHERE resource_type = N'DATABASE' - AND request_mode = N'S' - AND request_status = N'GRANT' - AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE') AS db ON s.session_id = db.request_session_id - WHERE r.status = 'rollback'; - END - - IF @Seconds > 0 - BEGIN - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) - SELECT - 47 AS CheckId, - 50 AS Priority, - 'Query Problems' AS FindingsGroup, - 'High Percentage Of Runnable Queries' AS Finding, - 'https://erikdarlingdata.com/go/RunnableQueue/' AS URL, - 'On the ' - + CASE WHEN y.pass = 1 - THEN '1st' - ELSE '2nd' - END - + ' pass, ' - + RTRIM(y.runnable_pct) - + '% of your queries were waiting to get on a CPU to run. ' - + ' This can indicate CPU pressure.' - FROM - ( - SELECT - 1 AS pass, - x.total, - x.runnable, - CONVERT(decimal(5,2), - ( - x.runnable / - (1. * NULLIF(x.total, 0)) - ) - ) * 100. AS runnable_pct - FROM - ( - SELECT - COUNT_BIG(*) AS total, - SUM(CASE WHEN status = 'runnable' - THEN 1 - ELSE 0 - END) AS runnable - FROM sys.dm_exec_requests - WHERE session_id > 50 - ) AS x - ) AS y - WHERE y.runnable_pct > 20.; - END - - /* Server Performance - Too Much Free Memory - CheckID 34 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 34',10,1) WITH NOWAIT; - END - - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) - SELECT 34 AS CheckID, - 50 AS Priority, - 'Server Performance' AS FindingGroup, - 'Too Much Free Memory' AS Finding, - 'https://www.brentozar.com/go/freememory' AS URL, - CAST((CAST(cFree.cntr_value AS BIGINT) / 1024 / 1024 ) AS NVARCHAR(100)) + N'GB of free memory inside SQL Server''s buffer pool,' + @LineFeed + ' which is ' + CAST((CAST(cTotal.cntr_value AS BIGINT) / 1024 / 1024) AS NVARCHAR(100)) + N'GB. You would think lots of free memory would be good, but check out the URL for more information.' AS Details, - 'Run sp_BlitzCache @SortOrder = ''memory grant'' to find queries with huge memory grants and tune them.' AS HowToStopIt - FROM sys.dm_os_performance_counters cFree - INNER JOIN sys.dm_os_performance_counters cTotal ON cTotal.object_name LIKE N'%Memory Manager%' - AND cTotal.counter_name = N'Total Server Memory (KB) ' - WHERE cFree.object_name LIKE N'%Memory Manager%' - AND cFree.counter_name = N'Free Memory (KB) ' - AND CAST(cFree.cntr_value AS BIGINT) > 20480000000 - AND CAST(cTotal.cntr_value AS BIGINT) * .3 <= CAST(cFree.cntr_value AS BIGINT) - AND CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) NOT LIKE '%Standard%'; - - /* Server Performance - Target Memory Lower Than Max - CheckID 35 */ - IF SERVERPROPERTY('EngineEdition') <> 5 /*SERVERPROPERTY('Edition') <> 'SQL Azure'*/ - BEGIN - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 35',10,1) WITH NOWAIT; - END - - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) - SELECT 35 AS CheckID, - 10 AS Priority, - 'Server Performance' AS FindingGroup, - 'Target Memory Lower Than Max' AS Finding, - 'https://www.brentozar.com/go/target' AS URL, - N'Max server memory is ' + CAST(cMax.value_in_use AS NVARCHAR(50)) + N' MB but target server memory is only ' + CAST((CAST(cTarget.cntr_value AS BIGINT) / 1024) AS NVARCHAR(50)) + N' MB,' + @LineFeed - + N'indicating that SQL Server may be under external memory pressure or max server memory may be set too high.' AS Details, - 'Investigate what OS processes are using memory, and double-check the max server memory setting.' AS HowToStopIt - FROM sys.configurations cMax - INNER JOIN sys.dm_os_performance_counters cTarget ON cTarget.object_name LIKE N'%Memory Manager%' - AND cTarget.counter_name = N'Target Server Memory (KB) ' - WHERE cMax.name = 'max server memory (MB)' - AND CAST(cMax.value_in_use AS BIGINT) >= 1.5 * (CAST(cTarget.cntr_value AS BIGINT) / 1024) - AND CAST(cMax.value_in_use AS BIGINT) < 2147483647 /* Not set to default of unlimited */ - AND CAST(cTarget.cntr_value AS BIGINT) < .8 * (SELECT available_physical_memory_kb FROM sys.dm_os_sys_memory); /* Target memory less than 80% of physical memory (in case they set max too high) */ - END - - /* Server Info - Database Size, Total GB - CheckID 21 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 21',10,1) WITH NOWAIT; - END - - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) - SELECT 21 AS CheckID, - 251 AS Priority, - 'Server Info' AS FindingGroup, - 'Database Size, Total GB' AS Finding, - CAST(SUM (CAST(size AS BIGINT)*8./1024./1024.) AS VARCHAR(100)) AS Details, - SUM (CAST(size AS BIGINT))*8./1024./1024. AS DetailsInt, - 'https://www.brentozar.com/askbrent/' AS URL - FROM #MasterFiles - WHERE database_id > 4; - - /* Server Info - Database Count - CheckID 22 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 22',10,1) WITH NOWAIT; - END - - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) - SELECT 22 AS CheckID, - 251 AS Priority, - 'Server Info' AS FindingGroup, - 'Database Count' AS Finding, - CAST(SUM(1) AS VARCHAR(100)) AS Details, - SUM (1) AS DetailsInt, - 'https://www.brentozar.com/askbrent/' AS URL - FROM sys.databases - WHERE database_id > 4; - - /* Server Info - Memory Grants pending - CheckID 39 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 39',10,1) WITH NOWAIT; - END - - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) - SELECT 39 AS CheckID, - 50 AS Priority, - 'Server Performance' AS FindingGroup, - 'Memory Grants Pending' AS Finding, - CAST(PendingGrants.Details AS NVARCHAR(50)) AS Details, - PendingGrants.DetailsInt, - 'https://www.brentozar.com/blitz/memory-grants/' AS URL - FROM - ( - SELECT - COUNT(1) AS Details, - COUNT(1) AS DetailsInt - FROM sys.dm_exec_query_memory_grants AS Grants - WHERE queue_id IS NOT NULL - ) AS PendingGrants - WHERE PendingGrants.Details > 0; - - /* Server Info - Memory Grant/Workspace info - CheckID 40 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 40',10,1) WITH NOWAIT; - END - - DECLARE @MaxWorkspace BIGINT - SET @MaxWorkspace = (SELECT CAST(cntr_value AS BIGINT)/1024 FROM #PerfmonStats WHERE counter_name = N'Maximum Workspace Memory (KB)') - - IF (@MaxWorkspace IS NULL - OR @MaxWorkspace = 0) - BEGIN - SET @MaxWorkspace = 1 - END - - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) - SELECT 40 AS CheckID, - 251 AS Priority, - 'Server Info' AS FindingGroup, - 'Memory Grant/Workspace info' AS Finding, - + 'Grants Outstanding: ' + CAST((SELECT COUNT(*) FROM sys.dm_exec_query_memory_grants WHERE queue_id IS NULL) AS NVARCHAR(50)) + @LineFeed - + 'Total Granted(MB): ' + CAST(ISNULL(SUM(Grants.granted_memory_kb) / 1024, 0) AS NVARCHAR(50)) + @LineFeed - + 'Total WorkSpace(MB): ' + CAST(ISNULL(@MaxWorkspace, 0) AS NVARCHAR(50)) + @LineFeed - + 'Granted workspace: ' + CAST(ISNULL((CAST(SUM(Grants.granted_memory_kb) / 1024 AS MONEY) - / CAST(@MaxWorkspace AS MONEY)) * 100, 0) AS NVARCHAR(50)) + '%' + @LineFeed - + 'Oldest Grant in seconds: ' + CAST(ISNULL(DATEDIFF(SECOND, MIN(Grants.request_time), GETDATE()), 0) AS NVARCHAR(50)) AS Details, - (SELECT COUNT(*) FROM sys.dm_exec_query_memory_grants WHERE queue_id IS NULL) AS DetailsInt, - 'https://www.brentozar.com/askbrent/' AS URL - FROM sys.dm_exec_query_memory_grants AS Grants; - - /* Query Problems - Queries with high memory grants - CheckID 46 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 46',10,1) WITH NOWAIT; - END - - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, URL, QueryText, QueryPlan) - SELECT 46 AS CheckID, - 100 AS Priority, - 'Query Problems' AS FindingGroup, - 'Query with a memory grant exceeding ' - +CAST(@MemoryGrantThresholdPct AS NVARCHAR(15)) - +'%' AS Finding, - 'Granted size: '+ CAST(CAST(Grants.granted_memory_kb / 1024 AS INT) AS NVARCHAR(50)) - +N'MB ' - + @LineFeed - +N'Granted pct of max workspace: ' - + CAST(ISNULL((CAST(Grants.granted_memory_kb / 1024 AS MONEY) - / CAST(@MaxWorkspace AS MONEY)) * 100, 0) AS NVARCHAR(50)) + '%' - + @LineFeed - +N'SQLHandle: ' - +CONVERT(NVARCHAR(128),Grants.[sql_handle],1), - 'https://www.brentozar.com/memory-grants-sql-servers-public-toilet/' AS URL, - SQLText.[text], - QueryPlan.query_plan - FROM sys.dm_exec_query_memory_grants AS Grants - OUTER APPLY sys.dm_exec_sql_text(Grants.[sql_handle]) AS SQLText - OUTER APPLY sys.dm_exec_query_plan(Grants.[plan_handle]) AS QueryPlan - WHERE Grants.granted_memory_kb > ((@MemoryGrantThresholdPct/100.00)*(@MaxWorkspace*1024)); - - /* Query Problems - Memory Leak in USERSTORE_TOKENPERM Cache - CheckID 45 */ - IF EXISTS (SELECT * FROM sys.all_columns WHERE object_id = OBJECT_ID('sys.dm_os_memory_clerks') AND name = 'pages_kb') - BEGIN - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 45',10,1) WITH NOWAIT; - END - - /* SQL 2012+ version */ - SET @StringToExecute = N' - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, URL) - SELECT 45 AS CheckID, - 50 AS Priority, - ''Query Problems'' AS FindingsGroup, - ''Memory Leak in USERSTORE_TOKENPERM Cache'' AS Finding, - N''UserStore_TokenPerm clerk is using '' + CAST(CAST(SUM(CASE WHEN type = ''USERSTORE_TOKENPERM'' AND name = ''TokenAndPermUserStore'' THEN pages_kb * 1.0 ELSE 0.0 END) / 1024.0 / 1024.0 AS INT) AS NVARCHAR(100)) - + N''GB RAM, total buffer pool is '' + CAST(CAST(SUM(pages_kb) / 1024.0 / 1024.0 AS INT) AS NVARCHAR(100)) + N''GB.'' - AS details, - ''https://www.BrentOzar.com/go/userstore'' AS URL - FROM sys.dm_os_memory_clerks - HAVING SUM(CASE WHEN type = ''USERSTORE_TOKENPERM'' AND name = ''TokenAndPermUserStore'' THEN pages_kb * 1.0 ELSE 0.0 END) / SUM(pages_kb) >= 0.1 - AND SUM(pages_kb) / 1024.0 / 1024.0 >= 1; /* At least 1GB RAM overall */'; - EXEC sp_executesql @StringToExecute; - END - ELSE - BEGIN - /* Antiques Roadshow SQL 2008R2 - version */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 45 (Legacy version)',10,1) WITH NOWAIT; - END - - SET @StringToExecute = N' - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, URL) - SELECT 45 AS CheckID, - 50 AS Priority, - ''Performance'' AS FindingsGroup, - ''Memory Leak in USERSTORE_TOKENPERM Cache'' AS Finding, - N''UserStore_TokenPerm clerk is using '' + CAST(CAST(SUM(CASE WHEN type = ''USERSTORE_TOKENPERM'' AND name = ''TokenAndPermUserStore'' THEN single_pages_kb + multi_pages_kb * 1.0 ELSE 0.0 END) / 1024.0 / 1024.0 AS INT) AS NVARCHAR(100)) - + N''GB RAM, total buffer pool is '' + CAST(CAST(SUM(single_pages_kb + multi_pages_kb) / 1024.0 / 1024.0 AS INT) AS NVARCHAR(100)) + N''GB.'' - AS details, - ''https://www.BrentOzar.com/go/userstore'' AS URL - FROM sys.dm_os_memory_clerks - HAVING SUM(CASE WHEN type = ''USERSTORE_TOKENPERM'' AND name = ''TokenAndPermUserStore'' THEN single_pages_kb + multi_pages_kb * 1.0 ELSE 0.0 END) / SUM(single_pages_kb + multi_pages_kb) >= 0.1 - AND SUM(single_pages_kb + multi_pages_kb) / 1024.0 / 1024.0 >= 1; /* At least 1GB RAM overall */'; - EXEC sp_executesql @StringToExecute; - END - - - - IF @Seconds > 0 - BEGIN - - IF EXISTS ( SELECT 1/0 - FROM sys.all_objects AS ao - WHERE ao.name = 'dm_exec_query_profiles' ) - BEGIN - - IF EXISTS( SELECT 1/0 - FROM sys.dm_exec_requests AS r - JOIN sys.dm_exec_sessions AS s - ON r.session_id = s.session_id - WHERE s.host_name IS NOT NULL - AND r.total_elapsed_time > 5000 - AND r.request_id > 0 ) - BEGIN - - SET @StringToExecute = N' - DECLARE @bad_estimate TABLE - ( - session_id INT, - request_id INT, - estimate_inaccuracy BIT - ); - - INSERT @bad_estimate ( session_id, request_id, estimate_inaccuracy ) - SELECT x.session_id, - x.request_id, - x.estimate_inaccuracy - FROM ( - SELECT deqp.session_id, - deqp.request_id, - CASE WHEN (deqp.row_count/10000) > deqp.estimate_row_count - THEN 1 - ELSE 0 - END AS estimate_inaccuracy - FROM sys.dm_exec_query_profiles AS deqp - INNER JOIN sys.dm_exec_requests r ON deqp.session_id = r.session_id AND deqp.request_id = r.request_id - WHERE deqp.session_id <> @@SPID - AND r.total_elapsed_time > 5000 - ) AS x - WHERE x.estimate_inaccuracy = 1 - GROUP BY x.session_id, - x.request_id, - x.estimate_inaccuracy; - - DECLARE @parallelism_skew TABLE - ( - session_id INT, - request_id INT, - parallelism_skew BIT - ); - - INSERT @parallelism_skew ( session_id, request_id, parallelism_skew ) - SELECT y.session_id, - y.request_id, - y.parallelism_skew - FROM ( - SELECT x.session_id, - x.request_id, - x.node_id, - x.thread_id, - x.row_count, - x.sum_node_rows, - x.node_dop, - x.sum_node_rows / x.node_dop AS even_distribution, - x.row_count / (1. * ISNULL(NULLIF(x.sum_node_rows / x.node_dop, 0), 1)) AS skew_percent, - CASE - WHEN x.row_count > 10000 - AND x.row_count / (1. * ISNULL(NULLIF(x.sum_node_rows / x.node_dop, 0), 1)) > 2. - THEN 1 - WHEN x.row_count > 10000 - AND x.row_count / (1. * ISNULL(NULLIF(x.sum_node_rows / x.node_dop, 0), 1)) < 0.5 - THEN 1 - ELSE 0 - END AS parallelism_skew - FROM ( - SELECT deqp.session_id, - deqp.request_id, - deqp.node_id, - deqp.thread_id, - deqp.row_count, - SUM(deqp.row_count) - OVER ( PARTITION BY deqp.session_id, - deqp.request_id, - deqp.node_id - ORDER BY deqp.row_count - ROWS BETWEEN UNBOUNDED PRECEDING - AND UNBOUNDED FOLLOWING ) - AS sum_node_rows, - COUNT(*) - OVER ( PARTITION BY deqp.session_id, - deqp.request_id, - deqp.node_id - ORDER BY deqp.row_count - ROWS BETWEEN UNBOUNDED PRECEDING - AND UNBOUNDED FOLLOWING ) - AS node_dop - FROM sys.dm_exec_query_profiles AS deqp - WHERE deqp.thread_id > 0 - AND deqp.session_id <> @@SPID - AND EXISTS - ( - SELECT 1/0 - FROM sys.dm_exec_query_profiles AS deqp2 - WHERE deqp.session_id = deqp2.session_id - AND deqp.node_id = deqp2.node_id - AND deqp2.thread_id > 0 - GROUP BY deqp2.session_id, deqp2.node_id - HAVING COUNT(deqp2.node_id) > 1 - ) - ) AS x - ) AS y - WHERE y.parallelism_skew = 1 - GROUP BY y.session_id, - y.request_id, - y.parallelism_skew; - - /* Queries in dm_exec_query_profiles showing signs of poor cardinality estimates - CheckID 42 */ - IF (@Debug = 1) - BEGIN - RAISERROR(''Running CheckID 42'',10,1) WITH NOWAIT; - END - - INSERT INTO #BlitzFirstResults - (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, OpenTransactionCount, QueryHash, QueryPlan) - SELECT 42 AS CheckID, - 100 AS Priority, - ''Query Performance'' AS FindingsGroup, - ''Queries with 10000x cardinality misestimations'' AS Findings, - ''https://www.brentozar.com/go/skewedup'' AS URL, - ''The query on SPID '' - + RTRIM(b.session_id) - + '' has been running for '' - + RTRIM(r.total_elapsed_time / 1000) - + '' seconds, with a large cardinality misestimate'' AS Details, - ''No quick fix here: time to dig into the actual execution plan. '' AS HowToStopIt, - r.start_time, - s.login_name, - s.nt_user_name, - s.program_name, - s.host_name, - r.database_id, - DB_NAME(r.database_id), - dest.text, - s.open_transaction_count, - r.query_hash, '; - - IF @dm_exec_query_statistics_xml = 1 - SET @StringToExecute = @StringToExecute + N' COALESCE(qs_live.query_plan, qp.query_plan) AS query_plan '; - ELSE - SET @StringToExecute = @StringToExecute + N' qp.query_plan '; - - SET @StringToExecute = @StringToExecute + N' - FROM @bad_estimate AS b - JOIN sys.dm_exec_requests AS r - ON r.session_id = b.session_id - AND r.request_id = b.request_id - JOIN sys.dm_exec_sessions AS s - ON s.session_id = b.session_id - CROSS APPLY sys.dm_exec_sql_text(r.sql_handle) AS dest - CROSS APPLY sys.dm_exec_query_plan(r.plan_handle) AS qp '; - - IF EXISTS (SELECT * FROM sys.all_objects WHERE name = 'dm_exec_query_statistics_xml') - /* GitHub #3210 */ - SET @StringToExecute = N' - SET LOCK_TIMEOUT 1000 ' + @StringToExecute + N' OUTER APPLY sys.dm_exec_query_statistics_xml(s.session_id) qs_live '; - - SET @StringToExecute = @StringToExecute + N'; - - /* Queries in dm_exec_query_profiles showing signs of unbalanced parallelism - CheckID 43 */ - IF (@Debug = 1) - BEGIN - RAISERROR(''Running CheckID 43'',10,1) WITH NOWAIT; - END - - INSERT INTO #BlitzFirstResults - (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, StartTime, LoginName, NTUserName, ProgramName, HostName, DatabaseID, DatabaseName, QueryText, OpenTransactionCount, QueryHash, QueryPlan) - SELECT 43 AS CheckID, - 100 AS Priority, - ''Query Performance'' AS FindingsGroup, - ''Queries with 10000x skewed parallelism'' AS Findings, - ''https://www.brentozar.com/go/skewedup'' AS URL, - ''The query on SPID '' - + RTRIM(p.session_id) - + '' has been running for '' - + RTRIM(r.total_elapsed_time / 1000) - + '' seconds, with a parallel threads doing uneven work.'' AS Details, - ''No quick fix here: time to dig into the actual execution plan. '' AS HowToStopIt, - r.start_time, - s.login_name, - s.nt_user_name, - s.program_name, - s.host_name, - r.database_id, - DB_NAME(r.database_id), - dest.text, - s.open_transaction_count, - r.query_hash, '; - - IF @dm_exec_query_statistics_xml = 1 - SET @StringToExecute = @StringToExecute + N' COALESCE(qs_live.query_plan, qp.query_plan) AS query_plan '; - ELSE - SET @StringToExecute = @StringToExecute + N' qp.query_plan '; - - SET @StringToExecute = @StringToExecute + N' - FROM @parallelism_skew AS p - JOIN sys.dm_exec_requests AS r - ON r.session_id = p.session_id - AND r.request_id = p.request_id - JOIN sys.dm_exec_sessions AS s - ON s.session_id = p.session_id - CROSS APPLY sys.dm_exec_sql_text(r.sql_handle) AS dest - CROSS APPLY sys.dm_exec_query_plan(r.plan_handle) AS qp '; - - IF EXISTS (SELECT * FROM sys.all_objects WHERE name = 'dm_exec_query_statistics_xml') - SET @StringToExecute = @StringToExecute + N' OUTER APPLY sys.dm_exec_query_statistics_xml(s.session_id) qs_live '; - - - SET @StringToExecute = @StringToExecute + N';'; - - EXECUTE sp_executesql @StringToExecute, N'@Debug BIT',@Debug = @Debug; - END - - END - END - - /* Server Performance - High CPU Utilization - CheckID 24 */ - IF @Seconds < 30 - BEGIN - /* If we're waiting less than 30 seconds, run this check now rather than wait til the end. - We get this data from the ring buffers, and it's only updated once per minute, so might - as well get it now - whereas if we're checking 30+ seconds, it might get updated by the - end of our sp_BlitzFirst session. */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 24',10,1) WITH NOWAIT; - END - - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) - SELECT 24, 50, 'Server Performance', 'High CPU Utilization', CAST(100 - SystemIdle AS NVARCHAR(20)) + N'%.', 100 - SystemIdle, 'https://www.brentozar.com/go/cpu' - FROM ( - SELECT record, - record.value('(./Record/SchedulerMonitorEvent/SystemHealth/SystemIdle)[1]', 'int') AS SystemIdle - FROM ( - SELECT TOP 1 CONVERT(XML, record) AS record - FROM sys.dm_os_ring_buffers - WHERE ring_buffer_type = N'RING_BUFFER_SCHEDULER_MONITOR' - AND record LIKE '%%' - ORDER BY timestamp DESC) AS rb - ) AS y - WHERE 100 - SystemIdle >= 50; - - /* CPU Utilization - CheckID 23 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 23',10,1) WITH NOWAIT; - END - - IF SERVERPROPERTY('EngineEdition') <> 5 /*SERVERPROPERTY('Edition') <> 'SQL Azure'*/ - WITH y - AS - ( - SELECT CONVERT(VARCHAR(5), 100 - ca.c.value('.', 'INT')) AS system_idle, - CONVERT(VARCHAR(30), rb.event_date) AS event_date, - CONVERT(VARCHAR(8000), rb.record) AS record, - event_date as event_date_raw - FROM - ( SELECT CONVERT(XML, dorb.record) AS record, - DATEADD(ms, -( ts.ms_ticks - dorb.timestamp ), GETDATE()) AS event_date - FROM sys.dm_os_ring_buffers AS dorb - CROSS JOIN - ( SELECT dosi.ms_ticks FROM sys.dm_os_sys_info AS dosi ) AS ts - WHERE dorb.ring_buffer_type = N'RING_BUFFER_SCHEDULER_MONITOR' - AND record LIKE '%%' ) AS rb - CROSS APPLY rb.record.nodes('/Record/SchedulerMonitorEvent/SystemHealth/SystemIdle') AS ca(c) - ) - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL, HowToStopIt) - SELECT TOP 1 - 23, - 250, - 'Server Info', - 'CPU Utilization', - y.system_idle + N'%. Ring buffer details: ' + CAST(y.record AS NVARCHAR(4000)), - y.system_idle , - 'https://www.brentozar.com/go/cpu', - STUFF(( SELECT TOP 2147483647 - CHAR(10) + CHAR(13) - + y2.system_idle - + '% ON ' - + y2.event_date - + ' Ring buffer details: ' - + y2.record - FROM y AS y2 - ORDER BY y2.event_date_raw DESC - FOR XML PATH(N''), TYPE ).value(N'.[1]', N'VARCHAR(MAX)'), 1, 1, N'') AS query - FROM y - ORDER BY y.event_date_raw DESC; - - - /* Highlight if non SQL processes are using >25% CPU - CheckID 28 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 28',10,1) WITH NOWAIT; - END - - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) - SELECT 28, 50, 'Server Performance', 'High CPU Utilization - Not SQL', CONVERT(NVARCHAR(100),100 - (y.SQLUsage + y.SystemIdle)) + N'% - Other Processes (not SQL Server) are using this much CPU. This may impact on the performance of your SQL Server instance', 100 - (y.SQLUsage + y.SystemIdle), 'https://www.brentozar.com/go/cpu' - FROM ( - SELECT record, - record.value('(./Record/SchedulerMonitorEvent/SystemHealth/SystemIdle)[1]', 'int') AS SystemIdle - ,record.value('(./Record/SchedulerMonitorEvent/SystemHealth/ProcessUtilization)[1]', 'int') AS SQLUsage - FROM ( - SELECT TOP 1 CONVERT(XML, record) AS record - FROM sys.dm_os_ring_buffers - WHERE ring_buffer_type = N'RING_BUFFER_SCHEDULER_MONITOR' - AND record LIKE '%%' - ORDER BY timestamp DESC) AS rb - ) AS y - WHERE 100 - (y.SQLUsage + y.SystemIdle) >= 25; - - END; /* IF @Seconds < 30 */ - - /* Query Problems - Statistics Updated Recently - CheckID 44 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 44',10,1) WITH NOWAIT; - END - - IF 20 >= (SELECT COUNT(*) FROM sys.databases WHERE name NOT IN ('master', 'model', 'msdb', 'tempdb')) - AND @Seconds > 0 - BEGIN - CREATE TABLE #UpdatedStats (HowToStopIt NVARCHAR(4000), RowsForSorting BIGINT); - IF EXISTS(SELECT * FROM sys.all_objects WHERE name = 'dm_db_stats_properties') - BEGIN - /* We don't want to hang around to obtain locks */ - SET LOCK_TIMEOUT 0; - - IF SERVERPROPERTY('EngineEdition') <> 5 /*SERVERPROPERTY('Edition') <> 'SQL Azure'*/ - SET @StringToExecute = N'USE [?];' + @LineFeed; - ELSE - SET @StringToExecute = N''; - - SET @StringToExecute = @StringToExecute + 'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; SET LOCK_TIMEOUT 1000;' + @LineFeed + - 'BEGIN TRY' + @LineFeed + - ' INSERT INTO #UpdatedStats(HowToStopIt, RowsForSorting)' + @LineFeed + - ' SELECT HowToStopIt = ' + @LineFeed + - ' QUOTENAME(DB_NAME()) + N''.'' +' + @LineFeed + - ' QUOTENAME(SCHEMA_NAME(obj.schema_id)) + N''.'' +' + @LineFeed + - ' QUOTENAME(obj.name) +' + @LineFeed + - ' N'' statistic '' + QUOTENAME(stat.name) + ' + @LineFeed + - ' N'' was updated on '' + CONVERT(NVARCHAR(50), sp.last_updated, 121) + N'','' + ' + @LineFeed + - ' N'' had '' + CAST(sp.rows AS NVARCHAR(50)) + N'' rows, with '' +' + @LineFeed + - ' CAST(sp.rows_sampled AS NVARCHAR(50)) + N'' rows sampled,'' + ' + @LineFeed + - ' N'' producing '' + CAST(sp.steps AS NVARCHAR(50)) + N'' steps in the histogram.'',' + @LineFeed + - ' sp.rows' + @LineFeed + - ' FROM sys.objects AS obj WITH (NOLOCK)' + @LineFeed + - ' INNER JOIN sys.stats AS stat WITH (NOLOCK) ON stat.object_id = obj.object_id ' + @LineFeed + - ' CROSS APPLY sys.dm_db_stats_properties(stat.object_id, stat.stats_id) AS sp ' + @LineFeed + - ' WHERE sp.last_updated > DATEADD(MI, -15, GETDATE())' + @LineFeed + - ' AND obj.is_ms_shipped = 0' + @LineFeed + - ' AND ''[?]'' <> ''[tempdb]'';' + @LineFeed + - 'END TRY' + @LineFeed + - 'BEGIN CATCH' + @LineFeed + - ' IF (ERROR_NUMBER() = 1222)' + @LineFeed + - ' BEGIN ' + @LineFeed + - ' INSERT INTO #UpdatedStats(HowToStopIt, RowsForSorting)' + @LineFeed + - ' SELECT HowToStopIt = ' + @LineFeed + - ' QUOTENAME(DB_NAME()) +' + @LineFeed + - ' N'' No information could be retrieved as the lock timeout was exceeded,''+' + @LineFeed + - ' N'' this is likely due to an Index operation in Progress'',' + @LineFeed + - ' -1' + @LineFeed + - ' END' + @LineFeed + - ' ELSE' + @LineFeed + - ' BEGIN' + @LineFeed + - ' INSERT INTO #UpdatedStats(HowToStopIt, RowsForSorting)' + @LineFeed + - ' SELECT HowToStopIt = ' + @LineFeed + - ' QUOTENAME(DB_NAME()) +' + @LineFeed + - ' N'' No information could be retrieved as a result of error: ''+' + @LineFeed + - ' CAST(ERROR_NUMBER() AS NVARCHAR(10)) +' + @LineFeed + - ' N'' with message: ''+' + @LineFeed + - ' CAST(ERROR_MESSAGE() AS NVARCHAR(128)),' + @LineFeed + - ' -1' + @LineFeed + - ' END' + @LineFeed + - 'END CATCH' - ; - - IF SERVERPROPERTY('EngineEdition') <> 5 /*SERVERPROPERTY('Edition') <> 'SQL Azure'*/ - BEGIN - BEGIN TRY - EXEC sp_MSforeachdb @StringToExecute; - END TRY - BEGIN CATCH - IF (ERROR_NUMBER() = 1222) - BEGIN - INSERT INTO #UpdatedStats(HowToStopIt, RowsForSorting) - SELECT HowToStopIt = N'No information could be retrieved as the lock timeout was exceeded while iterating databases,' + - N' this is likely due to an Index operation in Progress', -1; - END - ELSE - BEGIN - THROW; - END - END CATCH - END - ELSE - EXEC(@StringToExecute); - - /* Set timeout back to a default value of -1 */ - SET LOCK_TIMEOUT -1; - END; - - /* We mark timeout exceeded with a -1 so only show these IF there is statistics info that succeeded */ - IF EXISTS (SELECT * FROM #UpdatedStats WHERE RowsForSorting > -1) - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) - SELECT 44 AS CheckId, - 50 AS Priority, - 'Query Problems' AS FindingGroup, - 'Statistics Updated Recently' AS Finding, - 'https://www.brentozar.com/go/stats' AS URL, - 'In the last 15 minutes, statistics were updated. To see which ones, click the HowToStopIt column.' + @LineFeed + @LineFeed - + 'This effectively clears the plan cache for queries that involve these tables,' + @LineFeed - + 'which thereby causes parameter sniffing: those queries are now getting brand new' + @LineFeed - + 'query plans based on whatever parameters happen to call them next.' + @LineFeed + @LineFeed - + 'Be on the lookout for sudden parameter sniffing issues after this time range.', - HowToStopIt = (SELECT (SELECT HowToStopIt + NCHAR(10)) - FROM #UpdatedStats - ORDER BY RowsForSorting DESC - FOR XML PATH('')); - - END - - RAISERROR('Finished running investigatory queries',10,1) WITH NOWAIT; - - - /* End of checks. If we haven't waited @Seconds seconds, wait. */ - IF DATEADD(SECOND,1,SYSDATETIMEOFFSET()) < @FinishSampleTime - BEGIN - RAISERROR('Waiting to match @Seconds parameter',10,1) WITH NOWAIT; - WAITFOR TIME @FinishSampleTimeWaitFor; - END; - - IF @total_cpu_usage IN (0, 1) - BEGIN - EXEC sys.sp_executesql - @get_thread_time_ms, - N'@thread_time_ms FLOAT OUTPUT', - @thread_time_ms OUTPUT; - END - - RAISERROR('Capturing second pass of wait stats, perfmon counters, file stats',10,1) WITH NOWAIT; - /* Populate #FileStats, #PerfmonStats, #WaitStats with DMV data. In a second, we'll compare these. */ - SET @StringToExecute = N' - INSERT #WaitStats(Pass, SampleTime, wait_type, wait_time_ms, thread_time_ms, signal_wait_time_ms, waiting_tasks_count) - SELECT - x.Pass, - x.SampleTime, - x.wait_type, - SUM(x.sum_wait_time_ms) AS sum_wait_time_ms, - @thread_time_ms AS thread_time_ms, - SUM(x.sum_signal_wait_time_ms) AS sum_signal_wait_time_ms, - SUM(x.sum_waiting_tasks) AS sum_waiting_tasks - FROM ( - SELECT - 2 AS Pass, - SYSDATETIMEOFFSET() AS SampleTime, - owt.wait_type, - SUM(owt.wait_duration_ms) OVER (PARTITION BY owt.wait_type, owt.session_id) - - CASE WHEN @Seconds = 0 THEN 0 ELSE (@Seconds * 1000) END AS sum_wait_time_ms, - 0 AS sum_signal_wait_time_ms, - CASE @Seconds WHEN 0 THEN 0 ELSE 1 END AS sum_waiting_tasks - FROM sys.dm_os_waiting_tasks owt - WHERE owt.session_id > 50 - AND owt.wait_duration_ms >= CASE @Seconds WHEN 0 THEN 0 ELSE @Seconds * 1000 END - UNION ALL - SELECT - 2 AS Pass, - SYSDATETIMEOFFSET() AS SampleTime, - os.wait_type, - SUM(os.wait_time_ms) OVER (PARTITION BY os.wait_type) AS sum_wait_time_ms, - SUM(os.signal_wait_time_ms) OVER (PARTITION BY os.wait_type ) AS sum_signal_wait_time_ms, - SUM(os.waiting_tasks_count) OVER (PARTITION BY os.wait_type) AS sum_waiting_tasks '; - - IF SERVERPROPERTY('EngineEdition') = 5 /*SERVERPROPERTY('Edition') = 'SQL Azure'*/ - SET @StringToExecute = @StringToExecute + N' FROM sys.dm_db_wait_stats os '; - ELSE - SET @StringToExecute = @StringToExecute + N' FROM sys.dm_os_wait_stats os '; - - SET @StringToExecute = @StringToExecute + N' - ) x - WHERE NOT EXISTS - ( - SELECT * - FROM ##WaitCategories AS wc - WHERE wc.WaitType = x.wait_type - AND wc.Ignorable = 1 - ) - GROUP BY x.Pass, x.SampleTime, x.wait_type - ORDER BY sum_wait_time_ms DESC;'; - - EXEC sys.sp_executesql - @StringToExecute, - N'@StartSampleTime DATETIMEOFFSET, - @Seconds INT, - @thread_time_ms FLOAT', - @StartSampleTime, - @Seconds, - @thread_time_ms; - - WITH w AS - ( - SELECT - total_waits = - CONVERT - ( - FLOAT, - SUM(ws.wait_time_ms) - ) - FROM #WaitStats AS ws - WHERE Pass = 2 - ) - UPDATE ws - SET ws.thread_time_ms += w.total_waits - FROM #WaitStats AS ws - CROSS JOIN w - WHERE ws.Pass = 2 - OPTION(RECOMPILE); - - - INSERT INTO #FileStats (Pass, SampleTime, DatabaseID, FileID, DatabaseName, FileLogicalName, SizeOnDiskMB, io_stall_read_ms , - num_of_reads, [bytes_read] , io_stall_write_ms,num_of_writes, [bytes_written], PhysicalName, TypeDesc, avg_stall_read_ms, avg_stall_write_ms) - SELECT 2 AS Pass, - SYSDATETIMEOFFSET() AS SampleTime, - mf.[database_id], - mf.[file_id], - DB_NAME(vfs.database_id) AS [db_name], - mf.name + N' [' + mf.type_desc COLLATE SQL_Latin1_General_CP1_CI_AS + N']' AS file_logical_name , - CAST(( ( vfs.size_on_disk_bytes / 1024.0 ) / 1024.0 ) AS INT) AS size_on_disk_mb , - vfs.io_stall_read_ms , - vfs.num_of_reads , - vfs.[num_of_bytes_read], - vfs.io_stall_write_ms , - vfs.num_of_writes , - vfs.[num_of_bytes_written], - mf.physical_name, - mf.type_desc, - 0, - 0 - FROM sys.dm_io_virtual_file_stats (NULL, NULL) AS vfs - INNER JOIN #MasterFiles AS mf ON vfs.file_id = mf.file_id - AND vfs.database_id = mf.database_id - WHERE vfs.num_of_reads > 0 - OR vfs.num_of_writes > 0; - - INSERT INTO #PerfmonStats (Pass, SampleTime, [object_name],[counter_name],[instance_name],[cntr_value],[cntr_type]) - SELECT 2 AS Pass, - SYSDATETIMEOFFSET() AS SampleTime, - RTRIM(dmv.object_name), RTRIM(dmv.counter_name), RTRIM(dmv.instance_name), dmv.cntr_value, dmv.cntr_type - FROM #PerfmonCounters counters - INNER JOIN sys.dm_os_performance_counters dmv ON counters.counter_name COLLATE SQL_Latin1_General_CP1_CI_AS = RTRIM(dmv.counter_name) COLLATE SQL_Latin1_General_CP1_CI_AS - AND counters.[object_name] COLLATE SQL_Latin1_General_CP1_CI_AS = RTRIM(dmv.[object_name]) COLLATE SQL_Latin1_General_CP1_CI_AS - AND (counters.[instance_name] IS NULL OR counters.[instance_name] COLLATE SQL_Latin1_General_CP1_CI_AS = RTRIM(dmv.[instance_name]) COLLATE SQL_Latin1_General_CP1_CI_AS); - - /* Set the latencies and averages. We could do this with a CTE, but we're not ambitious today. */ - UPDATE fNow - SET avg_stall_read_ms = ((fNow.io_stall_read_ms - fBase.io_stall_read_ms) / (fNow.num_of_reads - fBase.num_of_reads)) - FROM #FileStats fNow - INNER JOIN #FileStats fBase ON fNow.DatabaseID = fBase.DatabaseID AND fNow.FileID = fBase.FileID AND fNow.SampleTime > fBase.SampleTime AND fNow.num_of_reads > fBase.num_of_reads AND fNow.io_stall_read_ms > fBase.io_stall_read_ms - WHERE (fNow.num_of_reads - fBase.num_of_reads) > 0; - - UPDATE fNow - SET avg_stall_write_ms = ((fNow.io_stall_write_ms - fBase.io_stall_write_ms) / (fNow.num_of_writes - fBase.num_of_writes)) - FROM #FileStats fNow - INNER JOIN #FileStats fBase ON fNow.DatabaseID = fBase.DatabaseID AND fNow.FileID = fBase.FileID AND fNow.SampleTime > fBase.SampleTime AND fNow.num_of_writes > fBase.num_of_writes AND fNow.io_stall_write_ms > fBase.io_stall_write_ms - WHERE (fNow.num_of_writes - fBase.num_of_writes) > 0; - - UPDATE pNow - SET [value_delta] = pNow.cntr_value - pFirst.cntr_value, - [value_per_second] = ((1.0 * pNow.cntr_value - pFirst.cntr_value) / DATEDIFF(ss, pFirst.SampleTime, pNow.SampleTime)) - FROM #PerfmonStats pNow - INNER JOIN #PerfmonStats pFirst ON pFirst.[object_name] = pNow.[object_name] AND pFirst.counter_name = pNow.counter_name AND (pFirst.instance_name = pNow.instance_name OR (pFirst.instance_name IS NULL AND pNow.instance_name IS NULL)) - AND pNow.ID > pFirst.ID - WHERE DATEDIFF(ss, pFirst.SampleTime, pNow.SampleTime) > 0; - - - /* Query Stats - If we're within 10 seconds of our projected finish time, do the plan cache analysis. - CheckID 18 */ - IF DATEDIFF(ss, @FinishSampleTime, SYSDATETIMEOFFSET()) > 10 AND @CheckProcedureCache = 1 - BEGIN - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 18',10,1) WITH NOWAIT; - END - - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) - VALUES (18, 210, 'Query Stats', 'Plan Cache Analysis Skipped', 'https://www.brentozar.com/go/topqueries', - 'Due to excessive load, the plan cache analysis was skipped. To override this, use @ExpertMode = 1.'); - - END; - ELSE IF @CheckProcedureCache = 1 - BEGIN - - - RAISERROR('@CheckProcedureCache = 1, capturing second pass of plan cache',10,1) WITH NOWAIT; - - /* Populate #QueryStats. SQL 2005 doesn't have query hash or query plan hash. */ - IF @@VERSION LIKE 'Microsoft SQL Server 2005%' - BEGIN - IF @FilterPlansByDatabase IS NULL - BEGIN - SET @StringToExecute = N'INSERT INTO #QueryStats ([sql_handle], Pass, SampleTime, statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, Points) - SELECT [sql_handle], 2 AS Pass, SYSDATETIMEOFFSET(), statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, NULL AS query_hash, NULL AS query_plan_hash, 0 - FROM sys.dm_exec_query_stats qs - WHERE qs.last_execution_time >= @StartSampleTimeText;'; - END; - ELSE - BEGIN - SET @StringToExecute = N'INSERT INTO #QueryStats ([sql_handle], Pass, SampleTime, statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, Points) - SELECT [sql_handle], 2 AS Pass, SYSDATETIMEOFFSET(), statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, NULL AS query_hash, NULL AS query_plan_hash, 0 - FROM sys.dm_exec_query_stats qs - CROSS APPLY sys.dm_exec_plan_attributes(qs.plan_handle) AS attr - INNER JOIN #FilterPlansByDatabase dbs ON CAST(attr.value AS INT) = dbs.DatabaseID - WHERE qs.last_execution_time >= @StartSampleTimeText - AND attr.attribute = ''dbid'';'; - END; - END; - ELSE - BEGIN - IF @FilterPlansByDatabase IS NULL - BEGIN - SET @StringToExecute = N'INSERT INTO #QueryStats ([sql_handle], Pass, SampleTime, statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, Points) - SELECT [sql_handle], 2 AS Pass, SYSDATETIMEOFFSET(), statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, 0 - FROM sys.dm_exec_query_stats qs - WHERE qs.last_execution_time >= @StartSampleTimeText'; - END; - ELSE - BEGIN - SET @StringToExecute = N'INSERT INTO #QueryStats ([sql_handle], Pass, SampleTime, statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, Points) - SELECT [sql_handle], 2 AS Pass, SYSDATETIMEOFFSET(), statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, 0 - FROM sys.dm_exec_query_stats qs - CROSS APPLY sys.dm_exec_plan_attributes(qs.plan_handle) AS attr - INNER JOIN #FilterPlansByDatabase dbs ON CAST(attr.value AS INT) = dbs.DatabaseID - WHERE qs.last_execution_time >= @StartSampleTimeText - AND attr.attribute = ''dbid'';'; - END; - END; - /* Old version pre-2016/06/13: - IF @@VERSION LIKE 'Microsoft SQL Server 2005%' - SET @StringToExecute = N'INSERT INTO #QueryStats ([sql_handle], Pass, SampleTime, statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, Points) - SELECT [sql_handle], 2 AS Pass, SYSDATETIMEOFFSET(), statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, NULL AS query_hash, NULL AS query_plan_hash, 0 - FROM sys.dm_exec_query_stats qs - WHERE qs.last_execution_time >= @StartSampleTimeText;'; - ELSE - SET @StringToExecute = N'INSERT INTO #QueryStats ([sql_handle], Pass, SampleTime, statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, Points) - SELECT [sql_handle], 2 AS Pass, SYSDATETIMEOFFSET(), statement_start_offset, statement_end_offset, plan_generation_num, plan_handle, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time, query_hash, query_plan_hash, 0 - FROM sys.dm_exec_query_stats qs - WHERE qs.last_execution_time >= @StartSampleTimeText;'; - */ - SET @ParmDefinitions = N'@StartSampleTimeText NVARCHAR(100)'; - SET @Parm1 = CONVERT(NVARCHAR(100), CAST(@StartSampleTime AS DATETIME), 127); - - EXECUTE sp_executesql @StringToExecute, @ParmDefinitions, @StartSampleTimeText = @Parm1; - - RAISERROR('@CheckProcedureCache = 1, totaling up plan cache metrics',10,1) WITH NOWAIT; - - /* Get the totals for the entire plan cache */ - INSERT INTO #QueryStats (Pass, SampleTime, execution_count, total_worker_time, total_physical_reads, total_logical_writes, total_logical_reads, total_clr_time, total_elapsed_time, creation_time) - SELECT 0 AS Pass, SYSDATETIMEOFFSET(), SUM(execution_count), SUM(total_worker_time), SUM(total_physical_reads), SUM(total_logical_writes), SUM(total_logical_reads), SUM(total_clr_time), SUM(total_elapsed_time), MIN(creation_time) - FROM sys.dm_exec_query_stats qs; - - - RAISERROR('@CheckProcedureCache = 1, so analyzing execution plans',10,1) WITH NOWAIT; - /* - Pick the most resource-intensive queries to review. Update the Points field - in #QueryStats - if a query is in the top 10 for logical reads, CPU time, - duration, or execution, add 1 to its points. - */ - WITH qsTop AS ( - SELECT TOP 10 qsNow.ID - FROM #QueryStats qsNow - INNER JOIN #QueryStats qsFirst ON qsNow.[sql_handle] = qsFirst.[sql_handle] AND qsNow.statement_start_offset = qsFirst.statement_start_offset AND qsNow.statement_end_offset = qsFirst.statement_end_offset AND qsNow.plan_generation_num = qsFirst.plan_generation_num AND qsNow.plan_handle = qsFirst.plan_handle AND qsFirst.Pass = 1 - WHERE qsNow.total_elapsed_time > qsFirst.total_elapsed_time - AND qsNow.Pass = 2 - AND qsNow.total_elapsed_time - qsFirst.total_elapsed_time > 1000000 /* Only queries with over 1 second of runtime */ - ORDER BY (qsNow.total_elapsed_time - COALESCE(qsFirst.total_elapsed_time, 0)) DESC) - UPDATE #QueryStats - SET Points = Points + 1 - FROM #QueryStats qs - INNER JOIN qsTop ON qs.ID = qsTop.ID; - - WITH qsTop AS ( - SELECT TOP 10 qsNow.ID - FROM #QueryStats qsNow - INNER JOIN #QueryStats qsFirst ON qsNow.[sql_handle] = qsFirst.[sql_handle] AND qsNow.statement_start_offset = qsFirst.statement_start_offset AND qsNow.statement_end_offset = qsFirst.statement_end_offset AND qsNow.plan_generation_num = qsFirst.plan_generation_num AND qsNow.plan_handle = qsFirst.plan_handle AND qsFirst.Pass = 1 - WHERE qsNow.total_logical_reads > qsFirst.total_logical_reads - AND qsNow.Pass = 2 - AND qsNow.total_logical_reads - qsFirst.total_logical_reads > 1000 /* Only queries with over 1000 reads */ - ORDER BY (qsNow.total_logical_reads - COALESCE(qsFirst.total_logical_reads, 0)) DESC) - UPDATE #QueryStats - SET Points = Points + 1 - FROM #QueryStats qs - INNER JOIN qsTop ON qs.ID = qsTop.ID; - - WITH qsTop AS ( - SELECT TOP 10 qsNow.ID - FROM #QueryStats qsNow - INNER JOIN #QueryStats qsFirst ON qsNow.[sql_handle] = qsFirst.[sql_handle] AND qsNow.statement_start_offset = qsFirst.statement_start_offset AND qsNow.statement_end_offset = qsFirst.statement_end_offset AND qsNow.plan_generation_num = qsFirst.plan_generation_num AND qsNow.plan_handle = qsFirst.plan_handle AND qsFirst.Pass = 1 - WHERE qsNow.total_worker_time > qsFirst.total_worker_time - AND qsNow.Pass = 2 - AND qsNow.total_worker_time - qsFirst.total_worker_time > 1000000 /* Only queries with over 1 second of worker time */ - ORDER BY (qsNow.total_worker_time - COALESCE(qsFirst.total_worker_time, 0)) DESC) - UPDATE #QueryStats - SET Points = Points + 1 - FROM #QueryStats qs - INNER JOIN qsTop ON qs.ID = qsTop.ID; - - WITH qsTop AS ( - SELECT TOP 10 qsNow.ID - FROM #QueryStats qsNow - INNER JOIN #QueryStats qsFirst ON qsNow.[sql_handle] = qsFirst.[sql_handle] AND qsNow.statement_start_offset = qsFirst.statement_start_offset AND qsNow.statement_end_offset = qsFirst.statement_end_offset AND qsNow.plan_generation_num = qsFirst.plan_generation_num AND qsNow.plan_handle = qsFirst.plan_handle AND qsFirst.Pass = 1 - WHERE qsNow.execution_count > qsFirst.execution_count - AND qsNow.Pass = 2 - AND (qsNow.total_elapsed_time - qsFirst.total_elapsed_time > 1000000 /* Only queries with over 1 second of runtime */ - OR qsNow.total_logical_reads - qsFirst.total_logical_reads > 1000 /* Only queries with over 1000 reads */ - OR qsNow.total_worker_time - qsFirst.total_worker_time > 1000000 /* Only queries with over 1 second of worker time */) - ORDER BY (qsNow.execution_count - COALESCE(qsFirst.execution_count, 0)) DESC) - UPDATE #QueryStats - SET Points = Points + 1 - FROM #QueryStats qs - INNER JOIN qsTop ON qs.ID = qsTop.ID; - - /* Query Stats - Most Resource-Intensive Queries - CheckID 17 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 17',10,1) WITH NOWAIT; - END - - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, QueryText, QueryStatsNowID, QueryStatsFirstID, PlanHandle, QueryHash) - SELECT 17, 210, 'Query Stats', 'Most Resource-Intensive Queries', 'https://www.brentozar.com/go/topqueries', - 'Query stats during the sample:' + @LineFeed + - 'Executions: ' + CAST(qsNow.execution_count - (COALESCE(qsFirst.execution_count, 0)) AS NVARCHAR(100)) + @LineFeed + - 'Elapsed Time: ' + CAST(qsNow.total_elapsed_time - (COALESCE(qsFirst.total_elapsed_time, 0)) AS NVARCHAR(100)) + @LineFeed + - 'CPU Time: ' + CAST(qsNow.total_worker_time - (COALESCE(qsFirst.total_worker_time, 0)) AS NVARCHAR(100)) + @LineFeed + - 'Logical Reads: ' + CAST(qsNow.total_logical_reads - (COALESCE(qsFirst.total_logical_reads, 0)) AS NVARCHAR(100)) + @LineFeed + - 'Logical Writes: ' + CAST(qsNow.total_logical_writes - (COALESCE(qsFirst.total_logical_writes, 0)) AS NVARCHAR(100)) + @LineFeed + - 'CLR Time: ' + CAST(qsNow.total_clr_time - (COALESCE(qsFirst.total_clr_time, 0)) AS NVARCHAR(100)) + @LineFeed + - @LineFeed + @LineFeed + 'Query stats since ' + CONVERT(NVARCHAR(100), qsNow.creation_time ,121) + @LineFeed + - 'Executions: ' + CAST(qsNow.execution_count AS NVARCHAR(100)) + - CASE qsTotal.execution_count WHEN 0 THEN '' ELSE (' - Percent of Server Total: ' + CAST(CAST(100.0 * qsNow.execution_count / qsTotal.execution_count AS DECIMAL(6,2)) AS NVARCHAR(100)) + '%') END + @LineFeed + - 'Elapsed Time: ' + CAST(qsNow.total_elapsed_time AS NVARCHAR(100)) + - CASE qsTotal.total_elapsed_time WHEN 0 THEN '' ELSE (' - Percent of Server Total: ' + CAST(CAST(100.0 * qsNow.total_elapsed_time / qsTotal.total_elapsed_time AS DECIMAL(6,2)) AS NVARCHAR(100)) + '%') END + @LineFeed + - 'CPU Time: ' + CAST(qsNow.total_worker_time AS NVARCHAR(100)) + - CASE qsTotal.total_worker_time WHEN 0 THEN '' ELSE (' - Percent of Server Total: ' + CAST(CAST(100.0 * qsNow.total_worker_time / qsTotal.total_worker_time AS DECIMAL(6,2)) AS NVARCHAR(100)) + '%') END + @LineFeed + - 'Logical Reads: ' + CAST(qsNow.total_logical_reads AS NVARCHAR(100)) + - CASE qsTotal.total_logical_reads WHEN 0 THEN '' ELSE (' - Percent of Server Total: ' + CAST(CAST(100.0 * qsNow.total_logical_reads / qsTotal.total_logical_reads AS DECIMAL(6,2)) AS NVARCHAR(100)) + '%') END + @LineFeed + - 'Logical Writes: ' + CAST(qsNow.total_logical_writes AS NVARCHAR(100)) + - CASE qsTotal.total_logical_writes WHEN 0 THEN '' ELSE (' - Percent of Server Total: ' + CAST(CAST(100.0 * qsNow.total_logical_writes / qsTotal.total_logical_writes AS DECIMAL(6,2)) AS NVARCHAR(100)) + '%') END + @LineFeed + - 'CLR Time: ' + CAST(qsNow.total_clr_time AS NVARCHAR(100)) + - CASE qsTotal.total_clr_time WHEN 0 THEN '' ELSE (' - Percent of Server Total: ' + CAST(CAST(100.0 * qsNow.total_clr_time / qsTotal.total_clr_time AS DECIMAL(6,2)) AS NVARCHAR(100)) + '%') END + @LineFeed + - --@LineFeed + @LineFeed + 'Query hash: ' + CAST(qsNow.query_hash AS NVARCHAR(100)) + @LineFeed + - --@LineFeed + @LineFeed + 'Query plan hash: ' + CAST(qsNow.query_plan_hash AS NVARCHAR(100)) + - @LineFeed AS Details, - 'See the URL for tuning tips on why this query may be consuming resources.' AS HowToStopIt, - qp.query_plan, - QueryText = SUBSTRING(st.text, - (qsNow.statement_start_offset / 2) + 1, - ((CASE qsNow.statement_end_offset - WHEN -1 THEN DATALENGTH(st.text) - ELSE qsNow.statement_end_offset - END - qsNow.statement_start_offset) / 2) + 1), - qsNow.ID AS QueryStatsNowID, - qsFirst.ID AS QueryStatsFirstID, - qsNow.plan_handle AS PlanHandle, - qsNow.query_hash - FROM #QueryStats qsNow - INNER JOIN #QueryStats qsTotal ON qsTotal.Pass = 0 - LEFT OUTER JOIN #QueryStats qsFirst ON qsNow.[sql_handle] = qsFirst.[sql_handle] AND qsNow.statement_start_offset = qsFirst.statement_start_offset AND qsNow.statement_end_offset = qsFirst.statement_end_offset AND qsNow.plan_generation_num = qsFirst.plan_generation_num AND qsNow.plan_handle = qsFirst.plan_handle AND qsFirst.Pass = 1 - CROSS APPLY sys.dm_exec_sql_text(qsNow.sql_handle) AS st - CROSS APPLY sys.dm_exec_query_plan(qsNow.plan_handle) AS qp - WHERE qsNow.Points > 0 AND st.text IS NOT NULL AND qp.query_plan IS NOT NULL; - - UPDATE #BlitzFirstResults - SET DatabaseID = CAST(attr.value AS INT), - DatabaseName = DB_NAME(CAST(attr.value AS INT)) - FROM #BlitzFirstResults - CROSS APPLY sys.dm_exec_plan_attributes(#BlitzFirstResults.PlanHandle) AS attr - WHERE attr.attribute = 'dbid'; - - - END; /* IF DATEDIFF(ss, @FinishSampleTime, SYSDATETIMEOFFSET()) > 10 AND @CheckProcedureCache = 1 */ - - - RAISERROR('Analyzing changes between first and second passes of DMVs',10,1) WITH NOWAIT; - - /* Wait Stats - CheckID 6 */ - /* Compare the current wait stats to the sample we took at the start, and insert the top 10 waits. */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 6',10,1) WITH NOWAIT; - END - - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, DetailsInt) - SELECT TOP 10 6 AS CheckID, - 200 AS Priority, - 'Wait Stats' AS FindingGroup, - wNow.wait_type AS Finding, /* IF YOU CHANGE THIS, STUFF WILL BREAK. Other checks look for wait type names in the Finding field. See checks 11, 12 as example. */ - N'https://www.sqlskills.com/help/waits/' + LOWER(wNow.wait_type) + '/' AS URL, - 'For ' + CAST(((wNow.wait_time_ms - COALESCE(wBase.wait_time_ms,0)) / 1000) AS NVARCHAR(100)) + ' seconds over the last ' + CASE @Seconds WHEN 0 THEN (CAST(DATEDIFF(dd,@StartSampleTime,@FinishSampleTime) AS NVARCHAR(10)) + ' days') ELSE (CAST(@Seconds AS NVARCHAR(10)) + ' seconds') END + ', SQL Server was waiting on this particular bottleneck.' + @LineFeed + @LineFeed AS Details, - 'See the URL for more details on how to mitigate this wait type.' AS HowToStopIt, - ((wNow.wait_time_ms - COALESCE(wBase.wait_time_ms,0)) / 1000) AS DetailsInt - FROM #WaitStats wNow - LEFT OUTER JOIN #WaitStats wBase ON wNow.wait_type = wBase.wait_type AND wNow.SampleTime > wBase.SampleTime - WHERE wNow.wait_time_ms > (wBase.wait_time_ms + (.5 * (DATEDIFF(ss,@StartSampleTime,@FinishSampleTime)) * 1000)) /* Only look for things we've actually waited on for half of the time or more */ - ORDER BY (wNow.wait_time_ms - COALESCE(wBase.wait_time_ms,0)) DESC; - - /* Server Performance - Poison Wait Detected - CheckID 30 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 30',10,1) WITH NOWAIT; - END - - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, DetailsInt) - SELECT 30 AS CheckID, - 10 AS Priority, - 'Server Performance' AS FindingGroup, - 'Poison Wait Detected: ' + wNow.wait_type AS Finding, - N'https://www.brentozar.com/go/poison/#' + wNow.wait_type AS URL, - 'For ' + CAST(((wNow.wait_time_ms - COALESCE(wBase.wait_time_ms,0)) / 1000) AS NVARCHAR(100)) + ' seconds over the last ' + CASE @Seconds WHEN 0 THEN (CAST(DATEDIFF(dd,@StartSampleTime,@FinishSampleTime) AS NVARCHAR(10)) + ' days') ELSE (CAST(@Seconds AS NVARCHAR(10)) + ' seconds') END + ', SQL Server was waiting on this particular bottleneck.' + @LineFeed + @LineFeed AS Details, - 'See the URL for more details on how to mitigate this wait type.' AS HowToStopIt, - ((wNow.wait_time_ms - COALESCE(wBase.wait_time_ms,0)) / 1000) AS DetailsInt - FROM #WaitStats wNow - LEFT OUTER JOIN #WaitStats wBase ON wNow.wait_type = wBase.wait_type AND wNow.SampleTime > wBase.SampleTime - WHERE wNow.wait_type IN ('IO_QUEUE_LIMIT', 'IO_RETRY', 'LOG_RATE_GOVERNOR', 'POOL_LOG_RATE_GOVERNOR', 'PREEMPTIVE_DEBUG', 'RESMGR_THROTTLED', 'RESOURCE_SEMAPHORE', 'RESOURCE_SEMAPHORE_QUERY_COMPILE','SE_REPL_CATCHUP_THROTTLE','SE_REPL_COMMIT_ACK','SE_REPL_COMMIT_TURN','SE_REPL_ROLLBACK_ACK','SE_REPL_SLOW_SECONDARY_THROTTLE','THREADPOOL') - AND wNow.wait_time_ms > (wBase.wait_time_ms + 1000); - - - /* Server Performance - Slow Data File Reads - CheckID 11 */ - IF EXISTS (SELECT * FROM #BlitzFirstResults WHERE Finding LIKE 'PAGEIOLATCH%') - BEGIN - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 11',10,1) WITH NOWAIT; - END - - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, DatabaseID, DatabaseName) - SELECT TOP 10 11 AS CheckID, - 50 AS Priority, - 'Server Performance' AS FindingGroup, - 'Slow Data File Reads' AS Finding, - 'https://www.brentozar.com/go/slow/' AS URL, - 'Your server is experiencing PAGEIOLATCH% waits due to slow data file reads. This file is one of the reasons why.' + @LineFeed - + 'File: ' + fNow.PhysicalName + @LineFeed - + 'Number of reads during the sample: ' + CAST((fNow.num_of_reads - fBase.num_of_reads) AS NVARCHAR(20)) + @LineFeed - + 'Seconds spent waiting on storage for these reads: ' + CAST(((fNow.io_stall_read_ms - fBase.io_stall_read_ms) / 1000.0) AS NVARCHAR(20)) + @LineFeed - + 'Average read latency during the sample: ' + CAST(((fNow.io_stall_read_ms - fBase.io_stall_read_ms) / (fNow.num_of_reads - fBase.num_of_reads) ) AS NVARCHAR(20)) + ' milliseconds' + @LineFeed - + 'Microsoft guidance for data file read speed: 20ms or less.' + @LineFeed + @LineFeed AS Details, - 'See the URL for more details on how to mitigate this wait type.' AS HowToStopIt, - fNow.DatabaseID, - fNow.DatabaseName - FROM #FileStats fNow - INNER JOIN #FileStats fBase ON fNow.DatabaseID = fBase.DatabaseID AND fNow.FileID = fBase.FileID AND fNow.SampleTime > fBase.SampleTime AND fNow.num_of_reads > fBase.num_of_reads AND fNow.io_stall_read_ms > (fBase.io_stall_read_ms + 1000) - WHERE (fNow.io_stall_read_ms - fBase.io_stall_read_ms) / (fNow.num_of_reads - fBase.num_of_reads) >= @FileLatencyThresholdMS - AND fNow.TypeDesc = 'ROWS' - ORDER BY (fNow.io_stall_read_ms - fBase.io_stall_read_ms) / (fNow.num_of_reads - fBase.num_of_reads) DESC; - END; - - /* Server Performance - Slow Log File Writes - CheckID 12 */ - IF EXISTS (SELECT * FROM #BlitzFirstResults WHERE Finding LIKE 'WRITELOG%') - BEGIN - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 12',10,1) WITH NOWAIT; - END - - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, DatabaseID, DatabaseName) - SELECT TOP 10 12 AS CheckID, - 50 AS Priority, - 'Server Performance' AS FindingGroup, - 'Slow Log File Writes' AS Finding, - 'https://www.brentozar.com/go/slow/' AS URL, - 'Your server is experiencing WRITELOG waits due to slow log file writes. This file is one of the reasons why.' + @LineFeed - + 'File: ' + fNow.PhysicalName + @LineFeed - + 'Number of writes during the sample: ' + CAST((fNow.num_of_writes - fBase.num_of_writes) AS NVARCHAR(20)) + @LineFeed - + 'Seconds spent waiting on storage for these writes: ' + CAST(((fNow.io_stall_write_ms - fBase.io_stall_write_ms) / 1000.0) AS NVARCHAR(20)) + @LineFeed - + 'Average write latency during the sample: ' + CAST(((fNow.io_stall_write_ms - fBase.io_stall_write_ms) / (fNow.num_of_writes - fBase.num_of_writes) ) AS NVARCHAR(20)) + ' milliseconds' + @LineFeed - + 'Microsoft guidance for log file write speed: 3ms or less.' + @LineFeed + @LineFeed AS Details, - 'See the URL for more details on how to mitigate this wait type.' AS HowToStopIt, - fNow.DatabaseID, - fNow.DatabaseName - FROM #FileStats fNow - INNER JOIN #FileStats fBase ON fNow.DatabaseID = fBase.DatabaseID AND fNow.FileID = fBase.FileID AND fNow.SampleTime > fBase.SampleTime AND fNow.num_of_writes > fBase.num_of_writes AND fNow.io_stall_write_ms > (fBase.io_stall_write_ms + 1000) - WHERE (fNow.io_stall_write_ms - fBase.io_stall_write_ms) / (fNow.num_of_writes - fBase.num_of_writes) >= @FileLatencyThresholdMS - AND fNow.TypeDesc = 'LOG' - ORDER BY (fNow.io_stall_write_ms - fBase.io_stall_write_ms) / (fNow.num_of_writes - fBase.num_of_writes) DESC; - END; - - - /* SQL Server Internal Maintenance - Log File Growing - CheckID 13 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 13',10,1) WITH NOWAIT; - END - - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) - SELECT 13 AS CheckID, - 1 AS Priority, - 'SQL Server Internal Maintenance' AS FindingGroup, - 'Log File Growing' AS Finding, - 'https://www.brentozar.com/askbrent/file-growing/' AS URL, - 'Number of growths during the sample: ' + CAST(ps.value_delta AS NVARCHAR(20)) + @LineFeed - + 'Determined by sampling Perfmon counter ' + ps.object_name + ' - ' + ps.counter_name + @LineFeed AS Details, - 'Pre-grow data and log files during maintenance windows so that they do not grow during production loads. See the URL for more details.' AS HowToStopIt - FROM #PerfmonStats ps - WHERE ps.Pass = 2 - AND object_name = @ServiceName + ':Databases' - AND counter_name = 'Log Growths' - AND value_delta > 0; - - - /* SQL Server Internal Maintenance - Log File Shrinking - CheckID 14 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 14',10,1) WITH NOWAIT; - END - - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) - SELECT 14 AS CheckID, - 1 AS Priority, - 'SQL Server Internal Maintenance' AS FindingGroup, - 'Log File Shrinking' AS Finding, - 'https://www.brentozar.com/askbrent/file-shrinking/' AS URL, - 'Number of shrinks during the sample: ' + CAST(ps.value_delta AS NVARCHAR(20)) + @LineFeed - + 'Determined by sampling Perfmon counter ' + ps.object_name + ' - ' + ps.counter_name + @LineFeed AS Details, - 'Pre-grow data and log files during maintenance windows so that they do not grow during production loads. See the URL for more details.' AS HowToStopIt - FROM #PerfmonStats ps - WHERE ps.Pass = 2 - AND object_name = @ServiceName + ':Databases' - AND counter_name = 'Log Shrinks' - AND value_delta > 0; - - /* Query Problems - Compilations/Sec High - CheckID 15 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 15',10,1) WITH NOWAIT; - END - - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) - SELECT 15 AS CheckID, - 50 AS Priority, - 'Query Problems' AS FindingGroup, - 'Compilations/Sec High' AS Finding, - 'https://www.brentozar.com/askbrent/compilations/' AS URL, - 'Number of batch requests during the sample: ' + CAST(ps.value_delta AS NVARCHAR(20)) + @LineFeed - + 'Number of compilations during the sample: ' + CAST(psComp.value_delta AS NVARCHAR(20)) + @LineFeed - + 'For OLTP environments, Microsoft recommends that 90% of batch requests should hit the plan cache, and not be compiled from scratch. We are exceeding that threshold.' + @LineFeed AS Details, - 'To find the queries that are compiling, start with:' + @LineFeed - + 'sp_BlitzCache @SortOrder = ''recent compilations''' + @LineFeed - + 'If dynamic SQL or non-parameterized strings are involved, consider enabling Forced Parameterization. See the URL for more details.' AS HowToStopIt - FROM #PerfmonStats ps - INNER JOIN #PerfmonStats psComp ON psComp.Pass = 2 AND psComp.object_name = @ServiceName + ':SQL Statistics' AND psComp.counter_name = 'SQL Compilations/sec' AND psComp.value_delta > 0 - WHERE ps.Pass = 2 - AND ps.object_name = @ServiceName + ':SQL Statistics' - AND ps.counter_name = 'Batch Requests/sec' - AND psComp.value_delta > 75 /* Because sp_BlitzFirst does around 50 compilations and re-compilations */ - AND (psComp.value_delta > (10 * @Seconds) OR psComp.value_delta > ps.value_delta) /* Either doing 10 compilations per second, or more compilations than queries */ - AND (psComp.value_delta * 10) > ps.value_delta; /* Compilations are more than 10% of batch requests per second */ - - /* Query Problems - Re-Compilations/Sec High - CheckID 16 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 16',10,1) WITH NOWAIT; - END - - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) - SELECT 16 AS CheckID, - 50 AS Priority, - 'Query Problems' AS FindingGroup, - 'Re-Compilations/Sec High' AS Finding, - 'https://www.brentozar.com/askbrent/recompilations/' AS URL, - 'Number of batch requests during the sample: ' + CAST(ps.value_delta AS NVARCHAR(20)) + @LineFeed - + 'Number of recompilations during the sample: ' + CAST(psComp.value_delta AS NVARCHAR(20)) + @LineFeed - + 'More than 10% of our queries are being recompiled. This is typically due to statistics changing on objects.' + @LineFeed AS Details, - 'To find the queries that are being forced to recompile, start with:' + @LineFeed - + 'sp_BlitzCache @SortOrder = ''recent compilations''' + @LineFeed - + 'Examine those plans to find out which objects are changing so quickly that they hit the stats update threshold. See the URL for more details.' AS HowToStopIt - FROM #PerfmonStats ps - INNER JOIN #PerfmonStats psComp ON psComp.Pass = 2 AND psComp.object_name = @ServiceName + ':SQL Statistics' AND psComp.counter_name = 'SQL Re-Compilations/sec' AND psComp.value_delta > 0 - WHERE ps.Pass = 2 - AND ps.object_name = @ServiceName + ':SQL Statistics' - AND ps.counter_name = 'Batch Requests/sec' - AND psComp.value_delta > 75 /* Because sp_BlitzFirst does around 50 compilations and re-compilations */ - AND (psComp.value_delta > (10 * @Seconds) OR psComp.value_delta > ps.value_delta) /* Either doing 10 recompilations per second, or more recompilations than queries */ - AND (psComp.value_delta * 10) > ps.value_delta; /* Recompilations are more than 10% of batch requests per second */ - - /* Table Problems - Forwarded Fetches/Sec High - CheckID 29 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 29',10,1) WITH NOWAIT; - END - - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) - SELECT 29 AS CheckID, - 40 AS Priority, - 'Table Problems' AS FindingGroup, - 'Forwarded Fetches/Sec High' AS Finding, - 'https://www.brentozar.com/go/fetch/' AS URL, - CAST(ps.value_delta AS NVARCHAR(20)) + ' forwarded fetches (from SQLServer:Access Methods counter)' + @LineFeed - + 'Check your heaps: they need to be rebuilt, or they need a clustered index applied.' + @LineFeed AS Details, - 'Rebuild your heaps. If you use Ola Hallengren maintenance scripts, those do not rebuild heaps by default: https://www.brentozar.com/archive/2016/07/fix-forwarded-records/' AS HowToStopIt - FROM #PerfmonStats ps - INNER JOIN #PerfmonStats psComp ON psComp.Pass = 2 AND psComp.object_name = @ServiceName + ':Access Methods' AND psComp.counter_name = 'Forwarded Records/sec' AND psComp.value_delta > 100 - WHERE ps.Pass = 2 - AND ps.object_name = @ServiceName + ':Access Methods' - AND ps.counter_name = 'Forwarded Records/sec' - AND ps.value_delta > (100 * @Seconds); /* Ignore servers sitting idle */ - - /* Check for temp objects with high forwarded fetches. - This has to be done as dynamic SQL because we have to execute OBJECT_NAME inside TempDB. */ - IF EXISTS (SELECT * FROM #BlitzFirstResults WHERE CheckID = 29) - BEGIN - SET @StringToExecute = N' - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) - SELECT TOP 10 29 AS CheckID, - 40 AS Priority, - ''Table Problems'' AS FindingGroup, - ''Forwarded Fetches/Sec High: TempDB Object'' AS Finding, - ''https://www.brentozar.com/go/fetch/'' AS URL, - CAST(COALESCE(os.forwarded_fetch_count,0) - COALESCE(os_prior.forwarded_fetch_count,0) AS NVARCHAR(20)) + '' forwarded fetches on '' + - CASE WHEN OBJECT_NAME(os.object_id) IS NULL THEN ''an unknown table '' - WHEN LEN(OBJECT_NAME(os.object_id)) < 50 THEN ''a table variable, internal identifier '' + OBJECT_NAME(os.object_id) - ELSE ''a temp table '' + OBJECT_NAME(os.object_id) - END AS Details, - ''Look through your source code to find the object creating these objects, and tune the creation and population to reduce fetches. See the URL for details.'' AS HowToStopIt - FROM tempdb.sys.dm_db_index_operational_stats(DB_ID(''tempdb''), NULL, NULL, NULL) os - LEFT OUTER JOIN #TempdbOperationalStats os_prior ON os.object_id = os_prior.object_id - AND os.forwarded_fetch_count > os_prior.forwarded_fetch_count - WHERE os.database_id = DB_ID(''tempdb'') - AND os.forwarded_fetch_count - COALESCE(os_prior.forwarded_fetch_count,0) > 100 - ORDER BY os.forwarded_fetch_count DESC;' - - EXECUTE sp_executesql @StringToExecute; - END - - /* In-Memory OLTP - Garbage Collection in Progress - CheckID 31 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 31',10,1) WITH NOWAIT; - END - - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) - SELECT 31 AS CheckID, - 50 AS Priority, - 'In-Memory OLTP' AS FindingGroup, - 'Garbage Collection in Progress' AS Finding, - 'https://www.brentozar.com/go/garbage/' AS URL, - CAST(ps.value_delta AS NVARCHAR(50)) + ' rows processed (from SQL Server YYYY XTP Garbage Collection:Rows processed/sec counter)' + @LineFeed - + 'This can happen due to memory pressure (causing In-Memory OLTP to shrink its footprint) or' + @LineFeed - + 'due to transactional workloads that constantly insert/delete data.' AS Details, - 'Sadly, you cannot choose when garbage collection occurs. This is one of the many gotchas of Hekaton. Learn more: http://nedotter.com/archive/2016/04/row-version-lifecycle-for-in-memory-oltp/' AS HowToStopIt - FROM #PerfmonStats ps - INNER JOIN #PerfmonStats psComp ON psComp.Pass = 2 AND psComp.object_name LIKE '%XTP Garbage Collection' AND psComp.counter_name = 'Rows processed/sec' AND psComp.value_delta > 100 - WHERE ps.Pass = 2 - AND ps.object_name LIKE '%XTP Garbage Collection' - AND ps.counter_name = 'Rows processed/sec' - AND ps.value_delta > (100 * @Seconds); /* Ignore servers sitting idle */ - - /* In-Memory OLTP - Transactions Aborted - CheckID 32 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 32',10,1) WITH NOWAIT; - END - - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) - SELECT 32 AS CheckID, - 100 AS Priority, - 'In-Memory OLTP' AS FindingGroup, - 'Transactions Aborted' AS Finding, - 'https://www.brentozar.com/go/aborted/' AS URL, - CAST(ps.value_delta AS NVARCHAR(50)) + ' transactions aborted (from SQL Server YYYY XTP Transactions:Transactions aborted/sec counter)' + @LineFeed - + 'This may indicate that data is changing, or causing folks to retry their transactions, thereby increasing load.' AS Details, - 'Dig into your In-Memory OLTP transactions to figure out which ones are failing and being retried.' AS HowToStopIt - FROM #PerfmonStats ps - INNER JOIN #PerfmonStats psComp ON psComp.Pass = 2 AND psComp.object_name LIKE '%XTP Transactions' AND psComp.counter_name = 'Transactions aborted/sec' AND psComp.value_delta > 100 - WHERE ps.Pass = 2 - AND ps.object_name LIKE '%XTP Transactions' - AND ps.counter_name = 'Transactions aborted/sec' - AND ps.value_delta > (10 * @Seconds); /* Ignore servers sitting idle */ - - /* Query Problems - Suboptimal Plans/Sec High - CheckID 33 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 33',10,1) WITH NOWAIT; - END - - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) - SELECT 32 AS CheckID, - 100 AS Priority, - 'Query Problems' AS FindingGroup, - 'Suboptimal Plans/Sec High' AS Finding, - 'https://www.brentozar.com/go/suboptimal/' AS URL, - CAST(ps.value_delta AS NVARCHAR(50)) + ' plans reported in the ' + CAST(ps.instance_name AS NVARCHAR(100)) + ' workload group (from Workload GroupStats:Suboptimal plans/sec counter)' + @LineFeed - + 'Even if you are not using Resource Governor, it still tracks information about user queries, memory grants, etc.' AS Details, - 'Check out sp_BlitzCache to get more information about recent queries, or try sp_BlitzWho to see currently running queries.' AS HowToStopIt - FROM #PerfmonStats ps - INNER JOIN #PerfmonStats psComp ON psComp.Pass = 2 AND psComp.object_name = @ServiceName + ':Workload GroupStats' AND psComp.counter_name = 'Suboptimal plans/sec' AND psComp.value_delta > 100 - WHERE ps.Pass = 2 - AND ps.object_name = @ServiceName + ':Workload GroupStats' - AND ps.counter_name = 'Suboptimal plans/sec' - AND ps.value_delta > (10 * @Seconds); /* Ignore servers sitting idle */ - - /* Azure Performance - Database is Maxed Out - CheckID 41 */ - IF SERVERPROPERTY('EngineEdition') = 5 /*SERVERPROPERTY('Edition') = 'SQL Azure'*/ - BEGIN - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 41',10,1) WITH NOWAIT; - END - - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) - SELECT 41 AS CheckID, - 10 AS Priority, - 'Azure Performance' AS FindingGroup, - 'Database is Maxed Out' AS Finding, - 'https://www.brentozar.com/go/maxedout' AS URL, - N'At ' + CONVERT(NVARCHAR(100), s.end_time ,121) + N', your database approached (or hit) your DTU limits:' + @LineFeed - + N'Average CPU percent: ' + CAST(avg_cpu_percent AS NVARCHAR(50)) + @LineFeed - + N'Average data IO percent: ' + CAST(avg_data_io_percent AS NVARCHAR(50)) + @LineFeed - + N'Average log write percent: ' + CAST(avg_log_write_percent AS NVARCHAR(50)) + @LineFeed - + N'Max worker percent: ' + CAST(max_worker_percent AS NVARCHAR(50)) + @LineFeed - + N'Max session percent: ' + CAST(max_session_percent AS NVARCHAR(50)) AS Details, - 'Tune your queries or indexes with sp_BlitzCache or sp_BlitzIndex, or consider upgrading to a higher DTU level.' AS HowToStopIt - FROM sys.dm_db_resource_stats s - WHERE s.end_time >= DATEADD(MI, -5, GETDATE()) - AND (avg_cpu_percent > 90 - OR avg_data_io_percent >= 90 - OR avg_log_write_percent >=90 - OR max_worker_percent >= 90 - OR max_session_percent >= 90); - END - - /* Server Info - Batch Requests per Sec - CheckID 19 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 19',10,1) WITH NOWAIT; - END - - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, DetailsInt) - SELECT 19 AS CheckID, - 250 AS Priority, - 'Server Info' AS FindingGroup, - 'Batch Requests per Sec' AS Finding, - 'https://www.brentozar.com/go/measure' AS URL, - CAST(CAST(ps.value_delta AS MONEY) / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS NVARCHAR(20)) AS Details, - ps.value_delta / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS DetailsInt - FROM #PerfmonStats ps - INNER JOIN #PerfmonStats ps1 ON ps.object_name = ps1.object_name AND ps.counter_name = ps1.counter_name AND ps1.Pass = 1 - WHERE ps.Pass = 2 - AND ps.object_name = @ServiceName + ':SQL Statistics' - AND ps.counter_name = 'Batch Requests/sec'; - - - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','SQL Compilations/sec', NULL); - INSERT INTO #PerfmonCounters ([object_name],[counter_name],[instance_name]) VALUES (@ServiceName + ':SQL Statistics','SQL Re-Compilations/sec', NULL); - - /* Server Info - SQL Compilations/sec - CheckID 25 */ - IF @ExpertMode = 1 - BEGIN - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 25',10,1) WITH NOWAIT; - END - - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, DetailsInt) - SELECT 25 AS CheckID, - 250 AS Priority, - 'Server Info' AS FindingGroup, - 'SQL Compilations per Sec' AS Finding, - 'https://www.brentozar.com/go/measure' AS URL, - CAST(ps.value_delta / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS NVARCHAR(20)) AS Details, - ps.value_delta / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS DetailsInt - FROM #PerfmonStats ps - INNER JOIN #PerfmonStats ps1 ON ps.object_name = ps1.object_name AND ps.counter_name = ps1.counter_name AND ps1.Pass = 1 - WHERE ps.Pass = 2 - AND ps.object_name = @ServiceName + ':SQL Statistics' - AND ps.counter_name = 'SQL Compilations/sec'; - END - - /* Server Info - SQL Re-Compilations/sec - CheckID 26 */ - IF @ExpertMode = 1 - BEGIN - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 26',10,1) WITH NOWAIT; - END - - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, DetailsInt) - SELECT 26 AS CheckID, - 250 AS Priority, - 'Server Info' AS FindingGroup, - 'SQL Re-Compilations per Sec' AS Finding, - 'https://www.brentozar.com/go/measure' AS URL, - CAST(ps.value_delta / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS NVARCHAR(20)) AS Details, - ps.value_delta / (DATEDIFF(ss, ps1.SampleTime, ps.SampleTime)) AS DetailsInt - FROM #PerfmonStats ps - INNER JOIN #PerfmonStats ps1 ON ps.object_name = ps1.object_name AND ps.counter_name = ps1.counter_name AND ps1.Pass = 1 - WHERE ps.Pass = 2 - AND ps.object_name = @ServiceName + ':SQL Statistics' - AND ps.counter_name = 'SQL Re-Compilations/sec'; - END - - /* Server Info - Wait Time per Core per Sec - CheckID 20 */ - IF @Seconds > 0 - BEGIN; - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 20',10,1) WITH NOWAIT; - END; - - WITH waits1(SampleTime, waits_ms) AS (SELECT SampleTime, SUM(ws1.wait_time_ms) FROM #WaitStats ws1 WHERE ws1.Pass = 1 GROUP BY SampleTime), - waits2(SampleTime, waits_ms) AS (SELECT SampleTime, SUM(ws2.wait_time_ms) FROM #WaitStats ws2 WHERE ws2.Pass = 2 GROUP BY SampleTime), - cores(cpu_count) AS (SELECT SUM(1) FROM sys.dm_os_schedulers WHERE status = 'VISIBLE ONLINE' AND is_online = 1) - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, DetailsInt) - SELECT 20 AS CheckID, - 250 AS Priority, - 'Server Info' AS FindingGroup, - 'Wait Time per Core per Sec' AS Finding, - 'https://www.brentozar.com/go/measure' AS URL, - CAST((CAST(waits2.waits_ms - waits1.waits_ms AS MONEY)) / 1000 / i.cpu_count / ISNULL(NULLIF(DATEDIFF(ss, waits1.SampleTime, waits2.SampleTime), 0), 1) AS NVARCHAR(20)) AS Details, - (waits2.waits_ms - waits1.waits_ms) / 1000 / i.cpu_count / ISNULL(NULLIF(DATEDIFF(ss, waits1.SampleTime, waits2.SampleTime), 0), 1) AS DetailsInt - FROM cores i - CROSS JOIN waits1 - CROSS JOIN waits2; - END; - - IF @Seconds > 0 - BEGIN - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) - SELECT - 47 AS CheckId, - 50 AS Priority, - 'Query Problems' AS FindingsGroup, - 'High Percentage Of Runnable Queries' AS Finding, - 'https://erikdarlingdata.com/go/RunnableQueue/' AS URL, - 'On the ' - + CASE WHEN y.pass = 1 - THEN '1st' - ELSE '2nd' - END - + ' pass, ' - + RTRIM(y.runnable_pct) - + '% of your queries were waiting to get on a CPU to run. ' - + ' This can indicate CPU pressure.' - FROM - ( - SELECT - 2 AS pass, - x.total, - x.runnable, - CONVERT(decimal(5,2), - ( - x.runnable / - (1. * NULLIF(x.total, 0)) - ) - ) * 100. AS runnable_pct - FROM - ( - SELECT - COUNT_BIG(*) AS total, - SUM(CASE WHEN status = 'runnable' - THEN 1 - ELSE 0 - END) AS runnable - FROM sys.dm_exec_requests - WHERE session_id > 50 - ) AS x - ) AS y - WHERE y.runnable_pct > 20.; - END - - /* If we're waiting 30+ seconds, run these checks at the end. - We get this data from the ring buffers, and it's only updated once per minute, so might - as well get it now - whereas if we're checking 30+ seconds, it might get updated by the - end of our sp_BlitzFirst session. */ - IF @Seconds >= 30 - BEGIN - /* Server Performance - High CPU Utilization CheckID 24 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 24',10,1) WITH NOWAIT; - END - - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) - SELECT 24, 50, 'Server Performance', 'High CPU Utilization', CAST(100 - SystemIdle AS NVARCHAR(20)) + N'%. Ring buffer details: ' + CAST(record AS NVARCHAR(4000)), 100 - SystemIdle, 'https://www.brentozar.com/go/cpu' - FROM ( - SELECT record, - record.value('(./Record/SchedulerMonitorEvent/SystemHealth/SystemIdle)[1]', 'int') AS SystemIdle - FROM ( - SELECT TOP 1 CONVERT(XML, record) AS record - FROM sys.dm_os_ring_buffers - WHERE ring_buffer_type = N'RING_BUFFER_SCHEDULER_MONITOR' - AND record LIKE '%%' - ORDER BY timestamp DESC) AS rb - ) AS y - WHERE 100 - SystemIdle >= 50; - - /* Server Performance - CPU Utilization CheckID 23 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 23',10,1) WITH NOWAIT; - END - - INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) - SELECT 23, 250, 'Server Info', 'CPU Utilization', CAST(100 - SystemIdle AS NVARCHAR(20)) + N'%. Ring buffer details: ' + CAST(record AS NVARCHAR(4000)), 100 - SystemIdle, 'https://www.brentozar.com/go/cpu' - FROM ( - SELECT record, - record.value('(./Record/SchedulerMonitorEvent/SystemHealth/SystemIdle)[1]', 'int') AS SystemIdle - FROM ( - SELECT TOP 1 CONVERT(XML, record) AS record - FROM sys.dm_os_ring_buffers - WHERE ring_buffer_type = N'RING_BUFFER_SCHEDULER_MONITOR' - AND record LIKE '%%' - ORDER BY timestamp DESC) AS rb - ) AS y; - - END; /* IF @Seconds >= 30 */ - - IF /* Let people on <2016 know about the thread time column */ - ( - @Seconds > 0 - AND @total_cpu_usage = 0 - ) - BEGIN - INSERT INTO - #BlitzFirstResults - ( - CheckID, - Priority, - FindingsGroup, - Finding, - Details, - URL - ) - SELECT - 48, - 254, - N'Informational', - N'Thread Time comes from the plan cache in versions earlier than 2016, and is not as reliable', - N'The oldest plan in your cache is from ' + - CONVERT(nvarchar(30), MIN(s.creation_time)) + - N' and your server was last restarted on ' + - CONVERT(nvarchar(30), MAX(o.sqlserver_start_time)), - N'https://docs.microsoft.com/en-us/sql/relational-databases/system-dynamic-management-views/sys-dm-os-schedulers-transact-sql' - FROM sys.dm_exec_query_stats AS s - CROSS JOIN sys.dm_os_sys_info AS o - OPTION(RECOMPILE); - END /* Let people on <2016 know about the thread time column */ - - /* If we didn't find anything, apologize. */ - IF NOT EXISTS (SELECT * FROM #BlitzFirstResults WHERE Priority < 250) - BEGIN - - INSERT INTO #BlitzFirstResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - VALUES ( -1 , - 1 , - 'No Problems Found' , - 'From Your Community Volunteers' , - 'http://FirstResponderKit.org/' , - 'Try running our more in-depth checks with sp_Blitz, or there may not be an unusual SQL Server performance problem. ' - ); - - END; /*IF NOT EXISTS (SELECT * FROM #BlitzFirstResults) */ - - /* Add credits for the nice folks who put so much time into building and maintaining this for free: */ - INSERT INTO #BlitzFirstResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - VALUES ( -1 , - 255 , - 'Thanks!' , - 'From Your Community Volunteers' , - 'http://FirstResponderKit.org/' , - 'To get help or add your own contributions, join us at http://FirstResponderKit.org.' - ); - - INSERT INTO #BlitzFirstResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - - ) - VALUES ( -1 , - 0 , - 'sp_BlitzFirst ' + CAST(CONVERT(DATETIMEOFFSET, @VersionDate, 102) AS VARCHAR(100)), - 'From Your Community Volunteers' , - 'http://FirstResponderKit.org/' , - 'We hope you found this tool useful.' - ); - - /* Outdated sp_BlitzFirst - sp_BlitzFirst is Over 6 Months Old - CheckID 27 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 27',10,1) WITH NOWAIT; - END - - IF DATEDIFF(MM, @VersionDate, SYSDATETIMEOFFSET()) > 6 - BEGIN - INSERT INTO #BlitzFirstResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 27 AS CheckID , - 0 AS Priority , - 'Outdated sp_BlitzFirst' AS FindingsGroup , - 'sp_BlitzFirst is Over 6 Months Old' AS Finding , - 'http://FirstResponderKit.org/' AS URL , - 'Some things get better with age, like fine wine and your T-SQL. However, sp_BlitzFirst is not one of those things - time to go download the current one.' AS Details; - END; - - IF @CheckServerInfo = 0 /* Github #1680 */ - BEGIN - DELETE #BlitzFirstResults - WHERE FindingsGroup = 'Server Info'; - END - - RAISERROR('Analysis finished, outputting results',10,1) WITH NOWAIT; - - - /* If they want to run sp_BlitzCache and export to table, go for it. */ - IF @OutputTableNameBlitzCache IS NOT NULL - AND @OutputDatabaseName IS NOT NULL - AND @OutputSchemaName IS NOT NULL - AND EXISTS ( SELECT * - FROM sys.databases - WHERE QUOTENAME([name]) = @OutputDatabaseName) - BEGIN - - - RAISERROR('Calling sp_BlitzCache',10,1) WITH NOWAIT; - - - /* If they have an newer version of sp_BlitzCache that supports @MinutesBack and @CheckDateOverride */ - IF EXISTS (SELECT * FROM sys.objects o - INNER JOIN sys.parameters pMB ON o.object_id = pMB.object_id AND pMB.name = '@MinutesBack' - INNER JOIN sys.parameters pCDO ON o.object_id = pCDO.object_id AND pCDO.name = '@CheckDateOverride' - WHERE o.name = 'sp_BlitzCache') - BEGIN - /* Get the most recent sp_BlitzCache execution before this one - don't use sp_BlitzFirst because user logs are added in there at any time */ - SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''' - + @OutputSchemaName + ''' AND QUOTENAME(TABLE_NAME) = ''' - + QUOTENAME(@OutputTableNameBlitzCache) + ''') SELECT TOP 1 @BlitzCacheMinutesBack = DATEDIFF(MI,CheckDate,SYSDATETIMEOFFSET()) FROM ' - + @OutputDatabaseName + '.' - + @OutputSchemaName + '.' - + QUOTENAME(@OutputTableNameBlitzCache) - + ' WHERE ServerName = ''' + CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128)) + ''' ORDER BY CheckDate DESC;'; - EXEC sp_executesql @StringToExecute, N'@BlitzCacheMinutesBack INT OUTPUT', @BlitzCacheMinutesBack OUTPUT; - - /* If there's no data, let's just analyze the last 15 minutes of the plan cache */ - IF @BlitzCacheMinutesBack IS NULL OR @BlitzCacheMinutesBack < 1 OR @BlitzCacheMinutesBack > 60 - SET @BlitzCacheMinutesBack = 15; - - IF(@OutputType = 'NONE') - BEGIN - EXEC sp_BlitzCache - @OutputDatabaseName = @UnquotedOutputDatabaseName, - @OutputSchemaName = @UnquotedOutputSchemaName, - @OutputTableName = @OutputTableNameBlitzCache, - @CheckDateOverride = @StartSampleTime, - @SortOrder = 'all', - @SkipAnalysis = @BlitzCacheSkipAnalysis, - @MinutesBack = @BlitzCacheMinutesBack, - @Debug = @Debug, - @OutputType = @OutputType - ; - END; - ELSE - BEGIN - - EXEC sp_BlitzCache - @OutputDatabaseName = @UnquotedOutputDatabaseName, - @OutputSchemaName = @UnquotedOutputSchemaName, - @OutputTableName = @OutputTableNameBlitzCache, - @CheckDateOverride = @StartSampleTime, - @SortOrder = 'all', - @SkipAnalysis = @BlitzCacheSkipAnalysis, - @MinutesBack = @BlitzCacheMinutesBack, - @Debug = @Debug - ; - END; - - /* Delete history older than @OutputTableRetentionDays */ - SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' - + @OutputSchemaName + ''') DELETE ' - + @OutputDatabaseName + '.' - + @OutputSchemaName + '.' - + QUOTENAME(@OutputTableNameBlitzCache) - + ' WHERE ServerName = @SrvName AND CheckDate < @CheckDate;'; - EXEC sp_executesql @StringToExecute, - N'@SrvName NVARCHAR(128), @CheckDate date', - @LocalServerName, @OutputTableCleanupDate; - - - END; - - ELSE - BEGIN - /* No sp_BlitzCache found, or it's outdated - CheckID 36 */ - IF (@Debug = 1) - BEGIN - RAISERROR('Running CheckID 36',10,1) WITH NOWAIT; - END - - INSERT INTO #BlitzFirstResults - ( CheckID , - Priority , - FindingsGroup , - Finding , - URL , - Details - ) - SELECT 36 AS CheckID , - 0 AS Priority , - 'Outdated or Missing sp_BlitzCache' AS FindingsGroup , - 'Update Your sp_BlitzCache' AS Finding , - 'http://FirstResponderKit.org/' AS URL , - 'You passed in @OutputTableNameBlitzCache, but we need a newer version of sp_BlitzCache in master or the current database.' AS Details; - END; - - RAISERROR('sp_BlitzCache Finished',10,1) WITH NOWAIT; - - END; /* End running sp_BlitzCache */ - - /* @OutputTableName lets us export the results to a permanent table */ - IF @OutputDatabaseName IS NOT NULL - AND @OutputSchemaName IS NOT NULL - AND @OutputTableName IS NOT NULL - AND @OutputTableName NOT LIKE '#%' - AND EXISTS ( SELECT * - FROM sys.databases - WHERE QUOTENAME([name]) = @OutputDatabaseName) - BEGIN - SET @StringToExecute = 'USE ' - + @OutputDatabaseName - + '; IF EXISTS(SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' - + @OutputSchemaName - + ''') AND NOT EXISTS (SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''' - + @OutputSchemaName + ''' AND QUOTENAME(TABLE_NAME) = ''' - + @OutputTableName + ''') CREATE TABLE ' - + @OutputSchemaName + '.' - + @OutputTableName - + ' (ID INT IDENTITY(1,1) NOT NULL, - ServerName NVARCHAR(128), - CheckDate DATETIMEOFFSET, - CheckID INT NOT NULL, - Priority TINYINT NOT NULL, - FindingsGroup VARCHAR(50) NOT NULL, - Finding VARCHAR(200) NOT NULL, - URL VARCHAR(200) NOT NULL, - Details NVARCHAR(4000) NULL, - HowToStopIt [XML] NULL, - QueryPlan [XML] NULL, - QueryText NVARCHAR(MAX) NULL, - StartTime DATETIMEOFFSET NULL, - LoginName NVARCHAR(128) NULL, - NTUserName NVARCHAR(128) NULL, - OriginalLoginName NVARCHAR(128) NULL, - ProgramName NVARCHAR(128) NULL, - HostName NVARCHAR(128) NULL, - DatabaseID INT NULL, - DatabaseName NVARCHAR(128) NULL, - OpenTransactionCount INT NULL, - DetailsInt INT NULL, - QueryHash BINARY(8) NULL, - JoinKey AS ServerName + CAST(CheckDate AS NVARCHAR(50)), - PRIMARY KEY CLUSTERED (ID ASC));'; - - EXEC(@StringToExecute); - - /* If the table doesn't have the new QueryHash column, add it. See Github #2162. */ - SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableName; - SET @StringToExecute = N'IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns - WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''QueryHash'') - ALTER TABLE ' + @ObjectFullName + N' ADD QueryHash BINARY(8) NULL;'; - EXEC(@StringToExecute); - - /* If the table doesn't have the new JoinKey computed column, add it. See Github #2164. */ - SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableName; - SET @StringToExecute = N'IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns - WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''JoinKey'') - ALTER TABLE ' + @ObjectFullName + N' ADD JoinKey AS ServerName + CAST(CheckDate AS NVARCHAR(50));'; - EXEC(@StringToExecute); - - SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' - + @OutputSchemaName + ''') INSERT ' - + @OutputDatabaseName + '.' - + @OutputSchemaName + '.' - + @OutputTableName - + ' (ServerName, CheckDate, CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, QueryText, StartTime, LoginName, NTUserName, OriginalLoginName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, DetailsInt, QueryHash) SELECT ' - + ' @SrvName, @CheckDate, CheckID, Priority, FindingsGroup, Finding, URL, LEFT(Details,4000), HowToStopIt, QueryPlan, QueryText, StartTime, LoginName, NTUserName, OriginalLoginName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, DetailsInt, QueryHash FROM #BlitzFirstResults ORDER BY Priority , FindingsGroup , Finding , Details'; - - EXEC sp_executesql @StringToExecute, - N'@SrvName NVARCHAR(128), @CheckDate datetimeoffset', - @LocalServerName, @StartSampleTime; - - /* Delete history older than @OutputTableRetentionDays */ - SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' - + @OutputSchemaName + ''') DELETE ' - + @OutputDatabaseName + '.' - + @OutputSchemaName + '.' - + @OutputTableName - + ' WHERE ServerName = @SrvName AND CheckDate < @CheckDate ;'; - - EXEC sp_executesql @StringToExecute, - N'@SrvName NVARCHAR(128), @CheckDate date', - @LocalServerName, @OutputTableCleanupDate; - - END; - ELSE IF (SUBSTRING(@OutputTableName, 2, 2) = '##') - BEGIN - SET @StringToExecute = N' IF (OBJECT_ID(''tempdb..' - + @OutputTableName - + ''') IS NULL) CREATE TABLE ' - + @OutputTableName - + ' (ID INT IDENTITY(1,1) NOT NULL, - ServerName NVARCHAR(128), - CheckDate DATETIMEOFFSET, - CheckID INT NOT NULL, - Priority TINYINT NOT NULL, - FindingsGroup VARCHAR(50) NOT NULL, - Finding VARCHAR(200) NOT NULL, - URL VARCHAR(200) NOT NULL, - Details NVARCHAR(4000) NULL, - HowToStopIt [XML] NULL, - QueryPlan [XML] NULL, - QueryText NVARCHAR(MAX) NULL, - StartTime DATETIMEOFFSET NULL, - LoginName NVARCHAR(128) NULL, - NTUserName NVARCHAR(128) NULL, - OriginalLoginName NVARCHAR(128) NULL, - ProgramName NVARCHAR(128) NULL, - HostName NVARCHAR(128) NULL, - DatabaseID INT NULL, - DatabaseName NVARCHAR(128) NULL, - OpenTransactionCount INT NULL, - DetailsInt INT NULL, - QueryHash BINARY(8) NULL, - JoinKey AS ServerName + CAST(CheckDate AS NVARCHAR(50)), - PRIMARY KEY CLUSTERED (ID ASC));' - + ' INSERT ' - + @OutputTableName - + ' (ServerName, CheckDate, CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt, QueryPlan, QueryText, StartTime, LoginName, NTUserName, OriginalLoginName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, DetailsInt) SELECT ' - + ' @SrvName, @CheckDate, CheckID, Priority, FindingsGroup, Finding, URL, LEFT(Details,4000), HowToStopIt, QueryPlan, QueryText, StartTime, LoginName, NTUserName, OriginalLoginName, ProgramName, HostName, DatabaseID, DatabaseName, OpenTransactionCount, DetailsInt FROM #BlitzFirstResults ORDER BY Priority , FindingsGroup , Finding , Details'; - - EXEC sp_executesql @StringToExecute, - N'@SrvName NVARCHAR(128), @CheckDate datetimeoffset', - @LocalServerName, @StartSampleTime; - END; - ELSE IF (SUBSTRING(@OutputTableName, 2, 1) = '#') - BEGIN - RAISERROR('Due to the nature of Dymamic SQL, only global (i.e. double pound (##)) temp tables are supported for @OutputTableName', 16, 0); - END; - - /* @OutputTableNameFileStats lets us export the results to a permanent table */ - IF @OutputDatabaseName IS NOT NULL - AND @OutputSchemaName IS NOT NULL - AND @OutputTableNameFileStats IS NOT NULL - AND @OutputTableNameFileStats NOT LIKE '#%' - AND EXISTS ( SELECT * - FROM sys.databases - WHERE QUOTENAME([name]) = @OutputDatabaseName) - BEGIN - /* Create the table */ - SET @StringToExecute = 'USE ' - + @OutputDatabaseName - + '; IF EXISTS(SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' - + @OutputSchemaName - + ''') AND NOT EXISTS (SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''' - + @OutputSchemaName + ''' AND QUOTENAME(TABLE_NAME) = ''' - + @OutputTableNameFileStats + ''') CREATE TABLE ' - + @OutputSchemaName + '.' - + @OutputTableNameFileStats - + ' (ID INT IDENTITY(1,1) NOT NULL, - ServerName NVARCHAR(128), - CheckDate DATETIMEOFFSET, - DatabaseID INT NOT NULL, - FileID INT NOT NULL, - DatabaseName NVARCHAR(256) , - FileLogicalName NVARCHAR(256) , - TypeDesc NVARCHAR(60) , - SizeOnDiskMB BIGINT , - io_stall_read_ms BIGINT , - num_of_reads BIGINT , - bytes_read BIGINT , - io_stall_write_ms BIGINT , - num_of_writes BIGINT , - bytes_written BIGINT, - PhysicalName NVARCHAR(520) , - PRIMARY KEY CLUSTERED (ID ASC));'; - - EXEC(@StringToExecute); - - SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableNameFileStats_View; - - /* If the view exists without the most recently added columns, drop it. See Github #2162. */ - IF OBJECT_ID(@ObjectFullName) IS NOT NULL - BEGIN - SET @StringToExecute = N'USE ' + @OutputDatabaseName + N'; IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns - WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''JoinKey'') - DROP VIEW ' + @OutputSchemaName + N'.' + @OutputTableNameFileStats_View + N';'; - - EXEC(@StringToExecute); - END - - /* Create the view */ - IF OBJECT_ID(@ObjectFullName) IS NULL - BEGIN - SET @StringToExecute = 'USE ' - + @OutputDatabaseName - + '; EXEC (''CREATE VIEW ' - + @OutputSchemaName + '.' - + @OutputTableNameFileStats_View + ' AS ' + @LineFeed - + 'WITH RowDates as' + @LineFeed - + '(' + @LineFeed - + ' SELECT ' + @LineFeed - + ' ROW_NUMBER() OVER (ORDER BY [ServerName], [CheckDate]) ID,' + @LineFeed - + ' [CheckDate]' + @LineFeed - + ' FROM ' + @OutputSchemaName + '.' + @OutputTableNameFileStats + '' + @LineFeed - + ' GROUP BY [ServerName], [CheckDate]' + @LineFeed - + '),' + @LineFeed - + 'CheckDates as' + @LineFeed - + '(' + @LineFeed - + ' SELECT ThisDate.CheckDate,' + @LineFeed - + ' LastDate.CheckDate as PreviousCheckDate' + @LineFeed - + ' FROM RowDates ThisDate' + @LineFeed - + ' JOIN RowDates LastDate' + @LineFeed - + ' ON ThisDate.ID = LastDate.ID + 1' + @LineFeed - + ')' + @LineFeed - + ' SELECT f.ServerName,' + @LineFeed - + ' f.CheckDate,' + @LineFeed - + ' f.DatabaseID,' + @LineFeed - + ' f.DatabaseName,' + @LineFeed - + ' f.FileID,' + @LineFeed - + ' f.FileLogicalName,' + @LineFeed - + ' f.TypeDesc,' + @LineFeed - + ' f.PhysicalName,' + @LineFeed - + ' f.SizeOnDiskMB,' + @LineFeed - + ' DATEDIFF(ss, fPrior.CheckDate, f.CheckDate) AS ElapsedSeconds,' + @LineFeed - + ' (f.SizeOnDiskMB - fPrior.SizeOnDiskMB) AS SizeOnDiskMBgrowth,' + @LineFeed - + ' (f.io_stall_read_ms - fPrior.io_stall_read_ms) AS io_stall_read_ms,' + @LineFeed - + ' io_stall_read_ms_average = CASE' + @LineFeed - + ' WHEN(f.num_of_reads - fPrior.num_of_reads) = 0' + @LineFeed - + ' THEN 0' + @LineFeed - + ' ELSE(f.io_stall_read_ms - fPrior.io_stall_read_ms) / (f.num_of_reads - fPrior.num_of_reads)' + @LineFeed - + ' END,' + @LineFeed - + ' (f.num_of_reads - fPrior.num_of_reads) AS num_of_reads,' + @LineFeed - + ' (f.bytes_read - fPrior.bytes_read) / 1024.0 / 1024.0 AS megabytes_read,' + @LineFeed - + ' (f.io_stall_write_ms - fPrior.io_stall_write_ms) AS io_stall_write_ms,' + @LineFeed - + ' io_stall_write_ms_average = CASE' + @LineFeed - + ' WHEN(f.num_of_writes - fPrior.num_of_writes) = 0' + @LineFeed - + ' THEN 0' + @LineFeed - + ' ELSE(f.io_stall_write_ms - fPrior.io_stall_write_ms) / (f.num_of_writes - fPrior.num_of_writes)' + @LineFeed - + ' END,' + @LineFeed - + ' (f.num_of_writes - fPrior.num_of_writes) AS num_of_writes,' + @LineFeed - + ' (f.bytes_written - fPrior.bytes_written) / 1024.0 / 1024.0 AS megabytes_written, ' + @LineFeed - + ' f.ServerName + CAST(f.CheckDate AS NVARCHAR(50)) AS JoinKey' + @LineFeed - + ' FROM ' + @OutputSchemaName + '.' + @OutputTableNameFileStats + ' f' + @LineFeed - + ' INNER HASH JOIN CheckDates DATES ON f.CheckDate = DATES.CheckDate' + @LineFeed - + ' INNER JOIN ' + @OutputSchemaName + '.' + @OutputTableNameFileStats + ' fPrior ON f.ServerName = fPrior.ServerName' + @LineFeed - + ' AND f.DatabaseID = fPrior.DatabaseID' + @LineFeed - + ' AND f.FileID = fPrior.FileID' + @LineFeed - + ' AND fPrior.CheckDate = DATES.PreviousCheckDate' + @LineFeed - + '' + @LineFeed - + ' WHERE f.num_of_reads >= fPrior.num_of_reads' + @LineFeed - + ' AND f.num_of_writes >= fPrior.num_of_writes' + @LineFeed - + ' AND DATEDIFF(MI, fPrior.CheckDate, f.CheckDate) BETWEEN 1 AND 60;'')' - - EXEC(@StringToExecute); - END; - - - SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' - + @OutputSchemaName + ''') INSERT ' - + @OutputDatabaseName + '.' - + @OutputSchemaName + '.' - + @OutputTableNameFileStats - + ' (ServerName, CheckDate, DatabaseID, FileID, DatabaseName, FileLogicalName, TypeDesc, SizeOnDiskMB, io_stall_read_ms, num_of_reads, bytes_read, io_stall_write_ms, num_of_writes, bytes_written, PhysicalName) SELECT ' - + ' @SrvName, @CheckDate, DatabaseID, FileID, DatabaseName, FileLogicalName, TypeDesc, SizeOnDiskMB, io_stall_read_ms, num_of_reads, bytes_read, io_stall_write_ms, num_of_writes, bytes_written, PhysicalName FROM #FileStats WHERE Pass = 2'; - - EXEC sp_executesql @StringToExecute, - N'@SrvName NVARCHAR(128), @CheckDate datetimeoffset', - @LocalServerName, @StartSampleTime; - - /* Delete history older than @OutputTableRetentionDays */ - SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' - + @OutputSchemaName + ''') DELETE ' - + @OutputDatabaseName + '.' - + @OutputSchemaName + '.' - + @OutputTableNameFileStats - + ' WHERE ServerName = @SrvName AND CheckDate < @CheckDate ;'; - - EXEC sp_executesql @StringToExecute, - N'@SrvName NVARCHAR(128), @CheckDate date', - @LocalServerName, @OutputTableCleanupDate; - - END; - ELSE IF (SUBSTRING(@OutputTableNameFileStats, 2, 2) = '##') - BEGIN - SET @StringToExecute = N' IF (OBJECT_ID(''tempdb..' - + @OutputTableNameFileStats - + ''') IS NULL) CREATE TABLE ' - + @OutputTableNameFileStats - + ' (ID INT IDENTITY(1,1) NOT NULL, - ServerName NVARCHAR(128), - CheckDate DATETIMEOFFSET, - DatabaseID INT NOT NULL, - FileID INT NOT NULL, - DatabaseName NVARCHAR(256) , - FileLogicalName NVARCHAR(256) , - TypeDesc NVARCHAR(60) , - SizeOnDiskMB BIGINT , - io_stall_read_ms BIGINT , - num_of_reads BIGINT , - bytes_read BIGINT , - io_stall_write_ms BIGINT , - num_of_writes BIGINT , - bytes_written BIGINT, - PhysicalName NVARCHAR(520) , - DetailsInt INT NULL, - PRIMARY KEY CLUSTERED (ID ASC));' - + ' INSERT ' - + @OutputTableNameFileStats - + ' (ServerName, CheckDate, DatabaseID, FileID, DatabaseName, FileLogicalName, TypeDesc, SizeOnDiskMB, io_stall_read_ms, num_of_reads, bytes_read, io_stall_write_ms, num_of_writes, bytes_written, PhysicalName) SELECT ' - + ' @SrvName, @CheckDate, DatabaseID, FileID, DatabaseName, FileLogicalName, TypeDesc, SizeOnDiskMB, io_stall_read_ms, num_of_reads, bytes_read, io_stall_write_ms, num_of_writes, bytes_written, PhysicalName FROM #FileStats WHERE Pass = 2'; - - EXEC sp_executesql @StringToExecute, - N'@SrvName NVARCHAR(128), @CheckDate datetimeoffset', - @LocalServerName, @StartSampleTime; - END; - ELSE IF (SUBSTRING(@OutputTableNameFileStats, 2, 1) = '#') - BEGIN - RAISERROR('Due to the nature of Dymamic SQL, only global (i.e. double pound (##)) temp tables are supported for @OutputTableName', 16, 0); - END; - - - /* @OutputTableNamePerfmonStats lets us export the results to a permanent table */ - IF @OutputDatabaseName IS NOT NULL - AND @OutputSchemaName IS NOT NULL - AND @OutputTableNamePerfmonStats IS NOT NULL - AND @OutputTableNamePerfmonStats NOT LIKE '#%' - AND EXISTS ( SELECT * - FROM sys.databases - WHERE QUOTENAME([name]) = @OutputDatabaseName) - BEGIN - /* Create the table */ - SET @StringToExecute = 'USE ' - + @OutputDatabaseName - + '; IF EXISTS(SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' - + @OutputSchemaName - + ''') AND NOT EXISTS (SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''' - + @OutputSchemaName + ''' AND QUOTENAME(TABLE_NAME) = ''' - + @OutputTableNamePerfmonStats + ''') CREATE TABLE ' - + @OutputSchemaName + '.' - + @OutputTableNamePerfmonStats - + ' (ID INT IDENTITY(1,1) NOT NULL, - ServerName NVARCHAR(128), - CheckDate DATETIMEOFFSET, - [object_name] NVARCHAR(128) NOT NULL, - [counter_name] NVARCHAR(128) NOT NULL, - [instance_name] NVARCHAR(128) NULL, - [cntr_value] BIGINT NULL, - [cntr_type] INT NOT NULL, - [value_delta] BIGINT NULL, - [value_per_second] DECIMAL(18,2) NULL, - PRIMARY KEY CLUSTERED (ID ASC));'; - - EXEC(@StringToExecute); - - SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableNamePerfmonStats_View; - - /* If the view exists without the most recently added columns, drop it. See Github #2162. */ - IF OBJECT_ID(@ObjectFullName) IS NOT NULL - BEGIN - SET @StringToExecute = N'USE ' + @OutputDatabaseName + N'; IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns - WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''JoinKey'') - DROP VIEW ' + @OutputSchemaName + N'.' + @OutputTableNamePerfmonStats_View + N';'; - - EXEC(@StringToExecute); - END - - /* Create the view */ - IF OBJECT_ID(@ObjectFullName) IS NULL - BEGIN - SET @StringToExecute = 'USE ' - + @OutputDatabaseName - + '; EXEC (''CREATE VIEW ' - + @OutputSchemaName + '.' - + @OutputTableNamePerfmonStats_View + ' AS ' + @LineFeed - + 'WITH RowDates as' + @LineFeed - + '(' + @LineFeed - + ' SELECT ' + @LineFeed - + ' ROW_NUMBER() OVER (ORDER BY [ServerName], [CheckDate]) ID,' + @LineFeed - + ' [CheckDate]' + @LineFeed - + ' FROM ' + @OutputSchemaName + '.' +@OutputTableNamePerfmonStats + '' + @LineFeed - + ' GROUP BY [ServerName], [CheckDate]' + @LineFeed - + '),' + @LineFeed - + 'CheckDates as' + @LineFeed - + '(' + @LineFeed - + ' SELECT ThisDate.CheckDate,' + @LineFeed - + ' LastDate.CheckDate as PreviousCheckDate' + @LineFeed - + ' FROM RowDates ThisDate' + @LineFeed - + ' JOIN RowDates LastDate' + @LineFeed - + ' ON ThisDate.ID = LastDate.ID + 1' + @LineFeed - + ')' + @LineFeed - + 'SELECT' + @LineFeed - + ' pMon.[ServerName]' + @LineFeed - + ' ,pMon.[CheckDate]' + @LineFeed - + ' ,pMon.[object_name]' + @LineFeed - + ' ,pMon.[counter_name]' + @LineFeed - + ' ,pMon.[instance_name]' + @LineFeed - + ' ,DATEDIFF(SECOND,pMonPrior.[CheckDate],pMon.[CheckDate]) AS ElapsedSeconds' + @LineFeed - + ' ,pMon.[cntr_value]' + @LineFeed - + ' ,pMon.[cntr_type]' + @LineFeed - + ' ,(pMon.[cntr_value] - pMonPrior.[cntr_value]) AS cntr_delta' + @LineFeed - + ' ,(pMon.cntr_value - pMonPrior.cntr_value) * 1.0 / DATEDIFF(ss, pMonPrior.CheckDate, pMon.CheckDate) AS cntr_delta_per_second' + @LineFeed - + ' ,pMon.ServerName + CAST(pMon.CheckDate AS NVARCHAR(50)) AS JoinKey' + @LineFeed - + ' FROM ' + @OutputSchemaName + '.' +@OutputTableNamePerfmonStats + ' pMon' + @LineFeed - + ' INNER HASH JOIN CheckDates Dates' + @LineFeed - + ' ON Dates.CheckDate = pMon.CheckDate' + @LineFeed - + ' JOIN ' + @OutputSchemaName + '.' +@OutputTableNamePerfmonStats + ' pMonPrior' + @LineFeed - + ' ON Dates.PreviousCheckDate = pMonPrior.CheckDate' + @LineFeed - + ' AND pMon.[ServerName] = pMonPrior.[ServerName] ' + @LineFeed - + ' AND pMon.[object_name] = pMonPrior.[object_name] ' + @LineFeed - + ' AND pMon.[counter_name] = pMonPrior.[counter_name] ' + @LineFeed - + ' AND pMon.[instance_name] = pMonPrior.[instance_name]' + @LineFeed - + ' WHERE DATEDIFF(MI, pMonPrior.CheckDate, pMon.CheckDate) BETWEEN 1 AND 60;'')' - - EXEC(@StringToExecute); - END - - SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableNamePerfmonStatsActuals_View; - - /* If the view exists without the most recently added columns, drop it. See Github #2162. */ - IF OBJECT_ID(@ObjectFullName) IS NOT NULL - BEGIN - SET @StringToExecute = N'USE ' + @OutputDatabaseName + N'; IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns - WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''JoinKey'') - DROP VIEW ' + @OutputSchemaName + N'.' + @OutputTableNamePerfmonStatsActuals_View + N';'; - - EXEC(@StringToExecute); - END - - /* Create the second view */ - IF OBJECT_ID(@ObjectFullName) IS NULL - BEGIN - SET @StringToExecute = 'USE ' - + @OutputDatabaseName - + '; EXEC (''CREATE VIEW ' - + @OutputSchemaName + '.' - + @OutputTableNamePerfmonStatsActuals_View + ' AS ' + @LineFeed - + 'WITH PERF_AVERAGE_BULK AS' + @LineFeed - + '(' + @LineFeed - + ' SELECT ServerName,' + @LineFeed - + ' object_name,' + @LineFeed - + ' instance_name,' + @LineFeed - + ' counter_name,' + @LineFeed - + ' CASE WHEN CHARINDEX(''''('''', counter_name) = 0 THEN counter_name ELSE LEFT (counter_name, CHARINDEX(''''('''',counter_name)-1) END AS counter_join,' + @LineFeed - + ' CheckDate,' + @LineFeed - + ' cntr_delta' + @LineFeed - + ' FROM ' + @OutputSchemaName + '.' + @OutputTableNamePerfmonStats_View + @LineFeed - + ' WHERE cntr_type IN(1073874176)' + @LineFeed - + ' AND cntr_delta <> 0' + @LineFeed - + '),' + @LineFeed - + 'PERF_LARGE_RAW_BASE AS' + @LineFeed - + '(' + @LineFeed - + ' SELECT ServerName,' + @LineFeed - + ' object_name,' + @LineFeed - + ' instance_name,' + @LineFeed - + ' LEFT(counter_name, CHARINDEX(''''BASE'''', UPPER(counter_name))-1) AS counter_join,' + @LineFeed - + ' CheckDate,' + @LineFeed - + ' cntr_delta' + @LineFeed - + ' FROM ' + @OutputSchemaName + '.' + @OutputTableNamePerfmonStats_View + '' + @LineFeed - + ' WHERE cntr_type IN(1073939712)' + @LineFeed - + ' AND cntr_delta <> 0' + @LineFeed - + '),' + @LineFeed - + 'PERF_AVERAGE_FRACTION AS' + @LineFeed - + '(' + @LineFeed - + ' SELECT ServerName,' + @LineFeed - + ' object_name,' + @LineFeed - + ' instance_name,' + @LineFeed - + ' counter_name,' + @LineFeed - + ' counter_name AS counter_join,' + @LineFeed - + ' CheckDate,' + @LineFeed - + ' cntr_delta' + @LineFeed - + ' FROM ' + @OutputSchemaName + '.' + @OutputTableNamePerfmonStats_View + '' + @LineFeed - + ' WHERE cntr_type IN(537003264)' + @LineFeed - + ' AND cntr_delta <> 0' + @LineFeed - + '),' + @LineFeed - + 'PERF_COUNTER_BULK_COUNT AS' + @LineFeed - + '(' + @LineFeed - + ' SELECT ServerName,' + @LineFeed - + ' object_name,' + @LineFeed - + ' instance_name,' + @LineFeed - + ' counter_name,' + @LineFeed - + ' CheckDate,' + @LineFeed - + ' cntr_delta / ElapsedSeconds AS cntr_value' + @LineFeed - + ' FROM ' + @OutputSchemaName + '.' + @OutputTableNamePerfmonStats_View + '' + @LineFeed - + ' WHERE cntr_type IN(272696576, 272696320)' + @LineFeed - + ' AND cntr_delta <> 0' + @LineFeed - + '),' + @LineFeed - + 'PERF_COUNTER_RAWCOUNT AS' + @LineFeed - + '(' + @LineFeed - + ' SELECT ServerName,' + @LineFeed - + ' object_name,' + @LineFeed - + ' instance_name,' + @LineFeed - + ' counter_name,' + @LineFeed - + ' CheckDate,' + @LineFeed - + ' cntr_value' + @LineFeed - + ' FROM ' + @OutputSchemaName + '.' + @OutputTableNamePerfmonStats_View + '' + @LineFeed - + ' WHERE cntr_type IN(65792, 65536)' + @LineFeed - + ')' + @LineFeed - + '' + @LineFeed - + 'SELECT NUM.ServerName,' + @LineFeed - + ' NUM.object_name,' + @LineFeed - + ' NUM.counter_name,' + @LineFeed - + ' NUM.instance_name,' + @LineFeed - + ' NUM.CheckDate,' + @LineFeed - + ' NUM.cntr_delta / DEN.cntr_delta AS cntr_value,' + @LineFeed - + ' NUM.ServerName + CAST(NUM.CheckDate AS NVARCHAR(50)) AS JoinKey' + @LineFeed - + ' ' + @LineFeed - + 'FROM PERF_AVERAGE_BULK AS NUM' + @LineFeed - + ' JOIN PERF_LARGE_RAW_BASE AS DEN ON NUM.counter_join = DEN.counter_join' + @LineFeed - + ' AND NUM.CheckDate = DEN.CheckDate' + @LineFeed - + ' AND NUM.ServerName = DEN.ServerName' + @LineFeed - + ' AND NUM.object_name = DEN.object_name' + @LineFeed - + ' AND NUM.instance_name = DEN.instance_name' + @LineFeed - + ' AND DEN.cntr_delta <> 0' + @LineFeed - + '' + @LineFeed - + 'UNION ALL' + @LineFeed - + '' + @LineFeed - + 'SELECT NUM.ServerName,' + @LineFeed - + ' NUM.object_name,' + @LineFeed - + ' NUM.counter_name,' + @LineFeed - + ' NUM.instance_name,' + @LineFeed - + ' NUM.CheckDate,' + @LineFeed - + ' CAST((CAST(NUM.cntr_delta as DECIMAL(19)) / DEN.cntr_delta) as decimal(23,3)) AS cntr_value,' + @LineFeed - + ' NUM.ServerName + CAST(NUM.CheckDate AS NVARCHAR(50)) AS JoinKey' + @LineFeed - + 'FROM PERF_AVERAGE_FRACTION AS NUM' + @LineFeed - + ' JOIN PERF_LARGE_RAW_BASE AS DEN ON NUM.counter_join = DEN.counter_join' + @LineFeed - + ' AND NUM.CheckDate = DEN.CheckDate' + @LineFeed - + ' AND NUM.ServerName = DEN.ServerName' + @LineFeed - + ' AND NUM.object_name = DEN.object_name' + @LineFeed - + ' AND NUM.instance_name = DEN.instance_name' + @LineFeed - + ' AND DEN.cntr_delta <> 0' + @LineFeed - + 'UNION ALL' + @LineFeed - + '' + @LineFeed - + 'SELECT ServerName,' + @LineFeed - + ' object_name,' + @LineFeed - + ' counter_name,' + @LineFeed - + ' instance_name,' + @LineFeed - + ' CheckDate,' + @LineFeed - + ' cntr_value,' + @LineFeed - + ' ServerName + CAST(CheckDate AS NVARCHAR(50)) AS JoinKey' + @LineFeed - + 'FROM PERF_COUNTER_BULK_COUNT' + @LineFeed - + '' + @LineFeed - + 'UNION ALL' + @LineFeed - + '' + @LineFeed - + 'SELECT ServerName,' + @LineFeed - + ' object_name,' + @LineFeed - + ' counter_name,' + @LineFeed - + ' instance_name,' + @LineFeed - + ' CheckDate,' + @LineFeed - + ' cntr_value,' + @LineFeed - + ' ServerName + CAST(CheckDate AS NVARCHAR(50)) AS JoinKey' + @LineFeed - + 'FROM PERF_COUNTER_RAWCOUNT;'')'; - - EXEC(@StringToExecute); - END; - - - SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' - + @OutputSchemaName + ''') INSERT ' - + @OutputDatabaseName + '.' - + @OutputSchemaName + '.' - + @OutputTableNamePerfmonStats - + ' (ServerName, CheckDate, object_name, counter_name, instance_name, cntr_value, cntr_type, value_delta, value_per_second) SELECT ' - + ' @SrvName, @CheckDate, object_name, counter_name, instance_name, cntr_value, cntr_type, value_delta, value_per_second FROM #PerfmonStats WHERE Pass = 2'; - - EXEC sp_executesql @StringToExecute, - N'@SrvName NVARCHAR(128), @CheckDate datetimeoffset', - @LocalServerName, @StartSampleTime; - - /* Delete history older than @OutputTableRetentionDays */ - SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' - + @OutputSchemaName + ''') DELETE ' - + @OutputDatabaseName + '.' - + @OutputSchemaName + '.' - + @OutputTableNamePerfmonStats - + ' WHERE ServerName = @SrvName AND CheckDate < @CheckDate ;'; - - EXEC sp_executesql @StringToExecute, - N'@SrvName NVARCHAR(128), @CheckDate date', - @LocalServerName, @OutputTableCleanupDate; - - - - END; - ELSE IF (SUBSTRING(@OutputTableNamePerfmonStats, 2, 2) = '##') - BEGIN - SET @StringToExecute = N' IF (OBJECT_ID(''tempdb..' - + @OutputTableNamePerfmonStats - + ''') IS NULL) CREATE TABLE ' - + @OutputTableNamePerfmonStats - + ' (ID INT IDENTITY(1,1) NOT NULL, - ServerName NVARCHAR(128), - CheckDate DATETIMEOFFSET, - [object_name] NVARCHAR(128) NOT NULL, - [counter_name] NVARCHAR(128) NOT NULL, - [instance_name] NVARCHAR(128) NULL, - [cntr_value] BIGINT NULL, - [cntr_type] INT NOT NULL, - [value_delta] BIGINT NULL, - [value_per_second] DECIMAL(18,2) NULL, - PRIMARY KEY CLUSTERED (ID ASC));' - + ' INSERT ' - + @OutputTableNamePerfmonStats - + ' (ServerName, CheckDate, object_name, counter_name, instance_name, cntr_value, cntr_type, value_delta, value_per_second) SELECT ' - + CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128)) - + ' @SrvName, @CheckDate, object_name, counter_name, instance_name, cntr_value, cntr_type, value_delta, value_per_second FROM #PerfmonStats WHERE Pass = 2'; - - EXEC sp_executesql @StringToExecute, - N'@SrvName NVARCHAR(128), @CheckDate datetimeoffset', - @LocalServerName, @StartSampleTime; - END; - ELSE IF (SUBSTRING(@OutputTableNamePerfmonStats, 2, 1) = '#') - BEGIN - RAISERROR('Due to the nature of Dymamic SQL, only global (i.e. double pound (##)) temp tables are supported for @OutputTableName', 16, 0); - END; - - - /* @OutputTableNameWaitStats lets us export the results to a permanent table */ - IF @OutputDatabaseName IS NOT NULL - AND @OutputSchemaName IS NOT NULL - AND @OutputTableNameWaitStats IS NOT NULL - AND @OutputTableNameWaitStats NOT LIKE '#%' - AND EXISTS ( SELECT * - FROM sys.databases - WHERE QUOTENAME([name]) = @OutputDatabaseName) - BEGIN - /* Create the table */ - SET @StringToExecute = 'USE ' - + @OutputDatabaseName - + '; IF EXISTS(SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' - + @OutputSchemaName - + ''') AND NOT EXISTS (SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''' - + @OutputSchemaName + ''' AND QUOTENAME(TABLE_NAME) = ''' - + @OutputTableNameWaitStats + ''') ' + @LineFeed - + 'BEGIN' + @LineFeed - + 'CREATE TABLE ' - + @OutputSchemaName + '.' - + @OutputTableNameWaitStats - + ' (ID INT IDENTITY(1,1) NOT NULL, - ServerName NVARCHAR(128), - CheckDate DATETIMEOFFSET, - wait_type NVARCHAR(60), - wait_time_ms BIGINT, - signal_wait_time_ms BIGINT, - waiting_tasks_count BIGINT , - PRIMARY KEY CLUSTERED (ID));' + @LineFeed - + 'CREATE NONCLUSTERED INDEX IX_ServerName_wait_type_CheckDate_Includes ON ' + @OutputSchemaName + '.' + @OutputTableNameWaitStats + @LineFeed - + '(ServerName, wait_type, CheckDate) INCLUDE (wait_time_ms, signal_wait_time_ms, waiting_tasks_count);' + @LineFeed - + 'END'; - - EXEC(@StringToExecute); - - /* Create the wait stats category table */ - SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableNameWaitStats_Categories; - IF OBJECT_ID(@ObjectFullName) IS NULL - BEGIN - SET @StringToExecute = 'USE ' - + @OutputDatabaseName - + '; EXEC (''CREATE TABLE ' - + @OutputSchemaName + '.' - + @OutputTableNameWaitStats_Categories + ' (WaitType NVARCHAR(60) PRIMARY KEY CLUSTERED, WaitCategory NVARCHAR(128) NOT NULL, Ignorable BIT DEFAULT 0);'')'; - - EXEC(@StringToExecute); - END; - - /* Make sure the wait stats category table has the current number of rows */ - SET @StringToExecute = 'USE ' - + @OutputDatabaseName - + '; EXEC (''IF (SELECT COALESCE(SUM(1),0) FROM ' + @OutputSchemaName + '.' + @OutputTableNameWaitStats_Categories + ') <> (SELECT COALESCE(SUM(1),0) FROM ##WaitCategories)' + @LineFeed - + 'BEGIN ' + @LineFeed - + 'TRUNCATE TABLE ' + @OutputSchemaName + '.' + @OutputTableNameWaitStats_Categories + @LineFeed - + 'INSERT INTO ' + @OutputSchemaName + '.' + @OutputTableNameWaitStats_Categories + ' (WaitType, WaitCategory, Ignorable) SELECT WaitType, WaitCategory, Ignorable FROM ##WaitCategories;' + @LineFeed - + 'END'')'; - - EXEC(@StringToExecute); - - - SET @ObjectFullName = @OutputDatabaseName + N'.' + @OutputSchemaName + N'.' + @OutputTableNameWaitStats_View; - - /* If the view exists without the most recently added columns, drop it. See Github #2162. */ - IF OBJECT_ID(@ObjectFullName) IS NOT NULL - BEGIN - SET @StringToExecute = N'USE ' + @OutputDatabaseName + N'; IF NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.sys.all_columns - WHERE object_id = (OBJECT_ID(''' + @ObjectFullName + N''')) AND name = ''JoinKey'') - DROP VIEW ' + @OutputSchemaName + N'.' + @OutputTableNameWaitStats_View + N';'; - - EXEC(@StringToExecute); - END - - - /* Create the wait stats view */ - IF OBJECT_ID(@ObjectFullName) IS NULL - BEGIN - SET @StringToExecute = 'USE ' - + @OutputDatabaseName - + '; EXEC (''CREATE VIEW ' - + @OutputSchemaName + '.' - + @OutputTableNameWaitStats_View + ' AS ' + @LineFeed - + 'WITH RowDates as' + @LineFeed - + '(' + @LineFeed - + ' SELECT ' + @LineFeed - + ' ROW_NUMBER() OVER (ORDER BY [ServerName], [CheckDate]) ID,' + @LineFeed - + ' [CheckDate]' + @LineFeed - + ' FROM ' + @OutputSchemaName + '.' + @OutputTableNameWaitStats + @LineFeed - + ' GROUP BY [ServerName], [CheckDate]' + @LineFeed - + '),' + @LineFeed - + 'CheckDates as' + @LineFeed - + '(' + @LineFeed - + ' SELECT ThisDate.CheckDate,' + @LineFeed - + ' LastDate.CheckDate as PreviousCheckDate' + @LineFeed - + ' FROM RowDates ThisDate' + @LineFeed - + ' JOIN RowDates LastDate' + @LineFeed - + ' ON ThisDate.ID = LastDate.ID + 1' + @LineFeed - + ')' + @LineFeed - + 'SELECT w.ServerName, w.CheckDate, w.wait_type, COALESCE(wc.WaitCategory, ''''Other'''') AS WaitCategory, COALESCE(wc.Ignorable,0) AS Ignorable' + @LineFeed - + ', DATEDIFF(ss, wPrior.CheckDate, w.CheckDate) AS ElapsedSeconds' + @LineFeed - + ', (w.wait_time_ms - wPrior.wait_time_ms) AS wait_time_ms_delta' + @LineFeed - + ', (w.wait_time_ms - wPrior.wait_time_ms) / 60000.0 AS wait_time_minutes_delta' + @LineFeed - + ', (w.wait_time_ms - wPrior.wait_time_ms) / 1000.0 / DATEDIFF(ss, wPrior.CheckDate, w.CheckDate) AS wait_time_minutes_per_minute' + @LineFeed - + ', (w.signal_wait_time_ms - wPrior.signal_wait_time_ms) AS signal_wait_time_ms_delta' + @LineFeed - + ', (w.waiting_tasks_count - wPrior.waiting_tasks_count) AS waiting_tasks_count_delta' + @LineFeed - + ', w.ServerName + CAST(w.CheckDate AS NVARCHAR(50)) AS JoinKey' + @LineFeed - + 'FROM ' + @OutputSchemaName + '.' + @OutputTableNameWaitStats + ' w' + @LineFeed - + 'INNER HASH JOIN CheckDates Dates' + @LineFeed - + 'ON Dates.CheckDate = w.CheckDate' + @LineFeed - + 'INNER JOIN ' + @OutputSchemaName + '.' + @OutputTableNameWaitStats + ' wPrior ON w.ServerName = wPrior.ServerName AND w.wait_type = wPrior.wait_type AND Dates.PreviousCheckDate = wPrior.CheckDate' + @LineFeed - + 'LEFT OUTER JOIN ' + @OutputSchemaName + '.' + @OutputTableNameWaitStats_Categories + ' wc ON w.wait_type = wc.WaitType' + @LineFeed - + 'WHERE DATEDIFF(MI, wPrior.CheckDate, w.CheckDate) BETWEEN 1 AND 60' + @LineFeed - + 'AND [w].[wait_time_ms] >= [wPrior].[wait_time_ms];'')' - - EXEC(@StringToExecute); - END; - - - SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' - + @OutputSchemaName + ''') INSERT ' - + @OutputDatabaseName + '.' - + @OutputSchemaName + '.' - + @OutputTableNameWaitStats - + ' (ServerName, CheckDate, wait_type, wait_time_ms, signal_wait_time_ms, waiting_tasks_count) SELECT ' - + ' @SrvName, @CheckDate, wait_type, wait_time_ms, signal_wait_time_ms, waiting_tasks_count FROM #WaitStats WHERE Pass = 2 AND wait_time_ms > 0 AND waiting_tasks_count > 0'; - - EXEC sp_executesql @StringToExecute, - N'@SrvName NVARCHAR(128), @CheckDate datetimeoffset', - @LocalServerName, @StartSampleTime; - - /* Delete history older than @OutputTableRetentionDays */ - SET @StringToExecute = N' IF EXISTS(SELECT * FROM ' - + @OutputDatabaseName - + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' - + @OutputSchemaName + ''') DELETE ' - + @OutputDatabaseName + '.' - + @OutputSchemaName + '.' - + @OutputTableNameWaitStats - + ' WHERE ServerName = @SrvName AND CheckDate < @CheckDate ;'; - - EXEC sp_executesql @StringToExecute, - N'@SrvName NVARCHAR(128), @CheckDate date', - @LocalServerName, @OutputTableCleanupDate; - - END; - ELSE IF (SUBSTRING(@OutputTableNameWaitStats, 2, 2) = '##') - BEGIN - SET @StringToExecute = N' IF (OBJECT_ID(''tempdb..' - + @OutputTableNameWaitStats - + ''') IS NULL) CREATE TABLE ' - + @OutputTableNameWaitStats - + ' (ID INT IDENTITY(1,1) NOT NULL, - ServerName NVARCHAR(128), - CheckDate DATETIMEOFFSET, - wait_type NVARCHAR(60), - wait_time_ms BIGINT, - signal_wait_time_ms BIGINT, - waiting_tasks_count BIGINT , - PRIMARY KEY CLUSTERED (ID ASC));' - + ' INSERT ' - + @OutputTableNameWaitStats - + ' (ServerName, CheckDate, wait_type, wait_time_ms, signal_wait_time_ms, waiting_tasks_count) SELECT ' - + ' @SrvName, @CheckDate, wait_type, wait_time_ms, signal_wait_time_ms, waiting_tasks_count FROM #WaitStats WHERE Pass = 2 AND wait_time_ms > 0 AND waiting_tasks_count > 0'; - - EXEC sp_executesql @StringToExecute, - N'@SrvName NVARCHAR(128), @CheckDate datetimeoffset', - @LocalServerName, @StartSampleTime; - END; - ELSE IF (SUBSTRING(@OutputTableNameWaitStats, 2, 1) = '#') - BEGIN - RAISERROR('Due to the nature of Dymamic SQL, only global (i.e. double pound (##)) temp tables are supported for @OutputTableName', 16, 0); - END; - - - - - DECLARE @separator AS VARCHAR(1); - IF @OutputType = 'RSV' - SET @separator = CHAR(31); - ELSE - SET @separator = ','; - - IF @OutputType = 'COUNT' AND @SinceStartup = 0 - BEGIN - SELECT COUNT(*) AS Warnings - FROM #BlitzFirstResults; - END; - ELSE - IF @OutputType = 'Opserver1' AND @SinceStartup = 0 AND @OutputResultSets LIKE N'%Findings%' - BEGIN - - SELECT r.[Priority] , - r.[FindingsGroup] , - r.[Finding] , - r.[URL] , - r.[Details], - r.[HowToStopIt] , - r.[CheckID] , - r.[StartTime], - r.[LoginName], - r.[NTUserName], - r.[OriginalLoginName], - r.[ProgramName], - r.[HostName], - r.[DatabaseID], - r.[DatabaseName], - r.[OpenTransactionCount], - r.[QueryPlan], - r.[QueryText], - qsNow.plan_handle AS PlanHandle, - qsNow.sql_handle AS SqlHandle, - qsNow.statement_start_offset AS StatementStartOffset, - qsNow.statement_end_offset AS StatementEndOffset, - [Executions] = qsNow.execution_count - (COALESCE(qsFirst.execution_count, 0)), - [ExecutionsPercent] = CAST(100.0 * (qsNow.execution_count - (COALESCE(qsFirst.execution_count, 0))) / (qsTotal.execution_count - qsTotalFirst.execution_count) AS DECIMAL(6,2)), - [Duration] = qsNow.total_elapsed_time - (COALESCE(qsFirst.total_elapsed_time, 0)), - [DurationPercent] = CAST(100.0 * (qsNow.total_elapsed_time - (COALESCE(qsFirst.total_elapsed_time, 0))) / (qsTotal.total_elapsed_time - qsTotalFirst.total_elapsed_time) AS DECIMAL(6,2)), - [CPU] = qsNow.total_worker_time - (COALESCE(qsFirst.total_worker_time, 0)), - [CPUPercent] = CAST(100.0 * (qsNow.total_worker_time - (COALESCE(qsFirst.total_worker_time, 0))) / (qsTotal.total_worker_time - qsTotalFirst.total_worker_time) AS DECIMAL(6,2)), - [Reads] = qsNow.total_logical_reads - (COALESCE(qsFirst.total_logical_reads, 0)), - [ReadsPercent] = CAST(100.0 * (qsNow.total_logical_reads - (COALESCE(qsFirst.total_logical_reads, 0))) / (qsTotal.total_logical_reads - qsTotalFirst.total_logical_reads) AS DECIMAL(6,2)), - [PlanCreationTime] = CONVERT(NVARCHAR(100), qsNow.creation_time ,121), - [TotalExecutions] = qsNow.execution_count, - [TotalExecutionsPercent] = CAST(100.0 * qsNow.execution_count / qsTotal.execution_count AS DECIMAL(6,2)), - [TotalDuration] = qsNow.total_elapsed_time, - [TotalDurationPercent] = CAST(100.0 * qsNow.total_elapsed_time / qsTotal.total_elapsed_time AS DECIMAL(6,2)), - [TotalCPU] = qsNow.total_worker_time, - [TotalCPUPercent] = CAST(100.0 * qsNow.total_worker_time / qsTotal.total_worker_time AS DECIMAL(6,2)), - [TotalReads] = qsNow.total_logical_reads, - [TotalReadsPercent] = CAST(100.0 * qsNow.total_logical_reads / qsTotal.total_logical_reads AS DECIMAL(6,2)), - r.[DetailsInt] - FROM #BlitzFirstResults r - LEFT OUTER JOIN #QueryStats qsTotal ON qsTotal.Pass = 0 - LEFT OUTER JOIN #QueryStats qsTotalFirst ON qsTotalFirst.Pass = -1 - LEFT OUTER JOIN #QueryStats qsNow ON r.QueryStatsNowID = qsNow.ID - LEFT OUTER JOIN #QueryStats qsFirst ON r.QueryStatsFirstID = qsFirst.ID - ORDER BY r.Priority , - r.FindingsGroup , - CASE - WHEN r.CheckID = 6 THEN DetailsInt - ELSE 0 - END DESC, - r.Finding, - r.ID; - END; - ELSE IF @OutputType IN ( 'CSV', 'RSV' ) AND @SinceStartup = 0 AND @OutputResultSets LIKE N'%Findings%' - BEGIN - - SELECT Result = CAST([Priority] AS NVARCHAR(100)) - + @separator + CAST(CheckID AS NVARCHAR(100)) - + @separator + COALESCE([FindingsGroup], - '(N/A)') + @separator - + COALESCE([Finding], '(N/A)') + @separator - + COALESCE(DatabaseName, '(N/A)') + @separator - + COALESCE([URL], '(N/A)') + @separator - + COALESCE([Details], '(N/A)') - FROM #BlitzFirstResults - ORDER BY Priority , - FindingsGroup , - CASE - WHEN CheckID = 6 THEN DetailsInt - ELSE 0 - END DESC, - Finding, - Details; - END; - ELSE IF @OutputType = 'Top10' AND @OutputResultSets LIKE N'%WaitStats%' - BEGIN - /* Measure waits in hours */ - ;WITH max_batch AS ( - SELECT MAX(SampleTime) AS SampleTime - FROM #WaitStats - ) - SELECT TOP 10 - CAST(DATEDIFF(mi,wd1.SampleTime, wd2.SampleTime) / 60.0 AS DECIMAL(18,1)) AS [Hours Sample], - CAST(c.[Total Thread Time (Seconds)] / 60. / 60. AS DECIMAL(18,1)) AS [Thread Time (Hours)], - wd1.wait_type, - COALESCE(wcat.WaitCategory, 'Other') AS wait_category, - CAST(c.[Wait Time (Seconds)] / 60. / 60. AS DECIMAL(18,1)) AS [Wait Time (Hours)], - CAST((wd2.wait_time_ms - wd1.wait_time_ms) / 1000.0 / cores.cpu_count / DATEDIFF(ss, wd1.SampleTime, wd2.SampleTime) AS DECIMAL(18,1)) AS [Per Core Per Hour], - (wd2.waiting_tasks_count - wd1.waiting_tasks_count) AS [Number of Waits], - CASE WHEN (wd2.waiting_tasks_count - wd1.waiting_tasks_count) > 0 - THEN - CAST((wd2.wait_time_ms-wd1.wait_time_ms)/ - (1.0*(wd2.waiting_tasks_count - wd1.waiting_tasks_count)) AS NUMERIC(12,1)) - ELSE 0 END AS [Avg ms Per Wait] - FROM max_batch b - JOIN #WaitStats wd2 ON - wd2.SampleTime =b.SampleTime - JOIN #WaitStats wd1 ON - wd1.wait_type=wd2.wait_type AND - wd2.SampleTime > wd1.SampleTime - CROSS APPLY (SELECT SUM(1) AS cpu_count FROM sys.dm_os_schedulers WHERE status = 'VISIBLE ONLINE' AND is_online = 1) AS cores - CROSS APPLY (SELECT - CAST((wd2.wait_time_ms-wd1.wait_time_ms)/1000. AS DECIMAL(18,1)) AS [Wait Time (Seconds)], - CAST((wd2.signal_wait_time_ms - wd1.signal_wait_time_ms)/1000. AS DECIMAL(18,1)) AS [Signal Wait Time (Seconds)], - CAST((wd2.thread_time_ms)/1000. AS DECIMAL(18,1)) AS [Total Thread Time (Seconds)] - ) AS c - LEFT OUTER JOIN ##WaitCategories wcat ON wd1.wait_type = wcat.WaitType - WHERE (wd2.waiting_tasks_count - wd1.waiting_tasks_count) > 0 - AND wd2.wait_time_ms-wd1.wait_time_ms > 0 - ORDER BY [Wait Time (Seconds)] DESC; - END; - ELSE IF @ExpertMode = 0 AND @OutputType <> 'NONE' AND @OutputXMLasNVARCHAR = 0 AND @SinceStartup = 0 AND @OutputResultSets LIKE N'%Findings%' - BEGIN - SELECT [Priority] , - [FindingsGroup] , - [Finding] , - [URL] , - CAST(@StockDetailsHeader + [Details] + @StockDetailsFooter AS XML) AS Details, - CAST(@StockWarningHeader + HowToStopIt + @StockWarningFooter AS XML) AS HowToStopIt, - [QueryText], - [QueryPlan] - FROM #BlitzFirstResults - WHERE (@Seconds > 0 OR (Priority IN (0, 250, 251, 255))) /* For @Seconds = 0, filter out broken checks for now */ - ORDER BY Priority , - FindingsGroup , - CASE - WHEN CheckID = 6 THEN DetailsInt - ELSE 0 - END DESC, - Finding, - ID, - CAST(Details AS NVARCHAR(4000)); - END; - ELSE IF @OutputType <> 'NONE' AND @OutputXMLasNVARCHAR = 1 AND @SinceStartup = 0 AND @OutputResultSets LIKE N'%Findings%' - BEGIN - SELECT [Priority] , - [FindingsGroup] , - [Finding] , - [URL] , - CAST(LEFT(@StockDetailsHeader + [Details] + @StockDetailsFooter,32000) AS TEXT) AS Details, - CAST(LEFT([HowToStopIt],32000) AS TEXT) AS HowToStopIt, - CAST([QueryText] AS NVARCHAR(MAX)) AS QueryText, - CAST([QueryPlan] AS NVARCHAR(MAX)) AS QueryPlan - FROM #BlitzFirstResults - WHERE (@Seconds > 0 OR (Priority IN (0, 250, 251, 255))) /* For @Seconds = 0, filter out broken checks for now */ - ORDER BY Priority , - FindingsGroup , - CASE - WHEN CheckID = 6 THEN DetailsInt - ELSE 0 - END DESC, - Finding, - ID, - CAST(Details AS NVARCHAR(4000)); - END; - ELSE IF @ExpertMode = 1 AND @OutputType <> 'NONE' AND @OutputResultSets LIKE N'%Findings%' - BEGIN - IF @SinceStartup = 0 - SELECT r.[Priority] , - r.[FindingsGroup] , - r.[Finding] , - r.[URL] , - CAST(@StockDetailsHeader + r.[Details] + @StockDetailsFooter AS XML) AS Details, - CAST(@StockWarningHeader + r.HowToStopIt + @StockWarningFooter AS XML) AS HowToStopIt, - r.[CheckID] , - r.[StartTime], - r.[LoginName], - r.[NTUserName], - r.[OriginalLoginName], - r.[ProgramName], - r.[HostName], - r.[DatabaseID], - r.[DatabaseName], - r.[OpenTransactionCount], - r.[QueryPlan], - r.[QueryText], - qsNow.plan_handle AS PlanHandle, - qsNow.sql_handle AS SqlHandle, - qsNow.statement_start_offset AS StatementStartOffset, - qsNow.statement_end_offset AS StatementEndOffset, - [Executions] = qsNow.execution_count - (COALESCE(qsFirst.execution_count, 0)), - [ExecutionsPercent] = CAST(100.0 * (qsNow.execution_count - (COALESCE(qsFirst.execution_count, 0))) / (qsTotal.execution_count - qsTotalFirst.execution_count) AS DECIMAL(6,2)), - [Duration] = qsNow.total_elapsed_time - (COALESCE(qsFirst.total_elapsed_time, 0)), - [DurationPercent] = CAST(100.0 * (qsNow.total_elapsed_time - (COALESCE(qsFirst.total_elapsed_time, 0))) / (qsTotal.total_elapsed_time - qsTotalFirst.total_elapsed_time) AS DECIMAL(6,2)), - [CPU] = qsNow.total_worker_time - (COALESCE(qsFirst.total_worker_time, 0)), - [CPUPercent] = CAST(100.0 * (qsNow.total_worker_time - (COALESCE(qsFirst.total_worker_time, 0))) / (qsTotal.total_worker_time - qsTotalFirst.total_worker_time) AS DECIMAL(6,2)), - [Reads] = qsNow.total_logical_reads - (COALESCE(qsFirst.total_logical_reads, 0)), - [ReadsPercent] = CAST(100.0 * (qsNow.total_logical_reads - (COALESCE(qsFirst.total_logical_reads, 0))) / (qsTotal.total_logical_reads - qsTotalFirst.total_logical_reads) AS DECIMAL(6,2)), - [PlanCreationTime] = CONVERT(NVARCHAR(100), qsNow.creation_time ,121), - [TotalExecutions] = qsNow.execution_count, - [TotalExecutionsPercent] = CAST(100.0 * qsNow.execution_count / qsTotal.execution_count AS DECIMAL(6,2)), - [TotalDuration] = qsNow.total_elapsed_time, - [TotalDurationPercent] = CAST(100.0 * qsNow.total_elapsed_time / qsTotal.total_elapsed_time AS DECIMAL(6,2)), - [TotalCPU] = qsNow.total_worker_time, - [TotalCPUPercent] = CAST(100.0 * qsNow.total_worker_time / qsTotal.total_worker_time AS DECIMAL(6,2)), - [TotalReads] = qsNow.total_logical_reads, - [TotalReadsPercent] = CAST(100.0 * qsNow.total_logical_reads / qsTotal.total_logical_reads AS DECIMAL(6,2)), - r.[DetailsInt] - FROM #BlitzFirstResults r - LEFT OUTER JOIN #QueryStats qsTotal ON qsTotal.Pass = 0 - LEFT OUTER JOIN #QueryStats qsTotalFirst ON qsTotalFirst.Pass = -1 - LEFT OUTER JOIN #QueryStats qsNow ON r.QueryStatsNowID = qsNow.ID - LEFT OUTER JOIN #QueryStats qsFirst ON r.QueryStatsFirstID = qsFirst.ID - WHERE (@Seconds > 0 OR (Priority IN (0, 250, 251, 255))) /* For @Seconds = 0, filter out broken checks for now */ - ORDER BY r.Priority , - r.FindingsGroup , - CASE - WHEN r.CheckID = 6 THEN DetailsInt - ELSE 0 - END DESC, - r.Finding, - r.ID, - CAST(r.Details AS NVARCHAR(4000)); - - ------------------------- - --What happened: #WaitStats - ------------------------- - IF @Seconds = 0 AND @OutputResultSets LIKE N'%WaitStats%' - BEGIN - /* Measure waits in hours */ - ;WITH max_batch AS ( - SELECT MAX(SampleTime) AS SampleTime - FROM #WaitStats - ) - SELECT - 'WAIT STATS' AS Pattern, - b.SampleTime AS [Sample Ended], - CAST(DATEDIFF(mi,wd1.SampleTime, wd2.SampleTime) / 60. AS DECIMAL(18,1)) AS [Hours Sample], - CAST(c.[Total Thread Time (Seconds)] / 60. / 60. AS DECIMAL(18,1)) AS [Thread Time (Hours)], - wd1.wait_type, - COALESCE(wcat.WaitCategory, 'Other') AS wait_category, - CAST(c.[Wait Time (Seconds)] / 60. / 60. AS DECIMAL(18,1)) AS [Wait Time (Hours)], - CAST((wd2.wait_time_ms - wd1.wait_time_ms) / 1000.0 / cores.cpu_count / DATEDIFF(ss, wd1.SampleTime, wd2.SampleTime) AS DECIMAL(18,1)) AS [Per Core Per Hour], - CAST(c.[Signal Wait Time (Seconds)] / 60.0 / 60 AS DECIMAL(18,1)) AS [Signal Wait Time (Hours)], - CASE WHEN c.[Wait Time (Seconds)] > 0 - THEN CAST(100.*(c.[Signal Wait Time (Seconds)]/c.[Wait Time (Seconds)]) AS NUMERIC(4,1)) - ELSE 0 END AS [Percent Signal Waits], - (wd2.waiting_tasks_count - wd1.waiting_tasks_count) AS [Number of Waits], - CASE WHEN (wd2.waiting_tasks_count - wd1.waiting_tasks_count) > 0 - THEN - CAST((wd2.wait_time_ms-wd1.wait_time_ms)/ - (1.0*(wd2.waiting_tasks_count - wd1.waiting_tasks_count)) AS NUMERIC(12,1)) - ELSE 0 END AS [Avg ms Per Wait], - N'https://www.sqlskills.com/help/waits/' + LOWER(wd1.wait_type) + '/' AS URL - FROM max_batch b - JOIN #WaitStats wd2 ON - wd2.SampleTime =b.SampleTime - JOIN #WaitStats wd1 ON - wd1.wait_type=wd2.wait_type AND - wd2.SampleTime > wd1.SampleTime - CROSS APPLY (SELECT SUM(1) AS cpu_count FROM sys.dm_os_schedulers WHERE status = 'VISIBLE ONLINE' AND is_online = 1) AS cores - CROSS APPLY (SELECT - CAST((wd2.wait_time_ms-wd1.wait_time_ms)/1000. AS DECIMAL(18,1)) AS [Wait Time (Seconds)], - CAST((wd2.signal_wait_time_ms - wd1.signal_wait_time_ms)/1000. AS DECIMAL(18,1)) AS [Signal Wait Time (Seconds)], - CAST((wd2.thread_time_ms)/1000. AS DECIMAL(18,1)) AS [Total Thread Time (Seconds)] - ) AS c - LEFT OUTER JOIN ##WaitCategories wcat ON wd1.wait_type = wcat.WaitType - WHERE (wd2.waiting_tasks_count - wd1.waiting_tasks_count) > 0 - AND wd2.wait_time_ms-wd1.wait_time_ms > 0 - ORDER BY [Wait Time (Seconds)] DESC; - END; - ELSE IF @OutputResultSets LIKE N'%WaitStats%' - BEGIN - /* Measure waits in seconds */ - ;WITH max_batch AS ( - SELECT MAX(SampleTime) AS SampleTime - FROM #WaitStats - ) - SELECT - 'WAIT STATS' AS Pattern, - b.SampleTime AS [Sample Ended], - DATEDIFF(ss,wd1.SampleTime, wd2.SampleTime) AS [Seconds Sample], - c.[Total Thread Time (Seconds)], - wd1.wait_type, - COALESCE(wcat.WaitCategory, 'Other') AS wait_category, - c.[Wait Time (Seconds)], - CAST((CAST(wd2.wait_time_ms - wd1.wait_time_ms AS MONEY)) / 1000.0 / cores.cpu_count / DATEDIFF(ss, wd1.SampleTime, wd2.SampleTime) AS DECIMAL(18,1)) AS [Per Core Per Second], - c.[Signal Wait Time (Seconds)], - CASE WHEN c.[Wait Time (Seconds)] > 0 - THEN CAST(100.*(c.[Signal Wait Time (Seconds)]/c.[Wait Time (Seconds)]) AS NUMERIC(4,1)) - ELSE 0 END AS [Percent Signal Waits], - (wd2.waiting_tasks_count - wd1.waiting_tasks_count) AS [Number of Waits], - CASE WHEN (wd2.waiting_tasks_count - wd1.waiting_tasks_count) > 0 - THEN - CAST((wd2.wait_time_ms-wd1.wait_time_ms)/ - (1.0*(wd2.waiting_tasks_count - wd1.waiting_tasks_count)) AS NUMERIC(12,1)) - ELSE 0 END AS [Avg ms Per Wait], - N'https://www.sqlskills.com/help/waits/' + LOWER(wd1.wait_type) + '/' AS URL - FROM max_batch b - JOIN #WaitStats wd2 ON - wd2.SampleTime =b.SampleTime - JOIN #WaitStats wd1 ON - wd1.wait_type=wd2.wait_type AND - wd2.SampleTime > wd1.SampleTime - CROSS APPLY (SELECT SUM(1) AS cpu_count FROM sys.dm_os_schedulers WHERE status = 'VISIBLE ONLINE' AND is_online = 1) AS cores - CROSS APPLY (SELECT - CAST((wd2.wait_time_ms-wd1.wait_time_ms)/1000. AS DECIMAL(18,1)) AS [Wait Time (Seconds)], - CAST((wd2.signal_wait_time_ms - wd1.signal_wait_time_ms)/1000. AS DECIMAL(18,1)) AS [Signal Wait Time (Seconds)], - CAST((wd2.thread_time_ms - wd1.thread_time_ms)/1000. AS DECIMAL(18,1)) AS [Total Thread Time (Seconds)] - ) AS c - LEFT OUTER JOIN ##WaitCategories wcat ON wd1.wait_type = wcat.WaitType - WHERE (wd2.waiting_tasks_count - wd1.waiting_tasks_count) > 0 - AND wd2.wait_time_ms-wd1.wait_time_ms > 0 - ORDER BY [Wait Time (Seconds)] DESC; - END; - - ------------------------- - --What happened: #FileStats - ------------------------- - IF @OutputResultSets LIKE N'%FileStats%' - WITH readstats AS ( - SELECT 'PHYSICAL READS' AS Pattern, - ROW_NUMBER() OVER (ORDER BY wd2.avg_stall_read_ms DESC) AS StallRank, - wd2.SampleTime AS [Sample Time], - DATEDIFF(ss,wd1.SampleTime, wd2.SampleTime) AS [Sample (seconds)], - wd1.DatabaseName , - wd1.FileLogicalName AS [File Name], - UPPER(SUBSTRING(wd1.PhysicalName, 1, 2)) AS [Drive] , - wd1.SizeOnDiskMB , - ( wd2.num_of_reads - wd1.num_of_reads ) AS [# Reads/Writes], - CASE WHEN wd2.num_of_reads - wd1.num_of_reads > 0 - THEN CAST(( wd2.bytes_read - wd1.bytes_read)/1024./1024. AS NUMERIC(21,1)) - ELSE 0 - END AS [MB Read/Written], - wd2.avg_stall_read_ms AS [Avg Stall (ms)], - wd1.PhysicalName AS [file physical name] - FROM #FileStats wd2 - JOIN #FileStats wd1 ON wd2.SampleTime > wd1.SampleTime - AND wd1.DatabaseID = wd2.DatabaseID - AND wd1.FileID = wd2.FileID - ), - writestats AS ( - SELECT - 'PHYSICAL WRITES' AS Pattern, - ROW_NUMBER() OVER (ORDER BY wd2.avg_stall_write_ms DESC) AS StallRank, - wd2.SampleTime AS [Sample Time], - DATEDIFF(ss,wd1.SampleTime, wd2.SampleTime) AS [Sample (seconds)], - wd1.DatabaseName , - wd1.FileLogicalName AS [File Name], - UPPER(SUBSTRING(wd1.PhysicalName, 1, 2)) AS [Drive] , - wd1.SizeOnDiskMB , - ( wd2.num_of_writes - wd1.num_of_writes ) AS [# Reads/Writes], - CASE WHEN wd2.num_of_writes - wd1.num_of_writes > 0 - THEN CAST(( wd2.bytes_written - wd1.bytes_written)/1024./1024. AS NUMERIC(21,1)) - ELSE 0 - END AS [MB Read/Written], - wd2.avg_stall_write_ms AS [Avg Stall (ms)], - wd1.PhysicalName AS [file physical name] - FROM #FileStats wd2 - JOIN #FileStats wd1 ON wd2.SampleTime > wd1.SampleTime - AND wd1.DatabaseID = wd2.DatabaseID - AND wd1.FileID = wd2.FileID - ) - SELECT - Pattern, [Sample Time], [Sample (seconds)], [File Name], [Drive], [# Reads/Writes],[MB Read/Written],[Avg Stall (ms)], [file physical name], [DatabaseName], [StallRank] - FROM readstats - WHERE StallRank <=20 AND [MB Read/Written] > 0 - UNION ALL - SELECT Pattern, [Sample Time], [Sample (seconds)], [File Name], [Drive], [# Reads/Writes],[MB Read/Written],[Avg Stall (ms)], [file physical name], [DatabaseName], [StallRank] - FROM writestats - WHERE StallRank <=20 AND [MB Read/Written] > 0 - ORDER BY Pattern, StallRank; - - - ------------------------- - --What happened: #PerfmonStats - ------------------------- - - IF @OutputResultSets LIKE N'%PerfmonStats%' - SELECT 'PERFMON' AS Pattern, pLast.[object_name], pLast.counter_name, pLast.instance_name, - pFirst.SampleTime AS FirstSampleTime, pFirst.cntr_value AS FirstSampleValue, - pLast.SampleTime AS LastSampleTime, pLast.cntr_value AS LastSampleValue, - pLast.cntr_value - pFirst.cntr_value AS ValueDelta, - ((1.0 * pLast.cntr_value - pFirst.cntr_value) / DATEDIFF(ss, pFirst.SampleTime, pLast.SampleTime)) AS ValuePerSecond - FROM #PerfmonStats pLast - INNER JOIN #PerfmonStats pFirst ON pFirst.[object_name] = pLast.[object_name] AND pFirst.counter_name = pLast.counter_name AND (pFirst.instance_name = pLast.instance_name OR (pFirst.instance_name IS NULL AND pLast.instance_name IS NULL)) - AND pLast.ID > pFirst.ID - WHERE pLast.cntr_value <> pFirst.cntr_value - ORDER BY Pattern, pLast.[object_name], pLast.counter_name, pLast.instance_name; - - - ------------------------- - --What happened: #QueryStats - ------------------------- - IF @CheckProcedureCache = 1 AND @OutputResultSets LIKE N'%BlitzCache%' - BEGIN - - SELECT qsNow.*, qsFirst.* - FROM #QueryStats qsNow - INNER JOIN #QueryStats qsFirst ON qsNow.[sql_handle] = qsFirst.[sql_handle] AND qsNow.statement_start_offset = qsFirst.statement_start_offset AND qsNow.statement_end_offset = qsFirst.statement_end_offset AND qsNow.plan_generation_num = qsFirst.plan_generation_num AND qsNow.plan_handle = qsFirst.plan_handle AND qsFirst.Pass = 1 - WHERE qsNow.Pass = 2; - END; - ELSE - BEGIN - SELECT 'Plan Cache' AS [Pattern], 'Plan cache not analyzed' AS [Finding], 'Use @CheckProcedureCache = 1 or run sp_BlitzCache for more analysis' AS [More Info], CONVERT(XML, @StockDetailsHeader + 'firstresponderkit.org' + @StockDetailsFooter) AS [Details]; - END; - END; - - DROP TABLE #BlitzFirstResults; - - /* What's running right now? This is the first and last result set. */ - IF @SinceStartup = 0 AND @Seconds > 0 AND @ExpertMode = 1 AND @OutputType <> 'NONE' AND @OutputResultSets LIKE N'%BlitzWho_End%' - BEGIN - IF OBJECT_ID('master.dbo.sp_BlitzWho') IS NULL AND OBJECT_ID('dbo.sp_BlitzWho') IS NULL - BEGIN - PRINT N'sp_BlitzWho is not installed in the current database_files. You can get a copy from http://FirstResponderKit.org'; - END; - ELSE - BEGIN - EXEC (@BlitzWho); - END; - END; /* IF @SinceStartup = 0 AND @Seconds > 0 AND @ExpertMode = 1 AND @OutputType <> 'NONE' - What's running right now? This is the first and last result set. */ - -END; /* IF @LogMessage IS NULL */ -END; /* ELSE IF @OutputType = 'SCHEMA' */ - -SET NOCOUNT OFF; -GO - - - -/* How to run it: -EXEC dbo.sp_BlitzFirst - -With extra diagnostic info: -EXEC dbo.sp_BlitzFirst @ExpertMode = 1; - -Saving output to tables: -EXEC sp_BlitzFirst - @OutputDatabaseName = 'DBAtools' -, @OutputSchemaName = 'dbo' -, @OutputTableName = 'BlitzFirst' -, @OutputTableNameFileStats = 'BlitzFirst_FileStats' -, @OutputTableNamePerfmonStats = 'BlitzFirst_PerfmonStats' -, @OutputTableNameWaitStats = 'BlitzFirst_WaitStats' -, @OutputTableNameBlitzCache = 'BlitzCache' -, @OutputTableNameBlitzWho = 'BlitzWho' -, @OutputType = 'none' -*/ diff --git a/README.md b/README.md index 1ff8d59bb..b357f249c 100644 --- a/README.md +++ b/README.md @@ -20,9 +20,7 @@ Navigation - [sp_BlitzIndex: Tune Your Indexes](#sp_blitzindex-tune-your-indexes) - [Advanced sp_BlitzIndex Parameters](#advanced-sp_blitzindex-parameters) - Performance Tuning: - - [sp_BlitzInMemoryOLTP: Hekaton Analysis](#sp_blitzinmemoryoltp-hekaton-analysis) - [sp_BlitzLock: Deadlock Analysis](#sp_blitzlock-deadlock-analysis) - - [sp_BlitzQueryStore: Like BlitzCache, for Query Store](#sp_blitzquerystore-how-has-a-query-plan-changed-over-time) - [sp_BlitzWho: What Queries are Running Now](#sp_blitzwho-what-queries-are-running-now) - [sp_BlitzAnalysis: Query sp_BlitzFirst output tables](#sp_blitzanalysis-query-sp_BlitzFirst-output-tables) - Backups and Restores: @@ -52,14 +50,16 @@ If you're stuck with versions of SQL Server that Microsoft no longer supports, l ## How to Install the Scripts -There are three installation scripts. Choose the one that most suits your needs: +For SQL Server, to install all of the scripts at once, open Install-All-Scripts.sql in SSMS or Azure Data Studio, switch to the database where you want to install the procs, and run it. It will install the stored procedures if they don't already exist, or update them to the current version if they do exist. -* **Install-Core-Blitz-No-Query-Store.sql** - if you don't know which one to use, use this. This contains the most commonly used stored procedures. Open this script in SSMS or Azure Data Studio, switch to the database where you want to install the stored procedures, and run it. It will install the stored procedures if they don't already exist, or update them to the current version if they do exist. -* **Install-Core-Blitz-With-Query-Store.sql** - for SQL Server 2016 & newer only. Same as above, but adds sp_BlitzQueryStore. -* **Install-All-Scripts.sql** - you're very clever (and also attractive), so as you may guess, this installs all of the scripts, including sp_DatabaseRestore and sp_AllNightLog, both of which depend on Ola Hallengren's Maintenance Solution. When running this script, you'll get warnings if you don't already have his scripts installed. To get those, go to https://ola.hallengren.com. +For Azure SQL DB, use Install-Azure.sql, which will only install the procs that are compatible with Azure SQL DB. + +If you hit an error when running Install-All-Scripts, it's likely because you're using an older version of SQL Server that Microsoft no longer supports. In that case, check out the Deprecated folder. That's where we keep old versions of the scripts around as a last-ditch effort - but really, if Microsoft won't support their own old stuff, you shouldn't try to do it either. We recommend installing these stored procedures in the master database, but if you want to use another one, that's totally fine - they're all supported in any database - but just be aware that you can run into problems if you have these procs in multiple databases. You may not keep them all up to date, and you may hit an issue when you're running an older version. +There are a couple of Install-Core scripts included for legacy purposes, for folks with installers they've built. You can ignore those. + ## How to Get Support Everyone here is expected to abide by the [Contributor Covenant Code of Conduct](CONTRIBUTING.md#the-contributor-covenant-code-of-conduct). @@ -217,7 +217,7 @@ Common sp_BlitzFirst parameters include: * @Seconds = 5 by default. You can specify longer samples if you want to track stats during a load test or demo, for example. * @ShowSleepingSPIDs = 0 by default. When set to 1, shows long-running sleeping queries that might be blocking others. -* @ExpertMode = 0 by default. When set to 1, it calls sp_BlitzWho when it starts (to show you what queries are running right now), plus outputs additional result sets for wait stats, Perfmon counters, and file stats during the sample, then finishes with one final execution of sp_BlitzWho to show you what was running at the end of the sample. +* @ExpertMode = 0 by default. When set to 1, it calls sp_BlitzWho when it starts (to show you what queries are running right now), plus outputs additional result sets for wait stats, Perfmon counters, and file stats during the sample, then finishes with one final execution of sp_BlitzWho to show you what was running at the end of the sample. When set to 2, it does the same as 1, but skips the calls to sp_BlitzWho. ### Logging sp_BlitzFirst to Tables @@ -296,21 +296,6 @@ In addition to the [parameters common to many of the stored procedures](#paramet [*Back to top*](#header1) -## sp_BlitzInMemoryOLTP: Hekaton Analysis - -Examines your usage of In-Memory OLTP tables. Parameters you can use: - -* @instanceLevelOnly BIT: This flag determines whether or not to simply report on the server-level environment (if applicable, i.e. there is no server-level environment for Azure SQL Database). With this parameter, memory-optimized databases are ignored. If you specify @instanceLevelOnly and a database name, the database name is ignored. -* @dbName NVARCHAR(4000) = N'ALL' - If you don't specify a database name, then sp_BlitzInMemoryOLTP reports on all memory-optimized databases within the instance that it executes in, or in the case of Azure SQL Database, the database that you provisioned. This is because the default for the @dbName parameter is N'ALL'. -* @tableName NVARCHAR(4000) = NULL -* @debug BIT - -To interpret the output of this stored procedure, read [Ned Otter's sp_BlitzInMemoryOLTP documentation](http://nedotter.com/archive/2018/06/new-kid-on-the-block-sp_blitzinmemoryoltp/). - - -[*Back to top*](#header1) - - ## sp_BlitzLock: Deadlock Analysis @@ -336,29 +321,6 @@ Known issues: [*Back to top*](#header1) -## sp_BlitzQueryStore: How Has a Query Plan Changed Over Time - -Analyzes data in Query Store schema (2016+ only) in many similar ways to what sp_BlitzCache does for the plan cache. - -* @Help: Right now this just prints the license if set to 1. I'm going to add better documentation here as the script matures. -* @DatabaseName: This one is required. Query Store is per database, so you have to point it at one to examine. -* @Top: How many plans from each "worst" you want to get. We look at your maxes for CPU, reads, duration, writes, memory, rows, executions, and additionally tempdb and log bytes for 2017. So it's the number of plans from each of those to gather. -* @StartDate: Fairly obvious, when you want to start looking at queries from. If NULL, we'll only go back seven days. -* @EndDate: When you want to stop looking at queries from. If you leave it NULL, we'll look ahead seven days. -* @MinimumExecutionCount: The minimum number of times a query has to have been executed (not just compiled) to be analyzed. -* @DurationFilter: The minimum number of seconds a query has to have been executed for to be analyzed. -* @StoredProcName: If you want to look at a single stored procedure. -* @Failed: If you want to look at failed queries, for some reason. I dunno, MS made such a big deal out of being able to look at these, I figured I'd add it. -* @PlanIdFilter: If you want to filter by a particular plan id. Remember that a query may have many different plans. -* @QueryIdFilter: If you want to filter by a particular query id. If you want to look at one specific plan for a query. -* @ExportToExcel: Leaves XML out of the input and tidies up query text so you can easily paste it into Excel. -* @HideSummary: Pulls the rolled up warnings and information out of the results. -* @SkipXML: Skips XML analysis. -* @Debug: Prints dynamic SQL and selects data from all temp tables if set to 1. - -[*Back to top*](#header1) - - ## sp_BlitzWho: What Queries are Running Now This is like sp_who, except it goes into way, way, way more details. @@ -481,28 +443,6 @@ The reason behind that is, if you have 500 databases, and you're taking log back [*Back to top*](#header1) -## sp_AllNightLog: Back Up Faster to Lose Less Data - -You manage a SQL Server instance with hundreds or thousands of mission-critical databases. You want to back them all up as quickly as possible, and one maintenance plan job isn't going to cut it. - -Let's scale out our backup jobs by: - -* Creating a table with a list of databases and their desired Recovery Point Objective (RPO, aka data loss) - done with sp_AllNightLog_Setup -* Set up several Agent jobs to back up databases as necessary - also done with sp_AllNightLog_Setup -* Inside each of those Agent jobs, they call sp_AllNightLog @Backup = 1, which loops through the table to find databases that need to be backed up, then call [Ola Hallengren's DatabaseBackup stored procedure](https://ola.hallengren.com/) -* Keeping that database list up to date as new databases are added - done by a job calling sp_AllNightLog @PollForNewDatabases = 1 - -For more information about how this works, see [sp_AllNightLog documentation.](https://www.BrentOzar.com/sp_AllNightLog) - -Known issues: - -* The msdbCentral database name is hard-coded. -* sp_AllNightLog depends on Ola Hallengren's DatabaseBackup, which must be installed separately. (We expect it to be installed in the same database as the SQL Server First Responder Kit.) - - -[*Back to top*](#header1) - - ## sp_DatabaseRestore: Easier Multi-File Restores If you use [Ola Hallengren's backup scripts](http://ola.hallengren.com), DatabaseRestore.sql helps you rapidly restore a database to the most recent point in time. From 1d65e5b99c4e28e600f715680f31fd18f35af875 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Fri, 19 Apr 2024 09:47:44 -0700 Subject: [PATCH 550/662] #3499 sp_DatabaseRestore install warnings Let them know that they'll need Ola's scripts. Closes #3499. --- sp_DatabaseRestore.sql | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/sp_DatabaseRestore.sql b/sp_DatabaseRestore.sql index 43805aee8..9491740c6 100755 --- a/sp_DatabaseRestore.sql +++ b/sp_DatabaseRestore.sql @@ -1,3 +1,13 @@ +IF OBJECT_ID('dbo.CommandExecute') IS NULL +BEGIN + PRINT 'sp_DatabaseRestore is about to install, but you have not yet installed the Ola Hallengren maintenance scripts.' + PRINT 'sp_DatabaseRestore will still install, but to use that stored proc, you will need to install this:' + PRINT 'https://ola.hallengren.com' + PRINT 'You will see a bunch of warnings below because the Ola scripts are not installed yet, and that is okay:' +END +GO + + IF OBJECT_ID('dbo.sp_DatabaseRestore') IS NULL EXEC ('CREATE PROCEDURE dbo.sp_DatabaseRestore AS RETURN 0;'); GO From 69d26c71d6e0bd2b374bfc19969f935ba32a74aa Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Fri, 19 Apr 2024 09:50:52 -0700 Subject: [PATCH 551/662] Remove deprecated tests No need to test sp_BlitzInMemoryOLTP or sp_BlitzQueryStore. --- tests/sp_BlitzInMemoryOLTP.tests.ps1 | 13 ------------- tests/sp_BlitzQueryStore.tests.ps1 | 12 ------------ 2 files changed, 25 deletions(-) delete mode 100644 tests/sp_BlitzInMemoryOLTP.tests.ps1 delete mode 100644 tests/sp_BlitzQueryStore.tests.ps1 diff --git a/tests/sp_BlitzInMemoryOLTP.tests.ps1 b/tests/sp_BlitzInMemoryOLTP.tests.ps1 deleted file mode 100644 index 710f0bc5e..000000000 --- a/tests/sp_BlitzInMemoryOLTP.tests.ps1 +++ /dev/null @@ -1,13 +0,0 @@ -Describe "sp_BlitzInMemoryOLTP Tests" { - - It "sp_BlitzInMemoryOLTP Check" { - # Create InMemory OLTP Database - Invoke-SqlCmd -Query "CREATE DATABASE sp_BlitzInMemoryOLTPTest;ALTER DATABASE sp_BlitzInMemoryOLTPTest SET MEMORY_OPTIMIZED_ELEVATE_TO_SNAPSHOT = ON;ALTER DATABASE sp_BlitzInMemoryOLTPTest ADD FILEGROUP sp_BlitzInMemoryOLTPTest CONTAINS MEMORY_OPTIMIZED_DATA;" - # Note: Added 'SELECT 1 AS A' as an empty first resultset causes issues returning the full DataSet - $results = Invoke-SqlCmd -Query "SELECT 1 AS A;EXEC dbo.sp_BlitzInMemoryOLTP" -OutputAs DataSet - # Adjust table count to get the actual tables returned from sp_BlitzInMemoryOLTP (So reporting isn't confusing) - $tableCount = $results.Tables.Count -1 - $tableCount | Should -BeGreaterThan 0 - } - -} \ No newline at end of file diff --git a/tests/sp_BlitzQueryStore.tests.ps1 b/tests/sp_BlitzQueryStore.tests.ps1 deleted file mode 100644 index 079ff1081..000000000 --- a/tests/sp_BlitzQueryStore.tests.ps1 +++ /dev/null @@ -1,12 +0,0 @@ -Describe "sp_BlitzQueryStore Tests" { - - It "sp_BlitzQueryStore Check" { - # Note: Added 'SELECT 1 AS A' as an empty first resultset causes issues returning the full DataSet - $results = Invoke-SqlCmd -Query "SELECT 1 AS A;EXEC dbo.sp_BlitzQueryStore" -OutputAs DataSet - # Adjust table count to get the actual tables returned from sp_BlitzQueryStore (So reporting isn't confusing) - $tableCount = $results.Tables.Count - 1 - ## We haven't specified @DatabaseName and don't have DBs with Query Store enabled so table count is 0 - $tableCount | Should -Be 0 - } - -} \ No newline at end of file From 3aaddcc05b318d408eeee0de52018caf2b8cce2d Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Fri, 19 Apr 2024 09:53:36 -0700 Subject: [PATCH 552/662] Update integration-tests.yml Removing deprecated procs --- .github/workflows/integration-tests.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index eccb5901e..37b1a5870 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -47,9 +47,7 @@ jobs: sqlcmd -S localhost -U sa -P dbatools.I0 -d master -i "sp_BlitzAnalysis.sql" -I -b -t 60 sqlcmd -S localhost -U sa -P dbatools.I0 -d master -i "sp_BlitzBackups.sql" -I -b -t 60 sqlcmd -S localhost -U sa -P dbatools.I0 -d master -i "sp_BlitzIndex.sql" -I -b -t 60 - sqlcmd -S localhost -U sa -P dbatools.I0 -d master -i "sp_BlitzInMemoryOLTP.sql" -I -b -t 60 sqlcmd -S localhost -U sa -P dbatools.I0 -d master -i "sp_BlitzLock.sql" -I -b -t 60 - sqlcmd -S localhost -U sa -P dbatools.I0 -d master -i "sp_BlitzQueryStore.sql" -I -b -t 60 sqlcmd -S localhost -U sa -P dbatools.I0 -d master -Q "SELECT * FROM sys.procedures WHERE name LIKE 'sp_Blitz%';" -I -b -t 60 - name: Run Pester Tests From 62152b13e00676bb289d14aa03fed6d4230198d5 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Fri, 19 Apr 2024 10:25:28 -0700 Subject: [PATCH 553/662] #3504 sp_BlitzFirst Azure SQL DB More accurate startup time detection. Closes #3504. --- sp_BlitzFirst.sql | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/sp_BlitzFirst.sql b/sp_BlitzFirst.sql index 7d55cffdc..fa1ec7e48 100644 --- a/sp_BlitzFirst.sql +++ b/sp_BlitzFirst.sql @@ -281,16 +281,23 @@ BEGIN /* Set start/finish times AFTER sp_BlitzWho runs. For more info: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2244 */ IF @Seconds = 0 AND SERVERPROPERTY('EngineEdition') = 5 /*SERVERPROPERTY('Edition') = 'SQL Azure'*/ - WITH WaitTimes AS ( - SELECT wait_type, wait_time_ms, - NTILE(3) OVER(ORDER BY wait_time_ms) AS grouper - FROM sys.dm_os_wait_stats w - WHERE wait_type IN ('DIRTY_PAGE_POLL','HADR_FILESTREAM_IOMGR_IOCOMPLETION','LAZYWRITER_SLEEP', - 'LOGMGR_QUEUE','REQUEST_FOR_DEADLOCK_SEARCH','XE_TIMER_EVENT') - ) - SELECT @StartSampleTime = DATEADD(mi, AVG(-wait_time_ms / 1000 / 60), SYSDATETIMEOFFSET()), @FinishSampleTime = SYSDATETIMEOFFSET() - FROM WaitTimes - WHERE grouper = 2; + BEGIN + /* Use the most accurate (but undocumented) DMV if it's available: */ + IF EXISTS(SELECT * FROM sys.all_columns ac WHERE ac.object_id = OBJECT_ID('sys.dm_cloud_database_epoch') AND ac.name = 'last_role_transition_time') + SELECT @StartSampleTime = DATEADD(MINUTE,DATEDIFF(MINUTE, GETDATE(), GETUTCDATE()),last_role_transition_time) , @FinishSampleTime = SYSDATETIMEOFFSET() + FROM sys.dm_cloud_database_epoch; + ELSE + WITH WaitTimes AS ( + SELECT wait_type, wait_time_ms, + NTILE(3) OVER(ORDER BY wait_time_ms) AS grouper + FROM sys.dm_os_wait_stats w + WHERE wait_type IN ('DIRTY_PAGE_POLL','HADR_FILESTREAM_IOMGR_IOCOMPLETION','LAZYWRITER_SLEEP', + 'LOGMGR_QUEUE','REQUEST_FOR_DEADLOCK_SEARCH','XE_TIMER_EVENT') + ) + SELECT @StartSampleTime = DATEADD(mi, AVG(-wait_time_ms / 1000 / 60), SYSDATETIMEOFFSET()), @FinishSampleTime = SYSDATETIMEOFFSET() + FROM WaitTimes + WHERE grouper = 2; + END ELSE IF @Seconds = 0 AND SERVERPROPERTY('EngineEdition') <> 5 /*SERVERPROPERTY('Edition') <> 'SQL Azure'*/ SELECT @StartSampleTime = DATEADD(MINUTE,DATEDIFF(MINUTE, GETDATE(), GETUTCDATE()),create_date) , @FinishSampleTime = SYSDATETIMEOFFSET() FROM sys.databases From d222aae9d4a0964277bbb9a13efd378c348395ed Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Wed, 1 May 2024 06:57:15 -0700 Subject: [PATCH 554/662] #3507 sp_BlitzFirst wait stats seconds The headline news result set now shows the accurate number of seconds used for the wait stats sample. Closes #3507. --- sp_BlitzFirst.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sp_BlitzFirst.sql b/sp_BlitzFirst.sql index fa1ec7e48..61a9fd346 100644 --- a/sp_BlitzFirst.sql +++ b/sp_BlitzFirst.sql @@ -2955,7 +2955,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 'Wait Stats' AS FindingGroup, wNow.wait_type AS Finding, /* IF YOU CHANGE THIS, STUFF WILL BREAK. Other checks look for wait type names in the Finding field. See checks 11, 12 as example. */ N'https://www.sqlskills.com/help/waits/' + LOWER(wNow.wait_type) + '/' AS URL, - 'For ' + CAST(((wNow.wait_time_ms - COALESCE(wBase.wait_time_ms,0)) / 1000) AS NVARCHAR(100)) + ' seconds over the last ' + CASE @Seconds WHEN 0 THEN (CAST(DATEDIFF(dd,@StartSampleTime,@FinishSampleTime) AS NVARCHAR(10)) + ' days') ELSE (CAST(@Seconds AS NVARCHAR(10)) + ' seconds') END + ', SQL Server was waiting on this particular bottleneck.' + @LineFeed + @LineFeed AS Details, + 'For ' + CAST(((wNow.wait_time_ms - COALESCE(wBase.wait_time_ms,0)) / 1000) AS NVARCHAR(100)) + ' seconds over the last ' + CASE @Seconds WHEN 0 THEN (CAST(DATEDIFF(dd,@StartSampleTime,@FinishSampleTime) AS NVARCHAR(10)) + ' days') ELSE (CAST(DATEDIFF(ss, wBase.SampleTime, wNow.SampleTime) AS NVARCHAR(10)) + ' seconds') END + ', SQL Server was waiting on this particular bottleneck.' + @LineFeed + @LineFeed AS Details, 'See the URL for more details on how to mitigate this wait type.' AS HowToStopIt, ((wNow.wait_time_ms - COALESCE(wBase.wait_time_ms,0)) / 1000) AS DetailsInt FROM #WaitStats wNow @@ -2975,7 +2975,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 'Server Performance' AS FindingGroup, 'Poison Wait Detected: ' + wNow.wait_type AS Finding, N'https://www.brentozar.com/go/poison/#' + wNow.wait_type AS URL, - 'For ' + CAST(((wNow.wait_time_ms - COALESCE(wBase.wait_time_ms,0)) / 1000) AS NVARCHAR(100)) + ' seconds over the last ' + CASE @Seconds WHEN 0 THEN (CAST(DATEDIFF(dd,@StartSampleTime,@FinishSampleTime) AS NVARCHAR(10)) + ' days') ELSE (CAST(@Seconds AS NVARCHAR(10)) + ' seconds') END + ', SQL Server was waiting on this particular bottleneck.' + @LineFeed + @LineFeed AS Details, + 'For ' + CAST(((wNow.wait_time_ms - COALESCE(wBase.wait_time_ms,0)) / 1000) AS NVARCHAR(100)) + ' seconds over the last ' + CASE @Seconds WHEN 0 THEN (CAST(DATEDIFF(dd,@StartSampleTime,@FinishSampleTime) AS NVARCHAR(10)) + ' days') ELSE (CAST(DATEDIFF(ss, wBase.SampleTime, wNow.SampleTime) AS NVARCHAR(10)) + ' seconds') END + ', SQL Server was waiting on this particular bottleneck.' + @LineFeed + @LineFeed AS Details, 'See the URL for more details on how to mitigate this wait type.' AS HowToStopIt, ((wNow.wait_time_ms - COALESCE(wBase.wait_time_ms,0)) / 1000) AS DetailsInt FROM #WaitStats wNow From d529cd5c370f9fc007adf76dd8252b1f4c7e6f85 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Wed, 1 May 2024 07:52:47 -0700 Subject: [PATCH 555/662] #3491 sp_BlitzFirst max worker threads Add warning when open connections is higher than max worker threads. Closes #3491. --- .../sp_BlitzFirst_Checks_by_Priority.md | 6 ++- sp_BlitzFirst.sql | 37 ++++++++++++++++++- 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/Documentation/sp_BlitzFirst_Checks_by_Priority.md b/Documentation/sp_BlitzFirst_Checks_by_Priority.md index 8c4440766..053423541 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: 47 -If you want to add a new check, start at 48 +CURRENT HIGH CHECKID: 49 +If you want to add a new check, start at 50. | Priority | FindingsGroup | Finding | URL | CheckID | |----------|---------------------------------|---------------------------------------|-------------------------------------------------|----------| @@ -47,6 +47,7 @@ If you want to add a new check, start at 48 | 100 | Query Problems | Skewed Parallelism | https://www.brentozar.com/go/skewedup | 43 | | 100 | Query Problems | Query with a memory grant exceeding @MemoryGrantThresholdPct | https://www.brentozar.com/memory-grants-sql-servers-public-toilet/ | 46 | | 200 | Wait Stats | (One per wait type) | https://www.brentozar.com/sql/wait-stats/#(waittype) | 6 | +| 210 | Potential Upcoming Problems | High Number of Connections |https://www.brentozar.com/archive/2014/05/connections-slow-sql-server-threadpool/ | 49 | | 210 | Query Stats | Plan Cache Analysis Skipped | https://www.brentozar.com/go/topqueries | 18 | | 210 | Query Stats | Top Resource-Intensive Queries | https://www.brentozar.com/go/topqueries | 17 | | 250 | Server Info | Batch Requests per Second | https://www.brentozar.com/go/measure | 19 | @@ -57,3 +58,4 @@ If you want to add a new check, start at 48 | 251 | Server Info | Database Count | | 22 | | 251 | Server Info | Database Size, Total GB | | 21 | | 251 | Server Info | Memory Grant/Workspace info | | 40 | +| 254 | Informational | Thread Time Inaccurate | | 48 | diff --git a/sp_BlitzFirst.sql b/sp_BlitzFirst.sql index fa1ec7e48..7175c5d3f 100644 --- a/sp_BlitzFirst.sql +++ b/sp_BlitzFirst.sql @@ -143,7 +143,9 @@ DECLARE @StringToExecute NVARCHAR(MAX), @dm_exec_query_statistics_xml BIT = 0, @total_cpu_usage BIT = 0, @get_thread_time_ms NVARCHAR(MAX) = N'', - @thread_time_ms FLOAT = 0; + @thread_time_ms FLOAT = 0, + @logical_processors INT = 0, + @max_worker_threads INT = 0; /* Sanitize our inputs */ SELECT @@ -308,6 +310,10 @@ BEGIN @FinishSampleTimeWaitFor = DATEADD(ss, @Seconds, GETDATE()); + SELECT @logical_processors = COUNT(*) + FROM sys.dm_os_schedulers + WHERE status = 'VISIBLE ONLINE'; + IF EXISTS ( @@ -2581,6 +2587,35 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, END + /* Potential Upcoming Problems - High Number of Connections - CheckID 49 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 49',10,1) WITH NOWAIT; + END + IF CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) LIKE '%64%' AND SERVERPROPERTY('EngineEdition') <> 5 + BEGIN + IF @logical_processors <= 4 + SET @max_worker_threads = 512; + ELSE IF @logical_processors > 64 AND + ((@v = 13 AND @build >= 5026) OR @v >= 14) + SET @max_worker_threads = 512 + ((@logical_processors - 4) * 32) + ELSE + SET @max_worker_threads = 512 + ((@logical_processors - 4) * 16) + + IF @max_worker_threads > 0 + BEGIN + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) + SELECT 49 AS CheckID, + 210 AS Priority, + 'Potential Upcoming Problems' AS FindingGroup, + 'High Number of Connections' AS Finding, + 'https://www.brentozar.com/archive/2014/05/connections-slow-sql-server-threadpool/' AS URL, + 'There are ' + CAST(SUM(1) AS VARCHAR(20)) + ' open connections, which would lead to ' + @LineFeed + 'worker thread exhaustion and THREADPOOL waits' + @LineFeed + 'if they all ran queries at the same time.' AS Details + FROM sys.dm_exec_connections c + HAVING SUM(1) > @max_worker_threads; + END + END + RAISERROR('Finished running investigatory queries',10,1) WITH NOWAIT; From fc8cc9111f6b9ab03b963aa0e831c004514aed9b Mon Sep 17 00:00:00 2001 From: Gary Hunt Date: Mon, 6 May 2024 18:27:05 +0100 Subject: [PATCH 556/662] Use of DB_NAME() For consistency with the INSERT query above - DB_NAME should be used in both places --- sp_BlitzIndex.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index 670a8f553..9737edf8b 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -829,7 +829,7 @@ IF @GetAllDatabases = 1 IF EXISTS (SELECT * FROM sys.all_objects o INNER JOIN sys.all_columns c ON o.object_id = c.object_id AND o.name = 'dm_hadr_availability_replica_states' AND c.name = 'role_desc') BEGIN SET @dsql = N'UPDATE #DatabaseList SET secondary_role_allow_connections_desc = ''NO'' WHERE DatabaseName IN ( - SELECT d.name + SELECT DB_NAME(d.database_id) FROM sys.dm_hadr_availability_replica_states rs INNER JOIN sys.databases d ON rs.replica_id = d.replica_id INNER JOIN sys.availability_replicas r ON rs.replica_id = r.replica_id From f07681d2ec5820b679b1cba68fe7ec9454f77bbb Mon Sep 17 00:00:00 2001 From: Gary Hunt Date: Mon, 6 May 2024 18:43:52 +0100 Subject: [PATCH 557/662] NumDatabases includes those already marked as unreachable There is code to identify databases which can't be analysed that results in secondary_role_allow_connections_desc = 'NO', but these aren't excluded from the NumDatabases count --- sp_BlitzIndex.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index 670a8f553..f3c3aa5a2 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -894,7 +894,7 @@ ELSE ELSE @DatabaseName END; END; -SET @NumDatabases = (SELECT COUNT(*) FROM #DatabaseList AS D LEFT OUTER JOIN #Ignore_Databases AS I ON D.DatabaseName = I.DatabaseName WHERE I.DatabaseName IS NULL); +SET @NumDatabases = (SELECT COUNT(*) FROM #DatabaseList AS D LEFT OUTER JOIN #Ignore_Databases AS I ON D.DatabaseName = I.DatabaseName WHERE I.DatabaseName IS NULL AND ISNULL(D.secondary_role_allow_connections_desc, 'YES') != 'NO'); SET @msg = N'Number of databases to examine: ' + CAST(@NumDatabases AS NVARCHAR(50)); RAISERROR (@msg,0,1) WITH NOWAIT; From b8084c23e656cd0d332ba9a6a13091b5f472ee1d Mon Sep 17 00:00:00 2001 From: Gary Hunt Date: Mon, 6 May 2024 18:46:30 +0100 Subject: [PATCH 558/662] Insert columns don't appear correct The URL column ending up with an URL - which makes me thing that the field order and value order has become incorrect --- sp_BlitzIndex.sql | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index 670a8f553..47213f4d2 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -913,8 +913,8 @@ BEGIN TRY 0 , @ScriptVersionName, CASE WHEN @GetAllDatabases = 1 THEN N'All Databases' ELSE N'Database ' + QUOTENAME(@DatabaseName) + N' as of ' + CONVERT(NVARCHAR(16), GETDATE(), 121) END, - N'From Your Community Volunteers', N'http://FirstResponderKit.org', + N'From Your Community Volunteers', N'', N'', N'' @@ -925,9 +925,9 @@ BEGIN TRY 0, N'You''re trying to run sp_BlitzIndex on a server with ' + CAST(@NumDatabases AS NVARCHAR(8)) + N' databases. ', N'Running sp_BlitzIndex on a server with 50+ databases may cause temporary problems for the server and/or user.', - N'If you''re sure you want to do this, run again with the parameter @BringThePain = 1.', - 'http://FirstResponderKit.org', '', + 'http://FirstResponderKit.org', + N'If you''re sure you want to do this, run again with the parameter @BringThePain = 1.', '', '', '' From a57c72efc1cba3056a81faa215a3a31c5342c97f Mon Sep 17 00:00:00 2001 From: Gary Hunt Date: Mon, 6 May 2024 23:39:24 +0100 Subject: [PATCH 559/662] Capitalised name column This will fail on a case-sensitive database --- sp_BlitzIndex.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index 670a8f553..032167e26 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -1329,7 +1329,7 @@ BEGIN TRY si.index_id, si.type, @i_DatabaseName AS database_name, - COALESCE(sc.NAME, ''Unknown'') AS [schema_name], + COALESCE(sc.name, ''Unknown'') AS [schema_name], COALESCE(so.name, ''Unknown'') AS [object_name], COALESCE(si.name, ''Unknown'') AS [index_name], CASE WHEN so.[type] = CAST(''V'' AS CHAR(2)) THEN 1 ELSE 0 END, From 45925435e45d3ba2fc1034a9775144073f3137ef Mon Sep 17 00:00:00 2001 From: Gary Hunt Date: Tue, 7 May 2024 00:05:13 +0100 Subject: [PATCH 560/662] Typos Corrected 3 typos --- sp_BlitzIndex.sql | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index 670a8f553..f6fddd14f 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -1441,7 +1441,7 @@ BEGIN TRY RAISERROR (N'Preferring non-2012 syntax with LEFT JOIN to sys.dm_db_index_operational_stats',0,1) WITH NOWAIT; --NOTE: If you want to use the newer syntax for 2012+, you'll have to change 2147483647 to 11 on line ~819 - --This change was made because on a table with lots of paritions, the OUTER APPLY was crazy slow. + --This change was made because on a table with lots of partitions, the OUTER APPLY was crazy slow. -- get relevant columns from sys.dm_db_partition_stats, sys.partitions and sys.objects IF OBJECT_ID('tempdb..#dm_db_partition_stats_etc') IS NOT NULL @@ -1627,7 +1627,7 @@ BEGIN TRY BEGIN RAISERROR (N'Using 2012 syntax to query sys.dm_db_index_operational_stats',0,1) WITH NOWAIT; --This is the syntax that will be used if you change 2147483647 to 11 on line ~819. - --If you have a lot of paritions and this suddenly starts running for a long time, change it back. + --If you have a lot of partitions and this suddenly starts running for a long time, change it back. SET @dsql = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; SELECT ' + CAST(@DatabaseID AS NVARCHAR(10)) + N' AS database_id, ps.object_id, @@ -5277,7 +5277,7 @@ BEGIN FROM #TemporalTables AS t OPTION ( RECOMPILE ); - RAISERROR(N'check_id 121: Optimized For Sequental Keys.', 0,1) WITH NOWAIT; + RAISERROR(N'check_id 121: Optimized For Sequential Keys.', 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 ) @@ -5560,9 +5560,9 @@ BEGIN [table_nc_index_ratio] NUMERIC(29,1), [heap_count] INT, [heap_gb] NUMERIC(29,1), - [partioned_table_count] INT, - [partioned_nc_count] INT, - [partioned_gb] NUMERIC(29,1), + [partitioned_table_count] INT, + [partitioned_nc_count] INT, + [partitioned_gb] NUMERIC(29,1), [filtered_index_count] INT, [indexed_view_count] INT, [max_table_row_count] INT, @@ -5625,9 +5625,9 @@ BEGIN [table_nc_index_ratio], [heap_count], [heap_gb], - [partioned_table_count], - [partioned_nc_count], - [partioned_gb], + [partitioned_table_count], + [partitioned_nc_count], + [partitioned_gb], [filtered_index_count], [indexed_view_count], [max_table_row_count], From 0cb6c37936ff9919f11d5019b0548e36ff1b2faf Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Wed, 22 May 2024 14:02:27 -0700 Subject: [PATCH 561/662] 2024-05-22 version numbers Bumping for release, building release scripts. --- Install-All-Scripts.sql | 131 +++- Install-Azure.sql | 1635 ++------------------------------------- SqlServerVersions.sql | 3 + 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, 182 insertions(+), 1607 deletions(-) diff --git a/Install-All-Scripts.sql b/Install-All-Scripts.sql index bb75555d7..377f9fa50 100644 --- a/Install-All-Scripts.sql +++ b/Install-All-Scripts.sql @@ -38,7 +38,7 @@ AS SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.19', @VersionDate = '20240222'; + SELECT @Version = '8.20', @VersionDate = '20240522'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -10321,7 +10321,7 @@ AS SET NOCOUNT ON; SET STATISTICS XML OFF; -SELECT @Version = '8.19', @VersionDate = '20240222'; +SELECT @Version = '8.20', @VersionDate = '20240522'; IF(@VersionCheckMode = 1) BEGIN @@ -11199,7 +11199,7 @@ AS SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.19', @VersionDate = '20240222'; + SELECT @Version = '8.20', @VersionDate = '20240522'; IF(@VersionCheckMode = 1) BEGIN @@ -12981,7 +12981,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.19', @VersionDate = '20240222'; +SELECT @Version = '8.20', @VersionDate = '20240522'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -20355,7 +20355,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.19', @VersionDate = '20240222'; +SELECT @Version = '8.20', @VersionDate = '20240522'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -21136,7 +21136,7 @@ IF @GetAllDatabases = 1 IF EXISTS (SELECT * FROM sys.all_objects o INNER JOIN sys.all_columns c ON o.object_id = c.object_id AND o.name = 'dm_hadr_availability_replica_states' AND c.name = 'role_desc') BEGIN SET @dsql = N'UPDATE #DatabaseList SET secondary_role_allow_connections_desc = ''NO'' WHERE DatabaseName IN ( - SELECT d.name + SELECT DB_NAME(d.database_id) FROM sys.dm_hadr_availability_replica_states rs INNER JOIN sys.databases d ON rs.replica_id = d.replica_id INNER JOIN sys.availability_replicas r ON rs.replica_id = r.replica_id @@ -21201,7 +21201,7 @@ ELSE ELSE @DatabaseName END; END; -SET @NumDatabases = (SELECT COUNT(*) FROM #DatabaseList AS D LEFT OUTER JOIN #Ignore_Databases AS I ON D.DatabaseName = I.DatabaseName WHERE I.DatabaseName IS NULL); +SET @NumDatabases = (SELECT COUNT(*) FROM #DatabaseList AS D LEFT OUTER JOIN #Ignore_Databases AS I ON D.DatabaseName = I.DatabaseName WHERE I.DatabaseName IS NULL AND ISNULL(D.secondary_role_allow_connections_desc, 'YES') != 'NO'); SET @msg = N'Number of databases to examine: ' + CAST(@NumDatabases AS NVARCHAR(50)); RAISERROR (@msg,0,1) WITH NOWAIT; @@ -21220,8 +21220,8 @@ BEGIN TRY 0 , @ScriptVersionName, CASE WHEN @GetAllDatabases = 1 THEN N'All Databases' ELSE N'Database ' + QUOTENAME(@DatabaseName) + N' as of ' + CONVERT(NVARCHAR(16), GETDATE(), 121) END, - N'From Your Community Volunteers', N'http://FirstResponderKit.org', + N'From Your Community Volunteers', N'', N'', N'' @@ -21232,9 +21232,9 @@ BEGIN TRY 0, N'You''re trying to run sp_BlitzIndex on a server with ' + CAST(@NumDatabases AS NVARCHAR(8)) + N' databases. ', N'Running sp_BlitzIndex on a server with 50+ databases may cause temporary problems for the server and/or user.', - N'If you''re sure you want to do this, run again with the parameter @BringThePain = 1.', - 'http://FirstResponderKit.org', '', + 'http://FirstResponderKit.org', + N'If you''re sure you want to do this, run again with the parameter @BringThePain = 1.', '', '', '' @@ -21636,7 +21636,7 @@ BEGIN TRY si.index_id, si.type, @i_DatabaseName AS database_name, - COALESCE(sc.NAME, ''Unknown'') AS [schema_name], + COALESCE(sc.name, ''Unknown'') AS [schema_name], COALESCE(so.name, ''Unknown'') AS [object_name], COALESCE(si.name, ''Unknown'') AS [index_name], CASE WHEN so.[type] = CAST(''V'' AS CHAR(2)) THEN 1 ELSE 0 END, @@ -21748,7 +21748,7 @@ BEGIN TRY RAISERROR (N'Preferring non-2012 syntax with LEFT JOIN to sys.dm_db_index_operational_stats',0,1) WITH NOWAIT; --NOTE: If you want to use the newer syntax for 2012+, you'll have to change 2147483647 to 11 on line ~819 - --This change was made because on a table with lots of paritions, the OUTER APPLY was crazy slow. + --This change was made because on a table with lots of partitions, the OUTER APPLY was crazy slow. -- get relevant columns from sys.dm_db_partition_stats, sys.partitions and sys.objects IF OBJECT_ID('tempdb..#dm_db_partition_stats_etc') IS NOT NULL @@ -21934,7 +21934,7 @@ BEGIN TRY BEGIN RAISERROR (N'Using 2012 syntax to query sys.dm_db_index_operational_stats',0,1) WITH NOWAIT; --This is the syntax that will be used if you change 2147483647 to 11 on line ~819. - --If you have a lot of paritions and this suddenly starts running for a long time, change it back. + --If you have a lot of partitions and this suddenly starts running for a long time, change it back. SET @dsql = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; SELECT ' + CAST(@DatabaseID AS NVARCHAR(10)) + N' AS database_id, ps.object_id, @@ -25584,7 +25584,7 @@ BEGIN FROM #TemporalTables AS t OPTION ( RECOMPILE ); - RAISERROR(N'check_id 121: Optimized For Sequental Keys.', 0,1) WITH NOWAIT; + RAISERROR(N'check_id 121: Optimized For Sequential Keys.', 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 ) @@ -25867,9 +25867,9 @@ BEGIN [table_nc_index_ratio] NUMERIC(29,1), [heap_count] INT, [heap_gb] NUMERIC(29,1), - [partioned_table_count] INT, - [partioned_nc_count] INT, - [partioned_gb] NUMERIC(29,1), + [partitioned_table_count] INT, + [partitioned_nc_count] INT, + [partitioned_gb] NUMERIC(29,1), [filtered_index_count] INT, [indexed_view_count] INT, [max_table_row_count] INT, @@ -25932,9 +25932,9 @@ BEGIN [table_nc_index_ratio], [heap_count], [heap_gb], - [partioned_table_count], - [partioned_nc_count], - [partioned_gb], + [partitioned_table_count], + [partitioned_nc_count], + [partitioned_gb], [filtered_index_count], [indexed_view_count], [max_table_row_count], @@ -26843,7 +26843,7 @@ BEGIN SET XACT_ABORT OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.19', @VersionDate = '20240222'; + SELECT @Version = '8.20', @VersionDate = '20240522'; IF @VersionCheckMode = 1 BEGIN @@ -30974,7 +30974,7 @@ BEGIN SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.19', @VersionDate = '20240222'; + SELECT @Version = '8.20', @VersionDate = '20240522'; IF(@VersionCheckMode = 1) BEGIN @@ -32327,6 +32327,16 @@ EXEC sp_executesql @StringToExecute, END GO +IF OBJECT_ID('dbo.CommandExecute') IS NULL +BEGIN + PRINT 'sp_DatabaseRestore is about to install, but you have not yet installed the Ola Hallengren maintenance scripts.' + PRINT 'sp_DatabaseRestore will still install, but to use that stored proc, you will need to install this:' + PRINT 'https://ola.hallengren.com' + PRINT 'You will see a bunch of warnings below because the Ola scripts are not installed yet, and that is okay:' +END +GO + + IF OBJECT_ID('dbo.sp_DatabaseRestore') IS NULL EXEC ('CREATE PROCEDURE dbo.sp_DatabaseRestore AS RETURN 0;'); GO @@ -32376,7 +32386,7 @@ SET STATISTICS XML OFF; /*Versioning details*/ -SELECT @Version = '8.19', @VersionDate = '20240222'; +SELECT @Version = '8.20', @VersionDate = '20240522'; IF(@VersionCheckMode = 1) BEGIN @@ -34045,7 +34055,7 @@ BEGIN SET NOCOUNT ON; SET STATISTICS XML OFF; - SELECT @Version = '8.19', @VersionDate = '20240222'; + SELECT @Version = '8.20', @VersionDate = '20240522'; IF(@VersionCheckMode = 1) BEGIN @@ -34407,6 +34417,8 @@ DELETE FROM dbo.SqlServerVersions; INSERT INTO dbo.SqlServerVersions (MajorVersionNumber, MinorVersionNumber, Branch, [Url], ReleaseDate, MainstreamSupportEndDate, ExtendedSupportEndDate, MajorVersionName, MinorVersionName) VALUES + (16, 4125, 'CU13', 'https://support.microsoft.com/en-us/help/5036432', '2024-05-16', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 13'), + (16, 4115, 'CU12', 'https://support.microsoft.com/en-us/help/5033663', '2024-03-14', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 12'), (16, 4105, 'CU11', 'https://support.microsoft.com/en-us/help/5032679', '2024-01-11', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 11'), (16, 4100, 'CU10 GDR', 'https://support.microsoft.com/en-us/help/5033592', '2024-01-09', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 10 GDR'), (16, 4095, 'CU10', 'https://support.microsoft.com/en-us/help/5031778', '2023-11-16', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 10'), @@ -34421,6 +34433,7 @@ VALUES (16, 4003, 'CU1', 'https://support.microsoft.com/en-us/help/5022375', '2023-02-16', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 1'), (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'), + (15, 4365, 'CU26', 'https://support.microsoft.com/kb/5035123', '2024-04-11', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 26'), (15, 4355, 'CU25', 'https://support.microsoft.com/kb/5033688', '2024-02-15', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 25'), (15, 4345, 'CU24', 'https://support.microsoft.com/kb/5031908', '2023-12-14', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 24'), (15, 4335, 'CU23', 'https://support.microsoft.com/kb/5030333', '2023-10-12', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 23'), @@ -34849,7 +34862,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.19', @VersionDate = '20240222'; +SELECT @Version = '8.20', @VersionDate = '20240522'; IF(@VersionCheckMode = 1) BEGIN @@ -34945,7 +34958,9 @@ DECLARE @StringToExecute NVARCHAR(MAX), @dm_exec_query_statistics_xml BIT = 0, @total_cpu_usage BIT = 0, @get_thread_time_ms NVARCHAR(MAX) = N'', - @thread_time_ms FLOAT = 0; + @thread_time_ms FLOAT = 0, + @logical_processors INT = 0, + @max_worker_threads INT = 0; /* Sanitize our inputs */ SELECT @@ -35083,16 +35098,23 @@ BEGIN /* Set start/finish times AFTER sp_BlitzWho runs. For more info: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2244 */ IF @Seconds = 0 AND SERVERPROPERTY('EngineEdition') = 5 /*SERVERPROPERTY('Edition') = 'SQL Azure'*/ - WITH WaitTimes AS ( - SELECT wait_type, wait_time_ms, - NTILE(3) OVER(ORDER BY wait_time_ms) AS grouper - FROM sys.dm_os_wait_stats w - WHERE wait_type IN ('DIRTY_PAGE_POLL','HADR_FILESTREAM_IOMGR_IOCOMPLETION','LAZYWRITER_SLEEP', - 'LOGMGR_QUEUE','REQUEST_FOR_DEADLOCK_SEARCH','XE_TIMER_EVENT') - ) - SELECT @StartSampleTime = DATEADD(mi, AVG(-wait_time_ms / 1000 / 60), SYSDATETIMEOFFSET()), @FinishSampleTime = SYSDATETIMEOFFSET() - FROM WaitTimes - WHERE grouper = 2; + BEGIN + /* Use the most accurate (but undocumented) DMV if it's available: */ + IF EXISTS(SELECT * FROM sys.all_columns ac WHERE ac.object_id = OBJECT_ID('sys.dm_cloud_database_epoch') AND ac.name = 'last_role_transition_time') + SELECT @StartSampleTime = DATEADD(MINUTE,DATEDIFF(MINUTE, GETDATE(), GETUTCDATE()),last_role_transition_time) , @FinishSampleTime = SYSDATETIMEOFFSET() + FROM sys.dm_cloud_database_epoch; + ELSE + WITH WaitTimes AS ( + SELECT wait_type, wait_time_ms, + NTILE(3) OVER(ORDER BY wait_time_ms) AS grouper + FROM sys.dm_os_wait_stats w + WHERE wait_type IN ('DIRTY_PAGE_POLL','HADR_FILESTREAM_IOMGR_IOCOMPLETION','LAZYWRITER_SLEEP', + 'LOGMGR_QUEUE','REQUEST_FOR_DEADLOCK_SEARCH','XE_TIMER_EVENT') + ) + SELECT @StartSampleTime = DATEADD(mi, AVG(-wait_time_ms / 1000 / 60), SYSDATETIMEOFFSET()), @FinishSampleTime = SYSDATETIMEOFFSET() + FROM WaitTimes + WHERE grouper = 2; + END ELSE IF @Seconds = 0 AND SERVERPROPERTY('EngineEdition') <> 5 /*SERVERPROPERTY('Edition') <> 'SQL Azure'*/ SELECT @StartSampleTime = DATEADD(MINUTE,DATEDIFF(MINUTE, GETDATE(), GETUTCDATE()),create_date) , @FinishSampleTime = SYSDATETIMEOFFSET() FROM sys.databases @@ -35103,6 +35125,10 @@ BEGIN @FinishSampleTimeWaitFor = DATEADD(ss, @Seconds, GETDATE()); + SELECT @logical_processors = COUNT(*) + FROM sys.dm_os_schedulers + WHERE status = 'VISIBLE ONLINE'; + IF EXISTS ( @@ -37376,6 +37402,35 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, END + /* Potential Upcoming Problems - High Number of Connections - CheckID 49 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 49',10,1) WITH NOWAIT; + END + IF CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) LIKE '%64%' AND SERVERPROPERTY('EngineEdition') <> 5 + BEGIN + IF @logical_processors <= 4 + SET @max_worker_threads = 512; + ELSE IF @logical_processors > 64 AND + ((@v = 13 AND @build >= 5026) OR @v >= 14) + SET @max_worker_threads = 512 + ((@logical_processors - 4) * 32) + ELSE + SET @max_worker_threads = 512 + ((@logical_processors - 4) * 16) + + IF @max_worker_threads > 0 + BEGIN + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) + SELECT 49 AS CheckID, + 210 AS Priority, + 'Potential Upcoming Problems' AS FindingGroup, + 'High Number of Connections' AS Finding, + 'https://www.brentozar.com/archive/2014/05/connections-slow-sql-server-threadpool/' AS URL, + 'There are ' + CAST(SUM(1) AS VARCHAR(20)) + ' open connections, which would lead to ' + @LineFeed + 'worker thread exhaustion and THREADPOOL waits' + @LineFeed + 'if they all ran queries at the same time.' AS Details + FROM sys.dm_exec_connections c + HAVING SUM(1) > @max_worker_threads; + END + END + RAISERROR('Finished running investigatory queries',10,1) WITH NOWAIT; @@ -37750,7 +37805,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 'Wait Stats' AS FindingGroup, wNow.wait_type AS Finding, /* IF YOU CHANGE THIS, STUFF WILL BREAK. Other checks look for wait type names in the Finding field. See checks 11, 12 as example. */ N'https://www.sqlskills.com/help/waits/' + LOWER(wNow.wait_type) + '/' AS URL, - 'For ' + CAST(((wNow.wait_time_ms - COALESCE(wBase.wait_time_ms,0)) / 1000) AS NVARCHAR(100)) + ' seconds over the last ' + CASE @Seconds WHEN 0 THEN (CAST(DATEDIFF(dd,@StartSampleTime,@FinishSampleTime) AS NVARCHAR(10)) + ' days') ELSE (CAST(@Seconds AS NVARCHAR(10)) + ' seconds') END + ', SQL Server was waiting on this particular bottleneck.' + @LineFeed + @LineFeed AS Details, + 'For ' + CAST(((wNow.wait_time_ms - COALESCE(wBase.wait_time_ms,0)) / 1000) AS NVARCHAR(100)) + ' seconds over the last ' + CASE @Seconds WHEN 0 THEN (CAST(DATEDIFF(dd,@StartSampleTime,@FinishSampleTime) AS NVARCHAR(10)) + ' days') ELSE (CAST(DATEDIFF(ss, wBase.SampleTime, wNow.SampleTime) AS NVARCHAR(10)) + ' seconds') END + ', SQL Server was waiting on this particular bottleneck.' + @LineFeed + @LineFeed AS Details, 'See the URL for more details on how to mitigate this wait type.' AS HowToStopIt, ((wNow.wait_time_ms - COALESCE(wBase.wait_time_ms,0)) / 1000) AS DetailsInt FROM #WaitStats wNow @@ -37770,7 +37825,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 'Server Performance' AS FindingGroup, 'Poison Wait Detected: ' + wNow.wait_type AS Finding, N'https://www.brentozar.com/go/poison/#' + wNow.wait_type AS URL, - 'For ' + CAST(((wNow.wait_time_ms - COALESCE(wBase.wait_time_ms,0)) / 1000) AS NVARCHAR(100)) + ' seconds over the last ' + CASE @Seconds WHEN 0 THEN (CAST(DATEDIFF(dd,@StartSampleTime,@FinishSampleTime) AS NVARCHAR(10)) + ' days') ELSE (CAST(@Seconds AS NVARCHAR(10)) + ' seconds') END + ', SQL Server was waiting on this particular bottleneck.' + @LineFeed + @LineFeed AS Details, + 'For ' + CAST(((wNow.wait_time_ms - COALESCE(wBase.wait_time_ms,0)) / 1000) AS NVARCHAR(100)) + ' seconds over the last ' + CASE @Seconds WHEN 0 THEN (CAST(DATEDIFF(dd,@StartSampleTime,@FinishSampleTime) AS NVARCHAR(10)) + ' days') ELSE (CAST(DATEDIFF(ss, wBase.SampleTime, wNow.SampleTime) AS NVARCHAR(10)) + ' seconds') END + ', SQL Server was waiting on this particular bottleneck.' + @LineFeed + @LineFeed AS Details, 'See the URL for more details on how to mitigate this wait type.' AS HowToStopIt, ((wNow.wait_time_ms - COALESCE(wBase.wait_time_ms,0)) / 1000) AS DetailsInt FROM #WaitStats wNow diff --git a/Install-Azure.sql b/Install-Azure.sql index ffe645fd3..1e59498f7 100644 --- a/Install-Azure.sql +++ b/Install-Azure.sql @@ -37,7 +37,7 @@ AS SET NOCOUNT ON; SET STATISTICS XML OFF; -SELECT @Version = '8.19', @VersionDate = '20240222'; +SELECT @Version = '8.20', @VersionDate = '20240522'; IF(@VersionCheckMode = 1) BEGIN @@ -888,1531 +888,6 @@ BEGIN END -GO -IF OBJECT_ID('dbo.sp_BlitzBackups') IS NULL - EXEC ('CREATE PROCEDURE dbo.sp_BlitzBackups AS RETURN 0;'); -GO -ALTER PROCEDURE [dbo].[sp_BlitzBackups] - @Help TINYINT = 0 , - @HoursBack INT = 168, - @MSDBName NVARCHAR(256) = 'msdb', - @AGName NVARCHAR(256) = NULL, - @RestoreSpeedFullMBps INT = NULL, - @RestoreSpeedDiffMBps INT = NULL, - @RestoreSpeedLogMBps INT = NULL, - @Debug TINYINT = 0, - @PushBackupHistoryToListener BIT = 0, - @WriteBackupsToListenerName NVARCHAR(256) = NULL, - @WriteBackupsToDatabaseName NVARCHAR(256) = NULL, - @WriteBackupsLastHours INT = 168, - @Version VARCHAR(30) = NULL OUTPUT, - @VersionDate DATETIME = NULL OUTPUT, - @VersionCheckMode BIT = 0 -WITH RECOMPILE -AS - BEGIN - SET NOCOUNT ON; - SET STATISTICS XML OFF; - SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - - SELECT @Version = '8.19', @VersionDate = '20240222'; - - IF(@VersionCheckMode = 1) - BEGIN - RETURN; - END; - - IF @Help = 1 PRINT ' - /* - sp_BlitzBackups from http://FirstResponderKit.org - - This script checks your backups to see how much data you might lose when - this server fails, and how long it might take to recover. - - To learn more, visit http://FirstResponderKit.org where you can download new - versions for free, watch training videos on how it works, get more info on - the findings, contribute your own code, and more. - - Known limitations of this version: - - Only Microsoft-supported versions of SQL Server. Sorry, 2005 and 2000. - - Unknown limitations of this version: - - None. (If we knew them, they would be known. Duh.) - - Changes - for the full list of improvements and fixes in this version, see: - https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/ - - - Parameter explanations: - - @HoursBack INT = 168 How many hours of history to examine, back from now. - You can check just the last 24 hours of backups, for example. - @MSDBName NVARCHAR(255) You can restore MSDB from different servers and check them - centrally. Also useful if you create a DBA utility database - and merge data from several servers in an AG into one DB. - @RestoreSpeedFullMBps INT By default, we use the backup speed from MSDB to guesstimate - how fast your restores will go. If you have done performance - tuning and testing of your backups (or if they horribly go even - slower in your DR environment, and you want to account for - that), then you can pass in different numbers here. - @RestoreSpeedDiffMBps INT See above. - @RestoreSpeedLogMBps INT See above. - - For more documentation: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/ - - MIT License - - Copyright (c) Brent Ozar Unlimited - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE. - - - - - */'; -ELSE -BEGIN -DECLARE @StringToExecute NVARCHAR(MAX) = N'', - @InnerStringToExecute NVARCHAR(MAX) = N'', - @ProductVersion NVARCHAR(128), - @ProductVersionMajor DECIMAL(10, 2), - @ProductVersionMinor DECIMAL(10, 2), - @StartTime DATETIME2, @ResultText NVARCHAR(MAX), - @crlf NVARCHAR(2), - @MoreInfoHeader NVARCHAR(100), - @MoreInfoFooter NVARCHAR(100); - -IF @HoursBack > 0 - SET @HoursBack = @HoursBack * -1; - -IF @WriteBackupsLastHours > 0 - SET @WriteBackupsLastHours = @WriteBackupsLastHours * -1; - -SELECT @crlf = NCHAR(13) + NCHAR(10), - @StartTime = DATEADD(hh, @HoursBack, GETDATE()), - @MoreInfoHeader = N''; - -SET @ProductVersion = CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)); -SELECT @ProductVersionMajor = SUBSTRING(@ProductVersion, 1, CHARINDEX('.', @ProductVersion) + 1), - @ProductVersionMinor = PARSENAME(CONVERT(VARCHAR(32), @ProductVersion), 2); - -CREATE TABLE #Backups -( - id INT IDENTITY(1, 1), - database_name NVARCHAR(128), - database_guid UNIQUEIDENTIFIER, - RPOWorstCaseMinutes DECIMAL(18, 1), - RTOWorstCaseMinutes DECIMAL(18, 1), - RPOWorstCaseBackupSetID INT, - RPOWorstCaseBackupSetFinishTime DATETIME, - RPOWorstCaseBackupSetIDPrior INT, - RPOWorstCaseBackupSetPriorFinishTime DATETIME, - RPOWorstCaseMoreInfoQuery XML, - RTOWorstCaseBackupFileSizeMB DECIMAL(18, 2), - RTOWorstCaseMoreInfoQuery XML, - FullMBpsAvg DECIMAL(18, 2), - FullMBpsMin DECIMAL(18, 2), - FullMBpsMax DECIMAL(18, 2), - FullSizeMBAvg DECIMAL(18, 2), - FullSizeMBMin DECIMAL(18, 2), - FullSizeMBMax DECIMAL(18, 2), - FullCompressedSizeMBAvg DECIMAL(18, 2), - FullCompressedSizeMBMin DECIMAL(18, 2), - FullCompressedSizeMBMax DECIMAL(18, 2), - DiffMBpsAvg DECIMAL(18, 2), - DiffMBpsMin DECIMAL(18, 2), - DiffMBpsMax DECIMAL(18, 2), - DiffSizeMBAvg DECIMAL(18, 2), - DiffSizeMBMin DECIMAL(18, 2), - DiffSizeMBMax DECIMAL(18, 2), - DiffCompressedSizeMBAvg DECIMAL(18, 2), - DiffCompressedSizeMBMin DECIMAL(18, 2), - DiffCompressedSizeMBMax DECIMAL(18, 2), - LogMBpsAvg DECIMAL(18, 2), - LogMBpsMin DECIMAL(18, 2), - LogMBpsMax DECIMAL(18, 2), - LogSizeMBAvg DECIMAL(18, 2), - LogSizeMBMin DECIMAL(18, 2), - LogSizeMBMax DECIMAL(18, 2), - LogCompressedSizeMBAvg DECIMAL(18, 2), - LogCompressedSizeMBMin DECIMAL(18, 2), - LogCompressedSizeMBMax DECIMAL(18, 2) -); - -CREATE TABLE #RTORecoveryPoints -( - id INT IDENTITY(1, 1), - database_name NVARCHAR(128), - database_guid UNIQUEIDENTIFIER, - rto_worst_case_size_mb AS - ( COALESCE(log_file_size_mb, 0) + COALESCE(diff_file_size_mb, 0) + COALESCE(full_file_size_mb, 0)), - rto_worst_case_time_seconds AS - ( COALESCE(log_time_seconds, 0) + COALESCE(diff_time_seconds, 0) + COALESCE(full_time_seconds, 0)), - full_backup_set_id INT, - full_last_lsn NUMERIC(25, 0), - full_backup_set_uuid UNIQUEIDENTIFIER, - full_time_seconds BIGINT, - full_file_size_mb DECIMAL(18, 2), - diff_backup_set_id INT, - diff_last_lsn NUMERIC(25, 0), - diff_time_seconds BIGINT, - diff_file_size_mb DECIMAL(18, 2), - log_backup_set_id INT, - log_last_lsn NUMERIC(25, 0), - log_time_seconds BIGINT, - log_file_size_mb DECIMAL(18, 2), - log_backups INT -); - -CREATE TABLE #Recoverability - ( - Id INT IDENTITY , - DatabaseName NVARCHAR(128), - DatabaseGUID UNIQUEIDENTIFIER, - LastBackupRecoveryModel NVARCHAR(60), - FirstFullBackupSizeMB DECIMAL (18,2), - FirstFullBackupDate DATETIME, - LastFullBackupSizeMB DECIMAL (18,2), - LastFullBackupDate DATETIME, - AvgFullBackupThroughputMB DECIMAL (18,2), - AvgFullBackupDurationSeconds INT, - AvgDiffBackupThroughputMB DECIMAL (18,2), - AvgDiffBackupDurationSeconds INT, - AvgLogBackupThroughputMB DECIMAL (18,2), - AvgLogBackupDurationSeconds INT, - AvgFullSizeMB DECIMAL (18,2), - AvgDiffSizeMB DECIMAL (18,2), - AvgLogSizeMB DECIMAL (18,2), - IsBigDiff AS CASE WHEN (AvgFullSizeMB > 10240. AND ((AvgDiffSizeMB * 100.) / AvgFullSizeMB >= 40.)) THEN 1 ELSE 0 END, - IsBigLog AS CASE WHEN (AvgFullSizeMB > 10240. AND ((AvgLogSizeMB * 100.) / AvgFullSizeMB >= 20.)) THEN 1 ELSE 0 END - ); - -CREATE TABLE #Trending -( - DatabaseName NVARCHAR(128), - DatabaseGUID UNIQUEIDENTIFIER, - [0] DECIMAL(18, 2), - [-1] DECIMAL(18, 2), - [-2] DECIMAL(18, 2), - [-3] DECIMAL(18, 2), - [-4] DECIMAL(18, 2), - [-5] DECIMAL(18, 2), - [-6] DECIMAL(18, 2), - [-7] DECIMAL(18, 2), - [-8] DECIMAL(18, 2), - [-9] DECIMAL(18, 2), - [-10] DECIMAL(18, 2), - [-11] DECIMAL(18, 2), - [-12] DECIMAL(18, 2) -); - - -CREATE TABLE #Warnings -( - Id INT IDENTITY(1, 1) PRIMARY KEY CLUSTERED, - CheckId INT, - Priority INT, - DatabaseName VARCHAR(128), - Finding VARCHAR(256), - Warning VARCHAR(8000) -); - -IF NOT EXISTS(SELECT * FROM sys.databases WHERE name = @MSDBName) - BEGIN - RAISERROR('@MSDBName was specified, but the database does not exist.', 16, 1) WITH NOWAIT; - RETURN; - END - -IF @PushBackupHistoryToListener = 1 -GOTO PushBackupHistoryToListener - - - RAISERROR('Inserting to #Backups', 0, 1) WITH NOWAIT; - - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - - SET @StringToExecute += N'WITH Backups AS (SELECT bs.database_name, bs.database_guid, bs.type AS backup_type ' + @crlf - + ' , MBpsAvg = CAST(AVG(( bs.backup_size / ( CASE WHEN DATEDIFF(ss, bs.backup_start_date, bs.backup_finish_date) = 0 THEN 1 ELSE DATEDIFF(ss, bs.backup_start_date, bs.backup_finish_date) END ) / 1048576 )) AS INT) ' + @crlf - + ' , MBpsMin = CAST(MIN(( bs.backup_size / ( CASE WHEN DATEDIFF(ss, bs.backup_start_date, bs.backup_finish_date) = 0 THEN 1 ELSE DATEDIFF(ss, bs.backup_start_date, bs.backup_finish_date) END ) / 1048576 )) AS INT) ' + @crlf - + ' , MBpsMax = CAST(MAX(( bs.backup_size / ( CASE WHEN DATEDIFF(ss, bs.backup_start_date, bs.backup_finish_date) = 0 THEN 1 ELSE DATEDIFF(ss, bs.backup_start_date, bs.backup_finish_date) END ) / 1048576 )) AS INT) ' + @crlf - + ' , SizeMBAvg = AVG(backup_size / 1048576.0) ' + @crlf - + ' , SizeMBMin = MIN(backup_size / 1048576.0) ' + @crlf - + ' , SizeMBMax = MAX(backup_size / 1048576.0) ' + @crlf - + ' , CompressedSizeMBAvg = AVG(compressed_backup_size / 1048576.0) ' + @crlf - + ' , CompressedSizeMBMin = MIN(compressed_backup_size / 1048576.0) ' + @crlf - + ' , CompressedSizeMBMax = MAX(compressed_backup_size / 1048576.0) ' + @crlf; - - - SET @StringToExecute += N' FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset bs ' + @crlf - + N' WHERE bs.backup_finish_date >= @StartTime AND bs.is_damaged = 0 ' + @crlf - + N' GROUP BY bs.database_name, bs.database_guid, bs.type)' + @crlf; - - SET @StringToExecute += + N'INSERT INTO #Backups(database_name, database_guid, ' + @crlf - + N' FullMBpsAvg, FullMBpsMin, FullMBpsMax, FullSizeMBAvg, FullSizeMBMin, FullSizeMBMax, FullCompressedSizeMBAvg, FullCompressedSizeMBMin, FullCompressedSizeMBMax, ' + @crlf - + N' DiffMBpsAvg, DiffMBpsMin, DiffMBpsMax, DiffSizeMBAvg, DiffSizeMBMin, DiffSizeMBMax, DiffCompressedSizeMBAvg, DiffCompressedSizeMBMin, DiffCompressedSizeMBMax, ' + @crlf - + N' LogMBpsAvg, LogMBpsMin, LogMBpsMax, LogSizeMBAvg, LogSizeMBMin, LogSizeMBMax, LogCompressedSizeMBAvg, LogCompressedSizeMBMin, LogCompressedSizeMBMax ) ' + @crlf - + N'SELECT bF.database_name, bF.database_guid ' + @crlf - + N' , bF.MBpsAvg AS FullMBpsAvg ' + @crlf - + N' , bF.MBpsMin AS FullMBpsMin ' + @crlf - + N' , bF.MBpsMax AS FullMBpsMax ' + @crlf - + N' , bF.SizeMBAvg AS FullSizeMBAvg ' + @crlf - + N' , bF.SizeMBMin AS FullSizeMBMin ' + @crlf - + N' , bF.SizeMBMax AS FullSizeMBMax ' + @crlf - + N' , bF.CompressedSizeMBAvg AS FullCompressedSizeMBAvg ' + @crlf - + N' , bF.CompressedSizeMBMin AS FullCompressedSizeMBMin ' + @crlf - + N' , bF.CompressedSizeMBMax AS FullCompressedSizeMBMax ' + @crlf - + N' , bD.MBpsAvg AS DiffMBpsAvg ' + @crlf - + N' , bD.MBpsMin AS DiffMBpsMin ' + @crlf - + N' , bD.MBpsMax AS DiffMBpsMax ' + @crlf - + N' , bD.SizeMBAvg AS DiffSizeMBAvg ' + @crlf - + N' , bD.SizeMBMin AS DiffSizeMBMin ' + @crlf - + N' , bD.SizeMBMax AS DiffSizeMBMax ' + @crlf - + N' , bD.CompressedSizeMBAvg AS DiffCompressedSizeMBAvg ' + @crlf - + N' , bD.CompressedSizeMBMin AS DiffCompressedSizeMBMin ' + @crlf - + N' , bD.CompressedSizeMBMax AS DiffCompressedSizeMBMax ' + @crlf - + N' , bL.MBpsAvg AS LogMBpsAvg ' + @crlf - + N' , bL.MBpsMin AS LogMBpsMin ' + @crlf - + N' , bL.MBpsMax AS LogMBpsMax ' + @crlf - + N' , bL.SizeMBAvg AS LogSizeMBAvg ' + @crlf - + N' , bL.SizeMBMin AS LogSizeMBMin ' + @crlf - + N' , bL.SizeMBMax AS LogSizeMBMax ' + @crlf - + N' , bL.CompressedSizeMBAvg AS LogCompressedSizeMBAvg ' + @crlf - + N' , bL.CompressedSizeMBMin AS LogCompressedSizeMBMin ' + @crlf - + N' , bL.CompressedSizeMBMax AS LogCompressedSizeMBMax ' + @crlf - + N' FROM Backups bF ' + @crlf - + N' LEFT OUTER JOIN Backups bD ON bF.database_name = bD.database_name AND bF.database_guid = bD.database_guid AND bD.backup_type = ''I''' + @crlf - + N' LEFT OUTER JOIN Backups bL ON bF.database_name = bL.database_name AND bF.database_guid = bL.database_guid AND bL.backup_type = ''L''' + @crlf - + N' WHERE bF.backup_type = ''D''; ' + @crlf; - - IF @Debug = 1 - PRINT @StringToExecute; - - EXEC sys.sp_executesql @StringToExecute, N'@StartTime DATETIME2', @StartTime; - - - RAISERROR('Updating #Backups with worst RPO case', 0, 1) WITH NOWAIT; - - SET @StringToExecute =N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - - SET @StringToExecute += N' - SELECT bs.database_name, bs.database_guid, bs.backup_set_id, bsPrior.backup_set_id AS backup_set_id_prior, - bs.backup_finish_date, bsPrior.backup_finish_date AS backup_finish_date_prior, - DATEDIFF(ss, bsPrior.backup_finish_date, bs.backup_finish_date) AS backup_gap_seconds - INTO #backup_gaps - FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS bs - CROSS APPLY ( - SELECT TOP 1 bs1.backup_set_id, bs1.backup_finish_date - FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS bs1 - WHERE bs.database_name = bs1.database_name - AND bs.database_guid = bs1.database_guid - AND bs.backup_finish_date > bs1.backup_finish_date - AND bs.backup_set_id > bs1.backup_set_id - ORDER BY bs1.backup_finish_date DESC, bs1.backup_set_id DESC - ) bsPrior - WHERE bs.backup_finish_date > @StartTime - - CREATE CLUSTERED INDEX cx_backup_gaps ON #backup_gaps (database_name, database_guid, backup_set_id, backup_finish_date, backup_gap_seconds); - - WITH max_gaps AS ( - SELECT g.database_name, g.database_guid, g.backup_set_id, g.backup_set_id_prior, g.backup_finish_date_prior, - g.backup_finish_date, MAX(g.backup_gap_seconds) AS max_backup_gap_seconds - FROM #backup_gaps AS g - GROUP BY g.database_name, g.database_guid, g.backup_set_id, g.backup_set_id_prior, g.backup_finish_date_prior, g.backup_finish_date - ) - UPDATE #Backups - SET RPOWorstCaseMinutes = bg.max_backup_gap_seconds / 60.0 - , RPOWorstCaseBackupSetID = bg.backup_set_id - , RPOWorstCaseBackupSetFinishTime = bg.backup_finish_date - , RPOWorstCaseBackupSetIDPrior = bg.backup_set_id_prior - , RPOWorstCaseBackupSetPriorFinishTime = bg.backup_finish_date_prior - FROM #Backups b - INNER HASH JOIN max_gaps bg ON b.database_name = bg.database_name AND b.database_guid = bg.database_guid - LEFT OUTER HASH JOIN max_gaps bgBigger ON bg.database_name = bgBigger.database_name AND bg.database_guid = bgBigger.database_guid AND bg.max_backup_gap_seconds < bgBigger.max_backup_gap_seconds - WHERE bgBigger.backup_set_id IS NULL; - '; - IF @Debug = 1 - PRINT @StringToExecute; - - EXEC sys.sp_executesql @StringToExecute, N'@StartTime DATETIME2', @StartTime; - - RAISERROR('Updating #Backups with worst RPO case queries', 0, 1) WITH NOWAIT; - - UPDATE #Backups - SET RPOWorstCaseMoreInfoQuery = @MoreInfoHeader + N'SELECT * ' + @crlf - + N' FROM ' + QUOTENAME(@MSDBName) + '.dbo.backupset ' + @crlf - + N' WHERE database_name = ''' + database_name + ''' ' + @crlf - + N' AND database_guid = ''' + CAST(database_guid AS NVARCHAR(50)) + ''' ' + @crlf - + N' AND backup_finish_date >= DATEADD(hh, -2, ''' + CAST(CONVERT(DATETIME, RPOWorstCaseBackupSetPriorFinishTime, 102) AS NVARCHAR(100)) + ''') ' + @crlf - + N' AND backup_finish_date <= DATEADD(hh, 2, ''' + CAST(CONVERT(DATETIME, RPOWorstCaseBackupSetPriorFinishTime, 102) AS NVARCHAR(100)) + ''') ' + @crlf - + N' ORDER BY backup_finish_date;' - + @MoreInfoFooter; - - -/* RTO */ - -RAISERROR('Gathering RTO information', 0, 1) WITH NOWAIT; - - - SET @StringToExecute =N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - - SET @StringToExecute += N' - INSERT INTO #RTORecoveryPoints(database_name, database_guid, log_last_lsn) - SELECT database_name, database_guid, MAX(last_lsn) AS log_last_lsn - FROM ' + QUOTENAME(@MSDBName) + '.dbo.backupset bLastLog - WHERE type = ''L'' - AND bLastLog.backup_finish_date >= @StartTime - GROUP BY database_name, database_guid; - '; - - IF @Debug = 1 - PRINT @StringToExecute; - - EXEC sys.sp_executesql @StringToExecute, N'@StartTime DATETIME2', @StartTime; - -/* Find the most recent full backups for those logs */ - -RAISERROR('Updating #RTORecoveryPoints', 0, 1) WITH NOWAIT; - - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - - SET @StringToExecute += N' - UPDATE #RTORecoveryPoints - SET log_backup_set_id = bLasted.backup_set_id - ,full_backup_set_id = bLasted.backup_set_id - ,full_last_lsn = bLasted.last_lsn - ,full_backup_set_uuid = bLasted.backup_set_uuid - FROM #RTORecoveryPoints rp - CROSS APPLY ( - SELECT TOP 1 bLog.backup_set_id AS backup_set_id_log, bLastFull.backup_set_id, bLastFull.last_lsn, bLastFull.backup_set_uuid, bLastFull.database_guid, bLastFull.database_name - FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset bLog - INNER JOIN ' + QUOTENAME(@MSDBName) + N'.dbo.backupset bLastFull - ON bLog.database_guid = bLastFull.database_guid - AND bLog.database_name = bLastFull.database_name - AND bLog.first_lsn > bLastFull.last_lsn - AND bLastFull.type = ''D'' - WHERE rp.database_guid = bLog.database_guid - AND rp.database_name = bLog.database_name - ) bLasted - LEFT OUTER JOIN ' + QUOTENAME(@MSDBName) + N'.dbo.backupset bLaterFulls ON bLasted.database_guid = bLaterFulls.database_guid AND bLasted.database_name = bLaterFulls.database_name - AND bLasted.last_lsn < bLaterFulls.last_lsn - AND bLaterFulls.first_lsn < bLasted.last_lsn - AND bLaterFulls.type = ''D'' - WHERE bLaterFulls.backup_set_id IS NULL; - '; - - IF @Debug = 1 - PRINT @StringToExecute; - - EXEC sys.sp_executesql @StringToExecute; - -/* Add any full backups in the StartDate range that weren't part of the above log backup chain */ - -RAISERROR('Add any full backups in the StartDate range that weren''t part of the above log backup chain', 0, 1) WITH NOWAIT; - - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - - SET @StringToExecute += N' - INSERT INTO #RTORecoveryPoints(database_name, database_guid, full_backup_set_id, full_last_lsn, full_backup_set_uuid) - SELECT bFull.database_name, bFull.database_guid, bFull.backup_set_id, bFull.last_lsn, bFull.backup_set_uuid - FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset bFull - LEFT OUTER JOIN #RTORecoveryPoints rp ON bFull.backup_set_uuid = rp.full_backup_set_uuid - WHERE bFull.type = ''D'' - AND bFull.backup_finish_date IS NOT NULL - AND rp.full_backup_set_uuid IS NULL - AND bFull.backup_finish_date >= @StartTime; - '; - - IF @Debug = 1 - PRINT @StringToExecute; - - EXEC sys.sp_executesql @StringToExecute, N'@StartTime DATETIME2', @StartTime; - -/* Fill out the most recent log for that full, but before the next full */ - -RAISERROR('Fill out the most recent log for that full, but before the next full', 0, 1) WITH NOWAIT; - - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - - SET @StringToExecute += N' - UPDATE rp - SET log_last_lsn = (SELECT MAX(last_lsn) FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset bLog WHERE bLog.first_lsn >= rp.full_last_lsn AND bLog.first_lsn <= rpNextFull.full_last_lsn AND bLog.type = ''L'') - FROM #RTORecoveryPoints rp - INNER JOIN #RTORecoveryPoints rpNextFull ON rp.database_guid = rpNextFull.database_guid AND rp.database_name = rpNextFull.database_name - AND rp.full_last_lsn < rpNextFull.full_last_lsn - LEFT OUTER JOIN #RTORecoveryPoints rpEarlierFull ON rp.database_guid = rpEarlierFull.database_guid AND rp.database_name = rpEarlierFull.database_name - AND rp.full_last_lsn < rpEarlierFull.full_last_lsn - AND rpNextFull.full_last_lsn > rpEarlierFull.full_last_lsn - WHERE rpEarlierFull.full_backup_set_id IS NULL; - '; - - IF @Debug = 1 - PRINT @StringToExecute; - - EXEC sys.sp_executesql @StringToExecute; - -/* Fill out a diff in that range */ - -RAISERROR('Fill out a diff in that range', 0, 1) WITH NOWAIT; - - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - - SET @StringToExecute += N' - UPDATE #RTORecoveryPoints - SET diff_last_lsn = (SELECT TOP 1 bDiff.last_lsn FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset bDiff - WHERE rp.database_guid = bDiff.database_guid AND rp.database_name = bDiff.database_name - AND bDiff.type = ''I'' - AND bDiff.last_lsn < rp.log_last_lsn - AND rp.full_backup_set_uuid = bDiff.differential_base_guid - ORDER BY bDiff.last_lsn DESC) - FROM #RTORecoveryPoints rp - WHERE diff_last_lsn IS NULL; - '; - - IF @Debug = 1 - PRINT @StringToExecute; - - EXEC sys.sp_executesql @StringToExecute; - -/* Get time & size totals for full & diff */ - -RAISERROR('Get time & size totals for full & diff', 0, 1) WITH NOWAIT; - - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - - SET @StringToExecute += N' - UPDATE #RTORecoveryPoints - SET full_time_seconds = DATEDIFF(ss,bFull.backup_start_date, bFull.backup_finish_date) - , full_file_size_mb = bFull.backup_size / 1048576.0 - , diff_backup_set_id = bDiff.backup_set_id - , diff_time_seconds = DATEDIFF(ss,bDiff.backup_start_date, bDiff.backup_finish_date) - , diff_file_size_mb = bDiff.backup_size / 1048576.0 - FROM #RTORecoveryPoints rp - INNER JOIN ' + QUOTENAME(@MSDBName) + N'.dbo.backupset bFull ON rp.database_guid = bFull.database_guid AND rp.database_name = bFull.database_name AND rp.full_last_lsn = bFull.last_lsn - LEFT OUTER JOIN ' + QUOTENAME(@MSDBName) + N'.dbo.backupset bDiff ON rp.database_guid = bDiff.database_guid AND rp.database_name = bDiff.database_name AND rp.diff_last_lsn = bDiff.last_lsn AND bDiff.last_lsn IS NOT NULL; - '; - - IF @Debug = 1 - PRINT @StringToExecute; - - EXEC sys.sp_executesql @StringToExecute; - - -/* Get time & size totals for logs */ - -RAISERROR('Get time & size totals for logs', 0, 1) WITH NOWAIT; - - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - - SET @StringToExecute += N' - WITH LogTotals AS ( - SELECT rp.id, log_time_seconds = SUM(DATEDIFF(ss,bLog.backup_start_date, bLog.backup_finish_date)) - , log_file_size = SUM(bLog.backup_size) - , SUM(1) AS log_backups - FROM #RTORecoveryPoints rp - INNER JOIN ' + QUOTENAME(@MSDBName) + N'.dbo.backupset bLog ON rp.database_guid = bLog.database_guid AND rp.database_name = bLog.database_name AND bLog.type = ''L'' - AND bLog.first_lsn > COALESCE(rp.diff_last_lsn, rp.full_last_lsn) - AND bLog.first_lsn <= rp.log_last_lsn - GROUP BY rp.id - ) - UPDATE #RTORecoveryPoints - SET log_time_seconds = lt.log_time_seconds - , log_file_size_mb = lt.log_file_size / 1048576.0 - , log_backups = lt.log_backups - FROM #RTORecoveryPoints rp - INNER JOIN LogTotals lt ON rp.id = lt.id; - '; - - IF @Debug = 1 - PRINT @StringToExecute; - - EXEC sys.sp_executesql @StringToExecute; - -RAISERROR('Gathering RTO worst cases', 0, 1) WITH NOWAIT; - - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - - SET @StringToExecute += N' - WITH WorstCases AS ( - SELECT rp.* - FROM #RTORecoveryPoints rp - LEFT OUTER JOIN #RTORecoveryPoints rpNewer - ON rp.database_guid = rpNewer.database_guid - AND rp.database_name = rpNewer.database_name - AND rp.full_last_lsn < rpNewer.full_last_lsn - AND rpNewer.rto_worst_case_size_mb = (SELECT TOP 1 rto_worst_case_size_mb FROM #RTORecoveryPoints s WHERE rp.database_guid = s.database_guid AND rp.database_name = s.database_name ORDER BY rto_worst_case_size_mb DESC) - WHERE rp.rto_worst_case_size_mb = (SELECT TOP 1 rto_worst_case_size_mb FROM #RTORecoveryPoints s WHERE rp.database_guid = s.database_guid AND rp.database_name = s.database_name ORDER BY rto_worst_case_size_mb DESC) - /* OR rp.rto_worst_case_time_seconds = (SELECT TOP 1 rto_worst_case_time_seconds FROM #RTORecoveryPoints s WHERE rp.database_guid = s.database_guid AND rp.database_name = s.database_name ORDER BY rto_worst_case_time_seconds DESC) */ - AND rpNewer.database_guid IS NULL - ) - UPDATE #Backups - SET RTOWorstCaseMinutes = - /* Fulls */ - (CASE WHEN @RestoreSpeedFullMBps IS NULL - THEN wc.full_time_seconds / 60.0 - ELSE @RestoreSpeedFullMBps / wc.full_file_size_mb - END) - - /* Diffs, which might not have been taken */ - + (CASE WHEN @RestoreSpeedDiffMBps IS NOT NULL AND wc.diff_file_size_mb IS NOT NULL - THEN @RestoreSpeedDiffMBps / wc.diff_file_size_mb - ELSE COALESCE(wc.diff_time_seconds,0) / 60.0 - END) - - /* Logs, which might not have been taken */ - + (CASE WHEN @RestoreSpeedLogMBps IS NOT NULL AND wc.log_file_size_mb IS NOT NULL - THEN @RestoreSpeedLogMBps / wc.log_file_size_mb - ELSE COALESCE(wc.log_time_seconds,0) / 60.0 - END) - , RTOWorstCaseBackupFileSizeMB = wc.rto_worst_case_size_mb - FROM #Backups b - INNER JOIN WorstCases wc - ON b.database_guid = wc.database_guid - AND b.database_name = wc.database_name; - '; - - IF @Debug = 1 - PRINT @StringToExecute; - - EXEC sys.sp_executesql @StringToExecute, N'@RestoreSpeedFullMBps INT, @RestoreSpeedDiffMBps INT, @RestoreSpeedLogMBps INT', @RestoreSpeedFullMBps, @RestoreSpeedDiffMBps, @RestoreSpeedLogMBps; - - - -/*Populating Recoverability*/ - - - /*Get distinct list of databases*/ - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - - SET @StringToExecute += N' - SELECT DISTINCT b.database_name, database_guid - FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b;' - - IF @Debug = 1 - PRINT @StringToExecute; - - INSERT #Recoverability ( DatabaseName, DatabaseGUID ) - EXEC sys.sp_executesql @StringToExecute; - - - /*Find most recent recovery model, backup size, and backup date*/ - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - - SET @StringToExecute += N' - UPDATE r - SET r.LastBackupRecoveryModel = ca.recovery_model, - r.LastFullBackupSizeMB = ca.compressed_backup_size, - r.LastFullBackupDate = ca.backup_finish_date - FROM #Recoverability r - CROSS APPLY ( - SELECT TOP 1 b.recovery_model, (b.compressed_backup_size / 1048576.0) AS compressed_backup_size, b.backup_finish_date - FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b - WHERE r.DatabaseName = b.database_name - AND r.DatabaseGUID = b.database_guid - AND b.type = ''D'' - AND b.backup_finish_date > @StartTime - ORDER BY b.backup_finish_date DESC - ) ca;' - - IF @Debug = 1 - PRINT @StringToExecute; - - EXEC sys.sp_executesql @StringToExecute, N'@StartTime DATETIME2', @StartTime; - - /*Find first backup size and date*/ - SET @StringToExecute =N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - - SET @StringToExecute += N' - UPDATE r - SET r.FirstFullBackupSizeMB = ca.compressed_backup_size, - r.FirstFullBackupDate = ca.backup_finish_date - FROM #Recoverability r - CROSS APPLY ( - SELECT TOP 1 (b.compressed_backup_size / 1048576.0) AS compressed_backup_size, b.backup_finish_date - FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b - WHERE r.DatabaseName = b.database_name - AND r.DatabaseGUID = b.database_guid - AND b.type = ''D'' - AND b.backup_finish_date > @StartTime - ORDER BY b.backup_finish_date ASC - ) ca;' - - IF @Debug = 1 - PRINT @StringToExecute; - - EXEC sys.sp_executesql @StringToExecute, N'@StartTime DATETIME2', @StartTime; - - - /*Find average backup throughputs for full, diff, and log*/ - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - - SET @StringToExecute += N' - UPDATE r - SET r.AvgFullBackupThroughputMB = ca_full.AvgFullSpeed, - r.AvgDiffBackupThroughputMB = ca_diff.AvgDiffSpeed, - r.AvgLogBackupThroughputMB = ca_log.AvgLogSpeed, - r.AvgFullBackupDurationSeconds = AvgFullDuration, - r.AvgDiffBackupDurationSeconds = AvgDiffDuration, - r.AvgLogBackupDurationSeconds = AvgLogDuration - FROM #Recoverability AS r - OUTER APPLY ( - SELECT b.database_name, - AVG( b.compressed_backup_size / ( DATEDIFF(ss, b.backup_start_date, b.backup_finish_date) ) / 1048576.0 ) AS AvgFullSpeed, - AVG( DATEDIFF(ss, b.backup_start_date, b.backup_finish_date) ) AS AvgFullDuration - FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset b - WHERE r.DatabaseName = b.database_name - AND r.DatabaseGUID = b.database_guid - AND b.type = ''D'' - AND DATEDIFF(SECOND, b.backup_start_date, b.backup_finish_date) > 0 - AND b.backup_finish_date > @StartTime - GROUP BY b.database_name - ) ca_full - OUTER APPLY ( - SELECT b.database_name, - AVG( b.compressed_backup_size / ( DATEDIFF(ss, b.backup_start_date, b.backup_finish_date) ) / 1048576.0 ) AS AvgDiffSpeed, - AVG( DATEDIFF(ss, b.backup_start_date, b.backup_finish_date) ) AS AvgDiffDuration - FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset b - WHERE r.DatabaseName = b.database_name - AND r.DatabaseGUID = b.database_guid - AND b.type = ''I'' - AND DATEDIFF(SECOND, b.backup_start_date, b.backup_finish_date) > 0 - AND b.backup_finish_date > @StartTime - GROUP BY b.database_name - ) ca_diff - OUTER APPLY ( - SELECT b.database_name, - AVG( b.compressed_backup_size / ( DATEDIFF(ss, b.backup_start_date, b.backup_finish_date) ) / 1048576.0 ) AS AvgLogSpeed, - AVG( DATEDIFF(ss, b.backup_start_date, b.backup_finish_date) ) AS AvgLogDuration - FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset b - WHERE r.DatabaseName = b.database_name - AND r.DatabaseGUID = b.database_guid - AND b.type = ''L'' - AND DATEDIFF(SECOND, b.backup_start_date, b.backup_finish_date) > 0 - AND b.backup_finish_date > @StartTime - GROUP BY b.database_name - ) ca_log;' - - IF @Debug = 1 - PRINT @StringToExecute; - - EXEC sys.sp_executesql @StringToExecute, N'@StartTime DATETIME2', @StartTime; - - - /*Find max and avg diff and log sizes*/ - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - - SET @StringToExecute += N' - UPDATE r - SET r.AvgFullSizeMB = fulls.avg_full_size, - r.AvgDiffSizeMB = diffs.avg_diff_size, - r.AvgLogSizeMB = logs.avg_log_size - FROM #Recoverability AS r - OUTER APPLY ( - SELECT b.database_name, AVG(b.compressed_backup_size / 1048576.0) AS avg_full_size - FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b - WHERE r.DatabaseName = b.database_name - AND r.DatabaseGUID = b.database_guid - AND b.type = ''D'' - AND b.backup_finish_date > @StartTime - GROUP BY b.database_name - ) AS fulls - OUTER APPLY ( - SELECT b.database_name, AVG(b.compressed_backup_size / 1048576.0) AS avg_diff_size - FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b - WHERE r.DatabaseName = b.database_name - AND r.DatabaseGUID = b.database_guid - AND b.type = ''I'' - AND b.backup_finish_date > @StartTime - GROUP BY b.database_name - ) AS diffs - OUTER APPLY ( - SELECT b.database_name, AVG(b.compressed_backup_size / 1048576.0) AS avg_log_size - FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b - WHERE r.DatabaseName = b.database_name - AND r.DatabaseGUID = b.database_guid - AND b.type = ''L'' - AND b.backup_finish_date > @StartTime - GROUP BY b.database_name - ) AS logs;' - - IF @Debug = 1 - PRINT @StringToExecute; - - EXEC sys.sp_executesql @StringToExecute, N'@StartTime DATETIME2', @StartTime; - -/*Trending - only works if backupfile is populated, which means in msdb */ -IF @MSDBName = N'msdb' -BEGIN - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' --+ @crlf; - - SET @StringToExecute += N' - SELECT p.DatabaseName, - p.DatabaseGUID, - p.[0], - p.[-1], - p.[-2], - p.[-3], - p.[-4], - p.[-5], - p.[-6], - p.[-7], - p.[-8], - p.[-9], - p.[-10], - p.[-11], - p.[-12] - FROM ( SELECT b.database_name AS DatabaseName, - b.database_guid AS DatabaseGUID, - DATEDIFF(MONTH, @StartTime, b.backup_start_date) AS MonthsAgo , - CONVERT(DECIMAL(18, 2), AVG(bf.file_size / 1048576.0)) AS AvgSizeMB - FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b - INNER JOIN ' + QUOTENAME(@MSDBName) + N'.dbo.backupfile AS bf - ON b.backup_set_id = bf.backup_set_id - WHERE b.database_name NOT IN ( ''master'', ''msdb'', ''model'', ''tempdb'' ) - AND bf.file_type = ''D'' - AND b.backup_start_date >= DATEADD(YEAR, -1, @StartTime) - AND b.backup_start_date <= SYSDATETIME() - GROUP BY b.database_name, - b.database_guid, - DATEDIFF(mm, @StartTime, b.backup_start_date) - ) AS bckstat PIVOT ( SUM(bckstat.AvgSizeMB) FOR bckstat.MonthsAgo IN ( [0], [-1], [-2], [-3], [-4], [-5], [-6], [-7], [-8], [-9], [-10], [-11], [-12] ) ) AS p - ORDER BY p.DatabaseName; - ' - - IF @Debug = 1 - PRINT @StringToExecute; - - INSERT #Trending ( DatabaseName, DatabaseGUID, [0], [-1], [-2], [-3], [-4], [-5], [-6], [-7], [-8], [-9], [-10], [-11], [-12] ) - EXEC sys.sp_executesql @StringToExecute, N'@StartTime DATETIME2', @StartTime; - -END - -/*End Trending*/ - -/*End populating Recoverability*/ - -RAISERROR('Returning data', 0, 1) WITH NOWAIT; - - SELECT b.* - FROM #Backups AS b - ORDER BY b.database_name; - - SELECT r.*, - t.[0], t.[-1], t.[-2], t.[-3], t.[-4], t.[-5], t.[-6], t.[-7], t.[-8], t.[-9], t.[-10], t.[-11], t.[-12] - FROM #Recoverability AS r - LEFT JOIN #Trending t - ON r.DatabaseName = t.DatabaseName - AND r.DatabaseGUID = t.DatabaseGUID - WHERE r.LastBackupRecoveryModel IS NOT NULL - ORDER BY r.DatabaseName - - -RAISERROR('Rules analysis starting', 0, 1) WITH NOWAIT; - -/*Looking for out of band backups by finding most common backup operator user_name and noting backups taken by other user_names*/ - - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - - SET @StringToExecute += N' - WITH common_people AS ( - SELECT TOP 1 b.user_name, COUNT_BIG(*) AS Records - FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b - GROUP BY b.user_name - ORDER BY Records DESC - ) - SELECT - 1 AS CheckId, - 100 AS [Priority], - b.database_name AS [Database Name], - ''Non-Agent backups taken'' AS [Finding], - ''The database '' + QUOTENAME(b.database_name) + '' has been backed up by '' + QUOTENAME(b.user_name) + '' '' + CONVERT(VARCHAR(10), COUNT(*)) + '' times.'' AS [Warning] - FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b - WHERE b.user_name NOT LIKE ''%Agent%'' AND b.user_name NOT LIKE ''%AGENT%'' - AND NOT EXISTS ( - SELECT 1 - FROM common_people AS cp - WHERE cp.user_name = b.user_name - ) - GROUP BY b.database_name, b.user_name - HAVING COUNT(*) > 1;' + @crlf; - - IF @Debug = 1 - PRINT @StringToExecute; - - INSERT #Warnings (CheckId, Priority, DatabaseName, Finding, Warning ) - EXEC sys.sp_executesql @StringToExecute; - - /*Looking for compatibility level changing. Only looking for databases that have changed more than twice (It''s possible someone may have changed up, had CE problems, and then changed back)*/ - - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - - SET @StringToExecute += N'SELECT - 2 AS CheckId, - 100 AS [Priority], - b.database_name AS [Database Name], - ''Compatibility level changing'' AS [Finding], - ''The database '' + QUOTENAME(b.database_name) + '' has changed compatibility levels '' + CONVERT(VARCHAR(10), COUNT(DISTINCT b.compatibility_level)) + '' times.'' AS [Warning] - FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b - GROUP BY b.database_name - HAVING COUNT(DISTINCT b.compatibility_level) > 2;' + @crlf; - - IF @Debug = 1 - PRINT @StringToExecute; - - INSERT #Warnings ( CheckId, Priority, DatabaseName, Finding, Warning ) - EXEC sys.sp_executesql @StringToExecute; - - /*Looking for password protected backups. This hasn''t been a popular option ever, and was largely replaced by encrypted backups, but it''s simple to check for.*/ - - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - - SET @StringToExecute += N'SELECT - 3 AS CheckId, - 100 AS [Priority], - b.database_name AS [Database Name], - ''Password backups'' AS [Finding], - ''The database '' + QUOTENAME(b.database_name) + '' has been backed up with a password '' + CONVERT(VARCHAR(10), COUNT(*)) + '' times. Who has the password?'' AS [Warning] - FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b - WHERE b.is_password_protected = 1 - GROUP BY b.database_name;' + @crlf; - - IF @Debug = 1 - PRINT @StringToExecute; - - INSERT #Warnings ( CheckId, Priority, DatabaseName, Finding, Warning ) - EXEC sys.sp_executesql @StringToExecute; - - /*Looking for snapshot backups. There are legit reasons for these, but we should flag them so the questions get asked. What questions? Good question.*/ - - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - - SET @StringToExecute += N'SELECT - 4 AS CheckId, - 100 AS [Priority], - b.database_name AS [Database Name], - ''Snapshot backups'' AS [Finding], - ''The database '' + QUOTENAME(b.database_name) + '' has had '' + CONVERT(VARCHAR(10), COUNT(*)) + '' snapshot backups. This message is purely informational.'' AS [Warning] - FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b - WHERE b.is_snapshot = 1 - GROUP BY b.database_name;' + @crlf; - - IF @Debug = 1 - PRINT @StringToExecute; - - INSERT #Warnings ( CheckId, Priority, DatabaseName, Finding, Warning ) - EXEC sys.sp_executesql @StringToExecute; - - /*It''s fine to take backups of read only databases, but it''s not always necessary (there''s no new data, after all).*/ - - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - - SET @StringToExecute += N'SELECT - 5 AS CheckId, - 100 AS [Priority], - b.database_name AS [Database Name], - ''Read only state backups'' AS [Finding], - ''The database '' + QUOTENAME(b.database_name) + '' has been backed up '' + CONVERT(VARCHAR(10), COUNT(*)) + '' times while in a read-only state. This can be normal if it''''s a secondary, but a bit odd otherwise.'' AS [Warning] - FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b - WHERE b.is_readonly = 1 - GROUP BY b.database_name;' + @crlf; - - IF @Debug = 1 - PRINT @StringToExecute; - - INSERT #Warnings ( CheckId, Priority, DatabaseName, Finding, Warning ) - EXEC sys.sp_executesql @StringToExecute; - - /*So, I''ve come across people who think they need to change their database to single user mode to take a backup. Or that doing that will help something. I just need to know, here.*/ - - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - - SET @StringToExecute += N'SELECT - 6 AS CheckId, - 100 AS [Priority], - b.database_name AS [Database Name], - ''Single user mode backups'' AS [Finding], - ''The database '' + QUOTENAME(b.database_name) + '' has been backed up '' + CONVERT(VARCHAR(10), COUNT(*)) + '' times while in single-user mode. This is really weird! Make sure your backup process doesn''''t include a mode change anywhere.'' AS [Warning] - FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b - WHERE b.is_single_user = 1 - GROUP BY b.database_name;' + @crlf; - - IF @Debug = 1 - PRINT @StringToExecute; - - INSERT #Warnings ( CheckId, Priority, DatabaseName, Finding, Warning ) - EXEC sys.sp_executesql @StringToExecute; - - /*C''mon, it''s 2017. Take your backups with CHECKSUMS, people.*/ - - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - - SET @StringToExecute += N'SELECT - 7 AS CheckId, - 100 AS [Priority], - b.database_name AS [Database Name], - ''No CHECKSUMS'' AS [Finding], - ''The database '' + QUOTENAME(b.database_name) + '' has been backed up '' + CONVERT(VARCHAR(10), COUNT(*)) + '' times without CHECKSUMS in the past 30 days. CHECKSUMS can help alert you to corruption errors.'' AS [Warning] - FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b - WHERE b.has_backup_checksums = 0 - AND b.backup_finish_date >= DATEADD(DAY, -30, SYSDATETIME()) - GROUP BY b.database_name;' + @crlf; - - IF @Debug = 1 - PRINT @StringToExecute; - - INSERT #Warnings ( CheckId, Priority, DatabaseName, Finding, Warning ) - EXEC sys.sp_executesql @StringToExecute; - - /*Damaged is a Black Flag album. You don''t want your backups to be like a Black Flag album. */ - - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - - SET @StringToExecute += N'SELECT - 8 AS CheckId, - 100 AS [Priority], - b.database_name AS [Database Name], - ''Damaged backups'' AS [Finding], - ''The database '' + QUOTENAME(b.database_name) + '' has had '' + CONVERT(VARCHAR(10), COUNT(*)) + '' damaged backups taken without stopping to throw an error. This is done by specifying CONTINUE_AFTER_ERROR in your BACKUP commands.'' AS [Warning] - FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b - WHERE b.is_damaged = 1 - GROUP BY b.database_name;' + @crlf; - - IF @Debug = 1 - PRINT @StringToExecute; - - INSERT #Warnings ( CheckId, Priority, DatabaseName, Finding, Warning ) - EXEC sys.sp_executesql @StringToExecute; - - /*Checking for encrypted backups and the last backup of the encryption key.*/ - - /*2014 ONLY*/ - -IF @ProductVersionMajor >= 12 - BEGIN - - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - - SET @StringToExecute += N'SELECT - 9 AS CheckId, - 100 AS [Priority], - b.database_name AS [Database Name], - ''Encrypted backups'' AS [Finding], - ''The database '' + QUOTENAME(b.database_name) + '' has had '' + CONVERT(VARCHAR(10), COUNT(*)) + '' '' + b.encryptor_type + '' backups, and the last time a certificate was backed up is ' - + CASE WHEN LOWER(@MSDBName) <> N'msdb' - THEN + N'...well, that information is on another server, anyway.'' AS [Warning]' - ELSE + CONVERT(VARCHAR(30), (SELECT MAX(c.pvt_key_last_backup_date) FROM sys.certificates AS c WHERE c.name NOT LIKE '##%')) + N'.'' AS [Warning]' - END + - N' - FROM ' + QUOTENAME(@MSDBName) + N'.dbo.backupset AS b - WHERE b.encryptor_type IS NOT NULL - GROUP BY b.database_name, b.encryptor_type;' + @crlf; - - IF @Debug = 1 - PRINT @StringToExecute; - - INSERT #Warnings ( CheckId, Priority, DatabaseName, Finding, Warning ) - EXEC sys.sp_executesql @StringToExecute; - - END - - /*Looking for backups that have BULK LOGGED data in them -- this can screw up point in time LOG recovery.*/ - - SET @StringToExecute =N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - - SET @StringToExecute += N'SELECT - 10 AS CheckId, - 100 AS [Priority], - b.database_name AS [Database Name], - ''Bulk logged backups'' AS [Finding], - ''The database '' + QUOTENAME(b.database_name) + '' has had '' + CONVERT(VARCHAR(10), COUNT(*)) + '' backups with bulk logged data. This can make point in time recovery awkward. '' AS [Warning] - FROM ' + QUOTENAME(@MSDBName) + '.dbo.backupset AS b - WHERE b.has_bulk_logged_data = 1 - GROUP BY b.database_name;' + @crlf; - - IF @Debug = 1 - PRINT @StringToExecute; - - INSERT #Warnings ( CheckId, Priority, DatabaseName, Finding, Warning ) - EXEC sys.sp_executesql @StringToExecute; - - /*Looking for recovery model being switched between FULL and SIMPLE, because it''s a bad practice.*/ - - SET @StringToExecute =N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - - SET @StringToExecute += N'SELECT - 11 AS CheckId, - 100 AS [Priority], - b.database_name AS [Database Name], - ''Recovery model switched'' AS [Finding], - ''The database '' + QUOTENAME(b.database_name) + '' has changed recovery models from between FULL and SIMPLE '' + CONVERT(VARCHAR(10), COUNT(DISTINCT b.recovery_model)) + '' times. This breaks the log chain and is generally a bad idea.'' AS [Warning] - FROM ' + QUOTENAME(@MSDBName) + '.dbo.backupset AS b - WHERE b.recovery_model <> ''BULK-LOGGED'' - GROUP BY b.database_name - HAVING COUNT(DISTINCT b.recovery_model) > 4;' + @crlf; - - IF @Debug = 1 - PRINT @StringToExecute; - - INSERT #Warnings ( CheckId, Priority, DatabaseName, Finding, Warning ) - EXEC sys.sp_executesql @StringToExecute; - - /*Looking for uncompressed backups.*/ - - SET @StringToExecute =N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - - SET @StringToExecute += N'SELECT - 12 AS CheckId, - 100 AS [Priority], - b.database_name AS [Database Name], - ''Uncompressed backups'' AS [Finding], - ''The database '' + QUOTENAME(b.database_name) + '' has had '' + CONVERT(VARCHAR(10), COUNT(*)) + '' uncompressed backups in the last 30 days. This is a free way to save time and space. And SPACETIME. If your version of SQL supports it.'' AS [Warning] - FROM ' + QUOTENAME(@MSDBName) + '.dbo.backupset AS b - WHERE backup_size = compressed_backup_size AND type = ''D'' - AND b.backup_finish_date >= DATEADD(DAY, -30, SYSDATETIME()) - GROUP BY b.database_name;' + @crlf; - - IF @Debug = 1 - PRINT @StringToExecute; - - INSERT #Warnings ( CheckId, Priority, DatabaseName, Finding, Warning ) - EXEC sys.sp_executesql @StringToExecute; - -RAISERROR('Rules analysis starting on temp tables', 0, 1) WITH NOWAIT; - - INSERT #Warnings ( CheckId, Priority, DatabaseName, Finding, Warning ) - SELECT - 13 AS CheckId, - 100 AS Priority, - r.DatabaseName as [DatabaseName], - 'Big Diffs' AS [Finding], - 'On average, Differential backups for this database are >=40% of the size of the average Full backup.' AS [Warning] - FROM #Recoverability AS r - WHERE r.IsBigDiff = 1 - - INSERT #Warnings ( CheckId, Priority, DatabaseName, Finding, Warning ) - SELECT - 13 AS CheckId, - 100 AS Priority, - r.DatabaseName as [DatabaseName], - 'Big Logs' AS [Finding], - 'On average, Log backups for this database are >=20% of the size of the average Full backup.' AS [Warning] - FROM #Recoverability AS r - WHERE r.IsBigLog = 1 - - - -/*Insert thank you stuff last*/ - INSERT #Warnings ( CheckId, Priority, DatabaseName, Finding, Warning ) - - SELECT - 2147483647 AS [CheckId], - 2147483647 AS [Priority], - 'From Your Community Volunteers' AS [DatabaseName], - 'sp_BlitzBackups Version: ' + @Version + ', Version Date: ' + CONVERT(VARCHAR(30), @VersionDate) + '.' AS [Finding], - 'Thanks for using our stored procedure. We hope you find it useful! Check out our other free SQL Server scripts at firstresponderkit.org!' AS [Warning]; - -RAISERROR('Rules analysis finished', 0, 1) WITH NOWAIT; - -SELECT w.CheckId, w.Priority, w.DatabaseName, w.Finding, w.Warning -FROM #Warnings AS w -ORDER BY w.Priority, w.CheckId; - -DROP TABLE #Backups, #Warnings, #Recoverability, #RTORecoveryPoints - - -RETURN; - -PushBackupHistoryToListener: - -RAISERROR('Pushing backup history to listener', 0, 1) WITH NOWAIT; - -DECLARE @msg NVARCHAR(4000) = N''; -DECLARE @RemoteCheck TABLE (c INT NULL); - - -IF @WriteBackupsToDatabaseName IS NULL - BEGIN - RAISERROR('@WriteBackupsToDatabaseName can''t be NULL.', 16, 1) WITH NOWAIT - RETURN; - END - -IF LOWER(@WriteBackupsToDatabaseName) = N'msdb' - BEGIN - RAISERROR('We can''t write to the real msdb, we have to write to a fake msdb.', 16, 1) WITH NOWAIT - RETURN; - END - -IF @WriteBackupsToListenerName IS NULL -BEGIN - IF @AGName IS NULL - BEGIN - RAISERROR('@WriteBackupsToListenerName and @AGName can''t both be NULL.', 16, 1) WITH NOWAIT; - RETURN; - END - ELSE - BEGIN - SELECT @WriteBackupsToListenerName = dns_name - FROM sys.availability_groups AS ag - JOIN sys.availability_group_listeners AS agl - ON ag.group_id = agl.group_id - WHERE name = @AGName; - END - -END - -IF @WriteBackupsToListenerName IS NOT NULL -BEGIN - IF NOT EXISTS - ( - SELECT * - FROM sys.servers s - WHERE name = @WriteBackupsToListenerName - ) - BEGIN - SET @msg = N'We need a linked server to write data across. Please set one up for ' + @WriteBackupsToListenerName + N'.'; - RAISERROR(@msg, 16, 1) WITH NOWAIT; - RETURN; - END -END - - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - - SET @StringToExecute += N'SELECT TOP 1 1 FROM ' - + QUOTENAME(@WriteBackupsToListenerName) + N'.master.sys.databases d WHERE d.name = @i_WriteBackupsToDatabaseName;' - - IF @Debug = 1 - PRINT @StringToExecute; - - INSERT @RemoteCheck (c) - EXEC sp_executesql @StringToExecute, N'@i_WriteBackupsToDatabaseName NVARCHAR(256)', @i_WriteBackupsToDatabaseName = @WriteBackupsToDatabaseName; - - IF @@ROWCOUNT = 0 - BEGIN - SET @msg = N'The database ' + @WriteBackupsToDatabaseName + N' doesn''t appear to exist on that server.' - RAISERROR(@msg, 16, 1) WITH NOWAIT - RETURN; - END - - - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - - SET @StringToExecute += N'SELECT TOP 1 1 FROM ' - + QUOTENAME(@WriteBackupsToListenerName) + '.' + QUOTENAME(@WriteBackupsToDatabaseName) + '.sys.tables WHERE name = ''backupset'' AND SCHEMA_NAME(schema_id) = ''dbo''; - ' + @crlf; - - IF @Debug = 1 - PRINT @StringToExecute; - - INSERT @RemoteCheck (c) - EXEC sp_executesql @StringToExecute; - - IF @@ROWCOUNT = 0 - BEGIN - - SET @msg = N'The database ' + @WriteBackupsToDatabaseName + N' doesn''t appear to have a table called dbo.backupset in it.' - RAISERROR(@msg, 0, 1) WITH NOWAIT - RAISERROR('Don''t worry, we''ll create it for you!', 0, 1) WITH NOWAIT - - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - - SET @StringToExecute += N'CREATE TABLE ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.dbo.backupset - ( backup_set_id INT IDENTITY(1, 1), backup_set_uuid UNIQUEIDENTIFIER, media_set_id INT, first_family_number TINYINT, first_media_number SMALLINT, - last_family_number TINYINT, last_media_number SMALLINT, catalog_family_number TINYINT, catalog_media_number SMALLINT, position INT, expiration_date DATETIME, - software_vendor_id INT, name NVARCHAR(128), description NVARCHAR(255), user_name NVARCHAR(128), software_major_version TINYINT, software_minor_version TINYINT, - software_build_version SMALLINT, time_zone SMALLINT, mtf_minor_version TINYINT, first_lsn NUMERIC(25, 0), last_lsn NUMERIC(25, 0), checkpoint_lsn NUMERIC(25, 0), - database_backup_lsn NUMERIC(25, 0), database_creation_date DATETIME, backup_start_date DATETIME, backup_finish_date DATETIME, type CHAR(1), sort_order SMALLINT, - code_page SMALLINT, compatibility_level TINYINT, database_version INT, backup_size NUMERIC(20, 0), database_name NVARCHAR(128), server_name NVARCHAR(128), - machine_name NVARCHAR(128), flags INT, unicode_locale INT, unicode_compare_style INT, collation_name NVARCHAR(128), is_password_protected BIT, recovery_model NVARCHAR(60), - has_bulk_logged_data BIT, is_snapshot BIT, is_readonly BIT, is_single_user BIT, has_backup_checksums BIT, is_damaged BIT, begins_log_chain BIT, has_incomplete_metadata BIT, - is_force_offline BIT, is_copy_only BIT, first_recovery_fork_guid UNIQUEIDENTIFIER, last_recovery_fork_guid UNIQUEIDENTIFIER, fork_point_lsn NUMERIC(25, 0), database_guid UNIQUEIDENTIFIER, - family_guid UNIQUEIDENTIFIER, differential_base_lsn NUMERIC(25, 0), differential_base_guid UNIQUEIDENTIFIER, compressed_backup_size NUMERIC(20, 0), key_algorithm NVARCHAR(32), - encryptor_thumbprint VARBINARY(20) , encryptor_type NVARCHAR(32) - ); - ' + @crlf; - - SET @InnerStringToExecute = N'EXEC( ''' + @StringToExecute + ''' ) AT ' + QUOTENAME(@WriteBackupsToListenerName) + N';' - - IF @Debug = 1 - PRINT @InnerStringToExecute; - - EXEC sp_executesql @InnerStringToExecute - - - RAISERROR('We''ll even make the indexes!', 0, 1) WITH NOWAIT - - /*Checking for and creating the PK/CX*/ - - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - - SET @StringToExecute += N' - - IF NOT EXISTS ( - SELECT t.name, i.name - FROM ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.sys.tables AS t - JOIN ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.sys.indexes AS i - ON t.object_id = i.object_id - WHERE t.name = ? - AND i.name LIKE ? - ) - - BEGIN - ALTER TABLE ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.[dbo].[backupset] ADD PRIMARY KEY CLUSTERED ([backup_set_id] ASC) - END - ' - - SET @InnerStringToExecute = N'EXEC( ''' + @StringToExecute + ''', ''backupset'', ''PK[_][_]%'' ) AT ' + QUOTENAME(@WriteBackupsToListenerName) + N';' - - IF @Debug = 1 - PRINT @InnerStringToExecute; - - EXEC sp_executesql @InnerStringToExecute - - - - /*Checking for and creating index on backup_set_uuid*/ - - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - - SET @StringToExecute += N'IF NOT EXISTS ( - SELECT t.name, i.name - FROM ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.sys.tables AS t - JOIN ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.sys.indexes AS i - ON t.object_id = i.object_id - WHERE t.name = ? - AND i.name = ? - ) - - BEGIN - CREATE NONCLUSTERED INDEX [backupsetuuid] ON ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.[dbo].[backupset] ([backup_set_uuid] ASC) - END - ' - - SET @InnerStringToExecute = N'EXEC( ''' + @StringToExecute + ''', ''backupset'', ''backupsetuuid'' ) AT ' + QUOTENAME(@WriteBackupsToListenerName) + N';' - - IF @Debug = 1 - PRINT @InnerStringToExecute; - - EXEC sp_executesql @InnerStringToExecute - - - - /*Checking for and creating index on media_set_id*/ - - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - - SET @StringToExecute += 'IF NOT EXISTS ( - SELECT t.name, i.name - FROM ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.sys.tables AS t - JOIN ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.sys.indexes AS i - ON t.object_id = i.object_id - WHERE t.name = ? - AND i.name = ? - ) - - BEGIN - CREATE NONCLUSTERED INDEX [backupsetMediaSetId] ON ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.[dbo].[backupset] ([media_set_id] ASC) - END - ' - - SET @InnerStringToExecute = N'EXEC( ''' + @StringToExecute + ''', ''backupset'', ''backupsetMediaSetId'' ) AT ' + QUOTENAME(@WriteBackupsToListenerName) + N';' - - IF @Debug = 1 - PRINT @InnerStringToExecute; - - EXEC sp_executesql @InnerStringToExecute - - - - /*Checking for and creating index on backup_finish_date*/ - - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - - SET @StringToExecute += N'IF NOT EXISTS ( - SELECT t.name, i.name - FROM ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.sys.tables AS t - JOIN ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.sys.indexes AS i - ON t.object_id = i.object_id - WHERE t.name = ? - AND i.name = ? - ) - - BEGIN - CREATE NONCLUSTERED INDEX [backupsetDate] ON ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.[dbo].[backupset] ([backup_finish_date] ASC) - END - ' - - SET @InnerStringToExecute = N'EXEC( ''' + @StringToExecute + ''', ''backupset'', ''backupsetDate'' ) AT ' + QUOTENAME(@WriteBackupsToListenerName) + N';' - - IF @Debug = 1 - PRINT @InnerStringToExecute; - - EXEC sp_executesql @InnerStringToExecute - - - - /*Checking for and creating index on database_name*/ - - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - - SET @StringToExecute += N'IF NOT EXISTS ( - SELECT t.name, i.name - FROM ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.sys.tables AS t - JOIN ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.sys.indexes AS i - ON t.object_id = i.object_id - WHERE t.name = ? - AND i.name = ? - ) - - BEGIN - CREATE NONCLUSTERED INDEX [backupsetDatabaseName] ON ' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.[dbo].[backupset] ([database_name] ASC ) INCLUDE ([backup_set_id], [media_set_id]) - END - - ' - - SET @InnerStringToExecute = N'EXEC( ''' + @StringToExecute + ''', ''backupset'', ''backupsetDatabaseName'' ) AT ' + QUOTENAME(@WriteBackupsToListenerName) + N';' - - IF @Debug = 1 - PRINT @InnerStringToExecute; - - EXEC sp_executesql @InnerStringToExecute - - RAISERROR('Table and indexes created! You''re welcome!', 0, 1) WITH NOWAIT - END - - - RAISERROR('Beginning inserts', 0, 1) WITH NOWAIT; - RAISERROR(@crlf, 0, 1) WITH NOWAIT; - - /* - Batching code comes from the lovely and talented Michael J. Swart - http://michaeljswart.com/2014/09/take-care-when-scripting-batches/ - If you're ever in Canada, he says you can stay at his house, too. - */ - - - SET @StringToExecute = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + @crlf; - - SET @StringToExecute += N' - DECLARE - @StartDate DATETIME = DATEADD(HOUR, @i_WriteBackupsLastHours, SYSDATETIME()), - @StartDateNext DATETIME, - @RC INT = 1, - @msg NVARCHAR(4000) = N''''; - - SELECT @StartDate = MIN(b.backup_start_date) - FROM msdb.dbo.backupset b - WHERE b.backup_start_date >= @StartDate - AND NOT EXISTS ( - SELECT 1 - FROM ' + QUOTENAME(@WriteBackupsToListenerName) + N'.' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.dbo.backupset b2 - WHERE b.backup_set_uuid = b2.backup_set_uuid - AND b2.backup_start_date >= @StartDate - ) - - SET @StartDateNext = DATEADD(MINUTE, 10, @StartDate); - - IF - ( @StartDate IS NULL ) - BEGIN - SET @msg = N''No data to move, exiting.'' - RAISERROR(@msg, 0, 1) WITH NOWAIT - - RETURN; - END - - RAISERROR(''Starting insert loop'', 0, 1) WITH NOWAIT; - - WHILE EXISTS ( - SELECT 1 - FROM msdb.dbo.backupset b - WHERE NOT EXISTS ( - SELECT 1 - FROM ' + QUOTENAME(@WriteBackupsToListenerName) + N'.' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.dbo.backupset b2 - WHERE b.backup_set_uuid = b2.backup_set_uuid - AND b2.backup_start_date >= @StartDate - ) - ) - BEGIN - - SET @msg = N''Inserting data for '' + CONVERT(NVARCHAR(30), @StartDate) + '' through '' + + CONVERT(NVARCHAR(30), @StartDateNext) + ''.'' - RAISERROR(@msg, 0, 1) WITH NOWAIT - - ' - - SET @StringToExecute += N'INSERT ' + QUOTENAME(@WriteBackupsToListenerName) + N'.' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.dbo.backupset - ' - SET @StringToExecute += N' (database_name, database_guid, backup_set_uuid, type, backup_size, backup_start_date, backup_finish_date, media_set_id, time_zone, - compressed_backup_size, recovery_model, server_name, machine_name, first_lsn, last_lsn, user_name, compatibility_level, - is_password_protected, is_snapshot, is_readonly, is_single_user, has_backup_checksums, is_damaged, ' + CASE WHEN @ProductVersionMajor >= 12 - THEN + N'encryptor_type, has_bulk_logged_data)' + @crlf - ELSE + N'has_bulk_logged_data)' + @crlf - END - - SET @StringToExecute +=N' - SELECT database_name, database_guid, backup_set_uuid, type, backup_size, backup_start_date, backup_finish_date, media_set_id, time_zone, - compressed_backup_size, recovery_model, server_name, machine_name, first_lsn, last_lsn, user_name, compatibility_level, - is_password_protected, is_snapshot, is_readonly, is_single_user, has_backup_checksums, is_damaged, ' + CASE WHEN @ProductVersionMajor >= 12 - THEN + N'encryptor_type, has_bulk_logged_data' + @crlf - ELSE + N'has_bulk_logged_data' + @crlf - END - SET @StringToExecute +=N' - FROM msdb.dbo.backupset b - WHERE 1=1 - AND b.backup_start_date >= @StartDate - AND b.backup_start_date < @StartDateNext - AND NOT EXISTS ( - SELECT 1 - FROM ' + QUOTENAME(@WriteBackupsToListenerName) + N'.' + QUOTENAME(@WriteBackupsToDatabaseName) + N'.dbo.backupset b2 - WHERE b.backup_set_uuid = b2.backup_set_uuid - AND b2.backup_start_date >= @StartDate - )' + @crlf; - - - SET @StringToExecute +=N' - SET @RC = @@ROWCOUNT; - - SET @msg = N''Inserted '' + CONVERT(NVARCHAR(30), @RC) + '' rows for ''+ CONVERT(NVARCHAR(30), @StartDate) + '' through '' + CONVERT(NVARCHAR(30), @StartDateNext) + ''.'' - RAISERROR(@msg, 0, 1) WITH NOWAIT - - SET @StartDate = @StartDateNext; - SET @StartDateNext = DATEADD(MINUTE, 10, @StartDate); - - IF - ( @StartDate > SYSDATETIME() ) - BEGIN - - SET @msg = N''No more data to move, exiting.'' - RAISERROR(@msg, 0, 1) WITH NOWAIT - - BREAK; - - END - END' + @crlf; - - IF @Debug = 1 - PRINT @StringToExecute; - - EXEC sp_executesql @StringToExecute, N'@i_WriteBackupsLastHours INT', @i_WriteBackupsLastHours = @WriteBackupsLastHours; - -END; - -END; - GO SET ANSI_NULLS ON; SET ANSI_PADDING ON; @@ -2697,7 +1172,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.19', @VersionDate = '20240222'; +SELECT @Version = '8.20', @VersionDate = '20240522'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -10070,7 +8545,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.19', @VersionDate = '20240222'; +SELECT @Version = '8.20', @VersionDate = '20240522'; IF(@VersionCheckMode = 1) BEGIN @@ -10166,7 +8641,9 @@ DECLARE @StringToExecute NVARCHAR(MAX), @dm_exec_query_statistics_xml BIT = 0, @total_cpu_usage BIT = 0, @get_thread_time_ms NVARCHAR(MAX) = N'', - @thread_time_ms FLOAT = 0; + @thread_time_ms FLOAT = 0, + @logical_processors INT = 0, + @max_worker_threads INT = 0; /* Sanitize our inputs */ SELECT @@ -10304,16 +8781,23 @@ BEGIN /* Set start/finish times AFTER sp_BlitzWho runs. For more info: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2244 */ IF @Seconds = 0 AND SERVERPROPERTY('EngineEdition') = 5 /*SERVERPROPERTY('Edition') = 'SQL Azure'*/ - WITH WaitTimes AS ( - SELECT wait_type, wait_time_ms, - NTILE(3) OVER(ORDER BY wait_time_ms) AS grouper - FROM sys.dm_os_wait_stats w - WHERE wait_type IN ('DIRTY_PAGE_POLL','HADR_FILESTREAM_IOMGR_IOCOMPLETION','LAZYWRITER_SLEEP', - 'LOGMGR_QUEUE','REQUEST_FOR_DEADLOCK_SEARCH','XE_TIMER_EVENT') - ) - SELECT @StartSampleTime = DATEADD(mi, AVG(-wait_time_ms / 1000 / 60), SYSDATETIMEOFFSET()), @FinishSampleTime = SYSDATETIMEOFFSET() - FROM WaitTimes - WHERE grouper = 2; + BEGIN + /* Use the most accurate (but undocumented) DMV if it's available: */ + IF EXISTS(SELECT * FROM sys.all_columns ac WHERE ac.object_id = OBJECT_ID('sys.dm_cloud_database_epoch') AND ac.name = 'last_role_transition_time') + SELECT @StartSampleTime = DATEADD(MINUTE,DATEDIFF(MINUTE, GETDATE(), GETUTCDATE()),last_role_transition_time) , @FinishSampleTime = SYSDATETIMEOFFSET() + FROM sys.dm_cloud_database_epoch; + ELSE + WITH WaitTimes AS ( + SELECT wait_type, wait_time_ms, + NTILE(3) OVER(ORDER BY wait_time_ms) AS grouper + FROM sys.dm_os_wait_stats w + WHERE wait_type IN ('DIRTY_PAGE_POLL','HADR_FILESTREAM_IOMGR_IOCOMPLETION','LAZYWRITER_SLEEP', + 'LOGMGR_QUEUE','REQUEST_FOR_DEADLOCK_SEARCH','XE_TIMER_EVENT') + ) + SELECT @StartSampleTime = DATEADD(mi, AVG(-wait_time_ms / 1000 / 60), SYSDATETIMEOFFSET()), @FinishSampleTime = SYSDATETIMEOFFSET() + FROM WaitTimes + WHERE grouper = 2; + END ELSE IF @Seconds = 0 AND SERVERPROPERTY('EngineEdition') <> 5 /*SERVERPROPERTY('Edition') <> 'SQL Azure'*/ SELECT @StartSampleTime = DATEADD(MINUTE,DATEDIFF(MINUTE, GETDATE(), GETUTCDATE()),create_date) , @FinishSampleTime = SYSDATETIMEOFFSET() FROM sys.databases @@ -10324,6 +8808,10 @@ BEGIN @FinishSampleTimeWaitFor = DATEADD(ss, @Seconds, GETDATE()); + SELECT @logical_processors = COUNT(*) + FROM sys.dm_os_schedulers + WHERE status = 'VISIBLE ONLINE'; + IF EXISTS ( @@ -12597,6 +11085,35 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, END + /* Potential Upcoming Problems - High Number of Connections - CheckID 49 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 49',10,1) WITH NOWAIT; + END + IF CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) LIKE '%64%' AND SERVERPROPERTY('EngineEdition') <> 5 + BEGIN + IF @logical_processors <= 4 + SET @max_worker_threads = 512; + ELSE IF @logical_processors > 64 AND + ((@v = 13 AND @build >= 5026) OR @v >= 14) + SET @max_worker_threads = 512 + ((@logical_processors - 4) * 32) + ELSE + SET @max_worker_threads = 512 + ((@logical_processors - 4) * 16) + + IF @max_worker_threads > 0 + BEGIN + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) + SELECT 49 AS CheckID, + 210 AS Priority, + 'Potential Upcoming Problems' AS FindingGroup, + 'High Number of Connections' AS Finding, + 'https://www.brentozar.com/archive/2014/05/connections-slow-sql-server-threadpool/' AS URL, + 'There are ' + CAST(SUM(1) AS VARCHAR(20)) + ' open connections, which would lead to ' + @LineFeed + 'worker thread exhaustion and THREADPOOL waits' + @LineFeed + 'if they all ran queries at the same time.' AS Details + FROM sys.dm_exec_connections c + HAVING SUM(1) > @max_worker_threads; + END + END + RAISERROR('Finished running investigatory queries',10,1) WITH NOWAIT; @@ -12971,7 +11488,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 'Wait Stats' AS FindingGroup, wNow.wait_type AS Finding, /* IF YOU CHANGE THIS, STUFF WILL BREAK. Other checks look for wait type names in the Finding field. See checks 11, 12 as example. */ N'https://www.sqlskills.com/help/waits/' + LOWER(wNow.wait_type) + '/' AS URL, - 'For ' + CAST(((wNow.wait_time_ms - COALESCE(wBase.wait_time_ms,0)) / 1000) AS NVARCHAR(100)) + ' seconds over the last ' + CASE @Seconds WHEN 0 THEN (CAST(DATEDIFF(dd,@StartSampleTime,@FinishSampleTime) AS NVARCHAR(10)) + ' days') ELSE (CAST(@Seconds AS NVARCHAR(10)) + ' seconds') END + ', SQL Server was waiting on this particular bottleneck.' + @LineFeed + @LineFeed AS Details, + 'For ' + CAST(((wNow.wait_time_ms - COALESCE(wBase.wait_time_ms,0)) / 1000) AS NVARCHAR(100)) + ' seconds over the last ' + CASE @Seconds WHEN 0 THEN (CAST(DATEDIFF(dd,@StartSampleTime,@FinishSampleTime) AS NVARCHAR(10)) + ' days') ELSE (CAST(DATEDIFF(ss, wBase.SampleTime, wNow.SampleTime) AS NVARCHAR(10)) + ' seconds') END + ', SQL Server was waiting on this particular bottleneck.' + @LineFeed + @LineFeed AS Details, 'See the URL for more details on how to mitigate this wait type.' AS HowToStopIt, ((wNow.wait_time_ms - COALESCE(wBase.wait_time_ms,0)) / 1000) AS DetailsInt FROM #WaitStats wNow @@ -12991,7 +11508,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 'Server Performance' AS FindingGroup, 'Poison Wait Detected: ' + wNow.wait_type AS Finding, N'https://www.brentozar.com/go/poison/#' + wNow.wait_type AS URL, - 'For ' + CAST(((wNow.wait_time_ms - COALESCE(wBase.wait_time_ms,0)) / 1000) AS NVARCHAR(100)) + ' seconds over the last ' + CASE @Seconds WHEN 0 THEN (CAST(DATEDIFF(dd,@StartSampleTime,@FinishSampleTime) AS NVARCHAR(10)) + ' days') ELSE (CAST(@Seconds AS NVARCHAR(10)) + ' seconds') END + ', SQL Server was waiting on this particular bottleneck.' + @LineFeed + @LineFeed AS Details, + 'For ' + CAST(((wNow.wait_time_ms - COALESCE(wBase.wait_time_ms,0)) / 1000) AS NVARCHAR(100)) + ' seconds over the last ' + CASE @Seconds WHEN 0 THEN (CAST(DATEDIFF(dd,@StartSampleTime,@FinishSampleTime) AS NVARCHAR(10)) + ' days') ELSE (CAST(DATEDIFF(ss, wBase.SampleTime, wNow.SampleTime) AS NVARCHAR(10)) + ' seconds') END + ', SQL Server was waiting on this particular bottleneck.' + @LineFeed + @LineFeed AS Details, 'See the URL for more details on how to mitigate this wait type.' AS HowToStopIt, ((wNow.wait_time_ms - COALESCE(wBase.wait_time_ms,0)) / 1000) AS DetailsInt FROM #WaitStats wNow @@ -15037,7 +13554,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.19', @VersionDate = '20240222'; +SELECT @Version = '8.20', @VersionDate = '20240522'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -15818,7 +14335,7 @@ IF @GetAllDatabases = 1 IF EXISTS (SELECT * FROM sys.all_objects o INNER JOIN sys.all_columns c ON o.object_id = c.object_id AND o.name = 'dm_hadr_availability_replica_states' AND c.name = 'role_desc') BEGIN SET @dsql = N'UPDATE #DatabaseList SET secondary_role_allow_connections_desc = ''NO'' WHERE DatabaseName IN ( - SELECT d.name + SELECT DB_NAME(d.database_id) FROM sys.dm_hadr_availability_replica_states rs INNER JOIN sys.databases d ON rs.replica_id = d.replica_id INNER JOIN sys.availability_replicas r ON rs.replica_id = r.replica_id @@ -15883,7 +14400,7 @@ ELSE ELSE @DatabaseName END; END; -SET @NumDatabases = (SELECT COUNT(*) FROM #DatabaseList AS D LEFT OUTER JOIN #Ignore_Databases AS I ON D.DatabaseName = I.DatabaseName WHERE I.DatabaseName IS NULL); +SET @NumDatabases = (SELECT COUNT(*) FROM #DatabaseList AS D LEFT OUTER JOIN #Ignore_Databases AS I ON D.DatabaseName = I.DatabaseName WHERE I.DatabaseName IS NULL AND ISNULL(D.secondary_role_allow_connections_desc, 'YES') != 'NO'); SET @msg = N'Number of databases to examine: ' + CAST(@NumDatabases AS NVARCHAR(50)); RAISERROR (@msg,0,1) WITH NOWAIT; @@ -15902,8 +14419,8 @@ BEGIN TRY 0 , @ScriptVersionName, CASE WHEN @GetAllDatabases = 1 THEN N'All Databases' ELSE N'Database ' + QUOTENAME(@DatabaseName) + N' as of ' + CONVERT(NVARCHAR(16), GETDATE(), 121) END, - N'From Your Community Volunteers', N'http://FirstResponderKit.org', + N'From Your Community Volunteers', N'', N'', N'' @@ -15914,9 +14431,9 @@ BEGIN TRY 0, N'You''re trying to run sp_BlitzIndex on a server with ' + CAST(@NumDatabases AS NVARCHAR(8)) + N' databases. ', N'Running sp_BlitzIndex on a server with 50+ databases may cause temporary problems for the server and/or user.', - N'If you''re sure you want to do this, run again with the parameter @BringThePain = 1.', - 'http://FirstResponderKit.org', '', + 'http://FirstResponderKit.org', + N'If you''re sure you want to do this, run again with the parameter @BringThePain = 1.', '', '', '' @@ -16318,7 +14835,7 @@ BEGIN TRY si.index_id, si.type, @i_DatabaseName AS database_name, - COALESCE(sc.NAME, ''Unknown'') AS [schema_name], + COALESCE(sc.name, ''Unknown'') AS [schema_name], COALESCE(so.name, ''Unknown'') AS [object_name], COALESCE(si.name, ''Unknown'') AS [index_name], CASE WHEN so.[type] = CAST(''V'' AS CHAR(2)) THEN 1 ELSE 0 END, @@ -16430,7 +14947,7 @@ BEGIN TRY RAISERROR (N'Preferring non-2012 syntax with LEFT JOIN to sys.dm_db_index_operational_stats',0,1) WITH NOWAIT; --NOTE: If you want to use the newer syntax for 2012+, you'll have to change 2147483647 to 11 on line ~819 - --This change was made because on a table with lots of paritions, the OUTER APPLY was crazy slow. + --This change was made because on a table with lots of partitions, the OUTER APPLY was crazy slow. -- get relevant columns from sys.dm_db_partition_stats, sys.partitions and sys.objects IF OBJECT_ID('tempdb..#dm_db_partition_stats_etc') IS NOT NULL @@ -16616,7 +15133,7 @@ BEGIN TRY BEGIN RAISERROR (N'Using 2012 syntax to query sys.dm_db_index_operational_stats',0,1) WITH NOWAIT; --This is the syntax that will be used if you change 2147483647 to 11 on line ~819. - --If you have a lot of paritions and this suddenly starts running for a long time, change it back. + --If you have a lot of partitions and this suddenly starts running for a long time, change it back. SET @dsql = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; SELECT ' + CAST(@DatabaseID AS NVARCHAR(10)) + N' AS database_id, ps.object_id, @@ -20266,7 +18783,7 @@ BEGIN FROM #TemporalTables AS t OPTION ( RECOMPILE ); - RAISERROR(N'check_id 121: Optimized For Sequental Keys.', 0,1) WITH NOWAIT; + RAISERROR(N'check_id 121: Optimized For Sequential Keys.', 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 ) @@ -20549,9 +19066,9 @@ BEGIN [table_nc_index_ratio] NUMERIC(29,1), [heap_count] INT, [heap_gb] NUMERIC(29,1), - [partioned_table_count] INT, - [partioned_nc_count] INT, - [partioned_gb] NUMERIC(29,1), + [partitioned_table_count] INT, + [partitioned_nc_count] INT, + [partitioned_gb] NUMERIC(29,1), [filtered_index_count] INT, [indexed_view_count] INT, [max_table_row_count] INT, @@ -20614,9 +19131,9 @@ BEGIN [table_nc_index_ratio], [heap_count], [heap_gb], - [partioned_table_count], - [partioned_nc_count], - [partioned_gb], + [partitioned_table_count], + [partitioned_nc_count], + [partitioned_gb], [filtered_index_count], [indexed_view_count], [max_table_row_count], @@ -21525,7 +20042,7 @@ BEGIN SET XACT_ABORT OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.19', @VersionDate = '20240222'; + SELECT @Version = '8.20', @VersionDate = '20240522'; IF @VersionCheckMode = 1 BEGIN @@ -25656,7 +24173,7 @@ BEGIN SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.19', @VersionDate = '20240222'; + SELECT @Version = '8.20', @VersionDate = '20240522'; IF(@VersionCheckMode = 1) BEGIN diff --git a/SqlServerVersions.sql b/SqlServerVersions.sql index 795ecd52e..1acba8376 100644 --- a/SqlServerVersions.sql +++ b/SqlServerVersions.sql @@ -41,6 +41,8 @@ DELETE FROM dbo.SqlServerVersions; INSERT INTO dbo.SqlServerVersions (MajorVersionNumber, MinorVersionNumber, Branch, [Url], ReleaseDate, MainstreamSupportEndDate, ExtendedSupportEndDate, MajorVersionName, MinorVersionName) VALUES + (16, 4125, 'CU13', 'https://support.microsoft.com/en-us/help/5036432', '2024-05-16', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 13'), + (16, 4115, 'CU12', 'https://support.microsoft.com/en-us/help/5033663', '2024-03-14', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 12'), (16, 4105, 'CU11', 'https://support.microsoft.com/en-us/help/5032679', '2024-01-11', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 11'), (16, 4100, 'CU10 GDR', 'https://support.microsoft.com/en-us/help/5033592', '2024-01-09', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 10 GDR'), (16, 4095, 'CU10', 'https://support.microsoft.com/en-us/help/5031778', '2023-11-16', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 10'), @@ -55,6 +57,7 @@ VALUES (16, 4003, 'CU1', 'https://support.microsoft.com/en-us/help/5022375', '2023-02-16', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 1'), (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'), + (15, 4365, 'CU26', 'https://support.microsoft.com/kb/5035123', '2024-04-11', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 26'), (15, 4355, 'CU25', 'https://support.microsoft.com/kb/5033688', '2024-02-15', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 25'), (15, 4345, 'CU24', 'https://support.microsoft.com/kb/5031908', '2023-12-14', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 24'), (15, 4335, 'CU23', 'https://support.microsoft.com/kb/5030333', '2023-10-12', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 23'), diff --git a/sp_Blitz.sql b/sp_Blitz.sql index 5353df50e..09bbeef81 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -38,7 +38,7 @@ AS SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.19', @VersionDate = '20240222'; + SELECT @Version = '8.20', @VersionDate = '20240522'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) diff --git a/sp_BlitzAnalysis.sql b/sp_BlitzAnalysis.sql index 52256e652..50612bf8b 100644 --- a/sp_BlitzAnalysis.sql +++ b/sp_BlitzAnalysis.sql @@ -37,7 +37,7 @@ AS SET NOCOUNT ON; SET STATISTICS XML OFF; -SELECT @Version = '8.19', @VersionDate = '20240222'; +SELECT @Version = '8.20', @VersionDate = '20240522'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_BlitzBackups.sql b/sp_BlitzBackups.sql index 8fc7fa297..6f910d56e 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.19', @VersionDate = '20240222'; + SELECT @Version = '8.20', @VersionDate = '20240522'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_BlitzCache.sql b/sp_BlitzCache.sql index 51216d6ae..db2f3c188 100644 --- a/sp_BlitzCache.sql +++ b/sp_BlitzCache.sql @@ -281,7 +281,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.19', @VersionDate = '20240222'; +SELECT @Version = '8.20', @VersionDate = '20240522'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) diff --git a/sp_BlitzFirst.sql b/sp_BlitzFirst.sql index b76dc883b..caa4add1c 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.19', @VersionDate = '20240222'; +SELECT @Version = '8.20', @VersionDate = '20240522'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index dcefadf77..6a1fc8185 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -48,7 +48,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.19', @VersionDate = '20240222'; +SELECT @Version = '8.20', @VersionDate = '20240522'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index 313887b95..9ec7a3fd4 100644 --- a/sp_BlitzLock.sql +++ b/sp_BlitzLock.sql @@ -36,7 +36,7 @@ BEGIN SET XACT_ABORT OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.19', @VersionDate = '20240222'; + SELECT @Version = '8.20', @VersionDate = '20240522'; IF @VersionCheckMode = 1 BEGIN diff --git a/sp_BlitzWho.sql b/sp_BlitzWho.sql index a6728be9d..70a680f5f 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.19', @VersionDate = '20240222'; + SELECT @Version = '8.20', @VersionDate = '20240522'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_DatabaseRestore.sql b/sp_DatabaseRestore.sql index 9491740c6..49c9408ef 100755 --- a/sp_DatabaseRestore.sql +++ b/sp_DatabaseRestore.sql @@ -57,7 +57,7 @@ SET STATISTICS XML OFF; /*Versioning details*/ -SELECT @Version = '8.19', @VersionDate = '20240222'; +SELECT @Version = '8.20', @VersionDate = '20240522'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_ineachdb.sql b/sp_ineachdb.sql index 06c84e070..83d35848f 100644 --- a/sp_ineachdb.sql +++ b/sp_ineachdb.sql @@ -36,7 +36,7 @@ BEGIN SET NOCOUNT ON; SET STATISTICS XML OFF; - SELECT @Version = '8.19', @VersionDate = '20240222'; + SELECT @Version = '8.20', @VersionDate = '20240522'; IF(@VersionCheckMode = 1) BEGIN From ac6531e154ad324824d92c3e8deb18729d670772 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Wed, 22 May 2024 15:27:55 -0700 Subject: [PATCH 562/662] #3522 sp_BlitzIndex permissions Fixing error if they can't query sql_expression_dependencies. Closes #3522. --- Install-All-Scripts.sql | 9 +++++++-- Install-Azure.sql | 9 +++++++-- sp_BlitzIndex.sql | 9 +++++++-- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/Install-All-Scripts.sql b/Install-All-Scripts.sql index 377f9fa50..5001508dd 100644 --- a/Install-All-Scripts.sql +++ b/Install-All-Scripts.sql @@ -22809,8 +22809,13 @@ OPTION (RECOMPILE);'; AND ic.object_id = sed.referenced_id ) OPTION(RECOMPILE);' - INSERT #FilteredIndexes ( database_id, database_name, schema_name, table_name, index_name, column_name ) - EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; + BEGIN TRY + INSERT #FilteredIndexes ( database_id, database_name, schema_name, table_name, index_name, column_name ) + EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; + END TRY + BEGIN CATCH + RAISERROR (N'Skipping #FilteredIndexes population due to error, typically low permissions.', 0,1) WITH NOWAIT; + END CATCH END; END; diff --git a/Install-Azure.sql b/Install-Azure.sql index 1e59498f7..acc04d6b8 100644 --- a/Install-Azure.sql +++ b/Install-Azure.sql @@ -16008,8 +16008,13 @@ OPTION (RECOMPILE);'; AND ic.object_id = sed.referenced_id ) OPTION(RECOMPILE);' - INSERT #FilteredIndexes ( database_id, database_name, schema_name, table_name, index_name, column_name ) - EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; + BEGIN TRY + INSERT #FilteredIndexes ( database_id, database_name, schema_name, table_name, index_name, column_name ) + EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; + END TRY + BEGIN CATCH + RAISERROR (N'Skipping #FilteredIndexes population due to error, typically low permissions.', 0,1) WITH NOWAIT; + END CATCH END; END; diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index 6a1fc8185..16b130ff6 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -2502,8 +2502,13 @@ OPTION (RECOMPILE);'; AND ic.object_id = sed.referenced_id ) OPTION(RECOMPILE);' - INSERT #FilteredIndexes ( database_id, database_name, schema_name, table_name, index_name, column_name ) - EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; + BEGIN TRY + INSERT #FilteredIndexes ( database_id, database_name, schema_name, table_name, index_name, column_name ) + EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; + END TRY + BEGIN CATCH + RAISERROR (N'Skipping #FilteredIndexes population due to error, typically low permissions.', 0,1) WITH NOWAIT; + END CATCH END; END; From bb963e56bc6baa2abb843e282da85c3b5e42c1bc Mon Sep 17 00:00:00 2001 From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com> Date: Tue, 28 May 2024 14:47:38 -0400 Subject: [PATCH 563/662] Update sp_BlitzLock.sql Adds DeadlockType parameter for filtering to regular or parallel deadlocks. Closes #3525 --- sp_BlitzLock.sql | 99 ++++++++++++++++++++++++++++-------------------- 1 file changed, 58 insertions(+), 41 deletions(-) diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index 9ec7a3fd4..2605f8cfd 100644 --- a/sp_BlitzLock.sql +++ b/sp_BlitzLock.sql @@ -18,7 +18,8 @@ ALTER PROCEDURE @EventSessionName sysname = N'system_health', @TargetSessionType sysname = NULL, @VictimsOnly bit = 0, - @Debug bit = 0, + @DeadlockType nvarchar(20) = NULL, + @Debug bit = 0, @Help bit = 0, @Version varchar(30) = NULL OUTPUT, @VersionDate datetime = NULL OUTPUT, @@ -198,7 +199,7 @@ BEGIN @StartDateOriginal datetime = @StartDate, @EndDateOriginal datetime = @EndDate, @StartDateUTC datetime, - @EndDateUTC datetime; + @EndDateUTC datetime;; /*Temporary objects used in the procedure*/ DECLARE @@ -708,50 +709,63 @@ BEGIN END CATCH; END; + IF @DeadlockType IS NOT NULL + BEGIN + SELECT + @DeadlockType = + CASE + WHEN LOWER(@DeadlockType) LIKE 'regular%' + THEN N'Regular Deadlock' + WHEN LOWER(@DeadlockType) LIKE N'parallel%' + THEN N'Parallel Deadlock' + ELSE NULL + END; + END; + /*If @TargetSessionType, we need to figure out if it's ring buffer or event file*/ /*Azure has differently named views, so we need to separate. Thanks, Azure.*/ - IF - ( - @Azure = 0 - AND @TargetSessionType IS NULL - ) - BEGIN - RAISERROR('@TargetSessionType is NULL, assigning for non-Azure instance', 0, 1) WITH NOWAIT; - - SELECT TOP (1) - @TargetSessionType = t.target_name - FROM sys.dm_xe_sessions AS s - JOIN sys.dm_xe_session_targets AS t - ON s.address = t.event_session_address - WHERE s.name = @EventSessionName - AND t.target_name IN (N'event_file', N'ring_buffer') - ORDER BY t.target_name - OPTION(RECOMPILE); - - RAISERROR('@TargetSessionType assigned as %s for non-Azure', 0, 1, @TargetSessionType) WITH NOWAIT; - END; + IF + ( + @Azure = 0 + AND @TargetSessionType IS NULL + ) + BEGIN + RAISERROR('@TargetSessionType is NULL, assigning for non-Azure instance', 0, 1) WITH NOWAIT; + + SELECT TOP (1) + @TargetSessionType = t.target_name + FROM sys.dm_xe_sessions AS s + JOIN sys.dm_xe_session_targets AS t + ON s.address = t.event_session_address + WHERE s.name = @EventSessionName + AND t.target_name IN (N'event_file', N'ring_buffer') + ORDER BY t.target_name + OPTION(RECOMPILE); - IF - ( - @Azure = 1 - AND @TargetSessionType IS NULL - ) - BEGIN - RAISERROR('@TargetSessionType is NULL, assigning for Azure instance', 0, 1) WITH NOWAIT; + RAISERROR('@TargetSessionType assigned as %s for non-Azure', 0, 1, @TargetSessionType) WITH NOWAIT; + END; - SELECT TOP (1) - @TargetSessionType = t.target_name - FROM sys.dm_xe_database_sessions AS s - JOIN sys.dm_xe_database_session_targets AS t - ON s.address = t.event_session_address - WHERE s.name = @EventSessionName - AND t.target_name IN (N'event_file', N'ring_buffer') - ORDER BY t.target_name - OPTION(RECOMPILE); + IF + ( + @Azure = 1 + AND @TargetSessionType IS NULL + ) + BEGIN + RAISERROR('@TargetSessionType is NULL, assigning for Azure instance', 0, 1) WITH NOWAIT; + + SELECT TOP (1) + @TargetSessionType = t.target_name + FROM sys.dm_xe_database_sessions AS s + JOIN sys.dm_xe_database_session_targets AS t + ON s.address = t.event_session_address + WHERE s.name = @EventSessionName + AND t.target_name IN (N'event_file', N'ring_buffer') + ORDER BY t.target_name + OPTION(RECOMPILE); - RAISERROR('@TargetSessionType assigned as %s for Azure', 0, 1, @TargetSessionType) WITH NOWAIT; - END; + RAISERROR('@TargetSessionType assigned as %s for Azure', 0, 1, @TargetSessionType) WITH NOWAIT; + END; /*The system health stuff gets handled different from user extended events.*/ @@ -3449,7 +3463,8 @@ BEGIN AND (d.client_app = @AppName OR @AppName IS NULL) AND (d.host_name = @HostName OR @HostName IS NULL) AND (d.login_name = @LoginName OR @LoginName IS NULL) - OPTION (RECOMPILE, LOOP JOIN, HASH JOIN); + AND (d.deadlock_type = @DeadlockType OR @DeadlockType IS NULL) + OPTION (RECOMPILE, LOOP JOIN, HASH JOIN); UPDATE d SET d.inputbuf = @@ -4063,6 +4078,8 @@ BEGIN @TargetSessionType, VictimsOnly = @VictimsOnly, + DeadlockType = + @DeadlockType, Debug = @Debug, Help = From f2aed4162ffbe8689f2f8cede0fc83dd6b9b7f52 Mon Sep 17 00:00:00 2001 From: Reece Goding <67124261+ReeceGoding@users.noreply.github.com> Date: Sun, 2 Jun 2024 16:02:09 +0100 Subject: [PATCH 564/662] sp_Blitz.sql: Add much more detail about TF 7745 and 7752. --- sp_Blitz.sql | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index 09bbeef81..36a61307f 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -155,6 +155,7 @@ AS ,@MinServerMemory bigint ,@MaxServerMemory bigint ,@ColumnStoreIndexesInUse bit + ,@QueryStoreInUse bit ,@TraceFileIssue bit -- Flag for Windows OS to help with Linux support ,@IsWindowsOperatingSystem BIT @@ -7587,6 +7588,20 @@ IF @ProductVersionMajor >= 10 IF EXISTS (SELECT * FROM #TemporaryDatabaseResults) SET @ColumnStoreIndexesInUse = 1; END; + /* Check if Query Store is in use - for Github issue #3527 */ + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 74 ) /* Trace flags */ + AND @ProductVersionMajor > 12 /* The relevant column only exists in versions that support Query store */ + BEGIN + TRUNCATE TABLE #TemporaryDatabaseResults; + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 74) WITH NOWAIT; + + EXEC dbo.sp_MSforeachdb 'USE [?]; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; IF EXISTS(SELECT * FROM sys.databases WHERE is_query_store_on = 1 INSERT INTO #TemporaryDatabaseResults (DatabaseName, Finding) VALUES (DB_NAME(), ''Yup'') OPTION (RECOMPILE);'; + IF EXISTS (SELECT * FROM #TemporaryDatabaseResults) SET @QueryStoreInUse = 1; + END; + /* Non-Default Database Scoped Config - Github issue #598 */ IF EXISTS ( SELECT * FROM sys.all_objects WHERE [name] = 'database_scoped_configurations' ) BEGIN @@ -8354,6 +8369,7 @@ IF @ProductVersionMajor >= 10 'Informational' AS FindingsGroup , 'Trace Flag On' AS Finding , CASE WHEN [T].[TraceFlag] = '834' AND @ColumnStoreIndexesInUse = 1 THEN 'https://support.microsoft.com/en-us/kb/3210239' + WHEN [T].[TraceFlag] IN ('7745', '7752') THEN 'https://www.sqlskills.com/blogs/erin/query-store-trace-flags/' ELSE'https://www.BrentOzar.com/go/traceflags/' END AS URL , 'Trace flag ' + CASE WHEN [T].[TraceFlag] = '652' THEN '652 enabled globally, which disables pre-fetching during index scans. This is usually a very bad idea.' @@ -8372,6 +8388,13 @@ IF @ProductVersionMajor >= 10 WHEN [T].[TraceFlag] = '3226' THEN '3226 enabled globally, which keeps the event log clean by not reporting successful backups.' WHEN [T].[TraceFlag] = '3505' THEN '3505 enabled globally, which disables Checkpoints. This is usually a very bad idea.' WHEN [T].[TraceFlag] = '4199' THEN '4199 enabled globally, which enables non-default Query Optimizer fixes, changing query plans from the default behaviors.' + WHEN [T].[TraceFlag] = '7745' AND @ProductVersionMajor > 12 AND @QueryStoreInUse = 1 THEN '7745 enabled globally, which makes shutdowns/failovers quicker by not waiting for Query Store to flush to disk. This good idea loses you the non-flused Query Store data.' + WHEN [T].[TraceFlag] = '7745' AND @ProductVersionMajor > 12 THEN '7745 enabled globally, which is for Query Store. None of your databases have Query Store enabled, so why do you have this turned on?' + WHEN [T].[TraceFlag] = '7745' AND @ProductVersionMajor <= 12 THEN '7745 enabled globally, which is for Query Store. Query Store does not exist on your SQL Server version, so why do you have this turned on?' + WHEN [T].[TraceFlag] = '7752' AND @ProductVersionMajor > 14 THEN '7752 enabled globally, which is for Query Store. However, it has no effect in your SQL Server version. Consider turning it off.' + WHEN [T].[TraceFlag] = '7752' AND @ProductVersionMajor > 12 AND @QueryStoreInUse = 1 THEN '7752 enabled globally, which stops queries needing to wait on Query Store loading up after database recovery.' + WHEN [T].[TraceFlag] = '7752' AND @ProductVersionMajor > 12 THEN '7752 enabled globally, which is for Query Store. None of your databases have Query Store enabled, so why do you have this turned on?' + WHEN [T].[TraceFlag] = '7752' AND @ProductVersionMajor <= 12 THEN '7752 enabled globally, which is for Query Store. Query Store does not exist on your SQL Server version, so why do you have this turned on?' WHEN [T].[TraceFlag] = '8048' THEN '8048 enabled globally, which tries to reduce CMEMTHREAD waits on servers with a lot of logical processors.' WHEN [T].[TraceFlag] = '8017' AND (CAST(SERVERPROPERTY('Edition') AS NVARCHAR(1000)) LIKE N'%Express%') THEN '8017 is enabled globally, but this is the default for Express Edition.' WHEN [T].[TraceFlag] = '8017' AND (CAST(SERVERPROPERTY('Edition') AS NVARCHAR(1000)) NOT LIKE N'%Express%') THEN '8017 is enabled globally, which disables the creation of schedulers for all logical processors.' From 87a59a0114f6a2dd38e122108d6cce7ccf866815 Mon Sep 17 00:00:00 2001 From: Reece Goding <67124261+ReeceGoding@users.noreply.github.com> Date: Sun, 2 Jun 2024 16:11:44 +0100 Subject: [PATCH 565/662] sp_Blitz.sql: Missing bracket. --- sp_Blitz.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index 36a61307f..aba6ab73c 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -7598,7 +7598,7 @@ IF @ProductVersionMajor >= 10 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 74) WITH NOWAIT; - EXEC dbo.sp_MSforeachdb 'USE [?]; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; IF EXISTS(SELECT * FROM sys.databases WHERE is_query_store_on = 1 INSERT INTO #TemporaryDatabaseResults (DatabaseName, Finding) VALUES (DB_NAME(), ''Yup'') OPTION (RECOMPILE);'; + EXEC dbo.sp_MSforeachdb 'USE [?]; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; IF EXISTS(SELECT * FROM sys.databases WHERE is_query_store_on = 1) INSERT INTO #TemporaryDatabaseResults (DatabaseName, Finding) VALUES (DB_NAME(), ''Yup'') OPTION (RECOMPILE);'; IF EXISTS (SELECT * FROM #TemporaryDatabaseResults) SET @QueryStoreInUse = 1; END; From f16ba9c3d01e7d2fccbf84ff0a3c790d60500151 Mon Sep 17 00:00:00 2001 From: Reece Goding <67124261+ReeceGoding@users.noreply.github.com> Date: Sat, 15 Jun 2024 19:04:51 +0100 Subject: [PATCH 566/662] Added check for Query Store Wait Stats --- Documentation/sp_Blitz_Checks_by_Priority.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Documentation/sp_Blitz_Checks_by_Priority.md b/Documentation/sp_Blitz_Checks_by_Priority.md index 0600e82e3..f393f71cf 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: 261. -If you want to add a new one, start at 262. +CURRENT HIGH CHECKID: 262. +If you want to add a new one, start at 263. | Priority | FindingsGroup | Finding | URL | CheckID | |----------|-----------------------------|---------------------------------------------------------|------------------------------------------------------------------------|----------| @@ -247,6 +247,7 @@ If you want to add a new one, start at 262. | 200 | Performance | Non-Dynamic Memory | https://www.BrentOzar.com/go/memory | 190 | | 200 | Performance | Old Compatibility Level | https://www.BrentOzar.com/go/compatlevel | 62 | | 200 | Performance | Query Store Disabled | https://www.BrentOzar.com/go/querystore | 163 | +| 200 | Performance | Query Store Wait Stats Disabled | https://www.sqlskills.com/blogs/erin/query-store-settings/ | 262 | | 200 | Performance | Snapshot Backups Occurring | https://www.BrentOzar.com/go/snaps | 178 | | 200 | Performance | User-Created Statistics In Place | https://www.BrentOzar.com/go/userstats | 122 | | 200 | Performance | SSAS/SSIS/SSRS Installed | https://www.BrentOzar.com/go/services | 224 | From a7ef1d97a64035ae817200fb2e4b6d2b462d80e7 Mon Sep 17 00:00:00 2001 From: Reece Goding <67124261+ReeceGoding@users.noreply.github.com> Date: Sat, 15 Jun 2024 19:55:45 +0100 Subject: [PATCH 567/662] Added check for Query Store Wait Stats --- sp_Blitz.sql | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index 09bbeef81..caea2adb6 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -6758,6 +6758,37 @@ IF @ProductVersionMajor >= 10 AND N''?'' NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''DWConfiguration'', ''DWDiagnostics'', ''DWQueue'', ''ReportServer'', ''ReportServerTempDB'') OPTION (RECOMPILE)'; END; + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 262 ) + AND EXISTS(SELECT * FROM sys.all_objects WHERE name = 'database_query_store_options') + AND @ProductVersionMajor > 13 /* The relevant column only exists in 2017+ */ + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 262) WITH NOWAIT; + + EXEC dbo.sp_MSforeachdb 'USE [?]; + SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + INSERT INTO #BlitzResults + (CheckID, + DatabaseName, + Priority, + FindingsGroup, + Finding, + URL, + Details) + SELECT TOP 1 262, + N''?'', + 200, + ''Performance'', + ''Query Store Wait Stats Disabled'', + ''https://www.sqlskills.com/blogs/erin/query-store-settings/'', + (''The new SQL Server 2017 Query Store feature for tracking wait stats has not been enabled on this database. It is very useful for tracking wait stats at a query level.'') + FROM [?].sys.database_query_store_options + WHERE desired_state <> 0 + AND wait_stats_capture_mode = 0 + OPTION (RECOMPILE)'; + END; IF @ProductVersionMajor = 13 AND @ProductVersionMinor < 2149 --2016 CU1 has the fix in it AND NOT EXISTS ( SELECT 1 From e16ccc25a79ba941c041fbee2fc196f93a43f0a2 Mon Sep 17 00:00:00 2001 From: Reece Goding <67124261+ReeceGoding@users.noreply.github.com> Date: Sat, 15 Jun 2024 20:48:05 +0100 Subject: [PATCH 568/662] Added check for effectively disabled Query Store --- Documentation/sp_Blitz_Checks_by_Priority.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Documentation/sp_Blitz_Checks_by_Priority.md b/Documentation/sp_Blitz_Checks_by_Priority.md index 0600e82e3..ae70fab9b 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: 261. -If you want to add a new one, start at 262. +CURRENT HIGH CHECKID: 262. +If you want to add a new one, start at 263. | Priority | FindingsGroup | Finding | URL | CheckID | |----------|-----------------------------|---------------------------------------------------------|------------------------------------------------------------------------|----------| @@ -247,6 +247,7 @@ If you want to add a new one, start at 262. | 200 | Performance | Non-Dynamic Memory | https://www.BrentOzar.com/go/memory | 190 | | 200 | Performance | Old Compatibility Level | https://www.BrentOzar.com/go/compatlevel | 62 | | 200 | Performance | Query Store Disabled | https://www.BrentOzar.com/go/querystore | 163 | +| 200 | Performance | Query Store Effectively Disabled | https://learn.microsoft.com/en-us/sql/relational-databases/performance/best-practice-with-the-query-store?view=sql-server-ver16#Verify | 262 | | 200 | Performance | Snapshot Backups Occurring | https://www.BrentOzar.com/go/snaps | 178 | | 200 | Performance | User-Created Statistics In Place | https://www.BrentOzar.com/go/userstats | 122 | | 200 | Performance | SSAS/SSIS/SSRS Installed | https://www.BrentOzar.com/go/services | 224 | From 631624daaf5ce72bd6c6b2d0b34bbb6c0e81c02c Mon Sep 17 00:00:00 2001 From: Reece Goding <67124261+ReeceGoding@users.noreply.github.com> Date: Sat, 15 Jun 2024 20:57:15 +0100 Subject: [PATCH 569/662] Added check for Undesired Query Store states --- Documentation/sp_Blitz_Checks_by_Priority.md | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/sp_Blitz_Checks_by_Priority.md b/Documentation/sp_Blitz_Checks_by_Priority.md index ae70fab9b..b758451b8 100644 --- a/Documentation/sp_Blitz_Checks_by_Priority.md +++ b/Documentation/sp_Blitz_Checks_by_Priority.md @@ -248,6 +248,7 @@ If you want to add a new one, start at 263. | 200 | Performance | Old Compatibility Level | https://www.BrentOzar.com/go/compatlevel | 62 | | 200 | Performance | Query Store Disabled | https://www.BrentOzar.com/go/querystore | 163 | | 200 | Performance | Query Store Effectively Disabled | https://learn.microsoft.com/en-us/sql/relational-databases/performance/best-practice-with-the-query-store?view=sql-server-ver16#Verify | 262 | +| 200 | Performance | Undesired Query Store State | https://learn.microsoft.com/en-us/sql/relational-databases/performance/best-practice-with-the-query-store?view=sql-server-ver16#Verify | 263 | | 200 | Performance | Snapshot Backups Occurring | https://www.BrentOzar.com/go/snaps | 178 | | 200 | Performance | User-Created Statistics In Place | https://www.BrentOzar.com/go/userstats | 122 | | 200 | Performance | SSAS/SSIS/SSRS Installed | https://www.BrentOzar.com/go/services | 224 | From 37f34671d791d4234603da51d6e32ee86a34599c Mon Sep 17 00:00:00 2001 From: Reece Goding <67124261+ReeceGoding@users.noreply.github.com> Date: Sat, 15 Jun 2024 20:57:36 +0100 Subject: [PATCH 570/662] Incremented checkid. --- Documentation/sp_Blitz_Checks_by_Priority.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Documentation/sp_Blitz_Checks_by_Priority.md b/Documentation/sp_Blitz_Checks_by_Priority.md index b758451b8..95729a39b 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: 262. -If you want to add a new one, start at 263. +CURRENT HIGH CHECKID: 263. +If you want to add a new one, start at 264. | Priority | FindingsGroup | Finding | URL | CheckID | |----------|-----------------------------|---------------------------------------------------------|------------------------------------------------------------------------|----------| From cee1c5380167a14866ae1f3ea3ea0955f3a63261 Mon Sep 17 00:00:00 2001 From: Reece Goding <67124261+ReeceGoding@users.noreply.github.com> Date: Sat, 15 Jun 2024 21:01:32 +0100 Subject: [PATCH 571/662] Added check for Query Store being disabled, as well as being in an undesired state. --- sp_Blitz.sql | 61 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index 09bbeef81..112f888c8 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -6758,6 +6758,67 @@ IF @ProductVersionMajor >= 10 AND N''?'' NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''DWConfiguration'', ''DWDiagnostics'', ''DWQueue'', ''ReportServer'', ''ReportServerTempDB'') OPTION (RECOMPILE)'; END; + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 262 ) + AND EXISTS(SELECT * FROM sys.all_objects WHERE name = 'database_query_store_options') + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 262) WITH NOWAIT; + + EXEC dbo.sp_MSforeachdb 'USE [?]; + SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + INSERT INTO #BlitzResults + (CheckID, + DatabaseName, + Priority, + FindingsGroup, + Finding, + URL, + Details) + SELECT TOP 1 262, + N''?'', + 200, + ''Performance'', + ''Query Store Effectively Disabled'', + ''https://learn.microsoft.com/en-us/sql/relational-databases/performance/best-practice-with-the-query-store?view=sql-server-ver16#Verify'', + (''Query Store is not in a state where it is writing, so it is effectively disabled. Check your Query Store settings.'') + FROM [?].sys.database_query_store_options + WHERE desired_state <> 0 + AND actual_state <> 2 + OPTION (RECOMPILE)'; + END; + + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 263 ) + AND EXISTS(SELECT * FROM sys.all_objects WHERE name = 'database_query_store_options') + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 263) WITH NOWAIT; + + EXEC dbo.sp_MSforeachdb 'USE [?]; + SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + INSERT INTO #BlitzResults + (CheckID, + DatabaseName, + Priority, + FindingsGroup, + Finding, + URL, + Details) + SELECT TOP 1 263, + N''?'', + 200, + ''Performance'', + ''Undesired Query Store State'', + ''https://learn.microsoft.com/en-us/sql/relational-databases/performance/best-practice-with-the-query-store?view=sql-server-ver16#Verify'', + (''You have asked for Query Store to be in '' + desired_state_desc + '' mode, but it is in '' + actual_state_desc + '' mode.'') + FROM [?].sys.database_query_store_options + WHERE desired_state <> 0 + AND desired_state <> actual_state + OPTION (RECOMPILE)'; + END; IF @ProductVersionMajor = 13 AND @ProductVersionMinor < 2149 --2016 CU1 has the fix in it AND NOT EXISTS ( SELECT 1 From 2d1cbd0211e442862a362525a1e612d3d7f9b3b9 Mon Sep 17 00:00:00 2001 From: Reece Goding <67124261+ReeceGoding@users.noreply.github.com> Date: Sat, 15 Jun 2024 21:22:02 +0100 Subject: [PATCH 572/662] Fixed Query Store URL. --- sp_Blitz.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index 112f888c8..b234f21fb 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -6781,7 +6781,7 @@ IF @ProductVersionMajor >= 10 200, ''Performance'', ''Query Store Effectively Disabled'', - ''https://learn.microsoft.com/en-us/sql/relational-databases/performance/best-practice-with-the-query-store?view=sql-server-ver16#Verify'', + ''https://learn.microsoft.com/en-us/sql/relational-databases/performance/best-practice-with-the-query-store#Verify'', (''Query Store is not in a state where it is writing, so it is effectively disabled. Check your Query Store settings.'') FROM [?].sys.database_query_store_options WHERE desired_state <> 0 @@ -6812,7 +6812,7 @@ IF @ProductVersionMajor >= 10 200, ''Performance'', ''Undesired Query Store State'', - ''https://learn.microsoft.com/en-us/sql/relational-databases/performance/best-practice-with-the-query-store?view=sql-server-ver16#Verify'', + ''https://learn.microsoft.com/en-us/sql/relational-databases/performance/best-practice-with-the-query-store#Verify'', (''You have asked for Query Store to be in '' + desired_state_desc + '' mode, but it is in '' + actual_state_desc + '' mode.'') FROM [?].sys.database_query_store_options WHERE desired_state <> 0 From 8bd64277d8818a48feeaa1be6baf05615438ab6d Mon Sep 17 00:00:00 2001 From: Reece Goding <67124261+ReeceGoding@users.noreply.github.com> Date: Sat, 15 Jun 2024 21:23:06 +0100 Subject: [PATCH 573/662] Fixed Query Store URL. --- Documentation/sp_Blitz_Checks_by_Priority.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Documentation/sp_Blitz_Checks_by_Priority.md b/Documentation/sp_Blitz_Checks_by_Priority.md index 95729a39b..bd7af1c5b 100644 --- a/Documentation/sp_Blitz_Checks_by_Priority.md +++ b/Documentation/sp_Blitz_Checks_by_Priority.md @@ -247,8 +247,8 @@ If you want to add a new one, start at 264. | 200 | Performance | Non-Dynamic Memory | https://www.BrentOzar.com/go/memory | 190 | | 200 | Performance | Old Compatibility Level | https://www.BrentOzar.com/go/compatlevel | 62 | | 200 | Performance | Query Store Disabled | https://www.BrentOzar.com/go/querystore | 163 | -| 200 | Performance | Query Store Effectively Disabled | https://learn.microsoft.com/en-us/sql/relational-databases/performance/best-practice-with-the-query-store?view=sql-server-ver16#Verify | 262 | -| 200 | Performance | Undesired Query Store State | https://learn.microsoft.com/en-us/sql/relational-databases/performance/best-practice-with-the-query-store?view=sql-server-ver16#Verify | 263 | +| 200 | Performance | Query Store Effectively Disabled | https://learn.microsoft.com/en-us/sql/relational-databases/performance/best-practice-with-the-query-store#Verify | 262 | +| 200 | Performance | Undesired Query Store State | https://learn.microsoft.com/en-us/sql/relational-databases/performance/best-practice-with-the-query-store#Verify | 263 | | 200 | Performance | Snapshot Backups Occurring | https://www.BrentOzar.com/go/snaps | 178 | | 200 | Performance | User-Created Statistics In Place | https://www.BrentOzar.com/go/userstats | 122 | | 200 | Performance | SSAS/SSIS/SSRS Installed | https://www.BrentOzar.com/go/services | 224 | From a81a424e56b385b4c9d134541aec0ae424677e48 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Thu, 20 Jun 2024 06:59:55 -0700 Subject: [PATCH 574/662] 3537 removing readme references For deprecated scripts. Closes #3537 and #3529. --- Deprecated/readme.txt | 7 ++----- README.md | 4 +--- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/Deprecated/readme.txt b/Deprecated/readme.txt index 013ede2a5..1e4e774fb 100644 --- a/Deprecated/readme.txt +++ b/Deprecated/readme.txt @@ -1,6 +1,3 @@ -These old versions worked on SQL Server 2005. +sp_AllNightLog, sp_AllNightLog_Setup, sp_BlitzInMemoryOLTP, and sp_BlitzQueryStore are no longer maintained. They may still work, but no guarantees. Please don't submit issues or pull requests to change them. You're welcome to fork the code and build your own supported version, of course, since they're open source. -They are no longer maintained or updated. - -Don't use 'em on SQL Server 2008 or newer - these are only for folks who still -need to manage SQL Server 2005. \ No newline at end of file +The other files in this folder are older versions of the First Responder Kit that work with versions of Microsoft SQL Server that Microsoft themselves no longer support. If you're cursed enough to work with antiques like SQL Server 2012, 2008, or heaven forbid, 2005, you may still be able to get value out of these, but we don't support the scripts anymore either. \ No newline at end of file diff --git a/README.md b/README.md index b357f249c..8caaad4c8 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,6 @@ Navigation - [sp_BlitzAnalysis: Query sp_BlitzFirst output tables](#sp_blitzanalysis-query-sp_BlitzFirst-output-tables) - Backups and Restores: - [sp_BlitzBackups: How Much Data Could You Lose](#sp_blitzbackups-how-much-data-could-you-lose) - - [sp_AllNightLog: Back Up Faster to Lose Less Data](#sp_allnightlog-back-up-faster-to-lose-less-data) - [sp_DatabaseRestore: Easier Multi-File Restores](#sp_databaserestore-easier-multi-file-restores) - [Parameters Common to Many of the Stored Procedures](#parameters-common-to-many-of-the-stored-procedures) - [License MIT](#license) @@ -42,7 +41,7 @@ To install, [download the latest release ZIP](https://github.com/BrentOzarULTD/S The First Responder Kit runs on: * SQL Server on Windows - all versions that Microsoft supports. For end of support dates, check out the "Support Ends" column at https://sqlserverupdates.com. -* SQL Server on Linux - yes, fully supported except sp_AllNightLog and sp_DatabaseRestore, which require xp_cmdshell, which Microsoft doesn't provide on Linux. +* SQL Server on Linux - yes, fully supported except sp_DatabaseRestore, which require xp_cmdshell, which Microsoft doesn't provide on Linux. * Amazon RDS SQL Server - fully supported. * Azure SQL DB - not supported. Some of the procedures work, but some don't, and Microsoft has a tendency to change DMVs in Azure without warning, so we don't put any effort into supporting it. If it works, great! If not, any changes to make it work would be on you. [See the contributing.md file](CONTRIBUTING.md) for how to do that. @@ -275,7 +274,6 @@ sp_BlitzIndex focuses on mainstream index types. Other index types have varying * Fully supported: rowstore indexes, columnstore indexes, temporal tables. * Columnstore indexes: fully supported. Key columns are shown as includes rather than keys since they're not in a specific order. -* In-Memory OLTP (Hekaton): unsupported. These objects show up in the results, but for more info, you'll want to use sp_BlitzInMemoryOLTP instead. * Graph tables: unsupported. These objects show up in the results, but we don't do anything special with 'em, like call out that they're graph tables. * Spatial indexes: unsupported. We call out that they're spatial, but we don't do any special handling for them. * XML indexes: unsupported. These objects show up in the results, but we don't include the index's columns or sizes. From 593467b0e30a17255b501cf4bb84f9f5167719b5 Mon Sep 17 00:00:00 2001 From: Reece Goding <67124261+ReeceGoding@users.noreply.github.com> Date: Thu, 20 Jun 2024 19:44:19 +0100 Subject: [PATCH 575/662] Incremented checkids --- Documentation/sp_Blitz_Checks_by_Priority.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Documentation/sp_Blitz_Checks_by_Priority.md b/Documentation/sp_Blitz_Checks_by_Priority.md index bd7af1c5b..c9fcf18f2 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: 263. -If you want to add a new one, start at 264. +CURRENT HIGH CHECKID: 264. +If you want to add a new one, start at 265. | Priority | FindingsGroup | Finding | URL | CheckID | |----------|-----------------------------|---------------------------------------------------------|------------------------------------------------------------------------|----------| @@ -247,8 +247,8 @@ If you want to add a new one, start at 264. | 200 | Performance | Non-Dynamic Memory | https://www.BrentOzar.com/go/memory | 190 | | 200 | Performance | Old Compatibility Level | https://www.BrentOzar.com/go/compatlevel | 62 | | 200 | Performance | Query Store Disabled | https://www.BrentOzar.com/go/querystore | 163 | -| 200 | Performance | Query Store Effectively Disabled | https://learn.microsoft.com/en-us/sql/relational-databases/performance/best-practice-with-the-query-store#Verify | 262 | -| 200 | Performance | Undesired Query Store State | https://learn.microsoft.com/en-us/sql/relational-databases/performance/best-practice-with-the-query-store#Verify | 263 | +| 200 | Performance | Query Store Effectively Disabled | https://learn.microsoft.com/en-us/sql/relational-databases/performance/best-practice-with-the-query-store#Verify | 263 | +| 200 | Performance | Undesired Query Store State | https://learn.microsoft.com/en-us/sql/relational-databases/performance/best-practice-with-the-query-store#Verify | 264 | | 200 | Performance | Snapshot Backups Occurring | https://www.BrentOzar.com/go/snaps | 178 | | 200 | Performance | User-Created Statistics In Place | https://www.BrentOzar.com/go/userstats | 122 | | 200 | Performance | SSAS/SSIS/SSRS Installed | https://www.BrentOzar.com/go/services | 224 | From 71e4db5aee5c2c3f522e17b2bca333386c7e2d4b Mon Sep 17 00:00:00 2001 From: Reece Goding <67124261+ReeceGoding@users.noreply.github.com> Date: Thu, 20 Jun 2024 19:47:05 +0100 Subject: [PATCH 576/662] Incremented check ids --- sp_Blitz.sql | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index b234f21fb..4ad9ebdd7 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -6760,11 +6760,11 @@ IF @ProductVersionMajor >= 10 IF NOT EXISTS ( SELECT 1 FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 262 ) + WHERE DatabaseName IS NULL AND CheckID = 263 ) AND EXISTS(SELECT * FROM sys.all_objects WHERE name = 'database_query_store_options') BEGIN - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 262) WITH NOWAIT; + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 263) WITH NOWAIT; EXEC dbo.sp_MSforeachdb 'USE [?]; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; @@ -6776,7 +6776,7 @@ IF @ProductVersionMajor >= 10 Finding, URL, Details) - SELECT TOP 1 262, + SELECT TOP 1 263, N''?'', 200, ''Performance'', @@ -6791,11 +6791,11 @@ IF @ProductVersionMajor >= 10 IF NOT EXISTS ( SELECT 1 FROM #SkipChecks - WHERE DatabaseName IS NULL AND CheckID = 263 ) + WHERE DatabaseName IS NULL AND CheckID = 264 ) AND EXISTS(SELECT * FROM sys.all_objects WHERE name = 'database_query_store_options') BEGIN - IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 263) WITH NOWAIT; + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 264) WITH NOWAIT; EXEC dbo.sp_MSforeachdb 'USE [?]; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; @@ -6807,7 +6807,7 @@ IF @ProductVersionMajor >= 10 Finding, URL, Details) - SELECT TOP 1 263, + SELECT TOP 1 264, N''?'', 200, ''Performance'', From b7342f31af7fc4b8bf445a8138eee2b2ecd67f16 Mon Sep 17 00:00:00 2001 From: Reece Goding <67124261+ReeceGoding@users.noreply.github.com> Date: Thu, 20 Jun 2024 19:59:57 +0100 Subject: [PATCH 577/662] Removed unintentional extra line break. --- Documentation/sp_Blitz_Checks_by_Priority.md | 1 - 1 file changed, 1 deletion(-) diff --git a/Documentation/sp_Blitz_Checks_by_Priority.md b/Documentation/sp_Blitz_Checks_by_Priority.md index 8daf118fb..9609705b6 100644 --- a/Documentation/sp_Blitz_Checks_by_Priority.md +++ b/Documentation/sp_Blitz_Checks_by_Priority.md @@ -9,7 +9,6 @@ If you want to change anything about a check - the priority, finding, URL, or ID CURRENT HIGH CHECKID: 264. If you want to add a new one, start at 265. - | Priority | FindingsGroup | Finding | URL | CheckID | |----------|-----------------------------|---------------------------------------------------------|------------------------------------------------------------------------|----------| | 0 | Outdated sp_Blitz | sp_Blitz is Over 6 Months Old | https://www.BrentOzar.com/blitz/ | 155 | From 8e815a5711f69fef1b0c787d609f0a745ba04969 Mon Sep 17 00:00:00 2001 From: John McCall Date: Sun, 23 Jun 2024 11:15:17 -0400 Subject: [PATCH 578/662] feat: allow enabling service broker on restore --- README.md | 2 +- sp_DatabaseRestore.sql | 450 +++++++++++++++++++++-------------------- 2 files changed, 228 insertions(+), 224 deletions(-) diff --git a/README.md b/README.md index 8caaad4c8..e874b611c 100644 --- a/README.md +++ b/README.md @@ -462,7 +462,7 @@ Parameters include: * @Debug - default 0. When 1, we print out messages of what we're doing in the messages tab of SSMS. * @StopAt NVARCHAR(14) - pass in a date time to stop your restores at a time like '20170508201501'. This doesn't use the StopAt parameter for the restore command - it simply stops restoring logs that would have this date/time's contents in it. (For example, if you're taking backups every 15 minutes on the hour, and you pass in 9:05 AM as part of the restore time, the restores would stop at your last log backup that doesn't include 9:05AM's data - but it won't restore right up to 9:05 AM.) * @SkipBackupsAlreadyInMsdb - default 0. When set to 1, we check MSDB for the most recently restored backup from this log path, and skip all backup files prior to that. Useful if you're pulling backups from across a slow network and you don't want to wait to check the restore header of each backup. - +* @EnableBroker - default 0. When set to 1, we run RESTORE WITH ENABLE_BROKER, enabling the service broker. Unless specified, the service broker is disabled on restore even if it was enabled when the backup was taken. For information about how this works, see [Tara Kizer's white paper on Log Shipping 2.0 with Google Compute Engine.](https://www.brentozar.com/archive/2017/03/new-white-paper-build-sql-server-disaster-recovery-plan-google-compute-engine/) diff --git a/sp_DatabaseRestore.sql b/sp_DatabaseRestore.sql index 49c9408ef..6463d410b 100755 --- a/sp_DatabaseRestore.sql +++ b/sp_DatabaseRestore.sql @@ -12,28 +12,28 @@ IF OBJECT_ID('dbo.sp_DatabaseRestore') IS NULL EXEC ('CREATE PROCEDURE dbo.sp_DatabaseRestore AS RETURN 0;'); GO ALTER PROCEDURE [dbo].[sp_DatabaseRestore] - @Database NVARCHAR(128) = NULL, - @RestoreDatabaseName NVARCHAR(128) = NULL, - @BackupPathFull NVARCHAR(260) = NULL, - @BackupPathDiff NVARCHAR(260) = NULL, + @Database NVARCHAR(128) = NULL, + @RestoreDatabaseName NVARCHAR(128) = NULL, + @BackupPathFull NVARCHAR(260) = NULL, + @BackupPathDiff NVARCHAR(260) = NULL, @BackupPathLog NVARCHAR(260) = NULL, - @MoveFiles BIT = 1, - @MoveDataDrive NVARCHAR(260) = NULL, - @MoveLogDrive NVARCHAR(260) = NULL, + @MoveFiles BIT = 1, + @MoveDataDrive NVARCHAR(260) = NULL, + @MoveLogDrive NVARCHAR(260) = NULL, @MoveFilestreamDrive NVARCHAR(260) = NULL, - @MoveFullTextCatalogDrive NVARCHAR(260) = NULL, + @MoveFullTextCatalogDrive NVARCHAR(260) = NULL, @BufferCount INT = NULL, @MaxTransferSize INT = NULL, @BlockSize INT = NULL, - @TestRestore BIT = 0, - @RunCheckDB BIT = 0, + @TestRestore BIT = 0, + @RunCheckDB BIT = 0, @RestoreDiff BIT = 0, - @ContinueLogs BIT = 0, + @ContinueLogs BIT = 0, @StandbyMode BIT = 0, @StandbyUndoPath NVARCHAR(MAX) = NULL, - @RunRecovery BIT = 0, + @RunRecovery BIT = 0, @ForceSimpleRecovery BIT = 0, - @ExistingDBAction tinyint = 0, + @ExistingDBAction TINYINT = 0, @StopAt NVARCHAR(14) = NULL, @OnlyLogsAfter NVARCHAR(14) = NULL, @SimpleFolderEnumeration BIT = 0, @@ -44,13 +44,14 @@ ALTER PROCEDURE [dbo].[sp_DatabaseRestore] @KeepCdc BIT = 0, @Execute CHAR(1) = Y, @FileExtensionDiff NVARCHAR(128) = NULL, - @Debug INT = 0, + @Debug INT = 0, @Help BIT = 0, @Version VARCHAR(30) = NULL OUTPUT, @VersionDate DATETIME = NULL OUTPUT, @VersionCheckMode BIT = 0, @FileNamePrefix NVARCHAR(260) = NULL, - @RunStoredProcAfterRestore NVARCHAR(260) = NULL + @RunStoredProcAfterRestore NVARCHAR(260) = NULL, + @EnableBroker BIT = 0 AS SET NOCOUNT ON; SET STATISTICS XML OFF; @@ -64,43 +65,43 @@ BEGIN RETURN; END; - + IF @Help = 1 BEGIN PRINT ' /* sp_DatabaseRestore from http://FirstResponderKit.org - + This script will restore a database from a given file path. - + To learn more, visit http://FirstResponderKit.org where you can download new versions for free, watch training videos on how it works, get more info on the findings, contribute your own code, and more. - + Known limitations of this version: - Only Microsoft-supported versions of SQL Server. Sorry, 2005 and 2000. - Tastes awful with marmite. - + Unknown limitations of this version: - None. (If we knew them, they would be known. Duh.) - + Changes - for the full list of improvements and fixes in this version, see: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/ - + MIT License - + Copyright (c) Brent Ozar Unlimited - + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - + The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -108,119 +109,119 @@ BEGIN LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - + */ '; - + PRINT ' /* - EXEC dbo.sp_DatabaseRestore - @Database = ''LogShipMe'', - @BackupPathFull = ''D:\Backup\SQL2016PROD1A\LogShipMe\FULL\'', - @BackupPathLog = ''D:\Backup\SQL2016PROD1A\LogShipMe\LOG\'', - @ContinueLogs = 0, + EXEC dbo.sp_DatabaseRestore + @Database = ''LogShipMe'', + @BackupPathFull = ''D:\Backup\SQL2016PROD1A\LogShipMe\FULL\'', + @BackupPathLog = ''D:\Backup\SQL2016PROD1A\LogShipMe\LOG\'', + @ContinueLogs = 0, @RunRecovery = 0; - - EXEC dbo.sp_DatabaseRestore - @Database = ''LogShipMe'', - @BackupPathFull = ''D:\Backup\SQL2016PROD1A\LogShipMe\FULL\'', - @BackupPathLog = ''D:\Backup\SQL2016PROD1A\LogShipMe\LOG\'', - @ContinueLogs = 1, + + EXEC dbo.sp_DatabaseRestore + @Database = ''LogShipMe'', + @BackupPathFull = ''D:\Backup\SQL2016PROD1A\LogShipMe\FULL\'', + @BackupPathLog = ''D:\Backup\SQL2016PROD1A\LogShipMe\LOG\'', + @ContinueLogs = 1, @RunRecovery = 0; - - EXEC dbo.sp_DatabaseRestore - @Database = ''LogShipMe'', - @BackupPathFull = ''D:\Backup\SQL2016PROD1A\LogShipMe\FULL\'', - @BackupPathLog = ''D:\Backup\SQL2016PROD1A\LogShipMe\LOG\'', - @ContinueLogs = 1, + + EXEC dbo.sp_DatabaseRestore + @Database = ''LogShipMe'', + @BackupPathFull = ''D:\Backup\SQL2016PROD1A\LogShipMe\FULL\'', + @BackupPathLog = ''D:\Backup\SQL2016PROD1A\LogShipMe\LOG\'', + @ContinueLogs = 1, @RunRecovery = 1; - - EXEC dbo.sp_DatabaseRestore - @Database = ''LogShipMe'', - @BackupPathFull = ''D:\Backup\SQL2016PROD1A\LogShipMe\FULL\'', - @BackupPathLog = ''D:\Backup\SQL2016PROD1A\LogShipMe\LOG\'', - @ContinueLogs = 0, + + EXEC dbo.sp_DatabaseRestore + @Database = ''LogShipMe'', + @BackupPathFull = ''D:\Backup\SQL2016PROD1A\LogShipMe\FULL\'', + @BackupPathLog = ''D:\Backup\SQL2016PROD1A\LogShipMe\LOG\'', + @ContinueLogs = 0, @RunRecovery = 1; - - EXEC dbo.sp_DatabaseRestore - @Database = ''LogShipMe'', - @BackupPathFull = ''D:\Backup\SQL2016PROD1A\LogShipMe\FULL\'', + + EXEC dbo.sp_DatabaseRestore + @Database = ''LogShipMe'', + @BackupPathFull = ''D:\Backup\SQL2016PROD1A\LogShipMe\FULL\'', @BackupPathDiff = ''D:\Backup\SQL2016PROD1A\LogShipMe\DIFF\'', - @BackupPathLog = ''D:\Backup\SQL2016PROD1A\LogShipMe\LOG\'', + @BackupPathLog = ''D:\Backup\SQL2016PROD1A\LogShipMe\LOG\'', @RestoreDiff = 1, - @ContinueLogs = 0, + @ContinueLogs = 0, @RunRecovery = 1; - - EXEC dbo.sp_DatabaseRestore - @Database = ''LogShipMe'', - @BackupPathFull = ''\\StorageServer\LogShipMe\FULL\'', + + EXEC dbo.sp_DatabaseRestore + @Database = ''LogShipMe'', + @BackupPathFull = ''\\StorageServer\LogShipMe\FULL\'', @BackupPathDiff = ''\\StorageServer\LogShipMe\DIFF\'', - @BackupPathLog = ''\\StorageServer\LogShipMe\LOG\'', + @BackupPathLog = ''\\StorageServer\LogShipMe\LOG\'', @RestoreDiff = 1, - @ContinueLogs = 0, + @ContinueLogs = 0, @RunRecovery = 1, @TestRestore = 1, @RunCheckDB = 1, @Debug = 0; - EXEC dbo.sp_DatabaseRestore - @Database = ''LogShipMe'', - @BackupPathFull = ''\\StorageServer\LogShipMe\FULL\'', + EXEC dbo.sp_DatabaseRestore + @Database = ''LogShipMe'', + @BackupPathFull = ''\\StorageServer\LogShipMe\FULL\'', @BackupPathLog = ''\\StorageServer\LogShipMe\LOG\'', @StandbyMode = 1, @StandbyUndoPath = ''D:\Data\'', - @ContinueLogs = 1, + @ContinueLogs = 1, @RunRecovery = 0, @Debug = 0; --Restore just through the latest DIFF, ignoring logs, and using a custom ".dif" file extension - EXEC dbo.sp_DatabaseRestore - @Database = ''LogShipMe'', - @BackupPathFull = ''D:\Backup\SQL2016PROD1A\LogShipMe\FULL\'', + EXEC dbo.sp_DatabaseRestore + @Database = ''LogShipMe'', + @BackupPathFull = ''D:\Backup\SQL2016PROD1A\LogShipMe\FULL\'', @BackupPathDiff = ''D:\Backup\SQL2016PROD1A\LogShipMe\DIFF\'', @RestoreDiff = 1, @FileExtensionDiff = ''dif'', - @ContinueLogs = 0, + @ContinueLogs = 0, @RunRecovery = 1; -- Restore from stripped backup set when multiple paths are used. This example will restore stripped full backup set along with stripped transactional logs set from multiple backup paths - EXEC dbo.sp_DatabaseRestore - @Database = ''DBA'', - @BackupPathFull = ''D:\Backup1\DBA\FULL,D:\Backup2\DBA\FULL'', - @BackupPathLog = ''D:\Backup1\DBA\LOG,D:\Backup2\DBA\LOG'', + EXEC dbo.sp_DatabaseRestore + @Database = ''DBA'', + @BackupPathFull = ''D:\Backup1\DBA\FULL,D:\Backup2\DBA\FULL'', + @BackupPathLog = ''D:\Backup1\DBA\LOG,D:\Backup2\DBA\LOG'', @StandbyMode = 0, - @ContinueLogs = 1, + @ContinueLogs = 1, @RunRecovery = 0, @Debug = 0; - + --This example will restore the latest differential backup, and stop transaction logs at the specified date time. It will execute and print debug information. - EXEC dbo.sp_DatabaseRestore - @Database = ''DBA'', - @BackupPathFull = ''\\StorageServer\LogShipMe\FULL\'', + EXEC dbo.sp_DatabaseRestore + @Database = ''DBA'', + @BackupPathFull = ''\\StorageServer\LogShipMe\FULL\'', @BackupPathDiff = ''\\StorageServer\LogShipMe\DIFF\'', - @BackupPathLog = ''\\StorageServer\LogShipMe\LOG\'', + @BackupPathLog = ''\\StorageServer\LogShipMe\LOG\'', @RestoreDiff = 1, - @ContinueLogs = 0, + @ContinueLogs = 0, @RunRecovery = 1, @StopAt = ''20170508201501'', @Debug = 1; --This example will NOT execute the restore. Commands will be printed in a copy/paste ready format only - EXEC dbo.sp_DatabaseRestore - @Database = ''DBA'', - @BackupPathFull = ''\\StorageServer\LogShipMe\FULL\'', + EXEC dbo.sp_DatabaseRestore + @Database = ''DBA'', + @BackupPathFull = ''\\StorageServer\LogShipMe\FULL\'', @BackupPathDiff = ''\\StorageServer\LogShipMe\DIFF\'', - @BackupPathLog = ''\\StorageServer\LogShipMe\LOG\'', + @BackupPathLog = ''\\StorageServer\LogShipMe\LOG\'', @RestoreDiff = 1, - @ContinueLogs = 0, + @ContinueLogs = 0, @RunRecovery = 1, @TestRestore = 1, @RunCheckDB = 1, @Debug = 0, @Execute = ''N''; '; - - RETURN; + + RETURN; END; -- Get the SQL Server version number because the columns returned by RESTORE commands vary by version @@ -237,7 +238,7 @@ BEGIN RETURN; END; -BEGIN TRY +BEGIN TRY DECLARE @CurrentDatabaseContext AS VARCHAR(128) = (SELECT DB_NAME()); DECLARE @CommandExecuteCheck VARCHAR(315) @@ -269,16 +270,16 @@ DECLARE @cmd NVARCHAR(4000) = N'', --Holds xp_cmdshell command @LogRestoreRanking INT = 1, --Holds Log iteration # when multiple paths & backup files are being stripped @LogFirstLSN NUMERIC(25, 0), --Holds first LSN in log backup headers @LogLastLSN NUMERIC(25, 0), --Holds last LSN in log backup headers - @LogLastNameInMsdbAS NVARCHAR(MAX) = N'', -- Holds last TRN file name already restored + @LogLastNameInMsdbAS NVARCHAR(MAX) = N'', -- Holds last TRN file name already restored @FileListParamSQL NVARCHAR(4000) = N'', --Holds INSERT list for #FileListParameters @BackupParameters NVARCHAR(500) = N'', --Used to save BlockSize, MaxTransferSize and BufferCount @RestoreDatabaseID SMALLINT, --Holds DB_ID of @RestoreDatabaseName - @UnquotedRestoreDatabaseName nvarchar(128); --Holds the unquoted @RestoreDatabaseName + @UnquotedRestoreDatabaseName NVARCHAR(128); --Holds the unquoted @RestoreDatabaseName DECLARE @FileListSimple TABLE ( - BackupFile NVARCHAR(255) NOT NULL, - depth int NOT NULL, - [file] int NOT NULL + BackupFile NVARCHAR(255) NOT NULL, + depth INT NOT NULL, + [file] INT NOT NULL ); DECLARE @FileList TABLE ( @@ -377,8 +378,8 @@ CREATE TABLE #Headers KeyAlgorithm NVARCHAR(32), EncryptorThumbprint VARBINARY(20), EncryptorType NVARCHAR(32), - LastValidRestoreTime DATETIME, - TimeZone NVARCHAR(32), + LastValidRestoreTime DATETIME, + TimeZone NVARCHAR(32), CompressionAlgorithm NVARCHAR(32), -- -- Seq added to retain order by @@ -540,7 +541,7 @@ SET @UnquotedRestoreDatabaseName = PARSENAME(@RestoreDatabaseName,1); IF NOT EXISTS (SELECT * FROM sys.configurations WHERE name = 'xp_cmdshell' AND value_in_use = 1) SET @SimpleFolderEnumeration = 1; -SET @HeadersSQL = +SET @HeadersSQL = N'INSERT INTO #Headers WITH (TABLOCK) (BackupName, BackupDescription, BackupType, ExpirationDate, Compressed, Position, DeviceType, UserName, ServerName ,DatabaseName, DatabaseVersion, DatabaseCreationDate, BackupSize, FirstLSN, LastLSN, CheckpointLSN, DatabaseBackupLSN @@ -549,7 +550,7 @@ N'INSERT INTO #Headers WITH (TABLOCK) ,RecoveryForkID, Collation, FamilyGUID, HasBulkLoggedData, IsSnapshot, IsReadOnly, IsSingleUser, HasBackupChecksums ,IsDamaged, BeginsLogChain, HasIncompleteMetaData, IsForceOffline, IsCopyOnly, FirstRecoveryForkID, ForkPointLSN ,RecoveryModel, DifferentialBaseLSN, DifferentialBaseGUID, BackupTypeDescription, BackupSetGUID, CompressedBackupSize'; - + IF @MajorVersion >= 11 SET @HeadersSQL += NCHAR(13) + NCHAR(10) + N', Containment'; @@ -566,7 +567,7 @@ IF @BackupPathFull IS NOT NULL BEGIN DECLARE @CurrentBackupPathFull NVARCHAR(255); - -- Split CSV string logic has taken from Ola Hallengren's :) + -- Split CSV string logic has taken from Ola Hallengren's :) WITH BackupPaths ( StartPosition, EndPosition, PathItem ) @@ -593,7 +594,7 @@ BEGIN IF @@rowcount = 0 BREAK; IF @SimpleFolderEnumeration = 1 - BEGIN -- Get list of files + BEGIN -- Get list of files INSERT INTO @FileListSimple (BackupFile, depth, [file]) EXEC master.sys.xp_dirtree @CurrentBackupPathFull, 1, 1; INSERT @FileList (BackupPath,BackupFile) SELECT @CurrentBackupPathFull, BackupFile FROM @FileListSimple; DELETE FROM @FileListSimple; @@ -605,12 +606,12 @@ BEGIN BEGIN IF @cmd IS NULL PRINT '@cmd is NULL for @CurrentBackupPathFull'; PRINT @cmd; - END; + END; INSERT INTO @FileList (BackupFile) EXEC master.sys.xp_cmdshell @cmd; UPDATE @FileList SET BackupPath = @CurrentBackupPathFull WHERE BackupPath IS NULL; END; - + IF @Debug = 1 BEGIN SELECT BackupPath, BackupFile FROM @FileList; @@ -628,8 +629,8 @@ BEGIN BEGIN /*Full Sanity check folders*/ IF ( - SELECT COUNT(*) - FROM @FileList AS fl + SELECT COUNT(*) + FROM @FileList AS fl WHERE fl.BackupFile = 'The system cannot find the path specified.' OR fl.BackupFile = 'File Not Found' ) = 1 @@ -638,8 +639,8 @@ BEGIN RETURN; END; IF ( - SELECT COUNT(*) - FROM @FileList AS fl + SELECT COUNT(*) + FROM @FileList AS fl WHERE fl.BackupFile = 'Access is denied.' ) = 1 BEGIN @@ -647,13 +648,13 @@ BEGIN RETURN; END; IF ( - SELECT COUNT(*) - FROM @FileList AS fl + SELECT COUNT(*) + FROM @FileList AS fl ) = 1 - AND + AND ( - SELECT COUNT(*) - FROM @FileList AS fl + SELECT COUNT(*) + FROM @FileList AS fl WHERE fl.BackupFile IS NULL ) = 1 BEGIN @@ -661,8 +662,8 @@ BEGIN RETURN; END IF ( - SELECT COUNT(*) - FROM @FileList AS fl + SELECT COUNT(*) + FROM @FileList AS fl WHERE fl.BackupFile = 'The user name or password is incorrect.' ) = 1 BEGIN @@ -687,8 +688,8 @@ BEGIN AND BackupFile LIKE N'%' + @Database + N'%' AND (REPLACE( RIGHT( REPLACE( BackupFile, RIGHT( BackupFile, PATINDEX( '%_[0-9][0-9]%', REVERSE( BackupFile ) ) ), '' ), 18 ), '_', '' ) > @StopAt); END; - - -- Find latest full backup + + -- Find latest full backup SELECT @LastFullBackup = MAX(BackupFile) FROM @FileList WHERE BackupFile LIKE N'%.bak' @@ -717,11 +718,11 @@ BEGIN FROM @FileList WHERE LEFT( BackupFile, LEN( BackupFile ) - PATINDEX( '%[_]%', REVERSE( BackupFile ) ) ) = LEFT( @LastFullBackup, LEN( @LastFullBackup ) - PATINDEX( '%[_]%', REVERSE( @LastFullBackup ) ) ) AND PATINDEX( '%[_]%', REVERSE( @LastFullBackup ) ) <= 7 -- there is a 1 or 2 digit index at the end of the string which indicates split backups. Ola only supports up to 64 file split. - ORDER BY REPLACE( RIGHT( REPLACE( BackupFile, RIGHT( BackupFile, PATINDEX( '%_[0-9][0-9]%', REVERSE( BackupFile ) ) ), '' ), 16 ), '_', '' ) DESC; + ORDER BY REPLACE( RIGHT( REPLACE( BackupFile, RIGHT( BackupFile, PATINDEX( '%_[0-9][0-9]%', REVERSE( BackupFile ) ) ), '' ), 16 ), '_', '' ) DESC; -- File list can be obtained by running RESTORE FILELISTONLY of any file from the given BackupSet therefore we do not have to cater for split backups when building @FileListParamSQL - SET @FileListParamSQL = + SET @FileListParamSQL = N'INSERT INTO #FileListParameters WITH (TABLOCK) (LogicalName, PhysicalName, Type, FileGroupName, Size, MaxSize, FileID, CreateLSN, DropLSN ,UniqueID, ReadOnlyLSN, ReadWriteLSN, BackupSizeInBytes, SourceBlockSize, FileGroupID, LogGroupGUID @@ -755,7 +756,7 @@ BEGIN SELECT '@FileList' AS table_name, BackupPath, BackupFile FROM @FileList WHERE BackupFile IS NOT NULL; END - --get the backup completed data so we can apply tlogs from that point forwards + --get the backup completed data so we can apply tlogs from that point forwards SET @sql = REPLACE(@HeadersSQL, N'{Path}', @CurrentBackupPathFull + @LastFullBackup); IF @Debug = 1 @@ -813,7 +814,7 @@ BEGIN SELECT @MoveOption = @MoveOption + N', MOVE ''' + Files.LogicalName + N''' TO ''' + Files.TargetPhysicalName + '''' FROM Files WHERE Files.TargetPhysicalName <> Files.PhysicalName; - + IF @Debug = 1 PRINT @MoveOption END; @@ -833,7 +834,7 @@ BEGIN END; IF @Debug IN (0, 1) AND @Execute = 'Y' BEGIN - IF DATABASEPROPERTYEX(@UnquotedRestoreDatabaseName,'STATUS') != 'RESTORING' + IF DATABASEPROPERTYEX(@UnquotedRestoreDatabaseName,'STATUS') != 'RESTORING' BEGIN EXECUTE @sql = [dbo].[CommandExecute] @DatabaseContext=N'master', @Command = @sql, @CommandType = 'ALTER DATABASE SINGLE_USER', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y'; END @@ -848,7 +849,7 @@ BEGIN BEGIN RAISERROR('Killing connections', 0, 1) WITH NOWAIT; SET @sql = N'/* Kill connections */' + NCHAR(13); - SELECT + SELECT @sql = @sql + N'KILL ' + CAST(spid as nvarchar(5)) + N';' + NCHAR(13) FROM --database_ID was only added to sys.dm_exec_sessions in SQL Server 2012 but we need to support older @@ -866,7 +867,7 @@ BEGIN IF @ExistingDBAction = 3 BEGIN RAISERROR('Dropping database', 0, 1) WITH NOWAIT; - + SET @sql = N'DROP DATABASE ' + @RestoreDatabaseName + NCHAR(13); IF @Debug = 1 OR @Execute = 'N' BEGIN @@ -888,7 +889,7 @@ BEGIN END; IF @Debug IN (0, 1) AND @Execute = 'Y' BEGIN - IF DATABASEPROPERTYEX(@UnquotedRestoreDatabaseName,'STATUS') != 'RESTORING' + IF DATABASEPROPERTYEX(@UnquotedRestoreDatabaseName,'STATUS') != 'RESTORING' BEGIN EXECUTE @sql = [dbo].[CommandExecute] @DatabaseContext=N'master', @Command = @sql, @CommandType = 'OFFLINE DATABASE', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y'; END @@ -909,7 +910,7 @@ BEGIN IF @ContinueLogs = 0 BEGIN IF @Execute = 'Y' RAISERROR('@ContinueLogs set to 0', 0, 1) WITH NOWAIT; - + /* now take split backups into account */ IF (SELECT COUNT(*) FROM #SplitFullBackups) > 0 BEGIN @@ -949,7 +950,7 @@ BEGIN IF @sql IS NULL PRINT '@sql is NULL for RESTORE DATABASE: @BackupPathFull, @LastFullBackup, @MoveOption'; PRINT @sql; END; - + IF @Debug IN (0, 1) AND @Execute = 'Y' EXECUTE @sql = [dbo].[CommandExecute] @DatabaseContext=N'master', @Command = @sql, @CommandType = 'RESTORE DATABASE', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y'; @@ -957,44 +958,44 @@ BEGIN --setting the @BackupDateTime to a numeric string so that it can be used in comparisons SET @BackupDateTime = REPLACE( RIGHT( REPLACE( @LastFullBackup, RIGHT( @LastFullBackup, PATINDEX( '%_[0-9][0-9]%', REVERSE( @LastFullBackup ) ) ), '' ), 16 ), '_', '' ); - - SELECT @FullLastLSN = CAST(LastLSN AS NUMERIC(25, 0)) FROM #Headers WHERE BackupType = 1; + + SELECT @FullLastLSN = CAST(LastLSN AS NUMERIC(25, 0)) FROM #Headers WHERE BackupType = 1; IF @Debug = 1 BEGIN IF @BackupDateTime IS NULL PRINT '@BackupDateTime is NULL for REPLACE: @LastFullBackup'; PRINT @BackupDateTime; - END; - + END; + END; ELSE BEGIN - + SELECT @DatabaseLastLSN = CAST(f.redo_start_lsn AS NUMERIC(25, 0)) FROM master.sys.databases d JOIN master.sys.master_files f ON d.database_id = f.database_id WHERE d.name = SUBSTRING(@RestoreDatabaseName, 2, LEN(@RestoreDatabaseName) - 2) AND f.file_id = 1; - + END; END; IF @BackupPathFull IS NULL AND @ContinueLogs = 1 BEGIN - + SELECT @DatabaseLastLSN = CAST(f.redo_start_lsn AS NUMERIC(25, 0)) FROM master.sys.databases d JOIN master.sys.master_files f ON d.database_id = f.database_id WHERE d.name = SUBSTRING(@RestoreDatabaseName, 2, LEN(@RestoreDatabaseName) - 2) AND f.file_id = 1; - + END; IF @BackupPathDiff IS NOT NULL -BEGIN +BEGIN DELETE FROM @FileList; DELETE FROM @FileListSimple; DELETE FROM @PathItem; DECLARE @CurrentBackupPathDiff NVARCHAR(512); - -- Split CSV string logic has taken from Ola Hallengren's :) + -- Split CSV string logic has taken from Ola Hallengren's :) WITH BackupPaths ( StartPosition, EndPosition, PathItem ) @@ -1021,7 +1022,7 @@ BEGIN IF @@rowcount = 0 BREAK; IF @SimpleFolderEnumeration = 1 - BEGIN -- Get list of files + BEGIN -- Get list of files INSERT INTO @FileListSimple (BackupFile, depth, [file]) EXEC master.sys.xp_dirtree @CurrentBackupPathDiff, 1, 1; INSERT @FileList (BackupPath,BackupFile) SELECT @CurrentBackupPathDiff, BackupFile FROM @FileListSimple; DELETE FROM @FileListSimple; @@ -1033,11 +1034,11 @@ BEGIN BEGIN IF @cmd IS NULL PRINT '@cmd is NULL for @CurrentBackupPathDiff'; PRINT @cmd; - END; + END; INSERT INTO @FileList (BackupFile) EXEC master.sys.xp_cmdshell @cmd; UPDATE @FileList SET BackupPath = @CurrentBackupPathDiff WHERE BackupPath IS NULL; END; - + IF @Debug = 1 BEGIN SELECT BackupPath,BackupFile FROM @FileList WHERE BackupFile IS NOT NULL; @@ -1046,8 +1047,8 @@ BEGIN BEGIN /*Full Sanity check folders*/ IF ( - SELECT COUNT(*) - FROM @FileList AS fl + SELECT COUNT(*) + FROM @FileList AS fl WHERE fl.BackupFile = 'The system cannot find the path specified.' ) = 1 BEGIN @@ -1055,8 +1056,8 @@ BEGIN RETURN; END; IF ( - SELECT COUNT(*) - FROM @FileList AS fl + SELECT COUNT(*) + FROM @FileList AS fl WHERE fl.BackupFile = 'Access is denied.' ) = 1 BEGIN @@ -1064,8 +1065,8 @@ BEGIN RETURN; END; IF ( - SELECT COUNT(*) - FROM @FileList AS fl + SELECT COUNT(*) + FROM @FileList AS fl WHERE fl.BackupFile = 'The user name or password is incorrect.' ) = 1 BEGIN @@ -1075,7 +1076,7 @@ BEGIN END; END /*End folder sanity check*/ - -- Find latest diff backup + -- Find latest diff backup IF @FileExtensionDiff IS NULL BEGIN IF @Execute = 'Y' OR @Debug = 1 RAISERROR('No @FileExtensionDiff given, assuming "bak".', 0, 1) WITH NOWAIT; @@ -1091,7 +1092,7 @@ BEGIN (@StopAt IS NULL OR REPLACE( RIGHT( REPLACE( BackupFile, RIGHT( BackupFile, PATINDEX( '%_[0-9][0-9]%', REVERSE( BackupFile ) ) ), '' ), 16 ), '_', '' ) <= @StopAt) ORDER BY BackupFile DESC; - -- Load FileList data into Temp Table sorted by DateTime Stamp desc + -- Load FileList data into Temp Table sorted by DateTime Stamp desc SELECT BackupPath, BackupFile INTO #SplitDiffBackups FROM @FileList WHERE LEFT( BackupFile, LEN( BackupFile ) - PATINDEX( '%[_]%', REVERSE( BackupFile ) ) ) = LEFT( @LastDiffBackup, LEN( @LastDiffBackup ) - PATINDEX( '%[_]%', REVERSE( @LastDiffBackup ) ) ) @@ -1128,49 +1129,49 @@ BEGIN END ELSE IF (SELECT COUNT(*) FROM #SplitDiffBackups) > 0 SET @sql = @sql + ', STANDBY = ''' + @StandbyUndoPath + @Database + 'Undo.ldf''' + NCHAR(13) + NCHAR(10); - ELSE + ELSE SET @sql = N'RESTORE DATABASE ' + @RestoreDatabaseName + N' FROM DISK = ''' + @BackupPathDiff + @LastDiffBackup + N''' WITH STANDBY = ''' + @StandbyUndoPath + @Database + 'Undo.ldf''' + @BackupParameters + @MoveOption + NCHAR(13) + NCHAR(10); END; IF @Debug = 1 OR @Execute = 'N' BEGIN IF @sql IS NULL PRINT '@sql is NULL for RESTORE DATABASE: @BackupPathDiff, @LastDiffBackup'; PRINT @sql; - END; + END; IF @Debug IN (0, 1) AND @Execute = 'Y' EXECUTE @sql = [dbo].[CommandExecute] @DatabaseContext=N'master', @Command = @sql, @CommandType = 'RESTORE DATABASE', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y'; - - --get the backup completed data so we can apply tlogs from that point forwards + + --get the backup completed data so we can apply tlogs from that point forwards SET @sql = REPLACE(@HeadersSQL, N'{Path}', @CurrentBackupPathDiff + @LastDiffBackup); - + IF @Debug = 1 BEGIN IF @sql IS NULL PRINT '@sql is NULL for REPLACE: @CurrentBackupPathDiff, @LastDiffBackup'; PRINT @sql; - END; - + END; + EXECUTE (@sql); IF @Debug = 1 BEGIN SELECT '#Headers' AS table_name, @LastDiffBackup AS DiffbackupFile, * FROM #Headers AS h WHERE h.BackupType = 5; END - - --set the @BackupDateTime to the date time on the most recent differential + + --set the @BackupDateTime to the date time on the most recent differential SET @BackupDateTime = ISNULL( @LastDiffBackupDateTime, @BackupDateTime ); IF @Debug = 1 BEGIN IF @BackupDateTime IS NULL PRINT '@BackupDateTime is NULL for REPLACE: @LastDiffBackupDateTime'; PRINT @BackupDateTime; - END; + END; SELECT @DiffLastLSN = CAST(LastLSN AS NUMERIC(25, 0)) - FROM #Headers - WHERE BackupType = 5; + FROM #Headers + WHERE BackupType = 5; END; IF @DiffLastLSN IS NULL BEGIN SET @DiffLastLSN=@FullLastLSN END -END +END IF @BackupPathLog IS NOT NULL @@ -1180,7 +1181,7 @@ BEGIN DELETE FROM @PathItem; DECLARE @CurrentBackupPathLog NVARCHAR(512); - -- Split CSV string logic has taken from Ola Hallengren's :) + -- Split CSV string logic has taken from Ola Hallengren's :) WITH BackupPaths ( StartPosition, EndPosition, PathItem ) @@ -1206,7 +1207,7 @@ BEGIN IF @@rowcount = 0 BREAK; IF @SimpleFolderEnumeration = 1 - BEGIN -- Get list of files + BEGIN -- Get list of files INSERT INTO @FileListSimple (BackupFile, depth, [file]) EXEC master.sys.xp_dirtree @BackupPathLog, 1, 1; INSERT @FileList (BackupPath, BackupFile) SELECT @CurrentBackupPathLog, BackupFile FROM @FileListSimple; DELETE FROM @FileListSimple; @@ -1218,12 +1219,12 @@ BEGIN BEGIN IF @cmd IS NULL PRINT '@cmd is NULL for @CurrentBackupPathLog'; PRINT @cmd; - END; + END; INSERT INTO @FileList (BackupFile) EXEC master.sys.xp_cmdshell @cmd; UPDATE @FileList SET BackupPath = @CurrentBackupPathLog WHERE BackupPath IS NULL; END; - + IF @SimpleFolderEnumeration = 1 BEGIN /*Check what we can*/ @@ -1237,8 +1238,8 @@ BEGIN BEGIN /*Full Sanity check folders*/ IF ( - SELECT COUNT(*) - FROM @FileList AS fl + SELECT COUNT(*) + FROM @FileList AS fl WHERE fl.BackupFile = 'The system cannot find the path specified.' OR fl.BackupFile = 'File Not Found' ) = 1 @@ -1248,8 +1249,8 @@ BEGIN END; IF ( - SELECT COUNT(*) - FROM @FileList AS fl + SELECT COUNT(*) + FROM @FileList AS fl WHERE fl.BackupFile = 'Access is denied.' ) = 1 BEGIN @@ -1258,13 +1259,13 @@ BEGIN END; IF ( - SELECT COUNT(*) - FROM @FileList AS fl + SELECT COUNT(*) + FROM @FileList AS fl ) = 1 - AND + AND ( - SELECT COUNT(*) - FROM @FileList AS fl + SELECT COUNT(*) + FROM @FileList AS fl WHERE fl.BackupFile IS NULL ) = 1 BEGIN @@ -1273,8 +1274,8 @@ BEGIN END IF ( - SELECT COUNT(*) - FROM @FileList AS fl + SELECT COUNT(*) + FROM @FileList AS fl WHERE fl.BackupFile = 'The user name or password is incorrect.' ) = 1 BEGIN @@ -1282,11 +1283,11 @@ BEGIN RETURN; END; END; - END + END /*End folder sanity check*/ IF @Debug = 1 -BEGIN +BEGIN SELECT * FROM @FileList WHERE BackupFile IS NOT NULL; END @@ -1300,12 +1301,12 @@ BEGIN WHERE physical_device_name like @BackupPathLog + '%' AND rh.destination_database_name = @UnquotedRestoreDatabaseName ORDER BY physical_device_name DESC - + IF @Debug = 1 BEGIN SELECT 'Keeping LOG backups with name > : ' + @LogLastNameInMsdbAS END - + DELETE fl FROM @FileList AS fl WHERE fl.BackupPath + fl.BackupFile <= @LogLastNameInMsdbAS @@ -1315,20 +1316,20 @@ END IF (@OnlyLogsAfter IS NOT NULL) BEGIN - + IF @Execute = 'Y' OR @Debug = 1 RAISERROR('@OnlyLogsAfter is NOT NULL, deleting from @FileList', 0, 1) WITH NOWAIT; - + DELETE fl FROM @FileList AS fl WHERE BackupFile LIKE N'%.trn' - AND BackupFile LIKE N'%' + @Database + N'%' + AND BackupFile LIKE N'%' + @Database + N'%' AND REPLACE( RIGHT( REPLACE( fl.BackupFile, RIGHT( fl.BackupFile, PATINDEX( '%_[0-9][0-9]%', REVERSE( fl.BackupFile ) ) ), '' ), 16 ), '_', '' ) < @OnlyLogsAfter; - + END -- Check for log backups IF(@BackupDateTime IS NOT NULL AND @BackupDateTime <> '') - BEGIN + BEGIN DELETE FROM @FileList WHERE BackupFile LIKE N'%.trn' AND BackupFile LIKE N'%' + @Database + N'%' @@ -1353,7 +1354,7 @@ IF (@LogRecoveryOption = N'') IF (@StopAt IS NOT NULL) BEGIN - + IF @Execute = 'Y' OR @Debug = 1 RAISERROR('@StopAt is NOT NULL, deleting from @FileList', 0, 1) WITH NOWAIT; IF LEN(@StopAt) <> 14 OR PATINDEX('%[^0-9]%', @StopAt) > 0 @@ -1373,18 +1374,18 @@ BEGIN IF @BackupDateTime = @StopAt BEGIN - IF @Debug = 1 + IF @Debug = 1 BEGIN RAISERROR('@StopAt is the end time of a FULL backup, no log files will be restored.', 0, 1) WITH NOWAIT; END END ELSE - BEGIN + BEGIN DECLARE @ExtraLogFile NVARCHAR(255) SELECT TOP 1 @ExtraLogFile = fl.BackupFile FROM @FileList AS fl WHERE BackupFile LIKE N'%.trn' - AND BackupFile LIKE N'%' + @Database + N'%' + AND BackupFile LIKE N'%' + @Database + N'%' AND REPLACE( RIGHT( REPLACE( fl.BackupFile, RIGHT( fl.BackupFile, PATINDEX( '%_[0-9][0-9]%', REVERSE( fl.BackupFile ) ) ), '' ), 16 ), '_', '' ) > @StopAt ORDER BY BackupFile; END @@ -1402,11 +1403,11 @@ BEGIN -- If this is a split backup, @ExtraLogFile contains only the first split backup file, either _1.trn or _01.trn -- Change @ExtraLogFile to the max split backup file, then delete all log files greater than this SET @ExtraLogFile = REPLACE(REPLACE(@ExtraLogFile, '_1.trn', '_9.trn'), '_01.trn', '_64.trn') - + DELETE fl FROM @FileList AS fl WHERE BackupFile LIKE N'%.trn' - AND BackupFile LIKE N'%' + @Database + N'%' + AND BackupFile LIKE N'%' + @Database + N'%' AND fl.BackupFile > @ExtraLogFile END END @@ -1417,38 +1418,38 @@ SELECT BackupPath,BackupFile,DENSE_RANK() OVER (ORDER BY REPLACE( RIGHT( REPLACE FROM @FileList WHERE BackupFile IS NOT NULL; --- Loop through all the files for the database +-- Loop through all the files for the database WHILE 1 = 1 BEGIN - -- Get the TOP record to use in "Restore HeaderOnly/FileListOnly" statement + -- Get the TOP record to use in "Restore HeaderOnly/FileListOnly" statement SELECT TOP 1 @CurrentBackupPathLog = BackupPath, @BackupFile = BackupFile FROM #SplitLogBackups WHERE DenseRank = @LogRestoreRanking; IF @@rowcount = 0 BREAK; IF @i = 1 - + BEGIN SET @sql = REPLACE(@HeadersSQL, N'{Path}', @CurrentBackupPathLog + @BackupFile); - + IF @Debug = 1 BEGIN IF @sql IS NULL PRINT '@sql is NULL for REPLACE: @HeadersSQL, @CurrentBackupPathLog, @BackupFile'; PRINT @sql; - END; - + END; + EXECUTE (@sql); - - SELECT TOP 1 @LogFirstLSN = CAST(FirstLSN AS NUMERIC(25, 0)), - @LogLastLSN = CAST(LastLSN AS NUMERIC(25, 0)) - FROM #Headers + + SELECT TOP 1 @LogFirstLSN = CAST(FirstLSN AS NUMERIC(25, 0)), + @LogLastLSN = CAST(LastLSN AS NUMERIC(25, 0)) + FROM #Headers WHERE BackupType = 2; - + IF (@ContinueLogs = 0 AND @LogFirstLSN <= @FullLastLSN AND @FullLastLSN <= @LogLastLSN AND @RestoreDiff = 0) OR (@ContinueLogs = 1 AND @LogFirstLSN <= @DatabaseLastLSN AND @DatabaseLastLSN < @LogLastLSN AND @RestoreDiff = 0) SET @i = 2; - + IF (@ContinueLogs = 0 AND @LogFirstLSN <= @DiffLastLSN AND @DiffLastLSN <= @LogLastLSN AND @RestoreDiff = 1) OR (@ContinueLogs = 1 AND @LogFirstLSN <= @DatabaseLastLSN AND @DatabaseLastLSN < @LogLastLSN AND @RestoreDiff = 1) SET @i = 2; - + DELETE FROM #Headers WHERE BackupType = 2; @@ -1469,7 +1470,7 @@ WHERE BackupFile IS NOT NULL; SET @sql = N'RESTORE LOG ' + @RestoreDatabaseName + N' FROM ' + STUFF( (SELECT CHAR( 10 ) + ',DISK=''' + BackupPath + BackupFile + '''' - FROM #SplitLogBackups + FROM #SplitLogBackups WHERE DenseRank = @LogRestoreRanking ORDER BY BackupFile FOR XML PATH ('')), @@ -1479,17 +1480,17 @@ WHERE BackupFile IS NOT NULL; END; ELSE SET @sql = N'RESTORE LOG ' + @RestoreDatabaseName + N' FROM DISK = ''' + @CurrentBackupPathLog + @BackupFile + N''' WITH ' + @LogRecoveryOption + NCHAR(13) + NCHAR(10); - + IF @Debug = 1 OR @Execute = 'N' BEGIN IF @sql IS NULL PRINT '@sql is NULL for RESTORE LOG: @RestoreDatabaseName, @CurrentBackupPathLog, @BackupFile'; PRINT @sql; - END; - + END; + IF @Debug IN (0, 1) AND @Execute = 'Y' EXECUTE @sql = [dbo].[CommandExecute] @DatabaseContext=N'master', @Command = @sql, @CommandType = 'RESTORE LOG', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y'; END; - + SET @LogRestoreRanking += 1; END; @@ -1500,21 +1501,24 @@ WHERE BackupFile IS NOT NULL; END END --- Put database in a useable state +-- Put database in a useable state IF @RunRecovery = 1 BEGIN SET @sql = N'RESTORE DATABASE ' + @RestoreDatabaseName + N' WITH RECOVERY'; - + IF @KeepCdc = 1 SET @sql = @sql + N', KEEP_CDC'; + IF @EnableBroker = 1 + SET @sql = @sql + N', ENABLE_BROKER'; + SET @sql = @sql + NCHAR(13); IF @Debug = 1 OR @Execute = 'N' BEGIN IF @sql IS NULL PRINT '@sql is NULL for RESTORE DATABASE: @RestoreDatabaseName'; PRINT @sql; - END; + END; IF @Debug IN (0, 1) AND @Execute = 'Y' EXECUTE @sql = [dbo].[CommandExecute] @DatabaseContext=N'master', @Command = @sql, @CommandType = 'RECOVER DATABASE', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y'; @@ -1529,23 +1533,23 @@ IF @ForceSimpleRecovery = 1 BEGIN IF @sql IS NULL PRINT '@sql is NULL for SET RECOVERY SIMPLE: @RestoreDatabaseName'; PRINT @sql; - END; + END; IF @Debug IN (0, 1) AND @Execute = 'Y' EXECUTE @sql = [dbo].[CommandExecute] @DatabaseContext=N'master', @Command = @sql, @CommandType = 'SIMPLE LOGGING', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y'; - END; + END; -- Run checkdb against this database IF @RunCheckDB = 1 BEGIN SET @sql = N'DBCC CHECKDB (' + @RestoreDatabaseName + N') WITH NO_INFOMSGS, ALL_ERRORMSGS, DATA_PURITY;'; - + IF @Debug = 1 OR @Execute = 'N' BEGIN IF @sql IS NULL PRINT '@sql is NULL for Run Integrity Check: @RestoreDatabaseName'; PRINT @sql; - END; - + END; + IF @Debug IN (0, 1) AND @Execute = 'Y' EXECUTE @sql = [dbo].[CommandExecute] @DatabaseContext=N'master', @Command = @sql, @CommandType = 'DBCC CHECKDB', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y'; END; @@ -1608,7 +1612,7 @@ IF @DatabaseOwner IS NOT NULL END; -- Link a user entry in the sys.database_principals system catalog view in the restored database to a SQL Server login of the same name -IF @FixOrphanUsers = 1 +IF @FixOrphanUsers = 1 BEGIN SET @sql = N' -- Fixup Orphan Users by setting database user sid to match login sid @@ -1627,7 +1631,7 @@ SELECT ''ALTER USER ['' + d.name + ''] WITH LOGIN = ['' + d.name + '']; '' SELECT @FixOrphansSql = (SELECT SqlToExecute AS [text()] FROM @OrphanUsers FOR XML PATH (''''), TYPE).value(''text()[1]'',''NVARCHAR(MAX)''); -IF @FixOrphansSql IS NULL +IF @FixOrphansSql IS NULL PRINT ''No orphan users require a sid fixup.''; ELSE BEGIN @@ -1643,7 +1647,7 @@ END;' IF @Debug IN (0, 1) AND @Execute = 'Y' EXECUTE [dbo].[CommandExecute] @DatabaseContext = 'master', @Command = @sql, @CommandType = 'UPDATE', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y'; - END; + END; IF @RunStoredProcAfterRestore IS NOT NULL AND LEN(LTRIM(@RunStoredProcAfterRestore)) > 0 BEGIN @@ -1655,7 +1659,7 @@ BEGIN IF @sql IS NULL PRINT '@sql is NULL when building for @RunStoredProcAfterRestore' PRINT @sql END - + IF @RunRecovery = 0 BEGIN PRINT 'Unable to run Run Stored Procedure After Restore as database is not recovered. Run command again with @RunRecovery = 1' @@ -1671,13 +1675,13 @@ END IF @TestRestore = 1 BEGIN SET @sql = N'DROP DATABASE ' + @RestoreDatabaseName + NCHAR(13); - + IF @Debug = 1 OR @Execute = 'N' BEGIN IF @sql IS NULL PRINT '@sql is NULL for DROP DATABASE: @RestoreDatabaseName'; PRINT @sql; - END; - + END; + IF @Debug IN (0, 1) AND @Execute = 'Y' EXECUTE @sql = [dbo].[CommandExecute] @DatabaseContext=N'master',@Command = @sql, @CommandType = 'DROP DATABASE', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y'; From a3a444e8d787bd2500c521a55ba0df0114de1044 Mon Sep 17 00:00:00 2001 From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com> Date: Tue, 25 Jun 2024 13:23:08 -0400 Subject: [PATCH 579/662] Update sp_BlitzLock.sql Closes #3545 --- sp_BlitzLock.sql | 48 ++++++++++++++++++++++++++++-------------------- 1 file changed, 28 insertions(+), 20 deletions(-) diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index 2605f8cfd..d2bae97a8 100644 --- a/sp_BlitzLock.sql +++ b/sp_BlitzLock.sql @@ -18,8 +18,8 @@ ALTER PROCEDURE @EventSessionName sysname = N'system_health', @TargetSessionType sysname = NULL, @VictimsOnly bit = 0, - @DeadlockType nvarchar(20) = NULL, - @Debug bit = 0, + @DeadlockType nvarchar(20) = NULL, + @Debug bit = 0, @Help bit = 0, @Version varchar(30) = NULL OUTPUT, @VersionDate datetime = NULL OUTPUT, @@ -709,18 +709,18 @@ BEGIN END CATCH; END; - IF @DeadlockType IS NOT NULL - BEGIN - SELECT - @DeadlockType = - CASE - WHEN LOWER(@DeadlockType) LIKE 'regular%' - THEN N'Regular Deadlock' - WHEN LOWER(@DeadlockType) LIKE N'parallel%' - THEN N'Parallel Deadlock' - ELSE NULL - END; - END; + IF @DeadlockType IS NOT NULL + BEGIN + SELECT + @DeadlockType = + CASE + WHEN LOWER(@DeadlockType) LIKE 'regular%' + THEN N'Regular Deadlock' + WHEN LOWER(@DeadlockType) LIKE N'parallel%' + THEN N'Parallel Deadlock' + ELSE NULL + END; + END; /*If @TargetSessionType, we need to figure out if it's ring buffer or event file*/ /*Azure has differently named views, so we need to separate. Thanks, Azure.*/ @@ -3463,8 +3463,8 @@ BEGIN AND (d.client_app = @AppName OR @AppName IS NULL) AND (d.host_name = @HostName OR @HostName IS NULL) AND (d.login_name = @LoginName OR @LoginName IS NULL) - AND (d.deadlock_type = @DeadlockType OR @DeadlockType IS NULL) - OPTION (RECOMPILE, LOOP JOIN, HASH JOIN); + AND (d.deadlock_type = @DeadlockType OR @DeadlockType IS NULL) + OPTION (RECOMPILE, LOOP JOIN, HASH JOIN); UPDATE d SET d.inputbuf = @@ -3851,7 +3851,11 @@ BEGIN deqs.max_reserved_threads, deqs.min_used_threads, deqs.max_used_threads, - deqs.total_rows + deqs.total_rows, + max_worker_time_ms = + deqs.max_worker_time / 1000., + max_elapsed_time_ms = + deqs.max_elapsed_time / 1000. INTO #dm_exec_query_stats FROM sys.dm_exec_query_stats AS deqs WHERE EXISTS @@ -3883,8 +3887,10 @@ BEGIN ap.executions_per_second, ap.total_worker_time_ms, ap.avg_worker_time_ms, + ap.max_worker_time_ms, ap.total_elapsed_time_ms, ap.avg_elapsed_time_ms, + ap.max_elapsed_time_ms, ap.total_logical_reads_mb, ap.total_physical_reads_mb, ap.total_logical_writes_mb, @@ -3927,7 +3933,9 @@ BEGIN c.min_used_threads, c.max_used_threads, c.total_rows, - c.query_plan + c.query_plan, + c.max_worker_time_ms, + c.max_elapsed_time_ms FROM #available_plans AS ap OUTER APPLY ( @@ -4078,8 +4086,8 @@ BEGIN @TargetSessionType, VictimsOnly = @VictimsOnly, - DeadlockType = - @DeadlockType, + DeadlockType = + @DeadlockType, Debug = @Debug, Help = From af048390193b9760dad6c66e46665bd466abfdab Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Mon, 1 Jul 2024 08:32:30 -0700 Subject: [PATCH 580/662] 2024-07-01 Release Bumping version numbers and dates, adding 2019 CU27. --- Install-All-Scripts.sql | 690 ++++++++++++++++++++++++---------------- Install-Azure.sql | 115 ++++--- SqlServerVersions.sql | 1 + 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, 499 insertions(+), 327 deletions(-) diff --git a/Install-All-Scripts.sql b/Install-All-Scripts.sql index 5001508dd..18edfe7c3 100644 --- a/Install-All-Scripts.sql +++ b/Install-All-Scripts.sql @@ -38,7 +38,7 @@ AS SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.20', @VersionDate = '20240522'; + SELECT @Version = '8.21', @VersionDate = '20240701'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -155,6 +155,7 @@ AS ,@MinServerMemory bigint ,@MaxServerMemory bigint ,@ColumnStoreIndexesInUse bit + ,@QueryStoreInUse bit ,@TraceFileIssue bit -- Flag for Windows OS to help with Linux support ,@IsWindowsOperatingSystem BIT @@ -6758,6 +6759,99 @@ IF @ProductVersionMajor >= 10 AND N''?'' NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''DWConfiguration'', ''DWDiagnostics'', ''DWQueue'', ''ReportServer'', ''ReportServerTempDB'') OPTION (RECOMPILE)'; END; + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 262 ) + AND EXISTS(SELECT * FROM sys.all_objects WHERE name = 'database_query_store_options') + AND @ProductVersionMajor > 13 /* The relevant column only exists in 2017+ */ + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 262) WITH NOWAIT; + + EXEC dbo.sp_MSforeachdb 'USE [?]; + SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + INSERT INTO #BlitzResults + (CheckID, + DatabaseName, + Priority, + FindingsGroup, + Finding, + URL, + Details) + SELECT TOP 1 262, + N''?'', + 200, + ''Performance'', + ''Query Store Wait Stats Disabled'', + ''https://www.sqlskills.com/blogs/erin/query-store-settings/'', + (''The new SQL Server 2017 Query Store feature for tracking wait stats has not been enabled on this database. It is very useful for tracking wait stats at a query level.'') + FROM [?].sys.database_query_store_options + WHERE desired_state <> 0 + AND wait_stats_capture_mode = 0 + OPTION (RECOMPILE)'; + END; + + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 263 ) + AND EXISTS(SELECT * FROM sys.all_objects WHERE name = 'database_query_store_options') + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 263) WITH NOWAIT; + + EXEC dbo.sp_MSforeachdb 'USE [?]; + SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + INSERT INTO #BlitzResults + (CheckID, + DatabaseName, + Priority, + FindingsGroup, + Finding, + URL, + Details) + SELECT TOP 1 263, + N''?'', + 200, + ''Performance'', + ''Query Store Effectively Disabled'', + ''https://learn.microsoft.com/en-us/sql/relational-databases/performance/best-practice-with-the-query-store#Verify'', + (''Query Store is not in a state where it is writing, so it is effectively disabled. Check your Query Store settings.'') + FROM [?].sys.database_query_store_options + WHERE desired_state <> 0 + AND actual_state <> 2 + OPTION (RECOMPILE)'; + END; + + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 264 ) + AND EXISTS(SELECT * FROM sys.all_objects WHERE name = 'database_query_store_options') + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 264) WITH NOWAIT; + + EXEC dbo.sp_MSforeachdb 'USE [?]; + SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + INSERT INTO #BlitzResults + (CheckID, + DatabaseName, + Priority, + FindingsGroup, + Finding, + URL, + Details) + SELECT TOP 1 264, + N''?'', + 200, + ''Performance'', + ''Undesired Query Store State'', + ''https://learn.microsoft.com/en-us/sql/relational-databases/performance/best-practice-with-the-query-store#Verify'', + (''You have asked for Query Store to be in '' + desired_state_desc + '' mode, but it is in '' + actual_state_desc + '' mode.'') + FROM [?].sys.database_query_store_options + WHERE desired_state <> 0 + AND desired_state <> actual_state + OPTION (RECOMPILE)'; + END; IF @ProductVersionMajor = 13 AND @ProductVersionMinor < 2149 --2016 CU1 has the fix in it AND NOT EXISTS ( SELECT 1 @@ -7587,6 +7681,20 @@ IF @ProductVersionMajor >= 10 IF EXISTS (SELECT * FROM #TemporaryDatabaseResults) SET @ColumnStoreIndexesInUse = 1; END; + /* Check if Query Store is in use - for Github issue #3527 */ + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 74 ) /* Trace flags */ + AND @ProductVersionMajor > 12 /* The relevant column only exists in versions that support Query store */ + BEGIN + TRUNCATE TABLE #TemporaryDatabaseResults; + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 74) WITH NOWAIT; + + EXEC dbo.sp_MSforeachdb 'USE [?]; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; IF EXISTS(SELECT * FROM sys.databases WHERE is_query_store_on = 1) INSERT INTO #TemporaryDatabaseResults (DatabaseName, Finding) VALUES (DB_NAME(), ''Yup'') OPTION (RECOMPILE);'; + IF EXISTS (SELECT * FROM #TemporaryDatabaseResults) SET @QueryStoreInUse = 1; + END; + /* Non-Default Database Scoped Config - Github issue #598 */ IF EXISTS ( SELECT * FROM sys.all_objects WHERE [name] = 'database_scoped_configurations' ) BEGIN @@ -8354,6 +8462,7 @@ IF @ProductVersionMajor >= 10 'Informational' AS FindingsGroup , 'Trace Flag On' AS Finding , CASE WHEN [T].[TraceFlag] = '834' AND @ColumnStoreIndexesInUse = 1 THEN 'https://support.microsoft.com/en-us/kb/3210239' + WHEN [T].[TraceFlag] IN ('7745', '7752') THEN 'https://www.sqlskills.com/blogs/erin/query-store-trace-flags/' ELSE'https://www.BrentOzar.com/go/traceflags/' END AS URL , 'Trace flag ' + CASE WHEN [T].[TraceFlag] = '652' THEN '652 enabled globally, which disables pre-fetching during index scans. This is usually a very bad idea.' @@ -8372,6 +8481,13 @@ IF @ProductVersionMajor >= 10 WHEN [T].[TraceFlag] = '3226' THEN '3226 enabled globally, which keeps the event log clean by not reporting successful backups.' WHEN [T].[TraceFlag] = '3505' THEN '3505 enabled globally, which disables Checkpoints. This is usually a very bad idea.' WHEN [T].[TraceFlag] = '4199' THEN '4199 enabled globally, which enables non-default Query Optimizer fixes, changing query plans from the default behaviors.' + WHEN [T].[TraceFlag] = '7745' AND @ProductVersionMajor > 12 AND @QueryStoreInUse = 1 THEN '7745 enabled globally, which makes shutdowns/failovers quicker by not waiting for Query Store to flush to disk. This good idea loses you the non-flushed Query Store data.' + WHEN [T].[TraceFlag] = '7745' AND @ProductVersionMajor > 12 THEN '7745 enabled globally, which is for Query Store. None of your databases have Query Store enabled, so why do you have this turned on?' + WHEN [T].[TraceFlag] = '7745' AND @ProductVersionMajor <= 12 THEN '7745 enabled globally, which is for Query Store. Query Store does not exist on your SQL Server version, so why do you have this turned on?' + WHEN [T].[TraceFlag] = '7752' AND @ProductVersionMajor > 14 THEN '7752 enabled globally, which is for Query Store. However, it has no effect in your SQL Server version. Consider turning it off.' + WHEN [T].[TraceFlag] = '7752' AND @ProductVersionMajor > 12 AND @QueryStoreInUse = 1 THEN '7752 enabled globally, which stops queries needing to wait on Query Store loading up after database recovery.' + WHEN [T].[TraceFlag] = '7752' AND @ProductVersionMajor > 12 THEN '7752 enabled globally, which is for Query Store. None of your databases have Query Store enabled, so why do you have this turned on?' + WHEN [T].[TraceFlag] = '7752' AND @ProductVersionMajor <= 12 THEN '7752 enabled globally, which is for Query Store. Query Store does not exist on your SQL Server version, so why do you have this turned on?' WHEN [T].[TraceFlag] = '8048' THEN '8048 enabled globally, which tries to reduce CMEMTHREAD waits on servers with a lot of logical processors.' WHEN [T].[TraceFlag] = '8017' AND (CAST(SERVERPROPERTY('Edition') AS NVARCHAR(1000)) LIKE N'%Express%') THEN '8017 is enabled globally, but this is the default for Express Edition.' WHEN [T].[TraceFlag] = '8017' AND (CAST(SERVERPROPERTY('Edition') AS NVARCHAR(1000)) NOT LIKE N'%Express%') THEN '8017 is enabled globally, which disables the creation of schedulers for all logical processors.' @@ -10321,7 +10437,7 @@ AS SET NOCOUNT ON; SET STATISTICS XML OFF; -SELECT @Version = '8.20', @VersionDate = '20240522'; +SELECT @Version = '8.21', @VersionDate = '20240701'; IF(@VersionCheckMode = 1) BEGIN @@ -11199,7 +11315,7 @@ AS SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.20', @VersionDate = '20240522'; + SELECT @Version = '8.21', @VersionDate = '20240701'; IF(@VersionCheckMode = 1) BEGIN @@ -12981,7 +13097,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.20', @VersionDate = '20240522'; +SELECT @Version = '8.21', @VersionDate = '20240701'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -20355,7 +20471,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.20', @VersionDate = '20240522'; +SELECT @Version = '8.21', @VersionDate = '20240701'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -26830,6 +26946,7 @@ ALTER PROCEDURE @EventSessionName sysname = N'system_health', @TargetSessionType sysname = NULL, @VictimsOnly bit = 0, + @DeadlockType nvarchar(20) = NULL, @Debug bit = 0, @Help bit = 0, @Version varchar(30) = NULL OUTPUT, @@ -26848,7 +26965,7 @@ BEGIN SET XACT_ABORT OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.20', @VersionDate = '20240522'; + SELECT @Version = '8.21', @VersionDate = '20240701'; IF @VersionCheckMode = 1 BEGIN @@ -27010,7 +27127,7 @@ BEGIN @StartDateOriginal datetime = @StartDate, @EndDateOriginal datetime = @EndDate, @StartDateUTC datetime, - @EndDateUTC datetime; + @EndDateUTC datetime;; /*Temporary objects used in the procedure*/ DECLARE @@ -27520,50 +27637,63 @@ BEGIN END CATCH; END; + IF @DeadlockType IS NOT NULL + BEGIN + SELECT + @DeadlockType = + CASE + WHEN LOWER(@DeadlockType) LIKE 'regular%' + THEN N'Regular Deadlock' + WHEN LOWER(@DeadlockType) LIKE N'parallel%' + THEN N'Parallel Deadlock' + ELSE NULL + END; + END; + /*If @TargetSessionType, we need to figure out if it's ring buffer or event file*/ /*Azure has differently named views, so we need to separate. Thanks, Azure.*/ - IF - ( - @Azure = 0 - AND @TargetSessionType IS NULL - ) - BEGIN - RAISERROR('@TargetSessionType is NULL, assigning for non-Azure instance', 0, 1) WITH NOWAIT; + IF + ( + @Azure = 0 + AND @TargetSessionType IS NULL + ) + BEGIN + RAISERROR('@TargetSessionType is NULL, assigning for non-Azure instance', 0, 1) WITH NOWAIT; - SELECT TOP (1) - @TargetSessionType = t.target_name - FROM sys.dm_xe_sessions AS s - JOIN sys.dm_xe_session_targets AS t - ON s.address = t.event_session_address - WHERE s.name = @EventSessionName - AND t.target_name IN (N'event_file', N'ring_buffer') - ORDER BY t.target_name - OPTION(RECOMPILE); + SELECT TOP (1) + @TargetSessionType = t.target_name + FROM sys.dm_xe_sessions AS s + JOIN sys.dm_xe_session_targets AS t + ON s.address = t.event_session_address + WHERE s.name = @EventSessionName + AND t.target_name IN (N'event_file', N'ring_buffer') + ORDER BY t.target_name + OPTION(RECOMPILE); - RAISERROR('@TargetSessionType assigned as %s for non-Azure', 0, 1, @TargetSessionType) WITH NOWAIT; - END; + RAISERROR('@TargetSessionType assigned as %s for non-Azure', 0, 1, @TargetSessionType) WITH NOWAIT; + END; - IF - ( - @Azure = 1 - AND @TargetSessionType IS NULL - ) - BEGIN - RAISERROR('@TargetSessionType is NULL, assigning for Azure instance', 0, 1) WITH NOWAIT; + IF + ( + @Azure = 1 + AND @TargetSessionType IS NULL + ) + BEGIN + RAISERROR('@TargetSessionType is NULL, assigning for Azure instance', 0, 1) WITH NOWAIT; - SELECT TOP (1) - @TargetSessionType = t.target_name - FROM sys.dm_xe_database_sessions AS s - JOIN sys.dm_xe_database_session_targets AS t - ON s.address = t.event_session_address - WHERE s.name = @EventSessionName - AND t.target_name IN (N'event_file', N'ring_buffer') - ORDER BY t.target_name - OPTION(RECOMPILE); + SELECT TOP (1) + @TargetSessionType = t.target_name + FROM sys.dm_xe_database_sessions AS s + JOIN sys.dm_xe_database_session_targets AS t + ON s.address = t.event_session_address + WHERE s.name = @EventSessionName + AND t.target_name IN (N'event_file', N'ring_buffer') + ORDER BY t.target_name + OPTION(RECOMPILE); - RAISERROR('@TargetSessionType assigned as %s for Azure', 0, 1, @TargetSessionType) WITH NOWAIT; - END; + RAISERROR('@TargetSessionType assigned as %s for Azure', 0, 1, @TargetSessionType) WITH NOWAIT; + END; /*The system health stuff gets handled different from user extended events.*/ @@ -30261,6 +30391,7 @@ BEGIN AND (d.client_app = @AppName OR @AppName IS NULL) AND (d.host_name = @HostName OR @HostName IS NULL) AND (d.login_name = @LoginName OR @LoginName IS NULL) + AND (d.deadlock_type = @DeadlockType OR @DeadlockType IS NULL) OPTION (RECOMPILE, LOOP JOIN, HASH JOIN); UPDATE d @@ -30648,7 +30779,11 @@ BEGIN deqs.max_reserved_threads, deqs.min_used_threads, deqs.max_used_threads, - deqs.total_rows + deqs.total_rows, + max_worker_time_ms = + deqs.max_worker_time / 1000., + max_elapsed_time_ms = + deqs.max_elapsed_time / 1000. INTO #dm_exec_query_stats FROM sys.dm_exec_query_stats AS deqs WHERE EXISTS @@ -30680,8 +30815,10 @@ BEGIN ap.executions_per_second, ap.total_worker_time_ms, ap.avg_worker_time_ms, + ap.max_worker_time_ms, ap.total_elapsed_time_ms, ap.avg_elapsed_time_ms, + ap.max_elapsed_time_ms, ap.total_logical_reads_mb, ap.total_physical_reads_mb, ap.total_logical_writes_mb, @@ -30724,7 +30861,9 @@ BEGIN c.min_used_threads, c.max_used_threads, c.total_rows, - c.query_plan + c.query_plan, + c.max_worker_time_ms, + c.max_elapsed_time_ms FROM #available_plans AS ap OUTER APPLY ( @@ -30875,6 +31014,8 @@ BEGIN @TargetSessionType, VictimsOnly = @VictimsOnly, + DeadlockType = + @DeadlockType, Debug = @Debug, Help = @@ -30979,7 +31120,7 @@ BEGIN SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.20', @VersionDate = '20240522'; + SELECT @Version = '8.21', @VersionDate = '20240701'; IF(@VersionCheckMode = 1) BEGIN @@ -32346,28 +32487,28 @@ IF OBJECT_ID('dbo.sp_DatabaseRestore') IS NULL EXEC ('CREATE PROCEDURE dbo.sp_DatabaseRestore AS RETURN 0;'); GO ALTER PROCEDURE [dbo].[sp_DatabaseRestore] - @Database NVARCHAR(128) = NULL, - @RestoreDatabaseName NVARCHAR(128) = NULL, - @BackupPathFull NVARCHAR(260) = NULL, - @BackupPathDiff NVARCHAR(260) = NULL, + @Database NVARCHAR(128) = NULL, + @RestoreDatabaseName NVARCHAR(128) = NULL, + @BackupPathFull NVARCHAR(260) = NULL, + @BackupPathDiff NVARCHAR(260) = NULL, @BackupPathLog NVARCHAR(260) = NULL, - @MoveFiles BIT = 1, - @MoveDataDrive NVARCHAR(260) = NULL, - @MoveLogDrive NVARCHAR(260) = NULL, + @MoveFiles BIT = 1, + @MoveDataDrive NVARCHAR(260) = NULL, + @MoveLogDrive NVARCHAR(260) = NULL, @MoveFilestreamDrive NVARCHAR(260) = NULL, - @MoveFullTextCatalogDrive NVARCHAR(260) = NULL, + @MoveFullTextCatalogDrive NVARCHAR(260) = NULL, @BufferCount INT = NULL, @MaxTransferSize INT = NULL, @BlockSize INT = NULL, - @TestRestore BIT = 0, - @RunCheckDB BIT = 0, + @TestRestore BIT = 0, + @RunCheckDB BIT = 0, @RestoreDiff BIT = 0, - @ContinueLogs BIT = 0, + @ContinueLogs BIT = 0, @StandbyMode BIT = 0, @StandbyUndoPath NVARCHAR(MAX) = NULL, - @RunRecovery BIT = 0, + @RunRecovery BIT = 0, @ForceSimpleRecovery BIT = 0, - @ExistingDBAction tinyint = 0, + @ExistingDBAction TINYINT = 0, @StopAt NVARCHAR(14) = NULL, @OnlyLogsAfter NVARCHAR(14) = NULL, @SimpleFolderEnumeration BIT = 0, @@ -32378,63 +32519,64 @@ ALTER PROCEDURE [dbo].[sp_DatabaseRestore] @KeepCdc BIT = 0, @Execute CHAR(1) = Y, @FileExtensionDiff NVARCHAR(128) = NULL, - @Debug INT = 0, + @Debug INT = 0, @Help BIT = 0, @Version VARCHAR(30) = NULL OUTPUT, @VersionDate DATETIME = NULL OUTPUT, @VersionCheckMode BIT = 0, @FileNamePrefix NVARCHAR(260) = NULL, - @RunStoredProcAfterRestore NVARCHAR(260) = NULL + @RunStoredProcAfterRestore NVARCHAR(260) = NULL, + @EnableBroker BIT = 0 AS SET NOCOUNT ON; SET STATISTICS XML OFF; /*Versioning details*/ -SELECT @Version = '8.20', @VersionDate = '20240522'; +SELECT @Version = '8.21', @VersionDate = '20240701'; IF(@VersionCheckMode = 1) BEGIN RETURN; END; - + IF @Help = 1 BEGIN PRINT ' /* sp_DatabaseRestore from http://FirstResponderKit.org - + This script will restore a database from a given file path. - + To learn more, visit http://FirstResponderKit.org where you can download new versions for free, watch training videos on how it works, get more info on the findings, contribute your own code, and more. - + Known limitations of this version: - Only Microsoft-supported versions of SQL Server. Sorry, 2005 and 2000. - Tastes awful with marmite. - + Unknown limitations of this version: - None. (If we knew them, they would be known. Duh.) - + Changes - for the full list of improvements and fixes in this version, see: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/ - + MIT License - + Copyright (c) Brent Ozar Unlimited - + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - + The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -32442,119 +32584,119 @@ BEGIN LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - + */ '; - + PRINT ' /* - EXEC dbo.sp_DatabaseRestore - @Database = ''LogShipMe'', - @BackupPathFull = ''D:\Backup\SQL2016PROD1A\LogShipMe\FULL\'', - @BackupPathLog = ''D:\Backup\SQL2016PROD1A\LogShipMe\LOG\'', - @ContinueLogs = 0, + EXEC dbo.sp_DatabaseRestore + @Database = ''LogShipMe'', + @BackupPathFull = ''D:\Backup\SQL2016PROD1A\LogShipMe\FULL\'', + @BackupPathLog = ''D:\Backup\SQL2016PROD1A\LogShipMe\LOG\'', + @ContinueLogs = 0, @RunRecovery = 0; - - EXEC dbo.sp_DatabaseRestore - @Database = ''LogShipMe'', - @BackupPathFull = ''D:\Backup\SQL2016PROD1A\LogShipMe\FULL\'', - @BackupPathLog = ''D:\Backup\SQL2016PROD1A\LogShipMe\LOG\'', - @ContinueLogs = 1, + + EXEC dbo.sp_DatabaseRestore + @Database = ''LogShipMe'', + @BackupPathFull = ''D:\Backup\SQL2016PROD1A\LogShipMe\FULL\'', + @BackupPathLog = ''D:\Backup\SQL2016PROD1A\LogShipMe\LOG\'', + @ContinueLogs = 1, @RunRecovery = 0; - - EXEC dbo.sp_DatabaseRestore - @Database = ''LogShipMe'', - @BackupPathFull = ''D:\Backup\SQL2016PROD1A\LogShipMe\FULL\'', - @BackupPathLog = ''D:\Backup\SQL2016PROD1A\LogShipMe\LOG\'', - @ContinueLogs = 1, + + EXEC dbo.sp_DatabaseRestore + @Database = ''LogShipMe'', + @BackupPathFull = ''D:\Backup\SQL2016PROD1A\LogShipMe\FULL\'', + @BackupPathLog = ''D:\Backup\SQL2016PROD1A\LogShipMe\LOG\'', + @ContinueLogs = 1, @RunRecovery = 1; - - EXEC dbo.sp_DatabaseRestore - @Database = ''LogShipMe'', - @BackupPathFull = ''D:\Backup\SQL2016PROD1A\LogShipMe\FULL\'', - @BackupPathLog = ''D:\Backup\SQL2016PROD1A\LogShipMe\LOG\'', - @ContinueLogs = 0, + + EXEC dbo.sp_DatabaseRestore + @Database = ''LogShipMe'', + @BackupPathFull = ''D:\Backup\SQL2016PROD1A\LogShipMe\FULL\'', + @BackupPathLog = ''D:\Backup\SQL2016PROD1A\LogShipMe\LOG\'', + @ContinueLogs = 0, @RunRecovery = 1; - - EXEC dbo.sp_DatabaseRestore - @Database = ''LogShipMe'', - @BackupPathFull = ''D:\Backup\SQL2016PROD1A\LogShipMe\FULL\'', + + EXEC dbo.sp_DatabaseRestore + @Database = ''LogShipMe'', + @BackupPathFull = ''D:\Backup\SQL2016PROD1A\LogShipMe\FULL\'', @BackupPathDiff = ''D:\Backup\SQL2016PROD1A\LogShipMe\DIFF\'', - @BackupPathLog = ''D:\Backup\SQL2016PROD1A\LogShipMe\LOG\'', + @BackupPathLog = ''D:\Backup\SQL2016PROD1A\LogShipMe\LOG\'', @RestoreDiff = 1, - @ContinueLogs = 0, + @ContinueLogs = 0, @RunRecovery = 1; - - EXEC dbo.sp_DatabaseRestore - @Database = ''LogShipMe'', - @BackupPathFull = ''\\StorageServer\LogShipMe\FULL\'', + + EXEC dbo.sp_DatabaseRestore + @Database = ''LogShipMe'', + @BackupPathFull = ''\\StorageServer\LogShipMe\FULL\'', @BackupPathDiff = ''\\StorageServer\LogShipMe\DIFF\'', - @BackupPathLog = ''\\StorageServer\LogShipMe\LOG\'', + @BackupPathLog = ''\\StorageServer\LogShipMe\LOG\'', @RestoreDiff = 1, - @ContinueLogs = 0, + @ContinueLogs = 0, @RunRecovery = 1, @TestRestore = 1, @RunCheckDB = 1, @Debug = 0; - EXEC dbo.sp_DatabaseRestore - @Database = ''LogShipMe'', - @BackupPathFull = ''\\StorageServer\LogShipMe\FULL\'', + EXEC dbo.sp_DatabaseRestore + @Database = ''LogShipMe'', + @BackupPathFull = ''\\StorageServer\LogShipMe\FULL\'', @BackupPathLog = ''\\StorageServer\LogShipMe\LOG\'', @StandbyMode = 1, @StandbyUndoPath = ''D:\Data\'', - @ContinueLogs = 1, + @ContinueLogs = 1, @RunRecovery = 0, @Debug = 0; --Restore just through the latest DIFF, ignoring logs, and using a custom ".dif" file extension - EXEC dbo.sp_DatabaseRestore - @Database = ''LogShipMe'', - @BackupPathFull = ''D:\Backup\SQL2016PROD1A\LogShipMe\FULL\'', + EXEC dbo.sp_DatabaseRestore + @Database = ''LogShipMe'', + @BackupPathFull = ''D:\Backup\SQL2016PROD1A\LogShipMe\FULL\'', @BackupPathDiff = ''D:\Backup\SQL2016PROD1A\LogShipMe\DIFF\'', @RestoreDiff = 1, @FileExtensionDiff = ''dif'', - @ContinueLogs = 0, + @ContinueLogs = 0, @RunRecovery = 1; -- Restore from stripped backup set when multiple paths are used. This example will restore stripped full backup set along with stripped transactional logs set from multiple backup paths - EXEC dbo.sp_DatabaseRestore - @Database = ''DBA'', - @BackupPathFull = ''D:\Backup1\DBA\FULL,D:\Backup2\DBA\FULL'', - @BackupPathLog = ''D:\Backup1\DBA\LOG,D:\Backup2\DBA\LOG'', + EXEC dbo.sp_DatabaseRestore + @Database = ''DBA'', + @BackupPathFull = ''D:\Backup1\DBA\FULL,D:\Backup2\DBA\FULL'', + @BackupPathLog = ''D:\Backup1\DBA\LOG,D:\Backup2\DBA\LOG'', @StandbyMode = 0, - @ContinueLogs = 1, + @ContinueLogs = 1, @RunRecovery = 0, @Debug = 0; - + --This example will restore the latest differential backup, and stop transaction logs at the specified date time. It will execute and print debug information. - EXEC dbo.sp_DatabaseRestore - @Database = ''DBA'', - @BackupPathFull = ''\\StorageServer\LogShipMe\FULL\'', + EXEC dbo.sp_DatabaseRestore + @Database = ''DBA'', + @BackupPathFull = ''\\StorageServer\LogShipMe\FULL\'', @BackupPathDiff = ''\\StorageServer\LogShipMe\DIFF\'', - @BackupPathLog = ''\\StorageServer\LogShipMe\LOG\'', + @BackupPathLog = ''\\StorageServer\LogShipMe\LOG\'', @RestoreDiff = 1, - @ContinueLogs = 0, + @ContinueLogs = 0, @RunRecovery = 1, @StopAt = ''20170508201501'', @Debug = 1; --This example will NOT execute the restore. Commands will be printed in a copy/paste ready format only - EXEC dbo.sp_DatabaseRestore - @Database = ''DBA'', - @BackupPathFull = ''\\StorageServer\LogShipMe\FULL\'', + EXEC dbo.sp_DatabaseRestore + @Database = ''DBA'', + @BackupPathFull = ''\\StorageServer\LogShipMe\FULL\'', @BackupPathDiff = ''\\StorageServer\LogShipMe\DIFF\'', - @BackupPathLog = ''\\StorageServer\LogShipMe\LOG\'', + @BackupPathLog = ''\\StorageServer\LogShipMe\LOG\'', @RestoreDiff = 1, - @ContinueLogs = 0, + @ContinueLogs = 0, @RunRecovery = 1, @TestRestore = 1, @RunCheckDB = 1, @Debug = 0, @Execute = ''N''; '; - - RETURN; + + RETURN; END; -- Get the SQL Server version number because the columns returned by RESTORE commands vary by version @@ -32571,7 +32713,7 @@ BEGIN RETURN; END; -BEGIN TRY +BEGIN TRY DECLARE @CurrentDatabaseContext AS VARCHAR(128) = (SELECT DB_NAME()); DECLARE @CommandExecuteCheck VARCHAR(315) @@ -32603,16 +32745,16 @@ DECLARE @cmd NVARCHAR(4000) = N'', --Holds xp_cmdshell command @LogRestoreRanking INT = 1, --Holds Log iteration # when multiple paths & backup files are being stripped @LogFirstLSN NUMERIC(25, 0), --Holds first LSN in log backup headers @LogLastLSN NUMERIC(25, 0), --Holds last LSN in log backup headers - @LogLastNameInMsdbAS NVARCHAR(MAX) = N'', -- Holds last TRN file name already restored + @LogLastNameInMsdbAS NVARCHAR(MAX) = N'', -- Holds last TRN file name already restored @FileListParamSQL NVARCHAR(4000) = N'', --Holds INSERT list for #FileListParameters @BackupParameters NVARCHAR(500) = N'', --Used to save BlockSize, MaxTransferSize and BufferCount @RestoreDatabaseID SMALLINT, --Holds DB_ID of @RestoreDatabaseName - @UnquotedRestoreDatabaseName nvarchar(128); --Holds the unquoted @RestoreDatabaseName + @UnquotedRestoreDatabaseName NVARCHAR(128); --Holds the unquoted @RestoreDatabaseName DECLARE @FileListSimple TABLE ( - BackupFile NVARCHAR(255) NOT NULL, - depth int NOT NULL, - [file] int NOT NULL + BackupFile NVARCHAR(255) NOT NULL, + depth INT NOT NULL, + [file] INT NOT NULL ); DECLARE @FileList TABLE ( @@ -32711,8 +32853,8 @@ CREATE TABLE #Headers KeyAlgorithm NVARCHAR(32), EncryptorThumbprint VARBINARY(20), EncryptorType NVARCHAR(32), - LastValidRestoreTime DATETIME, - TimeZone NVARCHAR(32), + LastValidRestoreTime DATETIME, + TimeZone NVARCHAR(32), CompressionAlgorithm NVARCHAR(32), -- -- Seq added to retain order by @@ -32874,7 +33016,7 @@ SET @UnquotedRestoreDatabaseName = PARSENAME(@RestoreDatabaseName,1); IF NOT EXISTS (SELECT * FROM sys.configurations WHERE name = 'xp_cmdshell' AND value_in_use = 1) SET @SimpleFolderEnumeration = 1; -SET @HeadersSQL = +SET @HeadersSQL = N'INSERT INTO #Headers WITH (TABLOCK) (BackupName, BackupDescription, BackupType, ExpirationDate, Compressed, Position, DeviceType, UserName, ServerName ,DatabaseName, DatabaseVersion, DatabaseCreationDate, BackupSize, FirstLSN, LastLSN, CheckpointLSN, DatabaseBackupLSN @@ -32883,7 +33025,7 @@ N'INSERT INTO #Headers WITH (TABLOCK) ,RecoveryForkID, Collation, FamilyGUID, HasBulkLoggedData, IsSnapshot, IsReadOnly, IsSingleUser, HasBackupChecksums ,IsDamaged, BeginsLogChain, HasIncompleteMetaData, IsForceOffline, IsCopyOnly, FirstRecoveryForkID, ForkPointLSN ,RecoveryModel, DifferentialBaseLSN, DifferentialBaseGUID, BackupTypeDescription, BackupSetGUID, CompressedBackupSize'; - + IF @MajorVersion >= 11 SET @HeadersSQL += NCHAR(13) + NCHAR(10) + N', Containment'; @@ -32900,7 +33042,7 @@ IF @BackupPathFull IS NOT NULL BEGIN DECLARE @CurrentBackupPathFull NVARCHAR(255); - -- Split CSV string logic has taken from Ola Hallengren's :) + -- Split CSV string logic has taken from Ola Hallengren's :) WITH BackupPaths ( StartPosition, EndPosition, PathItem ) @@ -32927,7 +33069,7 @@ BEGIN IF @@rowcount = 0 BREAK; IF @SimpleFolderEnumeration = 1 - BEGIN -- Get list of files + BEGIN -- Get list of files INSERT INTO @FileListSimple (BackupFile, depth, [file]) EXEC master.sys.xp_dirtree @CurrentBackupPathFull, 1, 1; INSERT @FileList (BackupPath,BackupFile) SELECT @CurrentBackupPathFull, BackupFile FROM @FileListSimple; DELETE FROM @FileListSimple; @@ -32939,12 +33081,12 @@ BEGIN BEGIN IF @cmd IS NULL PRINT '@cmd is NULL for @CurrentBackupPathFull'; PRINT @cmd; - END; + END; INSERT INTO @FileList (BackupFile) EXEC master.sys.xp_cmdshell @cmd; UPDATE @FileList SET BackupPath = @CurrentBackupPathFull WHERE BackupPath IS NULL; END; - + IF @Debug = 1 BEGIN SELECT BackupPath, BackupFile FROM @FileList; @@ -32962,8 +33104,8 @@ BEGIN BEGIN /*Full Sanity check folders*/ IF ( - SELECT COUNT(*) - FROM @FileList AS fl + SELECT COUNT(*) + FROM @FileList AS fl WHERE fl.BackupFile = 'The system cannot find the path specified.' OR fl.BackupFile = 'File Not Found' ) = 1 @@ -32972,8 +33114,8 @@ BEGIN RETURN; END; IF ( - SELECT COUNT(*) - FROM @FileList AS fl + SELECT COUNT(*) + FROM @FileList AS fl WHERE fl.BackupFile = 'Access is denied.' ) = 1 BEGIN @@ -32981,13 +33123,13 @@ BEGIN RETURN; END; IF ( - SELECT COUNT(*) - FROM @FileList AS fl + SELECT COUNT(*) + FROM @FileList AS fl ) = 1 - AND + AND ( - SELECT COUNT(*) - FROM @FileList AS fl + SELECT COUNT(*) + FROM @FileList AS fl WHERE fl.BackupFile IS NULL ) = 1 BEGIN @@ -32995,8 +33137,8 @@ BEGIN RETURN; END IF ( - SELECT COUNT(*) - FROM @FileList AS fl + SELECT COUNT(*) + FROM @FileList AS fl WHERE fl.BackupFile = 'The user name or password is incorrect.' ) = 1 BEGIN @@ -33021,8 +33163,8 @@ BEGIN AND BackupFile LIKE N'%' + @Database + N'%' AND (REPLACE( RIGHT( REPLACE( BackupFile, RIGHT( BackupFile, PATINDEX( '%_[0-9][0-9]%', REVERSE( BackupFile ) ) ), '' ), 18 ), '_', '' ) > @StopAt); END; - - -- Find latest full backup + + -- Find latest full backup SELECT @LastFullBackup = MAX(BackupFile) FROM @FileList WHERE BackupFile LIKE N'%.bak' @@ -33051,11 +33193,11 @@ BEGIN FROM @FileList WHERE LEFT( BackupFile, LEN( BackupFile ) - PATINDEX( '%[_]%', REVERSE( BackupFile ) ) ) = LEFT( @LastFullBackup, LEN( @LastFullBackup ) - PATINDEX( '%[_]%', REVERSE( @LastFullBackup ) ) ) AND PATINDEX( '%[_]%', REVERSE( @LastFullBackup ) ) <= 7 -- there is a 1 or 2 digit index at the end of the string which indicates split backups. Ola only supports up to 64 file split. - ORDER BY REPLACE( RIGHT( REPLACE( BackupFile, RIGHT( BackupFile, PATINDEX( '%_[0-9][0-9]%', REVERSE( BackupFile ) ) ), '' ), 16 ), '_', '' ) DESC; + ORDER BY REPLACE( RIGHT( REPLACE( BackupFile, RIGHT( BackupFile, PATINDEX( '%_[0-9][0-9]%', REVERSE( BackupFile ) ) ), '' ), 16 ), '_', '' ) DESC; -- File list can be obtained by running RESTORE FILELISTONLY of any file from the given BackupSet therefore we do not have to cater for split backups when building @FileListParamSQL - SET @FileListParamSQL = + SET @FileListParamSQL = N'INSERT INTO #FileListParameters WITH (TABLOCK) (LogicalName, PhysicalName, Type, FileGroupName, Size, MaxSize, FileID, CreateLSN, DropLSN ,UniqueID, ReadOnlyLSN, ReadWriteLSN, BackupSizeInBytes, SourceBlockSize, FileGroupID, LogGroupGUID @@ -33089,7 +33231,7 @@ BEGIN SELECT '@FileList' AS table_name, BackupPath, BackupFile FROM @FileList WHERE BackupFile IS NOT NULL; END - --get the backup completed data so we can apply tlogs from that point forwards + --get the backup completed data so we can apply tlogs from that point forwards SET @sql = REPLACE(@HeadersSQL, N'{Path}', @CurrentBackupPathFull + @LastFullBackup); IF @Debug = 1 @@ -33147,7 +33289,7 @@ BEGIN SELECT @MoveOption = @MoveOption + N', MOVE ''' + Files.LogicalName + N''' TO ''' + Files.TargetPhysicalName + '''' FROM Files WHERE Files.TargetPhysicalName <> Files.PhysicalName; - + IF @Debug = 1 PRINT @MoveOption END; @@ -33167,7 +33309,7 @@ BEGIN END; IF @Debug IN (0, 1) AND @Execute = 'Y' BEGIN - IF DATABASEPROPERTYEX(@UnquotedRestoreDatabaseName,'STATUS') != 'RESTORING' + IF DATABASEPROPERTYEX(@UnquotedRestoreDatabaseName,'STATUS') != 'RESTORING' BEGIN EXECUTE @sql = [dbo].[CommandExecute] @DatabaseContext=N'master', @Command = @sql, @CommandType = 'ALTER DATABASE SINGLE_USER', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y'; END @@ -33182,7 +33324,7 @@ BEGIN BEGIN RAISERROR('Killing connections', 0, 1) WITH NOWAIT; SET @sql = N'/* Kill connections */' + NCHAR(13); - SELECT + SELECT @sql = @sql + N'KILL ' + CAST(spid as nvarchar(5)) + N';' + NCHAR(13) FROM --database_ID was only added to sys.dm_exec_sessions in SQL Server 2012 but we need to support older @@ -33200,7 +33342,7 @@ BEGIN IF @ExistingDBAction = 3 BEGIN RAISERROR('Dropping database', 0, 1) WITH NOWAIT; - + SET @sql = N'DROP DATABASE ' + @RestoreDatabaseName + NCHAR(13); IF @Debug = 1 OR @Execute = 'N' BEGIN @@ -33222,7 +33364,7 @@ BEGIN END; IF @Debug IN (0, 1) AND @Execute = 'Y' BEGIN - IF DATABASEPROPERTYEX(@UnquotedRestoreDatabaseName,'STATUS') != 'RESTORING' + IF DATABASEPROPERTYEX(@UnquotedRestoreDatabaseName,'STATUS') != 'RESTORING' BEGIN EXECUTE @sql = [dbo].[CommandExecute] @DatabaseContext=N'master', @Command = @sql, @CommandType = 'OFFLINE DATABASE', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y'; END @@ -33243,7 +33385,7 @@ BEGIN IF @ContinueLogs = 0 BEGIN IF @Execute = 'Y' RAISERROR('@ContinueLogs set to 0', 0, 1) WITH NOWAIT; - + /* now take split backups into account */ IF (SELECT COUNT(*) FROM #SplitFullBackups) > 0 BEGIN @@ -33283,7 +33425,7 @@ BEGIN IF @sql IS NULL PRINT '@sql is NULL for RESTORE DATABASE: @BackupPathFull, @LastFullBackup, @MoveOption'; PRINT @sql; END; - + IF @Debug IN (0, 1) AND @Execute = 'Y' EXECUTE @sql = [dbo].[CommandExecute] @DatabaseContext=N'master', @Command = @sql, @CommandType = 'RESTORE DATABASE', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y'; @@ -33291,44 +33433,44 @@ BEGIN --setting the @BackupDateTime to a numeric string so that it can be used in comparisons SET @BackupDateTime = REPLACE( RIGHT( REPLACE( @LastFullBackup, RIGHT( @LastFullBackup, PATINDEX( '%_[0-9][0-9]%', REVERSE( @LastFullBackup ) ) ), '' ), 16 ), '_', '' ); - - SELECT @FullLastLSN = CAST(LastLSN AS NUMERIC(25, 0)) FROM #Headers WHERE BackupType = 1; + + SELECT @FullLastLSN = CAST(LastLSN AS NUMERIC(25, 0)) FROM #Headers WHERE BackupType = 1; IF @Debug = 1 BEGIN IF @BackupDateTime IS NULL PRINT '@BackupDateTime is NULL for REPLACE: @LastFullBackup'; PRINT @BackupDateTime; - END; - + END; + END; ELSE BEGIN - + SELECT @DatabaseLastLSN = CAST(f.redo_start_lsn AS NUMERIC(25, 0)) FROM master.sys.databases d JOIN master.sys.master_files f ON d.database_id = f.database_id WHERE d.name = SUBSTRING(@RestoreDatabaseName, 2, LEN(@RestoreDatabaseName) - 2) AND f.file_id = 1; - + END; END; IF @BackupPathFull IS NULL AND @ContinueLogs = 1 BEGIN - + SELECT @DatabaseLastLSN = CAST(f.redo_start_lsn AS NUMERIC(25, 0)) FROM master.sys.databases d JOIN master.sys.master_files f ON d.database_id = f.database_id WHERE d.name = SUBSTRING(@RestoreDatabaseName, 2, LEN(@RestoreDatabaseName) - 2) AND f.file_id = 1; - + END; IF @BackupPathDiff IS NOT NULL -BEGIN +BEGIN DELETE FROM @FileList; DELETE FROM @FileListSimple; DELETE FROM @PathItem; DECLARE @CurrentBackupPathDiff NVARCHAR(512); - -- Split CSV string logic has taken from Ola Hallengren's :) + -- Split CSV string logic has taken from Ola Hallengren's :) WITH BackupPaths ( StartPosition, EndPosition, PathItem ) @@ -33355,7 +33497,7 @@ BEGIN IF @@rowcount = 0 BREAK; IF @SimpleFolderEnumeration = 1 - BEGIN -- Get list of files + BEGIN -- Get list of files INSERT INTO @FileListSimple (BackupFile, depth, [file]) EXEC master.sys.xp_dirtree @CurrentBackupPathDiff, 1, 1; INSERT @FileList (BackupPath,BackupFile) SELECT @CurrentBackupPathDiff, BackupFile FROM @FileListSimple; DELETE FROM @FileListSimple; @@ -33367,11 +33509,11 @@ BEGIN BEGIN IF @cmd IS NULL PRINT '@cmd is NULL for @CurrentBackupPathDiff'; PRINT @cmd; - END; + END; INSERT INTO @FileList (BackupFile) EXEC master.sys.xp_cmdshell @cmd; UPDATE @FileList SET BackupPath = @CurrentBackupPathDiff WHERE BackupPath IS NULL; END; - + IF @Debug = 1 BEGIN SELECT BackupPath,BackupFile FROM @FileList WHERE BackupFile IS NOT NULL; @@ -33380,8 +33522,8 @@ BEGIN BEGIN /*Full Sanity check folders*/ IF ( - SELECT COUNT(*) - FROM @FileList AS fl + SELECT COUNT(*) + FROM @FileList AS fl WHERE fl.BackupFile = 'The system cannot find the path specified.' ) = 1 BEGIN @@ -33389,8 +33531,8 @@ BEGIN RETURN; END; IF ( - SELECT COUNT(*) - FROM @FileList AS fl + SELECT COUNT(*) + FROM @FileList AS fl WHERE fl.BackupFile = 'Access is denied.' ) = 1 BEGIN @@ -33398,8 +33540,8 @@ BEGIN RETURN; END; IF ( - SELECT COUNT(*) - FROM @FileList AS fl + SELECT COUNT(*) + FROM @FileList AS fl WHERE fl.BackupFile = 'The user name or password is incorrect.' ) = 1 BEGIN @@ -33409,7 +33551,7 @@ BEGIN END; END /*End folder sanity check*/ - -- Find latest diff backup + -- Find latest diff backup IF @FileExtensionDiff IS NULL BEGIN IF @Execute = 'Y' OR @Debug = 1 RAISERROR('No @FileExtensionDiff given, assuming "bak".', 0, 1) WITH NOWAIT; @@ -33425,7 +33567,7 @@ BEGIN (@StopAt IS NULL OR REPLACE( RIGHT( REPLACE( BackupFile, RIGHT( BackupFile, PATINDEX( '%_[0-9][0-9]%', REVERSE( BackupFile ) ) ), '' ), 16 ), '_', '' ) <= @StopAt) ORDER BY BackupFile DESC; - -- Load FileList data into Temp Table sorted by DateTime Stamp desc + -- Load FileList data into Temp Table sorted by DateTime Stamp desc SELECT BackupPath, BackupFile INTO #SplitDiffBackups FROM @FileList WHERE LEFT( BackupFile, LEN( BackupFile ) - PATINDEX( '%[_]%', REVERSE( BackupFile ) ) ) = LEFT( @LastDiffBackup, LEN( @LastDiffBackup ) - PATINDEX( '%[_]%', REVERSE( @LastDiffBackup ) ) ) @@ -33462,49 +33604,49 @@ BEGIN END ELSE IF (SELECT COUNT(*) FROM #SplitDiffBackups) > 0 SET @sql = @sql + ', STANDBY = ''' + @StandbyUndoPath + @Database + 'Undo.ldf''' + NCHAR(13) + NCHAR(10); - ELSE + ELSE SET @sql = N'RESTORE DATABASE ' + @RestoreDatabaseName + N' FROM DISK = ''' + @BackupPathDiff + @LastDiffBackup + N''' WITH STANDBY = ''' + @StandbyUndoPath + @Database + 'Undo.ldf''' + @BackupParameters + @MoveOption + NCHAR(13) + NCHAR(10); END; IF @Debug = 1 OR @Execute = 'N' BEGIN IF @sql IS NULL PRINT '@sql is NULL for RESTORE DATABASE: @BackupPathDiff, @LastDiffBackup'; PRINT @sql; - END; + END; IF @Debug IN (0, 1) AND @Execute = 'Y' EXECUTE @sql = [dbo].[CommandExecute] @DatabaseContext=N'master', @Command = @sql, @CommandType = 'RESTORE DATABASE', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y'; - - --get the backup completed data so we can apply tlogs from that point forwards + + --get the backup completed data so we can apply tlogs from that point forwards SET @sql = REPLACE(@HeadersSQL, N'{Path}', @CurrentBackupPathDiff + @LastDiffBackup); - + IF @Debug = 1 BEGIN IF @sql IS NULL PRINT '@sql is NULL for REPLACE: @CurrentBackupPathDiff, @LastDiffBackup'; PRINT @sql; - END; - + END; + EXECUTE (@sql); IF @Debug = 1 BEGIN SELECT '#Headers' AS table_name, @LastDiffBackup AS DiffbackupFile, * FROM #Headers AS h WHERE h.BackupType = 5; END - - --set the @BackupDateTime to the date time on the most recent differential + + --set the @BackupDateTime to the date time on the most recent differential SET @BackupDateTime = ISNULL( @LastDiffBackupDateTime, @BackupDateTime ); IF @Debug = 1 BEGIN IF @BackupDateTime IS NULL PRINT '@BackupDateTime is NULL for REPLACE: @LastDiffBackupDateTime'; PRINT @BackupDateTime; - END; + END; SELECT @DiffLastLSN = CAST(LastLSN AS NUMERIC(25, 0)) - FROM #Headers - WHERE BackupType = 5; + FROM #Headers + WHERE BackupType = 5; END; IF @DiffLastLSN IS NULL BEGIN SET @DiffLastLSN=@FullLastLSN END -END +END IF @BackupPathLog IS NOT NULL @@ -33514,7 +33656,7 @@ BEGIN DELETE FROM @PathItem; DECLARE @CurrentBackupPathLog NVARCHAR(512); - -- Split CSV string logic has taken from Ola Hallengren's :) + -- Split CSV string logic has taken from Ola Hallengren's :) WITH BackupPaths ( StartPosition, EndPosition, PathItem ) @@ -33540,7 +33682,7 @@ BEGIN IF @@rowcount = 0 BREAK; IF @SimpleFolderEnumeration = 1 - BEGIN -- Get list of files + BEGIN -- Get list of files INSERT INTO @FileListSimple (BackupFile, depth, [file]) EXEC master.sys.xp_dirtree @BackupPathLog, 1, 1; INSERT @FileList (BackupPath, BackupFile) SELECT @CurrentBackupPathLog, BackupFile FROM @FileListSimple; DELETE FROM @FileListSimple; @@ -33552,12 +33694,12 @@ BEGIN BEGIN IF @cmd IS NULL PRINT '@cmd is NULL for @CurrentBackupPathLog'; PRINT @cmd; - END; + END; INSERT INTO @FileList (BackupFile) EXEC master.sys.xp_cmdshell @cmd; UPDATE @FileList SET BackupPath = @CurrentBackupPathLog WHERE BackupPath IS NULL; END; - + IF @SimpleFolderEnumeration = 1 BEGIN /*Check what we can*/ @@ -33571,8 +33713,8 @@ BEGIN BEGIN /*Full Sanity check folders*/ IF ( - SELECT COUNT(*) - FROM @FileList AS fl + SELECT COUNT(*) + FROM @FileList AS fl WHERE fl.BackupFile = 'The system cannot find the path specified.' OR fl.BackupFile = 'File Not Found' ) = 1 @@ -33582,8 +33724,8 @@ BEGIN END; IF ( - SELECT COUNT(*) - FROM @FileList AS fl + SELECT COUNT(*) + FROM @FileList AS fl WHERE fl.BackupFile = 'Access is denied.' ) = 1 BEGIN @@ -33592,13 +33734,13 @@ BEGIN END; IF ( - SELECT COUNT(*) - FROM @FileList AS fl + SELECT COUNT(*) + FROM @FileList AS fl ) = 1 - AND + AND ( - SELECT COUNT(*) - FROM @FileList AS fl + SELECT COUNT(*) + FROM @FileList AS fl WHERE fl.BackupFile IS NULL ) = 1 BEGIN @@ -33607,8 +33749,8 @@ BEGIN END IF ( - SELECT COUNT(*) - FROM @FileList AS fl + SELECT COUNT(*) + FROM @FileList AS fl WHERE fl.BackupFile = 'The user name or password is incorrect.' ) = 1 BEGIN @@ -33616,11 +33758,11 @@ BEGIN RETURN; END; END; - END + END /*End folder sanity check*/ IF @Debug = 1 -BEGIN +BEGIN SELECT * FROM @FileList WHERE BackupFile IS NOT NULL; END @@ -33634,12 +33776,12 @@ BEGIN WHERE physical_device_name like @BackupPathLog + '%' AND rh.destination_database_name = @UnquotedRestoreDatabaseName ORDER BY physical_device_name DESC - + IF @Debug = 1 BEGIN SELECT 'Keeping LOG backups with name > : ' + @LogLastNameInMsdbAS END - + DELETE fl FROM @FileList AS fl WHERE fl.BackupPath + fl.BackupFile <= @LogLastNameInMsdbAS @@ -33649,20 +33791,20 @@ END IF (@OnlyLogsAfter IS NOT NULL) BEGIN - + IF @Execute = 'Y' OR @Debug = 1 RAISERROR('@OnlyLogsAfter is NOT NULL, deleting from @FileList', 0, 1) WITH NOWAIT; - + DELETE fl FROM @FileList AS fl WHERE BackupFile LIKE N'%.trn' - AND BackupFile LIKE N'%' + @Database + N'%' + AND BackupFile LIKE N'%' + @Database + N'%' AND REPLACE( RIGHT( REPLACE( fl.BackupFile, RIGHT( fl.BackupFile, PATINDEX( '%_[0-9][0-9]%', REVERSE( fl.BackupFile ) ) ), '' ), 16 ), '_', '' ) < @OnlyLogsAfter; - + END -- Check for log backups IF(@BackupDateTime IS NOT NULL AND @BackupDateTime <> '') - BEGIN + BEGIN DELETE FROM @FileList WHERE BackupFile LIKE N'%.trn' AND BackupFile LIKE N'%' + @Database + N'%' @@ -33687,7 +33829,7 @@ IF (@LogRecoveryOption = N'') IF (@StopAt IS NOT NULL) BEGIN - + IF @Execute = 'Y' OR @Debug = 1 RAISERROR('@StopAt is NOT NULL, deleting from @FileList', 0, 1) WITH NOWAIT; IF LEN(@StopAt) <> 14 OR PATINDEX('%[^0-9]%', @StopAt) > 0 @@ -33707,18 +33849,18 @@ BEGIN IF @BackupDateTime = @StopAt BEGIN - IF @Debug = 1 + IF @Debug = 1 BEGIN RAISERROR('@StopAt is the end time of a FULL backup, no log files will be restored.', 0, 1) WITH NOWAIT; END END ELSE - BEGIN + BEGIN DECLARE @ExtraLogFile NVARCHAR(255) SELECT TOP 1 @ExtraLogFile = fl.BackupFile FROM @FileList AS fl WHERE BackupFile LIKE N'%.trn' - AND BackupFile LIKE N'%' + @Database + N'%' + AND BackupFile LIKE N'%' + @Database + N'%' AND REPLACE( RIGHT( REPLACE( fl.BackupFile, RIGHT( fl.BackupFile, PATINDEX( '%_[0-9][0-9]%', REVERSE( fl.BackupFile ) ) ), '' ), 16 ), '_', '' ) > @StopAt ORDER BY BackupFile; END @@ -33736,11 +33878,11 @@ BEGIN -- If this is a split backup, @ExtraLogFile contains only the first split backup file, either _1.trn or _01.trn -- Change @ExtraLogFile to the max split backup file, then delete all log files greater than this SET @ExtraLogFile = REPLACE(REPLACE(@ExtraLogFile, '_1.trn', '_9.trn'), '_01.trn', '_64.trn') - + DELETE fl FROM @FileList AS fl WHERE BackupFile LIKE N'%.trn' - AND BackupFile LIKE N'%' + @Database + N'%' + AND BackupFile LIKE N'%' + @Database + N'%' AND fl.BackupFile > @ExtraLogFile END END @@ -33751,38 +33893,38 @@ SELECT BackupPath,BackupFile,DENSE_RANK() OVER (ORDER BY REPLACE( RIGHT( REPLACE FROM @FileList WHERE BackupFile IS NOT NULL; --- Loop through all the files for the database +-- Loop through all the files for the database WHILE 1 = 1 BEGIN - -- Get the TOP record to use in "Restore HeaderOnly/FileListOnly" statement + -- Get the TOP record to use in "Restore HeaderOnly/FileListOnly" statement SELECT TOP 1 @CurrentBackupPathLog = BackupPath, @BackupFile = BackupFile FROM #SplitLogBackups WHERE DenseRank = @LogRestoreRanking; IF @@rowcount = 0 BREAK; IF @i = 1 - + BEGIN SET @sql = REPLACE(@HeadersSQL, N'{Path}', @CurrentBackupPathLog + @BackupFile); - + IF @Debug = 1 BEGIN IF @sql IS NULL PRINT '@sql is NULL for REPLACE: @HeadersSQL, @CurrentBackupPathLog, @BackupFile'; PRINT @sql; - END; - + END; + EXECUTE (@sql); - - SELECT TOP 1 @LogFirstLSN = CAST(FirstLSN AS NUMERIC(25, 0)), - @LogLastLSN = CAST(LastLSN AS NUMERIC(25, 0)) - FROM #Headers + + SELECT TOP 1 @LogFirstLSN = CAST(FirstLSN AS NUMERIC(25, 0)), + @LogLastLSN = CAST(LastLSN AS NUMERIC(25, 0)) + FROM #Headers WHERE BackupType = 2; - + IF (@ContinueLogs = 0 AND @LogFirstLSN <= @FullLastLSN AND @FullLastLSN <= @LogLastLSN AND @RestoreDiff = 0) OR (@ContinueLogs = 1 AND @LogFirstLSN <= @DatabaseLastLSN AND @DatabaseLastLSN < @LogLastLSN AND @RestoreDiff = 0) SET @i = 2; - + IF (@ContinueLogs = 0 AND @LogFirstLSN <= @DiffLastLSN AND @DiffLastLSN <= @LogLastLSN AND @RestoreDiff = 1) OR (@ContinueLogs = 1 AND @LogFirstLSN <= @DatabaseLastLSN AND @DatabaseLastLSN < @LogLastLSN AND @RestoreDiff = 1) SET @i = 2; - + DELETE FROM #Headers WHERE BackupType = 2; @@ -33803,7 +33945,7 @@ WHERE BackupFile IS NOT NULL; SET @sql = N'RESTORE LOG ' + @RestoreDatabaseName + N' FROM ' + STUFF( (SELECT CHAR( 10 ) + ',DISK=''' + BackupPath + BackupFile + '''' - FROM #SplitLogBackups + FROM #SplitLogBackups WHERE DenseRank = @LogRestoreRanking ORDER BY BackupFile FOR XML PATH ('')), @@ -33813,17 +33955,17 @@ WHERE BackupFile IS NOT NULL; END; ELSE SET @sql = N'RESTORE LOG ' + @RestoreDatabaseName + N' FROM DISK = ''' + @CurrentBackupPathLog + @BackupFile + N''' WITH ' + @LogRecoveryOption + NCHAR(13) + NCHAR(10); - + IF @Debug = 1 OR @Execute = 'N' BEGIN IF @sql IS NULL PRINT '@sql is NULL for RESTORE LOG: @RestoreDatabaseName, @CurrentBackupPathLog, @BackupFile'; PRINT @sql; - END; - + END; + IF @Debug IN (0, 1) AND @Execute = 'Y' EXECUTE @sql = [dbo].[CommandExecute] @DatabaseContext=N'master', @Command = @sql, @CommandType = 'RESTORE LOG', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y'; END; - + SET @LogRestoreRanking += 1; END; @@ -33834,21 +33976,24 @@ WHERE BackupFile IS NOT NULL; END END --- Put database in a useable state +-- Put database in a useable state IF @RunRecovery = 1 BEGIN SET @sql = N'RESTORE DATABASE ' + @RestoreDatabaseName + N' WITH RECOVERY'; - + IF @KeepCdc = 1 SET @sql = @sql + N', KEEP_CDC'; + IF @EnableBroker = 1 + SET @sql = @sql + N', ENABLE_BROKER'; + SET @sql = @sql + NCHAR(13); IF @Debug = 1 OR @Execute = 'N' BEGIN IF @sql IS NULL PRINT '@sql is NULL for RESTORE DATABASE: @RestoreDatabaseName'; PRINT @sql; - END; + END; IF @Debug IN (0, 1) AND @Execute = 'Y' EXECUTE @sql = [dbo].[CommandExecute] @DatabaseContext=N'master', @Command = @sql, @CommandType = 'RECOVER DATABASE', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y'; @@ -33863,23 +34008,23 @@ IF @ForceSimpleRecovery = 1 BEGIN IF @sql IS NULL PRINT '@sql is NULL for SET RECOVERY SIMPLE: @RestoreDatabaseName'; PRINT @sql; - END; + END; IF @Debug IN (0, 1) AND @Execute = 'Y' EXECUTE @sql = [dbo].[CommandExecute] @DatabaseContext=N'master', @Command = @sql, @CommandType = 'SIMPLE LOGGING', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y'; - END; + END; -- Run checkdb against this database IF @RunCheckDB = 1 BEGIN SET @sql = N'DBCC CHECKDB (' + @RestoreDatabaseName + N') WITH NO_INFOMSGS, ALL_ERRORMSGS, DATA_PURITY;'; - + IF @Debug = 1 OR @Execute = 'N' BEGIN IF @sql IS NULL PRINT '@sql is NULL for Run Integrity Check: @RestoreDatabaseName'; PRINT @sql; - END; - + END; + IF @Debug IN (0, 1) AND @Execute = 'Y' EXECUTE @sql = [dbo].[CommandExecute] @DatabaseContext=N'master', @Command = @sql, @CommandType = 'DBCC CHECKDB', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y'; END; @@ -33942,7 +34087,7 @@ IF @DatabaseOwner IS NOT NULL END; -- Link a user entry in the sys.database_principals system catalog view in the restored database to a SQL Server login of the same name -IF @FixOrphanUsers = 1 +IF @FixOrphanUsers = 1 BEGIN SET @sql = N' -- Fixup Orphan Users by setting database user sid to match login sid @@ -33961,7 +34106,7 @@ SELECT ''ALTER USER ['' + d.name + ''] WITH LOGIN = ['' + d.name + '']; '' SELECT @FixOrphansSql = (SELECT SqlToExecute AS [text()] FROM @OrphanUsers FOR XML PATH (''''), TYPE).value(''text()[1]'',''NVARCHAR(MAX)''); -IF @FixOrphansSql IS NULL +IF @FixOrphansSql IS NULL PRINT ''No orphan users require a sid fixup.''; ELSE BEGIN @@ -33977,7 +34122,7 @@ END;' IF @Debug IN (0, 1) AND @Execute = 'Y' EXECUTE [dbo].[CommandExecute] @DatabaseContext = 'master', @Command = @sql, @CommandType = 'UPDATE', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y'; - END; + END; IF @RunStoredProcAfterRestore IS NOT NULL AND LEN(LTRIM(@RunStoredProcAfterRestore)) > 0 BEGIN @@ -33989,7 +34134,7 @@ BEGIN IF @sql IS NULL PRINT '@sql is NULL when building for @RunStoredProcAfterRestore' PRINT @sql END - + IF @RunRecovery = 0 BEGIN PRINT 'Unable to run Run Stored Procedure After Restore as database is not recovered. Run command again with @RunRecovery = 1' @@ -34005,13 +34150,13 @@ END IF @TestRestore = 1 BEGIN SET @sql = N'DROP DATABASE ' + @RestoreDatabaseName + NCHAR(13); - + IF @Debug = 1 OR @Execute = 'N' BEGIN IF @sql IS NULL PRINT '@sql is NULL for DROP DATABASE: @RestoreDatabaseName'; PRINT @sql; - END; - + END; + IF @Debug IN (0, 1) AND @Execute = 'Y' EXECUTE @sql = [dbo].[CommandExecute] @DatabaseContext=N'master',@Command = @sql, @CommandType = 'DROP DATABASE', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y'; @@ -34060,7 +34205,7 @@ BEGIN SET NOCOUNT ON; SET STATISTICS XML OFF; - SELECT @Version = '8.20', @VersionDate = '20240522'; + SELECT @Version = '8.21', @VersionDate = '20240701'; IF(@VersionCheckMode = 1) BEGIN @@ -34438,6 +34583,7 @@ VALUES (16, 4003, 'CU1', 'https://support.microsoft.com/en-us/help/5022375', '2023-02-16', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 1'), (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'), + (15, 4375, 'CU27', 'https://support.microsoft.com/kb/5037331', '2024-06-14', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 27'), (15, 4365, 'CU26', 'https://support.microsoft.com/kb/5035123', '2024-04-11', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 26'), (15, 4355, 'CU25', 'https://support.microsoft.com/kb/5033688', '2024-02-15', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 25'), (15, 4345, 'CU24', 'https://support.microsoft.com/kb/5031908', '2023-12-14', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 24'), @@ -34867,7 +35013,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.20', @VersionDate = '20240522'; +SELECT @Version = '8.21', @VersionDate = '20240701'; IF(@VersionCheckMode = 1) BEGIN diff --git a/Install-Azure.sql b/Install-Azure.sql index acc04d6b8..41de92e6d 100644 --- a/Install-Azure.sql +++ b/Install-Azure.sql @@ -37,7 +37,7 @@ AS SET NOCOUNT ON; SET STATISTICS XML OFF; -SELECT @Version = '8.20', @VersionDate = '20240522'; +SELECT @Version = '8.21', @VersionDate = '20240701'; IF(@VersionCheckMode = 1) BEGIN @@ -1172,7 +1172,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.20', @VersionDate = '20240522'; +SELECT @Version = '8.21', @VersionDate = '20240701'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -8545,7 +8545,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.20', @VersionDate = '20240522'; +SELECT @Version = '8.21', @VersionDate = '20240701'; IF(@VersionCheckMode = 1) BEGIN @@ -13554,7 +13554,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.20', @VersionDate = '20240522'; +SELECT @Version = '8.21', @VersionDate = '20240701'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -20029,6 +20029,7 @@ ALTER PROCEDURE @EventSessionName sysname = N'system_health', @TargetSessionType sysname = NULL, @VictimsOnly bit = 0, + @DeadlockType nvarchar(20) = NULL, @Debug bit = 0, @Help bit = 0, @Version varchar(30) = NULL OUTPUT, @@ -20047,7 +20048,7 @@ BEGIN SET XACT_ABORT OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.20', @VersionDate = '20240522'; + SELECT @Version = '8.21', @VersionDate = '20240701'; IF @VersionCheckMode = 1 BEGIN @@ -20209,7 +20210,7 @@ BEGIN @StartDateOriginal datetime = @StartDate, @EndDateOriginal datetime = @EndDate, @StartDateUTC datetime, - @EndDateUTC datetime; + @EndDateUTC datetime;; /*Temporary objects used in the procedure*/ DECLARE @@ -20719,50 +20720,63 @@ BEGIN END CATCH; END; + IF @DeadlockType IS NOT NULL + BEGIN + SELECT + @DeadlockType = + CASE + WHEN LOWER(@DeadlockType) LIKE 'regular%' + THEN N'Regular Deadlock' + WHEN LOWER(@DeadlockType) LIKE N'parallel%' + THEN N'Parallel Deadlock' + ELSE NULL + END; + END; + /*If @TargetSessionType, we need to figure out if it's ring buffer or event file*/ /*Azure has differently named views, so we need to separate. Thanks, Azure.*/ - IF - ( - @Azure = 0 - AND @TargetSessionType IS NULL - ) - BEGIN - RAISERROR('@TargetSessionType is NULL, assigning for non-Azure instance', 0, 1) WITH NOWAIT; + IF + ( + @Azure = 0 + AND @TargetSessionType IS NULL + ) + BEGIN + RAISERROR('@TargetSessionType is NULL, assigning for non-Azure instance', 0, 1) WITH NOWAIT; - SELECT TOP (1) - @TargetSessionType = t.target_name - FROM sys.dm_xe_sessions AS s - JOIN sys.dm_xe_session_targets AS t - ON s.address = t.event_session_address - WHERE s.name = @EventSessionName - AND t.target_name IN (N'event_file', N'ring_buffer') - ORDER BY t.target_name - OPTION(RECOMPILE); + SELECT TOP (1) + @TargetSessionType = t.target_name + FROM sys.dm_xe_sessions AS s + JOIN sys.dm_xe_session_targets AS t + ON s.address = t.event_session_address + WHERE s.name = @EventSessionName + AND t.target_name IN (N'event_file', N'ring_buffer') + ORDER BY t.target_name + OPTION(RECOMPILE); - RAISERROR('@TargetSessionType assigned as %s for non-Azure', 0, 1, @TargetSessionType) WITH NOWAIT; - END; + RAISERROR('@TargetSessionType assigned as %s for non-Azure', 0, 1, @TargetSessionType) WITH NOWAIT; + END; - IF - ( - @Azure = 1 - AND @TargetSessionType IS NULL - ) - BEGIN - RAISERROR('@TargetSessionType is NULL, assigning for Azure instance', 0, 1) WITH NOWAIT; + IF + ( + @Azure = 1 + AND @TargetSessionType IS NULL + ) + BEGIN + RAISERROR('@TargetSessionType is NULL, assigning for Azure instance', 0, 1) WITH NOWAIT; - SELECT TOP (1) - @TargetSessionType = t.target_name - FROM sys.dm_xe_database_sessions AS s - JOIN sys.dm_xe_database_session_targets AS t - ON s.address = t.event_session_address - WHERE s.name = @EventSessionName - AND t.target_name IN (N'event_file', N'ring_buffer') - ORDER BY t.target_name - OPTION(RECOMPILE); + SELECT TOP (1) + @TargetSessionType = t.target_name + FROM sys.dm_xe_database_sessions AS s + JOIN sys.dm_xe_database_session_targets AS t + ON s.address = t.event_session_address + WHERE s.name = @EventSessionName + AND t.target_name IN (N'event_file', N'ring_buffer') + ORDER BY t.target_name + OPTION(RECOMPILE); - RAISERROR('@TargetSessionType assigned as %s for Azure', 0, 1, @TargetSessionType) WITH NOWAIT; - END; + RAISERROR('@TargetSessionType assigned as %s for Azure', 0, 1, @TargetSessionType) WITH NOWAIT; + END; /*The system health stuff gets handled different from user extended events.*/ @@ -23460,6 +23474,7 @@ BEGIN AND (d.client_app = @AppName OR @AppName IS NULL) AND (d.host_name = @HostName OR @HostName IS NULL) AND (d.login_name = @LoginName OR @LoginName IS NULL) + AND (d.deadlock_type = @DeadlockType OR @DeadlockType IS NULL) OPTION (RECOMPILE, LOOP JOIN, HASH JOIN); UPDATE d @@ -23847,7 +23862,11 @@ BEGIN deqs.max_reserved_threads, deqs.min_used_threads, deqs.max_used_threads, - deqs.total_rows + deqs.total_rows, + max_worker_time_ms = + deqs.max_worker_time / 1000., + max_elapsed_time_ms = + deqs.max_elapsed_time / 1000. INTO #dm_exec_query_stats FROM sys.dm_exec_query_stats AS deqs WHERE EXISTS @@ -23879,8 +23898,10 @@ BEGIN ap.executions_per_second, ap.total_worker_time_ms, ap.avg_worker_time_ms, + ap.max_worker_time_ms, ap.total_elapsed_time_ms, ap.avg_elapsed_time_ms, + ap.max_elapsed_time_ms, ap.total_logical_reads_mb, ap.total_physical_reads_mb, ap.total_logical_writes_mb, @@ -23923,7 +23944,9 @@ BEGIN c.min_used_threads, c.max_used_threads, c.total_rows, - c.query_plan + c.query_plan, + c.max_worker_time_ms, + c.max_elapsed_time_ms FROM #available_plans AS ap OUTER APPLY ( @@ -24074,6 +24097,8 @@ BEGIN @TargetSessionType, VictimsOnly = @VictimsOnly, + DeadlockType = + @DeadlockType, Debug = @Debug, Help = @@ -24178,7 +24203,7 @@ BEGIN SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.20', @VersionDate = '20240522'; + SELECT @Version = '8.21', @VersionDate = '20240701'; IF(@VersionCheckMode = 1) BEGIN diff --git a/SqlServerVersions.sql b/SqlServerVersions.sql index 1acba8376..e8004f31a 100644 --- a/SqlServerVersions.sql +++ b/SqlServerVersions.sql @@ -57,6 +57,7 @@ VALUES (16, 4003, 'CU1', 'https://support.microsoft.com/en-us/help/5022375', '2023-02-16', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 1'), (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'), + (15, 4375, 'CU27', 'https://support.microsoft.com/kb/5037331', '2024-06-14', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 27'), (15, 4365, 'CU26', 'https://support.microsoft.com/kb/5035123', '2024-04-11', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 26'), (15, 4355, 'CU25', 'https://support.microsoft.com/kb/5033688', '2024-02-15', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 25'), (15, 4345, 'CU24', 'https://support.microsoft.com/kb/5031908', '2023-12-14', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 24'), diff --git a/sp_Blitz.sql b/sp_Blitz.sql index f9e0f49d4..e101ac4eb 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -38,7 +38,7 @@ AS SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.20', @VersionDate = '20240522'; + SELECT @Version = '8.21', @VersionDate = '20240701'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) diff --git a/sp_BlitzAnalysis.sql b/sp_BlitzAnalysis.sql index 50612bf8b..0976fcb1e 100644 --- a/sp_BlitzAnalysis.sql +++ b/sp_BlitzAnalysis.sql @@ -37,7 +37,7 @@ AS SET NOCOUNT ON; SET STATISTICS XML OFF; -SELECT @Version = '8.20', @VersionDate = '20240522'; +SELECT @Version = '8.21', @VersionDate = '20240701'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_BlitzBackups.sql b/sp_BlitzBackups.sql index 6f910d56e..1b54148a1 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.20', @VersionDate = '20240522'; + SELECT @Version = '8.21', @VersionDate = '20240701'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_BlitzCache.sql b/sp_BlitzCache.sql index db2f3c188..5f5d9f8be 100644 --- a/sp_BlitzCache.sql +++ b/sp_BlitzCache.sql @@ -281,7 +281,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.20', @VersionDate = '20240522'; +SELECT @Version = '8.21', @VersionDate = '20240701'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) diff --git a/sp_BlitzFirst.sql b/sp_BlitzFirst.sql index caa4add1c..94539e13a 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.20', @VersionDate = '20240522'; +SELECT @Version = '8.21', @VersionDate = '20240701'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index 16b130ff6..67ee35096 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -48,7 +48,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.20', @VersionDate = '20240522'; +SELECT @Version = '8.21', @VersionDate = '20240701'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index d2bae97a8..5038c62b0 100644 --- a/sp_BlitzLock.sql +++ b/sp_BlitzLock.sql @@ -37,7 +37,7 @@ BEGIN SET XACT_ABORT OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.20', @VersionDate = '20240522'; + SELECT @Version = '8.21', @VersionDate = '20240701'; IF @VersionCheckMode = 1 BEGIN diff --git a/sp_BlitzWho.sql b/sp_BlitzWho.sql index 70a680f5f..01e2fdd60 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.20', @VersionDate = '20240522'; + SELECT @Version = '8.21', @VersionDate = '20240701'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_DatabaseRestore.sql b/sp_DatabaseRestore.sql index 6463d410b..cbf5f491e 100755 --- a/sp_DatabaseRestore.sql +++ b/sp_DatabaseRestore.sql @@ -58,7 +58,7 @@ SET STATISTICS XML OFF; /*Versioning details*/ -SELECT @Version = '8.20', @VersionDate = '20240522'; +SELECT @Version = '8.21', @VersionDate = '20240701'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_ineachdb.sql b/sp_ineachdb.sql index 83d35848f..ded778d56 100644 --- a/sp_ineachdb.sql +++ b/sp_ineachdb.sql @@ -36,7 +36,7 @@ BEGIN SET NOCOUNT ON; SET STATISTICS XML OFF; - SELECT @Version = '8.20', @VersionDate = '20240522'; + SELECT @Version = '8.21', @VersionDate = '20240701'; IF(@VersionCheckMode = 1) BEGIN From d2800dd91704182c4f5c06d557b8f625f26edef6 Mon Sep 17 00:00:00 2001 From: Klaas Date: Tue, 2 Jul 2024 15:03:29 +0200 Subject: [PATCH 581/662] Update sp_Blitz.sql validate logins if we are sysadmin sp_validatelogins is only executed if we are not sysadmin added execution if we are sysadmin --- sp_Blitz.sql | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index e101ac4eb..2eaa9b1f7 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -1836,9 +1836,18 @@ AS IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 2301) WITH NOWAIT; /* - #InvalidLogins is filled at the start during the permissions check + #InvalidLogins is filled at the start during the permissions check IF we are not sysadmin + filling it now if we are sysadmin */ - + IF @sa = 1 + BEGIN + INSERT INTO #InvalidLogins + ( + [LoginSID] + ,[LoginName] + ) + EXEC sp_validatelogins; + END; INSERT INTO #BlitzResults ( CheckID , Priority , From 8900dcb2931a9b5d1091e5d3110f8185ee7fda93 Mon Sep 17 00:00:00 2001 From: Reece Goding <67124261+ReeceGoding@users.noreply.github.com> Date: Wed, 3 Jul 2024 20:16:13 +0100 Subject: [PATCH 582/662] Added check for unusual Query Store configuration and Query Store Trace Flags --- Documentation/sp_Blitz_Checks_by_Priority.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Documentation/sp_Blitz_Checks_by_Priority.md b/Documentation/sp_Blitz_Checks_by_Priority.md index 9609705b6..0458b8c7e 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: 264. -If you want to add a new one, start at 265. +CURRENT HIGH CHECKID: 265. +If you want to add a new one, start at 266. | Priority | FindingsGroup | Finding | URL | CheckID | |----------|-----------------------------|---------------------------------------------------------|------------------------------------------------------------------------|----------| @@ -153,7 +153,7 @@ If you want to add a new one, start at 265. | 200 | Informational | Tables in the Master Database | https://www.BrentOzar.com/go/mastuser | 27 | | 200 | Informational | Tables in the Model Database | https://www.BrentOzar.com/go/model | 29 | | 200 | Informational | Tables in the MSDB Database | https://www.BrentOzar.com/go/msdbuser | 28 | -| 200 | Informational | TraceFlag On | https://www.BrentOzar.com/go/traceflags/ | 74 | +| 200 | Informational | TraceFlag On / Recommended Trace Flag Off | https://www.BrentOzar.com/go/traceflags/ | 74 | | 200 | Licensing | Enterprise Edition Features In Use | https://www.BrentOzar.com/go/ee | 33 | | 200 | Licensing | Non-Production License | https://www.BrentOzar.com/go/licensing | 173 | | 200 | Monitoring | Agent Jobs Without Failure Emails | https://www.BrentOzar.com/go/alerts | 94 | @@ -250,6 +250,7 @@ If you want to add a new one, start at 265. | 200 | Performance | Query Store Wait Stats Disabled | https://www.sqlskills.com/blogs/erin/query-store-settings/ | 262 | | 200 | Performance | Query Store Effectively Disabled | https://learn.microsoft.com/en-us/sql/relational-databases/performance/best-practice-with-the-query-store#Verify | 263 | | 200 | Performance | Undesired Query Store State | https://learn.microsoft.com/en-us/sql/relational-databases/performance/best-practice-with-the-query-store#Verify | 264 | +| 200 | Performance | Query Store Unusually Configured | https://www.sqlskills.com/blogs/erin/query-store-best-practices/ | 265 | | 200 | Performance | Snapshot Backups Occurring | https://www.BrentOzar.com/go/snaps | 178 | | 200 | Performance | User-Created Statistics In Place | https://www.BrentOzar.com/go/userstats | 122 | | 200 | Performance | SSAS/SSIS/SSRS Installed | https://www.BrentOzar.com/go/services | 224 | From 1af37884228aaf9372505b1a07564da4b2a3803a Mon Sep 17 00:00:00 2001 From: Reece Goding <67124261+ReeceGoding@users.noreply.github.com> Date: Wed, 3 Jul 2024 20:22:25 +0100 Subject: [PATCH 583/662] Added checks for unusual Query Store configuration and Query Store trace flags --- sp_Blitz.sql | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 50 insertions(+), 2 deletions(-) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index 2eaa9b1f7..231121536 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -8490,11 +8490,11 @@ IF @ProductVersionMajor >= 10 WHEN [T].[TraceFlag] = '3226' THEN '3226 enabled globally, which keeps the event log clean by not reporting successful backups.' WHEN [T].[TraceFlag] = '3505' THEN '3505 enabled globally, which disables Checkpoints. This is usually a very bad idea.' WHEN [T].[TraceFlag] = '4199' THEN '4199 enabled globally, which enables non-default Query Optimizer fixes, changing query plans from the default behaviors.' - WHEN [T].[TraceFlag] = '7745' AND @ProductVersionMajor > 12 AND @QueryStoreInUse = 1 THEN '7745 enabled globally, which makes shutdowns/failovers quicker by not waiting for Query Store to flush to disk. This good idea loses you the non-flushed Query Store data.' + WHEN [T].[TraceFlag] = '7745' AND @QueryStoreInUse = 1 THEN '7745 enabled globally, which makes shutdowns/failovers quicker by not waiting for Query Store to flush to disk. This good idea loses you the non-flushed Query Store data.' WHEN [T].[TraceFlag] = '7745' AND @ProductVersionMajor > 12 THEN '7745 enabled globally, which is for Query Store. None of your databases have Query Store enabled, so why do you have this turned on?' WHEN [T].[TraceFlag] = '7745' AND @ProductVersionMajor <= 12 THEN '7745 enabled globally, which is for Query Store. Query Store does not exist on your SQL Server version, so why do you have this turned on?' WHEN [T].[TraceFlag] = '7752' AND @ProductVersionMajor > 14 THEN '7752 enabled globally, which is for Query Store. However, it has no effect in your SQL Server version. Consider turning it off.' - WHEN [T].[TraceFlag] = '7752' AND @ProductVersionMajor > 12 AND @QueryStoreInUse = 1 THEN '7752 enabled globally, which stops queries needing to wait on Query Store loading up after database recovery.' + WHEN [T].[TraceFlag] = '7752' AND @QueryStoreInUse = 1 THEN '7752 enabled globally, which stops queries needing to wait on Query Store loading up after database recovery.' WHEN [T].[TraceFlag] = '7752' AND @ProductVersionMajor > 12 THEN '7752 enabled globally, which is for Query Store. None of your databases have Query Store enabled, so why do you have this turned on?' WHEN [T].[TraceFlag] = '7752' AND @ProductVersionMajor <= 12 THEN '7752 enabled globally, which is for Query Store. Query Store does not exist on your SQL Server version, so why do you have this turned on?' WHEN [T].[TraceFlag] = '8048' THEN '8048 enabled globally, which tries to reduce CMEMTHREAD waits on servers with a lot of logical processors.' @@ -8504,6 +8504,54 @@ IF @ProductVersionMajor >= 10 ELSE [T].[TraceFlag] + ' is enabled globally.' END AS Details FROM #TraceStatus T; + + + IF NOT EXISTS ( SELECT 1 + FROM #TraceStatus T + WHERE [T].[TraceFlag] = '7745' ) + AND @QueryStoreInUse = 1 + + BEGIN + INSERT INTO #BlitzResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT 74 AS CheckID , + 200 AS Priority , + 'Informational' AS FindingsGroup , + 'Recommended Trace Flag Off' AS Finding , + 'https://www.sqlskills.com/blogs/erin/query-store-trace-flags/' AS URL , + 'Trace Flag 7745 not enabled globally. It makes shutdowns/failovers quicker by not waiting for Query Store to flush to disk. It is recommended, but it loses you the non-flushed Query Store data.' AS Details + FROM #TraceStatus T + END; + + IF NOT EXISTS ( SELECT 1 + FROM #TraceStatus T + WHERE [T].[TraceFlag] = '7752' ) + AND @ProductVersionMajor < 15 + AND @QueryStoreInUse = 1 + + BEGIN + INSERT INTO #BlitzResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT 74 AS CheckID , + 200 AS Priority , + 'Informational' AS FindingsGroup , + 'Recommended Trace Flag Off' AS Finding , + 'https://www.sqlskills.com/blogs/erin/query-store-trace-flags/' AS URL , + 'Trace Flag 7752 not enabled globally. It stops queries needing to wait on Query Store loading up after database recovery. It is so recommended that it is enabled by default as of SQL Server 2019.' AS Details + FROM #TraceStatus T + END; END; /* High CMEMTHREAD waits that could need trace flag 8048. From 29f11bcda5283c80568b016dd1ede7ed74b40250 Mon Sep 17 00:00:00 2001 From: Reece Goding <67124261+ReeceGoding@users.noreply.github.com> Date: Wed, 3 Jul 2024 20:27:49 +0100 Subject: [PATCH 584/662] Added check for 'Query Store Unusually Configured' --- sp_Blitz.sql | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index 231121536..6250a53ba 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -6861,6 +6861,41 @@ IF @ProductVersionMajor >= 10 AND desired_state <> actual_state OPTION (RECOMPILE)'; END; + + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 265 ) + AND EXISTS(SELECT * FROM sys.all_objects WHERE name = 'database_query_store_options') + BEGIN + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 265) WITH NOWAIT; + + EXEC dbo.sp_MSforeachdb 'USE [?]; + SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + INSERT INTO #BlitzResults + (CheckID, + DatabaseName, + Priority, + FindingsGroup, + Finding, + URL, + Details) + SELECT TOP 1 265, + N''?'', + 200, + ''Performance'', + ''Query Store Unusually Configured'', + ''https://www.sqlskills.com/blogs/erin/query-store-best-practices/'', + (''The '' + query_capture_mode_desc + '' query capture mode '' + + CASE query_capture_mode_desc + WHEN ''ALL'' THEN ''captures more data than you will probably use. If your workload is heavily ad-hoc, then it can also cause Query Store to capture so much that it turns itself off.'' + WHEN ''NONE'' THEN ''stops Query Store capturing data for new queries.'' + WHEN ''CUSTOM'' THEN ''suggests that somebody has gone out of their way to only capture exactly what they want.'' + ELSE ''is not documented.'' END) + FROM [?].sys.database_query_store_options + WHERE desired_state <> 0 /* No point in checking this if Query Store is off. */ + AND query_capture_mode_desc <> ''AUTO'' + OPTION (RECOMPILE)'; + END; IF @ProductVersionMajor = 13 AND @ProductVersionMinor < 2149 --2016 CU1 has the fix in it AND NOT EXISTS ( SELECT 1 From 1976f54eae5d9741eaba767d248370fa25f7e993 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Fri, 16 Aug 2024 02:39:39 -0700 Subject: [PATCH 585/662] #3556 sp_BlitzIndex missing index processing Add filter on database_id to speed processing and reduce chance of throwing errors. Closes #3556. --- sp_BlitzIndex.sql | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index 67ee35096..de5cf8b8f 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -1879,6 +1879,7 @@ WITH ON ty.user_type_id = co.user_type_id WHERE id_inner.index_handle = id.index_handle AND id_inner.object_id = id.object_id + AND id_inner.database_id = DB_ID(''' + QUOTENAME(@DatabaseName) + N''') AND cn_inner.IndexColumnType = cn.IndexColumnType FOR XML PATH('''') ), @@ -1916,6 +1917,7 @@ WITH ) x (n) CROSS APPLY n.nodes(''x'') node(v) )AS cn + WHERE id.database_id = DB_ID(''' + QUOTENAME(@DatabaseName) + N''') GROUP BY id.index_handle, id.object_id, From e68196a9e8391774d59d302138bc6c1d040edf8f Mon Sep 17 00:00:00 2001 From: Robert Hague Date: Mon, 26 Aug 2024 12:44:26 +0200 Subject: [PATCH 586/662] sp_DatabaseRestore: fix backup file selection for @StopAt and non-split full backups --- sp_DatabaseRestore.sql | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/sp_DatabaseRestore.sql b/sp_DatabaseRestore.sql index cbf5f491e..3d987dfa3 100755 --- a/sp_DatabaseRestore.sql +++ b/sp_DatabaseRestore.sql @@ -690,13 +690,15 @@ BEGIN END; -- Find latest full backup - SELECT @LastFullBackup = MAX(BackupFile) + -- Get the TOP record to use in "Restore HeaderOnly/FileListOnly" statement as well as Non-Split Backups Restore Command + SELECT TOP 1 @LastFullBackup = BackupFile, @CurrentBackupPathFull = BackupPath FROM @FileList WHERE BackupFile LIKE N'%.bak' AND BackupFile LIKE N'%' + @Database + N'%' AND - (@StopAt IS NULL OR REPLACE( RIGHT( REPLACE( @LastFullBackup, RIGHT( @LastFullBackup, PATINDEX( '%_[0-9][0-9]%', REVERSE( @LastFullBackup ) ) ), '' ), 16 ), '_', '' ) <= @StopAt); + (@StopAt IS NULL OR REPLACE( RIGHT( REPLACE( BackupFile, RIGHT( BackupFile, PATINDEX( '%_[0-9][0-9]%', REVERSE( BackupFile ) ) ), '' ), 16 ), '_', '' ) <= @StopAt) + ORDER BY BackupFile DESC; /* To get all backups that belong to the same set we can do two things: 1. RESTORE HEADERONLY of ALL backup files in the folder and look for BackupSetGUID. @@ -736,11 +738,6 @@ BEGIN SET @FileListParamSQL += N')' + NCHAR(13) + NCHAR(10); SET @FileListParamSQL += N'EXEC (''RESTORE FILELISTONLY FROM DISK=''''{Path}'''''')'; - -- get the TOP record to use in "Restore HeaderOnly/FileListOnly" statement as well as Non-Split Backups Restore Command - SELECT TOP 1 @CurrentBackupPathFull = BackupPath, @LastFullBackup = BackupFile - FROM @FileList - ORDER BY REPLACE( RIGHT( REPLACE( BackupFile, RIGHT( BackupFile, PATINDEX( '%_[0-9][0-9]%', REVERSE( BackupFile ) ) ), '' ), 16 ), '_', '' ) DESC; - SET @sql = REPLACE(@FileListParamSQL, N'{Path}', @CurrentBackupPathFull + @LastFullBackup); IF @Debug = 1 From b0ac500be4b0c32d34459d5f65798110060f40e5 Mon Sep 17 00:00:00 2001 From: Reece Goding <67124261+ReeceGoding@users.noreply.github.com> Date: Sun, 1 Sep 2024 11:12:48 +0100 Subject: [PATCH 587/662] sp_Blitz.sql: Updated #IgnorableWaits to match the 2021 version of Paul Randal's script --- sp_Blitz.sql | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index 6250a53ba..f072015bb 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -1108,6 +1108,7 @@ AS INSERT INTO #IgnorableWaits VALUES ('CLR_AUTO_EVENT'); INSERT INTO #IgnorableWaits VALUES ('CLR_MANUAL_EVENT'); INSERT INTO #IgnorableWaits VALUES ('CLR_SEMAPHORE'); + INSERT INTO #IgnorableWaits VALUES ('CXCONSUMER'); INSERT INTO #IgnorableWaits VALUES ('DBMIRROR_DBM_EVENT'); INSERT INTO #IgnorableWaits VALUES ('DBMIRROR_DBM_MUTEX'); INSERT INTO #IgnorableWaits VALUES ('DBMIRROR_EVENTS_QUEUE'); @@ -1134,7 +1135,10 @@ AS INSERT INTO #IgnorableWaits VALUES ('PARALLEL_REDO_WORKER_WAIT_WORK'); INSERT INTO #IgnorableWaits VALUES ('POPULATE_LOCK_ORDINALS'); INSERT INTO #IgnorableWaits VALUES ('PREEMPTIVE_HADR_LEASE_MECHANISM'); + INSERT INTO #IgnorableWaits VALUES ('PREEMPTIVE_OS_FLUSHFILEBUFFERS'); INSERT INTO #IgnorableWaits VALUES ('PREEMPTIVE_SP_SERVER_DIAGNOSTICS'); + INSERT INTO #IgnorableWaits VALUES ('PVS_PREALLOCATE'); + INSERT INTO #IgnorableWaits VALUES ('PWAIT_EXTENSIBILITY_CLEANUP_TASK'); INSERT INTO #IgnorableWaits VALUES ('QDS_ASYNC_QUEUE'); INSERT INTO #IgnorableWaits VALUES ('QDS_CLEANUP_STALE_QUERIES_TASK_MAIN_LOOP_SLEEP'); INSERT INTO #IgnorableWaits VALUES ('QDS_PERSIST_TASK_MAIN_LOOP_SLEEP'); @@ -1148,6 +1152,7 @@ AS INSERT INTO #IgnorableWaits VALUES ('SQLTRACE_BUFFER_FLUSH'); INSERT INTO #IgnorableWaits VALUES ('SQLTRACE_INCREMENTAL_FLUSH_SLEEP'); INSERT INTO #IgnorableWaits VALUES ('UCS_SESSION_REGISTRATION'); + INSERT INTO #IgnorableWaits VALUES ('VDI_CLIENT_OTHER'); INSERT INTO #IgnorableWaits VALUES ('WAIT_XTP_OFFLINE_CKPT_NEW_LOG'); INSERT INTO #IgnorableWaits VALUES ('WAITFOR'); INSERT INTO #IgnorableWaits VALUES ('XE_DISPATCHER_WAIT'); From 21c2ecce3096287688f7ddae4248cb2e099e2ce7 Mon Sep 17 00:00:00 2001 From: Vlad Drumea <48413726+VladDBA@users.noreply.github.com> Date: Tue, 10 Sep 2024 21:01:04 +0300 Subject: [PATCH 588/662] For #3567 --- sp_BlitzLock.sql | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index 5038c62b0..ce42356e9 100644 --- a/sp_BlitzLock.sql +++ b/sp_BlitzLock.sql @@ -4049,17 +4049,23 @@ BEGIN FROM @sysAssObjId AS s OPTION(RECOMPILE); + IF OBJECT_ID('tempdb..#available_plans') IS NOT NULL + BEGIN SELECT table_name = N'#available_plans', * FROM #available_plans AS ap OPTION(RECOMPILE); + END; + IF OBJECT_ID('tempdb..#dm_exec_query_stats') IS NOT NULL + BEGIN SELECT table_name = N'#dm_exec_query_stats', * FROM #dm_exec_query_stats OPTION(RECOMPILE); + END; SELECT procedure_parameters = From 37165059298f868d9742383fb39d534752ccd735 Mon Sep 17 00:00:00 2001 From: Reece Goding <67124261+ReeceGoding@users.noreply.github.com> Date: Fri, 13 Sep 2024 13:52:38 +0100 Subject: [PATCH 589/662] Removed CXCONSUMER from #IgnorableWaits --- sp_Blitz.sql | 1 - 1 file changed, 1 deletion(-) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index f072015bb..ad92d53ae 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -1108,7 +1108,6 @@ AS INSERT INTO #IgnorableWaits VALUES ('CLR_AUTO_EVENT'); INSERT INTO #IgnorableWaits VALUES ('CLR_MANUAL_EVENT'); INSERT INTO #IgnorableWaits VALUES ('CLR_SEMAPHORE'); - INSERT INTO #IgnorableWaits VALUES ('CXCONSUMER'); INSERT INTO #IgnorableWaits VALUES ('DBMIRROR_DBM_EVENT'); INSERT INTO #IgnorableWaits VALUES ('DBMIRROR_DBM_MUTEX'); INSERT INTO #IgnorableWaits VALUES ('DBMIRROR_EVENTS_QUEUE'); From a0e6def3b58c6f74995c0ae3e3514b2f9395bf00 Mon Sep 17 00:00:00 2001 From: Vlad Drumea <48413726+VladDBA@users.noreply.github.com> Date: Sat, 14 Sep 2024 17:52:01 +0300 Subject: [PATCH 590/662] Added new and missing build versions of SQL Server --- SqlServerVersions.sql | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/SqlServerVersions.sql b/SqlServerVersions.sql index e8004f31a..a1862dffb 100644 --- a/SqlServerVersions.sql +++ b/SqlServerVersions.sql @@ -41,6 +41,9 @@ DELETE FROM dbo.SqlServerVersions; INSERT INTO dbo.SqlServerVersions (MajorVersionNumber, MinorVersionNumber, Branch, [Url], ReleaseDate, MainstreamSupportEndDate, ExtendedSupportEndDate, MajorVersionName, MinorVersionName) VALUES + /*2022*/ + (16, 4140, 'CU14 GDR', 'https://support.microsoft.com/en-us/help/5042578', '2024-09-10', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 14 GDR'), + (16, 4135, 'CU14', 'https://support.microsoft.com/en-us/help/5038325', '2024-07-23', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 14'), (16, 4125, 'CU13', 'https://support.microsoft.com/en-us/help/5036432', '2024-05-16', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 13'), (16, 4115, 'CU12', 'https://support.microsoft.com/en-us/help/5033663', '2024-03-14', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 12'), (16, 4105, 'CU11', 'https://support.microsoft.com/en-us/help/5032679', '2024-01-11', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 11'), @@ -57,6 +60,9 @@ VALUES (16, 4003, 'CU1', 'https://support.microsoft.com/en-us/help/5022375', '2023-02-16', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 1'), (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, 4390, 'CU28 GDR', 'https://support.microsoft.com/kb/5042749', '2024-09-10', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 28 GDR'), + (15, 4385, 'CU28', 'https://support.microsoft.com/kb/5039747', '2024-08-01', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 28'), (15, 4375, 'CU27', 'https://support.microsoft.com/kb/5037331', '2024-06-14', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 27'), (15, 4365, 'CU26', 'https://support.microsoft.com/kb/5035123', '2024-04-11', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 26'), (15, 4355, 'CU25', 'https://support.microsoft.com/kb/5033688', '2024-02-15', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 25'), @@ -89,7 +95,10 @@ VALUES (15, 4003, 'CU1', 'https://support.microsoft.com/en-us/help/4527376', '2020-01-07', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 1 '), (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 '), - (14, 3465, 'RTM CU31 GDR', 'https://support.microsoft.com/kb/5029376', '2023-10-12', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 31 GDR'), + /*2017*/ + (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'), + (14, 3471, 'RTM CU31 GDR', 'https://support.microsoft.com/kb/5040940', '2024-07-09', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 31 GDR'), + (14, 3465, 'RTM CU31 GDR', 'https://support.microsoft.com/kb/5029376', '2023-10-10', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 31 GDR'), (14, 3460, 'RTM CU31 GDR', 'https://support.microsoft.com/kb/5021126', '2023-02-14', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 31 GDR'), (14, 3456, 'RTM CU31', 'https://support.microsoft.com/en-us/help/5016884', '2022-09-20', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 31'), (14, 3451, 'RTM CU30', 'https://support.microsoft.com/en-us/help/5013756', '2022-07-13', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 30'), @@ -125,8 +134,16 @@ VALUES (14, 3008, 'RTM CU2', 'https://support.microsoft.com/en-us/help/4052574', '2017-11-28', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 2'), (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, 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', '2023-07-09', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3 Azure Feature Pack GDR'), + (13, 7029, 'SP3 Azure Feature Pack GDR', 'https://support.microsoft.com/en-us/help/5029187', '2023-10-10', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3 Azure Feature Pack GDR'), + (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, 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'), + (13, 6441, 'SP3 GDR', 'https://support.microsoft.com/kb/5040946', '2024-07-09', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3 GDR'), + (13, 6435, 'SP3 GDR', 'https://support.microsoft.com/kb/5029186', '2023-10-10', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3 GDR'), (13, 6430, 'SP3 GDR', 'https://support.microsoft.com/kb/5021129', '2023-02-14', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3 GDR'), (13, 6419, 'SP3 GDR', 'https://support.microsoft.com/en-us/help/5014355', '2022-06-14', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3 GDR'), (13, 6404, 'SP3 GDR', 'https://support.microsoft.com/en-us/help/5006943', '2021-10-27', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3 GDR'), @@ -179,6 +196,7 @@ VALUES (13, 2164, 'RTM CU2', 'https://support.microsoft.com/en-us/help/3182270 ', '2016-09-22', '2018-01-09', '2018-01-09', 'SQL Server 2016', 'RTM Cumulative Update 2'), (13, 2149, 'RTM CU1', 'https://support.microsoft.com/en-us/help/3164674 ', '2016-07-25', '2018-01-09', '2018-01-09', 'SQL Server 2016', 'RTM Cumulative Update 1'), (13, 1601, 'RTM ', '', '2016-06-01', '2019-01-09', '2019-01-09', 'SQL Server 2016', 'RTM '), + /*2014*/ (12, 6444, 'SP3 CU4 GDR', 'https://support.microsoft.com/kb/5021045', '2023-02-14', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 4 GDR'), (12, 6439, 'SP3 CU4 GDR', 'https://support.microsoft.com/en-us/help/5014164', '2022-06-14', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 4 GDR'), (12, 6433, 'SP3 CU4 GDR', 'https://support.microsoft.com/en-us/help/4583462', '2021-01-12', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 4 GDR'), @@ -243,6 +261,7 @@ VALUES (12, 2269, 'RTM MS15-058: GDR Security Update ', 'https://support.microsoft.com/en-us/help/3045324', '2015-07-14', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM MS15-058: GDR Security Update '), (12, 2254, 'RTM MS14-044: GDR Security Update', 'https://support.microsoft.com/en-us/help/2977315', '2014-08-12', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM MS14-044: GDR Security Update'), (12, 2000, 'RTM ', '', '2014-04-01', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM '), + /*2012*/ (11, 7507, 'SP4 GDR Security Update', 'https://support.microsoft.com/en-us/help/4583465', '2021-01-12', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'Service Pack 4 GDR Security Update for CVE-2021-1636'), (11, 7493, 'SP4 GDR Security Update', 'https://support.microsoft.com/en-us/help/4532098', '2020-02-11', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'Service Pack 4 GDR Security Update for CVE-2020-0618'), (11, 7469, 'SP4 On-Demand Hotfix Update', 'https://support.microsoft.com/en-us/help/4091266', '2018-03-28', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'Service Pack 4 SP4 On-Demand Hotfix Update'), @@ -310,6 +329,7 @@ VALUES (11, 2316, 'RTM CU1', 'https://support.microsoft.com/en-us/help/2679368', '2012-04-12', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'RTM Cumulative Update 1'), (11, 2218, 'RTM MS12-070: GDR Security Update', 'https://support.microsoft.com/en-us/help/2716442', '2012-10-09', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'RTM MS12-070: GDR Security Update'), (11, 2100, 'RTM ', '', '2012-03-06', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'RTM '), + /*2008 R2*/ (10, 6529, 'SP3 MS15-058: QFE Security Update', 'https://support.microsoft.com/en-us/help/3045314', '2015-07-14', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'Service Pack 3 MS15-058: QFE Security Update'), (10, 6220, 'SP3 MS15-058: QFE Security Update', 'https://support.microsoft.com/en-us/help/3045316', '2015-07-14', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'Service Pack 3 MS15-058: QFE Security Update'), (10, 6000, 'SP3 ', 'https://support.microsoft.com/en-us/help/2979597', '2014-09-26', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'Service Pack 3 '), @@ -364,6 +384,7 @@ VALUES (10, 1702, 'RTM CU1', 'https://support.microsoft.com/en-us/help/981355', '2010-05-18', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM Cumulative Update 1'), (10, 1617, 'RTM MS11-049: GDR Security Update', 'https://support.microsoft.com/en-us/help/2494088', '2011-06-14', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM MS11-049: GDR Security Update'), (10, 1600, 'RTM ', '', '2010-05-10', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM '), + /*2008*/ (10, 6535, 'SP3 MS15-058: QFE Security Update', 'https://support.microsoft.com/en-us/help/3045308', '2015-07-14', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 MS15-058: QFE Security Update'), (10, 6241, 'SP3 MS15-058: GDR Security Update', 'https://support.microsoft.com/en-us/help/3045311', '2015-07-14', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 MS15-058: GDR Security Update'), (10, 5890, 'SP3 MS15-058: QFE Security Update', 'https://support.microsoft.com/en-us/help/3045303', '2015-07-14', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 MS15-058: QFE Security Update'), From 50e3b79f11d5b64d633c5194bba4924f1ca792f7 Mon Sep 17 00:00:00 2001 From: Vlad Drumea <48413726+VladDBA@users.noreply.github.com> Date: Sat, 14 Sep 2024 17:56:01 +0300 Subject: [PATCH 591/662] typo in a release year --- SqlServerVersions.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SqlServerVersions.sql b/SqlServerVersions.sql index a1862dffb..b8dc0837e 100644 --- a/SqlServerVersions.sql +++ b/SqlServerVersions.sql @@ -136,7 +136,7 @@ VALUES (14, 1000, 'RTM ', '', '2017-10-02', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM '), /*2016*/ (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', '2023-07-09', '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'), (13, 7029, 'SP3 Azure Feature Pack GDR', 'https://support.microsoft.com/en-us/help/5029187', '2023-10-10', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3 Azure Feature Pack GDR'), (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'), From 156fa58cfb525c5eaad60c46005969529fbd5852 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Tue, 8 Oct 2024 04:27:03 -0700 Subject: [PATCH 592/662] #3566 sp_BlitzLock better qualify synonym Was throwing errors on SQL 2022, possibly others, due to synonym location. Closes #3566. --- sp_BlitzLock.sql | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index ce42356e9..9f6d7ff3a 100644 --- a/sp_BlitzLock.sql +++ b/sp_BlitzLock.sql @@ -3675,8 +3675,9 @@ BEGIN SET STATISTICS XML ON; END; - INSERT INTO - DeadLockTbl + SET @StringToExecute = N' + + INSERT INTO ' + QUOTENAME(DB_NAME()) + N'..DeadLockTbl ( ServerName, deadlock_type, @@ -3720,7 +3721,8 @@ BEGIN deadlock_graph ) EXEC sys.sp_executesql - @deadlock_result; + @deadlock_result;' + EXEC sys.sp_executesql @StringToExecute, N'@deadlock_result NVARCHAR(MAX)', @deadlock_result; IF @Debug = 1 BEGIN @@ -3734,8 +3736,9 @@ BEGIN SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Findings to table %s', 0, 1, @d) WITH NOWAIT; - INSERT INTO - DeadlockFindings + SET @StringToExecute = N' + + INSERT INTO ' + QUOTENAME(DB_NAME()) + N'..DeadlockFindings ( ServerName, check_id, @@ -3753,7 +3756,8 @@ BEGIN df.finding FROM #deadlock_findings AS df ORDER BY df.check_id - OPTION(RECOMPILE); + OPTION(RECOMPILE);' + EXEC sys.sp_executesql @StringToExecute; RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; From a3d63235eed81761071f2e60d982aab2c7a8f2b5 Mon Sep 17 00:00:00 2001 From: Tisit Date: Tue, 1 Oct 2024 23:45:25 +0200 Subject: [PATCH 593/662] sp_Blitz: Fix checks 73 to work with sysadmin permissions --- sp_Blitz.sql | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index ad92d53ae..a5f415ee8 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -334,18 +334,15 @@ AS END CATCH; END; /*Need execute on sp_validatelogins*/ - IF ISNULL(@SkipGetAlertInfo, 0) != 1 /*If @SkipGetAlertInfo hasn't been set to 1 by the caller*/ - BEGIN - BEGIN TRY - /* Try to fill the table for check 73 */ - INSERT INTO #AlertInfo - EXEC [master].[dbo].[sp_MSgetalertinfo] @includeaddresses = 0; - - SET @SkipGetAlertInfo = 0; /*We can execute sp_MSgetalertinfo*/ - END TRY - BEGIN CATCH - SET @SkipGetAlertInfo = 1; /*We have don't have execute rights or sp_MSgetalertinfo throws an error so skip it*/ - END CATCH; + IF NOT EXISTS + ( + SELECT + 1/0 + FROM fn_my_permissions(N'[master].[dbo].[sp_MSgetalertinfo]', N'OBJECT') AS fmp + WHERE fmp.permission_name = N'EXECUTE' + ) + BEGIN + SET @SkipGetAlertInfo = 1; END; /*Need execute on sp_MSgetalertinfo*/ IF ISNULL(@SkipModel, 0) != 1 /*If @SkipModel hasn't been set to 1 by the caller*/ @@ -8467,6 +8464,9 @@ IF @ProductVersionMajor >= 10 BEGIN IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 73) WITH NOWAIT; + + INSERT INTO #AlertInfo + EXEC [master].[dbo].[sp_MSgetalertinfo] @includeaddresses = 0; INSERT INTO #BlitzResults ( CheckID , From 1a73cb4f6b861b6236028fdac95917e384efc3a8 Mon Sep 17 00:00:00 2001 From: Tisit Date: Wed, 9 Oct 2024 16:33:16 +0200 Subject: [PATCH 594/662] sp_Blitz: Check 191 avoid false positives in case user doesn't have permissions on sys.master_files --- sp_Blitz.sql | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index ad92d53ae..449f9083e 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -6108,6 +6108,8 @@ IF @ProductVersionMajor >= 10 FROM #SkipChecks WHERE DatabaseName IS NULL AND CheckID = 191 ) AND (SELECT COUNT(*) FROM sys.master_files WHERE database_id = 2) <> (SELECT COUNT(*) FROM tempdb.sys.database_files) + /* User may have no permissions to see tempdb files in sys.master_files. In that case count returned will be 0 and we want to skip the check */ + AND (SELECT COUNT(*) FROM sys.master_files WHERE database_id = 2) <> 0 BEGIN IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 191) WITH NOWAIT From c1fcf2e113b2a486770da570139f77ee82eece1f Mon Sep 17 00:00:00 2001 From: Vlad Drumea <48413726+VladDBA@users.noreply.github.com> Date: Tue, 15 Oct 2024 22:48:16 +0300 Subject: [PATCH 595/662] Inclusive language changes for sp_BlitzIndex --- .../sp_BlitzIndex_Checks_by_Priority.md | 120 ++++++++-------- sp_BlitzIndex.sql | 136 +++++++++--------- 2 files changed, 128 insertions(+), 128 deletions(-) diff --git a/Documentation/sp_BlitzIndex_Checks_by_Priority.md b/Documentation/sp_BlitzIndex_Checks_by_Priority.md index ad51fa87b..3cc69a7f4 100644 --- a/Documentation/sp_BlitzIndex_Checks_by_Priority.md +++ b/Documentation/sp_BlitzIndex_Checks_by_Priority.md @@ -9,63 +9,63 @@ If you want to change anything about a check - the priority, finding, URL, or ID CURRENT HIGH CHECKID: 121 If you want to add a new check, start at 122. -| Priority | FindingsGroup | Finding | URL | CheckID | -|----------|---------------------------------|---------------------------------------|-------------------------------------------------|----------| -| 10 | Index Hoarder | Many NC Indexes on a Single Table | https://www.brentozar.com/go/IndexHoarder | 20 | -| 10 | Index Hoarder | Unused NC Index with High Writes | https://www.brentozar.com/go/IndexHoarder | 22 | -| 20 | Multiple Index Personalities | Duplicate Keys | https://www.brentozar.com/go/duplicateindex | 1 | -| 30 | Multiple Index Personalities | Borderline Duplicate Keys | https://www.brentozar.com/go/duplicateindex | 2 | -| 40 | Indexaphobia | High Value Missing Index | https://www.brentozar.com/go/indexaphobia | 50 | -| 70 | Aggressive Indexes | Total Lock Time with Long Average Waits | https://www.brentozar.com/go/aggressiveindexes | 11 | -| 70 | Aggressive Indexes | Total Lock Time with Short Average Waits | https://www.brentozar.com/go/aggressiveindexes | 12 | -| 80 | Abnormal Psychology | Columnstore Indexes with Trace Flag 834 | https://support.microsoft.com/en-us/kb/3210239 | 72 | -| 80 | Abnormal Psychology | Identity Column Near End of Range | https://www.brentozar.com/go/AbnormalPsychology | 68 | -| 80 | Abnormal Psychology | Filter Columns Not In Index Definition | https://www.brentozar.com/go/IndexFeatures | 34 | -| 90 | Functioning Statistaholics | Low Sampling Rates | https://www.brentozar.com/go/stats | 91 | -| 90 | Functioning Statistaholics | Statistics Not Updated Recently | https://www.brentozar.com/go/stats | 90 | -| 90 | Functioning Statistaholics | Statistics with NO RECOMPUTE | https://www.brentozar.com/go/stats | 92 | -| 100 | Index Hoarder | NC index with High Writes:Reads | https://www.brentozar.com/go/IndexHoarder | 48 | -| 100 | Self Loathing Indexes | Heap with a Nonclustered Primary Key | https://www.brentozar.com/go/SelfLoathing | 47 | -| 100 | Self Loathing Indexes | Heap with Forwarded Fetches | https://www.brentozar.com/go/SelfLoathing | 43 | -| 100 | Self Loathing Indexes | Large Active Heap | https://www.brentozar.com/go/SelfLoathing | 44 | -| 100 | Self Loathing Indexes | Low Fill Factor on Clustered Index | https://www.brentozar.com/go/SelfLoathing | 40 | -| 100 | Self Loathing Indexes | Low Fill Factor on Nonclustered Index | https://www.brentozar.com/go/SelfLoathing | 40 | -| 100 | Self Loathing Indexes | Medium Active Heap | https://www.brentozar.com/go/SelfLoathing | 45 | -| 100 | Self Loathing Indexes | Small Active Heap | https://www.brentozar.com/go/SelfLoathing | 46 | -| 100 | Serial Forcer | Computed Column with Scalar UDF | https://www.brentozar.com/go/serialudf | 99 | -| 100 | Serial Forcer | Check Constraint with Scalar UDF | https://www.brentozar.com/go/computedscalar | 94 | -| 150 | Abnormal Psychology | Cascading Updates or Deletes | https://www.brentozar.com/go/AbnormalPsychology | 71 | -| 150 | Abnormal Psychology | Unindexed Foreign Keys | https://www.brentozar.com/go/AbnormalPsychology | 72 | -| 150 | Abnormal Psychology | Columnstore Index | https://www.brentozar.com/go/AbnormalPsychology | 61 | -| 150 | Abnormal Psychology | Column Collation Does Not Match Database Collation| https://www.brentozar.com/go/AbnormalPsychology | 69 | -| 150 | Abnormal Psychology | Compressed Index | https://www.brentozar.com/go/AbnormalPsychology | 63 | -| 150 | Abnormal Psychology | In-Memory OLTP | https://www.brentozar.com/go/AbnormalPsychology | 73 | -| 150 | Abnormal Psychology | Non-Aligned Index on a Partitioned Table | https://www.brentozar.com/go/AbnormalPsychology | 65 | -| 150 | Abnormal Psychology | Partitioned Index | https://www.brentozar.com/go/AbnormalPsychology | 64 | -| 150 | Abnormal Psychology | Spatial Index | https://www.brentozar.com/go/AbnormalPsychology | 62 | -| 150 | Abnormal Psychology | XML Index | https://www.brentozar.com/go/AbnormalPsychology | 60 | -| 150 | Index Hoarder | Borderline: Wide Indexes (7 or More Columns) | https://www.brentozar.com/go/IndexHoarder | 23 | -| 150 | Index Hoarder | More Than 5 Percent NC Indexes Are Unused | https://www.brentozar.com/go/IndexHoarder | 21 | -| 150 | Index Hoarder | Non-Unique Clustered Index | https://www.brentozar.com/go/IndexHoarder | 28 | -| 150 | Index Hoarder | Unused NC Index with Low Writes | https://www.brentozar.com/go/IndexHoarder | 29 | -| 150 | Index Hoarder | Wide Clustered Index (>3 columns or >16 bytes) | https://www.brentozar.com/go/IndexHoarder | 24 | -| 150 | Self Loathing Indexes | Disabled Index | https://www.brentozar.com/go/SelfLoathing | 42 | -| 150 | Self Loathing Indexes | Hypothetical Index | https://www.brentozar.com/go/SelfLoathing | 41 | -| 200 | Abnormal Psychology | Identity Column Using a Negative Seed or Increment Other Than 1 | https://www.brentozar.com/go/AbnormalPsychology | 74 | -| 200 | Abnormal Psychology | Recently Created Tables/Indexes (1 week) | https://www.brentozar.com/go/AbnormalPsychology | 66 | -| 200 | Abnormal Psychology | Recently Modified Tables/Indexes (2 days) | https://www.brentozar.com/go/AbnormalPsychology | 67 | -| 200 | Abnormal Psychology | Replicated Columns | https://www.brentozar.com/go/AbnormalPsychology | 70 | -| 200 | Abnormal Psychology | Temporal Tables | https://www.brentozar.com/go/AbnormalPsychology | 110 | -| 200 | Cold Calculators | Definition Defeatists | https://www.brentozar.com/go/serialudf | 100 | -| 200 | Functioning Statistaholics | Filter Fixation | https://www.brentozar.com/go/stats | 93 | -| 200 | Index Hoarder | Addicted to Nulls | https://www.brentozar.com/go/IndexHoarder | 25 | -| 200 | Index Hoarder | Addicted to Strings | https://www.brentozar.com/go/IndexHoarder | 27 | -| 200 | Index Hoarder | Wide Tables: 35+ cols or > 2000 non-LOB bytes | https://www.brentozar.com/go/IndexHoarder | 26 | -| 200 | Self Loathing Indexes | Heaps with Deletes | https://www.brentozar.com/go/SelfLoathing | 49 | -| 200 | Workaholics | Scan-a-lots (index-usage-stats) | https://www.brentozar.com/go/Workaholics | 80 | -| 200 | Workaholics | Top Recent Accesses (index-op-stats) | https://www.brentozar.com/go/Workaholics | 81 | -| 250 | Feature-Phobic Indexes | Few Indexes Use Includes | https://www.brentozar.com/go/IndexFeatures | 31 | -| 250 | Feature-Phobic Indexes | No Filtered Indexes or Indexed Views | https://www.brentozar.com/go/IndexFeatures | 32 | -| 250 | Feature-Phobic Indexes | No Indexes Use Includes | https://www.brentozar.com/go/IndexFeatures | 30 | -| 250 | Feature-Phobic Indexes | Potential Filtered Index (Based on Column Name) | https://www.brentozar.com/go/IndexFeatures | 33 | -| 250 | Medicated 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 | +| 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 | +| 90 | Statistics Warnings | Low Sampling Rates | https://www.brentozar.com/go/stats | 91 | +| 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 | 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 de5cf8b8f..d2bcff05c 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -3500,7 +3500,7 @@ BEGIN SELECT 1 AS check_id, ip.index_sanity_id, 20 AS Priority, - 'Multiple Index Personalities' AS findings_group, + 'Redundant Indexes' AS findings_group, 'Duplicate keys' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/duplicateindex' AS URL, @@ -3537,8 +3537,8 @@ BEGIN SELECT 2 AS check_id, ip.index_sanity_id, 30 AS Priority, - 'Multiple Index Personalities' AS findings_group, - 'Borderline duplicate keys' AS finding, + 'Redundant Indexes' AS findings_group, + 'Approximate Duplicate Keys' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/duplicateindex' AS URL, ip.db_schema_object_indexid AS details, @@ -3571,7 +3571,7 @@ BEGIN SELECT 11 AS check_id, i.index_sanity_id, 70 AS Priority, - N'Aggressive ' + N'Locking-Prone ' + CASE COALESCE((SELECT SUM(1) FROM #IndexSanity iMe INNER JOIN #IndexSanity iOthers @@ -3632,7 +3632,7 @@ BEGIN SELECT 20 AS check_id, MAX(i.index_sanity_id) AS index_sanity_id, 10 AS Priority, - 'Index Hoarder' AS findings_group, + 'Over-Indexing' AS findings_group, 'Many NC Indexes on a Single Table' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/IndexHoarder' AS URL, @@ -3656,13 +3656,13 @@ BEGIN ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); - RAISERROR(N'check_id 22: NC indexes with 0 reads. (Borderline) and >= 10,000 writes', 0,1) WITH NOWAIT; + RAISERROR(N'check_id 22: NC indexes with 0 reads and >= 10,000 writes', 0,1) WITH NOWAIT; INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, secret_columns, index_usage_summary, index_size_summary ) SELECT 22 AS check_id, i.index_sanity_id, 10 AS Priority, - N'Index Hoarder' AS findings_group, + N'Over-Indexing' AS findings_group, N'Unused NC Index with High Writes' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/IndexHoarder' AS URL, @@ -3695,7 +3695,7 @@ BEGIN SELECT 34 AS check_id, i.index_sanity_id, 80 AS Priority, - N'Abnormal Psychology' AS findings_group, + N'Abnormal Design Pattern' AS findings_group, N'Filter Columns Not In Index Definition' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/IndexFeatures' AS URL, @@ -3729,7 +3729,7 @@ BEGIN SELECT 40 AS check_id, i.index_sanity_id, 100 AS Priority, - N'Self Loathing Indexes' AS findings_group, + N'Indexes Worth Reviewing' AS findings_group, N'Low Fill Factor on Nonclustered Index' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/SelfLoathing' AS URL, @@ -3756,7 +3756,7 @@ BEGIN SELECT 40 AS check_id, i.index_sanity_id, 100 AS Priority, - N'Self Loathing Indexes' AS findings_group, + N'Indexes Worth Reviewing' AS findings_group, N'Low Fill Factor on Clustered Index' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/SelfLoathing' AS URL, @@ -3795,7 +3795,7 @@ BEGIN SELECT 43 AS check_id, i.index_sanity_id, 100 AS Priority, - N'Self Loathing Indexes' AS findings_group, + N'Indexes Worth Reviewing' AS findings_group, N'Heaps with Forwarded Fetches' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/SelfLoathing' AS URL, @@ -3838,7 +3838,7 @@ BEGIN SELECT 44 AS check_id, i.index_sanity_id, 100 AS Priority, - N'Self Loathing Indexes' AS findings_group, + N'Indexes Worth Reviewing' AS findings_group, N'Large Active Heap' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/SelfLoathing' AS URL, @@ -3876,7 +3876,7 @@ BEGIN SELECT 45 AS check_id, i.index_sanity_id, 100 AS Priority, - N'Self Loathing Indexes' AS findings_group, + N'Indexes Worth Reviewing' AS findings_group, N'Medium Active heap' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/SelfLoathing' AS URL, @@ -3915,7 +3915,7 @@ BEGIN SELECT 46 AS check_id, i.index_sanity_id, 100 AS Priority, - N'Self Loathing Indexes' AS findings_group, + N'Indexes Worth Reviewing' AS findings_group, N'Small Active heap' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/SelfLoathing' AS URL, @@ -3942,7 +3942,7 @@ BEGIN SELECT 47 AS check_id, i.index_sanity_id, 100 AS Priority, - N'Self Loathing Indexes' AS findings_group, + N'Indexes Worth Reviewing' AS findings_group, N'Heap with a Nonclustered Primary Key' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/SelfLoathing' AS URL, @@ -3970,7 +3970,7 @@ BEGIN SELECT 48 AS check_id, i.index_sanity_id, 100 AS Priority, - N'Index Hoarder' AS findings_group, + N'Over-Indexing' AS findings_group, N'NC index with High Writes:Reads' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/IndexHoarder' AS URL, @@ -4000,7 +4000,7 @@ BEGIN --Indexaphobia --Missing indexes with value >= 5 million: : Check_id 50-59 ---------------------------------------- - RAISERROR(N'check_id 50: Indexaphobia.', 0,1) WITH NOWAIT; + RAISERROR(N'check_id 50: High Value Missing Index.', 0,1) WITH NOWAIT; WITH index_size_cte AS ( SELECT i.database_id, i.schema_name, @@ -4038,7 +4038,7 @@ BEGIN 50 AS check_id, sz.index_sanity_id, 40 AS Priority, - N'Indexaphobia' AS findings_group, + N'Index Suggestion' AS findings_group, N'High Value Missing Index' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/Indexaphobia' AS URL, @@ -4083,7 +4083,7 @@ BEGIN SELECT 68 AS check_id, i.index_sanity_id, 80 AS Priority, - N'Abnormal Psychology' AS findings_group, + N'Abnormal Design Pattern' AS findings_group, N'Identity Column Within ' + CAST (calc1.percent_remaining AS NVARCHAR(256)) + N' Percent End of Range' AS finding, @@ -4148,7 +4148,7 @@ BEGIN SELECT 72 AS check_id, i.index_sanity_id, 80 AS Priority, - N'Abnormal Psychology' AS findings_group, + N'Abnormal Design Pattern' AS findings_group, 'Columnstore Indexes with Trace Flag 834' AS finding, [database_name] AS [Database Name], N'https://support.microsoft.com/en-us/kb/3210239' AS URL, @@ -4172,7 +4172,7 @@ BEGIN secret_columns, index_usage_summary, index_size_summary ) SELECT 90 AS check_id, 90 AS Priority, - 'Functioning Statistaholics' AS findings_group, + 'Statistics Warnings' AS findings_group, 'Statistics Not Updated Recently', s.database_name, 'https://www.brentozar.com/go/stats' AS URL, @@ -4199,7 +4199,7 @@ BEGIN secret_columns, index_usage_summary, index_size_summary ) SELECT 91 AS check_id, 90 AS Priority, - 'Functioning Statistaholics' AS findings_group, + 'Statistics Warnings' AS findings_group, 'Low Sampling Rates', s.database_name, 'https://www.brentozar.com/go/stats' AS URL, @@ -4218,7 +4218,7 @@ BEGIN secret_columns, index_usage_summary, index_size_summary ) SELECT 92 AS check_id, 90 AS Priority, - 'Functioning Statistaholics' AS findings_group, + 'Statistics Warnings' AS findings_group, 'Statistics With NO RECOMPUTE', s.database_name, 'https://www.brentozar.com/go/stats' AS URL, @@ -4237,7 +4237,7 @@ BEGIN secret_columns, index_usage_summary, index_size_summary ) SELECT 94 AS check_id, 100 AS Priority, - 'Serial Forcer' AS findings_group, + 'Forced Serialization' AS findings_group, 'Check Constraint with Scalar UDF' AS finding, cc.database_name, 'https://www.brentozar.com/go/computedscalar' AS URL, @@ -4256,7 +4256,7 @@ BEGIN secret_columns, index_usage_summary, index_size_summary ) SELECT 99 AS check_id, 100 AS Priority, - 'Serial Forcer' AS findings_group, + 'Forced Serialization' AS findings_group, 'Computed Column with Scalar UDF' AS finding, cc.database_name, 'https://www.brentozar.com/go/serialudf' AS URL, @@ -4313,7 +4313,7 @@ BEGIN SELECT 21 AS check_id, MAX(i.index_sanity_id) AS index_sanity_id, 150 AS Priority, - N'Index Hoarder' AS findings_group, + N'Over-Indexing' AS findings_group, N'More Than 5 Percent NC Indexes Are Unused' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/IndexHoarder' AS URL, @@ -4348,8 +4348,8 @@ BEGIN SELECT 23 AS check_id, i.index_sanity_id, 150 AS Priority, - N'Index Hoarder' AS findings_group, - N'Borderline: Wide Indexes (7 or More Columns)' AS finding, + N'Over-Indexing' AS findings_group, + N'Approximate: Wide Indexes (7 or More Columns)' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/IndexHoarder' AS URL, CAST(count_key_columns + count_included_columns AS NVARCHAR(10)) + ' columns on ' @@ -4376,7 +4376,7 @@ BEGIN SELECT 24 AS check_id, i.index_sanity_id, 150 AS Priority, - N'Index Hoarder' AS findings_group, + N'Over-Indexing' AS findings_group, N'Wide Clustered Index (> 3 columns OR > 16 bytes)' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/IndexHoarder' AS URL, @@ -4408,7 +4408,7 @@ BEGIN AND i.is_CX_columnstore = 0 ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); - RAISERROR(N'check_id 25: Addicted to nullable columns.', 0,1) WITH NOWAIT; + RAISERROR(N'check_id 25: High ratio of nullable columns.', 0,1) WITH NOWAIT; WITH count_columns AS ( SELECT [object_id], [database_id], @@ -4426,8 +4426,8 @@ BEGIN SELECT 25 AS check_id, i.index_sanity_id, 200 AS Priority, - N'Index Hoarder' AS findings_group, - N'Addicted to Nulls' AS finding, + N'Over-Indexing' AS findings_group, + N'High Ratio of Nulls' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/IndexHoarder' AS URL, i.db_schema_object_name @@ -4467,7 +4467,7 @@ BEGIN SELECT 26 AS check_id, i.index_sanity_id, 150 AS Priority, - N'Index Hoarder' AS findings_group, + N'Over-Indexing' AS findings_group, N'Wide Tables: 35+ cols or > 2000 non-LOB bytes' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/IndexHoarder' AS URL, @@ -4494,7 +4494,7 @@ BEGIN cc.sum_max_length >= 2000) ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); - RAISERROR(N'check_id 27: Addicted to strings.', 0,1) WITH NOWAIT; + RAISERROR(N'check_id 27: High Ratio of Strings.', 0,1) WITH NOWAIT; WITH count_columns AS ( SELECT [object_id], [database_id], @@ -4512,8 +4512,8 @@ BEGIN SELECT 27 AS check_id, i.index_sanity_id, 200 AS Priority, - N'Index Hoarder' AS findings_group, - N'Addicted to strings' AS finding, + N'Over-Indexing' AS findings_group, + N'High Ratio of Strings' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/IndexHoarder' AS URL, i.db_schema_object_name @@ -4541,7 +4541,7 @@ BEGIN SELECT 28 AS check_id, i.index_sanity_id, 150 AS Priority, - N'Index Hoarder' AS findings_group, + N'Over-Indexing' AS findings_group, N'Non-Unique Clustered Index' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/IndexHoarder' AS URL, @@ -4567,13 +4567,13 @@ BEGIN AND is_CX_columnstore=0 /* not a clustered columnstore-- no unique option on those */ ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); - RAISERROR(N'check_id 29: NC indexes with 0 reads. (Borderline) and < 10,000 writes', 0,1) WITH NOWAIT; + RAISERROR(N'check_id 29: NC indexes with 0 reads and < 10,000 writes', 0,1) WITH NOWAIT; INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, secret_columns, index_usage_summary, index_size_summary ) SELECT 29 AS check_id, i.index_sanity_id, 150 AS Priority, - N'Index Hoarder' AS findings_group, + N'Over-Indexing' AS findings_group, N'Unused NC index with Low Writes' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/IndexHoarder' AS URL, @@ -4619,7 +4619,7 @@ BEGIN SELECT 30 AS check_id, NULL AS index_sanity_id, 250 AS Priority, - N'Feature-Phobic Indexes' AS findings_group, + N'Omitted Index Features' AS findings_group, database_name AS [Database Name], N'No Indexes Use Includes' AS finding, 'https://www.brentozar.com/go/IndexFeatures' AS URL, N'No Indexes Use Includes' AS details, @@ -4637,7 +4637,7 @@ BEGIN SELECT 31 AS check_id, NULL AS index_sanity_id, 250 AS Priority, - N'Feature-Phobic Indexes' AS findings_group, + N'Omitted Index Features' AS findings_group, N'Few Indexes Use Includes' AS findings, database_name AS [Database Name], N'https://www.brentozar.com/go/IndexFeatures' AS URL, @@ -4658,7 +4658,7 @@ BEGIN 32 AS check_id, NULL AS index_sanity_id, 250 AS Priority, - N'Feature-Phobic Indexes' AS findings_group, + N'Omitted Index Features' AS findings_group, N'No Filtered Indexes or Indexed Views' AS finding, i.database_name AS [Database Name], N'https://www.brentozar.com/go/IndexFeatures' AS URL, @@ -4684,7 +4684,7 @@ BEGIN SELECT 33 AS check_id, i.index_sanity_id AS index_sanity_id, 250 AS Priority, - N'Feature-Phobic Indexes' AS findings_group, + N'Omitted Index Features' AS findings_group, N'Potential Filtered Index (Based on Column Name)' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/IndexFeatures' AS URL, @@ -4712,7 +4712,7 @@ BEGIN SELECT 41 AS check_id, i.index_sanity_id, 150 AS Priority, - N'Self Loathing Indexes' AS findings_group, + N'Indexes Worth Reviewing' AS findings_group, N'Hypothetical Index' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/SelfLoathing' AS URL, @@ -4733,7 +4733,7 @@ BEGIN SELECT 42 AS check_id, index_sanity_id, 150 AS Priority, - N'Self Loathing Indexes' AS findings_group, + N'Indexes Worth Reviewing' AS findings_group, N'Disabled Index' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/SelfLoathing' AS URL, @@ -4763,7 +4763,7 @@ BEGIN SELECT 49 AS check_id, i.index_sanity_id, 200 AS Priority, - N'Self Loathing Indexes' AS findings_group, + N'Indexes Worth Reviewing' AS findings_group, N'Heaps with Deletes' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/SelfLoathing' AS URL, @@ -4791,7 +4791,7 @@ BEGIN SELECT 60 AS check_id, i.index_sanity_id, 150 AS Priority, - N'Abnormal Psychology' AS findings_group, + N'Abnormal Design Pattern' AS findings_group, N'XML Index' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, @@ -4811,7 +4811,7 @@ BEGIN SELECT 61 AS check_id, i.index_sanity_id, 150 AS Priority, - N'Abnormal Psychology' AS findings_group, + N'Abnormal Design Pattern' AS findings_group, CASE WHEN i.is_NC_columnstore=1 THEN N'NC Columnstore Index' ELSE N'Clustered Columnstore Index' @@ -4835,7 +4835,7 @@ BEGIN SELECT 62 AS check_id, i.index_sanity_id, 150 AS Priority, - N'Abnormal Psychology' AS findings_group, + N'Abnormal Design Pattern' AS findings_group, N'Spatial Index' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, @@ -4855,7 +4855,7 @@ BEGIN SELECT 63 AS check_id, i.index_sanity_id, 150 AS Priority, - N'Abnormal Psychology' AS findings_group, + N'Abnormal Design Pattern' AS findings_group, N'Compressed Index' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, @@ -4875,7 +4875,7 @@ BEGIN SELECT 64 AS check_id, i.index_sanity_id, 150 AS Priority, - N'Abnormal Psychology' AS findings_group, + N'Abnormal Design Pattern' AS findings_group, N'Partitioned Index' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, @@ -4895,7 +4895,7 @@ BEGIN SELECT 65 AS check_id, i.index_sanity_id, 150 AS Priority, - N'Abnormal Psychology' AS findings_group, + N'Abnormal Design Pattern' AS findings_group, N'Non-Aligned Index on a Partitioned Table' AS finding, i.[database_name] AS [Database Name], N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, @@ -4921,7 +4921,7 @@ BEGIN SELECT 66 AS check_id, i.index_sanity_id, 200 AS Priority, - N'Abnormal Psychology' AS findings_group, + N'Abnormal Design Pattern' AS findings_group, N'Recently Created Tables/Indexes (1 week)' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, @@ -4944,7 +4944,7 @@ BEGIN SELECT 67 AS check_id, i.index_sanity_id, 200 AS Priority, - N'Abnormal Psychology' AS findings_group, + N'Abnormal Design Pattern' AS findings_group, N'Recently Modified Tables/Indexes (2 days)' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, @@ -4981,7 +4981,7 @@ BEGIN SELECT 69 AS check_id, i.index_sanity_id, 150 AS Priority, - N'Abnormal Psychology' AS findings_group, + N'Abnormal Design Pattern' AS findings_group, N'Column Collation Does Not Match Database Collation' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, @@ -5020,7 +5020,7 @@ BEGIN SELECT 70 AS check_id, i.index_sanity_id, 200 AS Priority, - N'Abnormal Psychology' AS findings_group, + N'Abnormal Design Pattern' AS findings_group, N'Replicated Columns' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, @@ -5050,7 +5050,7 @@ BEGIN SELECT 71 AS check_id, NULL AS index_sanity_id, 150 AS Priority, - N'Abnormal Psychology' AS findings_group, + N'Abnormal Design Pattern' AS findings_group, N'Cascading Updates or Deletes' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, @@ -5078,7 +5078,7 @@ BEGIN SELECT 72 AS check_id, NULL AS index_sanity_id, 150 AS Priority, - N'Abnormal Psychology' AS findings_group, + N'Abnormal Design Pattern' AS findings_group, N'Unindexed Foreign Keys' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, @@ -5102,7 +5102,7 @@ BEGIN SELECT 73 AS check_id, i.index_sanity_id, 150 AS Priority, - N'Abnormal Psychology' AS findings_group, + N'Abnormal Design Pattern' AS findings_group, N'In-Memory OLTP' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, @@ -5122,7 +5122,7 @@ BEGIN SELECT 74 AS check_id, i.index_sanity_id, 200 AS Priority, - N'Abnormal Psychology' AS findings_group, + N'Abnormal Design Pattern' AS findings_group, N'Identity Column Using a Negative Seed or Increment Other Than 1' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, @@ -5174,7 +5174,7 @@ BEGIN 80 AS check_id, i.index_sanity_id AS index_sanity_id, 200 AS Priority, - N'Workaholics' AS findings_group, + N'High Workloads' AS findings_group, N'Scan-a-lots (index-usage-stats)' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/Workaholics' AS URL, @@ -5202,7 +5202,7 @@ BEGIN 81 AS check_id, i.index_sanity_id AS index_sanity_id, 200 AS Priority, - N'Workaholics' AS findings_group, + N'High Workloads' AS findings_group, N'Top Recent Accesses (index-op-stats)' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/Workaholics' AS URL, @@ -5230,8 +5230,8 @@ BEGIN secret_columns, index_usage_summary, index_size_summary ) SELECT 93 AS check_id, 200 AS Priority, - 'Functioning Statistaholics' AS findings_group, - 'Filter Fixation', + 'Statistics Warnings' AS findings_group, + 'Statistics With Filters', s.database_name, 'https://www.brentozar.com/go/stats' AS URL, 'The statistic ' + QUOTENAME(s.statistics_name) + ' is filtered on [' + s.filter_definition + ']. It could be part of a filtered index, or just a filtered statistic. This is purely informational.' AS details, @@ -5249,8 +5249,8 @@ BEGIN secret_columns, index_usage_summary, index_size_summary ) SELECT 100 AS check_id, 200 AS Priority, - 'Cold Calculators' AS findings_group, - 'Definition Defeatists' AS finding, + 'Repeated Calculations' AS findings_group, + 'Computed Columns Not Persisted' AS finding, cc.database_name, '' AS URL, 'The computed column ' + QUOTENAME(cc.column_name) + ' on ' + QUOTENAME(cc.schema_name) + '.' + QUOTENAME(cc.table_name) + ' is not persisted, which means it will be calculated when a query runs.' + @@ -5270,7 +5270,7 @@ BEGIN SELECT 110 AS check_id, 200 AS Priority, - 'Abnormal Psychology' AS findings_group, + 'Abnormal Design Pattern' AS findings_group, 'Temporal Tables', t.database_name, '' AS URL, @@ -5290,7 +5290,7 @@ BEGIN SELECT 121 AS check_id, 200 AS Priority, - 'Medicated Indexes' AS findings_group, + 'Specialized Indexes' AS findings_group, 'Optimized For Sequential Keys', i.database_name, '' AS URL, From 1b25ef281ab560ae491f7755e455ce39dc02343a Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Sat, 19 Oct 2024 03:39:18 -0700 Subject: [PATCH 596/662] 2024-10-19 release Bumping version numbers and dates, adding latest GDRs to build list. --- Install-All-Scripts.sql | 352 ++++++++++++++++++++++++++++------------ Install-Azure.sql | 172 +++++++++++--------- SqlServerVersions.sql | 8 + 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, 354 insertions(+), 198 deletions(-) diff --git a/Install-All-Scripts.sql b/Install-All-Scripts.sql index 18edfe7c3..e074f4866 100644 --- a/Install-All-Scripts.sql +++ b/Install-All-Scripts.sql @@ -38,7 +38,7 @@ AS SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.21', @VersionDate = '20240701'; + SELECT @Version = '8.22', @VersionDate = '20241019'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -334,18 +334,15 @@ AS END CATCH; END; /*Need execute on sp_validatelogins*/ - IF ISNULL(@SkipGetAlertInfo, 0) != 1 /*If @SkipGetAlertInfo hasn't been set to 1 by the caller*/ - BEGIN - BEGIN TRY - /* Try to fill the table for check 73 */ - INSERT INTO #AlertInfo - EXEC [master].[dbo].[sp_MSgetalertinfo] @includeaddresses = 0; - - SET @SkipGetAlertInfo = 0; /*We can execute sp_MSgetalertinfo*/ - END TRY - BEGIN CATCH - SET @SkipGetAlertInfo = 1; /*We have don't have execute rights or sp_MSgetalertinfo throws an error so skip it*/ - END CATCH; + IF NOT EXISTS + ( + SELECT + 1/0 + FROM fn_my_permissions(N'[master].[dbo].[sp_MSgetalertinfo]', N'OBJECT') AS fmp + WHERE fmp.permission_name = N'EXECUTE' + ) + BEGIN + SET @SkipGetAlertInfo = 1; END; /*Need execute on sp_MSgetalertinfo*/ IF ISNULL(@SkipModel, 0) != 1 /*If @SkipModel hasn't been set to 1 by the caller*/ @@ -1134,7 +1131,10 @@ AS INSERT INTO #IgnorableWaits VALUES ('PARALLEL_REDO_WORKER_WAIT_WORK'); INSERT INTO #IgnorableWaits VALUES ('POPULATE_LOCK_ORDINALS'); INSERT INTO #IgnorableWaits VALUES ('PREEMPTIVE_HADR_LEASE_MECHANISM'); + INSERT INTO #IgnorableWaits VALUES ('PREEMPTIVE_OS_FLUSHFILEBUFFERS'); INSERT INTO #IgnorableWaits VALUES ('PREEMPTIVE_SP_SERVER_DIAGNOSTICS'); + INSERT INTO #IgnorableWaits VALUES ('PVS_PREALLOCATE'); + INSERT INTO #IgnorableWaits VALUES ('PWAIT_EXTENSIBILITY_CLEANUP_TASK'); INSERT INTO #IgnorableWaits VALUES ('QDS_ASYNC_QUEUE'); INSERT INTO #IgnorableWaits VALUES ('QDS_CLEANUP_STALE_QUERIES_TASK_MAIN_LOOP_SLEEP'); INSERT INTO #IgnorableWaits VALUES ('QDS_PERSIST_TASK_MAIN_LOOP_SLEEP'); @@ -1148,6 +1148,7 @@ AS INSERT INTO #IgnorableWaits VALUES ('SQLTRACE_BUFFER_FLUSH'); INSERT INTO #IgnorableWaits VALUES ('SQLTRACE_INCREMENTAL_FLUSH_SLEEP'); INSERT INTO #IgnorableWaits VALUES ('UCS_SESSION_REGISTRATION'); + INSERT INTO #IgnorableWaits VALUES ('VDI_CLIENT_OTHER'); INSERT INTO #IgnorableWaits VALUES ('WAIT_XTP_OFFLINE_CKPT_NEW_LOG'); INSERT INTO #IgnorableWaits VALUES ('WAITFOR'); INSERT INTO #IgnorableWaits VALUES ('XE_DISPATCHER_WAIT'); @@ -1836,9 +1837,18 @@ AS IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 2301) WITH NOWAIT; /* - #InvalidLogins is filled at the start during the permissions check + #InvalidLogins is filled at the start during the permissions check IF we are not sysadmin + filling it now if we are sysadmin */ - + IF @sa = 1 + BEGIN + INSERT INTO #InvalidLogins + ( + [LoginSID] + ,[LoginName] + ) + EXEC sp_validatelogins; + END; INSERT INTO #BlitzResults ( CheckID , Priority , @@ -6095,6 +6105,8 @@ IF @ProductVersionMajor >= 10 FROM #SkipChecks WHERE DatabaseName IS NULL AND CheckID = 191 ) AND (SELECT COUNT(*) FROM sys.master_files WHERE database_id = 2) <> (SELECT COUNT(*) FROM tempdb.sys.database_files) + /* User may have no permissions to see tempdb files in sys.master_files. In that case count returned will be 0 and we want to skip the check */ + AND (SELECT COUNT(*) FROM sys.master_files WHERE database_id = 2) <> 0 BEGIN IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 191) WITH NOWAIT @@ -6852,6 +6864,41 @@ IF @ProductVersionMajor >= 10 AND desired_state <> actual_state OPTION (RECOMPILE)'; END; + + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 265 ) + AND EXISTS(SELECT * FROM sys.all_objects WHERE name = 'database_query_store_options') + BEGIN + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 265) WITH NOWAIT; + + EXEC dbo.sp_MSforeachdb 'USE [?]; + SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + INSERT INTO #BlitzResults + (CheckID, + DatabaseName, + Priority, + FindingsGroup, + Finding, + URL, + Details) + SELECT TOP 1 265, + N''?'', + 200, + ''Performance'', + ''Query Store Unusually Configured'', + ''https://www.sqlskills.com/blogs/erin/query-store-best-practices/'', + (''The '' + query_capture_mode_desc + '' query capture mode '' + + CASE query_capture_mode_desc + WHEN ''ALL'' THEN ''captures more data than you will probably use. If your workload is heavily ad-hoc, then it can also cause Query Store to capture so much that it turns itself off.'' + WHEN ''NONE'' THEN ''stops Query Store capturing data for new queries.'' + WHEN ''CUSTOM'' THEN ''suggests that somebody has gone out of their way to only capture exactly what they want.'' + ELSE ''is not documented.'' END) + FROM [?].sys.database_query_store_options + WHERE desired_state <> 0 /* No point in checking this if Query Store is off. */ + AND query_capture_mode_desc <> ''AUTO'' + OPTION (RECOMPILE)'; + END; IF @ProductVersionMajor = 13 AND @ProductVersionMinor < 2149 --2016 CU1 has the fix in it AND NOT EXISTS ( SELECT 1 @@ -8419,6 +8466,9 @@ IF @ProductVersionMajor >= 10 BEGIN IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 73) WITH NOWAIT; + + INSERT INTO #AlertInfo + EXEC [master].[dbo].[sp_MSgetalertinfo] @includeaddresses = 0; INSERT INTO #BlitzResults ( CheckID , @@ -8481,11 +8531,11 @@ IF @ProductVersionMajor >= 10 WHEN [T].[TraceFlag] = '3226' THEN '3226 enabled globally, which keeps the event log clean by not reporting successful backups.' WHEN [T].[TraceFlag] = '3505' THEN '3505 enabled globally, which disables Checkpoints. This is usually a very bad idea.' WHEN [T].[TraceFlag] = '4199' THEN '4199 enabled globally, which enables non-default Query Optimizer fixes, changing query plans from the default behaviors.' - WHEN [T].[TraceFlag] = '7745' AND @ProductVersionMajor > 12 AND @QueryStoreInUse = 1 THEN '7745 enabled globally, which makes shutdowns/failovers quicker by not waiting for Query Store to flush to disk. This good idea loses you the non-flushed Query Store data.' + WHEN [T].[TraceFlag] = '7745' AND @QueryStoreInUse = 1 THEN '7745 enabled globally, which makes shutdowns/failovers quicker by not waiting for Query Store to flush to disk. This good idea loses you the non-flushed Query Store data.' WHEN [T].[TraceFlag] = '7745' AND @ProductVersionMajor > 12 THEN '7745 enabled globally, which is for Query Store. None of your databases have Query Store enabled, so why do you have this turned on?' WHEN [T].[TraceFlag] = '7745' AND @ProductVersionMajor <= 12 THEN '7745 enabled globally, which is for Query Store. Query Store does not exist on your SQL Server version, so why do you have this turned on?' WHEN [T].[TraceFlag] = '7752' AND @ProductVersionMajor > 14 THEN '7752 enabled globally, which is for Query Store. However, it has no effect in your SQL Server version. Consider turning it off.' - WHEN [T].[TraceFlag] = '7752' AND @ProductVersionMajor > 12 AND @QueryStoreInUse = 1 THEN '7752 enabled globally, which stops queries needing to wait on Query Store loading up after database recovery.' + WHEN [T].[TraceFlag] = '7752' AND @QueryStoreInUse = 1 THEN '7752 enabled globally, which stops queries needing to wait on Query Store loading up after database recovery.' WHEN [T].[TraceFlag] = '7752' AND @ProductVersionMajor > 12 THEN '7752 enabled globally, which is for Query Store. None of your databases have Query Store enabled, so why do you have this turned on?' WHEN [T].[TraceFlag] = '7752' AND @ProductVersionMajor <= 12 THEN '7752 enabled globally, which is for Query Store. Query Store does not exist on your SQL Server version, so why do you have this turned on?' WHEN [T].[TraceFlag] = '8048' THEN '8048 enabled globally, which tries to reduce CMEMTHREAD waits on servers with a lot of logical processors.' @@ -8495,6 +8545,54 @@ IF @ProductVersionMajor >= 10 ELSE [T].[TraceFlag] + ' is enabled globally.' END AS Details FROM #TraceStatus T; + + + IF NOT EXISTS ( SELECT 1 + FROM #TraceStatus T + WHERE [T].[TraceFlag] = '7745' ) + AND @QueryStoreInUse = 1 + + BEGIN + INSERT INTO #BlitzResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT 74 AS CheckID , + 200 AS Priority , + 'Informational' AS FindingsGroup , + 'Recommended Trace Flag Off' AS Finding , + 'https://www.sqlskills.com/blogs/erin/query-store-trace-flags/' AS URL , + 'Trace Flag 7745 not enabled globally. It makes shutdowns/failovers quicker by not waiting for Query Store to flush to disk. It is recommended, but it loses you the non-flushed Query Store data.' AS Details + FROM #TraceStatus T + END; + + IF NOT EXISTS ( SELECT 1 + FROM #TraceStatus T + WHERE [T].[TraceFlag] = '7752' ) + AND @ProductVersionMajor < 15 + AND @QueryStoreInUse = 1 + + BEGIN + INSERT INTO #BlitzResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT 74 AS CheckID , + 200 AS Priority , + 'Informational' AS FindingsGroup , + 'Recommended Trace Flag Off' AS Finding , + 'https://www.sqlskills.com/blogs/erin/query-store-trace-flags/' AS URL , + 'Trace Flag 7752 not enabled globally. It stops queries needing to wait on Query Store loading up after database recovery. It is so recommended that it is enabled by default as of SQL Server 2019.' AS Details + FROM #TraceStatus T + END; END; /* High CMEMTHREAD waits that could need trace flag 8048. @@ -10437,7 +10535,7 @@ AS SET NOCOUNT ON; SET STATISTICS XML OFF; -SELECT @Version = '8.21', @VersionDate = '20240701'; +SELECT @Version = '8.22', @VersionDate = '20241019'; IF(@VersionCheckMode = 1) BEGIN @@ -11315,7 +11413,7 @@ AS SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.21', @VersionDate = '20240701'; + SELECT @Version = '8.22', @VersionDate = '20241019'; IF(@VersionCheckMode = 1) BEGIN @@ -13097,7 +13195,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.21', @VersionDate = '20240701'; +SELECT @Version = '8.22', @VersionDate = '20241019'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -20471,7 +20569,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.21', @VersionDate = '20240701'; +SELECT @Version = '8.22', @VersionDate = '20241019'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -22302,6 +22400,7 @@ WITH ON ty.user_type_id = co.user_type_id WHERE id_inner.index_handle = id.index_handle AND id_inner.object_id = id.object_id + AND id_inner.database_id = DB_ID(''' + QUOTENAME(@DatabaseName) + N''') AND cn_inner.IndexColumnType = cn.IndexColumnType FOR XML PATH('''') ), @@ -22339,6 +22438,7 @@ WITH ) x (n) CROSS APPLY n.nodes(''x'') node(v) )AS cn + WHERE id.database_id = DB_ID(''' + QUOTENAME(@DatabaseName) + N''') GROUP BY id.index_handle, id.object_id, @@ -23921,7 +24021,7 @@ BEGIN SELECT 1 AS check_id, ip.index_sanity_id, 20 AS Priority, - 'Multiple Index Personalities' AS findings_group, + 'Redundant Indexes' AS findings_group, 'Duplicate keys' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/duplicateindex' AS URL, @@ -23958,8 +24058,8 @@ BEGIN SELECT 2 AS check_id, ip.index_sanity_id, 30 AS Priority, - 'Multiple Index Personalities' AS findings_group, - 'Borderline duplicate keys' AS finding, + 'Redundant Indexes' AS findings_group, + 'Approximate Duplicate Keys' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/duplicateindex' AS URL, ip.db_schema_object_indexid AS details, @@ -23992,7 +24092,7 @@ BEGIN SELECT 11 AS check_id, i.index_sanity_id, 70 AS Priority, - N'Aggressive ' + N'Locking-Prone ' + CASE COALESCE((SELECT SUM(1) FROM #IndexSanity iMe INNER JOIN #IndexSanity iOthers @@ -24053,7 +24153,7 @@ BEGIN SELECT 20 AS check_id, MAX(i.index_sanity_id) AS index_sanity_id, 10 AS Priority, - 'Index Hoarder' AS findings_group, + 'Over-Indexing' AS findings_group, 'Many NC Indexes on a Single Table' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/IndexHoarder' AS URL, @@ -24077,13 +24177,13 @@ BEGIN ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); - RAISERROR(N'check_id 22: NC indexes with 0 reads. (Borderline) and >= 10,000 writes', 0,1) WITH NOWAIT; + RAISERROR(N'check_id 22: NC indexes with 0 reads and >= 10,000 writes', 0,1) WITH NOWAIT; INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, secret_columns, index_usage_summary, index_size_summary ) SELECT 22 AS check_id, i.index_sanity_id, 10 AS Priority, - N'Index Hoarder' AS findings_group, + N'Over-Indexing' AS findings_group, N'Unused NC Index with High Writes' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/IndexHoarder' AS URL, @@ -24116,7 +24216,7 @@ BEGIN SELECT 34 AS check_id, i.index_sanity_id, 80 AS Priority, - N'Abnormal Psychology' AS findings_group, + N'Abnormal Design Pattern' AS findings_group, N'Filter Columns Not In Index Definition' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/IndexFeatures' AS URL, @@ -24150,7 +24250,7 @@ BEGIN SELECT 40 AS check_id, i.index_sanity_id, 100 AS Priority, - N'Self Loathing Indexes' AS findings_group, + N'Indexes Worth Reviewing' AS findings_group, N'Low Fill Factor on Nonclustered Index' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/SelfLoathing' AS URL, @@ -24177,7 +24277,7 @@ BEGIN SELECT 40 AS check_id, i.index_sanity_id, 100 AS Priority, - N'Self Loathing Indexes' AS findings_group, + N'Indexes Worth Reviewing' AS findings_group, N'Low Fill Factor on Clustered Index' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/SelfLoathing' AS URL, @@ -24216,7 +24316,7 @@ BEGIN SELECT 43 AS check_id, i.index_sanity_id, 100 AS Priority, - N'Self Loathing Indexes' AS findings_group, + N'Indexes Worth Reviewing' AS findings_group, N'Heaps with Forwarded Fetches' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/SelfLoathing' AS URL, @@ -24259,7 +24359,7 @@ BEGIN SELECT 44 AS check_id, i.index_sanity_id, 100 AS Priority, - N'Self Loathing Indexes' AS findings_group, + N'Indexes Worth Reviewing' AS findings_group, N'Large Active Heap' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/SelfLoathing' AS URL, @@ -24297,7 +24397,7 @@ BEGIN SELECT 45 AS check_id, i.index_sanity_id, 100 AS Priority, - N'Self Loathing Indexes' AS findings_group, + N'Indexes Worth Reviewing' AS findings_group, N'Medium Active heap' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/SelfLoathing' AS URL, @@ -24336,7 +24436,7 @@ BEGIN SELECT 46 AS check_id, i.index_sanity_id, 100 AS Priority, - N'Self Loathing Indexes' AS findings_group, + N'Indexes Worth Reviewing' AS findings_group, N'Small Active heap' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/SelfLoathing' AS URL, @@ -24363,7 +24463,7 @@ BEGIN SELECT 47 AS check_id, i.index_sanity_id, 100 AS Priority, - N'Self Loathing Indexes' AS findings_group, + N'Indexes Worth Reviewing' AS findings_group, N'Heap with a Nonclustered Primary Key' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/SelfLoathing' AS URL, @@ -24391,7 +24491,7 @@ BEGIN SELECT 48 AS check_id, i.index_sanity_id, 100 AS Priority, - N'Index Hoarder' AS findings_group, + N'Over-Indexing' AS findings_group, N'NC index with High Writes:Reads' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/IndexHoarder' AS URL, @@ -24421,7 +24521,7 @@ BEGIN --Indexaphobia --Missing indexes with value >= 5 million: : Check_id 50-59 ---------------------------------------- - RAISERROR(N'check_id 50: Indexaphobia.', 0,1) WITH NOWAIT; + RAISERROR(N'check_id 50: High Value Missing Index.', 0,1) WITH NOWAIT; WITH index_size_cte AS ( SELECT i.database_id, i.schema_name, @@ -24459,7 +24559,7 @@ BEGIN 50 AS check_id, sz.index_sanity_id, 40 AS Priority, - N'Indexaphobia' AS findings_group, + N'Index Suggestion' AS findings_group, N'High Value Missing Index' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/Indexaphobia' AS URL, @@ -24504,7 +24604,7 @@ BEGIN SELECT 68 AS check_id, i.index_sanity_id, 80 AS Priority, - N'Abnormal Psychology' AS findings_group, + N'Abnormal Design Pattern' AS findings_group, N'Identity Column Within ' + CAST (calc1.percent_remaining AS NVARCHAR(256)) + N' Percent End of Range' AS finding, @@ -24569,7 +24669,7 @@ BEGIN SELECT 72 AS check_id, i.index_sanity_id, 80 AS Priority, - N'Abnormal Psychology' AS findings_group, + N'Abnormal Design Pattern' AS findings_group, 'Columnstore Indexes with Trace Flag 834' AS finding, [database_name] AS [Database Name], N'https://support.microsoft.com/en-us/kb/3210239' AS URL, @@ -24593,7 +24693,7 @@ BEGIN secret_columns, index_usage_summary, index_size_summary ) SELECT 90 AS check_id, 90 AS Priority, - 'Functioning Statistaholics' AS findings_group, + 'Statistics Warnings' AS findings_group, 'Statistics Not Updated Recently', s.database_name, 'https://www.brentozar.com/go/stats' AS URL, @@ -24620,7 +24720,7 @@ BEGIN secret_columns, index_usage_summary, index_size_summary ) SELECT 91 AS check_id, 90 AS Priority, - 'Functioning Statistaholics' AS findings_group, + 'Statistics Warnings' AS findings_group, 'Low Sampling Rates', s.database_name, 'https://www.brentozar.com/go/stats' AS URL, @@ -24639,7 +24739,7 @@ BEGIN secret_columns, index_usage_summary, index_size_summary ) SELECT 92 AS check_id, 90 AS Priority, - 'Functioning Statistaholics' AS findings_group, + 'Statistics Warnings' AS findings_group, 'Statistics With NO RECOMPUTE', s.database_name, 'https://www.brentozar.com/go/stats' AS URL, @@ -24658,7 +24758,7 @@ BEGIN secret_columns, index_usage_summary, index_size_summary ) SELECT 94 AS check_id, 100 AS Priority, - 'Serial Forcer' AS findings_group, + 'Forced Serialization' AS findings_group, 'Check Constraint with Scalar UDF' AS finding, cc.database_name, 'https://www.brentozar.com/go/computedscalar' AS URL, @@ -24677,7 +24777,7 @@ BEGIN secret_columns, index_usage_summary, index_size_summary ) SELECT 99 AS check_id, 100 AS Priority, - 'Serial Forcer' AS findings_group, + 'Forced Serialization' AS findings_group, 'Computed Column with Scalar UDF' AS finding, cc.database_name, 'https://www.brentozar.com/go/serialudf' AS URL, @@ -24734,7 +24834,7 @@ BEGIN SELECT 21 AS check_id, MAX(i.index_sanity_id) AS index_sanity_id, 150 AS Priority, - N'Index Hoarder' AS findings_group, + N'Over-Indexing' AS findings_group, N'More Than 5 Percent NC Indexes Are Unused' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/IndexHoarder' AS URL, @@ -24769,8 +24869,8 @@ BEGIN SELECT 23 AS check_id, i.index_sanity_id, 150 AS Priority, - N'Index Hoarder' AS findings_group, - N'Borderline: Wide Indexes (7 or More Columns)' AS finding, + N'Over-Indexing' AS findings_group, + N'Approximate: Wide Indexes (7 or More Columns)' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/IndexHoarder' AS URL, CAST(count_key_columns + count_included_columns AS NVARCHAR(10)) + ' columns on ' @@ -24797,7 +24897,7 @@ BEGIN SELECT 24 AS check_id, i.index_sanity_id, 150 AS Priority, - N'Index Hoarder' AS findings_group, + N'Over-Indexing' AS findings_group, N'Wide Clustered Index (> 3 columns OR > 16 bytes)' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/IndexHoarder' AS URL, @@ -24829,7 +24929,7 @@ BEGIN AND i.is_CX_columnstore = 0 ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); - RAISERROR(N'check_id 25: Addicted to nullable columns.', 0,1) WITH NOWAIT; + RAISERROR(N'check_id 25: High ratio of nullable columns.', 0,1) WITH NOWAIT; WITH count_columns AS ( SELECT [object_id], [database_id], @@ -24847,8 +24947,8 @@ BEGIN SELECT 25 AS check_id, i.index_sanity_id, 200 AS Priority, - N'Index Hoarder' AS findings_group, - N'Addicted to Nulls' AS finding, + N'Over-Indexing' AS findings_group, + N'High Ratio of Nulls' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/IndexHoarder' AS URL, i.db_schema_object_name @@ -24888,7 +24988,7 @@ BEGIN SELECT 26 AS check_id, i.index_sanity_id, 150 AS Priority, - N'Index Hoarder' AS findings_group, + N'Over-Indexing' AS findings_group, N'Wide Tables: 35+ cols or > 2000 non-LOB bytes' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/IndexHoarder' AS URL, @@ -24915,7 +25015,7 @@ BEGIN cc.sum_max_length >= 2000) ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); - RAISERROR(N'check_id 27: Addicted to strings.', 0,1) WITH NOWAIT; + RAISERROR(N'check_id 27: High Ratio of Strings.', 0,1) WITH NOWAIT; WITH count_columns AS ( SELECT [object_id], [database_id], @@ -24933,8 +25033,8 @@ BEGIN SELECT 27 AS check_id, i.index_sanity_id, 200 AS Priority, - N'Index Hoarder' AS findings_group, - N'Addicted to strings' AS finding, + N'Over-Indexing' AS findings_group, + N'High Ratio of Strings' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/IndexHoarder' AS URL, i.db_schema_object_name @@ -24962,7 +25062,7 @@ BEGIN SELECT 28 AS check_id, i.index_sanity_id, 150 AS Priority, - N'Index Hoarder' AS findings_group, + N'Over-Indexing' AS findings_group, N'Non-Unique Clustered Index' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/IndexHoarder' AS URL, @@ -24988,13 +25088,13 @@ BEGIN AND is_CX_columnstore=0 /* not a clustered columnstore-- no unique option on those */ ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); - RAISERROR(N'check_id 29: NC indexes with 0 reads. (Borderline) and < 10,000 writes', 0,1) WITH NOWAIT; + RAISERROR(N'check_id 29: NC indexes with 0 reads and < 10,000 writes', 0,1) WITH NOWAIT; INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, secret_columns, index_usage_summary, index_size_summary ) SELECT 29 AS check_id, i.index_sanity_id, 150 AS Priority, - N'Index Hoarder' AS findings_group, + N'Over-Indexing' AS findings_group, N'Unused NC index with Low Writes' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/IndexHoarder' AS URL, @@ -25040,7 +25140,7 @@ BEGIN SELECT 30 AS check_id, NULL AS index_sanity_id, 250 AS Priority, - N'Feature-Phobic Indexes' AS findings_group, + N'Omitted Index Features' AS findings_group, database_name AS [Database Name], N'No Indexes Use Includes' AS finding, 'https://www.brentozar.com/go/IndexFeatures' AS URL, N'No Indexes Use Includes' AS details, @@ -25058,7 +25158,7 @@ BEGIN SELECT 31 AS check_id, NULL AS index_sanity_id, 250 AS Priority, - N'Feature-Phobic Indexes' AS findings_group, + N'Omitted Index Features' AS findings_group, N'Few Indexes Use Includes' AS findings, database_name AS [Database Name], N'https://www.brentozar.com/go/IndexFeatures' AS URL, @@ -25079,7 +25179,7 @@ BEGIN 32 AS check_id, NULL AS index_sanity_id, 250 AS Priority, - N'Feature-Phobic Indexes' AS findings_group, + N'Omitted Index Features' AS findings_group, N'No Filtered Indexes or Indexed Views' AS finding, i.database_name AS [Database Name], N'https://www.brentozar.com/go/IndexFeatures' AS URL, @@ -25105,7 +25205,7 @@ BEGIN SELECT 33 AS check_id, i.index_sanity_id AS index_sanity_id, 250 AS Priority, - N'Feature-Phobic Indexes' AS findings_group, + N'Omitted Index Features' AS findings_group, N'Potential Filtered Index (Based on Column Name)' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/IndexFeatures' AS URL, @@ -25133,7 +25233,7 @@ BEGIN SELECT 41 AS check_id, i.index_sanity_id, 150 AS Priority, - N'Self Loathing Indexes' AS findings_group, + N'Indexes Worth Reviewing' AS findings_group, N'Hypothetical Index' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/SelfLoathing' AS URL, @@ -25154,7 +25254,7 @@ BEGIN SELECT 42 AS check_id, index_sanity_id, 150 AS Priority, - N'Self Loathing Indexes' AS findings_group, + N'Indexes Worth Reviewing' AS findings_group, N'Disabled Index' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/SelfLoathing' AS URL, @@ -25184,7 +25284,7 @@ BEGIN SELECT 49 AS check_id, i.index_sanity_id, 200 AS Priority, - N'Self Loathing Indexes' AS findings_group, + N'Indexes Worth Reviewing' AS findings_group, N'Heaps with Deletes' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/SelfLoathing' AS URL, @@ -25212,7 +25312,7 @@ BEGIN SELECT 60 AS check_id, i.index_sanity_id, 150 AS Priority, - N'Abnormal Psychology' AS findings_group, + N'Abnormal Design Pattern' AS findings_group, N'XML Index' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, @@ -25232,7 +25332,7 @@ BEGIN SELECT 61 AS check_id, i.index_sanity_id, 150 AS Priority, - N'Abnormal Psychology' AS findings_group, + N'Abnormal Design Pattern' AS findings_group, CASE WHEN i.is_NC_columnstore=1 THEN N'NC Columnstore Index' ELSE N'Clustered Columnstore Index' @@ -25256,7 +25356,7 @@ BEGIN SELECT 62 AS check_id, i.index_sanity_id, 150 AS Priority, - N'Abnormal Psychology' AS findings_group, + N'Abnormal Design Pattern' AS findings_group, N'Spatial Index' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, @@ -25276,7 +25376,7 @@ BEGIN SELECT 63 AS check_id, i.index_sanity_id, 150 AS Priority, - N'Abnormal Psychology' AS findings_group, + N'Abnormal Design Pattern' AS findings_group, N'Compressed Index' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, @@ -25296,7 +25396,7 @@ BEGIN SELECT 64 AS check_id, i.index_sanity_id, 150 AS Priority, - N'Abnormal Psychology' AS findings_group, + N'Abnormal Design Pattern' AS findings_group, N'Partitioned Index' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, @@ -25316,7 +25416,7 @@ BEGIN SELECT 65 AS check_id, i.index_sanity_id, 150 AS Priority, - N'Abnormal Psychology' AS findings_group, + N'Abnormal Design Pattern' AS findings_group, N'Non-Aligned Index on a Partitioned Table' AS finding, i.[database_name] AS [Database Name], N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, @@ -25342,7 +25442,7 @@ BEGIN SELECT 66 AS check_id, i.index_sanity_id, 200 AS Priority, - N'Abnormal Psychology' AS findings_group, + N'Abnormal Design Pattern' AS findings_group, N'Recently Created Tables/Indexes (1 week)' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, @@ -25365,7 +25465,7 @@ BEGIN SELECT 67 AS check_id, i.index_sanity_id, 200 AS Priority, - N'Abnormal Psychology' AS findings_group, + N'Abnormal Design Pattern' AS findings_group, N'Recently Modified Tables/Indexes (2 days)' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, @@ -25402,7 +25502,7 @@ BEGIN SELECT 69 AS check_id, i.index_sanity_id, 150 AS Priority, - N'Abnormal Psychology' AS findings_group, + N'Abnormal Design Pattern' AS findings_group, N'Column Collation Does Not Match Database Collation' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, @@ -25441,7 +25541,7 @@ BEGIN SELECT 70 AS check_id, i.index_sanity_id, 200 AS Priority, - N'Abnormal Psychology' AS findings_group, + N'Abnormal Design Pattern' AS findings_group, N'Replicated Columns' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, @@ -25471,7 +25571,7 @@ BEGIN SELECT 71 AS check_id, NULL AS index_sanity_id, 150 AS Priority, - N'Abnormal Psychology' AS findings_group, + N'Abnormal Design Pattern' AS findings_group, N'Cascading Updates or Deletes' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, @@ -25499,7 +25599,7 @@ BEGIN SELECT 72 AS check_id, NULL AS index_sanity_id, 150 AS Priority, - N'Abnormal Psychology' AS findings_group, + N'Abnormal Design Pattern' AS findings_group, N'Unindexed Foreign Keys' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, @@ -25523,7 +25623,7 @@ BEGIN SELECT 73 AS check_id, i.index_sanity_id, 150 AS Priority, - N'Abnormal Psychology' AS findings_group, + N'Abnormal Design Pattern' AS findings_group, N'In-Memory OLTP' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, @@ -25543,7 +25643,7 @@ BEGIN SELECT 74 AS check_id, i.index_sanity_id, 200 AS Priority, - N'Abnormal Psychology' AS findings_group, + N'Abnormal Design Pattern' AS findings_group, N'Identity Column Using a Negative Seed or Increment Other Than 1' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, @@ -25595,7 +25695,7 @@ BEGIN 80 AS check_id, i.index_sanity_id AS index_sanity_id, 200 AS Priority, - N'Workaholics' AS findings_group, + N'High Workloads' AS findings_group, N'Scan-a-lots (index-usage-stats)' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/Workaholics' AS URL, @@ -25623,7 +25723,7 @@ BEGIN 81 AS check_id, i.index_sanity_id AS index_sanity_id, 200 AS Priority, - N'Workaholics' AS findings_group, + N'High Workloads' AS findings_group, N'Top Recent Accesses (index-op-stats)' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/Workaholics' AS URL, @@ -25651,8 +25751,8 @@ BEGIN secret_columns, index_usage_summary, index_size_summary ) SELECT 93 AS check_id, 200 AS Priority, - 'Functioning Statistaholics' AS findings_group, - 'Filter Fixation', + 'Statistics Warnings' AS findings_group, + 'Statistics With Filters', s.database_name, 'https://www.brentozar.com/go/stats' AS URL, 'The statistic ' + QUOTENAME(s.statistics_name) + ' is filtered on [' + s.filter_definition + ']. It could be part of a filtered index, or just a filtered statistic. This is purely informational.' AS details, @@ -25670,8 +25770,8 @@ BEGIN secret_columns, index_usage_summary, index_size_summary ) SELECT 100 AS check_id, 200 AS Priority, - 'Cold Calculators' AS findings_group, - 'Definition Defeatists' AS finding, + 'Repeated Calculations' AS findings_group, + 'Computed Columns Not Persisted' AS finding, cc.database_name, '' AS URL, 'The computed column ' + QUOTENAME(cc.column_name) + ' on ' + QUOTENAME(cc.schema_name) + '.' + QUOTENAME(cc.table_name) + ' is not persisted, which means it will be calculated when a query runs.' + @@ -25691,7 +25791,7 @@ BEGIN SELECT 110 AS check_id, 200 AS Priority, - 'Abnormal Psychology' AS findings_group, + 'Abnormal Design Pattern' AS findings_group, 'Temporal Tables', t.database_name, '' AS URL, @@ -25711,7 +25811,7 @@ BEGIN SELECT 121 AS check_id, 200 AS Priority, - 'Medicated Indexes' AS findings_group, + 'Specialized Indexes' AS findings_group, 'Optimized For Sequential Keys', i.database_name, '' AS URL, @@ -26965,7 +27065,7 @@ BEGIN SET XACT_ABORT OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.21', @VersionDate = '20240701'; + SELECT @Version = '8.22', @VersionDate = '20241019'; IF @VersionCheckMode = 1 BEGIN @@ -30603,8 +30703,9 @@ BEGIN SET STATISTICS XML ON; END; - INSERT INTO - DeadLockTbl + SET @StringToExecute = N' + + INSERT INTO ' + QUOTENAME(DB_NAME()) + N'..DeadLockTbl ( ServerName, deadlock_type, @@ -30648,7 +30749,8 @@ BEGIN deadlock_graph ) EXEC sys.sp_executesql - @deadlock_result; + @deadlock_result;' + EXEC sys.sp_executesql @StringToExecute, N'@deadlock_result NVARCHAR(MAX)', @deadlock_result; IF @Debug = 1 BEGIN @@ -30662,8 +30764,9 @@ BEGIN SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Findings to table %s', 0, 1, @d) WITH NOWAIT; - INSERT INTO - DeadlockFindings + SET @StringToExecute = N' + + INSERT INTO ' + QUOTENAME(DB_NAME()) + N'..DeadlockFindings ( ServerName, check_id, @@ -30681,7 +30784,8 @@ BEGIN df.finding FROM #deadlock_findings AS df ORDER BY df.check_id - OPTION(RECOMPILE); + OPTION(RECOMPILE);' + EXEC sys.sp_executesql @StringToExecute; RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; @@ -30977,17 +31081,23 @@ BEGIN FROM @sysAssObjId AS s OPTION(RECOMPILE); + IF OBJECT_ID('tempdb..#available_plans') IS NOT NULL + BEGIN SELECT table_name = N'#available_plans', * FROM #available_plans AS ap OPTION(RECOMPILE); + END; + IF OBJECT_ID('tempdb..#dm_exec_query_stats') IS NOT NULL + BEGIN SELECT table_name = N'#dm_exec_query_stats', * FROM #dm_exec_query_stats OPTION(RECOMPILE); + END; SELECT procedure_parameters = @@ -31120,7 +31230,7 @@ BEGIN SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.21', @VersionDate = '20240701'; + SELECT @Version = '8.22', @VersionDate = '20241019'; IF(@VersionCheckMode = 1) BEGIN @@ -32533,7 +32643,7 @@ SET STATISTICS XML OFF; /*Versioning details*/ -SELECT @Version = '8.21', @VersionDate = '20240701'; +SELECT @Version = '8.22', @VersionDate = '20241019'; IF(@VersionCheckMode = 1) BEGIN @@ -33165,13 +33275,15 @@ BEGIN END; -- Find latest full backup - SELECT @LastFullBackup = MAX(BackupFile) + -- Get the TOP record to use in "Restore HeaderOnly/FileListOnly" statement as well as Non-Split Backups Restore Command + SELECT TOP 1 @LastFullBackup = BackupFile, @CurrentBackupPathFull = BackupPath FROM @FileList WHERE BackupFile LIKE N'%.bak' AND BackupFile LIKE N'%' + @Database + N'%' AND - (@StopAt IS NULL OR REPLACE( RIGHT( REPLACE( @LastFullBackup, RIGHT( @LastFullBackup, PATINDEX( '%_[0-9][0-9]%', REVERSE( @LastFullBackup ) ) ), '' ), 16 ), '_', '' ) <= @StopAt); + (@StopAt IS NULL OR REPLACE( RIGHT( REPLACE( BackupFile, RIGHT( BackupFile, PATINDEX( '%_[0-9][0-9]%', REVERSE( BackupFile ) ) ), '' ), 16 ), '_', '' ) <= @StopAt) + ORDER BY BackupFile DESC; /* To get all backups that belong to the same set we can do two things: 1. RESTORE HEADERONLY of ALL backup files in the folder and look for BackupSetGUID. @@ -33211,11 +33323,6 @@ BEGIN SET @FileListParamSQL += N')' + NCHAR(13) + NCHAR(10); SET @FileListParamSQL += N'EXEC (''RESTORE FILELISTONLY FROM DISK=''''{Path}'''''')'; - -- get the TOP record to use in "Restore HeaderOnly/FileListOnly" statement as well as Non-Split Backups Restore Command - SELECT TOP 1 @CurrentBackupPathFull = BackupPath, @LastFullBackup = BackupFile - FROM @FileList - ORDER BY REPLACE( RIGHT( REPLACE( BackupFile, RIGHT( BackupFile, PATINDEX( '%_[0-9][0-9]%', REVERSE( BackupFile ) ) ), '' ), 16 ), '_', '' ) DESC; - SET @sql = REPLACE(@FileListParamSQL, N'{Path}', @CurrentBackupPathFull + @LastFullBackup); IF @Debug = 1 @@ -34205,7 +34312,7 @@ BEGIN SET NOCOUNT ON; SET STATISTICS XML OFF; - SELECT @Version = '8.21', @VersionDate = '20240701'; + SELECT @Version = '8.22', @VersionDate = '20241019'; IF(@VersionCheckMode = 1) BEGIN @@ -34567,6 +34674,11 @@ DELETE FROM dbo.SqlServerVersions; INSERT INTO dbo.SqlServerVersions (MajorVersionNumber, MinorVersionNumber, Branch, [Url], ReleaseDate, MainstreamSupportEndDate, ExtendedSupportEndDate, MajorVersionName, MinorVersionName) VALUES + /*2022*/ + (16, 4150, 'CU15 GDR', 'https://support.microsoft.com/en-us/help/5046059', '2024-10-08', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 15 GDR'), + (16, 4145, 'CU15', 'https://support.microsoft.com/en-us/help/5041321', '2024-09-25', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 15'), + (16, 4140, 'CU14 GDR', 'https://support.microsoft.com/en-us/help/5042578', '2024-09-10', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 14 GDR'), + (16, 4135, 'CU14', 'https://support.microsoft.com/en-us/help/5038325', '2024-07-23', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 14'), (16, 4125, 'CU13', 'https://support.microsoft.com/en-us/help/5036432', '2024-05-16', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 13'), (16, 4115, 'CU12', 'https://support.microsoft.com/en-us/help/5033663', '2024-03-14', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 12'), (16, 4105, 'CU11', 'https://support.microsoft.com/en-us/help/5032679', '2024-01-11', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 11'), @@ -34583,6 +34695,10 @@ VALUES (16, 4003, 'CU1', 'https://support.microsoft.com/en-us/help/5022375', '2023-02-16', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 1'), (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, 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'), + (15, 4390, 'CU28 GDR', 'https://support.microsoft.com/kb/5042749', '2024-09-10', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 28 GDR'), + (15, 4385, 'CU28', 'https://support.microsoft.com/kb/5039747', '2024-08-01', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 28'), (15, 4375, 'CU27', 'https://support.microsoft.com/kb/5037331', '2024-06-14', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 27'), (15, 4365, 'CU26', 'https://support.microsoft.com/kb/5035123', '2024-04-11', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 26'), (15, 4355, 'CU25', 'https://support.microsoft.com/kb/5033688', '2024-02-15', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 25'), @@ -34615,7 +34731,11 @@ VALUES (15, 4003, 'CU1', 'https://support.microsoft.com/en-us/help/4527376', '2020-01-07', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 1 '), (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 '), - (14, 3465, 'RTM CU31 GDR', 'https://support.microsoft.com/kb/5029376', '2023-10-12', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 31 GDR'), + /*2017*/ + (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'), + (14, 3471, 'RTM CU31 GDR', 'https://support.microsoft.com/kb/5040940', '2024-07-09', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 31 GDR'), + (14, 3465, 'RTM CU31 GDR', 'https://support.microsoft.com/kb/5029376', '2023-10-10', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 31 GDR'), (14, 3460, 'RTM CU31 GDR', 'https://support.microsoft.com/kb/5021126', '2023-02-14', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 31 GDR'), (14, 3456, 'RTM CU31', 'https://support.microsoft.com/en-us/help/5016884', '2022-09-20', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 31'), (14, 3451, 'RTM CU30', 'https://support.microsoft.com/en-us/help/5013756', '2022-07-13', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 30'), @@ -34651,8 +34771,18 @@ VALUES (14, 3008, 'RTM CU2', 'https://support.microsoft.com/en-us/help/4052574', '2017-11-28', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 2'), (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, 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'), + (13, 7029, 'SP3 Azure Feature Pack GDR', 'https://support.microsoft.com/en-us/help/5029187', '2023-10-10', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3 Azure Feature Pack GDR'), + (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, 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'), + (13, 6441, 'SP3 GDR', 'https://support.microsoft.com/kb/5040946', '2024-07-09', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3 GDR'), + (13, 6435, 'SP3 GDR', 'https://support.microsoft.com/kb/5029186', '2023-10-10', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3 GDR'), (13, 6430, 'SP3 GDR', 'https://support.microsoft.com/kb/5021129', '2023-02-14', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3 GDR'), (13, 6419, 'SP3 GDR', 'https://support.microsoft.com/en-us/help/5014355', '2022-06-14', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3 GDR'), (13, 6404, 'SP3 GDR', 'https://support.microsoft.com/en-us/help/5006943', '2021-10-27', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3 GDR'), @@ -34705,6 +34835,8 @@ VALUES (13, 2164, 'RTM CU2', 'https://support.microsoft.com/en-us/help/3182270 ', '2016-09-22', '2018-01-09', '2018-01-09', 'SQL Server 2016', 'RTM Cumulative Update 2'), (13, 2149, 'RTM CU1', 'https://support.microsoft.com/en-us/help/3164674 ', '2016-07-25', '2018-01-09', '2018-01-09', 'SQL Server 2016', 'RTM Cumulative Update 1'), (13, 1601, 'RTM ', '', '2016-06-01', '2019-01-09', '2019-01-09', 'SQL Server 2016', 'RTM '), + /*2014*/ + (12, 6449, 'SP3 CU4 GDR', 'https://support.microsoft.com/kb/5029185', '2023-10-12', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 4 GDR'), (12, 6444, 'SP3 CU4 GDR', 'https://support.microsoft.com/kb/5021045', '2023-02-14', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 4 GDR'), (12, 6439, 'SP3 CU4 GDR', 'https://support.microsoft.com/en-us/help/5014164', '2022-06-14', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 4 GDR'), (12, 6433, 'SP3 CU4 GDR', 'https://support.microsoft.com/en-us/help/4583462', '2021-01-12', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 4 GDR'), @@ -34769,6 +34901,8 @@ VALUES (12, 2269, 'RTM MS15-058: GDR Security Update ', 'https://support.microsoft.com/en-us/help/3045324', '2015-07-14', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM MS15-058: GDR Security Update '), (12, 2254, 'RTM MS14-044: GDR Security Update', 'https://support.microsoft.com/en-us/help/2977315', '2014-08-12', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM MS14-044: GDR Security Update'), (12, 2000, 'RTM ', '', '2014-04-01', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM '), + /*2012*/ + (11, 7512, 'SP4 GDR Security Update', 'https://support.microsoft.com/en-us/help/5021123', '2023-02-16', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'Service Pack 4 GDR Security Update for CVE-2021-1636'), (11, 7507, 'SP4 GDR Security Update', 'https://support.microsoft.com/en-us/help/4583465', '2021-01-12', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'Service Pack 4 GDR Security Update for CVE-2021-1636'), (11, 7493, 'SP4 GDR Security Update', 'https://support.microsoft.com/en-us/help/4532098', '2020-02-11', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'Service Pack 4 GDR Security Update for CVE-2020-0618'), (11, 7469, 'SP4 On-Demand Hotfix Update', 'https://support.microsoft.com/en-us/help/4091266', '2018-03-28', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'Service Pack 4 SP4 On-Demand Hotfix Update'), @@ -34836,6 +34970,7 @@ VALUES (11, 2316, 'RTM CU1', 'https://support.microsoft.com/en-us/help/2679368', '2012-04-12', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'RTM Cumulative Update 1'), (11, 2218, 'RTM MS12-070: GDR Security Update', 'https://support.microsoft.com/en-us/help/2716442', '2012-10-09', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'RTM MS12-070: GDR Security Update'), (11, 2100, 'RTM ', '', '2012-03-06', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'RTM '), + /*2008 R2*/ (10, 6529, 'SP3 MS15-058: QFE Security Update', 'https://support.microsoft.com/en-us/help/3045314', '2015-07-14', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'Service Pack 3 MS15-058: QFE Security Update'), (10, 6220, 'SP3 MS15-058: QFE Security Update', 'https://support.microsoft.com/en-us/help/3045316', '2015-07-14', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'Service Pack 3 MS15-058: QFE Security Update'), (10, 6000, 'SP3 ', 'https://support.microsoft.com/en-us/help/2979597', '2014-09-26', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'Service Pack 3 '), @@ -34890,6 +35025,7 @@ VALUES (10, 1702, 'RTM CU1', 'https://support.microsoft.com/en-us/help/981355', '2010-05-18', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM Cumulative Update 1'), (10, 1617, 'RTM MS11-049: GDR Security Update', 'https://support.microsoft.com/en-us/help/2494088', '2011-06-14', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM MS11-049: GDR Security Update'), (10, 1600, 'RTM ', '', '2010-05-10', '2014-07-08', '2019-07-09', 'SQL Server 2008 R2', 'RTM '), + /*2008*/ (10, 6535, 'SP3 MS15-058: QFE Security Update', 'https://support.microsoft.com/en-us/help/3045308', '2015-07-14', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 MS15-058: QFE Security Update'), (10, 6241, 'SP3 MS15-058: GDR Security Update', 'https://support.microsoft.com/en-us/help/3045311', '2015-07-14', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 MS15-058: GDR Security Update'), (10, 5890, 'SP3 MS15-058: QFE Security Update', 'https://support.microsoft.com/en-us/help/3045303', '2015-07-14', '2015-10-13', '2015-10-13', 'SQL Server 2008', 'Service Pack 3 MS15-058: QFE Security Update'), @@ -35013,7 +35149,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.21', @VersionDate = '20240701'; +SELECT @Version = '8.22', @VersionDate = '20241019'; IF(@VersionCheckMode = 1) BEGIN diff --git a/Install-Azure.sql b/Install-Azure.sql index 41de92e6d..049b9cc17 100644 --- a/Install-Azure.sql +++ b/Install-Azure.sql @@ -37,7 +37,7 @@ AS SET NOCOUNT ON; SET STATISTICS XML OFF; -SELECT @Version = '8.21', @VersionDate = '20240701'; +SELECT @Version = '8.22', @VersionDate = '20241019'; IF(@VersionCheckMode = 1) BEGIN @@ -1172,7 +1172,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.21', @VersionDate = '20240701'; +SELECT @Version = '8.22', @VersionDate = '20241019'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -8545,7 +8545,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.21', @VersionDate = '20240701'; +SELECT @Version = '8.22', @VersionDate = '20241019'; IF(@VersionCheckMode = 1) BEGIN @@ -13554,7 +13554,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.21', @VersionDate = '20240701'; +SELECT @Version = '8.22', @VersionDate = '20241019'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -15385,6 +15385,7 @@ WITH ON ty.user_type_id = co.user_type_id WHERE id_inner.index_handle = id.index_handle AND id_inner.object_id = id.object_id + AND id_inner.database_id = DB_ID(''' + QUOTENAME(@DatabaseName) + N''') AND cn_inner.IndexColumnType = cn.IndexColumnType FOR XML PATH('''') ), @@ -15422,6 +15423,7 @@ WITH ) x (n) CROSS APPLY n.nodes(''x'') node(v) )AS cn + WHERE id.database_id = DB_ID(''' + QUOTENAME(@DatabaseName) + N''') GROUP BY id.index_handle, id.object_id, @@ -17004,7 +17006,7 @@ BEGIN SELECT 1 AS check_id, ip.index_sanity_id, 20 AS Priority, - 'Multiple Index Personalities' AS findings_group, + 'Redundant Indexes' AS findings_group, 'Duplicate keys' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/duplicateindex' AS URL, @@ -17041,8 +17043,8 @@ BEGIN SELECT 2 AS check_id, ip.index_sanity_id, 30 AS Priority, - 'Multiple Index Personalities' AS findings_group, - 'Borderline duplicate keys' AS finding, + 'Redundant Indexes' AS findings_group, + 'Approximate Duplicate Keys' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/duplicateindex' AS URL, ip.db_schema_object_indexid AS details, @@ -17075,7 +17077,7 @@ BEGIN SELECT 11 AS check_id, i.index_sanity_id, 70 AS Priority, - N'Aggressive ' + N'Locking-Prone ' + CASE COALESCE((SELECT SUM(1) FROM #IndexSanity iMe INNER JOIN #IndexSanity iOthers @@ -17136,7 +17138,7 @@ BEGIN SELECT 20 AS check_id, MAX(i.index_sanity_id) AS index_sanity_id, 10 AS Priority, - 'Index Hoarder' AS findings_group, + 'Over-Indexing' AS findings_group, 'Many NC Indexes on a Single Table' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/IndexHoarder' AS URL, @@ -17160,13 +17162,13 @@ BEGIN ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); - RAISERROR(N'check_id 22: NC indexes with 0 reads. (Borderline) and >= 10,000 writes', 0,1) WITH NOWAIT; + RAISERROR(N'check_id 22: NC indexes with 0 reads and >= 10,000 writes', 0,1) WITH NOWAIT; INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, secret_columns, index_usage_summary, index_size_summary ) SELECT 22 AS check_id, i.index_sanity_id, 10 AS Priority, - N'Index Hoarder' AS findings_group, + N'Over-Indexing' AS findings_group, N'Unused NC Index with High Writes' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/IndexHoarder' AS URL, @@ -17199,7 +17201,7 @@ BEGIN SELECT 34 AS check_id, i.index_sanity_id, 80 AS Priority, - N'Abnormal Psychology' AS findings_group, + N'Abnormal Design Pattern' AS findings_group, N'Filter Columns Not In Index Definition' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/IndexFeatures' AS URL, @@ -17233,7 +17235,7 @@ BEGIN SELECT 40 AS check_id, i.index_sanity_id, 100 AS Priority, - N'Self Loathing Indexes' AS findings_group, + N'Indexes Worth Reviewing' AS findings_group, N'Low Fill Factor on Nonclustered Index' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/SelfLoathing' AS URL, @@ -17260,7 +17262,7 @@ BEGIN SELECT 40 AS check_id, i.index_sanity_id, 100 AS Priority, - N'Self Loathing Indexes' AS findings_group, + N'Indexes Worth Reviewing' AS findings_group, N'Low Fill Factor on Clustered Index' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/SelfLoathing' AS URL, @@ -17299,7 +17301,7 @@ BEGIN SELECT 43 AS check_id, i.index_sanity_id, 100 AS Priority, - N'Self Loathing Indexes' AS findings_group, + N'Indexes Worth Reviewing' AS findings_group, N'Heaps with Forwarded Fetches' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/SelfLoathing' AS URL, @@ -17342,7 +17344,7 @@ BEGIN SELECT 44 AS check_id, i.index_sanity_id, 100 AS Priority, - N'Self Loathing Indexes' AS findings_group, + N'Indexes Worth Reviewing' AS findings_group, N'Large Active Heap' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/SelfLoathing' AS URL, @@ -17380,7 +17382,7 @@ BEGIN SELECT 45 AS check_id, i.index_sanity_id, 100 AS Priority, - N'Self Loathing Indexes' AS findings_group, + N'Indexes Worth Reviewing' AS findings_group, N'Medium Active heap' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/SelfLoathing' AS URL, @@ -17419,7 +17421,7 @@ BEGIN SELECT 46 AS check_id, i.index_sanity_id, 100 AS Priority, - N'Self Loathing Indexes' AS findings_group, + N'Indexes Worth Reviewing' AS findings_group, N'Small Active heap' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/SelfLoathing' AS URL, @@ -17446,7 +17448,7 @@ BEGIN SELECT 47 AS check_id, i.index_sanity_id, 100 AS Priority, - N'Self Loathing Indexes' AS findings_group, + N'Indexes Worth Reviewing' AS findings_group, N'Heap with a Nonclustered Primary Key' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/SelfLoathing' AS URL, @@ -17474,7 +17476,7 @@ BEGIN SELECT 48 AS check_id, i.index_sanity_id, 100 AS Priority, - N'Index Hoarder' AS findings_group, + N'Over-Indexing' AS findings_group, N'NC index with High Writes:Reads' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/IndexHoarder' AS URL, @@ -17504,7 +17506,7 @@ BEGIN --Indexaphobia --Missing indexes with value >= 5 million: : Check_id 50-59 ---------------------------------------- - RAISERROR(N'check_id 50: Indexaphobia.', 0,1) WITH NOWAIT; + RAISERROR(N'check_id 50: High Value Missing Index.', 0,1) WITH NOWAIT; WITH index_size_cte AS ( SELECT i.database_id, i.schema_name, @@ -17542,7 +17544,7 @@ BEGIN 50 AS check_id, sz.index_sanity_id, 40 AS Priority, - N'Indexaphobia' AS findings_group, + N'Index Suggestion' AS findings_group, N'High Value Missing Index' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/Indexaphobia' AS URL, @@ -17587,7 +17589,7 @@ BEGIN SELECT 68 AS check_id, i.index_sanity_id, 80 AS Priority, - N'Abnormal Psychology' AS findings_group, + N'Abnormal Design Pattern' AS findings_group, N'Identity Column Within ' + CAST (calc1.percent_remaining AS NVARCHAR(256)) + N' Percent End of Range' AS finding, @@ -17652,7 +17654,7 @@ BEGIN SELECT 72 AS check_id, i.index_sanity_id, 80 AS Priority, - N'Abnormal Psychology' AS findings_group, + N'Abnormal Design Pattern' AS findings_group, 'Columnstore Indexes with Trace Flag 834' AS finding, [database_name] AS [Database Name], N'https://support.microsoft.com/en-us/kb/3210239' AS URL, @@ -17676,7 +17678,7 @@ BEGIN secret_columns, index_usage_summary, index_size_summary ) SELECT 90 AS check_id, 90 AS Priority, - 'Functioning Statistaholics' AS findings_group, + 'Statistics Warnings' AS findings_group, 'Statistics Not Updated Recently', s.database_name, 'https://www.brentozar.com/go/stats' AS URL, @@ -17703,7 +17705,7 @@ BEGIN secret_columns, index_usage_summary, index_size_summary ) SELECT 91 AS check_id, 90 AS Priority, - 'Functioning Statistaholics' AS findings_group, + 'Statistics Warnings' AS findings_group, 'Low Sampling Rates', s.database_name, 'https://www.brentozar.com/go/stats' AS URL, @@ -17722,7 +17724,7 @@ BEGIN secret_columns, index_usage_summary, index_size_summary ) SELECT 92 AS check_id, 90 AS Priority, - 'Functioning Statistaholics' AS findings_group, + 'Statistics Warnings' AS findings_group, 'Statistics With NO RECOMPUTE', s.database_name, 'https://www.brentozar.com/go/stats' AS URL, @@ -17741,7 +17743,7 @@ BEGIN secret_columns, index_usage_summary, index_size_summary ) SELECT 94 AS check_id, 100 AS Priority, - 'Serial Forcer' AS findings_group, + 'Forced Serialization' AS findings_group, 'Check Constraint with Scalar UDF' AS finding, cc.database_name, 'https://www.brentozar.com/go/computedscalar' AS URL, @@ -17760,7 +17762,7 @@ BEGIN secret_columns, index_usage_summary, index_size_summary ) SELECT 99 AS check_id, 100 AS Priority, - 'Serial Forcer' AS findings_group, + 'Forced Serialization' AS findings_group, 'Computed Column with Scalar UDF' AS finding, cc.database_name, 'https://www.brentozar.com/go/serialudf' AS URL, @@ -17817,7 +17819,7 @@ BEGIN SELECT 21 AS check_id, MAX(i.index_sanity_id) AS index_sanity_id, 150 AS Priority, - N'Index Hoarder' AS findings_group, + N'Over-Indexing' AS findings_group, N'More Than 5 Percent NC Indexes Are Unused' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/IndexHoarder' AS URL, @@ -17852,8 +17854,8 @@ BEGIN SELECT 23 AS check_id, i.index_sanity_id, 150 AS Priority, - N'Index Hoarder' AS findings_group, - N'Borderline: Wide Indexes (7 or More Columns)' AS finding, + N'Over-Indexing' AS findings_group, + N'Approximate: Wide Indexes (7 or More Columns)' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/IndexHoarder' AS URL, CAST(count_key_columns + count_included_columns AS NVARCHAR(10)) + ' columns on ' @@ -17880,7 +17882,7 @@ BEGIN SELECT 24 AS check_id, i.index_sanity_id, 150 AS Priority, - N'Index Hoarder' AS findings_group, + N'Over-Indexing' AS findings_group, N'Wide Clustered Index (> 3 columns OR > 16 bytes)' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/IndexHoarder' AS URL, @@ -17912,7 +17914,7 @@ BEGIN AND i.is_CX_columnstore = 0 ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); - RAISERROR(N'check_id 25: Addicted to nullable columns.', 0,1) WITH NOWAIT; + RAISERROR(N'check_id 25: High ratio of nullable columns.', 0,1) WITH NOWAIT; WITH count_columns AS ( SELECT [object_id], [database_id], @@ -17930,8 +17932,8 @@ BEGIN SELECT 25 AS check_id, i.index_sanity_id, 200 AS Priority, - N'Index Hoarder' AS findings_group, - N'Addicted to Nulls' AS finding, + N'Over-Indexing' AS findings_group, + N'High Ratio of Nulls' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/IndexHoarder' AS URL, i.db_schema_object_name @@ -17971,7 +17973,7 @@ BEGIN SELECT 26 AS check_id, i.index_sanity_id, 150 AS Priority, - N'Index Hoarder' AS findings_group, + N'Over-Indexing' AS findings_group, N'Wide Tables: 35+ cols or > 2000 non-LOB bytes' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/IndexHoarder' AS URL, @@ -17998,7 +18000,7 @@ BEGIN cc.sum_max_length >= 2000) ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); - RAISERROR(N'check_id 27: Addicted to strings.', 0,1) WITH NOWAIT; + RAISERROR(N'check_id 27: High Ratio of Strings.', 0,1) WITH NOWAIT; WITH count_columns AS ( SELECT [object_id], [database_id], @@ -18016,8 +18018,8 @@ BEGIN SELECT 27 AS check_id, i.index_sanity_id, 200 AS Priority, - N'Index Hoarder' AS findings_group, - N'Addicted to strings' AS finding, + N'Over-Indexing' AS findings_group, + N'High Ratio of Strings' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/IndexHoarder' AS URL, i.db_schema_object_name @@ -18045,7 +18047,7 @@ BEGIN SELECT 28 AS check_id, i.index_sanity_id, 150 AS Priority, - N'Index Hoarder' AS findings_group, + N'Over-Indexing' AS findings_group, N'Non-Unique Clustered Index' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/IndexHoarder' AS URL, @@ -18071,13 +18073,13 @@ BEGIN AND is_CX_columnstore=0 /* not a clustered columnstore-- no unique option on those */ ORDER BY i.db_schema_object_name DESC OPTION ( RECOMPILE ); - RAISERROR(N'check_id 29: NC indexes with 0 reads. (Borderline) and < 10,000 writes', 0,1) WITH NOWAIT; + RAISERROR(N'check_id 29: NC indexes with 0 reads and < 10,000 writes', 0,1) WITH NOWAIT; INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, secret_columns, index_usage_summary, index_size_summary ) SELECT 29 AS check_id, i.index_sanity_id, 150 AS Priority, - N'Index Hoarder' AS findings_group, + N'Over-Indexing' AS findings_group, N'Unused NC index with Low Writes' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/IndexHoarder' AS URL, @@ -18123,7 +18125,7 @@ BEGIN SELECT 30 AS check_id, NULL AS index_sanity_id, 250 AS Priority, - N'Feature-Phobic Indexes' AS findings_group, + N'Omitted Index Features' AS findings_group, database_name AS [Database Name], N'No Indexes Use Includes' AS finding, 'https://www.brentozar.com/go/IndexFeatures' AS URL, N'No Indexes Use Includes' AS details, @@ -18141,7 +18143,7 @@ BEGIN SELECT 31 AS check_id, NULL AS index_sanity_id, 250 AS Priority, - N'Feature-Phobic Indexes' AS findings_group, + N'Omitted Index Features' AS findings_group, N'Few Indexes Use Includes' AS findings, database_name AS [Database Name], N'https://www.brentozar.com/go/IndexFeatures' AS URL, @@ -18162,7 +18164,7 @@ BEGIN 32 AS check_id, NULL AS index_sanity_id, 250 AS Priority, - N'Feature-Phobic Indexes' AS findings_group, + N'Omitted Index Features' AS findings_group, N'No Filtered Indexes or Indexed Views' AS finding, i.database_name AS [Database Name], N'https://www.brentozar.com/go/IndexFeatures' AS URL, @@ -18188,7 +18190,7 @@ BEGIN SELECT 33 AS check_id, i.index_sanity_id AS index_sanity_id, 250 AS Priority, - N'Feature-Phobic Indexes' AS findings_group, + N'Omitted Index Features' AS findings_group, N'Potential Filtered Index (Based on Column Name)' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/IndexFeatures' AS URL, @@ -18216,7 +18218,7 @@ BEGIN SELECT 41 AS check_id, i.index_sanity_id, 150 AS Priority, - N'Self Loathing Indexes' AS findings_group, + N'Indexes Worth Reviewing' AS findings_group, N'Hypothetical Index' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/SelfLoathing' AS URL, @@ -18237,7 +18239,7 @@ BEGIN SELECT 42 AS check_id, index_sanity_id, 150 AS Priority, - N'Self Loathing Indexes' AS findings_group, + N'Indexes Worth Reviewing' AS findings_group, N'Disabled Index' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/SelfLoathing' AS URL, @@ -18267,7 +18269,7 @@ BEGIN SELECT 49 AS check_id, i.index_sanity_id, 200 AS Priority, - N'Self Loathing Indexes' AS findings_group, + N'Indexes Worth Reviewing' AS findings_group, N'Heaps with Deletes' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/SelfLoathing' AS URL, @@ -18295,7 +18297,7 @@ BEGIN SELECT 60 AS check_id, i.index_sanity_id, 150 AS Priority, - N'Abnormal Psychology' AS findings_group, + N'Abnormal Design Pattern' AS findings_group, N'XML Index' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, @@ -18315,7 +18317,7 @@ BEGIN SELECT 61 AS check_id, i.index_sanity_id, 150 AS Priority, - N'Abnormal Psychology' AS findings_group, + N'Abnormal Design Pattern' AS findings_group, CASE WHEN i.is_NC_columnstore=1 THEN N'NC Columnstore Index' ELSE N'Clustered Columnstore Index' @@ -18339,7 +18341,7 @@ BEGIN SELECT 62 AS check_id, i.index_sanity_id, 150 AS Priority, - N'Abnormal Psychology' AS findings_group, + N'Abnormal Design Pattern' AS findings_group, N'Spatial Index' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, @@ -18359,7 +18361,7 @@ BEGIN SELECT 63 AS check_id, i.index_sanity_id, 150 AS Priority, - N'Abnormal Psychology' AS findings_group, + N'Abnormal Design Pattern' AS findings_group, N'Compressed Index' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, @@ -18379,7 +18381,7 @@ BEGIN SELECT 64 AS check_id, i.index_sanity_id, 150 AS Priority, - N'Abnormal Psychology' AS findings_group, + N'Abnormal Design Pattern' AS findings_group, N'Partitioned Index' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, @@ -18399,7 +18401,7 @@ BEGIN SELECT 65 AS check_id, i.index_sanity_id, 150 AS Priority, - N'Abnormal Psychology' AS findings_group, + N'Abnormal Design Pattern' AS findings_group, N'Non-Aligned Index on a Partitioned Table' AS finding, i.[database_name] AS [Database Name], N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, @@ -18425,7 +18427,7 @@ BEGIN SELECT 66 AS check_id, i.index_sanity_id, 200 AS Priority, - N'Abnormal Psychology' AS findings_group, + N'Abnormal Design Pattern' AS findings_group, N'Recently Created Tables/Indexes (1 week)' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, @@ -18448,7 +18450,7 @@ BEGIN SELECT 67 AS check_id, i.index_sanity_id, 200 AS Priority, - N'Abnormal Psychology' AS findings_group, + N'Abnormal Design Pattern' AS findings_group, N'Recently Modified Tables/Indexes (2 days)' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, @@ -18485,7 +18487,7 @@ BEGIN SELECT 69 AS check_id, i.index_sanity_id, 150 AS Priority, - N'Abnormal Psychology' AS findings_group, + N'Abnormal Design Pattern' AS findings_group, N'Column Collation Does Not Match Database Collation' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, @@ -18524,7 +18526,7 @@ BEGIN SELECT 70 AS check_id, i.index_sanity_id, 200 AS Priority, - N'Abnormal Psychology' AS findings_group, + N'Abnormal Design Pattern' AS findings_group, N'Replicated Columns' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, @@ -18554,7 +18556,7 @@ BEGIN SELECT 71 AS check_id, NULL AS index_sanity_id, 150 AS Priority, - N'Abnormal Psychology' AS findings_group, + N'Abnormal Design Pattern' AS findings_group, N'Cascading Updates or Deletes' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, @@ -18582,7 +18584,7 @@ BEGIN SELECT 72 AS check_id, NULL AS index_sanity_id, 150 AS Priority, - N'Abnormal Psychology' AS findings_group, + N'Abnormal Design Pattern' AS findings_group, N'Unindexed Foreign Keys' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, @@ -18606,7 +18608,7 @@ BEGIN SELECT 73 AS check_id, i.index_sanity_id, 150 AS Priority, - N'Abnormal Psychology' AS findings_group, + N'Abnormal Design Pattern' AS findings_group, N'In-Memory OLTP' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, @@ -18626,7 +18628,7 @@ BEGIN SELECT 74 AS check_id, i.index_sanity_id, 200 AS Priority, - N'Abnormal Psychology' AS findings_group, + N'Abnormal Design Pattern' AS findings_group, N'Identity Column Using a Negative Seed or Increment Other Than 1' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/AbnormalPsychology' AS URL, @@ -18678,7 +18680,7 @@ BEGIN 80 AS check_id, i.index_sanity_id AS index_sanity_id, 200 AS Priority, - N'Workaholics' AS findings_group, + N'High Workloads' AS findings_group, N'Scan-a-lots (index-usage-stats)' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/Workaholics' AS URL, @@ -18706,7 +18708,7 @@ BEGIN 81 AS check_id, i.index_sanity_id AS index_sanity_id, 200 AS Priority, - N'Workaholics' AS findings_group, + N'High Workloads' AS findings_group, N'Top Recent Accesses (index-op-stats)' AS finding, [database_name] AS [Database Name], N'https://www.brentozar.com/go/Workaholics' AS URL, @@ -18734,8 +18736,8 @@ BEGIN secret_columns, index_usage_summary, index_size_summary ) SELECT 93 AS check_id, 200 AS Priority, - 'Functioning Statistaholics' AS findings_group, - 'Filter Fixation', + 'Statistics Warnings' AS findings_group, + 'Statistics With Filters', s.database_name, 'https://www.brentozar.com/go/stats' AS URL, 'The statistic ' + QUOTENAME(s.statistics_name) + ' is filtered on [' + s.filter_definition + ']. It could be part of a filtered index, or just a filtered statistic. This is purely informational.' AS details, @@ -18753,8 +18755,8 @@ BEGIN secret_columns, index_usage_summary, index_size_summary ) SELECT 100 AS check_id, 200 AS Priority, - 'Cold Calculators' AS findings_group, - 'Definition Defeatists' AS finding, + 'Repeated Calculations' AS findings_group, + 'Computed Columns Not Persisted' AS finding, cc.database_name, '' AS URL, 'The computed column ' + QUOTENAME(cc.column_name) + ' on ' + QUOTENAME(cc.schema_name) + '.' + QUOTENAME(cc.table_name) + ' is not persisted, which means it will be calculated when a query runs.' + @@ -18774,7 +18776,7 @@ BEGIN SELECT 110 AS check_id, 200 AS Priority, - 'Abnormal Psychology' AS findings_group, + 'Abnormal Design Pattern' AS findings_group, 'Temporal Tables', t.database_name, '' AS URL, @@ -18794,7 +18796,7 @@ BEGIN SELECT 121 AS check_id, 200 AS Priority, - 'Medicated Indexes' AS findings_group, + 'Specialized Indexes' AS findings_group, 'Optimized For Sequential Keys', i.database_name, '' AS URL, @@ -20048,7 +20050,7 @@ BEGIN SET XACT_ABORT OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.21', @VersionDate = '20240701'; + SELECT @Version = '8.22', @VersionDate = '20241019'; IF @VersionCheckMode = 1 BEGIN @@ -23686,8 +23688,9 @@ BEGIN SET STATISTICS XML ON; END; - INSERT INTO - DeadLockTbl + SET @StringToExecute = N' + + INSERT INTO ' + QUOTENAME(DB_NAME()) + N'..DeadLockTbl ( ServerName, deadlock_type, @@ -23731,7 +23734,8 @@ BEGIN deadlock_graph ) EXEC sys.sp_executesql - @deadlock_result; + @deadlock_result;' + EXEC sys.sp_executesql @StringToExecute, N'@deadlock_result NVARCHAR(MAX)', @deadlock_result; IF @Debug = 1 BEGIN @@ -23745,8 +23749,9 @@ BEGIN SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Findings to table %s', 0, 1, @d) WITH NOWAIT; - INSERT INTO - DeadlockFindings + SET @StringToExecute = N' + + INSERT INTO ' + QUOTENAME(DB_NAME()) + N'..DeadlockFindings ( ServerName, check_id, @@ -23764,7 +23769,8 @@ BEGIN df.finding FROM #deadlock_findings AS df ORDER BY df.check_id - OPTION(RECOMPILE); + OPTION(RECOMPILE);' + EXEC sys.sp_executesql @StringToExecute; RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; @@ -24060,17 +24066,23 @@ BEGIN FROM @sysAssObjId AS s OPTION(RECOMPILE); + IF OBJECT_ID('tempdb..#available_plans') IS NOT NULL + BEGIN SELECT table_name = N'#available_plans', * FROM #available_plans AS ap OPTION(RECOMPILE); + END; + IF OBJECT_ID('tempdb..#dm_exec_query_stats') IS NOT NULL + BEGIN SELECT table_name = N'#dm_exec_query_stats', * FROM #dm_exec_query_stats OPTION(RECOMPILE); + END; SELECT procedure_parameters = @@ -24203,7 +24215,7 @@ BEGIN SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.21', @VersionDate = '20240701'; + SELECT @Version = '8.22', @VersionDate = '20241019'; IF(@VersionCheckMode = 1) BEGIN diff --git a/SqlServerVersions.sql b/SqlServerVersions.sql index b8dc0837e..c47f87942 100644 --- a/SqlServerVersions.sql +++ b/SqlServerVersions.sql @@ -42,6 +42,8 @@ INSERT INTO dbo.SqlServerVersions (MajorVersionNumber, MinorVersionNumber, Branch, [Url], ReleaseDate, MainstreamSupportEndDate, ExtendedSupportEndDate, MajorVersionName, MinorVersionName) VALUES /*2022*/ + (16, 4150, 'CU15 GDR', 'https://support.microsoft.com/en-us/help/5046059', '2024-10-08', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 15 GDR'), + (16, 4145, 'CU15', 'https://support.microsoft.com/en-us/help/5041321', '2024-09-25', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 15'), (16, 4140, 'CU14 GDR', 'https://support.microsoft.com/en-us/help/5042578', '2024-09-10', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 14 GDR'), (16, 4135, 'CU14', 'https://support.microsoft.com/en-us/help/5038325', '2024-07-23', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 14'), (16, 4125, 'CU13', 'https://support.microsoft.com/en-us/help/5036432', '2024-05-16', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 13'), @@ -61,6 +63,7 @@ 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, 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'), (15, 4390, 'CU28 GDR', 'https://support.microsoft.com/kb/5042749', '2024-09-10', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 28 GDR'), (15, 4385, 'CU28', 'https://support.microsoft.com/kb/5039747', '2024-08-01', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 28'), (15, 4375, 'CU27', 'https://support.microsoft.com/kb/5037331', '2024-06-14', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 27'), @@ -96,6 +99,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, 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'), (14, 3471, 'RTM CU31 GDR', 'https://support.microsoft.com/kb/5040940', '2024-07-09', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 31 GDR'), (14, 3465, 'RTM CU31 GDR', 'https://support.microsoft.com/kb/5029376', '2023-10-10', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 31 GDR'), @@ -135,12 +139,14 @@ 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, 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'), (13, 7029, 'SP3 Azure Feature Pack GDR', 'https://support.microsoft.com/en-us/help/5029187', '2023-10-10', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3 Azure Feature Pack GDR'), (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, 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'), (13, 6441, 'SP3 GDR', 'https://support.microsoft.com/kb/5040946', '2024-07-09', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3 GDR'), (13, 6435, 'SP3 GDR', 'https://support.microsoft.com/kb/5029186', '2023-10-10', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3 GDR'), @@ -197,6 +203,7 @@ VALUES (13, 2149, 'RTM CU1', 'https://support.microsoft.com/en-us/help/3164674 ', '2016-07-25', '2018-01-09', '2018-01-09', 'SQL Server 2016', 'RTM Cumulative Update 1'), (13, 1601, 'RTM ', '', '2016-06-01', '2019-01-09', '2019-01-09', 'SQL Server 2016', 'RTM '), /*2014*/ + (12, 6449, 'SP3 CU4 GDR', 'https://support.microsoft.com/kb/5029185', '2023-10-12', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 4 GDR'), (12, 6444, 'SP3 CU4 GDR', 'https://support.microsoft.com/kb/5021045', '2023-02-14', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 4 GDR'), (12, 6439, 'SP3 CU4 GDR', 'https://support.microsoft.com/en-us/help/5014164', '2022-06-14', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 4 GDR'), (12, 6433, 'SP3 CU4 GDR', 'https://support.microsoft.com/en-us/help/4583462', '2021-01-12', '2019-07-09', '2024-07-09', 'SQL Server 2014', 'Service Pack 3 Cumulative Update 4 GDR'), @@ -262,6 +269,7 @@ VALUES (12, 2254, 'RTM MS14-044: GDR Security Update', 'https://support.microsoft.com/en-us/help/2977315', '2014-08-12', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM MS14-044: GDR Security Update'), (12, 2000, 'RTM ', '', '2014-04-01', '2016-07-12', '2016-07-12', 'SQL Server 2014', 'RTM '), /*2012*/ + (11, 7512, 'SP4 GDR Security Update', 'https://support.microsoft.com/en-us/help/5021123', '2023-02-16', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'Service Pack 4 GDR Security Update for CVE-2021-1636'), (11, 7507, 'SP4 GDR Security Update', 'https://support.microsoft.com/en-us/help/4583465', '2021-01-12', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'Service Pack 4 GDR Security Update for CVE-2021-1636'), (11, 7493, 'SP4 GDR Security Update', 'https://support.microsoft.com/en-us/help/4532098', '2020-02-11', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'Service Pack 4 GDR Security Update for CVE-2020-0618'), (11, 7469, 'SP4 On-Demand Hotfix Update', 'https://support.microsoft.com/en-us/help/4091266', '2018-03-28', '2017-07-11', '2022-07-12', 'SQL Server 2012', 'Service Pack 4 SP4 On-Demand Hotfix Update'), diff --git a/sp_Blitz.sql b/sp_Blitz.sql index 8cd63185b..9f7df17db 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -38,7 +38,7 @@ AS SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.21', @VersionDate = '20240701'; + SELECT @Version = '8.22', @VersionDate = '20241019'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) diff --git a/sp_BlitzAnalysis.sql b/sp_BlitzAnalysis.sql index 0976fcb1e..7c2358393 100644 --- a/sp_BlitzAnalysis.sql +++ b/sp_BlitzAnalysis.sql @@ -37,7 +37,7 @@ AS SET NOCOUNT ON; SET STATISTICS XML OFF; -SELECT @Version = '8.21', @VersionDate = '20240701'; +SELECT @Version = '8.22', @VersionDate = '20241019'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_BlitzBackups.sql b/sp_BlitzBackups.sql index 1b54148a1..c7a44df6e 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.21', @VersionDate = '20240701'; + SELECT @Version = '8.22', @VersionDate = '20241019'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_BlitzCache.sql b/sp_BlitzCache.sql index 5f5d9f8be..188a80476 100644 --- a/sp_BlitzCache.sql +++ b/sp_BlitzCache.sql @@ -281,7 +281,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.21', @VersionDate = '20240701'; +SELECT @Version = '8.22', @VersionDate = '20241019'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) diff --git a/sp_BlitzFirst.sql b/sp_BlitzFirst.sql index 94539e13a..8cfc9f27c 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.21', @VersionDate = '20240701'; +SELECT @Version = '8.22', @VersionDate = '20241019'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index d2bcff05c..32e5e70a4 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -48,7 +48,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.21', @VersionDate = '20240701'; +SELECT @Version = '8.22', @VersionDate = '20241019'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index 9f6d7ff3a..3b36017de 100644 --- a/sp_BlitzLock.sql +++ b/sp_BlitzLock.sql @@ -37,7 +37,7 @@ BEGIN SET XACT_ABORT OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.21', @VersionDate = '20240701'; + SELECT @Version = '8.22', @VersionDate = '20241019'; IF @VersionCheckMode = 1 BEGIN diff --git a/sp_BlitzWho.sql b/sp_BlitzWho.sql index 01e2fdd60..7f57e8af5 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.21', @VersionDate = '20240701'; + SELECT @Version = '8.22', @VersionDate = '20241019'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_DatabaseRestore.sql b/sp_DatabaseRestore.sql index 3d987dfa3..f85be751a 100755 --- a/sp_DatabaseRestore.sql +++ b/sp_DatabaseRestore.sql @@ -58,7 +58,7 @@ SET STATISTICS XML OFF; /*Versioning details*/ -SELECT @Version = '8.21', @VersionDate = '20240701'; +SELECT @Version = '8.22', @VersionDate = '20241019'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_ineachdb.sql b/sp_ineachdb.sql index ded778d56..c213cbd4e 100644 --- a/sp_ineachdb.sql +++ b/sp_ineachdb.sql @@ -36,7 +36,7 @@ BEGIN SET NOCOUNT ON; SET STATISTICS XML OFF; - SELECT @Version = '8.21', @VersionDate = '20240701'; + SELECT @Version = '8.22', @VersionDate = '20241019'; IF(@VersionCheckMode = 1) BEGIN From 9bac87496715ad84ede69b5f9d295377cd59cd45 Mon Sep 17 00:00:00 2001 From: Goran Schwarz Date: Wed, 23 Oct 2024 23:08:30 +0200 Subject: [PATCH 597/662] When your lazy and do not want to fill in all parameters: @DatabaseName, @SchemaName and @TableName Now you can simply do: EXEC sp_BlitzIndex @ObjectName = 'schema.tablename' Or if you're really lazy do: EXEC sp_BlitzIndex 'schema.tablename' --- sp_BlitzIndex.sql | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index 32e5e70a4..747e42cf5 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -13,6 +13,7 @@ IF OBJECT_ID('dbo.sp_BlitzIndex') IS NULL GO ALTER PROCEDURE dbo.sp_BlitzIndex + @ObjectName NVARCHAR(386) = NULL, /* 'dbname.schema.table' -- if you are lazy and want to fill in @DatabaseName, @SchemaName and @TableName, and since it's the first parameter can simply do: sp_BlitzIndex 'sch.table' */ @DatabaseName NVARCHAR(128) = NULL, /*Defaults to current DB if not specified*/ @SchemaName NVARCHAR(128) = NULL, /*Requires table_name as well.*/ @TableName NVARCHAR(128) = NULL, /*Requires schema_name as well.*/ @@ -132,6 +133,11 @@ DECLARE @PartitionCount INT; DECLARE @OptimizeForSequentialKey BIT = 0; DECLARE @StringToExecute NVARCHAR(MAX); +/* If user was lazy and just used @ObjectName with a fully qualified table name, then lets parse out the various parts */ +SET @DatabaseName = COALESCE(@DatabaseName, PARSENAME(@ObjectName, 3)) /* 3 = Database name */ +SET @SchemaName = COALESCE(@SchemaName, PARSENAME(@ObjectName, 2)) /* 2 = Schema name */ +SET @TableName = COALESCE(@TableName, PARSENAME(@ObjectName, 1)) /* 1 = Table name */ + /* Let's get @SortOrder set to lower case here for comparisons later */ SET @SortOrder = REPLACE(LOWER(@SortOrder), N' ', N'_'); From c9b929e67a25c1df771c902389953871757a937c Mon Sep 17 00:00:00 2001 From: Peter Date: Thu, 24 Oct 2024 19:11:40 +0100 Subject: [PATCH 598/662] Update data type of reserved MB fields in [#dm_db_partition_stats_etc] to match similar fields in other temp tables. BlitzIndex will now report "size" for indexes less than 1 MB. --- sp_BlitzIndex.sql | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index 747e42cf5..e74a830c1 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -1462,9 +1462,9 @@ BEGIN TRY , partition_number int , partition_id bigint , row_count bigint - , reserved_MB bigint - , reserved_LOB_MB bigint - , reserved_row_overflow_MB bigint + , reserved_MB NUMERIC(29,2) + , reserved_LOB_MB NUMERIC(29,2) + , reserved_row_overflow_MB NUMERIC(29,2) , lock_escalation_desc nvarchar(60) , data_compression_desc nvarchar(60) ) From c25cce4217801d5791b14f7fe5e19e29e1bce51b Mon Sep 17 00:00:00 2001 From: VincenzoMarchese <99604774+VincenzoMarchese@users.noreply.github.com> Date: Fri, 25 Oct 2024 12:12:22 +0200 Subject: [PATCH 599/662] Update sp_BlitzIndex.sql The select from the #BlitzIndexResults table is present 2 times in the same block --- sp_BlitzIndex.sql | 1 - 1 file changed, 1 deletion(-) diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index 747e42cf5..84a809dd0 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -2960,7 +2960,6 @@ BEGIN SELECT '#MissingIndexes' AS table_name, * FROM #MissingIndexes; SELECT '#ForeignKeys' AS table_name, * FROM #ForeignKeys; SELECT '#UnindexedForeignKeys' AS table_name, * FROM #UnindexedForeignKeys; - SELECT '#BlitzIndexResults' AS table_name, * FROM #BlitzIndexResults; SELECT '#IndexCreateTsql' AS table_name, * FROM #IndexCreateTsql; SELECT '#DatabaseList' AS table_name, * FROM #DatabaseList; SELECT '#Statistics' AS table_name, * FROM #Statistics; From 4da4830904023bfcccf535b70651e9ce8d44a252 Mon Sep 17 00:00:00 2001 From: Igor Galiney <60065267+igaliney@users.noreply.github.com> Date: Thu, 5 Dec 2024 11:05:10 +0200 Subject: [PATCH 600/662] Replace illegal character & in sp_Blitz for Markdown output result The issue is caused by an illegal character (&) in the description of the Finding column for CheckID = 162. To resolve this issue, update the description to replace & with and, ensuring XML compatibility. --- sp_Blitz.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index 9f7df17db..ca0963bf9 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -8617,7 +8617,7 @@ IF @ProductVersionMajor >= 10 SELECT 162 AS CheckID , 50 AS Priority , 'Performance' AS FindingGroup , - 'Poison Wait Detected: CMEMTHREAD & NUMA' AS Finding , + 'Poison Wait Detected: CMEMTHREAD and NUMA' AS Finding , 'https://www.brentozar.com/go/poison' AS URL , CONVERT(VARCHAR(10), (MAX([wait_time_ms]) / 1000) / 86400) + ':' + CONVERT(VARCHAR(20), DATEADD(s, (MAX([wait_time_ms]) / 1000), 0), 108) + ' of this wait have been recorded' + CASE WHEN ts.status = 1 THEN ' despite enabling trace flag 8048 already.' From 57f34db3d45fd0bd345fdc9e88e13f1f49442d47 Mon Sep 17 00:00:00 2001 From: BrentOzar Date: Sun, 8 Dec 2024 18:13:09 -0800 Subject: [PATCH 601/662] #3586 - adding target & total memory counters to server info section. Closes #3586. --- Documentation/sp_Blitz_Checks_by_Priority.md | 5 ++-- sp_Blitz.sql | 30 ++++++++++++++++++++ 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/Documentation/sp_Blitz_Checks_by_Priority.md b/Documentation/sp_Blitz_Checks_by_Priority.md index 0458b8c7e..5c12ec039 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: 265. -If you want to add a new one, start at 266. +CURRENT HIGH CHECKID: 266. +If you want to add a new one, start at 267. | Priority | FindingsGroup | Finding | URL | CheckID | |----------|-----------------------------|---------------------------------------------------------|------------------------------------------------------------------------|----------| @@ -301,6 +301,7 @@ If you want to add a new one, start at 266. | 250 | Server Info | Drive Space | | 92 | | 250 | Server Info | Full-text Filter Daemon is Currently Offline | | 168 | | 250 | Server Info | Hardware | | 84 | +| 250 | Server Info | Hardware - Memory Counters | https://www.BrentOzar.com/go/target | 266 | | 250 | Server Info | Hardware - NUMA Config | | 114 | | 250 | Server Info | Instant File Initialization Enabled | https://www.BrentOzar.com/go/instant | 193 | | 250 | Server Info | Locked Pages in Memory Enabled | https://www.BrentOzar.com/go/lpim | 166 | diff --git a/sp_Blitz.sql b/sp_Blitz.sql index ca0963bf9..f1afb0014 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -9970,6 +9970,36 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 END; END; /* CheckID 261 */ + + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 266 ) + BEGIN + INSERT INTO #BlitzResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT 266 AS CheckID , + 250 AS Priority , + 'Server Info' AS FindingsGroup , + 'Hardware - Memory Counters' AS Finding , + 'https://www.brentozar.com/go/target' AS URL , + N'Target Server Memory (GB): ' + CAST((CAST((pTarget.cntr_value / 1024.0 / 1024.0) AS DECIMAL(10,1))) AS NVARCHAR(100)) + + N' Total Server Memory (GB): ' + CAST((CAST((pTotal.cntr_value / 1024.0 / 1024.0) AS DECIMAL(10,1))) AS NVARCHAR(100)) + FROM sys.dm_os_performance_counters pTarget + INNER JOIN sys.dm_os_performance_counters pTotal + ON pTotal.object_name LIKE 'SQLServer:Memory Manager%' + AND pTotal.counter_name LIKE 'Total Server Memory (KB)%' + WHERE pTarget.object_name LIKE 'SQLServer:Memory Manager%' + AND pTarget.counter_name LIKE 'Target Server Memory (KB)%' + END + + + END; /* IF @CheckServerInfo = 1 */ END; /* IF ( ( SERVERPROPERTY('ServerName') NOT IN ( SELECT ServerName */ From 33eb49310a4f8bfbccd9619d4c669516ba9d811c Mon Sep 17 00:00:00 2001 From: Tisit Date: Mon, 9 Dec 2024 23:14:21 +0100 Subject: [PATCH 602/662] sp_Blitz - correctly check permissions for sys.traces --- sp_Blitz.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index f1afb0014..f52dd28bc 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -287,8 +287,8 @@ AS ( SELECT 1/0 - FROM fn_my_permissions(N'sys.traces', N'OBJECT') AS fmp - WHERE fmp.permission_name = N'ALTER' + FROM fn_my_permissions(NULL, NULL) AS fmp + WHERE fmp.permission_name = N'ALTER TRACE' ) BEGIN SET @SkipTrace = 1; From c6e082606b33f137a3995a0d6540ccc6b8060ad6 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Sat, 28 Dec 2024 02:54:53 -0800 Subject: [PATCH 603/662] #3599 sp_Blitz overflow Really, really big data files would overflow an NVARCHAR(10). Closes #3599. --- sp_Blitz.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index f52dd28bc..05ee61dcd 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -7049,7 +7049,7 @@ IF @ProductVersionMajor >= 10 ''File Configuration'' AS FindingsGroup, ''File growth set to percent'', ''https://www.brentozar.com/go/percentgrowth'' AS URL, - ''The ['' + DB_NAME() + ''] database file '' + f.physical_name + '' has grown to '' + CONVERT(NVARCHAR(10), CONVERT(NUMERIC(38, 2), (f.size / 128.) / 1024.)) + '' GB, and is using percent filegrowth settings. This can lead to slow performance during growths if Instant File Initialization is not enabled.'' + ''The ['' + DB_NAME() + ''] database file '' + f.physical_name + '' has grown to '' + CONVERT(NVARCHAR(20), CONVERT(NUMERIC(38, 2), (f.size / 128.) / 1024.)) + '' GB, and is using percent filegrowth settings. This can lead to slow performance during growths if Instant File Initialization is not enabled.'' FROM [?].sys.database_files f WHERE is_percent_growth = 1 and size > 128000 OPTION (RECOMPILE);'; END; From 09c1136b406f5d468663fb4981f90e813992bb48 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Sat, 28 Dec 2024 03:35:18 -0800 Subject: [PATCH 604/662] 2024-12-28 release Bumping dates and version numbers. --- Install-All-Scripts.sql | 76 +++++++++++++++++++++++++++++++---------- Install-Azure.sql | 25 ++++++++------ SqlServerVersions.sql | 5 +++ 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, 88 insertions(+), 38 deletions(-) diff --git a/Install-All-Scripts.sql b/Install-All-Scripts.sql index e074f4866..b9465696d 100644 --- a/Install-All-Scripts.sql +++ b/Install-All-Scripts.sql @@ -38,7 +38,7 @@ AS SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.22', @VersionDate = '20241019'; + SELECT @Version = '8.23', @VersionDate = '20241228'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -287,8 +287,8 @@ AS ( SELECT 1/0 - FROM fn_my_permissions(N'sys.traces', N'OBJECT') AS fmp - WHERE fmp.permission_name = N'ALTER' + FROM fn_my_permissions(NULL, NULL) AS fmp + WHERE fmp.permission_name = N'ALTER TRACE' ) BEGIN SET @SkipTrace = 1; @@ -7049,7 +7049,7 @@ IF @ProductVersionMajor >= 10 ''File Configuration'' AS FindingsGroup, ''File growth set to percent'', ''https://www.brentozar.com/go/percentgrowth'' AS URL, - ''The ['' + DB_NAME() + ''] database file '' + f.physical_name + '' has grown to '' + CONVERT(NVARCHAR(10), CONVERT(NUMERIC(38, 2), (f.size / 128.) / 1024.)) + '' GB, and is using percent filegrowth settings. This can lead to slow performance during growths if Instant File Initialization is not enabled.'' + ''The ['' + DB_NAME() + ''] database file '' + f.physical_name + '' has grown to '' + CONVERT(NVARCHAR(20), CONVERT(NUMERIC(38, 2), (f.size / 128.) / 1024.)) + '' GB, and is using percent filegrowth settings. This can lead to slow performance during growths if Instant File Initialization is not enabled.'' FROM [?].sys.database_files f WHERE is_percent_growth = 1 and size > 128000 OPTION (RECOMPILE);'; END; @@ -8617,7 +8617,7 @@ IF @ProductVersionMajor >= 10 SELECT 162 AS CheckID , 50 AS Priority , 'Performance' AS FindingGroup , - 'Poison Wait Detected: CMEMTHREAD & NUMA' AS Finding , + 'Poison Wait Detected: CMEMTHREAD and NUMA' AS Finding , 'https://www.brentozar.com/go/poison' AS URL , CONVERT(VARCHAR(10), (MAX([wait_time_ms]) / 1000) / 86400) + ':' + CONVERT(VARCHAR(20), DATEADD(s, (MAX([wait_time_ms]) / 1000), 0), 108) + ' of this wait have been recorded' + CASE WHEN ts.status = 1 THEN ' despite enabling trace flag 8048 already.' @@ -9970,6 +9970,36 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 END; END; /* CheckID 261 */ + + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 266 ) + BEGIN + INSERT INTO #BlitzResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT 266 AS CheckID , + 250 AS Priority , + 'Server Info' AS FindingsGroup , + 'Hardware - Memory Counters' AS Finding , + 'https://www.brentozar.com/go/target' AS URL , + N'Target Server Memory (GB): ' + CAST((CAST((pTarget.cntr_value / 1024.0 / 1024.0) AS DECIMAL(10,1))) AS NVARCHAR(100)) + + N' Total Server Memory (GB): ' + CAST((CAST((pTotal.cntr_value / 1024.0 / 1024.0) AS DECIMAL(10,1))) AS NVARCHAR(100)) + FROM sys.dm_os_performance_counters pTarget + INNER JOIN sys.dm_os_performance_counters pTotal + ON pTotal.object_name LIKE 'SQLServer:Memory Manager%' + AND pTotal.counter_name LIKE 'Total Server Memory (KB)%' + WHERE pTarget.object_name LIKE 'SQLServer:Memory Manager%' + AND pTarget.counter_name LIKE 'Target Server Memory (KB)%' + END + + + END; /* IF @CheckServerInfo = 1 */ END; /* IF ( ( SERVERPROPERTY('ServerName') NOT IN ( SELECT ServerName */ @@ -10535,7 +10565,7 @@ AS SET NOCOUNT ON; SET STATISTICS XML OFF; -SELECT @Version = '8.22', @VersionDate = '20241019'; +SELECT @Version = '8.23', @VersionDate = '20241228'; IF(@VersionCheckMode = 1) BEGIN @@ -11413,7 +11443,7 @@ AS SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.22', @VersionDate = '20241019'; + SELECT @Version = '8.23', @VersionDate = '20241228'; IF(@VersionCheckMode = 1) BEGIN @@ -13195,7 +13225,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.22', @VersionDate = '20241019'; +SELECT @Version = '8.23', @VersionDate = '20241228'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -20534,6 +20564,7 @@ IF OBJECT_ID('dbo.sp_BlitzIndex') IS NULL GO ALTER PROCEDURE dbo.sp_BlitzIndex + @ObjectName NVARCHAR(386) = NULL, /* 'dbname.schema.table' -- if you are lazy and want to fill in @DatabaseName, @SchemaName and @TableName, and since it's the first parameter can simply do: sp_BlitzIndex 'sch.table' */ @DatabaseName NVARCHAR(128) = NULL, /*Defaults to current DB if not specified*/ @SchemaName NVARCHAR(128) = NULL, /*Requires table_name as well.*/ @TableName NVARCHAR(128) = NULL, /*Requires schema_name as well.*/ @@ -20569,7 +20600,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.22', @VersionDate = '20241019'; +SELECT @Version = '8.23', @VersionDate = '20241228'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -20653,6 +20684,11 @@ DECLARE @PartitionCount INT; DECLARE @OptimizeForSequentialKey BIT = 0; DECLARE @StringToExecute NVARCHAR(MAX); +/* If user was lazy and just used @ObjectName with a fully qualified table name, then lets parse out the various parts */ +SET @DatabaseName = COALESCE(@DatabaseName, PARSENAME(@ObjectName, 3)) /* 3 = Database name */ +SET @SchemaName = COALESCE(@SchemaName, PARSENAME(@ObjectName, 2)) /* 2 = Schema name */ +SET @TableName = COALESCE(@TableName, PARSENAME(@ObjectName, 1)) /* 1 = Table name */ + /* Let's get @SortOrder set to lower case here for comparisons later */ SET @SortOrder = REPLACE(LOWER(@SortOrder), N' ', N'_'); @@ -21977,9 +22013,9 @@ BEGIN TRY , partition_number int , partition_id bigint , row_count bigint - , reserved_MB bigint - , reserved_LOB_MB bigint - , reserved_row_overflow_MB bigint + , reserved_MB NUMERIC(29,2) + , reserved_LOB_MB NUMERIC(29,2) + , reserved_row_overflow_MB NUMERIC(29,2) , lock_escalation_desc nvarchar(60) , data_compression_desc nvarchar(60) ) @@ -23475,7 +23511,6 @@ BEGIN SELECT '#MissingIndexes' AS table_name, * FROM #MissingIndexes; SELECT '#ForeignKeys' AS table_name, * FROM #ForeignKeys; SELECT '#UnindexedForeignKeys' AS table_name, * FROM #UnindexedForeignKeys; - SELECT '#BlitzIndexResults' AS table_name, * FROM #BlitzIndexResults; SELECT '#IndexCreateTsql' AS table_name, * FROM #IndexCreateTsql; SELECT '#DatabaseList' AS table_name, * FROM #DatabaseList; SELECT '#Statistics' AS table_name, * FROM #Statistics; @@ -27065,7 +27100,7 @@ BEGIN SET XACT_ABORT OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.22', @VersionDate = '20241019'; + SELECT @Version = '8.23', @VersionDate = '20241228'; IF @VersionCheckMode = 1 BEGIN @@ -31230,7 +31265,7 @@ BEGIN SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.22', @VersionDate = '20241019'; + SELECT @Version = '8.23', @VersionDate = '20241228'; IF(@VersionCheckMode = 1) BEGIN @@ -32643,7 +32678,7 @@ SET STATISTICS XML OFF; /*Versioning details*/ -SELECT @Version = '8.22', @VersionDate = '20241019'; +SELECT @Version = '8.23', @VersionDate = '20241228'; IF(@VersionCheckMode = 1) BEGIN @@ -34312,7 +34347,7 @@ BEGIN SET NOCOUNT ON; SET STATISTICS XML OFF; - SELECT @Version = '8.22', @VersionDate = '20241019'; + SELECT @Version = '8.23', @VersionDate = '20241228'; IF(@VersionCheckMode = 1) BEGIN @@ -34675,6 +34710,7 @@ INSERT INTO dbo.SqlServerVersions (MajorVersionNumber, MinorVersionNumber, Branch, [Url], ReleaseDate, MainstreamSupportEndDate, ExtendedSupportEndDate, MajorVersionName, MinorVersionName) VALUES /*2022*/ + (16, 4165, 'CU16', 'https://support.microsoft.com/en-us/help/5048033', '2024-11-14', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 16'), (16, 4150, 'CU15 GDR', 'https://support.microsoft.com/en-us/help/5046059', '2024-10-08', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 15 GDR'), (16, 4145, 'CU15', 'https://support.microsoft.com/en-us/help/5041321', '2024-09-25', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 15'), (16, 4140, 'CU14 GDR', 'https://support.microsoft.com/en-us/help/5042578', '2024-09-10', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 14 GDR'), @@ -34696,6 +34732,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, 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'), (15, 4390, 'CU28 GDR', 'https://support.microsoft.com/kb/5042749', '2024-09-10', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 28 GDR'), (15, 4385, 'CU28', 'https://support.microsoft.com/kb/5039747', '2024-08-01', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 28'), @@ -34732,6 +34770,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, 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'), (14, 3471, 'RTM CU31 GDR', 'https://support.microsoft.com/kb/5040940', '2024-07-09', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 31 GDR'), @@ -34779,6 +34818,7 @@ 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, 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'), (13, 6441, 'SP3 GDR', 'https://support.microsoft.com/kb/5040946', '2024-07-09', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3 GDR'), @@ -35149,7 +35189,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.22', @VersionDate = '20241019'; +SELECT @Version = '8.23', @VersionDate = '20241228'; IF(@VersionCheckMode = 1) BEGIN diff --git a/Install-Azure.sql b/Install-Azure.sql index 049b9cc17..0ca1ddc29 100644 --- a/Install-Azure.sql +++ b/Install-Azure.sql @@ -37,7 +37,7 @@ AS SET NOCOUNT ON; SET STATISTICS XML OFF; -SELECT @Version = '8.22', @VersionDate = '20241019'; +SELECT @Version = '8.23', @VersionDate = '20241228'; IF(@VersionCheckMode = 1) BEGIN @@ -1172,7 +1172,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.22', @VersionDate = '20241019'; +SELECT @Version = '8.23', @VersionDate = '20241228'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -8545,7 +8545,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.22', @VersionDate = '20241019'; +SELECT @Version = '8.23', @VersionDate = '20241228'; IF(@VersionCheckMode = 1) BEGIN @@ -13519,6 +13519,7 @@ IF OBJECT_ID('dbo.sp_BlitzIndex') IS NULL GO ALTER PROCEDURE dbo.sp_BlitzIndex + @ObjectName NVARCHAR(386) = NULL, /* 'dbname.schema.table' -- if you are lazy and want to fill in @DatabaseName, @SchemaName and @TableName, and since it's the first parameter can simply do: sp_BlitzIndex 'sch.table' */ @DatabaseName NVARCHAR(128) = NULL, /*Defaults to current DB if not specified*/ @SchemaName NVARCHAR(128) = NULL, /*Requires table_name as well.*/ @TableName NVARCHAR(128) = NULL, /*Requires schema_name as well.*/ @@ -13554,7 +13555,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.22', @VersionDate = '20241019'; +SELECT @Version = '8.23', @VersionDate = '20241228'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -13638,6 +13639,11 @@ DECLARE @PartitionCount INT; DECLARE @OptimizeForSequentialKey BIT = 0; DECLARE @StringToExecute NVARCHAR(MAX); +/* If user was lazy and just used @ObjectName with a fully qualified table name, then lets parse out the various parts */ +SET @DatabaseName = COALESCE(@DatabaseName, PARSENAME(@ObjectName, 3)) /* 3 = Database name */ +SET @SchemaName = COALESCE(@SchemaName, PARSENAME(@ObjectName, 2)) /* 2 = Schema name */ +SET @TableName = COALESCE(@TableName, PARSENAME(@ObjectName, 1)) /* 1 = Table name */ + /* Let's get @SortOrder set to lower case here for comparisons later */ SET @SortOrder = REPLACE(LOWER(@SortOrder), N' ', N'_'); @@ -14962,9 +14968,9 @@ BEGIN TRY , partition_number int , partition_id bigint , row_count bigint - , reserved_MB bigint - , reserved_LOB_MB bigint - , reserved_row_overflow_MB bigint + , reserved_MB NUMERIC(29,2) + , reserved_LOB_MB NUMERIC(29,2) + , reserved_row_overflow_MB NUMERIC(29,2) , lock_escalation_desc nvarchar(60) , data_compression_desc nvarchar(60) ) @@ -16460,7 +16466,6 @@ BEGIN SELECT '#MissingIndexes' AS table_name, * FROM #MissingIndexes; SELECT '#ForeignKeys' AS table_name, * FROM #ForeignKeys; SELECT '#UnindexedForeignKeys' AS table_name, * FROM #UnindexedForeignKeys; - SELECT '#BlitzIndexResults' AS table_name, * FROM #BlitzIndexResults; SELECT '#IndexCreateTsql' AS table_name, * FROM #IndexCreateTsql; SELECT '#DatabaseList' AS table_name, * FROM #DatabaseList; SELECT '#Statistics' AS table_name, * FROM #Statistics; @@ -20050,7 +20055,7 @@ BEGIN SET XACT_ABORT OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.22', @VersionDate = '20241019'; + SELECT @Version = '8.23', @VersionDate = '20241228'; IF @VersionCheckMode = 1 BEGIN @@ -24215,7 +24220,7 @@ BEGIN SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.22', @VersionDate = '20241019'; + SELECT @Version = '8.23', @VersionDate = '20241228'; IF(@VersionCheckMode = 1) BEGIN diff --git a/SqlServerVersions.sql b/SqlServerVersions.sql index c47f87942..01c4d63af 100644 --- a/SqlServerVersions.sql +++ b/SqlServerVersions.sql @@ -42,6 +42,7 @@ INSERT INTO dbo.SqlServerVersions (MajorVersionNumber, MinorVersionNumber, Branch, [Url], ReleaseDate, MainstreamSupportEndDate, ExtendedSupportEndDate, MajorVersionName, MinorVersionName) VALUES /*2022*/ + (16, 4165, 'CU16', 'https://support.microsoft.com/en-us/help/5048033', '2024-11-14', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 16'), (16, 4150, 'CU15 GDR', 'https://support.microsoft.com/en-us/help/5046059', '2024-10-08', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 15 GDR'), (16, 4145, 'CU15', 'https://support.microsoft.com/en-us/help/5041321', '2024-09-25', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 15'), (16, 4140, 'CU14 GDR', 'https://support.microsoft.com/en-us/help/5042578', '2024-09-10', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 14 GDR'), @@ -63,6 +64,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, 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'), (15, 4390, 'CU28 GDR', 'https://support.microsoft.com/kb/5042749', '2024-09-10', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 28 GDR'), (15, 4385, 'CU28', 'https://support.microsoft.com/kb/5039747', '2024-08-01', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 28'), @@ -99,6 +102,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, 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'), (14, 3471, 'RTM CU31 GDR', 'https://support.microsoft.com/kb/5040940', '2024-07-09', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 31 GDR'), @@ -146,6 +150,7 @@ 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, 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'), (13, 6441, 'SP3 GDR', 'https://support.microsoft.com/kb/5040946', '2024-07-09', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3 GDR'), diff --git a/sp_Blitz.sql b/sp_Blitz.sql index 05ee61dcd..d430a3b3e 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -38,7 +38,7 @@ AS SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.22', @VersionDate = '20241019'; + SELECT @Version = '8.23', @VersionDate = '20241228'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) diff --git a/sp_BlitzAnalysis.sql b/sp_BlitzAnalysis.sql index 7c2358393..74d290709 100644 --- a/sp_BlitzAnalysis.sql +++ b/sp_BlitzAnalysis.sql @@ -37,7 +37,7 @@ AS SET NOCOUNT ON; SET STATISTICS XML OFF; -SELECT @Version = '8.22', @VersionDate = '20241019'; +SELECT @Version = '8.23', @VersionDate = '20241228'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_BlitzBackups.sql b/sp_BlitzBackups.sql index c7a44df6e..f3285ece8 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.22', @VersionDate = '20241019'; + SELECT @Version = '8.23', @VersionDate = '20241228'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_BlitzCache.sql b/sp_BlitzCache.sql index 188a80476..33ae3a2cb 100644 --- a/sp_BlitzCache.sql +++ b/sp_BlitzCache.sql @@ -281,7 +281,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.22', @VersionDate = '20241019'; +SELECT @Version = '8.23', @VersionDate = '20241228'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) diff --git a/sp_BlitzFirst.sql b/sp_BlitzFirst.sql index 8cfc9f27c..e32fa33f6 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.22', @VersionDate = '20241019'; +SELECT @Version = '8.23', @VersionDate = '20241228'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index ebbed0d3f..01cd26ad9 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -49,7 +49,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.22', @VersionDate = '20241019'; +SELECT @Version = '8.23', @VersionDate = '20241228'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index 3b36017de..42423741c 100644 --- a/sp_BlitzLock.sql +++ b/sp_BlitzLock.sql @@ -37,7 +37,7 @@ BEGIN SET XACT_ABORT OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.22', @VersionDate = '20241019'; + SELECT @Version = '8.23', @VersionDate = '20241228'; IF @VersionCheckMode = 1 BEGIN diff --git a/sp_BlitzWho.sql b/sp_BlitzWho.sql index 7f57e8af5..2f3ceadd9 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.22', @VersionDate = '20241019'; + SELECT @Version = '8.23', @VersionDate = '20241228'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_DatabaseRestore.sql b/sp_DatabaseRestore.sql index f85be751a..905518c5d 100755 --- a/sp_DatabaseRestore.sql +++ b/sp_DatabaseRestore.sql @@ -58,7 +58,7 @@ SET STATISTICS XML OFF; /*Versioning details*/ -SELECT @Version = '8.22', @VersionDate = '20241019'; +SELECT @Version = '8.23', @VersionDate = '20241228'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_ineachdb.sql b/sp_ineachdb.sql index c213cbd4e..0d2a85057 100644 --- a/sp_ineachdb.sql +++ b/sp_ineachdb.sql @@ -36,7 +36,7 @@ BEGIN SET NOCOUNT ON; SET STATISTICS XML OFF; - SELECT @Version = '8.22', @VersionDate = '20241019'; + SELECT @Version = '8.23', @VersionDate = '20241228'; IF(@VersionCheckMode = 1) BEGIN From 8f5b195ea6cdbbe193a43e2b698628ff3a7a54c4 Mon Sep 17 00:00:00 2001 From: Philippe Meunier Date: Thu, 16 Jan 2025 10:08:31 -0500 Subject: [PATCH 605/662] Update sp_DatabaseRestore.sql Increase @CommandExecuteCheck size so large database name fits in the command --- sp_DatabaseRestore.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sp_DatabaseRestore.sql b/sp_DatabaseRestore.sql index 905518c5d..f478a9d8c 100755 --- a/sp_DatabaseRestore.sql +++ b/sp_DatabaseRestore.sql @@ -240,7 +240,7 @@ END; BEGIN TRY DECLARE @CurrentDatabaseContext AS VARCHAR(128) = (SELECT DB_NAME()); -DECLARE @CommandExecuteCheck VARCHAR(315) +DECLARE @CommandExecuteCheck VARCHAR(400); SET @CommandExecuteCheck = 'IF NOT EXISTS (SELECT name FROM ' +@CurrentDatabaseContext+'.sys.objects WHERE type = ''P'' AND name = ''CommandExecute'') BEGIN From 1f583974336b342631eec7dcb888dab202508332 Mon Sep 17 00:00:00 2001 From: Vlad Drumea <48413726+VladDBA@users.noreply.github.com> Date: Sun, 2 Mar 2025 16:10:58 +0200 Subject: [PATCH 606/662] added new parameter @KeepCRLF --- sp_BlitzCache.sql | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/sp_BlitzCache.sql b/sp_BlitzCache.sql index 33ae3a2cb..db4d24e4f 100644 --- a/sp_BlitzCache.sql +++ b/sp_BlitzCache.sql @@ -273,7 +273,8 @@ ALTER PROCEDURE dbo.sp_BlitzCache @MinutesBack INT = NULL, @Version VARCHAR(30) = NULL OUTPUT, @VersionDate DATETIME = NULL OUTPUT, - @VersionCheckMode BIT = 0 + @VersionCheckMode BIT = 0, + @KeepCRLF BIT = 0 WITH RECOMPILE AS BEGIN @@ -483,7 +484,12 @@ IF @Help = 1 UNION ALL SELECT N'@VersionCheckMode', N'BIT', - N'Setting this to 1 will make the procedure stop after setting @Version and @VersionDate.'; + N'Setting this to 1 will make the procedure stop after setting @Version and @VersionDate.' + + UNION ALL + SELECT N'@KeepCRLF', + N'BIT', + N'Retain CR/LF in query text to avoid issues caused by line comments.'; /* Column definitions */ @@ -2760,7 +2766,10 @@ SET PercentCPU = y.PercentCPU, /* Strip newlines and tabs. Tabs are replaced with multiple spaces so that the later whitespace trim will completely eliminate them */ - QueryText = REPLACE(REPLACE(REPLACE(QueryText, @cr, ' '), @lf, ' '), @tab, ' ') + QueryText = CASE WHEN @KeepCRLF = 1 + THEN REPLACE(QueryText, @tab, ' ') + ELSE REPLACE(REPLACE(REPLACE(QueryText, @cr, ' '), @lf, ' '), @tab, ' ') + END FROM ( SELECT PlanHandle, CASE @total_cpu WHEN 0 THEN 0 @@ -2816,7 +2825,10 @@ SET PercentCPU = y.PercentCPU, /* Strip newlines and tabs. Tabs are replaced with multiple spaces so that the later whitespace trim will completely eliminate them */ - QueryText = REPLACE(REPLACE(REPLACE(QueryText, @cr, ' '), @lf, ' '), @tab, ' ') + QueryText = CASE WHEN @KeepCRLF = 1 + THEN REPLACE(QueryText, @tab, ' ') + ELSE REPLACE(REPLACE(REPLACE(QueryText, @cr, ' '), @lf, ' '), @tab, ' ') + END FROM ( SELECT DatabaseName, SqlHandle, From e93fbbfcf9eb4aa61f59da847e619bab182a913a Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Mon, 3 Mar 2025 02:52:27 +0000 Subject: [PATCH 607/662] #3610 sp_Blitz remove ampersand Which broke Markdown output. Closes #3610. --- sp_Blitz.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index d430a3b3e..517c62e6a 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -4076,7 +4076,7 @@ AS ''Informational'' AS FindingGroup , ''Backup Compression Default Off'' AS Finding , ''https://www.brentozar.com/go/backup'' AS URL , - ''Uncompressed full backups have happened recently, and backup compression is not turned on at the server level. Backup compression is included with SQL Server 2008R2 & newer, even in Standard Edition. We recommend turning backup compression on by default so that ad-hoc backups will get compressed.'' + ''Uncompressed full backups have happened recently, and backup compression is not turned on at the server level. Backup compression is included with Standard Edition. We recommend turning backup compression on by default so that ad-hoc backups will get compressed.'' FROM sys.configurations WHERE configuration_id = 1579 AND CAST(value_in_use AS INT) = 0 AND EXISTS (SELECT * FROM msdb.dbo.backupset WHERE backup_size = compressed_backup_size AND type = ''D'' AND backup_finish_date >= DATEADD(DD, -14, GETDATE())) OPTION (RECOMPILE);'; From 69fb073a03700fa03c952b9222655b0bed3b41e6 Mon Sep 17 00:00:00 2001 From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com> Date: Thu, 6 Mar 2025 09:44:51 -0500 Subject: [PATCH 608/662] Add table mode Closes #3614 --- sp_BlitzLock.sql | 436 ++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 359 insertions(+), 77 deletions(-) diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index 42423741c..cb5fdb6a3 100644 --- a/sp_BlitzLock.sql +++ b/sp_BlitzLock.sql @@ -1,6 +1,6 @@ IF OBJECT_ID('dbo.sp_BlitzLock') IS NULL BEGIN - EXEC ('CREATE PROCEDURE dbo.sp_BlitzLock AS RETURN 0;'); + EXECUTE ('CREATE PROCEDURE dbo.sp_BlitzLock AS RETURN 0;'); END; GO @@ -19,6 +19,11 @@ ALTER PROCEDURE @TargetSessionType sysname = NULL, @VictimsOnly bit = 0, @DeadlockType nvarchar(20) = NULL, + @TargetDatabaseName sysname = NULL, + @TargetSchemaName sysname = NULL, + @TargetTableName sysname = NULL, + @TargetColumnName sysname = NULL, + @TargetTimestampColumnName sysname = NULL, @Debug bit = 0, @Help bit = 0, @Version varchar(30) = NULL OUTPUT, @@ -54,6 +59,7 @@ BEGIN Variables you can use: + /*Filtering parameters*/ @DatabaseName: If you want to filter to a specific database @StartDate: The date you want to start searching on, defaults to last 7 days @@ -72,16 +78,32 @@ BEGIN @LoginName: If you want to filter to a specific login + @DeadlockType: Search for regular or parallel deadlocks specifically + + /*Extended Event session details*/ @EventSessionName: If you want to point this at an XE session rather than the system health session. - @TargetSessionType: Can be ''ring_buffer'' or ''event_file''. Leave NULL to auto-detect. + @TargetSessionType: Can be ''ring_buffer'', ''event_file'', or ''table''. Leave NULL to auto-detect. + /*Output to a table*/ @OutputDatabaseName: If you want to output information to a specific database @OutputSchemaName: Specify a schema name to output information to a specific Schema @OutputTableName: Specify table name to to output information to a specific table + /*Point at a table containing deadlock XML*/ + @TargetDatabaseName: The database that contains the table with deadlock report XML + + @TargetSchemaName: The schema of the table containing deadlock report XML + + @TargetTableName: The name of the table containing deadlock report XML + + @TargetColumnName: The name of the XML column that contains the deadlock report + + @TargetTimestampColumnName: The name of the datetime column for filtering by date range (optional) + + To learn more, visit http://FirstResponderKit.org where you can download new versions for free, watch training videos on how it works, get more info on the findings, contribute your own code, and more. @@ -199,7 +221,9 @@ BEGIN @StartDateOriginal datetime = @StartDate, @EndDateOriginal datetime = @EndDate, @StartDateUTC datetime, - @EndDateUTC datetime;; + @EndDateUTC datetime, + @extract_sql nvarchar(MAX), + @validation_sql nvarchar(MAX); /*Temporary objects used in the procedure*/ DECLARE @@ -324,7 +348,184 @@ BEGIN @TargetSessionType = N'ring_buffer'; END; + IF @TargetDatabaseName IS NOT NULL + AND @TargetSchemaName IS NOT NULL + AND @TargetTableName IS NOT NULL + AND @TargetColumnName IS NOT NULL + BEGIN + SET @TargetSessionType = N'table'; + END; + + /* Add this after the existing parameter validations */ + IF @TargetSessionType = N'table' + BEGIN + IF @TargetDatabaseName IS NULL + OR @TargetSchemaName IS NULL + OR @TargetTableName IS NULL + OR @TargetColumnName IS NULL + BEGIN + RAISERROR(N'When using a table as a source, you must specify @TargetDatabaseName, @TargetSchemaName, @TargetTableName, and @TargetColumnName.', 11, 1) WITH NOWAIT; + RETURN; + END; + + IF @TargetDatabaseName IS NULL + BEGIN + SET @TargetDatabaseName = DB_NAME(); + END; + + IF @TargetSchemaName IS NULL + BEGIN + SET @TargetSchemaName = N'dbo'; + END; + + /* Check if target database exists */ + IF NOT EXISTS + ( + SELECT + 1/0 + FROM sys.databases AS d + WHERE d.name = @TargetDatabaseName + ) + BEGIN + RAISERROR(N'The specified @TargetDatabaseName %s does not exist.', 11, 1, @TargetDatabaseName) WITH NOWAIT; + RETURN; + END; + + /* Use dynamic SQL to validate schema, table, and column existence */ + SET @validation_sql = N' + IF NOT EXISTS + ( + SELECT + 1/0 + FROM ' + QUOTENAME(@TargetDatabaseName) + N'.sys.schemas AS s + WHERE s.name = @schema + ) + BEGIN + RAISERROR(N''The specified @TargetSchemaName %s does not exist in database %s.'', 11, 1, @schema, @database) WITH NOWAIT; + RETURN; + END; + + IF NOT EXISTS + ( + SELECT + 1/0 + FROM ' + QUOTENAME(@TargetDatabaseName) + N'.sys.tables AS t + JOIN ' + QUOTENAME(@TargetDatabaseName) + N'.sys.schemas AS s + ON t.schema_id = s.schema_id + WHERE t.name = @table + AND s.name = @schema + ) + BEGIN + RAISERROR(N''The specified @TargetTableName %s does not exist in schema %s in database %s.'', 11, 1, @table, @schema, @database) WITH NOWAIT; + RETURN; + END; + + IF NOT EXISTS + ( + SELECT + 1/0 + FROM ' + QUOTENAME(@TargetDatabaseName) + N'.sys.columns AS c + JOIN ' + QUOTENAME(@TargetDatabaseName) + N'.sys.tables AS t + ON c.object_id = t.object_id + JOIN ' + QUOTENAME(@TargetDatabaseName) + N'.sys.schemas AS s + ON t.schema_id = s.schema_id + WHERE c.name = @column + AND t.name = @table + AND s.name = @schema + ) + BEGIN + RAISERROR(N''The specified @TargetColumnName %s does not exist in table %s.%s in database %s.'', 11, 1, @column, @schema, @table, @database) WITH NOWAIT; + RETURN; + END; + + /* Validate column is XML type */ + IF NOT EXISTS + ( + SELECT + 1/0 + FROM ' + QUOTENAME(@TargetDatabaseName) + N'.sys.columns AS c + JOIN ' + QUOTENAME(@TargetDatabaseName) + N'.sys.types AS ty + ON c.user_type_id = ty.user_type_id + JOIN ' + QUOTENAME(@TargetDatabaseName) + N'.sys.tables AS t + ON c.object_id = t.object_id + JOIN ' + QUOTENAME(@TargetDatabaseName) + N'.sys.schemas AS s + ON t.schema_id = s.schema_id + WHERE c.name = @column + AND t.name = @table + AND s.name = @schema + AND ty.name = N''xml'' + ) + BEGIN + RAISERROR(N''The specified @TargetColumnName %s must be of XML data type.'', 11, 1, @column) WITH NOWAIT; + RETURN; + END;'; + + /* Validate timestamp_column if specified */ + IF @TargetTimestampColumnName IS NOT NULL + BEGIN + SET @validation_sql = @validation_sql + N' + IF NOT EXISTS + ( + SELECT + 1/0 + FROM ' + QUOTENAME(@TargetDatabaseName) + N'.sys.columns AS c + JOIN ' + QUOTENAME(@TargetDatabaseName) + N'.sys.tables AS t + ON c.object_id = t.object_id + JOIN ' + QUOTENAME(@TargetDatabaseName) + N'.sys.schemas AS s + ON t.schema_id = s.schema_id + WHERE c.name = @timestamp_column + AND t.name = @table + AND s.name = @schema + ) + BEGIN + RAISERROR(N''The specified @TargetTimestampColumnName %s does not exist in table %s.%s in database %s.'', 11, 1, @timestamp_column, @schema, @table, @database) WITH NOWAIT; + RETURN; + END; + + /* Validate timestamp column is datetime type */ + IF NOT EXISTS + ( + SELECT + 1/0 + FROM ' + QUOTENAME(@TargetDatabaseName) + N'.sys.columns AS c + JOIN ' + QUOTENAME(@TargetDatabaseName) + N'.sys.types AS ty + ON c.user_type_id = ty.user_type_id + JOIN ' + QUOTENAME(@TargetDatabaseName) + N'.sys.tables AS t + ON c.object_id = t.object_id + JOIN ' + QUOTENAME(@TargetDatabaseName) + N'.sys.schemas AS s + ON t.schema_id = s.schema_id + WHERE c.name = @timestamp_column + AND t.name = @table + AND s.name = @schema + AND ty.name LIKE ''%date%'' + ) + BEGIN + RAISERROR(N''The specified @TargetTimestampColumnName %s must be of datetime data type.'', 11, 1, @timestamp_column) WITH NOWAIT; + RETURN; + END;'; + END; + + IF @Debug = 1 BEGIN PRINT @validation_sql; END; + + EXECUTE sys.sp_executesql + @validation_sql, + N' + @database sysname, + @schema sysname, + @table sysname, + @column sysname, + @timestamp_column sysname + ', + @TargetDatabaseName, + @TargetSchemaName, + @TargetTableName, + @TargetColumnName, + @TargetTimestampColumnName; + END; + + IF @Azure = 0 + AND LOWER(@TargetSessionType) <> N'table' BEGIN IF NOT EXISTS ( @@ -341,8 +542,9 @@ BEGIN RETURN; END; END; - + IF @Azure = 1 + AND LOWER(@TargetSessionType) <> N'table' BEGIN IF NOT EXISTS ( @@ -401,7 +603,7 @@ BEGIN N'@r sysname OUTPUT'; IF @Debug = 1 BEGIN PRINT @StringToExecute; END; - EXEC sys.sp_executesql + EXECUTE sys.sp_executesql @StringToExecute, @StringToExecuteParams, @r OUTPUT; @@ -441,7 +643,7 @@ BEGIN N' ADD spid smallint NULL;'; IF @Debug = 1 BEGIN PRINT @StringToExecute; END; - EXEC sys.sp_executesql + EXECUTE sys.sp_executesql @StringToExecute; /* If the table doesn't have the new wait_resource column, add it. See Github #3101. */ @@ -457,7 +659,7 @@ BEGIN N' ADD wait_resource nvarchar(MAX) NULL;'; IF @Debug = 1 BEGIN PRINT @StringToExecute; END; - EXEC sys.sp_executesql + EXECUTE sys.sp_executesql @StringToExecute; /* If the table doesn't have the new client option column, add it. See Github #3101. */ @@ -473,7 +675,7 @@ BEGIN N' ADD client_option_1 varchar(500) NULL;'; IF @Debug = 1 BEGIN PRINT @StringToExecute; END; - EXEC sys.sp_executesql + EXECUTE sys.sp_executesql @StringToExecute; /* If the table doesn't have the new client option column, add it. See Github #3101. */ @@ -489,7 +691,7 @@ BEGIN N' ADD client_option_2 varchar(500) NULL;'; IF @Debug = 1 BEGIN PRINT @StringToExecute; END; - EXEC sys.sp_executesql + EXECUTE sys.sp_executesql @StringToExecute; /* If the table doesn't have the new lock mode column, add it. See Github #3101. */ @@ -505,7 +707,7 @@ BEGIN N' ADD lock_mode nvarchar(256) NULL;'; IF @Debug = 1 BEGIN PRINT @StringToExecute; END; - EXEC sys.sp_executesql + EXECUTE sys.sp_executesql @StringToExecute; /* If the table doesn't have the new status column, add it. See Github #3101. */ @@ -521,7 +723,7 @@ BEGIN N' ADD status nvarchar(256) NULL;'; IF @Debug = 1 BEGIN PRINT @StringToExecute; END; - EXEC sys.sp_executesql + EXECUTE sys.sp_executesql @StringToExecute; END; ELSE /* end if @r is not null. if it is null there is no table, create it from above execution */ @@ -579,7 +781,7 @@ BEGIN )'; IF @Debug = 1 BEGIN PRINT @StringToExecute; END; - EXEC sys.sp_executesql + EXECUTE sys.sp_executesql @StringToExecute; /*table created.*/ @@ -594,7 +796,7 @@ BEGIN N'@r sysname OUTPUT'; IF @Debug = 1 BEGIN PRINT @StringToExecute; END; - EXEC sys.sp_executesql + EXECUTE sys.sp_executesql @StringToExecute, @StringToExecuteParams, @r OUTPUT; @@ -622,7 +824,7 @@ BEGIN );'; IF @Debug = 1 BEGIN PRINT @StringToExecute; END; - EXEC sys.sp_executesql + EXECUTE sys.sp_executesql @StringToExecute; END; END; @@ -651,7 +853,7 @@ BEGIN @OutputTableFindings; IF @Debug = 1 BEGIN PRINT @StringToExecute; END; - EXEC sys.sp_executesql + EXECUTE sys.sp_executesql @StringToExecute; /*create synonym for deadlock table.*/ @@ -678,7 +880,7 @@ BEGIN @OutputTableName; IF @Debug = 1 BEGIN PRINT @StringToExecute; END; - EXEC sys.sp_executesql + EXECUTE sys.sp_executesql @StringToExecute; END; END; @@ -750,6 +952,7 @@ BEGIN ( @Azure = 1 AND @TargetSessionType IS NULL + AND LOWER(@TargetSessionType) <> N'table' ) BEGIN RAISERROR('@TargetSessionType is NULL, assigning for Azure instance', 0, 1) WITH NOWAIT; @@ -1048,6 +1251,75 @@ BEGIN RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; END; + /* If table target */ + IF @TargetSessionType = 'table' + BEGIN + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Inserting to #deadlock_data from table source %s', 0, 1, @d) WITH NOWAIT; + + /* Build dynamic SQL to extract the XML */ + SET @extract_sql = N' + SELECT + deadlock_xml = ' + + QUOTENAME(@TargetColumnName) + + N' + FROM ' + + QUOTENAME(@TargetDatabaseName) + + N'.' + + QUOTENAME(@TargetSchemaName) + + N'.' + + QUOTENAME(@TargetTableName) + + N' AS x + LEFT JOIN #t AS t + ON 1 = 1 + CROSS APPLY x.' + + QUOTENAME(@TargetColumnName) + + N'.nodes(''/event'') AS e(x) + WHERE + ( + e.x.exist(''@name[ .= "xml_deadlock_report"]'') = 1 + OR e.x.exist(''@name[ .= "database_xml_deadlock_report"]'') = 1 + OR e.x.exist(''@name[ .= "xml_deadlock_report_filtered"]'') = 1 + )'; + + /* Add timestamp filtering if specified */ + IF @TargetTimestampColumnName IS NOT NULL + BEGIN + SET @extract_sql = @extract_sql + N' + AND x.' + QUOTENAME(@TargetTimestampColumnName) + N' >= @StartDate + AND x.' + QUOTENAME(@TargetTimestampColumnName) + N' < @EndDate'; + END; + + /* If no timestamp column but date filtering is needed, handle XML-based filtering */ + IF @TargetTimestampColumnName IS NULL + BEGIN + SET @extract_sql = @extract_sql + N' + AND e.x.exist(''@timestamp[. >= sql:variable("@StartDate") and . < sql:variable("@EndDate")]'') = 1'; + END; + + IF @Debug = 1 BEGIN PRINT @extract_sql; END; + + /* Execute the dynamic SQL */ + INSERT + #deadlock_data + WITH + (TABLOCKX) + ( + deadlock_xml + ) + EXECUTE sys.sp_executesql + @extract_sql, + N' + @StartDate datetime, + @EndDate datetime + ', + @StartDate, + @EndDate; + + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + END; + /*Parse process and input buffer xml*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Initial Parse process and input buffer xml %s', 0, 1, @d) WITH NOWAIT; @@ -1735,7 +2007,7 @@ BEGIN '; IF @Debug = 1 BEGIN PRINT @StringToExecute; END; - EXEC sys.sp_executesql + EXECUTE sys.sp_executesql @StringToExecute; END; @@ -1884,7 +2156,7 @@ BEGIN COUNT_BIG(DISTINCT dp.event_date) ) + N' deadlocks.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT dp.event_date) DESC) FROM #deadlock_process AS dp @@ -1939,7 +2211,7 @@ BEGIN COUNT_BIG(DISTINCT dow.event_date) ) + N' deadlock(s) between read queries and modification queries.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT dow.event_date) DESC) FROM #deadlock_owner_waiter AS dow @@ -2001,7 +2273,7 @@ BEGIN COUNT_BIG(DISTINCT dow.event_date) ) + N' deadlock(s).', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT dow.event_date) DESC) FROM #deadlock_owner_waiter AS dow @@ -2044,7 +2316,7 @@ BEGIN COUNT_BIG(DISTINCT dow.event_date) ) + N' deadlock(s).', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT dow.event_date) DESC) FROM #deadlock_owner_waiter AS dow @@ -2093,7 +2365,7 @@ BEGIN COUNT_BIG(DISTINCT dow.event_date) ) + N' deadlock(s).', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT dow.event_date) DESC) FROM #deadlock_owner_waiter AS dow @@ -2142,7 +2414,7 @@ BEGIN COUNT_BIG(DISTINCT dp.event_date) ) + N' instances of Serializable deadlocks.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT dp.event_date) DESC) FROM #deadlock_process AS dp @@ -2185,7 +2457,7 @@ BEGIN COUNT_BIG(DISTINCT dp.event_date) ) + N' instances of Repeatable Read deadlocks.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT dp.event_date) DESC) FROM #deadlock_process AS dp @@ -2247,7 +2519,7 @@ BEGIN N'UNKNOWN' ) + N'.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT dp.event_date) DESC) FROM #deadlock_process AS dp @@ -2359,7 +2631,7 @@ BEGIN 1, N'' ) + N' locks.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY CONVERT(bigint, lt.lock_count) DESC) FROM lock_types AS lt @@ -2429,7 +2701,7 @@ BEGIN dow.database_name, object_name = ds.proc_name, finding_group = N'More Info - Query', - finding = N'EXEC sp_BlitzCache ' + + finding = N'EXECUTE sp_BlitzCache ' + CASE WHEN ds.proc_name = N'adhoc' THEN N'@OnlySqlHandles = ' + ds.sql_handle_csv @@ -2485,7 +2757,7 @@ BEGIN object_name = ds.proc_name, finding_group = N'More Info - Query', finding = - N'EXEC sp_BlitzQueryStore ' + + N'EXECUTE sp_BlitzQueryStore ' + N'@DatabaseName = ' + QUOTENAME(ds.database_name, N'''') + N', ' + @@ -2538,7 +2810,7 @@ BEGIN COUNT_BIG(DISTINCT ds.id) ) + N' deadlocks.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT ds.id) DESC) FROM #deadlock_stack AS ds @@ -2598,7 +2870,7 @@ BEGIN bi.object_name, finding_group = N'More Info - Table', finding = - N'EXEC sp_BlitzIndex ' + + N'EXECUTE sp_BlitzIndex ' + N'@DatabaseName = ' + QUOTENAME(bi.database_name, N'''') + N', @SchemaName = ' + @@ -2639,19 +2911,19 @@ BEGIN ) ), wait_time_hms = - /*the more wait time you rack up the less accurate this gets, + /*the more wait time you rack up the less accurate this gets, it's either that or erroring out*/ - CASE - WHEN + CASE + WHEN SUM ( CONVERT ( - bigint, + bigint, dp.wait_time ) )/1000 > 2147483647 - THEN + THEN CONVERT ( nvarchar(30), @@ -2664,7 +2936,7 @@ BEGIN ( CONVERT ( - bigint, + bigint, dp.wait_time ) ) @@ -2675,16 +2947,16 @@ BEGIN ), 14 ) - WHEN + WHEN SUM ( CONVERT ( - bigint, + bigint, dp.wait_time ) ) BETWEEN 2147483648 AND 2147483647000 - THEN + THEN CONVERT ( nvarchar(30), @@ -2697,7 +2969,7 @@ BEGIN ( CONVERT ( - bigint, + bigint, dp.wait_time ) ) @@ -2779,7 +3051,7 @@ BEGIN 14 ) + N' [dd hh:mm:ss:ms] of deadlock wait time.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY cs.total_waits DESC) FROM chopsuey AS cs @@ -2853,19 +3125,19 @@ BEGIN ) ) + N' ' + - /*the more wait time you rack up the less accurate this gets, + /*the more wait time you rack up the less accurate this gets, it's either that or erroring out*/ - CASE - WHEN + CASE + WHEN SUM ( CONVERT ( - bigint, + bigint, wt.total_wait_time_ms ) )/1000 > 2147483647 - THEN + THEN CONVERT ( nvarchar(30), @@ -2878,7 +3150,7 @@ BEGIN ( CONVERT ( - bigint, + bigint, wt.total_wait_time_ms ) ) @@ -2889,16 +3161,16 @@ BEGIN ), 14 ) - WHEN + WHEN SUM ( CONVERT ( - bigint, + bigint, wt.total_wait_time_ms ) ) BETWEEN 2147483648 AND 2147483647000 - THEN + THEN CONVERT ( nvarchar(30), @@ -2911,7 +3183,7 @@ BEGIN ( CONVERT ( - bigint, + bigint, wt.total_wait_time_ms ) ) @@ -2944,7 +3216,7 @@ BEGIN 14 ) END + N' [dd hh:mm:ss:ms] of deadlock wait time.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY SUM(CONVERT(bigint, wt.total_wait_time_ms)) DESC) FROM wait_time AS wt @@ -2982,7 +3254,7 @@ BEGIN N'There have been ' + RTRIM(COUNT_BIG(DISTINCT aj.event_date)) + N' deadlocks from this Agent Job and Step.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT aj.event_date) DESC) FROM #agent_job AS aj @@ -3675,9 +3947,9 @@ BEGIN SET STATISTICS XML ON; END; - SET @StringToExecute = N' + SET @StringToExecute = N' - INSERT INTO ' + QUOTENAME(DB_NAME()) + N'..DeadLockTbl + INSERT INTO ' + QUOTENAME(DB_NAME()) + N'..DeadLockTbl ( ServerName, deadlock_type, @@ -3720,9 +3992,9 @@ BEGIN waiter_waiting_to_close, deadlock_graph ) - EXEC sys.sp_executesql - @deadlock_result;' - EXEC sys.sp_executesql @StringToExecute, N'@deadlock_result NVARCHAR(MAX)', @deadlock_result; + EXECUTE sys.sp_executesql + @deadlock_result;'; + EXECUTE sys.sp_executesql @StringToExecute, N'@deadlock_result NVARCHAR(MAX)', @deadlock_result; IF @Debug = 1 BEGIN @@ -3738,7 +4010,7 @@ BEGIN SET @StringToExecute = N' - INSERT INTO ' + QUOTENAME(DB_NAME()) + N'..DeadlockFindings + INSERT INTO ' + QUOTENAME(DB_NAME()) + N'..DeadlockFindings ( ServerName, check_id, @@ -3756,8 +4028,8 @@ BEGIN df.finding FROM #deadlock_findings AS df ORDER BY df.check_id - OPTION(RECOMPILE);' - EXEC sys.sp_executesql @StringToExecute; + OPTION(RECOMPILE);'; + EXECUTE sys.sp_executesql @StringToExecute; RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; @@ -3767,23 +4039,23 @@ BEGIN BEGIN SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Results to client %s', 0, 1, @d) WITH NOWAIT; - + IF @Debug = 1 BEGIN SET STATISTICS XML ON; END; - - EXEC sys.sp_executesql + + EXECUTE sys.sp_executesql @deadlock_result; - + IF @Debug = 1 BEGIN SET STATISTICS XML OFF; PRINT @deadlock_result; END; - + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Getting available execution plans for deadlocks %s', 0, 1, @d) WITH NOWAIT; - + SELECT DISTINCT available_plans = 'available_plans', @@ -3850,15 +4122,15 @@ BEGIN min_used_grant_mb = deqs.min_used_grant_kb * 8. / 1024., max_used_grant_mb = - deqs.max_used_grant_kb * 8. / 1024., + deqs.max_used_grant_kb * 8. / 1024., deqs.min_reserved_threads, deqs.max_reserved_threads, deqs.min_used_threads, deqs.max_used_threads, deqs.total_rows, - max_worker_time_ms = + max_worker_time_ms = deqs.max_worker_time / 1000., - max_elapsed_time_ms = + max_elapsed_time_ms = deqs.max_elapsed_time / 1000. INTO #dm_exec_query_stats FROM sys.dm_exec_query_stats AS deqs @@ -3870,7 +4142,7 @@ BEGIN WHERE ap.sql_handle = deqs.sql_handle ) AND deqs.query_hash IS NOT NULL; - + CREATE CLUSTERED INDEX deqs ON #dm_exec_query_stats @@ -3878,7 +4150,7 @@ BEGIN sql_handle, plan_handle ); - + SELECT ap.available_plans, ap.database_name, @@ -3912,7 +4184,7 @@ BEGIN ap.statement_end_offset FROM ( - + SELECT ap.*, c.statement_start_offset, @@ -3964,10 +4236,10 @@ BEGIN OPTION(RECOMPILE, LOOP JOIN, HASH JOIN); RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - + SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Returning findings %s', 0, 1, @d) WITH NOWAIT; - + SELECT df.check_id, df.database_name, @@ -3979,7 +4251,7 @@ BEGIN df.check_id, df.sort_order OPTION(RECOMPILE); - + SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; END; /*done with output to client app.*/ @@ -4063,7 +4335,7 @@ BEGIN END; IF OBJECT_ID('tempdb..#dm_exec_query_stats') IS NOT NULL - BEGIN + BEGIN SELECT table_name = N'#dm_exec_query_stats', * @@ -4098,6 +4370,16 @@ BEGIN @VictimsOnly, DeadlockType = @DeadlockType, + TargetDatabaseName = + @TargetDatabaseName, + TargetSchemaName = + @TargetSchemaName, + TargetTableName = + @TargetTableName, + TargetColumnName = + @TargetColumnName, + TargetTimestampColumnName = + @TargetTimestampColumnName, Debug = @Debug, Help = From 5760c4343cbe646034704a39869a33e708b9ec64 Mon Sep 17 00:00:00 2001 From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com> Date: Tue, 11 Mar 2025 13:55:33 -0400 Subject: [PATCH 609/662] Update sp_BlitzLock.sql Doing some more work on this as I find data source issues. Apparently there's more than one way to report a deadlock! --- sp_BlitzLock.sql | 135 +++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 113 insertions(+), 22 deletions(-) diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index cb5fdb6a3..79029b7ca 100644 --- a/sp_BlitzLock.sql +++ b/sp_BlitzLock.sql @@ -223,7 +223,9 @@ BEGIN @StartDateUTC datetime, @EndDateUTC datetime, @extract_sql nvarchar(MAX), - @validation_sql nvarchar(MAX); + @validation_sql nvarchar(MAX), + @xe bit, + @xd bit; /*Temporary objects used in the procedure*/ DECLARE @@ -348,8 +350,8 @@ BEGIN @TargetSessionType = N'ring_buffer'; END; - IF @TargetDatabaseName IS NOT NULL - AND @TargetSchemaName IS NOT NULL + IF ISNULL(@TargetDatabaseName, DB_NAME()) IS NOT NULL + AND ISNULL(@TargetSchemaName, N'dbo') IS NOT NULL AND @TargetTableName IS NOT NULL AND @TargetColumnName IS NOT NULL BEGIN @@ -359,15 +361,6 @@ BEGIN /* Add this after the existing parameter validations */ IF @TargetSessionType = N'table' BEGIN - IF @TargetDatabaseName IS NULL - OR @TargetSchemaName IS NULL - OR @TargetTableName IS NULL - OR @TargetColumnName IS NULL - BEGIN - RAISERROR(N'When using a table as a source, you must specify @TargetDatabaseName, @TargetSchemaName, @TargetTableName, and @TargetColumnName.', 11, 1) WITH NOWAIT; - RETURN; - END; - IF @TargetDatabaseName IS NULL BEGIN SET @TargetDatabaseName = DB_NAME(); @@ -377,6 +370,16 @@ BEGIN BEGIN SET @TargetSchemaName = N'dbo'; END; + + IF @TargetTableName IS NULL + OR @TargetColumnName IS NULL + BEGIN + RAISERROR(N' + When using a table as a source, you must specify @TargetTableName, and @TargetColumnName. + When @TargetDatabaseName or @TargetSchemaName is NULL, they default to DB_NAME() AND dbo', + 11, 1) WITH NOWAIT; + RETURN; + END; /* Check if target database exists */ IF NOT EXISTS @@ -1257,8 +1260,42 @@ BEGIN SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Inserting to #deadlock_data from table source %s', 0, 1, @d) WITH NOWAIT; + /* + First, we need to heck the XML structure. + Depending on the data source, the XML could + contain either the /event or /deadlock nodes. + When the /event nodes are not present, there + is no @name attribute to evaluate. + */ + + SELECT + @extract_sql = N' + SELECT TOP (1) + @xe = xe.e.exist(''.''), + @xd = xd.e.exist(''.'') + FROM [master].[dbo].[bpr] AS x + OUTER APPLY x.[bpr].nodes(''/event'') AS xe(e) + OUTER APPLY x.[bpr].nodes(''/deadlock'') AS xd(e) + OPTION(RECOMPILE); + '; + + IF @Debug = 1 BEGIN PRINT @extract_sql; END; + + EXECUTE sys.sp_executesql + @extract_sql, + N' + @xe bit OUTPUT, + @xd bit OUTPUT + ', + @xe OUTPUT, + @xd OUTPUT; + + /* Build dynamic SQL to extract the XML */ - SET @extract_sql = N' + IF @xe = 1 + AND @xd IS NULL + BEGIN + SET @extract_sql = N' SELECT deadlock_xml = ' + QUOTENAME(@TargetColumnName) + @@ -1281,21 +1318,60 @@ BEGIN OR e.x.exist(''@name[ .= "database_xml_deadlock_report"]'') = 1 OR e.x.exist(''@name[ .= "xml_deadlock_report_filtered"]'') = 1 )'; + END; + + IF @xe IS NULL + AND @xd = 1 + BEGIN + SET @extract_sql = N' + SELECT + deadlock_xml = ' + + QUOTENAME(@TargetColumnName) + + N' + FROM ' + + QUOTENAME(@TargetDatabaseName) + + N'.' + + QUOTENAME(@TargetSchemaName) + + N'.' + + QUOTENAME(@TargetTableName) + + N' AS x + LEFT JOIN #t AS t + ON 1 = 1 + CROSS APPLY x.' + + QUOTENAME(@TargetColumnName) + + N'.nodes(''/deadlock'') AS e(x) + WHERE 1 = 1'; + END; /* Add timestamp filtering if specified */ - IF @TargetTimestampColumnName IS NOT NULL + IF @TargetTimestampColumnName IS NOT NULL BEGIN SET @extract_sql = @extract_sql + N' AND x.' + QUOTENAME(@TargetTimestampColumnName) + N' >= @StartDate AND x.' + QUOTENAME(@TargetTimestampColumnName) + N' < @EndDate'; END; - /* If no timestamp column but date filtering is needed, handle XML-based filtering */ - IF @TargetTimestampColumnName IS NULL + /* If no timestamp column but date filtering is needed, handle XML-based filtering when possible */ + IF @TargetTimestampColumnName IS NULL + AND @xe = 1 + AND @xd IS NULL BEGIN SET @extract_sql = @extract_sql + N' AND e.x.exist(''@timestamp[. >= sql:variable("@StartDate") and . < sql:variable("@EndDate")]'') = 1'; END; + + /*Woof*/ + IF @TargetTimestampColumnName IS NULL + AND @xe IS NULL + AND @xd = 1 + BEGIN + SET @extract_sql = @extract_sql + N' + AND e.x.exist(''(/deadlock/process-list/process/@lasttranstarted)[. >= sql:variable("@StartDate") and . < sql:variable("@EndDate")]'') = 1'; + END; + + SET @extract_sql += N' + OPTION(RECOMPILE); + '; IF @Debug = 1 BEGIN PRINT @extract_sql; END; @@ -1335,6 +1411,21 @@ BEGIN FROM #deadlock_data AS d1 LEFT JOIN #t AS t ON 1 = 1 + WHERE @xe = 1 + + UNION ALL + + SELECT + d1.deadlock_xml, + event_date = d1.deadlock_xml.value('(/deadlock/process-list/process/@lasttranstarted)[1]', 'datetime2'), + victim_id = d1.deadlock_xml.value('(/deadlock/victim-list/victimProcess/@id)[1]', 'nvarchar(256)'), + is_parallel = d1.deadlock_xml.exist('/deadlock/resource-list/exchangeEvent'), + is_parallel_batch = d1.deadlock_xml.exist('/deadlock/resource-list/SyncPoint'), + deadlock_graph = d1.deadlock_xml.query('.') + FROM #deadlock_data AS d1 + LEFT JOIN #t AS t + ON 1 = 1 + WHERE @xd = 1 OPTION(RECOMPILE); SET @d = CONVERT(varchar(40), GETDATE(), 109); @@ -4370,16 +4461,16 @@ BEGIN @VictimsOnly, DeadlockType = @DeadlockType, - TargetDatabaseName = - @TargetDatabaseName, + TargetDatabaseName = + @TargetDatabaseName, TargetSchemaName = - @TargetSchemaName, + @TargetSchemaName, TargetTableName = - @TargetTableName, + @TargetTableName, TargetColumnName = - @TargetColumnName, + @TargetColumnName, TargetTimestampColumnName = - @TargetTimestampColumnName, + @TargetTimestampColumnName, Debug = @Debug, Help = From 842f6d0772b48d32dda4e32dd81e5ee2c2d699f7 Mon Sep 17 00:00:00 2001 From: RG Date: Sat, 5 Apr 2025 20:57:17 +0100 Subject: [PATCH 610/662] Added reports of resumable index operations (paused or running) to the mode 0 and mode 4 outputs, as well as the mode where you name one table. The priority set here is very high, but that seems correct. They can prevent DDL! Not included: Any reference to the 2022 feature that auto-kills paused indexes. It is in the ALTER DATABASE SCOPED CONFIGURATION docs. I will make an issue for it. --- .../sp_BlitzIndex_Checks_by_Priority.md | 6 +- sp_BlitzIndex.sql | 241 +++++++++++++++++- 2 files changed, 240 insertions(+), 7 deletions(-) diff --git a/Documentation/sp_BlitzIndex_Checks_by_Priority.md b/Documentation/sp_BlitzIndex_Checks_by_Priority.md index 3cc69a7f4..d6af3eec2 100644 --- a/Documentation/sp_BlitzIndex_Checks_by_Priority.md +++ b/Documentation/sp_BlitzIndex_Checks_by_Priority.md @@ -6,13 +6,15 @@ 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: 121 -If you want to add a new check, start at 122. +CURRENT HIGH CHECKID: 123 +If you want to add a new check, start at 124. | 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 | diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index 01cd26ad9..6ae4e5968 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -261,8 +261,11 @@ IF OBJECT_ID('tempdb..#FilteredIndexes') IS NOT NULL DROP TABLE #FilteredIndexes; IF OBJECT_ID('tempdb..#Ignore_Databases') IS NOT NULL - DROP TABLE #Ignore_Databases - + DROP TABLE #Ignore_Databases; + +IF OBJECT_ID('tempdb..#IndexResumableOperations') IS NOT NULL + DROP TABLE #IndexResumableOperations; + IF OBJECT_ID('tempdb..#dm_db_partition_stats_etc') IS NOT NULL DROP TABLE #dm_db_partition_stats_etc IF OBJECT_ID('tempdb..#dm_db_index_operational_stats') IS NOT NULL @@ -803,6 +806,59 @@ IF OBJECT_ID('tempdb..#dm_db_index_operational_stats') IS NOT NULL column_name NVARCHAR(128) NULL ); + CREATE TABLE #IndexResumableOperations + ( + database_name NVARCHAR(128) NULL, + database_id INT NOT NULL, + schema_name NVARCHAR(128) NOT NULL, + table_name NVARCHAR(128) NOT NULL, + /* + Every following non-computed column has + the same definitions as in + sys.index_resumable_operations. + */ + [object_id] INT NOT NULL, + index_id INT NOT NULL, + [name] NVARCHAR(128) NOT NULL, + /* + We have done nothing to make this query text pleasant + to read. Until somebody has a better idea, we trust + that copying Microsoft's approach is wise. + */ + sql_text NVARCHAR(MAX) NULL, + last_max_dop_used SMALLINT NOT NULL, + partition_number INT NULL, + state TINYINT NOT NULL, + state_desc NVARCHAR(60) NULL, + start_time DATETIME NOT NULL, + last_pause_time DATETIME NULL, + total_execution_time INT NOT NULL, + percent_complete FLOAT NOT NULL, + page_count BIGINT NOT NULL, + /* + sys.indexes will not always have the name of the index + because a resumable CREATE INDEX does not populate + sys.indexes until it is done. + So it is better to work out the full name here + rather than pull it from another temp table. + */ + [db_schema_table_index] AS + [schema_name] + N'.' + [table_name] + N'.' + [name], + /* For convenience. */ + reserved_MB_pretty_print AS + CONVERT(NVARCHAR(100), CONVERT(MONEY, page_count * 8. / 1024.)) + + 'MB and ' + + state_desc, + more_info AS + N'New index: SELECT * FROM ' + QUOTENAME(database_name) + + N'.sys.index_resumable_operations WHERE [object_id] = ' + + CONVERT(NVARCHAR(100), [object_id]) + + N'; Old index: ' + + N'EXEC dbo.sp_BlitzIndex @DatabaseName=' + QUOTENAME([database_name],N'''') + + N', @SchemaName=' + QUOTENAME([schema_name],N'''') + + N', @TableName=' + QUOTENAME([table_name],N'''') + N';' + ); + CREATE TABLE #Ignore_Databases ( DatabaseName NVARCHAR(128), @@ -2517,8 +2573,53 @@ OPTION (RECOMPILE);'; BEGIN CATCH RAISERROR (N'Skipping #FilteredIndexes population due to error, typically low permissions.', 0,1) WITH NOWAIT; END CATCH + END; + + IF @Mode NOT IN(1, 2, 3) + /* + The sys.index_resumable_operations view was a 2017 addition, so we need to check for it and go dynamic. + */ + AND EXISTS (SELECT * FROM sys.all_objects WHERE name = 'index_resumable_operations') + BEGIN + SET @dsql=N'SELECT @i_DatabaseName AS database_name, + DB_ID(@i_DatabaseName) AS [database_id], + s.name AS schema_name, + t.name AS table_name, + iro.[object_id], + iro.index_id, + iro.name, + iro.sql_text, + iro.last_max_dop_used, + iro.partition_number, + iro.state, + iro.state_desc, + iro.start_time, + iro.last_pause_time, + iro.total_execution_time, + iro.percent_complete, + iro.page_count + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.index_resumable_operations AS iro + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.tables AS t + ON t.object_id = iro.object_id + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s + ON t.schema_id = s.schema_id + OPTION(RECOMPILE);' + + BEGIN TRY + RAISERROR (N'Inserting data into #IndexResumableOperations',0,1) WITH NOWAIT; + INSERT #IndexResumableOperations + ( database_name, database_id, schema_name, table_name, + [object_id], index_id, name, sql_text, last_max_dop_used, partition_number, state, state_desc, + start_time, last_pause_time, total_execution_time, percent_complete, page_count ) + EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; + END TRY + BEGIN CATCH + RAISERROR (N'Skipping #IndexResumableOperations population due to error, typically low permissions', 0,1) WITH NOWAIT; + END CATCH + END; + + END; - END; END; END TRY @@ -2967,7 +3068,8 @@ BEGIN SELECT '#ComputedColumns' AS table_name, * FROM #ComputedColumns; SELECT '#TraceStatus' AS table_name, * FROM #TraceStatus; SELECT '#CheckConstraints' AS table_name, * FROM #CheckConstraints; - SELECT '#FilteredIndexes' AS table_name, * FROM #FilteredIndexes; + SELECT '#FilteredIndexes' AS table_name, * FROM #FilteredIndexes; + SELECT '#IndexResumableOperations' AS table_name, * FROM #IndexResumableOperations; END @@ -3185,7 +3287,50 @@ BEGIN ORDER BY s.auto_created, s.user_created, s.name, hist.step_number;'; EXEC sp_executesql @dsql, N'@ObjectID INT', @ObjectID; END - END + + /* Check for resumable index operations. */ + IF (SELECT TOP (1) [object_id] FROM #IndexResumableOperations WHERE [object_id] = @ObjectID AND database_id = @DatabaseID) IS NOT NULL + BEGIN + SELECT + N'Resumable Index Operation' AS finding, + N'This may invalidate your analysis!' AS warning, + iro.state_desc + ' on ' + iro.db_schema_table_index + + CASE iro.state + WHEN 0 THEN + ' at MAXDOP ' + CONVERT(NVARCHAR(30), iro.last_max_dop_used) + + '. First started ' + CONVERT(NVARCHAR(50), iro.start_time, 120) + '. ' + + CONVERT(NVARCHAR(6), CONVERT(MONEY, iro.percent_complete)) + '% complete after ' + + CONVERT(NVARCHAR(30), iro.total_execution_time) + + ' minute(s). This blocks DDL and can pile up ghosts.' + WHEN 1 THEN + ' since ' + CONVERT(NVARCHAR(50), iro.last_pause_time, 120) + '. ' + + CONVERT(NVARCHAR(6), CONVERT(MONEY, iro.percent_complete)) + '% complete' + + /* + At 100% completion, resumable indexes open up a transaction and go back to paused for what ought to be a moment. + Updating statistics is one of the things that it can do in this false paused state. + Updating stats can take a while, so we point it out as a likely delay. + It seems that any of the normal operations that happen at the very end of an index build can cause this. + */ + CASE WHEN iro.percent_complete > 99.9 + THEN '. It is probably still running, perhaps updating statistics.' + ELSE ' after ' + CONVERT(NVARCHAR(30), iro.total_execution_time) + + ' minute(s). This blocks DDL, fails transactions needing table-level X locks, and can pile up ghosts.' + END + ELSE ' which is an undocumented resumable index state description.' + END AS details, + N'https://www.BrentOzar.com/go/resumable' AS URL, + iro.more_info AS [More Info] + FROM #IndexResumableOperations AS iro + WHERE iro.database_id = @DatabaseID + AND iro.[object_id] = @ObjectID + OPTION ( RECOMPILE ); + END + ELSE + BEGIN + SELECT N'No resumable index operations.' AS finding; + END; + + END /* END @ShowColumnstoreOnly = 0 */ /* Visualize columnstore index contents. More info: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2584 */ IF 2 = (SELECT SUM(1) FROM sys.all_objects WHERE name IN ('column_store_row_groups','column_store_segments')) @@ -3566,6 +3711,92 @@ BEGIN ORDER BY ips.total_rows DESC, ip.[schema_name], ip.[object_name], ip.key_column_names, ip.include_column_names OPTION ( RECOMPILE ); + ---------------------------------------- + --Resumable Indexing: Check_id 122-123 + ---------------------------------------- + /* + This is more complicated than you would expect! + As of SQL Server 2022, I am aware of 6 cases that we need to check: + 1) A resumable rowstore CREATE INDEX that is currently running + 2) A resumable rowstore CREATE INDEX that is currently paused + 3) A resumable rowstore REBUILD that is currently running + 4) A resumable rowstore REBUILD that is currently paused + 5) A resumable rowstore CREATE INDEX [...] DROP_EXISTING = ON that is currently running + 6) A resumable rowstore CREATE INDEX [...] DROP_EXISTING = ON that is currently paused + In cases 1 and 2, sys.indexes has no data at all about the index in question. + This makes #IndexSanity much harder to use, since it depends on sys.indexes. + We must therefore get as much from #IndexResumableOperations as possible. + */ + RAISERROR(N'check_id 122: Resumable Index Operation Paused', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, + [database_name], URL, details, index_definition, secret_columns, + index_usage_summary, index_size_summary, create_tsql, more_info ) + SELECT 122 AS check_id, + i.index_sanity_id, + 10 AS Priority, + N'Resumable Indexing' AS findings_group, + N'Resumable Index Operation Paused' AS finding, + iro.[database_name] AS [Database Name], + N'https://www.BrentOzar.com/go/resumable' AS URL, + iro.state_desc + ' on ' + iro.db_schema_table_index + + ' since ' + CONVERT(NVARCHAR(50), iro.last_pause_time, 120) + '. ' + + CONVERT(NVARCHAR(6), CONVERT(MONEY, iro.percent_complete)) + '% complete' + + /* + At 100% completion, resumable indexes open up a transaction and go back to paused for what ought to be a moment. + Updating statistics is one of the things that it can do in this false paused state. + Updating stats can take a while, so we point it out as a likely delay. + It seems that any of the normal operations that happen at the very end of an index build can cause this. + */ + CASE WHEN iro.percent_complete > 99.9 + THEN '. It is probably still running, perhaps updating statistics.' + ELSE ' after ' + CONVERT(NVARCHAR(30), iro.total_execution_time) + + ' minute(s). This blocks DDL, fails transactions needing table-level X locks, and can pile up ghosts.' + END AS details, + 'Old index: ' + ISNULL(i.index_definition, 'not found. Either the index is new or you need @IncludeInactiveIndexes = 1') AS index_definition, + i.secret_columns, + i.index_usage_summary, + 'New index: ' + iro.reserved_MB_pretty_print + '; Old index: ' + ISNULL(sz.index_size_summary,'not found.') AS index_size_summary, + 'New index: ' + iro.sql_text AS create_tsql, + iro.more_info + FROM #IndexResumableOperations iro + LEFT JOIN #IndexSanity AS i ON i.database_id = iro.database_id + AND i.[object_id] = iro.[object_id] + AND i.index_id = iro.index_id + LEFT JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + WHERE iro.state = 1 + OPTION ( RECOMPILE ); + + RAISERROR(N'check_id 123: Resumable Index Operation Running', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, + [database_name], URL, details, index_definition, secret_columns, + index_usage_summary, index_size_summary, create_tsql, more_info ) + SELECT 123 AS check_id, + i.index_sanity_id, + 10 AS Priority, + N'Resumable Indexing' AS findings_group, + N'Resumable Index Operation Running' AS finding, + iro.[database_name] AS [Database Name], + N'https://www.BrentOzar.com/go/resumable' AS URL, + iro.state_desc + ' on ' + iro.db_schema_table_index + + ' at MAXDOP ' + CONVERT(NVARCHAR(30), iro.last_max_dop_used) + + '. First started ' + CONVERT(NVARCHAR(50), iro.start_time, 120) + '. ' + + CONVERT(NVARCHAR(6), CONVERT(MONEY, iro.percent_complete)) + '% complete after ' + + CONVERT(NVARCHAR(30), iro.total_execution_time) + + ' minute(s). This blocks DDL and can pile up ghosts.' AS details, + 'Old index: ' + ISNULL(i.index_definition, 'not found. Either the index is new or you need @IncludeInactiveIndexes = 1') AS index_definition, + i.secret_columns, + i.index_usage_summary, + 'New index: ' + iro.reserved_MB_pretty_print + '; Old index: ' + ISNULL(sz.index_size_summary,'not found.') AS index_size_summary, + 'New index: ' + iro.sql_text AS create_tsql, + iro.more_info + FROM #IndexResumableOperations iro + LEFT JOIN #IndexSanity AS i ON i.database_id = iro.database_id + AND i.[object_id] = iro.[object_id] + AND i.index_id = iro.index_id + LEFT JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + WHERE iro.state = 0 + OPTION ( RECOMPILE ); + ---------------------------------------- --Aggressive Indexes: Check_id 10-19 ---------------------------------------- From e5e1ec6a3ee484df62756ff38405df78fa2e946d Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Sun, 6 Apr 2025 06:31:36 -0700 Subject: [PATCH 611/662] #3620 paused index operation Added warnings in sp_BlitzIndex and sp_Blitz. Closes #3620. --- Documentation/sp_Blitz_Checks_by_Priority.md | 5 +- sp_Blitz.sql | 84 +++++++++----------- sp_BlitzIndex.sql | 62 ++++++++++----- 3 files changed, 82 insertions(+), 69 deletions(-) diff --git a/Documentation/sp_Blitz_Checks_by_Priority.md b/Documentation/sp_Blitz_Checks_by_Priority.md index 5c12ec039..8f964ffe5 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: 266. -If you want to add a new one, start at 267. +CURRENT HIGH CHECKID: 267. +If you want to add a new one, start at 268. | Priority | FindingsGroup | Finding | URL | CheckID | |----------|-----------------------------|---------------------------------------------------------|------------------------------------------------------------------------|----------| @@ -276,6 +276,7 @@ If you want to add a new one, start at 267. | 210 | Non-Default Database Scoped Config | Legacy CE | https://www.BrentOzar.com/go/dbscope | 195 | | 210 | Non-Default Database Scoped Config | Parameter Sniffing | https://www.BrentOzar.com/go/dbscope | 196 | | 210 | Non-Default Database Scoped Config | Query Optimizer Hotfixes | https://www.BrentOzar.com/go/dbscope | 197 | +| 210 | Non-Default Database Scoped Config | All Others | https://www.BrentOzar.com/go/dbscope | 267 | | 230 | Security | Control Server Permissions | https://www.BrentOzar.com/go/sa | 104 | | 230 | Security | Database Owner <> SA | https://www.BrentOzar.com/go/owndb | 55 | | 230 | Security | Database Owner is Unknown | | 213 | diff --git a/sp_Blitz.sql b/sp_Blitz.sql index 517c62e6a..e568f5886 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -7749,52 +7749,44 @@ IF @ProductVersionMajor >= 10 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d] through [%d] and [%d] through [%d].', 0, 1, 194, 197, 237, 255) WITH NOWAIT; INSERT INTO #DatabaseScopedConfigurationDefaults (configuration_id, [name], default_value, default_value_for_secondary, CheckID) - SELECT 1, 'MAXDOP', '0', NULL, 194 - UNION ALL - SELECT 2, 'LEGACY_CARDINALITY_ESTIMATION', '0', NULL, 195 - UNION ALL - SELECT 3, 'PARAMETER_SNIFFING', '1', NULL, 196 - UNION ALL - SELECT 4, 'QUERY_OPTIMIZER_HOTFIXES', '0', NULL, 197 - UNION ALL - SELECT 6, 'IDENTITY_CACHE', '1', NULL, 237 - UNION ALL - SELECT 7, 'INTERLEAVED_EXECUTION_TVF', '1', NULL, 238 - UNION ALL - SELECT 8, 'BATCH_MODE_MEMORY_GRANT_FEEDBACK', '1', NULL, 239 - UNION ALL - SELECT 9, 'BATCH_MODE_ADAPTIVE_JOINS', '1', NULL, 240 - UNION ALL - SELECT 10, 'TSQL_SCALAR_UDF_INLINING', '1', NULL, 241 - UNION ALL - SELECT 11, 'ELEVATE_ONLINE', 'OFF', NULL, 242 - UNION ALL - SELECT 12, 'ELEVATE_RESUMABLE', 'OFF', NULL, 243 - UNION ALL - SELECT 13, 'OPTIMIZE_FOR_AD_HOC_WORKLOADS', '0', NULL, 244 - UNION ALL - SELECT 14, 'XTP_PROCEDURE_EXECUTION_STATISTICS', '0', NULL, 245 - UNION ALL - SELECT 15, 'XTP_QUERY_EXECUTION_STATISTICS', '0', NULL, 246 - UNION ALL - SELECT 16, 'ROW_MODE_MEMORY_GRANT_FEEDBACK', '1', NULL, 247 - UNION ALL - SELECT 17, 'ISOLATE_SECURITY_POLICY_CARDINALITY', '0', NULL, 248 - UNION ALL - SELECT 18, 'BATCH_MODE_ON_ROWSTORE', '1', NULL, 249 - UNION ALL - SELECT 19, 'DEFERRED_COMPILATION_TV', '1', NULL, 250 - UNION ALL - SELECT 20, 'ACCELERATED_PLAN_FORCING', '1', NULL, 251 - UNION ALL - SELECT 21, 'GLOBAL_TEMPORARY_TABLE_AUTO_DROP', '1', NULL, 252 - UNION ALL - SELECT 22, 'LIGHTWEIGHT_QUERY_PROFILING', '1', NULL, 253 - UNION ALL - SELECT 23, 'VERBOSE_TRUNCATION_WARNINGS', '1', NULL, 254 - UNION ALL - SELECT 24, 'LAST_QUERY_PLAN_STATS', '0', NULL, 255; - EXEC dbo.sp_MSforeachdb 'USE [?]; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; INSERT INTO #BlitzResults (CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details) + VALUES + (1, 'MAXDOP', '0', NULL, 194), + (2, 'LEGACY_CARDINALITY_ESTIMATION', '0', NULL, 195), + (3, 'PARAMETER_SNIFFING', '1', NULL, 196), + (4, 'QUERY_OPTIMIZER_HOTFIXES', '0', NULL, 197), + (6, 'IDENTITY_CACHE', '1', NULL, 237), + (7, 'INTERLEAVED_EXECUTION_TVF', '1', NULL, 238), + (8, 'BATCH_MODE_MEMORY_GRANT_FEEDBACK', '1', NULL, 239), + (9, 'BATCH_MODE_ADAPTIVE_JOINS', '1', NULL, 240), + (10, 'TSQL_SCALAR_UDF_INLINING', '1', NULL, 241), + (11, 'ELEVATE_ONLINE', 'OFF', NULL, 242), + (12, 'ELEVATE_RESUMABLE', 'OFF', NULL, 243), + (13, 'OPTIMIZE_FOR_AD_HOC_WORKLOADS', '0', NULL, 244), + (14, 'XTP_PROCEDURE_EXECUTION_STATISTICS', '0', NULL, 245), + (15, 'XTP_QUERY_EXECUTION_STATISTICS', '0', NULL, 246), + (16, 'ROW_MODE_MEMORY_GRANT_FEEDBACK', '1', NULL, 247), + (17, 'ISOLATE_SECURITY_POLICY_CARDINALITY', '0', NULL, 248), + (18, 'BATCH_MODE_ON_ROWSTORE', '1', NULL, 249), + (19, 'DEFERRED_COMPILATION_TV', '1', NULL, 250), + (20, 'ACCELERATED_PLAN_FORCING', '1', NULL, 251), + (21, 'GLOBAL_TEMPORARY_TABLE_AUTO_DROP', '1', NULL, 252), + (22, 'LIGHTWEIGHT_QUERY_PROFILING', '1', NULL, 253), + (23, 'VERBOSE_TRUNCATION_WARNINGS', '1', NULL, 254), + (24, 'LAST_QUERY_PLAN_STATS', '0', NULL, 255), + (25, 'PAUSED_RESUMABLE_INDEX_ABORT_DURATION_MINUTES', '1440', NULL, 267), + (26, 'DW_COMPATIBILITY_LEVEL', '0', NULL, 267), + (27, 'EXEC_QUERY_STATS_FOR_SCALAR_FUNCTIONS', '1', NULL, 267), + (28, 'PARAMETER_SENSITIVE_PLAN_OPTIMIZATION', '1', NULL, 267), + (29, 'ASYNC_STATS_UPDATE_WAIT_AT_LOW_PRIORITY', '0', NULL, 267), + (31, 'CE_FEEDBACK', '1', NULL, 267), + (33, 'MEMORY_GRANT_FEEDBACK_PERSISTENCE', '1', NULL, 267), + (34, 'MEMORY_GRANT_FEEDBACK_PERCENTILE_GRANT', '1', NULL, 267), + (35, 'OPTIMIZED_PLAN_FORCING', '1', NULL, 267), + (37, 'DOP_FEEDBACK', '0', NULL, 267), + (38, 'LEDGER_DIGEST_STORAGE_ENDPOINT', 'OFF', NULL, 267), + (39, 'FORCE_SHOWPLAN_RUNTIME_PARAMETER_COLLECTION', '0', NULL, 267); + +EXEC dbo.sp_MSforeachdb 'USE [?]; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; INSERT INTO #BlitzResults (CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details) SELECT def1.CheckID, DB_NAME(), 210, ''Non-Default Database Scoped Config'', dsc.[name], ''https://www.brentozar.com/go/dbscope'', (''Set value: '' + COALESCE(CAST(dsc.value AS NVARCHAR(100)),''Empty'') + '' Default: '' + COALESCE(CAST(def1.default_value AS NVARCHAR(100)),''Empty'') + '' Set value for secondary: '' + COALESCE(CAST(dsc.value_for_secondary AS NVARCHAR(100)),''Empty'') + '' Default value for secondary: '' + COALESCE(CAST(def1.default_value_for_secondary AS NVARCHAR(100)),''Empty'')) FROM [?].sys.database_scoped_configurations dsc INNER JOIN #DatabaseScopedConfigurationDefaults def1 ON dsc.configuration_id = def1.configuration_id diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index 6ae4e5968..63519ff4a 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -131,6 +131,7 @@ DECLARE @ColumnList NVARCHAR(MAX); DECLARE @ColumnListWithApostrophes NVARCHAR(MAX); DECLARE @PartitionCount INT; DECLARE @OptimizeForSequentialKey BIT = 0; +DECLARE @ResumableIndexesDisappearAfter INT = 0; DECLARE @StringToExecute NVARCHAR(MAX); /* If user was lazy and just used @ObjectName with a fully qualified table name, then lets parse out the various parts */ @@ -2612,7 +2613,17 @@ OPTION (RECOMPILE);'; [object_id], index_id, name, sql_text, last_max_dop_used, partition_number, state, state_desc, start_time, last_pause_time, total_execution_time, percent_complete, page_count ) EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; - END TRY + + SET @dsql=N'SELECT @ResumableIndexesDisappearAfter = CAST(value AS INT) + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.database_scoped_configurations + WHERE name = ''PAUSED_RESUMABLE_INDEX_ABORT_DURATION_MINUTES'' + AND value > 0;' + EXEC sp_executesql @dsql, N'@ResumableIndexesDisappearAfter INT OUT', @ResumableIndexesDisappearAfter out; + + IF @ResumableIndexesDisappearAfter IS NULL + SET @ResumableIndexesDisappearAfter = 0; + + END TRY BEGIN CATCH RAISERROR (N'Skipping #IndexResumableOperations population due to error, typically low permissions', 0,1) WITH NOWAIT; END CATCH @@ -3294,17 +3305,22 @@ BEGIN SELECT N'Resumable Index Operation' AS finding, N'This may invalidate your analysis!' AS warning, - iro.state_desc + ' on ' + iro.db_schema_table_index + + iro.state_desc + N' on ' + iro.db_schema_table_index + CASE iro.state WHEN 0 THEN - ' at MAXDOP ' + CONVERT(NVARCHAR(30), iro.last_max_dop_used) + - '. First started ' + CONVERT(NVARCHAR(50), iro.start_time, 120) + '. ' + - CONVERT(NVARCHAR(6), CONVERT(MONEY, iro.percent_complete)) + '% complete after ' + + N' at MAXDOP ' + CONVERT(NVARCHAR(30), iro.last_max_dop_used) + + N'. First started ' + CONVERT(NVARCHAR(50), iro.start_time, 120) + N'. ' + + CONVERT(NVARCHAR(6), CONVERT(MONEY, iro.percent_complete)) + N'% complete after ' + CONVERT(NVARCHAR(30), iro.total_execution_time) + - ' minute(s). This blocks DDL and can pile up ghosts.' + N' minute(s). ' + + CASE WHEN @ResumableIndexesDisappearAfter > 0 + THEN N' Will be automatically removed by the database server at ' + CONVERT(NVARCHAR(50), (DATEADD(mi, @ResumableIndexesDisappearAfter, iro.last_pause_time)), 121) + N'. ' + ELSE N' Will not be automatically removed by the database server. ' + END + + N'This blocks DDL and can pile up ghosts.' WHEN 1 THEN - ' since ' + CONVERT(NVARCHAR(50), iro.last_pause_time, 120) + '. ' + - CONVERT(NVARCHAR(6), CONVERT(MONEY, iro.percent_complete)) + '% complete' + + N' since ' + CONVERT(NVARCHAR(50), iro.last_pause_time, 120) + N'. ' + + CONVERT(NVARCHAR(6), CONVERT(MONEY, iro.percent_complete)) + N'% complete' + /* At 100% completion, resumable indexes open up a transaction and go back to paused for what ought to be a moment. Updating statistics is one of the things that it can do in this false paused state. @@ -3312,11 +3328,11 @@ BEGIN It seems that any of the normal operations that happen at the very end of an index build can cause this. */ CASE WHEN iro.percent_complete > 99.9 - THEN '. It is probably still running, perhaps updating statistics.' - ELSE ' after ' + CONVERT(NVARCHAR(30), iro.total_execution_time) - + ' minute(s). This blocks DDL, fails transactions needing table-level X locks, and can pile up ghosts.' + THEN N'. It is probably still running, perhaps updating statistics.' + ELSE N' after ' + CONVERT(NVARCHAR(30), iro.total_execution_time) + + N' minute(s). This blocks DDL, fails transactions needing table-level X locks, and can pile up ghosts.' END - ELSE ' which is an undocumented resumable index state description.' + ELSE N' which is an undocumented resumable index state description.' END AS details, N'https://www.BrentOzar.com/go/resumable' AS URL, iro.more_info AS [More Info] @@ -3738,9 +3754,9 @@ BEGIN N'Resumable Index Operation Paused' AS finding, iro.[database_name] AS [Database Name], N'https://www.BrentOzar.com/go/resumable' AS URL, - iro.state_desc + ' on ' + iro.db_schema_table_index + - ' since ' + CONVERT(NVARCHAR(50), iro.last_pause_time, 120) + '. ' + - CONVERT(NVARCHAR(6), CONVERT(MONEY, iro.percent_complete)) + '% complete' + + iro.state_desc + N' on ' + iro.db_schema_table_index + + N' since ' + CONVERT(NVARCHAR(50), iro.last_pause_time, 120) + N'. ' + + CONVERT(NVARCHAR(6), CONVERT(MONEY, iro.percent_complete)) + N'% complete' + /* At 100% completion, resumable indexes open up a transaction and go back to paused for what ought to be a moment. Updating statistics is one of the things that it can do in this false paused state. @@ -3748,15 +3764,19 @@ BEGIN It seems that any of the normal operations that happen at the very end of an index build can cause this. */ CASE WHEN iro.percent_complete > 99.9 - THEN '. It is probably still running, perhaps updating statistics.' - ELSE ' after ' + CONVERT(NVARCHAR(30), iro.total_execution_time) - + ' minute(s). This blocks DDL, fails transactions needing table-level X locks, and can pile up ghosts.' + THEN N'. It is probably still running, perhaps updating statistics.' + ELSE N' after ' + CONVERT(NVARCHAR(30), iro.total_execution_time) + + N' minute(s). This blocks DDL, fails transactions needing table-level X locks, and can pile up ghosts. ' + END + + CASE WHEN @ResumableIndexesDisappearAfter > 0 + THEN N' Will be automatically removed by the database server at ' + CONVERT(NVARCHAR(50), (DATEADD(mi, @ResumableIndexesDisappearAfter, iro.last_pause_time)), 121) + N'. ' + ELSE N' Will not be automatically removed by the database server. ' END AS details, - 'Old index: ' + ISNULL(i.index_definition, 'not found. Either the index is new or you need @IncludeInactiveIndexes = 1') AS index_definition, + N'Old index: ' + ISNULL(i.index_definition, N'not found. Either the index is new or you need @IncludeInactiveIndexes = 1') AS index_definition, i.secret_columns, i.index_usage_summary, - 'New index: ' + iro.reserved_MB_pretty_print + '; Old index: ' + ISNULL(sz.index_size_summary,'not found.') AS index_size_summary, - 'New index: ' + iro.sql_text AS create_tsql, + N'New index: ' + iro.reserved_MB_pretty_print + N'; Old index: ' + ISNULL(sz.index_size_summary,'not found.') AS index_size_summary, + N'New index: ' + iro.sql_text AS create_tsql, iro.more_info FROM #IndexResumableOperations iro LEFT JOIN #IndexSanity AS i ON i.database_id = iro.database_id From 273ed2938b48e9ee4a9beccbdefcd57abcf7ebae Mon Sep 17 00:00:00 2001 From: RG Date: Sun, 6 Apr 2025 17:47:07 +0100 Subject: [PATCH 612/662] Added warning for non-clustered indexes on history tables. These cause MERGEs on the main table to fail. Also put #TemportalTables in the debug output. It was missing. --- .../sp_BlitzIndex_Checks_by_Priority.md | 129 +++++++++--------- sp_BlitzIndex.sql | 44 +++++- 2 files changed, 104 insertions(+), 69 deletions(-) diff --git a/Documentation/sp_BlitzIndex_Checks_by_Priority.md b/Documentation/sp_BlitzIndex_Checks_by_Priority.md index d6af3eec2..619438d0f 100644 --- a/Documentation/sp_BlitzIndex_Checks_by_Priority.md +++ b/Documentation/sp_BlitzIndex_Checks_by_Priority.md @@ -6,68 +6,69 @@ 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: 123 -If you want to add a new check, start at 124. +CURRENT HIGH CHECKID: 124 +If you want to add a new check, start at 125. -| 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 | -| 90 | Statistics Warnings | Low Sampling Rates | https://www.brentozar.com/go/stats | 91 | -| 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 | 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 | 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 | 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 63519ff4a..f7624172d 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -777,7 +777,8 @@ IF OBJECT_ID('tempdb..#dm_db_index_operational_stats') IS NOT NULL history_schema_name NVARCHAR(128) NOT NULL, start_column_name NVARCHAR(128) NOT NULL, end_column_name NVARCHAR(128) NOT NULL, - period_name NVARCHAR(128) NOT NULL + period_name NVARCHAR(128) NOT NULL, + history_table_object_id INT NULL ); CREATE TABLE #CheckConstraints @@ -2482,7 +2483,8 @@ OPTION (RECOMPILE);'; oa.htn AS history_table_name, c1.name AS start_column_name, c2.name AS end_column_name, - p.name AS period_name + p.name AS period_name, + t.history_table_id AS history_table_object_id FROM ' + QUOTENAME(@DatabaseName) + N'.sys.periods AS p INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.tables AS t ON p.object_id = t.object_id @@ -2508,7 +2510,7 @@ OPTION (RECOMPILE);'; RAISERROR('@dsql is null',16,1); INSERT #TemporalTables ( database_name, database_id, schema_name, table_name, history_schema_name, - history_table_name, start_column_name, end_column_name, period_name ) + history_table_name, start_column_name, end_column_name, period_name, history_table_object_id ) EXEC sp_executesql @dsql; END; @@ -3077,7 +3079,8 @@ BEGIN SELECT '#Statistics' AS table_name, * FROM #Statistics; SELECT '#PartitionCompressionInfo' AS table_name, * FROM #PartitionCompressionInfo; SELECT '#ComputedColumns' AS table_name, * FROM #ComputedColumns; - SELECT '#TraceStatus' AS table_name, * FROM #TraceStatus; + SELECT '#TraceStatus' AS table_name, * FROM #TraceStatus; + SELECT '#TemporalTables' AS table_name, * FROM #TemporalTables; SELECT '#CheckConstraints' AS table_name, * FROM #CheckConstraints; SELECT '#FilteredIndexes' AS table_name, * FROM #FilteredIndexes; SELECT '#IndexResumableOperations' AS table_name, * FROM #IndexResumableOperations; @@ -3974,7 +3977,38 @@ BEGIN WHERE i.filter_columns_not_in_index IS NOT NULL ORDER BY i.db_schema_object_indexid OPTION ( RECOMPILE ); - + + RAISERROR(N'check_id 124: History Table With NonClustered Index', 0,1) WITH NOWAIT; + + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 124 AS check_id, + i.index_sanity_id, + 80 AS Priority, + N'Abnormal Design Pattern' AS findings_group, + N'History Table With NonClustered Index' AS finding, + i.[database_name] AS [Database Name], + N'https://sqlserverfast.com/blog/hugo/2023/09/an-update-on-merge/' AS URL, + N'The history table ' + + QUOTENAME(hist.history_schema_name) + + '.' + + QUOTENAME(hist.history_table_name) + + ' has a non-clustered index. This can cause MERGEs on the main table to fail! See item 8 on the URL.' + AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + sz.index_size_summary + FROM #IndexSanity i + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + JOIN #TemporalTables hist + ON i.[object_id] = hist.history_table_object_id + AND i.[database_id] = hist.[database_id] + WHERE hist.history_table_object_id IS NOT NULL + AND i.index_type = 2 /* NC only */ + ORDER BY i.db_schema_object_indexid + OPTION ( RECOMPILE ); + ---------------------------------------- --Self Loathing Indexes : Check_id 40-49 ---------------------------------------- From d217ac6b043c3b774ff075364ac6d9ee66687bf1 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Mon, 7 Apr 2025 04:10:22 -0700 Subject: [PATCH 613/662] 2025-04-07 sqlserverversions Adding latest SQL Server 2022 and 2019 builds. --- SqlServerVersions.sql | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/SqlServerVersions.sql b/SqlServerVersions.sql index 01c4d63af..4866d6e22 100644 --- a/SqlServerVersions.sql +++ b/SqlServerVersions.sql @@ -42,6 +42,8 @@ INSERT INTO dbo.SqlServerVersions (MajorVersionNumber, MinorVersionNumber, Branch, [Url], ReleaseDate, MainstreamSupportEndDate, ExtendedSupportEndDate, MajorVersionName, MinorVersionName) VALUES /*2022*/ + (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'), (16, 4165, 'CU16', 'https://support.microsoft.com/en-us/help/5048033', '2024-11-14', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 16'), (16, 4150, 'CU15 GDR', 'https://support.microsoft.com/en-us/help/5046059', '2024-10-08', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 15 GDR'), (16, 4145, 'CU15', 'https://support.microsoft.com/en-us/help/5041321', '2024-09-25', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 15'), @@ -64,6 +66,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, 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, 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'), From 19841c8c7076d0a68d1553bb1924b5a91be76f0b Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Mon, 7 Apr 2025 04:22:56 -0700 Subject: [PATCH 614/662] 2025-04-07 release prep Bumping version numbers and dates. --- Install-All-Scripts.sql | 964 +++++++++++++++++++++++++++++++++------- Install-Azure.sql | 864 +++++++++++++++++++++++++++++++---- 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 +- 12 files changed, 1592 insertions(+), 256 deletions(-) diff --git a/Install-All-Scripts.sql b/Install-All-Scripts.sql index b9465696d..83c596c58 100644 --- a/Install-All-Scripts.sql +++ b/Install-All-Scripts.sql @@ -38,7 +38,7 @@ AS SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.23', @VersionDate = '20241228'; + SELECT @Version = '8.24', @VersionDate = '20250407'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -4076,7 +4076,7 @@ AS ''Informational'' AS FindingGroup , ''Backup Compression Default Off'' AS Finding , ''https://www.brentozar.com/go/backup'' AS URL , - ''Uncompressed full backups have happened recently, and backup compression is not turned on at the server level. Backup compression is included with SQL Server 2008R2 & newer, even in Standard Edition. We recommend turning backup compression on by default so that ad-hoc backups will get compressed.'' + ''Uncompressed full backups have happened recently, and backup compression is not turned on at the server level. Backup compression is included with Standard Edition. We recommend turning backup compression on by default so that ad-hoc backups will get compressed.'' FROM sys.configurations WHERE configuration_id = 1579 AND CAST(value_in_use AS INT) = 0 AND EXISTS (SELECT * FROM msdb.dbo.backupset WHERE backup_size = compressed_backup_size AND type = ''D'' AND backup_finish_date >= DATEADD(DD, -14, GETDATE())) OPTION (RECOMPILE);'; @@ -7749,52 +7749,44 @@ IF @ProductVersionMajor >= 10 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d] through [%d] and [%d] through [%d].', 0, 1, 194, 197, 237, 255) WITH NOWAIT; INSERT INTO #DatabaseScopedConfigurationDefaults (configuration_id, [name], default_value, default_value_for_secondary, CheckID) - SELECT 1, 'MAXDOP', '0', NULL, 194 - UNION ALL - SELECT 2, 'LEGACY_CARDINALITY_ESTIMATION', '0', NULL, 195 - UNION ALL - SELECT 3, 'PARAMETER_SNIFFING', '1', NULL, 196 - UNION ALL - SELECT 4, 'QUERY_OPTIMIZER_HOTFIXES', '0', NULL, 197 - UNION ALL - SELECT 6, 'IDENTITY_CACHE', '1', NULL, 237 - UNION ALL - SELECT 7, 'INTERLEAVED_EXECUTION_TVF', '1', NULL, 238 - UNION ALL - SELECT 8, 'BATCH_MODE_MEMORY_GRANT_FEEDBACK', '1', NULL, 239 - UNION ALL - SELECT 9, 'BATCH_MODE_ADAPTIVE_JOINS', '1', NULL, 240 - UNION ALL - SELECT 10, 'TSQL_SCALAR_UDF_INLINING', '1', NULL, 241 - UNION ALL - SELECT 11, 'ELEVATE_ONLINE', 'OFF', NULL, 242 - UNION ALL - SELECT 12, 'ELEVATE_RESUMABLE', 'OFF', NULL, 243 - UNION ALL - SELECT 13, 'OPTIMIZE_FOR_AD_HOC_WORKLOADS', '0', NULL, 244 - UNION ALL - SELECT 14, 'XTP_PROCEDURE_EXECUTION_STATISTICS', '0', NULL, 245 - UNION ALL - SELECT 15, 'XTP_QUERY_EXECUTION_STATISTICS', '0', NULL, 246 - UNION ALL - SELECT 16, 'ROW_MODE_MEMORY_GRANT_FEEDBACK', '1', NULL, 247 - UNION ALL - SELECT 17, 'ISOLATE_SECURITY_POLICY_CARDINALITY', '0', NULL, 248 - UNION ALL - SELECT 18, 'BATCH_MODE_ON_ROWSTORE', '1', NULL, 249 - UNION ALL - SELECT 19, 'DEFERRED_COMPILATION_TV', '1', NULL, 250 - UNION ALL - SELECT 20, 'ACCELERATED_PLAN_FORCING', '1', NULL, 251 - UNION ALL - SELECT 21, 'GLOBAL_TEMPORARY_TABLE_AUTO_DROP', '1', NULL, 252 - UNION ALL - SELECT 22, 'LIGHTWEIGHT_QUERY_PROFILING', '1', NULL, 253 - UNION ALL - SELECT 23, 'VERBOSE_TRUNCATION_WARNINGS', '1', NULL, 254 - UNION ALL - SELECT 24, 'LAST_QUERY_PLAN_STATS', '0', NULL, 255; - EXEC dbo.sp_MSforeachdb 'USE [?]; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; INSERT INTO #BlitzResults (CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details) + VALUES + (1, 'MAXDOP', '0', NULL, 194), + (2, 'LEGACY_CARDINALITY_ESTIMATION', '0', NULL, 195), + (3, 'PARAMETER_SNIFFING', '1', NULL, 196), + (4, 'QUERY_OPTIMIZER_HOTFIXES', '0', NULL, 197), + (6, 'IDENTITY_CACHE', '1', NULL, 237), + (7, 'INTERLEAVED_EXECUTION_TVF', '1', NULL, 238), + (8, 'BATCH_MODE_MEMORY_GRANT_FEEDBACK', '1', NULL, 239), + (9, 'BATCH_MODE_ADAPTIVE_JOINS', '1', NULL, 240), + (10, 'TSQL_SCALAR_UDF_INLINING', '1', NULL, 241), + (11, 'ELEVATE_ONLINE', 'OFF', NULL, 242), + (12, 'ELEVATE_RESUMABLE', 'OFF', NULL, 243), + (13, 'OPTIMIZE_FOR_AD_HOC_WORKLOADS', '0', NULL, 244), + (14, 'XTP_PROCEDURE_EXECUTION_STATISTICS', '0', NULL, 245), + (15, 'XTP_QUERY_EXECUTION_STATISTICS', '0', NULL, 246), + (16, 'ROW_MODE_MEMORY_GRANT_FEEDBACK', '1', NULL, 247), + (17, 'ISOLATE_SECURITY_POLICY_CARDINALITY', '0', NULL, 248), + (18, 'BATCH_MODE_ON_ROWSTORE', '1', NULL, 249), + (19, 'DEFERRED_COMPILATION_TV', '1', NULL, 250), + (20, 'ACCELERATED_PLAN_FORCING', '1', NULL, 251), + (21, 'GLOBAL_TEMPORARY_TABLE_AUTO_DROP', '1', NULL, 252), + (22, 'LIGHTWEIGHT_QUERY_PROFILING', '1', NULL, 253), + (23, 'VERBOSE_TRUNCATION_WARNINGS', '1', NULL, 254), + (24, 'LAST_QUERY_PLAN_STATS', '0', NULL, 255), + (25, 'PAUSED_RESUMABLE_INDEX_ABORT_DURATION_MINUTES', '1440', NULL, 267), + (26, 'DW_COMPATIBILITY_LEVEL', '0', NULL, 267), + (27, 'EXEC_QUERY_STATS_FOR_SCALAR_FUNCTIONS', '1', NULL, 267), + (28, 'PARAMETER_SENSITIVE_PLAN_OPTIMIZATION', '1', NULL, 267), + (29, 'ASYNC_STATS_UPDATE_WAIT_AT_LOW_PRIORITY', '0', NULL, 267), + (31, 'CE_FEEDBACK', '1', NULL, 267), + (33, 'MEMORY_GRANT_FEEDBACK_PERSISTENCE', '1', NULL, 267), + (34, 'MEMORY_GRANT_FEEDBACK_PERCENTILE_GRANT', '1', NULL, 267), + (35, 'OPTIMIZED_PLAN_FORCING', '1', NULL, 267), + (37, 'DOP_FEEDBACK', '0', NULL, 267), + (38, 'LEDGER_DIGEST_STORAGE_ENDPOINT', 'OFF', NULL, 267), + (39, 'FORCE_SHOWPLAN_RUNTIME_PARAMETER_COLLECTION', '0', NULL, 267); + +EXEC dbo.sp_MSforeachdb 'USE [?]; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; INSERT INTO #BlitzResults (CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details) SELECT def1.CheckID, DB_NAME(), 210, ''Non-Default Database Scoped Config'', dsc.[name], ''https://www.brentozar.com/go/dbscope'', (''Set value: '' + COALESCE(CAST(dsc.value AS NVARCHAR(100)),''Empty'') + '' Default: '' + COALESCE(CAST(def1.default_value AS NVARCHAR(100)),''Empty'') + '' Set value for secondary: '' + COALESCE(CAST(dsc.value_for_secondary AS NVARCHAR(100)),''Empty'') + '' Default value for secondary: '' + COALESCE(CAST(def1.default_value_for_secondary AS NVARCHAR(100)),''Empty'')) FROM [?].sys.database_scoped_configurations dsc INNER JOIN #DatabaseScopedConfigurationDefaults def1 ON dsc.configuration_id = def1.configuration_id @@ -10565,7 +10557,7 @@ AS SET NOCOUNT ON; SET STATISTICS XML OFF; -SELECT @Version = '8.23', @VersionDate = '20241228'; +SELECT @Version = '8.24', @VersionDate = '20250407'; IF(@VersionCheckMode = 1) BEGIN @@ -11443,7 +11435,7 @@ AS SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.23', @VersionDate = '20241228'; + SELECT @Version = '8.24', @VersionDate = '20250407'; IF(@VersionCheckMode = 1) BEGIN @@ -13217,7 +13209,8 @@ ALTER PROCEDURE dbo.sp_BlitzCache @MinutesBack INT = NULL, @Version VARCHAR(30) = NULL OUTPUT, @VersionDate DATETIME = NULL OUTPUT, - @VersionCheckMode BIT = 0 + @VersionCheckMode BIT = 0, + @KeepCRLF BIT = 0 WITH RECOMPILE AS BEGIN @@ -13225,7 +13218,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.23', @VersionDate = '20241228'; +SELECT @Version = '8.24', @VersionDate = '20250407'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -13427,7 +13420,12 @@ IF @Help = 1 UNION ALL SELECT N'@VersionCheckMode', N'BIT', - N'Setting this to 1 will make the procedure stop after setting @Version and @VersionDate.'; + N'Setting this to 1 will make the procedure stop after setting @Version and @VersionDate.' + + UNION ALL + SELECT N'@KeepCRLF', + N'BIT', + N'Retain CR/LF in query text to avoid issues caused by line comments.'; /* Column definitions */ @@ -15704,7 +15702,10 @@ SET PercentCPU = y.PercentCPU, /* Strip newlines and tabs. Tabs are replaced with multiple spaces so that the later whitespace trim will completely eliminate them */ - QueryText = REPLACE(REPLACE(REPLACE(QueryText, @cr, ' '), @lf, ' '), @tab, ' ') + QueryText = CASE WHEN @KeepCRLF = 1 + THEN REPLACE(QueryText, @tab, ' ') + ELSE REPLACE(REPLACE(REPLACE(QueryText, @cr, ' '), @lf, ' '), @tab, ' ') + END FROM ( SELECT PlanHandle, CASE @total_cpu WHEN 0 THEN 0 @@ -15760,7 +15761,10 @@ SET PercentCPU = y.PercentCPU, /* Strip newlines and tabs. Tabs are replaced with multiple spaces so that the later whitespace trim will completely eliminate them */ - QueryText = REPLACE(REPLACE(REPLACE(QueryText, @cr, ' '), @lf, ' '), @tab, ' ') + QueryText = CASE WHEN @KeepCRLF = 1 + THEN REPLACE(QueryText, @tab, ' ') + ELSE REPLACE(REPLACE(REPLACE(QueryText, @cr, ' '), @lf, ' '), @tab, ' ') + END FROM ( SELECT DatabaseName, SqlHandle, @@ -20600,7 +20604,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.23', @VersionDate = '20241228'; +SELECT @Version = '8.24', @VersionDate = '20250407'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -20682,6 +20686,7 @@ DECLARE @ColumnList NVARCHAR(MAX); DECLARE @ColumnListWithApostrophes NVARCHAR(MAX); DECLARE @PartitionCount INT; DECLARE @OptimizeForSequentialKey BIT = 0; +DECLARE @ResumableIndexesDisappearAfter INT = 0; DECLARE @StringToExecute NVARCHAR(MAX); /* If user was lazy and just used @ObjectName with a fully qualified table name, then lets parse out the various parts */ @@ -20812,8 +20817,11 @@ IF OBJECT_ID('tempdb..#FilteredIndexes') IS NOT NULL DROP TABLE #FilteredIndexes; IF OBJECT_ID('tempdb..#Ignore_Databases') IS NOT NULL - DROP TABLE #Ignore_Databases - + DROP TABLE #Ignore_Databases; + +IF OBJECT_ID('tempdb..#IndexResumableOperations') IS NOT NULL + DROP TABLE #IndexResumableOperations; + IF OBJECT_ID('tempdb..#dm_db_partition_stats_etc') IS NOT NULL DROP TABLE #dm_db_partition_stats_etc IF OBJECT_ID('tempdb..#dm_db_index_operational_stats') IS NOT NULL @@ -21324,7 +21332,8 @@ IF OBJECT_ID('tempdb..#dm_db_index_operational_stats') IS NOT NULL history_schema_name NVARCHAR(128) NOT NULL, start_column_name NVARCHAR(128) NOT NULL, end_column_name NVARCHAR(128) NOT NULL, - period_name NVARCHAR(128) NOT NULL + period_name NVARCHAR(128) NOT NULL, + history_table_object_id INT NULL ); CREATE TABLE #CheckConstraints @@ -21354,6 +21363,59 @@ IF OBJECT_ID('tempdb..#dm_db_index_operational_stats') IS NOT NULL column_name NVARCHAR(128) NULL ); + CREATE TABLE #IndexResumableOperations + ( + database_name NVARCHAR(128) NULL, + database_id INT NOT NULL, + schema_name NVARCHAR(128) NOT NULL, + table_name NVARCHAR(128) NOT NULL, + /* + Every following non-computed column has + the same definitions as in + sys.index_resumable_operations. + */ + [object_id] INT NOT NULL, + index_id INT NOT NULL, + [name] NVARCHAR(128) NOT NULL, + /* + We have done nothing to make this query text pleasant + to read. Until somebody has a better idea, we trust + that copying Microsoft's approach is wise. + */ + sql_text NVARCHAR(MAX) NULL, + last_max_dop_used SMALLINT NOT NULL, + partition_number INT NULL, + state TINYINT NOT NULL, + state_desc NVARCHAR(60) NULL, + start_time DATETIME NOT NULL, + last_pause_time DATETIME NULL, + total_execution_time INT NOT NULL, + percent_complete FLOAT NOT NULL, + page_count BIGINT NOT NULL, + /* + sys.indexes will not always have the name of the index + because a resumable CREATE INDEX does not populate + sys.indexes until it is done. + So it is better to work out the full name here + rather than pull it from another temp table. + */ + [db_schema_table_index] AS + [schema_name] + N'.' + [table_name] + N'.' + [name], + /* For convenience. */ + reserved_MB_pretty_print AS + CONVERT(NVARCHAR(100), CONVERT(MONEY, page_count * 8. / 1024.)) + + 'MB and ' + + state_desc, + more_info AS + N'New index: SELECT * FROM ' + QUOTENAME(database_name) + + N'.sys.index_resumable_operations WHERE [object_id] = ' + + CONVERT(NVARCHAR(100), [object_id]) + + N'; Old index: ' + + N'EXEC dbo.sp_BlitzIndex @DatabaseName=' + QUOTENAME([database_name],N'''') + + N', @SchemaName=' + QUOTENAME([schema_name],N'''') + + N', @TableName=' + QUOTENAME([table_name],N'''') + N';' + ); + CREATE TABLE #Ignore_Databases ( DatabaseName NVARCHAR(128), @@ -22976,7 +23038,8 @@ OPTION (RECOMPILE);'; oa.htn AS history_table_name, c1.name AS start_column_name, c2.name AS end_column_name, - p.name AS period_name + p.name AS period_name, + t.history_table_id AS history_table_object_id FROM ' + QUOTENAME(@DatabaseName) + N'.sys.periods AS p INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.tables AS t ON p.object_id = t.object_id @@ -23002,7 +23065,7 @@ OPTION (RECOMPILE);'; RAISERROR('@dsql is null',16,1); INSERT #TemporalTables ( database_name, database_id, schema_name, table_name, history_schema_name, - history_table_name, start_column_name, end_column_name, period_name ) + history_table_name, start_column_name, end_column_name, period_name, history_table_object_id ) EXEC sp_executesql @dsql; END; @@ -23068,8 +23131,63 @@ OPTION (RECOMPILE);'; BEGIN CATCH RAISERROR (N'Skipping #FilteredIndexes population due to error, typically low permissions.', 0,1) WITH NOWAIT; END CATCH + END; + + IF @Mode NOT IN(1, 2, 3) + /* + The sys.index_resumable_operations view was a 2017 addition, so we need to check for it and go dynamic. + */ + AND EXISTS (SELECT * FROM sys.all_objects WHERE name = 'index_resumable_operations') + BEGIN + SET @dsql=N'SELECT @i_DatabaseName AS database_name, + DB_ID(@i_DatabaseName) AS [database_id], + s.name AS schema_name, + t.name AS table_name, + iro.[object_id], + iro.index_id, + iro.name, + iro.sql_text, + iro.last_max_dop_used, + iro.partition_number, + iro.state, + iro.state_desc, + iro.start_time, + iro.last_pause_time, + iro.total_execution_time, + iro.percent_complete, + iro.page_count + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.index_resumable_operations AS iro + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.tables AS t + ON t.object_id = iro.object_id + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s + ON t.schema_id = s.schema_id + OPTION(RECOMPILE);' + + BEGIN TRY + RAISERROR (N'Inserting data into #IndexResumableOperations',0,1) WITH NOWAIT; + INSERT #IndexResumableOperations + ( database_name, database_id, schema_name, table_name, + [object_id], index_id, name, sql_text, last_max_dop_used, partition_number, state, state_desc, + start_time, last_pause_time, total_execution_time, percent_complete, page_count ) + EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; + + SET @dsql=N'SELECT @ResumableIndexesDisappearAfter = CAST(value AS INT) + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.database_scoped_configurations + WHERE name = ''PAUSED_RESUMABLE_INDEX_ABORT_DURATION_MINUTES'' + AND value > 0;' + EXEC sp_executesql @dsql, N'@ResumableIndexesDisappearAfter INT OUT', @ResumableIndexesDisappearAfter out; + + IF @ResumableIndexesDisappearAfter IS NULL + SET @ResumableIndexesDisappearAfter = 0; + + END TRY + BEGIN CATCH + RAISERROR (N'Skipping #IndexResumableOperations population due to error, typically low permissions', 0,1) WITH NOWAIT; + END CATCH + END; + + END; - END; END; END TRY @@ -23516,9 +23634,11 @@ BEGIN SELECT '#Statistics' AS table_name, * FROM #Statistics; SELECT '#PartitionCompressionInfo' AS table_name, * FROM #PartitionCompressionInfo; SELECT '#ComputedColumns' AS table_name, * FROM #ComputedColumns; - SELECT '#TraceStatus' AS table_name, * FROM #TraceStatus; + SELECT '#TraceStatus' AS table_name, * FROM #TraceStatus; + SELECT '#TemporalTables' AS table_name, * FROM #TemporalTables; SELECT '#CheckConstraints' AS table_name, * FROM #CheckConstraints; - SELECT '#FilteredIndexes' AS table_name, * FROM #FilteredIndexes; + SELECT '#FilteredIndexes' AS table_name, * FROM #FilteredIndexes; + SELECT '#IndexResumableOperations' AS table_name, * FROM #IndexResumableOperations; END @@ -23736,7 +23856,55 @@ BEGIN ORDER BY s.auto_created, s.user_created, s.name, hist.step_number;'; EXEC sp_executesql @dsql, N'@ObjectID INT', @ObjectID; END - END + + /* Check for resumable index operations. */ + IF (SELECT TOP (1) [object_id] FROM #IndexResumableOperations WHERE [object_id] = @ObjectID AND database_id = @DatabaseID) IS NOT NULL + BEGIN + SELECT + N'Resumable Index Operation' AS finding, + N'This may invalidate your analysis!' AS warning, + iro.state_desc + N' on ' + iro.db_schema_table_index + + CASE iro.state + WHEN 0 THEN + N' at MAXDOP ' + CONVERT(NVARCHAR(30), iro.last_max_dop_used) + + N'. First started ' + CONVERT(NVARCHAR(50), iro.start_time, 120) + N'. ' + + CONVERT(NVARCHAR(6), CONVERT(MONEY, iro.percent_complete)) + N'% complete after ' + + CONVERT(NVARCHAR(30), iro.total_execution_time) + + N' minute(s). ' + + CASE WHEN @ResumableIndexesDisappearAfter > 0 + THEN N' Will be automatically removed by the database server at ' + CONVERT(NVARCHAR(50), (DATEADD(mi, @ResumableIndexesDisappearAfter, iro.last_pause_time)), 121) + N'. ' + ELSE N' Will not be automatically removed by the database server. ' + END + + N'This blocks DDL and can pile up ghosts.' + WHEN 1 THEN + N' since ' + CONVERT(NVARCHAR(50), iro.last_pause_time, 120) + N'. ' + + CONVERT(NVARCHAR(6), CONVERT(MONEY, iro.percent_complete)) + N'% complete' + + /* + At 100% completion, resumable indexes open up a transaction and go back to paused for what ought to be a moment. + Updating statistics is one of the things that it can do in this false paused state. + Updating stats can take a while, so we point it out as a likely delay. + It seems that any of the normal operations that happen at the very end of an index build can cause this. + */ + CASE WHEN iro.percent_complete > 99.9 + THEN N'. It is probably still running, perhaps updating statistics.' + ELSE N' after ' + CONVERT(NVARCHAR(30), iro.total_execution_time) + + N' minute(s). This blocks DDL, fails transactions needing table-level X locks, and can pile up ghosts.' + END + ELSE N' which is an undocumented resumable index state description.' + END AS details, + N'https://www.BrentOzar.com/go/resumable' AS URL, + iro.more_info AS [More Info] + FROM #IndexResumableOperations AS iro + WHERE iro.database_id = @DatabaseID + AND iro.[object_id] = @ObjectID + OPTION ( RECOMPILE ); + END + ELSE + BEGIN + SELECT N'No resumable index operations.' AS finding; + END; + + END /* END @ShowColumnstoreOnly = 0 */ /* Visualize columnstore index contents. More info: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2584 */ IF 2 = (SELECT SUM(1) FROM sys.all_objects WHERE name IN ('column_store_row_groups','column_store_segments')) @@ -24117,6 +24285,96 @@ BEGIN ORDER BY ips.total_rows DESC, ip.[schema_name], ip.[object_name], ip.key_column_names, ip.include_column_names OPTION ( RECOMPILE ); + ---------------------------------------- + --Resumable Indexing: Check_id 122-123 + ---------------------------------------- + /* + This is more complicated than you would expect! + As of SQL Server 2022, I am aware of 6 cases that we need to check: + 1) A resumable rowstore CREATE INDEX that is currently running + 2) A resumable rowstore CREATE INDEX that is currently paused + 3) A resumable rowstore REBUILD that is currently running + 4) A resumable rowstore REBUILD that is currently paused + 5) A resumable rowstore CREATE INDEX [...] DROP_EXISTING = ON that is currently running + 6) A resumable rowstore CREATE INDEX [...] DROP_EXISTING = ON that is currently paused + In cases 1 and 2, sys.indexes has no data at all about the index in question. + This makes #IndexSanity much harder to use, since it depends on sys.indexes. + We must therefore get as much from #IndexResumableOperations as possible. + */ + RAISERROR(N'check_id 122: Resumable Index Operation Paused', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, + [database_name], URL, details, index_definition, secret_columns, + index_usage_summary, index_size_summary, create_tsql, more_info ) + SELECT 122 AS check_id, + i.index_sanity_id, + 10 AS Priority, + N'Resumable Indexing' AS findings_group, + N'Resumable Index Operation Paused' AS finding, + iro.[database_name] AS [Database Name], + N'https://www.BrentOzar.com/go/resumable' AS URL, + iro.state_desc + N' on ' + iro.db_schema_table_index + + N' since ' + CONVERT(NVARCHAR(50), iro.last_pause_time, 120) + N'. ' + + CONVERT(NVARCHAR(6), CONVERT(MONEY, iro.percent_complete)) + N'% complete' + + /* + At 100% completion, resumable indexes open up a transaction and go back to paused for what ought to be a moment. + Updating statistics is one of the things that it can do in this false paused state. + Updating stats can take a while, so we point it out as a likely delay. + It seems that any of the normal operations that happen at the very end of an index build can cause this. + */ + CASE WHEN iro.percent_complete > 99.9 + THEN N'. It is probably still running, perhaps updating statistics.' + ELSE N' after ' + CONVERT(NVARCHAR(30), iro.total_execution_time) + + N' minute(s). This blocks DDL, fails transactions needing table-level X locks, and can pile up ghosts. ' + END + + CASE WHEN @ResumableIndexesDisappearAfter > 0 + THEN N' Will be automatically removed by the database server at ' + CONVERT(NVARCHAR(50), (DATEADD(mi, @ResumableIndexesDisappearAfter, iro.last_pause_time)), 121) + N'. ' + ELSE N' Will not be automatically removed by the database server. ' + END AS details, + N'Old index: ' + ISNULL(i.index_definition, N'not found. Either the index is new or you need @IncludeInactiveIndexes = 1') AS index_definition, + i.secret_columns, + i.index_usage_summary, + N'New index: ' + iro.reserved_MB_pretty_print + N'; Old index: ' + ISNULL(sz.index_size_summary,'not found.') AS index_size_summary, + N'New index: ' + iro.sql_text AS create_tsql, + iro.more_info + FROM #IndexResumableOperations iro + LEFT JOIN #IndexSanity AS i ON i.database_id = iro.database_id + AND i.[object_id] = iro.[object_id] + AND i.index_id = iro.index_id + LEFT JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + WHERE iro.state = 1 + OPTION ( RECOMPILE ); + + RAISERROR(N'check_id 123: Resumable Index Operation Running', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, + [database_name], URL, details, index_definition, secret_columns, + index_usage_summary, index_size_summary, create_tsql, more_info ) + SELECT 123 AS check_id, + i.index_sanity_id, + 10 AS Priority, + N'Resumable Indexing' AS findings_group, + N'Resumable Index Operation Running' AS finding, + iro.[database_name] AS [Database Name], + N'https://www.BrentOzar.com/go/resumable' AS URL, + iro.state_desc + ' on ' + iro.db_schema_table_index + + ' at MAXDOP ' + CONVERT(NVARCHAR(30), iro.last_max_dop_used) + + '. First started ' + CONVERT(NVARCHAR(50), iro.start_time, 120) + '. ' + + CONVERT(NVARCHAR(6), CONVERT(MONEY, iro.percent_complete)) + '% complete after ' + + CONVERT(NVARCHAR(30), iro.total_execution_time) + + ' minute(s). This blocks DDL and can pile up ghosts.' AS details, + 'Old index: ' + ISNULL(i.index_definition, 'not found. Either the index is new or you need @IncludeInactiveIndexes = 1') AS index_definition, + i.secret_columns, + i.index_usage_summary, + 'New index: ' + iro.reserved_MB_pretty_print + '; Old index: ' + ISNULL(sz.index_size_summary,'not found.') AS index_size_summary, + 'New index: ' + iro.sql_text AS create_tsql, + iro.more_info + FROM #IndexResumableOperations iro + LEFT JOIN #IndexSanity AS i ON i.database_id = iro.database_id + AND i.[object_id] = iro.[object_id] + AND i.index_id = iro.index_id + LEFT JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + WHERE iro.state = 0 + OPTION ( RECOMPILE ); + ---------------------------------------- --Aggressive Indexes: Check_id 10-19 ---------------------------------------- @@ -24274,7 +24532,38 @@ BEGIN WHERE i.filter_columns_not_in_index IS NOT NULL ORDER BY i.db_schema_object_indexid OPTION ( RECOMPILE ); - + + RAISERROR(N'check_id 124: History Table With NonClustered Index', 0,1) WITH NOWAIT; + + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 124 AS check_id, + i.index_sanity_id, + 80 AS Priority, + N'Abnormal Design Pattern' AS findings_group, + N'History Table With NonClustered Index' AS finding, + i.[database_name] AS [Database Name], + N'https://sqlserverfast.com/blog/hugo/2023/09/an-update-on-merge/' AS URL, + N'The history table ' + + QUOTENAME(hist.history_schema_name) + + '.' + + QUOTENAME(hist.history_table_name) + + ' has a non-clustered index. This can cause MERGEs on the main table to fail! See item 8 on the URL.' + AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + sz.index_size_summary + FROM #IndexSanity i + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + JOIN #TemporalTables hist + ON i.[object_id] = hist.history_table_object_id + AND i.[database_id] = hist.[database_id] + WHERE hist.history_table_object_id IS NOT NULL + AND i.index_type = 2 /* NC only */ + ORDER BY i.db_schema_object_indexid + OPTION ( RECOMPILE ); + ---------------------------------------- --Self Loathing Indexes : Check_id 40-49 ---------------------------------------- @@ -27063,7 +27352,7 @@ BEGIN CATCH GO IF OBJECT_ID('dbo.sp_BlitzLock') IS NULL BEGIN - EXEC ('CREATE PROCEDURE dbo.sp_BlitzLock AS RETURN 0;'); + EXECUTE ('CREATE PROCEDURE dbo.sp_BlitzLock AS RETURN 0;'); END; GO @@ -27082,6 +27371,11 @@ ALTER PROCEDURE @TargetSessionType sysname = NULL, @VictimsOnly bit = 0, @DeadlockType nvarchar(20) = NULL, + @TargetDatabaseName sysname = NULL, + @TargetSchemaName sysname = NULL, + @TargetTableName sysname = NULL, + @TargetColumnName sysname = NULL, + @TargetTimestampColumnName sysname = NULL, @Debug bit = 0, @Help bit = 0, @Version varchar(30) = NULL OUTPUT, @@ -27100,7 +27394,7 @@ BEGIN SET XACT_ABORT OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.23', @VersionDate = '20241228'; + SELECT @Version = '8.24', @VersionDate = '20250407'; IF @VersionCheckMode = 1 BEGIN @@ -27117,6 +27411,7 @@ BEGIN Variables you can use: + /*Filtering parameters*/ @DatabaseName: If you want to filter to a specific database @StartDate: The date you want to start searching on, defaults to last 7 days @@ -27135,16 +27430,32 @@ BEGIN @LoginName: If you want to filter to a specific login + @DeadlockType: Search for regular or parallel deadlocks specifically + + /*Extended Event session details*/ @EventSessionName: If you want to point this at an XE session rather than the system health session. - @TargetSessionType: Can be ''ring_buffer'' or ''event_file''. Leave NULL to auto-detect. + @TargetSessionType: Can be ''ring_buffer'', ''event_file'', or ''table''. Leave NULL to auto-detect. + /*Output to a table*/ @OutputDatabaseName: If you want to output information to a specific database @OutputSchemaName: Specify a schema name to output information to a specific Schema @OutputTableName: Specify table name to to output information to a specific table + /*Point at a table containing deadlock XML*/ + @TargetDatabaseName: The database that contains the table with deadlock report XML + + @TargetSchemaName: The schema of the table containing deadlock report XML + + @TargetTableName: The name of the table containing deadlock report XML + + @TargetColumnName: The name of the XML column that contains the deadlock report + + @TargetTimestampColumnName: The name of the datetime column for filtering by date range (optional) + + To learn more, visit http://FirstResponderKit.org where you can download new versions for free, watch training videos on how it works, get more info on the findings, contribute your own code, and more. @@ -27262,7 +27573,11 @@ BEGIN @StartDateOriginal datetime = @StartDate, @EndDateOriginal datetime = @EndDate, @StartDateUTC datetime, - @EndDateUTC datetime;; + @EndDateUTC datetime, + @extract_sql nvarchar(MAX), + @validation_sql nvarchar(MAX), + @xe bit, + @xd bit; /*Temporary objects used in the procedure*/ DECLARE @@ -27387,7 +27702,185 @@ BEGIN @TargetSessionType = N'ring_buffer'; END; + IF ISNULL(@TargetDatabaseName, DB_NAME()) IS NOT NULL + AND ISNULL(@TargetSchemaName, N'dbo') IS NOT NULL + AND @TargetTableName IS NOT NULL + AND @TargetColumnName IS NOT NULL + BEGIN + SET @TargetSessionType = N'table'; + END; + + /* Add this after the existing parameter validations */ + IF @TargetSessionType = N'table' + BEGIN + IF @TargetDatabaseName IS NULL + BEGIN + SET @TargetDatabaseName = DB_NAME(); + END; + + IF @TargetSchemaName IS NULL + BEGIN + SET @TargetSchemaName = N'dbo'; + END; + + IF @TargetTableName IS NULL + OR @TargetColumnName IS NULL + BEGIN + RAISERROR(N' + When using a table as a source, you must specify @TargetTableName, and @TargetColumnName. + When @TargetDatabaseName or @TargetSchemaName is NULL, they default to DB_NAME() AND dbo', + 11, 1) WITH NOWAIT; + RETURN; + END; + + /* Check if target database exists */ + IF NOT EXISTS + ( + SELECT + 1/0 + FROM sys.databases AS d + WHERE d.name = @TargetDatabaseName + ) + BEGIN + RAISERROR(N'The specified @TargetDatabaseName %s does not exist.', 11, 1, @TargetDatabaseName) WITH NOWAIT; + RETURN; + END; + + /* Use dynamic SQL to validate schema, table, and column existence */ + SET @validation_sql = N' + IF NOT EXISTS + ( + SELECT + 1/0 + FROM ' + QUOTENAME(@TargetDatabaseName) + N'.sys.schemas AS s + WHERE s.name = @schema + ) + BEGIN + RAISERROR(N''The specified @TargetSchemaName %s does not exist in database %s.'', 11, 1, @schema, @database) WITH NOWAIT; + RETURN; + END; + + IF NOT EXISTS + ( + SELECT + 1/0 + FROM ' + QUOTENAME(@TargetDatabaseName) + N'.sys.tables AS t + JOIN ' + QUOTENAME(@TargetDatabaseName) + N'.sys.schemas AS s + ON t.schema_id = s.schema_id + WHERE t.name = @table + AND s.name = @schema + ) + BEGIN + RAISERROR(N''The specified @TargetTableName %s does not exist in schema %s in database %s.'', 11, 1, @table, @schema, @database) WITH NOWAIT; + RETURN; + END; + + IF NOT EXISTS + ( + SELECT + 1/0 + FROM ' + QUOTENAME(@TargetDatabaseName) + N'.sys.columns AS c + JOIN ' + QUOTENAME(@TargetDatabaseName) + N'.sys.tables AS t + ON c.object_id = t.object_id + JOIN ' + QUOTENAME(@TargetDatabaseName) + N'.sys.schemas AS s + ON t.schema_id = s.schema_id + WHERE c.name = @column + AND t.name = @table + AND s.name = @schema + ) + BEGIN + RAISERROR(N''The specified @TargetColumnName %s does not exist in table %s.%s in database %s.'', 11, 1, @column, @schema, @table, @database) WITH NOWAIT; + RETURN; + END; + + /* Validate column is XML type */ + IF NOT EXISTS + ( + SELECT + 1/0 + FROM ' + QUOTENAME(@TargetDatabaseName) + N'.sys.columns AS c + JOIN ' + QUOTENAME(@TargetDatabaseName) + N'.sys.types AS ty + ON c.user_type_id = ty.user_type_id + JOIN ' + QUOTENAME(@TargetDatabaseName) + N'.sys.tables AS t + ON c.object_id = t.object_id + JOIN ' + QUOTENAME(@TargetDatabaseName) + N'.sys.schemas AS s + ON t.schema_id = s.schema_id + WHERE c.name = @column + AND t.name = @table + AND s.name = @schema + AND ty.name = N''xml'' + ) + BEGIN + RAISERROR(N''The specified @TargetColumnName %s must be of XML data type.'', 11, 1, @column) WITH NOWAIT; + RETURN; + END;'; + + /* Validate timestamp_column if specified */ + IF @TargetTimestampColumnName IS NOT NULL + BEGIN + SET @validation_sql = @validation_sql + N' + IF NOT EXISTS + ( + SELECT + 1/0 + FROM ' + QUOTENAME(@TargetDatabaseName) + N'.sys.columns AS c + JOIN ' + QUOTENAME(@TargetDatabaseName) + N'.sys.tables AS t + ON c.object_id = t.object_id + JOIN ' + QUOTENAME(@TargetDatabaseName) + N'.sys.schemas AS s + ON t.schema_id = s.schema_id + WHERE c.name = @timestamp_column + AND t.name = @table + AND s.name = @schema + ) + BEGIN + RAISERROR(N''The specified @TargetTimestampColumnName %s does not exist in table %s.%s in database %s.'', 11, 1, @timestamp_column, @schema, @table, @database) WITH NOWAIT; + RETURN; + END; + + /* Validate timestamp column is datetime type */ + IF NOT EXISTS + ( + SELECT + 1/0 + FROM ' + QUOTENAME(@TargetDatabaseName) + N'.sys.columns AS c + JOIN ' + QUOTENAME(@TargetDatabaseName) + N'.sys.types AS ty + ON c.user_type_id = ty.user_type_id + JOIN ' + QUOTENAME(@TargetDatabaseName) + N'.sys.tables AS t + ON c.object_id = t.object_id + JOIN ' + QUOTENAME(@TargetDatabaseName) + N'.sys.schemas AS s + ON t.schema_id = s.schema_id + WHERE c.name = @timestamp_column + AND t.name = @table + AND s.name = @schema + AND ty.name LIKE ''%date%'' + ) + BEGIN + RAISERROR(N''The specified @TargetTimestampColumnName %s must be of datetime data type.'', 11, 1, @timestamp_column) WITH NOWAIT; + RETURN; + END;'; + END; + + IF @Debug = 1 BEGIN PRINT @validation_sql; END; + + EXECUTE sys.sp_executesql + @validation_sql, + N' + @database sysname, + @schema sysname, + @table sysname, + @column sysname, + @timestamp_column sysname + ', + @TargetDatabaseName, + @TargetSchemaName, + @TargetTableName, + @TargetColumnName, + @TargetTimestampColumnName; + END; + + IF @Azure = 0 + AND LOWER(@TargetSessionType) <> N'table' BEGIN IF NOT EXISTS ( @@ -27404,8 +27897,9 @@ BEGIN RETURN; END; END; - + IF @Azure = 1 + AND LOWER(@TargetSessionType) <> N'table' BEGIN IF NOT EXISTS ( @@ -27464,7 +27958,7 @@ BEGIN N'@r sysname OUTPUT'; IF @Debug = 1 BEGIN PRINT @StringToExecute; END; - EXEC sys.sp_executesql + EXECUTE sys.sp_executesql @StringToExecute, @StringToExecuteParams, @r OUTPUT; @@ -27504,7 +27998,7 @@ BEGIN N' ADD spid smallint NULL;'; IF @Debug = 1 BEGIN PRINT @StringToExecute; END; - EXEC sys.sp_executesql + EXECUTE sys.sp_executesql @StringToExecute; /* If the table doesn't have the new wait_resource column, add it. See Github #3101. */ @@ -27520,7 +28014,7 @@ BEGIN N' ADD wait_resource nvarchar(MAX) NULL;'; IF @Debug = 1 BEGIN PRINT @StringToExecute; END; - EXEC sys.sp_executesql + EXECUTE sys.sp_executesql @StringToExecute; /* If the table doesn't have the new client option column, add it. See Github #3101. */ @@ -27536,7 +28030,7 @@ BEGIN N' ADD client_option_1 varchar(500) NULL;'; IF @Debug = 1 BEGIN PRINT @StringToExecute; END; - EXEC sys.sp_executesql + EXECUTE sys.sp_executesql @StringToExecute; /* If the table doesn't have the new client option column, add it. See Github #3101. */ @@ -27552,7 +28046,7 @@ BEGIN N' ADD client_option_2 varchar(500) NULL;'; IF @Debug = 1 BEGIN PRINT @StringToExecute; END; - EXEC sys.sp_executesql + EXECUTE sys.sp_executesql @StringToExecute; /* If the table doesn't have the new lock mode column, add it. See Github #3101. */ @@ -27568,7 +28062,7 @@ BEGIN N' ADD lock_mode nvarchar(256) NULL;'; IF @Debug = 1 BEGIN PRINT @StringToExecute; END; - EXEC sys.sp_executesql + EXECUTE sys.sp_executesql @StringToExecute; /* If the table doesn't have the new status column, add it. See Github #3101. */ @@ -27584,7 +28078,7 @@ BEGIN N' ADD status nvarchar(256) NULL;'; IF @Debug = 1 BEGIN PRINT @StringToExecute; END; - EXEC sys.sp_executesql + EXECUTE sys.sp_executesql @StringToExecute; END; ELSE /* end if @r is not null. if it is null there is no table, create it from above execution */ @@ -27642,7 +28136,7 @@ BEGIN )'; IF @Debug = 1 BEGIN PRINT @StringToExecute; END; - EXEC sys.sp_executesql + EXECUTE sys.sp_executesql @StringToExecute; /*table created.*/ @@ -27657,7 +28151,7 @@ BEGIN N'@r sysname OUTPUT'; IF @Debug = 1 BEGIN PRINT @StringToExecute; END; - EXEC sys.sp_executesql + EXECUTE sys.sp_executesql @StringToExecute, @StringToExecuteParams, @r OUTPUT; @@ -27685,7 +28179,7 @@ BEGIN );'; IF @Debug = 1 BEGIN PRINT @StringToExecute; END; - EXEC sys.sp_executesql + EXECUTE sys.sp_executesql @StringToExecute; END; END; @@ -27714,7 +28208,7 @@ BEGIN @OutputTableFindings; IF @Debug = 1 BEGIN PRINT @StringToExecute; END; - EXEC sys.sp_executesql + EXECUTE sys.sp_executesql @StringToExecute; /*create synonym for deadlock table.*/ @@ -27741,7 +28235,7 @@ BEGIN @OutputTableName; IF @Debug = 1 BEGIN PRINT @StringToExecute; END; - EXEC sys.sp_executesql + EXECUTE sys.sp_executesql @StringToExecute; END; END; @@ -27813,6 +28307,7 @@ BEGIN ( @Azure = 1 AND @TargetSessionType IS NULL + AND LOWER(@TargetSessionType) <> N'table' ) BEGIN RAISERROR('@TargetSessionType is NULL, assigning for Azure instance', 0, 1) WITH NOWAIT; @@ -28111,6 +28606,148 @@ BEGIN RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; END; + /* If table target */ + IF @TargetSessionType = 'table' + BEGIN + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Inserting to #deadlock_data from table source %s', 0, 1, @d) WITH NOWAIT; + + /* + First, we need to heck the XML structure. + Depending on the data source, the XML could + contain either the /event or /deadlock nodes. + When the /event nodes are not present, there + is no @name attribute to evaluate. + */ + + SELECT + @extract_sql = N' + SELECT TOP (1) + @xe = xe.e.exist(''.''), + @xd = xd.e.exist(''.'') + FROM [master].[dbo].[bpr] AS x + OUTER APPLY x.[bpr].nodes(''/event'') AS xe(e) + OUTER APPLY x.[bpr].nodes(''/deadlock'') AS xd(e) + OPTION(RECOMPILE); + '; + + IF @Debug = 1 BEGIN PRINT @extract_sql; END; + + EXECUTE sys.sp_executesql + @extract_sql, + N' + @xe bit OUTPUT, + @xd bit OUTPUT + ', + @xe OUTPUT, + @xd OUTPUT; + + + /* Build dynamic SQL to extract the XML */ + IF @xe = 1 + AND @xd IS NULL + BEGIN + SET @extract_sql = N' + SELECT + deadlock_xml = ' + + QUOTENAME(@TargetColumnName) + + N' + FROM ' + + QUOTENAME(@TargetDatabaseName) + + N'.' + + QUOTENAME(@TargetSchemaName) + + N'.' + + QUOTENAME(@TargetTableName) + + N' AS x + LEFT JOIN #t AS t + ON 1 = 1 + CROSS APPLY x.' + + QUOTENAME(@TargetColumnName) + + N'.nodes(''/event'') AS e(x) + WHERE + ( + e.x.exist(''@name[ .= "xml_deadlock_report"]'') = 1 + OR e.x.exist(''@name[ .= "database_xml_deadlock_report"]'') = 1 + OR e.x.exist(''@name[ .= "xml_deadlock_report_filtered"]'') = 1 + )'; + END; + + IF @xe IS NULL + AND @xd = 1 + BEGIN + SET @extract_sql = N' + SELECT + deadlock_xml = ' + + QUOTENAME(@TargetColumnName) + + N' + FROM ' + + QUOTENAME(@TargetDatabaseName) + + N'.' + + QUOTENAME(@TargetSchemaName) + + N'.' + + QUOTENAME(@TargetTableName) + + N' AS x + LEFT JOIN #t AS t + ON 1 = 1 + CROSS APPLY x.' + + QUOTENAME(@TargetColumnName) + + N'.nodes(''/deadlock'') AS e(x) + WHERE 1 = 1'; + END; + + /* Add timestamp filtering if specified */ + IF @TargetTimestampColumnName IS NOT NULL + BEGIN + SET @extract_sql = @extract_sql + N' + AND x.' + QUOTENAME(@TargetTimestampColumnName) + N' >= @StartDate + AND x.' + QUOTENAME(@TargetTimestampColumnName) + N' < @EndDate'; + END; + + /* If no timestamp column but date filtering is needed, handle XML-based filtering when possible */ + IF @TargetTimestampColumnName IS NULL + AND @xe = 1 + AND @xd IS NULL + BEGIN + SET @extract_sql = @extract_sql + N' + AND e.x.exist(''@timestamp[. >= sql:variable("@StartDate") and . < sql:variable("@EndDate")]'') = 1'; + END; + + /*Woof*/ + IF @TargetTimestampColumnName IS NULL + AND @xe IS NULL + AND @xd = 1 + BEGIN + SET @extract_sql = @extract_sql + N' + AND e.x.exist(''(/deadlock/process-list/process/@lasttranstarted)[. >= sql:variable("@StartDate") and . < sql:variable("@EndDate")]'') = 1'; + END; + + SET @extract_sql += N' + OPTION(RECOMPILE); + '; + + IF @Debug = 1 BEGIN PRINT @extract_sql; END; + + /* Execute the dynamic SQL */ + INSERT + #deadlock_data + WITH + (TABLOCKX) + ( + deadlock_xml + ) + EXECUTE sys.sp_executesql + @extract_sql, + N' + @StartDate datetime, + @EndDate datetime + ', + @StartDate, + @EndDate; + + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + END; + /*Parse process and input buffer xml*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Initial Parse process and input buffer xml %s', 0, 1, @d) WITH NOWAIT; @@ -28126,6 +28763,21 @@ BEGIN FROM #deadlock_data AS d1 LEFT JOIN #t AS t ON 1 = 1 + WHERE @xe = 1 + + UNION ALL + + SELECT + d1.deadlock_xml, + event_date = d1.deadlock_xml.value('(/deadlock/process-list/process/@lasttranstarted)[1]', 'datetime2'), + victim_id = d1.deadlock_xml.value('(/deadlock/victim-list/victimProcess/@id)[1]', 'nvarchar(256)'), + is_parallel = d1.deadlock_xml.exist('/deadlock/resource-list/exchangeEvent'), + is_parallel_batch = d1.deadlock_xml.exist('/deadlock/resource-list/SyncPoint'), + deadlock_graph = d1.deadlock_xml.query('.') + FROM #deadlock_data AS d1 + LEFT JOIN #t AS t + ON 1 = 1 + WHERE @xd = 1 OPTION(RECOMPILE); SET @d = CONVERT(varchar(40), GETDATE(), 109); @@ -28798,7 +29450,7 @@ BEGIN '; IF @Debug = 1 BEGIN PRINT @StringToExecute; END; - EXEC sys.sp_executesql + EXECUTE sys.sp_executesql @StringToExecute; END; @@ -28947,7 +29599,7 @@ BEGIN COUNT_BIG(DISTINCT dp.event_date) ) + N' deadlocks.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT dp.event_date) DESC) FROM #deadlock_process AS dp @@ -29002,7 +29654,7 @@ BEGIN COUNT_BIG(DISTINCT dow.event_date) ) + N' deadlock(s) between read queries and modification queries.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT dow.event_date) DESC) FROM #deadlock_owner_waiter AS dow @@ -29064,7 +29716,7 @@ BEGIN COUNT_BIG(DISTINCT dow.event_date) ) + N' deadlock(s).', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT dow.event_date) DESC) FROM #deadlock_owner_waiter AS dow @@ -29107,7 +29759,7 @@ BEGIN COUNT_BIG(DISTINCT dow.event_date) ) + N' deadlock(s).', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT dow.event_date) DESC) FROM #deadlock_owner_waiter AS dow @@ -29156,7 +29808,7 @@ BEGIN COUNT_BIG(DISTINCT dow.event_date) ) + N' deadlock(s).', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT dow.event_date) DESC) FROM #deadlock_owner_waiter AS dow @@ -29205,7 +29857,7 @@ BEGIN COUNT_BIG(DISTINCT dp.event_date) ) + N' instances of Serializable deadlocks.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT dp.event_date) DESC) FROM #deadlock_process AS dp @@ -29248,7 +29900,7 @@ BEGIN COUNT_BIG(DISTINCT dp.event_date) ) + N' instances of Repeatable Read deadlocks.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT dp.event_date) DESC) FROM #deadlock_process AS dp @@ -29310,7 +29962,7 @@ BEGIN N'UNKNOWN' ) + N'.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT dp.event_date) DESC) FROM #deadlock_process AS dp @@ -29422,7 +30074,7 @@ BEGIN 1, N'' ) + N' locks.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY CONVERT(bigint, lt.lock_count) DESC) FROM lock_types AS lt @@ -29492,7 +30144,7 @@ BEGIN dow.database_name, object_name = ds.proc_name, finding_group = N'More Info - Query', - finding = N'EXEC sp_BlitzCache ' + + finding = N'EXECUTE sp_BlitzCache ' + CASE WHEN ds.proc_name = N'adhoc' THEN N'@OnlySqlHandles = ' + ds.sql_handle_csv @@ -29548,7 +30200,7 @@ BEGIN object_name = ds.proc_name, finding_group = N'More Info - Query', finding = - N'EXEC sp_BlitzQueryStore ' + + N'EXECUTE sp_BlitzQueryStore ' + N'@DatabaseName = ' + QUOTENAME(ds.database_name, N'''') + N', ' + @@ -29601,7 +30253,7 @@ BEGIN COUNT_BIG(DISTINCT ds.id) ) + N' deadlocks.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT ds.id) DESC) FROM #deadlock_stack AS ds @@ -29661,7 +30313,7 @@ BEGIN bi.object_name, finding_group = N'More Info - Table', finding = - N'EXEC sp_BlitzIndex ' + + N'EXECUTE sp_BlitzIndex ' + N'@DatabaseName = ' + QUOTENAME(bi.database_name, N'''') + N', @SchemaName = ' + @@ -29702,19 +30354,19 @@ BEGIN ) ), wait_time_hms = - /*the more wait time you rack up the less accurate this gets, + /*the more wait time you rack up the less accurate this gets, it's either that or erroring out*/ - CASE - WHEN + CASE + WHEN SUM ( CONVERT ( - bigint, + bigint, dp.wait_time ) )/1000 > 2147483647 - THEN + THEN CONVERT ( nvarchar(30), @@ -29727,7 +30379,7 @@ BEGIN ( CONVERT ( - bigint, + bigint, dp.wait_time ) ) @@ -29738,16 +30390,16 @@ BEGIN ), 14 ) - WHEN + WHEN SUM ( CONVERT ( - bigint, + bigint, dp.wait_time ) ) BETWEEN 2147483648 AND 2147483647000 - THEN + THEN CONVERT ( nvarchar(30), @@ -29760,7 +30412,7 @@ BEGIN ( CONVERT ( - bigint, + bigint, dp.wait_time ) ) @@ -29842,7 +30494,7 @@ BEGIN 14 ) + N' [dd hh:mm:ss:ms] of deadlock wait time.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY cs.total_waits DESC) FROM chopsuey AS cs @@ -29916,19 +30568,19 @@ BEGIN ) ) + N' ' + - /*the more wait time you rack up the less accurate this gets, + /*the more wait time you rack up the less accurate this gets, it's either that or erroring out*/ - CASE - WHEN + CASE + WHEN SUM ( CONVERT ( - bigint, + bigint, wt.total_wait_time_ms ) )/1000 > 2147483647 - THEN + THEN CONVERT ( nvarchar(30), @@ -29941,7 +30593,7 @@ BEGIN ( CONVERT ( - bigint, + bigint, wt.total_wait_time_ms ) ) @@ -29952,16 +30604,16 @@ BEGIN ), 14 ) - WHEN + WHEN SUM ( CONVERT ( - bigint, + bigint, wt.total_wait_time_ms ) ) BETWEEN 2147483648 AND 2147483647000 - THEN + THEN CONVERT ( nvarchar(30), @@ -29974,7 +30626,7 @@ BEGIN ( CONVERT ( - bigint, + bigint, wt.total_wait_time_ms ) ) @@ -30007,7 +30659,7 @@ BEGIN 14 ) END + N' [dd hh:mm:ss:ms] of deadlock wait time.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY SUM(CONVERT(bigint, wt.total_wait_time_ms)) DESC) FROM wait_time AS wt @@ -30045,7 +30697,7 @@ BEGIN N'There have been ' + RTRIM(COUNT_BIG(DISTINCT aj.event_date)) + N' deadlocks from this Agent Job and Step.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT aj.event_date) DESC) FROM #agent_job AS aj @@ -30738,9 +31390,9 @@ BEGIN SET STATISTICS XML ON; END; - SET @StringToExecute = N' + SET @StringToExecute = N' - INSERT INTO ' + QUOTENAME(DB_NAME()) + N'..DeadLockTbl + INSERT INTO ' + QUOTENAME(DB_NAME()) + N'..DeadLockTbl ( ServerName, deadlock_type, @@ -30783,9 +31435,9 @@ BEGIN waiter_waiting_to_close, deadlock_graph ) - EXEC sys.sp_executesql - @deadlock_result;' - EXEC sys.sp_executesql @StringToExecute, N'@deadlock_result NVARCHAR(MAX)', @deadlock_result; + EXECUTE sys.sp_executesql + @deadlock_result;'; + EXECUTE sys.sp_executesql @StringToExecute, N'@deadlock_result NVARCHAR(MAX)', @deadlock_result; IF @Debug = 1 BEGIN @@ -30801,7 +31453,7 @@ BEGIN SET @StringToExecute = N' - INSERT INTO ' + QUOTENAME(DB_NAME()) + N'..DeadlockFindings + INSERT INTO ' + QUOTENAME(DB_NAME()) + N'..DeadlockFindings ( ServerName, check_id, @@ -30819,8 +31471,8 @@ BEGIN df.finding FROM #deadlock_findings AS df ORDER BY df.check_id - OPTION(RECOMPILE);' - EXEC sys.sp_executesql @StringToExecute; + OPTION(RECOMPILE);'; + EXECUTE sys.sp_executesql @StringToExecute; RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; @@ -30830,23 +31482,23 @@ BEGIN BEGIN SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Results to client %s', 0, 1, @d) WITH NOWAIT; - + IF @Debug = 1 BEGIN SET STATISTICS XML ON; END; - - EXEC sys.sp_executesql + + EXECUTE sys.sp_executesql @deadlock_result; - + IF @Debug = 1 BEGIN SET STATISTICS XML OFF; PRINT @deadlock_result; END; - + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Getting available execution plans for deadlocks %s', 0, 1, @d) WITH NOWAIT; - + SELECT DISTINCT available_plans = 'available_plans', @@ -30913,15 +31565,15 @@ BEGIN min_used_grant_mb = deqs.min_used_grant_kb * 8. / 1024., max_used_grant_mb = - deqs.max_used_grant_kb * 8. / 1024., + deqs.max_used_grant_kb * 8. / 1024., deqs.min_reserved_threads, deqs.max_reserved_threads, deqs.min_used_threads, deqs.max_used_threads, deqs.total_rows, - max_worker_time_ms = + max_worker_time_ms = deqs.max_worker_time / 1000., - max_elapsed_time_ms = + max_elapsed_time_ms = deqs.max_elapsed_time / 1000. INTO #dm_exec_query_stats FROM sys.dm_exec_query_stats AS deqs @@ -30933,7 +31585,7 @@ BEGIN WHERE ap.sql_handle = deqs.sql_handle ) AND deqs.query_hash IS NOT NULL; - + CREATE CLUSTERED INDEX deqs ON #dm_exec_query_stats @@ -30941,7 +31593,7 @@ BEGIN sql_handle, plan_handle ); - + SELECT ap.available_plans, ap.database_name, @@ -30975,7 +31627,7 @@ BEGIN ap.statement_end_offset FROM ( - + SELECT ap.*, c.statement_start_offset, @@ -31027,10 +31679,10 @@ BEGIN OPTION(RECOMPILE, LOOP JOIN, HASH JOIN); RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - + SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Returning findings %s', 0, 1, @d) WITH NOWAIT; - + SELECT df.check_id, df.database_name, @@ -31042,7 +31694,7 @@ BEGIN df.check_id, df.sort_order OPTION(RECOMPILE); - + SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; END; /*done with output to client app.*/ @@ -31126,7 +31778,7 @@ BEGIN END; IF OBJECT_ID('tempdb..#dm_exec_query_stats') IS NOT NULL - BEGIN + BEGIN SELECT table_name = N'#dm_exec_query_stats', * @@ -31161,6 +31813,16 @@ BEGIN @VictimsOnly, DeadlockType = @DeadlockType, + TargetDatabaseName = + @TargetDatabaseName, + TargetSchemaName = + @TargetSchemaName, + TargetTableName = + @TargetTableName, + TargetColumnName = + @TargetColumnName, + TargetTimestampColumnName = + @TargetTimestampColumnName, Debug = @Debug, Help = @@ -31265,7 +31927,7 @@ BEGIN SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.23', @VersionDate = '20241228'; + SELECT @Version = '8.24', @VersionDate = '20250407'; IF(@VersionCheckMode = 1) BEGIN @@ -32678,7 +33340,7 @@ SET STATISTICS XML OFF; /*Versioning details*/ -SELECT @Version = '8.23', @VersionDate = '20241228'; +SELECT @Version = '8.24', @VersionDate = '20250407'; IF(@VersionCheckMode = 1) BEGIN @@ -32860,7 +33522,7 @@ END; BEGIN TRY DECLARE @CurrentDatabaseContext AS VARCHAR(128) = (SELECT DB_NAME()); -DECLARE @CommandExecuteCheck VARCHAR(315) +DECLARE @CommandExecuteCheck VARCHAR(400); SET @CommandExecuteCheck = 'IF NOT EXISTS (SELECT name FROM ' +@CurrentDatabaseContext+'.sys.objects WHERE type = ''P'' AND name = ''CommandExecute'') BEGIN @@ -34347,7 +35009,7 @@ BEGIN SET NOCOUNT ON; SET STATISTICS XML OFF; - SELECT @Version = '8.23', @VersionDate = '20241228'; + SELECT @Version = '8.24', @VersionDate = '20250407'; IF(@VersionCheckMode = 1) BEGIN @@ -34710,6 +35372,8 @@ INSERT INTO dbo.SqlServerVersions (MajorVersionNumber, MinorVersionNumber, Branch, [Url], ReleaseDate, MainstreamSupportEndDate, ExtendedSupportEndDate, MajorVersionName, MinorVersionName) VALUES /*2022*/ + (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'), (16, 4165, 'CU16', 'https://support.microsoft.com/en-us/help/5048033', '2024-11-14', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 16'), (16, 4150, 'CU15 GDR', 'https://support.microsoft.com/en-us/help/5046059', '2024-10-08', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 15 GDR'), (16, 4145, 'CU15', 'https://support.microsoft.com/en-us/help/5041321', '2024-09-25', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 15'), @@ -34732,6 +35396,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, 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, 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'), @@ -35189,7 +35855,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.23', @VersionDate = '20241228'; +SELECT @Version = '8.24', @VersionDate = '20250407'; IF(@VersionCheckMode = 1) BEGIN diff --git a/Install-Azure.sql b/Install-Azure.sql index 0ca1ddc29..bed3ddb87 100644 --- a/Install-Azure.sql +++ b/Install-Azure.sql @@ -37,7 +37,7 @@ AS SET NOCOUNT ON; SET STATISTICS XML OFF; -SELECT @Version = '8.23', @VersionDate = '20241228'; +SELECT @Version = '8.24', @VersionDate = '20250407'; IF(@VersionCheckMode = 1) BEGIN @@ -1164,7 +1164,8 @@ ALTER PROCEDURE dbo.sp_BlitzCache @MinutesBack INT = NULL, @Version VARCHAR(30) = NULL OUTPUT, @VersionDate DATETIME = NULL OUTPUT, - @VersionCheckMode BIT = 0 + @VersionCheckMode BIT = 0, + @KeepCRLF BIT = 0 WITH RECOMPILE AS BEGIN @@ -1172,7 +1173,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.23', @VersionDate = '20241228'; +SELECT @Version = '8.24', @VersionDate = '20250407'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -1374,7 +1375,12 @@ IF @Help = 1 UNION ALL SELECT N'@VersionCheckMode', N'BIT', - N'Setting this to 1 will make the procedure stop after setting @Version and @VersionDate.'; + N'Setting this to 1 will make the procedure stop after setting @Version and @VersionDate.' + + UNION ALL + SELECT N'@KeepCRLF', + N'BIT', + N'Retain CR/LF in query text to avoid issues caused by line comments.'; /* Column definitions */ @@ -3651,7 +3657,10 @@ SET PercentCPU = y.PercentCPU, /* Strip newlines and tabs. Tabs are replaced with multiple spaces so that the later whitespace trim will completely eliminate them */ - QueryText = REPLACE(REPLACE(REPLACE(QueryText, @cr, ' '), @lf, ' '), @tab, ' ') + QueryText = CASE WHEN @KeepCRLF = 1 + THEN REPLACE(QueryText, @tab, ' ') + ELSE REPLACE(REPLACE(REPLACE(QueryText, @cr, ' '), @lf, ' '), @tab, ' ') + END FROM ( SELECT PlanHandle, CASE @total_cpu WHEN 0 THEN 0 @@ -3707,7 +3716,10 @@ SET PercentCPU = y.PercentCPU, /* Strip newlines and tabs. Tabs are replaced with multiple spaces so that the later whitespace trim will completely eliminate them */ - QueryText = REPLACE(REPLACE(REPLACE(QueryText, @cr, ' '), @lf, ' '), @tab, ' ') + QueryText = CASE WHEN @KeepCRLF = 1 + THEN REPLACE(QueryText, @tab, ' ') + ELSE REPLACE(REPLACE(REPLACE(QueryText, @cr, ' '), @lf, ' '), @tab, ' ') + END FROM ( SELECT DatabaseName, SqlHandle, @@ -8545,7 +8557,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.23', @VersionDate = '20241228'; +SELECT @Version = '8.24', @VersionDate = '20250407'; IF(@VersionCheckMode = 1) BEGIN @@ -13555,7 +13567,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.23', @VersionDate = '20241228'; +SELECT @Version = '8.24', @VersionDate = '20250407'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -13637,6 +13649,7 @@ DECLARE @ColumnList NVARCHAR(MAX); DECLARE @ColumnListWithApostrophes NVARCHAR(MAX); DECLARE @PartitionCount INT; DECLARE @OptimizeForSequentialKey BIT = 0; +DECLARE @ResumableIndexesDisappearAfter INT = 0; DECLARE @StringToExecute NVARCHAR(MAX); /* If user was lazy and just used @ObjectName with a fully qualified table name, then lets parse out the various parts */ @@ -13767,8 +13780,11 @@ IF OBJECT_ID('tempdb..#FilteredIndexes') IS NOT NULL DROP TABLE #FilteredIndexes; IF OBJECT_ID('tempdb..#Ignore_Databases') IS NOT NULL - DROP TABLE #Ignore_Databases - + DROP TABLE #Ignore_Databases; + +IF OBJECT_ID('tempdb..#IndexResumableOperations') IS NOT NULL + DROP TABLE #IndexResumableOperations; + IF OBJECT_ID('tempdb..#dm_db_partition_stats_etc') IS NOT NULL DROP TABLE #dm_db_partition_stats_etc IF OBJECT_ID('tempdb..#dm_db_index_operational_stats') IS NOT NULL @@ -14279,7 +14295,8 @@ IF OBJECT_ID('tempdb..#dm_db_index_operational_stats') IS NOT NULL history_schema_name NVARCHAR(128) NOT NULL, start_column_name NVARCHAR(128) NOT NULL, end_column_name NVARCHAR(128) NOT NULL, - period_name NVARCHAR(128) NOT NULL + period_name NVARCHAR(128) NOT NULL, + history_table_object_id INT NULL ); CREATE TABLE #CheckConstraints @@ -14309,6 +14326,59 @@ IF OBJECT_ID('tempdb..#dm_db_index_operational_stats') IS NOT NULL column_name NVARCHAR(128) NULL ); + CREATE TABLE #IndexResumableOperations + ( + database_name NVARCHAR(128) NULL, + database_id INT NOT NULL, + schema_name NVARCHAR(128) NOT NULL, + table_name NVARCHAR(128) NOT NULL, + /* + Every following non-computed column has + the same definitions as in + sys.index_resumable_operations. + */ + [object_id] INT NOT NULL, + index_id INT NOT NULL, + [name] NVARCHAR(128) NOT NULL, + /* + We have done nothing to make this query text pleasant + to read. Until somebody has a better idea, we trust + that copying Microsoft's approach is wise. + */ + sql_text NVARCHAR(MAX) NULL, + last_max_dop_used SMALLINT NOT NULL, + partition_number INT NULL, + state TINYINT NOT NULL, + state_desc NVARCHAR(60) NULL, + start_time DATETIME NOT NULL, + last_pause_time DATETIME NULL, + total_execution_time INT NOT NULL, + percent_complete FLOAT NOT NULL, + page_count BIGINT NOT NULL, + /* + sys.indexes will not always have the name of the index + because a resumable CREATE INDEX does not populate + sys.indexes until it is done. + So it is better to work out the full name here + rather than pull it from another temp table. + */ + [db_schema_table_index] AS + [schema_name] + N'.' + [table_name] + N'.' + [name], + /* For convenience. */ + reserved_MB_pretty_print AS + CONVERT(NVARCHAR(100), CONVERT(MONEY, page_count * 8. / 1024.)) + + 'MB and ' + + state_desc, + more_info AS + N'New index: SELECT * FROM ' + QUOTENAME(database_name) + + N'.sys.index_resumable_operations WHERE [object_id] = ' + + CONVERT(NVARCHAR(100), [object_id]) + + N'; Old index: ' + + N'EXEC dbo.sp_BlitzIndex @DatabaseName=' + QUOTENAME([database_name],N'''') + + N', @SchemaName=' + QUOTENAME([schema_name],N'''') + + N', @TableName=' + QUOTENAME([table_name],N'''') + N';' + ); + CREATE TABLE #Ignore_Databases ( DatabaseName NVARCHAR(128), @@ -15931,7 +16001,8 @@ OPTION (RECOMPILE);'; oa.htn AS history_table_name, c1.name AS start_column_name, c2.name AS end_column_name, - p.name AS period_name + p.name AS period_name, + t.history_table_id AS history_table_object_id FROM ' + QUOTENAME(@DatabaseName) + N'.sys.periods AS p INNER JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.tables AS t ON p.object_id = t.object_id @@ -15957,7 +16028,7 @@ OPTION (RECOMPILE);'; RAISERROR('@dsql is null',16,1); INSERT #TemporalTables ( database_name, database_id, schema_name, table_name, history_schema_name, - history_table_name, start_column_name, end_column_name, period_name ) + history_table_name, start_column_name, end_column_name, period_name, history_table_object_id ) EXEC sp_executesql @dsql; END; @@ -16023,8 +16094,63 @@ OPTION (RECOMPILE);'; BEGIN CATCH RAISERROR (N'Skipping #FilteredIndexes population due to error, typically low permissions.', 0,1) WITH NOWAIT; END CATCH + END; + + IF @Mode NOT IN(1, 2, 3) + /* + The sys.index_resumable_operations view was a 2017 addition, so we need to check for it and go dynamic. + */ + AND EXISTS (SELECT * FROM sys.all_objects WHERE name = 'index_resumable_operations') + BEGIN + SET @dsql=N'SELECT @i_DatabaseName AS database_name, + DB_ID(@i_DatabaseName) AS [database_id], + s.name AS schema_name, + t.name AS table_name, + iro.[object_id], + iro.index_id, + iro.name, + iro.sql_text, + iro.last_max_dop_used, + iro.partition_number, + iro.state, + iro.state_desc, + iro.start_time, + iro.last_pause_time, + iro.total_execution_time, + iro.percent_complete, + iro.page_count + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.index_resumable_operations AS iro + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.tables AS t + ON t.object_id = iro.object_id + JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas AS s + ON t.schema_id = s.schema_id + OPTION(RECOMPILE);' + + BEGIN TRY + RAISERROR (N'Inserting data into #IndexResumableOperations',0,1) WITH NOWAIT; + INSERT #IndexResumableOperations + ( database_name, database_id, schema_name, table_name, + [object_id], index_id, name, sql_text, last_max_dop_used, partition_number, state, state_desc, + start_time, last_pause_time, total_execution_time, percent_complete, page_count ) + EXEC sp_executesql @dsql, @params = N'@i_DatabaseName NVARCHAR(128)', @i_DatabaseName = @DatabaseName; + + SET @dsql=N'SELECT @ResumableIndexesDisappearAfter = CAST(value AS INT) + FROM ' + QUOTENAME(@DatabaseName) + N'.sys.database_scoped_configurations + WHERE name = ''PAUSED_RESUMABLE_INDEX_ABORT_DURATION_MINUTES'' + AND value > 0;' + EXEC sp_executesql @dsql, N'@ResumableIndexesDisappearAfter INT OUT', @ResumableIndexesDisappearAfter out; + + IF @ResumableIndexesDisappearAfter IS NULL + SET @ResumableIndexesDisappearAfter = 0; + + END TRY + BEGIN CATCH + RAISERROR (N'Skipping #IndexResumableOperations population due to error, typically low permissions', 0,1) WITH NOWAIT; + END CATCH + END; + + END; - END; END; END TRY @@ -16471,9 +16597,11 @@ BEGIN SELECT '#Statistics' AS table_name, * FROM #Statistics; SELECT '#PartitionCompressionInfo' AS table_name, * FROM #PartitionCompressionInfo; SELECT '#ComputedColumns' AS table_name, * FROM #ComputedColumns; - SELECT '#TraceStatus' AS table_name, * FROM #TraceStatus; + SELECT '#TraceStatus' AS table_name, * FROM #TraceStatus; + SELECT '#TemporalTables' AS table_name, * FROM #TemporalTables; SELECT '#CheckConstraints' AS table_name, * FROM #CheckConstraints; - SELECT '#FilteredIndexes' AS table_name, * FROM #FilteredIndexes; + SELECT '#FilteredIndexes' AS table_name, * FROM #FilteredIndexes; + SELECT '#IndexResumableOperations' AS table_name, * FROM #IndexResumableOperations; END @@ -16691,7 +16819,55 @@ BEGIN ORDER BY s.auto_created, s.user_created, s.name, hist.step_number;'; EXEC sp_executesql @dsql, N'@ObjectID INT', @ObjectID; END - END + + /* Check for resumable index operations. */ + IF (SELECT TOP (1) [object_id] FROM #IndexResumableOperations WHERE [object_id] = @ObjectID AND database_id = @DatabaseID) IS NOT NULL + BEGIN + SELECT + N'Resumable Index Operation' AS finding, + N'This may invalidate your analysis!' AS warning, + iro.state_desc + N' on ' + iro.db_schema_table_index + + CASE iro.state + WHEN 0 THEN + N' at MAXDOP ' + CONVERT(NVARCHAR(30), iro.last_max_dop_used) + + N'. First started ' + CONVERT(NVARCHAR(50), iro.start_time, 120) + N'. ' + + CONVERT(NVARCHAR(6), CONVERT(MONEY, iro.percent_complete)) + N'% complete after ' + + CONVERT(NVARCHAR(30), iro.total_execution_time) + + N' minute(s). ' + + CASE WHEN @ResumableIndexesDisappearAfter > 0 + THEN N' Will be automatically removed by the database server at ' + CONVERT(NVARCHAR(50), (DATEADD(mi, @ResumableIndexesDisappearAfter, iro.last_pause_time)), 121) + N'. ' + ELSE N' Will not be automatically removed by the database server. ' + END + + N'This blocks DDL and can pile up ghosts.' + WHEN 1 THEN + N' since ' + CONVERT(NVARCHAR(50), iro.last_pause_time, 120) + N'. ' + + CONVERT(NVARCHAR(6), CONVERT(MONEY, iro.percent_complete)) + N'% complete' + + /* + At 100% completion, resumable indexes open up a transaction and go back to paused for what ought to be a moment. + Updating statistics is one of the things that it can do in this false paused state. + Updating stats can take a while, so we point it out as a likely delay. + It seems that any of the normal operations that happen at the very end of an index build can cause this. + */ + CASE WHEN iro.percent_complete > 99.9 + THEN N'. It is probably still running, perhaps updating statistics.' + ELSE N' after ' + CONVERT(NVARCHAR(30), iro.total_execution_time) + + N' minute(s). This blocks DDL, fails transactions needing table-level X locks, and can pile up ghosts.' + END + ELSE N' which is an undocumented resumable index state description.' + END AS details, + N'https://www.BrentOzar.com/go/resumable' AS URL, + iro.more_info AS [More Info] + FROM #IndexResumableOperations AS iro + WHERE iro.database_id = @DatabaseID + AND iro.[object_id] = @ObjectID + OPTION ( RECOMPILE ); + END + ELSE + BEGIN + SELECT N'No resumable index operations.' AS finding; + END; + + END /* END @ShowColumnstoreOnly = 0 */ /* Visualize columnstore index contents. More info: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2584 */ IF 2 = (SELECT SUM(1) FROM sys.all_objects WHERE name IN ('column_store_row_groups','column_store_segments')) @@ -17072,6 +17248,96 @@ BEGIN ORDER BY ips.total_rows DESC, ip.[schema_name], ip.[object_name], ip.key_column_names, ip.include_column_names OPTION ( RECOMPILE ); + ---------------------------------------- + --Resumable Indexing: Check_id 122-123 + ---------------------------------------- + /* + This is more complicated than you would expect! + As of SQL Server 2022, I am aware of 6 cases that we need to check: + 1) A resumable rowstore CREATE INDEX that is currently running + 2) A resumable rowstore CREATE INDEX that is currently paused + 3) A resumable rowstore REBUILD that is currently running + 4) A resumable rowstore REBUILD that is currently paused + 5) A resumable rowstore CREATE INDEX [...] DROP_EXISTING = ON that is currently running + 6) A resumable rowstore CREATE INDEX [...] DROP_EXISTING = ON that is currently paused + In cases 1 and 2, sys.indexes has no data at all about the index in question. + This makes #IndexSanity much harder to use, since it depends on sys.indexes. + We must therefore get as much from #IndexResumableOperations as possible. + */ + RAISERROR(N'check_id 122: Resumable Index Operation Paused', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, + [database_name], URL, details, index_definition, secret_columns, + index_usage_summary, index_size_summary, create_tsql, more_info ) + SELECT 122 AS check_id, + i.index_sanity_id, + 10 AS Priority, + N'Resumable Indexing' AS findings_group, + N'Resumable Index Operation Paused' AS finding, + iro.[database_name] AS [Database Name], + N'https://www.BrentOzar.com/go/resumable' AS URL, + iro.state_desc + N' on ' + iro.db_schema_table_index + + N' since ' + CONVERT(NVARCHAR(50), iro.last_pause_time, 120) + N'. ' + + CONVERT(NVARCHAR(6), CONVERT(MONEY, iro.percent_complete)) + N'% complete' + + /* + At 100% completion, resumable indexes open up a transaction and go back to paused for what ought to be a moment. + Updating statistics is one of the things that it can do in this false paused state. + Updating stats can take a while, so we point it out as a likely delay. + It seems that any of the normal operations that happen at the very end of an index build can cause this. + */ + CASE WHEN iro.percent_complete > 99.9 + THEN N'. It is probably still running, perhaps updating statistics.' + ELSE N' after ' + CONVERT(NVARCHAR(30), iro.total_execution_time) + + N' minute(s). This blocks DDL, fails transactions needing table-level X locks, and can pile up ghosts. ' + END + + CASE WHEN @ResumableIndexesDisappearAfter > 0 + THEN N' Will be automatically removed by the database server at ' + CONVERT(NVARCHAR(50), (DATEADD(mi, @ResumableIndexesDisappearAfter, iro.last_pause_time)), 121) + N'. ' + ELSE N' Will not be automatically removed by the database server. ' + END AS details, + N'Old index: ' + ISNULL(i.index_definition, N'not found. Either the index is new or you need @IncludeInactiveIndexes = 1') AS index_definition, + i.secret_columns, + i.index_usage_summary, + N'New index: ' + iro.reserved_MB_pretty_print + N'; Old index: ' + ISNULL(sz.index_size_summary,'not found.') AS index_size_summary, + N'New index: ' + iro.sql_text AS create_tsql, + iro.more_info + FROM #IndexResumableOperations iro + LEFT JOIN #IndexSanity AS i ON i.database_id = iro.database_id + AND i.[object_id] = iro.[object_id] + AND i.index_id = iro.index_id + LEFT JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + WHERE iro.state = 1 + OPTION ( RECOMPILE ); + + RAISERROR(N'check_id 123: Resumable Index Operation Running', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, + [database_name], URL, details, index_definition, secret_columns, + index_usage_summary, index_size_summary, create_tsql, more_info ) + SELECT 123 AS check_id, + i.index_sanity_id, + 10 AS Priority, + N'Resumable Indexing' AS findings_group, + N'Resumable Index Operation Running' AS finding, + iro.[database_name] AS [Database Name], + N'https://www.BrentOzar.com/go/resumable' AS URL, + iro.state_desc + ' on ' + iro.db_schema_table_index + + ' at MAXDOP ' + CONVERT(NVARCHAR(30), iro.last_max_dop_used) + + '. First started ' + CONVERT(NVARCHAR(50), iro.start_time, 120) + '. ' + + CONVERT(NVARCHAR(6), CONVERT(MONEY, iro.percent_complete)) + '% complete after ' + + CONVERT(NVARCHAR(30), iro.total_execution_time) + + ' minute(s). This blocks DDL and can pile up ghosts.' AS details, + 'Old index: ' + ISNULL(i.index_definition, 'not found. Either the index is new or you need @IncludeInactiveIndexes = 1') AS index_definition, + i.secret_columns, + i.index_usage_summary, + 'New index: ' + iro.reserved_MB_pretty_print + '; Old index: ' + ISNULL(sz.index_size_summary,'not found.') AS index_size_summary, + 'New index: ' + iro.sql_text AS create_tsql, + iro.more_info + FROM #IndexResumableOperations iro + LEFT JOIN #IndexSanity AS i ON i.database_id = iro.database_id + AND i.[object_id] = iro.[object_id] + AND i.index_id = iro.index_id + LEFT JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + WHERE iro.state = 0 + OPTION ( RECOMPILE ); + ---------------------------------------- --Aggressive Indexes: Check_id 10-19 ---------------------------------------- @@ -17229,7 +17495,38 @@ BEGIN WHERE i.filter_columns_not_in_index IS NOT NULL ORDER BY i.db_schema_object_indexid OPTION ( RECOMPILE ); - + + RAISERROR(N'check_id 124: History Table With NonClustered Index', 0,1) WITH NOWAIT; + + INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 124 AS check_id, + i.index_sanity_id, + 80 AS Priority, + N'Abnormal Design Pattern' AS findings_group, + N'History Table With NonClustered Index' AS finding, + i.[database_name] AS [Database Name], + N'https://sqlserverfast.com/blog/hugo/2023/09/an-update-on-merge/' AS URL, + N'The history table ' + + QUOTENAME(hist.history_schema_name) + + '.' + + QUOTENAME(hist.history_table_name) + + ' has a non-clustered index. This can cause MERGEs on the main table to fail! See item 8 on the URL.' + AS details, + i.index_definition, + i.secret_columns, + i.index_usage_summary, + sz.index_size_summary + FROM #IndexSanity i + JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id + JOIN #TemporalTables hist + ON i.[object_id] = hist.history_table_object_id + AND i.[database_id] = hist.[database_id] + WHERE hist.history_table_object_id IS NOT NULL + AND i.index_type = 2 /* NC only */ + ORDER BY i.db_schema_object_indexid + OPTION ( RECOMPILE ); + ---------------------------------------- --Self Loathing Indexes : Check_id 40-49 ---------------------------------------- @@ -20018,7 +20315,7 @@ BEGIN CATCH GO IF OBJECT_ID('dbo.sp_BlitzLock') IS NULL BEGIN - EXEC ('CREATE PROCEDURE dbo.sp_BlitzLock AS RETURN 0;'); + EXECUTE ('CREATE PROCEDURE dbo.sp_BlitzLock AS RETURN 0;'); END; GO @@ -20037,6 +20334,11 @@ ALTER PROCEDURE @TargetSessionType sysname = NULL, @VictimsOnly bit = 0, @DeadlockType nvarchar(20) = NULL, + @TargetDatabaseName sysname = NULL, + @TargetSchemaName sysname = NULL, + @TargetTableName sysname = NULL, + @TargetColumnName sysname = NULL, + @TargetTimestampColumnName sysname = NULL, @Debug bit = 0, @Help bit = 0, @Version varchar(30) = NULL OUTPUT, @@ -20055,7 +20357,7 @@ BEGIN SET XACT_ABORT OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.23', @VersionDate = '20241228'; + SELECT @Version = '8.24', @VersionDate = '20250407'; IF @VersionCheckMode = 1 BEGIN @@ -20072,6 +20374,7 @@ BEGIN Variables you can use: + /*Filtering parameters*/ @DatabaseName: If you want to filter to a specific database @StartDate: The date you want to start searching on, defaults to last 7 days @@ -20090,16 +20393,32 @@ BEGIN @LoginName: If you want to filter to a specific login + @DeadlockType: Search for regular or parallel deadlocks specifically + + /*Extended Event session details*/ @EventSessionName: If you want to point this at an XE session rather than the system health session. - @TargetSessionType: Can be ''ring_buffer'' or ''event_file''. Leave NULL to auto-detect. + @TargetSessionType: Can be ''ring_buffer'', ''event_file'', or ''table''. Leave NULL to auto-detect. + /*Output to a table*/ @OutputDatabaseName: If you want to output information to a specific database @OutputSchemaName: Specify a schema name to output information to a specific Schema @OutputTableName: Specify table name to to output information to a specific table + /*Point at a table containing deadlock XML*/ + @TargetDatabaseName: The database that contains the table with deadlock report XML + + @TargetSchemaName: The schema of the table containing deadlock report XML + + @TargetTableName: The name of the table containing deadlock report XML + + @TargetColumnName: The name of the XML column that contains the deadlock report + + @TargetTimestampColumnName: The name of the datetime column for filtering by date range (optional) + + To learn more, visit http://FirstResponderKit.org where you can download new versions for free, watch training videos on how it works, get more info on the findings, contribute your own code, and more. @@ -20217,7 +20536,11 @@ BEGIN @StartDateOriginal datetime = @StartDate, @EndDateOriginal datetime = @EndDate, @StartDateUTC datetime, - @EndDateUTC datetime;; + @EndDateUTC datetime, + @extract_sql nvarchar(MAX), + @validation_sql nvarchar(MAX), + @xe bit, + @xd bit; /*Temporary objects used in the procedure*/ DECLARE @@ -20342,7 +20665,185 @@ BEGIN @TargetSessionType = N'ring_buffer'; END; + IF ISNULL(@TargetDatabaseName, DB_NAME()) IS NOT NULL + AND ISNULL(@TargetSchemaName, N'dbo') IS NOT NULL + AND @TargetTableName IS NOT NULL + AND @TargetColumnName IS NOT NULL + BEGIN + SET @TargetSessionType = N'table'; + END; + + /* Add this after the existing parameter validations */ + IF @TargetSessionType = N'table' + BEGIN + IF @TargetDatabaseName IS NULL + BEGIN + SET @TargetDatabaseName = DB_NAME(); + END; + + IF @TargetSchemaName IS NULL + BEGIN + SET @TargetSchemaName = N'dbo'; + END; + + IF @TargetTableName IS NULL + OR @TargetColumnName IS NULL + BEGIN + RAISERROR(N' + When using a table as a source, you must specify @TargetTableName, and @TargetColumnName. + When @TargetDatabaseName or @TargetSchemaName is NULL, they default to DB_NAME() AND dbo', + 11, 1) WITH NOWAIT; + RETURN; + END; + + /* Check if target database exists */ + IF NOT EXISTS + ( + SELECT + 1/0 + FROM sys.databases AS d + WHERE d.name = @TargetDatabaseName + ) + BEGIN + RAISERROR(N'The specified @TargetDatabaseName %s does not exist.', 11, 1, @TargetDatabaseName) WITH NOWAIT; + RETURN; + END; + + /* Use dynamic SQL to validate schema, table, and column existence */ + SET @validation_sql = N' + IF NOT EXISTS + ( + SELECT + 1/0 + FROM ' + QUOTENAME(@TargetDatabaseName) + N'.sys.schemas AS s + WHERE s.name = @schema + ) + BEGIN + RAISERROR(N''The specified @TargetSchemaName %s does not exist in database %s.'', 11, 1, @schema, @database) WITH NOWAIT; + RETURN; + END; + + IF NOT EXISTS + ( + SELECT + 1/0 + FROM ' + QUOTENAME(@TargetDatabaseName) + N'.sys.tables AS t + JOIN ' + QUOTENAME(@TargetDatabaseName) + N'.sys.schemas AS s + ON t.schema_id = s.schema_id + WHERE t.name = @table + AND s.name = @schema + ) + BEGIN + RAISERROR(N''The specified @TargetTableName %s does not exist in schema %s in database %s.'', 11, 1, @table, @schema, @database) WITH NOWAIT; + RETURN; + END; + + IF NOT EXISTS + ( + SELECT + 1/0 + FROM ' + QUOTENAME(@TargetDatabaseName) + N'.sys.columns AS c + JOIN ' + QUOTENAME(@TargetDatabaseName) + N'.sys.tables AS t + ON c.object_id = t.object_id + JOIN ' + QUOTENAME(@TargetDatabaseName) + N'.sys.schemas AS s + ON t.schema_id = s.schema_id + WHERE c.name = @column + AND t.name = @table + AND s.name = @schema + ) + BEGIN + RAISERROR(N''The specified @TargetColumnName %s does not exist in table %s.%s in database %s.'', 11, 1, @column, @schema, @table, @database) WITH NOWAIT; + RETURN; + END; + + /* Validate column is XML type */ + IF NOT EXISTS + ( + SELECT + 1/0 + FROM ' + QUOTENAME(@TargetDatabaseName) + N'.sys.columns AS c + JOIN ' + QUOTENAME(@TargetDatabaseName) + N'.sys.types AS ty + ON c.user_type_id = ty.user_type_id + JOIN ' + QUOTENAME(@TargetDatabaseName) + N'.sys.tables AS t + ON c.object_id = t.object_id + JOIN ' + QUOTENAME(@TargetDatabaseName) + N'.sys.schemas AS s + ON t.schema_id = s.schema_id + WHERE c.name = @column + AND t.name = @table + AND s.name = @schema + AND ty.name = N''xml'' + ) + BEGIN + RAISERROR(N''The specified @TargetColumnName %s must be of XML data type.'', 11, 1, @column) WITH NOWAIT; + RETURN; + END;'; + + /* Validate timestamp_column if specified */ + IF @TargetTimestampColumnName IS NOT NULL + BEGIN + SET @validation_sql = @validation_sql + N' + IF NOT EXISTS + ( + SELECT + 1/0 + FROM ' + QUOTENAME(@TargetDatabaseName) + N'.sys.columns AS c + JOIN ' + QUOTENAME(@TargetDatabaseName) + N'.sys.tables AS t + ON c.object_id = t.object_id + JOIN ' + QUOTENAME(@TargetDatabaseName) + N'.sys.schemas AS s + ON t.schema_id = s.schema_id + WHERE c.name = @timestamp_column + AND t.name = @table + AND s.name = @schema + ) + BEGIN + RAISERROR(N''The specified @TargetTimestampColumnName %s does not exist in table %s.%s in database %s.'', 11, 1, @timestamp_column, @schema, @table, @database) WITH NOWAIT; + RETURN; + END; + + /* Validate timestamp column is datetime type */ + IF NOT EXISTS + ( + SELECT + 1/0 + FROM ' + QUOTENAME(@TargetDatabaseName) + N'.sys.columns AS c + JOIN ' + QUOTENAME(@TargetDatabaseName) + N'.sys.types AS ty + ON c.user_type_id = ty.user_type_id + JOIN ' + QUOTENAME(@TargetDatabaseName) + N'.sys.tables AS t + ON c.object_id = t.object_id + JOIN ' + QUOTENAME(@TargetDatabaseName) + N'.sys.schemas AS s + ON t.schema_id = s.schema_id + WHERE c.name = @timestamp_column + AND t.name = @table + AND s.name = @schema + AND ty.name LIKE ''%date%'' + ) + BEGIN + RAISERROR(N''The specified @TargetTimestampColumnName %s must be of datetime data type.'', 11, 1, @timestamp_column) WITH NOWAIT; + RETURN; + END;'; + END; + + IF @Debug = 1 BEGIN PRINT @validation_sql; END; + + EXECUTE sys.sp_executesql + @validation_sql, + N' + @database sysname, + @schema sysname, + @table sysname, + @column sysname, + @timestamp_column sysname + ', + @TargetDatabaseName, + @TargetSchemaName, + @TargetTableName, + @TargetColumnName, + @TargetTimestampColumnName; + END; + + IF @Azure = 0 + AND LOWER(@TargetSessionType) <> N'table' BEGIN IF NOT EXISTS ( @@ -20359,8 +20860,9 @@ BEGIN RETURN; END; END; - + IF @Azure = 1 + AND LOWER(@TargetSessionType) <> N'table' BEGIN IF NOT EXISTS ( @@ -20419,7 +20921,7 @@ BEGIN N'@r sysname OUTPUT'; IF @Debug = 1 BEGIN PRINT @StringToExecute; END; - EXEC sys.sp_executesql + EXECUTE sys.sp_executesql @StringToExecute, @StringToExecuteParams, @r OUTPUT; @@ -20459,7 +20961,7 @@ BEGIN N' ADD spid smallint NULL;'; IF @Debug = 1 BEGIN PRINT @StringToExecute; END; - EXEC sys.sp_executesql + EXECUTE sys.sp_executesql @StringToExecute; /* If the table doesn't have the new wait_resource column, add it. See Github #3101. */ @@ -20475,7 +20977,7 @@ BEGIN N' ADD wait_resource nvarchar(MAX) NULL;'; IF @Debug = 1 BEGIN PRINT @StringToExecute; END; - EXEC sys.sp_executesql + EXECUTE sys.sp_executesql @StringToExecute; /* If the table doesn't have the new client option column, add it. See Github #3101. */ @@ -20491,7 +20993,7 @@ BEGIN N' ADD client_option_1 varchar(500) NULL;'; IF @Debug = 1 BEGIN PRINT @StringToExecute; END; - EXEC sys.sp_executesql + EXECUTE sys.sp_executesql @StringToExecute; /* If the table doesn't have the new client option column, add it. See Github #3101. */ @@ -20507,7 +21009,7 @@ BEGIN N' ADD client_option_2 varchar(500) NULL;'; IF @Debug = 1 BEGIN PRINT @StringToExecute; END; - EXEC sys.sp_executesql + EXECUTE sys.sp_executesql @StringToExecute; /* If the table doesn't have the new lock mode column, add it. See Github #3101. */ @@ -20523,7 +21025,7 @@ BEGIN N' ADD lock_mode nvarchar(256) NULL;'; IF @Debug = 1 BEGIN PRINT @StringToExecute; END; - EXEC sys.sp_executesql + EXECUTE sys.sp_executesql @StringToExecute; /* If the table doesn't have the new status column, add it. See Github #3101. */ @@ -20539,7 +21041,7 @@ BEGIN N' ADD status nvarchar(256) NULL;'; IF @Debug = 1 BEGIN PRINT @StringToExecute; END; - EXEC sys.sp_executesql + EXECUTE sys.sp_executesql @StringToExecute; END; ELSE /* end if @r is not null. if it is null there is no table, create it from above execution */ @@ -20597,7 +21099,7 @@ BEGIN )'; IF @Debug = 1 BEGIN PRINT @StringToExecute; END; - EXEC sys.sp_executesql + EXECUTE sys.sp_executesql @StringToExecute; /*table created.*/ @@ -20612,7 +21114,7 @@ BEGIN N'@r sysname OUTPUT'; IF @Debug = 1 BEGIN PRINT @StringToExecute; END; - EXEC sys.sp_executesql + EXECUTE sys.sp_executesql @StringToExecute, @StringToExecuteParams, @r OUTPUT; @@ -20640,7 +21142,7 @@ BEGIN );'; IF @Debug = 1 BEGIN PRINT @StringToExecute; END; - EXEC sys.sp_executesql + EXECUTE sys.sp_executesql @StringToExecute; END; END; @@ -20669,7 +21171,7 @@ BEGIN @OutputTableFindings; IF @Debug = 1 BEGIN PRINT @StringToExecute; END; - EXEC sys.sp_executesql + EXECUTE sys.sp_executesql @StringToExecute; /*create synonym for deadlock table.*/ @@ -20696,7 +21198,7 @@ BEGIN @OutputTableName; IF @Debug = 1 BEGIN PRINT @StringToExecute; END; - EXEC sys.sp_executesql + EXECUTE sys.sp_executesql @StringToExecute; END; END; @@ -20768,6 +21270,7 @@ BEGIN ( @Azure = 1 AND @TargetSessionType IS NULL + AND LOWER(@TargetSessionType) <> N'table' ) BEGIN RAISERROR('@TargetSessionType is NULL, assigning for Azure instance', 0, 1) WITH NOWAIT; @@ -21066,6 +21569,148 @@ BEGIN RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; END; + /* If table target */ + IF @TargetSessionType = 'table' + BEGIN + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Inserting to #deadlock_data from table source %s', 0, 1, @d) WITH NOWAIT; + + /* + First, we need to heck the XML structure. + Depending on the data source, the XML could + contain either the /event or /deadlock nodes. + When the /event nodes are not present, there + is no @name attribute to evaluate. + */ + + SELECT + @extract_sql = N' + SELECT TOP (1) + @xe = xe.e.exist(''.''), + @xd = xd.e.exist(''.'') + FROM [master].[dbo].[bpr] AS x + OUTER APPLY x.[bpr].nodes(''/event'') AS xe(e) + OUTER APPLY x.[bpr].nodes(''/deadlock'') AS xd(e) + OPTION(RECOMPILE); + '; + + IF @Debug = 1 BEGIN PRINT @extract_sql; END; + + EXECUTE sys.sp_executesql + @extract_sql, + N' + @xe bit OUTPUT, + @xd bit OUTPUT + ', + @xe OUTPUT, + @xd OUTPUT; + + + /* Build dynamic SQL to extract the XML */ + IF @xe = 1 + AND @xd IS NULL + BEGIN + SET @extract_sql = N' + SELECT + deadlock_xml = ' + + QUOTENAME(@TargetColumnName) + + N' + FROM ' + + QUOTENAME(@TargetDatabaseName) + + N'.' + + QUOTENAME(@TargetSchemaName) + + N'.' + + QUOTENAME(@TargetTableName) + + N' AS x + LEFT JOIN #t AS t + ON 1 = 1 + CROSS APPLY x.' + + QUOTENAME(@TargetColumnName) + + N'.nodes(''/event'') AS e(x) + WHERE + ( + e.x.exist(''@name[ .= "xml_deadlock_report"]'') = 1 + OR e.x.exist(''@name[ .= "database_xml_deadlock_report"]'') = 1 + OR e.x.exist(''@name[ .= "xml_deadlock_report_filtered"]'') = 1 + )'; + END; + + IF @xe IS NULL + AND @xd = 1 + BEGIN + SET @extract_sql = N' + SELECT + deadlock_xml = ' + + QUOTENAME(@TargetColumnName) + + N' + FROM ' + + QUOTENAME(@TargetDatabaseName) + + N'.' + + QUOTENAME(@TargetSchemaName) + + N'.' + + QUOTENAME(@TargetTableName) + + N' AS x + LEFT JOIN #t AS t + ON 1 = 1 + CROSS APPLY x.' + + QUOTENAME(@TargetColumnName) + + N'.nodes(''/deadlock'') AS e(x) + WHERE 1 = 1'; + END; + + /* Add timestamp filtering if specified */ + IF @TargetTimestampColumnName IS NOT NULL + BEGIN + SET @extract_sql = @extract_sql + N' + AND x.' + QUOTENAME(@TargetTimestampColumnName) + N' >= @StartDate + AND x.' + QUOTENAME(@TargetTimestampColumnName) + N' < @EndDate'; + END; + + /* If no timestamp column but date filtering is needed, handle XML-based filtering when possible */ + IF @TargetTimestampColumnName IS NULL + AND @xe = 1 + AND @xd IS NULL + BEGIN + SET @extract_sql = @extract_sql + N' + AND e.x.exist(''@timestamp[. >= sql:variable("@StartDate") and . < sql:variable("@EndDate")]'') = 1'; + END; + + /*Woof*/ + IF @TargetTimestampColumnName IS NULL + AND @xe IS NULL + AND @xd = 1 + BEGIN + SET @extract_sql = @extract_sql + N' + AND e.x.exist(''(/deadlock/process-list/process/@lasttranstarted)[. >= sql:variable("@StartDate") and . < sql:variable("@EndDate")]'') = 1'; + END; + + SET @extract_sql += N' + OPTION(RECOMPILE); + '; + + IF @Debug = 1 BEGIN PRINT @extract_sql; END; + + /* Execute the dynamic SQL */ + INSERT + #deadlock_data + WITH + (TABLOCKX) + ( + deadlock_xml + ) + EXECUTE sys.sp_executesql + @extract_sql, + N' + @StartDate datetime, + @EndDate datetime + ', + @StartDate, + @EndDate; + + SET @d = CONVERT(varchar(40), GETDATE(), 109); + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; + END; + /*Parse process and input buffer xml*/ SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Initial Parse process and input buffer xml %s', 0, 1, @d) WITH NOWAIT; @@ -21081,6 +21726,21 @@ BEGIN FROM #deadlock_data AS d1 LEFT JOIN #t AS t ON 1 = 1 + WHERE @xe = 1 + + UNION ALL + + SELECT + d1.deadlock_xml, + event_date = d1.deadlock_xml.value('(/deadlock/process-list/process/@lasttranstarted)[1]', 'datetime2'), + victim_id = d1.deadlock_xml.value('(/deadlock/victim-list/victimProcess/@id)[1]', 'nvarchar(256)'), + is_parallel = d1.deadlock_xml.exist('/deadlock/resource-list/exchangeEvent'), + is_parallel_batch = d1.deadlock_xml.exist('/deadlock/resource-list/SyncPoint'), + deadlock_graph = d1.deadlock_xml.query('.') + FROM #deadlock_data AS d1 + LEFT JOIN #t AS t + ON 1 = 1 + WHERE @xd = 1 OPTION(RECOMPILE); SET @d = CONVERT(varchar(40), GETDATE(), 109); @@ -21753,7 +22413,7 @@ BEGIN '; IF @Debug = 1 BEGIN PRINT @StringToExecute; END; - EXEC sys.sp_executesql + EXECUTE sys.sp_executesql @StringToExecute; END; @@ -21902,7 +22562,7 @@ BEGIN COUNT_BIG(DISTINCT dp.event_date) ) + N' deadlocks.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT dp.event_date) DESC) FROM #deadlock_process AS dp @@ -21957,7 +22617,7 @@ BEGIN COUNT_BIG(DISTINCT dow.event_date) ) + N' deadlock(s) between read queries and modification queries.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT dow.event_date) DESC) FROM #deadlock_owner_waiter AS dow @@ -22019,7 +22679,7 @@ BEGIN COUNT_BIG(DISTINCT dow.event_date) ) + N' deadlock(s).', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT dow.event_date) DESC) FROM #deadlock_owner_waiter AS dow @@ -22062,7 +22722,7 @@ BEGIN COUNT_BIG(DISTINCT dow.event_date) ) + N' deadlock(s).', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT dow.event_date) DESC) FROM #deadlock_owner_waiter AS dow @@ -22111,7 +22771,7 @@ BEGIN COUNT_BIG(DISTINCT dow.event_date) ) + N' deadlock(s).', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT dow.event_date) DESC) FROM #deadlock_owner_waiter AS dow @@ -22160,7 +22820,7 @@ BEGIN COUNT_BIG(DISTINCT dp.event_date) ) + N' instances of Serializable deadlocks.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT dp.event_date) DESC) FROM #deadlock_process AS dp @@ -22203,7 +22863,7 @@ BEGIN COUNT_BIG(DISTINCT dp.event_date) ) + N' instances of Repeatable Read deadlocks.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT dp.event_date) DESC) FROM #deadlock_process AS dp @@ -22265,7 +22925,7 @@ BEGIN N'UNKNOWN' ) + N'.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT dp.event_date) DESC) FROM #deadlock_process AS dp @@ -22377,7 +23037,7 @@ BEGIN 1, N'' ) + N' locks.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY CONVERT(bigint, lt.lock_count) DESC) FROM lock_types AS lt @@ -22447,7 +23107,7 @@ BEGIN dow.database_name, object_name = ds.proc_name, finding_group = N'More Info - Query', - finding = N'EXEC sp_BlitzCache ' + + finding = N'EXECUTE sp_BlitzCache ' + CASE WHEN ds.proc_name = N'adhoc' THEN N'@OnlySqlHandles = ' + ds.sql_handle_csv @@ -22503,7 +23163,7 @@ BEGIN object_name = ds.proc_name, finding_group = N'More Info - Query', finding = - N'EXEC sp_BlitzQueryStore ' + + N'EXECUTE sp_BlitzQueryStore ' + N'@DatabaseName = ' + QUOTENAME(ds.database_name, N'''') + N', ' + @@ -22556,7 +23216,7 @@ BEGIN COUNT_BIG(DISTINCT ds.id) ) + N' deadlocks.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT ds.id) DESC) FROM #deadlock_stack AS ds @@ -22616,7 +23276,7 @@ BEGIN bi.object_name, finding_group = N'More Info - Table', finding = - N'EXEC sp_BlitzIndex ' + + N'EXECUTE sp_BlitzIndex ' + N'@DatabaseName = ' + QUOTENAME(bi.database_name, N'''') + N', @SchemaName = ' + @@ -22657,19 +23317,19 @@ BEGIN ) ), wait_time_hms = - /*the more wait time you rack up the less accurate this gets, + /*the more wait time you rack up the less accurate this gets, it's either that or erroring out*/ - CASE - WHEN + CASE + WHEN SUM ( CONVERT ( - bigint, + bigint, dp.wait_time ) )/1000 > 2147483647 - THEN + THEN CONVERT ( nvarchar(30), @@ -22682,7 +23342,7 @@ BEGIN ( CONVERT ( - bigint, + bigint, dp.wait_time ) ) @@ -22693,16 +23353,16 @@ BEGIN ), 14 ) - WHEN + WHEN SUM ( CONVERT ( - bigint, + bigint, dp.wait_time ) ) BETWEEN 2147483648 AND 2147483647000 - THEN + THEN CONVERT ( nvarchar(30), @@ -22715,7 +23375,7 @@ BEGIN ( CONVERT ( - bigint, + bigint, dp.wait_time ) ) @@ -22797,7 +23457,7 @@ BEGIN 14 ) + N' [dd hh:mm:ss:ms] of deadlock wait time.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY cs.total_waits DESC) FROM chopsuey AS cs @@ -22871,19 +23531,19 @@ BEGIN ) ) + N' ' + - /*the more wait time you rack up the less accurate this gets, + /*the more wait time you rack up the less accurate this gets, it's either that or erroring out*/ - CASE - WHEN + CASE + WHEN SUM ( CONVERT ( - bigint, + bigint, wt.total_wait_time_ms ) )/1000 > 2147483647 - THEN + THEN CONVERT ( nvarchar(30), @@ -22896,7 +23556,7 @@ BEGIN ( CONVERT ( - bigint, + bigint, wt.total_wait_time_ms ) ) @@ -22907,16 +23567,16 @@ BEGIN ), 14 ) - WHEN + WHEN SUM ( CONVERT ( - bigint, + bigint, wt.total_wait_time_ms ) ) BETWEEN 2147483648 AND 2147483647000 - THEN + THEN CONVERT ( nvarchar(30), @@ -22929,7 +23589,7 @@ BEGIN ( CONVERT ( - bigint, + bigint, wt.total_wait_time_ms ) ) @@ -22962,7 +23622,7 @@ BEGIN 14 ) END + N' [dd hh:mm:ss:ms] of deadlock wait time.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY SUM(CONVERT(bigint, wt.total_wait_time_ms)) DESC) FROM wait_time AS wt @@ -23000,7 +23660,7 @@ BEGIN N'There have been ' + RTRIM(COUNT_BIG(DISTINCT aj.event_date)) + N' deadlocks from this Agent Job and Step.', - sort_order = + sort_order = ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT aj.event_date) DESC) FROM #agent_job AS aj @@ -23693,9 +24353,9 @@ BEGIN SET STATISTICS XML ON; END; - SET @StringToExecute = N' + SET @StringToExecute = N' - INSERT INTO ' + QUOTENAME(DB_NAME()) + N'..DeadLockTbl + INSERT INTO ' + QUOTENAME(DB_NAME()) + N'..DeadLockTbl ( ServerName, deadlock_type, @@ -23738,9 +24398,9 @@ BEGIN waiter_waiting_to_close, deadlock_graph ) - EXEC sys.sp_executesql - @deadlock_result;' - EXEC sys.sp_executesql @StringToExecute, N'@deadlock_result NVARCHAR(MAX)', @deadlock_result; + EXECUTE sys.sp_executesql + @deadlock_result;'; + EXECUTE sys.sp_executesql @StringToExecute, N'@deadlock_result NVARCHAR(MAX)', @deadlock_result; IF @Debug = 1 BEGIN @@ -23756,7 +24416,7 @@ BEGIN SET @StringToExecute = N' - INSERT INTO ' + QUOTENAME(DB_NAME()) + N'..DeadlockFindings + INSERT INTO ' + QUOTENAME(DB_NAME()) + N'..DeadlockFindings ( ServerName, check_id, @@ -23774,8 +24434,8 @@ BEGIN df.finding FROM #deadlock_findings AS df ORDER BY df.check_id - OPTION(RECOMPILE);' - EXEC sys.sp_executesql @StringToExecute; + OPTION(RECOMPILE);'; + EXECUTE sys.sp_executesql @StringToExecute; RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; @@ -23785,23 +24445,23 @@ BEGIN BEGIN SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Results to client %s', 0, 1, @d) WITH NOWAIT; - + IF @Debug = 1 BEGIN SET STATISTICS XML ON; END; - - EXEC sys.sp_executesql + + EXECUTE sys.sp_executesql @deadlock_result; - + IF @Debug = 1 BEGIN SET STATISTICS XML OFF; PRINT @deadlock_result; END; - + RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Getting available execution plans for deadlocks %s', 0, 1, @d) WITH NOWAIT; - + SELECT DISTINCT available_plans = 'available_plans', @@ -23868,15 +24528,15 @@ BEGIN min_used_grant_mb = deqs.min_used_grant_kb * 8. / 1024., max_used_grant_mb = - deqs.max_used_grant_kb * 8. / 1024., + deqs.max_used_grant_kb * 8. / 1024., deqs.min_reserved_threads, deqs.max_reserved_threads, deqs.min_used_threads, deqs.max_used_threads, deqs.total_rows, - max_worker_time_ms = + max_worker_time_ms = deqs.max_worker_time / 1000., - max_elapsed_time_ms = + max_elapsed_time_ms = deqs.max_elapsed_time / 1000. INTO #dm_exec_query_stats FROM sys.dm_exec_query_stats AS deqs @@ -23888,7 +24548,7 @@ BEGIN WHERE ap.sql_handle = deqs.sql_handle ) AND deqs.query_hash IS NOT NULL; - + CREATE CLUSTERED INDEX deqs ON #dm_exec_query_stats @@ -23896,7 +24556,7 @@ BEGIN sql_handle, plan_handle ); - + SELECT ap.available_plans, ap.database_name, @@ -23930,7 +24590,7 @@ BEGIN ap.statement_end_offset FROM ( - + SELECT ap.*, c.statement_start_offset, @@ -23982,10 +24642,10 @@ BEGIN OPTION(RECOMPILE, LOOP JOIN, HASH JOIN); RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - + SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Returning findings %s', 0, 1, @d) WITH NOWAIT; - + SELECT df.check_id, df.database_name, @@ -23997,7 +24657,7 @@ BEGIN df.check_id, df.sort_order OPTION(RECOMPILE); - + SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; END; /*done with output to client app.*/ @@ -24081,7 +24741,7 @@ BEGIN END; IF OBJECT_ID('tempdb..#dm_exec_query_stats') IS NOT NULL - BEGIN + BEGIN SELECT table_name = N'#dm_exec_query_stats', * @@ -24116,6 +24776,16 @@ BEGIN @VictimsOnly, DeadlockType = @DeadlockType, + TargetDatabaseName = + @TargetDatabaseName, + TargetSchemaName = + @TargetSchemaName, + TargetTableName = + @TargetTableName, + TargetColumnName = + @TargetColumnName, + TargetTimestampColumnName = + @TargetTimestampColumnName, Debug = @Debug, Help = @@ -24220,7 +24890,7 @@ BEGIN SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.23', @VersionDate = '20241228'; + SELECT @Version = '8.24', @VersionDate = '20250407'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_Blitz.sql b/sp_Blitz.sql index e568f5886..43de4bbfe 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -38,7 +38,7 @@ AS SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.23', @VersionDate = '20241228'; + SELECT @Version = '8.24', @VersionDate = '20250407'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) diff --git a/sp_BlitzAnalysis.sql b/sp_BlitzAnalysis.sql index 74d290709..96216408c 100644 --- a/sp_BlitzAnalysis.sql +++ b/sp_BlitzAnalysis.sql @@ -37,7 +37,7 @@ AS SET NOCOUNT ON; SET STATISTICS XML OFF; -SELECT @Version = '8.23', @VersionDate = '20241228'; +SELECT @Version = '8.24', @VersionDate = '20250407'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_BlitzBackups.sql b/sp_BlitzBackups.sql index f3285ece8..5f33342e4 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.23', @VersionDate = '20241228'; + SELECT @Version = '8.24', @VersionDate = '20250407'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_BlitzCache.sql b/sp_BlitzCache.sql index db4d24e4f..9c6453f70 100644 --- a/sp_BlitzCache.sql +++ b/sp_BlitzCache.sql @@ -282,7 +282,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.23', @VersionDate = '20241228'; +SELECT @Version = '8.24', @VersionDate = '20250407'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) diff --git a/sp_BlitzFirst.sql b/sp_BlitzFirst.sql index e32fa33f6..bdbcda1d4 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.23', @VersionDate = '20241228'; +SELECT @Version = '8.24', @VersionDate = '20250407'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index f7624172d..365cbe6c6 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -49,7 +49,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.23', @VersionDate = '20241228'; +SELECT @Version = '8.24', @VersionDate = '20250407'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index 79029b7ca..3b4f65248 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.23', @VersionDate = '20241228'; + SELECT @Version = '8.24', @VersionDate = '20250407'; IF @VersionCheckMode = 1 BEGIN diff --git a/sp_BlitzWho.sql b/sp_BlitzWho.sql index 2f3ceadd9..6bb2df72f 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.23', @VersionDate = '20241228'; + SELECT @Version = '8.24', @VersionDate = '20250407'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_DatabaseRestore.sql b/sp_DatabaseRestore.sql index f478a9d8c..b685a0d08 100755 --- a/sp_DatabaseRestore.sql +++ b/sp_DatabaseRestore.sql @@ -58,7 +58,7 @@ SET STATISTICS XML OFF; /*Versioning details*/ -SELECT @Version = '8.23', @VersionDate = '20241228'; +SELECT @Version = '8.24', @VersionDate = '20250407'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_ineachdb.sql b/sp_ineachdb.sql index 0d2a85057..16df3b7c5 100644 --- a/sp_ineachdb.sql +++ b/sp_ineachdb.sql @@ -36,7 +36,7 @@ BEGIN SET NOCOUNT ON; SET STATISTICS XML OFF; - SELECT @Version = '8.23', @VersionDate = '20241228'; + SELECT @Version = '8.24', @VersionDate = '20250407'; IF(@VersionCheckMode = 1) BEGIN From f7ec30f1f2c0ebd5b63d1724f28b1b4d09ba8892 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Tue, 15 Apr 2025 08:48:04 -0400 Subject: [PATCH 615/662] #3627 sp_Blitz 7745 warnings Remove duplicate warnings for trace flag 7745. Closes #3627. --- sp_Blitz.sql | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index 43de4bbfe..385a73ebd 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -8558,8 +8558,7 @@ EXEC dbo.sp_MSforeachdb 'USE [?]; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITT 'Informational' AS FindingsGroup , 'Recommended Trace Flag Off' AS Finding , 'https://www.sqlskills.com/blogs/erin/query-store-trace-flags/' AS URL , - 'Trace Flag 7745 not enabled globally. It makes shutdowns/failovers quicker by not waiting for Query Store to flush to disk. It is recommended, but it loses you the non-flushed Query Store data.' AS Details - FROM #TraceStatus T + 'Trace Flag 7745 not enabled globally. It makes shutdowns/failovers quicker by not waiting for Query Store to flush to disk. It is recommended, but it loses you the non-flushed Query Store data.' AS Details; END; IF NOT EXISTS ( SELECT 1 From dcfc4ec92aeb80c5ed9e8479b2f00c2c726e9ed1 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Tue, 15 Apr 2025 09:18:00 -0400 Subject: [PATCH 616/662] #3629 sp_BlitzFirst move avg ms To the left in result sets. Closes #3629. --- sp_BlitzFirst.sql | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/sp_BlitzFirst.sql b/sp_BlitzFirst.sql index bdbcda1d4..f36f5fee7 100644 --- a/sp_BlitzFirst.sql +++ b/sp_BlitzFirst.sql @@ -4654,13 +4654,13 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, wd1.wait_type, COALESCE(wcat.WaitCategory, 'Other') AS wait_category, CAST(c.[Wait Time (Seconds)] / 60. / 60. AS DECIMAL(18,1)) AS [Wait Time (Hours)], - CAST((wd2.wait_time_ms - wd1.wait_time_ms) / 1000.0 / cores.cpu_count / DATEDIFF(ss, wd1.SampleTime, wd2.SampleTime) AS DECIMAL(18,1)) AS [Per Core Per Hour], - (wd2.waiting_tasks_count - wd1.waiting_tasks_count) AS [Number of Waits], CASE WHEN (wd2.waiting_tasks_count - wd1.waiting_tasks_count) > 0 THEN CAST((wd2.wait_time_ms-wd1.wait_time_ms)/ (1.0*(wd2.waiting_tasks_count - wd1.waiting_tasks_count)) AS NUMERIC(12,1)) - ELSE 0 END AS [Avg ms Per Wait] + ELSE 0 END AS [Avg ms Per Wait], + CAST((wd2.wait_time_ms - wd1.wait_time_ms) / 1000.0 / cores.cpu_count / DATEDIFF(ss, wd1.SampleTime, wd2.SampleTime) AS DECIMAL(18,1)) AS [Per Core Per Hour], + (wd2.waiting_tasks_count - wd1.waiting_tasks_count) AS [Number of Waits] FROM max_batch b JOIN #WaitStats wd2 ON wd2.SampleTime =b.SampleTime @@ -4799,17 +4799,17 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, wd1.wait_type, COALESCE(wcat.WaitCategory, 'Other') AS wait_category, CAST(c.[Wait Time (Seconds)] / 60. / 60. AS DECIMAL(18,1)) AS [Wait Time (Hours)], + CASE WHEN (wd2.waiting_tasks_count - wd1.waiting_tasks_count) > 0 + THEN + CAST((wd2.wait_time_ms-wd1.wait_time_ms)/ + (1.0*(wd2.waiting_tasks_count - wd1.waiting_tasks_count)) AS NUMERIC(12,1)) + ELSE 0 END AS [Avg ms Per Wait], CAST((wd2.wait_time_ms - wd1.wait_time_ms) / 1000.0 / cores.cpu_count / DATEDIFF(ss, wd1.SampleTime, wd2.SampleTime) AS DECIMAL(18,1)) AS [Per Core Per Hour], CAST(c.[Signal Wait Time (Seconds)] / 60.0 / 60 AS DECIMAL(18,1)) AS [Signal Wait Time (Hours)], CASE WHEN c.[Wait Time (Seconds)] > 0 THEN CAST(100.*(c.[Signal Wait Time (Seconds)]/c.[Wait Time (Seconds)]) AS NUMERIC(4,1)) ELSE 0 END AS [Percent Signal Waits], (wd2.waiting_tasks_count - wd1.waiting_tasks_count) AS [Number of Waits], - CASE WHEN (wd2.waiting_tasks_count - wd1.waiting_tasks_count) > 0 - THEN - CAST((wd2.wait_time_ms-wd1.wait_time_ms)/ - (1.0*(wd2.waiting_tasks_count - wd1.waiting_tasks_count)) AS NUMERIC(12,1)) - ELSE 0 END AS [Avg ms Per Wait], N'https://www.sqlskills.com/help/waits/' + LOWER(wd1.wait_type) + '/' AS URL FROM max_batch b JOIN #WaitStats wd2 ON @@ -4843,17 +4843,17 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, wd1.wait_type, COALESCE(wcat.WaitCategory, 'Other') AS wait_category, c.[Wait Time (Seconds)], + CASE WHEN (wd2.waiting_tasks_count - wd1.waiting_tasks_count) > 0 + THEN + CAST((wd2.wait_time_ms-wd1.wait_time_ms)/ + (1.0*(wd2.waiting_tasks_count - wd1.waiting_tasks_count)) AS NUMERIC(12,1)) + ELSE 0 END AS [Avg ms Per Wait], CAST((CAST(wd2.wait_time_ms - wd1.wait_time_ms AS MONEY)) / 1000.0 / cores.cpu_count / DATEDIFF(ss, wd1.SampleTime, wd2.SampleTime) AS DECIMAL(18,1)) AS [Per Core Per Second], c.[Signal Wait Time (Seconds)], CASE WHEN c.[Wait Time (Seconds)] > 0 THEN CAST(100.*(c.[Signal Wait Time (Seconds)]/c.[Wait Time (Seconds)]) AS NUMERIC(4,1)) ELSE 0 END AS [Percent Signal Waits], (wd2.waiting_tasks_count - wd1.waiting_tasks_count) AS [Number of Waits], - CASE WHEN (wd2.waiting_tasks_count - wd1.waiting_tasks_count) > 0 - THEN - CAST((wd2.wait_time_ms-wd1.wait_time_ms)/ - (1.0*(wd2.waiting_tasks_count - wd1.waiting_tasks_count)) AS NUMERIC(12,1)) - ELSE 0 END AS [Avg ms Per Wait], N'https://www.sqlskills.com/help/waits/' + LOWER(wd1.wait_type) + '/' AS URL FROM max_batch b JOIN #WaitStats wd2 ON From b6ca85e4ff2904ce92272e7cafdecf71b2e4f962 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Tue, 15 Apr 2025 12:11:20 -0400 Subject: [PATCH 617/662] #3632 sp_BlitzCache readable replicas Lets you query the plan cache on secondaries. Closes #3632. --- README.md | 1 + sp_BlitzCache.sql | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e874b611c..9698711a6 100644 --- a/README.md +++ b/README.md @@ -180,6 +180,7 @@ Other common parameters include: * @ExportToExcel = 1 - turn this on, and it doesn't return XML fields that would hinder you from copy/pasting the data into Excel. * @ExpertMode = 1 - turn this on, and you get more columns with more data. Doesn't take longer to run though. * @IgnoreSystemDBs = 0 - if you want to show queries in master/model/msdb. By default we hide these. Additionally hides queries from databases named `dbadmin`, `dbmaintenance`, and `dbatools`. +* @IgnoreReadableReplicaDBs = 0 - if you want to analyze the plan cache on an Availability Group readable replica. You will also have to connect to the replica using ApplicationIntent = ReadOnly, since SQL Server itself will abort queries that try to do work in readable secondaries. * @MinimumExecutionCount = 0 - in servers like data warehouses where lots of queries only run a few times, you can set a floor number for examination. [*Back to top*](#header1) diff --git a/sp_BlitzCache.sql b/sp_BlitzCache.sql index 9c6453f70..655f855b5 100644 --- a/sp_BlitzCache.sql +++ b/sp_BlitzCache.sql @@ -256,6 +256,7 @@ ALTER PROCEDURE dbo.sp_BlitzCache @DurationFilter DECIMAL(38,4) = NULL , @HideSummary BIT = 0 , @IgnoreSystemDBs BIT = 1 , + @IgnoreReadableReplicaDBs BIT = 1 , @OnlyQueryHashes VARCHAR(MAX) = NULL , @IgnoreQueryHashes VARCHAR(MAX) = NULL , @OnlySqlHandles VARCHAR(MAX) = NULL , @@ -1405,7 +1406,7 @@ CREATE TABLE #plan_usage ); -IF EXISTS (SELECT * FROM sys.all_objects o WHERE o.name = 'dm_hadr_database_replica_states') +IF @IgnoreReadableReplicaDBs = 1 AND EXISTS (SELECT * FROM sys.all_objects o WHERE o.name = 'dm_hadr_database_replica_states') BEGIN RAISERROR('Checking for Read intent databases to exclude',0,0) WITH NOWAIT; @@ -1824,7 +1825,7 @@ IF @VersionShowsAirQuoteActualPlans = 1 SET @body += N' WHERE 1 = 1 ' + @nl ; - IF EXISTS (SELECT * FROM sys.all_objects o WHERE o.name = 'dm_hadr_database_replica_states') + IF @IgnoreReadableReplicaDBs = 1 AND EXISTS (SELECT * FROM sys.all_objects o WHERE o.name = 'dm_hadr_database_replica_states') BEGIN RAISERROR(N'Ignoring readable secondaries databases by default', 0, 1) WITH NOWAIT; SET @body += N' AND CAST(xpa.value AS INT) NOT IN (SELECT database_id FROM #ReadableDBs)' + @nl ; From 5b95670eb3187f9175edac89d8111b7d40acce0e Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Tue, 15 Apr 2025 15:59:57 -0400 Subject: [PATCH 618/662] #3631 sp_BlitzFirst add thread time To headline news result set. Closes #3631. --- .../sp_BlitzFirst_Checks_by_Priority.md | 5 ++-- sp_BlitzFirst.sql | 29 +++++++++++++++++++ 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/Documentation/sp_BlitzFirst_Checks_by_Priority.md b/Documentation/sp_BlitzFirst_Checks_by_Priority.md index 053423541..2b7a0e25d 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: 49 -If you want to add a new check, start at 50. +CURRENT HIGH CHECKID: 50 +If you want to add a new check, start at 51. | Priority | FindingsGroup | Finding | URL | CheckID | |----------|---------------------------------|---------------------------------------|-------------------------------------------------|----------| @@ -58,4 +58,5 @@ If you want to add a new check, start at 50. | 251 | Server Info | Database Count | | 22 | | 251 | Server Info | Database Size, Total GB | | 21 | | 251 | Server Info | Memory Grant/Workspace info | | 40 | +| 251 | Server Info | Thread Time | https://www.brentozar.com/go/threadtime | 50 | | 254 | Informational | Thread Time Inaccurate | | 48 | diff --git a/sp_BlitzFirst.sql b/sp_BlitzFirst.sql index f36f5fee7..72004d33c 100644 --- a/sp_BlitzFirst.sql +++ b/sp_BlitzFirst.sql @@ -2021,6 +2021,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, FROM sys.databases WHERE database_id > 4; + /* Server Info - Memory Grants pending - CheckID 39 */ IF (@Debug = 1) BEGIN @@ -3325,6 +3326,34 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, OR max_session_percent >= 90); END + /* Server Info - Thread Time - CheckID 50 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 50',10,1) WITH NOWAIT; + END + + ;WITH max_batch AS ( + SELECT MAX(SampleTime) AS SampleTime + FROM #WaitStats + ) + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) + SELECT TOP 1 50 AS CheckID, + 251 AS Priority, + 'Server Info' AS FindingGroup, + 'Thread Time' AS Finding, + CAST(CAST(c.[Total Thread Time (Seconds)] AS DECIMAL(18,1)) AS VARCHAR(100)) AS Details, + CAST(c.[Total Thread Time (Seconds)] AS DECIMAL(18,1)) AS DetailsInt, + 'https://www.brentozar.com/go/threadtime' AS URL + FROM max_batch b + JOIN #WaitStats wd2 ON + wd2.SampleTime =b.SampleTime + JOIN #WaitStats wd1 ON + wd1.wait_type=wd2.wait_type AND + wd2.SampleTime > wd1.SampleTime + CROSS APPLY (SELECT + CAST((wd2.thread_time_ms - wd1.thread_time_ms)/1000. AS DECIMAL(18,1)) AS [Total Thread Time (Seconds)] + ) AS c; + /* Server Info - Batch Requests per Sec - CheckID 19 */ IF (@Debug = 1) BEGIN From 04c60f8fced3821d1553abd17d34069ff2cafc11 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Tue, 15 Apr 2025 17:22:12 -0400 Subject: [PATCH 619/662] #3635 sp_Blitz AG latency warning Closes #3635. --- Documentation/sp_Blitz_Checks_by_Priority.md | 5 ++- sp_Blitz.sql | 39 ++++++++++++++++++++ 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/Documentation/sp_Blitz_Checks_by_Priority.md b/Documentation/sp_Blitz_Checks_by_Priority.md index 8f964ffe5..c803f7d43 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: 267. -If you want to add a new one, start at 268. +CURRENT HIGH CHECKID: 268. +If you want to add a new one, start at 269. | Priority | FindingsGroup | Finding | URL | CheckID | |----------|-----------------------------|---------------------------------------------------------|------------------------------------------------------------------------|----------| @@ -32,6 +32,7 @@ If you want to add a new one, start at 268. | 1 | Security | Dangerous Service Account | https://vladdba.com/SQLServerSvcAccount | 259 | | 1 | Security | Dangerous Service Account | https://vladdba.com/SQLServerSvcAccount | 260 | | 1 | Security | Dangerous Service Account | https://vladdba.com/SQLServerSvcAccount | 261 | +| 5 | Availability | AG Replica Falling Behind | https://www.BrentOzar.com/go/ag | 268 | | 5 | Monitoring | Disabled Internal Monitoring Features | https://msdn.microsoft.com/en-us/library/ms190737.aspx | 177 | | 5 | Reliability | Dangerous Third Party Modules | https://support.microsoft.com/en-us/kb/2033238 | 179 | | 5 | Reliability | Priority Boost Enabled | https://www.BrentOzar.com/go/priorityboost | 126 | diff --git a/sp_Blitz.sql b/sp_Blitz.sql index 385a73ebd..0782feb7a 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -6706,6 +6706,45 @@ IF @ProductVersionMajor >= 10 + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 268 ) + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 268) WITH NOWAIT; + + INSERT INTO #BlitzResults + ( CheckID , + Priority , + DatabaseName , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT 268 AS CheckID, + 5 AS Priority, + DB_NAME(ps.database_id), + 'Availability' AS FindingsGroup, + 'AG Replica Falling Behind' AS Finding, + 'https://www.BrentOzar.com/go/ag' AS URL, + ag.name + N' AG replica server ' + + ar.replica_server_name + N' is ' + + CASE WHEN DATEDIFF(SECOND, drs.last_commit_time, ps.last_commit_time) < 200 THEN (CAST(DATEDIFF(SECOND, drs.last_commit_time, ps.last_commit_time) AS NVARCHAR(10)) + N' seconds ') + ELSE (CAST(DATEDIFF(MINUTE, drs.last_commit_time, ps.last_commit_time) AS NVARCHAR(10)) + N' minutes ') END + + N' behind the primary.' + AS details + FROM sys.dm_hadr_database_replica_states AS drs + JOIN sys.availability_replicas AS ar ON drs.replica_id = ar.replica_id + JOIN sys.availability_groups AS ag ON ar.group_id = ag.group_id + JOIN sys.dm_hadr_database_replica_states AS ps + ON drs.group_id = ps.group_id + AND drs.database_id = ps.database_id + AND ps.is_local = 1 /* Primary */ + WHERE drs.is_local = 0 /* Secondary */ + AND DATEDIFF(SECOND, drs.last_commit_time, ps.last_commit_time) > 60; + END; + IF @CheckUserDatabaseObjects = 1 BEGIN From 126a60882dbcfabd88b16b93f9d266abae252f30 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Tue, 15 Apr 2025 17:37:52 -0400 Subject: [PATCH 620/662] #3637 sp_BlitzFirst deadlock warning Add warning if deadlocks are happening now. Closes #3637. --- .../sp_BlitzFirst_Checks_by_Priority.md | 9 ++++---- sp_BlitzFirst.sql | 22 +++++++++++++++++++ 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/Documentation/sp_BlitzFirst_Checks_by_Priority.md b/Documentation/sp_BlitzFirst_Checks_by_Priority.md index 2b7a0e25d..b81ea5505 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: 50 -If you want to add a new check, start at 51. +CURRENT HIGH CHECKID: 51 +If you want to add a new check, start at 52. | Priority | FindingsGroup | Finding | URL | CheckID | |----------|---------------------------------|---------------------------------------|-------------------------------------------------|----------| @@ -42,10 +42,11 @@ If you want to add a new check, start at 51. | 50 | Server Performance | Too Much Free Memory | https://www.brentozar.com/go/freememory | 34 | | 50 | Server Performance | Memory Grants pending | https://www.brentozar.com/blitz/memory-grants | 39 | | 100 | In-Memory OLTP | Transactions aborted | https://www.brentozar.com/go/aborted | 32 | -| 100 | Query Problems | Suboptimal Plans/Sec High | https://www.brentozar.com/go/suboptimal | 33 | | 100 | Query Problems | Bad Estimates | https://www.brentozar.com/go/skewedup | 42 | -| 100 | Query Problems | Skewed Parallelism | https://www.brentozar.com/go/skewedup | 43 | +| 100 | Query Problems | Deadlocks | https://www.brentozar.com/go/deadlocks | 51 | | 100 | Query Problems | Query with a memory grant exceeding @MemoryGrantThresholdPct | https://www.brentozar.com/memory-grants-sql-servers-public-toilet/ | 46 | +| 100 | Query Problems | Skewed Parallelism | https://www.brentozar.com/go/skewedup | 43 | +| 100 | Query Problems | Suboptimal Plans/Sec High | https://www.brentozar.com/go/suboptimal | 33 | | 200 | Wait Stats | (One per wait type) | https://www.brentozar.com/sql/wait-stats/#(waittype) | 6 | | 210 | Potential Upcoming Problems | High Number of Connections |https://www.brentozar.com/archive/2014/05/connections-slow-sql-server-threadpool/ | 49 | | 210 | Query Stats | Plan Cache Analysis Skipped | https://www.brentozar.com/go/topqueries | 18 | diff --git a/sp_BlitzFirst.sql b/sp_BlitzFirst.sql index 72004d33c..5ea9f4472 100644 --- a/sp_BlitzFirst.sql +++ b/sp_BlitzFirst.sql @@ -3081,6 +3081,28 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, END; + /* Query Problems - Deadlocks - CheckID 51 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 51',10,1) WITH NOWAIT; + END + + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) + SELECT 51 AS CheckID, + 100 AS Priority, + 'Query Problems' AS FindingGroup, + 'Deadlocks' AS Finding, + ' https://www.brentozar.com/go/deadlocks' AS URL, + 'Number of deadlocks during the sample: ' + CAST(ps.value_delta AS NVARCHAR(20)) + @LineFeed + + 'Determined by sampling Perfmon counter ' + ps.object_name + ' - ' + ps.counter_name + @LineFeed AS Details, + 'Check sp_BlitzLock to find which indexes and queries to tune.' AS HowToStopIt + FROM #PerfmonStats ps + WHERE ps.Pass = 2 + AND counter_name = 'Number of Deadlocks/sec' + AND instance_name LIKE '_Total%' + AND value_delta > 0; + + /* SQL Server Internal Maintenance - Log File Growing - CheckID 13 */ IF (@Debug = 1) BEGIN From 5aae39864bb84482a434e3535573f81e27953ef8 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Sat, 26 Apr 2025 07:09:58 -0700 Subject: [PATCH 621/662] Update README.md Remove sp_BlitzLock @Top parameter. --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 9698711a6..bfcf2470d 100644 --- a/README.md +++ b/README.md @@ -301,7 +301,6 @@ In addition to the [parameters common to many of the stored procedures](#paramet Checks either the System Health session or a specific Extended Event session that captures deadlocks and parses out all the XML for you. Parameters you can use: -* @Top: Use if you want to limit the number of deadlocks to return. This is ordered by event date ascending. * @DatabaseName: If you want to filter to a specific database * @StartDate: The date you want to start searching on. * @EndDate: The date you want to stop searching on. From 77ebf85fc31b5a6b92bf1fa117664242c07b4a93 Mon Sep 17 00:00:00 2001 From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com> Date: Tue, 29 Apr 2025 11:23:02 -0400 Subject: [PATCH 622/662] Issue 3640 Closes #3640 Fixes hardcoded table and column names Fixes #dd temp table not populating when not in "table" mode. --- sp_BlitzLock.sql | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index 3b4f65248..52361d583 100644 --- a/sp_BlitzLock.sql +++ b/sp_BlitzLock.sql @@ -1255,7 +1255,7 @@ BEGIN END; /* If table target */ - IF @TargetSessionType = 'table' + IF LOWER(@TargetSessionType) = N'table' BEGIN SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Inserting to #deadlock_data from table source %s', 0, 1, @d) WITH NOWAIT; @@ -1273,9 +1273,19 @@ BEGIN SELECT TOP (1) @xe = xe.e.exist(''.''), @xd = xd.e.exist(''.'') - FROM [master].[dbo].[bpr] AS x - OUTER APPLY x.[bpr].nodes(''/event'') AS xe(e) - OUTER APPLY x.[bpr].nodes(''/deadlock'') AS xd(e) + FROM ' + + QUOTENAME(@TargetDatabaseName) + + N'.' + + QUOTENAME(@TargetSchemaName) + + N'.' + + QUOTENAME(@TargetTableName) + + N' AS x + OUTER APPLY x.' + + QUOTENAME(@TargetColumnName) + + N'.nodes(''/event'') AS xe(e) + OUTER APPLY x.' + + QUOTENAME(@TargetColumnName) + + N'.nodes(''/deadlock'') AS xd(e) OPTION(RECOMPILE); '; @@ -1412,6 +1422,7 @@ BEGIN LEFT JOIN #t AS t ON 1 = 1 WHERE @xe = 1 + OR LOWER(@TargetSessionType) <> N'table' UNION ALL From 5d8d560d801fd21015a243b2822b82a671910b3c Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Mon, 12 May 2025 07:47:38 -0700 Subject: [PATCH 623/662] #3643 - sp_BlitzFirst RDS Restores Now show up. Closes #3643. --- sp_BlitzFirst.sql | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sp_BlitzFirst.sql b/sp_BlitzFirst.sql index 5ea9f4472..fe1301bc4 100644 --- a/sp_BlitzFirst.sql +++ b/sp_BlitzFirst.sql @@ -1638,7 +1638,7 @@ BEGIN 'Maintenance Tasks Running' AS FindingGroup, 'Restore Running' AS Finding, 'https://www.brentozar.com/askbrent/backups/' AS URL, - 'Restore of ' + DB_NAME(db.resource_database_id) + ' database (' + (SELECT CAST(CAST(SUM(size * 8.0 / 1024 / 1024) AS BIGINT) AS NVARCHAR) FROM #MasterFiles WHERE database_id = db.resource_database_id) + '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), '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, @@ -1646,14 +1646,14 @@ BEGIN s.nt_user_name AS NTUserName, s.[program_name] AS ProgramName, s.[host_name] AS HostName, - db.[resource_database_id] AS DatabaseID, - DB_NAME(db.resource_database_id) AS DatabaseName, + COALESCE(db.[resource_database_id],0) AS DatabaseID, + COALESCE(DB_NAME(db.resource_database_id), 'Unknown') AS DatabaseName, 0 AS OpenTransactionCount, r.query_hash FROM sys.dm_exec_requests r INNER JOIN sys.dm_exec_connections c ON r.session_id = c.session_id INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id - INNER JOIN ( + LEFT OUTER JOIN ( SELECT DISTINCT request_session_id, resource_database_id FROM sys.dm_tran_locks WHERE resource_type = N'DATABASE' From d354ad4f7d6c4b78070ff3ba054f619f419ae6c9 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Tue, 20 May 2025 06:59:15 -0400 Subject: [PATCH 624/662] #3646 sp_Blitz 2025 db scoped Adds new database scoped configurations. Closes #3646. --- sp_Blitz.sql | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index 0782feb7a..2d0f72f05 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -7821,9 +7821,15 @@ IF @ProductVersionMajor >= 10 (33, 'MEMORY_GRANT_FEEDBACK_PERSISTENCE', '1', NULL, 267), (34, 'MEMORY_GRANT_FEEDBACK_PERCENTILE_GRANT', '1', NULL, 267), (35, 'OPTIMIZED_PLAN_FORCING', '1', NULL, 267), - (37, 'DOP_FEEDBACK', '0', NULL, 267), + (37, 'DOP_FEEDBACK', CASE WHEN @ProductVersionMajor >= 17 THEN '1' ELSE '0' END, NULL, 267), (38, 'LEDGER_DIGEST_STORAGE_ENDPOINT', 'OFF', NULL, 267), - (39, 'FORCE_SHOWPLAN_RUNTIME_PARAMETER_COLLECTION', '0', NULL, 267); + (39, 'FORCE_SHOWPLAN_RUNTIME_PARAMETER_COLLECTION', '0', NULL, 267), + (40, 'READABLE_SECONDARY_TEMPORARY_STATS_AUTO_CREATE', '1', NULL, 267), + (41, 'READABLE_SECONDARY_TEMPORARY_STATS_AUTO_UPDATE', '1', NULL, 267), + (42, 'OPTIMIZED_SP_EXECUTESQL', '0', NULL, 267), + (43, 'OPTIMIZED_HALLOWEEN_PROTECTION', '1', NULL, 267), + (44, 'FULLTEXT_INDEX_VERSION', '2', NULL, 267), + (47, 'OPTIONAL_PARAMETER_OPTIMIZATION', '1', NULL, 267); EXEC dbo.sp_MSforeachdb 'USE [?]; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; INSERT INTO #BlitzResults (CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details) SELECT def1.CheckID, DB_NAME(), 210, ''Non-Default Database Scoped Config'', dsc.[name], ''https://www.brentozar.com/go/dbscope'', (''Set value: '' + COALESCE(CAST(dsc.value AS NVARCHAR(100)),''Empty'') + '' Default: '' + COALESCE(CAST(def1.default_value AS NVARCHAR(100)),''Empty'') + '' Set value for secondary: '' + COALESCE(CAST(dsc.value_for_secondary AS NVARCHAR(100)),''Empty'') + '' Default value for secondary: '' + COALESCE(CAST(def1.default_value_for_secondary AS NVARCHAR(100)),''Empty'')) From de328c66c5c0f3c1258eb44dd3e2a612f8b27577 Mon Sep 17 00:00:00 2001 From: Ben <60398078+bwiggin10@users.noreply.github.com> Date: Mon, 16 Jun 2025 16:04:10 -0400 Subject: [PATCH 625/662] Add check for Query Store to sp_ineachdb --- sp_ineachdb.sql | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/sp_ineachdb.sql b/sp_ineachdb.sql index 16df3b7c5..b156bcf58 100644 --- a/sp_ineachdb.sql +++ b/sp_ineachdb.sql @@ -29,7 +29,8 @@ ALTER PROCEDURE [dbo].[sp_ineachdb] @Version varchar(30) = NULL OUTPUT, @VersionDate datetime = NULL OUTPUT, @VersionCheckMode bit = 0, - @is_ag_writeable_copy bit = 0 + @is_ag_writeable_copy bit = 0, + @is_query_store_on bit = 0 -- WITH EXECUTE AS OWNER – maybe not a great idea, depending on the security of your system AS BEGIN @@ -235,6 +236,23 @@ OPTION (MAXRECURSION 0); ) ); + -- delete any databases that don't match query store criteria + IF @SQLVersion >= 13 + BEGIN + DELETE dbs FROM #ineachdb AS dbs + WHERE EXISTS + ( + SELECT 1 + FROM sys.databases AS d + WHERE d.database_id = dbs.id + AND NOT + ( + is_query_store_on = COALESCE(@is_query_store_on, is_query_store_on) + AND NOT (@is_query_store_on = 1 AND d.database_id = 3) OR (@is_query_store_on = 0 AND d.database_id = 3) -- Excluding the model database which shows QS enabled in SQL2022+ + ) + ); + END + -- if a user access is specified, remove any that are NOT in that state IF @user_access IN (N'SINGLE_USER', N'MULTI_USER', N'RESTRICTED_USER') BEGIN From 470d55126071d5a8cafef5de5590ee6101e21faf Mon Sep 17 00:00:00 2001 From: Ben <60398078+bwiggin10@users.noreply.github.com> Date: Mon, 16 Jun 2025 16:06:33 -0400 Subject: [PATCH 626/662] Formatting --- sp_ineachdb.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sp_ineachdb.sql b/sp_ineachdb.sql index b156bcf58..bf9b147b4 100644 --- a/sp_ineachdb.sql +++ b/sp_ineachdb.sql @@ -241,7 +241,7 @@ OPTION (MAXRECURSION 0); BEGIN DELETE dbs FROM #ineachdb AS dbs WHERE EXISTS - ( + ( SELECT 1 FROM sys.databases AS d WHERE d.database_id = dbs.id From e85173dd2af8356ea0c9d79eebb84857a161bb81 Mon Sep 17 00:00:00 2001 From: Jane Palmer Date: Thu, 19 Jun 2025 10:57:47 +0100 Subject: [PATCH 627/662] Update sp_Blitz.sql Extend details for Invalid Logins - empty AD Groups will occasionally show up here (it's an AD thing), and it's a pain to delete that group and then have to reinstate it in SQL when it turns out you do need it. --- sp_Blitz.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index 2d0f72f05..c8cf8c65d 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -1862,7 +1862,7 @@ AS 'Security' AS FindingsGroup , 'Invalid login defined with Windows Authentication' AS Finding , 'https://docs.microsoft.com/en-us/sql/relational-databases/system-stored-procedures/sp-validatelogins-transact-sql' AS URL , - ( 'Windows user or group ' + QUOTENAME(LoginName) + ' is mapped to a SQL Server principal but no longer exists in the Windows environment.') AS Details + ( 'Windows user or group ' + QUOTENAME(LoginName) + ' is mapped to a SQL Server principal but no longer exists in the Windows environment. Sometimes empty AD groups can show up here so check thoroughly.') AS Details FROM #InvalidLogins ; END; From 39337ba705f892d18eaab5bf310f0138ccef99aa Mon Sep 17 00:00:00 2001 From: Ben <60398078+bwiggin10@users.noreply.github.com> Date: Thu, 26 Jun 2025 11:33:13 -0400 Subject: [PATCH 628/662] Exclude Model Database From @QueryStoreInUse Check The model database always returns 1 for is_query_store_on in sys.databases in SQL 2022, so this will always show query store is in use even if no user databases have it enabled. --- sp_Blitz.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index 2d0f72f05..0f37fb1c4 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -7777,7 +7777,7 @@ IF @ProductVersionMajor >= 10 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 74) WITH NOWAIT; - EXEC dbo.sp_MSforeachdb 'USE [?]; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; IF EXISTS(SELECT * FROM sys.databases WHERE is_query_store_on = 1) INSERT INTO #TemporaryDatabaseResults (DatabaseName, Finding) VALUES (DB_NAME(), ''Yup'') OPTION (RECOMPILE);'; + EXEC dbo.sp_MSforeachdb 'USE [?]; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; IF EXISTS(SELECT * FROM sys.databases WHERE is_query_store_on = 1 AND database_id <> 3) INSERT INTO #TemporaryDatabaseResults (DatabaseName, Finding) VALUES (DB_NAME(), ''Yup'') OPTION (RECOMPILE);'; IF EXISTS (SELECT * FROM #TemporaryDatabaseResults) SET @QueryStoreInUse = 1; END; From 0e3be2ff0c1fdc126e0670306e87701f6bb9441c Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Thu, 3 Jul 2025 03:51:27 -0700 Subject: [PATCH 629/662] #3655 sp_Blitz add 2025 dsc Adds preview_features database_scoped_configuration. Closes #3655. --- sp_Blitz.sql | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index 2d0f72f05..68fe4bca0 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -7829,7 +7829,8 @@ IF @ProductVersionMajor >= 10 (42, 'OPTIMIZED_SP_EXECUTESQL', '0', NULL, 267), (43, 'OPTIMIZED_HALLOWEEN_PROTECTION', '1', NULL, 267), (44, 'FULLTEXT_INDEX_VERSION', '2', NULL, 267), - (47, 'OPTIONAL_PARAMETER_OPTIMIZATION', '1', NULL, 267); + (47, 'OPTIONAL_PARAMETER_OPTIMIZATION', '1', NULL, 267), + (48, 'PREVIEW_FEATURES', '0', NULL, 267); EXEC dbo.sp_MSforeachdb 'USE [?]; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; INSERT INTO #BlitzResults (CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details) SELECT def1.CheckID, DB_NAME(), 210, ''Non-Default Database Scoped Config'', dsc.[name], ''https://www.brentozar.com/go/dbscope'', (''Set value: '' + COALESCE(CAST(dsc.value AS NVARCHAR(100)),''Empty'') + '' Default: '' + COALESCE(CAST(def1.default_value AS NVARCHAR(100)),''Empty'') + '' Set value for secondary: '' + COALESCE(CAST(dsc.value_for_secondary AS NVARCHAR(100)),''Empty'') + '' Default value for secondary: '' + COALESCE(CAST(def1.default_value_for_secondary AS NVARCHAR(100)),''Empty'')) From 572f71c97bf3dbbf8d90372b4439736e278bb815 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Thu, 3 Jul 2025 05:44:01 -0700 Subject: [PATCH 630/662] #3657 sp_Blitz 2025 configurations And switched to a table value constructor for shorter code. Closes #3657. --- Documentation/sp_Blitz_Checks_by_Priority.md | 5 +- sp_Blitz.sql | 304 ++++++++----------- 2 files changed, 137 insertions(+), 172 deletions(-) diff --git a/Documentation/sp_Blitz_Checks_by_Priority.md b/Documentation/sp_Blitz_Checks_by_Priority.md index c803f7d43..c81a97708 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: 268. -If you want to add a new one, start at 269. +CURRENT HIGH CHECKID: 269. +If you want to add a new one, start at 270. | Priority | FindingsGroup | Finding | URL | CheckID | |----------|-----------------------------|---------------------------------------------------------|------------------------------------------------------------------------|----------| @@ -242,6 +242,7 @@ If you want to add a new one, start at 269. | 200 | Non-Default Server Config | user options | https://www.BrentOzar.com/go/conf | 1063 | | 200 | Non-Default Server Config | Web Assistant Procedures | https://www.BrentOzar.com/go/conf | 1064 | | 200 | Non-Default Server Config | xp_cmdshell | https://www.BrentOzar.com/go/conf | 1065 | +| 200 | Non-Default Server Config | Configuration Changed | https://www.BrentOzar.com/go/conf | 269 | | 200 | Performance | Buffer Pool Extensions Enabled | https://www.BrentOzar.com/go/bpe | 174 | | 200 | Performance | Default Parallelism Settings | https://www.BrentOzar.com/go/cxpacket | 188 | | 200 | Performance | In-Memory OLTP (Hekaton) In Use | https://www.BrentOzar.com/go/hekaton | 146 | diff --git a/sp_Blitz.sql b/sp_Blitz.sql index b2433cf1f..58c97a514 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -2320,177 +2320,141 @@ AS IF @Debug IN (1, 2) RAISERROR('Generating default configuration values', 0, 1) WITH NOWAIT; - INSERT INTO #ConfigurationDefaults - VALUES ( 'access check cache bucket count', 0, 1001 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'access check cache quota', 0, 1002 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'Ad Hoc Distributed Queries', 0, 1003 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'affinity I/O mask', 0, 1004 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'affinity mask', 0, 1005 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'affinity64 mask', 0, 1066 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'affinity64 I/O mask', 0, 1067 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'Agent XPs', 0, 1071 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'allow updates', 0, 1007 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'awe enabled', 0, 1008 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'backup checksum default', 0, 1070 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'backup compression default', 0, 1073 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'blocked process threshold', 0, 1009 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'blocked process threshold (s)', 0, 1009 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'c2 audit mode', 0, 1010 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'clr enabled', 0, 1011 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'common criteria compliance enabled', 0, 1074 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'contained database authentication', 0, 1068 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'cost threshold for parallelism', 5, 1012 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'cross db ownership chaining', 0, 1013 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'cursor threshold', -1, 1014 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'Database Mail XPs', 0, 1072 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'default full-text language', 1033, 1016 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'default language', 0, 1017 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'default trace enabled', 1, 1018 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'disallow results from triggers', 0, 1019 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'EKM provider enabled', 0, 1075 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'filestream access level', 0, 1076 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'fill factor (%)', 0, 1020 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'ft crawl bandwidth (max)', 100, 1021 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'ft crawl bandwidth (min)', 0, 1022 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'ft notify bandwidth (max)', 100, 1023 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'ft notify bandwidth (min)', 0, 1024 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'index create memory (KB)', 0, 1025 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'in-doubt xact resolution', 0, 1026 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'lightweight pooling', 0, 1027 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'locks', 0, 1028 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'max degree of parallelism', 0, 1029 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'max full-text crawl range', 4, 1030 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'max server memory (MB)', 2147483647, 1031 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'max text repl size (B)', 65536, 1032 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'max worker threads', 0, 1033 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'media retention', 0, 1034 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'min memory per query (KB)', 1024, 1035 ); - /* Accepting both 0 and 16 below because both have been seen in the wild as defaults. */ - IF EXISTS ( SELECT * - FROM sys.configurations - WHERE name = 'min server memory (MB)' - AND value_in_use IN ( 0, 16 ) ) - INSERT INTO #ConfigurationDefaults - SELECT 'min server memory (MB)' , - CAST(value_in_use AS BIGINT), 1036 - FROM sys.configurations - WHERE name = 'min server memory (MB)'; - ELSE - INSERT INTO #ConfigurationDefaults - VALUES ( 'min server memory (MB)', 0, 1036 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'nested triggers', 1, 1037 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'network packet size (B)', 4096, 1038 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'Ole Automation Procedures', 0, 1039 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'open objects', 0, 1040 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'optimize for ad hoc workloads', 0, 1041 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'PH timeout (s)', 60, 1042 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'precompute rank', 0, 1043 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'priority boost', 0, 1044 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'query governor cost limit', 0, 1045 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'query wait (s)', -1, 1046 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'recovery interval (min)', 0, 1047 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'remote access', 1, 1048 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'remote admin connections', 0, 1049 ); - /* SQL Server 2012 changes a configuration default */ - IF @@VERSION LIKE '%Microsoft SQL Server 2005%' - OR @@VERSION LIKE '%Microsoft SQL Server 2008%' - BEGIN - INSERT INTO #ConfigurationDefaults - VALUES ( 'remote login timeout (s)', 20, 1069 ); - END; + INSERT INTO #ConfigurationDefaults + VALUES + ( 'access check cache bucket count', 0, 1001 ), + ( 'access check cache quota', 0, 1002 ), + ( 'Ad Hoc Distributed Queries', 0, 1003 ), + ( 'affinity I/O mask', 0, 1004 ), + ( 'affinity mask', 0, 1005 ), + ( 'affinity64 mask', 0, 1066 ), + ( 'affinity64 I/O mask', 0, 1067 ), + ( 'Agent XPs', 0, 1071 ), + ( 'allow updates', 0, 1007 ), + ( 'awe enabled', 0, 1008 ), + ( 'backup checksum default', 0, 1070 ), + ( 'backup compression default', 0, 1073 ), + ( 'blocked process threshold', 0, 1009 ), + ( 'blocked process threshold (s)', 0, 1009 ), + ( 'c2 audit mode', 0, 1010 ), + ( 'clr enabled', 0, 1011 ), + ( 'common criteria compliance enabled', 0, 1074 ), + ( 'contained database authentication', 0, 1068 ), + ( 'cost threshold for parallelism', 5, 1012 ), + ( 'cross db ownership chaining', 0, 1013 ), + ( 'cursor threshold', -1, 1014 ), + ( 'Database Mail XPs', 0, 1072 ), + ( 'default full-text language', 1033, 1016 ), + ( 'default language', 0, 1017 ), + ( 'default trace enabled', 1, 1018 ), + ( 'disallow results from triggers', 0, 1019 ), + ( 'EKM provider enabled', 0, 1075 ), + ( 'filestream access level', 0, 1076 ), + ( 'fill factor (%)', 0, 1020 ), + ( 'ft crawl bandwidth (max)', 100, 1021 ), + ( 'ft crawl bandwidth (min)', 0, 1022 ), + ( 'ft notify bandwidth (max)', 100, 1023 ), + ( 'ft notify bandwidth (min)', 0, 1024 ), + ( 'index create memory (KB)', 0, 1025 ), + ( 'in-doubt xact resolution', 0, 1026 ), + ( 'lightweight pooling', 0, 1027 ), + ( 'locks', 0, 1028 ), + ( 'max degree of parallelism', 0, 1029 ), + ( 'max full-text crawl range', 4, 1030 ), + ( 'max server memory (MB)', 2147483647, 1031 ), + ( 'max text repl size (B)', 65536, 1032 ), + ( 'max worker threads', 0, 1033 ), + ( 'media retention', 0, 1034 ), + ( 'min memory per query (KB)', 1024, 1035 ), + ( 'nested triggers', 1, 1037 ), + ( 'network packet size (B)', 4096, 1038 ), + ( 'Ole Automation Procedures', 0, 1039 ), + ( 'open objects', 0, 1040 ), + ( 'optimize for ad hoc workloads', 0, 1041 ), + ( 'PH timeout (s)', 60, 1042 ), + ( 'precompute rank', 0, 1043 ), + ( 'priority boost', 0, 1044 ), + ( 'query governor cost limit', 0, 1045 ), + ( 'query wait (s)', -1, 1046 ), + ( 'recovery interval (min)', 0, 1047 ), + ( 'remote access', 1, 1048 ), + ( 'remote admin connections', 0, 1049 ), + ( 'remote login timeout (s)', CASE + WHEN @@VERSION LIKE '%Microsoft SQL Server 2005%' + OR @@VERSION LIKE '%Microsoft SQL Server 2008%' THEN 20 + ELSE 10 + END, 1069 ), + ( 'remote proc trans', 0, 1050 ), + ( 'remote query timeout (s)', 600, 1051 ), + ( 'Replication XPs', 0, 1052 ), + ( 'RPC parameter data validation', 0, 1053 ), + ( 'scan for startup procs', 0, 1054 ), + ( 'server trigger recursion', 1, 1055 ), + ( 'set working set size', 0, 1056 ), + ( 'show advanced options', 0, 1057 ), + ( 'SMO and DMO XPs', 1, 1058 ), + ( 'SQL Mail XPs', 0, 1059 ), + ( 'transform noise words', 0, 1060 ), + ( 'two digit year cutoff', 2049, 1061 ), + ( 'user connections', 0, 1062 ), + ( 'user options', 0, 1063 ), + ( 'Web Assistant Procedures', 0, 1064 ), + ( 'xp_cmdshell', 0, 1065 ), + ( 'automatic soft-NUMA disabled', 0, 269), + ( 'external scripts enabled', 0, 269), + ( 'clr strict security', 1, 269), + ( 'column encryption enclave type', 0, 269), + ( 'tempdb metadata memory-optimized', 0, 269), + ( 'ADR cleaner retry timeout (min)', 15, 269), + ( 'ADR Preallocation Factor', 4, 269), + ( 'version high part of SQL Server', 1114112, 269), + ( 'version low part of SQL Server', 52428803, 269), + ( 'Data processed daily limit in TB', 2147483647, 269), + ( 'Data processed weekly limit in TB', 2147483647, 269), + ( 'Data processed monthly limit in TB', 2147483647, 269), + ( 'ADR Cleaner Thread Count', 1, 269), + ( 'hardware offload enabled', 0, 269), + ( 'hardware offload config', 0, 269), + ( 'hardware offload mode', 0, 269), + ( 'backup compression algorithm', 0, 269), + ( 'ADR cleaner lock timeout (s)', 5, 269), + ( 'SLOG memory quota (%)', 75, 269), + ( 'max RPC request params (KB)', 0, 269), + ( 'max UCS send boxcars', 256, 269), + ( 'availability group commit time (ms)', 0, 269), + ( 'tiered memory enabled', 0, 269), + ( 'max server tiered memory (MB)', 2147483647, 269), + ( 'hadoop connectivity', 0, 269), + ( 'polybase network encryption', 1, 269), + ( 'remote data archive', 0, 269), + ( 'allow polybase export', 0, 269), + ( 'allow filesystem enumeration', 1, 269), + ( 'polybase enabled', 0, 269), + ( 'suppress recovery model errors', 0, 269), + ( 'openrowset auto_create_statistics', 1, 269), + ( 'external rest endpoint enabled', 0, 269), + ( 'external xtp dll gen util enabled', 0, 269), + ( 'external AI runtimes enabled', 0, 269), + ( 'allow server scoped db credentials', 0, 269); + + /* Either 0 or 16 is fine here */ + IF EXISTS ( + SELECT * FROM sys.configurations + WHERE name = 'min server memory (MB)' + AND value_in_use IN (0, 16) + ) + BEGIN + INSERT INTO #ConfigurationDefaults + SELECT 'min server memory (MB)', CAST(value_in_use AS BIGINT), 1036 + FROM sys.configurations + WHERE name = 'min server memory (MB)'; + END ELSE - BEGIN - INSERT INTO #ConfigurationDefaults - VALUES ( 'remote login timeout (s)', 10, 1069 ); - END; - INSERT INTO #ConfigurationDefaults - VALUES ( 'remote proc trans', 0, 1050 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'remote query timeout (s)', 600, 1051 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'Replication XPs', 0, 1052 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'RPC parameter data validation', 0, 1053 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'scan for startup procs', 0, 1054 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'server trigger recursion', 1, 1055 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'set working set size', 0, 1056 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'show advanced options', 0, 1057 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'SMO and DMO XPs', 1, 1058 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'SQL Mail XPs', 0, 1059 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'transform noise words', 0, 1060 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'two digit year cutoff', 2049, 1061 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'user connections', 0, 1062 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'user options', 0, 1063 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'Web Assistant Procedures', 0, 1064 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'xp_cmdshell', 0, 1065 ); + BEGIN + INSERT INTO #ConfigurationDefaults + VALUES ('min server memory (MB)', 0, 1036); + END; + IF NOT EXISTS ( SELECT 1 FROM #SkipChecks From 628c147614db124fa337ba851396e55cd0d06968 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Fri, 4 Jul 2025 06:38:33 -0700 Subject: [PATCH 631/662] #3660 sp_BlitzFirst thread time Adds units of measure, updates release dates and versions for 2025-07-04 release. Closes #3660. --- Install-All-Scripts.sql | 533 ++++++++++++++++++++++++---------------- Install-Azure.sql | 140 ++++++++--- SqlServerVersions.sql | 4 + sp_Blitz.sql | 2 +- sp_BlitzAnalysis.sql | 2 +- sp_BlitzBackups.sql | 2 +- sp_BlitzCache.sql | 2 +- sp_BlitzFirst.sql | 45 +++- sp_BlitzIndex.sql | 2 +- sp_BlitzLock.sql | 2 +- sp_BlitzWho.sql | 2 +- sp_DatabaseRestore.sql | 2 +- sp_ineachdb.sql | 2 +- 13 files changed, 479 insertions(+), 261 deletions(-) diff --git a/Install-All-Scripts.sql b/Install-All-Scripts.sql index 83c596c58..5f310717a 100644 --- a/Install-All-Scripts.sql +++ b/Install-All-Scripts.sql @@ -38,7 +38,7 @@ AS SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.24', @VersionDate = '20250407'; + SELECT @Version = '8.25', @VersionDate = '20250704'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -1862,7 +1862,7 @@ AS 'Security' AS FindingsGroup , 'Invalid login defined with Windows Authentication' AS Finding , 'https://docs.microsoft.com/en-us/sql/relational-databases/system-stored-procedures/sp-validatelogins-transact-sql' AS URL , - ( 'Windows user or group ' + QUOTENAME(LoginName) + ' is mapped to a SQL Server principal but no longer exists in the Windows environment.') AS Details + ( 'Windows user or group ' + QUOTENAME(LoginName) + ' is mapped to a SQL Server principal but no longer exists in the Windows environment. Sometimes empty AD groups can show up here so check thoroughly.') AS Details FROM #InvalidLogins ; END; @@ -2320,177 +2320,141 @@ AS IF @Debug IN (1, 2) RAISERROR('Generating default configuration values', 0, 1) WITH NOWAIT; - INSERT INTO #ConfigurationDefaults - VALUES ( 'access check cache bucket count', 0, 1001 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'access check cache quota', 0, 1002 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'Ad Hoc Distributed Queries', 0, 1003 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'affinity I/O mask', 0, 1004 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'affinity mask', 0, 1005 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'affinity64 mask', 0, 1066 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'affinity64 I/O mask', 0, 1067 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'Agent XPs', 0, 1071 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'allow updates', 0, 1007 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'awe enabled', 0, 1008 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'backup checksum default', 0, 1070 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'backup compression default', 0, 1073 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'blocked process threshold', 0, 1009 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'blocked process threshold (s)', 0, 1009 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'c2 audit mode', 0, 1010 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'clr enabled', 0, 1011 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'common criteria compliance enabled', 0, 1074 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'contained database authentication', 0, 1068 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'cost threshold for parallelism', 5, 1012 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'cross db ownership chaining', 0, 1013 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'cursor threshold', -1, 1014 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'Database Mail XPs', 0, 1072 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'default full-text language', 1033, 1016 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'default language', 0, 1017 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'default trace enabled', 1, 1018 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'disallow results from triggers', 0, 1019 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'EKM provider enabled', 0, 1075 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'filestream access level', 0, 1076 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'fill factor (%)', 0, 1020 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'ft crawl bandwidth (max)', 100, 1021 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'ft crawl bandwidth (min)', 0, 1022 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'ft notify bandwidth (max)', 100, 1023 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'ft notify bandwidth (min)', 0, 1024 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'index create memory (KB)', 0, 1025 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'in-doubt xact resolution', 0, 1026 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'lightweight pooling', 0, 1027 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'locks', 0, 1028 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'max degree of parallelism', 0, 1029 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'max full-text crawl range', 4, 1030 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'max server memory (MB)', 2147483647, 1031 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'max text repl size (B)', 65536, 1032 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'max worker threads', 0, 1033 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'media retention', 0, 1034 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'min memory per query (KB)', 1024, 1035 ); - /* Accepting both 0 and 16 below because both have been seen in the wild as defaults. */ - IF EXISTS ( SELECT * - FROM sys.configurations - WHERE name = 'min server memory (MB)' - AND value_in_use IN ( 0, 16 ) ) - INSERT INTO #ConfigurationDefaults - SELECT 'min server memory (MB)' , - CAST(value_in_use AS BIGINT), 1036 - FROM sys.configurations - WHERE name = 'min server memory (MB)'; - ELSE - INSERT INTO #ConfigurationDefaults - VALUES ( 'min server memory (MB)', 0, 1036 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'nested triggers', 1, 1037 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'network packet size (B)', 4096, 1038 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'Ole Automation Procedures', 0, 1039 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'open objects', 0, 1040 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'optimize for ad hoc workloads', 0, 1041 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'PH timeout (s)', 60, 1042 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'precompute rank', 0, 1043 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'priority boost', 0, 1044 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'query governor cost limit', 0, 1045 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'query wait (s)', -1, 1046 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'recovery interval (min)', 0, 1047 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'remote access', 1, 1048 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'remote admin connections', 0, 1049 ); - /* SQL Server 2012 changes a configuration default */ - IF @@VERSION LIKE '%Microsoft SQL Server 2005%' - OR @@VERSION LIKE '%Microsoft SQL Server 2008%' - BEGIN - INSERT INTO #ConfigurationDefaults - VALUES ( 'remote login timeout (s)', 20, 1069 ); - END; + INSERT INTO #ConfigurationDefaults + VALUES + ( 'access check cache bucket count', 0, 1001 ), + ( 'access check cache quota', 0, 1002 ), + ( 'Ad Hoc Distributed Queries', 0, 1003 ), + ( 'affinity I/O mask', 0, 1004 ), + ( 'affinity mask', 0, 1005 ), + ( 'affinity64 mask', 0, 1066 ), + ( 'affinity64 I/O mask', 0, 1067 ), + ( 'Agent XPs', 0, 1071 ), + ( 'allow updates', 0, 1007 ), + ( 'awe enabled', 0, 1008 ), + ( 'backup checksum default', 0, 1070 ), + ( 'backup compression default', 0, 1073 ), + ( 'blocked process threshold', 0, 1009 ), + ( 'blocked process threshold (s)', 0, 1009 ), + ( 'c2 audit mode', 0, 1010 ), + ( 'clr enabled', 0, 1011 ), + ( 'common criteria compliance enabled', 0, 1074 ), + ( 'contained database authentication', 0, 1068 ), + ( 'cost threshold for parallelism', 5, 1012 ), + ( 'cross db ownership chaining', 0, 1013 ), + ( 'cursor threshold', -1, 1014 ), + ( 'Database Mail XPs', 0, 1072 ), + ( 'default full-text language', 1033, 1016 ), + ( 'default language', 0, 1017 ), + ( 'default trace enabled', 1, 1018 ), + ( 'disallow results from triggers', 0, 1019 ), + ( 'EKM provider enabled', 0, 1075 ), + ( 'filestream access level', 0, 1076 ), + ( 'fill factor (%)', 0, 1020 ), + ( 'ft crawl bandwidth (max)', 100, 1021 ), + ( 'ft crawl bandwidth (min)', 0, 1022 ), + ( 'ft notify bandwidth (max)', 100, 1023 ), + ( 'ft notify bandwidth (min)', 0, 1024 ), + ( 'index create memory (KB)', 0, 1025 ), + ( 'in-doubt xact resolution', 0, 1026 ), + ( 'lightweight pooling', 0, 1027 ), + ( 'locks', 0, 1028 ), + ( 'max degree of parallelism', 0, 1029 ), + ( 'max full-text crawl range', 4, 1030 ), + ( 'max server memory (MB)', 2147483647, 1031 ), + ( 'max text repl size (B)', 65536, 1032 ), + ( 'max worker threads', 0, 1033 ), + ( 'media retention', 0, 1034 ), + ( 'min memory per query (KB)', 1024, 1035 ), + ( 'nested triggers', 1, 1037 ), + ( 'network packet size (B)', 4096, 1038 ), + ( 'Ole Automation Procedures', 0, 1039 ), + ( 'open objects', 0, 1040 ), + ( 'optimize for ad hoc workloads', 0, 1041 ), + ( 'PH timeout (s)', 60, 1042 ), + ( 'precompute rank', 0, 1043 ), + ( 'priority boost', 0, 1044 ), + ( 'query governor cost limit', 0, 1045 ), + ( 'query wait (s)', -1, 1046 ), + ( 'recovery interval (min)', 0, 1047 ), + ( 'remote access', 1, 1048 ), + ( 'remote admin connections', 0, 1049 ), + ( 'remote login timeout (s)', CASE + WHEN @@VERSION LIKE '%Microsoft SQL Server 2005%' + OR @@VERSION LIKE '%Microsoft SQL Server 2008%' THEN 20 + ELSE 10 + END, 1069 ), + ( 'remote proc trans', 0, 1050 ), + ( 'remote query timeout (s)', 600, 1051 ), + ( 'Replication XPs', 0, 1052 ), + ( 'RPC parameter data validation', 0, 1053 ), + ( 'scan for startup procs', 0, 1054 ), + ( 'server trigger recursion', 1, 1055 ), + ( 'set working set size', 0, 1056 ), + ( 'show advanced options', 0, 1057 ), + ( 'SMO and DMO XPs', 1, 1058 ), + ( 'SQL Mail XPs', 0, 1059 ), + ( 'transform noise words', 0, 1060 ), + ( 'two digit year cutoff', 2049, 1061 ), + ( 'user connections', 0, 1062 ), + ( 'user options', 0, 1063 ), + ( 'Web Assistant Procedures', 0, 1064 ), + ( 'xp_cmdshell', 0, 1065 ), + ( 'automatic soft-NUMA disabled', 0, 269), + ( 'external scripts enabled', 0, 269), + ( 'clr strict security', 1, 269), + ( 'column encryption enclave type', 0, 269), + ( 'tempdb metadata memory-optimized', 0, 269), + ( 'ADR cleaner retry timeout (min)', 15, 269), + ( 'ADR Preallocation Factor', 4, 269), + ( 'version high part of SQL Server', 1114112, 269), + ( 'version low part of SQL Server', 52428803, 269), + ( 'Data processed daily limit in TB', 2147483647, 269), + ( 'Data processed weekly limit in TB', 2147483647, 269), + ( 'Data processed monthly limit in TB', 2147483647, 269), + ( 'ADR Cleaner Thread Count', 1, 269), + ( 'hardware offload enabled', 0, 269), + ( 'hardware offload config', 0, 269), + ( 'hardware offload mode', 0, 269), + ( 'backup compression algorithm', 0, 269), + ( 'ADR cleaner lock timeout (s)', 5, 269), + ( 'SLOG memory quota (%)', 75, 269), + ( 'max RPC request params (KB)', 0, 269), + ( 'max UCS send boxcars', 256, 269), + ( 'availability group commit time (ms)', 0, 269), + ( 'tiered memory enabled', 0, 269), + ( 'max server tiered memory (MB)', 2147483647, 269), + ( 'hadoop connectivity', 0, 269), + ( 'polybase network encryption', 1, 269), + ( 'remote data archive', 0, 269), + ( 'allow polybase export', 0, 269), + ( 'allow filesystem enumeration', 1, 269), + ( 'polybase enabled', 0, 269), + ( 'suppress recovery model errors', 0, 269), + ( 'openrowset auto_create_statistics', 1, 269), + ( 'external rest endpoint enabled', 0, 269), + ( 'external xtp dll gen util enabled', 0, 269), + ( 'external AI runtimes enabled', 0, 269), + ( 'allow server scoped db credentials', 0, 269); + + /* Either 0 or 16 is fine here */ + IF EXISTS ( + SELECT * FROM sys.configurations + WHERE name = 'min server memory (MB)' + AND value_in_use IN (0, 16) + ) + BEGIN + INSERT INTO #ConfigurationDefaults + SELECT 'min server memory (MB)', CAST(value_in_use AS BIGINT), 1036 + FROM sys.configurations + WHERE name = 'min server memory (MB)'; + END ELSE - BEGIN - INSERT INTO #ConfigurationDefaults - VALUES ( 'remote login timeout (s)', 10, 1069 ); - END; - INSERT INTO #ConfigurationDefaults - VALUES ( 'remote proc trans', 0, 1050 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'remote query timeout (s)', 600, 1051 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'Replication XPs', 0, 1052 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'RPC parameter data validation', 0, 1053 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'scan for startup procs', 0, 1054 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'server trigger recursion', 1, 1055 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'set working set size', 0, 1056 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'show advanced options', 0, 1057 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'SMO and DMO XPs', 1, 1058 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'SQL Mail XPs', 0, 1059 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'transform noise words', 0, 1060 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'two digit year cutoff', 2049, 1061 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'user connections', 0, 1062 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'user options', 0, 1063 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'Web Assistant Procedures', 0, 1064 ); - INSERT INTO #ConfigurationDefaults - VALUES ( 'xp_cmdshell', 0, 1065 ); + BEGIN + INSERT INTO #ConfigurationDefaults + VALUES ('min server memory (MB)', 0, 1036); + END; + IF NOT EXISTS ( SELECT 1 FROM #SkipChecks @@ -6706,6 +6670,45 @@ IF @ProductVersionMajor >= 10 + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 268 ) + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 268) WITH NOWAIT; + + INSERT INTO #BlitzResults + ( CheckID , + Priority , + DatabaseName , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT 268 AS CheckID, + 5 AS Priority, + DB_NAME(ps.database_id), + 'Availability' AS FindingsGroup, + 'AG Replica Falling Behind' AS Finding, + 'https://www.BrentOzar.com/go/ag' AS URL, + ag.name + N' AG replica server ' + + ar.replica_server_name + N' is ' + + CASE WHEN DATEDIFF(SECOND, drs.last_commit_time, ps.last_commit_time) < 200 THEN (CAST(DATEDIFF(SECOND, drs.last_commit_time, ps.last_commit_time) AS NVARCHAR(10)) + N' seconds ') + ELSE (CAST(DATEDIFF(MINUTE, drs.last_commit_time, ps.last_commit_time) AS NVARCHAR(10)) + N' minutes ') END + + N' behind the primary.' + AS details + FROM sys.dm_hadr_database_replica_states AS drs + JOIN sys.availability_replicas AS ar ON drs.replica_id = ar.replica_id + JOIN sys.availability_groups AS ag ON ar.group_id = ag.group_id + JOIN sys.dm_hadr_database_replica_states AS ps + ON drs.group_id = ps.group_id + AND drs.database_id = ps.database_id + AND ps.is_local = 1 /* Primary */ + WHERE drs.is_local = 0 /* Secondary */ + AND DATEDIFF(SECOND, drs.last_commit_time, ps.last_commit_time) > 60; + END; + IF @CheckUserDatabaseObjects = 1 BEGIN @@ -7738,7 +7741,7 @@ IF @ProductVersionMajor >= 10 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 74) WITH NOWAIT; - EXEC dbo.sp_MSforeachdb 'USE [?]; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; IF EXISTS(SELECT * FROM sys.databases WHERE is_query_store_on = 1) INSERT INTO #TemporaryDatabaseResults (DatabaseName, Finding) VALUES (DB_NAME(), ''Yup'') OPTION (RECOMPILE);'; + EXEC dbo.sp_MSforeachdb 'USE [?]; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; IF EXISTS(SELECT * FROM sys.databases WHERE is_query_store_on = 1 AND database_id <> 3) INSERT INTO #TemporaryDatabaseResults (DatabaseName, Finding) VALUES (DB_NAME(), ''Yup'') OPTION (RECOMPILE);'; IF EXISTS (SELECT * FROM #TemporaryDatabaseResults) SET @QueryStoreInUse = 1; END; @@ -7782,9 +7785,16 @@ IF @ProductVersionMajor >= 10 (33, 'MEMORY_GRANT_FEEDBACK_PERSISTENCE', '1', NULL, 267), (34, 'MEMORY_GRANT_FEEDBACK_PERCENTILE_GRANT', '1', NULL, 267), (35, 'OPTIMIZED_PLAN_FORCING', '1', NULL, 267), - (37, 'DOP_FEEDBACK', '0', NULL, 267), + (37, 'DOP_FEEDBACK', CASE WHEN @ProductVersionMajor >= 17 THEN '1' ELSE '0' END, NULL, 267), (38, 'LEDGER_DIGEST_STORAGE_ENDPOINT', 'OFF', NULL, 267), - (39, 'FORCE_SHOWPLAN_RUNTIME_PARAMETER_COLLECTION', '0', NULL, 267); + (39, 'FORCE_SHOWPLAN_RUNTIME_PARAMETER_COLLECTION', '0', NULL, 267), + (40, 'READABLE_SECONDARY_TEMPORARY_STATS_AUTO_CREATE', '1', NULL, 267), + (41, 'READABLE_SECONDARY_TEMPORARY_STATS_AUTO_UPDATE', '1', NULL, 267), + (42, 'OPTIMIZED_SP_EXECUTESQL', '0', NULL, 267), + (43, 'OPTIMIZED_HALLOWEEN_PROTECTION', '1', NULL, 267), + (44, 'FULLTEXT_INDEX_VERSION', '2', NULL, 267), + (47, 'OPTIONAL_PARAMETER_OPTIMIZATION', '1', NULL, 267), + (48, 'PREVIEW_FEATURES', '0', NULL, 267); EXEC dbo.sp_MSforeachdb 'USE [?]; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; INSERT INTO #BlitzResults (CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details) SELECT def1.CheckID, DB_NAME(), 210, ''Non-Default Database Scoped Config'', dsc.[name], ''https://www.brentozar.com/go/dbscope'', (''Set value: '' + COALESCE(CAST(dsc.value AS NVARCHAR(100)),''Empty'') + '' Default: '' + COALESCE(CAST(def1.default_value AS NVARCHAR(100)),''Empty'') + '' Set value for secondary: '' + COALESCE(CAST(dsc.value_for_secondary AS NVARCHAR(100)),''Empty'') + '' Default value for secondary: '' + COALESCE(CAST(def1.default_value_for_secondary AS NVARCHAR(100)),''Empty'')) @@ -8558,8 +8568,7 @@ EXEC dbo.sp_MSforeachdb 'USE [?]; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITT 'Informational' AS FindingsGroup , 'Recommended Trace Flag Off' AS Finding , 'https://www.sqlskills.com/blogs/erin/query-store-trace-flags/' AS URL , - 'Trace Flag 7745 not enabled globally. It makes shutdowns/failovers quicker by not waiting for Query Store to flush to disk. It is recommended, but it loses you the non-flushed Query Store data.' AS Details - FROM #TraceStatus T + 'Trace Flag 7745 not enabled globally. It makes shutdowns/failovers quicker by not waiting for Query Store to flush to disk. It is recommended, but it loses you the non-flushed Query Store data.' AS Details; END; IF NOT EXISTS ( SELECT 1 @@ -10557,7 +10566,7 @@ AS SET NOCOUNT ON; SET STATISTICS XML OFF; -SELECT @Version = '8.24', @VersionDate = '20250407'; +SELECT @Version = '8.25', @VersionDate = '20250704'; IF(@VersionCheckMode = 1) BEGIN @@ -11435,7 +11444,7 @@ AS SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.24', @VersionDate = '20250407'; + SELECT @Version = '8.25', @VersionDate = '20250704'; IF(@VersionCheckMode = 1) BEGIN @@ -13192,6 +13201,7 @@ ALTER PROCEDURE dbo.sp_BlitzCache @DurationFilter DECIMAL(38,4) = NULL , @HideSummary BIT = 0 , @IgnoreSystemDBs BIT = 1 , + @IgnoreReadableReplicaDBs BIT = 1 , @OnlyQueryHashes VARCHAR(MAX) = NULL , @IgnoreQueryHashes VARCHAR(MAX) = NULL , @OnlySqlHandles VARCHAR(MAX) = NULL , @@ -13218,7 +13228,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.24', @VersionDate = '20250407'; +SELECT @Version = '8.25', @VersionDate = '20250704'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -14341,7 +14351,7 @@ CREATE TABLE #plan_usage ); -IF EXISTS (SELECT * FROM sys.all_objects o WHERE o.name = 'dm_hadr_database_replica_states') +IF @IgnoreReadableReplicaDBs = 1 AND EXISTS (SELECT * FROM sys.all_objects o WHERE o.name = 'dm_hadr_database_replica_states') BEGIN RAISERROR('Checking for Read intent databases to exclude',0,0) WITH NOWAIT; @@ -14760,7 +14770,7 @@ IF @VersionShowsAirQuoteActualPlans = 1 SET @body += N' WHERE 1 = 1 ' + @nl ; - IF EXISTS (SELECT * FROM sys.all_objects o WHERE o.name = 'dm_hadr_database_replica_states') + IF @IgnoreReadableReplicaDBs = 1 AND EXISTS (SELECT * FROM sys.all_objects o WHERE o.name = 'dm_hadr_database_replica_states') BEGIN RAISERROR(N'Ignoring readable secondaries databases by default', 0, 1) WITH NOWAIT; SET @body += N' AND CAST(xpa.value AS INT) NOT IN (SELECT database_id FROM #ReadableDBs)' + @nl ; @@ -20604,7 +20614,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.24', @VersionDate = '20250407'; +SELECT @Version = '8.25', @VersionDate = '20250704'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -27394,7 +27404,7 @@ BEGIN SET XACT_ABORT OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.24', @VersionDate = '20250407'; + SELECT @Version = '8.25', @VersionDate = '20250704'; IF @VersionCheckMode = 1 BEGIN @@ -28607,7 +28617,7 @@ BEGIN END; /* If table target */ - IF @TargetSessionType = 'table' + IF LOWER(@TargetSessionType) = N'table' BEGIN SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Inserting to #deadlock_data from table source %s', 0, 1, @d) WITH NOWAIT; @@ -28625,9 +28635,19 @@ BEGIN SELECT TOP (1) @xe = xe.e.exist(''.''), @xd = xd.e.exist(''.'') - FROM [master].[dbo].[bpr] AS x - OUTER APPLY x.[bpr].nodes(''/event'') AS xe(e) - OUTER APPLY x.[bpr].nodes(''/deadlock'') AS xd(e) + FROM ' + + QUOTENAME(@TargetDatabaseName) + + N'.' + + QUOTENAME(@TargetSchemaName) + + N'.' + + QUOTENAME(@TargetTableName) + + N' AS x + OUTER APPLY x.' + + QUOTENAME(@TargetColumnName) + + N'.nodes(''/event'') AS xe(e) + OUTER APPLY x.' + + QUOTENAME(@TargetColumnName) + + N'.nodes(''/deadlock'') AS xd(e) OPTION(RECOMPILE); '; @@ -28764,6 +28784,7 @@ BEGIN LEFT JOIN #t AS t ON 1 = 1 WHERE @xe = 1 + OR LOWER(@TargetSessionType) <> N'table' UNION ALL @@ -31927,7 +31948,7 @@ BEGIN SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.24', @VersionDate = '20250407'; + SELECT @Version = '8.25', @VersionDate = '20250704'; IF(@VersionCheckMode = 1) BEGIN @@ -33340,7 +33361,7 @@ SET STATISTICS XML OFF; /*Versioning details*/ -SELECT @Version = '8.24', @VersionDate = '20250407'; +SELECT @Version = '8.25', @VersionDate = '20250704'; IF(@VersionCheckMode = 1) BEGIN @@ -35002,14 +35023,15 @@ ALTER PROCEDURE [dbo].[sp_ineachdb] @Version varchar(30) = NULL OUTPUT, @VersionDate datetime = NULL OUTPUT, @VersionCheckMode bit = 0, - @is_ag_writeable_copy bit = 0 + @is_ag_writeable_copy bit = 0, + @is_query_store_on bit = 0 -- 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.24', @VersionDate = '20250407'; + SELECT @Version = '8.25', @VersionDate = '20250704'; IF(@VersionCheckMode = 1) BEGIN @@ -35208,6 +35230,23 @@ OPTION (MAXRECURSION 0); ) ); + -- delete any databases that don't match query store criteria + IF @SQLVersion >= 13 + BEGIN + DELETE dbs FROM #ineachdb AS dbs + WHERE EXISTS + ( + SELECT 1 + FROM sys.databases AS d + WHERE d.database_id = dbs.id + AND NOT + ( + is_query_store_on = COALESCE(@is_query_store_on, is_query_store_on) + AND NOT (@is_query_store_on = 1 AND d.database_id = 3) OR (@is_query_store_on = 0 AND d.database_id = 3) -- Excluding the model database which shows QS enabled in SQL2022+ + ) + ); + END + -- if a user access is specified, remove any that are NOT in that state IF @user_access IN (N'SINGLE_USER', N'MULTI_USER', N'RESTRICTED_USER') BEGIN @@ -35372,6 +35411,10 @@ INSERT INTO dbo.SqlServerVersions (MajorVersionNumber, MinorVersionNumber, Branch, [Url], ReleaseDate, MainstreamSupportEndDate, ExtendedSupportEndDate, MajorVersionName, MinorVersionName) VALUES /*2022*/ + (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, 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'), (16, 4165, 'CU16', 'https://support.microsoft.com/en-us/help/5048033', '2024-11-14', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 16'), @@ -35855,7 +35898,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.24', @VersionDate = '20250407'; +SELECT @Version = '8.25', @VersionDate = '20250704'; IF(@VersionCheckMode = 1) BEGIN @@ -37446,7 +37489,7 @@ BEGIN 'Maintenance Tasks Running' AS FindingGroup, 'Restore Running' AS Finding, 'https://www.brentozar.com/askbrent/backups/' AS URL, - 'Restore of ' + DB_NAME(db.resource_database_id) + ' database (' + (SELECT CAST(CAST(SUM(size * 8.0 / 1024 / 1024) AS BIGINT) AS NVARCHAR) FROM #MasterFiles WHERE database_id = db.resource_database_id) + '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), '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, @@ -37454,14 +37497,14 @@ BEGIN s.nt_user_name AS NTUserName, s.[program_name] AS ProgramName, s.[host_name] AS HostName, - db.[resource_database_id] AS DatabaseID, - DB_NAME(db.resource_database_id) AS DatabaseName, + COALESCE(db.[resource_database_id],0) AS DatabaseID, + COALESCE(DB_NAME(db.resource_database_id), 'Unknown') AS DatabaseName, 0 AS OpenTransactionCount, r.query_hash FROM sys.dm_exec_requests r INNER JOIN sys.dm_exec_connections c ON r.session_id = c.session_id INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id - INNER JOIN ( + LEFT OUTER JOIN ( SELECT DISTINCT request_session_id, resource_database_id FROM sys.dm_tran_locks WHERE resource_type = N'DATABASE' @@ -37829,6 +37872,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, FROM sys.databases WHERE database_id > 4; + /* Server Info - Memory Grants pending - CheckID 39 */ IF (@Debug = 1) BEGIN @@ -38888,6 +38932,28 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, END; + /* Query Problems - Deadlocks - CheckID 51 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 51',10,1) WITH NOWAIT; + END + + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) + SELECT 51 AS CheckID, + 100 AS Priority, + 'Query Problems' AS FindingGroup, + 'Deadlocks' AS Finding, + ' https://www.brentozar.com/go/deadlocks' AS URL, + 'Number of deadlocks during the sample: ' + CAST(ps.value_delta AS NVARCHAR(20)) + @LineFeed + + 'Determined by sampling Perfmon counter ' + ps.object_name + ' - ' + ps.counter_name + @LineFeed AS Details, + 'Check sp_BlitzLock to find which indexes and queries to tune.' AS HowToStopIt + FROM #PerfmonStats ps + WHERE ps.Pass = 2 + AND counter_name = 'Number of Deadlocks/sec' + AND instance_name LIKE '_Total%' + AND value_delta > 0; + + /* SQL Server Internal Maintenance - Log File Growing - CheckID 13 */ IF (@Debug = 1) BEGIN @@ -39133,6 +39199,53 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, OR max_session_percent >= 90); END + /* Server Info - Thread Time - CheckID 50 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 50',10,1) WITH NOWAIT; + END + + ;WITH max_batch AS ( + SELECT MAX(SampleTime) AS SampleTime + FROM #WaitStats + ) + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) + SELECT TOP 1 + 50 AS CheckID, + 251 AS Priority, + 'Server Info' AS FindingGroup, + 'Thread Time' AS Finding, + LTRIM( + CASE + WHEN c.[TotalThreadTimeSeconds] >= 86400 THEN + CAST(c.[TotalThreadTimeSeconds] / 86400 AS VARCHAR) + 'd ' + ELSE '' + END + + CASE + WHEN c.[TotalThreadTimeSeconds] % 86400 >= 3600 THEN + CAST((c.[TotalThreadTimeSeconds] % 86400) / 3600 AS VARCHAR) + 'h ' + ELSE '' + END + + CASE + WHEN c.[TotalThreadTimeSeconds] % 3600 >= 60 THEN + CAST((c.[TotalThreadTimeSeconds] % 3600) / 60 AS VARCHAR) + 'm ' + ELSE '' + END + + CASE + WHEN c.[TotalThreadTimeSeconds] % 60 > 0 OR c.[TotalThreadTimeSeconds] = 0 THEN + CAST(c.[TotalThreadTimeSeconds] % 60 AS VARCHAR) + 's' + ELSE '' + END + ) AS Details, + CAST(c.[TotalThreadTimeSeconds] AS DECIMAL(18,1)) AS DetailsInt, + 'https://www.brentozar.com/go/threadtime' AS URL + FROM max_batch b + JOIN #WaitStats wd2 ON wd2.SampleTime = b.SampleTime + JOIN #WaitStats wd1 ON wd1.wait_type = wd2.wait_type AND wd2.SampleTime > wd1.SampleTime + CROSS APPLY ( + SELECT CAST((wd2.thread_time_ms - wd1.thread_time_ms) / 1000 AS INT) AS TotalThreadTimeSeconds + ) AS c; + /* Server Info - Batch Requests per Sec - CheckID 19 */ IF (@Debug = 1) BEGIN @@ -40462,13 +40575,13 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, wd1.wait_type, COALESCE(wcat.WaitCategory, 'Other') AS wait_category, CAST(c.[Wait Time (Seconds)] / 60. / 60. AS DECIMAL(18,1)) AS [Wait Time (Hours)], - CAST((wd2.wait_time_ms - wd1.wait_time_ms) / 1000.0 / cores.cpu_count / DATEDIFF(ss, wd1.SampleTime, wd2.SampleTime) AS DECIMAL(18,1)) AS [Per Core Per Hour], - (wd2.waiting_tasks_count - wd1.waiting_tasks_count) AS [Number of Waits], CASE WHEN (wd2.waiting_tasks_count - wd1.waiting_tasks_count) > 0 THEN CAST((wd2.wait_time_ms-wd1.wait_time_ms)/ (1.0*(wd2.waiting_tasks_count - wd1.waiting_tasks_count)) AS NUMERIC(12,1)) - ELSE 0 END AS [Avg ms Per Wait] + ELSE 0 END AS [Avg ms Per Wait], + CAST((wd2.wait_time_ms - wd1.wait_time_ms) / 1000.0 / cores.cpu_count / DATEDIFF(ss, wd1.SampleTime, wd2.SampleTime) AS DECIMAL(18,1)) AS [Per Core Per Hour], + (wd2.waiting_tasks_count - wd1.waiting_tasks_count) AS [Number of Waits] FROM max_batch b JOIN #WaitStats wd2 ON wd2.SampleTime =b.SampleTime @@ -40607,17 +40720,17 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, wd1.wait_type, COALESCE(wcat.WaitCategory, 'Other') AS wait_category, CAST(c.[Wait Time (Seconds)] / 60. / 60. AS DECIMAL(18,1)) AS [Wait Time (Hours)], + CASE WHEN (wd2.waiting_tasks_count - wd1.waiting_tasks_count) > 0 + THEN + CAST((wd2.wait_time_ms-wd1.wait_time_ms)/ + (1.0*(wd2.waiting_tasks_count - wd1.waiting_tasks_count)) AS NUMERIC(12,1)) + ELSE 0 END AS [Avg ms Per Wait], CAST((wd2.wait_time_ms - wd1.wait_time_ms) / 1000.0 / cores.cpu_count / DATEDIFF(ss, wd1.SampleTime, wd2.SampleTime) AS DECIMAL(18,1)) AS [Per Core Per Hour], CAST(c.[Signal Wait Time (Seconds)] / 60.0 / 60 AS DECIMAL(18,1)) AS [Signal Wait Time (Hours)], CASE WHEN c.[Wait Time (Seconds)] > 0 THEN CAST(100.*(c.[Signal Wait Time (Seconds)]/c.[Wait Time (Seconds)]) AS NUMERIC(4,1)) ELSE 0 END AS [Percent Signal Waits], (wd2.waiting_tasks_count - wd1.waiting_tasks_count) AS [Number of Waits], - CASE WHEN (wd2.waiting_tasks_count - wd1.waiting_tasks_count) > 0 - THEN - CAST((wd2.wait_time_ms-wd1.wait_time_ms)/ - (1.0*(wd2.waiting_tasks_count - wd1.waiting_tasks_count)) AS NUMERIC(12,1)) - ELSE 0 END AS [Avg ms Per Wait], N'https://www.sqlskills.com/help/waits/' + LOWER(wd1.wait_type) + '/' AS URL FROM max_batch b JOIN #WaitStats wd2 ON @@ -40651,17 +40764,17 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, wd1.wait_type, COALESCE(wcat.WaitCategory, 'Other') AS wait_category, c.[Wait Time (Seconds)], + CASE WHEN (wd2.waiting_tasks_count - wd1.waiting_tasks_count) > 0 + THEN + CAST((wd2.wait_time_ms-wd1.wait_time_ms)/ + (1.0*(wd2.waiting_tasks_count - wd1.waiting_tasks_count)) AS NUMERIC(12,1)) + ELSE 0 END AS [Avg ms Per Wait], CAST((CAST(wd2.wait_time_ms - wd1.wait_time_ms AS MONEY)) / 1000.0 / cores.cpu_count / DATEDIFF(ss, wd1.SampleTime, wd2.SampleTime) AS DECIMAL(18,1)) AS [Per Core Per Second], c.[Signal Wait Time (Seconds)], CASE WHEN c.[Wait Time (Seconds)] > 0 THEN CAST(100.*(c.[Signal Wait Time (Seconds)]/c.[Wait Time (Seconds)]) AS NUMERIC(4,1)) ELSE 0 END AS [Percent Signal Waits], (wd2.waiting_tasks_count - wd1.waiting_tasks_count) AS [Number of Waits], - CASE WHEN (wd2.waiting_tasks_count - wd1.waiting_tasks_count) > 0 - THEN - CAST((wd2.wait_time_ms-wd1.wait_time_ms)/ - (1.0*(wd2.waiting_tasks_count - wd1.waiting_tasks_count)) AS NUMERIC(12,1)) - ELSE 0 END AS [Avg ms Per Wait], N'https://www.sqlskills.com/help/waits/' + LOWER(wd1.wait_type) + '/' AS URL FROM max_batch b JOIN #WaitStats wd2 ON diff --git a/Install-Azure.sql b/Install-Azure.sql index bed3ddb87..fbbe2acf5 100644 --- a/Install-Azure.sql +++ b/Install-Azure.sql @@ -37,7 +37,7 @@ AS SET NOCOUNT ON; SET STATISTICS XML OFF; -SELECT @Version = '8.24', @VersionDate = '20250407'; +SELECT @Version = '8.25', @VersionDate = '20250704'; IF(@VersionCheckMode = 1) BEGIN @@ -1147,6 +1147,7 @@ ALTER PROCEDURE dbo.sp_BlitzCache @DurationFilter DECIMAL(38,4) = NULL , @HideSummary BIT = 0 , @IgnoreSystemDBs BIT = 1 , + @IgnoreReadableReplicaDBs BIT = 1 , @OnlyQueryHashes VARCHAR(MAX) = NULL , @IgnoreQueryHashes VARCHAR(MAX) = NULL , @OnlySqlHandles VARCHAR(MAX) = NULL , @@ -1173,7 +1174,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.24', @VersionDate = '20250407'; +SELECT @Version = '8.25', @VersionDate = '20250704'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -2296,7 +2297,7 @@ CREATE TABLE #plan_usage ); -IF EXISTS (SELECT * FROM sys.all_objects o WHERE o.name = 'dm_hadr_database_replica_states') +IF @IgnoreReadableReplicaDBs = 1 AND EXISTS (SELECT * FROM sys.all_objects o WHERE o.name = 'dm_hadr_database_replica_states') BEGIN RAISERROR('Checking for Read intent databases to exclude',0,0) WITH NOWAIT; @@ -2715,7 +2716,7 @@ IF @VersionShowsAirQuoteActualPlans = 1 SET @body += N' WHERE 1 = 1 ' + @nl ; - IF EXISTS (SELECT * FROM sys.all_objects o WHERE o.name = 'dm_hadr_database_replica_states') + IF @IgnoreReadableReplicaDBs = 1 AND EXISTS (SELECT * FROM sys.all_objects o WHERE o.name = 'dm_hadr_database_replica_states') BEGIN RAISERROR(N'Ignoring readable secondaries databases by default', 0, 1) WITH NOWAIT; SET @body += N' AND CAST(xpa.value AS INT) NOT IN (SELECT database_id FROM #ReadableDBs)' + @nl ; @@ -8557,7 +8558,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.24', @VersionDate = '20250407'; +SELECT @Version = '8.25', @VersionDate = '20250704'; IF(@VersionCheckMode = 1) BEGIN @@ -10148,7 +10149,7 @@ BEGIN 'Maintenance Tasks Running' AS FindingGroup, 'Restore Running' AS Finding, 'https://www.brentozar.com/askbrent/backups/' AS URL, - 'Restore of ' + DB_NAME(db.resource_database_id) + ' database (' + (SELECT CAST(CAST(SUM(size * 8.0 / 1024 / 1024) AS BIGINT) AS NVARCHAR) FROM #MasterFiles WHERE database_id = db.resource_database_id) + '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), '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, @@ -10156,14 +10157,14 @@ BEGIN s.nt_user_name AS NTUserName, s.[program_name] AS ProgramName, s.[host_name] AS HostName, - db.[resource_database_id] AS DatabaseID, - DB_NAME(db.resource_database_id) AS DatabaseName, + COALESCE(db.[resource_database_id],0) AS DatabaseID, + COALESCE(DB_NAME(db.resource_database_id), 'Unknown') AS DatabaseName, 0 AS OpenTransactionCount, r.query_hash FROM sys.dm_exec_requests r INNER JOIN sys.dm_exec_connections c ON r.session_id = c.session_id INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id - INNER JOIN ( + LEFT OUTER JOIN ( SELECT DISTINCT request_session_id, resource_database_id FROM sys.dm_tran_locks WHERE resource_type = N'DATABASE' @@ -10531,6 +10532,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, FROM sys.databases WHERE database_id > 4; + /* Server Info - Memory Grants pending - CheckID 39 */ IF (@Debug = 1) BEGIN @@ -11590,6 +11592,28 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, END; + /* Query Problems - Deadlocks - CheckID 51 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 51',10,1) WITH NOWAIT; + END + + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details, HowToStopIt) + SELECT 51 AS CheckID, + 100 AS Priority, + 'Query Problems' AS FindingGroup, + 'Deadlocks' AS Finding, + ' https://www.brentozar.com/go/deadlocks' AS URL, + 'Number of deadlocks during the sample: ' + CAST(ps.value_delta AS NVARCHAR(20)) + @LineFeed + + 'Determined by sampling Perfmon counter ' + ps.object_name + ' - ' + ps.counter_name + @LineFeed AS Details, + 'Check sp_BlitzLock to find which indexes and queries to tune.' AS HowToStopIt + FROM #PerfmonStats ps + WHERE ps.Pass = 2 + AND counter_name = 'Number of Deadlocks/sec' + AND instance_name LIKE '_Total%' + AND value_delta > 0; + + /* SQL Server Internal Maintenance - Log File Growing - CheckID 13 */ IF (@Debug = 1) BEGIN @@ -11835,6 +11859,53 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, OR max_session_percent >= 90); END + /* Server Info - Thread Time - CheckID 50 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 50',10,1) WITH NOWAIT; + END + + ;WITH max_batch AS ( + SELECT MAX(SampleTime) AS SampleTime + FROM #WaitStats + ) + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) + SELECT TOP 1 + 50 AS CheckID, + 251 AS Priority, + 'Server Info' AS FindingGroup, + 'Thread Time' AS Finding, + LTRIM( + CASE + WHEN c.[TotalThreadTimeSeconds] >= 86400 THEN + CAST(c.[TotalThreadTimeSeconds] / 86400 AS VARCHAR) + 'd ' + ELSE '' + END + + CASE + WHEN c.[TotalThreadTimeSeconds] % 86400 >= 3600 THEN + CAST((c.[TotalThreadTimeSeconds] % 86400) / 3600 AS VARCHAR) + 'h ' + ELSE '' + END + + CASE + WHEN c.[TotalThreadTimeSeconds] % 3600 >= 60 THEN + CAST((c.[TotalThreadTimeSeconds] % 3600) / 60 AS VARCHAR) + 'm ' + ELSE '' + END + + CASE + WHEN c.[TotalThreadTimeSeconds] % 60 > 0 OR c.[TotalThreadTimeSeconds] = 0 THEN + CAST(c.[TotalThreadTimeSeconds] % 60 AS VARCHAR) + 's' + ELSE '' + END + ) AS Details, + CAST(c.[TotalThreadTimeSeconds] AS DECIMAL(18,1)) AS DetailsInt, + 'https://www.brentozar.com/go/threadtime' AS URL + FROM max_batch b + JOIN #WaitStats wd2 ON wd2.SampleTime = b.SampleTime + JOIN #WaitStats wd1 ON wd1.wait_type = wd2.wait_type AND wd2.SampleTime > wd1.SampleTime + CROSS APPLY ( + SELECT CAST((wd2.thread_time_ms - wd1.thread_time_ms) / 1000 AS INT) AS TotalThreadTimeSeconds + ) AS c; + /* Server Info - Batch Requests per Sec - CheckID 19 */ IF (@Debug = 1) BEGIN @@ -13164,13 +13235,13 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, wd1.wait_type, COALESCE(wcat.WaitCategory, 'Other') AS wait_category, CAST(c.[Wait Time (Seconds)] / 60. / 60. AS DECIMAL(18,1)) AS [Wait Time (Hours)], - CAST((wd2.wait_time_ms - wd1.wait_time_ms) / 1000.0 / cores.cpu_count / DATEDIFF(ss, wd1.SampleTime, wd2.SampleTime) AS DECIMAL(18,1)) AS [Per Core Per Hour], - (wd2.waiting_tasks_count - wd1.waiting_tasks_count) AS [Number of Waits], CASE WHEN (wd2.waiting_tasks_count - wd1.waiting_tasks_count) > 0 THEN CAST((wd2.wait_time_ms-wd1.wait_time_ms)/ (1.0*(wd2.waiting_tasks_count - wd1.waiting_tasks_count)) AS NUMERIC(12,1)) - ELSE 0 END AS [Avg ms Per Wait] + ELSE 0 END AS [Avg ms Per Wait], + CAST((wd2.wait_time_ms - wd1.wait_time_ms) / 1000.0 / cores.cpu_count / DATEDIFF(ss, wd1.SampleTime, wd2.SampleTime) AS DECIMAL(18,1)) AS [Per Core Per Hour], + (wd2.waiting_tasks_count - wd1.waiting_tasks_count) AS [Number of Waits] FROM max_batch b JOIN #WaitStats wd2 ON wd2.SampleTime =b.SampleTime @@ -13309,17 +13380,17 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, wd1.wait_type, COALESCE(wcat.WaitCategory, 'Other') AS wait_category, CAST(c.[Wait Time (Seconds)] / 60. / 60. AS DECIMAL(18,1)) AS [Wait Time (Hours)], + CASE WHEN (wd2.waiting_tasks_count - wd1.waiting_tasks_count) > 0 + THEN + CAST((wd2.wait_time_ms-wd1.wait_time_ms)/ + (1.0*(wd2.waiting_tasks_count - wd1.waiting_tasks_count)) AS NUMERIC(12,1)) + ELSE 0 END AS [Avg ms Per Wait], CAST((wd2.wait_time_ms - wd1.wait_time_ms) / 1000.0 / cores.cpu_count / DATEDIFF(ss, wd1.SampleTime, wd2.SampleTime) AS DECIMAL(18,1)) AS [Per Core Per Hour], CAST(c.[Signal Wait Time (Seconds)] / 60.0 / 60 AS DECIMAL(18,1)) AS [Signal Wait Time (Hours)], CASE WHEN c.[Wait Time (Seconds)] > 0 THEN CAST(100.*(c.[Signal Wait Time (Seconds)]/c.[Wait Time (Seconds)]) AS NUMERIC(4,1)) ELSE 0 END AS [Percent Signal Waits], (wd2.waiting_tasks_count - wd1.waiting_tasks_count) AS [Number of Waits], - CASE WHEN (wd2.waiting_tasks_count - wd1.waiting_tasks_count) > 0 - THEN - CAST((wd2.wait_time_ms-wd1.wait_time_ms)/ - (1.0*(wd2.waiting_tasks_count - wd1.waiting_tasks_count)) AS NUMERIC(12,1)) - ELSE 0 END AS [Avg ms Per Wait], N'https://www.sqlskills.com/help/waits/' + LOWER(wd1.wait_type) + '/' AS URL FROM max_batch b JOIN #WaitStats wd2 ON @@ -13353,17 +13424,17 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, wd1.wait_type, COALESCE(wcat.WaitCategory, 'Other') AS wait_category, c.[Wait Time (Seconds)], + CASE WHEN (wd2.waiting_tasks_count - wd1.waiting_tasks_count) > 0 + THEN + CAST((wd2.wait_time_ms-wd1.wait_time_ms)/ + (1.0*(wd2.waiting_tasks_count - wd1.waiting_tasks_count)) AS NUMERIC(12,1)) + ELSE 0 END AS [Avg ms Per Wait], CAST((CAST(wd2.wait_time_ms - wd1.wait_time_ms AS MONEY)) / 1000.0 / cores.cpu_count / DATEDIFF(ss, wd1.SampleTime, wd2.SampleTime) AS DECIMAL(18,1)) AS [Per Core Per Second], c.[Signal Wait Time (Seconds)], CASE WHEN c.[Wait Time (Seconds)] > 0 THEN CAST(100.*(c.[Signal Wait Time (Seconds)]/c.[Wait Time (Seconds)]) AS NUMERIC(4,1)) ELSE 0 END AS [Percent Signal Waits], (wd2.waiting_tasks_count - wd1.waiting_tasks_count) AS [Number of Waits], - CASE WHEN (wd2.waiting_tasks_count - wd1.waiting_tasks_count) > 0 - THEN - CAST((wd2.wait_time_ms-wd1.wait_time_ms)/ - (1.0*(wd2.waiting_tasks_count - wd1.waiting_tasks_count)) AS NUMERIC(12,1)) - ELSE 0 END AS [Avg ms Per Wait], N'https://www.sqlskills.com/help/waits/' + LOWER(wd1.wait_type) + '/' AS URL FROM max_batch b JOIN #WaitStats wd2 ON @@ -13567,7 +13638,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.24', @VersionDate = '20250407'; +SELECT @Version = '8.25', @VersionDate = '20250704'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -20357,7 +20428,7 @@ BEGIN SET XACT_ABORT OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.24', @VersionDate = '20250407'; + SELECT @Version = '8.25', @VersionDate = '20250704'; IF @VersionCheckMode = 1 BEGIN @@ -21570,7 +21641,7 @@ BEGIN END; /* If table target */ - IF @TargetSessionType = 'table' + IF LOWER(@TargetSessionType) = N'table' BEGIN SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Inserting to #deadlock_data from table source %s', 0, 1, @d) WITH NOWAIT; @@ -21588,9 +21659,19 @@ BEGIN SELECT TOP (1) @xe = xe.e.exist(''.''), @xd = xd.e.exist(''.'') - FROM [master].[dbo].[bpr] AS x - OUTER APPLY x.[bpr].nodes(''/event'') AS xe(e) - OUTER APPLY x.[bpr].nodes(''/deadlock'') AS xd(e) + FROM ' + + QUOTENAME(@TargetDatabaseName) + + N'.' + + QUOTENAME(@TargetSchemaName) + + N'.' + + QUOTENAME(@TargetTableName) + + N' AS x + OUTER APPLY x.' + + QUOTENAME(@TargetColumnName) + + N'.nodes(''/event'') AS xe(e) + OUTER APPLY x.' + + QUOTENAME(@TargetColumnName) + + N'.nodes(''/deadlock'') AS xd(e) OPTION(RECOMPILE); '; @@ -21727,6 +21808,7 @@ BEGIN LEFT JOIN #t AS t ON 1 = 1 WHERE @xe = 1 + OR LOWER(@TargetSessionType) <> N'table' UNION ALL @@ -24890,7 +24972,7 @@ BEGIN SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.24', @VersionDate = '20250407'; + SELECT @Version = '8.25', @VersionDate = '20250704'; IF(@VersionCheckMode = 1) BEGIN diff --git a/SqlServerVersions.sql b/SqlServerVersions.sql index 4866d6e22..94ae4a0b0 100644 --- a/SqlServerVersions.sql +++ b/SqlServerVersions.sql @@ -42,6 +42,10 @@ INSERT INTO dbo.SqlServerVersions (MajorVersionNumber, MinorVersionNumber, Branch, [Url], ReleaseDate, MainstreamSupportEndDate, ExtendedSupportEndDate, MajorVersionName, MinorVersionName) VALUES /*2022*/ + (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, 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'), (16, 4165, 'CU16', 'https://support.microsoft.com/en-us/help/5048033', '2024-11-14', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 16'), diff --git a/sp_Blitz.sql b/sp_Blitz.sql index 58c97a514..e15695fe4 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -38,7 +38,7 @@ AS SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.24', @VersionDate = '20250407'; + SELECT @Version = '8.25', @VersionDate = '20250704'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) diff --git a/sp_BlitzAnalysis.sql b/sp_BlitzAnalysis.sql index 96216408c..b30dedd22 100644 --- a/sp_BlitzAnalysis.sql +++ b/sp_BlitzAnalysis.sql @@ -37,7 +37,7 @@ AS SET NOCOUNT ON; SET STATISTICS XML OFF; -SELECT @Version = '8.24', @VersionDate = '20250407'; +SELECT @Version = '8.25', @VersionDate = '20250704'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_BlitzBackups.sql b/sp_BlitzBackups.sql index 5f33342e4..822d79f53 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.24', @VersionDate = '20250407'; + SELECT @Version = '8.25', @VersionDate = '20250704'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_BlitzCache.sql b/sp_BlitzCache.sql index 655f855b5..19e02afe3 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.24', @VersionDate = '20250407'; +SELECT @Version = '8.25', @VersionDate = '20250704'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) diff --git a/sp_BlitzFirst.sql b/sp_BlitzFirst.sql index fe1301bc4..28a20f9e6 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.24', @VersionDate = '20250407'; +SELECT @Version = '8.25', @VersionDate = '20250704'; IF(@VersionCheckMode = 1) BEGIN @@ -3359,22 +3359,41 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, FROM #WaitStats ) INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) - SELECT TOP 1 50 AS CheckID, + SELECT TOP 1 + 50 AS CheckID, 251 AS Priority, 'Server Info' AS FindingGroup, 'Thread Time' AS Finding, - CAST(CAST(c.[Total Thread Time (Seconds)] AS DECIMAL(18,1)) AS VARCHAR(100)) AS Details, - CAST(c.[Total Thread Time (Seconds)] AS DECIMAL(18,1)) AS DetailsInt, + LTRIM( + CASE + WHEN c.[TotalThreadTimeSeconds] >= 86400 THEN + CAST(c.[TotalThreadTimeSeconds] / 86400 AS VARCHAR) + 'd ' + ELSE '' + END + + CASE + WHEN c.[TotalThreadTimeSeconds] % 86400 >= 3600 THEN + CAST((c.[TotalThreadTimeSeconds] % 86400) / 3600 AS VARCHAR) + 'h ' + ELSE '' + END + + CASE + WHEN c.[TotalThreadTimeSeconds] % 3600 >= 60 THEN + CAST((c.[TotalThreadTimeSeconds] % 3600) / 60 AS VARCHAR) + 'm ' + ELSE '' + END + + CASE + WHEN c.[TotalThreadTimeSeconds] % 60 > 0 OR c.[TotalThreadTimeSeconds] = 0 THEN + CAST(c.[TotalThreadTimeSeconds] % 60 AS VARCHAR) + 's' + ELSE '' + END + ) AS Details, + CAST(c.[TotalThreadTimeSeconds] AS DECIMAL(18,1)) AS DetailsInt, 'https://www.brentozar.com/go/threadtime' AS URL - FROM max_batch b - JOIN #WaitStats wd2 ON - wd2.SampleTime =b.SampleTime - JOIN #WaitStats wd1 ON - wd1.wait_type=wd2.wait_type AND - wd2.SampleTime > wd1.SampleTime - CROSS APPLY (SELECT - CAST((wd2.thread_time_ms - wd1.thread_time_ms)/1000. AS DECIMAL(18,1)) AS [Total Thread Time (Seconds)] - ) AS c; + FROM max_batch b + JOIN #WaitStats wd2 ON wd2.SampleTime = b.SampleTime + JOIN #WaitStats wd1 ON wd1.wait_type = wd2.wait_type AND wd2.SampleTime > wd1.SampleTime + CROSS APPLY ( + SELECT CAST((wd2.thread_time_ms - wd1.thread_time_ms) / 1000 AS INT) AS TotalThreadTimeSeconds + ) AS c; /* Server Info - Batch Requests per Sec - CheckID 19 */ IF (@Debug = 1) diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index 365cbe6c6..629cf39ca 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -49,7 +49,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.24', @VersionDate = '20250407'; +SELECT @Version = '8.25', @VersionDate = '20250704'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index 52361d583..c0a4589d6 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.24', @VersionDate = '20250407'; + SELECT @Version = '8.25', @VersionDate = '20250704'; IF @VersionCheckMode = 1 BEGIN diff --git a/sp_BlitzWho.sql b/sp_BlitzWho.sql index 6bb2df72f..5c49699e9 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.24', @VersionDate = '20250407'; + SELECT @Version = '8.25', @VersionDate = '20250704'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_DatabaseRestore.sql b/sp_DatabaseRestore.sql index b685a0d08..f4b5cb858 100755 --- a/sp_DatabaseRestore.sql +++ b/sp_DatabaseRestore.sql @@ -58,7 +58,7 @@ SET STATISTICS XML OFF; /*Versioning details*/ -SELECT @Version = '8.24', @VersionDate = '20250407'; +SELECT @Version = '8.25', @VersionDate = '20250704'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_ineachdb.sql b/sp_ineachdb.sql index bf9b147b4..a477eb9c3 100644 --- a/sp_ineachdb.sql +++ b/sp_ineachdb.sql @@ -37,7 +37,7 @@ BEGIN SET NOCOUNT ON; SET STATISTICS XML OFF; - SELECT @Version = '8.24', @VersionDate = '20250407'; + SELECT @Version = '8.25', @VersionDate = '20250704'; IF(@VersionCheckMode = 1) BEGIN From d2f338068d17fdbb4eadaf7cbe6f36bcf04f56cb Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Fri, 4 Jul 2025 06:41:41 -0700 Subject: [PATCH 632/662] #3662 remove unit tests Closes #3662. --- tests/run-tests.ps1 | 14 ----------- tests/sp_Blitz.tests.ps1 | 10 -------- tests/sp_BlitzAnalysis.tests.ps1 | 15 ------------ tests/sp_BlitzBackups.tests.ps1 | 20 ---------------- tests/sp_BlitzCache.tests.ps1 | 13 ----------- tests/sp_BlitzFirst.tests.ps1 | 40 -------------------------------- tests/sp_BlitzIndex.tests.ps1 | 10 -------- tests/sp_BlitzLock.tests.ps1 | 11 --------- tests/sp_BlitzWho.tests.ps1 | 15 ------------ 9 files changed, 148 deletions(-) delete mode 100644 tests/run-tests.ps1 delete mode 100644 tests/sp_Blitz.tests.ps1 delete mode 100644 tests/sp_BlitzAnalysis.tests.ps1 delete mode 100644 tests/sp_BlitzBackups.tests.ps1 delete mode 100644 tests/sp_BlitzCache.tests.ps1 delete mode 100644 tests/sp_BlitzFirst.tests.ps1 delete mode 100644 tests/sp_BlitzIndex.tests.ps1 delete mode 100644 tests/sp_BlitzLock.tests.ps1 delete mode 100644 tests/sp_BlitzWho.tests.ps1 diff --git a/tests/run-tests.ps1 b/tests/run-tests.ps1 deleted file mode 100644 index c16ea15f5..000000000 --- a/tests/run-tests.ps1 +++ /dev/null @@ -1,14 +0,0 @@ -# Assign default values if script-scoped variables are not set -$ServerInstance = if ($null -ne $script:ServerInstance) { $script:ServerInstance } else { "localhost" } -$UserName = if ($null -ne $script:UserName) { $script:UserName } else { "sa" } -$Password = if ($null -ne $script:Password) { $script:Password } else { "dbatools.I0" } -$TrustServerCertificate = if ($null -ne $script:TrustServerCertificate) { $script:TrustServerCertificate } else { $true } - -$PSDefaultParameterValues = @{ - "*:ServerInstance" = $ServerInstance - "*:UserName" = $UserName - "*:Password" = $Password - "*:TrustServerCertificate" = $TrustServerCertificate -} - -Invoke-Pester -PassThru \ No newline at end of file diff --git a/tests/sp_Blitz.tests.ps1 b/tests/sp_Blitz.tests.ps1 deleted file mode 100644 index 341b9c348..000000000 --- a/tests/sp_Blitz.tests.ps1 +++ /dev/null @@ -1,10 +0,0 @@ -Describe "sp_Blitz Tests" { - - It "sp_Blitz Check" { - $results = Invoke-SqlCmd -Query "EXEC dbo.sp_Blitz" -OutputAs DataSet - $results.Tables.Count | Should -Be 1 - $results.Tables[0].Columns.Count | Should -Be 9 - $results.Tables[0].Rows.Count | Should -BeGreaterThan 0 - } - -} diff --git a/tests/sp_BlitzAnalysis.tests.ps1 b/tests/sp_BlitzAnalysis.tests.ps1 deleted file mode 100644 index f346b6197..000000000 --- a/tests/sp_BlitzAnalysis.tests.ps1 +++ /dev/null @@ -1,15 +0,0 @@ -Describe "sp_BlitzAnalysis Tests" { - - It "sp_BlitzAnalysis Check" { - - # Run sp_BlitzFirst to populate the tables used by sp_BlitzAnalysis - Invoke-SqlCmd -Query "EXEC dbo.sp_BlitzFirst @OutputDatabaseName = 'tempdb', @OutputSchemaName = N'dbo', @OutputTableName = N'BlitzFirst', @OutputTableNameFileStats = N'BlitzFirst_FileStats',@OutputTableNamePerfmonStats = N'BlitzFirst_PerfmonStats', - @OutputTableNameWaitStats = N'BlitzFirst_WaitStats', - @OutputTableNameBlitzCache = N'BlitzCache', - @OutputTableNameBlitzWho= N'BlitzWho'" - - $results = Invoke-SqlCmd -Query "EXEC dbo.sp_BlitzAnalysis @OutputDatabaseName = 'tempdb'" -OutputAs DataSet - $results.Tables.Count | Should -BeGreaterThan 6 - } - -} diff --git a/tests/sp_BlitzBackups.tests.ps1 b/tests/sp_BlitzBackups.tests.ps1 deleted file mode 100644 index 162e869fb..000000000 --- a/tests/sp_BlitzBackups.tests.ps1 +++ /dev/null @@ -1,20 +0,0 @@ -Describe "sp_BlitzBackups Tests" { - - It "sp_BlitzBackups Check" { - # Give sp_BlitzBackups something to capture by performing a dummy backup of model DB - # Test to be run in GitHub action but backing up model to NUL should be safe on most systems - Invoke-SqlCmd -Query "BACKUP DATABASE model TO DISK='NUL'" - $results = Invoke-SqlCmd -Query "EXEC dbo.sp_BlitzBackups" -OutputAs DataSet - $results.Tables.Count | Should -Be 3 - - $results.Tables[0].Columns.Count | Should -Be 39 - $results.Tables[0].Rows.Count | Should -BeGreaterThan 0 - - $results.Tables[1].Columns.Count | Should -Be 32 - $results.Tables[1].Rows.Count | Should -BeGreaterThan 0 - - $results.Tables[2].Columns.Count | Should -Be 5 - $results.Tables[2].Rows.Count | Should -BeGreaterThan 0 - } - -} diff --git a/tests/sp_BlitzCache.tests.ps1 b/tests/sp_BlitzCache.tests.ps1 deleted file mode 100644 index 4483e090b..000000000 --- a/tests/sp_BlitzCache.tests.ps1 +++ /dev/null @@ -1,13 +0,0 @@ -Describe "sp_BlitzCache Tests" { - - It "sp_BlitzCache Check" { - # Note: Added 'SELECT 1 AS A' as an empty first resultset causes issues returning the full DataSet - $results = Invoke-SqlCmd -Query "SELECT 1 AS A;EXEC dbo.sp_BlitzCache" -OutputAs DataSet - # Adjust table count to get the actual tables returned from sp_BlitzCache (So reporting isn't confusing) - $tableCount = $results.Tables.Count -1 - $tableCount | Should -Be 2 - $results.Tables[1].Columns.Count | Should -Be 43 - $results.Tables[2].Columns.Count | Should -Be 6 - $results.Tables[2].Rows.Count | Should -BeGreaterThan 0 - } -} \ No newline at end of file diff --git a/tests/sp_BlitzFirst.tests.ps1 b/tests/sp_BlitzFirst.tests.ps1 deleted file mode 100644 index d2bb42d3f..000000000 --- a/tests/sp_BlitzFirst.tests.ps1 +++ /dev/null @@ -1,40 +0,0 @@ -Describe "sp_BlitzFirst Tests" { - - It "sp_BlitzFirst Check" { - # Give sp_BlitzFirst something to capture - Start-Job -ScriptBlock { - Invoke-SqlCmd -Query "WAITFOR DELAY '00:00:15'" -ServerInstance $using:ServerInstance -Username $using:UserName -Password $using:Password -TrustServerCertificate:$using:TrustServerCertificate - } - Start-Sleep -Milliseconds 1000 - $results = Invoke-SqlCmd -Query "EXEC dbo.sp_BlitzFirst" -OutputAs DataSet - $results.Tables.Count | Should -Be 1 - $results.Tables[0].Columns.Count | Should -Be 8 - $results.Tables[0].Rows.Count | Should -BeGreaterThan 0 - - $results = Invoke-SqlCmd -Query "EXEC dbo.sp_BlitzFirst @ExpertMode=1" -OutputAs DataSet - $results.Tables.Count | Should -Be 7 - - $results.Tables[0].Columns.Count | Should -Be 21 - $results.Tables[0].Rows.Count | Should -BeGreaterThan 0 - - $results.Tables[1].Columns.Count | Should -Be 40 - $results.Tables[1].Rows.Count | Should -BeGreaterThan 0 - - $results.Tables[2].Columns.Count | Should -Be 13 - $results.Tables[2].Rows.Count | Should -BeGreaterThan 0 - - $results.Tables[3].Columns.Count | Should -Be 11 - $results.Tables[3].Rows.Count | Should -BeGreaterThan 0 - - $results.Tables[4].Columns.Count | Should -Be 10 - $results.Tables[4].Rows.Count | Should -BeGreaterThan 0 - - $results.Tables[5].Columns.Count | Should -Be 4 - $results.Tables[5].Rows.Count | Should -BeGreaterThan 0 - - $results.Tables[6].Columns.Count | Should -Be 21 - $results.Tables[6].Rows.Count | Should -BeGreaterThan 0 - - } - -} \ No newline at end of file diff --git a/tests/sp_BlitzIndex.tests.ps1 b/tests/sp_BlitzIndex.tests.ps1 deleted file mode 100644 index 63c479ad3..000000000 --- a/tests/sp_BlitzIndex.tests.ps1 +++ /dev/null @@ -1,10 +0,0 @@ -Describe "sp_BlitzIndex Tests" { - - It "sp_BlitzIndex Check" { - $results = Invoke-SqlCmd -Query "EXEC dbo.sp_BlitzIndex" -OutputAs DataSet - $results.Tables.Count | Should -Be 1 - $results.Tables[0].Columns.Count | Should -Be 12 - $results.Tables[0].Rows.Count | Should -BeGreaterThan 0 - } - -} \ No newline at end of file diff --git a/tests/sp_BlitzLock.tests.ps1 b/tests/sp_BlitzLock.tests.ps1 deleted file mode 100644 index 5368e3283..000000000 --- a/tests/sp_BlitzLock.tests.ps1 +++ /dev/null @@ -1,11 +0,0 @@ -Describe "sp_BlitzLock Tests" { - - It "sp_BlitzLock Check" { - # Note: Added 'SELECT 1 AS A' as an empty first resultset causes issues returning the full DataSet - $results = Invoke-SqlCmd -Query "SELECT 1 AS A;EXEC dbo.sp_BlitzLock" -OutputAs DataSet - # Adjust table count to get the actual tables returned from sp_BlitzLock (So reporting isn't confusing) - $tableCount = $results.Tables.Count - 1 - $tableCount | Should -Be 3 - } - -} \ No newline at end of file diff --git a/tests/sp_BlitzWho.tests.ps1 b/tests/sp_BlitzWho.tests.ps1 deleted file mode 100644 index 3e9febd56..000000000 --- a/tests/sp_BlitzWho.tests.ps1 +++ /dev/null @@ -1,15 +0,0 @@ -Describe "sp_BlitzWho Tests" { - - It "sp_BlitzWho Check" { - # Give sp_BlitzWho something to capture - Start-Job -ScriptBlock { - Invoke-SqlCmd -Query "WAITFOR DELAY '00:00:15'" -ServerInstance $using:ServerInstance -Username $using:UserName -Password $using:Password -TrustServerCertificate:$using:TrustServerCertificate - } - Start-Sleep -Milliseconds 1000 - $results = Invoke-SqlCmd -Query "EXEC dbo.sp_BlitzWho" -OutputAs DataSet - $results.Tables.Count | Should -Be 1 - $results.Tables[0].Columns.Count | Should -Be 21 - $results.Tables[0].Rows.Count | Should -BeGreaterThan 0 - } - -} \ No newline at end of file From 60cee45c443a166a70712ed759d3341b6c39709b Mon Sep 17 00:00:00 2001 From: rodik Date: Wed, 16 Jul 2025 14:52:31 +0200 Subject: [PATCH 633/662] 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 c0a4589d6..bf97f1977 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 634/662] 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 bf97f1977..bd280b174 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 635/662] 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 a477eb9c3..5f81ee422 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 636/662] 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 e15695fe4..f1e92f4c1 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 637/662] 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 f1e92f4c1..4e3dbaccf 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 638/662] 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 4e3dbaccf..521b16f14 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 639/662] #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 94ae4a0b0..cb1bb869a 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 640/662] 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 19e02afe3..7d4175fdd 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 641/662] 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 521b16f14..41eceb780 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 642/662] 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 521b16f14..97bcf0645 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 643/662] 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 97bcf0645..0b6403a08 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 644/662] 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 0b6403a08..97bcf0645 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 645/662] #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 c81a97708..d59eeb858 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 e48ee2b7d..48ab2efe0 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 646/662] #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 b81ea5505..36d2bbe7d 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 28a20f9e6..5e08c1289 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 647/662] 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 619438d0f..1b64b5d3c 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 629cf39ca..c37e755a9 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 648/662] 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 1b64b5d3c..4da92c722 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 bfcf2470d..9490300eb 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 c37e755a9..4b63b7f86 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 649/662] #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 5e08c1289..bfcb66bd6 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 650/662] #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 5c49699e9..0267ff3cf 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 651/662] #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 4b63b7f86..feb81a434 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 652/662] 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 feb81a434..56d83b58d 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 653/662] 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 4da92c722..04ecd4666 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 56d83b58d..078d5c5df 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 654/662] 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 bfcb66bd6..df1dd7d00 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 655/662] 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 078d5c5df..67eb3c7db 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 656/662] 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 078d5c5df..40044171b 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 657/662] 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 48ab2efe0..b0d360120 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 658/662] 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 36d2bbe7d..433d096fe 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 df1dd7d00..912452296 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 659/662] 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 5f310717a..f1ec7b551 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 fbbe2acf5..5f0a15cdf 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 cb1bb869a..3bb177a6a 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 b0d360120..6bd44a704 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 b30dedd22..09f11f49e 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 822d79f53..dafec335f 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 7d4175fdd..0df4e8a4e 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 912452296..f5dda196e 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 7821b1d22..57e2760cb 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 bd280b174..bd4e75da7 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 0267ff3cf..a9b61f89d 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 f4b5cb858..1a087860f 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 5f81ee422..eca2b7a98 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 From 409baed6d1d464bbd2a859c98549d8d29db76fc3 Mon Sep 17 00:00:00 2001 From: Ben <60398078+bwiggin10@users.noreply.github.com> Date: Wed, 8 Oct 2025 13:28:00 -0400 Subject: [PATCH 660/662] Fix database object-related trace flag check wording This adds additional context to the checks for Trace Flags 834, 7745 and 7752 that have messages dependent on @CheckUserDatabasesObjects = 1 (Or that and @BringThePain = 1 for database counts over 50). So when you just ran sp_Blitz, you could end up with Trace Flag 7745 telling you that no databases have query store enabled even if you do (Because the check @QueryStoreInUse couldn't run) --- sp_Blitz.sql | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index 6bd44a704..102cbdc2f 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -8562,6 +8562,7 @@ EXEC dbo.sp_MSforeachdb 'USE [?]; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITT CASE WHEN [T].[TraceFlag] = '652' THEN '652 enabled globally, which disables pre-fetching during index scans. This is usually a very bad idea.' WHEN [T].[TraceFlag] = '661' THEN '661 enabled globally, which disables ghost record removal, causing the database to grow in size. This is usually a very bad idea.' WHEN [T].[TraceFlag] = '834' AND @ColumnStoreIndexesInUse = 1 THEN '834 is enabled globally, but you also have columnstore indexes. That combination is not recommended by Microsoft.' + WHEN [T].[TraceFlag] = '834' AND @CheckUserDatabaseObjects = 0 THEN '834 is enabled globally, but @CheckUserDatabaseObjects was set to 0, so we skipped checking if any databases have columnstore indexes. That combination is not recommended by Microsoft.' WHEN [T].[TraceFlag] = '1117' THEN '1117 enabled globally, which grows all files in a filegroup at the same time.' WHEN [T].[TraceFlag] = '1118' THEN '1118 enabled globally, which tries to reduce SGAM waits.' WHEN [T].[TraceFlag] = '1211' THEN '1211 enabled globally, which disables lock escalation when you least expect it. This is usually a very bad idea.' @@ -8575,10 +8576,12 @@ EXEC dbo.sp_MSforeachdb 'USE [?]; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITT WHEN [T].[TraceFlag] = '3226' THEN '3226 enabled globally, which keeps the event log clean by not reporting successful backups.' WHEN [T].[TraceFlag] = '3505' THEN '3505 enabled globally, which disables Checkpoints. This is usually a very bad idea.' WHEN [T].[TraceFlag] = '4199' THEN '4199 enabled globally, which enables non-default Query Optimizer fixes, changing query plans from the default behaviors.' + WHEN [T].[TraceFlag] = '7745' AND @CheckUserDatabaseObjects = 0 THEN '7745 enabled globally, which makes shutdowns/failovers quicker by not waiting for Query Store to flush to disk. This good idea loses you the non-flushed Query Store data. @CheckUserDatabaseObjects was set to 0, so we skipped checking if any databases have Query Store enabled.' WHEN [T].[TraceFlag] = '7745' AND @QueryStoreInUse = 1 THEN '7745 enabled globally, which makes shutdowns/failovers quicker by not waiting for Query Store to flush to disk. This good idea loses you the non-flushed Query Store data.' WHEN [T].[TraceFlag] = '7745' AND @ProductVersionMajor > 12 THEN '7745 enabled globally, which is for Query Store. None of your databases have Query Store enabled, so why do you have this turned on?' WHEN [T].[TraceFlag] = '7745' AND @ProductVersionMajor <= 12 THEN '7745 enabled globally, which is for Query Store. Query Store does not exist on your SQL Server version, so why do you have this turned on?' WHEN [T].[TraceFlag] = '7752' AND @ProductVersionMajor > 14 THEN '7752 enabled globally, which is for Query Store. However, it has no effect in your SQL Server version. Consider turning it off.' + WHEN [T].[TraceFlag] = '7752' AND @CheckUserDatabaseObjects = 0 THEN '7752 enabled globally, which stops queries needing to wait on Query Store loading up after database recovery. @CheckUserDatabaseObjects was set to 0, so we skipped checking if any databases have Query Store enabled.' WHEN [T].[TraceFlag] = '7752' AND @QueryStoreInUse = 1 THEN '7752 enabled globally, which stops queries needing to wait on Query Store loading up after database recovery.' WHEN [T].[TraceFlag] = '7752' AND @ProductVersionMajor > 12 THEN '7752 enabled globally, which is for Query Store. None of your databases have Query Store enabled, so why do you have this turned on?' WHEN [T].[TraceFlag] = '7752' AND @ProductVersionMajor <= 12 THEN '7752 enabled globally, which is for Query Store. Query Store does not exist on your SQL Server version, so why do you have this turned on?' From 44d3f8106f69af2a8c82e0026fc06adaf7a47995 Mon Sep 17 00:00:00 2001 From: ReeceGoding <67124261+ReeceGoding@users.noreply.github.com> Date: Fri, 10 Oct 2025 01:50:21 +0100 Subject: [PATCH 661/662] Made the sys.dm_os_ring_buffers CPU checks respect Linux always setting system_idle to 0. Disabled "High CPU Utilization - Not SQL" check on Linux. Closes #3710. --- sp_BlitzFirst.sql | 73 ++++++++++++++++++++++++++++++++++++----------- 1 file changed, 56 insertions(+), 17 deletions(-) diff --git a/sp_BlitzFirst.sql b/sp_BlitzFirst.sql index f5dda196e..f431cb7ac 100644 --- a/sp_BlitzFirst.sql +++ b/sp_BlitzFirst.sql @@ -145,7 +145,18 @@ DECLARE @StringToExecute NVARCHAR(MAX), @get_thread_time_ms NVARCHAR(MAX) = N'', @thread_time_ms FLOAT = 0, @logical_processors INT = 0, - @max_worker_threads INT = 0; + @max_worker_threads INT = 0, + @is_windows_operating_system BIT = 1; + +IF EXISTS +( + SELECT 1 + FROM sys.all_objects + WHERE name = 'dm_os_host_info' +) +BEGIN + SELECT @is_windows_operating_system = CASE WHEN host_platform = 'Windows' THEN 1 ELSE 0 END FROM sys.dm_os_host_info; +END; /* Sanitize our inputs */ SELECT @@ -2399,19 +2410,28 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, RAISERROR('Running CheckID 24',10,1) WITH NOWAIT; END + /* Traditionally, we use 100 - SystemIdle here. + However, SystemIdle is always 0 on Linux. + So if we are on Linux, we use ProcessUtilization instead. + This is the approach found in + https://techcommunity.microsoft.com/blog/sqlserver/sql-server-cpu-usage-available-in-sys-dm-os-ring-buffers-dmv-starting-sql-server/825361 */ INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) - SELECT 24, 50, 'Server Performance', 'High CPU Utilization', CAST(100 - SystemIdle AS NVARCHAR(20)) + N'%.', 100 - SystemIdle, 'https://www.brentozar.com/go/cpu' + SELECT 24, 50, 'Server Performance', 'High CPU Utilization', CAST(CpuUsage AS NVARCHAR(20)) + N'%.', CpuUsage, 'https://www.brentozar.com/go/cpu' + FROM ( + SELECT CASE WHEN @is_windows_operating_system = 1 THEN 100 - SystemIdle ELSE ProcessUtilization END AS CpuUsage FROM ( SELECT record, - record.value('(./Record/SchedulerMonitorEvent/SystemHealth/SystemIdle)[1]', 'int') AS SystemIdle + record.value('(./Record/SchedulerMonitorEvent/SystemHealth/SystemIdle)[1]', 'int') AS SystemIdle, + record.value('(./Record/SchedulerMonitorEvent/SystemHealth/ProcessUtilization)[1]', 'int') AS ProcessUtilization FROM ( SELECT TOP 1 CONVERT(XML, record) AS record FROM sys.dm_os_ring_buffers WHERE ring_buffer_type = N'RING_BUFFER_SCHEDULER_MONITOR' AND record LIKE '%%' ORDER BY timestamp DESC) AS rb - ) AS y - WHERE 100 - SystemIdle >= 50; + ) AS ShreddedCpuXml + ) AS OsCpu + WHERE CpuUsage >= 50; /* CPU Utilization - CheckID 23 */ IF (@Debug = 1) @@ -2423,7 +2443,8 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, WITH y AS ( - SELECT CONVERT(VARCHAR(5), 100 - ca.c.value('.', 'INT')) AS system_idle, + /* See earlier comments about SystemIdle on Linux. */ + SELECT CONVERT(VARCHAR(5), CASE WHEN @is_windows_operating_system = 1 THEN 100 - ca.c.value('.', 'INT') ELSE ca2.p.value('.', 'INT') END) AS cpu_usage, CONVERT(VARCHAR(30), rb.event_date) AS event_date, CONVERT(VARCHAR(8000), rb.record) AS record, event_date as event_date_raw @@ -2436,6 +2457,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, WHERE dorb.ring_buffer_type = N'RING_BUFFER_SCHEDULER_MONITOR' AND record LIKE '%%' ) AS rb CROSS APPLY rb.record.nodes('/Record/SchedulerMonitorEvent/SystemHealth/SystemIdle') AS ca(c) + CROSS APPLY rb.record.nodes('/Record/SchedulerMonitorEvent/SystemHealth/ProcessUtilization') AS ca2(p) ) INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL, HowToStopIt) SELECT TOP 1 @@ -2443,12 +2465,12 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 250, 'Server Info', 'CPU Utilization', - y.system_idle + N'%. Ring buffer details: ' + CAST(y.record AS NVARCHAR(4000)), - y.system_idle , + y.cpu_usage + N'%. Ring buffer details: ' + CAST(y.record AS NVARCHAR(4000)), + y.cpu_usage , 'https://www.brentozar.com/go/cpu', STUFF(( SELECT TOP 2147483647 CHAR(10) + CHAR(13) - + y2.system_idle + + y2.cpu_usage + '% ON ' + y2.event_date + ' Ring buffer details: ' @@ -2479,7 +2501,12 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, AND record LIKE '%%' ORDER BY timestamp DESC) AS rb ) AS y - WHERE 100 - (y.SQLUsage + y.SystemIdle) >= 25; + WHERE 100 - (y.SQLUsage + y.SystemIdle) >= 25 + /* SystemIdle is always 0 on Linux, as described earlier. + We therefore cannot distinguish between a totally idle Linux server and + a Linux server where SQL Server is being crushed by other CPU-heavy processes. + We therefore disable this check on Linux. */ + AND @is_windows_operating_system = 1; END; /* IF @Seconds < 30 */ @@ -3593,18 +3620,24 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, END INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) - SELECT 24, 50, 'Server Performance', 'High CPU Utilization', CAST(100 - SystemIdle AS NVARCHAR(20)) + N'%. Ring buffer details: ' + CAST(record AS NVARCHAR(4000)), 100 - SystemIdle, 'https://www.brentozar.com/go/cpu' + SELECT 24, 50, 'Server Performance', 'High CPU Utilization', CAST(CpuUsage AS NVARCHAR(20)) + N'%. Ring buffer details: ' + CAST(record AS NVARCHAR(4000)), CpuUsage, 'https://www.brentozar.com/go/cpu' + FROM ( + SELECT record, + CASE WHEN @is_windows_operating_system = 1 THEN 100 - SystemIdle ELSE ProcessUtilization END AS CpuUsage FROM ( SELECT record, - record.value('(./Record/SchedulerMonitorEvent/SystemHealth/SystemIdle)[1]', 'int') AS SystemIdle + record.value('(./Record/SchedulerMonitorEvent/SystemHealth/SystemIdle)[1]', 'int') AS SystemIdle, + /* See earlier comments about SystemIdle on Linux. */ + record.value('(./Record/SchedulerMonitorEvent/SystemHealth/ProcessUtilization)[1]', 'int') AS ProcessUtilization FROM ( SELECT TOP 1 CONVERT(XML, record) AS record FROM sys.dm_os_ring_buffers WHERE ring_buffer_type = N'RING_BUFFER_SCHEDULER_MONITOR' AND record LIKE '%%' ORDER BY timestamp DESC) AS rb - ) AS y - WHERE 100 - SystemIdle >= 50; + ) AS ShreddedCpuXml + ) AS OsCpu + WHERE CpuUsage >= 50; /* Server Performance - CPU Utilization CheckID 23 */ IF (@Debug = 1) @@ -3613,17 +3646,23 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, END INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, Details, DetailsInt, URL) - SELECT 23, 250, 'Server Info', 'CPU Utilization', CAST(100 - SystemIdle AS NVARCHAR(20)) + N'%. Ring buffer details: ' + CAST(record AS NVARCHAR(4000)), 100 - SystemIdle, 'https://www.brentozar.com/go/cpu' + SELECT 23, 250, 'Server Info', 'CPU Utilization', CAST(CpuUsage AS NVARCHAR(20)) + N'%. Ring buffer details: ' + CAST(record AS NVARCHAR(4000)), CpuUsage, 'https://www.brentozar.com/go/cpu' + FROM ( + SELECT record, + CASE WHEN @is_windows_operating_system = 1 THEN 100 - SystemIdle ELSE ProcessUtilization END AS CpuUsage FROM ( SELECT record, - record.value('(./Record/SchedulerMonitorEvent/SystemHealth/SystemIdle)[1]', 'int') AS SystemIdle + record.value('(./Record/SchedulerMonitorEvent/SystemHealth/SystemIdle)[1]', 'int') AS SystemIdle, + /* See earlier comments about SystemIdle on Linux. */ + record.value('(./Record/SchedulerMonitorEvent/SystemHealth/ProcessUtilization)[1]', 'int') AS ProcessUtilization FROM ( SELECT TOP 1 CONVERT(XML, record) AS record FROM sys.dm_os_ring_buffers WHERE ring_buffer_type = N'RING_BUFFER_SCHEDULER_MONITOR' AND record LIKE '%%' ORDER BY timestamp DESC) AS rb - ) AS y; + ) AS ShreddedCpuXml + ) AS OsCpu; END; /* IF @Seconds >= 30 */ From f8c6c6d6cc07471fe2cbed91d71ece2896027d3f Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Sat, 18 Oct 2025 06:44:20 -0700 Subject: [PATCH 662/662] #3721 sp_Blitz TempDB RG Check to see if TempDB config will support RG config for SQL 2025. Closes #3721. --- Documentation/sp_Blitz_Checks_by_Priority.md | 5 +- sp_Blitz.sql | 60 ++++++++++++++++++++ 2 files changed, 63 insertions(+), 2 deletions(-) diff --git a/Documentation/sp_Blitz_Checks_by_Priority.md b/Documentation/sp_Blitz_Checks_by_Priority.md index d59eeb858..af9d6c669 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: 270. -If you want to add a new one, start at 271. +CURRENT HIGH CHECKID: 271. +If you want to add a new one, start at 272. | Priority | FindingsGroup | Finding | URL | CheckID | |----------|-----------------------------|---------------------------------------------------------|------------------------------------------------------------------------|----------| @@ -131,6 +131,7 @@ If you want to add a new one, start at 271. | 170 | File Configuration | High VLF Count | https://www.BrentOzar.com/go/vlf | 69 | | 170 | File Configuration | Multiple Log Files on One Drive | https://www.BrentOzar.com/go/manylogs | 41 | | 170 | File Configuration | System Database on C Drive | https://www.BrentOzar.com/go/drivec | 24 | +| 170 | File Configuration | TempDB Governor Config Problem | https://www.BrentOzar.com/go/tempdbrg | 271 | | 170 | File Configuration | TempDB Has >16 Data Files | https://www.BrentOzar.com/go/tempdb | 175 | | 170 | File Configuration | TempDB Only Has 1 Data File | https://www.BrentOzar.com/go/tempdb | 40 | | 170 | File Configuration | TempDB Unevenly Sized Data Files | https://www.BrentOzar.com/go/tempdb | 183 | diff --git a/sp_Blitz.sql b/sp_Blitz.sql index 102cbdc2f..f011e706c 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -6752,6 +6752,66 @@ IF @ProductVersionMajor >= 10 END; + + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 271 ) + AND EXISTS (SELECT * FROM sys.all_columns WHERE name = 'group_max_tempdb_data_percent') + AND EXISTS (SELECT * FROM sys.all_columns WHERE name = 'group_max_tempdb_data_mb') + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 271) WITH NOWAIT; + + IF EXISTS (SELECT * FROM sys.resource_governor_workload_groups + WHERE group_max_tempdb_data_percent <> 0 + AND group_max_tempdb_data_mb IS NULL) + BEGIN + DECLARE @TempDBfiles TABLE (config VARCHAR(50), data_files INT) + /* Valid configs */ + INSERT INTO @TempDBfiles + SELECT 'Fixed predictable growth' AS config, SUM(1) AS data_files + FROM master.sys.master_files + WHERE database_id = DB_ID('tempdb') + AND type = 0 /* data */ + AND max_size <> -1 /* only limited ones */ + AND growth <> 0 /* growth is set */ + HAVING SUM(1) > 0 + UNION ALL + SELECT 'Growth turned off' AS config, SUM(1) AS data_files + FROM master.sys.master_files + WHERE database_id = DB_ID('tempdb') + AND type = 0 /* data */ + AND max_size = -1 /* unlimited */ + AND growth = 0 + HAVING SUM(1) > 0; + + IF 1 <> (SELECT COUNT(*) FROM @TempDBfiles) + OR (SELECT SUM(data_files) FROM @TempDBfiles) <> + (SELECT SUM(1) + FROM master.sys.master_files + WHERE database_id = DB_ID('tempdb') + AND type = 0 /* data */) + BEGIN + INSERT INTO #BlitzResults + ( CheckID , + Priority , + DatabaseName , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT 271 AS CheckID, + 170 AS Priority, + 'tempdb', + 'File Configuration' AS FindingsGroup, + 'TempDB Governor Config Problem' AS Finding, + 'https://www.BrentOzar.com/go/tempdbrg' AS URL, + 'Resource Governor is configured to cap TempDB usage by percent, but the TempDB file configuration will not allow that to take effect.' AS details + END + END + END + IF @CheckUserDatabaseObjects = 1 BEGIN